Ver Fonte

```
fix(product): 修复产品表单图片上传和数据显示问题

- 修复商品描述和商品说明注释位置错误
- 修复促销标题字段绑定错误,从packagingSpec改为promotionTitle
- 移除移动端详情标签页,统一使用电脑端详情
- 新增oss文件列表查询接口导入,用于图片URL转换
- 在分类查询和分类树获取中添加platform参数
- 实现主图和轮播图ossId到URL的转换逻辑
- 将图片上传组件从upload-image改为image-upload
- 修复图片上传组件中的URL处理逻辑,确保正确回显和提交
- 添加供应到期时间计算和回显功能
```

肖路 há 3 dias atrás
pai
commit
8e08c4612f

+ 12 - 2
src/components/ImageUpload/index.vue

@@ -99,6 +99,15 @@ watch(
       let list: OssVO[] = [];
       if (Array.isArray(val)) {
         list = val as OssVO[];
+      } else if (typeof val === 'string' && (val.startsWith('http://') || val.startsWith('https://'))) {
+        // 值已经是URL(后端直接返回了URL),跳过listByIds,直接按逗号分隔构造回显数据
+        const urls = val.split(',').filter((u) => u.trim());
+        fileList.value = urls.map((url, index) => ({
+          name: url,
+          url: url,
+          ossId: url
+        }));
+        return;
       } else {
         const res = await listByIds(val);
         list = res.data;
@@ -108,7 +117,8 @@ watch(
         // 字符串回显处理 如果此处存的是url可直接回显 如果存的是id需要调用接口查出来
         let itemData;
         if (typeof item === 'string') {
-          itemData = { name: item, url: item };
+          // URL字符串也设置ossId,与上方URL绕过分支保持一致,确保listToString能正确追踪
+          itemData = { name: item, url: item, ossId: item };
         } else {
           // 此处name使用ossId 防止删除出现重名
           itemData = { name: item.ossId, url: item.url, ossId: item.ossId };
@@ -227,7 +237,7 @@ const listToString = (list: any[], separator?: string) => {
   separator = separator || ',';
   for (const i in list) {
     if (undefined !== list[i].ossId && list[i].url.indexOf('blob:') !== 0) {
-      strs += list[i].ossId + separator;
+      strs += list[i].url + separator;
     }
   }
   return strs != '' ? strs.substring(0, strs.length - 1) : '';

+ 40 - 10
src/views/product/base/add.vue

@@ -188,7 +188,7 @@
               <div class="form-item-tip">A10产品名称由系统自动拼接:品牌名 + 规格型号 + 产品分类(三级分类)+ 发票规格,无需手动填写</div>
             </el-form-item>
 
-                        <!-- 商品描述 -->
+            <!-- 商品描述 -->
             <el-form-item label="商品描述:">
               <el-input
                 v-model="productForm.productDescription"
@@ -318,10 +318,10 @@
 
             <!-- 促销标题 -->
             <el-form-item label="促销标题:">
-              <el-input v-model="productForm.packagingSpec" type="textarea" :rows="3" placeholder="请输入促销标题" maxlength="300" show-word-limit />
+              <el-input v-model="productForm.promotionTitle" type="textarea" :rows="3" placeholder="请输入促销标题" maxlength="300" show-word-limit />
             </el-form-item>
 
-                        <!-- 商品说明 -->
+            <!-- 商品说明 -->
             <el-form-item label="商品说明:">
               <el-input
                 v-model="productForm.productExplain"
@@ -621,9 +621,6 @@
                 <el-tab-pane label="电脑端详情" name="pc">
                   <Editor v-model="productForm.pcDetail" :height="400" />
                 </el-tab-pane>
-                <el-tab-pane label="移动端详情" name="mobile">
-                  <Editor v-model="productForm.mobileDetail" :height="400" />
-                </el-tab-pane>
               </el-tabs>
             </el-form-item>
           </el-form>
@@ -783,6 +780,7 @@ import {
   updateBaseAudit,
   getBaseAudit,
 } from '@/api/product/baseAudit';
+import { listByIds } from '@/api/system/oss';
 import { BaseAuditVO, BaseAuditQuery, BaseAuditForm } from '@/api/product/baseAudit/types';
 import { getTaxCode } from '@/api/system/taxCode';
 import { listBrand, getBrand } from '@/api/product/brand';
@@ -1160,7 +1158,7 @@ const handleLevel3Search = async (keyword: string) => {
   }
   level3SearchLoading.value = true;
   try {
-    const res = await categoryList({ classLevel: 3, categoryName: keyword, pageNum: 1, pageSize: 50 });
+    const res = await categoryList({ classLevel: 3, categoryName: keyword, platform: 0, pageNum: 1, pageSize: 50 });
     level3SearchOptions.value = (res as any).data || (res as any).rows || [];
   } catch (error) {
     console.error('搜索三级分类失败:', error);
@@ -1413,13 +1411,45 @@ const handleSubmit = async () => {
       }
     }
 
+    // 将主图ossId转为url(upload-image组件emit的是URL,需判断是否已是完整URL)
+    let productImageUrl = '';
+    if (productForm.productImage) {
+      const val = String(productForm.productImage);
+      if (val.startsWith('http://') || val.startsWith('https://')) {
+        // 已是完整URL,直接使用
+        productImageUrl = val;
+      } else {
+        const mainRes = await listByIds(val);
+        if (mainRes.data && mainRes.data.length > 0) {
+          productImageUrl = mainRes.data[0].url;
+        }
+      }
+    }
+    // 将轮播图ossId转为url(upload-image组件emit的是逗号分隔的URL字符串)
+    let imageUrlStr = '';
+    if (carouselImages.value) {
+      const val = carouselImages.value;
+      const firstItem = val.split(',')[0];
+      if (firstItem && (firstItem.startsWith('http://') || firstItem.startsWith('https://'))) {
+        // 已是完整URL,直接使用
+        imageUrlStr = val;
+      } else {
+        const carouselRes = await listByIds(val);
+        if (carouselRes.data && carouselRes.data.length > 0) {
+          imageUrlStr = carouselRes.data.map((item) => item.url).join(',');
+        }
+      }
+    }
+
     // 准备提交数据,包含定制信息(A10产品名称由前端自动拼接,不传后端)
     const submitProductData: any = {
       ...productForm,
+      // 主图存url
+      productImage: productImageUrl,
       // 将服务保障ID数组转换为逗号分隔字符串
       serviceGuarantee: serviceGuarantees.value.map((id) => String(id)).join(','),
-      // 轮播图(ImageUpload 已是逗号分隔字符串)
-      imageUrl: carouselImages.value,
+      // 轮播图存url
+      imageUrl: imageUrlStr,
       // 将商品属性值转换为JSON字符串
       attributesList: JSON.stringify(productAttributesValues.value),
       isCustomize: customForm.isCustomize ? 1 : 0,
@@ -1593,7 +1623,7 @@ const formatRowPrice = (row: any, field: string) => {
 // 获取分类树
 const getCategoryTree = async () => {
   try {
-    const res = await categoryTree();
+    const res = await categoryTree({ platform: 0 });
     categoryOptions.value = res.data || [];
   } catch (error) {
     console.error('获取分类树失败:', error);

+ 69 - 14
src/views/product/baseAudit/add.vue

@@ -174,7 +174,7 @@
               <div class="form-item-tip">A10产品名称由系统自动拼接:品牌名 + 规格型号 + 产品分类(三级分类)+ 发票规格,无需手动填写</div>
             </el-form-item>
 
-                        <!-- 商品描述 -->
+            <!-- 商品描述 -->
             <el-form-item label="商品描述:">
               <el-input
                 v-model="productForm.productDescription"
@@ -233,7 +233,7 @@
               </el-col>
 
               <el-col :span="12">
-                <el-form-item label="单位:">
+                <el-form-item label="单位:" disabled>
                   <el-select
                     v-model="productForm.unitId"
                     placeholder="请选择"
@@ -313,7 +313,7 @@
               <el-input v-model="productForm.promotionTitle" type="textarea" :rows="3" placeholder="请输入促销标题" maxlength="300" show-word-limit />
             </el-form-item>
 
-                        <!-- 商品说明 -->
+            <!-- 商品说明 -->
             <el-form-item label="商品说明:">
               <el-input
                 v-model="productForm.productExplain"
@@ -597,13 +597,13 @@
           <el-form ref="detailFormRef" :model="productForm" label-width="120px" class="product-info-form">
             <!-- 商品主图 -->
             <el-form-item label="商品主图:" required>
-              <upload-image v-model="productForm.productImage" :limit="1" width="178px" height="178px" imageText="" />
+              <image-upload v-model="productForm.productImage" :limit="1" width="178px" height="178px" imageText="" />
               <div class="form-item-tip">从图片库选择,建议尺寸300*300px</div>
             </el-form-item>
 
             <!-- 商品轮播图 -->
             <el-form-item label="商品轮播图:" required>
-              <upload-image v-model="carouselImages" :limit="20" width="120px" height="120px" imageText="" />
+              <image-upload v-model="carouselImages" :limit="20" width="120px" height="120px" imageText="" />
               <div class="form-item-tip">从图片库选择,支持多选,建议尺寸300*300px</div>
             </el-form-item>
 
@@ -613,9 +613,6 @@
                 <el-tab-pane label="电脑端详情" name="pc">
                   <Editor v-model="productForm.pcDetail" :height="400" />
                 </el-tab-pane>
-                <el-tab-pane label="移动端详情" name="mobile">
-                  <Editor v-model="productForm.mobileDetail" :height="400" />
-                </el-tab-pane>
               </el-tabs>
             </el-form-item>
           </el-form>
@@ -782,6 +779,7 @@ import { listInfo } from '@/api/customer/supplierInfo';
 import { InfoVO } from '@/api/customer/supplierInfo/types';
 import { listComStaff } from '@/api/system/comStaff';
 import { ComStaffVO } from '@/api/system/comStaff/types';
+import { listByIds } from '@/api/system/oss';
 
 const route = useRoute();
 const router = useRouter();
@@ -985,7 +983,7 @@ const productForm = reactive<BaseForm>({
   mediumCategoryId: undefined,
   bottomCategoryId: undefined,
   unitId: undefined,
-  productImage: undefined,
+  productImage: '',
   imageUrl: undefined,
   isSelf: 0,
   productReviewStatus: 0,
@@ -1149,7 +1147,7 @@ const handleLevel3Search = async (keyword: string) => {
   }
   level3SearchLoading.value = true;
   try {
-    const res = await categoryList({ classLevel: 3, categoryName: keyword, pageNum: 1, pageSize: 50 });
+    const res = await categoryList({ classLevel: 3, categoryName: keyword, platform: 0, pageNum: 1, pageSize: 50 });
     level3SearchOptions.value = (res as any).data || (res as any).rows || [];
   } catch (error) {
     console.error('搜索三级分类失败:', error);
@@ -1397,13 +1395,47 @@ const handleSubmit = async () => {
       return;
     }
 
+    
+    
+    // 将主图ossId转为url(upload-image组件emit的是URL,需判断是否已是完整URL)
+    let productImageUrl = '';
+    if (productForm.productImage) {
+      const val = String(productForm.productImage);
+      if (val.startsWith('http://') || val.startsWith('https://')) {
+        // 已是完整URL,直接使用
+        productImageUrl = val;
+      } else {
+        const mainRes = await listByIds(val);
+        if (mainRes.data && mainRes.data.length > 0) {
+          productImageUrl = mainRes.data[0].url;
+        }
+      }
+    }
+    // 将轮播图ossId转为url(upload-image组件emit的是逗号分隔的URL字符串)
+    let imageUrlStr = '';
+    if (carouselImages.value) {
+      const val = carouselImages.value;
+      const firstItem = val.split(',')[0];
+      if (firstItem && (firstItem.startsWith('http://') || firstItem.startsWith('https://'))) {
+        // 已是完整URL,直接使用
+        imageUrlStr = val;
+      } else {
+        const carouselRes = await listByIds(val);
+        if (carouselRes.data && carouselRes.data.length > 0) {
+          imageUrlStr = carouselRes.data.map((item) => item.url).join(',');
+        }
+      }
+    }
+
     // 准备提交数据,包含定制信息(A10产品名称由前端自动拼接,不传后端)
     const submitProductData: any = {
       ...productForm,
+      // 主图存url
+      productImage: productImageUrl,
       // 将服务保障ID数组转换为逗号分隔字符串
       serviceGuarantee: serviceGuarantees.value.map((id) => String(id)).join(','),
-      // 轮播图URL逗号分隔
-      imageUrl: carouselImages.value.join(','),
+      // 轮播图存url
+      imageUrl: imageUrlStr,
       // 将商品属性值转换为JSON字符串
       attributesList: JSON.stringify(productAttributesValues.value),
       isCustomize: customForm.isCustomize ? 1 : 0,
@@ -1411,7 +1443,9 @@ const handleSubmit = async () => {
       customizedCraft: customForm.selectedCrafts.join(','),
       customDescription: customForm.customDescription,
       customDetailsJson: JSON.stringify(customForm.customDetails),
-      diyAttributesList: diyAttributesList.value.filter((item) => item.attributeKey || item.attributeValue)
+      diyAttributesList: diyAttributesList.value.filter((item) => item.attributeKey || item.attributeValue),
+      // 供应到期时间:后端保存日期格式,将计算好的到期日期提交
+      supplyValidityPeriod: supplyExpiryDate.value || undefined
     };
     // A10产品名称不传后端
     delete submitProductData.a10ProductName;
@@ -1565,7 +1599,7 @@ const formatRowPrice = (row: any, field: string) => {
 // 获取分类树
 const getCategoryTree = async () => {
   try {
-    const res = await categoryTree();
+    const res = await categoryTree({ platform: 0 });
     categoryOptions.value = res.data || [];
   } catch (error) {
     console.error('获取分类树失败:', error);
@@ -1807,6 +1841,27 @@ const loadProductDetail = async () => {
         productForm.afterSalesService = Number(res.data.productBaseVo.afterSalesService);
       }
 
+      // 回显供应时间 - 后端返回日期格式,需计算为天数
+      if (res.data.productBaseVo.supplyValidityPeriod) {
+        const endDate = new Date(res.data.productBaseVo.supplyValidityPeriod);
+        const now = new Date();
+        // 清除时分秒,只比较日期
+        now.setHours(0, 0, 0, 0);
+        endDate.setHours(0, 0, 0, 0);
+        const diffDays = Math.ceil((endDate.getTime() - now.getTime()) / (1000 * 60 * 60 * 24));
+        productForm.supplyValidityPeriod = diffDays > 0 ? diffDays : 0;
+        // 回显到期时间显示
+        const y = endDate.getFullYear();
+        const m = String(endDate.getMonth() + 1).padStart(2, '0');
+        const d = String(endDate.getDate()).padStart(2, '0');
+        supplyExpiryDate.value = `${y}-${m}-${d}`;
+      }
+
+      // 回显主图 - limit=1时组件内部会自动包装为数组,这里直接赋URL字符串即可
+      if (res.data.productBaseVo.productImage) {
+        productForm.productImage = res.data.productBaseVo.productImage;
+      }
+
       // 回显轮播图
       if (res.data.productBaseVo.imageUrl) {
         carouselImages.value = res.data.productBaseVo.imageUrl.split(',').filter((url: string) => url.trim());