Files
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

162 lines
5.4 KiB
JavaScript

import { chromium } from 'playwright'
/**
* 全站布局审计
*
* 查两类问题(都是「页脚被裁 / 页面被挤」的典型症状):
* 1) 横向溢出:文档元素 scrollWidth 超过 clientWidth,页面出现左右滚动条
* 2) 页脚错位:footer 的左边界没有对齐内容区,或宽度小于视口(被侧边栏挤掉一块)
* 3) 页脚被遮挡:footer 宽度正常但被侧栏覆盖
*/
const BASE = 'http://127.0.0.1:8160/www'
const PAGES = [
'/',
'/demo.html',
'/about.html',
'/projects/',
'/projects/iotlcm/',
'/projects/xiu-dtu/',
'/projects/xiu-admin/',
'/projects/iotlcm/compare.html',
'/projects/iotlcm/guide/quickstart.html',
'/projects/iotlcm/guide/deploy.html',
'/guide/portal-deploy.html',
'/public/screenshots/readme.html',
]
const VIEWPORTS = [
{ w: 1440, h: 900, name: 'desktop' },
{ w: 1024, h: 800, name: 'laptop' },
{ w: 820, h: 900, name: 'tablet' },
{ w: 390, h: 844, name: 'mobile' },
]
const browser = await chromium.launch({
executablePath: 'C:\\Program Files\\Google\\Chrome\\Application\\chrome.exe',
})
const problems = []
for (const vp of VIEWPORTS) {
const page = await browser.newPage({ viewport: { width: vp.w, height: vp.h } })
for (const path of PAGES) {
try {
await page.goto(BASE + path, { waitUntil: 'networkidle', timeout: 30000 })
await page.waitForTimeout(250)
const r = await page.evaluate(() => {
const out = {}
// 1) 横向溢出
out.docScrollWidth = document.documentElement.scrollWidth
out.docClientWidth = document.documentElement.clientWidth
out.hOverflow = out.docScrollWidth - out.docClientWidth
// 找出真正超宽的元素(排除 fixed 定位的装饰层)
const wide = []
document.querySelectorAll('body *').forEach((el) => {
const cs = getComputedStyle(el)
if (cs.position === 'fixed' || cs.position === 'sticky') return
if (cs.overflowX !== 'visible') return
const rect = el.getBoundingClientRect()
if (rect.width === 0) return
if (rect.right > document.documentElement.clientWidth + 1) {
wide.push({
tag: el.tagName.toLowerCase(),
cls: (el.className || '').toString().slice(0, 60),
right: Math.round(rect.right),
width: Math.round(rect.width),
})
}
})
out.wideEls = wide.slice(0, 5)
out.wideCount = wide.length
// 2) 页脚位置
const footer = document.querySelector('footer.pf-foot')
if (footer) {
const fr = footer.getBoundingClientRect()
const wrap = footer.querySelector('.p-wrap')
const wr = wrap ? wrap.getBoundingClientRect() : null
out.footer = {
left: Math.round(fr.left),
width: Math.round(fr.width),
vpWidth: document.documentElement.clientWidth,
wrapLeft: wr ? Math.round(wr.left) : null,
wrapWidth: wr ? Math.round(wr.width) : null,
// 页脚是否铺满视口宽度(允许 1px 误差)
spansVp: Math.abs(fr.width - document.documentElement.clientWidth) <= 1,
// 页脚是否从视口左边开始
startsAtZero: Math.abs(fr.left) <= 1,
}
} else {
out.footer = null
}
// 3) 侧边栏是否覆盖页脚(取侧栏底部与页脚顶部比较)
const aside = document.querySelector('.VPSidebar')
if (aside) {
const ar = aside.getBoundingClientRect()
const cs = getComputedStyle(aside)
out.sidebar = {
left: Math.round(ar.left),
right: Math.round(ar.right),
top: Math.round(ar.top),
bottom: Math.round(ar.bottom),
width: Math.round(ar.width),
position: cs.position,
visible: cs.display !== 'none' && ar.width > 0,
}
} else {
out.sidebar = null
}
return out
})
const issues = []
if (r.hOverflow > 2) {
issues.push(`横向溢出 ${r.hOverflow}px`)
}
if (r.footer) {
if (!r.footer.spansVp) issues.push(`页脚宽度 ${r.footer.width} ≠ 视口 ${r.footer.vpWidth}`)
if (!r.footer.startsAtZero) issues.push(`页脚左偏 ${r.footer.left}px`)
// 页脚内容区如果没从 0 附近开始,说明被侧栏挤了
if (r.sidebar?.visible && r.footer.wrapLeft !== null) {
if (Math.abs(r.footer.wrapLeft - r.footer.left) > 40) {
issues.push(`页脚内容被挤:wrap.left=${r.footer.wrapLeft}, footer.left=${r.footer.left}`)
}
}
} else {
issues.push('页脚缺失')
}
if (issues.length) {
problems.push({ vp: vp.name, path, issues, detail: r })
}
} catch (e) {
problems.push({ vp: vp.name, path, issues: [`访问失败: ${e.message.slice(0, 80)}`] })
}
}
await page.close()
}
await browser.close()
if (!problems.length) {
console.log('✅ 未发现布局问题')
} else {
console.log(`❌ 发现 ${problems.length} 处问题\n`)
for (const p of problems) {
console.log(`[${p.vp}] ${p.path}`)
for (const i of p.issues) console.log(' - ' + i)
if (p.detail?.wideEls?.length) {
console.log(' 超宽元素: ' + JSON.stringify(p.detail.wideEls))
}
console.log()
}
}