400 lines
21 KiB
JavaScript
400 lines
21 KiB
JavaScript
/**
|
||
* 生成软著软件说明书(用户操作手册)
|
||
*/
|
||
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 Template System', 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 Template System)'),
|
||
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); });
|