Skip to content

Commit 51e79f2

Browse files
author
puhui999
committed
fix: 封装 SpuSelect SpuAndSkuList 为商品活动商品选择商品编辑通用组件
1 parent 76ccc54 commit 51e79f2

File tree

12 files changed

+229
-177
lines changed

12 files changed

+229
-177
lines changed

src/api/mall/product/spu.ts

Lines changed: 7 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -47,17 +47,13 @@ export interface Spu {
4747
recommendBest?: boolean // 是否精品
4848
recommendNew?: boolean // 是否新品
4949
recommendGood?: boolean // 是否优品
50-
}
51-
52-
// TODO @puhui999: SpuRespVO 合并到 SPU 里?前端少点 VO 类哈;
53-
export interface SpuRespVO extends Spu {
54-
price: number
55-
salesCount: number
56-
marketPrice: number
57-
costPrice: number
58-
stock: number
59-
createTime: Date
60-
status: number
50+
price?: number // 商品价格
51+
salesCount?: number // 商品销量
52+
marketPrice?: number // 市场价
53+
costPrice?: number // 成本价
54+
stock?: number // 商品库存
55+
createTime?: Date // 商品创建时间
56+
status?: number // 商品状态
6157
}
6258

6359
// 获得 Spu 列表

src/api/mall/promotion/seckill/seckillActivity.ts

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import request from '@/config/axios'
2-
import { Sku, SpuRespVO } from '@/api/mall/product/spu'
2+
import { Sku, Spu } from '@/api/mall/product/spu'
33

44
export interface SeckillActivityVO {
55
id: number
@@ -21,18 +21,20 @@ export interface SeckillActivityVO {
2121
products: SeckillProductVO[]
2222
}
2323

24+
// 秒杀活动所需属性
2425
export interface SeckillProductVO {
2526
spuId: number
2627
skuId: number
2728
seckillPrice: number
2829
stock: number
2930
}
3031

32+
// 扩展 Sku 配置
3133
type SkuExtension = Sku & {
3234
productConfig: SeckillProductVO
3335
}
3436

35-
export interface SpuExtension extends SpuRespVO {
37+
export interface SpuExtension extends Spu {
3638
skus: SkuExtension[] // 重写类型
3739
}
3840

src/views/mall/product/spu/components/BasicInfoForm.vue

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -274,10 +274,7 @@ watch(
274274
const emit = defineEmits(['update:activeName'])
275275
const validate = async () => {
276276
// 校验 sku
277-
if (!skuListRef.value.validateSku()) {
278-
message.warning('商品相关价格不能低于 0.01 元!!')
279-
throw new Error('商品相关价格不能低于 0.01 元!!')
280-
}
277+
skuListRef.value.validateSku()
281278
// 校验表单
282279
if (!productSpuBasicInfoRef) return
283280
return await unref(productSpuBasicInfoRef).validate((valid) => {

src/views/mall/product/spu/components/SkuList.vue

Lines changed: 34 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -259,16 +259,18 @@ import { UploadImg } from '@/components/UploadFile'
259259
import type { Property, Sku, Spu } from '@/api/mall/product/spu'
260260
import { createImageViewer } from '@/components/ImageViewer'
261261
import { RuleConfig } from '@/views/mall/product/spu/components/index'
262+
import { Properties } from './index'
262263
263264
defineOptions({ name: 'SkuList' })
265+
const message = useMessage() // 消息弹窗
264266
265267
const props = defineProps({
266268
propFormData: {
267269
type: Object as PropType<Spu>,
268270
default: () => {}
269271
},
270272
propertyList: {
271-
type: Array,
273+
type: Array as PropType<Properties[]>,
272274
default: () => []
273275
},
274276
ruleConfig: {
@@ -323,26 +325,47 @@ const tableHeaders = ref<{ prop: string; label: string }[]>([]) // 多属性表
323325
/**
324326
* 保存时,每个商品规格的表单要校验下。例如说,销售金额最低是 0.01 这种。
325327
*/
326-
const validateSku = (): boolean => {
328+
const validateSku = () => {
327329
const checks = ['price', 'marketPrice', 'costPrice']
330+
let warningInfo = '请检查商品各行相关属性配置,'
328331
let validate = true // 默认通过
329-
for (const sku of formData.value!.skus) {
332+
for (const sku of formData.value!.skus!) {
330333
// 作为活动组件的校验
331334
if (props.isActivityComponent) {
332335
for (const rule of props.ruleConfig) {
333-
if (sku[rule.name] < rule.geValue) {
336+
const arg = getValue(sku, rule.name)
337+
if (!rule.rule(arg)) {
334338
validate = false // 只要有一个不通过则直接不通过
339+
warningInfo += rule.message
335340
break
336341
}
337342
}
338343
} else {
339344
if (checks.some((check) => sku[check] < 0.01)) {
340345
validate = false // 只要有一个不通过则直接不通过
346+
warningInfo = '商品相关价格不能低于 0.01 元!!'
341347
break
342348
}
343349
}
350+
// 只要有一个不通过则结束后续的校验
351+
if (!validate) {
352+
message.warning(warningInfo)
353+
throw new Error(warningInfo)
354+
}
355+
}
356+
}
357+
const getValue = (obj, arg) => {
358+
const keys = arg.split('.')
359+
let value = obj
360+
for (const key of keys) {
361+
if (value && typeof value === 'object' && key in value) {
362+
value = value[key]
363+
} else {
364+
value = undefined
365+
break
366+
}
344367
}
345-
return validate
368+
return value
346369
}
347370
348371
const emit = defineEmits<{
@@ -417,13 +440,13 @@ const generateTableData = (propertyList: any[]) => {
417440
* 生成 skus 前置校验
418441
*/
419442
const validateData = (propertyList: any[]) => {
420-
const skuPropertyIds = []
443+
const skuPropertyIds: number[] = []
421444
formData.value!.skus!.forEach((sku) =>
422445
sku.properties
423446
?.map((property) => property.propertyId)
424-
.forEach((propertyId) => {
425-
if (skuPropertyIds.indexOf(propertyId) === -1) {
426-
skuPropertyIds.push(propertyId)
447+
?.forEach((propertyId) => {
448+
if (skuPropertyIds.indexOf(propertyId!) === -1) {
449+
skuPropertyIds.push(propertyId!)
427450
}
428451
})
429452
)
@@ -457,7 +480,7 @@ const build = (propertyValuesList: Property[][]) => {
457480
/** 监听属性列表,生成相关参数和表头 */
458481
watch(
459482
() => props.propertyList,
460-
(propertyList) => {
483+
(propertyList: Properties[]) => {
461484
// 如果不是多规格则结束
462485
if (!formData.value!.specType) {
463486
return
@@ -497,7 +520,7 @@ watch(
497520
return
498521
}
499522
// 添加新属性没有属性值也不做处理
500-
if (propertyList.some((item) => item.values.length === 0)) {
523+
if (propertyList.some((item) => item.values!.length === 0)) {
501524
return
502525
}
503526
// 生成 table 数据,即 sku 列表

src/views/mall/product/spu/components/index.ts

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,8 +14,19 @@ interface Properties {
1414
}
1515

1616
interface RuleConfig {
17-
name: string // 需要校验的字段
18-
geValue: number // TODO 暂定大于一个数字
17+
// 需要校验的字段
18+
// 例:name: 'name' 则表示校验 sku.name 的值
19+
// 例:name: 'productConfig.stock' 则表示校验 sku.productConfig.name 的值,此处 productConfig 表示我在 Sku 上扩展的属性
20+
name: string
21+
// 校验规格为一个毁掉函数,其中 arg 为需要校验的字段的值。
22+
// 例:需要校验价格必须大于0.01
23+
// {
24+
// name:'price',
25+
// rule:(arg) => arg > 0.01
26+
// }
27+
rule: (arg: any) => boolean
28+
// 校验不通过时的消息提示
29+
message: string
1930
}
2031

2132
/**

src/views/mall/promotion/seckill/activity/components/SpuAndSkuList.vue renamed to src/views/mall/promotion/components/SpuAndSkuList.vue

Lines changed: 34 additions & 72 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
<template>
2-
<el-table :data="spuData">
2+
<el-table :data="spuData" :default-expand-all="true">
33
<el-table-column type="expand" width="30">
44
<template #default="{ row }">
55
<SkuList
@@ -46,90 +46,39 @@
4646
<el-table-column align="center" label="库存" min-width="90" prop="stock" />
4747
</el-table>
4848
</template>
49-
<script lang="ts" setup>
49+
<script generic="T extends Spu" lang="ts" setup>
5050
// TODO 后续计划重新封装作为活动商品配置通用组件;可以等其他活动做到的时候,在统一处理 SPU 选择组件哈
5151
import { formatToFraction } from '@/utils'
5252
import { createImageViewer } from '@/components/ImageViewer'
53-
import * as ProductSpuApi from '@/api/mall/product/spu'
54-
import { SpuRespVO } from '@/api/mall/product/spu'
55-
import {
56-
getPropertyList,
57-
Properties,
58-
RuleConfig,
59-
SkuList
60-
} from '@/views/mall/product/spu/components'
61-
import { SeckillProductVO, SpuExtension } from '@/api/mall/promotion/seckill/seckillActivity'
53+
import { Spu } from '@/api/mall/product/spu'
54+
import { RuleConfig, SkuList } from '@/views/mall/product/spu/components'
55+
import { SeckillProductVO } from '@/api/mall/promotion/seckill/seckillActivity'
56+
import { SpuProperty } from '@/views/mall/promotion/components/index'
6257
6358
defineOptions({ name: 'PromotionSpuAndSkuList' })
6459
65-
const message = useMessage() // 消息弹窗
60+
// TODO @puhui999:是不是改成传递一个 spu 就好啦? 因为活动商品可以多选所以展示编辑的时候需要展示多个
61+
const props = defineProps<{
62+
spuList: T[]
63+
ruleConfig: RuleConfig[]
64+
spuPropertyListP: SpuProperty<T>[]
65+
}>()
6666
67-
// TODO @puhui999:是不是改成传递一个 spu 就好啦?
68-
const props = defineProps({
69-
spuList: {
70-
type: Array,
71-
default: () => []
72-
}
73-
})
74-
const spuData = ref<SpuRespVO[]>([]) // spu 详情数据列表
67+
const spuData = ref<Spu[]>([]) // spu 详情数据列表
7568
const skuListRef = ref() // 商品属性列表Ref
76-
interface spuProperty {
77-
spuId: number
78-
spuDetail: SpuExtension
79-
propertyList: Properties[]
80-
} // TODO @puhui999:类名首字母大写哈
8169
82-
const spuPropertyList = ref<spuProperty[]>([]) // spuId 对应的 sku 的属性列表
83-
/**
84-
* 获取 SPU 详情
85-
* @param spuIds
86-
*/
87-
const getSpuDetails = async (spuIds: number[]) => {
88-
const spuProperties: spuProperty[] = []
89-
// TODO puhui999: 考虑后端添加通过 spuIds 批量获取
90-
for (const spuId of spuIds) {
91-
// 获取 SPU 详情
92-
const res = (await ProductSpuApi.getSpu(spuId)) as SpuExtension
93-
if (!res) {
94-
continue
95-
}
96-
// 初始化每个 sku 秒杀配置
97-
res.skus?.forEach((sku) => {
98-
const config: SeckillProductVO = {
99-
spuId,
100-
skuId: sku.id!,
101-
stock: 0,
102-
seckillPrice: 0
103-
}
104-
sku.productConfig = config
105-
})
106-
spuProperties.push({ spuId, spuDetail: res, propertyList: getPropertyList(res) })
107-
}
108-
spuPropertyList.value = spuProperties
109-
}
110-
const ruleConfig: RuleConfig[] = [
111-
{
112-
name: 'stock',
113-
geValue: 10
114-
},
115-
{
116-
name: 'seckillPrice',
117-
geValue: 0.01
118-
}
119-
]
70+
const spuPropertyList = ref<SpuProperty<T>[]>([]) // spuId 对应的 sku 的属性列表
71+
12072
/**
12173
* 获取所有 sku 秒杀配置
74+
* @param extendedAttribute 在 sku 上扩展的属性,例:秒杀活动 sku 扩展属性 productConfig 请参考 seckillActivity.ts
12275
*/
123-
const getSkuConfigs = (): SeckillProductVO[] => {
124-
if (!skuListRef.value.validateSku()) {
125-
// TODO 作为通用组件是需要进一步完善
126-
message.warning('请检查商品相关属性配置!!')
127-
throw new Error('请检查商品相关属性配置!!')
128-
}
76+
const getSkuConfigs: <V>(extendedAttribute: string) => V[] = (extendedAttribute: string) => {
77+
skuListRef.value.validateSku()
12978
const seckillProducts: SeckillProductVO[] = []
13079
spuPropertyList.value.forEach((item) => {
13180
item.spuDetail.skus.forEach((sku) => {
132-
seckillProducts.push(sku.productConfig)
81+
seckillProducts.push(sku[extendedAttribute])
13382
})
13483
})
13584
return seckillProducts
@@ -152,8 +101,21 @@ watch(
152101
() => props.spuList,
153102
(data) => {
154103
if (!data) return
155-
spuData.value = data as SpuRespVO[]
156-
getSpuDetails(spuData.value.map((spu) => spu.id!))
104+
spuData.value = data as Spu[]
105+
},
106+
{
107+
deep: true,
108+
immediate: true
109+
}
110+
)
111+
/**
112+
* 将传进来的值赋值给 skuList
113+
*/
114+
watch(
115+
() => props.spuPropertyListP,
116+
(data) => {
117+
if (!data) return
118+
spuPropertyList.value = data as SpuProperty<T>[]
157119
},
158120
{
159121
deep: true,

0 commit comments

Comments
 (0)