feat(erp/mes/hrm): 新增采购订单/销售订单/计量器具导入弹窗;导入完成自动关闭
- 新增 采购订单/销售订单/计量器具 导入弹窗(下载模板、上传、进度条、导入结果)
- 采购订单导入支持"是否审核"开关;销售订单、计量器具无开关
- 修复导入弹窗导入成功后不自动关闭的问题(员工合同/员工档案/库存现有量一并修复)
| | |
| | | }); |
| | | } |
| | | |
| | | /** éè´è®¢å导å
¥ç»æ */ |
| | | export interface ImportResult { |
| | | /** å建æåçéè´åç¼å· */ |
| | | createList?: string[]; |
| | | /** æ´æ°æåçéè´åç¼å· */ |
| | | updateList?: string[]; |
| | | /** 导å
¥å¤±è´¥éåï¼key 为éè´åç¼å·æè¡å·ï¼value 为失败åå */ |
| | | failureList?: Record<string, string>; |
| | | } |
| | | |
| | | /** è·åéè´å®¡æ ¸æµç¨å表ååº */ |
| | | export interface ProcessDefinition { |
| | | id: string; // æµç¨å®ä¹ID |
| | |
| | | params: { id }, |
| | | }); |
| | | } |
| | | |
| | | /** ä¸è½½éè´è®¢å导å
¥æ¨¡æ¿ */ |
| | | export function importPurchaseOrderTemplate() { |
| | | return requestClient.download('/erp/purchase-order/get-import-template'); |
| | | } |
| | | |
| | | /** 导å
¥éè´è®¢å */ |
| | | export function importPurchaseOrder(file: File, directApprove: boolean) { |
| | | return requestClient.upload<ImportResult>( |
| | | '/erp/purchase-order/import', |
| | | { file, directApprove }, |
| | | ); |
| | | } |
| | |
| | | } |
| | | } |
| | | |
| | | /** éå®è®¢å导å
¥ç»æ */ |
| | | export interface ImportResult { |
| | | /** å建æåçéå®åç¼å· */ |
| | | createList?: string[]; |
| | | /** æ´æ°æåçéå®åç¼å· */ |
| | | updateList?: string[]; |
| | | /** 导å
¥å¤±è´¥éåï¼key 为éå®åç¼å·æè¡å·ï¼value 为失败åå */ |
| | | failureList?: Record<string, string>; |
| | | } |
| | | |
| | | /** æ¥è¯¢éå®è®¢åå页 */ |
| | | export function getSaleOrderPage(params: PageParam) { |
| | | return requestClient.get<PageResult<ErpSaleOrderApi.SaleOrder>>( |
| | |
| | | params: { id }, |
| | | }); |
| | | } |
| | | |
| | | /** ä¸è½½éå®è®¢å导å
¥æ¨¡æ¿ */ |
| | | export function importSaleOrderTemplate() { |
| | | return requestClient.download('/erp/sale-order/get-import-template'); |
| | | } |
| | | |
| | | /** 导å
¥éå®è®¢å */ |
| | | export function importSaleOrder(file: File) { |
| | | return requestClient.upload<ImportResult>('/erp/sale-order/import', { file }); |
| | | } |
| | |
| | | } |
| | | } |
| | | |
| | | /** 计éå¨å
·å¯¼å
¥ç»æ */ |
| | | export interface ImportResult { |
| | | /** å建æåç计éå¨å
·ç¼ç */ |
| | | createList?: string[]; |
| | | /** æ´æ°æåç计éå¨å
·ç¼ç */ |
| | | updateList?: string[]; |
| | | /** 导å
¥å¤±è´¥éåï¼key 为计éå¨å
·ç¼ç æè¡å·ï¼value 为失败åå */ |
| | | failureList?: Record<string, string>; |
| | | } |
| | | |
| | | /** æ¥è¯¢è®¡éå¨å
·å页 */ |
| | | export function getMeteringPage(params: PageParam) { |
| | | return requestClient.get<PageResult<MesDvMeteringApi.Metering>>( |
| | |
| | | '/mes/dv/metering/simple-list', |
| | | ); |
| | | } |
| | | |
| | | /** ä¸è½½è®¡éå¨å
·å¯¼å
¥æ¨¡æ¿ */ |
| | | export function importMeteringTemplate() { |
| | | return requestClient.download('/mes/dv/metering/get-import-template'); |
| | | } |
| | | |
| | | /** 导å
¥è®¡éå¨å
· */ |
| | | export function importMetering(file: File) { |
| | | return requestClient.upload<ImportResult>('/mes/dv/metering/import', { file }); |
| | | } |
| | |
| | | |
| | | import { useGridColumns, useGridFormSchema } from './data'; |
| | | import Form from './modules/form.vue'; |
| | | import ImportForm from './modules/import-form.vue'; |
| | | |
| | | /** ERP éè´è®¢åå表 */ |
| | | defineOptions({ name: 'ErpPurchaseOrder' }); |
| | |
| | | |
| | | const [FormModal, formModalApi] = useVbenModal({ |
| | | connectedComponent: Form, |
| | | destroyOnClose: true, |
| | | }); |
| | | |
| | | const [ImportModal, importModalApi] = useVbenModal({ |
| | | connectedComponent: ImportForm, |
| | | destroyOnClose: true, |
| | | }); |
| | | |
| | |
| | | async function handleExport() { |
| | | const data = await exportPurchaseOrder(await gridApi.formApi.getValues()); |
| | | downloadFileFromBlobPart({ fileName: 'éè´è®¢å.xls', source: data }); |
| | | } |
| | | |
| | | /** 导å
¥éè´è®¢å */ |
| | | function handleImport() { |
| | | importModalApi.open(); |
| | | } |
| | | |
| | | /** æ°å¢éè´è®¢å */ |
| | |
| | | <template> |
| | | <Page auto-content-height> |
| | | <FormModal @success="handleRefresh" /> |
| | | <ImportModal @success="handleRefresh" /> |
| | | <Grid table-title="éè´è®¢åå表"> |
| | | <template #toolbar-tools> |
| | | <TableAction |
| | |
| | | onClick: handleExport, |
| | | }, |
| | | { |
| | | label: '导å
¥', |
| | | type: 'primary', |
| | | icon: ACTION_ICON.UPLOAD, |
| | | auth: ['erp:purchase-order:import'], |
| | | onClick: handleImport, |
| | | }, |
| | | { |
| | | label: 'æ¹éå é¤', |
| | | type: 'primary', |
| | | danger: true, |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | <script lang="ts" setup> |
| | | import type { FileType } from 'ant-design-vue/es/upload/interface'; |
| | | import type { ImportResult } from '#/api/erp/purchase/order'; |
| | | |
| | | import { computed, ref } from 'vue'; |
| | | |
| | | import { useVbenModal } from '#/packages/effects/common-ui/src'; |
| | | import { downloadFileFromBlobPart } from '#/packages/utils/src'; |
| | | |
| | | import { Button, message, Progress, Switch, Upload } from 'ant-design-vue'; |
| | | |
| | | import { |
| | | importPurchaseOrder, |
| | | importPurchaseOrderTemplate, |
| | | } from '#/api/erp/purchase/order'; |
| | | |
| | | const emit = defineEmits(['success']); |
| | | |
| | | const fileList = ref<FileType[]>([]); |
| | | /** æ¯å¦å°å¯¼å
¥ç订åç´æ¥ç½®ä¸ºâå·²å®¡æ ¸â */ |
| | | const directApprove = ref(false); |
| | | const importResult = ref<ImportResult>(); |
| | | const importing = ref(false); |
| | | const progressPercent = ref(0); |
| | | let progressTimer: ReturnType<typeof setInterval> | undefined; |
| | | |
| | | const [Modal, modalApi] = useVbenModal({ |
| | | async onConfirm() { |
| | | let shouldClose = false; |
| | | if (fileList.value.length === 0) { |
| | | message.warning('è¯·éæ©è¦å¯¼å
¥çæä»¶'); |
| | | return; |
| | | } |
| | | if (importing.value) return; |
| | | modalApi.lock(); |
| | | importing.value = true; |
| | | startMockProgress(); |
| | | try { |
| | | importResult.value = await importPurchaseOrder( |
| | | fileList.value[0] as File, |
| | | directApprove.value, |
| | | ); |
| | | // 宿ï¼è¿åº¦æ¡èµ°æ»¡ |
| | | stopMockProgress(); |
| | | progressPercent.value = 100; |
| | | await delay(300); |
| | | emit('success'); |
| | | const failedCount = Object.keys(importResult.value?.failureList ?? {}).length; |
| | | if (failedCount > 0) { |
| | | message.warning( |
| | | `导å
¥å®æï¼æ°å¢ ${createCount.value} æ¡ï¼å¤±è´¥ ${failedCount} æ¡ï¼è¯·æ¥ç䏿¹å¤±è´¥æç»`, |
| | | ); |
| | | } else { |
| | | message.success('导å
¥å®æ'); |
| | | shouldClose = true; |
| | | } |
| | | } finally { |
| | | stopMockProgress(); |
| | | importing.value = false; |
| | | modalApi.unlock(); |
| | | if (shouldClose) { |
| | | await modalApi.close(); |
| | | } |
| | | } |
| | | }, |
| | | }); |
| | | |
| | | /** 模æè¿åº¦æ¡ï¼è¯·æ±æé´ä» 0 èµ°å° 90% */ |
| | | function startMockProgress() { |
| | | progressPercent.value = 0; |
| | | progressTimer = setInterval(() => { |
| | | if (progressPercent.value < 90) { |
| | | progressPercent.value = Math.min( |
| | | 90, |
| | | progressPercent.value + Math.random() * 12, |
| | | ); |
| | | } |
| | | }, 300); |
| | | } |
| | | |
| | | function stopMockProgress() { |
| | | if (progressTimer) { |
| | | clearInterval(progressTimer); |
| | | progressTimer = undefined; |
| | | } |
| | | } |
| | | |
| | | function delay(ms: number) { |
| | | return new Promise((resolve) => setTimeout(resolve, ms)); |
| | | } |
| | | |
| | | function beforeUpload(file: FileType) { |
| | | fileList.value = [file]; |
| | | importResult.value = undefined; |
| | | return false; |
| | | } |
| | | |
| | | function handleRemove() { |
| | | fileList.value = []; |
| | | importResult.value = undefined; |
| | | } |
| | | |
| | | async function handleDownloadTemplate() { |
| | | const data = await importPurchaseOrderTemplate(); |
| | | downloadFileFromBlobPart({ fileName: 'éè´è®¢å导å
¥æ¨¡æ¿.xls', source: data }); |
| | | } |
| | | |
| | | const createCount = computed(() => importResult.value?.createList?.length ?? 0); |
| | | const updateCount = computed(() => importResult.value?.updateList?.length ?? 0); |
| | | const failureCount = computed( |
| | | () => Object.keys(importResult.value?.failureList ?? {}).length, |
| | | ); |
| | | </script> |
| | | |
| | | <template> |
| | | <Modal title="导å
¥éè´è®¢å" class="w-[720px]"> |
| | | <div class="mx-4"> |
| | | <Upload |
| | | :max-count="1" |
| | | accept=".xls,.xlsx" |
| | | :file-list="fileList" |
| | | :before-upload="beforeUpload" |
| | | @remove="handleRemove" |
| | | > |
| | | <Button type="primary">éæ© Excel æä»¶</Button> |
| | | </Upload> |
| | | <div class="mt-4"> |
| | | <span class="mr-2">æ¯å¦å®¡æ ¸</span> |
| | | <Switch v-model:checked="directApprove" /> |
| | | </div> |
| | | <div class="mt-2 rounded-md bg-gray-50 p-2 text-xs leading-5 text-gray-500"> |
| | | <p>ä»
å
许导å
¥ xlsãxlsx æ ¼å¼æä»¶ï¼æ¨¡æ¿ä¸âä¾åºååç§°âæåç§°ï¼æç¼ç ï¼å¡«åï¼éä¸ç³»ç»å
ä¾åºåä¸è´ï¼â产åç¼ç âæç©æç¼ç å¡«åï¼â产ååç§°âåä»
ä¾å¯¹ç
§ï¼å¹é
以产åç¼ç 为åï¼ï¼âéè´æ¶é´âæ ¼å¼å¦ 2026-01-01 10:00:00ï¼ç空åå½åæ¶é´ã</p> |
| | | <p>ä¸å¼ éè´åå¯å«å¤æ¡æç»ï¼ç¸åâéè´åç¼å·âçå¤è¡ä¼å并为ä¸å¼ 订åï¼ä¸»è¡¨å段å该ç»é¦è¡ï¼ï¼âéè´åç¼å·âçç©ºæ¶æ¯ä¸è¡åçæä¸å¼ æ°è®¢åãéè´åç¼å·å·²å卿¶ä¼å¯¼å
¥å¤±è´¥ã</p> |
| | | <p>订åç¶æï¼å¾é䏿¹å¼å
³ = ç´æ¥ç½®ä¸ºâå·²å®¡æ ¸âï¼ä¸å¾é = âæªå®¡æ ¸âã导å
¥ä¸ä¼èªå¨æäº¤å®¡æ¹æµç¨ã</p> |
| | | </div> |
| | | <!-- 导å
¥è¿åº¦æ¡ --> |
| | | <div v-if="importing" class="mt-4"> |
| | | <div class="mb-2 text-sm text-gray-600">æ£å¨å¯¼å
¥éè´è®¢åæ°æ®ï¼è¯·ç¨å...</div> |
| | | <Progress :percent="progressPercent" status="active" /> |
| | | </div> |
| | | <!-- 导å
¥ç»æ --> |
| | | <div |
| | | v-if="importResult" |
| | | class="mt-4 rounded-md border border-gray-200 bg-gray-50 p-3" |
| | | > |
| | | <div class="mb-2 font-medium">导å
¥ç»æ</div> |
| | | <div class="mb-2 flex gap-4"> |
| | | <span class="text-green-600">æ°å¢ï¼{{ createCount }} å¼ </span> |
| | | <span class="text-blue-600">è¦çæ´æ°ï¼{{ updateCount }} å¼ </span> |
| | | <span class="text-red-600">失败ï¼{{ failureCount }} å¼ </span> |
| | | </div> |
| | | <div v-if="failureCount > 0" class="max-h-40 overflow-y-auto"> |
| | | <div |
| | | v-for="(reason, name) in importResult.failureList" |
| | | :key="name" |
| | | class="text-sm leading-6 text-red-500" |
| | | > |
| | | {{ name }}ï¼{{ reason }} |
| | | </div> |
| | | </div> |
| | | </div> |
| | | </div> |
| | | <template #prepend-footer> |
| | | <Button @click="handleDownloadTemplate">ä¸è½½å¯¼å
¥æ¨¡æ¿</Button> |
| | | </template> |
| | | </Modal> |
| | | </template> |
| | |
| | | |
| | | import { useGridColumns, useGridFormSchema } from './data'; |
| | | import Form from './modules/form.vue'; |
| | | import ImportForm from './modules/import-form.vue'; |
| | | import SaleOrderPrint from './modules/print.vue'; |
| | | import SalesNoticeForm from '#/views/wls/salesnotice/modules/form.vue'; |
| | | |
| | |
| | | |
| | | const [FormModal, formModalApi] = useVbenModal({ |
| | | connectedComponent: Form, |
| | | destroyOnClose: true, |
| | | }); |
| | | |
| | | const [ImportModal, importModalApi] = useVbenModal({ |
| | | connectedComponent: ImportForm, |
| | | destroyOnClose: true, |
| | | }); |
| | | |
| | |
| | | async function handleExport() { |
| | | const data = await exportSaleOrder(await gridApi.formApi.getValues()); |
| | | downloadFileFromBlobPart({ fileName: 'éå®è®¢å.xls', source: data }); |
| | | } |
| | | |
| | | /** 导å
¥éå®è®¢å */ |
| | | function handleImport() { |
| | | importModalApi.open(); |
| | | } |
| | | |
| | | /** æ°å¢éå®è®¢å */ |
| | |
| | | <template> |
| | | <Page auto-content-height> |
| | | <FormModal @success="handleRefresh" /> |
| | | <ImportModal @success="handleRefresh" /> |
| | | <SalesNoticeFormModal /> |
| | | <SaleOrderPrint ref="printRef" /> |
| | | <Grid table-title="éå®è®¢åå表"> |
| | |
| | | onClick: handleExport, |
| | | }, |
| | | { |
| | | label: '导å
¥', |
| | | type: 'primary', |
| | | icon: ACTION_ICON.UPLOAD, |
| | | auth: ['erp:sale-order:import'], |
| | | onClick: handleImport, |
| | | }, |
| | | { |
| | | label: 'æ¹éå é¤', |
| | | type: 'primary', |
| | | danger: true, |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | <script lang="ts" setup> |
| | | import type { FileType } from 'ant-design-vue/es/upload/interface'; |
| | | import type { ImportResult } from '#/api/erp/sale/order'; |
| | | |
| | | import { computed, ref } from 'vue'; |
| | | |
| | | import { useVbenModal } from '#/packages/effects/common-ui/src'; |
| | | import { downloadFileFromBlobPart } from '#/packages/utils/src'; |
| | | |
| | | import { Button, message, Progress, Upload } from 'ant-design-vue'; |
| | | |
| | | import { importSaleOrder, importSaleOrderTemplate } from '#/api/erp/sale/order'; |
| | | |
| | | const emit = defineEmits(['success']); |
| | | |
| | | const fileList = ref<FileType[]>([]); |
| | | const importResult = ref<ImportResult>(); |
| | | const importing = ref(false); |
| | | const progressPercent = ref(0); |
| | | let progressTimer: ReturnType<typeof setInterval> | undefined; |
| | | |
| | | const [Modal, modalApi] = useVbenModal({ |
| | | async onConfirm() { |
| | | let shouldClose = false; |
| | | if (fileList.value.length === 0) { |
| | | message.warning('è¯·éæ©è¦å¯¼å
¥çæä»¶'); |
| | | return; |
| | | } |
| | | if (importing.value) return; |
| | | modalApi.lock(); |
| | | importing.value = true; |
| | | startMockProgress(); |
| | | try { |
| | | importResult.value = await importSaleOrder(fileList.value[0] as File); |
| | | // 宿ï¼è¿åº¦æ¡èµ°æ»¡ |
| | | stopMockProgress(); |
| | | progressPercent.value = 100; |
| | | await delay(300); |
| | | emit('success'); |
| | | const failedCount = Object.keys(importResult.value?.failureList ?? {}).length; |
| | | if (failedCount > 0) { |
| | | message.warning( |
| | | `导å
¥å®æï¼æ°å¢ ${createCount.value} æ¡ï¼å¤±è´¥ ${failedCount} æ¡ï¼è¯·æ¥ç䏿¹å¤±è´¥æç»`, |
| | | ); |
| | | } else { |
| | | message.success('导å
¥å®æ'); |
| | | shouldClose = true; |
| | | } |
| | | } finally { |
| | | stopMockProgress(); |
| | | importing.value = false; |
| | | modalApi.unlock(); |
| | | if (shouldClose) { |
| | | await modalApi.close(); |
| | | } |
| | | } |
| | | }, |
| | | }); |
| | | |
| | | /** 模æè¿åº¦æ¡ï¼è¯·æ±æé´ä» 0 èµ°å° 90% */ |
| | | function startMockProgress() { |
| | | progressPercent.value = 0; |
| | | progressTimer = setInterval(() => { |
| | | if (progressPercent.value < 90) { |
| | | progressPercent.value = Math.min( |
| | | 90, |
| | | progressPercent.value + Math.random() * 12, |
| | | ); |
| | | } |
| | | }, 300); |
| | | } |
| | | |
| | | function stopMockProgress() { |
| | | if (progressTimer) { |
| | | clearInterval(progressTimer); |
| | | progressTimer = undefined; |
| | | } |
| | | } |
| | | |
| | | function delay(ms: number) { |
| | | return new Promise((resolve) => setTimeout(resolve, ms)); |
| | | } |
| | | |
| | | function beforeUpload(file: FileType) { |
| | | fileList.value = [file]; |
| | | importResult.value = undefined; |
| | | return false; |
| | | } |
| | | |
| | | function handleRemove() { |
| | | fileList.value = []; |
| | | importResult.value = undefined; |
| | | } |
| | | |
| | | async function handleDownloadTemplate() { |
| | | const data = await importSaleOrderTemplate(); |
| | | downloadFileFromBlobPart({ fileName: 'éå®è®¢å导å
¥æ¨¡æ¿.xls', source: data }); |
| | | } |
| | | |
| | | const createCount = computed(() => importResult.value?.createList?.length ?? 0); |
| | | const updateCount = computed(() => importResult.value?.updateList?.length ?? 0); |
| | | const failureCount = computed( |
| | | () => Object.keys(importResult.value?.failureList ?? {}).length, |
| | | ); |
| | | </script> |
| | | |
| | | <template> |
| | | <Modal title="导å
¥éå®è®¢å" class="w-[720px]"> |
| | | <div class="mx-4"> |
| | | <Upload |
| | | :max-count="1" |
| | | accept=".xls,.xlsx" |
| | | :file-list="fileList" |
| | | :before-upload="beforeUpload" |
| | | @remove="handleRemove" |
| | | > |
| | | <Button type="primary">éæ© Excel æä»¶</Button> |
| | | </Upload> |
| | | <div class="mt-2 rounded-md bg-gray-50 p-2 text-xs leading-5 text-gray-500"> |
| | | <p>ä»
å
许导å
¥ xlsãxlsx æ ¼å¼æä»¶ï¼æ¨¡æ¿ä¸â客æ·åç§°âæåç§°å¡«åï¼éä¸ CRM 客æ·ä¸è´ï¼â产åç¼ç âæç©æç¼ç å¡«åï¼â产ååç§°âåä»
ä¾å¯¹ç
§ï¼å¹é
以产åç¼ç 为åï¼ï¼âä¸åæ¶é´âæ ¼å¼å¦ 2026-01-01 10:00:00ï¼ç空åå½åæ¶é´ï¼âåä»·âç空åç©æéå®ä»·ã</p> |
| | | <p>ä¸å¼ éå®åå¯å«å¤æ¡æç»ï¼ç¸åâéå®åç¼å·âçå¤è¡ä¼å并为ä¸å¼ 订åï¼ä¸»è¡¨å段å该ç»é¦è¡ï¼ï¼âéå®åç¼å·âçç©ºæ¶æ¯ä¸è¡åçæä¸å¼ æ°è®¢åãéå®åç¼å·å·²å卿¶ä¼å¯¼å
¥å¤±è´¥ã</p> |
| | | <p>导å
¥åç订åä¸ºâæªæäº¤âç¶æï¼å¯å¨å表ä¸ç»§ç»ç¼è¾ææäº¤å®¡æ¹ï¼å¯¼å
¥ä¸ä¼èªå¨æäº¤å®¡æ¹æµç¨ã</p> |
| | | </div> |
| | | <!-- 导å
¥è¿åº¦æ¡ --> |
| | | <div v-if="importing" class="mt-4"> |
| | | <div class="mb-2 text-sm text-gray-600">æ£å¨å¯¼å
¥éå®è®¢åæ°æ®ï¼è¯·ç¨å...</div> |
| | | <Progress :percent="progressPercent" status="active" /> |
| | | </div> |
| | | <!-- 导å
¥ç»æ --> |
| | | <div |
| | | v-if="importResult" |
| | | class="mt-4 rounded-md border border-gray-200 bg-gray-50 p-3" |
| | | > |
| | | <div class="mb-2 font-medium">导å
¥ç»æ</div> |
| | | <div class="mb-2 flex gap-4"> |
| | | <span class="text-green-600">æ°å¢ï¼{{ createCount }} å¼ </span> |
| | | <span class="text-blue-600">è¦çæ´æ°ï¼{{ updateCount }} å¼ </span> |
| | | <span class="text-red-600">失败ï¼{{ failureCount }} å¼ </span> |
| | | </div> |
| | | <div v-if="failureCount > 0" class="max-h-40 overflow-y-auto"> |
| | | <div |
| | | v-for="(reason, name) in importResult.failureList" |
| | | :key="name" |
| | | class="text-sm leading-6 text-red-500" |
| | | > |
| | | {{ name }}ï¼{{ reason }} |
| | | </div> |
| | | </div> |
| | | </div> |
| | | </div> |
| | | <template #prepend-footer> |
| | | <Button @click="handleDownloadTemplate">ä¸è½½å¯¼å
¥æ¨¡æ¿</Button> |
| | | </template> |
| | | </Modal> |
| | | </template> |
| | |
| | | |
| | | const [Modal, modalApi] = useVbenModal({ |
| | | async onConfirm() { |
| | | let shouldClose = false; |
| | | if (fileList.value.length === 0) { |
| | | message.warning('è¯·éæ©è¦å¯¼å
¥çæä»¶'); |
| | | return; |
| | |
| | | // 宿ï¼è¿åº¦æ¡èµ°æ»¡ |
| | | stopMockProgress(); |
| | | progressPercent.value = 100; |
| | | await delay(500); |
| | | message.success('导å
¥å®æ'); |
| | | await delay(300); |
| | | emit('success'); |
| | | const failedCount = Object.keys(importResult.value?.failureNames ?? {}).length; |
| | | if (failedCount > 0) { |
| | | message.warning( |
| | | `导å
¥å®æï¼æ°å¢ ${createCount.value} æ¡ï¼è¦ç ${updateCount.value} æ¡ï¼å¤±è´¥ ${failedCount} æ¡ï¼è¯·æ¥ç䏿¹å¤±è´¥æç»`, |
| | | ); |
| | | } else { |
| | | message.success('导å
¥å®æ'); |
| | | shouldClose = true; |
| | | } |
| | | } finally { |
| | | stopMockProgress(); |
| | | importing.value = false; |
| | | modalApi.unlock(); |
| | | if (shouldClose) { |
| | | await modalApi.close(); |
| | | } |
| | | } |
| | | }, |
| | | }); |
| | |
| | | |
| | | const [Modal, modalApi] = useVbenModal({ |
| | | async onConfirm() { |
| | | let shouldClose = false; |
| | | if (fileList.value.length === 0) { |
| | | message.warning('è¯·éæ©è¦å¯¼å
¥çæä»¶'); |
| | | return; |
| | |
| | | // 宿ï¼è¿åº¦æ¡èµ°æ»¡ |
| | | stopMockProgress(); |
| | | progressPercent.value = 100; |
| | | await delay(500); |
| | | message.success('导å
¥å®æ'); |
| | | await delay(300); |
| | | emit('success'); |
| | | const failedCount = Object.keys(importResult.value?.failureNames ?? {}).length; |
| | | if (failedCount > 0) { |
| | | message.warning( |
| | | `导å
¥å®æï¼æå ${createCount.value} æ¡ãè¦ç ${updateCount.value} æ¡ï¼å¤±è´¥ ${failedCount} æ¡ï¼è¯·æ¥ç䏿¹å¤±è´¥æç»`, |
| | | ); |
| | | } else { |
| | | message.success('导å
¥å®æ'); |
| | | shouldClose = true; |
| | | } |
| | | } finally { |
| | | stopMockProgress(); |
| | | importing.value = false; |
| | | modalApi.unlock(); |
| | | if (shouldClose) { |
| | | await modalApi.close(); |
| | | } |
| | | } |
| | | }, |
| | | }); |
| | |
| | | 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, |
| | | }); |
| | | |
| | | /** å·æ°è¡¨æ ¼ */ |
| | | function handleRefresh() { |
| | | gridApi.query(); |
| | | } |
| | | |
| | | /** 导å
¥è®¡éå¨å
· */ |
| | | function handleImport() { |
| | | importModalApi.open(); |
| | | } |
| | | |
| | | /** å建计éå¨å
· */ |
| | |
| | | <template> |
| | | <Page auto-content-height> |
| | | <FormModal @success="handleRefresh" /> |
| | | <ImportModal @success="handleRefresh" /> |
| | | <Grid table-title="计éå¨å
·å表"> |
| | | <template #toolbar-tools> |
| | | <TableAction |
| | |
| | | auth: ['mes:dv-metering:export'], |
| | | onClick: handleExport, |
| | | }, |
| | | { |
| | | label: '导å
¥', |
| | | type: 'primary', |
| | | icon: ACTION_ICON.UPLOAD, |
| | | auth: ['mes:dv-metering:import'], |
| | | onClick: handleImport, |
| | | }, |
| | | ]" |
| | | /> |
| | | </template> |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | <script lang="ts" setup> |
| | | import type { FileType } from 'ant-design-vue/es/upload/interface'; |
| | | import type { ImportResult } from '#/api/mes/dv/metering'; |
| | | |
| | | import { computed, ref } from 'vue'; |
| | | |
| | | import { useVbenModal } from '#/packages/effects/common-ui/src'; |
| | | import { downloadFileFromBlobPart } from '#/packages/utils/src'; |
| | | |
| | | import { Button, message, Progress, Upload } from 'ant-design-vue'; |
| | | |
| | | import { importMetering, importMeteringTemplate } from '#/api/mes/dv/metering'; |
| | | |
| | | const emit = defineEmits(['success']); |
| | | |
| | | const fileList = ref<FileType[]>([]); |
| | | const importResult = ref<ImportResult>(); |
| | | const importing = ref(false); |
| | | const progressPercent = ref(0); |
| | | let progressTimer: ReturnType<typeof setInterval> | undefined; |
| | | |
| | | const [Modal, modalApi] = useVbenModal({ |
| | | async onConfirm() { |
| | | let shouldClose = false; |
| | | if (fileList.value.length === 0) { |
| | | message.warning('è¯·éæ©è¦å¯¼å
¥çæä»¶'); |
| | | return; |
| | | } |
| | | if (importing.value) return; |
| | | modalApi.lock(); |
| | | importing.value = true; |
| | | startMockProgress(); |
| | | try { |
| | | importResult.value = await importMetering(fileList.value[0] as File); |
| | | // 宿ï¼è¿åº¦æ¡èµ°æ»¡ |
| | | stopMockProgress(); |
| | | progressPercent.value = 100; |
| | | await delay(300); |
| | | emit('success'); |
| | | const failedCount = Object.keys(importResult.value?.failureList ?? {}).length; |
| | | if (failedCount > 0) { |
| | | message.warning( |
| | | `导å
¥å®æï¼æ°å¢ ${createCount.value} æ¡ï¼å¤±è´¥ ${failedCount} æ¡ï¼è¯·æ¥ç䏿¹å¤±è´¥æç»`, |
| | | ); |
| | | } else { |
| | | message.success('导å
¥å®æ'); |
| | | shouldClose = true; |
| | | } |
| | | } finally { |
| | | stopMockProgress(); |
| | | importing.value = false; |
| | | modalApi.unlock(); |
| | | if (shouldClose) { |
| | | await modalApi.close(); |
| | | } |
| | | } |
| | | }, |
| | | }); |
| | | |
| | | /** 模æè¿åº¦æ¡ï¼è¯·æ±æé´ä» 0 èµ°å° 90% */ |
| | | function startMockProgress() { |
| | | progressPercent.value = 0; |
| | | progressTimer = setInterval(() => { |
| | | if (progressPercent.value < 90) { |
| | | progressPercent.value = Math.min( |
| | | 90, |
| | | progressPercent.value + Math.random() * 12, |
| | | ); |
| | | } |
| | | }, 300); |
| | | } |
| | | |
| | | function stopMockProgress() { |
| | | if (progressTimer) { |
| | | clearInterval(progressTimer); |
| | | progressTimer = undefined; |
| | | } |
| | | } |
| | | |
| | | function delay(ms: number) { |
| | | return new Promise((resolve) => setTimeout(resolve, ms)); |
| | | } |
| | | |
| | | function beforeUpload(file: FileType) { |
| | | fileList.value = [file]; |
| | | importResult.value = undefined; |
| | | return false; |
| | | } |
| | | |
| | | function handleRemove() { |
| | | fileList.value = []; |
| | | importResult.value = undefined; |
| | | } |
| | | |
| | | async function handleDownloadTemplate() { |
| | | const data = await importMeteringTemplate(); |
| | | downloadFileFromBlobPart({ fileName: '计éå¨å
·å¯¼å
¥æ¨¡æ¿.xls', source: data }); |
| | | } |
| | | |
| | | const createCount = computed(() => importResult.value?.createList?.length ?? 0); |
| | | const updateCount = computed(() => importResult.value?.updateList?.length ?? 0); |
| | | const failureCount = computed( |
| | | () => Object.keys(importResult.value?.failureList ?? {}).length, |
| | | ); |
| | | </script> |
| | | |
| | | <template> |
| | | <Modal title="导å
¥è®¡éå¨å
·" class="w-[720px]"> |
| | | <div class="mx-4"> |
| | | <Upload |
| | | :max-count="1" |
| | | accept=".xls,.xlsx" |
| | | :file-list="fileList" |
| | | :before-upload="beforeUpload" |
| | | @remove="handleRemove" |
| | | > |
| | | <Button type="primary">éæ© Excel æä»¶</Button> |
| | | </Upload> |
| | | <div class="mt-2 rounded-md bg-gray-50 p-2 text-xs leading-5 text-gray-500"> |
| | | <p>ä»
å
许导å
¥ xlsãxlsx æ ¼å¼æä»¶ãå¿
å¡«åï¼è®¡éå¨å
·ç¼ç ã计éå¨å
·åç§°ãå¨å
·ç±»å«ãç¶æï¼âå¨å
·ç±»å« / ç¶æâå¡«åå
¸ä¸æï¼å¦ å¡å°º/å
¶ä»ãå¨ç¨/åç¨/æ¥åºï¼ã</p> |
| | | <p>â使ç¨é¨é¨ / 责任人âæç³»ç»å
çé¨é¨åç§° / ç¨æ·æµç§°å¹é
ï¼å¹é
ä¸å°è¯¥è¡ä¼å¤±è´¥ï¼âæ ¡å卿(æ)âç空é»è®¤ 12ï¼âæå
¥ä½¿ç¨æ¥æâæ ¼å¼å¦ 2026-01-01ã</p> |
| | | <p>计éå¨å
·ç¼ç å·²å卿¶ä¼å¯¼å
¥å¤±è´¥ï¼ä»
æ°å¢ï¼ä¸è¦çï¼ã</p> |
| | | </div> |
| | | <!-- 导å
¥è¿åº¦æ¡ --> |
| | | <div v-if="importing" class="mt-4"> |
| | | <div class="mb-2 text-sm text-gray-600">æ£å¨å¯¼å
¥è®¡éå¨å
·æ°æ®ï¼è¯·ç¨å...</div> |
| | | <Progress :percent="progressPercent" status="active" /> |
| | | </div> |
| | | <!-- 导å
¥ç»æ --> |
| | | <div |
| | | v-if="importResult" |
| | | class="mt-4 rounded-md border border-gray-200 bg-gray-50 p-3" |
| | | > |
| | | <div class="mb-2 font-medium">导å
¥ç»æ</div> |
| | | <div class="mb-2 flex gap-4"> |
| | | <span class="text-green-600">æ°å¢ï¼{{ createCount }} æ¡</span> |
| | | <span class="text-blue-600">è¦çæ´æ°ï¼{{ updateCount }} æ¡</span> |
| | | <span class="text-red-600">失败ï¼{{ failureCount }} æ¡</span> |
| | | </div> |
| | | <div v-if="failureCount > 0" class="max-h-40 overflow-y-auto"> |
| | | <div |
| | | v-for="(reason, name) in importResult.failureList" |
| | | :key="name" |
| | | class="text-sm leading-6 text-red-500" |
| | | > |
| | | {{ name }}ï¼{{ reason }} |
| | | </div> |
| | | </div> |
| | | </div> |
| | | </div> |
| | | <template #prepend-footer> |
| | | <Button @click="handleDownloadTemplate">ä¸è½½å¯¼å
¥æ¨¡æ¿</Button> |
| | | </template> |
| | | </Modal> |
| | | </template> |
| | |
| | | |
| | | const [Modal, modalApi] = useVbenModal({ |
| | | async onConfirm() { |
| | | let shouldClose = false; |
| | | if (fileList.value.length === 0) { |
| | | message.warning('è¯·éæ©è¦å¯¼å
¥çæä»¶'); |
| | | return; |
| | |
| | | // 宿ï¼è¿åº¦æ¡èµ°æ»¡ |
| | | stopMockProgress(); |
| | | progressPercent.value = 100; |
| | | await delay(500); |
| | | message.success('导å
¥å®æ'); |
| | | await delay(300); |
| | | emit('success'); |
| | | const failedCount = Object.keys(importResult.value?.failureNames ?? {}).length; |
| | | if (failedCount > 0) { |
| | | message.warning( |
| | | `导å
¥å®æï¼æå ${createCount.value} æ¡ãè¦ç ${updateCount.value} æ¡ï¼å¤±è´¥ ${failedCount} æ¡ï¼è¯·æ¥ç䏿¹å¤±è´¥æç»`, |
| | | ); |
| | | } else { |
| | | message.success('导å
¥å®æ'); |
| | | shouldClose = true; |
| | | } |
| | | } finally { |
| | | stopMockProgress(); |
| | | importing.value = false; |
| | | modalApi.unlock(); |
| | | if (shouldClose) { |
| | | await modalApi.close(); |
| | | } |
| | | } |
| | | }, |
| | | }); |