Browse Source

fix: mall SeckillActivity

puhui999 1 year ago
parent
commit
36c0bce184

+ 1 - 2
src/api/mall/promotion/seckill/seckillActivity.ts

@@ -23,14 +23,13 @@ export interface SeckillActivityVO {
 
 // 秒杀活动所需属性
 export interface SeckillProductVO {
-  spuId: number
   skuId: number
   seckillPrice: number
   stock: number
 }
 
 // 扩展 Sku 配置
-type SkuExtension = Sku & {
+export type SkuExtension = Sku & {
   productConfig: SeckillProductVO
 }
 

+ 1 - 0
src/views/mall/product/spu/components/SkuList.vue

@@ -194,6 +194,7 @@
   <!-- 情况三:作为活动组件 -->
   <el-table
     v-if="isActivityComponent"
+    ref="activitySkuListRef"
     :data="formData!.skus"
     border
     max-height="500"

+ 2 - 17
src/views/mall/promotion/components/SpuAndSkuList.vue

@@ -10,22 +10,7 @@
           :rule-config="ruleConfig"
         >
           <template #extension>
-            <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>
-            </el-table-column>
+            <slot></slot>
           </template>
         </SkuList>
       </template>
@@ -56,7 +41,7 @@ import { SpuProperty } from '@/views/mall/promotion/components/index'
 defineOptions({ name: 'PromotionSpuAndSkuList' })
 
 const props = defineProps<{
-  spuList: T[]
+  spuList: T[] // TODO 为了方便兼容后续可能有需要展示多个 spu 的情况暂时保持,如果后续都是只操作一个 spu 的话则可更改为接受一个 spu 或保持
   ruleConfig: RuleConfig[]
   spuPropertyListP: SpuProperty<T>[]
 }>()

+ 52 - 26
src/views/mall/promotion/components/SpuSelect.vue

@@ -145,17 +145,61 @@ const queryParams = ref({
 }) // 查询参数
 const propertyList = ref<Properties[]>([]) // 商品属性列表
 const spuListRef = ref<InstanceType<typeof ElTable>>()
-const spuData = ref<ProductSpuApi.Spu | {}>() // 商品详情
+const spuData = ref<ProductSpuApi.Spu>() // 商品详情
 const isExpand = ref(false) // 控制 SKU 列表显示
 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 = {}
   propertyList.value = []
   isExpand.value = false
-  // 如果展开个数为 0
-  if (expandedRows.length === 0) {
+  if (expandedRows?.length === 0) {
+    // 如果展开个数为 0
     expandRowKeys.value = []
     return
   }
@@ -167,33 +211,15 @@ const expandChange = async (row: ProductSpuApi.Spu, expandedRows: ProductSpuApi.
   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<{
-  (e: 'confirm', spuIds: number[], skuIds?: number[]): void
+  (e: 'confirm', spuId: number, skuIds?: number[]): void
 }>()
 /**
  * 确认选择返回选中的 spu 和 sku (如果需要选择sku的话)
  */
 const confirm = () => {
-  if (selectedSpuIds.value.length === 0) {
+  if (selectedSpuId.value === 0) {
     message.warning('没有选择任何商品')
     return
   }
@@ -203,8 +229,8 @@ const confirm = () => {
   }
   // 返回各自 id 列表
   props.isSelectSku
-    ? emits('confirm', selectedSpuIds.value, selectedSkuIds.value)
-    : emits('confirm', selectedSpuIds.value)
+    ? emits('confirm', selectedSpuId.value, selectedSkuIds.value)
+    : emits('confirm', selectedSpuId.value)
   // 关闭弹窗
   dialogVisible.value = false
 }

+ 71 - 36
src/views/mall/promotion/seckill/activity/SeckillActivityForm.vue

@@ -8,14 +8,31 @@
       :schema="allSchemas.formSchema"
     >
       <!-- 先选择 -->
-      <template #spuIds>
+      <template #spuId>
         <el-button @click="spuSelectRef.open()">选择商品</el-button>
         <SpuAndSkuList
           ref="spuAndSkuListRef"
           :rule-config="ruleConfig"
           :spu-list="spuList"
           :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>
+          </el-table-column>
+        </SpuAndSkuList>
       </template>
     </Form>
     <template #footer>
@@ -23,15 +40,17 @@
       <el-button @click="dialogVisible = false">取 消</el-button>
     </template>
   </Dialog>
-  <SpuSelect ref="spuSelectRef" @confirm="selectSpu" />
+  <SpuSelect ref="spuSelectRef" :isSelectSku="true" @confirm="selectSpu" />
 </template>
 <script lang="ts" setup>
 import { SpuAndSkuList, SpuProperty, SpuSelect } from '../../components'
 import { allSchemas, rules } from './seckillActivity.data'
 
 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 { getPropertyList, RuleConfig } from '@/views/mall/product/spu/components'
+import { convertToInteger } from '@/utils'
 
 defineOptions({ name: 'PromotionSeckillActivityForm' })
 
@@ -43,6 +62,9 @@ const dialogTitle = ref('') // 弹窗的标题
 const formLoading = ref(false) // 表单的加载中:1)修改时的数据加载;2)提交的按钮禁用
 const formType = ref('') // 表单的类型:create - 新增;update - 修改
 const formRef = ref() // 表单 Ref
+
+// ================= 商品选择相关 =================
+
 const spuSelectRef = ref() // 商品和属性选择 Ref
 const spuAndSkuListRef = ref() // sku 秒杀配置组件Ref
 const ruleConfig: RuleConfig[] = [
@@ -57,6 +79,46 @@ const ruleConfig: RuleConfig[] = [
     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) => {
   dialogVisible.value = true
@@ -76,38 +138,6 @@ const open = async (type: string, id?: number) => {
 }
 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 () => {
   spuList.value = []
@@ -126,7 +156,12 @@ const submitForm = async () => {
   formLoading.value = true
   try {
     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')
     if (formType.value === 'create') {
       await SeckillActivityApi.createSeckillActivity(data)

+ 13 - 16
src/views/mall/promotion/seckill/activity/seckillActivity.data.ts

@@ -152,6 +152,17 @@ const crudSchemas = reactive<CrudSchema[]>([
       width: 120
     }
   },
+  {
+    label: '排序',
+    field: 'sort',
+    form: {
+      component: 'InputNumber',
+      value: 0
+    },
+    table: {
+      width: 80
+    }
+  },
   {
     label: '秒杀库存',
     field: 'stock',
@@ -167,17 +178,14 @@ const crudSchemas = reactive<CrudSchema[]>([
   {
     label: '秒杀总库存',
     field: 'totalStock',
-    form: {
-      component: 'InputNumber',
-      value: 0
-    },
+    isForm: false,
     table: {
       width: 120
     }
   },
   {
     label: '秒杀活动商品',
-    field: 'spuIds',
+    field: 'spuId',
     isTable: false,
     isSearch: false,
     form: {
@@ -206,17 +214,6 @@ const crudSchemas = reactive<CrudSchema[]>([
       width: 120
     }
   },
-  {
-    label: '排序',
-    field: 'sort',
-    form: {
-      component: 'InputNumber',
-      value: 0
-    },
-    table: {
-      width: 80
-    }
-  },
   {
     label: '状态',
     field: 'status',