mirror of
				https://gitee.com/hhyykk/ipms-sjy-ui.git
				synced 2025-11-04 20:28:45 +08:00 
			
		
		
		
	!176 mall seckillActivity
Merge pull request !176 from puhui999/dev-to-dev
This commit is contained in:
		@@ -47,17 +47,13 @@ export interface Spu {
 | 
				
			|||||||
  recommendBest?: boolean // 是否精品
 | 
					  recommendBest?: boolean // 是否精品
 | 
				
			||||||
  recommendNew?: boolean // 是否新品
 | 
					  recommendNew?: boolean // 是否新品
 | 
				
			||||||
  recommendGood?: boolean // 是否优品
 | 
					  recommendGood?: boolean // 是否优品
 | 
				
			||||||
}
 | 
					  price?: number // 商品价格
 | 
				
			||||||
 | 
					  salesCount?: number // 商品销量
 | 
				
			||||||
// TODO @puhui999: SpuRespVO 合并到 SPU 里?前端少点 VO 类哈;
 | 
					  marketPrice?: number // 市场价
 | 
				
			||||||
export interface SpuRespVO extends Spu {
 | 
					  costPrice?: number // 成本价
 | 
				
			||||||
  price: number
 | 
					  stock?: number // 商品库存
 | 
				
			||||||
  salesCount: number
 | 
					  createTime?: Date // 商品创建时间
 | 
				
			||||||
  marketPrice: number
 | 
					  status?: number // 商品状态
 | 
				
			||||||
  costPrice: number
 | 
					 | 
				
			||||||
  stock: number
 | 
					 | 
				
			||||||
  createTime: Date
 | 
					 | 
				
			||||||
  status: number
 | 
					 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// 获得 Spu 列表
 | 
					// 获得 Spu 列表
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,5 +1,5 @@
 | 
				
			|||||||
import request from '@/config/axios'
 | 
					import request from '@/config/axios'
 | 
				
			||||||
import { Sku, SpuRespVO } from '@/api/mall/product/spu'
 | 
					import { Sku, Spu } from '@/api/mall/product/spu'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export interface SeckillActivityVO {
 | 
					export interface SeckillActivityVO {
 | 
				
			||||||
  id: number
 | 
					  id: number
 | 
				
			||||||
@@ -21,6 +21,7 @@ export interface SeckillActivityVO {
 | 
				
			|||||||
  products: SeckillProductVO[]
 | 
					  products: SeckillProductVO[]
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// 秒杀活动所需属性
 | 
				
			||||||
export interface SeckillProductVO {
 | 
					export interface SeckillProductVO {
 | 
				
			||||||
  spuId: number
 | 
					  spuId: number
 | 
				
			||||||
  skuId: number
 | 
					  skuId: number
 | 
				
			||||||
@@ -28,11 +29,12 @@ export interface SeckillProductVO {
 | 
				
			|||||||
  stock: number
 | 
					  stock: number
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// 扩展 Sku 配置
 | 
				
			||||||
type SkuExtension = Sku & {
 | 
					type SkuExtension = Sku & {
 | 
				
			||||||
  productConfig: SeckillProductVO
 | 
					  productConfig: SeckillProductVO
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export interface SpuExtension extends SpuRespVO {
 | 
					export interface SpuExtension extends Spu {
 | 
				
			||||||
  skus: SkuExtension[] // 重写类型
 | 
					  skus: SkuExtension[] // 重写类型
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -55,9 +55,9 @@ const { delView } = useTagsViewStore() // 视图操作
 | 
				
			|||||||
const formLoading = ref(false) // 表单的加载中:1)修改时的数据加载;2)提交的按钮禁用
 | 
					const formLoading = ref(false) // 表单的加载中:1)修改时的数据加载;2)提交的按钮禁用
 | 
				
			||||||
const activeName = ref('basicInfo') // Tag 激活的窗口
 | 
					const activeName = ref('basicInfo') // Tag 激活的窗口
 | 
				
			||||||
const isDetail = ref(false) // 是否查看详情
 | 
					const isDetail = ref(false) // 是否查看详情
 | 
				
			||||||
const basicInfoRef = ref<ComponentRef<typeof BasicInfoForm>>() // 商品信息Ref
 | 
					const basicInfoRef = ref() // 商品信息Ref
 | 
				
			||||||
const descriptionRef = ref<ComponentRef<typeof DescriptionForm>>() // 商品详情Ref
 | 
					const descriptionRef = ref() // 商品详情Ref
 | 
				
			||||||
const otherSettingsRef = ref<ComponentRef<typeof OtherSettingsForm>>() // 其他设置Ref
 | 
					const otherSettingsRef = ref() // 其他设置Ref
 | 
				
			||||||
// spu 表单数据
 | 
					// spu 表单数据
 | 
				
			||||||
const formData = ref<ProductSpuApi.Spu>({
 | 
					const formData = ref<ProductSpuApi.Spu>({
 | 
				
			||||||
  name: '', // 商品名称
 | 
					  name: '', // 商品名称
 | 
				
			||||||
@@ -98,7 +98,7 @@ const formData = ref<ProductSpuApi.Spu>({
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
/** 获得详情 */
 | 
					/** 获得详情 */
 | 
				
			||||||
const getDetail = async () => {
 | 
					const getDetail = async () => {
 | 
				
			||||||
  if ('productSpuDetail' === name) {
 | 
					  if ('ProductSpuDetail' === name) {
 | 
				
			||||||
    isDetail.value = true
 | 
					    isDetail.value = true
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
  const id = params.spuId as number
 | 
					  const id = params.spuId as number
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -176,7 +176,7 @@ import { checkSelectedNode, defaultProps, handleTree, treeToString } from '@/uti
 | 
				
			|||||||
import { createImageViewer } from '@/components/ImageViewer'
 | 
					import { createImageViewer } from '@/components/ImageViewer'
 | 
				
			||||||
import { DICT_TYPE, getIntDictOptions } from '@/utils/dict'
 | 
					import { DICT_TYPE, getIntDictOptions } from '@/utils/dict'
 | 
				
			||||||
import { UploadImg, UploadImgs } from '@/components/UploadFile'
 | 
					import { UploadImg, UploadImgs } from '@/components/UploadFile'
 | 
				
			||||||
import { ProductAttributes, ProductPropertyAddForm, SkuList } from './index'
 | 
					import { getPropertyList, ProductAttributes, ProductPropertyAddForm, SkuList } from './index'
 | 
				
			||||||
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'
 | 
				
			||||||
@@ -242,7 +242,7 @@ const rules = reactive({
 | 
				
			|||||||
  introduction: [required],
 | 
					  introduction: [required],
 | 
				
			||||||
  picUrl: [required],
 | 
					  picUrl: [required],
 | 
				
			||||||
  sliderPicUrls: [required],
 | 
					  sliderPicUrls: [required],
 | 
				
			||||||
  // deliveryTemplateId: [required],
 | 
					  deliveryTemplateId: [required],
 | 
				
			||||||
  brandId: [required],
 | 
					  brandId: [required],
 | 
				
			||||||
  specType: [required],
 | 
					  specType: [required],
 | 
				
			||||||
  subCommissionType: [required]
 | 
					  subCommissionType: [required]
 | 
				
			||||||
@@ -261,26 +261,7 @@ watch(
 | 
				
			|||||||
    formData.sliderPicUrls = data['sliderPicUrls']?.map((item) => ({
 | 
					    formData.sliderPicUrls = data['sliderPicUrls']?.map((item) => ({
 | 
				
			||||||
      url: item
 | 
					      url: item
 | 
				
			||||||
    }))
 | 
					    }))
 | 
				
			||||||
    // 只有是多规格才处理
 | 
					    propertyList.value = getPropertyList(data)
 | 
				
			||||||
    if (!formData.specType) {
 | 
					 | 
				
			||||||
      return
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
    //  直接拿返回的 skus 属性逆向生成出 propertyList
 | 
					 | 
				
			||||||
    const properties = []
 | 
					 | 
				
			||||||
    formData.skus?.forEach((sku) => {
 | 
					 | 
				
			||||||
      sku.properties?.forEach(({ propertyId, propertyName, valueId, valueName }) => {
 | 
					 | 
				
			||||||
        // 添加属性
 | 
					 | 
				
			||||||
        if (!properties?.some((item) => item.id === propertyId)) {
 | 
					 | 
				
			||||||
          properties.push({ id: propertyId, name: propertyName, values: [] })
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
        // 添加属性值
 | 
					 | 
				
			||||||
        const index = properties?.findIndex((item) => item.id === propertyId)
 | 
					 | 
				
			||||||
        if (!properties[index].values?.some((value) => value.id === valueId)) {
 | 
					 | 
				
			||||||
          properties[index].values?.push({ id: valueId, name: valueName })
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
      })
 | 
					 | 
				
			||||||
    })
 | 
					 | 
				
			||||||
    propertyList.value = properties
 | 
					 | 
				
			||||||
  },
 | 
					  },
 | 
				
			||||||
  {
 | 
					  {
 | 
				
			||||||
    immediate: true
 | 
					    immediate: true
 | 
				
			||||||
@@ -293,10 +274,7 @@ watch(
 | 
				
			|||||||
const emit = defineEmits(['update:activeName'])
 | 
					const emit = defineEmits(['update:activeName'])
 | 
				
			||||||
const validate = async () => {
 | 
					const validate = async () => {
 | 
				
			||||||
  // 校验 sku
 | 
					  // 校验 sku
 | 
				
			||||||
  if (!skuListRef.value.validateSku()) {
 | 
					  skuListRef.value.validateSku()
 | 
				
			||||||
    message.warning('商品相关价格不能低于 0.01 元!!')
 | 
					 | 
				
			||||||
    throw new Error('商品相关价格不能低于 0.01 元!!')
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
  // 校验表单
 | 
					  // 校验表单
 | 
				
			||||||
  if (!productSpuBasicInfoRef) return
 | 
					  if (!productSpuBasicInfoRef) return
 | 
				
			||||||
  return await unref(productSpuBasicInfoRef).validate((valid) => {
 | 
					  return await unref(productSpuBasicInfoRef).validate((valid) => {
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -259,8 +259,10 @@ import { UploadImg } from '@/components/UploadFile'
 | 
				
			|||||||
import type { Property, Sku, Spu } from '@/api/mall/product/spu'
 | 
					import type { Property, Sku, Spu } from '@/api/mall/product/spu'
 | 
				
			||||||
import { createImageViewer } from '@/components/ImageViewer'
 | 
					import { createImageViewer } from '@/components/ImageViewer'
 | 
				
			||||||
import { RuleConfig } from '@/views/mall/product/spu/components/index'
 | 
					import { RuleConfig } from '@/views/mall/product/spu/components/index'
 | 
				
			||||||
 | 
					import { Properties } from './index'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
defineOptions({ name: 'SkuList' })
 | 
					defineOptions({ name: 'SkuList' })
 | 
				
			||||||
 | 
					const message = useMessage() // 消息弹窗
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const props = defineProps({
 | 
					const props = defineProps({
 | 
				
			||||||
  propFormData: {
 | 
					  propFormData: {
 | 
				
			||||||
@@ -268,7 +270,7 @@ const props = defineProps({
 | 
				
			|||||||
    default: () => {}
 | 
					    default: () => {}
 | 
				
			||||||
  },
 | 
					  },
 | 
				
			||||||
  propertyList: {
 | 
					  propertyList: {
 | 
				
			||||||
    type: Array,
 | 
					    type: Array as PropType<Properties[]>,
 | 
				
			||||||
    default: () => []
 | 
					    default: () => []
 | 
				
			||||||
  },
 | 
					  },
 | 
				
			||||||
  ruleConfig: {
 | 
					  ruleConfig: {
 | 
				
			||||||
@@ -323,26 +325,47 @@ const tableHeaders = ref<{ prop: string; label: string }[]>([]) // 多属性表
 | 
				
			|||||||
/**
 | 
					/**
 | 
				
			||||||
 * 保存时,每个商品规格的表单要校验下。例如说,销售金额最低是 0.01 这种。
 | 
					 * 保存时,每个商品规格的表单要校验下。例如说,销售金额最低是 0.01 这种。
 | 
				
			||||||
 */
 | 
					 */
 | 
				
			||||||
const validateSku = (): boolean => {
 | 
					const validateSku = () => {
 | 
				
			||||||
  const checks = ['price', 'marketPrice', 'costPrice']
 | 
					  const checks = ['price', 'marketPrice', 'costPrice']
 | 
				
			||||||
 | 
					  let warningInfo = '请检查商品各行相关属性配置,'
 | 
				
			||||||
  let validate = true // 默认通过
 | 
					  let validate = true // 默认通过
 | 
				
			||||||
  for (const sku of formData.value!.skus) {
 | 
					  for (const sku of formData.value!.skus!) {
 | 
				
			||||||
    // 作为活动组件的校验
 | 
					    // 作为活动组件的校验
 | 
				
			||||||
    if (props.isActivityComponent) {
 | 
					    if (props.isActivityComponent) {
 | 
				
			||||||
      for (const rule of props.ruleConfig) {
 | 
					      for (const rule of props.ruleConfig) {
 | 
				
			||||||
        if (sku[rule.name] < rule.geValue) {
 | 
					        const arg = getValue(sku, rule.name)
 | 
				
			||||||
 | 
					        if (!rule.rule(arg)) {
 | 
				
			||||||
          validate = false // 只要有一个不通过则直接不通过
 | 
					          validate = false // 只要有一个不通过则直接不通过
 | 
				
			||||||
 | 
					          warningInfo += rule.message
 | 
				
			||||||
          break
 | 
					          break
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
    } else {
 | 
					    } else {
 | 
				
			||||||
      if (checks.some((check) => sku[check] < 0.01)) {
 | 
					      if (checks.some((check) => sku[check] < 0.01)) {
 | 
				
			||||||
        validate = false // 只要有一个不通过则直接不通过
 | 
					        validate = false // 只要有一个不通过则直接不通过
 | 
				
			||||||
 | 
					        warningInfo = '商品相关价格不能低于 0.01 元!!'
 | 
				
			||||||
        break
 | 
					        break
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					    // 只要有一个不通过则结束后续的校验
 | 
				
			||||||
 | 
					    if (!validate) {
 | 
				
			||||||
 | 
					      message.warning(warningInfo)
 | 
				
			||||||
 | 
					      throw new Error(warningInfo)
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
  return validate
 | 
					  }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					const getValue = (obj, arg) => {
 | 
				
			||||||
 | 
					  const keys = arg.split('.')
 | 
				
			||||||
 | 
					  let value = obj
 | 
				
			||||||
 | 
					  for (const key of keys) {
 | 
				
			||||||
 | 
					    if (value && typeof value === 'object' && key in value) {
 | 
				
			||||||
 | 
					      value = value[key]
 | 
				
			||||||
 | 
					    } else {
 | 
				
			||||||
 | 
					      value = undefined
 | 
				
			||||||
 | 
					      break
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					  return value
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const emit = defineEmits<{
 | 
					const emit = defineEmits<{
 | 
				
			||||||
@@ -417,13 +440,13 @@ const generateTableData = (propertyList: any[]) => {
 | 
				
			|||||||
 * 生成 skus 前置校验
 | 
					 * 生成 skus 前置校验
 | 
				
			||||||
 */
 | 
					 */
 | 
				
			||||||
const validateData = (propertyList: any[]) => {
 | 
					const validateData = (propertyList: any[]) => {
 | 
				
			||||||
  const skuPropertyIds = []
 | 
					  const skuPropertyIds: number[] = []
 | 
				
			||||||
  formData.value!.skus!.forEach((sku) =>
 | 
					  formData.value!.skus!.forEach((sku) =>
 | 
				
			||||||
    sku.properties
 | 
					    sku.properties
 | 
				
			||||||
      ?.map((property) => property.propertyId)
 | 
					      ?.map((property) => property.propertyId)
 | 
				
			||||||
      .forEach((propertyId) => {
 | 
					      ?.forEach((propertyId) => {
 | 
				
			||||||
        if (skuPropertyIds.indexOf(propertyId) === -1) {
 | 
					        if (skuPropertyIds.indexOf(propertyId!) === -1) {
 | 
				
			||||||
          skuPropertyIds.push(propertyId)
 | 
					          skuPropertyIds.push(propertyId!)
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
      })
 | 
					      })
 | 
				
			||||||
  )
 | 
					  )
 | 
				
			||||||
@@ -457,7 +480,7 @@ const build = (propertyValuesList: Property[][]) => {
 | 
				
			|||||||
/** 监听属性列表,生成相关参数和表头 */
 | 
					/** 监听属性列表,生成相关参数和表头 */
 | 
				
			||||||
watch(
 | 
					watch(
 | 
				
			||||||
  () => props.propertyList,
 | 
					  () => props.propertyList,
 | 
				
			||||||
  (propertyList) => {
 | 
					  (propertyList: Properties[]) => {
 | 
				
			||||||
    // 如果不是多规格则结束
 | 
					    // 如果不是多规格则结束
 | 
				
			||||||
    if (!formData.value!.specType) {
 | 
					    if (!formData.value!.specType) {
 | 
				
			||||||
      return
 | 
					      return
 | 
				
			||||||
@@ -497,7 +520,7 @@ watch(
 | 
				
			|||||||
      return
 | 
					      return
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
    // 添加新属性没有属性值也不做处理
 | 
					    // 添加新属性没有属性值也不做处理
 | 
				
			||||||
    if (propertyList.some((item) => item.values.length === 0)) {
 | 
					    if (propertyList.some((item) => item.values!.length === 0)) {
 | 
				
			||||||
      return
 | 
					      return
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
    // 生成 table 数据,即 sku 列表
 | 
					    // 生成 table 数据,即 sku 列表
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -14,8 +14,19 @@ interface Properties {
 | 
				
			|||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
interface RuleConfig {
 | 
					interface RuleConfig {
 | 
				
			||||||
  name: string // 需要校验的字段
 | 
					  // 需要校验的字段
 | 
				
			||||||
  geValue: number // TODO 暂定大于一个数字
 | 
					  // 例:name: 'name' 则表示校验 sku.name 的值
 | 
				
			||||||
 | 
					  // 例:name: 'productConfig.stock' 则表示校验 sku.productConfig.name 的值,此处 productConfig 表示我在 Sku 上扩展的属性
 | 
				
			||||||
 | 
					  name: string
 | 
				
			||||||
 | 
					  // 校验规格为一个毁掉函数,其中 arg 为需要校验的字段的值。
 | 
				
			||||||
 | 
					  // 例:需要校验价格必须大于0.01
 | 
				
			||||||
 | 
					  // {
 | 
				
			||||||
 | 
					  //  name:'price',
 | 
				
			||||||
 | 
					  //  rule:(arg) => arg > 0.01
 | 
				
			||||||
 | 
					  // }
 | 
				
			||||||
 | 
					  rule: (arg: any) => boolean
 | 
				
			||||||
 | 
					  // 校验不通过时的消息提示
 | 
				
			||||||
 | 
					  message: string
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/**
 | 
					/**
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,5 +1,5 @@
 | 
				
			|||||||
<template>
 | 
					<template>
 | 
				
			||||||
  <el-table :data="spuData">
 | 
					  <el-table :data="spuData" :default-expand-all="true">
 | 
				
			||||||
    <el-table-column type="expand" width="30">
 | 
					    <el-table-column type="expand" width="30">
 | 
				
			||||||
      <template #default="{ row }">
 | 
					      <template #default="{ row }">
 | 
				
			||||||
        <SkuList
 | 
					        <SkuList
 | 
				
			||||||
@@ -46,87 +46,39 @@
 | 
				
			|||||||
    <el-table-column align="center" label="库存" min-width="90" prop="stock" />
 | 
					    <el-table-column align="center" label="库存" min-width="90" prop="stock" />
 | 
				
			||||||
  </el-table>
 | 
					  </el-table>
 | 
				
			||||||
</template>
 | 
					</template>
 | 
				
			||||||
<script lang="ts" name="SpuAndSkuList" setup>
 | 
					<script generic="T extends Spu" lang="ts" setup>
 | 
				
			||||||
// TODO 后续计划重新封装作为活动商品配置通用组件;可以等其他活动做到的时候,在统一处理 SPU 选择组件哈
 | 
					// TODO 后续计划重新封装作为活动商品配置通用组件;可以等其他活动做到的时候,在统一处理 SPU 选择组件哈
 | 
				
			||||||
import { formatToFraction } from '@/utils'
 | 
					import { formatToFraction } from '@/utils'
 | 
				
			||||||
import { createImageViewer } from '@/components/ImageViewer'
 | 
					import { createImageViewer } from '@/components/ImageViewer'
 | 
				
			||||||
import * as ProductSpuApi from '@/api/mall/product/spu'
 | 
					import { Spu } from '@/api/mall/product/spu'
 | 
				
			||||||
import { SpuRespVO } from '@/api/mall/product/spu'
 | 
					import { RuleConfig, SkuList } from '@/views/mall/product/spu/components'
 | 
				
			||||||
import {
 | 
					import { SeckillProductVO } from '@/api/mall/promotion/seckill/seckillActivity'
 | 
				
			||||||
  getPropertyList,
 | 
					import { SpuProperty } from '@/views/mall/promotion/components/index'
 | 
				
			||||||
  Properties,
 | 
					 | 
				
			||||||
  RuleConfig,
 | 
					 | 
				
			||||||
  SkuList
 | 
					 | 
				
			||||||
} from '@/views/mall/product/spu/components'
 | 
					 | 
				
			||||||
import { SeckillProductVO, SpuExtension } from '@/api/mall/promotion/seckill/seckillActivity'
 | 
					 | 
				
			||||||
const message = useMessage() // 消息弹窗
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
// TODO @puhui999:是不是改成传递一个 spu 就好啦?
 | 
					defineOptions({ name: 'PromotionSpuAndSkuList' })
 | 
				
			||||||
const props = defineProps({
 | 
					
 | 
				
			||||||
  spuList: {
 | 
					// TODO @puhui999:是不是改成传递一个 spu 就好啦? 因为活动商品可以多选所以展示编辑的时候需要展示多个
 | 
				
			||||||
    type: Array,
 | 
					const props = defineProps<{
 | 
				
			||||||
    default: () => []
 | 
					  spuList: T[]
 | 
				
			||||||
  }
 | 
					  ruleConfig: RuleConfig[]
 | 
				
			||||||
})
 | 
					  spuPropertyListP: SpuProperty<T>[]
 | 
				
			||||||
const spuData = ref<SpuRespVO[]>([]) // spu 详情数据列表
 | 
					}>()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const spuData = ref<Spu[]>([]) // spu 详情数据列表
 | 
				
			||||||
const skuListRef = ref() // 商品属性列表Ref
 | 
					const skuListRef = ref() // 商品属性列表Ref
 | 
				
			||||||
interface spuProperty {
 | 
					 | 
				
			||||||
  spuId: number
 | 
					 | 
				
			||||||
  spuDetail: SpuExtension
 | 
					 | 
				
			||||||
  propertyList: Properties[]
 | 
					 | 
				
			||||||
} // TODO @puhui999:类名首字母大写哈
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
const spuPropertyList = ref<spuProperty[]>([]) // spuId 对应的 sku 的属性列表
 | 
					const spuPropertyList = ref<SpuProperty<T>[]>([]) // spuId 对应的 sku 的属性列表
 | 
				
			||||||
/**
 | 
					
 | 
				
			||||||
 * 获取 SPU 详情
 | 
					 | 
				
			||||||
 * @param spuIds
 | 
					 | 
				
			||||||
 */
 | 
					 | 
				
			||||||
const getSpuDetails = async (spuIds: number[]) => {
 | 
					 | 
				
			||||||
  const spuProperties: spuProperty[] = []
 | 
					 | 
				
			||||||
  // TODO puhui999: 考虑后端添加通过 spuIds 批量获取
 | 
					 | 
				
			||||||
  for (const spuId of spuIds) {
 | 
					 | 
				
			||||||
    // 获取 SPU 详情
 | 
					 | 
				
			||||||
    const res = (await ProductSpuApi.getSpu(spuId)) as SpuExtension
 | 
					 | 
				
			||||||
    if (!res) {
 | 
					 | 
				
			||||||
      continue
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
    // 初始化每个 sku 秒杀配置
 | 
					 | 
				
			||||||
    res.skus?.forEach((sku) => {
 | 
					 | 
				
			||||||
      const config: SeckillProductVO = {
 | 
					 | 
				
			||||||
        spuId,
 | 
					 | 
				
			||||||
        skuId: sku.id!,
 | 
					 | 
				
			||||||
        stock: 0,
 | 
					 | 
				
			||||||
        seckillPrice: 0
 | 
					 | 
				
			||||||
      }
 | 
					 | 
				
			||||||
      sku.productConfig = config
 | 
					 | 
				
			||||||
    })
 | 
					 | 
				
			||||||
    spuProperties.push({ spuId, spuDetail: res, propertyList: getPropertyList(res) })
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
  spuPropertyList.value = spuProperties
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
const ruleConfig: RuleConfig[] = [
 | 
					 | 
				
			||||||
  {
 | 
					 | 
				
			||||||
    name: 'stock',
 | 
					 | 
				
			||||||
    geValue: 10
 | 
					 | 
				
			||||||
  },
 | 
					 | 
				
			||||||
  {
 | 
					 | 
				
			||||||
    name: 'seckillPrice',
 | 
					 | 
				
			||||||
    geValue: 0.01
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
]
 | 
					 | 
				
			||||||
/**
 | 
					/**
 | 
				
			||||||
 * 获取所有 sku 秒杀配置
 | 
					 * 获取所有 sku 秒杀配置
 | 
				
			||||||
 | 
					 * @param extendedAttribute 在 sku 上扩展的属性,例:秒杀活动 sku 扩展属性 productConfig 请参考 seckillActivity.ts
 | 
				
			||||||
 */
 | 
					 */
 | 
				
			||||||
const getSkuConfigs = (): SeckillProductVO[] => {
 | 
					const getSkuConfigs: <V>(extendedAttribute: string) => V[] = (extendedAttribute: string) => {
 | 
				
			||||||
  if (!skuListRef.value.validateSku()) {
 | 
					  skuListRef.value.validateSku()
 | 
				
			||||||
    // TODO 作为通用组件是需要进一步完善
 | 
					 | 
				
			||||||
    message.warning('请检查商品相关属性配置!!')
 | 
					 | 
				
			||||||
    throw new Error('请检查商品相关属性配置!!')
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
  const seckillProducts: SeckillProductVO[] = []
 | 
					  const seckillProducts: SeckillProductVO[] = []
 | 
				
			||||||
  spuPropertyList.value.forEach((item) => {
 | 
					  spuPropertyList.value.forEach((item) => {
 | 
				
			||||||
    item.spuDetail.skus.forEach((sku) => {
 | 
					    item.spuDetail.skus.forEach((sku) => {
 | 
				
			||||||
      seckillProducts.push(sku.productConfig)
 | 
					      seckillProducts.push(sku[extendedAttribute])
 | 
				
			||||||
    })
 | 
					    })
 | 
				
			||||||
  })
 | 
					  })
 | 
				
			||||||
  return seckillProducts
 | 
					  return seckillProducts
 | 
				
			||||||
@@ -149,8 +101,21 @@ watch(
 | 
				
			|||||||
  () => props.spuList,
 | 
					  () => props.spuList,
 | 
				
			||||||
  (data) => {
 | 
					  (data) => {
 | 
				
			||||||
    if (!data) return
 | 
					    if (!data) return
 | 
				
			||||||
    spuData.value = data as SpuRespVO[]
 | 
					    spuData.value = data as Spu[]
 | 
				
			||||||
    getSpuDetails(spuData.value.map((spu) => spu.id!))
 | 
					  },
 | 
				
			||||||
 | 
					  {
 | 
				
			||||||
 | 
					    deep: true,
 | 
				
			||||||
 | 
					    immediate: true
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					/**
 | 
				
			||||||
 | 
					 * 将传进来的值赋值给 skuList
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					watch(
 | 
				
			||||||
 | 
					  () => props.spuPropertyListP,
 | 
				
			||||||
 | 
					  (data) => {
 | 
				
			||||||
 | 
					    if (!data) return
 | 
				
			||||||
 | 
					    spuPropertyList.value = data as SpuProperty<T>[]
 | 
				
			||||||
  },
 | 
					  },
 | 
				
			||||||
  {
 | 
					  {
 | 
				
			||||||
    deep: true,
 | 
					    deep: true,
 | 
				
			||||||
@@ -20,7 +20,6 @@
 | 
				
			|||||||
            class="w-1/1"
 | 
					            class="w-1/1"
 | 
				
			||||||
            node-key="id"
 | 
					            node-key="id"
 | 
				
			||||||
            placeholder="请选择商品分类"
 | 
					            placeholder="请选择商品分类"
 | 
				
			||||||
            @change="nodeClick"
 | 
					 | 
				
			||||||
          />
 | 
					          />
 | 
				
			||||||
        </el-col>
 | 
					        </el-col>
 | 
				
			||||||
        <el-col :span="6">
 | 
					        <el-col :span="6">
 | 
				
			||||||
@@ -110,18 +109,20 @@
 | 
				
			|||||||
  </Dialog>
 | 
					  </Dialog>
 | 
				
			||||||
</template>
 | 
					</template>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
<script lang="ts" name="SeckillActivitySpuAndSkuSelect" setup>
 | 
					<script lang="ts" setup>
 | 
				
			||||||
import { getPropertyList, Properties, SkuList } from '@/views/mall/product/spu/components'
 | 
					import { getPropertyList, Properties, SkuList } from '@/views/mall/product/spu/components'
 | 
				
			||||||
import { ElTable } from 'element-plus'
 | 
					import { ElTable } from 'element-plus'
 | 
				
			||||||
import { dateFormatter } from '@/utils/formatTime'
 | 
					import { dateFormatter } from '@/utils/formatTime'
 | 
				
			||||||
import { createImageViewer } from '@/components/ImageViewer'
 | 
					import { createImageViewer } from '@/components/ImageViewer'
 | 
				
			||||||
import { formatToFraction } from '@/utils'
 | 
					import { formatToFraction } from '@/utils'
 | 
				
			||||||
import { checkSelectedNode, defaultProps, handleTree } from '@/utils/tree'
 | 
					import { defaultProps, handleTree } from '@/utils/tree'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import * as ProductCategoryApi from '@/api/mall/product/category'
 | 
					import * as ProductCategoryApi from '@/api/mall/product/category'
 | 
				
			||||||
import * as ProductSpuApi from '@/api/mall/product/spu'
 | 
					import * as ProductSpuApi from '@/api/mall/product/spu'
 | 
				
			||||||
import { propTypes } from '@/utils/propTypes'
 | 
					import { propTypes } from '@/utils/propTypes'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					defineOptions({ name: 'PromotionSpuSelect' })
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const props = defineProps({
 | 
					const props = defineProps({
 | 
				
			||||||
  // 默认不需要(不需要的情况下只返回 spu,需要的情况下返回 选中的 spu 和 sku 列表)
 | 
					  // 默认不需要(不需要的情况下只返回 spu,需要的情况下返回 选中的 spu 和 sku 列表)
 | 
				
			||||||
  // 其它活动需要选择商品和商品属性导入此组件即可,需添加组件属性 :isSelectSku='true'
 | 
					  // 其它活动需要选择商品和商品属性导入此组件即可,需添加组件属性 :isSelectSku='true'
 | 
				
			||||||
@@ -159,7 +160,7 @@ const expandChange = async (row: ProductSpuApi.Spu, expandedRows: ProductSpuApi.
 | 
				
			|||||||
    return
 | 
					    return
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
  // 获取 SPU 详情
 | 
					  // 获取 SPU 详情
 | 
				
			||||||
  const res = (await ProductSpuApi.getSpu(row.id as number)) as ProductSpuApi.SpuRespVO
 | 
					  const res = (await ProductSpuApi.getSpu(row.id as number)) as ProductSpuApi.Spu
 | 
				
			||||||
  propertyList.value = getPropertyList(res)
 | 
					  propertyList.value = getPropertyList(res)
 | 
				
			||||||
  spuData.value = res
 | 
					  spuData.value = res
 | 
				
			||||||
  isExpand.value = true
 | 
					  isExpand.value = true
 | 
				
			||||||
@@ -167,53 +168,50 @@ const expandChange = async (row: ProductSpuApi.Spu, expandedRows: ProductSpuApi.
 | 
				
			|||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
//============ 商品选择相关 ============
 | 
					//============ 商品选择相关 ============
 | 
				
			||||||
const selectedSpu = ref<ProductSpuApi.Spu>() // 选中的商品 spu 只能选择一个
 | 
					const selectedSpuIds = ref<number[]>([]) // 选中的商品 spuIds
 | 
				
			||||||
const selectedSku = ref<ProductSpuApi.Sku[]>() // 选中的商品 sku
 | 
					const selectedSkuIds = ref<number[]>([]) // 选中的商品 skuIds
 | 
				
			||||||
const selectSku = (val: ProductSpuApi.Sku[]) => {
 | 
					const selectSku = (val: ProductSpuApi.Sku[]) => {
 | 
				
			||||||
  selectedSku.value = val
 | 
					  selectedSkuIds.value = val.map((sku) => sku.id!)
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
const selectSpu = (val: ProductSpuApi.Spu[]) => {
 | 
					const selectSpu = (val: ProductSpuApi.Spu[]) => {
 | 
				
			||||||
  // 只选择一个
 | 
					  selectedSpuIds.value = val.map((spu) => spu.id!)
 | 
				
			||||||
  selectedSpu.value = val[0]
 | 
					  // // 只选择一个
 | 
				
			||||||
  // 如果大于1个
 | 
					  // selectedSpu.value = val[0]
 | 
				
			||||||
  if (val.length > 1) {
 | 
					  // // 如果大于1个
 | 
				
			||||||
    // 清空选择
 | 
					  // if (val.length > 1) {
 | 
				
			||||||
    spuListRef.value.clearSelection()
 | 
					  //   // 清空选择
 | 
				
			||||||
    // 变更为最后一次选择的
 | 
					  //   spuListRef.value.clearSelection()
 | 
				
			||||||
    spuListRef.value.toggleRowSelection(val.pop(), true)
 | 
					  //   // 变更为最后一次选择的
 | 
				
			||||||
  }
 | 
					  //   spuListRef.value.toggleRowSelection(val.pop(), true)
 | 
				
			||||||
 | 
					  // }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
// 确认选择时的触发事件
 | 
					// 确认选择时的触发事件
 | 
				
			||||||
const emits = defineEmits<{
 | 
					const emits = defineEmits<{
 | 
				
			||||||
  (e: 'confirm', value: ProductSpuApi.Spu, value1?: ProductSpuApi.Sku[]): void
 | 
					  (e: 'confirm', spuIds: number[], skuIds?: number[]): void
 | 
				
			||||||
}>()
 | 
					}>()
 | 
				
			||||||
/**
 | 
					/**
 | 
				
			||||||
 * 确认选择返回选中的 spu 和 sku (如果需要选择sku的话)
 | 
					 * 确认选择返回选中的 spu 和 sku (如果需要选择sku的话)
 | 
				
			||||||
 */
 | 
					 */
 | 
				
			||||||
const confirm = () => {
 | 
					const confirm = () => {
 | 
				
			||||||
  if (typeof selectedSpu.value === 'undefined') {
 | 
					  if (selectedSpuIds.value.length === 0) {
 | 
				
			||||||
    message.warning('没有选择任何商品')
 | 
					    message.warning('没有选择任何商品')
 | 
				
			||||||
    return
 | 
					    return
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
  if (
 | 
					  if (props.isSelectSku && selectedSkuIds.value.length === 0) {
 | 
				
			||||||
    (props.isSelectSku && typeof selectedSku.value === 'undefined') ||
 | 
					 | 
				
			||||||
    selectedSku.value?.length === 0
 | 
					 | 
				
			||||||
  ) {
 | 
					 | 
				
			||||||
    message.warning('没有选择任何商品属性')
 | 
					    message.warning('没有选择任何商品属性')
 | 
				
			||||||
    return
 | 
					    return
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
  // TODO 返回选择 sku 没测试过,后续测试完善
 | 
					  // 返回各自 id 列表
 | 
				
			||||||
  props.isSelectSku
 | 
					  props.isSelectSku
 | 
				
			||||||
    ? emits('confirm', selectedSpu.value!, selectedSku.value!)
 | 
					    ? emits('confirm', selectedSpuIds.value, selectedSkuIds.value)
 | 
				
			||||||
    : emits('confirm', selectedSpu.value!)
 | 
					    : emits('confirm', selectedSpuIds.value)
 | 
				
			||||||
  // 关闭弹窗
 | 
					  // 关闭弹窗
 | 
				
			||||||
  dialogVisible.value = false
 | 
					  dialogVisible.value = false
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// TODO @puhui999:直接叫商品选择;不用外部传入标题;
 | 
					/** 打开弹窗 */
 | 
				
			||||||
/** 打开弹窗 TODO 没做国际化 */
 | 
					const open = () => {
 | 
				
			||||||
const open = (title: string) => {
 | 
					  dialogTitle.value = '商品选择'
 | 
				
			||||||
  dialogTitle.value = title
 | 
					 | 
				
			||||||
  dialogVisible.value = true
 | 
					  dialogVisible.value = true
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
defineExpose({ open }) // 提供 open 方法,用于打开弹窗
 | 
					defineExpose({ open }) // 提供 open 方法,用于打开弹窗
 | 
				
			||||||
@@ -259,15 +257,6 @@ const imagePreview = (imgUrl: string) => {
 | 
				
			|||||||
const categoryList = ref() // 分类树
 | 
					const categoryList = ref() // 分类树
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// TODO @puhui999:商品搜索的时候,可以通过一级搜二级;所以这个校验可以去掉哈;也就是说,只允许挂在二级,但是一级可搜索到
 | 
					// TODO @puhui999:商品搜索的时候,可以通过一级搜二级;所以这个校验可以去掉哈;也就是说,只允许挂在二级,但是一级可搜索到
 | 
				
			||||||
/**
 | 
					 | 
				
			||||||
 * 校验所选是否为二级及以下节点
 | 
					 | 
				
			||||||
 */
 | 
					 | 
				
			||||||
const nodeClick = () => {
 | 
					 | 
				
			||||||
  if (!checkSelectedNode(categoryList.value, queryParams.value.categoryId)) {
 | 
					 | 
				
			||||||
    queryParams.value.categoryId = null
 | 
					 | 
				
			||||||
    message.warning('必须选择二级及以下节点!!')
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
/** 初始化 **/
 | 
					/** 初始化 **/
 | 
				
			||||||
onMounted(async () => {
 | 
					onMounted(async () => {
 | 
				
			||||||
  await getList()
 | 
					  await getList()
 | 
				
			||||||
							
								
								
									
										13
									
								
								src/views/mall/promotion/components/index.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										13
									
								
								src/views/mall/promotion/components/index.ts
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,13 @@
 | 
				
			|||||||
 | 
					import SpuSelect from './SpuSelect.vue'
 | 
				
			||||||
 | 
					import SpuAndSkuList from './SpuAndSkuList.vue'
 | 
				
			||||||
 | 
					import { Properties } from '@/views/mall/product/spu/components'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					type SpuProperty<T> = {
 | 
				
			||||||
 | 
					  spuId: number
 | 
				
			||||||
 | 
					  spuDetail: T
 | 
				
			||||||
 | 
					  propertyList: Properties[]
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					/**
 | 
				
			||||||
 | 
					 * 提供商品活动商品选择通用组件
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					export { SpuSelect, SpuAndSkuList, SpuProperty }
 | 
				
			||||||
@@ -8,10 +8,14 @@
 | 
				
			|||||||
      :schema="allSchemas.formSchema"
 | 
					      :schema="allSchemas.formSchema"
 | 
				
			||||||
    >
 | 
					    >
 | 
				
			||||||
      <!-- 先选择 -->
 | 
					      <!-- 先选择 -->
 | 
				
			||||||
      <template #spuId>
 | 
					      <template #spuIds>
 | 
				
			||||||
        <el-button @click="spuAndSkuSelectForm.open('秒杀商品选择')">选择商品</el-button>
 | 
					        <el-button @click="spuSelectRef.open()">选择商品</el-button>
 | 
				
			||||||
        <!-- TODO @puhui999:默认展开 SKU 哈,毕竟 SKU 是主角,SPU 是配角 -->
 | 
					        <SpuAndSkuList
 | 
				
			||||||
        <SpuAndSkuList ref="spuAndSkuListRef" :spu-list="spuList" />
 | 
					          ref="spuAndSkuListRef"
 | 
				
			||||||
 | 
					          :rule-config="ruleConfig"
 | 
				
			||||||
 | 
					          :spu-list="spuList"
 | 
				
			||||||
 | 
					          :spu-property-list-p="spuPropertyList"
 | 
				
			||||||
 | 
					        />
 | 
				
			||||||
      </template>
 | 
					      </template>
 | 
				
			||||||
    </Form>
 | 
					    </Form>
 | 
				
			||||||
    <template #footer>
 | 
					    <template #footer>
 | 
				
			||||||
@@ -19,15 +23,17 @@
 | 
				
			|||||||
      <el-button @click="dialogVisible = false">取 消</el-button>
 | 
					      <el-button @click="dialogVisible = false">取 消</el-button>
 | 
				
			||||||
    </template>
 | 
					    </template>
 | 
				
			||||||
  </Dialog>
 | 
					  </Dialog>
 | 
				
			||||||
  <!-- TODO @puhui999:这个组件是不是 SpuSelect,不需要带 sku 或者 Form 呀 -->
 | 
					  <SpuSelect ref="spuSelectRef" @confirm="selectSpu" />
 | 
				
			||||||
  <SpuAndSkuSelectForm ref="spuAndSkuSelectForm" @confirm="selectSpu" />
 | 
					 | 
				
			||||||
</template>
 | 
					</template>
 | 
				
			||||||
<script lang="ts" name="PromotionSeckillActivityForm" setup>
 | 
					<script lang="ts" setup>
 | 
				
			||||||
import { SpuAndSkuList, SpuAndSkuSelectForm } from './components'
 | 
					import { SpuAndSkuList, SpuProperty, SpuSelect } from '../../components'
 | 
				
			||||||
import { allSchemas, rules } from './seckillActivity.data'
 | 
					import { allSchemas, rules } from './seckillActivity.data'
 | 
				
			||||||
import { Spu } from '@/api/mall/product/spu'
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
import * as SeckillActivityApi from '@/api/mall/promotion/seckill/seckillActivity'
 | 
					import * as SeckillActivityApi from '@/api/mall/promotion/seckill/seckillActivity'
 | 
				
			||||||
 | 
					import { getPropertyList, RuleConfig } from '@/views/mall/product/spu/components'
 | 
				
			||||||
 | 
					import * as ProductSpuApi from '@/api/mall/product/spu'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					defineOptions({ name: 'PromotionSeckillActivityForm' })
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const { t } = useI18n() // 国际化
 | 
					const { t } = useI18n() // 国际化
 | 
				
			||||||
const message = useMessage() // 消息弹窗
 | 
					const message = useMessage() // 消息弹窗
 | 
				
			||||||
@@ -37,14 +43,26 @@ 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 spuAndSkuSelectForm = ref() // 商品和属性选择 Ref
 | 
					const spuSelectRef = ref() // 商品和属性选择 Ref
 | 
				
			||||||
const spuAndSkuListRef = ref() // sku 秒杀配置组件Ref
 | 
					const spuAndSkuListRef = ref() // sku 秒杀配置组件Ref
 | 
				
			||||||
 | 
					const ruleConfig: RuleConfig[] = [
 | 
				
			||||||
 | 
					  {
 | 
				
			||||||
 | 
					    name: 'productConfig.stock',
 | 
				
			||||||
 | 
					    rule: (arg) => arg > 1,
 | 
				
			||||||
 | 
					    message: '商品秒杀库存必须大于 1 !!!'
 | 
				
			||||||
 | 
					  },
 | 
				
			||||||
 | 
					  {
 | 
				
			||||||
 | 
					    name: 'productConfig.seckillPrice',
 | 
				
			||||||
 | 
					    rule: (arg) => arg > 0.01,
 | 
				
			||||||
 | 
					    message: '商品秒杀价格必须大于 0.01 !!!'
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					]
 | 
				
			||||||
/** 打开弹窗 */
 | 
					/** 打开弹窗 */
 | 
				
			||||||
const open = async (type: string, id?: number) => {
 | 
					const open = async (type: string, id?: number) => {
 | 
				
			||||||
  dialogVisible.value = true
 | 
					  dialogVisible.value = true
 | 
				
			||||||
  dialogTitle.value = t('action.' + type)
 | 
					  dialogTitle.value = t('action.' + type)
 | 
				
			||||||
  formType.value = type
 | 
					  formType.value = type
 | 
				
			||||||
 | 
					  resetForm()
 | 
				
			||||||
  // 修改时,设置数据 TODO 没测试估计有问题
 | 
					  // 修改时,设置数据 TODO 没测试估计有问题
 | 
				
			||||||
  if (id) {
 | 
					  if (id) {
 | 
				
			||||||
    formLoading.value = true
 | 
					    formLoading.value = true
 | 
				
			||||||
@@ -58,12 +76,49 @@ const open = async (type: string, id?: number) => {
 | 
				
			|||||||
}
 | 
					}
 | 
				
			||||||
defineExpose({ open }) // 提供 open 方法,用于打开弹窗
 | 
					defineExpose({ open }) // 提供 open 方法,用于打开弹窗
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const spuList = ref<Spu[]>([]) // 选择的 spu
 | 
					const spuList = ref<SeckillActivityApi.SpuExtension[]>([]) // 选择的 spu
 | 
				
			||||||
const selectSpu = (val: Spu) => {
 | 
					const spuPropertyList = ref<SpuProperty<SeckillActivityApi.SpuExtension>[]>([])
 | 
				
			||||||
  formRef.value.setValues({ spuId: val.id })
 | 
					const selectSpu = (spuIds: number[]) => {
 | 
				
			||||||
  spuList.value = [val]
 | 
					  formRef.value.setValues({ spuIds })
 | 
				
			||||||
 | 
					  getSpuDetails(spuIds)
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					/**
 | 
				
			||||||
 | 
					 * 获取 SPU 详情
 | 
				
			||||||
 | 
					 * TODO 获取 SPU 详情,放到各自活动表单来做,让 SpuAndSkuList 职责单一点
 | 
				
			||||||
 | 
					 * @param spuIds
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					const getSpuDetails = async (spuIds: number[]) => {
 | 
				
			||||||
 | 
					  const spuProperties: SpuProperty<SeckillActivityApi.SpuExtension>[] = []
 | 
				
			||||||
 | 
					  spuList.value = []
 | 
				
			||||||
 | 
					  // TODO puhui999: 考虑后端添加通过 spuIds 批量获取
 | 
				
			||||||
 | 
					  for (const spuId of spuIds) {
 | 
				
			||||||
 | 
					    // 获取 SPU 详情
 | 
				
			||||||
 | 
					    const res = (await ProductSpuApi.getSpu(spuId)) as SeckillActivityApi.SpuExtension
 | 
				
			||||||
 | 
					    if (!res) {
 | 
				
			||||||
 | 
					      continue
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    spuList.value.push(res)
 | 
				
			||||||
 | 
					    // 初始化每个 sku 秒杀配置
 | 
				
			||||||
 | 
					    res.skus?.forEach((sku) => {
 | 
				
			||||||
 | 
					      const config: SeckillActivityApi.SeckillProductVO = {
 | 
				
			||||||
 | 
					        spuId,
 | 
				
			||||||
 | 
					        skuId: sku.id!,
 | 
				
			||||||
 | 
					        stock: 0,
 | 
				
			||||||
 | 
					        seckillPrice: 0
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					      sku.productConfig = config
 | 
				
			||||||
 | 
					    })
 | 
				
			||||||
 | 
					    spuProperties.push({ spuId, spuDetail: res, propertyList: getPropertyList(res) })
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					  spuPropertyList.value = spuProperties
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/** 重置表单 */
 | 
				
			||||||
 | 
					const resetForm = () => {
 | 
				
			||||||
 | 
					  spuList.value = []
 | 
				
			||||||
 | 
					  spuPropertyList.value = []
 | 
				
			||||||
 | 
					  formRef.value.getElFormRef().resetFields()
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
/** 提交表单 */
 | 
					/** 提交表单 */
 | 
				
			||||||
const emit = defineEmits(['success']) // 定义 success 事件,用于操作成功后的回调
 | 
					const emit = defineEmits(['success']) // 定义 success 事件,用于操作成功后的回调
 | 
				
			||||||
const submitForm = async () => {
 | 
					const submitForm = async () => {
 | 
				
			||||||
@@ -76,7 +131,7 @@ const submitForm = async () => {
 | 
				
			|||||||
  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!)
 | 
					    data.spuIds = spuList.value.map((spu) => spu.id!)
 | 
				
			||||||
    data.products = spuAndSkuListRef.value.getSkuConfigs()
 | 
					    data.products = spuAndSkuListRef.value.getSkuConfigs('productConfig')
 | 
				
			||||||
    if (formType.value === 'create') {
 | 
					    if (formType.value === 'create') {
 | 
				
			||||||
      await SeckillActivityApi.createSeckillActivity(data)
 | 
					      await SeckillActivityApi.createSeckillActivity(data)
 | 
				
			||||||
      message.success(t('common.createSuccess'))
 | 
					      message.success(t('common.createSuccess'))
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,4 +0,0 @@
 | 
				
			|||||||
import SpuAndSkuSelectForm from './SpuAndSkuSelectForm.vue'
 | 
					 | 
				
			||||||
import SpuAndSkuList from './SpuAndSkuList.vue'
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
export { SpuAndSkuSelectForm, SpuAndSkuList }
 | 
					 | 
				
			||||||
@@ -24,11 +24,19 @@
 | 
				
			|||||||
      v-model:pageSize="tableObject.pageSize"
 | 
					      v-model:pageSize="tableObject.pageSize"
 | 
				
			||||||
      :columns="allSchemas.tableColumns"
 | 
					      :columns="allSchemas.tableColumns"
 | 
				
			||||||
      :data="tableObject.tableList"
 | 
					      :data="tableObject.tableList"
 | 
				
			||||||
 | 
					      :expand="true"
 | 
				
			||||||
      :loading="tableObject.loading"
 | 
					      :loading="tableObject.loading"
 | 
				
			||||||
      :pagination="{
 | 
					      :pagination="{
 | 
				
			||||||
        total: tableObject.total
 | 
					        total: tableObject.total
 | 
				
			||||||
      }"
 | 
					      }"
 | 
				
			||||||
 | 
					      @expand-change="expandChange"
 | 
				
			||||||
    >
 | 
					    >
 | 
				
			||||||
 | 
					      <template #expand> 展示活动商品和商品相关属性活动配置</template>
 | 
				
			||||||
 | 
					      <template #configIds="{ row }">
 | 
				
			||||||
 | 
					        <el-tag v-for="(name, index) in convertSeckillConfigNames(row)" :key="index" class="mr-5px">
 | 
				
			||||||
 | 
					          {{ name }}
 | 
				
			||||||
 | 
					        </el-tag>
 | 
				
			||||||
 | 
					      </template>
 | 
				
			||||||
      <template #action="{ row }">
 | 
					      <template #action="{ row }">
 | 
				
			||||||
        <el-button
 | 
					        <el-button
 | 
				
			||||||
          v-hasPermi="['promotion:seckill-activity:update']"
 | 
					          v-hasPermi="['promotion:seckill-activity:update']"
 | 
				
			||||||
@@ -53,11 +61,14 @@
 | 
				
			|||||||
  <!-- 表单弹窗:添加/修改 -->
 | 
					  <!-- 表单弹窗:添加/修改 -->
 | 
				
			||||||
  <SeckillActivityForm ref="formRef" @success="getList" />
 | 
					  <SeckillActivityForm ref="formRef" @success="getList" />
 | 
				
			||||||
</template>
 | 
					</template>
 | 
				
			||||||
<script lang="ts" name="PromotionSeckillActivity" setup>
 | 
					<script lang="ts" setup>
 | 
				
			||||||
import { allSchemas } from './seckillActivity.data'
 | 
					import { allSchemas } from './seckillActivity.data'
 | 
				
			||||||
 | 
					import { getListAllSimple } from '@/api/mall/promotion/seckill/seckillConfig'
 | 
				
			||||||
import * as SeckillActivityApi from '@/api/mall/promotion/seckill/seckillActivity'
 | 
					import * as SeckillActivityApi from '@/api/mall/promotion/seckill/seckillActivity'
 | 
				
			||||||
import SeckillActivityForm from './SeckillActivityForm.vue'
 | 
					import SeckillActivityForm from './SeckillActivityForm.vue'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					defineOptions({ name: 'PromotionSeckillActivity' })
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// tableObject:表格的属性对象,可获得分页大小、条数等属性
 | 
					// tableObject:表格的属性对象,可获得分页大小、条数等属性
 | 
				
			||||||
// tableMethods:表格的操作对象,可进行获得分页、删除记录等操作
 | 
					// tableMethods:表格的操作对象,可进行获得分页、删除记录等操作
 | 
				
			||||||
// 详细可见:https://doc.iocoder.cn/vue3/crud-schema/
 | 
					// 详细可见:https://doc.iocoder.cn/vue3/crud-schema/
 | 
				
			||||||
@@ -78,9 +89,21 @@ const openForm = (type: string, id?: number) => {
 | 
				
			|||||||
const handleDelete = (id: number) => {
 | 
					const handleDelete = (id: number) => {
 | 
				
			||||||
  tableMethods.delList(id, false)
 | 
					  tableMethods.delList(id, false)
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					const seckillConfigAllSimple = ref([]) // 时段配置精简列表
 | 
				
			||||||
 | 
					const convertSeckillConfigNames = computed(
 | 
				
			||||||
 | 
					  () => (row) =>
 | 
				
			||||||
 | 
					    seckillConfigAllSimple.value
 | 
				
			||||||
 | 
					      ?.filter((item) => row.configIds.includes(item.id))
 | 
				
			||||||
 | 
					      ?.map((config) => config.name)
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					const expandChange = (row, expandedRows) => {
 | 
				
			||||||
 | 
					  // TODO puhui:等 CRUD 完事后弄
 | 
				
			||||||
 | 
					  console.log(row, expandedRows)
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/** 初始化 **/
 | 
					/** 初始化 **/
 | 
				
			||||||
onMounted(() => {
 | 
					onMounted(async () => {
 | 
				
			||||||
  getList()
 | 
					  await getList()
 | 
				
			||||||
 | 
					  seckillConfigAllSimple.value = await getListAllSimple()
 | 
				
			||||||
})
 | 
					})
 | 
				
			||||||
</script>
 | 
					</script>
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -16,7 +16,6 @@ export const rules = reactive({
 | 
				
			|||||||
})
 | 
					})
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// CrudSchema https://doc.iocoder.cn/vue3/crud-schema/
 | 
					// CrudSchema https://doc.iocoder.cn/vue3/crud-schema/
 | 
				
			||||||
// TODO @puhui999:table 宽度调整下,有点太长啦;部分字段可以隐藏哈,根据需求;
 | 
					 | 
				
			||||||
const crudSchemas = reactive<CrudSchema[]>([
 | 
					const crudSchemas = reactive<CrudSchema[]>([
 | 
				
			||||||
  {
 | 
					  {
 | 
				
			||||||
    label: '秒杀活动名称',
 | 
					    label: '秒杀活动名称',
 | 
				
			||||||
@@ -40,8 +39,7 @@ const crudSchemas = reactive<CrudSchema[]>([
 | 
				
			|||||||
      component: 'DatePicker',
 | 
					      component: 'DatePicker',
 | 
				
			||||||
      componentProps: {
 | 
					      componentProps: {
 | 
				
			||||||
        valueFormat: 'YYYY-MM-DD',
 | 
					        valueFormat: 'YYYY-MM-DD',
 | 
				
			||||||
        type: 'daterange',
 | 
					        type: 'daterange'
 | 
				
			||||||
        defaultTime: [new Date('1 00:00:00'), new Date('1 23:59:59')]
 | 
					 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    form: {
 | 
					    form: {
 | 
				
			||||||
@@ -52,7 +50,7 @@ const crudSchemas = reactive<CrudSchema[]>([
 | 
				
			|||||||
      }
 | 
					      }
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    table: {
 | 
					    table: {
 | 
				
			||||||
      width: 300
 | 
					      width: 120
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
  },
 | 
					  },
 | 
				
			||||||
  {
 | 
					  {
 | 
				
			||||||
@@ -64,8 +62,7 @@ const crudSchemas = reactive<CrudSchema[]>([
 | 
				
			|||||||
      component: 'DatePicker',
 | 
					      component: 'DatePicker',
 | 
				
			||||||
      componentProps: {
 | 
					      componentProps: {
 | 
				
			||||||
        valueFormat: 'YYYY-MM-DD',
 | 
					        valueFormat: 'YYYY-MM-DD',
 | 
				
			||||||
        type: 'daterange',
 | 
					        type: 'daterange'
 | 
				
			||||||
        defaultTime: [new Date('1 00:00:00'), new Date('1 23:59:59')]
 | 
					 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    form: {
 | 
					    form: {
 | 
				
			||||||
@@ -76,11 +73,11 @@ const crudSchemas = reactive<CrudSchema[]>([
 | 
				
			|||||||
      }
 | 
					      }
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    table: {
 | 
					    table: {
 | 
				
			||||||
      width: 300
 | 
					      width: 120
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
  },
 | 
					  },
 | 
				
			||||||
  {
 | 
					  {
 | 
				
			||||||
    label: '秒杀时段', // todo @PUHUI999: 在列表界面,格式化不对
 | 
					    label: '秒杀时段',
 | 
				
			||||||
    field: 'configIds',
 | 
					    field: 'configIds',
 | 
				
			||||||
    form: {
 | 
					    form: {
 | 
				
			||||||
      component: 'Select',
 | 
					      component: 'Select',
 | 
				
			||||||
@@ -106,7 +103,7 @@ const crudSchemas = reactive<CrudSchema[]>([
 | 
				
			|||||||
      value: 0
 | 
					      value: 0
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    table: {
 | 
					    table: {
 | 
				
			||||||
      width: 300
 | 
					      width: 120
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
  },
 | 
					  },
 | 
				
			||||||
  {
 | 
					  {
 | 
				
			||||||
@@ -118,7 +115,7 @@ const crudSchemas = reactive<CrudSchema[]>([
 | 
				
			|||||||
      value: 0
 | 
					      value: 0
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    table: {
 | 
					    table: {
 | 
				
			||||||
      width: 300
 | 
					      width: 120
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
  },
 | 
					  },
 | 
				
			||||||
  {
 | 
					  {
 | 
				
			||||||
@@ -130,7 +127,7 @@ const crudSchemas = reactive<CrudSchema[]>([
 | 
				
			|||||||
      value: 0
 | 
					      value: 0
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    table: {
 | 
					    table: {
 | 
				
			||||||
      width: 300
 | 
					      width: 120
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
  },
 | 
					  },
 | 
				
			||||||
  {
 | 
					  {
 | 
				
			||||||
@@ -141,7 +138,7 @@ const crudSchemas = reactive<CrudSchema[]>([
 | 
				
			|||||||
      value: 0
 | 
					      value: 0
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    table: {
 | 
					    table: {
 | 
				
			||||||
      width: 300
 | 
					      width: 120
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
  },
 | 
					  },
 | 
				
			||||||
  {
 | 
					  {
 | 
				
			||||||
@@ -152,7 +149,7 @@ const crudSchemas = reactive<CrudSchema[]>([
 | 
				
			|||||||
      value: 0
 | 
					      value: 0
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    table: {
 | 
					    table: {
 | 
				
			||||||
      width: 300
 | 
					      width: 120
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
  },
 | 
					  },
 | 
				
			||||||
  {
 | 
					  {
 | 
				
			||||||
@@ -164,7 +161,7 @@ const crudSchemas = reactive<CrudSchema[]>([
 | 
				
			|||||||
      value: 0
 | 
					      value: 0
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    table: {
 | 
					    table: {
 | 
				
			||||||
      width: 300
 | 
					      width: 120
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
  },
 | 
					  },
 | 
				
			||||||
  {
 | 
					  {
 | 
				
			||||||
@@ -175,12 +172,14 @@ const crudSchemas = reactive<CrudSchema[]>([
 | 
				
			|||||||
      value: 0
 | 
					      value: 0
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    table: {
 | 
					    table: {
 | 
				
			||||||
      width: 300
 | 
					      width: 120
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
  },
 | 
					  },
 | 
				
			||||||
  {
 | 
					  {
 | 
				
			||||||
    label: '秒杀活动商品', // TODO @puhui999:格式化的商品不对;
 | 
					    label: '秒杀活动商品',
 | 
				
			||||||
    field: 'spuId',
 | 
					    field: 'spuIds',
 | 
				
			||||||
 | 
					    isTable: false,
 | 
				
			||||||
 | 
					    isSearch: false,
 | 
				
			||||||
    form: {
 | 
					    form: {
 | 
				
			||||||
      colProps: {
 | 
					      colProps: {
 | 
				
			||||||
        span: 24
 | 
					        span: 24
 | 
				
			||||||
@@ -204,7 +203,7 @@ const crudSchemas = reactive<CrudSchema[]>([
 | 
				
			|||||||
    },
 | 
					    },
 | 
				
			||||||
    isForm: false,
 | 
					    isForm: false,
 | 
				
			||||||
    table: {
 | 
					    table: {
 | 
				
			||||||
      width: 300
 | 
					      width: 120
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
  },
 | 
					  },
 | 
				
			||||||
  {
 | 
					  {
 | 
				
			||||||
@@ -215,12 +214,12 @@ const crudSchemas = reactive<CrudSchema[]>([
 | 
				
			|||||||
      value: 0
 | 
					      value: 0
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    table: {
 | 
					    table: {
 | 
				
			||||||
      width: 300
 | 
					      width: 80
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
  },
 | 
					  },
 | 
				
			||||||
  {
 | 
					  {
 | 
				
			||||||
    label: '状态',
 | 
					    label: '状态',
 | 
				
			||||||
    field: 'status', // TODO @puhui999:状态在 table 格式化不对;
 | 
					    field: 'status', // TODO @puhui999:状态在 table 格式化不对;建表插入的数据状态值不对,改为 0 或 1 就好了
 | 
				
			||||||
    dictType: DICT_TYPE.COMMON_STATUS,
 | 
					    dictType: DICT_TYPE.COMMON_STATUS,
 | 
				
			||||||
    dictClass: 'number',
 | 
					    dictClass: 'number',
 | 
				
			||||||
    isForm: false,
 | 
					    isForm: false,
 | 
				
			||||||
@@ -235,6 +234,7 @@ const crudSchemas = reactive<CrudSchema[]>([
 | 
				
			|||||||
  {
 | 
					  {
 | 
				
			||||||
    label: '备注',
 | 
					    label: '备注',
 | 
				
			||||||
    field: 'remark',
 | 
					    field: 'remark',
 | 
				
			||||||
 | 
					    isSearch: false,
 | 
				
			||||||
    form: {
 | 
					    form: {
 | 
				
			||||||
      component: 'Input',
 | 
					      component: 'Input',
 | 
				
			||||||
      componentProps: {
 | 
					      componentProps: {
 | 
				
			||||||
 
 | 
				
			|||||||
		Reference in New Issue
	
	Block a user