fix: 完善 SPU 查看详情

This commit is contained in:
puhui999
2023-05-31 14:42:45 +08:00
parent e555977757
commit 39c92cb944
10 changed files with 387 additions and 37 deletions

View File

@@ -1,5 +1,11 @@
<template>
<el-form ref="productSpuBasicInfoRef" :model="formData" :rules="rules" label-width="120px">
<el-form
v-if="!isDetail"
ref="productSpuBasicInfoRef"
:model="formData"
:rules="rules"
label-width="120px"
>
<el-row>
<el-col :span="12">
<el-form-item label="商品名称" prop="name">
@@ -115,18 +121,72 @@
</el-row>
</el-form>
<ProductAttributesAddForm ref="attributesAddFormRef" :propertyList="propertyList" />
<!-- 详情跟表单放在一块可以共用已有功能再抽离成组件有点过度封装的感觉 -->
<Descriptions v-if="isDetail" :data="formData" :schema="allSchemas.detailSchema">
<template #categoryId="{ row }"> {{ categoryString(row.categoryId) }}</template>
<template #brandId="{ row }">
{{ brandList.find((item) => item.id === row.brandId)?.name }}
</template>
<template #specType="{ row }">
{{ row.specType ? '多规格' : '单规格' }}
</template>
<template #subCommissionType="{ row }">
{{ row.subCommissionType ? '自行设置' : '默认设置' }}
</template>
<template #picUrl="{ row }">
<el-image :src="row.picUrl" class="w-60px h-60px" @click="imagePreview(row.picUrl)" />
</template>
<template #sliderPicUrls="{ row }">
<el-image
v-for="(item, index) in row.sliderPicUrls"
:key="index"
:src="item.url"
class="w-60px h-60px mr-10px"
@click="imagePreview(row.sliderPicUrls)"
/>
</template>
<template #skus>
<SkuList
ref="skuDetailListRef"
:is-detail="isDetail"
:prop-form-data="formData"
:propertyList="propertyList"
/>
</template>
</Descriptions>
</template>
<script lang="ts" name="ProductSpuBasicInfoForm" setup>
import { PropType } from 'vue'
import { copyValueToTarget } from '@/utils'
import { propTypes } from '@/utils/propTypes'
import { checkSelectedNode, defaultProps, handleTree } from '@/utils/tree'
import { checkSelectedNode, defaultProps, handleTree, treeToString } from '@/utils/tree'
import { DICT_TYPE, getIntDictOptions } from '@/utils/dict'
import type { Spu } from '@/api/mall/product/spu'
import { UploadImg, UploadImgs } from '@/components/UploadFile'
import { ProductAttributes, ProductAttributesAddForm, SkuList } from './index'
import { basicInfoSchema } from './spu.data'
import { createImageViewer } from '@/components/ImageViewer'
import * as ProductCategoryApi from '@/api/mall/product/category'
import { getSimpleBrandList } from '@/api/mall/product/brand'
import { isArray } from '@/utils/is'
// ====== 商品详情相关操作 ======
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() // 消息弹窗
@@ -135,7 +195,8 @@ const props = defineProps({
type: Object as PropType<Spu>,
default: () => {}
},
activeName: propTypes.string.def('')
activeName: propTypes.string.def(''),
isDetail: propTypes.bool.def(false) // 是否作为详情组件
})
const attributesAddFormRef = ref() // 添加商品属性表单
const productSpuBasicInfoRef = ref() // 表单 Ref
@@ -149,11 +210,11 @@ const formData = reactive<Spu>({
name: '', // 商品名称
categoryId: null, // 商品分类
keyword: '', // 关键字
unit: '', // 单位
unit: null, // 单位
picUrl: '', // 商品封面图
sliderPicUrls: [], // 商品轮播图
introduction: '', // 商品简介
deliveryTemplateId: 1, // 运费模版
deliveryTemplateId: null, // 运费模版
brandId: null, // 商品品牌
specType: false, // 商品规格
subCommissionType: false, // 分销类型
@@ -273,9 +334,16 @@ const categoryList = ref([]) // 分类树
const nodeClick = () => {
if (!checkSelectedNode(categoryList.value, formData.categoryId)) {
formData.categoryId = null
message.warning('必须选择二级节点!!')
message.warning('必须选择二级及以下节点!!')
}
}
/**
* 获取分类的节点的完整结构
* @param categoryId 分类id
*/
const categoryString = (categoryId) => {
return treeToString(categoryList.value, categoryId)
}
const brandList = ref([]) // 精简商品品牌列表
onMounted(async () => {
// 获得分类树

View File

@@ -1,10 +1,27 @@
<template>
<el-form ref="descriptionFormRef" :model="formData" :rules="rules" label-width="120px">
<el-form
v-if="!isDetail"
ref="descriptionFormRef"
:model="formData"
:rules="rules"
label-width="120px"
>
<!--富文本编辑器组件-->
<el-form-item label="商品详情" prop="description">
<Editor v-model:modelValue="formData.description" />
</el-form-item>
</el-form>
<Descriptions
v-if="isDetail"
:data="formData"
:schema="allSchemas.detailSchema"
class="descriptionFormDescriptions"
>
<!-- 展示 HTML 内容 -->
<template #description="{ row }">
<div style="width: 600px" v-html="row.description"></div>
</template>
</Descriptions>
</template>
<script lang="ts" name="DescriptionForm" setup>
import type { Spu } from '@/api/mall/product/spu'
@@ -12,6 +29,9 @@ import { Editor } from '@/components/Editor'
import { PropType } from 'vue'
import { propTypes } from '@/utils/propTypes'
import { copyValueToTarget } from '@/utils'
import { descriptionSchema } from './spu.data'
const { allSchemas } = useCrudSchemas(descriptionSchema)
const message = useMessage() // 消息弹窗
const props = defineProps({
@@ -19,7 +39,8 @@ const props = defineProps({
type: Object as PropType<Spu>,
default: () => {}
},
activeName: propTypes.string.def('')
activeName: propTypes.string.def(''),
isDetail: propTypes.bool.def(false) // 是否作为详情组件
})
const descriptionFormRef = ref() // 表单Ref
const formData = ref<Spu>({

View File

@@ -1,5 +1,11 @@
<template>
<el-form ref="otherSettingsFormRef" :model="formData" :rules="rules" label-width="120px">
<el-form
v-if="!isDetail"
ref="otherSettingsFormRef"
:model="formData"
:rules="rules"
label-width="120px"
>
<el-row>
<el-col :span="24">
<el-row :gutter="20">
@@ -50,12 +56,38 @@
</el-col>
</el-row>
</el-form>
<Descriptions v-if="isDetail" :data="formData" :schema="allSchemas.detailSchema">
<template #recommendHot="{ row }">
{{ row.recommendHot ? '是' : '否' }}
</template>
<template #recommendBenefit="{ row }">
{{ row.recommendBenefit ? '是' : '否' }}
</template>
<template #recommendBest="{ row }">
{{ row.recommendBest ? '是' : '否' }}
</template>
<template #recommendNew="{ row }">
{{ row.recommendNew ? '是' : '否' }}
</template>
<template #recommendGood="{ row }">
{{ row.recommendGood ? '是' : '否' }}
</template>
<template #activityOrders>
<el-tag>默认</el-tag>
<el-tag class="ml-2" type="success">秒杀</el-tag>
<el-tag class="ml-2" type="info">砍价</el-tag>
<el-tag class="ml-2" type="warning">拼团</el-tag>
</template>
</Descriptions>
</template>
<script lang="ts" name="OtherSettingsForm" setup>
import type { Spu } from '@/api/mall/product/spu'
import { PropType } from 'vue'
import { propTypes } from '@/utils/propTypes'
import { copyValueToTarget } from '@/utils'
import { otherSettingsSchema } from './spu.data'
const { allSchemas } = useCrudSchemas(otherSettingsSchema)
const message = useMessage() // 消息弹窗
@@ -64,7 +96,8 @@ const props = defineProps({
type: Object as PropType<Spu>,
default: () => {}
},
activeName: propTypes.string.def('')
activeName: propTypes.string.def(''),
isDetail: propTypes.bool.def(false) // 是否作为详情组件
})
const otherSettingsFormRef = ref() // 表单Ref

View File

@@ -1,5 +1,6 @@
<template>
<el-table
v-if="!isDetail"
:data="isBatch ? skuList : formData!.skus"
border
class="tabNumWidth"
@@ -21,8 +22,8 @@
min-width="120"
>
<template #default="{ row }">
<!-- TODO puhui999展示成蓝色有点区分度哈 fix: 字体加粗颜色使用 #99a9bf 蓝色有点不好看哈哈-->
<span style="font-weight: bold; color: #99a9bf">
<!-- TODO puhui999展示成蓝色有点区分度哈 fix-->
<span style="font-weight: bold; color: #40aaff">
{{ row.properties[index]?.valueName }}
</span>
</template>
@@ -108,6 +109,84 @@
</template>
</el-table-column>
</el-table>
<el-table
v-if="isDetail"
:data="formData!.skus"
border
max-height="500"
size="small"
style="width: 99%"
>
<el-table-column align="center" label="图片" min-width="80">
<template #default="{ row }">
<el-image :src="row.picUrl" class="w-60px h-60px" @click="imagePreview(row.picUrl)" />
</template>
</el-table-column>
<template v-if="formData!.specType && !isBatch">
<!-- 根据商品属性动态添加 -->
<el-table-column
v-for="(item, index) in tableHeaders"
:key="index"
:label="item.label"
align="center"
min-width="80"
>
<template #default="{ row }">
<!-- TODO puhui999展示成蓝色有点区分度哈 fix-->
<span style="font-weight: bold; color: #40aaff">
{{ row.properties[index]?.valueName }}
</span>
</template>
</el-table-column>
</template>
<el-table-column align="center" label="商品条码" min-width="100">
<template #default="{ row }">
{{ row.barCode }}
</template>
</el-table-column>
<el-table-column align="center" label="销售价(元)" min-width="80">
<template #default="{ row }">
{{ row.price }}
</template>
</el-table-column>
<el-table-column align="center" label="市场价(元)" min-width="80">
<template #default="{ row }">
{{ row.marketPrice }}
</template>
</el-table-column>
<el-table-column align="center" label="成本价(元)" min-width="80">
<template #default="{ row }">
{{ row.costPrice }}
</template>
</el-table-column>
<el-table-column align="center" label="库存" min-width="80">
<template #default="{ row }">
{{ row.stock }}
</template>
</el-table-column>
<el-table-column align="center" label="重量(kg)" min-width="80">
<template #default="{ row }">
{{ row.weight }}
</template>
</el-table-column>
<el-table-column align="center" label="体积(m^3)" min-width="80">
<template #default="{ row }">
{{ row.volume }}
</template>
</el-table-column>
<template v-if="formData!.subCommissionType">
<el-table-column align="center" label="一级返佣(元)" min-width="80">
<template #default="{ row }">
{{ row.subCommissionFirstPrice }}
</template>
</el-table-column>
<el-table-column align="center" label="二级返佣(元)" min-width="80">
<template #default="{ row }">
{{ row.subCommissionSecondPrice }}
</template>
</el-table-column>
</template>
</el-table>
</template>
<script lang="ts" name="SkuList" setup>
import { PropType, Ref } from 'vue'
@@ -115,6 +194,7 @@ import { copyValueToTarget } from '@/utils'
import { propTypes } from '@/utils/propTypes'
import { UploadImg } from '@/components/UploadFile'
import type { Property, Sku, Spu } from '@/api/mall/product/spu'
import { createImageViewer } from '@/components/ImageViewer'
const props = defineProps({
propFormData: {
@@ -125,7 +205,8 @@ const props = defineProps({
type: Array,
default: () => []
},
isBatch: propTypes.bool.def(false) // 是否作为批量操作组件
isBatch: propTypes.bool.def(false), // 是否作为批量操作组件
isDetail: propTypes.bool.def(false) // 是否作为 sku 详情组件
})
const formData: Ref<Spu | undefined> = ref<Spu>() // 表单数据
const skuList = ref<Sku[]>([
@@ -143,20 +224,27 @@ const skuList = ref<Sku[]>([
}
]) // 批量添加时的临时数据
/** 商品图预览 */
const imagePreview = (imgUrl: string) => {
createImageViewer({
urlList: [imgUrl]
})
}
/** 批量添加 */
const batchAdd = () => {
formData.value!.skus.forEach((item) => {
formData.value!.skus!.forEach((item) => {
copyValueToTarget(item, skuList.value[0])
})
}
/** 删除 sku */
const deleteSku = (row) => {
const index = formData.value!.skus.findIndex(
const index = formData.value!.skus!.findIndex(
// 直接把列表转成字符串比较
(sku) => JSON.stringify(sku.properties) === JSON.stringify(row.properties)
)
formData.value!.skus.splice(index, 1)
formData.value!.skus!.splice(index, 1)
}
const tableHeaders = ref<{ prop: string; label: string }[]>([]) // 多属性表头
/**
@@ -221,13 +309,13 @@ const generateTableData = (propertyList: any[]) => {
subCommissionSecondPrice: 0
}
// 如果存在属性相同的 sku 则不做处理
const index = formData.value!.skus.findIndex(
const index = formData.value!.skus!.findIndex(
(sku) => JSON.stringify(sku.properties) === JSON.stringify(row.properties)
)
if (index !== -1) {
continue
}
formData.value!.skus.push(row)
formData.value!.skus!.push(row)
}
}
@@ -236,7 +324,7 @@ const generateTableData = (propertyList: any[]) => {
*/
const validateData = (propertyList: any[]) => {
const skuPropertyIds = []
formData.value!.skus.forEach((sku) =>
formData.value!.skus!.forEach((sku) =>
sku.properties
?.map((property) => property.propertyId)
.forEach((propertyId) => {

View File

@@ -0,0 +1,105 @@
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: 'unit',
dictType: DICT_TYPE.PRODUCT_UNIT
},
{
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'
},
{
label: '是否热卖推荐',
field: 'recommendHot'
},
{
label: '是否优惠推荐',
field: 'recommendBenefit'
},
{
label: '是否精品推荐',
field: 'recommendBest'
},
{
label: '是否新品推荐',
field: 'recommendNew'
},
{
label: '是否优品推荐',
field: 'recommendGood'
},
{
label: '赠送的优惠劵',
field: 'giveCouponTemplateIds'
},
{
label: '活动显示排序',
field: 'activityOrders'
}
])