You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
software/generate_soldier_app_doc.js

483 lines
33 KiB

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

const { Document, Packer, Paragraph, TextRun, Table, TableRow, TableCell, ImageRun,
Header, Footer, AlignmentType, PageOrientation, LevelFormat, ExternalHyperlink,
InternalHyperlink, Bookmark, FootnoteReferenceRun, PositionalTab,
PositionalTabAlignment, PositionalTabRelativeTo, PositionalTabLeader,
TabStopType, TabStopPosition, Column, SectionType,
TableOfContents, HeadingLevel, BorderStyle, WidthType, ShadingType,
VerticalAlign, PageNumber, PageBreak } = require('docx');
const fs = require('fs');
// 辅助函数:创建代码块样式段落
function codeBlock(lines) {
const border = { style: BorderStyle.SINGLE, size: 1, color: "E0E0E0" };
const borders = { top: border, bottom: border, left: border, right: border };
return new Table({
width: { size: 9360, type: WidthType.DXA },
columnWidths: [9360],
rows: [
new TableRow({
children: [
new TableCell({
borders,
width: { size: 9360, type: WidthType.DXA },
shading: { fill: "F5F5F5", type: ShadingType.CLEAR },
margins: { top: 100, bottom: 100, left: 120, right: 120 },
children: lines.map(line => new Paragraph({
spacing: { before: 0, after: 0, line: 276 },
children: [new TextRun({ font: "Consolas", size: 18, text: line || " " })]
}))
})
]
})
]
});
}
// 辅助函数:创建正文段落
function bodyPara(text, opts = {}) {
return new Paragraph({
spacing: { before: 120, after: 120, line: 360 },
children: [new TextRun({ font: "宋体", size: 24, text, ...opts })]
});
}
// 辅助函数:创建加粗正文
function boldPara(text) {
return bodyPara(text, { bold: true });
}
// 辅助函数:创建小节标题
function subHeading(text, level = HeadingLevel.HEADING_2) {
return new Paragraph({
heading: level,
spacing: { before: 240, after: 120 },
children: [new TextRun({ text, bold: true, font: "黑体", size: level === HeadingLevel.HEADING_1 ? 32 : (level === HeadingLevel.HEADING_2 ? 28 : 26) })]
});
}
const doc = new Document({
styles: {
default: { document: { run: { font: "宋体", size: 24 } } },
paragraphStyles: [
{ id: "Heading1", name: "Heading 1", basedOn: "Normal", next: "Normal", quickFormat: true,
run: { size: 32, bold: true, font: "黑体" },
paragraph: { spacing: { before: 400, after: 200 }, outlineLevel: 0 } },
{ id: "Heading2", name: "Heading 2", basedOn: "Normal", next: "Normal", quickFormat: true,
run: { size: 28, bold: true, font: "黑体" },
paragraph: { spacing: { before: 300, after: 150 }, outlineLevel: 1 } },
{ id: "Heading3", name: "Heading 3", basedOn: "Normal", next: "Normal", quickFormat: true,
run: { size: 26, bold: true, font: "黑体" },
paragraph: { spacing: { before: 200, after: 100 }, 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: [{
properties: {
page: {
size: { width: 11906, height: 16838 },
margin: { top: 1440, right: 1440, bottom: 1440, left: 1440 }
}
},
headers: {
default: new Header({ children: [new Paragraph({
alignment: AlignmentType.RIGHT,
children: [new TextRun({ font: "宋体", size: 18, color: "666666", text: "智途投送 - 单兵终端APP设计文档" })]
})] })
},
footers: {
default: new Footer({ children: [new Paragraph({
alignment: AlignmentType.CENTER,
children: [
new TextRun({ font: "宋体", size: 18, text: "第 " }),
new TextRun({ children: [PageNumber.CURRENT], font: "宋体", size: 18 }),
new TextRun({ font: "宋体", size: 18, text: " 页" })
]
})] })
},
children: [
// 封面标题
new Paragraph({ alignment: AlignmentType.CENTER, spacing: { before: 2400, after: 400 },
children: [new TextRun({ font: "黑体", size: 44, bold: true, text: "单兵终端APP" })] }),
new Paragraph({ alignment: AlignmentType.CENTER, spacing: { before: 200, after: 2400 },
children: [new TextRun({ font: "黑体", size: 36, text: "软件架构与基本功能实现说明" })] }),
new Paragraph({ alignment: AlignmentType.CENTER, spacing: { before: 400, after: 200 },
children: [new TextRun({ font: "宋体", size: 24, text: "智途投送软件系统" })] }),
new Paragraph({ alignment: AlignmentType.CENTER, spacing: { before: 100, after: 100 },
children: [new TextRun({ font: "宋体", size: 24, text: new Date().toISOString().split('T')[0] })] }),
new Paragraph({ children: [new PageBreak()] }),
// 目录
new TableOfContents("目录", { hyperlink: true, headingStyleRange: "1-3" }),
new Paragraph({ children: [new PageBreak()] }),
// 第一章 项目概述
subHeading("一、项目概述", HeadingLevel.HEADING_1),
bodyPara("单兵终端APP是“智途投送”系统的移动端前端应用供前线士兵使用。它基于 Capacitor 混合应用框架开发,以 HTML5 + JavaScript 实现业务逻辑,通过 Android WebView 渲染,并借助 Capacitor 插件调用原生能力(如 GPS 定位)。"),
bodyPara("APP 主要功能包括:士兵登录/注册、实时 GPS 定位与自动上报、紧急物资需求填报、投放点地图选点、任务进度监控、无人机状态查看、一键 SOS 求救等。"),
subHeading("1.1 技术栈", HeadingLevel.HEADING_2),
new Paragraph({ numbering: { reference: "bullets", level: 0 }, spacing: { before: 80, after: 80 },
children: [new TextRun({ font: "宋体", size: 24, text: "前端框架:原生 HTML5 + JavaScriptES6+),无额外前端框架" })] }),
new Paragraph({ numbering: { reference: "bullets", level: 0 }, spacing: { before: 80, after: 80 },
children: [new TextRun({ font: "宋体", size: 24, text: "混合容器Capacitor 6.x生成 Android 原生工程)" })] }),
new Paragraph({ numbering: { reference: "bullets", level: 0 }, spacing: { before: 80, after: 80 },
children: [new TextRun({ font: "宋体", size: 24, text: "原生插件:@capacitor/geolocationGPS 定位)" })] }),
new Paragraph({ numbering: { reference: "bullets", level: 0 }, spacing: { before: 80, after: 80 },
children: [new TextRun({ font: "宋体", size: 24, text: "地图服务:高德地图 JS API 2.0(动态地图 + 静态地图 fallback" })] }),
new Paragraph({ numbering: { reference: "bullets", level: 0 }, spacing: { before: 80, after: 80 },
children: [new TextRun({ font: "宋体", size: 24, text: "后端通信RESTful APIFlask 后端fetch + AbortController 超时控制" })] }),
new Paragraph({ numbering: { reference: "bullets", level: 0 }, spacing: { before: 80, after: 80 },
children: [new TextRun({ font: "宋体", size: 24, text: "数据持久化localStorage会话信息、服务器地址缓存" })] }),
subHeading("1.2 项目结构", HeadingLevel.HEADING_2),
codeBlock([
"单兵终端APP/",
"├── capacitor.config.json # Capacitor 配置应用ID、明文传输等",
"├── package.json # 依赖:@capacitor/core、geolocation、android",
"├── index.html # SPA 单页结构(所有页面 div 容器)",
"├── css/style.css # 全局样式",
"├── js/",
"│ ├── app.js # 主应用路由、状态、UI 交互",
"│ ├── api.js # API 封装REST 请求 + Mock 降级",
"│ └── location.js # GPS 定位 + 高德地图封装",
"└── android/ # Capacitor 生成的 Android 工程"
]),
// 第二章 软件架构
new Paragraph({ children: [new PageBreak()] }),
subHeading("二、软件架构", HeadingLevel.HEADING_1),
bodyPara("单兵终端APP采用经典的分层架构从上到下依次为表现层UI 层、业务逻辑层、数据访问层、原生能力层。整体为单页应用SPA模式所有页面通过 JavaScript 动态切换,避免原生 Activity 跳转带来的开发复杂度。"),
subHeading("2.1 架构分层", HeadingLevel.HEADING_2),
// 架构表格
new Table({
width: { size: 9360, type: WidthType.DXA },
columnWidths: [2340, 7020],
rows: [
new TableRow({ children: [
new TableCell({ borders: { top: {style:BorderStyle.SINGLE,size:1,color:"CCCCCC"}, bottom:{style:BorderStyle.SINGLE,size:1,color:"CCCCCC"}, left:{style:BorderStyle.SINGLE,size:1,color:"CCCCCC"}, right:{style:BorderStyle.SINGLE,size:1,color:"CCCCCC"} },
width: { size: 2340, type: WidthType.DXA }, shading: { fill: "D5E8F0", type: ShadingType.CLEAR }, margins: { top: 80, bottom: 80, left: 120, right: 120 },
children: [new Paragraph({ children: [new TextRun({ bold: true, font: "宋体", size: 22, text: "分层" })] })] }),
new TableCell({ borders: { top: {style:BorderStyle.SINGLE,size:1,color:"CCCCCC"}, bottom:{style:BorderStyle.SINGLE,size:1,color:"CCCCCC"}, left:{style:BorderStyle.SINGLE,size:1,color:"CCCCCC"}, right:{style:BorderStyle.SINGLE,size:1,color:"CCCCCC"} },
width: { size: 7020, type: WidthType.DXA }, shading: { fill: "D5E8F0", type: ShadingType.CLEAR }, margins: { top: 80, bottom: 80, left: 120, right: 120 },
children: [new Paragraph({ children: [new TextRun({ bold: true, font: "宋体", size: 22, text: "职责与对应文件" })] })] })
]}),
new TableRow({ children: [
new TableCell({ borders: { top: {style:BorderStyle.SINGLE,size:1,color:"CCCCCC"}, bottom:{style:BorderStyle.SINGLE,size:1,color:"CCCCCC"}, left:{style:BorderStyle.SINGLE,size:1,color:"CCCCCC"}, right:{style:BorderStyle.SINGLE,size:1,color:"CCCCCC"} },
width: { size: 2340, type: WidthType.DXA }, margins: { top: 80, bottom: 80, left: 120, right: 120 },
children: [new Paragraph({ children: [new TextRun({ font: "宋体", size: 22, text: "表现层UI" })] })] }),
new TableCell({ borders: { top: {style:BorderStyle.SINGLE,size:1,color:"CCCCCC"}, bottom:{style:BorderStyle.SINGLE,size:1,color:"CCCCCC"}, left:{style:BorderStyle.SINGLE,size:1,color:"CCCCCC"}, right:{style:BorderStyle.SINGLE,size:1,color:"CCCCCC"} },
width: { size: 7020, type: WidthType.DXA }, margins: { top: 80, bottom: 80, left: 120, right: 120 },
children: [new Paragraph({ children: [new TextRun({ font: "宋体", size: 22, text: "index.html单页多视图+ css/style.css负责页面布局、控件渲染、事件绑定" })] })] })
]}),
new TableRow({ children: [
new TableCell({ borders: { top: {style:BorderStyle.SINGLE,size:1,color:"CCCCCC"}, bottom:{style:BorderStyle.SINGLE,size:1,color:"CCCCCC"}, left:{style:BorderStyle.SINGLE,size:1,color:"CCCCCC"}, right:{style:BorderStyle.SINGLE,size:1,color:"CCCCCC"} },
width: { size: 2340, type: WidthType.DXA }, margins: { top: 80, bottom: 80, left: 120, right: 120 },
children: [new Paragraph({ children: [new TextRun({ font: "宋体", size: 22, text: "业务逻辑层" })] })] }),
new TableCell({ borders: { top: {style:BorderStyle.SINGLE,size:1,color:"CCCCCC"}, bottom:{style:BorderStyle.SINGLE,size:1,color:"CCCCCC"}, left:{style:BorderStyle.SINGLE,size:1,color:"CCCCCC"}, right:{style:BorderStyle.SINGLE,size:1,color:"CCCCCC"} },
width: { size: 7020, type: WidthType.DXA }, margins: { top: 80, bottom: 80, left: 120, right: 120 },
children: [new Paragraph({ children: [new TextRun({ font: "宋体", size: 22, text: "js/app.js路由管理、状态管理、页面切换、表单校验、业务事件处理" })] })] })
]}),
new TableRow({ children: [
new TableCell({ borders: { top: {style:BorderStyle.SINGLE,size:1,color:"CCCCCC"}, bottom:{style:BorderStyle.SINGLE,size:1,color:"CCCCCC"}, left:{style:BorderStyle.SINGLE,size:1,color:"CCCCCC"}, right:{style:BorderStyle.SINGLE,size:1,color:"CCCCCC"} },
width: { size: 2340, type: WidthType.DXA }, margins: { top: 80, bottom: 80, left: 120, right: 120 },
children: [new Paragraph({ children: [new TextRun({ font: "宋体", size: 22, text: "数据访问层" })] })] }),
new TableCell({ borders: { top: {style:BorderStyle.SINGLE,size:1,color:"CCCCCC"}, bottom:{style:BorderStyle.SINGLE,size:1,color:"CCCCCC"}, left:{style:BorderStyle.SINGLE,size:1,color:"CCCCCC"}, right:{style:BorderStyle.SINGLE,size:1,color:"CCCCCC"} },
width: { size: 7020, type: WidthType.DXA }, margins: { top: 80, bottom: 80, left: 120, right: 120 },
children: [new Paragraph({ children: [new TextRun({ font: "宋体", size: 22, text: "js/api.js封装 HTTP 请求、超时控制、后端不可用时自动降级为 Mock 数据" })] })] })
]}),
new TableRow({ children: [
new TableCell({ borders: { top: {style:BorderStyle.SINGLE,size:1,color:"CCCCCC"}, bottom:{style:BorderStyle.SINGLE,size:1,color:"CCCCCC"}, left:{style:BorderStyle.SINGLE,size:1,color:"CCCCCC"}, right:{style:BorderStyle.SINGLE,size:1,color:"CCCCCC"} },
width: { size: 2340, type: WidthType.DXA }, margins: { top: 80, bottom: 80, left: 120, right: 120 },
children: [new Paragraph({ children: [new TextRun({ font: "宋体", size: 22, text: "原生能力层" })] })] }),
new TableCell({ borders: { top: {style:BorderStyle.SINGLE,size:1,color:"CCCCCC"}, bottom:{style:BorderStyle.SINGLE,size:1,color:"CCCCCC"}, left:{style:BorderStyle.SINGLE,size:1,color:"CCCCCC"}, right:{style:BorderStyle.SINGLE,size:1,color:"CCCCCC"} },
width: { size: 7020, type: WidthType.DXA }, margins: { top: 80, bottom: 80, left: 120, right: 120 },
children: [new Paragraph({ children: [new TextRun({ font: "宋体", size: 22, text: "js/location.js + Capacitor 插件GPS 定位、高德地图渲染、逆地理编码" })] })] })
]}),
]
}),
subHeading("2.2 核心模块关系", HeadingLevel.HEADING_2),
bodyPara("App 启动时app.js 读取 localStorage 中的会话信息,若已登录则进入首页并启动两个定时任务:"),
new Paragraph({ numbering: { reference: "numbers", level: 0 }, spacing: { before: 80, after: 80 },
children: [new TextRun({ font: "宋体", size: 24, text: "轮询任务(每 5 秒):当前页为“任务”或“无人机”时,自动拉取最新数据。" })] }),
new Paragraph({ numbering: { reference: "numbers", level: 0 }, spacing: { before: 80, after: 80 },
children: [new TextRun({ font: "宋体", size: 24, text: "定位上报任务(每 10 秒):通过 LocationModule 获取坐标并上传后端。" })] }),
bodyPara("用户操作(如提交需求、选择投放点)由 app.js 收集表单数据,调用 API 模块发送请求;地图相关操作统一委托给 LocationModule 处理,避免业务层直接依赖地图 SDK。"),
// 第三章 基本功能实现
new Paragraph({ children: [new PageBreak()] }),
subHeading("三、基本功能实现", HeadingLevel.HEADING_1),
bodyPara("以下按功能模块逐一说明核心实现逻辑,并给出关键代码片段。"),
// 3.1 项目配置
subHeading("3.1 项目配置与入口", HeadingLevel.HEADING_2),
bodyPara("Capacitor 配置开启了 Android 明文传输cleartext便于局域网调试 Flask 后端;应用 ID 为 com.zhitu.soldier。"),
boldPara("capacitor.config.json"),
codeBlock([
'{',
' "appId": "com.zhitu.soldier",',
' "appName": "智途投送-单兵终端",',
' "webDir": "www",',
' "server": {',
' "androidScheme": "http",',
' "cleartext": true',
' },',
' "plugins": {',
' "Geolocation": { "enabled": true }',
' }',
'}'
]),
// 3.2 登录注册
subHeading("3.2 登录与注册", HeadingLevel.HEADING_2),
bodyPara("登录模块支持两套机制:演示账号本地登录(无需后端即可体验)和真实后端账号登录。会话信息以 JSON 形式存储在 localStorage 中,退出时清除。"),
boldPara("js/app.js - 登录逻辑"),
codeBlock([
'async function doLogin() {',
' const id = document.getElementById("login-id").value.trim();',
' const pwd = document.getElementById("login-pwd").value.trim();',
' // 演示账号快速登录',
' const demoAccounts = {',
' "soldier_001": { name: "张三", unit: "第3步兵师/1连" },',
' "soldier_002": { name: "李四", unit: "第3步兵师/2连" }',
' };',
' if (demoAccounts[id] && pwd === "123456") {',
' localStorage.setItem("soldier_session", JSON.stringify({',
' soldier_id: id, name: demoAccounts[id].name, ...',
' }));',
' router("home");',
' startPolling();',
' startLocationReporting();',
' return;',
' }',
' // 真实后端登录',
' const result = await API.login(id, pwd);',
' if (result.ok) { ... }',
'}'
]),
// 3.3 路由管理
subHeading("3.3 路由与页面管理", HeadingLevel.HEADING_2),
bodyPara(`APP 为单页应用SPA所有页面以 <div class="page"> 形式放在同一 HTML 中。通过 CSS class active 控制显示/隐藏,配合 pageStack 实现返回上一页。底部 Tab 栏仅在首页、任务、无人机、我的四个主页面显示。`),
boldPara("js/app.js - 路由切换"),
codeBlock([
'function router(page) {',
' document.querySelectorAll(".page").forEach(p => p.classList.remove("active"));',
' const target = document.getElementById("page-" + page);',
' if (target) target.classList.add("active");',
' // Tab 高亮与显隐控制',
' document.querySelectorAll(".tab-item").forEach(t => t.classList.remove("active"));',
' const tabItem = document.querySelector(\'.tab-item[data-page="\' + page + \'"]\');',
' if (tabItem) tabItem.classList.add("active");',
' const tabBar = document.getElementById("tab-bar");',
' tabBar.style.display = TAB_PAGES.includes(page) ? "flex" : "none";',
' pageStack.push(page);',
' currentPage = page;',
' onPageEnter(page); // 页面专属初始化',
'}'
]),
// 3.4 GPS定位
subHeading("3.4 GPS 定位与自动上报", HeadingLevel.HEADING_2),
bodyPara("定位模块实现了四级降级策略:高德 JS 定位 → Capacitor 原生 GPS → 浏览器 Geolocation → IP 网络定位 → 默认坐标。确保在各种网络与权限环境下都能获得可用位置。"),
boldPara("js/location.js - 四级定位降级"),
codeBlock([
'async function getCurrentPosition() {',
' // 1. 高德定位',
' try { return await getAmapPosition(); }',
' catch (e) { errors.push("高德:" + e.message); }',
' // 2. Capacitor 原生定位',
' try { return await getCapacitorPosition(); }',
' catch (e) { errors.push("原生:" + e.message); }',
' // 3. 浏览器定位',
' try { return await getBrowserPosition(); }',
' catch (e) { errors.push("浏览器:" + e.message); }',
' // 4. IP 定位',
' try { return await getIpPosition(); }',
' catch (e) { errors.push("IP:" + e.message); }',
' // 5. 默认兜底',
' return { lat: 30.2500, lng: 120.1600, accuracy: 100, source: "default" };',
'}'
]),
bodyPara("登录成功后app.js 调用 LocationModule.startReporting() 启动定时上报,每隔 10 秒将坐标发送至后端 /api/soldier/location。"),
boldPara("js/app.js - 启动自动上报"),
codeBlock([
'function startLocationReporting() {',
' LocationModule.startReporting(CONFIG.soldierId, CONFIG.soldierName, 10000);',
'}'
]),
// 3.5 物资需求上报
subHeading("3.5 物资需求上报", HeadingLevel.HEADING_2),
bodyPara("士兵在“需求上报”页选择物资类型、数量、紧急程度,并关联投放点。提交时组装 JSON 对象,通过 POST /api/demand 发送至后端。投放点数据可以是地图选点结果,也可以是系统推荐列表中的安全点。"),
boldPara("js/app.js - 提交需求"),
codeBlock([
'async function submitDemand() {',
' const type = document.getElementById("demand-type").value;',
' const qty = document.getElementById("demand-qty").value;',
' const urgency = document.querySelector("#urgency-group .radio-label.active")',
' .dataset.value;',
' const demand = {',
' soldier_id: CONFIG.soldierId,',
' type, quantity: parseInt(qty), urgency,',
' drop_point: selectedDropPoint,',
' status: "待处理",',
' created_at: new Date().toISOString()',
' };',
' await API.postDemand(demand);',
' showToast("✅ 需求上报成功!");',
' router("home");',
'}'
]),
// 3.6 投放点与地图
subHeading("3.6 投放点选择与地图集成", HeadingLevel.HEADING_2),
bodyPara("投放点选择页整合了三种交互方式:地图直接点击选点、地点关键词搜索、附近推荐列表。地图基于高德 JS API 2.0 动态初始化,支持逆地理编码获取地址名称;若动态地图加载失败,自动降级为静态地图图片。"),
boldPara("js/location.js - 地图选点初始化"),
codeBlock([
'async function initPickerMap(containerId, onSelectCallback) {',
' const AMap = await loadAmapScript();',
' const container = document.getElementById(containerId);',
' container.style.width = "100%";',
' container.style.height = "280px";',
' container.innerHTML = "";',
' pickerMap = new AMap.Map(containerId, {',
' zoom: 15, center: [center.lng, center.lat], resizeEnable: true',
' });',
' // 点击地图选点',
' pickerMap.on("click", (e) => {',
' const lng = e.lnglat.lng, lat = e.lnglat.lat;',
' pickerGeocoder.getAddress([lng, lat], (status, result) => {',
' let address = result.regeocode.formattedAddress;',
' onSelectCallback({ lat, lng, name, address });',
' });',
' });',
'}'
]),
// 3.7 任务监控
subHeading("3.7 任务进度监控", HeadingLevel.HEADING_2),
bodyPara("任务页展示当前运输任务的进度、预计到达时间、飞行路径与投放点安全系数。进入页面时调用 API.getCurrentTask() 获取数据;若后端不可用,返回 Mock 数据保证界面不空白。"),
boldPara("js/app.js - 加载任务信息"),
codeBlock([
'async function loadTaskInfo() {',
' const task = await API.getCurrentTask(CONFIG.soldierId);',
' if (task) {',
' document.getElementById("task-id").textContent = task.id;',
' document.getElementById("task-status").textContent =',
' statusMap[task.status]?.text || task.status;',
' const prog = task.progress || 0;',
' const filled = Math.round(20 * prog / 100);',
' document.getElementById("task-progress-text").textContent =',
' "█".repeat(filled) + "░".repeat(20 - filled) + " " + prog + "%";',
' }',
'}'
]),
// 3.8 无人机状态
subHeading("3.8 无人机状态查看", HeadingLevel.HEADING_2),
bodyPara("无人机页展示实时飞行数据:速度、高度、电量、温度、距离目标等,以及最近动态日志。与任务页共用轮询机制,每 5 秒自动刷新。"),
boldPara("js/app.js - 加载无人机状态"),
codeBlock([
'async function loadDroneStatus() {',
' const status = await API.getDroneStatus();',
' if (status) {',
' document.getElementById("drone-battery").textContent = status.battery + "%";',
' document.getElementById("drone-speed").textContent = status.speed + "m/s";',
' document.getElementById("drone-alt").textContent = status.altitude + "m";',
' document.getElementById("drone-dist").textContent = status.distance + "m";',
' }',
' const logs = await API.getDroneLogs();',
' document.getElementById("drone-logs").innerHTML =',
' logs.map(l => `<div class="log-row">...`).join("");',
'}'
]),
// 3.9 SOS
subHeading("3.9 一键 SOS 求救", HeadingLevel.HEADING_2),
bodyPara("设置页提供紧急求救按钮,点击后二次确认,随后获取当前 GPS 坐标并立即上报后端 /api/sos。上报内容包含士兵ID、姓名、坐标和时间戳。"),
boldPara("js/app.js - SOS 求救"),
codeBlock([
'async function triggerSOS() {',
' if (!confirm("确认发送求救信号?此操作将立即上报您的当前位置。")) return;',
' const pos = await LocationModule.getCurrentPosition();',
' await API.sendSOS({',
' soldier_id: CONFIG.soldierId,',
' soldier_name: CONFIG.soldierName,',
' lat: pos.lat, lng: pos.lng,',
' time: new Date().toISOString()',
' });',
' showToast("🚨 求救信号已发送!");',
'}'
]),
// 3.10 API封装
subHeading("3.10 API 通信封装与离线降级", HeadingLevel.HEADING_2),
bodyPara("api.js 封装了所有后端接口请求统一处理超时5 秒、JSON 序列化和错误捕获。对于投放点、任务、无人机状态等查询类接口,若后端不可用或超时,自动返回 Mock 数据,确保 APP 在离线/演示场景下仍可正常使用。"),
boldPara("js/api.js - 统一请求与超时控制"),
codeBlock([
'async function request(url, options = {}) {',
' const fullUrl = url.startsWith("http") ? url : BASE + url;',
' const controller = new AbortController();',
' const timeoutId = setTimeout(() => controller.abort(), 5000);',
' try {',
' const resp = await fetch(fullUrl, {',
' headers: { "Content-Type": "application/json" },',
' signal: controller.signal, ...options',
' });',
' clearTimeout(timeoutId);',
' return resp.json();',
' } catch (e) {',
' clearTimeout(timeoutId);',
' if (e.name === "AbortError")',
' throw new Error("请求超时,请检查后端是否启动");',
' throw e;',
' }',
'}'
]),
boldPara("js/api.js - Mock 降级示例"),
codeBlock([
'async function getDropPoints() {',
' try {',
' const data = await request("/api/drop-points");',
' return data.drop_points || data;',
' } catch (e) {',
' return getMockDropPoints(); // 离线兜底',
' }',
'}'
]),
// 第四章 总结
new Paragraph({ children: [new PageBreak()] }),
subHeading("四、设计特点与总结", HeadingLevel.HEADING_1),
bodyPara("单兵终端APP在设计上遵循“简单、可靠、可演示”的原则主要特点如下"),
new Paragraph({ numbering: { reference: "numbers", level: 0 }, spacing: { before: 80, after: 80 },
children: [new TextRun({ font: "宋体", size: 24, text: "混合架构,跨平台成本低:基于 Capacitor 将 Web 技术打包为 Android APK一套代码同时支持手机浏览器预览和真机安装。" })] }),
new Paragraph({ numbering: { reference: "numbers", level: 0 }, spacing: { before: 80, after: 80 },
children: [new TextRun({ font: "宋体", size: 24, text: "多级定位降级,适应战场复杂环境:四级定位策略 + 手动修正,确保在城市、室内、弱网环境下仍能获取可用坐标。" })] }),
new Paragraph({ numbering: { reference: "numbers", level: 0 }, spacing: { before: 80, after: 80 },
children: [new TextRun({ font: "宋体", size: 24, text: "Mock 数据兜底,支持离线演示:所有查询类 API 在后端不可用时自动返回模拟数据,便于开发调试与现场演示。" })] }),
new Paragraph({ numbering: { reference: "numbers", level: 0 }, spacing: { before: 80, after: 80 },
children: [new TextRun({ font: "宋体", size: 24, text: "模块职责清晰易于维护app.js 负责业务与UI、api.js 负责网络、location.js 负责定位与地图,三层之间通过明确接口协作。" })] }),
new Paragraph({ numbering: { reference: "numbers", level: 0 }, spacing: { before: 80, after: 80 },
children: [new TextRun({ font: "宋体", size: 24, text: "安全与权限考虑Android 开启明文传输用于局域网调试高德地图、GPS 权限在运行时动态申请SOS 操作提供二次确认防止误触。" })] }),
bodyPara("综上所述单兵终端APP通过简洁的分层架构和稳健的降级策略实现了前线士兵在复杂战场环境下的物资需求上报、位置共享与任务协同功能。"),
]
}]
});
Packer.toBuffer(doc).then(buffer => {
fs.writeFileSync("单兵终端APP_架构与功能实现说明.docx", buffer);
console.log("文档已生成单兵终端APP_架构与功能实现说明.docx");
});