Просмотр исходного кода

📖 MALL:商品编辑 => 优化 SKU 表单

YunaiV 1 год назад
Родитель
Сommit
20b4a7fd66

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

@@ -70,11 +70,6 @@ export const getPropertyList = (params: any) => {
   return request.get({ url: '/product/property/list', params })
 }
 
-// 获得属性项列表
-export const getPropertyListAndValue = (data: any) => {
-  return request.post({ url: '/product/property/get-value-list', data })
-}
-
 // ------------------------ 属性值 -------------------
 
 // 获得属性值分页

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

@@ -34,12 +34,19 @@
         <el-input v-model="row.barCode" class="w-100%" />
       </template>
     </el-table-column>
-    <el-table-column align="center" label="销售价(元)" min-width="168">
+    <el-table-column align="center" label="销售价" min-width="168">
       <template #default="{ row }">
-        <el-input-number v-model="row.price" :min="0" :precision="2" :step="0.1" class="w-100%" />
+        <el-input-number
+          v-model="row.price"
+          :min="0"
+          :precision="2"
+          :step="0.1"
+          class="w-100%"
+          controls-position="right"
+        />
       </template>
     </el-table-column>
-    <el-table-column align="center" label="市场价(元)" min-width="168">
+    <el-table-column align="center" label="市场价" min-width="168">
       <template #default="{ row }">
         <el-input-number
           v-model="row.marketPrice"
@@ -47,10 +54,11 @@
           :precision="2"
           :step="0.1"
           class="w-100%"
+          controls-position="right"
         />
       </template>
     </el-table-column>
-    <el-table-column align="center" label="成本价(元)" min-width="168">
+    <el-table-column align="center" label="成本价" min-width="168">
       <template #default="{ row }">
         <el-input-number
           v-model="row.costPrice"
@@ -58,22 +66,37 @@
           :precision="2"
           :step="0.1"
           class="w-100%"
+          controls-position="right"
         />
       </template>
     </el-table-column>
     <el-table-column align="center" label="库存" min-width="168">
       <template #default="{ row }">
-        <el-input-number v-model="row.stock" :min="0" class="w-100%" />
+        <el-input-number v-model="row.stock" :min="0" class="w-100%" controls-position="right" />
       </template>
     </el-table-column>
     <el-table-column align="center" label="重量(kg)" min-width="168">
       <template #default="{ row }">
-        <el-input-number v-model="row.weight" :min="0" :precision="2" :step="0.1" class="w-100%" />
+        <el-input-number
+          v-model="row.weight"
+          :min="0"
+          :precision="2"
+          :step="0.1"
+          class="w-100%"
+          controls-position="right"
+        />
       </template>
     </el-table-column>
     <el-table-column align="center" label="体积(m^3)" min-width="168">
       <template #default="{ row }">
-        <el-input-number v-model="row.volume" :min="0" :precision="2" :step="0.1" class="w-100%" />
+        <el-input-number
+          v-model="row.volume"
+          :min="0"
+          :precision="2"
+          :step="0.1"
+          class="w-100%"
+          controls-position="right"
+        />
       </template>
     </el-table-column>
     <template v-if="formData!.subCommissionType">
@@ -85,6 +108,7 @@
             :precision="2"
             :step="0.1"
             class="w-100%"
+            controls-position="right"
           />
         </template>
       </el-table-column>
@@ -96,6 +120,7 @@
             :precision="2"
             :step="0.1"
             class="w-100%"
+            controls-position="right"
           />
         </template>
       </el-table-column>
@@ -124,7 +149,12 @@
     <el-table-column v-if="isComponent" type="selection" width="45" />
     <el-table-column align="center" label="图片" min-width="80">
       <template #default="{ row }">
-        <el-image :src="row.picUrl" class="h-60px w-60px" @click="imagePreview(row.picUrl)" />
+        <el-image
+          v-if="row.picUrl"
+          :src="row.picUrl"
+          class="h-60px w-60px"
+          @click="imagePreview(row.picUrl)"
+        />
       </template>
     </el-table-column>
     <template v-if="formData!.specType && !isBatch">

+ 1 - 3
src/views/mall/product/spu/form/InfoForm.vue

@@ -100,9 +100,7 @@ const rules = reactive({
   brandId: [required]
 })
 
-/**
- * 将传进来的值赋值给 formData
- */
+/** 将传进来的值赋值给 formData */
 watch(
   () => props.propFormData,
   (data) => {

+ 18 - 12
src/views/mall/product/spu/form/ProductAttributes.vue

@@ -1,9 +1,10 @@
+<!-- 商品发布 - 库存价格 - 属性列表 -->
 <template>
   <el-col v-for="(item, index) in attributeList" :key="index">
     <div>
       <el-text class="mx-1">属性名:</el-text>
-      <el-tag class="mx-1" closable type="success" @close="handleCloseProperty(index)"
-        >{{ item.name }}
+      <el-tag class="mx-1" :closable="!isDetail" type="success" @close="handleCloseProperty(index)">
+        {{ item.name }}
       </el-tag>
     </div>
     <div>
@@ -12,7 +13,7 @@
         v-for="(value, valueIndex) in item.values"
         :key="value.id"
         class="mx-1"
-        closable
+        :closable="!isDetail"
         @close="handleCloseValue(index, valueIndex)"
       >
         {{ value.name }}
@@ -43,6 +44,9 @@
 <script lang="ts" setup>
 import { ElInput } from 'element-plus'
 import * as PropertyApi from '@/api/mall/product/property'
+import { PropertyVO } from '@/api/mall/product/property'
+import { PropertyAndValues } from '@/views/mall/product/spu/components'
+import { propTypes } from '@/utils/propTypes'
 
 defineOptions({ name: 'ProductAttributes' })
 
@@ -51,7 +55,7 @@ const message = useMessage() // 消息弹窗
 const inputValue = ref('') // 输入框值
 const attributeIndex = ref<number | null>(null) // 获取焦点时记录当前属性项的index
 // 输入框显隐控制
-const inputVisible = computed(() => (index) => {
+const inputVisible = computed(() => (index: number) => {
   if (attributeIndex.value === null) return false
   if (attributeIndex.value === index) return true
 })
@@ -59,17 +63,18 @@ const inputRef = ref([]) //标签输入框Ref
 /** 解决 ref 在 v-for 中的获取问题*/
 const setInputRef = (el) => {
   if (el === null || typeof el === 'undefined') return
-  // 如果不存在id相同的元素才添加
+  // 如果不存在 id 相同的元素才添加
   if (!inputRef.value.some((item) => item.input?.attributes.id === el.input?.attributes.id)) {
     inputRef.value.push(el)
   }
 }
-const attributeList = ref([]) // 商品属性列表
+const attributeList = ref<PropertyAndValues[]>([]) // 商品属性列表
 const props = defineProps({
   propertyList: {
     type: Array,
     default: () => {}
-  }
+  },
+  isDetail: propTypes.bool.def(false) // 是否作为详情组件
 })
 
 watch(
@@ -85,22 +90,23 @@ watch(
 )
 
 /** 删除属性值*/
-const handleCloseValue = (index, valueIndex) => {
+const handleCloseValue = (index: number, valueIndex: number) => {
   attributeList.value[index].values?.splice(valueIndex, 1)
 }
+
 /** 删除属性*/
-const handleCloseProperty = (index) => {
+const handleCloseProperty = (index: number) => {
   attributeList.value?.splice(index, 1)
 }
+
 /** 显示输入框并获取焦点 */
 const showInput = async (index) => {
   attributeIndex.value = index
   inputRef.value[index].focus()
 }
 
-const emit = defineEmits(['success']) // 定义 success 事件,用于操作成功后的回调
-
 /** 输入框失去焦点或点击回车时触发 */
+const emit = defineEmits(['success']) // 定义 success 事件,用于操作成功后的回调
 const handleInputConfirm = async (index, propertyId) => {
   if (inputValue.value) {
     // 保存属性值
@@ -110,7 +116,7 @@ const handleInputConfirm = async (index, propertyId) => {
       message.success(t('common.createSuccess'))
       emit('success', attributeList.value)
     } catch {
-      message.error('添加失败,请重试') // TODO 缺少国际化
+      message.error('添加失败,请重试')
     }
   }
   attributeIndex.value = null

+ 12 - 17
src/views/mall/product/spu/form/ProductPropertyAddForm.vue

@@ -1,5 +1,6 @@
+<!-- 商品发布 - 库存价格 - 添加属性 -->
 <template>
-  <Dialog v-model="dialogVisible" :title="dialogTitle">
+  <Dialog v-model="dialogVisible" title="添加商品属性">
     <el-form
       ref="formRef"
       v-loading="formLoading"
@@ -26,8 +27,7 @@ const { t } = useI18n() // 国际化
 const message = useMessage() // 消息弹窗
 
 const dialogVisible = ref(false) // 弹窗的是否展示
-const dialogTitle = ref('添加商品属性') // 弹窗的标题
-const formLoading = ref(false) // 表单的加载中:1)修改时的数据加载;2)提交的按钮禁用
+const formLoading = ref(false) // 表单的加载中
 const formData = ref({
   name: ''
 })
@@ -44,7 +44,7 @@ const props = defineProps({
 })
 
 watch(
-  () => props.propertyList,
+  () => props.propertyList, // 解决 props 无法直接修改父组件的问题
   (data) => {
     if (!data) return
     attributeList.value = data
@@ -54,6 +54,7 @@ watch(
     immediate: true
   }
 )
+
 /** 打开弹窗 */
 const open = async () => {
   dialogVisible.value = true
@@ -71,19 +72,13 @@ const submitForm = async () => {
   formLoading.value = true
   try {
     const data = formData.value as PropertyApi.PropertyVO
-    // 检查属性是否已存在,如果有则返回属性和其下属性值
-    const res = await PropertyApi.getPropertyListAndValue({ name: data.name })
-    if (res.length === 0) {
-      const propertyId = await PropertyApi.createProperty(data)
-      attributeList.value.push({ id: propertyId, ...formData.value, values: [] })
-    } else {
-      if (res[0].values === null) {
-        res[0].values = []
-      }
-      // 不需要属性值
-      res[0].values = []
-      attributeList.value.push(res[0]) // 因为只用一个
-    }
+    const propertyId = await PropertyApi.createProperty(data)
+    // 添加到属性列表
+    attributeList.value.push({
+      id: propertyId,
+      ...formData.value,
+      values: []
+    })
     message.success(t('common.createSuccess'))
     dialogVisible.value = false
   } finally {

+ 72 - 79
src/views/mall/product/spu/form/SkuForm.vue

@@ -1,58 +1,49 @@
+<!-- 商品发布 - 库存价格 -->
 <template>
-  <!-- 情况一:添加/修改 -->
-  <el-form
-    ref="productSpuSkuRef"
-    :model="formData"
-    :rules="rules"
-    label-width="120px"
-    :disabled="isDetail"
-  >
-    <el-row>
-      <el-col :span="12">
-        <el-form-item label="商品规格" props="specType">
-          <el-radio-group v-model="formData.specType" @change="onChangeSpec">
-            <el-radio :label="false" class="radio">单规格</el-radio>
-            <el-radio :label="true">多规格</el-radio>
-          </el-radio-group>
-        </el-form-item>
-      </el-col>
-      <el-col :span="12">
-        <el-form-item label="分销类型" props="subCommissionType">
-          <el-radio-group v-model="formData.subCommissionType" @change="changeSubCommissionType">
-            <el-radio :label="false">默认设置</el-radio>
-            <el-radio :label="true" class="radio">单独设置</el-radio>
-          </el-radio-group>
-        </el-form-item>
-      </el-col>
-      <!-- 多规格添加-->
-      <el-col :span="24">
-        <el-form-item v-if="!formData.specType">
-          <SkuList
-            ref="skuListRef"
-            :prop-form-data="formData"
-            :propertyList="propertyList"
-            :rule-config="ruleConfig"
-          />
-        </el-form-item>
-        <el-form-item v-if="formData.specType" label="商品属性">
-          <el-button class="mb-10px mr-15px" @click="attributesAddFormRef.open">添加属性</el-button>
-          <ProductAttributes :propertyList="propertyList" @success="generateSkus" />
-        </el-form-item>
-        <template v-if="formData.specType && propertyList.length > 0">
-          <el-form-item label="批量设置">
-            <SkuList :is-batch="true" :prop-form-data="formData" :propertyList="propertyList" />
-          </el-form-item>
-          <el-form-item label="属性列表">
-            <SkuList
-              ref="skuListRef"
-              :prop-form-data="formData"
-              :propertyList="propertyList"
-              :rule-config="ruleConfig"
-            />
-          </el-form-item>
-        </template>
-      </el-col>
-    </el-row>
+  <el-form ref="formRef" :model="formData" :rules="rules" label-width="120px" :disabled="isDetail">
+    <el-form-item label="分销类型" props="subCommissionType">
+      <el-radio-group v-model="formData.subCommissionType" @change="changeSubCommissionType">
+        <el-radio :label="false">默认设置</el-radio>
+        <el-radio :label="true" class="radio">单独设置</el-radio>
+      </el-radio-group>
+    </el-form-item>
+    <el-form-item label="商品规格" props="specType">
+      <el-radio-group v-model="formData.specType" @change="onChangeSpec">
+        <el-radio :label="false" class="radio">单规格</el-radio>
+        <el-radio :label="true">多规格</el-radio>
+      </el-radio-group>
+    </el-form-item>
+    <!-- 多规格添加-->
+    <el-form-item v-if="!formData.specType">
+      <SkuList
+        ref="skuListRef"
+        :prop-form-data="formData"
+        :property-list="propertyList"
+        :rule-config="ruleConfig"
+      />
+    </el-form-item>
+    <el-form-item v-if="formData.specType" label="商品属性">
+      <el-button class="mb-10px mr-15px" @click="attributesAddFormRef.open">添加属性</el-button>
+      <ProductAttributes
+        :property-list="propertyList"
+        @success="generateSkus"
+        :is-detail="isDetail"
+      />
+    </el-form-item>
+    <template v-if="formData.specType && propertyList.length > 0">
+      <el-form-item label="批量设置" v-if="!isDetail">
+        <SkuList :is-batch="true" :prop-form-data="formData" :property-list="propertyList" />
+      </el-form-item>
+      <el-form-item label="规格列表">
+        <SkuList
+          ref="skuListRef"
+          :prop-form-data="formData"
+          :property-list="propertyList"
+          :rule-config="ruleConfig"
+          :is-detail="isDetail"
+        />
+      </el-form-item>
+    </template>
   </el-form>
 
   <!-- 商品属性添加 Form 表单 -->
@@ -62,7 +53,12 @@
 import { PropType } from 'vue'
 import { copyValueToTarget } from '@/utils'
 import { propTypes } from '@/utils/propTypes'
-import { getPropertyList, RuleConfig, SkuList } from '@/views/mall/product/spu/components/index.ts'
+import {
+  getPropertyList,
+  PropertyAndValues,
+  RuleConfig,
+  SkuList
+} from '@/views/mall/product/spu/components/index'
 import ProductAttributes from './ProductAttributes.vue'
 import ProductPropertyAddForm from './ProductPropertyAddForm.vue'
 import type { Spu } from '@/api/mall/product/spu'
@@ -100,17 +96,12 @@ const props = defineProps({
     type: Object as PropType<Spu>,
     default: () => {}
   },
-  activeName: propTypes.string.def(''),
   isDetail: propTypes.bool.def(false) // 是否作为详情组件
 })
 const attributesAddFormRef = ref() // 添加商品属性表单
-const productSpuSkuRef = ref() // 表单 Ref
-const propertyList = ref([]) // 商品属性列表
-const skuListRef = ref() // 商品属性列表Ref
-/** 调用 SkuList generateTableData 方法*/
-const generateSkus = (propertyList) => {
-  skuListRef.value.generateTableData(propertyList)
-}
+const formRef = ref() // 表单 Ref
+const propertyList = ref<PropertyAndValues[]>([]) // 商品属性列表
+const skuListRef = ref() // 商品属性列表 Ref
 const formData = reactive<Spu>({
   specType: false, // 商品规格
   subCommissionType: false, // 分销类型
@@ -121,9 +112,7 @@ const rules = reactive({
   subCommissionType: [required]
 })
 
-/**
- * 将传进来的值赋值给 formData
- */
+/** 将传进来的值赋值给 formData */
 watch(
   () => props.propFormData,
   (data) => {
@@ -131,6 +120,7 @@ watch(
       return
     }
     copyValueToTarget(formData, data)
+    // 将 SKU 的属性,整理成 PropertyAndValues 数组
     propertyList.value = getPropertyList(data)
   },
   {
@@ -144,25 +134,23 @@ const validate = async () => {
   // 校验 sku
   skuListRef.value.validateSku()
   // 校验表单
-  if (!productSpuSkuRef) return
-  return await unref(productSpuSkuRef).validate((valid) => {
-    if (!valid) {
-      message.warning('商品信息未完善!!')
-      emit('update:activeName', 'sku')
-      // 目的截断之后的校验
-      throw new Error('商品信息未完善!!')
-    } else {
-      // 校验通过更新数据
-      Object.assign(props.propFormData, formData)
-    }
-  })
+  if (!formRef) return
+  try {
+    await unref(formRef).validate()
+    // 校验通过更新数据
+    Object.assign(props.propFormData, formData)
+  } catch (e) {
+    message.error('【库存价格】不完善,请填写相关信息')
+    emit('update:activeName', 'sku')
+    throw e // 目的截断之后的校验
+  }
 }
 defineExpose({ validate })
 
 /** 分销类型 */
 const changeSubCommissionType = () => {
   // 默认为零,类型切换后也要重置为零
-  for (const item of formData.skus) {
+  for (const item of formData.skus!) {
     item.firstBrokeragePrice = 0
     item.secondBrokeragePrice = 0
   }
@@ -188,4 +176,9 @@ const onChangeSpec = () => {
     }
   ]
 }
+
+/** 调用 SkuList generateTableData 方法*/
+const generateSkus = (propertyList) => {
+  skuListRef.value.generateTableData(propertyList)
+}
 </script>