|
|
const { Document, Packer, Paragraph, TextRun, Table, TableRow, TableCell,
|
|
|
Header, Footer, AlignmentType, HeadingLevel, BorderStyle, WidthType,
|
|
|
ShadingType, VerticalAlign, PageNumber } = require("C:/Users/ADMINI~1/AppData/Local/Temp/docx-gen/node_modules/docx");
|
|
|
const fs = require("fs");
|
|
|
|
|
|
const NAVY = "0A2A70";
|
|
|
const HEADER_BG = "123A7A";
|
|
|
const ALT = "F4F7FB";
|
|
|
const LINE = "C5D0E0";
|
|
|
const MUTED = "5B6775";
|
|
|
|
|
|
const thin = { style: BorderStyle.SINGLE, size: 4, color: LINE };
|
|
|
const borders = { top: thin, bottom: thin, left: thin, right: thin, insideHorizontal: thin, insideVertical: thin };
|
|
|
const noBorder = { style: BorderStyle.NONE, size: 0, color: "FFFFFF" };
|
|
|
const noBorders = { top: noBorder, bottom: noBorder, left: noBorder, right: noBorder };
|
|
|
|
|
|
const font = { ascii: "Times New Roman", eastAsia: "SimSun", hAnsi: "Times New Roman" };
|
|
|
const fontHei = { ascii: "Times New Roman", eastAsia: "SimHei", hAnsi: "Times New Roman" };
|
|
|
|
|
|
function run(text, opts = {}) {
|
|
|
return new TextRun({
|
|
|
text,
|
|
|
font: opts.hei ? fontHei : font,
|
|
|
size: opts.size || 21,
|
|
|
bold: !!opts.bold,
|
|
|
color: opts.color || "000000",
|
|
|
italics: !!opts.italics,
|
|
|
});
|
|
|
}
|
|
|
|
|
|
function p(text, opts = {}) {
|
|
|
return new Paragraph({
|
|
|
spacing: { before: opts.before ?? 60, after: opts.after ?? 60, line: 312 },
|
|
|
alignment: opts.align || AlignmentType.JUSTIFIED,
|
|
|
indent: opts.indent ? { firstLine: 480 } : undefined,
|
|
|
keepNext: !!opts.keepNext,
|
|
|
children: [run(text, opts)],
|
|
|
});
|
|
|
}
|
|
|
|
|
|
function h1(text) {
|
|
|
return new Paragraph({
|
|
|
heading: HeadingLevel.HEADING_1,
|
|
|
spacing: { before: 280, after: 140, line: 312 },
|
|
|
border: { bottom: { style: BorderStyle.SINGLE, size: 12, color: NAVY, space: 4 } },
|
|
|
children: [run(text, { hei: true, size: 32, bold: true, color: NAVY })],
|
|
|
});
|
|
|
}
|
|
|
|
|
|
function h2(text) {
|
|
|
return new Paragraph({
|
|
|
heading: HeadingLevel.HEADING_2,
|
|
|
spacing: { before: 240, after: 100, line: 312 },
|
|
|
children: [run(text, { hei: true, size: 28, bold: true, color: NAVY })],
|
|
|
});
|
|
|
}
|
|
|
|
|
|
function cell(text, opts = {}) {
|
|
|
const fill = opts.fill;
|
|
|
return new TableCell({
|
|
|
borders,
|
|
|
width: { size: opts.w, type: WidthType.DXA },
|
|
|
shading: fill ? { type: ShadingType.CLEAR, fill } : undefined,
|
|
|
margins: { top: 50, bottom: 50, left: 70, right: 70 },
|
|
|
verticalAlign: VerticalAlign.CENTER,
|
|
|
children: [
|
|
|
new Paragraph({
|
|
|
spacing: { before: 20, after: 20, line: 276 },
|
|
|
alignment: opts.center ? AlignmentType.CENTER : AlignmentType.LEFT,
|
|
|
children: [run(String(text), { size: opts.size || 18, bold: !!opts.bold, color: opts.color || "000000" })],
|
|
|
}),
|
|
|
],
|
|
|
});
|
|
|
}
|
|
|
|
|
|
function table(headers, rows, widths) {
|
|
|
const total = widths.reduce((a, b) => a + b, 0);
|
|
|
const headerRow = new TableRow({
|
|
|
tableHeader: true,
|
|
|
cantSplit: true,
|
|
|
children: headers.map((h, i) => cell(h, { w: widths[i], fill: HEADER_BG, bold: true, color: "FFFFFF", size: 18, center: true })),
|
|
|
});
|
|
|
const body = rows.map((row, ri) => new TableRow({
|
|
|
cantSplit: true,
|
|
|
children: row.map((val, i) => cell(val, { w: widths[i], fill: ri % 2 ? ALT : "FFFFFF", size: 18, center: i === 0 })),
|
|
|
}));
|
|
|
return new Table({
|
|
|
width: { size: total, type: WidthType.DXA },
|
|
|
columnWidths: widths,
|
|
|
borders,
|
|
|
rows: [headerRow, ...body],
|
|
|
});
|
|
|
}
|
|
|
|
|
|
const W = [700, 1400, 1900, 3400, 1800, 900]; // 10100 usable-ish; page content width ~ 11906-1701-1417 = 8788
|
|
|
// Recalc content width: 11906 - 1418 - 1134 = 9354. Use slightly narrower margins.
|
|
|
const content = 9354;
|
|
|
const patrolW = [620, 1200, 1700, 2800, 1934, 1100];
|
|
|
const sysW = [620, 1400, 1400, 2600, 2234, 1100];
|
|
|
const stopW = [700, 2800, 5854];
|
|
|
|
|
|
const patrol = [
|
|
|
["1", "巡检任务", "task-summary", "cards/taskSummary/inspectionTask", "patrol:task:list", "dashboard"],
|
|
|
["2", "实时监控", "video-preview", "video/LivePreview", "video:preview:list", "monitor"],
|
|
|
["3", "巡检方案", "patrol-plan", "cards/patrolPlan/notstart", "patrol:plan:list", "task"],
|
|
|
["4", "点位管理", "site-management", "cards/site_management/site_management", "patrol:point:list", "tree"],
|
|
|
["5", "巡检报告", "patrol-report", "cards/patrolReport/InspectionReporter", "patrol:report:list", "documentation"],
|
|
|
["6", "巡检计划", "patrol-schedule", "cards/tasksShow/index", "patrol:schedule:list", "date"],
|
|
|
["7", "缺陷记录", "defect-record", "cards/defectRecord/index", "patrol:defect:list", "bug"],
|
|
|
["8", "历史回放", "video-playback", "video/HistoryPlayback", "video:playback:list", "time"],
|
|
|
];
|
|
|
|
|
|
const system = [
|
|
|
["1", "用户管理", "user", "system/user/index", "system:user:list", "user"],
|
|
|
["2", "角色管理", "role", "system/role/index", "system:role:list", "peoples"],
|
|
|
["3", "菜单管理", "menu", "system/menu/index", "system:menu:list", "tree-table"],
|
|
|
["4", "部门管理", "dept", "system/dept/index", "system:dept:list", "tree"],
|
|
|
["5", "岗位管理", "post", "system/post/index", "system:post:list", "post"],
|
|
|
["6", "字典管理", "dict", "system/dict/index", "system:dict:list", "dict"],
|
|
|
["7", "参数设置", "config", "system/config/index", "system:config:list", "edit"],
|
|
|
["8", "通知公告", "notice", "system/notice/index", "system:notice:list", "message"],
|
|
|
];
|
|
|
|
|
|
const stop = [
|
|
|
["1", "问题清单", "不是当前左侧菜单,停用"],
|
|
|
["2", "巡视任务管理 / 巡检任务管理", "旧名称,与「巡检任务」重复,停用"],
|
|
|
["3", "联动动作配置", "当前系统未使用,停用"],
|
|
|
["4", "设备管理", "当前左侧不展示,停用"],
|
|
|
["5", "点位配置", "另一套点位页面,与「点位管理」重复,停用"],
|
|
|
["6", "区域管理", "当前左侧不展示,停用"],
|
|
|
["7", "动环巡检(现有停用的旧目录)", "不要改旧记录,按本文新建一条目录"],
|
|
|
["8", "系统监控", "若依自带,当前系统不用,停用"],
|
|
|
["9", "系统工具", "若依自带,当前系统不用,停用"],
|
|
|
["10", "测试官网", "若依自带,停用"],
|
|
|
["11", "基础数据", "当前左侧不展示,停用"],
|
|
|
["12", "巡视管理", "旧目录,停用"],
|
|
|
];
|
|
|
|
|
|
const hiddenPages = [
|
|
|
["1", "巡检报告详情", "从巡检报告点进去,不要单独建菜单"],
|
|
|
["2", "分配角色", "从用户管理点进去,不要单独建菜单"],
|
|
|
["3", "分配用户", "从角色管理点进去,不要单独建菜单"],
|
|
|
["4", "字典数据", "从字典管理点进去,不要单独建菜单"],
|
|
|
];
|
|
|
|
|
|
const children = [];
|
|
|
children.push(new Paragraph({
|
|
|
spacing: { before: 0, after: 40, line: 312 },
|
|
|
children: [run("动环智能巡检系统", { hei: true, size: 22, color: MUTED })],
|
|
|
}));
|
|
|
children.push(new Paragraph({
|
|
|
spacing: { before: 40, after: 80, line: 360 },
|
|
|
border: { bottom: { style: BorderStyle.SINGLE, size: 16, color: NAVY, space: 6 } },
|
|
|
children: [run("菜单整理说明(供测试录入)", { hei: true, size: 36, bold: true, color: NAVY })],
|
|
|
}));
|
|
|
children.push(p("录入位置:系统管理 → 菜单管理。按本文新建两组菜单,并把旧菜单停用。普通账号登录后,左侧只应看到「动环巡检」下的 8 项,顺序与下表一致。", { indent: true, size: 24 }));
|
|
|
children.push(p("管理员从顶部进入「系统管理」后,左侧应直接展开用户管理、角色管理等 8 项,不再套一层「系统管理」。", { indent: true, size: 24, before: 40 }));
|
|
|
|
|
|
children.push(h1("一、填写规则"));
|
|
|
children.push(p("菜单类型按若依约定:目录填 M,菜单填 C,按钮填 F。本文只建目录和菜单,不建按钮。", { indent: true, size: 24 }));
|
|
|
children.push(p("路由地址不要带前导斜杠。组件路径相对 src/views,不要写 .vue,也不要写开头的斜杠。显示状态一律选「显示」,菜单状态一律选「正常」。是否外链选「否」,是否缓存可按页面默认。", { indent: true, size: 24 }));
|
|
|
children.push(p("父级只建目录,组件路径留空。子菜单的上级菜单选对应目录。排序按表中数字填写,数字越小越靠前。", { indent: true, size: 24 }));
|
|
|
children.push(p("权限标识先按表填写。若后端角色里还没有这些标识,给管理员角色勾上对应菜单即可;普通角色只勾「动环巡检」及其 8 个子菜单。", { indent: true, size: 24 }));
|
|
|
|
|
|
children.push(h1("二、动环巡检"));
|
|
|
children.push(p("先新增一条目录,再把下面 8 条菜单挂到它下面。", { indent: true, size: 24, keepNext: true }));
|
|
|
|
|
|
children.push(new Paragraph({
|
|
|
keepNext: true,
|
|
|
spacing: { before: 80, after: 60, line: 312 },
|
|
|
children: [run("表 1 父级目录:动环巡检", { bold: true, size: 21 })],
|
|
|
}));
|
|
|
children.push(table(
|
|
|
["字段", "填写内容"],
|
|
|
[
|
|
|
["菜单名称", "动环巡检"],
|
|
|
["上级菜单", "主类目"],
|
|
|
["菜单类型", "目录(M)"],
|
|
|
["显示排序", "1"],
|
|
|
["路由地址", "inspection"],
|
|
|
["组件路径", "留空"],
|
|
|
["菜单图标", "dashboard"],
|
|
|
["显示状态", "显示"],
|
|
|
["菜单状态", "正常"],
|
|
|
],
|
|
|
[2200, 7154]
|
|
|
));
|
|
|
|
|
|
children.push(new Paragraph({
|
|
|
keepNext: true,
|
|
|
spacing: { before: 200, after: 60, line: 312 },
|
|
|
children: [run("表 2 动环巡检下的菜单(上级菜单均选「动环巡检」,类型均为菜单 C)", { bold: true, size: 21 })],
|
|
|
}));
|
|
|
children.push(table(
|
|
|
["排序", "菜单名称", "路由地址", "组件路径", "权限标识", "图标"],
|
|
|
patrol,
|
|
|
patrolW
|
|
|
));
|
|
|
|
|
|
children.push(h1("三、系统管理"));
|
|
|
children.push(p("只分配给管理员角色。若库里已有名为「系统管理」、路由为 system 的目录,直接在它下面按本表核对或补齐,不要再新建第二条同名目录。子菜单已存在且路由、组件一致的,只改名称、排序和图标,不要重复新增。", { indent: true, size: 24, keepNext: true }));
|
|
|
|
|
|
children.push(new Paragraph({
|
|
|
keepNext: true,
|
|
|
spacing: { before: 80, after: 60, line: 312 },
|
|
|
children: [run("表 3 父级目录:系统管理", { bold: true, size: 21 })],
|
|
|
}));
|
|
|
children.push(table(
|
|
|
["字段", "填写内容"],
|
|
|
[
|
|
|
["菜单名称", "系统管理"],
|
|
|
["上级菜单", "主类目"],
|
|
|
["菜单类型", "目录(M)"],
|
|
|
["显示排序", "2"],
|
|
|
["路由地址", "system"],
|
|
|
["组件路径", "留空"],
|
|
|
["菜单图标", "system"],
|
|
|
["显示状态", "显示"],
|
|
|
["菜单状态", "正常"],
|
|
|
],
|
|
|
[2200, 7154]
|
|
|
));
|
|
|
|
|
|
children.push(new Paragraph({
|
|
|
keepNext: true,
|
|
|
spacing: { before: 200, after: 60, line: 312 },
|
|
|
children: [run("表 4 系统管理下的菜单(上级菜单均选「系统管理」,类型均为菜单 C)", { bold: true, size: 21 })],
|
|
|
}));
|
|
|
children.push(table(
|
|
|
["排序", "菜单名称", "路由地址", "组件路径", "权限标识", "图标"],
|
|
|
system,
|
|
|
sysW
|
|
|
));
|
|
|
|
|
|
children.push(h1("四、需要停用的旧菜单"));
|
|
|
children.push(p("这些是若依或旧巡视系统残留,当前页面用不到。处理方式是把菜单状态改为「停用」,不要物理删除,避免角色、按钮权限丢关联。停用后重新登录,左侧不应再出现这些名称。", { indent: true, size: 24, keepNext: true }));
|
|
|
children.push(new Paragraph({
|
|
|
keepNext: true,
|
|
|
spacing: { before: 80, after: 60, line: 312 },
|
|
|
children: [run("表 5 停用清单", { bold: true, size: 21 })],
|
|
|
}));
|
|
|
children.push(table(["序号", "菜单名称", "处理说明"], stop, stopW));
|
|
|
|
|
|
children.push(h2("不要单独建菜单的页面"));
|
|
|
children.push(p("下面这些页面从其他菜单点进去,前端已有隐藏路由。菜单管理里不要再加同名菜单。", { indent: true, size: 24, keepNext: true }));
|
|
|
children.push(new Paragraph({
|
|
|
keepNext: true,
|
|
|
spacing: { before: 80, after: 60, line: 312 },
|
|
|
children: [run("表 6 隐藏页面", { bold: true, size: 21 })],
|
|
|
}));
|
|
|
children.push(table(["序号", "页面", "说明"], hiddenPages, stopW));
|
|
|
|
|
|
children.push(h1("五、录入后核对"));
|
|
|
children.push(p("用管理员账号退出后重新登录。左侧第一组应为:巡检任务、实时监控、巡检方案、点位管理、巡检报告、巡检计划、缺陷记录、历史回放。点每一项都能打开对应页面,地址分别是 /task-summary、/video-preview、/patrol-plan、/site-management、/patrol-report、/patrol-schedule、/defect-record、/video-playback。", { indent: true, size: 24 }));
|
|
|
children.push(p("从顶部进入系统管理后,左侧应依次为:用户管理、角色管理、菜单管理、部门管理、岗位管理、字典管理、参数设置、通知公告。", { indent: true, size: 24 }));
|
|
|
children.push(p("用普通账号登录,左侧只有动环巡检的 8 项,看不到系统管理,也看不到第四节里已停用的旧菜单。", { indent: true, size: 24 }));
|
|
|
children.push(p("若某一项能看到但点开是 404,先核对组件路径是否与表中完全一致,包括大小写和下划线。路由地址多写了前导斜杠时,也会和现有页面地址对不上。", { indent: true, size: 24 }));
|
|
|
|
|
|
const doc = new Document({
|
|
|
styles: {
|
|
|
default: {
|
|
|
document: { styles: [{ id: "Normal", run: { font: "SimSun", size: 24 } }] },
|
|
|
},
|
|
|
},
|
|
|
sections: [{
|
|
|
properties: {
|
|
|
page: {
|
|
|
size: { width: 11906, height: 16838 },
|
|
|
margin: { top: 1134, bottom: 1134, left: 1134, right: 1134 },
|
|
|
},
|
|
|
},
|
|
|
headers: {
|
|
|
default: new Header({
|
|
|
children: [new Paragraph({
|
|
|
border: { bottom: { style: BorderStyle.SINGLE, size: 8, color: NAVY, space: 6 } },
|
|
|
spacing: { after: 80, line: 276 },
|
|
|
children: [
|
|
|
run("动环智能巡检系统", { size: 18, color: NAVY, bold: true }),
|
|
|
run(" 菜单整理说明", { size: 18, color: MUTED }),
|
|
|
],
|
|
|
})],
|
|
|
}),
|
|
|
},
|
|
|
footers: {
|
|
|
default: new Footer({
|
|
|
children: [new Paragraph({
|
|
|
alignment: AlignmentType.CENTER,
|
|
|
border: { top: { style: BorderStyle.SINGLE, size: 6, color: LINE, space: 6 } },
|
|
|
spacing: { before: 60, line: 276 },
|
|
|
children: [
|
|
|
run("第 ", { size: 18, color: MUTED }),
|
|
|
new TextRun({ children: [PageNumber.CURRENT], font, size: 18, color: MUTED }),
|
|
|
run(" 页", { size: 18, color: MUTED }),
|
|
|
],
|
|
|
})],
|
|
|
}),
|
|
|
},
|
|
|
children,
|
|
|
}],
|
|
|
});
|
|
|
|
|
|
Packer.toBuffer(doc).then((buf) => {
|
|
|
const out = "E:/work/yanyuan/webs-admin/docs/菜单整理说明-测试录入.docx";
|
|
|
fs.mkdirSync("E:/work/yanyuan/webs-admin/docs", { recursive: true });
|
|
|
fs.writeFileSync(out, buf);
|
|
|
console.log("wrote", out, buf.length);
|
|
|
});
|