Przeglądaj źródła

12-17-zl-前端

林小张 9 godzin temu
rodzic
commit
5340f47a2c

+ 34 - 0
src/api/decoration/section.ts

@@ -0,0 +1,34 @@
+import request from '@/utils/request';
+
+// 装修模块标题配置接口
+const baseUrl = '/system/decorationSection';
+
+// 列表查询
+export function listDecorationSection(params: any) {
+  return request.get(baseUrl + '/list', { params });
+}
+
+// 详情
+export function getDecorationSection(id: number) {
+  return request.get(`${baseUrl}/${id}`);
+}
+
+// 根据模块类型查询
+export function getDecorationSectionByType(sectionType: string) {
+  return request.get(baseUrl + '/list', { params: { sectionType } });
+}
+
+// 新增
+export function addDecorationSection(data: any) {
+  return request.post(baseUrl, data);
+}
+
+// 修改
+export function updateDecorationSection(data: any) {
+  return request.put(baseUrl, data);
+}
+
+// 删除
+export function delDecorationSection(id: number | string) {
+  return request.delete(`${baseUrl}/${id}`);
+}

+ 0 - 60
src/router/index.ts

@@ -89,66 +89,6 @@ export const constantRoutes: RouteRecordRaw[] = [
       }
     ]
   },
-  {
-    path: '/decoration',
-    component: Layout,
-    redirect: '/decoration/carousel',
-    alwaysShow: true,
-    meta: { title: '平台装修', icon: 'system' },
-    children: [
-      {
-        path: 'carousel',
-        component: () => import('@/views/platform/decoration/carousel/index.vue'),
-        name: 'Carousel',
-        meta: { title: '轮播广告', icon: 'component' }
-      },
-      {
-        path: 'solution',
-        component: () => import('@/views/platform/decoration/solution/index.vue'),
-        name: 'Solution',
-        meta: { title: '方案管理', icon: 'documentation' }
-      },
-      {
-        path: 'brand',
-        component: () => import('@/views/platform/decoration/brand/index.vue'),
-        name: 'Brand',
-        meta: { title: '品牌中心', icon: 'shopping' }
-      },
-      {
-        path: 'fresh',
-        component: () => import('@/views/platform/decoration/fresh/index.vue'),
-        name: 'Fresh',
-        meta: { title: '新鲜好物', icon: 'star' }
-      },
-      {
-        path: 'flashSale',
-        component: () => import('@/views/platform/decoration/flashSale/index.vue'),
-        name: 'FlashSale',
-        meta: { title: '品牌闪购', icon: 'star' }
-      }
-    ]
-  },
-  {
-    path: '/industrial',
-    component: Layout,
-    redirect: '/industrial/carousel',
-    alwaysShow: true,
-    meta: { title: '工业装修', icon: 'tool' },
-    children: [
-      {
-        path: 'carousel',
-        component: () => import('@/views/platform/industrial/carousel/index.vue'),
-        name: 'IndustrialCarousel',
-        meta: { title: '轮播广告', icon: 'component' }
-      },
-      {
-        path: 'booth',
-        component: () => import('@/views/platform/industrial/booth/index.vue'),
-        name: 'IndustrialBooth',
-        meta: { title: '轮播展位商品', icon: 'shopping' }
-      }
-    ]
-  }
 ];
 
 // 动态路由,基于用户权限动态去加载

+ 64 - 33
src/views/platform/decoration/brand/index.vue

@@ -64,21 +64,26 @@
 </template>
 
 <script setup name="Brand" lang="ts">
-import { reactive, ref } from 'vue';
+import { reactive, ref, onMounted } from 'vue';
 import { ElMessage, ElMessageBox } from 'element-plus';
 import { Picture } from '@element-plus/icons-vue';
+import { listAdContent, addAdContent, updateAdContent, delAdContent } from '@/api/ad/content';
+import { listByIds } from '@/api/system/oss';
 
-const queryParams = reactive({ pageNum: 1, pageSize: 10 });
+const adType = 'decoration_brand';
+
+const queryParams = reactive({ pageNum: 1, pageSize: 10, adType });
 const form = reactive({
   id: undefined as number | undefined,
   title: '',
   imageUrl: '',
   link: '',
-  sort: 0
+  sort: 0,
+  adType,
+  status: 1
 });
 const rules = {
-  title: [{ required: true, message: '品牌名称不能为空', trigger: 'blur' }],
-  imageUrl: [{ required: true, message: '品牌图片不能为空', trigger: 'change' }]
+  title: [{ required: true, message: '品牌名称不能为空', trigger: 'blur' }]
 };
 
 const dialog = reactive({ visible: false, title: '' });
@@ -87,53 +92,77 @@ const total = ref(0);
 const brandList = ref<any[]>([]);
 const brandFormRef = ref();
 
-// 模拟数据
-const mockData = [
-  { id: 1, title: '罗技', imageUrl: 'https://via.placeholder.com/400x300/1a1a2e/FFFFFF?text=Logitech', link: '/brand/logitech', sort: 1 },
-  { id: 2, title: '摩飞', imageUrl: 'https://via.placeholder.com/400x300/16213e/FFFFFF?text=Morphy+Richards', link: '/brand/morphy', sort: 2 },
-  { id: 3, title: '小熊', imageUrl: 'https://via.placeholder.com/400x300/f5e6ca/333333?text=Bear', link: '/brand/bear', sort: 3 },
-  { id: 4, title: '希诺', imageUrl: 'https://via.placeholder.com/400x300/e8e8e8/333333?text=HEENOOR', link: '/brand/heenoor', sort: 4 },
-  { id: 5, title: '什湖', imageUrl: 'https://via.placeholder.com/400x300/87ceeb/333333?text=什湖', link: '/brand/shihu', sort: 5 }
-];
-
-const getList = () => {
+const getList = async () => {
   loading.value = true;
-  setTimeout(() => {
-    const start = (queryParams.pageNum - 1) * queryParams.pageSize;
-    const end = start + queryParams.pageSize;
-    brandList.value = mockData.slice(start, end);
-    total.value = mockData.length;
+  try {
+    const res = await listAdContent(queryParams);
+    const rows = res.rows || [];
+    // 把 ossId 转换成真正的图片 URL
+    const ossIds = rows.map((item: any) => item.imageUrl).filter((id: any) => id).join(',');
+    if (ossIds) {
+      try {
+        const ossRes = await listByIds(ossIds);
+        const ossMap = new Map(ossRes.data.map((oss: any) => [String(oss.ossId), oss.url]));
+        rows.forEach((item: any) => {
+          if (item.imageUrl && ossMap.has(String(item.imageUrl))) {
+            item.imageUrl = ossMap.get(String(item.imageUrl));
+          }
+        });
+      } catch (e) {
+        console.error('获取图片URL失败', e);
+      }
+    }
+    brandList.value = rows;
+    total.value = res.total || 0;
+  } catch (error) {
+    console.error('获取列表失败', error);
+  } finally {
     loading.value = false;
-  }, 300);
+  }
 };
 
 const handleAdd = () => {
   reset();
   dialog.visible = true;
-  dialog.title = '添加品牌';
+  dialog.title = '添加品牌广告';
 };
 
 const handleUpdate = (row: any) => {
   reset();
   Object.assign(form, row);
   dialog.visible = true;
-  dialog.title = '修改品牌';
+  dialog.title = '修改品牌广告';
 };
 
-const submitForm = () => {
-  brandFormRef.value?.validate((valid: boolean) => {
+const submitForm = async () => {
+  brandFormRef.value?.validate(async (valid: boolean) => {
     if (valid) {
-      ElMessage.success(form.id ? '修改成功' : '添加成功');
-      dialog.visible = false;
-      getList();
+      try {
+        if (form.id) {
+          await updateAdContent(form);
+          ElMessage.success('修改成功');
+        } else {
+          await addAdContent(form);
+          ElMessage.success('添加成功');
+        }
+        dialog.visible = false;
+        getList();
+      } catch (error) {
+        ElMessage.error('操作失败');
+      }
     }
   });
 };
 
 const handleDelete = (row: any) => {
-  ElMessageBox.confirm('是否确认删除品牌"' + row.title + '"?').then(() => {
-    ElMessage.success('删除成功');
-    getList();
+  ElMessageBox.confirm('是否确认删除品牌广告"' + row.title + '"?').then(async () => {
+    try {
+      await delAdContent(row.id);
+      ElMessage.success('删除成功');
+      getList();
+    } catch (error) {
+      ElMessage.error('删除失败');
+    }
   }).catch(() => {});
 };
 
@@ -143,11 +172,13 @@ const cancel = () => {
 };
 
 const reset = () => {
-  Object.assign(form, { id: undefined, title: '', imageUrl: '', link: '', sort: 0 });
+  Object.assign(form, { id: undefined, title: '', imageUrl: '', link: '', sort: 0, adType, status: 1 });
   brandFormRef.value?.resetFields();
 };
 
-getList();
+onMounted(() => {
+  getList();
+});
 </script>
 
 <style scoped lang="scss">

+ 18 - 1
src/views/platform/decoration/carousel/index.vue

@@ -147,6 +147,7 @@
 
 <script setup name="DecorationCarousel" lang="ts">
 import { listAdContent, addAdContent, updateAdContent, delAdContent } from '@/api/ad/content';
+import { listByIds } from '@/api/system/oss';
 import dayjs from 'dayjs';
 
 const { proxy } = getCurrentInstance() as ComponentInternalInstance;
@@ -209,7 +210,23 @@ const getList = async () => {
     params.endTime = dateRange.value[1];
   }
   const res = await listAdContent(params);
-  carouselList.value = res.rows || [];
+  const rows = res.rows || [];
+  // 把 ossId 转换成真正的图片 URL
+  const ossIds = rows.map((item: any) => item.imageUrl).filter((id: any) => id).join(',');
+  if (ossIds) {
+    try {
+      const ossRes = await listByIds(ossIds);
+      const ossMap = new Map(ossRes.data.map((oss: any) => [String(oss.ossId), oss.url]));
+      rows.forEach((item: any) => {
+        if (item.imageUrl && ossMap.has(String(item.imageUrl))) {
+          item.imageUrl = ossMap.get(String(item.imageUrl));
+        }
+      });
+    } catch (e) {
+      console.error('获取图片URL失败', e);
+    }
+  }
+  carouselList.value = rows;
   total.value = res.total || 0;
   loading.value = false;
 };

+ 169 - 15
src/views/platform/decoration/flashSale/index.vue

@@ -14,14 +14,17 @@
 
       <!-- 品牌展示区域 -->
       <div class="brand-showcase">
-        <!-- 左侧大图 -->
+        <!-- 左侧大图(可点击编辑) -->
         <div class="showcase-left">
-          <el-image :src="featuredImage" fit="cover" class="featured-image">
+          <el-image :src="bannerConfig.imageUrl || 'https://via.placeholder.com/300x400/1a1a2e/FFFFFF?text=Featured'" fit="cover" class="featured-image">
             <template #error>
               <div class="image-slot"><el-icon><Picture /></el-icon></div>
             </template>
           </el-image>
-          <el-button type="warning" class="view-all-btn" @click="handleViewAll">查看全部></el-button>
+          <div class="edit-overlay" @click="handleEditBanner">
+            <span>点击编辑</span>
+          </div>
+          <el-button type="warning" class="view-all-btn" @click.stop="handleViewAll">查看全部></el-button>
         </div>
 
         <!-- 右侧品牌网格 -->
@@ -76,7 +79,7 @@
           </el-col>
           <el-col :span="12">
             <el-form-item label="链接">
-              <el-input v-model="headerForm.link" placeholder="请输入链接地址" />
+              <el-input v-model="headerForm.linkUrl" placeholder="请输入链接地址" />
             </el-form-item>
           </el-col>
         </el-row>
@@ -98,48 +101,173 @@
         <el-button @click="headerDialog.visible = false">取 消</el-button>
       </template>
     </el-dialog>
+
+    <!-- 编辑左侧大图对话框 -->
+    <el-dialog v-model="bannerDialog.visible" title="编辑品牌广告" width="500px" append-to-body>
+      <el-form :model="bannerForm" label-width="80px">
+        <el-form-item label="链接">
+          <el-input v-model="bannerForm.link" placeholder="请输入跳转链接" />
+        </el-form-item>
+        <el-form-item label="图片">
+          <image-upload v-model="bannerForm.imageUrl" :limit="1" />
+        </el-form-item>
+      </el-form>
+      <template #footer>
+        <el-button type="primary" @click="saveBannerConfig">确 认</el-button>
+        <el-button @click="bannerDialog.visible = false">取 消</el-button>
+      </template>
+    </el-dialog>
   </div>
 </template>
 
 <script setup name="FlashSale" lang="ts">
-import { ref, reactive } from 'vue';
+import { ref, reactive, onMounted } from 'vue';
 import { ElMessage } from 'element-plus';
 import { Picture, Close } from '@element-plus/icons-vue';
+import { getDecorationSectionByType, addDecorationSection, updateDecorationSection } from '@/api/decoration/section';
+import { listAdContent, addAdContent, updateAdContent } from '@/api/ad/content';
+import { listByIds } from '@/api/system/oss';
 
 const searchKeyword = ref('');
-const featuredImage = ref('https://via.placeholder.com/300x400/1a1a2e/FFFFFF?text=Featured');
+
+// 左侧大图配置
+const bannerConfig = ref({
+  id: null as number | null,
+  imageUrl: '',
+  link: ''
+});
+
+const bannerDialog = reactive({ visible: false });
+const bannerForm = reactive({
+  id: null as number | null,
+  title: '品牌闪购大图',
+  imageUrl: '',
+  link: '',
+  adType: 'flash_sale_banner',
+  status: 1
+});
+
+// 加载左侧大图
+const loadBannerConfig = async () => {
+  try {
+    const res = await listAdContent({ adType: 'flash_sale_banner', pageNum: 1, pageSize: 1 });
+    if (res.rows && res.rows.length > 0) {
+      const data = res.rows[0];
+      let imageUrl = data.imageUrl || '';
+      // 把 ossId 转换成真正的图片 URL
+      if (imageUrl && /^\d+$/.test(imageUrl)) {
+        try {
+          const ossRes = await listByIds(imageUrl);
+          if (ossRes.data && ossRes.data.length > 0) {
+            imageUrl = ossRes.data[0].url;
+          }
+        } catch (e) {
+          console.error('获取图片URL失败', e);
+        }
+      }
+      bannerConfig.value = {
+        id: data.id,
+        imageUrl: imageUrl,
+        link: data.link || ''
+      };
+    }
+  } catch (error) {
+    console.error('加载大图配置失败', error);
+  }
+};
+
+const handleEditBanner = () => {
+  Object.assign(bannerForm, bannerConfig.value, { adType: 'flash_sale_banner', status: 1 });
+  bannerDialog.visible = true;
+};
+
+const saveBannerConfig = async () => {
+  try {
+    if (bannerForm.id) {
+      await updateAdContent(bannerForm);
+    } else {
+      await addAdContent(bannerForm);
+    }
+    await loadBannerConfig();
+    bannerDialog.visible = false;
+    ElMessage.success('大图配置已保存');
+  } catch (error) {
+    ElMessage.error('保存失败');
+  }
+};
 
 // 标题配置
 const headerConfig = ref({
+  id: null as number | null,
   title: '大牌推荐',
   subtitle: '甄选大牌,优质好品',
   linkText: '查看更多品牌信息',
-  link: 'https://www.yoe365.com/brand',
+  linkUrl: 'https://www.yoe365.com/brand',
   bgColor: '#ffffff',
   textColor: '#303133'
 });
 
 const headerDialog = reactive({ visible: false });
 const headerForm = reactive({
+  id: null as number | null,
   title: '',
   subtitle: '',
   linkText: '',
-  link: '',
+  linkUrl: '',
   bgColor: '#ffffff',
   textColor: '#303133'
 });
 
+// 加载标题配置
+const loadHeaderConfig = async () => {
+  try {
+    const res = await getDecorationSectionByType('flash_sale');
+    if (res.rows && res.rows.length > 0) {
+      const data = res.rows[0];
+      headerConfig.value = {
+        id: data.id,
+        title: data.title || '大牌推荐',
+        subtitle: data.subtitle || '甄选大牌,优质好品',
+        linkText: data.linkText || '查看更多品牌信息',
+        linkUrl: data.linkUrl || 'https://www.yoe365.com/brand',
+        bgColor: data.bgColor || '#ffffff',
+        textColor: data.textColor || '#303133'
+      };
+    }
+  } catch (error) {
+    console.error('加载标题配置失败', error);
+  }
+};
+
 const handleEditHeader = () => {
   Object.assign(headerForm, headerConfig.value);
   headerDialog.visible = true;
 };
 
-const saveHeaderConfig = () => {
-  Object.assign(headerConfig.value, headerForm);
-  headerDialog.visible = false;
-  ElMessage.success('标题配置已保存');
+const saveHeaderConfig = async () => {
+  try {
+    const data = {
+      ...headerForm,
+      sectionType: 'flash_sale'
+    };
+    if (headerForm.id) {
+      await updateDecorationSection(data);
+    } else {
+      await addDecorationSection(data);
+    }
+    await loadHeaderConfig();
+    headerDialog.visible = false;
+    ElMessage.success('标题配置已保存');
+  } catch (error) {
+    ElMessage.error('保存失败');
+  }
 };
 
+onMounted(() => {
+  loadHeaderConfig();
+  loadBannerConfig();
+});
+
 const brandList = ref([
   { id: 1, title: '联想', description: '联想', logoUrl: 'https://via.placeholder.com/120x60/FFFFFF/333333?text=Lenovo' },
   { id: 2, title: '惠普', description: '惠普', logoUrl: 'https://via.placeholder.com/120x60/FFFFFF/333333?text=HP' },
@@ -152,7 +280,11 @@ const brandList = ref([
 ]);
 
 const handleViewAll = () => {
-  ElMessage.info('查看全部功能开发中...');
+  if (bannerConfig.value.link) {
+    window.open(bannerConfig.value.link, '_blank');
+  } else {
+    ElMessage.info('暂未配置跳转链接');
+  }
 };
 
 const handleRemove = (item: any) => {
@@ -164,8 +296,8 @@ const handleSearch = () => {
 };
 
 const handleLinkClick = () => {
-  if (headerConfig.value.link) {
-    window.open(headerConfig.value.link, '_blank');
+  if (headerConfig.value.linkUrl) {
+    window.open(headerConfig.value.linkUrl, '_blank');
   }
 };
 </script>
@@ -228,6 +360,28 @@ const handleLinkClick = () => {
     border-radius: 8px;
   }
 
+  .edit-overlay {
+    position: absolute;
+    top: 0;
+    left: 0;
+    right: 0;
+    bottom: 60px;
+    background: rgba(0, 0, 0, 0.3);
+    display: flex;
+    align-items: center;
+    justify-content: center;
+    border-radius: 8px 8px 0 0;
+    cursor: pointer;
+    opacity: 0;
+    transition: opacity 0.3s;
+    color: #fff;
+    font-size: 14px;
+  }
+
+  &:hover .edit-overlay {
+    opacity: 1;
+  }
+
   .view-all-btn {
     position: absolute;
     bottom: 20px;

+ 173 - 3
src/views/platform/decoration/fresh/index.vue

@@ -1,18 +1,188 @@
 <template>
   <div class="fresh-container">
     <el-card shadow="hover">
-      <el-empty description="新鲜好物页面开发中..." />
+      <!-- 标题区域(可点击编辑) -->
+      <div class="section-header" @click="handleEditHeader">
+        <div class="header-left">
+          <h2 class="section-title">{{ headerConfig.title }}</h2>
+          <span class="section-subtitle">{{ headerConfig.subtitle }}</span>
+        </div>
+        <div class="header-right">
+          <el-link type="primary" :underline="false" @click.stop="handleLinkClick">{{ headerConfig.linkText }}></el-link>
+        </div>
+      </div>
+
+      <!-- 商品列表区域(等同事接口) -->
+      <el-empty description="商品列表开发中,等待商品模块接口..." />
     </el-card>
+
+    <!-- 编辑标题对话框 -->
+    <el-dialog v-model="headerDialog.visible" title="编辑标题" width="600px" append-to-body>
+      <el-form :model="headerForm" label-width="100px">
+        <el-row :gutter="20">
+          <el-col :span="12">
+            <el-form-item label="标题名称" required>
+              <el-input v-model="headerForm.title" placeholder="请输入标题名称" />
+            </el-form-item>
+          </el-col>
+          <el-col :span="12">
+            <el-form-item label="副标题" required>
+              <el-input v-model="headerForm.subtitle" placeholder="请输入副标题" />
+            </el-form-item>
+          </el-col>
+        </el-row>
+        <el-row :gutter="20">
+          <el-col :span="12">
+            <el-form-item label="链接文字">
+              <el-input v-model="headerForm.linkText" placeholder="请输入链接文字" />
+            </el-form-item>
+          </el-col>
+          <el-col :span="12">
+            <el-form-item label="链接">
+              <el-input v-model="headerForm.linkUrl" placeholder="请输入链接地址" />
+            </el-form-item>
+          </el-col>
+        </el-row>
+        <el-row :gutter="20">
+          <el-col :span="12">
+            <el-form-item label="背景色">
+              <el-color-picker v-model="headerForm.bgColor" />
+            </el-form-item>
+          </el-col>
+          <el-col :span="12">
+            <el-form-item label="字体颜色">
+              <el-color-picker v-model="headerForm.textColor" />
+            </el-form-item>
+          </el-col>
+        </el-row>
+      </el-form>
+      <template #footer>
+        <el-button type="primary" @click="saveHeaderConfig">确 认</el-button>
+        <el-button @click="headerDialog.visible = false">取 消</el-button>
+      </template>
+    </el-dialog>
   </div>
 </template>
 
 <script setup name="Fresh" lang="ts">
-// 新鲜好物页面
+import { ref, reactive, onMounted } from 'vue';
+import { ElMessage } from 'element-plus';
+import { getDecorationSectionByType, addDecorationSection, updateDecorationSection } from '@/api/decoration/section';
+
+// 标题配置
+const headerConfig = ref({
+  id: null as number | null,
+  title: '新鲜好物',
+  subtitle: '寻觅世间好物',
+  linkText: '更多',
+  linkUrl: '',
+  bgColor: '#ffffff',
+  textColor: '#303133'
+});
+
+const headerDialog = reactive({ visible: false });
+const headerForm = reactive({
+  id: null as number | null,
+  title: '',
+  subtitle: '',
+  linkText: '',
+  linkUrl: '',
+  bgColor: '#ffffff',
+  textColor: '#303133'
+});
+
+// 加载标题配置
+const loadHeaderConfig = async () => {
+  try {
+    const res = await getDecorationSectionByType('fresh');
+    if (res.rows && res.rows.length > 0) {
+      const data = res.rows[0];
+      headerConfig.value = {
+        id: data.id,
+        title: data.title || '新鲜好物',
+        subtitle: data.subtitle || '寻觅世间好物',
+        linkText: data.linkText || '更多',
+        linkUrl: data.linkUrl || '',
+        bgColor: data.bgColor || '#ffffff',
+        textColor: data.textColor || '#303133'
+      };
+    }
+  } catch (error) {
+    console.error('加载标题配置失败', error);
+  }
+};
+
+const handleEditHeader = () => {
+  Object.assign(headerForm, headerConfig.value);
+  headerDialog.visible = true;
+};
+
+const saveHeaderConfig = async () => {
+  try {
+    const data = {
+      ...headerForm,
+      sectionType: 'fresh'
+    };
+    if (headerForm.id) {
+      await updateDecorationSection(data);
+    } else {
+      await addDecorationSection(data);
+    }
+    await loadHeaderConfig();
+    headerDialog.visible = false;
+    ElMessage.success('标题配置已保存');
+  } catch (error) {
+    ElMessage.error('保存失败');
+  }
+};
+
+const handleLinkClick = () => {
+  if (headerConfig.value.linkUrl) {
+    window.open(headerConfig.value.linkUrl, '_blank');
+  }
+};
+
+onMounted(() => {
+  loadHeaderConfig();
+});
 </script>
 
 <style scoped lang="scss">
 .fresh-container {
   height: 100%;
 }
-</style>
 
+.section-header {
+  display: flex;
+  justify-content: space-between;
+  align-items: center;
+  margin-bottom: 20px;
+  padding: 12px 16px;
+  background: #f8f9fa;
+  border-radius: 4px;
+  cursor: pointer;
+  transition: background 0.3s;
+
+  &:hover {
+    background: #ebeef5;
+  }
+
+  .header-left {
+    display: flex;
+    align-items: baseline;
+    gap: 12px;
+
+    .section-title {
+      margin: 0;
+      font-size: 20px;
+      font-weight: 600;
+      color: #303133;
+    }
+
+    .section-subtitle {
+      font-size: 14px;
+      color: #909399;
+    }
+  }
+}
+</style>

+ 140 - 12
src/views/platform/decoration/solution/index.vue

@@ -1,14 +1,14 @@
 <template>
   <div class="solution-container">
     <el-card shadow="hover">
-      <!-- 标题区域 -->
-      <div class="solution-header">
+      <!-- 标题区域(可点击编辑) -->
+      <div class="solution-header" @click="handleEditHeader">
         <div class="header-left">
-          <h2 class="solution-title">热门方案</h2>
-          <span class="solution-subtitle">为采购人提供"业务+服务"的项目解决方案</span>
+          <h2 class="solution-title">{{ headerConfig.title }}</h2>
+          <span class="solution-subtitle">{{ headerConfig.subtitle }}</span>
         </div>
         <div class="header-right">
-          <el-link type="primary" :underline="false" @click="handleMore">更多方案></el-link>
+          <el-link type="primary" :underline="false" @click.stop="handleLinkClick">{{ headerConfig.linkText }}></el-link>
         </div>
       </div>
 
@@ -56,6 +56,52 @@
       />
     </el-card>
 
+    <!-- 编辑标题对话框 -->
+    <el-dialog v-model="headerDialog.visible" title="编辑标题" width="600px" append-to-body>
+      <el-form :model="headerForm" label-width="100px">
+        <el-row :gutter="20">
+          <el-col :span="12">
+            <el-form-item label="标题名称" required>
+              <el-input v-model="headerForm.title" placeholder="请输入标题名称" />
+            </el-form-item>
+          </el-col>
+          <el-col :span="12">
+            <el-form-item label="副标题" required>
+              <el-input v-model="headerForm.subtitle" placeholder="请输入副标题" />
+            </el-form-item>
+          </el-col>
+        </el-row>
+        <el-row :gutter="20">
+          <el-col :span="12">
+            <el-form-item label="链接文字">
+              <el-input v-model="headerForm.linkText" placeholder="请输入链接文字" />
+            </el-form-item>
+          </el-col>
+          <el-col :span="12">
+            <el-form-item label="链接">
+              <el-input v-model="headerForm.linkUrl" placeholder="请输入链接地址" />
+            </el-form-item>
+          </el-col>
+        </el-row>
+        <el-row :gutter="20">
+          <el-col :span="12">
+            <el-form-item label="背景色">
+              <el-color-picker v-model="headerForm.bgColor" />
+            </el-form-item>
+          </el-col>
+          <el-col :span="12">
+            <el-form-item label="字体颜色">
+              <el-color-picker v-model="headerForm.textColor" />
+            </el-form-item>
+          </el-col>
+        </el-row>
+      </el-form>
+      <template #footer>
+        <el-button type="primary" @click="saveHeaderConfig">确 认</el-button>
+        <el-button @click="headerDialog.visible = false">取 消</el-button>
+      </template>
+    </el-dialog>
+
     <!-- 添加或修改对话框 -->
     <el-dialog v-model="dialog.visible" :title="dialog.title" width="600px" append-to-body @close="cancel">
       <el-form ref="solutionFormRef" :model="form" :rules="rules" label-width="100px">
@@ -83,15 +129,89 @@
 </template>
 
 <script setup name="Solution" lang="ts">
-import { reactive, ref } from 'vue';
+import { reactive, ref, onMounted } from 'vue';
 import { ElMessage, ElMessageBox } from 'element-plus';
 import { Picture } from '@element-plus/icons-vue';
+import { getDecorationSectionByType, addDecorationSection, updateDecorationSection } from '@/api/decoration/section';
 
 const queryParams = reactive({
   pageNum: 1,
   pageSize: 10
 });
 
+// 标题配置
+const headerConfig = ref({
+  id: null as number | null,
+  title: '热门方案',
+  subtitle: '为采购人提供"业务+服务"的项目解决方案',
+  linkText: '更多方案',
+  linkUrl: '',
+  bgColor: '#ffffff',
+  textColor: '#303133'
+});
+
+const headerDialog = reactive({ visible: false });
+const headerForm = reactive({
+  id: null as number | null,
+  title: '',
+  subtitle: '',
+  linkText: '',
+  linkUrl: '',
+  bgColor: '#ffffff',
+  textColor: '#303133'
+});
+
+// 加载标题配置
+const loadHeaderConfig = async () => {
+  try {
+    const res = await getDecorationSectionByType('solution');
+    if (res.rows && res.rows.length > 0) {
+      const data = res.rows[0];
+      headerConfig.value = {
+        id: data.id,
+        title: data.title || '热门方案',
+        subtitle: data.subtitle || '为采购人提供"业务+服务"的项目解决方案',
+        linkText: data.linkText || '更多方案',
+        linkUrl: data.linkUrl || '',
+        bgColor: data.bgColor || '#ffffff',
+        textColor: data.textColor || '#303133'
+      };
+    }
+  } catch (error) {
+    console.error('加载标题配置失败', error);
+  }
+};
+
+const handleEditHeader = () => {
+  Object.assign(headerForm, headerConfig.value);
+  headerDialog.visible = true;
+};
+
+const saveHeaderConfig = async () => {
+  try {
+    const data = {
+      ...headerForm,
+      sectionType: 'solution'
+    };
+    if (headerForm.id) {
+      await updateDecorationSection(data);
+    } else {
+      await addDecorationSection(data);
+    }
+    await loadHeaderConfig();
+    headerDialog.visible = false;
+    ElMessage.success('标题配置已保存');
+  } catch (error) {
+    ElMessage.error('保存失败');
+  }
+};
+
+const handleLinkClick = () => {
+  if (headerConfig.value.linkUrl) {
+    window.open(headerConfig.value.linkUrl, '_blank');
+  }
+};
+
 const form = reactive({
   id: undefined as number | undefined,
   title: '',
@@ -165,9 +285,7 @@ const handleCardClick = (item: any) => {
   console.log('点击方案:', item);
 };
 
-const handleMore = () => {
-  ElMessage.info('更多方案功能开发中...');
-};
+
 
 const cancel = () => {
   dialog.visible = false;
@@ -179,7 +297,10 @@ const reset = () => {
   solutionFormRef.value?.resetFields();
 };
 
-getList();
+onMounted(() => {
+  loadHeaderConfig();
+  getList();
+});
 </script>
 
 <style scoped lang="scss">
@@ -192,8 +313,15 @@ getList();
   justify-content: space-between;
   align-items: center;
   margin-bottom: 20px;
-  padding-bottom: 16px;
-  border-bottom: 1px solid #ebeef5;
+  padding: 12px 16px;
+  background: #f8f9fa;
+  border-radius: 4px;
+  cursor: pointer;
+  transition: background 0.3s;
+
+  &:hover {
+    background: #ebeef5;
+  }
 
   .header-left {
     display: flex;

+ 18 - 1
src/views/platform/gift/carousel/index.vue

@@ -180,6 +180,7 @@ import type { ComponentInternalInstance } from 'vue';
 import type { ElFormInstance } from 'element-plus';
 import dayjs from 'dayjs';
 import { listAdContent, addAdContent, updateAdContent, delAdContent } from '@/api/ad/content';
+import { listByIds } from '@/api/system/oss';
 
 const { proxy } = getCurrentInstance() as ComponentInternalInstance;
 
@@ -242,7 +243,23 @@ const getList = async () => {
     params.endTime = dateRange.value[1];
   }
   const res = await listAdContent(params);
-  carouselList.value = res.rows || [];
+  const rows = res.rows || [];
+  // 把 ossId 转换成真正的图片 URL
+  const ossIds = rows.map((item: any) => item.imageUrl).filter((id: any) => id).join(',');
+  if (ossIds) {
+    try {
+      const ossRes = await listByIds(ossIds);
+      const ossMap = new Map(ossRes.data.map((oss: any) => [String(oss.ossId), oss.url]));
+      rows.forEach((item: any) => {
+        if (item.imageUrl && ossMap.has(String(item.imageUrl))) {
+          item.imageUrl = ossMap.get(String(item.imageUrl));
+        }
+      });
+    } catch (e) {
+      console.error('获取图片URL失败', e);
+    }
+  }
+  carouselList.value = rows;
   total.value = res.total || 0;
   loading.value = false;
 };

+ 20 - 3
src/views/platform/gift/icon/index.vue

@@ -115,9 +115,10 @@
 <script setup name="GiftIcon" lang="ts">
 import { getCurrentInstance, onMounted, reactive, ref } from 'vue';
 import type { ComponentInternalInstance } from 'vue';
-import type { ElFormInstance } from 'element-plus';
+import type { FormInstance } from 'element-plus';
 import dayjs from 'dayjs';
 import { listAdContent, addAdContent, updateAdContent, delAdContent } from '@/api/ad/content';
+import { listByIds } from '@/api/system/oss';
 
 const { proxy } = getCurrentInstance() as ComponentInternalInstance;
 
@@ -136,7 +137,7 @@ const dialog = reactive<DialogOption>({
   title: ''
 });
 
-const iconFormRef = ref<ElFormInstance>();
+const iconFormRef = ref<FormInstance>();
 
 const initFormData = {
   id: undefined as number | undefined,
@@ -166,7 +167,23 @@ const getList = async () => {
     pageSize: queryParams.value.pageSize,
     adType: queryParams.value.adType
   });
-  iconList.value = res.rows || [];
+  const rows = res.rows || [];
+  // 把 ossId 转换成真正的图片 URL
+  const ossIds = rows.map((item: any) => item.imageUrl).filter((id: any) => id).join(',');
+  if (ossIds) {
+    try {
+      const ossRes = await listByIds(ossIds);
+      const ossMap = new Map(ossRes.data.map((oss: any) => [String(oss.ossId), oss.url]));
+      rows.forEach((item: any) => {
+        if (item.imageUrl && ossMap.has(String(item.imageUrl))) {
+          item.imageUrl = ossMap.get(String(item.imageUrl));
+        }
+      });
+    } catch (e) {
+      console.error('获取图片URL失败', e);
+    }
+  }
+  iconList.value = rows;
   total.value = res.total || 0;
   loading.value = false;
 };

+ 18 - 1
src/views/platform/industrial/carousel/index.vue

@@ -147,6 +147,7 @@
 
 <script setup name="IndustrialCarousel" lang="ts">
 import { listAdContent, addAdContent, updateAdContent, delAdContent } from '@/api/ad/content';
+import { listByIds } from '@/api/system/oss';
 import dayjs from 'dayjs';
 
 const { proxy } = getCurrentInstance() as ComponentInternalInstance;
@@ -209,7 +210,23 @@ const getList = async () => {
     params.endTime = dateRange.value[1];
   }
   const res = await listAdContent(params);
-  carouselList.value = res.rows || [];
+  const rows = res.rows || [];
+  // 把 ossId 转换成真正的图片 URL
+  const ossIds = rows.map((item: any) => item.imageUrl).filter((id: any) => id).join(',');
+  if (ossIds) {
+    try {
+      const ossRes = await listByIds(ossIds);
+      const ossMap = new Map(ossRes.data.map((oss: any) => [String(oss.ossId), oss.url]));
+      rows.forEach((item: any) => {
+        if (item.imageUrl && ossMap.has(String(item.imageUrl))) {
+          item.imageUrl = ossMap.get(String(item.imageUrl));
+        }
+      });
+    } catch (e) {
+      console.error('获取图片URL失败', e);
+    }
+  }
+  carouselList.value = rows;
   total.value = res.total || 0;
   loading.value = false;
 };