2026-01-29 22:03:42 +08:00
|
|
|
<template>
|
2026-02-05 16:33:33 +08:00
|
|
|
<div>
|
|
|
|
|
<!-- 导入对话框 -->
|
|
|
|
|
<el-dialog
|
|
|
|
|
:title="title"
|
|
|
|
|
:visible.sync="visible"
|
|
|
|
|
width="550px"
|
|
|
|
|
center
|
|
|
|
|
append-to-body
|
|
|
|
|
@open="handleDialogOpen"
|
|
|
|
|
@close="handleDialogClose"
|
|
|
|
|
:close-on-click-modal="false"
|
|
|
|
|
:close-on-press-escape="false"
|
|
|
|
|
custom-class="import-dialog-wrapper"
|
|
|
|
|
>
|
|
|
|
|
<!-- 全屏Loading遮罩层 -->
|
|
|
|
|
<div v-show="isUploading" class="import-loading-overlay">
|
|
|
|
|
<i class="el-icon-loading"></i>
|
|
|
|
|
<p>正在导入中,请稍候...</p>
|
|
|
|
|
</div>
|
2026-01-29 22:03:42 +08:00
|
|
|
|
2026-02-05 16:33:33 +08:00
|
|
|
<el-form :model="formData" label-position="top" size="medium">
|
|
|
|
|
<!-- 导入类型 -->
|
|
|
|
|
<el-form-item label="导入类型">
|
|
|
|
|
<el-radio-group v-model="formData.importType" @change="handleImportTypeChange" style="width: 100%">
|
|
|
|
|
<el-radio label="person" border>个人中介</el-radio>
|
|
|
|
|
<el-radio label="entity" border>机构中介</el-radio>
|
|
|
|
|
</el-radio-group>
|
|
|
|
|
</el-form-item>
|
2026-01-29 22:03:42 +08:00
|
|
|
|
2026-02-05 16:33:33 +08:00
|
|
|
<!-- 文件上传 -->
|
|
|
|
|
<el-form-item label="选择文件">
|
|
|
|
|
<el-upload
|
|
|
|
|
ref="upload"
|
|
|
|
|
:limit="1"
|
|
|
|
|
accept=".xlsx, .xls"
|
|
|
|
|
:headers="headers"
|
|
|
|
|
:action="uploadUrl"
|
|
|
|
|
:disabled="isUploading"
|
|
|
|
|
:on-progress="handleFileUploadProgress"
|
|
|
|
|
:on-success="handleFileSuccess"
|
|
|
|
|
:on-error="handleFileError"
|
|
|
|
|
:on-change="handleFileChange"
|
|
|
|
|
:on-remove="handleFileRemove"
|
|
|
|
|
:auto-upload="false"
|
|
|
|
|
drag
|
|
|
|
|
>
|
|
|
|
|
<i class="el-icon-upload"></i>
|
|
|
|
|
<div class="el-upload__text">将文件拖到此处,或<em>点击上传</em></div>
|
|
|
|
|
<div class="el-upload__tip" slot="tip">
|
|
|
|
|
仅支持 .xls 和 .xlsx 格式,文件大小不超过 10MB
|
|
|
|
|
</div>
|
|
|
|
|
</el-upload>
|
|
|
|
|
</el-form-item>
|
2026-01-29 22:03:42 +08:00
|
|
|
|
2026-02-09 09:10:35 +08:00
|
|
|
<!-- 下载模板 -->
|
2026-02-05 16:33:33 +08:00
|
|
|
<el-form-item>
|
2026-02-09 09:10:35 +08:00
|
|
|
<el-link type="primary" :underline="false" @click="handleDownloadTemplate">
|
|
|
|
|
<i class="el-icon-download"></i>
|
|
|
|
|
下载导入模板
|
|
|
|
|
</el-link>
|
2026-02-05 16:33:33 +08:00
|
|
|
</el-form-item>
|
|
|
|
|
</el-form>
|
2026-01-29 22:03:42 +08:00
|
|
|
|
2026-02-05 16:33:33 +08:00
|
|
|
<!-- 底部按钮 -->
|
|
|
|
|
<div slot="footer" class="dialog-footer">
|
|
|
|
|
<el-button
|
|
|
|
|
type="primary"
|
|
|
|
|
icon="el-icon-upload2"
|
|
|
|
|
:loading="isUploading"
|
|
|
|
|
:disabled="!isFileSelected"
|
|
|
|
|
@click="handleSubmit"
|
|
|
|
|
>
|
|
|
|
|
{{ isUploading ? '导入中...' : '开始导入' }}
|
|
|
|
|
</el-button>
|
|
|
|
|
<el-button icon="el-icon-close" @click="visible = false" :disabled="isUploading">
|
|
|
|
|
取 消
|
|
|
|
|
</el-button>
|
|
|
|
|
</div>
|
|
|
|
|
</el-dialog>
|
2026-02-05 16:31:01 +08:00
|
|
|
|
2026-02-05 16:33:33 +08:00
|
|
|
<!-- 导入结果对话框 -->
|
|
|
|
|
<import-result-dialog
|
|
|
|
|
:visible.sync="importResultVisible"
|
|
|
|
|
:content="importResultContent"
|
|
|
|
|
title="导入结果"
|
|
|
|
|
@close="handleImportResultClose"
|
|
|
|
|
/>
|
|
|
|
|
</div>
|
2026-01-29 22:03:42 +08:00
|
|
|
</template>
|
|
|
|
|
|
|
|
|
|
<script>
|
|
|
|
|
import {getToken} from "@/utils/auth";
|
2026-02-09 09:10:35 +08:00
|
|
|
import {getEntityImportStatus, getPersonImportStatus} from "@/api/ccdiIntermediary";
|
2026-02-05 16:31:01 +08:00
|
|
|
import ImportResultDialog from "@/components/ImportResultDialog.vue";
|
2026-01-29 22:03:42 +08:00
|
|
|
|
|
|
|
|
export default {
|
|
|
|
|
name: "ImportDialog",
|
2026-02-05 16:31:01 +08:00
|
|
|
components: { ImportResultDialog },
|
2026-01-29 22:03:42 +08:00
|
|
|
props: {
|
|
|
|
|
visible: {
|
|
|
|
|
type: Boolean,
|
|
|
|
|
default: false
|
|
|
|
|
},
|
|
|
|
|
title: {
|
|
|
|
|
type: String,
|
|
|
|
|
default: "数据导入"
|
|
|
|
|
}
|
|
|
|
|
},
|
|
|
|
|
data() {
|
|
|
|
|
return {
|
|
|
|
|
formData: {
|
2026-02-09 09:10:35 +08:00
|
|
|
importType: "person"
|
2026-01-29 22:03:42 +08:00
|
|
|
},
|
|
|
|
|
headers: { Authorization: "Bearer " + getToken() },
|
|
|
|
|
isUploading: false,
|
2026-02-05 16:31:01 +08:00
|
|
|
isFileSelected: false,
|
|
|
|
|
// 导入结果弹窗
|
|
|
|
|
importResultVisible: false,
|
2026-02-08 17:18:18 +08:00
|
|
|
importResultContent: "",
|
|
|
|
|
// 轮询状态
|
|
|
|
|
pollingTimer: null,
|
|
|
|
|
currentTaskId: null
|
2026-01-29 22:03:42 +08:00
|
|
|
};
|
|
|
|
|
},
|
|
|
|
|
computed: {
|
|
|
|
|
uploadUrl() {
|
|
|
|
|
const baseUrl = process.env.VUE_APP_BASE_API;
|
|
|
|
|
if (this.formData.importType === 'person') {
|
2026-02-09 09:10:35 +08:00
|
|
|
return `${baseUrl}/ccdi/intermediary/importPersonData`;
|
2026-01-29 22:03:42 +08:00
|
|
|
} else {
|
2026-02-09 09:10:35 +08:00
|
|
|
return `${baseUrl}/ccdi/intermediary/importEntityData`;
|
2026-01-29 22:03:42 +08:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
},
|
|
|
|
|
methods: {
|
|
|
|
|
handleDialogOpen() {
|
|
|
|
|
this.isFileSelected = false;
|
|
|
|
|
},
|
|
|
|
|
handleDialogClose() {
|
|
|
|
|
if (this.$refs.upload) {
|
|
|
|
|
this.$refs.upload.clearFiles();
|
|
|
|
|
}
|
|
|
|
|
this.isFileSelected = false;
|
|
|
|
|
this.$emit("close");
|
|
|
|
|
},
|
|
|
|
|
handleImportTypeChange() {
|
|
|
|
|
if (this.$refs.upload) {
|
|
|
|
|
this.$refs.upload.clearFiles();
|
|
|
|
|
}
|
|
|
|
|
this.isFileSelected = false;
|
|
|
|
|
},
|
|
|
|
|
handleDownloadTemplate() {
|
|
|
|
|
if (this.formData.importType === 'person') {
|
2026-02-05 13:33:27 +08:00
|
|
|
this.download('ccdi/intermediary/importPersonTemplate', {}, `个人中介黑名单模板_${new Date().getTime()}.xlsx`);
|
2026-01-29 22:03:42 +08:00
|
|
|
} else {
|
2026-02-05 13:33:27 +08:00
|
|
|
this.download('ccdi/intermediary/importEntityTemplate', {}, `机构中介黑名单模板_${new Date().getTime()}.xlsx`);
|
2026-01-29 22:03:42 +08:00
|
|
|
}
|
|
|
|
|
},
|
|
|
|
|
handleFileUploadProgress() {
|
|
|
|
|
this.isUploading = true;
|
|
|
|
|
},
|
|
|
|
|
handleFileChange(file, fileList) {
|
|
|
|
|
this.isFileSelected = fileList.length > 0;
|
|
|
|
|
},
|
|
|
|
|
handleFileRemove(file, fileList) {
|
|
|
|
|
this.isFileSelected = fileList.length > 0;
|
|
|
|
|
},
|
|
|
|
|
handleFileSuccess(response) {
|
|
|
|
|
this.isUploading = false;
|
2026-02-05 13:33:27 +08:00
|
|
|
|
2026-02-08 17:18:18 +08:00
|
|
|
if (response.code === 200 && response.data && response.data.taskId) {
|
|
|
|
|
const taskId = response.data.taskId;
|
|
|
|
|
this.currentTaskId = taskId;
|
2026-02-05 13:33:27 +08:00
|
|
|
|
2026-02-08 17:18:18 +08:00
|
|
|
// 显示通知
|
|
|
|
|
this.$notify({
|
|
|
|
|
title: '导入任务已提交',
|
|
|
|
|
message: '正在后台处理中,处理完成后将通知您',
|
|
|
|
|
type: 'info',
|
|
|
|
|
duration: 3000
|
|
|
|
|
});
|
2026-02-05 13:33:27 +08:00
|
|
|
|
2026-02-08 17:18:18 +08:00
|
|
|
// 关闭对话框 - 使用$emit更新父组件的visible
|
|
|
|
|
this.$emit('update:visible', false);
|
|
|
|
|
this.$refs.upload.clearFiles();
|
|
|
|
|
|
|
|
|
|
// 通知父组件刷新列表
|
|
|
|
|
this.$emit("success");
|
|
|
|
|
|
|
|
|
|
// 开始轮询
|
|
|
|
|
this.startImportStatusPolling(taskId);
|
|
|
|
|
} else {
|
|
|
|
|
this.$modal.msgError(response.msg || '导入失败');
|
|
|
|
|
}
|
2026-01-29 22:03:42 +08:00
|
|
|
},
|
2026-02-05 16:31:01 +08:00
|
|
|
// 导入结果弹窗关闭
|
|
|
|
|
handleImportResultClose() {
|
|
|
|
|
this.importResultVisible = false;
|
|
|
|
|
this.importResultContent = "";
|
|
|
|
|
},
|
2026-01-29 22:03:42 +08:00
|
|
|
handleFileError() {
|
|
|
|
|
this.isUploading = false;
|
|
|
|
|
this.$modal.msgError("导入失败,请检查文件格式是否正确");
|
|
|
|
|
this.$refs.upload.clearFiles();
|
|
|
|
|
},
|
|
|
|
|
handleSubmit() {
|
2026-02-08 18:34:52 +08:00
|
|
|
// 触发清除历史记录事件
|
|
|
|
|
this.$emit('clear-import-history', this.formData.importType);
|
|
|
|
|
|
|
|
|
|
// 提交文件上传
|
2026-01-29 22:03:42 +08:00
|
|
|
this.$refs.upload.submit();
|
2026-02-08 17:18:18 +08:00
|
|
|
},
|
|
|
|
|
/** 开始轮询导入状态 */
|
|
|
|
|
startImportStatusPolling(taskId) {
|
|
|
|
|
let pollCount = 0;
|
|
|
|
|
const maxPolls = 150; // 最多5分钟
|
|
|
|
|
|
|
|
|
|
this.pollingTimer = setInterval(async () => {
|
|
|
|
|
try {
|
|
|
|
|
pollCount++;
|
|
|
|
|
|
|
|
|
|
if (pollCount > maxPolls) {
|
|
|
|
|
clearInterval(this.pollingTimer);
|
|
|
|
|
this.$modal.msgWarning('导入任务处理超时,请联系管理员');
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 根据导入类型调用不同的API
|
|
|
|
|
const apiMethod = this.formData.importType === 'person'
|
|
|
|
|
? getPersonImportStatus
|
|
|
|
|
: getEntityImportStatus;
|
|
|
|
|
|
|
|
|
|
const response = await apiMethod(taskId);
|
|
|
|
|
|
|
|
|
|
if (response.data && response.data.status !== 'PROCESSING') {
|
|
|
|
|
clearInterval(this.pollingTimer);
|
|
|
|
|
this.handleImportComplete(response.data);
|
|
|
|
|
}
|
|
|
|
|
} catch (error) {
|
|
|
|
|
clearInterval(this.pollingTimer);
|
|
|
|
|
this.$modal.msgError('查询导入状态失败: ' + error.message);
|
|
|
|
|
}
|
|
|
|
|
}, 2000); // 每2秒轮询一次
|
|
|
|
|
},
|
|
|
|
|
/** 处理导入完成 */
|
|
|
|
|
handleImportComplete(statusResult) {
|
|
|
|
|
if (statusResult.status === 'SUCCESS') {
|
|
|
|
|
this.$notify({
|
|
|
|
|
title: '导入完成',
|
|
|
|
|
message: `全部成功!共导入${statusResult.totalCount}条数据`,
|
|
|
|
|
type: 'success',
|
|
|
|
|
duration: 5000
|
|
|
|
|
});
|
|
|
|
|
} else if (statusResult.failureCount > 0) {
|
|
|
|
|
this.$notify({
|
|
|
|
|
title: '导入完成',
|
|
|
|
|
message: `成功${statusResult.successCount}条,失败${statusResult.failureCount}条`,
|
|
|
|
|
type: 'warning',
|
|
|
|
|
duration: 5000
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 通知父组件更新失败记录状态
|
|
|
|
|
this.$emit("import-complete", {
|
|
|
|
|
taskId: statusResult.taskId,
|
|
|
|
|
hasFailures: statusResult.failureCount > 0,
|
|
|
|
|
importType: this.formData.importType,
|
|
|
|
|
totalCount: statusResult.totalCount,
|
|
|
|
|
successCount: statusResult.successCount,
|
|
|
|
|
failureCount: statusResult.failureCount
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
},
|
|
|
|
|
/** 组件销毁时清除定时器 */
|
|
|
|
|
beforeDestroy() {
|
|
|
|
|
if (this.pollingTimer) {
|
|
|
|
|
clearInterval(this.pollingTimer);
|
|
|
|
|
this.pollingTimer = null;
|
2026-01-29 22:03:42 +08:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
</script>
|
|
|
|
|
|
|
|
|
|
<style scoped lang="scss">
|
|
|
|
|
::v-deep .el-form {
|
|
|
|
|
.el-form-item {
|
|
|
|
|
margin-bottom: 22px;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.el-radio-group {
|
|
|
|
|
display: flex;
|
|
|
|
|
|
|
|
|
|
.el-radio {
|
|
|
|
|
flex: 1;
|
|
|
|
|
text-align: center;
|
|
|
|
|
margin-right: 0;
|
|
|
|
|
|
|
|
|
|
&:first-child {
|
|
|
|
|
.el-radio__label {
|
|
|
|
|
border-top-right-radius: 0;
|
|
|
|
|
border-bottom-right-radius: 0;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
&:last-child {
|
|
|
|
|
.el-radio__label {
|
|
|
|
|
border-top-left-radius: 0;
|
|
|
|
|
border-bottom-left-radius: 0;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
&.is-bordered {
|
|
|
|
|
padding: 10px 0;
|
|
|
|
|
height: auto;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.el-upload {
|
|
|
|
|
width: 100%;
|
|
|
|
|
|
|
|
|
|
.el-upload-dragger {
|
|
|
|
|
width: 100% !important;
|
|
|
|
|
height: 140px;
|
|
|
|
|
box-sizing: border-box;
|
|
|
|
|
|
|
|
|
|
.el-icon-upload {
|
|
|
|
|
font-size: 50px;
|
|
|
|
|
color: #409EFF;
|
|
|
|
|
margin: 15px 0 10px;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.el-upload__text {
|
|
|
|
|
font-size: 14px;
|
|
|
|
|
color: #606266;
|
|
|
|
|
|
|
|
|
|
em {
|
|
|
|
|
color: #409EFF;
|
|
|
|
|
font-style: normal;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.el-upload__tip {
|
|
|
|
|
font-size: 12px;
|
|
|
|
|
color: #909399;
|
|
|
|
|
margin-top: 8px;
|
|
|
|
|
line-height: 1.5;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.dialog-footer {
|
|
|
|
|
text-align: center;
|
|
|
|
|
padding: 5px 0 0;
|
|
|
|
|
|
|
|
|
|
.el-button {
|
|
|
|
|
min-width: 110px;
|
|
|
|
|
margin: 0 8px;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.import-loading-overlay {
|
|
|
|
|
position: fixed;
|
|
|
|
|
top: 0;
|
|
|
|
|
left: 0;
|
|
|
|
|
right: 0;
|
|
|
|
|
bottom: 0;
|
|
|
|
|
background-color: rgba(255, 255, 255, 0.9);
|
|
|
|
|
display: flex;
|
|
|
|
|
flex-direction: column;
|
|
|
|
|
align-items: center;
|
|
|
|
|
justify-content: center;
|
|
|
|
|
z-index: 10000;
|
|
|
|
|
|
|
|
|
|
.el-icon-loading {
|
|
|
|
|
font-size: 48px;
|
|
|
|
|
color: #409EFF;
|
|
|
|
|
animation: rotating 2s linear infinite;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
p {
|
|
|
|
|
margin-top: 15px;
|
|
|
|
|
font-size: 14px;
|
|
|
|
|
color: #606266;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@keyframes rotating {
|
|
|
|
|
from {
|
|
|
|
|
transform: rotate(0deg);
|
|
|
|
|
}
|
|
|
|
|
to {
|
|
|
|
|
transform: rotate(360deg);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
</style>
|