Prechádzať zdrojové kódy

fix: 解决商品上一版遗留的各种小bug关键部分已添加fix注释。完成的TODO也已添加fix标记

puhui999 1 rok pred
rodič
commit
4ddba9d454

+ 5 - 0
src/api/mall/product/spu.ts

@@ -82,3 +82,8 @@ export const getSpu = (id: number) => {
 export const deleteSpu = (id: number) => {
   return request.delete({ url: `/product/spu/delete?id=${id}` })
 }
+
+// 导出商品 Spu
+export const exportUser = (params) => {
+  return request.download({ url: '/product/spu/export', params })
+}

+ 44 - 14
src/components/UploadFile/src/UploadImgs.vue

@@ -1,19 +1,19 @@
 <template>
   <div class="upload-box">
     <el-upload
+      v-model:file-list="fileList"
+      :accept="fileType.join(',')"
       :action="updateUrl"
-      list-type="picture-card"
+      :before-upload="beforeUpload"
       :class="['upload', drag ? 'no-border' : '']"
-      v-model:file-list="fileList"
-      :multiple="true"
-      :limit="limit"
+      :drag="drag"
       :headers="uploadHeaders"
-      :before-upload="beforeUpload"
+      :limit="limit"
+      :multiple="true"
+      :on-error="uploadError"
       :on-exceed="handleExceed"
       :on-success="uploadSuccess"
-      :on-error="uploadError"
-      :drag="drag"
-      :accept="fileType.join(',')"
+      list-type="picture-card"
     >
       <div class="upload-empty">
         <slot name="empty">
@@ -40,15 +40,15 @@
     </div>
     <el-image-viewer
       v-if="imgViewVisible"
-      @close="imgViewVisible = false"
       :url-list="[viewImageUrl]"
+      @close="imgViewVisible = false"
     />
   </div>
 </template>
-<script setup lang="ts" name="UploadImgs">
+<script lang="ts" name="UploadImgs" setup>
 import { PropType } from 'vue'
+import type { UploadFile, UploadProps, UploadUserFile } from 'element-plus'
 import { ElNotification } from 'element-plus'
-import type { UploadProps, UploadFile, UploadUserFile } from 'element-plus'
 
 import { propTypes } from '@/utils/propTypes'
 import { getAccessToken, getTenantId } from '@/utils/auth'
@@ -88,8 +88,19 @@ const uploadHeaders = ref({
   'tenant-id': getTenantId()
 })
 
-const fileList = ref<UploadUserFile[]>(props.modelValue)
-
+const fileList = ref<UploadUserFile[]>()
+// fix: 改为动态监听赋值解决图片回显问题
+watch(
+  () => props.modelValue,
+  (data) => {
+    if (!data) return
+    fileList.value = data
+  },
+  {
+    deep: true,
+    immediate: true
+  }
+)
 /**
  * @description 文件上传之前判断
  * @param rawFile 上传的文件
@@ -116,9 +127,11 @@ const beforeUpload: UploadProps['beforeUpload'] = (rawFile) => {
 interface UploadEmits {
   (e: 'update:modelValue', value: UploadUserFile[]): void
 }
+
 const emit = defineEmits<UploadEmits>()
 const uploadSuccess = (response, uploadFile: UploadFile) => {
   if (!response) return
+  // TODO 多图上传组件成功后只是把保存成功后的url替换掉组件选图时的文件路径,所以返回的fileList包含的是一个包含文件信息的对象列表
   uploadFile.url = response.data
   emit('update:modelValue', fileList.value)
   message.success('上传成功')
@@ -159,35 +172,40 @@ const handlePictureCardPreview: UploadProps['onPreview'] = (uploadFile) => {
 }
 </script>
 
-<style scoped lang="scss">
+<style lang="scss" scoped>
 .is-error {
   .upload {
     :deep(.el-upload--picture-card),
     :deep(.el-upload-dragger) {
       border: 1px dashed var(--el-color-danger) !important;
+
       &:hover {
         border-color: var(--el-color-primary) !important;
       }
     }
   }
 }
+
 :deep(.disabled) {
   .el-upload--picture-card,
   .el-upload-dragger {
     cursor: not-allowed;
     background: var(--el-disabled-bg-color) !important;
     border: 1px dashed var(--el-border-color-darker);
+
     &:hover {
       border-color: var(--el-border-color-darker) !important;
     }
   }
 }
+
 .upload-box {
   .no-border {
     :deep(.el-upload--picture-card) {
       border: none !important;
     }
   }
+
   :deep(.upload) {
     .el-upload-dragger {
       display: flex;
@@ -199,14 +217,17 @@ const handlePictureCardPreview: UploadProps['onPreview'] = (uploadFile) => {
       overflow: hidden;
       border: 1px dashed var(--el-border-color-darker);
       border-radius: v-bind(borderRadius);
+
       &:hover {
         border: 1px dashed var(--el-color-primary);
       }
     }
+
     .el-upload-dragger.is-dragover {
       background-color: var(--el-color-primary-light-9);
       border: 2px dashed var(--el-color-primary) !important;
     }
+
     .el-upload-list__item,
     .el-upload--picture-card {
       width: v-bind(width);
@@ -214,11 +235,13 @@ const handlePictureCardPreview: UploadProps['onPreview'] = (uploadFile) => {
       background-color: transparent;
       border-radius: v-bind(borderRadius);
     }
+
     .upload-image {
       width: 100%;
       height: 100%;
       object-fit: contain;
     }
+
     .upload-handle {
       position: absolute;
       top: 0;
@@ -233,6 +256,7 @@ const handlePictureCardPreview: UploadProps['onPreview'] = (uploadFile) => {
       background: rgb(0 0 0 / 60%);
       opacity: 0;
       transition: var(--el-transition-duration-fast);
+
       .handle-icon {
         display: flex;
         flex-direction: column;
@@ -240,15 +264,18 @@ const handlePictureCardPreview: UploadProps['onPreview'] = (uploadFile) => {
         justify-content: center;
         padding: 0 6%;
         color: aliceblue;
+
         .el-icon {
           margin-bottom: 15%;
           font-size: 140%;
         }
+
         span {
           font-size: 100%;
         }
       }
     }
+
     .el-upload-list__item {
       &:hover {
         .upload-handle {
@@ -256,6 +283,7 @@ const handlePictureCardPreview: UploadProps['onPreview'] = (uploadFile) => {
         }
       }
     }
+
     .upload-empty {
       display: flex;
       flex-direction: column;
@@ -263,12 +291,14 @@ const handlePictureCardPreview: UploadProps['onPreview'] = (uploadFile) => {
       font-size: 12px;
       line-height: 30px;
       color: var(--el-color-info);
+
       .el-icon {
         font-size: 28px;
         color: var(--el-text-color-secondary);
       }
     }
   }
+
   .el-upload__tip {
     line-height: 15px;
     text-align: center;

+ 17 - 4
src/router/modules/remaining.ts

@@ -349,22 +349,35 @@ const remainingRouter: AppRouteRecordRaw[] = [
   {
     path: '/product',
     component: Layout,
-    name: 'ProductManagementEdit',
+    name: 'Product',
     meta: {
       hidden: true
     },
     children: [
       {
-        path: 'productManagementAdd', // TODO @puhui999:最好拆成 add 和 edit 两个路由;添加商品;修改商品
+        path: 'productSpuAdd', // TODO @puhui999:最好拆成 add 和 edit 两个路由;添加商品;修改商品 fix
         component: () => import('@/views/mall/product/spu/addForm.vue'),
-        name: 'ProductManagementAdd',
+        name: 'ProductSpuAdd',
         meta: {
           noCache: true,
           hidden: true,
           canTo: true,
           icon: 'ep:edit',
           title: '添加商品',
-          activeMenu: '/product/product-management'
+          activeMenu: '/product/product-spu'
+        }
+      },
+      {
+        path: 'productSpuEdit/:spuId(\\d+)',
+        component: () => import('@/views/mall/product/spu/addForm.vue'),
+        name: 'productSpuEdit',
+        meta: {
+          noCache: true,
+          hidden: true,
+          canTo: true,
+          icon: 'ep:edit',
+          title: '编辑商品',
+          activeMenu: '/product/product-spu'
         }
       }
     ]

+ 80 - 131
src/views/mall/product/spu/addForm.vue

@@ -3,21 +3,21 @@
     <el-tabs v-model="activeName">
       <el-tab-pane label="商品信息" name="basicInfo">
         <BasicInfoForm
-          ref="BasicInfoRef"
+          ref="basicInfoRef"
           v-model:activeName="activeName"
           :propFormData="formData"
         />
       </el-tab-pane>
       <el-tab-pane label="商品详情" name="description">
         <DescriptionForm
-          ref="DescriptionRef"
+          ref="descriptionRef"
           v-model:activeName="activeName"
           :propFormData="formData"
         />
       </el-tab-pane>
       <el-tab-pane label="其他设置" name="otherSettings">
         <OtherSettingsForm
-          ref="OtherSettingsRef"
+          ref="otherSettingsRef"
           v-model:activeName="activeName"
           :propFormData="formData"
         />
@@ -31,88 +31,55 @@
     </el-form>
   </ContentWrap>
 </template>
-<script lang="ts" name="ProductManagementForm" setup>
+<script lang="ts" name="ProductSpuForm" setup>
+import { cloneDeep } from 'lodash-es'
 import { useTagsViewStore } from '@/store/modules/tagsView'
 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'
+// 业务api
+import * as ProductSpuApi from '@/api/mall/product/spu'
 import * as PropertyApi from '@/api/mall/product/property'
+
 const { t } = useI18n() // 国际化
 const message = useMessage() // 消息弹窗
 const { push, currentRoute } = useRouter() // 路由
-const { query } = useRoute() // 查询参数
+const { params } = useRoute() // 查询参数
 const { delView } = useTagsViewStore() // 视图操作
 
 const formLoading = ref(false) // 表单的加载中:1)修改时的数据加载;2)提交的按钮禁用
 const activeName = ref('basicInfo') // Tag 激活的窗口
-const BasicInfoRef = ref<ComponentRef<typeof BasicInfoForm>>() // 商品信息Ref
-const DescriptionRef = ref<ComponentRef<typeof DescriptionForm>>() // 商品详情Ref
-const OtherSettingsRef = ref<ComponentRef<typeof OtherSettingsForm>>() // 其他设置Ref
-const formData = ref<SpuType>({
-  name: '213', // 商品名称
+const basicInfoRef = ref<ComponentRef<typeof BasicInfoForm>>() // 商品信息Ref
+const descriptionRef = ref<ComponentRef<typeof DescriptionForm>>() // 商品详情Ref
+const otherSettingsRef = ref<ComponentRef<typeof OtherSettingsForm>>() // 其他设置Ref
+// spu 表单数据
+const formData = ref<ProductSpuApi.SpuType>({
+  name: '', // 商品名称
   categoryId: null, // 商品分类
-  keyword: '213', // 关键字
+  keyword: '', // 关键字
   unit: null, // 单位
-  picUrl:
-    'http://127.0.0.1:48080/admin-api/infra/file/4/get/6ffdf8f5dfc03f7ceec1ff1ebc138adb8b721a057d505ccfb0e61a8783af1371.png', // 商品封面图
-  sliderPicUrls: [
-    {
-      name: 'http://127.0.0.1:48080/admin-api/infra/file/4/get/6ffdf8f5dfc03f7ceec1ff1ebc138adb8b721a057d505ccfb0e61a8783af1371.png',
-      url: 'http://127.0.0.1:48080/admin-api/infra/file/4/get/6ffdf8f5dfc03f7ceec1ff1ebc138adb8b721a057d505ccfb0e61a8783af1371.png'
-    }
-  ], // 商品轮播图
-  introduction: '213', // 商品简介
-  deliveryTemplateId: 0, // 运费模版
+  picUrl: '', // 商品封面图
+  sliderPicUrls: [], // 商品轮播图
+  introduction: '', // 商品简介
+  deliveryTemplateId: 1, // 运费模版
   specType: false, // 商品规格
   subCommissionType: false, // 分销类型
   skus: [
     {
-      /**
-       * 商品价格,单位:分 TODO @puhui999:注释放在尾巴哈,简洁一点~
-       */
-      price: 0,
-      /**
-       * 市场价,单位:分
-       */
-      marketPrice: 0,
-      /**
-       * 成本价,单位:分
-       */
-      costPrice: 0,
-      /**
-       * 商品条码
-       */
-      barCode: '',
-      /**
-       * 图片地址
-       */
-      picUrl: '',
-      /**
-       * 库存
-       */
-      stock: 0,
-      /**
-       * 商品重量,单位:kg 千克
-       */
-      weight: 0,
-      /**
-       * 商品体积,单位:m^3 平米
-       */
-      volume: 0,
-      /**
-       * 一级分销的佣金,单位:分
-       */
-      subCommissionFirstPrice: 0,
-      /**
-       * 二级分销的佣金,单位:分
-       */
-      subCommissionSecondPrice: 0
+      price: 0, // 商品价格
+      marketPrice: 0, // 市场价
+      costPrice: 0, // 成本价
+      barCode: '', // 商品条码
+      picUrl: '', // 图片地址
+      stock: 0, // 库存
+      weight: 0, // 商品重量
+      volume: 0, // 商品体积
+      subCommissionFirstPrice: 0, // 一级分销的佣金
+      subCommissionSecondPrice: 0 // 二级分销的佣金
     }
   ],
-  description: '5425', // 商品详情
-  sort: 1, // 商品排序
-  giveIntegral: 1, // 赠送积分
-  virtualSalesCount: 1, // 虚拟销量
+  description: '', // 商品详情
+  sort: 0, // 商品排序
+  giveIntegral: 0, // 赠送积分
+  virtualSalesCount: 0, // 虚拟销量
   recommendHot: false, // 是否热卖
   recommendBenefit: false, // 是否优惠
   recommendBest: false, // 是否精品
@@ -122,11 +89,11 @@ const formData = ref<SpuType>({
 
 /** 获得详情 */
 const getDetail = async () => {
-  const id = query.id as unknown as number
+  const id = params.spuId as number
   if (id) {
     formLoading.value = true
     try {
-      const res = (await managementApi.getSpu(id)) as SpuType
+      const res = (await ProductSpuApi.getSpu(id)) as ProductSpuApi.SpuType
       formData.value = res
       // 直接取第一个值就能得到所有属性的id
       // TODO @puhui999:可以直接拿 propertyName 拼接处规格 id + 属性,可以看下商品 uniapp 详情的做法
@@ -134,7 +101,7 @@ const getDetail = async () => {
       const PropertyS = await PropertyApi.getPropertyListAndValue({ propertyIds })
       await nextTick()
       // 回显商品属性
-      BasicInfoRef.value.addAttribute(PropertyS)
+      basicInfoRef.value.addAttribute(PropertyS)
     } finally {
       formLoading.value = false
     }
@@ -145,54 +112,37 @@ const getDetail = async () => {
 const submitForm = async () => {
   // 提交请求
   formLoading.value = true
-  const newSkus = JSON.parse(JSON.stringify(formData.value.skus)) //深拷贝一份skus保存失败时使用
-  // TODO 三个表单逐一校验,如果有一个表单校验不通过则切换到对应表单,如果有两个及以上的情况则切换到最前面的一个并弹出提示消息
+  // 三个表单逐一校验,如果有一个表单校验不通过则切换到对应表单,如果有两个及以上的情况则切换到最前面的一个并弹出提示消息
   // 校验各表单
   try {
-    await unref(BasicInfoRef)?.validate()
-    await unref(DescriptionRef)?.validate()
-    await unref(OtherSettingsRef)?.validate()
-    // TODO @puhui:直接做深拷贝?这样最终 server 端不满足,不需要恢复
+    await unref(basicInfoRef)?.validate()
+    await unref(descriptionRef)?.validate()
+    await unref(otherSettingsRef)?.validate()
+    const deepCopyFormData = cloneDeep(unref(formData.value)) // 深拷贝一份 fix:这样最终 server 端不满足,不需要恢复
     // 处理掉一些无关数据
-    formData.value.skus.forEach((item) => {
+    deepCopyFormData.skus.forEach((item) => {
       // 给sku name赋值
-      item.name = formData.value.name
-      // 多规格情况移除skus相关属性值value
-      if (formData.value.specType) {
-        item.properties.forEach((item2) => {
-          delete item2.valueName
-        })
-      }
+      item.name = deepCopyFormData.name
     })
     // 处理轮播图列表
     const newSliderPicUrls = []
-    formData.value.sliderPicUrls.forEach((item) => {
+    deepCopyFormData.sliderPicUrls.forEach((item) => {
       // 如果是前端选的图
-      // TODO @puhui999:疑问哈,为啥会是 object 呀?
-      if (typeof item === 'object') {
-        newSliderPicUrls.push(item.url)
-      } else {
-        newSliderPicUrls.push(item)
-      }
+      // TODO @puhui999:疑问哈,为啥会是 object 呀?fix
+      typeof item === 'object' ? newSliderPicUrls.push(item.url) : newSliderPicUrls.push(item)
     })
-    formData.value.sliderPicUrls = newSliderPicUrls
+    deepCopyFormData.sliderPicUrls = newSliderPicUrls
     // 校验都通过后提交表单
-    const data = formData.value as SpuType
-    // 移除skus.
-    const id = query.id as unknown as number
+    const data = deepCopyFormData as ProductSpuApi.SpuType
+    const id = params.spuId as number
     if (!id) {
-      await managementApi.createSpu(data)
+      await ProductSpuApi.createSpu(data)
       message.success(t('common.createSuccess'))
     } else {
-      await managementApi.updateSpu(data)
+      await ProductSpuApi.updateSpu(data)
       message.success(t('common.updateSuccess'))
     }
     close()
-  } catch (e) {
-    // 如果是后端校验失败,恢复skus数据
-    if (typeof e === 'string') {
-      formData.value.skus = newSkus
-    }
   } finally {
     formLoading.value = false
   }
@@ -200,41 +150,40 @@ 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 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 = () => {
-  // TODO @puhui999:是不是不用 reset 呀?close 默认销毁
-  resetForm()
   delView(unref(currentRoute))
-  push('/product/product-management')
+  push('/product/product-spu')
 }
 
 /** 初始化 */
-onMounted(() => {
-  getDetail()
+onMounted(async () => {
+  await getDetail()
 })
 </script>

+ 29 - 27
src/views/mall/product/spu/components/BasicInfoForm.vue

@@ -1,5 +1,5 @@
 <template>
-  <el-form ref="ProductManagementBasicInfoRef" :model="formData" :rules="rules" label-width="120px">
+  <el-form ref="productSpuBasicInfoRef" :model="formData" :rules="rules" label-width="120px">
     <el-row>
       <el-col :span="12">
         <el-form-item label="商品名称" prop="name">
@@ -54,7 +54,7 @@
       </el-col>
       <el-col :span="24">
         <el-form-item label="商品轮播图" prop="sliderPicUrls">
-          <UploadImgs v-model="formData.sliderPicUrls" />
+          <UploadImgs v-model:modelValue="formData.sliderPicUrls" />
         </el-form-item>
       </el-col>
       <el-col :span="12">
@@ -86,36 +86,36 @@
       <!-- 多规格添加-->
       <el-col :span="24">
         <el-form-item v-if="formData.specType" label="商品属性">
-          <!-- 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" />
+          <!-- TODO @puhui999:参考 https://admin.java.crmeb.net/store/list/creatProduct 添加规格好做么?添加的时候,不用输入备注哈 fix-->
+          <el-button class="mr-15px mb-10px" @click="attributesAddFormRef.open">添加规格</el-button>
+          <ProductAttributes :propertyList="propertyList" />
         </el-form-item>
-        <template v-if="formData.specType && attributeList.length > 0">
+        <template v-if="formData.specType && propertyList.length > 0">
           <el-form-item label="批量设置">
-            <SkuList :attributeList="attributeList" :is-batch="true" :prop-form-data="formData" />
+            <SkuList :is-batch="true" :prop-form-data="formData" :propertyList="propertyList" />
           </el-form-item>
           <el-form-item label="属性列表">
-            <SkuList :attributeList="attributeList" :prop-form-data="formData" />
+            <SkuList :prop-form-data="formData" :propertyList="propertyList" />
           </el-form-item>
         </template>
         <el-form-item v-if="!formData.specType">
-          <SkuList :attributeList="attributeList" :prop-form-data="formData" />
+          <SkuList :prop-form-data="formData" :propertyList="propertyList" />
         </el-form-item>
       </el-col>
     </el-row>
   </el-form>
-  <ProductAttributesAddForm ref="AttributesAddFormRef" @success="addAttribute" />
+  <ProductAttributesAddForm ref="attributesAddFormRef" @success="addAttribute" />
 </template>
-<script lang="ts" name="ProductManagementBasicInfoForm" setup>
+<script lang="ts" name="ProductSpuBasicInfoForm" setup>
 import { PropType } from 'vue'
 import { defaultProps, handleTree } from '@/utils/tree'
 import { DICT_TYPE, getIntDictOptions } from '@/utils/dict'
-import type { SpuType } from '@/api/mall/product/management/type/spuType'
+import type { SpuType } from '@/api/mall/product/spu'
 import { UploadImg, UploadImgs } from '@/components/UploadFile'
-import { copyValueToTarget } from '@/utils'
 import { ProductAttributes, ProductAttributesAddForm, SkuList } from './index'
 import * as ProductCategoryApi from '@/api/mall/product/category'
 import { propTypes } from '@/utils/propTypes'
+import { copyValueToTarget } from '@/utils'
 
 const message = useMessage() // 消息弹窗
 
@@ -126,17 +126,14 @@ const props = defineProps({
   },
   activeName: propTypes.string.def('')
 })
-const AttributesAddFormRef = ref() // 添加商品属性表单 TODO @puhui999:小写开头哈
-const ProductManagementBasicInfoRef = ref() // 表单Ref TODO @puhui999:小写开头哈
-// TODO @puhui999:attributeList 改成 propertyList,会更统一一点
-const attributeList = ref([]) // 商品属性列表
-/** 添加商品属性 */ // TODO @puhui999:propFormData 算出来
+const attributesAddFormRef = ref() // 添加商品属性表单 TODO @puhui999:小写开头哈 fix
+const productSpuBasicInfoRef = ref() // 表单Ref TODO @puhui999:小写开头哈  fix
+// TODO @puhui999:attributeList 改成 propertyList,会更统一一点 fix
+const propertyList = ref([]) // 商品属性列表
+/** 添加商品属性 */
+// TODO @puhui999:propFormData 算出来 fix: 因为ProductAttributesAddForm添加属性成功回调得使用不能完全依赖于propFormData
 const addAttribute = (property: any) => {
-  if (Array.isArray(property)) {
-    attributeList.value = property
-    return
-  }
-  attributeList.value.push(property)
+  Array.isArray(property) ? (propertyList.value = property) : propertyList.value.push(property)
 }
 const formData = reactive<SpuType>({
   name: '', // 商品名称
@@ -171,10 +168,15 @@ watch(
   () => props.propFormData,
   (data) => {
     if (!data) return
+    // fix:三个表单组件监听赋值必须使用 copyValueToTarget 使用 formData.value = data 会监听非常多次
     copyValueToTarget(formData, data)
+    // fix: 多图上传组件需要一个包含url属性的对象才能正常回显
+    formData.sliderPicUrls = data['sliderPicUrls'].map((item) => ({
+      url: item
+    }))
   },
   {
-    deep: true,
+    // fix: 去掉深度监听只有对象引用发生改变的时候才执行,解决改一动多的问题
     immediate: true
   }
 )
@@ -185,8 +187,8 @@ watch(
 const emit = defineEmits(['update:activeName'])
 const validate = async () => {
   // 校验表单
-  if (!ProductManagementBasicInfoRef) return
-  return await unref(ProductManagementBasicInfoRef).validate((valid) => {
+  if (!productSpuBasicInfoRef) return
+  return await unref(productSpuBasicInfoRef).validate((valid) => {
     if (!valid) {
       message.warning('商品信息未完善!!')
       emit('update:activeName', 'basicInfo')
@@ -212,7 +214,7 @@ const changeSubCommissionType = () => {
 /** 选择规格 */
 const onChangeSpec = () => {
   // 重置商品属性列表
-  attributeList.value = []
+  propertyList.value = []
   // 重置sku列表
   formData.skus = [
     {

+ 8 - 9
src/views/mall/product/spu/components/DescriptionForm.vue

@@ -1,5 +1,5 @@
 <template>
-  <el-form ref="DescriptionFormRef" :model="formData" :rules="rules" label-width="120px">
+  <el-form ref="descriptionFormRef" :model="formData" :rules="rules" label-width="120px">
     <!--富文本编辑器组件-->
     <el-form-item label="商品详情" prop="description">
       <Editor v-model:modelValue="formData.description" />
@@ -7,11 +7,11 @@
   </el-form>
 </template>
 <script lang="ts" name="DescriptionForm" setup>
-import type { SpuType } from '@/api/mall/product/management/type/spuType'
+import type { SpuType } from '@/api/mall/product/spu'
 import { Editor } from '@/components/Editor'
 import { PropType } from 'vue'
-import { copyValueToTarget } from '@/utils'
 import { propTypes } from '@/utils/propTypes'
+import { copyValueToTarget } from '@/utils'
 
 const message = useMessage() // 消息弹窗
 const props = defineProps({
@@ -21,7 +21,7 @@ const props = defineProps({
   },
   activeName: propTypes.string.def('')
 })
-const DescriptionFormRef = ref() // 表单Ref
+const descriptionFormRef = ref() // 表单Ref
 const formData = ref<SpuType>({
   description: '' // 商品详情
 })
@@ -29,7 +29,6 @@ const formData = ref<SpuType>({
 const rules = reactive({
   description: [required]
 })
-
 /**
  * 富文本编辑器如果输入过再清空会有残留,需再重置一次
  */
@@ -45,7 +44,6 @@ watch(
     immediate: true
   }
 )
-
 /**
  * 将传进来的值赋值给formData
  */
@@ -53,10 +51,11 @@ watch(
   () => props.propFormData,
   (data) => {
     if (!data) return
+    // fix:三个表单组件监听赋值必须使用 copyValueToTarget 使用 formData.value = data 会监听非常多次
     copyValueToTarget(formData.value, data)
   },
   {
-    deep: true,
+    // fix: 去掉深度监听只有对象引用发生改变的时候才执行,解决改一动多的问题
     immediate: true
   }
 )
@@ -67,8 +66,8 @@ watch(
 const emit = defineEmits(['update:activeName'])
 const validate = async () => {
   // 校验表单
-  if (!DescriptionFormRef) return
-  return unref(DescriptionFormRef).validate((valid) => {
+  if (!descriptionFormRef) return
+  return await unref(descriptionFormRef).validate((valid) => {
     if (!valid) {
       message.warning('商品详情为完善!!')
       emit('update:activeName', 'description')

+ 14 - 18
src/views/mall/product/spu/components/OtherSettingsForm.vue

@@ -1,5 +1,5 @@
 <template>
-  <el-form ref="OtherSettingsFormRef" :model="formData" :rules="rules" label-width="120px">
+  <el-form ref="otherSettingsFormRef" :model="formData" :rules="rules" label-width="120px">
     <el-row>
       <!-- TODO @puhui999:横着三个哈  fix-->
       <el-col :span="24">
@@ -55,8 +55,8 @@
 <script lang="ts" name="OtherSettingsForm" setup>
 import type { SpuType } from '@/api/mall/product/spu'
 import { PropType } from 'vue'
-import { copyValueToTarget } from '@/utils'
 import { propTypes } from '@/utils/propTypes'
+import { copyValueToTarget } from '@/utils'
 
 const message = useMessage() // 消息弹窗
 
@@ -68,7 +68,7 @@ const props = defineProps({
   activeName: propTypes.string.def('')
 })
 
-const OtherSettingsFormRef = ref() // 表单Ref
+const otherSettingsFormRef = ref() // 表单Ref
 // 表单数据
 const formData = ref<SpuType>({
   sort: 1, // 商品排序
@@ -100,7 +100,7 @@ const checkboxGroup = ref<string[]>([]) // 选中的推荐选项
 const onChangeGroup = () => {
   // TODO @puhui999:是不是可以遍历 recommend,然后进行是否选中;fix
   recommendOptions.forEach(({ value }) => {
-    formData.value[value] = checkboxGroup.value.includes(value) ? true : false
+    formData.value[value] = checkboxGroup.value.includes(value)
   })
 }
 
@@ -111,22 +111,28 @@ watch(
   () => props.propFormData,
   (data) => {
     if (!data) return
+    // fix:三个表单组件监听赋值必须使用 copyValueToTarget 使用 formData.value = data 会监听非常多次
     copyValueToTarget(formData.value, data)
+    recommendOptions.forEach(({ value }) => {
+      // TODO 如果先修改其他设置的值,再改变商品详情或是商品信息会重置其他设置页面中的相关值 fix:已修复
+      if (formData.value[value] && !checkboxGroup.value.includes(value)) {
+        checkboxGroup.value.push(value)
+      }
+    })
   },
   {
-    deep: true,
+    // fix: 去掉深度监听只有对象引用发生改变的时候才执行,解决改一动多的问题
     immediate: true
   }
 )
-
 /**
  * 表单校验
  */
 const emit = defineEmits(['update:activeName'])
 const validate = async () => {
   // 校验表单
-  if (!OtherSettingsFormRef) return
-  return await unref(OtherSettingsFormRef).validate((valid) => {
+  if (!otherSettingsFormRef) return
+  return await unref(otherSettingsFormRef).validate((valid) => {
     if (!valid) {
       message.warning('商品其他设置未完善!!')
       emit('update:activeName', 'otherSettings')
@@ -139,14 +145,4 @@ const validate = async () => {
   })
 }
 defineExpose({ validate })
-onMounted(async () => {
-  await nextTick()
-  // TODO 如果先修改其他设置的值,再改变商品详情或是商品信息会重置其他设置页面中的相关值 fix:已修复,改为组件初始化时赋值
-  checkboxGroup.value = []
-  recommendOptions.forEach(({ value }) => {
-    if (formData.value[value]) {
-      checkboxGroup.value.push(value)
-    }
-  })
-})
 </script>

+ 3 - 2
src/views/mall/product/spu/components/ProductAttributes.vue

@@ -54,14 +54,14 @@ const inputVisible = computed(() => (index) => {
 const InputRef = ref() //标签输入框Ref
 const attributeList = ref([]) // 商品属性列表
 const props = defineProps({
-  attributeData: {
+  propertyList: {
     type: Array,
     default: () => {}
   }
 })
 
 watch(
-  () => props.attributeData,
+  () => props.propertyList,
   (data) => {
     if (!data) return
     attributeList.value = data
@@ -80,6 +80,7 @@ const handleClose = (index, valueIndex) => {
 /** 显示输入框并获取焦点 */
 const showInput = async (index) => {
   attributeIndex.value = index
+  // TODO 嗯!!!自动获取焦点还是有点问题,后续继续改进
   // 因为组件在ref中所以需要用索引获取对应的Ref
   InputRef.value[index]!.input!.focus()
 }

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

@@ -25,13 +25,13 @@
         </template>
       </el-table-column>
     </template>
-    <!-- TODO @puhui999: controls-position=" " 可以去掉哈,不然太长了,手动输入更方便 -->
+    <!-- TODO @puhui999: controls-position=" " 可以去掉哈,不然太长了,手动输入更方便 fix -->
     <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:用户输入的时候,是按照元;分主要是我们自己用; -->
+    <!-- TODO @puhui999:用户输入的时候,是按照元;分主要是我们自己用;fix -->
     <el-table-column align="center" label="销售价(元)" min-width="168">
       <template #default="{ row }">
         <el-input-number v-model="row.price" :min="0" class="w-100%" />
@@ -96,7 +96,7 @@ const props = defineProps({
     type: Object as PropType<SpuType>,
     default: () => {}
   },
-  attributeList: {
+  propertyList: {
     type: Array,
     default: () => []
   },
@@ -142,7 +142,7 @@ watch(
   }
 )
 
-// TODO @芋艿:看看 chatgpt 可以进一步下面几个方法的实现不
+// TODO @芋艿:看看 chatgpt 可以进一步下面几个方法的实现不 fix
 /** 生成表数据 */
 const generateTableData = (data: any[]) => {
   // 构建数据结构 fix: 使用map替换多重for循环
@@ -207,8 +207,8 @@ const build = (propertyValuesList: Property[][]) => {
 
 /** 监听属性列表生成相关参数和表头 */
 watch(
-  () => props.attributeList,
-  (attributeList) => {
+  () => props.propertyList,
+  (propertyList) => {
     // 如果不是多规格则结束
     if (!formData.value.specType) return
     // 如果当前组件作为批量添加数据使用则重置表数据
@@ -229,15 +229,15 @@ watch(
       ]
     }
     // 判断代理对象是否为空
-    if (JSON.stringify(attributeList) === '[]') return
+    if (JSON.stringify(propertyList) === '[]') return
     // 重置表头
     tableHeaders.value = []
     // 生成表头
-    attributeList.forEach((item, index) => {
+    propertyList.forEach((item, index) => {
       // name加属性项index区分属性值
       tableHeaders.value.push({ prop: `name${index}`, label: item.name })
     })
-    generateTableData(attributeList)
+    generateTableData(propertyList)
   },
   {
     deep: true,

+ 6 - 3
src/views/mall/product/spu/index.vue

@@ -8,7 +8,7 @@
       class="-mb-15px"
       label-width="68px"
     >
-      <!-- TODO @puhui999:https://admin.java.crmeb.net/store/index,参考,使用分类 + 标题搜索 -->
+      <!-- TODO @puhui999:https://admin.java.crmeb.net/store/index,参考,使用分类 + 标题搜索 fix-->
       <el-form-item label="品牌名称" prop="name">
         <el-input
           v-model="queryParams.name"
@@ -351,11 +351,11 @@ const resetQuery = () => {
 const openForm = (id?: number) => {
   // 修改
   if (typeof id === 'number') {
-    push('/product/productManagementAdd?id=' + id)
+    push('/product/productSpuEdit/' + id)
     return
   }
   // 新增
-  push('/product/productManagementAdd')
+  push('/product/productSpuAdd')
 }
 
 // 监听路由变化更新列表 TODO @puhui999:这个是必须加的么?fix: 因为编辑表单是以路由的方式打开,保存表单后列表不会刷新
@@ -377,8 +377,11 @@ onMounted(async () => {
 </script>
 <style lang="scss" scoped>
 .demo-table-expand {
+  padding-left: 42px;
+
   :deep(.el-form-item__label) {
     width: 82px;
+    font-weight: bold;
     color: #99a9bf;
   }
 }