feat(mes): 采购入库单 / 销售出库单 前端新增导入入口
1. 两个页面工具栏新增「导入」按钮(权限 mes:wm-item-receipt:import / mes:wm-product-sales:import)
2. 新增通用导入弹窗组件 wm-doc-import,支持下载模板、上传 Excel、按成功/失败分类提示
3. api 补充 importXxxTemplate / importXxx 方法
| | |
| | | remark?: string; // 夿³¨ |
| | | createTime?: Date; // å建æ¶é´ |
| | | } |
| | | |
| | | /** 忮坼å
¥ç»æ */ |
| | | export interface WmDocImportResult { |
| | | createCodes?: string[]; // å建æåçåæ®ç¼ç |
| | | failureCodes?: Record<string, string>; // 失败çç¼ç ååå |
| | | } |
| | | } |
| | | |
| | | /** æ¥è¯¢éè´å
¥åºåå页 */ |
| | |
| | | }); |
| | | } |
| | | |
| | | /** ä¸è½½éè´å
¥åºå导å
¥æ¨¡æ¿ */ |
| | | export function importItemReceiptTemplate() { |
| | | return requestClient.download('/mes/wm/item-receipt/get-import-template'); |
| | | } |
| | | |
| | | /** 导å
¥éè´å
¥åºå */ |
| | | export function importItemReceipt(file: File) { |
| | | return requestClient.upload<WmDocImportResult>( |
| | | '/mes/wm/item-receipt/import', |
| | | { file }, |
| | | ); |
| | | } |
| | | |
| | | /** æ ¹æ®å°è´§éç¥åçæéè´å
¥åºå */ |
| | | export function generateItemReceiptFromArrivalNotice(arrivalNoticeId: number) { |
| | | return requestClient.post<number>( |
| | |
| | | remark?: string; // 夿³¨ |
| | | createTime?: Date; // å建æ¶é´ |
| | | } |
| | | |
| | | /** 忮坼å
¥ç»æ */ |
| | | export interface WmDocImportResult { |
| | | createCodes?: string[]; // å建æåçåæ®ç¼ç |
| | | failureCodes?: Record<string, string>; // 失败çç¼ç ååå |
| | | } |
| | | } |
| | | |
| | | /** æ¥è¯¢éå®åºåºåå页 */ |
| | |
| | | ); |
| | | } |
| | | |
| | | /** ä¸è½½éå®åºåºå导å
¥æ¨¡æ¿ */ |
| | | export function importProductSalesTemplate() { |
| | | return requestClient.download('/mes/wm/product-sales/get-import-template'); |
| | | } |
| | | |
| | | /** 导å
¥éå®åºåºå */ |
| | | export function importProductSales(file: File) { |
| | | return requestClient.upload<MesWmProductSalesApi.WmDocImportResult>( |
| | | '/mes/wm/product-sales/import', |
| | | { file }, |
| | | ); |
| | | } |
| | | |
| | | /** 导åºéå®åºåºå */ |
| | | export function exportProductSales(params: any) { |
| | | return requestClient.download('/mes/wm/product-sales/export-excel', { |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | <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'; |
| | | |
| | | defineOptions({ name: 'WmDocImportForm' }); |
| | | |
| | | const props = withDefaults( |
| | | defineProps<{ |
| | | /** ä¸è½½æ¨¡æ¿æ¥å£ */ |
| | | templateApi: () => Promise<Blob | BlobPart | any>; |
| | | /** 导å
¥æ¥å£ */ |
| | | importApi: (file: File) => Promise<any>; |
| | | /** åæ®åç§°ï¼ç¨äºæç¤ºä¸æä»¶å */ |
| | | subject: string; |
| | | /** ä¸ä¼ æ¡æ ç¾ */ |
| | | fileLabel?: string; |
| | | }>(), |
| | | { |
| | | fileLabel: 'Excel æ°æ®', |
| | | }, |
| | | ); |
| | | |
| | | const emit = defineEmits(['success']); |
| | | |
| | | const [Form, formApi] = useVbenForm({ |
| | | commonConfig: { |
| | | componentProps: { class: 'w-full' }, |
| | | formItemClass: 'col-span-2', |
| | | labelWidth: 120, |
| | | }, |
| | | layout: 'horizontal', |
| | | schema: [ |
| | | { |
| | | fieldName: 'file', |
| | | label: '', |
| | | component: 'Upload', |
| | | rules: 'required', |
| | | help: 'ä»
å
许导å
¥ xlsãxlsx æ ¼å¼æä»¶ï¼åä¸åæ®ç¼ç çå¤è¡ä¼å½å¹¶ä¸ºä¸å¼ åæ®çæç»', |
| | | }, |
| | | ], |
| | | 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 props.importApi(data.file as File)) as any; |
| | | const createCodes: string[] = result?.createCodes ?? []; |
| | | const failureCodes: Record<string, string> = result?.failureCodes ?? {}; |
| | | const failCount = Object.keys(failureCodes).length; |
| | | |
| | | await modalApi.close(); |
| | | if (createCodes.length > 0) { |
| | | emit('success'); |
| | | } |
| | | |
| | | // ç»ææç¤ºï¼æå/失败å嫿±æ»ï¼å¤±è´¥æç»æå¤å±ç¤º 5 æ¡ |
| | | const lines: string[] = []; |
| | | if (createCodes.length > 0) { |
| | | lines.push(`æå导å
¥ ${createCodes.length} å¼ ${props.subject}`); |
| | | } |
| | | if (failCount > 0) { |
| | | const detail = Object.entries(failureCodes) |
| | | .slice(0, 5) |
| | | .map(([key, reason]) => `${key}ï¼${reason}`) |
| | | .join('ï¼'); |
| | | lines.push( |
| | | `${failCount} è¡/å¼ æªå¯¼å
¥ââ${detail}${failCount > 5 ? 'ï¼å
¶ä½ç¥ï¼' : ''}`, |
| | | ); |
| | | } |
| | | if (lines.length === 0) { |
| | | message.warning('没æå¯å¯¼å
¥çæ°æ®ï¼è¯·æ£æ¥æä»¶å
容'); |
| | | return; |
| | | } |
| | | if (createCodes.length > 0 && failCount === 0) { |
| | | message.success(lines[0]); |
| | | } else if (failCount > 0 && createCodes.length === 0) { |
| | | message.error(lines.join('ï¼')); |
| | | } else { |
| | | message.warning(lines.join('ï¼')); |
| | | } |
| | | } finally { |
| | | modalApi.unlock(); |
| | | } |
| | | }, |
| | | }); |
| | | |
| | | /** ä¸ä¼ å */ |
| | | function beforeUpload(file: FileType) { |
| | | formApi.setFieldValue('file', file); |
| | | return false; |
| | | } |
| | | |
| | | /** ä¸è½½æ¨¡ç */ |
| | | async function handleDownload() { |
| | | const data = await props.templateApi(); |
| | | downloadFileFromBlobPart({ |
| | | fileName: `${props.subject}导å
¥æ¨¡æ¿.xls`, |
| | | source: data, |
| | | }); |
| | | } |
| | | </script> |
| | | |
| | | <template> |
| | | <Modal :title="`导å
¥${subject}`" class="w-2/5"> |
| | | <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> |
| | |
| | | exportItemReceipt, |
| | | generateItemReceiptFromArrivalNotice, |
| | | getItemReceiptPage, |
| | | importItemReceipt, |
| | | importItemReceiptTemplate, |
| | | } from '#/api/mes/wm/itemreceipt'; |
| | | import { $t } from '#/locales'; |
| | | import WmDocImportForm from '#/components/wm-doc-import/index.vue'; |
| | | |
| | | import { useGridColumns, useGridFormSchema } from './data'; |
| | | import Form from './modules/form.vue'; |
| | |
| | | |
| | | const [AnSelectModal, anSelectModalApi] = useVbenModal({ |
| | | connectedComponent: AnSelectDialog, |
| | | destroyOnClose: true, |
| | | }); |
| | | |
| | | const [ImportModal, importModalApi] = useVbenModal({ |
| | | connectedComponent: WmDocImportForm, |
| | | destroyOnClose: true, |
| | | }); |
| | | |
| | |
| | | } |
| | | } |
| | | |
| | | /** 导å
¥éè´å
¥åºå */ |
| | | function handleImport() { |
| | | importModalApi |
| | | .setData({ |
| | | subject: 'éè´å
¥åºå', |
| | | templateApi: importItemReceiptTemplate, |
| | | importApi: importItemReceipt, |
| | | }) |
| | | .open(); |
| | | } |
| | | |
| | | /** 导åºè¡¨æ ¼ */ |
| | | async function handleExport() { |
| | | const data = await exportItemReceipt(await gridApi.formApi.getValues()); |
| | |
| | | <Page auto-content-height> |
| | | <FormModal @success="handleRefresh" /> |
| | | <AnSelectModal @selected="handleArrivalNoticeSelected" /> |
| | | <ImportModal @success="handleRefresh" /> |
| | | |
| | | <Grid table-title="éè´å
¥åºåå表"> |
| | | <template #toolbar-tools> |
| | |
| | | onClick: handleGenerateFromAN, |
| | | }, |
| | | { |
| | | label: '导å
¥', |
| | | type: 'primary', |
| | | icon: ACTION_ICON.UPLOAD, |
| | | auth: ['mes:wm-item-receipt:import'], |
| | | onClick: handleImport, |
| | | }, |
| | | { |
| | | label: $t('ui.actionTitle.export'), |
| | | type: 'primary', |
| | | icon: ACTION_ICON.DOWNLOAD, |
| | |
| | | deleteProductSales, |
| | | exportProductSales, |
| | | getProductSalesPage, |
| | | importProductSales, |
| | | importProductSalesTemplate, |
| | | } from '#/api/mes/wm/productsales'; |
| | | import { $t } from '#/locales'; |
| | | import WmDocImportForm from '#/components/wm-doc-import/index.vue'; |
| | | |
| | | import { useGridColumns, useGridFormSchema } from './data'; |
| | | import Form from './modules/form.vue'; |
| | | |
| | | const [FormModal, formModalApi] = useVbenModal({ |
| | | connectedComponent: Form, |
| | | destroyOnClose: true, |
| | | }); |
| | | |
| | | const [ImportModal, importModalApi] = useVbenModal({ |
| | | connectedComponent: WmDocImportForm, |
| | | destroyOnClose: true, |
| | | }); |
| | | |
| | |
| | | handleRefresh(); |
| | | } |
| | | |
| | | /** 导å
¥éå®åºåºå */ |
| | | function handleImport() { |
| | | importModalApi |
| | | .setData({ |
| | | subject: 'éå®åºåºå', |
| | | templateApi: importProductSalesTemplate, |
| | | importApi: importProductSales, |
| | | }) |
| | | .open(); |
| | | } |
| | | |
| | | /** 导åºè¡¨æ ¼ */ |
| | | async function handleExport() { |
| | | const data = await exportProductSales(await gridApi.formApi.getValues()); |
| | |
| | | </script> |
| | | |
| | | <template> |
| | | <Page auto-content-height><FormModal @success="handleRefresh" /> |
| | | <Page auto-content-height> |
| | | <FormModal @success="handleRefresh" /> |
| | | <ImportModal @success="handleRefresh" /> |
| | | |
| | | <Grid table-title="éå®åºåºåå表"> |
| | | <template #toolbar-tools> |
| | |
| | | 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, |