<script lang="ts" setup>
|
import type { VxeTableGridOptions } from '#/adapter/vxe-table';
|
import type { MesProWorkOrderApi, TaskInfo } from '#/api/mes/pro/workorder';
|
|
import { ref } from 'vue';
|
import { useRouter } from 'vue-router';
|
|
import { Page, useVbenModal } from '@vben/common-ui';
|
import { MesProWorkOrderStatusEnum, MesProWorkOrderTypeEnum } from '@vben/constants';
|
|
import { Button, Card, Collapse, Progress, Tag, message } from 'ant-design-vue';
|
|
import { ACTION_ICON, TableAction, useVbenVxeGrid } from '#/adapter/vxe-table';
|
import {
|
getWorkOrderPage,
|
getWorkOrderProgress,
|
checkAndAutoFinishWorkOrder,
|
} from '#/api/mes/pro/workorder';
|
import { previewAutoGenerate, autoGenerateTasks } from '#/api/mes/pro/task';
|
|
import { useGridColumns, useGridFormSchema, useTaskGridColumns } from './data';
|
import QuickFeedbackForm from './modules/quick-feedback.vue';
|
import SchedulePreviewModal from './modules/schedule-preview.vue';
|
|
defineOptions({ name: 'WorkorderCenter' });
|
|
const router = useRouter();
|
|
const progressData = ref<MesProWorkOrderApi.WorkOrderProgress | null>(null);
|
const activeProcessKeys = ref<string[]>([]);
|
const expandedRowId = ref<number | null>(null);
|
|
const [QuickFeedbackModal, quickFeedbackModalApi] = useVbenModal({
|
connectedComponent: QuickFeedbackForm,
|
destroyOnClose: true,
|
});
|
|
const [SchedulePreviewModal, schedulePreviewModalApi] = useVbenModal({
|
connectedComponent: SchedulePreviewModal,
|
destroyOnClose: true,
|
});
|
|
function handleRefresh() {
|
gridApi.query();
|
// 如果进度详情展开中,刷新进度数据
|
if (expandedRowId.value) {
|
refreshProgress();
|
}
|
}
|
|
async function refreshProgress() {
|
if (!expandedRowId.value) return;
|
progressData.value = await getWorkOrderProgress(expandedRowId.value);
|
}
|
|
/** 快速报工成功后刷新进度并检查自动完成 */
|
async function handleFeedbackSuccess() {
|
handleRefresh();
|
if (expandedRowId.value) {
|
try {
|
await checkAndAutoFinishWorkOrder(expandedRowId.value);
|
} catch {
|
// 自动完成检查失败不影响主流程
|
}
|
}
|
}
|
|
/** 查看工单进度(展开行) */
|
async function handleToggleProgress(row: MesProWorkOrderApi.WorkOrder) {
|
if (expandedRowId.value === row.id) {
|
expandedRowId.value = null;
|
progressData.value = null;
|
return;
|
}
|
expandedRowId.value = row.id!;
|
progressData.value = await getWorkOrderProgress(row.id!);
|
// 默认展开第一个工序
|
if (progressData.value?.processList?.length) {
|
activeProcessKeys.value = [String(progressData.value.processList[0].processId)];
|
}
|
}
|
|
/** 工序进度条颜色 */
|
function getProcessStatusColor(status: number): string {
|
switch (status) {
|
case 2: return '#52c41a';
|
case 1: return '#1677ff';
|
default: return '#d9d9d9';
|
}
|
}
|
|
/** 工序状态文字 */
|
function getProcessStatusText(status: number): string {
|
switch (status) {
|
case 2: return '已完成';
|
case 1: return '进行中';
|
default: return '未开始';
|
}
|
}
|
|
/** 快速报工 */
|
function handleQuickFeedback(task: TaskInfo) {
|
quickFeedbackModalApi
|
.setData({ taskId: task.taskId, taskCode: task.taskCode, taskQuantity: task.quantity, taskProducedQuantity: task.producedQuantity })
|
.open();
|
}
|
|
/** 一键排产预览 */
|
async function handleSchedulePreview(row: MesProWorkOrderApi.WorkOrder) {
|
try {
|
const preview = await previewAutoGenerate({ workOrderId: row.id! });
|
schedulePreviewModalApi
|
.setData({ workOrder: row, preview })
|
.open();
|
} catch {
|
message.error('获取排产预览失败');
|
}
|
}
|
|
/** 执行一键排产 */
|
async function handleScheduleExecute(row: MesProWorkOrderApi.WorkOrder) {
|
try {
|
const result = await autoGenerateTasks({ workOrderId: row.id! });
|
message.success(`排产成功,生成 ${result.taskIds?.length || 0} 个任务`);
|
handleRefresh();
|
} catch {
|
message.error('排产失败');
|
}
|
}
|
|
const [Grid, gridApi] = useVbenVxeGrid({
|
formOptions: {
|
schema: useGridFormSchema(),
|
},
|
gridOptions: {
|
columns: useGridColumns(),
|
height: 'auto',
|
keepSource: true,
|
proxyConfig: {
|
ajax: {
|
query: async ({ page }, formValues) => {
|
return await getWorkOrderPage({
|
pageNo: page.currentPage,
|
pageSize: page.pageSize,
|
status: MesProWorkOrderStatusEnum.CONFIRMED,
|
type: MesProWorkOrderTypeEnum.SELF,
|
...formValues,
|
});
|
},
|
},
|
},
|
rowConfig: {
|
keyField: 'id',
|
isHover: true,
|
},
|
toolbarConfig: {
|
refresh: true,
|
search: true,
|
},
|
} as VxeTableGridOptions<MesProWorkOrderApi.WorkOrder>,
|
});
|
</script>
|
|
<template>
|
<Page auto-content-height>
|
<QuickFeedbackModal @success="handleFeedbackSuccess" />
|
<SchedulePreviewModal @success="handleRefresh" />
|
|
<Grid table-title="工单中心">
|
<template #toolbar-tools>
|
<TableAction
|
:actions="[
|
{
|
label: '创建工单',
|
type: 'primary',
|
icon: ACTION_ICON.ADD,
|
auth: ['mes:pro-work-order:create'],
|
onClick: () => router.push({ name: 'MesProWorkorder' }),
|
},
|
]"
|
/>
|
</template>
|
|
<template #code="{ row }">
|
<Button type="link" @click="handleToggleProgress(row)">
|
{{ row.code }}
|
</Button>
|
</template>
|
|
<template #actions="{ row }">
|
<TableAction
|
:actions="[
|
{
|
label: '排产预览',
|
type: 'link',
|
auth: ['mes:pro-task:create'],
|
ifShow: row.quantityScheduled < row.quantity,
|
onClick: handleSchedulePreview.bind(null, row),
|
},
|
{
|
label: '一键排产',
|
type: 'link',
|
auth: ['mes:pro-task:create'],
|
ifShow: row.quantityScheduled < row.quantity,
|
popConfirm: {
|
title: '确认一键排产?将按照工艺路线自动生成任务。',
|
confirm: handleScheduleExecute.bind(null, row),
|
},
|
},
|
]"
|
/>
|
</template>
|
</Grid>
|
|
<!-- 展开的工单进度详情 -->
|
<Card
|
v-if="progressData"
|
title="工单进度详情"
|
class="mt-4"
|
size="small"
|
>
|
<template #extra>
|
<Button type="link" @click="expandedRowId = null; progressData = null">收起</Button>
|
</template>
|
|
<!-- 总体进度 -->
|
<div class="mb-4 flex items-center gap-4">
|
<span class="text-gray-500">总进度:</span>
|
<Progress
|
:percent="Math.round(progressData.progressPercent || 0)"
|
:stroke-color="{
|
from: '#1677ff',
|
to: '#52c41a',
|
}"
|
class="flex-1"
|
/>
|
<span class="text-sm text-gray-400">
|
{{ progressData.quantityProduced }} / {{ progressData.quantity }}
|
</span>
|
</div>
|
|
<!-- 工序进度步骤条 -->
|
<div class="mb-4 flex items-center gap-0">
|
<template v-for="(proc, idx) in progressData.processList" :key="proc.processId!">
|
<!-- 节点 -->
|
<div class="flex flex-col items-center" style="flex: 1">
|
<Tag :color="getProcessStatusColor(proc.status!)">
|
{{ proc.processName }}
|
</Tag>
|
<span class="mt-1 text-xs text-gray-400">
|
{{ getProcessStatusText(proc.status!) }}
|
</span>
|
<Progress
|
v-if="proc.keyFlag"
|
:percent="Math.round(proc.progressPercent || 0)"
|
:size="80"
|
type="circle"
|
:stroke-color="getProcessStatusColor(proc.status!)"
|
class="mt-1"
|
/>
|
</div>
|
<!-- 连线 -->
|
<div
|
v-if="idx < progressData.processList!.length - 1"
|
class="h-0.5 flex-1"
|
:style="{
|
background:
|
proc.status === 2
|
? '#52c41a'
|
: proc.status === 1
|
? '#1677ff'
|
: '#d9d9d9',
|
}"
|
/>
|
</template>
|
</div>
|
|
<!-- 任务明细 -->
|
<Collapse v-model:activeKey="activeProcessKeys">
|
<Collapse.Panel
|
v-for="proc in progressData.processList"
|
:key="String(proc.processId)"
|
:header="`${proc.processName}(${proc.producedQuantity || 0} / ${proc.plannedQuantity || proc.producedQuantity || 0})`"
|
>
|
<vxe-grid
|
:key="proc.processId"
|
:columns="useTaskGridColumns()"
|
:data="proc.taskList || []"
|
height="auto"
|
size="small"
|
>
|
<template #taskProgress="{ row: taskRow }">
|
<Progress
|
:percent="Math.round(taskRow.progressPercent || 0)"
|
size="small"
|
/>
|
</template>
|
<template #taskActions="{ row: taskRow }">
|
<Button
|
type="link"
|
size="small"
|
:disabled="taskRow.progressPercent >= 100"
|
@click="handleQuickFeedback(taskRow)"
|
>
|
报工
|
</Button>
|
</template>
|
</vxe-grid>
|
</Collapse.Panel>
|
</Collapse>
|
</Card>
|
</Page>
|
</template>
|