Browse Source

feat(vipSite): 新增大客户专属站点功能模块

- 添加客户站点配置相关API接口
- 新增频道页面配置管理功能
- 实现站点楼层管理模块
- 添加站点产品配置功能
- 实现站点设置管理功能
- 新增VIP站点主界面及配置页面
- 优化项目案例页面字段显示逻辑
- 添加商品批量导入功能
- 修复装饰案例页面数据映射问题
- 优化礼品楼层广告页面交互体验
肖路 1 month ago
parent
commit
70de7047db

+ 11 - 0
src/api/customerOperation/customerList.ts

@@ -11,6 +11,17 @@ export function getCustomerList(params: any) {
   });
 }
 
+
+// /**
+//  * 客户列表-分页查询
+//  */
+// export function getCustomerList(params: any) {
+//   return request({
+//     url: '/customer/customerInfo/list',
+//     method: 'get',
+//     params
+//   });
+// }
 /**
  * 获取客户详情
  */

+ 63 - 0
src/api/product/site/index.ts

@@ -0,0 +1,63 @@
+import request from '@/utils/request';
+import { AxiosPromise } from 'axios';
+import { SiteVO, SiteForm, SiteQuery } from '@/api/product/site/types';
+
+/**
+ * 查询客户站点配置列表
+ * @param query
+ * @returns {*}
+ */
+
+export const listSite = (query?: SiteQuery): AxiosPromise<SiteVO[]> => {
+  return request({
+    url: '/product/site/list',
+    method: 'get',
+    params: query
+  });
+};
+
+/**
+ * 查询客户站点配置详细
+ * @param id
+ */
+export const getSite = (id: string | number): AxiosPromise<SiteVO> => {
+  return request({
+    url: '/product/site/' + id,
+    method: 'get'
+  });
+};
+
+/**
+ * 新增客户站点配置
+ * @param data
+ */
+export const addSite = (data: SiteForm) => {
+  return request({
+    url: '/product/site',
+    method: 'post',
+    data: data
+  });
+};
+
+/**
+ * 修改客户站点配置
+ * @param data
+ */
+export const updateSite = (data: SiteForm) => {
+  return request({
+    url: '/product/site',
+    method: 'put',
+    data: data
+  });
+};
+
+/**
+ * 删除客户站点配置
+ * @param id
+ */
+export const delSite = (id: string | number | Array<string | number>) => {
+  return request({
+    url: '/product/site/' + id,
+    method: 'delete'
+  });
+};

+ 305 - 0
src/api/product/site/types.ts

@@ -0,0 +1,305 @@
+export interface SiteVO {
+  /**
+   * 主键ID,自增
+   */
+  id: string | number;
+
+  /**
+   * 站点名称
+   */
+  siteName: string;
+
+  /**
+   * 站点域名
+   */
+  siteDomain: string;
+
+  /**
+   * 客户id
+   */
+  clientId: string | number;
+
+  /**
+   * 客户编号
+   */
+  clientNo: string;
+
+  /**
+   * 客户名称
+   */
+  clientName: string;
+
+  /**
+   * 站点Logo路径或URL
+   */
+  siteLogo: string;
+
+  /**
+   * 登录页背景图路径或URL
+   */
+  loginImg: string;
+
+  /**
+   * 是否显示(例如:1-显示,0-隐藏)
+   */
+  isShow: number;
+
+  /**
+   * 站点启用开始时间
+   */
+  startTime: string;
+
+  /**
+   * 站点启用结束时间
+   */
+  endTime: string;
+
+  /**
+   * 站点标题(HTML title)
+   */
+  siteTitle: string;
+
+  /**
+   * 站点描述(SEO description)
+   */
+  siteDescribe: string;
+
+  /**
+   * 站点关键词(SEO keywords)
+   */
+  siteKeywords: string;
+
+  /**
+   * 背景颜色(如 #FFFFFF 或 CSS 颜色值)
+   */
+  backgroundColor: string;
+
+  /**
+   * 是否为主站(例如:1-是,0-否)
+   */
+  isMainsite: number;
+
+  /**
+   * 副标题
+   */
+  subTitle: string;
+
+  /**
+   * 备注
+   */
+  remark: string;
+
+  /**
+   * 协议文件路径
+   */
+  protocolFile: string;
+
+  /**
+   * 状态(0正常 1停用)
+   */
+  status: string;
+}
+
+export interface SiteForm extends BaseEntity {
+  /**
+   * 主键ID,自增
+   */
+  id?: string | number;
+
+  /**
+   * 站点名称
+   */
+  siteName?: string;
+
+  /**
+   * 站点域名
+   */
+  siteDomain?: string;
+
+  /**
+   * 客户id
+   */
+  clientId?: string | number;
+
+  /**
+   * 客户编号
+   */
+  clientNo?: string;
+
+  /**
+   * 客户名称
+   */
+  clientName?: string;
+
+  /**
+   * 站点Logo路径或URL
+   */
+  siteLogo?: string;
+
+  /**
+   * 登录页背景图路径或URL
+   */
+  loginImg?: string;
+
+  /**
+   * 是否显示(例如:1-显示,0-隐藏)
+   */
+  isShow?: number;
+
+  /**
+   * 站点启用开始时间
+   */
+  startTime?: string;
+
+  /**
+   * 站点启用结束时间
+   */
+  endTime?: string;
+
+  /**
+   * 站点标题(HTML title)
+   */
+  siteTitle?: string;
+
+  /**
+   * 站点描述(SEO description)
+   */
+  siteDescribe?: string;
+
+  /**
+   * 站点关键词(SEO keywords)
+   */
+  siteKeywords?: string;
+
+  /**
+   * 背景颜色(如 #FFFFFF 或 CSS 颜色值)
+   */
+  backgroundColor?: string;
+
+  /**
+   * 是否为主站(例如:1-是,0-否)
+   */
+  isMainsite?: number;
+
+  /**
+   * 副标题
+   */
+  subTitle?: string;
+
+  /**
+   * 备注
+   */
+  remark?: string;
+
+  /**
+   * 协议文件路径
+   */
+  protocolFile?: string;
+
+  /**
+   * 状态(0正常 1停用)
+   */
+  status?: string;
+}
+
+export interface SiteQuery extends PageQuery {
+  /**
+   * 站点名称
+   */
+  siteName?: string;
+
+  /**
+   * 站点域名
+   */
+  siteDomain?: string;
+
+  /**
+   * 客户id
+   */
+  clientId?: string | number;
+
+  /**
+   * 客户编号
+   */
+  clientNo?: string;
+
+  /**
+   * 客户名称
+   */
+  clientName?: string;
+
+  /**
+   * 站点Logo路径或URL
+   */
+  siteLogo?: string;
+
+  /**
+   * 登录页背景图路径或URL
+   */
+  loginImg?: string;
+
+  /**
+   * 是否显示(例如:1-显示,0-隐藏)
+   */
+  isShow?: number;
+
+  /**
+   * 站点启用开始时间
+   */
+  startTime?: string;
+
+  /**
+   * 站点启用结束时间
+   */
+  endTime?: string;
+
+  /**
+   * 站点标题(HTML title)
+   */
+  siteTitle?: string;
+
+  /**
+   * 站点描述(SEO description)
+   */
+  siteDescribe?: string;
+
+  /**
+   * 站点关键词(SEO keywords)
+   */
+  siteKeywords?: string;
+
+  /**
+   * 背景颜色(如 #FFFFFF 或 CSS 颜色值)
+   */
+  backgroundColor?: string;
+
+  /**
+   * 是否为主站(例如:1-是,0-否)
+   */
+  isMainsite?: number;
+
+  /**
+   * 副标题
+   */
+  subTitle?: string;
+
+  /**
+   * 协议文件路径
+   */
+  protocolFile?: string;
+
+  /**
+   * 状态(0正常 1停用)
+   */
+  status?: string;
+
+  /**
+   * 平台标识
+   */
+  platformCode?: string;
+
+  /**
+   * 日期范围参数
+   */
+  params?: any;
+}

+ 63 - 0
src/api/product/siteChannelPage/index.ts

@@ -0,0 +1,63 @@
+import request from '@/utils/request';
+import { AxiosPromise } from 'axios';
+import { SiteChannelPageVO, SiteChannelPageForm, SiteChannelPageQuery } from '@/api/product/siteChannelPage/types';
+
+/**
+ * 查询频道页面配置列表
+ * @param query
+ * @returns {*}
+ */
+
+export const listSiteChannelPage = (query?: SiteChannelPageQuery): AxiosPromise<SiteChannelPageVO[]> => {
+  return request({
+    url: '/product/siteChannelPage/list',
+    method: 'get',
+    params: query
+  });
+};
+
+/**
+ * 查询频道页面配置详细
+ * @param id
+ */
+export const getSiteChannelPage = (id: string | number): AxiosPromise<SiteChannelPageVO> => {
+  return request({
+    url: '/product/siteChannelPage/' + id,
+    method: 'get'
+  });
+};
+
+/**
+ * 新增频道页面配置
+ * @param data
+ */
+export const addSiteChannelPage = (data: SiteChannelPageForm) => {
+  return request({
+    url: '/product/siteChannelPage',
+    method: 'post',
+    data: data
+  });
+};
+
+/**
+ * 修改频道页面配置
+ * @param data
+ */
+export const updateSiteChannelPage = (data: SiteChannelPageForm) => {
+  return request({
+    url: '/product/siteChannelPage',
+    method: 'put',
+    data: data
+  });
+};
+
+/**
+ * 删除频道页面配置
+ * @param id
+ */
+export const delSiteChannelPage = (id: string | number | Array<string | number>) => {
+  return request({
+    url: '/product/siteChannelPage/' + id,
+    method: 'delete'
+  });
+};

+ 160 - 0
src/api/product/siteChannelPage/types.ts

@@ -0,0 +1,160 @@
+export interface SiteChannelPageVO {
+  /**
+   * 主键ID,自增
+   */
+  id: string | number;
+
+  /**
+   * 客户编号
+   */
+  clientNo: string;
+
+  /**
+   * 客户id
+   */
+  clientId: string | number;
+
+  /**
+   * 简称
+   */
+  shortName: string;
+
+  /**
+   * 频道名称
+   */
+  channelName: string;
+
+  /**
+   * 频道类型
+   */
+  channelType: string;
+
+  /**
+   * 排序序号
+   */
+  serial: number;
+
+  /**
+   * 副标题
+   */
+  subTitle: string;
+
+  /**
+   * 图片路径或URL
+   */
+  pic: string;
+
+  /**
+   * 状态(0正常 1停用)
+   */
+  status: string;
+}
+
+export interface SiteChannelPageForm extends BaseEntity {
+  /**
+   * 主键ID,自增
+   */
+  id?: string | number;
+
+  /**
+   * 客户编号
+   */
+  clientNo?: string;
+
+  /**
+   * 客户id
+   */
+  clientId?: string | number;
+
+  /**
+   * 简称
+   */
+  shortName?: string;
+
+  /**
+   * 频道名称
+   */
+  channelName?: string;
+
+  /**
+   * 频道类型
+   */
+  channelType?: string;
+
+  /**
+   * 排序序号
+   */
+  serial?: number;
+
+  /**
+   * 副标题
+   */
+  subTitle?: string;
+
+  /**
+   * 图片路径或URL
+   */
+  pic?: string;
+
+  /**
+   * 状态(0正常 1停用)
+   */
+  status?: string;
+}
+
+export interface SiteChannelPageQuery extends PageQuery {
+  /**
+   * 客户编号
+   */
+  clientNo?: string;
+
+  /**
+   * 客户id
+   */
+  clientId?: string | number;
+
+  /**
+   * 简称
+   */
+  shortName?: string;
+
+  /**
+   * 频道名称
+   */
+  channelName?: string;
+
+  /**
+   * 频道类型
+   */
+  channelType?: string;
+
+  /**
+   * 排序序号
+   */
+  serial?: number;
+
+  /**
+   * 副标题
+   */
+  subTitle?: string;
+
+  /**
+   * 图片路径或URL
+   */
+  pic?: string;
+
+  /**
+   * 状态(0正常 1停用)
+   */
+  status?: string;
+
+  /**
+   * 平台标识
+   */
+  platformCode?: string;
+
+  /**
+   * 日期范围参数
+   */
+  params?: any;
+}

+ 63 - 0
src/api/product/siteFloor/index.ts

@@ -0,0 +1,63 @@
+import request from '@/utils/request';
+import { AxiosPromise } from 'axios';
+import { SiteFloorVO, SiteFloorForm, SiteFloorQuery } from '@/api/product/siteFloor/types';
+
+/**
+ * 查询客户站点楼层主列表
+ * @param query
+ * @returns {*}
+ */
+
+export const listSiteFloor = (query?: SiteFloorQuery): AxiosPromise<SiteFloorVO[]> => {
+  return request({
+    url: '/product/siteFloor/list',
+    method: 'get',
+    params: query
+  });
+};
+
+/**
+ * 查询客户站点楼层主详细
+ * @param id
+ */
+export const getSiteFloor = (id: string | number): AxiosPromise<SiteFloorVO> => {
+  return request({
+    url: '/product/siteFloor/' + id,
+    method: 'get'
+  });
+};
+
+/**
+ * 新增客户站点楼层主
+ * @param data
+ */
+export const addSiteFloor = (data: SiteFloorForm) => {
+  return request({
+    url: '/product/siteFloor',
+    method: 'post',
+    data: data
+  });
+};
+
+/**
+ * 修改客户站点楼层主
+ * @param data
+ */
+export const updateSiteFloor = (data: SiteFloorForm) => {
+  return request({
+    url: '/product/siteFloor',
+    method: 'put',
+    data: data
+  });
+};
+
+/**
+ * 删除客户站点楼层主
+ * @param id
+ */
+export const delSiteFloor = (id: string | number | Array<string | number>) => {
+  return request({
+    url: '/product/siteFloor/' + id,
+    method: 'delete'
+  });
+};

+ 170 - 0
src/api/product/siteFloor/types.ts

@@ -0,0 +1,170 @@
+export interface SiteFloorVO {
+  /**
+   * ID
+   */
+  id: string | number;
+
+  /**
+   * 站点id
+   */
+  siteId: string | number;
+
+  /**
+   * 客户id
+   */
+  customerId: string | number;
+
+  /**
+   * 楼层名称
+   */
+  name: string;
+
+  /**
+   * 楼层图片
+   */
+  imageUrl: string;
+
+  /**
+   * 链接地址
+   */
+  link: string;
+
+  /**
+   * 品牌编号集合
+   */
+  brandNos: string;
+
+  /**
+   * 排序
+   */
+  sort: number;
+
+  /**
+   * 是否显示(0否 1是)
+   */
+  isShow: string;
+
+  /**
+   * 状态(0正常 1停用)
+   */
+  status: string;
+
+  /**
+   * 备注
+   */
+  remark: string;
+}
+
+export interface SiteFloorForm extends BaseEntity {
+  /**
+   * ID
+   */
+  id?: string | number;
+
+  /**
+   * 站点id
+   */
+  siteId?: string | number;
+
+  /**
+   * 客户id
+   */
+  customerId?: string | number;
+
+  /**
+   * 楼层名称
+   */
+  name?: string;
+
+  /**
+   * 楼层图片
+   */
+  imageUrl?: string;
+
+  /**
+   * 链接地址
+   */
+  link?: string;
+
+  /**
+   * 品牌编号集合
+   */
+  brandNos?: string;
+
+  /**
+   * 排序
+   */
+  sort?: number;
+
+  /**
+   * 是否显示(0否 1是)
+   */
+  isShow?: string;
+
+  /**
+   * 状态(0正常 1停用)
+   */
+  status?: string;
+
+  /**
+   * 备注
+   */
+  remark?: string;
+}
+
+export interface SiteFloorQuery extends PageQuery {
+  /**
+   * 站点id
+   */
+  siteId?: string | number;
+
+  /**
+   * 客户id
+   */
+  customerId?: string | number;
+
+  /**
+   * 楼层名称
+   */
+  name?: string;
+
+  /**
+   * 楼层图片
+   */
+  imageUrl?: string;
+
+  /**
+   * 链接地址
+   */
+  link?: string;
+
+  /**
+   * 品牌编号集合
+   */
+  brandNos?: string;
+
+  /**
+   * 排序
+   */
+  sort?: number;
+
+  /**
+   * 是否显示(0否 1是)
+   */
+  isShow?: string;
+
+  /**
+   * 状态(0正常 1停用)
+   */
+  status?: string;
+
+  /**
+   * 平台标识
+   */
+  platformCode?: string;
+
+  /**
+   * 日期范围参数
+   */
+  params?: any;
+}

+ 63 - 0
src/api/product/siteFloorLink/index.ts

@@ -0,0 +1,63 @@
+import request from '@/utils/request';
+import { AxiosPromise } from 'axios';
+import { SiteFloorLinkVO, SiteFloorLinkForm, SiteFloorLinkQuery } from '@/api/product/siteFloorLink/types';
+
+/**
+ * 查询客户站点楼层关联列表
+ * @param query
+ * @returns {*}
+ */
+
+export const listSiteFloorLink = (query?: SiteFloorLinkQuery): AxiosPromise<SiteFloorLinkVO[]> => {
+  return request({
+    url: '/product/siteFloorLink/list',
+    method: 'get',
+    params: query
+  });
+};
+
+/**
+ * 查询客户站点楼层关联详细
+ * @param id
+ */
+export const getSiteFloorLink = (id: string | number): AxiosPromise<SiteFloorLinkVO> => {
+  return request({
+    url: '/product/siteFloorLink/' + id,
+    method: 'get'
+  });
+};
+
+/**
+ * 新增客户站点楼层关联
+ * @param data
+ */
+export const addSiteFloorLink = (data: SiteFloorLinkForm) => {
+  return request({
+    url: '/product/siteFloorLink',
+    method: 'post',
+    data: data
+  });
+};
+
+/**
+ * 修改客户站点楼层关联
+ * @param data
+ */
+export const updateSiteFloorLink = (data: SiteFloorLinkForm) => {
+  return request({
+    url: '/product/siteFloorLink',
+    method: 'put',
+    data: data
+  });
+};
+
+/**
+ * 删除客户站点楼层关联
+ * @param id
+ */
+export const delSiteFloorLink = (id: string | number | Array<string | number>) => {
+  return request({
+    url: '/product/siteFloorLink/' + id,
+    method: 'delete'
+  });
+};

+ 155 - 0
src/api/product/siteFloorLink/types.ts

@@ -0,0 +1,155 @@
+export interface SiteFloorLinkVO {
+  /**
+   * ID
+   */
+  id: string | number;
+
+  /**
+   * 楼层ID
+   */
+  floorId: string | number;
+
+  /**
+   * 类型(1商品 2品牌)
+   */
+  type: number;
+
+  /**
+   * 商品编号
+   */
+  productNo: string;
+
+  /**
+   * 品牌编号
+   */
+  brandNo: string;
+
+  /**
+   * 商品ID
+   */
+  productId: string | number;
+
+  /**
+   * 品牌id
+   */
+  brandId: string | number;
+
+  /**
+   * 排序
+   */
+  sort: number;
+
+  /**
+   * 状态(0正常 1停用)
+   */
+  status: string;
+
+  /**
+   * 备注
+   */
+  remark: string;
+}
+
+export interface SiteFloorLinkForm extends BaseEntity {
+  /**
+   * ID
+   */
+  id?: string | number;
+
+  /**
+   * 楼层ID
+   */
+  floorId?: string | number;
+
+  /**
+   * 类型(1商品 2品牌)
+   */
+  type?: number;
+
+  /**
+   * 商品编号
+   */
+  productNo?: string;
+
+  /**
+   * 品牌编号
+   */
+  brandNo?: string;
+
+  /**
+   * 商品ID
+   */
+  productId?: string | number;
+
+  /**
+   * 品牌id
+   */
+  brandId?: string | number;
+
+  /**
+   * 排序
+   */
+  sort?: number;
+
+  /**
+   * 状态(0正常 1停用)
+   */
+  status?: string;
+
+  /**
+   * 备注
+   */
+  remark?: string;
+}
+
+export interface SiteFloorLinkQuery extends PageQuery {
+  /**
+   * 楼层ID
+   */
+  floorId?: string | number;
+
+  /**
+   * 类型(1商品 2品牌)
+   */
+  type?: number;
+
+  /**
+   * 商品编号
+   */
+  productNo?: string;
+
+  /**
+   * 品牌编号
+   */
+  brandNo?: string;
+
+  /**
+   * 商品ID
+   */
+  productId?: string | number;
+
+  /**
+   * 品牌id
+   */
+  brandId?: string | number;
+
+  /**
+   * 排序
+   */
+  sort?: number;
+
+  /**
+   * 状态(0正常 1停用)
+   */
+  status?: string;
+
+  /**
+   * 平台标识
+   */
+  platformCode?: string;
+
+  /**
+   * 日期范围参数
+   */
+  params?: any;
+}

+ 18 - 0
src/api/product/siteProduct.ts

@@ -12,6 +12,24 @@ export function listSiteProduct(query?: any) {
   });
 }
 
+/**
+ * 新增客户站点产品配置
+ * @param data
+ */
+export function addSiteProduct(data: {
+  siteId?: string | number;
+  productId?: string | number;
+  productNo?: string;
+  agreementPrice?: string;
+  [key: string]: any;
+}) {
+  return request({
+    url: '/product/siteProduct',
+    method: 'post',
+    data: data
+  });
+}
+
 /**
  * 删除站点产品
  * @param ids 产品ID数组

+ 63 - 0
src/api/product/siteProduct/index.ts

@@ -0,0 +1,63 @@
+import request from '@/utils/request';
+import { AxiosPromise } from 'axios';
+import { SiteProductVO, SiteProductForm, SiteProductQuery } from '@/api/product/siteProduct/types';
+
+/**
+ * 查询客户站点产品配置列表
+ * @param query
+ * @returns {*}
+ */
+
+export const listSiteProduct = (query?: SiteProductQuery): AxiosPromise<SiteProductVO[]> => {
+  return request({
+    url: '/product/siteProduct/list',
+    method: 'get',
+    params: query
+  });
+};
+
+/**
+ * 查询客户站点产品配置详细
+ * @param id
+ */
+export const getSiteProduct = (id: string | number): AxiosPromise<SiteProductVO> => {
+  return request({
+    url: '/product/siteProduct/' + id,
+    method: 'get'
+  });
+};
+
+/**
+ * 新增客户站点产品配置
+ * @param data
+ */
+export const addSiteProduct = (data: SiteProductForm) => {
+  return request({
+    url: '/product/siteProduct',
+    method: 'post',
+    data: data
+  });
+};
+
+/**
+ * 修改客户站点产品配置
+ * @param data
+ */
+export const updateSiteProduct = (data: SiteProductForm) => {
+  return request({
+    url: '/product/siteProduct',
+    method: 'put',
+    data: data
+  });
+};
+
+/**
+ * 删除客户站点产品配置
+ * @param id
+ */
+export const delSiteProduct = (id: string | number | Array<string | number>) => {
+  return request({
+    url: '/product/siteProduct/' + id,
+    method: 'delete'
+  });
+};

+ 215 - 0
src/api/product/siteProduct/types.ts

@@ -0,0 +1,215 @@
+export interface SiteProductVO {
+  /**
+   * 主键ID,自增
+   */
+  id: string | number;
+
+  /**
+   * 站点Id
+   */
+  siteId: string | number;
+
+  /**
+   * 客户编号
+   */
+  clientNo: string;
+
+  /**
+   * 客户id
+   */
+  clientId: string | number;
+
+  /**
+   * 产品编号
+   */
+  productNo: string;
+
+  /**
+   * 产品id
+   */
+  productId: string | number;
+
+  /**
+   * 是否在中心展示(例如:1-是,0-否)
+   */
+  centerView: number;
+
+  /**
+   * 展示方案1可见性(1-可见,0-不可见)
+   */
+  zsfa1View: number;
+
+  /**
+   * 展示方案2可见性(1-可见,0-不可见)
+   */
+  zsfa2View: number;
+
+  /**
+   * 展示方案3可见性(1-可见,0-不可见)
+   */
+  zsfa3View: number;
+
+  /**
+   * 展示方案4可见性(1-可见,0-不可见)
+   */
+  zsfa4View: number;
+
+  /**
+   * 协议价格(字符串形式,可能含格式或货币符号)
+   */
+  agreementPrice: string;
+
+  /**
+   * 状态(0正常 1停用)
+   */
+  status: string;
+
+  /**
+   * 备注
+   */
+  remark: string;
+}
+
+export interface SiteProductForm extends BaseEntity {
+  /**
+   * 主键ID,自增
+   */
+  id?: string | number;
+
+  /**
+   * 站点Id
+   */
+  siteId?: string | number;
+
+  /**
+   * 客户编号
+   */
+  clientNo?: string;
+
+  /**
+   * 客户id
+   */
+  clientId?: string | number;
+
+  /**
+   * 产品编号
+   */
+  productNo?: string;
+
+  /**
+   * 产品id
+   */
+  productId?: string | number;
+
+  /**
+   * 是否在中心展示(例如:1-是,0-否)
+   */
+  centerView?: number;
+
+  /**
+   * 展示方案1可见性(1-可见,0-不可见)
+   */
+  zsfa1View?: number;
+
+  /**
+   * 展示方案2可见性(1-可见,0-不可见)
+   */
+  zsfa2View?: number;
+
+  /**
+   * 展示方案3可见性(1-可见,0-不可见)
+   */
+  zsfa3View?: number;
+
+  /**
+   * 展示方案4可见性(1-可见,0-不可见)
+   */
+  zsfa4View?: number;
+
+  /**
+   * 协议价格(字符串形式,可能含格式或货币符号)
+   */
+  agreementPrice?: string;
+
+  /**
+   * 状态(0正常 1停用)
+   */
+  status?: string;
+
+  /**
+   * 备注
+   */
+  remark?: string;
+}
+
+export interface SiteProductQuery extends PageQuery {
+  /**
+   * 客户编号
+   */
+  clientNo?: string;
+
+  /**
+   * 站点Id
+   */
+  siteId?: string | number;
+
+  /**
+   * 客户id
+   */
+  clientId?: string | number;
+
+  /**
+   * 产品编号
+   */
+  productNo?: string;
+
+  /**
+   * 产品id
+   */
+  productId?: string | number;
+
+  /**
+   * 是否在中心展示(例如:1-是,0-否)
+   */
+  centerView?: number;
+
+  /**
+   * 展示方案1可见性(1-可见,0-不可见)
+   */
+  zsfa1View?: number;
+
+  /**
+   * 展示方案2可见性(1-可见,0-不可见)
+   */
+  zsfa2View?: number;
+
+  /**
+   * 展示方案3可见性(1-可见,0-不可见)
+   */
+  zsfa3View?: number;
+
+  /**
+   * 展示方案4可见性(1-可见,0-不可见)
+   */
+  zsfa4View?: number;
+
+  /**
+   * 协议价格(字符串形式,可能含格式或货币符号)
+   */
+  agreementPrice?: string;
+
+  /**
+   * 状态(0正常 1停用)
+   */
+  status?: string;
+
+  /**
+   * 平台标识
+   */
+  platformCode?: string;
+
+  /**
+   * 日期范围参数
+   */
+  params?: any;
+}

+ 63 - 0
src/api/product/siteSetting/index.ts

@@ -0,0 +1,63 @@
+import request from '@/utils/request';
+import { AxiosPromise } from 'axios';
+import { SiteSettingVO, SiteSettingForm, SiteSettingQuery } from '@/api/product/siteSetting/types';
+
+/**
+ * 查询客户站点设置列表
+ * @param query
+ * @returns {*}
+ */
+
+export const listSiteSetting = (query?: SiteSettingQuery): AxiosPromise<SiteSettingVO[]> => {
+  return request({
+    url: '/product/siteSetting/list',
+    method: 'get',
+    params: query
+  });
+};
+
+/**
+ * 查询客户站点设置详细
+ * @param id
+ */
+export const getSiteSetting = (id: string | number): AxiosPromise<SiteSettingVO> => {
+  return request({
+    url: '/product/siteSetting/' + id,
+    method: 'get'
+  });
+};
+
+/**
+ * 新增客户站点设置
+ * @param data
+ */
+export const addSiteSetting = (data: SiteSettingForm) => {
+  return request({
+    url: '/product/siteSetting',
+    method: 'post',
+    data: data
+  });
+};
+
+/**
+ * 修改客户站点设置
+ * @param data
+ */
+export const updateSiteSetting = (data: SiteSettingForm) => {
+  return request({
+    url: '/product/siteSetting',
+    method: 'put',
+    data: data
+  });
+};
+
+/**
+ * 删除客户站点设置
+ * @param id
+ */
+export const delSiteSetting = (id: string | number | Array<string | number>) => {
+  return request({
+    url: '/product/siteSetting/' + id,
+    method: 'delete'
+  });
+};

+ 294 - 0
src/api/product/siteSetting/types.ts

@@ -0,0 +1,294 @@
+export interface SiteSettingVO {
+  /**
+   * 主键ID,自增
+   */
+  id: string | number;
+
+  /**
+   * 客户编号
+   */
+  clientNo: string;
+
+  /**
+   * 客户编号
+   */
+  clientId: string | number;
+
+  /**
+   * 设置类型
+   */
+  settingType: string;
+
+  /**
+   * 广告类型
+   */
+  advertisType: string;
+
+  /**
+   * 商品id
+   */
+  productId: string | number;
+
+  /**
+   * 产品编号
+   */
+  productNo: string;
+
+  /**
+   * 产品名称
+   */
+  productName: string;
+
+  /**
+   * 导航名称
+   */
+  navigationName: string;
+
+  /**
+   * URL链接
+   */
+  url: string;
+
+  /**
+   * 排序值
+   */
+  sort: number;
+
+  /**
+   * 是否启用(例如:1-是,0-否)
+   */
+  isEnable: number;
+
+  /**
+   * 备注
+   */
+  remark: string;
+
+  /**
+   * 广告图片(存储图片路径或Base64编码字符串)
+   */
+  advertiseImage: string;
+
+  /**
+   * 广告图片(存储图片路径或Base64编码字符串)Url
+   */
+  advertiseImageUrl: string;
+  /**
+   * 开始时间
+   */
+  startTime: string;
+
+  /**
+   * 结束时间
+   */
+  endTime: string;
+
+  /**
+   * 公告编号
+   */
+  announcementNo: string;
+
+  /**
+   * 公告内容
+   */
+  announcementContent: string;
+
+  /**
+   * 状态(0正常 1停用)
+   */
+  status: string;
+}
+
+export interface SiteSettingForm extends BaseEntity {
+  /**
+   * 主键ID,自增
+   */
+  id?: string | number;
+
+  /**
+   * 客户编号
+   */
+  clientNo?: string;
+
+  /**
+   * 客户编号
+   */
+  clientId?: string | number;
+
+  /**
+   * 设置类型
+   */
+  settingType?: string;
+
+  /**
+   * 广告类型
+   */
+  advertisType?: string;
+
+  /**
+   * 商品id
+   */
+  productId?: string | number;
+
+  /**
+   * 产品编号
+   */
+  productNo?: string;
+
+  /**
+   * 产品名称
+   */
+  productName?: string;
+
+  /**
+   * 导航名称
+   */
+  navigationName?: string;
+
+  /**
+   * URL链接
+   */
+  url?: string;
+
+  /**
+   * 排序值
+   */
+  sort?: number;
+
+  /**
+   * 是否启用(例如:1-是,0-否)
+   */
+  isEnable?: number;
+
+  /**
+   * 备注
+   */
+  remark?: string;
+
+  /**
+   * 广告图片(存储图片路径或Base64编码字符串)
+   */
+  advertiseImage?: string;
+
+  /**
+   * 开始时间
+   */
+  startTime?: string;
+
+  /**
+   * 结束时间
+   */
+  endTime?: string;
+
+  /**
+   * 公告编号
+   */
+  announcementNo?: string;
+
+  /**
+   * 公告内容
+   */
+  announcementContent?: string;
+
+  /**
+   * 状态(0正常 1停用)
+   */
+  status?: string;
+}
+
+export interface SiteSettingQuery extends PageQuery {
+  /**
+   * 客户编号
+   */
+  clientNo?: string;
+
+  /**
+   * 客户编号
+   */
+  clientId?: string | number;
+
+  /**
+   * 设置类型
+   */
+  settingType?: string;
+
+  /**
+   * 广告类型
+   */
+  advertisType?: string;
+
+  /**
+   * 商品id
+   */
+  productId?: string | number;
+
+  /**
+   * 产品编号
+   */
+  productNo?: string;
+
+  /**
+   * 产品名称
+   */
+  productName?: string;
+
+  /**
+   * 导航名称
+   */
+  navigationName?: string;
+
+  /**
+   * URL链接
+   */
+  url?: string;
+
+  /**
+   * 排序值
+   */
+  sort?: number;
+
+  /**
+   * 是否启用(例如:1-是,0-否)
+   */
+  isEnable?: number;
+
+  /**
+   * 广告图片(存储图片路径或Base64编码字符串)
+   */
+  advertiseImage?: string;
+
+  /**
+   * 开始时间
+   */
+  startTime?: string;
+
+  /**
+   * 结束时间
+   */
+  endTime?: string;
+
+  /**
+   * 公告编号
+   */
+  announcementNo?: string;
+
+  /**
+   * 公告内容
+   */
+  announcementContent?: string;
+
+  /**
+   * 状态(0正常 1停用)
+   */
+  status?: string;
+
+  /**
+   * 平台标识
+   */
+  platformCode?: string;
+
+  /**
+   * 日期范围参数
+   */
+  params?: any;
+}

+ 21 - 0
src/router/index.ts

@@ -177,6 +177,27 @@ export const constantRoutes: RouteRecordRaw[] = [
     ]
   },
 
+  {
+    path: '/customerOperation',
+    component: Layout,
+    hidden: true,
+    redirect: 'noredirect',
+    children: [
+      {
+        path: 'vipSite/siteConfig',
+        component: () => import('@/views/platform/customerOperation/vipSite/siteConfig.vue'),
+        name: 'VipSiteConfig',
+        meta: { title: '站点配置', activeMenu: '/customerOperation/vipSite', noCache: true }
+      },
+      {
+        path: 'vipSite/productConfig',
+        component: () => import('@/views/platform/customerOperation/vipSite/productConfig.vue'),
+        name: 'VipProductConfig',
+        meta: { title: '商品配置', activeMenu: '/customerOperation/vipSite', noCache: true }
+      }
+    ]
+  },
+
 ];
 
 // 动态路由,基于用户权限动态去加载

+ 319 - 0
src/views/platform/customerOperation/vipSite/index.vue

@@ -0,0 +1,319 @@
+<template>
+  <div class="p-2">
+    <!-- 搜索区域 -->
+    <transition :enter-active-class="proxy?.animate.searchAnimate.enter" :leave-active-class="proxy?.animate.searchAnimate.leave">
+      <div v-show="showSearch" class="mb-[10px]">
+        <el-card shadow="hover">
+          <el-form ref="queryFormRef" :model="queryParams" label-width="80px">
+            <el-row :gutter="10">
+              <el-col :span="6">
+                <el-form-item label="站点名称" prop="siteName">
+                  <el-input v-model="queryParams.siteName" placeholder="请输入站点名称" clearable style="width: 100%" @keyup.enter="handleQuery" />
+                </el-form-item>
+              </el-col>
+              <el-col :span="6">
+                <el-form-item label="客户名称" prop="clientId">
+                  <el-select
+                    v-model="queryParams.clientId"
+                    filterable
+                    remote
+                    reserve-keyword
+                    :remote-method="searchQueryCustomer"
+                    :loading="queryCustomerLoading"
+                    placeholder="请选择客户"
+                    clearable
+                    style="width: 100%"
+                    @focus="handleQueryCustomerFocus"
+                  >
+                    <el-option
+                      v-for="item in queryCustomerList"
+                      :key="item.id"
+                      :label="`${item.customerNo} ${item.customerName}`"
+                      :value="item.id"
+                    />
+                  </el-select>
+                </el-form-item>
+              </el-col>
+              <el-col :span="6">
+                <el-form-item label="平台状态" prop="status">
+                  <el-select v-model="queryParams.status" placeholder="请选择" clearable style="width: 100%">
+                    <el-option label="启用" value="0" />
+                    <el-option label="禁用" value="1" />
+                  </el-select>
+                </el-form-item>
+              </el-col>
+              <el-col :span="6">
+                <el-form-item label="发布时间" prop="startTimeRange">
+                  <el-date-picker
+                    v-model="queryParams.startTimeRange"
+                    type="daterange"
+                    range-separator="至"
+                    start-placeholder="开始时间"
+                    end-placeholder="结束时间"
+                    value-format="YYYY-MM-DD"
+                    style="width: 100%"
+                  />
+                </el-form-item>
+              </el-col>
+              <el-col :span="6">
+                <el-form-item label="截止时间" prop="endTimeRange">
+                  <el-date-picker
+                    v-model="queryParams.endTimeRange"
+                    type="daterange"
+                    range-separator="至"
+                    start-placeholder="开始时间"
+                    end-placeholder="结束时间"
+                    value-format="YYYY-MM-DD"
+                    style="width: 100%"
+                  />
+                </el-form-item>
+              </el-col>
+              <el-col :span="18">
+                <el-form-item>
+                  <el-button type="primary" icon="Search" @click="handleQuery">搜索</el-button>
+                  <el-button icon="Refresh" @click="resetQuery">重置</el-button>
+                  <el-button type="primary" icon="Plus" @click="handleAdd">新增</el-button>
+                </el-form-item>
+              </el-col>
+            </el-row>
+          </el-form>
+        </el-card>
+      </div>
+    </transition>
+
+    <!-- 表格区域 -->
+    <el-card shadow="never">
+      <template #header>
+        <div class="flex justify-between items-center">
+          <span class="font-bold text-[#409EFF]">大客户专属站点信息列表</span>
+          <right-toolbar v-model:showSearch="showSearch" @queryTable="getList" />
+        </div>
+      </template>
+
+      <el-table v-loading="loading" border :data="siteList">
+        <el-table-column label="客户编号" align="center" prop="clientNo" width="100" />
+        <el-table-column label="客户名称" align="center" prop="clientName" min-width="150" />
+        <el-table-column label="站点名称" align="center" prop="siteName" min-width="180" />
+        <el-table-column label="站点域名" align="center" prop="siteDomain" min-width="220">
+          <template #default="scope">
+            <el-link type="primary" :href="scope.row.siteDomain" target="_blank">{{ scope.row.siteDomain }}</el-link>
+          </template>
+        </el-table-column>
+        <el-table-column label="状态" align="center" width="80">
+          <template #default="scope">
+            <el-tag :type="scope.row.status === '0' ? 'success' : 'info'" size="small">
+              {{ scope.row.status === '0' ? '启用' : '禁用' }}
+            </el-tag>
+          </template>
+        </el-table-column>
+        <el-table-column label="开始时间" align="center" prop="startTime" width="120">
+          <template #default="scope">
+            <span>{{ parseTime(scope.row.startTime, '{y}-{m}-{d}') }}</span>
+          </template>
+        </el-table-column>
+        <el-table-column label="结束时间" align="center" prop="endTime" width="120">
+          <template #default="scope">
+            <span>{{ parseTime(scope.row.endTime, '{y}-{m}-{d}') }}</span>
+          </template>
+        </el-table-column>
+        <el-table-column label="操作" align="center" width="120" fixed="right">
+          <template #default="scope">
+            <div class="flex flex-col items-center gap-1">
+              <el-link type="primary" :underline="false" @click="handleSiteConfig(scope.row)">站点配置</el-link>
+              <el-link type="primary" :underline="false" @click="handleProductConfig(scope.row)">商品配置</el-link>
+              <el-link type="primary" :underline="false" @click="handleStyleDesign(scope.row)">样式设计</el-link>
+              <el-link
+                :type="scope.row.status === '0' ? 'danger' : 'success'"
+                :underline="false"
+                @click="handleStatusChange(scope.row)"
+              >
+                {{ scope.row.status === '0' ? '停 用' : '启 用' }}
+              </el-link>
+            </div>
+          </template>
+        </el-table-column>
+      </el-table>
+
+      <pagination v-show="total > 0" :total="total" v-model:page="queryParams.pageNum" v-model:limit="queryParams.pageSize" @pagination="getList" />
+    </el-card>
+  </div>
+</template>
+
+<script setup name="VipSite" lang="ts">
+import { listSite, getSite, delSite, addSite, updateSite } from '@/api/product/site';
+import { SiteVO, SiteQuery } from '@/api/product/site/types';
+import { getCustomerList, getCustomerInfo } from '@/api/customerOperation/customerList';
+import { useRouter } from 'vue-router';
+
+const { proxy } = getCurrentInstance() as ComponentInternalInstance;
+const router = useRouter();
+
+const siteList = ref<SiteVO[]>([]);
+const loading = ref(true);
+const showSearch = ref(true);
+const total = ref(0);
+
+const queryFormRef = ref<ElFormInstance>();
+
+// 搜索框客户下拉数据
+const queryCustomerList = ref<any[]>([]);
+const queryCustomerLoading = ref(false);
+
+// 搜索框客户远程搜索
+const searchQueryCustomer = async (query: string) => {
+  queryCustomerLoading.value = true;
+  try {
+    const params: any = { pageSize: 20, pageNum: 1 };
+    if (query) params.clientName = query;
+    const res = await getCustomerList(params);
+    queryCustomerList.value = res.rows || [];
+  } catch (error) {
+    queryCustomerList.value = [];
+  } finally {
+    queryCustomerLoading.value = false;
+  }
+};
+
+// 搜索框客户聚焦时加载
+const handleQueryCustomerFocus = () => {
+  if (queryCustomerList.value.length === 0) {
+    searchQueryCustomer('');
+  }
+};
+
+// 查询参数
+const queryParams = ref<SiteQuery & { startTimeRange?: string[]; endTimeRange?: string[] }>({
+  pageNum: 1,
+  pageSize: 10,
+  siteName: undefined,
+  clientId: undefined,
+  status: undefined,
+  startTimeRange: undefined,
+  endTimeRange: undefined,
+  params: {}
+});
+
+/** 通过客户接口回显列表中的客户编号和名称 */
+const enrichListWithCustomerInfo = async () => {
+  const uniqueClientIds = [...new Set(siteList.value.map(item => item.clientId).filter(Boolean))];
+  if (uniqueClientIds.length === 0) return;
+  const customerMap = new Map<string, any>();
+  await Promise.all(
+    uniqueClientIds.map(async (clientId) => {
+      try {
+        const res = await getCustomerInfo(clientId as string | number);
+        if (res.data) {
+          customerMap.set(String(clientId), res.data);
+        }
+      } catch {}
+    })
+  );
+  siteList.value = siteList.value.map(item => {
+    const customer = customerMap.get(String(item.clientId));
+    if (customer) {
+      return { ...item, clientNo: customer.customerNo || item.clientNo, clientName: customer.customerName || item.clientName };
+    }
+    return item;
+  });
+};
+
+/** 查询客户站点配置列表 */
+const getList = async () => {
+  loading.value = true;
+  try {
+    // 处理日期范围参数
+    const params: any = { ...queryParams.value };
+    if (queryParams.value.startTimeRange && queryParams.value.startTimeRange.length === 2) {
+      params.params = {
+        ...params.params,
+        beginStartTime: queryParams.value.startTimeRange[0],
+        endStartTime: queryParams.value.startTimeRange[1]
+      };
+    }
+    if (queryParams.value.endTimeRange && queryParams.value.endTimeRange.length === 2) {
+      params.params = {
+        ...params.params,
+        beginEndTime: queryParams.value.endTimeRange[0],
+        endEndTime: queryParams.value.endTimeRange[1]
+      };
+    }
+    delete params.startTimeRange;
+    delete params.endTimeRange;
+
+    const res = await listSite(params);
+    siteList.value = res.rows;
+    total.value = res.total;
+    await enrichListWithCustomerInfo();
+  } catch (error) {
+    console.error('获取站点列表失败:', error);
+  } finally {
+    loading.value = false;
+  }
+};
+
+/** 搜索按钮操作 */
+const handleQuery = () => {
+  queryParams.value.pageNum = 1;
+  getList();
+};
+
+/** 重置按钮操作 */
+const resetQuery = () => {
+  queryFormRef.value?.resetFields();
+  queryParams.value = {
+    pageNum: 1,
+    pageSize: 10,
+    siteName: undefined,
+    clientId: undefined,
+    status: undefined,
+    startTimeRange: undefined,
+    endTimeRange: undefined,
+    params: {}
+  };
+  handleQuery();
+};
+
+/** 新增按钮操作 */
+const handleAdd = () => {
+  router.push('/customerOperation/vipSite/siteConfig');
+};
+
+/** 站点配置 */
+const handleSiteConfig = (row: SiteVO) => {
+  router.push({
+    path: '/customerOperation/vipSite/siteConfig',
+    query: { id: row.id }
+  });
+};
+
+/** 商品配置 */
+const handleProductConfig = (row: SiteVO) => {
+  router.push({
+    path: '/customerOperation/vipSite/productConfig',
+    query: { siteId: row.id, siteName: row.siteName, clientNo: row.clientNo, clientName: row.clientName }
+  });
+};
+
+/** 样式设计 */
+const handleStyleDesign = (row: SiteVO) => {
+  proxy?.$modal.msgWarning('样式设计功能开发中...');
+};
+
+/** 状态切换 */
+const handleStatusChange = async (row: SiteVO) => {
+  const newStatus = row.status === '0' ? '1' : '0';
+  const text = newStatus === '0' ? '启用' : '停用';
+  try {
+    await proxy?.$modal.confirm(`确认要"${text}"站点"${row.siteName}"吗?`);
+    await updateSite({ id: row.id, status: newStatus });
+    proxy?.$modal.msgSuccess(`${text}成功`);
+    await getList();
+  } catch (error) {
+    // 用户取消操作
+  }
+};
+
+onMounted(() => {
+  getList();
+});
+</script>

+ 563 - 0
src/views/platform/customerOperation/vipSite/productConfig.vue

@@ -0,0 +1,563 @@
+<template>
+  <div class="p-2">
+    <!-- 返回按钮 + 标题 -->
+    <el-card shadow="never" class="mb-2">
+      <div class="flex items-center">
+        <el-button link @click="handleBack">
+          <el-icon><ArrowLeft /></el-icon>
+          返回
+        </el-button>
+        <el-divider direction="vertical" />
+        <span class="text-base font-medium">商品配置</span>
+      </div>
+    </el-card>
+
+    <!-- 搜索区域 -->
+    <transition :enter-active-class="proxy?.animate.searchAnimate.enter" :leave-active-class="proxy?.animate.searchAnimate.leave">
+      <div v-show="showSearch" class="mb-[10px]">
+        <el-card shadow="hover">
+          <el-form ref="queryFormRef" :model="queryParams" :inline="true" label-width="70px">
+            <el-form-item label="商品编号" prop="productNo">
+              <el-input v-model="queryParams.productNo" placeholder="请输入商品编号" clearable style="width: 180px" @keyup.enter="handleQuery" />
+            </el-form-item>
+            <el-form-item label="商品名称" prop="itemName">
+              <el-input v-model="queryParams.itemName" placeholder="请输入商品名称" clearable style="width: 180px" @keyup.enter="handleQuery" />
+            </el-form-item>
+            <el-form-item label="商品品牌" prop="brandId">
+              <el-select v-model="queryParams.brandId" placeholder="请选择" clearable style="width: 120px">
+                <el-option v-for="item in brandOptions" :key="item.id" :label="item.brandName" :value="item.id" />
+              </el-select>
+            </el-form-item>
+            <el-form-item label="商品类别" prop="categoryId">
+              <el-select v-model="queryParams.topCategoryId" placeholder="请选择" clearable style="width: 100px" @change="handleTopCategoryChange">
+                <el-option v-for="item in topCategoryOptions" :key="item.id" :label="item.categoryName" :value="item.id" />
+              </el-select>
+              <el-select v-model="queryParams.mediumCategoryId" placeholder="请选择" clearable style="width: 100px; margin-left: 5px" @change="handleMediumCategoryChange">
+                <el-option v-for="item in mediumCategoryOptions" :key="item.id" :label="item.categoryName" :value="item.id" />
+              </el-select>
+              <el-select v-model="queryParams.bottomCategoryId" placeholder="请选择" clearable style="width: 100px; margin-left: 5px">
+                <el-option v-for="item in bottomCategoryOptions" :key="item.id" :label="item.categoryName" :value="item.id" />
+              </el-select>
+            </el-form-item>
+            <el-form-item>
+              <el-button type="primary" icon="Search" @click="handleQuery">搜索</el-button>
+              <el-button icon="Refresh" @click="resetQuery">重置</el-button>
+            </el-form-item>
+          </el-form>
+        </el-card>
+      </div>
+    </transition>
+
+    <!-- 表格区域 -->
+    <el-card shadow="never">
+      <template #header>
+        <div class="flex justify-between items-center">
+          <span class="font-bold text-[#409EFF]">商品列表信息列表</span>
+          <div class="flex gap-2">
+            <el-button type="primary" icon="Plus" @click="handleAddProduct">添加商品</el-button>
+            <el-button type="primary" icon="Upload" @click="handleImportProduct">导入商品</el-button>
+            <el-button type="primary" icon="Download" @click="handleExportProduct">导出商品</el-button>
+          </div>
+        </div>
+      </template>
+
+      <el-table v-loading="loading" border :data="productList">
+        <el-table-column label="商品编号" align="center" prop="productNo" width="100" />
+        <el-table-column label="商品图片" align="center" width="100">
+          <template #default="scope">
+            <image-preview :src="scope.row.productImageUrl" :width="60" :height="60"/>
+          </template>
+        </el-table-column>
+        <el-table-column label="商品信息" align="center" min-width="400">
+          <template #default="scope">
+            <div class="text-left" style="font-size: 12px;">
+              <div>{{ scope.row.itemName }}</div>
+              <div class="text-gray-500">品牌:{{ scope.row.brandName || '-' }}</div>
+            </div>
+          </template>
+        </el-table-column>
+        <el-table-column label="商品类别" align="center" prop="categoryName" width="120" />
+        <el-table-column label="单位" align="center" width="100">
+          <template #default="scope">
+            <div style="font-size: 12px;">
+              <div>单位:{{ scope.row.unitName || '个' }}</div>
+              <div>起订量:{{ scope.row.minOrderQuantity || 1 }}</div>
+            </div>
+          </template>
+        </el-table-column>
+        <el-table-column label="SKU价格" align="center" width="130">
+          <template #default="scope">
+            <div class="text-left" style="font-size: 12px;">
+              <div>市场价:¥{{ scope.row.marketPrice || '0.00' }}</div>
+              <div class="text-[#f56c6c]">平台售价:¥{{ scope.row.platformPrice || '0.00' }}</div>
+              <div>最低售价:¥{{ scope.row.minPrice || '0.00' }}</div>
+            </div>
+          </template>
+        </el-table-column>
+        <el-table-column label="采购价" align="center" prop="purchasePrice" width="100" />
+        <el-table-column label="协议价" align="center" prop="agreementPrice" width="100" />
+        <el-table-column label="平台售价毛利率" align="center" width="130">
+          <template #default="scope">
+            {{ scope.row.grossMargin ? `${scope.row.grossMargin}%` : '-' }}
+          </template>
+        </el-table-column>
+        <el-table-column label="商品状态" align="center" width="100">
+          <template #default="scope">
+            <el-tag v-if="scope.row.productStatus === '1'" type="success" size="small">上架</el-tag>
+            <el-tag v-else type="info" size="small">下架</el-tag>
+          </template>
+        </el-table-column>
+        <el-table-column label="操作" align="center" width="100" fixed="right">
+          <template #default="scope">
+            <div class="flex flex-col items-center gap-1">
+              <el-link type="primary" :underline="false" @click="handlePriceEdit(scope.row)">价格修改</el-link>
+              <el-link type="danger" :underline="false" @click="handleDelete(scope.row)">删 除</el-link>
+            </div>
+          </template>
+        </el-table-column>
+      </el-table>
+
+      <pagination v-show="total > 0" :total="total" v-model:page="queryParams.pageNum" v-model:limit="queryParams.pageSize" @pagination="getList" />
+    </el-card>
+
+    <!-- 价格修改弹窗 -->
+    <el-dialog title="价格修改" v-model="priceDialog.visible" width="500px" append-to-body>
+      <el-form ref="priceFormRef" :model="priceDialog.form" :rules="priceRules" label-width="100px">
+        <el-form-item label="商品名称">
+          <span>{{ priceDialog.row?.itemName }}</span>
+        </el-form-item>
+        <el-form-item label="协议价" prop="agreementPrice">
+          <el-input-number v-model="priceDialog.form.agreementPrice" :precision="2" :min="0" controls-position="right" style="width: 100%" />
+        </el-form-item>
+      </el-form>
+      <template #footer>
+        <el-button @click="priceDialog.visible = false">取 消</el-button>
+        <el-button type="primary" @click="submitPriceForm">确 定</el-button>
+      </template>
+    </el-dialog>
+
+    <!-- 添加商品弹窗 -->
+    <el-dialog title="添加商品" v-model="addProductDialog.visible" width="1500px" append-to-body top="5vh">
+      <div class="add-product-dialog">
+        <el-form :model="addProductQuery" :inline="true" class="mb-4">
+          <el-form-item>
+            <el-button type="primary" icon="Plus" @click="handleBatchAdd">加入清单</el-button>
+          </el-form-item>
+          <el-form-item label="商品名称:">
+            <el-input v-model="addProductQuery.itemName" placeholder="商品名称" clearable style="width: 180px" />
+          </el-form-item>
+          <el-form-item label="商品编号:">
+            <el-input v-model="addProductQuery.productNo" placeholder="商品编号" clearable style="width: 180px" />
+          </el-form-item>
+          <el-form-item>
+            <el-button type="primary" icon="Search" @click="handleSearchProducts">搜索</el-button>
+          </el-form-item>
+        </el-form>
+
+        <el-table
+          ref="addProductTableRef"
+          v-loading="addProductDialog.loading"
+          :data="addProductDialog.productList"
+          border
+          @selection-change="handleSelectionChange"
+          max-height="500"
+        >
+          <el-table-column type="selection" width="55" align="center" />
+          <el-table-column label="商品编号" align="center" prop="productNo" width="100" />
+          <el-table-column label="商品图片" align="center" width="100">
+            <template #default="scope">
+              <image-preview :src="scope.row.productImageUrl" :width="60" :height="60"/>
+            </template>
+          </el-table-column>
+          <el-table-column label="商品信息" align="center" min-width="400">
+            <template #default="scope">
+              <div class="text-left" style="font-size: 12px;">
+                <div>{{ scope.row.itemName }}</div>
+                <div class="text-gray-500">品牌:{{ scope.row.brandName || '-' }}</div>
+              </div>
+            </template>
+          </el-table-column>
+          <el-table-column label="商品分类" align="center" prop="categoryName" width="120" />
+          <el-table-column label="单位" align="center" width="100">
+            <template #default="scope">
+              <div class="text-left" style="font-size: 12px;">
+                <div>单位:{{ scope.row.unitName || '个' }}</div>
+                <div>起订量:{{ scope.row.minOrderQuantity || 1 }}</div>
+              </div>
+            </template>
+          </el-table-column>
+          <el-table-column label="SKU价格" align="center" width="130">
+            <template #default="scope">
+              <div class="text-left" style="font-size: 12px;">
+                <div>市场价:¥{{ scope.row.midRangePrice || '0.00' }}</div>
+                <div class="text-[#f56c6c]">平台价:¥{{ scope.row.standardPrice || '0.00' }}</div>
+                <div>最低价:¥{{ scope.row.certificatePrice || '0.00' }}</div>
+              </div>
+            </template>
+          </el-table-column>
+          <el-table-column label="库存情况" align="center" width="130">
+            <template #default="scope">
+              <div class="text-left" style="font-size: 12px;">
+                <div class="text-[#f56c6c]">库存总数:{{ scope.row.stock || 0 }}</div>
+                <div>现有库存:{{ scope.row.availableStock || 0 }}</div>
+                <div>虚拟库存:{{ scope.row.virtualStock || 0 }}</div>
+              </div>
+            </template>
+          </el-table-column>
+          <el-table-column label="采购价" align="center" prop="purchasingPrice" width="80" />
+          <el-table-column label="商品状态" align="center" prop="productStatus" width="80" />
+          <el-table-column label="协议价" align="center" width="150">
+            <template #default="scope">
+              <el-input-number
+                v-model="scope.row.agreementPrice"
+                :precision="2"
+                :min="0"
+                controls-position="right"
+                style="width: 120px"
+                placeholder="请输入"
+              />
+            </template>
+          </el-table-column>
+          <el-table-column label="操作" align="center" width="100" fixed="right">
+            <template #default="scope">
+              <el-link type="primary" :underline="false" @click="handleAddSingleProduct(scope.row)">加入清单</el-link>
+            </template>
+          </el-table-column>
+        </el-table>
+
+        <pagination
+          v-show="addProductDialog.productList.length > 0"
+          v-model:page="addProductQuery.pageNum"
+          v-model:limit="addProductQuery.pageSize"
+          :total="addProductDialog.total"
+          @pagination="getProductList"
+        />
+      </div>
+    </el-dialog>
+  </div>
+</template>
+
+<script setup name="ProductConfig" lang="ts">
+import { ref, reactive, onMounted, getCurrentInstance } from 'vue';
+import type { ComponentInternalInstance } from 'vue';
+import type { FormInstance } from 'element-plus';
+import { useRoute, useRouter } from 'vue-router';
+import { ArrowLeft } from '@element-plus/icons-vue';
+import { listBase } from '@/api/pmsProduct/base';
+import { BaseVO, BaseQuery } from '@/api/pmsProduct/base/types';
+import { listProductCategory } from '@/api/customerOperation/customerBlacklist';
+import { listBrand } from '@/api/product/brand';
+import { addSiteProduct } from '@/api/product/siteProduct';
+
+const { proxy } = getCurrentInstance() as ComponentInternalInstance;
+const route = useRoute();
+const router = useRouter();
+
+const productList = ref<any[]>([]);
+const loading = ref(false);
+const showSearch = ref(true);
+const total = ref(0);
+
+const queryFormRef = ref<ElFormInstance>();
+
+// 从路由获取站点信息
+const siteId = ref<string | number | undefined>(undefined);
+const siteName = ref<string>('');
+
+// 品牌选项
+const brandOptions = ref<any[]>([]);
+
+// 分类三级联动
+const topCategoryOptions = ref<any[]>([]);
+const mediumCategoryOptions = ref<any[]>([]);
+const bottomCategoryOptions = ref<any[]>([]);
+const allCategoryList = ref<any[]>([]);
+
+// 查询参数
+const queryParams = ref({
+  pageNum: 1,
+  pageSize: 10,
+  siteId: undefined as string | number | undefined,
+  productNo: undefined as string | undefined,
+  itemName: undefined as string | undefined,
+  brandId: undefined as number | undefined,
+  topCategoryId: undefined as number | undefined,
+  mediumCategoryId: undefined as number | undefined,
+  bottomCategoryId: undefined as number | undefined
+});
+
+// 价格修改弹窗
+const priceDialog = reactive({
+  visible: false,
+  row: null as any,
+  form: {
+    id: undefined as number | undefined,
+    agreementPrice: 0
+  }
+});
+const priceFormRef = ref<FormInstance>();
+const priceRules = {
+  agreementPrice: [{ required: true, message: '请输入协议价', trigger: 'blur' }]
+};
+
+// 添加商品弹窗
+const addProductDialog = reactive({
+  visible: false,
+  loading: false,
+  productList: [] as any[],
+  total: 0
+});
+const addProductQuery = ref<BaseQuery>({
+  pageNum: 1,
+  pageSize: 10,
+  productNo: undefined,
+  itemName: undefined
+});
+const selectedProducts = ref<BaseVO[]>([]);
+const addProductTableRef = ref<any>();
+
+/** 初始化 */
+const initData = () => {
+  siteId.value = route.query.siteId as string;
+  siteName.value = route.query.siteName as string || '';
+  queryParams.value.siteId = siteId.value;
+  getList();
+};
+
+/** 查询商品列表 */
+const getList = async () => {
+  loading.value = true;
+  try {
+    // TODO: 调用站点商品列表接口
+    const res = await listBase({
+      ...queryParams.value,
+      bottomCategoryId: queryParams.value.bottomCategoryId || queryParams.value.mediumCategoryId || queryParams.value.topCategoryId
+    });
+    productList.value = res.rows || [];
+    total.value = res.total || 0;
+  } catch (error) {
+    console.error('获取商品列表失败:', error);
+  } finally {
+    loading.value = false;
+  }
+};
+
+/** 加载品牌列表 */
+const loadBrandOptions = async () => {
+  try {
+    const res = await listBrand({ pageNum: 1, pageSize: 1000 });
+    brandOptions.value = res.rows || [];
+  } catch (error) {
+    console.error('获取品牌列表失败:', error);
+  }
+};
+
+/** 加载商品分类 */
+const loadCategoryOptions = async () => {
+  try {
+    const res = await listProductCategory({ pageNum: 1, pageSize: 1000, dataSource: 'A10' });
+    allCategoryList.value = res.rows || [];
+    topCategoryOptions.value = allCategoryList.value.filter((item: any) => !item.parentId || item.parentId === 0);
+  } catch (error) {
+    console.error('加载分类失败:', error);
+  }
+};
+
+/** 顶级分类变化 */
+const handleTopCategoryChange = (val: number | undefined) => {
+  queryParams.value.mediumCategoryId = undefined;
+  queryParams.value.bottomCategoryId = undefined;
+  bottomCategoryOptions.value = [];
+  if (val) {
+    mediumCategoryOptions.value = allCategoryList.value.filter((item: any) => item.parentId === val);
+  } else {
+    mediumCategoryOptions.value = [];
+  }
+};
+
+/** 中级分类变化 */
+const handleMediumCategoryChange = (val: number | undefined) => {
+  queryParams.value.bottomCategoryId = undefined;
+  if (val) {
+    bottomCategoryOptions.value = allCategoryList.value.filter((item: any) => item.parentId === val);
+  } else {
+    bottomCategoryOptions.value = [];
+  }
+};
+
+/** 搜索 */
+const handleQuery = () => {
+  queryParams.value.pageNum = 1;
+  getList();
+};
+
+/** 重置 */
+const resetQuery = () => {
+  queryFormRef.value?.resetFields();
+  queryParams.value = {
+    pageNum: 1,
+    pageSize: 10,
+    siteId: siteId.value,
+    productNo: undefined,
+    itemName: undefined,
+    brandId: undefined,
+    topCategoryId: undefined,
+    mediumCategoryId: undefined,
+    bottomCategoryId: undefined
+  };
+  mediumCategoryOptions.value = [];
+  bottomCategoryOptions.value = [];
+  handleQuery();
+};
+
+/** 返回 */
+const handleBack = () => {
+  router.push('/customerOperation/vipSite');
+};
+
+/** 价格修改 */
+const handlePriceEdit = (row: any) => {
+  priceDialog.row = row;
+  priceDialog.form = {
+    id: row.id,
+    agreementPrice: row.agreementPrice || 0
+  };
+  priceDialog.visible = true;
+};
+
+/** 提交价格修改 */
+const submitPriceForm = async () => {
+  await priceFormRef.value?.validate();
+  // TODO: 调用修改协议价接口
+  proxy?.$modal.msgSuccess('价格修改成功');
+  priceDialog.visible = false;
+  await getList();
+};
+
+/** 删除商品 */
+const handleDelete = async (row: any) => {
+  await proxy?.$modal.confirm(`确认要删除商品"${row.itemName}"吗?`);
+  // TODO: 调用删除接口
+  proxy?.$modal.msgSuccess('删除成功');
+  await getList();
+};
+
+/** 添加商品 */
+const handleAddProduct = () => {
+  addProductDialog.visible = true;
+  addProductQuery.value = {
+    pageNum: 1,
+    pageSize: 10,
+    productNo: undefined,
+    itemName: undefined
+  };
+  selectedProducts.value = [];
+  getProductList();
+};
+
+/** 获取可添加的商品列表 */
+const getProductList = async () => {
+  addProductDialog.loading = true;
+  try {
+    const res = await listBase(addProductQuery.value);
+    addProductDialog.productList = res.rows || [];
+    addProductDialog.total = res.total || 0;
+  } catch (error) {
+    console.error('获取商品列表失败:', error);
+    addProductDialog.productList = [];
+    addProductDialog.total = 0;
+  } finally {
+    addProductDialog.loading = false;
+  }
+};
+
+/** 搜索商品 */
+const handleSearchProducts = () => {
+  addProductQuery.value.pageNum = 1;
+  getProductList();
+};
+
+/** 选择变化 */
+const handleSelectionChange = (selection: BaseVO[]) => {
+  selectedProducts.value = selection;
+};
+
+/** 批量加入清单 */
+const handleBatchAdd = async () => {
+  if (selectedProducts.value.length === 0) {
+    proxy?.$modal.msgWarning('请先选择要添加的商品');
+    return;
+  }
+  // 校验是否填写协议价
+  const noPrice = (selectedProducts.value as any[]).filter((item: any) => !item.agreementPrice || Number(item.agreementPrice) <= 0);
+  if (noPrice.length > 0) {
+    proxy?.$modal.msgWarning(`有 ${noPrice.length} 个商品未填写协议价,请填写后再加入清单`);
+    return;
+  }
+  try {
+    await Promise.all(
+      (selectedProducts.value as any[]).map((item: any) =>
+        addSiteProduct({
+          siteId: siteId.value,
+          productId: item.id,
+          productNo: item.productNo,
+          agreementPrice: String(item.agreementPrice)
+        })
+      )
+    );
+    proxy?.$modal.msgSuccess(`成功添加 ${selectedProducts.value.length} 个商品`);
+    addProductDialog.visible = false;
+    selectedProducts.value = [];
+    if (addProductTableRef.value) {
+      addProductTableRef.value.clearSelection();
+    }
+    await getList();
+  } catch (error) {
+    console.error('添加商品失败:', error);
+  }
+};
+
+/** 添加单个商品 */
+const handleAddSingleProduct = async (row: any) => {
+  if (!row.agreementPrice || Number(row.agreementPrice) <= 0) {
+    proxy?.$modal.msgWarning('请先填写协议价');
+    return;
+  }
+  try {
+    await addSiteProduct({
+      siteId: siteId.value,
+      productId: row.id,
+      productNo: row.productNo,
+      agreementPrice: String(row.agreementPrice)
+    });
+    proxy?.$modal.msgSuccess('添加成功');
+    await getList();
+  } catch (error) {
+    console.error('添加商品失败:', error);
+  }
+};
+
+/** 导入商品 */
+const handleImportProduct = () => {
+  proxy?.$modal.msgWarning('导入商品功能开发中...');
+};
+
+/** 导出商品 */
+const handleExportProduct = () => {
+  proxy?.$modal.msgWarning('导出商品功能开发中...');
+};
+
+onMounted(() => {
+  initData();
+  loadBrandOptions();
+  loadCategoryOptions();
+});
+</script>
+
+<style scoped lang="scss">
+.add-product-dialog {
+  :deep(.el-form--inline .el-form-item) {
+    margin-right: 10px;
+  }
+}
+</style>

+ 306 - 0
src/views/platform/customerOperation/vipSite/siteConfig.vue

@@ -0,0 +1,306 @@
+<template>
+  <div class="p-2">
+    <!-- 返回按钮 + 标题 -->
+    <el-card shadow="never" class="mb-2">
+      <div class="flex items-center">
+        <el-button link @click="handleBack">
+          <el-icon><ArrowLeft /></el-icon>
+          返回
+        </el-button>
+        <el-divider direction="vertical" />
+        <span class="text-base font-medium">{{ isEdit ? '编辑站点' : '新增站点' }}</span>
+      </div>
+    </el-card>
+
+    <!-- 表单区域 -->
+    <el-card shadow="never">
+      <template #header>
+        <div class="flex justify-between items-center">
+          <span class="font-bold text-[#409EFF]">站点配置信息列表</span>
+          <el-button type="primary" @click="submitForm">保存</el-button>
+        </div>
+      </template>
+
+      <el-form ref="formRef" :model="form" :rules="rules" label-width="100px" class="max-w-[1000px]">
+        <el-form-item label="客户名称" prop="clientId" required>
+          <el-input
+            v-if="isEdit"
+            :model-value="`${form.clientNo} ${form.clientName}`"
+            disabled
+            style="width: 100%"
+          />
+          <el-select
+            v-else
+            v-model="form.clientId"
+            filterable
+            remote
+            reserve-keyword
+            :remote-method="searchCustomer"
+            :loading="customerLoading"
+            placeholder="请选择客户"
+            style="width: 100%"
+            @change="handleCustomerChange"
+            @focus="handleCustomerFocus"
+          >
+            <el-option
+              v-for="item in customerList"
+              :key="item.i"
+              :label="`${item.customerNo} ${item.customerName}`"
+              :value="item.id"
+            />
+          </el-select>
+        </el-form-item>
+
+        <el-form-item label="站点名称" prop="siteName" required>
+          <el-input v-model="form.siteName" placeholder="请输入站点名称" />
+        </el-form-item>
+
+        <el-form-item label="站点标题" prop="siteTitle">
+          <el-input v-model="form.siteTitle" placeholder="请输入站点标题" />
+        </el-form-item>
+
+        <el-form-item label="开始时间" prop="startTime">
+          <div class="flex gap-4 items-center">
+            <el-date-picker
+              v-model="form.startTime"
+              type="date"
+              placeholder="选择日期"
+              value-format="YYYY-MM-DD"
+              style="width: 200px"
+            />
+            <span class="text-gray-500">结束时间:</span>
+            <el-date-picker
+              v-model="form.endTime"
+              type="date"
+              placeholder="选择日期"
+              value-format="YYYY-MM-DD"
+              style="width: 200px"
+            />
+          </div>
+        </el-form-item>
+
+        <el-form-item label="是否启用" prop="status">
+          <el-switch
+            v-model="form.status"
+            active-value="0"
+            inactive-value="1"
+          />
+        </el-form-item>
+
+        <el-form-item label="站点logo" prop="siteLogo">
+          <div class="flex items-center gap-2">
+            <el-image
+              v-if="form.siteLogo"
+              :src="form.siteLogo"
+              :preview-src-list="[form.siteLogo]"
+              fit="cover"
+              class="w-[100px] h-[100px] rounded border border-gray-200"
+            />
+            <div class="flex flex-col gap-2">
+              <el-button type="primary" @click="openFileSelector('siteLogo')">选择图片</el-button>
+              <el-button v-if="form.siteLogo" type="danger" plain @click="form.siteLogo = ''">删除</el-button>
+            </div>
+          </div>
+        </el-form-item>
+
+        <el-form-item label="登陆广告图" prop="loginImg">
+          <div class="flex items-center gap-2">
+            <el-image
+              v-if="form.loginImg"
+              :src="form.loginImg"
+              :preview-src-list="[form.loginImg]"
+              fit="cover"
+              class="w-[100px] h-[100px] rounded border border-gray-200"
+            />
+            <div class="flex flex-col gap-2">
+              <el-button type="primary" @click="openFileSelector('loginImg')">选择图片</el-button>
+              <el-button v-if="form.loginImg" type="danger" plain @click="form.loginImg = ''">删除</el-button>
+            </div>
+          </div>
+        </el-form-item>
+
+        <el-form-item label="站点描述" prop="siteDescribe">
+          <el-input
+            v-model="form.siteDescribe"
+            type="textarea"
+            :rows="4"
+            placeholder="请输入站点描述"
+          />
+        </el-form-item>
+
+        <el-form-item label="关键字设置" prop="siteKeywords">
+          <el-input
+            v-model="form.siteKeywords"
+            type="textarea"
+            :rows="4"
+            placeholder="请输入关键字,多个关键字用逗号分隔"
+          />
+        </el-form-item>
+
+        <el-form-item>
+          <el-button type="primary" @click="submitForm">保 存</el-button>
+        </el-form-item>
+      </el-form>
+    </el-card>
+
+    <!-- 文件选择器 -->
+    <FileSelector
+      v-model="fileSelectorVisible"
+      :allowed-types="[1]"
+      :multiple="false"
+      title="选择图片"
+      @confirm="handleFileConfirm"
+    />
+  </div>
+</template>
+
+<script setup name="SiteConfig" lang="ts">
+import { ref, reactive, onMounted, computed, getCurrentInstance } from 'vue';
+import type { ComponentInternalInstance } from 'vue';
+import type { FormInstance } from 'element-plus';
+import { useRoute, useRouter } from 'vue-router';
+import { ArrowLeft } from '@element-plus/icons-vue';
+import { getSite, addSite, updateSite } from '@/api/product/site';
+import { SiteForm } from '@/api/product/site/types';
+import { getCustomerList } from '@/api/customerOperation/customerList';
+import FileSelector from '@/components/FileSelector/index.vue';
+
+const { proxy } = getCurrentInstance() as ComponentInternalInstance;
+const route = useRoute();
+const router = useRouter();
+
+// 是否编辑模式
+const isEdit = computed(() => !!route.query.id);
+
+// 客户列表
+const customerList = ref<any[]>([]);
+const customerLoading = ref(false);
+
+// 文件选择器
+const fileSelectorVisible = ref(false);
+const currentFileField = ref<'siteLogo' | 'loginImg'>('siteLogo');
+
+// 表单
+const formRef = ref<FormInstance>();
+const initForm: SiteForm = {
+  id: undefined,
+  siteName: '',
+  siteDomain: '',
+  clientId: undefined,
+  clientNo: '',
+  clientName: '',
+  siteLogo: '',
+  loginImg: '',
+  siteTitle: '',
+  siteDescribe: '',
+  siteKeywords: '',
+  startTime: '',
+  endTime: '',
+  status: '1',
+  isShow: 1
+};
+const form = ref<SiteForm>({ ...initForm });
+
+const rules = {
+  clientId: [{ required: true, message: '请选择客户', trigger: 'change' }],
+  siteName: [{ required: true, message: '请输入站点名称', trigger: 'blur' }]
+};
+
+// 搜索客户
+const searchCustomer = async (query: string) => {
+  customerLoading.value = true;
+  try {
+    const params: any = { pageSize: 20, pageNum: 1 };
+    if (query) {
+      params.clientName = query;
+    }
+    const res = await getCustomerList(params);
+    customerList.value = res.rows || [];
+  } catch (error) {
+    console.error('搜索客户失败:', error);
+    customerList.value = [];
+  } finally {
+    customerLoading.value = false;
+  }
+};
+
+// 客户选择框聚焦时加载数据
+const handleCustomerFocus = () => {
+  if (customerList.value.length === 0) {
+    searchCustomer('');
+  }
+};
+
+// 客户选择变化
+const handleCustomerChange = (clientId: string | number) => {
+  const customer = customerList.value.find(item => item.id === clientId);
+  if (customer) {
+    form.value.clientNo = customer.customerNo;
+    form.value.clientName = customer.customerName;
+    // 默认站点名称为客户名称
+    if (!form.value.siteName) {
+      form.value.siteName = customer.customerName;
+    }
+  }
+};
+
+// 打开文件选择器
+const openFileSelector = (field: 'siteLogo' | 'loginImg') => {
+  currentFileField.value = field;
+  fileSelectorVisible.value = true;
+};
+
+// 文件选择确认
+const handleFileConfirm = (files: any[]) => {
+  if (files.length > 0) {
+    const file = files[0];
+    form.value[currentFileField.value] = file.fileUrl || file.url;
+  }
+};
+
+// 获取详情
+const getDetail = async (id: number | string) => {
+  try {
+    const res = await getSite(id);
+    if (res.data) {
+      form.value = { ...res.data };
+    }
+  } catch (error) {
+    console.error('获取详情失败:', error);
+  }
+};
+
+// 返回
+const handleBack = () => {
+  router.push('/customerOperation/vipSite');
+};
+
+// 提交表单
+const submitForm = async () => {
+  const valid = await formRef.value?.validate();
+  if (!valid) return;
+
+  try {
+    if (form.value.id) {
+      await updateSite(form.value);
+      proxy?.$modal.msgSuccess('修改成功');
+    } else {
+      await addSite(form.value);
+      proxy?.$modal.msgSuccess('添加成功');
+    }
+    handleBack();
+  } catch (error) {
+    console.error('保存失败:', error);
+  }
+};
+
+onMounted(async () => {
+  const id = route.query.id;
+  if (id) {
+    await getDetail(id as string);
+  }
+});
+</script>
+
+<style scoped lang="scss">
+</style>

+ 34 - 5
src/views/platform/decoration/case/index.vue

@@ -8,7 +8,7 @@
         </el-form-item>
         <el-form-item label="服务类别" prop="projectTypeId">
           <el-select v-model="queryParams.projectTypeId" placeholder="请选择" clearable style="width: 160px">
-            <el-option v-for="item in projectTypeOptions" :key="item.id" :label="item.projectTypeName" :value="item.id" />
+            <el-option v-for="item in projectTypeOptions" :key="item.id" :label="item.typeName" :value="item.id" />
           </el-select>
         </el-form-item>
         <el-form-item>
@@ -54,7 +54,11 @@
           </template>
         </el-table-column>
         <el-table-column label="标题" prop="caseTitle" align="center" min-width="200" show-overflow-tooltip />
-        <el-table-column label="分类" prop="projectTypeName" align="center" width="120" />
+        <el-table-column label="项目类型" align="center" width="120">
+          <template #default="{ row }">
+            {{ getProjectTypeName(row) }}
+          </template>
+        </el-table-column>
         <el-table-column label="推荐" align="center" width="80">
           <template #default="{ row }">
             <span :class="row.isRecommend === '1' ? 'status-active' : 'status-inactive'">
@@ -121,7 +125,7 @@
           <el-col :span="12">
             <el-form-item label="项目类型" prop="projectTypeId">
               <el-select v-model="form.projectTypeId" placeholder="请选择项目类型" style="width: 100%">
-                <el-option v-for="item in projectTypeOptions" :key="item.id" :label="item.projectTypeName" :value="item.id" />
+                <el-option v-for="item in projectTypeOptions" :key="item.id" :label="item.typeName" :value="item.id" />
               </el-select>
             </el-form-item>
           </el-col>
@@ -279,6 +283,15 @@ const loadOptions = async () => {
   }
 };
 
+/** 根据行数据获取项目类型名称 */
+const getProjectTypeName = (row: any): string => {
+  if (row.typeName) return row.typeName;
+  const matched = projectTypeOptions.value.find(
+    item => item.typeName === row.projectType || String(item.id) === String(row.projectType)
+  );
+  return matched?.typeName || row.projectType || '-';
+};
+
 /** 搜索 */
 const handleQuery = () => {
   queryParams.pageNum = 1;
@@ -307,6 +320,10 @@ const handleView = async (row: any) => {
   dialog.title = '查看项目案例';
   dialog.isView = true;
   dialog.visible = true;
+  // 确保选项已加载,再加载详情以保证字段能正确匹配回显
+  if (!projectTypeOptions.value.length || !industryOptions.value.length) {
+    await loadOptions();
+  }
   await loadDetail(row.id);
 };
 
@@ -316,6 +333,10 @@ const handleEdit = async (row: any) => {
   dialog.title = '编辑项目案例';
   dialog.isView = false;
   dialog.visible = true;
+  // 确保选项已加载,再加载详情以保证字段能正确匹配回显
+  if (!projectTypeOptions.value.length || !industryOptions.value.length) {
+    await loadOptions();
+  }
   await loadDetail(row.id);
 };
 
@@ -324,11 +345,19 @@ const loadDetail = async (id: number) => {
   try {
     const res = await getServiceCase(id);
     const data = res.data || {};
+    // 通过 projectType 字段匹配项目类型选项
+    const matchedProjectType = projectTypeOptions.value.find(
+      item => item.typeName === data.projectType || item.id === data.projectType
+    );
+    // 通过 clientIndustry 字段匹配客户行业选项
+    const matchedIndustry = industryOptions.value.find(
+      item => item.industryCategoryName === data.clientIndustry || item.id === data.clientIndustry
+    );
     form.value = {
       id: data.id,
       caseTitle: data.caseTitle || '',
-      industryCategoryId: data.industryCategoryId,
-      projectTypeId: data.projectTypeId,
+      industryCategoryId: matchedIndustry?.id ?? data.industryCategoryId,
+      projectTypeId: matchedProjectType?.id ?? data.projectTypeId,
       isShow: data.isShow || '0',
       isRecommend: data.isRecommend || '0',
       isRelatedRecommend: data.isRelatedRecommend || '0',

+ 1 - 0
src/views/platform/decoration/caseAd/index.vue

@@ -130,6 +130,7 @@ onMounted(() => {
 
   .ad-image {
     width: 100%;
+    height: 300px;
     display: block;
   }
 

+ 83 - 2
src/views/platform/gift/floorAd/index.vue

@@ -132,6 +132,24 @@
       </template>
     </el-dialog>
 
+    <!-- 导入商品对话框 -->
+    <el-dialog v-model="importProductDialog.visible" title="导入商品" width="500px" append-to-body>
+      <el-form label-width="100px">
+        <el-form-item label="商品编号">
+          <el-input
+            v-model="importProductDialog.productNos"
+            type="textarea"
+            :rows="5"
+            placeholder="请输入商品编号,多个用逗号隔开"
+          />
+        </el-form-item>
+      </el-form>
+      <template #footer>
+        <el-button type="primary" @click="confirmImportProducts">确 定</el-button>
+        <el-button @click="importProductDialog.visible = false">取 消</el-button>
+      </template>
+    </el-dialog>
+
     <!-- 选择商品对话框 -->
     <el-dialog v-model="selectDialog.visible" title="商品信息" width="900px" append-to-body>
       <div class="select-dialog-header">
@@ -228,6 +246,12 @@ const productDialog = reactive({
 });
 const linkedProducts = ref<any[]>([]);
 
+// 导入商品弹框
+const importProductDialog = reactive({
+  visible: false,
+  productNos: ''
+});
+
 // 选择商品弹框
 const selectDialog = reactive({
   visible: false,
@@ -328,9 +352,66 @@ const handleAddProduct = () => {
   getProductList();
 };
 
-// 导入商品(暂时和新增一样
+// 导入商品(打开导入弹框
 const handleImportProduct = () => {
-  handleAddProduct();
+  importProductDialog.productNos = '';
+  importProductDialog.visible = true;
+};
+
+// 确认导入商品
+const confirmImportProducts = async () => {
+  const input = importProductDialog.productNos.trim();
+  if (!input) {
+    proxy?.$modal.msgWarning('请输入商品编号');
+    return;
+  }
+
+  // 解析商品编号(支持逗号、中文逗号、空格、换行分隔)
+  const productNos = input.split(/[,,\s\n]+/).map((s: string) => s.trim()).filter(Boolean);
+  if (productNos.length === 0) {
+    proxy?.$modal.msgWarning('请输入有效的商品编号');
+    return;
+  }
+
+  // 检查重复
+  const existingProductNos = linkedProducts.value.map((item: any) => item.productNo);
+  const newProductNos = productNos.filter((no: string) => !existingProductNos.includes(no));
+
+  if (newProductNos.length === 0) {
+    proxy?.$modal.msgWarning('所有商品编号已存在,请勿重复添加');
+    return;
+  }
+
+  try {
+    // 先查询商品信息获取 productId
+    const productRes = await listProduct({ productNos: newProductNos.join(','), pageSize: 1000 });
+    const productMap = new Map<string, any>((productRes.rows || []).map((p: any) => [p.productNo, p]));
+
+    for (const productNo of newProductNos) {
+      const product = productMap.get(productNo);
+      if (!product) {
+        console.warn(`商品编号 ${productNo} 不存在`);
+        continue;
+      }
+      await addGiftFloorLink({
+        floorId: productDialog.floorId,
+        productId: product.id,
+        productNo: productNo,
+        sort: 0,
+        status: '0'
+      });
+    }
+    const skipped = productNos.length - newProductNos.length;
+    if (skipped > 0) {
+      proxy?.$modal.msgSuccess(`导入成功,${skipped}个商品已存在被跳过`);
+    } else {
+      proxy?.$modal.msgSuccess('导入成功');
+    }
+    importProductDialog.visible = false;
+    getLinkedProducts();
+  } catch (error) {
+    proxy?.$modal.msgError('导入失败');
+  }
 };
 
 // 获取商品列表

+ 45 - 49
src/views/platform/gift/forYou/index.vue

@@ -130,25 +130,30 @@
     </el-dialog>
 
     <!-- 导入商品对话框 -->
-    <el-dialog v-model="importDialog.visible" title="导入商品(商品编号逗号隔开)" width="500px" append-to-body>
-      <el-form label-width="80px">
+    <el-dialog v-model="importProductDialog.visible" title="导入商品" width="500px" append-to-body>
+      <el-form label-width="100px">
         <el-form-item label="商品编号">
-          <el-input v-model="importDialog.productNos" type="textarea" :rows="5" placeholder="请输入内容" />
+          <el-input
+            v-model="importProductDialog.productNos"
+            type="textarea"
+            :rows="5"
+            placeholder="请输入商品编号,多个用逗号隔开"
+          />
         </el-form-item>
       </el-form>
       <template #footer>
-        <el-button type="primary" @click="confirmImportProducts">确 认</el-button>
-        <el-button @click="importDialog.visible = false">取 消</el-button>
+        <el-button type="primary" @click="confirmImportProducts">确 </el-button>
+        <el-button @click="importProductDialog.visible = false">取 消</el-button>
       </template>
     </el-dialog>
 
     <!-- 选择商品对话框 -->
-    <el-dialog v-model="selectDialog.visible" title="商品信息" width="900px" append-to-body>
+    <el-dialog v-model="selectProductDialog.visible" title="选择商品" width="900px" append-to-body>
       <div class="select-dialog-header">
-        <el-input v-model="selectDialog.keyword" placeholder="请输入商品编号名称输入搜索" style="width: 300px" />
+        <el-input v-model="selectProductDialog.keyword" placeholder="请输入商品编号名称搜索" style="width: 300px" />
         <el-button type="primary" @click="searchProducts">搜 索</el-button>
       </div>
-      <el-table v-loading="selectDialog.loading" :data="productList" border @selection-change="handleSelectionChange">
+      <el-table v-loading="selectProductDialog.loading" :data="productList" border @selection-change="handleProductSelectionChange">
         <el-table-column type="selection" width="50" align="center" />
         <el-table-column label="商品编号" align="center" prop="productNo" width="120" />
         <el-table-column label="商品名称" align="center" prop="itemName" min-width="180" show-overflow-tooltip />
@@ -163,22 +168,17 @@
             {{ scope.row.minSellingPrice || scope.row.marketPrice || '-' }}
           </template>
         </el-table-column>
-        <el-table-column label="排序" align="center" width="100">
-          <template #default="scope">
-            <el-input-number v-model="scope.row.sort" :min="0" size="small" controls-position="right" style="width: 80px" />
-          </template>
-        </el-table-column>
       </el-table>
       <pagination
-        v-show="selectDialog.total > 0"
-        v-model:page="selectDialog.pageNum"
-        v-model:limit="selectDialog.pageSize"
-        :total="selectDialog.total"
+        v-show="selectProductDialog.total > 0"
+        v-model:page="selectProductDialog.pageNum"
+        v-model:limit="selectProductDialog.pageSize"
+        :total="selectProductDialog.total"
         @pagination="getProductList"
       />
       <template #footer>
         <el-button type="primary" @click="confirmSelectProducts">确 定</el-button>
-        <el-button @click="selectDialog.visible = false">取 消</el-button>
+        <el-button @click="selectProductDialog.visible = false">取 消</el-button>
       </template>
     </el-dialog>
   </div>
@@ -239,7 +239,7 @@ const productDialog = reactive({
 const linkedProducts = ref<any[]>([]);
 
 // 选择商品弹框
-const selectDialog = reactive({
+const selectProductDialog = reactive({
   visible: false,
   loading: false,
   keyword: '',
@@ -251,7 +251,7 @@ const productList = ref<any[]>([]);
 const selectedProducts = ref<any[]>([]);
 
 // 导入商品弹框
-const importDialog = reactive({
+const importProductDialog = reactive({
   visible: false,
   productNos: ''
 });
@@ -338,30 +338,28 @@ const handleRemoveLinked = (row: any) => {
 
 // 新增商品
 const handleAddProduct = () => {
-  selectDialog.keyword = '';
-  selectDialog.pageNum = 1;
-  selectDialog.visible = true;
+  selectProductDialog.keyword = '';
+  selectProductDialog.pageNum = 1;
+  selectProductDialog.visible = true;
   getProductList();
 };
 
 // 导入商品
 const handleImportProduct = () => {
-  importDialog.productNos = '';
-  importDialog.visible = true;
+  importProductDialog.productNos = '';
+  importProductDialog.visible = true;
 };
 
 // 确认导入商品
 const confirmImportProducts = async () => {
-  if (!importDialog.productNos.trim()) {
+  const input = importProductDialog.productNos.trim();
+  if (!input) {
     proxy?.$modal.msgWarning('请输入商品编号');
     return;
   }
 
-  // 解析商品编号(支持逗号、中文逗号、换行分隔)
-  const productNos = importDialog.productNos
-    .split(/[,,\n]/)
-    .map((no: string) => no.trim())
-    .filter((no: string) => no);
+  // 解析商品编号(支持逗号、中文逗号、空格、换行分隔)
+  const productNos = input.split(/[,,\s\n]+/).map((s: string) => s.trim()).filter(Boolean);
 
   if (productNos.length === 0) {
     proxy?.$modal.msgWarning('请输入有效的商品编号');
@@ -382,7 +380,6 @@ const confirmImportProducts = async () => {
     const productRes = await listProduct({ productNos: newProductNos.join(','), pageSize: 1000 });
     const productMap = new Map((productRes.rows || []).map((p: any) => [p.productNo, p]));
 
-    let successCount = 0;
     for (const productNo of newProductNos) {
       const product = productMap.get(productNo);
       if (!product) {
@@ -396,15 +393,14 @@ const confirmImportProducts = async () => {
         sort: 0,
         status: '0'
       });
-      successCount++;
     }
-    
-    if (successCount > 0) {
-      proxy?.$modal.msgSuccess(`成功导入 ${successCount} 个商品`);
+    const skipped = productNos.length - newProductNos.length;
+    if (skipped > 0) {
+      proxy?.$modal.msgSuccess(`导入成功,${skipped}个商品已存在被跳过`);
     } else {
-      proxy?.$modal.msgWarning('没有找到有效的商品');
+      proxy?.$modal.msgSuccess('导入成功');
     }
-    importDialog.visible = false;
+    importProductDialog.visible = false;
     getLinkedProducts();
   } catch (error) {
     proxy?.$modal.msgError('导入失败');
@@ -413,30 +409,30 @@ const confirmImportProducts = async () => {
 
 // 获取商品列表
 const getProductList = async () => {
-  selectDialog.loading = true;
+  selectProductDialog.loading = true;
   try {
     const res = await listProduct({
-      keyword: selectDialog.keyword,
-      pageNum: selectDialog.pageNum,
-      pageSize: selectDialog.pageSize
+      keyword: selectProductDialog.keyword,
+      pageNum: selectProductDialog.pageNum,
+      pageSize: selectProductDialog.pageSize
     });
-    productList.value = (res.rows || []).map((item: any) => ({ ...item, sort: 0 }));
-    selectDialog.total = res.total || 0;
+    productList.value = res.rows || [];
+    selectProductDialog.total = res.total || 0;
   } catch (error) {
     console.error('加载商品列表失败', error);
   } finally {
-    selectDialog.loading = false;
+    selectProductDialog.loading = false;
   }
 };
 
 // 搜索商品
 const searchProducts = () => {
-  selectDialog.pageNum = 1;
+  selectProductDialog.pageNum = 1;
   getProductList();
 };
 
 // 选择变化
-const handleSelectionChange = (selection: any[]) => {
+const handleProductSelectionChange = (selection: any[]) => {
   selectedProducts.value = selection;
 };
 
@@ -463,12 +459,12 @@ const confirmSelectProducts = async () => {
         floorId: productDialog.floorId,
         productId: product.id,
         productNo: product.productNo,
-        sort: product.sort || 0,
+        sort: 0,
         status: '0'
       });
     }
     proxy?.$modal.msgSuccess('添加成功');
-    selectDialog.visible = false;
+    selectProductDialog.visible = false;
     getLinkedProducts();
   } catch (error) {
     proxy?.$modal.msgError('添加失败');