<script lang="ts" setup>
|
import type { VbenFormSchema } from '#/adapter/form';
|
import type { VxeTableGridOptions } from '#/adapter/vxe-table';
|
import type { MesWmProductIssueApi } from '#/api/mes/wm/productissue';
|
|
import { h, markRaw, onBeforeUnmount, onMounted, ref, watch, nextTick } from 'vue';
|
|
import {
|
DICT_TYPE,
|
MesAutoCodeRuleCode,
|
MesProWorkOrderStatusEnum,
|
MesWmProductIssueStatusEnum,
|
} from '@vben/constants';
|
import { IconifyIcon } from '@vben/icons';
|
import { getDictOptions } from '@vben/hooks';
|
|
import { Button, Divider, message, Popconfirm, Tag } from 'ant-design-vue';
|
|
import { useVbenForm } from '#/adapter/form';
|
import { useVbenVxeGrid } from '#/adapter/vxe-table';
|
import { generateAutoCode } from '#/api/mes/md/autocode/record';
|
import {
|
cancelProductIssue,
|
createProductIssue,
|
deleteProductIssue,
|
getProductIssuePage,
|
submitProductIssue,
|
updateProductIssue,
|
} from '#/api/mes/wm/productissue';
|
import { MdWorkstationSelect } from '#/views/mes/md/workstation/components';
|
import { ProWorkOrderSelect } from '#/views/mes/pro/workorder/components';
|
import { $t } from '#/locales';
|
|
defineOptions({ name: 'WorkbenchProductIssueTab' });
|
|
const props = defineProps<{
|
/** 当前工作站 ID */
|
workstationId?: number;
|
/** 当前工作站名称 */
|
workstationName?: string;
|
}>();
|
|
// --- 表单状态 ---
|
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_PRODUCT_ISSUE_CODE,
|
);
|
await formApi.setFieldValue('code', code);
|
},
|
},
|
{ default: () => '生成' },
|
),
|
},
|
{
|
fieldName: 'name',
|
label: '领料单名称',
|
component: 'Input',
|
componentProps: { placeholder: '请输入领料单名称' },
|
rules: 'required',
|
},
|
{
|
fieldName: 'workOrderId',
|
label: '生产订单',
|
component: markRaw(ProWorkOrderSelect),
|
componentProps: {
|
status: MesProWorkOrderStatusEnum.CONFIRMED,
|
},
|
rules: 'selectRequired',
|
},
|
{
|
fieldName: 'workstationId',
|
label: '工作站',
|
component: markRaw(MdWorkstationSelect),
|
componentProps: { placeholder: '请选择工作站' },
|
},
|
{
|
fieldName: 'requiredTime',
|
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.workstationId) {
|
await formApi.setFieldValue('workstationId', props.workstationId);
|
}
|
}
|
|
/** 保存领料单 */
|
async function handleSave() {
|
const { valid } = await formApi.validate();
|
if (!valid) return;
|
const data =
|
(await formApi.getValues()) as MesWmProductIssueApi.ProductIssue;
|
try {
|
if (editingId.value) {
|
await updateProductIssue({ ...data, id: editingId.value });
|
message.success('修改成功');
|
} else {
|
const id = await createProductIssue(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();
|
}
|
|
/** 提交 */
|
async function handleSubmitRow(row: MesWmProductIssueApi.ProductIssue) {
|
await submitProductIssue(row.id!);
|
message.success('提交成功');
|
gridApi.query();
|
}
|
|
/** 删除 */
|
async function handleDeleteRow(row: MesWmProductIssueApi.ProductIssue) {
|
await deleteProductIssue(row.id!);
|
message.success('删除成功');
|
gridApi.query();
|
}
|
|
/** 取消 */
|
async function handleCancelRow(row: MesWmProductIssueApi.ProductIssue) {
|
await cancelProductIssue(row.id!);
|
message.success('取消成功');
|
gridApi.query();
|
}
|
|
// --- 列表 ---
|
const gridColumns: VxeTableGridOptions<MesWmProductIssueApi.ProductIssue>['columns'] = [
|
{ field: 'code', title: '领料单编号', minWidth: 150 },
|
{ field: 'name', title: '领料单名称', minWidth: 130 },
|
{ field: 'workOrderCode', title: '生产订单', minWidth: 130 },
|
{
|
field: 'requiredTime',
|
title: '需求时间',
|
width: 170,
|
formatter: 'formatDateTime',
|
},
|
{
|
field: 'status',
|
title: '状态',
|
width: 100,
|
cellRender: {
|
name: 'CellDict',
|
props: { type: DICT_TYPE.MES_WM_PRODUCT_ISSUE_STATUS },
|
},
|
},
|
{ title: '操作', width: 200, 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 getProductIssuePage({
|
pageNo: page.currentPage,
|
pageSize: page.pageSize,
|
workstationId: props.workstationId,
|
...formValues,
|
});
|
},
|
},
|
},
|
rowConfig: { keyField: 'id', isHover: true },
|
toolbarConfig: { refresh: true },
|
pagerConfig: { pageSize: 10 },
|
} as VxeTableGridOptions<MesWmProductIssueApi.ProductIssue>,
|
});
|
|
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.workstationId,
|
() => gridApi.query(),
|
);
|
</script>
|
|
<template>
|
<div class="issue-tab">
|
<!-- 顶部:表单 / 新建按钮 -->
|
<div class="issue-tab__header">
|
<div class="issue-tab__header-left">
|
<IconifyIcon icon="lucide:clipboard-list" class="issue-tab__icon" />
|
<span class="issue-tab__title">领料单</span>
|
<Tag v-if="isCreating" color="blue">新建中</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 }">
|
<Popconfirm
|
v-if="row.status === MesWmProductIssueStatusEnum.PREPARE"
|
title="确认提交?提交后不能修改"
|
@confirm="handleSubmitRow(row)"
|
>
|
<Button type="link" size="small">提交</Button>
|
</Popconfirm>
|
<Popconfirm
|
v-if="row.status === MesWmProductIssueStatusEnum.PREPARE"
|
:title="`确认删除 ${row.code}?`"
|
@confirm="handleDeleteRow(row)"
|
>
|
<Button type="link" size="small" danger>删除</Button>
|
</Popconfirm>
|
<Popconfirm
|
v-if="
|
row.status === MesWmProductIssueStatusEnum.APPROVING ||
|
row.status === MesWmProductIssueStatusEnum.APPROVED
|
"
|
title="确认取消?取消后不可恢复"
|
@confirm="handleCancelRow(row)"
|
>
|
<Button type="link" size="small" danger>取消</Button>
|
</Popconfirm>
|
</template>
|
</Grid>
|
</div>
|
</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: #1890ff;
|
}
|
|
.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;
|
}
|
</style>
|