fix: mall SeckillActivity
This commit is contained in:
parent
b98149f1a7
commit
36c0bce184
@ -23,14 +23,13 @@ export interface SeckillActivityVO {
|
|||||||
|
|
||||||
// 秒杀活动所需属性
|
// 秒杀活动所需属性
|
||||||
export interface SeckillProductVO {
|
export interface SeckillProductVO {
|
||||||
spuId: number
|
|
||||||
skuId: number
|
skuId: number
|
||||||
seckillPrice: number
|
seckillPrice: number
|
||||||
stock: number
|
stock: number
|
||||||
}
|
}
|
||||||
|
|
||||||
// 扩展 Sku 配置
|
// 扩展 Sku 配置
|
||||||
type SkuExtension = Sku & {
|
export type SkuExtension = Sku & {
|
||||||
productConfig: SeckillProductVO
|
productConfig: SeckillProductVO
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -194,6 +194,7 @@
|
|||||||
<!-- 情况三:作为活动组件 -->
|
<!-- 情况三:作为活动组件 -->
|
||||||
<el-table
|
<el-table
|
||||||
v-if="isActivityComponent"
|
v-if="isActivityComponent"
|
||||||
|
ref="activitySkuListRef"
|
||||||
:data="formData!.skus"
|
:data="formData!.skus"
|
||||||
border
|
border
|
||||||
max-height="500"
|
max-height="500"
|
||||||
|
@ -10,22 +10,7 @@
|
|||||||
:rule-config="ruleConfig"
|
:rule-config="ruleConfig"
|
||||||
>
|
>
|
||||||
<template #extension>
|
<template #extension>
|
||||||
<el-table-column align="center" label="秒杀库存" min-width="168">
|
<slot></slot>
|
||||||
<template #default="{ row: sku }">
|
|
||||||
<el-input-number v-model="sku.productConfig.stock" :min="0" class="w-100%" />
|
|
||||||
</template>
|
|
||||||
</el-table-column>
|
|
||||||
<el-table-column align="center" label="秒杀价格(元)" min-width="168">
|
|
||||||
<template #default="{ row: sku }">
|
|
||||||
<el-input-number
|
|
||||||
v-model="sku.productConfig.seckillPrice"
|
|
||||||
:min="0"
|
|
||||||
:precision="2"
|
|
||||||
:step="0.1"
|
|
||||||
class="w-100%"
|
|
||||||
/>
|
|
||||||
</template>
|
|
||||||
</el-table-column>
|
|
||||||
</template>
|
</template>
|
||||||
</SkuList>
|
</SkuList>
|
||||||
</template>
|
</template>
|
||||||
@ -56,7 +41,7 @@ import { SpuProperty } from '@/views/mall/promotion/components/index'
|
|||||||
defineOptions({ name: 'PromotionSpuAndSkuList' })
|
defineOptions({ name: 'PromotionSpuAndSkuList' })
|
||||||
|
|
||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
spuList: T[]
|
spuList: T[] // TODO 为了方便兼容后续可能有需要展示多个 spu 的情况暂时保持,如果后续都是只操作一个 spu 的话则可更改为接受一个 spu 或保持
|
||||||
ruleConfig: RuleConfig[]
|
ruleConfig: RuleConfig[]
|
||||||
spuPropertyListP: SpuProperty<T>[]
|
spuPropertyListP: SpuProperty<T>[]
|
||||||
}>()
|
}>()
|
||||||
|
@ -145,17 +145,61 @@ const queryParams = ref({
|
|||||||
}) // 查询参数
|
}) // 查询参数
|
||||||
const propertyList = ref<Properties[]>([]) // 商品属性列表
|
const propertyList = ref<Properties[]>([]) // 商品属性列表
|
||||||
const spuListRef = ref<InstanceType<typeof ElTable>>()
|
const spuListRef = ref<InstanceType<typeof ElTable>>()
|
||||||
const spuData = ref<ProductSpuApi.Spu | {}>() // 商品详情
|
const spuData = ref<ProductSpuApi.Spu>() // 商品详情
|
||||||
const isExpand = ref(false) // 控制 SKU 列表显示
|
const isExpand = ref(false) // 控制 SKU 列表显示
|
||||||
const expandRowKeys = ref<number[]>() // 控制展开行需要设置 row-key 属性才能使用,该属性为展开行的 keys 数组。
|
const expandRowKeys = ref<number[]>() // 控制展开行需要设置 row-key 属性才能使用,该属性为展开行的 keys 数组。
|
||||||
|
|
||||||
|
//============ 商品选择相关 ============
|
||||||
|
const selectedSpuId = ref<number>(0) // 选中的商品 spuId
|
||||||
|
const selectedSkuIds = ref<number[]>([]) // 选中的商品 skuIds
|
||||||
|
const selectSku = (val: ProductSpuApi.Sku[]) => {
|
||||||
|
if (selectedSpuId.value === 0) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
selectedSkuIds.value = val.map((sku) => sku.id!)
|
||||||
|
}
|
||||||
|
const selectSpu = (val: ProductSpuApi.Spu[]) => {
|
||||||
|
if (val.length === 0) {
|
||||||
|
selectedSpuId.value = 0
|
||||||
|
return
|
||||||
|
}
|
||||||
|
// 只选择一个
|
||||||
|
selectedSpuId.value = val.map((spu) => spu.id!)[0]
|
||||||
|
// 切换选择 spu 如果有选择的 sku 则清空,确保选择的 sku 是对应的 spu 下面的
|
||||||
|
if (selectedSkuIds.value.length > 0) {
|
||||||
|
selectedSkuIds.value = []
|
||||||
|
}
|
||||||
|
// 如果大于1个
|
||||||
|
if (val.length > 1) {
|
||||||
|
// 清空选择
|
||||||
|
spuListRef.value.clearSelection()
|
||||||
|
// 变更为最后一次选择的
|
||||||
|
spuListRef.value.toggleRowSelection(val.pop(), true)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
expandChange(val[0], val)
|
||||||
|
}
|
||||||
|
|
||||||
// 计算商品属性
|
// 计算商品属性
|
||||||
const expandChange = async (row: ProductSpuApi.Spu, expandedRows: ProductSpuApi.Spu[]) => {
|
const expandChange = async (row: ProductSpuApi.Spu, expandedRows?: ProductSpuApi.Spu[]) => {
|
||||||
|
// 判断需要展开的 spuId === 选择的 spuId。如果选择了 A 就展开 A 的 skuList。如果选择了 A 手动展开 B 则阻断
|
||||||
|
// 目的防止误选 sku
|
||||||
|
if (selectedSpuId.value !== 0) {
|
||||||
|
if (row.id !== selectedSpuId.value) {
|
||||||
|
message.warning('你已选择商品请先取消')
|
||||||
|
expandRowKeys.value = [selectedSpuId.value]
|
||||||
|
return
|
||||||
|
}
|
||||||
|
// 如果以展开 skuList 则选择此对应的 spu 不需要重新获取渲染 skuList
|
||||||
|
if (isExpand.value && spuData.value?.id === row.id) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
spuData.value = {}
|
spuData.value = {}
|
||||||
propertyList.value = []
|
propertyList.value = []
|
||||||
isExpand.value = false
|
isExpand.value = false
|
||||||
|
if (expandedRows?.length === 0) {
|
||||||
// 如果展开个数为 0
|
// 如果展开个数为 0
|
||||||
if (expandedRows.length === 0) {
|
|
||||||
expandRowKeys.value = []
|
expandRowKeys.value = []
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@ -167,33 +211,15 @@ const expandChange = async (row: ProductSpuApi.Spu, expandedRows: ProductSpuApi.
|
|||||||
expandRowKeys.value = [row.id!]
|
expandRowKeys.value = [row.id!]
|
||||||
}
|
}
|
||||||
|
|
||||||
//============ 商品选择相关 ============
|
|
||||||
const selectedSpuIds = ref<number[]>([]) // 选中的商品 spuIds
|
|
||||||
const selectedSkuIds = ref<number[]>([]) // 选中的商品 skuIds
|
|
||||||
const selectSku = (val: ProductSpuApi.Sku[]) => {
|
|
||||||
selectedSkuIds.value = val.map((sku) => sku.id!)
|
|
||||||
}
|
|
||||||
const selectSpu = (val: ProductSpuApi.Spu[]) => {
|
|
||||||
selectedSpuIds.value = val.map((spu) => spu.id!)
|
|
||||||
// // 只选择一个
|
|
||||||
// selectedSpu.value = val[0]
|
|
||||||
// // 如果大于1个
|
|
||||||
// if (val.length > 1) {
|
|
||||||
// // 清空选择
|
|
||||||
// spuListRef.value.clearSelection()
|
|
||||||
// // 变更为最后一次选择的
|
|
||||||
// spuListRef.value.toggleRowSelection(val.pop(), true)
|
|
||||||
// }
|
|
||||||
}
|
|
||||||
// 确认选择时的触发事件
|
// 确认选择时的触发事件
|
||||||
const emits = defineEmits<{
|
const emits = defineEmits<{
|
||||||
(e: 'confirm', spuIds: number[], skuIds?: number[]): void
|
(e: 'confirm', spuId: number, skuIds?: number[]): void
|
||||||
}>()
|
}>()
|
||||||
/**
|
/**
|
||||||
* 确认选择返回选中的 spu 和 sku (如果需要选择sku的话)
|
* 确认选择返回选中的 spu 和 sku (如果需要选择sku的话)
|
||||||
*/
|
*/
|
||||||
const confirm = () => {
|
const confirm = () => {
|
||||||
if (selectedSpuIds.value.length === 0) {
|
if (selectedSpuId.value === 0) {
|
||||||
message.warning('没有选择任何商品')
|
message.warning('没有选择任何商品')
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@ -203,8 +229,8 @@ const confirm = () => {
|
|||||||
}
|
}
|
||||||
// 返回各自 id 列表
|
// 返回各自 id 列表
|
||||||
props.isSelectSku
|
props.isSelectSku
|
||||||
? emits('confirm', selectedSpuIds.value, selectedSkuIds.value)
|
? emits('confirm', selectedSpuId.value, selectedSkuIds.value)
|
||||||
: emits('confirm', selectedSpuIds.value)
|
: emits('confirm', selectedSpuId.value)
|
||||||
// 关闭弹窗
|
// 关闭弹窗
|
||||||
dialogVisible.value = false
|
dialogVisible.value = false
|
||||||
}
|
}
|
||||||
|
@ -8,30 +8,49 @@
|
|||||||
:schema="allSchemas.formSchema"
|
:schema="allSchemas.formSchema"
|
||||||
>
|
>
|
||||||
<!-- 先选择 -->
|
<!-- 先选择 -->
|
||||||
<template #spuIds>
|
<template #spuId>
|
||||||
<el-button @click="spuSelectRef.open()">选择商品</el-button>
|
<el-button @click="spuSelectRef.open()">选择商品</el-button>
|
||||||
<SpuAndSkuList
|
<SpuAndSkuList
|
||||||
ref="spuAndSkuListRef"
|
ref="spuAndSkuListRef"
|
||||||
:rule-config="ruleConfig"
|
:rule-config="ruleConfig"
|
||||||
:spu-list="spuList"
|
:spu-list="spuList"
|
||||||
:spu-property-list-p="spuPropertyList"
|
:spu-property-list-p="spuPropertyList"
|
||||||
|
>
|
||||||
|
<el-table-column align="center" label="秒杀库存" min-width="168">
|
||||||
|
<template #default="{ row: sku }">
|
||||||
|
<el-input-number v-model="sku.productConfig.stock" :min="0" class="w-100%" />
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
<el-table-column align="center" label="秒杀价格(元)" min-width="168">
|
||||||
|
<template #default="{ row: sku }">
|
||||||
|
<el-input-number
|
||||||
|
v-model="sku.productConfig.seckillPrice"
|
||||||
|
:min="0"
|
||||||
|
:precision="2"
|
||||||
|
:step="0.1"
|
||||||
|
class="w-100%"
|
||||||
/>
|
/>
|
||||||
</template>
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
</SpuAndSkuList>
|
||||||
|
</template>
|
||||||
</Form>
|
</Form>
|
||||||
<template #footer>
|
<template #footer>
|
||||||
<el-button :disabled="formLoading" type="primary" @click="submitForm">确 定</el-button>
|
<el-button :disabled="formLoading" type="primary" @click="submitForm">确 定</el-button>
|
||||||
<el-button @click="dialogVisible = false">取 消</el-button>
|
<el-button @click="dialogVisible = false">取 消</el-button>
|
||||||
</template>
|
</template>
|
||||||
</Dialog>
|
</Dialog>
|
||||||
<SpuSelect ref="spuSelectRef" @confirm="selectSpu" />
|
<SpuSelect ref="spuSelectRef" :isSelectSku="true" @confirm="selectSpu" />
|
||||||
</template>
|
</template>
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { SpuAndSkuList, SpuProperty, SpuSelect } from '../../components'
|
import { SpuAndSkuList, SpuProperty, SpuSelect } from '../../components'
|
||||||
import { allSchemas, rules } from './seckillActivity.data'
|
import { allSchemas, rules } from './seckillActivity.data'
|
||||||
|
|
||||||
import * as SeckillActivityApi from '@/api/mall/promotion/seckill/seckillActivity'
|
import * as SeckillActivityApi from '@/api/mall/promotion/seckill/seckillActivity'
|
||||||
|
import { SeckillProductVO } from '@/api/mall/promotion/seckill/seckillActivity'
|
||||||
import * as ProductSpuApi from '@/api/mall/product/spu'
|
import * as ProductSpuApi from '@/api/mall/product/spu'
|
||||||
import { getPropertyList, RuleConfig } from '@/views/mall/product/spu/components'
|
import { getPropertyList, RuleConfig } from '@/views/mall/product/spu/components'
|
||||||
|
import { convertToInteger } from '@/utils'
|
||||||
|
|
||||||
defineOptions({ name: 'PromotionSeckillActivityForm' })
|
defineOptions({ name: 'PromotionSeckillActivityForm' })
|
||||||
|
|
||||||
@ -43,6 +62,9 @@ const dialogTitle = ref('') // 弹窗的标题
|
|||||||
const formLoading = ref(false) // 表单的加载中:1)修改时的数据加载;2)提交的按钮禁用
|
const formLoading = ref(false) // 表单的加载中:1)修改时的数据加载;2)提交的按钮禁用
|
||||||
const formType = ref('') // 表单的类型:create - 新增;update - 修改
|
const formType = ref('') // 表单的类型:create - 新增;update - 修改
|
||||||
const formRef = ref() // 表单 Ref
|
const formRef = ref() // 表单 Ref
|
||||||
|
|
||||||
|
// ================= 商品选择相关 =================
|
||||||
|
|
||||||
const spuSelectRef = ref() // 商品和属性选择 Ref
|
const spuSelectRef = ref() // 商品和属性选择 Ref
|
||||||
const spuAndSkuListRef = ref() // sku 秒杀配置组件Ref
|
const spuAndSkuListRef = ref() // sku 秒杀配置组件Ref
|
||||||
const ruleConfig: RuleConfig[] = [
|
const ruleConfig: RuleConfig[] = [
|
||||||
@ -57,6 +79,46 @@ const ruleConfig: RuleConfig[] = [
|
|||||||
message: '商品秒杀价格必须大于 0.01 !!!'
|
message: '商品秒杀价格必须大于 0.01 !!!'
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
const spuList = ref<SeckillActivityApi.SpuExtension[]>([]) // 选择的 spu
|
||||||
|
const spuPropertyList = ref<SpuProperty<SeckillActivityApi.SpuExtension>[]>([])
|
||||||
|
const selectSpu = (spuId: number, skuIds: number[]) => {
|
||||||
|
formRef.value.setValues({ spuId })
|
||||||
|
getSpuDetails(spuId, skuIds)
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* 获取 SPU 详情
|
||||||
|
* @param spuIds
|
||||||
|
*/
|
||||||
|
const getSpuDetails = async (spuId: number, skuIds: number[]) => {
|
||||||
|
const spuProperties: SpuProperty<SeckillActivityApi.SpuExtension>[] = []
|
||||||
|
const res = (await ProductSpuApi.getSpuDetailList([spuId])) as SeckillActivityApi.SpuExtension[]
|
||||||
|
if (res.length == 0) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
spuList.value = []
|
||||||
|
// 因为只能选择一个
|
||||||
|
const spu = res[0]
|
||||||
|
const selectSkus = spu?.skus?.filter((sku) => skuIds.includes(sku.id!))
|
||||||
|
selectSkus?.forEach((sku) => {
|
||||||
|
const config: SeckillActivityApi.SeckillProductVO = {
|
||||||
|
skuId: sku.id!,
|
||||||
|
stock: 0,
|
||||||
|
seckillPrice: 0
|
||||||
|
}
|
||||||
|
sku.productConfig = config
|
||||||
|
})
|
||||||
|
spu.skus = selectSkus as SeckillActivityApi.SkuExtension[]
|
||||||
|
spuProperties.push({
|
||||||
|
spuId: spu.id!,
|
||||||
|
spuDetail: spu,
|
||||||
|
propertyList: getPropertyList(spu)
|
||||||
|
})
|
||||||
|
spuList.value.push(...res)
|
||||||
|
spuPropertyList.value = spuProperties
|
||||||
|
}
|
||||||
|
|
||||||
|
// ================= end =================
|
||||||
|
|
||||||
/** 打开弹窗 */
|
/** 打开弹窗 */
|
||||||
const open = async (type: string, id?: number) => {
|
const open = async (type: string, id?: number) => {
|
||||||
dialogVisible.value = true
|
dialogVisible.value = true
|
||||||
@ -76,38 +138,6 @@ const open = async (type: string, id?: number) => {
|
|||||||
}
|
}
|
||||||
defineExpose({ open }) // 提供 open 方法,用于打开弹窗
|
defineExpose({ open }) // 提供 open 方法,用于打开弹窗
|
||||||
|
|
||||||
const spuList = ref<SeckillActivityApi.SpuExtension[]>([]) // 选择的 spu
|
|
||||||
const spuPropertyList = ref<SpuProperty<SeckillActivityApi.SpuExtension>[]>([])
|
|
||||||
const selectSpu = (spuIds: number[]) => {
|
|
||||||
formRef.value.setValues({ spuIds })
|
|
||||||
getSpuDetails(spuIds)
|
|
||||||
}
|
|
||||||
/**
|
|
||||||
* 获取 SPU 详情
|
|
||||||
* TODO 获取 SPU 详情,放到各自活动表单来做,让 SpuAndSkuList 职责单一点
|
|
||||||
* @param spuIds
|
|
||||||
*/
|
|
||||||
const getSpuDetails = async (spuIds: number[]) => {
|
|
||||||
const spuProperties: SpuProperty<SeckillActivityApi.SpuExtension>[] = []
|
|
||||||
const res = (await ProductSpuApi.getSpuDetailList(spuIds)) as SeckillActivityApi.SpuExtension[]
|
|
||||||
spuList.value = []
|
|
||||||
res?.forEach((spu) => {
|
|
||||||
// 初始化每个 sku 秒杀配置
|
|
||||||
spu.skus?.forEach((sku) => {
|
|
||||||
const config: SeckillActivityApi.SeckillProductVO = {
|
|
||||||
spuId: spu.id!,
|
|
||||||
skuId: sku.id!,
|
|
||||||
stock: 0,
|
|
||||||
seckillPrice: 0
|
|
||||||
}
|
|
||||||
sku.productConfig = config
|
|
||||||
})
|
|
||||||
spuProperties.push({ spuId: spu.id!, spuDetail: spu, propertyList: getPropertyList(spu) })
|
|
||||||
})
|
|
||||||
spuList.value.push(...res)
|
|
||||||
spuPropertyList.value = spuProperties
|
|
||||||
}
|
|
||||||
|
|
||||||
/** 重置表单 */
|
/** 重置表单 */
|
||||||
const resetForm = async () => {
|
const resetForm = async () => {
|
||||||
spuList.value = []
|
spuList.value = []
|
||||||
@ -126,7 +156,12 @@ const submitForm = async () => {
|
|||||||
formLoading.value = true
|
formLoading.value = true
|
||||||
try {
|
try {
|
||||||
const data = formRef.value.formModel as SeckillActivityApi.SeckillActivityVO
|
const data = formRef.value.formModel as SeckillActivityApi.SeckillActivityVO
|
||||||
data.spuIds = spuList.value.map((spu) => spu.id!)
|
const products = spuAndSkuListRef.value.getSkuConfigs('productConfig')
|
||||||
|
products.forEach((item: SeckillProductVO) => {
|
||||||
|
// 秒杀价格元转分
|
||||||
|
item.seckillPrice = convertToInteger(item.seckillPrice)
|
||||||
|
})
|
||||||
|
// 获取秒杀商品配置
|
||||||
data.products = spuAndSkuListRef.value.getSkuConfigs('productConfig')
|
data.products = spuAndSkuListRef.value.getSkuConfigs('productConfig')
|
||||||
if (formType.value === 'create') {
|
if (formType.value === 'create') {
|
||||||
await SeckillActivityApi.createSeckillActivity(data)
|
await SeckillActivityApi.createSeckillActivity(data)
|
||||||
|
@ -152,6 +152,17 @@ const crudSchemas = reactive<CrudSchema[]>([
|
|||||||
width: 120
|
width: 120
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
label: '排序',
|
||||||
|
field: 'sort',
|
||||||
|
form: {
|
||||||
|
component: 'InputNumber',
|
||||||
|
value: 0
|
||||||
|
},
|
||||||
|
table: {
|
||||||
|
width: 80
|
||||||
|
}
|
||||||
|
},
|
||||||
{
|
{
|
||||||
label: '秒杀库存',
|
label: '秒杀库存',
|
||||||
field: 'stock',
|
field: 'stock',
|
||||||
@ -167,17 +178,14 @@ const crudSchemas = reactive<CrudSchema[]>([
|
|||||||
{
|
{
|
||||||
label: '秒杀总库存',
|
label: '秒杀总库存',
|
||||||
field: 'totalStock',
|
field: 'totalStock',
|
||||||
form: {
|
isForm: false,
|
||||||
component: 'InputNumber',
|
|
||||||
value: 0
|
|
||||||
},
|
|
||||||
table: {
|
table: {
|
||||||
width: 120
|
width: 120
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: '秒杀活动商品',
|
label: '秒杀活动商品',
|
||||||
field: 'spuIds',
|
field: 'spuId',
|
||||||
isTable: false,
|
isTable: false,
|
||||||
isSearch: false,
|
isSearch: false,
|
||||||
form: {
|
form: {
|
||||||
@ -206,17 +214,6 @@ const crudSchemas = reactive<CrudSchema[]>([
|
|||||||
width: 120
|
width: 120
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
|
||||||
label: '排序',
|
|
||||||
field: 'sort',
|
|
||||||
form: {
|
|
||||||
component: 'InputNumber',
|
|
||||||
value: 0
|
|
||||||
},
|
|
||||||
table: {
|
|
||||||
width: 80
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
label: '状态',
|
label: '状态',
|
||||||
field: 'status',
|
field: 'status',
|
||||||
|
Loading…
Reference in New Issue
Block a user