mirror of
				https://gitee.com/hhyykk/ipms-sjy-ui.git
				synced 2025-10-31 10:18:43 +08:00 
			
		
		
		
	fix: 解决商品上一版遗留的各种小bug关键部分已添加fix注释。完成的TODO也已添加fix标记
This commit is contained in:
		| @@ -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 }) | ||||
| } | ||||
|   | ||||
| @@ -1,19 +1,19 @@ | ||||
| <template> | ||||
|   <div class="upload-box"> | ||||
|     <el-upload | ||||
|       :action="updateUrl" | ||||
|       list-type="picture-card" | ||||
|       :class="['upload', drag ? 'no-border' : '']" | ||||
|       v-model:file-list="fileList" | ||||
|       :multiple="true" | ||||
|       :limit="limit" | ||||
|       :headers="uploadHeaders" | ||||
|       :accept="fileType.join(',')" | ||||
|       :action="updateUrl" | ||||
|       :before-upload="beforeUpload" | ||||
|       :class="['upload', drag ? 'no-border' : '']" | ||||
|       :drag="drag" | ||||
|       :headers="uploadHeaders" | ||||
|       :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; | ||||
|   | ||||
| @@ -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' | ||||
|         } | ||||
|       } | ||||
|     ] | ||||
|   | ||||
| @@ -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> | ||||
|   | ||||
| @@ -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 = [ | ||||
|     { | ||||
|   | ||||
| @@ -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') | ||||
|   | ||||
| @@ -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> | ||||
|   | ||||
| @@ -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() | ||||
| } | ||||
|   | ||||
| @@ -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, | ||||
|   | ||||
| @@ -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; | ||||
|   } | ||||
| } | ||||
|   | ||||
		Reference in New Issue
	
	Block a user
	 puhui999
					puhui999