From f35825c1922149df154e3913d6dfebee57ee8cee Mon Sep 17 00:00:00 2001
From: zhangwencui <1064582902@qq.com>
Date: 星期四, 16 七月 2026 15:52:40 +0800
Subject: [PATCH] 部分功能修改
---
src/views/mes/workbench/index.vue | 8
src/views/mes/workbench/components/tabs/ProductIssueFormModal.vue | 346 ++++++++++++++
src/views/mes/workbench/components/tabs/ReturnIssueLineFormModal.vue | 160 ++++++
src/views/mes/workbench/components/tabs/ProductIssueLineFormModal.vue | 113 ++++
src/views/mes/md/item/index.vue | 7
src/views/mes/workbench/components/tabs/ReturnIssueFormModal.vue | 327 +++++++++++++
src/views/mes/workbench/components/tabs/ReturnIssueTab.vue | 214 ++++++--
src/api/mes/wm/productissue/index.ts | 3
src/views/basicData/mdm/data.ts | 27 +
src/views/basicData/mdm/modules/form.vue | 4
src/api/mes/wm/returnissue/index.ts | 3
src/views/mes/workbench/components/tabs/ProductIssueTab.vue | 214 ++++++--
12 files changed, 1,280 insertions(+), 146 deletions(-)
diff --git a/src/api/mes/wm/productissue/index.ts b/src/api/mes/wm/productissue/index.ts
index 00cec5e..d17e086 100644
--- a/src/api/mes/wm/productissue/index.ts
+++ b/src/api/mes/wm/productissue/index.ts
@@ -8,6 +8,9 @@
id?: number; // 棰嗘枡鍗曠紪鍙�
code?: string; // 棰嗘枡鍗曠紪鍙�
name?: string; // 棰嗘枡鍗曞悕绉�
+ taskId?: number; // 鐢熶骇浠诲姟 ID
+ taskCode?: string; // 鐢熶骇浠诲姟缂栫爜
+ taskName?: string; // 鐢熶骇浠诲姟鍚嶇О
workstationId?: number; // 宸ヤ綔绔欑紪鍙�
workstationCode?: string; // 宸ヤ綔绔欑紪鐮�
workstationName?: string; // 宸ヤ綔绔欏悕绉�
diff --git a/src/api/mes/wm/returnissue/index.ts b/src/api/mes/wm/returnissue/index.ts
index f1d5d02..cbb91bc 100644
--- a/src/api/mes/wm/returnissue/index.ts
+++ b/src/api/mes/wm/returnissue/index.ts
@@ -8,6 +8,9 @@
id?: number; // 閫�鏂欏崟缂栧彿
code?: string; // 閫�鏂欏崟缂栧彿
name?: string; // 閫�鏂欏崟鍚嶇О
+ taskId?: number; // 鐢熶骇浠诲姟 ID
+ taskCode?: string; // 鐢熶骇浠诲姟缂栫爜
+ taskName?: string; // 鐢熶骇浠诲姟鍚嶇О
workstationId?: number; // 宸ヤ綔绔欑紪鍙�
workstationName?: string; // 宸ヤ綔绔欏悕绉�
workOrderId?: number; // 鐢熶骇璁㈠崟缂栧彿
diff --git a/src/views/basicData/mdm/data.ts b/src/views/basicData/mdm/data.ts
index bbde65a..3ff52a2 100644
--- a/src/views/basicData/mdm/data.ts
+++ b/src/views/basicData/mdm/data.ts
@@ -1,12 +1,18 @@
-import type { VbenFormSchema } from "#/adapter/form";
+import type { VbenFormApi, VbenFormSchema } from "#/adapter/form";
import type { VxeTableGridOptions } from "#/adapter/vxe-table";
+
+import { h } from 'vue';
import { CommonStatusEnum, DICT_TYPE } from "#/packages/constants/src";
import { getDictOptions } from "#/packages/effects/hooks/src";
import { z } from '#/adapter/form';
+import { generateAutoCode } from '#/api/mes/md/autocode/record';
+import { MesAutoCodeRuleCode } from '@vben/constants';
import { getItemTypeSimpleList } from '#/api/mes/md/item/type';
import { getUnitPage } from '#/api/mdm/unit';
+
+import { Button } from 'ant-design-vue';
// 缂撳瓨鍗曚綅鍒楄〃
let unitListCache: any[] | null = null;
@@ -30,7 +36,7 @@
}
/** 鏂板/淇敼鐨勮〃鍗� */
-export function useFormSchema(): VbenFormSchema[] {
+export function useFormSchema(formApi?: VbenFormApi): VbenFormSchema[] {
return [
{
component: "Input",
@@ -45,9 +51,24 @@
fieldName: "code",
label: "鐗╂枡缂栫爜",
componentProps: {
+ maxLength: 64,
placeholder: "璇疯緭鍏ョ墿鏂欑紪鐮�",
},
- rules: "required",
+ rules: z.string().min(1, '鐗╂枡缂栫爜涓嶈兘涓虹┖').max(64),
+ suffix: () =>
+ h(
+ Button,
+ {
+ type: 'default',
+ onClick: async () => {
+ const code = await generateAutoCode(
+ MesAutoCodeRuleCode.MD_ITEM_CODE,
+ );
+ await formApi?.setFieldValue('code', code);
+ },
+ },
+ { default: () => '鐢熸垚' },
+ ),
},
{
component: "Input",
diff --git a/src/views/basicData/mdm/modules/form.vue b/src/views/basicData/mdm/modules/form.vue
index 7cf30a7..73da21f 100644
--- a/src/views/basicData/mdm/modules/form.vue
+++ b/src/views/basicData/mdm/modules/form.vue
@@ -26,7 +26,7 @@
labelWidth: 110,
},
layout: 'horizontal',
- schema: useFormSchema(),
+ schema: [],
showDefaultActions: false,
wrapperClass: 'grid-cols-2',
});
@@ -54,6 +54,8 @@
return;
}
const data = modalApi.getData<MdmItemApi.Item>();
+ // 璁剧疆 schema锛坒ormApi 宸插垵濮嬪寲锛�
+ formApi.setState({ schema: useFormSchema(formApi) });
if (data?.id) {
// 缂栬緫妯″紡
modalApi.lock();
diff --git a/src/views/mes/md/item/index.vue b/src/views/mes/md/item/index.vue
index 33a0829..66c6421 100644
--- a/src/views/mes/md/item/index.vue
+++ b/src/views/mes/md/item/index.vue
@@ -160,13 +160,6 @@
<template #toolbar-tools>
<TableAction :actions="[
{
- label: $t('ui.actionTitle.create', ['鐗╂枡']),
- type: 'primary',
- icon: ACTION_ICON.ADD,
- auth: ['mes:md-item:create'],
- onClick: handleCreate,
- },
- {
label: $t('ui.actionTitle.import'),
type: 'primary',
icon: ACTION_ICON.UPLOAD,
diff --git a/src/views/mes/workbench/components/tabs/ProductIssueFormModal.vue b/src/views/mes/workbench/components/tabs/ProductIssueFormModal.vue
new file mode 100644
index 0000000..97235e4
--- /dev/null
+++ b/src/views/mes/workbench/components/tabs/ProductIssueFormModal.vue
@@ -0,0 +1,346 @@
+<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 type { MesWmProductIssueLineApi } from '#/api/mes/wm/productissue/line';
+
+import { computed, h, markRaw, ref } from 'vue';
+
+import {
+ DICT_TYPE,
+ MesAutoCodeRuleCode,
+ MesProWorkOrderStatusEnum,
+ MesWmProductIssueStatusEnum,
+} from '@vben/constants';
+
+import { Button, Divider, message, Popconfirm } from 'ant-design-vue';
+
+import { useVbenForm } from '#/adapter/form';
+import { confirm, useVbenModal } from '@vben/common-ui';
+import { ACTION_ICON, TableAction, useVbenVxeGrid } from '#/adapter/vxe-table';
+import { generateAutoCode } from '#/api/mes/md/autocode/record';
+import {
+ checkProductIssueQuantity,
+ getProductIssue,
+ stockProductIssue,
+ submitProductIssue,
+ updateProductIssue,
+} from '#/api/mes/wm/productissue';
+import {
+ deleteProductIssueLine,
+ getProductIssueLinePage,
+} from '#/api/mes/wm/productissue/line';
+import { MdWorkstationSelect } from '#/views/mes/md/workstation/components';
+import { ProWorkOrderSelect } from '#/views/mes/pro/workorder/components';
+
+import ProductIssueLineFormModal from './ProductIssueLineFormModal.vue';
+
+const emit = defineEmits(['success']);
+const formData = ref<MesWmProductIssueApi.ProductIssue>();
+
+const canSubmit = computed(
+ () => formData.value?.status === MesWmProductIssueStatusEnum.PREPARE,
+);
+
+/** 鏄惁鍙墽琛屾嫞璐э紙瀹℃壒涓姸鎬侊級 */
+const canStock = computed(
+ () => formData.value?.status === MesWmProductIssueStatusEnum.APPROVING,
+);
+
+/** 鏋勫缓缂栬緫琛ㄥ崟 schema */
+function buildFormSchema(): VbenFormSchema[] {
+ return [
+ {
+ fieldName: 'id',
+ component: 'Input',
+ dependencies: { triggerFields: [''], show: () => false },
+ },
+ {
+ fieldName: 'status',
+ component: 'Input',
+ dependencies: { triggerFields: [''], show: () => false },
+ },
+ {
+ 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: 'requiredTime',
+ label: '闇�姹傛椂闂�',
+ component: 'DatePicker',
+ componentProps: {
+ class: '!w-full',
+ format: 'YYYY-MM-DD HH:mm:ss',
+ placeholder: '璇烽�夋嫨闇�姹傛椂闂�',
+ showTime: true,
+ valueFormat: 'x',
+ },
+ },
+ {
+ fieldName: 'workOrderId',
+ label: '鐢熶骇璁㈠崟',
+ component: markRaw(ProWorkOrderSelect),
+ componentProps: {
+ status: MesProWorkOrderStatusEnum.CONFIRMED,
+ },
+ },
+ {
+ fieldName: 'workstationId',
+ label: '宸ヤ綔绔�',
+ component: markRaw(MdWorkstationSelect),
+ componentProps: { placeholder: '璇烽�夋嫨宸ヤ綔绔�' },
+ },
+ {
+ fieldName: 'remark',
+ label: '澶囨敞',
+ component: 'Textarea',
+ formItemClass: 'col-span-3',
+ 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',
+});
+
+// --- 鐗╂枡琛屽垪琛� ---
+const lineGridColumns: VxeTableGridOptions<MesWmProductIssueLineApi.ProductIssueLine>['columns'] = [
+ { field: 'itemCode', title: '鐗╂枡缂栫爜', minWidth: 120 },
+ { field: 'itemName', title: '鐗╂枡鍚嶇О', minWidth: 140 },
+ { field: 'specification', title: '瑙勬牸鍨嬪彿', minWidth: 120 },
+ { field: 'unitMeasureName', title: '鍗曚綅', width: 80 },
+ { field: 'quantity', title: '棰嗘枡鏁伴噺', width: 100 },
+ { title: '鎿嶄綔', width: 140, fixed: 'right', slots: { default: 'lineActions' } },
+];
+
+const [LineGrid, lineGridApi] = useVbenVxeGrid({
+ gridOptions: {
+ columns: lineGridColumns,
+ height: 260,
+ keepSource: true,
+ proxyConfig: {
+ ajax: {
+ query: async ({ page }) => {
+ if (!formData.value?.id) return { list: [], total: 0 };
+ return await getProductIssueLinePage({
+ issueId: formData.value.id,
+ pageNo: page.currentPage,
+ pageSize: page.pageSize,
+ });
+ },
+ },
+ },
+ rowConfig: { keyField: 'id', isHover: true },
+ toolbarConfig: { refresh: true },
+ pagerConfig: { pageSize: 10 },
+ } as VxeTableGridOptions<MesWmProductIssueLineApi.ProductIssueLine>,
+});
+
+/** 娣诲姞鐗╂枡琛� */
+function handleCreateLine() {
+ lineFormModalApi.setData({ issueId: formData.value?.id }).open();
+}
+
+/** 缂栬緫鐗╂枡琛� */
+function handleEditLine(row: MesWmProductIssueLineApi.ProductIssueLine) {
+ lineFormModalApi.setData({ id: row.id, issueId: formData.value?.id }).open();
+}
+
+/** 鍒犻櫎鐗╂枡琛� */
+async function handleDeleteLine(row: MesWmProductIssueLineApi.ProductIssueLine) {
+ try {
+ await deleteProductIssueLine(row.id!);
+ message.success('鍒犻櫎鎴愬姛');
+ lineGridApi.query();
+ } catch {
+ message.error('鍒犻櫎澶辫触');
+ }
+}
+
+/** 鐗╂枡琛屾搷浣滄垚鍔� */
+function onLineSuccess() {
+ lineGridApi.query();
+}
+
+const [LineFormModal, lineFormModalApi] = useVbenModal({
+ connectedComponent: ProductIssueLineFormModal,
+ destroyOnClose: true,
+});
+
+/** 鎻愪氦棰嗘枡鍗� */
+async function handleSubmit() {
+ if (!formData.value?.id) return;
+ modalApi.lock();
+ try {
+ // 鍏堜繚瀛樿〃鍗�
+ const data =
+ (await formApi.getValues()) as MesWmProductIssueApi.ProductIssue;
+ await updateProductIssue({ ...formData.value, ...data });
+ // 鍐嶆彁浜�
+ await submitProductIssue(formData.value.id);
+ message.success('鎻愪氦鎴愬姛');
+ await modalApi.close();
+ emit('success');
+ } finally {
+ modalApi.unlock();
+ }
+}
+
+/** 鎵ц鎷h揣锛氶鏂欐暟閲忎笌鎷h揣鏁伴噺涓嶄竴鑷存椂浜屾纭 */
+async function handleStock() {
+ if (!formData.value?.id) return;
+ const quantityMatch = await checkProductIssueQuantity(formData.value.id);
+ if (!quantityMatch) {
+ try {
+ await confirm('棰嗘枡鏁伴噺涓庢嫞璐ф暟閲忎笉涓�鑷达紝纭鎵ц鎷h揣锛�');
+ } catch {
+ return;
+ }
+ }
+ modalApi.lock();
+ try {
+ await stockProductIssue(formData.value.id);
+ message.success('鎷h揣鎴愬姛');
+ await modalApi.close();
+ emit('success');
+ } finally {
+ modalApi.unlock();
+ }
+}
+
+const [Modal, modalApi] = useVbenModal({
+ async onConfirm() {
+ const { valid } = await formApi.validate();
+ if (!valid) return;
+ modalApi.lock();
+ try {
+ const data =
+ (await formApi.getValues()) as MesWmProductIssueApi.ProductIssue;
+ await updateProductIssue({ ...formData.value, ...data });
+ formData.value = { ...formData.value, ...data };
+ emit('success');
+ message.success('淇濆瓨鎴愬姛');
+ await modalApi.close();
+ } finally {
+ modalApi.unlock();
+ }
+ },
+ async onOpenChange(isOpen: boolean) {
+ if (!isOpen) {
+ formData.value = undefined;
+ return;
+ }
+ const data = modalApi.getData<{ id: number }>();
+ if (data?.id) {
+ modalApi.lock();
+ try {
+ formData.value = await getProductIssue(data.id);
+ await formApi.setValues(formData.value);
+ // 鍔犺浇鍚庡埛鏂扮墿鏂欒
+ lineGridApi.query();
+ } finally {
+ modalApi.unlock();
+ }
+ }
+ },
+});
+</script>
+
+<template>
+ <Modal title="缂栬緫棰嗘枡鍗�" class="w-3/5">
+ <Form class="mx-4" />
+ <!-- 鐗╂枡淇℃伅 -->
+ <template v-if="formData?.id">
+ <Divider style="margin: 12px 0">
+ <span style="font-weight: 600; color: #1890ff;">鐗╂枡淇℃伅</span>
+ </Divider>
+ <div class="mx-4" style="height: 300px">
+ <LineGrid table-title="鐗╂枡鍒楄〃">
+ <template #toolbar-tools>
+ <TableAction
+ :actions="[
+ {
+ label: '娣诲姞鐗╂枡',
+ type: 'primary',
+ icon: ACTION_ICON.ADD,
+ onClick: handleCreateLine,
+ },
+ ]"
+ />
+ </template>
+ <template #lineActions="{ row }">
+ <TableAction
+ :actions="[
+ {
+ label: '缂栬緫',
+ type: 'link',
+ icon: ACTION_ICON.EDIT,
+ onClick: handleEditLine.bind(null, row),
+ },
+ {
+ label: '鍒犻櫎',
+ type: 'link',
+ danger: true,
+ icon: ACTION_ICON.DELETE,
+ popConfirm: {
+ title: `纭鍒犻櫎鐗╂枡 ${row.itemName}锛焋,
+ confirm: handleDeleteLine.bind(null, row),
+ },
+ },
+ ]"
+ />
+ </template>
+ </LineGrid>
+ </div>
+ </template>
+ <template #prepend-footer>
+ <div class="flex flex-auto items-center gap-2">
+ <Popconfirm
+ v-if="canSubmit"
+ title="纭鎻愪氦璇ラ鏂欏崟锛熴�愭彁浜ゅ悗灏嗕笉鑳戒慨鏀广��"
+ @confirm="handleSubmit"
+ >
+ <Button type="primary">鎻愪氦</Button>
+ </Popconfirm>
+ <Button v-if="canStock" type="primary" @click="handleStock">
+ 鎵ц鎷h揣
+ </Button>
+ </div>
+ </template>
+ </Modal>
+ <LineFormModal @success="onLineSuccess" />
+</template>
diff --git a/src/views/mes/workbench/components/tabs/ProductIssueLineFormModal.vue b/src/views/mes/workbench/components/tabs/ProductIssueLineFormModal.vue
new file mode 100644
index 0000000..78f182f
--- /dev/null
+++ b/src/views/mes/workbench/components/tabs/ProductIssueLineFormModal.vue
@@ -0,0 +1,113 @@
+<script lang="ts" setup>
+import type { VbenFormSchema } from '#/adapter/form';
+import type { MesWmProductIssueLineApi } from '#/api/mes/wm/productissue/line';
+
+import { computed, markRaw, ref } from 'vue';
+
+import { useVbenModal } from '@vben/common-ui';
+
+import { message } from 'ant-design-vue';
+
+import { useVbenForm } from '#/adapter/form';
+import {
+ createProductIssueLine,
+ getProductIssueLine,
+ updateProductIssueLine,
+} from '#/api/mes/wm/productissue/line';
+import { MdItemSelect } from '#/views/mes/md/item/components';
+
+const emit = defineEmits(['success']);
+const formData = ref<MesWmProductIssueLineApi.ProductIssueLine>();
+const issueId = ref<number>();
+
+const getTitle = computed(() => {
+ return formData.value?.id ? '缂栬緫鐗╂枡' : '娣诲姞鐗╂枡';
+});
+
+/** 鐗╂枡琛岃〃鍗� schema */
+function buildLineFormSchema(): VbenFormSchema[] {
+ return [
+ {
+ fieldName: 'itemId',
+ label: '浜у搧鐗╂枡',
+ component: markRaw(MdItemSelect),
+ componentProps: { placeholder: '璇烽�夋嫨浜у搧鐗╂枡' },
+ rules: 'selectRequired',
+ },
+ {
+ fieldName: 'quantity',
+ label: '棰嗘枡鏁伴噺',
+ component: 'InputNumber',
+ componentProps: {
+ class: '!w-full',
+ min: 0,
+ placeholder: '璇疯緭鍏ラ鏂欐暟閲�',
+ precision: 2,
+ },
+ rules: 'required',
+ },
+ {
+ fieldName: 'remark',
+ label: '澶囨敞',
+ component: 'Textarea',
+ formItemClass: 'col-span-3',
+ componentProps: { placeholder: '璇疯緭鍏ュ娉�', rows: 2 },
+ },
+ ];
+}
+
+const [Form, formApi] = useVbenForm({
+ commonConfig: { componentProps: { class: 'w-full' }, formItemClass: 'col-span-1', labelWidth: 90 },
+ layout: 'horizontal',
+ schema: buildLineFormSchema(),
+ showDefaultActions: false,
+ wrapperClass: 'grid-cols-3',
+});
+
+const [Modal, modalApi] = useVbenModal({
+ async onConfirm() {
+ const { valid } = await formApi.validate();
+ if (!valid) return;
+ modalApi.lock();
+ const data = (await formApi.getValues()) as MesWmProductIssueLineApi.ProductIssueLine;
+ data.issueId = issueId.value;
+ try {
+ if (formData.value?.id) {
+ await updateProductIssueLine({ ...data, id: formData.value.id });
+ } else {
+ await createProductIssueLine(data);
+ }
+ await modalApi.close();
+ emit('success');
+ message.success('鎿嶄綔鎴愬姛');
+ } finally {
+ modalApi.unlock();
+ }
+ },
+ async onOpenChange(isOpen: boolean) {
+ if (!isOpen) {
+ formData.value = undefined;
+ return;
+ }
+ const data = modalApi.getData<{ id?: number; issueId: number }>();
+ issueId.value = data.issueId;
+ if (!data.id) {
+ await formApi.resetForm();
+ return;
+ }
+ modalApi.lock();
+ try {
+ formData.value = await getProductIssueLine(data.id);
+ await formApi.setValues(formData.value);
+ } finally {
+ modalApi.unlock();
+ }
+ },
+});
+</script>
+
+<template>
+ <Modal :title="getTitle" class="w-3/5">
+ <Form class="mx-4" />
+ </Modal>
+</template>
diff --git a/src/views/mes/workbench/components/tabs/ProductIssueTab.vue b/src/views/mes/workbench/components/tabs/ProductIssueTab.vue
index e59ad60..9731a1b 100644
--- a/src/views/mes/workbench/components/tabs/ProductIssueTab.vue
+++ b/src/views/mes/workbench/components/tabs/ProductIssueTab.vue
@@ -3,7 +3,7 @@
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 { computed, h, markRaw, onBeforeUnmount, onMounted, ref, watch, nextTick } from 'vue';
import {
DICT_TYPE,
@@ -12,11 +12,11 @@
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 { 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 {
@@ -29,7 +29,8 @@
} 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';
+
+import ProductIssueFormModal from './ProductIssueFormModal.vue';
defineOptions({ name: 'WorkbenchProductIssueTab' });
@@ -38,6 +39,14 @@
workstationId?: number;
/** 褰撳墠宸ヤ綔绔欏悕绉� */
workstationName?: string;
+ /** 褰撳墠閫変腑鐨勪换鍔� ID */
+ taskId?: number;
+ /** 褰撳墠閫変腑鐨勪换鍔$紪鐮� */
+ taskCode?: string;
+ /** 褰撳墠閫変腑鐨勪换鍔″悕绉� */
+ taskName?: string;
+ /** 褰撳墠閫変腑鐨勪换鍔″叧鑱斿伐鍗� ID */
+ workOrderId?: number;
}>();
// --- 琛ㄥ崟鐘舵�� ---
@@ -77,19 +86,29 @@
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,
},
- rules: 'selectRequired',
},
{
fieldName: 'workstationId',
label: '宸ヤ綔绔�',
component: markRaw(MdWorkstationSelect),
- componentProps: { placeholder: '璇烽�夋嫨宸ヤ綔绔�' },
+ componentProps: { placeholder: '浠庨�変腑浠诲姟鑷姩甯﹀叆', disabled: true },
},
{
fieldName: 'requiredTime',
@@ -129,7 +148,13 @@
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);
}
@@ -165,6 +190,11 @@
formApi.resetForm();
}
+/** 缂栬緫棰嗘枡鍗曪細鎵撳紑缂栬緫寮圭獥锛堜粎鑽夌鐘舵�佸彲缂栬緫锛� */
+function handleEditRow(row: MesWmProductIssueApi.ProductIssue) {
+ editFormModalApi.setData({ id: row.id }).open();
+}
+
/** 鎻愪氦 */
async function handleSubmitRow(row: MesWmProductIssueApi.ProductIssue) {
await submitProductIssue(row.id!);
@@ -190,6 +220,12 @@
const gridColumns: VxeTableGridOptions<MesWmProductIssueApi.ProductIssue>['columns'] = [
{ field: 'code', title: '棰嗘枡鍗曠紪鍙�', minWidth: 150 },
{ field: 'name', title: '棰嗘枡鍗曞悕绉�', minWidth: 130 },
+ {
+ field: 'taskCode',
+ title: '鐢熶骇浠诲姟',
+ minWidth: 130,
+ formatter: ({ row }) => row.taskCode || props.taskCode || '',
+ },
{ field: 'workOrderCode', title: '鐢熶骇璁㈠崟', minWidth: 130 },
{
field: 'requiredTime',
@@ -206,7 +242,7 @@
props: { type: DICT_TYPE.MES_WM_PRODUCT_ISSUE_STATUS },
},
},
- { title: '鎿嶄綔', width: 200, fixed: 'right', slots: { default: 'actions' } },
+ { title: '鎿嶄綔', width: 260, fixed: 'right', slots: { default: 'actions' } },
];
const gridHeight = ref(300);
@@ -224,7 +260,7 @@
return await getProductIssuePage({
pageNo: page.currentPage,
pageSize: page.pageSize,
- workstationId: props.workstationId,
+ taskId: props.taskId,
...formValues,
});
},
@@ -261,77 +297,112 @@
resizeObserver = null;
});
-// 宸ヤ綔绔欏彉鍖栨椂鍒锋柊鍒楄〃
+// 浠诲姟鎴栧伐浣滅珯鍙樺寲鏃跺埛鏂板垪琛�
watch(
- () => props.workstationId,
+ () => [props.taskId, props.workstationId],
() => gridApi.query(),
);
+
+/** 鏄惁宸查�変腑宸ヤ綔绔欏拰浠诲姟锛屾湭閫変腑鏃朵笉灞曠ず琛ㄦ牸 */
+const isReady = computed(() => !!props.taskId && !!props.workstationId);
+
+/** 缂栬緫寮圭獥 */
+const [EditFormModal, editFormModalApi] = useVbenModal({
+ connectedComponent: ProductIssueFormModal,
+ destroyOnClose: true,
+});
+
+/** 缂栬緫寮圭獥鎿嶄綔鎴愬姛鍚庡埛鏂板垪琛� */
+function onEditSuccess() {
+ 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 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: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 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 v-if="isCreating" class="issue-tab__form">
+ <Form />
</div>
- </div>
- <!-- 琛ㄥ崟鍖哄煙锛堜粎鏂板缓鏃跺睍绀猴級 -->
- <div v-if="isCreating" class="issue-tab__form">
- <Form />
- </div>
+ <Divider style="margin: 12px 0" />
- <Divider style="margin: 12px 0" />
+ <!-- 搴曢儴锛氶鏂欒褰曞垪琛� -->
+ <div ref="listRef" class="issue-tab__list">
+ <Grid table-title="棰嗘枡璁板綍">
+ <template #actions="{ row }">
+ <Button
+ v-if="row.status === MesWmProductIssueStatusEnum.PREPARE"
+ type="link"
+ size="small"
+ @click="handleEditRow(row)"
+ >
+ 缂栬緫
+ </Button>
+ <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 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>
+ </template>
+
+ <!-- 缂栬緫寮圭獥 -->
+ <EditFormModal @success="onEditSuccess" />
</div>
</template>
@@ -386,4 +457,11 @@
min-height: 0;
overflow: hidden;
}
+
+.issue-tab__empty {
+ flex: 1;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+}
</style>
diff --git a/src/views/mes/workbench/components/tabs/ReturnIssueFormModal.vue b/src/views/mes/workbench/components/tabs/ReturnIssueFormModal.vue
new file mode 100644
index 0000000..89453de
--- /dev/null
+++ b/src/views/mes/workbench/components/tabs/ReturnIssueFormModal.vue
@@ -0,0 +1,327 @@
+<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 type { MesWmReturnIssueLineApi } from '#/api/mes/wm/returnissue/line';
+
+import { computed, h, markRaw, ref } from 'vue';
+
+import {
+ DICT_TYPE,
+ MesAutoCodeRuleCode,
+ MesProWorkOrderStatusEnum,
+ MesWmReturnIssueStatusEnum,
+} from '@vben/constants';
+import { getDictOptions } from '@vben/hooks';
+
+import { Button, Divider, message, Popconfirm } from 'ant-design-vue';
+
+import { useVbenForm } from '#/adapter/form';
+import { useVbenModal } from '@vben/common-ui';
+import { ACTION_ICON, TableAction, useVbenVxeGrid } from '#/adapter/vxe-table';
+import { generateAutoCode } from '#/api/mes/md/autocode/record';
+import {
+ getReturnIssue,
+ submitReturnIssue,
+ updateReturnIssue,
+} from '#/api/mes/wm/returnissue';
+import {
+ deleteReturnIssueLine,
+ getReturnIssueLinePage,
+} from '#/api/mes/wm/returnissue/line';
+import { MdWorkstationSelect } from '#/views/mes/md/workstation/components';
+import { ProWorkOrderSelect } from '#/views/mes/pro/workorder/components';
+
+import ReturnIssueLineFormModal from './ReturnIssueLineFormModal.vue';
+
+const emit = defineEmits(['success']);
+const formData = ref<MesWmReturnIssueApi.ReturnIssue>();
+
+const canSubmit = computed(
+ () => formData.value?.status === MesWmReturnIssueStatusEnum.PREPARE,
+);
+
+/** 鏋勫缓缂栬緫琛ㄥ崟 schema */
+function buildFormSchema(): VbenFormSchema[] {
+ return [
+ {
+ fieldName: 'id',
+ component: 'Input',
+ dependencies: { triggerFields: [''], show: () => false },
+ },
+ {
+ fieldName: 'status',
+ component: 'Input',
+ dependencies: { triggerFields: [''], show: () => false },
+ },
+ {
+ 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: 'workOrderId',
+ label: '鐢熶骇璁㈠崟',
+ component: markRaw(ProWorkOrderSelect),
+ componentProps: {
+ status: MesProWorkOrderStatusEnum.CONFIRMED,
+ },
+ },
+ {
+ fieldName: 'workstationId',
+ label: '宸ヤ綔绔�',
+ component: markRaw(MdWorkstationSelect),
+ componentProps: { placeholder: '璇烽�夋嫨宸ヤ綔绔�' },
+ },
+ {
+ 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',
+ formItemClass: 'col-span-3',
+ 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',
+});
+
+// --- 鐗╂枡琛屽垪琛� ---
+const lineGridColumns: VxeTableGridOptions<MesWmReturnIssueLineApi.ReturnIssueLine>['columns'] = [
+ { field: 'itemCode', title: '鐗╂枡缂栫爜', minWidth: 120 },
+ { field: 'itemName', title: '鐗╂枡鍚嶇О', minWidth: 140 },
+ { field: 'specification', title: '瑙勬牸鍨嬪彿', minWidth: 120 },
+ { field: 'unitMeasureName', title: '鍗曚綅', width: 80 },
+ { field: 'quantity', title: '閫�鏂欐暟閲�', width: 100 },
+ { field: 'batchCode', title: '鎵规鍙�', minWidth: 120 },
+ { title: '鎿嶄綔', width: 140, fixed: 'right', slots: { default: 'lineActions' } },
+];
+
+const [LineGrid, lineGridApi] = useVbenVxeGrid({
+ gridOptions: {
+ columns: lineGridColumns,
+ height: 260,
+ keepSource: true,
+ proxyConfig: {
+ ajax: {
+ query: async ({ page }) => {
+ if (!formData.value?.id) return { list: [], total: 0 };
+ return await getReturnIssueLinePage({
+ issueId: formData.value.id,
+ pageNo: page.currentPage,
+ pageSize: page.pageSize,
+ });
+ },
+ },
+ },
+ rowConfig: { keyField: 'id', isHover: true },
+ toolbarConfig: { refresh: true },
+ pagerConfig: { pageSize: 10 },
+ } as VxeTableGridOptions<MesWmReturnIssueLineApi.ReturnIssueLine>,
+});
+
+/** 娣诲姞鐗╂枡琛� */
+function handleCreateLine() {
+ lineFormModalApi.setData({ issueId: formData.value?.id }).open();
+}
+
+/** 缂栬緫鐗╂枡琛� */
+function handleEditLine(row: MesWmReturnIssueLineApi.ReturnIssueLine) {
+ lineFormModalApi.setData({ id: row.id, issueId: formData.value?.id }).open();
+}
+
+/** 鍒犻櫎鐗╂枡琛� */
+async function handleDeleteLine(row: MesWmReturnIssueLineApi.ReturnIssueLine) {
+ try {
+ await deleteReturnIssueLine(row.id!);
+ message.success('鍒犻櫎鎴愬姛');
+ lineGridApi.query();
+ } catch {
+ message.error('鍒犻櫎澶辫触');
+ }
+}
+
+/** 鐗╂枡琛屾搷浣滄垚鍔� */
+function onLineSuccess() {
+ lineGridApi.query();
+}
+
+const [LineFormModal, lineFormModalApi] = useVbenModal({
+ connectedComponent: ReturnIssueLineFormModal,
+ destroyOnClose: true,
+});
+
+/** 鎻愪氦閫�鏂欏崟 */
+async function handleSubmit() {
+ if (!formData.value?.id) return;
+ modalApi.lock();
+ try {
+ // 鍏堜繚瀛樿〃鍗�
+ const data =
+ (await formApi.getValues()) as MesWmReturnIssueApi.ReturnIssue;
+ await updateReturnIssue({ ...formData.value, ...data });
+ // 鍐嶆彁浜�
+ await submitReturnIssue(formData.value.id);
+ message.success('鎻愪氦鎴愬姛');
+ await modalApi.close();
+ emit('success');
+ } finally {
+ modalApi.unlock();
+ }
+}
+
+const [Modal, modalApi] = useVbenModal({
+ async onConfirm() {
+ const { valid } = await formApi.validate();
+ if (!valid) return;
+ modalApi.lock();
+ try {
+ const data =
+ (await formApi.getValues()) as MesWmReturnIssueApi.ReturnIssue;
+ await updateReturnIssue({ ...formData.value, ...data });
+ formData.value = { ...formData.value, ...data };
+ emit('success');
+ message.success('淇濆瓨鎴愬姛');
+ await modalApi.close();
+ } finally {
+ modalApi.unlock();
+ }
+ },
+ async onOpenChange(isOpen: boolean) {
+ if (!isOpen) {
+ formData.value = undefined;
+ return;
+ }
+ const data = modalApi.getData<{ id: number }>();
+ if (data?.id) {
+ modalApi.lock();
+ try {
+ formData.value = await getReturnIssue(data.id);
+ await formApi.setValues(formData.value);
+ // 鍔犺浇鍚庡埛鏂扮墿鏂欒
+ lineGridApi.query();
+ } finally {
+ modalApi.unlock();
+ }
+ }
+ },
+});
+</script>
+
+<template>
+ <Modal title="缂栬緫閫�鏂欏崟" class="w-3/5">
+ <Form class="mx-4" />
+ <!-- 鐗╂枡淇℃伅 -->
+ <template v-if="formData?.id">
+ <Divider style="margin: 12px 0">
+ <span style="font-weight: 600; color: #fa8c16;">鐗╂枡淇℃伅</span>
+ </Divider>
+ <div class="mx-4" style="height: 300px">
+ <LineGrid table-title="鐗╂枡鍒楄〃">
+ <template #toolbar-tools>
+ <TableAction
+ :actions="[
+ {
+ label: '娣诲姞鐗╂枡',
+ type: 'primary',
+ icon: ACTION_ICON.ADD,
+ onClick: handleCreateLine,
+ },
+ ]"
+ />
+ </template>
+ <template #lineActions="{ row }">
+ <TableAction
+ :actions="[
+ {
+ label: '缂栬緫',
+ type: 'link',
+ icon: ACTION_ICON.EDIT,
+ onClick: handleEditLine.bind(null, row),
+ },
+ {
+ label: '鍒犻櫎',
+ type: 'link',
+ danger: true,
+ icon: ACTION_ICON.DELETE,
+ popConfirm: {
+ title: `纭鍒犻櫎鐗╂枡 ${row.itemName}锛焋,
+ confirm: handleDeleteLine.bind(null, row),
+ },
+ },
+ ]"
+ />
+ </template>
+ </LineGrid>
+ </div>
+ </template>
+ <template #prepend-footer>
+ <div class="flex flex-auto items-center gap-2">
+ <Popconfirm
+ v-if="canSubmit"
+ title="纭鎻愪氦璇ラ��鏂欏崟锛熴�愭彁浜ゅ悗灏嗕笉鑳戒慨鏀广��"
+ @confirm="handleSubmit"
+ >
+ <Button type="primary">鎻愪氦</Button>
+ </Popconfirm>
+ </div>
+ </template>
+ </Modal>
+ <LineFormModal @success="onLineSuccess" />
+</template>
diff --git a/src/views/mes/workbench/components/tabs/ReturnIssueLineFormModal.vue b/src/views/mes/workbench/components/tabs/ReturnIssueLineFormModal.vue
new file mode 100644
index 0000000..cf2ef03
--- /dev/null
+++ b/src/views/mes/workbench/components/tabs/ReturnIssueLineFormModal.vue
@@ -0,0 +1,160 @@
+<script lang="ts" setup>
+import type { VbenFormSchema } from '#/adapter/form';
+import type { MesWmMaterialStockApi } from '#/api/mes/wm/materialstock';
+import type { MesWmReturnIssueLineApi } from '#/api/mes/wm/returnissue/line';
+
+import { computed, markRaw, ref } from 'vue';
+
+import { useVbenModal } from '@vben/common-ui';
+
+import { message } from 'ant-design-vue';
+
+import { z } from '#/adapter/form';
+import { useVbenForm } from '#/adapter/form';
+import {
+ createReturnIssueLine,
+ getReturnIssueLine,
+ updateReturnIssueLine,
+} from '#/api/mes/wm/returnissue/line';
+import { MdItemSelect } from '#/views/mes/md/item/components';
+import { WmMaterialStockSelect } from '#/views/wls/materialstock/components';
+
+const emit = defineEmits(['success']);
+const formData = ref<MesWmReturnIssueLineApi.ReturnIssueLine>();
+const issueId = ref<number>();
+
+const getTitle = computed(() => {
+ return formData.value?.id ? '缂栬緫鐗╂枡' : '娣诲姞鐗╂枡';
+});
+
+/** 鐗╂枡琛岃〃鍗� schema */
+function buildLineFormSchema(): VbenFormSchema[] {
+ return [
+ {
+ fieldName: 'materialStockId',
+ label: '搴撳瓨璁板綍',
+ component: markRaw(WmMaterialStockSelect),
+ componentProps: {
+ onChange: async (stock?: MesWmMaterialStockApi.MaterialStock) => {
+ await formApi.setValues({
+ batchCode: stock?.batchCode,
+ batchId: stock?.batchId,
+ itemId: stock?.itemId,
+ quantity: stock?.quantity,
+ quantityMax: stock?.quantity,
+ });
+ },
+ virtualFilter: 'only',
+ },
+ rules: 'selectRequired',
+ },
+ {
+ fieldName: 'quantityMax',
+ component: 'Input',
+ dependencies: { triggerFields: [''], show: () => false },
+ },
+ {
+ fieldName: 'quantity',
+ label: '閫�鏂欐暟閲�',
+ component: 'InputNumber',
+ componentProps: {
+ class: '!w-full',
+ min: 0,
+ placeholder: '璇疯緭鍏ラ��鏂欐暟閲�',
+ precision: 2,
+ },
+ rules: 'required',
+ dependencies: {
+ triggerFields: ['quantityMax'],
+ componentProps: (values) => ({
+ class: '!w-full',
+ max: values.quantityMax,
+ min: 0,
+ placeholder: '璇疯緭鍏ラ��鏂欐暟閲�',
+ precision: 2,
+ }),
+ },
+ },
+ {
+ fieldName: 'rqcCheckFlag',
+ label: '闇�瑕佽川妫�',
+ component: 'Switch',
+ rules: z.boolean().default(false),
+ },
+ {
+ fieldName: 'itemId',
+ label: '鐗╂枡',
+ component: markRaw(MdItemSelect),
+ componentProps: { disabled: true },
+ },
+ {
+ fieldName: 'batchCode',
+ label: '鎵规鍙�',
+ component: 'Input',
+ componentProps: { disabled: true, placeholder: '閫夋嫨搴撳瓨鍚庤嚜鍔ㄥ甫鍑�' },
+ },
+ {
+ fieldName: 'remark',
+ label: '澶囨敞',
+ component: 'Textarea',
+ formItemClass: 'col-span-3',
+ componentProps: { placeholder: '璇疯緭鍏ュ娉�', rows: 2 },
+ },
+ ];
+}
+
+const [Form, formApi] = useVbenForm({
+ commonConfig: { componentProps: { class: 'w-full' }, formItemClass: 'col-span-1', labelWidth: 90 },
+ layout: 'horizontal',
+ schema: buildLineFormSchema(),
+ showDefaultActions: false,
+ wrapperClass: 'grid-cols-3',
+});
+
+const [Modal, modalApi] = useVbenModal({
+ async onConfirm() {
+ const { valid } = await formApi.validate();
+ if (!valid) return;
+ modalApi.lock();
+ const data = (await formApi.getValues()) as MesWmReturnIssueLineApi.ReturnIssueLine;
+ data.issueId = issueId.value;
+ try {
+ if (formData.value?.id) {
+ await updateReturnIssueLine({ ...data, id: formData.value.id });
+ } else {
+ await createReturnIssueLine(data);
+ }
+ await modalApi.close();
+ emit('success');
+ message.success('鎿嶄綔鎴愬姛');
+ } finally {
+ modalApi.unlock();
+ }
+ },
+ async onOpenChange(isOpen: boolean) {
+ if (!isOpen) {
+ formData.value = undefined;
+ return;
+ }
+ const data = modalApi.getData<{ id?: number; issueId: number }>();
+ issueId.value = data.issueId;
+ if (!data.id) {
+ await formApi.resetForm();
+ return;
+ }
+ modalApi.lock();
+ try {
+ formData.value = await getReturnIssueLine(data.id);
+ await formApi.setValues(formData.value);
+ } finally {
+ modalApi.unlock();
+ }
+ },
+});
+</script>
+
+<template>
+ <Modal :title="getTitle" class="w-3/5">
+ <Form class="mx-4" />
+ </Modal>
+</template>
diff --git a/src/views/mes/workbench/components/tabs/ReturnIssueTab.vue b/src/views/mes/workbench/components/tabs/ReturnIssueTab.vue
index 50ca5ef..236020c 100644
--- a/src/views/mes/workbench/components/tabs/ReturnIssueTab.vue
+++ b/src/views/mes/workbench/components/tabs/ReturnIssueTab.vue
@@ -3,7 +3,7 @@
import type { VxeTableGridOptions } from '#/adapter/vxe-table';
import type { MesWmReturnIssueApi } from '#/api/mes/wm/returnissue';
-import { h, markRaw, onBeforeUnmount, onMounted, ref, watch, nextTick } from 'vue';
+import { computed, h, markRaw, onBeforeUnmount, onMounted, ref, watch, nextTick } from 'vue';
import {
DICT_TYPE,
@@ -14,9 +14,10 @@
import { IconifyIcon } from '@vben/icons';
import { getDictOptions } from '@vben/hooks';
-import { Button, Divider, message, Popconfirm, Tag } from 'ant-design-vue';
+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 {
@@ -29,7 +30,8 @@
} from '#/api/mes/wm/returnissue';
import { MdWorkstationSelect } from '#/views/mes/md/workstation/components';
import { ProWorkOrderSelect } from '#/views/mes/pro/workorder/components';
-import { $t } from '#/locales';
+
+import ReturnIssueFormModal from './ReturnIssueFormModal.vue';
defineOptions({ name: 'WorkbenchReturnIssueTab' });
@@ -38,6 +40,14 @@
workstationId?: number;
/** 褰撳墠宸ヤ綔绔欏悕绉� */
workstationName?: string;
+ /** 褰撳墠閫変腑鐨勪换鍔� ID */
+ taskId?: number;
+ /** 褰撳墠閫変腑鐨勪换鍔$紪鐮� */
+ taskCode?: string;
+ /** 褰撳墠閫変腑鐨勪换鍔″悕绉� */
+ taskName?: string;
+ /** 褰撳墠閫変腑鐨勪换鍔″叧鑱斿伐鍗� ID */
+ workOrderId?: number;
}>();
// --- 琛ㄥ崟鐘舵�� ---
@@ -88,19 +98,29 @@
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,
},
- rules: 'selectRequired',
},
{
fieldName: 'workstationId',
label: '宸ヤ綔绔�',
component: markRaw(MdWorkstationSelect),
- componentProps: { placeholder: '璇烽�夋嫨宸ヤ綔绔�' },
+ componentProps: { placeholder: '浠庨�変腑浠诲姟鑷姩甯﹀叆', disabled: true },
},
{
fieldName: 'returnDate',
@@ -140,6 +160,13 @@
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);
}
@@ -173,6 +200,11 @@
isCreating.value = false;
editingId.value = undefined;
formApi.resetForm();
+}
+
+/** 缂栬緫閫�鏂欏崟锛氭墦寮�缂栬緫寮圭獥锛堜粎鑽夌鐘舵�佸彲缂栬緫锛� */
+function handleEditRow(row: MesWmReturnIssueApi.ReturnIssue) {
+ editFormModalApi.setData({ id: row.id }).open();
}
/** 鎻愪氦 */
@@ -209,6 +241,12 @@
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',
@@ -225,7 +263,7 @@
props: { type: DICT_TYPE.MES_WM_RETURN_ISSUE_STATUS },
},
},
- { title: '鎿嶄綔', width: 200, fixed: 'right', slots: { default: 'actions' } },
+ { title: '鎿嶄綔', width: 260, fixed: 'right', slots: { default: 'actions' } },
];
const gridHeight = ref(300);
@@ -243,7 +281,7 @@
return await getReturnIssuePage({
pageNo: page.currentPage,
pageSize: page.pageSize,
- workstationId: props.workstationId,
+ taskId: props.taskId,
...formValues,
});
},
@@ -280,78 +318,113 @@
resizeObserver = null;
});
-// 宸ヤ綔绔欏彉鍖栨椂鍒锋柊鍒楄〃
+// 浠诲姟鎴栧伐浣滅珯鍙樺寲鏃跺埛鏂板垪琛�
watch(
- () => props.workstationId,
+ () => [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 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 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 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 v-if="isCreating" class="issue-tab__form">
+ <Form />
</div>
- </div>
- <!-- 琛ㄥ崟鍖哄煙锛堜粎鏂板缓鏃跺睍绀猴級 -->
- <div v-if="isCreating" class="issue-tab__form">
- <Form />
- </div>
+ <Divider style="margin: 12px 0" />
- <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>
- <!-- 搴曢儴锛氶��鏂欒褰曞垪琛� -->
- <div ref="listRef" class="issue-tab__list">
- <Grid table-title="閫�鏂欒褰�">
- <template #actions="{ row }">
- <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>
@@ -406,4 +479,11 @@
min-height: 0;
overflow: hidden;
}
+
+.issue-tab__empty {
+ flex: 1;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+}
</style>
diff --git a/src/views/mes/workbench/index.vue b/src/views/mes/workbench/index.vue
index 2307911..3640291 100644
--- a/src/views/mes/workbench/index.vue
+++ b/src/views/mes/workbench/index.vue
@@ -209,11 +209,19 @@
/>
<ProductIssueTab
v-else-if="activeTab === 'product-issue'"
+ :task-id="selectedTask?.id"
+ :task-code="selectedTask?.code"
+ :task-name="selectedTask?.name"
+ :work-order-id="selectedTask?.workOrderId"
:workstation-id="workstationId"
:workstation-name="workstationName"
/>
<ReturnIssueTab
v-else-if="activeTab === 'return-issue'"
+ :task-id="selectedTask?.id"
+ :task-code="selectedTask?.code"
+ :task-name="selectedTask?.name"
+ :work-order-id="selectedTask?.workOrderId"
:workstation-id="workstationId"
:workstation-name="workstationName"
/>
--
Gitblit v1.9.3