yumeng 2 дней назад
Родитель
Сommit
dc75effaf4
4 измененных файлов с 88 добавлено и 6 удалено
  1. 2 0
      src/api/modules.js
  2. 14 0
      src/router/index.js
  3. 8 2
      src/stores/auth.js
  4. 64 4
      src/views/modules/CrudRoleView.vue

+ 2 - 0
src/api/modules.js

@@ -10,6 +10,8 @@ export const deleteUserApi = (id) => http.delete(`/users/${id}`);
 export const listRolesApi = (keyword) => http.get("/roles", { params: { keyword } });
 export const saveRoleApi = (data) => http.post("/roles", data);
 export const deleteRoleApi = (id) => http.delete(`/roles/${id}`);
+export const getRoleMenuIdsApi = (id) => http.get(`/roles/${id}/menu-ids`);
+export const saveRoleMenuIdsApi = (id, data) => http.post(`/roles/${id}/menu-ids`, data);
 
 export const listDeptsApi = (keyword) => http.get("/depts", { params: { keyword } });
 export const saveDeptApi = (data) => http.post("/depts", data);

+ 14 - 0
src/router/index.js

@@ -41,6 +41,20 @@ router.beforeEach((to) => {
   if (to.path === "/login" && token) {
     return "/dashboard";
   }
+  if (token && to.path !== "/login") {
+    const menus = JSON.parse(localStorage.getItem("VIDEO_SCRIPT_MENUS") || "[]");
+    if (Array.isArray(menus) && menus.length) {
+      const hasPermission = menus.some((menu) => {
+        if (!menu?.routePath) {
+          return false;
+        }
+        return to.path === menu.routePath || to.path.startsWith(`${menu.routePath}/`);
+      });
+      if (!hasPermission) {
+        return menus[0]?.routePath || "/dashboard";
+      }
+    }
+  }
   return true;
 });
 

+ 8 - 2
src/stores/auth.js

@@ -4,8 +4,8 @@ import { loginApi, profileApi } from "../api/modules";
 export const useAuthStore = defineStore("auth", {
   state: () => ({
     token: localStorage.getItem("VIDEO_SCRIPT_TOKEN") || "",
-    userInfo: null,
-    menus: [],
+    userInfo: JSON.parse(localStorage.getItem("VIDEO_SCRIPT_USER") || "null"),
+    menus: JSON.parse(localStorage.getItem("VIDEO_SCRIPT_MENUS") || "[]"),
   }),
   actions: {
     async login(form) {
@@ -14,17 +14,23 @@ export const useAuthStore = defineStore("auth", {
       this.userInfo = payload.userInfo;
       this.menus = payload.menus || [];
       localStorage.setItem("VIDEO_SCRIPT_TOKEN", payload.token);
+      localStorage.setItem("VIDEO_SCRIPT_USER", JSON.stringify(this.userInfo));
+      localStorage.setItem("VIDEO_SCRIPT_MENUS", JSON.stringify(this.menus));
     },
     async loadProfile() {
       const payload = await profileApi();
       this.userInfo = payload.userInfo;
       this.menus = payload.menus || [];
+      localStorage.setItem("VIDEO_SCRIPT_USER", JSON.stringify(this.userInfo));
+      localStorage.setItem("VIDEO_SCRIPT_MENUS", JSON.stringify(this.menus));
     },
     logout() {
       this.token = "";
       this.userInfo = null;
       this.menus = [];
       localStorage.removeItem("VIDEO_SCRIPT_TOKEN");
+      localStorage.removeItem("VIDEO_SCRIPT_USER");
+      localStorage.removeItem("VIDEO_SCRIPT_MENUS");
     },
   },
 });

+ 64 - 4
src/views/modules/CrudRoleView.vue

@@ -12,9 +12,10 @@
       <el-table-column prop="roleName" label="角色名称" />
       <el-table-column prop="remark" label="备注" />
       <el-table-column prop="status" label="状态" />
-      <el-table-column label="操作" width="180">
+      <el-table-column label="操作" width="260">
         <template #default="{ row }">
           <el-button link type="primary" @click="openDialog(row)">编辑</el-button>
+          <el-button link type="primary" @click="openPermissionDialog(row)">分配权限</el-button>
           <el-button link type="danger" @click="handleDelete(row.id)">删除</el-button>
         </template>
       </el-table-column>
@@ -36,24 +37,83 @@
         <el-button type="primary" @click="handleSave">保存</el-button>
       </template>
     </el-dialog>
+    <el-dialog v-model="permissionVisible" :title="`分配权限 - ${currentRoleName || ''}`" width="560px">
+      <el-tree
+        ref="permissionTreeRef"
+        :data="menuTreeData"
+        node-key="id"
+        show-checkbox
+        check-strictly
+        default-expand-all
+        :props="{ label: 'menuName', children: 'children' }"
+        empty-text="暂无菜单数据"
+      />
+      <template #footer>
+        <el-button @click="permissionVisible = false">取消</el-button>
+        <el-button type="primary" @click="handleSavePermissions">保存权限</el-button>
+      </template>
+    </el-dialog>
   </div>
 </template>
 
 <script setup>
-import { onMounted, reactive, ref } from "vue";
+import { computed, nextTick, onMounted, reactive, ref } from "vue";
 import { ElMessageBox } from "element-plus";
-import { deleteRoleApi, listRolesApi, saveRoleApi } from "../../api/modules";
+import { deleteRoleApi, getRoleMenuIdsApi, listMenusApi, listRolesApi, saveRoleApi, saveRoleMenuIdsApi } from "../../api/modules";
 
 const keyword = ref("");
 const tableData = ref([]);
 const visible = ref(false);
+const permissionVisible = ref(false);
+const permissionTreeRef = ref();
+const menuOptions = ref([]);
+const currentRoleId = ref(null);
+const currentRoleName = ref("");
 const form = reactive({ id: null, roleCode: "", roleName: "", remark: "", status: 1 });
 
+const menuTreeData = computed(() => buildMenuTree(menuOptions.value));
+
 const resetForm = () => Object.assign(form, { id: null, roleCode: "", roleName: "", remark: "", status: 1 });
 const loadData = async () => { tableData.value = await listRolesApi(keyword.value); };
 const openDialog = (row) => { resetForm(); if (row) Object.assign(form, row); visible.value = true; };
 const handleSave = async () => { await saveRoleApi({ ...form }); visible.value = false; await loadData(); };
 const handleDelete = async (id) => { await ElMessageBox.confirm("确认删除该角色?", "提示"); await deleteRoleApi(id); await loadData(); };
+const loadMenus = async () => { menuOptions.value = await listMenusApi(""); };
+const openPermissionDialog = async (row) => {
+  currentRoleId.value = row.id;
+  currentRoleName.value = row.roleName;
+  if (!menuOptions.value.length) {
+    await loadMenus();
+  }
+  permissionVisible.value = true;
+  await nextTick();
+  permissionTreeRef.value?.setCheckedKeys([]);
+  const checkedKeys = await getRoleMenuIdsApi(row.id);
+  permissionTreeRef.value?.setCheckedKeys(checkedKeys || []);
+};
+const handleSavePermissions = async () => {
+  const checkedKeys = permissionTreeRef.value?.getCheckedKeys(false) || [];
+  await saveRoleMenuIdsApi(currentRoleId.value, { roleId: currentRoleId.value, menuIds: checkedKeys });
+  permissionVisible.value = false;
+};
+
+onMounted(async () => {
+  await Promise.all([loadData(), loadMenus()]);
+});
 
-onMounted(loadData);
+function buildMenuTree(items) {
+  const nodeMap = new Map();
+  const roots = [];
+  items.forEach((item) => {
+    nodeMap.set(item.id, { ...item, children: [] });
+  });
+  nodeMap.forEach((node) => {
+    if (node.parentId && nodeMap.has(node.parentId)) {
+      nodeMap.get(node.parentId).children.push(node);
+      return;
+    }
+    roots.push(node);
+  });
+  return roots;
+}
 </script>