feat: 新增装机绑定功能与权限、BOM工序配置

1.  新增装机绑定实体与相关CRUD逻辑,支持工件工序物料绑定/解绑
2.  为BOM物料新增装配工序字段,支持按工序校验绑定
3.  扩展权限表,新增父权限码字段支持菜单按钮关联
4.  统一各页面帮助弹窗参数,新增角色管理帮助文档
5.  新增公共格式化工具函数,优化侧边栏菜单展开逻辑
6.  新增工位终端绑定代理接口与MES内部绑定API
7.  修复主入口数据库连接与schema初始化逻辑,新增一键迁移工具
This commit is contained in:
SunYF
2026-09-07 11:58:19 +08:00
parent 92f0439d89
commit 6ee131a4bd
70 changed files with 6966 additions and 365 deletions
+104 -26
View File
@@ -22,7 +22,7 @@
</el-form-item>
</el-form>
<!-- 汇总卡 -->
<!-- 汇总卡基于当前筛选的全部明细三个视图共用 -->
<el-row :gutter="16" style="margin-bottom:14px">
<el-col :span="6">
<el-card shadow="never" class="stat-card">
@@ -50,24 +50,64 @@
</el-col>
</el-row>
<el-table :data="rows" border size="small">
<el-table-column prop="operator" label="操作人" min-width="110" />
<el-table-column prop="date" label="日期" width="110" />
<el-table-column prop="processCode" label="工位" width="80" align="center" />
<el-table-column prop="processName" label="工位名" min-width="130" show-overflow-tooltip />
<el-table-column prop="doneCount" label="完成数" width="90" align="right" />
<el-table-column prop="okCount" label="合格数" width="90" align="right" />
<el-table-column prop="ngCount" label="NG数" width="90" align="right" />
<el-table-column label="合格率" width="100" align="center">
<template #default="{ row }">
<el-tag :type="rateOf(row) >= 100 ? 'success' : (rateOf(row) >= 0 ? 'warning' : 'info')" effect="plain">
{{ rateText(row) }}
</el-tag>
</template>
</el-table-column>
</el-table>
<el-tabs v-model="activeTab">
<!-- 按人 -->
<el-tab-pane label="按人" name="byOperator">
<el-table :data="rowsByOperator" border size="small">
<el-table-column prop="operator" label="操作人" min-width="130" />
<el-table-column prop="doneCount" label="完成数" width="110" align="right" />
<el-table-column prop="okCount" label="合格数" width="110" align="right" />
<el-table-column prop="ngCount" label="NG数" width="110" align="right" />
<el-table-column label="合格率" width="110" align="center">
<template #default="{ row }">
<el-tag :type="rateOf(row) >= 100 ? 'success' : (rateOf(row) >= 0 ? 'warning' : 'info')" effect="plain">
{{ rateText(row) }}
</el-tag>
</template>
</el-table-column>
</el-table>
</el-tab-pane>
<!-- 按工位 -->
<el-tab-pane label="按工位" name="byStation">
<el-table :data="rowsByStation" border size="small">
<el-table-column prop="station" label="工位" width="90" align="center" />
<el-table-column prop="stationName" label="工位名" min-width="140" show-overflow-tooltip />
<el-table-column prop="doneCount" label="完成数" width="110" align="right" />
<el-table-column prop="okCount" label="合格数" width="110" align="right" />
<el-table-column prop="ngCount" label="NG数" width="110" align="right" />
<el-table-column label="合格率" width="110" align="center">
<template #default="{ row }">
<el-tag :type="rateOf(row) >= 100 ? 'success' : (rateOf(row) >= 0 ? 'warning' : 'info')" effect="plain">
{{ rateText(row) }}
</el-tag>
</template>
</el-table-column>
</el-table>
</el-tab-pane>
<!-- 明细 -->
<el-tab-pane label="明细" name="detail">
<el-table :data="rows" border size="small">
<el-table-column prop="operator" label="操作人" min-width="110" />
<el-table-column prop="date" label="日期" width="110" />
<el-table-column prop="processCode" label="工位" width="80" align="center" />
<el-table-column prop="processName" label="工位名" min-width="130" show-overflow-tooltip />
<el-table-column prop="doneCount" label="完成数" width="90" align="right" />
<el-table-column prop="okCount" label="合格数" width="90" align="right" />
<el-table-column prop="ngCount" label="NG数" width="90" align="right" />
<el-table-column label="合格率" width="100" align="center">
<template #default="{ row }">
<el-tag :type="rateOf(row) >= 100 ? 'success' : (rateOf(row) >= 0 ? 'warning' : 'info')" effect="plain">
{{ rateText(row) }}
</el-tag>
</template>
</el-table-column>
</el-table>
</el-tab-pane>
</el-tabs>
</el-card>
<PageHelp :data="helpPerformance" />
<PageHelp :data="helpPerformance" page="Performance" />
</template>
<script setup>
@@ -77,6 +117,7 @@ import PageHelp from '../components/PageHelp.vue'
import { helpPerformance } from '../help'
import request from '../utils/request'
const activeTab = ref('byOperator')
const query = reactive({ operator: '', range: null })
const rows = ref([])
@@ -93,6 +134,34 @@ const summary = computed(() => {
}
})
// 按人聚合:同一操作人的完成/合格/NG 累加
const rowsByOperator = computed(() => {
const m = new Map()
for (const r of rows.value) {
const k = r.operator
if (!m.has(k)) m.set(k, { operator: k, doneCount: 0, okCount: 0, ngCount: 0 })
const o = m.get(k)
o.doneCount += Number(r.doneCount) || 0
o.okCount += Number(r.okCount) || 0
o.ngCount += Number(r.ngCount) || 0
}
return [...m.values()]
})
// 按工位聚合:同一工位(工序编号)的完成/合格/NG 累加
const rowsByStation = computed(() => {
const m = new Map()
for (const r of rows.value) {
const k = r.processCode
if (!m.has(k)) m.set(k, { station: r.processCode, stationName: r.processName, doneCount: 0, okCount: 0, ngCount: 0 })
const o = m.get(k)
o.doneCount += Number(r.doneCount) || 0
o.okCount += Number(r.okCount) || 0
o.ngCount += Number(r.ngCount) || 0
}
return [...m.values()]
})
function rateOf(row) {
const done = Number(row.doneCount) || 0
const ok = Number(row.okCount) || 0
@@ -114,20 +183,29 @@ async function load() {
rows.value = data.rows || []
}
// 导出 CSV(块6):按当前查询结果生成,含 BOM 以便 Excel 打开中文不乱码
// 导出 CSV(块6):按当前激活的 tab 视图生成,含 BOM 以便 Excel 打开中文不乱码
function exportCsv() {
if (!rows.value.length) return ElMessage.warning('当前无数据可导出')
const header = ['操作人', '日期', '工位', '工位名', '完成数', '合格数', 'NG数', '合格率']
const lines = rows.value.map((r) => {
const rate = Number(r.doneCount) ? ((Number(r.okCount) / Number(r.doneCount)) * 100).toFixed(1) + '%' : '-'
return [r.operator, r.date, r.processCode, r.processName, r.doneCount, r.okCount, r.ngCount, rate]
})
let header, lines
if (activeTab.value === 'byOperator') {
header = ['操作人', '完成数', '合格数', 'NG数', '合格率']
lines = rowsByOperator.value.map((r) => [r.operator, r.doneCount, r.okCount, r.ngCount, rateText(r)])
} else if (activeTab.value === 'byStation') {
header = ['工位', '工位名', '完成数', '合格数', 'NG数', '合格率']
lines = rowsByStation.value.map((r) => [r.station, r.stationName, r.doneCount, r.okCount, r.ngCount, rateText(r)])
} else {
header = ['操作人', '日期', '工位', '工位名', '完成数', '合格数', 'NG数', '合格率']
lines = rows.value.map((r) => {
const rate = Number(r.doneCount) ? ((Number(r.okCount) / Number(r.doneCount)) * 100).toFixed(1) + '%' : '-'
return [r.operator, r.date, r.processCode, r.processName, r.doneCount, r.okCount, r.ngCount, rate]
})
}
if (!lines.length) return ElMessage.warning('当前无数据可导出')
const esc = (v) => `"${String(v ?? '').replace(/"/g, '""')}"`
const csv = '\ufeff' + [header, ...lines].map((row) => row.map(esc).join(',')).join('\r\n')
const blob = new Blob([csv], { type: 'text/csv;charset=utf-8;' })
const a = document.createElement('a')
a.href = URL.createObjectURL(blob)
a.download = `绩效报表_${new Date().toISOString().slice(0, 10)}.csv`
a.download = `绩效报表_${activeTab.value}_${new Date().toISOString().slice(0, 10)}.csv`
a.click()
URL.revokeObjectURL(a.href)
}