backend/src/views/basicSettings/roles/components/edit.vue

272 lines
7.0 KiB
Vue

<template>
<el-dialog v-model="visible" :title="isEdit ? '编辑角色' : '添加角色'" width="600px" @close="handleClose">
<el-form :model="form" :rules="rules" ref="formRef" label-width="100px">
<el-form-item label="角色名称" prop="name">
<el-input v-model="form.name" placeholder="请输入角色名称" maxlength="50" show-word-limit />
</el-form-item>
<el-form-item label="状态" prop="status">
<el-radio-group v-model="form.status">
<el-radio :label="1">启用</el-radio>
<el-radio :label="0">禁用</el-radio>
</el-radio-group>
</el-form-item>
<el-form-item label="权限设置" prop="rights">
<div style="margin-bottom: 10px;">
<el-button size="small" @click="toggleSelectAll">
{{ isAllSelected ? '全不选' : '全选' }}
</el-button>
<el-button size="small" @click="handleExpandAll">展开/折叠</el-button>
</div>
<el-tree ref="treeRef" :data="menuTree" show-checkbox node-key="id"
:props="{ children: 'children', label: 'title' }" @check="updateSelectStatus"
style="width: 100%; border: 1px solid var(--el-border-color); border-radius: 4px; padding: 10px; max-height: 400px; overflow-y: auto;" />
</el-form-item>
</el-form>
<template #footer>
<el-button @click="handleClose">取消</el-button>
<el-button type="primary" @click="handleSubmit" :loading="submitting">
确定
</el-button>
</template>
</el-dialog>
</template>
<script setup lang="ts">
import { ref, watch, nextTick } from "vue";
import { ElMessage } from "element-plus";
import { createRole, updateRole } from "@/api/role";
import { getAllMenus } from "@/api/menu";
interface Props {
modelValue: boolean;
role?: any;
}
const props = withDefaults(defineProps<Props>(), {
modelValue: false,
role: null,
});
const emit = defineEmits(["update:modelValue", "success"]);
// --- 状态定义 ---
const visible = ref(false);
const isEdit = ref(false);
const submitting = ref(false);
const formRef = ref();
const treeRef = ref();
const menuTree = ref<any[]>([]);
const isAllSelected = ref(false);
const isExpandAll = ref(false);
const form = ref({
name: "",
status: 1,
rights: [] as number[],
});
const rules = {
name: [
{ required: true, message: "请输入角色名称", trigger: "blur" },
{ min: 2, max: 50, message: "角色名称长度在 2 到 50 个字符", trigger: "blur" },
],
status: [{ required: true, message: "请选择状态", trigger: "change" }],
};
// --- 辅助功能 ---
// 获取树中所有节点的 ID 列表
const getAllMenuIds = (data: any[]): number[] => {
const ids: number[] = [];
const traverse = (list: any[]) => {
list.forEach((item) => {
ids.push(item.id);
if (item.children && item.children.length > 0) traverse(item.children);
});
};
traverse(data);
return ids;
};
// 展开折叠
const handleExpandAll = () => {
if (!treeRef.value) return;
// 切换状态
isExpandAll.value = !isExpandAll.value;
// 获取树的所有节点对象
// nodesMap 包含了树中所有的节点实例
const nodes = treeRef.value.store.nodesMap;
for (let id in nodes) {
// 强制修改每个节点的 expanded 属性
nodes[id].expanded = isExpandAll.value;
}
};
// 更新“全选/全不选”按钮的状态
const updateSelectStatus = () => {
if (!treeRef.value || menuTree.value.length === 0) {
isAllSelected.value = false;
return;
}
const checkedKeys = treeRef.value.getCheckedKeys();
const allIds = getAllMenuIds(menuTree.value);
// 只有当勾选数等于总数时,才显示“全不选”
isAllSelected.value = allIds.length > 0 && checkedKeys.length === allIds.length;
};
// 全选/全不选 切换逻辑
const toggleSelectAll = () => {
if (!treeRef.value) return;
if (isAllSelected.value) {
// 当前是全选状态 -> 执行清空
treeRef.value.setCheckedKeys([]);
isAllSelected.value = false;
} else {
// 当前不是全选状态 -> 执行全选
const allIds = getAllMenuIds(menuTree.value);
treeRef.value.setCheckedKeys(allIds);
isAllSelected.value = true;
}
};
// --- 数据加载与监听 ---
// 加载菜单树数据
const loadMenus = async () => {
try {
const res = await getAllMenus();
if (res.code === 200) {
menuTree.value = res.data || [];
}
} catch (error) {
console.error("加载菜单失败:", error);
ElMessage.error("加载菜单失败");
}
};
// 解析权限数据
const parseRights = (rights: any): number[] => {
if (!rights) return [];
if (Array.isArray(rights)) return rights;
if (typeof rights === "string") {
try {
const parsed = JSON.parse(rights);
return Array.isArray(parsed) ? parsed : [];
} catch {
return [];
}
}
return [];
};
// 核心监听:处理弹窗打开时的初始化
watch(
() => props.modelValue,
async (val) => {
visible.value = val;
if (val) {
// 先加载菜单数据
await loadMenus();
// 判断是编辑还是新增
if (props.role) {
isEdit.value = true;
form.value = {
name: props.role.name,
status: props.role.status,
rights: parseRights(props.role.rights),
};
// 设置树的回显
nextTick(() => {
if (treeRef.value) {
treeRef.value.setCheckedKeys(form.value.rights);
updateSelectStatus(); // 初始化按钮状态
}
});
} else {
isEdit.value = false;
isAllSelected.value = false;
resetForm();
}
}
}
);
watch(visible, (val) => {
emit("update:modelValue", val);
});
// --- 表单操作 ---
const resetForm = () => {
form.value = {
name: "",
status: 1,
rights: [],
};
if (formRef.value) formRef.value.clearValidate();
if (treeRef.value) treeRef.value.setCheckedKeys([]);
};
const handleClose = () => {
visible.value = false;
resetForm();
};
const handleSubmit = async () => {
if (!formRef.value) return;
try {
await formRef.value.validate();
// 组合选中节点(全选 + 半选)
const checkedKeys = treeRef.value.getCheckedKeys();
const halfCheckedKeys = treeRef.value.getHalfCheckedKeys();
const allCheckedKeys = [...checkedKeys, ...halfCheckedKeys];
const submitData = {
name: form.value.name,
status: form.value.status,
rights: allCheckedKeys,
};
submitting.value = true;
let res;
if (isEdit.value && props.role) {
res = await updateRole(props.role.id, submitData);
} else {
res = await createRole(submitData);
}
if (res.code === 200) {
ElMessage.success(isEdit.value ? "更新成功" : "创建成功");
emit("success");
handleClose();
} else {
ElMessage.error(res.msg || "操作失败");
}
} catch (error: any) {
if (error !== false) {
ElMessage.error(error.message || "操作失败");
}
} finally {
submitting.value = false;
}
};
</script>
<style scoped>
:deep(.el-tree) {
background-color: var(--el-bg-color);
}
</style>