1. 全局替换"工序"为"工艺"统一术语,包括页面文案、枚举、注释
2. 重构工单与工件工艺数据模型:
- 新增工艺组合表flow_material作为备料/齐套唯一源头
- 新增工位状态表station_state支持自主停单/恢复接单
- 移除station_process派工表,改用工单工艺组合作为路线唯一源头
- 替换processCode为flowId作为工艺关联标识
- 重构工件当前进度字段为currentStationNo
3. 删除冗余的静态备份资源文件
4. 调整物料选择组件默认启用仅显示激活物料
5. 优化工单创建/编辑逻辑,新增工艺组合校验与保存
272 lines
7.1 KiB
Vue
272 lines
7.1 KiB
Vue
<template>
|
||
<div class="page">
|
||
<!-- 顶部:工位 / 工单 / 工件 / 同步状态 / 用户 -->
|
||
<header class="top-bar">
|
||
<div class="brand">工位终端</div>
|
||
<div class="chip">
|
||
<span class="lab">工位</span>
|
||
<b>{{ stationNo ? '工位 ' + stationNo + (stationName ? '·' + stationName : '') : '…' }}</b>
|
||
</div>
|
||
<div class="chip">
|
||
<span class="lab">工单</span>
|
||
<b>{{ orderNo || '未选择' }}</b>
|
||
</div>
|
||
<div class="chip">
|
||
<span class="lab">工件</span>
|
||
<b class="sn">{{ sn || '未上料' }}</b>
|
||
</div>
|
||
|
||
<div class="spacer"></div>
|
||
|
||
<el-button size="large" :type="pendingTotal ? 'warning' : 'default'" @click="manualFlush">
|
||
待同步 {{ pendingTotal }}
|
||
</el-button>
|
||
<el-button size="large" @click="wlRef?.open()">我的工作量</el-button>
|
||
<span class="user-name">{{ user.name || user.username || '-' }}</span>
|
||
<span class="clock">{{ clock }}</span>
|
||
<el-button size="large" type="danger" plain @click="logout">退出</el-button>
|
||
</header>
|
||
|
||
<!-- 停单横幅(醒目置顶:已停止接单 + 原因,恢复接单即关闭) -->
|
||
<transition name="fade">
|
||
<div v-if="paused" class="pause-banner">
|
||
⛔ 已停止接单{{ pauseReason ? ':' + pauseReason : '' }}(本工位暂停新的报工提交,作业信息不受影响)
|
||
</div>
|
||
</transition>
|
||
|
||
<!-- 不合格告警横幅 -->
|
||
<transition name="fade">
|
||
<div v-if="hasNg" class="alert-banner">⚠ 存在不合格,请复检!</div>
|
||
</transition>
|
||
|
||
<el-tabs v-model="tab" type="border-card" class="main-tabs">
|
||
<el-tab-pane v-if="isOnline" label="上线建档" name="online">
|
||
<OnlinePanel />
|
||
</el-tab-pane>
|
||
<el-tab-pane v-if="isOffline" label="下线完工" name="offline">
|
||
<OfflinePanel />
|
||
</el-tab-pane>
|
||
<el-tab-pane v-if="!isVirtual" label="作业" name="work">
|
||
<WorkPanel ref="workRef" @call-material="tab = 'material'" />
|
||
</el-tab-pane>
|
||
<el-tab-pane label="物料 / 叫料" name="material">
|
||
<MaterialPanel />
|
||
</el-tab-pane>
|
||
<el-tab-pane v-if="!isVirtual" label="移料" name="move">
|
||
<MovePanel />
|
||
</el-tab-pane>
|
||
<el-tab-pane v-if="!isVirtual" label="工艺文件" name="pdf">
|
||
<PdfPanel />
|
||
</el-tab-pane>
|
||
<el-tab-pane label="巡检 / 异常" name="inspect">
|
||
<InspectPanel />
|
||
</el-tab-pane>
|
||
</el-tabs>
|
||
|
||
<WorkloadDialog ref="wlRef" />
|
||
|
||
<!-- 全局帮助:右下角悬浮「?」,内容随当前页签切换 -->
|
||
<PageHelp :data="helpData" />
|
||
</div>
|
||
</template>
|
||
|
||
<script setup>
|
||
import { computed, onBeforeUnmount, onMounted, ref } from 'vue'
|
||
import { useRouter } from 'vue-router'
|
||
import { ElMessage } from 'element-plus'
|
||
import WorkPanel from './panels/WorkPanel.vue'
|
||
import MaterialPanel from './panels/MaterialPanel.vue'
|
||
import MovePanel from './panels/MovePanel.vue'
|
||
import PdfPanel from './panels/PdfPanel.vue'
|
||
import InspectPanel from './panels/InspectPanel.vue'
|
||
import OnlinePanel from './panels/OnlinePanel.vue'
|
||
import OfflinePanel from './panels/OfflinePanel.vue'
|
||
import WorkloadDialog from './panels/WorkloadDialog.vue'
|
||
import PageHelp from '../components/PageHelp.vue'
|
||
import {
|
||
helpInspect,
|
||
helpMaterial,
|
||
helpMove,
|
||
helpOffline,
|
||
helpOnline,
|
||
helpPdf,
|
||
helpWorkstation
|
||
} from '../help'
|
||
import { stationLogout, syncFlush } from '../api'
|
||
import { clearAuth } from '../utils/request'
|
||
import {
|
||
hasNg,
|
||
loadConfig,
|
||
loadMoves,
|
||
loadPoints,
|
||
loadStats,
|
||
loadStationState,
|
||
loadTight,
|
||
loadTask,
|
||
loadWip,
|
||
orderNo,
|
||
paused,
|
||
pauseReason,
|
||
pendingTotal,
|
||
refreshAll,
|
||
sn,
|
||
state,
|
||
stationNo,
|
||
user
|
||
} from '../store/station'
|
||
|
||
const router = useRouter()
|
||
const tab = ref('work')
|
||
const workRef = ref()
|
||
const wlRef = ref()
|
||
const clock = ref('')
|
||
const stationName = computed(() => state.task?.stationName || '')
|
||
// 0=上线位、13=下线位为虚拟工位,分别展示上线建档 / 下线完工面板
|
||
const isOnline = computed(() => Number(stationNo.value) === 0)
|
||
const isOffline = computed(() => Number(stationNo.value) === 13)
|
||
const isVirtual = computed(() => isOnline.value || isOffline.value)
|
||
|
||
// 帮助内容随当前页签切换(全局唯一一个 PageHelp 悬浮按钮)
|
||
const HELP_MAP = {
|
||
online: helpOnline,
|
||
offline: helpOffline,
|
||
work: helpWorkstation,
|
||
material: helpMaterial,
|
||
move: helpMove,
|
||
pdf: helpPdf,
|
||
inspect: helpInspect
|
||
}
|
||
const helpData = computed(() => HELP_MAP[tab.value] || helpWorkstation)
|
||
|
||
let timerTask = null
|
||
let timerTight = null
|
||
let timerStats = null
|
||
let timerClock = null
|
||
|
||
function tickClock() {
|
||
const d = new Date()
|
||
const p = (n) => String(n).padStart(2, '0')
|
||
clock.value = `${d.getFullYear()}-${p(d.getMonth() + 1)}-${p(d.getDate())} ${p(d.getHours())}:${p(d.getMinutes())}:${p(d.getSeconds())}`
|
||
}
|
||
|
||
async function manualFlush() {
|
||
try {
|
||
const data = await syncFlush()
|
||
ElMessage.success(`本轮同步成功 ${data?.count ?? 0} 条`)
|
||
loadStats()
|
||
} catch (e) { /* 统一错误提示 */ }
|
||
}
|
||
|
||
function logout() {
|
||
stationLogout({ username: user?.username || '', stationNo: stationNo.value || 0 }).catch(() => {})
|
||
clearAuth()
|
||
router.push('/login')
|
||
}
|
||
|
||
onMounted(async () => {
|
||
tickClock()
|
||
await loadConfig()
|
||
// 虚拟工位默认落到对应面板;物理工位默认作业页
|
||
if (isOnline.value) tab.value = 'online'
|
||
else if (isOffline.value) tab.value = 'offline'
|
||
else tab.value = 'work'
|
||
refreshAll()
|
||
loadMoves()
|
||
workRef.value?.focusScan?.()
|
||
|
||
timerTask = setInterval(loadTask, 5000) // 任务 5 秒
|
||
timerTight = setInterval(loadTight, 2000) // 拧紧结果 2 秒
|
||
timerStats = setInterval(loadStats, 10000) // 同步统计 10 秒
|
||
timerClock = setInterval(() => {
|
||
tickClock()
|
||
loadPoints() // 点位台账 10 秒
|
||
loadMoves()
|
||
loadWip() // 在制品 10 秒
|
||
loadStationState() // 停单状态 10 秒
|
||
}, 10000)
|
||
})
|
||
|
||
onBeforeUnmount(() => {
|
||
clearInterval(timerTask)
|
||
clearInterval(timerTight)
|
||
clearInterval(timerStats)
|
||
clearInterval(timerClock)
|
||
})
|
||
</script>
|
||
|
||
<style scoped>
|
||
.page {
|
||
min-height: 100vh;
|
||
background: #f0f2f5;
|
||
}
|
||
.top-bar {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 14px;
|
||
padding: 10px 16px;
|
||
background: #fff;
|
||
box-shadow: 0 1px 6px rgba(0, 21, 41, 0.08);
|
||
flex-wrap: wrap;
|
||
}
|
||
.brand {
|
||
font-size: 20px;
|
||
font-weight: 700;
|
||
color: #1f6feb;
|
||
}
|
||
.chip {
|
||
display: flex;
|
||
align-items: baseline;
|
||
gap: 6px;
|
||
font-size: 15px;
|
||
}
|
||
.chip .lab {
|
||
color: #909399;
|
||
}
|
||
.chip b.sn {
|
||
color: #1f6feb;
|
||
}
|
||
.spacer {
|
||
flex: 1;
|
||
}
|
||
.user-name {
|
||
font-size: 15px;
|
||
}
|
||
.clock {
|
||
font-size: 15px;
|
||
color: #606266;
|
||
font-variant-numeric: tabular-nums;
|
||
}
|
||
.alert-banner {
|
||
background: #fef0f0;
|
||
color: #f56c6c;
|
||
text-align: center;
|
||
padding: 10px;
|
||
font-size: 18px;
|
||
font-weight: 700;
|
||
}
|
||
.pause-banner {
|
||
background: #f56c6c;
|
||
color: #fff;
|
||
text-align: center;
|
||
padding: 12px;
|
||
font-size: 20px;
|
||
font-weight: 700;
|
||
letter-spacing: 1px;
|
||
}
|
||
.main-tabs {
|
||
margin: 12px;
|
||
min-height: calc(100vh - 140px);
|
||
}
|
||
.main-tabs :deep(.el-tabs__content) {
|
||
padding: 12px;
|
||
}
|
||
.fade-enter-active,
|
||
.fade-leave-active {
|
||
transition: opacity 0.3s;
|
||
}
|
||
.fade-enter-from,
|
||
.fade-leave-to {
|
||
opacity: 0;
|
||
}
|
||
</style>
|