docs: update MES workflow and user manual

This commit is contained in:
meswork764
2026-05-26 17:59:27 +08:00
parent 39638e9032
commit acca29b36b
7 changed files with 9943 additions and 377 deletions

View File

@@ -175,6 +175,7 @@
style="width: 100%; margin-top: 20px"
tooltip-effect="dark"
@row-click="handleRowClick"
:row-class-name="tableRowClassName"
>
<el-table-column
align="center"
@@ -743,6 +744,14 @@ export default {
this.loading = false;
if (response.data.rows && response.data.rows.length !== 0) {
this.tableData = response.data.rows;
this.tableData.sort((a, b) => {
const aIsUrgent = a.加急状态 === '1';
const bIsUrgent = b.加急状态 === '1';
if (aIsUrgent && !bIsUrgent) return -1;
if (!aIsUrgent && bIsUrgent) return 1;
return 0;
});
this.total = response.data.total
} else {
this.tableData = [];
@@ -1122,10 +1131,27 @@ export default {
const seconds = String(date.getSeconds()).padStart(2, "0");
return `${year}${month}${day}_${hours}${minutes}${seconds}`;
},
tableRowClassName({ row, rowIndex }) {
const isUrgent = row.加急状态 == "1";
if (isUrgent) {
return "priority-row";
}
return "";
},
},
};
</script>
<style scoped>
::v-deep .priority-row {
background-color: #f56c6c !important;
color: #000000 !important;
}
::v-deep .priority-row td {
background-color: #f56c6c !important;
color: #000000 !important;
border: #f56c6c;
}
</style>
<style>
.search-container {
margin-bottom: 20px;
@@ -1343,4 +1369,4 @@ button {
width: 20px;
height: 20px;
}
</style>
</style>

View File

@@ -0,0 +1,143 @@
# 元利 MES 项目 — 今日工作汇报
日期2026-05-26
项目MES_Manage_View_V20
## 一、今日主要工作
### 1. 丰富并修改元利项目详细流程图
今日继续完善 `元利项目详细流程图.md`,在原有系统级流程基础上,进一步补充页面级、业务级流程说明。
重点补充内容:
- 登录、菜单权限、动态路由生成流程。
- 工艺、图纸、生产订单、排产、生产执行、质检、外协、工时校验、生产订单关闭等模块之间的衔接关系。
- 机加排产、装配排产、机加中心、装配中心、质量中心、工时校验等关键页面的详细业务流。
- SAP 回写、SAP 查询、WMS/库存任务、MES 存储过程之间的数据流转关系。
- 将流程图尽量调整为“框 + 箭头 + 文字”的形式,减少字母节点指引,便于培训和现场讲解。
本次调整后,流程图更适合用于:
- 给客户讲解整体 MES 业务闭环。
- 给实施人员定位页面与数据流关系。
- 给开发人员判断新增需求影响范围。
### 2. 工时校验数据对接梳理
今日继续梳理工时校验页面与 MES/SAP 数据之间的对接关系,重点围绕 `ProductionManagement/WorkHoursCheck`
已梳理内容:
- 对接数据时发现装配人员工时数据错误,经检查由于用户重复提交导致的问题
- 在排查过程中发现装配人员报工加班时,同时生成了正常报工的工时
### 3. 生产订单关闭培训与相关数据处理
今日进行了生产订单关闭相关培训和数据处理,重点说明生产订单关闭页面的使用边界。
培训内容:
- 生产订单关闭前需要先确认订单在 MES 中的任务完成情况。
- 关闭前需要核对生产订单状态、入库情况、报工情况和 SAP 状态。
- 批量关闭时要区分已成功关闭、关闭失败、不能关闭三类结果。
- SAP 关闭失败时不应重复点击,应先确认 SAP 返回原因。
数据处理内容:
- 协助检查生产订单关闭相关数据。
- 梳理关闭条件与当前订单状态之间的关系。
- 对异常数据进行核对,避免误关或重复关闭。
### 4. 沟通报工结果数据上传到 SAP 的影响
今日围绕报工结果上传 SAP 的影响进行了沟通和确认。
沟通重点:
- 超过班次工作时间部分的工时 如果不选择加班,系统自动记录这段工时
- 这段工时如果上次到SAP会导致其他生成订单工时不准确的问题,因为计算方法核算的是当日总报工时间记为8小时
- 这段时间如果上传,就会导致其他工时减少
- 休息日/法定时选择不加班工时记录的问题讨论结果是MES正常记录 但是不传输给SAP
### 5. 修改装配报工数据重复问题
今日处理装配报工数据重复问题。
处理重点:
- 排查装配报工过程中可能重复写入的数据链路。
- 核对装配任务、报工记录、质检/送检记录之间的关联关系。
- 调整重复数据产生场景,避免同一任务重复形成报工结果。
- 修改后重点关注装配中心报工、送检、任务完成状态的回显是否一致。
处理结果:
- 已针对装配报工重复问题进行修正。
- 后续需要继续观察现场报工数据,确认是否仍存在重复写入或重复统计。
### 6. 丰富并修改用户使用说明书
今日重点丰富 `元利项目详细用户使用说明书.md`
主要修改内容:
- 将第 21 章逐页说明整体扩展,不再只写笼统功能说明。
- 对 78 个页面按源码逐页补充:
- 页面用途
- 具体操作步骤
- 查询条件/输入项
- 结果表重点列
- 页面按钮/状态/页签
- 弹窗/表单重点项
- 后台处理/外部接口
- 操作注意事项
- 将第 21 章页面标题从英文页面名改为中文页面名,例如:
- `WorkHoursCheck` 改为 `工时校验`
- `SelfMakePlandone` 改为 `机加已排产`
- `MachiningCenter` 改为 `机加中心`
- `QualityCenter` 改为 `质量中心`
-`机加已排产` 章节进行了重点细化补充日程拖拽、指派数量、拆分派工、二次派工、原料状态、图纸、外协分组、最晚开始、红色加急排序、SAP 日期同步等操作。
说明书修改后更适合:
- 现场用户培训。
- 新员工快速熟悉页面。
- 实施人员按页面排查问题。
- 后续新增需求评审时对照现有功能。
### 7. 修改原料退库检,增加加急显示
今日处理原料退库检页面加急显示需求。
涉及页面:
- `QualityManagement/CheckTask/materialReturn.vue`
处理内容:
- 在原料退库检任务中增加加急状态/加急信息显示。
- 方便质检人员优先识别加急退库检任务。
- 便于后续按加急任务排序、提醒或优先处理。
业务影响:
- 质检人员在原料退库检页面可以更直观看到加急任务。
- 加急退库检任务可优先处理,减少对生产或后续流程的影响。
## 二、今日产出与修改文件
| 文件 | 今日处理内容 |
|---|---|
| `元利项目详细流程图.md` | 丰富业务流程图,补充多页面、多模块数据流 |
| `元利项目详细用户使用说明书.md` | 扩展第 21 章逐页操作说明,标题改为中文页面名 |
| `ProductionManagement/WorkHoursCheck` | 梳理工时校验与 SAP 上传关系 |
| `PlanManagement/PlannOrderClose` | 生产订单关闭培训与数据处理 |
| 装配报工相关页面/数据 | 修正装配报工重复数据问题 |
| `QualityManagement/CheckTask/materialReturn.vue` | 原料退库检增加加急显示 |
## 三、今日沟通事项
1. 与相关人员沟通工时校验数据上传 SAP 的影响明确报工结果、工时、SAP 工序资源之间的关系。
2. 对生产订单关闭进行培训,说明关闭前校验、关闭失败处理和数据风险。
3. 结合装配报工重复问题,沟通现场报工操作和后台数据写入逻辑。
4. 结合用户说明书和流程图,明确后续培训资料需要按页面逐步讲解,不能只写功能概述。
## 四、当前风险与注意事项
| 风险点 | 影响 | 处理建议 |
|---|---|---|
| 工时上传 SAP 重复提交 | 可能造成 SAP 工时或库存单据重复 | 上传失败时先查失败原因,不要连续重复提交 |
| 生产订单关闭条件不一致 | 可能误关订单或关闭失败 | 关闭前核对 MES 任务、入库、报工和 SAP 状态 |
| 装配报工重复 | 影响工时、任务进度、质检/送检统计 | 修复后继续观察现场数据 |
| 加急任务未突出显示 | 质检处理优先级不明确 | 原料退库检已增加加急显示,后续可扩展排序 |
| 用户手册仍需继续细化 | 培训时可能仍有页面操作细节不足 | 后续按重点业务页面继续逐页补强 |

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,101 @@
const fs = require('fs');
const path = require('path');
const file = path.join(process.cwd(), 'MES_Manage_View_V20', '元利项目详细用户使用说明书.md');
const text = fs.readFileSync(file, 'utf8');
const names = {
'404.vue': '404 页面',
PunctualWork: '及时开工异常',
CraftMaintain: '产品结构维护',
Devicedata: '设备采集数据',
DeviceDataHistory: '设备历史数据',
DeviceInformation: '设备信息维护',
DeviceRealData: '设备实时数据',
InstallMakePlan: '装配排产',
InstallMakePlanDo: '装配已排产',
PlannOrderClose: '生产订单关闭',
PlanShell: '计划外壳跟踪',
priorityinstall: '装配优先级调整',
SelfMakePlan: '机加排产',
SelfMakePlando: '机加待排产',
SelfMakePlandone: '机加已排产',
PartDrawing: '零件图纸维护',
PartDrawlook: '零件图纸查看',
ProcedureManagement: '工序维护',
ProcessDrawing: '工艺图纸维护',
ProcessesDevelop: '工艺开发',
ProcessInquiry: '工艺查询',
ProcessProduct: '生产工艺维护',
ProcurementDrawlook: '采购图纸查看',
ProductionOrder: '生产订单',
AbnormalControl: '异常管控',
AssemblePlanTrack: '装配计划跟踪',
AssembleStatus: '装配状态',
AssembleTaskManage: '装配任务管理',
AssembleTasksSum: '装配任务汇总',
AssemblyCenter: '装配中心',
DeviceStatus: '设备状态',
MachiningCenter: '机加中心',
OutsourceMange: '外协管理',
OutsourceSheet: '外协报表',
PersonnelBinding: '人员绑定',
PickingTask: '拣配任务',
ProductionPlanTrack: '生产计划跟踪',
ProductionTaskManage: '生产任务管理',
ProductionTasksSum: '生产任务汇总',
SaleOutsourceMange: '销售外协管理',
ShiftManagement: '班次管理',
Timesheet: '工时报表',
Urgent: '加急任务',
Workhours: '工时查询',
'index - 副本.vue': '工时校验(副本)',
WorkHoursCheck: '工时校验',
WorkhoursEdit: '工时编辑',
'final.vue': '终检',
'firstSpecial.vue': '首检',
'materialReturn.vue': '原材料退库检',
'OtherInbound.vue': '其他入库检',
'processReceive.vue': '序检收检',
'processSpecial.vue': '过程特殊检验',
'purchaseIncoming.vue': '采购入库检',
'salesReturn.vue': '销售退货检',
QualityCenter: '质量中心',
QualityDetails: '质检明细',
'index copy.vue': '质量标准查询(副本)',
QualityStandardQuery: '质量标准查询',
Unqualified: '不合格品处理',
saleorderstatus: '销售订单状态',
MenuManagement: '菜单管理',
OperationLog: '操作日志',
OrgManagement: '组织管理',
PersonnelManagement: '人员管理',
SystemRrolemaintenance: '系统角色维护',
WorkstationManagement: '工位管理',
dashboard: '首页看板',
'AppMain.vue': '内容区域',
'index.js': '布局组件索引',
'Navbar.vue': '顶部导航',
Sidebar: '侧边栏',
'Item.vue': '侧边栏菜单项',
'SidebarItem.vue': '侧边栏菜单节点',
'TagsView.vue': '标签页导航',
'Layout.vue': '系统布局',
'ResizeHandler.js': '窗口自适应',
login: '系统登录'
};
const updated = text.replace(/^### (21\.\d+) (.+)$/gm, (match, num, oldName) => {
const nextName = names[oldName.trim()];
return nextName ? `### ${num} ${nextName}` : match;
});
fs.writeFileSync(file, updated, 'utf8');
const missing = [...text.matchAll(/^### (21\.\d+) (.+)$/gm)]
.map(m => m[2].trim())
.filter(name => !names[name]);
if (missing.length) {
console.log(`missing mappings: ${[...new Set(missing)].join(', ')}`);
}
console.log('chapter 21 titles renamed');

View File

@@ -0,0 +1,573 @@
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(/&nbsp;/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();

Binary file not shown.