Archived
forked from admin/userbase
This commit is contained in:
@@ -0,0 +1,177 @@
|
||||
<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>
|
||||
@@ -0,0 +1,88 @@
|
||||
<template>
|
||||
<Modal :model-value="props.visible" title="用户排序" :width="420" :mask-closable="false"
|
||||
@on-visible-change="onVisibleChange">
|
||||
<Alert show-icon>排序号为正整数,1 为最靠前,越大越靠后</Alert>
|
||||
<Form :model="form" :label-width="100">
|
||||
<FormItem label="当前排序号:">
|
||||
<span :style="{ color: props.currentSortNo == null ? '#999' : '#333' }">
|
||||
{{ props.currentSortNo == null ? '暂无' : props.currentSortNo }}
|
||||
</span>
|
||||
</FormItem>
|
||||
<FormItem label="当前用户:">
|
||||
<span>{{ props.userAccount || '-' }}</span>
|
||||
</FormItem>
|
||||
<FormItem label="修改排序号:">
|
||||
<InputNumber :max="999" :min="1" v-model="form.sortNo" :style="{ width: '140px' }" />
|
||||
</FormItem>
|
||||
</Form>
|
||||
|
||||
<template #footer>
|
||||
<Button type="primary" :loading="submitting" @click="handleSubmit">提交</Button>
|
||||
<Button @click="onVisibleChange(false)">取消</Button>
|
||||
</template>
|
||||
</Modal>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { reactive, ref, watch } from "vue";
|
||||
import { Message } from "view-ui-plus";
|
||||
import Apis from "@/api";
|
||||
|
||||
const props = defineProps<{
|
||||
visible: boolean;
|
||||
userId: number | null;
|
||||
userAccount: string;
|
||||
currentSortNo: number | null;
|
||||
groupId: number | null;
|
||||
}>();
|
||||
|
||||
const emit = defineEmits<{
|
||||
'update:visible': [value: boolean];
|
||||
saved: [];
|
||||
}>();
|
||||
|
||||
// 弹窗显示状态变化(点 X / ESC / 取消 都会触发 on-visible-change)一律回写父组件
|
||||
function onVisibleChange(v: boolean) {
|
||||
emit('update:visible', v);
|
||||
}
|
||||
|
||||
const form = reactive<{
|
||||
sortNo: number;
|
||||
}>({
|
||||
sortNo: 1,
|
||||
});
|
||||
|
||||
const submitting = ref(false);
|
||||
|
||||
// 每次打开弹窗:把输入框重置为「自身排序号,没有则默认 1」,不沿用上次输入
|
||||
watch(() => props.visible, (open) => {
|
||||
if (open) {
|
||||
form.sortNo = props.currentSortNo ?? 1;
|
||||
}
|
||||
});
|
||||
|
||||
async function handleSubmit() {
|
||||
if (form.sortNo == null) {
|
||||
Message.warning('请输入排序号(1~999)');
|
||||
return;
|
||||
}
|
||||
if (props.userId == null || props.groupId == null) {
|
||||
Message.warning('请先选择组与用户');
|
||||
return;
|
||||
}
|
||||
submitting.value = true;
|
||||
try {
|
||||
await Apis.SysUser.changeSortNo({
|
||||
pathParams: { userId: props.userId },
|
||||
data: { groupId: props.groupId, sortNo: form.sortNo },
|
||||
});
|
||||
Message.success('排序设置成功');
|
||||
emit('update:visible', false);
|
||||
emit('saved');
|
||||
} catch (e: any) {
|
||||
Message.error(e?.message || '排序设置失败');
|
||||
} finally {
|
||||
submitting.value = false;
|
||||
}
|
||||
}
|
||||
</script>
|
||||
Reference in New Issue
Block a user