feat&refactor: 完成多模块功能迭代与配置优化

本次提交覆盖多个业务模块的功能完善与体验优化:
1.  **鉴权与配置调整**:
    - 统一JWT滑动续签逻辑,简化Token存储,移除RefreshToken相关冗余代码
    - 调整多项目配置文件中JWT过期时间为3600秒,统一会话闲置窗口
    - 工位配置放开1~12限制,改为仅校验大于0
2.  **术语统一替换**:全链路将"精密件"替换为"电气件",修正物料管理描述
3.  **功能新增**:
    - 新增工位类型、工艺路线与产线点位台账模块
    - 添加工艺PDF预览面板、工位终端代理转发接口
    - 新增操作日志按操作人列表筛选、工位登出日志记录
    - 新增PLC移料指令与产线点位状态管理
4.  **业务流程优化**:
    - 调整BOM物料删除校验逻辑,优化工单备料计算
    - 补充物料图号、检测单号等追溯字段
    - 完善工艺流程图与工位绑定关系说明
    - 优化前端页面文案与交互细节
5.  **代码规范与维护**:
    - 新增通用工具函数与前端静态资源
    - 整理路由权限与中间件逻辑
    - 修复部分接口与配置的不兼容问题
This commit is contained in:
SunYF
2026-09-10 16:59:15 +08:00
parent 6d8bb75696
commit 0ab21b1234
202 changed files with 21397 additions and 3172 deletions
+63 -22
View File
@@ -1,6 +1,7 @@
<script setup>
defineOptions({ name: 'Inspection' })
import { computed, nextTick, onMounted, reactive, ref } from 'vue'
import { useRoute } from 'vue-router'
import { ElMessage } from 'element-plus'
import request from '../utils/request'
import { getRealName } from '../utils/auth'
@@ -13,6 +14,10 @@ import { helpInspection } from '../help'
// ===== 多 Tab:录入在前默认打开,记录在后点击才见(参照出库管理) =====
const activeTab = ref('entry') // entry=质量检验(录入) / records=检验记录
// 菜单按业务分开(来料/过程/完工检验):从路由 meta 锁定检验类型,同表同接口按类别过滤
const route = useRoute()
const pageType = String(route.meta.inspectionType || '')
/* ===================== Tab1 质量检验(录入,默认打开) ===================== */
const targetType = ref('BATCH')
const status = ref('合格')
@@ -23,7 +28,8 @@ const submitting = ref(false)
const inspectQty = ref(0)
const passQty = ref(0)
const checkDesc = ref('')
const failReason = ref('')
const inspectionNo = ref('')
const reportNo = ref('')
// 待检编号列表(合并单/批量,统一一次提交)
const targetList = ref([])
@@ -102,8 +108,9 @@ async function submitInspection() {
inspectQty: Number(inspectQty.value) || 0,
passQty: Number(passQty.value) || 0,
checkDesc: checkDesc.value.trim(),
failReason: failReason.value.trim(),
inspector: inspector.value.trim()
reportNo: reportNo.value.trim(),
inspector: inspector.value.trim(),
inspectionNo: inspectionNo.value.trim(),
})
const n = data?.flipped ?? targetList.value.length
ElMessage.success(`检验已记录:共 ${n} 条 → ${status.value}`)
@@ -111,7 +118,7 @@ async function submitInspection() {
inspectQty.value = 0
passQty.value = 0
checkDesc.value = ''
failReason.value = ''
reportNo.value = ''
// 提交成功后主动刷新记录 Tab(用户切过去即可见最新)
await loadRecords()
} finally {
@@ -132,10 +139,23 @@ function defaultStart() {
// 筛选独立条件(不再笼统关键字);默认近3个月
const filters = reactive({
targetId: '', materialCode: '', inspectionType: '', status: '', inspector: '',
targetId: '', materialCode: '', inspectionType: pageType, status: '', inspector: '',
startDate: defaultStart(), endDate: ''
})
const loading = ref(false)
const statsList = ref([])
const statsLoading = ref(false)
async function loadStats() {
statsLoading.value = true
try {
const data = await request.get('/inspection/stats', {
params: { inspectionType: filters.inspectionType, startDate: filters.startDate, endDate: filters.endDate }
})
statsList.value = data?.list || []
} finally {
statsLoading.value = false
}
}
const list = ref([])
const total = ref(0)
const page = reactive({ current: 1, size: 20 })
@@ -215,7 +235,7 @@ onMounted(() => {
<el-form-item label="目标类型">
<el-radio-group v-model="targetType" @change="() => loadPicker()">
<el-radio-button value="BATCH">批次 (结构件)</el-radio-button>
<el-radio-button value="SN">序列号 SN (精密件)</el-radio-button>
<el-radio-button value="SN">序列号 SN (电气件)</el-radio-button>
</el-radio-group>
</el-form-item>
<el-form-item label="检验结论">
@@ -225,8 +245,14 @@ onMounted(() => {
</el-radio-group>
</el-form-item>
<el-form-item label="检验员">
<el-input v-model="inspector" placeholder="检验员姓名" clearable style="width:260px" />
</el-form-item>
<el-input v-model="inspector" placeholder="检验员姓名" clearable style="width:260px" />
</el-form-item>
<el-form-item label="检测单号">
<el-input v-model="inspectionNo" placeholder="检验报告单号(可空,与入库单关联)" clearable style="width:420px" />
</el-form-item>
<el-form-item label="报检单号">
<el-input v-model="reportNo" placeholder="生产报检单号(可空,与报检流程关联)" clearable style="width:420px" />
</el-form-item>
<el-divider content-position="left">检验量化(可空)</el-divider>
<el-form-item label="检验数量">
@@ -238,10 +264,6 @@ onMounted(() => {
<el-form-item label="检测说明">
<el-input v-model="checkDesc" placeholder="检验项目/方法/标准(可空)" clearable style="width:420px" />
</el-form-item>
<el-form-item v-if="status === '不合格'" label="不合格原因">
<el-input v-model="failReason" type="textarea" :rows="2" placeholder="不合格原因(可空)" style="width:420px" />
</el-form-item>
<el-divider content-position="left">待检编号(可扫/选多条,统一提交)</el-divider>
<!-- 从库存直接拾取,避免手输批次号(远程模糊搜索,最近 100 条) -->
@@ -303,7 +325,7 @@ onMounted(() => {
<el-input v-model="filters.materialCode" placeholder="物料编码" clearable style="width:150px"
@keyup.enter="search" />
</el-form-item>
<el-form-item label="检验类型">
<el-form-item v-if="!pageType" label="检验类型">
<el-select v-model="filters.inspectionType" placeholder="全部" clearable style="width:120px" @change="search">
<el-option label="来料检" value="incoming" />
<el-option label="过程检" value="process" />
@@ -366,17 +388,15 @@ onMounted(() => {
<span v-else>-</span>
</template>
</el-table-column>
<el-table-column label="不合格原因" min-width="140">
<template #default="{ row }">
<el-tooltip v-if="row.failReason" :content="row.failReason" placement="top">
<span style="color:#f56c6c">{{ row.failReason }}</span>
</el-tooltip>
<span v-else>-</span>
</template>
<el-table-column prop="reportNo" label="报检单号" min-width="130" align="center">
<template #default="{ row }">{{ row.reportNo || '-' }}</template>
</el-table-column>
<el-table-column prop="inspector" label="检验员" width="90" align="center">
<template #default="{ row }">{{ row.inspector || '-' }}</template>
</el-table-column>
<template #default="{ row }">{{ row.inspector || '-' }}</template>
</el-table-column>
<el-table-column prop="inspectionNo" label="检测单号" min-width="130" align="center">
<template #default="{ row }">{{ row.inspectionNo || '-' }}</template>
</el-table-column>
<el-table-column label="检验时间" width="155" align="center">
<template #default="{ row }">{{ fmtTime(row.createdAt) }}</template>
</el-table-column>
@@ -390,6 +410,27 @@ onMounted(() => {
@size-change="(s) => { page.size = s; page.current = 1; loadRecords() }" />
</div>
</el-tab-pane>
<!-- ============ 按物料统计(点击才加载) ============ -->
<el-tab-pane label="按物料统计" name="stats">
<el-card shadow="never" header="检验次数 / 合格率(按物料)">
<div style="margin-bottom:10px">
<el-button type="primary" :loading="statsLoading" @click="loadStats">统计</el-button>
<span style="margin-left:8px;color:#909399;font-size:12px">按当前时间区间与检验类别统计;每次检验独立计数,不合并记录</span>
</div>
<el-table :data="statsList" border size="small" v-loading="statsLoading" empty-text="点「统计」查看结果">
<el-table-column prop="materialCode" label="物料编码" min-width="160" />
<el-table-column prop="total" label="检验次数" width="110" align="center" />
<el-table-column prop="pass" label="合格次数" width="110" align="center" />
<el-table-column prop="fail" label="不合格次数" width="120" align="center" />
<el-table-column label="合格率" width="120" align="center">
<template #default="{ row }">
<span :style="row.passRate >= 100 ? 'color:#67c23a' : 'color:#e6a23c'">{{ row.passRate }}%</span>
</template>
</el-table-column>
</el-table>
</el-card>
</el-tab-pane>
</el-tabs>
</div>
<PageHelp :data="helpInspection" page="Inspection" />