📖 MALL:商品编辑 => 将 detail 还是使用 form 复用,减少维护成本

This commit is contained in:
YunaiV 2024-01-11 00:25:39 +08:00
parent c0905fdc8e
commit 2c76d3aeee
8 changed files with 36 additions and 234 deletions

View File

@ -33,14 +33,14 @@ export interface GiveCouponTemplate {
export interface Spu { export interface Spu {
id?: number id?: number
name?: string // 商品名称 name?: string // 商品名称
categoryId?: number | undefined // 商品分类 categoryId?: number // 商品分类
keyword?: string // 关键字 keyword?: string // 关键字
unit?: number | undefined // 单位 unit?: number | undefined // 单位
picUrl?: string // 商品封面图 picUrl?: string // 商品封面图
sliderPicUrls?: string[] // 商品轮播图 sliderPicUrls?: string[] // 商品轮播图
introduction?: string // 商品简介 introduction?: string // 商品简介
deliveryTemplateId?: number | undefined // 运费模版 deliveryTemplateId?: number | undefined // 运费模版
brandId?: number | undefined // 商品品牌编号 brandId?: number // 商品品牌编号
specType?: boolean // 商品规格 specType?: boolean // 商品规格
subCommissionType?: boolean // 分销类型 subCommissionType?: boolean // 分销类型
skus?: Sku[] // sku数组 skus?: Sku[] // sku数组

View File

@ -1,13 +1,5 @@
<template> <template>
<!-- 情况一添加/修改 --> <el-form ref="formRef" :model="formData" :rules="rules" label-width="120px" :disabled="isDetail">
<el-form
v-if="!isDetail"
ref="productSpuBasicInfoRef"
:model="formData"
:rules="rules"
label-width="120px"
>
<!-- TODO 芋艿宽度 -->
<el-form-item label="商品名称" prop="name"> <el-form-item label="商品名称" prop="name">
<el-input <el-input
v-model="formData.name" v-model="formData.name"
@ -17,6 +9,7 @@
maxlength="64" maxlength="64"
:show-word-limit="true" :show-word-limit="true"
:clearable="true" :clearable="true"
class="w-80!"
/> />
</el-form-item> </el-form-item>
<el-form-item label="商品分类" prop="categoryId"> <el-form-item label="商品分类" prop="categoryId">
@ -24,21 +17,35 @@
v-model="formData.categoryId" v-model="formData.categoryId"
:options="categoryList" :options="categoryList"
:props="defaultProps" :props="defaultProps"
class="w-1/1" class="w-80"
clearable clearable
placeholder="请选择商品分类" placeholder="请选择商品分类"
filterable filterable
/> />
</el-form-item> </el-form-item>
<el-form-item label="商品品牌" prop="brandId">
<el-select v-model="formData.brandId" placeholder="请选择商品品牌" class="w-80">
<el-option
v-for="item in brandList"
:key="item.id"
:label="item.name"
:value="item.id as number"
/>
</el-select>
</el-form-item>
<el-form-item label="商品关键字" prop="keyword"> <el-form-item label="商品关键字" prop="keyword">
<el-input v-model="formData.keyword" placeholder="请输入商品关键字" /> <el-input v-model="formData.keyword" placeholder="请输入商品关键字" class="w-80!" />
</el-form-item> </el-form-item>
<el-form-item label="商品简介" prop="introduction"> <el-form-item label="商品简介" prop="introduction">
<el-input <el-input
v-model="formData.introduction" v-model="formData.introduction"
:rows="3" placeholder="请输入商品名称"
placeholder="请输入商品简介"
type="textarea" type="textarea"
:autosize="{ minRows: 2, maxRows: 2 }"
maxlength="128"
:show-word-limit="true"
:clearable="true"
class="w-80!"
/> />
</el-form-item> </el-form-item>
<el-form-item label="商品封面图" prop="picUrl"> <el-form-item label="商品封面图" prop="picUrl">
@ -47,67 +54,20 @@
<el-form-item label="商品轮播图" prop="sliderPicUrls"> <el-form-item label="商品轮播图" prop="sliderPicUrls">
<UploadImgs v-model:modelValue="formData.sliderPicUrls" /> <UploadImgs v-model:modelValue="formData.sliderPicUrls" />
</el-form-item> </el-form-item>
<el-form-item label="品牌" prop="brandId">
<el-select v-model="formData.brandId" placeholder="请选择">
<el-option v-for="item in brandList" :key="item.id" :label="item.name" :value="item.id" />
</el-select>
</el-form-item>
</el-form> </el-form>
<!-- 情况二详情 -->
<Descriptions v-if="isDetail" :data="formData" :schema="allSchemas.detailSchema">
<template #categoryId="{ row }"> {{ formatCategoryName(row.categoryId) }}</template>
<template #brandId="{ row }">
{{ brandList.find((item) => item.id === row.brandId)?.name }}
</template>
<template #picUrl="{ row }">
<el-image :src="row.picUrl" class="h-60px w-60px" @click="imagePreview(row.picUrl)" />
</template>
<template #sliderPicUrls="{ row }">
<el-image
v-for="(item, index) in row.sliderPicUrls"
:key="index"
:src="item.url"
class="mr-10px h-60px w-60px"
@click="imagePreview(row.sliderPicUrls)"
/>
</template>
</Descriptions>
</template> </template>
<script lang="ts" setup> <script lang="ts" setup>
import { PropType } from 'vue' import { PropType } from 'vue'
import { isArray } from '@/utils/is'
import { copyValueToTarget } from '@/utils' import { copyValueToTarget } from '@/utils'
import { propTypes } from '@/utils/propTypes' import { propTypes } from '@/utils/propTypes'
import { defaultProps, handleTree, treeToString } from '@/utils/tree' import { defaultProps, handleTree, treeToString } from '@/utils/tree'
import { createImageViewer } from '@/components/ImageViewer'
import { basicInfoSchema } from './spu.data'
import type { Spu } from '@/api/mall/product/spu' import type { Spu } from '@/api/mall/product/spu'
import * as ProductCategoryApi from '@/api/mall/product/category' import * as ProductCategoryApi from '@/api/mall/product/category'
import * as ProductBrandApi from '@/api/mall/product/brand' import * as ProductBrandApi from '@/api/mall/product/brand'
import * as ExpressTemplateApi from '@/api/mall/trade/delivery/expressTemplate' import { BrandVO } from '@/api/mall/product/brand'
defineOptions({ name: 'ProductSpuBasicInfoForm' }) defineOptions({ name: 'ProductSpuBasicInfoForm' })
// ====== ======
const { allSchemas } = useCrudSchemas(basicInfoSchema)
/** 商品图预览 */
const imagePreview = (args) => {
const urlList = []
if (isArray(args)) {
args.forEach((item) => {
urlList.push(item.url)
})
} else {
urlList.push(args)
}
createImageViewer({
urlList
})
}
// ====== end ======
const message = useMessage() // const message = useMessage() //
const props = defineProps({ const props = defineProps({
@ -118,15 +78,16 @@ const props = defineProps({
activeName: propTypes.string.def(''), activeName: propTypes.string.def(''),
isDetail: propTypes.bool.def(false) // isDetail: propTypes.bool.def(false) //
}) })
const productSpuBasicInfoRef = ref() // Ref
const formRef = ref() // Ref
const formData = reactive<Spu>({ const formData = reactive<Spu>({
name: '', // name: '', //
categoryId: null, // categoryId: undefined, //
keyword: '', // keyword: '', //
picUrl: '', // picUrl: '', //
sliderPicUrls: [], // sliderPicUrls: [], //
introduction: '', // introduction: '', //
brandId: null // brandId: undefined //
}) })
const rules = reactive({ const rules = reactive({
name: [required], name: [required],
@ -163,8 +124,8 @@ watch(
const emit = defineEmits(['update:activeName']) const emit = defineEmits(['update:activeName'])
const validate = async () => { const validate = async () => {
// //
if (!productSpuBasicInfoRef) return if (!formRef) return
return await unref(productSpuBasicInfoRef).validate((valid) => { return await unref(formRef).validate((valid) => {
if (!valid) { if (!valid) {
message.warning('商品信息未完善!!') message.warning('商品信息未完善!!')
emit('update:activeName', 'basicInfo') emit('update:activeName', 'basicInfo')
@ -184,7 +145,7 @@ const formatCategoryName = (categoryId: number) => {
return treeToString(categoryList.value, categoryId) return treeToString(categoryList.value, categoryId)
} }
const brandList = ref([]) // const brandList = ref<BrandVO[]>([]) //
onMounted(async () => { onMounted(async () => {
// //
const data = await ProductCategoryApi.getCategoryList({}) const data = await ProductCategoryApi.getCategoryList({})

View File

@ -1,11 +1,10 @@
<template> <template>
<!-- 情况一添加/修改 -->
<el-form <el-form
v-if="!isDetail"
ref="productSpuBasicInfoRef" ref="productSpuBasicInfoRef"
:model="formData" :model="formData"
:rules="rules" :rules="rules"
label-width="120px" label-width="120px"
:disabled="isDetail"
> >
<!-- TODO 芋艿宽度 --> <!-- TODO 芋艿宽度 -->
<!-- TODO 芋艿这里要挪出去 --> <!-- TODO 芋艿这里要挪出去 -->
@ -20,29 +19,16 @@
</el-select> </el-select>
</el-form-item> </el-form-item>
</el-form> </el-form>
<!-- 情况二详情 -->
<Descriptions v-if="isDetail" :data="formData" :schema="allSchemas.detailSchema">
<template #deliveryTemplateId="{ row }">
{{ deliveryTemplateList.find((item) => item.id === row.deliveryTemplateId)?.name }}
</template>
</Descriptions>
</template> </template>
<script lang="ts" setup> <script lang="ts" setup>
import { PropType } from 'vue' import { PropType } from 'vue'
import { copyValueToTarget } from '@/utils' import { copyValueToTarget } from '@/utils'
import { propTypes } from '@/utils/propTypes' import { propTypes } from '@/utils/propTypes'
import { basicInfoSchema } from './spu.data'
import type { Spu } from '@/api/mall/product/spu' import type { Spu } from '@/api/mall/product/spu'
import * as ExpressTemplateApi from '@/api/mall/trade/delivery/expressTemplate' import * as ExpressTemplateApi from '@/api/mall/trade/delivery/expressTemplate'
defineOptions({ name: 'ProductSpuBasicInfoForm' }) defineOptions({ name: 'ProductSpuBasicInfoForm' })
// ====== ======
const { allSchemas } = useCrudSchemas(basicInfoSchema)
// ====== end ======
const message = useMessage() // const message = useMessage() //
const props = defineProps({ const props = defineProps({
@ -55,7 +41,7 @@ const props = defineProps({
}) })
const productSpuBasicInfoRef = ref() // Ref const productSpuBasicInfoRef = ref() // Ref
const formData = reactive<Spu>({ const formData = reactive<Spu>({
deliveryTemplateId: null // deliveryTemplateId: undefined //
}) })
const rules = reactive({ const rules = reactive({
deliveryTemplateId: [required] deliveryTemplateId: [required]

View File

@ -1,30 +1,16 @@
<template> <template>
<!-- 情况一添加/修改 -->
<el-form <el-form
v-if="!isDetail"
ref="descriptionFormRef" ref="descriptionFormRef"
:model="formData" :model="formData"
:rules="rules" :rules="rules"
label-width="120px" label-width="120px"
:disabled="isDetail"
> >
<!--富文本编辑器组件--> <!--富文本编辑器组件-->
<el-form-item label="商品详情" prop="description"> <el-form-item label="商品详情" prop="description">
<Editor v-model:modelValue="formData.description" /> <Editor v-model:modelValue="formData.description" />
</el-form-item> </el-form-item>
</el-form> </el-form>
<!-- 情况二详情 -->
<Descriptions
v-if="isDetail"
:data="formData"
:schema="allSchemas.detailSchema"
class="descriptionFormDescriptions"
>
<!-- 展示 HTML 内容 -->
<template #description="{ row }">
<div v-dompurify-html="row.description" style="width: 600px"></div>
</template>
</Descriptions>
</template> </template>
<script lang="ts" setup> <script lang="ts" setup>
import type { Spu } from '@/api/mall/product/spu' import type { Spu } from '@/api/mall/product/spu'
@ -32,13 +18,11 @@ import { Editor } from '@/components/Editor'
import { PropType } from 'vue' import { PropType } from 'vue'
import { propTypes } from '@/utils/propTypes' import { propTypes } from '@/utils/propTypes'
import { copyValueToTarget } from '@/utils' import { copyValueToTarget } from '@/utils'
import { descriptionSchema } from './spu.data'
defineOptions({ name: 'DescriptionForm' }) defineOptions({ name: 'DescriptionForm' })
const message = useMessage() // const message = useMessage() //
const { allSchemas } = useCrudSchemas(descriptionSchema)
const props = defineProps({ const props = defineProps({
propFormData: { propFormData: {
type: Object as PropType<Spu>, type: Object as PropType<Spu>,

View File

@ -1,11 +1,10 @@
<template> <template>
<!-- 情况一添加/修改 -->
<el-form <el-form
v-if="!isDetail"
ref="otherSettingsFormRef" ref="otherSettingsFormRef"
:model="formData" :model="formData"
:rules="rules" :rules="rules"
label-width="120px" label-width="120px"
:disabled="isDetail"
> >
<el-form-item label="商品排序" prop="sort"> <el-form-item label="商品排序" prop="sort">
<el-input-number v-model="formData.sort" :min="0" /> <el-input-number v-model="formData.sort" :min="0" />
@ -17,23 +16,17 @@
<el-input-number v-model="formData.virtualSalesCount" :min="0" placeholder="请输入虚拟销量" /> <el-input-number v-model="formData.virtualSalesCount" :min="0" placeholder="请输入虚拟销量" />
</el-form-item> </el-form-item>
</el-form> </el-form>
<!-- 情况二详情 -->
<Descriptions v-if="isDetail" :data="formData" :schema="allSchemas.detailSchema" />
</template> </template>
<script lang="ts" setup> <script lang="ts" setup>
import type { Spu } from '@/api/mall/product/spu' import type { Spu } from '@/api/mall/product/spu'
import { PropType } from 'vue' import { PropType } from 'vue'
import { propTypes } from '@/utils/propTypes' import { propTypes } from '@/utils/propTypes'
import { copyValueToTarget } from '@/utils' import { copyValueToTarget } from '@/utils'
import { otherSettingsSchema } from './spu.data'
defineOptions({ name: 'OtherSettingsForm' }) defineOptions({ name: 'OtherSettingsForm' })
const message = useMessage() // const message = useMessage() //
const { allSchemas } = useCrudSchemas(otherSettingsSchema)
const props = defineProps({ const props = defineProps({
propFormData: { propFormData: {
type: Object as PropType<Spu>, type: Object as PropType<Spu>,

View File

@ -1,11 +1,11 @@
<template> <template>
<!-- 情况一添加/修改 --> <!-- 情况一添加/修改 -->
<el-form <el-form
v-if="!isDetail"
ref="productSpuSkuRef" ref="productSpuSkuRef"
:model="formData" :model="formData"
:rules="rules" :rules="rules"
label-width="120px" label-width="120px"
:disabled="isDetail"
> >
<el-row> <el-row>
<el-col :span="12"> <el-col :span="12">
@ -55,46 +55,16 @@
</el-row> </el-row>
</el-form> </el-form>
<!-- 情况二详情 -->
<Descriptions v-if="isDetail" :data="formData" :schema="allSchemas.detailSchema">
<template #specType="{ row }">
{{ row.specType ? '多规格' : '单规格' }}
</template>
<template #subCommissionType="{ row }">
{{ row.subCommissionType ? '单独设置' : '默认设置' }}
</template>
<template #sliderPicUrls="{ row }">
<el-image
v-for="(item, index) in row.sliderPicUrls"
:key="index"
:src="item.url"
class="mr-10px h-60px w-60px"
@click="imagePreview(row.sliderPicUrls)"
/>
</template>
<template #skus>
<SkuList
ref="skuDetailListRef"
:is-detail="isDetail"
:prop-form-data="formData"
:propertyList="propertyList"
/>
</template>
</Descriptions>
<!-- 商品属性添加 Form 表单 --> <!-- 商品属性添加 Form 表单 -->
<ProductPropertyAddForm ref="attributesAddFormRef" :propertyList="propertyList" /> <ProductPropertyAddForm ref="attributesAddFormRef" :propertyList="propertyList" />
</template> </template>
<script lang="ts" setup> <script lang="ts" setup>
import { PropType } from 'vue' import { PropType } from 'vue'
import { isArray } from '@/utils/is'
import { copyValueToTarget } from '@/utils' import { copyValueToTarget } from '@/utils'
import { propTypes } from '@/utils/propTypes' import { propTypes } from '@/utils/propTypes'
import { createImageViewer } from '@/components/ImageViewer'
import { getPropertyList, RuleConfig, SkuList } from '@/views/mall/product/spu/components/index.ts' import { getPropertyList, RuleConfig, SkuList } from '@/views/mall/product/spu/components/index.ts'
import ProductAttributes from './ProductAttributes.vue' import ProductAttributes from './ProductAttributes.vue'
import ProductPropertyAddForm from './ProductPropertyAddForm.vue' import ProductPropertyAddForm from './ProductPropertyAddForm.vue'
import { basicInfoSchema } from './spu.data'
import type { Spu } from '@/api/mall/product/spu' import type { Spu } from '@/api/mall/product/spu'
defineOptions({ name: 'ProductSpuSkuForm' }) defineOptions({ name: 'ProductSpuSkuForm' })
@ -123,25 +93,6 @@ const ruleConfig: RuleConfig[] = [
} }
] ]
// ====== ======
const { allSchemas } = useCrudSchemas(basicInfoSchema)
/** 商品图预览 */
const imagePreview = (args) => {
const urlList = []
if (isArray(args)) {
args.forEach((item) => {
urlList.push(item.url)
})
} else {
urlList.push(args)
}
createImageViewer({
urlList
})
}
// ====== end ======
const message = useMessage() // const message = useMessage() //
const props = defineProps({ const props = defineProps({

View File

@ -33,8 +33,7 @@
:propFormData="formData" :propFormData="formData"
/> />
</el-tab-pane> </el-tab-pane>
<!-- TODO 芋艿物流设置 --> <el-tab-pane label="其它设置" name="otherSettings">
<el-tab-pane label="其他设置" name="otherSettings">
<OtherSettingsForm <OtherSettingsForm
ref="otherSettingsRef" ref="otherSettingsRef"
v-model:activeName="activeName" v-model:activeName="activeName"

View File

@ -1,72 +0,0 @@
import { CrudSchema } from '@/hooks/web/useCrudSchemas'
export const basicInfoSchema = reactive<CrudSchema[]>([
{
label: '商品名称',
field: 'name'
},
{
label: '关键字',
field: 'keyword'
},
{
label: '商品简介',
field: 'introduction'
},
{
label: '商品分类',
field: 'categoryId'
},
{
label: '商品品牌',
field: 'brandId'
},
{
label: '商品封面图',
field: 'picUrl'
},
{
label: '商品轮播图',
field: 'sliderPicUrls'
},
{
label: '商品视频',
field: 'videoUrl'
},
{
label: '规格类型',
field: 'specType'
},
{
label: '分销类型',
field: 'subCommissionType'
},
{
label: '物流模版',
field: 'deliveryTemplateId'
},
{
label: '商品属性列表',
field: 'skus'
}
])
export const descriptionSchema = reactive<CrudSchema[]>([
{
label: '商品详情',
field: 'description'
}
])
export const otherSettingsSchema = reactive<CrudSchema[]>([
{
label: '商品排序',
field: 'sort'
},
{
label: '赠送积分',
field: 'giveIntegral'
},
{
label: '虚拟销量',
field: 'virtualSalesCount'
}
])