<script lang="ts" setup>
|
import type { MesMdWorkstationApi } from '#/api/mes/md/workstation';
|
import type { PendingTask, WorkbenchTabItem, WorkbenchTabKey } from './types';
|
|
import { computed, ref, watch } from 'vue';
|
|
import { IconifyIcon } from '@vben/icons';
|
|
import { Button } from 'ant-design-vue';
|
|
import { getPendingFeedbackPage } from '#/api/mes/pro/task';
|
import { router } from '#/router';
|
|
import TaskListPanel from './components/TaskListPanel.vue';
|
import WorkstationPanel from './components/WorkstationPanel.vue';
|
import WorkstationSelectDialog from './components/WorkstationSelectDialog.vue';
|
import FeedbackHistoryTab from './components/tabs/FeedbackHistoryTab.vue';
|
import FeedbackTab from './components/tabs/FeedbackTab.vue';
|
import ProductIssueTab from './components/tabs/ProductIssueTab.vue';
|
import RecordParamTab from './components/tabs/RecordParamTab.vue';
|
import ReturnIssueTab from './components/tabs/ReturnIssueTab.vue';
|
import TaskDetailTab from './components/tabs/TaskDetailTab.vue';
|
|
defineOptions({ name: 'MesWorkbench' });
|
|
// --- Tab 配置 ---
|
const tabItems: WorkbenchTabItem[] = [
|
{ key: 'detail', label: '工单详情', icon: 'lucide:file-text' },
|
{ key: 'feedback', label: '报工', icon: 'lucide:edit-3' },
|
{ key: 'history', label: '报工记录', icon: 'lucide:history' },
|
{ key: 'record-param', label: '生产记录参数', icon: 'lucide:file-cog' },
|
{ key: 'product-issue', label: '领料', icon: 'lucide:clipboard-list' },
|
{ key: 'return-issue', label: '退料', icon: 'lucide:rotate-ccw' },
|
];
|
|
const activeTab = ref<WorkbenchTabKey>('detail');
|
const activeTabLabel = computed(
|
() => tabItems.find((t) => t.key === activeTab.value)?.label ?? '',
|
);
|
|
// --- 工作站 ---
|
const workstation = ref<MesMdWorkstationApi.Workstation>();
|
const workstationId = computed(() => workstation.value?.id);
|
const workstationName = computed(() => workstation.value?.name ?? '');
|
const wsSelectDialogRef = ref<InstanceType<typeof WorkstationSelectDialog>>();
|
|
function openWorkstationSelect() {
|
wsSelectDialogRef.value?.open();
|
}
|
|
function handleWorkstationSelect(ws: MesMdWorkstationApi.Workstation) {
|
workstation.value = ws;
|
}
|
|
// --- 任务列表 ---
|
const taskList = ref<PendingTask[]>([]);
|
const taskLoading = ref(false);
|
const selectedTask = ref<PendingTask>();
|
|
async function loadTaskList() {
|
if (!workstationId.value) {
|
taskList.value = [];
|
selectedTask.value = undefined;
|
return;
|
}
|
taskLoading.value = true;
|
try {
|
const result = await getPendingFeedbackPage({
|
workstationId: workstationId.value,
|
pageNo: 1,
|
pageSize: 200,
|
});
|
taskList.value = result.list ?? [];
|
// 同步 selectedTask 到最新数据(不自动选中第一个,需用户手动点击)
|
if (selectedTask.value) {
|
const updated = taskList.value.find(
|
(t) => t.id === selectedTask.value!.id,
|
);
|
if (updated) {
|
selectedTask.value = updated;
|
} else if (taskList.value.length) {
|
// 之前选中的任务不在新列表中了,清空
|
selectedTask.value = undefined;
|
} else {
|
selectedTask.value = undefined;
|
}
|
} else {
|
selectedTask.value = undefined;
|
}
|
} finally {
|
taskLoading.value = false;
|
}
|
}
|
|
// 工作站变更时重新加载任务列表
|
watch(workstationId, () => {
|
loadTaskList();
|
});
|
|
function handleSelectTask(task: PendingTask) {
|
selectedTask.value = task;
|
}
|
|
// --- 报工刷新 ---
|
const reportRefreshKey = ref(0);
|
|
function onReported() {
|
reportRefreshKey.value += 1;
|
// 刷新任务列表以更新进度
|
loadTaskList();
|
}
|
|
// --- 返回系统 ---
|
function goBack() {
|
router.push('/');
|
}
|
</script>
|
|
<template>
|
<div class="workbench-page">
|
<!-- 顶部导航栏 -->
|
<header class="workbench-topbar">
|
<div class="workbench-topbar__brand">
|
<IconifyIcon icon="lucide:factory" class="workbench-topbar__logo" />
|
<div class="workbench-topbar__brand-text">
|
<span class="workbench-topbar__title">生产工作台</span>
|
<span v-if="workstationName" class="workbench-topbar__sub">
|
{{ workstationName }}
|
</span>
|
</div>
|
</div>
|
|
<nav class="workbench-nav">
|
<button
|
v-for="tab in tabItems"
|
:key="tab.key"
|
type="button"
|
class="workbench-nav__item"
|
:class="{ 'workbench-nav__item--active': activeTab === tab.key }"
|
@click="activeTab = tab.key"
|
>
|
<IconifyIcon :icon="tab.icon" class="workbench-nav__icon" />
|
{{ tab.label }}
|
</button>
|
</nav>
|
|
<Button class="workbench-back-btn" @click="goBack">
|
<template #icon>
|
<IconifyIcon icon="lucide:arrow-left" />
|
</template>
|
返回系统
|
</Button>
|
</header>
|
|
<!-- 主体区域 -->
|
<div class="workbench-body">
|
<!-- 左侧边栏 -->
|
<aside class="workbench-aside">
|
<!-- 工作站选择面板 -->
|
<WorkstationPanel
|
:workstation="workstation"
|
@change="openWorkstationSelect"
|
/>
|
|
<!-- 工单列表 -->
|
<TaskListPanel
|
:list="taskList"
|
:selected-id="selectedTask?.id"
|
:loading="taskLoading"
|
@select="handleSelectTask"
|
/>
|
</aside>
|
|
<!-- 分割线拖拽按钮(视觉装饰) -->
|
<div class="workbench-split">
|
<div class="workbench-split__line" />
|
</div>
|
|
<!-- 右侧主区域 -->
|
<main class="workbench-main">
|
<div class="workbench-main__head">
|
<span class="workbench-main__tab-title">{{ activeTabLabel }}</span>
|
<span v-if="selectedTask?.code" class="workbench-main__context">
|
工单 {{ selectedTask.code }}
|
</span>
|
</div>
|
<div class="workbench-main__body">
|
<TaskDetailTab
|
v-if="activeTab === 'detail'"
|
:task="selectedTask"
|
/>
|
<FeedbackTab
|
v-else-if="activeTab === 'feedback'"
|
:task="selectedTask"
|
:refresh-key="reportRefreshKey"
|
@reported="onReported"
|
/>
|
<FeedbackHistoryTab
|
v-else-if="activeTab === 'history'"
|
:task-id="selectedTask?.id"
|
:refresh-key="reportRefreshKey"
|
@reported="onReported"
|
/>
|
<RecordParamTab
|
v-else-if="activeTab === 'record-param'"
|
:task-id="selectedTask?.id"
|
:work-order-id="selectedTask?.id"
|
:workstation-id="workstationId"
|
/>
|
<ProductIssueTab
|
v-else-if="activeTab === 'product-issue'"
|
:task-id="selectedTask?.id"
|
:task-code="selectedTask?.code"
|
:task-name="selectedTask?.name"
|
:work-order-id="selectedTask?.workOrderId"
|
:workstation-id="workstationId"
|
:workstation-name="workstationName"
|
/>
|
<ReturnIssueTab
|
v-else-if="activeTab === 'return-issue'"
|
:task-id="selectedTask?.id"
|
:task-code="selectedTask?.code"
|
:task-name="selectedTask?.name"
|
:work-order-id="selectedTask?.workOrderId"
|
:workstation-id="workstationId"
|
:workstation-name="workstationName"
|
/>
|
</div>
|
</main>
|
</div>
|
|
<!-- 工作站选择弹窗 -->
|
<WorkstationSelectDialog
|
ref="wsSelectDialogRef"
|
@select="handleWorkstationSelect"
|
/>
|
</div>
|
</template>
|
|
<style lang="scss" scoped>
|
$radius: 10px;
|
$shadow: 0 2px 12px rgb(0 0 0 / 8%);
|
$shadow-sm: 0 1px 4px rgb(0 0 0 / 6%);
|
$aside-width: 420px;
|
|
.workbench-page {
|
display: flex;
|
flex-direction: column;
|
width: 100%;
|
height: 100vh;
|
overflow: hidden;
|
background: #f5f5f5;
|
}
|
|
// --- 顶部导航栏 ---
|
.workbench-topbar {
|
display: flex;
|
align-items: flex-end;
|
gap: 20px;
|
flex-shrink: 0;
|
min-height: 56px;
|
padding: 0 20px;
|
background: linear-gradient(
|
105deg,
|
#1a3a5c 0%,
|
#1890ff 42%,
|
#40a9ff 100%
|
);
|
color: #fff;
|
box-shadow: $shadow;
|
z-index: 10;
|
}
|
|
.workbench-topbar__brand {
|
display: flex;
|
align-items: center;
|
gap: 10px;
|
flex-shrink: 0;
|
padding-bottom: 10px;
|
}
|
|
.workbench-topbar__logo {
|
font-size: 28px;
|
filter: brightness(1.1);
|
}
|
|
.workbench-topbar__brand-text {
|
display: flex;
|
flex-direction: column;
|
gap: 2px;
|
}
|
|
.workbench-topbar__title {
|
font-size: 18px;
|
font-weight: 600;
|
letter-spacing: 0.02em;
|
}
|
|
.workbench-topbar__sub {
|
font-size: 13px;
|
opacity: 0.85;
|
}
|
|
.workbench-nav {
|
flex: 1;
|
display: flex;
|
align-items: flex-end;
|
gap: 4px;
|
min-width: 0;
|
padding-bottom: 0;
|
overflow-x: auto;
|
scrollbar-width: none;
|
|
&::-webkit-scrollbar {
|
display: none;
|
}
|
}
|
|
.workbench-nav__item {
|
flex-shrink: 0;
|
display: inline-flex;
|
align-items: center;
|
gap: 6px;
|
padding: 10px 20px;
|
border: none;
|
border-radius: 8px 8px 0 0;
|
background: transparent;
|
color: rgb(255 255 255 / 75%);
|
font-size: 15px;
|
cursor: pointer;
|
transition: all 0.2s ease;
|
|
&:hover:not(&--active) {
|
color: #fff;
|
background: rgb(255 255 255 / 12%);
|
}
|
|
&--active {
|
color: #1890ff;
|
background: #f5f5f5;
|
font-weight: 600;
|
box-shadow: 0 -2px 8px rgb(0 0 0 / 6%);
|
}
|
}
|
|
.workbench-nav__icon {
|
font-size: 18px;
|
}
|
|
.workbench-back-btn {
|
flex-shrink: 0;
|
margin-bottom: 8px;
|
border-color: rgb(255 255 255 / 35%) !important;
|
background: rgb(255 255 255 / 12%) !important;
|
color: #fff !important;
|
|
&:hover {
|
border-color: rgb(255 255 255 / 55%) !important;
|
background: rgb(255 255 255 / 22%) !important;
|
}
|
}
|
|
// --- 主体布局 ---
|
.workbench-body {
|
flex: 1;
|
min-height: 0;
|
display: flex;
|
align-items: stretch;
|
padding: 12px;
|
gap: 0;
|
}
|
|
.workbench-aside {
|
flex-shrink: 0;
|
width: $aside-width;
|
display: flex;
|
flex-direction: column;
|
gap: 12px;
|
min-height: 0;
|
overflow: hidden;
|
}
|
|
|
.workbench-split {
|
flex-shrink: 0;
|
width: 12px;
|
display: flex;
|
align-items: center;
|
justify-content: center;
|
}
|
|
.workbench-split__line {
|
width: 1px;
|
height: 100%;
|
background: #e8e8e8;
|
}
|
|
.workbench-main {
|
flex: 1;
|
margin-left: 12px;
|
min-width: 0;
|
min-height: 0;
|
display: flex;
|
flex-direction: column;
|
background: #fff;
|
border-radius: $radius;
|
border: 1px solid #f0f0f0;
|
box-shadow: $shadow-sm;
|
overflow: hidden;
|
}
|
|
.workbench-main__head {
|
display: flex;
|
align-items: center;
|
gap: 14px;
|
flex-shrink: 0;
|
padding: 14px 24px;
|
border-bottom: 1px solid #f0f0f0;
|
background: linear-gradient(180deg, #fafbfc 0%, #fff 100%);
|
}
|
|
.workbench-main__tab-title {
|
font-size: 18px;
|
font-weight: 600;
|
color: #1890ff;
|
}
|
|
.workbench-main__context {
|
font-size: 14px;
|
color: #595959;
|
padding: 4px 12px;
|
background: #f0f0f0;
|
border-radius: 6px;
|
font-weight: 500;
|
}
|
|
.workbench-main__body {
|
flex: 1;
|
min-height: 0;
|
overflow: hidden;
|
}
|
</style>
|