mirror of
				https://gitee.com/hhyykk/ipms-sjy-ui.git
				synced 2025-11-04 04:08:44 +08:00 
			
		
		
		
	fix:解决商品上一版遗留的各种小bug关键部分已添加fix注释。完成的TODO也已添加fix标记
This commit is contained in:
		@@ -54,3 +54,8 @@ export const getBrand = (id: number) => {
 | 
				
			|||||||
export const getBrandParam = (params: PageParam) => {
 | 
					export const getBrandParam = (params: PageParam) => {
 | 
				
			||||||
  return request.get({ url: '/product/brand/page', params })
 | 
					  return request.get({ url: '/product/brand/page', params })
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// 获得商品品牌精简信息列表
 | 
				
			||||||
 | 
					export const getSimpleBrandList = () => {
 | 
				
			||||||
 | 
					  return request.get({ url: '/product/brand/list-all-simple' })
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -34,6 +34,7 @@ export interface SpuType {
 | 
				
			|||||||
  sliderPicUrls?: string[] // 商品轮播图
 | 
					  sliderPicUrls?: string[] // 商品轮播图
 | 
				
			||||||
  introduction?: string // 商品简介
 | 
					  introduction?: string // 商品简介
 | 
				
			||||||
  deliveryTemplateId?: number | null // 运费模版
 | 
					  deliveryTemplateId?: number | null // 运费模版
 | 
				
			||||||
 | 
					  brandId?: number | null // 商品品牌编号
 | 
				
			||||||
  specType?: boolean // 商品规格
 | 
					  specType?: boolean // 商品规格
 | 
				
			||||||
  subCommissionType?: boolean // 分销类型
 | 
					  subCommissionType?: boolean // 分销类型
 | 
				
			||||||
  skus: SkuType[] // sku数组
 | 
					  skus: SkuType[] // sku数组
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -173,3 +173,24 @@ export const copyValueToTarget = (target, source) => {
 | 
				
			|||||||
  // 更新目标对象值
 | 
					  // 更新目标对象值
 | 
				
			||||||
  Object.assign(target, newObj)
 | 
					  Object.assign(target, newObj)
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/**
 | 
				
			||||||
 | 
					 * 将一个整数转换为分数保留两位小数
 | 
				
			||||||
 | 
					 * @param num
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					export const formatToFraction = (num: number | string | undefined): number => {
 | 
				
			||||||
 | 
					  if (typeof num === 'undefined') return 0
 | 
				
			||||||
 | 
					  const parsedNumber = typeof num === 'string' ? parseFloat(num) : num
 | 
				
			||||||
 | 
					  return parseFloat((parsedNumber / 100).toFixed(2))
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/**
 | 
				
			||||||
 | 
					 * 将一个分数转换为整数
 | 
				
			||||||
 | 
					 * @param num
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					export const convertToInteger = (num: number | string | undefined): number => {
 | 
				
			||||||
 | 
					  if (typeof num === 'undefined') return 0
 | 
				
			||||||
 | 
					  const parsedNumber = typeof num === 'string' ? parseFloat(num) : num
 | 
				
			||||||
 | 
					  // TODO 分转元后还有小数则四舍五入
 | 
				
			||||||
 | 
					  return Math.round(parsedNumber * 100)
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -38,6 +38,7 @@ import { BasicInfoForm, DescriptionForm, OtherSettingsForm } from './components'
 | 
				
			|||||||
// 业务api
 | 
					// 业务api
 | 
				
			||||||
import * as ProductSpuApi from '@/api/mall/product/spu'
 | 
					import * as ProductSpuApi from '@/api/mall/product/spu'
 | 
				
			||||||
import * as PropertyApi from '@/api/mall/product/property'
 | 
					import * as PropertyApi from '@/api/mall/product/property'
 | 
				
			||||||
 | 
					import { convertToInteger, formatToFraction } from '@/utils'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const { t } = useI18n() // 国际化
 | 
					const { t } = useI18n() // 国际化
 | 
				
			||||||
const message = useMessage() // 消息弹窗
 | 
					const message = useMessage() // 消息弹窗
 | 
				
			||||||
@@ -60,6 +61,7 @@ const formData = ref<ProductSpuApi.SpuType>({
 | 
				
			|||||||
  sliderPicUrls: [], // 商品轮播图
 | 
					  sliderPicUrls: [], // 商品轮播图
 | 
				
			||||||
  introduction: '', // 商品简介
 | 
					  introduction: '', // 商品简介
 | 
				
			||||||
  deliveryTemplateId: 1, // 运费模版
 | 
					  deliveryTemplateId: 1, // 运费模版
 | 
				
			||||||
 | 
					  brandId: null, // 商品品牌
 | 
				
			||||||
  specType: false, // 商品规格
 | 
					  specType: false, // 商品规格
 | 
				
			||||||
  subCommissionType: false, // 分销类型
 | 
					  subCommissionType: false, // 分销类型
 | 
				
			||||||
  skus: [
 | 
					  skus: [
 | 
				
			||||||
@@ -94,14 +96,34 @@ const getDetail = async () => {
 | 
				
			|||||||
    formLoading.value = true
 | 
					    formLoading.value = true
 | 
				
			||||||
    try {
 | 
					    try {
 | 
				
			||||||
      const res = (await ProductSpuApi.getSpu(id)) as ProductSpuApi.SpuType
 | 
					      const res = (await ProductSpuApi.getSpu(id)) as ProductSpuApi.SpuType
 | 
				
			||||||
 | 
					      res.skus.forEach((item) => {
 | 
				
			||||||
 | 
					        // 回显价格分转元
 | 
				
			||||||
 | 
					        item.price = formatToFraction(item.price)
 | 
				
			||||||
 | 
					        item.marketPrice = formatToFraction(item.marketPrice)
 | 
				
			||||||
 | 
					        item.costPrice = formatToFraction(item.costPrice)
 | 
				
			||||||
 | 
					        item.subCommissionFirstPrice = formatToFraction(item.subCommissionFirstPrice)
 | 
				
			||||||
 | 
					        item.subCommissionSecondPrice = formatToFraction(item.subCommissionSecondPrice)
 | 
				
			||||||
 | 
					      })
 | 
				
			||||||
      formData.value = res
 | 
					      formData.value = res
 | 
				
			||||||
      // 直接取第一个值就能得到所有属性的id
 | 
					      // 只有是多规格才处理
 | 
				
			||||||
      // TODO @puhui999:可以直接拿 propertyName 拼接处规格 id + 属性,可以看下商品 uniapp 详情的做法
 | 
					      if (res.specType) {
 | 
				
			||||||
      const propertyIds = res.skus[0]?.properties.map((item) => item.propertyId)
 | 
					        // TODO @puhui999:可以直接拿 propertyName 拼接处规格 id + 属性,可以看下商品 uniapp 详情的做法
 | 
				
			||||||
      const PropertyS = await PropertyApi.getPropertyListAndValue({ propertyIds })
 | 
					        // fix: 考虑到 sku 数量和通过属性算出来的sku不一致的情况
 | 
				
			||||||
      await nextTick()
 | 
					        const propertyIds = []
 | 
				
			||||||
      // 回显商品属性
 | 
					        res.skus.forEach((sku) =>
 | 
				
			||||||
      basicInfoRef.value.addAttribute(PropertyS)
 | 
					          sku.properties
 | 
				
			||||||
 | 
					            ?.map((property) => property.propertyId)
 | 
				
			||||||
 | 
					            .forEach((propertyId) => {
 | 
				
			||||||
 | 
					              if (propertyIds.indexOf(propertyId) === -1) {
 | 
				
			||||||
 | 
					                propertyIds.push(propertyId)
 | 
				
			||||||
 | 
					              }
 | 
				
			||||||
 | 
					            })
 | 
				
			||||||
 | 
					        )
 | 
				
			||||||
 | 
					        const properties = await PropertyApi.getPropertyListAndValue({ propertyIds })
 | 
				
			||||||
 | 
					        await nextTick()
 | 
				
			||||||
 | 
					        // 回显商品属性
 | 
				
			||||||
 | 
					        basicInfoRef.value.addAttribute(properties)
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
    } finally {
 | 
					    } finally {
 | 
				
			||||||
      formLoading.value = false
 | 
					      formLoading.value = false
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
@@ -119,10 +141,26 @@ const submitForm = async () => {
 | 
				
			|||||||
    await unref(descriptionRef)?.validate()
 | 
					    await unref(descriptionRef)?.validate()
 | 
				
			||||||
    await unref(otherSettingsRef)?.validate()
 | 
					    await unref(otherSettingsRef)?.validate()
 | 
				
			||||||
    const deepCopyFormData = cloneDeep(unref(formData.value)) // 深拷贝一份 fix:这样最终 server 端不满足,不需要恢复,
 | 
					    const deepCopyFormData = cloneDeep(unref(formData.value)) // 深拷贝一份 fix:这样最终 server 端不满足,不需要恢复,
 | 
				
			||||||
    // 处理掉一些无关数据
 | 
					    // TODO 兜底处理 sku 空数据详见 SkuList TODO
 | 
				
			||||||
 | 
					    formData.value.skus.forEach((sku) => {
 | 
				
			||||||
 | 
					      // 因为是空数据这里判断一下商品条码是否为空就行
 | 
				
			||||||
 | 
					      if (sku.barCode === '') {
 | 
				
			||||||
 | 
					        const index = deepCopyFormData.skus.findIndex(
 | 
				
			||||||
 | 
					          (item) => JSON.stringify(item.properties) === JSON.stringify(sku.properties)
 | 
				
			||||||
 | 
					        )
 | 
				
			||||||
 | 
					        // 删除这条 sku
 | 
				
			||||||
 | 
					        deepCopyFormData.skus.splice(index, 1)
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					    })
 | 
				
			||||||
    deepCopyFormData.skus.forEach((item) => {
 | 
					    deepCopyFormData.skus.forEach((item) => {
 | 
				
			||||||
      // 给sku name赋值
 | 
					      // 给sku name赋值
 | 
				
			||||||
      item.name = deepCopyFormData.name
 | 
					      item.name = deepCopyFormData.name
 | 
				
			||||||
 | 
					      // sku相关价格元转分
 | 
				
			||||||
 | 
					      item.price = convertToInteger(item.price)
 | 
				
			||||||
 | 
					      item.marketPrice = convertToInteger(item.marketPrice)
 | 
				
			||||||
 | 
					      item.costPrice = convertToInteger(item.costPrice)
 | 
				
			||||||
 | 
					      item.subCommissionFirstPrice = convertToInteger(item.subCommissionFirstPrice)
 | 
				
			||||||
 | 
					      item.subCommissionSecondPrice = convertToInteger(item.subCommissionSecondPrice)
 | 
				
			||||||
    })
 | 
					    })
 | 
				
			||||||
    // 处理轮播图列表
 | 
					    // 处理轮播图列表
 | 
				
			||||||
    const newSliderPicUrls = []
 | 
					    const newSliderPicUrls = []
 | 
				
			||||||
@@ -148,34 +186,6 @@ const submitForm = async () => {
 | 
				
			|||||||
  }
 | 
					  }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/**
 | 
					 | 
				
			||||||
 * 重置表单
 | 
					 | 
				
			||||||
 * fix:先注释保留,如果后期没有使用到则移除
 | 
					 | 
				
			||||||
 */
 | 
					 | 
				
			||||||
// const resetForm = async () => {
 | 
					 | 
				
			||||||
//   formData.value = {
 | 
					 | 
				
			||||||
//     name: '', // 商品名称
 | 
					 | 
				
			||||||
//     categoryId: 0, // 商品分类
 | 
					 | 
				
			||||||
//     keyword: '', // 关键字
 | 
					 | 
				
			||||||
//     unit: '', // 单位
 | 
					 | 
				
			||||||
//     picUrl: '', // 商品封面图
 | 
					 | 
				
			||||||
//     sliderPicUrls: [], // 商品轮播图
 | 
					 | 
				
			||||||
//     introduction: '', // 商品简介
 | 
					 | 
				
			||||||
//     deliveryTemplateId: 0, // 运费模版
 | 
					 | 
				
			||||||
//     selectRule: '',
 | 
					 | 
				
			||||||
//     specType: false, // 商品规格
 | 
					 | 
				
			||||||
//     subCommissionType: false, // 分销类型
 | 
					 | 
				
			||||||
//     description: '', // 商品详情
 | 
					 | 
				
			||||||
//     sort: 1, // 商品排序
 | 
					 | 
				
			||||||
//     giveIntegral: 1, // 赠送积分
 | 
					 | 
				
			||||||
//     virtualSalesCount: 1, // 虚拟销量
 | 
					 | 
				
			||||||
//     recommendHot: false, // 是否热卖
 | 
					 | 
				
			||||||
//     recommendBenefit: false, // 是否优惠
 | 
					 | 
				
			||||||
//     recommendBest: false, // 是否精品
 | 
					 | 
				
			||||||
//     recommendNew: false, // 是否新品
 | 
					 | 
				
			||||||
//     recommendGood: false // 是否优品
 | 
					 | 
				
			||||||
//   }
 | 
					 | 
				
			||||||
// }
 | 
					 | 
				
			||||||
/** 关闭按钮 */
 | 
					/** 关闭按钮 */
 | 
				
			||||||
const close = () => {
 | 
					const close = () => {
 | 
				
			||||||
  delView(unref(currentRoute))
 | 
					  delView(unref(currentRoute))
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -59,13 +59,23 @@
 | 
				
			|||||||
      </el-col>
 | 
					      </el-col>
 | 
				
			||||||
      <el-col :span="12">
 | 
					      <el-col :span="12">
 | 
				
			||||||
        <el-form-item label="运费模板" prop="deliveryTemplateId">
 | 
					        <el-form-item label="运费模板" prop="deliveryTemplateId">
 | 
				
			||||||
          <el-select v-model="formData.deliveryTemplateId" class="w-1/1" placeholder="请选择">
 | 
					          <el-select v-model="formData.deliveryTemplateId" placeholder="请选择">
 | 
				
			||||||
            <el-option v-for="item in []" :key="item.id" :label="item.name" :value="item.id" />
 | 
					            <el-option v-for="item in []" :key="item.id" :label="item.name" :value="item.id" />
 | 
				
			||||||
          </el-select>
 | 
					          </el-select>
 | 
				
			||||||
 | 
					          <el-button class="ml-20px">运费模板</el-button>
 | 
				
			||||||
        </el-form-item>
 | 
					        </el-form-item>
 | 
				
			||||||
      </el-col>
 | 
					      </el-col>
 | 
				
			||||||
      <el-col :span="12">
 | 
					      <el-col :span="12">
 | 
				
			||||||
        <el-button class="ml-20px">运费模板</el-button>
 | 
					        <el-form-item label="品牌" prop="brandId">
 | 
				
			||||||
 | 
					          <el-select v-model="formData.brandId" placeholder="请选择">
 | 
				
			||||||
 | 
					            <el-option
 | 
				
			||||||
 | 
					              v-for="item in brandList"
 | 
				
			||||||
 | 
					              :key="item.id"
 | 
				
			||||||
 | 
					              :label="item.name"
 | 
				
			||||||
 | 
					              :value="item.id"
 | 
				
			||||||
 | 
					            />
 | 
				
			||||||
 | 
					          </el-select>
 | 
				
			||||||
 | 
					        </el-form-item>
 | 
				
			||||||
      </el-col>
 | 
					      </el-col>
 | 
				
			||||||
      <el-col :span="12">
 | 
					      <el-col :span="12">
 | 
				
			||||||
        <el-form-item label="商品规格" props="specType">
 | 
					        <el-form-item label="商品规格" props="specType">
 | 
				
			||||||
@@ -108,14 +118,15 @@
 | 
				
			|||||||
</template>
 | 
					</template>
 | 
				
			||||||
<script lang="ts" name="ProductSpuBasicInfoForm" setup>
 | 
					<script lang="ts" name="ProductSpuBasicInfoForm" setup>
 | 
				
			||||||
import { PropType } from 'vue'
 | 
					import { PropType } from 'vue'
 | 
				
			||||||
 | 
					import { copyValueToTarget } from '@/utils'
 | 
				
			||||||
 | 
					import { propTypes } from '@/utils/propTypes'
 | 
				
			||||||
import { defaultProps, handleTree } from '@/utils/tree'
 | 
					import { defaultProps, handleTree } from '@/utils/tree'
 | 
				
			||||||
import { DICT_TYPE, getIntDictOptions } from '@/utils/dict'
 | 
					import { DICT_TYPE, getIntDictOptions } from '@/utils/dict'
 | 
				
			||||||
import type { SpuType } from '@/api/mall/product/spu'
 | 
					import type { SpuType } from '@/api/mall/product/spu'
 | 
				
			||||||
import { UploadImg, UploadImgs } from '@/components/UploadFile'
 | 
					import { UploadImg, UploadImgs } from '@/components/UploadFile'
 | 
				
			||||||
import { ProductAttributes, ProductAttributesAddForm, SkuList } from './index'
 | 
					import { ProductAttributes, ProductAttributesAddForm, SkuList } from './index'
 | 
				
			||||||
import * as ProductCategoryApi from '@/api/mall/product/category'
 | 
					import * as ProductCategoryApi from '@/api/mall/product/category'
 | 
				
			||||||
import { propTypes } from '@/utils/propTypes'
 | 
					import { getSimpleBrandList } from '@/api/mall/product/brand'
 | 
				
			||||||
import { copyValueToTarget } from '@/utils'
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
const message = useMessage() // 消息弹窗
 | 
					const message = useMessage() // 消息弹窗
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -135,15 +146,20 @@ const propertyList = ref([]) // 商品属性列表
 | 
				
			|||||||
const addAttribute = (property: any) => {
 | 
					const addAttribute = (property: any) => {
 | 
				
			||||||
  Array.isArray(property) ? (propertyList.value = property) : propertyList.value.push(property)
 | 
					  Array.isArray(property) ? (propertyList.value = property) : propertyList.value.push(property)
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					/** 调用 SkuList generateTableData 方法*/
 | 
				
			||||||
 | 
					// const generateSkus(propertyList){
 | 
				
			||||||
 | 
					//   skuList.value.generateTableData()
 | 
				
			||||||
 | 
					// }
 | 
				
			||||||
const formData = reactive<SpuType>({
 | 
					const formData = reactive<SpuType>({
 | 
				
			||||||
  name: '', // 商品名称
 | 
					  name: '', // 商品名称
 | 
				
			||||||
  categoryId: undefined, // 商品分类
 | 
					  categoryId: null, // 商品分类
 | 
				
			||||||
  keyword: '', // 关键字
 | 
					  keyword: '', // 关键字
 | 
				
			||||||
  unit: '', // 单位
 | 
					  unit: '', // 单位
 | 
				
			||||||
  picUrl: '', // 商品封面图
 | 
					  picUrl: '', // 商品封面图
 | 
				
			||||||
  sliderPicUrls: [], // 商品轮播图
 | 
					  sliderPicUrls: [], // 商品轮播图
 | 
				
			||||||
  introduction: '', // 商品简介
 | 
					  introduction: '', // 商品简介
 | 
				
			||||||
  deliveryTemplateId: 1, // 运费模版
 | 
					  deliveryTemplateId: 1, // 运费模版
 | 
				
			||||||
 | 
					  brandId: null, // 商品品牌
 | 
				
			||||||
  specType: false, // 商品规格
 | 
					  specType: false, // 商品规格
 | 
				
			||||||
  subCommissionType: false, // 分销类型
 | 
					  subCommissionType: false, // 分销类型
 | 
				
			||||||
  skus: []
 | 
					  skus: []
 | 
				
			||||||
@@ -157,6 +173,7 @@ const rules = reactive({
 | 
				
			|||||||
  picUrl: [required],
 | 
					  picUrl: [required],
 | 
				
			||||||
  sliderPicUrls: [required],
 | 
					  sliderPicUrls: [required],
 | 
				
			||||||
  // deliveryTemplateId: [required],
 | 
					  // deliveryTemplateId: [required],
 | 
				
			||||||
 | 
					  brandId: [required],
 | 
				
			||||||
  specType: [required],
 | 
					  specType: [required],
 | 
				
			||||||
  subCommissionType: [required]
 | 
					  subCommissionType: [required]
 | 
				
			||||||
})
 | 
					})
 | 
				
			||||||
@@ -232,10 +249,13 @@ const onChangeSpec = () => {
 | 
				
			|||||||
  ]
 | 
					  ]
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const categoryList = ref() // 分类树
 | 
					const categoryList = ref([]) // 分类树
 | 
				
			||||||
 | 
					const brandList = ref([]) // 精简商品品牌列表
 | 
				
			||||||
onMounted(async () => {
 | 
					onMounted(async () => {
 | 
				
			||||||
  // 获得分类树
 | 
					  // 获得分类树
 | 
				
			||||||
  const data = await ProductCategoryApi.getCategoryList({})
 | 
					  const data = await ProductCategoryApi.getCategoryList({})
 | 
				
			||||||
  categoryList.value = handleTree(data, 'id', 'parentId')
 | 
					  categoryList.value = handleTree(data, 'id', 'parentId')
 | 
				
			||||||
 | 
					  // 获取商品品牌列表
 | 
				
			||||||
 | 
					  brandList.value = await getSimpleBrandList()
 | 
				
			||||||
})
 | 
					})
 | 
				
			||||||
</script>
 | 
					</script>
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -34,17 +34,29 @@
 | 
				
			|||||||
    <!-- TODO @puhui999:用户输入的时候,是按照元;分主要是我们自己用;fix -->
 | 
					    <!-- TODO @puhui999:用户输入的时候,是按照元;分主要是我们自己用;fix -->
 | 
				
			||||||
    <el-table-column align="center" label="销售价(元)" min-width="168">
 | 
					    <el-table-column align="center" label="销售价(元)" min-width="168">
 | 
				
			||||||
      <template #default="{ row }">
 | 
					      <template #default="{ row }">
 | 
				
			||||||
        <el-input-number v-model="row.price" :min="0" class="w-100%" />
 | 
					        <el-input-number v-model="row.price" :min="0" :precision="2" :step="0.1" class="w-100%" />
 | 
				
			||||||
      </template>
 | 
					      </template>
 | 
				
			||||||
    </el-table-column>
 | 
					    </el-table-column>
 | 
				
			||||||
    <el-table-column align="center" label="市场价(元)" min-width="168">
 | 
					    <el-table-column align="center" label="市场价(元)" min-width="168">
 | 
				
			||||||
      <template #default="{ row }">
 | 
					      <template #default="{ row }">
 | 
				
			||||||
        <el-input-number v-model="row.marketPrice" :min="0" class="w-100%" />
 | 
					        <el-input-number
 | 
				
			||||||
 | 
					          v-model="row.marketPrice"
 | 
				
			||||||
 | 
					          :min="0"
 | 
				
			||||||
 | 
					          :precision="2"
 | 
				
			||||||
 | 
					          :step="0.1"
 | 
				
			||||||
 | 
					          class="w-100%"
 | 
				
			||||||
 | 
					        />
 | 
				
			||||||
      </template>
 | 
					      </template>
 | 
				
			||||||
    </el-table-column>
 | 
					    </el-table-column>
 | 
				
			||||||
    <el-table-column align="center" label="成本价(元)" min-width="168">
 | 
					    <el-table-column align="center" label="成本价(元)" min-width="168">
 | 
				
			||||||
      <template #default="{ row }">
 | 
					      <template #default="{ row }">
 | 
				
			||||||
        <el-input-number v-model="row.costPrice" :min="0" class="w-100%" />
 | 
					        <el-input-number
 | 
				
			||||||
 | 
					          v-model="row.costPrice"
 | 
				
			||||||
 | 
					          :min="0"
 | 
				
			||||||
 | 
					          :precision="2"
 | 
				
			||||||
 | 
					          :step="0.1"
 | 
				
			||||||
 | 
					          class="w-100%"
 | 
				
			||||||
 | 
					        />
 | 
				
			||||||
      </template>
 | 
					      </template>
 | 
				
			||||||
    </el-table-column>
 | 
					    </el-table-column>
 | 
				
			||||||
    <el-table-column align="center" label="库存" min-width="168">
 | 
					    <el-table-column align="center" label="库存" min-width="168">
 | 
				
			||||||
@@ -54,42 +66,54 @@
 | 
				
			|||||||
    </el-table-column>
 | 
					    </el-table-column>
 | 
				
			||||||
    <el-table-column align="center" label="重量(kg)" min-width="168">
 | 
					    <el-table-column align="center" label="重量(kg)" min-width="168">
 | 
				
			||||||
      <template #default="{ row }">
 | 
					      <template #default="{ row }">
 | 
				
			||||||
        <el-input-number v-model="row.weight" :min="0" class="w-100%" />
 | 
					        <el-input-number v-model="row.weight" :min="0" :precision="2" :step="0.1" class="w-100%" />
 | 
				
			||||||
      </template>
 | 
					      </template>
 | 
				
			||||||
    </el-table-column>
 | 
					    </el-table-column>
 | 
				
			||||||
    <el-table-column align="center" label="体积(m^3)" min-width="168">
 | 
					    <el-table-column align="center" label="体积(m^3)" min-width="168">
 | 
				
			||||||
      <template #default="{ row }">
 | 
					      <template #default="{ row }">
 | 
				
			||||||
        <el-input-number v-model="row.volume" :min="0" class="w-100%" />
 | 
					        <el-input-number v-model="row.volume" :min="0" :precision="2" :step="0.1" class="w-100%" />
 | 
				
			||||||
      </template>
 | 
					      </template>
 | 
				
			||||||
    </el-table-column>
 | 
					    </el-table-column>
 | 
				
			||||||
    <template v-if="formData.subCommissionType">
 | 
					    <template v-if="formData.subCommissionType">
 | 
				
			||||||
      <el-table-column align="center" label="一级返佣(元)" min-width="168">
 | 
					      <el-table-column align="center" label="一级返佣(元)" min-width="168">
 | 
				
			||||||
        <template #default="{ row }">
 | 
					        <template #default="{ row }">
 | 
				
			||||||
          <el-input-number v-model="row.subCommissionFirstPrice" :min="0" class="w-100%" />
 | 
					          <el-input-number
 | 
				
			||||||
 | 
					            v-model="row.subCommissionFirstPrice"
 | 
				
			||||||
 | 
					            :min="0"
 | 
				
			||||||
 | 
					            :precision="2"
 | 
				
			||||||
 | 
					            :step="0.1"
 | 
				
			||||||
 | 
					            class="w-100%"
 | 
				
			||||||
 | 
					          />
 | 
				
			||||||
        </template>
 | 
					        </template>
 | 
				
			||||||
      </el-table-column>
 | 
					      </el-table-column>
 | 
				
			||||||
      <el-table-column align="center" label="二级返佣(元)" min-width="168">
 | 
					      <el-table-column align="center" label="二级返佣(元)" min-width="168">
 | 
				
			||||||
        <template #default="{ row }">
 | 
					        <template #default="{ row }">
 | 
				
			||||||
          <el-input-number v-model="row.subCommissionSecondPrice" :min="0" class="w-100%" />
 | 
					          <el-input-number
 | 
				
			||||||
 | 
					            v-model="row.subCommissionSecondPrice"
 | 
				
			||||||
 | 
					            :min="0"
 | 
				
			||||||
 | 
					            :precision="2"
 | 
				
			||||||
 | 
					            :step="0.1"
 | 
				
			||||||
 | 
					            class="w-100%"
 | 
				
			||||||
 | 
					          />
 | 
				
			||||||
        </template>
 | 
					        </template>
 | 
				
			||||||
      </el-table-column>
 | 
					      </el-table-column>
 | 
				
			||||||
    </template>
 | 
					    </template>
 | 
				
			||||||
    <el-table-column v-if="formData.specType" align="center" fixed="right" label="操作" width="80">
 | 
					    <el-table-column v-if="formData.specType" align="center" fixed="right" label="操作" width="80">
 | 
				
			||||||
      <template #default>
 | 
					      <template #default="{ row }">
 | 
				
			||||||
        <el-button v-if="isBatch" link size="small" type="primary" @click="batchAdd">
 | 
					        <el-button v-if="isBatch" link size="small" type="primary" @click="batchAdd">
 | 
				
			||||||
          批量添加
 | 
					          批量添加
 | 
				
			||||||
        </el-button>
 | 
					        </el-button>
 | 
				
			||||||
        <el-button v-else link size="small" type="primary">删除</el-button>
 | 
					        <el-button v-else link size="small" type="primary" @click="deleteSku(row)">删除</el-button>
 | 
				
			||||||
      </template>
 | 
					      </template>
 | 
				
			||||||
    </el-table-column>
 | 
					    </el-table-column>
 | 
				
			||||||
  </el-table>
 | 
					  </el-table>
 | 
				
			||||||
</template>
 | 
					</template>
 | 
				
			||||||
<script lang="ts" name="SkuList" setup>
 | 
					<script lang="ts" name="SkuList" setup>
 | 
				
			||||||
import { UploadImg } from '@/components/UploadFile'
 | 
					 | 
				
			||||||
import { PropType } from 'vue'
 | 
					import { PropType } from 'vue'
 | 
				
			||||||
import type { Property, SkuType, SpuType } from '@/api/mall/product/spu'
 | 
					 | 
				
			||||||
import { propTypes } from '@/utils/propTypes'
 | 
					 | 
				
			||||||
import { copyValueToTarget } from '@/utils'
 | 
					import { copyValueToTarget } from '@/utils'
 | 
				
			||||||
 | 
					import { propTypes } from '@/utils/propTypes'
 | 
				
			||||||
 | 
					import { UploadImg } from '@/components/UploadFile'
 | 
				
			||||||
 | 
					import type { Property, SkuType, SpuType } from '@/api/mall/product/spu'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const props = defineProps({
 | 
					const props = defineProps({
 | 
				
			||||||
  propFormData: {
 | 
					  propFormData: {
 | 
				
			||||||
@@ -124,7 +148,14 @@ const batchAdd = () => {
 | 
				
			|||||||
    copyValueToTarget(item, skuList.value[0])
 | 
					    copyValueToTarget(item, skuList.value[0])
 | 
				
			||||||
  })
 | 
					  })
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					/** 删除 sku */
 | 
				
			||||||
 | 
					const deleteSku = (row) => {
 | 
				
			||||||
 | 
					  const index = formData.value.skus.findIndex(
 | 
				
			||||||
 | 
					    // 直接把列表转成字符串比较
 | 
				
			||||||
 | 
					    (sku) => JSON.stringify(sku.properties) === JSON.stringify(row.properties)
 | 
				
			||||||
 | 
					  )
 | 
				
			||||||
 | 
					  formData.value.skus.splice(index, 1)
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
const tableHeaders = ref<{ prop: string; label: string }[]>([]) // 多属性表头
 | 
					const tableHeaders = ref<{ prop: string; label: string }[]>([]) // 多属性表头
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/**
 | 
					/**
 | 
				
			||||||
@@ -142,11 +173,11 @@ watch(
 | 
				
			|||||||
  }
 | 
					  }
 | 
				
			||||||
)
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// TODO @芋艿:看看 chatgpt 可以进一步下面几个方法的实现不 fix
 | 
					// TODO @芋艿:看看 chatgpt 可以进一步下面几个方法的实现不 fix: 添加相关处理逻辑解决编辑表单时或查看详情时数据回显问题
 | 
				
			||||||
/** 生成表数据 */
 | 
					/** 生成表数据 */
 | 
				
			||||||
const generateTableData = (data: any[]) => {
 | 
					const generateTableData = (propertyList: any[]) => {
 | 
				
			||||||
  // 构建数据结构 fix: 使用map替换多重for循环
 | 
					  // 构建数据结构 fix: 使用map替换多重for循环
 | 
				
			||||||
  const propertiesItemList = data.map((item) =>
 | 
					  const propertiesItemList = propertyList.map((item) =>
 | 
				
			||||||
    item.values.map((v) => ({
 | 
					    item.values.map((v) => ({
 | 
				
			||||||
      propertyId: item.id,
 | 
					      propertyId: item.id,
 | 
				
			||||||
      propertyName: item.name,
 | 
					      propertyName: item.name,
 | 
				
			||||||
@@ -155,19 +186,14 @@ const generateTableData = (data: any[]) => {
 | 
				
			|||||||
    }))
 | 
					    }))
 | 
				
			||||||
  )
 | 
					  )
 | 
				
			||||||
  const buildList = build(propertiesItemList)
 | 
					  const buildList = build(propertiesItemList)
 | 
				
			||||||
  // 如果构建后的组合数跟sku数量一样的话则不用处理,添加新属性没有属性值也不做处理
 | 
					  // 如果回显的 sku 属性和添加的属性不一致则重置 skus 列表
 | 
				
			||||||
  // fix: 解决编辑表单时或查看详情时数据回显问题
 | 
					  if (!validateData(propertyList)) {
 | 
				
			||||||
  if (
 | 
					    // 如果不一致则重置表数据,默认添加新的属性重新生成sku列表
 | 
				
			||||||
    buildList.length === formData.value.skus.length ||
 | 
					    formData.value!.skus = []
 | 
				
			||||||
    data.some((item) => item.values.length === 0)
 | 
					 | 
				
			||||||
  ) {
 | 
					 | 
				
			||||||
    return
 | 
					 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
  // 重置表数据
 | 
					  for (const item of buildList) {
 | 
				
			||||||
  formData.value!.skus = []
 | 
					 | 
				
			||||||
  buildList.forEach((item) => {
 | 
					 | 
				
			||||||
    const row = {
 | 
					    const row = {
 | 
				
			||||||
      properties: Array.isArray(item) ? item : [item],
 | 
					      properties: Array.isArray(item) ? item : [item], // 如果只有一个属性的话返回的是一个property对象
 | 
				
			||||||
      price: 0,
 | 
					      price: 0,
 | 
				
			||||||
      marketPrice: 0,
 | 
					      marketPrice: 0,
 | 
				
			||||||
      costPrice: 0,
 | 
					      costPrice: 0,
 | 
				
			||||||
@@ -179,8 +205,36 @@ const generateTableData = (data: any[]) => {
 | 
				
			|||||||
      subCommissionFirstPrice: 0,
 | 
					      subCommissionFirstPrice: 0,
 | 
				
			||||||
      subCommissionSecondPrice: 0
 | 
					      subCommissionSecondPrice: 0
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					    const index = formData.value!.skus.findIndex(
 | 
				
			||||||
 | 
					      (sku) => JSON.stringify(sku.properties) === JSON.stringify(row.properties)
 | 
				
			||||||
 | 
					    )
 | 
				
			||||||
 | 
					    // 如果存在属性相同的 sku 则不做处理
 | 
				
			||||||
 | 
					    if (index !== -1) {
 | 
				
			||||||
 | 
					      continue
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					     * TODO 嗯。。有一个问题回显数据时已删除的 sku 会被重新添加暂时没想到好办法,保存时先手动重新删除一下因为是一条空数据很好辨别 不手动删也没是提交表单时会检测删除空sku来兜底
 | 
				
			||||||
 | 
					     *
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
    formData.value.skus.push(row)
 | 
					    formData.value.skus.push(row)
 | 
				
			||||||
  })
 | 
					  }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					/**
 | 
				
			||||||
 | 
					 * 生成 skus 前置校验
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					const validateData = (propertyList: any[]) => {
 | 
				
			||||||
 | 
					  const skuPropertyIds = []
 | 
				
			||||||
 | 
					  formData.value.skus.forEach((sku) =>
 | 
				
			||||||
 | 
					    sku.properties
 | 
				
			||||||
 | 
					      ?.map((property) => property.propertyId)
 | 
				
			||||||
 | 
					      .forEach((propertyId) => {
 | 
				
			||||||
 | 
					        if (skuPropertyIds.indexOf(propertyId) === -1) {
 | 
				
			||||||
 | 
					          skuPropertyIds.push(propertyId)
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					      })
 | 
				
			||||||
 | 
					  )
 | 
				
			||||||
 | 
					  const propertyIds = propertyList.map((item) => item.id)
 | 
				
			||||||
 | 
					  return skuPropertyIds.length === propertyIds.length
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
/** 构建所有排列组合 */
 | 
					/** 构建所有排列组合 */
 | 
				
			||||||
const build = (propertyValuesList: Property[][]) => {
 | 
					const build = (propertyValuesList: Property[][]) => {
 | 
				
			||||||
@@ -237,6 +291,10 @@ watch(
 | 
				
			|||||||
      // name加属性项index区分属性值
 | 
					      // name加属性项index区分属性值
 | 
				
			||||||
      tableHeaders.value.push({ prop: `name${index}`, label: item.name })
 | 
					      tableHeaders.value.push({ prop: `name${index}`, label: item.name })
 | 
				
			||||||
    })
 | 
					    })
 | 
				
			||||||
 | 
					    // 如果回显的 sku 属性和添加的属性一致则不处理
 | 
				
			||||||
 | 
					    if (validateData(propertyList)) return
 | 
				
			||||||
 | 
					    // 添加新属性没有属性值也不做处理
 | 
				
			||||||
 | 
					    if (propertyList.some((item) => item.values.length === 0)) return
 | 
				
			||||||
    generateTableData(propertyList)
 | 
					    generateTableData(propertyList)
 | 
				
			||||||
  },
 | 
					  },
 | 
				
			||||||
  {
 | 
					  {
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -75,10 +75,10 @@
 | 
				
			|||||||
        <template #default="{ row }">
 | 
					        <template #default="{ row }">
 | 
				
			||||||
          <el-form class="demo-table-expand" inline label-position="left">
 | 
					          <el-form class="demo-table-expand" inline label-position="left">
 | 
				
			||||||
            <el-form-item label="市场价:">
 | 
					            <el-form-item label="市场价:">
 | 
				
			||||||
              <span>{{ row.marketPrice }}</span>
 | 
					              <span>{{ formatToFraction(row.marketPrice) }}</span>
 | 
				
			||||||
            </el-form-item>
 | 
					            </el-form-item>
 | 
				
			||||||
            <el-form-item label="成本价:">
 | 
					            <el-form-item label="成本价:">
 | 
				
			||||||
              <span>{{ row.costPrice }}</span>
 | 
					              <span>{{ formatToFraction(row.costPrice) }}</span>
 | 
				
			||||||
            </el-form-item>
 | 
					            </el-form-item>
 | 
				
			||||||
            <el-form-item label="虚拟销量:">
 | 
					            <el-form-item label="虚拟销量:">
 | 
				
			||||||
              <span>{{ row.virtualSalesCount }}</span>
 | 
					              <span>{{ row.virtualSalesCount }}</span>
 | 
				
			||||||
@@ -99,7 +99,11 @@
 | 
				
			|||||||
      </el-table-column>
 | 
					      </el-table-column>
 | 
				
			||||||
      <el-table-column :show-overflow-tooltip="true" label="商品名称" min-width="300" prop="name" />
 | 
					      <el-table-column :show-overflow-tooltip="true" label="商品名称" min-width="300" prop="name" />
 | 
				
			||||||
      <!-- TODO 价格 / 100.0 -->
 | 
					      <!-- TODO 价格 / 100.0 -->
 | 
				
			||||||
      <el-table-column align="center" label="商品售价" min-width="90" prop="price" />
 | 
					      <el-table-column align="center" label="商品售价" min-width="90" prop="price">
 | 
				
			||||||
 | 
					        <template #default="{ row }">
 | 
				
			||||||
 | 
					          {{ formatToFraction(row.price) }}
 | 
				
			||||||
 | 
					        </template>
 | 
				
			||||||
 | 
					      </el-table-column>
 | 
				
			||||||
      <el-table-column align="center" label="销量" min-width="90" prop="salesCount" />
 | 
					      <el-table-column align="center" label="销量" min-width="90" prop="salesCount" />
 | 
				
			||||||
      <el-table-column align="center" label="库存" min-width="90" prop="stock" />
 | 
					      <el-table-column align="center" label="库存" min-width="90" prop="stock" />
 | 
				
			||||||
      <el-table-column align="center" label="排序" min-width="70" prop="sort" />
 | 
					      <el-table-column align="center" label="排序" min-width="70" prop="sort" />
 | 
				
			||||||
@@ -129,9 +133,12 @@
 | 
				
			|||||||
          </template>
 | 
					          </template>
 | 
				
			||||||
        </template>
 | 
					        </template>
 | 
				
			||||||
      </el-table-column>
 | 
					      </el-table-column>
 | 
				
			||||||
      <el-table-column align="center" fixed="right" label="操作" min-width="150">
 | 
					      <el-table-column align="center" fixed="right" label="操作" min-width="200">
 | 
				
			||||||
        <template #default="{ row }">
 | 
					        <template #default="{ row }">
 | 
				
			||||||
          <!-- TODO @puhui999:【详情】,可以后面点做哈 -->
 | 
					          <!-- TODO @puhui999:【详情】,可以后面点做哈 -->
 | 
				
			||||||
 | 
					          <el-button v-hasPermi="['product:spu:update']" link type="primary" @click="openDetail">
 | 
				
			||||||
 | 
					            详情
 | 
				
			||||||
 | 
					          </el-button>
 | 
				
			||||||
          <template v-if="queryParams.tabType === 4">
 | 
					          <template v-if="queryParams.tabType === 4">
 | 
				
			||||||
            <el-button
 | 
					            <el-button
 | 
				
			||||||
              v-hasPermi="['product:spu:delete']"
 | 
					              v-hasPermi="['product:spu:delete']"
 | 
				
			||||||
@@ -151,7 +158,9 @@
 | 
				
			|||||||
            </el-button>
 | 
					            </el-button>
 | 
				
			||||||
          </template>
 | 
					          </template>
 | 
				
			||||||
          <template v-else>
 | 
					          <template v-else>
 | 
				
			||||||
 | 
					            <!-- 只有不是上架和回收站的商品可以编辑 -->
 | 
				
			||||||
            <el-button
 | 
					            <el-button
 | 
				
			||||||
 | 
					              v-if="queryParams.tabType !== 0"
 | 
				
			||||||
              v-hasPermi="['product:spu:update']"
 | 
					              v-hasPermi="['product:spu:update']"
 | 
				
			||||||
              link
 | 
					              link
 | 
				
			||||||
              type="primary"
 | 
					              type="primary"
 | 
				
			||||||
@@ -189,6 +198,7 @@ import { defaultProps, handleTree } from '@/utils/tree'
 | 
				
			|||||||
import { ProductSpuStatusEnum } from '@/utils/constants'
 | 
					import { ProductSpuStatusEnum } from '@/utils/constants'
 | 
				
			||||||
import * as ProductSpuApi from '@/api/mall/product/spu'
 | 
					import * as ProductSpuApi from '@/api/mall/product/spu'
 | 
				
			||||||
import * as ProductCategoryApi from '@/api/mall/product/category'
 | 
					import * as ProductCategoryApi from '@/api/mall/product/category'
 | 
				
			||||||
 | 
					import { formatToFraction } from '@/utils'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const message = useMessage() // 消息弹窗
 | 
					const message = useMessage() // 消息弹窗
 | 
				
			||||||
const { t } = useI18n() // 国际化
 | 
					const { t } = useI18n() // 国际化
 | 
				
			||||||
@@ -357,7 +367,12 @@ const openForm = (id?: number) => {
 | 
				
			|||||||
  // 新增
 | 
					  // 新增
 | 
				
			||||||
  push('/product/productSpuAdd')
 | 
					  push('/product/productSpuAdd')
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					/**
 | 
				
			||||||
 | 
					 * 查看商品详情
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					const openDetail = () => {
 | 
				
			||||||
 | 
					  message.alert('查看详情未完善!!!')
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
// 监听路由变化更新列表 TODO @puhui999:这个是必须加的么?fix: 因为编辑表单是以路由的方式打开,保存表单后列表不会刷新
 | 
					// 监听路由变化更新列表 TODO @puhui999:这个是必须加的么?fix: 因为编辑表单是以路由的方式打开,保存表单后列表不会刷新
 | 
				
			||||||
watch(
 | 
					watch(
 | 
				
			||||||
  () => currentRoute.value,
 | 
					  () => currentRoute.value,
 | 
				
			||||||
 
 | 
				
			|||||||
		Reference in New Issue
	
	Block a user