Forráskód Böngészése

feat(product): 添加分类管理功能并优化产品分类组件

- 在产品分类API中新增平台参数支持和大客户ID字段
- 添加categoryA10分类管理和categoryBinding绑定相关API接口
- 新增商品管理路由页面用于分类下的商品管理功能
- 在VIP站点页面添加分类管理入口链接
- 修复多处产品状态比较逻辑,统一使用数字类型判断
- 移除产品分类页面中的平台列显示
- 添加商品管理按钮到分类表格操作列
- 集成A10分类选择树形组件到分类表单
- 实现路由参数传递平台和大客户ID功能
- 添加分类管理相关类型定义文件
肖路 3 napja
szülő
commit
32d4c3f69f

+ 2 - 2
src/components/upload-image/index.vue

@@ -7,11 +7,11 @@
           :class="{ 'rounded-full': type == 'avatar' }"
           :style="style"
         >
-          <div class="w-full h-full relative" v-if="imagesData && imagesData.length > 0 && imagesData[0] != ''">
+          <div class="w-full h-full relative" v-if="imagesData && imagesData.length > 0 && imagesData[0] != ''" @click="openDialog">
             <div class="w-full h-full flex items-center justify-center">
               <el-image class="w-full h-full" :src="imagesData[0].indexOf('data:image') != -1 ? imagesData[0] : img(imagesData[0])"></el-image>
             </div>
-            <div class="absolute z-[1] flex items-center justify-center w-full h-full inset-0 bg-black bg-opacity-60 operation">
+            <div class="absolute z-[1] flex items-center justify-center w-full h-full inset-0 bg-black bg-opacity-60 operation" @click.stop>
               <icon name="element ZoomIn" color="#fff" size="18px" class="mr-[10px]" @click="previewImage(imagesData, 0)" />
               <icon name="element Delete" color="#fff" size="18px" @click.stop="removeImage" />
             </div>

+ 43 - 208
src/views/platform/decoration/case/index.vue

@@ -6,8 +6,8 @@
         <el-form-item label="服务标题" prop="caseTitle">
           <el-input v-model="queryParams.caseTitle" placeholder="请输入服务标题" clearable style="width: 240px" @keyup.enter="handleQuery" />
         </el-form-item>
-        <el-form-item label="服务类别" prop="projectTypeId">
-          <el-select v-model="queryParams.projectTypeId" placeholder="请选择" clearable style="width: 160px">
+        <el-form-item label="服务类别" prop="projectType">
+          <el-select v-model="queryParams.projectType" placeholder="请选择" clearable style="width: 160px">
             <el-option v-for="item in projectTypeOptions" :key="item.id" :label="item.typeName" :value="item.id" />
           </el-select>
         </el-form-item>
@@ -62,30 +62,23 @@
         <el-table-column label="推荐" align="center" width="80">
           <template #default="{ row }">
             <span :class="row.isRecommend === '1' ? 'status-active' : 'status-inactive'">
-              {{ row.isRecommend === '1' ? '推荐' : '推荐' }}
-            </span>
-          </template>
-        </el-table-column>
-        <el-table-column label="相关推荐" align="center" width="100">
-          <template #default="{ row }">
-            <span :class="row.isRelatedRecommend === '1' ? 'status-active' : 'status-inactive'">
-              {{ row.isRelatedRecommend === '1' ? '推荐' : '推荐' }}
+              {{ row.isRecommend === '1' ? '推荐' : '不推荐' }}
             </span>
           </template>
         </el-table-column>
         <el-table-column label="是否显示" align="center" width="100">
           <template #default="{ row }">
             <span :class="row.isShow === '1' ? 'status-active' : 'status-inactive'">
-              {{ row.isShow === '1' ? '显示' : '显示' }}
+              {{ row.isShow === '1' ? '显示' : '不显示' }}
             </span>
           </template>
         </el-table-column>
         <el-table-column label="发布时间" prop="createTime" align="center" width="120" />
-        <el-table-column label="相关" align="center" width="80">
+        <!-- <el-table-column label="相关" align="center" width="80">
           <template #default="{ row }">
             阅读:{{ row.readCount || 0 }}
           </template>
-        </el-table-column>
+        </el-table-column> -->
         <el-table-column label="操作" align="center" width="150" fixed="right">
           <template #default="{ row }">
             <el-button type="primary" link @click="handleView(row)">查看</el-button>
@@ -114,8 +107,8 @@
             </el-form-item>
           </el-col>
           <el-col :span="12">
-            <el-form-item label="客户行业" prop="industryCategoryId">
-              <el-select v-model="form.industryCategoryId" placeholder="请选择客户行业" style="width: 100%">
+            <el-form-item label="客户行业" prop="clientIndustry">
+              <el-select v-model="form.clientIndustry" placeholder="请选择客户行业" style="width: 100%">
                 <el-option v-for="item in industryOptions" :key="item.id" :label="item.industryCategoryName" :value="item.id" />
               </el-select>
             </el-form-item>
@@ -123,8 +116,8 @@
         </el-row>
         <el-row :gutter="20">
           <el-col :span="12">
-            <el-form-item label="项目类型" prop="projectTypeId">
-              <el-select v-model="form.projectTypeId" placeholder="请选择项目类型" style="width: 100%">
+            <el-form-item label="项目类型" prop="projectType">
+              <el-select v-model="form.projectType" placeholder="请选择项目类型" style="width: 100%">
                 <el-option v-for="item in projectTypeOptions" :key="item.id" :label="item.typeName" :value="item.id" />
               </el-select>
             </el-form-item>
@@ -136,54 +129,30 @@
           </el-col>
         </el-row>
         <el-row :gutter="20">
-          <el-col :span="12">
+          <el-col :span="24">
             <el-form-item label="是否推荐">
               <el-switch v-model="form.isRecommend" active-value="1" inactive-value="0" />
             </el-form-item>
           </el-col>
-          <el-col :span="12">
-            <el-form-item label="相关推荐">
-              <el-switch v-model="form.isRelatedRecommend" active-value="1" inactive-value="0" />
-            </el-form-item>
-          </el-col>
         </el-row>
         <el-form-item label="上传方案">
-          <file-upload v-model="form.planFile" :limit="1" :disabled="dialog.isView" />
+          <file-upload v-model="form.uploadProgram" :limit="1" :disabled="dialog.isView" />
         </el-form-item>
         <el-form-item label="案例图片">
-          <div class="case-images">
-            <div v-for="(img, index) in imageList" :key="index" class="image-item">
-              <el-image :src="img.url" fit="cover" class="image-preview" />
-              <div class="image-actions" v-if="!dialog.isView">
-                <el-button type="primary" link size="small" @click="setMainImage(index)">
-                  {{ img.isMain ? '主图' : '主图' }}
-                </el-button>
-                <el-button type="danger" link size="small" @click="removeImage(index)">删除</el-button>
-              </div>
-              <div v-if="img.isMain" class="main-tag">主图</div>
-            </div>
-            <el-upload
-              v-if="!dialog.isView && imageList.length < 5"
-              :action="uploadUrl"
-              :headers="uploadHeaders"
-              :show-file-list="false"
-              :on-success="handleImageSuccess"
-              :before-upload="handleBeforeImageUpload"
-              accept=".jpg,.jpeg,.png"
-              class="image-uploader"
-            >
-              <div class="upload-btn">
-                <el-icon><Plus /></el-icon>
-                <span>上传图片</span>
-              </div>
-            </el-upload>
-          </div>
+          <template v-if="dialog.isView">
+            <el-image v-if="form.caseImage" :src="form.caseImage" fit="cover" style="width: 120px; height: 120px; border-radius: 4px" :preview-src-list="[form.caseImage]" preview-teleported />
+            <span v-else class="text-gray-400">暂无图片</span>
+          </template>
+          <upload-image v-else v-model="form.caseImage" :limit="1" width="120px" height="120px" />
         </el-form-item>
         <el-form-item label="项目简介" prop="projectBrief">
           <el-input v-model="form.projectBrief" type="textarea" :rows="3" placeholder="请输入项目简介" />
         </el-form-item>
-        <el-form-item label="项目详情" prop="projectDetail">
-          <editor v-model="form.projectDetail" :min-height="200" :read-only="dialog.isView" />
+        <el-form-item label="项目详情" prop="projectDetails">
+          <editor v-model="form.projectDetails" :min-height="200" :read-only="dialog.isView" />
+        </el-form-item>
+        <el-form-item label="备注">
+          <el-input v-model="form.remark" type="textarea" :rows="2" placeholder="请输入备注" />
         </el-form-item>
       </el-form>
       <template #footer>
@@ -202,21 +171,15 @@ import { Search, Refresh, Plus, Picture } from '@element-plus/icons-vue';
 import { listServiceCase, getServiceCase, addServiceCase, updateServiceCase, delServiceCase } from '@/api/product/serviceCase';
 import { listIndustryCategory } from '@/api/customer/industryCategory';
 import { listProjectType } from '@/api/globalSetting/projectType';
-import { globalHeaders } from '@/utils/request';
-
 const { proxy } = getCurrentInstance() as ComponentInternalInstance;
 
-// 上传地址
-const uploadUrl = import.meta.env.VITE_APP_BASE_API + '/resource/oss/upload';
-const uploadHeaders = globalHeaders();
-
 // 查询表单
 const queryFormRef = ref<FormInstance>();
 const queryParams = reactive({
   pageNum: 1,
   pageSize: 10,
   caseTitle: '',
-  projectTypeId: undefined as number | undefined
+  projectType: undefined as number | undefined
 });
 
 // 列表数据
@@ -239,22 +202,21 @@ const dialog = reactive({
 const initForm = {
   id: undefined as number | undefined,
   caseTitle: '',
-  industryCategoryId: undefined as number | undefined,
-  projectTypeId: undefined as number | undefined,
+  clientIndustry: undefined as number | undefined,
+  projectType: undefined as number | undefined,
   isShow: '1',
   isRecommend: '0',
-  isRelatedRecommend: '0',
-  planFile: '',
+  uploadProgram: '',
   caseImage: '',
   projectBrief: '',
-  projectDetail: ''
+  projectDetails: '',
+  remark: ''
 };
 const form = ref({ ...initForm });
-const imageList = ref<{ url: string; ossId?: string; isMain: boolean }[]>([]);
 
 const rules = {
   caseTitle: [{ required: true, message: '请输入案例标题', trigger: 'blur' }],
-  projectTypeId: [{ required: true, message: '请选择项目类型', trigger: 'change' }]
+  projectType: [{ required: true, message: '请选择项目类型', trigger: 'change' }]
 };
 
 /** 获取列表 */
@@ -289,7 +251,7 @@ const getProjectTypeName = (row: any): string => {
   const matched = projectTypeOptions.value.find(
     item => item.typeName === row.projectType || String(item.id) === String(row.projectType)
   );
-  return matched?.typeName || row.projectType || '-';
+  return matched?.typeName || row.projectTypeName || row.projectType || '-';
 };
 
 /** 搜索 */
@@ -302,7 +264,7 @@ const handleQuery = () => {
 const resetQuery = () => {
   queryFormRef.value?.resetFields();
   queryParams.caseTitle = '';
-  queryParams.projectTypeId = undefined;
+  queryParams.projectType = undefined;
   handleQuery();
 };
 
@@ -345,35 +307,28 @@ const loadDetail = async (id: number) => {
   try {
     const res = await getServiceCase(id);
     const data = res.data || {};
-    // 通过 projectType 字段匹配项目类型选项
+    // 通过 projectType 字段匹配项目类型选项(使用 String() 避免类型不匹配)
     const matchedProjectType = projectTypeOptions.value.find(
-      item => item.typeName === data.projectType || item.id === data.projectType
+      item => item.typeName === data.projectType || String(item.id) === String(data.projectType)
     );
-    // 通过 clientIndustry 字段匹配客户行业选项
+    // 通过 clientIndustry 字段匹配客户行业选项(使用 String() 避免类型不匹配)
     const matchedIndustry = industryOptions.value.find(
-      item => item.industryCategoryName === data.clientIndustry || item.id === data.clientIndustry
+      item => item.industryCategoryName === data.clientIndustry || String(item.id) === String(data.clientIndustry)
     );
     form.value = {
       id: data.id,
       caseTitle: data.caseTitle || '',
-      industryCategoryId: matchedIndustry?.id ?? data.industryCategoryId,
-      projectTypeId: matchedProjectType?.id ?? data.projectTypeId,
+      // 优先使用后端返回的ID字段,兜底使用匹配到的选项ID
+      clientIndustry: data.clientIndustry ?? matchedIndustry?.id,
+      projectType: data.projectType ?? matchedProjectType?.id,
       isShow: data.isShow || '0',
       isRecommend: data.isRecommend || '0',
-      isRelatedRecommend: data.isRelatedRecommend || '0',
-      planFile: data.planFile || '',
+      uploadProgram: data.uploadProgram || '',
       caseImage: data.caseImage || '',
       projectBrief: data.projectBrief || '',
-      projectDetail: data.projectDetail || ''
+      projectDetails: data.projectDetails || '',
+      remark: data.remark || ''
     };
-    // 解析图片列表
-    if (data.caseImage) {
-      const images = data.caseImage.split(',').filter((s: string) => s);
-      imageList.value = images.map((url: string, index: number) => ({
-        url,
-        isMain: index === 0
-      }));
-    }
   } catch (error) {
     console.error('获取详情失败', error);
   }
@@ -382,7 +337,6 @@ const loadDetail = async (id: number) => {
 /** 重置表单 */
 const resetForm = () => {
   form.value = { ...initForm };
-  imageList.value = [];
   formRef.value?.resetFields();
 };
 
@@ -395,63 +349,11 @@ const handleDelete = (row: any) => {
   });
 };
 
-/** 图片上传前校验 */
-const handleBeforeImageUpload = (file: any) => {
-  const isImage = ['image/jpeg', 'image/png', 'image/jpg'].includes(file.type);
-  const isLt5M = file.size / 1024 / 1024 < 5;
-  if (!isImage) {
-    proxy?.$modal.msgError('只能上传 JPG/PNG 格式图片!');
-    return false;
-  }
-  if (!isLt5M) {
-    proxy?.$modal.msgError('图片大小不能超过 5MB!');
-    return false;
-  }
-  proxy?.$modal.loading('正在上传...');
-  return true;
-};
-
-/** 图片上传成功 */
-const handleImageSuccess = (res: any) => {
-  proxy?.$modal.closeLoading();
-  if (res.code === 200) {
-    imageList.value.push({
-      url: res.data.url,
-      ossId: res.data.ossId,
-      isMain: imageList.value.length === 0
-    });
-  } else {
-    proxy?.$modal.msgError(res.msg || '上传失败');
-  }
-};
-
-/** 设置主图 */
-const setMainImage = (index: number) => {
-  imageList.value.forEach((img, i) => {
-    img.isMain = i === index;
-  });
-};
-
-/** 删除图片 */
-const removeImage = (index: number) => {
-  const wasMain = imageList.value[index].isMain;
-  imageList.value.splice(index, 1);
-  if (wasMain && imageList.value.length > 0) {
-    imageList.value[0].isMain = true;
-  }
-};
-
 /** 提交表单 */
 const submitForm = async () => {
   const valid = await formRef.value?.validate();
   if (!valid) return;
 
-  // 组装图片URL,主图放第一个
-  const mainImg = imageList.value.find(img => img.isMain);
-  const otherImgs = imageList.value.filter(img => !img.isMain);
-  const allImgs = mainImg ? [mainImg, ...otherImgs] : otherImgs;
-  form.value.caseImage = allImgs.map(img => img.url).join(',');
-
   try {
     if (form.value.id) {
       await updateServiceCase(form.value);
@@ -467,8 +369,8 @@ const submitForm = async () => {
   }
 };
 
-onMounted(() => {
-  loadOptions();
+onMounted(async () => {
+  await loadOptions();
   getList();
 });
 </script>
@@ -500,71 +402,4 @@ onMounted(() => {
   color: #909399;
   cursor: pointer;
 }
-
-.case-images {
-  display: flex;
-  flex-wrap: wrap;
-  gap: 10px;
-
-  .image-item {
-    position: relative;
-    width: 100px;
-    border: 1px solid #dcdfe6;
-    border-radius: 4px;
-    overflow: hidden;
-
-    .image-preview {
-      width: 100px;
-      height: 100px;
-    }
-
-    .image-actions {
-      display: flex;
-      justify-content: space-around;
-      padding: 5px 0;
-      background: #f5f7fa;
-    }
-
-    .main-tag {
-      position: absolute;
-      top: 0;
-      left: 0;
-      background: #409eff;
-      color: #fff;
-      font-size: 12px;
-      padding: 2px 6px;
-    }
-  }
-
-  .image-uploader {
-    width: 100px;
-    height: 130px;
-    border: 1px dashed #dcdfe6;
-    border-radius: 4px;
-    display: flex;
-    align-items: center;
-    justify-content: center;
-    cursor: pointer;
-
-    &:hover {
-      border-color: #409eff;
-    }
-
-    .upload-btn {
-      display: flex;
-      flex-direction: column;
-      align-items: center;
-      color: #8c939d;
-
-      .el-icon {
-        font-size: 24px;
-        margin-bottom: 5px;
-      }
-
-      span {
-        font-size: 12px;
-      }
-    }
-  }
-}
 </style>

+ 3 - 61
src/views/platform/decoration/caseAd/index.vue

@@ -1,17 +1,7 @@
 <template>
   <div class="case-ad-page">
     <div class="case-ad-container">
-      <div class="ad-image-wrapper" @click="triggerUpload">
-        <el-image v-if="imageUrl" :src="imageUrl" fit="fill" class="ad-image" />
-        <div v-else class="image-placeholder">
-          <el-icon size="48"><Picture /></el-icon>
-          <span>点击上传图片</span>
-        </div>
-      </div>
-      <!-- 隐藏的image-upload -->
-      <div style="display: none">
-        <upload-image ref="uploadRef" v-model="imageOssId" :limit="1" />
-      </div>
+      <upload-image v-model="imageOssId" :limit="1" width="100%" height="300px" image-text="点击上传图片" />
     </div>
   </div>
 </template>
@@ -19,15 +9,12 @@
 <script setup name="DecorationCaseAd" lang="ts">
 import { ref, onMounted, watch } from 'vue';
 import { ElMessage } from 'element-plus';
-import { Picture } from '@element-plus/icons-vue';
+import uploadImage from '@/components/upload-image/index.vue';
 import { listAdContent, updateAdContent, addAdContent } from '@/api/ad/content';
 import dayjs from 'dayjs';
 
-const uploadRef = ref();
-// 图片ossId(给image-upload组件用)
+// 图片ossId(给upload-image组件用)
 const imageOssId = ref('');
-// 图片URL(用于显示)
-const imageUrl = ref('');
 
 const adData = ref({
   id: undefined as number | undefined,
@@ -53,26 +40,12 @@ const loadAdData = async () => {
       adData.value.startTime = item.startTime || '';
       adData.value.endTime = item.endTime || '';
       imageOssId.value = item.imageUrl || '';
-      imageUrl.value = item.imageUrl || '';
     }
   } catch (error) {
     console.error('加载广告数据失败', error);
   }
 };
 
-// 点击触发上传
-const triggerUpload = () => {
-  // 先清空旧图片,这样才能重新上传
-  imageOssId.value = '';
-  // 等待DOM更新后再触发上传
-  setTimeout(() => {
-    const uploadEl = document.querySelector('.case-ad-page .el-upload--picture-card') as HTMLElement;
-    if (uploadEl) {
-      uploadEl.click();
-    }
-  }, 100);
-};
-
 // 监听ossId变化,自动保存
 watch(imageOssId, async (newVal) => {
   if (newVal) {
@@ -119,35 +92,4 @@ onMounted(() => {
   max-width: 100%;
   margin: 0 auto;
 }
-
-.ad-image-wrapper {
-  width: 100%;
-  cursor: pointer;
-
-  &:hover {
-    opacity: 0.95;
-  }
-
-  .ad-image {
-    width: 100%;
-    height: 300px;
-    display: block;
-  }
-
-  .image-placeholder {
-    width: 100%;
-    height: 300px;
-    background: #fff;
-    display: flex;
-    flex-direction: column;
-    align-items: center;
-    justify-content: center;
-    color: #999;
-
-    span {
-      margin-top: 10px;
-      font-size: 14px;
-    }
-  }
-}
 </style>

+ 2 - 2
src/views/platform/decoration/floorAd/index.vue

@@ -5,7 +5,7 @@
       <div class="ad-card">
         <!-- 标签页切换 -->
         <el-tabs v-model="activeTab" @tab-change="handleTabChange">
-          <el-tab-pane label="首页三联广告" name="triple" />
+          <!-- <el-tab-pane label="首页三联广告" name="triple" /> -->
           <el-tab-pane label="首页图标广告" name="icon" />
           <el-tab-pane label="首页横幅广告" name="banner" />
         </el-tabs>
@@ -124,7 +124,7 @@ import { listAdContent, updateAdContent } from '@/api/ad/content';
 import dayjs from 'dayjs';
 
 // 当前标签页
-const activeTab = ref('triple');
+const activeTab = ref('icon');
 
 // 当前选中的广告索引(默认null,点击才展开)
 const selectedIndex = ref<number | null>(null);

+ 3 - 3
src/views/platform/decoration/guide/index.vue

@@ -417,8 +417,8 @@ const handleRecommend = async (row: any) => {
       ElMessage.error('推荐位不存在');
       return;
     }
-    if (guideList.value.length >= 3) {
-      ElMessage.warning('最多只能添加3个采购指南');
+    if (guideList.value.length >= 5) {
+      ElMessage.warning('最多只能添加5个采购指南');
       return;
     }
     await addRecommendLink({ recommendId: recommendId.value, programId: row.id });
@@ -622,7 +622,7 @@ const confirmSelectProducts = async () => {
 
 .guide-grid {
   display: grid;
-  grid-template-columns: repeat(3, 1fr);
+  grid-template-columns: repeat(5, 1fr);
   gap: 15px;
   margin-bottom: 12px;
 }

+ 2 - 2
src/views/platform/enterprise/floorAd/index.vue

@@ -5,7 +5,7 @@
       <div class="ad-card">
         <!-- 标签页切换 -->
         <el-tabs v-model="activeTab" @tab-change="handleTabChange">
-          <el-tab-pane label="首页三联广告" name="triple" />
+          <!-- <el-tab-pane label="首页三联广告" name="triple" /> -->
           <el-tab-pane label="首页图标广告" name="icon" />
           <el-tab-pane label="首页横幅广告" name="banner" />
         </el-tabs>
@@ -124,7 +124,7 @@ import { listAdContent, updateAdContent } from '@/api/ad/content';
 import dayjs from 'dayjs';
 
 // 当前标签页
-const activeTab = ref('triple');
+const activeTab = ref('icon');
 
 // 当前选中的广告索引(默认null,点击才展开)
 const selectedIndex = ref<number | null>(null);

+ 30 - 9
src/views/platform/industrial/carousel/index.vue

@@ -92,7 +92,8 @@
             placeholder="选择开始时间"
             value-format="YYYY-MM-DD"
             style="width: 100%"
-            @change="form.endTime = ''"
+            :disabled-date="disabledStartDate"
+            @change="carouselFormRef?.validateField('endTime')"
           />
         </el-form-item>
         <el-form-item label="结束时间" prop="endTime">
@@ -172,18 +173,38 @@ const initFormData = {
 
 const form = ref({ ...initFormData });
 
-const disabledEndDate = (date: Date) => {
-  if (!form.value.startTime) return false;
-  const start = new Date(form.value.startTime + 'T00:00:00');
-  return date.getTime() < start.getTime();
-};
-
-const rules = reactive({
+const rules = reactive<import('element-plus').FormRules>({
   title: [{ required: true, message: '广告名称不能为空', trigger: 'blur' }],
   startTime: [{ required: true, message: '开始时间不能为空', trigger: 'change' }],
-  endTime: [{ required: true, message: '结束时间不能为空', trigger: 'change' }]
+  endTime: [
+    { required: true, message: '结束时间不能为空', trigger: 'change' },
+    {
+      validator: (_rule: any, value: any, callback: (error?: Error) => void) => {
+        if (value && form.value.startTime && dayjs(value).isBefore(dayjs(form.value.startTime).add(1, 'day'))) {
+          callback(new Error('截止时间必须大于开始时间'));
+        } else {
+          callback();
+        }
+      },
+      trigger: 'change'
+    }
+  ]
 });
 
+/** 禁用过去日期(开始时间:今天之前不可选) */
+const disabledStartDate = (time: Date) => {
+  return time.getTime() < Date.now() - 8.64e7;
+};
+
+/** 禁用过去日期且禁用不晚于开始时间的日期(结束时间) */
+const disabledEndDate = (time: Date) => {
+  const base = time.getTime() < Date.now() - 8.64e7;
+  if (form.value.startTime) {
+    return base || time.getTime() <= dayjs(form.value.startTime).valueOf();
+  }
+  return base;
+};
+
 /** 查询列表 */
 const getList = async () => {
   loading.value = true;

+ 3 - 10
src/views/platform/industrial/floorAd/index.vue

@@ -4,10 +4,10 @@
       <div class="ad-card">
         <!-- 标签页切换 -->
         <el-tabs v-model="activeTab" @tab-change="handleTabChange">
-          <el-tab-pane label="首页三联广告" name="triple" />
+          <!-- <el-tab-pane label="首页三联广告" name="triple" /> -->
           <el-tab-pane label="首页图标广告" name="icon" />
           <el-tab-pane label="首页logo" name="logo" />
-          <el-tab-pane label="首页联系电话" name="contact" />
+          <!-- <el-tab-pane label="首页联系电话" name="contact" /> -->
         </el-tabs>
 
         <!-- 三联广告展示区 -->
@@ -162,7 +162,7 @@ import { Picture } from '@element-plus/icons-vue';
 import { listAdContent, updateAdContent, addAdContent } from '@/api/ad/content';
 import dayjs from 'dayjs';
 
-const activeTab = ref('triple');
+const activeTab = ref('icon');
 const selectedIndex = ref<number | null>(null);
 
 // 广告类型映射(工业装修专用,避免和平台装修冲突)
@@ -317,7 +317,6 @@ onMounted(() => {
 <style scoped lang="scss">
 .floor-ad-page {
   min-height: 100vh;
-  background: #f5f5f5;
   padding: 20px;
 }
 
@@ -327,7 +326,6 @@ onMounted(() => {
 }
 
 .ad-card {
-  background: #fff;
   border-radius: 4px;
   padding: 20px;
 
@@ -402,11 +400,9 @@ onMounted(() => {
     border-radius: 4px;
     border: 2px solid transparent;
     &:hover {
-      background: #f5f5f5;
     }
     &.active {
       border-color: #409eff;
-      background: #ecf5ff;
     }
 
     .icon-img {
@@ -459,12 +455,10 @@ onMounted(() => {
     border: 2px solid transparent;
 
     &:hover {
-      background: #f5f5f5;
     }
 
     &.active {
       border-color: #409eff;
-      background: #ecf5ff;
     }
 
     .contact-info {
@@ -489,7 +483,6 @@ onMounted(() => {
   align-items: center;
   width: 100%;
   height: 100%;
-  background: #f5f7fa;
   color: #909399;
   font-size: 24px;
 }

+ 8 - 3
src/views/product/category/categoryProduct.vue

@@ -216,7 +216,8 @@ const router = useRouter();
 // 从路由参数中获取分类 ID
 const routeCategoryId = computed(() => {
   const id = route.query.categoryId;
-  return id !== undefined && id !== '' ? Number(id) : undefined;
+  // 保持字符串类型,避免大整数精度丢失
+  return id !== undefined && id !== '' ? String(id) : undefined;
 });
 
 // 从路由参数中获取 platform
@@ -386,7 +387,9 @@ const handleBatchAdd = async () => {
           productNo: item.productNo,
           bottomCategoryId: routeCategoryId.value,
           platform: routePlatform.value,
-          customerId: routeCustomerId.value
+          customerId: routeCustomerId.value,
+          // 确保大整数ID以字符串形式传输,避免精度丢失
+          _idAsString: true
         })
       )
     );
@@ -410,7 +413,9 @@ const handleAddSingleProduct = async (row: any) => {
       productNo: row.productNo,
       bottomCategoryId: routeCategoryId.value,
       platform: routePlatform.value,
-      customerId: routeCustomerId.value
+      customerId: routeCustomerId.value,
+      // 确保大整数ID以字符串形式传输,避免精度丢失
+      _idAsString: true
     });
     proxy?.$modal.msgSuccess('添加成功');
     await getList();

+ 10 - 10
src/views/product/category/index.vue

@@ -44,7 +44,7 @@
           </template>
         </el-table-column>
         <el-table-column prop="sort" align="center" label="排序" width="150"></el-table-column>
-        <el-table-column prop="isShow" align="center" label="是否示" width="150">
+        <el-table-column prop="isShow" align="center" label="是否示" width="150">
           <template #default="scope">
             <span>{{ scope.row.isShow === 1 ? '是' : '否' }}</span>
           </template>
@@ -52,19 +52,19 @@
         <el-table-column fixed="right" align="center" label="操作">
           <template #default="scope">
             <el-tooltip content="新增分类" placement="top">
-              <el-button v-hasPermi="['product:category:add']" link type="primary" icon="Plus" @click="handleAdd(scope.row)" />
+              <el-button  link type="primary" icon="Plus" @click="handleAdd(scope.row)" />
             </el-tooltip>
             <el-tooltip content="修改" placement="top">
-              <el-button v-hasPermi="['product:category:edit']" link type="primary" icon="Edit" @click="handleUpdate(scope.row)" />
+              <el-button  link type="primary" icon="Edit" @click="handleUpdate(scope.row)" />
             </el-tooltip>
             <el-tooltip content="删除" placement="top">
-              <el-button v-hasPermi="['product:category:remove']" link type="primary" icon="Delete" @click="handleDelete(scope.row)" />
-            </el-tooltip>
-            <el-tooltip v-if="scope.row.classLevel === 3" content="设置审核员" placement="top">
-              <el-button v-hasPermi="['product:category:edit']" link type="primary" icon="User" @click="handleSetReviewer(scope.row)" />
+              <el-button  link type="primary" icon="Delete" @click="handleDelete(scope.row)" />
             </el-tooltip>
+<!--            <el-tooltip v-if="scope.row.classLevel === 3" content="设置审核员" placement="top">-->
+<!--              <el-button v-hasPermi="['product:category:edit']" link type="primary" icon="User" @click="handleSetReviewer(scope.row)" />-->
+<!--            </el-tooltip>-->
             <el-tooltip v-if="scope.row.classLevel === 3" content="商品管理" placement="top">
-              <el-button v-hasPermi="['product:category:edit']" link type="primary" icon="Goods" @click="handleProductManage(scope.row)" />
+              <el-button link type="primary" icon="Goods" @click="handleProductManage(scope.row)" />
             </el-tooltip>
           </template>
         </el-table-column>
@@ -132,9 +132,9 @@
               <el-input-number v-model="form.sort" controls-position="right" :min="0" style="width: 100%" />
             </el-form-item>
           </el-col>
-        
+
           <el-col :span="12">
-            <el-form-item label="是否示" prop="isShow">
+            <el-form-item label="是否示" prop="isShow">
               <el-switch v-model="form.isShow" :active-value="1" :inactive-value="0" />
             </el-form-item>
           </el-col>

+ 61 - 71
src/views/product/topics/form.vue

@@ -100,10 +100,6 @@
           <el-col :span="24">
             <el-form-item label="封面图" prop="coverImage">
               <div class="flex items-center gap-2">
-                <el-button @click="openCoverUpload" :disabled="isViewMode">
-                  <el-icon><Upload /></el-icon>
-                  <span class="ml-1">选择上传文件</span>
-                </el-button>
                 <el-button @click="openCoverSelector" :disabled="isViewMode">
                   <el-icon><Picture /></el-icon>
                   <span class="ml-1">从图片库选择</span>
@@ -124,7 +120,7 @@
           <el-col :span="24">
             <el-form-item label="上传方案" prop="uploadScheme">
               <div class="flex items-center gap-2">
-                <el-button @click="openSchemeSelector" :disabled="isViewMode">
+                <el-button @click="openSchemeUpload" :disabled="isViewMode">
                   <el-icon><Upload /></el-icon>
                   <span class="ml-1">选择文件</span>
                 </el-button>
@@ -166,49 +162,40 @@
       </el-form>
     </el-card>
 
-    <!-- 封面图上传对话框 -->
-    <el-dialog v-model="coverUploadVisible" title="上传封面图" width="500px" append-to-body>
+    <!-- 封面图选择器 -->
+    <FileSelector
+      v-model="coverSelectorVisible"
+      title="选择封面图"
+      :allowed-types="[1]"
+      :multiple="false"
+      :allow-upload="true"
+      @confirm="handleCoverSelected"
+    />
+
+    <!-- 方案文件上传对话框 -->
+    <el-dialog v-model="schemeUploadVisible" title="上传方案文件" width="500px" append-to-body>
       <el-upload
-        ref="coverUploadRef"
+        ref="schemeUploadRef"
         class="upload-demo"
         drag
-        action="#"
-        :auto-upload="false"
+        :action="uploadUrl"
+        :headers="uploadHeaders"
         :limit="1"
-        accept="image/*"
-        :on-change="handleCoverUploadChange"
+        accept=".pdf,.doc,.docx,.xls,.xlsx,.ppt,.pptx,.txt"
+        :on-success="handleSchemeUploadSuccess"
+        :before-upload="handleSchemeBeforeUpload"
+        :on-error="handleSchemeUploadError"
       >
         <el-icon class="el-icon--upload"><UploadFilled /></el-icon>
         <div class="el-upload__text">将文件拖到此处,或<em>点击上传</em></div>
         <template #tip>
-          <div class="el-upload__tip">只能上传图片文件</div>
+          <div class="el-upload__tip">支持 pdf, doc, docx, xls, xlsx, ppt, pptx, txt 格式文件</div>
         </template>
       </el-upload>
       <template #footer>
-        <el-button @click="coverUploadVisible = false">取消</el-button>
-        <el-button type="primary" @click="confirmCoverUpload">确定</el-button>
+        <el-button @click="schemeUploadVisible = false">关闭</el-button>
       </template>
     </el-dialog>
-
-    <!-- 封面图选择器 -->
-    <FileSelector
-      v-model="coverSelectorVisible"
-      title="选择封面图"
-      :allowed-types="[1]"
-      :multiple="false"
-      :allow-upload="true"
-      @confirm="handleCoverSelected"
-    />
-
-    <!-- 方案文件选择器 -->
-    <FileSelector
-      v-model="schemeSelectorVisible"
-      title="选择方案文件"
-      :allowed-types="[4]"
-      :multiple="false"
-      :allow-upload="true"
-      @confirm="handleSchemeSelected"
-    />
   </div>
 </template>
 
@@ -222,6 +209,7 @@ import { listCustomerTag } from '@/api/customer/customerTag';
 import { TopicsForm } from '@/api/product/topics/types';
 import FileSelector from '@/components/FileSelector/index.vue';
 import { img } from '@/utils/common';
+import { globalHeaders } from '@/utils/request';
 
 const { proxy } = getCurrentInstance() as ComponentInternalInstance;
 const router = useRouter();
@@ -229,7 +217,7 @@ const route = useRoute();
 
 // 表单引用
 const formRef = ref<ElFormInstance>();
-const coverUploadRef = ref();
+const schemeUploadRef = ref();
 
 // 加载状态
 const submitLoading = ref(false);
@@ -248,12 +236,12 @@ const industryOptions = ref<any[]>([]);
 const tagOptions = ref<any[]>([]);
 
 // 对话框状态
-const coverUploadVisible = ref(false);
 const coverSelectorVisible = ref(false);
-const schemeSelectorVisible = ref(false);
+const schemeUploadVisible = ref(false);
 
-// 临时上传文件
-const tempCoverFile = ref<any>(null);
+// 上传配置
+const uploadUrl = import.meta.env.VITE_APP_BASE_API + '/resource/oss/upload';
+const uploadHeaders = globalHeaders();
 
 // 方案文件名
 const schemeFileName = ref('');
@@ -343,37 +331,14 @@ const handleBack = () => {
   router.back();
 };
 
-/** 打开封面图上传对话框 */
-const openCoverUpload = () => {
-  tempCoverFile.value = null;
-  coverUploadVisible.value = true;
-};
-
 /** 打开封面图选择器 */
 const openCoverSelector = () => {
   coverSelectorVisible.value = true;
 };
 
-/** 打开方案文件选择器 */
-const openSchemeSelector = () => {
-  schemeSelectorVisible.value = true;
-};
-
-/** 处理封面图上传文件变化 */
-const handleCoverUploadChange = (file: any) => {
-  tempCoverFile.value = file;
-};
-
-/** 确认上传封面图 */
-const confirmCoverUpload = () => {
-  if (tempCoverFile.value) {
-    const reader = new FileReader();
-    reader.onload = (e) => {
-      form.value.coverImage = e.target?.result as string;
-    };
-    reader.readAsDataURL(tempCoverFile.value.raw);
-    coverUploadVisible.value = false;
-  }
+/** 打开方案文件上传对话框 */
+const openSchemeUpload = () => {
+  schemeUploadVisible.value = true;
 };
 
 /** 处理封面图选择 */
@@ -388,14 +353,39 @@ const clearCoverImage = () => {
   form.value.coverImage = '';
 };
 
-/** 处理方案文件选择 */
-const handleSchemeSelected = (files: any[]) => {
-  if (files && files.length > 0) {
-    form.value.uploadScheme = files[0].path || files[0].url;
-    schemeFileName.value = files[0].name || files[0].originalName || '已选择文件';
+/** 方案文件上传前校验 */
+const handleSchemeBeforeUpload = (file: any) => {
+  const fileName = file.name.split('.');
+  const fileExt = fileName[fileName.length - 1].toLowerCase();
+  const allowedTypes = ['pdf', 'doc', 'docx', 'xls', 'xlsx', 'ppt', 'pptx', 'txt'];
+  if (!allowedTypes.includes(fileExt)) {
+    proxy?.$modal.msgError('文件格式不正确,请上传 pdf, doc, docx, xls, xlsx, ppt, pptx, txt 格式文件!');
+    return false;
+  }
+  if (file.name.includes(',')) {
+    proxy?.$modal.msgError('文件名不正确,不能包含英文逗号!');
+    return false;
+  }
+  return true;
+};
+
+/** 方案文件上传成功回调 */
+const handleSchemeUploadSuccess = (res: any, file: any) => {
+  if (res.code === 200) {
+    form.value.uploadScheme = res.data.url;
+    schemeFileName.value = res.data.fileName || file.name;
+    proxy?.$modal.msgSuccess('上传成功');
+    schemeUploadVisible.value = false;
+  } else {
+    proxy?.$modal.msgError(res.msg || '上传失败');
   }
 };
 
+/** 方案文件上传失败回调 */
+const handleSchemeUploadError = () => {
+  proxy?.$modal.msgError('上传文件失败');
+};
+
 /** 清除方案文件 */
 const clearScheme = () => {
   form.value.uploadScheme = '';

+ 1 - 1
src/views/product/topics/index.vue

@@ -38,7 +38,7 @@
       </template>
 
       <el-table v-loading="loading" border :data="topicsList" @selection-change="handleSelectionChange">
-        <el-table-column label="编号" align="center" prop="procurementTopicsId" width="100" />
+        <el-table-column label="编号" align="center" prop="id" width="100" />
         <el-table-column label="标题" align="center" prop="title" min-width="180" show-overflow-tooltip>
           <template #default="scope">
             <el-button link type="primary" @click="handleView(scope.row)">{{ scope.row.title }}</el-button>

+ 10 - 25
src/views/system/loginSetting/index.vue

@@ -10,7 +10,7 @@
       <div class="tips-section">
         <p>网站登录页顶部4张图片,每次刷新可随机显示,最多可设置上传4张。</p>
         <p>选择上传文件并提交表单生效,图片请依照输入框下提示文字内容选项。</p>
-        <p>背景色指图片下方区域颜色,适用于较宽分辨率下浏览页面时整体充满窗宽,因此请注择和上传图片最为接近或相邻原的色彩。</p>
+
       </div>
 
       <!-- 图片1 -->
@@ -35,10 +35,7 @@
             <div class="upload-btn">
               <upload-image v-model="item.image" :limit="1" width="100px" height="100px" />
             </div>
-            <div class="color-picker">
-              <span>背景色:</span>
-              <el-color-picker v-model="item.bgColor" />
-            </div>
+
             <div class="action-btns">
               <el-button @click="handleReset(index)">重 置</el-button>
               <el-button type="primary" @click="handleConfirm(index)">确 认</el-button>
@@ -61,10 +58,10 @@ const loading = ref(true);
 const configList = ref<any[]>([]);
 
 const imageList = reactive([
-  { image: '', bgColor: '#409EFF', configKeyImage: 'loginImage1', configKeyColor: 'loginBgColor1' },
-  { image: '', bgColor: '#409EFF', configKeyImage: 'loginImage2', configKeyColor: 'loginBgColor2' },
-  { image: '', bgColor: '#409EFF', configKeyImage: 'loginImage3', configKeyColor: 'loginBgColor3' },
-  { image: '', bgColor: '#409EFF', configKeyImage: 'loginImage4', configKeyColor: 'loginBgColor4' }
+  { image: '', configKeyImage: 'loginImage1' },
+  { image: '', configKeyImage: 'loginImage2' },
+  { image: '', configKeyImage: 'loginImage3' },
+  { image: '', configKeyImage: 'loginImage4' }
 ]);
 
 const getList = async () => {
@@ -77,9 +74,7 @@ const getList = async () => {
         if (item.configKey === img.configKeyImage) {
           imageList[index].image = item.value || '';
         }
-        if (item.configKey === img.configKeyColor) {
-          imageList[index].bgColor = item.value || '#409EFF';
-        }
+
       });
     });
   } finally {
@@ -89,7 +84,7 @@ const getList = async () => {
 
 const handleReset = (index: number) => {
   imageList[index].image = '';
-  imageList[index].bgColor = '#409EFF';
+
 };
 
 const handleConfirm = async (index: number) => {
@@ -103,13 +98,7 @@ const handleConfirm = async (index: number) => {
     await addPlatformConfig({ configType: '1', configKey: item.configKeyImage, name: `登录图片${index + 1}`, value: item.image });
   }
 
-  // 保存背景色
-  const existColor = configList.value.find((c: any) => c.configKey === item.configKeyColor);
-  if (existColor) {
-    await updatePlatformConfig({ ...existColor, value: item.bgColor });
-  } else {
-    await addPlatformConfig({ configType: '1', configKey: item.configKeyColor, name: `登录背景色${index + 1}`, value: item.bgColor });
-  }
+
 
   proxy?.$modal.msgSuccess('保存成功');
   getList();
@@ -186,11 +175,7 @@ onMounted(() => {
   flex-direction: column;
   gap: 15px;
 }
-.color-picker {
-  display: flex;
-  align-items: center;
-  gap: 10px;
-}
+
 .action-btns {
   display: flex;
   gap: 10px;