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
<script lang="ts" setup>
import type { FileType } from 'ant-design-vue/es/upload/interface';
import type { HrmEmployeeContractApi } from '#/api/hrm/employee/contract';
 
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 {
  importEmployeeContract,
  importEmployeeContractTemplate,
} from '#/api/hrm/employee/contract';
 
const emit = defineEmits(['success']);
 
const fileList = ref<FileType[]>([]);
const updateSupport = ref(false);
const importResult = ref<HrmEmployeeContractApi.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 importEmployeeContract(
        fileList.value[0] as File,
        updateSupport.value,
      );
      // 完成:进度条走满
      stopMockProgress();
      progressPercent.value = 100;
      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();
      }
    }
  },
});
 
/** 模拟进度条:请求期间从 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 importEmployeeContractTemplate();
  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-[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="updateSupport" />
      </div>
      <div class="mt-2 rounded-md bg-gray-50 p-2 text-xs leading-5 text-gray-500">
        <p>仅允许导入 xls、xlsx 格式文件;模板中“员工姓名/合同类型/合同期限类型”按名称或中文填写(合同类型如:劳动合同、劳务合同)。</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.failureNames"
            :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>