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