574 lines
30 KiB
JavaScript
574 lines
30 KiB
JavaScript
const fs = require('fs');
|
||
const path = require('path');
|
||
|
||
const repoRoot = process.cwd();
|
||
const projectRoot = path.join(repoRoot, 'MES_Manage_View_V20');
|
||
const viewsRoot = path.join(projectRoot, 'src', 'views');
|
||
const manualPath = path.join(projectRoot, '元利项目详细用户使用说明书.md');
|
||
|
||
function readText(file) {
|
||
const buf = fs.readFileSync(file);
|
||
if (buf.length >= 3 && buf[0] === 0xef && buf[1] === 0xbb && buf[2] === 0xbf) {
|
||
return buf.slice(3).toString('utf8');
|
||
}
|
||
if (buf.length >= 2 && buf[0] === 0xff && buf[1] === 0xfe) {
|
||
return buf.slice(2).toString('utf16le');
|
||
}
|
||
return buf.toString('utf8');
|
||
}
|
||
|
||
function writeText(file, text) {
|
||
fs.writeFileSync(file, text, 'utf8');
|
||
}
|
||
|
||
function walk(dir, out = []) {
|
||
if (!fs.existsSync(dir)) return out;
|
||
for (const entry of fs.readdirSync(dir, { withFileTypes: true })) {
|
||
const full = path.join(dir, entry.name);
|
||
if (entry.isDirectory()) walk(full, out);
|
||
else if (/\.(vue|js)$/i.test(entry.name)) out.push(full);
|
||
}
|
||
return out;
|
||
}
|
||
|
||
function rel(file) {
|
||
return path.relative(projectRoot, file).replace(/\\/g, '/');
|
||
}
|
||
|
||
function clean(value) {
|
||
return (value || '')
|
||
.replace(/<!--[\s\S]*?-->/g, ' ')
|
||
.replace(/<[^>]+>/g, ' ')
|
||
.replace(/\{\{[\s\S]*?\}\}/g, ' ')
|
||
.replace(/ /g, ' ')
|
||
.replace(/\s+/g, ' ')
|
||
.trim();
|
||
}
|
||
|
||
function uniq(values) {
|
||
const seen = new Set();
|
||
const out = [];
|
||
for (const value of values) {
|
||
const v = String(value || '').trim();
|
||
if (!v || seen.has(v)) continue;
|
||
seen.add(v);
|
||
out.push(v);
|
||
}
|
||
return out;
|
||
}
|
||
|
||
function stripNoise(values) {
|
||
return uniq(values)
|
||
.filter(x => x.length <= 42)
|
||
.filter(x => !/[{};]/.test(x))
|
||
.filter(x => !/^(scope|row|item|index|true|false|null|undefined)$/i.test(x))
|
||
.filter(x => !/^\d+$/.test(x));
|
||
}
|
||
|
||
function extractAll(re, text, map) {
|
||
const out = [];
|
||
let match;
|
||
while ((match = re.exec(text))) out.push(map(match));
|
||
return out;
|
||
}
|
||
|
||
function attr(attrs, name) {
|
||
const m = new RegExp(`${name}="([^"]*)"`).exec(attrs);
|
||
return m ? clean(m[1]) : '';
|
||
}
|
||
|
||
function extractTemplate(text) {
|
||
const start = text.search(/<template\b[^>]*>/i);
|
||
if (start < 0) return text;
|
||
const open = /<template\b[^>]*>/i.exec(text.slice(start));
|
||
if (!open) return text;
|
||
const contentStart = start + open.index + open[0].length;
|
||
const scriptStart = text.search(/<script\b/i);
|
||
const searchEnd = scriptStart >= 0 ? scriptStart : text.length;
|
||
const close = text.lastIndexOf('</template>', searchEnd);
|
||
if (close > contentStart) return text.slice(contentStart, close);
|
||
return text.slice(contentStart, searchEnd);
|
||
}
|
||
|
||
function extractScript(text) {
|
||
const m = /<script\b[^>]*>([\s\S]*?)<\/script>/i.exec(text);
|
||
return m ? m[1] : text;
|
||
}
|
||
|
||
function extractPage(file) {
|
||
const text = readText(file);
|
||
const template = extractTemplate(text);
|
||
const script = extractScript(text);
|
||
const relative = rel(file);
|
||
const parts = relative.split('/');
|
||
const module = parts[2] || '根页面';
|
||
const name = path.basename(file) === 'index.vue'
|
||
? parts[parts.length - 2]
|
||
: path.basename(file);
|
||
|
||
const labels = extractAll(/\blabel="([^"]+)"/g, template, m => clean(m[1]));
|
||
const placeholders = extractAll(/\bplaceholder="([^"]+)"/g, template, m => clean(m[1]));
|
||
const titles = extractAll(/\btitle="([^"]+)"/g, template, m => clean(m[1]));
|
||
const formLabels = extractAll(/<el-form-item\b([^>]*)>/g, template, m => attr(m[1], 'label'));
|
||
const tabLabels = extractAll(/<el-tab-pane\b([^>]*)>/g, template, m => attr(m[1], 'label'));
|
||
const options = extractAll(/<el-option\b([^>]*)>/g, template, m => attr(m[1], 'label'));
|
||
const buttons = extractAll(/<el-button\b([^>]*)>([\s\S]*?)<\/el-button>/g, template, m => {
|
||
const textValue = clean(m[2]) || attr(m[1], 'title') || attr(m[1], 'aria-label');
|
||
const click = attr(m[1], '@click') || attr(m[1], 'v-on:click');
|
||
return textValue ? { text: textValue, click } : null;
|
||
})
|
||
.filter(Boolean)
|
||
.filter(b => b.text.length <= 42)
|
||
.filter(b => !/[{};]/.test(b.text));
|
||
const tableColumns = extractAll(/<el-table-column\b([^>]*)>/g, template, m => {
|
||
const label = attr(m[1], 'label');
|
||
const prop = attr(m[1], 'prop');
|
||
const type = attr(m[1], 'type');
|
||
return label || prop || type ? { label, prop, type } : null;
|
||
}).filter(Boolean);
|
||
const vModels = extractAll(/\bv-model(?:\.[\w-]+)?="([^"]+)"/g, template, m => clean(m[1]));
|
||
const params = extractAll(/\.(?:push|unshift)\s*\(\s*\[\s*['"]([^'"]+)['"]\s*,\s*([^\]\n]+)\]/g, script, m => ({
|
||
name: clean(m[1]),
|
||
expr: clean(m[2])
|
||
}));
|
||
const procs = extractAll(/CreateData\s*\(\s*['"]([^'"]+)['"]\s*,\s*['"]([^'"]+)['"]/g, script, m => ({
|
||
type: m[1],
|
||
name: clean(m[2])
|
||
}));
|
||
const sapCalls = extractAll(/\b(getB1|postB1|patchB1|posttm)\s*\(\s*([`'"])([\s\S]*?)\2/g, script, m => ({
|
||
method: m[1],
|
||
url: clean(m[3]).slice(0, 90)
|
||
}));
|
||
const dialogs = extractAll(/<el-dialog\b([\s\S]*?)>/g, template, m => {
|
||
const title = attr(m[1], 'title') || clean((/:title="([^"]+)"/.exec(m[1]) || [])[1]);
|
||
return title;
|
||
});
|
||
const methods = extractAll(/^\s*([A-Za-z_$\u4e00-\u9fa5][\w$\u4e00-\u9fa5]*)\s*\([^)]*\)\s*\{/gm, script, m => m[1]);
|
||
const hasCalendar = /FullCalendar|@fullcalendar|eventDrop|eventResize/.test(text);
|
||
const hasPagination = /el-pagination|pageSize|pageCurrent/.test(text);
|
||
const hasUpload = /el-upload|上传|upload/i.test(text);
|
||
const hasTree = /el-tree|treeData|Tree/.test(text);
|
||
|
||
return {
|
||
file,
|
||
relative,
|
||
module,
|
||
name,
|
||
text,
|
||
labels: stripNoise(labels),
|
||
placeholders: stripNoise(placeholders),
|
||
titles: stripNoise(titles),
|
||
formLabels: stripNoise(formLabels),
|
||
tabLabels: stripNoise(tabLabels),
|
||
options: stripNoise(options),
|
||
buttons,
|
||
tableColumns,
|
||
vModels: stripNoise(vModels),
|
||
params,
|
||
procs,
|
||
sapCalls,
|
||
dialogs: stripNoise(dialogs),
|
||
methods: stripNoise(methods),
|
||
hasCalendar,
|
||
hasPagination,
|
||
hasUpload,
|
||
hasTree
|
||
};
|
||
}
|
||
|
||
function roleFor(page) {
|
||
if (/SystemMaintenance|layout|login|dashboard|404/.test(page.relative)) return '系统管理员/相关用户';
|
||
if (/QualityManagement/.test(page.relative)) return '质检员/质量主管';
|
||
if (/PlanManagement/.test(page.relative)) return '计划员/生产主管';
|
||
if (/ProductionManagement/.test(page.relative)) return '操作工/班组长/生产主管';
|
||
if (/ProcessManagement|CraftManagement/.test(page.relative)) return '工艺员/技术人员';
|
||
if (/DeviceManagement/.test(page.relative)) return '设备管理员/生产主管';
|
||
if (/SaleManagement/.test(page.relative)) return '销售/计划/生产跟单人员';
|
||
if (/AnomalousManagement/.test(page.relative)) return '班组长/生产主管/计划员';
|
||
return '相关业务人员';
|
||
}
|
||
|
||
function moduleName(page) {
|
||
const map = {
|
||
AnomalousManagement: '异常管理',
|
||
CraftManagement: '产品结构维护',
|
||
DeviceManagement: '设备管理',
|
||
PlanManagement: '计划排产',
|
||
ProcessManagement: '工艺与图纸管理',
|
||
ProductionManagement: '生产执行管理',
|
||
QualityManagement: '质量管理',
|
||
SaleManagement: '销售与交付查询',
|
||
SystemMaintenance: '系统维护',
|
||
dashboard: '首页与看板',
|
||
layout: '布局与导航',
|
||
login: '登录',
|
||
};
|
||
if (page.relative === 'src/views/404.vue') return '错误页';
|
||
return map[page.module] || page.module;
|
||
}
|
||
|
||
function purpose(page) {
|
||
const p = page.relative;
|
||
const rules = [
|
||
[/404\.vue$/, '该页面用于路由不存在或无效地址访问时展示 404 提示,帮助用户返回首页或重新选择菜单。'],
|
||
[/login/, '该页面是系统登录入口,用户输入工号、密码并选择角色后进入 MES;同时提供修改密码功能。'],
|
||
[/dashboard/, '该页面是系统首页/看板入口,用户登录后查看系统概览或进入各业务模块。'],
|
||
[/PunctualWork/, '该页面用于查看未及时开工、待执行、可执行、已开工和已完成任务,帮助班组长或计划员催办生产开工。'],
|
||
[/CraftMaintain/, '该页面用于维护产品结构树,支持新增、编辑、删除产品结构节点,为工艺、BOM 和生产任务提供层级基础资料。'],
|
||
[/DeviceDataHistory/, '该页面用于查询设备历史采集数据,按工位、设备、参数和时间追溯设备运行记录。'],
|
||
[/DeviceInformation/, '该页面用于维护设备台账、设备属性、维修/工时/点检相关信息。'],
|
||
[/DeviceRealData|Devicedata/, '该页面用于查看设备实时数据或采集缓存数据,判断设备当前运行、报警和参数状态。'],
|
||
[/SelfMakePlandone[\\/]/, '该页面用于机加/自制件已排产任务查看和调整,支持计划时间、指派对象、指派数量、最晚开始、二次派工和备注维护。'],
|
||
[/SelfMakePlando[\\/]/, '该页面用于机加/自制件待排产任务处理,支持表单排产、日程拖拽调整、部分派工、批量排产、二次派工、原料状态和图纸查看。'],
|
||
[/SelfMakePlan[\\/]/, '该页面用于机加/自制件排产前准备,查询生产订单工序任务并维护计划、工位、数量和说明后确认排产。'],
|
||
[/InstallMakePlanDo|InstallMakePlan/, '该页面用于装配件排产,维护装配工位、人员、计划开始/完成、优先级、说明,并在排产完成时同步 SAP 或生成相关请求。'],
|
||
[/PlanShell/, '该页面用于计划外壳和计划跟踪,集中查看订单计划、齐套、缺料、发货或关闭相关信息。'],
|
||
[/PlannOrderClose/, '该页面用于关闭生产订单,用户查询待关闭订单后执行 MES/SAP 关闭动作。'],
|
||
[/priorityinstall/, '该页面用于装配计划优先级和日程调整,支持表单和日程方式查看重点任务。'],
|
||
[/ProductionOrder/, '该页面用于查询和同步生产订单,把 SAP/ERP 生产订单转成 MES 生产计划与工序任务。'],
|
||
[/PartDrawing/, '该页面用于维护零件图纸,支持按物料查询、查看、上传/验证和删除图纸。'],
|
||
[/PartDrawlook|ProcurementDrawlook/, '该页面用于按物料查看零件图纸,供生产、采购、外协或质检快速查阅技术文件。'],
|
||
[/ProcessDrawing/, '该页面用于维护工艺图纸,支持查询、查看、上传/验证和删除工艺图纸。'],
|
||
[/ProcedureManagement/, '该页面用于维护工序/路线阶段主数据,并与 SAP B1 RouteStages 保持一致。'],
|
||
[/ProcessInquiry/, '该页面用于查询标准工艺或生产工艺,供排产、生产、质检核对工序路线。'],
|
||
[/ProcessProduct|ProcessesDevelop/, '该页面用于维护产品工艺资料、工序路线、参数和图纸关系。'],
|
||
[/MachiningCenter/, '该页面是机加生产执行主界面,操作工按工位执行收料、开工、暂停、报工、检验、退库、发料和图纸查看。'],
|
||
[/AssemblyCenter/, '该页面是装配生产执行主界面,装配人员按工位执行开工、报工、送检、退库、缺料查询和图纸查看。'],
|
||
[/ProductionTaskManage|AssembleTaskManage/, '该页面用于生产/装配任务管理,查询任务、查看工时、维护派工和任务说明。'],
|
||
[/ProductionTasksSum|AssembleTasksSum/, '该页面用于生产/装配任务汇总,按订单、人员、工位、状态和时间统计任务执行情况。'],
|
||
[/ProductionPlanTrack|AssemblePlanTrack/, '该页面用于跟踪生产/装配计划执行进度,查看计划、完成、延期和异常情况。'],
|
||
[/AssembleStatus|DeviceStatus/, '该页面用于查看现场状态,关注工位、设备、任务是否开工、进行中或完成。'],
|
||
[/OutsourceMange|SaleOutsourceMange/, '该页面用于外协任务管理,处理外协派工、供应商、发料、收料、质检记录和 SAP 采购请求。'],
|
||
[/OutsourceSheet/, '该页面用于外协报表查询和导出,跟踪外协订单、供应商、数量、质检和结算状态。'],
|
||
[/PickingTask/, '该页面用于生产领料/拣配任务查询与处理,核对订单物料和库存任务。'],
|
||
[/PersonnelBinding/, '该页面用于维护人员与工位绑定关系,控制或提示人员可操作的工位。'],
|
||
[/ShiftManagement/, '该页面用于维护班次、班次人员和排班基础数据。'],
|
||
[/Timesheet|Workhours$/, '该页面用于查询工时记录,按人员、设备、任务、订单和日期统计工时。'],
|
||
[/WorkhoursEdit/, '该页面用于修正或补录工时/报工记录,处理异常工时。'],
|
||
[/WorkHoursCheck/, '该页面用于工时校验和 SAP 提交,用户核对报工记录后批量或单条校验并上传相关单据。'],
|
||
[/Urgent/, '该页面用于维护加急任务,跟踪加急订单、加急数量和完成情况。'],
|
||
[/AbnormalControl/, '该页面用于生产异常管理,查询异常、处理异常、转办、查看进度和关闭异常。'],
|
||
[/QualityCenter/, '该页面是质量综合处理中心,集中处理待检、收检、检测明细、不合格和 SAP/WMS 联动。'],
|
||
[/QualityDetails/, '该页面用于查询质检明细,按订单、物料、检验类型和时间追溯检测记录。'],
|
||
[/QualityStandardQuery/, '该页面用于查询或维护质量检验标准、检测项目、上下限和启用状态。'],
|
||
[/Unqualified/, '该页面用于不合格品处理,记录处置方式、责任、原因和处理结果。'],
|
||
[/CheckTask[\\/].*purchaseIncoming/, '该页面用于采购/来料检验,质检人员录入来料检验数量、合格数、不合格数和检测明细。'],
|
||
[/CheckTask[\\/].*processReceive/, '该页面用于工序收检/序检收检,质检人员接收工序检验任务并录入检验结果。'],
|
||
[/CheckTask[\\/].*firstSpecial/, '该页面用于首检或特殊首件检验,录入首件检测结果。'],
|
||
[/CheckTask[\\/].*final/, '该页面用于终检任务处理,录入最终检验结果。'],
|
||
[/CheckTask[\\/].*materialReturn/, '该页面用于材料退库检验,核对退库物料并录入检验结论。'],
|
||
[/CheckTask[\\/].*salesReturn/, '该页面用于销售退货检验,录入退货质检结果。'],
|
||
[/CheckTask[\\/].*OtherInbound/, '该页面用于其他入库检验,录入其他入库质检结果。'],
|
||
[/CheckTask[\\/].*processSpecial/, '该页面用于过程特殊检验,录入特殊检验项目和结果。'],
|
||
[/saleorderstatus/, '该页面用于查询销售订单状态,跟踪销售订单在生产、交付和关闭过程中的状态。'],
|
||
[/MenuManagement/, '该页面用于维护系统菜单、按钮权限和路由配置。'],
|
||
[/OperationLog/, '该页面用于查询操作日志,追溯用户操作、报工或业务变更记录。'],
|
||
[/OrgManagement/, '该页面用于维护组织、部门或组织树基础资料。'],
|
||
[/PersonnelManagement/, '该页面用于维护人员账号、部门、角色和启用状态。'],
|
||
[/SystemRrolemaintenance/, '该页面用于维护系统角色和角色权限。'],
|
||
[/WorkstationManagement/, '该页面用于维护工位/资源主数据,并同步 SAP B1 Resources。'],
|
||
[/layout|Sidebar|Navbar|TagsView|AppMain|ResizeHandler/, '该文件属于系统布局和导航组件,用户通过它切换菜单、标签页、折叠菜单或退出登录,不维护具体业务单据。'],
|
||
];
|
||
const found = rules.find(([re]) => re.test(p));
|
||
if (found) return found[1];
|
||
return '该页面为业务页面,用户按查询条件筛选数据,在列表或弹窗中查看明细,并按页面按钮完成维护、提交或状态更新。';
|
||
}
|
||
|
||
function actionVerb(button) {
|
||
const text = button.text;
|
||
if (/查询|搜索|全部|待执行|可执行|已开工|已完成|未完成/.test(text)) return '查询/筛选';
|
||
if (/新增|添加|新建|创建/.test(text)) return '新增';
|
||
if (/编辑|修改|保存|确定|确 定/.test(text)) return '编辑/保存';
|
||
if (/删除|移除/.test(text)) return '删除';
|
||
if (/导出|打印/.test(text)) return '导出/打印';
|
||
if (/查看|详情|图纸|明细|进度/.test(text)) return '查看';
|
||
if (/排产|派工|开工|报工|送检|收料|发料|退库|关闭|提交|处理|转办|完结|校验|上传/.test(text)) return '业务提交';
|
||
if (/取消|关闭|返回/.test(text)) return '取消/关闭';
|
||
return '操作';
|
||
}
|
||
|
||
function fieldName(value) {
|
||
return value
|
||
.replace(/^.*\./, '')
|
||
.replace(/\[[^\]]+\]/g, '')
|
||
.replace(/Form$/, '')
|
||
.replace(/Time$/, '时间')
|
||
.replace(/No$/, '编号');
|
||
}
|
||
|
||
function queryItems(page) {
|
||
const fromParams = page.params
|
||
.filter(p => !/任务列表|操作人|最后编辑人|editstr|personnel|TaskAID/.test(p.name))
|
||
.map(p => p.name);
|
||
const fromModels = page.vModels
|
||
.filter(v => !/^scope\.row|^item\.|^row\.|multipleSelection|loading|dialog|Visible|activeTab/.test(v))
|
||
.map(fieldName);
|
||
const fromPlaceholders = page.placeholders
|
||
.map(x => x.replace(/^请输入/, '').replace(/^请选择/, '').replace(/起始|结束/g, '').trim())
|
||
.filter(Boolean);
|
||
return stripNoise([...fromParams, ...fromModels, ...fromPlaceholders]).slice(0, 22);
|
||
}
|
||
|
||
function tableItems(page) {
|
||
return stripNoise(page.tableColumns.map(c => c.label || c.prop || c.type)).slice(0, 34);
|
||
}
|
||
|
||
function buttonItems(page) {
|
||
return stripNoise(page.buttons.map(b => b.text)).slice(0, 30);
|
||
}
|
||
|
||
function procItems(page) {
|
||
return uniq(page.procs.map(p => p.name)).slice(0, 20);
|
||
}
|
||
|
||
function sapItems(page) {
|
||
return uniq(page.sapCalls.map(s => `${s.method} ${s.url}`)).slice(0, 12);
|
||
}
|
||
|
||
function lineList(values, fallback) {
|
||
if (!values.length) return `- ${fallback}`;
|
||
return values.map(v => `- \`${v}\``).join('\n');
|
||
}
|
||
|
||
function detailedSteps(page) {
|
||
const steps = [];
|
||
const queries = queryItems(page);
|
||
const tabs = page.tabLabels;
|
||
const columns = tableItems(page);
|
||
const buttons = page.buttons.slice(0, 24);
|
||
const dialogs = page.dialogs;
|
||
const forms = page.formLabels;
|
||
const procs = procItems(page);
|
||
const pathText = page.relative;
|
||
|
||
steps.push(`从左侧菜单进入该页,确认页面标题、当前账号和业务角色正确。`);
|
||
if (tabs.length) {
|
||
steps.push(`先查看页签:${tabs.map(x => `\`${x}\``).join('、')};需要表格处理时进入表单/列表页签,需要看时间排布时进入日程/看板页签。`);
|
||
}
|
||
if (queries.length) {
|
||
steps.push(`在查询区按需要填写筛选条件:${queries.slice(0, 12).map(x => `\`${x}\``).join('、')}。`);
|
||
if (queries.length > 12) steps.push(`如果仍查不到数据,继续补充条件:${queries.slice(12, 22).map(x => `\`${x}\``).join('、')},或清空过窄条件后重新查询。`);
|
||
} else {
|
||
steps.push(`该页没有识别到固定查询区,按页面提示直接查看信息或使用菜单/按钮进入下一步。`);
|
||
}
|
||
const queryButtons = buttons.filter(b => actionVerb(b) === '查询/筛选');
|
||
if (queryButtons.length) {
|
||
steps.push(`点击 ${stripNoise(queryButtons.map(b => b.text)).slice(0, 8).map(x => `\`${x}\``).join('、')} 刷新列表;状态按钮会切换任务范围。`);
|
||
} else if (buttons.length) {
|
||
steps.push(`点击页面上的查询或刷新类按钮获取最新数据;如果页面自动加载,则先核对默认列表是否符合当前业务范围。`);
|
||
}
|
||
if (page.hasPagination) {
|
||
steps.push(`列表较多时使用分页区切换页码或调整每页条数,避免只检查第一页。`);
|
||
}
|
||
if (columns.length) {
|
||
steps.push(`查询后先核对关键列:${columns.slice(0, 12).map(x => `\`${x}\``).join('、')}。`);
|
||
if (columns.length > 12) steps.push(`继续横向滚动或展开表格,核对:${columns.slice(12, 26).map(x => `\`${x}\``).join('、')}。`);
|
||
}
|
||
if (page.hasTree) {
|
||
steps.push(`如果页面左侧或上方有树形结构,先选中组织、物料、菜单或产品节点,再查看右侧列表;新增子节点前确认父节点选中正确。`);
|
||
}
|
||
const nonQueryButtons = buttons.filter(b => !['查询/筛选', '取消/关闭'].includes(actionVerb(b)));
|
||
for (const button of nonQueryButtons.slice(0, 10)) {
|
||
const verb = actionVerb(button);
|
||
if (verb === '新增') steps.push(`需要新增时点击 \`${button.text}\`,在弹窗中逐项填写必填字段,确认编码、名称、状态等基础信息后保存。`);
|
||
else if (verb === '编辑/保存') steps.push(`需要维护已有记录时先选中目标行,再点击或触发表格内 \`${button.text}\`,修改字段后保存并等待成功提示。`);
|
||
else if (verb === '删除') steps.push(`需要删除时先确认该记录没有正在使用的业务数据,再点击 \`${button.text}\`,按确认提示执行。`);
|
||
else if (verb === '导出/打印') steps.push(`需要留存或流转时点击 \`${button.text}\`,导出/打印前确认当前查询条件和选中记录正确。`);
|
||
else if (verb === '查看') steps.push(`需要查看明细时点击 \`${button.text}\`,在弹窗或新区域核对图纸、明细、进度、记录或关联单据。`);
|
||
else if (verb === '业务提交') steps.push(`需要推进业务时先选中目标行并核对数量、时间、人员、工位、物料和备注,再点击 \`${button.text}\` 提交。`);
|
||
else steps.push(`按业务需要点击 \`${button.text}\`,操作前先确认选中行和输入内容。`);
|
||
}
|
||
if (dialogs.length) {
|
||
steps.push(`出现弹窗时按弹窗标题处理:${dialogs.slice(0, 8).map(x => `\`${x}\``).join('、')};弹窗关闭前确认必填项已保存。`);
|
||
}
|
||
if (forms.length) {
|
||
steps.push(`弹窗或表单内重点填写:${forms.slice(0, 14).map(x => `\`${x}\``).join('、')},提交前逐项核对。`);
|
||
}
|
||
if (page.hasUpload) {
|
||
steps.push(`涉及上传时先选择正确文件,再点击上传/保存;上传后重新查询,确认文件名、版本、上传时间或预览内容已显示。`);
|
||
}
|
||
if (page.hasCalendar) {
|
||
steps.push(`进入日程页后,单击任务查看详情;拖拽任务可调整计划开始时间,拉伸任务可调整计划完成时间,系统提示确认后才会保存。`);
|
||
steps.push(`日程保存失败或取消时任务会恢复原日期;保存成功后回到表单页重新查询,核对计划开始和计划完成是否同步。`);
|
||
}
|
||
if (procs.length) {
|
||
steps.push(`保存或提交成功后重新点击查询,确认页面数据已通过 ${procs.slice(0, 5).map(x => `\`${x}\``).join('、')} 等后台过程更新。`);
|
||
}
|
||
if (page.sapCalls.length) {
|
||
steps.push(`涉及 SAP/WMS 接口时,必须查看页面成功或失败提示;失败时先记录错误信息,不要连续重复提交同一单据。`);
|
||
}
|
||
|
||
if (/SelfMakePlando[\\/]/.test(pathText)) {
|
||
steps.push(`在 \`指派数量\` 中输入小于计划数的数量时,表示本次只派出部分数量,剩余数量需要继续跟踪和后续派工。`);
|
||
steps.push(`点击 \`批量排产\` 前应选择同一生产订单的多道工序,系统会按订单行号排序并继承上一道工序的部分计划信息。`);
|
||
steps.push(`点击 \`排产完成\` 后,系统校验指派对象、指派数量、计划开始、计划完成,并同步生产订单最早开始和最晚完成。`);
|
||
}
|
||
if (/SelfMakePlandone[\\/]/.test(pathText)) {
|
||
steps.push(`已排产列表中仍可调整指派对象、指派数量、计划开始、计划完成、外协分组、最晚开始和二次派工;调整后要重新查询确认。`);
|
||
}
|
||
if (/MachiningCenter|AssemblyCenter/.test(pathText)) {
|
||
steps.push(`现场执行类操作必须按实际顺序处理:先确认任务和工位,再收料/开工,完成加工或装配后再报工/送检。`);
|
||
}
|
||
if (/QualityManagement/.test(pathText)) {
|
||
steps.push(`录入检验结果时先核对检验类型、订单、物料、批次/工序,再填写合格数、不合格数和检测明细,避免检验记录挂错任务。`);
|
||
}
|
||
if (/WorkHoursCheck/.test(pathText)) {
|
||
steps.push(`工时校验前先比对 MES 报工、设备工时和 SAP 工序资源;只对核对无误的记录执行提交。`);
|
||
}
|
||
if (/WorkstationManagement/.test(pathText)) {
|
||
steps.push(`工位新增或修改会影响 SAP 资源、派工、报工和看板,保存后要用排产或生产执行页面验证工位是否可选。`);
|
||
}
|
||
if (/MenuManagement|SystemRrolemaintenance|PersonnelManagement/.test(pathText)) {
|
||
steps.push(`权限类修改保存后,需要让相关用户重新登录或刷新权限,再验证菜单、按钮和数据范围是否生效。`);
|
||
}
|
||
|
||
return steps.map((s, i) => `${i + 1}. ${s}`).join('\n');
|
||
}
|
||
|
||
function notes(page) {
|
||
const values = [];
|
||
if (/PlanManagement/.test(page.relative)) values.push('排产、派工和计划时间会影响生产看板、及时开工统计和 SAP 生产订单日期,提交前必须核对。');
|
||
if (/ProductionManagement/.test(page.relative)) values.push('生产执行页面的数据会直接影响报工、工时、质检和库存任务,现场操作必须与实际加工进度一致。');
|
||
if (/QualityManagement/.test(page.relative)) values.push('质检结果保存后会影响合格入库、不合格处理和后续工序,提交前必须核对数量。');
|
||
if (/ProcessManagement|CraftManagement/.test(page.relative)) values.push('工艺、图纸和结构资料会被排产、生产、质检引用,版本错误会直接影响现场执行。');
|
||
if (/DeviceManagement/.test(page.relative)) values.push('设备和采集数据用于看板、OEE 和报工分析,发现异常时应先确认设备编码和采集点位。');
|
||
if (/SystemMaintenance/.test(page.relative)) values.push('系统维护类数据会影响登录、权限、菜单和业务可选项,修改后需要验证。');
|
||
if (page.sapCalls.length) values.push('该页存在 SAP/WMS 调用,失败时应记录返回提示并确认是否已经生成外部单据。');
|
||
if (page.hasCalendar) values.push('日程拖拽属于直接改计划时间的操作,拖拽前应确认任务未被现场执行锁定。');
|
||
if (!values.length) values.push('操作后建议重新查询页面,确认列表、状态和明细已经刷新。');
|
||
return values.map(v => `- ${v}`).join('\n');
|
||
}
|
||
|
||
function section(page, index) {
|
||
const query = queryItems(page);
|
||
const columns = tableItems(page);
|
||
const buttons = buttonItems(page);
|
||
const procs = procItems(page);
|
||
const sap = sapItems(page);
|
||
|
||
return `### 21.${index} ${page.name}
|
||
|
||
页面路径:\`${page.relative}\`
|
||
所属模块:${moduleName(page)}
|
||
建议使用人员:${roleFor(page)}
|
||
|
||
页面用途:${purpose(page)}
|
||
|
||
**具体操作步骤**
|
||
${detailedSteps(page)}
|
||
|
||
**查询条件/输入项**
|
||
${lineList(query, '未识别到固定输入项,按现场页面提示操作。')}
|
||
|
||
**结果表重点列**
|
||
${lineList(columns, '未识别到固定表格列,以页面实际显示为准。')}
|
||
|
||
**页面按钮/状态/页签**
|
||
${lineList([...buttons, ...page.tabLabels].slice(0, 36), '未识别到明确按钮,以页面实际显示为准。')}
|
||
|
||
**弹窗/表单重点项**
|
||
${lineList([...page.dialogs, ...page.formLabels].slice(0, 30), '未识别到固定弹窗或表单项。')}
|
||
|
||
**后台处理/外部接口**
|
||
${lineList([...procs.map(x => `存储过程:${x}`), ...sap.map(x => `接口:${x}`)].slice(0, 30), '未发现页面级后台提交动作。')}
|
||
|
||
**操作注意事项**
|
||
${notes(page)}
|
||
`;
|
||
}
|
||
|
||
function moduleOrder(page) {
|
||
const order = [
|
||
'404.vue',
|
||
'AnomalousManagement',
|
||
'CraftManagement',
|
||
'DeviceManagement',
|
||
'PlanManagement',
|
||
'ProcessManagement',
|
||
'ProductionManagement',
|
||
'QualityManagement',
|
||
'SaleManagement',
|
||
'SystemMaintenance',
|
||
'dashboard',
|
||
'layout',
|
||
'login'
|
||
];
|
||
const key = page.relative === 'src/views/404.vue' ? '404.vue' : page.module;
|
||
const idx = order.indexOf(key);
|
||
return idx >= 0 ? idx : 99;
|
||
}
|
||
|
||
function moduleHeading(page) {
|
||
const key = page.relative === 'src/views/404.vue' ? '404.vue' : page.module;
|
||
const headings = {
|
||
'404.vue': '错误页',
|
||
AnomalousManagement: '异常管理',
|
||
CraftManagement: '产品结构维护',
|
||
DeviceManagement: '设备管理',
|
||
PlanManagement: '计划排产',
|
||
ProcessManagement: '工艺与图纸管理',
|
||
ProductionManagement: '生产执行管理',
|
||
QualityManagement: '质量管理',
|
||
SaleManagement: '销售与交付查询',
|
||
SystemMaintenance: '系统维护',
|
||
dashboard: '首页与看板',
|
||
layout: '布局与导航',
|
||
login: '登录'
|
||
};
|
||
return headings[key] || key;
|
||
}
|
||
|
||
function buildChapter(pages) {
|
||
const sorted = pages.sort((a, b) => {
|
||
const m = moduleOrder(a) - moduleOrder(b);
|
||
if (m !== 0) return m;
|
||
return a.relative.localeCompare(b.relative, 'zh-Hans-CN');
|
||
});
|
||
|
||
const lines = [];
|
||
lines.push('## 二十一、逐页详细操作说明(按 src/views 页面)');
|
||
lines.push('');
|
||
lines.push('本章按源码页面逐页编写。每个页面都从实际 `v-model`、表格列、按钮、弹窗、页签、存储过程和 SAP/WMS 调用中提取操作线索,转换成用户可执行的步骤。');
|
||
lines.push('');
|
||
lines.push('通用阅读方法:');
|
||
lines.push('- `查询条件/输入项` 是用户进入页面后可以填写、选择或用于筛选的字段。');
|
||
lines.push('- `结果表重点列` 是查询后需要核对的表格列,表格可横向滚动时应继续向右查看。');
|
||
lines.push('- `页面按钮/状态/页签` 是页面上能触发查询、查看、编辑、提交、导出、状态切换或页面切换的入口。');
|
||
lines.push('- `后台处理/外部接口` 用于说明保存后可能调用的存储过程、SAP 或 WMS 接口,业务人员主要关注成功/失败提示。');
|
||
lines.push('- 涉及排产、报工、质检、外协、SAP/WMS 的操作,提交前必须核对订单、物料、数量、人员、工位和日期。');
|
||
lines.push('');
|
||
|
||
let current = '';
|
||
let moduleIndex = 0;
|
||
sorted.forEach((page, i) => {
|
||
const heading = moduleHeading(page);
|
||
if (heading !== current) {
|
||
moduleIndex += 1;
|
||
current = heading;
|
||
lines.push(`## 21-${moduleIndex} ${heading}`);
|
||
lines.push('');
|
||
}
|
||
lines.push(section(page, i + 1).trimEnd());
|
||
lines.push('');
|
||
});
|
||
return lines.join('\n').trimEnd() + '\n';
|
||
}
|
||
|
||
function main() {
|
||
const pages = walk(viewsRoot)
|
||
.filter(file => !/node_modules/.test(file))
|
||
.map(extractPage);
|
||
const chapter = buildChapter(pages);
|
||
const manual = readText(manualPath);
|
||
const marker = '## 二十一、逐页详细操作说明(按 src/views 页面)';
|
||
const idx = manual.indexOf(marker);
|
||
if (idx < 0) throw new Error(`找不到章节标记:${marker}`);
|
||
const nextChapter = manual.slice(idx + marker.length).search(/\n## 二十二、/);
|
||
const head = manual.slice(0, idx).trimEnd() + '\n\n';
|
||
const tail = nextChapter >= 0 ? manual.slice(idx + marker.length + nextChapter) : '';
|
||
writeText(manualPath, head + chapter + (tail ? '\n' + tail.trimStart() : ''));
|
||
console.log(`rewrote chapter 21 for ${pages.length} pages`);
|
||
}
|
||
|
||
main();
|