liu
2026-09-03 d5656aecc633cdac7ebc286a85b07274fed16e1b
feat(srm): 供应商档案新增 Excel 导入导出

- 工具栏新增 导入/导出 按钮(权限 srm:supplier:import / export)
- 新增导入弹窗(下载导入模板/选择文件/是否更新),导入后按新增/更新/失败汇总提示
- API 新增 exportSupplierExcel、getSupplierImportTemplate、importSupplierExcel

Co-Authored-By: Claude <noreply@anthropic.com>
已添加1个文件
已修改3个文件
177 ■■■■■ 文件已修改
src/api/srm/supplier/index.ts 18 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/srm/supplier/data.ts 24 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/srm/supplier/index.vue 35 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/srm/supplier/modules/import-form.vue 100 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/api/srm/supplier/index.ts
@@ -55,3 +55,21 @@
export function deleteSupplier(id: number) {
  return requestClient.delete(`/srm/supplier/delete?id=${id}`);
}
/** å¯¼å‡ºä¾›åº”商档案 Excel(按列表查询条件) */
export function exportSupplierExcel(params: any) {
  return requestClient.download('/srm/supplier/export-excel', { params });
}
/** ä¸‹è½½ä¾›åº”商档案导入模板 */
export function getSupplierImportTemplate() {
  return requestClient.download('/srm/supplier/get-import-template');
}
/** å¯¼å…¥ä¾›åº”商档案 Excel */
export function importSupplierExcel(data: {
  file: File;
  updateSupport: boolean;
}) {
  return requestClient.upload('/srm/supplier/import-excel', data);
}
src/views/srm/supplier/data.ts
@@ -176,3 +176,27 @@
    },
  ];
}
/** å¯¼å…¥å¼¹çª—的表单 */
export function useImportFormSchema(): VbenFormSchema[] {
  return [
    {
      fieldName: 'file',
      label: '供应商档案',
      component: 'Upload',
      rules: 'required',
      help: '仅允许导入 xls、xlsx æ ¼å¼æ–‡ä»¶',
    },
    {
      fieldName: 'updateSupport',
      label: '是否更新',
      component: 'Switch',
      componentProps: {
        checkedChildren: '是',
        unCheckedChildren: '否',
      },
      rules: z.boolean().default(false),
      help: '勾选后按“供应商编码”匹配已存在档案并覆盖更新;不勾选时重复编码计入失败明细',
    },
  ];
}
src/views/srm/supplier/index.vue
@@ -3,18 +3,21 @@
import type { SrmSupplierApi } from '#/api/srm/supplier';
import { Page, useVbenModal } from '@vben/common-ui';
import { downloadFileFromBlobPart } from '@vben/utils';
import { message } from 'ant-design-vue';
import { ACTION_ICON, TableAction, useVbenVxeGrid } from '#/adapter/vxe-table';
import {
  deleteSupplier,
  exportSupplierExcel,
  getSupplierPage,
} from '#/api/srm/supplier';
import { $t } from '#/locales';
import { useGridColumns, useGridFormSchema } from './data';
import SupplierForm from './modules/form.vue';
import SupplierImportForm from './modules/import-form.vue';
defineOptions({ name: 'SrmSupplier' });
@@ -28,6 +31,18 @@
function handleEdit(row: SrmSupplierApi.SupplierVO) {
  formModalApi.setData(row).open();
}
/** å¯¼å…¥ä¾›åº”商 */
function handleImport() {
  importModalApi.open();
}
/** å¯¼å‡ºä¾›åº”商(按当前搜索条件) */
async function handleExport() {
  const formValues = await gridApi.formApi.getValues();
  const data = await exportSupplierExcel(formValues);
  downloadFileFromBlobPart({ fileName: '供应商数据.xlsx', source: data });
}
async function handleDelete(row: SrmSupplierApi.SupplierVO) {
@@ -46,6 +61,11 @@
const [FormModal, formModalApi] = useVbenModal({
  connectedComponent: SupplierForm,
  destroyOnClose: true,
});
const [SupplierImportModal, importModalApi] = useVbenModal({
  connectedComponent: SupplierImportForm,
  destroyOnClose: true,
});
@@ -77,6 +97,7 @@
<template>
  <Page auto-content-height>
    <FormModal @success="handleRefresh" />
    <SupplierImportModal @success="handleRefresh" />
    <Grid table-title="供应商列表">
      <template #toolbar-tools>
        <TableAction
@@ -88,6 +109,20 @@
              auth: ['srm:supplier:create'],
              onClick: handleCreate,
            },
            {
              label: $t('ui.actionTitle.import'),
              type: 'primary',
              icon: ACTION_ICON.UPLOAD,
              auth: ['srm:supplier:import'],
              onClick: handleImport,
            },
            {
              label: $t('ui.actionTitle.export'),
              type: 'primary',
              icon: ACTION_ICON.DOWNLOAD,
              auth: ['srm:supplier:export'],
              onClick: handleExport,
            },
          ]"
        />
      </template>
src/views/srm/supplier/modules/import-form.vue
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,100 @@
<script lang="ts" setup>
import type { FileType } from 'ant-design-vue/es/upload/interface';
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 {
  getSupplierImportTemplate,
  importSupplierExcel,
} from '#/api/srm/supplier';
import { useImportFormSchema } from '../data';
const emit = defineEmits(['success']);
const [Form, formApi] = useVbenForm({
  commonConfig: {
    formItemClass: 'col-span-2',
    labelWidth: 110,
  },
  layout: 'horizontal',
  schema: useImportFormSchema(),
  showDefaultActions: false,
});
const [Modal, modalApi] = useVbenModal({
  async onConfirm() {
    const { valid } = await formApi.validate();
    if (!valid) {
      return;
    }
    const data = await formApi.getValues();
    modalApi.lock();
    try {
      const res = (await importSupplierExcel({
        file: data.file,
        updateSupport: data.updateSupport,
      })) as any;
      // å…¼å®¹å“åº”结构:业务数据直接返回或包在 { code, data } ä¸­
      const result = res?.data ?? res ?? {};
      const createList = result.createList ?? [];
      const updateList = result.updateList ?? [];
      const failureList = result.failureList ?? {};
      const failureCount = Object.keys(failureList).length;
      let tip = `导入完成:新增 ${createList.length} æ¡ã€æ›´æ–° ${updateList.length} æ¡ã€å¤±è´¥ ${failureCount} æ¡ã€‚`;
      if (failureCount > 0) {
        const sample = Object.entries(failureList)
          .slice(0, 5)
          .map(([name, reason]) => `${name}:${reason}`)
          .join(';');
        tip += `失败原因示例:${sample}`;
      }
      if (failureCount > 0) {
        message.warning(tip);
      } else {
        message.success(tip);
      }
      // å…³é—­å¹¶åˆ·æ–°
      await modalApi.close();
      emit('success');
    } finally {
      modalApi.unlock();
    }
  },
});
/** ä¸Šä¼ å‰ */
function beforeUpload(file: FileType) {
  formApi.setFieldValue('file', file);
  return false;
}
/** ä¸‹è½½æ¨¡æ¿ */
async function handleDownload() {
  const data = await getSupplierImportTemplate();
  downloadFileFromBlobPart({ fileName: '供应商导入模板.xlsx', source: data });
}
</script>
<template>
  <Modal title="供应商档案导入" class="w-1/3">
    <Form class="mx-4">
      <template #file>
        <div class="w-full">
          <Upload :max-count="1" accept=".xls,.xlsx" :before-upload="beforeUpload">
            <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>