203 lines
8.9 KiB
Vue
203 lines
8.9 KiB
Vue
<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>
|