From 85a3578025c1fa732322613340cbb0bf2c31fdb3 Mon Sep 17 00:00:00 2001
From: 云 <2163098428@qq.com>
Date: 星期三, 02 九月 2026 16:56:39 +0800
Subject: [PATCH] feat(mes): 添加库存现有量导入功能
---
src/views/wls/materialstock/components/import-form.vue | 165 +++++++++++++++++++++++++++++++++++++++++
src/views/wls/materialstock/components/index.ts | 1
src/api/mes/wm/materialstock/index.ts | 23 +++++
src/views/wls/materialstock/index.vue | 29 +++++++
4 files changed, 217 insertions(+), 1 deletions(-)
diff --git a/src/api/mes/wm/materialstock/index.ts b/src/api/mes/wm/materialstock/index.ts
index edbd6fe..06a2911 100644
--- a/src/api/mes/wm/materialstock/index.ts
+++ b/src/api/mes/wm/materialstock/index.ts
@@ -64,6 +64,29 @@
});
}
+/** 搴撳瓨鐜版湁閲忓鍏ョ粨鏋� */
+export interface ImportResult {
+ /** 鏂板缓搴撳瓨璁板綍鎴愬姛鐨勮锛屽厓绱犳牸寮忋�岀墿鏂欑紪鐮�-浠撳簱銆� */
+ createNames?: string[];
+ /** 宸插瓨鍦ㄥ簱瀛樿褰曡皟鏁存垚鍔熺殑琛岋紝鍏冪礌鏍煎紡銆岀墿鏂欑紪鐮�-浠撳簱銆� */
+ updateNames?: string[];
+ /** 瀵煎叆澶辫触鐨勮褰曪紝key 涓恒�岀墿鏂欑紪鐮�-浠撳簱銆嶏紝value 涓哄け璐ュ師鍥� */
+ failureNames?: Record<string, string>;
+}
+
+/** 涓嬭浇搴撳瓨鐜版湁閲忓鍏ユā鏉� */
+export function importMaterialStockTemplate() {
+ return requestClient.download('/mes/wm/material-stock/import-template');
+}
+
+/** 瀵煎叆搴撳瓨鐜版湁閲� */
+export function importMaterialStock(file: File) {
+ return requestClient.upload<MesWmMaterialStockApi.ImportResult>(
+ '/mes/wm/material-stock/import',
+ { file },
+ );
+}
+
/** 搴撳瓨璋冩暣锛堣嚜瀹氫箟鍑哄叆搴擄級- 鍏ュ簱 */
export interface MaterialStockAdjustInParams {
itemId: number; // 鐗╂枡ID
diff --git a/src/views/wls/materialstock/components/import-form.vue b/src/views/wls/materialstock/components/import-form.vue
new file mode 100644
index 0000000..cbe0d00
--- /dev/null
+++ b/src/views/wls/materialstock/components/import-form.vue
@@ -0,0 +1,165 @@
+<script lang="ts" setup>
+import type { FileType } from 'ant-design-vue/es/upload/interface';
+import type { MesWmMaterialStockApi } from '#/api/mes/wm/materialstock';
+
+import { computed, ref } from 'vue';
+
+import { useVbenModal } from '@vben/common-ui';
+import { downloadFileFromBlobPart } from '@vben/utils';
+
+import { Button, message, Progress, Upload } from 'ant-design-vue';
+
+import {
+ importMaterialStock,
+ importMaterialStockTemplate,
+} from '#/api/mes/wm/materialstock';
+
+defineOptions({ name: 'WmMaterialStockImportModal' });
+
+const emit = defineEmits<{ success: [] }>();
+
+const fileList = ref<FileType[]>([]);
+const importResult = ref<MesWmMaterialStockApi.ImportResult>();
+const importing = ref(false);
+const progressPercent = ref(0);
+let progressTimer: ReturnType<typeof setInterval> | undefined;
+
+const [Modal, modalApi] = useVbenModal({
+ async onConfirm() {
+ if (fileList.value.length === 0) {
+ message.warning('璇烽�夋嫨瑕佸鍏ョ殑鏂囦欢');
+ return;
+ }
+ if (importing.value) return;
+ modalApi.lock();
+ importing.value = true;
+ startMockProgress();
+ try {
+ importResult.value = await importMaterialStock(fileList.value[0] as File);
+ // 瀹屾垚锛氳繘搴︽潯璧版弧
+ stopMockProgress();
+ progressPercent.value = 100;
+ await delay(500);
+ message.success('瀵煎叆瀹屾垚');
+ emit('success');
+ } finally {
+ stopMockProgress();
+ importing.value = false;
+ modalApi.unlock();
+ }
+ },
+});
+
+/** 妯℃嫙杩涘害鏉★細璇锋眰鏈熼棿浠� 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 importMaterialStockTemplate();
+ downloadFileFromBlobPart({ fileName: '搴撳瓨鐜版湁閲忓鍏ユā鏉�.xls', source: data });
+}
+
+const createCount = computed(() => importResult.value?.createNames?.length ?? 0);
+const updateCount = computed(() => importResult.value?.updateNames?.length ?? 0);
+const failureCount = computed(
+ () => Object.keys(importResult.value?.failureNames ?? {}).length,
+);
+</script>
+
+<template>
+ <Modal title="瀵煎叆搴撳瓨鐜版湁閲�" class="w-[640px]">
+ <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-3 rounded-md border border-gray-200 bg-gray-50 p-3 text-sm leading-6 text-gray-600"
+ >
+ 妯℃澘鍒楋細鐗╂枡缂栫爜銆佷粨搴撱�佸簱鍖恒�佸簱浣嶃�佹壒娆″彿銆佷緵搴斿晢銆�<b>鎿嶄綔绫诲瀷</b>銆�<b>鍦ㄥ簱鏁伴噺</b>銆�<br />
+ 鎿嶄綔绫诲瀷蹇呭~锛�
+ <b class="text-gray-800">澧炲姞</b> / <b class="text-gray-800">鍑忓皯</b>
+ 鎸�"鍦ㄥ簱鏁伴噺"浣滀负鏈鍙樺姩閲忓姞鍑忓簱瀛橈紱
+ <b class="text-gray-800">瑕嗙洊</b>
+ 鎶婅璁板綍鍦ㄥ簱鏁伴噺鐩存帴璁句负"鍦ㄥ簱鏁伴噺"锛堟湡鍒濆缓璐︽帹鑽愶級锛屽凡瀛樺湪璁板綍鑷姩鎸夊樊棰濊皟鏁淬��<br />
+ 浠撳簱/搴撳尯/搴撲綅/渚涘簲鍟嗗彲濉紪鐮佹垨鍚嶇О锛涚墿鏂欏惎鐢ㄦ壒娆$鐞嗘椂鎵规鍙峰繀濉紱姣忚鐙珛鎵ц锛屽け璐ヨ涓嶅奖鍝嶅叾瀹冭锛涗粎鏀寔
+ xls銆亁lsx 鏂囦欢
+ </div>
+ <!-- 瀵煎叆杩涘害鏉� -->
+ <div v-if="importing" class="mt-4">
+ <div class="mb-2 text-sm text-gray-600">姝e湪瀵煎叆搴撳瓨鏁版嵁锛岃绋嶅��...</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 flex-wrap 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="createCount > 0" class="max-h-32 overflow-y-auto text-sm leading-6 text-green-600">
+ <div v-for="name in importResult.createNames" :key="`c-${name}`">
+ {{ name }}
+ </div>
+ </div>
+ <div v-if="updateCount > 0" class="max-h-32 overflow-y-auto text-sm leading-6 text-blue-600">
+ <div v-for="name in importResult.updateNames" :key="`u-${name}`">
+ {{ name }}
+ </div>
+ </div>
+ <div v-if="failureCount > 0" class="max-h-40 overflow-y-auto">
+ <div
+ v-for="(reason, name) in importResult.failureNames"
+ :key="`f-${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>
\ No newline at end of file
diff --git a/src/views/wls/materialstock/components/index.ts b/src/views/wls/materialstock/components/index.ts
index 60d3bf1..7ecabaf 100644
--- a/src/views/wls/materialstock/components/index.ts
+++ b/src/views/wls/materialstock/components/index.ts
@@ -2,3 +2,4 @@
export { default as WmMaterialStockSelect } from './select.vue';
export { default as WmStockAdjustModal } from './stock-adjust-modal.vue';
export { default as WmStockMoveModal } from './move-modal.vue';
+export { default as WmMaterialStockImportModal } from './import-form.vue';
diff --git a/src/views/wls/materialstock/index.vue b/src/views/wls/materialstock/index.vue
index 17e3625..91a398b 100644
--- a/src/views/wls/materialstock/index.vue
+++ b/src/views/wls/materialstock/index.vue
@@ -19,7 +19,11 @@
import { MdItemTypeTree } from '#/views/mes/md/item/type/components';
import { WmBatchDetail } from '#/views/wls/batch/components';
import AreaForm from '#/views/wls/warehouse/area/modules/form.vue';
-import { WmStockAdjustModal, WmStockMoveModal } from './components';
+import {
+ WmMaterialStockImportModal,
+ WmStockAdjustModal,
+ WmStockMoveModal,
+} from './components';
import { useGridColumns, useGridFormSchema } from './data';
@@ -35,6 +39,11 @@
const [StockMoveModal, stockMoveModalApi] = useVbenModal({
connectedComponent: WmStockMoveModal,
+ destroyOnClose: true,
+});
+
+const [ImportModal, importModalApi] = useVbenModal({
+ connectedComponent: WmMaterialStockImportModal,
destroyOnClose: true,
});
@@ -72,6 +81,16 @@
/** 鎵撳紑搴撳瓨璋冩暣寮圭獥 */
function handleOpenStockAdjust() {
stockAdjustModalApi.setData({}).open();
+}
+
+/** 鎵撳紑搴撳瓨鐜版湁閲忓鍏ュ脊绐� */
+function handleOpenImport() {
+ importModalApi.open();
+}
+
+/** 瀵煎叆鎴愬姛鍥炶皟 */
+function handleStockImportSuccess() {
+ gridApi.query();
}
/** 搴撳瓨璋冩暣鎴愬姛鍥炶皟 */
@@ -155,6 +174,7 @@
<WmBatchDetail ref="batchDetailRef" />
<StockAdjustModal @success="handleStockAdjustSuccess" />
<StockMoveModal @success="handleStockMoveSuccess" />
+ <ImportModal @success="handleStockImportSuccess" />
<div class="flex h-full w-full">
<!-- 宸︿晶鐗╂枡鍒嗙被鏍� -->
@@ -175,6 +195,13 @@
onClick: handleOpenStockAdjust,
},
{
+ label: '瀵煎叆',
+ type: 'primary',
+ icon: ACTION_ICON.UPLOAD,
+ auth: ['mes:wm-material-stock:import'],
+ onClick: handleOpenImport,
+ },
+ {
label: $t('ui.actionTitle.export'),
type: 'primary',
icon: ACTION_ICON.DOWNLOAD,
--
Gitblit v1.9.3