2026-08-28 15:06:01 +08:00
|
|
|
|
<template>
|
2026-09-10 16:59:15 +08:00
|
|
|
|
<div class="page">
|
|
|
|
|
|
<!-- 顶部:工位 / 工单 / 工件 / 同步状态 / 用户 -->
|
2026-08-28 15:06:01 +08:00
|
|
|
|
<header class="top-bar">
|
2026-08-31 08:06:55 +08:00
|
|
|
|
<div class="brand">工位终端</div>
|
2026-09-10 16:59:15 +08:00
|
|
|
|
<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>
|
2026-08-28 15:06:01 +08:00
|
|
|
|
|
|
|
|
|
|
<div class="spacer"></div>
|
|
|
|
|
|
|
2026-09-10 16:59:15 +08:00
|
|
|
|
<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>
|
2026-08-28 15:06:01 +08:00
|
|
|
|
</header>
|
|
|
|
|
|
|
2026-09-22 11:32:29 +08:00
|
|
|
|
<!-- 停单横幅(醒目置顶:已停止接单 + 原因,恢复接单即关闭) -->
|
|
|
|
|
|
<transition name="fade">
|
|
|
|
|
|
<div v-if="paused" class="pause-banner">
|
|
|
|
|
|
⛔ 已停止接单{{ pauseReason ? ':' + pauseReason : '' }}(本工位暂停新的报工提交,作业信息不受影响)
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</transition>
|
|
|
|
|
|
|
2026-09-10 16:59:15 +08:00
|
|
|
|
<!-- 不合格告警横幅 -->
|
2026-08-28 15:06:01 +08:00
|
|
|
|
<transition name="fade">
|
2026-09-10 16:59:15 +08:00
|
|
|
|
<div v-if="hasNg" class="alert-banner">⚠ 存在不合格,请复检!</div>
|
2026-08-28 15:06:01 +08:00
|
|
|
|
</transition>
|
|
|
|
|
|
|
2026-09-10 16:59:15 +08:00
|
|
|
|
<el-tabs v-model="tab" type="border-card" class="main-tabs">
|
2026-09-17 12:49:07 +08:00
|
|
|
|
<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">
|
2026-09-22 11:32:29 +08:00
|
|
|
|
<WorkPanel ref="workRef" @call-material="tab = 'material'" />
|
2026-09-10 16:59:15 +08:00
|
|
|
|
</el-tab-pane>
|
|
|
|
|
|
<el-tab-pane label="物料 / 叫料" name="material">
|
|
|
|
|
|
<MaterialPanel />
|
|
|
|
|
|
</el-tab-pane>
|
2026-09-17 12:49:07 +08:00
|
|
|
|
<el-tab-pane v-if="!isVirtual" label="移料" name="move">
|
2026-09-10 16:59:15 +08:00
|
|
|
|
<MovePanel />
|
|
|
|
|
|
</el-tab-pane>
|
2026-09-17 12:49:07 +08:00
|
|
|
|
<el-tab-pane v-if="!isVirtual" label="工艺文件" name="pdf">
|
2026-09-10 16:59:15 +08:00
|
|
|
|
<PdfPanel />
|
|
|
|
|
|
</el-tab-pane>
|
2026-09-15 16:37:39 +08:00
|
|
|
|
<el-tab-pane label="巡检 / 异常" name="inspect">
|
|
|
|
|
|
<InspectPanel />
|
|
|
|
|
|
</el-tab-pane>
|
2026-09-10 16:59:15 +08:00
|
|
|
|
</el-tabs>
|
|
|
|
|
|
|
|
|
|
|
|
<WorkloadDialog ref="wlRef" />
|
2026-09-17 12:49:07 +08:00
|
|
|
|
|
|
|
|
|
|
<!-- 全局帮助:右下角悬浮「?」,内容随当前页签切换 -->
|
|
|
|
|
|
<PageHelp :data="helpData" />
|
2026-08-28 15:06:01 +08:00
|
|
|
|
</div>
|
|
|
|
|
|
</template>
|
|
|
|
|
|
|
|
|
|
|
|
<script setup>
|
2026-09-10 16:59:15 +08:00
|
|
|
|
import { computed, onBeforeUnmount, onMounted, ref } from 'vue'
|
2026-08-28 15:06:01 +08:00
|
|
|
|
import { useRouter } from 'vue-router'
|
2026-09-10 16:59:15 +08:00
|
|
|
|
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'
|
2026-09-15 16:37:39 +08:00
|
|
|
|
import InspectPanel from './panels/InspectPanel.vue'
|
2026-09-17 12:49:07 +08:00
|
|
|
|
import OnlinePanel from './panels/OnlinePanel.vue'
|
|
|
|
|
|
import OfflinePanel from './panels/OfflinePanel.vue'
|
2026-09-10 16:59:15 +08:00
|
|
|
|
import WorkloadDialog from './panels/WorkloadDialog.vue'
|
2026-09-17 12:49:07 +08:00
|
|
|
|
import PageHelp from '../components/PageHelp.vue'
|
|
|
|
|
|
import {
|
|
|
|
|
|
helpInspect,
|
|
|
|
|
|
helpMaterial,
|
|
|
|
|
|
helpMove,
|
|
|
|
|
|
helpOffline,
|
|
|
|
|
|
helpOnline,
|
|
|
|
|
|
helpPdf,
|
|
|
|
|
|
helpWorkstation
|
|
|
|
|
|
} from '../help'
|
2026-09-10 16:59:15 +08:00
|
|
|
|
import { stationLogout, syncFlush } from '../api'
|
|
|
|
|
|
import { clearAuth } from '../utils/request'
|
2026-08-28 15:06:01 +08:00
|
|
|
|
import {
|
2026-09-10 16:59:15 +08:00
|
|
|
|
hasNg,
|
|
|
|
|
|
loadConfig,
|
|
|
|
|
|
loadMoves,
|
|
|
|
|
|
loadPoints,
|
|
|
|
|
|
loadStats,
|
2026-09-22 11:32:29 +08:00
|
|
|
|
loadStationState,
|
2026-09-10 16:59:15 +08:00
|
|
|
|
loadTight,
|
|
|
|
|
|
loadTask,
|
2026-09-17 12:49:07 +08:00
|
|
|
|
loadWip,
|
2026-09-10 16:59:15 +08:00
|
|
|
|
orderNo,
|
2026-09-22 11:32:29 +08:00
|
|
|
|
paused,
|
|
|
|
|
|
pauseReason,
|
2026-09-10 16:59:15 +08:00
|
|
|
|
pendingTotal,
|
|
|
|
|
|
refreshAll,
|
|
|
|
|
|
sn,
|
|
|
|
|
|
state,
|
|
|
|
|
|
stationNo,
|
|
|
|
|
|
user
|
|
|
|
|
|
} from '../store/station'
|
2026-08-28 15:06:01 +08:00
|
|
|
|
|
|
|
|
|
|
const router = useRouter()
|
2026-09-10 16:59:15 +08:00
|
|
|
|
const tab = ref('work')
|
|
|
|
|
|
const workRef = ref()
|
|
|
|
|
|
const wlRef = ref()
|
|
|
|
|
|
const clock = ref('')
|
|
|
|
|
|
const stationName = computed(() => state.task?.stationName || '')
|
2026-09-17 12:49:07 +08:00
|
|
|
|
// 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)
|
2026-08-31 08:06:55 +08:00
|
|
|
|
|
2026-09-10 16:59:15 +08:00
|
|
|
|
let timerTask = null
|
|
|
|
|
|
let timerTight = null
|
|
|
|
|
|
let timerStats = null
|
|
|
|
|
|
let timerClock = null
|
2026-08-28 15:06:01 +08:00
|
|
|
|
|
2026-09-10 16:59:15 +08:00
|
|
|
|
function tickClock() {
|
|
|
|
|
|
const d = new Date()
|
2026-08-28 15:06:01 +08:00
|
|
|
|
const p = (n) => String(n).padStart(2, '0')
|
2026-09-10 16:59:15 +08:00
|
|
|
|
clock.value = `${d.getFullYear()}-${p(d.getMonth() + 1)}-${p(d.getDate())} ${p(d.getHours())}:${p(d.getMinutes())}:${p(d.getSeconds())}`
|
2026-08-28 15:06:01 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
async function manualFlush() {
|
|
|
|
|
|
try {
|
|
|
|
|
|
const data = await syncFlush()
|
|
|
|
|
|
ElMessage.success(`本轮同步成功 ${data?.count ?? 0} 条`)
|
|
|
|
|
|
loadStats()
|
2026-09-10 16:59:15 +08:00
|
|
|
|
} catch (e) { /* 统一错误提示 */ }
|
2026-08-28 15:06:01 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
function logout() {
|
2026-09-10 16:59:15 +08:00
|
|
|
|
stationLogout({ username: user?.username || '', stationNo: stationNo.value || 0 }).catch(() => {})
|
2026-08-28 15:06:01 +08:00
|
|
|
|
clearAuth()
|
|
|
|
|
|
router.push('/login')
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
onMounted(async () => {
|
2026-09-10 16:59:15 +08:00
|
|
|
|
tickClock()
|
2026-08-28 15:06:01 +08:00
|
|
|
|
await loadConfig()
|
2026-09-17 12:49:07 +08:00
|
|
|
|
// 虚拟工位默认落到对应面板;物理工位默认作业页
|
|
|
|
|
|
if (isOnline.value) tab.value = 'online'
|
|
|
|
|
|
else if (isOffline.value) tab.value = 'offline'
|
|
|
|
|
|
else tab.value = 'work'
|
2026-08-28 15:06:01 +08:00
|
|
|
|
refreshAll()
|
2026-09-10 16:59:15 +08:00
|
|
|
|
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()
|
2026-09-17 12:49:07 +08:00
|
|
|
|
loadWip() // 在制品 10 秒
|
2026-09-22 11:32:29 +08:00
|
|
|
|
loadStationState() // 停单状态 10 秒
|
2026-09-10 16:59:15 +08:00
|
|
|
|
}, 10000)
|
2026-08-28 15:06:01 +08:00
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
|
|
onBeforeUnmount(() => {
|
|
|
|
|
|
clearInterval(timerTask)
|
|
|
|
|
|
clearInterval(timerTight)
|
|
|
|
|
|
clearInterval(timerStats)
|
2026-09-10 16:59:15 +08:00
|
|
|
|
clearInterval(timerClock)
|
2026-08-28 15:06:01 +08:00
|
|
|
|
})
|
|
|
|
|
|
</script>
|
|
|
|
|
|
|
|
|
|
|
|
<style scoped>
|
|
|
|
|
|
.page {
|
2026-09-10 16:59:15 +08:00
|
|
|
|
min-height: 100vh;
|
|
|
|
|
|
background: #f0f2f5;
|
2026-08-28 15:06:01 +08:00
|
|
|
|
}
|
|
|
|
|
|
.top-bar {
|
|
|
|
|
|
display: flex;
|
|
|
|
|
|
align-items: center;
|
2026-09-10 16:59:15 +08:00
|
|
|
|
gap: 14px;
|
|
|
|
|
|
padding: 10px 16px;
|
|
|
|
|
|
background: #fff;
|
|
|
|
|
|
box-shadow: 0 1px 6px rgba(0, 21, 41, 0.08);
|
|
|
|
|
|
flex-wrap: wrap;
|
2026-08-28 15:06:01 +08:00
|
|
|
|
}
|
|
|
|
|
|
.brand {
|
2026-09-10 16:59:15 +08:00
|
|
|
|
font-size: 20px;
|
|
|
|
|
|
font-weight: 700;
|
|
|
|
|
|
color: #1f6feb;
|
2026-08-28 15:06:01 +08:00
|
|
|
|
}
|
2026-09-10 16:59:15 +08:00
|
|
|
|
.chip {
|
2026-08-28 15:06:01 +08:00
|
|
|
|
display: flex;
|
2026-09-10 16:59:15 +08:00
|
|
|
|
align-items: baseline;
|
|
|
|
|
|
gap: 6px;
|
|
|
|
|
|
font-size: 15px;
|
2026-08-28 15:06:01 +08:00
|
|
|
|
}
|
2026-09-10 16:59:15 +08:00
|
|
|
|
.chip .lab {
|
|
|
|
|
|
color: #909399;
|
2026-08-28 15:06:01 +08:00
|
|
|
|
}
|
2026-09-10 16:59:15 +08:00
|
|
|
|
.chip b.sn {
|
|
|
|
|
|
color: #1f6feb;
|
2026-08-28 15:06:01 +08:00
|
|
|
|
}
|
|
|
|
|
|
.spacer {
|
|
|
|
|
|
flex: 1;
|
|
|
|
|
|
}
|
|
|
|
|
|
.user-name {
|
2026-09-10 16:59:15 +08:00
|
|
|
|
font-size: 15px;
|
2026-08-28 15:06:01 +08:00
|
|
|
|
}
|
2026-09-10 16:59:15 +08:00
|
|
|
|
.clock {
|
|
|
|
|
|
font-size: 15px;
|
|
|
|
|
|
color: #606266;
|
|
|
|
|
|
font-variant-numeric: tabular-nums;
|
2026-08-28 15:06:01 +08:00
|
|
|
|
}
|
|
|
|
|
|
.alert-banner {
|
2026-09-10 16:59:15 +08:00
|
|
|
|
background: #fef0f0;
|
|
|
|
|
|
color: #f56c6c;
|
2026-08-28 15:06:01 +08:00
|
|
|
|
text-align: center;
|
2026-09-10 16:59:15 +08:00
|
|
|
|
padding: 10px;
|
2026-08-31 08:06:55 +08:00
|
|
|
|
font-size: 18px;
|
2026-09-10 16:59:15 +08:00
|
|
|
|
font-weight: 700;
|
2026-08-28 15:06:01 +08:00
|
|
|
|
}
|
2026-09-22 11:32:29 +08:00
|
|
|
|
.pause-banner {
|
|
|
|
|
|
background: #f56c6c;
|
|
|
|
|
|
color: #fff;
|
|
|
|
|
|
text-align: center;
|
|
|
|
|
|
padding: 12px;
|
|
|
|
|
|
font-size: 20px;
|
|
|
|
|
|
font-weight: 700;
|
|
|
|
|
|
letter-spacing: 1px;
|
|
|
|
|
|
}
|
2026-09-10 16:59:15 +08:00
|
|
|
|
.main-tabs {
|
|
|
|
|
|
margin: 12px;
|
|
|
|
|
|
min-height: calc(100vh - 140px);
|
2026-08-31 08:06:55 +08:00
|
|
|
|
}
|
2026-09-10 16:59:15 +08:00
|
|
|
|
.main-tabs :deep(.el-tabs__content) {
|
|
|
|
|
|
padding: 12px;
|
2026-08-31 08:06:55 +08:00
|
|
|
|
}
|
2026-09-10 16:59:15 +08:00
|
|
|
|
.fade-enter-active,
|
|
|
|
|
|
.fade-leave-active {
|
|
|
|
|
|
transition: opacity 0.3s;
|
2026-08-31 08:06:55 +08:00
|
|
|
|
}
|
2026-09-10 16:59:15 +08:00
|
|
|
|
.fade-enter-from,
|
|
|
|
|
|
.fade-leave-to {
|
|
|
|
|
|
opacity: 0;
|
2026-08-31 08:06:55 +08:00
|
|
|
|
}
|
|
|
|
|
|
</style>
|