📖 MALL:商品编辑的简化

This commit is contained in:
YunaiV 2024-01-10 23:50:19 +08:00
parent a5d6d18b6a
commit f1c858b9af
8 changed files with 330 additions and 455 deletions

View File

@ -57,7 +57,6 @@
"pinia": "^2.1.7", "pinia": "^2.1.7",
"qrcode": "^1.5.3", "qrcode": "^1.5.3",
"qs": "^6.11.2", "qs": "^6.11.2",
"sortablejs": "^1.15.0",
"steady-xml": "^0.1.0", "steady-xml": "^0.1.0",
"url": "^0.11.3", "url": "^0.11.3",
"video.js": "^7.21.5", "video.js": "^7.21.5",
@ -81,7 +80,6 @@
"@types/nprogress": "^0.2.3", "@types/nprogress": "^0.2.3",
"@types/qrcode": "^1.5.5", "@types/qrcode": "^1.5.5",
"@types/qs": "^6.9.10", "@types/qs": "^6.9.10",
"@types/sortablejs": "^1.15.5",
"@typescript-eslint/eslint-plugin": "^6.11.0", "@typescript-eslint/eslint-plugin": "^6.11.0",
"@typescript-eslint/parser": "^6.11.0", "@typescript-eslint/parser": "^6.11.0",
"@unocss/transformer-variant-group": "^0.57.4", "@unocss/transformer-variant-group": "^0.57.4",

View File

@ -48,11 +48,6 @@ export interface Spu {
sort?: number // 商品排序 sort?: number // 商品排序
giveIntegral?: number // 赠送积分 giveIntegral?: number // 赠送积分
virtualSalesCount?: number // 虚拟销量 virtualSalesCount?: number // 虚拟销量
recommendHot?: boolean // 是否热卖
recommendBenefit?: boolean // 是否优惠
recommendBest?: boolean // 是否精品
recommendNew?: boolean // 是否新品
recommendGood?: boolean // 是否优品
price?: number // 商品价格 price?: number // 商品价格
salesCount?: number // 商品销量 salesCount?: number // 商品销量
marketPrice?: number // 市场价 marketPrice?: number // 市场价
@ -60,7 +55,6 @@ export interface Spu {
stock?: number // 商品库存 stock?: number // 商品库存
createTime?: Date // 商品创建时间 createTime?: Date // 商品创建时间
status?: number // 商品状态 status?: number // 商品状态
activityOrders: number[] // 活动排序
} }
// 获得 Spu 列表 // 获得 Spu 列表

View File

@ -1,66 +0,0 @@
<template>
<div ref="elTagWrappingRef">
<template v-if="activityOrders && activityOrders.length > 0">
<el-tag
v-for="activityType in activityOrders"
:key="activityType"
:type="promotionTypes.find((item) => item.value === activityType)?.colorType"
class="mr-[10px]"
>
{{ promotionTypes.find((item) => item.value === activityType)?.label }}
</el-tag>
</template>
<template v-else>
<el-tag
v-for="type in promotionTypes"
:key="type.value as number"
:type="type.colorType"
class="mr-[10px]"
>
{{ type.label }}
</el-tag>
</template>
</div>
</template>
<script lang="ts" setup>
import Sortable from 'sortablejs'
import type { DictDataType } from '@/utils/dict'
defineOptions({ name: 'ActivityOrdersSort' })
const props = defineProps<{
promotionTypes: DictDataType[]
activityOrders: number[]
}>()
const emit = defineEmits<{
(e: 'update:activityOrders', v: number[])
}>()
const elTagWrappingRef = ref() // elTag Ref
const initSortable = () => {
new Sortable(elTagWrappingRef.value, {
swapThreshold: 1,
animation: 150,
onEnd: (el) => {
const innerText = el.to.innerText
//
const activityOrder = innerText.split('\n')
//
const sortedActivityOrder = activityOrder.map((activityName) => {
return props.promotionTypes.find((item) => item.label === activityName)?.value
})
emit('update:activityOrders', sortedActivityOrder as number[])
}
})
}
onMounted(async () => {
await nextTick()
//
if (props.activityOrders && props.activityOrders.length === 0) {
emit(
'update:activityOrders',
props.promotionTypes.map((item) => item.value as number)
)
}
initSortable()
})
</script>

View File

@ -7,13 +7,18 @@
:rules="rules" :rules="rules"
label-width="120px" label-width="120px"
> >
<el-row> <!-- TODO 芋艿宽度 -->
<el-col :span="12">
<el-form-item label="商品名称" prop="name"> <el-form-item label="商品名称" prop="name">
<el-input v-model="formData.name" placeholder="请输入商品名称" /> <el-input
v-model="formData.name"
placeholder="请输入商品名称"
type="textarea"
:autosize="{ minRows: 2, maxRows: 2 }"
maxlength="64"
:show-word-limit="true"
:clearable="true"
/>
</el-form-item> </el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="商品分类" prop="categoryId"> <el-form-item label="商品分类" prop="categoryId">
<el-cascader <el-cascader
v-model="formData.categoryId" v-model="formData.categoryId"
@ -25,25 +30,9 @@
filterable filterable
/> />
</el-form-item> </el-form-item>
</el-col>
<el-col :span="12">
<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="请输入商品关键字" />
</el-form-item> </el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="单位" prop="unit">
<el-select v-model="formData.unit" class="w-1/1" placeholder="请选择单位">
<el-option
v-for="dict in getIntDictOptions(DICT_TYPE.PRODUCT_UNIT)"
:key="dict.value"
:label="dict.label"
:value="dict.value"
/>
</el-select>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="商品简介" prop="introduction"> <el-form-item label="商品简介" prop="introduction">
<el-input <el-input
v-model="formData.introduction" v-model="formData.introduction"
@ -52,18 +41,13 @@
type="textarea" type="textarea"
/> />
</el-form-item> </el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="商品封面图" prop="picUrl"> <el-form-item label="商品封面图" prop="picUrl">
<UploadImg v-model="formData.picUrl" height="80px" /> <UploadImg v-model="formData.picUrl" height="80px" />
</el-form-item> </el-form-item>
</el-col>
<el-col :span="24">
<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-col> <!-- TODO 芋艿这里要挪出去 -->
<el-col :span="12">
<el-form-item label="运费模板" prop="deliveryTemplateId"> <el-form-item label="运费模板" prop="deliveryTemplateId">
<el-select v-model="formData.deliveryTemplateId" placeholder="请选择"> <el-select v-model="formData.deliveryTemplateId" placeholder="请选择">
<el-option <el-option
@ -74,64 +58,11 @@
/> />
</el-select> </el-select>
</el-form-item> </el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="品牌" prop="brandId"> <el-form-item label="品牌" prop="brandId">
<el-select v-model="formData.brandId" placeholder="请选择"> <el-select v-model="formData.brandId" placeholder="请选择">
<el-option <el-option v-for="item in brandList" :key="item.id" :label="item.name" :value="item.id" />
v-for="item in brandList"
:key="item.id"
:label="item.name"
:value="item.id"
/>
</el-select> </el-select>
</el-form-item> </el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="商品规格" props="specType">
<el-radio-group v-model="formData.specType" @change="onChangeSpec">
<el-radio :label="false" class="radio">单规格</el-radio>
<el-radio :label="true">多规格</el-radio>
</el-radio-group>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="分销类型" props="subCommissionType">
<el-radio-group v-model="formData.subCommissionType" @change="changeSubCommissionType">
<el-radio :label="false">默认设置</el-radio>
<el-radio :label="true" class="radio">单独设置</el-radio>
</el-radio-group>
</el-form-item>
</el-col>
<!-- 多规格添加-->
<el-col :span="24">
<el-form-item v-if="!formData.specType">
<SkuList
ref="skuListRef"
:prop-form-data="formData"
:propertyList="propertyList"
:rule-config="ruleConfig"
/>
</el-form-item>
<el-form-item v-if="formData.specType" label="商品属性">
<el-button class="mb-10px mr-15px" @click="attributesAddFormRef.open">添加属性</el-button>
<ProductAttributes :propertyList="propertyList" @success="generateSkus" />
</el-form-item>
<template v-if="formData.specType && propertyList.length > 0">
<el-form-item label="批量设置">
<SkuList :is-batch="true" :prop-form-data="formData" :propertyList="propertyList" />
</el-form-item>
<el-form-item label="属性列表">
<SkuList
ref="skuListRef"
:prop-form-data="formData"
:propertyList="propertyList"
:rule-config="ruleConfig"
/>
</el-form-item>
</template>
</el-col>
</el-row>
</el-form> </el-form>
<!-- 情况二详情 --> <!-- 情况二详情 -->
@ -161,30 +92,15 @@
@click="imagePreview(row.sliderPicUrls)" @click="imagePreview(row.sliderPicUrls)"
/> />
</template> </template>
<template #skus>
<SkuList
ref="skuDetailListRef"
:is-detail="isDetail"
:prop-form-data="formData"
:propertyList="propertyList"
/>
</template>
</Descriptions> </Descriptions>
<!-- 商品属性添加 Form 表单 -->
<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 { isArray } from '@/utils/is'
import { copyValueToTarget } from '@/utils' import { copyValueToTarget } from '@/utils'
import { propTypes } from '@/utils/propTypes' import { propTypes } from '@/utils/propTypes'
import { checkSelectedNode, defaultProps, handleTree, treeToString } from '@/utils/tree' import { defaultProps, handleTree, treeToString } from '@/utils/tree'
import { createImageViewer } from '@/components/ImageViewer' import { createImageViewer } from '@/components/ImageViewer'
import { DICT_TYPE, getIntDictOptions } from '@/utils/dict'
import { getPropertyList, RuleConfig, SkuList } from '@/views/mall/product/spu/components/index.ts'
import ProductAttributes from './ProductAttributes.vue'
import ProductPropertyAddForm from './ProductPropertyAddForm.vue'
import { basicInfoSchema } from './spu.data' 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'
@ -193,30 +109,6 @@ import * as ExpressTemplateApi from '@/api/mall/trade/delivery/expressTemplate'
defineOptions({ name: 'ProductSpuBasicInfoForm' }) defineOptions({ name: 'ProductSpuBasicInfoForm' })
// sku
const ruleConfig: RuleConfig[] = [
{
name: 'stock',
rule: (arg) => arg >= 0,
message: '商品库存必须大于等于 1 '
},
{
name: 'price',
rule: (arg) => arg >= 0.01,
message: '商品销售价格必须大于等于 0.01 元!!!'
},
{
name: 'marketPrice',
rule: (arg) => arg >= 0.01,
message: '商品市场价格必须大于等于 0.01 元!!!'
},
{
name: 'costPrice',
rule: (arg) => arg >= 0.01,
message: '商品成本价格必须大于等于 0.00 元!!!'
}
]
// ====== ====== // ====== ======
const { allSchemas } = useCrudSchemas(basicInfoSchema) const { allSchemas } = useCrudSchemas(basicInfoSchema)
/** 商品图预览 */ /** 商品图预览 */
@ -246,40 +138,26 @@ const props = defineProps({
activeName: propTypes.string.def(''), activeName: propTypes.string.def(''),
isDetail: propTypes.bool.def(false) // isDetail: propTypes.bool.def(false) //
}) })
const attributesAddFormRef = ref() //
const productSpuBasicInfoRef = ref() // Ref const productSpuBasicInfoRef = ref() // Ref
const propertyList = ref([]) //
const skuListRef = ref() // Ref
/** 调用 SkuList generateTableData 方法*/
const generateSkus = (propertyList) => {
skuListRef.value.generateTableData(propertyList)
}
const formData = reactive<Spu>({ const formData = reactive<Spu>({
name: '', // name: '', //
categoryId: null, // categoryId: null, //
keyword: '', // keyword: '', //
unit: null, //
picUrl: '', // picUrl: '', //
sliderPicUrls: [], // sliderPicUrls: [], //
introduction: '', // introduction: '', //
deliveryTemplateId: null, // deliveryTemplateId: null, //
brandId: null, // brandId: null //
specType: false, //
subCommissionType: false, //
skus: []
}) })
const rules = reactive({ const rules = reactive({
name: [required], name: [required],
categoryId: [required], categoryId: [required],
keyword: [required], keyword: [required],
unit: [required],
introduction: [required], introduction: [required],
picUrl: [required], picUrl: [required],
sliderPicUrls: [required], sliderPicUrls: [required],
deliveryTemplateId: [required], deliveryTemplateId: [required],
brandId: [required], brandId: [required]
specType: [required],
subCommissionType: [required]
}) })
/** /**
@ -295,7 +173,6 @@ watch(
formData.sliderPicUrls = data['sliderPicUrls']?.map((item) => ({ formData.sliderPicUrls = data['sliderPicUrls']?.map((item) => ({
url: item url: item
})) }))
propertyList.value = getPropertyList(data)
}, },
{ {
immediate: true immediate: true
@ -307,8 +184,6 @@ watch(
*/ */
const emit = defineEmits(['update:activeName']) const emit = defineEmits(['update:activeName'])
const validate = async () => { const validate = async () => {
// sku
skuListRef.value.validateSku()
// //
if (!productSpuBasicInfoRef) return if (!productSpuBasicInfoRef) return
return await unref(productSpuBasicInfoRef).validate((valid) => { return await unref(productSpuBasicInfoRef).validate((valid) => {
@ -325,39 +200,9 @@ const validate = async () => {
} }
defineExpose({ validate }) defineExpose({ validate })
/** 分销类型 */
const changeSubCommissionType = () => {
//
for (const item of formData.skus) {
item.firstBrokeragePrice = 0
item.secondBrokeragePrice = 0
}
}
/** 选择规格 */
const onChangeSpec = () => {
//
propertyList.value = []
// sku
formData.skus = [
{
price: 0,
marketPrice: 0,
costPrice: 0,
barCode: '',
picUrl: '',
stock: 0,
weight: 0,
volume: 0,
firstBrokeragePrice: 0,
secondBrokeragePrice: 0
}
]
}
const categoryList = ref([]) //
/** 获取分类的节点的完整结构 */ /** 获取分类的节点的完整结构 */
const formatCategoryName = (categoryId) => { const categoryList = ref<any[]>([]) //
const formatCategoryName = (categoryId: number) => {
return treeToString(categoryList.value, categoryId) return treeToString(categoryList.value, categoryId)
} }

View File

@ -7,78 +7,19 @@
:rules="rules" :rules="rules"
label-width="120px" label-width="120px"
> >
<el-row>
<el-col :span="24">
<el-row :gutter="20">
<el-col :span="8">
<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" />
</el-form-item> </el-form-item>
</el-col>
<el-col :span="8">
<el-form-item label="赠送积分" prop="giveIntegral"> <el-form-item label="赠送积分" prop="giveIntegral">
<el-input-number v-model="formData.giveIntegral" :min="0" /> <el-input-number v-model="formData.giveIntegral" :min="0" />
</el-form-item> </el-form-item>
</el-col>
<el-col :span="8">
<el-form-item label="虚拟销量" prop="virtualSalesCount"> <el-form-item label="虚拟销量" prop="virtualSalesCount">
<el-input-number <el-input-number v-model="formData.virtualSalesCount" :min="0" placeholder="请输入虚拟销量" />
v-model="formData.virtualSalesCount"
:min="0"
placeholder="请输入虚拟销量"
/>
</el-form-item> </el-form-item>
</el-col>
</el-row>
</el-col>
<el-col :span="24">
<el-form-item label="商品推荐">
<el-checkbox-group v-model="checkboxGroup" @change="onChangeGroup">
<el-checkbox v-for="(item, index) in recommendOptions" :key="index" :label="item.value">
{{ item.name }}
</el-checkbox>
</el-checkbox-group>
</el-form-item>
</el-col>
<el-col :span="24">
<el-form-item label="活动优先级">
<ActivityOrdersSort
v-model:activity-orders="formData.activityOrders"
:promotion-types="promotionTypes"
/>
</el-form-item>
</el-col>
</el-row>
</el-form> </el-form>
<!-- 情况二详情 --> <!-- 情况二详情 -->
<Descriptions v-if="isDetail" :data="formData" :schema="allSchemas.detailSchema"> <Descriptions v-if="isDetail" :data="formData" :schema="allSchemas.detailSchema" />
<template #recommendHot="{ row }">
<dict-tag :type="DICT_TYPE.INFRA_BOOLEAN_STRING" :value="row.recommendHot" />
</template>
<template #recommendBenefit="{ row }">
<dict-tag :type="DICT_TYPE.INFRA_BOOLEAN_STRING" :value="row.recommendBenefit" />
</template>
<template #recommendBest="{ row }">
<dict-tag :type="DICT_TYPE.INFRA_BOOLEAN_STRING" :value="row.recommendBest" />
</template>
<template #recommendNew="{ row }">
<dict-tag :type="DICT_TYPE.INFRA_BOOLEAN_STRING" :value="row.recommendNew" />
</template>
<template #recommendGood="{ row }">
<dict-tag :type="DICT_TYPE.INFRA_BOOLEAN_STRING" :value="row.recommendGood" />
</template>
<template #activityOrders="{ row }">
<el-tag
v-for="activityType in row.activityOrders"
:key="activityType"
:type="promotionTypes.find((item) => item.value === activityType)?.colorType"
class="mr-[10px]"
>
{{ promotionTypes.find((item) => item.value === activityType)?.label }}
</el-tag>
</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'
@ -86,8 +27,6 @@ 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' import { otherSettingsSchema } from './spu.data'
import { DICT_TYPE, DictDataType } from '@/utils/dict'
import ActivityOrdersSort from './ActivityOrdersSort.vue'
defineOptions({ name: 'OtherSettingsForm' }) defineOptions({ name: 'OtherSettingsForm' })
@ -104,44 +43,12 @@ const props = defineProps({
isDetail: propTypes.bool.def(false) // isDetail: propTypes.bool.def(false) //
}) })
// TODO @puhui999 promotion_type_enum
//
const promotionTypes = ref<DictDataType[]>([
{
dictType: 'promotionTypes',
label: '秒杀',
value: 1,
colorType: 'warning',
cssClass: ''
},
{
dictType: 'promotionTypes',
label: '砍价',
value: 2,
colorType: 'warning',
cssClass: ''
},
{
dictType: 'promotionTypes',
label: '拼团',
value: 3,
colorType: 'warning',
cssClass: ''
}
])
const otherSettingsFormRef = ref() // Ref const otherSettingsFormRef = ref() // Ref
// //
const formData = ref<Spu>({ const formData = ref<Spu>({
sort: 1, // sort: 1, //
giveIntegral: 1, // giveIntegral: 1, //
virtualSalesCount: 1, // virtualSalesCount: 1 //
recommendHot: false, //
recommendBenefit: false, //
recommendBest: false, //
recommendNew: false, //
recommendGood: false, //
activityOrders: [] //
}) })
// //
const rules = reactive({ const rules = reactive({
@ -149,21 +56,6 @@ const rules = reactive({
giveIntegral: [required], giveIntegral: [required],
virtualSalesCount: [required] virtualSalesCount: [required]
}) })
const recommendOptions = [
{ name: '是否热卖', value: 'recommendHot' },
{ name: '是否优惠', value: 'recommendBenefit' },
{ name: '是否精品', value: 'recommendBest' },
{ name: '是否新品', value: 'recommendNew' },
{ name: '是否优品', value: 'recommendGood' }
] //
const checkboxGroup = ref<string[]>([]) //
/** 选择商品后赋值 */
const onChangeGroup = () => {
recommendOptions.forEach(({ value }) => {
formData.value[value] = checkboxGroup.value.includes(value)
})
}
/** /**
* 将传进来的值赋值给formData * 将传进来的值赋值给formData
@ -175,11 +67,6 @@ watch(
return return
} }
copyValueToTarget(formData.value, data) copyValueToTarget(formData.value, data)
recommendOptions.forEach(({ value }) => {
if (formData.value[value] && !checkboxGroup.value.includes(value)) {
checkboxGroup.value.push(value)
}
})
}, },
{ {
immediate: true immediate: true

View File

@ -0,0 +1,240 @@
<template>
<!-- 情况一添加/修改 -->
<el-form
v-if="!isDetail"
ref="productSpuSkuRef"
:model="formData"
:rules="rules"
label-width="120px"
>
<el-row>
<el-col :span="12">
<el-form-item label="商品规格" props="specType">
<el-radio-group v-model="formData.specType" @change="onChangeSpec">
<el-radio :label="false" class="radio">单规格</el-radio>
<el-radio :label="true">多规格</el-radio>
</el-radio-group>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="分销类型" props="subCommissionType">
<el-radio-group v-model="formData.subCommissionType" @change="changeSubCommissionType">
<el-radio :label="false">默认设置</el-radio>
<el-radio :label="true" class="radio">单独设置</el-radio>
</el-radio-group>
</el-form-item>
</el-col>
<!-- 多规格添加-->
<el-col :span="24">
<el-form-item v-if="!formData.specType">
<SkuList
ref="skuListRef"
:prop-form-data="formData"
:propertyList="propertyList"
:rule-config="ruleConfig"
/>
</el-form-item>
<el-form-item v-if="formData.specType" label="商品属性">
<el-button class="mb-10px mr-15px" @click="attributesAddFormRef.open">添加属性</el-button>
<ProductAttributes :propertyList="propertyList" @success="generateSkus" />
</el-form-item>
<template v-if="formData.specType && propertyList.length > 0">
<el-form-item label="批量设置">
<SkuList :is-batch="true" :prop-form-data="formData" :propertyList="propertyList" />
</el-form-item>
<el-form-item label="属性列表">
<SkuList
ref="skuListRef"
:prop-form-data="formData"
:propertyList="propertyList"
:rule-config="ruleConfig"
/>
</el-form-item>
</template>
</el-col>
</el-row>
</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 表单 -->
<ProductPropertyAddForm ref="attributesAddFormRef" :propertyList="propertyList" />
</template>
<script lang="ts" setup>
import { PropType } from 'vue'
import { isArray } from '@/utils/is'
import { copyValueToTarget } from '@/utils'
import { propTypes } from '@/utils/propTypes'
import { createImageViewer } from '@/components/ImageViewer'
import { getPropertyList, RuleConfig, SkuList } from '@/views/mall/product/spu/components/index.ts'
import ProductAttributes from './ProductAttributes.vue'
import ProductPropertyAddForm from './ProductPropertyAddForm.vue'
import { basicInfoSchema } from './spu.data'
import type { Spu } from '@/api/mall/product/spu'
defineOptions({ name: 'ProductSpuSkuForm' })
// sku
const ruleConfig: RuleConfig[] = [
{
name: 'stock',
rule: (arg) => arg >= 0,
message: '商品库存必须大于等于 1 '
},
{
name: 'price',
rule: (arg) => arg >= 0.01,
message: '商品销售价格必须大于等于 0.01 元!!!'
},
{
name: 'marketPrice',
rule: (arg) => arg >= 0.01,
message: '商品市场价格必须大于等于 0.01 元!!!'
},
{
name: 'costPrice',
rule: (arg) => arg >= 0.01,
message: '商品成本价格必须大于等于 0.00 元!!!'
}
]
// ====== ======
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 props = defineProps({
propFormData: {
type: Object as PropType<Spu>,
default: () => {}
},
activeName: propTypes.string.def(''),
isDetail: propTypes.bool.def(false) //
})
const attributesAddFormRef = ref() //
const productSpuSkuRef = ref() // Ref
const propertyList = ref([]) //
const skuListRef = ref() // Ref
/** 调用 SkuList generateTableData 方法*/
const generateSkus = (propertyList) => {
skuListRef.value.generateTableData(propertyList)
}
const formData = reactive<Spu>({
specType: false, //
subCommissionType: false, //
skus: []
})
const rules = reactive({
specType: [required],
subCommissionType: [required]
})
/**
* 将传进来的值赋值给 formData
*/
watch(
() => props.propFormData,
(data) => {
if (!data) {
return
}
copyValueToTarget(formData, data)
propertyList.value = getPropertyList(data)
},
{
immediate: true
}
)
/** 表单校验 */
const emit = defineEmits(['update:activeName'])
const validate = async () => {
// sku
skuListRef.value.validateSku()
//
if (!productSpuSkuRef) return
return await unref(productSpuSkuRef).validate((valid) => {
if (!valid) {
message.warning('商品信息未完善!!')
emit('update:activeName', 'sku')
//
throw new Error('商品信息未完善!!')
} else {
//
Object.assign(props.propFormData, formData)
}
})
}
defineExpose({ validate })
/** 分销类型 */
const changeSubCommissionType = () => {
//
for (const item of formData.skus) {
item.firstBrokeragePrice = 0
item.secondBrokeragePrice = 0
}
}
/** 选择规格 */
const onChangeSpec = () => {
//
propertyList.value = []
// sku
formData.skus = [
{
price: 0,
marketPrice: 0,
costPrice: 0,
barCode: '',
picUrl: '',
stock: 0,
weight: 0,
volume: 0,
firstBrokeragePrice: 0,
secondBrokeragePrice: 0
}
]
}
</script>

View File

@ -1,7 +1,7 @@
<template> <template>
<ContentWrap v-loading="formLoading"> <ContentWrap v-loading="formLoading">
<el-tabs v-model="activeName"> <el-tabs v-model="activeName">
<el-tab-pane label="商品信息" name="basicInfo"> <el-tab-pane label="基础信息" name="basicInfo">
<BasicInfoForm <BasicInfoForm
ref="basicInfoRef" ref="basicInfoRef"
v-model:activeName="activeName" v-model:activeName="activeName"
@ -9,6 +9,14 @@
:propFormData="formData" :propFormData="formData"
/> />
</el-tab-pane> </el-tab-pane>
<el-tab-pane label="价格库存" name="sku">
<SkuForm
ref="skuRef"
v-model:activeName="activeName"
:is-detail="isDetail"
:propFormData="formData"
/>
</el-tab-pane>
<el-tab-pane label="商品详情" name="description"> <el-tab-pane label="商品详情" name="description">
<DescriptionForm <DescriptionForm
ref="descriptionRef" ref="descriptionRef"
@ -17,6 +25,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"
@ -43,6 +52,7 @@ import * as ProductSpuApi from '@/api/mall/product/spu'
import BasicInfoForm from './BasicInfoForm.vue' import BasicInfoForm from './BasicInfoForm.vue'
import DescriptionForm from './DescriptionForm.vue' import DescriptionForm from './DescriptionForm.vue'
import OtherSettingsForm from './OtherSettingsForm.vue' import OtherSettingsForm from './OtherSettingsForm.vue'
import SkuForm from './SkuForm.vue'
import { convertToInteger, floatToFixed2, formatToFraction } from '@/utils' import { convertToInteger, floatToFixed2, formatToFraction } from '@/utils'
defineOptions({ name: 'ProductSpuForm' }) defineOptions({ name: 'ProductSpuForm' })
@ -57,6 +67,7 @@ const formLoading = ref(false) // 表单的加载中1修改时的数据加
const activeName = ref('basicInfo') // Tag const activeName = ref('basicInfo') // Tag
const isDetail = ref(false) // const isDetail = ref(false) //
const basicInfoRef = ref() // Ref const basicInfoRef = ref() // Ref
const skuRef = ref() // Ref
const descriptionRef = ref() // Ref const descriptionRef = ref() // Ref
const otherSettingsRef = ref() // Ref const otherSettingsRef = ref() // Ref
// spu // spu
@ -64,7 +75,6 @@ const formData = ref<ProductSpuApi.Spu>({
name: '', // name: '', //
categoryId: undefined, // categoryId: undefined, //
keyword: '', // keyword: '', //
unit: undefined, //
picUrl: '', // picUrl: '', //
sliderPicUrls: [], // sliderPicUrls: [], //
introduction: '', // introduction: '', //
@ -89,13 +99,7 @@ const formData = ref<ProductSpuApi.Spu>({
description: '', // description: '', //
sort: 0, // sort: 0, //
giveIntegral: 0, // giveIntegral: 0, //
virtualSalesCount: 0, // virtualSalesCount: 0 //
recommendHot: false, //
recommendBenefit: false, //
recommendBest: false, //
recommendNew: false, //
recommendGood: false, //
activityOrders: [] //
}) })
/** 获得详情 */ /** 获得详情 */
@ -139,6 +143,7 @@ const submitForm = async () => {
// //
try { try {
await unref(basicInfoRef)?.validate() await unref(basicInfoRef)?.validate()
await unref(skuRef)?.validate()
await unref(descriptionRef)?.validate() await unref(descriptionRef)?.validate()
await unref(otherSettingsRef)?.validate() await unref(otherSettingsRef)?.validate()
// , server // , server
@ -181,6 +186,7 @@ const close = () => {
delView(unref(currentRoute)) delView(unref(currentRoute))
push({ name: 'ProductSpu' }) push({ name: 'ProductSpu' })
} }
/** 初始化 */ /** 初始化 */
onMounted(async () => { onMounted(async () => {
await getDetail() await getDetail()

View File

@ -33,11 +33,6 @@ export const basicInfoSchema = reactive<CrudSchema[]>([
label: '商品视频', label: '商品视频',
field: 'videoUrl' field: 'videoUrl'
}, },
{
label: '单位',
field: 'unit',
dictType: DICT_TYPE.PRODUCT_UNIT
},
{ {
label: '规格类型', label: '规格类型',
field: 'specType' field: 'specType'
@ -73,29 +68,5 @@ export const otherSettingsSchema = reactive<CrudSchema[]>([
{ {
label: '虚拟销量', label: '虚拟销量',
field: 'virtualSalesCount' field: 'virtualSalesCount'
},
{
label: '是否热卖推荐',
field: 'recommendHot'
},
{
label: '是否优惠推荐',
field: 'recommendBenefit'
},
{
label: '是否精品推荐',
field: 'recommendBest'
},
{
label: '是否新品推荐',
field: 'recommendNew'
},
{
label: '是否优品推荐',
field: 'recommendGood'
},
{
label: '活动显示排序',
field: 'activityOrders'
} }
]) ])