diff --git a/.claude/memory/screenshots/din-dish-add-pricing-after.png b/.claude/memory/screenshots/din-dish-add-pricing-after.png deleted file mode 100644 index 6f830a0..0000000 Binary files a/.claude/memory/screenshots/din-dish-add-pricing-after.png and /dev/null differ diff --git a/.claude/memory/screenshots/din-dish-add-pricing-v2.png b/.claude/memory/screenshots/din-dish-add-pricing-v2.png deleted file mode 100644 index c757488..0000000 Binary files a/.claude/memory/screenshots/din-dish-add-pricing-v2.png and /dev/null differ diff --git a/.claude/skills/android-capture/SKILL.md b/.claude/skills/android-capture/SKILL.md new file mode 100644 index 0000000..8988f51 --- /dev/null +++ b/.claude/skills/android-capture/SKILL.md @@ -0,0 +1,132 @@ +--- +name: android-capture +description: 通过 Android 真机 + adb 无线调试截取原生 APP 实际页面截图(用于操作手册)。当用户要求「截取 APP 实际页面」「真机调试截图」「生成 XX 端 APP 操作手册截图」时使用。 +--- + +# Skill:Android 真机 APP 截图 + +## 触发方式 + +用户说「截取 XX APP 的实际页面截图」「真机调试截图」时触发。 +用于原生 Android / Flutter 混开 APP(iOS 同款只做 Android)的实际页面截图。 + +## 连接方式:无线调试(云桌面场景首选) + +**关键背景**:本环境是云桌面 + 云终端盒子,无本地物理机。手机 USB 插在云终端盒子上,但盒子只传画面不传 USB 设备,**云桌面的 Windows 看不到手机**,`adb devices` 永远为空。因此必须走 **adb over WiFi(无线调试)**,USB 方式在此环境不可用。 + +### 无线调试流程(Android 11+) + +```bash +# 1. 手机:设置 → 开发者选项 → 无线调试 → 打开 +# 2. 手机点「使用配对码配对设备」,拿到 6 位配对码 + 配对 IP:端口 +# 3. 云桌面执行配对(配对码时效短,拿到后立刻执行) +echo "6位配对码" | adb pair 手机IP:配对端口 + +# 4. 配对成功后,看手机「无线调试」主页面的「IP 地址和端口」(调试端口,与配对端口不同) +adb connect 手机IP:调试端口 + +# 5. 验证(显示 device 即成功) +adb devices -l +``` + +- 配对码有 30 秒~几分钟时效,超时需手机重新点「使用配对码配对设备」 +- 配对端口与调试端口是**两个不同端口**,别混用 +- 若 `adb pair` 报 `protocol fault`,通常是配对码过期,重新获取 + +## 前置:环境检测(每次先跑) + +```bash +adb version # adb 是否可用 +adb devices -l # 真机是否连接(显示 device 才算连上) +adb shell wm size # 屏幕分辨率(用于坐标换算) +``` + +- `adb` 不在 PATH:见「环境准备」装 platform-tools +- `adb devices` 空:无线调试未连上,回到「无线调试流程」重新配对/连接 + +## 环境准备(首次需要) + +### 1. 安装 adb(Windows,curl 一键) + +```bash +curl -L -o /d/Work/platform-tools.zip https://dl.google.com/android/repository/platform-tools-latest-windows.zip +unzip -q -o /d/Work/platform-tools.zip -d /d/Work/android-sdk/ +/d/Work/android-sdk/platform-tools/adb.exe version # 验证 +``` + +- 或手动下载 [Android SDK Platform-Tools](https://developer.android.com/tools/releases/platform-tools) 解压 +- 或安装 Android Studio(自带 adb) + +> 本机已装:`D:\android-sdk\platform-tools\adb.exe`,已加入用户 PATH(注册表已配,但云桌面需**注销重登**才在 cmd 生效;脚本执行时用 `export PATH="$PATH:/d/Work/android-sdk/platform-tools"` 临时生效即可) + +### 2. 手机开启无线调试 + +1. 「设置 → 关于手机 → 版本号」连点 7 次,开启「开发者选项」 +2. 「设置 → 开发者选项」打开「无线调试」(需 Android 11+) +3. 手机连 WiFi(与云桌面同一网络) + +### 3. 确认目标 APP 包名 + +```bash +adb shell pm list packages | grep -iE "health|yixiong|healthy|jk" +``` + +本机实际包名:`com.sw.healthyclients`(健康客户端)、`com.sw.healthexpertclient.dev`(健康专家客户端 dev) + +## 截图流程 + +### Step 1:启动 APP + +```bash +adb shell monkey -p 包名 1 # 无需知道 Activity,直接拉起 +sleep 3 # 等待加载 +``` + +### Step 2:截图 + 读界面文字验证(每个页面都做) + +```bash +# 截图 +adb exec-out screencap -p > 页面名.png + +# 读界面文字,确认截到的是哪个页面(用 adb shell cat 直接读,避免 pull 的路径坑) +adb shell "uiautomator dump /sdcard/ui.xml" +adb shell cat /sdcard/ui.xml | grep -oE 'text="[^"]+"' | grep -v 'text=""' +``` + +> **必须读文字验证画面**:截图前先用 uiautomator 读文字,确认当前页面/弹窗是目标,再截图,避免截错页面。 + +### Step 3:模拟操作 + +```bash +adb shell input tap 540 1200 # 点击坐标 +adb shell input swipe 540 1600 540 600 300 # 上滑 +adb shell input text "admin" # 输入英文/数字(中文需 adbkeyboard 或 broadcast) +adb shell input keyevent 4 # 返回 +``` + +### Step 4:元素定位(确定点击坐标) + +```bash +adb shell "uiautomator dump /sdcard/ui.xml" +adb shell cat /sdcard/ui.xml | grep -oE 'text="[^"]+"[^>]*bounds="\[[0-9]+,[0-9]+\]\[[0-9]+,[0-9]+\]"' +# 从 bounds="[x1,y1][x2,y2]" 取中心点:tap_x=(x1+x2)/2, tap_y=(y1+y2)/2 +``` + +### Step 5:交给操作手册 skill + +截图完成后,交给 `doc-operation-manual` skill 组织成操作手册。 + +## 输出物 + +- `交付文档/{子系统名}/操作手册/screenshots/*.png`(APP 实际页面截图) + +## 注意事项 + +- **git bash 路径坑**:`/sdcard/ui.xml` 会被 MSYS 转义成 Windows 路径,用 `adb shell "uiautomator dump /sdcard/ui.xml"`(加引号)+ `export MSYS_NO_PATHCONV=1` 规避 +- **读文字验证画面**:截图前务必用 uiautomator 读 text 确认页面,避免截错 +- **配对码时效短**:无线调试配对码几十秒就失效,拿到立刻配对 +- **截图命名用中文页面名**,与操作手册章节一一对应 +- **点击坐标依分辨率换算**:先 `adb shell wm size` 确认(本机 1264×2800) +- **中文输入受限**:`adb shell input text` 只支持 ASCII,中文需 adbkeyboard 或 `am broadcast` +- 登录页等需要验证码/密码的场景,输入密码可自动化,验证码需人工识别 +- 若 APP 未适配真机(自定义 ROM 兼容问题),先与开发确认 diff --git a/.claude/skills/doc-brochure/SKILL.md b/.claude/skills/doc-brochure/SKILL.md index 0b88249..b20cb17 100644 --- a/.claude/skills/doc-brochure/SKILL.md +++ b/.claude/skills/doc-brochure/SKILL.md @@ -1,6 +1,6 @@ --- name: doc-brochure -description: 生成指定子系统的《产品宣传册》(HTML 静态页,A4 分页排版,可直接转 PDF)。当用户要求「生成 XX 系统的宣传册/产品手册」时使用。 +description: 生成指定子系统的《产品宣传册》(横版 A4,HTML 生成后自动转 PDF 交付)。当用户要求「生成 XX 系统的宣传册/产品手册」时使用。 --- # Skill:生成产品宣传册 @@ -37,6 +37,7 @@ description: 生成指定子系统的《产品宣传册》(HTML 静态页,A4 - **核心功能**:选 4~6 个最有代表性的功能模块,每个配 1 个 emoji 图标 + 简介 + 2 个亮点 - **特色优势**:6 条优势(01~06 编号卡片) - **终端覆盖**:列出该子系统覆盖的终端(每个配 emoji + 一句说明) +- **界面展示**:放 5 张 APP 竖版截图(手机边框 130×282)+ 3 张 Web 端截图(浏览器边框 290×181),分两行(上方 5 张 APP、下方 3 张 Web),从各端 `doc/screenshots/` 选核心页面(长截图用 object-fit 顶部裁剪) ### Step 3:填充 HTML 模板 @@ -45,18 +46,21 @@ description: 生成指定子系统的《产品宣传册》(HTML 静态页,A4 3. 保持内联 CSS 与设计令牌不变,仅替换文字与 emoji 4. 确保所有 `{...}` 占位符都被替换,不留占位符残留 -### Step 4:写入输出文件 +### Step 4:写入 HTML 源文件并检查 -输出到 `交付文档/{子系统名}/{子系统名}产品宣传册.html`。 +1. 输出 HTML 到 `交付文档/{子系统名}/{子系统名}产品宣传册.html` +2. 用浏览器打开检查:内容、截图是否加载、分页、无 `{占位符}` 残留 -### Step 5:转 PDF 检查(可选) +### Step 5:生成 PDF(必做) -- 宣传册模板已内置 `@page A4 landscape`(横版)与 `.page` 分页 -- 可用 headless 浏览器打印转 PDF,或直接交付 HTML(甲方以 HTML 浏览 / 转 PDF 均可) +1. 检查 HTML 无误后,运行 `node _templates/docs/pdf-export.js ` 转出同名 PDF +2. PDF 为最终交付物,HTML 作为源文件保留 +3. 转 PDF 参数已内置:横版 A4(landscape)+ 保留背景色(printBackground) ## 输出物 -- `交付文档/{子系统名}/{子系统名}产品宣传册.html` +- `交付文档/{子系统名}/{子系统名}产品宣传册.pdf`(交付物) +- `交付文档/{子系统名}/{子系统名}产品宣传册.html`(源文件) ## 注意事项 @@ -65,4 +69,4 @@ description: 生成指定子系统的《产品宣传册》(HTML 静态页,A4 - **配色统一**:沿用模板品牌蓝 `#1890FF` 设计令牌,不另造配色 - **内容通用**:宣传册必须通用,不得体现客户信息(如「油田」、客户公司名、委托方名称等) - emoji 图标用于视觉占位,正式交付如需替换为专业图标,告知用户 -- 文档名称「{子系统名}产品宣传册.html」为中文,符合甲方要求 +- 文档名称「{子系统名}产品宣传册.html / .pdf」为中文,符合甲方要求 diff --git a/.claude/skills/doc-operation-manual/SKILL.md b/.claude/skills/doc-operation-manual/SKILL.md index d75d9a6..c0605a5 100644 --- a/.claude/skills/doc-operation-manual/SKILL.md +++ b/.claude/skills/doc-operation-manual/SKILL.md @@ -1,6 +1,6 @@ --- name: doc-operation-manual -description: 生成指定子系统、指定终端的《操作手册》。当用户要求「生成 XX 系统 XX 端操作手册」「编写 XX 端使用手册/操作说明」时使用。操作手册按「子系统 + 终端」分册。 +description: 生成指定子系统、指定终端的《操作手册》。当用户要求「生成 XX 系统 XX 端操作手册」「编写 XX 端使用手册/操作说明」时使用。操作手册按「子系统 + 终端」分册,截图必须用实际系统截图(不用原型图)。 --- # Skill:生成操作手册 @@ -12,44 +12,75 @@ description: 生成指定子系统、指定终端的《操作手册》。当用 ## 输入参数 -- **子系统名**(必须,中文):如 `体重管理系统` -- **终端名**(必须,中文):如 `Web管理端` / `员工端APP` / `体检医生工作台` / `医疗点PAD` +- **子系统名**(必须,中文):如 `健康监测系统` +- **终端名**(必须,中文):如 `Web管理端` / `员工端APP` / `医疗点PAD` - **适用对象**(可选):用户角色,缺省按终端推断 +## 核心原则:截图必须用实际系统截图 + +**操作手册严禁用原型图**。原型和实际交付页面有差异,必须用测试环境真实页面截图。截图来源按终端区分: + +| 终端 | 截图方式 | 对应 skill | +|------|---------|-----------| +| Web 管理端 | 浏览器自动化截图 | `web-capture` | +| 员工端 APP(原生 Android/Flutter) | 真机 adb 截图 | `android-capture`(待编写) | +| 医疗点 PAD(横屏 Web) | 浏览器横屏视口截图 | `web-capture` | +| 微信小程序 | 浏览器模拟手机视口(若为 H5) | `web-capture` | + ## 模板与素材 - 模板:`_templates/docs/operation-manual-template.md` - 公共章节素材:`_templates/docs/common-sections-template.md` -- 素材:该终端 `doc/PRD.md` 的「逐页说明」(功能说明 + 交互说明)与截图 +- **文字素材**:该终端 `doc/PRD.md` 的「逐页说明」(功能说明 + 交互说明 + 业务规则),用于提炼操作步骤与注意事项 +- **截图素材**:由截图 skill(`web-capture` / `android-capture`)生成的实际系统截图,存放在 `交付文档/{子系统名}/操作手册/screenshots/` ## 执行流程 -### Step 1:定位目标终端目录 +### Step 0:先截图(前置,必须) + +1. **Web 管理端**:调用 `web-capture` skill,登录测试环境 → 导航到目标模块 → 逐页面截图 +2. **员工端 APP**:调用 `android-capture` skill,真机 adb 逐页面截图 +3. 截图存放 `交付文档/{子系统名}/操作手册/screenshots/`,命名用中文页面名 +4. 若截图 skill 尚未就绪或模块未上线,**暂停并告知用户**,不退回用原型图 + +### Step 1:定位模块文字素材 1. 读根目录 `index.html`,grep `SITE_MAP` 中 `name:'{子系统名}'` 的条目 -2. 根据「终端名」匹配所属端(SITE_MAP 顶层 `id`/`label`,如「Web管理端」→ `id:'web-admin'`),定位到唯一模块目录,如 `web-admin/weight-management` -3. 读该终端 `prd` 字段指向的 `doc/PRD.md`(缺失则 Glob 补查),获取页面清单与逐页说明 +2. 根据「终端名」匹配所属端,定位模块目录与 `doc/PRD.md` +3. 读 PRD 的「模块概述」「页面清单」「逐页说明」,获取**菜单结构 + 功能说明 + 交互说明 + 业务规则**(用于文字,不用其截图) ### Step 2:整理运行环境与适用对象 - 运行环境引用 `项目背景.md` 2.x 视觉规范对应终端尺寸: - - Web 管理端:浏览器,最小宽度 1280px,侧边栏 240px + - Web 管理端:浏览器,最小宽度 1280px - 员工端 APP:390×844(iPhone 14) - 医疗点 PAD:1280×800 横屏 - 微信小程序:375×812 -- 适用对象按终端推断(平台管理员/能源员工/体检医生/医疗点医护人员等) +- 适用对象按终端推断(平台管理员/能源员工/医疗点医护人员等) -### Step 3:将 PRD 改写为操作手册 +### Step 3:按业务线路组织操作手册(核心) -按 `operation-manual-template.md` 生成,核心是把 PRD 的「交互说明」从**设计视角改写为「用户操作视角」的分步操作**: +**先梳理业务线路**(不是按页面/菜单顺序罗列): + +1. 从 PRD 的「模块概述」「页面导航结构」提炼**业务主线**——即用户完成某个业务目标的完整操作流程 +2. 每条业务线路对应手册一个章节(通常对应一个一级菜单/业务域,但不是简单按菜单排) +3. 示例(健康监测):① 档案查询 → ② 健康现状查看 → ③ 健康排查 → ④ 穿戴监测 → ⑤ 环境监测 → ⑥ 异常处理 + +**选择性覆盖原则**(与 PRD 前后呼应,但绝不完全一样): + +- 只覆盖**核心操作**(高频、关键业务操作),不罗列所有小交互 +- 只收录**主要弹窗**和**核心二级页面**(详情页等),不列所有弹窗 +- **页面状态选择性说明**:只说明用户常见的关键状态,不穷举所有状态(如只讲"进行中"和"已结束",不讲所有中间态) + +按 `operation-manual-template.md` 生成: - **1. 概述**:系统简介 + 适用对象 + 运行环境 + 登录说明(步骤化) -- **2. 界面总览**:布局说明 + 首页/工作台截图 + 导航结构 -- **3. 功能操作指南**:按「一级菜单/业务线」分组,每个功能点写: - - **操作步骤**(编号列表,从「进入某页面」到「完成某操作」) - - **界面截图**(沿用 PRD 截图) - - **注意事项**(从 PRD 业务规则提炼用户须知的要点) -- **4. 常见问题与故障处理**:提炼常见操作疑问与处理方法(表格) +- **2. 界面总览**:布局说明 + 首页/工作台截图 + 导航结构(含业务线路导读) +- **3. 功能操作指南**:按业务线路分组,每条线路写核心操作: + - **操作步骤**(编号列表,从「进入某页面」到「完成核心操作」) + - **界面截图**(核心页面 + 主要弹窗,引用实际系统截图) + - **注意事项**(关键业务规则,用户须知) +- **4. 常见问题与故障处理**:核心操作的高频问题(表格) - **5. 附录**:术语表 + 快捷操作 ### Step 4:并入公共操作章节 @@ -58,8 +89,9 @@ description: 生成指定子系统、指定终端的《操作手册》。当用 ### Step 5:截图引用 -- 沿用该终端 `doc/screenshots/` 现有截图 -- 路径从 `交付文档/{子系统名}/操作手册/` 指回:`../../../{端目录}/doc/screenshots/{页面名}.jpg` +- 引用 `交付文档/{子系统名}/操作手册/screenshots/` 下的实际系统截图 +- 路径相对操作手册文件:`screenshots/{页面名}.png` +- 若某页面截图缺失,标注「待补」,不退回用原型图 ### Step 6:写入输出文件 @@ -68,10 +100,12 @@ description: 生成指定子系统、指定终端的《操作手册》。当用 ## 输出物 - `交付文档/{子系统名}/操作手册/{子系统名}{终端名}操作手册.md` +- `交付文档/{子系统名}/操作手册/screenshots/*.png`(实际系统截图) ## 注意事项 -- 操作手册面向**终端用户**,语言要通俗、步骤化,避免出现实现细节(如字段名、接口名) -- 截图沿用现有,不重新截图 +- **截图必须实际系统截图**,严禁原型图;模块未上线或截图 skill 未就绪时,暂停并告知用户 +- **与 PRD 前后呼应**:手册章节对应 PRD 功能模块,但只覆盖核心操作、主要弹窗、核心二级页面,不逐页罗列所有弹窗/小交互/页面状态 +- 操作手册面向**终端用户**,语言通俗、步骤化,避免实现细节(字段名、接口名) - 每个子系统的每个终端单独一份手册,公共操作(登录/消息等)在各手册中重复体现(甲方要求) - 文档全部使用简体中文 diff --git a/.claude/skills/web-capture/SKILL.md b/.claude/skills/web-capture/SKILL.md new file mode 100644 index 0000000..0fe4d12 --- /dev/null +++ b/.claude/skills/web-capture/SKILL.md @@ -0,0 +1,92 @@ +--- +name: web-capture +description: 截取 Web 管理端测试环境的实际系统页面截图(用于操作手册)。当用户要求「截取 XX 模块的实际页面」「生成 XX 模块操作手册截图」「登录测试环境截图」时使用。 +--- + +# Skill:Web 管理端实际系统截图 + +## 触发方式 + +用户说「截取 XX 模块的实际页面截图」「登录测试环境截图」「生成 XX 模块操作手册」时触发。 + +## 输入参数 + +- **测试环境地址**(必须):如 `https://dev.yixiong-tech.com:8086/login` +- **账号 / 密码**(必须):如 `admin` / `Aa987654321!` +- **目标模块**(必须,中文):如 `健康监测`、`营养管理` + +## 关键约束(本系统特有,务必遵守) + +1. **验证码需人工识别**:登录页有 4 位图片验证码,Claude 无法自动识别图片。流程是——截图验证码存成文件 → 请用户看图报出验证码 → 填入登录。 +2. **微前端子应用**:各业务模块是 Qiankun 子应用,URL 为 `/subXxx/...`,不是简单的一级路径。 +3. **纯 JS 路由,无 ``**:页面用 Vue Router,菜单是 Ant Design 组件,**不能用 `navigate_page` 直接跳菜单**,也不能找 ``。 +4. **popover 菜单项 click 不跳转**:首页模块卡片 hover 弹出的 `ant-popover` 菜单项,直接 click 不生效。**可靠导航方式是 `router.push()`**(见下)。 + +## 模块 → 子应用 URL 映射 + +| 模块名 | 子应用路由前缀 | +|--------|--------------| +| 系统管理 | `/subSystemManagement/` | +| 健康监测 | `/subHealthMonitoring/` | +| 健康评估 | `/subHealthAssessment/` | +| 专家咨询 | `/subHealthExpertConsult/` | +| 健康助手 | `/subHealthHelper/` | +| 应急就医 | `/subEmergencyMedical/` | +| 营养管理 | `/subNutritionManagement/` | +| 体重管理 | `/subWeightManagement/` | + +## 执行流程 + +### Step 1:打开登录页并截图验证码 + +1. 用 chrome-devtools `new_page` 打开测试环境登录页 +2. `fill_form` 填账号、密码 +3. `take_screenshot` 截图(含验证码),保存到临时文件(如 `_login_captcha.png`) +4. **请用户看图报出 4 位验证码** + +### Step 2:登录 + +1. 用户报验证码后,`fill` 验证码输入框 + `click` 登录按钮 +2. 用 `evaluate_script` 检查 `location.href` 是否从 `/login` 跳到 `/home` +3. 若失败(验证码错误/过期),重新截图验证码请用户再报 + +### Step 3:导航到目标模块 + +用 `evaluate_script` 调用 Vue Router 的 `push`: + +```js +() => { + const router = document.querySelector('#app').__vue_app__.config.globalProperties.$router; + router.push('/subHealthMonitoring/archiveModule/personalEmergencyData'); + return '已导航'; +} +``` + +- 子应用内部具体路径需先进入模块后,从浏览器地址栏读取实际 URL +- 导航后页面跳转到子应用,`location.href` 会变成 `/subXxx/...` + +### Step 4:摸清模块菜单结构 + +1. `take_snapshot` 获取侧边栏菜单:一级菜单(`menuitem`)+ 二级菜单 +2. 记录菜单层级,规划截图清单(每个叶子页面截一张) + +### Step 5:逐页面截图 + +1. 对每个叶子页面:`router.push` 到对应 URL(或点击侧边栏菜单项) +2. `take_screenshot` 保存到 `交付文档/{子系统名}/操作手册/screenshots/{页面名}.png` +3. 截图命名用中文页面名(如 `个人监测数据.png`),与操作手册章节对应 + +### Step 6:交给操作手册 skill + +截图完成后,交给 `doc-operation-manual` skill,用真实截图组织成操作手册。 + +## 输出物 + +- `交付文档/{子系统名}/操作手册/screenshots/*.png`(实际系统截图) + +## 注意事项 + +- **验证码是每次登录的障碍**:优先建议让开发在测试环境关闭验证码;未关闭则每次登录请用户人工报验证码 +- **模块可能未上线**:首页模块卡片标「开发中,敬请期待」的模块不可截图,先和用户确认目标模块是否可用 +- **截图以实际系统为准**:操作手册严禁用原型图,必须是测试环境真实页面 +- 截图文件名用中文页面名,与操作手册章节一一对应 diff --git a/_templates/docs/brochure-template.html b/_templates/docs/brochure-template.html index efe4160..b7d0147 100644 --- a/_templates/docs/brochure-template.html +++ b/_templates/docs/brochure-template.html @@ -54,12 +54,12 @@ /* ===== 封面 ===== */ .cover{ background:linear-gradient(135deg,#0a2540 0%,#0a5cb8 55%,#1890FF 100%);color:#fff; } .cover .inner{ justify-content:space-between;position:relative; } - .cover__top{ display:flex;justify-content:space-between;align-items:center;font-size:13px;opacity:.92; } + .cover__top{ display:flex;justify-content:space-between;align-items:center;font-size:14px;color:rgba(255,255,255,.96); } .cover__logo{ font-weight:800;font-size:16px;letter-spacing:1px;display:flex;align-items:center;gap:8px; } .cover__logo .dot{ width:10px;height:10px;border-radius:50%;background:linear-gradient(135deg,#40c9ff,#1890FF); } .cover__mid{ display:flex;align-items:center;justify-content:space-between;gap:40px; } .cover__title{ font-size:44px;font-weight:900;line-height:1.2;margin-bottom:14px;letter-spacing:2px; } - .cover__slogan{ font-size:16px;opacity:.92;line-height:1.9; } + .cover__slogan{ font-size:16px;color:rgba(255,255,255,.96);line-height:1.9; } .cover__tags{ margin-top:22px;display:flex;gap:10px;flex-wrap:wrap; } .cover__tag{ padding:7px 16px;border:1px solid rgba(255,255,255,.5);border-radius:20px;font-size:13px;background:rgba(255,255,255,.08); } .cover__deco{ width:280px;height:280px;position:relative;flex-shrink:0; } @@ -67,7 +67,7 @@ .r1{ width:280px;height:280px;border:2px solid rgba(255,255,255,.18); } .r2{ width:210px;height:210px;border:2px dashed rgba(255,255,255,.22);left:35px;top:35px; } .r3{ width:140px;height:140px;background:radial-gradient(circle,rgba(255,255,255,.28),transparent 70%);left:70px;top:70px; } - .cover__bottom{ display:flex;justify-content:space-between;font-size:12px;opacity:.82; } + .cover__bottom{ display:flex;justify-content:space-between;font-size:14px;color:rgba(255,255,255,.96); } /* ===== 概述 ===== */ .stat-row{ display:grid;grid-template-columns:repeat(4,1fr);gap:14px;margin:18px 0; } @@ -100,11 +100,21 @@ .term span{ font-size:11.5px;color:var(--ink-2);line-height:1.6; } /* ===== 封底 ===== */ + .shots{ display:flex;flex-direction:column;gap:20px;flex:1;justify-content:center; } + .shots__row{ display:flex;gap:26px;justify-content:center;align-items:flex-end; } + .shot{ display:flex;flex-direction:column;align-items:center;gap:8px; } + .shot__frame{ overflow:hidden;box-shadow:0 6px 18px rgba(15,60,120,.14);background:#f5f7fa; } + .shot__frame--phone{ border:4px solid #2d3748;border-radius:16px; } + .shot__frame--phone img{ width:130px;height:282px;object-fit:cover;object-position:top;display:block; } + .shot__frame--web{ border:1px solid #d9e1ec;border-radius:10px; } + .shot__frame--web img{ width:290px;height:181px;object-fit:cover;object-position:top;display:block; } + .shot__label{ font-size:11px;color:var(--ink-2); } + .back{ background:linear-gradient(135deg,#0a2540,#0a5cb8);color:#fff; } .back .inner{ align-items:center;justify-content:center;text-align:center; } .back__title{ font-size:32px;font-weight:900;margin-bottom:12px; } - .back__line{ font-size:14px;opacity:.9;line-height:2; } - .back__foot{ margin-top:30px;font-size:12px;opacity:.7; } + .back__line{ font-size:15px;color:rgba(255,255,255,.96);line-height:2; } + .back__foot{ margin-top:30px;font-size:14px;color:rgba(255,255,255,.92); } @@ -184,7 +194,30 @@ - + +
+
+
界面展示

产品界面预览

+
+
+
+
{页面名}
+ {页面名} +
+ +
+
+
+
{页面名}
+ {页面名} +
+ +
+
+
+
+ +
{子系统名}
diff --git a/_templates/docs/pdf-export.js b/_templates/docs/pdf-export.js new file mode 100644 index 0000000..bce99fb --- /dev/null +++ b/_templates/docs/pdf-export.js @@ -0,0 +1,43 @@ +#!/usr/bin/env node +/** + * HTML 宣传册转 PDF(横版 A4,保留背景) + * 用法:node pdf-export.js + * 输出:与 HTML 同目录同名 .pdf + * 依赖:playwright(chromium),无则用系统 Edge 兜底 + */ +const { chromium } = require('playwright'); +const { pathToFileURL } = require('url'); +const path = require('path'); + +const htmlFile = process.argv[2]; +if (!htmlFile) { + console.error('用法:node pdf-export.js '); + process.exit(1); +} + +(async () => { + const abs = path.resolve(htmlFile); + const url = pathToFileURL(abs).href; + const pdfFile = process.argv[3] || abs.replace(/\.html$/i, '.pdf'); + + let browser; + try { + browser = await chromium.launch(); + } catch (e) { + // playwright 自带 chromium 不可用时,退回系统 Edge + browser = await chromium.launch({ channel: 'msedge' }); + } + + const page = await browser.newPage(); + await page.goto(url, { waitUntil: 'networkidle' }); + await page.pdf({ + path: pdfFile, + format: 'A4', + landscape: true, + printBackground: true, + margin: { top: 0, bottom: 0, left: 0, right: 0 }, + displayHeaderFooter: false, + }); + await browser.close(); + console.log('PDF 已生成:' + pdfFile); +})();