gaoluyang
8 天以前 41b0134c353d4e9289e49b201d5690d7a8fdd369
feat(wls): 销售出库页面支持 Excel 导入

- 工具栏新增「导入」按钮与导入弹窗(上传、下载模板、结果提示)
- 导入成功刷新列表;失败逐条展示单号/行号与原因
- 接口对接 /mes/wm/product-sales/get-import-template、/import

Co-Authored-By: Claude Code <noreply@anthropic.com>
已添加1个文件
已修改3个文件
158 ■■■■■ 文件已修改
src/api/mes/wm/productsales/index.ts 19 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/wls/productsales/data.ts 13 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/wls/productsales/index.vue 19 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/wls/productsales/modules/import-form.vue 107 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/api/mes/wm/productsales/index.ts
@@ -25,6 +25,12 @@
    remark?: string; // å¤‡æ³¨
    createTime?: Date; // åˆ›å»ºæ—¶é—´
  }
  /** é”€å”®å‡ºåº“单导入结果 */
  export interface ProductSalesImportRespVO {
    createList?: string[]; // åˆ›å»ºæˆåŠŸçš„å‡ºåº“å•å·
    failureList?: Record<string, string>; // å¯¼å…¥å¤±è´¥ï¼škey ä¸ºå‡ºåº“单号或行号,value ä¸ºå¤±è´¥åŽŸå› 
  }
}
/** æŸ¥è¯¢é”€å”®å‡ºåº“单分页 */
@@ -95,3 +101,16 @@
    params,
  });
}
/** ä¸‹è½½é”€å”®å‡ºåº“单导入模板 */
export function importProductSalesTemplate() {
  return requestClient.download('/mes/wm/product-sales/get-import-template');
}
/** å¯¼å…¥é”€å”®å‡ºåº“单 */
export function importProductSales(file: File) {
  return requestClient.upload<MesWmProductSalesApi.ProductSalesImportRespVO>(
    '/mes/wm/product-sales/import',
    { file },
  );
}
src/views/wls/productsales/data.ts
@@ -268,6 +268,19 @@
  ];
}
/** å¯¼å…¥çš„表单 */
export function useImportFormSchema(): VbenFormSchema[] {
  return [
    {
      fieldName: 'file',
      label: '销售出库单数据',
      component: 'Upload',
      rules: 'required',
      help: '仅允许导入 xls、xlsx æ ¼å¼æ–‡ä»¶ï¼›ç›¸åŒå‡ºåº“单号的多行会合并为同一张出库单',
    },
  ];
}
/** åˆ—表的搜索表单 */
export function useGridFormSchema(): VbenFormSchema[] {
  return [
src/views/wls/productsales/index.vue
@@ -19,9 +19,15 @@
import { useGridColumns, useGridFormSchema } from './data';
import Form from './modules/form.vue';
import ImportForm from './modules/import-form.vue';
const [FormModal, formModalApi] = useVbenModal({
  connectedComponent: Form,
  destroyOnClose: true,
});
const [ImportModal, importModalApi] = useVbenModal({
  connectedComponent: ImportForm,
  destroyOnClose: true,
});
@@ -33,6 +39,11 @@
/** åˆ›å»ºé”€å”®å‡ºåº“单 */
function handleCreate() {
  formModalApi.setData({ formType: 'create' }).open();
}
/** å¯¼å…¥é”€å”®å‡ºåº“单 */
function handleImport() {
  importModalApi.open();
}
/** æŸ¥çœ‹é”€å”®å‡ºåº“单 */
@@ -121,6 +132,7 @@
<template>
  <Page auto-content-height><FormModal @success="handleRefresh" />
    <ImportModal @success="handleRefresh" />
    <Grid table-title="销售出库单列表">
      <template #toolbar-tools>
@@ -134,6 +146,13 @@
              onClick: handleCreate,
            },
            {
              label: '导入',
              type: 'primary',
              icon: ACTION_ICON.UPLOAD,
              auth: ['mes:wm-product-sales:import'],
              onClick: handleImport,
            },
            {
              label: $t('ui.actionTitle.export'),
              type: 'primary',
              icon: ACTION_ICON.DOWNLOAD,
src/views/wls/productsales/modules/import-form.vue
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,107 @@
<script lang="ts" setup>
import type { FileType } from 'ant-design-vue/es/upload/interface';
import type { MesWmProductSalesApi } from '#/api/mes/wm/productsales';
import { useVbenModal } from '@vben/common-ui';
import { downloadFileFromBlobPart } from '@vben/utils';
import { Button, message, Upload } from 'ant-design-vue';
import { useVbenForm } from '#/adapter/form';
import {
  importProductSales,
  importProductSalesTemplate,
} from '#/api/mes/wm/productsales';
import { $t } from '#/locales';
import { useImportFormSchema } from '../data';
const emit = defineEmits(['success']);
const [Form, formApi] = useVbenForm({
  commonConfig: {
    componentProps: {
      class: 'w-full',
    },
    formItemClass: 'col-span-2',
    labelWidth: 120,
  },
  layout: 'horizontal',
  schema: useImportFormSchema(),
  showDefaultActions: false,
});
const [Modal, modalApi] = useVbenModal({
  async onConfirm() {
    const { valid } = await formApi.validate();
    if (!valid) {
      return;
    }
    modalApi.lock();
    const data = await formApi.getValues();
    try {
      const result = await importProductSales(data.file);
      const importData = result as MesWmProductSalesApi.ProductSalesImportRespVO;
      const createList = importData.createList || [];
      const failureList = importData.failureList || {};
      const failureEntries = Object.entries(failureList);
      const failureText = failureEntries
        .map(([key, reason]) => `${key}:${reason}`)
        .join(';');
      const text = `导入成功 ${createList.length} å¼ ${
        createList.length > 0 ? `(${createList.join('、')})` : ''
      };导入失败 ${failureEntries.length} å¼ ${
        failureText ? `(${failureText})` : ''
      }`;
      if (failureEntries.length > 0) {
        message.warning(text, 8);
      } else {
        message.success(text);
      }
      // å…¨éƒ¨å¤±è´¥æ—¶ä¿ç•™å¼¹çª—,方便用户看完原因后重试
      if (createList.length > 0 || failureEntries.length === 0) {
        await modalApi.close();
        emit('success');
      }
    } finally {
      modalApi.unlock();
    }
  },
});
/** ä¸Šä¼ å‰ */
function beforeUpload(file: FileType) {
  formApi.setFieldValue('file', file);
  return false;
}
/** ä¸‹è½½æ¨¡æ¿ */
async function handleDownload() {
  const data = await importProductSalesTemplate();
  downloadFileFromBlobPart({ fileName: '销售出库单导入模板.xls', source: data });
}
</script>
<template>
  <Modal :title="$t('ui.actionTitle.import', ['销售出库单'])" class="w-1/3">
    <Form class="mx-4">
      <template #file>
        <div class="w-full">
          <Upload
            :before-upload="beforeUpload"
            :max-count="1"
            accept=".xls,.xlsx"
          >
            <Button type="primary">选择 Excel æ–‡ä»¶</Button>
          </Upload>
        </div>
      </template>
    </Form>
    <template #prepend-footer>
      <div class="flex flex-auto items-center">
        <Button @click="handleDownload">下载导入模板</Button>
      </div>
    </template>
  </Modal>
</template>