Files
td_official/src/views/system/user/comm/roleInfo.vue
2025-09-04 09:03:42 +08:00

478 lines
13 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>
<div class="roleInfo">
<div class="title_detail">
<span>当前选定角色信息预览</span>
<div style="margin-top: 10px">
<el-table :data="roleList" border height="150">
<el-table-column label="所属部门" align="center" prop="deptName" />
<el-table-column label="关联项目" align="center" prop="projectName" />
<el-table-column label="web端担任角色" align="center" prop="webRoles" />
<el-table-column label="APP端担任角色" align="center" prop="appRoles" />
</el-table>
</div>
</div>
<div class="title_detail" style="margin-top: 20px">
<span>选择或修改当前角色信息</span>
<div style="margin-top: 10px" class="box_detail">
<!-- 项目列表选择区 -->
<div class="project_list">
<span>关联项目模块</span>
<div class="project-items">
<div
v-for="item in projectOptions"
:key="item.id"
class="project-item"
:class="{ 'project-item-selected': isProjectSelected(item.id) }"
@click="toggleProjectSelection(item)"
>
<div class="project-item-content">
<el-checkbox v-model="item.checked" :value="item.id" class="project-checkbox" @change="handleProjectCheck(item, $event)" />
<span class="project-name">{{ item.projectName }}</span>
</div>
</div>
</div>
</div>
<!-- 角色分配区 -->
<div class="post_list">
<span>关联项目角色分配</span>
<div v-if="selectedProjects.length === 0" class="no-selection">请从左侧选择项目进行角色分配</div>
<div v-for="(project, index) in selectedProjects" :key="project.id" class="project-role-container">
<div class="project-header">
<span class="project-title">{{ project.projectName }}</span>
<el-button type="text" class="remove-project" @click="removeProject(project.id)"> 移除 </el-button>
</div>
<div class="role-assignment">
<div class="role-group">
<label class="role-label">web端角色</label>
<el-checkbox-group v-model="project.webRoles" @change="updateRoleList">
<el-checkbox v-for="role in allRoles" :key="role.roleId" :value="role.roleId">
{{ role.roleName }}
</el-checkbox>
</el-checkbox-group>
</div>
<div class="role-group">
<label class="role-label">APP端角色</label>
<el-checkbox-group v-model="project.appRoles" @change="updateRoleList">
<el-checkbox v-for="role in AppRoles" :key="role.roleId" :value="role.roleId">
{{ role.roleName }}
</el-checkbox>
</el-checkbox-group>
</div>
</div>
</div>
</div>
</div>
</div>
<div class="box_submit">
<el-button type="primary" @click="submitForm"> </el-button>
<el-button @click="cancel()"> </el-button>
</div>
</div>
</template>
<script setup name="RoleProjectManagement" lang="ts">
import { ref, reactive, toRefs, getCurrentInstance, ComponentInternalInstance, defineExpose, watch } from 'vue';
import { ElFormInstance } from 'element-plus';
import api from '@/api/system/user';
import { listProject } from '@/api/project/project';
import { getRoleList } from '@/api/system/post';
// 类型定义
interface Project {
id: number | string;
projectName: string;
checked: boolean;
webRoles: string[];
appRoles: string[];
}
interface Role {
roleId: number | string;
roleName: string;
roleSort?: number;
}
interface RoleInfo {
userNick: string;
deptName: string;
postName: string;
projectName: string;
webRoles: string;
appRoles: string;
}
// 组件实例
const { proxy } = getCurrentInstance() as ComponentInternalInstance;
// 响应式数据
const projectOptions = ref<Project[]>([]);
const selectedProjects = ref<Project[]>([]);
const allRoles = ref<Role[]>([]); // web端角色
const AppRoles = ref<Role[]>([]); // APP端角色
const roleList = ref<RoleInfo[]>([]);
// 表单初始数据
const initFormData = {
userId: undefined,
deptId: undefined,
userName: '',
nickName: undefined,
password: '',
phonenumber: undefined,
email: undefined,
sex: undefined,
projectRoles: [] as Array<{ projectId: number | string; webRoles: string[]; appRoles: string[] }>,
status: '0',
remark: '',
postIds: [],
filePath: undefined,
deptName: '' // 新增部门名称字段
};
const data = reactive({
form: { ...initFormData }
});
const deptName = ref('');
const { form } = toRefs(data);
// 核心方法:更新预览列表
const updateRoleList = () => {
if (selectedProjects.value.length === 0) {
roleList.value = [];
return;
}
roleList.value = selectedProjects.value.map((project) => {
// 处理web端角色名称
var webRoleNames = project.webRoles
.map((roleId) => {
const role = allRoles.value.find((r) => r.roleId === roleId);
return role ? role.roleName : '';
})
.filter(Boolean);
// 处理APP端角色名称
var appRoleNames = project.appRoles
.map((roleId) => {
const role = AppRoles.value.find((r) => r.roleId === roleId);
return role ? role.roleName : '';
})
.filter(Boolean);
webRoleNames = [...new Set(webRoleNames)];
appRoleNames = [...new Set(appRoleNames)];
return {
deptName: deptName.value,
projectName: project.projectName,
webRoles: webRoleNames.length > 0 ? webRoleNames.join('') : '无',
appRoles: appRoleNames.length > 0 ? appRoleNames.join('') : '无'
};
});
};
// 监听已选项目变化,自动更新预览列表
watch(
selectedProjects,
() => {
updateRoleList();
},
{ deep: true }
);
// 检查项目是否被选中
const isProjectSelected = (projectId: number | string) => {
return selectedProjects.value.some((p) => p.id === projectId);
};
// 切换项目选择状态
const toggleProjectSelection = (project: Project) => {
// handleProjectCheck(project, !project.checked);
};
// 处理项目勾选状态变化
const handleProjectCheck = (project: Project, checked: boolean) => {
// project.checked = checked;
const index = selectedProjects.value.findIndex((p) => p.id === project.id);
if (checked && index === -1) {
// 添加选中的项目
selectedProjects.value.push({
...project,
webRoles: [],
appRoles: []
});
} else if (!checked && index !== -1) {
// 移除取消选中的项目
selectedProjects.value.splice(index, 1);
}
};
// 移除项目
const removeProject = (projectId: number | string) => {
// 更新选中项目列表
selectedProjects.value = selectedProjects.value.filter((p) => p.id !== projectId);
// 更新项目选项的勾选状态
const project = projectOptions.value.find((p) => p.id === projectId);
if (project) {
project.checked = false;
}
};
// 提交表单
const submitForm = async () => {
// 整理项目角色数据
form.value.projectRoles = selectedProjects.value.map((project) => ({
projectId: project.id,
roleIds: [...new Set(project.webRoles), ...new Set(project.appRoles)]
}));
// 提交数据
try {
if (form.value.userId) {
await api.updateUser(form.value);
} else {
await api.addUser(form.value);
}
proxy?.$modal.msgSuccess('操作成功');
proxy?.$emit('submit', form.value);
cancel();
} catch (error) {
proxy?.$modal.msgError('操作失败,请重试');
console.error('提交失败:', error);
}
};
// 取消操作
const cancel = () => {
resetForm();
proxy?.$emit('close');
};
// 重置表单
const resetForm = () => {
data.form = { ...initFormData };
projectOptions.value.forEach((p) => (p.checked = false));
selectedProjects.value = [];
roleList.value = [];
};
// 初始化数据
const initData = async () => {
try {
// 获取项目列表
const projectRes = await listProject();
projectOptions.value = projectRes.rows.map((item: any) => ({
id: item.id,
projectName: item.projectName,
checked: false
}));
} catch (error) {
proxy?.$modal.msgError('数据加载失败');
console.error('初始化数据失败:', error);
}
};
// 打开弹窗时调用
const open = async (row?: any, deptId?: any) => {
resetForm();
await initData();
deptName.value = row.deptName;
console.log(row);
if (row) {
try {
if (!row.createTime) {
form.value = { ...row };
// 获取角色列表
if (form.value.deptId) {
deptId = form.value.deptId;
}
const roleRes = await getRoleList(deptId);
allRoles.value = roleRes.data.filter((item: Role) => item.roleSort === 1);
AppRoles.value = roleRes.data.filter((item: Role) => item.roleSort !== 1);
} else {
const { data } = await api.getUser(row.userId);
Object.assign(form.value, data.user);
// 获取角色列表
if (form.value.deptId) {
deptId = form.value.deptId;
}
const roleRes = await getRoleList(deptId);
// 区分web端和app端角色
allRoles.value = roleRes.data.filter((item: Role) => item.roleSort === 1);
AppRoles.value = roleRes.data.filter((item: Role) => item.roleSort !== 1);
// 加载项目角色数据
if (data.projectRoles && data.projectRoles.length) {
data.projectRoles.forEach((pr: any) => {
const project = projectOptions.value.find((p) => p.id === pr.projectId);
if (project) {
project.checked = true;
selectedProjects.value.push({
...project,
webRoles: pr.roleIds || [],
appRoles: pr.roleIds || []
});
}
});
}
}
} catch (error) {
proxy?.$modal.msgError('加载用户数据失败');
console.error('加载用户数据失败:', error);
}
}
};
// 暴露方法
defineExpose({ open });
</script>
<style lang="scss">
.roleInfo {
position: relative;
height: 100%;
padding-bottom: 60px;
box-sizing: border-box;
.title_detail {
> span {
font-size: 16px;
font-weight: 600;
color: #333;
display: inline-block;
padding-bottom: 5px;
border-bottom: 2px solid #409eff;
}
.box_detail {
display: flex;
gap: 15px;
margin-top: 15px;
> div {
height: 350px;
padding: 15px;
border-radius: 4px;
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
overflow-y: auto;
box-sizing: border-box;
}
.project_list {
width: 320px;
background-color: #fff;
.project-items {
margin-top: 10px;
}
.project-item {
padding: 10px 12px;
margin-bottom: 8px;
border-radius: 4px;
cursor: pointer;
transition: all 0.2s ease;
border: 1px solid #eee;
&:hover {
background-color: #f5f7fa;
border-color: #e4e7ed;
}
.project-item-content {
display: flex;
align-items: center;
}
.project-checkbox {
margin-right: 8px;
}
.project-name {
flex: 1;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
}
.project-item-selected {
background-color: #ecf5ff;
border-color: #c6e2ff;
}
}
.post_list {
flex: 1;
background-color: #fff;
.no-selection {
height: 100%;
display: flex;
align-items: center;
justify-content: center;
color: #909399;
font-size: 14px;
}
.project-role-container {
padding: 15px;
margin-bottom: 15px;
background-color: #f9f9f9;
border-radius: 4px;
border: 1px solid #f0f0f0;
.project-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 15px;
.project-title {
font-weight: 500;
color: #333;
font-size: 14px;
}
.remove-project {
color: #f56c6c;
padding: 0 5px;
&:hover {
color: #e4393c;
}
}
}
.role-assignment {
.role-group {
margin-bottom: 15px;
.role-label {
display: inline-block;
width: 110px;
color: #606266;
font-size: 14px;
}
.el-checkbox-group {
display: inline-block;
margin-left: 10px;
.el-checkbox {
margin-right: 15px;
margin-bottom: 8px;
}
}
}
}
}
}
}
}
.box_submit {
position: absolute;
right: 20px;
bottom: 0px;
display: flex;
gap: 10px;
}
}
</style>