<script lang="ts" setup>
|
import type { VbenFormSchema } from '#/adapter/form';
|
import type { VxeTableGridOptions } from '#/adapter/vxe-table';
|
import type { MesWmReturnIssueApi } from '#/api/mes/wm/returnissue';
|
|
import { computed, h, markRaw, onBeforeUnmount, onMounted, ref, watch, nextTick } from 'vue';
|
|
import {
|
DICT_TYPE,
|
MesAutoCodeRuleCode,
|
MesProWorkOrderStatusEnum,
|
MesWmReturnIssueStatusEnum,
|
} from '@vben/constants';
|
import { IconifyIcon } from '@vben/icons';
|
import { getDictOptions } from '@vben/hooks';
|
|
import { Button, Divider, Empty, message, Popconfirm, Tag } from 'ant-design-vue';
|
|
import { useVbenForm } from '#/adapter/form';
|
import { useVbenModal } from '@vben/common-ui';
|
import { useVbenVxeGrid } from '#/adapter/vxe-table';
|
import { generateAutoCode } from '#/api/mes/md/autocode/record';
|
import {
|
cancelReturnIssue,
|
createReturnIssue,
|
deleteReturnIssue,
|
getReturnIssuePage,
|
submitReturnIssue,
|
updateReturnIssue,
|
} from '#/api/mes/wm/returnissue';
|
import { MdWorkstationSelect } from '#/views/mes/md/workstation/components';
|
import { ProWorkOrderSelect } from '#/views/mes/pro/workorder/components';
|
|
import ReturnIssueFormModal from './ReturnIssueFormModal.vue';
|
|
defineOptions({ name: 'WorkbenchReturnIssueTab' });
|
|
const props = defineProps<{
|
/** 当前工作站 ID */
|
workstationId?: number;
|
/** 当前工作站名称 */
|
workstationName?: string;
|
/** 当前选中的任务 ID */
|
taskId?: number;
|
/** 当前选中的任务编码 */
|
taskCode?: string;
|
/** 当前选中的任务名称 */
|
taskName?: string;
|
/** 当前选中的任务关联工单 ID */
|
workOrderId?: number;
|
}>();
|
|
// --- 表单状态 ---
|
const editingId = ref<number>();
|
const isCreating = ref(false);
|
|
/** 构建表单 schema */
|
function buildFormSchema(): VbenFormSchema[] {
|
return [
|
{
|
fieldName: 'code',
|
label: '退料单编号',
|
component: 'Input',
|
componentProps: { placeholder: '请输入退料单编号' },
|
rules: 'required',
|
suffix: () =>
|
h(
|
Button,
|
{
|
type: 'default',
|
size: 'small',
|
onClick: async () => {
|
const code = await generateAutoCode(
|
MesAutoCodeRuleCode.WM_RETURN_ISSUE_CODE,
|
);
|
await formApi.setFieldValue('code', code);
|
},
|
},
|
{ default: () => '生成' },
|
),
|
},
|
{
|
fieldName: 'name',
|
label: '退料单名称',
|
component: 'Input',
|
componentProps: { placeholder: '请输入退料单名称' },
|
rules: 'required',
|
},
|
{
|
fieldName: 'type',
|
label: '退料类型',
|
component: 'Select',
|
componentProps: {
|
allowClear: true,
|
options: getDictOptions(DICT_TYPE.MES_WM_RETURN_ISSUE_TYPE, 'number'),
|
placeholder: '请选择退料类型',
|
},
|
rules: 'required',
|
},
|
{
|
fieldName: 'taskId',
|
label: '生产任务',
|
component: 'Input',
|
componentProps: {
|
placeholder: '从选中任务自动带入',
|
disabled: true,
|
},
|
rules: 'required',
|
},
|
{
|
fieldName: 'workOrderId',
|
label: '生产订单',
|
component: markRaw(ProWorkOrderSelect),
|
componentProps: {
|
status: MesProWorkOrderStatusEnum.CONFIRMED,
|
disabled: true,
|
},
|
},
|
{
|
fieldName: 'workstationId',
|
label: '工作站',
|
component: markRaw(MdWorkstationSelect),
|
componentProps: { placeholder: '从选中任务自动带入', disabled: true },
|
},
|
{
|
fieldName: 'returnDate',
|
label: '退料日期',
|
component: 'DatePicker',
|
componentProps: {
|
class: '!w-full',
|
format: 'YYYY-MM-DD HH:mm:ss',
|
placeholder: '请选择退料日期',
|
showTime: true,
|
valueFormat: 'x',
|
},
|
},
|
{
|
fieldName: 'remark',
|
label: '备注',
|
component: 'Textarea',
|
componentProps: { placeholder: '请输入备注', rows: 2 },
|
},
|
];
|
}
|
|
const [Form, formApi] = useVbenForm({
|
commonConfig: {
|
componentProps: { class: 'w-full' },
|
formItemClass: 'col-span-1',
|
labelWidth: 100,
|
},
|
layout: 'horizontal',
|
schema: buildFormSchema(),
|
showDefaultActions: false,
|
wrapperClass: 'grid-cols-3',
|
});
|
|
/** 新建退料单 */
|
async function handleCreate() {
|
isCreating.value = true;
|
editingId.value = undefined;
|
await formApi.resetForm();
|
// 预填任务信息
|
if (props.taskId) {
|
await formApi.setFieldValue('taskId', props.taskId);
|
}
|
if (props.workOrderId) {
|
await formApi.setFieldValue('workOrderId', props.workOrderId);
|
}
|
if (props.workstationId) {
|
await formApi.setFieldValue('workstationId', props.workstationId);
|
}
|
}
|
|
/** 保存退料单 */
|
async function handleSave() {
|
const { valid } = await formApi.validate();
|
if (!valid) return;
|
const data =
|
(await formApi.getValues()) as MesWmReturnIssueApi.ReturnIssue;
|
try {
|
if (editingId.value) {
|
await updateReturnIssue({ ...data, id: editingId.value });
|
message.success('修改成功');
|
} else {
|
const id = await createReturnIssue(data);
|
editingId.value = id;
|
await formApi.setFieldValue('id', id);
|
message.success('创建成功');
|
}
|
isCreating.value = false;
|
gridApi.query();
|
} catch {
|
message.error('保存失败');
|
}
|
}
|
|
/** 取消编辑 */
|
function handleCancelEdit() {
|
isCreating.value = false;
|
editingId.value = undefined;
|
formApi.resetForm();
|
}
|
|
/** 编辑退料单:打开编辑弹窗(仅草稿状态可编辑) */
|
function handleEditRow(row: MesWmReturnIssueApi.ReturnIssue) {
|
editFormModalApi.setData({ id: row.id }).open();
|
}
|
|
/** 提交 */
|
async function handleSubmitRow(row: MesWmReturnIssueApi.ReturnIssue) {
|
await submitReturnIssue(row.id!);
|
message.success('提交成功');
|
gridApi.query();
|
}
|
|
/** 删除 */
|
async function handleDeleteRow(row: MesWmReturnIssueApi.ReturnIssue) {
|
await deleteReturnIssue(row.id!);
|
message.success('删除成功');
|
gridApi.query();
|
}
|
|
/** 取消 */
|
async function handleCancelRow(row: MesWmReturnIssueApi.ReturnIssue) {
|
await cancelReturnIssue(row.id!);
|
message.success('取消成功');
|
gridApi.query();
|
}
|
|
// --- 列表 ---
|
const gridColumns: VxeTableGridOptions<MesWmReturnIssueApi.ReturnIssue>['columns'] = [
|
{ field: 'code', title: '退料单编号', minWidth: 150 },
|
{ field: 'name', title: '退料单名称', minWidth: 130 },
|
{
|
field: 'type',
|
title: '退料类型',
|
width: 100,
|
cellRender: {
|
name: 'CellDict',
|
props: { type: DICT_TYPE.MES_WM_RETURN_ISSUE_TYPE },
|
},
|
},
|
{
|
field: 'taskCode',
|
title: '生产任务',
|
minWidth: 130,
|
formatter: ({ row }) => row.taskCode || props.taskCode || '',
|
},
|
{ field: 'workOrderCode', title: '生产订单', minWidth: 130 },
|
{
|
field: 'returnDate',
|
title: '退料日期',
|
width: 170,
|
formatter: 'formatDateTime',
|
},
|
{
|
field: 'status',
|
title: '状态',
|
width: 100,
|
cellRender: {
|
name: 'CellDict',
|
props: { type: DICT_TYPE.MES_WM_RETURN_ISSUE_STATUS },
|
},
|
},
|
{ title: '操作', width: 260, fixed: 'right', slots: { default: 'actions' } },
|
];
|
|
const gridHeight = ref(300);
|
const listRef = ref<HTMLElement>();
|
let resizeObserver: ResizeObserver | null = null;
|
|
const [Grid, gridApi] = useVbenVxeGrid({
|
gridOptions: {
|
columns: gridColumns,
|
height: gridHeight.value,
|
keepSource: true,
|
proxyConfig: {
|
ajax: {
|
query: async ({ page }, formValues) => {
|
return await getReturnIssuePage({
|
pageNo: page.currentPage,
|
pageSize: page.pageSize,
|
taskId: props.taskId,
|
...formValues,
|
});
|
},
|
},
|
},
|
rowConfig: { keyField: 'id', isHover: true },
|
toolbarConfig: { refresh: true },
|
pagerConfig: { pageSize: 10 },
|
} as VxeTableGridOptions<MesWmReturnIssueApi.ReturnIssue>,
|
});
|
|
function updateGridHeight() {
|
if (listRef.value) {
|
const h = listRef.value.clientHeight;
|
if (h > 100 && h !== gridHeight.value) {
|
gridHeight.value = h;
|
gridApi.setGridOptions({ height: h });
|
}
|
}
|
}
|
|
onMounted(() => {
|
nextTick(() => {
|
updateGridHeight();
|
if (listRef.value) {
|
resizeObserver = new ResizeObserver(() => updateGridHeight());
|
resizeObserver.observe(listRef.value);
|
}
|
});
|
});
|
|
onBeforeUnmount(() => {
|
resizeObserver?.disconnect();
|
resizeObserver = null;
|
});
|
|
// 任务或工作站变化时刷新列表
|
watch(
|
() => [props.taskId, props.workstationId],
|
() => gridApi.query(),
|
);
|
|
/** 是否已选中工作站和任务,未选中时不展示表格 */
|
const isReady = computed(() => !!props.taskId && !!props.workstationId);
|
|
/** 编辑弹窗 */
|
const [EditFormModal, editFormModalApi] = useVbenModal({
|
connectedComponent: ReturnIssueFormModal,
|
destroyOnClose: true,
|
});
|
|
/** 编辑弹窗操作成功后刷新列表 */
|
function onEditSuccess() {
|
gridApi.query();
|
}
|
</script>
|
|
<template>
|
<div class="issue-tab">
|
<!-- 未选中工作站或任务时显示提示 -->
|
<div v-if="!isReady" class="issue-tab__empty">
|
<Empty
|
:image="Empty.PRESENTED_IMAGE_SIMPLE"
|
description="请先在左侧选择工作站和任务"
|
/>
|
</div>
|
<template v-else>
|
<!-- 顶部:表单 / 新建按钮 -->
|
<div class="issue-tab__header">
|
<div class="issue-tab__header-left">
|
<IconifyIcon icon="lucide:rotate-ccw" class="issue-tab__icon" />
|
<span class="issue-tab__title">退料单</span>
|
<Tag v-if="isCreating" color="orange">新建中</Tag>
|
</div>
|
<div class="issue-tab__header-actions">
|
<template v-if="!isCreating">
|
<Button type="primary" size="small" @click="handleCreate">
|
<template #icon><IconifyIcon icon="lucide:plus" /></template>
|
新建退料单
|
</Button>
|
</template>
|
<template v-else>
|
<Button type="primary" size="small" @click="handleSave">
|
<template #icon><IconifyIcon icon="lucide:save" /></template>
|
保存
|
</Button>
|
<Button size="small" @click="handleCancelEdit">取消</Button>
|
</template>
|
</div>
|
</div>
|
|
<!-- 表单区域(仅新建时展示) -->
|
<div v-if="isCreating" class="issue-tab__form">
|
<Form />
|
</div>
|
|
<Divider style="margin: 12px 0" />
|
|
<!-- 底部:退料记录列表 -->
|
<div ref="listRef" class="issue-tab__list">
|
<Grid table-title="退料记录">
|
<template #actions="{ row }">
|
<Button
|
v-if="row.status === MesWmReturnIssueStatusEnum.PREPARE"
|
type="link"
|
size="small"
|
@click="handleEditRow(row)"
|
>
|
编辑
|
</Button>
|
<Popconfirm
|
v-if="row.status === MesWmReturnIssueStatusEnum.PREPARE"
|
title="确认提交?提交后不能修改"
|
@confirm="handleSubmitRow(row)"
|
>
|
<Button type="link" size="small">提交</Button>
|
</Popconfirm>
|
<Popconfirm
|
v-if="row.status === MesWmReturnIssueStatusEnum.PREPARE"
|
:title="`确认删除 ${row.code}?`"
|
@confirm="handleDeleteRow(row)"
|
>
|
<Button type="link" size="small" danger>删除</Button>
|
</Popconfirm>
|
<Popconfirm
|
v-if="
|
row.status === MesWmReturnIssueStatusEnum.CONFIRMED ||
|
row.status === MesWmReturnIssueStatusEnum.APPROVING ||
|
row.status === MesWmReturnIssueStatusEnum.APPROVED
|
"
|
title="确认取消?取消后不可恢复"
|
@confirm="handleCancelRow(row)"
|
>
|
<Button type="link" size="small" danger>取消</Button>
|
</Popconfirm>
|
</template>
|
</Grid>
|
</div>
|
|
</template>
|
|
<!-- 编辑弹窗 -->
|
<EditFormModal @success="onEditSuccess" />
|
</div>
|
</template>
|
|
<style lang="scss" scoped>
|
.issue-tab {
|
padding: 20px 24px;
|
height: 100%;
|
display: flex;
|
flex-direction: column;
|
overflow: hidden;
|
}
|
|
.issue-tab__header {
|
display: flex;
|
align-items: center;
|
justify-content: space-between;
|
gap: 12px;
|
}
|
|
.issue-tab__header-left {
|
display: flex;
|
align-items: center;
|
gap: 8px;
|
}
|
|
.issue-tab__icon {
|
font-size: 22px;
|
color: #fa8c16;
|
}
|
|
.issue-tab__title {
|
font-size: 17px;
|
font-weight: 600;
|
color: #1a1a1a;
|
}
|
|
.issue-tab__header-actions {
|
display: flex;
|
gap: 8px;
|
}
|
|
.issue-tab__form {
|
margin-top: 16px;
|
padding: 16px;
|
background: #fafbfc;
|
border-radius: 10px;
|
border: 1px solid #f0f0f0;
|
}
|
|
.issue-tab__list {
|
flex: 1;
|
min-height: 0;
|
overflow: hidden;
|
}
|
|
.issue-tab__empty {
|
flex: 1;
|
display: flex;
|
align-items: center;
|
justify-content: center;
|
}
|
</style>
|