2025-12-23 13:37:48 +08:00
|
|
|
<script setup lang="ts">
|
2025-12-29 17:21:47 +08:00
|
|
|
import { ref, reactive, nextTick, onUnmounted, getCurrentInstance } from 'vue';
|
2025-12-23 13:37:48 +08:00
|
|
|
import { useRoute, useRouter } from 'vue-router';
|
|
|
|
|
import { message } from 'ant-design-vue';
|
2025-12-29 09:02:46 +08:00
|
|
|
import { debounce } from 'lodash';
|
|
|
|
|
import { usePwoStore, useJobStore } from '@/store';
|
|
|
|
|
import { listLotTraceOrder, getLotTraceOrder, addQualityAbnormalContact } from '@/api/pwoManage';
|
|
|
|
|
import type { LotTraceOrderData } from '@/api/pwoManage/model';
|
2025-12-23 13:37:48 +08:00
|
|
|
|
2025-12-29 17:21:47 +08:00
|
|
|
const { proxy } = getCurrentInstance() as any
|
|
|
|
|
const { mes_station_status, lot_trace_order_status } = proxy.useDict("mes_station_status", "lot_trace_order_status")
|
|
|
|
|
|
2025-12-23 13:37:48 +08:00
|
|
|
const route = useRoute();
|
|
|
|
|
const router = useRouter();
|
|
|
|
|
|
|
|
|
|
const pwoStore = usePwoStore();
|
2025-12-29 09:02:46 +08:00
|
|
|
const jobStore = useJobStore();
|
2025-12-23 13:37:48 +08:00
|
|
|
|
2025-12-29 09:02:46 +08:00
|
|
|
const workOrderInfo = reactive<LotTraceOrderData>({});
|
|
|
|
|
const processInfo = jobStore.jobInfo;
|
2025-12-23 13:37:48 +08:00
|
|
|
|
|
|
|
|
// 孙组件
|
|
|
|
|
const infeedRef = ref<any>(null);
|
|
|
|
|
const collapsed = ref(false);
|
|
|
|
|
const toggleCollapse = async () => {
|
|
|
|
|
collapsed.value = !collapsed.value;
|
|
|
|
|
await nextTick();
|
|
|
|
|
infeedRef.value?.renderTableHeight();
|
|
|
|
|
}
|
|
|
|
|
|
2025-12-29 09:02:46 +08:00
|
|
|
const redirectTo = (routeName: string) => {
|
|
|
|
|
router.push({ name: routeName });
|
|
|
|
|
if (routeName === 'PwoManage') {
|
|
|
|
|
handleRefresh();
|
|
|
|
|
}
|
|
|
|
|
}
|
2025-12-23 13:37:48 +08:00
|
|
|
|
|
|
|
|
const openHoldModal = ref(false);
|
2025-12-29 09:02:46 +08:00
|
|
|
const planFinishDate = ref('');
|
2025-12-23 13:37:48 +08:00
|
|
|
const handleHold = () => {
|
2025-12-29 09:02:46 +08:00
|
|
|
if (!workOrderInfo.code) {
|
|
|
|
|
message.error('请先选择工单!');
|
|
|
|
|
return;
|
|
|
|
|
}
|
2025-12-23 13:37:48 +08:00
|
|
|
openHoldModal.value = true;
|
|
|
|
|
};
|
|
|
|
|
const handleCloseHold = () => {
|
2025-12-29 09:02:46 +08:00
|
|
|
planFinishDate.value = '';
|
2025-12-23 13:37:48 +08:00
|
|
|
openHoldModal.value = false;
|
|
|
|
|
};
|
2025-12-29 09:02:46 +08:00
|
|
|
const handleSubmitHold = async () => {
|
|
|
|
|
const tmpPlanFinishDate = planFinishDate.value;
|
|
|
|
|
// 修改随工单状态
|
|
|
|
|
try {
|
|
|
|
|
message.success('Hold 成功!')
|
|
|
|
|
planFinishDate.value = '';
|
|
|
|
|
openHoldModal.value = false;
|
|
|
|
|
} catch (error: any) {
|
|
|
|
|
message.error(error.message || 'Hold 失败');
|
|
|
|
|
return;
|
|
|
|
|
}
|
2025-12-23 13:37:48 +08:00
|
|
|
|
2025-12-29 09:02:46 +08:00
|
|
|
// 添加修改记录
|
|
|
|
|
try {
|
|
|
|
|
addQualityAbnormalContact({
|
|
|
|
|
materialCode: workOrderInfo.tarMaterialCode,
|
|
|
|
|
abnormalOperation: processInfo.operationCode,
|
|
|
|
|
planFinishDate: tmpPlanFinishDate,
|
|
|
|
|
status: "Hold",
|
|
|
|
|
})
|
|
|
|
|
} catch (error: any) {
|
|
|
|
|
message.error(error.message || '添加记录异常');
|
|
|
|
|
return;
|
|
|
|
|
}
|
2025-12-23 13:37:48 +08:00
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const handleCopy = async (text: string | number | undefined) => {
|
|
|
|
|
if (!text) return;
|
|
|
|
|
await navigator.clipboard.writeText(String(text));
|
|
|
|
|
message.success(`已复制: ${ text }`);
|
|
|
|
|
}
|
2025-12-29 09:02:46 +08:00
|
|
|
|
|
|
|
|
const pwoCodeOptions = ref<LotTraceOrderData[]>([])
|
|
|
|
|
const handleSearch = debounce(async (code: string) => {
|
|
|
|
|
if (!code) return;
|
|
|
|
|
try {
|
|
|
|
|
const { rows } = await listLotTraceOrder({
|
|
|
|
|
code,
|
|
|
|
|
pageSize: 10,
|
|
|
|
|
pageNum: 1,
|
|
|
|
|
});
|
|
|
|
|
pwoCodeOptions.value = rows.map(item => {
|
|
|
|
|
return {
|
|
|
|
|
id: item.id,
|
|
|
|
|
label: item.code,
|
|
|
|
|
value: item.code,
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
} catch (error: any) {
|
|
|
|
|
message.error(error.message || '获取工单信息失败');
|
|
|
|
|
}
|
|
|
|
|
}, 500)
|
|
|
|
|
|
|
|
|
|
const loadingPwoInfo = ref(false);
|
|
|
|
|
const handleChange = async (value: string, option: LotTraceOrderData) => {
|
|
|
|
|
workOrderInfo.code = value;
|
|
|
|
|
try {
|
|
|
|
|
loadingPwoInfo.value = true;
|
|
|
|
|
if (!option.id) throw new Error();
|
|
|
|
|
const { data } = await getLotTraceOrder(option.id)
|
|
|
|
|
Object.assign(workOrderInfo, data);
|
|
|
|
|
pwoStore.setInfo(data);
|
|
|
|
|
jobStore.resetInfo();
|
|
|
|
|
} catch (error: any) {
|
|
|
|
|
message.error(error.message || '获取工单信息失败');
|
|
|
|
|
} finally {
|
|
|
|
|
loadingPwoInfo.value = false;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 刷新工单信息
|
|
|
|
|
const handleRefresh = async () => {
|
|
|
|
|
if (!workOrderInfo.id) return;
|
|
|
|
|
try {
|
|
|
|
|
loadingPwoInfo.value = true;
|
|
|
|
|
const { data } = await getLotTraceOrder(workOrderInfo.id)
|
|
|
|
|
Object.assign(workOrderInfo, data);
|
|
|
|
|
pwoStore.setInfo(data);
|
|
|
|
|
} catch (error: any) {
|
|
|
|
|
message.error(error.message || '刷新工单信息失败');
|
|
|
|
|
} finally {
|
|
|
|
|
loadingPwoInfo.value = false;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
onUnmounted(() => {
|
|
|
|
|
pwoStore.resetInfo();
|
|
|
|
|
jobStore.resetInfo();
|
|
|
|
|
})
|
2025-12-23 13:37:48 +08:00
|
|
|
</script>
|
|
|
|
|
|
|
|
|
|
<template>
|
|
|
|
|
<div class="page-container">
|
2025-12-29 09:02:46 +08:00
|
|
|
<Header title="过站工控机" showHome showLogout>
|
2025-12-23 13:37:48 +08:00
|
|
|
<template #right-opts>
|
|
|
|
|
<a-button @click="toggleCollapse">{{ collapsed ? '展开' : '折叠' }}</a-button>
|
|
|
|
|
</template>
|
|
|
|
|
</Header>
|
|
|
|
|
|
|
|
|
|
<div class="content-wrapper">
|
|
|
|
|
<!-- Top Section -->
|
|
|
|
|
<div class="top-section">
|
|
|
|
|
<a-row :gutter="16" class="full-height-row">
|
|
|
|
|
<!-- Work Order Info -->
|
|
|
|
|
<a-col :span="13">
|
|
|
|
|
<a-spin :spinning="loadingPwoInfo">
|
|
|
|
|
<a-card title="工单信息" class="info-card" :bordered="false">
|
|
|
|
|
<a-form :model="workOrderInfo" :colon="false" v-show="!collapsed">
|
|
|
|
|
<a-row :gutter="36">
|
|
|
|
|
<a-col :span="12">
|
|
|
|
|
<a-form-item label="工单批次">
|
|
|
|
|
<a-input v-model:value="workOrderInfo.batchNo" readonly>
|
|
|
|
|
<template #suffix>
|
2025-12-29 17:21:47 +08:00
|
|
|
<a-button @click="handleCopy(workOrderInfo.batchNo)" size="small">
|
2025-12-23 13:37:48 +08:00
|
|
|
<template #icon><i-lucide-copy /></template>
|
|
|
|
|
</a-button>
|
|
|
|
|
</template>
|
|
|
|
|
</a-input>
|
|
|
|
|
</a-form-item>
|
|
|
|
|
<a-form-item label="工单状态">
|
2025-12-29 17:21:47 +08:00
|
|
|
<a-input readonly>
|
|
|
|
|
<template #prefix>
|
|
|
|
|
<DictTag :options="lot_trace_order_status" :value="workOrderInfo.status" size="medium" />
|
2025-12-23 13:37:48 +08:00
|
|
|
</template>
|
|
|
|
|
</a-input>
|
|
|
|
|
</a-form-item>
|
|
|
|
|
<a-form-item label="总计数量">
|
|
|
|
|
<a-input v-model:value="workOrderInfo.planQty" readonly>
|
|
|
|
|
<template #suffix>
|
2025-12-29 17:21:47 +08:00
|
|
|
<a-button @click="handleCopy(workOrderInfo.planQty)" size="small">
|
2025-12-23 13:37:48 +08:00
|
|
|
<template #icon><i-lucide-copy /></template>
|
|
|
|
|
</a-button>
|
|
|
|
|
</template>
|
|
|
|
|
</a-input>
|
|
|
|
|
</a-form-item>
|
|
|
|
|
</a-col>
|
|
|
|
|
<a-col :span="12">
|
|
|
|
|
<a-form-item label="产品编码">
|
|
|
|
|
<a-input v-model:value="workOrderInfo.tarMaterialCode" readonly>
|
|
|
|
|
<template #suffix>
|
2025-12-29 17:21:47 +08:00
|
|
|
<a-button @click="handleCopy(workOrderInfo.tarMaterialCode)" size="small">
|
2025-12-23 13:37:48 +08:00
|
|
|
<template #icon><i-lucide-copy /></template>
|
|
|
|
|
</a-button>
|
|
|
|
|
</template>
|
|
|
|
|
</a-input>
|
|
|
|
|
</a-form-item>
|
|
|
|
|
<a-form-item label="产品名称">
|
|
|
|
|
<a-input v-model:value="workOrderInfo.tarMaterialName" readonly>
|
|
|
|
|
<template #suffix>
|
2025-12-29 17:21:47 +08:00
|
|
|
<a-button @click="handleCopy(workOrderInfo.tarMaterialName)" size="small">
|
2025-12-23 13:37:48 +08:00
|
|
|
<template #icon><i-lucide-copy /></template>
|
|
|
|
|
</a-button>
|
|
|
|
|
</template>
|
|
|
|
|
</a-input>
|
|
|
|
|
</a-form-item>
|
2025-12-29 09:02:46 +08:00
|
|
|
<!-- <a-form-item label="产品规格">
|
|
|
|
|
<a-input readonly>
|
2025-12-23 13:37:48 +08:00
|
|
|
<template #suffix>
|
2025-12-29 17:21:47 +08:00
|
|
|
<a-button @click="handleCopy('')" size="small">
|
2025-12-23 13:37:48 +08:00
|
|
|
<template #icon><i-lucide-copy /></template>
|
|
|
|
|
</a-button>
|
|
|
|
|
</template>
|
|
|
|
|
</a-input>
|
2025-12-29 09:02:46 +08:00
|
|
|
</a-form-item> -->
|
2025-12-23 13:37:48 +08:00
|
|
|
</a-col>
|
|
|
|
|
</a-row>
|
|
|
|
|
</a-form>
|
|
|
|
|
<template #extra>
|
2025-12-29 09:02:46 +08:00
|
|
|
<a-space>
|
|
|
|
|
工单编码
|
|
|
|
|
<a-select v-model:value="workOrderInfo.code" show-search placeholder="输入工单编码" style="width: 300px"
|
|
|
|
|
:show-arrow="false" :options="pwoCodeOptions" @search="handleSearch" :disabled="route.name !== 'PwoManageIndex'"
|
|
|
|
|
@change="(val, option) => handleChange(val as string, option as LotTraceOrderData)" />
|
|
|
|
|
<a-button @click="handleRefresh"><template #icon><i-lucide-rotate-cw /></template></a-button>
|
|
|
|
|
</a-space>
|
2025-12-23 13:37:48 +08:00
|
|
|
</template>
|
|
|
|
|
</a-card>
|
|
|
|
|
</a-spin>
|
|
|
|
|
</a-col>
|
|
|
|
|
|
|
|
|
|
<!-- Process Info -->
|
|
|
|
|
<a-col :span="8">
|
|
|
|
|
<a-card title="工序信息" class="info-card" :bordered="false">
|
2025-12-29 09:02:46 +08:00
|
|
|
<a-form :model="processInfo" :colon="false" v-show="!collapsed">
|
2025-12-23 13:37:48 +08:00
|
|
|
<a-form-item label="工序名称">
|
|
|
|
|
<a-input v-model:value="processInfo.operationTitle" readonly>
|
|
|
|
|
<template #suffix>
|
2025-12-29 17:21:47 +08:00
|
|
|
<a-button @click="handleCopy(processInfo.operationTitle)" size="small">
|
2025-12-23 13:37:48 +08:00
|
|
|
<template #icon><i-lucide-copy /></template>
|
|
|
|
|
</a-button>
|
|
|
|
|
</template>
|
|
|
|
|
</a-input>
|
|
|
|
|
</a-form-item>
|
2025-12-29 09:02:46 +08:00
|
|
|
<a-form-item label="工序状态">
|
2025-12-29 17:21:47 +08:00
|
|
|
<a-input readonly>
|
|
|
|
|
<template #prefix>
|
|
|
|
|
<DictTag :options="mes_station_status" :value="processInfo.status" size="medium" />
|
2025-12-23 13:37:48 +08:00
|
|
|
</template>
|
|
|
|
|
</a-input>
|
|
|
|
|
</a-form-item>
|
2025-12-29 09:02:46 +08:00
|
|
|
<a-form-item label="作业编码">
|
|
|
|
|
<a-input v-model:value="processInfo.code" readonly>
|
2025-12-23 13:37:48 +08:00
|
|
|
<template #suffix>
|
2025-12-29 17:21:47 +08:00
|
|
|
<a-button @click="handleCopy(processInfo.code)" size="small">
|
2025-12-23 13:37:48 +08:00
|
|
|
<template #icon><i-lucide-copy /></template>
|
|
|
|
|
</a-button>
|
|
|
|
|
</template>
|
|
|
|
|
</a-input>
|
|
|
|
|
</a-form-item>
|
|
|
|
|
</a-form>
|
|
|
|
|
</a-card>
|
|
|
|
|
</a-col>
|
|
|
|
|
|
|
|
|
|
<!-- Action Buttons -->
|
|
|
|
|
<a-col :span="3" class="action-buttons-col">
|
|
|
|
|
<div class="action-buttons" v-show="!collapsed">
|
2025-12-29 09:02:46 +08:00
|
|
|
<a-button class="action-btn green-btn" @click="redirectTo('PwoManage')">工单管理</a-button>
|
|
|
|
|
<a-button class="action-btn orange-btn" @click="redirectTo('PrimaryMaterial')">主材清单</a-button>
|
|
|
|
|
<a-button class="action-btn red-btn" @click="handleHold">Hold</a-button>
|
2025-12-23 13:37:48 +08:00
|
|
|
</div>
|
|
|
|
|
<a-card title="操作" class="info-card" :bordered="false" v-show="collapsed">
|
|
|
|
|
<template #extra>
|
|
|
|
|
<a-dropdown>
|
|
|
|
|
<template #overlay>
|
|
|
|
|
<a-menu>
|
2025-12-29 09:02:46 +08:00
|
|
|
<a-menu-item key="1" @click="redirectTo('PwoManage')">工单管理</a-menu-item>
|
|
|
|
|
<a-menu-item key="2" @click="redirectTo('PrimaryMaterial')">主材清单</a-menu-item>
|
2025-12-23 13:37:48 +08:00
|
|
|
<a-menu-item key="4" @click="handleHold">Hold</a-menu-item>
|
|
|
|
|
</a-menu>
|
|
|
|
|
</template>
|
|
|
|
|
<a-button>
|
|
|
|
|
更多
|
|
|
|
|
<i-lucide-chevron-down />
|
|
|
|
|
</a-button>
|
|
|
|
|
</a-dropdown>
|
|
|
|
|
</template>
|
|
|
|
|
</a-card>
|
|
|
|
|
</a-col>
|
|
|
|
|
</a-row>
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
<!-- Bottom Section -->
|
|
|
|
|
<main class="bottom-section">
|
|
|
|
|
<router-view v-slot="{ Component }">
|
|
|
|
|
<component :is="Component" ref="infeedRef" />
|
|
|
|
|
</router-view>
|
|
|
|
|
</main>
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
<!-- Hold Modal -->
|
|
|
|
|
<a-modal
|
|
|
|
|
v-model:open="openHoldModal"
|
|
|
|
|
title="Hold 操作"
|
|
|
|
|
@cancel="handleCloseHold"
|
|
|
|
|
@ok="handleSubmitHold"
|
|
|
|
|
>
|
2025-12-29 09:02:46 +08:00
|
|
|
<a-form :colon="false" :label-col="{ span: 6 }" :wrapper-col="{ span: 18 }">
|
2025-12-23 13:37:48 +08:00
|
|
|
<a-form-item label="工单编码">
|
|
|
|
|
<a-input v-model:value="workOrderInfo.code" readonly />
|
|
|
|
|
</a-form-item>
|
|
|
|
|
<a-form-item label="目标产品编码">
|
|
|
|
|
<a-input v-model:value="workOrderInfo.tarMaterialCode" readonly />
|
|
|
|
|
</a-form-item>
|
|
|
|
|
<a-form-item label="目标产品名称">
|
|
|
|
|
<a-input v-model:value="workOrderInfo.tarMaterialName" readonly />
|
|
|
|
|
</a-form-item>
|
2025-12-29 09:02:46 +08:00
|
|
|
<a-form-item label="发起工序名称">
|
2025-12-23 13:37:48 +08:00
|
|
|
<a-input v-model:value="processInfo.operationTitle" readonly />
|
|
|
|
|
</a-form-item>
|
|
|
|
|
<a-form-item label="产品规格">
|
2025-12-29 09:02:46 +08:00
|
|
|
<a-input readonly />
|
2025-12-23 13:37:48 +08:00
|
|
|
</a-form-item>
|
|
|
|
|
<a-form-item label="计划完成日期">
|
2025-12-29 09:02:46 +08:00
|
|
|
<a-date-picker v-model:value="planFinishDate" placeholder="选择计划完成日期"
|
|
|
|
|
valueFormat="YYYY-MM-DD HH:mm:ss" show-time style="width: 100%" />
|
2025-12-23 13:37:48 +08:00
|
|
|
</a-form-item>
|
|
|
|
|
</a-form>
|
|
|
|
|
</a-modal>
|
|
|
|
|
</div>
|
|
|
|
|
</template>
|
|
|
|
|
|
|
|
|
|
<style scoped lang="scss">
|
|
|
|
|
.page-container {
|
|
|
|
|
height: 100vh;
|
|
|
|
|
background-color: #f0f2f5;
|
|
|
|
|
display: flex;
|
|
|
|
|
flex-direction: column;
|
|
|
|
|
overflow: hidden;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.content-wrapper {
|
|
|
|
|
padding: 16px;
|
|
|
|
|
flex: 1;
|
|
|
|
|
display: flex;
|
|
|
|
|
flex-direction: column;
|
|
|
|
|
gap: 16px;
|
|
|
|
|
min-height: 0;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.top-section {
|
|
|
|
|
background: transparent;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
:deep(.ant-spin-nested-loading),
|
|
|
|
|
:deep(.ant-spin-container) {
|
|
|
|
|
height: 100%;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.info-card {
|
|
|
|
|
height: 100%;
|
|
|
|
|
border-radius: 8px;
|
|
|
|
|
|
|
|
|
|
:deep(.ant-card-head) {
|
|
|
|
|
min-height: 48px;
|
|
|
|
|
padding: 0 16px;
|
|
|
|
|
border-bottom: none;
|
|
|
|
|
font-size: 20px;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
:deep(.ant-card-body) {
|
|
|
|
|
padding: 1px 16px;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
:deep(.ant-form-item) {
|
|
|
|
|
margin-bottom: 12px;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.action-buttons-col {
|
|
|
|
|
display: flex;
|
|
|
|
|
flex-direction: column;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.action-buttons {
|
|
|
|
|
display: flex;
|
|
|
|
|
flex-direction: column;
|
|
|
|
|
gap: 6px;
|
|
|
|
|
height: 100%;
|
|
|
|
|
|
|
|
|
|
.action-btn {
|
|
|
|
|
height: 48px;
|
|
|
|
|
font-size: 16px;
|
|
|
|
|
color: #fff;
|
|
|
|
|
border: none;
|
|
|
|
|
border-radius: 4px;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.bottom-section {
|
|
|
|
|
background: #fff;
|
|
|
|
|
padding: 16px;
|
|
|
|
|
border-radius: 8px;
|
|
|
|
|
flex: 1;
|
|
|
|
|
min-height: 0;
|
|
|
|
|
overflow: auto;
|
|
|
|
|
}
|
|
|
|
|
</style>
|