林小张 1 день назад
Родитель
Сommit
5669e1b118

+ 30 - 0
src/api/ad/content.ts

@@ -0,0 +1,30 @@
+import request from '@/utils/request';
+
+// 广告内容统一接口
+const baseUrl = '/system/adContent';
+
+// 列表查询
+export function listAdContent(params: any) {
+  return request.get(baseUrl + '/list', { params });
+}
+
+// 详情
+export function getAdContent(id: number) {
+  return request.get(`${baseUrl}/${id}`);
+}
+
+// 新增
+export function addAdContent(data: any) {
+  return request.post(baseUrl, data);
+}
+
+// 修改
+export function updateAdContent(data: any) {
+  return request.put(baseUrl, data);
+}
+
+// 删除
+export function delAdContent(id: number | string) {
+  return request.delete(`${baseUrl}/${id}`);
+}
+

+ 60 - 0
src/router/index.ts

@@ -88,6 +88,66 @@ export const constantRoutes: RouteRecordRaw[] = [
         meta: { title: '个人中心', icon: 'user' }
       }
     ]
+  },
+  {
+    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' }
+      }
+    ]
   }
 ];
 

+ 228 - 0
src/views/platform/decoration/brand/index.vue

@@ -0,0 +1,228 @@
+<template>
+  <div class="brand-container">
+    <el-card shadow="hover">
+      <!-- 操作栏 -->
+      <div class="action-bar mb-[16px]">
+        <el-button type="primary" plain icon="Plus" @click="handleAdd">添加品牌</el-button>
+        <el-button icon="Refresh" @click="getList">刷新</el-button>
+      </div>
+
+      <!-- 品牌卡片网格 -->
+      <div class="brand-grid">
+        <div v-for="item in brandList" :key="item.id" class="brand-card">
+          <el-image :src="item.imageUrl" fit="cover" lazy class="brand-image">
+            <template #error>
+              <div class="image-slot">
+                <el-icon><Picture /></el-icon>
+              </div>
+            </template>
+          </el-image>
+          <!-- 悬浮操作 -->
+          <div class="card-overlay">
+            <el-button type="primary" size="small" icon="Edit" @click="handleUpdate(item)">编辑</el-button>
+            <el-button type="danger" size="small" icon="Delete" @click="handleDelete(item)">删除</el-button>
+          </div>
+        </div>
+      </div>
+
+      <!-- 空状态 -->
+      <el-empty v-if="!loading && brandList.length === 0" description="暂无品牌数据" />
+
+      <!-- 分页 -->
+      <pagination
+        v-show="total > 0"
+        v-model:page="queryParams.pageNum"
+        v-model:limit="queryParams.pageSize"
+        :total="total"
+        @pagination="getList"
+      />
+    </el-card>
+
+    <!-- 添加或修改对话框 -->
+    <el-dialog v-model="dialog.visible" :title="dialog.title" width="500px" append-to-body @close="cancel">
+      <el-form ref="brandFormRef" :model="form" :rules="rules" label-width="100px">
+        <el-form-item label="品牌名称" prop="title">
+          <el-input v-model="form.title" placeholder="请输入品牌名称" />
+        </el-form-item>
+        <el-form-item label="品牌图片" prop="imageUrl">
+          <image-upload v-model="form.imageUrl" :limit="1" />
+          <div class="upload-tip">建议尺寸:400x300,支持 jpg/png 格式</div>
+        </el-form-item>
+        <el-form-item label="跳转链接" prop="link">
+          <el-input v-model="form.link" placeholder="请输入跳转链接" />
+        </el-form-item>
+        <el-form-item label="排序" prop="sort">
+          <el-input-number v-model="form.sort" :min="0" controls-position="right" />
+        </el-form-item>
+      </el-form>
+      <template #footer>
+        <el-button type="primary" @click="submitForm">确 定</el-button>
+        <el-button @click="cancel">取 消</el-button>
+      </template>
+    </el-dialog>
+  </div>
+</template>
+
+<script setup name="Brand" lang="ts">
+import { reactive, ref } from 'vue';
+import { ElMessage, ElMessageBox } from 'element-plus';
+import { Picture } from '@element-plus/icons-vue';
+
+const queryParams = reactive({ pageNum: 1, pageSize: 10 });
+const form = reactive({
+  id: undefined as number | undefined,
+  title: '',
+  imageUrl: '',
+  link: '',
+  sort: 0
+});
+const rules = {
+  title: [{ required: true, message: '品牌名称不能为空', trigger: 'blur' }],
+  imageUrl: [{ required: true, message: '品牌图片不能为空', trigger: 'change' }]
+};
+
+const dialog = reactive({ visible: false, title: '' });
+const loading = ref(false);
+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 = () => {
+  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;
+    loading.value = false;
+  }, 300);
+};
+
+const handleAdd = () => {
+  reset();
+  dialog.visible = true;
+  dialog.title = '添加品牌';
+};
+
+const handleUpdate = (row: any) => {
+  reset();
+  Object.assign(form, row);
+  dialog.visible = true;
+  dialog.title = '修改品牌';
+};
+
+const submitForm = () => {
+  brandFormRef.value?.validate((valid: boolean) => {
+    if (valid) {
+      ElMessage.success(form.id ? '修改成功' : '添加成功');
+      dialog.visible = false;
+      getList();
+    }
+  });
+};
+
+const handleDelete = (row: any) => {
+  ElMessageBox.confirm('是否确认删除品牌"' + row.title + '"?').then(() => {
+    ElMessage.success('删除成功');
+    getList();
+  }).catch(() => {});
+};
+
+const cancel = () => {
+  dialog.visible = false;
+  reset();
+};
+
+const reset = () => {
+  Object.assign(form, { id: undefined, title: '', imageUrl: '', link: '', sort: 0 });
+  brandFormRef.value?.resetFields();
+};
+
+getList();
+</script>
+
+<style scoped lang="scss">
+.brand-container {
+  height: 100%;
+}
+
+.action-bar {
+  display: flex;
+  gap: 10px;
+}
+
+.brand-grid {
+  display: grid;
+  grid-template-columns: repeat(5, 1fr);
+  gap: 16px;
+  margin-bottom: 20px;
+
+  @media (max-width: 1400px) {
+    grid-template-columns: repeat(4, 1fr);
+  }
+  @media (max-width: 1100px) {
+    grid-template-columns: repeat(3, 1fr);
+  }
+  @media (max-width: 800px) {
+    grid-template-columns: repeat(2, 1fr);
+  }
+}
+
+.brand-card {
+  position: relative;
+  border-radius: 8px;
+  overflow: hidden;
+  cursor: pointer;
+  aspect-ratio: 4 / 3;
+
+  &:hover .card-overlay {
+    opacity: 1;
+  }
+
+  .brand-image {
+    width: 100%;
+    height: 100%;
+  }
+
+  .image-slot {
+    display: flex;
+    align-items: center;
+    justify-content: center;
+    width: 100%;
+    height: 100%;
+    background: #f5f7fa;
+    color: #c0c4cc;
+    font-size: 40px;
+  }
+
+  .card-overlay {
+    position: absolute;
+    top: 0;
+    left: 0;
+    right: 0;
+    bottom: 0;
+    background: rgba(0, 0, 0, 0.5);
+    display: flex;
+    align-items: center;
+    justify-content: center;
+    gap: 10px;
+    opacity: 0;
+    transition: opacity 0.3s;
+  }
+}
+
+.upload-tip {
+  font-size: 12px;
+  color: #909399;
+  margin-top: 8px;
+}
+</style>

+ 313 - 0
src/views/platform/decoration/carousel/index.vue

@@ -0,0 +1,313 @@
+<template>
+  <div class="carousel-container">
+    <el-card shadow="hover">
+      <!-- 搜索区域 -->
+      <transition
+        :enter-active-class="proxy?.animate.searchAnimate.enter"
+        :leave-active-class="proxy?.animate.searchAnimate.leave"
+      >
+        <div v-show="showSearch" class="mb-[10px]">
+          <el-card shadow="hover">
+            <el-form ref="queryFormRef" :model="queryParams" :inline="true" label-width="100px">
+              <el-form-item label="广告名称" prop="adName">
+                <el-input
+                  v-model="queryParams.adName"
+                  placeholder="请输入广告名称"
+                  clearable
+                  @keyup.enter="handleQuery"
+                />
+              </el-form-item>
+              <el-form-item label="起始时间" prop="dateRange">
+                <el-date-picker
+                  v-model="dateRange"
+                  type="daterange"
+                  range-separator="-"
+                  start-placeholder="开始时间"
+                  end-placeholder="结束时间"
+                  value-format="YYYY-MM-DD"
+                  style="width: 240px"
+                />
+              </el-form-item>
+              <el-form-item>
+                <el-button type="primary" icon="Search" @click="handleQuery">搜索</el-button>
+                <el-button icon="Refresh" @click="resetQuery">重置</el-button>
+                <el-button type="primary" plain icon="Plus" @click="handleAdd">添加广告</el-button>
+              </el-form-item>
+            </el-form>
+          </el-card>
+        </div>
+      </transition>
+
+      <!-- 操作栏 -->
+      <el-row :gutter="10" class="mb8">
+        <right-toolbar v-model:show-search="showSearch" @query-table="getList"></right-toolbar>
+      </el-row>
+
+      <!-- 表格标题 -->
+      <div class="table-title mb-[10px]">
+        <span>轮播广告信息列表</span>
+      </div>
+
+      <!-- 表格 -->
+      <el-table v-loading="loading" :data="carouselList" border>
+        <el-table-column label="广告图片" align="center" width="120">
+          <template #default="scope">
+            <el-image
+              :src="scope.row.imageUrl"
+              :preview-src-list="[scope.row.imageUrl]"
+              fit="cover"
+              style="width: 80px; height: 60px; border-radius: 4px"
+              lazy
+            />
+          </template>
+        </el-table-column>
+        <el-table-column label="广告名称" align="center" prop="title" :show-overflow-tooltip="true" min-width="200" />
+        <el-table-column label="时间" align="center" width="280">
+          <template #default="scope">
+            <div>开始时间:{{ scope.row.startTime }}</div>
+            <div>到期时间:{{ scope.row.endTime }}</div>
+          </template>
+        </el-table-column>
+        <el-table-column label="上线/下线" align="center" width="100">
+          <template #default="scope">
+            <el-tag :type="scope.row.status === 1 ? 'success' : 'danger'">
+              {{ scope.row.status === 1 ? '上线' : '下线' }}
+            </el-tag>
+          </template>
+        </el-table-column>
+        <el-table-column label="排序" align="center" prop="sort" width="80" />
+        <el-table-column label="链接" align="center" prop="link" :show-overflow-tooltip="true" min-width="200" />
+        <el-table-column label="操作" align="center" width="150" class-name="small-padding fixed-width">
+          <template #default="scope">
+            <el-button link type="primary" icon="Edit" @click="handleUpdate(scope.row)">编辑</el-button>
+            <el-button link type="danger" icon="Delete" @click="handleDelete(scope.row)">删除</el-button>
+          </template>
+        </el-table-column>
+      </el-table>
+
+      <!-- 分页 -->
+      <pagination
+        v-show="total > 0"
+        v-model:page="queryParams.pageNum"
+        v-model:limit="queryParams.pageSize"
+        :total="total"
+        @pagination="getList"
+      />
+    </el-card>
+
+    <!-- 添加或修改对话框 -->
+    <el-dialog v-model="dialog.visible" :title="dialog.title" width="600px" append-to-body @close="cancel">
+      <el-form ref="carouselFormRef" :model="form" :rules="rules" label-width="100px">
+        <el-form-item label="广告名称" prop="title">
+          <el-input v-model="form.title" placeholder="请输入广告名称" />
+        </el-form-item>
+        <el-form-item label="广告图片" prop="imageUrl">
+          <image-upload v-model="form.imageUrl" :limit="1" />
+        </el-form-item>
+        <el-form-item label="开始时间" prop="startTime">
+          <el-date-picker
+            v-model="form.startTime"
+            type="date"
+            placeholder="选择开始时间"
+            value-format="YYYY-MM-DD"
+            style="width: 100%"
+          />
+        </el-form-item>
+        <el-form-item label="结束时间" prop="endTime">
+          <el-date-picker
+            v-model="form.endTime"
+            type="date"
+            placeholder="选择结束时间"
+            value-format="YYYY-MM-DD"
+            style="width: 100%"
+          />
+        </el-form-item>
+        <el-form-item label="链接地址" prop="link">
+          <el-input v-model="form.link" placeholder="请输入链接地址" />
+        </el-form-item>
+        <el-form-item label="排序" prop="sort">
+          <el-input-number v-model="form.sort" controls-position="right" :min="0" />
+        </el-form-item>
+        <el-form-item label="状态" prop="status">
+          <el-radio-group v-model="form.status">
+            <el-radio :value="1">上线</el-radio>
+            <el-radio :value="0">下线</el-radio>
+          </el-radio-group>
+        </el-form-item>
+      </el-form>
+      <template #footer>
+        <div class="dialog-footer">
+          <el-button type="primary" @click="submitForm">确 定</el-button>
+          <el-button @click="cancel">取 消</el-button>
+        </div>
+      </template>
+    </el-dialog>
+  </div>
+</template>
+
+<script setup name="DecorationCarousel" lang="ts">
+import { listAdContent, addAdContent, updateAdContent, delAdContent } from '@/api/ad/content';
+import dayjs from 'dayjs';
+
+const { proxy } = getCurrentInstance() as ComponentInternalInstance;
+
+const carouselList = ref<any[]>([]);
+const loading = ref(false);
+const total = ref(0);
+const showSearch = ref(true);
+const dateRange = ref<[string, string]>(['', '']);
+
+const queryParams = ref({
+  pageNum: 1,
+  pageSize: 10,
+  adName: '',
+  startTime: '',
+  endTime: ''
+});
+
+const dialog = reactive<DialogOption>({
+  visible: false,
+  title: ''
+});
+
+const carouselFormRef = ref<ElFormInstance>();
+
+const initFormData = {
+  id: undefined as number | undefined,
+  adType: 'decoration_banner',
+  title: '',
+  imageUrl: '',
+  startTime: '',
+  endTime: '',
+  link: '',
+  color: '#ffffff',
+  remark: '',
+  extJson: '{}',
+  sort: 0,
+  status: 1
+};
+
+const form = ref({ ...initFormData });
+
+const rules = reactive({
+  title: [{ required: true, message: '广告名称不能为空', trigger: 'blur' }],
+  startTime: [{ required: true, message: '开始时间不能为空', trigger: 'change' }],
+  endTime: [{ required: true, message: '结束时间不能为空', trigger: 'change' }]
+});
+
+/** 查询列表 */
+const getList = async () => {
+  loading.value = true;
+  const params: any = {
+    pageNum: queryParams.value.pageNum,
+    pageSize: queryParams.value.pageSize,
+    title: queryParams.value.adName,
+    adType: 'decoration_banner'
+  };
+  if (dateRange.value && dateRange.value[0] && dateRange.value[1]) {
+    params.startTime = dateRange.value[0];
+    params.endTime = dateRange.value[1];
+  }
+  const res = await listAdContent(params);
+  carouselList.value = res.rows || [];
+  total.value = res.total || 0;
+  loading.value = false;
+};
+
+/** 搜索按钮操作 */
+const handleQuery = () => {
+  queryParams.value.pageNum = 1;
+  getList();
+};
+
+/** 重置按钮操作 */
+const resetQuery = () => {
+  dateRange.value = ['', ''];
+  queryParams.value.adName = '';
+  queryParams.value.startTime = '';
+  queryParams.value.endTime = '';
+  handleQuery();
+};
+
+/** 取消按钮 */
+const cancel = () => {
+  reset();
+  dialog.visible = false;
+};
+
+/** 表单重置 */
+const reset = () => {
+  form.value = { ...initFormData };
+  carouselFormRef.value?.resetFields();
+};
+
+/** 新增按钮操作 */
+const handleAdd = () => {
+  reset();
+  const today = dayjs().format('YYYY-MM-DD');
+  const future = dayjs().add(30, 'day').format('YYYY-MM-DD');
+  form.value.startTime = today;
+  form.value.endTime = future;
+  dialog.visible = true;
+  dialog.title = '添加广告';
+};
+
+/** 修改按钮操作 */
+const handleUpdate = (row: any) => {
+  reset();
+  form.value = {
+    ...row,
+    adType: 'decoration_banner',
+    status: row.status ?? 1,
+    color: row.color || '#ffffff',
+    remark: row.remark || '',
+    extJson: row.extJson || '{}',
+    startTime: row.startTime ? dayjs(row.startTime).format('YYYY-MM-DD') : '',
+    endTime: row.endTime ? dayjs(row.endTime).format('YYYY-MM-DD') : ''
+  };
+  dialog.visible = true;
+  dialog.title = '修改广告';
+};
+
+/** 提交按钮 */
+const submitForm = () => {
+  carouselFormRef.value?.validate((valid: boolean) => {
+    if (valid) {
+      const api = form.value.id ? updateAdContent : addAdContent;
+      api(form.value).then(() => {
+        proxy?.$modal.msgSuccess(form.value.id ? '修改成功' : '新增成功');
+        dialog.visible = false;
+        getList();
+      });
+    }
+  });
+};
+
+/** 删除按钮操作 */
+const handleDelete = (row: any) => {
+  proxy?.$modal.confirm('是否确认删除广告名称为"' + row.title + '"的数据项?').then(() => {
+    delAdContent(row.id).then(() => {
+      proxy?.$modal.msgSuccess('删除成功');
+      getList();
+    });
+  });
+};
+
+onMounted(() => {
+  getList();
+});
+</script>
+
+<style scoped lang="scss">
+.carousel-container {
+  height: 100%;
+}
+
+.table-title {
+  font-size: 16px;
+  font-weight: 600;
+  color: #303133;
+  padding: 0 4px;
+}
+</style>
+

+ 331 - 0
src/views/platform/decoration/flashSale/index.vue

@@ -0,0 +1,331 @@
+<template>
+  <div class="flash-sale-container">
+    <el-card shadow="hover">
+      <!-- 标题区域(可点击编辑) -->
+      <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>
+
+      <!-- 品牌展示区域 -->
+      <div class="brand-showcase">
+        <!-- 左侧大图 -->
+        <div class="showcase-left">
+          <el-image :src="featuredImage" 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>
+
+        <!-- 右侧品牌网格 -->
+        <div class="showcase-right">
+          <div v-for="item in brandList" :key="item.id" class="brand-item">
+            <el-icon class="close-icon" @click="handleRemove(item)"><Close /></el-icon>
+            <div class="brand-logo">
+              <el-image :src="item.logoUrl" fit="contain" class="logo-image">
+                <template #error>
+                  <span class="logo-text">{{ item.title }}</span>
+                </template>
+              </el-image>
+            </div>
+            <div class="brand-info">
+              <p class="brand-name">{{ item.title }}</p>
+              <p class="brand-desc">{{ item.description }}</p>
+            </div>
+          </div>
+        </div>
+      </div>
+
+      <!-- 搜索区域 -->
+      <div class="search-section">
+        <h3 class="search-title">品牌闪购搜索列表</h3>
+        <div class="search-bar">
+          <el-input v-model="searchKeyword" placeholder="请输入品牌编号或名称" clearable style="width: 300px" @keyup.enter="handleSearch" />
+          <el-button type="primary" @click="handleSearch">搜索</el-button>
+        </div>
+      </div>
+    </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.link" 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="FlashSale" lang="ts">
+import { ref, reactive } from 'vue';
+import { ElMessage } from 'element-plus';
+import { Picture, Close } from '@element-plus/icons-vue';
+
+const searchKeyword = ref('');
+const featuredImage = ref('https://via.placeholder.com/300x400/1a1a2e/FFFFFF?text=Featured');
+
+// 标题配置
+const headerConfig = ref({
+  title: '大牌推荐',
+  subtitle: '甄选大牌,优质好品',
+  linkText: '查看更多品牌信息',
+  link: 'https://www.yoe365.com/brand',
+  bgColor: '#ffffff',
+  textColor: '#303133'
+});
+
+const headerDialog = reactive({ visible: false });
+const headerForm = reactive({
+  title: '',
+  subtitle: '',
+  linkText: '',
+  link: '',
+  bgColor: '#ffffff',
+  textColor: '#303133'
+});
+
+const handleEditHeader = () => {
+  Object.assign(headerForm, headerConfig.value);
+  headerDialog.visible = true;
+};
+
+const saveHeaderConfig = () => {
+  Object.assign(headerConfig.value, headerForm);
+  headerDialog.visible = false;
+  ElMessage.success('标题配置已保存');
+};
+
+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' },
+  { id: 3, title: '奔图', description: '奔图', logoUrl: 'https://via.placeholder.com/120x60/FFFFFF/333333?text=PANTUM' },
+  { id: 4, title: 'MAXHUB', description: 'MAXHUB', logoUrl: 'https://via.placeholder.com/120x60/FFFFFF/333333?text=MAXHUB' },
+  { id: 5, title: '苹果', description: '苹果', logoUrl: 'https://via.placeholder.com/120x60/FFFFFF/333333?text=Apple' },
+  { id: 6, title: '飞利浦', description: '飞利浦', logoUrl: 'https://via.placeholder.com/120x60/FFFFFF/333333?text=PHILIPS' },
+  { id: 7, title: '美的', description: '美的', logoUrl: 'https://via.placeholder.com/120x60/FFFFFF/333333?text=Midea' },
+  { id: 8, title: '格力', description: '<p><br></p>', logoUrl: 'https://via.placeholder.com/120x60/FFFFFF/333333?text=GREE' }
+]);
+
+const handleViewAll = () => {
+  ElMessage.info('查看全部功能开发中...');
+};
+
+const handleRemove = (item: any) => {
+  ElMessage.success(`已移除品牌: ${item.title}`);
+};
+
+const handleSearch = () => {
+  ElMessage.info(`搜索: ${searchKeyword.value || '全部'}`);
+};
+
+const handleLinkClick = () => {
+  if (headerConfig.value.link) {
+    window.open(headerConfig.value.link, '_blank');
+  }
+};
+</script>
+
+<style scoped lang="scss">
+.flash-sale-container {
+  height: 100%;
+}
+
+.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;
+    }
+  }
+}
+
+.brand-showcase {
+  display: flex;
+  gap: 16px;
+  margin-bottom: 30px;
+  padding: 16px;
+  background: #f8f9fa;
+  border-radius: 8px;
+}
+
+.showcase-left {
+  position: relative;
+  width: 200px;
+  flex-shrink: 0;
+
+  .featured-image {
+    width: 100%;
+    height: 280px;
+    border-radius: 8px;
+  }
+
+  .view-all-btn {
+    position: absolute;
+    bottom: 20px;
+    left: 50%;
+    transform: translateX(-50%);
+  }
+
+  .image-slot {
+    display: flex;
+    align-items: center;
+    justify-content: center;
+    width: 100%;
+    height: 100%;
+    background: #e0e0e0;
+    color: #999;
+    font-size: 40px;
+  }
+}
+
+.showcase-right {
+  flex: 1;
+  display: grid;
+  grid-template-columns: repeat(4, 1fr);
+  gap: 12px;
+
+  @media (max-width: 1200px) {
+    grid-template-columns: repeat(3, 1fr);
+  }
+}
+
+.brand-item {
+  position: relative;
+  background: #fff;
+  border: 1px solid #e4e7ed;
+  border-radius: 8px;
+  padding: 16px;
+  text-align: center;
+  transition: all 0.3s;
+
+  &:hover {
+    box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
+    .close-icon { opacity: 1; }
+  }
+
+  .close-icon {
+    position: absolute;
+    top: 8px;
+    right: 8px;
+    font-size: 16px;
+    color: #909399;
+    cursor: pointer;
+    opacity: 0;
+    transition: opacity 0.3s;
+
+    &:hover { color: #f56c6c; }
+  }
+
+  .brand-logo {
+    height: 50px;
+    display: flex;
+    align-items: center;
+    justify-content: center;
+    margin-bottom: 12px;
+
+    .logo-image { max-height: 100%; max-width: 100%; }
+    .logo-text { font-size: 18px; font-weight: 600; color: #303133; }
+  }
+
+  .brand-info {
+    .brand-name {
+      margin: 0 0 4px 0;
+      font-size: 14px;
+      font-weight: 500;
+      color: #303133;
+    }
+    .brand-desc {
+      margin: 0;
+      font-size: 12px;
+      color: #909399;
+    }
+  }
+}
+
+.search-section {
+  padding: 20px;
+  background: #f8f9fa;
+  border-radius: 8px;
+
+  .search-title {
+    margin: 0 0 16px 0;
+    font-size: 16px;
+    font-weight: 600;
+    color: #303133;
+  }
+
+  .search-bar {
+    display: flex;
+    gap: 12px;
+  }
+}
+</style>

+ 18 - 0
src/views/platform/decoration/fresh/index.vue

@@ -0,0 +1,18 @@
+<template>
+  <div class="fresh-container">
+    <el-card shadow="hover">
+      <el-empty description="新鲜好物页面开发中..." />
+    </el-card>
+  </div>
+</template>
+
+<script setup name="Fresh" lang="ts">
+// 新鲜好物页面
+</script>
+
+<style scoped lang="scss">
+.fresh-container {
+  height: 100%;
+}
+</style>
+

+ 171 - 0
src/views/platform/decoration/index.vue

@@ -0,0 +1,171 @@
+<template>
+  <div class="decoration-container">
+    <el-row :gutter="20" class="decoration-row">
+      <!-- 左侧导航菜单 -->
+      <el-col :span="isCollapse ? 1 : 4" class="sidebar-col">
+        <el-card shadow="hover" class="sidebar-card">
+          <div class="sidebar-header">
+            <h3>平台装修</h3>
+            <el-button
+              link
+              :icon="isCollapse ? 'ArrowRight' : 'ArrowLeft'"
+              @click="toggleCollapse"
+              class="collapse-btn"
+            />
+          </div>
+          <div v-show="!isCollapse" class="menu-list">
+            <div
+              v-for="item in menuList"
+              :key="item.key"
+              class="menu-item"
+              :class="{ active: activeMenu === item.key }"
+              @click="handleMenuClick(item.key)"
+            >
+              <el-icon class="menu-icon">
+                <component :is="item.icon" />
+              </el-icon>
+              <span class="menu-text">{{ item.label }}</span>
+            </div>
+          </div>
+        </el-card>
+      </el-col>
+
+      <!-- 右侧内容区 -->
+      <el-col :span="isCollapse ? 23 : 20" class="content-col">
+        <component :is="currentComponent" />
+      </el-col>
+    </el-row>
+  </div>
+</template>
+
+<script setup name="PlatformDecoration" lang="ts">
+import { ref, shallowRef, markRaw } from 'vue';
+import { Picture, Document, Shop, Star, Lightning } from '@element-plus/icons-vue';
+import Carousel from './carousel/index.vue';
+import Solution from './solution/index.vue';
+import Brand from './brand/index.vue';
+import Fresh from './fresh/index.vue';
+import FlashSale from './flashSale/index.vue';
+
+const isCollapse = ref(false);
+const activeMenu = ref('carousel');
+
+// 菜单列表
+const menuList = [
+  { key: 'carousel', label: '轮播广告', icon: Picture },
+  { key: 'solution', label: '方案管理', icon: Document },
+  { key: 'brand', label: '品牌中心', icon: Shop },
+  { key: 'fresh', label: '新鲜好物', icon: Star },
+  { key: 'flashSale', label: '品牌闪购', icon: Lightning }
+];
+
+// 组件映射
+const componentMap = {
+  carousel: markRaw(Carousel),
+  solution: markRaw(Solution),
+  brand: markRaw(Brand),
+  fresh: markRaw(Fresh),
+  flashSale: markRaw(FlashSale)
+};
+
+const currentComponent = shallowRef(componentMap.carousel);
+
+// 切换折叠状态
+const toggleCollapse = () => {
+  isCollapse.value = !isCollapse.value;
+};
+
+// 菜单点击
+const handleMenuClick = (key: string) => {
+  activeMenu.value = key;
+  currentComponent.value = componentMap[key as keyof typeof componentMap];
+};
+</script>
+
+<style scoped lang="scss">
+.decoration-container {
+  padding: 20px;
+  height: calc(100vh - 84px);
+}
+
+.decoration-row {
+  height: 100%;
+}
+
+.sidebar-col {
+  transition: all 0.3s;
+}
+
+.sidebar-card {
+  height: 100%;
+  :deep(.el-card__body) {
+    padding: 0;
+    height: 100%;
+    display: flex;
+    flex-direction: column;
+  }
+}
+
+.sidebar-header {
+  display: flex;
+  justify-content: space-between;
+  align-items: center;
+  padding: 16px 20px;
+  border-bottom: 1px solid #ebeef5;
+  background: #fafafa;
+
+  h3 {
+    margin: 0;
+    font-size: 16px;
+    font-weight: 600;
+    color: #303133;
+  }
+
+  .collapse-btn {
+    padding: 0;
+    font-size: 16px;
+  }
+}
+
+.menu-list {
+  flex: 1;
+  overflow-y: auto;
+  padding: 10px;
+}
+
+.menu-item {
+  display: flex;
+  align-items: center;
+  padding: 12px 15px;
+  border-radius: 6px;
+  cursor: pointer;
+  margin-bottom: 8px;
+  transition: all 0.3s;
+  color: #606266;
+
+  &:hover {
+    background: #f5f7fa;
+  }
+
+  &.active {
+    background: #e6f4ff;
+    color: #409eff;
+    font-weight: 500;
+  }
+
+  .menu-icon {
+    font-size: 18px;
+    margin-right: 10px;
+  }
+
+  .menu-text {
+    font-size: 14px;
+  }
+}
+
+.content-col {
+  height: 100%;
+  overflow: hidden;
+}
+</style>
+

+ 298 - 0
src/views/platform/decoration/solution/index.vue

@@ -0,0 +1,298 @@
+<template>
+  <div class="solution-container">
+    <el-card shadow="hover">
+      <!-- 标题区域 -->
+      <div class="solution-header">
+        <div class="header-left">
+          <h2 class="solution-title">热门方案</h2>
+          <span class="solution-subtitle">为采购人提供"业务+服务"的项目解决方案</span>
+        </div>
+        <div class="header-right">
+          <el-link type="primary" :underline="false" @click="handleMore">更多方案></el-link>
+        </div>
+      </div>
+
+      <!-- 方案卡片网格 -->
+      <div class="solution-grid">
+        <div
+          v-for="item in solutionList"
+          :key="item.id"
+          class="solution-card"
+          @click="handleCardClick(item)"
+        >
+          <!-- 卡片上半部分:标题和描述 -->
+          <div class="card-header">
+            <h3 class="card-title">{{ item.title }}</h3>
+            <p class="card-description">{{ item.description }}</p>
+          </div>
+          <!-- 卡片下半部分:图片 -->
+          <div class="card-image">
+            <el-image :src="item.imageUrl" fit="contain" lazy class="solution-image">
+              <template #error>
+                <div class="image-slot">
+                  <el-icon><Picture /></el-icon>
+                </div>
+              </template>
+            </el-image>
+          </div>
+          <!-- 操作按钮 -->
+          <div class="card-actions">
+            <el-button link type="primary" icon="Edit" @click.stop="handleUpdate(item)">编辑</el-button>
+            <el-button link type="danger" icon="Delete" @click.stop="handleDelete(item)">删除</el-button>
+          </div>
+        </div>
+      </div>
+
+      <!-- 空状态 -->
+      <el-empty v-if="!loading && solutionList.length === 0" description="暂无方案数据" />
+
+      <!-- 分页 -->
+      <pagination
+        v-show="total > 0"
+        v-model:page="queryParams.pageNum"
+        v-model:limit="queryParams.pageSize"
+        :total="total"
+        @pagination="getList"
+      />
+    </el-card>
+
+    <!-- 添加或修改对话框 -->
+    <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">
+        <el-form-item label="方案标题" prop="title">
+          <el-input v-model="form.title" placeholder="请输入方案标题" />
+        </el-form-item>
+        <el-form-item label="方案描述" prop="description">
+          <el-input v-model="form.description" type="textarea" :rows="3" placeholder="请输入方案描述" />
+        </el-form-item>
+        <el-form-item label="方案图片" prop="imageUrl">
+          <image-upload v-model="form.imageUrl" :limit="1" />
+        </el-form-item>
+        <el-form-item label="排序" prop="sort">
+          <el-input-number v-model="form.sort" :min="0" controls-position="right" />
+        </el-form-item>
+      </el-form>
+      <template #footer>
+        <div class="dialog-footer">
+          <el-button type="primary" @click="submitForm">确 定</el-button>
+          <el-button @click="cancel">取 消</el-button>
+        </div>
+      </template>
+    </el-dialog>
+  </div>
+</template>
+
+<script setup name="Solution" lang="ts">
+import { reactive, ref } from 'vue';
+import { ElMessage, ElMessageBox } from 'element-plus';
+import { Picture } from '@element-plus/icons-vue';
+
+const queryParams = reactive({
+  pageNum: 1,
+  pageSize: 10
+});
+
+const form = reactive({
+  id: undefined as number | undefined,
+  title: '',
+  description: '',
+  imageUrl: '',
+  sort: 0
+});
+
+const rules = {
+  title: [{ required: true, message: '方案标题不能为空', trigger: 'blur' }],
+  description: [{ required: true, message: '方案描述不能为空', trigger: 'blur' }]
+};
+
+const dialog = reactive({ visible: false, title: '' });
+const loading = ref(false);
+const total = ref(0);
+const solutionList = ref<any[]>([]);
+const solutionFormRef = ref();
+
+// 模拟数据
+const mockData = [
+  { id: 1, title: '50档/50-100档', description: '对公业务伴手礼', imageUrl: 'https://via.placeholder.com/200x150/F5F5F5/333333?text=耳机', sort: 1 },
+  { id: 2, title: '100-200元档', description: '对公业务伴手礼', imageUrl: 'https://via.placeholder.com/200x150/F5F5F5/333333?text=鼠标', sort: 2 },
+  { id: 3, title: '200-500元档', description: '对公业务伴手礼', imageUrl: 'https://via.placeholder.com/200x150/F5F5F5/333333?text=手表', sort: 3 },
+  { id: 4, title: '500-1000元档', description: '对公业务伴手礼', imageUrl: 'https://via.placeholder.com/200x150/F5F5F5/333333?text=耳机2', sort: 4 },
+  { id: 5, title: '开门红电器', description: '对公业务伴手礼', imageUrl: 'https://via.placeholder.com/200x150/F5F5F5/333333?text=电饭煲', sort: 5 }
+];
+
+const getList = () => {
+  loading.value = true;
+  setTimeout(() => {
+    const start = (queryParams.pageNum - 1) * queryParams.pageSize;
+    const end = start + queryParams.pageSize;
+    solutionList.value = mockData.slice(start, end);
+    total.value = mockData.length;
+    loading.value = false;
+  }, 300);
+};
+
+const handleAdd = () => {
+  reset();
+  dialog.visible = true;
+  dialog.title = '添加方案';
+};
+
+const handleUpdate = (row: any) => {
+  reset();
+  Object.assign(form, row);
+  dialog.visible = true;
+  dialog.title = '修改方案';
+};
+
+const submitForm = () => {
+  solutionFormRef.value?.validate((valid: boolean) => {
+    if (valid) {
+      ElMessage.success(form.id ? '修改成功' : '添加成功');
+      dialog.visible = false;
+      getList();
+    }
+  });
+};
+
+const handleDelete = (row: any) => {
+  ElMessageBox.confirm('是否确认删除方案"' + row.title + '"?').then(() => {
+    ElMessage.success('删除成功');
+    getList();
+  }).catch(() => {});
+};
+
+const handleCardClick = (item: any) => {
+  console.log('点击方案:', item);
+};
+
+const handleMore = () => {
+  ElMessage.info('更多方案功能开发中...');
+};
+
+const cancel = () => {
+  dialog.visible = false;
+  reset();
+};
+
+const reset = () => {
+  Object.assign(form, { id: undefined, title: '', description: '', imageUrl: '', sort: 0 });
+  solutionFormRef.value?.resetFields();
+};
+
+getList();
+</script>
+
+<style scoped lang="scss">
+.solution-container {
+  height: 100%;
+}
+
+.solution-header {
+  display: flex;
+  justify-content: space-between;
+  align-items: center;
+  margin-bottom: 20px;
+  padding-bottom: 16px;
+  border-bottom: 1px solid #ebeef5;
+
+  .header-left {
+    display: flex;
+    align-items: baseline;
+    gap: 16px;
+
+    .solution-title {
+      margin: 0;
+      font-size: 20px;
+      font-weight: 600;
+      color: #303133;
+    }
+
+    .solution-subtitle {
+      font-size: 14px;
+      color: #909399;
+    }
+  }
+}
+
+.solution-grid {
+  display: grid;
+  grid-template-columns: repeat(4, 1fr);
+  gap: 16px;
+  margin-bottom: 20px;
+
+  @media (max-width: 1400px) {
+    grid-template-columns: repeat(3, 1fr);
+  }
+
+  @media (max-width: 1000px) {
+    grid-template-columns: repeat(2, 1fr);
+  }
+}
+
+.solution-card {
+  background: #f8f9fa;
+  border: 1px solid #e4e7ed;
+  border-radius: 8px;
+  padding: 20px;
+  cursor: pointer;
+  transition: all 0.3s;
+  display: flex;
+  flex-direction: column;
+
+  &:hover {
+    box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
+    transform: translateY(-2px);
+  }
+
+  .card-header {
+    margin-bottom: 16px;
+
+    .card-title {
+      margin: 0 0 8px 0;
+      font-size: 16px;
+      font-weight: 600;
+      color: #303133;
+    }
+
+    .card-description {
+      margin: 0;
+      font-size: 13px;
+      color: #909399;
+    }
+  }
+
+  .card-image {
+    flex: 1;
+    display: flex;
+    align-items: center;
+    justify-content: center;
+    min-height: 120px;
+
+    .solution-image {
+      max-width: 100%;
+      max-height: 120px;
+    }
+
+    .image-slot {
+      display: flex;
+      align-items: center;
+      justify-content: center;
+      width: 100px;
+      height: 100px;
+      background: #ebeef5;
+      border-radius: 8px;
+      color: #c0c4cc;
+      font-size: 30px;
+    }
+  }
+
+  .card-actions {
+    display: flex;
+    justify-content: flex-end;
+    gap: 8px;
+    margin-top: 12px;
+    padding-top: 12px;
+    border-top: 1px solid #e4e7ed;
+  }
+}
+</style>

+ 346 - 0
src/views/platform/gift/carousel/index.vue

@@ -0,0 +1,346 @@
+<template>
+  <div class="carousel-container">
+    <el-card shadow="hover">
+      <!-- 搜索区域 -->
+      <transition
+        :enter-active-class="proxy?.animate.searchAnimate.enter"
+        :leave-active-class="proxy?.animate.searchAnimate.leave"
+      >
+        <div v-show="showSearch" class="mb-[10px]">
+          <el-card shadow="hover">
+            <el-form ref="queryFormRef" :model="queryParams" :inline="true" label-width="100px">
+              <el-form-item label="广告名称" prop="title">
+                <el-input
+                  v-model="queryParams.title"
+                  placeholder="请输入广告名称"
+                  clearable
+                  @keyup.enter="handleQuery"
+                />
+              </el-form-item>
+              <el-form-item label="起始时间" prop="dateRange">
+                <el-date-picker
+                  v-model="dateRange"
+                  type="daterange"
+                  range-separator="-"
+                  start-placeholder="开始时间"
+                  end-placeholder="结束时间"
+                  value-format="YYYY-MM-DD"
+                  style="width: 240px"
+                />
+              </el-form-item>
+              <el-form-item>
+                <el-button type="primary" icon="Search" @click="handleQuery">搜索</el-button>
+                <el-button icon="Refresh" @click="resetQuery">重置</el-button>
+                <el-button type="primary" plain icon="Plus" @click="handleAdd">添加广告</el-button>
+              </el-form-item>
+            </el-form>
+          </el-card>
+        </div>
+      </transition>
+
+      <!-- 操作栏 -->
+      <el-row :gutter="10" class="mb8">
+        <right-toolbar v-model:show-search="showSearch" @query-table="getList"></right-toolbar>
+      </el-row>
+
+      <!-- 表格标题 -->
+      <div class="table-title mb-[10px]">
+        <span>轮播广告信息列表</span>
+      </div>
+
+      <!-- 表格 -->
+      <el-table v-loading="loading" :data="carouselList" border>
+        <el-table-column label="广告图片" align="center" width="120">
+          <template #default="scope">
+            <el-image
+              :src="scope.row.imageUrl"
+              :preview-src-list="[scope.row.imageUrl]"
+              fit="cover"
+              style="width: 80px; height: 60px; border-radius: 4px"
+              lazy
+            />
+          </template>
+        </el-table-column>
+        <el-table-column label="广告名称" align="center" prop="title" :show-overflow-tooltip="true" min-width="200" />
+        <el-table-column label="时间" align="center" width="280">
+          <template #default="scope">
+            <div>开始时间:{{ scope.row.startTime }}</div>
+            <div>到期时间:{{ scope.row.endTime }}</div>
+          </template>
+        </el-table-column>
+        <el-table-column label="上线/下线" align="center" width="100">
+          <template #default="scope">
+            <el-tag :type="scope.row.status === 1 ? 'success' : 'danger'">
+              {{ scope.row.status === 1 ? '上线' : '下线' }}
+            </el-tag>
+          </template>
+        </el-table-column>
+        <el-table-column label="排序" align="center" prop="sort" width="80" />
+        <el-table-column label="链接" align="center" prop="link" :show-overflow-tooltip="true" min-width="200" />
+        <el-table-column label="操作" align="center" width="150" class-name="small-padding fixed-width">
+          <template #default="scope">
+            <el-button link type="primary" icon="Edit" @click="handleUpdate(scope.row)">编辑</el-button>
+            <el-button link type="danger" icon="Delete" @click="handleDelete(scope.row)">删除</el-button>
+          </template>
+        </el-table-column>
+      </el-table>
+
+      <!-- 分页 -->
+      <pagination
+        v-show="total > 0"
+        v-model:page="queryParams.pageNum"
+        v-model:limit="queryParams.pageSize"
+        :total="total"
+        @pagination="getList"
+      />
+    </el-card>
+
+    <!-- 添加或修改对话框 -->
+    <el-dialog v-model="dialog.visible" :title="dialog.title" width="620px" append-to-body @close="cancel">
+      <el-form ref="carouselFormRef" :model="form" :rules="rules" label-width="100px">
+        <el-row :gutter="20">
+          <el-col :span="12">
+            <el-form-item label="广告名称" prop="title">
+              <el-input v-model="form.title" placeholder="请输入广告名称" />
+            </el-form-item>
+          </el-col>
+          <el-col :span="12">
+            <el-form-item label="上线/下线" prop="status">
+              <el-radio-group v-model="form.status">
+                <el-radio :value="1">上线</el-radio>
+                <el-radio :value="0">下线</el-radio>
+              </el-radio-group>
+            </el-form-item>
+          </el-col>
+        </el-row>
+
+        <el-row :gutter="20">
+          <el-col :span="12">
+            <el-form-item label="开始时间" prop="startTime">
+              <el-date-picker
+                v-model="form.startTime"
+                type="date"
+                placeholder="请选择"
+                value-format="YYYY-MM-DD"
+                style="width: 100%"
+              />
+            </el-form-item>
+          </el-col>
+          <el-col :span="12">
+            <el-form-item label="到期时间" prop="endTime">
+              <el-date-picker
+                v-model="form.endTime"
+                type="date"
+                placeholder="请选择"
+                value-format="YYYY-MM-DD"
+                style="width: 100%"
+              />
+            </el-form-item>
+          </el-col>
+        </el-row>
+
+        <el-row :gutter="20">
+          <el-col :span="12">
+            <el-form-item label="背景色" prop="color">
+              <el-color-picker v-model="form.color" show-alpha />
+            </el-form-item>
+          </el-col>
+          <el-col :span="12">
+            <el-form-item label="排序" prop="sort">
+              <el-input-number v-model="form.sort" :min="0" controls-position="right" />
+            </el-form-item>
+          </el-col>
+        </el-row>
+
+        <el-form-item label="链接" prop="link">
+          <el-input v-model="form.link" placeholder="请输入链接" />
+        </el-form-item>
+
+        <el-form-item label="图片" prop="imageUrl">
+          <image-upload v-model="form.imageUrl" :limit="1" />
+        </el-form-item>
+
+        <el-form-item label="描述" prop="remark">
+          <el-input v-model="form.remark" type="textarea" :rows="3" placeholder="请输入内容" />
+        </el-form-item>
+      </el-form>
+      <template #footer>
+        <div class="dialog-footer">
+          <el-button type="primary" @click="submitForm">确 认</el-button>
+          <el-button @click="cancel">取 消</el-button>
+        </div>
+      </template>
+    </el-dialog>
+  </div>
+</template>
+
+<script setup name="GiftCarousel" lang="ts">
+import { getCurrentInstance, onMounted, reactive, ref } from 'vue';
+import type { ComponentInternalInstance } from 'vue';
+import type { ElFormInstance } from 'element-plus';
+import dayjs from 'dayjs';
+import { listAdContent, addAdContent, updateAdContent, delAdContent } from '@/api/ad/content';
+
+const { proxy } = getCurrentInstance() as ComponentInternalInstance;
+
+const carouselList = ref<any[]>([]);
+const loading = ref(false);
+const total = ref(0);
+const showSearch = ref(true);
+const dateRange = ref<[string, string]>(['', '']);
+
+const queryParams = ref({
+  pageNum: 1,
+  pageSize: 10,
+  title: '',
+  adType: 'gift_banner',
+  startTime: '',
+  endTime: ''
+});
+
+const dialog = reactive<DialogOption>({
+  visible: false,
+  title: ''
+});
+
+const carouselFormRef = ref<ElFormInstance>();
+
+const initFormData = {
+  id: undefined as number | undefined,
+  adType: 'gift_banner',
+  title: '',
+  imageUrl: '',
+  startTime: '',
+  endTime: '',
+  link: '',
+  color: '#ffffff',
+  remark: '',
+  extJson: '{}',
+  sort: 0,
+  status: 1
+};
+
+const form = ref({ ...initFormData });
+
+const rules = reactive({
+  title: [{ required: true, message: '广告名称不能为空', trigger: 'blur' }],
+  startTime: [{ required: true, message: '开始时间不能为空', trigger: 'change' }],
+  endTime: [{ required: true, message: '结束时间不能为空', trigger: 'change' }]
+});
+
+/** 查询列表 */
+const getList = async () => {
+  loading.value = true;
+  const params: any = {
+    pageNum: queryParams.value.pageNum,
+    pageSize: queryParams.value.pageSize,
+    title: queryParams.value.title,
+    adType: queryParams.value.adType
+  };
+  if (dateRange.value && dateRange.value[0] && dateRange.value[1]) {
+    params.startTime = dateRange.value[0];
+    params.endTime = dateRange.value[1];
+  }
+  const res = await listAdContent(params);
+  carouselList.value = res.rows || [];
+  total.value = res.total || 0;
+  loading.value = false;
+};
+
+/** 搜索按钮操作 */
+const handleQuery = () => {
+  queryParams.value.pageNum = 1;
+  getList();
+};
+
+/** 重置按钮操作 */
+const resetQuery = () => {
+  dateRange.value = ['', ''];
+  queryParams.value.title = '';
+  queryParams.value.startTime = '';
+  queryParams.value.endTime = '';
+  handleQuery();
+};
+
+/** 取消按钮 */
+const cancel = () => {
+  reset();
+  dialog.visible = false;
+};
+
+/** 表单重置 */
+const reset = () => {
+  form.value = { ...initFormData };
+  carouselFormRef.value?.resetFields();
+};
+
+/** 新增按钮操作 */
+const handleAdd = () => {
+  reset();
+  const today = dayjs().format('YYYY-MM-DD');
+  const future = dayjs().add(30, 'day').format('YYYY-MM-DD');
+  form.value.startTime = today;
+  form.value.endTime = future;
+  dialog.visible = true;
+  dialog.title = '添加广告';
+};
+
+/** 修改按钮操作 */
+const handleUpdate = (row: any) => {
+  reset();
+  form.value = {
+    ...row,
+    adType: 'gift_banner',
+    status: row.status ?? 1,
+    color: row.color || '#ffffff',
+    remark: row.remark || '',
+    extJson: row.extJson || '{}',
+    startTime: row.startTime ? dayjs(row.startTime).format('YYYY-MM-DD') : '',
+    endTime: row.endTime ? dayjs(row.endTime).format('YYYY-MM-DD') : ''
+  };
+  dialog.visible = true;
+  dialog.title = '修改广告';
+};
+
+/** 提交按钮 */
+const submitForm = () => {
+  carouselFormRef.value?.validate((valid: boolean) => {
+    if (valid) {
+      const api = form.value.id ? updateAdContent : addAdContent;
+      api(form.value).then(() => {
+        proxy?.$modal.msgSuccess(form.value.id ? '修改成功' : '新增成功');
+        dialog.visible = false;
+        getList();
+      });
+    }
+  });
+};
+
+/** 删除按钮操作 */
+const handleDelete = (row: any) => {
+  proxy?.$modal.confirm('是否确认删除广告名称为"' + row.title + '"的数据项?').then(() => {
+    delAdContent(row.id).then(() => {
+      proxy?.$modal.msgSuccess('删除成功');
+      getList();
+    });
+  });
+};
+
+onMounted(() => {
+  getList();
+});
+</script>
+
+<style scoped lang="scss">
+.carousel-container {
+  height: 100%;
+}
+
+.table-title {
+  font-size: 18px;
+  font-weight: bold;
+  color: #303133;
+  margin-bottom: 10px;
+}
+</style>
+

+ 269 - 0
src/views/platform/gift/icon/index.vue

@@ -0,0 +1,269 @@
+<template>
+  <div class="icon-container">
+    <el-card shadow="hover">
+      <!-- 顶部操作 -->
+      <div class="action-bar mb-[10px]">
+        <el-button type="primary" plain icon="Plus" @click="handleAdd">添加图标广告</el-button>
+        <el-button icon="Refresh" @click="getList">刷新</el-button>
+      </div>
+
+      <!-- 表格标题 -->
+      <div class="table-title mb-[10px]">
+        <span>福礼图标广告信息列表</span>
+      </div>
+
+      <!-- 表格 -->
+      <el-table v-loading="loading" :data="iconList" border>
+        <el-table-column label="分类标题" align="center" prop="title" min-width="160" />
+        <el-table-column label="封面图片" align="center" width="140">
+          <template #default="scope">
+            <el-image
+              :src="scope.row.imageUrl"
+              :preview-src-list="[scope.row.imageUrl]"
+              fit="cover"
+              style="width: 64px; height: 64px"
+              lazy
+            >
+              <template #error>
+                <div class="image-slot">
+                  <el-icon><Picture /></el-icon>
+                </div>
+              </template>
+            </el-image>
+          </template>
+        </el-table-column>
+        <el-table-column label="链接地址" align="center" prop="link" :show-overflow-tooltip="true" min-width="220">
+          <template #default="scope">
+            <span class="link-text">{{ scope.row.link || '#' }}</span>
+          </template>
+        </el-table-column>
+        <el-table-column label="状态" align="center" width="100">
+          <template #default="scope">
+            <el-tag :type="scope.row.status === 1 ? 'success' : 'info'">
+              {{ scope.row.status === 1 ? '显示' : '隐藏' }}
+            </el-tag>
+          </template>
+        </el-table-column>
+        <el-table-column label="排序" align="center" prop="sort" width="80" />
+        <el-table-column label="操作" align="center" width="150" class-name="small-padding fixed-width">
+          <template #default="scope">
+            <el-button link type="primary" icon="Edit" @click="handleUpdate(scope.row)">编辑</el-button>
+            <el-button link type="danger" icon="Delete" @click="handleDelete(scope.row)">删除</el-button>
+          </template>
+        </el-table-column>
+      </el-table>
+
+      <!-- 分页 -->
+      <pagination
+        v-show="total > 0"
+        v-model:page="queryParams.pageNum"
+        v-model:limit="queryParams.pageSize"
+        :total="total"
+        @pagination="getList"
+      />
+    </el-card>
+
+    <!-- 新增/编辑对话框 -->
+    <el-dialog v-model="dialog.visible" :title="dialog.title" width="620px" append-to-body @close="cancel">
+      <el-form ref="iconFormRef" :model="form" :rules="rules" label-width="90px">
+        <el-row :gutter="20">
+          <el-col :span="12">
+            <el-form-item label="公告标题" prop="title">
+              <el-input v-model="form.title" placeholder="请输入公告标题" />
+            </el-form-item>
+          </el-col>
+          <el-col :span="12">
+            <el-form-item label="公告链接" prop="link">
+              <el-input v-model="form.link" placeholder="请输入公告链接" />
+            </el-form-item>
+          </el-col>
+        </el-row>
+
+        <el-row :gutter="20">
+          <el-col :span="12">
+            <el-form-item label="排序" prop="sort">
+              <el-input-number v-model="form.sort" :min="0" controls-position="right" style="width: 100%" />
+            </el-form-item>
+          </el-col>
+          <el-col :span="12">
+            <el-form-item label="状态" prop="status">
+              <el-switch
+                v-model="form.status"
+                :active-value="1"
+                :inactive-value="0"
+                active-text="显示"
+                inactive-text="隐藏"
+              />
+            </el-form-item>
+          </el-col>
+        </el-row>
+
+        <el-form-item label="封面图片" prop="imageUrl">
+          <image-upload v-model="form.imageUrl" :limit="1" />
+        </el-form-item>
+      </el-form>
+      <template #footer>
+        <div class="dialog-footer">
+          <el-button type="primary" @click="submitForm">确认</el-button>
+          <el-button @click="cancel">取消</el-button>
+        </div>
+      </template>
+    </el-dialog>
+  </div>
+</template>
+
+<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 dayjs from 'dayjs';
+import { listAdContent, addAdContent, updateAdContent, delAdContent } from '@/api/ad/content';
+
+const { proxy } = getCurrentInstance() as ComponentInternalInstance;
+
+const loading = ref(false);
+const total = ref(0);
+const iconList = ref<any[]>([]);
+
+const queryParams = ref({
+  pageNum: 1,
+  pageSize: 10,
+  adType: 'gift_icon'
+});
+
+const dialog = reactive<DialogOption>({
+  visible: false,
+  title: ''
+});
+
+const iconFormRef = ref<ElFormInstance>();
+
+const initFormData = {
+  id: undefined as number | undefined,
+  adType: 'gift_icon',
+  title: '',
+  imageUrl: '',
+  link: '',
+  sort: 0,
+  status: 1,
+  color: '#ffffff',
+  remark: '',
+  extJson: '{}',
+  startTime: '',
+  endTime: ''
+};
+
+const form = ref({ ...initFormData });
+
+const rules = reactive({
+  title: [{ required: true, message: '公告标题不能为空', trigger: 'blur' }]
+});
+
+const getList = async () => {
+  loading.value = true;
+  const res = await listAdContent({
+    pageNum: queryParams.value.pageNum,
+    pageSize: queryParams.value.pageSize,
+    adType: queryParams.value.adType
+  });
+  iconList.value = res.rows || [];
+  total.value = res.total || 0;
+  loading.value = false;
+};
+
+const reset = () => {
+  form.value = { ...initFormData };
+  iconFormRef.value?.resetFields();
+};
+
+const handleAdd = () => {
+  reset();
+  const today = dayjs().format('YYYY-MM-DD');
+  const future = dayjs().add(365, 'day').format('YYYY-MM-DD');
+  form.value.startTime = today;
+  form.value.endTime = future;
+  dialog.visible = true;
+  dialog.title = '新增图标广告';
+};
+
+const handleUpdate = (row: any) => {
+  reset();
+  form.value = {
+    ...row,
+    adType: 'gift_icon',
+    status: row.status ?? 1,
+    color: row.color || '#ffffff',
+    remark: row.remark || '',
+    extJson: row.extJson || '{}',
+    startTime: row.startTime ? dayjs(row.startTime).format('YYYY-MM-DD') : '',
+    endTime: row.endTime ? dayjs(row.endTime).format('YYYY-MM-DD') : ''
+  };
+  dialog.visible = true;
+  dialog.title = '编辑图标广告';
+};
+
+const submitForm = () => {
+  iconFormRef.value?.validate((valid: boolean) => {
+    if (valid) {
+      const api = form.value.id ? updateAdContent : addAdContent;
+      api(form.value).then(() => {
+        proxy?.$modal.msgSuccess(form.value.id ? '修改成功' : '新增成功');
+        dialog.visible = false;
+        getList();
+      });
+    }
+  });
+};
+
+const handleDelete = (row: any) => {
+  proxy?.$modal.confirm(`是否确认删除分类标题为\"${row.title}\"的数据项?`).then(() => {
+    delAdContent(row.id).then(() => {
+      proxy?.$modal.msgSuccess('删除成功');
+      getList();
+    });
+  });
+};
+
+const cancel = () => {
+  reset();
+  dialog.visible = false;
+};
+
+onMounted(() => {
+  getList();
+});
+</script>
+
+<style scoped lang="scss">
+.icon-container {
+  height: 100%;
+}
+
+.action-bar {
+  display: flex;
+  justify-content: flex-end;
+}
+
+.table-title {
+  font-size: 16px;
+  font-weight: 600;
+  color: #303133;
+  padding: 0 4px;
+}
+
+.image-slot {
+  width: 64px;
+  height: 64px;
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  background: #f5f7fa;
+  color: #c0c4cc;
+  border-radius: 4px;
+}
+
+.link-text {
+  color: #409eff;
+}
+</style>
+

+ 250 - 0
src/views/platform/gift/title/index.vue

@@ -0,0 +1,250 @@
+<template>
+  <div class="title-container">
+    <el-card shadow="hover">
+      <!-- 顶部操作 -->
+      <div class="action-bar mb-[10px]">
+        <el-button type="primary" plain icon="Plus" @click="handleAdd">新增</el-button>
+        <el-button icon="Refresh" @click="getList">刷新</el-button>
+      </div>
+
+      <!-- 表格标题 -->
+      <div class="table-title mb-[10px]">
+        <span>福礼标题信息列表</span>
+      </div>
+
+      <!-- 表格 -->
+      <el-table v-loading="loading" :data="titleList" border>
+        <el-table-column label="标题名称" align="center" prop="title" min-width="140" />
+        <el-table-column label="导航类型" align="center" prop="remark" min-width="120" />
+        <el-table-column label="链接地址" align="center" prop="link" :show-overflow-tooltip="true" min-width="240" />
+        <el-table-column label="状态" align="center" width="100">
+          <template #default="scope">
+            <el-tag :type="scope.row.status === 1 ? 'success' : 'info'">
+              {{ scope.row.status === 1 ? '显示' : '隐藏' }}
+            </el-tag>
+          </template>
+        </el-table-column>
+        <el-table-column label="排序" align="center" prop="sort" width="80" />
+        <el-table-column label="操作" align="center" width="150" class-name="small-padding fixed-width">
+          <template #default="scope">
+            <el-button link type="primary" icon="Edit" @click="handleUpdate(scope.row)">编辑</el-button>
+            <el-button link type="danger" icon="Delete" @click="handleDelete(scope.row)">删除</el-button>
+          </template>
+        </el-table-column>
+      </el-table>
+
+      <!-- 分页 -->
+      <pagination
+        v-show="total > 0"
+        v-model:page="queryParams.pageNum"
+        v-model:limit="queryParams.pageSize"
+        :total="total"
+        @pagination="getList"
+      />
+    </el-card>
+
+    <!-- 新增/编辑对话框 -->
+    <el-dialog v-model="dialog.visible" :title="dialog.title" width="620px" append-to-body @close="cancel">
+      <el-form ref="titleFormRef" :model="form" :rules="rules" label-width="90px">
+        <el-row :gutter="20">
+          <el-col :span="12">
+            <el-form-item label="标题名称" prop="title">
+              <el-input v-model="form.title" placeholder="请输入标题名称" />
+            </el-form-item>
+          </el-col>
+          <el-col :span="12">
+            <el-form-item label="标题链接" prop="link">
+              <el-input v-model="form.link" placeholder="请输入标题链接" />
+            </el-form-item>
+          </el-col>
+        </el-row>
+
+        <el-row :gutter="20">
+          <el-col :span="12">
+            <el-form-item label="导航类型" prop="remark">
+              <el-select v-model="form.remark" placeholder="请选择" style="width: 100%">
+                <el-option
+                  v-for="item in navTypeOptions"
+                  :key="item.value"
+                  :label="item.label"
+                  :value="item.value"
+                />
+              </el-select>
+            </el-form-item>
+          </el-col>
+          <el-col :span="12">
+            <el-form-item label="排序" prop="sort">
+              <el-input-number v-model="form.sort" :min="0" controls-position="right" style="width: 100%" />
+            </el-form-item>
+          </el-col>
+        </el-row>
+
+        <el-form-item label="状态" prop="status">
+          <el-switch
+            v-model="form.status"
+            :active-value="1"
+            :inactive-value="0"
+            active-text="显示"
+            inactive-text="隐藏"
+          />
+        </el-form-item>
+      </el-form>
+      <template #footer>
+        <div class="dialog-footer">
+          <el-button type="primary" @click="submitForm">确认</el-button>
+          <el-button @click="cancel">取消</el-button>
+        </div>
+      </template>
+    </el-dialog>
+  </div>
+</template>
+
+<script setup name="GiftTitle" lang="ts">
+import { getCurrentInstance, onMounted, reactive, ref } from 'vue';
+import type { ComponentInternalInstance } from 'vue';
+import type { ElFormInstance } from 'element-plus';
+import dayjs from 'dayjs';
+import { listAdContent, addAdContent, updateAdContent, delAdContent } from '@/api/ad/content';
+
+const { proxy } = getCurrentInstance() as ComponentInternalInstance;
+
+const loading = ref(false);
+const total = ref(0);
+const titleList = ref<any[]>([]);
+const navTypeOptions = [
+  { label: '首页导航', value: '首页导航' },
+  { label: '其他导航', value: '其他导航' }
+];
+
+const queryParams = ref({
+  pageNum: 1,
+  pageSize: 10,
+  adType: 'gift_title',
+  title: ''
+});
+
+const dialog = reactive<DialogOption>({
+  visible: false,
+  title: ''
+});
+
+const titleFormRef = ref<ElFormInstance>();
+
+const initFormData = {
+  id: undefined as number | undefined,
+  adType: 'gift_title',
+  title: '',
+  remark: '',
+  link: '',
+  imageUrl: '', // 后端校验需要,标题广告可用占位
+  sort: 0,
+  status: 1,
+  color: '#ffffff',
+  extJson: '{}',
+  startTime: '',
+  endTime: ''
+};
+
+const form = ref({ ...initFormData });
+
+const rules = reactive({
+  title: [{ required: true, message: '标题名称不能为空', trigger: 'blur' }]
+});
+
+const getList = async () => {
+  loading.value = true;
+  const res = await listAdContent({
+    pageNum: queryParams.value.pageNum,
+    pageSize: queryParams.value.pageSize,
+    adType: queryParams.value.adType,
+    title: queryParams.value.title
+  });
+  titleList.value = res.rows || [];
+  total.value = res.total || 0;
+  loading.value = false;
+};
+
+const reset = () => {
+  form.value = { ...initFormData };
+  titleFormRef.value?.resetFields();
+};
+
+const handleAdd = () => {
+  reset();
+  const today = dayjs().format('YYYY-MM-DD');
+  const future = dayjs().add(365, 'day').format('YYYY-MM-DD');
+  form.value.startTime = today;
+  form.value.endTime = future;
+  form.value.imageUrl = form.value.imageUrl || 'https://via.placeholder.com/1x1';
+  dialog.visible = true;
+  dialog.title = '新增标题';
+};
+
+const handleUpdate = (row: any) => {
+  reset();
+  form.value = {
+    ...row,
+    adType: 'gift_title',
+    status: row.status ?? 1,
+    remark: row.remark || '',
+    color: row.color || '#ffffff',
+    extJson: row.extJson || '{}',
+    imageUrl: row.imageUrl || 'https://via.placeholder.com/1x1',
+    startTime: row.startTime ? dayjs(row.startTime).format('YYYY-MM-DD') : '',
+    endTime: row.endTime ? dayjs(row.endTime).format('YYYY-MM-DD') : ''
+  };
+  dialog.visible = true;
+  dialog.title = '编辑标题';
+};
+
+const submitForm = () => {
+  titleFormRef.value?.validate((valid: boolean) => {
+    if (valid) {
+      const api = form.value.id ? updateAdContent : addAdContent;
+      api(form.value).then(() => {
+        proxy?.$modal.msgSuccess(form.value.id ? '修改成功' : '新增成功');
+        dialog.visible = false;
+        getList();
+      });
+    }
+  });
+};
+
+const handleDelete = (row: any) => {
+  proxy
+    ?.$modal.confirm(`是否确认删除标题名称为"${row.title}"的数据项?`)
+    .then(() => delAdContent(row.id))
+    .then(() => {
+      proxy?.$modal.msgSuccess('删除成功');
+      getList();
+    });
+};
+
+const cancel = () => {
+  reset();
+  dialog.visible = false;
+};
+
+onMounted(() => {
+  getList();
+});
+</script>
+
+<style scoped lang="scss">
+.title-container {
+  height: 100%;
+}
+
+.action-bar {
+  display: flex;
+  justify-content: flex-end;
+}
+
+.table-title {
+  font-size: 16px;
+  font-weight: 600;
+  color: #303133;
+  padding: 0 4px;
+}
+</style>
+

+ 715 - 0
src/views/platform/industrial/booth/index.vue

@@ -0,0 +1,715 @@
+<template>
+  <div class="booth-container">
+    <el-card shadow="hover">
+      <!-- 操作栏 -->
+      <el-row :gutter="10" class="mb8">
+        <el-col :span="1.5">
+          <el-button type="primary" icon="View" @click="handleViewProducts">查看推荐商品</el-button>
+        </el-col>
+      </el-row>
+
+      <!-- 商品展示区域 -->
+      <div class="product-display-area">
+        <div class="product-grid">
+          <div
+            v-for="(product, index) in displayProducts"
+            :key="product.id"
+            class="product-card"
+          >
+            <el-icon class="delete-icon" @click="handleRemoveProduct(product.id)">
+              <Close />
+            </el-icon>
+            <div class="product-image">
+              <el-image
+                :src="product.imageUrl"
+                fit="cover"
+                lazy
+                class="image"
+              >
+                <template #error>
+                  <div class="image-slot">
+                    <el-icon><Picture /></el-icon>
+                  </div>
+                </template>
+              </el-image>
+            </div>
+            <div class="product-info">
+              <p class="product-name">{{ product.name }}</p>
+              <p class="product-price">¥{{ product.price }}</p>
+            </div>
+          </div>
+        </div>
+        
+        <!-- 导航箭头 -->
+        <el-icon
+          v-if="hasPrev"
+          class="nav-arrow nav-arrow-left"
+          @click="handlePrev"
+        >
+          <ArrowLeft />
+        </el-icon>
+        <el-icon
+          v-if="hasNext"
+          class="nav-arrow nav-arrow-right"
+          @click="handleNext"
+        >
+          <ArrowRight />
+        </el-icon>
+      </div>
+
+      <!-- 空状态 -->
+      <el-empty v-if="displayProducts.length === 0" description="暂无商品,请点击查看推荐商品添加" />
+    </el-card>
+
+    <!-- 已选商品对话框 -->
+    <el-dialog
+      v-model="dialog.visible"
+      title="已选商品"
+      width="1000px"
+      append-to-body
+      @close="handleDialogClose"
+    >
+      <!-- 操作按钮 -->
+      <div class="dialog-actions mb-[10px]">
+        <el-button type="primary" plain icon="Plus" @click="handleAddProduct">新增商品</el-button>
+        <el-button type="primary" plain icon="Upload" @click="handleImportProduct">导入商品</el-button>
+      </div>
+
+      <!-- 商品表格 -->
+      <el-table :data="selectedProducts" border max-height="500">
+        <el-table-column label="轮播商品编号" align="center" prop="id" width="150" />
+        <el-table-column label="轮播商品图片" align="center" width="120">
+          <template #default="scope">
+            <el-image
+              :src="scope.row.imageUrl"
+              fit="cover"
+              style="width: 80px; height: 80px; border-radius: 4px"
+              lazy
+            >
+              <template #error>
+                <div class="image-slot-small">
+                  <el-icon><Picture /></el-icon>
+                </div>
+              </template>
+            </el-image>
+          </template>
+        </el-table-column>
+        <el-table-column label="轮播商品名称" align="center" prop="name" :show-overflow-tooltip="true" min-width="300" />
+        <el-table-column label="价格" align="center" prop="price" width="120">
+          <template #default="scope">
+            <span class="price-text">¥{{ scope.row.price }}</span>
+          </template>
+        </el-table-column>
+        <el-table-column label="排序(值越大排越前)" align="center" width="200">
+          <template #default="scope">
+            <el-input-number
+              v-model="scope.row.sort"
+              :min="0"
+              :max="999"
+              controls-position="right"
+              style="width: 100%"
+              @change="handleSortChange(scope.row)"
+            />
+          </template>
+        </el-table-column>
+        <el-table-column label="操作" align="center" width="100">
+          <template #default="scope">
+            <el-button link type="danger" icon="Delete" @click="handleDeleteProduct(scope.row.id)">删除</el-button>
+          </template>
+        </el-table-column>
+      </el-table>
+
+      <template #footer>
+        <div class="dialog-footer">
+          <el-button type="primary" @click="handleConfirm">确认</el-button>
+          <el-button @click="handleDialogClose">取消</el-button>
+        </div>
+      </template>
+    </el-dialog>
+
+    <!-- 选择轮播商品对话框 -->
+    <el-dialog
+      v-model="selectDialog.visible"
+      title="选择轮播商品"
+      width="1200px"
+      append-to-body
+      @close="handleSelectDialogClose"
+    >
+      <!-- 搜索栏 -->
+      <div class="search-bar mb-[10px]">
+        <el-input
+          v-model="searchKeyword"
+          placeholder="请输入商品编号名称输入搜索"
+          clearable
+          style="width: 400px"
+          @keyup.enter="handleSearchProducts"
+        >
+          <template #append>
+            <el-button icon="Search" @click="handleSearchProducts">搜索</el-button>
+          </template>
+        </el-input>
+      </div>
+
+      <!-- 商品表格 -->
+      <el-table
+        ref="newProductTableRef"
+        :data="filteredAvailableProducts"
+        border
+        max-height="500"
+        @selection-change="handleSelectionChange"
+      >
+        <el-table-column type="selection" width="55" align="center" />
+        <el-table-column label="轮播商品编号" align="center" prop="id" width="150" />
+        <el-table-column label="轮播商品图片" align="center" width="120">
+          <template #default="scope">
+            <el-image
+              :src="scope.row.imageUrl"
+              fit="cover"
+              style="width: 80px; height: 80px; border-radius: 4px"
+              lazy
+            >
+              <template #error>
+                <div class="image-slot-small">
+                  <el-icon><Picture /></el-icon>
+                </div>
+              </template>
+            </el-image>
+          </template>
+        </el-table-column>
+        <el-table-column label="轮播商品名称" align="center" prop="name" :show-overflow-tooltip="true" min-width="300" />
+        <el-table-column label="价格" align="center" prop="price" width="120">
+          <template #default="scope">
+            <span class="price-text">¥{{ scope.row.price }}</span>
+          </template>
+        </el-table-column>
+      </el-table>
+
+      <!-- 分页 -->
+      <pagination
+        v-show="filteredProducts.length > 0"
+        v-model:page="newProductPagination.pageNum"
+        v-model:limit="newProductPagination.pageSize"
+        :total="filteredProducts.length"
+        @pagination="() => {}"
+      />
+
+      <template #footer>
+        <div class="dialog-footer">
+          <el-button type="primary" @click="handleConfirmSelect">确认</el-button>
+          <el-button @click="handleSelectDialogClose">取消</el-button>
+        </div>
+      </template>
+    </el-dialog>
+  </div>
+</template>
+
+<script setup name="IndustrialBooth" lang="ts">
+import { ref, reactive, computed, watch, nextTick, onMounted } from 'vue';
+import { ElMessage, ElMessageBox } from 'element-plus';
+import { Close, Picture, ArrowLeft, ArrowRight, View, Plus, Upload } from '@element-plus/icons-vue';
+
+// 对话框
+const dialog = reactive({
+  visible: false
+});
+
+// 选择商品对话框
+const selectDialog = reactive({
+  visible: false
+});
+
+// 搜索关键词
+const searchKeyword = ref('');
+
+// 可选商品列表(模拟数据)
+const availableProducts = ref([
+  {
+    id: '002169823',
+    name: '江润双色塑钢培训椅',
+    imageUrl: 'https://via.placeholder.com/200x200/87CEEB/FFFFFF?text=培训椅',
+    price: '299.00'
+  },
+  {
+    id: '002169822',
+    name: '江润培训桌700*550*750mm',
+    imageUrl: 'https://via.placeholder.com/200x200/000000/FFFFFF?text=培训桌',
+    price: '799.00'
+  },
+  {
+    id: '002169821',
+    name: '江润六边形折叠培训桌 800*500*750mm (直径 1.6m)',
+    imageUrl: 'https://via.placeholder.com/200x200/FFFFFF/000000?text=折叠桌',
+    price: '699.00'
+  },
+  {
+    id: '002169820',
+    name: '梦洁200*230cm锦玉梦韵四件套',
+    imageUrl: 'https://via.placeholder.com/200x200/4169E1/FFFFFF?text=四件套',
+    price: '1299.00'
+  },
+  {
+    id: '002169819',
+    name: '洁丽雅纯棉四件套2*2.3M',
+    imageUrl: 'https://via.placeholder.com/200x200/FFFFFF/000000?text=纯棉',
+    price: '249.00'
+  },
+  {
+    id: '002169818',
+    name: '润实验台2000*750*800mm',
+    imageUrl: 'https://via.placeholder.com/200x200/808080/FFFFFF?text=实验台',
+    price: '3699.00'
+  },
+  {
+    id: '002169817',
+    name: '工业级安全防护手套 防割防滑',
+    imageUrl: 'https://via.placeholder.com/200x200/FF6347/FFFFFF?text=手套',
+    price: '45.00'
+  },
+  {
+    id: '002169816',
+    name: '专业级电钻套装 多功能工具箱',
+    imageUrl: 'https://via.placeholder.com/200x200/FFD700/000000?text=电钻',
+    price: '599.00'
+  }
+]);
+
+// 选中的商品ID列表(用于新增商品标签页)
+const selectedProductIds = ref<string[]>([]);
+
+// 新增商品分页
+const newProductPagination = reactive({
+  pageNum: 1,
+  pageSize: 20,
+  total: 1895427
+});
+
+// 已选商品分页
+const selectedProductPagination = reactive({
+  pageNum: 1,
+  pageSize: 20,
+  total: 0
+});
+
+// 当前显示的商品索引
+const currentIndex = ref(0);
+const pageSize = ref(5); // 每页显示5个商品
+
+// 模拟数据 - 已选商品列表
+const selectedProducts = ref([
+  {
+    id: '000294732',
+    name: 'VICTOR/胜利 温度校验仪VC02+不支持第三方检定1台',
+    imageUrl: 'https://via.placeholder.com/200x200/FFD700/000000?text=VC02+',
+    price: '1997.00',
+    sort: 9
+  },
+  {
+    id: '000130005',
+    name: 'CJ/超佳聚氨酯泡沫胶填缝剂 A2料 750mL(900g) 1罐',
+    imageUrl: 'https://via.placeholder.com/200x200/DC143C/FFFFFF?text=CJ',
+    price: '26.00',
+    sort: 8
+  },
+  {
+    id: '000371498',
+    name: 'STANLEY/史丹利公制T形球头内六角扳手 94-288-23 4mm 1支',
+    imageUrl: 'https://via.placeholder.com/200x200/FFD700/000000?text=STANLEY',
+    price: '26.00',
+    sort: 7
+  },
+  {
+    id: '000207453',
+    name: 'AIRTAC/亚德客 4V200系列电磁阀 4V21008B 两位五通 DIN插座式接口 Rc1/4 DC24V 1个',
+    imageUrl: 'https://via.placeholder.com/200x200/808080/FFFFFF?text=AIRTAC',
+    price: '77.00',
+    sort: 6
+  },
+  {
+    id: '000359317',
+    name: 'CNFB/桥防 246系利铝青钢防爆克丝钳 T8246-1006-AL 8" 1把',
+    imageUrl: 'https://via.placeholder.com/200x200/DC143C/FFFFFF?text=CNFB',
+    price: '410.00',
+    sort: 5
+  },
+  {
+    id: '000123456',
+    name: 'DELIXI/德力西 DZ47sLE小型漏保护断路器 DZ47sLE 2P C 32A',
+    imageUrl: 'https://via.placeholder.com/200x200/FFFFFF/000000?text=DELIXI',
+    price: '38.00',
+    sort: 4
+  },
+  {
+    id: '000789012',
+    name: '工业级安全帽 白色 ABS材质 1顶',
+    imageUrl: 'https://via.placeholder.com/200x200/FFFFFF/000000?text=安全帽',
+    price: '45.00',
+    sort: 3
+  }
+]);
+
+// 计算当前显示的商品(按排序值降序排列)
+const sortedProducts = computed(() => {
+  return [...selectedProducts.value].sort((a, b) => b.sort - a.sort);
+});
+
+// 当前页显示的商品
+const displayProducts = computed(() => {
+  const start = currentIndex.value * pageSize.value;
+  const end = start + pageSize.value;
+  return sortedProducts.value.slice(start, end);
+});
+
+// 是否有上一页
+const hasPrev = computed(() => {
+  return currentIndex.value > 0;
+});
+
+// 是否有下一页
+const hasNext = computed(() => {
+  const totalPages = Math.ceil(sortedProducts.value.length / pageSize.value);
+  return currentIndex.value < totalPages - 1;
+});
+
+// 查看推荐商品
+const handleViewProducts = () => {
+  dialog.visible = true;
+};
+
+// 对话框关闭
+const handleDialogClose = () => {
+  dialog.visible = false;
+};
+
+// 新增商品
+const handleAddProduct = () => {
+  selectDialog.visible = true;
+  selectedProductIds.value = [];
+  searchKeyword.value = '';
+  newProductPagination.pageNum = 1;
+  // 等待DOM更新后,清除表格选中状态
+  nextTick(() => {
+    if (newProductTableRef.value) {
+      newProductTableRef.value.clearSelection();
+    }
+  });
+};
+
+// 选择商品对话框关闭
+const handleSelectDialogClose = () => {
+  selectDialog.visible = false;
+  selectedProductIds.value = [];
+  searchKeyword.value = '';
+  // 清除表格选中状态
+  if (newProductTableRef.value) {
+    newProductTableRef.value.clearSelection();
+  }
+};
+
+// 搜索商品
+const handleSearchProducts = () => {
+  newProductPagination.pageNum = 1;
+  // 这里可以调用搜索接口
+  ElMessage.success('搜索功能开发中...');
+};
+
+// 表格选择变化
+const newProductTableRef = ref();
+const handleSelectionChange = (selection: any[]) => {
+  selectedProductIds.value = selection.map((item) => item.id);
+};
+
+
+// 确认选择商品
+const handleConfirmSelect = () => {
+  if (selectedProductIds.value.length === 0) {
+    ElMessage.warning('请至少选择一个商品');
+    return;
+  }
+
+  // 添加选中的商品到已选列表
+  selectedProductIds.value.forEach((id) => {
+    const product = availableProducts.value.find((p) => p.id === id);
+    if (product && !selectedProducts.value.find((p) => p.id === id)) {
+      selectedProducts.value.push({
+        ...product,
+        sort: Math.max(...selectedProducts.value.map((p) => p.sort), 0) + 1
+      });
+    }
+  });
+
+  ElMessage.success(`成功添加 ${selectedProductIds.value.length} 个商品`);
+  handleSelectDialogClose();
+};
+
+// 过滤后的可选商品列表(不分页,用于搜索)
+const filteredProducts = computed(() => {
+  let products = [...availableProducts.value];
+  
+  // 根据搜索关键词过滤
+  if (searchKeyword.value) {
+    const keyword = searchKeyword.value.toLowerCase();
+    products = products.filter(
+      (p) =>
+        p.id.toLowerCase().includes(keyword) ||
+        p.name.toLowerCase().includes(keyword)
+    );
+  }
+
+  return products;
+});
+
+// 分页后的可选商品列表
+const filteredAvailableProducts = computed(() => {
+  const start = (newProductPagination.pageNum - 1) * newProductPagination.pageSize;
+  const end = start + newProductPagination.pageSize;
+  return filteredProducts.value.slice(start, end);
+});
+
+// 更新分页总数
+watch(
+  () => filteredProducts.value.length,
+  (newTotal) => {
+    newProductPagination.total = newTotal;
+  },
+  { immediate: true }
+);
+
+
+// 导入商品
+const handleImportProduct = () => {
+  ElMessage.info('导入商品功能开发中...');
+};
+
+// 删除商品
+const handleDeleteProduct = (id: string) => {
+  ElMessageBox.confirm('是否确认删除该商品?', '提示', {
+    confirmButtonText: '确定',
+    cancelButtonText: '取消',
+    type: 'warning'
+  })
+    .then(() => {
+      const index = selectedProducts.value.findIndex((item) => item.id === id);
+      if (index !== -1) {
+        selectedProducts.value.splice(index, 1);
+        ElMessage.success('删除成功');
+        // 如果当前页没有商品了,回到上一页
+        if (displayProducts.value.length === 0 && currentIndex.value > 0) {
+          currentIndex.value--;
+        }
+      }
+    })
+    .catch(() => {});
+};
+
+// 排序改变
+const handleSortChange = (row: any) => {
+  // 排序值改变后,重新计算显示的商品
+  console.log('排序值改变:', row);
+};
+
+// 确认
+const handleConfirm = () => {
+  ElMessage.success('保存成功');
+  dialog.visible = false;
+};
+
+// 上一页
+const handlePrev = () => {
+  if (hasPrev.value) {
+    currentIndex.value--;
+  }
+};
+
+// 下一页
+const handleNext = () => {
+  if (hasNext.value) {
+    currentIndex.value++;
+  }
+};
+
+// 移除商品(从展示区域移除)
+const handleRemoveProduct = (id: string) => {
+  ElMessageBox.confirm('是否确认从展示区域移除该商品?', '提示', {
+    confirmButtonText: '确定',
+    cancelButtonText: '取消',
+    type: 'warning'
+  })
+    .then(() => {
+      const index = selectedProducts.value.findIndex((item) => item.id === id);
+      if (index !== -1) {
+        selectedProducts.value.splice(index, 1);
+        ElMessage.success('移除成功');
+        // 如果当前页没有商品了,回到上一页
+        if (displayProducts.value.length === 0 && currentIndex.value > 0) {
+          currentIndex.value--;
+        }
+      }
+    })
+    .catch(() => {});
+};
+
+onMounted(() => {
+  // 初始化
+});
+</script>
+
+<style scoped lang="scss">
+.booth-container {
+  height: 100%;
+}
+
+.product-display-area {
+  position: relative;
+  min-height: 400px;
+  padding: 20px 0;
+}
+
+.product-grid {
+  display: grid;
+  grid-template-columns: repeat(5, 1fr);
+  gap: 20px;
+  padding: 0 40px;
+}
+
+.product-card {
+  position: relative;
+  background: #fff;
+  border: 1px solid #ebeef5;
+  border-radius: 8px;
+  padding: 12px;
+  cursor: pointer;
+  transition: all 0.3s;
+  box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
+
+  &:hover {
+    box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
+    transform: translateY(-2px);
+  }
+
+  .delete-icon {
+    position: absolute;
+    top: 8px;
+    right: 8px;
+    font-size: 18px;
+    color: #909399;
+    cursor: pointer;
+    z-index: 10;
+    background: rgba(255, 255, 255, 0.8);
+    border-radius: 50%;
+    padding: 4px;
+    transition: all 0.3s;
+
+    &:hover {
+      color: #f56c6c;
+      background: rgba(255, 255, 255, 1);
+    }
+  }
+
+  .product-image {
+    width: 100%;
+    height: 180px;
+    margin-bottom: 12px;
+    background: #f5f7fa;
+    border-radius: 4px;
+    overflow: hidden;
+
+    .image {
+      width: 100%;
+      height: 100%;
+    }
+
+    .image-slot {
+      display: flex;
+      justify-content: center;
+      align-items: center;
+      width: 100%;
+      height: 100%;
+      background: #f5f7fa;
+      color: #909399;
+      font-size: 40px;
+    }
+  }
+
+  .product-info {
+    .product-name {
+      margin: 0 0 8px 0;
+      font-size: 13px;
+      color: #303133;
+      line-height: 1.5;
+      overflow: hidden;
+      text-overflow: ellipsis;
+      display: -webkit-box;
+      -webkit-line-clamp: 2;
+      -webkit-box-orient: vertical;
+      min-height: 39px;
+    }
+
+    .product-price {
+      margin: 0;
+      font-size: 18px;
+      font-weight: 600;
+      color: #f56c6c;
+    }
+  }
+}
+
+.nav-arrow {
+  position: absolute;
+  top: 50%;
+  transform: translateY(-50%);
+  font-size: 32px;
+  color: #909399;
+  cursor: pointer;
+  background: rgba(255, 255, 255, 0.9);
+  border-radius: 50%;
+  padding: 8px;
+  box-shadow: 0 2px 8px rgba(0, 0, 0, 0.15);
+  transition: all 0.3s;
+  z-index: 10;
+
+  &:hover {
+    color: #409eff;
+    background: #fff;
+    box-shadow: 0 4px 12px rgba(0, 0, 0, 0.2);
+  }
+
+  &.nav-arrow-left {
+    left: 10px;
+  }
+
+  &.nav-arrow-right {
+    right: 10px;
+  }
+}
+
+.dialog-actions {
+  display: flex;
+  gap: 10px;
+}
+
+.image-slot-small {
+  display: flex;
+  justify-content: center;
+  align-items: center;
+  width: 100%;
+  height: 100%;
+  background: #f5f7fa;
+  color: #909399;
+  font-size: 20px;
+}
+
+.price-text {
+  color: #f56c6c;
+  font-weight: 600;
+}
+
+.search-bar {
+  display: flex;
+  align-items: center;
+}
+
+:deep(.el-dialog__body) {
+  padding: 20px;
+}
+
+</style>

+ 312 - 0
src/views/platform/industrial/carousel/index.vue

@@ -0,0 +1,312 @@
+<template>
+  <div class="carousel-container">
+    <el-card shadow="hover">
+      <!-- 搜索区域 -->
+      <transition
+        :enter-active-class="proxy?.animate.searchAnimate.enter"
+        :leave-active-class="proxy?.animate.searchAnimate.leave"
+      >
+        <div v-show="showSearch" class="mb-[10px]">
+          <el-card shadow="hover">
+            <el-form ref="queryFormRef" :model="queryParams" :inline="true" label-width="100px">
+              <el-form-item label="广告名称" prop="adName">
+                <el-input
+                  v-model="queryParams.adName"
+                  placeholder="请输入广告名称"
+                  clearable
+                  @keyup.enter="handleQuery"
+                />
+              </el-form-item>
+              <el-form-item label="起始时间" prop="dateRange">
+                <el-date-picker
+                  v-model="dateRange"
+                  type="daterange"
+                  range-separator="-"
+                  start-placeholder="开始时间"
+                  end-placeholder="结束时间"
+                  value-format="YYYY-MM-DD"
+                  style="width: 240px"
+                />
+              </el-form-item>
+              <el-form-item>
+                <el-button type="primary" icon="Search" @click="handleQuery">搜索</el-button>
+                <el-button icon="Refresh" @click="resetQuery">重置</el-button>
+                <el-button type="primary" plain icon="Plus" @click="handleAdd">添加广告</el-button>
+              </el-form-item>
+            </el-form>
+          </el-card>
+        </div>
+      </transition>
+
+      <!-- 操作栏 -->
+      <el-row :gutter="10" class="mb8">
+        <right-toolbar v-model:show-search="showSearch" @query-table="getList"></right-toolbar>
+      </el-row>
+
+      <!-- 表格标题 -->
+      <div class="table-title mb-[10px]">
+        <span>轮播广告信息列表</span>
+      </div>
+
+      <!-- 表格 -->
+      <el-table v-loading="loading" :data="carouselList" border>
+        <el-table-column label="广告图片" align="center" width="120">
+          <template #default="scope">
+            <el-image
+              :src="scope.row.imageUrl"
+              :preview-src-list="[scope.row.imageUrl]"
+              fit="cover"
+              style="width: 80px; height: 60px; border-radius: 4px"
+              lazy
+            />
+          </template>
+        </el-table-column>
+        <el-table-column label="广告名称" align="center" prop="title" :show-overflow-tooltip="true" min-width="200" />
+        <el-table-column label="时间" align="center" width="280">
+          <template #default="scope">
+            <div>开始时间:{{ scope.row.startTime }}</div>
+            <div>到期时间:{{ scope.row.endTime }}</div>
+          </template>
+        </el-table-column>
+        <el-table-column label="上线/下线" align="center" width="100">
+          <template #default="scope">
+            <el-tag :type="scope.row.status === 1 ? 'success' : 'danger'">
+              {{ scope.row.status === 1 ? '上线' : '下线' }}
+            </el-tag>
+          </template>
+        </el-table-column>
+        <el-table-column label="排序" align="center" prop="sort" width="80" />
+        <el-table-column label="链接" align="center" prop="link" :show-overflow-tooltip="true" min-width="200" />
+        <el-table-column label="操作" align="center" width="150" class-name="small-padding fixed-width">
+          <template #default="scope">
+            <el-button link type="primary" icon="Edit" @click="handleUpdate(scope.row)">编辑</el-button>
+            <el-button link type="danger" icon="Delete" @click="handleDelete(scope.row)">删除</el-button>
+          </template>
+        </el-table-column>
+      </el-table>
+
+      <!-- 分页 -->
+      <pagination
+        v-show="total > 0"
+        v-model:page="queryParams.pageNum"
+        v-model:limit="queryParams.pageSize"
+        :total="total"
+        @pagination="getList"
+      />
+    </el-card>
+
+    <!-- 添加或修改对话框 -->
+    <el-dialog v-model="dialog.visible" :title="dialog.title" width="600px" append-to-body @close="cancel">
+      <el-form ref="carouselFormRef" :model="form" :rules="rules" label-width="100px">
+        <el-form-item label="广告名称" prop="title">
+          <el-input v-model="form.title" placeholder="请输入广告名称" />
+        </el-form-item>
+        <el-form-item label="广告图片" prop="imageUrl">
+          <image-upload v-model="form.imageUrl" :limit="1" />
+        </el-form-item>
+        <el-form-item label="开始时间" prop="startTime">
+          <el-date-picker
+            v-model="form.startTime"
+            type="date"
+            placeholder="选择开始时间"
+            value-format="YYYY-MM-DD"
+            style="width: 100%"
+          />
+        </el-form-item>
+        <el-form-item label="结束时间" prop="endTime">
+          <el-date-picker
+            v-model="form.endTime"
+            type="date"
+            placeholder="选择结束时间"
+            value-format="YYYY-MM-DD"
+            style="width: 100%"
+          />
+        </el-form-item>
+        <el-form-item label="链接地址" prop="link">
+          <el-input v-model="form.link" placeholder="请输入链接地址" />
+        </el-form-item>
+        <el-form-item label="排序" prop="sort">
+          <el-input-number v-model="form.sort" controls-position="right" :min="0" />
+        </el-form-item>
+        <el-form-item label="状态" prop="status">
+          <el-radio-group v-model="form.status">
+            <el-radio :value="1">上线</el-radio>
+            <el-radio :value="0">下线</el-radio>
+          </el-radio-group>
+        </el-form-item>
+      </el-form>
+      <template #footer>
+        <div class="dialog-footer">
+          <el-button type="primary" @click="submitForm">确 定</el-button>
+          <el-button @click="cancel">取 消</el-button>
+        </div>
+      </template>
+    </el-dialog>
+  </div>
+</template>
+
+<script setup name="IndustrialCarousel" lang="ts">
+import { listAdContent, addAdContent, updateAdContent, delAdContent } from '@/api/ad/content';
+import dayjs from 'dayjs';
+
+const { proxy } = getCurrentInstance() as ComponentInternalInstance;
+
+const carouselList = ref<any[]>([]);
+const loading = ref(false);
+const total = ref(0);
+const showSearch = ref(true);
+const dateRange = ref<[string, string]>(['', '']);
+
+const queryParams = ref({
+  pageNum: 1,
+  pageSize: 10,
+  adName: '',
+  startTime: '',
+  endTime: ''
+});
+
+const dialog = reactive<DialogOption>({
+  visible: false,
+  title: ''
+});
+
+const carouselFormRef = ref<ElFormInstance>();
+
+const initFormData = {
+  id: undefined as number | undefined,
+  adType: 'industrial_banner',
+  title: '',
+  imageUrl: '',
+  startTime: '',
+  endTime: '',
+  link: '',
+  color: '#ffffff',
+  remark: '',
+  extJson: '{}',
+  sort: 0,
+  status: 1
+};
+
+const form = ref({ ...initFormData });
+
+const rules = reactive({
+  title: [{ required: true, message: '广告名称不能为空', trigger: 'blur' }],
+  startTime: [{ required: true, message: '开始时间不能为空', trigger: 'change' }],
+  endTime: [{ required: true, message: '结束时间不能为空', trigger: 'change' }]
+});
+
+/** 查询列表 */
+const getList = async () => {
+  loading.value = true;
+  const params: any = {
+    pageNum: queryParams.value.pageNum,
+    pageSize: queryParams.value.pageSize,
+    title: queryParams.value.adName,
+    adType: 'industrial_banner'
+  };
+  if (dateRange.value && dateRange.value[0] && dateRange.value[1]) {
+    params.startTime = dateRange.value[0];
+    params.endTime = dateRange.value[1];
+  }
+  const res = await listAdContent(params);
+  carouselList.value = res.rows || [];
+  total.value = res.total || 0;
+  loading.value = false;
+};
+
+/** 搜索按钮操作 */
+const handleQuery = () => {
+  queryParams.value.pageNum = 1;
+  getList();
+};
+
+/** 重置按钮操作 */
+const resetQuery = () => {
+  dateRange.value = ['', ''];
+  queryParams.value.adName = '';
+  queryParams.value.startTime = '';
+  queryParams.value.endTime = '';
+  handleQuery();
+};
+
+/** 取消按钮 */
+const cancel = () => {
+  reset();
+  dialog.visible = false;
+};
+
+/** 表单重置 */
+const reset = () => {
+  form.value = { ...initFormData };
+  carouselFormRef.value?.resetFields();
+};
+
+/** 新增按钮操作 */
+const handleAdd = () => {
+  reset();
+  const today = dayjs().format('YYYY-MM-DD');
+  const future = dayjs().add(30, 'day').format('YYYY-MM-DD');
+  form.value.startTime = today;
+  form.value.endTime = future;
+  dialog.visible = true;
+  dialog.title = '添加广告';
+};
+
+/** 修改按钮操作 */
+const handleUpdate = (row: any) => {
+  reset();
+  form.value = {
+    ...row,
+    adType: 'industrial_banner',
+    status: row.status ?? 1,
+    color: row.color || '#ffffff',
+    remark: row.remark || '',
+    extJson: row.extJson || '{}',
+    startTime: row.startTime ? dayjs(row.startTime).format('YYYY-MM-DD') : '',
+    endTime: row.endTime ? dayjs(row.endTime).format('YYYY-MM-DD') : ''
+  };
+  dialog.visible = true;
+  dialog.title = '修改广告';
+};
+
+/** 提交按钮 */
+const submitForm = () => {
+  carouselFormRef.value?.validate((valid: boolean) => {
+    if (valid) {
+      const api = form.value.id ? updateAdContent : addAdContent;
+      api(form.value).then(() => {
+        proxy?.$modal.msgSuccess(form.value.id ? '修改成功' : '新增成功');
+        dialog.visible = false;
+        getList();
+      });
+    }
+  });
+};
+
+/** 删除按钮操作 */
+const handleDelete = (row: any) => {
+  proxy?.$modal.confirm('是否确认删除广告名称为"' + row.title + '"的数据项?').then(() => {
+    delAdContent(row.id).then(() => {
+      proxy?.$modal.msgSuccess('删除成功');
+      getList();
+    });
+  });
+};
+
+onMounted(() => {
+  getList();
+});
+</script>
+
+<style scoped lang="scss">
+.carousel-container {
+  height: 100%;
+}
+
+.table-title {
+  font-size: 16px;
+  font-weight: 600;
+  color: #303133;
+  padding: 0 4px;
+}
+</style>

+ 386 - 0
动态路由代码示例.md

@@ -0,0 +1,386 @@
+# 动态路由代码示例
+
+## 📦 简化版代码示例
+
+### 1. 路由守卫(简化版)
+
+```typescript
+// src/permission.ts (简化版)
+
+router.beforeEach(async (to, from, next) => {
+  // 1. 检查是否已登录
+  if (getToken()) {
+    // 2. 检查用户信息是否已加载
+    if (useUserStore().roles.length === 0) {
+      // 3. 获取用户信息
+      await useUserStore().getInfo();
+      
+      // 4. 生成动态路由
+      const routes = await usePermissionStore().generateRoutes();
+      
+      // 5. 注册路由
+      routes.forEach(route => {
+        router.addRoute(route);
+      });
+      
+      // 6. 重新跳转
+      next({ ...to, replace: true });
+    } else {
+      next();
+    }
+  } else {
+    next('/login');
+  }
+});
+```
+
+---
+
+### 2. 生成路由(简化版)
+
+```typescript
+// src/store/modules/permission.ts (简化版)
+
+const generateRoutes = async () => {
+  // 步骤1: 调用后端接口
+  const res = await getRouters();
+  const menuData = res.data;
+  
+  // 步骤2: 转换路由格式
+  const routes = transformRoutes(menuData);
+  
+  // 步骤3: 保存到 Store
+  setSidebarRouters(routes);
+  
+  return routes;
+};
+
+// 转换函数(简化版)
+const transformRoutes = (menuData) => {
+  return menuData.map(menu => {
+    const route = {
+      name: menu.name,
+      path: menu.path,
+      component: loadComponent(menu.component), // 动态加载组件
+      meta: menu.meta,
+      children: menu.children ? transformRoutes(menu.children) : undefined
+    };
+    
+    // 处理特殊组件
+    if (menu.component === 'Layout') {
+      route.component = Layout;
+    }
+    
+    return route;
+  });
+};
+
+// 加载组件(简化版)
+const loadComponent = (componentPath) => {
+  // componentPath: 'system/user/index'
+  // 返回: () => import('@/views/system/user/index.vue')
+  return () => import(`@/views/${componentPath}.vue`);
+};
+```
+
+---
+
+### 3. 后端返回数据示例
+
+```json
+// GET /system/menu/getRouters 返回的数据
+
+[
+  {
+    "name": "System",
+    "path": "/system",
+    "component": "Layout",
+    "redirect": "/system/user",
+    "alwaysShow": true,
+    "meta": {
+      "title": "系统管理",
+      "icon": "system"
+    },
+    "children": [
+      {
+        "name": "User",
+        "path": "user",
+        "component": "system/user/index",
+        "meta": {
+          "title": "用户管理",
+          "icon": "user"
+        }
+      }
+    ]
+  },
+  {
+    "name": "Platform",
+    "path": "/platform",
+    "component": "Layout",
+    "redirect": "/platform/carousel",
+    "alwaysShow": true,
+    "meta": {
+      "title": "平台装修",
+      "icon": "system"
+    },
+    "children": [
+      {
+        "name": "Carousel",
+        "path": "carousel",
+        "component": "platform/decoration/carousel/index",
+        "meta": {
+          "title": "轮播广告",
+          "icon": "component"
+        }
+      }
+    ]
+  }
+]
+```
+
+---
+
+### 4. 转换后的路由对象示例
+
+```typescript
+// 转换后的路由对象(Vue Router 格式)
+
+[
+  {
+    name: "System",
+    path: "/system",
+    component: Layout, // 已转换为组件对象
+    redirect: "/system/user",
+    alwaysShow: true,
+    meta: {
+      title: "系统管理",
+      icon: "system"
+    },
+    children: [
+      {
+        name: "User",
+        path: "user",
+        component: () => import('@/views/system/user/index.vue'), // 动态导入
+        meta: {
+          title: "用户管理",
+          icon: "user"
+        }
+      }
+    ]
+  },
+  {
+    name: "Platform",
+    path: "/platform",
+    component: Layout,
+    redirect: "/platform/carousel",
+    alwaysShow: true,
+    meta: {
+      title: "平台装修",
+      icon: "system"
+    },
+    children: [
+      {
+        name: "Carousel",
+        path: "carousel",
+        component: () => import('@/views/platform/decoration/carousel/index.vue'),
+        meta: {
+          title: "轮播广告",
+          icon: "component"
+        }
+      }
+    ]
+  }
+]
+```
+
+---
+
+### 5. 注册路由示例
+
+```typescript
+// 在路由守卫中注册路由
+
+const routes = await generateRoutes();
+
+routes.forEach(route => {
+  // 将路由添加到 Vue Router
+  router.addRoute(route);
+  
+  // 现在可以通过以下方式访问:
+  // router.push('/system/user')
+  // router.push('/platform/carousel')
+});
+```
+
+---
+
+### 6. 侧边栏使用路由示例
+
+```vue
+<!-- src/layout/components/Sidebar/index.vue -->
+
+<template>
+  <el-menu :default-active="activeMenu">
+    <template v-for="route in sidebarRouters" :key="route.path">
+      <!-- 有子菜单 -->
+      <el-sub-menu v-if="route.children" :index="route.path">
+        <template #title>
+          <el-icon><component :is="route.meta.icon" /></el-icon>
+          <span>{{ route.meta.title }}</span>
+        </template>
+        <el-menu-item 
+          v-for="child in route.children" 
+          :key="child.path"
+          :index="child.path"
+          @click="handleMenuClick(child)"
+        >
+          {{ child.meta.title }}
+        </el-menu-item>
+      </el-sub-menu>
+      
+      <!-- 无子菜单 -->
+      <el-menu-item v-else :index="route.path" @click="handleMenuClick(route)">
+        <el-icon><component :is="route.meta.icon" /></el-icon>
+        <span>{{ route.meta.title }}</span>
+      </el-menu-item>
+    </template>
+  </el-menu>
+</template>
+
+<script setup>
+import { usePermissionStore } from '@/store/modules/permission';
+import { useRouter } from 'vue-router';
+
+const permissionStore = usePermissionStore();
+const router = useRouter();
+
+// 获取侧边栏路由
+const sidebarRouters = computed(() => {
+  return permissionStore.getSidebarRoutes();
+});
+
+// 菜单点击事件
+const handleMenuClick = (route) => {
+  router.push(route.path);
+};
+</script>
+```
+
+---
+
+## 🔄 完整流程示例
+
+### 场景:用户登录后访问 `/platform/carousel`
+
+```typescript
+// 步骤1: 用户访问 /platform/carousel
+router.push('/platform/carousel');
+
+// 步骤2: 路由守卫拦截
+router.beforeEach(async (to, from, next) => {
+  // to.path = '/platform/carousel'
+  
+  // 步骤3: 检查 Token
+  if (getToken()) {
+    // 步骤4: 检查用户信息
+    if (useUserStore().roles.length === 0) {
+      // 步骤5: 获取用户信息
+      await useUserStore().getInfo();
+      
+      // 步骤6: 调用后端接口
+      const res = await getRouters();
+      // res.data = [{ name: "Platform", path: "/platform", ... }]
+      
+      // 步骤7: 转换路由
+      const routes = await generateRoutes();
+      // routes = [{ name: "Platform", component: Layout, ... }]
+      
+      // 步骤8: 注册路由
+      routes.forEach(route => {
+        router.addRoute(route);
+      });
+      
+      // 步骤9: 重新跳转
+      next({ path: '/platform/carousel', replace: true });
+    } else {
+      // 用户信息已加载,直接放行
+      next();
+    }
+  }
+});
+
+// 步骤10: 路由匹配成功,显示页面
+// <router-view> 渲染 Carousel 组件
+```
+
+---
+
+## 📊 数据流转图
+
+```
+后端数据库 (sys_menu)
+    ↓
+后端接口 (/system/menu/getRouters)
+    ↓
+前端 API (getRouters())
+    ↓
+路由转换 (filterAsyncRouter)
+    ↓
+组件加载 (loadView)
+    ↓
+路由注册 (router.addRoute)
+    ↓
+侧边栏显示 (sidebarRouters)
+    ↓
+页面渲染 (<router-view>)
+```
+
+---
+
+## 🎯 关键函数说明
+
+### `generateRoutes()`
+- **作用**:生成动态路由的主函数
+- **输入**:无(内部调用后端接口)
+- **输出**:路由对象数组
+- **流程**:获取数据 → 转换格式 → 保存到 Store → 返回路由
+
+### `filterAsyncRouter()`
+- **作用**:将后端 JSON 转换为 Vue Router 格式
+- **输入**:后端返回的路由 JSON 数组
+- **输出**:Vue Router 路由对象数组
+- **关键**:处理组件加载、递归处理子路由
+
+### `loadView()`
+- **作用**:根据路径字符串动态加载 Vue 组件
+- **输入**:组件路径字符串(如 `'system/user/index'`)
+- **输出**:组件导入函数(如 `() => import('@/views/system/user/index.vue')`)
+- **原理**:使用 `import.meta.glob()` 预扫描所有组件文件
+
+### `router.addRoute()`
+- **作用**:将路由添加到 Vue Router 实例
+- **输入**:路由对象
+- **输出**:无
+- **效果**:路由立即可用,可以通过 `router.push()` 访问
+
+---
+
+## 💡 常见问题
+
+### Q1: 为什么需要重新跳转?
+**A**: 因为路由是异步注册的,需要确保路由已注册后再跳转,否则会 404。
+
+### Q2: 组件路径如何匹配?
+**A**: 后端返回的 `component` 路径(如 `'system/user/index'`)必须对应 `views` 目录下的文件路径(`views/system/user/index.vue`)。
+
+### Q3: 路由名称重复会怎样?
+**A**: `duplicateRouteChecker` 会检查并报错,可能导致路由冲突和 404。
+
+### Q4: 如何实现权限控制?
+**A**: 后端根据用户角色和权限过滤菜单数据,前端只显示后端返回的菜单。
+
+### Q5: 静态路由和动态路由的区别?
+**A**: 
+- **静态路由**:写在 `router/index.ts` 中,所有用户都能访问
+- **动态路由**:从后端获取,根据用户权限动态生成
+

+ 591 - 0
动态路由流程说明.md

@@ -0,0 +1,591 @@
+# 动态路由流程详解
+
+## 📋 概述
+
+动态路由是指根据用户权限从后端获取菜单数据,然后在前端动态生成和注册路由的过程。这样可以实现基于角色的权限控制(RBAC),不同角色的用户看到不同的菜单和页面。
+
+---
+
+## 🔄 完整流程
+
+```
+用户登录 
+  ↓
+路由守卫拦截 (permission.ts)
+  ↓
+检查是否有 Token
+  ↓
+检查用户信息是否已加载 (roles.length === 0?)
+  ↓
+调用后端接口获取菜单数据 (/system/menu/getRouters)
+  ↓
+后端返回菜单 JSON 数据
+  ↓
+前端转换路由格式 (filterAsyncRouter)
+  ↓
+动态加载 Vue 组件 (loadView)
+  ↓
+注册路由到 Vue Router (router.addRoute)
+  ↓
+更新侧边栏菜单 (setSidebarRouters)
+  ↓
+跳转到目标页面
+```
+
+---
+
+## 📝 详细代码流程
+
+### 1️⃣ 路由守卫拦截 (`src/permission.ts`)
+
+**作用**:在每次路由跳转前进行检查,确保用户已登录且路由已加载。
+
+```typescript
+// src/permission.ts
+router.beforeEach(async (to, from, next) => {
+  NProgress.start(); // 显示加载进度条
+  
+  if (getToken()) {
+    // ✅ 有 Token,已登录
+    if (to.path === '/login') {
+      // 已登录用户访问登录页,重定向到首页
+      next({ path: '/' });
+    } else if (isWhiteList(to.path)) {
+      // 白名单路径,直接放行
+      next();
+    } else {
+      // 🔑 关键:检查用户信息是否已加载
+      if (useUserStore().roles.length === 0) {
+        // 用户信息未加载,需要获取用户信息和路由
+        isRelogin.show = true;
+        
+        // 1. 获取用户信息
+        const [err] = await tos(useUserStore().getInfo());
+        if (err) {
+          // 获取失败,退出登录
+          await useUserStore().logout();
+          next({ path: '/' });
+        } else {
+          isRelogin.show = false;
+          
+          // 2. 🚀 生成动态路由(核心步骤)
+          const accessRoutes = await usePermissionStore().generateRoutes();
+          
+          // 3. 将路由添加到 Vue Router
+          accessRoutes.forEach((route) => {
+            if (!isHttp(route.path)) {
+              router.addRoute(route); // 动态注册路由
+            }
+          });
+          
+          // 4. 重新跳转到目标页面(确保路由已注册)
+          next({ 
+            path: to.path, 
+            replace: true,
+            params: to.params,
+            query: to.query,
+            hash: to.hash,
+            name: to.name as string
+          });
+        }
+      } else {
+        // 用户信息已加载,直接放行
+        next();
+      }
+    }
+  } else {
+    // ❌ 没有 Token,未登录
+    if (isWhiteList(to.path)) {
+      next(); // 白名单路径
+    } else {
+      // 重定向到登录页
+      const redirect = encodeURIComponent(to.fullPath || '/');
+      next(`/login?redirect=${redirect}`);
+    }
+  }
+});
+```
+
+---
+
+### 2️⃣ 调用后端接口获取菜单 (`src/api/menu.ts`)
+
+**作用**:向后端请求当前用户有权限的菜单数据。
+
+```typescript
+// src/api/menu.ts
+export function getRouters(): AxiosPromise<RouteRecordRaw[]> {
+  return request({
+    url: '/system/menu/getRouters',  // 后端接口地址
+    method: 'get'
+  });
+}
+```
+
+**后端返回的数据格式示例**:
+```json
+[
+  {
+    "name": "System",
+    "path": "/system",
+    "component": "Layout",
+    "redirect": "/system/user",
+    "alwaysShow": true,
+    "meta": {
+      "title": "系统管理",
+      "icon": "system"
+    },
+    "children": [
+      {
+        "name": "User",
+        "path": "user",
+        "component": "system/user/index",
+        "meta": {
+          "title": "用户管理",
+          "icon": "user"
+        }
+      },
+      {
+        "name": "Role",
+        "path": "role",
+        "component": "system/role/index",
+        "meta": {
+          "title": "角色管理",
+          "icon": "peoples"
+        }
+      }
+    ]
+  },
+  {
+    "name": "Platform",
+    "path": "/platform",
+    "component": "Layout",
+    "redirect": "/platform/carousel",
+    "alwaysShow": true,
+    "meta": {
+      "title": "平台装修",
+      "icon": "system"
+    },
+    "children": [
+      {
+        "name": "Carousel",
+        "path": "carousel",
+        "component": "platform/decoration/carousel/index",
+        "meta": {
+          "title": "轮播广告",
+          "icon": "component"
+        }
+      }
+    ]
+  }
+]
+```
+
+---
+
+### 3️⃣ 生成动态路由 (`src/store/modules/permission.ts`)
+
+**作用**:将后端返回的菜单 JSON 转换为 Vue Router 可用的路由对象。
+
+```typescript
+// src/store/modules/permission.ts
+
+const generateRoutes = async (): Promise<RouteRecordRaw[]> => {
+  // 1. 📡 调用后端接口获取菜单数据
+  const res = await getRouters();
+  const { data } = res;
+  
+  // 2. 📋 深拷贝数据(用于不同用途)
+  const sdata = JSON.parse(JSON.stringify(data));  // 侧边栏路由
+  const rdata = JSON.parse(JSON.stringify(data));   // 重写路由(扁平化)
+  const defaultData = JSON.parse(JSON.stringify(data)); // 默认路由
+  
+  // 3. 🔄 转换路由格式
+  const sidebarRoutes = filterAsyncRouter(sdata);        // 侧边栏菜单
+  const rewriteRoutes = filterAsyncRouter(rdata, undefined, true); // 扁平化路由
+  const defaultRoutes = filterAsyncRouter(defaultData);  // 默认路由
+  
+  // 4. 🔐 处理动态路由权限(如果有)
+  const asyncRoutes = filterDynamicRoutes(dynamicRoutes);
+  asyncRoutes.forEach((route) => {
+    router.addRoute(route);
+  });
+  
+  // 5. 💾 保存路由到 Store
+  setRoutes(rewriteRoutes);                              // 保存所有路由
+  setSidebarRouters(constantRoutes.concat(sidebarRoutes)); // 侧边栏路由
+  setDefaultRoutes(sidebarRoutes);                       // 默认路由
+  setTopbarRoutes(defaultRoutes);                        // 顶部栏路由
+  
+  // 6. ✅ 检查路由名称是否重复
+  duplicateRouteChecker(asyncRoutes, sidebarRoutes);
+  
+  return rewriteRoutes;
+};
+```
+
+---
+
+### 4️⃣ 转换路由格式 (`filterAsyncRouter`)
+
+**作用**:将后端返回的路由字符串转换为 Vue Router 组件对象。
+
+```typescript
+// src/store/modules/permission.ts
+
+const filterAsyncRouter = (
+  asyncRouterMap: RouteRecordRaw[], 
+  lastRouter?: RouteRecordRaw, 
+  type = false
+): RouteRecordRaw[] => {
+  return asyncRouterMap.filter((route) => {
+    // 1. 🔄 如果是重写路由,扁平化子路由
+    if (type && route.children) {
+      route.children = filterChildren(route.children, undefined);
+    }
+    
+    // 2. 🎨 处理特殊组件
+    if (route.component?.toString() === 'Layout') {
+      // Layout 组件:主布局容器
+      route.component = Layout;
+    } else if (route.component?.toString() === 'ParentView') {
+      // ParentView 组件:父级视图容器(用于嵌套路由)
+      route.component = ParentView;
+    } else if (route.component?.toString() === 'InnerLink') {
+      // InnerLink 组件:内部链接
+      route.component = InnerLink;
+    } else {
+      // 3. 📦 动态加载 Vue 组件
+      // 例如:'system/user/index' → 加载 '@/views/system/user/index.vue'
+      route.component = loadView(route.component, route.name as string);
+    }
+    
+    // 4. 🔁 递归处理子路由
+    if (route.children != null && route.children && route.children.length) {
+      route.children = filterAsyncRouter(route.children, route, type);
+    } else {
+      // 没有子路由,删除空属性
+      delete route.children;
+      delete route.redirect;
+    }
+    
+    return true;
+  });
+};
+```
+
+**转换示例**:
+
+**转换前(后端返回)**:
+```json
+{
+  "name": "User",
+  "path": "user",
+  "component": "system/user/index",
+  "meta": {
+    "title": "用户管理",
+    "icon": "user"
+  }
+}
+```
+
+**转换后(Vue Router 格式)**:
+```typescript
+{
+  name: "User",
+  path: "user",
+  component: () => import('@/views/system/user/index.vue'), // 动态导入组件
+  meta: {
+    title: "用户管理",
+    icon: "user"
+  }
+}
+```
+
+---
+
+### 5️⃣ 动态加载 Vue 组件 (`loadView`)
+
+**作用**:根据组件路径字符串,动态加载对应的 Vue 组件文件。
+
+```typescript
+// src/store/modules/permission.ts
+
+// 匹配 views 目录下所有的 .vue 文件
+const modules = import.meta.glob('./../../views/**/*.vue');
+
+export const loadView = (view: any, name: string) => {
+  let res;
+  
+  // 遍历所有已匹配的组件文件
+  for (const path in modules) {
+    // 例如:path = './../../views/system/user/index.vue'
+    const viewsIndex = path.indexOf('/views/');
+    let dir = path.substring(viewsIndex + 7); // 'system/user/index.vue'
+    dir = dir.substring(0, dir.lastIndexOf('.vue')); // 'system/user/index'
+    
+    // 匹配组件路径
+    if (dir === view) {
+      // 创建自定义名称组件
+      res = createCustomNameComponent(modules[path], { name });
+      return res;
+    }
+  }
+  
+  return res;
+};
+```
+
+**工作原理**:
+1. `import.meta.glob()` 在构建时扫描所有 `.vue` 文件,生成一个映射对象
+2. 根据后端返回的组件路径(如 `'system/user/index'`),在映射中查找对应的文件
+3. 返回一个动态导入函数,Vue Router 会在需要时加载该组件
+
+**modules 对象示例**:
+```typescript
+{
+  './../../views/system/user/index.vue': () => import('./../../views/system/user/index.vue'),
+  './../../views/system/role/index.vue': () => import('./../../views/system/role/index.vue'),
+  './../../views/platform/decoration/carousel/index.vue': () => import('./../../views/platform/decoration/carousel/index.vue'),
+  // ... 更多组件
+}
+```
+
+---
+
+### 6️⃣ 扁平化子路由 (`filterChildren`)
+
+**作用**:将嵌套的子路由扁平化,用于路由重写。
+
+```typescript
+// src/store/modules/permission.ts
+
+const filterChildren = (
+  childrenMap: RouteRecordRaw[], 
+  lastRouter?: RouteRecordRaw
+): RouteRecordRaw[] => {
+  let children: RouteRecordRaw[] = [];
+  
+  childrenMap.forEach((el) => {
+    // 拼接完整路径
+    // 例如:父路由 '/platform' + 子路由 'carousel' = '/platform/carousel'
+    el.path = lastRouter ? lastRouter.path + '/' + el.path : el.path;
+    
+    // 如果子路由还有子路由,且使用 ParentView,继续递归扁平化
+    if (el.children && el.children.length && el.component?.toString() === 'ParentView') {
+      children = children.concat(filterChildren(el.children, el));
+    } else {
+      children.push(el);
+    }
+  });
+  
+  return children;
+};
+```
+
+**示例**:
+
+**扁平化前**:
+```typescript
+{
+  path: '/platform',
+  children: [
+    {
+      path: 'decoration',
+      children: [
+        { path: 'carousel' },
+        { path: 'solution' }
+      ]
+    }
+  ]
+}
+```
+
+**扁平化后**:
+```typescript
+[
+  { path: '/platform/decoration/carousel' },
+  { path: '/platform/decoration/solution' }
+]
+```
+
+---
+
+### 7️⃣ 注册路由到 Vue Router
+
+**作用**:将转换后的路由添加到 Vue Router 实例中。
+
+```typescript
+// src/permission.ts (在路由守卫中)
+
+const accessRoutes = await usePermissionStore().generateRoutes();
+
+// 遍历所有路由,添加到 Vue Router
+accessRoutes.forEach((route) => {
+  if (!isHttp(route.path)) {
+    router.addRoute(route); // 动态注册路由
+  }
+});
+```
+
+**`router.addRoute()` 的作用**:
+- 将路由添加到路由表中
+- 支持嵌套路由
+- 支持命名路由
+- 支持路由元信息(meta)
+
+---
+
+### 8️⃣ 更新侧边栏菜单
+
+**作用**:将生成的路由保存到 Store,供侧边栏组件使用。
+
+```typescript
+// src/store/modules/permission.ts
+
+setSidebarRouters(constantRoutes.concat(sidebarRoutes));
+```
+
+**侧边栏组件使用**:
+```vue
+<!-- src/layout/components/Sidebar/index.vue -->
+<template>
+  <el-menu :default-active="activeMenu">
+    <sidebar-item 
+      v-for="route in sidebarRouters" 
+      :key="route.path" 
+      :item="route"
+    />
+  </el-menu>
+</template>
+
+<script setup>
+import { usePermissionStore } from '@/store/modules/permission';
+
+const permissionStore = usePermissionStore();
+const sidebarRouters = computed(() => permissionStore.getSidebarRoutes());
+</script>
+```
+
+---
+
+## 🎯 完整示例:平台装修菜单
+
+### 后端返回的数据:
+```json
+{
+  "name": "Platform",
+  "path": "/platform",
+  "component": "Layout",
+  "redirect": "/platform/carousel",
+  "alwaysShow": true,
+  "meta": {
+    "title": "平台装修",
+    "icon": "system"
+  },
+  "children": [
+    {
+      "name": "Carousel",
+      "path": "carousel",
+      "component": "platform/decoration/carousel/index",
+      "meta": {
+        "title": "轮播广告",
+        "icon": "component"
+      }
+    },
+    {
+      "name": "Solution",
+      "path": "solution",
+      "component": "platform/decoration/solution/index",
+      "meta": {
+        "title": "方案管理",
+        "icon": "documentation"
+      }
+    }
+  ]
+}
+```
+
+### 转换后的路由对象:
+```typescript
+{
+  name: "Platform",
+  path: "/platform",
+  component: Layout, // 已转换为组件对象
+  redirect: "/platform/carousel",
+  alwaysShow: true,
+  meta: {
+    title: "平台装修",
+    icon: "system"
+  },
+  children: [
+    {
+      name: "Carousel",
+      path: "carousel",
+      component: () => import('@/views/platform/decoration/carousel/index.vue'),
+      meta: {
+        title: "轮播广告",
+        icon: "component"
+      }
+    },
+    {
+      name: "Solution",
+      path: "solution",
+      component: () => import('@/views/platform/decoration/solution/index.vue'),
+      meta: {
+        title: "方案管理",
+        icon: "documentation"
+      }
+    }
+  ]
+}
+```
+
+### 扁平化后的路由(用于路由重写):
+```typescript
+[
+  {
+    name: "Carousel",
+    path: "/platform/carousel",
+    component: () => import('@/views/platform/decoration/carousel/index.vue'),
+    meta: { title: "轮播广告", icon: "component" }
+  },
+  {
+    name: "Solution",
+    path: "/platform/solution",
+    component: () => import('@/views/platform/decoration/solution/index.vue'),
+    meta: { title: "方案管理", icon: "documentation" }
+  }
+]
+```
+
+---
+
+## 🔍 关键点总结
+
+1. **路由守卫**:在每次路由跳转前检查并加载动态路由
+2. **后端接口**:获取用户有权限的菜单数据
+3. **路由转换**:将 JSON 数据转换为 Vue Router 格式
+4. **组件加载**:动态导入 Vue 组件文件
+5. **路由注册**:使用 `router.addRoute()` 添加到路由表
+6. **菜单更新**:更新侧边栏菜单显示
+
+---
+
+## ⚠️ 注意事项
+
+1. **路由名称不能重复**:`duplicateRouteChecker` 会检查并报错
+2. **组件路径必须正确**:后端返回的 `component` 路径必须对应 `views` 目录下的文件
+3. **首次加载**:只在用户信息未加载时(`roles.length === 0`)才生成路由
+4. **路由重定向**:生成路由后需要重新跳转,确保路由已注册
+5. **平台标识**:后端会根据 `X-Platform-Code` 头过滤菜单
+
+---
+
+## 🚀 优势
+
+1. **权限控制**:不同角色看到不同的菜单和页面
+2. **灵活配置**:菜单配置在数据库中,无需修改代码
+3. **代码分割**:使用动态导入,实现按需加载
+4. **易于维护**:菜单结构清晰,易于管理
+