Browse Source

Merge remote-tracking branch 'origin/master' into master

肖路 3 weeks ago
parent
commit
41fb14e142
69 changed files with 9517 additions and 2256 deletions
  1. 30 0
      src/api/diy/index.ts
  2. 73 0
      src/api/enterprisePurchase/adLeft/index.ts
  3. 86 0
      src/api/enterprisePurchase/adLeft/types.ts
  4. 63 0
      src/api/enterprisePurchase/adModuleConfig/index.ts
  5. 174 0
      src/api/enterprisePurchase/adModuleConfig/types.ts
  6. 63 0
      src/api/enterprisePurchase/adModuleItem/index.ts
  7. 174 0
      src/api/enterprisePurchase/adModuleItem/types.ts
  8. 75 0
      src/api/enterprisePurchase/carousel/index.ts
  9. 116 0
      src/api/enterprisePurchase/carousel/types.ts
  10. 75 0
      src/api/enterprisePurchase/categoryMain/index.ts
  11. 140 0
      src/api/enterprisePurchase/categoryMain/types.ts
  12. 75 0
      src/api/enterprisePurchase/headerCategory/index.ts
  13. 116 0
      src/api/enterprisePurchase/headerCategory/types.ts
  14. 75 0
      src/api/enterprisePurchase/quickEntryItems/index.ts
  15. 146 0
      src/api/enterprisePurchase/quickEntryItems/types.ts
  16. 63 0
      src/api/enterprisePurchase/quickEntryModule/index.ts
  17. 71 0
      src/api/enterprisePurchase/quickEntryModule/types.ts
  18. 79 0
      src/api/enterprisePurchase/recommendCategoryConfig/index.ts
  19. 172 0
      src/api/enterprisePurchase/recommendCategoryConfig/types.ts
  20. 63 0
      src/api/enterprisePurchase/recommendThemeConfig/index.ts
  21. 65 0
      src/api/enterprisePurchase/recommendThemeConfig/types.ts
  22. 63 0
      src/api/enterprisePurchase/scenarioCards/index.ts
  23. 131 0
      src/api/enterprisePurchase/scenarioCards/types.ts
  24. 67 0
      src/api/enterprisePurchase/scenarioGlobalSettings/index.ts
  25. 116 0
      src/api/enterprisePurchase/scenarioGlobalSettings/types.ts
  26. 73 0
      src/api/enterprisePurchase/searchConfig/index.ts
  27. 198 0
      src/api/enterprisePurchase/searchConfig/types.ts
  28. 3 3
      src/api/mall/pageCategory/api.ts
  29. BIN
      src/assets/images/diy/category_style1_1.png
  30. BIN
      src/assets/images/diy/category_style2_1.png
  31. BIN
      src/assets/images/diy/category_style2_2.png
  32. 130 28
      src/components/LinkSelector/index.vue
  33. 3 0
      src/components/WebLinkInput/index.vue
  34. 17 8
      src/components/goodsMini/index.vue
  35. 2 2
      src/components/heat-map/index.vue
  36. 655 0
      src/components/heat-mapMini/index.vue
  37. 4 4
      src/views/diy/components/edit-active-cube.vue
  38. 3 98
      src/views/diy/components/edit-carousel-search.vue
  39. 1 1
      src/views/diy/components/edit-float-btn.vue
  40. 0 273
      src/views/diy/components/edit-goods-coupon.vue
  41. 0 405
      src/views/diy/components/edit-goods-list copy.vue
  42. 7 0
      src/views/diy/components/edit-goods-list.vue
  43. 2 2
      src/views/diy/components/edit-graphic-nav.vue
  44. 1 1
      src/views/diy/components/edit-horz-blank.vue
  45. 1 1
      src/views/diy/components/edit-horz-line.vue
  46. 1 1
      src/views/diy/components/edit-hot-area.vue
  47. 2 2
      src/views/diy/components/edit-image-ads.vue
  48. 52 501
      src/views/diy/components/edit-many-goods-list.vue
  49. 2 2
      src/views/diy/components/edit-notice.vue
  50. 2 2
      src/views/diy/components/edit-picture-show.vue
  51. 3 17
      src/views/diy/components/edit-rich-text.vue
  52. 3 2
      src/views/diy/components/edit-rubik-cube.vue
  53. 0 135
      src/views/diy/components/edit-shop-exchange-goods.vue
  54. 0 38
      src/views/diy/components/edit-shop-exchange-info.vue
  55. 31 13
      src/views/diy/components/edit-shop-goods-recommend.vue
  56. 1 1
      src/views/diy/components/edit-shop-search.vue
  57. 37 199
      src/views/diy/components/edit-single-recommend.vue
  58. 1 1
      src/views/diy/components/edit-text.vue
  59. 47 489
      src/views/diy/edit.ts
  60. 1 3
      src/views/diy/edit.vue
  61. 4 4
      src/views/diy/miniEdit.vue
  62. 1 1
      src/views/diy/pccomponents/edit/navigation-edit.vue
  63. 2 2
      src/views/diy/pccomponents/pages/floor.vue
  64. 4 4
      src/views/diy/pccomponents/pages/navigation.vue
  65. 5683 0
      src/views/enterprisePurchase/index.vue
  66. 141 0
      src/views/mall/miniPageSet/index.vue
  67. 7 5
      src/views/mall/navigation/index.vue
  68. 10 4
      src/views/mall/pageCategory/index.vue
  69. 11 4
      src/views/mall/pageLink/index.vue

+ 30 - 0
src/api/diy/index.ts

@@ -175,3 +175,33 @@ export function navigationSave(data: any) {
     data: data
   });
 }
+
+// 查询小程序页面设置列表
+
+export function miniPageSetCurrent(query: any) {
+  return request({
+    url: '/mall/miniPageSet/current',
+    method: 'get',
+    data: query
+  });
+}
+
+// 新增小程序页面设置
+
+export function miniPageSetAdd(data: any) {
+  return request({
+    url: '/mall/miniPageSet',
+    method: 'post',
+    data: data
+  });
+}
+
+// 新增小程序页面设置
+
+export function miniPageSetEdit(data: any) {
+  return request({
+    url: '/mall/miniPageSet',
+    method: 'put',
+    data: data
+  });
+}

+ 73 - 0
src/api/enterprisePurchase/adLeft/index.ts

@@ -0,0 +1,73 @@
+import request from '@/utils/request';
+import { AxiosPromise } from 'axios';
+import { AdLeftVO, AdLeftForm, AdLeftQuery } from '@/api/enterprisePurchase/adLeft/types';
+
+/**
+ * 查询首页左侧广告配置列表
+ * @param query
+ * @returns {*}
+ */
+
+export const listAdLeft = (query?: AdLeftQuery): AxiosPromise<AdLeftVO[]> => {
+  return request({
+    url: '/mall/adLeft/list',
+    method: 'get',
+    params: query
+  });
+};
+
+/**
+ * 查询首页左侧广告配置详细
+ * @param id
+ */
+export const getAdLeft = (id: string | number): AxiosPromise<AdLeftVO> => {
+  return request({
+    url: '/mall/adLeft/' + id,
+    method: 'get'
+  });
+};
+
+/**
+ * 新增首页左侧广告配置
+ * @param data
+ */
+export const addAdLeft = (data: AdLeftForm) => {
+  return request({
+    url: '/mall/adLeft',
+    method: 'post',
+    data: data
+  });
+};
+
+/**
+ * 修改首页左侧广告配置
+ * @param data
+ */
+export const updateAdLeft = (data: AdLeftForm) => {
+  return request({
+    url: '/mall/adLeft',
+    method: 'put',
+    data: data
+  });
+};
+
+/**
+ * 删除首页左侧广告配置
+ * @param id
+ */
+export const delAdLeft = (id: string | number | Array<string | number>) => {
+  return request({
+    url: '/mall/adLeft/' + id,
+    method: 'delete'
+  });
+};
+
+/**
+ * 获取当前配置
+ */
+export const getCurrentAdLeft = () => {
+  return request({
+    url: '/mall/adLeft/current',
+    method: 'get'
+  });
+};

+ 86 - 0
src/api/enterprisePurchase/adLeft/types.ts

@@ -0,0 +1,86 @@
+export interface AdLeftVO {
+  /**
+   * 主键ID
+   */
+  id: string | number;
+
+  /**
+   * 广告图片URL(建议尺寸790*460)
+   */
+  imageUrl: string;
+
+  /**
+   * 跳转地址(全路径)
+   */
+  link: string;
+
+  /**
+   * 状态:1启用 0禁用
+   */
+  status: number;
+
+  /**
+   * 备注
+   */
+  remark: string;
+
+}
+
+export interface AdLeftForm extends BaseEntity {
+  /**
+   * 主键ID
+   */
+  id?: string | number;
+
+  /**
+   * 广告图片URL(建议尺寸790*460)
+   */
+  imageUrl?: string;
+
+  /**
+   * 跳转地址(全路径)
+   */
+  link?: string;
+
+  /**
+   * 状态:1启用 0禁用
+   */
+  status?: number;
+
+  /**
+   * 备注
+   */
+  remark?: string;
+
+}
+
+export interface AdLeftQuery extends PageQuery {
+
+  /**
+   * 广告图片URL(建议尺寸790*460)
+   */
+  imageUrl?: string;
+
+  /**
+   * 跳转地址(全路径)
+   */
+  link?: string;
+
+  /**
+   * 状态:1启用 0禁用
+   */
+  status?: number;
+
+  /**
+   * 平台标识
+   */
+  platformCode?: string;
+
+    /**
+     * 日期范围参数
+     */
+    params?: any;
+}
+
+
+

+ 63 - 0
src/api/enterprisePurchase/adModuleConfig/index.ts

@@ -0,0 +1,63 @@
+import request from '@/utils/request';
+import { AxiosPromise } from 'axios';
+import { AdModuleConfigVO, AdModuleConfigForm, AdModuleConfigQuery } from '@/api/enterprisePurchase/adModuleConfig/types';
+
+/**
+ * 查询广告模块主配置列表
+ * @param query
+ * @returns {*}
+ */
+
+export const listAdModuleConfig = (query?: AdModuleConfigQuery): AxiosPromise<AdModuleConfigVO[]> => {
+  return request({
+    url: '/mall/adModuleConfig/list',
+    method: 'get',
+    params: query
+  });
+};
+
+/**
+ * 查询广告模块主配置详细
+ * @param id
+ */
+export const getAdModuleConfig = (id: string | number): AxiosPromise<AdModuleConfigVO> => {
+  return request({
+    url: '/mall/adModuleConfig/' + id,
+    method: 'get'
+  });
+};
+
+/**
+ * 新增广告模块主配置
+ * @param data
+ */
+export const addAdModuleConfig = (data: AdModuleConfigForm) => {
+  return request({
+    url: '/mall/adModuleConfig',
+    method: 'post',
+    data: data
+  });
+};
+
+/**
+ * 修改广告模块主配置
+ * @param data
+ */
+export const updateAdModuleConfig = (data: AdModuleConfigForm) => {
+  return request({
+    url: '/mall/adModuleConfig',
+    method: 'put',
+    data: data
+  });
+};
+
+/**
+ * 删除广告模块主配置
+ * @param id
+ */
+export const delAdModuleConfig = (id: string | number | Array<string | number>) => {
+  return request({
+    url: '/mall/adModuleConfig/' + id,
+    method: 'delete'
+  });
+};

+ 174 - 0
src/api/enterprisePurchase/adModuleConfig/types.ts

@@ -0,0 +1,174 @@
+export interface AdModuleConfigVO {
+  /**
+   * 主键ID
+   */
+  id: string | number;
+
+  /**
+   * 模块编码
+   */
+  moduleCode: string;
+
+  /**
+   * 模块名称
+   */
+  moduleName: string;
+
+  /**
+   * 主标题
+   */
+  mainTitle: string;
+
+  /**
+   * 主标题颜色
+   */
+  mainTitleColor: string;
+
+  /**
+   * 副标题
+   */
+  subTitle: string;
+
+  /**
+   * 副标题颜色
+   */
+  subTitleColor: string;
+
+  /**
+   * 跳转链接
+   */
+  jumpLink: string;
+
+  /**
+   * 排序
+   */
+  sortOrder: number;
+
+  /**
+   * 状态 (1启用 0禁用)
+   */
+  status: number;
+
+  /**
+   * 备注
+   */
+  remark: string;
+
+  adModuleItemList: any[];
+}
+
+export interface AdModuleConfigForm extends BaseEntity {
+  /**
+   * 主键ID
+   */
+  id?: string | number;
+
+  /**
+   * 模块编码
+   */
+  moduleCode?: string;
+
+  /**
+   * 模块名称
+   */
+  moduleName?: string;
+
+  /**
+   * 主标题
+   */
+  mainTitle?: string;
+
+  /**
+   * 主标题颜色
+   */
+  mainTitleColor?: string;
+
+  /**
+   * 副标题
+   */
+  subTitle?: string;
+
+  /**
+   * 副标题颜色
+   */
+  subTitleColor?: string;
+
+  /**
+   * 跳转链接
+   */
+  jumpLink?: string;
+
+  /**
+   * 排序
+   */
+  sortOrder?: number;
+
+  /**
+   * 状态 (1启用 0禁用)
+   */
+  status?: number;
+
+  /**
+   * 备注
+   */
+  remark?: string;
+
+  adModuleItemList?: any[];
+}
+
+export interface AdModuleConfigQuery extends PageQuery {
+  /**
+   * 模块编码
+   */
+  moduleCode?: string;
+
+  /**
+   * 模块名称
+   */
+  moduleName?: string;
+
+  /**
+   * 主标题
+   */
+  mainTitle?: string;
+
+  /**
+   * 主标题颜色
+   */
+  mainTitleColor?: string;
+
+  /**
+   * 副标题
+   */
+  subTitle?: string;
+
+  /**
+   * 副标题颜色
+   */
+  subTitleColor?: string;
+
+  /**
+   * 跳转链接
+   */
+  jumpLink?: string;
+
+  /**
+   * 排序
+   */
+  sortOrder?: number;
+
+  /**
+   * 状态 (1启用 0禁用)
+   */
+  status?: number;
+
+  /**
+   * 平台标识
+   */
+  platformCode?: string;
+
+  /**
+   * 日期范围参数
+   */
+  params?: any;
+}

+ 63 - 0
src/api/enterprisePurchase/adModuleItem/index.ts

@@ -0,0 +1,63 @@
+import request from '@/utils/request';
+import { AxiosPromise } from 'axios';
+import { AdModuleItemVO, AdModuleItemForm, AdModuleItemQuery } from '@/api/enterprisePurchase/adModuleItem/types';
+
+/**
+ * 查询广告模块子项列表
+ * @param query
+ * @returns {*}
+ */
+
+export const listAdModuleItem = (query?: AdModuleItemQuery): AxiosPromise<AdModuleItemVO[]> => {
+  return request({
+    url: '/mall/adModuleItem/list',
+    method: 'get',
+    params: query
+  });
+};
+
+/**
+ * 查询广告模块子项详细
+ * @param id
+ */
+export const getAdModuleItem = (id: string | number): AxiosPromise<AdModuleItemVO> => {
+  return request({
+    url: '/mall/adModuleItem/' + id,
+    method: 'get'
+  });
+};
+
+/**
+ * 新增广告模块子项
+ * @param data
+ */
+export const addAdModuleItem = (data: AdModuleItemForm) => {
+  return request({
+    url: '/mall/adModuleItem',
+    method: 'post',
+    data: data
+  });
+};
+
+/**
+ * 修改广告模块子项
+ * @param data
+ */
+export const updateAdModuleItem = (data: AdModuleItemForm) => {
+  return request({
+    url: '/mall/adModuleItem',
+    method: 'put',
+    data: data
+  });
+};
+
+/**
+ * 删除广告模块子项
+ * @param id
+ */
+export const delAdModuleItem = (id: string | number | Array<string | number>) => {
+  return request({
+    url: '/mall/adModuleItem/' + id,
+    method: 'delete'
+  });
+};

+ 174 - 0
src/api/enterprisePurchase/adModuleItem/types.ts

@@ -0,0 +1,174 @@
+export interface AdModuleItemVO {
+  /**
+   * 主键ID
+   */
+  id: string | number;
+
+  /**
+   * 关联模块ID
+   */
+  moduleId: string | number;
+
+  brandId: string | number;
+
+  /**
+   * 产品id
+   */
+  productId: string | number;
+
+  /**
+   * 商品或品牌名称
+   */
+  productName: string;
+
+  /**
+   * 图片链接
+   */
+  imageUrl: string;
+
+  /**
+   * 价格
+   */
+  price: number;
+
+  /**
+   * 标签文本
+   */
+  tagText: string;
+
+  /**
+   * 标签链接
+   */
+  tagLink: string;
+
+  /**
+   * 销量
+   */
+  salesCount: number;
+
+  /**
+   * 排序
+   */
+  sortOrder: number;
+
+  /**
+   * 备注
+   */
+  remark: string;
+}
+
+export interface AdModuleItemForm extends BaseEntity {
+  /**
+   * 主键ID
+   */
+  id?: string | number;
+
+  /**
+   * 关联模块ID
+   */
+  moduleId?: string | number;
+
+  brandId?: string | number;
+
+  /**
+   * 产品id
+   */
+  productId?: string | number;
+
+  /**
+   * 商品或品牌名称
+   */
+  productName?: string;
+
+  /**
+   * 图片链接
+   */
+  imageUrl?: string;
+
+  /**
+   * 价格
+   */
+  price?: number;
+
+  /**
+   * 标签文本
+   */
+  tagText?: string;
+
+  /**
+   * 标签链接
+   */
+  tagLink?: string;
+
+  /**
+   * 销量
+   */
+  salesCount?: number;
+
+  /**
+   * 排序
+   */
+  sortOrder?: number;
+
+  /**
+   * 备注
+   */
+  remark?: string;
+}
+
+export interface AdModuleItemQuery extends PageQuery {
+  /**
+   * 关联模块ID
+   */
+  moduleId?: string | number;
+
+  /**
+   * 产品id
+   */
+  productId?: string | number;
+
+  /**
+   * 商品或品牌名称
+   */
+  productName?: string;
+
+  /**
+   * 图片链接
+   */
+  imageUrl?: string;
+
+  /**
+   * 价格
+   */
+  price?: number;
+
+  /**
+   * 标签文本
+   */
+  tagText?: string;
+
+  /**
+   * 标签链接
+   */
+  tagLink?: string;
+
+  /**
+   * 销量
+   */
+  salesCount?: number;
+
+  /**
+   * 排序
+   */
+  sortOrder?: number;
+
+  /**
+   * 平台标识
+   */
+  platformCode?: string;
+
+  /**
+   * 日期范围参数
+   */
+  params?: any;
+}

+ 75 - 0
src/api/enterprisePurchase/carousel/index.ts

@@ -0,0 +1,75 @@
+import request from '@/utils/request';
+import { AxiosPromise } from 'axios';
+import { CarouselVO, CarouselForm, CarouselQuery } from '@/api/enterprisePurchase/carousel/types';
+
+/**
+ * 查询首页广告轮播图配置列表
+ * @param query
+ * @returns {*}
+ */
+
+export const listCarousel = (query?: CarouselQuery): AxiosPromise<CarouselVO[]> => {
+  return request({
+    url: '/mall/carousel/list',
+    method: 'get',
+    params: query
+  });
+};
+
+/**
+ * 查询首页广告轮播图配置详细
+ * @param id
+ */
+export const getCarousel = (id: string | number): AxiosPromise<CarouselVO> => {
+  return request({
+    url: '/mall/carousel/' + id,
+    method: 'get'
+  });
+};
+
+/**
+ * 新增首页广告轮播图配置
+ * @param data
+ */
+export const addCarousel = (data: CarouselForm) => {
+  return request({
+    url: '/mall/carousel',
+    method: 'post',
+    data: data
+  });
+};
+
+/**
+ * 修改首页广告轮播图配置
+ * @param data
+ */
+export const updateCarousel = (data: CarouselForm) => {
+  return request({
+    url: '/mall/carousel',
+    method: 'put',
+    data: data
+  });
+};
+
+/**
+ * 删除首页广告轮播图配置
+ * @param id
+ */
+export const delCarousel = (id: string | number | Array<string | number>) => {
+  return request({
+    url: '/mall/carousel/' + id,
+    method: 'delete'
+  });
+};
+
+export function changeStatus(id: string | number, status: number) {
+  const data = {
+    id,
+    status
+  };
+  return request({
+    url: '/mall/carousel/changeStatus',
+    method: 'put',
+    data: data
+  });
+}

+ 116 - 0
src/api/enterprisePurchase/carousel/types.ts

@@ -0,0 +1,116 @@
+export interface CarouselVO {
+  /**
+   * 主键ID
+   */
+  id: string | number;
+
+  /**
+   * 轮播图片URL(建议尺寸552*190)
+   */
+  imageUrl: string;
+
+  /**
+   * 点击跳转地址(全路径)
+   */
+  link: string;
+
+  /**
+   * 打开方式:_self当前页 _blank新窗口
+   */
+  target: string;
+
+  /**
+   * 排序,值越小越靠前
+   */
+  sortOrder: number;
+
+  /**
+   * 状态:1启用 0禁用
+   */
+  status: number;
+
+  /**
+   * 备注
+   */
+  remark: string;
+
+}
+
+export interface CarouselForm extends BaseEntity {
+  /**
+   * 主键ID
+   */
+  id?: string | number;
+
+  /**
+   * 轮播图片URL(建议尺寸552*190)
+   */
+  imageUrl?: string;
+
+  /**
+   * 点击跳转地址(全路径)
+   */
+  link?: string;
+
+  /**
+   * 打开方式:_self当前页 _blank新窗口
+   */
+  target?: string;
+
+  /**
+   * 排序,值越小越靠前
+   */
+  sortOrder?: number;
+
+  /**
+   * 状态:1启用 0禁用
+   */
+  status?: number;
+
+  /**
+   * 备注
+   */
+  remark?: string;
+
+}
+
+export interface CarouselQuery extends PageQuery {
+
+  /**
+   * 轮播图片URL(建议尺寸552*190)
+   */
+  imageUrl?: string;
+
+  /**
+   * 点击跳转地址(全路径)
+   */
+  link?: string;
+
+  /**
+   * 打开方式:_self当前页 _blank新窗口
+   */
+  target?: string;
+
+  /**
+   * 排序,值越小越靠前
+   */
+  sortOrder?: number;
+
+  /**
+   * 状态:1启用 0禁用
+   */
+  status?: number;
+
+  /**
+   * 平台标识
+   */
+  platformCode?: string;
+
+    /**
+     * 日期范围参数
+     */
+    params?: any;
+}
+
+
+

+ 75 - 0
src/api/enterprisePurchase/categoryMain/index.ts

@@ -0,0 +1,75 @@
+import request from '@/utils/request';
+import { AxiosPromise } from 'axios';
+import { CategoryMainVO, CategoryMainForm, CategoryMainQuery } from '@/api/enterprisePurchase/categoryMain/types';
+
+/**
+ * 查询分类设置主列表
+ * @param query
+ * @returns {*}
+ */
+
+export const listCategoryMain = (query?: CategoryMainQuery): AxiosPromise<CategoryMainVO[]> => {
+  return request({
+    url: '/mall/categoryMain/list',
+    method: 'get',
+    params: query
+  });
+};
+
+/**
+ * 查询分类设置主详细
+ * @param id
+ */
+export const getCategoryMain = (id: string | number): AxiosPromise<CategoryMainVO> => {
+  return request({
+    url: '/mall/categoryMain/' + id,
+    method: 'get'
+  });
+};
+
+/**
+ * 新增分类设置主
+ * @param data
+ */
+export const addCategoryMain = (data: CategoryMainForm) => {
+  return request({
+    url: '/mall/categoryMain',
+    method: 'post',
+    data: data
+  });
+};
+
+/**
+ * 修改分类设置主
+ * @param data
+ */
+export const updateCategoryMain = (data: CategoryMainForm) => {
+  return request({
+    url: '/mall/categoryMain',
+    method: 'put',
+    data: data
+  });
+};
+
+/**
+ * 删除分类设置主
+ * @param id
+ */
+export const delCategoryMain = (id: string | number | Array<string | number>) => {
+  return request({
+    url: '/mall/categoryMain/' + id,
+    method: 'delete'
+  });
+};
+
+export function changeStatus(id: string | number, status: number) {
+  const data = {
+    id,
+    status
+  };
+  return request({
+    url: '/mall/categoryMain/changeStatus',
+    method: 'put',
+    data: data
+  });
+}

+ 140 - 0
src/api/enterprisePurchase/categoryMain/types.ts

@@ -0,0 +1,140 @@
+export interface CategoryMainVO {
+  /**
+   * 主键ID
+   */
+  id: string | number;
+
+  /**
+   * 菜单名称
+   */
+  name: string;
+
+  /**
+   * 图标URL
+   */
+  icon: string;
+
+  /**
+   * 同步分类id
+   */
+  syncCategoryId: string | number;
+
+  /**
+   * 同步分类
+   */
+  syncCategory: string;
+
+  /**
+   * 启用状态: 1-启用, 0-禁用
+   */
+  status: number;
+
+  /**
+   * 面板主标题
+   */
+  panelMainTitle: string;
+
+  /**
+   * 面板副标题
+   */
+  panelSubTitle: string;
+
+  /**
+   * 备注
+   */
+  remark: string;
+}
+
+export interface CategoryMainForm extends BaseEntity {
+  /**
+   * 主键ID
+   */
+  id?: string | number;
+
+  /**
+   * 菜单名称
+   */
+  name?: string;
+
+  /**
+   * 图标URL
+   */
+  icon?: string;
+
+  /**
+   * 同步分类id
+   */
+  syncCategoryId?: string | number;
+
+  /**
+   * 同步分类
+   */
+  syncCategory?: string;
+
+  /**
+   * 启用状态: 1-启用, 0-禁用
+   */
+  status?: number;
+
+  /**
+   * 面板主标题
+   */
+  panelMainTitle?: string;
+
+  /**
+   * 面板副标题
+   */
+  panelSubTitle?: string;
+
+  /**
+   * 备注
+   */
+  remark?: string;
+}
+
+export interface CategoryMainQuery extends PageQuery {
+  /**
+   * 菜单名称
+   */
+  name?: string;
+
+  /**
+   * 图标URL
+   */
+  icon?: string;
+
+  /**
+   * 同步分类id
+   */
+  syncCategoryId?: string | number;
+
+  /**
+   * 同步分类
+   */
+  syncCategory?: string;
+
+  /**
+   * 启用状态: 1-启用, 0-禁用
+   */
+  status?: number;
+
+  /**
+   * 面板主标题
+   */
+  panelMainTitle?: string;
+
+  /**
+   * 面板副标题
+   */
+  panelSubTitle?: string;
+
+  /**
+   * 平台标识
+   */
+  platformCode?: string;
+
+  /**
+   * 日期范围参数
+   */
+  params?: any;
+}

+ 75 - 0
src/api/enterprisePurchase/headerCategory/index.ts

@@ -0,0 +1,75 @@
+import request from '@/utils/request';
+import { AxiosPromise } from 'axios';
+import { HeaderCategoryVO, HeaderCategoryForm, HeaderCategoryQuery } from '@/api/enterprisePurchase/headerCategory/types';
+
+/**
+ * 查询头部分类管理列表
+ * @param query
+ * @returns {*}
+ */
+
+export const listHeaderCategory = (query?: HeaderCategoryQuery): AxiosPromise<HeaderCategoryVO[]> => {
+  return request({
+    url: '/mall/headerCategory/list',
+    method: 'get',
+    params: query
+  });
+};
+
+/**
+ * 查询头部分类管理详细
+ * @param id
+ */
+export const getHeaderCategory = (id: string | number): AxiosPromise<HeaderCategoryVO> => {
+  return request({
+    url: '/mall/headerCategory/' + id,
+    method: 'get'
+  });
+};
+
+/**
+ * 新增头部分类管理
+ * @param data
+ */
+export const addHeaderCategory = (data: HeaderCategoryForm) => {
+  return request({
+    url: '/mall/headerCategory',
+    method: 'post',
+    data: data
+  });
+};
+
+/**
+ * 修改头部分类管理
+ * @param data
+ */
+export const updateHeaderCategory = (data: HeaderCategoryForm) => {
+  return request({
+    url: '/mall/headerCategory',
+    method: 'put',
+    data: data
+  });
+};
+
+/**
+ * 删除头部分类管理
+ * @param id
+ */
+export const delHeaderCategory = (id: string | number | Array<string | number>) => {
+  return request({
+    url: '/mall/headerCategory/' + id,
+    method: 'delete'
+  });
+};
+
+export function changeStatus(id: string | number, status: number) {
+  const data = {
+    id,
+    status
+  };
+  return request({
+    url: '/mall/headerCategory/changeStatus',
+    method: 'put',
+    data: data
+  });
+}

+ 116 - 0
src/api/enterprisePurchase/headerCategory/types.ts

@@ -0,0 +1,116 @@
+export interface HeaderCategoryVO {
+  /**
+   * 主键ID
+   */
+  id: string | number;
+
+  /**
+   * 分类名称 
+   */
+  title: string;
+
+  /**
+   * 分类图标URL 
+   */
+  icon: string;
+
+  /**
+   * 跳转地址 
+   */
+  link: string;
+
+  /**
+   * 打开方式: current(当前页), new(新窗口) (对应前端: headerForm.openMode)
+   */
+  openMode: string;
+
+  /**
+   * 启用状态: 1-启用, 0-禁用
+   */
+  status: number;
+
+  /**
+   * 备注
+   */
+  remark: string;
+
+}
+
+export interface HeaderCategoryForm extends BaseEntity {
+  /**
+   * 主键ID
+   */
+  id?: string | number;
+
+  /**
+   * 分类名称 
+   */
+  title?: string;
+
+  /**
+   * 分类图标URL 
+   */
+  icon?: string;
+
+  /**
+   * 跳转地址 
+   */
+  link?: string;
+
+  /**
+   * 打开方式: current(当前页), new(新窗口) (对应前端: headerForm.openMode)
+   */
+  openMode?: string;
+
+  /**
+   * 启用状态: 1-启用, 0-禁用
+   */
+  status?: number;
+
+  /**
+   * 备注
+   */
+  remark?: string;
+
+}
+
+export interface HeaderCategoryQuery extends PageQuery {
+
+  /**
+   * 分类名称 
+   */
+  title?: string;
+
+  /**
+   * 分类图标URL 
+   */
+  icon?: string;
+
+  /**
+   * 跳转地址 
+   */
+  link?: string;
+
+  /**
+   * 打开方式: current(当前页), new(新窗口) (对应前端: headerForm.openMode)
+   */
+  openMode?: string;
+
+  /**
+   * 启用状态: 1-启用, 0-禁用
+   */
+  status?: number;
+
+  /**
+   * 平台标识
+   */
+  platformCode?: string;
+
+    /**
+     * 日期范围参数
+     */
+    params?: any;
+}
+
+
+

+ 75 - 0
src/api/enterprisePurchase/quickEntryItems/index.ts

@@ -0,0 +1,75 @@
+import request from '@/utils/request';
+import { AxiosPromise } from 'axios';
+import { QuickEntryItemsVO, QuickEntryItemsForm, QuickEntryItemsQuery } from '@/api/enterprisePurchase/quickEntryItems/types';
+
+/**
+ * 查询快捷入口项列列表
+ * @param query
+ * @returns {*}
+ */
+
+export const listQuickEntryItems = (query?: QuickEntryItemsQuery): AxiosPromise<QuickEntryItemsVO[]> => {
+  return request({
+    url: '/mall/quickEntryItems/list',
+    method: 'get',
+    params: query
+  });
+};
+
+/**
+ * 查询快捷入口项列详细
+ * @param id
+ */
+export const getQuickEntryItems = (id: string | number): AxiosPromise<QuickEntryItemsVO> => {
+  return request({
+    url: '/mall/quickEntryItems/' + id,
+    method: 'get'
+  });
+};
+
+/**
+ * 新增快捷入口项列
+ * @param data
+ */
+export const addQuickEntryItems = (data: QuickEntryItemsForm) => {
+  return request({
+    url: '/mall/quickEntryItems',
+    method: 'post',
+    data: data
+  });
+};
+
+/**
+ * 修改快捷入口项列
+ * @param data
+ */
+export const updateQuickEntryItems = (data: QuickEntryItemsForm) => {
+  return request({
+    url: '/mall/quickEntryItems',
+    method: 'put',
+    data: data
+  });
+};
+
+/**
+ * 删除快捷入口项列
+ * @param id
+ */
+export const delQuickEntryItems = (id: string | number | Array<string | number>) => {
+  return request({
+    url: '/mall/quickEntryItems/' + id,
+    method: 'delete'
+  });
+};
+
+export function changeStatus(id: string | number, status: number) {
+  const data = {
+    id,
+    status
+  };
+  return request({
+    url: '/mall/quickEntryItems/changeStatus',
+    method: 'put',
+    data: data
+  });
+}

+ 146 - 0
src/api/enterprisePurchase/quickEntryItems/types.ts

@@ -0,0 +1,146 @@
+export interface QuickEntryItemsVO {
+  /**
+   * 主键ID
+   */
+  id: string | number;
+
+  /**
+   * 关联模块ID
+   */
+  moduleId: string | number;
+
+  /**
+   * 排序权重
+   */
+  sortOrder: number;
+
+  /**
+   * 入口名称
+   */
+  name: string;
+
+  /**
+   * 图标地址
+   */
+  iconUrl: string;
+
+  /**
+   * 角标文字
+   */
+  tagText: string;
+
+  /**
+   * 入口跳转地址
+   */
+  jumpLink: string;
+
+  /**
+   * 状态 (1启用 0禁用)
+   */
+  status: number;
+
+  /**
+   * 备注
+   */
+  remark: string;
+
+}
+
+export interface QuickEntryItemsForm extends BaseEntity {
+  /**
+   * 主键ID
+   */
+  id?: string | number;
+
+  /**
+   * 关联模块ID
+   */
+  moduleId?: string | number;
+
+  /**
+   * 排序权重
+   */
+  sortOrder?: number;
+
+  /**
+   * 入口名称
+   */
+  name?: string;
+
+  /**
+   * 图标地址
+   */
+  iconUrl?: string;
+
+  /**
+   * 角标文字
+   */
+  tagText?: string;
+
+  /**
+   * 入口跳转地址
+   */
+  jumpLink?: string;
+
+  /**
+   * 状态 (1启用 0禁用)
+   */
+  status?: number;
+
+  /**
+   * 备注
+   */
+  remark?: string;
+
+}
+
+export interface QuickEntryItemsQuery extends PageQuery {
+
+  /**
+   * 关联模块ID
+   */
+  moduleId?: string | number;
+
+  /**
+   * 排序权重
+   */
+  sortOrder?: number;
+
+  /**
+   * 入口名称
+   */
+  name?: string;
+
+  /**
+   * 图标地址
+   */
+  iconUrl?: string;
+
+  /**
+   * 角标文字
+   */
+  tagText?: string;
+
+  /**
+   * 入口跳转地址
+   */
+  jumpLink?: string;
+
+  /**
+   * 状态 (1启用 0禁用)
+   */
+  status?: number;
+
+  /**
+   * 平台标识
+   */
+  platformCode?: string;
+
+    /**
+     * 日期范围参数
+     */
+    params?: any;
+}
+
+
+

+ 63 - 0
src/api/enterprisePurchase/quickEntryModule/index.ts

@@ -0,0 +1,63 @@
+import request from '@/utils/request';
+import { AxiosPromise } from 'axios';
+import { QuickEntryModuleVO, QuickEntryModuleForm, QuickEntryModuleQuery } from '@/api/enterprisePurchase/quickEntryModule/types';
+
+/**
+ * 查询快捷入口模块配置列表
+ * @param query
+ * @returns {*}
+ */
+
+export const listQuickEntryModule = (query?: QuickEntryModuleQuery): AxiosPromise<QuickEntryModuleVO[]> => {
+  return request({
+    url: '/mall/quickEntryModule/list',
+    method: 'get',
+    params: query
+  });
+};
+
+/**
+ * 查询快捷入口模块配置详细
+ * @param id
+ */
+export const getQuickEntryModule = (id: string | number): AxiosPromise<QuickEntryModuleVO> => {
+  return request({
+    url: '/mall/quickEntryModule/' + id,
+    method: 'get'
+  });
+};
+
+/**
+ * 新增快捷入口模块配置
+ * @param data
+ */
+export const addQuickEntryModule = (data: QuickEntryModuleForm) => {
+  return request({
+    url: '/mall/quickEntryModule',
+    method: 'post',
+    data: data
+  });
+};
+
+/**
+ * 修改快捷入口模块配置
+ * @param data
+ */
+export const updateQuickEntryModule = (data: QuickEntryModuleForm) => {
+  return request({
+    url: '/mall/quickEntryModule',
+    method: 'put',
+    data: data
+  });
+};
+
+/**
+ * 删除快捷入口模块配置
+ * @param id
+ */
+export const delQuickEntryModule = (id: string | number | Array<string | number>) => {
+  return request({
+    url: '/mall/quickEntryModule/' + id,
+    method: 'delete'
+  });
+};

+ 71 - 0
src/api/enterprisePurchase/quickEntryModule/types.ts

@@ -0,0 +1,71 @@
+export interface QuickEntryModuleVO {
+  /**
+   * 主键ID
+   */
+  id: string | number;
+
+  /**
+   * 模块名称
+   */
+  moduleName: string;
+
+  /**
+   * 模块跳转链接
+   */
+  jumpLink: string;
+
+  /**
+   * 备注
+   */
+  remark: string;
+
+}
+
+export interface QuickEntryModuleForm extends BaseEntity {
+  /**
+   * 主键ID
+   */
+  id?: string | number;
+
+  /**
+   * 模块名称
+   */
+  moduleName?: string;
+
+  /**
+   * 模块跳转链接
+   */
+  jumpLink?: string;
+
+  /**
+   * 备注
+   */
+  remark?: string;
+
+}
+
+export interface QuickEntryModuleQuery extends PageQuery {
+
+  /**
+   * 模块名称
+   */
+  moduleName?: string;
+
+  /**
+   * 模块跳转链接
+   */
+  jumpLink?: string;
+
+  /**
+   * 平台标识
+   */
+  platformCode?: string;
+
+    /**
+     * 日期范围参数
+     */
+    params?: any;
+}
+
+
+

+ 79 - 0
src/api/enterprisePurchase/recommendCategoryConfig/index.ts

@@ -0,0 +1,79 @@
+import request from '@/utils/request';
+import { AxiosPromise } from 'axios';
+import {
+  RecommendCategoryConfigVO,
+  RecommendCategoryConfigForm,
+  RecommendCategoryConfigQuery
+} from '@/api/enterprisePurchase/recommendCategoryConfig/types';
+
+/**
+ * 查询为你推荐分类配置列表
+ * @param query
+ * @returns {*}
+ */
+
+export const listRecommendCategoryConfig = (query?: RecommendCategoryConfigQuery): AxiosPromise<RecommendCategoryConfigVO[]> => {
+  return request({
+    url: '/mall/recommendCategoryConfig/list',
+    method: 'get',
+    params: query
+  });
+};
+
+/**
+ * 查询为你推荐分类配置详细
+ * @param id
+ */
+export const getRecommendCategoryConfig = (id: string | number): AxiosPromise<RecommendCategoryConfigVO> => {
+  return request({
+    url: '/mall/recommendCategoryConfig/' + id,
+    method: 'get'
+  });
+};
+
+/**
+ * 新增为你推荐分类配置
+ * @param data
+ */
+export const addRecommendCategoryConfig = (data: RecommendCategoryConfigForm) => {
+  return request({
+    url: '/mall/recommendCategoryConfig',
+    method: 'post',
+    data: data
+  });
+};
+
+/**
+ * 修改为你推荐分类配置
+ * @param data
+ */
+export const updateRecommendCategoryConfig = (data: RecommendCategoryConfigForm) => {
+  return request({
+    url: '/mall/recommendCategoryConfig',
+    method: 'put',
+    data: data
+  });
+};
+
+/**
+ * 删除为你推荐分类配置
+ * @param id
+ */
+export const delRecommendCategoryConfig = (id: string | number | Array<string | number>) => {
+  return request({
+    url: '/mall/recommendCategoryConfig/' + id,
+    method: 'delete'
+  });
+};
+
+export function changeStatus(id: string | number, status: number) {
+  const data = {
+    id,
+    status
+  };
+  return request({
+    url: '/mall/recommendCategoryConfig/changeStatus',
+    method: 'put',
+    data: data
+  });
+}

+ 172 - 0
src/api/enterprisePurchase/recommendCategoryConfig/types.ts

@@ -0,0 +1,172 @@
+export interface RecommendCategoryConfigVO {
+  /**
+   * 主键ID
+   */
+  id: string | number;
+
+  /**
+   * 主标题
+   */
+  name: string;
+
+  /**
+   * 副标题
+   */
+  subTitle: string;
+
+  /**
+   * 分类图标URL
+   */
+  iconUrl: string;
+
+  /**
+   * 数据类型: select=商品自选, category=分类映射
+   */
+  dataType: string;
+
+  /**
+   * 映射分类ID路径
+   */
+  categoryPath: string;
+
+  /**
+   * 映射分类名称路径
+   */
+  categoryLabel: string;
+
+  /**
+   * 自选商品ID集合(JSON)
+   */
+  selectedProductIds: string | number;
+
+  selectedProducts: any[];
+
+  /**
+   * 排序
+   */
+  sortOrder: number;
+
+  /**
+   * 状态(1启用 0禁用)
+   */
+  status: number;
+
+  /**
+   * 备注
+   */
+  remark: string;
+}
+
+export interface RecommendCategoryConfigForm extends BaseEntity {
+  /**
+   * 主键ID
+   */
+  id?: string | number;
+
+  /**
+   * 主标题
+   */
+  name?: string;
+
+  /**
+   * 副标题
+   */
+  subTitle?: string;
+
+  /**
+   * 分类图标URL
+   */
+  iconUrl?: string;
+
+  /**
+   * 数据类型: select=商品自选, category=分类映射
+   */
+  dataType?: string;
+
+  /**
+   * 映射分类ID路径
+   */
+  categoryPath?: string;
+
+  /**
+   * 映射分类名称路径
+   */
+  categoryLabel?: string;
+
+  /**
+   * 自选商品ID集合(JSON)
+   */
+  selectedProductIds?: string | number;
+
+  /**
+   * 排序
+   */
+  sortOrder?: number;
+
+  /**
+   * 状态(1启用 0禁用)
+   */
+  status?: number;
+
+  /**
+   * 备注
+   */
+  remark?: string;
+}
+
+export interface RecommendCategoryConfigQuery extends PageQuery {
+  /**
+   * 主标题
+   */
+  name?: string;
+
+  /**
+   * 副标题
+   */
+  subTitle?: string;
+
+  /**
+   * 分类图标URL
+   */
+  iconUrl?: string;
+
+  /**
+   * 数据类型: select=商品自选, category=分类映射
+   */
+  dataType?: string;
+
+  /**
+   * 映射分类ID路径
+   */
+  categoryPath?: string;
+
+  /**
+   * 映射分类名称路径
+   */
+  categoryLabel?: string;
+
+  /**
+   * 自选商品ID集合(JSON)
+   */
+  selectedProductIds?: string | number;
+
+  /**
+   * 排序
+   */
+  sortOrder?: number;
+
+  /**
+   * 状态(1启用 0禁用)
+   */
+  status?: number;
+
+  /**
+   * 平台标识
+   */
+  platformCode?: string;
+
+  /**
+   * 日期范围参数
+   */
+  params?: any;
+}

+ 63 - 0
src/api/enterprisePurchase/recommendThemeConfig/index.ts

@@ -0,0 +1,63 @@
+import request from '@/utils/request';
+import { AxiosPromise } from 'axios';
+import { RecommendThemeConfigVO, RecommendThemeConfigForm, RecommendThemeConfigQuery } from '@/api/enterprisePurchase/recommendThemeConfig/types';
+
+/**
+ * 查询为你推荐全局主题配置列表
+ * @param query
+ * @returns {*}
+ */
+
+export const listRecommendThemeConfig = (query?: RecommendThemeConfigQuery): AxiosPromise<RecommendThemeConfigVO[]> => {
+  return request({
+    url: '/mall/recommendThemeConfig/list',
+    method: 'get',
+    params: query
+  });
+};
+
+/**
+ * 查询为你推荐全局主题配置详细
+ * @param id
+ */
+export const getRecommendThemeConfig = (id: string | number): AxiosPromise<RecommendThemeConfigVO> => {
+  return request({
+    url: '/mall/recommendThemeConfig/' + id,
+    method: 'get'
+  });
+};
+
+/**
+ * 新增为你推荐全局主题配置
+ * @param data
+ */
+export const addRecommendThemeConfig = (data: RecommendThemeConfigForm) => {
+  return request({
+    url: '/mall/recommendThemeConfig',
+    method: 'post',
+    data: data
+  });
+};
+
+/**
+ * 修改为你推荐全局主题配置
+ * @param data
+ */
+export const updateRecommendThemeConfig = (data: RecommendThemeConfigForm) => {
+  return request({
+    url: '/mall/recommendThemeConfig',
+    method: 'put',
+    data: data
+  });
+};
+
+/**
+ * 删除为你推荐全局主题配置
+ * @param id
+ */
+export const delRecommendThemeConfig = (id: string | number | Array<string | number>) => {
+  return request({
+    url: '/mall/recommendThemeConfig/' + id,
+    method: 'delete'
+  });
+};

+ 65 - 0
src/api/enterprisePurchase/recommendThemeConfig/types.ts

@@ -0,0 +1,65 @@
+export interface RecommendThemeConfigVO {
+  /**
+   * 主键ID
+   */
+  id: string | number;
+
+  /**
+   * 主题选中色调
+   */
+  themeColor: string;
+
+  /**
+   * 商品列表主题色
+   */
+  productThemeColor: string;
+
+  /**
+   * 备注
+   */
+  remark: string;
+}
+
+export interface RecommendThemeConfigForm extends BaseEntity {
+  /**
+   * 主键ID
+   */
+  id?: string | number;
+
+  /**
+   * 主题选中色调
+   */
+  themeColor?: string;
+
+  /**
+   * 商品列表主题色
+   */
+  productThemeColor?: string;
+
+  /**
+   * 备注
+   */
+  remark?: string;
+}
+
+export interface RecommendThemeConfigQuery extends PageQuery {
+  /**
+   * 主题选中色调
+   */
+  themeColor?: string;
+
+  /**
+   * 商品列表主题色
+   */
+  productThemeColor?: string;
+
+  /**
+   * 平台标识
+   */
+  platformCode?: string;
+
+  /**
+   * 日期范围参数
+   */
+  params?: any;
+}

+ 63 - 0
src/api/enterprisePurchase/scenarioCards/index.ts

@@ -0,0 +1,63 @@
+import request from '@/utils/request';
+import { AxiosPromise } from 'axios';
+import { ScenarioCardsVO, ScenarioCardsForm, ScenarioCardsQuery } from '@/api/enterprisePurchase/scenarioCards/types';
+
+/**
+ * 查询场景解决方案卡片列表
+ * @param query
+ * @returns {*}
+ */
+
+export const listScenarioCards = (query?: ScenarioCardsQuery): AxiosPromise<ScenarioCardsVO[]> => {
+  return request({
+    url: '/mall/scenarioCards/list',
+    method: 'get',
+    params: query
+  });
+};
+
+/**
+ * 查询场景解决方案卡片详细
+ * @param id
+ */
+export const getScenarioCards = (id: string | number): AxiosPromise<ScenarioCardsVO> => {
+  return request({
+    url: '/mall/scenarioCards/' + id,
+    method: 'get'
+  });
+};
+
+/**
+ * 新增场景解决方案卡片
+ * @param data
+ */
+export const addScenarioCards = (data: ScenarioCardsForm) => {
+  return request({
+    url: '/mall/scenarioCards',
+    method: 'post',
+    data: data
+  });
+};
+
+/**
+ * 修改场景解决方案卡片
+ * @param data
+ */
+export const updateScenarioCards = (data: ScenarioCardsForm) => {
+  return request({
+    url: '/mall/scenarioCards',
+    method: 'put',
+    data: data
+  });
+};
+
+/**
+ * 删除场景解决方案卡片
+ * @param id
+ */
+export const delScenarioCards = (id: string | number | Array<string | number>) => {
+  return request({
+    url: '/mall/scenarioCards/' + id,
+    method: 'delete'
+  });
+};

+ 131 - 0
src/api/enterprisePurchase/scenarioCards/types.ts

@@ -0,0 +1,131 @@
+export interface ScenarioCardsVO {
+  /**
+   * 主键ID
+   */
+  id: string | number;
+
+  /**
+   * 排序权重
+   */
+  sortOrder: number;
+
+  /**
+   * 卡片主标题
+   */
+  title: string;
+
+  /**
+   * 标题颜色
+   */
+  titleColor: string;
+
+  /**
+   * 卡片副标题
+   */
+  subTitle: string;
+
+  /**
+   * 封面图片URL
+   */
+  imageUrl: string;
+
+  /**
+   * 卡片跳转链接
+   */
+  jumpLink: string;
+
+  /**
+   * 备注
+   */
+  remark: string;
+
+}
+
+export interface ScenarioCardsForm extends BaseEntity {
+  /**
+   * 主键ID
+   */
+  id?: string | number;
+
+  /**
+   * 排序权重
+   */
+  sortOrder?: number;
+
+  /**
+   * 卡片主标题
+   */
+  title?: string;
+
+  /**
+   * 标题颜色
+   */
+  titleColor?: string;
+
+  /**
+   * 卡片副标题
+   */
+  subTitle?: string;
+
+  /**
+   * 封面图片URL
+   */
+  imageUrl?: string;
+
+  /**
+   * 卡片跳转链接
+   */
+  jumpLink?: string;
+
+  /**
+   * 备注
+   */
+  remark?: string;
+
+}
+
+export interface ScenarioCardsQuery extends PageQuery {
+
+  /**
+   * 排序权重
+   */
+  sortOrder?: number;
+
+  /**
+   * 卡片主标题
+   */
+  title?: string;
+
+  /**
+   * 标题颜色
+   */
+  titleColor?: string;
+
+  /**
+   * 卡片副标题
+   */
+  subTitle?: string;
+
+  /**
+   * 封面图片URL
+   */
+  imageUrl?: string;
+
+  /**
+   * 卡片跳转链接
+   */
+  jumpLink?: string;
+
+  /**
+   * 平台标识
+   */
+  platformCode?: string;
+
+    /**
+     * 日期范围参数
+     */
+    params?: any;
+}
+
+
+

+ 67 - 0
src/api/enterprisePurchase/scenarioGlobalSettings/index.ts

@@ -0,0 +1,67 @@
+import request from '@/utils/request';
+import { AxiosPromise } from 'axios';
+import {
+  ScenarioGlobalSettingsVO,
+  ScenarioGlobalSettingsForm,
+  ScenarioGlobalSettingsQuery
+} from '@/api/enterprisePurchase/scenarioGlobalSettings/types';
+
+/**
+ * 查询场景解决方案全局设置列表
+ * @param query
+ * @returns {*}
+ */
+
+export const listScenarioGlobalSettings = (query?: ScenarioGlobalSettingsQuery): AxiosPromise<ScenarioGlobalSettingsVO[]> => {
+  return request({
+    url: '/mall/scenarioGlobalSettings/list',
+    method: 'get',
+    params: query
+  });
+};
+
+/**
+ * 查询场景解决方案全局设置详细
+ * @param id
+ */
+export const getScenarioGlobalSettings = (id: string | number): AxiosPromise<ScenarioGlobalSettingsVO> => {
+  return request({
+    url: '/mall/scenarioGlobalSettings/' + id,
+    method: 'get'
+  });
+};
+
+/**
+ * 新增场景解决方案全局设置
+ * @param data
+ */
+export const addScenarioGlobalSettings = (data: ScenarioGlobalSettingsForm) => {
+  return request({
+    url: '/mall/scenarioGlobalSettings',
+    method: 'post',
+    data: data
+  });
+};
+
+/**
+ * 修改场景解决方案全局设置
+ * @param data
+ */
+export const updateScenarioGlobalSettings = (data: ScenarioGlobalSettingsForm) => {
+  return request({
+    url: '/mall/scenarioGlobalSettings',
+    method: 'put',
+    data: data
+  });
+};
+
+/**
+ * 删除场景解决方案全局设置
+ * @param id
+ */
+export const delScenarioGlobalSettings = (id: string | number | Array<string | number>) => {
+  return request({
+    url: '/mall/scenarioGlobalSettings/' + id,
+    method: 'delete'
+  });
+};

+ 116 - 0
src/api/enterprisePurchase/scenarioGlobalSettings/types.ts

@@ -0,0 +1,116 @@
+export interface ScenarioGlobalSettingsVO {
+  /**
+   * 主键ID
+   */
+  id: string | number;
+
+  /**
+   * 场景主标题
+   */
+  mainTitle: string;
+
+  /**
+   * 场景副标题
+   */
+  subTitle: string;
+
+  /**
+   * 按钮文字
+   */
+  btnText: string;
+
+  /**
+   * 全局跳转链接
+   */
+  jumpLink: string;
+
+  /**
+   * 主题背景色
+   */
+  themeColor: string;
+
+  /**
+   * 备注
+   */
+  remark: string;
+
+}
+
+export interface ScenarioGlobalSettingsForm extends BaseEntity {
+  /**
+   * 主键ID
+   */
+  id?: string | number;
+
+  /**
+   * 场景主标题
+   */
+  mainTitle?: string;
+
+  /**
+   * 场景副标题
+   */
+  subTitle?: string;
+
+  /**
+   * 按钮文字
+   */
+  btnText?: string;
+
+  /**
+   * 全局跳转链接
+   */
+  jumpLink?: string;
+
+  /**
+   * 主题背景色
+   */
+  themeColor?: string;
+
+  /**
+   * 备注
+   */
+  remark?: string;
+
+}
+
+export interface ScenarioGlobalSettingsQuery extends PageQuery {
+
+  /**
+   * 场景主标题
+   */
+  mainTitle?: string;
+
+  /**
+   * 场景副标题
+   */
+  subTitle?: string;
+
+  /**
+   * 按钮文字
+   */
+  btnText?: string;
+
+  /**
+   * 全局跳转链接
+   */
+  jumpLink?: string;
+
+  /**
+   * 主题背景色
+   */
+  themeColor?: string;
+
+  /**
+   * 平台标识
+   */
+  platformCode?: string;
+
+    /**
+     * 日期范围参数
+     */
+    params?: any;
+}
+
+
+

+ 73 - 0
src/api/enterprisePurchase/searchConfig/index.ts

@@ -0,0 +1,73 @@
+import request from '@/utils/request';
+import { AxiosPromise } from 'axios';
+import { SearchConfigVO, SearchConfigForm, SearchConfigQuery } from '@/api/enterprisePurchase/searchConfig/types';
+
+/**
+ * 查询搜索栏全局配置(单行配置)列表
+ * @param query
+ * @returns {*}
+ */
+
+export const listSearchConfig = (query?: SearchConfigQuery): AxiosPromise<SearchConfigVO[]> => {
+  return request({
+    url: '/mall/searchConfig/list',
+    method: 'get',
+    params: query
+  });
+};
+
+/**
+ * 查询搜索栏全局配置(单行配置)详细
+ * @param id
+ */
+export const getSearchConfig = (id: string | number): AxiosPromise<SearchConfigVO> => {
+  return request({
+    url: '/mall/searchConfig/' + id,
+    method: 'get'
+  });
+};
+
+/**
+ * 新增搜索栏全局配置(单行配置)
+ * @param data
+ */
+export const addSearchConfig = (data: SearchConfigForm) => {
+  return request({
+    url: '/mall/searchConfig',
+    method: 'post',
+    data: data
+  });
+};
+
+/**
+ * 修改搜索栏全局配置(单行配置)
+ * @param data
+ */
+export const updateSearchConfig = (data: SearchConfigForm) => {
+  return request({
+    url: '/mall/searchConfig',
+    method: 'put',
+    data: data
+  });
+};
+
+/**
+ * 删除搜索栏全局配置(单行配置)
+ * @param id
+ */
+export const delSearchConfig = (id: string | number | Array<string | number>) => {
+  return request({
+    url: '/mall/searchConfig/' + id,
+    method: 'delete'
+  });
+};
+
+/**
+ * 获取当前配置
+ */
+export const getCurrentSearchConfig = () => {
+  return request({
+    url: '/mall/searchConfig/current',
+    method: 'get'
+  });
+};

+ 198 - 0
src/api/enterprisePurchase/searchConfig/types.ts

@@ -0,0 +1,198 @@
+export interface SearchConfigVO {
+  /**
+   * 主键ID
+   */
+  id: string | number;
+
+  /**
+   * 页面主标题
+   */
+  mainTitle: string;
+
+  /**
+   * 副标题/Slogan
+   */
+  subTitle: string;
+
+  /**
+   * 搜索框滚动占位文字,英文逗号分隔
+   */
+  placeholderText: string;
+
+  /**
+   * 全局主题色(HEX)
+   */
+  themeColor: string;
+
+  /**
+   * 右侧功能按钮文字
+   */
+  rightBtnText: string;
+
+  /**
+   * 右侧功能按钮图标URL
+   */
+  rightBtnIcon: string;
+
+  /**
+   * 右侧功能按钮跳转地址
+   */
+  rightBtnLink: string;
+
+  /**
+   * 左侧广告图片URL
+   */
+  leftAdImage: string;
+
+  /**
+   * 左侧广告图片URLUrl
+   */
+  leftAdImageUrl: string;
+  /**
+   * 左侧广告跳转地址
+   */
+  leftAdLink: string;
+
+  /**
+   * 分类设置主题色
+   */
+  categoryThemeColor: string;
+
+  /**
+   * 头部分类主题色
+   */
+  headerThemeColor: string;
+
+  /**
+   * 备注
+   */
+  remark: string;
+
+  hotWordList: any[];
+}
+
+export interface SearchConfigForm extends BaseEntity {
+  /**
+   * 主键ID
+   */
+  id?: string | number;
+
+  /**
+   * 页面主标题
+   */
+  mainTitle?: string;
+
+  /**
+   * 副标题/Slogan
+   */
+  subTitle?: string;
+
+  /**
+   * 搜索框滚动占位文字,英文逗号分隔
+   */
+  placeholderText?: string;
+
+  /**
+   * 全局主题色(HEX)
+   */
+  themeColor?: string;
+
+  /**
+   * 右侧功能按钮文字
+   */
+  rightBtnText?: string;
+
+  /**
+   * 右侧功能按钮图标URL
+   */
+  rightBtnIcon?: string;
+
+  /**
+   * 右侧功能按钮跳转地址
+   */
+  rightBtnLink?: string;
+
+  /**
+   * 左侧广告图片URL
+   */
+  leftAdImage?: string;
+
+  /**
+   * 左侧广告跳转地址
+   */
+  leftAdLink?: string;
+
+  /**
+   * 分类设置主题色
+   */
+  categoryThemeColor?: string;
+
+  /**
+   * 头部分类主题色
+   */
+  headerThemeColor?: string;
+
+  /**
+   * 备注
+   */
+  remark?: string;
+
+  hotWordList: any[];
+}
+
+export interface SearchConfigQuery extends PageQuery {
+  /**
+   * 页面主标题
+   */
+  mainTitle?: string;
+
+  /**
+   * 副标题/Slogan
+   */
+  subTitle?: string;
+
+  /**
+   * 搜索框滚动占位文字,英文逗号分隔
+   */
+  placeholderText?: string;
+
+  /**
+   * 全局主题色(HEX)
+   */
+  themeColor?: string;
+
+  /**
+   * 右侧功能按钮文字
+   */
+  rightBtnText?: string;
+
+  /**
+   * 右侧功能按钮图标URL
+   */
+  rightBtnIcon?: string;
+
+  /**
+   * 右侧功能按钮跳转地址
+   */
+  rightBtnLink?: string;
+
+  /**
+   * 左侧广告图片URL
+   */
+  leftAdImage?: string;
+
+  /**
+   * 左侧广告跳转地址
+   */
+  leftAdLink?: string;
+
+  /**
+   * 平台标识
+   */
+  platformCode?: string;
+
+  /**
+   * 日期范围参数
+   */
+  params?: any;
+}

+ 3 - 3
src/api/mall/pageCategory/api.ts

@@ -66,12 +66,12 @@ export const delPageCategory = (id: string | number | Array<string | number>) =>
 /**
  * 查询分类树结构(用于选择器)
  */
-export const getCategoryTree = (): AxiosPromise<PageCategoryVO[]> => {
+export function getCategoryTree(pageType: any) {
   return request({
-    url: '/mall/pageCategory/tree',
+    url: `/mall/pageCategory/tree?pageType=${pageType}`,
     method: 'get'
   });
-};
+}
 
 /**
  * 查询完整的分类树结构(用于树形展示)

BIN
src/assets/images/diy/category_style1_1.png


BIN
src/assets/images/diy/category_style2_1.png


BIN
src/assets/images/diy/category_style2_2.png


+ 130 - 28
src/components/LinkSelector/index.vue

@@ -18,7 +18,7 @@
             <div v-for="(item1, index1) in boxData" :key="index1">
               <h5 class="button-title">
                 <div class="title">{{ item1.name }}</div>
-                <div v-if="index1 == 0">
+                <div v-if="index1 == 0 && pageType == 1">
                   <span class="mr-[5px]">是否新窗口打开</span>
                   <el-switch v-model="switchVal" />
                 </div>
@@ -35,8 +35,8 @@
             </div>
           </template>
           <template v-else>
-            <div v-if="boxpageKey == 'goodsClassify'">
-              <div>
+            <div v-if="boxpageKey == 'goodsClassify' || boxpageKey == 'appgoodsClassify'">
+              <div v-if="pageType == 1">
                 <span class="mr-[5px]">是否新窗口打开</span>
                 <el-switch v-model="switchVal" />
               </div>
@@ -70,24 +70,29 @@
               </el-table>
             </div>
             <!-- 商品 -->
-            <div v-if="boxpageKey == 'goodsItem'">
+            <div v-if="boxpageKey == 'goodsItem' || boxpageKey == 'appgoodsItem'">
               <div class="flex justify-between">
                 <el-input v-model="queryParams2.itemName" placeholder="请输入商品名称" clearable style="width: 300px; margin-bottom: 10px">
                   <template #append>
                     <el-button :icon="Search" @click="handleQuery2" />
                   </template>
                 </el-input>
-                <div>
+                <div v-if="pageType == 1">
                   <span class="mr-[5px]">是否新窗口打开</span>
                   <el-switch v-model="switchVal" />
                 </div>
               </div>
               <el-table height="480" ref="multipleTableRef" v-loading="loading2" :data="tableData2" border>
-                <el-table-column width="80" align="center">
+                <el-table-column width="80" align="center" v-if="pageType == 1">
                   <template #default="{ row }">
                     <el-checkbox v-model="tableId2" :true-value="row.productNo" />
                   </template>
                 </el-table-column>
+                <el-table-column width="80" align="center" v-else>
+                  <template #default="{ row }">
+                    <el-checkbox v-model="tableId2" :true-value="row.id" />
+                  </template>
+                </el-table-column>
                 <el-table-column label="商品图片" align="center" prop="productImage" width="100">
                   <template #default="scope">
                     <image-preview :src="scope.row.productImage" :width="60" :height="60" />
@@ -291,7 +296,9 @@
                     >
                       <template #error>
                         <div class="image-placeholder">
-                          <el-icon><Picture /></el-icon>
+                          <el-icon>
+                            <Picture />
+                          </el-icon>
                         </div>
                       </template>
                     </el-image>
@@ -316,6 +323,28 @@
                 @pagination="getTableData6"
               />
             </div>
+            <!-- 新闻咨询 -->
+            <div v-if="boxpageKey == 'appnews'">
+              <el-table v-loading="loading7" border :data="tableData7" height="480">
+                <el-table-column width="80" align="center">
+                  <template #default="{ row }">
+                    <el-checkbox v-model="tableId7" :true-value="row.id" />
+                  </template>
+                </el-table-column>
+                <el-table-column label="标题" align="center" prop="title" min-width="200" show-overflow-tooltip>
+                  <template #default="scope">
+                    {{ scope.row.title }}
+                  </template>
+                </el-table-column>
+              </el-table>
+              <pagination
+                v-show="total7 > 0"
+                :total="total7"
+                v-model:page="queryParams7.pageNum"
+                v-model:limit="queryParams7.pageSize"
+                @pagination="getTableData7"
+              />
+            </div>
             <!-- 自定义 -->
             <div v-if="boxpageKey == 'zdy'">
               <div class="flex justify-between">
@@ -350,6 +379,14 @@ import { listProgram } from '@/api/product/program/index';
 import { programList } from '@/api/pmsProduct/program';
 import { listTopics } from '@/api/product/topics';
 import { listServiceCase } from '@/api/product/serviceCase';
+import { listGiftNotice } from '@/api/product/giftNotice';
+
+interface Props {
+  pageType?: any;
+}
+const props = defineProps<Props>();
+const pageType = props.pageType || '1';
+
 const showDialog = ref(false);
 const menuData = ref<any[]>([]);
 const defaultOpeneds = ref<any>(['0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '10']);
@@ -417,6 +454,15 @@ const queryParams6 = reactive({
   pageSize: 10,
   caseTitle: ''
 });
+//新闻咨询
+const tableId7 = ref<any>(null);
+const total7 = ref(0);
+const loading7 = ref(true);
+const tableData7 = ref<any>([]);
+const queryParams7 = reactive({
+  pageNum: 1,
+  pageSize: 10
+});
 //自定义
 const input = ref('');
 
@@ -436,7 +482,7 @@ const open = () => {
 // 加载分类数据
 const loadCategoryData = async () => {
   try {
-    const res = await getCategoryTree();
+    const res = await getCategoryTree(pageType);
     if (res.data && res.data.length > 0) {
       const firstLevelChildren = res.data[0].children;
       menuData.value = firstLevelChildren;
@@ -484,12 +530,12 @@ const onMenu = (res: any) => {
     if (list.length == 2) {
       const datas = menuData.value[list[0]].children[list[1]];
       boxpageKey.value = datas.pageKey;
-      if (boxpageKey.value == 'goodsClassify') {
+      if (boxpageKey.value == 'goodsClassify' || boxpageKey.value == 'appgoodsClassify') {
         if (tableData1.value.length == 0) {
           getTableData1();
         }
       }
-      if (boxpageKey.value == 'goodsItem') {
+      if (boxpageKey.value == 'goodsItem' || boxpageKey.value == 'appgoodsItem') {
         handleQuery2();
       }
       if (boxpageKey.value == 'plan') {
@@ -504,6 +550,9 @@ const onMenu = (res: any) => {
       if (boxpageKey.value == 'project') {
         handleQuery6();
       }
+      if (boxpageKey.value == 'appnews') {
+        handleQuery7();
+      }
       console.log(datas, 'datas');
     } else {
       if (menuData.value[list[0]].type == '4') {
@@ -538,7 +587,7 @@ const loadChildren = async (row: CategoryVO, treeNode: any, resolve: (data: Cate
     // 标记子节点是否还有子级
     const children = data.map((item: CategoryVO) => ({
       ...item,
-      hasChildren: item.hasChildren ?? item.classLevel < 3
+      hasChildren: item.hasChildren ?? item.classLevel < (pageType == 1 ? 3 : 2)
     }));
     resolve(children);
   } catch (error) {
@@ -637,7 +686,7 @@ const handleQuery6 = () => {
 const getTableData6 = async () => {
   loading6.value = true;
   try {
-    const res = await listServiceCase(queryParams5);
+    const res = await listServiceCase(queryParams6);
     tableData6.value = res.rows || [];
     total6.value = res.total || 0;
   } finally {
@@ -645,6 +694,24 @@ const getTableData6 = async () => {
   }
 };
 
+//新闻咨询查询
+const handleQuery7 = () => {
+  queryParams7.pageNum = 1;
+  getTableData7();
+};
+
+/** 获取列表新闻咨询 */
+const getTableData7 = async () => {
+  loading7.value = true;
+  try {
+    const res = await listGiftNotice(queryParams7);
+    tableData7.value = res.rows || [];
+    total7.value = res.total || 0;
+  } finally {
+    loading7.value = false;
+  }
+};
+
 const onSelected = (item: any) => {
   selected.value = item.url;
 };
@@ -652,7 +719,11 @@ const onSelected = (item: any) => {
 const onConfirm = () => {
   if (boxShow1.value) {
     if (selected.value) {
-      emit('update:modelValue', `${selected.value}?openType=${switchVal.value ? 1 : 0}`);
+      if (pageType == '1') {
+        emit('update:modelValue', `${selected.value}?openType=${switchVal.value ? 1 : 0}`);
+      } else {
+        emit('update:modelValue', `${selected.value}`);
+      }
       showDialog.value = false;
     } else {
       ElMessage({
@@ -662,18 +733,29 @@ const onConfirm = () => {
       });
     }
   } else {
-    if (boxpageKey.value == 'goodsClassify') {
+    if (boxpageKey.value == 'goodsClassify' || boxpageKey.value == 'appgoodsClassify') {
       if (tableId1.value) {
-        let url1 = '/search?type=' + selectedRowObj.value.classLevel;
-        if (selectedRowObj.value.classLevel == 1) {
-          url1 = url1 + '&topCategoryId=' + tableId1.value;
-        } else if (selectedRowObj.value.classLevel == 2) {
-          url1 = url1 + '&middleCategoryId=' + tableId1.value;
-        } else if (selectedRowObj.value.classLevel == 3) {
-          url1 = url1 + '&bottomCategoryId=' + tableId1.value;
+        if (pageType == '1') {
+          let url1 = '/search?type=' + selectedRowObj.value.classLevel;
+          if (selectedRowObj.value.classLevel == 1) {
+            url1 = url1 + '&topCategoryId=' + tableId1.value;
+          } else if (selectedRowObj.value.classLevel == 2) {
+            url1 = url1 + '&mediumCategoryId=' + tableId1.value;
+          } else if (selectedRowObj.value.classLevel == 3) {
+            url1 = url1 + '&bottomCategoryId=' + tableId1.value;
+          }
+          url1 = url1 + '&openType=' + (switchVal.value ? 1 : 0);
+          emit('update:modelValue', url1);
+        } else {
+          let url1 = '/addon/shop/pages/goods/category';
+          if (selectedRowObj.value.classLevel == 1) {
+            url1 = url1 + '?categoryId=' + tableId1.value;
+          } else if (selectedRowObj.value.classLevel == 2) {
+            url1 = url1 + '?mediumCategoryId=' + tableId1.value;
+          }
+          emit('update:modelValue', url1);
         }
-        url1 = url1 + '&openType=' + (switchVal.value ? 1 : 0);
-        emit('update:modelValue', url1);
+
         showDialog.value = false;
       } else {
         ElMessage({
@@ -683,12 +765,18 @@ const onConfirm = () => {
         });
       }
     }
-    if (boxpageKey.value == 'goodsItem') {
+    if (boxpageKey.value == 'goodsItem' || boxpageKey.value == 'appgoodsItem') {
       if (tableId2.value) {
-        let url2 = '/item?productNo=' + tableId2.value;
-        url2 = url2 + '&openType=' + (switchVal.value ? 1 : 0);
-        emit('update:modelValue', url2);
-        showDialog.value = false;
+        if (pageType == '1') {
+          let url2 = '/item?productNo=' + tableId2.value;
+          url2 = url2 + '&openType=' + (switchVal.value ? 1 : 0);
+          emit('update:modelValue', url2);
+          showDialog.value = false;
+        } else {
+          const url2 = '/addon/shop/pages/goods/detail?goodsId=' + tableId2.value;
+          emit('update:modelValue', url2);
+          showDialog.value = false;
+        }
       } else {
         ElMessage({
           message: '请选择一个商品',
@@ -765,6 +853,19 @@ const onConfirm = () => {
         });
       }
     }
+    if (boxpageKey.value == 'appnews') {
+      if (tableId7.value) {
+        const url7 = '/addon/shop_giftcard/pages/detail?id=' + tableId7.value;
+        emit('update:modelValue', url7);
+        showDialog.value = false;
+      } else {
+        ElMessage({
+          message: '请选择一个新闻咨询',
+          type: 'warning',
+          duration: 2000
+        });
+      }
+    }
   }
 };
 
@@ -798,6 +899,7 @@ defineExpose({
       display: flex;
       justify-content: space-between;
       align-items: center;
+
       .title {
         font-size: 14px;
         font-weight: 600;

+ 3 - 0
src/components/WebLinkInput/index.vue

@@ -22,6 +22,7 @@
       ref="linkSelectorRef"
       v-model:visible="selectorVisible"
       v-model="localValue"
+      :pageType="pageType"
       @confirm="handleLinkConfirm"
       @close="handleSelectorClose"
     />
@@ -38,6 +39,7 @@ interface Props {
   placeholder?: string;
   disabled?: boolean;
   inputStyle?: Record<string, any>;
+  pageType?: string;
 }
 
 // 定义事件
@@ -50,6 +52,7 @@ const props = withDefaults(defineProps<Props>(), {
   modelValue: '',
   placeholder: '请输入链接或点击选择',
   disabled: false,
+  pageType: '1',
   inputStyle: () => ({})
 });
 

+ 17 - 8
src/components/goodsMini/index.vue

@@ -159,10 +159,15 @@ const getList = async () => {
     let result = [];
     if (props.navIndex || props.navIndex == 0) {
       if (props.type == 'editManyGoodsList') {
-        result = tableData.value.filter((item: any) => diyStore.editComponent.list[props.navIndex].goods_ids.includes(item.id));
+        result = tableData.value.filter((item: any) => diyStore.editComponent.list[props.navIndex].goodsIds.includes(item.id));
       }
     } else {
-      result = tableData.value.filter((item: any) => diyStore.editComponent.goodsIds.includes(item.id));
+      //商品推荐
+      if (props.type == 'editShopGoodsRecommend') {
+        result = tableData.value.filter((item: any) => diyStore.editComponent.goods_ids.includes(item.id));
+      } else {
+        result = tableData.value.filter((item: any) => diyStore.editComponent.goodsIds.includes(item.id));
+      }
     }
     resultList.value = result;
     nextTick(() => {
@@ -180,14 +185,18 @@ const getList = async () => {
 const onConfirm = () => {
   if (props.navIndex || props.navIndex == 0) {
     if (props.type == 'editManyGoodsList') {
-      const newIds = calculateNewIds(diyStore.editComponent.list[props.navIndex].goods_ids, tableData.value, multipleSelection.value);
-      // console.log(newIds,'?????????????')
-      // return
-      diyStore.editComponent.list[props.navIndex].goods_ids = newIds;
+      const newIds = calculateNewIds(diyStore.editComponent.list[props.navIndex].goodsIds, tableData.value, multipleSelection.value);
+      diyStore.editComponent.list[props.navIndex].goodsIds = newIds;
     }
   } else {
-    const newIds = calculateNewIds(diyStore.editComponent.goodsIds, tableData.value, multipleSelection.value);
-    diyStore.editComponent.goodsIds = newIds;
+    //商品推荐
+    if (props.type == 'editShopGoodsRecommend') {
+      const newIds = calculateNewIds(diyStore.editComponent.goods_ids, tableData.value, multipleSelection.value);
+      diyStore.editComponent.goods_ids = newIds;
+    } else {
+      const newIds = calculateNewIds(diyStore.editComponent.goodsIds, tableData.value, multipleSelection.value);
+      diyStore.editComponent.goodsIds = newIds;
+    }
   }
   showDialog.value = false;
 };

+ 2 - 2
src/components/heat-map/index.vue

@@ -78,8 +78,8 @@ import { img, deepClone } from '@/utils/common';
 
 const prop = defineProps({
   modelValue: {
-    type: String,
-    default: ''
+    type: Object as () => Record<string, any>,
+    default: () => {}
   }
 });
 

+ 655 - 0
src/components/heat-mapMini/index.vue

@@ -0,0 +1,655 @@
+<template>
+  <div>
+    <div @click="show">
+      <slot>
+        <div v-if="value.heatMapData.length" class="cursor-pointer">
+          已选<span class="text-primary p-[4px]">{{ value.heatMapData.length }}</span
+          >个热区
+        </div>
+        <div v-else class="cursor-pointer">添加热区</div>
+      </slot>
+    </div>
+    <el-dialog v-model="showDialog" title="热区设置" width="810px" :destroy-on-close="true" :close-on-click-modal="false">
+      <div class="flex">
+        <div
+          class="hot-area-img-wrap content-box relative bg-gray-100 border border-dashed border-gray-500 bg-no-repeat"
+          :style="{ backgroundImage: 'url(' + img(value.imageUrl) + ')', width: contentBoxWidth + 'px', height: contentBoxHeight + 'px' }"
+        >
+          <div
+            v-for="(item, index) in dragBoxArr"
+            :id="'box_' + index"
+            :key="index"
+            class="area-box border border-solid border-[#ccc] w-[100px] h-[100px] absolute top-0 left-0 select-none p-[5px]"
+            :style="{ left: item.left + item.unit, top: item.top + item.unit, width: item.width + item.unit, height: item.height + item.unit }"
+            @mousedown="mouseDown($event, index)"
+          >
+            <span>{{ Number(index) + 1 }}</span>
+            <template v-if="item.link">
+              <span class="p-[4px]">|</span>
+              <span>{{ item.link }}</span>
+            </template>
+            <span class="box1" @mousedown.stop="resizeMouseDown($event, index)"></span>
+            <span class="box2" @mousedown.stop="resizeMouseDown($event, index)"></span>
+            <span class="box3" @mousedown.stop="resizeMouseDown($event, index)"></span>
+            <span class="box4" @mousedown.stop="resizeMouseDown($event, index)"></span>
+          </div>
+        </div>
+
+        <el-form label-width="80px" class="pl-[20px]">
+          <h3 class="mb-[10px] text-lg text-black">热区管理</h3>
+          <el-button type="primary" plain size="small" class="mb-[10px]" @click="addArea">添加热区</el-button>
+          <div class="overflow-y-auto h-[300px]">
+            <template v-for="(item, index) in dragBoxArr" :key="index">
+              <div class="mb-[16px]" v-if="item">
+                <el-form-item :label="'热区' + (Number(index) + 1)">
+                  <div class="flex items-center">
+                    <div style="width: 230px">
+                      <WebLinkInput :pageType="'2'" v-model="item.link" placeholder="请输入或选择链接" />
+                    </div>
+                    <icon
+                      class="del cursor-pointer mx-[10px]"
+                      name="element CircleCloseFilled"
+                      color="#bbb"
+                      size="20px"
+                      @click="dragBoxArr.splice(index, 1)"
+                    />
+                  </div>
+                </el-form-item>
+              </div>
+            </template>
+          </div>
+        </el-form>
+      </div>
+
+      <template #footer>
+        <span class="dialog-footer">
+          <el-button @click="showDialog = false">返回</el-button>
+          <el-button type="primary" @click="save">确定</el-button>
+        </span>
+      </template>
+    </el-dialog>
+  </div>
+</template>
+
+<script lang="ts" setup>
+import { ref, reactive, computed } from 'vue';
+import { ElMessage } from 'element-plus';
+import { img, deepClone } from '@/utils/common';
+
+const prop = defineProps({
+  modelValue: {
+    type: Object as () => Record<string, any>,
+    default: () => {}
+  }
+});
+
+const emit = defineEmits(['update:modelValue']);
+
+const value: any = computed({
+  get() {
+    return prop.modelValue;
+  },
+  set(value) {
+    emit('update:modelValue', value);
+  }
+});
+
+/**
+ * 公式:
+ * 宽度:400
+ * 比例:原图高/原图宽,示例:466/698=0.66
+ * 高度:宽度*比例,示例:400*0.66=264
+ */
+
+const showDialog = ref(false);
+const contentBoxWidth = ref(400);
+const contentBoxHeight = ref(400);
+const dragBoxArr: any = reactive([]);
+
+const imgRatio = ref(1); // 图片比例
+
+// 热区尺寸
+const areaRadio = ref(0.25); // 占位图比例
+const areaWidth = ref(100);
+const areaHeight = ref(100);
+const areaNum = ref(4); // 每行显示的数量
+
+// 添加热区
+const addArea = () => {
+  let left = (dragBoxArr.length % areaNum.value) * areaWidth.value;
+  let top = Math.floor(dragBoxArr.length / areaNum.value) * areaHeight.value;
+  const edgeHeight = top + areaHeight.value / 2;
+  if (top >= contentBoxHeight.value || edgeHeight >= contentBoxHeight.value) {
+    top = 0;
+    left = 0;
+  }
+
+  dragBoxArr.push({
+    left,
+    top,
+    width: areaWidth.value,
+    height: areaHeight.value,
+    unit: 'px',
+    link: ''
+  });
+};
+
+// 移动事件
+const mouseDown = (e: any, index: any) => {
+  const box: any = document.getElementById('box_' + index);
+  const disX = e.clientX - box.offsetLeft;
+  const disY = e.clientY - box.offsetTop;
+
+  // 鼠标移动时
+  document.onmousemove = function (e) {
+    box.style.left = e.clientX - disX + 'px';
+    box.style.top = e.clientY - disY + 'px';
+
+    // 边界判断
+    if (e.clientX - disX < 0) {
+      box.style.left = 0;
+    }
+
+    if (e.clientX - disX > contentBoxWidth.value - box.offsetWidth) {
+      box.style.left = contentBoxWidth.value - box.offsetWidth + 'px';
+    }
+
+    if (e.clientY - disY < 0) {
+      box.style.top = 0;
+    }
+
+    if (e.clientY - disY > contentBoxHeight.value - box.offsetHeight) {
+      box.style.top = contentBoxHeight.value - box.offsetHeight + 'px';
+    }
+
+    dragBoxArr[index].left = box.offsetLeft;
+    dragBoxArr[index].top = box.offsetTop;
+    dragBoxArr[index].width = box.offsetWidth;
+    dragBoxArr[index].height = box.offsetHeight;
+    dragBoxArr[index].unit = 'px';
+  };
+
+  // 鼠标抬起时
+  document.onmouseup = function (e) {
+    document.onmousemove = null;
+  };
+};
+
+// 拖拽大小事件
+const resizeMouseDown = (e: any, index: any) => {
+  const oEv = e;
+  oEv.stopPropagation();
+  const box: any = document.getElementById('box_' + index);
+  const className = e.target.className;
+
+  // 获取移动前盒子的宽高,
+  const oldWidth = box.offsetWidth;
+  const oldHeight = box.offsetHeight;
+
+  // 获取鼠标距离屏幕的left和top值
+  const oldX = oEv.clientX;
+  const oldY = oEv.clientY;
+
+  // 元素相对于最近的父级定位
+  const oldLeft = box.offsetLeft;
+  const oldTop = box.offsetTop;
+
+  // 设置最小的宽度
+  const minWidth = 50;
+  const minHeight = 50;
+
+  document.onmousemove = function (e) {
+    const oEv = e;
+    // console.log('move', "width:" + oldWidth,
+    //     ',oldLeft: ' + oldLeft, ',oldTop: ' + oldTop,
+    //     ',oldX:clientX-- ' + oldX + ':' + oEv.clientX,
+    //     ',oldY:clientY-- ' + oldY + ':' + oEv.clientY,
+    // )
+
+    // 左上角
+    if (className == 'box1') {
+      let width = oldWidth - (oEv.clientX - oldX);
+      const maxWidth = contentBoxWidth.value;
+
+      let height = oldHeight - (oEv.clientY - oldY);
+      const maxHeight = contentBoxHeight.value - oldTop;
+
+      let left = oldLeft + (oEv.clientX - oldX);
+      let top = oldTop + (oEv.clientY - oldY);
+
+      if (width < minWidth) {
+        width = minWidth;
+      }
+      if (width > maxWidth) {
+        width = maxWidth;
+      }
+
+      if (height < minHeight) {
+        height = minHeight;
+      }
+      if (height > maxHeight) {
+        height = maxHeight;
+      }
+
+      if (oldLeft == 0 && oldTop == 0) {
+        // 坐标:left = 0,top = 0
+
+        if (width == minWidth && height == minHeight) {
+          // 宽高 = 最小值,left = 最小宽度,top = 最小高度
+          left = minWidth;
+          top = minHeight;
+        } else if (width == minWidth && height > minHeight) {
+          // 宽 = 最小值,高 > 最小值,left = 最小宽度,top = 不予处理
+          left = minWidth;
+        } else if (width > minWidth && height == minHeight) {
+          // 宽 > 最小值,高 = 最小值,left = 不予处理,top = 最小高度
+          top = minHeight;
+        } else if (width > minWidth && height > minHeight) {
+          // 宽 > 最小值,高 > 最小值,left = 不予处理,top = 不予处理
+        }
+      } else if (oldLeft == 0 && oldTop > 0) {
+        // 坐标:left = 0,top > 0
+
+        if (width == minWidth && height == minHeight) {
+          // 宽高 = 最小值,left = 最小宽度,top = 元素上偏移位置
+          left = minWidth;
+          top = box.offsetTop;
+        } else if (width == minWidth && height > minHeight) {
+          // 宽 = 最小值,高 > 最小值,left = 最小宽度,top = 元素上偏移位置
+          left = minWidth;
+          top = box.offsetTop;
+        } else if (width > minWidth && height == minHeight) {
+          // 宽 > 最小值,高 = 最小值,left = 不予处理,top = 元素上偏移位置
+          top = box.offsetTop;
+        } else if (width > minWidth && height > minHeight) {
+          // 宽 > 最小值,高 > 最小值,left = 不予处理,top = 不予处理
+        }
+      } else if (oldLeft > 0 && oldTop == 0) {
+        // 坐标:left > 0,top = 0
+
+        if (width == minWidth && height == minHeight) {
+          // 宽高 = 最小值,left = 元素左偏移位置,top = 元素上偏移位置
+          left = box.offsetLeft;
+          top = box.offsetTop;
+        } else if (width == minWidth && height > minHeight) {
+          // 宽 = 最小值,高 > 最小值,left = 元素左偏移位置,top = 0
+          left = box.offsetLeft;
+          top = 0;
+        } else if (width > minWidth && height == minHeight) {
+          // 宽 > 最小值,高 = 最小值,left = 不予处理,top = 元素上偏移位置
+          top = box.offsetTop;
+        } else if (width > minWidth && height > minHeight) {
+          // 宽 > 最小值,高 > 最小值,left = 不予处理,top = 不予处理
+        }
+      } else if (oldLeft > 0 && oldTop > 0) {
+        // 坐标:left > 0,top > 0
+
+        if (width == minWidth && height == minHeight) {
+          // 宽高 = 最小值,left = 元素左偏移位置,top = 元素上偏移位置
+          left = box.offsetLeft;
+          top = box.offsetTop;
+        } else if (width == minWidth && height > minHeight) {
+          // 宽 = 最小值,高 > 最小值,left = 元素左偏移位置,top = 元素上偏移位置
+          left = box.offsetLeft;
+          top = box.offsetTop;
+        } else if (width > minWidth && height == minHeight) {
+          // 宽 > 最小值,高 = 最小值,left = 不予处理,top = 元素上偏移位置
+          top = box.offsetTop;
+        } else if (width > minWidth && height > minHeight) {
+          // 宽 > 最小值,高 > 最小值,left = 不予处理,top = 不予处理
+        }
+      }
+
+      // 左上宽
+      if (left < 0) {
+        left = 0;
+        width = oldWidth - (oEv.clientX - oldX) + (oldLeft + (oEv.clientX - oldX));
+      }
+
+      // 左上 高
+      if (top < 0) {
+        top = 0;
+        height = oldTop + (oEv.clientY - oldY) + (oldHeight - (oEv.clientY - oldY));
+      }
+
+      box.style.width = width + 'px';
+      box.style.height = height + 'px';
+      box.style.left = left + 'px';
+      box.style.top = top + 'px';
+    } else if (className == 'box2') {
+      // 右上角
+
+      let width = oldWidth + (oEv.clientX - oldX);
+      const maxWidth = contentBoxWidth.value - oldLeft;
+
+      let height = oldHeight - (oEv.clientY - oldY);
+      const maxHeight = contentBoxHeight.value - oldTop;
+
+      let top = oldTop + (oEv.clientY - oldY);
+
+      if (width < minWidth) {
+        width = minWidth;
+      }
+      if (width > maxWidth) {
+        width = maxWidth;
+      }
+
+      if (height < minHeight) {
+        height = minHeight;
+      }
+      if (height > maxHeight) {
+        height = maxHeight;
+      }
+
+      if (oldLeft == 0 && oldTop == 0) {
+        // 坐标:left = 0,top = 0
+
+        if (width == minWidth && height == minHeight) {
+          // 宽高 = 最小值,top = 最小高度
+          top = minHeight;
+        } else if (width == minWidth && height > minHeight) {
+          // 宽 = 最小值,高 > 最小值,不予处理
+        } else if (width > minWidth && height == minHeight) {
+          // 宽 > 最小值,高 = 最小值,top = 最小高度
+          top = minHeight;
+        } else if (width > minWidth && height > minHeight) {
+          // 宽 > 最小值,高 > 最小值,不予处理
+        }
+      } else if (oldLeft == 0 && oldTop > 0) {
+        // 坐标:left = 0,top > 0
+
+        if (width == minWidth && height == minHeight) {
+          // 宽高 = 最小值,top = 元素上偏移位置
+          top = box.offsetTop;
+        } else if (width == minWidth && height > minHeight) {
+          // 宽 = 最小值,高 > 最小值,top = 元素上偏移位置
+          top = box.offsetTop;
+        } else if (width > minWidth && height == minHeight) {
+          // 宽 > 最小值,高 = 最小值,top = 元素上偏移位置
+          top = box.offsetTop;
+        } else if (width > minWidth && height > minHeight) {
+          // 宽 > 最小值,高 > 最小值,不予处理
+        }
+      } else if (oldLeft > 0 && oldTop == 0) {
+        // 坐标:left = 0,top = 0
+
+        if (width == minWidth && height == minHeight) {
+          // 宽高 = 最小值,top = 元素上偏移位置
+          top = box.offsetTop;
+        } else if (width == minWidth && height > minHeight) {
+          // 宽 = 最小值,高 > 最小值,top = 0
+          top = 0;
+        } else if (width > minWidth && height == minHeight) {
+          // 宽 > 最小值,高 = 最小值,top = 元素上偏移位置
+          top = box.offsetTop;
+        } else if (width > minWidth && height > minHeight) {
+          // 宽 > 最小值,高 > 最小值,不予处理
+        }
+      } else if (oldLeft > 0 && oldTop > 0) {
+        // 坐标:left > 0,top > 0
+
+        if (width == minWidth && height == minHeight) {
+          // 宽高 = 最小值,top = 元素上偏移位置
+          top = box.offsetTop;
+        } else if (width == minWidth && height > minHeight) {
+          // 宽 = 最小值,高 > 最小值,top = 元素上偏移位置
+          top = box.offsetTop;
+        } else if (width > minWidth && height == minHeight) {
+          // 宽 > 最小值,高 = 最小值,top = 元素上偏移位置
+          top = box.offsetTop;
+        } else if (width > minWidth && height > minHeight) {
+          // 宽 > 最小值,高 > 最小值,不予处理
+        }
+      }
+
+      // 右上高
+      if (top < 0) {
+        top = 0;
+        height = oldTop + (oEv.clientY - oldY) + (oldHeight - (oEv.clientY - oldY));
+      }
+
+      box.style.width = width + 'px';
+      box.style.height = height + 'px';
+      box.style.top = top + 'px';
+    } else if (className == 'box3') {
+      // 左下角
+
+      let width = oldWidth - (oEv.clientX - oldX);
+      const maxWidth = contentBoxWidth.value;
+
+      let height = oldHeight + (oEv.clientY - oldY);
+      const maxHeight = contentBoxHeight.value - oldTop;
+
+      let left = oldLeft + (oEv.clientX - oldX);
+
+      if (width < minWidth) {
+        width = minWidth;
+      }
+      if (width > maxWidth) {
+        width = maxWidth;
+      }
+
+      if (height < minHeight) {
+        height = minHeight;
+      }
+      if (height > maxHeight) {
+        height = maxHeight;
+      }
+
+      if (oldLeft == 0 && oldTop == 0) {
+        // 坐标:left = 0,top = 0
+
+        if (width == minWidth && height == minHeight) {
+          // 宽高 = 最小值,left = 最小宽度
+          left = minWidth;
+        } else if (width == minWidth && height > minHeight) {
+          // 宽 = 最小值,高 > 最小值,left = 最小宽度
+          left = minWidth;
+        } else if (width > minWidth && height == minHeight) {
+          // 宽 > 最小值,高 = 最小值,不予处理
+        } else if (width > minWidth && height > minHeight) {
+          // 宽 > 最小值,高 > 最小值,不予处理
+        }
+      } else if (oldLeft == 0 && oldTop > 0) {
+        // 坐标:left = 0,top > 0
+
+        if (width == minWidth && height == minHeight) {
+          // 宽高 = 最小值,left = 最小宽度
+          left = minWidth;
+        } else if (width == minWidth && height > minHeight) {
+          // 宽 = 最小值,高 > 最小值,left = 最小宽度
+          left = minWidth;
+        } else if (width > minWidth && height == minHeight) {
+          // 宽 > 最小值,高 = 最小值,不予处理
+        } else if (width > minWidth && height > minHeight) {
+          // 宽 > 最小值,高 > 最小值,不予处理
+        }
+      } else if (oldLeft > 0 && oldTop == 0) {
+        // 坐标:left > 0,top = 0
+
+        if (width == minWidth && height == minHeight) {
+          // 宽高 = 最小值,left = 元素左偏移位置
+          left = box.offsetLeft;
+        } else if (width == minWidth && height > minHeight) {
+          // 宽 = 最小值,高 > 最小值,left = 元素左偏移位置
+          left = box.offsetLeft;
+        } else if (width > minWidth && height == minHeight) {
+          // 宽 > 最小值,高 = 最小值,不予处理
+        } else if (width > minWidth && height > minHeight) {
+          // 宽 > 最小值,高 > 最小值,不予处理
+        }
+      } else if (oldLeft > 0 && oldTop > 0) {
+        // 坐标:left > 0,top > 0
+
+        if (width == minWidth && height == minHeight) {
+          // 宽高 = 最小值,left = 元素左偏移位置
+          left = box.offsetLeft;
+        } else if (width == minWidth && height > minHeight) {
+          // 宽 = 最小值,高 > 最小值,left = 元素左偏移位置
+          left = box.offsetLeft;
+        } else if (width > minWidth && height == minHeight) {
+          // 宽 > 最小值,高 = 最小值,不予处理
+        } else if (width > minWidth && height > minHeight) {
+          // 宽 > 最小值,高 > 最小值,不予处理
+        }
+      }
+
+      if (left < 0) {
+        left = 0;
+        width = oldWidth - (oEv.clientX - oldX) + (oldLeft + (oEv.clientX - oldX));
+      }
+
+      box.style.width = width + 'px';
+      box.style.height = height + 'px';
+      box.style.left = left + 'px';
+    } else if (className == 'box4') {
+      // 右下角
+
+      let width = oldWidth + (oEv.clientX - oldX);
+      const maxWidth = contentBoxWidth.value - oldLeft;
+
+      let height = oldHeight + (oEv.clientY - oldY);
+      const maxHeight = contentBoxHeight.value - oldTop;
+
+      if (width < minWidth) {
+        width = minWidth;
+      }
+      if (width > maxWidth) {
+        width = maxWidth;
+      }
+
+      if (height < minHeight) {
+        height = minHeight;
+      }
+      if (height > maxHeight) {
+        height = maxHeight;
+      }
+
+      box.style.width = width + 'px';
+      box.style.height = height + 'px';
+    }
+
+    dragBoxArr[index].left = box.offsetLeft;
+    dragBoxArr[index].top = box.offsetTop;
+    dragBoxArr[index].width = box.offsetWidth;
+    dragBoxArr[index].height = box.offsetHeight;
+    dragBoxArr[index].unit = 'px';
+  };
+
+  // 鼠标抬起时
+  document.onmouseup = function () {
+    document.onmousemove = null;
+    document.onmouseup = null;
+  };
+};
+
+const show = () => {
+  // 每次打开时赋值
+  if (!value.value.imageUrl) {
+    ElMessage({
+      type: 'warning',
+      message: '请上传图片'
+    });
+    return;
+  }
+
+  // 计算图片比例
+  imgRatio.value = value.value.imgHeight / value.value.imgWidth;
+
+  // 根据图片比例,调整图片高度
+  contentBoxHeight.value = Math.floor(contentBoxWidth.value * imgRatio.value);
+
+  areaWidth.value = Math.floor(areaRadio.value * contentBoxHeight.value);
+  areaHeight.value = areaWidth.value;
+  areaNum.value = Math.floor(contentBoxWidth.value / areaWidth.value);
+
+  if (Object.keys(value.value.heatMapData).length) {
+    dragBoxArr.splice(0, dragBoxArr.length, ...value.value.heatMapData);
+  } else {
+    dragBoxArr.splice(0, dragBoxArr.length);
+    addArea();
+  }
+  showDialog.value = true;
+};
+
+const save = () => {
+  let isOk = true;
+  for (let i = 0; i < dragBoxArr.length; i++) {
+    if (!dragBoxArr[i].link) {
+      ElMessage({
+        type: 'warning',
+        message: '请选择热区' + (i + 1) + '的链接地址'
+      });
+      isOk = false;
+      break;
+    }
+  }
+
+  if (!isOk) return;
+
+  dragBoxArr.forEach((item: any, index: number) => {
+    const box: any = document.getElementById('box_' + index);
+    item.width = Number(((box.offsetWidth / contentBoxWidth.value) * 100).toFixed(2));
+    item.height = Number(((box.offsetHeight / contentBoxHeight.value) * 100).toFixed(2));
+    item.left = Number(((box.offsetLeft / contentBoxWidth.value) * 100).toFixed(2));
+    item.top = Number(((box.offsetTop / contentBoxHeight.value) * 100).toFixed(2));
+    item.unit = '%';
+  });
+
+  value.value.heatMapData = deepClone(dragBoxArr);
+
+  showDialog.value = false;
+};
+
+defineExpose({
+  showDialog
+});
+</script>
+
+<style lang="scss" scoped>
+.area-box {
+  background-color: rgba(255, 255, 255, 0.7);
+}
+
+.hot-area-img-wrap {
+  // width: 1200px;
+
+  background-size: 100%;
+}
+
+.box1,
+.box2,
+.box3,
+.box4 {
+  width: 10px;
+  height: 10px;
+  background-color: #fff;
+  position: absolute;
+  border-radius: 50%;
+  border: 1px solid #333;
+}
+
+.box1 {
+  top: -5px;
+  left: -5px;
+  cursor: nw-resize;
+}
+
+.box2 {
+  top: -5px;
+  right: -5px;
+  cursor: ne-resize;
+}
+
+.box3 {
+  left: -5px;
+  bottom: -5px;
+  cursor: sw-resize;
+}
+
+.box4 {
+  bottom: -5px;
+  right: -5px;
+  cursor: se-resize;
+}
+</style>

+ 4 - 4
src/views/diy/components/edit-active-cube.vue

@@ -21,13 +21,13 @@
             <upload-image v-model="diyStore.editComponent.textImg" :limit="1" />
           </el-form-item>
           <el-form-item label="链接地址">
-            <diy-link v-model="diyStore.editComponent.textLink" />
+            <WebLinkInput :pageType="'2'" v-model="diyStore.editComponent.textLink" placeholder="请输入或选择链接" />
           </el-form-item>
           <el-form-item label="副标题">
             <el-input v-model.trim="diyStore.editComponent.subTitle.text" placeholder="请输入副标题" clearable maxlength="8" show-word-limit />
           </el-form-item>
           <el-form-item label="链接地址">
-            <diy-link v-model="diyStore.editComponent.subTitle.link" />
+            <WebLinkInput :pageType="'2'" v-model="diyStore.editComponent.subTitle.link" placeholder="请输入或选择链接" />
           </el-form-item>
         </el-form>
 
@@ -138,7 +138,7 @@
               </div>
 
               <el-form-item label="链接地址">
-                <diy-link v-model="item.link" />
+                <WebLinkInput :pageType="'2'" v-model="item.link" placeholder="请输入或选择链接" />
               </el-form-item>
 
               <div
@@ -590,7 +590,7 @@ const addItem = () => {
       endColor: '#FE1E00'
     },
     imageUrl: '',
-    link: { name: '' }
+    link: ''
   });
 };
 

+ 3 - 98
src/views/diy/components/edit-carousel-search.vue

@@ -55,7 +55,7 @@
             </div>
           </el-form-item>
           <el-form-item label="链接地址">
-            <WebLinkInput v-model="diyStore.editComponent.search.link" placeholder="请输入或选择链接" />
+            <WebLinkInput :pageType="'2'" v-model="diyStore.editComponent.search.link" placeholder="请输入或选择链接" />
           </el-form-item>
         </el-form>
 
@@ -120,101 +120,6 @@
       </div>
 
       <el-collapse v-model="activeNames" @change="handleChange" class="collapse-wrap">
-        <el-collapse-item title="选项卡设置" name="tab">
-          <div class="edit-attr-item-wrap">
-            <el-form label-width="100px" class="px-[10px]" @submit.prevent>
-              <el-form-item label="展示开关">
-                <el-switch v-model="diyStore.editComponent.tab.control" />
-              </el-form-item>
-
-              <p class="text-sm text-gray-400 mb-[10px]">鼠标拖拽可调整顺序</p>
-
-              <div ref="tabBoxRef">
-                <div
-                  v-for="(item, index) in diyStore.editComponent.tab.list"
-                  :key="item.id"
-                  class="item-wrap p-[10px] pb-0 relative border border-dashed border-gray-300 mb-[16px]"
-                >
-                  <el-form-item label="分类名称">
-                    <el-input v-model.trim="item.text" placeholder="请输入分类名称" clearable maxlength="4" show-word-limit />
-                  </el-form-item>
-
-                  <el-form-item label="数据来源">
-                    <el-input
-                      v-model.trim="item.diy_title"
-                      placeholder="请选择微页面"
-                      readonly
-                      class="select-diy-page-input"
-                      @click="diyPageShowDialogOpen(index)"
-                    >
-                      <template #suffix>
-                        <div @click.stop="tabClear(index)">
-                          <el-icon v-if="item.diy_title">
-                            <Close />
-                          </el-icon>
-                          <el-icon v-else>
-                            <ArrowRight />
-                          </el-icon>
-                        </div>
-                      </template>
-                    </el-input>
-                  </el-form-item>
-
-                  <div
-                    class="del absolute cursor-pointer z-[2] top-[-8px] right-[-8px]"
-                    v-show="diyStore.editComponent.tab.list.length > 1"
-                    @click="diyStore.editComponent.tab.list.splice(index, 1)"
-                  >
-                    <icon name="element CircleCloseFilled" color="#bbb" size="20px" />
-                  </div>
-                </div>
-                <el-button v-show="diyStore.editComponent.tab.list.length < 50" class="w-full" @click="addTabItem">添加一个选项卡</el-button>
-              </div>
-
-              <!-- 选择微页面弹出框 -->
-              <el-dialog
-                v-model="diyPageShowDialog"
-                title="选择微页面"
-                width="1000px"
-                :close-on-press-escape="true"
-                :destroy-on-close="true"
-                :close-on-click-modal="false"
-              >
-                <el-table
-                  :data="diyPageTable.data"
-                  ref="diyPageTableRef"
-                  size="large"
-                  v-loading="diyPageTable.loading"
-                  height="490px"
-                  @current-change="handleCurrentDiyPageChange"
-                  row-key="id"
-                  highlight-current-row
-                >
-                  <template #empty>
-                    <span>{{ !diyPageTable.loading ? '还没有安装应用' : '' }}</span>
-                  </template>
-                  <el-table-column prop="page_title" label="页面名称" min-width="120" />
-                  <el-table-column prop="addon_name" label="页面类型" min-width="80" />
-                  <el-table-column prop="type_name" label="所属应用" min-width="80" />
-                </el-table>
-                <div class="mt-[16px] flex justify-end">
-                  <el-pagination
-                    v-model:current-page="diyPageTable.page"
-                    v-model:page-size="diyPageTable.limit"
-                    layout="total, sizes, prev, pager, next, jumper"
-                    :total="diyPageTable.total"
-                    @size-change="loadDiyPageList"
-                    @current-change="loadDiyPageList"
-                  />
-                </div>
-                <div class="flex items-center justify-end mt-[15px]">
-                  <el-button type="primary" @click="saveDiyPageId">确认</el-button>
-                  <el-button @click="diyPageShowDialog = false">取消</el-button>
-                </div>
-              </el-dialog>
-            </el-form>
-          </div>
-        </el-collapse-item>
         <el-collapse-item title="轮播图设置" name="swiper">
           <el-form label-width="100px" class="px-[10px]">
             <el-form-item label="展示开关">
@@ -252,7 +157,7 @@
                 </div>
 
                 <el-form-item label="链接地址">
-                  <WebLinkInput v-model="item.link" placeholder="请输入或选择链接" />
+                  <WebLinkInput :pageType="'2'" v-model="item.link" placeholder="请输入或选择链接" />
                 </el-form-item>
               </div>
             </div>
@@ -664,7 +569,7 @@ const addImageAd = () => {
     imageUrl: '',
     imgWidth: 0,
     imgHeight: 0,
-    link: { name: '' }
+    link: ''
   });
 };
 

+ 1 - 1
src/views/diy/components/edit-float-btn.vue

@@ -61,7 +61,7 @@
               </div>
 
               <el-form-item label="链接地址">
-                <diy-link v-model="item.link" />
+                <WebLinkInput :pageType="'2'" v-model="item.link" placeholder="请输入或选择链接" />
               </el-form-item>
             </div>
           </div>

+ 0 - 273
src/views/diy/components/edit-goods-coupon.vue

@@ -1,273 +0,0 @@
-<template>
-  <div>
-    <!-- 内容 -->
-    <div class="content-wrap" v-show="diyStore.editTab == 'content'">
-      <div class="edit-attr-item-wrap">
-        <h3 class="mb-[10px]">风格选择</h3>
-        <el-form label-width="80px" class="px-[10px]">
-          <el-form-item label="风格选择" class="flex">
-            <span class="text-primary flex-1 cursor-pointer" @click="showCouponStyle">{{ diyStore.editComponent.styleName }}</span>
-            <el-icon @click="showCouponStyle" class="cursor-pointer">
-              <ArrowRight />
-            </el-icon>
-          </el-form-item>
-        </el-form>
-
-        <el-dialog v-model="showCouponDialog" title="风格选择" width="500px">
-          <div class="data-bos">
-            <template v-for="(item, index) in couponStyleList" :key="index">
-              <div
-                :class="{ 'border-primary': selectCouponStyle.value == item.value }"
-                @click="changeCouponStyle(item)"
-                class="data-list flex items-center justify-center overflow-hidden w-[200px] h-[100px] m-[6px] cursor-pointer border bg-gray-50"
-              >
-                <img :src="img(item.url)" />
-              </div>
-            </template>
-          </div>
-
-          <template #footer>
-            <span class="dialog-footer">
-              <el-button @click="showCouponDialog = false">取消</el-button>
-              <el-button type="primary" @click="confirmCouponStyle">确认</el-button>
-            </span>
-          </template>
-        </el-dialog>
-      </div>
-
-      <div class="edit-attr-item-wrap">
-        <h3 class="mb-[10px]">优惠券内容</h3>
-        <el-form label-width="90px" class="px-[10px]">
-          <el-form-item label="标题">
-            <el-input
-              v-model.trim="diyStore.editComponent.couponTitle"
-              clearable
-              :maxlength="diyStore.editComponent.style == 'style-3' ? 4 : 8"
-              show-word-limit
-            />
-          </el-form-item>
-
-          <el-form-item label="副标题">
-            <el-input
-              v-model.trim="diyStore.editComponent.couponSubTitle"
-              clearable
-              :maxlength="diyStore.editComponent.style == 'style-3' ? 7 : 10"
-              show-word-limit
-            />
-          </el-form-item>
-        </el-form>
-      </div>
-
-      <div class="edit-attr-item-wrap">
-        <h3 class="mb-[10px]">优惠券数据</h3>
-        <el-form label-width="90px" class="px-[10px]">
-          <el-form-item label="选择优惠券">
-            <el-radio-group v-model="diyStore.editComponent.source" title="选择商品">
-              <el-radio value="all">全部</el-radio>
-              <el-radio value="custom">手动选择</el-radio>
-            </el-radio-group>
-          </el-form-item>
-          <el-form-item label="手动选择" v-if="diyStore.editComponent.source == 'custom'">
-            <coupon-select-popup ref="couponSelectPopupRef" v-model="diyStore.editComponent.couponIds" :min="1" :max="20" />
-          </el-form-item>
-          <el-form-item label="优惠券数量" v-if="diyStore.editComponent.source == 'all'">
-            <el-slider show-input v-model="diyStore.editComponent.num" :min="1" :max="20" size="small" class="goods-coupon-slider" />
-          </el-form-item>
-          <el-form-item label="按钮文字" v-if="diyStore.editComponent.style != 'style-4'">
-            <el-input v-model.trim="diyStore.editComponent.btnText" clearable maxlength="5" show-word-limit />
-          </el-form-item>
-        </el-form>
-      </div>
-    </div>
-
-    <!-- 样式 -->
-    <div class="style-wrap" v-show="diyStore.editTab == 'style'">
-      <div class="edit-attr-item-wrap" v-if="diyStore.editComponent.style == 'style-4'">
-        <h3 class="mb-[10px]">标题样式</h3>
-        <el-form label-width="90px" class="px-[10px]">
-          <el-form-item label="标题颜色">
-            <el-color-picker v-model="diyStore.editComponent.titleColor" show-alpha :predefine="diyStore.predefineColors" />
-          </el-form-item>
-          <el-form-item label="副标题颜色">
-            <el-color-picker v-model="diyStore.editComponent.subTitleColor" show-alpha :predefine="diyStore.predefineColors" />
-          </el-form-item>
-        </el-form>
-      </div>
-
-      <div class="edit-attr-item-wrap" v-if="diyStore.editComponent.style == 'style-4'">
-        <h3 class="mb-[10px]">优惠券项样式</h3>
-        <el-form label-width="90px" class="px-[10px]">
-          <el-form-item label="金额颜色">
-            <el-color-picker v-model="diyStore.editComponent.couponItem.moneyColor" show-alpha :predefine="diyStore.predefineColors" />
-          </el-form-item>
-          <el-form-item label="标题颜色">
-            <el-color-picker v-model="diyStore.editComponent.couponItem.textColor" show-alpha :predefine="diyStore.predefineColors" />
-          </el-form-item>
-          <el-form-item label="副标题颜色">
-            <el-color-picker v-model="diyStore.editComponent.couponItem.subTextColor" show-alpha :predefine="diyStore.predefineColors" />
-          </el-form-item>
-          <el-form-item label="背景颜色">
-            <el-color-picker v-model="diyStore.editComponent.couponItem.bgColor" show-alpha :predefine="diyStore.predefineColors" />
-          </el-form-item>
-          <el-form-item label="圆角">
-            <el-slider v-model="diyStore.editComponent.couponItem.aroundRadius" show-input size="small" class="ml-[10px] diy-nav-slider" :max="50" />
-          </el-form-item>
-        </el-form>
-      </div>
-
-      <!-- 组件样式 -->
-      <slot name="style"></slot>
-    </div>
-  </div>
-</template>
-
-<script lang="ts" setup>
-import useDiyStore from '@/store/modules/diy';
-import { img } from '@/utils/common';
-// import couponSelectPopup from '@/addon/shop/views/goods/components/coupon-select-popup.vue';
-
-import style1 from '@/assets/images/diy/shop/style-1.png';
-import style2 from '@/assets/images/diy/shop/style-2.png';
-import style3 from '@/assets/images/diy/shop/style-3.png';
-import style4 from '@/assets/images/diy/shop/style-4.png';
-
-const diyStore = useDiyStore();
-diyStore.editComponent.ignore = ['componentBgColor', 'componentBgUrl']; // 忽略公共属性
-
-// 组件验证
-diyStore.editComponent.verify = (index: number) => {
-  const res = { code: true, message: '' };
-
-  if (diyStore.value[index].source == 'custom') {
-    if (diyStore.value[index].couponIds.length == 0) {
-      res.code = false;
-      res.message = '请选择优惠券';
-      return res;
-    }
-  }
-
-  if (diyStore.value[index].btnText == '') {
-    res.code = false;
-    res.message = '请输入按钮文字';
-    return res;
-  }
-
-  if (diyStore.value[index].couponTitle == '') {
-    res.code = false;
-    res.message = '请输入标题';
-    return res;
-  }
-
-  if (diyStore.value[index].couponSubTitle == '') {
-    res.code = false;
-    res.message = '请输入副标题';
-    return res;
-  }
-
-  return res;
-};
-
-const selectCouponStyle = reactive({
-  title: diyStore.editComponent.styleName,
-  value: diyStore.editComponent.style
-});
-
-// 风格样式
-const showCouponDialog = ref(false);
-
-const showCouponStyle = () => {
-  showCouponDialog.value = true;
-  selectCouponStyle.title = diyStore.editComponent.styleName;
-  selectCouponStyle.value = diyStore.editComponent.style;
-};
-
-const couponStyleList = reactive([
-  {
-    url: style1,
-    title: '风格1',
-    value: 'style-1'
-  },
-  {
-    url: style2,
-    title: '风格2',
-    value: 'style-2'
-  },
-  {
-    url: style3,
-    title: '风格3',
-    value: 'style-3'
-  },
-  {
-    url: style4,
-    title: '风格4',
-    value: 'style-4'
-  }
-]);
-
-const changeCouponStyle = (item: any) => {
-  selectCouponStyle.title = item.title;
-  selectCouponStyle.value = item.value;
-};
-
-const confirmCouponStyle = () => {
-  diyStore.editComponent.styleName = selectCouponStyle.title;
-  diyStore.editComponent.style = selectCouponStyle.value;
-  if (diyStore.editComponent.style == 'style-3') {
-    if (diyStore.editComponent.couponTitle && diyStore.editComponent.couponTitle.length > 4) {
-      diyStore.editComponent.couponTitle = diyStore.editComponent.couponTitle.substring(0, 4);
-    }
-    if (diyStore.editComponent.couponSubTitle && diyStore.editComponent.couponSubTitle.length > 7) {
-      diyStore.editComponent.couponSubTitle = diyStore.editComponent.couponSubTitle.substring(0, 7);
-    }
-  }
-  initStyleFn();
-  showCouponDialog.value = false;
-};
-
-const initStyleFn = () => {
-  const index = diyStore.editComponent.ignore.indexOf('componentBgColor');
-  if (diyStore.editComponent.style == 'style-4' && index != -1) {
-    diyStore.editComponent.ignore.splice(index, 1);
-    diyStore.editComponent.titleColor = '#ffffff';
-    diyStore.editComponent.subTitleColor = '#ffffff';
-
-    diyStore.editComponent.couponItem.moneyColor = '#fa191d';
-    diyStore.editComponent.couponItem.textColor = '#333333';
-    diyStore.editComponent.couponItem.subTextColor = '#999999';
-    diyStore.editComponent.couponItem.bgColor = '#ffffff';
-    diyStore.editComponent.couponItem.aroundRadius = 10;
-    diyStore.editComponent.componentStartBgColor = '#fa191d';
-  } else if (diyStore.editComponent.style != 'style-4' && index == -1) {
-    diyStore.editComponent.ignore.push('componentBgColor');
-  }
-};
-
-defineExpose({});
-</script>
-
-<style lang="scss" scoped></style>
-
-<style lang="scss">
-.goods-coupon-slider {
-  .el-slider__input {
-    width: 100px;
-  }
-}
-
-.data-bos {
-  display: flex;
-  flex-wrap: wrap;
-  width: 624px;
-  gap: 0 12px;
-  .data-list {
-    background-color: #f9fafb;
-    border: 1px solid #e5e7eb;
-    &.border-primary {
-      border-color: var(--el-color-primary);
-    }
-    img {
-      width: 100%;
-    }
-  }
-}
-</style>

+ 0 - 405
src/views/diy/components/edit-goods-list copy.vue

@@ -1,405 +0,0 @@
-<template>
-  <div>
-    <!-- 内容 -->
-    <div class="content-wrap" v-show="diyStore.editTab == 'content'">
-      <div class="edit-attr-item-wrap">
-        <h3 class="mb-[10px]">风格选择</h3>
-        <div class="flex items-center mb-[18px] rounded overflow-hidden">
-          <span
-            class="iconfont iconzuoyoutuwenpc border-[1px] border-solid border-[#eee] cursor-pointer flex-1 flex items-center justify-center py-[5px]"
-            :class="{ 'border-[var(--el-color-primary)] text-[var(--el-color-primary)]': diyStore.editComponent.style == 'style-1' }"
-            @click="styleChangeFn('style-1')"
-          ></span>
-
-          <span
-            class="iconfont iconshangxiatuwenpc border-[1px] border-solid border-[#eee] cursor-pointer flex-1 flex items-center justify-center py-[5px]"
-            :class="{ 'border-[var(--el-color-primary)] text-[var(--el-color-primary)]': diyStore.editComponent.style == 'style-2' }"
-            @click="styleChangeFn('style-2')"
-          ></span>
-          <span
-            class="iconfont iconliebiaopc border-[1px] border-solid border-[#eee] cursor-pointer flex-1 flex items-center justify-center py-[5px]"
-            :class="{ 'border-[var(--el-color-primary)] text-[var(--el-color-primary)]': diyStore.editComponent.style == 'style-3' }"
-            @click="styleChangeFn('style-3')"
-          ></span>
-        </div>
-      </div>
-      <div class="edit-attr-item-wrap">
-        <h3 class="mb-[10px]">选择数据源</h3>
-        <el-form label-width="80px" class="px-[10px]">
-          <el-form-item label="排序">
-            <el-radio-group v-model="diyStore.editComponent.sortWay">
-              <el-radio value="default">综合</el-radio>
-              <el-radio value="sale_num">销量</el-radio>
-              <el-radio value="price">价格</el-radio>
-            </el-radio-group>
-          </el-form-item>
-          <el-form-item label="选择商品">
-            <el-radio-group v-model="diyStore.editComponent.source" title="选择商品">
-              <el-radio :value="1">指定商品</el-radio>
-              <el-radio :value="2">商品分类</el-radio>
-              <el-radio :value="3">商品品牌</el-radio>
-              <!-- <el-radio value="all">全部商品</el-radio>
-              <el-radio value="category">选择分类</el-radio>
-              <el-radio value="custom">手动选择</el-radio> -->
-            </el-radio-group>
-          </el-form-item>
-          <el-form-item label="指定商品" v-if="diyStore.editComponent.source == 1">
-            <div class="data-num" @click="openDialog">
-              <!-- <span v-if="diyStore.editComponent.goodsIds.length == 0">请选择</span>
-              <span v-else>已选择{{ diyStore.editComponent.goodsIds.length }}个</span> -->
-              <el-icon><ArrowRight /></el-icon>
-            </div>
-          </el-form-item>
-          <el-form-item label="选择分类" v-if="diyStore.editComponent.source == 'category'">
-            <div class="flex items-center w-full">
-              <div class="cursor-pointer ml-auto" @click="categoryShowDialogOpen">
-                <span class="text-[var(--el-color-primary)]">{{ diyStore.editComponent.goods_category_name }}</span>
-                <span class="iconfont iconxiangyoujiantou"></span>
-              </div>
-            </div>
-          </el-form-item>
-          <el-form-item label="商品数量" v-if="diyStore.editComponent.source == 'all' || diyStore.editComponent.source == 'category'">
-            <el-slider class="goods-list-slider" show-input v-model="diyStore.editComponent.num" :min="1" :max="20" size="small" />
-          </el-form-item>
-          <!-- <el-form-item label="手动选择" v-if="diyStore.editComponent.source == 'custom'">
-            <goods-select-popup ref="goodsSelectPopupRef" v-model="diyStore.editComponent.goods_ids" :min="1" :max="99" />
-          </el-form-item> -->
-        </el-form>
-
-        <el-dialog v-model="categoryShowDialog" title="商品分类" width="750px" :destroy-on-close="true" :close-on-click-modal="false">
-          <el-table
-            :data="categoryTable.data"
-            ref="categoryTableRef"
-            size="large"
-            v-loading="categoryTable.loading"
-            height="450px"
-            @selection-change="handleSelectionChange"
-            row-key="category_id"
-            :expand-row-keys="expand_category_ids"
-            :tree-props="{ hasChildren: 'hasChildren', children: 'child_list' }"
-          >
-            <template #empty>
-              <span>{{ !categoryTable.loading ? '还没有安装应用' : '' }}</span>
-            </template>
-            <el-table-column type="selection" width="55" />
-            <el-table-column label="分类名称" min-width="120">
-              <template #default="{ row }">
-                <span class="order-2">{{ row.category_name }}</span>
-              </template>
-            </el-table-column>
-            <el-table-column label="分类图片" width="170" align="left">
-              <template #default="{ row }">
-                <div class="h-[30px]">
-                  <el-image class="w-[30px] h-[30px]" :src="img(row.image)" fit="contain">
-                    <template #error>
-                      <div class="image-slot">
-                        <!-- <img class="w-[30px] h-[30px]" src="@/addon/shop/assets/category_default.png" /> -->
-                      </div>
-                    </template>
-                  </el-image>
-                </div>
-              </template>
-            </el-table-column>
-          </el-table>
-          <div class="flex items-center justify-end mt-[15px]">
-            <el-button type="primary" @click="saveCategoryId">确认</el-button>
-            <el-button @click="categoryShowDialog = false">取消</el-button>
-          </div>
-        </el-dialog>
-      </div>
-
-      <div class="edit-attr-item-wrap">
-        <h3 class="mb-[10px]">购买按钮</h3>
-        <el-form label-width="90px" class="px-[10px]">
-          <el-form-item label="是否显示">
-            <el-switch v-model="diyStore.editComponent.btnStyle.control" />
-          </el-form-item>
-          <el-form-item label="点击事件" v-if="diyStore.editComponent.btnStyle.control">
-            <el-radio-group v-model="diyStore.editComponent.btnStyle.cartEvent">
-              <el-radio value="detail">商品详情</el-radio>
-              <el-radio v-if="diyStore.editComponent.style != 'style-3'" value="cart">加入购物车</el-radio>
-            </el-radio-group>
-          </el-form-item>
-          <el-form-item label="样式" class="!items-center" v-if="diyStore.editComponent.btnStyle.control">
-            <div class="flex">
-              <template v-for="(item, index) in btnStyleList" :key="index">
-                <div
-                  v-if="item.isShow == true"
-                  class="cursor-pointer flex items-center justify-center border-[1px] border-solid border-transparent rounded-[6px] py-[5px] px-[8px] mr-[10px]"
-                  :class="{ '!border-[var(--el-color-primary)]': diyStore.editComponent.btnStyle.style == item.value }"
-                >
-                  <div
-                    v-if="item.type == 'icon'"
-                    :class="['nc-iconfont !text-[25px] text-[var(--el-color-primary)]', item.title]"
-                    @click="changeBtnStyle(item)"
-                  ></div>
-                  <div
-                    v-if="item.type == 'button'"
-                    class="leading-[1] text-[12px] px-[10px] py-[8px] text-[#fff] rounded-[20px] bg-[var(--el-color-primary)]"
-                    @click="changeBtnStyle(item)"
-                  >
-                    {{ item.title }}
-                  </div>
-                </div>
-              </template>
-            </div>
-          </el-form-item>
-          <el-form-item label="文本" v-if="diyStore.editComponent.btnStyle.control && diyStore.editComponent.btnStyle.style == 'button'">
-            <el-input v-model.trim="diyStore.editComponent.btnStyle.text" placeholder="请输入按钮文字" clearable maxlength="4" show-word-limit />
-          </el-form-item>
-        </el-form>
-      </div>
-
-      <div class="edit-attr-item-wrap">
-        <h3 class="mb-[10px]">显示内容</h3>
-        <el-form label-width="90px" class="px-[10px]">
-          <el-form-item label="商品名称" v-if="diyStore.editComponent.goodsNameStyle.isShow">
-            <el-switch v-model="diyStore.editComponent.goodsNameStyle.control" />
-          </el-form-item>
-          <el-form-item label="销售价" v-if="diyStore.editComponent.priceStyle.isShow">
-            <el-switch v-model="diyStore.editComponent.priceStyle.control" />
-          </el-form-item>
-          <el-form-item label="商品销量" v-if="diyStore.editComponent.saleStyle.isShow">
-            <el-switch v-model="diyStore.editComponent.saleStyle.control" />
-          </el-form-item>
-          <el-form-item label="商品标签" v-if="diyStore.editComponent.labelStyle.isShow">
-            <el-switch v-model="diyStore.editComponent.labelStyle.control" />
-          </el-form-item>
-        </el-form>
-      </div>
-    </div>
-
-    <!-- 样式 -->
-    <div class="style-wrap" v-show="diyStore.editTab == 'style'">
-      <div class="edit-attr-item-wrap">
-        <h3 class="mb-[10px]">商品样式</h3>
-        <el-form label-width="80px" class="px-[10px]">
-          <el-form-item label="商品背景">
-            <el-color-picker v-model="diyStore.editComponent.elementBgColor" show-alpha :predefine="diyStore.predefineColors" />
-          </el-form-item>
-          <el-form-item label="商品名称">
-            <el-color-picker v-model="diyStore.editComponent.goodsNameStyle.color" show-alpha :predefine="diyStore.predefineColors" />
-            <div class="mr-[20px]"></div>
-            <el-radio-group v-model="diyStore.editComponent.goodsNameStyle.fontWeight">
-              <el-radio :value="'normal'">常规</el-radio>
-              <el-radio :value="'bold'">加粗</el-radio>
-            </el-radio-group>
-          </el-form-item>
-          <el-form-item label="图片圆角">
-            <el-slider v-model="diyStore.editComponent.imgElementRounded" show-input size="small" class="ml-[10px] diy-nav-slider" :max="50" />
-          </el-form-item>
-          <el-form-item label="销售价">
-            <el-color-picker v-model="diyStore.editComponent.priceStyle.color" show-alpha :predefine="diyStore.predefineColors" />
-          </el-form-item>
-          <el-form-item label="商品销量">
-            <el-color-picker v-model="diyStore.editComponent.saleStyle.color" show-alpha :predefine="diyStore.predefineColors" />
-          </el-form-item>
-          <el-form-item label="上圆角">
-            <el-slider v-model="diyStore.editComponent.topElementRounded" show-input size="small" class="ml-[10px] diy-nav-slider" :max="50" />
-          </el-form-item>
-          <el-form-item label="下圆角">
-            <el-slider v-model="diyStore.editComponent.bottomElementRounded" show-input size="small" class="ml-[10px] diy-nav-slider" :max="50" />
-          </el-form-item>
-        </el-form>
-      </div>
-
-      <div class="edit-attr-item-wrap" v-if="diyStore.editComponent.btnStyle.control">
-        <h3 class="mb-[10px]">购买按钮</h3>
-        <el-form label-width="80px" class="px-[10px]">
-          <el-form-item label="是否加粗" v-if="diyStore.editComponent.btnStyle.style == 'button'">
-            <el-switch v-model="diyStore.editComponent.btnStyle.fontWeight" />
-          </el-form-item>
-          <el-form-item label="文字颜色">
-            <el-color-picker v-model="diyStore.editComponent.btnStyle.textColor" show-alpha :predefine="diyStore.predefineColors" />
-          </el-form-item>
-          <el-form-item label="背景颜色">
-            <el-color-picker v-model="diyStore.editComponent.btnStyle.startBgColor" show-alpha :predefine="diyStore.predefineColors" />
-            <icon name="iconfont iconmap-connect" size="20px" class="block !text-gray-400 mx-[5px]" />
-            <el-color-picker v-model="diyStore.editComponent.btnStyle.endBgColor" show-alpha :predefine="diyStore.predefineColors" />
-          </el-form-item>
-          <el-form-item label="圆角" v-if="diyStore.editComponent.btnStyle.style == 'button'">
-            <el-slider v-model="diyStore.editComponent.btnStyle.aroundRadius" show-input size="small" class="ml-[10px] diy-nav-slider" :max="50" />
-          </el-form-item>
-        </el-form>
-      </div>
-
-      <!-- 组件样式 -->
-      <slot name="style"></slot>
-    </div>
-
-    <goods-dialog ref="goodsDialogRef" :categoryOptions="categoryOptions"></goods-dialog>
-  </div>
-</template>
-
-<script lang="ts" setup>
-// import { getCategoryTree } from '@/addon/shop/api/goods';
-import { img } from '@/utils/common';
-import useDiyStore from '@/store/modules/diy';
-import { ElTable } from 'element-plus';
-// import goodsSelectPopup from '@/addon/shop/views/goods/components/goods-select-popup.vue';
-const categoryOptions = ref<any>([]);
-const goodsDialogRef = ref<any>(null);
-
-const diyStore: any = useDiyStore();
-diyStore.editComponent.ignore = []; // 忽略公共属性
-
-// 组件验证
-diyStore.editComponent.verify = (index: number) => {
-  const res = { code: true, message: '' };
-
-  if (diyStore.value[index].source == 'category') {
-    if (diyStore.value[index].goods_category == '') {
-      res.code = false;
-      res.message = '请选择商品分类';
-    }
-  } else if (diyStore.value[index].source == 'custom') {
-    if (diyStore.value[index].goods_ids.length == 0) {
-      res.code = false;
-      res.message = '请选择商品';
-    }
-  }
-  return res;
-};
-
-const categoryShowDialog = ref(false);
-
-const categoryTable = reactive({
-  loading: true,
-  data: []
-});
-onMounted(() => {
-  loadCategoryList();
-  btnStyleList.forEach((item, index, arr) => {
-    if (item.type == 'button') {
-      if (diyStore.editComponent.style == 'style-3') {
-        item.isShow = false;
-      } else {
-        item.isShow = true;
-      }
-    }
-  });
-});
-
-const styleChangeFn = (style) => {
-  btnStyleList.forEach((item, index, arr) => {
-    if (item.type == 'button') {
-      if (style == 'style-3') {
-        item.isShow = false;
-      } else {
-        item.isShow = true;
-      }
-    }
-  });
-
-  if (style == 'style-3') {
-    diyStore.editComponent.btnStyle.style = btnStyleList[1].value;
-    diyStore.editComponent.btnStyle.cartEvent = 'detail';
-
-    diyStore.editComponent.saleStyle.isShow = false;
-    diyStore.editComponent.labelStyle.isShow = false;
-  } else {
-    diyStore.editComponent.btnStyle.style = btnStyleList[0].value;
-
-    diyStore.editComponent.saleStyle.isShow = true;
-    diyStore.editComponent.labelStyle.isShow = true;
-  }
-  diyStore.editComponent.style = style;
-};
-
-const btnStyleList = reactive([
-  {
-    isShow: true,
-    type: 'button',
-    title: diyStore.editComponent.btnStyle.text,
-    value: 'button'
-  },
-  {
-    isShow: true,
-    type: 'icon',
-    title: 'nc-icon-jiahaoV6xx',
-    value: 'nc-icon-jiahaoV6xx'
-  },
-  {
-    isShow: true,
-    type: 'icon',
-    title: 'nc-icon-gouwuche1',
-    value: 'nc-icon-gouwuche1'
-  }
-]);
-
-const changeBtnStyle = (item: any) => {
-  diyStore.editComponent.btnStyle.style = item.value;
-};
-
-const categoryTableRef = ref<InstanceType<typeof ElTable>>();
-/**
- * 获取商品分类列表
- */
-let currCategoryData: any = null;
-const loadCategoryList = () => {
-  categoryTable.loading = true;
-
-  // getCategoryTree()
-  //   .then((res) => {
-  //     categoryTable.loading = false;
-  //     categoryTable.data = res.data;
-  //   })
-  //   .catch(() => {
-  //     categoryTable.loading = false;
-  //   });
-};
-
-// 选择商品分类
-const handleSelectionChange = (val: string | any[]) => {
-  let data = '';
-  if (val) data = val[val.length - 1];
-  if (val.length > 1) categoryTableRef.value!.clearSelection();
-  if (data) categoryTableRef.value!.toggleRowSelection(data, true);
-  currCategoryData = data;
-};
-
-const saveCategoryId = () => {
-  diyStore.editComponent.goods_category = currCategoryData.category_id;
-  diyStore.editComponent.goods_category_name = currCategoryData.category_name;
-  categoryShowDialog.value = false;
-};
-
-const categoryShowDialogOpen = () => {
-  categoryShowDialog.value = true;
-  nextTick(() => {
-    setRowSelection();
-  });
-};
-
-// 分类数据选中回填,设置展开行
-const expand_category_ids = ref<Array<any>>([]);
-const setRowSelection = () => {
-  expand_category_ids.value = [];
-  categoryTable.data.forEach((el: any) => {
-    if (diyStore.editComponent.goods_category == el.category_id) {
-      categoryTableRef.value!.toggleRowSelection(el, true);
-    } else if (el.child_list && el.child_list.length) {
-      el.child_list.forEach((v: any) => {
-        if (diyStore.editComponent.goods_category == v.category_id) {
-          expand_category_ids.value.push(el.category_id.toString());
-          categoryTableRef.value!.toggleRowSelection(v, true);
-        }
-      });
-    }
-  });
-};
-
-//打开弹窗
-const openDialog = () => {
-  goodsDialogRef.value.onOpen();
-};
-
-defineExpose({});
-</script>
-<style lang="scss">
-.goods-list-slider {
-  .el-slider__input {
-    width: 100px;
-  }
-}
-</style>

+ 7 - 0
src/views/diy/components/edit-goods-list.vue

@@ -227,6 +227,13 @@ const getCategoryTree = async () => {
   const list = res.data || [];
   categoryOptions.value = [...list];
   categoryOptions1.value = [...list];
+  categoryOptions1.value.forEach((item1: any) => {
+    if (item1.children && item1.children.length > 0) {
+      item1.children.forEach((item2: any) => {
+        item2.children = [];
+      });
+    }
+  });
   categoryOptions.value.unshift({
     id: '',
     label: '全部'

+ 2 - 2
src/views/diy/components/edit-graphic-nav.vue

@@ -74,7 +74,7 @@
               </div>
 
               <el-form-item label="链接地址">
-                <WebLinkInput v-model="item.link" placeholder="请输入或选择链接" />
+                <WebLinkInput :pageType="'2'" v-model="item.link" placeholder="请输入或选择链接" />
               </el-form-item>
             </div>
           </div>
@@ -200,7 +200,7 @@ const addGraphicNav = () => {
     imageUrl: '',
     imgWidth: 0,
     imgHeight: 0,
-    link: { name: '' },
+    link: '',
     label: {
       control: false,
       text: '热门',

+ 1 - 1
src/views/diy/components/edit-horz-blank.vue

@@ -1,6 +1,6 @@
 <template>
   <div>
-    <!-- 内容 -->
+    <!-- 内容  -->
     <div class="content-wrap" v-show="diyStore.editTab == 'content'">
       <div class="edit-attr-item-wrap">
         <h3 class="mb-[10px]">高度设置</h3>

+ 1 - 1
src/views/diy/components/edit-horz-line.vue

@@ -1,6 +1,6 @@
 <template>
   <div>
-    <!-- 内容 -->
+    <!-- 内容  -->
     <div class="content-wrap" v-show="diyStore.editTab == 'content'">
       <div class="edit-attr-item-wrap">
         <h3 class="mb-[10px]">线条风格</h3>

+ 1 - 1
src/views/diy/components/edit-hot-area.vue

@@ -12,7 +12,7 @@
               </el-form-item>
 
               <el-form-item label="热区设置">
-                <heat-map v-model="diyStore.editComponent" />
+                <heat-mapMini v-model="diyStore.editComponent" />
               </el-form-item>
             </div>
           </div>

+ 2 - 2
src/views/diy/components/edit-image-ads.vue

@@ -38,7 +38,7 @@
               </div>
 
               <el-form-item label="链接地址">
-                <diy-link v-model="item.link" />
+                <WebLinkInput :pageType="'2'" v-model="item.link" placeholder="请输入或选择链接" />
               </el-form-item>
             </div>
           </div>
@@ -108,7 +108,7 @@ const addImageAd = () => {
     imageUrl: '',
     imgWidth: 0,
     imgHeight: 0,
-    link: { name: '' }
+    link: ''
   });
 };
 

+ 52 - 501
src/views/diy/components/edit-many-goods-list.vue

@@ -53,26 +53,28 @@
                 </el-form-item>
                 <el-form-item label="选择商品">
                   <el-radio-group v-model="item.source">
-                    <el-radio value="custom">手动选择</el-radio>
-                    <el-radio value="category">选择分类</el-radio>
-                    <el-radio value="brand">选择品牌</el-radio>
+                    <el-radio :value="1">指定商品</el-radio>
+                    <el-radio :value="2">商品分类</el-radio>
                   </el-radio-group>
                 </el-form-item>
-                <el-form-item label="选择分类" v-if="item.source == 'category'">
-                  <div class="flex items-center w-full">
-                    <div class="cursor-pointer ml-auto" @click="categoryShowDialogOpen(index)">
-                      <span class="text-[var(--el-color-primary)]">{{ item.goods_category_name }}</span>
-                      <span class="iconfont iconxiangyoujiantou"></span>
-                    </div>
-                  </div>
-                </el-form-item>
-                <el-form-item label="手动选择" v-if="item.source == 'custom'">
+                <el-form-item label="指定商品" v-if="item.source == 1">
                   <div class="data-num" @click="openDialog(index)">
-                    <span v-if="item.goods_ids.length == 0">请选择</span>
-                    <span v-else>已选择{{ item.goods_ids.length }}个</span>
-                    <el-icon><ArrowRight /></el-icon>
+                    <span v-if="item.goodsIds.length == 0">请选择</span>
+                    <span v-else>已选择{{ item.goodsIds.length }}个</span>
+                    <el-icon>
+                      <ArrowRight />
+                    </el-icon>
                   </div>
-                  <!-- <goods-select-popup ref="goodsSelectPopupRef" v-model="item.goods_ids" :min="1" :max="99" /> -->
+                </el-form-item>
+                <el-form-item label="选择分类" v-if="item.source == 2">
+                  <el-cascader
+                    v-model="item.categoryIds"
+                    :options="categoryOptions1"
+                    :props="cascaderProps"
+                    placeholder="全部分类"
+                    clearable
+                    collapse-tags
+                  />
                 </el-form-item>
                 <el-form-item label="图片上传" v-show="diyStore.editComponent.headStyle == 'style-3'">
                   <upload-image v-model="item.imageUrl" :limit="1" />
@@ -91,208 +93,8 @@
             <el-button class="w-full" @click="addItem">添加一个多商品组</el-button>
           </div>
         </el-form>
-
-        <!-- 选择一级商品分类弹出框 -->
-        <el-dialog v-model="firstCategoryShowDialog" title="商品分类" width="750px" :destroy-on-close="true" :close-on-click-modal="false">
-          <el-table
-            :data="firstCategoryTable.data"
-            ref="firstCategoryTableRef"
-            size="large"
-            v-loading="firstCategoryTable.loading"
-            height="450px"
-            @current-change="handleCurrentCategoryChange"
-            row-key="category_id"
-            highlight-current-row
-          >
-            <template #empty>
-              <span>{{ !firstCategoryTable.loading ? '还没有安装应用' : '' }}</span>
-            </template>
-            <el-table-column label="分类名称" min-width="120">
-              <template #default="{ row }">
-                <span class="order-2">{{ row.category_name }}</span>
-              </template>
-            </el-table-column>
-            <el-table-column label="分类图片" width="170" align="left">
-              <template #default="{ row }">
-                <div class="h-[30px]">
-                  <el-image class="w-[30px] h-[30px]" :src="img(row.image)" fit="contain">
-                    <template #error>
-                      <div class="image-slot">
-                        <img class="w-[30px] h-[30px]" src="@/assets/images/diy/shop/category_default.png" />
-                      </div>
-                    </template>
-                  </el-image>
-                </div>
-              </template>
-            </el-table-column>
-          </el-table>
-          <div class="flex items-center justify-end mt-[15px]">
-            <el-button type="primary" @click="saveFirstCategoryId">确认</el-button>
-            <el-button @click="firstCategoryShowDialog = false">取消</el-button>
-          </div>
-        </el-dialog>
-
-        <el-dialog v-model="categoryShowDialog" title="商品分类" width="750px" :destroy-on-close="true" :close-on-click-modal="false">
-          <div class="table w-[100%] mt-[15px]" v-loading="categoryTable.loading">
-            <div class="table-head flex items-center bg-[#f5f7f9] py-[10px] text-[14px]" :style="{ paddingRight: scrollBarWidth + 'px' }">
-              <div class="w-[6%]"></div>
-              <div class="w-[10%]">
-                <!-- <el-checkbox v-model="staircheckAll" :indeterminate="isStairIndeterminate" @change="handleCheckAllChange" /> -->
-              </div>
-              <div class="w-[50%]">分类名称</div>
-              <div class="w-[34%] h-[30px] leading-[30px]">分类图片</div>
-            </div>
-            <div class="table-body max-h-[500px] overflow-y-auto" ref="tableBodyRef">
-              <!-- 遍历一级分类 -->
-              <div v-for="(row, rowIndex) in categoryTable.data" :key="rowIndex" class="flex flex-col">
-                <div class="flex items-center border-solid border-[#e5e7eb] py-[10px] border-b-[1px]">
-                  <!-- 图标:展开/收起子级 -->
-                  <div
-                    v-if="row.child_list && row.child_list.length"
-                    class="w-[6%] cursor-pointer text-center !text-[10px]"
-                    @click="secondLevelArrowChange(row)"
-                    :class="{ 'iconfont iconxiangyoujiantou': row.child_list.length, 'arrow-show': row.isShow }"
-                  ></div>
-                  <div v-else class="w-[6%]"></div>
-                  <!-- 一级分类复选框 -->
-                  <div class="w-[10%]">
-                    <el-checkbox
-                      v-model="row.secondLevelCheckAll"
-                      :indeterminate="row.isSecondLevelIndeterminate"
-                      @change="handleCheckboxChange($event, row)"
-                    />
-                  </div>
-                  <!-- 一级分类名称 -->
-                  <div class="ml-2 flex flex-col items-start w-[50%]">
-                    <span :title="row.category_name" class="multi-hidden leading-[1.4] mr-5 text-[14px] text-[#666]">
-                      {{ row.category_name }}
-                    </span>
-                  </div>
-                  <!-- 一级分类图片 -->
-                  <div class="flex items-center cursor-pointer w-[34%]">
-                    <div class="min-w-[30px] h-[30px] flex items-center justify-center">
-                      <el-image v-if="row.img" class="w-[30px] h-[30px]" :src="img(row.img)" fit="contain">
-                        <template #error>
-                          <div class="image-slot">
-                            <img class="w-[30px] h-[30px]" src="@/assets/images/diy/shop/category_default.png" />
-                          </div>
-                        </template>
-                      </el-image>
-                      <img v-else class="w-[30px] h-[30px]" src="@/assets/images/diy/shop/category_default.png" fit="contain" />
-                    </div>
-                  </div>
-                </div>
-                <!-- 子级分类 -->
-                <div v-show="row.child_list && row.isShow">
-                  <div
-                    v-for="(item, index) in row.child_list"
-                    :key="index"
-                    class="flex items-center py-[10px] border-solid border-b-[1px]"
-                    :class="{ 'hidden': !row.isShow, 'border-[#e5e7eb]': index == row.child_list.length - 1 }"
-                  >
-                    <div class="w-[9%]"></div>
-                    <!-- 子级分类复选框 -->
-                    <div class="w-[7%]">
-                      <el-checkbox v-model="item.threeLevelCheckAll" @change="handleCheckboxChange($event, item, row)" />
-                    </div>
-                    <!-- 子级分类名称 -->
-                    <div class="ml-2 flex flex-col items-start w-[50%]">
-                      <span :title="item.category_name" class="multi-hidden leading-[1.4] mr-5 text-[14px] text-[#666]">
-                        {{ item.category_name }}
-                      </span>
-                    </div>
-                    <!-- 子级分类图片 -->
-                    <div class="flex items-center cursor-pointer w-[34%]">
-                      <div class="min-w-[30px] h-[30px] flex items-center justify-center">
-                        <el-image v-if="row.img" class="w-[30px] h-[30px]" :src="img(row.img)" fit="contain">
-                          <template #error>
-                            <div class="image-slot">
-                              <img class="w-[30px] h-[30px]" src="@/assets/images/diy/shop/category_default.png" />
-                            </div>
-                          </template>
-                        </el-image>
-                        <img v-else class="w-[30px] h-[30px]" src="@/assets/images/diy/shop/category_default.png" fit="contain" />
-                      </div>
-                    </div>
-                  </div>
-                </div>
-              </div>
-              <div
-                v-if="!categoryTable.data.length && !categoryTable.loading"
-                class="h-[60px] flex items-center justify-center border-solid border-[#e5e7eb] py-[12px] border-b-[1px]"
-              >
-                还没有安装应用
-              </div>
-            </div>
-          </div>
-          <div class="flex items-center justify-end mt-[15px]">
-            <el-button type="primary" @click="saveCategoryId">确认</el-button>
-            <el-button @click="categoryShowDialog = false">取消</el-button>
-          </div>
-        </el-dialog>
-      </div>
-
-      <div class="edit-attr-item-wrap mt-[20px]">
-        <h3 class="mb-[10px]">购买按钮</h3>
-        <el-form label-width="90px" class="px-[10px]">
-          <el-form-item label="是否显示">
-            <el-switch v-model="diyStore.editComponent.btnStyle.control" />
-          </el-form-item>
-          <el-form-item label="点击事件" v-if="diyStore.editComponent.btnStyle.control">
-            <el-radio-group v-model="diyStore.editComponent.btnStyle.cartEvent">
-              <el-radio value="detail">商品详情</el-radio>
-              <el-radio value="cart">加入购物车</el-radio>
-            </el-radio-group>
-          </el-form-item>
-          <el-form-item label="样式" class="!items-center" v-if="diyStore.editComponent.btnStyle.control">
-            <div class="flex">
-              <template v-for="(item, index) in btnStyleList" :key="index">
-                <div
-                  v-if="item.isShow == true"
-                  class="cursor-pointer flex items-center justify-center border-[1px] border-solid border-transparent rounded-[6px] py-[5px] px-[8px] mr-[10px]"
-                  :class="{ '!border-[var(--el-color-primary)]': diyStore.editComponent.btnStyle.style == item.value }"
-                >
-                  <div
-                    v-if="item.type == 'icon'"
-                    :class="['nc-iconfont !text-[25px] text-[var(--el-color-primary)]', item.title]"
-                    @click="changeBtnStyle(item)"
-                  ></div>
-                  <div
-                    v-if="item.type == 'button'"
-                    class="leading-[1] text-[12px] px-[10px] py-[8px] text-[#fff] rounded-[20px] bg-[var(--el-color-primary)]"
-                    @click="changeBtnStyle(item)"
-                  >
-                    {{ item.title }}
-                  </div>
-                </div>
-              </template>
-            </div>
-          </el-form-item>
-          <el-form-item label="文本" v-if="diyStore.editComponent.btnStyle.control && diyStore.editComponent.btnStyle.style == 'button'">
-            <el-input v-model.trim="diyStore.editComponent.btnStyle.text" placeholder="请输入按钮文字" clearable maxlength="4" show-word-limit />
-          </el-form-item>
-        </el-form>
-      </div>
-
-      <div class="edit-attr-item-wrap">
-        <h3 class="mb-[10px]">显示内容</h3>
-        <el-form label-width="90px" class="px-[10px]">
-          <el-form-item label="商品名称" v-if="diyStore.editComponent.goodsNameStyle.isShow">
-            <el-switch v-model="diyStore.editComponent.goodsNameStyle.control" />
-          </el-form-item>
-          <el-form-item label="销售价" v-if="diyStore.editComponent.priceStyle.isShow">
-            <el-switch v-model="diyStore.editComponent.priceStyle.control" />
-          </el-form-item>
-          <el-form-item label="商品销量" v-if="diyStore.editComponent.saleStyle.isShow">
-            <el-switch v-model="diyStore.editComponent.saleStyle.control" />
-          </el-form-item>
-          <el-form-item label="商品标签" v-if="diyStore.editComponent.labelStyle.isShow">
-            <el-switch v-model="diyStore.editComponent.labelStyle.control" />
-          </el-form-item>
-        </el-form>
       </div>
     </div>
-
     <!-- 样式 -->
     <div class="style-wrap" v-show="diyStore.editTab == 'style'">
       <div class="edit-attr-item-wrap">
@@ -359,165 +161,15 @@ import { categoryTree } from '@/api/pmsProduct/base';
 // import goodsSelectPopup from '@/addon/shop/views/goods/components/goods-select-popup.vue';
 const goodsDialogRef = ref<any>(null);
 const categoryOptions = ref<any>([]);
+const categoryOptions1 = ref<any>([]);
 const navIndex = ref<any>(0);
-
+const goodsBoxRef = ref();
 const diyStore: any = useDiyStore();
 diyStore.editComponent.ignore = ['componentBgUrl']; // 忽略公共属性
-
-// 组件验证
-diyStore.editComponent.verify = (index: number) => {
-  const res = { code: true, message: '' };
-
-  if (diyStore.value[index].source == 'custom') {
-    diyStore.value[index].list.forEach((item: any) => {
-      if (item.source === 'category') {
-        if (item.goods_category == '') {
-          res.code = false;
-          res.message = '请选择商品分类';
-          return res;
-        }
-      } else if (item.source == 'custom') {
-        if (item.goods_ids.length == 0) {
-          res.code = false;
-          res.message = '请选择商品';
-        }
-      }
-    });
-  } else if (diyStore.value[index].source == 'goods_category') {
-    if (diyStore.value[index].goods_category == '') {
-      res.code = false;
-      res.message = '请选择商品分类';
-      return res;
-    }
-  }
-
-  return res;
-};
-
-diyStore.editComponent.list.forEach((item: any) => {
-  if (!item.id) item.id = diyStore.generateRandom();
-});
-
-//打开弹窗
-const openDialog = (res: any) => {
-  navIndex.value = res;
-  goodsDialogRef.value.onOpen();
-};
-
-/** 查询分类树 */
-const getCategoryTree = async () => {
-  categoryOptions.value = [];
-  const res = await categoryTree({
-    platform: diyStore.type == 1 ? 0 : diyStore.type == 2 ? 1 : diyStore.type == 3 ? 2 : diyStore.type == 4 ? 4 : 0,
-    pageNum: 1,
-    pageSize: 9999
-  });
-  const list = res.data || [];
-  categoryOptions.value = [...list];
-  categoryOptions.value.unshift({
-    id: '',
-    label: '全部'
-  });
-};
-
-// 一级商品分类
-const firstCategoryShowDialog = ref(false);
-
-const firstCategoryTable = reactive({
-  loading: true,
-  data: [],
-  searchParam: {
-    level: 1
-  }
-});
-
-const firstCategoryTableRef = ref<InstanceType<typeof ElTable>>();
-
-/**
- * 获取商品分类列表
- */
-const loadCategoryList = () => {
-  firstCategoryTable.loading = true;
-
-  // getCategoryList({
-  //   ...firstCategoryTable.searchParam
-  // })
-  //   .then((res) => {
-  //     firstCategoryTable.loading = false;
-  //     firstCategoryTable.data = res.data;
-  //   })
-  //   .catch(() => {
-  //     firstCategoryTable.loading = false;
-  //   });
-};
-
-const saveFirstCategoryId = () => {
-  diyStore.editComponent.goods_category = currFirstCategory.category_id;
-  diyStore.editComponent.goods_category_name = currFirstCategory.category_name;
-  firstCategoryShowDialog.value = false;
-};
-
-const firstCategoryShowDialogOpen = () => {
-  firstCategoryShowDialog.value = true;
-  if (currFirstCategory) {
-    setTimeout(() => {
-      firstCategoryTableRef.value!.setCurrentRow(currFirstCategory);
-    }, 200);
-  }
-};
-
-const btnStyleList = reactive([
-  {
-    isShow: true,
-    type: 'button',
-    title: diyStore.editComponent.btnStyle.text,
-    value: 'button'
-  },
-  {
-    isShow: true,
-    type: 'icon',
-    title: 'nc-icon-jiahaoV6xx',
-    value: 'nc-icon-jiahaoV6xx'
-  },
-  {
-    isShow: true,
-    type: 'icon',
-    title: 'nc-icon-gouwuche1',
-    value: 'nc-icon-gouwuche1'
-  }
-]);
-diyStore.editComponent.btnStyle.style = 'nc-icon-jiahaoV6xx';
-
-const changeBtnStyle = (item: any) => {
-  diyStore.editComponent.btnStyle.style = item.value;
-};
-
-const clearCategory = () => {
-  diyStore.editComponent.goods_category = '';
-  diyStore.editComponent.goods_category_name = '';
-};
-
-// 选择商品分类
-let currFirstCategory: any = {};
-const handleCurrentCategoryChange = (val: string | any[]) => {
-  currFirstCategory = val;
-};
-
-// 商品分类树结构
-const categoryShowDialog = ref(false);
-
-const goodsBoxRef = ref();
-
-const categoryTable = reactive({
-  loading: true,
-  data: []
-});
+const cascaderProps = { multiple: true, value: 'id', label: 'label', children: 'children' };
 
 onMounted(() => {
   getCategoryTree();
-  loadCategoryTree();
-
-  loadCategoryList();
 
   nextTick(() => {
     const sortable = Sortable.create(goodsBoxRef.value, {
@@ -538,34 +190,14 @@ onMounted(() => {
   window.addEventListener('resize', getScrollBarWidth);
 });
 
-/**
- * 获取商品分类列表
- */
-// let currCategoryData: any = null
-const loadCategoryTree = () => {
-  categoryTable.loading = true;
-
-  // getCategoryTree()
-  //   .then((res) => {
-  //     categoryTable.loading = false;
-  //     categoryTable.data = res.data;
-  //     categoryTable.data.forEach((item: any) => {
-  //       // 初始化一级分类的字段
-  //       item.isShow = false; // 控制子级是否展开
-  //       item.isSecondLevelIndeterminate = false; // 一级分类不确定状态
-  //       item.secondLevelCheckAll = false; // 一级分类复选框状态
-
-  //       // 如果有子分类(child_list),初始化子分类的字段
-  //       if (item.child_list && item.child_list.length) {
-  //         item.child_list.forEach((childItem: any) => {
-  //           childItem.threeLevelCheckAll = false; // 子分类复选框状态
-  //         });
-  //       }
-  //     });
-  //   })
-  //   .catch(() => {
-  //     categoryTable.loading = false;
-  //   });
+const scrollBarWidth = ref(0);
+const tableBodyRef = ref(null);
+const getScrollBarWidth = () => {
+  nextTick(() => {
+    if (tableBodyRef.value) {
+      scrollBarWidth.value = tableBodyRef.value.offsetWidth - tableBodyRef.value.clientWidth;
+    }
+  });
 };
 
 const addItem = () => {
@@ -581,118 +213,36 @@ const addItem = () => {
   });
 };
 
-const scrollBarWidth = ref(0);
-const tableBodyRef = ref(null);
-
-const getScrollBarWidth = () => {
-  nextTick(() => {
-    if (tableBodyRef.value) {
-      scrollBarWidth.value = tableBodyRef.value.offsetWidth - tableBodyRef.value.clientWidth;
-    }
+/** 查询分类树 */
+const getCategoryTree = async () => {
+  categoryOptions.value = [];
+  categoryOptions1.value = [];
+  const res = await categoryTree({
+    platform: diyStore.type == 1 ? 0 : diyStore.type == 2 ? 1 : diyStore.type == 3 ? 2 : diyStore.type == 4 ? 4 : 0,
+    pageNum: 1,
+    pageSize: 9999
   });
-};
-
-// 选择商品分类
-let selectIndex = 0; // 当前选择的下标
-const selectedCategories = ref({});
-// 方法:切换子级展开/收起
-const secondLevelArrowChange = (row: any) => {
-  row.isShow = !row.isShow;
-  nextTick(() => getScrollBarWidth());
-};
-
-const saveCategoryId = () => {
-  const selected = selectedCategories.value[selectIndex];
-  if (!selected || !selected.category_id) {
-    // 确保 `category_id` 存在
-    ElMessage({
-      type: 'warning',
-      message: '请选择分类'
-    });
-    return;
-  }
-  diyStore.editComponent.list[selectIndex].goods_category = selectedCategories.value[selectIndex].category_id;
-  diyStore.editComponent.list[selectIndex].goods_category_name = selectedCategories.value[selectIndex].category_name;
-  categoryShowDialog.value = false;
-};
-
-const clearAllSelections = () => {
-  categoryTable.data.forEach((row: any) => {
-    row.secondLevelCheckAll = false;
-    if (row.child_list) {
-      row.child_list.forEach((child: any) => {
-        child.threeLevelCheckAll = false;
+  const list = res.data || [];
+  categoryOptions.value = [...list];
+  categoryOptions1.value = [...list];
+  categoryOptions1.value.forEach((item1: any) => {
+    if (item1.children && item1.children.length > 0) {
+      item1.children.forEach((item2: any) => {
+        item2.children = [];
       });
     }
   });
-};
-
-const categoryShowDialogOpen = (index: any) => {
-  selectIndex = index;
-  clearAllSelections();
-
-  // 设置 isShow 状态
-  categoryTable.data.forEach((row: any) => {
-    row.isShow = false; // 默认所有分类都是合住的
-  });
-  // 确保 `selectedCategories.value[selectIndex]` 存在
-  if (!selectedCategories.value[selectIndex]) {
-    selectedCategories.value[selectIndex] = {}; // 初始化为空对象
-  }
-  // 回显已选中的分类
-  nextTick(() => {
-    const selectedCategory = diyStore.editComponent.list[selectIndex];
-    // 初始化 selectedCategories.value[selectIndex]
-    if (!selectedCategories.value[selectIndex]) {
-      selectedCategories.value[selectIndex] = {}; // 初始化为空对象
-      selectedCategories.value[selectIndex].category_id = selectedCategory.goods_category;
-      selectedCategories.value[selectIndex].category_name = selectedCategory.goods_category_name;
-    }
-
-    if (selectedCategory) {
-      categoryTable.data.forEach((row: any) => {
-        if (row.category_id === selectedCategory.goods_category) {
-          row.secondLevelCheckAll = true;
-          row.isShow = true; // 展开选中的一级分类
-        }
-        if (row.child_list) {
-          row.child_list.forEach((child: any) => {
-            if (child.category_id === selectedCategory.goods_category) {
-              child.threeLevelCheckAll = true;
-              row.isShow = true;
-            }
-          });
-        }
-      });
-    }
+  categoryOptions.value.unshift({
+    id: '',
+    label: '全部'
   });
-  nextTick(() => getScrollBarWidth());
-  categoryShowDialog.value = true;
 };
 
-// 处理复选框变化
-const handleCheckboxChange = (checked: any, target: any, parentRow?: any) => {
-  clearAllSelections(); // 清空所有复选框的选中状态
-  if (checked) {
-    // 设置当前选中的分类
-    if (parentRow) {
-      // 如果是子分类
-      target.threeLevelCheckAll = checked;
-      selectedCategories.value[selectIndex] = target;
-      parentRow.isShow = true; // 展开父级分类
-    } else {
-      // 如果是一级分类
-      target.secondLevelCheckAll = checked;
-      selectedCategories.value[selectIndex] = target;
-      target.isShow = true; // 展开选中的一级分类
-    }
-  } else {
-    // 取消勾选时,清空选中的分类 ID
-    delete selectedCategories.value[selectIndex];
-  }
+//打开弹窗
+const openDialog = (res: any) => {
+  navIndex.value = res;
+  goodsDialogRef.value.onOpen();
 };
-
-defineExpose({});
 </script>
 
 <style lang="scss" scoped>
@@ -700,6 +250,7 @@ defineExpose({});
   transform: rotate(90deg) !important;
   /* 提高优先级 */
 }
+
 .data-num {
   width: 100%;
   font-size: 14px;

+ 2 - 2
src/views/diy/components/edit-notice.vue

@@ -86,7 +86,7 @@
               </div>
 
               <el-form-item label="链接地址" v-if="diyStore.editComponent.showType == 'link'">
-                <diy-link v-model="item.link" />
+                <WebLinkInput :pageType="'2'" v-model="item.link" placeholder="请输入或选择链接" />
               </el-form-item>
             </div>
           </div>
@@ -175,7 +175,7 @@ const addNotice = () => {
   diyStore.editComponent.list.push({
     id: diyStore.generateRandom(),
     text: '公告',
-    link: { name: '' }
+    link: ''
   });
 };
 

+ 2 - 2
src/views/diy/components/edit-picture-show.vue

@@ -53,7 +53,7 @@
             </el-form-item>
 
             <el-form-item label="链接地址">
-              <diy-link v-model="item.link" />
+              <WebLinkInput :pageType="'2'" v-model="item.link" placeholder="请输入或选择链接" />
             </el-form-item>
           </div>
         </el-form>
@@ -109,7 +109,7 @@
             </el-form-item>
 
             <el-form-item label="链接地址">
-              <diy-link v-model="item.link" />
+              <WebLinkInput :pageType="'2'" v-model="item.link" placeholder="请输入或选择链接" />
             </el-form-item>
           </div>
         </el-form>

+ 3 - 17
src/views/diy/components/edit-rich-text.vue

@@ -4,7 +4,9 @@
     <div class="content-wrap" v-show="diyStore.editTab == 'content'">
       <div class="edit-attr-item-wrap">
         <h3 class="mb-[10px]">内容设置</h3>
-        <editor v-model="diyStore.editComponent.html" :height="600" class="editor-width" />
+        <div class="editor-width">
+          <editor v-model="diyStore.editComponent.html" :height="600" />
+        </div>
       </div>
     </div>
 
@@ -18,23 +20,7 @@
 
 <script lang="ts" setup>
 import useDiyStore from '@/store/modules/diy';
-
 const diyStore = useDiyStore();
-diyStore.editComponent.ignore = []; // 忽略公共属性
-
-// 组件验证
-diyStore.editComponent.verify = (index: number) => {
-  const res = { code: true, message: '' };
-
-  if (diyStore.value[index].html == '<p><br></p>') {
-    res.code = false;
-    res.message = '请输入富文本内容';
-    return res;
-  }
-  return res;
-};
-
-defineExpose({});
 </script>
 
 <style lang="scss" scoped></style>

+ 3 - 2
src/views/diy/components/edit-rubik-cube.vue

@@ -54,7 +54,7 @@
             </el-form-item>
 
             <el-form-item label="图片上传">
-              <diy-link v-model="item.link" />
+              <WebLinkInput :pageType="'2'" v-model="item.link" placeholder="请输入或选择链接" />
             </el-form-item>
           </div>
         </el-form>
@@ -307,7 +307,7 @@ const changeTemplateList = (v: number) => {
               imageUrl: '',
               imgWidth: 0,
               imgHeight: 0,
-              link: { name: '' }
+              link: ''
             });
           }
         }
@@ -378,6 +378,7 @@ defineExpose({});
     }
 
     &.selected {
+      position: static !important;
       color: var(--el-color-primary);
       border-color: var(--el-color-primary);
     }

+ 0 - 135
src/views/diy/components/edit-shop-exchange-goods.vue

@@ -1,135 +0,0 @@
-<template>
-  <div>
-    <!-- 内容 -->
-    <div class="content-wrap" v-show="diyStore.editTab == 'content'">
-      <!-- <div class="edit-attr-item-wrap">
-              <h3 class="mb-[10px]">{{ t('selectStyle') }}</h3>
-              <div class="flex items-center mb-[18px] rounded overflow-hidden">
-                <span
-                  class="iconfont icongudingzhanshi border-[1px] border-solid border-[#eee] cursor-pointer flex-1 flex items-center justify-center py-[5px]"
-                  :class="{ 'border-[var(--el-color-primary)] text-[var(--el-color-primary)]': diyStore.editComponent.style == 'style-1' }"
-                  @click="diyStore.editComponent.style = 'style-1'"></span>
-                <span
-                  class="iconfont icontuwendaohang3 border-[1px] border-solid border-[#eee] cursor-pointer flex-1 flex items-center justify-center py-[5px]"
-                  :class="{ 'border-[var(--el-color-primary)] text-[var(--el-color-primary)]': diyStore.editComponent.style == 'style-2' }"
-                  @click="diyStore.editComponent.style = 'style-2'"></span>
-                <span
-                  class="iconfont iconshangpinliebiaohengxianghuadong border-[1px] border-solid border-[#eee] cursor-pointer flex-1 flex items-center justify-center py-[5px]"
-                  :class="{ 'border-[var(--el-color-primary)] text-[var(--el-color-primary)]': diyStore.editComponent.style == 'style-3' }"
-                  @click="diyStore.editComponent.style = 'style-3'"></span>
-              </div>
-            </div> -->
-      <div class="edit-attr-item-wrap">
-        <h3 class="mb-[10px]">选择数据源</h3>
-        <el-form label-width="80px" class="px-[10px]">
-          <el-form-item label="排序">
-            <el-radio-group v-model="diyStore.editComponent.sortWay">
-              <el-radio label="total_order_num">综合综合</el-radio>
-              <el-radio label="total_exchange_num">销量</el-radio>
-              <el-radio label="price">价格</el-radio>
-            </el-radio-group>
-          </el-form-item>
-          <el-form-item label="选择商品">
-            <el-radio-group v-model="diyStore.editComponent.source" title="选择商品">
-              <el-radio label="all">全部商品</el-radio>
-              <el-radio label="custom">手动选择</el-radio>
-            </el-radio-group>
-          </el-form-item>
-          <!-- <el-form-item :label="t('selectCategory')" v-if="diyStore.editComponent.source == 'category'">
-                      <div class="flex items-center w-full">
-                        <div class="cursor-pointer ml-auto" @click="categoryShowDialogOpen">
-                          <span class="text-[var(--el-color-primary)]">{{ diyStore.editComponent.goods_category_name }}</span>
-                          <span class="iconfont iconxiangyoujiantou"></span>
-                        </div>
-                      </div>
-                    </el-form-item>
-                    <el-form-item :label="t('goodsNum')" v-if="diyStore.editComponent.source == 'all' || diyStore.editComponent.source == 'category'">
-                      <div class="flex items-center w-full ml-[5px]">
-                        <el-slider class="flex-1" v-model="diyStore.editComponent.num" :min="1" max="20" size="small" />
-                        <span class="ml-[15px]">{{ diyStore.editComponent.num }}</span>
-                      </div>
-                    </el-form-item> -->
-          <el-form-item label="手动选择" v-if="diyStore.editComponent.source == 'custom'">
-            <el-button type="primary" @click="goodsSelectPopupRef.show(diyStore.editComponent.goods_ids)"> 选择商品 </el-button>
-            <div class="inline-block ml-[10px] text-[14px]" v-show="diyStore.editComponent.goods_ids.length">
-              <span>已选</span>
-              <span class="text-primary mx-[2px]">{{ diyStore.editComponent.goods_ids.length }}</span>
-              <span>个</span>
-            </div>
-            <goods-select-popup ref="goodsSelectPopupRef" :min="1" @select="goodsSelect" />
-          </el-form-item>
-        </el-form>
-      </div>
-    </div>
-
-    <!-- 样式 -->
-    <div class="style-wrap" v-if="diyStore.editTab == 'style'">
-      <div class="edit-attr-item-wrap">
-        <h3 class="mb-[10px]">商品样式</h3>
-        <el-form label-width="80px" class="px-[10px]">
-          <el-form-item label="商品名称">
-            <el-color-picker v-model="diyStore.editComponent.goodsNameStyle.color" show-alpha :predefine="diyStore.predefineColors" />
-            <div class="mr-[20px]"></div>
-            <el-radio-group v-model="diyStore.editComponent.goodsNameStyle.fontWeight">
-              <el-radio :label="'normal'">常规</el-radio>
-              <el-radio :label="'bold'">加粗</el-radio>
-            </el-radio-group>
-          </el-form-item>
-          <el-form-item label="图片圆角">
-            <el-slider v-model="diyStore.editComponent.imgElementRounded" show-input size="small" class="ml-[10px] diy-nav-slider" :max="50" />
-          </el-form-item>
-          <el-form-item label="已兑人数">
-            <el-color-picker v-model="diyStore.editComponent.saleStyle.color" show-alpha :predefine="diyStore.predefineColors" />
-          </el-form-item>
-          <el-form-item label="销售价">
-            <el-color-picker v-model="diyStore.editComponent.priceStyle.mainColor" show-alpha :predefine="diyStore.predefineColors" />
-          </el-form-item>
-          <el-form-item label="上圆角">
-            <el-slider v-model="diyStore.editComponent.topElementRounded" show-input size="small" class="ml-[10px] diy-nav-slider" :max="50" />
-          </el-form-item>
-          <el-form-item label="下圆角">
-            <el-slider v-model="diyStore.editComponent.bottomElementRounded" show-input size="small" class="ml-[10px] diy-nav-slider" :max="50" />
-          </el-form-item>
-        </el-form>
-      </div>
-
-      <!-- 组件样式 -->
-      <slot name="style"></slot>
-    </div>
-  </div>
-</template>
-
-<script lang="ts" setup>
-import useDiyStore from '@/store/modules/diy';
-import goodsSelectPopup from './goods-select-popup.vue';
-
-const diyStore: any = useDiyStore();
-diyStore.editComponent.ignore = []; // 忽略公共属性
-
-// 组件验证
-diyStore.editComponent.verify = (index: number) => {
-  const res = { code: true, message: '' };
-
-  if (diyStore.value[index].source == 'category') {
-    if (diyStore.value[index].goods_category == '') {
-      res.code = false;
-      res.message = '请选择商品分类';
-    }
-  } else if (diyStore.value[index].source == 'custom') {
-    if (diyStore.value[index].goods_ids.length == 0) {
-      res.code = false;
-      res.message = '请选择商品';
-    }
-  }
-
-  return res;
-};
-
-const goodsSelectPopupRef = ref();
-const goodsSelect = (val: any) => {
-  diyStore.editComponent.goods_ids = val.map((el: any) => el.id);
-};
-defineExpose({});
-</script>
-
-<style lang="scss" scoped></style>

+ 0 - 38
src/views/diy/components/edit-shop-exchange-info.vue

@@ -1,38 +0,0 @@
-<template>
-  <div>
-    <!-- 内容 -->
-    <div class="content-wrap" v-show="diyStore.editTab == 'content'">
-      <div class="edit-attr-item-wrap">
-        <h3 class="mb-[10px]">会员样式</h3>
-        <el-form label-width="80px" class="px-[10px]">
-          <el-form-item label="背景图片">
-            <upload-image v-model="diyStore.editComponent.bgUrl" :limit="1" />
-          </el-form-item>
-        </el-form>
-      </div>
-    </div>
-
-    <!-- 样式 -->
-    <div class="style-wrap" v-show="diyStore.editTab == 'style'">
-      <!-- 组件样式 -->
-      <slot name="style"></slot>
-    </div>
-  </div>
-</template>
-
-<script lang="ts" setup>
-import useDiyStore from '@/store/modules/diy';
-
-const diyStore: any = useDiyStore();
-diyStore.editComponent.ignore = ['componentBgColor', 'componentBgUrl']; // 忽略公共属性
-
-// 组件验证
-diyStore.editComponent.verify = (index: number) => {
-  const res = { code: true, message: '' };
-  return res;
-};
-
-defineExpose({});
-</script>
-
-<style lang="scss" scoped></style>

+ 31 - 13
src/views/diy/components/edit-shop-goods-recommend.vue

@@ -6,18 +6,13 @@
         <h3 class="mb-[10px]">选择数据源</h3>
         <el-form label-width="80px" class="px-[10px]">
           <el-form-item label="选择商品">
-            <el-radio-group v-model="diyStore.editComponent.source" title="选择商品">
-              <el-radio value="all">默认</el-radio>
-              <el-radio value="custom">手动选择</el-radio>
-            </el-radio-group>
-          </el-form-item>
-          <el-form-item label="手动选择" v-if="diyStore.editComponent.source == 'custom'">
-            <goods-select-popup
-              ref="goodsSelectPopupRef"
-              v-model="diyStore.editComponent.goods_ids"
-              :min="diyStore.editComponent.list.length"
-              :max="diyStore.editComponent.list.length"
-            />
+            <div class="data-num" @click="openDialog">
+              <span v-if="diyStore.editComponent.goods_ids.length == 0">请选择</span>
+              <span v-else>已选择{{ diyStore.editComponent.goods_ids.length }}个</span>
+              <el-icon>
+                <ArrowRight />
+              </el-icon>
+            </div>
           </el-form-item>
         </el-form>
       </div>
@@ -93,14 +88,17 @@
       <!-- 组件样式 -->
       <slot name="style"></slot>
     </div>
+    <goods-mini ref="goodsDialogRef" :type="'editShopGoodsRecommend'" :categoryOptions="categoryOptions"></goods-mini>
   </div>
 </template>
 
 <script lang="ts" setup>
+import { categoryTree } from '@/api/pmsProduct/base';
 import useDiyStore from '@/store/modules/diy';
-import goodsSelectPopup from './goods-select-popup.vue';
 import Sortable from 'sortablejs';
 import { range } from 'lodash-es';
+const goodsDialogRef = ref<any>(null);
+const categoryOptions = ref<any>([]);
 
 const diyStore: any = useDiyStore();
 diyStore.editComponent.ignore = ['componentBgUrl']; // 忽略公共属性
@@ -141,6 +139,7 @@ diyStore.editComponent.list.forEach((item: any) => {
 const blockBoxRef = ref();
 
 onMounted(() => {
+  getCategoryTree();
   nextTick(() => {
     const sortable = Sortable.create(blockBoxRef.value, {
       group: 'item-wrap',
@@ -192,6 +191,25 @@ const deleteTempFn = (index) => {
   diyStore.editComponent.list.splice(index, 1);
   diyStore.editComponent.goods_ids.splice(index, 1);
 };
+/** 查询分类树 */
+const getCategoryTree = async () => {
+  categoryOptions.value = [];
+  const res = await categoryTree({
+    platform: 0,
+    pageNum: 1,
+    pageSize: 9999
+  });
+  const list = res.data || [];
+  categoryOptions.value = [...list];
+  categoryOptions.value.unshift({
+    id: '',
+    label: '全部'
+  });
+};
+//打开弹窗
+const openDialog = () => {
+  goodsDialogRef.value.onOpen();
+};
 
 defineExpose({});
 </script>

+ 1 - 1
src/views/diy/components/edit-shop-search.vue

@@ -1,6 +1,6 @@
 <template>
   <div>
-    <!-- 内容 -->
+    <!-- 内容  -->
     <div class="content-wrap" v-show="diyStore.editTab == 'content'">
       <div class="edit-attr-item-wrap">
         <h3 class="mb-[10px]">搜索设置</h3>

+ 37 - 199
src/views/diy/components/edit-single-recommend.vue

@@ -5,63 +5,34 @@
       <div class="edit-attr-item-wrap">
         <h3 class="mb-[10px]">标题内容</h3>
         <el-form label-width="80px" class="px-[10px]">
-          <el-form-item label="风格选择" class="flex">
-            <span class="text-primary flex-1 cursor-pointer" @click="showTitleStyle">{{ diyStore.editComponent.titleStyle.title }}</span>
-            <el-icon @click="showTitleStyle" class="cursor-pointer">
-              <ArrowRight />
-            </el-icon>
-          </el-form-item>
           <el-form-item label="图片上传">
             <upload-image v-model="diyStore.editComponent.textImg" :limit="1" />
           </el-form-item>
           <el-form-item label="链接地址">
-            <diy-link v-model="diyStore.editComponent.textLink" />
+            <WebLinkInput :pageType="'2'" v-model="diyStore.editComponent.textLink" placeholder="请输入或选择链接" />
           </el-form-item>
           <el-form-item label="副标题">
             <el-input v-model.trim="diyStore.editComponent.subTitle.text" placeholder="请输入副标题" clearable maxlength="8" show-word-limit />
           </el-form-item>
           <el-form-item label="链接地址">
-            <diy-link v-model="diyStore.editComponent.subTitle.link" />
+            <WebLinkInput :pageType="'2'" v-model="diyStore.editComponent.subTitle.link" placeholder="请输入或选择链接" />
           </el-form-item>
         </el-form>
-
-        <el-dialog v-model="showTitleDialog" title="风格选择" width="460px">
-          <div class="flex flex-wrap">
-            <template v-for="(item, index) in titleStyleList" :key="index">
-              <div
-                :class="{ 'border-primary': selectTitleStyle.value == item.value }"
-                @click="changeTitleStyle(item)"
-                class="flex items-center justify-center overflow-hidden w-[200px] h-[100px] mr-[12px] mb-[12px] cursor-pointer border bg-[#eee]"
-              >
-                <img :src="img(item.url)" />
-              </div>
-            </template>
-          </div>
-
-          <template #footer>
-            <span class="dialog-footer">
-              <el-button @click="showTitleDialog = false">取消</el-button>
-              <el-button type="primary" @click="confirmTitleStyle">确认</el-button>
-            </span>
-          </template>
-        </el-dialog>
       </div>
-
       <div class="edit-attr-item-wrap">
         <h3 class="mb-[10px]">选择数据源</h3>
         <el-form label-width="80px" class="px-[10px]">
           <el-form-item label="选择商品">
-            <el-radio-group v-model="diyStore.editComponent.source" title="选择商品">
-              <el-radio label="all">默认</el-radio>
-              <el-radio label="custom">手动选择</el-radio>
-            </el-radio-group>
-          </el-form-item>
-          <el-form-item label="手动选择" v-if="diyStore.editComponent.source == 'custom'">
-            <goods-select-popup ref="goodsSelectPopupRef" v-model="diyStore.editComponent.goods_ids" :min="1" :max="1" />
+            <div class="data-num" @click="openDialog">
+              <span v-if="diyStore.editComponent.goodsIds.length == 0">请选择</span>
+              <span v-else>已选择{{ diyStore.editComponent.goodsIds.length }}个</span>
+              <el-icon>
+                <ArrowRight />
+              </el-icon>
+            </div>
           </el-form-item>
         </el-form>
       </div>
-
       <div class="edit-attr-item-wrap">
         <h3 class="mb-[10px]">图片设置</h3>
         <el-form label-width="80px" class="px-[10px]">
@@ -84,7 +55,7 @@
               </div>
 
               <el-form-item label="链接地址">
-                <diy-link v-model="item.link" />
+                <WebLinkInput :pageType="'2'" v-model="item.link" placeholder="请输入或选择链接" />
               </el-form-item>
             </div>
           </div>
@@ -92,47 +63,6 @@
           <el-button v-show="diyStore.editComponent.list.length < 10" class="w-full" @click="addImageAd">添加图片</el-button>
         </el-form>
       </div>
-
-      <el-dialog v-model="categoryShowDialog" title="商品分类" width="750px" :destroy-on-close="true" :close-on-click-modal="false">
-        <el-table
-          :data="categoryTable.data"
-          ref="categoryTableRef"
-          size="large"
-          v-loading="categoryTable.loading"
-          height="450px"
-          @selection-change="handleSelectionChange"
-          row-key="category_id"
-          :expand-row-keys="expand_category_ids"
-          :tree-props="{ hasChildren: 'hasChildren', children: 'child_list' }"
-        >
-          <template #empty>
-            <span>{{ !categoryTable.loading ? '还没有安装应用' : '' }}</span>
-          </template>
-          <el-table-column type="selection" width="55" />
-          <el-table-column label="分类名称" min-width="120">
-            <template #default="{ row }">
-              <span class="order-2">{{ row.category_name }}</span>
-            </template>
-          </el-table-column>
-          <el-table-column label="分类图片" width="170" align="left">
-            <template #default="{ row }">
-              <div class="h-[30px]">
-                <el-image class="w-[30px] h-[30px]" :src="img(row.image)" fit="contain">
-                  <template #error>
-                    <div class="image-slot">
-                      <img class="w-[30px] h-[30px]" src="@/assets/images/diy/shop/category_default.png" />
-                    </div>
-                  </template>
-                </el-image>
-              </div>
-            </template>
-          </el-table-column>
-        </el-table>
-        <div class="flex items-center justify-end mt-[15px]">
-          <el-button type="primary" @click="saveCategoryId">确认</el-button>
-          <el-button @click="categoryShowDialog = false">取消</el-button>
-        </div>
-      </el-dialog>
     </div>
 
     <!-- 样式 -->
@@ -177,8 +107,8 @@
             <el-color-picker v-model="diyStore.editComponent.goodsNameStyle.color" show-alpha :predefine="diyStore.predefineColors" />
             <div class="mr-[20px]"></div>
             <el-radio-group v-model="diyStore.editComponent.goodsNameStyle.fontWeight">
-              <el-radio :label="'normal'">常规</el-radio>
-              <el-radio :label="'bold'">加粗</el-radio>
+              <el-radio :value="'normal'">常规</el-radio>
+              <el-radio :value="'bold'">加粗</el-radio>
             </el-radio-group>
           </el-form-item>
           <el-form-item label="销售价">
@@ -199,10 +129,12 @@
       <!-- 组件样式 -->
       <slot name="style"></slot>
     </div>
+    <goods-mini ref="goodsDialogRef" :categoryOptions="categoryOptions"></goods-mini>
   </div>
 </template>
 
 <script lang="ts" setup>
+import { categoryTree } from '@/api/pmsProduct/base';
 // import { getCategoryTree } from '@/addon/shop/api/goods';
 import { img } from '@/utils/common';
 import useDiyStore from '@/store/modules/diy';
@@ -213,135 +145,41 @@ import goodsSelectPopup from './goods-select-popup.vue';
 const diyStore: any = useDiyStore();
 diyStore.editComponent.ignore = ['componentBgUrl']; // 忽略公共属性
 
-const selectTitleStyle = reactive({
-  title: diyStore.editComponent.titleStyle.title,
-  value: diyStore.editComponent.titleStyle.value
-});
-
-// 标题风格样式
-const showTitleDialog = ref(false);
+const goodsDialogRef = ref<any>(null);
+const categoryOptions = ref<any>([]);
 
-const showTitleStyle = () => {
-  selectTitleStyle.title = diyStore.editComponent.titleStyle.title;
-  selectTitleStyle.value = diyStore.editComponent.titleStyle.value;
-  showTitleDialog.value = true;
-};
+onMounted(() => {
+  getCategoryTree();
+});
 
-const changeTitleStyle = (item: any) => {
-  selectTitleStyle.title = item.title;
-  selectTitleStyle.value = item.value;
+/** 查询分类树 */
+const getCategoryTree = async () => {
+  categoryOptions.value = [];
+  const res = await categoryTree({
+    platform: 0,
+    pageNum: 1,
+    pageSize: 9999
+  });
+  const list = res.data || [];
+  categoryOptions.value = [...list];
+  categoryOptions.value.unshift({
+    id: '',
+    label: '全部'
+  });
 };
 
-const confirmTitleStyle = () => {
-  diyStore.editComponent.titleStyle.title = selectTitleStyle.title;
-  diyStore.editComponent.titleStyle.value = selectTitleStyle.value;
-  showTitleDialog.value = false;
+//打开弹窗
+const openDialog = () => {
+  goodsDialogRef.value.onOpen();
 };
 
-const titleStyleList = reactive([
-  {
-    url: 'addon/shop/diy/single_recommend/title_style_01.png',
-    title: '风格1',
-    value: 'style-1'
-  }
-]);
-
 const addImageAd = () => {
   diyStore.editComponent.list.push({
     id: diyStore.generateRandom(),
     imageUrl: '',
     imgWidth: 0,
     imgHeight: 0,
-    link: { name: '' }
-  });
-};
-
-// 组件验证
-diyStore.editComponent.verify = (index: number) => {
-  const res = { code: true, message: '' };
-  if (diyStore.value[index].source == 'custom') {
-    if (diyStore.value[index].goods_ids.length == 0) {
-      res.code = false;
-      res.message = '请选择商品';
-    }
-  }
-
-  diyStore.value[index].list.forEach((item, index) => {
-    if (item.imageUrl === '') {
-      res.code = false;
-      res.message = '请上传图片';
-      return res;
-    }
-  });
-
-  return res;
-};
-
-const categoryShowDialog = ref(false);
-
-const categoryTable = reactive({
-  loading: true,
-  data: []
-});
-onMounted(() => {
-  loadCategoryList();
-});
-
-const categoryTableRef = ref<InstanceType<typeof ElTable>>();
-/**
- * 获取商品分类列表
- */
-let currCategoryData: any = null;
-const loadCategoryList = () => {
-  categoryTable.loading = true;
-
-  getCategoryTree()
-    .then((res) => {
-      categoryTable.loading = false;
-      categoryTable.data = res.data;
-    })
-    .catch(() => {
-      categoryTable.loading = false;
-    });
-};
-
-// 选择商品分类
-const handleSelectionChange = (val: string | any[]) => {
-  let data = '';
-  if (val) data = val[val.length - 1];
-  if (val.length > 1) categoryTableRef.value!.clearSelection();
-  if (data) categoryTableRef.value!.toggleRowSelection(data, true);
-  currCategoryData = data;
-};
-
-const saveCategoryId = () => {
-  diyStore.editComponent.goods_category = currCategoryData.category_id;
-  diyStore.editComponent.goods_category_name = currCategoryData.category_name;
-  categoryShowDialog.value = false;
-};
-
-const categoryShowDialogOpen = () => {
-  categoryShowDialog.value = true;
-  nextTick(() => {
-    setRowSelection();
-  });
-};
-
-//分类数据选中回填,设置展开行
-const expand_category_ids = ref<Array<any>>([]);
-const setRowSelection = () => {
-  expand_category_ids.value = [];
-  categoryTable.data.forEach((el: any) => {
-    if (diyStore.editComponent.goods_category == el.category_id) {
-      categoryTableRef.value!.toggleRowSelection(el, true);
-    } else if (el.child_list && el.child_list.length) {
-      el.child_list.forEach((v: any) => {
-        if (diyStore.editComponent.goods_category == v.category_id) {
-          expand_category_ids.value.push(el.category_id.toString());
-          categoryTableRef.value!.toggleRowSelection(v, true);
-        }
-      });
-    }
+    link: ''
   });
 };
 

+ 1 - 1
src/views/diy/components/edit-text.vue

@@ -20,7 +20,7 @@
             <el-input v-model.trim="diyStore.editComponent.text" placeholder="请输入标题" clearable maxlength="15" show-word-limit />
           </el-form-item>
           <el-form-item label="链接地址">
-            <WebLinkInput v-model="diyStore.editComponent.link" placeholder="请输入或选择链接" />
+            <WebLinkInput :pageType="'2'" v-model="diyStore.editComponent.link" placeholder="请输入或选择链接" />
           </el-form-item>
           <el-form-item label="对齐方式" v-show="diyStore.editComponent.style == 'style-1'">
             <el-radio-group v-model="diyStore.editComponent.textAlign">

+ 47 - 489
src/views/diy/edit.ts

@@ -198,37 +198,6 @@ export const data1 = {
             'heatMapData': []
           }
         },
-        'MemberLevel': {
-          'title': '会员等级',
-          'icon': 'iconfont iconhuiyuandengjipc',
-          'path': 'edit-member-level',
-          'uses': 1,
-          'value': {
-            'style': 'style-1',
-            'styleName': '风格1'
-          },
-          'template': {
-            'textColor': '#303133',
-            'pageStartBgColor': '',
-            'pageEndBgColor': '',
-            'pageGradientAngle': 'to bottom',
-            'componentBgUrl': '',
-            'componentBgAlpha': 2,
-            'componentStartBgColor': '',
-            'componentEndBgColor': '',
-            'componentGradientAngle': 'to bottom',
-            'topRounded': 12,
-            'bottomRounded': 0,
-            'elementBgColor': '',
-            'topElementRounded': 0,
-            'bottomElementRounded': 0,
-            'margin': {
-              'top': 0,
-              'bottom': 0,
-              'both': 10
-            }
-          }
-        },
         'Notice': {
           'title': '公告',
           'icon': 'iconfont icongonggaopc',
@@ -272,10 +241,8 @@ export const data1 = {
               'value': 'style-1'
             },
             'text': '超值爆款',
-            'textImg': 'static/resource/images/diy/active_cube/active_cube_text1.png',
-            'textLink': {
-              'name': ''
-            },
+            'textImg': '',
+            'textLink': '',
             'titleColor': '#F91700',
             'subTitle': {
               'text': '为您精选爆款',
@@ -312,7 +279,7 @@ export const data1 = {
                   'endColor': '#FFFFFF'
                 },
                 'link': '',
-                'imageUrl': 'static/resource/images/diy/active_cube/active_cube_goods1.png'
+                'imageUrl': ''
               },
               {
                 'title': {
@@ -335,7 +302,7 @@ export const data1 = {
                   'endColor': '#FFFFFF'
                 },
                 'link': '',
-                'imageUrl': 'static/resource/images/diy/active_cube/active_cube_goods2.png'
+                'imageUrl': ''
               },
               {
                 'title': {
@@ -358,7 +325,7 @@ export const data1 = {
                   'endColor': '#FFFFFF'
                 },
                 'link': '',
-                'imageUrl': 'static/resource/images/diy/active_cube/active_cube_goods3.png'
+                'imageUrl': ''
               },
               {
                 'title': {
@@ -381,7 +348,7 @@ export const data1 = {
                   'endColor': '#FFFFFF'
                 },
                 'link': '',
-                'imageUrl': 'static/resource/images/diy/active_cube/active_cube_goods4.png'
+                'imageUrl': ''
               }
             ],
             'template': {
@@ -544,7 +511,7 @@ export const data1 = {
           'value': {
             'moduleOne': {
               'head': {
-                'textImg': 'static/resource/images/diy/picture_show/picture_show_head_text3.png',
+                'textImg': '',
                 'subText': '最高补1200元',
                 'subTextColor': '#666666'
               },
@@ -557,7 +524,7 @@ export const data1 = {
                     'endColor': '#F5443E'
                   },
                   'link': '',
-                  'imageUrl': 'static/resource/images/diy/picture_show/picture_05.png'
+                  'imageUrl': ''
                 },
                 {
                   'btnTitle': {
@@ -566,10 +533,8 @@ export const data1 = {
                     'startColor': '#F5443E',
                     'endColor': '#F5443E'
                   },
-                  'link': {
-                    'name': ''
-                  },
-                  'imageUrl': 'static/resource/images/diy/picture_show/picture_06.png'
+                  'link': '',
+                  'imageUrl': ''
                 }
               ],
               'listFrame': {
@@ -579,7 +544,7 @@ export const data1 = {
             },
             'moduleTwo': {
               'head': {
-                'textImg': 'static/resource/images/diy/picture_show/picture_show_head_text4.png',
+                'textImg': '',
                 'subText': '每日上新',
                 'subTextColor': '#666666'
               },
@@ -591,10 +556,8 @@ export const data1 = {
                     'startColor': '#F5443E',
                     'endColor': '#F5443E'
                   },
-                  'link': {
-                    'name': ''
-                  },
-                  'imageUrl': 'static/resource/images/diy/picture_show/picture_07.png'
+                  'link': '',
+                  'imageUrl': ''
                 },
                 {
                   'btnTitle': {
@@ -603,10 +566,8 @@ export const data1 = {
                     'startColor': '#F5443E',
                     'endColor': '#F5443E'
                   },
-                  'link': {
-                    'name': ''
-                  },
-                  'imageUrl': 'static/resource/images/diy/picture_show/picture_08.png'
+                  'link': '',
+                  'imageUrl': ''
                 }
               ],
               'listFrame': {
@@ -802,10 +763,11 @@ export const data1 = {
               {
                 'title': '推荐',
                 'desc': '猜你喜欢',
-                'source': 'custom',
+                'source': 1,
                 'goods_category': '',
                 'goods_category_name': '请选择',
                 'goodsIds': [],
+                'categoryIds': [],
                 'imageUrl': ''
               }
             ],
@@ -833,113 +795,6 @@ export const data1 = {
             }
           }
         },
-        'GoodsCoupon': {
-          'title': '优惠券',
-          'icon': 'iconfont iconyouhuiquanpc',
-          'path': 'edit-goods-coupon',
-          'uses': 0,
-          'value': {
-            'style': 'style-1',
-            'styleName': '风格一',
-            'source': 'custom',
-            'num': 6,
-            'couponIds': [],
-            'btnText': '立即领取',
-            'couponTitle': '先领券 再购物',
-            'couponSubTitle': '领券下单 享购物优惠',
-            'titleColor': '#ffffff',
-            'subTitleColor': '#ffffff',
-            'couponItem': {
-              'bgColor': '#ffffff',
-              'textColor': '#333333',
-              'subTextColor': '#666666',
-              'moneyColor': '#333333',
-              'aroundRadius': 12
-            }
-          },
-          'template': {
-            'textColor': '#303133',
-            'pageStartBgColor': '',
-            'pageEndBgColor': '',
-            'pageGradientAngle': 'to bottom',
-            'componentBgUrl': '',
-            'componentBgAlpha': 2,
-            'componentStartBgColor': '',
-            'componentEndBgColor': '',
-            'componentGradientAngle': 'to bottom',
-            'topRounded': 0,
-            'bottomRounded': 0,
-            'elementBgColor': '',
-            'topElementRounded': 0,
-            'bottomElementRounded': 0,
-            'margin': {
-              'top': 10,
-              'bottom': 10,
-              'both': 10
-            }
-          }
-        },
-        'ShopExchangeInfo': {
-          'title': '积分兑换',
-          'icon': 'iconfont iconjifenpc',
-          'path': 'edit-shop-exchange-info',
-          'uses': 0,
-          'value': {
-            'bgUrl': 'addon/shop/diy/point/point_index_bg.jpg'
-          }
-        },
-        'ShopExchangeGoods': {
-          'title': '积分商品',
-          'icon': 'iconfont iconjifenshangpinpc',
-          'path': 'edit-shop-exchange-goods',
-          'uses': 0,
-          'value': {
-            'style': 'style-2',
-            'source': 'custom',
-            'num': 10,
-            'goods_category': '',
-            'goods_category_name': '请选择',
-            'goodsIds': [],
-            'sortWay': 'total_order_num',
-            'goodsNameStyle': {
-              'color': '#333',
-              'control': true,
-              'fontWeight': 'normal'
-            },
-            'priceStyle': {
-              'mainColor': '#FF4142',
-              'mainControl': true,
-              'lineColor': '#999CA7',
-              'lineControl': true
-            },
-            'saleStyle': {
-              'color': '#999999',
-              'control': true
-            },
-            'imgElementRounded': 10
-          },
-          'template': {
-            'textColor': '#303133',
-            'pageStartBgColor': '',
-            'pageEndBgColor': '',
-            'pageGradientAngle': 'to bottom',
-            'componentBgUrl': '',
-            'componentBgAlpha': 2,
-            'componentStartBgColor': '',
-            'componentEndBgColor': '',
-            'componentGradientAngle': 'to bottom',
-            'topRounded': 0,
-            'bottomRounded': 0,
-            'elementBgColor': '',
-            'topElementRounded': 0,
-            'bottomElementRounded': 0,
-            'margin': {
-              'top': 0,
-              'bottom': 0,
-              'both': 10
-            }
-          }
-        },
         'ShopGoodsRecommend': {
           'title': '商品推荐',
           'icon': 'iconfont icona-shangpintuijianpc30',
@@ -950,6 +805,7 @@ export const data1 = {
               'mainColor': '#333333'
             },
             'source': 'custom',
+            'goods_ids': [],
             'goodsIds': [],
             'list': [
               {
@@ -1050,35 +906,27 @@ export const data1 = {
               'title': '风格1',
               'value': 'style-1'
             },
-            'textImg': 'addon/shop/diy/index/style3/single_recommend_text1.png',
-            'textLink': {
-              'name': ''
-            },
+            'textImg': 'https://v6.site.niucloud.com/addon/shop/diy/index/style3/single_recommend_text1.png',
+            'textLink': '',
             'titleColor': '#999999',
             'subTitle': {
               'text': '更多',
               'textColor': '#999999',
-              'link': {
-                'name': ''
-              }
+              'link': ''
             },
             'source': 'custom',
             'goodsIds': [],
             'imageHeight': 250,
             'list': [
               {
-                'link': {
-                  'name': ''
-                },
-                'imageUrl': 'addon/shop/diy/index/style3/single_recommend_banner1.jpg',
+                'link': '',
+                'imageUrl': 'https://v6.site.niucloud.com/addon/shop/diy/index/style3/single_recommend_banner1.jpg',
                 'imgWidth': 345,
                 'imgHeight': 495
               },
               {
-                'link': {
-                  'name': ''
-                },
-                'imageUrl': 'addon/shop/diy/index/style3/single_recommend_banner2.jpg',
+                'link': '',
+                'imageUrl': 'https://v6.site.niucloud.com/addon/shop/diy/index/style3/single_recommend_banner2.jpg',
                 'imgWidth': 345,
                 'imgHeight': 495
               }
@@ -1141,9 +989,7 @@ export const data1 = {
               'textColor': '#FFFFFF',
               'startColor': '#FB792F',
               'endColor': '#F91700',
-              'link': {
-                'name': ''
-              }
+              'link': ''
             },
             'countDown': {
               'numberColor': 'rgba(255, 0, 0, 1)',
@@ -2115,51 +1961,6 @@ export const data3 = {
       },
       'pageStyle': 'padding-top:2rpx;padding-bottom:0rpx;padding-right:20rpx;padding-left:20rpx;'
     },
-    {
-      'path': 'edit-goods-coupon',
-      'uses': 0,
-      'id': '5zq2inzbmu00',
-      'componentName': 'GoodsCoupon',
-      'componentTitle': '优惠券',
-      'ignore': ['componentBgColor', 'componentBgUrl'],
-      'style': 'style-3',
-      'styleName': '风格3',
-      'source': 'custom',
-      'num': 6,
-      'couponIds': [],
-      'btnText': '立即领取',
-      'couponTitle': '每日省钱',
-      'couponSubTitle': '先领券 再购物',
-      'titleColor': '#ffffff',
-      'subTitleColor': '#ffffff',
-      'couponItem': {
-        'bgColor': '#ffffff',
-        'textColor': '#333333',
-        'subTextColor': '#666666',
-        'moneyColor': '#333333',
-        'aroundRadius': 12
-      },
-      'textColor': '#303133',
-      'pageStartBgColor': '',
-      'pageEndBgColor': '',
-      'pageGradientAngle': 'to bottom',
-      'componentBgUrl': '',
-      'componentBgAlpha': 2,
-      'componentStartBgColor': '',
-      'componentEndBgColor': '',
-      'componentGradientAngle': 'to bottom',
-      'topRounded': 12,
-      'bottomRounded': 12,
-      'elementBgColor': '',
-      'topElementRounded': 0,
-      'bottomElementRounded': 0,
-      'margin': {
-        'top': 10,
-        'bottom': null,
-        'both': 10
-      },
-      'pageStyle': 'padding-top:20rpx;padding-bottom:0rpx;padding-right:20rpx;padding-left:20rpx;'
-    },
     {
       'path': 'edit-shop-goods-recommend',
       'uses': 0,
@@ -2172,6 +1973,7 @@ export const data3 = {
         'mainColor': '#333333'
       },
       'source': 'custom',
+      'goods_ids': [],
       'goodsIds': [],
       'list': [
         {
@@ -2275,18 +2077,14 @@ export const data3 = {
         'value': 'style-5'
       },
       'text': '超值爆款',
-      'textLink': {
-        'name': ''
-      },
+      'textLink': '',
       'titleColor': '#F91700',
       'subTitle': {
         'text': '为您精选爆款',
         'textColor': 'rgba(153, 153, 153, 1)',
         'startColor': 'rgba(255, 255, 255, 1)',
         'endColor': 'rgba(255, 255, 255, 1)',
-        'link': {
-          'name': ''
-        }
+        'link': ''
       },
       'blockStyle': {
         'title': '风格2',
@@ -2315,9 +2113,7 @@ export const data3 = {
             'startColor': '#FFF1DB',
             'endColor': '#FFFBF4'
           },
-          'link': {
-            'name': ''
-          },
+          'link': '',
           'imageUrl': 'https://v6.site.niucloud.com/static/resource/images/diy/active_cube/active_cube_goods1.png',
           'id': '77tls7gaho80'
         },
@@ -2341,9 +2137,7 @@ export const data3 = {
             'startColor': '#E6F6E2',
             'endColor': '#F5FDF3'
           },
-          'link': {
-            'name': ''
-          },
+          'link': '',
           'imageUrl': 'https://v6.site.niucloud.com/static/resource/images/diy/active_cube/active_cube_goods2.png',
           'id': 'm4scwuc67do'
         },
@@ -2367,9 +2161,7 @@ export const data3 = {
             'startColor': '#E2F6FF',
             'endColor': '#F2FAFF'
           },
-          'link': {
-            'name': ''
-          },
+          'link': '',
           'imageUrl': 'https://v6.site.niucloud.com/static/resource/images/diy/active_cube/active_cube_goods3.png',
           'id': '33nbfp8czea0'
         },
@@ -2393,9 +2185,7 @@ export const data3 = {
             'startColor': '#FFEAEA',
             'endColor': '#FFFCFB'
           },
-          'link': {
-            'name': ''
-          },
+          'link': '',
           'imageUrl': 'https://v6.site.niucloud.com/static/resource/images/diy/active_cube/active_cube_goods4.png',
           'id': '49scoy4bgsg0'
         }
@@ -2444,9 +2234,7 @@ export const data3 = {
               'startColor': '#F5443E',
               'endColor': '#F5443E'
             },
-            'link': {
-              'name': ''
-            },
+            'link': '',
             'imageUrl': 'https://v6.site.niucloud.com/static/resource/images/diy/picture_show/picture_01.png'
           },
           {
@@ -2456,9 +2244,7 @@ export const data3 = {
               'startColor': '#F5443E',
               'endColor': '#F5443E'
             },
-            'link': {
-              'name': ''
-            },
+            'link': '',
             'imageUrl': 'https://v6.site.niucloud.com/static/resource/images/diy/picture_show/picture_02.png'
           }
         ],
@@ -2481,9 +2267,7 @@ export const data3 = {
               'startColor': '#F5443E',
               'endColor': '#F5443E'
             },
-            'link': {
-              'name': ''
-            },
+            'link': '',
             'imageUrl': 'https://v6.site.niucloud.com/static/resource/images/diy/picture_show/picture_03.png'
           },
           {
@@ -2493,9 +2277,7 @@ export const data3 = {
               'startColor': '#F5443E',
               'endColor': '#F5443E'
             },
-            'link': {
-              'name': ''
-            },
+            'link': '',
             'imageUrl': 'https://v6.site.niucloud.com/static/resource/images/diy/picture_show/picture_04.png'
           }
         ],
@@ -2529,203 +2311,6 @@ export const data3 = {
       },
       'pageStyle': 'padding-top:20rpx;padding-bottom:0rpx;padding-right:20rpx;padding-left:20rpx;'
     },
-    {
-      'path': 'edit-picture-show',
-      'uses': 0,
-      'id': '3wz1r5bww3q0',
-      'componentName': 'PictureShow',
-      'componentTitle': '图片展播',
-      'ignore': [],
-      'moduleOne': {
-        'head': {
-          'textImg': 'https://v6.site.niucloud.com/static/resource/images/diy/picture_show/picture_show_head_text3.png',
-          'subText': '最高补1200元',
-          'subTextColor': '#666666'
-        },
-        'list': [
-          {
-            'btnTitle': {
-              'text': '全网低价',
-              'color': '#ffffff',
-              'startColor': '#F5443E',
-              'endColor': '#F5443E'
-            },
-            'link': {
-              'name': ''
-            },
-            'imageUrl': 'https://v6.site.niucloud.com/static/resource/images/diy/picture_show/picture_05.png'
-          },
-          {
-            'btnTitle': {
-              'text': '大牌特惠',
-              'color': '#ffffff',
-              'startColor': '#F5443E',
-              'endColor': '#F5443E'
-            },
-            'link': {
-              'name': ''
-            },
-            'imageUrl': 'https://v6.site.niucloud.com/static/resource/images/diy/picture_show/picture_06.png'
-          }
-        ],
-        'listFrame': {
-          'startColor': 'rgba(212, 239, 255, 1)',
-          'endColor': 'rgba(235, 244, 250, 1)'
-        }
-      },
-      'moduleTwo': {
-        'head': {
-          'textImg': 'https://v6.site.niucloud.com/static/resource/images/diy/picture_show/picture_show_head_text4.png',
-          'subText': '每日上新',
-          'subTextColor': '#666666'
-        },
-        'list': [
-          {
-            'btnTitle': {
-              'text': '人气爆款',
-              'color': '#ffffff',
-              'startColor': '#F5443E',
-              'endColor': '#F5443E'
-            },
-            'link': {
-              'name': ''
-            },
-            'imageUrl': 'https://v6.site.niucloud.com/static/resource/images/diy/picture_show/picture_07.png'
-          },
-          {
-            'btnTitle': {
-              'text': '官方正品',
-              'color': '#ffffff',
-              'startColor': '#F5443E',
-              'endColor': '#F5443E'
-            },
-            'link': {
-              'name': ''
-            },
-            'imageUrl': 'https://v6.site.niucloud.com/static/resource/images/diy/picture_show/picture_08.png'
-          }
-        ],
-        'listFrame': {
-          'startColor': 'rgba(255, 241, 212, 1)',
-          'endColor': 'rgba(249, 242, 229, 1)'
-        }
-      },
-      'moduleRounded': {
-        'topRounded': 10,
-        'bottomRounded': 10
-      },
-      'textColor': '#303133',
-      'pageStartBgColor': '',
-      'pageEndBgColor': '',
-      'pageGradientAngle': 'to bottom',
-      'componentBgUrl': '',
-      'componentBgAlpha': 2,
-      'componentStartBgColor': '',
-      'componentEndBgColor': '',
-      'componentGradientAngle': 'to bottom',
-      'topRounded': 0,
-      'bottomRounded': 0,
-      'elementBgColor': '',
-      'topElementRounded': 0,
-      'bottomElementRounded': 0,
-      'margin': {
-        'top': 10,
-        'bottom': null,
-        'both': 10
-      },
-      'pageStyle': 'padding-top:20rpx;padding-bottom:0rpx;padding-right:20rpx;padding-left:20rpx;'
-    },
-    // {
-    //   'path': 'edit-single-recommend',
-    //   'uses': 0,
-    //   'id': '2mxsu6pbqpu0',
-    //   'componentName': 'SingleRecommend',
-    //   'mode': 'aspectFill',
-    //   'componentTitle': '精选推荐',
-    //   'ignore': [],
-    //   'titleStyle': {
-    //     'title': '风格1',
-    //     'value': 'style-1'
-    //   },
-    //   'textImg': 'https://v6.site.niucloud.com/addon/shop/diy/index/style3/single_recommend_text1.png',
-    //   'textLink': {
-    //     'name': ''
-    //   },
-    //   'titleColor': 'rgba(153, 153, 153, 1)',
-    //   'subTitle': {
-    //     'text': '更多',
-    //     'textColor': 'rgba(153, 153, 153, 1)',
-    //     'link': {
-    //       'name': ''
-    //     }
-    //   },
-    //   'source': 'custom',
-    //   'goodsIds': [],
-    //   'imageHeight': '250',
-    //   'list': [
-    //     {
-    //       'id': '18o4pyaufktc',
-    //       'imageUrl': 'addon/shop/diy/index/style3/single_recommend_banner1.jpg',
-    //       'imgWidth': 345,
-    //       'imgHeight': 495,
-    //       'link': {
-    //         'name': ''
-    //       },
-    //       'width': 355,
-    //       'height': 509.3478260869565
-    //     },
-    //     {
-    //       'id': '18o8pyaufktc',
-    //       'imageUrl': 'addon/shop/diy/index/style3/single_recommend_banner2.jpg',
-    //       'imgWidth': 345,
-    //       'imgHeight': 495,
-    //       'link': {
-    //         'name': ''
-    //       },
-    //       'width': 355,
-    //       'height': 509.3478260869565
-    //     }
-    //   ],
-    //   'goodsNameStyle': {
-    //     'color': '#303133',
-    //     'control': true,
-    //     'fontWeight': 'normal'
-    //   },
-    //   'priceStyle': {
-    //     'mainColor': '#FF4142',
-    //     'mainControl': true,
-    //     'lineColor': '#999CA7',
-    //     'lineControl': true
-    //   },
-    //   'saleStyle': {
-    //     'color': 'rgba(255, 0, 0, 1)',
-    //     'control': true
-    //   },
-    //   'textColor': '#303133',
-    //   'pageStartBgColor': '',
-    //   'pageEndBgColor': '',
-    //   'pageGradientAngle': 'to bottom',
-    //   'componentBgUrl': '',
-    //   'componentBgAlpha': 2,
-    //   'componentStartBgColor': null,
-    //   'componentEndBgColor': null,
-    //   'componentGradientAngle': 'to bottom',
-    //   'topRounded': 0,
-    //   'bottomRounded': 0,
-    //   'elementBgColor': 'rgba(255, 255, 255, 1)',
-    //   'topElementRounded': 12,
-    //   'bottomElementRounded': 12,
-    //   'margin': {
-    //     'top': 15,
-    //     'bottom': 0,
-    //     'both': 10
-    //   },
-    //   'pageStyle': 'padding-top:20rpx;padding-bottom:20rpx;padding-right:20rpx;padding-left:20rpx;',
-    //   'topCarouselRounded': 12,
-    //   'bottomCarouselRounded': 12,
-    //   'indicatorColor': 'rgba(255, 255, 255, 0.6)',
-    //   'indicatorActiveColor': 'rgba(255, 255, 255, 1)'
-    // },
     {
       'path': 'edit-image-ads',
       'uses': 0,
@@ -2737,9 +2322,7 @@ export const data3 = {
       'isSameScreen': false,
       'list': [
         {
-          'link': {
-            'name': ''
-          },
+          'link': '',
           'imageUrl': 'https://v6.site.niucloud.com/addon/shop/diy/index/style3/discount_img.png',
           'imgWidth': 710,
           'imgHeight': 170,
@@ -2821,10 +2404,11 @@ export const data3 = {
         {
           'title': '推荐',
           'desc': '猜你喜欢',
-          'source': 'custom',
+          'source': 1,
           'goods_category': '',
           'goods_category_name': '请选择',
           'goodsIds': [],
+          'categoryIds': [],
           'imageUrl': '',
           'id': '67pl1ysjhr40'
         },
@@ -2832,30 +2416,33 @@ export const data3 = {
           'id': '6z59zcmk4jk0',
           'title': '衣鞋包饰',
           'desc': '分类描述',
-          'source': 'custom',
+          'source': 1,
           'goods_category': '',
           'goods_category_name': '请选择',
           'goodsIds': [],
+          'categoryIds': [],
           'imageUrl': ''
         },
         {
           'id': '1cfbll6wnmw0',
           'title': '居家百货',
           'desc': '分类描述',
-          'source': 'custom',
+          'source': 1,
           'goods_category': '',
           'goods_category_name': '请选择',
           'goodsIds': [],
+          'categoryIds': [],
           'imageUrl': ''
         },
         {
           'id': '49p79g5l5qs0',
           'title': '食品营养',
           'desc': '分类描述',
-          'source': 'custom',
+          'source': 1,
           'goods_category': '',
           'goods_category_name': '请选择',
           'goodsIds': [],
+          'categoryIds': [],
           'imageUrl': ''
         }
       ],
@@ -2989,35 +2576,6 @@ export const data4 = {
       'bgUrl': '',
       'isShowAccount': true
     },
-    {
-      'path': 'edit-member-level',
-      'uses': 1,
-      'id': '533e6ynytmo0',
-      'componentName': 'MemberLevel',
-      'componentTitle': '会员等级',
-      'ignore': ['componentBgColor', 'componentBgUrl'],
-      'style': 'style-5',
-      'styleName': '风格5',
-      'textColor': '#303133',
-      'componentStartBgColor': '',
-      'componentEndBgColor': '',
-      'topRounded': 12,
-      'bottomRounded': 12,
-      'elementBgColor': '',
-      'topElementRounded': 0,
-      'bottomElementRounded': 0,
-      'margin': {
-        'top': -45,
-        'bottom': 0,
-        'both': 10
-      },
-      'pageStartBgColor': '',
-      'pageEndBgColor': '',
-      'pageGradientAngle': 'to bottom',
-      'componentBgUrl': '',
-      'componentBgAlpha': 2,
-      'componentGradientAngle': 'to bottom'
-    },
     {
       'path': 'edit-shop-order-info',
       'uses': 1,
@@ -3251,7 +2809,7 @@ export const data4 = {
           'imgHeight': 92
         },
         {
-          'title': '优惠券',
+          'title': '优惠券1',
           'link': '',
           'imageUrl': 'https://v6.site.niucloud.com/addon/shop/diy/member/style1/nav_coupon.png',
           'label': {

+ 1 - 3
src/views/diy/edit.vue

@@ -286,14 +286,13 @@ const save = (type?: number) => {
 
   if (isRepeat.value) return;
   isRepeat.value = true;
-
   const datas = {
     id: id.value,
     page_title: diyStore.pageTitle,
     title: diyStore.global.title,
     type: diyStore.type,
     is_default: diyStore.isDefault,
-    isDefault:1,
+    isDefault: 1,
     value: JSON.stringify({
       global: toRaw(diyStore.global),
       value: toRaw(diyStore.value)
@@ -590,4 +589,3 @@ watch(
   }
 }
 </style>
-

+ 4 - 4
src/views/diy/miniEdit.vue

@@ -256,14 +256,14 @@ onMounted(() => {
     diyStore.type = query.type.toString();
     diyStore.pageTitle = query.title.toString();
     // 首页模板1
-    // diyStore.global = data3.global;
-    // diyStore.value = data3.value;
+    diyStore.global = data3.global;
+    diyStore.value = data3.value;
     // 个人中心模板
     // diyStore.global = data4.global;
     // diyStore.value = data4.value;
     // 商品详情模板
-    diyStore.global = data2.shop_goods_detail_style1.data.global;
-    diyStore.value = data2.shop_goods_detail_style1.data.value;
+    // diyStore.global = data2.shop_goods_detail_style1.data.global;
+    // diyStore.value = data2.shop_goods_detail_style1.data.value;
   }
 });
 

+ 1 - 1
src/views/diy/pccomponents/edit/navigation-edit.vue

@@ -44,7 +44,7 @@
                   <div class="flex-row-start">
                     <upload-image v-model="element.imageUrl" :limit="1" />
                     <div class="flex-column-between images-bos">
-                      <div class="annotation3">(建议上传尺寸相同图片,推荐尺寸150*150)</div>
+                      <div class="annotation3">(建议上传尺寸相同图片,推荐尺寸170*112)</div>
                       <div class="flex-row-between images-box">
                         <div>缩放模式</div>
                         <div class="flex-row-start" @click="openImageType(element, index)">

+ 2 - 2
src/views/diy/pccomponents/pages/floor.vue

@@ -67,7 +67,7 @@ const props = defineProps<{
   row?: any;
 }>();
 const componentData = props.row ? props.row : diyStore.componentList[props.index];
-const dataList = ref<any>([{}, {}, {}, {}, {}, {}, {}, {}]);
+const dataList = ref<any>([{}, {}, {}, {}, {}, {}, {}, {}, {}, {}]);
 
 onMounted(() => {
   getDataList();
@@ -82,7 +82,7 @@ watch(
 );
 
 const getDataList = () => {
-  dataList.value = [{}, {}, {}, {}, {}, {}, {}, {}];
+  dataList.value = [{}, {}, {}, {}, {}, {}, {}, {}, {}, {}];
   //手动选择
   if (componentData.goodsIds.length > 0) {
     const apiFunc = diyStore.clientId && diyStore.clientId !== 'undefined' ? getCustomerProductPage : listBase;

+ 4 - 4
src/views/diy/pccomponents/pages/navigation.vue

@@ -135,8 +135,8 @@ const subtitleCss = computed(() => {
       cursor: pointer;
 
       .img {
-        height: 80px;
-        width: 80px;
+        height: 112px;
+        aspect-ratio: 170 / 112;
       }
     }
   }
@@ -152,8 +152,8 @@ const subtitleCss = computed(() => {
         height: 170px;
 
         .img {
-          height: 80px;
-          width: 80px;
+          height: 112px;
+          aspect-ratio: 170 / 112;
         }
       }
     }

+ 5683 - 0
src/views/enterprisePurchase/index.vue

@@ -0,0 +1,5683 @@
+<template>
+  <div class="parameter-settings">
+    <!-- 顶部二级菜单 -->
+    <div class="sub-tabs">
+      <div
+        v-for="tab in subTabs"
+        :key="tab.value"
+        :class="['sub-tab-item', { active: activeSubTab === tab.value }]"
+        @click="activeSubTab = tab.value"
+      >
+        {{ tab.label }}
+      </div>
+    </div>
+
+    <!-- 内容区域 -->
+    <div class="content-body">
+      <!-- 搜索栏编辑区 -->
+      <div v-if="activeSubTab === 'search'" class="search-editor">
+        <!-- 实时预览区 (参照图1) -->
+        <div class="preview-section">
+          <div class="preview-title">实时预览</div>
+          <div class="live-preview-box">
+            <div class="search-bar-mockup">
+              <!-- 左侧标题 -->
+              <div class="mockup-left">
+                <div class="main-title" :style="{ color: form.themeColor }">{{ form.mainTitle }}</div>
+                <div class="sub-title">{{ form.subTitle }}</div>
+              </div>
+
+              <!-- 中间搜索框 -->
+              <div class="mockup-center" :style="{ '--theme-color': form.themeColor }">
+                <div class="search-input-wrapper" :style="{ borderColor: form.themeColor }">
+                  <div class="placeholder-scroll">
+                    <transition-group name="list-scroll" tag="div" class="scroll-container">
+                      <div v-for="(text, index) in searchPlaceholderList" :key="text" v-show="index === currentPlaceholderIndex" class="scroll-item">
+                        {{ text }}
+                      </div>
+                    </transition-group>
+                  </div>
+                  <div class="search-btn" :style="{ backgroundColor: form.themeColor }">搜 索</div>
+                </div>
+                <div class="hot-words">
+                  <span v-for="item in form.hotWordsList" :key="item.name" class="hot-word">{{ item.name }}</span>
+                </div>
+              </div>
+
+              <!-- 右侧功能按钮 (可配置) -->
+              <div class="mockup-right">
+                <div class="cart-btn" :style="{ borderColor: form.themeColor, color: form.themeColor }">
+                  <img v-if="form.rightBtnIcon" :src="form.rightBtnIcon" style="width: 16px; height: 16px; margin-right: 4px; object-fit: contain" />
+                  <span v-if="form.rightBtnText">{{ form.rightBtnText }}</span>
+                  <span v-else-if="!form.rightBtnIcon">购物车</span>
+                </div>
+              </div>
+            </div>
+          </div>
+        </div>
+
+        <!-- 配置项表单 -->
+        <div class="settings-section">
+          <el-form :model="form" label-width="120px" label-position="left">
+            <el-form-item label="主标题:">
+              <el-input v-model="form.mainTitle" placeholder="如:优易企业购" class="settings-input" />
+            </el-form-item>
+
+            <el-form-item label="副标题:">
+              <el-input v-model="form.subTitle" placeholder="如:省钱 · 省心 · 省时间" class="settings-input" />
+            </el-form-item>
+
+            <el-form-item label="搜索框文字:">
+              <el-input v-model="form.placeholderText" placeholder="支持输入多个,请用英文逗号(,)隔开实现上下滚动效果" class="settings-input" />
+            </el-form-item>
+
+            <el-form-item label="搜索热词:">
+              <div class="hot-words-config">
+                <div v-for="(item, index) in form.hotWordsList" :key="index" class="hot-word-row">
+                  <el-input v-model="item.name" placeholder="热词名称" class="hot-word-input-name" />
+                  <WebLinkInput v-model="item.link" placeholder="跳转地址" class="hot-word-input-link"/>
+                  <el-button type="danger" icon="Delete" circle plain size="small" @click="removeHotWord(index)" />
+                </div>
+                <el-button type="primary" icon="Plus" link @click="addHotWord" class="add-hotword-btn">添加热词</el-button>
+              </div>
+            </el-form-item>
+
+            <el-form-item label="按钮图标:">
+              <UploadImage v-model="form.rightBtnIcon" :limit="1" width="32px" height="32px" />
+              <div class="field-tip">建议尺寸: 32*32, 格式: PNG/SVG</div>
+            </el-form-item>
+
+            <el-form-item label="按钮文字:">
+              <el-input v-model="form.rightBtnText" placeholder="如:购物车" maxlength="6" show-word-limit class="settings-input" />
+            </el-form-item>
+
+            <el-form-item label="跳转地址:">
+              <WebLinkInput v-model="form.rightBtnLink" placeholder="请输入跳转地址" class="settings-input"/>
+            </el-form-item>
+
+            <el-form-item label="主题色:">
+              <div class="color-picker-wrap">
+                <el-color-picker v-model="form.themeColor" />
+                <span class="color-val">{{ form.themeColor }}</span>
+              </div>
+            </el-form-item>
+          </el-form>
+        </div>
+      </div>
+
+      <!-- 广告图编辑区 -->
+      <div v-else-if="activeSubTab === 'carousel'" class="carousel-editor">
+        <!-- 模块一:左侧广告设置 -->
+        <div class="editor-section">
+          <div class="section-header">
+            <span class="section-title">模块一:左侧广告设置</span>
+            <span class="section-desc">尺寸要求:790 * 460,支持上传本地图片并设置跳转链接</span>
+          </div>
+          <div class="left-ad-container">
+            <div
+              class="left-ad-preview-wrapper"
+              :class="{ expanded: leftAdHover }"
+              @mouseenter="leftAdHover = true"
+              @mouseleave="leftAdHover = false"
+            >
+              <UploadImage
+                v-model="leftAdForm.leftAdImage"
+                :limit="1"
+                width="790px"
+                height="460px"
+                imageText="上传广告图"
+                @change="onLeftAdImageChange"
+              />
+            </div>
+
+            <div class="left-ad-settings" v-if="leftAdForm.leftAdImage">
+              <el-form label-width="90px" label-position="left">
+                <el-form-item label="跳转地址:">
+                  <WebLinkInput v-model="leftAdForm.leftAdLink" placeholder="请输入以 http:// 或 https:// 开头的地址" class="settings-input-ad"/>
+                </el-form-item>
+              </el-form>
+              <div class="left-ad-tip">
+                <el-icon><InfoFilled /></el-icon>
+                <span>鼠标悬停查看全图预览效果,点击在新窗口打开</span>
+              </div>
+            </div>
+          </div>
+        </div>
+
+        <!-- 模块二:轮播图管理 -->
+        <div class="editor-section">
+          <div class="section-header">
+            <span class="section-title">模块二:轮播图设置</span>
+            <span class="section-desc">尺寸要求:552 * 190,支持拖拽排序与实时状态切换</span>
+          </div>
+
+          <!-- 实时预览区 -->
+          <div class="carousel-preview-box">
+            <div class="preview-mockup">
+              <el-carousel height="190px" trigger="click" indicator-position="inside" arrow="never">
+                <el-carousel-item v-for="item in activeCarouselList" :key="item.id">
+                  <div class="carousel-slide">
+                    <img :src="item.image" alt="轮播图" />
+                  </div>
+                </el-carousel-item>
+                <div v-if="activeCarouselList.length === 0" class="carousel-empty">
+                  <el-icon :size="40"><Picture /></el-icon>
+                  <p>暂无启用的轮播图预览</p>
+                </div>
+              </el-carousel>
+            </div>
+          </div>
+
+          <!-- 列表管理区 -->
+          <div class="carousel-list-box">
+            <div class="list-toolbar">
+              <el-button type="primary" icon="Plus" @click="handleAddCarousel" class="btn-add-carousel">新增轮播图</el-button>
+              <span class="drag-tip">提示:列表支持状态切换及拖拽排序管理</span>
+            </div>
+
+            <el-table :data="carouselList" style="width: 100%" row-key="id" border header-cell-class-name="table-header-custom">
+              <el-table-column label="排序" width="70" align="center">
+                <template #default="{ $index }">
+                  <div class="rank-box">
+                    <el-icon v-if="$index > 0" class="rank-icon" @click="moveRow($index, -1)"><CaretTop /></el-icon>
+                    <el-icon v-if="$index < carouselList.length - 1" class="rank-icon" @click="moveRow($index, 1)"><CaretBottom /></el-icon>
+                  </div>
+                </template>
+              </el-table-column>
+              <el-table-column label="序号" type="index" width="60" align="center" />
+              <el-table-column label="图片" width="180">
+                <template #default="scope">
+                  <el-image :src="scope.row.image" :preview-src-list="[scope.row.image]" fit="cover" class="table-img" preview-teleported />
+                </template>
+              </el-table-column>
+              <el-table-column prop="link" label="跳转地址" show-overflow-tooltip />
+              <!-- <el-table-column label="打开方式" width="120" align="center">
+                <template #default="scope">
+                  <el-tag :type="scope.row.target === '_blank' ? 'success' : 'info'" size="small">
+                    {{ scope.row.target === '_blank' ? '新窗口' : '当前窗口' }}
+                  </el-tag>
+                </template>
+              </el-table-column> -->
+              <el-table-column label="状态" width="100" align="center">
+                <template #default="scope">
+                  <el-switch
+                    v-model="scope.row.status"
+                    :active-value="1"
+                    :inactive-value="0"
+                    @change="handleCarouselStatusChange(scope.row)"
+                    active-color="#13ce66"
+                  />
+                </template>
+              </el-table-column>
+              <el-table-column label="操作" width="150" align="center">
+                <template #default="scope">
+                  <el-button link type="primary" @click="handleEditCarousel(scope.row)">修改</el-button>
+                  <el-button link type="danger" @click="handleRemoveCarousel(scope.$index)">删除</el-button>
+                </template>
+              </el-table-column>
+            </el-table>
+          </div>
+        </div>
+      </div>
+
+      <!-- 快捷入口编辑区 -->
+      <div v-else-if="activeSubTab === 'quick-entry'" class="quick-entry-editor">
+        <div class="preview-section">
+          <div class="preview-title">快捷入口实时预览 (尺寸: 230 * 167)</div>
+          <div class="quick-entry-preview-outer">
+            <!-- 仿真预览模块 (230*167) -->
+            <div class="qe-mockup-card">
+              <div class="qe-card-header">
+                <span class="qe-card-title">{{ quickEntrySettings.moduleName }}</span>
+                <el-icon class="qe-header-arrow"><ArrowRight /></el-icon>
+              </div>
+
+              <div class="qe-grid-container">
+                <div class="qe-grid-wrapper" :style="{ transform: `translateX(-${qePageIndex * 198}px)` }">
+                  <!-- 网格页 -->
+                  <div v-for="pageIdx in qePageCount" :key="pageIdx" class="qe-grid-page">
+                    <div v-for="(item, idx) in getPageItems(pageIdx - 1)" :key="idx" class="qe-item">
+                      <div class="qe-icon-wrap">
+                        <img v-if="item.icon" :src="item.icon" class="qe-icon-img" />
+                        <el-icon v-else class="qe-icon-placeholder"><Menu /></el-icon>
+                        <div v-if="item.tag" class="qe-tag-bubble">{{ item.tag }}</div>
+                      </div>
+                      <span class="qe-name">{{ item.name }}</span>
+                    </div>
+                  </div>
+                </div>
+
+                <!-- 左右翻页按钮 (仅在多于8项时显示) -->
+                <div v-if="quickEntryList.length > 8" class="qe-nav-btns">
+                  <div v-if="qePageIndex > 0" class="qe-nav-btn prev" @click="qePageIndex--">
+                    <el-icon><ArrowLeft /></el-icon>
+                  </div>
+                  <div v-if="qePageIndex < qePageCount - 1" class="qe-nav-btn next" @click="qePageIndex++">
+                    <el-icon><ArrowRight /></el-icon>
+                  </div>
+                </div>
+              </div>
+            </div>
+          </div>
+        </div>
+
+        <!-- 列表设置 -->
+        <div class="editor-section">
+          <div class="section-header">
+            <span class="section-title">模块基础配置</span>
+          </div>
+          <div class="config-form-inline">
+            <el-form :inline="true" :model="quickEntrySettings" label-width="100px">
+              <el-form-item label="模块名称:">
+                <el-input v-model="quickEntrySettings.moduleName" placeholder="如:企业工作台" />
+              </el-form-item>
+              <el-form-item label="跳转地址:">
+                <WebLinkInput v-model="quickEntrySettings.jumpLink" placeholder="标题点击跳转地址" style="width: 300px"/>
+              </el-form-item>
+            </el-form>
+          </div>
+
+          <div class="section-header m-t-20">
+            <span class="section-title">入口项管理列表</span>
+          </div>
+          <div class="list-toolbar">
+            <div class="drag-tip">提示:一页显示8个,超过8个将自动启用预览区右滑功能</div>
+            <el-button type="primary" icon="Plus" @click="handleAddQuickEntry">新增入口</el-button>
+          </div>
+
+          <el-table :data="quickEntryList" border style="width: 100%" header-cell-class-name="table-header-custom">
+            <el-table-column label="排序" width="80" align="center">
+              <template #default="{ $index }">
+                <div class="rank-box">
+                  <el-icon v-if="$index > 0" class="rank-icon" @click="moveQE($index, -1)"><CaretTop /></el-icon>
+                  <el-icon v-if="$index < quickEntryList.length - 1" class="rank-icon" @click="moveQE($index, 1)"><CaretBottom /></el-icon>
+                </div>
+              </template>
+            </el-table-column>
+            <el-table-column label="图标" width="100" align="center">
+              <template #default="{ row }">
+                <div class="table-icon-cell">
+                  <img v-if="row.icon" :src="row.icon" style="width: 24px; height: 24px" />
+                  <el-icon v-else style="font-size: 20px; color: #ccc"><Menu /></el-icon>
+                </div>
+              </template>
+            </el-table-column>
+            <el-table-column prop="name" label="名称" width="150" />
+            <el-table-column label="标签" width="120" align="center">
+              <template #default="{ row }">
+                <el-tag v-if="row.tag" type="danger" size="small" effect="plain">{{ row.tag }}</el-tag>
+                <span v-else>-</span>
+              </template>
+            </el-table-column>
+            <el-table-column prop="link" label="跳转地址" show-overflow-tooltip />
+            <el-table-column label="状态" width="100" align="center">
+              <template #default="{ row }">
+                <el-switch v-model="row.status" :active-value="1" :inactive-value="0" @change="handleQuickEntryStatusChange(row)" />
+              </template>
+            </el-table-column>
+            <el-table-column label="操作" width="150" fixed="right" align="center">
+              <template #default="{ row, $index }">
+                <el-button type="primary" link @click="handleEditQuickEntry(row, $index)">编辑</el-button>
+                <el-button type="danger" link @click="handleDeleteQuickEntry($index)">删除</el-button>
+              </template>
+            </el-table-column>
+          </el-table>
+        </div>
+      </div>
+      <!-- 分类设置编辑区 -->
+      <div v-else-if="activeSubTab === 'category'" class="category-editor">
+        <!-- 模块一:实时预览 (高度还原图1、2) -->
+        <div class="editor-section">
+          <div class="section-header">
+            <span class="section-title">分类实时预览</span>
+            <span class="section-desc">尺寸要求:280 * 398,悬停可查看右滑面板效果 (图1、图2)</span>
+          </div>
+
+          <div class="category-preview-container">
+            <!-- 图1: 分类菜单 -->
+            <div class="category-menu-mockup">
+              <div v-for="item in categoryList.filter((c) => c.status === 1).slice(0, 10)" :key="item.id" class="menu-item">
+                <div class="menu-icon">
+                  <img v-if="item.icon" :src="item.icon" alt="" />
+                  <el-icon v-else><Menu /></el-icon>
+                </div>
+                <div class="menu-name">{{ item.name }}</div>
+
+                <!-- 图2: 悬停右滑面板 - CSS 控制显示 -->
+                <div class="category-panel-mockup">
+                  <div class="panel-header-line"></div>
+                  <div class="panel-content">
+                    <!-- 顶部标签栏 -->
+                    <div class="panel-tabs">
+                      <span v-for="tag in item.tags" :key="tag.name" class="panel-tab-item">{{ tag.name }}</span>
+                    </div>
+
+                    <!-- 品牌位 (图3) - 绝对定位至右上角 -->
+                    <div class="brand-box">
+                      <div class="brand-main-title">
+                        {{ item.panelData.mainTitle }}<span class="brand-strong">{{ item.panelData.subTitle }}</span>
+                      </div>
+                      <div class="brand-notes">
+                        <template v-for="(note, nIdx) in item.panelData.notes" :key="nIdx">
+                          <span class="note-item">{{ note.name }}</span>
+                          <span v-if="nIdx < item.panelData.notes.length - 1" class="note-sep">|</span>
+                        </template>
+                      </div>
+                    </div>
+
+                    <div class="panel-body">
+                      <!-- 左侧分类列表 -->
+                      <div class="panel-main">
+                        <div
+                          v-for="group in getSubCategoryGroups(item.syncCategoryId).length
+                            ? getSubCategoryGroups(item.syncCategoryId)
+                            : item.panelData.groups"
+                          :key="group.title"
+                          class="category-group"
+                        >
+                          <div class="group-title">{{ group.title }}</div>
+                          <div class="group-items">
+                            <span v-for="sub in group.items" :key="sub" class="group-item">{{ sub }}</span>
+                          </div>
+                        </div>
+                      </div>
+                    </div>
+                  </div>
+                </div>
+              </div>
+            </div>
+          </div>
+        </div>
+
+        <!-- 模块二:列表设置 -->
+        <div class="editor-section">
+          <div class="section-header">
+            <span class="section-title">分类列表设置</span>
+            <span class="section-desc">管理首页分类菜单的显示内容、图标、标签及关联分类</span>
+          </div>
+
+          <div class="list-toolbar">
+            <div class="theme-color-setting-pro">
+              <span class="label">主题色:</span>
+              <el-color-picker v-model="categoryThemeColor" />
+              <span class="value">{{ categoryThemeColor }}</span>
+            </div>
+            <el-button type="primary" icon="Plus" @click="handleAddCategory">新增分类</el-button>
+          </div>
+
+          <el-table :data="categoryList" border style="width: 100%" header-cell-class-name="table-header-custom">
+            <el-table-column label="排序" width="70" align="center">
+              <template #default="{ $index }">
+                <div class="rank-box">
+                  <el-icon v-if="$index > 0" class="rank-icon" @click="moveCategory($index, -1)"><CaretTop /></el-icon>
+                  <el-icon v-if="$index < categoryList.length - 1" class="rank-icon" @click="moveCategory($index, 1)"><CaretBottom /></el-icon>
+                </div>
+              </template>
+            </el-table-column>
+            <el-table-column label="菜单名称" prop="name" min-width="200" show-overflow-tooltip />
+            <el-table-column label="图标" width="80" align="center">
+              <template #default="scope">
+                <el-image v-if="scope.row.icon" :src="scope.row.icon" fit="contain" class="table-icon-preview" />
+                <span v-else class="text-gray">-</span>
+              </template>
+            </el-table-column>
+            <el-table-column label="同步分类" prop="syncCategory" width="120" />
+            <el-table-column label="标签" min-width="180">
+              <template #default="scope">
+                <div class="tag-wrap">
+                  <el-tag v-for="tag in scope.row.tags" :key="tag.name" size="small" class="m-r-5">{{ tag.name }}</el-tag>
+                </div>
+              </template>
+            </el-table-column>
+            <el-table-column label="状态" width="100" align="center">
+              <template #default="scope">
+                <el-switch
+                  v-model="scope.row.status"
+                  :active-value="1"
+                  :inactive-value="0"
+                  @change="handleCategoryStatusChange(scope.row)"
+                  active-color="#13ce66"
+                />
+              </template>
+            </el-table-column>
+            <el-table-column label="操作" width="150" align="center" fixed="right">
+              <template #default="scope">
+                <el-button link type="primary" @click="handleEditCategory(scope.row)">编辑</el-button>
+                <el-button link type="danger" @click="handleRemoveCategory(scope.$index)">删除</el-button>
+              </template>
+            </el-table-column>
+          </el-table>
+        </div>
+      </div>
+
+      <!-- 头部分类编辑区 -->
+      <div v-else-if="activeSubTab === 'headerCategory'" class="header-category-editor">
+        <!-- 实时预览区 (1350*60) -->
+        <div class="preview-section">
+          <div class="preview-title">头部分类实时预览</div>
+          <div class="header-preview-outer">
+            <div class="header-preview-box">
+              <!-- 左侧箭头 -->
+              <div v-show="showLeftArrow" class="nav-arrow left-arrow" @click="scrollHeaderNav('left')">
+                <el-icon><ArrowLeft /></el-icon>
+              </div>
+
+              <div class="header-nav-scroll" ref="headerNavScrollRef" @scroll="updateNavArrows">
+                <div class="header-nav-list">
+                  <div v-for="item in headerCategoryList.filter((i) => i.status === 1)" :key="item.id" class="header-nav-item">
+                    <div class="item-icon">
+                      <img v-if="item.icon" :src="item.icon" alt="" />
+                      <el-icon v-else :style="{ color: headerThemeColor }"><Menu /></el-icon>
+                    </div>
+                    <span class="item-text" :style="{ '--hover-color': headerThemeColor }">{{ item.title }}</span>
+                  </div>
+                </div>
+              </div>
+
+              <!-- 右侧箭头 -->
+              <div v-show="showRightArrow" class="nav-arrow right-arrow" @click="scrollHeaderNav('right')">
+                <el-icon><ArrowRight /></el-icon>
+              </div>
+            </div>
+          </div>
+          <div class="preview-desc">尺寸要求:1350 * 60,导航项横向排列,支持图标与主题色联动</div>
+        </div>
+
+        <!-- 列表设置 -->
+        <div class="editor-section">
+          <div class="section-header">
+            <span class="section-title">分类列表设置管理</span>
+          </div>
+
+          <div class="list-toolbar">
+            <div class="theme-color-setting-pro">
+              <span class="label">主题色设置:</span>
+              <el-color-picker v-model="headerThemeColor" />
+              <span class="value">{{ headerThemeColor }}</span>
+            </div>
+            <el-button type="primary" icon="Plus" @click="handleAddHeaderCategory">新增分类</el-button>
+          </div>
+
+          <el-table :data="headerCategoryList" border style="width: 100%" header-cell-class-name="table-header-custom">
+            <el-table-column label="排序" width="80" align="center">
+              <template #default="{ $index }">
+                <div class="rank-box">
+                  <el-icon v-if="$index > 0" class="rank-icon" @click="moveHeader($index, -1)"><CaretTop /></el-icon>
+                  <el-icon v-if="$index < headerCategoryList.length - 1" class="rank-icon" @click="moveHeader($index, 1)"><CaretBottom /></el-icon>
+                </div>
+              </template>
+            </el-table-column>
+            <el-table-column prop="title" label="分类标题名称" min-width="150" />
+            <el-table-column label="图标" width="100" align="center">
+              <template #default="{ row }">
+                <img v-if="row.icon" :src="row.icon" class="table-icon-preview" style="width: 22px; height: 22px" />
+                <span v-else>-</span>
+              </template>
+            </el-table-column>
+            <el-table-column prop="link" label="跳转地址" show-overflow-tooltip />
+            <el-table-column label="打开方式" width="120" align="center">
+              <template #default="{ row }">
+                <el-tag :type="row.openMode === 'new' ? 'success' : 'info'" size="small">
+                  {{ row.openMode === 'new' ? '新窗口' : '当前页' }}
+                </el-tag>
+              </template>
+            </el-table-column>
+            <el-table-column label="状态" width="100" align="center">
+              <template #default="{ row }">
+                <el-switch
+                  v-model="row.status"
+                  :active-value="1"
+                  :inactive-value="0"
+                  @change="handleHeaderCategoryStatusChange(row)"
+                  active-color="#13ce66"
+                />
+              </template>
+            </el-table-column>
+            <el-table-column label="操作" width="150" fixed="right" align="center">
+              <template #default="{ row, $index }">
+                <el-button type="primary" link @click="handleEditHeaderCategory(row, $index)">编辑</el-button>
+                <el-button type="danger" link @click="handleDeleteHeaderCategory($index)">删除</el-button>
+              </template>
+            </el-table-column>
+          </el-table>
+        </div>
+      </div>
+
+      <!-- 广告模块编辑区 -->
+      <div v-else-if="activeSubTab === 'ad-module'" class="ad-module-editor">
+        <div class="preview-section">
+          <div class="preview-title">广告模块实时预览</div>
+          <div class="ad-preview-grid">
+            <!-- 广告一: 企业购x百亿补贴 (484*190) -->
+            <div class="ad-item ad-subsidy" :style="{ width: '484px', height: '190px' }">
+              <div class="ad-header">
+                <div class="ad-title-main" :style="getAdTitleStyle(0, 'main')">{{ getAdTitleText(0, 'main') }}</div>
+                <div class="ad-title-sub" :style="getAdTitleStyle(0, 'sub')">{{ getAdTitleText(0, 'sub') }}</div>
+              </div>
+              <div class="ad-products-subsidy">
+                <div v-for="item in adModules[0].items" :key="item.id" class="product-item">
+                  <div class="product-img"><img :src="item.imageUrl" alt="" /></div>
+                  <div class="product-price">¥{{ item.price }}</div>
+                </div>
+              </div>
+              <div class="ad-hover-mask">
+                <el-button type="primary" size="small" @click="handleEditAd(0)">设置</el-button>
+              </div>
+            </div>
+
+            <!-- 广告二: 企采榜单 (253*196) -->
+            <div class="ad-item ad-ranking" :style="{ width: '253px', height: '196px' }">
+              <div class="ad-header">
+                <div class="ad-title-main" :style="getAdTitleStyle(1, 'main')">{{ getAdTitleText(1, 'main') }}</div>
+                <div class="ad-title-sub" :style="getAdTitleStyle(1, 'sub')">{{ getAdTitleText(1, 'sub') }}</div>
+              </div>
+              <div class="ad-products-ranking">
+                <div v-for="item in adModules[1].items" :key="item.id" class="ranking-item">
+                  <div class="ranking-badge">{{ item.tagText || '排行榜' }} ></div>
+                  <div class="product-img"><img :src="item.imageUrl" alt="" /></div>
+                  <div class="ranking-footer">已售{{ item.salesCount }}件</div>
+                </div>
+              </div>
+              <div class="ad-hover-mask">
+                <el-button type="primary" size="small" @click="handleEditAd(1)">设置</el-button>
+              </div>
+            </div>
+
+            <!-- 广告三: 品牌好店 (253*196) -->
+            <div class="ad-item ad-brand" :style="{ width: '253px', height: '196px' }">
+              <div class="ad-header">
+                <div class="ad-title-main" :style="getAdTitleStyle(2, 'main')">{{ getAdTitleText(2, 'main') }}</div>
+                <div class="ad-title-sub" :style="getAdTitleStyle(2, 'sub')">{{ getAdTitleText(2, 'sub') }}</div>
+              </div>
+              <div class="ad-brands-content">
+                <div v-for="item in adModules[2].items" :key="item.id" class="brand-item">
+                  <div class="brand-logo"><img :src="item.imageUrl" alt="" /></div>
+                  <div class="brand-name">{{ item.tagLink || item.productName }}</div>
+                  <div class="brand-tag-btn">{{ item.tagText || '品质保障' }}</div>
+                </div>
+              </div>
+              <div class="ad-hover-mask">
+                <el-button type="primary" size="small" @click="handleEditAd(2)">设置</el-button>
+              </div>
+            </div>
+
+            <!-- 广告四: 企业精选 (253*196) -->
+            <div class="ad-item ad-selection" :style="{ width: '253px', height: '196px' }">
+              <div class="ad-header">
+                <div class="ad-title-main" :style="getAdTitleStyle(3, 'main')">{{ getAdTitleText(3, 'main') }}</div>
+                <div class="ad-title-sub" :style="getAdTitleStyle(3, 'sub')">{{ getAdTitleText(3, 'sub') }}</div>
+              </div>
+              <div class="ad-products-selection">
+                <div v-for="item in adModules[3].items" :key="item.id" class="selection-item">
+                  <div class="product-img"><img :src="item.imageUrl" alt="" /></div>
+                  <div class="product-price-row">
+                    <span class="p-unit">¥</span>
+                    <span class="p-val">{{ item.price }}</span>
+                  </div>
+                </div>
+              </div>
+              <div class="ad-hover-mask">
+                <el-button type="primary" size="small" @click="handleEditAd(3)">设置</el-button>
+              </div>
+            </div>
+
+            <!-- 广告五: 企业购x京东新品 (253*196) -->
+            <div class="ad-item ad-new" :style="{ width: '253px', height: '196px' }">
+              <div class="ad-header">
+                <div class="ad-title-main" :style="getAdTitleStyle(4, 'main')">{{ getAdTitleText(4, 'main') }}</div>
+                <div class="ad-title-sub" :style="getAdTitleStyle(4, 'sub')">{{ getAdTitleText(4, 'sub') }}</div>
+              </div>
+              <div class="ad-products-selection">
+                <div v-for="item in adModules[4].items" :key="item.id" class="selection-item">
+                  <div class="product-img"><img :src="item.imageUrl" alt="" /></div>
+                  <div class="product-price-row center">
+                    <span class="p-unit">¥</span>
+                    <span class="p-val">{{ item.price }}</span>
+                  </div>
+                </div>
+              </div>
+              <div class="ad-hover-mask">
+                <el-button type="primary" size="small" @click="handleEditAd(4)">设置</el-button>
+              </div>
+            </div>
+          </div>
+          <div class="preview-desc">提示:支持 5 个独立广告模块配置,悬停模块显示“设置”按钮进行内容管理</div>
+        </div>
+      </div>
+
+      <!-- 场景方案编辑区 -->
+      <!-- 场景方案编辑区 -->
+      <div v-else-if="activeSubTab === 'scenario'" class="scenario-editor-container">
+        <!-- 实时预览区 -->
+        <div class="preview-section-standard">
+          <div class="section-title-standard">场景方案实时预览</div>
+          <div class="scenario-preview-outer">
+            <div class="scenario-preview-box-clean" :style="{ '--s-theme-color': scenarioSettings.themeColor }">
+              <!-- 左侧标题区 -->
+              <div class="scenario-header-left">
+                <div class="s-title-group">
+                  <span class="s-main-title">{{ scenarioSettings.mainTitle }}</span>
+                  <span class="s-sub-title-inline">{{ scenarioSettings.subTitle }}</span>
+                </div>
+                <div class="s-btn-wrap">
+                  <div class="s-btn-premium">
+                    {{ scenarioSettings.btnText }}
+                    <el-icon class="m-l-5"><CaretRight /></el-icon>
+                  </div>
+                </div>
+              </div>
+
+              <!-- 右侧方案卡片 -->
+              <div class="scenario-cards-wrap">
+                <div
+                  v-for="(item, idx) in scenarioList"
+                  :key="item.id"
+                  class="scenario-card-premium"
+                  :class="{ 'hidden-card-fourth': idx === 3 }"
+                  :style="{ backgroundColor: hexToRgba(item.bgColor, item.opacity) }"
+                >
+                  <div class="card-top-header">
+                    <div class="card-titles-group">
+                      <span class="card-main-title" :style="{ color: item.titleColor }">{{ item.title }}</span>
+                      <span class="card-sub-title" :style="{ color: item.subTitleColor }">{{ item.subTitle }}</span>
+                    </div>
+                    <div class="card-arrow-icon" :style="{ backgroundColor: item.titleColor }">
+                      <el-icon><ArrowRight /></el-icon>
+                    </div>
+                  </div>
+                  <div class="card-image-content">
+                    <img :src="item.image || defaultPlaceholder" alt="" />
+                  </div>
+                </div>
+              </div>
+            </div>
+          </div>
+          <div class="preview-tip">尺寸要求:1600 * 158,支持响应式隐藏及主题色联动</div>
+        </div>
+
+        <!-- 全局配置 -->
+        <div class="config-section-standard">
+          <div class="section-title-standard">场景解决方案全局设置</div>
+          <div class="settings-form-standard">
+            <el-form :model="scenarioSettings" label-width="120px" label-position="left">
+              <el-form-item label="场景主标题:">
+                <el-input v-model="scenarioSettings.mainTitle" placeholder="请输入主标题" style="width: 400px" />
+              </el-form-item>
+              <el-form-item label="场景副标题:">
+                <el-input v-model="scenarioSettings.subTitle" placeholder="请输入副标题" style="width: 400px" />
+              </el-form-item>
+              <el-form-item label="按钮文字:">
+                <el-input v-model="scenarioSettings.btnText" placeholder="请输入按钮文字" style="width: 400px" />
+              </el-form-item>
+              <el-form-item label="跳转链接:">
+                <WebLinkInput v-model="scenarioSettings.jumpLink" placeholder="请输入跳转地址" style="width: 400px"/>
+              </el-form-item>
+              <el-form-item label="主题背景色:">
+                <div class="theme-color-setting-pro">
+                  <el-color-picker v-model="scenarioSettings.themeColor" />
+                  <span class="value">{{ scenarioSettings.themeColor }}</span>
+                </div>
+              </el-form-item>
+            </el-form>
+          </div>
+        </div>
+
+        <!-- 列表管理 -->
+        <div class="config-section-standard">
+          <div class="section-title-standard">方案卡片设置管理</div>
+          <el-table :data="scenarioList" border style="width: 100%" header-cell-class-name="table-header-custom" class="standard-table">
+            <el-table-column label="排序" width="80" align="center">
+              <template #default="{ $index }">
+                <div class="rank-action-btns">
+                  <el-icon v-if="$index > 0" class="rank-btn-mini" @click="moveScenario($index, -1)"><ArrowUp /></el-icon>
+                  <el-icon v-if="$index < scenarioList.length - 1" class="rank-btn-mini" @click="moveScenario($index, 1)"><ArrowDown /></el-icon>
+                </div>
+              </template>
+            </el-table-column>
+            <el-table-column label="分类方案名称" min-width="250">
+              <template #default="{ row }">
+                <div class="flex-column">
+                  <span :style="{ color: row.titleColor, fontSize: '14px', fontWeight: 'bold' }">{{ row.title }}</span>
+                  <span class="text-gray" style="font-size: 12px; margin-top: 4px">{{ row.subTitle }}</span>
+                </div>
+              </template>
+            </el-table-column>
+            <el-table-column label="图标/封面图" width="200" align="center">
+              <template #default="{ row }">
+                <el-image :src="row.image" fit="cover" style="width: 160px; height: 48px; border-radius: 4px" />
+              </template>
+            </el-table-column>
+            <el-table-column prop="link" label="跳转链接" min-width="300" show-overflow-tooltip />
+            <el-table-column label="操作" width="120" align="center" fixed="right">
+              <template #default="{ row, $index }">
+                <el-button type="primary" link @click="handleEditScenario(row, $index)">编辑内容</el-button>
+              </template>
+            </el-table-column>
+          </el-table>
+        </div>
+      </div>
+
+      <!-- 推荐设置编辑区 -->
+      <div v-else-if="activeSubTab === 'recommend'" class="recommend-editor-container">
+        <!-- 实时预览区 -->
+        <div class="preview-section-standard">
+          <div class="section-title-standard">为你推荐实时预览</div>
+          <div class="recommend-preview-outer">
+            <div class="recommend-preview-container">
+              <!-- 左侧箭头 -->
+              <div v-if="recShowLeft" class="recommend-nav-btn prev" @click="scrollRecommend('left')">
+                <el-icon><CaretLeft /></el-icon>
+              </div>
+
+              <div
+                ref="recommendScrollRef"
+                class="recommend-preview-box"
+                :style="{ '--r-theme-color': recommendThemeColor }"
+                @scroll="updateRecArrows"
+              >
+                <div
+                  v-for="item in activeRecommendList"
+                  :key="item.id"
+                  :class="['recommend-item', { active: recommendActiveId === item.id }]"
+                  @click="recommendActiveId = item.id"
+                >
+                  <div class="recommend-icon">
+                    <img :src="item.icon || defaultPlaceholder" alt="" />
+                  </div>
+                  <div class="recommend-text">
+                    <div class="r-main-title" :style="{ color: recommendActiveId === item.id ? recommendThemeColor : '#333' }">{{ item.name }}</div>
+                    <div
+                      class="r-sub-title"
+                      :style="{
+                        color: recommendActiveId === item.id ? recommendThemeColor : '#999',
+                        opacity: recommendActiveId === item.id ? 0.8 : 1
+                      }"
+                    >
+                      {{ item.subTitle }}
+                    </div>
+                  </div>
+                </div>
+              </div>
+
+              <!-- 右侧箭头 -->
+              <div v-if="recShowRight" class="recommend-nav-btn next" @click="scrollRecommend('right')">
+                <el-icon><CaretRight /></el-icon>
+              </div>
+            </div>
+          </div>
+          <div class="preview-desc">尺寸要求:1600 * 88,分类图标 32 * 32,支持响应式平滑滚动及选中态色值联动</div>
+        </div>
+
+        <!-- 配置表单区域 -->
+        <div class="config-section-standard">
+          <div class="section-title-standard">为你推荐配置设置</div>
+          <el-form label-width="120px" inline class="m-b-20">
+            <el-form-item label="主题选中色调:">
+              <div class="theme-color-setting-pro">
+                <el-color-picker v-model="recommendThemeColor" />
+                <span class="value">{{ recommendThemeColor }}</span>
+              </div>
+            </el-form-item>
+            <el-form-item label="商品列表主题色:">
+              <div class="theme-color-setting-pro">
+                <el-color-picker v-model="recommendProductThemeColor" />
+                <span class="value">{{ recommendProductThemeColor }}</span>
+              </div>
+            </el-form-item>
+            <el-form-item>
+              <el-button type="primary" icon="Plus" @click="handleAddRecommend">新增推荐分类</el-button>
+            </el-form-item>
+          </el-form>
+
+          <el-table :data="recommendList" border style="width: 100%" header-cell-class-name="table-header-custom" class="standard-table">
+            <el-table-column label="位置" width="80" align="center">
+              <template #default="{ $index }">
+                <div class="rank-action-btns">
+                  <el-icon v-if="$index > 0" class="rank-btn-mini" @click="moveRecommend($index, -1)"><ArrowUp /></el-icon>
+                  <el-icon v-if="$index < recommendList.length - 1" class="rank-btn-mini" @click="moveRecommend($index, 1)"><ArrowDown /></el-icon>
+                </div>
+              </template>
+            </el-table-column>
+            <el-table-column label="分类名称" min-width="120">
+              <template #default="{ row }">
+                <span style="font-weight: bold; color: #333">{{ row.name }}</span>
+              </template>
+            </el-table-column>
+            <el-table-column prop="subTitle" label="副标题" min-width="150" />
+            <el-table-column label="图标" width="100" align="center">
+              <template #default="{ row }">
+                <div class="table-icon-preview">
+                  <img :src="row.icon || defaultPlaceholder" class="row-icon" />
+                </div>
+              </template>
+            </el-table-column>
+            <el-table-column label="数据类型" width="120" align="center">
+              <template #default="{ row }">
+                <el-tag :type="row.type === 'category' ? 'success' : 'warning'">
+                  {{ row.type === 'category' ? '分类映射' : '商品自选' }}
+                </el-tag>
+              </template>
+            </el-table-column>
+            <el-table-column label="关联类目/商品数" min-width="180">
+              <template #default="{ row }">
+                <div v-if="row.type === 'category'" class="text-gray">{{ row.categoryLabel || '未设置' }}</div>
+                <div v-else class="text-gray">已选 {{ row.selectedProducts?.length || 0 }} 个商品</div>
+              </template>
+            </el-table-column>
+            <el-table-column label="启用状态" width="100" align="center">
+              <template #default="{ row }">
+                <el-switch
+                  v-model="row.status"
+                  :active-value="1"
+                  :inactive-value="0"
+                  @change="handleRecommendStatusChange(row)"
+                  active-color="#13ce66"
+                />
+              </template>
+            </el-table-column>
+            <el-table-column label="操作" width="220" align="center" fixed="right">
+              <template #default="{ row, $index }">
+                <el-button type="primary" link @click="handleEditRecommend(row, $index)">编辑</el-button>
+                <el-button v-if="row.type === 'select'" type="primary" link @click="openRecommendProductSelect($index)">选商品</el-button>
+                <el-button type="danger" link @click="handleDeleteRecommend($index)">删除</el-button>
+              </template>
+            </el-table-column>
+          </el-table>
+        </div>
+      </div>
+    </div>
+
+    <!-- 广告模块设置弹窗 -->
+    <el-dialog
+      v-model="adDialogVisible"
+      :title="`设置广告模块 - ${adModules[currentAdIdx]?.title}`"
+      width="1050px"
+      custom-class="ad-setup-dialog"
+      destroy-on-close
+    >
+      <el-form :model="adForm" label-width="100px" class="dialog-form-inner">
+        <el-form-item label="模块标题:">
+          <div class="theme-color-setting-pro">
+            <el-input v-model="adForm.title" placeholder="请输入主标题" style="width: 300px" />
+            <el-color-picker v-model="adForm.titleColor" />
+            <span class="value">{{ adForm.titleColor }}</span>
+          </div>
+        </el-form-item>
+        <el-form-item label="副标题:">
+          <div class="theme-color-setting-pro">
+            <el-input v-model="adForm.subTitle" placeholder="请输入副标题" style="width: 300px" />
+            <el-color-picker v-model="adForm.subTitleColor" />
+            <span class="value">{{ adForm.subTitleColor }}</span>
+          </div>
+        </el-form-item>
+
+        <div class="config-subtitle">商品/品牌列表 (固定 {{ adForm.items.length }} 项)</div>
+        <el-table :data="adForm.items" border style="width: 100%" class="m-t-10">
+          <el-table-column label="位置" width="60" align="center">
+            <template #default="scope">{{ scope.$index + 1 }}</template>
+          </el-table-column>
+          <el-table-column label="图片" width="100" align="center">
+            <template #default="{ row }">
+              <img :src="row.imageUrl" style="width: 50px; height: 50px; object-fit: contain" />
+            </template>
+          </el-table-column>
+          <!-- 通用商品信息列 (百亿补贴、企采精选、京东新品) -->
+          <el-table-column v-if="currentAdIdx === 0 || currentAdIdx === 3 || currentAdIdx === 4" label="商品信息" min-width="180">
+            <template #default="{ row }">
+              <div class="table-info-cell">
+                <div class="info-name">{{ row.productName || '未选择' }}</div>
+                <div class="info-price">价格:¥{{ row.price }}</div>
+                <div class="info-id">ID: {{ row.id || '-' }}</div>
+              </div>
+            </template>
+          </el-table-column>
+
+          <!-- 企采榜单 专属列 -->
+          <template v-if="currentAdIdx === 1">
+            <el-table-column label="商品信息" min-width="150">
+              <template #default="{ row }">
+                <div class="info-name">{{ row.productName || '未选择' }}</div>
+                <div class="info-id">ID: {{ row.id || '-' }}</div>
+              </template>
+            </el-table-column>
+            <el-table-column label="排行标签" width="220">
+              <template #default="{ row }">
+                <el-input v-model="row.tagText" placeholder="标签文字" size="small" class="m-b-5" />
+                <WebLinkInput v-model="row.tagLink" placeholder="跳转链接" size="small"/>
+              </template>
+            </el-table-column>
+            <el-table-column label="销量数据" width="130">
+              <template #default="{ row }">
+                <el-input v-model="row.salesCount" placeholder="销量" size="small">
+                  <template #prepend>已售</template>
+                </el-input>
+              </template>
+            </el-table-column>
+          </template>
+
+          <!-- 品牌好店 专属列 -->
+          <template v-if="currentAdIdx === 2">
+            <el-table-column label="品牌名称" min-width="180">
+              <template #default="{ row }">
+                <div class="brand-name-display">{{ row.productName || '未选择' }}</div>
+                <div class="info-id m-t-5">ID: {{ row.id || '-' }}</div>
+              </template>
+            </el-table-column>
+            <el-table-column label="品牌标签" width="300">
+              <template #default="{ row }">
+                <div class="flex-column gap-10">
+                  <el-input v-model="row.tagText" placeholder="标签文字 (如: 品质保障)" />
+                  <el-input v-model="row.tagLink" placeholder="描述文字 (控制预览品牌名称)" />
+                </div>
+              </template>
+            </el-table-column>
+          </template>
+
+          <el-table-column label="操作" width="110" align="center" fixed="right">
+            <template #default="scope">
+              <el-button type="primary" link @click="openProductSelect(scope.$index)">
+                <el-icon class="m-r-5"><Edit /></el-icon>
+                {{ scope.row?.id ? '修改' : '选择' }}
+              </el-button>
+            </template>
+          </el-table-column>
+        </el-table>
+      </el-form>
+      <template #footer>
+        <el-button @click="adDialogVisible = false">取消</el-button>
+        <el-button type="primary" @click="submitAdForm">保存设置</el-button>
+      </template>
+    </el-dialog>
+
+    <!-- 选择商品/品牌抽屉 (加宽版) -->
+    <el-drawer v-model="selectDialogVisible" :title="currentAdIdx === 2 ? '选择品牌' : '选择商品'" size="850px" append-to-body>
+      <div class="select-dialog-content">
+        <el-input
+          v-model="productQueryParams.itemName"
+          :placeholder="currentAdIdx === 2 ? '输入品牌名称搜索' : '输入商品名称或ID搜索'"
+          prefix-icon="Search"
+          clearable
+          style="margin-bottom: 20px"
+          @input="getProductList"
+        />
+        <div class="select-list">
+          <div
+            v-for="item in pagedSelectList"
+            :key="item.id"
+            class="select-item-row"
+            :class="{ active: selectedTempId === item.id }"
+            @click="selectedTempId = item.id"
+          >
+            <img :src="item.image" class="select-item-img" />
+            <div class="select-item-info">
+              <div class="select-item-name">{{ item.name }}</div>
+              <div v-if="currentAdIdx !== 2" class="select-item-price">¥{{ item.price }}</div>
+              <div class="select-item-id">ID: {{ item.id }}</div>
+            </div>
+            <el-radio v-model="selectedTempId" :label="item.id"><span></span></el-radio>
+          </div>
+        </div>
+
+        <!-- 分页组件 -->
+        <div class="select-pagination">
+          <el-pagination
+            v-model:current-page="selectCurrentPage"
+            v-model:page-size="selectPageSize"
+            :total="productTotal"
+            :page-sizes="[8, 12, 20, 50]"
+            layout="total, sizes, prev, pager, next, jumper"
+            background
+            @current-change="onProductPageChange"
+            @size-change="onProductPageSizeChange"
+          />
+        </div>
+      </div>
+      <template #footer>
+        <div style="flex: auto; text-align: right; padding: 20px">
+          <el-button @click="selectDialogVisible = false">取消</el-button>
+          <el-button type="primary" @click="confirmSelect">确定选择</el-button>
+        </div>
+      </template>
+    </el-drawer>
+
+    <!-- 轮播图编辑弹窗 -->
+    <el-dialog v-model="dialogVisible" :title="dialogType === 'add' ? '新增轮播图' : '修改轮播图'" width="800px" destroy-on-close>
+      <el-form :model="carouselForm" label-width="100px" class="dialog-form-inner">
+        <el-form-item label="轮播图片:">
+          <UploadImage v-model="carouselForm.image" :limit="1" width="552px" height="190px" />
+          <div class="upload-tip">推荐尺寸:552 * 190,支持上传本地图片</div>
+        </el-form-item>
+        <el-form-item label="跳转地址:">
+          <WebLinkInput v-model="carouselForm.link" placeholder="请输入或选择链接" />
+        </el-form-item>
+        <!-- <el-form-item label="跳转地址:">
+          <el-input v-model="carouselForm.link" placeholder="请输入以 http:// 或 https:// 开头的地址" />
+        </el-form-item>
+        <el-form-item label="打开方式:">
+          <el-radio-group v-model="carouselForm.target">
+            <el-radio label="_self">当前窗口</el-radio>
+            <el-radio label="_blank">新窗口</el-radio>
+          </el-radio-group>
+        </el-form-item> -->
+        <el-form-item label="启用状态:">
+          <el-switch v-model="carouselForm.status" :active-value="1" :inactive-value="0" active-color="#13ce66" />
+        </el-form-item>
+      </el-form>
+      <template #footer>
+        <el-button @click="dialogVisible = false">取消</el-button>
+        <el-button type="primary" @click="submitCarouselForm">确定</el-button>
+      </template>
+    </el-dialog>
+
+    <!-- 分类设置编辑弹窗 -->
+    <el-dialog v-model="categoryDialogVisible" :title="categoryDialogType === 'add' ? '新增分类' : '修改分类'" width="900px" destroy-on-close>
+      <el-form :model="categoryForm" label-width="100px" class="dialog-form-inner">
+        <el-tabs type="border-card">
+          <el-tab-pane label="基础设置">
+            <el-form-item label="菜单名称:">
+              <el-input v-model="categoryForm.name" placeholder="如:办公电脑 / 办公打印 / 电脑组件" />
+            </el-form-item>
+            <el-form-item label="图标:">
+              <UploadImage v-model="categoryForm.icon" :limit="1" width="48px" height="48px" />
+              <div class="upload-tip">推荐尺寸:16 * 16,支持上传透明背景 PNG</div>
+            </el-form-item>
+            <el-form-item label="同步分类:">
+              <el-select
+                v-model="categoryForm.syncCategoryId"
+                placeholder="请选择或输入关联的一级分类"
+                style="width: 100%"
+                filterable
+                allow-create
+                default-first-option
+                @change="onSyncCategoryChange"
+              >
+                <el-option v-for="opt in syncCategoryOptions" :key="opt.value" :label="opt.label" :value="opt.value" />
+              </el-select>
+            </el-form-item>
+            <el-form-item label="标签设置:">
+              <div class="notes-config-list">
+                <div v-for="(tag, index) in categoryForm.tags" :key="index" class="note-config-row">
+                  <el-input v-model="tag.name" placeholder="标签名称" style="width: 120px" />
+                  <WebLinkInput v-model="tag.link" placeholder="跳转地址" style="flex: 1"/>
+                  <el-button type="danger" icon="Delete" circle plain size="small" @click="removeCategoryTag(index)" />
+                </div>
+                <el-button type="primary" icon="Plus" link @click="addCategoryTag">添加标签</el-button>
+              </div>
+              <div class="field-tip">用于在右滑面板顶部显示的业务标签,支持配置独立跳转地址</div>
+            </el-form-item>
+            <el-form-item label="启用状态:">
+              <el-switch v-model="categoryForm.status" :active-value="1" :inactive-value="0" active-color="#13ce66" />
+            </el-form-item>
+          </el-tab-pane>
+
+          <el-tab-pane label="右滑面板配置">
+            <div class="panel-config-section">
+              <div class="config-subtitle">品牌位设置 (图3)</div>
+              <el-row :gutter="20">
+                <el-col :span="12">
+                  <el-form-item label="主标题:">
+                    <el-input v-model="categoryForm.panelData.mainTitle" placeholder="如:京东" />
+                  </el-form-item>
+                </el-col>
+                <el-col :span="12">
+                  <el-form-item label="副标题:">
+                    <el-input v-model="categoryForm.panelData.subTitle" placeholder="如:3C数码" />
+                  </el-form-item>
+                </el-col>
+              </el-row>
+
+              <el-form-item label="便签列表:">
+                <div class="notes-config-list">
+                  <div v-for="(note, index) in categoryForm.panelData.notes" :key="index" class="note-config-row">
+                    <el-input v-model="note.name" placeholder="便签名称" style="width: 120px" />
+                    <WebLinkInput v-model="note.link" placeholder="跳转地址" style="flex: 1" />
+                    <el-button type="danger" icon="Delete" circle plain size="small" @click="removePanelNote(index)" />
+                  </div>
+                  <el-button type="primary" icon="Plus" link @click="addPanelNote">添加便签</el-button>
+                </div>
+              </el-form-item>
+            </div>
+
+            <div class="panel-config-section m-t-20">
+              <div class="config-subtitle">分类分组设置</div>
+              <div class="field-tip">目前支持在代码中静态配置,弹窗界面待进一步完善。</div>
+            </div>
+          </el-tab-pane>
+        </el-tabs>
+      </el-form>
+      <template #footer>
+        <el-button @click="categoryDialogVisible = false">取消</el-button>
+        <el-button type="primary" @click="submitCategoryForm">确定保存</el-button>
+      </template>
+    </el-dialog>
+
+    <!-- 头部分类编辑弹窗 -->
+    <el-dialog v-model="headerDialogVisible" :title="headerEditIndex > -1 ? '编辑头部分类' : '新增头部分类'" width="600px" destroy-on-close>
+      <el-form :model="headerForm" label-width="100px" class="dialog-form-inner">
+        <el-form-item label="分类名称:">
+          <el-input v-model="headerForm.title" placeholder="如:公共采购" />
+        </el-form-item>
+        <el-form-item label="分类图标:">
+          <UploadImage v-model="headerForm.icon" :limit="1" width="48px" height="48px" />
+          <div class="upload-tip">建议尺寸:20*20,透明背景 PNG</div>
+        </el-form-item>
+        <el-form-item label="跳转地址:">
+          <WebLinkInput v-model="headerForm.link" placeholder="请输入跳转链接" />
+        </el-form-item>
+        <el-form-item label="打开方式:">
+          <el-radio-group v-model="headerForm.openMode">
+            <el-radio label="current">当前页</el-radio>
+            <el-radio label="new">新窗口</el-radio>
+          </el-radio-group>
+        </el-form-item>
+        <el-form-item label="启用状态:">
+          <el-switch v-model="headerForm.status" :active-value="1" :inactive-value="0" active-color="#13ce66" />
+        </el-form-item>
+      </el-form>
+      <template #footer>
+        <el-button @click="headerDialogVisible = false">取消</el-button>
+        <el-button type="primary" @click="submitHeaderForm">确定保存</el-button>
+      </template>
+    </el-dialog>
+    <!-- 场景方案编辑弹窗 -->
+
+    <!-- 已选商品管理抽屉 (占据 45% 屏幕) -->
+    <el-drawer
+      v-model="selectedProductDialogVisible"
+      :title="`已选商品管理 - ${recommendList[currentRecommendIndex]?.name}`"
+      size="45%"
+      direction="rtl"
+      custom-class="selected-products-drawer"
+      destroy-on-close
+    >
+      <div class="drawer-content-wrapper">
+        <div class="selected-products-header">
+          <div class="left-actions">
+            <el-button type="primary" icon="Plus" @click="openProductDrawer">添加商品</el-button>
+            <el-button
+              type="danger"
+              plain
+              icon="Delete"
+              :disabled="!recommendList[currentRecommendIndex]?.selectedProducts?.length"
+              @click="batchRemoveSelectedProducts"
+              >批量移除</el-button
+            >
+          </div>
+          <div class="right-info">
+            共 <span class="count">{{ recommendList[currentRecommendIndex]?.selectedProducts?.length || 0 }}</span> 个商品
+          </div>
+        </div>
+
+        <el-table
+          ref="selectedProductsTableRef"
+          :data="pagedSelectedProducts"
+          border
+          height="calc(100vh - 280px)"
+          style="width: 100%"
+          header-cell-class-name="table-header-custom"
+          class="standard-table"
+        >
+          <el-table-column type="selection" width="55" align="center" />
+          <el-table-column label="商品图片" width="100" align="center">
+            <template #default="{ row }">
+              <el-image :src="row.image" style="width: 50px; height: 50px; border-radius: 4px; border: 1px solid #f0f0f0" fit="contain" />
+            </template>
+          </el-table-column>
+          <el-table-column prop="name" label="商品名称" min-width="250" show-overflow-tooltip />
+          <el-table-column prop="id" label="商品ID" width="120" align="center" />
+          <el-table-column label="单价" width="120" align="center">
+            <template #default="{ row }">
+              <span class="price-text">¥{{ row.price }}</span>
+            </template>
+          </el-table-column>
+          <el-table-column label="操作" width="100" align="center" fixed="right">
+            <template #default="{ $index }">
+              <el-button type="danger" link @click="removeSelectedProduct($index)">移除</el-button>
+            </template>
+          </el-table-column>
+
+          <template #empty>
+            <div class="empty-placeholder">
+              <el-empty description="暂未添加任何商品" :image-size="160">
+                <el-button type="primary" size="large" @click="openProductDrawer">去添加商品</el-button>
+              </el-empty>
+            </div>
+          </template>
+        </el-table>
+
+        <div class="drawer-pagination">
+          <el-pagination
+            v-model:current-page="selectedCurrentPage"
+            v-model:page-size="selectedPageSize"
+            :total="recommendList[currentRecommendIndex]?.selectedProducts?.length || 0"
+            :page-sizes="[10, 20, 50, 100]"
+            layout="total, sizes, prev, pager, next, jumper"
+            background
+          />
+        </div>
+      </div>
+
+      <template #footer>
+        <div class="drawer-footer-actions">
+          <el-button @click="selectedProductDialogVisible = false">取消</el-button>
+          <el-button type="primary" @click="submitSelectedProducts">确定保存</el-button>
+        </div>
+      </template>
+    </el-drawer>
+
+    <!-- 商品多选选择抽屉 (占据 1/3 屏幕) -->
+    <el-drawer
+      v-model="productSelectionDrawerVisible"
+      title="从商品库选择商品"
+      size="45%"
+      direction="rtl"
+      destroy-on-close
+      custom-class="product-selection-drawer"
+    >
+      <div class="drawer-content-wrapper">
+        <div class="drawer-search-bar">
+          <el-input v-model="productQueryParams.itemName" placeholder="输入商品名称或ID搜索" prefix-icon="Search" clearable @input="getProductList" />
+        </div>
+
+        <div class="drawer-stat-bar">
+          <el-icon class="info-icon"><InfoFilled /></el-icon>
+          <span
+            >已勾选 <span class="highlight">{{ drawerSelection.length }}</span> 个商品</span
+          >
+        </div>
+
+        <el-table
+          ref="drawerTableRef"
+          :data="pagedSelectList"
+          border
+          height="calc(100vh - 280px)"
+          style="width: 100%"
+          row-key="id"
+          @selection-change="handleDrawerSelectionChange"
+        >
+          <el-table-column type="selection" width="50" align="center" :reserve-selection="true" />
+          <el-table-column label="商品信息" min-width="200">
+            <template #default="{ row }">
+              <div class="drawer-product-info">
+                <el-image :src="row.image" class="mini-img" fit="contain" />
+                <div class="detail">
+                  <div class="name">{{ row.name }}</div>
+                  <div class="id">ID: {{ row.id }}</div>
+                  <div class="price">¥{{ row.price }}</div>
+                </div>
+              </div>
+            </template>
+          </el-table-column>
+        </el-table>
+
+        <div class="drawer-pagination">
+          <el-pagination
+            v-model:current-page="selectCurrentPage"
+            v-model:page-size="selectPageSize"
+            :total="productTotal"
+            :page-sizes="[10, 20, 50, 100]"
+            layout="total, sizes, prev, pager, next, jumper"
+            background
+            @current-change="onProductPageChange"
+            @size-change="onProductPageSizeChange"
+          />
+        </div>
+      </div>
+
+      <template #footer>
+        <div class="drawer-footer-actions">
+          <el-button @click="productSelectionDrawerVisible = false">取消</el-button>
+          <el-button type="primary" :disabled="!drawerSelection.length" @click="confirmDrawerSelection"
+            >确认添加 ({{ drawerSelection.length }})</el-button
+          >
+        </div>
+      </template>
+    </el-drawer>
+    <el-dialog v-model="scenarioDialogVisible" title="编辑场景方案卡片" width="650px" destroy-on-close>
+      <el-form :model="scenarioForm" label-width="100px" class="dialog-form-inner">
+        <el-form-item label="主标题:">
+          <div class="flex-center gap-10">
+            <el-input v-model="scenarioForm.title" style="width: 200px" />
+            <div class="theme-color-setting-pro">
+              <el-color-picker v-model="scenarioForm.titleColor" />
+              <span class="value">{{ scenarioForm.titleColor }}</span>
+            </div>
+          </div>
+        </el-form-item>
+        <el-form-item label="副标题:">
+          <div class="flex-center gap-10">
+            <el-input v-model="scenarioForm.subTitle" style="width: 200px" />
+            <div class="theme-color-setting-pro">
+              <el-color-picker v-model="scenarioForm.subTitleColor" />
+              <span class="value">{{ scenarioForm.subTitleColor }}</span>
+            </div>
+          </div>
+        </el-form-item>
+        <el-form-item label="卡片背景:">
+          <div class="flex-center gap-10">
+            <div class="theme-color-setting-pro">
+              <el-color-picker v-model="scenarioForm.bgColor" />
+              <span class="value">{{ scenarioForm.bgColor }}</span>
+            </div>
+            <span class="text-gray m-l-10">不透明度:</span>
+            <el-slider v-model="scenarioForm.opacity" :min="0" :max="1" :step="0.05" style="width: 120px" />
+            <span class="value m-l-5">{{ Math.round(scenarioForm.opacity * 100) }}%</span>
+          </div>
+        </el-form-item>
+        <el-form-item label="封面图片:">
+          <UploadImage v-model="scenarioForm.image" :limit="1" width="272px" height="80px" />
+          <div class="upload-tip">建议尺寸:272 * 80</div>
+        </el-form-item>
+        <el-form-item label="跳转链接:">
+          <WebLinkInput v-model="scenarioForm.link" placeholder="请输入跳转地址" />
+        </el-form-item>
+      </el-form>
+      <template #footer>
+        <el-button @click="scenarioDialogVisible = false">取消</el-button>
+        <el-button type="primary" @click="submitScenarioForm">保存修改</el-button>
+      </template>
+    </el-dialog>
+
+    <!-- 推荐设置编辑弹窗 -->
+    <el-dialog v-model="recommendDialogVisible" :title="recommendEditIndex > -1 ? '修改推荐分类' : '新增推荐分类'" width="600px" destroy-on-close>
+      <el-form :model="recommendForm" label-width="110px" class="dialog-form-inner">
+        <el-form-item label="主标题:">
+          <el-input v-model="recommendForm.name" placeholder="请输入分类名称" maxlength="6" show-word-limit />
+        </el-form-item>
+        <el-form-item label="副标题:">
+          <el-input v-model="recommendForm.subTitle" placeholder="请输入副标题" maxlength="12" show-word-limit />
+        </el-form-item>
+        <el-form-item label="分类图标:">
+          <UploadImage v-model="recommendForm.icon" :limit="1" width="64px" height="64px" />
+          <div class="upload-tip">建议尺寸:32 * 32,透明背景 PNG</div>
+        </el-form-item>
+        <el-form-item label="数据类型:">
+          <el-radio-group v-model="recommendForm.type">
+            <el-radio label="select">商品自选</el-radio>
+            <el-radio label="category">分类映射</el-radio>
+          </el-radio-group>
+        </el-form-item>
+
+        <el-form-item v-if="recommendForm.type === 'category'" label="映射分类:">
+          <el-cascader
+            v-model="recommendForm.categoryValue"
+            :options="categoryOptions"
+            :props="{ value: 'id', label: 'label', children: 'children', checkStrictly: true, expandTrigger: 'hover' }"
+            placeholder="请选择一级/二级/三级分类"
+            style="width: 100%"
+            filterable
+            clearable
+            @change="handleRecommendCategoryChange"
+          />
+          <div class="field-tip">选择后,该页签将自动抓取对应分类下的最新商品</div>
+        </el-form-item>
+
+        <el-form-item label="启用状态:">
+          <el-switch v-model="recommendForm.status" :active-value="1" :inactive-value="0" active-color="#13ce66" />
+        </el-form-item>
+      </el-form>
+      <template #footer>
+        <el-button @click="recommendDialogVisible = false">取消</el-button>
+        <el-button type="primary" @click="submitRecommendForm">保存设置</el-button>
+      </template>
+    </el-dialog>
+
+    <!-- 快捷入口编辑弹窗 -->
+    <el-dialog
+      v-model="quickEntryDialogVisible"
+      :title="quickEntryDialogType === 'add' ? '新增快捷入口' : '编辑快捷入口'"
+      width="600px"
+      append-to-body
+      destroy-on-close
+    >
+      <el-form :model="quickEntryForm" label-width="100px" class="dialog-form-inner">
+        <el-form-item label="入口名称:">
+          <el-input v-model="quickEntryForm.name" placeholder="请输入入口名称(建议4-5个字)" maxlength="6" show-word-limit />
+        </el-form-item>
+        <el-form-item label="图标:">
+          <UploadImage v-model="quickEntryForm.icon" :limit="1" width="48px" height="48px" />
+          <div class="upload-tip">建议尺寸: 48*48, 格式: PNG/SVG</div>
+        </el-form-item>
+        <el-form-item label="标签文字:">
+          <el-input v-model="quickEntryForm.tag" placeholder="如:返100" maxlength="4" style="width: 200px" />
+          <div class="field-tip">显示在图标右上角的红色气泡,留空则不显示</div>
+        </el-form-item>
+        <el-form-item label="跳转地址:">
+          <WebLinkInput v-model="quickEntryForm.link" placeholder="请输入跳转地址" />
+        </el-form-item>
+        <el-form-item label="启用状态:">
+          <el-switch v-model="quickEntryForm.status" :active-value="1" :inactive-value="0" />
+        </el-form-item>
+      </el-form>
+      <template #footer>
+        <el-button @click="quickEntryDialogVisible = false">取消</el-button>
+        <el-button type="primary" @click="submitQuickEntryForm">确定</el-button>
+      </template>
+    </el-dialog>
+
+    <!-- 底部固定操作栏 -->
+    <div class="footer-actions">
+      <el-button type="primary" class="btn-confirm" @click="handleMainSave">保存当前页配置</el-button>
+    </div>
+  </div>
+</template>
+
+<script lang="ts" setup>
+import UploadImage from '@/components/upload-image/index.vue';
+import WebLinkInput from '@/components/WebLinkInput/index.vue';
+const { proxy } = getCurrentInstance() as ComponentInternalInstance;
+import { ref, reactive, computed, watch, onMounted, onUnmounted, nextTick } from 'vue';
+import { ElMessage, ElMessageBox } from 'element-plus';
+import {
+  listSearchConfig,
+  getSearchConfig,
+  delSearchConfig,
+  addSearchConfig,
+  updateSearchConfig,
+  getCurrentSearchConfig
+} from '@/api/enterprisePurchase/searchConfig';
+import { SearchConfigVO, SearchConfigQuery, SearchConfigForm } from '@/api/enterprisePurchase/searchConfig/types';
+import { listAdLeft, getAdLeft, delAdLeft, addAdLeft, updateAdLeft, getCurrentAdLeft } from '@/api/enterprisePurchase/adLeft';
+import { AdLeftVO, AdLeftQuery, AdLeftForm } from '@/api/enterprisePurchase/adLeft/types';
+import { listCarousel, getCarousel, delCarousel, addCarousel, updateCarousel, changeStatus } from '@/api/enterprisePurchase/carousel';
+import { CarouselVO, CarouselQuery, CarouselForm } from '@/api/enterprisePurchase/carousel/types';
+import {
+  listCategoryMain,
+  getCategoryMain,
+  delCategoryMain,
+  addCategoryMain,
+  updateCategoryMain,
+  changeStatus as categoryMainChangeStatus
+} from '@/api/enterprisePurchase/categoryMain';
+import { CategoryMainVO, CategoryMainQuery, CategoryMainForm } from '@/api/enterprisePurchase/categoryMain/types';
+import { listScenarioCards, getScenarioCards, delScenarioCards, addScenarioCards, updateScenarioCards } from '@/api/enterprisePurchase/scenarioCards';
+import { ScenarioCardsVO, ScenarioCardsQuery, ScenarioCardsForm } from '@/api/enterprisePurchase/scenarioCards/types';
+import { listScenarioGlobalSettings, addScenarioGlobalSettings, updateScenarioGlobalSettings } from '@/api/enterprisePurchase/scenarioGlobalSettings';
+import { ScenarioGlobalSettingsForm } from '@/api/enterprisePurchase/scenarioGlobalSettings/types';
+import { listQuickEntryModule, addQuickEntryModule, updateQuickEntryModule } from '@/api/enterprisePurchase/quickEntryModule';
+import { QuickEntryModuleVO, QuickEntryModuleQuery, QuickEntryModuleForm } from '@/api/enterprisePurchase/quickEntryModule/types';
+import {
+  listQuickEntryItems,
+  getQuickEntryItems,
+  delQuickEntryItems,
+  addQuickEntryItems,
+  updateQuickEntryItems,
+  changeStatus as quickEntryItemsUpdateStatus
+} from '@/api/enterprisePurchase/quickEntryItems';
+import { QuickEntryItemsVO, QuickEntryItemsQuery, QuickEntryItemsForm } from '@/api/enterprisePurchase/quickEntryItems/types';
+import { listBase } from '@/api/pmsProduct/base';
+import { listBrand } from '@/api/product/brand';
+import { categoryTree } from '@/api/product/base/index';
+import { categoryTreeVO } from '@/api/product/category/types';
+import { listAdModuleConfig, getAdModuleConfig, addAdModuleConfig, updateAdModuleConfig } from '@/api/enterprisePurchase/adModuleConfig';
+import { AdModuleConfigVO, AdModuleConfigQuery, AdModuleConfigForm } from '@/api/enterprisePurchase/adModuleConfig/types';
+import {
+  listHeaderCategory,
+  getHeaderCategory,
+  delHeaderCategory,
+  addHeaderCategory,
+  updateHeaderCategory,
+  changeStatus as headerCategoryChangeStatus
+} from '@/api/enterprisePurchase/headerCategory';
+import {
+  listRecommendThemeConfig,
+  getRecommendThemeConfig,
+  delRecommendThemeConfig,
+  addRecommendThemeConfig,
+  updateRecommendThemeConfig
+} from '@/api/enterprisePurchase/recommendThemeConfig';
+import { RecommendThemeConfigVO, RecommendThemeConfigQuery, RecommendThemeConfigForm } from '@/api/enterprisePurchase/recommendThemeConfig/types';
+import {
+  listRecommendCategoryConfig,
+  getRecommendCategoryConfig,
+  delRecommendCategoryConfig,
+  addRecommendCategoryConfig,
+  updateRecommendCategoryConfig,
+  changeStatus as recommendThemeChangeStatus
+} from '@/api/enterprisePurchase/recommendCategoryConfig';
+import {
+  RecommendCategoryConfigVO,
+  RecommendCategoryConfigQuery,
+  RecommendCategoryConfigForm
+} from '@/api/enterprisePurchase/recommendCategoryConfig/types';
+import { HeaderCategoryVO, HeaderCategoryQuery, HeaderCategoryForm } from '@/api/enterprisePurchase/headerCategory/types';
+import {
+  ShoppingCart,
+  Plus,
+  Edit,
+  Delete,
+  Picture,
+  CaretTop,
+  CaretBottom,
+  Menu,
+  ArrowLeft,
+  ArrowRight,
+  Search,
+  InfoFilled,
+  ArrowUp,
+  ArrowDown
+} from '@element-plus/icons-vue';
+import { any } from 'vue-types';
+const uploadAction = import.meta.env.VITE_APP_BASE_API + '/resource/oss/upload';
+const defaultPlaceholder =
+  'data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMTAwIiBoZWlnaHQ9IjEwMCIgdmlld0JveD0iMCAwIDEwMCAxMDAiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+PHJlY3Qgd2lkdGg9IjEwMCIgaGVpZ2h0PSIxMDAiIGZpbGw9IiNmMmYyZjIiLz48dGV4dCB4PSI1MCUiIHk9IjUwJSIgZG9taW5hbnQtYmFzZWxpbmU9Im1pZGRsZSIgdGV4dC1hbmNob3I9Im1pZGRsZSIgZm9udC1zaXplPSIxMiIgZmlsbD0iI2JmYmZiZiI+5pys5peg5Zu+54mHPC90ZXh0Pjwvc3ZnPg==';
+
+// 基础导航状态
+const activeSubTab = ref('search');
+const subTabs = [
+  { label: '搜索栏', value: 'search' },
+  { label: '广告图', value: 'carousel' },
+  { label: '分类设置', value: 'category' },
+  { label: '头部分类', value: 'headerCategory' },
+  { label: '广告模块', value: 'ad-module' },
+  { label: '快捷入口', value: 'quick-entry' },
+  { label: '场景方案', value: 'scenario' },
+  { label: '推荐设置', value: 'recommend' }
+];
+
+// 首页核心配置表单 (搜索栏与基本设置)
+const form = reactive({
+  id: null,
+  mainTitle: '',
+  subTitle: '',
+  placeholderText: '',
+  hotWordsList: [{ name: '', link: '' }],
+  themeColor: '',
+  rightBtnIcon: '',
+  rightBtnText: '',
+  rightBtnLink: '',
+
+  categoryThemeColor: '',
+  headerThemeColor: ''
+});
+
+const leftAdForm = reactive({
+  id: null,
+  leftAdImage: '',
+  leftAdLink: ''
+});
+// 轮播图状态切换
+const handleCarouselStatusChange = async (row: any) => {
+  const oldValue = row.status; // 保存旧值(0 或 1)
+  try {
+    await changeStatus(row.id, row.status); // 传新值
+    proxy?.$modal.msgSuccess('操作成功');
+  } catch {
+    row.status = oldValue; // 失败回滚
+    proxy?.$modal.msgError('操作失败,请重试');
+  }
+};
+
+// 轮播图状态切换
+const handleCategoryStatusChange = async (row: any) => {
+  const oldValue = row.status; // 保存旧值(0 或 1)
+  try {
+    await categoryMainChangeStatus(row.id, row.status); // 传新值
+    proxy?.$modal.msgSuccess('操作成功');
+  } catch {
+    row.status = oldValue; // 失败回滚
+    proxy?.$modal.msgError('操作失败,请重试');
+  }
+};
+// 头部分类状态切换
+const handleHeaderCategoryStatusChange = async (row: any) => {
+  const oldValue = row.status; // 保存旧值(0 或 1)
+  try {
+    await headerCategoryChangeStatus(row.id, row.status); // 传新值
+    proxy?.$modal.msgSuccess('操作成功');
+  } catch {
+    row.status = oldValue; // 失败回滚
+    proxy?.$modal.msgError('操作失败,请重试');
+  }
+};
+// 快捷入口状态切换
+const handleQuickEntryStatusChange = async (row: any) => {
+  const oldValue = row.status; // 保存旧值(0 或 1)
+  try {
+    await quickEntryItemsUpdateStatus(row.id, row.status); // 传新值
+    proxy?.$modal.msgSuccess('操作成功');
+  } catch {
+    row.status = oldValue; // 失败回滚
+    proxy?.$modal.msgError('操作失败,请重试');
+  }
+};
+
+// 推荐状态切换
+const handleRecommendStatusChange = async (row: any) => {
+  const oldValue = row.status; // 保存旧值(0 或 1)
+  try {
+    await recommendThemeChangeStatus(row.id, row.status); // 传新值
+    proxy?.$modal.msgSuccess('操作成功');
+  } catch {
+    row.status = oldValue; // 失败回滚
+    proxy?.$modal.msgError('操作失败,请重试');
+  }
+};
+
+// 左侧广告配置ID(单例)
+const leftAdId = ref<string | number>('');
+const leftAdHover = ref(false);
+
+// 保存配置
+const handleMainSave = async () => {
+  try {
+    let res;
+    if (activeSubTab.value == 'search' || activeSubTab.value == 'category' || activeSubTab.value == 'headerCategory') {
+      // 构建提交数据,过滤空的热词
+      form.categoryThemeColor = categoryThemeColor.value;
+      form.headerThemeColor = headerThemeColor.value;
+      const submitData = {
+        ...form,
+        hotWordList: form.hotWordsList.filter((item) => item.name && item.name.trim() !== '')
+      };
+      if (form.id) {
+        res = await updateSearchConfig(submitData);
+      } else {
+        // 无ID则新增
+        res = await addSearchConfig(submitData);
+      }
+
+      if (res.code === 200) {
+        ElMessage.success('配置保存成功');
+        // 如果是新增,保存返回的ID
+        if (!form.id && res.data) {
+          form.id = res.data.id;
+        }
+        // 重新获取最新配置
+        await getCurrentSearch();
+      } else {
+        ElMessage.error(res.msg || '保存失败');
+      }
+    } else if (activeSubTab.value == 'carousel') {
+      saveLeftAdConfig();
+    } else if (activeSubTab.value == 'scenario') {
+      saveScenarioGlobalSettings();
+    } else if (activeSubTab.value == 'quick-entry') {
+      saveQuickEntryModule();
+    } else if (activeSubTab.value == 'recommend') {
+      saveRecommendThemeConfig();
+    }
+  } catch (error) {
+    console.error('保存配置失败:', error);
+    ElMessage.error('保存配置失败');
+  }
+};
+
+// 获取当前配置
+const getCurrentAdLeftBtn = async () => {
+  try {
+    const res = await getCurrentAdLeft();
+    if (res.code === 200 && res.data) {
+      // 回显数据到表单
+      leftAdForm.id = res.data.id || null;
+      leftAdForm.leftAdImage = res.data.imageUrl || '';
+      leftAdForm.leftAdLink = res.data.link || '';
+    }
+  } catch (error) {
+    console.error('获取配置失败:', error);
+    ElMessage.error('获取配置失败');
+  }
+};
+// 获取当前配置
+const getCurrentSearch = async () => {
+  try {
+    const res = await getCurrentSearchConfig();
+    if (res.code === 200 && res.data) {
+      // 回显数据到表单
+      form.id = res.data.id || null;
+      form.mainTitle = res.data.mainTitle || '';
+      form.subTitle = res.data.subTitle || '';
+      form.placeholderText = res.data.placeholderText || '';
+      form.themeColor = res.data.themeColor || '';
+      form.rightBtnIcon = res.data.rightBtnIcon || '';
+      form.rightBtnText = res.data.rightBtnText || '';
+      form.rightBtnLink = res.data.rightBtnLink || '';
+
+      form.categoryThemeColor = res.data.categoryThemeColor || '';
+      form.headerThemeColor = res.data.headerThemeColor || '';
+      categoryThemeColor.value = form.categoryThemeColor;
+      headerThemeColor.value = form.headerThemeColor;
+
+      // 处理热词列表
+      if (res.data.hotWordList && Array.isArray(res.data.hotWordList)) {
+        form.hotWordsList =
+          res.data.hotWordList.length > 0
+            ? res.data.hotWordList.map((item) => ({
+                name: item.name || '',
+                link: item.link || ''
+              }))
+            : [{ name: '', link: '' }];
+      } else {
+        form.hotWordsList = [{ name: '', link: '' }];
+      }
+    }
+  } catch (error) {
+    console.error('获取配置失败:', error);
+    ElMessage.error('获取配置失败');
+  }
+};
+
+const handleMainReset = () => {
+  ElMessageBox.confirm('确定要重置当前页面的所有修改吗?', '提示', {
+    confirmButtonText: '确定重置',
+    cancelButtonText: '取消',
+    type: 'warning'
+  }).then(() => {
+    ElMessage.info('已还原至初始状态');
+  });
+};
+
+// 搜索栏逻辑
+const currentPlaceholderIndex = ref(0);
+let timer = null;
+
+const searchPlaceholderList = computed(() => {
+  return form.placeholderText
+    .split(',')
+    .map((t) => t.trim())
+    .filter((t) => t !== '');
+});
+
+const addHotWord = () => {
+  form.hotWordsList.push({ name: '', link: '' });
+};
+
+const removeHotWord = (index) => {
+  form.hotWordsList.splice(index, 1);
+};
+
+const startPlaceholderScroll = () => {
+  if (timer) clearInterval(timer);
+  if (searchPlaceholderList.value.length <= 1) return;
+
+  timer = setInterval(() => {
+    currentPlaceholderIndex.value = (currentPlaceholderIndex.value + 1) % searchPlaceholderList.value.length;
+  }, 3000);
+};
+
+// 广告图模块逻辑
+
+const carouselList = ref<CarouselVO[]>([]);
+
+// 获取轮播图列表
+const getCarouselList = async () => {
+  try {
+    const res = await listCarousel();
+    if (res.code === 200 && res.rows) {
+      carouselList.value = res.rows.map((item: CarouselVO) => ({
+        ...item,
+        image: item.imageUrl || ''
+      }));
+    }
+  } catch (error) {
+    console.error('获取轮播图列表失败:', error);
+    ElMessage.error('获取轮播图列表失败');
+  }
+};
+
+const activeCarouselList = computed(() => {
+  return carouselList.value.filter((item) => item.status === 1);
+});
+
+// 轮播图弹窗逻辑
+const dialogVisible = ref(false);
+const dialogType = ref('add'); // add | edit
+const currentEditIndex = ref(-1);
+const carouselForm = reactive({
+  image: '',
+  link: '',
+  target: '_self',
+  status: 1
+});
+
+const handleAddCarousel = () => {
+  dialogType.value = 'add';
+  carouselForm.image = '';
+  carouselForm.link = '';
+  carouselForm.target = '_self';
+  carouselForm.status = 1;
+  dialogVisible.value = true;
+};
+
+const handleEditCarousel = (row) => {
+  dialogType.value = 'edit';
+  currentEditIndex.value = carouselList.value.findIndex((item) => item.id === row.id);
+  carouselForm.image = row.image;
+  carouselForm.link = row.link;
+  carouselForm.target = row.target;
+  carouselForm.status = row.status;
+  dialogVisible.value = true;
+};
+
+const submitCarouselForm = async () => {
+  if (!carouselForm.image) {
+    return ElMessage.warning('请先上传轮播图片');
+  }
+
+  const data: CarouselForm = {
+    imageUrl: carouselForm.image,
+    link: carouselForm.link,
+    target: carouselForm.target,
+    status: carouselForm.status,
+    sortOrder: carouselList.value.length
+  };
+
+  try {
+    if (dialogType.value === 'add') {
+      const res = await addCarousel(data);
+      if (res.code === 200) {
+        ElMessage.success('新增成功');
+        dialogVisible.value = false;
+        getCarouselList();
+      } else {
+        ElMessage.error(res.msg || '新增失败');
+      }
+    } else {
+      const id = carouselList.value[currentEditIndex.value]?.id;
+      const res = await updateCarousel({ ...data, id });
+      if (res.code === 200) {
+        ElMessage.success('修改成功');
+        dialogVisible.value = false;
+        getCarouselList();
+      } else {
+        ElMessage.error(res.msg || '修改失败');
+      }
+    }
+  } catch (error) {
+    console.error('操作失败:', error);
+    ElMessage.error('操作失败');
+  }
+};
+
+const handleRemoveCarousel = async (index: number) => {
+  const item = carouselList.value[index];
+  try {
+    await ElMessageBox.confirm('确定要删除该轮播图吗?', '提示', { type: 'warning' });
+    const res = await delCarousel(item.id);
+    if (res.code === 200) {
+      ElMessage.success('删除成功');
+      getCarouselList();
+    } else {
+      ElMessage.error(res.msg || '删除失败');
+    }
+  } catch (error) {
+    if (error !== 'cancel') {
+      console.error('删除失败:', error);
+      ElMessage.error('删除失败');
+    }
+  }
+};
+
+const moveRow = async (index: number, direction: number) => {
+  const newIndex = index + direction;
+  if (newIndex < 0 || newIndex >= carouselList.value.length) return;
+  const item = carouselList.value.splice(index, 1)[0];
+  carouselList.value.splice(newIndex, 0, item);
+
+  // 同步排序到后端
+  try {
+    const start = Math.min(index, newIndex);
+    const end = Math.max(index, newIndex);
+    for (let i = start; i <= end; i++) {
+      const row = carouselList.value[i];
+      await updateCarousel({
+        id: row.id,
+        imageUrl: row.imageUrl,
+        link: row.link,
+        target: row.target,
+        status: row.status,
+        sortOrder: i
+      });
+    }
+  } catch (error) {
+    console.error('排序更新失败:', error);
+    ElMessage.error('排序更新失败');
+  }
+};
+
+// 颜色转换工具:Hex 转 RGBA
+const hexToRgba = (hex, opacity) => {
+  if (!hex) return `rgba(255, 255, 255, ${opacity})`;
+  const r = parseInt(hex.slice(1, 3), 16);
+  const g = parseInt(hex.slice(3, 5), 16);
+  const b = parseInt(hex.slice(5, 7), 16);
+  return `rgba(${r}, ${g}, ${b}, ${opacity})`;
+};
+
+const handleSelectSearch = () => {
+  // 模拟搜索过滤,computed 会自动处理
+};
+
+// 左侧广告图片变更处理
+const onLeftAdImageChange = (file: any) => {
+  if (!file || !file.url) {
+    // 图片被删除
+    handleDeleteLeftAd();
+  }
+};
+
+// ==================== 左侧广告单例配置 ====================
+
+// 获取左侧广告配置
+const fetchLeftAdConfig = async () => {
+  try {
+    const res = await listAdLeft({ pageNum: 1, pageSize: 1 });
+    if (res.code === 200 && res.rows && res.rows.length > 0) {
+      const adData = res.rows[0];
+      leftAdId.value = adData.id;
+      leftAdForm.id = adData.id;
+      leftAdForm.leftAdImage = adData.imageUrl || '';
+      leftAdForm.leftAdLink = adData.link || '';
+    }
+  } catch (error) {
+    console.error('获取左侧广告配置失败:', error);
+  }
+};
+
+// 保存左侧广告配置
+const saveLeftAdConfig = async () => {
+  try {
+    if (!leftAdForm.leftAdImage) {
+      ElMessage.warning('请先上传广告图片');
+      return;
+    }
+
+    const submitData: AdLeftForm = {
+      id: leftAdId.value || leftAdForm.id || undefined,
+      imageUrl: leftAdForm.leftAdImage,
+      link: leftAdForm.leftAdLink,
+      status: 1 // 默认启用
+    };
+
+    let res;
+    if (submitData.id) {
+      // 有ID则更新
+      res = await updateAdLeft(submitData);
+    } else {
+      // 无ID则新增
+      res = await addAdLeft(submitData);
+    }
+
+    if (res.code === 200) {
+      ElMessage.success('左侧广告保存成功');
+      // 如果是新增,保存返回的ID
+      if (!leftAdId.value && res.data) {
+        leftAdId.value = res.data.id;
+        leftAdForm.id = leftAdId.value;
+      }
+      // 重新获取最新配置
+      await fetchLeftAdConfig();
+    } else {
+      ElMessage.error(res.msg || '保存失败');
+    }
+  } catch (error) {
+    console.error('保存左侧广告失败:', error);
+    ElMessage.error('保存左侧广告失败');
+  }
+};
+
+// 删除左侧广告
+const handleDeleteLeftAd = () => {
+  ElMessageBox.confirm('确定要删除左侧广告吗?', '提示', {
+    confirmButtonText: '确定删除',
+    cancelButtonText: '取消',
+    type: 'warning'
+  })
+    .then(async () => {
+      try {
+        if (leftAdId.value) {
+          const res = await delAdLeft(leftAdId.value);
+          if (res.code === 200) {
+            ElMessage.success('删除成功');
+            // 清空表单数据
+            leftAdId.value = '';
+            leftAdForm.id = leftAdId.value;
+            leftAdForm.leftAdImage = '';
+            leftAdForm.leftAdLink = '';
+          } else {
+            ElMessage.error(res.msg || '删除失败');
+          }
+        } else {
+          // 如果没有ID,直接清空表单
+          leftAdForm.leftAdImage = '';
+          leftAdForm.leftAdLink = '';
+          ElMessage.success('已清除');
+        }
+      } catch (error) {
+        console.error('删除左侧广告失败:', error);
+        ElMessage.error('删除失败');
+      }
+    })
+    .catch(() => {
+      // 用户取消操作
+    });
+};
+
+// 分类设置模块逻辑
+const categoryThemeColor = ref('#e60012');
+const syncCategoryOptions = computed(() => {
+  return (categoryOptions.value || []).map((item: any) => ({
+    value: item.id,
+    label: item.label
+  }));
+});
+
+const onSyncCategoryChange = (val: string | number) => {
+  if (val) {
+    const node = findNodeById(categoryOptions.value, val);
+    categoryForm.syncCategory = node ? node.label : '';
+  } else {
+    categoryForm.syncCategory = '';
+  }
+};
+
+const categoryList = ref<CategoryMainVO[]>([]);
+
+// 获取分类列表
+const getCategoryList = async () => {
+  try {
+    const res = await listCategoryMain();
+    if (res.code === 200 && res.rows) {
+      categoryList.value = res.rows.map((item: CategoryMainVO) => {
+        let extra = { tags: [] as any[], notes: [] as any[], groups: [] as any[] };
+        try {
+          if ((item as any).remark) {
+            extra = JSON.parse((item as any).remark);
+          }
+        } catch (e) {
+          /* remark JSON 解析失败使用默认值 */
+        }
+
+        return {
+          ...item,
+          tags: extra.tags || [],
+          panelData: {
+            mainTitle: (item as any).panelMainTitle || '',
+            subTitle: (item as any).panelSubTitle || '',
+            notes: extra.notes || [],
+            groups: extra.groups || []
+          }
+        };
+      });
+    }
+  } catch (error) {
+    console.error('获取分类列表失败:', error);
+    ElMessage.error('获取分类列表失败');
+  }
+};
+
+const addCategoryTag = () => {
+  categoryForm.tags.push({ name: '', link: '' });
+};
+
+const removeCategoryTag = (index) => {
+  categoryForm.tags.splice(index, 1);
+};
+
+const categoryDialogVisible = ref(false);
+const categoryDialogType = ref('add');
+const categoryForm = reactive({
+  id: null,
+  name: '',
+  icon: '',
+  syncCategoryId: null,
+  syncCategory: '',
+  tags: [],
+  status: 1,
+  panelData: {
+    mainTitle: '',
+    subTitle: '',
+    notes: [],
+    groups: []
+  }
+});
+
+const handleAddCategory = () => {
+  categoryDialogType.value = 'add';
+  Object.assign(categoryForm, {
+    id: null,
+    name: '',
+    icon: '',
+    syncCategoryId: null,
+    syncCategory: '',
+    tags: [],
+    status: 1,
+    panelData: { mainTitle: '', subTitle: '', notes: [], groups: [] }
+  });
+  categoryDialogVisible.value = true;
+};
+
+const handleEditCategory = (row) => {
+  categoryDialogType.value = 'edit';
+  Object.assign(categoryForm, JSON.parse(JSON.stringify(row)));
+  categoryDialogVisible.value = true;
+};
+
+const submitCategoryForm = async () => {
+  if (!categoryForm.name) return ElMessage.warning('请输入菜单名称');
+
+  const data: CategoryMainForm = {
+    name: categoryForm.name,
+    icon: categoryForm.icon,
+    syncCategoryId: categoryForm.syncCategoryId,
+    syncCategory: categoryForm.syncCategory,
+    status: categoryForm.status,
+    panelMainTitle: categoryForm.panelData.mainTitle,
+    panelSubTitle: categoryForm.panelData.subTitle,
+    remark: JSON.stringify({
+      tags: categoryForm.tags,
+      notes: categoryForm.panelData.notes,
+      groups: categoryForm.panelData.groups
+    })
+  };
+
+  try {
+    if (categoryDialogType.value === 'add') {
+      const res = await addCategoryMain(data);
+      if (res.code === 200) {
+        ElMessage.success('新增成功');
+        categoryDialogVisible.value = false;
+        getCategoryList();
+      } else {
+        ElMessage.error(res.msg || '新增失败');
+      }
+    } else {
+      const id = categoryForm.id;
+      const res = await updateCategoryMain({ ...data, id });
+      if (res.code === 200) {
+        ElMessage.success('修改成功');
+        categoryDialogVisible.value = false;
+        getCategoryList();
+      } else {
+        ElMessage.error(res.msg || '修改失败');
+      }
+    }
+  } catch (error) {
+    console.error('操作失败:', error);
+    ElMessage.error('操作失败');
+  }
+};
+
+// 场景方案模块逻辑
+const scenarioSettings = reactive({
+  id: null as string | number | null,
+  mainTitle: '场景解决方案',
+  subTitle: '一站全买齐',
+  btnText: '进入全场景',
+  jumpLink: '',
+  themeColor: '#66e0a3'
+});
+
+// 获取场景全局设置
+const getScenarioGlobalSettingsList = async () => {
+  try {
+    const res = await listScenarioGlobalSettings();
+    if (res.code === 200 && res.rows && res.rows.length > 0) {
+      const data = res.rows[0];
+      scenarioSettings.id = data.id;
+      scenarioSettings.mainTitle = data.mainTitle || '';
+      scenarioSettings.subTitle = data.subTitle || '';
+      scenarioSettings.btnText = data.btnText || '';
+      scenarioSettings.jumpLink = data.jumpLink || '';
+      scenarioSettings.themeColor = data.themeColor || '#66e0a3';
+    }
+  } catch (error) {
+    console.error('获取场景全局设置失败:', error);
+    ElMessage.error('获取场景全局设置失败');
+  }
+};
+
+// 保存场景全局设置
+const saveScenarioGlobalSettings = async () => {
+  const data: ScenarioGlobalSettingsForm = {
+    mainTitle: scenarioSettings.mainTitle,
+    subTitle: scenarioSettings.subTitle,
+    btnText: scenarioSettings.btnText,
+    jumpLink: scenarioSettings.jumpLink,
+    themeColor: scenarioSettings.themeColor
+  };
+
+  try {
+    let res;
+    if (scenarioSettings.id) {
+      res = await updateScenarioGlobalSettings({ ...data, id: scenarioSettings.id });
+    } else {
+      res = await addScenarioGlobalSettings(data);
+      if (res.code === 200 && res.data) {
+        scenarioSettings.id = res.data.id;
+      }
+    }
+    if (res.code === 200) {
+      ElMessage.success('场景全局设置保存成功');
+    } else {
+      ElMessage.error(res.msg || '保存失败');
+    }
+  } catch (error) {
+    console.error('保存场景全局设置失败:', error);
+    ElMessage.error('保存失败');
+  }
+};
+
+const scenarioList = ref<ScenarioCardsVO[]>([]);
+
+// 获取场景卡片列表
+const getScenarioList = async () => {
+  try {
+    const res = await listScenarioCards();
+    if (res.code === 200 && res.rows) {
+      scenarioList.value = res.rows.map((item: ScenarioCardsVO) => {
+        let extra = { subTitleColor: '#333333', bgColor: '#ffffff', opacity: 1 };
+        try {
+          if ((item as any).remark) {
+            extra = { ...extra, ...JSON.parse((item as any).remark) };
+          }
+        } catch (e) {
+          /* remark JSON 解析失败使用默认值 */
+        }
+
+        return {
+          ...item,
+          image: (item as any).imageUrl || '',
+          link: (item as any).jumpLink || '',
+          subTitleColor: extra.subTitleColor,
+          bgColor: extra.bgColor,
+          opacity: extra.opacity
+        };
+      });
+    }
+  } catch (error) {
+    console.error('获取场景卡片列表失败:', error);
+    ElMessage.error('获取场景卡片列表失败');
+  }
+};
+
+const scenarioDialogVisible = ref(false);
+const scenarioEditIndex = ref(-1);
+const scenarioForm = reactive({
+  title: '',
+  titleColor: '#008b4e',
+  subTitle: '',
+  subTitleColor: '#333333',
+  image: '',
+  link: '',
+  bgColor: '#ffffff',
+  opacity: 1
+});
+
+const handleEditScenario = (row, index) => {
+  scenarioEditIndex.value = index;
+  Object.assign(scenarioForm, JSON.parse(JSON.stringify(row)));
+  scenarioDialogVisible.value = true;
+};
+
+const submitScenarioForm = async () => {
+  const item = scenarioList.value[scenarioEditIndex.value];
+  if (!item) return;
+
+  const data: ScenarioCardsForm = {
+    id: item.id,
+    title: scenarioForm.title,
+    titleColor: scenarioForm.titleColor,
+    subTitle: scenarioForm.subTitle,
+    imageUrl: scenarioForm.image,
+    jumpLink: scenarioForm.link,
+    sortOrder: (item as any).sortOrder ?? scenarioEditIndex.value,
+    remark: JSON.stringify({
+      subTitleColor: scenarioForm.subTitleColor,
+      bgColor: scenarioForm.bgColor,
+      opacity: scenarioForm.opacity
+    })
+  };
+
+  try {
+    const res = await updateScenarioCards(data);
+    if (res.code === 200) {
+      ElMessage.success('方案修改成功');
+      scenarioDialogVisible.value = false;
+      getScenarioList();
+    } else {
+      ElMessage.error(res.msg || '修改失败');
+    }
+  } catch (error) {
+    console.error('修改失败:', error);
+    ElMessage.error('修改失败');
+  }
+};
+
+const moveScenario = async (index: number, direction: number) => {
+  const newIndex = index + direction;
+  if (newIndex < 0 || newIndex >= scenarioList.value.length) return;
+  const item = scenarioList.value.splice(index, 1)[0];
+  scenarioList.value.splice(newIndex, 0, item);
+
+  // 同步排序到后端
+  try {
+    const start = Math.min(index, newIndex);
+    const end = Math.max(index, newIndex);
+    for (let i = start; i <= end; i++) {
+      const row = scenarioList.value[i] as any;
+      await updateScenarioCards({
+        id: row.id,
+        title: row.title,
+        titleColor: row.titleColor,
+        subTitle: row.subTitle,
+        imageUrl: row.image || row.imageUrl,
+        jumpLink: row.link || row.jumpLink,
+        sortOrder: i,
+        remark: JSON.stringify({
+          subTitleColor: row.subTitleColor || '#333333',
+          bgColor: row.bgColor || '#ffffff',
+          opacity: row.opacity ?? 1
+        })
+      });
+    }
+  } catch (error) {
+    console.error('排序更新失败:', error);
+    ElMessage.error('排序更新失败');
+  }
+};
+
+const handleRemoveCategory = async (index: number) => {
+  const item = categoryList.value[index];
+  try {
+    await ElMessageBox.confirm('确定要删除该分类吗?', '提示');
+    const res = await delCategoryMain(item.id);
+    if (res.code === 200) {
+      ElMessage.success('删除成功');
+      getCategoryList();
+    } else {
+      ElMessage.error(res.msg || '删除失败');
+    }
+  } catch (error) {
+    if (error !== 'cancel') {
+      console.error('删除失败:', error);
+      ElMessage.error('删除失败');
+    }
+  }
+};
+
+const addPanelNote = () => {
+  categoryForm.panelData.notes.push({ name: '', link: '' });
+};
+
+const removePanelNote = (index) => {
+  categoryForm.panelData.notes.splice(index, 1);
+};
+
+const moveCategory = async (index: number, direction: number) => {
+  const newIndex = index + direction;
+  if (newIndex < 0 || newIndex >= categoryList.value.length) return;
+  const item = categoryList.value.splice(index, 1)[0];
+  categoryList.value.splice(newIndex, 0, item);
+
+  // 同步排序到后端
+  try {
+    const start = Math.min(index, newIndex);
+    const end = Math.max(index, newIndex);
+    for (let i = start; i <= end; i++) {
+      const row = categoryList.value[i] as any;
+      await updateCategoryMain({ id: row.id, sortOrder: i } as any);
+    }
+  } catch (error) {
+    console.error('排序更新失败:', error);
+  }
+};
+
+onMounted(() => {
+  startPlaceholderScroll();
+});
+
+// 头部分类模块逻辑
+const headerThemeColor = ref('#e60012');
+const headerCategoryList = ref<HeaderCategoryVO[]>([]);
+
+// 获取头部分类列表
+const getHeaderCategoryList = async () => {
+  try {
+    const res = await listHeaderCategory();
+    if (res.code === 200 && res.rows) {
+      headerCategoryList.value = res.rows;
+    }
+  } catch (error) {
+    console.error('获取头部分类列表失败:', error);
+    ElMessage.error('获取头部分类列表失败');
+  }
+};
+
+const headerDialogVisible = ref(false);
+const headerEditIndex = ref(-1);
+const headerForm = reactive({ title: '', icon: '', link: '', openMode: 'current', status: 1 });
+
+const handleAddHeaderCategory = () => {
+  headerEditIndex.value = -1;
+  Object.assign(headerForm, { title: '', icon: '', link: '', openMode: 'current', status: 1 });
+  headerDialogVisible.value = true;
+};
+
+const handleEditHeaderCategory = (row, index) => {
+  headerEditIndex.value = index;
+  Object.assign(headerForm, JSON.parse(JSON.stringify(row)));
+  headerDialogVisible.value = true;
+};
+
+const submitHeaderForm = async () => {
+  const data: HeaderCategoryForm = {
+    title: headerForm.title,
+    icon: headerForm.icon,
+    link: headerForm.link,
+    openMode: headerForm.openMode,
+    status: headerForm.status
+  };
+
+  try {
+    if (headerEditIndex.value > -1) {
+      const id = headerCategoryList.value[headerEditIndex.value]?.id;
+      const res = await updateHeaderCategory({ ...data, id });
+      if (res.code === 200) {
+        ElMessage.success('修改成功');
+        headerDialogVisible.value = false;
+        getHeaderCategoryList();
+      } else {
+        ElMessage.error(res.msg || '修改失败');
+      }
+    } else {
+      const res = await addHeaderCategory(data);
+      if (res.code === 200) {
+        ElMessage.success('新增成功');
+        headerDialogVisible.value = false;
+        getHeaderCategoryList();
+      } else {
+        ElMessage.error(res.msg || '新增失败');
+      }
+    }
+  } catch (error) {
+    console.error('操作失败:', error);
+    ElMessage.error('操作失败');
+  }
+};
+
+const handleDeleteHeaderCategory = async (index: number) => {
+  const item = headerCategoryList.value[index];
+  try {
+    await ElMessageBox.confirm('确定删除该分类吗?', '提示');
+    const res = await delHeaderCategory(item.id);
+    if (res.code === 200) {
+      ElMessage.success('删除成功');
+      getHeaderCategoryList();
+    } else {
+      ElMessage.error(res.msg || '删除失败');
+    }
+  } catch (error) {
+    if (error !== 'cancel') {
+      console.error('删除失败:', error);
+      ElMessage.error('删除失败');
+    }
+  }
+};
+
+const moveHeader = async (index: number, direction: number) => {
+  const newIndex = index + direction;
+  if (newIndex < 0 || newIndex >= headerCategoryList.value.length) return;
+  const item = headerCategoryList.value.splice(index, 1)[0];
+  headerCategoryList.value.splice(newIndex, 0, item);
+
+  // 同步排序到后端
+  try {
+    const start = Math.min(index, newIndex);
+    const end = Math.max(index, newIndex);
+    for (let i = start; i <= end; i++) {
+      const row = headerCategoryList.value[i] as any;
+      await updateHeaderCategory({ id: row.id, sortOrder: i } as any);
+    }
+  } catch (error) {
+    console.error('排序更新失败:', error);
+  }
+};
+
+// 导航滚动逻辑
+const headerNavScrollRef = ref(null);
+const showLeftArrow = ref(false);
+const showRightArrow = ref(false);
+
+const updateNavArrows = () => {
+  if (!headerNavScrollRef.value) return;
+  const { scrollLeft, scrollWidth, clientWidth } = headerNavScrollRef.value;
+  showLeftArrow.value = scrollLeft > 5;
+  showRightArrow.value = scrollLeft + clientWidth < scrollWidth - 5;
+};
+
+const scrollHeaderNav = (direction) => {
+  const container = headerNavScrollRef.value;
+  if (!container) return;
+  const scrollAmount = 350;
+  if (direction === 'left') {
+    container.scrollLeft -= scrollAmount;
+  } else {
+    container.scrollLeft += scrollAmount;
+  }
+};
+
+// 广告模块逻辑
+const adModules = ref([
+  {
+    id: 1,
+    title: '企业购x百亿补贴',
+    titleColor: '#333333',
+    subTitle: '先采后付 享底价',
+    subTitleColor: '#999999',
+    type: 'subsidy',
+    items: [
+      { id: 101, productName: '企业商用台式机', imageUrl: '/static/images/purchase/pc_desktop.jpg', price: '69.9' },
+      { id: 102, productName: '商务笔记本', imageUrl: '/static/images/purchase/laptop_hp.jpg', price: '84.8' },
+      { id: 103, productName: '智能打印机', imageUrl: '/static/images/purchase/printer_office.jpg', price: '139.9' },
+      { id: 104, productName: '高效办公组网', imageUrl: '/static/images/purchase/network_router.jpg', price: '1749' }
+    ]
+  },
+  {
+    id: 2,
+    title: '企采榜单',
+    titleColor: '#333333',
+    subTitle: '同行都在买',
+    subTitleColor: '#f58220',
+    type: 'ranking',
+    items: [
+      {
+        id: 201,
+        productName: '办公电脑榜',
+        imageUrl: '/static/images/purchase/laptop_lenovo.jpg',
+        price: '0',
+        tagText: '办公电脑榜',
+        tagLink: '',
+        salesCount: '1543'
+      },
+      {
+        id: 202,
+        productName: '文具榜',
+        imageUrl: '/static/images/purchase/stationery_ranking.jpg',
+        price: '0',
+        tagText: '文具榜',
+        tagLink: '',
+        salesCount: '1200'
+      }
+    ]
+  },
+  {
+    id: 3,
+    title: '品牌好店',
+    titleColor: '#333333',
+    subTitle: '返2000元E卡',
+    subTitleColor: '#f58220',
+    type: 'brand',
+    items: [
+      { id: 301, productName: '鲁花', imageUrl: '/static/images/purchase/oil_luhua.jpg', tagText: '品质保障', tagLink: '鲁花京东自营旗舰店' },
+      { id: 302, productName: '金龙鱼', imageUrl: '/static/images/purchase/oil_jinlongyu.jpg', tagText: '热销品牌', tagLink: '金龙鱼京东自营旗舰店' }
+    ]
+  },
+  {
+    id: 4,
+    title: '企业精选',
+    titleColor: '#333333',
+    subTitle: '品牌专供 库存充足',
+    subTitleColor: '#999999',
+    type: 'selection',
+    items: [
+      { id: 401, productName: '高性能工作站', imageUrl: '/static/images/purchase/pc_desktop.jpg', price: '10740' },
+      { id: 402, productName: '办公咖啡机', imageUrl: '/static/images/purchase/coffee_machine.jpg', price: '877' }
+    ]
+  },
+  {
+    id: 5,
+    title: '企业购x京东新品',
+    titleColor: '#333333',
+    subTitle: '美的新鲜',
+    subTitleColor: '#f58220',
+    type: 'new',
+    items: [
+      { id: 501, productName: '商用冷柜', imageUrl: '/static/images/purchase/freezer.jpg', price: '7188' },
+      { id: 502, productName: '得力笔记本', imageUrl: '/static/images/purchase/notebook_deli.jpg', price: '34.9' }
+    ]
+  }
+]) as any;
+
+const adDialogVisible = ref(false);
+const currentAdIdx = ref(-1);
+const currentItemIdx = ref(-1);
+const adForm = reactive({ title: '', titleColor: '#333333', subTitle: '', subTitleColor: '#f58220', items: [] });
+
+// 实时预览辅助函数
+const getAdTitleStyle = (index: number, type: string) => {
+  const isEditing = adDialogVisible.value && currentAdIdx.value === index;
+  const key = type === 'main' ? 'titleColor' : 'subTitleColor';
+  const defaultColor = type === 'main' ? '#333' : '#999';
+  const color = isEditing ? adForm[key] : adModules.value[index]?.[key] || defaultColor;
+  return { color };
+};
+
+const getAdTitleText = (index: number, type: string) => {
+  const isEditing = adDialogVisible.value && currentAdIdx.value === index;
+  const key = type === 'main' ? 'title' : 'subTitle';
+  return isEditing ? adForm[key] : adModules.value[index]?.[key] || '';
+};
+
+const emptyItem = () => ({ id: null, productId: '', productName: '', imageUrl: '', price: '', tagText: '', tagLink: '', salesCount: '' });
+const slotCounts = [4, 2, 2, 2, 2]; // 每个模块固定条目数
+
+const handleEditAd = (index: number) => {
+  currentAdIdx.value = index;
+  const ad = adModules.value[index] || {};
+  const data = JSON.parse(JSON.stringify(ad));
+  // 确保 items 有固定数量的条目(不够用空模板补齐)
+  const count = slotCounts[index] || 2;
+  while (data.items.length < count) {
+    data.items.push(emptyItem());
+  }
+  Object.assign(adForm, data);
+  adDialogVisible.value = true;
+};
+
+const submitAdForm = async () => {
+  const moduleData = adModules.value[currentAdIdx.value] as any;
+  if (!moduleData) return;
+
+  const data: AdModuleConfigForm = {
+    moduleCode: moduleData.type || '',
+    moduleName: moduleData.title || '',
+    mainTitle: adForm.title,
+    mainTitleColor: adForm.titleColor,
+    subTitle: adForm.subTitle,
+    subTitleColor: adForm.subTitleColor,
+    jumpLink: '',
+    status: 1,
+    sortOrder: currentAdIdx.value,
+    adModuleItemList: adForm.items
+      .filter((item: any) => item.productId || item.imageUrl)
+      .map((item: any) => ({
+        productId: item.productId || item.id,
+        productName: item.productName || '',
+        imageUrl: item.imageUrl || '',
+        price: item.price || 0,
+        tagText: item.tagText || '',
+        tagLink: item.tagLink || '',
+        salesCount: item.salesCount || 0
+      }))
+  };
+
+  try {
+    let res;
+    // 从 API 加载的真实数据才有有效 id,mock id(1-5)用 POST 新增
+    const isFromApi = moduleData.moduleCode && moduleData.id && String(moduleData.id).length > 10;
+    if (isFromApi) {
+      res = await updateAdModuleConfig({ ...data, id: moduleData.id });
+    } else {
+      res = await addAdModuleConfig(data);
+    }
+    if (res.code === 200) {
+      // 新增时回写后端返回的 id
+      const savedData = JSON.parse(JSON.stringify(adForm));
+      if (!isFromApi && res.data?.id) {
+        savedData.id = res.data.id;
+      }
+      adModules.value[currentAdIdx.value] = savedData;
+      adDialogVisible.value = false;
+      ElMessage.success('配置保存成功');
+    } else {
+      ElMessage.error(res.msg || '保存失败');
+    }
+  } catch (error) {
+    console.error('保存广告模块配置失败:', error);
+    ElMessage.error('保存失败');
+  }
+};
+
+// 获取广告模块配置列表
+const getAdModuleList = async () => {
+  const defaults = adModules.value; // 保留当前 mock 数据作为缺省填充
+  try {
+    const res = await listAdModuleConfig();
+    if (res.code === 200 && res.rows && res.rows.length > 0) {
+      const apiList = res.rows.map((item: AdModuleConfigVO) => ({
+        ...item,
+        title: item.mainTitle || '',
+        titleColor: item.mainTitleColor || '#333333',
+        subTitle: item.subTitle || '',
+        subTitleColor: item.subTitleColor || '#999999',
+        type: item.moduleCode || '',
+        items: (item.adModuleItemList || []).map((sub: any) => ({
+          id: sub.id,
+          productId: sub.productId || '',
+          productName: sub.productName || '',
+          imageUrl: sub.imageUrl || '',
+          price: sub.price || 0,
+          tagText: sub.tagText || '',
+          tagLink: sub.tagLink || '',
+          salesCount: sub.salesCount || 0
+        }))
+      }));
+      // 确保始终有 5 个模块(模板固定访问 adModules[0]~[4])
+      adModules.value = Array.from({ length: 5 }, (_, i) => apiList[i] || defaults[i] || { title: '', items: [] });
+    }
+  } catch (error) {
+    console.error('获取广告模块配置失败:', error);
+  }
+};
+
+// 推荐设置模块逻辑
+const recommendThemeColor = ref('#e60012');
+const recommendProductThemeColor = ref('#e60012');
+const recommendActiveId = ref(1);
+
+const recommendThemeConfigId = ref<string | number | null>(null);
+
+const getRecommendThemeConfigList = async () => {
+  try {
+    const res = await listRecommendThemeConfig();
+    if (res.code === 200 && res.rows && res.rows.length > 0) {
+      const data = res.rows[0];
+      recommendThemeConfigId.value = data.id;
+      recommendThemeColor.value = data.themeColor || '#e60012';
+      recommendProductThemeColor.value = data.productThemeColor || '#e60012';
+    }
+  } catch (error) {
+    console.error('获取推荐主题配置失败:', error);
+  }
+};
+
+const saveRecommendThemeConfig = async () => {
+  const data: RecommendThemeConfigForm = {
+    themeColor: recommendThemeColor.value,
+    productThemeColor: recommendProductThemeColor.value
+  };
+  try {
+    let res;
+    if (recommendThemeConfigId.value) {
+      res = await updateRecommendThemeConfig({ ...data, id: recommendThemeConfigId.value });
+    } else {
+      res = await addRecommendThemeConfig(data);
+      if (res.code === 200 && res.data) {
+        recommendThemeConfigId.value = res.data.id;
+      }
+    }
+    if (res.code === 200) {
+      ElMessage.success('推荐主题配置保存成功');
+    } else {
+      ElMessage.error(res.msg || '保存失败');
+    }
+  } catch (error) {
+    console.error('保存推荐主题配置失败:', error);
+    ElMessage.error('保存失败');
+  }
+};
+const recommendList = ref<RecommendCategoryConfigVO[]>([]);
+
+const getRecommendCategoryList = async () => {
+  try {
+    const res = await listRecommendCategoryConfig();
+    if (res.code === 200 && res.rows) {
+      recommendList.value = res.rows.map((item: RecommendCategoryConfigVO) => ({
+        ...item,
+        type: (item as any).dataType || 'select',
+        icon: (item as any).iconUrl || '',
+        categoryValue: [],
+        selectedProducts: (() => {
+          try {
+            return (item as any).selectedProductIds ? JSON.parse((item as any).selectedProductIds) : [];
+          } catch {
+            return [];
+          }
+        })()
+      }));
+    }
+  } catch (error) {
+    console.error('获取推荐分类列表失败:', error);
+    ElMessage.error('获取推荐分类列表失败');
+  }
+};
+
+const activeRecommendList = computed(() => {
+  return recommendList.value.filter((item) => item.status === 1);
+});
+
+const recommendDialogVisible = ref(false);
+const recommendEditIndex = ref(-1);
+const recommendForm = reactive({
+  name: '',
+  subTitle: '',
+  icon: '',
+  type: 'select',
+  categoryValue: [],
+  categoryLabel: '',
+  selectedProducts: [],
+  status: 1
+});
+
+const categoryOptions = ref<categoryTreeVO[]>([]);
+
+const getCategoryTreeData = async () => {
+  try {
+    const res = await categoryTree({ dataSource: 'A10' } as any);
+    categoryOptions.value = (res.data || []) as any[];
+  } catch (error) {
+    console.error('获取分类树失败:', error);
+  }
+};
+
+const findNodeById = (nodes: any[], id: string | number): any => {
+  for (const node of nodes) {
+    if (node.id === id) return node;
+    if (node.children) {
+      const found = findNodeById(node.children, id);
+      if (found) return found;
+    }
+  }
+  return null;
+};
+
+// 根据 syncCategoryId 从分类树获取二级+三级分组展示数据
+const getSubCategoryGroups = (syncCategoryId: string | number | null) => {
+  if (!syncCategoryId) return [];
+  const node = findNodeById(categoryOptions.value, syncCategoryId);
+  if (!node || !node.children) return [];
+  return node.children.map((child: any) => ({
+    title: child.label,
+    items: (child.children || []).map((sub: any) => sub.label)
+  }));
+};
+
+const handleAddRecommend = () => {
+  recommendEditIndex.value = -1;
+  Object.assign(recommendForm, {
+    name: '',
+    subTitle: '',
+    icon: '',
+    type: 'select',
+    categoryValue: [],
+    categoryLabel: '',
+    selectedProducts: [],
+    status: 1
+  });
+  recommendDialogVisible.value = true;
+};
+
+const handleEditRecommend = (row, index) => {
+  recommendEditIndex.value = index;
+  Object.assign(recommendForm, JSON.parse(JSON.stringify(row)));
+  recommendDialogVisible.value = true;
+};
+
+const getLabelsByValues = (values: (string | number)[], tree: any[]): string => {
+  const labels: string[] = [];
+  for (const val of values) {
+    const node = findNodeById(tree, val);
+    if (node) labels.push(node.label);
+  }
+  return labels.join(' > ');
+};
+
+const handleRecommendCategoryChange = (val) => {
+  if (val && val.length > 0) {
+    recommendForm.categoryLabel = getLabelsByValues(val, categoryOptions.value);
+  } else {
+    recommendForm.categoryLabel = '';
+  }
+};
+
+const submitRecommendForm = async () => {
+  // 序列化已选商品
+  const selectedProducts = recommendEditIndex.value > -1 ? recommendList.value[recommendEditIndex.value]?.selectedProducts || [] : [];
+
+  const data: RecommendCategoryConfigForm = {
+    name: recommendForm.name,
+    subTitle: recommendForm.subTitle,
+    iconUrl: recommendForm.icon,
+    dataType: recommendForm.type,
+    categoryLabel: recommendForm.categoryLabel || '',
+    categoryPath: (recommendForm.categoryValue || []).join(','),
+    selectedProductIds: JSON.stringify(selectedProducts),
+    status: recommendForm.status,
+    sortOrder: recommendList.value.length
+  };
+
+  try {
+    if (recommendEditIndex.value > -1) {
+      const id = recommendList.value[recommendEditIndex.value]?.id;
+      const isFromApi = id && String(id).length > 10;
+      const res = isFromApi ? await updateRecommendCategoryConfig({ ...data, id }) : await addRecommendCategoryConfig(data);
+      if (res.code === 200) {
+        ElMessage.success('修改成功');
+        recommendDialogVisible.value = false;
+        getRecommendCategoryList();
+      } else {
+        ElMessage.error(res.msg || '保存失败');
+      }
+    } else {
+      const res = await addRecommendCategoryConfig(data);
+      if (res.code === 200) {
+        ElMessage.success('新增成功');
+        recommendDialogVisible.value = false;
+        getRecommendCategoryList();
+      } else {
+        ElMessage.error(res.msg || '保存失败');
+      }
+    }
+  } catch (error) {
+    console.error('保存失败:', error);
+    ElMessage.error('保存失败');
+  }
+};
+
+const handleDeleteRecommend = async (index: number) => {
+  const item = recommendList.value[index];
+  try {
+    await ElMessageBox.confirm('确定要删除该推荐分类吗?', '提示');
+    const res = await delRecommendCategoryConfig(item.id);
+    if (res.code === 200) {
+      ElMessage.success('删除成功');
+      getRecommendCategoryList();
+    } else {
+      ElMessage.error(res.msg || '删除失败');
+    }
+  } catch (error) {
+    if (error !== 'cancel') {
+      console.error('删除失败:', error);
+      ElMessage.error('删除失败');
+    }
+  }
+};
+
+const moveRecommend = async (index: number, direction: number) => {
+  const newIndex = index + direction;
+  if (newIndex < 0 || newIndex >= recommendList.value.length) return;
+  const item = recommendList.value.splice(index, 1)[0];
+  recommendList.value.splice(newIndex, 0, item);
+
+  try {
+    const start = Math.min(index, newIndex);
+    const end = Math.max(index, newIndex);
+    for (let i = start; i <= end; i++) {
+      const row = recommendList.value[i] as any;
+      await updateRecommendCategoryConfig({
+        id: row.id,
+        name: row.name,
+        subTitle: row.subTitle,
+        iconUrl: row.icon || row.iconUrl,
+        dataType: row.type || row.dataType,
+        categoryLabel: row.categoryLabel || '',
+        status: row.status,
+        sortOrder: i
+      });
+    }
+  } catch (error) {
+    console.error('排序更新失败:', error);
+    ElMessage.error('排序更新失败');
+  }
+};
+
+// 推荐商品选择增强逻辑
+const selectedProductDialogVisible = ref(false);
+const productSelectionDrawerVisible = ref(false);
+const currentRecommendIndex = ref(-1);
+const drawerSelection = ref([]);
+const selectedProductsTableRef = ref(null);
+const drawerTableRef = ref(null);
+
+const selectedCurrentPage = ref(1);
+const selectedPageSize = ref(10);
+
+const pagedSelectedProducts = computed(() => {
+  const list = recommendList.value[currentRecommendIndex.value]?.selectedProducts || [];
+  const start = (selectedCurrentPage.value - 1) * selectedPageSize.value;
+  const end = start + selectedPageSize.value;
+  return list.slice(start, end);
+});
+
+const handleSelectedProductsSelectionChange = (val) => {
+  currentItemIdx.value = val; // 临时借用 currentItemIdx 存储选中的行
+};
+
+const openRecommendProductSelect = (index) => {
+  currentRecommendIndex.value = index;
+  selectedCurrentPage.value = 1;
+  selectedProductDialogVisible.value = true;
+};
+
+const submitSelectedProducts = async () => {
+  const item = recommendList.value[currentRecommendIndex.value];
+  if (!item) return;
+
+  const selectedProducts = item.selectedProducts || [];
+  const data: RecommendCategoryConfigForm = {
+    id: item.id,
+    name: item.name,
+    subTitle: item.subTitle,
+    iconUrl: (item as any).icon || (item as any).iconUrl || '',
+    dataType: (item as any).type || (item as any).dataType || 'select',
+    categoryLabel: (item as any).categoryLabel || '',
+    selectedProductIds: JSON.stringify(selectedProducts),
+    status: item.status
+  };
+
+  try {
+    const res = await updateRecommendCategoryConfig(data);
+    if (res.code === 200) {
+      selectedProductDialogVisible.value = false;
+      ElMessage.success('已选商品配置已保存');
+    } else {
+      ElMessage.error(res.msg || '保存失败');
+    }
+  } catch (error) {
+    console.error('保存已选商品失败:', error);
+    ElMessage.error('保存失败');
+  }
+};
+
+const openProductDrawer = async () => {
+  const currentList = recommendList.value[currentRecommendIndex.value]?.selectedProducts || [];
+  drawerSelection.value = JSON.parse(JSON.stringify(currentList));
+  productQueryParams.itemName = '';
+  productQueryParams.pageNum = 1;
+  productQueryParams.pageSize = selectPageSize.value;
+  selectCurrentPage.value = 1;
+  productSelectionDrawerVisible.value = true;
+  await getProductList();
+
+  // 回显勾选逻辑
+  nextTick(() => {
+    if (drawerTableRef.value) {
+      drawerTableRef.value.clearSelection();
+      productList.value.forEach((item) => {
+        if (currentList.some((exist) => exist.id === item.id)) {
+          drawerTableRef.value.toggleRowSelection(item, true);
+        }
+      });
+    }
+  });
+};
+
+const handleDrawerSelectionChange = (val) => {
+  drawerSelection.value = val;
+};
+
+const confirmDrawerSelection = () => {
+  // 直接全量覆盖,支持在抽屉里取消勾选来删除
+  recommendList.value[currentRecommendIndex.value].selectedProducts = JSON.parse(JSON.stringify(drawerSelection.value));
+
+  ElMessage.success('商品列表同步成功');
+  productSelectionDrawerVisible.value = false;
+};
+
+const removeSelectedProduct = (index) => {
+  recommendList.value[currentRecommendIndex.value].selectedProducts.splice(index, 1);
+  ElMessage.success('移除成功');
+};
+
+const batchRemoveSelectedProducts = () => {
+  const selectedRows = selectedProductsTableRef.value?.getSelectionRows() || [];
+  if (selectedRows.length === 0) return ElMessage.warning('请先勾选要移除的商品');
+
+  ElMessageBox.confirm(`确定移除选中的 ${selectedRows.length} 个商品吗?`, '提示').then(() => {
+    const currentList = recommendList.value[currentRecommendIndex.value].selectedProducts;
+    const selectedIds = selectedRows.map((r) => r.id);
+    recommendList.value[currentRecommendIndex.value].selectedProducts = currentList.filter((item) => !selectedIds.includes(item.id));
+    ElMessage.success('批量移除成功');
+  });
+};
+
+// 推荐预览滚动逻辑
+const recommendScrollRef = ref(null);
+const recShowLeft = ref(false);
+const recShowRight = ref(false);
+
+const updateRecArrows = () => {
+  if (!recommendScrollRef.value) return;
+  const { scrollLeft, scrollWidth, clientWidth } = recommendScrollRef.value;
+  recShowLeft.value = scrollLeft > 5;
+  recShowRight.value = scrollLeft + clientWidth < scrollWidth - 5;
+};
+
+const scrollRecommend = (direction) => {
+  const container = recommendScrollRef.value;
+  if (!container) return;
+  const scrollAmount = 400;
+  if (direction === 'left') {
+    container.scrollLeft -= scrollAmount;
+  } else {
+    container.scrollLeft += scrollAmount;
+  }
+};
+
+// 商品选择逻辑
+const selectDialogVisible = ref(false);
+const selectedTempId = ref(null);
+const selectCurrentPage = ref(1);
+const selectPageSize = ref(8);
+
+// 商品列表(API数据)
+const productList = ref<any[]>([]);
+const productTotal = ref(0);
+
+const productQueryParams = reactive({
+  pageNum: 1,
+  pageSize: 8,
+  itemName: '',
+  productStatus: 1
+});
+
+const brandQueryParams = reactive({
+  pageNum: 1,
+  pageSize: 8,
+  brandName: ''
+});
+
+/** 获取商品/品牌列表(根据广告模块类型自动切换) */
+const getProductList = async () => {
+  try {
+    if (selectDialogVisible.value && currentAdIdx.value === 2) {
+      // 品牌好店:查询品牌
+      brandQueryParams.brandName = productQueryParams.itemName;
+      brandQueryParams.pageNum = productQueryParams.pageNum;
+      brandQueryParams.pageSize = productQueryParams.pageSize;
+      const res = await listBrand(brandQueryParams);
+      productList.value = (res.rows || []).map((item: any) => ({
+        id: item.id,
+        name: item.brandName || '',
+        image: item.brandLogo || '',
+        price: ''
+      }));
+      productTotal.value = res.total || 0;
+    } else {
+      // 其他广告模块:查询商品
+      const res = await listBase(productQueryParams);
+      productList.value = (res.rows || []).map((item: any) => ({
+        id: item.id,
+        name: item.itemName || '',
+        image: item.productImage || item.productImageUrl || '',
+        price: item.memberPrice ?? item.minSellingPrice ?? item.marketPrice ?? '',
+        productNo: item.productNo || '',
+        isSelf: item.isSelf,
+        minOrderQuantity: item.minOrderQuantity || 1
+      }));
+      productTotal.value = res.total || 0;
+    }
+  } catch (error) {
+    console.error('获取列表失败:', error);
+    ElMessage.error('获取列表失败');
+  }
+};
+
+const filteredSelectList = computed(() => productList.value);
+
+const pagedSelectList = computed(() => productList.value);
+
+const onProductPageChange = (page: number) => {
+  productQueryParams.pageNum = page;
+  selectCurrentPage.value = page;
+  getProductList();
+};
+
+const onProductPageSizeChange = (size: number) => {
+  productQueryParams.pageSize = size;
+  productQueryParams.pageNum = 1;
+  selectCurrentPage.value = 1;
+  selectPageSize.value = size;
+  getProductList();
+};
+
+const openProductSelect = (index: number) => {
+  currentItemIdx.value = index;
+  selectedTempId.value = adForm.items[index].id;
+  productQueryParams.itemName = '';
+  productQueryParams.pageNum = 1;
+  productQueryParams.pageSize = selectPageSize.value;
+  selectCurrentPage.value = 1;
+  selectDialogVisible.value = true;
+  getProductList();
+};
+
+const confirmSelect = () => {
+  const item = productList.value.find((i) => i.id === selectedTempId.value);
+  if (item) {
+    const target = adForm.items[currentItemIdx.value];
+    target.id = item.id;
+    target.productId = item.id;
+    target.productName = item.name;
+    target.imageUrl = item.image;
+    target.price = item.price;
+    selectDialogVisible.value = false;
+    ElMessage.success('选择成功');
+  }
+};
+
+// 快捷入口模块逻辑
+const qePageIndex = ref(0);
+const quickEntrySettings = reactive({
+  id: null as string | number | null,
+  moduleName: '企业工作台',
+  jumpLink: ''
+});
+
+// 获取快捷入口模块配置
+const getQuickEntryModuleList = async () => {
+  try {
+    const res = await listQuickEntryModule();
+    if (res.code === 200 && res.rows && res.rows.length > 0) {
+      const data = res.rows[0];
+      quickEntrySettings.id = data.id;
+      quickEntrySettings.moduleName = data.moduleName || '';
+      quickEntrySettings.jumpLink = data.jumpLink || '';
+    }
+  } catch (error) {
+    console.error('获取快捷入口模块配置失败:', error);
+    ElMessage.error('获取快捷入口模块配置失败');
+  }
+};
+
+// 保存快捷入口模块配置
+const saveQuickEntryModule = async () => {
+  const data: QuickEntryModuleForm = {
+    moduleName: quickEntrySettings.moduleName,
+    jumpLink: quickEntrySettings.jumpLink
+  };
+
+  try {
+    let res;
+    if (quickEntrySettings.id) {
+      res = await updateQuickEntryModule({ ...data, id: quickEntrySettings.id });
+    } else {
+      res = await addQuickEntryModule(data);
+      if (res.code === 200 && res.data) {
+        quickEntrySettings.id = res.data.id;
+      }
+    }
+    if (res.code === 200) {
+      ElMessage.success('快捷入口模块配置保存成功');
+    } else {
+      ElMessage.error(res.msg || '保存失败');
+    }
+  } catch (error) {
+    console.error('保存快捷入口模块配置失败:', error);
+    ElMessage.error('保存失败');
+  }
+};
+
+const quickEntryList = ref<QuickEntryItemsVO[]>([]);
+
+// 获取快捷入口项列表
+const getQuickEntryItemsList = async () => {
+  try {
+    const res = await listQuickEntryItems();
+    if (res.code === 200 && res.rows) {
+      quickEntryList.value = res.rows.map((item: QuickEntryItemsVO) => ({
+        ...item,
+        icon: (item as any).iconUrl || '',
+        tag: (item as any).tagText || '',
+        link: (item as any).jumpLink || ''
+      }));
+    }
+  } catch (error) {
+    console.error('获取快捷入口项列表失败:', error);
+    ElMessage.error('获取快捷入口项列表失败');
+  }
+};
+
+const qePageCount = computed(() => Math.ceil(quickEntryList.value.filter((i) => i.status === 1).length / 8));
+
+const getPageItems = (pageIdx) => {
+  const activeItems = quickEntryList.value.filter((i) => i.status === 1);
+  return activeItems.slice(pageIdx * 8, (pageIdx + 1) * 8);
+};
+
+const quickEntryDialogVisible = ref(false);
+const quickEntryDialogType = ref('add');
+const qeEditIndex = ref(-1);
+const quickEntryForm = reactive({ name: '', icon: '', tag: '', link: '', status: 1 });
+
+const handleAddQuickEntry = () => {
+  quickEntryDialogType.value = 'add';
+  Object.assign(quickEntryForm, { name: '', icon: '', tag: '', link: '', status: 1 });
+  quickEntryDialogVisible.value = true;
+};
+
+const handleEditQuickEntry = (row, index) => {
+  quickEntryDialogType.value = 'edit';
+  qeEditIndex.value = index;
+  Object.assign(quickEntryForm, JSON.parse(JSON.stringify(row)));
+  quickEntryDialogVisible.value = true;
+};
+
+const submitQuickEntryForm = async () => {
+  if (!quickEntryForm.name) return ElMessage.warning('请输入入口名称');
+
+  const data: QuickEntryItemsForm = {
+    name: quickEntryForm.name,
+    iconUrl: quickEntryForm.icon,
+    tagText: quickEntryForm.tag,
+    jumpLink: quickEntryForm.link,
+    status: quickEntryForm.status,
+    sortOrder: quickEntryList.value.length
+  };
+
+  try {
+    if (quickEntryDialogType.value === 'add') {
+      const res = await addQuickEntryItems(data);
+      if (res.code === 200) {
+        ElMessage.success('新增成功');
+        quickEntryDialogVisible.value = false;
+        getQuickEntryItemsList();
+      } else {
+        ElMessage.error(res.msg || '新增失败');
+      }
+    } else {
+      const id = quickEntryList.value[qeEditIndex.value]?.id;
+      const res = await updateQuickEntryItems({ ...data, id });
+      if (res.code === 200) {
+        ElMessage.success('修改成功');
+        quickEntryDialogVisible.value = false;
+        getQuickEntryItemsList();
+      } else {
+        ElMessage.error(res.msg || '修改失败');
+      }
+    }
+  } catch (error) {
+    console.error('操作失败:', error);
+    ElMessage.error('操作失败');
+  }
+};
+
+const handleDeleteQuickEntry = async (index: number) => {
+  const item = quickEntryList.value[index];
+  try {
+    await ElMessageBox.confirm('确定要删除该入口吗?', '提示', { type: 'warning' });
+    const res = await delQuickEntryItems(item.id);
+    if (res.code === 200) {
+      ElMessage.success('删除成功');
+      getQuickEntryItemsList();
+    } else {
+      ElMessage.error(res.msg || '删除失败');
+    }
+  } catch (error) {
+    if (error !== 'cancel') {
+      console.error('删除失败:', error);
+      ElMessage.error('删除失败');
+    }
+  }
+};
+
+const moveQE = async (index: number, direction: number) => {
+  const newIndex = index + direction;
+  if (newIndex < 0 || newIndex >= quickEntryList.value.length) return;
+  const item = quickEntryList.value.splice(index, 1)[0];
+  quickEntryList.value.splice(newIndex, 0, item);
+
+  // 同步排序到后端
+  try {
+    const start = Math.min(index, newIndex);
+    const end = Math.max(index, newIndex);
+    for (let i = start; i <= end; i++) {
+      const row = quickEntryList.value[i] as any;
+      await updateQuickEntryItems({
+        id: row.id,
+        name: row.name,
+        iconUrl: row.icon || row.iconUrl,
+        tagText: row.tag || row.tagText,
+        jumpLink: row.link || row.jumpLink,
+        status: row.status,
+        sortOrder: i
+      });
+    }
+  } catch (error) {
+    console.error('排序更新失败:', error);
+    ElMessage.error('排序更新失败');
+  }
+};
+
+watch(
+  headerCategoryList,
+  () => {
+    nextTick(() => updateNavArrows());
+  },
+  { deep: true }
+);
+
+onMounted(() => {
+  startPlaceholderScroll();
+  // 获取当前配置
+  getCurrentSearch();
+  getCurrentAdLeftBtn();
+  getCarouselList();
+  getHeaderCategoryList();
+  getCategoryList();
+  getScenarioGlobalSettingsList();
+  getScenarioList();
+  getQuickEntryModuleList();
+  getQuickEntryItemsList();
+  getAdModuleList();
+  getRecommendThemeConfigList();
+  getRecommendCategoryList();
+  getCategoryTreeData();
+  // 获取左侧广告配置
+  nextTick(() => {
+    updateNavArrows();
+    updateRecArrows();
+  });
+  // 延迟再次检测,确保图片加载后布局稳定
+  setTimeout(() => {
+    updateRecArrows();
+  }, 500);
+  window.addEventListener('resize', updateNavArrows);
+  window.addEventListener('resize', updateRecArrows);
+});
+
+onUnmounted(() => {
+  if (timer) clearInterval(timer);
+  window.removeEventListener('resize', updateNavArrows);
+  window.removeEventListener('resize', updateRecArrows);
+});
+
+watch(
+  activeRecommendList,
+  () => {
+    nextTick(() => {
+      updateRecArrows();
+    });
+  },
+  { deep: true }
+);
+
+// 监听子页签切换,确保切换回推荐设置时更新箭头状态
+watch(activeSubTab, (newVal) => {
+  if (newVal === 'recommend') {
+    nextTick(() => {
+      setTimeout(() => {
+        updateRecArrows();
+      }, 300);
+    });
+  }
+});
+</script>
+
+<style scoped>
+/* 基础辅助类 */
+.flex-column {
+  display: flex;
+  flex-direction: column;
+}
+.flex-center {
+  display: flex;
+  align-items: center;
+}
+.gap-5 {
+  gap: 5px;
+}
+.gap-10 {
+  gap: 10px;
+}
+.m-b-30 {
+  margin-bottom: 30px;
+}
+
+.scenario-editor-container {
+  padding: 32px 40px;
+  display: flex;
+  flex-direction: column;
+  background-color: #fff; /* 纯白背景 */
+  min-height: 100%;
+}
+
+.preview-section-standard {
+  background: #fcfdfe;
+  border: 1px solid #eef2f6;
+  border-radius: 8px;
+  padding: 32px;
+  margin-bottom: 32px;
+}
+
+.section-title-standard {
+  font-size: 16px;
+  font-weight: bold;
+  color: #333;
+  margin-bottom: 24px;
+  display: flex;
+  align-items: center;
+}
+
+.section-title-standard::before {
+  content: '';
+  width: 4px;
+  height: 16px;
+  background-color: #e60012;
+  margin-right: 12px;
+  border-radius: 2px;
+}
+
+.config-section-standard {
+  padding: 32px 0;
+  border-bottom: 1px solid #f0f0f0;
+}
+
+.settings-form-standard {
+  padding-left: 16px;
+}
+
+.preview-tip {
+  font-size: 12px;
+  color: #999;
+  margin-top: 16px;
+}
+
+.rank-action-btns {
+  display: flex;
+  flex-direction: column;
+  align-items: center;
+  gap: 4px;
+}
+
+.rank-btn-mini {
+  cursor: pointer;
+  font-size: 14px;
+  color: #909399;
+  transition: color 0.2s;
+}
+
+.rank-btn-mini:hover {
+  color: #e60012;
+}
+
+.standard-table :deep(.el-table__header) th {
+  background-color: #f8fafc;
+  color: #606266;
+  font-weight: bold;
+}
+
+.parameter-settings {
+  flex: 1;
+  min-height: 0; /* 关键:防止 flex 子项被内容撑开 */
+  display: flex;
+  flex-direction: column;
+  background-color: #fff;
+  overflow: hidden;
+}
+
+.sub-tabs {
+  display: flex;
+  padding: 0 20px;
+  border-bottom: 1px solid #f0f0f0;
+  flex-shrink: 0;
+  height: 48px; /* 固定高度,防止切换时抖动 */
+  align-items: center;
+}
+
+.sub-tab-item {
+  padding: 12px 20px;
+  cursor: pointer;
+  font-size: 14px;
+  color: #666;
+  font-weight: 500;
+  position: relative;
+}
+
+.sub-tab-item.active {
+  color: var(--primary-color);
+}
+
+.sub-tab-item.active::after {
+  content: '';
+  position: absolute;
+  bottom: 0;
+  left: 20px;
+  right: 20px;
+  height: 2px;
+  background-color: var(--primary-color);
+}
+
+.content-body {
+  flex: 1;
+  overflow-y: auto;
+}
+
+/* 搜索编辑区布局 */
+.search-editor {
+  display: flex;
+  flex-direction: column;
+  padding: 20px 40px;
+  gap: 30px;
+}
+
+/* 预览区域 */
+.preview-section {
+  background-color: #fff;
+  border: 1px dashed #dcdfe6;
+  border-radius: 8px;
+  padding: 20px;
+}
+
+.preview-title {
+  font-size: 13px;
+  color: #909399;
+  margin-bottom: 15px;
+  font-weight: bold;
+}
+
+.live-preview-box {
+  background-color: #fff;
+  padding: 40px 20px;
+  border-radius: 4px;
+  box-shadow: 0 2px 12px rgba(0, 0, 0, 0.04);
+}
+
+/* 仿真 Mockup */
+.search-bar-mockup {
+  display: flex;
+  align-items: center;
+  max-width: 1400px; /* 进一步加宽容器 */
+  margin: 0 auto;
+  gap: 30px;
+}
+
+.mockup-left {
+  flex-shrink: 0;
+  text-align: center;
+}
+
+.main-title {
+  font-size: 32px;
+  font-weight: 800;
+  line-height: 1.2;
+}
+
+.sub-title {
+  font-size: 12px;
+  color: #999;
+  margin-top: 4px;
+  letter-spacing: 1px;
+}
+
+.mockup-center {
+  flex: 2; /* 增加中心区域的弹性占比,使搜索框更宽 */
+}
+
+.search-input-wrapper {
+  height: 44px;
+  border: 2px solid transparent; /* 由动态绑定控制颜色 */
+  border-radius: 8px;
+  display: flex;
+  align-items: center;
+  overflow: hidden;
+  position: relative;
+}
+
+.placeholder-scroll {
+  flex: 1;
+  padding: 0 15px;
+  height: 100%;
+  overflow: hidden;
+}
+
+.scroll-container {
+  height: 100%;
+  position: relative;
+}
+
+.scroll-item {
+  height: 100%;
+  display: flex;
+  align-items: center;
+  color: #999;
+  font-size: 15px;
+}
+
+.search-btn {
+  width: 90px;
+  height: 34px;
+  color: #fff;
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  font-weight: bold;
+  font-size: 15px;
+  cursor: pointer;
+  border-radius: 6px;
+  letter-spacing: 2px;
+  margin-right: 4px;
+  transition: opacity 0.2s;
+}
+
+.search-btn:hover {
+  opacity: 0.9;
+}
+
+.hot-words {
+  margin-top: 8px;
+  display: flex;
+  gap: 15px;
+  padding-left: 5px;
+}
+
+.hot-word {
+  font-size: 12px;
+  color: #999;
+  cursor: pointer;
+  transition: color 0.2s;
+}
+
+.hot-word:hover {
+  color: var(--theme-color);
+}
+
+.mockup-right {
+  flex-shrink: 0;
+}
+
+.cart-btn {
+  display: flex;
+  align-items: center;
+  gap: 8px;
+  padding: 8px 16px;
+  border: 1px solid transparent; /* 由动态绑定控制颜色 */
+  border-radius: 8px;
+  font-size: 14px;
+  cursor: pointer;
+  background-color: #fff;
+  transition: all 0.2s;
+}
+
+.cart-btn:hover {
+  filter: brightness(0.95);
+}
+
+/* 广告图编辑区 */
+.carousel-editor {
+  padding: 20px 40px;
+  display: flex;
+  flex-direction: column;
+  gap: 40px;
+}
+
+.editor-section {
+  display: flex;
+  flex-direction: column;
+  gap: 20px;
+}
+
+.section-header {
+  display: flex;
+  flex-direction: column;
+  gap: 4px;
+}
+
+.section-title {
+  font-size: 16px;
+  font-weight: bold;
+  color: #333;
+}
+
+.section-desc {
+  font-size: 13px;
+  color: #999;
+}
+
+/* 左侧广告设置 */
+.left-ad-container {
+  display: flex;
+  flex-direction: column;
+  gap: 10px;
+}
+
+.left-ad-preview-wrapper {
+  width: 80px;
+  height: 460px;
+  border-radius: 8px;
+  overflow: hidden;
+  position: relative;
+  cursor: pointer;
+  transition: width 0.4s cubic-bezier(0.4, 0, 0.2, 1);
+  box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
+  border: 1px solid #eee;
+}
+
+.left-ad-preview-wrapper.expanded {
+  width: 790px;
+}
+
+.left-ad-img {
+  width: 790px; /* 固定宽度,外层容器裁剪 */
+  height: 460px;
+  object-fit: cover;
+  display: block;
+}
+
+.left-ad-empty {
+  width: 100%;
+  height: 100%;
+  background-color: #f5f7fa;
+  display: flex;
+  flex-direction: column;
+  align-items: center;
+  justify-content: center;
+  color: #909399;
+  border: 1px dashed #dcdfe6;
+}
+
+.ad-actions {
+  position: absolute;
+  top: 20px;
+  right: 20px;
+  display: flex;
+  gap: 10px;
+  background: rgba(255, 255, 255, 0.9);
+  padding: 8px;
+  border-radius: 30px;
+  box-shadow: 0 2px 8px rgba(0, 0, 0, 0.15);
+}
+
+.left-ad-settings {
+  flex: 1;
+  display: flex;
+  flex-direction: column;
+  gap: 15px;
+  padding: 10px 0; /* 移除背景和边框 */
+}
+
+.settings-input-ad {
+  max-width: 500px;
+}
+
+.left-ad-tip {
+  display: flex;
+  align-items: center;
+  gap: 6px;
+  font-size: 13px;
+  color: #a8abb2;
+}
+
+/* 轮播图预览 */
+.carousel-preview-box {
+  padding: 10px 0;
+}
+
+.preview-mockup {
+  width: 552px;
+  height: 190px;
+  border-radius: 8px;
+  overflow: hidden;
+  box-shadow: 0 10px 30px rgba(0, 0, 0, 0.1);
+  background-color: #f5f7fa;
+}
+
+/* 强力锁定轮播图指示器至左下角 */
+:deep(.el-carousel__indicators--horizontal) {
+  left: 20px !important;
+  right: auto !important;
+  bottom: 15px !important;
+  transform: none !important;
+  width: fit-content !important;
+  margin: 0 !important;
+  padding: 0 !important;
+  display: flex !important;
+  justify-content: flex-start !important;
+}
+
+:deep(.el-carousel__indicator--horizontal) {
+  display: inline-block !important;
+  padding: 0 3px !important;
+}
+
+:deep(.el-carousel__indicators--inside) {
+  left: 20px !important;
+  transform: none !important;
+}
+
+:deep(.el-carousel__indicator) {
+  padding: 0 3px;
+}
+
+:deep(.el-carousel__button) {
+  width: 6px;
+  height: 6px;
+  border-radius: 50%;
+  background-color: rgba(255, 255, 255, 0.4) !important;
+  opacity: 1 !important;
+  transition: all 0.3s;
+}
+
+:deep(.el-carousel__indicator.is-active .el-carousel__button) {
+  width: 20px; /* 激活状态变为长胶囊 */
+  border-radius: 10px;
+  background-color: #ffffff !important;
+}
+
+.carousel-slide {
+  width: 100%;
+  height: 100%;
+}
+
+.carousel-slide img {
+  width: 100%;
+  height: 100%;
+  object-fit: cover;
+}
+
+.carousel-empty {
+  height: 100%;
+  display: flex;
+  flex-direction: column;
+  align-items: center;
+  justify-content: center;
+  color: #ccc;
+  background-color: #f9f9f9;
+}
+
+/* 轮播列表 */
+.carousel-list-box {
+  display: flex;
+  flex-direction: column;
+  gap: 15px;
+  margin-top: 10px;
+}
+
+.list-toolbar {
+  display: flex;
+  justify-content: space-between;
+  align-items: center;
+  padding: 0 5px;
+}
+
+.btn-add-carousel {
+  padding: 0 25px;
+  font-weight: bold;
+}
+
+/* 自定义表头样式 */
+:deep(.table-header-custom) {
+  background-color: #f8f9fb !important;
+  color: #333 !important;
+  font-weight: bold !important;
+  height: 50px;
+}
+
+.drag-tip {
+  font-size: 12px;
+  color: #a8abb2;
+}
+
+.table-img {
+  width: 140px;
+  height: 48px;
+  border-radius: 4px;
+  border: 1px solid #f0f0f0;
+  display: block;
+}
+
+.rank-box {
+  display: flex;
+  flex-direction: column;
+  align-items: center;
+  gap: 2px;
+}
+
+.rank-icon {
+  cursor: pointer;
+  color: #909399;
+  font-size: 16px;
+  transition: color 0.2s;
+}
+
+.rank-icon:hover {
+  color: #409eff;
+}
+
+/* 弹窗上传样式 */
+.upload-placeholder {
+  width: 240px;
+  height: 82px;
+  border: 1px dashed #dcdfe6;
+  border-radius: 6px;
+  cursor: pointer;
+  overflow: hidden;
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  background-color: #f5f7fa;
+  transition: border-color 0.2s;
+}
+
+.upload-placeholder:hover {
+  border-color: #409eff;
+}
+
+.form-preview-img {
+  width: 100%;
+  height: 100%;
+  object-fit: cover;
+}
+
+.upload-icon {
+  font-size: 28px;
+  color: #8c939d;
+}
+
+.upload-tip {
+  font-size: 12px;
+  color: #999;
+  margin-top: 5px;
+  line-height: 1.4;
+}
+
+.dialog-form-inner {
+  padding: 30px 40px; /* 大幅增加内边距,使其更高、更大气 */
+}
+
+.dialog-form-inner :deep(.el-form-item) {
+  margin-bottom: 25px; /* 增加表单项间距 */
+}
+
+.list-scroll-enter-active,
+.list-scroll-leave-active {
+  transition: all 0.5s ease;
+  position: absolute;
+  width: 100%;
+}
+
+.list-scroll-enter-from {
+  transform: translateY(100%);
+  opacity: 0;
+}
+
+.list-scroll-leave-to {
+  transform: translateY(-100%);
+  opacity: 0;
+}
+
+/* 设置表单 */
+.settings-section {
+  padding: 0 10px;
+}
+
+.settings-input {
+  max-width: 600px;
+}
+
+.color-picker-wrap {
+  display: flex;
+  align-items: center;
+}
+
+.tip-text {
+  font-size: 12px;
+  color: #999;
+  margin-top: 4px;
+}
+
+.color-val {
+  margin-left: 10px;
+  color: #666;
+  font-family: monospace;
+}
+
+/* 热词配置样式 */
+.hot-words-config {
+  display: flex;
+  flex-direction: column;
+  gap: 10px;
+  width: 800px; /* 加宽配置区域 */
+}
+
+.hot-word-row {
+  display: flex;
+  align-items: center;
+  gap: 10px;
+}
+
+.hot-word-input-name {
+  width: 200px; /* 稍微加宽名称框 */
+}
+
+.hot-word-input-link {
+  flex: 1; /* 此时地址框会占据剩余更多空间 */
+}
+
+.add-hotword-btn {
+  width: fit-content;
+  padding: 0;
+  margin-top: 5px;
+}
+
+.footer-actions {
+  padding: 20px 40px;
+  border-top: 1px solid #f0f0f0;
+  display: flex;
+  gap: 15px;
+  flex-shrink: 0;
+}
+
+.btn-confirm {
+  padding: 0 30px;
+}
+
+.btn-reset {
+  padding: 0 30px;
+}
+/* 分类设置编辑区 */
+.category-editor {
+  padding: 20px 40px;
+  display: flex;
+  flex-direction: column;
+  gap: 40px;
+}
+
+.category-preview-container {
+  padding: 20px 0;
+  display: flex;
+  justify-content: flex-start;
+}
+
+/* 图1: 仿真菜单 */
+.category-menu-mockup {
+  width: 280px;
+  height: 398px;
+  background: #f8f9fa;
+  border-radius: 4px;
+  padding: 10px 0;
+  box-shadow: 0 2px 10px rgba(0, 0, 0, 0.05);
+  position: relative;
+  z-index: 10;
+  overflow: visible; /* 必须可见,否则遮罩层无法伸出 */
+}
+
+/* 仿真菜单项 */
+.menu-item {
+  height: 44px;
+  display: flex;
+  align-items: center;
+  padding: 0 15px;
+  cursor: pointer;
+  position: relative;
+  transition: all 0.1s;
+  background: transparent;
+  box-sizing: border-box;
+}
+
+.menu-item:hover {
+  background-color: #fff !important;
+  color: v-bind(categoryThemeColor);
+  border: 1px solid v-bind(categoryThemeColor);
+  border-right: none;
+  border-radius: 12px 0 0 12px;
+  z-index: 1000;
+  margin-left: 10px;
+  padding-left: 15px;
+  width: calc(100% - 10px);
+}
+
+.menu-icon {
+  width: 16px;
+  height: 16px;
+  margin-right: 15px; /* 增加间距 */
+  color: #999;
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  flex-shrink: 0;
+}
+
+.menu-icon img {
+  width: 100%;
+  height: 100%;
+  object-fit: contain;
+}
+
+.menu-name {
+  font-size: 14px;
+  color: #333;
+  flex: 1;
+  white-space: nowrap;
+  overflow: hidden;
+  text-overflow: ellipsis;
+}
+
+/* 遮盖层:仅抹除中间垂直线,保留上下边框连贯 */
+.menu-item:hover::after {
+  content: '';
+  position: absolute;
+  top: 0; /* 保持在边框内侧 */
+  bottom: 0; /* 保持在边框内侧 */
+  right: -1px; /* 贴合右边缘 */
+  width: 2px; /* 覆盖面板左边框 */
+  background: #fff;
+  z-index: 1001;
+}
+
+.menu-item:hover .menu-icon {
+  color: v-bind(categoryThemeColor);
+}
+
+.menu-name {
+  font-size: 14px;
+  color: #333;
+}
+
+.menu-item:hover .menu-name {
+  color: v-bind(categoryThemeColor);
+  font-weight: bold;
+}
+
+/* 图2: 右滑面板 */
+.category-panel-mockup {
+  position: absolute;
+  left: 100%; /* 紧贴菜单项右侧 */
+  margin-left: 0;
+  top: -1px; /* 顶部边框对齐 */
+  width: 980px;
+  min-height: 480px;
+  background: #fff;
+  box-shadow: 15px 15px 40px rgba(0, 0, 0, 0.1);
+  border-radius: 0 12px 12px 12px;
+  z-index: 500;
+  border: 1px solid v-bind(categoryThemeColor);
+  display: none;
+  flex-direction: column;
+  box-sizing: border-box;
+  cursor: default;
+}
+
+/* 悬停时显示面板 */
+.menu-item:hover .category-panel-mockup {
+  display: flex;
+}
+
+.panel-content {
+  flex: 1;
+  padding: 25px 30px;
+  position: relative; /* 为品牌位定位提供基准 */
+  overflow: hidden;
+}
+
+.panel-tabs {
+  display: flex;
+  gap: 15px;
+  margin-bottom: 25px;
+  padding-right: 200px; /* 为右上角品牌位留出空间 */
+}
+
+.panel-tab-item {
+  padding: 6px 14px;
+  background-color: #f5f5f5;
+  color: #666;
+  font-size: 12px;
+  border-radius: 4px;
+  cursor: pointer;
+  transition: all 0.2s;
+}
+
+.panel-tab-item:hover {
+  background-color: #f5f5f5; /* 保持浅灰或根据主题调整 */
+  color: v-bind(categoryThemeColor);
+  filter: brightness(0.95);
+}
+
+.panel-body {
+  display: flex;
+  flex-direction: column; /* 改为垂直布局,内容横向撑开 */
+  flex: 1;
+}
+
+.panel-main {
+  flex: 1;
+  overflow-y: auto;
+  max-height: 400px;
+}
+
+.category-group {
+  margin-bottom: 15px;
+  display: flex;
+}
+
+.group-title {
+  width: 80px;
+  font-size: 12px;
+  font-weight: bold;
+  color: #333;
+  flex-shrink: 0;
+  line-height: 1.6;
+}
+
+.group-items {
+  flex: 1;
+  display: flex;
+  flex-wrap: wrap;
+  gap: 6px 15px;
+}
+
+.group-item {
+  font-size: 12px;
+  color: #666;
+  cursor: pointer;
+  transition: color 0.2s;
+}
+
+.group-item:hover {
+  color: v-bind(categoryThemeColor);
+}
+
+/* 品牌位移至右上角 (图3) */
+.panel-side {
+  display: none; /* 移除侧边栏布局 */
+}
+
+.brand-box {
+  position: absolute;
+  top: 25px;
+  right: 30px;
+  display: flex;
+  flex-direction: column;
+  align-items: flex-end; /* 右对齐更美观 */
+  z-index: 105;
+}
+
+.brand-main-title {
+  font-size: 20px;
+  font-weight: 900;
+  color: v-bind(categoryThemeColor);
+  margin-bottom: 4px;
+  display: flex;
+  align-items: baseline;
+}
+
+.brand-strong {
+  color: #333;
+  margin-left: 2px;
+  font-size: 20px;
+}
+
+.brand-notes {
+  display: flex;
+  gap: 5px;
+  color: v-bind(categoryThemeColor);
+  font-size: 12px;
+  justify-content: flex-end;
+}
+
+.note-item {
+  cursor: pointer;
+}
+
+.note-sep {
+  color: #eee;
+  margin: 0 2px;
+}
+
+/* 工具栏主题色设置 - 专业版 */
+.theme-color-setting-pro {
+  display: flex;
+  align-items: center;
+  gap: 8px;
+  margin-right: 20px;
+}
+
+.theme-color-setting-pro .label {
+  font-size: 14px;
+  color: #333;
+}
+
+.theme-color-setting-pro .value {
+  font-size: 13px;
+  color: #666;
+  margin-left: 5px;
+  font-family: monospace;
+}
+
+/* 覆盖 el-color-picker 样式 */
+:deep(.theme-color-setting-pro .el-color-picker__trigger) {
+  width: 28px;
+  height: 28px;
+  padding: 2px;
+  border-radius: 4px;
+}
+
+/* 列表样式 */
+.table-icon-preview {
+  width: 24px;
+  height: 24px;
+}
+
+.tag-wrap {
+  display: flex;
+  flex-wrap: wrap;
+  gap: 5px;
+}
+
+.m-r-5 {
+  margin-right: 5px;
+}
+.m-t-20 {
+  margin-top: 20px;
+}
+
+/* 弹窗样式 */
+.panel-config-section {
+  background: #fafafa;
+  padding: 15px;
+  border-radius: 4px;
+  border: 1px dashed #eee;
+}
+
+/* 移除冗余的 .config-subtitle 定义,统一使用下方全局定义的样式 */
+
+.notes-config-list {
+  display: flex;
+  flex-direction: column;
+  gap: 10px;
+}
+
+.note-config-row {
+  display: flex;
+  gap: 10px;
+  align-items: center;
+}
+
+.field-tip {
+  font-size: 12px;
+  color: #999;
+  margin-top: 5px;
+}
+
+/* 正方形图标上传 */
+.upload-placeholder-square {
+  width: 80px;
+  height: 80px;
+  border: 1px dashed #d9d9d9;
+  border-radius: 4px;
+  cursor: pointer;
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  background-color: #fafafa;
+  transition: border-color 0.2s;
+}
+
+.upload-placeholder-square:hover {
+  border-color: v-bind(categoryThemeColor);
+}
+
+.upload-placeholder-square .upload-icon {
+  font-size: 20px;
+  color: #999;
+}
+
+.form-preview-img-square {
+  width: 16px;
+  height: 16px;
+  object-fit: contain;
+}
+
+/* 头部分类样式 */
+.header-category-editor {
+  padding: 20px 40px;
+  display: flex;
+  flex-direction: column;
+  gap: 40px;
+}
+
+.header-preview-outer {
+  width: 100%;
+  background: #f8f9fa;
+  padding: 40px 20px;
+  border-radius: 8px;
+  display: block; /* 改为 block 以便支持内部滚动 */
+  overflow-x: auto; /* 允许内部滚动,防止溢出 */
+  box-sizing: border-box;
+}
+
+.header-preview-box {
+  width: 1350px; /* 强制保持 1350px 宽度以高度还原设计 */
+  height: 60px;
+  background: #fff;
+  box-shadow: 0 4px 20px rgba(0, 0, 0, 0.08);
+  border-radius: 4px;
+  display: flex;
+  align-items: center;
+  padding: 0 40px; /* 为箭头预留空间 */
+  margin: 0 auto; /* 在容器足够大时居中 */
+  flex-shrink: 0; /* 禁止缩小 */
+  position: relative;
+  overflow: hidden; /* 关键:确保内部超出部分被剪裁,由滚动容器处理 */
+}
+
+.header-nav-scroll {
+  flex: 1;
+  overflow-x: auto;
+  scrollbar-width: none;
+  display: flex;
+  align-items: center;
+  scroll-behavior: smooth;
+}
+
+.header-nav-scroll::-webkit-scrollbar {
+  display: none;
+}
+
+.header-nav-list {
+  display: flex;
+  align-items: center;
+  gap: 30px;
+  flex-shrink: 0;
+}
+
+.nav-arrow {
+  position: absolute;
+  top: 50%;
+  transform: translateY(-50%);
+  width: 24px;
+  height: 24px;
+  background: #eee;
+  border-radius: 50%;
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  cursor: pointer;
+  z-index: 10;
+  color: #666;
+  transition: all 0.2s;
+  box-shadow: 0 2px 6px rgba(0, 0, 0, 0.1);
+}
+
+.nav-arrow:hover {
+  background: #e0e0e0;
+  color: #333;
+}
+
+.left-arrow {
+  left: 8px;
+}
+
+.right-arrow {
+  right: 8px;
+}
+
+.header-nav-item {
+  display: flex;
+  align-items: center;
+  gap: 8px;
+  cursor: pointer;
+  white-space: nowrap;
+  flex-shrink: 0;
+}
+
+.header-nav-item .item-icon {
+  width: 22px;
+  height: 22px;
+  display: flex;
+  align-items: center;
+  justify-content: center;
+}
+
+.header-nav-item .item-icon img {
+  width: 100%;
+  height: 100%;
+  object-fit: contain;
+}
+
+.header-nav-item .item-text {
+  font-size: 16px;
+  font-weight: bold;
+  color: #333;
+  transition: color 0.2s;
+}
+
+.header-nav-item:hover .item-text {
+  color: var(--hover-color);
+}
+
+/* 广告模块样式 */
+.ad-preview-grid {
+  display: flex;
+  flex-wrap: nowrap;
+  gap: 20px;
+  max-width: 1600px;
+  margin: 0;
+}
+
+.ad-item {
+  background: #f8f9fa;
+  border-radius: 12px;
+  padding: 15px;
+  position: relative;
+  overflow: hidden;
+  box-sizing: border-box;
+}
+
+.ad-item:hover .ad-hover-mask {
+  display: flex;
+}
+
+.ad-hover-mask {
+  position: absolute;
+  top: 0;
+  left: 0;
+  right: 0;
+  bottom: 0;
+  background: rgba(0, 0, 0, 0.4);
+  display: none;
+  align-items: center;
+  justify-content: center;
+  z-index: 100;
+}
+
+.ad-header {
+  display: flex;
+  justify-content: space-between;
+  align-items: baseline;
+  margin-bottom: 12px;
+}
+
+.ad-title-main {
+  font-size: 18px;
+  font-weight: 800;
+  color: #333;
+}
+
+.ad-title-sub {
+  font-size: 13px;
+}
+
+.ad-title-sub.orange {
+  color: #f58220;
+}
+.ad-title-sub.gray {
+  color: #999;
+}
+
+/* Ad 1: 百亿补贴 */
+.ad-products-subsidy {
+  display: flex;
+  gap: 15px;
+}
+
+.ad-subsidy .product-item {
+  flex: 1;
+  text-align: center;
+}
+
+.ad-subsidy .product-img {
+  width: 94px;
+  height: 94px;
+  background: #fff;
+  border-radius: 8px;
+  margin-bottom: 8px;
+  overflow: hidden;
+}
+
+.ad-subsidy .product-img img {
+  width: 100%;
+  height: 100%;
+  object-fit: contain;
+}
+
+.ad-subsidy .product-price {
+  color: #e60012;
+  font-weight: bold;
+  font-size: 15px;
+}
+
+.ad-ranking {
+  padding: 12px !important;
+}
+
+.ad-ranking .ad-header {
+  margin-bottom: 8px;
+}
+
+/* Ad 2: 榜单 */
+.ad-products-ranking {
+  display: flex;
+  gap: 8px;
+  flex: 1;
+}
+
+.ranking-item {
+  flex: 1;
+  background: #fff;
+  border-radius: 8px;
+  padding: 6px 5px 0;
+  position: relative;
+  text-align: center;
+  display: flex;
+  flex-direction: column;
+  align-items: center;
+  overflow: hidden;
+  height: 135px; /* 高度调小一点 */
+}
+
+.ranking-badge {
+  display: inline-block;
+  background: #fff3e5;
+  color: #f58220;
+  font-size: 11px;
+  padding: 2px 10px;
+  border-radius: 20px;
+  white-space: nowrap;
+  margin-bottom: 4px;
+  transform: scale(0.9); /* 略微缩小标签以腾出空间 */
+}
+
+.ranking-item .product-img {
+  width: 80px; /* 略微缩小图片从 84 降至 80 */
+  height: 80px;
+  margin-bottom: 4px;
+  display: flex;
+  align-items: center;
+  justify-content: center;
+}
+
+.ranking-item .product-img img {
+  width: 100%;
+  height: 100%;
+  object-fit: contain;
+}
+
+.ranking-footer {
+  background: #fff1f1;
+  color: #e60012;
+  font-size: 12px;
+  padding: 5px 0;
+  font-weight: bold;
+  width: 100%;
+  margin-top: auto;
+  line-height: 1.2;
+}
+
+/* Ad 3: 品牌 */
+.ad-brands-content {
+  display: flex;
+  gap: 10px;
+}
+
+.brand-item {
+  flex: 1;
+  text-align: center;
+}
+
+.brand-logo {
+  width: 50px;
+  height: 50px;
+  margin: 0 auto 8px;
+  background: #fff;
+  border-radius: 4px;
+  padding: 5px;
+}
+
+.brand-logo img {
+  width: 100%;
+  height: 100%;
+  object-fit: contain;
+}
+
+.brand-name {
+  font-size: 11px;
+  color: #0071bc;
+  margin-bottom: 5px;
+  height: 20px;
+  line-height: 20px;
+  white-space: nowrap;
+  overflow: hidden;
+  text-overflow: ellipsis;
+  width: 100%;
+}
+
+.brand-tag-btn {
+  display: inline-block;
+  padding: 2px 10px;
+  border: 1px solid #e60012;
+  color: #e60012;
+  border-radius: 10px;
+  font-size: 11px;
+}
+
+/* Ad 4 & 5: 精选/新品 */
+.ad-products-selection {
+  display: flex;
+  gap: 10px;
+}
+
+.selection-item {
+  flex: 1;
+  text-align: center;
+}
+
+.selection-item .product-img {
+  width: 84px;
+  height: 84px;
+  background: #fff;
+  border-radius: 8px;
+  margin-bottom: 5px;
+}
+
+.selection-item .product-img img {
+  width: 100%;
+  height: 100%;
+  object-fit: contain;
+}
+
+.product-price-row {
+  display: flex;
+  align-items: baseline;
+  justify-content: flex-start;
+  gap: 2px;
+}
+
+.product-price-row.center {
+  justify-content: center;
+}
+
+.p-unit {
+  font-size: 12px;
+  color: #e60012;
+  font-weight: bold;
+}
+.p-val {
+  font-size: 16px;
+  color: #e60012;
+  font-weight: 800;
+}
+.p-tag {
+  background: #3fa9f5;
+  color: #fff;
+  font-size: 10px;
+  padding: 0 4px;
+  border-radius: 2px;
+  margin-left: 2px;
+}
+
+/* 选择弹窗样式 */
+.select-item-row {
+  display: flex;
+  align-items: center;
+  padding: 12px;
+  border: 1px solid #eee;
+  border-radius: 8px;
+  margin-bottom: 10px;
+  cursor: pointer;
+  transition: all 0.2s;
+}
+
+.select-item-row:hover {
+  border-color: #409eff;
+  background: #f0f7ff;
+}
+.select-item-row.active {
+  border-color: #409eff;
+  background: #f0f7ff;
+}
+
+.select-item-img {
+  width: 80px;
+  height: 80px;
+  object-fit: contain;
+  margin-right: 20px;
+  background: #f9f9f9;
+  border-radius: 4px;
+}
+
+.select-item-info {
+  flex: 1;
+}
+.select-item-name {
+  font-size: 14px;
+  color: #333;
+  margin-bottom: 4px;
+}
+.select-item-price {
+  color: #e60012;
+  font-weight: bold;
+}
+
+.select-pagination {
+  margin-top: 30px;
+  display: flex;
+  justify-content: center;
+  padding-bottom: 20px;
+}
+
+.select-item-id {
+  font-size: 11px;
+  color: #999;
+  margin-top: 2px;
+}
+
+/* 场景方案预览样式 */
+.scenario-preview-outer {
+  width: 100%;
+  overflow-x: auto;
+  padding: 10px 0;
+}
+
+.scenario-preview-box-clean {
+  width: 1600px;
+  max-width: 100%;
+  height: 158px;
+  background: var(--s-theme-color, #66e0a3);
+  border-radius: 16px; /* 加大圆角 */
+  display: flex;
+  align-items: center;
+  padding: 0 20px 0 32px; /* 减小左边距使标题区左移 */
+  box-sizing: border-box;
+  position: relative;
+  overflow: hidden;
+}
+
+.s-title-group {
+  display: flex;
+  align-items: center;
+  gap: 12px;
+  margin-bottom: 12px;
+  white-space: nowrap; /* 强制不换行,向后追加 */
+}
+
+.s-main-title {
+  font-size: 24px; /* 调整为 24px */
+  font-weight: 900;
+  color: #fff;
+  flex-shrink: 0; /* 确保不收缩 */
+}
+
+.s-sub-title-inline {
+  font-size: 24px; /* 调整为 24px */
+  font-weight: 900;
+  color: #fff;
+  flex-shrink: 0; /* 确保不收缩 */
+}
+
+.s-btn-wrap {
+  margin-top: 15px;
+}
+
+.s-btn-premium {
+  display: inline-flex;
+  align-items: center;
+  background-color: #fff;
+  color: #e60012;
+  font-size: 14px;
+  font-weight: bold;
+  padding: 6px 20px;
+  border-radius: 20px;
+}
+
+.scenario-card-premium {
+  width: 288px;
+  height: 130px;
+  border-radius: 8px;
+  display: flex;
+  flex-direction: column;
+  position: relative;
+}
+
+.card-top-header {
+  height: 42px; /* 固定高度,确保下方图片间距准确 */
+  display: flex;
+  justify-content: space-between;
+  align-items: center;
+  padding: 0 12px;
+  box-sizing: border-box;
+}
+
+.card-titles-group {
+  display: flex;
+  align-items: center;
+  gap: 8px;
+}
+
+.card-main-title {
+  font-size: 15px;
+  font-weight: bold;
+}
+
+.card-sub-title {
+  font-size: 13px;
+  font-weight: bold;
+}
+
+.card-arrow-icon {
+  width: 18px;
+  height: 18px;
+  border-radius: 50%;
+  color: #fff;
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  font-size: 10px;
+}
+
+.card-image-content {
+  width: 272px;
+  height: 80px;
+  margin: 0 8px 8px 8px; /* 左右下保留相同间距 (8px) */
+  border-radius: 4px;
+  overflow: hidden;
+}
+
+.card-image-content img {
+  width: 100%;
+  height: 100%;
+  object-fit: cover;
+}
+
+/* 响应式逻辑:优先隐藏最后一个方案卡片,再隐藏副标题 */
+@media (max-width: 1680px) {
+  .scenario-preview-box-clean {
+    width: 1300px;
+  }
+  .hidden-card-fourth {
+    display: none !important;
+  }
+}
+
+@media (max-width: 1400px) {
+  .scenario-preview-box-clean {
+    width: 1000px;
+  }
+  .s-sub-title-inline {
+    display: none !important;
+  }
+}
+
+.scenario-header-left {
+  width: 320px;
+  flex-shrink: 0;
+  display: flex;
+  flex-direction: column;
+}
+
+.scenario-cards-wrap {
+  flex: 1;
+  display: flex;
+  justify-content: flex-end;
+  gap: 12px; /* 间距调小 */
+}
+
+.scenario-card {
+  width: 288px;
+  height: 130px;
+  background-color: #fff;
+  border-radius: 12px;
+  padding: 12px;
+  box-sizing: border-box;
+  display: flex;
+  flex-direction: column;
+  transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
+  cursor: pointer;
+  box-shadow: 0 4px 12px rgba(0, 0, 0, 0.05);
+  border: 1px solid rgba(0, 0, 0, 0.02);
+}
+
+.scenario-card:hover {
+  transform: translateY(-8px);
+  box-shadow: 0 12px 24px rgba(0, 0, 0, 0.1);
+  border-color: rgba(102, 224, 163, 0.3);
+}
+
+.card-top {
+  display: flex;
+  justify-content: space-between;
+  align-items: center;
+  margin-bottom: 8px;
+  padding: 0 5px;
+}
+
+.card-titles {
+  display: flex;
+  align-items: center;
+  gap: 8px;
+}
+
+.card-main-title {
+  font-size: 18px;
+  font-weight: bold;
+}
+
+.card-sub-title {
+  font-size: 12px;
+}
+
+.card-arrow {
+  width: 18px;
+  height: 18px;
+  border-radius: 50%;
+  color: #fff;
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  font-size: 10px;
+}
+
+.card-img {
+  width: 272px;
+  height: 80px;
+  border-radius: 6px;
+  overflow: hidden;
+  background-color: #f5f7fa;
+}
+
+.card-img img {
+  width: 100%;
+  height: 100%;
+  object-fit: cover;
+}
+
+/* 响应式调整 */
+@media (max-width: 1500px) {
+  .scenario-preview-box {
+    width: 1300px;
+  }
+  .hidden-card-fourth {
+    display: none !important;
+  }
+}
+
+@media (max-width: 1200px) {
+  .scenario-preview-box {
+    width: 1000px;
+  }
+  .s-sub-title {
+    display: none !important;
+  }
+}
+
+/* 广告模块弹窗精修 */
+.ad-setup-dialog :deep(.el-dialog__body) {
+  padding: 30px 40px;
+  max-height: 700px;
+  overflow-y: auto;
+}
+
+.brand-name-display {
+  font-size: 15px;
+  font-weight: bold;
+  color: #333;
+}
+
+.ad-setup-dialog :deep(.el-table__row) {
+  height: 90px; /* 增加行高,大气美观 */
+}
+
+.config-subtitle {
+  font-size: 15px;
+  font-weight: bold;
+  color: #333;
+  margin: 25px 0 15px;
+  display: flex;
+  align-items: center;
+  line-height: 1;
+}
+
+.config-subtitle::before {
+  content: '';
+  width: 3px;
+  height: 14px;
+  background: #e60012;
+  margin-right: 8px;
+}
+
+/* 快捷入口预览样式 */
+.quick-entry-preview-outer {
+  display: flex;
+  flex-direction: column;
+  align-items: flex-start;
+  padding: 20px 0;
+}
+
+.qe-mockup-card {
+  width: 230px;
+  height: 167px;
+  background: #fff;
+  border-radius: 12px;
+  box-shadow: 0 4px 20px rgba(0, 0, 0, 0.08);
+  padding: 12px 16px;
+  box-sizing: border-box;
+  display: flex;
+  flex-direction: column;
+  overflow: hidden;
+  position: relative;
+}
+
+.qe-card-header {
+  display: flex;
+  justify-content: space-between;
+  align-items: center;
+  margin-bottom: 12px;
+}
+
+.qe-card-title {
+  font-size: 16px;
+  font-weight: bold;
+  color: #333;
+}
+
+.qe-header-arrow {
+  font-size: 12px;
+  color: #999;
+}
+
+.qe-grid-container {
+  width: 198px;
+  height: 108px;
+  margin: 0 auto;
+  position: relative;
+  overflow: hidden;
+}
+
+.qe-grid-wrapper {
+  display: flex;
+  transition: transform 0.4s cubic-bezier(0.4, 0, 0.2, 1);
+  height: 100%;
+}
+
+.qe-grid-page {
+  width: 198px;
+  flex-shrink: 0;
+  display: grid;
+  grid-template-columns: repeat(4, 1fr);
+  grid-template-rows: repeat(2, 1fr);
+  gap: 12px 0;
+}
+
+.qe-item {
+  display: flex;
+  flex-direction: column;
+  align-items: center;
+  gap: 4px;
+}
+
+.qe-icon-wrap {
+  width: 24px;
+  height: 24px;
+  position: relative;
+  display: flex;
+  align-items: center;
+  justify-content: center;
+}
+
+.qe-icon-img {
+  width: 24px;
+  height: 24px;
+  object-fit: contain;
+}
+
+.qe-icon-placeholder {
+  font-size: 20px;
+  color: #666;
+}
+
+.qe-tag-bubble {
+  position: absolute;
+  top: -8px;
+  right: -15px;
+  background: #ff4d4f;
+  color: #fff;
+  font-size: 9px; /* 精确 9px */
+  padding: 1px 5px;
+  border-radius: 10px;
+  white-space: nowrap;
+  transform: scale(0.9);
+  z-index: 100; /* 提升至最高图层 */
+}
+
+.qe-name {
+  font-size: 11px; /* 精确 11px */
+  color: #333;
+  text-align: center;
+  white-space: nowrap;
+  overflow: hidden;
+  text-overflow: ellipsis;
+  width: 100%;
+}
+
+.qe-nav-btns {
+  position: absolute;
+  top: 0;
+  left: 0;
+  right: 0;
+  bottom: 0;
+  pointer-events: none;
+}
+
+.qe-nav-btn {
+  position: absolute;
+  top: 50%;
+  transform: translateY(-50%);
+  width: 22px;
+  height: 22px;
+  background: rgba(255, 255, 255, 0.9);
+  border-radius: 50%;
+  box-shadow: 0 2px 8px rgba(0, 0, 0, 0.15);
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  cursor: pointer;
+  pointer-events: auto;
+  color: #666;
+  z-index: 110; /* 提升至最高图层,确保不被内容遮挡 */
+}
+
+.qe-nav-btn:hover {
+  background: #fff;
+  color: #e60012;
+}
+
+.qe-nav-btn.prev {
+  left: -10px;
+}
+.qe-nav-btn.next {
+  right: -10px;
+}
+
+/* 快捷入口编辑器特定样式 */
+.quick-entry-editor {
+  display: flex;
+  flex-direction: column;
+  padding: 20px 40px;
+  gap: 30px;
+}
+
+.config-form-inline {
+  background: #fcfdfe;
+  padding: 20px;
+  border-radius: 8px;
+  border: 1px solid #eef2f6;
+}
+
+.table-icon-cell {
+  display: flex;
+  align-items: center;
+  justify-content: center;
+}
+/* 推荐设置编辑区容器 */
+.recommend-editor-container {
+  padding: 24px;
+  border-bottom: none !important;
+}
+
+/* 推荐设置预览样式 */
+.recommend-preview-outer {
+  width: 100%;
+  padding: 10px 0 20px;
+  position: relative;
+}
+
+.recommend-preview-container {
+  width: 1600px !important; /* 严格按照要求设置背景尺寸 */
+  height: 88px !important;
+  position: relative;
+  background: #fff;
+  border-radius: 8px;
+  box-shadow: 0 2px 10px rgba(0, 0, 0, 0.05);
+  overflow: hidden;
+  margin-bottom: 10px;
+}
+
+.recommend-preview-box {
+  width: 100% !important;
+  height: 100% !important;
+  display: flex;
+  align-items: center;
+  padding: 0 40px;
+  box-sizing: border-box;
+  overflow-x: auto;
+  scroll-behavior: smooth;
+  -ms-overflow-style: none;
+  scrollbar-width: none;
+}
+
+.recommend-preview-box::-webkit-scrollbar {
+  display: none;
+}
+
+.recommend-item {
+  display: flex;
+  align-items: center;
+  padding: 10px 24px;
+  margin: 0 4px;
+  cursor: pointer;
+  transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
+  height: 60px;
+  border-radius: 8px;
+  flex-shrink: 0;
+}
+
+.recommend-item:hover {
+  background: #f5f5f5;
+}
+
+.recommend-item.active {
+  background: transparent;
+}
+
+.recommend-icon {
+  width: 32px;
+  height: 32px;
+  margin-right: 12px;
+  flex-shrink: 0;
+  display: flex;
+  align-items: center;
+  justify-content: center;
+}
+
+.table-icon-preview {
+  width: 40px;
+  height: 40px;
+  background: #f5f5f5;
+  border-radius: 4px;
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  margin: 0 auto;
+  overflow: hidden;
+}
+
+.row-icon {
+  width: 100%;
+  height: 100%;
+  object-fit: contain;
+}
+
+.recommend-text {
+  display: flex;
+  flex-direction: column;
+  white-space: nowrap;
+}
+
+.r-main-title {
+  font-size: 16px;
+  font-weight: bold;
+  line-height: 1.4;
+  white-space: nowrap;
+}
+
+.r-sub-title {
+  font-size: 12px;
+  color: #999;
+  margin-top: 2px;
+  white-space: nowrap;
+}
+
+.recommend-nav-btn {
+  position: absolute;
+  top: 50%;
+  transform: translateY(-50%);
+  width: 32px;
+  height: 32px;
+  background: rgba(255, 255, 255, 0.9);
+  box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
+  border-radius: 50%;
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  cursor: pointer;
+  color: #666;
+  z-index: 100;
+  transition: all 0.2s;
+  border: 1px solid #eee;
+}
+
+.recommend-nav-btn:hover {
+  color: var(--r-theme-color);
+  background: #fff;
+  box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
+}
+
+.recommend-nav-btn.prev {
+  left: 10px;
+}
+.recommend-nav-btn.next {
+  right: 10px;
+}
+
+/* 修复色块标签换行 */
+.config-section-standard :deep(.el-form-item__label) {
+  white-space: nowrap;
+}
+/* 去除底部冗余线条 */
+.recommend-editor-container :deep(.el-table__inner-wrapper::before),
+.recommend-editor-container :deep(.el-table--border::after),
+.recommend-editor-container :deep(.el-table--group::after),
+.recommend-editor-container :deep(.el-table::before) {
+  display: none !important;
+}
+
+.recommend-icon img {
+  width: 100%;
+  height: 100%;
+  object-fit: contain;
+  background: #f2f2f2;
+  border-radius: 4px;
+}
+
+/* 已选商品管理弹窗增强 */
+.selected-products-header {
+  display: flex;
+  justify-content: space-between;
+  align-items: center;
+  margin-bottom: 20px;
+  padding: 10px 0;
+}
+
+.selected-products-header .right-info {
+  font-size: 14px;
+  color: #666;
+}
+
+.selected-products-header .count {
+  color: #e60012;
+  font-weight: 800;
+  margin: 0 4px;
+}
+
+.price-text {
+  color: #e60012;
+  font-weight: bold;
+}
+
+.empty-placeholder {
+  padding: 60px 0;
+}
+
+/* 商品多选抽屉样式 */
+.product-selection-drawer :deep(.el-drawer__body) {
+  padding: 0;
+  background: #fcfdfe;
+}
+
+.drawer-content-wrapper {
+  display: flex;
+  flex-direction: column;
+  height: 100%;
+}
+
+.drawer-search-bar {
+  padding: 20px;
+  background: #fff;
+  border-bottom: 1px solid #f0f2f5;
+}
+
+.drawer-stat-bar {
+  padding: 12px 20px;
+  background: #e6f7ff;
+  border: 1px solid #91d5ff;
+  color: #1890ff;
+  font-size: 13px;
+  display: flex;
+  align-items: center;
+  gap: 8px;
+  margin: 15px 20px 0;
+  border-radius: 4px;
+}
+
+.drawer-stat-bar .highlight {
+  font-weight: bold;
+  font-size: 16px;
+  margin: 0 2px;
+}
+
+.drawer-content-wrapper :deep(.el-table) {
+  margin: 15px 20px;
+  width: auto !important;
+}
+
+.drawer-product-info {
+  display: flex;
+  align-items: center;
+  gap: 12px;
+}
+
+.drawer-product-info .mini-img {
+  width: 50px;
+  height: 50px;
+  border-radius: 4px;
+  border: 1px solid #f0f0f0;
+  flex-shrink: 0;
+}
+
+.drawer-product-info .detail {
+  flex: 1;
+  overflow: hidden;
+}
+
+.drawer-product-info .name {
+  font-size: 13px;
+  color: #333;
+  line-height: 1.4;
+  margin-bottom: 4px;
+  display: -webkit-box;
+  -webkit-line-clamp: 2;
+  -webkit-box-orient: vertical;
+  overflow: hidden;
+}
+
+.drawer-product-info .id {
+  font-size: 11px;
+  color: #999;
+}
+
+.drawer-product-info .price {
+  color: #e60012;
+  font-weight: bold;
+  margin-top: 2px;
+}
+
+.drawer-pagination {
+  padding: 20px;
+  display: flex;
+  justify-content: center;
+  background: #fff;
+  border-top: 1px solid #f0f2f5;
+}
+
+.drawer-footer-actions {
+  display: flex;
+  justify-content: flex-end;
+  gap: 12px;
+  padding: 20px;
+}
+
+.m-l-10 {
+  margin-left: 10px;
+}
+</style>

+ 141 - 0
src/views/mall/miniPageSet/index.vue

@@ -0,0 +1,141 @@
+<template>
+  <div>
+    <el-card class="box-card mt-[15px] !border-none" shadow="never" v-loading="loading">
+      <div class="flex-1">
+        <div class="flex items-center border-l-[3px] border-primary pl-[5px] leading-[1.1] mt-[10px]">
+          <span class="text-[14px]">正在编辑</span>
+          <span class="text-[14px] text-primary mx-[3px]">H5/小程序</span>
+          <span class="text-[14px]">分类页面</span>
+        </div>
+        <el-form :model="formData" label-width="100px" ref="formRef">
+          <el-form-item label="分类类型" class="mt-[30px]">
+            <el-radio-group v-model="formData.level">
+              <el-radio value="1">一级分类</el-radio>
+              <el-radio value="2">二级分类</el-radio>
+            </el-radio-group>
+          </el-form-item>
+          <el-form-item label="分类模板">
+            <template v-for="(item, index) in templateList" :key="index">
+              <div
+                v-if="formData.level == item.type"
+                :class="[
+                  'w-[150px] border-[1px] border-[#ededed] border-solid overflow-hidden text-[#ededed] rounded-[4px] mr-[20px] relative',
+                  formData.template === item.template ? 'border-color text-color' : ''
+                ]"
+                @click="levelChange(item.template)"
+              >
+                <img class="w-[100%]" :src="item.preview" fit-object="contain" />
+                <span class="iconfont iconicon-selected absolute right-0 bottom-[-8px]"></span>
+              </div>
+            </template>
+          </el-form-item>
+          <el-form-item label="页面名称">
+            <el-input v-model="formData.pageTitle" clearable placeholder="请输入页面名称" style="width: 280px" maxlength="10" show-word-limit />
+          </el-form-item>
+          <el-form-item label="搜索栏">
+            <el-radio-group v-model="formData.searchControl">
+              <el-radio value="1">开启</el-radio>
+              <el-radio value="0">关闭</el-radio>
+            </el-radio-group>
+          </el-form-item>
+          <el-form-item>
+            <el-button type="primary" class="mt-[15px]" @click="onSave">保存</el-button>
+          </el-form-item>
+        </el-form>
+      </div>
+    </el-card>
+  </div>
+</template>
+
+<script lang="ts" setup>
+import { miniPageSetCurrent, miniPageSetAdd, miniPageSetEdit } from '@/api/diy/index';
+import category_style1_1 from '@/assets/images/diy/category_style1_1.png';
+import category_style2_1 from '@/assets/images/diy/category_style2_1.png';
+import category_style2_2 from '@/assets/images/diy/category_style2_2.png';
+const loading = ref<boolean>(false);
+const id = ref<any>(null);
+const formData = ref<any>({
+  level: '1',
+  template: 'style-1',
+  pageTitle: '',
+  searchControl: '1'
+});
+const templateList = reactive([
+  {
+    template: 'style-1',
+    preview: category_style1_1,
+    type: '1'
+  },
+  {
+    template: 'style-1',
+    preview: category_style2_1,
+    type: '2'
+  },
+  {
+    template: 'style-2',
+    preview: category_style2_2,
+    type: '2'
+  }
+]);
+
+const getData = () => {
+  loading.value = true;
+  miniPageSetCurrent({})
+    .then((res) => {
+      loading.value = false;
+      if (res.code == 200) {
+        if (res.data.id) {
+          formData.value = {
+            level: res.data.level,
+            template: res.data.template,
+            pageTitle: res.data.pageTitle,
+            searchControl: res.data.searchControl
+          };
+          id.value = res.data.id;
+        } else {
+          formData.value = {
+            level: '1',
+            template: 'style-1',
+            pageTitle: '',
+            searchControl: '1'
+          };
+          id.value = null;
+        }
+      }
+    })
+    .catch(() => {
+      loading.value = false;
+    });
+};
+
+const levelChange = (value: any) => {
+  formData.value.template = value;
+};
+
+const onSave = () => {
+  const api = id.value ? miniPageSetEdit : miniPageSetAdd;
+  if (id.value) {
+    formData.value.id = id.value;
+  }
+  api(formData.value)
+    .then((res) => {
+      if (res.code == 200) {
+        getData();
+      }
+    })
+    .catch(() => {});
+};
+
+onMounted(() => {
+  getData();
+});
+</script>
+<style lang="scss" scoped>
+.border-color {
+  border-color: var(--el-color-primary);
+}
+
+.text-color {
+  color: var(--el-color-primary);
+}
+</style>

+ 7 - 5
src/views/mall/navigation/index.vue

@@ -12,7 +12,7 @@
                 class="w-[22px] h-[22px] mb-[5px] leading-1"
                 :src="item.iconNormal"
                 fit="cover"
-                v-if="['1', '2'].includes(diyBottomData.value.type.toString())"
+                v-if="['icon_text', 'icon'].includes(diyBottomData.value.type.toString())"
               >
                 <template #error>
                   <div class="image-slot flex justify-center items-center mt-1">
@@ -24,7 +24,7 @@
               </el-image>
               <span
                 class="text-[12px]"
-                v-if="['1', '3'].includes(diyBottomData.value.type.toString())"
+                v-if="['icon_text', 'text'].includes(diyBottomData.value.type.toString())"
                 :style="{ 'color': diyBottomData.value.bottomNavTextColor }"
                 >{{ item.text }}</span
               >
@@ -81,9 +81,9 @@
             <el-tab-pane label="样式设置" name="setStyle">
               <el-form-item label="导航类型">
                 <el-radio-group v-model="diyBottomData.value.type" class="ml-4">
-                  <el-radio value="1" size="large">图文</el-radio>
-                  <el-radio value="2" size="large">图片</el-radio>
-                  <el-radio value="3" size="large">文字</el-radio>
+                  <el-radio value="icon_text" size="large">图文</el-radio>
+                  <el-radio value="icon" size="large">图片</el-radio>
+                  <el-radio value="text" size="large">文字</el-radio>
                 </el-radio-group>
               </el-form-item>
               <el-form-item label="文字颜色">
@@ -155,6 +155,7 @@ const getData = () => {
         diyBottomData.value.bottomNavBgColor = res.data.bottomNavBgColor;
         diyBottomData.value.bottomNavTextColor = res.data.bottomNavTextColor;
         diyBottomData.value.bottomNavSelectedColor = res.data.bottomNavSelectedColor;
+        diyBottomData.value.type = res.data.navStyle;
       }
     })
     .catch(() => {
@@ -180,6 +181,7 @@ const onSave = () => {
     bottomNavTextColor: diyBottomData.value.bottomNavTextColor,
     bottomNavSelectedColor: diyBottomData.value.bottomNavSelectedColor,
     navItems: diyBottomData.value.list,
+    navStyle: diyBottomData.value.type,
     id: id.value
   };
   navigationSave(datas)

+ 10 - 4
src/views/mall/pageCategory/index.vue

@@ -13,6 +13,12 @@
             <el-form-item label="分类名称" prop="name">
               <el-input v-model="queryParams.name" placeholder="请输入" clearable @keyup.enter="handleQuery" />
             </el-form-item>
+            <el-form-item label="页面类型" prop="pageType">
+              <el-select v-model="queryParams.pageType" placeholder="页面类型" clearable>
+                <el-option label="pc端" :value="'1'" />
+                <el-option label="小程序端" :value="'2'" />
+              </el-select>
+            </el-form-item>
             <!-- <el-form-item label="添加时间" prop="createTime">
               <el-date-picker clearable v-model="queryParams.createTime" type="date" value-format="YYYY-MM-DD" placeholder="请选择添加时间" />
             </el-form-item> -->
@@ -56,7 +62,7 @@
           </template>
         </el-table-column>
         <el-table-column prop="level" align="center" label="层级" width="80"></el-table-column>
-        <el-table-column prop="pageType" align="center" label="页面类型" width="80">
+        <el-table-column prop="pageType" align="center" label="页面类型" width="120">
           <template #default="scope">
             <el-tag> {{ scope.row.pageType == '1' ? 'pc端' : '小程序' }}</el-tag>
           </template>
@@ -228,7 +234,6 @@ const initFormData: PageCategoryForm = {
   sort: 100,
   status: 1, // 默认设置为正常状态
   isMer: 0,
-  pageType: '1',
   createTime: undefined
 };
 const initData: PageData<PageCategoryForm, PageCategoryQuery> = {
@@ -237,7 +242,8 @@ const initData: PageData<PageCategoryForm, PageCategoryQuery> = {
     title: undefined,
     pageKey: undefined,
     name: undefined,
-    createTime: undefined
+    createTime: undefined,
+    pageType: '1'
   },
   rules: {
     parentId: [{ required: true, message: '上级分类不能为空', trigger: 'blur' }],
@@ -336,7 +342,7 @@ const handleStatusChange = async (row: PageCategoryVO) => {
 /** 获取分类树数据方法 */
 const loadCategoryTreeData = async () => {
   try {
-    const res = await getCategoryTree();
+    const res = await getCategoryTree(queryParams.value.pageType);
     if (res.data) {
       // 直接使用后端返回的数据,不再添加前端顶级父类节点
       categoryOptions.value = res.data as CategoryOptionsType[];

+ 11 - 4
src/views/mall/pageLink/index.vue

@@ -10,6 +10,12 @@
             <el-form-item label="页面名称" prop="name">
               <el-input v-model="queryParams.name" placeholder="请输入页面名称" clearable @keyup.enter="handleQuery" />
             </el-form-item>
+            <el-form-item label="页面类型" prop="pageType">
+              <el-select v-model="queryParams.pageType" placeholder="页面类型" clearable>
+                <el-option label="pc端" :value="'1'" />
+                <el-option label="小程序端" :value="'2'" />
+              </el-select>
+            </el-form-item>
             <!-- <el-form-item label="添加时间" prop="createTime">
               <el-date-picker clearable v-model="queryParams.createTime" type="date" value-format="YYYY-MM-DD" placeholder="请选择添加时间" />
             </el-form-item> -->
@@ -66,7 +72,7 @@
           </template>
         </el-table-column>
         <el-table-column label="排序" align="center" prop="sort" />
-        <el-table-column prop="pageType" align="center" label="页面类型" width="80">
+        <el-table-column prop="pageType" align="center" label="页面类型" width="120">
           <template #default="scope">
             <el-tag> {{ scope.row.pageType == '1' ? 'pc端' : '小程序' }}</el-tag>
           </template>
@@ -201,8 +207,7 @@ const initFormData: PageLinkForm = {
   status: 1,
   sort: 100,
   createTime: undefined,
-  isMer: 0,
-  pageType: '1'
+  isMer: 0
 };
 const data = reactive<PageData<PageLinkForm, PageLinkQuery>>({
   form: { ...initFormData },
@@ -212,6 +217,7 @@ const data = reactive<PageData<PageLinkForm, PageLinkQuery>>({
     cateId: undefined,
     name: undefined,
     createTime: undefined,
+    pageType: '1',
     params: {}
   },
   rules: {}
@@ -223,7 +229,7 @@ const { queryParams, form, rules } = toRefs(data);
 const getCategorys = async () => {
   try {
     // 获取分类树形结构用于选择器
-    const res = await getCategoryTree();
+    const res = await getCategoryTree(queryParams.value.pageType);
     if (res.data) {
       categoryOptions.value = res.data as CategoryOptionsType[];
     }
@@ -240,6 +246,7 @@ const getList = async () => {
   pageLinkList.value = res.rows;
   total.value = res.total;
   loading.value = false;
+  getCategorys();
 };
 
 /** 取消按钮 */