/** * 统一 API 响应封装 */ export type ApiEnvelope = { code?: number message?: string msg?: string data?: T } & Record /** * 分页结果(CMS API 标准格式) */ export interface PageResult { list?: T[] count?: number total?: number page?: number limit?: number pages?: number } /** * 分页参数 */ export interface PageParam { page?: number limit?: number } // ==================== 轮播图 ==================== /** * CMS 轮播图(/cms/cms-banner/page 返回项) * * 字段命名以 CMS 实际返回为准,这里用宽松的 unknown 兜底, * 避免后端字段名微调(image / imageUrl / pic / url)导致取不到图。 * 前端统一经 ensureFullUrl() 处理图片地址(OSS 直连或 /api/file/ 代理)。 */ export interface CmsBanner { id?: number /** 轮播图地址(CMS 可能用 image / imageUrl / pic / url 任一字段名) */ image?: string imageUrl?: string pic?: string url?: string /** 标题 / 副标题(可选,部分模板会叠加在图上) */ title?: string subtitle?: string /** 点击跳转链接(可选) */ link?: string /** 排序(升序)—— 上游字段名为 sortNum */ sortNum?: number /** 排序(升序,兼容旧字段名) */ sortOrder?: number /** 状态:banner 表约定 1=启用/已发布、2=下架(与 article/product/case 的 0=发布 相反,前端按「非 2 即显示」处理) */ status?: number /** 逻辑删除标记:1=已删除 */ deleted?: number [key: string]: unknown } // ==================== 站点信息 ==================== /** * CMS getSiteInfo 返回的完整站点信息 */ export interface CmsSiteInfo { websiteId?: number websiteName?: string websiteCode?: string websiteIcon?: string websiteLogo?: string websiteDarkLogo?: string websiteType?: string /** * 模板主键(cms_website.template_id → app_template.id) * ⚠️ 主键存在跳号风险(历史上 id=8 被跳过),不可直接补零当目录名,请优先用 templateCode。 */ templateId?: number /** * 模板标识(app_template.code,形如 'template-08'),与前端 app/templates/ 目录一一对应。 * 上游 getSiteInfo 若未返回,由 SSR 侧 server/utils/template-map.ts 反查模板表回填。 */ templateCode?: string domain?: string keywords?: string /** 品牌标语 / Slogan(后台「网站设置」可配置) */ slogan?: string phone?: string email?: string address?: string icpNo?: string policeNo?: string content?: string | null comments?: string prefix?: string status?: number running?: number expirationTime?: string tenantId?: number topNavs?: CmsNavigation[] bottomNavs?: CmsNavigation[] slide?: unknown ads?: unknown layout?: unknown /** 社交外部链接(后台「网站设置」录入;标准字段名为 socialLinks,可为数组或 JSON 字符串) */ socialLinks?: SocialLink[] | unknown /** @deprecated 兼容旧字段名,优先使用 socialLinks */ links?: SocialLink[] | unknown /** 二维码(顶层字段,通常为 null;微信公众号二维码优先取 config.wxQrcode) */ qrCode?: string | null config?: SiteConfig setting?: SiteSetting serverTime?: ServerTime } /** * 站点配置(来自 config 字段) */ export interface SiteConfig { icpNo?: string copyright?: string SysDomain?: string Domain?: string address?: string tel?: string email?: string wxQrcode?: string wxQrcodeText?: string [key: string]: unknown } /** * 站点设置 */ export interface SiteSetting { id?: number websiteId?: number official?: boolean market?: boolean search?: boolean share?: boolean articleReview?: boolean plugin?: boolean editor?: number searchBtn?: boolean loginBtn?: boolean floatTool?: boolean copyrightLink?: boolean maxMenuNum?: boolean sortNumber?: number deleted?: number tenantId?: number /** * 在线留言是否要求滑块验证码。 * true=必须完成滑块验证才能提交;未设置/false=不显示滑块、直接提交。 * 由后台「网站设置」下发,配合前端 ContactForm 与后端 submit 校验。 */ requireCaptcha?: boolean /** * 首页「优势模块」配置(JSON 字符串,存于 cms_website_setting.features) * 结构见 FeatureSectionSetting;未配置(null/空)时前端回退模板默认数据。 */ features?: string } /** * 首页优势模块 - 单卡片 * * icon 为图标名(对应前端 FeatureIcon 的 36 个通用图标之一,如 building / box / case / message)。 * 后台只存名字,不存 SVG,避免样式耦合。 */ export interface FeatureItem { /** 卡片标题 */ title: string /** 卡片描述 */ desc: string /** 图标名(FeatureIcon 组件支持的名称集合) */ icon: string } /** * 首页单区块开关(预留位共用) */ export interface HomeBlockToggle { /** 是否显示该区块;默认 true */ enabled?: boolean } /** * 首页首屏特性卡片区块 */ export interface HeroBlock extends HomeBlockToggle { /** 首屏右侧特性卡片文案(纯文本,最多 4 条) */ features?: string[] } /** * 首页优势模块区块 */ export interface AdvantagesBlock extends HomeBlockToggle { /** 模块标题,如「我们的优势」 */ title?: string /** 模块副标题 */ subtitle?: string /** 卡片列表,最多 4 个 */ items?: FeatureItem[] } /** * 首页各区块统一开关集合 */ export interface HomeBlocks { /** 首屏特性卡片 */ hero?: HeroBlock /** 我们的优势 */ advantages?: AdvantagesBlock /** 产品展示(预留位) */ products?: HomeBlockToggle /** 案例展示(预留位) */ cases?: HomeBlockToggle /** 新闻动态(预留位) */ news?: HomeBlockToggle /** Banner 轮播 */ banner?: HomeBlockToggle /** CTA 联系 */ cta?: HomeBlockToggle } /** * 首页优势模块 - 完整配置 * * 存于 cms_website_setting.features(JSON 字符串)。 * 前端 useFeatures() 解析:未配置 → 回退模板默认 4 项;enabled=false → 隐藏整块。 * * 2026-08-05 后台新增「首页区块总览」,支持区块化新结构: * { hero, advantages, products, cases, news, banner, cta } * 同时兼容旧结构(顶层 enabled/title/subtitle/items/heroFeatures)。 */ export interface FeatureSectionSetting extends HomeBlocks { /** 旧结构:是否显示整块(默认 true) */ enabled?: boolean /** 旧结构:模块标题 */ title?: string /** 旧结构:模块副标题 */ subtitle?: string /** 旧结构:卡片列表 */ items?: FeatureItem[] /** 旧结构:Hero 首屏右侧特性卡片文案 */ heroFeatures?: string[] } /** * 服务器时间 */ export interface ServerTime { now?: string today?: string tomorrow?: string afterDay?: string week?: number nextWeek?: string timestamp?: number } // ==================== 导航 ==================== /** * CMS 导航项(来自 topNavs / bottomNavs) */ export interface CmsNavigation { navigationId?: number type?: number title?: string parentId?: number /** 栏目背景图(后台「栏目管理」上传,存 cms_navigation.background 列) */ background?: string /** 模型类型: index | page | article | product */ model?: string code?: string /** 跳转路径/路由(CMS 原字段) */ path?: string /** 链接地址(自定义 URL,优先级高于 path,用于导航跳转) */ url?: string component?: string suffix?: string | null target?: string icon?: string | null banner?: string | null mpBanner?: string | null color?: string | null hide?: number permission?: number password?: string position?: number top?: number bottom?: number active?: unknown meta?: unknown style?: unknown sortNumber?: number home?: number recommend?: boolean comments?: string deleted?: number status?: number tenantId?: number pageId?: number /** 绑定单页的访问路径 slug(VO 透传,来自 cms_page.path);存在时 getNavLink 优先跳 /page/{slug} */ pagePath?: string /** 绑定单页的标题(VO 透传,来自 cms_page.title) */ pageTitle?: string /** 绑定单页的状态(VO 透传,来自 cms_page.status) */ pageStatus?: number itemId?: number isMpWeixin?: boolean gutter?: number span?: number readNum?: number userId?: number merchantId?: number lang?: string langCategoryId?: number /** 子导航 */ children?: CmsNavigation[] /** 分类名称 */ categoryName?: string /** 分类路径 */ categoryPath?: string photo?: string | null text?: string [key: string]: unknown } // ==================== 文章 ==================== /** * CMS 文章 */ export interface Article { id?: number articleId?: number title?: string summary?: string content?: string /** 文章附件(结构同 PageAttachment:name/url/size/ext/sort) */ files?: PageAttachment[] /** 文章封面图(CMS 实际返回字段,优先级最高) */ image?: string cover?: string photo?: string author?: string source?: string categoryId?: number categoryName?: string navigationId?: number tags?: string[] viewCount?: number readNum?: number sortNumber?: number status?: number /** 推荐标识:1=推荐(后台勾选「推荐」)。上游 cms-article 实际返回字段为 recommend。 */ recommend?: number /** 置顶标识:>0 为置顶(部分租户文章表含 top 字段;上游未普遍支持时以 recommend 为准) */ top?: number publishTime?: string createTime?: string updateTime?: string [key: string]: unknown } // ==================== 产品 ==================== /** * CMS 产品 */ export interface Product { id?: number productId?: number productName?: string subtitle?: string description?: string content?: string cover?: string photo?: string images?: string[] categoryId?: number categoryName?: string tags?: string[] price?: string sortNumber?: number status?: number /** 置顶标识:>0 为置顶(首页明星产品据此过滤) */ top?: number /** 推荐标识:1=推荐(部分 CMS 版本产品表含 recommend 字段,与 top 并存) */ recommend?: number createTime?: string updateTime?: string [key: string]: unknown } // ==================== 案例 ==================== /** * CMS 案例 */ export interface CaseItem { id?: number title?: string summary?: string content?: string cover?: string images?: string[] categoryId?: number categoryName?: string clientName?: string projectTime?: string tags?: string[] sortNumber?: number status?: number /** 置顶标识:>0 为置顶(当前上游 cms-case 表暂无此字段,预留以便后续版本自动生效) */ top?: number /** 推荐标识:1=推荐(当前上游 cms-case 表暂无此字段,预留以便后续版本自动生效) */ recommend?: number createTime?: string updateTime?: string [key: string]: unknown } // ==================== 单页详情(/api/page/detail) ==================== /** * 单页详情返回结构 * * 代理层 server/api/page/detail.get.ts 有两个数据来源,返回结构统一: * 1. ?navigationId=4296 → 上游 /cms/cms-navigation/{id},把节点内嵌的 design 扁平化 * 2. ?path=about → 上游 /cms/cms-page/getByPath/{slug},即「单页管理」cms_page 表 * * 因此 navigationId(来源1专有)与 pageId/path/keywords/description(来源2专有) * 均为可选字段,使用前先判空。前端用 status 区分三种状态: * - 'ok' :记录存在且 content 非空,渲染正文 * - 'empty' :记录不存在,或记录存在但正文未录入 * - 'error' :上游接口异常 */ export interface PageDetail { status: 'ok' | 'empty' | 'error' /** 导航节点 ID(仅 navigationId 入口返回) */ navigationId?: number /** 单页 ID(仅 cms_page 入口返回) */ pageId?: number /** 单页路径 slug,如 about / contact(仅 cms_page 入口返回) */ path?: string title: string model: string parentId: number /** 正文 HTML(来源1为 design.content,来源2为 cms_page.content) */ content: string /** 结构化设计(来自 design.layout,预留) */ layout: unknown /** 头图(来源1为 design.photo,来源2为 cms_page.image) */ photo: string | null /** SEO 关键词(仅 cms_page 入口返回) */ keywords?: string /** SEO 描述(仅 cms_page 入口返回) */ description?: string hasContent: boolean updateTime: string | null /** 页面附件列表(仅 cms_page 入口返回,来自 cms_page.attachments JSON) */ attachments?: PageAttachment[] /** empty / error 时的说明 */ message?: string } /** * 单页附件(来自后台「单页管理」cms_page.attachments) * * 与 website-admin 上传组件写入结构保持一致: * { name, url, size, ext, sort } */ export interface PageAttachment { /** 原始文件名(含扩展名),用于展示与下载文件名 */ name: string /** 文件直链(MinIO / OSS 全 URL) */ url: string /** 文件大小(字节) */ size?: number /** 扩展名(小写,不含点),如 xlsx / pdf */ ext?: string /** 排序(升序) */ sort?: number } // ==================== 兼容旧类型 ==================== /** * 站点信息(兼容层,映射到 CmsSiteInfo) * @deprecated 使用 CmsSiteInfo */ export type SiteInfo = CmsSiteInfo /** * CMS 页面(兼容旧代码) */ export interface CmsPage { id?: number siteId?: number slug?: string title?: string description?: string keywords?: string type?: string sortNumber?: number showInNav?: boolean navName?: string content?: string cover?: string seoTitle?: string seoDescription?: string createTime?: string updateTime?: string } // ==================== 订阅与域名 ==================== /** * 应用订阅状态 */ export interface SubscriptionStatus { subscribed?: boolean status?: string expireTime?: string startTime?: string expired?: boolean subscriptionId?: number productId?: number productName?: string } /** * 域名解析结果 * 后端改造后可直接返回完整 AppProduct */ export interface DomainResolveResult { tenantId?: string appId?: string templateId?: string found?: boolean /** 后端可选返回的完整应用产品信息 */ appProduct?: AppProduct } // ==================== 应用产品 ==================== /** * 应用产品信息(app_product 表) * 后端库位于 https://websopy-api.websoft.top,表前缀 app_ */ export interface AppProduct { productId?: number productName?: string /** 应用唯一标识码 */ productCode?: string /** 应用类型:1=网站 等 */ appType?: number /** 绑定域名(核心字段) */ domain?: string /** 域名前缀 */ prefix?: string /** 前台访问 URL */ homeUrl?: string /** 后台管理 URL */ adminUrl?: string /** 应用图标 */ icon?: string /** 应用 Logo */ logo?: string /** * 关联模板主键(app_template.id),也可能已是字符串 "template-XX"。 * ⚠️ 主键存在跳号风险,解析目录名请优先用 templateCode,本字段仅兜底。 */ templateId?: number | string /** 关联模板标识(app_template.code,形如 'template-08'),与前端模板目录一一对应 */ templateCode?: string status?: number running?: number expirationTime?: string tenantId?: number createTime?: string updateTime?: string [key: string]: unknown } /** * 应用域名绑定(app_domain 表) * 自定义域名 → 租户/应用的绑定关系,作为「app_product.domain」之外的兜底解析来源 */ export interface AppDomain { /** 绑定域名 */ domain?: string /** 关联租户 ID(核心字段,用于下游接口 TenantId 头) */ tenantId?: number /** 关联应用产品 ID(用于回查完整 AppProduct 取模板/品牌) */ productId?: number /** 关联模板 ID(可选,优先以 AppProduct 为准) */ templateId?: number | string status?: number [key: string]: unknown } // ==================== 租户上下文 ==================== /** * 租户上下文(Server Middleware 识别结果) */ export interface TenantContext { tenantId: string appId: string templateId?: string source?: 'domain' | 'subdomain' | 'env' | 'header' host?: string /** 应用产品信息(中间件查询后填充) */ appProduct?: AppProduct } // ==================== 社交链接 ==================== export interface SocialLink { type?: string name?: string url?: string icon?: string }