<script lang="ts" setup>
|
import type { FormType } from "../data";
|
|
import type { VxeTableGridOptions } from "#/adapter/vxe-table";
|
import type { MesProBatchApi } from "#/api/mes/pro/batch";
|
import type { SystemUserApi } from "#/api/system/user";
|
|
import { computed, nextTick, ref } from "vue";
|
|
import { useVbenModal } from "@vben/common-ui";
|
import { DICT_TYPE } from "@vben/constants";
|
import { getDictOptions } from "@vben/hooks";
|
|
import { Button, Input, message, Select } from "ant-design-vue";
|
|
import { useVbenForm } from "#/adapter/form";
|
import { TableAction, useVbenVxeGrid } from "#/adapter/vxe-table";
|
import { createBatch, getBatch, updateBatch } from "#/api/mes/pro/batch";
|
import { $t } from "#/locales";
|
import { UserSelect } from "#/views/system/user/components";
|
|
import {
|
useFormSchema,
|
useWorkerEditGridColumns,
|
useWorkerReadGridColumns,
|
} from "../data";
|
|
defineOptions({ name: "MesProBatchForm" });
|
|
const emit = defineEmits(["success"]);
|
|
const formType = ref<FormType>("create");
|
const formData = ref<MesProBatchApi.Batch>();
|
|
const isEditable = computed(() =>
|
["create", "update"].includes(formType.value)
|
);
|
|
const getTitle = computed(() => {
|
switch (formType.value) {
|
case "detail": {
|
return $t("ui.actionTitle.view", ["生产批次"]);
|
}
|
case "update": {
|
return $t("ui.actionTitle.edit", ["生产批次"]);
|
}
|
default: {
|
return $t("ui.actionTitle.create", ["生产批次"]);
|
}
|
}
|
});
|
|
const [Form, formApi] = useVbenForm({
|
commonConfig: {
|
componentProps: {
|
class: "w-full",
|
},
|
formItemClass: "col-span-1",
|
labelWidth: 110,
|
},
|
layout: "horizontal",
|
schema: [],
|
showDefaultActions: false,
|
wrapperClass: "grid-cols-3",
|
});
|
|
// ========== 生产人员配置子表 ==========
|
const [WorkerGrid, workerGridApi] = useVbenVxeGrid({
|
gridOptions: {
|
autoResize: true,
|
border: true,
|
columns: useWorkerEditGridColumns(),
|
minHeight: 160,
|
pagerConfig: { enabled: false },
|
rowConfig: { isHover: true, keyField: "_rowKey" },
|
showOverflow: true,
|
toolbarConfig: { enabled: false },
|
} as VxeTableGridOptions<MesProBatchApi.BatchWorker>,
|
});
|
|
/** 新增行的临时行号(负数自增,避免与 loadData 的 index 冲突) */
|
let workerRowSeq = 0;
|
|
/** 添加人员行 */
|
function handleAddWorker() {
|
workerGridApi.grid.insertAt({ _rowKey: -(++workerRowSeq) }, -1);
|
}
|
|
/** 删除人员行 */
|
function handleDeleteWorker(row: MesProBatchApi.BatchWorker) {
|
workerGridApi.grid.remove(row);
|
}
|
|
/** 人员选择编号变化(单选场景下取第一个值) */
|
function handleWorkerUserIdChange(
|
value: number | number[] | undefined,
|
row: MesProBatchApi.BatchWorker
|
) {
|
row.userId = Array.isArray(value) ? value[0] : value;
|
}
|
|
/** 人员选择变化时记录姓名(便于校验提示) */
|
function handleWorkerUserChange(
|
user: SystemUserApi.User | SystemUserApi.User[] | undefined,
|
row: MesProBatchApi.BatchWorker
|
) {
|
row.userName = Array.isArray(user) ? user[0]?.nickname : user?.nickname;
|
}
|
|
/** 岗位类型字典选项 */
|
const workerTypeOptions = computed(() =>
|
getDictOptions(DICT_TYPE.MES_PRO_BATCH_WORKER_TYPE, "number").map(
|
(item) => ({ label: item.label, value: item.value as number })
|
)
|
);
|
|
/** 校验并获取人员配置列表 */
|
async function getValidWorkers(): Promise<
|
MesProBatchApi.BatchWorker[] | undefined
|
> {
|
// getData() 只返回 loadData 加载的数据;insertAt 插入的行需通过
|
// getInsertRecords() 获取,并排除 getRemoveRecords() 中已删除的行
|
const data = workerGridApi.grid.getData() as Array<
|
MesProBatchApi.BatchWorker & { _rowKey?: number }
|
>;
|
const removeRecords = workerGridApi.grid.getRemoveRecords() as Array<
|
MesProBatchApi.BatchWorker & { _rowKey?: number }
|
>;
|
const insertRecords = workerGridApi.grid.getInsertRecords() as Array<
|
MesProBatchApi.BatchWorker & { _rowKey?: number }
|
>;
|
const rows = [
|
...data.filter(
|
(row) => !removeRecords.some((removed) => removed._rowKey === row._rowKey)
|
),
|
...insertRecords,
|
].map(({ _rowKey, ...rest }) => rest);
|
if (rows.length === 0) {
|
message.warning("请至少配置一名生产人员");
|
return undefined;
|
}
|
for (const [index, row] of rows.entries()) {
|
if (!row.userId) {
|
message.warning(`请选择第 ${index + 1} 行的生产人员`);
|
return undefined;
|
}
|
if (!row.workerType) {
|
message.warning(`请选择第 ${index + 1} 行的岗位类型`);
|
return undefined;
|
}
|
}
|
return rows;
|
}
|
|
/** 等待人员子表 grid 挂载完成(弹窗打开瞬间 onOpenChange 内部表格可能尚未挂载) */
|
async function waitWorkerGridReady(): Promise<void> {
|
await nextTick();
|
for (let i = 0; i < 100; i++) {
|
if (typeof workerGridApi.grid?.loadData === "function") {
|
return;
|
}
|
await new Promise((resolve) => setTimeout(resolve, 30));
|
}
|
}
|
|
const [Modal, modalApi] = useVbenModal({
|
showCancelButton: false, // 自定义取消/关闭按钮,禁用默认取消按钮避免重复
|
async onConfirm() {
|
if (!isEditable.value) {
|
await modalApi.close();
|
return;
|
}
|
const { valid } = await formApi.validate();
|
if (!valid) return;
|
const workers = await getValidWorkers();
|
if (!workers) return;
|
modalApi.lock();
|
try {
|
const data = (await formApi.getValues()) as MesProBatchApi.Batch;
|
if (formData.value?.id) {
|
await updateBatch({ ...data, id: formData.value.id, workers });
|
} else {
|
await createBatch({ ...data, workers });
|
}
|
message.success($t("ui.actionMessage.operationSuccess"));
|
emit("success");
|
await modalApi.close();
|
} finally {
|
modalApi.unlock();
|
}
|
},
|
async onOpenChange(isOpen: boolean) {
|
if (!isOpen) {
|
formData.value = undefined;
|
return;
|
}
|
const data = modalApi.getData<{ formType: FormType; id?: number }>();
|
formType.value = data.formType;
|
formApi.setState({ schema: useFormSchema(formType.value) });
|
// 根据表单态切换人员子表列
|
workerGridApi.setGridOptions({
|
columns: isEditable.value
|
? useWorkerEditGridColumns()
|
: useWorkerReadGridColumns(),
|
});
|
if (data?.id) {
|
modalApi.lock();
|
try {
|
formData.value = await getBatch(data.id);
|
await formApi.setValues(formData.value);
|
const workers = (formData.value.workers ?? []).map(
|
(worker, index) => ({ ...worker, _rowKey: worker.id ?? index })
|
);
|
await waitWorkerGridReady();
|
await workerGridApi.grid.loadData(workers);
|
} finally {
|
modalApi.unlock();
|
}
|
} else {
|
await waitWorkerGridReady();
|
await workerGridApi.grid.loadData([]);
|
}
|
},
|
});
|
</script>
|
|
<template>
|
<Modal :title="getTitle"
|
class="w-3/5"
|
content-class="!max-h-[80vh] overflow-y-auto">
|
<Form class="mx-4" />
|
<div class="mx-4 mb-4">
|
<div class="mb-2 flex items-center justify-between">
|
<span class="font-medium">生产人员配置</span>
|
<Button v-if="isEditable" size="small" type="primary" @click="handleAddWorker">
|
添加人员
|
</Button>
|
</div>
|
<WorkerGrid>
|
<template #userId="{ row }">
|
<UserSelect
|
:model-value="row.userId"
|
placeholder="请选择生产人员"
|
@update:model-value="handleWorkerUserIdChange($event, row)"
|
@change="handleWorkerUserChange($event, row)"
|
/>
|
</template>
|
<template #workerType="{ row }">
|
<Select
|
v-model:value="row.workerType"
|
:options="workerTypeOptions"
|
class="w-full"
|
placeholder="请选择岗位类型"
|
/>
|
</template>
|
<template #remark="{ row }">
|
<Input v-model:value="row.remark" placeholder="请输入备注" />
|
</template>
|
<template #actions="{ row }">
|
<TableAction
|
:actions="[
|
{
|
label: $t('common.delete'),
|
type: 'link',
|
danger: true,
|
onClick: handleDeleteWorker.bind(null, row),
|
},
|
]"
|
/>
|
</template>
|
</WorkerGrid>
|
</div>
|
<template #append-footer>
|
<div class="flex gap-2">
|
<Button @click="modalApi.close()">
|
{{ isEditable ? '取消' : '关闭' }}
|
</Button>
|
</div>
|
</template>
|
</Modal>
|
</template>
|