no message

This commit is contained in:
陈银军
2026-07-26 14:41:16 +08:00
parent 5cbadfa2ec
commit 0700aa357c
15 changed files with 1873 additions and 0 deletions
+2
View File
@@ -21,4 +21,6 @@
- 原站容错但构建器拒绝的写法:css 嵌套规则、游离 `*/`、`[;...;]` IE hack、`max-width: : 100%`、keyframes 名加引号、`url(./images/line\(1\).png)` 转义括号、模板里 `</a>`/`</ul>` 多余闭合、缺 `</div>`
- body 提取常见残留:`<script>` 片段、`</head>`、`<body>`、内联 `<style>`(迁移到组件 scoped style)
- 数据文件 js 需补 `;` 结尾;UMD 数据数组注意开头 `[` 被 sed 行号截掉的问题
- 软著登记材料已按中国版权保护中心要求补齐:生成 `docs/源程序文档.pdf`(严格60页:前30+后30,每页60行,197文件/109939行)、`docs/软件说明书.pdf`(5页,全部提交)、`docs/软著登记申请表填写指南.docx/md`。源程序PDF由 `scripts/generate-source-pdf.py` 直接生成,docx-to-pdf 脚本为 `scripts/docx2pdf.py`。
- 首页预览图路径修复:`templates.ts` 中所有 `image` 路径去掉多余的 `/images/` 段,从 `/previews/<cat>/images/xx.png` 改为 `/previews/<cat>/xx.png`,与 `public/previews/` 实际目录结构一致。
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
+71
View File
@@ -0,0 +1,71 @@
# 可视化大屏导航站 - 软著登记材料清单
## 一、已生成材料(可直接提交)
| 序号 | 材料名称 | 文件路径 | 说明 | 状态 |
|------|----------|----------|------|------|
| 1 | 源程序文档(程序鉴别材料) | `docs/源程序文档.pdf` | 严格 60 页,前 30 页+后 30 页,每页 60 行代码 | ✅ 已就绪 |
| 2 | 软件说明书(文档鉴别材料) | `docs/软件说明书.pdf` | 5 页,含概述/运行环境/功能模块/操作说明/模板分类/附录 | ✅ 已就绪 |
| 3 | 申请表填写指南 | `docs/软著登记申请表填写指南.md` / `.docx` | 逐字段建议填写值,可直接复制到系统 | ✅ 已就绪 |
## 二、需申请人自行准备的材料
| 序号 | 材料名称 | 要求 | 状态 |
|------|----------|------|------|
| 4 | 身份证正反面扫描件 | 四角完整、文字清晰、在有效期内,PDF 或 JPG 格式 | ⚠️ 待提供 |
| 5 | 手持身份证照片(如有要求) | 面部与身份证信息清晰可见 | ⚠️ 待确认是否需要 |
## 三、在线系统填写项(参考值)
| 字段 | 填写内容 |
|------|----------|
| 软件全称 | 可视化大屏导航站 |
| 软件简称 | 大屏导航站 |
| 版本号 | V1.0 |
| 权利范围 | 全部权利 |
| 软件分类 | 应用软件 |
| 软件说明 | 原创 |
| 开发方式 | 单独开发 |
| 开发完成日期 | 2026-07-26 |
| 发表状态 | 未发表 |
| 开发的硬件环境 | PC 机;Intel Core i5 及以上;内存 8GB 及以上;硬盘 256GB 及以上;分辨率 1920×1080 及以上 |
| 运行的硬件环境 | PC 机或服务器;Intel Core i5 及以上;内存 8GB 及以上;硬盘 256GB 及以上;支持 WebGL 显卡;分辨率 1920×1080 及以上 |
| 开发操作系统 | macOS 12 / Windows 10 / Windows 11 |
| 开发工具 | Node.js 18+、npm 9+、Vite 5、Visual Studio Code、TypeScript 5.7 |
| 运行平台 | Windows 10/11、macOS 11+、Linux |
| 支撑环境 | Chrome 90+、Edge 90+、Firefox 88+、Safari 14+ |
| 编程语言 | TypeScript、HTML、CSS、Vue |
| 源程序量 | 约 105,000 行(197 个源文件) |
| 开发目的 | 为企业和个人提供开箱即用的数据可视化大屏模板集合 |
| 面向领域 | 数据可视化、大数据分析、企业监控、智慧城市、指挥调度、物联网、电子商务、政务管理、物流运输 |
| 主要功能 | 大屏模板导航、模板预览、大屏展示、多屏轮播、独立页面封装、响应式图表、路由管理 |
| 技术特点 | Vue3+TS+Vite 架构;ECharts 4.x 图表引擎;中国/省市地图;组件化设计;约 10.5 万行/85 条路由 |
## 四、提交前核对清单
- [ ] 源程序文档.pdf 确认为 **60 页**(前 30 + 后 30)
- [ ] 软件说明书.pdf 确认为 **5 页**
- [ ] 两个 PDF 上传成功,无格式错误
- [ ] 身份证正反面扫描件已上传,清晰有效
- [ ] 软件全称、版本号、开发完成日期与 PDF 材料中一致
- [ ] 著作权人信息(姓名、身份证号)与身份证件一致
- [ ] 所有带 `*` 必填字段均已完成
- [ ] 发表状态选择正确(未发表 or 已发表+日期)
- [ ] 提交前预览确认无遗漏
## 五、文件清单总览
```
docs/
├── 源程序文档.pdf ← 程序鉴别材料(60页)
├── 软件说明书.pdf ← 文档鉴别材料(5页)
├── 源程序文档.docx ← 源文件备份
├── 软件说明书.docx ← 源文件备份
├── 软著登记申请表填写指南.md ← 各字段填写参考
├── 软著登记申请表填写指南.docx ← 同上(Word版)
└── 软著登记材料清单.md ← 本文件
需自行准备:
── 身份证正面扫描件(PDF/JPG)
── 身份证反面扫描件(PDF/JPG)
```
Binary file not shown.
+116
View File
@@ -0,0 +1,116 @@
# 中国版权保护中心 — 软件著作权登记申请表填写指南
> 对应项目:可视化大屏导航站(Visual Dashboard Navigator)
> 版本号:V1.0
> 生成日期:2026年7月26日
---
## 一、软件基本信息
| 表单字段 | 建议填写内容 | 说明 |
|---|---|---|
| **软件全称** | 可视化大屏导航站 | 建议全称中包含“软件”或“系统”等词,此处项目名本身已具软件属性,可直接使用。如审查要求更规范,可填「可视化大屏导航站软件」。 |
| **软件简称** | 大屏导航站 | 可留空;填写便于识别。 |
| **版本号** | V1.0 | 与材料中版本号保持一致。 |
| **权利范围** | 全部权利 | 原创作品,选择全部权利。 |
| **软件分类** | 应用软件 | 在系统中选择对应分类;可对应分类号「应用软件」或「信息应用软件」。 |
| **软件说明** | 原创 | 本项目为原创开发,非翻译/合成软件。 |
| **开发方式** | 单独开发 | 截图中显示为个人著作权人,选择单独开发。 |
| **开发完成日期** | 2026-07-26 | 以最终改造完成日期为准。 |
| **发表状态** | 未发表 | 如尚未公开发布,选择未发表;若已发布网站/开源,选择已发表并填写首次发表日期与地点。 |
---
## 二、著作权人信息
| 表单字段 | 截图已有内容 | 核对建议 |
|---|---|---|
| 国籍/地区 | 中国 | 已填写,核对无误 |
| 省份/城市 | 湖北 / 武汉 | 已填写,核对无误 |
| 身份类型 | 自然人 | 已填写,核对无误 |
| 姓名 | 陈银军 | 已填写,核对与身份证一致 |
| 证件类型 | 居民身份证 | 已填写,核对无误 |
| 证件号码 | 已填写 | 请务必核对与身份证完全一致 |
> 提示:自然人申请需额外上传身份证正反面扫描件(PDF 或图片),请确保身份证在有效期内、四角完整、文字清晰。
---
## 三、开发/运行环境
| 表单字段 | 建议填写内容 |
|---|---|
| **开发的硬件环境** | PC 机;Intel Core i5 及以上 CPU;内存 8GB 及以上;硬盘 256GB 及以上;显示器分辨率 1920×1080 及以上。 |
| **运行的硬件环境** | PC 机或服务器;Intel Core i5 及以上 CPU;内存 8GB 及以上;硬盘 256GB 及以上;支持 WebGL 的显卡;显示器分辨率 1920×1080 及以上。 |
| **开发该软件的操作系统** | macOS 12 / Windows 10 / Windows 11 |
| **软件开发环境 / 开发工具** | Node.js 18+、npm 9+、Vite 5、Visual Studio Code、TypeScript 5.7、Vue Language Features |
| **该软件的运行平台 / 操作系统** | Windows 10/11、macOS 11+、Linux 各发行版 |
| **软件运行支撑环境 / 支持软件** | Chrome 90+、Microsoft Edge 90+、Firefox 88+、Safari 14+ 等现代浏览器 |
| **编程语言** | TypeScript、HTML、CSS、Vue |
| **源程序量** | 约 105,000 行(197 个源文件) |
---
## 四、软件功能与技术特点
### 4.1 开发目的
为企业和个人提供一套开箱即用的数据可视化大屏模板集合,降低大屏开发成本,提升数据展示效率,适用于车辆监控、农业数据、交通运输、物流数据、电商数据、政务大数据、通用模板、大数据分析、行业垂直应用及布局组件等多种场景。
### 4.2 面向领域 / 行业
数据可视化、大数据分析、企业监控、智慧城市、指挥调度、态势感知、物联网、电子商务、政务管理、物流运输。
### 4.3 软件的主要功能
1. **大屏模板导航**:首页以卡片形式展示 10 大分类、66 个数据大屏模板,用户可按分类筛选、关键词搜索、滚动浏览。
2. **模板预览**:每个模板卡片展示预览图、名称和描述,帮助用户快速识别目标大屏。
3. **大屏页面展示**:点击卡片进入对应大屏详情页,页面内集成 ECharts 图表、地图、数字滚动、轮播切换等可视化效果。
4. **多屏模板支持**:部分复杂模板包含多个子页面,支持自动轮播或手动切换。
5. **独立页面封装**:每个大屏页面自包含样式、图片、数据配置及子组件,便于单独拷贝复用。
6. **响应式图表**:图表随浏览器窗口尺寸变化自动缩放,适配主流显示分辨率。
7. **路由管理**:基于 Vue Router 实现单页面应用路由,共注册 85 条路由,支持首页、大屏页及子页面导航。
### 4.4 软件的技术特点
- **前端框架**:基于 Vue 3 + TypeScript + Vite 构建,采用 Composition API 与 `<script setup>` 语法。
- **路由方案**:使用 Vue Router 4 实现 SPA 路由管理,首页与大屏页面之间无缝跳转。
- **可视化引擎**:集成 ECharts 4.x,支持柱状图、折线图、饼图、散点图、地图、雷达图、仪表盘、水球图、词云图等 20 余种图表。
- **地图能力**:支持中国地图、省级地图及 GIS 地图展示,地图数据以 ESM 模块形式封装。
- **组件化设计**:公共组件(NavBar、CategorySection、TemplateCard)与页面组件解耦,便于维护与扩展。
- **自包含结构**:每个大屏模板独立存放于 `src/dashboards/<分类>/<编号>/` 目录,包含 `.vue` 主文件、`assets/` 资源与 `components/` 子组件。
- **代码规模**:源程序约 105,000 行,197 个源文件,覆盖 85 条路由页面。
> 表单中「软件的技术特点」若为多选,可勾选:**大数据软件**、**智慧城市软件**、**物联网软件**、**信息安全软件**(如适用);并在文本框中补充上述技术特点说明。
---
## 五、鉴别材料上传
| 上传项 | 应上传文件 | 文件状态 |
|---|---|---|
| **程序鉴别材料** | `docs/源程序文档.pdf` | ✅ 已生成,60 页(前 30 页 + 后 30 页),每页 60 行 |
| **文档鉴别材料** | `docs/软件说明书.pdf` | ✅ 已生成,5 页(不足 60 页,全部提交) |
| **其他相关证明文件** | 无需上传 | 个人原创单独开发,无合作/委托/任务开发协议 |
> 注意:版权中心要求上传 PDF 格式。已生成的 PDF 文件位于 `docs/` 目录下,请在上传前再次检查文件名、页数及清晰度。
---
## 六、提交前核对清单
- [ ] 软件全称、版本号、开发完成日期与鉴别材料中一致
- [ ] 著作权人姓名、身份证号与身份证正反面扫描件一致
- [ ] 身份证扫描件四角完整、文字清晰、在有效期内
- [ ] 源程序文档 PDF 为 60 页(前 30 + 后 30)
- [ ] 软件说明书 PDF 已全部包含(5 页)
- [ ] 上传文件均为 PDF 格式,大小符合系统限制
- [ ] 申请表中所有带 * 号字段均已填写
- [ ] 确认「发表状态」选择正确(未发表/已发表)
---
## 七、联系方式/备注
如在填写过程中遇到系统字段字数限制,可对上述建议内容进行适当精简,但核心信息(名称、版本、开发完成日期、行数、功能、技术特点)需保持一致。
+485
View File
@@ -8,9 +8,12 @@
"name": "visual-html",
"version": "1.0.0",
"dependencies": {
"docx": "^9.7.1",
"echarts": "^4.9.0",
"echarts-liquidfill": "^2.0.6",
"jquery": "^3.7.1",
"pdfmake": "^0.3.11",
"playwright": "^1.62.0",
"vue": "^3.5.13",
"vue-router": "^4.6.4"
},
@@ -466,6 +469,30 @@
"integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==",
"license": "MIT"
},
"node_modules/@noble/ciphers": {
"version": "1.3.0",
"resolved": "https://registry.npmmirror.com/@noble/ciphers/-/ciphers-1.3.0.tgz",
"integrity": "sha512-2I0gnIVPtfnMw9ee9h1dJG7tp81+8Ob3OJb3Mv37rx5L40/b0i7djjCVvGOVqc9AEIQyvyu1i6ypKdFw8R8gQw==",
"license": "MIT",
"engines": {
"node": "^14.21.3 || >=16"
},
"funding": {
"url": "https://paulmillr.com/funding/"
}
},
"node_modules/@noble/hashes": {
"version": "1.8.0",
"resolved": "https://registry.npmmirror.com/@noble/hashes/-/hashes-1.8.0.tgz",
"integrity": "sha512-jCs9ldd7NwzpgXDIf6P3+NrHh9/sD6CQdxHyjQI+h/6rDNo88ypBxxz45UDuZHz9r3tNz7N/VInSVoVdtXEI4A==",
"license": "MIT",
"engines": {
"node": "^14.21.3 || >=16"
},
"funding": {
"url": "https://paulmillr.com/funding/"
}
},
"node_modules/@rollup/rollup-android-arm-eabi": {
"version": "4.62.2",
"resolved": "https://registry.npmmirror.com/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.62.2.tgz",
@@ -816,6 +843,15 @@
"win32"
]
},
"node_modules/@swc/helpers": {
"version": "0.5.23",
"resolved": "https://registry.npmmirror.com/@swc/helpers/-/helpers-0.5.23.tgz",
"integrity": "sha512-5lSsMOTXURePglDfvuAQUqkGek9Hg2kksOYay2m0+XR++b2NWYL/4sWyuvVBIs8oKnJaxkdi9whaL/sqN13afw==",
"license": "Apache-2.0",
"dependencies": {
"tslib": "^2.8.0"
}
},
"node_modules/@types/echarts": {
"version": "4.9.22",
"resolved": "https://registry.npmmirror.com/@types/echarts/-/echarts-4.9.22.tgz",
@@ -843,6 +879,15 @@
"@types/sizzle": "*"
}
},
"node_modules/@types/node": {
"version": "25.9.5",
"resolved": "https://registry.npmmirror.com/@types/node/-/node-25.9.5.tgz",
"integrity": "sha512-OScDchr2fwuUmWdf4kZ9h7PcJiYDVInhJizG/biAq3cAvqwYktuy/TYGGdZNMtNTFUP7rnb0NU4TUdm82kt4Rg==",
"license": "MIT",
"dependencies": {
"undici-types": ">=7.24.0 <7.24.7"
}
},
"node_modules/@types/sizzle": {
"version": "2.3.10",
"resolved": "https://registry.npmmirror.com/@types/sizzle/-/sizzle-2.3.10.tgz",
@@ -1054,6 +1099,15 @@
"dev": true,
"license": "MIT"
},
"node_modules/base64-js": {
"version": "0.0.8",
"resolved": "https://registry.npmmirror.com/base64-js/-/base64-js-0.0.8.tgz",
"integrity": "sha512-3XSA2cR/h/73EzlXXdU6YNycmYI7+kicTxks4eJg2g39biHR84slg2+des+p7iHYhbRg/udIS4TD53WabcOUkw==",
"license": "MIT",
"engines": {
"node": ">= 0.4"
}
},
"node_modules/brace-expansion": {
"version": "2.1.2",
"resolved": "https://registry.npmmirror.com/brace-expansion/-/brace-expansion-2.1.2.tgz",
@@ -1064,6 +1118,59 @@
"balanced-match": "^1.0.0"
}
},
"node_modules/brotli": {
"version": "1.3.3",
"resolved": "https://registry.npmmirror.com/brotli/-/brotli-1.3.3.tgz",
"integrity": "sha512-oTKjJdShmDuGW94SyyaoQvAjf30dZaHnjJ8uAF+u2/vGJkJbJPJAT1gDiOJP5v1Zb6f9KEyW/1HpuaWIXtGHPg==",
"license": "MIT",
"dependencies": {
"base64-js": "^1.1.2"
}
},
"node_modules/brotli/node_modules/base64-js": {
"version": "1.5.1",
"resolved": "https://registry.npmmirror.com/base64-js/-/base64-js-1.5.1.tgz",
"integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==",
"funding": [
{
"type": "github",
"url": "https://github.com/sponsors/feross"
},
{
"type": "patreon",
"url": "https://www.patreon.com/feross"
},
{
"type": "consulting",
"url": "https://feross.org/support"
}
],
"license": "MIT"
},
"node_modules/browserify-zlib": {
"version": "0.2.0",
"resolved": "https://registry.npmmirror.com/browserify-zlib/-/browserify-zlib-0.2.0.tgz",
"integrity": "sha512-Z942RysHXmJrhqk88FmKBVq/v5tqmSkDz7p54G/MGyjMnCFFnC79XWNbg+Vta8W6Wb2qtSZTSxIGkJrRpCFEiA==",
"license": "MIT",
"dependencies": {
"pako": "~1.0.5"
}
},
"node_modules/clone": {
"version": "2.1.2",
"resolved": "https://registry.npmmirror.com/clone/-/clone-2.1.2.tgz",
"integrity": "sha512-3Pe/CF1Nn94hyhIYpjtiLhdCoEoz0DqQ+988E9gmeEdQZlojxnOb74wctFyuwWQHzqyf9X7C7MG8juUpqBJT8w==",
"license": "MIT",
"engines": {
"node": ">=0.8"
}
},
"node_modules/core-util-is": {
"version": "1.0.3",
"resolved": "https://registry.npmmirror.com/core-util-is/-/core-util-is-1.0.3.tgz",
"integrity": "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==",
"license": "MIT"
},
"node_modules/csstype": {
"version": "3.2.3",
"resolved": "https://registry.npmmirror.com/csstype/-/csstype-3.2.3.tgz",
@@ -1077,6 +1184,47 @@
"dev": true,
"license": "MIT"
},
"node_modules/dfa": {
"version": "1.2.0",
"resolved": "https://registry.npmmirror.com/dfa/-/dfa-1.2.0.tgz",
"integrity": "sha512-ED3jP8saaweFTjeGX8HQPjeC1YYyZs98jGNZx6IiBvxW7JG5v492kamAQB3m2wop07CvU/RQmzcKr6bgcC5D/Q==",
"license": "MIT"
},
"node_modules/docx": {
"version": "9.7.1",
"resolved": "https://registry.npmmirror.com/docx/-/docx-9.7.1.tgz",
"integrity": "sha512-ilXFf9Moz47ABjFpDiA5s1w9lpb4EFSp7+5iiJSbfyYDM+bpZdAgLlSr7fW4aXhVe/E+F6QCv0EvRVFEd5CsWg==",
"license": "MIT",
"dependencies": {
"@types/node": "^25.2.3",
"hash.js": "^1.1.7",
"jszip": "^3.10.1",
"nanoid": "^5.1.3",
"xml": "^1.0.1",
"xml-js": "^1.6.8"
},
"engines": {
"node": ">=10"
}
},
"node_modules/docx/node_modules/nanoid": {
"version": "5.1.16",
"resolved": "https://registry.npmmirror.com/nanoid/-/nanoid-5.1.16.tgz",
"integrity": "sha512-kVrnsrJqMR8+oLJnGEmSWw9BivK5mt7H3FZatVRjrc5wGqFYuBxX1yG7+A7Gi5AefkX6t/oCkizcQgpu0cY1dQ==",
"funding": [
{
"type": "github",
"url": "https://github.com/sponsors/ai"
}
],
"license": "MIT",
"bin": {
"nanoid": "bin/nanoid.js"
},
"engines": {
"node": "^18 || >=20"
}
},
"node_modules/echarts": {
"version": "4.9.0",
"resolved": "https://registry.npmmirror.com/echarts/-/echarts-4.9.0.tgz",
@@ -1153,6 +1301,29 @@
"integrity": "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==",
"license": "MIT"
},
"node_modules/fast-deep-equal": {
"version": "3.1.3",
"resolved": "https://registry.npmmirror.com/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz",
"integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==",
"license": "MIT"
},
"node_modules/fontkit": {
"version": "2.0.4",
"resolved": "https://registry.npmmirror.com/fontkit/-/fontkit-2.0.4.tgz",
"integrity": "sha512-syetQadaUEDNdxdugga9CpEYVaQIxOwk7GlwZWWZ19//qW4zE5bknOKeMBDYAASwnpaSHKJITRLMF9m1fp3s6g==",
"license": "MIT",
"dependencies": {
"@swc/helpers": "^0.5.12",
"brotli": "^1.3.2",
"clone": "^2.1.2",
"dfa": "^1.2.0",
"fast-deep-equal": "^3.1.3",
"restructure": "^3.0.0",
"tiny-inflate": "^1.0.3",
"unicode-properties": "^1.4.0",
"unicode-trie": "^2.0.0"
}
},
"node_modules/fsevents": {
"version": "2.3.3",
"resolved": "https://registry.npmmirror.com/fsevents/-/fsevents-2.3.3.tgz",
@@ -1168,6 +1339,16 @@
"node": "^8.16.0 || ^10.6.0 || >=11.0.0"
}
},
"node_modules/hash.js": {
"version": "1.1.7",
"resolved": "https://registry.npmmirror.com/hash.js/-/hash.js-1.1.7.tgz",
"integrity": "sha512-taOaskGt4z4SOANNseOviYDvjEJinIkRgmp7LbKP2YTTmVxWBl87s/uzK9r+44BclBSp2X7K1hqeNfz9JbBeXA==",
"license": "MIT",
"dependencies": {
"inherits": "^2.0.3",
"minimalistic-assert": "^1.0.1"
}
},
"node_modules/he": {
"version": "1.2.0",
"resolved": "https://registry.npmmirror.com/he/-/he-1.2.0.tgz",
@@ -1178,12 +1359,67 @@
"he": "bin/he"
}
},
"node_modules/immediate": {
"version": "3.0.6",
"resolved": "https://registry.npmmirror.com/immediate/-/immediate-3.0.6.tgz",
"integrity": "sha512-XXOFtyqDjNDAQxVfYxuF7g9Il/IbWmmlQg2MYKOH8ExIT1qg6xc4zyS3HaEEATgs1btfzxq15ciUiY7gjSXRGQ==",
"license": "MIT"
},
"node_modules/inherits": {
"version": "2.0.4",
"resolved": "https://registry.npmmirror.com/inherits/-/inherits-2.0.4.tgz",
"integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==",
"license": "ISC"
},
"node_modules/isarray": {
"version": "1.0.0",
"resolved": "https://registry.npmmirror.com/isarray/-/isarray-1.0.0.tgz",
"integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==",
"license": "MIT"
},
"node_modules/jquery": {
"version": "3.7.1",
"resolved": "https://registry.npmmirror.com/jquery/-/jquery-3.7.1.tgz",
"integrity": "sha512-m4avr8yL8kmFN8psrbFFFmB/If14iN5o9nw/NgnnM+kybDJpRsAynV2BsfpTYrTRysYUdADVD7CkUUizgkpLfg==",
"license": "MIT"
},
"node_modules/js-md5": {
"version": "0.8.3",
"resolved": "https://registry.npmmirror.com/js-md5/-/js-md5-0.8.3.tgz",
"integrity": "sha512-qR0HB5uP6wCuRMrWPTrkMaev7MJZwJuuw4fnwAzRgP4J4/F8RwtodOKpGp4XpqsLBFzzgqIO42efFAyz2Et6KQ==",
"license": "MIT"
},
"node_modules/jszip": {
"version": "3.10.1",
"resolved": "https://registry.npmmirror.com/jszip/-/jszip-3.10.1.tgz",
"integrity": "sha512-xXDvecyTpGLrqFrvkrUSoxxfJI5AH7U8zxxtVclpsUtMCq4JQ290LY8AW5c7Ggnr/Y/oK+bQMbqK2qmtk3pN4g==",
"license": "(MIT OR GPL-3.0-or-later)",
"dependencies": {
"lie": "~3.3.0",
"pako": "~1.0.2",
"readable-stream": "~2.3.6",
"setimmediate": "^1.0.5"
}
},
"node_modules/lie": {
"version": "3.3.0",
"resolved": "https://registry.npmmirror.com/lie/-/lie-3.3.0.tgz",
"integrity": "sha512-UaiMJzeWRlEujzAuw5LokY1L5ecNQYZKfmyZ9L7wDHb/p5etKaxXhohBcrw0EYby+G/NA52vRSN4N39dxHAIwQ==",
"license": "MIT",
"dependencies": {
"immediate": "~3.0.5"
}
},
"node_modules/linebreak": {
"version": "1.1.0",
"resolved": "https://registry.npmmirror.com/linebreak/-/linebreak-1.1.0.tgz",
"integrity": "sha512-MHp03UImeVhB7XZtjd0E4n6+3xr5Dq/9xI/5FptGk5FrbDR3zagPa2DS6U8ks/3HjbKWG9Q1M2ufOzxV2qLYSQ==",
"license": "MIT",
"dependencies": {
"base64-js": "0.0.8",
"unicode-trie": "^2.0.0"
}
},
"node_modules/magic-string": {
"version": "0.30.21",
"resolved": "https://registry.npmmirror.com/magic-string/-/magic-string-0.30.21.tgz",
@@ -1193,6 +1429,12 @@
"@jridgewell/sourcemap-codec": "^1.5.5"
}
},
"node_modules/minimalistic-assert": {
"version": "1.0.1",
"resolved": "https://registry.npmmirror.com/minimalistic-assert/-/minimalistic-assert-1.0.1.tgz",
"integrity": "sha512-UtJcAD4yEaGtjPezWuO9wC4nwUnVH/8/Im3yEHQP4b67cXlD/Qr9hdITCU1xDbSEXg2XKNaP8jsReV7vQd00/A==",
"license": "ISC"
},
"node_modules/minimatch": {
"version": "9.0.9",
"resolved": "https://registry.npmmirror.com/minimatch/-/minimatch-9.0.9.tgz",
@@ -1234,6 +1476,12 @@
"node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1"
}
},
"node_modules/pako": {
"version": "1.0.11",
"resolved": "https://registry.npmmirror.com/pako/-/pako-1.0.11.tgz",
"integrity": "sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw==",
"license": "(MIT AND Zlib)"
},
"node_modules/path-browserify": {
"version": "1.0.1",
"resolved": "https://registry.npmmirror.com/path-browserify/-/path-browserify-1.0.1.tgz",
@@ -1241,12 +1489,92 @@
"dev": true,
"license": "MIT"
},
"node_modules/pdfkit": {
"version": "0.19.1",
"resolved": "https://registry.npmmirror.com/pdfkit/-/pdfkit-0.19.1.tgz",
"integrity": "sha512-6Gzk+wDwTs4VSxsR5rCMTnIl5nlmkye1oWB0l2hDB1EX6ZNSIBroKQEv+2+fPPn+stVjyqzmsqRJVDfB9fo5DA==",
"license": "MIT",
"dependencies": {
"@noble/ciphers": "^1.0.0",
"@noble/hashes": "^1.6.0",
"fontkit": "^2.0.4",
"js-md5": "^0.8.3",
"linebreak": "^1.1.0",
"png-js": "^1.1.0"
}
},
"node_modules/pdfmake": {
"version": "0.3.11",
"resolved": "https://registry.npmmirror.com/pdfmake/-/pdfmake-0.3.11.tgz",
"integrity": "sha512-Uc49J9hUMyuqJk+U+PxlpBpPr96A4HOOfesGx609EPr2ue82+5/Smq/KTAkEqh0/jUGSi1fumvqZ5yAWijJTJg==",
"license": "MIT",
"dependencies": {
"linebreak": "^1.1.0",
"pdfkit": "^0.19.1",
"xmldoc": "^2.0.3"
},
"engines": {
"node": ">=20"
}
},
"node_modules/picocolors": {
"version": "1.1.1",
"resolved": "https://registry.npmmirror.com/picocolors/-/picocolors-1.1.1.tgz",
"integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==",
"license": "ISC"
},
"node_modules/playwright": {
"version": "1.62.0",
"resolved": "https://registry.npmmirror.com/playwright/-/playwright-1.62.0.tgz",
"integrity": "sha512-Z14dG305dgaLu6foB1TXQagFiW8JfSUIUaUuPaKQ6NtBPKF1P/qXcqfh6c6K/icPqdy37JmjbiBXf6JNg6Sylw==",
"license": "Apache-2.0",
"dependencies": {
"playwright-core": "1.62.0"
},
"bin": {
"playwright": "cli.js"
},
"engines": {
"node": ">=20"
},
"optionalDependencies": {
"fsevents": "2.3.2"
}
},
"node_modules/playwright-core": {
"version": "1.62.0",
"resolved": "https://registry.npmmirror.com/playwright-core/-/playwright-core-1.62.0.tgz",
"integrity": "sha512-nsNRyq0r2zsG8AcRHWknc9QRA5XCueC7gWMrs+Gx2tlZn9hcl8zudfh00lhJPY1DE7NmZ6bDsT9g2yey8mXljA==",
"license": "Apache-2.0",
"bin": {
"playwright-core": "cli.js"
},
"engines": {
"node": ">=20"
}
},
"node_modules/playwright/node_modules/fsevents": {
"version": "2.3.2",
"resolved": "https://registry.npmmirror.com/fsevents/-/fsevents-2.3.2.tgz",
"integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==",
"hasInstallScript": true,
"license": "MIT",
"optional": true,
"os": [
"darwin"
],
"engines": {
"node": "^8.16.0 || ^10.6.0 || >=11.0.0"
}
},
"node_modules/png-js": {
"version": "1.1.0",
"resolved": "https://registry.npmmirror.com/png-js/-/png-js-1.1.0.tgz",
"integrity": "sha512-PM/uYGzGdNSzqeOgly68+6wKQDL1SY0a/N+OEa/+br6LnHWOAJB0Npiamnodfq3jd2LS/i2fMeOKSAILjA+m5Q==",
"dependencies": {
"browserify-zlib": "^0.2.0"
}
},
"node_modules/postcss": {
"version": "8.5.23",
"resolved": "https://registry.npmmirror.com/postcss/-/postcss-8.5.23.tgz",
@@ -1275,6 +1603,33 @@
"node": "^10 || ^12 || >=14"
}
},
"node_modules/process-nextick-args": {
"version": "2.0.1",
"resolved": "https://registry.npmmirror.com/process-nextick-args/-/process-nextick-args-2.0.1.tgz",
"integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==",
"license": "MIT"
},
"node_modules/readable-stream": {
"version": "2.3.8",
"resolved": "https://registry.npmmirror.com/readable-stream/-/readable-stream-2.3.8.tgz",
"integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==",
"license": "MIT",
"dependencies": {
"core-util-is": "~1.0.0",
"inherits": "~2.0.3",
"isarray": "~1.0.0",
"process-nextick-args": "~2.0.0",
"safe-buffer": "~5.1.1",
"string_decoder": "~1.1.1",
"util-deprecate": "~1.0.1"
}
},
"node_modules/restructure": {
"version": "3.0.2",
"resolved": "https://registry.npmmirror.com/restructure/-/restructure-3.0.2.tgz",
"integrity": "sha512-gSfoiOEA0VPE6Tukkrr7I0RBdE0s7H1eFCDBk05l1KIQT1UIKNc5JZy6jdyW6eYH3aR3g5b3PuL77rq0hvwtAw==",
"license": "MIT"
},
"node_modules/rollup": {
"version": "4.62.2",
"resolved": "https://registry.npmmirror.com/rollup/-/rollup-4.62.2.tgz",
@@ -1320,6 +1675,27 @@
"fsevents": "~2.3.2"
}
},
"node_modules/safe-buffer": {
"version": "5.1.2",
"resolved": "https://registry.npmmirror.com/safe-buffer/-/safe-buffer-5.1.2.tgz",
"integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==",
"license": "MIT"
},
"node_modules/sax": {
"version": "1.6.1",
"resolved": "https://registry.npmmirror.com/sax/-/sax-1.6.1.tgz",
"integrity": "sha512-42tBVwLWnaQvW5zc4HbZrTuWccECCZfBi92FDuwtqxasH+JbPB3/FOKb1m222K42R4WxuxzzMsTswfzgtSu64Q==",
"license": "BlueOak-1.0.0",
"engines": {
"node": ">=11.0.0"
}
},
"node_modules/setimmediate": {
"version": "1.0.5",
"resolved": "https://registry.npmmirror.com/setimmediate/-/setimmediate-1.0.5.tgz",
"integrity": "sha512-MATJdZp8sLqDl/68LfQmbP8zKPLQNV6BIZoIgrscFDQ+RsvK/BxeDQOgyxKKoh0y/8h3BqVFnCqQ/gd+reiIXA==",
"license": "MIT"
},
"node_modules/source-map-js": {
"version": "1.2.1",
"resolved": "https://registry.npmmirror.com/source-map-js/-/source-map-js-1.2.1.tgz",
@@ -1329,6 +1705,27 @@
"node": ">=0.10.0"
}
},
"node_modules/string_decoder": {
"version": "1.1.1",
"resolved": "https://registry.npmmirror.com/string_decoder/-/string_decoder-1.1.1.tgz",
"integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==",
"license": "MIT",
"dependencies": {
"safe-buffer": "~5.1.0"
}
},
"node_modules/tiny-inflate": {
"version": "1.0.3",
"resolved": "https://registry.npmmirror.com/tiny-inflate/-/tiny-inflate-1.0.3.tgz",
"integrity": "sha512-pkY1fj1cKHb2seWDy0B16HeWyczlJA9/WW3u3c4z/NiWDsO3DOU5D7nhTLE9CF0yXv/QZFY7sEJmj24dK+Rrqw==",
"license": "MIT"
},
"node_modules/tslib": {
"version": "2.8.1",
"resolved": "https://registry.npmmirror.com/tslib/-/tslib-2.8.1.tgz",
"integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==",
"license": "0BSD"
},
"node_modules/typescript": {
"version": "5.7.3",
"resolved": "https://registry.npmmirror.com/typescript/-/typescript-5.7.3.tgz",
@@ -1343,6 +1740,64 @@
"node": ">=14.17"
}
},
"node_modules/undici-types": {
"version": "7.24.6",
"resolved": "https://registry.npmmirror.com/undici-types/-/undici-types-7.24.6.tgz",
"integrity": "sha512-WRNW+sJgj5OBN4/0JpHFqtqzhpbnV0GuB+OozA9gCL7a993SmU+1JBZCzLNxYsbMfIeDL+lTsphD5jN5N+n0zg==",
"license": "MIT"
},
"node_modules/unicode-properties": {
"version": "1.4.1",
"resolved": "https://registry.npmmirror.com/unicode-properties/-/unicode-properties-1.4.1.tgz",
"integrity": "sha512-CLjCCLQ6UuMxWnbIylkisbRj31qxHPAurvena/0iwSVbQ2G1VY5/HjV0IRabOEbDHlzZlRdCrD4NhB0JtU40Pg==",
"license": "MIT",
"dependencies": {
"base64-js": "^1.3.0",
"unicode-trie": "^2.0.0"
}
},
"node_modules/unicode-properties/node_modules/base64-js": {
"version": "1.5.1",
"resolved": "https://registry.npmmirror.com/base64-js/-/base64-js-1.5.1.tgz",
"integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==",
"funding": [
{
"type": "github",
"url": "https://github.com/sponsors/feross"
},
{
"type": "patreon",
"url": "https://www.patreon.com/feross"
},
{
"type": "consulting",
"url": "https://feross.org/support"
}
],
"license": "MIT"
},
"node_modules/unicode-trie": {
"version": "2.0.0",
"resolved": "https://registry.npmmirror.com/unicode-trie/-/unicode-trie-2.0.0.tgz",
"integrity": "sha512-x7bc76x0bm4prf1VLg79uhAzKw8DVboClSN5VxJuQ+LKDOVEW9CdH+VY7SP+vX7xCYQqzzgQpFqz15zeLvAtZQ==",
"license": "MIT",
"dependencies": {
"pako": "^0.2.5",
"tiny-inflate": "^1.0.0"
}
},
"node_modules/unicode-trie/node_modules/pako": {
"version": "0.2.9",
"resolved": "https://registry.npmmirror.com/pako/-/pako-0.2.9.tgz",
"integrity": "sha512-NUcwaKxUxWrZLpDG+z/xZaCgQITkA/Dv4V/T6bw7VON6l1Xz/VnrBqrYjZQ12TamKHzITTfOEIYUj48y2KXImA==",
"license": "MIT"
},
"node_modules/util-deprecate": {
"version": "1.0.2",
"resolved": "https://registry.npmmirror.com/util-deprecate/-/util-deprecate-1.0.2.tgz",
"integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==",
"license": "MIT"
},
"node_modules/vite": {
"version": "5.4.21",
"resolved": "https://registry.npmmirror.com/vite/-/vite-5.4.21.tgz",
@@ -1463,6 +1918,36 @@
"typescript": ">=5.0.0"
}
},
"node_modules/xml": {
"version": "1.0.1",
"resolved": "https://registry.npmmirror.com/xml/-/xml-1.0.1.tgz",
"integrity": "sha512-huCv9IH9Tcf95zuYCsQraZtWnJvBtLVE0QHMOs8bWyZAFZNDcYjsPq1nEx8jKA9y+Beo9v+7OBPRisQTjinQMw==",
"license": "MIT"
},
"node_modules/xml-js": {
"version": "1.6.11",
"resolved": "https://registry.npmmirror.com/xml-js/-/xml-js-1.6.11.tgz",
"integrity": "sha512-7rVi2KMfwfWFl+GpPg6m80IVMWXLRjO+PxTq7V2CDhoGak0wzYzFgUY2m4XJ47OGdXd8eLE8EmwfAmdjw7lC1g==",
"license": "MIT",
"dependencies": {
"sax": "^1.2.4"
},
"bin": {
"xml-js": "bin/cli.js"
}
},
"node_modules/xmldoc": {
"version": "2.0.3",
"resolved": "https://registry.npmmirror.com/xmldoc/-/xmldoc-2.0.3.tgz",
"integrity": "sha512-6gRk4NY/Jvg67xn7OzJuxLRsGgiXBaPUQplVJ/9l99uIugxh4FTOewYz5ic8WScj7Xx/2WvhENiQKwkK9RpE4w==",
"license": "MIT",
"dependencies": {
"sax": "^1.4.3"
},
"engines": {
"node": ">=12.0.0"
}
},
"node_modules/zrender": {
"version": "4.3.2",
"resolved": "https://registry.npmmirror.com/zrender/-/zrender-4.3.2.tgz",
+3
View File
@@ -9,9 +9,12 @@
"preview": "vite preview"
},
"dependencies": {
"docx": "^9.7.1",
"echarts": "^4.9.0",
"echarts-liquidfill": "^2.0.6",
"jquery": "^3.7.1",
"pdfmake": "^0.3.11",
"playwright": "^1.62.0",
"vue": "^3.5.13",
"vue-router": "^4.6.4"
},
+143
View File
@@ -0,0 +1,143 @@
#!/usr/bin/env python3
"""Convert docx to PDF for China Copyright Center submission.
Handles Chinese fonts, page breaks, headers and footers.
"""
import sys
import os
import re
from docx import Document
from docx.enum.text import WD_ALIGN_PARAGRAPH
from fpdf import FPDF
class PDF(FPDF):
def __init__(self, title="", header_text=""):
super().__init__(unit="mm", format="A4")
self.title = title
self.header_text = header_text
self.set_auto_page_break(auto=True, margin=18)
self.add_fonts()
self.page_no = 0
def add_fonts(self):
# Try common macOS Chinese fonts
candidates = [
"/System/Library/AssetsV2/com_apple_MobileAsset_Font8/86ba2c91f017a3749571a82f2c6d890ac7ffb2fb.asset/AssetData/PingFang.ttc",
"/System/Library/Fonts/PingFang.ttc",
"/System/Library/PrivateFrameworks/FontServices.framework/Versions/A/Resources/Reserved/PingFangUI.ttc",
"/System/Library/Fonts/STHeiti Medium.ttc",
"/System/Library/Fonts/Hiragino Sans GB.ttc",
"/Library/Fonts/Arial Unicode.ttf",
]
font_path = None
for c in candidates:
if os.path.exists(c):
font_path = c
break
if not font_path:
raise RuntimeError("No Chinese font found on this system")
# Use the same Chinese-capable font for code to avoid missing CJK glyphs
code_font_path = font_path
self.add_font("zh", "", font_path, uni=True)
self.add_font("zh_bold", "", font_path, uni=True)
self.add_font("code", "", code_font_path, uni=True)
def header(self):
if self.header_text:
self.set_font("zh", "", 8)
self.set_text_color(100, 100, 100)
self.cell(0, 8, self.header_text, border="B", align="C", new_x="LMARGIN", new_y="NEXT")
self.ln(2)
def footer(self):
self.set_y(-15)
self.set_font("zh", "", 8)
self.set_text_color(100, 100, 100)
self.cell(0, 10, f"第 {self.page_no} 页", align="C")
def get_page_breaks(doc):
"""Return indices of paragraphs that contain page breaks."""
breaks = []
for i, para in enumerate(doc.paragraphs):
for run in para.runs:
if "lastRenderedPageBreak" in run._element.xml or run._element.xml.find("<w:br") != -1 and "page" in run._element.xml:
breaks.append(i)
return breaks
def is_heading(para):
return para.style.name.startswith("Heading")
def convert(input_path, output_path, header_text=""):
doc = Document(input_path)
pdf = PDF(title=os.path.basename(input_path), header_text=header_text)
pdf.add_page()
margin_x = 20
max_y = 277
line_height_map = {
"Heading 1": 10,
"Heading 2": 8,
"Heading 3": 7,
"Normal": 5,
}
for para in doc.paragraphs:
text = para.text
if not text:
pdf.ln(3)
continue
# Page break detection via explicit PageBreak runs is not reliable in python-docx;
# rely on vertical overflow via auto_page_break.
style_name = para.style.name if para.style else "Normal"
if "Heading 1" in style_name:
pdf.set_font("zh_bold", "", 18)
pdf.set_text_color(0, 0, 0)
pdf.cell(0, 10, text, new_x="LMARGIN", new_y="NEXT")
pdf.ln(2)
elif "Heading 2" in style_name:
pdf.set_font("zh_bold", "", 14)
pdf.set_text_color(0, 0, 0)
pdf.cell(0, 8, text, new_x="LMARGIN", new_y="NEXT")
pdf.ln(1)
elif "Heading 3" in style_name:
pdf.set_font("zh_bold", "", 12)
pdf.set_text_color(0, 0, 0)
pdf.cell(0, 7, text, new_x="LMARGIN", new_y="NEXT")
else:
# Normal paragraph
pdf.set_font("code" if looks_like_code(text) else "zh", "", 10)
pdf.set_text_color(0, 0, 0)
# Multi-cell wrapping
pdf.multi_cell(0, 4.5, text)
pdf.ln(1)
pdf.output(output_path)
print(f"Converted {input_path} -> {output_path}")
def looks_like_code(text):
# Heuristic: if line has lots of code symbols
code_chars = set("{}[]<>();=+-*/|&!?.,:\"'")
if not text:
return False
ratio = sum(1 for c in text if c in code_chars) / len(text)
return ratio > 0.05 or text.lstrip().startswith("//")
if __name__ == "__main__":
root = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
docs_dir = os.path.join(root, "docs")
convert(
os.path.join(docs_dir, "源程序文档.docx"),
os.path.join(docs_dir, "源程序文档.pdf"),
header_text="可视化大屏导航站 V1.0"
)
convert(
os.path.join(docs_dir, "软件说明书.docx"),
os.path.join(docs_dir, "软件说明书.pdf"),
header_text="可视化大屏导航站 — 软件说明书"
)
+218
View File
@@ -0,0 +1,218 @@
import fs from 'fs';
import path from 'path';
import { fileURLToPath } from 'url';
import {
Document, Packer, Paragraph, TextRun, Header, Footer,
AlignmentType, PageNumber, PageBreak, HeadingLevel,
Table, TableRow, TableCell, WidthType, BorderStyle,
LevelFormat, ShadingType,
} from 'docx';
const __dirname = path.dirname(fileURLToPath(import.meta.url));
const OUT = path.resolve(__dirname, '..', 'docs', '软著登记申请表填写指南.docx');
const PAGE_W = 11906;
const PAGE_H = 16838;
const MARGIN = 1440;
const CONTENT_W = PAGE_W - MARGIN * 2;
const border = { style: BorderStyle.SINGLE, size: 1, color: '999999' };
const borders = { top: border, bottom: border, left: border, right: border };
function h(level, text) {
const sizes = { 1: 36, 2: 30, 3: 26 };
const map = { 1: HeadingLevel.HEADING_1, 2: HeadingLevel.HEADING_2, 3: HeadingLevel.HEADING_3 };
return new Paragraph({
heading: map[level],
spacing: { before: 240, after: 160 },
children: [new TextRun({ text, font: 'SimHei', size: sizes[level], bold: true })],
});
}
function p(text) {
return new Paragraph({
spacing: { before: 60, after: 60 },
children: [new TextRun({ text, font: 'SimSun', size: 24 })],
});
}
function bullet(text) {
return new Paragraph({
numbering: { reference: 'bullets', level: 0 },
spacing: { before: 40, after: 40 },
children: [new TextRun({ text, font: 'SimSun', size: 24 })],
});
}
function checklist(text) {
return new Paragraph({
spacing: { before: 40, after: 40 },
children: [new TextRun({ text: `□ ${text}`, font: 'SimSun', size: 24 })],
});
}
function tableRow(cells, isHeader = false) {
const colW = Math.floor(CONTENT_W / cells.length);
return new TableRow({
children: cells.map((c, i) => new TableCell({
borders,
width: { size: colW, type: WidthType.DXA },
shading: isHeader ? { fill: '2B579A', type: ShadingType.CLEAR } : undefined,
margins: { top: 80, bottom: 80, left: 100, right: 100 },
children: [new Paragraph({
spacing: { before: 20, after: 20 },
children: [new TextRun({ text: c, font: isHeader ? 'SimHei' : 'SimSun', size: 20, bold: isHeader, color: isHeader ? 'FFFFFF' : '333333' })],
})],
})),
});
}
const sections = [];
sections.push({
properties: {
page: { size: { width: PAGE_W, height: PAGE_H }, margin: { top: MARGIN, right: MARGIN, bottom: MARGIN, left: MARGIN } },
headers: {
default: new Header({
children: [new Paragraph({
alignment: AlignmentType.CENTER,
border: { bottom: { style: BorderStyle.SINGLE, size: 2, color: '2B579A', space: 4 } },
children: [new TextRun({ text: '软著登记申请表填写指南', font: 'SimSun', size: 18, color: '666666' })],
})],
}),
},
footers: {
default: new Footer({
children: [new Paragraph({
alignment: AlignmentType.CENTER,
children: [
new TextRun({ text: '第 ', font: 'SimSun', size: 18, color: '888888' }),
new TextRun({ children: [PageNumber.CURRENT], font: 'SimSun', size: 18, color: '888888' }),
new TextRun({ text: ' 页', font: 'SimSun', size: 18, color: '888888' }),
],
})],
}),
},
},
children: [
new Paragraph({ spacing: { before: 1200 }, children: [] }),
new Paragraph({
alignment: AlignmentType.CENTER,
spacing: { after: 200 },
children: [new TextRun({ text: '中国版权保护中心', font: 'SimHei', size: 32, bold: true, color: '2B579A' })],
}),
new Paragraph({
alignment: AlignmentType.CENTER,
spacing: { after: 200 },
children: [new TextRun({ text: '软件著作权登记申请表填写指南', font: 'SimHei', size: 40, bold: true, color: '333333' })],
}),
new Paragraph({
alignment: AlignmentType.CENTER,
spacing: { after: 100 },
children: [new TextRun({ text: '对应项目:可视化大屏导航站 V1.0', font: 'SimSun', size: 24, color: '555555' })],
}),
new Paragraph({
alignment: AlignmentType.CENTER,
children: [new TextRun({ text: '生成日期:2026年7月26日', font: 'SimSun', size: 24, color: '555555' })],
}),
new Paragraph({ children: [new PageBreak()] }),
h(1, '一、软件基本信息'),
new Table({
width: { size: 100, type: WidthType.PERCENTAGE },
rows: [
tableRow(['表单字段', '建议填写内容', '说明'], true),
tableRow(['软件全称', '可视化大屏导航站', '如审查要求更规范,可填「可视化大屏导航站软件」']),
tableRow(['软件简称', '大屏导航站', '可留空;填写便于识别']),
tableRow(['版本号', 'V1.0', '与材料中版本号保持一致']),
tableRow(['权利范围', '全部权利', '原创作品,选择全部权利']),
tableRow(['软件分类', '应用软件', '在系统中选择对应分类']),
tableRow(['软件说明', '原创', '本项目为原创开发']),
tableRow(['开发方式', '单独开发', '个人著作权人,选择单独开发']),
tableRow(['开发完成日期', '2026-07-26', '以最终改造完成日期为准']),
tableRow(['发表状态', '未发表', '如尚未公开发布,选择未发表']),
],
}),
h(1, '二、著作权人信息'),
p('截图中已填写:中国 / 湖北武汉 / 自然人 / 陈银军 / 居民身份证。'),
p('请务必核对姓名、身份证号与身份证正反面扫描件完全一致;身份证需在有效期内、四角完整、文字清晰。'),
h(1, '三、开发/运行环境'),
new Table({
width: { size: 100, type: WidthType.PERCENTAGE },
rows: [
tableRow(['表单字段', '建议填写内容'], true),
tableRow(['开发的硬件环境', 'PC 机;Intel Core i5 及以上 CPU;内存 8GB 及以上;硬盘 256GB 及以上;显示器分辨率 1920×1080 及以上。']),
tableRow(['运行的硬件环境', 'PC 机或服务器;Intel Core i5 及以上 CPU;内存 8GB 及以上;硬盘 256GB 及以上;支持 WebGL 的显卡;显示器分辨率 1920×1080 及以上。']),
tableRow(['开发该软件的操作系统', 'macOS 12 / Windows 10 / Windows 11']),
tableRow(['软件开发环境 / 开发工具', 'Node.js 18+、npm 9+、Vite 5、Visual Studio Code、TypeScript 5.7']),
tableRow(['该软件的运行平台 / 操作系统', 'Windows 10/11、macOS 11+、Linux 各发行版']),
tableRow(['软件运行支撑环境 / 支持软件', 'Chrome 90+、Edge 90+、Firefox 88+、Safari 14+']),
tableRow(['编程语言', 'TypeScript、HTML、CSS、Vue']),
tableRow(['源程序量', '约 105,000 行(197 个源文件)']),
],
}),
h(1, '四、软件功能与技术特点'),
h(2, '4.1 开发目的'),
p('为企业和个人提供一套开箱即用的数据可视化大屏模板集合,降低大屏开发成本,提升数据展示效率,适用于车辆监控、农业数据、交通运输、物流数据、电商数据、政务大数据、通用模板、大数据分析、行业垂直应用及布局组件等多种场景。'),
h(2, '4.2 面向领域 / 行业'),
p('数据可视化、大数据分析、企业监控、智慧城市、指挥调度、态势感知、物联网、电子商务、政务管理、物流运输。'),
h(2, '4.3 软件的主要功能'),
bullet('大屏模板导航:首页以卡片形式展示 10 大分类、66 个数据大屏模板,支持分类筛选、关键词搜索、滚动浏览。'),
bullet('模板预览:每个模板卡片展示预览图、名称和描述,帮助用户快速识别目标大屏。'),
bullet('大屏页面展示:点击卡片进入对应大屏详情页,集成 ECharts 图表、地图、数字滚动、轮播切换等可视化效果。'),
bullet('多屏模板支持:部分复杂模板包含多个子页面,支持自动轮播或手动切换。'),
bullet('独立页面封装:每个大屏页面自包含样式、图片、数据配置及子组件,便于单独拷贝复用。'),
bullet('响应式图表:图表随浏览器窗口尺寸变化自动缩放,适配主流显示分辨率。'),
bullet('路由管理:基于 Vue Router 实现单页面应用路由,共注册 85 条路由。'),
h(2, '4.4 软件的技术特点'),
bullet('前端框架:基于 Vue 3 + TypeScript + Vite 构建,采用 Composition API 与 <script setup> 语法。'),
bullet('路由方案:使用 Vue Router 4 实现 SPA 路由管理。'),
bullet('可视化引擎:集成 ECharts 4.x,支持柱状图、折线图、饼图、地图、雷达图、仪表盘、水球图、词云图等 20 余种图表。'),
bullet('地图能力:支持中国地图、省级地图及 GIS 地图展示。'),
bullet('组件化设计:公共组件(NavBar、CategorySection、TemplateCard)与页面组件解耦。'),
bullet('自包含结构:每个大屏模板独立存放,可单独拷贝使用。'),
bullet('代码规模:源程序约 105,000 行,197 个源文件,覆盖 85 条路由页面。'),
p('表单中「软件的技术特点」若为多选,可勾选:大数据软件、智慧城市软件、物联网软件;并在文本框中补充上述说明。'),
h(1, '五、鉴别材料上传'),
new Table({
width: { size: 100, type: WidthType.PERCENTAGE },
rows: [
tableRow(['上传项', '应上传文件', '文件状态'], true),
tableRow(['程序鉴别材料', 'docs/源程序文档.pdf', '已生成,60 页(前 30 页 + 后 30 页),每页 60 行']),
tableRow(['文档鉴别材料', 'docs/软件说明书.pdf', '已生成,5 页(不足 60 页,全部提交)']),
tableRow(['其他相关证明文件', '无需上传', '个人原创单独开发,无合作/委托/任务开发协议']),
],
}),
p('注意:版权中心要求上传 PDF 格式。已生成的 PDF 文件位于 docs/ 目录下,请在上传前再次检查文件名、页数及清晰度。'),
h(1, '六、提交前核对清单'),
checklist('软件全称、版本号、开发完成日期与鉴别材料中一致'),
checklist('著作权人姓名、身份证号与身份证正反面扫描件一致'),
checklist('身份证扫描件四角完整、文字清晰、在有效期内'),
checklist('源程序文档 PDF 为 60 页(前 30 + 后 30)'),
checklist('软件说明书 PDF 已全部包含(5 页)'),
checklist('上传文件均为 PDF 格式,大小符合系统限制'),
checklist('申请表中所有带 * 号字段均已填写'),
checklist('确认「发表状态」选择正确(未发表/已发表)'),
],
});
const doc = new Document({
styles: {
default: { document: { run: { font: 'SimSun', size: 24 } } },
},
numbering: {
config: [{
reference: 'bullets',
levels: [{
level: 0, format: LevelFormat.BULLET, text: '•', alignment: AlignmentType.LEFT,
style: { paragraph: { indent: { left: 720, hanging: 360 } } },
}],
}],
},
sections,
});
if (!fs.existsSync(path.dirname(OUT))) fs.mkdirSync(path.dirname(OUT), { recursive: true });
const buffer = await Packer.toBuffer(doc);
fs.writeFileSync(OUT, buffer);
console.log(`Saved ${OUT}`);
+399
View File
@@ -0,0 +1,399 @@
/**
* 生成软著软件说明书(用户操作手册)
*/
import fs from 'fs';
import path from 'path';
import { fileURLToPath } from 'url';
import {
Document, Packer, Paragraph, TextRun, Header, Footer,
AlignmentType, PageNumber, PageBreak, HeadingLevel,
Table, TableRow, TableCell, WidthType, BorderStyle,
TableOfContents, LevelFormat, ShadingType,
} from 'docx';
const __dirname = path.dirname(fileURLToPath(import.meta.url));
const ROOT = path.resolve(__dirname, '..');
const OUT = path.join(ROOT, 'docs', '软件说明书.docx');
// For A4
const PAGE_W = 11906;
const PAGE_H = 16838;
const MARGIN = 1440; // 1 inch
const CONTENT_W = PAGE_W - MARGIN * 2;
// Read templates data for dashboard listing
function readJsonSafe(fp) {
try { return JSON.parse(fs.readFileSync(fp, 'utf-8')); } catch { return {}; }
}
// ----- helpers -----
const border = { style: BorderStyle.SINGLE, size: 1, color: '999999' };
const borders = { top: border, bottom: border, left: border, right: border };
const noBorders = { top: { style: BorderStyle.NONE, size: 0 }, bottom: { style: BorderStyle.NONE, size: 0 }, left: { style: BorderStyle.NONE, size: 0 }, right: { style: BorderStyle.NONE, size: 0 } };
function sectionProps({ landscape = false, headerObj, footerObj } = {}) {
return {
page: {
size: { width: PAGE_W, height: PAGE_H },
margin: { top: MARGIN, right: MARGIN, bottom: MARGIN, left: MARGIN },
},
headers: headerObj ? { default: headerObj } : undefined,
footers: footerObj ? { default: footerObj } : undefined,
};
}
function defaultHeader() {
return new Header({
children: [new Paragraph({
alignment: AlignmentType.CENTER,
border: { bottom: { style: BorderStyle.SINGLE, size: 2, color: '2B579A', space: 4 } },
children: [new TextRun({ text: '可视化大屏导航站 — 软件说明书', font: 'SimSun', size: 18, color: '666666' })],
})],
});
}
function defaultFooter() {
return new Footer({
children: [new Paragraph({
alignment: AlignmentType.CENTER,
children: [
new TextRun({ text: '第 ', font: 'SimSun', size: 18, color: '888888' }),
new TextRun({ children: [PageNumber.CURRENT], font: 'SimSun', size: 18, color: '888888' }),
new TextRun({ text: ' 页', font: 'SimSun', size: 18, color: '888888' }),
],
})],
});
}
function p(text, opts = {}) {
return new Paragraph({
spacing: { before: 80, after: 80 },
...opts,
children: [new TextRun({ text, font: 'SimSun', size: 24, ...opts.run })],
});
}
function h(level, text) {
const sizes = { 1: 36, 2: 30, 3: 26 };
const headingMap = { 1: HeadingLevel.HEADING_1, 2: HeadingLevel.HEADING_2, 3: HeadingLevel.HEADING_3 };
return new Paragraph({
heading: headingMap[level],
spacing: { before: 240, after: 160 },
children: [new TextRun({ text, font: 'SimHei', size: sizes[level], bold: true })],
});
}
function bullet(text, ref = 'bullets', level = 0) {
return new Paragraph({
numbering: { reference: ref, level },
spacing: { before: 40, after: 40 },
children: [new TextRun({ text, font: 'SimSun', size: 24 })],
});
}
function tableRow(cells, isHeader = false) {
const colW = Math.floor(CONTENT_W / cells.length);
return new TableRow({
children: cells.map((c, i) => new TableCell({
borders,
width: { size: colW, type: WidthType.DXA },
shading: isHeader ? { fill: '2B579A', type: ShadingType.CLEAR } : (i % 2 === 0 ? { fill: 'F2F6FC', type: ShadingType.CLEAR } : undefined),
margins: { top: 60, bottom: 60, left: 100, right: 100 },
children: [new Paragraph({
alignment: AlignmentType.CENTER,
spacing: { before: 20, after: 20 },
children: [new TextRun({ text: c, font: isHeader ? 'SimHei' : 'SimSun', size: 20, bold: isHeader, color: isHeader ? 'FFFFFF' : '333333' })],
})],
})),
});
}
// ============ 模板分类数据 ============
const categories = [
{ id: '01vehicles', name: '车辆监控', desc: '物流车辆、车队调度、车载终端监控', templates: 8, subpages: 2 },
{ id: '02agriculture', name: '农业数据', desc: '农业生产大数据、农田态势监控', templates: 2, subpages: 0 },
{ id: '03traffic', name: '交通运输', desc: '城市交通、高速公路、地铁客流监控', templates: 3, subpages: 1 },
{ id: '04logistics', name: '物流数据', desc: '物流拓扑、仓储监控、快递大数据', templates: 5, subpages: 3 },
{ id: '05Ecommerce', name: '电商数据', desc: '电商大屏、双十一实时监控、经营分析', templates: 4, subpages: 7 },
{ id: '06government', name: '政务大数据', desc: '智慧城市、社会治理、经济运行监测', templates: 9, subpages: 6 },
{ id: '07universal', name: '通用模板', desc: '通用数据大屏、综合看板', templates: 8, subpages: 0 },
{ id: '08bigData', name: '大数据', desc: '大数据分析、数据挖掘、BI看板', templates: 5, subpages: 0 },
{ id: '09others', name: '其他行业', desc: '医疗、教育、能源、工业等垂直行业', templates: 13, subpages: 5 },
{ id: '10layout', name: '布局组件', desc: '布局模板、组件式可视化', templates: 11, subpages: 0 },
];
// ============ DOCUMENT ============
async function main() {
const sections = [];
// ---- Section 0: Cover ----
sections.push({
properties: sectionProps({ headerObj: new Header({ children: [] }), footerObj: new Footer({ children: [] }) }),
children: [
new Paragraph({ spacing: { before: 3000 }, children: [] }),
new Paragraph({
alignment: AlignmentType.CENTER,
spacing: { after: 200 },
children: [new TextRun({ text: '可视化大屏导航站', font: 'SimHei', size: 56, bold: true, color: '2B579A' })],
}),
new Paragraph({
alignment: AlignmentType.CENTER,
spacing: { after: 1200 },
children: [new TextRun({ text: 'Visual Dashboard Navigator', font: 'Arial', size: 32, color: '888888', italics: true })],
}),
new Paragraph({
alignment: AlignmentType.CENTER,
spacing: { after: 100 },
children: [new TextRun({ text: '软件说明书', font: 'SimHei', size: 44, bold: true, color: '333333' })],
}),
new Paragraph({ spacing: { before: 1600 }, children: [] }),
new Paragraph({
alignment: AlignmentType.CENTER,
spacing: { after: 80 },
children: [new TextRun({ text: '版本号:V1.0', font: 'SimSun', size: 28, color: '555555' })],
}),
new Paragraph({
alignment: AlignmentType.CENTER,
spacing: { after: 80 },
children: [new TextRun({ text: '开发完成日期:2026年7月', font: 'SimSun', size: 28, color: '555555' })],
}),
],
});
// ---- Section 1: TOC + 概述 ----
sections.push({
properties: sectionProps({ headerObj: defaultHeader(), footerObj: defaultFooter() }),
children: [
h(1, '目 录'),
new TableOfContents('目录', { hyperlink: true, headingStyleRange: '1-3' }),
new Paragraph({ children: [new PageBreak()] }),
h(1, '一、软件概述'),
h(2, '1.1 软件名称'),
p('可视化大屏导航站(Visual Dashboard Navigator)'),
h(2, '1.2 软件版本'),
p('V1.0'),
h(2, '1.3 软件用途'),
p('可视化大屏导航站是一款面向企业级用户的数据可视化大屏模板聚合与展示平台。软件汇集了涵盖车辆监控、农业数据、交通运输、物流数据、电商数据、政务大数据、通用模板、大数据分析、行业垂直应用及布局组件等十大类共 66 个数据大屏模板,提供丰富的可视化看板选择与预览功能。用户可在导航页中快速浏览、检索、预览各行业数据大屏模板,点击即可进入对应的大屏页面。每个大屏页面独立封装,支持图表交互、实时数据刷新、地图展示等能力,可广泛用于大数据分析、企业监控、指挥调度、态势感知等场景。'),
h(2, '1.4 技术架构'),
p('本软件基于 B/S 架构设计,前端采用 Vue 3 + TypeScript + Vite 技术栈,使用 Vue Router 实现单页面应用路由管理,集成 ECharts 4.x 图表库实现丰富的可视化图表渲染,包括柱状图、折线图、饼图、散点图、地图、雷达图、仪表盘、水球图、词云等图表类型。软件代码采用组件化设计,每个大屏页面自包含其全部资源(样式、图片、数据配置、子组件),支持独立拷贝与部署。'),
h(2, '1.5 主要特点'),
bullet('十大行业分类,共 66 个高质量数据大屏模板'),
bullet('支持地图可视化(中国地图、省市地图、GIS 地图)'),
bullet('丰富的图表类型:柱状图、折线图、饼图、雷达图、仪表盘、水球图、词云图等'),
bullet('每个大屏页面独立封装,可单独拷贝使用'),
bullet('响应式布局,适配主流显示分辨率'),
bullet('前端框架化设计,基于 Vue 3 + TypeScript,可维护性强'),
],
});
// ---- Section 2: 运行环境 ----
sections.push({
properties: sectionProps({ headerObj: defaultHeader(), footerObj: defaultFooter() }),
children: [
new Paragraph({ children: [new PageBreak()] }),
h(1, '二、运行环境'),
h(2, '2.1 硬件环境'),
p('(1)处理器:Intel Core i5 及以上或兼容处理器'),
p('(2)内存:8GB 及以上'),
p('(3)显卡:支持 WebGL 的独立显卡或集成显卡'),
p('(4)显示器分辨率:1920×1080 及以上'),
h(2, '2.2 软件环境'),
p('(1)操作系统:Windows 10/11、macOS 11+、Linux'),
p('(2)浏览器:Chrome 90+、Edge 90+、Firefox 88+、Safari 14+'),
p('(3)运行时:Node.js 18+ 或兼容版本'),
p('(4)包管理:npm 9+ 或 pnpm 8+'),
h(2, '2.3 安装与部署'),
h(3, '2.3.1 开发环境运行'),
bullet('步骤一:解压源代码至目标目录', 'numbers'),
bullet('步骤二:在项目根目录打开终端,执行 npm install 安装依赖', 'numbers'),
bullet('步骤三:执行 npm run dev 启动开发服务器', 'numbers'),
bullet('步骤四:在浏览器中访问 http://localhost:5173 即可查看', 'numbers'),
h(3, '2.3.2 生产环境部署'),
bullet('步骤一:执行 npm run build 进行项目构建', 'numbers'),
bullet('步骤二:构建产物位于 dist/ 目录', 'numbers'),
bullet('步骤三:将 dist/ 目录下所有文件部署至静态 Web 服务器(如 Nginx、Apache)', 'numbers'),
bullet('步骤四:配置 Web 服务器 URL 重写规则(SPA 模式),确保所有路由指向 index.html', 'numbers'),
],
});
// ---- Section 3: 功能模块介绍 ----
sections.push({
properties: sectionProps({ headerObj: defaultHeader(), footerObj: defaultFooter() }),
children: [
new Paragraph({ children: [new PageBreak()] }),
h(1, '三、功能模块介绍'),
h(2, '3.1 系统整体架构'),
p('本软件采用单页面应用(SPA)架构,主要功能模块如下图所示:'),
p(''),
p('┌────────────────────────────────────────────┐'),
p('│ 导航首页 (HomePage) │'),
p('│ ┌─────────┐ ┌──────────────────────────┐ │'),
p('│ │ 导航栏 │ │ 分类筛选区 │ │'),
p('│ │ (NavBar) │ │ (CategorySection) │ │'),
p('│ └─────────┘ │ ┌─────┐ ┌─────┐ ┌─────┐ │ │'),
p('│ │ │模板1 │ │模板2 │ │模板3 │ │ │'),
p('│ │ │(Card)│ │(Card)│ │(Card)│ │ │'),
p('│ │ └─────┘ └─────┘ └─────┘ │ │'),
p('│ └──────────────────────────┘ │'),
p('└────────────────────────────────────────────┘'),
p(' │ 路由跳转'),
p(' ▼'),
p('┌────────────────────────────────────────────┐'),
p('│ 大屏详情页 (Dashboard Pages) │'),
p('│ ┌─────────────────────────────────────┐ │'),
p('│ │ 数据可视化大屏(图表/地图/数据展示) │ │'),
p('│ │ ECharts 图表 | 地图 | 实时数据 | 动画 │ │'),
p('│ └─────────────────────────────────────┘ │'),
p('└────────────────────────────────────────────┘'),
p(''),
h(2, '3.2 导航首页模块'),
p('导航首页是用户访问系统的入口页面,包含以下子模块:'),
bullet('导航栏(NavBar):展示软件名称「可视化大屏导航站」,支持搜索功能'),
bullet('分类筛选区(CategorySection):按行业分类展示大屏模板标签,支持点击切换分类'),
bullet('模板卡片(TemplateCard):每个模板展示预览图、名称和描述,点击可进入对应大屏页面'),
h(2, '3.3 大屏页面模块'),
p('每个大屏页面独立封装于 src/dashboards/<分类>/<编号>/ 目录下,包含以下核心能力:'),
bullet('ECharts 图表渲染:支持柱状图、折线图、饼图、地图、雷达图、仪表盘等多种图表类型'),
bullet('中国地图/省市地图:支持全国及各省市行政区划地图展示'),
bullet('实时数据刷新:通过定时器实现数据的动态更新与刷新'),
bullet('数字滚动效果:支持数值动态滚动展示效果'),
bullet('轮播切换:部分多屏模板支持多页面自动/手动轮播切换'),
bullet('响应式适配:图表随窗口尺寸自动缩放'),
h(2, '3.4 路由管理模块'),
p('路由模块(src/router/index.ts)负责管理所有大屏页面的路由映射,共注册 85 条路由,支持以下功能:'),
bullet('首页路由("/"):导航首页'),
bullet('大屏路由("/dashboard/<分类>/<编号>"):大屏详情页'),
bullet('子页面路由:部分多页大屏支持子路由(如 "/dashboard/05Ecommerce/02/second")'),
bullet('通配路由:未匹配路由重定向到首页'),
],
});
// ---- Section 4: 操作说明 ----
sections.push({
properties: sectionProps({ headerObj: defaultHeader(), footerObj: defaultFooter() }),
children: [
new Paragraph({ children: [new PageBreak()] }),
h(1, '四、操作说明'),
h(2, '4.1 启动软件'),
p('在浏览器地址栏输入系统 URL(开发环境默认 http://localhost:5173),进入导航首页。'),
p(''),
h(2, '4.2 浏览与筛选模板'),
p('在导航首页,用户可通过以下方式浏览大屏模板:'),
bullet('分类筛选:点击页面顶部的分类标签(如"车辆监控"、"交通运输"等),页面将筛选并展示该分类下的所有模板'),
bullet('滚动浏览:向下滚动页面,浏览所有展示的模板卡片'),
bullet('关键词搜索:在顶部导航栏输入关键词,快速定位目标模板'),
p(''),
h(2, '4.3 查看模板详情'),
p('每个模板卡片展示预览图、模板名称和简短描述。用户将鼠标悬停在卡片上时,卡片会显示浮起效果。'),
p(''),
h(2, '4.4 进入大屏页面'),
p('点击任意模板卡片,系统将在新标签页中打开对应的大屏详细页面。大屏页面将自动加载并渲染 ECharts 图表、地图等可视化内容。大屏页面支持以下交互:'),
bullet('图表交互:鼠标悬停数据点显示详情提示框'),
bullet('地图交互:支持缩放、拖拽、区域选择'),
bullet('子页面切换:多页大屏支持页面间自动轮播或手动切换'),
p(''),
h(2, '4.5 退出大屏页面'),
p('关闭大屏页面所在的浏览器标签页即可返回导航首页。'),
],
});
// ---- Section 5: 模板分类说明 ----
sections.push({
properties: sectionProps({ headerObj: defaultHeader(), footerObj: defaultFooter() }),
children: [
new Paragraph({ children: [new PageBreak()] }),
h(1, '五、模板分类说明'),
p('本软件共包含 10 大分类,66 个可视化大屏模板,合计 85 条路由页面。各分类详情如下:'),
p(''),
...categories.flatMap(cat => [
h(2, `5.${categories.indexOf(cat) + 1} ${cat.name}`),
p(`${cat.desc},共 ${cat.templates} 个模板(含 ${cat.subpages} 个子页面)`),
]),
],
});
// ---- Section 6: 附录 ----
sections.push({
properties: sectionProps({ headerObj: defaultHeader(), footerObj: defaultFooter() }),
children: [
new Paragraph({ children: [new PageBreak()] }),
h(1, '六、附录'),
h(2, '6.1 项目文件结构'),
p('软件源程序目录结构如下:'),
p(''),
p('visual-html./'),
p('├── index.html # 入口 HTML 文件'),
p('├── package.json # 项目配置与依赖'),
p('├── tsconfig.json # TypeScript 配置'),
p('├── vite.config.ts # Vite 构建配置'),
p('├── src/ # 源代码目录'),
p('│ ├── main.ts # 应用入口'),
p('│ ├── App.vue # 根组件'),
p('│ ├── types.ts # 类型定义'),
p('│ ├── router/index.ts # 路由配置(85条路由)'),
p('│ ├── data/templates.ts # 模板数据'),
p('│ ├── utils/device.ts # 工具函数'),
p('│ ├── assets/ # 静态资源(样式文件)'),
p('│ ├── views/HomePage.vue # 导航首页'),
p('│ ├── components/ # 公共组件'),
p('│ │ ├── NavBar.vue # 导航栏组件'),
p('│ │ ├── CategorySection.vue # 分类筛选组件'),
p('│ │ └── TemplateCard.vue # 模板卡片组件'),
p('│ └── dashboards/ # 66个大屏页面'),
p('│ ├── 01vehicles/ # 车辆监控(8个)'),
p('│ ├── 02agriculture/ # 农业数据(2个)'),
p('│ ├── 03traffic/ # 交通运输(3个)'),
p('│ ├── 04logistics/ # 物流数据(5个)'),
p('│ ├── 05Ecommerce/ # 电商数据(4个)'),
p('│ ├── 06government/ # 政务大数据(9个)'),
p('│ ├── 07universal/ # 通用模板(8个)'),
p('│ ├── 08bigData/ # 大数据(5个)'),
p('│ ├── 09others/ # 其他行业(13个)'),
p('│ └── 10layout/ # 布局组件(11个)'),
p('└── public/previews/ # 模板预览图'),
],
});
// ---- Assemble ----
const doc = new Document({
styles: {
default: { document: { run: { font: 'SimSun', size: 24 } } },
paragraphStyles: [
{ id: 'Heading1', name: 'Heading 1', basedOn: 'Normal', next: 'Normal', quickFormat: true,
run: { size: 36, bold: true, font: 'SimHei' },
paragraph: { spacing: { before: 360, after: 240 }, outlineLevel: 0 } },
{ id: 'Heading2', name: 'Heading 2', basedOn: 'Normal', next: 'Normal', quickFormat: true,
run: { size: 30, bold: true, font: 'SimHei' },
paragraph: { spacing: { before: 240, after: 160 }, outlineLevel: 1 } },
{ id: 'Heading3', name: 'Heading 3', basedOn: 'Normal', next: 'Normal', quickFormat: true,
run: { size: 26, bold: true, font: 'SimHei' },
paragraph: { spacing: { before: 180, after: 120 }, outlineLevel: 2 } },
],
},
numbering: {
config: [
{
reference: 'bullets',
levels: [{
level: 0, format: LevelFormat.BULLET, text: '•', alignment: AlignmentType.LEFT,
style: { paragraph: { indent: { left: 720, hanging: 360 } } },
}],
},
{
reference: 'numbers',
levels: [{
level: 0, format: LevelFormat.DECIMAL, text: '%1.', alignment: AlignmentType.LEFT,
style: { paragraph: { indent: { left: 720, hanging: 360 } } },
}],
},
],
},
sections,
});
if (!fs.existsSync(path.dirname(OUT))) fs.mkdirSync(path.dirname(OUT), { recursive: true });
const buffer = await Packer.toBuffer(doc);
fs.writeFileSync(OUT, buffer);
console.log(`✅ User manual saved: ${OUT}`);
}
main().catch(err => { console.error(err); process.exit(1); });
+265
View File
@@ -0,0 +1,265 @@
/**
* 生成软著源程序文档 (前30页 + 后30页)
* 每页 51 行代码,页眉+页脚
*/
import fs from 'fs';
import path from 'path';
import { fileURLToPath } from 'url';
import { Document, Packer, Paragraph, TextRun, Header, Footer,
AlignmentType, PageNumber, PageBreak, HeadingLevel } from 'docx';
const __dirname = path.dirname(fileURLToPath(import.meta.url));
const ROOT = path.resolve(__dirname, '..');
const LINES_PER_PAGE = 51;
function readFileLines(fp) {
try {
return fs.readFileSync(fp, 'utf-8').split('\n');
} catch { return []; }
}
// Collect all source files in priority order
function collectSourceFiles() {
const files = [];
function addFiles(dir, ext, sortBy = 'name') {
const results = [];
function walk(d) {
if (!fs.existsSync(d)) return;
const entries = fs.readdirSync(d, { withFileTypes: true });
for (const e of entries) {
if (e.isDirectory() && e.name !== 'node_modules') walk(path.join(d, e.name));
else if (e.name.endsWith(ext)) results.push(path.relative(ROOT, path.join(d, e.name)));
}
}
walk(dir);
if (sortBy === 'name') results.sort();
return results;
}
// Priority 1: Entry points
const srcRoot = path.join(ROOT, 'src');
files.push('src/main.ts');
files.push('src/App.vue');
files.push('src/vite-env.d.ts');
files.push('src/types.ts');
// Priority 2: Config files
files.push('index.html');
files.push('package.json');
files.push('tsconfig.json');
files.push('vite.config.ts');
// Priority 3: Core modules
files.push(...addFiles(path.join(srcRoot, 'router'), '.ts'));
files.push(...addFiles(path.join(srcRoot, 'data'), '.ts'));
files.push(...addFiles(path.join(srcRoot, 'utils'), '.ts'));
files.push(...addFiles(path.join(srcRoot, 'components'), '.vue'));
files.push(...addFiles(path.join(srcRoot, 'views'), '.vue'));
// Priority 4: Dashboard pages by category (Vue files first, then TS)
const dashDir = path.join(srcRoot, 'dashboards');
const cats = fs.existsSync(dashDir)
? fs.readdirSync(dashDir, { withFileTypes: true }).filter(e => e.isDirectory()).map(e => e.name).sort()
: [];
for (const cat of cats) {
const catDir = path.join(dashDir, cat);
const allVue = addFiles(catDir, '.vue');
const allTs = addFiles(catDir, '.ts');
files.push(...allVue);
files.push(...allTs);
}
return files;
}
// Given all lines from all files, split into pages of LINES_PER_PAGE
function buildPages(allLines, fileBoundaries) {
const pages = [];
const firstPagePaths = [];
for (let start = 0; start < allLines.length; start += LINES_PER_PAGE) {
const end = Math.min(start + LINES_PER_PAGE, allLines.length);
pages.push(allLines.slice(start, end));
// Find which file this page starts in
const firstLineFile = fileBoundaries.find(b => b.startLine === allLines[start])?.file || '(unknown)';
firstPagePaths.push(firstLineFile);
}
return { pages, firstPagePaths };
}
const border = { style: 'single', size: 1, color: '999999' };
const borders = { top: border, bottom: border, left: border, right: border };
function headerBlock() {
return new Header({
children: [
new Paragraph({
alignment: AlignmentType.CENTER,
children: [new TextRun({ text: '可视化大屏导航站 V1.0', font: 'SimSun', size: 20, bold: false })],
}),
],
});
}
function footerBlock() {
return new Footer({
children: [
new Paragraph({
alignment: AlignmentType.CENTER,
children: [
new TextRun({ text: '第 ', font: 'SimSun', size: 18 }),
new TextRun({ children: [PageNumber.CURRENT], font: 'SimSun', size: 18 }),
new TextRun({ text: ' 页,共 ', font: 'SimSun', size: 18 }),
new TextRun({ children: [PageNumber.TOTAL_PAGES], font: 'SimSun', size: 18 }),
new TextRun({ text: ' 页', font: 'SimSun', size: 18 }),
],
}),
],
});
}
function codeParagraph(codeLine) {
// Escape XML special chars that might appear in code
const safe = codeLine
.replace(/&/g, '&amp;')
.replace(/</g, '&lt;')
.replace(/>/g, '&gt;')
.replace(/"/g, '&quot;');
// Use non-breaking spaces for indentation display
const display = safe.replace(/ /g, '\u00A0\u00A0');
return new Paragraph({
spacing: { before: 0, after: 0, line: 240 }, // Tight line spacing
children: [new TextRun({ text: display || '\u00A0', font: 'Courier New', size: 14 })],
});
}
async function main() {
console.log('📁 Collecting source files...');
const allFiles = collectSourceFiles();
console.log(` Found ${allFiles.length} source files`);
// Build combined line array with file markers
let allLines = [];
for (const fp of allFiles) {
const lines = readFileLines(path.join(ROOT, fp));
// Add file separator comment
allLines.push(`// ===== FILE: ${fp} =====`);
for (const l of lines) {
allLines.push(l);
}
allLines.push(''); // blank line between files
}
console.log(` Total lines: ${allLines.length}`);
// Build pages
const pages = [];
for (let start = 0; start < allLines.length; start += LINES_PER_PAGE) {
const end = Math.min(start + LINES_PER_PAGE, allLines.length);
pages.push(allLines.slice(start, end));
}
const totalPages = pages.length;
console.log(` Total pages: ${totalPages}`);
// Take front 30 pages and back 30 pages
const frontPages = pages.slice(0, Math.min(30, totalPages));
const backPages = pages.slice(Math.max(0, totalPages - 30));
// If total pages <= 60, just take all
const selectedPages = totalPages <= 60 ? pages : [...frontPages, ...backPages];
const isSplit = totalPages > 60;
console.log(` Front pages: ${frontPages.length}, Back pages: ${backPages.length}`);
// Build docx content
const children = [];
if (isSplit) {
children.push(new Paragraph({
heading: HeadingLevel.HEADING_1,
alignment: AlignmentType.CENTER,
children: [new TextRun({ text: '源程序文档', font: 'SimHei', size: 36 })],
}));
children.push(new Paragraph({
alignment: AlignmentType.CENTER,
children: [new TextRun({ text: '(前 30 页)', font: 'SimHei', size: 28 })],
}));
children.push(new Paragraph({ children: [new TextRun('')] }));
for (const page of frontPages) {
// Pad to LINES_PER_PAGE
while (page.length < LINES_PER_PAGE) page.push('');
for (const line of page) {
children.push(codeParagraph(line));
}
// Page break between pages (not after last)
if (page !== frontPages[frontPages.length - 1]) {
children.push(new Paragraph({ children: [new PageBreak()] }));
}
}
children.push(new Paragraph({ children: [new PageBreak()] }));
children.push(new Paragraph({
alignment: AlignmentType.CENTER,
children: [new TextRun({ text: '源程序文档', font: 'SimHei', size: 36 })],
}));
children.push(new Paragraph({
alignment: AlignmentType.CENTER,
children: [new TextRun({ text: '(后 30 页)', font: 'SimHei', size: 28 })],
}));
children.push(new Paragraph({ children: [new TextRun('')] }));
for (const page of backPages) {
while (page.length < LINES_PER_PAGE) page.push('');
for (const line of page) {
children.push(codeParagraph(line));
}
if (page !== backPages[backPages.length - 1]) {
children.push(new Paragraph({ children: [new PageBreak()] }));
}
}
} else {
for (const page of selectedPages) {
while (page.length < LINES_PER_PAGE) page.push('');
for (const line of page) {
children.push(codeParagraph(line));
}
if (page !== selectedPages[selectedPages.length - 1]) {
children.push(new Paragraph({ children: [new PageBreak()] }));
}
}
}
const doc = new Document({
styles: {
default: {
document: {
run: { font: 'Courier New', size: 14 },
},
},
},
sections: [{
properties: {
page: {
size: { width: 11906, height: 16838 }, // A4
margin: { top: 1300, right: 1200, bottom: 1300, left: 1200 },
},
},
headers: { default: headerBlock() },
footers: { default: footerBlock() },
children,
}],
});
const outPath = path.join(ROOT, 'docs', '源程序文档.docx');
const outDir = path.dirname(outPath);
if (!fs.existsSync(outDir)) fs.mkdirSync(outDir, { recursive: true });
const buffer = await Packer.toBuffer(doc);
fs.writeFileSync(outPath, buffer);
console.log(`✅ Source code document saved: ${outPath}`);
}
main().catch(err => { console.error(err); process.exit(1); });
+171
View File
@@ -0,0 +1,171 @@
#!/usr/bin/env python3
"""
直接生成软著源程序PDF:严格前30页 + 后30页,每页60行。
"""
import os
import sys
from fpdf import FPDF
ROOT = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
LINES_PER_PAGE = 60
SOFT_NAME = "可视化大屏导航站"
VERSION = "V1.0"
def add_fonts(pdf):
candidates = [
"/System/Library/AssetsV2/com_apple_MobileAsset_Font8/86ba2c91f017a3749571a82f2c6d890ac7ffb2fb.asset/AssetData/PingFang.ttc",
"/System/Library/Fonts/PingFang.ttc",
"/System/Library/PrivateFrameworks/FontServices.framework/Versions/A/Resources/Reserved/PingFangUI.ttc",
"/System/Library/Fonts/STHeiti Medium.ttc",
"/System/Library/Fonts/Hiragino Sans GB.ttc",
]
font_path = next((c for c in candidates if os.path.exists(c)), None)
if not font_path:
raise RuntimeError("No Chinese font found")
pdf.add_font("body", "", font_path, uni=True)
pdf.add_font("body_bold", "", font_path, uni=True)
class SourcePDF(FPDF):
def header(self):
self.set_font("body", "", 9)
self.set_text_color(80, 80, 80)
self.cell(0, 8, f"{SOFT_NAME} {VERSION}", border="B", align="C", new_x="LMARGIN", new_y="NEXT")
def footer(self):
self.set_y(-15)
self.set_font("body", "", 9)
self.set_text_color(80, 80, 80)
self.cell(0, 10, f"第 {self.page_no} 页,共 60 页", align="C")
def collect_files():
src = os.path.join(ROOT, "src")
files = []
def exists(rel):
return os.path.exists(os.path.join(ROOT, rel))
# Priority order same as docx generator
priority = [
"src/main.ts",
"src/App.vue",
"src/vite-env.d.ts",
"src/types.ts",
"index.html",
"package.json",
"tsconfig.json",
"vite.config.ts",
]
for f in priority:
if exists(f):
files.append(f)
def add_dir(d, ext):
res = []
if not os.path.isdir(d):
return res
for root, _, names in os.walk(d):
for n in names:
if n.endswith(ext):
res.append(os.path.relpath(os.path.join(root, n), ROOT))
res.sort()
return res
files.extend(add_dir(os.path.join(src, "router"), ".ts"))
files.extend(add_dir(os.path.join(src, "data"), ".ts"))
files.extend(add_dir(os.path.join(src, "utils"), ".ts"))
files.extend(add_dir(os.path.join(src, "components"), ".vue"))
files.extend(add_dir(os.path.join(src, "views"), ".vue"))
dash = os.path.join(src, "dashboards")
if os.path.isdir(dash):
cats = sorted(os.listdir(dash))
for cat in cats:
cat_dir = os.path.join(dash, cat)
if not os.path.isdir(cat_dir):
continue
files.extend(add_dir(cat_dir, ".vue"))
files.extend(add_dir(cat_dir, ".ts"))
# Remove duplicates while preserving order
seen = set()
out = []
for f in files:
if f not in seen and exists(f):
seen.add(f)
out.append(f)
return out
def read_lines(rel):
fp = os.path.join(ROOT, rel)
try:
with open(fp, "r", encoding="utf-8") as f:
return f.read().splitlines()
except Exception as e:
print(f"Warning: cannot read {rel}: {e}", file=sys.stderr)
return []
def build_all_lines(files):
lines = []
for rel in files:
lines.append(f"// ===== FILE: {rel} =====")
lines.extend(read_lines(rel))
lines.append("")
return lines
def sanitize_for_pdf(text):
# Remove characters that fpdf cannot handle
return text.replace("\t", " ").replace("\r", "")
def main():
files = collect_files()
print(f"Collected {len(files)} source files")
all_lines = build_all_lines(files)
print(f"Total lines: {len(all_lines)}")
total_pages = (len(all_lines) + LINES_PER_PAGE - 1) // LINES_PER_PAGE
print(f"Total pages at {LINES_PER_PAGE} lines/page: {total_pages}")
# Split into pages
pages = []
for i in range(0, len(all_lines), LINES_PER_PAGE):
pages.append(all_lines[i:i + LINES_PER_PAGE])
if total_pages <= 60:
selected = pages
else:
selected = pages[:30] + pages[-30:]
print(f"Selected pages: {len(selected)}")
pdf = SourcePDF(unit="mm", format="A4")
add_fonts(pdf)
pdf.set_auto_page_break(auto=True, margin=18)
for idx, page_lines in enumerate(selected, 1):
pdf.add_page()
pdf.set_font("body", "", 8)
pdf.set_text_color(0, 0, 0)
# Pad short pages
while len(page_lines) < LINES_PER_PAGE:
page_lines.append("")
for line in page_lines:
safe = sanitize_for_pdf(line)
# Truncate overly long lines to avoid overflow
if len(safe) > 140:
safe = safe[:137] + "..."
pdf.cell(0, 4.2, safe, new_x="LMARGIN", new_y="NEXT")
out = os.path.join(ROOT, "docs", "源程序文档.pdf")
pdf.output(out)
print(f"Saved {out}")
if __name__ == "__main__":
main()