| | |
| | | <div></div> |
| | | <div>角色信息</div> |
| | | </div> |
| | | <el-form ref="form" :inline="true" :model="form" :rules="rules" label-width="80px" class="demo-form-inline"> |
| | | <el-form |
| | | ref="form" |
| | | :inline="true" |
| | | :model="form" |
| | | :rules="rules" |
| | | label-width="80px" |
| | | class="demo-form-inline" |
| | | > |
| | | <el-form-item label="角色名称" prop="roleName"> |
| | | <el-input v-model="form.roleName" placeholder="请输入角色名称"></el-input> |
| | | <el-input |
| | | v-model="form.roleName" |
| | | placeholder="请输入角色名称" |
| | | ></el-input> |
| | | </el-form-item> |
| | | <el-form-item label="备注" prop="remark"> |
| | | <el-input v-model="form.remark" placeholder="请输入备注"></el-input> |
| | |
| | | <div></div> |
| | | <div>操作权限</div> |
| | | </div> |
| | | <div> |
| | | <div class="header"> |
| | | <div class="w20">模块名称</div> |
| | | <div class="sconed"> |
| | | <div class="subpage"> |
| | | <div class="title">页面名称</div> |
| | | <div class="btns">权限</div> |
| | | </div> |
| | | </div> |
| | | </div> |
| | | <div v-for="item in menu" :key="item.menuId"> |
| | | <div class="row"> |
| | | <div class="w20"> |
| | | <el-checkbox v-model="item.selected" @change="(e) => { |
| | | setCheckStatus1(item.menuId, e) |
| | | }" :checked="item.selected"> |
| | | {{ item.menuName }} |
| | | </el-checkbox> |
| | | </div> |
| | | <div class="sconed"> |
| | | <div class="subpage" |
| | | v-if="(item.children.length > 0 && item.children[0].children.length > 0) || item.children[0].children.menuType != 'F'"> |
| | | <div v-for="item1 in item.children" :key="item1.menuId" class="two"> |
| | | <div class="left"> |
| | | <el-checkbox v-model="item1.selected" @change="(e) => { |
| | | setCheckStatus2(item1.menuId, e, item.menuId) |
| | | }" :checked="item1.selected"> |
| | | {{ item1.menuName }} |
| | | </el-checkbox> |
| | | </div> |
| | | |
| | | <div class="right"> |
| | | <div v-for="item2 in item1.children" :key="item2.menuId"> |
| | | <el-checkbox v-model="item2.selected" @change="(e) => { |
| | | setCheckStatus3(item2.menuId, e, item1.menuId, item.menuId) |
| | | }" :checked="item2.selected"> |
| | | {{ item2.menuName }} |
| | | </el-checkbox> |
| | | </div> |
| | | </div> |
| | | </div> |
| | | </div> |
| | | <div class="subpage" v-else> |
| | | <div class="two"> |
| | | <!-- <div class="left"> |
| | | </div> --> |
| | | |
| | | <div class="right"> |
| | | <div v-for="item1 in item.children" :key="item1.menuId"> |
| | | <el-checkbox v-model="item1.selected" @change="(e) => { |
| | | setCheckStatus2(item1.menuId, e, item.menuId,) |
| | | }" :checked="item1.selected"> |
| | | {{ item1.menuName }} |
| | | </el-checkbox> |
| | | </div> |
| | | </div> |
| | | </div> |
| | | </div> |
| | | </div> |
| | | </div> |
| | | </div> |
| | | <div class="tree-container"> |
| | | <el-tree |
| | | ref="permissionTree" |
| | | :data="menu" |
| | | :props="defaultProps" |
| | | show-checkbox |
| | | node-key="menuId" |
| | | default-expand-all |
| | | :check-strictly="true" |
| | | @check="handleCheckChange" |
| | | > |
| | | </el-tree> |
| | | </div> |
| | | |
| | | <div class="btn_box"> |
| | |
| | | </template> |
| | | |
| | | <script> |
| | | import { roleInfoFromUserId, getRoleInfo, add, edit } from './service.js' |
| | | import { roleInfoFromUserId, getRoleInfo, add, edit } from "./service.js"; |
| | | export default { |
| | | components: {}, |
| | | props: {}, |
| | |
| | | ], |
| | | }, |
| | | menu: [], |
| | | defaultProps: { |
| | | children: "children", |
| | | label: "menuName", |
| | | }, |
| | | }; |
| | | }, |
| | | computed: {}, |
| | | watch: {}, |
| | | created() { |
| | | roleInfoFromUserId().then(res => { |
| | | roleInfoFromUserId().then((res) => { |
| | | // 确保菜单数据正确处理 |
| | | this.menu = this.processMenuData(res); |
| | | |
| | | if (this.$route.query.roleId) { |
| | | getRoleInfo({ id: this.$route.query.roleId }).then(resp => { |
| | | this.menu = this.setSelectedIds(res.data.data, resp.data.data.menus || []); |
| | | getRoleInfo({ id: this.$route.query.roleId }).then((resp) => { |
| | | // 设置表单数据 |
| | | const roleData = resp; |
| | | this.form = { |
| | | roleName: resp.data.data.roleName, |
| | | remark: resp.data.data.remark, |
| | | roleId: resp.data.data.roleId |
| | | } |
| | | }) |
| | | } else { |
| | | this.menu = res.data.data |
| | | roleName: roleData.roleName, |
| | | remark: roleData.remark, |
| | | roleId: roleData.roleId, |
| | | }; |
| | | |
| | | // 设置选中的菜单项 |
| | | this.$nextTick(() => { |
| | | if (roleData.menus && roleData.menus.length > 0) { |
| | | // 直接使用后端返回的选中节点 |
| | | this.$refs.permissionTree.setCheckedKeys(roleData.menus); |
| | | } |
| | | }); |
| | | }); |
| | | } |
| | | }) |
| | | }); |
| | | }, |
| | | mounted() { }, |
| | | mounted() {}, |
| | | methods: { |
| | | // 处理菜单数据,为每个节点添加必要的属性 |
| | | processMenuData(menuData) { |
| | | // 如果返回的是嵌套在data.data中的数据 |
| | | if (menuData.data && menuData.data.data) { |
| | | menuData = menuData.data.data; |
| | | } |
| | | |
| | | function traverse(item) { |
| | | // 确保有children属性 |
| | | if (!item.children) { |
| | | item.children = []; |
| | | } |
| | | |
| | | // 递归处理子节点 |
| | | if (item.children && item.children.length > 0) { |
| | | item.children.forEach(traverse); |
| | | } |
| | | } |
| | | |
| | | // 克隆一份数据,避免直接修改原数据 |
| | | const clonedData = JSON.parse(JSON.stringify(menuData)); |
| | | clonedData.forEach(traverse); |
| | | |
| | | return clonedData; |
| | | }, |
| | | |
| | | // 复选框状态变化事件处理 |
| | | handleCheckChange(data, checked) { |
| | | // 获取树实例 |
| | | const tree = this.$refs.permissionTree; |
| | | |
| | | // 如果是父节点被点击 |
| | | if (data.children && data.children.length > 0) { |
| | | // 获取当前节点的选中状态 |
| | | const isChecked = tree.getCheckedKeys().includes(data.menuId); |
| | | |
| | | if (isChecked) { |
| | | // 如果父节点被选中,选中所有子节点 |
| | | const childIds = this.getAllChildIds(data); |
| | | const currentCheckedKeys = tree.getCheckedKeys(); |
| | | const newCheckedKeys = [ |
| | | ...new Set([...currentCheckedKeys, ...childIds]), |
| | | ]; |
| | | tree.setCheckedKeys(newCheckedKeys); |
| | | } else { |
| | | // 如果父节点被取消选中,取消选中所有子节点 |
| | | const childIds = this.getAllChildIds(data); |
| | | const currentCheckedKeys = tree.getCheckedKeys(); |
| | | const newCheckedKeys = currentCheckedKeys.filter( |
| | | (key) => !childIds.includes(key) |
| | | ); |
| | | tree.setCheckedKeys(newCheckedKeys); |
| | | } |
| | | } else { |
| | | // 如果是子节点被点击,检查父节点状态 |
| | | this.updateParentNodeState(data); |
| | | } |
| | | }, |
| | | |
| | | // 获取节点的所有子节点ID |
| | | getAllChildIds(node) { |
| | | let ids = []; |
| | | if (node.children && node.children.length > 0) { |
| | | node.children.forEach((child) => { |
| | | ids.push(child.menuId); |
| | | ids = ids.concat(this.getAllChildIds(child)); |
| | | }); |
| | | } |
| | | return ids; |
| | | }, |
| | | |
| | | // 更新父节点状态 |
| | | updateParentNodeState(node) { |
| | | const tree = this.$refs.permissionTree; |
| | | const parentNode = this.findParentNode(this.menu, node.menuId); |
| | | |
| | | if (parentNode) { |
| | | const allChildren = this.getAllChildIds(parentNode); |
| | | const checkedChildren = allChildren.filter((id) => |
| | | tree.getCheckedKeys().includes(id) |
| | | ); |
| | | |
| | | if (checkedChildren.length === 0) { |
| | | // 如果没有子节点被选中,取消选中父节点 |
| | | tree.setChecked(parentNode.menuId, false); |
| | | } else if (checkedChildren.length === allChildren.length) { |
| | | // 如果所有子节点都被选中,选中父节点 |
| | | tree.setChecked(parentNode.menuId, true); |
| | | } else { |
| | | // 如果部分子节点被选中,设置父节点为半选状态 |
| | | tree.setChecked(parentNode.menuId, false); |
| | | const node = tree.getNode(parentNode.menuId); |
| | | if (node) { |
| | | node.indeterminate = true; |
| | | } |
| | | } |
| | | |
| | | // 递归更新更上层的父节点 |
| | | this.updateParentNodeState(parentNode); |
| | | } |
| | | }, |
| | | |
| | | // 查找节点的父节点 |
| | | findParentNode(nodes, targetId, parent = null) { |
| | | for (let node of nodes) { |
| | | if (node.menuId === targetId) { |
| | | return parent; |
| | | } |
| | | if (node.children && node.children.length > 0) { |
| | | const found = this.findParentNode(node.children, targetId, node); |
| | | if (found) return found; |
| | | } |
| | | } |
| | | return null; |
| | | }, |
| | | |
| | | setSelectedIds(arr, selectKeyList) { |
| | | function traverse(item) { |
| | | item.selected = selectKeyList.includes(item.menuId); |
| | |
| | | arr.forEach(traverse); |
| | | return arr; |
| | | }, |
| | | |
| | | onSubmit() { |
| | | this.$refs['form'].validate((valid) => { |
| | | this.$refs["form"].validate((valid) => { |
| | | if (valid) { |
| | | if (this.getSelectedIds(this.menu).length == 0) { |
| | | this.$baseMessage('请勾选操作权限', 'warning') |
| | | return |
| | | // 获取选中的节点ID列表 |
| | | const checkedKeys = this.$refs.permissionTree.getCheckedKeys(); |
| | | const halfCheckedKeys = |
| | | this.$refs.permissionTree.getHalfCheckedKeys(); |
| | | |
| | | // 合并完全选中和半选中的节点 |
| | | const allCheckedKeys = [...checkedKeys, ...halfCheckedKeys]; |
| | | |
| | | if (allCheckedKeys.length === 0) { |
| | | this.$message.warning("请勾选操作权限"); |
| | | return; |
| | | } |
| | | |
| | | let obj = { |
| | | ...this.form, |
| | | menuIds: this.getSelectedIds(this.menu) |
| | | } |
| | | menuIds: allCheckedKeys, |
| | | }; |
| | | |
| | | if (this.$route.query && this.$route.query.roleId) { |
| | | obj.roleId = this.$route.query.roleId |
| | | obj.roleId = this.$route.query.roleId; |
| | | edit(obj).then(() => { |
| | | this.$baseMessage('保存成功', 'success') |
| | | this.$router.go(-1) |
| | | }) |
| | | this.$message.success("保存成功"); |
| | | this.$router.go(-1); |
| | | }); |
| | | } else { |
| | | add(obj).then(() => { |
| | | this.$baseMessage('保存成功', 'success') |
| | | this.$router.go(-1) |
| | | }) |
| | | this.$message.success("保存成功"); |
| | | this.$router.go(-1); |
| | | }); |
| | | } |
| | | } |
| | | }) |
| | | }); |
| | | }, |
| | | |
| | | getSelectedIds(arr) { |
| | | let result = []; |
| | | function traverse(item) { |
| | |
| | | } |
| | | return result; |
| | | }, |
| | | setCheckStatus1(id, status) { //点击第1级 |
| | | if (!status) { |
| | | this.menu = this.menu.map(item => { |
| | | if (item.menuId == id) { |
| | | item.selected = status |
| | | if (item.children.length > 0) { |
| | | item.children = item.children.map(item1 => { |
| | | item1.selected = status |
| | | if (item1.children.length > 0) { |
| | | item1.children = item1.children.map(item2 => { |
| | | item2.selected = status |
| | | return { ...item2 } |
| | | }) |
| | | } |
| | | return { ...item1 } |
| | | }) |
| | | } |
| | | } |
| | | return { ...item } |
| | | }) |
| | | } else { |
| | | this.menu = this.menu.map(item => { |
| | | if (item.menuId == id) { |
| | | item.selected = true |
| | | } |
| | | return { ...item } |
| | | }) |
| | | } |
| | | }, |
| | | setCheckStatus2(id, status, aId) { //点击第2级 |
| | | this.menu = this.menu.map(item => { |
| | | if (item.menuId == aId) { |
| | | item.selected = true |
| | | if (item.children.length > 0) { |
| | | item.children = item.children.map(item1 => { |
| | | if (item1.menuId == id) { |
| | | item1.selected = status |
| | | } |
| | | return { ...item1 } |
| | | }) |
| | | } |
| | | } |
| | | return { ...item } |
| | | }) |
| | | }, |
| | | setCheckStatus3(id, status, bId, aId) {//点击第3级 |
| | | this.menu = this.menu.map(item => { |
| | | if (item.menuId == aId) { |
| | | item.selected = true |
| | | if (item.children.length > 0) { |
| | | item.children = item.children.map(item1 => { |
| | | if (item1.menuId == bId) { |
| | | item1.selected = true |
| | | if (item1.children.length > 0) { |
| | | item1.children = item1.children.map(item2 => { |
| | | if (item2.menuId == id) { |
| | | item2.selected = status |
| | | } |
| | | return { ...item2 } |
| | | }) |
| | | } |
| | | } |
| | | return { ...item1 } |
| | | }) |
| | | } |
| | | } |
| | | return { ...item } |
| | | }) |
| | | } |
| | | }, |
| | | }; |
| | | </script> |
| | |
| | | div:first-child { |
| | | width: 4px; |
| | | height: 16px; |
| | | background: #598DEC; |
| | | background: #598dec; |
| | | margin-right: 8px; |
| | | } |
| | | } |
| | |
| | | } |
| | | } |
| | | |
| | | .el-checkbox { |
| | | display: flex; |
| | | align-items: center; |
| | | } |
| | | |
| | | .row, |
| | | .header { |
| | | display: flex; |
| | | align-items: center; |
| | | .tree-container { |
| | | padding: 20px; |
| | | border: 1px solid #e8e8e8; |
| | | |
| | | .w20 { |
| | | width: 15%; |
| | | padding: 8px 20px; |
| | | } |
| | | |
| | | .sconed { |
| | | flex: 1; |
| | | |
| | | .subpage { |
| | | .title { |
| | | border: 1px solid #e8e8e8; |
| | | border-top: none; |
| | | border-bottom: none; |
| | | } |
| | | |
| | | .two { |
| | | display: flex; |
| | | align-items: center; |
| | | border: 1px solid #e8e8e8; |
| | | border-top: none; |
| | | border-right: none; |
| | | |
| | | .left { |
| | | width: 200px; |
| | | padding: 13px 20px; |
| | | border-right: 1px solid #e8e8e8; |
| | | } |
| | | |
| | | .right { |
| | | display: flex; |
| | | flex: 1; |
| | | |
| | | div { |
| | | padding: 13px 0 13px 20px; |
| | | } |
| | | } |
| | | } |
| | | |
| | | .two:last-child { |
| | | border-bottom: none; |
| | | } |
| | | |
| | | .btns { |
| | | display: flex; |
| | | align-items: center; |
| | | padding: 0 20px; |
| | | } |
| | | } |
| | | } |
| | | border-radius: 4px; |
| | | margin: 10px 0; |
| | | background-color: #fff; |
| | | max-height: 600px; |
| | | overflow-y: auto; |
| | | } |
| | | |
| | | .header { |
| | | background-color: #e8e8e8; |
| | | /* 树节点样式优化 */ |
| | | ::v-deep .el-tree-node__content { |
| | | height: 40px; |
| | | padding: 0 10px; |
| | | } |
| | | |
| | | .subpage { |
| | | display: flex; |
| | | } |
| | | ::v-deep .el-tree-node__label { |
| | | font-size: 14px; |
| | | } |
| | | |
| | | .title { |
| | | width: 200px; |
| | | padding: 8px 20px; |
| | | } |
| | | ::v-deep .el-tree-node.is-current > .el-tree-node__content { |
| | | background-color: #f0f7ff; |
| | | } |
| | | |
| | | /* 复选框样式优化 */ |
| | | ::v-deep .el-checkbox__inner { |
| | | border-radius: 2px; |
| | | } |
| | | |
| | | ::v-deep .el-tree-node:hover > .el-tree-node__content { |
| | | background-color: #f5f7fa; |
| | | } |
| | | </style> |