Files
website/scripts/render-check.mjs
T
Dai0814 07b8321d18
mirror-to-gitee / mirror (push) Successful in 4s
feat(portal): 移除教程模块(导航入口、教程中心页、三项目教程视频页)
按要求删除导航栏「教程」及整个教程模块。

删除内容
- 导航项「教程」(/guide/videos) 及其 activeMatch
- 侧边栏「教程中心」条目
- pages/guide/videos.md(教程中心页,76 行)
- pages/projects/{iotlcm,xiu-dtu,xiu-admin}/guide/tutorial.md(三份教程视频页,共 251 行)
- data/projects.ts 里三个项目的「教程视频」子页面登记

同步清理的引用(避免 404)
- pages/projects/iotlcm/index.md:“下一步”表格里的教程视频行
- pages/projects/iotlcm/guide/quickstart.md:“下一步”里的教程链接
- 4 个审计脚本里的教程路径:
  audit-band-offset.mjs / audit-layout.mjs / audit-sidebar-footer.mjs / render-check.mjs
- 注释里「教程等」的措辞

保留的内容(与本次无关,未动)
- DemoVideo.vue 及其 data/demo.ts 里的 demoVideos:
  那是「系统演示」页里的 BV 号外链卡片,不是教程栏目
- compare.md 里「录屏教程」对比维度:属于竞品对比表的正常描述

验证
- 导航实测 8 项:首页/系统演示/产品功能/核心能力/界面预览/项目/官方文档/联系我们,无「教程」
- 侧边栏实测仅「门户部署」
- 干净副本构建成功,产物中无 tutorial/videos 文件
- 断链检查:失效项里无一条与教程相关
- check-registry 与 check-nav-brand 通过
2026-11-06 11:56:25 +08:00

56 lines
2.6 KiB
JavaScript

import { chromium } from 'playwright'
import { createServer } from 'http'
import { stat, readFile } from 'fs/promises'
import { extname, join, normalize } from 'path'
const ROOT = process.argv[2]
if (!ROOT) { console.error('用法: node render-check.mjs <distDir>'); process.exit(1) }
const MIME = { '.html':'text/html', '.js':'text/javascript', '.css':'text/css', '.svg':'image/svg+xml', '.png':'image/png', '.jpg':'image/jpeg', '.json':'application/json', '.woff2':'font/woff2', '.woff':'font/woff', '.ico':'image/x-icon' }
const server = createServer(async (req, res) => {
try {
let p = decodeURIComponent(req.url.split('?')[0])
if (!p.startsWith('/')) p = '/' + p
let fp = normalize(join(ROOT, p))
try { await stat(fp) } catch {
try { await stat(fp + '.html'); fp = fp + '.html' }
catch { try { await stat(join(fp, 'index.html')); fp = join(fp, 'index.html') } catch { fp = join(ROOT, '404.html') } }
}
if ((await stat(fp)).isDirectory()) fp = join(fp, 'index.html')
const buf = await readFile(fp)
res.writeHead(200, { 'content-type': MIME[extname(fp)] || 'application/octet-stream' })
res.end(buf)
} catch { res.writeHead(404); res.end('nf') }
})
await new Promise(r => server.listen(0, r))
const port = server.address().port
const browser = await chromium.launch({ channel: 'chrome', args: ['--no-sandbox'] })
const ctx = await browser.newContext({ viewport: { width: 1440, height: 900 }, javaScriptEnabled: false })
const page = await ctx.newPage()
const pages = ['', 'demo', 'about', 'projects/', 'projects/iotlcm/', 'projects/xiu-dtu/', 'projects/xiu-admin/', 'guide/portal-deploy', 'projects/iotlcm/compare', 'projects/iotlcm/guide/deploy', 'projects/iotlcm/guide/quickstart']
const results = []
for (const p of pages) {
const resp = await page.goto(`http://127.0.0.1:${port}/${p}`, { waitUntil: 'load' })
const info = await page.evaluate(() => ({
is404: !!document.querySelector('.NotFound'),
h1: (document.querySelector('h1')?.textContent || '').trim().slice(0, 36),
hasNav: !!document.querySelector('.VPNav'),
hasFooter: !!document.querySelector('.pf-foot'),
}))
results.push({ p: p || '(home)', status: resp.status(), ...info })
}
console.log(`\n=== ${ROOT} 渲染验收 ===`)
let ok = 0, fail = 0
for (const r of results) {
const bad = r.is404 || r.status >= 400 || !r.hasNav
if (bad) fail++; else ok++
console.log(`${bad ? '❌' : '✅'} ${String(r.status).padEnd(4)} ${r.p.padEnd(34)} ${r.is404 ? '[404页]' : ''} ${r.h1}`)
}
console.log(`\n通过 ${ok} / 失败 ${fail}`)
await browser.close()
server.close()
process.exit(fail ? 1 : 0)