Files
UserhomeFront/src/views/group-user/user/EditUserContainer.vue
T

203 lines
8.9 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>
<UserForm v-model="visible" :initForm="detailData" :userGroups="userGroups" :roleOptions="roleOptions"
:isEdit="true" :loading="submitting" @submit="handleSubmit" @cancel="visible = false" />
</template>
<script setup lang="ts">
import { ref } from "vue";
import UserForm from "./UserForm.vue";
/* ---------- 对外暴露 ---------- */
const visible = ref(false);
// 编辑模式:当前编辑的用户 ID
const currentId = ref<number | null>(null);
// 编辑模式:回填数据(来自当前行 + 业务身份 roles + 角色属性 appRoles)
const detailData = ref<Record<string, any>>({});
// 所在组(只读):用户实际所属的多个组(ID + 完整路径名)
const userGroups = ref<{ id: number; name: string }[]>([]);
// 所在组(只读):默认选中的组 ID(来自 getGroups,失败则兜底当前组),仅用于展示,不提交
const userGroupIdsArr = ref<number[]>([]);
// 角色属性下拉:来自角色授权页的全量角色
const roleOptions = ref<{ id: number; name: string }[]>([]);
// 角色属性是否成功加载(控制编辑后是否回写 saveUserRoles,避免加载失败时误清空)
const roleOptionsLoaded = ref(false);
// 已绑定角色是否成功加载(与 roleOptionsLoaded 同时为真才回写,防止误清空)
const userRolesLoaded = ref(false);
// 待保存的角色属性(用户多选结果)
let appRolesToSave: number[] = [];
// 成功后回调
let onSuccessCallback: (() => void) | null = null;
// useForm 托管提交——currentId 是 ref,闭包内 .value 在 sendForm 时取当前值
const { form, loading: submitting, send: sendForm, onSuccess, onError } = useForm(
(dto) => Apis.SysUser.edit({ pathParams: { id: currentId.value }, data: dto }),
{ initialForm: { id: null, account: '', realName: '', password: '', confirmPassword: '', idcard: '', phone: '', gender: 0, userType: [] }, resetAfterSubmiting: true }
);
onSuccess(() => {
Message.success('编辑用户成功!');
visible.value = false;
onSuccessCallback?.();
});
onError((event) => {
const msg = event?.error?.message || '编辑失败';
Message.error(normalizeAccountError(msg, form.value.account));
});
/* ---------- 打开编辑弹窗 ---------- */
/**
* @param row - 当前行数据(来自 SysUser.list 接口返回的 SysUserVo,不含 groupId)
* @param groups - 所在组下拉(拍平的组织树,name 含完整路径),用于反查用户所属组的显示名
* @param curGroupId - 当前点击所在的组(左侧选中节点),作为"所在组"只读展示的兜底/默认高亮
* @param onSuccess - 编辑成功后的回调
*/
async function open(row: any, groups: any[], curGroupId?: number | null, onSuccess?: () => void) {
if (!row?.id) {
Message.error('用户 ID 不存在');
return;
}
currentId.value = row.id;
const groupList = groups || [];
onSuccessCallback = onSuccess || null;
roleOptionsLoaded.value = false;
userRolesLoaded.value = false;
userGroups.value = [];
userGroupIdsArr.value = [];
roleOptions.value = [];
detailData.value = {};
visible.value = true;
// 1) 用户所属组(权威来源 getGroups);失败或为空则用当前组兜底,绝不留空
try {
const groupIdsResp = await Apis.SysUser.getGroups({ pathParams: { userId: row.id } });
const ids: number[] = Array.isArray(groupIdsResp) ? groupIdsResp : [];
const groupMap = new Map<number, any>((groupList as any[]).map(g => [g.id, g]));
let groups_: { id: number; name: string }[] = ids.map(id => ({
id,
name: groupMap.get(id)?.name ?? ('组#' + id),
}));
if (groups_.length === 0 && curGroupId != null) {
groups_ = [{ id: curGroupId, name: groupMap.get(curGroupId)?.name ?? ('组#' + curGroupId) }];
}
userGroups.value = groups_;
userGroupIdsArr.value = groups_.map(g => g.id);
} catch (e: any) {
// getGroups 不可用(如后端未部署该接口)时,至少展示当前点击的组,避免空白
if (curGroupId != null) {
const groupMap = new Map<number, any>((groupList as any[]).map(g => [g.id, g]));
userGroups.value = [{ id: curGroupId, name: groupMap.get(curGroupId)?.name ?? ('组#' + curGroupId) }];
userGroupIdsArr.value = [curGroupId];
}
console.warn('[EditUserContainer] getGroups 失败,已用当前组兜底:', e?.message || e);
}
// 2) 角色属性下拉(相互独立,失败不影响所在组)
try {
const roleListResp = await Apis.AppRole.roleList({ params: { pageIndex: 1, pageSize: 999 } });
const list = roleListResp?.content || [];
roleOptions.value = list.map((r: any) => ({ id: r.id, name: r.name }));
roleOptionsLoaded.value = true;
} catch (e: any) {
Message.error('加载角色列表失败:' + (e?.message || '未知错误'));
}
// 3) 已绑定角色 -> appRoles 回填(相互独立)
// 所在组(只读)默认选中「当前组」:优先用进入编辑时的左侧树/列表组(curGroupId),否则取用户所属组的第一个
const defaultGroupId = (curGroupId != null && userGroupIdsArr.value.includes(curGroupId))
? curGroupId
: (userGroupIdsArr.value[0] ?? null);
try {
const userRolesResp = await Apis.GroupAuthz.userRoles({ params: { userId: row.id } });
const userRoleIds: number[] = (Array.isArray(userRolesResp) ? userRolesResp : [])
.map((r: any) => r.id)
.filter((x: any): x is number => x != null);
userRolesLoaded.value = true;
detailData.value = buildInitForm(row, userRoleIds, defaultGroupId);
} catch (e: any) {
// 已绑定角色加载失败:仍回填其它字段,但标记未加载成功(提交时不回写,避免误清空)
detailData.value = buildInitForm(row, [], defaultGroupId);
Message.error('加载用户已绑定角色失败:' + (e?.message || '未知错误'));
}
}
/* ---------- 把行数据映射成表单回填数据 ---------- */
function buildInitForm(row: any, userRoleIds: number[], defaultGroupId: number | null = null) {
const roles: number[] = Array.isArray(row.roles) ? row.roles : [];
// 用户身份(业务身份)= 1..7;角色属性已移除(超管手动入库、管理员走勾选)
const userType = roles.filter(r => r >= 1 && r <= 7);
return {
id: row.id,
account: row.account || '',
realName: row.realName || '',
idcard: row.idcard || '',
phone: row.phone || '',
password: '',
confirmPassword: '',
gender: row.gender ?? 0,
userType,
appRoles: userRoleIds || [],
userGroupId: defaultGroupId ?? null,
};
}
/* ---------- 提交:由 useForm 接管 loading 与异常处理 ---------- */
async function handleSubmit(formData: Record<string, any>) {
if (!currentId.value) return;
appRolesToSave = (formData.appRoles || []) as number[];
form.value = buildPayload(formData, currentId.value);
try {
await sendForm();
} catch {
// 编辑失败(onError 已提示),不继续回写角色
return;
}
// 编辑成功后再回写业务角色(全量覆盖);仅当角色列表与已绑定角色都成功加载才回写,防止误清空
if (roleOptionsLoaded.value && userRolesLoaded.value) {
try {
await Apis.AppRole.saveUserRoles({ data: { userId: currentId.value as number, roleIds: appRolesToSave } });
} catch (e: any) {
Message.error('用户已更新,但角色属性保存失败:' + (e?.message || '未知错误'));
}
}
}
/**
* 把表单字段映射成 SysUserRequest:
* - 只提交后端 SysUserRequest 里声明的字段
* - 编辑时不提交 groupId(后端 edit 会保留用户原分组,且 groupId 仅在 Create 分组校验)
* - 编辑时密码留空表示不修改,所以密码字段不传
*/
function buildPayload(formData: Record<string, any>, id: number) {
const roles: number[] = (formData.userType || []).map((v: any) => Number(v));
const payload: any = {
id,
account: formData.account,
realName: formData.realName,
idcard: formData.idcard || undefined,
phone: formData.phone || undefined,
gender: formData.gender,
roles,
};
// 编辑时若填写了密码才提交(密码为空表示不修改)
if (formData.password) {
payload.password = formData.password;
payload.confirmPassword = formData.confirmPassword;
}
return payload;
}
/* ---------- 工具:账号重复提示标准化 ---------- */
function normalizeAccountError(msg: string, account: string): string {
if (account && /账号/.test(msg) && /(已存在|重复|已被其他用户使用)/.test(msg)) {
return `账号${account}已存在`;
}
return msg;
}
defineExpose({ open, visible });
</script>