修改角色bug
deploy-userbase-front / build-and-deploy (push) Successful in 2m58s

This commit is contained in:
chenxing
2026-08-17 18:54:00 +08:00
parent 0fa4b0f38a
commit 2321571060
3 changed files with 54 additions and 73 deletions
+20 -17
View File
@@ -60,6 +60,7 @@
<script lang="ts" setup>
import { ref, computed } from "vue";
import { Message } from "view-ui-plus";
import { useDefaultStore } from "@/store";
import useCurrentInstance from "@/utils/hook/useCurrentInstance";
import MyConfirm from "@/components/MyConfirm";
import TablePageAlovaNew from '@/components/TablePageAlovaNew.vue';
@@ -149,26 +150,28 @@ const tableApi = (params: { pageIndex: number; pageSize: number }) => {
const tableHeight = ref($utils.useTableHeight({ isOperat: false, isPage: true, isSearch: true }))
/* ================= 用户身份(roles[])展示 ================= */
const userTypeMap: Record<number, string> = {
1: '管理员', 2: '老师', 3: '校领导', 4: '局领导',
5: '学生', 6: '家长', 7: '工作号',
};
const roleMap: Record<number, string> = {
1: '超级管理员', 2: '学校管理员', 3: '普通用户',
// 唯一真源:后端 SysRoleEnum(经 GetSysDict.sysrole 下发),杜绝前端硬编码漂移。
// roles 中仅存放 SysRoleEnum 的 key(1=老师 … 7=管理员),不再混用 UserTypeEnum。
const SYS_ROLE_MAP_FALLBACK: Record<number, string> = {
1: '老师', 2: '校领导', 3: '学生', 4: '家长',
5: '局领导', 6: '工作号', 7: '管理员',
};
const store = useDefaultStore();
const userTypeMap = computed<Record<number, string>>(() => {
const sysrole = (store.getSysDict as any)?.sysrole;
if (Array.isArray(sysrole) && sysrole.length) {
const m: Record<number, string> = {};
sysrole.forEach((it: any) => { m[Number(it.k)] = it.v; });
return m;
}
return SYS_ROLE_MAP_FALLBACK;
});
function describeRoles(roles: any): string {
if (!Array.isArray(roles) || roles.length === 0) return '-';
const userTypes: string[] = [];
const specificRoles: string[] = [];
roles.forEach((r: any) => {
const n = Number(r);
if (userTypeMap[n]) userTypes.push(userTypeMap[n]);
else if (roleMap[n]) specificRoles.push(roleMap[n]);
});
const parts: string[] = [];
if (userTypes.length) parts.push(userTypes.join('/'));
if (specificRoles.length) parts.push(specificRoles.join('/'));
return parts.length ? parts.join(' · ') : '-';
const names = roles
.map((r: any) => userTypeMap.value[Number(r)])
.filter((n: string) => !!n);
return names.length ? names.join('/') : '-';
}
/* ================= 表格列 ================= */
@@ -16,6 +16,8 @@ const currentId = ref<number | null>(null);
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,避免加载失败时误清空)
@@ -62,6 +64,7 @@ async function open(row: any, groups: any[], curGroupId?: number | null, onSucce
roleOptionsLoaded.value = false;
userRolesLoaded.value = false;
userGroups.value = [];
userGroupIdsArr.value = [];
roleOptions.value = [];
detailData.value = {};
visible.value = true;
@@ -79,11 +82,13 @@ async function open(row: any, groups: any[], curGroupId?: number | null, onSucce
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);
}
@@ -105,16 +110,16 @@ async function open(row: any, groups: any[], curGroupId?: number | null, onSucce
.map((r: any) => r.id)
.filter((x: any): x is number => x != null);
userRolesLoaded.value = true;
detailData.value = buildInitForm(row, userRoleIds);
detailData.value = buildInitForm(row, userRoleIds, userGroupIdsArr.value);
} catch (e: any) {
// 已绑定角色加载失败:仍回填其它字段,但标记未加载成功(提交时不回写,避免误清空)
detailData.value = buildInitForm(row, []);
detailData.value = buildInitForm(row, [], userGroupIdsArr.value);
Message.error('加载用户已绑定角色失败:' + (e?.message || '未知错误'));
}
}
/* ---------- 把行数据映射成表单回填数据 ---------- */
function buildInitForm(row: any, userRoleIds: number[]) {
function buildInitForm(row: any, userRoleIds: number[], userGroupIds: number[] = []) {
const roles: number[] = Array.isArray(row.roles) ? row.roles : [];
// 用户身份(业务身份)= 1..7;角色属性已移除(超管手动入库、管理员走勾选)
const userType = roles.filter(r => r >= 1 && r <= 7);
@@ -130,6 +135,7 @@ function buildInitForm(row: any, userRoleIds: number[]) {
gender: row.gender ?? 0,
userType,
appRoles: userRoleIds || [],
userGroupIds: userGroupIds || [],
};
}
+25 -53
View File
@@ -57,14 +57,14 @@
</Col>
</Row>
<!-- 第 5 行:所在组(只读)(仅编辑展示,显示用户全部所属组;不提交) -->
<!-- 第 5 行:所在组(只读)(仅编辑展示,默认选中当前组;提交时不带 groupId,不改变所在组) -->
<Row v-if="isEdit">
<Col span="24">
<FormItem label="所在组(只读):">
<div class="group-readonly-box" :title="userGroups.map(g => g.name).join(' / ')">
<span v-for="g in userGroups" :key="g.id" class="group-readonly-tag">{{ g.name }}</span>
<span v-if="userGroups.length === 0" class="group-readonly-empty">—</span>
</div>
<Select v-model="form.userGroupIds" multiple transfer
placeholder="默认选中当前组(提交不改变所在组)">
<Option v-for="g in userGroups" :key="g.id" :value="g.id" :label="g.name" />
</Select>
</FormItem>
</Col>
</Row>
@@ -105,6 +105,7 @@
<script setup lang="ts">
import { computed, reactive, ref, watch } from "vue";
import { useDefaultStore } from "@/store";
/* ---------- Props / Emits ---------- */
const props = withDefaults(defineProps<{
@@ -143,6 +144,7 @@ const defaultForm = (): Record<string, any> => ({
gender: 0,
userType: [] as number[],
appRoles: [] as number[],
userGroupIds: [] as number[],
});
const form = reactive(defaultForm());
@@ -157,15 +159,25 @@ watch(() => props.initForm, (val) => {
}, { immediate: true, deep: true });
/* ---------- 用户身份(业务身份多选)---------- */
const userTypeOptions = [
{ value: 1, label: '管理员' },
{ value: 2, label: '老师' },
{ value: 3, label: '校领导' },
{ value: 4, label: '局领导' },
{ value: 5, label: '学生' },
{ value: 6, label: '家长' },
{ value: 7, label: '工作号' },
// 唯一真源:后端 SysRoleEnum(经 GetSysDict.sysrole 下发),杜绝前端硬编码漂移。
// 若字典未加载(接口异常)则回退到与 SysRoleEnum 完全一致的兜底值。
const SYS_ROLE_FALLBACK = [
{ value: 1, label: '老师' },
{ value: 2, label: '校领导' },
{ value: 3, label: '学生' },
{ value: 4, label: '家长' },
{ value: 5, label: '局领导' },
{ value: 6, label: '工作号' },
{ value: 7, label: '管理员' },
];
const store = useDefaultStore();
const userTypeOptions = computed(() => {
const sysrole = (store.getSysDict as any)?.sysrole;
if (Array.isArray(sysrole) && sysrole.length) {
return sysrole.map((it: any) => ({ value: it.k, label: it.v }));
}
return SYS_ROLE_FALLBACK;
});
/* ---------- 校验规则 ---------- */
// 自定义确认密码校验:新增必填且必须与密码一致;编辑时两者皆空表示不修改,任一非空则必须一致
@@ -296,44 +308,4 @@ function onVisibleChange(val: boolean) {
flex-wrap: wrap;
gap: 0 18px;
}
/* 所在组(只读):白底圆角盒子 + 标签展示,视觉等同只读 Select。
改用纯 Vue 渲染,彻底绕开 view-ui-plus disabled+multiple Select
在「空数据挂载后异步补选项」时不重渲染已选标签的渲染竞态。 */
.group-readonly-box {
min-height: 32px;
border: 1px solid #dcdee2;
border-radius: 4px;
background: #fff;
padding: 3px 7px;
display: flex;
flex-wrap: wrap;
align-items: center;
gap: 4px 6px;
cursor: default;
transition: border-color .2s ease;
}
.group-readonly-tag {
display: inline-flex;
align-items: center;
height: 24px;
line-height: 22px;
padding: 0 8px;
border: 1px solid #e3e5e8;
border-radius: 3px;
background: #f7f7f7;
color: #515a6e;
font-size: 12px;
white-space: nowrap;
max-width: 100%;
overflow: hidden;
text-overflow: ellipsis;
}
.group-readonly-empty {
color: #c5c8ce;
font-size: 13px;
line-height: 24px;
}
</style>