feat(app): 添加多模板关于我们页面及相关路由和404页面
- 新增404页面,优化未找到页面体验,避免被搜索引擎索引 - 增加文件代理接口,隐藏真实文件服务器地址,支持文件请求代理 - 实现/article、/case、/product及/page动态路由兼容列表与详情展示 - 添加动态CMS页面兼容入口处理旧式路径,统一路由与SEO设置 - 新增模板1、模板7、模板2、模板3关于我们页面,实现多模板支持 - 模板增强支持CMS单页内容加载及SEO信息动态设置 - 配置环境变量及Git忽略文件规则辅助开发和构建环境管理
This commit is contained in:
@@ -0,0 +1,633 @@
|
||||
/**
|
||||
* 统一 API 响应封装
|
||||
*/
|
||||
export type ApiEnvelope<T = unknown> = {
|
||||
code?: number
|
||||
message?: string
|
||||
msg?: string
|
||||
data?: T
|
||||
} & Record<string, unknown>
|
||||
|
||||
/**
|
||||
* 分页结果(CMS API 标准格式)
|
||||
*/
|
||||
export interface PageResult<T = unknown> {
|
||||
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
|
||||
}
|
||||
Reference in New Issue
Block a user