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,141 @@
|
||||
#!/usr/bin/env node
|
||||
/**
|
||||
* CMS 内容批量发布脚本
|
||||
* ============================================================================
|
||||
* 背景(2026-08-05):
|
||||
* website-admin 后台的状态枚举与 CMS 后端语义相反——后台点「发布 / 上架」写入
|
||||
* status=1,而 CMS 权威语义是 0=已发布、1=待审核、2=已下线,
|
||||
* C 端 website-template(server/api/{article,product,case}/list.get.ts)只查
|
||||
* status=0,于是后台明明「已发布」,官网却一条都不显示。
|
||||
*
|
||||
* 后台枚举已在本次一并修正,但存量数据仍停在 status=1,需要用本脚本批量刷成 0。
|
||||
*
|
||||
* 用法:
|
||||
* # 1) 先空跑,只打印将要修改的内容,不写任何数据
|
||||
* ADMIN_TOKEN="eyJ..." TENANT_ID=10546 node scripts/publish-cms-content.mjs
|
||||
*
|
||||
* # 2) 确认无误后真正执行
|
||||
* ADMIN_TOKEN="eyJ..." TENANT_ID=10546 APPLY=1 node scripts/publish-cms-content.mjs
|
||||
*
|
||||
* # 只处理文章
|
||||
* ADMIN_TOKEN="eyJ..." TENANT_ID=10546 MODULES=article APPLY=1 node scripts/publish-cms-content.mjs
|
||||
*
|
||||
* token 获取:后台 DevTools → Network → 任意 XHR → Request Headers → Authorization
|
||||
* (连同 "Bearer " 前缀一起复制即可,脚本会自动补全)
|
||||
* ============================================================================
|
||||
*/
|
||||
|
||||
const API_BASE = process.env.CMS_API_BASE || 'https://cms-api.websoft.top/api'
|
||||
const TOKEN_RAW = process.env.ADMIN_TOKEN || ''
|
||||
const TENANT_ID = process.env.TENANT_ID || '10546'
|
||||
const APPLY = process.env.APPLY === '1'
|
||||
const MODULES = (process.env.MODULES || 'article,product,case')
|
||||
.split(',')
|
||||
.map((s) => s.trim())
|
||||
.filter(Boolean)
|
||||
|
||||
/** CMS 权威语义:0=已发布 / 在售(C 端可见) */
|
||||
const PUBLISHED = 0
|
||||
|
||||
/** 各模块的接口路径与主键字段 */
|
||||
const MODULE_CONF = {
|
||||
article: { path: 'cms-article', idKey: 'articleId', titleKey: 'title' },
|
||||
product: { path: 'cms-product', idKey: 'productId', titleKey: 'productName' },
|
||||
case: { path: 'cms-case', idKey: 'caseId', titleKey: 'title' }
|
||||
}
|
||||
|
||||
if (!TOKEN_RAW) {
|
||||
console.error('❌ 缺少 ADMIN_TOKEN。')
|
||||
console.error(' ADMIN_TOKEN="eyJ..." TENANT_ID=10546 node scripts/publish-cms-content.mjs')
|
||||
process.exit(1)
|
||||
}
|
||||
|
||||
const TOKEN = TOKEN_RAW.startsWith('Bearer ') ? TOKEN_RAW : `Bearer ${TOKEN_RAW}`
|
||||
|
||||
const headers = {
|
||||
'Content-Type': 'application/json',
|
||||
Authorization: TOKEN,
|
||||
TenantId: String(TENANT_ID)
|
||||
}
|
||||
|
||||
async function fetchList(conf) {
|
||||
const url = `${API_BASE}/cms/${conf.path}/page?page=1&limit=200`
|
||||
const res = await fetch(url, { headers })
|
||||
const json = await res.json().catch(() => null)
|
||||
if (!json || json.code !== 0) {
|
||||
throw new Error(`拉取列表失败 (${res.status}): ${JSON.stringify(json)?.slice(0, 200)}`)
|
||||
}
|
||||
return json.data?.list || []
|
||||
}
|
||||
|
||||
async function updateStatus(conf, id) {
|
||||
const url = `${API_BASE}/cms/${conf.path}/status`
|
||||
const res = await fetch(url, {
|
||||
method: 'PUT',
|
||||
headers,
|
||||
body: JSON.stringify({ [conf.idKey]: id, status: PUBLISHED })
|
||||
})
|
||||
const text = await res.text()
|
||||
let json = null
|
||||
try { json = JSON.parse(text) } catch { /* 非 JSON 响应 */ }
|
||||
return { ok: res.ok && (json === null || json.code === 0), status: res.status, body: text.slice(0, 200) }
|
||||
}
|
||||
|
||||
async function run() {
|
||||
console.log(`租户 ${TENANT_ID} | 模块 ${MODULES.join(', ')} | 模式 ${APPLY ? '实际执行' : '空跑预演'}`)
|
||||
console.log('─'.repeat(70))
|
||||
|
||||
let totalPending = 0
|
||||
let totalOk = 0
|
||||
let totalFail = 0
|
||||
|
||||
for (const mod of MODULES) {
|
||||
const conf = MODULE_CONF[mod]
|
||||
if (!conf) {
|
||||
console.log(`跳过未知模块 ${mod}`)
|
||||
continue
|
||||
}
|
||||
|
||||
let list
|
||||
try {
|
||||
list = await fetchList(conf)
|
||||
} catch (e) {
|
||||
console.log(`\n[${mod}] 拉取失败:${e.message}`)
|
||||
continue
|
||||
}
|
||||
|
||||
const pending = list.filter((it) => Number(it.status) !== PUBLISHED)
|
||||
console.log(`\n[${mod}] 共 ${list.length} 条,其中 ${pending.length} 条未发布(status !== 0)`)
|
||||
totalPending += pending.length
|
||||
|
||||
for (const it of pending) {
|
||||
const id = it[conf.idKey] ?? it.id
|
||||
const title = String(it[conf.titleKey] || it.title || '(无标题)').slice(0, 30)
|
||||
if (!APPLY) {
|
||||
console.log(` · [预演] ${id} status=${it.status} → 0 | ${title}`)
|
||||
continue
|
||||
}
|
||||
const r = await updateStatus(conf, id)
|
||||
if (r.ok) {
|
||||
totalOk++
|
||||
console.log(` ✓ ${id} → 已发布 | ${title}`)
|
||||
} else {
|
||||
totalFail++
|
||||
console.log(` ✗ ${id} 失败 HTTP ${r.status} ${r.body} | ${title}`)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
console.log('\n' + '─'.repeat(70))
|
||||
if (APPLY) {
|
||||
console.log(`完成:成功 ${totalOk} 条,失败 ${totalFail} 条`)
|
||||
console.log('前台若有缓存,等待缓存过期或重启 Node 进程后即可看到内容。')
|
||||
} else {
|
||||
console.log(`空跑结束:共 ${totalPending} 条待发布。加 APPLY=1 重新运行即可实际写入。`)
|
||||
}
|
||||
}
|
||||
|
||||
run().catch((e) => {
|
||||
console.error('执行出错:', e)
|
||||
process.exit(1)
|
||||
})
|
||||
Reference in New Issue
Block a user