liu
2 天以前 5a322d24b59c4b70c08e792f21162be0f05d0199
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
<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>