feat: 体重管理员工端/管理端全量弹窗截图重拍与 PRD 弹窗文档补全
员工端: - 重拍全部弹窗截图(2x 超采样高清,统一 390px 宽),补齐蓝牙秤/饮食/运动/目标/记录等弹窗 - 修复页面缺陷:排行趋势箭头渲染、goal-detail 签署跳转、promise-signing 完成跳转、basic-info 返回、diet-questionnaire 提交跳转 - diet-advice-v2 新增慢病注意提醒条 - PRD 逐页核对修订并补全弹窗截图引用 管理端: - screenshot.js MODALS 从 6 扩展至 38,新增设备独立弹窗页,弹窗非空校验增强 - PRD 补全报名/行动记录/食谱/历史活动/三测记录/设备管理/公共统计等弹窗文档与截图 - archive-unit-report.html 补充
@@ -58,6 +58,9 @@ thead th{background:#FAFAFA;padding:10px 12px;text-align:center;font-size:var(--
|
||||
tbody td{padding:12px 12px;text-align:center;font-size:var(--font-sm);color:var(--text-primary);border-bottom:1px solid var(--border);white-space:nowrap;}
|
||||
tbody tr:last-child td{border-bottom:none;}
|
||||
tbody tr:hover{background:#F8FBFF;}
|
||||
/* 统计行 */
|
||||
tbody tr.stat-row{background:#F0F5FF;font-weight:500;}
|
||||
tbody tr.stat-row:hover{background:#E6F0FF;}
|
||||
/* 标签 */
|
||||
.tag{display:inline-flex;align-items:center;padding:2px 8px;border-radius:10px;font-size:var(--font-xs);}
|
||||
.tag-green{background:#F6FFED;color:var(--success);}
|
||||
@@ -99,6 +102,11 @@ tbody tr:hover{background:#F8FBFF;}
|
||||
#tab-0 tbody tr:hover td.sticky-r,
|
||||
#tab-1 tbody tr:hover td.sticky-r{background:#F8FBFF;}
|
||||
.sticky-l3,.sticky-l4,.sticky-r{box-shadow:-2px 0 4px rgba(0,0,0,0.06);}
|
||||
/* 统计行固定列背景 */
|
||||
#tab-0 tbody tr.stat-row td[class*=sticky],
|
||||
#tab-1 tbody tr.stat-row td[class*=sticky]{background:#F0F5FF;}
|
||||
#tab-0 tbody tr.stat-row:hover td[class*=sticky],
|
||||
#tab-1 tbody tr.stat-row:hover td[class*=sticky]{background:#E6F0FF;}
|
||||
/* 组织机构二级联动 */
|
||||
.org-select-wrap{position:relative;display:inline-block;}
|
||||
.org-select-trigger{height:36px;min-width:160px;border:1px solid var(--border);border-radius:var(--radius-sm);padding:0 28px 0 12px;font-size:var(--font-sm);color:var(--text-primary);background:#fff url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='10' height='6'%3E%3Cpath d='M0 0l5 6 5-6z' fill='%23999'/%3E%3C/svg%3E") no-repeat right 10px center;cursor:pointer;display:flex;align-items:center;user-select:none;white-space:nowrap;}
|
||||
@@ -228,6 +236,9 @@ tbody tr:hover{background:#F8FBFF;}
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr class="stat-row">
|
||||
<td class="sticky-l1">统计行</td><td class="sticky-l2">3</td><td class="sticky-l3">4204</td><td>31</td><td>3.9</td><td>4.9</td><td>2.3</td><td>1.7</td><td>4.7</td><td>5350</td><td>3523</td><td>1585</td><td>80</td><td>80</td><td class="sticky-r"></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="sticky-l1">1</td><td class="sticky-l2">第四采油厂</td><td class="sticky-l3">1234</td><td>8</td><td>2.3</td><td>5</td><td>2</td><td>2</td><td>5</td><td>5410</td><td>3541</td><td>1570</td><td>86</td><td>86</td><td class="sticky-r"><a class="link" href="archive-unit-report-detail.html?unit=第四采油厂&total=1234&activities=8&weightLoss=2.3&lossRate=5&bmi=2&fatRate=2&waist=5&intake=5410&consume=3541&gap=1570&score=86&actionScore=86">详细数据</a></td>
|
||||
</tr>
|
||||
@@ -278,6 +289,9 @@ tbody tr:hover{background:#F8FBFF;}
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr class="stat-row">
|
||||
<td class="sticky-l1">统计行</td><td class="sticky-l2">2</td><td class="sticky-l3">3</td><td class="sticky-l4">3448</td><td>9</td><td>3.4</td><td>4.1</td><td>1.8</td><td>1.8</td><td>4.1</td><td>5232</td><td>3459</td><td>1559</td><td>80</td><td>78</td><td class="sticky-r"></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="sticky-l1">1</td><td class="sticky-l2">第四采油厂</td><td class="sticky-l3">作业一区</td><td class="sticky-l4">1234</td><td>3</td><td>3.2</td><td>4</td><td>1.8</td><td>2.0</td><td>4</td><td>5300</td><td>3500</td><td>1600</td><td>84</td><td>82</td><td class="sticky-r"><a class="link" href="archive-unit-report-detail.html?unit=第四采油厂&dept=作业一区&total=1234&activities=3&weightLoss=3.2&lossRate=4&bmi=1.8&fatRate=2.0&waist=4&intake=5300&consume=3500&gap=1600&score=84&actionScore=82">详细数据</a></td>
|
||||
</tr>
|
||||
|
||||
@@ -1,5 +1,23 @@
|
||||
# CHANGELOG | 体重管理 Web 管理端
|
||||
|
||||
## 2026-08-19 | 全模块弹窗截图补全 + PRD 弹窗文档完善
|
||||
|
||||
### 弹窗截图补全(screenshot.js)
|
||||
- **`doc/screenshot.js`**:MODALS 列表从 6 个扩展至 38 个,覆盖全部菜单组的内嵌弹窗;PAGES 列表新增 4 个新增设备独立弹窗页(scale/bluetooth/fat/sport-device-add.html)
|
||||
- 依赖前置状态的弹窗在 setup 中造数:员工选择弹窗预设组织机构、累计行动弹窗预设 selectedPlanId、单位详情/统计弹窗预设日期范围、食谱新增弹窗预填表单示例值、添加成员弹窗预选员工
|
||||
- 弹窗非空校验增强:表格数据行 / 信息卡(.ec-value)/ 详情值(.dialog-cell__val 等)/ 表单项填充四级判定,38 张弹窗截图全部通过非空校验
|
||||
- 用法:`node doc/screenshot.js`(全量)/ `modals`(仅弹窗)/ `<关键字>`(按文件名过滤)
|
||||
|
||||
### PRD 弹窗文档完善(PRD.md)
|
||||
- **报名管理**:承诺书预览弹窗、目标体重审核弹窗补截图
|
||||
- **过程管理·行动记录**:弹窗列表补"打卡记录弹窗"行(此前文档缺失),12 个弹窗全部配截图;交互说明补打卡次数入口
|
||||
- **过程管理·食堂食谱**:新增记录弹窗配截图
|
||||
- **历史活动**:人员范围弹窗 + 历史活动详情 5 个弹窗(饮食/运动/体重记录、承诺书、大病人员)配截图
|
||||
- **人员数据·三测记录**:补 3 张代表弹窗截图(其余与行动记录结构一致)
|
||||
- **设备管理**:4 个新增设备弹窗页配截图;蓝牙称成员管理弹窗/添加成员弹窗补文档(此前清单与说明均缺失),含清单行、导航树、操作列说明;体重秤/蓝牙称/体脂仪监测详情身体成分分析弹窗配截图(运动器械无弹窗,维持原说明)
|
||||
- **公共统计**:测量记录身体成分分析弹窗、单位详情弹窗(含按指标切换的明细表说明)、使用记录弹窗(含按设备类型切换的表头说明)配截图
|
||||
- 页面清单:行动记录/三测记录弹窗行更新为完整弹窗枚举;蓝牙称新增成员管理弹窗行
|
||||
|
||||
## 2026-06-18 | 异常事件页面补全
|
||||
|
||||
### 页面补全
|
||||
|
||||
@@ -7,7 +7,9 @@
|
||||
* - JPEG 质量 85%
|
||||
* - 目标体积 ≤ 80KB
|
||||
*
|
||||
* 用法: node doc/screenshot.js
|
||||
* 用法: node doc/screenshot.js # 全量页面 + 弹窗截图
|
||||
* node doc/screenshot.js modals # 仅弹窗截图
|
||||
* node doc/screenshot.js <关键字> # 仅文件名含关键字的页面
|
||||
*/
|
||||
const { chromium } = require('playwright');
|
||||
const path = require('path');
|
||||
@@ -16,7 +18,7 @@ const fs = require('fs');
|
||||
const MODULE_DIR = __dirname.replace(/\\doc$/, ''); // weight-management/
|
||||
const SCREENSHOTS_DIR = path.join(__dirname, 'screenshots');
|
||||
|
||||
// 主页面列表(排除内嵌弹窗文件,弹窗无需单独截图)
|
||||
// 主页面列表(弹窗见下方 MODALS 列表,通过 setup 打开后单独截图)
|
||||
const PAGES = [
|
||||
// ===== 档案 =====
|
||||
{ file: 'archive-personal-weight-data.html', desc: '个人体重数据列表' },
|
||||
@@ -25,9 +27,9 @@ const PAGES = [
|
||||
{ file: 'archive-unit-report-detail.html', desc: '单位报表详情' },
|
||||
{ file: 'archive-algorithm-model.html', desc: '算法模型(占位)' },
|
||||
|
||||
// ===== 计划管理 =====
|
||||
{ file: 'plan-list.html', desc: '计划列表' },
|
||||
{ file: 'plan-edit.html', desc: '新增/编辑计划' },
|
||||
// ===== 活动管理 =====
|
||||
{ file: 'plan-list.html', desc: '活动列表' },
|
||||
{ file: 'plan-edit.html', desc: '新增/编辑活动' },
|
||||
|
||||
// ===== 报名管理 =====
|
||||
{ file: 'registration-tracking.html', desc: '报名跟踪' },
|
||||
@@ -36,12 +38,22 @@ const PAGES = [
|
||||
// ===== 设备管理 =====
|
||||
{ file: 'scale-device.html', desc: '体重秤-设备管理' },
|
||||
{ file: 'scale-monitor.html', desc: '体重秤-设备监测' },
|
||||
{ file: 'scale-monitor-detail.html', desc: '体重秤-监测详情' },
|
||||
{ file: 'bluetooth-device.html', desc: '蓝牙称-设备管理' },
|
||||
{ file: 'bluetooth-monitor.html', desc: '蓝牙称-设备监测' },
|
||||
{ file: 'bluetooth-monitor-detail.html', desc: '蓝牙称-监测详情' },
|
||||
{ file: 'fat-device.html', desc: '体脂仪-设备管理' },
|
||||
{ file: 'fat-monitor.html', desc: '体脂仪-设备监测' },
|
||||
{ file: 'fat-monitor-detail.html', desc: '体脂仪-监测详情' },
|
||||
{ file: 'sport-device.html', desc: '运动器械-设备管理' },
|
||||
{ file: 'sport-monitor.html', desc: '运动器械-设备监测' },
|
||||
{ file: 'sport-monitor-detail.html', desc: '运动器械-监测详情' },
|
||||
|
||||
// ===== 新增设备(独立弹窗页) =====
|
||||
{ file: 'scale-device-add.html', desc: '体重秤-新增设备' },
|
||||
{ file: 'bluetooth-device-add.html', desc: '蓝牙称-新增设备' },
|
||||
{ file: 'fat-device-add.html', desc: '体脂仪-新增设备' },
|
||||
{ file: 'sport-device-add.html', desc: '运动器械-新增设备' },
|
||||
{ file: 'employee-data.html', desc: '员工监测数据' },
|
||||
{ file: 'employee-data-record.html', desc: '测量记录详情' },
|
||||
{ file: 'dept-stat.html', desc: '单位数据统计' },
|
||||
@@ -49,18 +61,20 @@ const PAGES = [
|
||||
|
||||
// ===== 过程管理 =====
|
||||
{ file: 'action-record.html', desc: '行动记录' },
|
||||
{ file: 'detail-data.html', desc: '详细数据' },
|
||||
{ file: 'weight-ranking.html', desc: '体重排名' },
|
||||
{ file: 'action-overview.html', desc: '活动总览' },
|
||||
{ file: 'action-overview-detail.html', desc: '活动详情' },
|
||||
{ file: 'action-overview-detail.html', desc: '赛况明细' },
|
||||
{ file: 'activity-statistics.html', desc: '活动统计' },
|
||||
{ file: 'recipe-record.html', desc: '食谱记录' },
|
||||
{ file: 'recipe-record.html', desc: '食堂食谱' },
|
||||
|
||||
// ===== 历史干预 =====
|
||||
{ file: 'history-plan-list.html', desc: '历史计划列表' },
|
||||
{ file: 'history-plan-detail.html', desc: '历史计划详情' },
|
||||
{ file: 'history-personnel-list.html', desc: '历史人员列表' },
|
||||
{ file: 'history-personnel-detail.html', desc: '历史人员详情' },
|
||||
// ===== 历史活动 =====
|
||||
{ file: 'history-plan-list.html', desc: '历史活动列表' },
|
||||
{ file: 'history-plan-detail.html', desc: '历史活动详情' },
|
||||
{ file: 'history-personnel-detail.html', desc: '人员详情' },
|
||||
|
||||
// ===== 人员数据 =====
|
||||
{ file: 'personnel-measure-record.html', desc: '三测记录' },
|
||||
{ file: 'personnel-overview-record.html', desc: '总览记录' },
|
||||
|
||||
// ===== 个人体重管理 =====
|
||||
{ file: 'personal-target-data.html', desc: '个人目标数据列表' },
|
||||
@@ -75,6 +89,290 @@ function getOutputName(file) {
|
||||
return file.replace('.html', '.jpg');
|
||||
}
|
||||
|
||||
// 弹窗截图列表:setup 在页面上下文执行,负责打开弹窗并填充数据;
|
||||
// 依赖前置状态/参数的弹窗(如 plan-edit 员工选择弹窗需先设置组织机构)在 setup 中造好数据,避免空弹窗
|
||||
const MODALS = [
|
||||
// ===== 活动管理 =====
|
||||
{
|
||||
file: 'plan-list.html',
|
||||
desc: '活动列表-人员筛选弹窗',
|
||||
output: 'plan-list-modal-person-filter.jpg',
|
||||
selector: '#personFilterModal .modal',
|
||||
setup: '(() => { openPersonFilterDialog(1); })()', // planId=1,12条mock人员自动渲染
|
||||
},
|
||||
{
|
||||
file: 'plan-list.html',
|
||||
desc: '活动列表-新增人员弹窗(搜索选择)',
|
||||
output: 'plan-list-modal-add-person.jpg',
|
||||
selector: '#addPersonDialog .dialog',
|
||||
setup: '(() => { openPersonFilterDialog(1); openAddPersonDialog(); searchPerson(); selectCandidate("CQ001250"); })()', // 选中候选人,填充信息卡
|
||||
},
|
||||
{
|
||||
file: 'plan-list.html',
|
||||
desc: '活动列表-审核人员弹窗',
|
||||
output: 'plan-list-modal-reviewer.jpg',
|
||||
selector: '#reviewerModal .modal',
|
||||
setup: '(() => { openReviewerDialog(1); })()', // 7条mock审核人员自动渲染
|
||||
},
|
||||
{
|
||||
file: 'plan-list.html',
|
||||
desc: '活动列表-新增审核人员弹窗(搜索选择)',
|
||||
output: 'plan-list-modal-add-reviewer.jpg',
|
||||
selector: '#addReviewerDialog .dialog',
|
||||
setup: '(() => { openReviewerDialog(1); openAddReviewerDialog(); searchReviewer(); selectReviewerCandidate("138-9210-8801"); })()',
|
||||
},
|
||||
{
|
||||
file: 'plan-edit.html',
|
||||
desc: '新增/编辑活动-员工选择弹窗',
|
||||
output: 'plan-edit-modal-emp-select.jpg',
|
||||
selector: '#empModal .modal-emp',
|
||||
setup: '(() => { document.getElementById("planOrg").value = "第四采油厂"; openEmpModal(); })()', // 需先设置组织机构(否则toast拦截),按单位过滤出6条员工
|
||||
},
|
||||
{
|
||||
file: 'plan-edit.html',
|
||||
desc: '新增/编辑活动-大病人员列表弹窗',
|
||||
output: 'plan-edit-modal-serious-list.jpg',
|
||||
selector: '#seriousModal .modal-emp',
|
||||
setup: '(() => { openSeriousModal(); })()', // 8条mock大病人员自动渲染
|
||||
},
|
||||
|
||||
// ===== 报名管理 =====
|
||||
{
|
||||
file: 'registration-tracking.html',
|
||||
desc: '报名跟踪-承诺书预览弹窗',
|
||||
output: 'registration-tracking-modal-promise.jpg',
|
||||
selector: '#promiseOverlay .promise-dialog',
|
||||
setup: '(() => { openPromise("张伟", "2026-03-05 10:23:41"); })()', // 传入姓名+签署时间,渲染承诺人/日期
|
||||
},
|
||||
{
|
||||
file: 'target-weight-review.html',
|
||||
desc: '目标体重审核-审核弹窗',
|
||||
output: 'target-weight-review-modal-audit.jpg',
|
||||
selector: '#auditOverlay .audit-dialog',
|
||||
setup: '(() => { openAudit(tabData[1][0].empId); })()', // 取"待审核"Tab第一条,自动渲染基本信息卡+指标对比表
|
||||
},
|
||||
|
||||
// ===== 过程管理:行动记录(12个弹窗) =====
|
||||
{
|
||||
file: 'action-record.html',
|
||||
desc: '行动记录-体重打卡记录弹窗',
|
||||
output: 'action-record-modal-checkin.jpg',
|
||||
selector: '#checkinOverlay .record-dialog',
|
||||
setup: '(() => { openCheckinDialog("CQ001234", "张伟", "第四采油厂", "健康管理部"); })()', // currentSubTab默认weight
|
||||
},
|
||||
{
|
||||
file: 'action-record.html',
|
||||
desc: '行动记录-饮食记录弹窗',
|
||||
output: 'action-record-modal-diet.jpg',
|
||||
selector: '#dietOverlay .record-dialog',
|
||||
setup: '(() => { openDietDialog("CQ001234", "张伟", "2026-06-10"); })()', // 基础信息卡+mock餐次记录
|
||||
},
|
||||
{
|
||||
file: 'action-record.html',
|
||||
desc: '行动记录-运动记录弹窗',
|
||||
output: 'action-record-modal-exercise.jpg',
|
||||
selector: '#exerciseOverlay .record-dialog',
|
||||
setup: '(() => { openExerciseDialog("CQ001234", "张伟", "2026-06-10"); })()',
|
||||
},
|
||||
{
|
||||
file: 'action-record.html',
|
||||
desc: '行动记录-体重记录弹窗',
|
||||
output: 'action-record-modal-weight.jpg',
|
||||
selector: '#weightOverlay .record-dialog',
|
||||
setup: '(() => { openWeightDialog("CQ001234", "张伟", "2026-06-10"); })()',
|
||||
},
|
||||
{
|
||||
file: 'action-record.html',
|
||||
desc: '行动记录-今日饮食未记录员工弹窗',
|
||||
output: 'action-record-modal-diet-unrecorded.jpg',
|
||||
selector: '#dietUnrecordedOverlay .record-dialog',
|
||||
setup: '(() => { openDietUnrecordedDialog(); })()', // 从dailyData过滤hasDiet=false
|
||||
},
|
||||
{
|
||||
file: 'action-record.html',
|
||||
desc: '行动记录-今日饮食已记录员工弹窗',
|
||||
output: 'action-record-modal-diet-recorded.jpg',
|
||||
selector: '#dietRecordedOverlay .record-dialog',
|
||||
setup: '(() => { openDietRecordedDialog(); })()',
|
||||
},
|
||||
{
|
||||
file: 'action-record.html',
|
||||
desc: '行动记录-今日运动未记录员工弹窗',
|
||||
output: 'action-record-modal-exercise-unrecorded.jpg',
|
||||
selector: '#exerciseUnrecordedOverlay .record-dialog',
|
||||
setup: '(() => { openExerciseUnrecordedDialog(); })()',
|
||||
},
|
||||
{
|
||||
file: 'action-record.html',
|
||||
desc: '行动记录-今日运动已记录员工弹窗',
|
||||
output: 'action-record-modal-exercise-recorded.jpg',
|
||||
selector: '#exerciseRecordedOverlay .record-dialog',
|
||||
setup: '(() => { openExerciseRecordedDialog(); })()',
|
||||
},
|
||||
{
|
||||
file: 'action-record.html',
|
||||
desc: '行动记录-今日体重未记录员工弹窗',
|
||||
output: 'action-record-modal-weight-unrecorded.jpg',
|
||||
selector: '#weightUnrecordedOverlay .record-dialog',
|
||||
setup: '(() => { openWeightUnrecordedDialog(); })()',
|
||||
},
|
||||
{
|
||||
file: 'action-record.html',
|
||||
desc: '行动记录-今日体重已记录员工弹窗',
|
||||
output: 'action-record-modal-weight-recorded.jpg',
|
||||
selector: '#weightRecordedOverlay .record-dialog',
|
||||
setup: '(() => { openWeightRecordedDialog(); })()',
|
||||
},
|
||||
{
|
||||
file: 'action-record.html',
|
||||
desc: '行动记录-活动期间累计行动员工弹窗',
|
||||
output: 'action-record-modal-cumulative.jpg',
|
||||
selector: '#cumulativeOverlay .record-dialog',
|
||||
setup: '(() => { selectedPlanId = plans[0].id; openCumulativeDialog("diet"); })()', // 需先选活动(否则toast拦截),plans[0]=第四采油厂活动
|
||||
},
|
||||
{
|
||||
file: 'action-record.html',
|
||||
desc: '行动记录-单位详情弹窗',
|
||||
output: 'action-record-modal-unit-detail.jpg',
|
||||
selector: '#unitDetailOverlay .record-dialog',
|
||||
setup: '(() => { document.getElementById("unitDateFrom").value = "2026-06-01"; document.getElementById("unitDateTo").value = "2026-06-30"; openUnitDetailDialog("第四采油厂", "total"); })()', // 造日期范围避免信息卡显示"-"
|
||||
},
|
||||
|
||||
// ===== 过程管理:食堂食谱 =====
|
||||
{
|
||||
file: 'recipe-record.html',
|
||||
desc: '食堂食谱-新增记录弹窗(已造示例数据)',
|
||||
output: 'recipe-record-modal-add.jpg',
|
||||
selector: '#addOverlay .record-dialog',
|
||||
setup: '(() => { openAddDialog(); document.getElementById("addUnit").value = "第四采油厂"; document.getElementById("addCanteen").value = "第一食堂"; document.getElementById("addMeal").value = "午餐"; var f = document.querySelector("#foodList .food-item .form-input"); if (f) f.value = "杂粮馒头"; })()', // 新增表单预填示例值
|
||||
},
|
||||
|
||||
// ===== 历史活动 =====
|
||||
{
|
||||
file: 'history-plan-list.html',
|
||||
desc: '历史活动列表-人员范围弹窗',
|
||||
output: 'history-plan-list-modal-staff.jpg',
|
||||
selector: '#staffModal .modal-dialog',
|
||||
setup: '(() => { showStaffScope("unit", historyPlans[0].planName); })()', // 按活动名生成shouldCount条人员数据
|
||||
},
|
||||
{
|
||||
file: 'history-plan-detail.html',
|
||||
desc: '历史活动详情-饮食记录弹窗',
|
||||
output: 'history-plan-detail-modal-diet.jpg',
|
||||
selector: '#dietOverlay .record-dialog',
|
||||
setup: '(() => { openDietDialog(actEmployees[0].empId, actEmployees[0].name, "2026-05-20"); })()',
|
||||
},
|
||||
{
|
||||
file: 'history-plan-detail.html',
|
||||
desc: '历史活动详情-运动记录弹窗',
|
||||
output: 'history-plan-detail-modal-exercise.jpg',
|
||||
selector: '#exerciseOverlay .record-dialog',
|
||||
setup: '(() => { openExerciseDialog(actEmployees[0].empId, actEmployees[0].name, "2026-05-20"); })()',
|
||||
},
|
||||
{
|
||||
file: 'history-plan-detail.html',
|
||||
desc: '历史活动详情-体重记录弹窗',
|
||||
output: 'history-plan-detail-modal-weight.jpg',
|
||||
selector: '#weightOverlay .record-dialog',
|
||||
setup: '(() => { openWeightDialog(actEmployees[0].empId, actEmployees[0].name, "2026-05-20"); })()',
|
||||
},
|
||||
{
|
||||
file: 'history-plan-detail.html',
|
||||
desc: '历史活动详情-承诺书弹窗',
|
||||
output: 'history-plan-detail-modal-promise.jpg',
|
||||
selector: '#promiseOverlay .promise-dialog',
|
||||
setup: '(() => { openPromise("张伟", "2026-04-12 09:30:15"); })()',
|
||||
},
|
||||
{
|
||||
file: 'history-plan-detail.html',
|
||||
desc: '历史活动详情-大病人员弹窗',
|
||||
output: 'history-plan-detail-modal-serious.jpg',
|
||||
selector: '#seriousModal .modal-emp',
|
||||
setup: '(() => { openSeriousModal(); })()',
|
||||
},
|
||||
|
||||
// ===== 人员数据:三测记录(结构同行动记录,取代表弹窗) =====
|
||||
{
|
||||
file: 'personnel-measure-record.html',
|
||||
desc: '三测记录-饮食记录弹窗',
|
||||
output: 'personnel-measure-record-modal-diet.jpg',
|
||||
selector: '#dietOverlay .record-dialog',
|
||||
setup: '(() => { openDietDialog("CQ001234", "张伟", "2026-06-10"); })()',
|
||||
},
|
||||
{
|
||||
file: 'personnel-measure-record.html',
|
||||
desc: '三测记录-今日饮食未记录员工弹窗',
|
||||
output: 'personnel-measure-record-modal-diet-unrecorded.jpg',
|
||||
selector: '#dietUnrecordedOverlay .record-dialog',
|
||||
setup: '(() => { openDietUnrecordedDialog(); })()',
|
||||
},
|
||||
{
|
||||
file: 'personnel-measure-record.html',
|
||||
desc: '三测记录-活动期间累计行动员工弹窗',
|
||||
output: 'personnel-measure-record-modal-cumulative.jpg',
|
||||
selector: '#cumulativeOverlay .record-dialog',
|
||||
setup: '(() => { selectedPlanId = plans[0].id; openCumulativeDialog("diet"); })()',
|
||||
},
|
||||
|
||||
// ===== 设备管理 =====
|
||||
{
|
||||
file: 'bluetooth-device.html',
|
||||
desc: '蓝牙称设备管理-成员管理弹窗',
|
||||
output: 'bluetooth-device-modal-member.jpg',
|
||||
selector: '#memberModal .modal',
|
||||
setup: '(() => { openMemberModal(0); })()', // 设备1的mock成员列表自动渲染
|
||||
},
|
||||
{
|
||||
file: 'bluetooth-device.html',
|
||||
desc: '蓝牙称设备管理-添加成员弹窗(搜索选择)',
|
||||
output: 'bluetooth-device-modal-add-member.jpg',
|
||||
selector: '#addMemberModal .modal',
|
||||
setup: '(() => { openMemberModal(0); openAddMemberModal(); document.getElementById("employeeSearch").value = "赵"; searchEmployee(); selectEmployee("CQ10001"); })()', // 搜索并选中员工,填充员工信息卡
|
||||
},
|
||||
{
|
||||
file: 'scale-monitor-detail.html',
|
||||
desc: '体重秤监测详情-身体成分分析弹窗',
|
||||
output: 'scale-monitor-detail-modal-detail.jpg',
|
||||
selector: '#dialogMask .dialog',
|
||||
setup: '(() => { document.querySelector(".tip-trigger").click(); })()', // 触发行内"查看",渲染data-tip JSON
|
||||
},
|
||||
{
|
||||
file: 'bluetooth-monitor-detail.html',
|
||||
desc: '蓝牙称监测详情-身体成分分析弹窗',
|
||||
output: 'bluetooth-monitor-detail-modal-detail.jpg',
|
||||
selector: '#dialogMask .dialog',
|
||||
setup: '(() => { document.querySelector(".tip-trigger").click(); })()',
|
||||
},
|
||||
{
|
||||
file: 'fat-monitor-detail.html',
|
||||
desc: '体脂仪监测详情-身体成分分析弹窗',
|
||||
output: 'fat-monitor-detail-modal-detail.jpg',
|
||||
selector: '#dialogMask .dialog',
|
||||
setup: '(() => { document.querySelector(".tip-trigger").click(); })()',
|
||||
},
|
||||
{
|
||||
file: 'employee-data-record.html',
|
||||
desc: '测量记录-身体成分分析弹窗',
|
||||
output: 'employee-data-record-modal-detail.jpg',
|
||||
selector: '#detailModal .modal',
|
||||
setup: '(() => { openModal(0); })()', // records[0]的测量详情分组渲染
|
||||
},
|
||||
{
|
||||
file: 'dept-stat.html',
|
||||
desc: '单位数据统计-单位详情弹窗',
|
||||
output: 'dept-stat-modal-unit-detail.jpg',
|
||||
selector: '#deptOverlay .record-dialog',
|
||||
setup: '(() => { document.getElementById("filterDateFrom").value = "2026-06-01"; document.getElementById("filterDateTo").value = "2026-06-30"; openDeptDetail("第四采油厂", "体重秤", "设备数量", 5, ""); })()', // 造日期范围+传入设备类型/指标,渲染设备明细表
|
||||
},
|
||||
{
|
||||
file: 'employee-stat.html',
|
||||
desc: '员工数据统计-使用记录弹窗',
|
||||
output: 'employee-stat-modal-usage.jpg',
|
||||
selector: '#usageOverlay .record-dialog',
|
||||
setup: '(() => { document.getElementById("filterDateFrom").value = "2026-06-01"; document.getElementById("filterDateTo").value = "2026-06-30"; openUsageDialog("张伟", "CQ001234", "第四采油厂", "健康管理部", "体重秤"); })()', // 造日期范围+员工/设备类型参数,渲染使用记录表
|
||||
},
|
||||
];
|
||||
|
||||
async function screenshotPage(browser, entry, index, total) {
|
||||
const context = await browser.newContext({
|
||||
viewport: { width: 1440, height: 900 },
|
||||
@@ -147,12 +445,83 @@ async function screenshotPage(browser, entry, index, total) {
|
||||
}
|
||||
}
|
||||
|
||||
// 弹窗截图:执行 setup 打开弹窗并填充数据后,对 selector 元素截图;
|
||||
// 截图前校验弹窗非空(表格有数据行 或 信息卡已填充),空数据弹窗在日志中告警
|
||||
async function captureModal(browser, entry, index, total) {
|
||||
const context = await browser.newContext({
|
||||
viewport: { width: 1440, height: 900 },
|
||||
deviceScaleFactor: 1,
|
||||
});
|
||||
const page = await context.newPage();
|
||||
const filePath = path.join(MODULE_DIR, entry.file);
|
||||
const fileUrl = `file:///${filePath.replace(/\\/g, '/')}`;
|
||||
|
||||
try {
|
||||
await page.goto(fileUrl, { waitUntil: 'networkidle', timeout: 15000 });
|
||||
await page.waitForTimeout(500);
|
||||
|
||||
// 注意:弹窗截图不注入隐藏弹窗层的样式
|
||||
await page.evaluate(entry.setup);
|
||||
await page.waitForTimeout(300);
|
||||
|
||||
const outPath = path.join(SCREENSHOTS_DIR, entry.output);
|
||||
const locator = page.locator(entry.selector).first();
|
||||
await locator.waitFor({ state: 'visible', timeout: 5000 });
|
||||
|
||||
// 非空校验:统计表格数据行 / 信息卡 / 详情值填充状态
|
||||
const audit = await locator.evaluate((el) => {
|
||||
const rows = el.querySelectorAll('tbody tr');
|
||||
const hasDataRows = Array.prototype.some.call(rows, (tr) => {
|
||||
const t = (tr.textContent || '').trim();
|
||||
return t && t.indexOf('暂无') === -1 && t.indexOf('未找到') === -1;
|
||||
});
|
||||
const cardValues = Array.prototype.map.call(el.querySelectorAll('.ec-value'), (v) => v.textContent.trim());
|
||||
const cardFilled = cardValues.length > 0 && cardValues.some((v) => v && v !== '-');
|
||||
const detailValues = Array.prototype.map.call(
|
||||
el.querySelectorAll('.dialog-cell__val, .detail-item__value, .record-info__value'),
|
||||
(v) => v.textContent.trim()
|
||||
);
|
||||
const detailFilled = detailValues.length > 0 && detailValues.some((v) => v && v !== '-');
|
||||
const filledInputs = Array.prototype.filter.call(
|
||||
el.querySelectorAll('input:not([type=checkbox]):not([type=radio]), select'),
|
||||
(i) => i.value && String(i.value).trim()
|
||||
).length;
|
||||
const contentLen = (el.textContent || '').replace(/\s/g, '').length;
|
||||
return { rowCount: rows.length, hasDataRows, cardFilled, detailFilled, filledInputs, contentLen };
|
||||
});
|
||||
|
||||
await locator.screenshot({ path: outPath, type: 'jpeg', quality: 75 });
|
||||
|
||||
const stats = fs.statSync(outPath);
|
||||
const sizeKB = (stats.size / 1024).toFixed(1);
|
||||
const label = `[${String(index + 1).padStart(2, '0')}/${total}]`;
|
||||
let dataFlag;
|
||||
if (audit.rowCount > 0) dataFlag = audit.hasDataRows ? `${audit.rowCount}行数据` : '⚠表格无数据';
|
||||
else if (audit.cardFilled) dataFlag = '信息卡已填充';
|
||||
else if (audit.detailFilled) dataFlag = '详情值已填充';
|
||||
else if (audit.filledInputs > 0) dataFlag = `${audit.filledInputs}个表单项已填`;
|
||||
else if (audit.contentLen > 120) dataFlag = '内容已渲染';
|
||||
else dataFlag = '⚠无数据';
|
||||
console.log(` ${label} ${entry.desc} → ${entry.output} (${sizeKB}KB, ${dataFlag})`);
|
||||
} catch (err) {
|
||||
console.error(` ${'✗'.padStart(9)} ${entry.desc}: ${err.message}`);
|
||||
process.exitCode = 1;
|
||||
} finally {
|
||||
await context.close();
|
||||
}
|
||||
}
|
||||
|
||||
(async () => {
|
||||
// 无参数=全部页面+弹窗;"modals"=仅弹窗;其他字符串=文件名含该关键字的页面
|
||||
const arg = process.argv[2] || '';
|
||||
const pages = arg === 'modals' ? [] : (arg ? PAGES.filter(p => p.file.indexOf(arg) !== -1) : PAGES);
|
||||
const modals = (!arg || arg === 'modals') ? MODALS : [];
|
||||
|
||||
console.log('Web管理端 - 体重管理模块 页面截图');
|
||||
console.log(`模块目录: ${MODULE_DIR}`);
|
||||
console.log(`输出目录: ${SCREENSHOTS_DIR}`);
|
||||
console.log(`视口: 1440×900, 1x`);
|
||||
console.log(`共 ${PAGES.length} 个页面\n`);
|
||||
console.log(`页面 ${pages.length} 个,弹窗 ${modals.length} 个\n`);
|
||||
|
||||
if (!fs.existsSync(SCREENSHOTS_DIR)) {
|
||||
fs.mkdirSync(SCREENSHOTS_DIR, { recursive: true });
|
||||
@@ -161,10 +530,13 @@ async function screenshotPage(browser, entry, index, total) {
|
||||
const browser = await chromium.launch({ headless: true });
|
||||
|
||||
try {
|
||||
for (let i = 0; i < PAGES.length; i++) {
|
||||
await screenshotPage(browser, PAGES[i], i, PAGES.length);
|
||||
for (let i = 0; i < pages.length; i++) {
|
||||
await screenshotPage(browser, pages[i], i, pages.length);
|
||||
}
|
||||
console.log(`\n✅ 全部完成!共 ${PAGES.length} 张截图`);
|
||||
for (let i = 0; i < modals.length; i++) {
|
||||
await captureModal(browser, modals[i], i, modals.length);
|
||||
}
|
||||
console.log(`\n✅ 全部完成!页面 ${pages.length} 张 + 弹窗 ${modals.length} 张`);
|
||||
console.log(` 输出目录: ${SCREENSHOTS_DIR}`);
|
||||
} catch (err) {
|
||||
console.error('执行出错:', err);
|
||||
|
||||
|
Before Width: | Height: | Size: 92 KiB After Width: | Height: | Size: 76 KiB |
|
Before Width: | Height: | Size: 95 KiB After Width: | Height: | Size: 91 KiB |
|
After Width: | Height: | Size: 20 KiB |
|
After Width: | Height: | Size: 79 KiB |
|
After Width: | Height: | Size: 109 KiB |
|
After Width: | Height: | Size: 50 KiB |
|
After Width: | Height: | Size: 21 KiB |
|
After Width: | Height: | Size: 100 KiB |
|
After Width: | Height: | Size: 40 KiB |
|
After Width: | Height: | Size: 18 KiB |
|
After Width: | Height: | Size: 69 KiB |
|
After Width: | Height: | Size: 116 KiB |
|
After Width: | Height: | Size: 36 KiB |
|
After Width: | Height: | Size: 18 KiB |
|
Before Width: | Height: | Size: 121 KiB After Width: | Height: | Size: 115 KiB |
|
Before Width: | Height: | Size: 93 KiB After Width: | Height: | Size: 89 KiB |
|
Before Width: | Height: | Size: 19 KiB After Width: | Height: | Size: 20 KiB |
|
Before Width: | Height: | Size: 73 KiB After Width: | Height: | Size: 75 KiB |
|
Before Width: | Height: | Size: 66 KiB After Width: | Height: | Size: 69 KiB |
|
Before Width: | Height: | Size: 80 KiB After Width: | Height: | Size: 80 KiB |
|
Before Width: | Height: | Size: 52 KiB After Width: | Height: | Size: 55 KiB |
|
After Width: | Height: | Size: 55 KiB |
|
After Width: | Height: | Size: 12 KiB |
|
After Width: | Height: | Size: 26 KiB |
|
Before Width: | Height: | Size: 67 KiB After Width: | Height: | Size: 69 KiB |
|
After Width: | Height: | Size: 29 KiB |
|
Before Width: | Height: | Size: 57 KiB After Width: | Height: | Size: 61 KiB |
|
Before Width: | Height: | Size: 58 KiB After Width: | Height: | Size: 60 KiB |
|
After Width: | Height: | Size: 33 KiB |
|
Before Width: | Height: | Size: 54 KiB After Width: | Height: | Size: 56 KiB |
|
Before Width: | Height: | Size: 91 KiB |
|
After Width: | Height: | Size: 28 KiB |
|
Before Width: | Height: | Size: 68 KiB After Width: | Height: | Size: 67 KiB |
|
Before Width: | Height: | Size: 65 KiB After Width: | Height: | Size: 66 KiB |
|
After Width: | Height: | Size: 25 KiB |
|
Before Width: | Height: | Size: 66 KiB After Width: | Height: | Size: 68 KiB |
|
Before Width: | Height: | Size: 86 KiB After Width: | Height: | Size: 87 KiB |
|
Before Width: | Height: | Size: 96 KiB After Width: | Height: | Size: 85 KiB |
|
After Width: | Height: | Size: 55 KiB |
|
Before Width: | Height: | Size: 65 KiB After Width: | Height: | Size: 66 KiB |
|
After Width: | Height: | Size: 34 KiB |
|
Before Width: | Height: | Size: 60 KiB After Width: | Height: | Size: 64 KiB |
|
Before Width: | Height: | Size: 58 KiB After Width: | Height: | Size: 60 KiB |
|
Before Width: | Height: | Size: 66 KiB After Width: | Height: | Size: 69 KiB |
|
Before Width: | Height: | Size: 74 KiB |
|
After Width: | Height: | Size: 16 KiB |
|
After Width: | Height: | Size: 17 KiB |
|
After Width: | Height: | Size: 22 KiB |
|
After Width: | Height: | Size: 22 KiB |
|
After Width: | Height: | Size: 18 KiB |
|
Before Width: | Height: | Size: 66 KiB After Width: | Height: | Size: 67 KiB |
|
After Width: | Height: | Size: 58 KiB |
|
Before Width: | Height: | Size: 79 KiB After Width: | Height: | Size: 81 KiB |
|
Before Width: | Height: | Size: 79 KiB After Width: | Height: | Size: 78 KiB |
|
Before Width: | Height: | Size: 81 KiB After Width: | Height: | Size: 83 KiB |
|
After Width: | Height: | Size: 79 KiB |
|
After Width: | Height: | Size: 50 KiB |
|
After Width: | Height: | Size: 24 KiB |
|
After Width: | Height: | Size: 105 KiB |
|
After Width: | Height: | Size: 75 KiB |
|
After Width: | Height: | Size: 44 KiB |
|
After Width: | Height: | Size: 48 KiB |
|
Before Width: | Height: | Size: 51 KiB After Width: | Height: | Size: 52 KiB |
|
After Width: | Height: | Size: 16 KiB |
|
After Width: | Height: | Size: 16 KiB |
|
After Width: | Height: | Size: 81 KiB |
|
After Width: | Height: | Size: 47 KiB |
|
Before Width: | Height: | Size: 60 KiB After Width: | Height: | Size: 67 KiB |
|
After Width: | Height: | Size: 17 KiB |
|
Before Width: | Height: | Size: 74 KiB After Width: | Height: | Size: 78 KiB |
|
After Width: | Height: | Size: 22 KiB |
|
Before Width: | Height: | Size: 74 KiB After Width: | Height: | Size: 171 KiB |
|
After Width: | Height: | Size: 55 KiB |
|
Before Width: | Height: | Size: 64 KiB After Width: | Height: | Size: 66 KiB |
|
After Width: | Height: | Size: 26 KiB |
|
Before Width: | Height: | Size: 61 KiB After Width: | Height: | Size: 66 KiB |
|
Before Width: | Height: | Size: 52 KiB After Width: | Height: | Size: 59 KiB |
|
After Width: | Height: | Size: 57 KiB |
|
Before Width: | Height: | Size: 68 KiB After Width: | Height: | Size: 69 KiB |
|
Before Width: | Height: | Size: 58 KiB After Width: | Height: | Size: 60 KiB |
|
Before Width: | Height: | Size: 59 KiB After Width: | Height: | Size: 59 KiB |
|
After Width: | Height: | Size: 32 KiB |
|
Before Width: | Height: | Size: 78 KiB After Width: | Height: | Size: 171 KiB |
|
Before Width: | Height: | Size: 90 KiB After Width: | Height: | Size: 54 KiB |