mirror of
				https://gitee.com/hhyykk/ipms-sjy-ui.git
				synced 2025-11-04 12:18:43 +08:00 
			
		
		
		
	code review 商品管理的实现
This commit is contained in:
		@@ -37,7 +37,6 @@ import { BasicInfoForm, DescriptionForm, OtherSettingsForm } from './components'
 | 
			
		||||
import type { SpuType } from '@/api/mall/product/management/type/spuType' // 业务api
 | 
			
		||||
import * as managementApi from '@/api/mall/product/management/spu'
 | 
			
		||||
import * as PropertyApi from '@/api/mall/product/property'
 | 
			
		||||
 | 
			
		||||
const { t } = useI18n() // 国际化
 | 
			
		||||
const message = useMessage() // 消息弹窗
 | 
			
		||||
const { push, currentRoute } = useRouter() // 路由
 | 
			
		||||
@@ -69,7 +68,7 @@ const formData = ref<SpuType>({
 | 
			
		||||
  skus: [
 | 
			
		||||
    {
 | 
			
		||||
      /**
 | 
			
		||||
       * 商品价格,单位:分
 | 
			
		||||
       * 商品价格,单位:分 TODO @puhui999:注释放在尾巴哈,简洁一点~
 | 
			
		||||
       */
 | 
			
		||||
      price: 0,
 | 
			
		||||
      /**
 | 
			
		||||
@@ -120,6 +119,7 @@ const formData = ref<SpuType>({
 | 
			
		||||
  recommendNew: false, // 是否新品
 | 
			
		||||
  recommendGood: false // 是否优品
 | 
			
		||||
})
 | 
			
		||||
 | 
			
		||||
/** 获得详情 */
 | 
			
		||||
const getDetail = async () => {
 | 
			
		||||
  const id = query.id as unknown as number
 | 
			
		||||
@@ -129,6 +129,7 @@ const getDetail = async () => {
 | 
			
		||||
      const res = (await managementApi.getSpu(id)) as SpuType
 | 
			
		||||
      formData.value = res
 | 
			
		||||
      // 直接取第一个值就能得到所有属性的id
 | 
			
		||||
      // TODO @puhui999:可以直接拿 propertyName 拼接处规格 id + 属性,可以看下商品 uniapp 详情的做法
 | 
			
		||||
      const propertyIds = res.skus[0]?.properties.map((item) => item.propertyId)
 | 
			
		||||
      const PropertyS = await PropertyApi.getPropertyListAndValue({ propertyIds })
 | 
			
		||||
      await nextTick()
 | 
			
		||||
@@ -151,6 +152,7 @@ const submitForm = async () => {
 | 
			
		||||
    await unref(BasicInfoRef)?.validate()
 | 
			
		||||
    await unref(DescriptionRef)?.validate()
 | 
			
		||||
    await unref(OtherSettingsRef)?.validate()
 | 
			
		||||
    // TODO @puhui:直接做深拷贝?这样最终 server 端不满足,不需要恢复
 | 
			
		||||
    // 处理掉一些无关数据
 | 
			
		||||
    formData.value.skus.forEach((item) => {
 | 
			
		||||
      // 给sku name赋值
 | 
			
		||||
@@ -166,6 +168,7 @@ const submitForm = async () => {
 | 
			
		||||
    const newSliderPicUrls = []
 | 
			
		||||
    formData.value.sliderPicUrls.forEach((item) => {
 | 
			
		||||
      // 如果是前端选的图
 | 
			
		||||
      // TODO @puhui999:疑问哈,为啥会是 object 呀?
 | 
			
		||||
      if (typeof item === 'object') {
 | 
			
		||||
        newSliderPicUrls.push(item.url)
 | 
			
		||||
      } else {
 | 
			
		||||
@@ -224,6 +227,7 @@ const resetForm = async () => {
 | 
			
		||||
}
 | 
			
		||||
/** 关闭按钮 */
 | 
			
		||||
const close = () => {
 | 
			
		||||
  // TODO @puhui999:是不是不用 reset 呀?close 默认销毁
 | 
			
		||||
  resetForm()
 | 
			
		||||
  delView(unref(currentRoute))
 | 
			
		||||
  push('/product/product-management')
 | 
			
		||||
@@ -7,6 +7,7 @@
 | 
			
		||||
        </el-form-item>
 | 
			
		||||
      </el-col>
 | 
			
		||||
      <el-col :span="12">
 | 
			
		||||
        <!-- TODO @puhui999:只能选根节点 -->
 | 
			
		||||
        <el-form-item label="商品分类" prop="categoryId">
 | 
			
		||||
          <el-tree-select
 | 
			
		||||
            v-model="formData.categoryId"
 | 
			
		||||
@@ -15,6 +16,7 @@
 | 
			
		||||
            check-strictly
 | 
			
		||||
            node-key="id"
 | 
			
		||||
            placeholder="请选择商品分类"
 | 
			
		||||
            class="w-1/1"
 | 
			
		||||
          />
 | 
			
		||||
        </el-form-item>
 | 
			
		||||
      </el-col>
 | 
			
		||||
@@ -25,7 +27,7 @@
 | 
			
		||||
      </el-col>
 | 
			
		||||
      <el-col :span="12">
 | 
			
		||||
        <el-form-item label="单位" prop="unit">
 | 
			
		||||
          <el-select v-model="formData.unit" placeholder="请选择单位">
 | 
			
		||||
          <el-select v-model="formData.unit" placeholder="请选择单位" class="w-1/1">
 | 
			
		||||
            <el-option
 | 
			
		||||
              v-for="dict in getIntDictOptions(DICT_TYPE.PRODUCT_UNIT)"
 | 
			
		||||
              :key="dict.value"
 | 
			
		||||
@@ -57,7 +59,7 @@
 | 
			
		||||
      </el-col>
 | 
			
		||||
      <el-col :span="12">
 | 
			
		||||
        <el-form-item label="运费模板" prop="deliveryTemplateId">
 | 
			
		||||
          <el-select v-model="formData.deliveryTemplateId" placeholder="请选择" style="width: 100%">
 | 
			
		||||
          <el-select v-model="formData.deliveryTemplateId" placeholder="请选择" class="w-1/1">
 | 
			
		||||
            <el-option v-for="item in []" :key="item.id" :label="item.name" :value="item.id" />
 | 
			
		||||
          </el-select>
 | 
			
		||||
        </el-form-item>
 | 
			
		||||
@@ -84,9 +86,8 @@
 | 
			
		||||
      <!-- 多规格添加-->
 | 
			
		||||
      <el-col :span="24">
 | 
			
		||||
        <el-form-item v-if="formData.specType" label="商品属性">
 | 
			
		||||
          <el-button class="mr-15px mb-10px" @click="AttributesAddFormRef.open()"
 | 
			
		||||
            >添加规格
 | 
			
		||||
          </el-button>
 | 
			
		||||
          <!-- TODO @puhui999:参考 https://admin.java.crmeb.net/store/list/creatProduct 添加规格好做么?添加的时候,不用输入备注哈 -->
 | 
			
		||||
          <el-button class="mr-15px mb-10px" @click="AttributesAddFormRef.open">添加规格</el-button>
 | 
			
		||||
          <ProductAttributes :attribute-data="attributeList" />
 | 
			
		||||
        </el-form-item>
 | 
			
		||||
        <template v-if="formData.specType && attributeList.length > 0">
 | 
			
		||||
@@ -108,17 +109,15 @@
 | 
			
		||||
<script lang="ts" name="ProductManagementBasicInfoForm" setup>
 | 
			
		||||
import { PropType } from 'vue'
 | 
			
		||||
import { defaultProps, handleTree } from '@/utils/tree'
 | 
			
		||||
import { ElInput } from 'element-plus'
 | 
			
		||||
import { DICT_TYPE, getIntDictOptions } from '@/utils/dict'
 | 
			
		||||
import type { SpuType } from '@/api/mall/product/management/type/spuType'
 | 
			
		||||
import { UploadImg, UploadImgs } from '@/components/UploadFile'
 | 
			
		||||
import { copyValueToTarget } from '@/utils/object'
 | 
			
		||||
import { ProductAttributes, ProductAttributesAddForm, SkuList } from './index'
 | 
			
		||||
// 业务Api
 | 
			
		||||
import * as ProductCategoryApi from '@/api/mall/product/category'
 | 
			
		||||
import { propTypes } from '@/utils/propTypes'
 | 
			
		||||
 | 
			
		||||
const message = useMessage() // 消息弹窗
 | 
			
		||||
 | 
			
		||||
const props = defineProps({
 | 
			
		||||
  propFormData: {
 | 
			
		||||
    type: Object as PropType<SpuType>,
 | 
			
		||||
@@ -126,10 +125,11 @@ const props = defineProps({
 | 
			
		||||
  },
 | 
			
		||||
  activeName: propTypes.string.def('')
 | 
			
		||||
})
 | 
			
		||||
const AttributesAddFormRef = ref() // 添加商品属性表单
 | 
			
		||||
const ProductManagementBasicInfoRef = ref() // 表单Ref
 | 
			
		||||
const AttributesAddFormRef = ref() // 添加商品属性表单 TODO @puhui999:小写开头哈
 | 
			
		||||
const ProductManagementBasicInfoRef = ref() // 表单Ref TODO @puhui999:小写开头哈
 | 
			
		||||
// TODO @puhui999:attributeList 改成 propertyList,会更统一一点
 | 
			
		||||
const attributeList = ref([]) // 商品属性列表
 | 
			
		||||
/** 添加商品属性 */
 | 
			
		||||
/** 添加商品属性 */ // TODO @puhui999:propFormData 算出来
 | 
			
		||||
const addAttribute = (property: any) => {
 | 
			
		||||
  if (Array.isArray(property)) {
 | 
			
		||||
    attributeList.value = property
 | 
			
		||||
@@ -162,8 +162,9 @@ const rules = reactive({
 | 
			
		||||
  specType: [required],
 | 
			
		||||
  subCommissionType: [required]
 | 
			
		||||
})
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * 将传进来的值赋值给formData
 | 
			
		||||
 * 将传进来的值赋值给 formData
 | 
			
		||||
 */
 | 
			
		||||
watch(
 | 
			
		||||
  () => props.propFormData,
 | 
			
		||||
@@ -176,10 +177,11 @@ watch(
 | 
			
		||||
    immediate: true
 | 
			
		||||
  }
 | 
			
		||||
)
 | 
			
		||||
const emit = defineEmits(['update:activeName'])
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * 表单校验
 | 
			
		||||
 */
 | 
			
		||||
const emit = defineEmits(['update:activeName'])
 | 
			
		||||
const validate = async () => {
 | 
			
		||||
  // 校验表单
 | 
			
		||||
  if (!ProductManagementBasicInfoRef) return
 | 
			
		||||
@@ -197,7 +199,7 @@ const validate = async () => {
 | 
			
		||||
}
 | 
			
		||||
defineExpose({ validate, addAttribute })
 | 
			
		||||
 | 
			
		||||
// 分销类型
 | 
			
		||||
/** 分销类型 */
 | 
			
		||||
const changeSubCommissionType = () => {
 | 
			
		||||
  // 默认为零,类型切换后也要重置为零
 | 
			
		||||
  for (const item of formData.skus) {
 | 
			
		||||
@@ -205,7 +207,8 @@ const changeSubCommissionType = () => {
 | 
			
		||||
    item.subCommissionSecondPrice = 0
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
// 选择规格
 | 
			
		||||
 | 
			
		||||
/** 选择规格 */
 | 
			
		||||
const onChangeSpec = () => {
 | 
			
		||||
  // 重置商品属性列表
 | 
			
		||||
  attributeList.value = []
 | 
			
		||||
@@ -25,6 +25,11 @@ const DescriptionFormRef = ref() // 表单Ref
 | 
			
		||||
const formData = ref<SpuType>({
 | 
			
		||||
  description: '' // 商品详情
 | 
			
		||||
})
 | 
			
		||||
// 表单规则
 | 
			
		||||
const rules = reactive({
 | 
			
		||||
  description: [required]
 | 
			
		||||
})
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * 富文本编辑器如果输入过再清空会有残留,需再重置一次
 | 
			
		||||
 */
 | 
			
		||||
@@ -40,10 +45,7 @@ watch(
 | 
			
		||||
    immediate: true
 | 
			
		||||
  }
 | 
			
		||||
)
 | 
			
		||||
// 表单规则
 | 
			
		||||
const rules = reactive({
 | 
			
		||||
  description: [required]
 | 
			
		||||
})
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * 将传进来的值赋值给formData
 | 
			
		||||
 */
 | 
			
		||||
@@ -58,10 +60,11 @@ watch(
 | 
			
		||||
    immediate: true
 | 
			
		||||
  }
 | 
			
		||||
)
 | 
			
		||||
const emit = defineEmits(['update:activeName'])
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * 表单校验
 | 
			
		||||
 */
 | 
			
		||||
const emit = defineEmits(['update:activeName'])
 | 
			
		||||
const validate = async () => {
 | 
			
		||||
  // 校验表单
 | 
			
		||||
  if (!DescriptionFormRef) return
 | 
			
		||||
@@ -1,6 +1,7 @@
 | 
			
		||||
<template>
 | 
			
		||||
  <el-form ref="OtherSettingsFormRef" :model="formData" :rules="rules" label-width="120px">
 | 
			
		||||
    <el-row>
 | 
			
		||||
      <!-- TODO @puhui999:横着三个哈 -->
 | 
			
		||||
      <el-col :span="24">
 | 
			
		||||
        <el-col :span="8">
 | 
			
		||||
          <el-form-item label="商品排序" prop="sort">
 | 
			
		||||
@@ -40,6 +41,7 @@
 | 
			
		||||
          <el-tag class="ml-2" type="warning">拼团</el-tag>
 | 
			
		||||
        </el-form-item>
 | 
			
		||||
      </el-col>
 | 
			
		||||
      <!-- TODO @puhui999:等优惠劵 ok 在搞 -->
 | 
			
		||||
      <el-col :span="24">
 | 
			
		||||
        <el-form-item label="赠送优惠劵">
 | 
			
		||||
          <el-button>选择优惠券</el-button>
 | 
			
		||||
@@ -49,13 +51,12 @@
 | 
			
		||||
  </el-form>
 | 
			
		||||
</template>
 | 
			
		||||
<script lang="ts" name="OtherSettingsForm" setup>
 | 
			
		||||
// 商品推荐
 | 
			
		||||
import type { SpuType } from '@/api/mall/product/management/type/spuType'
 | 
			
		||||
import { PropType } from 'vue'
 | 
			
		||||
import { copyValueToTarget } from '@/utils/object'
 | 
			
		||||
import { propTypes } from '@/utils/propTypes'
 | 
			
		||||
 | 
			
		||||
const message = useMessage() // 消息弹窗
 | 
			
		||||
 | 
			
		||||
const props = defineProps({
 | 
			
		||||
  propFormData: {
 | 
			
		||||
    type: Object as PropType<SpuType>,
 | 
			
		||||
@@ -63,7 +64,7 @@ const props = defineProps({
 | 
			
		||||
  },
 | 
			
		||||
  activeName: propTypes.string.def('')
 | 
			
		||||
})
 | 
			
		||||
// 商品推荐选项
 | 
			
		||||
// 商品推荐选项 TODO @puhui999:这种叫 recommendOptions 会更合适哈
 | 
			
		||||
const recommend = [
 | 
			
		||||
  { name: '是否热卖', value: 'recommendHot' },
 | 
			
		||||
  { name: '是否优惠', value: 'recommendBenefit' },
 | 
			
		||||
@@ -71,10 +72,10 @@ const recommend = [
 | 
			
		||||
  { name: '是否新品', value: 'recommendNew' },
 | 
			
		||||
  { name: '是否优品', value: 'recommendGood' }
 | 
			
		||||
]
 | 
			
		||||
// 选中推荐选项
 | 
			
		||||
const checkboxGroup = ref<string[]>(['recommendHot'])
 | 
			
		||||
// 选择商品后赋值
 | 
			
		||||
const checkboxGroup = ref<string[]>(['recommendHot']) // 选中推荐选项
 | 
			
		||||
/** 选择商品后赋值 */
 | 
			
		||||
const onChangeGroup = () => {
 | 
			
		||||
  // TODO @puhui999:是不是可以遍历 recommend,然后进行是否选中;
 | 
			
		||||
  checkboxGroup.value.includes('recommendHot')
 | 
			
		||||
    ? (formData.value.recommendHot = true)
 | 
			
		||||
    : (formData.value.recommendHot = false)
 | 
			
		||||
@@ -109,6 +110,7 @@ const rules = reactive({
 | 
			
		||||
  giveIntegral: [required],
 | 
			
		||||
  virtualSalesCount: [required]
 | 
			
		||||
})
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * 将传进来的值赋值给formData
 | 
			
		||||
 */
 | 
			
		||||
@@ -130,10 +132,11 @@ watch(
 | 
			
		||||
    immediate: true
 | 
			
		||||
  }
 | 
			
		||||
)
 | 
			
		||||
const emit = defineEmits(['update:activeName'])
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * 表单校验
 | 
			
		||||
 */
 | 
			
		||||
const emit = defineEmits(['update:activeName'])
 | 
			
		||||
const validate = async () => {
 | 
			
		||||
  // 校验表单
 | 
			
		||||
  if (!OtherSettingsFormRef) return
 | 
			
		||||
@@ -149,6 +152,5 @@ const validate = async () => {
 | 
			
		||||
    }
 | 
			
		||||
  })
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
defineExpose({ validate })
 | 
			
		||||
</script>
 | 
			
		||||
@@ -71,16 +71,19 @@ watch(
 | 
			
		||||
    immediate: true
 | 
			
		||||
  }
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
/** 删除标签 tagValue 标签值*/
 | 
			
		||||
const handleClose = (index, valueIndex) => {
 | 
			
		||||
  attributeList.value[index].values?.splice(valueIndex, 1)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/** 显示输入框并获取焦点 */
 | 
			
		||||
const showInput = async (index) => {
 | 
			
		||||
  attributeIndex.value = index
 | 
			
		||||
  // 因为组件在ref中所以需要用索引获取对应的Ref
 | 
			
		||||
  InputRef.value[index]!.input!.focus()
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/** 输入框失去焦点或点击回车时触发 */
 | 
			
		||||
const handleInputConfirm = async (index, propertyId) => {
 | 
			
		||||
  if (inputValue.value) {
 | 
			
		||||
@@ -25,11 +25,13 @@
 | 
			
		||||
        </template>
 | 
			
		||||
      </el-table-column>
 | 
			
		||||
    </template>
 | 
			
		||||
    <!-- TODO @puhui999: controls-position="right" 可以去掉哈,不然太长了,手动输入更方便 -->
 | 
			
		||||
    <el-table-column align="center" label="商品条码" min-width="168">
 | 
			
		||||
      <template #default="{ row }">
 | 
			
		||||
        <el-input v-model="row.barCode" class="w-100%" />
 | 
			
		||||
      </template>
 | 
			
		||||
    </el-table-column>
 | 
			
		||||
    <!-- TODO @puhui999:用户输入的时候,是按照元;分主要是我们自己用; -->
 | 
			
		||||
    <el-table-column align="center" label="销售价(分)" min-width="168">
 | 
			
		||||
      <template #default="{ row }">
 | 
			
		||||
        <el-input-number v-model="row.price" :min="0" class="w-100%" controls-position="right" />
 | 
			
		||||
@@ -94,15 +96,14 @@
 | 
			
		||||
    </template>
 | 
			
		||||
    <el-table-column v-if="formData.specType" align="center" fixed="right" label="操作" width="80">
 | 
			
		||||
      <template #default>
 | 
			
		||||
        <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 v-else link size="small" type="primary">删除</el-button>
 | 
			
		||||
      </template>
 | 
			
		||||
    </el-table-column>
 | 
			
		||||
  </el-table>
 | 
			
		||||
</template>
 | 
			
		||||
 | 
			
		||||
<script lang="ts" name="SkuList" setup>
 | 
			
		||||
import { UploadImg } from '@/components/UploadFile'
 | 
			
		||||
import { PropType } from 'vue'
 | 
			
		||||
@@ -123,7 +124,7 @@ const props = defineProps({
 | 
			
		||||
  isBatch: propTypes.bool.def(false) // 是否批量操作
 | 
			
		||||
})
 | 
			
		||||
const formData = ref<SpuType>() // 表单数据
 | 
			
		||||
// 批量添加时的零时数据
 | 
			
		||||
// 批量添加时的零时数据 TODO @puhui999:小写开头哈;然后变量都尾注释
 | 
			
		||||
const SkuData = ref<SkuType[]>([
 | 
			
		||||
  {
 | 
			
		||||
    /**
 | 
			
		||||
@@ -168,13 +169,16 @@ const SkuData = ref<SkuType[]>([
 | 
			
		||||
    subCommissionSecondPrice: 0
 | 
			
		||||
  }
 | 
			
		||||
])
 | 
			
		||||
 | 
			
		||||
/** 批量添加 */
 | 
			
		||||
const batchAdd = () => {
 | 
			
		||||
  formData.value.skus.forEach((item) => {
 | 
			
		||||
    copyValueToTarget(item, SkuData.value[0])
 | 
			
		||||
  })
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
const tableHeaderList = ref<{ prop: string; label: string }[]>([])
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * 将传进来的值赋值给SkuData
 | 
			
		||||
 */
 | 
			
		||||
@@ -189,6 +193,8 @@ watch(
 | 
			
		||||
    immediate: true
 | 
			
		||||
  }
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
// TODO @芋艿:看看 chatgpt 可以进一步下面几个方法的实现不
 | 
			
		||||
/** 生成表数据 */
 | 
			
		||||
const generateTableData = (data: any[]) => {
 | 
			
		||||
  // 构建数据结构
 | 
			
		||||
@@ -237,6 +243,7 @@ const generateTableData = (data: any[]) => {
 | 
			
		||||
    formData.value.skus.push(row)
 | 
			
		||||
  })
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/** 构建所有排列组合 */
 | 
			
		||||
const build = (list: any[]) => {
 | 
			
		||||
  if (list.length === 0) {
 | 
			
		||||
@@ -259,6 +266,7 @@ const build = (list: any[]) => {
 | 
			
		||||
    return result
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/** 监听属性列表生成相关参数和表头 */
 | 
			
		||||
watch(
 | 
			
		||||
  () => props.attributeList,
 | 
			
		||||
@@ -8,6 +8,7 @@
 | 
			
		||||
      class="-mb-15px"
 | 
			
		||||
      label-width="68px"
 | 
			
		||||
    >
 | 
			
		||||
      <!-- TODO @puhui999:https://admin.java.crmeb.net/store/index,参考,使用分类 + 标题搜索 -->
 | 
			
		||||
      <el-form-item label="品牌名称" prop="name">
 | 
			
		||||
        <el-input
 | 
			
		||||
          v-model="queryParams.name"
 | 
			
		||||
@@ -51,6 +52,7 @@
 | 
			
		||||
          <Icon class="mr-5px" icon="ep:plus" />
 | 
			
		||||
          新增
 | 
			
		||||
        </el-button>
 | 
			
		||||
        <!-- TODO @puhui999:增加一个【导出】操作 -->
 | 
			
		||||
      </el-form-item>
 | 
			
		||||
    </el-form>
 | 
			
		||||
  </ContentWrap>
 | 
			
		||||
@@ -66,6 +68,7 @@
 | 
			
		||||
      />
 | 
			
		||||
    </el-tabs>
 | 
			
		||||
    <el-table v-loading="loading" :data="list">
 | 
			
		||||
      <!-- TODO puhui999: ID 编号的展示 -->
 | 
			
		||||
      <!--   TODO 暂时不做折叠数据   -->
 | 
			
		||||
      <!--      <el-table-column type="expand">-->
 | 
			
		||||
      <!--        <template #default="{ row }">-->
 | 
			
		||||
@@ -92,6 +95,7 @@
 | 
			
		||||
        </template>
 | 
			
		||||
      </el-table-column>
 | 
			
		||||
      <el-table-column :show-overflow-tooltip="true" label="商品名称" min-width="300" prop="name" />
 | 
			
		||||
      <!-- TODO 价格 / 100.0 -->
 | 
			
		||||
      <el-table-column align="center" label="商品售价" min-width="90" prop="price" />
 | 
			
		||||
      <el-table-column align="center" label="销量" min-width="90" prop="salesCount" />
 | 
			
		||||
      <el-table-column align="center" label="库存" min-width="90" prop="stock" />
 | 
			
		||||
@@ -105,6 +109,7 @@
 | 
			
		||||
      />
 | 
			
		||||
      <el-table-column fixed="right" label="状态" min-width="80">
 | 
			
		||||
        <template #default="{ row }">
 | 
			
		||||
          <!-- TODO @puhui:是不是不用 Number(row.status) 去比较哈,直接 row.status < 0 -->
 | 
			
		||||
          <el-switch
 | 
			
		||||
            v-model="row.status"
 | 
			
		||||
            :active-value="1"
 | 
			
		||||
@@ -119,6 +124,7 @@
 | 
			
		||||
      </el-table-column>
 | 
			
		||||
      <el-table-column align="center" fixed="right" label="操作" min-width="150">
 | 
			
		||||
        <template #default="{ row }">
 | 
			
		||||
          <!-- TODO @puhui999:【详情】,可以后面点做哈 -->
 | 
			
		||||
          <template v-if="queryParams.tabType === 4">
 | 
			
		||||
            <el-button
 | 
			
		||||
              v-hasPermi="['product:spu:delete']"
 | 
			
		||||
@@ -166,6 +172,7 @@
 | 
			
		||||
      @pagination="getList"
 | 
			
		||||
    />
 | 
			
		||||
  </ContentWrap>
 | 
			
		||||
  <!-- https://kailong110120130.gitee.io/vue-element-plus-admin-doc/components/image-viewer.html,可以用这个么? -->
 | 
			
		||||
  <!-- 必须在表格外面展示。不然单元格会遮挡图层 -->
 | 
			
		||||
  <el-image-viewer
 | 
			
		||||
    v-if="imgViewVisible"
 | 
			
		||||
@@ -173,20 +180,21 @@
 | 
			
		||||
    @close="imgViewVisible = false"
 | 
			
		||||
  />
 | 
			
		||||
</template>
 | 
			
		||||
<script lang="ts" name="ProductManagement" setup>
 | 
			
		||||
<script lang="ts" name="ProductList" setup>
 | 
			
		||||
import { DICT_TYPE, getIntDictOptions } from '@/utils/dict'
 | 
			
		||||
import { dateFormatter } from '@/utils/formatTime' // 业务api
 | 
			
		||||
import { dateFormatter } from '@/utils/formatTime'
 | 
			
		||||
// TODO @puhui999:managementApi=》ProductSpuApi
 | 
			
		||||
import * as managementApi from '@/api/mall/product/management/spu'
 | 
			
		||||
import { ProductSpuStatusEnum } from '@/utils/constants'
 | 
			
		||||
import { TabsPaneContext } from 'element-plus'
 | 
			
		||||
 | 
			
		||||
const message = useMessage() // 消息弹窗
 | 
			
		||||
const { t } = useI18n() // 国际化
 | 
			
		||||
const { currentRoute, push } = useRouter() // 路由跳转
 | 
			
		||||
 | 
			
		||||
const loading = ref(false) // 列表的加载中
 | 
			
		||||
const total = ref(0) // 列表的总页数
 | 
			
		||||
const list = ref<any[]>([]) // 列表的数据
 | 
			
		||||
// tabs数据
 | 
			
		||||
// tabs 数据
 | 
			
		||||
const tabsData = ref([
 | 
			
		||||
  {
 | 
			
		||||
    count: 0,
 | 
			
		||||
@@ -214,7 +222,10 @@ const tabsData = ref([
 | 
			
		||||
    type: 4
 | 
			
		||||
  }
 | 
			
		||||
])
 | 
			
		||||
 | 
			
		||||
/** 获得每个 Tab 的数量 */
 | 
			
		||||
const getTabsCount = async () => {
 | 
			
		||||
  // TODO @puhui999:这里是不是可以不要 try catch 哈
 | 
			
		||||
  try {
 | 
			
		||||
    const res = await managementApi.getTabsCount()
 | 
			
		||||
    for (let objName in res) {
 | 
			
		||||
@@ -222,6 +233,7 @@ const getTabsCount = async () => {
 | 
			
		||||
    }
 | 
			
		||||
  } catch {}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
const imgViewVisible = ref(false) // 商品图预览
 | 
			
		||||
const imageViewerList = ref<string[]>([]) // 商品图预览列表
 | 
			
		||||
const queryParams = ref({
 | 
			
		||||
@@ -230,10 +242,13 @@ const queryParams = ref({
 | 
			
		||||
  tabType: 0
 | 
			
		||||
})
 | 
			
		||||
const queryFormRef = ref() // 搜索的表单
 | 
			
		||||
 | 
			
		||||
// TODO @puhui999:可以改成 handleTabClick:更准确一点;
 | 
			
		||||
const handleClick = (tab: TabsPaneContext) => {
 | 
			
		||||
  queryParams.value.tabType = tab.paneName
 | 
			
		||||
  getList()
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/** 查询列表 */
 | 
			
		||||
const getList = async () => {
 | 
			
		||||
  loading.value = true
 | 
			
		||||
@@ -246,8 +261,10 @@ const getList = async () => {
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// TODO @puhui999:是不是 changeStatus 和 addToTrash 调用一个统一的方法,去更新状态。这样逻辑会更干净一些。
 | 
			
		||||
/**
 | 
			
		||||
 * 更改SPU状态
 | 
			
		||||
 * 更改 SPU 状态
 | 
			
		||||
 *
 | 
			
		||||
 * @param row
 | 
			
		||||
 * @param status 更改前的值
 | 
			
		||||
 */
 | 
			
		||||
@@ -271,7 +288,7 @@ const changeStatus = async (row, status?: number) => {
 | 
			
		||||
    )
 | 
			
		||||
    await managementApi.updateStatus({ id: row.id, status: row.status })
 | 
			
		||||
    message.success('更新状态成功')
 | 
			
		||||
    // 刷新tabs数据
 | 
			
		||||
    // 刷新 tabs 数据
 | 
			
		||||
    await getTabsCount()
 | 
			
		||||
    // 刷新列表
 | 
			
		||||
    await getList()
 | 
			
		||||
@@ -288,8 +305,10 @@ const changeStatus = async (row, status?: number) => {
 | 
			
		||||
        : ProductSpuStatusEnum.DISABLE.status
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * 加入回收站
 | 
			
		||||
 *
 | 
			
		||||
 * @param row
 | 
			
		||||
 * @param status
 | 
			
		||||
 */
 | 
			
		||||
@@ -299,6 +318,7 @@ const addToTrash = (row, status) => {
 | 
			
		||||
  row.status = status
 | 
			
		||||
  changeStatus(row, num)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/** 删除按钮操作 */
 | 
			
		||||
const handleDelete = async (id: number) => {
 | 
			
		||||
  try {
 | 
			
		||||
@@ -313,6 +333,7 @@ const handleDelete = async (id: number) => {
 | 
			
		||||
    await getList()
 | 
			
		||||
  } catch {}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * 商品图预览
 | 
			
		||||
 * @param imgUrl
 | 
			
		||||
@@ -321,6 +342,7 @@ const imagePreview = (imgUrl: string) => {
 | 
			
		||||
  imageViewerList.value = [imgUrl]
 | 
			
		||||
  imgViewVisible.value = true
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/** 搜索按钮操作 */
 | 
			
		||||
const handleQuery = () => {
 | 
			
		||||
  getList()
 | 
			
		||||
@@ -334,16 +356,20 @@ const resetQuery = () => {
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * 新增或修改
 | 
			
		||||
 * @param id
 | 
			
		||||
 *
 | 
			
		||||
 * @param id 商品 SPU 编号
 | 
			
		||||
 */
 | 
			
		||||
const openForm = (id?: number) => {
 | 
			
		||||
  // 修改
 | 
			
		||||
  if (typeof id === 'number') {
 | 
			
		||||
    push('/product/productManagementAdd?id=' + id)
 | 
			
		||||
    return
 | 
			
		||||
  }
 | 
			
		||||
  // 新增
 | 
			
		||||
  push('/product/productManagementAdd')
 | 
			
		||||
}
 | 
			
		||||
// 监听路由变化更新列表
 | 
			
		||||
 | 
			
		||||
// 监听路由变化更新列表 TODO @puhui999:这个是必须加的么?
 | 
			
		||||
watch(
 | 
			
		||||
  () => currentRoute.value,
 | 
			
		||||
  () => {
 | 
			
		||||
@@ -353,6 +379,7 @@ watch(
 | 
			
		||||
    immediate: true
 | 
			
		||||
  }
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
/** 初始化 **/
 | 
			
		||||
onMounted(() => {
 | 
			
		||||
  getTabsCount()
 | 
			
		||||
		Reference in New Issue
	
	Block a user