gaoluyang
6 天以前 3972a27ebe534def8c231491913c1036af09ae31
Merge remote-tracking branch 'origin/dev_pro2.0' into dev_pro2.0
已添加4个文件
已修改8个文件
1426 ■■■■ 文件已修改
src/api/mes/wm/productissue/index.ts 3 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/api/mes/wm/returnissue/index.ts 3 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/basicData/mdm/data.ts 27 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/basicData/mdm/modules/form.vue 4 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/mes/md/item/index.vue 7 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/mes/workbench/components/tabs/ProductIssueFormModal.vue 346 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/mes/workbench/components/tabs/ProductIssueLineFormModal.vue 113 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/mes/workbench/components/tabs/ProductIssueTab.vue 214 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/mes/workbench/components/tabs/ReturnIssueFormModal.vue 327 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/mes/workbench/components/tabs/ReturnIssueLineFormModal.vue 160 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/mes/workbench/components/tabs/ReturnIssueTab.vue 214 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/mes/workbench/index.vue 8 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
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; // å·¥ä½œç«™åç§°
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; // ç”Ÿäº§è®¢å•编号
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",
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(formApi å·²åˆå§‹åŒ–)
    formApi.setState({ schema: useFormSchema(formApi) });
    if (data?.id) {
      // ç¼–辑模式
      modalApi.lock();
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,
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();
  }
}
/** æ‰§è¡Œæ‹£è´§ï¼šé¢†æ–™æ•°é‡ä¸Žæ‹£è´§æ•°é‡ä¸ä¸€è‡´æ—¶äºŒæ¬¡ç¡®è®¤ */
async function handleStock() {
  if (!formData.value?.id) return;
  const quantityMatch = await checkProductIssueQuantity(formData.value.id);
  if (!quantityMatch) {
    try {
      await confirm('领料数量与拣货数量不一致,确认执行拣货?');
    } catch {
      return;
    }
  }
  modalApi.lock();
  try {
    await stockProductIssue(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 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">
          æ‰§è¡Œæ‹£è´§
        </Button>
      </div>
    </template>
  </Modal>
  <LineFormModal @success="onLineSuccess" />
</template>
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>
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>
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>
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>
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>
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"
          />