feat&refactor: 完成多模块功能迭代与配置优化
本次提交覆盖多个业务模块的功能完善与体验优化:
1. **鉴权与配置调整**:
- 统一JWT滑动续签逻辑,简化Token存储,移除RefreshToken相关冗余代码
- 调整多项目配置文件中JWT过期时间为3600秒,统一会话闲置窗口
- 工位配置放开1~12限制,改为仅校验大于0
2. **术语统一替换**:全链路将"精密件"替换为"电气件",修正物料管理描述
3. **功能新增**:
- 新增工位类型、工艺路线与产线点位台账模块
- 添加工艺PDF预览面板、工位终端代理转发接口
- 新增操作日志按操作人列表筛选、工位登出日志记录
- 新增PLC移料指令与产线点位状态管理
4. **业务流程优化**:
- 调整BOM物料删除校验逻辑,优化工单备料计算
- 补充物料图号、检测单号等追溯字段
- 完善工艺流程图与工位绑定关系说明
- 优化前端页面文案与交互细节
5. **代码规范与维护**:
- 新增通用工具函数与前端静态资源
- 整理路由权限与中间件逻辑
- 修复部分接口与配置的不兼容问题
This commit is contained in:
@@ -0,0 +1,277 @@
|
||||
// 工位终端全局状态(模块级单例)
|
||||
// 终端一个工位一个进程:工位号由服务端配置固定,前端不可修改。
|
||||
// 状态集中在这里,作业/物料/移料/工艺各面板共享,避免多份轮询与状态不一致。
|
||||
import { computed, reactive } from 'vue'
|
||||
import {
|
||||
getBindPanel,
|
||||
getConfig,
|
||||
getLineMoves,
|
||||
getLinePoints,
|
||||
getSyncStats,
|
||||
getTaskCurrent,
|
||||
getTighteningList,
|
||||
issueLineMove,
|
||||
occupyLinePoint
|
||||
} from '../api'
|
||||
import { getUser } from '../utils/request'
|
||||
|
||||
export const state = reactive({
|
||||
stationNo: 0,
|
||||
screwCount: 0,
|
||||
orderNo: '',
|
||||
currentSn: '',
|
||||
task: null,
|
||||
points: [],
|
||||
moves: [],
|
||||
tightRows: [],
|
||||
bindPanel: null,
|
||||
stats: { pendingTorque: 0, pendingQueue: 0 },
|
||||
stepValues: {},
|
||||
stepChecks: {},
|
||||
bindInputs: {},
|
||||
lastError: ''
|
||||
})
|
||||
|
||||
export const user = getUser()
|
||||
|
||||
// 常用字段的可写别名(面板里 v-model 直接绑定)
|
||||
export const sn = computed({
|
||||
get: () => state.currentSn,
|
||||
set: (v) => { state.currentSn = v || '' }
|
||||
})
|
||||
export const orderNo = computed({
|
||||
get: () => state.orderNo,
|
||||
set: (v) => { state.orderNo = v || '' }
|
||||
})
|
||||
export const stationNo = computed(() => state.stationNo)
|
||||
export const screwCount = computed(() => state.screwCount)
|
||||
export const stepValues = computed(() => state.stepValues)
|
||||
export const stepChecks = computed(() => state.stepChecks)
|
||||
export const bindInputs = computed(() => state.bindInputs)
|
||||
export const tightRows = computed(() => state.tightRows)
|
||||
|
||||
// ---------- 派生数据 ----------
|
||||
export const taskSteps = computed(() => (Array.isArray(state.task?.steps) ? state.task.steps : []))
|
||||
export const manualSteps = computed(() => taskSteps.value.filter((s) => s.collectType === 'MANUAL'))
|
||||
export const autoSteps = computed(() => taskSteps.value.filter((s) => s.collectType === 'AUTO'))
|
||||
export const orderNos = computed(() => (Array.isArray(state.task?.orderNos) ? state.task.orderNos : []))
|
||||
export const flowName = computed(() => state.task?.flow?.name || '-')
|
||||
export const flowActive = computed(() => !!state.task?.flowActive)
|
||||
|
||||
// 本工位所属路线段(工单路线快照,段=一工艺流程图 + 一组并行工位)
|
||||
export const currentSegment = computed(() => state.task?.segment || null)
|
||||
export const segmentText = computed(() => {
|
||||
const s = currentSegment.value
|
||||
if (!s || !s.seq) return '未排路线段'
|
||||
const st = Array.isArray(s.stations) ? s.stations.join(' / ') : '-'
|
||||
return `第 ${s.seq} 段 · 并行工位 ${st}`
|
||||
})
|
||||
|
||||
// 本工位的产线点位台账(唯一事实源:本工位当前是否有工件)
|
||||
export const myPoint = computed(() =>
|
||||
state.points.find((p) => p.pointType === 'STATION' && String(p.pointNo) === String(state.stationNo))
|
||||
)
|
||||
export const myPointSn = computed(() => (myPoint.value?.occupied ? myPoint.value.currentSn : ''))
|
||||
// 本工位接驳台(AGV 点位,工位 1~10 各 R1/R2)
|
||||
export const myDocks = computed(() =>
|
||||
state.points.filter((p) => p.pointType === 'DOCK' && Number(p.stationNo) === Number(state.stationNo))
|
||||
)
|
||||
// 传送带主线点位(上料位/工位/缓存位/末端),按 seq 排序
|
||||
export const beltPoints = computed(() =>
|
||||
[...state.points].filter((p) => p.pointType !== 'DOCK').sort((a, b) => (a.seq || 0) - (b.seq || 0))
|
||||
)
|
||||
export const pointByNo = (no) => state.points.find((p) => p.pointNo === no)
|
||||
|
||||
export const autoTorqueOk = computed(() => state.tightRows.filter((r) => r.result === 'OK').length)
|
||||
export const autoTorqueState = computed(() => {
|
||||
const total = state.tightRows.length
|
||||
if (total === 0) return { ok: false, text: '待拧紧' }
|
||||
return autoTorqueOk.value >= total ? { ok: true, text: '合格' } : { ok: false, text: '不合格' }
|
||||
})
|
||||
export const manualNg = computed(() =>
|
||||
manualSteps.value.some((s) => {
|
||||
const v = state.stepValues[s.id]
|
||||
if (v === null || v === undefined || v === '') return false
|
||||
return Array.isArray(s.criteria) && s.criteria.length ? !s.criteria.every((c) => criterionOk(c, v)) : false
|
||||
})
|
||||
)
|
||||
export const autoNg = computed(() => state.tightRows.some((r) => r.result === 'NG'))
|
||||
export const hasNg = computed(() => manualNg.value || autoNg.value)
|
||||
export const pendingTotal = computed(() => (state.stats.pendingTorque || 0) + (state.stats.pendingQueue || 0))
|
||||
|
||||
// 未勾选"检测确认"的步骤(needCheck=true 必须人工确认后才允许报工)
|
||||
export const missingChecks = computed(() =>
|
||||
manualSteps.value.filter((s) => s.needCheck && !state.stepChecks[s.id])
|
||||
)
|
||||
// 未录入的步骤
|
||||
export const missingSteps = computed(() =>
|
||||
manualSteps.value.filter((s) => {
|
||||
const v = state.stepValues[s.id]
|
||||
return v === null || v === undefined || v === ''
|
||||
})
|
||||
)
|
||||
// 绑定是否齐套(本工序未配置需绑物料时不拦截)
|
||||
export const bindBlocking = computed(
|
||||
() => !!(state.bindPanel && state.bindPanel.enabled && !state.bindPanel.complete)
|
||||
)
|
||||
export const canReport = computed(
|
||||
() =>
|
||||
!!state.currentSn &&
|
||||
!!state.orderNo &&
|
||||
!bindBlocking.value &&
|
||||
missingSteps.value.length === 0 &&
|
||||
missingChecks.value.length === 0 &&
|
||||
(!autoSteps.value.some((s) => s.isTorque) || autoTorqueState.value.ok)
|
||||
)
|
||||
|
||||
// ---------- 判定工具 ----------
|
||||
export function criterionOk(c, value) {
|
||||
if (value === null || value === undefined || value === '') return false
|
||||
const v = Number(value)
|
||||
if (isNaN(v)) return false
|
||||
switch (c?.logic) {
|
||||
case 'GE': return v >= Number(c.target)
|
||||
case 'LE': return v <= Number(c.target)
|
||||
case 'GT': return v > Number(c.target)
|
||||
case 'LT': return v < Number(c.target)
|
||||
case 'RANGE': {
|
||||
const min = Number(c.min)
|
||||
const max = Number(c.max)
|
||||
if (!isNaN(min) && v < min) return false
|
||||
if (!isNaN(max) && v > max) return false
|
||||
return true
|
||||
}
|
||||
case 'EQUAL': return v === Number(c.target)
|
||||
default: return true
|
||||
}
|
||||
}
|
||||
|
||||
export function stepDisplay(s) {
|
||||
const v = state.stepValues[s.id]
|
||||
const hasValue = v !== null && v !== undefined && v !== ''
|
||||
if (!hasValue) return { text: '待录入', type: 'info' }
|
||||
if (s.criteria && s.criteria.length) {
|
||||
const ok = s.criteria.every((c) => criterionOk(c, v))
|
||||
return ok ? { text: '合格', type: 'success' } : { text: '不合格', type: 'danger' }
|
||||
}
|
||||
return { text: 'OK', type: 'success' }
|
||||
}
|
||||
|
||||
// ---------- 数据加载 ----------
|
||||
export async function loadConfig() {
|
||||
try {
|
||||
const cfg = await getConfig()
|
||||
if (cfg?.stationNo > 0) state.stationNo = cfg.stationNo
|
||||
if (cfg?.screwCount > 0) state.screwCount = cfg.screwCount
|
||||
} catch { /* 静默 */ }
|
||||
}
|
||||
|
||||
export async function loadTask() {
|
||||
if (!state.stationNo) return
|
||||
try {
|
||||
state.task = await getTaskCurrent(state.stationNo)
|
||||
if (orderNos.value.length && !orderNos.value.includes(state.orderNo)) {
|
||||
state.orderNo = orderNos.value[0]
|
||||
}
|
||||
} catch {
|
||||
state.task = null
|
||||
}
|
||||
}
|
||||
|
||||
export async function loadPoints() {
|
||||
try {
|
||||
state.points = (await getLinePoints()) || []
|
||||
} catch {
|
||||
state.points = []
|
||||
}
|
||||
}
|
||||
|
||||
export async function loadMoves() {
|
||||
try {
|
||||
state.moves = (await getLineMoves({ limit: 20 })) || []
|
||||
} catch {
|
||||
state.moves = []
|
||||
}
|
||||
}
|
||||
|
||||
export async function loadTight() {
|
||||
try {
|
||||
state.tightRows = (await getTighteningList({ limit: 50 })) || []
|
||||
} catch {
|
||||
state.tightRows = []
|
||||
}
|
||||
}
|
||||
|
||||
export async function loadBind() {
|
||||
const code = Number(state.stationNo)
|
||||
if (!state.currentSn || !code) {
|
||||
state.bindPanel = null
|
||||
return
|
||||
}
|
||||
try {
|
||||
const p = await getBindPanel({ sn: state.currentSn, processCode: code })
|
||||
state.bindPanel = p
|
||||
;(p?.required || []).forEach((r) => {
|
||||
if (state.bindInputs[r.materialCode] === undefined) state.bindInputs[r.materialCode] = ''
|
||||
})
|
||||
} catch {
|
||||
state.bindPanel = null
|
||||
}
|
||||
}
|
||||
|
||||
export async function loadStats() {
|
||||
try {
|
||||
state.stats = await getSyncStats()
|
||||
} catch { /* 静默 */ }
|
||||
}
|
||||
|
||||
export function refreshAll() {
|
||||
return Promise.all([loadTask(), loadTight(), loadStats(), loadPoints()])
|
||||
}
|
||||
|
||||
// 切换工件:清空步骤录入与绑定缓存,重新加载拧紧/绑定
|
||||
export function setCurrentSn(sn) {
|
||||
state.currentSn = sn || ''
|
||||
state.stepValues = {}
|
||||
state.stepChecks = {}
|
||||
state.bindInputs = {}
|
||||
state.bindPanel = null
|
||||
loadTight()
|
||||
loadBind()
|
||||
}
|
||||
|
||||
// ---------- 产线动作(把工件弄到本工位 / 移出本工位) ----------
|
||||
// bringSnToStation:扫描工件 SN 后自动判断来源——
|
||||
// 1) SN 已在产线某点位 → 下发移料指令(起点该点位 → 终点本工位),返回 {mode:'MOVE', cmdNo}
|
||||
// 2) SN 不在任何点位(首件/人工上料)→ 直接记账到本工位,返回 {mode:'IN'}
|
||||
// 3) SN 已在本工位 → 返回 {mode:'HERE'}
|
||||
export async function bringSnToStation(sn) {
|
||||
const target = String(state.stationNo)
|
||||
const src = state.points.find((p) => p.currentSn === sn && p.occupied)
|
||||
if (!src) {
|
||||
await occupyLinePoint({ pointNo: target, sn, orderNo: state.orderNo || '', action: 'IN' })
|
||||
return { mode: 'IN' }
|
||||
}
|
||||
if (String(src.pointNo) === target) return { mode: 'HERE' }
|
||||
const r = await issueLineMove({ sn, fromPoint: src.pointNo, toPoint: target, orderNo: state.orderNo || '' })
|
||||
return { mode: 'MOVE', cmdNo: r?.cmdNo, fromPoint: src.pointNo }
|
||||
}
|
||||
|
||||
// 工件移出本工位:默认目标为下游第一个空闲点位(缓存位优先)
|
||||
export function suggestOutPoint() {
|
||||
const mySeq = myPoint.value?.seq || 0
|
||||
const down = beltPoints.value.filter((p) => (p.seq || 0) > mySeq)
|
||||
return (down.find((p) => !p.occupied) || down[0] || null)
|
||||
}
|
||||
|
||||
export async function moveOut(sn, toPoint) {
|
||||
const r = await issueLineMove({
|
||||
sn,
|
||||
fromPoint: String(state.stationNo),
|
||||
toPoint,
|
||||
orderNo: state.orderNo || ''
|
||||
})
|
||||
return r?.cmdNo
|
||||
}
|
||||
|
||||
export const operatorName = () => user.name || user.username || ''
|
||||
Reference in New Issue
Block a user