gen_doc.py 38 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775
  1. # -*- coding: utf-8 -*-
  2. from docx import Document
  3. from docx.shared import Pt, Cm, RGBColor, Inches
  4. from docx.enum.text import WD_ALIGN_PARAGRAPH, WD_TAB_LEADER
  5. from docx.enum.table import WD_TABLE_ALIGNMENT, WD_ALIGN_VERTICAL
  6. from docx.oxml.ns import qn
  7. from docx.oxml import OxmlElement
  8. import copy
  9. doc = Document()
  10. # ── 页面设置 A4 ──────────────────────────────────────────────
  11. section = doc.sections[0]
  12. section.page_width = Cm(21)
  13. section.page_height = Cm(29.7)
  14. section.left_margin = Cm(2.5)
  15. section.right_margin = Cm(2.5)
  16. section.top_margin = Cm(2.5)
  17. section.bottom_margin = Cm(2.0)
  18. # ── 颜色常量 ────────────────────────────────────────────────
  19. C_TITLE = RGBColor(0x1e, 0x3a, 0x8a) # 深蓝
  20. C_H2 = RGBColor(0x1d, 0x4e, 0xd8) # 中蓝
  21. C_H3 = RGBColor(0x0e, 0x7a, 0x9e) # 青蓝
  22. C_LABEL = RGBColor(0x37, 0x41, 0x51) # 深灰
  23. C_BODY = RGBColor(0x1f, 0x29, 0x37) # 正文黑
  24. C_WHITE = RGBColor(0xFF, 0xFF, 0xFF)
  25. C_TH_BG = RGBColor(0x1e, 0x40, 0xaf)
  26. C_ROW1 = RGBColor(0xef, 0xf6, 0xff)
  27. C_ROW2 = RGBColor(0xff, 0xff, 0xff)
  28. C_BORDER = RGBColor(0xc3, 0xda, 0xf7)
  29. C_NOTE_BG= RGBColor(0xfe, 0xf9, 0xc3)
  30. C_NOTE_BD= RGBColor(0xf5, 0x9e, 0x0b)
  31. def set_cell_bg(cell, rgb: RGBColor):
  32. tc = cell._tc
  33. tcPr = tc.get_or_add_tcPr()
  34. shd = OxmlElement('w:shd')
  35. shd.set(qn('w:val'), 'clear')
  36. shd.set(qn('w:color'), 'auto')
  37. hex_color = '{:02X}{:02X}{:02X}'.format(rgb[0], rgb[1], rgb[2])
  38. shd.set(qn('w:fill'), hex_color)
  39. tcPr.append(shd)
  40. def set_cell_border(cell, top=None, bottom=None, left=None, right=None):
  41. tc = cell._tc
  42. tcPr = tc.get_or_add_tcPr()
  43. tcBorders = OxmlElement('w:tcBorders')
  44. for side, val in [('top',top),('bottom',bottom),('left',left),('right',right)]:
  45. if val:
  46. el = OxmlElement(f'w:{side}')
  47. el.set(qn('w:val'), val.get('val','single'))
  48. el.set(qn('w:sz'), str(val.get('sz',4)))
  49. el.set(qn('w:space'), '0')
  50. el.set(qn('w:color'), val.get('color','C3DAF7'))
  51. tcBorders.append(el)
  52. tcPr.append(tcBorders)
  53. def add_run(para, text, bold=False, size=None, color=None, italic=False):
  54. run = para.add_run(text)
  55. run.bold = bold
  56. run.italic = italic
  57. run.font.name = '微软雅黑'
  58. run._element.rPr.rFonts.set(qn('w:eastAsia'), '微软雅黑')
  59. if size:
  60. run.font.size = Pt(size)
  61. if color:
  62. run.font.color.rgb = color
  63. return run
  64. def heading1(text):
  65. para = doc.add_paragraph()
  66. para.paragraph_format.space_before = Pt(18)
  67. para.paragraph_format.space_after = Pt(8)
  68. para.paragraph_format.left_indent = Cm(0)
  69. # 左侧蓝条效果通过 border
  70. pPr = para._p.get_or_add_pPr()
  71. pBdr = OxmlElement('w:pBdr')
  72. left = OxmlElement('w:left')
  73. left.set(qn('w:val'), 'single')
  74. left.set(qn('w:sz'), '24')
  75. left.set(qn('w:space'), '6')
  76. left.set(qn('w:color'), '1E3A8A')
  77. pBdr.append(left)
  78. pPr.append(pBdr)
  79. para.paragraph_format.left_indent = Cm(0.5)
  80. add_run(para, text, bold=True, size=14, color=C_TITLE)
  81. return para
  82. def heading2(text):
  83. para = doc.add_paragraph()
  84. para.paragraph_format.space_before = Pt(10)
  85. para.paragraph_format.space_after = Pt(4)
  86. add_run(para, '◆ ', bold=True, size=12, color=C_H2)
  87. add_run(para, text, bold=True, size=12, color=C_H2)
  88. return para
  89. def heading3(text):
  90. para = doc.add_paragraph()
  91. para.paragraph_format.space_before = Pt(6)
  92. para.paragraph_format.space_after = Pt(3)
  93. add_run(para, '▸ ', bold=True, size=11, color=C_H3)
  94. add_run(para, text, bold=True, size=11, color=C_H3)
  95. return para
  96. def body(text, indent=0):
  97. para = doc.add_paragraph()
  98. para.paragraph_format.space_before = Pt(2)
  99. para.paragraph_format.space_after = Pt(2)
  100. para.paragraph_format.left_indent = Cm(indent)
  101. para.paragraph_format.first_line_indent = Cm(0)
  102. add_run(para, text, size=10.5, color=C_BODY)
  103. return para
  104. def bullet(text, level=0):
  105. para = doc.add_paragraph()
  106. para.paragraph_format.space_before = Pt(1)
  107. para.paragraph_format.space_after = Pt(1)
  108. indent = 0.5 + level * 0.5
  109. para.paragraph_format.left_indent = Cm(indent)
  110. bullets = ['●', '○', '–']
  111. add_run(para, bullets[level] + ' ', bold=(level==0), size=10, color=C_H3)
  112. add_run(para, text, size=10, color=C_BODY)
  113. return para
  114. def note(text):
  115. para = doc.add_paragraph()
  116. para.paragraph_format.space_before = Pt(6)
  117. para.paragraph_format.space_after = Pt(6)
  118. para.paragraph_format.left_indent = Cm(0.5)
  119. para.paragraph_format.right_indent = Cm(0.5)
  120. pPr = para._p.get_or_add_pPr()
  121. pBdr = OxmlElement('w:pBdr')
  122. for side in ['top','bottom','left','right']:
  123. el = OxmlElement(f'w:{side}')
  124. el.set(qn('w:val'), 'single')
  125. el.set(qn('w:sz'), '4' if side in ('top','bottom','right') else '18')
  126. el.set(qn('w:space'), '4')
  127. el.set(qn('w:color'), 'F59E0B')
  128. pBdr.append(el)
  129. pPr.append(pBdr)
  130. add_run(para, '📌 注意:', bold=True, size=10, color=RGBColor(0xd9,0x77,0x06))
  131. add_run(para, text, size=10, color=C_BODY)
  132. return para
  133. def make_table(headers, rows, col_widths=None):
  134. table = doc.add_table(rows=1+len(rows), cols=len(headers))
  135. table.alignment = WD_TABLE_ALIGNMENT.CENTER
  136. table.style = 'Table Grid'
  137. # 表头
  138. hdr = table.rows[0]
  139. for i, h in enumerate(headers):
  140. cell = hdr.cells[i]
  141. set_cell_bg(cell, C_TH_BG)
  142. cell.vertical_alignment = WD_ALIGN_VERTICAL.CENTER
  143. para = cell.paragraphs[0]
  144. para.alignment = WD_ALIGN_PARAGRAPH.CENTER
  145. add_run(para, h, bold=True, size=10, color=C_WHITE)
  146. # 数据行
  147. for r_idx, row in enumerate(rows):
  148. tr = table.rows[r_idx+1]
  149. bg = C_ROW1 if r_idx % 2 == 0 else C_ROW2
  150. for c_idx, val in enumerate(row):
  151. cell = tr.cells[c_idx]
  152. set_cell_bg(cell, bg)
  153. cell.vertical_alignment = WD_ALIGN_VERTICAL.CENTER
  154. para = cell.paragraphs[0]
  155. para.alignment = WD_ALIGN_PARAGRAPH.LEFT
  156. add_run(para, str(val), size=10, color=C_BODY)
  157. # 列宽
  158. if col_widths:
  159. for i, w in enumerate(col_widths):
  160. for row in table.rows:
  161. row.cells[i].width = Cm(w)
  162. doc.add_paragraph() # 表格后空行
  163. return table
  164. def divider():
  165. para = doc.add_paragraph()
  166. para.paragraph_format.space_before = Pt(4)
  167. para.paragraph_format.space_after = Pt(4)
  168. pPr = para._p.get_or_add_pPr()
  169. pBdr = OxmlElement('w:pBdr')
  170. bottom = OxmlElement('w:bottom')
  171. bottom.set(qn('w:val'), 'single')
  172. bottom.set(qn('w:sz'), '6')
  173. bottom.set(qn('w:space'), '1')
  174. bottom.set(qn('w:color'), 'C3DAF7')
  175. pBdr.append(bottom)
  176. pPr.append(pBdr)
  177. # ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
  178. # 封面
  179. # ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
  180. doc.add_paragraph()
  181. doc.add_paragraph()
  182. doc.add_paragraph()
  183. p = doc.add_paragraph()
  184. p.alignment = WD_ALIGN_PARAGRAPH.CENTER
  185. add_run(p, '实验室安全智能监测与管控中心', bold=True, size=22, color=C_TITLE)
  186. doc.add_paragraph()
  187. p = doc.add_paragraph()
  188. p.alignment = WD_ALIGN_PARAGRAPH.CENTER
  189. add_run(p, '数据可视化大屏', bold=True, size=16, color=C_H2)
  190. doc.add_paragraph()
  191. p = doc.add_paragraph()
  192. p.alignment = WD_ALIGN_PARAGRAPH.CENTER
  193. add_run(p, '使 用 说 明 文 档', bold=True, size=14, color=C_H3)
  194. doc.add_paragraph()
  195. doc.add_paragraph()
  196. p = doc.add_paragraph()
  197. p.alignment = WD_ALIGN_PARAGRAPH.CENTER
  198. add_run(p, '中国安全生产科学研究院', bold=True, size=12, color=C_LABEL)
  199. p = doc.add_paragraph()
  200. p.alignment = WD_ALIGN_PARAGRAPH.CENTER
  201. add_run(p, 'National Institute for Occupational Safety', italic=True, size=10, color=C_LABEL)
  202. doc.add_paragraph()
  203. p = doc.add_paragraph()
  204. p.alignment = WD_ALIGN_PARAGRAPH.CENTER
  205. add_run(p, '文档版本:V1.0  编制日期:2026年03月', size=10, color=C_LABEL)
  206. doc.add_page_break()
  207. # ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
  208. # 目录
  209. # ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
  210. p = doc.add_paragraph()
  211. p.alignment = WD_ALIGN_PARAGRAPH.CENTER
  212. add_run(p, '目  录', bold=True, size=14, color=C_TITLE)
  213. doc.add_paragraph()
  214. toc_items = [
  215. ('1', '文档概述', '3'),
  216. ('2', '系统简介', '3'),
  217. ('3', '运行环境与访问方式', '4'),
  218. ('4', '整体界面布局说明', '4'),
  219. ('5', '顶部导航栏', '5'),
  220. ('6', '1区 — 实验室基本情况统计', '5'),
  221. ('7', '2区 — 智能设备与实验室设备统计', '7'),
  222. ('8', '3区 — 实时监控', '8'),
  223. ('9', '4区 — 实验环境感知与风险预警', '10'),
  224. ('10', '全屏预警弹窗', '11'),
  225. ('11', '通用交互说明', '12'),
  226. ('12', '数据说明', '12'),
  227. ('13', '常见问题(FAQ)', '13'),
  228. ]
  229. for no, title, page in toc_items:
  230. para = doc.add_paragraph()
  231. para.paragraph_format.space_before = Pt(3)
  232. para.paragraph_format.space_after = Pt(3)
  233. tab_stops = para.paragraph_format.tab_stops
  234. tab_stops.add_tab_stop(Cm(14.5), leader=WD_TAB_LEADER.DOTS)
  235. add_run(para, f'{no}. {title}', size=10.5, color=C_BODY)
  236. para.add_run('\t')
  237. add_run(para, page, size=10.5, color=C_BODY)
  238. doc.add_page_break()
  239. # ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
  240. # 1. 文档概述
  241. # ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
  242. heading1('1. 文档概述')
  243. body('本文档为《实验室安全智能监测与管控中心》数据可视化大屏的使用说明,旨在帮助操作人员、管理人员及相关使用者快速了解系统各功能模块的展示内容、交互方式及数据含义,确保系统功能得到正确、高效的使用。')
  244. doc.add_paragraph()
  245. body('本文档适用对象:')
  246. bullet('实验室安全管理人员(日常监控运维)')
  247. bullet('院级行政管理人员(决策数据查看)')
  248. bullet('IT运维人员(系统部署与维护参考)')
  249. divider()
  250. # ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
  251. # 2. 系统简介
  252. # ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
  253. heading1('2. 系统简介')
  254. body('《实验室安全智能监测与管控中心》大屏系统是面向中国安全生产科学研究院(简称"安科院")各实验室的综合安全管理可视化平台,通过数据可视化手段将实验室的安全状态、环境感知数据、设备运行状况、人员进出信息及风险预警等核心数据以直观、实时的方式呈现于大屏上,便于管理人员进行统一监控与快速决策。')
  255. doc.add_paragraph()
  256. heading2('系统定位')
  257. make_table(
  258. ['维度', '说明'],
  259. [
  260. ['系统名称', '实验室安全智能监测与管控中心'],
  261. ['所属单位', '中国安全生产科学研究院(National Institute for Occupational Safety)'],
  262. ['系统类型', '数据可视化大屏(单页面应用)'],
  263. ['设计分辨率', '9600 × 2800 px(约为1920×1080的2.5倍)'],
  264. ['视觉风格', '深蓝科幻风,边框流动效果,科技感动效图标'],
  265. ['技术框架', 'HTML5 + CSS3 + ECharts 5.4.3(单文件内联,无需构建工具)'],
  266. ],
  267. col_widths=[4, 11.5]
  268. )
  269. heading2('主要功能概览')
  270. bullet('实验室基本情况统计(总数、分级、状态)')
  271. bullet('实验室安全分级统计(各二级单位堆叠柱状图)')
  272. bullet('实验室人员进出数量统计及走势折线图')
  273. bullet('智能环境感知应用设备统计(在线率、设备分类)')
  274. bullet('实验室设备分类及使用统计(分类、使用率、状态)')
  275. bullet('3区固定显示实时监控(9宫格监控画面,AI危险行为检测)')
  276. bullet('实验环境安全智能感知(传感器实时数据滚动)')
  277. bullet('实验室实时风险预警(预警通知轮播)')
  278. bullet('全屏预警弹窗(超阈值自动触发,科幻警报样式)')
  279. divider()
  280. # ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
  281. # 3. 运行环境与访问方式
  282. # ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
  283. heading1('3. 运行环境与访问方式')
  284. heading2('硬件环境要求')
  285. make_table(
  286. ['项目', '最低要求', '推荐配置'],
  287. [
  288. ['显示屏', '1920×1080(FHD)', '超大屏/拼接屏 9600×2800 px'],
  289. ['处理器', 'Intel i5 / 同等级别', 'Intel i7 或以上'],
  290. ['内存', '8 GB', '16 GB 或以上'],
  291. ['显卡', '支持硬件加速', '独立显卡(GPU加速渲染)'],
  292. ],
  293. col_widths=[4, 5, 6.5]
  294. )
  295. heading2('软件环境要求')
  296. make_table(
  297. ['软件', '版本要求', '说明'],
  298. [
  299. ['浏览器', 'Chrome 90+ / Edge 90+', '推荐使用最新版 Chrome,需支持 WebGL'],
  300. ['ECharts CDN', '5.4.3', '通过 jsDelivr CDN 在线加载,需保证网络可达'],
  301. ['操作系统', 'Windows 10+ / macOS 12+', '支持主流64位操作系统'],
  302. ],
  303. col_widths=[3, 5, 7.5]
  304. )
  305. heading2('访问方式')
  306. bullet('将项目文件夹(含 index-v2.html 及 baseBg.png)部署到本地或局域网 Web 服务器。')
  307. bullet('用浏览器打开 http://[服务器IP]/index-v2.html 即可访问。')
  308. bullet('也可直接双击 index-v2.html 在浏览器中本地打开(部分资源需本地服务器访问)。')
  309. bullet('首次加载时,系统将自动请求进入全屏模式(浏览器弹出授权提示,点击"允许"即可)。')
  310. note('若 ECharts 图表未正常显示,请检查网络连接是否可访问 cdn.jsdelivr.net,或将 ECharts 替换为本地文件引用。')
  311. divider()
  312. # ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
  313. # 4. 整体界面布局说明
  314. # ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
  315. heading1('4. 整体界面布局说明')
  316. body('大屏整体由顶部导航栏 + 内容区域两大部分构成,内容区域按从左到右 2:1.5:4.5:2 的比例划分为四个区域。布局示意如下:')
  317. doc.add_paragraph()
  318. # 布局示意表
  319. table = doc.add_table(rows=3, cols=5)
  320. table.alignment = WD_TABLE_ALIGNMENT.CENTER
  321. table.style = 'Table Grid'
  322. # 第1行:导航栏
  323. row0 = table.rows[0]
  324. row0.cells[0].merge(row0.cells[4])
  325. set_cell_bg(row0.cells[0], C_TH_BG)
  326. p = row0.cells[0].paragraphs[0]
  327. p.alignment = WD_ALIGN_PARAGRAPH.CENTER
  328. add_run(p, '顶部导航栏(时钟 | LOGO + 单位名称 + 系统大标题 | 天气)', bold=True, size=10, color=C_WHITE)
  329. # 第2行:列标题
  330. row1 = table.rows[1]
  331. for i, (txt, bg) in enumerate([
  332. ('1区(比例2)', C_ROW1),
  333. ('2区(比例1.5)', C_ROW2),
  334. ('3区(比例4.5)', C_ROW1),
  335. ('4区(比例2)', C_ROW2),
  336. ]):
  337. if i < 4:
  338. cell = row1.cells[i]
  339. set_cell_bg(cell, C_TH_BG if i in [0,2] else RGBColor(0x1d, 0x4e, 0xd8))
  340. p = cell.paragraphs[0]
  341. p.alignment = WD_ALIGN_PARAGRAPH.CENTER
  342. add_run(p, txt, bold=True, size=10, color=C_WHITE)
  343. # merge last 2 cols for col 4
  344. # Actually use 4 cols only: remove 5th
  345. row1.cells[4].merge(row1.cells[3])
  346. # 第3行:内容描述
  347. row2 = table.rows[2]
  348. contents = [
  349. '• 基本情况统计\n• 安全分级统计\n• 人员进出走势',
  350. '• 智能环境感知\n 应用设备统计\n• 设备分类及\n 使用统计',
  351. '实时监控(固定显示)\n左侧:搜索/筛选/树状图\n右侧:9宫格摄像头',
  352. '• 实验环境安全\n 智能感知\n• 实时风险预警',
  353. ]
  354. bgs = [C_ROW1, C_ROW2, C_ROW1, C_ROW2]
  355. for i, (txt, bg) in enumerate(zip(contents, bgs)):
  356. cell = row2.cells[i]
  357. set_cell_bg(cell, bg)
  358. cell.vertical_alignment = WD_ALIGN_VERTICAL.CENTER
  359. p = cell.paragraphs[0]
  360. p.alignment = WD_ALIGN_PARAGRAPH.LEFT
  361. add_run(p, txt, size=9.5, color=C_BODY)
  362. row2.cells[4].merge(row2.cells[3])
  363. doc.add_paragraph()
  364. body('各区域均设有科幻装饰线框(四角装饰线)、边框流动动画(border-beam)及动效图标,模块之间有呼吸感的间距,整体采用深蓝科幻主题色系。')
  365. divider()
  366. # ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
  367. # 5. 顶部导航栏
  368. # ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
  369. heading1('5. 顶部导航栏')
  370. body('导航栏位于页面最顶部,横贯全宽,分为左、中、右三个区域。')
  371. make_table(
  372. ['区域', '显示内容', '说明'],
  373. [
  374. ['左侧', '实时时钟(时:分:秒)\n日期及星期', '每秒自动更新,金色数字显示,字体采用等宽字体'],
  375. ['中间', 'LOGO图标\n中国安全生产科学研究院(单位名称)\n竖向分隔线\n实验室安全智能监测与管控中心(系统大标题)', 'LOGO为蓝色方形图标,带脉冲光晕动效;\n大标题采用渐变流光动画,中英文双语单位名称显示'],
  376. ['右侧', '城市 · 天气状况\n气温(°C) / AQI空气质量指数', '显示当前天气信息及空气质量'],
  377. ],
  378. col_widths=[2.5, 7, 6]
  379. )
  380. note('导航栏不含任何操作按钮,3区实时监控为固定显示,无需切换。')
  381. divider()
  382. # ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
  383. # 6. 1区
  384. # ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
  385. heading1('6. 1区 — 实验室基本情况统计')
  386. body('1区位于大屏左侧,由上至下包含三个功能模块。')
  387. heading2('6.1 实验室基本情况统计')
  388. body('本模块分为上部(80%)和下部(20%)两部分:')
  389. doc.add_paragraph()
  390. heading3('上部(按左4:右6比例划分)')
  391. bullet('左侧(4):SVG圆弧仪表图形,展示实验室总数(共128间),图形中心显示总数数字,下方配分级比例色条(I级红、II级橙、III级黄、IV级蓝)。', 0)
  392. bullet('右侧(6):环形图(donut chart),展示四个安全分级(I~IV级)的占比及数量,图外标注各级名称、数量(间)、百分比;图表圆心显示总数;右侧配彩色左边框明细行。', 0)
  393. doc.add_paragraph()
  394. heading3('下部(三种状态徽章)')
  395. bullet('横向并排三个状态徽章,实时显示各状态实验室数量:', 0)
  396. bullet('使用(绿色):当前正在使用的实验室数量', 1)
  397. bullet('异常(橙色):当前处于异常告警状态的实验室数量', 1)
  398. bullet('空闲(靛蓝):当前闲置的实验室数量', 1)
  399. doc.add_paragraph()
  400. body('分级说明:')
  401. make_table(
  402. ['安全级别', '标识颜色', '含义', '数量(示例)'],
  403. [
  404. ['I 级', '红色', '危险实验室', '12 间'],
  405. ['II 级', '橙色', '较危险实验室', '28 间'],
  406. ['III 级', '黄色', '一般危险实验室', '45 间'],
  407. ['IV 级', '蓝色', '较安全实验室', '43 间'],
  408. ],
  409. col_widths=[3, 3, 5, 4.5]
  410. )
  411. divider()
  412. heading2('6.2 实验室安全分级统计')
  413. body('本模块使用堆叠柱状图统计各二级单位实验室总数及分级数据。')
  414. bullet('X轴:各二级单位名称,单位名称下方显示该单位实验室总数。')
  415. bullet('Y轴:实验室数量(间)。')
  416. bullet('每根柱子由I~IV级叠加组成,颜色对应分级(红/橙/黄/蓝)。')
  417. bullet('图表顶部显示图例(I级、II级、III级、IV级及对应颜色)。')
  418. bullet('一屏展示6个柱子,当数据量超出时,图表每5秒自动向左滚动1个单位,循环展示所有二级单位数据。')
  419. doc.add_paragraph()
  420. note('鼠标悬停在柱状图上可显示当前单位各分级详细数量的悬浮提示框。')
  421. divider()
  422. heading2('6.3 实验室进入人数统计及走势')
  423. body('本模块展示今日实验室进入与在场人数统计,以及全天走势趋势。')
  424. doc.add_paragraph()
  425. heading3('数字翻牌器(上方)')
  426. bullet('今日进入总人数:以翻牌动画形式显示,共4位数字滚动展示,金色高亮。')
  427. bullet('当前在场实验人数:同样以翻牌动画形式显示,实时更新。')
  428. heading3('折线图(下方)')
  429. bullet('X轴:当天0~24小时,按9个时间节点显示(00:00、03:00、06:00…24:00)。')
  430. bullet('Y轴:人数(人)。')
  431. bullet('进入人数(蓝色实线+渐变填充)与在场人数(金色实线+渐变填充)双线对比展示。')
  432. divider()
  433. # ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
  434. # 7. 2区
  435. # ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
  436. heading1('7. 2区 — 智能设备与实验室设备统计')
  437. body('2区位于1区右侧,由上至下包含两个功能模块。')
  438. heading2('7.1 智能环境感知应用设备统计')
  439. body('本模块统计展示布设的智能物联应用设备整体情况,分为上部统计栏和下部图表区。')
  440. doc.add_paragraph()
  441. heading3('上部统计栏')
  442. bullet('在线设备数量(绿色高亮,示例:312台)')
  443. bullet('离线设备数量(红色高亮,示例:18台)')
  444. heading3('下部图表区(左右布局)')
  445. bullet('左侧:速度仪表盘样式图表,展示设备在线率百分比(示例:94.5%),金色数字显示。')
  446. bullet('右侧:2×2网格卡片,分别显示:')
  447. bullet('电子信息铭牌(86台)', 1)
  448. bullet('化学品智能终端(44台)', 1)
  449. bullet('传感器套件(128台)', 1)
  450. bullet('智能摄像设备(72台)', 1)
  451. divider()
  452. heading2('7.2 实验室设备分类及使用统计')
  453. body('本模块按 4:2:4 比例分为上、中、下三部分:')
  454. doc.add_paragraph()
  455. heading3('上部(占4)— 设备分类环形图')
  456. bullet('使用环形图展示所有实验室设备的分类分布情况。')
  457. bullet('右侧显示图例:各设备分类的颜色圆点标识、分类名称及数量(台)。')
  458. body('设备分类示例:', indent=1)
  459. make_table(
  460. ['分类', '数量(台)'],
  461. [
  462. ['检测设备', '680'],
  463. ['分析仪器', '520'],
  464. ['制备设备', '380'],
  465. ['安全设备', '280'],
  466. ['辅助设备', '240'],
  467. ['其他', '358'],
  468. ],
  469. col_widths=[7, 8.5]
  470. )
  471. heading3('中部(占2)— 汇总统计')
  472. make_table(
  473. ['指标', '说明'],
  474. [
  475. ['设备总数', '所有实验室设备总数(示例:2,458台)'],
  476. ['使用总时长', '累计设备使用时长(示例:18,620小时)'],
  477. ['设备使用率', '当前设备使用率(示例:62.4%)'],
  478. ],
  479. col_widths=[4, 11.5]
  480. )
  481. heading3('下部(占4)— 设备状态饼图')
  482. bullet('使用饼图统计各使用状态的设备数量及占比。')
  483. bullet('右侧显示四种状态的颜色圆点标识及对应数量(台)。')
  484. make_table(
  485. ['状态', '说明', '示例数量'],
  486. [
  487. ['使用', '当前正在使用中的设备', '486 台'],
  488. ['空闲', '空置未使用的设备', '1,840 台'],
  489. ['正常', '状态正常待命的设备', '98 台'],
  490. ['维修', '正在维修或故障设备', '34 台'],
  491. ],
  492. col_widths=[3, 8, 4.5]
  493. )
  494. divider()
  495. # ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
  496. # 8. 3区
  497. # ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
  498. heading1('8. 3区 — 实时监控')
  499. body('3区是大屏中央最大区域(比例占4.5),固定显示实时监控视图,分为左侧面板和右侧画面区两部分,无需切换。')
  500. heading2('8.1 左侧面板(功能操作区)')
  501. make_table(
  502. ['功能组件', '说明'],
  503. [
  504. ['搜索框', '可输入关键词搜索楼栋、楼层或房间名称'],
  505. ['二级单位下拉筛选', '下拉选择框,可按化学研究所/物理研究所等单位进行筛选过滤'],
  506. ['建筑结构树状图', '可展开/折叠的树形结构,层级为:院区 > 楼栋 > 楼层 > 房间\n点击节点可选中并高亮对应监控画面'],
  507. ],
  508. col_widths=[4, 11.5]
  509. )
  510. heading2('8.2 右侧画面区(9宫格监控)')
  511. bullet('标题行:左侧显示当前位置面包屑导航(院区 › 楼栋名称 › 楼层),右侧显示翻页按钮(‹ / ›)及页码信息。')
  512. bullet('9宫格画面:以3×3网格排列9路摄像头画面(16:9比例),每路画面使用Canvas模拟实时视频。')
  513. bullet('第一路摄像头(AI摄像头)特殊标注:')
  514. bullet('顶部左角:绿色"🤖 AI检测"徽标', 1)
  515. bullet('顶部右角:红色 REC 录制指示灯(闪烁)', 1)
  516. bullet('画面中叠加红色危险行为检测框及标签(如"危险行为: 未佩戴防护")', 1)
  517. bullet('其余8路摄像头显示常规画面(带室内场景模拟)。')
  518. note('9宫格摄像头画面为Canvas模拟渲染,不接入真实摄像头信号,如需接入真实视频流请联系IT运维人员进行配置。')
  519. divider()
  520. # ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
  521. # 9. 4区
  522. # ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
  523. heading1('9. 4区 — 实验环境感知与风险预警')
  524. body('4区位于大屏最右侧,由上至下按7:3比例划分为两个功能模块。')
  525. heading2('9.1 实验环境安全智能感知(占7)')
  526. body('本模块展示每间实验室硬件传感器的实时监测数据,区域内由下向上自动滚动展示所有实验室条目(30秒一循环,鼠标悬停自动暂停)。')
  527. doc.add_paragraph()
  528. heading3('每条实验室条目包含以下信息:')
  529. make_table(
  530. ['信息字段', '说明', '示例'],
  531. [
  532. ['实验室名称', '实验室中文名称(房号)', '化学分析实验室(A301)'],
  533. ['所属单位', '二级单位名称', '化学研究所'],
  534. ['告警状态', '正常/告警指示图标及文字', '● 正常 / 🚨 告警(闪烁)'],
  535. ['温度 T', '当前室内温度(°C)', '22.5°C'],
  536. ['湿度 H', '当前室内相对湿度(%RH)', '58%'],
  537. ['TVOC', '总挥发性有机化合物浓度(mg/m³)\n超标(>0.6)时标红闪烁', '0.82 mg/m³(红色告警)'],
  538. ['CO₂', '二氧化碳浓度(ppm)\n超标(>700)时标红闪烁', '650 ppm'],
  539. ['O₂', '氧气浓度(%)', '20.9%'],
  540. ],
  541. col_widths=[3, 7, 5.5]
  542. )
  543. body('当某传感器数值超出阈值时,该实验室条目整体高亮为红色渐变效果,并触发全屏预警弹窗(详见第10章)。')
  544. divider()
  545. heading2('9.2 实验室实时风险预警(占3)')
  546. body('本模块展示本月预警响应总数及实时预警通知轮播(22秒一循环,最新通知始终显示在最上方)。')
  547. doc.add_paragraph()
  548. heading3('顶部统计')
  549. bullet('本月预警响应总数:以琥珀色/橙黄色高亮数字显示(示例:42次)。')
  550. heading3('预警通知列表(每条包含)')
  551. make_table(
  552. ['字段', '说明'],
  553. [
  554. ['实验室信息', '实验室名称(房号)- 所属二级单位'],
  555. ['异常指标', '异常传感器类型及当前超标值'],
  556. ['预警时间', '精确到秒的时间戳(日期-时-分-秒)'],
  557. ],
  558. col_widths=[4, 11.5]
  559. )
  560. note('预警区域字体采用琥珀/橙色调(#fcd34d / #fb923c),不使用红色字体,区别于传感器告警区域。')
  561. divider()
  562. # ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
  563. # 10. 全屏预警弹窗
  564. # ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
  565. heading1('10. 全屏预警弹窗')
  566. body('当传感器监测数据超出设定阈值,系统将自动弹出全屏预警弹窗(系统启动约5秒后自动触发演示),以科幻电影系统报警样式呈现。')
  567. doc.add_paragraph()
  568. heading2('弹窗布局')
  569. body('弹窗分为左右两个区域:')
  570. make_table(
  571. ['区域', '内容', '说明'],
  572. [
  573. ['左侧', '告警实验室实时监控画面\n(900×700 Canvas模拟)', '带红色扫描线、噪点效果;\n叠加AI危险行为检测框;\n顶部显示摄像头编号、REC录制状态;\n底部显示实验室名称及AI检测标识'],
  574. ['右侧', '告警详细信息', '告警实验室、所属单位、告警指标、当前值/安全阈值四项信息卡片;\n底部显示紧急疏散提示文字(闪烁动效)'],
  575. ],
  576. col_widths=[2, 5, 8.5]
  577. )
  578. heading2('弹窗操作按钮')
  579. make_table(
  580. ['按钮', '功能'],
  581. [
  582. ['稍后处理', '关闭弹窗,暂时忽略告警(后续可在4区预警列表查看)'],
  583. ['确认处理', '关闭弹窗,标记告警已处理'],
  584. ],
  585. col_widths=[4, 11.5]
  586. )
  587. heading2('弹窗视觉特征')
  588. bullet('全屏深红色半透明遮罩,背景模糊(backdrop-filter: blur)。')
  589. bullet('弹窗主体为深红渐变边框,红色扫描线从上到下持续扫描(1.5秒一循环)。')
  590. bullet('标题"⚡ 系统预警 · ALERT"红色字体,💥 图标快速闪烁动效。')
  591. note('弹窗为全屏展示,关闭弹窗后系统恢复正常监控状态,预警信息仍保留在4区实时风险预警列表中。')
  592. divider()
  593. # ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
  594. # 11. 通用交互说明
  595. # ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
  596. heading1('11. 通用交互说明')
  597. make_table(
  598. ['交互行为', '触发方式', '效果说明'],
  599. [
  600. ['全屏显示', '页面加载时自动触发', '请求浏览器进入全屏模式(F11手动切换亦可)'],
  601. ['图表悬停提示', '鼠标悬停在图表区域', '显示详细数据悬浮提示框(Tooltip)'],
  602. ['安全分级柱状图滚动', '自动(每5秒)', '自动向左滚动1个X轴单位,循环展示'],
  603. ['传感器列表滚动', '自动(30秒一循环)', '由下向上平滑滚动,鼠标悬停可暂停'],
  604. ['预警通知滚动', '自动(22秒一循环)', '由下向上平滑滚动,鼠标悬停可暂停'],
  605. ['树状图展开/折叠', '点击监控视图左侧树状图节点', '展开或折叠对应子节点'],
  606. ['关闭预警弹窗', '点击"确认处理"或"稍后处理"按钮', '关闭全屏预警弹窗,恢复正常显示'],
  607. ],
  608. col_widths=[4, 5, 6.5]
  609. )
  610. divider()
  611. # ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
  612. # 12. 数据说明
  613. # ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
  614. heading1('12. 数据说明')
  615. body('当前版本大屏展示的数据均为内置模拟数据(Demo数据),用于系统功能演示。如需接入真实数据,请由IT运维人员按照以下说明进行后端数据对接。')
  616. doc.add_paragraph()
  617. heading2('传感器告警阈值(默认配置)')
  618. make_table(
  619. ['传感器指标', '正常范围', '告警阈值', '单位'],
  620. [
  621. ['TVOC', '< 0.6', '≥ 0.6', 'mg/m³'],
  622. ['CO₂', '< 700', '≥ 700', 'ppm'],
  623. ['O₂', '19.5 ~ 23.5', '< 19.5 或 > 23.5', '%'],
  624. ['温度', '16 ~ 28', '< 16 或 > 28', '°C'],
  625. ['湿度', '30 ~ 80', '< 30 或 > 80', '% RH'],
  626. ],
  627. col_widths=[4, 4, 5, 2.5]
  628. )
  629. heading2('数据刷新说明')
  630. make_table(
  631. ['数据模块', '刷新方式'],
  632. [
  633. ['实时时钟', '每秒刷新'],
  634. ['数字翻牌器(进入人数)', '页面加载时动画计数(2秒内完成)'],
  635. ['传感器数据列表', '页面加载时渲染(静态模拟数据)'],
  636. ['风险预警列表', '页面加载时渲染(静态模拟数据)'],
  637. ['ECharts图表', '页面加载时初始化渲染(静态模拟数据)'],
  638. ['柱状图自动滚动', '每5秒自动切换1个X轴步长'],
  639. ],
  640. col_widths=[6, 9.5]
  641. )
  642. divider()
  643. # ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
  644. # 13. FAQ
  645. # ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
  646. heading1('13. 常见问题(FAQ)')
  647. faqs = [
  648. ('Q1:打开页面后图表区域空白,没有显示图表?',
  649. '请检查网络连接是否正常,确保可以访问 cdn.jsdelivr.net(ECharts CDN地址)。如无外网访问条件,请将 ECharts JS 文件下载至本地,并将 HTML 文件头部的 CDN 引用路径改为本地相对路径。'),
  650. ('Q2:页面一打开就弹出了预警弹窗,如何关闭?',
  651. '这是系统的演示效果,页面加载约5秒后会自动弹出全屏预警弹窗。点击弹窗右下角的"确认处理"或"稍后处理"按钮即可关闭。'),
  652. ('Q3:3区实时监控画面不显示或摄像头为黑屏?',
  653. '9宫格摄像头画面为Canvas模拟渲染,页面加载完成后自动初始化。若画面为黑屏,请刷新页面后等待3~5秒。如需接入真实摄像头,请联系IT运维人员进行后端视频流配置。'),
  654. ('Q4:页面内容显示不完整或过小,无法正常查看?',
  655. '本大屏设计分辨率为 9600×2800px,需配合超大屏/拼接屏使用。普通1080p显示器需通过浏览器滚动查看完整内容,或将浏览器缩放比例调整为25%~33%以适配屏幕。'),
  656. ('Q5:9宫格监控画面是否为真实摄像头接入?',
  657. '当前版本为Canvas模拟画面,不接入真实摄像头信号。如需接入真实CCTV/RTSP视频流,需进行后端视频推流对接开发,请联系IT运维人员处理。'),
  658. ('Q6:传感器数据是否实时更新?',
  659. '当前版本数据为静态模拟数据,页面加载后不会自动刷新传感器数值。如需接入真实IoT传感器数据,需配置WebSocket或轮询接口,由IT运维人员进行后端集成开发。'),
  660. ('Q7:如何修改告警阈值?',
  661. '当前阈值配置在 index-v2.html 的 JavaScript 代码中(LABS数组及判断逻辑),修改对应字段的判断条件即可。生产环境建议将阈值配置抽取为后端可配置参数。'),
  662. ]
  663. for q, a in faqs:
  664. p = doc.add_paragraph()
  665. p.paragraph_format.space_before = Pt(8)
  666. p.paragraph_format.space_after = Pt(2)
  667. add_run(p, q, bold=True, size=11, color=C_H2)
  668. p2 = doc.add_paragraph()
  669. p2.paragraph_format.space_before = Pt(2)
  670. p2.paragraph_format.space_after = Pt(6)
  671. p2.paragraph_format.left_indent = Cm(0.5)
  672. add_run(p2, 'A:' + a, size=10.5, color=C_BODY)
  673. divider()
  674. # ── 页脚说明 ─────────────────────────────────────────────────────
  675. doc.add_paragraph()
  676. p = doc.add_paragraph()
  677. p.alignment = WD_ALIGN_PARAGRAPH.CENTER
  678. add_run(p, '中国安全生产科学研究院 · 实验室安全智能监测与管控中心 · 使用说明文档 V1.0', size=9, color=C_LABEL)
  679. p = doc.add_paragraph()
  680. p.alignment = WD_ALIGN_PARAGRAPH.CENTER
  681. add_run(p, '本文档仅供内部使用,未经许可不得对外发布。', size=9, italic=True, color=C_LABEL)
  682. # ── 保存 ────────────────────────────────────────────────────────
  683. out = r'C:\Users\Administrator\lab-safety-monitor\实验室安全大屏使用说明.docx'
  684. doc.save(out)
  685. print(f'已保存:{out}')