Przeglądaj źródła

超大屏测试用例提交

lihongfei 4 dni temu
rodzic
commit
3ef4a9c951

+ 23 - 0
.claude/settings.local.json

@@ -0,0 +1,23 @@
+{
+  "permissions": {
+    "allow": [
+      "Bash(python -c \"import openpyxl; print\\(''openpyxl ok''\\)\")",
+      "Bash(python -c \"import xlwt; print\\(''xlwt ok''\\)\")",
+      "Bash(where py)",
+      "Bash(py --version)",
+      "Bash(py -c \"import openpyxl; print\\(''openpyxl ok''\\)\")",
+      "Bash(py gen_testcase.py)",
+      "Bash(py -c \":*)",
+      "Bash(py -3 gen_testcase.py)",
+      "Bash(py -X utf8 gen_testcase.py)",
+      "Bash(pip install xlwt -q)",
+      "Bash(python3 -c \"import xlwt; print\\(''xlwt ok''\\)\")",
+      "Bash(C:/Users/Administrator/AppData/Local/Programs/Python/Python312/python.exe -c \"import xlwt; print\\(''xlwt ok''\\)\")",
+      "Bash(C:/Users/Administrator/AppData/Local/Programs/Python/Python312/python.exe -c \"import openpyxl; print\\(''openpyxl ok''\\)\")",
+      "Bash(C:/Users/Administrator/AppData/Local/Programs/Python/Python312/python.exe gen_test_case.py)",
+      "Bash(C:/Users/Administrator/AppData/Local/Programs/Python/Python312/python.exe -c \":*)",
+      "Bash(C:/Users/Administrator/AppData/Local/Programs/Python/Python312/python.exe fix_quotes.py)",
+      "Bash(C:/Users/Administrator/AppData/Local/Programs/Python/Python312/python.exe -c \"import xlwt; r = xlwt.Workbook\\(\\).add_sheet\\(''t''\\).row\\(0\\); print\\(dir\\(r\\)\\)\")"
+    ]
+  }
+}

+ 48 - 0
fix_quotes.py

@@ -0,0 +1,48 @@
+#!/usr/bin/env python3
+# -*- coding: utf-8 -*-
+"""Fix inner double quotes in gen_test_case.py"""
+
+with open('gen_test_case.py', 'rb') as f:
+    text = f.read().decode('utf-8')
+
+lines = text.split('\n')
+fixed_lines = []
+in_test_cases = False
+
+for line in lines:
+    if 'TEST_CASES = [' in line:
+        in_test_cases = True
+    if in_test_cases and ']' == line.strip() and not line.strip().startswith('#'):
+        # Might be closing bracket
+        pass
+
+    if in_test_cases:
+        stripped = line.strip()
+        # Check if this is a string line with inner double quotes
+        # Pattern: starts with " and has more than 2 " (start quote, inner, end quote)
+        if stripped.startswith('"') and stripped.count('"') > 2:
+            leading = len(line) - len(line.lstrip())
+            # Determine suffix
+            if stripped.endswith('",'):
+                end_suffix = '",'
+                inner = stripped[1:-2]
+            elif stripped.endswith('")'):
+                end_suffix = '")'
+                inner = stripped[1:-2]
+            elif stripped.endswith('"'):
+                end_suffix = '"'
+                inner = stripped[1:-1]
+            else:
+                fixed_lines.append(line)
+                continue
+            # Escape inner double quotes
+            inner_escaped = inner.replace('"', '\\"')
+            new_line = ' ' * leading + '"' + inner_escaped + end_suffix
+            fixed_lines.append(new_line)
+            continue
+    fixed_lines.append(line)
+
+result = '\n'.join(fixed_lines)
+with open('gen_test_case.py', 'wb') as f:
+    f.write(result.encode('utf-8'))
+print('Done - fixed inner double quotes')

Plik diff jest za duży
+ 1049 - 0
gen_test_case.py


BIN
img/design-preview-half.png


Plik diff jest za duży
+ 0 - 130
prompt/超大屏prompt.md


BIN
prototype-ui/baseBg.png


+ 0 - 79
prototype-ui/build_doc.py

@@ -1,79 +0,0 @@
-import json
-from docx import Document
-from docx.shared import Pt, RGBColor, Cm
-from docx.enum.text import WD_ALIGN_PARAGRAPH
-from docx.oxml.ns import qn
-from docx.oxml import OxmlElement
-
-def add_divider(doc):
-    p = doc.add_paragraph()
-    pPr = p._p.get_or_add_pPr()
-    pBdr = OxmlElement('w:pBdr')
-    bottom = OxmlElement('w:bottom')
-    bottom.set(qn('w:val'), 'single')
-    bottom.set(qn('w:sz'), '4')
-    bottom.set(qn('w:space'), '1')
-    bottom.set(qn('w:color'), 'B0C4DE')
-    pBdr.append(bottom)
-    pPr.append(pBdr)
-    p.paragraph_format.space_after = Pt(4)
-
-with open('modules.json', encoding='utf-8') as f:
-    modules = json.load(f)
-
-with open('title.txt', encoding='utf-8') as f:
-    lines = f.read().splitlines()
-    main_title, sub_title, note_text = lines[0], lines[1], lines[2]
-
-doc = Document()
-for section in doc.sections:
-    section.top_margin = Cm(2.5)
-    section.bottom_margin = Cm(2.5)
-    section.left_margin = Cm(3)
-    section.right_margin = Cm(3)
-
-t = doc.add_heading(level=0)
-t.alignment = WD_ALIGN_PARAGRAPH.CENTER
-r = t.add_run(main_title)
-r.font.size = Pt(22)
-r.font.color.rgb = RGBColor(0x0d, 0x2a, 0x6e)
-
-s = doc.add_paragraph()
-s.alignment = WD_ALIGN_PARAGRAPH.CENTER
-sr = s.add_run(sub_title)
-sr.font.size = Pt(13)
-sr.font.color.rgb = RGBColor(0x55, 0x77, 0x99)
-doc.add_paragraph()
-add_divider(doc)
-
-for module in modules:
-    h = doc.add_heading(level=1)
-    h.alignment = WD_ALIGN_PARAGRAPH.LEFT
-    hr = h.add_run(module['title'])
-    hr.font.size = Pt(15)
-    hr.font.bold = True
-    hr.font.color.rgb = RGBColor(0x1e, 0x40, 0x7e)
-    h.paragraph_format.space_before = Pt(14)
-    h.paragraph_format.space_after = Pt(4)
-
-    p = doc.add_paragraph()
-    p.alignment = WD_ALIGN_PARAGRAPH.JUSTIFY
-    pr = p.add_run(module['content'])
-    pr.font.size = Pt(11)
-    pr.font.color.rgb = RGBColor(0x22, 0x22, 0x22)
-    p.paragraph_format.first_line_indent = Pt(22)
-    p.paragraph_format.line_spacing = Pt(22)
-    p.paragraph_format.space_after = Pt(6)
-    add_divider(doc)
-
-doc.add_paragraph()
-np = doc.add_paragraph()
-np.alignment = WD_ALIGN_PARAGRAPH.LEFT
-nr = np.add_run(note_text)
-nr.font.size = Pt(9)
-nr.font.color.rgb = RGBColor(0x99, 0x99, 0x99)
-nr.font.italic = True
-
-outfile = '实验室安全智能监测与管控中心-可视化大屏模块说明.docx'
-doc.save(outfile)
-print('Saved:', outfile)

+ 0 - 56
prototype-ui/equip_list.txt

@@ -1,56 +0,0 @@
-货物名称|品牌|型号|单位|数量|备注
-智能管控一体机(定制软件、门禁)|海康威视|DS-D6122TL-D/C(交流)|台|91|已到货验收
-智能门禁|海康威视| DS-K1T6-GS4F|台|9|已到货验收
-化学品智能管理终端(挂墙式)|忠江智能|ZJ-LOCK-S02|台|5|已到货验收
-RFID安全监测门|芯|JG-U001|台|1|已到货验收
-温湿度传感器防爆型|山东蓝煜|LY-TH6000|台|71|已到货
-VOC传感器防爆型|忠江智能|ZJ-CGVOC-F02|台|3|已到货
-氧含量传感器防爆型|忠江智能|ZJ-CGO2-F02|台|11|已到货
-一氧化碳传感器防爆型|仁科|BS-FPC-CO-2000P-NO1|台|4|已到货
-氢气传感器防爆型|忠江智能|ZJ-CGH2-F02|台|1|已到货
-乙炔传感器防爆型|忠江智能|ZJ-CGYQ-F02|台|2|已到货
-硫化氢传感器防爆型|斯弗特|GTYQ-STC50|台|3|已到货
-氨气传感器防爆型|斯弗特|GTYQ-STC50|台|2|已到货
-二氧化硫传感器防爆型|斯弗特|GTYQ-STC50|台|2|已到货
-可燃气体传感器|斯弗特|GTYQ-STC50|台|2|已到货
-物联信息采集终端|忠江智能|ZJ-021-CJQ|台|122|已到货验收
-8口交换机(poe+千兆)|海康威视 |DS-3E0508SP-S|台|110|已到货验收
-16口交换机(poe+千兆)|海康威视 |DS-3E0518SP-SE|台|12|已到货验收
-24口交换机(poe+万兆)|海康威视 |DS-3E2728P-H|台|8|已到货验收
-光模块(10g-lc接口)|华三|SFP-XG-LX-SM1310-D|个|16|已发货
-跳线(LC-LC)|烽火|LC-LC|个|32|已到货验收
-ODF配线架(LC)|烽火|LC光纤配线架|个|10|已到货验收
-物联设备控制器|忠江智能|ZJ-015-KZQ|台|40|已到货验收
-声光报警器|奈邦智能 |NB-N-1101J-DYQ|台|5|已到货
-智能喇叭|忠江智能| ZJ-HT-G02|台|93|已安装
-物联网控制箱|龙硕| JXF-08|台|120|已到货已安装
-智能电箱|龙硕| JXF-11|台|10|已到货已安装
-应急疏散灯|ninaroco| LQ-55|台|8|已到货已安装
-电话报警成套装备|忠江智能|助通短信平台(软件)|台|1|软件
-网络话筒|播音者|DY1000|台|1|已到货验收
-对讲设备|菱声|MY-LS812|台|5|已安装
-半球网络摄像机(2.8mm,poe)|海康威视 |DS-2CD2325CV4-I|台|227|已到货验收
-半球网络摄像机(4mm,poe)|海康威视 |DS-2CD2325CV4-I|台|9|已到货验收
-双光谱摄像机|海康威视 |DS-2TD2608-1/QA/FP |台|18|已到货验收
-安全教育与考试一体机|触鼎| CD-22A|台|2|正常采购中
-积分兑换机|中吉 |TCN-CSC-10C(V22)|台|1|正常采购中
-视频存储|海康威视|DS-96256N-124R-V4 |台|1|采购中
-手持巡检仪|锦瑞达|JRD-860|台|8|已到货
-职工IC卡|海康威视 |IC卡|张|800|已到货验收
-大屏|创维 |75A23|台|2|已到货
-大屏主机|联想|M460|台|1|已到货
-防爆智能易燃气体安全储存柜(双瓶)|江苏君源|JY-QP02-易燃气体|台|6|已生产好,暂存厂家
-人体静电释放器|盾斯特|ES-PSA|台|3|已到货
-悬挂式七氟丙烷灭火装置|广东浙安消防|XQQW8/1.6|台|11|已到货
-LED防爆洁净荧光灯|新黎明防爆电器|BHY系列|台|4|已到货
-防爆照明开关|新黎明防爆电器|SW-10|个|2|已到货
-防爆插销|新黎明防爆电器|AC-16|个|10|已到货
-气瓶推车|江苏君源|JY-QPTC02|个|4|已到货
-防爆智能净气型毒害品安全存储柜|江苏君源|JY-FBDHPG03|个|1|已生产好,暂存厂家
-防爆智能净气型易燃品安全存储柜|江苏君源|JY-FBYRPG03|个|2|已生产好,暂存厂家
-防爆智能净气型强腐蚀品安全存储柜|江苏君源|JY-FBFSPG03|个|2|已生产好,暂存厂家
-静电接地系统|江苏君源|JY-XJD01|项|1|已到货
-UPS电源|海康威视 |DS-IUH1110L-K/T|台|6|已到货验收
-安装调试|无|安装调试|项|1|施工
-实验室安全智慧管控系统|忠江智能|定制|项|1|软件
-总计|||||

+ 0 - 166
prototype-ui/gen2.py

@@ -1,166 +0,0 @@
-# -*- coding: utf-8 -*-
-from docx import Document
-from docx.shared import Pt, RGBColor, Cm, Inches
-from docx.enum.text import WD_ALIGN_PARAGRAPH
-from docx.oxml.ns import qn
-from docx.oxml import OxmlElement
-
-def set_heading_style(paragraph, level=1):
-    run = paragraph.runs[0] if paragraph.runs else paragraph.add_run()
-    if level == 1:
-        run.font.size = Pt(18)
-        run.font.bold = True
-        run.font.color.rgb = RGBColor(0x1e, 0x40, 0x7e)
-    elif level == 2:
-        run.font.size = Pt(14)
-        run.font.bold = True
-        run.font.color.rgb = RGBColor(0x1a, 0x5a, 0xa0)
-    paragraph.paragraph_format.space_before = Pt(12)
-    paragraph.paragraph_format.space_after = Pt(4)
-
-def add_divider(doc):
-    p = doc.add_paragraph()
-    pPr = p._p.get_or_add_pPr()
-    pBdr = OxmlElement('w:pBdr')
-    bottom = OxmlElement('w:bottom')
-    bottom.set(qn('w:val'), 'single')
-    bottom.set(qn('w:sz'), '4')
-    bottom.set(qn('w:space'), '1')
-    bottom.set(qn('w:color'), 'B0C4DE')
-    pBdr.append(bottom)
-    pPr.append(pBdr)
-    p.paragraph_format.space_after = Pt(6)
-
-doc = Document()
-
-# 页面边距
-sections = doc.sections
-for section in sections:
-    section.top_margin = Cm(2.5)
-    section.bottom_margin = Cm(2.5)
-    section.left_margin = Cm(3)
-    section.right_margin = Cm(3)
-
-# 标题
-title = doc.add_heading('实验室安全智能监测与管控中心', 0)
-title.alignment = WD_ALIGN_PARAGRAPH.CENTER
-title_run = title.runs[0]
-title_run.font.size = Pt(22)
-title_run.font.color.rgb = RGBColor(0x0d, 0x2a, 0x6e)
-
-subtitle = doc.add_paragraph('可视化大屏模块说明文档')
-subtitle.alignment = WD_ALIGN_PARAGRAPH.CENTER
-subtitle_run = subtitle.runs[0]
-subtitle_run.font.size = Pt(13)
-subtitle_run.font.color.rgb = RGBColor(0x55, 0x77, 0x99)
-doc.add_paragraph()
-
-add_divider(doc)
-
-# ====== 模块列表 ======
-modules = [
-    {
-        "title": "一、顶部导航栏",
-        "content": (
-            "导航栏位于大屏顶部,横贯全屏,分为左、中、右三个区域。"
-            "左侧实时显示当前时间(时:分:秒)及日期、星期;"
-            "中间居中展示机构LOGO、单位名称(中国安全生产科学研究院)及系统大标题"实验室安全智能监测与管控中心";"
-            "右侧显示所在城市的天气状况、实时温度及空气质量指数(AQI),为用户提供环境背景信息参考。"
-        )
-    },
-    {
-        "title": "二、实验室基本情况统计",
-        "content": (
-            "本模块综合呈现全院实验室的整体规模与运行状态。"
-            "上部左侧以SVG圆弧仪表图直观显示实验室总数(128间),并在图形下方配以I~IV级对应红橙黄蓝的分级比例色条;"
-            "上部右侧采用环形图(donut chart)展示各安全等级实验室的占比与数量,圆心标注总数,外侧标注各级名称、数量及百分比,并附彩色明细行辅助阅读。"
-            "下部通过三栏状态徽章实时显示"使用中""异常""空闲"三种状态下的实验室数量,便于管理人员快速掌握当前资源利用情况。"
-        )
-    },
-    {
-        "title": "三、实验室安全分级统计",
-        "content": (
-            "本模块以堆叠柱状图的形式,统计并展示院内各二级单位的实验室总数及各安全等级(I~IV级)分布情况。"
-            "X轴为各二级单位名称,轴标签下方附注该单位实验室总数;Y轴为实验室数量;图例采用红、橙、黄、蓝四色区分等级。"
-            "当二级单位数量超出可视范围时,图表将自动从右向左循环滚动,每隔5秒滚动一列,确保所有数据均可完整呈现。"
-        )
-    },
-    {
-        "title": "四、实验室进入人数统计及走势",
-        "content": (
-            "本模块以数字翻牌器的形式,醒目展示今日累计进入实验室总人数及当前仍在实验室内的实验人数,两组数据并排一行,动态翻转效果直观呈现数据变化。"
-            "下方折线图按0时至24时划分为9个时间分段,分别绘制当天实验室进入人数及当前实验人数的动态走势曲线,帮助管理员掌握人员流动规律与高峰时段分布。"
-        )
-    },
-    {
-        "title": "五、智能环境感知应用设备统计",
-        "content": (
-            "本模块集中展示院内各类智能物联感知设备的部署与在线状况。"
-            "顶部以图标+数字的形式,分类显示各类设备的在线数量与离线数量;"
-            "左侧以速度仪表盘样式的图表直观呈现设备整体在线率百分比;"
-            "右侧以2×2网格布局分别显示电子信息铭牌、化学品智能终端、传感器套件、智能摄像设备的设备总数,为运维管理提供快速数据参考。"
-        )
-    },
-    {
-        "title": "六、实验室设备分类及使用统计",
-        "content": (
-            "本模块按上中下三段式布局,对实验室设备进行多维度统计分析。"
-            "上部使用环形图展示全院实验室设备的分类构成,图右侧配有各分类的色块标识、名称与数量(台);"
-            "中部汇总显示设备总数、设备累计使用总时长及设备使用率三项核心指标,便于评估设备利用效率;"
-            "下部使用饼图展示设备按使用状态(使用中、空闲、正常、维修)的分布数量及占比,右侧同步附注各状态的色块与数量说明,辅助设备管理决策。"
-        )
-    },
-    {
-        "title": "七、实时监控",
-        "content": (
-            "本模块为用户提供实验室实时视频监控查看功能,区域分为左右两个内容区。"
-            "左侧提供搜索框(可按楼栋、楼层检索)、二级单位下拉筛选,以及可展开折叠的建筑结构树状图(层级为院区→楼栋→楼层→房间),便于快速定位目标实验室。"
-            "右侧以9宫格(16:9比例)方式同屏显示9路实验室实时监控画面,其中首路画面来自智能摄像头,支持危险行为AI检测框标注;顶部显示当前院区/楼栋/楼层层级路径,并提供翻页按钮以切换查看更多房间画面。"
-        )
-    },
-    {
-        "title": "八、实验环境安全智能感知",
-        "content": (
-            "本模块实时展示全院各实验室的环境安全监测数据,由下而上滚动播放所有实验室条目。"
-            "每条实验室信息包括实验室名称(含房号)及所属二级单位,并以图标形式列出各传感器(温度、湿度、TVOC、CO₂、O₂等)的实时状态数值。"
-            "当某项监测值超出安全阈值时,对应条目以红色渐变高亮效果突出显示,并触发预警图标,使管理人员能第一时间识别异常实验室及异常指标,及时响应处置。"
-        )
-    },
-    {
-        "title": "九、实验室实时风险预警",
-        "content": (
-            "本模块集中呈现实验室环境异常预警信息,顶部以琥珀色高亮数字统计展示本月预警响应总数。"
-            "预警列表区域按时间倒序实时滚动轮播最新异常通知,最新一条置顶显示。"
-            "每条预警记录包括:实验室名称(含房号)及所属二级单位、触发预警的传感器类型及对应异常数值、精确到秒的预警时间(日期-时-分-秒)。"
-            "整体配色以琥珀/橙色调为主,视觉上区别于紧急红色,使管理人员在持续关注时不产生视觉疲劳,同时保持较强的信息提示性。"
-        )
-    },
-]
-
-for module in modules:
-    h = doc.add_heading(module["title"], level=1)
-    h.alignment = WD_ALIGN_PARAGRAPH.LEFT
-    set_heading_style(h, level=1)
-
-    p = doc.add_paragraph(module["content"])
-    p.alignment = WD_ALIGN_PARAGRAPH.JUSTIFY
-    run = p.runs[0]
-    run.font.size = Pt(11)
-    run.font.color.rgb = RGBColor(0x22, 0x22, 0x22)
-    p.paragraph_format.first_line_indent = Pt(22)
-    p.paragraph_format.line_spacing = Pt(22)
-    p.paragraph_format.space_after = Pt(6)
-
-    add_divider(doc)
-
-# 页脚备注
-doc.add_paragraph()
-note = doc.add_paragraph('注:本文档为实验室安全智能监测与管控中心可视化大屏各数据统计模块的功能说明,仅作文字描述参考,不含设计原型图。')
-note.alignment = WD_ALIGN_PARAGRAPH.LEFT
-note_run = note.runs[0]
-note_run.font.size = Pt(9)
-note_run.font.color.rgb = RGBColor(0x99, 0x99, 0x99)
-note_run.font.italic = True
-
-doc.save('实验室安全智能监测与管控中心-可视化大屏模块说明.docx')
-print("文档已生成:实验室安全智能监测与管控中心-可视化大屏模块说明.docx")

+ 0 - 774
prototype-ui/gen_doc.py

@@ -1,774 +0,0 @@
-# -*- coding: utf-8 -*-
-from docx import Document
-from docx.shared import Pt, Cm, RGBColor, Inches
-from docx.enum.text import WD_ALIGN_PARAGRAPH, WD_TAB_LEADER
-from docx.enum.table import WD_TABLE_ALIGNMENT, WD_ALIGN_VERTICAL
-from docx.oxml.ns import qn
-from docx.oxml import OxmlElement
-import copy
-
-doc = Document()
-
-# ── 页面设置 A4 ──────────────────────────────────────────────
-section = doc.sections[0]
-section.page_width  = Cm(21)
-section.page_height = Cm(29.7)
-section.left_margin   = Cm(2.5)
-section.right_margin  = Cm(2.5)
-section.top_margin    = Cm(2.5)
-section.bottom_margin = Cm(2.0)
-
-# ── 颜色常量 ────────────────────────────────────────────────
-C_TITLE  = RGBColor(0x1e, 0x3a, 0x8a)   # 深蓝
-C_H2     = RGBColor(0x1d, 0x4e, 0xd8)   # 中蓝
-C_H3     = RGBColor(0x0e, 0x7a, 0x9e)   # 青蓝
-C_LABEL  = RGBColor(0x37, 0x41, 0x51)   # 深灰
-C_BODY   = RGBColor(0x1f, 0x29, 0x37)   # 正文黑
-C_WHITE  = RGBColor(0xFF, 0xFF, 0xFF)
-C_TH_BG  = RGBColor(0x1e, 0x40, 0xaf)
-C_ROW1   = RGBColor(0xef, 0xf6, 0xff)
-C_ROW2   = RGBColor(0xff, 0xff, 0xff)
-C_BORDER = RGBColor(0xc3, 0xda, 0xf7)
-C_NOTE_BG= RGBColor(0xfe, 0xf9, 0xc3)
-C_NOTE_BD= RGBColor(0xf5, 0x9e, 0x0b)
-
-def set_cell_bg(cell, rgb: RGBColor):
-    tc = cell._tc
-    tcPr = tc.get_or_add_tcPr()
-    shd = OxmlElement('w:shd')
-    shd.set(qn('w:val'), 'clear')
-    shd.set(qn('w:color'), 'auto')
-    hex_color = '{:02X}{:02X}{:02X}'.format(rgb[0], rgb[1], rgb[2])
-    shd.set(qn('w:fill'), hex_color)
-    tcPr.append(shd)
-
-def set_cell_border(cell, top=None, bottom=None, left=None, right=None):
-    tc = cell._tc
-    tcPr = tc.get_or_add_tcPr()
-    tcBorders = OxmlElement('w:tcBorders')
-    for side, val in [('top',top),('bottom',bottom),('left',left),('right',right)]:
-        if val:
-            el = OxmlElement(f'w:{side}')
-            el.set(qn('w:val'), val.get('val','single'))
-            el.set(qn('w:sz'), str(val.get('sz',4)))
-            el.set(qn('w:space'), '0')
-            el.set(qn('w:color'), val.get('color','C3DAF7'))
-            tcBorders.append(el)
-    tcPr.append(tcBorders)
-
-def add_run(para, text, bold=False, size=None, color=None, italic=False):
-    run = para.add_run(text)
-    run.bold = bold
-    run.italic = italic
-    run.font.name = '微软雅黑'
-    run._element.rPr.rFonts.set(qn('w:eastAsia'), '微软雅黑')
-    if size:
-        run.font.size = Pt(size)
-    if color:
-        run.font.color.rgb = color
-    return run
-
-def heading1(text):
-    para = doc.add_paragraph()
-    para.paragraph_format.space_before = Pt(18)
-    para.paragraph_format.space_after  = Pt(8)
-    para.paragraph_format.left_indent  = Cm(0)
-    # 左侧蓝条效果通过 border
-    pPr = para._p.get_or_add_pPr()
-    pBdr = OxmlElement('w:pBdr')
-    left = OxmlElement('w:left')
-    left.set(qn('w:val'), 'single')
-    left.set(qn('w:sz'), '24')
-    left.set(qn('w:space'), '6')
-    left.set(qn('w:color'), '1E3A8A')
-    pBdr.append(left)
-    pPr.append(pBdr)
-    para.paragraph_format.left_indent = Cm(0.5)
-    add_run(para, text, bold=True, size=14, color=C_TITLE)
-    return para
-
-def heading2(text):
-    para = doc.add_paragraph()
-    para.paragraph_format.space_before = Pt(10)
-    para.paragraph_format.space_after  = Pt(4)
-    add_run(para, '◆ ', bold=True, size=12, color=C_H2)
-    add_run(para, text, bold=True, size=12, color=C_H2)
-    return para
-
-def heading3(text):
-    para = doc.add_paragraph()
-    para.paragraph_format.space_before = Pt(6)
-    para.paragraph_format.space_after  = Pt(3)
-    add_run(para, '▸ ', bold=True, size=11, color=C_H3)
-    add_run(para, text, bold=True, size=11, color=C_H3)
-    return para
-
-def body(text, indent=0):
-    para = doc.add_paragraph()
-    para.paragraph_format.space_before = Pt(2)
-    para.paragraph_format.space_after  = Pt(2)
-    para.paragraph_format.left_indent  = Cm(indent)
-    para.paragraph_format.first_line_indent = Cm(0)
-    add_run(para, text, size=10.5, color=C_BODY)
-    return para
-
-def bullet(text, level=0):
-    para = doc.add_paragraph()
-    para.paragraph_format.space_before = Pt(1)
-    para.paragraph_format.space_after  = Pt(1)
-    indent = 0.5 + level * 0.5
-    para.paragraph_format.left_indent = Cm(indent)
-    bullets = ['●', '○', '–']
-    add_run(para, bullets[level] + '  ', bold=(level==0), size=10, color=C_H3)
-    add_run(para, text, size=10, color=C_BODY)
-    return para
-
-def note(text):
-    para = doc.add_paragraph()
-    para.paragraph_format.space_before = Pt(6)
-    para.paragraph_format.space_after  = Pt(6)
-    para.paragraph_format.left_indent  = Cm(0.5)
-    para.paragraph_format.right_indent = Cm(0.5)
-    pPr = para._p.get_or_add_pPr()
-    pBdr = OxmlElement('w:pBdr')
-    for side in ['top','bottom','left','right']:
-        el = OxmlElement(f'w:{side}')
-        el.set(qn('w:val'), 'single')
-        el.set(qn('w:sz'), '4' if side in ('top','bottom','right') else '18')
-        el.set(qn('w:space'), '4')
-        el.set(qn('w:color'), 'F59E0B')
-        pBdr.append(el)
-    pPr.append(pBdr)
-    add_run(para, '📌 注意:', bold=True, size=10, color=RGBColor(0xd9,0x77,0x06))
-    add_run(para, text, size=10, color=C_BODY)
-    return para
-
-def make_table(headers, rows, col_widths=None):
-    table = doc.add_table(rows=1+len(rows), cols=len(headers))
-    table.alignment = WD_TABLE_ALIGNMENT.CENTER
-    table.style = 'Table Grid'
-    # 表头
-    hdr = table.rows[0]
-    for i, h in enumerate(headers):
-        cell = hdr.cells[i]
-        set_cell_bg(cell, C_TH_BG)
-        cell.vertical_alignment = WD_ALIGN_VERTICAL.CENTER
-        para = cell.paragraphs[0]
-        para.alignment = WD_ALIGN_PARAGRAPH.CENTER
-        add_run(para, h, bold=True, size=10, color=C_WHITE)
-    # 数据行
-    for r_idx, row in enumerate(rows):
-        tr = table.rows[r_idx+1]
-        bg = C_ROW1 if r_idx % 2 == 0 else C_ROW2
-        for c_idx, val in enumerate(row):
-            cell = tr.cells[c_idx]
-            set_cell_bg(cell, bg)
-            cell.vertical_alignment = WD_ALIGN_VERTICAL.CENTER
-            para = cell.paragraphs[0]
-            para.alignment = WD_ALIGN_PARAGRAPH.LEFT
-            add_run(para, str(val), size=10, color=C_BODY)
-    # 列宽
-    if col_widths:
-        for i, w in enumerate(col_widths):
-            for row in table.rows:
-                row.cells[i].width = Cm(w)
-    doc.add_paragraph()  # 表格后空行
-    return table
-
-def divider():
-    para = doc.add_paragraph()
-    para.paragraph_format.space_before = Pt(4)
-    para.paragraph_format.space_after  = Pt(4)
-    pPr = para._p.get_or_add_pPr()
-    pBdr = OxmlElement('w:pBdr')
-    bottom = OxmlElement('w:bottom')
-    bottom.set(qn('w:val'), 'single')
-    bottom.set(qn('w:sz'), '6')
-    bottom.set(qn('w:space'), '1')
-    bottom.set(qn('w:color'), 'C3DAF7')
-    pBdr.append(bottom)
-    pPr.append(pBdr)
-
-# ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
-# 封面
-# ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
-doc.add_paragraph()
-doc.add_paragraph()
-doc.add_paragraph()
-
-p = doc.add_paragraph()
-p.alignment = WD_ALIGN_PARAGRAPH.CENTER
-add_run(p, '实验室安全智能监测与管控中心', bold=True, size=22, color=C_TITLE)
-
-doc.add_paragraph()
-p = doc.add_paragraph()
-p.alignment = WD_ALIGN_PARAGRAPH.CENTER
-add_run(p, '数据可视化大屏', bold=True, size=16, color=C_H2)
-
-doc.add_paragraph()
-p = doc.add_paragraph()
-p.alignment = WD_ALIGN_PARAGRAPH.CENTER
-add_run(p, '使  用  说  明  文  档', bold=True, size=14, color=C_H3)
-
-doc.add_paragraph()
-doc.add_paragraph()
-
-p = doc.add_paragraph()
-p.alignment = WD_ALIGN_PARAGRAPH.CENTER
-add_run(p, '中国安全生产科学研究院', bold=True, size=12, color=C_LABEL)
-
-p = doc.add_paragraph()
-p.alignment = WD_ALIGN_PARAGRAPH.CENTER
-add_run(p, 'National Institute for Occupational Safety', italic=True, size=10, color=C_LABEL)
-
-doc.add_paragraph()
-p = doc.add_paragraph()
-p.alignment = WD_ALIGN_PARAGRAPH.CENTER
-add_run(p, '文档版本:V1.0  编制日期:2026年03月', size=10, color=C_LABEL)
-
-doc.add_page_break()
-
-# ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
-# 目录
-# ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
-p = doc.add_paragraph()
-p.alignment = WD_ALIGN_PARAGRAPH.CENTER
-add_run(p, '目  录', bold=True, size=14, color=C_TITLE)
-doc.add_paragraph()
-
-toc_items = [
-    ('1', '文档概述', '3'),
-    ('2', '系统简介', '3'),
-    ('3', '运行环境与访问方式', '4'),
-    ('4', '整体界面布局说明', '4'),
-    ('5', '顶部导航栏', '5'),
-    ('6', '1区 — 实验室基本情况统计', '5'),
-    ('7', '2区 — 智能设备与实验室设备统计', '7'),
-    ('8', '3区 — 实时监控', '8'),
-    ('9', '4区 — 实验环境感知与风险预警', '10'),
-    ('10', '全屏预警弹窗', '11'),
-    ('11', '通用交互说明', '12'),
-    ('12', '数据说明', '12'),
-    ('13', '常见问题(FAQ)', '13'),
-]
-for no, title, page in toc_items:
-    para = doc.add_paragraph()
-    para.paragraph_format.space_before = Pt(3)
-    para.paragraph_format.space_after  = Pt(3)
-    tab_stops = para.paragraph_format.tab_stops
-    tab_stops.add_tab_stop(Cm(14.5), leader=WD_TAB_LEADER.DOTS)
-    add_run(para, f'{no}. {title}', size=10.5, color=C_BODY)
-    para.add_run('\t')
-    add_run(para, page, size=10.5, color=C_BODY)
-
-doc.add_page_break()
-
-# ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
-# 1. 文档概述
-# ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
-heading1('1. 文档概述')
-body('本文档为《实验室安全智能监测与管控中心》数据可视化大屏的使用说明,旨在帮助操作人员、管理人员及相关使用者快速了解系统各功能模块的展示内容、交互方式及数据含义,确保系统功能得到正确、高效的使用。')
-doc.add_paragraph()
-body('本文档适用对象:')
-bullet('实验室安全管理人员(日常监控运维)')
-bullet('院级行政管理人员(决策数据查看)')
-bullet('IT运维人员(系统部署与维护参考)')
-
-divider()
-
-# ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
-# 2. 系统简介
-# ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
-heading1('2. 系统简介')
-body('《实验室安全智能监测与管控中心》大屏系统是面向中国安全生产科学研究院(简称"安科院")各实验室的综合安全管理可视化平台,通过数据可视化手段将实验室的安全状态、环境感知数据、设备运行状况、人员进出信息及风险预警等核心数据以直观、实时的方式呈现于大屏上,便于管理人员进行统一监控与快速决策。')
-
-doc.add_paragraph()
-heading2('系统定位')
-make_table(
-    ['维度', '说明'],
-    [
-        ['系统名称', '实验室安全智能监测与管控中心'],
-        ['所属单位', '中国安全生产科学研究院(National Institute for Occupational Safety)'],
-        ['系统类型', '数据可视化大屏(单页面应用)'],
-        ['设计分辨率', '9600 × 2800 px(约为1920×1080的2.5倍)'],
-        ['视觉风格', '深蓝科幻风,边框流动效果,科技感动效图标'],
-        ['技术框架', 'HTML5 + CSS3 + ECharts 5.4.3(单文件内联,无需构建工具)'],
-    ],
-    col_widths=[4, 11.5]
-)
-
-heading2('主要功能概览')
-bullet('实验室基本情况统计(总数、分级、状态)')
-bullet('实验室安全分级统计(各二级单位堆叠柱状图)')
-bullet('实验室人员进出数量统计及走势折线图')
-bullet('智能环境感知应用设备统计(在线率、设备分类)')
-bullet('实验室设备分类及使用统计(分类、使用率、状态)')
-bullet('3区固定显示实时监控(9宫格监控画面,AI危险行为检测)')
-bullet('实验环境安全智能感知(传感器实时数据滚动)')
-bullet('实验室实时风险预警(预警通知轮播)')
-bullet('全屏预警弹窗(超阈值自动触发,科幻警报样式)')
-
-divider()
-
-# ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
-# 3. 运行环境与访问方式
-# ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
-heading1('3. 运行环境与访问方式')
-heading2('硬件环境要求')
-make_table(
-    ['项目', '最低要求', '推荐配置'],
-    [
-        ['显示屏', '1920×1080(FHD)', '超大屏/拼接屏 9600×2800 px'],
-        ['处理器', 'Intel i5 / 同等级别', 'Intel i7 或以上'],
-        ['内存', '8 GB', '16 GB 或以上'],
-        ['显卡', '支持硬件加速', '独立显卡(GPU加速渲染)'],
-    ],
-    col_widths=[4, 5, 6.5]
-)
-
-heading2('软件环境要求')
-make_table(
-    ['软件', '版本要求', '说明'],
-    [
-        ['浏览器', 'Chrome 90+ / Edge 90+', '推荐使用最新版 Chrome,需支持 WebGL'],
-        ['ECharts CDN', '5.4.3', '通过 jsDelivr CDN 在线加载,需保证网络可达'],
-        ['操作系统', 'Windows 10+ / macOS 12+', '支持主流64位操作系统'],
-    ],
-    col_widths=[3, 5, 7.5]
-)
-
-heading2('访问方式')
-bullet('将项目文件夹(含 index-v2.html 及 baseBg.png)部署到本地或局域网 Web 服务器。')
-bullet('用浏览器打开 http://[服务器IP]/index-v2.html 即可访问。')
-bullet('也可直接双击 index-v2.html 在浏览器中本地打开(部分资源需本地服务器访问)。')
-bullet('首次加载时,系统将自动请求进入全屏模式(浏览器弹出授权提示,点击"允许"即可)。')
-
-note('若 ECharts 图表未正常显示,请检查网络连接是否可访问 cdn.jsdelivr.net,或将 ECharts 替换为本地文件引用。')
-divider()
-
-# ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
-# 4. 整体界面布局说明
-# ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
-heading1('4. 整体界面布局说明')
-body('大屏整体由顶部导航栏 + 内容区域两大部分构成,内容区域按从左到右 2:1.5:4.5:2 的比例划分为四个区域。布局示意如下:')
-doc.add_paragraph()
-
-# 布局示意表
-table = doc.add_table(rows=3, cols=5)
-table.alignment = WD_TABLE_ALIGNMENT.CENTER
-table.style = 'Table Grid'
-
-# 第1行:导航栏
-row0 = table.rows[0]
-row0.cells[0].merge(row0.cells[4])
-set_cell_bg(row0.cells[0], C_TH_BG)
-p = row0.cells[0].paragraphs[0]
-p.alignment = WD_ALIGN_PARAGRAPH.CENTER
-add_run(p, '顶部导航栏(时钟 | LOGO + 单位名称 + 系统大标题 | 天气)', bold=True, size=10, color=C_WHITE)
-
-# 第2行:列标题
-row1 = table.rows[1]
-for i, (txt, bg) in enumerate([
-    ('1区(比例2)', C_ROW1),
-    ('2区(比例1.5)', C_ROW2),
-    ('3区(比例4.5)', C_ROW1),
-    ('4区(比例2)', C_ROW2),
-]):
-    if i < 4:
-        cell = row1.cells[i]
-        set_cell_bg(cell, C_TH_BG if i in [0,2] else RGBColor(0x1d, 0x4e, 0xd8))
-        p = cell.paragraphs[0]
-        p.alignment = WD_ALIGN_PARAGRAPH.CENTER
-        add_run(p, txt, bold=True, size=10, color=C_WHITE)
-# merge last 2 cols for col 4
-# Actually use 4 cols only: remove 5th
-row1.cells[4].merge(row1.cells[3])
-
-# 第3行:内容描述
-row2 = table.rows[2]
-contents = [
-    '• 基本情况统计\n• 安全分级统计\n• 人员进出走势',
-    '• 智能环境感知\n  应用设备统计\n• 设备分类及\n  使用统计',
-    '实时监控(固定显示)\n左侧:搜索/筛选/树状图\n右侧:9宫格摄像头',
-    '• 实验环境安全\n  智能感知\n• 实时风险预警',
-]
-bgs = [C_ROW1, C_ROW2, C_ROW1, C_ROW2]
-for i, (txt, bg) in enumerate(zip(contents, bgs)):
-    cell = row2.cells[i]
-    set_cell_bg(cell, bg)
-    cell.vertical_alignment = WD_ALIGN_VERTICAL.CENTER
-    p = cell.paragraphs[0]
-    p.alignment = WD_ALIGN_PARAGRAPH.LEFT
-    add_run(p, txt, size=9.5, color=C_BODY)
-
-row2.cells[4].merge(row2.cells[3])
-doc.add_paragraph()
-
-body('各区域均设有科幻装饰线框(四角装饰线)、边框流动动画(border-beam)及动效图标,模块之间有呼吸感的间距,整体采用深蓝科幻主题色系。')
-
-divider()
-
-# ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
-# 5. 顶部导航栏
-# ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
-heading1('5. 顶部导航栏')
-body('导航栏位于页面最顶部,横贯全宽,分为左、中、右三个区域。')
-
-make_table(
-    ['区域', '显示内容', '说明'],
-    [
-        ['左侧', '实时时钟(时:分:秒)\n日期及星期', '每秒自动更新,金色数字显示,字体采用等宽字体'],
-        ['中间', 'LOGO图标\n中国安全生产科学研究院(单位名称)\n竖向分隔线\n实验室安全智能监测与管控中心(系统大标题)', 'LOGO为蓝色方形图标,带脉冲光晕动效;\n大标题采用渐变流光动画,中英文双语单位名称显示'],
-        ['右侧', '城市 · 天气状况\n气温(°C) / AQI空气质量指数', '显示当前天气信息及空气质量'],
-    ],
-    col_widths=[2.5, 7, 6]
-)
-
-note('导航栏不含任何操作按钮,3区实时监控为固定显示,无需切换。')
-divider()
-
-# ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
-# 6. 1区
-# ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
-heading1('6. 1区 — 实验室基本情况统计')
-body('1区位于大屏左侧,由上至下包含三个功能模块。')
-
-heading2('6.1 实验室基本情况统计')
-body('本模块分为上部(80%)和下部(20%)两部分:')
-doc.add_paragraph()
-heading3('上部(按左4:右6比例划分)')
-bullet('左侧(4):SVG圆弧仪表图形,展示实验室总数(共128间),图形中心显示总数数字,下方配分级比例色条(I级红、II级橙、III级黄、IV级蓝)。', 0)
-bullet('右侧(6):环形图(donut chart),展示四个安全分级(I~IV级)的占比及数量,图外标注各级名称、数量(间)、百分比;图表圆心显示总数;右侧配彩色左边框明细行。', 0)
-
-doc.add_paragraph()
-heading3('下部(三种状态徽章)')
-bullet('横向并排三个状态徽章,实时显示各状态实验室数量:', 0)
-bullet('使用(绿色):当前正在使用的实验室数量', 1)
-bullet('异常(橙色):当前处于异常告警状态的实验室数量', 1)
-bullet('空闲(靛蓝):当前闲置的实验室数量', 1)
-
-doc.add_paragraph()
-body('分级说明:')
-make_table(
-    ['安全级别', '标识颜色', '含义', '数量(示例)'],
-    [
-        ['I 级', '红色', '危险实验室', '12 间'],
-        ['II 级', '橙色', '较危险实验室', '28 间'],
-        ['III 级', '黄色', '一般危险实验室', '45 间'],
-        ['IV 级', '蓝色', '较安全实验室', '43 间'],
-    ],
-    col_widths=[3, 3, 5, 4.5]
-)
-
-divider()
-heading2('6.2 实验室安全分级统计')
-body('本模块使用堆叠柱状图统计各二级单位实验室总数及分级数据。')
-bullet('X轴:各二级单位名称,单位名称下方显示该单位实验室总数。')
-bullet('Y轴:实验室数量(间)。')
-bullet('每根柱子由I~IV级叠加组成,颜色对应分级(红/橙/黄/蓝)。')
-bullet('图表顶部显示图例(I级、II级、III级、IV级及对应颜色)。')
-bullet('一屏展示6个柱子,当数据量超出时,图表每5秒自动向左滚动1个单位,循环展示所有二级单位数据。')
-
-doc.add_paragraph()
-note('鼠标悬停在柱状图上可显示当前单位各分级详细数量的悬浮提示框。')
-divider()
-
-heading2('6.3 实验室进入人数统计及走势')
-body('本模块展示今日实验室进入与在场人数统计,以及全天走势趋势。')
-doc.add_paragraph()
-heading3('数字翻牌器(上方)')
-bullet('今日进入总人数:以翻牌动画形式显示,共4位数字滚动展示,金色高亮。')
-bullet('当前在场实验人数:同样以翻牌动画形式显示,实时更新。')
-
-heading3('折线图(下方)')
-bullet('X轴:当天0~24小时,按9个时间节点显示(00:00、03:00、06:00…24:00)。')
-bullet('Y轴:人数(人)。')
-bullet('进入人数(蓝色实线+渐变填充)与在场人数(金色实线+渐变填充)双线对比展示。')
-
-divider()
-
-# ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
-# 7. 2区
-# ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
-heading1('7. 2区 — 智能设备与实验室设备统计')
-body('2区位于1区右侧,由上至下包含两个功能模块。')
-
-heading2('7.1 智能环境感知应用设备统计')
-body('本模块统计展示布设的智能物联应用设备整体情况,分为上部统计栏和下部图表区。')
-doc.add_paragraph()
-heading3('上部统计栏')
-bullet('在线设备数量(绿色高亮,示例:312台)')
-bullet('离线设备数量(红色高亮,示例:18台)')
-
-heading3('下部图表区(左右布局)')
-bullet('左侧:速度仪表盘样式图表,展示设备在线率百分比(示例:94.5%),金色数字显示。')
-bullet('右侧:2×2网格卡片,分别显示:')
-bullet('电子信息铭牌(86台)', 1)
-bullet('化学品智能终端(44台)', 1)
-bullet('传感器套件(128台)', 1)
-bullet('智能摄像设备(72台)', 1)
-
-divider()
-heading2('7.2 实验室设备分类及使用统计')
-body('本模块按 4:2:4 比例分为上、中、下三部分:')
-doc.add_paragraph()
-heading3('上部(占4)— 设备分类环形图')
-bullet('使用环形图展示所有实验室设备的分类分布情况。')
-bullet('右侧显示图例:各设备分类的颜色圆点标识、分类名称及数量(台)。')
-body('设备分类示例:', indent=1)
-make_table(
-    ['分类', '数量(台)'],
-    [
-        ['检测设备', '680'],
-        ['分析仪器', '520'],
-        ['制备设备', '380'],
-        ['安全设备', '280'],
-        ['辅助设备', '240'],
-        ['其他', '358'],
-    ],
-    col_widths=[7, 8.5]
-)
-
-heading3('中部(占2)— 汇总统计')
-make_table(
-    ['指标', '说明'],
-    [
-        ['设备总数', '所有实验室设备总数(示例:2,458台)'],
-        ['使用总时长', '累计设备使用时长(示例:18,620小时)'],
-        ['设备使用率', '当前设备使用率(示例:62.4%)'],
-    ],
-    col_widths=[4, 11.5]
-)
-
-heading3('下部(占4)— 设备状态饼图')
-bullet('使用饼图统计各使用状态的设备数量及占比。')
-bullet('右侧显示四种状态的颜色圆点标识及对应数量(台)。')
-make_table(
-    ['状态', '说明', '示例数量'],
-    [
-        ['使用', '当前正在使用中的设备', '486 台'],
-        ['空闲', '空置未使用的设备', '1,840 台'],
-        ['正常', '状态正常待命的设备', '98 台'],
-        ['维修', '正在维修或故障设备', '34 台'],
-    ],
-    col_widths=[3, 8, 4.5]
-)
-
-divider()
-
-# ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
-# 8. 3区
-# ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
-heading1('8. 3区 — 实时监控')
-body('3区是大屏中央最大区域(比例占4.5),固定显示实时监控视图,分为左侧面板和右侧画面区两部分,无需切换。')
-
-heading2('8.1 左侧面板(功能操作区)')
-make_table(
-    ['功能组件', '说明'],
-    [
-        ['搜索框', '可输入关键词搜索楼栋、楼层或房间名称'],
-        ['二级单位下拉筛选', '下拉选择框,可按化学研究所/物理研究所等单位进行筛选过滤'],
-        ['建筑结构树状图', '可展开/折叠的树形结构,层级为:院区 > 楼栋 > 楼层 > 房间\n点击节点可选中并高亮对应监控画面'],
-    ],
-    col_widths=[4, 11.5]
-)
-
-heading2('8.2 右侧画面区(9宫格监控)')
-bullet('标题行:左侧显示当前位置面包屑导航(院区 › 楼栋名称 › 楼层),右侧显示翻页按钮(‹ / ›)及页码信息。')
-bullet('9宫格画面:以3×3网格排列9路摄像头画面(16:9比例),每路画面使用Canvas模拟实时视频。')
-bullet('第一路摄像头(AI摄像头)特殊标注:')
-bullet('顶部左角:绿色"🤖 AI检测"徽标', 1)
-bullet('顶部右角:红色 REC 录制指示灯(闪烁)', 1)
-bullet('画面中叠加红色危险行为检测框及标签(如"危险行为: 未佩戴防护")', 1)
-bullet('其余8路摄像头显示常规画面(带室内场景模拟)。')
-
-note('9宫格摄像头画面为Canvas模拟渲染,不接入真实摄像头信号,如需接入真实视频流请联系IT运维人员进行配置。')
-divider()
-
-# ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
-# 9. 4区
-# ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
-heading1('9. 4区 — 实验环境感知与风险预警')
-body('4区位于大屏最右侧,由上至下按7:3比例划分为两个功能模块。')
-
-heading2('9.1 实验环境安全智能感知(占7)')
-body('本模块展示每间实验室硬件传感器的实时监测数据,区域内由下向上自动滚动展示所有实验室条目(30秒一循环,鼠标悬停自动暂停)。')
-doc.add_paragraph()
-heading3('每条实验室条目包含以下信息:')
-make_table(
-    ['信息字段', '说明', '示例'],
-    [
-        ['实验室名称', '实验室中文名称(房号)', '化学分析实验室(A301)'],
-        ['所属单位', '二级单位名称', '化学研究所'],
-        ['告警状态', '正常/告警指示图标及文字', '● 正常 / 🚨 告警(闪烁)'],
-        ['温度 T', '当前室内温度(°C)', '22.5°C'],
-        ['湿度 H', '当前室内相对湿度(%RH)', '58%'],
-        ['TVOC', '总挥发性有机化合物浓度(mg/m³)\n超标(>0.6)时标红闪烁', '0.82 mg/m³(红色告警)'],
-        ['CO₂', '二氧化碳浓度(ppm)\n超标(>700)时标红闪烁', '650 ppm'],
-        ['O₂', '氧气浓度(%)', '20.9%'],
-    ],
-    col_widths=[3, 7, 5.5]
-)
-
-body('当某传感器数值超出阈值时,该实验室条目整体高亮为红色渐变效果,并触发全屏预警弹窗(详见第10章)。')
-
-divider()
-heading2('9.2 实验室实时风险预警(占3)')
-body('本模块展示本月预警响应总数及实时预警通知轮播(22秒一循环,最新通知始终显示在最上方)。')
-doc.add_paragraph()
-heading3('顶部统计')
-bullet('本月预警响应总数:以琥珀色/橙黄色高亮数字显示(示例:42次)。')
-
-heading3('预警通知列表(每条包含)')
-make_table(
-    ['字段', '说明'],
-    [
-        ['实验室信息', '实验室名称(房号)- 所属二级单位'],
-        ['异常指标', '异常传感器类型及当前超标值'],
-        ['预警时间', '精确到秒的时间戳(日期-时-分-秒)'],
-    ],
-    col_widths=[4, 11.5]
-)
-
-note('预警区域字体采用琥珀/橙色调(#fcd34d / #fb923c),不使用红色字体,区别于传感器告警区域。')
-divider()
-
-# ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
-# 10. 全屏预警弹窗
-# ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
-heading1('10. 全屏预警弹窗')
-body('当传感器监测数据超出设定阈值,系统将自动弹出全屏预警弹窗(系统启动约5秒后自动触发演示),以科幻电影系统报警样式呈现。')
-doc.add_paragraph()
-
-heading2('弹窗布局')
-body('弹窗分为左右两个区域:')
-make_table(
-    ['区域', '内容', '说明'],
-    [
-        ['左侧', '告警实验室实时监控画面\n(900×700 Canvas模拟)', '带红色扫描线、噪点效果;\n叠加AI危险行为检测框;\n顶部显示摄像头编号、REC录制状态;\n底部显示实验室名称及AI检测标识'],
-        ['右侧', '告警详细信息', '告警实验室、所属单位、告警指标、当前值/安全阈值四项信息卡片;\n底部显示紧急疏散提示文字(闪烁动效)'],
-    ],
-    col_widths=[2, 5, 8.5]
-)
-
-heading2('弹窗操作按钮')
-make_table(
-    ['按钮', '功能'],
-    [
-        ['稍后处理', '关闭弹窗,暂时忽略告警(后续可在4区预警列表查看)'],
-        ['确认处理', '关闭弹窗,标记告警已处理'],
-    ],
-    col_widths=[4, 11.5]
-)
-
-heading2('弹窗视觉特征')
-bullet('全屏深红色半透明遮罩,背景模糊(backdrop-filter: blur)。')
-bullet('弹窗主体为深红渐变边框,红色扫描线从上到下持续扫描(1.5秒一循环)。')
-bullet('标题"⚡ 系统预警 · ALERT"红色字体,💥 图标快速闪烁动效。')
-
-note('弹窗为全屏展示,关闭弹窗后系统恢复正常监控状态,预警信息仍保留在4区实时风险预警列表中。')
-divider()
-
-# ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
-# 11. 通用交互说明
-# ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
-heading1('11. 通用交互说明')
-make_table(
-    ['交互行为', '触发方式', '效果说明'],
-    [
-        ['全屏显示', '页面加载时自动触发', '请求浏览器进入全屏模式(F11手动切换亦可)'],
-        ['图表悬停提示', '鼠标悬停在图表区域', '显示详细数据悬浮提示框(Tooltip)'],
-        ['安全分级柱状图滚动', '自动(每5秒)', '自动向左滚动1个X轴单位,循环展示'],
-        ['传感器列表滚动', '自动(30秒一循环)', '由下向上平滑滚动,鼠标悬停可暂停'],
-        ['预警通知滚动', '自动(22秒一循环)', '由下向上平滑滚动,鼠标悬停可暂停'],
-        ['树状图展开/折叠', '点击监控视图左侧树状图节点', '展开或折叠对应子节点'],
-        ['关闭预警弹窗', '点击"确认处理"或"稍后处理"按钮', '关闭全屏预警弹窗,恢复正常显示'],
-    ],
-    col_widths=[4, 5, 6.5]
-)
-divider()
-
-# ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
-# 12. 数据说明
-# ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
-heading1('12. 数据说明')
-body('当前版本大屏展示的数据均为内置模拟数据(Demo数据),用于系统功能演示。如需接入真实数据,请由IT运维人员按照以下说明进行后端数据对接。')
-doc.add_paragraph()
-
-heading2('传感器告警阈值(默认配置)')
-make_table(
-    ['传感器指标', '正常范围', '告警阈值', '单位'],
-    [
-        ['TVOC', '< 0.6', '≥ 0.6', 'mg/m³'],
-        ['CO₂', '< 700', '≥ 700', 'ppm'],
-        ['O₂', '19.5 ~ 23.5', '< 19.5 或 > 23.5', '%'],
-        ['温度', '16 ~ 28', '< 16 或 > 28', '°C'],
-        ['湿度', '30 ~ 80', '< 30 或 > 80', '% RH'],
-    ],
-    col_widths=[4, 4, 5, 2.5]
-)
-
-heading2('数据刷新说明')
-make_table(
-    ['数据模块', '刷新方式'],
-    [
-        ['实时时钟', '每秒刷新'],
-        ['数字翻牌器(进入人数)', '页面加载时动画计数(2秒内完成)'],
-        ['传感器数据列表', '页面加载时渲染(静态模拟数据)'],
-        ['风险预警列表', '页面加载时渲染(静态模拟数据)'],
-        ['ECharts图表', '页面加载时初始化渲染(静态模拟数据)'],
-        ['柱状图自动滚动', '每5秒自动切换1个X轴步长'],
-    ],
-    col_widths=[6, 9.5]
-)
-
-divider()
-
-# ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
-# 13. FAQ
-# ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
-heading1('13. 常见问题(FAQ)')
-
-faqs = [
-    ('Q1:打开页面后图表区域空白,没有显示图表?',
-     '请检查网络连接是否正常,确保可以访问 cdn.jsdelivr.net(ECharts CDN地址)。如无外网访问条件,请将 ECharts JS 文件下载至本地,并将 HTML 文件头部的 CDN 引用路径改为本地相对路径。'),
-    ('Q2:页面一打开就弹出了预警弹窗,如何关闭?',
-     '这是系统的演示效果,页面加载约5秒后会自动弹出全屏预警弹窗。点击弹窗右下角的"确认处理"或"稍后处理"按钮即可关闭。'),
-    ('Q3:3区实时监控画面不显示或摄像头为黑屏?',
-     '9宫格摄像头画面为Canvas模拟渲染,页面加载完成后自动初始化。若画面为黑屏,请刷新页面后等待3~5秒。如需接入真实摄像头,请联系IT运维人员进行后端视频流配置。'),
-    ('Q4:页面内容显示不完整或过小,无法正常查看?',
-     '本大屏设计分辨率为 9600×2800px,需配合超大屏/拼接屏使用。普通1080p显示器需通过浏览器滚动查看完整内容,或将浏览器缩放比例调整为25%~33%以适配屏幕。'),
-    ('Q5:9宫格监控画面是否为真实摄像头接入?',
-     '当前版本为Canvas模拟画面,不接入真实摄像头信号。如需接入真实CCTV/RTSP视频流,需进行后端视频推流对接开发,请联系IT运维人员处理。'),
-    ('Q6:传感器数据是否实时更新?',
-     '当前版本数据为静态模拟数据,页面加载后不会自动刷新传感器数值。如需接入真实IoT传感器数据,需配置WebSocket或轮询接口,由IT运维人员进行后端集成开发。'),
-    ('Q7:如何修改告警阈值?',
-     '当前阈值配置在 index-v2.html 的 JavaScript 代码中(LABS数组及判断逻辑),修改对应字段的判断条件即可。生产环境建议将阈值配置抽取为后端可配置参数。'),
-]
-
-for q, a in faqs:
-    p = doc.add_paragraph()
-    p.paragraph_format.space_before = Pt(8)
-    p.paragraph_format.space_after  = Pt(2)
-    add_run(p, q, bold=True, size=11, color=C_H2)
-    p2 = doc.add_paragraph()
-    p2.paragraph_format.space_before = Pt(2)
-    p2.paragraph_format.space_after  = Pt(6)
-    p2.paragraph_format.left_indent  = Cm(0.5)
-    add_run(p2, 'A:' + a, size=10.5, color=C_BODY)
-
-divider()
-
-# ── 页脚说明 ─────────────────────────────────────────────────────
-doc.add_paragraph()
-p = doc.add_paragraph()
-p.alignment = WD_ALIGN_PARAGRAPH.CENTER
-add_run(p, '中国安全生产科学研究院  ·  实验室安全智能监测与管控中心  ·  使用说明文档 V1.0', size=9, color=C_LABEL)
-p = doc.add_paragraph()
-p.alignment = WD_ALIGN_PARAGRAPH.CENTER
-add_run(p, '本文档仅供内部使用,未经许可不得对外发布。', size=9, italic=True, color=C_LABEL)
-
-# ── 保存 ────────────────────────────────────────────────────────
-out = r'C:\Users\Administrator\lab-safety-monitor\实验室安全大屏使用说明.docx'
-doc.save(out)
-print(f'已保存:{out}')

+ 0 - 166
prototype-ui/generate_doc.py

@@ -1,166 +0,0 @@
-# -*- coding: utf-8 -*-
-from docx import Document
-from docx.shared import Pt, RGBColor, Cm, Inches
-from docx.enum.text import WD_ALIGN_PARAGRAPH
-from docx.oxml.ns import qn
-from docx.oxml import OxmlElement
-
-def set_heading_style(paragraph, level=1):
-    run = paragraph.runs[0] if paragraph.runs else paragraph.add_run()
-    if level == 1:
-        run.font.size = Pt(18)
-        run.font.bold = True
-        run.font.color.rgb = RGBColor(0x1e, 0x40, 0x7e)
-    elif level == 2:
-        run.font.size = Pt(14)
-        run.font.bold = True
-        run.font.color.rgb = RGBColor(0x1a, 0x5a, 0xa0)
-    paragraph.paragraph_format.space_before = Pt(12)
-    paragraph.paragraph_format.space_after = Pt(4)
-
-def add_divider(doc):
-    p = doc.add_paragraph()
-    pPr = p._p.get_or_add_pPr()
-    pBdr = OxmlElement('w:pBdr')
-    bottom = OxmlElement('w:bottom')
-    bottom.set(qn('w:val'), 'single')
-    bottom.set(qn('w:sz'), '4')
-    bottom.set(qn('w:space'), '1')
-    bottom.set(qn('w:color'), 'B0C4DE')
-    pBdr.append(bottom)
-    pPr.append(pBdr)
-    p.paragraph_format.space_after = Pt(6)
-
-doc = Document()
-
-# 页面边距
-sections = doc.sections
-for section in sections:
-    section.top_margin = Cm(2.5)
-    section.bottom_margin = Cm(2.5)
-    section.left_margin = Cm(3)
-    section.right_margin = Cm(3)
-
-# 标题
-title = doc.add_heading('实验室安全智能监测与管控中心', 0)
-title.alignment = WD_ALIGN_PARAGRAPH.CENTER
-title_run = title.runs[0]
-title_run.font.size = Pt(22)
-title_run.font.color.rgb = RGBColor(0x0d, 0x2a, 0x6e)
-
-subtitle = doc.add_paragraph('可视化大屏模块说明文档')
-subtitle.alignment = WD_ALIGN_PARAGRAPH.CENTER
-subtitle_run = subtitle.runs[0]
-subtitle_run.font.size = Pt(13)
-subtitle_run.font.color.rgb = RGBColor(0x55, 0x77, 0x99)
-doc.add_paragraph()
-
-add_divider(doc)
-
-# ====== 模块列表 ======
-modules = [
-    {
-        "title": "一、顶部导航栏",
-        "content": (
-            "导航栏位于大屏顶部,横贯全屏,分为左、中、右三个区域。"
-            "左侧实时显示当前时间(时:分:秒)及日期、星期;"
-            "中间居中展示机构LOGO、单位名称(中国安全生产科学研究院)及系统大标题"实验室安全智能监测与管控中心";"
-            "右侧显示所在城市的天气状况、实时温度及空气质量指数(AQI),为用户提供环境背景信息参考。"
-        )
-    },
-    {
-        "title": "二、实验室基本情况统计",
-        "content": (
-            "本模块综合呈现全院实验室的整体规模与运行状态。"
-            "上部左侧以SVG圆弧仪表图直观显示实验室总数(128间),并在图形下方配以I~IV级对应红橙黄蓝的分级比例色条;"
-            "上部右侧采用环形图(donut chart)展示各安全等级实验室的占比与数量,圆心标注总数,外侧标注各级名称、数量及百分比,并附彩色明细行辅助阅读。"
-            "下部通过三栏状态徽章实时显示"使用中""异常""空闲"三种状态下的实验室数量,便于管理人员快速掌握当前资源利用情况。"
-        )
-    },
-    {
-        "title": "三、实验室安全分级统计",
-        "content": (
-            "本模块以堆叠柱状图的形式,统计并展示院内各二级单位的实验室总数及各安全等级(I~IV级)分布情况。"
-            "X轴为各二级单位名称,轴标签下方附注该单位实验室总数;Y轴为实验室数量;图例采用红、橙、黄、蓝四色区分等级。"
-            "当二级单位数量超出可视范围时,图表将自动从右向左循环滚动,每隔5秒滚动一列,确保所有数据均可完整呈现。"
-        )
-    },
-    {
-        "title": "四、实验室进入人数统计及走势",
-        "content": (
-            "本模块以数字翻牌器的形式,醒目展示今日累计进入实验室总人数及当前仍在实验室内的实验人数,两组数据并排一行,动态翻转效果直观呈现数据变化。"
-            "下方折线图按0时至24时划分为9个时间分段,分别绘制当天实验室进入人数及当前实验人数的动态走势曲线,帮助管理员掌握人员流动规律与高峰时段分布。"
-        )
-    },
-    {
-        "title": "五、智能环境感知应用设备统计",
-        "content": (
-            "本模块集中展示院内各类智能物联感知设备的部署与在线状况。"
-            "顶部以图标+数字的形式,分类显示各类设备的在线数量与离线数量;"
-            "左侧以速度仪表盘样式的图表直观呈现设备整体在线率百分比;"
-            "右侧以2×2网格布局分别显示电子信息铭牌、化学品智能终端、传感器套件、智能摄像设备的设备总数,为运维管理提供快速数据参考。"
-        )
-    },
-    {
-        "title": "六、实验室设备分类及使用统计",
-        "content": (
-            "本模块按上中下三段式布局,对实验室设备进行多维度统计分析。"
-            "上部使用环形图展示全院实验室设备的分类构成,图右侧配有各分类的色块标识、名称与数量(台);"
-            "中部汇总显示设备总数、设备累计使用总时长及设备使用率三项核心指标,便于评估设备利用效率;"
-            "下部使用饼图展示设备按使用状态(使用中、空闲、正常、维修)的分布数量及占比,右侧同步附注各状态的色块与数量说明,辅助设备管理决策。"
-        )
-    },
-    {
-        "title": "七、实时监控",
-        "content": (
-            "本模块为用户提供实验室实时视频监控查看功能,区域分为左右两个内容区。"
-            "左侧提供搜索框(可按楼栋、楼层检索)、二级单位下拉筛选,以及可展开折叠的建筑结构树状图(层级为院区→楼栋→楼层→房间),便于快速定位目标实验室。"
-            "右侧以9宫格(16:9比例)方式同屏显示9路实验室实时监控画面,其中首路画面来自智能摄像头,支持危险行为AI检测框标注;顶部显示当前院区/楼栋/楼层层级路径,并提供翻页按钮以切换查看更多房间画面。"
-        )
-    },
-    {
-        "title": "八、实验环境安全智能感知",
-        "content": (
-            "本模块实时展示全院各实验室的环境安全监测数据,由下而上滚动播放所有实验室条目。"
-            "每条实验室信息包括实验室名称(含房号)及所属二级单位,并以图标形式列出各传感器(温度、湿度、TVOC、CO₂、O₂等)的实时状态数值。"
-            "当某项监测值超出安全阈值时,对应条目以红色渐变高亮效果突出显示,并触发预警图标,使管理人员能第一时间识别异常实验室及异常指标,及时响应处置。"
-        )
-    },
-    {
-        "title": "九、实验室实时风险预警",
-        "content": (
-            "本模块集中呈现实验室环境异常预警信息,顶部以琥珀色高亮数字统计展示本月预警响应总数。"
-            "预警列表区域按时间倒序实时滚动轮播最新异常通知,最新一条置顶显示。"
-            "每条预警记录包括:实验室名称(含房号)及所属二级单位、触发预警的传感器类型及对应异常数值、精确到秒的预警时间(日期-时-分-秒)。"
-            "整体配色以琥珀/橙色调为主,视觉上区别于紧急红色,使管理人员在持续关注时不产生视觉疲劳,同时保持较强的信息提示性。"
-        )
-    },
-]
-
-for module in modules:
-    h = doc.add_heading(module["title"], level=1)
-    h.alignment = WD_ALIGN_PARAGRAPH.LEFT
-    set_heading_style(h, level=1)
-
-    p = doc.add_paragraph(module["content"])
-    p.alignment = WD_ALIGN_PARAGRAPH.JUSTIFY
-    run = p.runs[0]
-    run.font.size = Pt(11)
-    run.font.color.rgb = RGBColor(0x22, 0x22, 0x22)
-    p.paragraph_format.first_line_indent = Pt(22)
-    p.paragraph_format.line_spacing = Pt(22)
-    p.paragraph_format.space_after = Pt(6)
-
-    add_divider(doc)
-
-# 页脚备注
-doc.add_paragraph()
-note = doc.add_paragraph('注:本文档为实验室安全智能监测与管控中心可视化大屏各数据统计模块的功能说明,仅作文字描述参考,不含设计原型图。')
-note.alignment = WD_ALIGN_PARAGRAPH.LEFT
-note_run = note.runs[0]
-note_run.font.size = Pt(9)
-note_run.font.color.rgb = RGBColor(0x99, 0x99, 0x99)
-note_run.font.italic = True
-
-doc.save('实验室安全智能监测与管控中心-可视化大屏模块说明.docx')
-print("文档已生成:实验室安全智能监测与管控中心-可视化大屏模块说明.docx")

Plik diff jest za duży
+ 0 - 1886
prototype-ui/index-v2.html


Plik diff jest za duży
+ 0 - 38
prototype-ui/modules.json


+ 0 - 930
prototype-ui/package-lock.json

@@ -1,930 +0,0 @@
-{
-  "name": "lab-safety-monitor",
-  "lockfileVersion": 3,
-  "requires": true,
-  "packages": {
-    "": {
-      "dependencies": {
-        "puppeteer-core": "^24.38.0"
-      }
-    },
-    "node_modules/@puppeteer/browsers": {
-      "version": "2.13.0",
-      "resolved": "https://registry.npmjs.org/@puppeteer/browsers/-/browsers-2.13.0.tgz",
-      "integrity": "sha512-46BZJYJjc/WwmKjsvDFykHtXrtomsCIrwYQPOP7VfMJoZY2bsDF9oROBABR3paDjDcmkUye1Pb1BqdcdiipaWA==",
-      "license": "Apache-2.0",
-      "dependencies": {
-        "debug": "^4.4.3",
-        "extract-zip": "^2.0.1",
-        "progress": "^2.0.3",
-        "proxy-agent": "^6.5.0",
-        "semver": "^7.7.4",
-        "tar-fs": "^3.1.1",
-        "yargs": "^17.7.2"
-      },
-      "bin": {
-        "browsers": "lib/cjs/main-cli.js"
-      },
-      "engines": {
-        "node": ">=18"
-      }
-    },
-    "node_modules/@tootallnate/quickjs-emscripten": {
-      "version": "0.23.0",
-      "resolved": "https://registry.npmjs.org/@tootallnate/quickjs-emscripten/-/quickjs-emscripten-0.23.0.tgz",
-      "integrity": "sha512-C5Mc6rdnsaJDjO3UpGW/CQTHtCKaYlScZTly4JIu97Jxo/odCiH0ITnDXSJPTOrEKk/ycSZ0AOgTmkDtkOsvIA==",
-      "license": "MIT"
-    },
-    "node_modules/@types/node": {
-      "version": "25.3.5",
-      "resolved": "https://registry.npmjs.org/@types/node/-/node-25.3.5.tgz",
-      "integrity": "sha512-oX8xrhvpiyRCQkG1MFchB09f+cXftgIXb3a7UUa4Y3wpmZPw5tyZGTLWhlESOLq1Rq6oDlc8npVU2/9xiCuXMA==",
-      "license": "MIT",
-      "optional": true,
-      "dependencies": {
-        "undici-types": "~7.18.0"
-      }
-    },
-    "node_modules/@types/yauzl": {
-      "version": "2.10.3",
-      "resolved": "https://registry.npmjs.org/@types/yauzl/-/yauzl-2.10.3.tgz",
-      "integrity": "sha512-oJoftv0LSuaDZE3Le4DbKX+KS9G36NzOeSap90UIK0yMA/NhKJhqlSGtNDORNRaIbQfzjXDrQa0ytJ6mNRGz/Q==",
-      "license": "MIT",
-      "optional": true,
-      "dependencies": {
-        "@types/node": "*"
-      }
-    },
-    "node_modules/agent-base": {
-      "version": "7.1.4",
-      "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.4.tgz",
-      "integrity": "sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ==",
-      "license": "MIT",
-      "engines": {
-        "node": ">= 14"
-      }
-    },
-    "node_modules/ansi-regex": {
-      "version": "5.0.1",
-      "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz",
-      "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==",
-      "license": "MIT",
-      "engines": {
-        "node": ">=8"
-      }
-    },
-    "node_modules/ansi-styles": {
-      "version": "4.3.0",
-      "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz",
-      "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==",
-      "license": "MIT",
-      "dependencies": {
-        "color-convert": "^2.0.1"
-      },
-      "engines": {
-        "node": ">=8"
-      },
-      "funding": {
-        "url": "https://github.com/chalk/ansi-styles?sponsor=1"
-      }
-    },
-    "node_modules/ast-types": {
-      "version": "0.13.4",
-      "resolved": "https://registry.npmjs.org/ast-types/-/ast-types-0.13.4.tgz",
-      "integrity": "sha512-x1FCFnFifvYDDzTaLII71vG5uvDwgtmDTEVWAxrgeiR8VjMONcCXJx7E+USjDtHlwFmt9MysbqgF9b9Vjr6w+w==",
-      "license": "MIT",
-      "dependencies": {
-        "tslib": "^2.0.1"
-      },
-      "engines": {
-        "node": ">=4"
-      }
-    },
-    "node_modules/b4a": {
-      "version": "1.8.0",
-      "resolved": "https://registry.npmjs.org/b4a/-/b4a-1.8.0.tgz",
-      "integrity": "sha512-qRuSmNSkGQaHwNbM7J78Wwy+ghLEYF1zNrSeMxj4Kgw6y33O3mXcQ6Ie9fRvfU/YnxWkOchPXbaLb73TkIsfdg==",
-      "license": "Apache-2.0",
-      "peerDependencies": {
-        "react-native-b4a": "*"
-      },
-      "peerDependenciesMeta": {
-        "react-native-b4a": {
-          "optional": true
-        }
-      }
-    },
-    "node_modules/bare-events": {
-      "version": "2.8.2",
-      "resolved": "https://registry.npmjs.org/bare-events/-/bare-events-2.8.2.tgz",
-      "integrity": "sha512-riJjyv1/mHLIPX4RwiK+oW9/4c3TEUeORHKefKAKnZ5kyslbN+HXowtbaVEqt4IMUB7OXlfixcs6gsFeo/jhiQ==",
-      "license": "Apache-2.0",
-      "peerDependencies": {
-        "bare-abort-controller": "*"
-      },
-      "peerDependenciesMeta": {
-        "bare-abort-controller": {
-          "optional": true
-        }
-      }
-    },
-    "node_modules/bare-fs": {
-      "version": "4.5.5",
-      "resolved": "https://registry.npmjs.org/bare-fs/-/bare-fs-4.5.5.tgz",
-      "integrity": "sha512-XvwYM6VZqKoqDll8BmSww5luA5eflDzY0uEFfBJtFKe4PAAtxBjU3YIxzIBzhyaEQBy1VXEQBto4cpN5RZJw+w==",
-      "license": "Apache-2.0",
-      "dependencies": {
-        "bare-events": "^2.5.4",
-        "bare-path": "^3.0.0",
-        "bare-stream": "^2.6.4",
-        "bare-url": "^2.2.2",
-        "fast-fifo": "^1.3.2"
-      },
-      "engines": {
-        "bare": ">=1.16.0"
-      },
-      "peerDependencies": {
-        "bare-buffer": "*"
-      },
-      "peerDependenciesMeta": {
-        "bare-buffer": {
-          "optional": true
-        }
-      }
-    },
-    "node_modules/bare-os": {
-      "version": "3.7.1",
-      "resolved": "https://registry.npmjs.org/bare-os/-/bare-os-3.7.1.tgz",
-      "integrity": "sha512-ebvMaS5BgZKmJlvuWh14dg9rbUI84QeV3WlWn6Ph6lFI8jJoh7ADtVTyD2c93euwbe+zgi0DVrl4YmqXeM9aIA==",
-      "license": "Apache-2.0",
-      "engines": {
-        "bare": ">=1.14.0"
-      }
-    },
-    "node_modules/bare-path": {
-      "version": "3.0.0",
-      "resolved": "https://registry.npmjs.org/bare-path/-/bare-path-3.0.0.tgz",
-      "integrity": "sha512-tyfW2cQcB5NN8Saijrhqn0Zh7AnFNsnczRcuWODH0eYAXBsJ5gVxAUuNr7tsHSC6IZ77cA0SitzT+s47kot8Mw==",
-      "license": "Apache-2.0",
-      "dependencies": {
-        "bare-os": "^3.0.1"
-      }
-    },
-    "node_modules/bare-stream": {
-      "version": "2.8.0",
-      "resolved": "https://registry.npmjs.org/bare-stream/-/bare-stream-2.8.0.tgz",
-      "integrity": "sha512-reUN0M2sHRqCdG4lUK3Fw8w98eeUIZHL5c3H7Mbhk2yVBL+oofgaIp0ieLfD5QXwPCypBpmEEKU2WZKzbAk8GA==",
-      "license": "Apache-2.0",
-      "dependencies": {
-        "streamx": "^2.21.0",
-        "teex": "^1.0.1"
-      },
-      "peerDependencies": {
-        "bare-buffer": "*",
-        "bare-events": "*"
-      },
-      "peerDependenciesMeta": {
-        "bare-buffer": {
-          "optional": true
-        },
-        "bare-events": {
-          "optional": true
-        }
-      }
-    },
-    "node_modules/bare-url": {
-      "version": "2.3.2",
-      "resolved": "https://registry.npmjs.org/bare-url/-/bare-url-2.3.2.tgz",
-      "integrity": "sha512-ZMq4gd9ngV5aTMa5p9+UfY0b3skwhHELaDkhEHetMdX0LRkW9kzaym4oo/Eh+Ghm0CCDuMTsRIGM/ytUc1ZYmw==",
-      "license": "Apache-2.0",
-      "dependencies": {
-        "bare-path": "^3.0.0"
-      }
-    },
-    "node_modules/basic-ftp": {
-      "version": "5.2.0",
-      "resolved": "https://registry.npmjs.org/basic-ftp/-/basic-ftp-5.2.0.tgz",
-      "integrity": "sha512-VoMINM2rqJwJgfdHq6RiUudKt2BV+FY5ZFezP/ypmwayk68+NzzAQy4XXLlqsGD4MCzq3DrmNFD/uUmBJuGoXw==",
-      "license": "MIT",
-      "engines": {
-        "node": ">=10.0.0"
-      }
-    },
-    "node_modules/buffer-crc32": {
-      "version": "0.2.13",
-      "resolved": "https://registry.npmjs.org/buffer-crc32/-/buffer-crc32-0.2.13.tgz",
-      "integrity": "sha512-VO9Ht/+p3SN7SKWqcrgEzjGbRSJYTx+Q1pTQC0wrWqHx0vpJraQ6GtHx8tvcg1rlK1byhU5gccxgOgj7B0TDkQ==",
-      "license": "MIT",
-      "engines": {
-        "node": "*"
-      }
-    },
-    "node_modules/chromium-bidi": {
-      "version": "14.0.0",
-      "resolved": "https://registry.npmjs.org/chromium-bidi/-/chromium-bidi-14.0.0.tgz",
-      "integrity": "sha512-9gYlLtS6tStdRWzrtXaTMnqcM4dudNegMXJxkR0I/CXObHalYeYcAMPrL19eroNZHtJ8DQmu1E+ZNOYu/IXMXw==",
-      "license": "Apache-2.0",
-      "dependencies": {
-        "mitt": "^3.0.1",
-        "zod": "^3.24.1"
-      },
-      "peerDependencies": {
-        "devtools-protocol": "*"
-      }
-    },
-    "node_modules/cliui": {
-      "version": "8.0.1",
-      "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz",
-      "integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==",
-      "license": "ISC",
-      "dependencies": {
-        "string-width": "^4.2.0",
-        "strip-ansi": "^6.0.1",
-        "wrap-ansi": "^7.0.0"
-      },
-      "engines": {
-        "node": ">=12"
-      }
-    },
-    "node_modules/color-convert": {
-      "version": "2.0.1",
-      "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
-      "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==",
-      "license": "MIT",
-      "dependencies": {
-        "color-name": "~1.1.4"
-      },
-      "engines": {
-        "node": ">=7.0.0"
-      }
-    },
-    "node_modules/color-name": {
-      "version": "1.1.4",
-      "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz",
-      "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==",
-      "license": "MIT"
-    },
-    "node_modules/data-uri-to-buffer": {
-      "version": "6.0.2",
-      "resolved": "https://registry.npmjs.org/data-uri-to-buffer/-/data-uri-to-buffer-6.0.2.tgz",
-      "integrity": "sha512-7hvf7/GW8e86rW0ptuwS3OcBGDjIi6SZva7hCyWC0yYry2cOPmLIjXAUHI6DK2HsnwJd9ifmt57i8eV2n4YNpw==",
-      "license": "MIT",
-      "engines": {
-        "node": ">= 14"
-      }
-    },
-    "node_modules/debug": {
-      "version": "4.4.3",
-      "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz",
-      "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==",
-      "license": "MIT",
-      "dependencies": {
-        "ms": "^2.1.3"
-      },
-      "engines": {
-        "node": ">=6.0"
-      },
-      "peerDependenciesMeta": {
-        "supports-color": {
-          "optional": true
-        }
-      }
-    },
-    "node_modules/degenerator": {
-      "version": "5.0.1",
-      "resolved": "https://registry.npmjs.org/degenerator/-/degenerator-5.0.1.tgz",
-      "integrity": "sha512-TllpMR/t0M5sqCXfj85i4XaAzxmS5tVA16dqvdkMwGmzI+dXLXnw3J+3Vdv7VKw+ThlTMboK6i9rnZ6Nntj5CQ==",
-      "license": "MIT",
-      "dependencies": {
-        "ast-types": "^0.13.4",
-        "escodegen": "^2.1.0",
-        "esprima": "^4.0.1"
-      },
-      "engines": {
-        "node": ">= 14"
-      }
-    },
-    "node_modules/devtools-protocol": {
-      "version": "0.0.1581282",
-      "resolved": "https://registry.npmjs.org/devtools-protocol/-/devtools-protocol-0.0.1581282.tgz",
-      "integrity": "sha512-nv7iKtNZQshSW2hKzYNr46nM/Cfh5SEvE2oV0/SEGgc9XupIY5ggf84Cz8eJIkBce7S3bmTAauFD6aysMpnqsQ==",
-      "license": "BSD-3-Clause",
-      "peer": true
-    },
-    "node_modules/emoji-regex": {
-      "version": "8.0.0",
-      "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz",
-      "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==",
-      "license": "MIT"
-    },
-    "node_modules/end-of-stream": {
-      "version": "1.4.5",
-      "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.5.tgz",
-      "integrity": "sha512-ooEGc6HP26xXq/N+GCGOT0JKCLDGrq2bQUZrQ7gyrJiZANJ/8YDTxTpQBXGMn+WbIQXNVpyWymm7KYVICQnyOg==",
-      "license": "MIT",
-      "dependencies": {
-        "once": "^1.4.0"
-      }
-    },
-    "node_modules/escalade": {
-      "version": "3.2.0",
-      "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz",
-      "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==",
-      "license": "MIT",
-      "engines": {
-        "node": ">=6"
-      }
-    },
-    "node_modules/escodegen": {
-      "version": "2.1.0",
-      "resolved": "https://registry.npmjs.org/escodegen/-/escodegen-2.1.0.tgz",
-      "integrity": "sha512-2NlIDTwUWJN0mRPQOdtQBzbUHvdGY2P1VXSyU83Q3xKxM7WHX2Ql8dKq782Q9TgQUNOLEzEYu9bzLNj1q88I5w==",
-      "license": "BSD-2-Clause",
-      "dependencies": {
-        "esprima": "^4.0.1",
-        "estraverse": "^5.2.0",
-        "esutils": "^2.0.2"
-      },
-      "bin": {
-        "escodegen": "bin/escodegen.js",
-        "esgenerate": "bin/esgenerate.js"
-      },
-      "engines": {
-        "node": ">=6.0"
-      },
-      "optionalDependencies": {
-        "source-map": "~0.6.1"
-      }
-    },
-    "node_modules/esprima": {
-      "version": "4.0.1",
-      "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz",
-      "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==",
-      "license": "BSD-2-Clause",
-      "bin": {
-        "esparse": "bin/esparse.js",
-        "esvalidate": "bin/esvalidate.js"
-      },
-      "engines": {
-        "node": ">=4"
-      }
-    },
-    "node_modules/estraverse": {
-      "version": "5.3.0",
-      "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz",
-      "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==",
-      "license": "BSD-2-Clause",
-      "engines": {
-        "node": ">=4.0"
-      }
-    },
-    "node_modules/esutils": {
-      "version": "2.0.3",
-      "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz",
-      "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==",
-      "license": "BSD-2-Clause",
-      "engines": {
-        "node": ">=0.10.0"
-      }
-    },
-    "node_modules/events-universal": {
-      "version": "1.0.1",
-      "resolved": "https://registry.npmjs.org/events-universal/-/events-universal-1.0.1.tgz",
-      "integrity": "sha512-LUd5euvbMLpwOF8m6ivPCbhQeSiYVNb8Vs0fQ8QjXo0JTkEHpz8pxdQf0gStltaPpw0Cca8b39KxvK9cfKRiAw==",
-      "license": "Apache-2.0",
-      "dependencies": {
-        "bare-events": "^2.7.0"
-      }
-    },
-    "node_modules/extract-zip": {
-      "version": "2.0.1",
-      "resolved": "https://registry.npmjs.org/extract-zip/-/extract-zip-2.0.1.tgz",
-      "integrity": "sha512-GDhU9ntwuKyGXdZBUgTIe+vXnWj0fppUEtMDL0+idd5Sta8TGpHssn/eusA9mrPr9qNDym6SxAYZjNvCn/9RBg==",
-      "license": "BSD-2-Clause",
-      "dependencies": {
-        "debug": "^4.1.1",
-        "get-stream": "^5.1.0",
-        "yauzl": "^2.10.0"
-      },
-      "bin": {
-        "extract-zip": "cli.js"
-      },
-      "engines": {
-        "node": ">= 10.17.0"
-      },
-      "optionalDependencies": {
-        "@types/yauzl": "^2.9.1"
-      }
-    },
-    "node_modules/fast-fifo": {
-      "version": "1.3.2",
-      "resolved": "https://registry.npmjs.org/fast-fifo/-/fast-fifo-1.3.2.tgz",
-      "integrity": "sha512-/d9sfos4yxzpwkDkuN7k2SqFKtYNmCTzgfEpz82x34IM9/zc8KGxQoXg1liNC/izpRM/MBdt44Nmx41ZWqk+FQ==",
-      "license": "MIT"
-    },
-    "node_modules/fd-slicer": {
-      "version": "1.1.0",
-      "resolved": "https://registry.npmjs.org/fd-slicer/-/fd-slicer-1.1.0.tgz",
-      "integrity": "sha512-cE1qsB/VwyQozZ+q1dGxR8LBYNZeofhEdUNGSMbQD3Gw2lAzX9Zb3uIU6Ebc/Fmyjo9AWWfnn0AUCHqtevs/8g==",
-      "license": "MIT",
-      "dependencies": {
-        "pend": "~1.2.0"
-      }
-    },
-    "node_modules/get-caller-file": {
-      "version": "2.0.5",
-      "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz",
-      "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==",
-      "license": "ISC",
-      "engines": {
-        "node": "6.* || 8.* || >= 10.*"
-      }
-    },
-    "node_modules/get-stream": {
-      "version": "5.2.0",
-      "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-5.2.0.tgz",
-      "integrity": "sha512-nBF+F1rAZVCu/p7rjzgA+Yb4lfYXrpl7a6VmJrU8wF9I1CKvP/QwPNZHnOlwbTkY6dvtFIzFMSyQXbLoTQPRpA==",
-      "license": "MIT",
-      "dependencies": {
-        "pump": "^3.0.0"
-      },
-      "engines": {
-        "node": ">=8"
-      },
-      "funding": {
-        "url": "https://github.com/sponsors/sindresorhus"
-      }
-    },
-    "node_modules/get-uri": {
-      "version": "6.0.5",
-      "resolved": "https://registry.npmjs.org/get-uri/-/get-uri-6.0.5.tgz",
-      "integrity": "sha512-b1O07XYq8eRuVzBNgJLstU6FYc1tS6wnMtF1I1D9lE8LxZSOGZ7LhxN54yPP6mGw5f2CkXY2BQUL9Fx41qvcIg==",
-      "license": "MIT",
-      "dependencies": {
-        "basic-ftp": "^5.0.2",
-        "data-uri-to-buffer": "^6.0.2",
-        "debug": "^4.3.4"
-      },
-      "engines": {
-        "node": ">= 14"
-      }
-    },
-    "node_modules/http-proxy-agent": {
-      "version": "7.0.2",
-      "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-7.0.2.tgz",
-      "integrity": "sha512-T1gkAiYYDWYx3V5Bmyu7HcfcvL7mUrTWiM6yOfa3PIphViJ/gFPbvidQ+veqSOHci/PxBcDabeUNCzpOODJZig==",
-      "license": "MIT",
-      "dependencies": {
-        "agent-base": "^7.1.0",
-        "debug": "^4.3.4"
-      },
-      "engines": {
-        "node": ">= 14"
-      }
-    },
-    "node_modules/https-proxy-agent": {
-      "version": "7.0.6",
-      "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.6.tgz",
-      "integrity": "sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw==",
-      "license": "MIT",
-      "dependencies": {
-        "agent-base": "^7.1.2",
-        "debug": "4"
-      },
-      "engines": {
-        "node": ">= 14"
-      }
-    },
-    "node_modules/ip-address": {
-      "version": "10.1.0",
-      "resolved": "https://registry.npmjs.org/ip-address/-/ip-address-10.1.0.tgz",
-      "integrity": "sha512-XXADHxXmvT9+CRxhXg56LJovE+bmWnEWB78LB83VZTprKTmaC5QfruXocxzTZ2Kl0DNwKuBdlIhjL8LeY8Sf8Q==",
-      "license": "MIT",
-      "engines": {
-        "node": ">= 12"
-      }
-    },
-    "node_modules/is-fullwidth-code-point": {
-      "version": "3.0.0",
-      "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz",
-      "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==",
-      "license": "MIT",
-      "engines": {
-        "node": ">=8"
-      }
-    },
-    "node_modules/lru-cache": {
-      "version": "7.18.3",
-      "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-7.18.3.tgz",
-      "integrity": "sha512-jumlc0BIUrS3qJGgIkWZsyfAM7NCWiBcCDhnd+3NNM5KbBmLTgHVfWBcg6W+rLUsIpzpERPsvwUP7CckAQSOoA==",
-      "license": "ISC",
-      "engines": {
-        "node": ">=12"
-      }
-    },
-    "node_modules/mitt": {
-      "version": "3.0.1",
-      "resolved": "https://registry.npmjs.org/mitt/-/mitt-3.0.1.tgz",
-      "integrity": "sha512-vKivATfr97l2/QBCYAkXYDbrIWPM2IIKEl7YPhjCvKlG3kE2gm+uBo6nEXK3M5/Ffh/FLpKExzOQ3JJoJGFKBw==",
-      "license": "MIT"
-    },
-    "node_modules/ms": {
-      "version": "2.1.3",
-      "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
-      "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==",
-      "license": "MIT"
-    },
-    "node_modules/netmask": {
-      "version": "2.0.2",
-      "resolved": "https://registry.npmjs.org/netmask/-/netmask-2.0.2.tgz",
-      "integrity": "sha512-dBpDMdxv9Irdq66304OLfEmQ9tbNRFnFTuZiLo+bD+r332bBmMJ8GBLXklIXXgxd3+v9+KUnZaUR5PJMa75Gsg==",
-      "license": "MIT",
-      "engines": {
-        "node": ">= 0.4.0"
-      }
-    },
-    "node_modules/once": {
-      "version": "1.4.0",
-      "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz",
-      "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==",
-      "license": "ISC",
-      "dependencies": {
-        "wrappy": "1"
-      }
-    },
-    "node_modules/pac-proxy-agent": {
-      "version": "7.2.0",
-      "resolved": "https://registry.npmjs.org/pac-proxy-agent/-/pac-proxy-agent-7.2.0.tgz",
-      "integrity": "sha512-TEB8ESquiLMc0lV8vcd5Ql/JAKAoyzHFXaStwjkzpOpC5Yv+pIzLfHvjTSdf3vpa2bMiUQrg9i6276yn8666aA==",
-      "license": "MIT",
-      "dependencies": {
-        "@tootallnate/quickjs-emscripten": "^0.23.0",
-        "agent-base": "^7.1.2",
-        "debug": "^4.3.4",
-        "get-uri": "^6.0.1",
-        "http-proxy-agent": "^7.0.0",
-        "https-proxy-agent": "^7.0.6",
-        "pac-resolver": "^7.0.1",
-        "socks-proxy-agent": "^8.0.5"
-      },
-      "engines": {
-        "node": ">= 14"
-      }
-    },
-    "node_modules/pac-resolver": {
-      "version": "7.0.1",
-      "resolved": "https://registry.npmjs.org/pac-resolver/-/pac-resolver-7.0.1.tgz",
-      "integrity": "sha512-5NPgf87AT2STgwa2ntRMr45jTKrYBGkVU36yT0ig/n/GMAa3oPqhZfIQ2kMEimReg0+t9kZViDVZ83qfVUlckg==",
-      "license": "MIT",
-      "dependencies": {
-        "degenerator": "^5.0.0",
-        "netmask": "^2.0.2"
-      },
-      "engines": {
-        "node": ">= 14"
-      }
-    },
-    "node_modules/pend": {
-      "version": "1.2.0",
-      "resolved": "https://registry.npmjs.org/pend/-/pend-1.2.0.tgz",
-      "integrity": "sha512-F3asv42UuXchdzt+xXqfW1OGlVBe+mxa2mqI0pg5yAHZPvFmY3Y6drSf/GQ1A86WgWEN9Kzh/WrgKa6iGcHXLg==",
-      "license": "MIT"
-    },
-    "node_modules/progress": {
-      "version": "2.0.3",
-      "resolved": "https://registry.npmjs.org/progress/-/progress-2.0.3.tgz",
-      "integrity": "sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA==",
-      "license": "MIT",
-      "engines": {
-        "node": ">=0.4.0"
-      }
-    },
-    "node_modules/proxy-agent": {
-      "version": "6.5.0",
-      "resolved": "https://registry.npmjs.org/proxy-agent/-/proxy-agent-6.5.0.tgz",
-      "integrity": "sha512-TmatMXdr2KlRiA2CyDu8GqR8EjahTG3aY3nXjdzFyoZbmB8hrBsTyMezhULIXKnC0jpfjlmiZ3+EaCzoInSu/A==",
-      "license": "MIT",
-      "dependencies": {
-        "agent-base": "^7.1.2",
-        "debug": "^4.3.4",
-        "http-proxy-agent": "^7.0.1",
-        "https-proxy-agent": "^7.0.6",
-        "lru-cache": "^7.14.1",
-        "pac-proxy-agent": "^7.1.0",
-        "proxy-from-env": "^1.1.0",
-        "socks-proxy-agent": "^8.0.5"
-      },
-      "engines": {
-        "node": ">= 14"
-      }
-    },
-    "node_modules/proxy-from-env": {
-      "version": "1.1.0",
-      "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz",
-      "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==",
-      "license": "MIT"
-    },
-    "node_modules/pump": {
-      "version": "3.0.4",
-      "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.4.tgz",
-      "integrity": "sha512-VS7sjc6KR7e1ukRFhQSY5LM2uBWAUPiOPa/A3mkKmiMwSmRFUITt0xuj+/lesgnCv+dPIEYlkzrcyXgquIHMcA==",
-      "license": "MIT",
-      "dependencies": {
-        "end-of-stream": "^1.1.0",
-        "once": "^1.3.1"
-      }
-    },
-    "node_modules/puppeteer-core": {
-      "version": "24.38.0",
-      "resolved": "https://registry.npmjs.org/puppeteer-core/-/puppeteer-core-24.38.0.tgz",
-      "integrity": "sha512-zB3S/tksIhgi2gZRndUe07AudBz5SXOB7hqG0kEa9/YXWrGwlVlYm3tZtwKgfRftBzbmLQl5iwHkQQl04n/mWw==",
-      "license": "Apache-2.0",
-      "dependencies": {
-        "@puppeteer/browsers": "2.13.0",
-        "chromium-bidi": "14.0.0",
-        "debug": "^4.4.3",
-        "devtools-protocol": "0.0.1581282",
-        "typed-query-selector": "^2.12.1",
-        "webdriver-bidi-protocol": "0.4.1",
-        "ws": "^8.19.0"
-      },
-      "engines": {
-        "node": ">=18"
-      }
-    },
-    "node_modules/require-directory": {
-      "version": "2.1.1",
-      "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz",
-      "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==",
-      "license": "MIT",
-      "engines": {
-        "node": ">=0.10.0"
-      }
-    },
-    "node_modules/semver": {
-      "version": "7.7.4",
-      "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.4.tgz",
-      "integrity": "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==",
-      "license": "ISC",
-      "bin": {
-        "semver": "bin/semver.js"
-      },
-      "engines": {
-        "node": ">=10"
-      }
-    },
-    "node_modules/smart-buffer": {
-      "version": "4.2.0",
-      "resolved": "https://registry.npmjs.org/smart-buffer/-/smart-buffer-4.2.0.tgz",
-      "integrity": "sha512-94hK0Hh8rPqQl2xXc3HsaBoOXKV20MToPkcXvwbISWLEs+64sBq5kFgn2kJDHb1Pry9yrP0dxrCI9RRci7RXKg==",
-      "license": "MIT",
-      "engines": {
-        "node": ">= 6.0.0",
-        "npm": ">= 3.0.0"
-      }
-    },
-    "node_modules/socks": {
-      "version": "2.8.7",
-      "resolved": "https://registry.npmjs.org/socks/-/socks-2.8.7.tgz",
-      "integrity": "sha512-HLpt+uLy/pxB+bum/9DzAgiKS8CX1EvbWxI4zlmgGCExImLdiad2iCwXT5Z4c9c3Eq8rP2318mPW2c+QbtjK8A==",
-      "license": "MIT",
-      "dependencies": {
-        "ip-address": "^10.0.1",
-        "smart-buffer": "^4.2.0"
-      },
-      "engines": {
-        "node": ">= 10.0.0",
-        "npm": ">= 3.0.0"
-      }
-    },
-    "node_modules/socks-proxy-agent": {
-      "version": "8.0.5",
-      "resolved": "https://registry.npmjs.org/socks-proxy-agent/-/socks-proxy-agent-8.0.5.tgz",
-      "integrity": "sha512-HehCEsotFqbPW9sJ8WVYB6UbmIMv7kUUORIF2Nncq4VQvBfNBLibW9YZR5dlYCSUhwcD628pRllm7n+E+YTzJw==",
-      "license": "MIT",
-      "dependencies": {
-        "agent-base": "^7.1.2",
-        "debug": "^4.3.4",
-        "socks": "^2.8.3"
-      },
-      "engines": {
-        "node": ">= 14"
-      }
-    },
-    "node_modules/source-map": {
-      "version": "0.6.1",
-      "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz",
-      "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==",
-      "license": "BSD-3-Clause",
-      "optional": true,
-      "engines": {
-        "node": ">=0.10.0"
-      }
-    },
-    "node_modules/streamx": {
-      "version": "2.23.0",
-      "resolved": "https://registry.npmjs.org/streamx/-/streamx-2.23.0.tgz",
-      "integrity": "sha512-kn+e44esVfn2Fa/O0CPFcex27fjIL6MkVae0Mm6q+E6f0hWv578YCERbv+4m02cjxvDsPKLnmxral/rR6lBMAg==",
-      "license": "MIT",
-      "dependencies": {
-        "events-universal": "^1.0.0",
-        "fast-fifo": "^1.3.2",
-        "text-decoder": "^1.1.0"
-      }
-    },
-    "node_modules/string-width": {
-      "version": "4.2.3",
-      "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz",
-      "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==",
-      "license": "MIT",
-      "dependencies": {
-        "emoji-regex": "^8.0.0",
-        "is-fullwidth-code-point": "^3.0.0",
-        "strip-ansi": "^6.0.1"
-      },
-      "engines": {
-        "node": ">=8"
-      }
-    },
-    "node_modules/strip-ansi": {
-      "version": "6.0.1",
-      "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz",
-      "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==",
-      "license": "MIT",
-      "dependencies": {
-        "ansi-regex": "^5.0.1"
-      },
-      "engines": {
-        "node": ">=8"
-      }
-    },
-    "node_modules/tar-fs": {
-      "version": "3.1.2",
-      "resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-3.1.2.tgz",
-      "integrity": "sha512-QGxxTxxyleAdyM3kpFs14ymbYmNFrfY+pHj7Z8FgtbZ7w2//VAgLMac7sT6nRpIHjppXO2AwwEOg0bPFVRcmXw==",
-      "license": "MIT",
-      "dependencies": {
-        "pump": "^3.0.0",
-        "tar-stream": "^3.1.5"
-      },
-      "optionalDependencies": {
-        "bare-fs": "^4.0.1",
-        "bare-path": "^3.0.0"
-      }
-    },
-    "node_modules/tar-stream": {
-      "version": "3.1.8",
-      "resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-3.1.8.tgz",
-      "integrity": "sha512-U6QpVRyCGHva435KoNWy9PRoi2IFYCgtEhq9nmrPPpbRacPs9IH4aJ3gbrFC8dPcXvdSZ4XXfXT5Fshbp2MtlQ==",
-      "license": "MIT",
-      "dependencies": {
-        "b4a": "^1.6.4",
-        "bare-fs": "^4.5.5",
-        "fast-fifo": "^1.2.0",
-        "streamx": "^2.15.0"
-      }
-    },
-    "node_modules/teex": {
-      "version": "1.0.1",
-      "resolved": "https://registry.npmjs.org/teex/-/teex-1.0.1.tgz",
-      "integrity": "sha512-eYE6iEI62Ni1H8oIa7KlDU6uQBtqr4Eajni3wX7rpfXD8ysFx8z0+dri+KWEPWpBsxXfxu58x/0jvTVT1ekOSg==",
-      "license": "MIT",
-      "dependencies": {
-        "streamx": "^2.12.5"
-      }
-    },
-    "node_modules/text-decoder": {
-      "version": "1.2.7",
-      "resolved": "https://registry.npmjs.org/text-decoder/-/text-decoder-1.2.7.tgz",
-      "integrity": "sha512-vlLytXkeP4xvEq2otHeJfSQIRyWxo/oZGEbXrtEEF9Hnmrdly59sUbzZ/QgyWuLYHctCHxFF4tRQZNQ9k60ExQ==",
-      "license": "Apache-2.0",
-      "dependencies": {
-        "b4a": "^1.6.4"
-      }
-    },
-    "node_modules/tslib": {
-      "version": "2.8.1",
-      "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz",
-      "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==",
-      "license": "0BSD"
-    },
-    "node_modules/typed-query-selector": {
-      "version": "2.12.1",
-      "resolved": "https://registry.npmjs.org/typed-query-selector/-/typed-query-selector-2.12.1.tgz",
-      "integrity": "sha512-uzR+FzI8qrUEIu96oaeBJmd9E7CFEiQ3goA5qCVgc4s5llSubcfGHq9yUstZx/k4s9dXHVKsE35YWoFyvEqEHA==",
-      "license": "MIT"
-    },
-    "node_modules/undici-types": {
-      "version": "7.18.2",
-      "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.18.2.tgz",
-      "integrity": "sha512-AsuCzffGHJybSaRrmr5eHr81mwJU3kjw6M+uprWvCXiNeN9SOGwQ3Jn8jb8m3Z6izVgknn1R0FTCEAP2QrLY/w==",
-      "license": "MIT",
-      "optional": true
-    },
-    "node_modules/webdriver-bidi-protocol": {
-      "version": "0.4.1",
-      "resolved": "https://registry.npmjs.org/webdriver-bidi-protocol/-/webdriver-bidi-protocol-0.4.1.tgz",
-      "integrity": "sha512-ARrjNjtWRRs2w4Tk7nqrf2gBI0QXWuOmMCx2hU+1jUt6d00MjMxURrhxhGbrsoiZKJrhTSTzbIrc554iKI10qw==",
-      "license": "Apache-2.0"
-    },
-    "node_modules/wrap-ansi": {
-      "version": "7.0.0",
-      "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz",
-      "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==",
-      "license": "MIT",
-      "dependencies": {
-        "ansi-styles": "^4.0.0",
-        "string-width": "^4.1.0",
-        "strip-ansi": "^6.0.0"
-      },
-      "engines": {
-        "node": ">=10"
-      },
-      "funding": {
-        "url": "https://github.com/chalk/wrap-ansi?sponsor=1"
-      }
-    },
-    "node_modules/wrappy": {
-      "version": "1.0.2",
-      "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz",
-      "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==",
-      "license": "ISC"
-    },
-    "node_modules/ws": {
-      "version": "8.19.0",
-      "resolved": "https://registry.npmjs.org/ws/-/ws-8.19.0.tgz",
-      "integrity": "sha512-blAT2mjOEIi0ZzruJfIhb3nps74PRWTCz1IjglWEEpQl5XS/UNama6u2/rjFkDDouqr4L67ry+1aGIALViWjDg==",
-      "license": "MIT",
-      "engines": {
-        "node": ">=10.0.0"
-      },
-      "peerDependencies": {
-        "bufferutil": "^4.0.1",
-        "utf-8-validate": ">=5.0.2"
-      },
-      "peerDependenciesMeta": {
-        "bufferutil": {
-          "optional": true
-        },
-        "utf-8-validate": {
-          "optional": true
-        }
-      }
-    },
-    "node_modules/y18n": {
-      "version": "5.0.8",
-      "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz",
-      "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==",
-      "license": "ISC",
-      "engines": {
-        "node": ">=10"
-      }
-    },
-    "node_modules/yargs": {
-      "version": "17.7.2",
-      "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz",
-      "integrity": "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==",
-      "license": "MIT",
-      "dependencies": {
-        "cliui": "^8.0.1",
-        "escalade": "^3.1.1",
-        "get-caller-file": "^2.0.5",
-        "require-directory": "^2.1.1",
-        "string-width": "^4.2.3",
-        "y18n": "^5.0.5",
-        "yargs-parser": "^21.1.1"
-      },
-      "engines": {
-        "node": ">=12"
-      }
-    },
-    "node_modules/yargs-parser": {
-      "version": "21.1.1",
-      "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz",
-      "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==",
-      "license": "ISC",
-      "engines": {
-        "node": ">=12"
-      }
-    },
-    "node_modules/yauzl": {
-      "version": "2.10.0",
-      "resolved": "https://registry.npmjs.org/yauzl/-/yauzl-2.10.0.tgz",
-      "integrity": "sha512-p4a9I6X6nu6IhoGmBqAcbJy1mlC4j27vEPZX9F4L4/vZT3Lyq1VkFHw/V/PUcB9Buo+DG3iHkT0x3Qya58zc3g==",
-      "license": "MIT",
-      "dependencies": {
-        "buffer-crc32": "~0.2.3",
-        "fd-slicer": "~1.1.0"
-      }
-    },
-    "node_modules/zod": {
-      "version": "3.25.76",
-      "resolved": "https://registry.npmjs.org/zod/-/zod-3.25.76.tgz",
-      "integrity": "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ==",
-      "license": "MIT",
-      "funding": {
-        "url": "https://github.com/sponsors/colinhacks"
-      }
-    }
-  }
-}

+ 0 - 5
prototype-ui/package.json

@@ -1,5 +0,0 @@
-{
-  "dependencies": {
-    "puppeteer-core": "^24.38.0"
-  }
-}

+ 0 - 49
prototype-ui/screenshot.js

@@ -1,49 +0,0 @@
-const puppeteer = require('puppeteer-core');
-const http = require('http');
-const fs = require('fs');
-const path = require('path');
-
-function startServer(root, port) {
-  return new Promise(resolve => {
-    const srv = http.createServer((req, res) => {
-      let fp = path.join(root, decodeURIComponent(req.url.split('?')[0]));
-      if (!fs.existsSync(fp) || fs.statSync(fp).isDirectory()) fp = path.join(root, 'index-v2.html');
-      const ext = path.extname(fp).toLowerCase();
-      const mime = { '.html':'text/html', '.js':'application/javascript', '.css':'text/css', '.png':'image/png', '.jpg':'image/jpeg' };
-      try { res.writeHead(200, { 'Content-Type': mime[ext] || 'application/octet-stream' }); fs.createReadStream(fp).pipe(res); }
-      catch(e) { res.writeHead(404); res.end(); }
-    });
-    srv.listen(port, () => resolve(srv));
-  });
-}
-
-(async () => {
-  const root = 'C:\\Users\\Administrator\\lab-safety-monitor';
-  const port = 19527;
-  const srv = await startServer(root, port);
-
-  const browser = await puppeteer.launch({
-    executablePath: 'C:\\Program Files (x86)\\Google\\Chrome\\Application\\chrome.exe',
-    headless: 'new',
-    args: ['--no-sandbox','--disable-setuid-sandbox','--window-size=9600,2800']
-  });
-
-  const page = await browser.newPage();
-  await page.setViewport({ width: 9600, height: 2800, deviceScaleFactor: 1 });
-  await page.goto(`http://localhost:${port}/index-v2.html`, { waitUntil: 'networkidle0', timeout: 30000 });
-
-  await new Promise(r => setTimeout(r, 2000));
-  // 关闭预警弹窗
-  await page.evaluate(() => {
-    const modal = document.getElementById('alert-modal');
-    if (modal) modal.style.display = 'none';
-    window.showAlert = () => {};
-  });
-  // 等待摄像头初始化
-  await new Promise(r => setTimeout(r, 5000));
-
-  await page.screenshot({ path: 'C:\\Users\\Administrator\\lab-safety-monitor\\design-preview.png', fullPage: true });
-  console.log('Done');
-  await browser.close();
-  srv.close();
-})().catch(e => { console.error(e.message); process.exit(1); });

+ 0 - 3
prototype-ui/test_cn.py

@@ -1,3 +0,0 @@
-# -*- coding: utf-8 -*-
-x = '导航栏位于大屏顶部'
-print(x)

+ 78 - 20
test_case/test_case_new.md

@@ -238,30 +238,88 @@
 ## 十二、应急疏散弹窗测试点
 
 ### 正常场景
-- 应急疏散弹窗全屏弹出,z-index 高于预警弹窗层级,两层弹窗可同时存在
-- 标题栏显示"应急疏散"文字,右上角关闭按钮点击后仅关闭本弹窗,底层预警弹窗保持可见
-- 左侧 SVG 平面图渲染深蓝科幻风格,楼层房间布局(A101–A109)位置准确
-- 告警房间(对应触发预警的房间)以红色边框高亮标注,高亮与预警弹窗中的实验室一致
-- 疏散路线关键房间以蓝色边框标注
-- 走道区域含集合点圆形标记,蓝色虚线箭头方向指向两侧紧急出口
-- SVG 右上方图例"应急疏散路线图"与 SVG 图形中的颜色语义一致(红=告警,蓝=疏散路线)
-- 右侧纵向排列 3 路视频监控占位区,每路含"楼道 2层"楼层分区标签,标签与占位区对齐
-- 底部左侧告警指标明细正确显示指标名称 + 当前值 / 阈值,与预警弹窗中的异常信息一致
-- 底部中部播放设备选择按钮展开显示可用播放设备列表,选择后高亮显示选中项
-- 喊话内容输入框可输入文字,字符实时显示无延迟
-- 点击"发送"按钮后,喊话内容提交成功,输入框自动清空,按钮短暂禁用防重复提交
-- 底部右侧"稍后处理"点击后关闭应急疏散弹窗,返回预警弹窗界面
-- 底部右侧"执行疏散"点击后触发疏散指令,按钮变为不可再次点击,界面给出"疏散指令已下达"反馈
+
+#### 弹窗层级与整体显示
+- 点击预警弹窗"🚪 应急疏散"按钮后,应急疏散弹窗全屏弹出,z-index 高于预警弹窗,两层弹窗同时可见且互不遮挡
+- 应急疏散弹窗弹出时有进场动画效果,动画流畅无卡顿,深蓝科幻风格与大屏整体主题一致
+- 标题栏显示"应急疏散"文字,字体样式、颜色与设计规范一致
+- 右上角关闭按钮点击后仅关闭应急疏散弹窗,底层全屏预警弹窗保持完整可见
+- 弹窗内各区域(左侧SVG、右侧监控、底部操作栏)布局清晰,无错位、无遮挡
+
+#### SVG 平面图
+- 左侧 SVG 平面图以深蓝科幻风格正确渲染,楼层房间布局(A101–A109)各房间位置、尺寸与设计稿一致
+- 触发预警的告警房间以红色边框高亮标注,高亮房间编号与预警弹窗中的实验室信息完全一致
+- 同时存在多个告警房间时,所有告警房间均以红色边框高亮显示,无遗漏
+- 疏散路线关键途经房间以蓝色边框标注,蓝色边框与红色告警边框视觉区分明确
+- 走道区域集合点以圆形标记显示,标记位置合理且不遮挡房间标签
+- 蓝色虚线箭头同时指向走道两端紧急出口(双向疏散路线),箭头方向清晰无歧义
+- SVG 右上方"应急疏散路线图"图例完整显示,红色=告警房间、蓝色=疏散路线的颜色语义与图形一致
+- 走道区域颜色与房间区域颜色有明显视觉区分,整体层次清晰
+
+#### 右侧视频监控区
+- 右侧纵向排列 3 路视频监控占位区,每路高宽比为 16:9,各路等宽等高排列整齐
+- 每路监控占位区上方含"楼道 2层"楼层分区标签,标签位置与对应监控区左对齐
+- 3 路监控均以 Canvas 模拟画面形式展示,画面内有动态模拟内容,不显示纯黑色空白
+
+#### 底部告警指标明细
+- 底部左侧告警指标明细区按"指标名称 | 当前值 | 阈值"格式逐行展示所有超限指标
+- 告警指标明细中的数值与预警弹窗中显示的异常信息完全一致,数据来源统一
+
+#### 底部语音广播区
+- 播放设备选择按钮点击后展开设备列表,列表中显示所有可用播放设备名称
+- 选择某播放设备后,按钮文字更新为已选设备名称,并高亮显示选中状态
+- 喊话内容输入框支持文字输入,字符实时显示,输入响应无延迟
+- 点击"发送"按钮后,喊话内容成功提交,输入框内容自动清空,发送按钮短暂禁用(约 2 秒)防重复提交
+- 发送成功后输入框恢复可用状态,可继续输入并再次发送
+
+#### 底部操作按钮
+- 底部右侧"稍后处理"按钮点击后,应急疏散弹窗关闭,焦点返回至底层全屏预警弹窗
+- 底部右侧"执行疏散"按钮点击后,疏散指令成功下达,按钮立即变为禁用状态(置灰),界面显示"疏散指令已下达"反馈提示
+- "执行疏散"触发后,疏散操作记录(含操作时间、触发人员信息)正确写入系统日志
 
 ### 异常场景
-- 喊话内容为空时点击"发送",提示"请输入喊话内容",不提交空内容
-- 播放设备列表为空时,设备选择按钮置灰且显示"无可用设备",发送按钮同步禁用
-- 左侧 SVG 平面图加载失败时,显示"平面图加载失败"占位,右侧和底部功能不受影响
+
+#### 语音广播异常
+- 喊话内容为空时点击"发送",按钮不可点击或点击后显示"请输入喊话内容"提示,不提交空请求
+- 播放设备列表为空时,设备选择按钮置灰并显示"无可用设备","发送"按钮同步禁用,不可点击
+- 喊话内容发送网络请求失败时,按钮恢复可点击状态,页面显示"发送失败,请重试"提示,输入内容不丢失
+- 播放设备下发指令失败时,界面给出明确错误提示,不显示发送成功反馈
+
+#### SVG 平面图异常
+- 左侧 SVG 平面图资源加载失败时,左侧区域显示"平面图加载失败"占位提示,右侧监控区及底部操作栏功能不受影响
+- SVG 渲染过程中某房间节点数据缺失时,该房间显示为灰色占位,不影响其他房间和路线渲染
+
+#### 视频监控异常
+- 右侧 3 路监控中某路 Canvas 加载失败时,该路显示"信号丢失"占位,其余两路正常显示
+- 3 路监控全部加载失败时,右侧区域各路均显示"信号丢失"占位,布局不塌陷
+
+#### 疏散指令异常
+- 点击"执行疏散"后网络请求失败时,按钮恢复可点击状态,界面显示"指令下达失败,请重试",不记录无效疏散日志
+- 告警指标明细数据接口返回异常时,该区域显示"数据获取失败"占位,不影响语音广播和操作按钮功能
 
 ### 边界场景
-- 喊话内容输入达到最大字符限制时,输入框不再接受新字符,边框标红并提示已达上限
-- 连续多次点击"执行疏散"按钮,系统只记录一次疏散操作,不重复下达指令
-- 关闭应急疏散弹窗后再次点击"🚪 应急疏散",弹窗可重新正常打开,状态重置
+
+#### 喊话字符限制
+- 喊话内容输入恰好达到最大字符上限时,输入框停止接受新字符,输入框边框标红并在下方显示"已达字符上限(XX/XX)"提示
+- 喊话内容输入仅 1 个字符时,点击"发送"应正常提交,不因字数过少被拦截
+- 喊话内容输入包含特殊字符(如 & < > " ')时,提交后不出现 XSS 注入或显示异常
+
+#### 多告警与疏散状态
+- 同时有多个房间触发告警时,SVG 平面图多个告警房间同时红色高亮,疏散路线覆盖所有告警位置
+- 疏散路线途经多个节点房间时,所有关键节点房间均以蓝色边框标注,路线连续无断点
+- 连续多次点击"执行疏散"按钮(快速双击),系统仅记录一次疏散操作,不重复下达指令(防抖机制)
+
+#### 弹窗状态重置
+- 关闭应急疏散弹窗后再次点击"🚪 应急疏散",弹窗重新正常打开,喊话输入框已清空、设备选择恢复默认状态
+- 执行疏散后关闭弹窗,再次打开时"执行疏散"按钮恢复可点击状态,不保留上次禁用状态
+
+#### 设备数量边界
+- 播放设备列表仅有 1 台设备时,设备选择按钮自动选中该设备,无需手动选择
+- 播放设备列表有 10 台以上设备时,设备选择下拉列表支持滚动查看,不出现列表溢出遮挡
+
+#### 分辨率适配
+- 在 9600×2800px 分辨率下,应急疏散弹窗各区域(SVG、监控、底部栏)布局比例与设计稿一致,无溢出
+- 在低于 9600×2800px 的显示器上,弹窗内容可正常访问,必要时出现内部滚动条,不遮挡操作按钮
 
 ---
 

BIN
test_case/test_case_new.xls


+ 2 - 2
test_pro/test_case_pro.md

@@ -25,7 +25,7 @@
 3. **分类整理**:按功能模块或测试类型对测试点进行归类,逻辑清晰地输出
 
 ## 输出格式
-以 Markdown 源代码形式输出,按模块分组,示例如下:
+以 xls文件形式输出,按模块分组,示例如下:
 
 ```
 # 登录模块测试点
@@ -47,4 +47,4 @@
 ## 输出规范
 1. 仅输出测试点,不输出详细的测试用例步骤
 2. 输出必须为 Markdown 源代码格式,不得使用渲染后的格式
-3. 每条测试点表述简洁、准确,包含**操作动作**与**预期结果**两个要素,避免歧义
+3. 每条测试点表述简洁、准确,包含:用例编号、模块名称、测试场景、测试类型、前置条件、测试操作、预期结果、优先级、设计方法、测试结果(下拉选择项:通过或不通过),并加上标题行