Files
userbase/src/views/group-user/user/BatchImportUser.vue
T
2026-08-12 19:19:46 +08:00

178 lines
5.6 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<template>
<Modal v-model="visible" title="批量导入用户" :mask-closable="false" :width="520" @on-visible-change="onVisibleChange">
<div class="import-wrap">
<div class="form-item">
<a @click="downloadTemplate" href="javascript:;">点击链接下载[用户导入模板.xlsx]</a>
</div>
<div class="upload-panel">
<Upload type="drag" :action="uploadUrl" :headers="uploadHeaders" :with-credentials="true"
accept="application/vnd.openxmlformats-officedocument.spreadsheetml.sheet, application/vnd.ms-excel"
:before-upload="beforeUpload" :on-success="onUploadSuccess" :on-error="onUploadError">
<div class="upload-box">
<Icon type="ios-cloud-upload" size="52" style="color: #6281eb"></Icon>
<h4>选择要上传的文件</h4>
<div class="info">支持扩展名:xls、xlsx;文件大小不超过 10MB</div>
</div>
</Upload>
</div>
<Alert v-if="resultMsg" :type="resultType" show-icon>
<span v-html="resultMsg"></span>
</Alert>
<Alert v-else style="margin-bottom: 0" type="warning" show-icon>
请按照模板格式填写,组 id 以第二列为准;左侧组名仅供参考。
</Alert>
</div>
<template #footer>
<Button @click="close">关闭</Button>
</template>
</Modal>
</template>
<script setup lang="ts">
import { computed, ref } from "vue";
import { Message } from "view-ui-plus";
import Utils from "@/utils";
import { baseConfig } from "@/assets/config/api.config";
const props = defineProps<{
modelValue: boolean;
currentGroupId?: number | null;
}>();
const emit = defineEmits<{
'update:modelValue': [value: boolean];
'success': [];
}>();
const visible = computed({
get: () => props.modelValue,
set: (val) => emit('update:modelValue', val),
});
const resultMsg = ref("");
const resultType = ref<"success" | "error">("success");
const uploadUrl = computed(() => {
const base = String(baseConfig.baseURL || "");
return `${base.replace(/\/$/, "")}/api/sysuser/BatchImport`;
});
const uploadHeaders = computed(() => {
const roleInfo = Utils.getCurrentRoleInfo() || {} as any;
return {
yschapirequest: "1",
schoolId: roleInfo.schId ?? "",
};
});
function close() {
visible.value = false;
}
function onVisibleChange(show: boolean) {
if (!show) {
resultMsg.value = "";
}
}
const downloadUrl = computed(() => {
const base = String(baseConfig.baseURL || "").replace(/\/$/, "");
const gid = props.currentGroupId;
return `${base}/api/sysuser/DownloadTemplate${gid ? `?groupId=${gid}` : ""}`;
});
function downloadTemplate() {
// 模板由后端生成真实 .xlsx(平铺当前组及子孙组),沿用 BschImport 的下载方式
window.location.href = downloadUrl.value;
}
function beforeUpload(file: File) {
const isExcel = /\.(xls|xlsx)$/i.test(file.name);
if (!isExcel) {
Message.error("仅支持 xls/xlsx 格式的 Excel 文件");
return false;
}
if (file.size > 10 * 1024 * 1024) {
Message.error("文件大小不能超过 10MB");
return false;
}
resultMsg.value = "";
return true;
}
function onUploadSuccess(res: any) {
// 后端可能把结果统一包装为 {code,msg,data},也可能直接返回 {total,success,fail,fails},两者都兼容。
const wrapped = typeof res?.code === "number";
const payload = wrapped && res.data && typeof res.data === "object" ? res.data : res;
const code = res?.code;
// 包装体且业务码非 0 => 失败(含参数错误 code=4、业务错误等)
if (wrapped && code !== 0) {
resultType.value = "error";
resultMsg.value = res?.msg || "导入失败";
return;
}
const total = Number(payload?.total ?? 0);
const success = Number(payload?.success ?? 0);
const fail = Number(payload?.fail ?? 0);
const fails = Array.isArray(payload?.fails) ? payload.fails : [];
if (total === 0 && success === 0 && fail === 0) {
// 极端兜底:返回结构异常
resultType.value = "error";
resultMsg.value = "导入返回结构异常,请联系管理员";
return;
}
resultType.value = "success";
let msg = `导入完成:共 ${total} 条,成功 ${success} 条,失败 ${fail} 条。`;
if (fail > 0 && fails.length) {
const lines = fails.slice(0, 5).map((f: any) => `第 ${f.row} 行:${f.reason}`);
if (fails.length > 5) lines.push(`…等 ${fails.length} 条`);
msg += `<br/>失败明细:<br/>${lines.join("<br/>")}`;
}
resultMsg.value = msg;
if (fail === 0 && total > 0) {
emit('success');
}
}
function onUploadError(err: any) {
resultType.value = "error";
// 包装体业务错误可能经由 on-error 返回(如 HTTP 非 200),优先取 msg
const msg = err?.msg || (typeof err === "string" ? err : err?.message) || "上传失败,请检查网络或后端服务";
resultMsg.value = msg;
}
</script>
<style lang="scss" scoped>
.import-wrap {
padding: 8px 0;
.form-item {
margin-bottom: 16px;
}
}
.upload-panel {
.upload-box {
background: #f7f8fc;
padding: 28px 0;
text-align: center;
h4 {
color: #505c7e;
margin: 8px 0 4px;
font-weight: normal;
}
}
.info {
font-size: 13px;
color: #999;
}
}
</style>