Browse Source

更细化的权限

刘洋 1 year ago
parent
commit
ad935044f2

+ 2 - 1
.gitignore

@@ -5,4 +5,5 @@ dist-ssr
 *.local
 *.history
 package-lock.json
-yarn.lock
+yarn.lock
+components.d.ts

+ 0 - 68
components.d.ts

@@ -1,68 +0,0 @@
-/* eslint-disable */
-/* prettier-ignore */
-// @ts-nocheck
-// Generated by unplugin-vue-components
-// Read more: https://github.com/vuejs/core/pull/3399
-export {}
-
-declare module 'vue' {
-  export interface GlobalComponents {
-    Chart: typeof import('./src/components/global/chart/index.vue')['default']
-    MyDialog: typeof import('./src/components/global/my-dialog/index.vue')['default']
-    RouterLink: typeof import('vue-router')['RouterLink']
-    RouterView: typeof import('vue-router')['RouterView']
-    SButtons: typeof import('./src/components/global/s-buttons/index.vue')['default']
-    SearchForm: typeof import('./src/components/global/search-form/index.vue')['default']
-    SearchFormItem: typeof import('./src/components/global/search-form/components/search-form-item.vue')['default']
-    SelectArea: typeof import('./src/components/common/select-area/index.vue')['default']
-    SelectRole: typeof import('./src/components/common/select-role/index.vue')['default']
-    SelectServiceLevel: typeof import('./src/components/common/select-service-level/index.vue')['default']
-    SelectServiceUnit: typeof import('./src/components/common/select-service-unit/index.vue')['default']
-    SelectSupplier: typeof import('./src/components/common/select-supplier/index.vue')['default']
-    SelectUser: typeof import('./src/components/common/select-user/index.vue')['default']
-    ServiceLevel: typeof import('./src/components/common/service-level/index.vue')['default']
-    TAside: typeof import('tdesign-vue-next')['Aside']
-    TButton: typeof import('tdesign-vue-next')['Button']
-    TCascader: typeof import('tdesign-vue-next')['Cascader']
-    TCheckbox: typeof import('tdesign-vue-next')['Checkbox']
-    TCheckboxGroup: typeof import('tdesign-vue-next')['CheckboxGroup']
-    TCol: typeof import('tdesign-vue-next')['Col']
-    TConfigProvider: typeof import('tdesign-vue-next')['ConfigProvider']
-    TContent: typeof import('tdesign-vue-next')['Content']
-    TDatePicker: typeof import('tdesign-vue-next')['DatePicker']
-    TDateRangePicker: typeof import('tdesign-vue-next')['DateRangePicker']
-    TDialog: typeof import('tdesign-vue-next')['Dialog']
-    TDropdown: typeof import('tdesign-vue-next')['Dropdown']
-    TForm: typeof import('tdesign-vue-next')['Form']
-    TFormItem: typeof import('tdesign-vue-next')['FormItem']
-    THeader: typeof import('tdesign-vue-next')['Header']
-    THeadMenu: typeof import('tdesign-vue-next')['HeadMenu']
-    TIcon: typeof import('tdesign-vue-next')['Icon']
-    TInput: typeof import('tdesign-vue-next')['Input']
-    TInputNumber: typeof import('tdesign-vue-next')['InputNumber']
-    TLayout: typeof import('tdesign-vue-next')['Layout']
-    TLink: typeof import('tdesign-vue-next')['Link']
-    TMenu: typeof import('tdesign-vue-next')['Menu']
-    TMenuItem: typeof import('tdesign-vue-next')['MenuItem']
-    TOption: typeof import('tdesign-vue-next')['Option']
-    TPopconfirm: typeof import('tdesign-vue-next')['Popconfirm']
-    TRadio: typeof import('tdesign-vue-next')['Radio']
-    TRadioGroup: typeof import('tdesign-vue-next')['RadioGroup']
-    TRow: typeof import('tdesign-vue-next')['Row']
-    TSelect: typeof import('tdesign-vue-next')['Select']
-    TSpace: typeof import('tdesign-vue-next')['Space']
-    TSubmenu: typeof import('tdesign-vue-next')['Submenu']
-    TSwitch: typeof import('tdesign-vue-next')['Switch']
-    TTable: typeof import('tdesign-vue-next')['Table']
-    TTabPanel: typeof import('tdesign-vue-next')['TabPanel']
-    TTabs: typeof import('tdesign-vue-next')['Tabs']
-    TTextarea: typeof import('tdesign-vue-next')['Textarea']
-    TTimePicker: typeof import('tdesign-vue-next')['TimePicker']
-    TTimeRangePicker: typeof import('tdesign-vue-next')['TimeRangePicker']
-    TTransfer: typeof import('tdesign-vue-next')['Transfer']
-    TTree: typeof import('tdesign-vue-next')['Tree']
-    TTreeSelect: typeof import('tdesign-vue-next')['TreeSelect']
-    TUpload: typeof import('tdesign-vue-next')['Upload']
-    UploadButton: typeof import('./src/components/common/upload-button/index.vue')['default']
-  }
-}

+ 7 - 0
src/api/my-workbenches.js

@@ -0,0 +1,7 @@
+import { request } from '@/utils/request.js';
+
+export const getMyMessages = (data) =>
+  request({
+    url: '/api/sys/message/countByTypes',
+    data,
+  });

+ 5 - 9
src/api/user.js

@@ -43,6 +43,7 @@ export const addUser = (data) =>
   request({
     url: '/api/admin/user/save',
     data,
+    loading: true,
   });
 export const toggleUserStatus = (data) =>
   request({
@@ -59,6 +60,7 @@ export const addRole = (data) =>
   request({
     url: '/api/admin/role/save',
     data,
+    loading: true,
   });
 export const deleteRole = (data) =>
   request({
@@ -73,9 +75,9 @@ export const getRoleDetail = (data) =>
   });
 export const logout = (data) =>
   request({
-    url: '/api/logout',
-    method: 'post',
+    url: '/api/admin/common/logout',
     data,
+    loading: true,
   });
 export const updateMyPassword = (data) =>
   request({
@@ -92,6 +94,7 @@ export const forgetPassword = (data) =>
   request({
     url: '/api/admin/common/forget_password',
     data,
+    loading,
   });
 export const getMenus = () =>
   request({
@@ -99,10 +102,3 @@ export const getMenus = () =>
     method: 'post',
     loading: true,
   });
-
-export const editUser = (data) =>
-  request({
-    url: '/api/edit',
-    method: 'post',
-    data,
-  });

+ 21 - 0
src/directives/perm.js

@@ -0,0 +1,21 @@
+import { useUserStore } from '@/store';
+const perm = (el, binding) => {
+  const userStore = useUserStore();
+  const { value } = binding;
+  if (!!value) {
+    if (!userStore.finePermissionIds.includes(value)) {
+      el.parentNode && el.parentNode.removeChild(el);
+    }
+  } else {
+    throw new Error(`请设置操作权限标签值`);
+  }
+};
+
+export default {
+  mounted(el, binding) {
+    perm(el, binding);
+  },
+  updated(el, binding) {
+    perm(el, binding);
+  },
+};

+ 5 - 2
src/layout/index.vue

@@ -19,6 +19,7 @@
         <div class="h-full header-wrap flex items-center justify-between">
           <div class="flex-1">
             <t-head-menu
+              style="height: 55px"
               :value="userStore.curPageModule"
               theme="light"
               @change="moduleChange"
@@ -129,8 +130,7 @@ const clickHandler = (data) => {
   if (data.content === '修改密码') {
     router.push({ name: 'PasswordModify' });
   } else if (data.content === '退出') {
-    clear();
-    window.location.href = '/';
+    userStore.logout();
   }
 };
 const setTheme = (hex) => {
@@ -163,6 +163,9 @@ const colorChoose = (data) => {
     .header-wrap {
       padding: 0 10px;
       padding-right: 20px;
+      .t-head-menu .t-menu__item {
+        height: 44px;
+      }
       .real-name {
         color: @light-text-color;
       }

+ 4 - 0
src/router/index.js

@@ -31,6 +31,10 @@ router.beforeEach(async (to, from, next) => {
   document.title = `${toTitle} - ${title}`;
   // const token = local.get(import.meta.env.VITE_APP_TOKEN_PREFIX);
   if (whiteRoutes.includes(to.name)) {
+    if (to.name === 'Login') {
+      clear();
+      userStore.resetUserInfo();
+    }
     next();
     return;
   }

+ 10 - 0
src/router/modules/user.js

@@ -66,6 +66,16 @@ export default {
             alias: 'role',
           },
         },
+        {
+          name: 'AddRole',
+          path: '/user/auth-manage/add-role',
+          component: () =>
+            import('@/views/user/auth-manage/role-manage/add-role.vue'),
+          meta: {
+            title: '新增/编辑角色',
+            bind: 'role',
+          },
+        },
       ],
     },
     // {

+ 19 - 13
src/store/modules/user.js

@@ -85,20 +85,10 @@ const useUserStore = defineStore('user', {
     curPageModule: '',
     headerMenus: [],
     moduleMenus: [],
+    finePermissionIds: [],
   }),
 
   actions: {
-    // setToken(token) {
-    //   local.set(import.meta.env.VITE_APP_TOKEN_PREFIX, token);
-    // },
-
-    // getToken() {
-    //   return local.get(import.meta.env.VITE_APP_TOKEN_PREFIX);
-    // },
-
-    // clearToken() {
-    //   local.remove(import.meta.env.VITE_APP_TOKEN_PREFIX);
-    // },
     setState(data) {
       this.$patch(data);
     },
@@ -133,7 +123,22 @@ const useUserStore = defineStore('user', {
         this.setCurPageModule(menus[0].name);
       }
     },
-
+    setFinePermissionIds(data) {
+      let ids = [];
+      for (let i = 0; i < data.length; i++) {
+        let item = data[i];
+        let arr = [
+          ...(item.conditions || []),
+          ...(item.buttons || []),
+          ...(item.lists || []),
+          ...(item.links || []),
+        ];
+        arr.map((v) => {
+          ids.push(v.id);
+        });
+      }
+      this.finePermissionIds = ids.map((item) => String(item));
+    },
     requestUserMenu() {
       return new Promise((resolve, reject) => {
         getMenus()
@@ -145,6 +150,7 @@ const useUserStore = defineStore('user', {
             // let allMenus = [...whiteMenuList, ...(response.privileges || [])];
             let allMenus = [...(response.privileges || [])];
             this.setMenu(allMenus);
+            this.setFinePermissionIds(allMenus);
             resolve(allMenus);
           })
           .catch((error) => {
@@ -172,9 +178,9 @@ const useUserStore = defineStore('user', {
 
     async logout() {
       await logout();
-      // this.clearToken();
       clear();
       this.resetUserInfo();
+      router.push({ name: 'Login' });
     },
   },
 });

+ 6 - 11
src/style/reset.less

@@ -373,18 +373,18 @@ Ensure the default browser behavior of the `hidden` attribute.
 
 /*---滚动条默认显示样式--*/
 ::-webkit-scrollbar-thumb {
-  background-color: #e6e6e6;
-  border-radius: 6px;
+  background-color: #bbb;
+  border-radius: 4px;
 }
 /*---鼠标点击滚动条显示样式--*/
 ::-webkit-scrollbar-thumb:hover {
-  background-color: #e6e6e6;
-  border-radius: 6px;
+  background-color: #aaa;
+  border-radius: 4px;
 }
 /*---滚动条大小--*/
 ::-webkit-scrollbar {
-  width: 6px;
-  height: 6px;
+  width: 8px;
+  height: 8px;
 }
 
 /*---滚动框背景样式--*/
@@ -392,8 +392,3 @@ Ensure the default browser behavior of the `hidden` attribute.
   background-color: rgba(0, 0, 0, 0);
   border-radius: 0;
 }
-
-html {
-  scrollbar-width: thin;
-  scrollbar-color: #e6e6e6 transparent;
-}

+ 7 - 0
src/style/tdesign-reset.less

@@ -1,5 +1,12 @@
 .t-layout .t-table {
   margin-top: 10px;
+  font-size: 14px !important;
+  color: #515a6e;
+  th {
+    background-color: #f8f8f9;
+    font-weight: bold;
+    color: #515a6e;
+  }
   .table-operations {
     text-align: center;
     & > .t-link:not(:first-child) {

+ 5 - 3
src/utils/request.js

@@ -6,6 +6,7 @@ import qs from 'qs';
 import { h } from 'vue';
 import { LoadingPlugin } from 'tdesign-vue-next';
 import { getAuthorization, DEVICE_ID } from './crypto';
+import router from '@/router';
 
 function setAuth(config) {
   let userSession = sessionStorage.getItem('user');
@@ -88,9 +89,10 @@ function createService() {
             err(`${error?.config?.url} 服务器内部错误`);
             break;
           case 401:
-            err('登录状态已过期,需要重新登录');
-            clear();
-            window.location.href = '/';
+            err('登录状态已过期或没有权限');
+            if (error.response?.data?.message.includes('登录')) {
+              router.push({ name: 'Login' });
+            }
             break;
           case 403:
             err(`${error?.config?.url} 没有权限访问该资源`);

+ 4 - 4
src/views/login/forget-pwd.vue

@@ -93,8 +93,8 @@ import { MessagePlugin } from 'tdesign-vue-next';
 import { useRequest } from 'vue-request';
 import { getBase64 } from '@/utils/crypto';
 import { getVerifyCode, forgetPassword } from '@/api/user';
-import { useRouter } from 'vue-router';
-const router = useRouter();
+
+const emit = defineEmits(['success']);
 const form = ref();
 const { run, loading } = useRequest(getVerifyCode, {
   onSuccess: () => {
@@ -127,7 +127,7 @@ const getCode = () => {
   // resume();
 };
 const formData = reactive({
-  mobileNumber: '17786475086',
+  mobileNumber: '',
   password: '',
   code: '',
   repeatPassword: '',
@@ -190,7 +190,7 @@ const submitHandle = () => {
       }).then(() => {
         MessagePlugin.success('密码修改成功,请登录');
         clear();
-        router.push({ name: 'Login' });
+        emit('success');
       });
     }
   });

+ 9 - 2
src/views/login/index.vue

@@ -38,6 +38,7 @@
             clearable
             placeholder="请输入账号"
             size="large"
+            @enter="loginHandle"
           >
             <template #prefix-icon>
               <desktop-icon />
@@ -52,6 +53,7 @@
             clearable
             placeholder="请输入密码"
             size="large"
+            @enter="loginHandle"
           >
             <template #prefix-icon>
               <lock-on-icon />
@@ -70,7 +72,7 @@
           >登 录</t-button
         >
       </t-form>
-      <ForgetPwd v-else />
+      <ForgetPwd v-else @success="findSuccess" />
     </div>
   </div>
   <!-- <button @click="loginHandle" class="m-t-10px">登录!</button> -->
@@ -92,7 +94,7 @@ const route = useRoute();
 const router = useRouter();
 
 const formData = reactive({
-  loginName: 'sysadmin',
+  loginName: 'liuyang',
   password: '123456',
 });
 const rules = {
@@ -126,6 +128,11 @@ const loginHandle = () => {
     }
   });
 };
+const findSuccess = () => {
+  forgetStatus.value = false;
+  formData.loginName = '';
+  formData.password = '';
+};
 </script>
 <style lang="less" scoped>
 .login {

+ 6 - 1
src/views/my-workbenches/workbenches/message-reminder/index.vue

@@ -2,6 +2,11 @@
   <div class="message-reminder">消息提醒</div>
 </template>
 
-<script setup name="MessageReminder"></script>
+<script setup name="MessageReminder">
+import useFetchTable from '@/hooks/useFetchTable';
+import { getMyMessages } from '@/api/my-workbenches';
+const { loading, pagination, tableData, fetchData, search } =
+  useFetchTable(getMyMessages);
+</script>
 
 <style></style>

+ 1 - 1
src/views/user/auth-manage/role-manage/add-role-dialog.vue

@@ -3,7 +3,7 @@
     :visible="visible"
     @close="emit('update:visible', false)"
     :header="`${isEdit ? '修改' : '新增'}角色`"
-    :width="800"
+    :width="1100"
     :closeOnOverlayClick="false"
   >
     <t-form ref="formRef" :data="formData" :rules="rules" labelWidth="120px">

+ 161 - 0
src/views/user/auth-manage/role-manage/add-role.vue

@@ -0,0 +1,161 @@
+<template>
+  <div class="add-role page-wrap">
+    <t-form ref="formRef" :data="formData" :rules="rules" labelWidth="120px">
+      <t-form-item label="角色名称" name="name">
+        <t-input v-model="formData.name"></t-input>
+      </t-form-item>
+      <t-form-item label="绑定权限">
+        <!-- <t-tree-select v-model="formData.d" :data="treeData" multiple /> -->
+        <t-table
+          row-key="id"
+          :columns="columns"
+          :data="treeFlatArr"
+          bordered
+          v-loading="loading"
+        >
+          <template #page="{ row }">
+            <t-checkbox v-model="row.hasRole"></t-checkbox>
+          </template>
+          <template #conditions="{ row }">
+            <div v-for="item in row.conditions" :key="item.id">
+              <t-checkbox v-model="item.hasRole">{{ item.name }}</t-checkbox>
+            </div>
+          </template>
+          <template #buttons="{ row }">
+            <div v-for="item in row.buttons" :key="item.id">
+              <t-checkbox v-model="item.hasRole">{{ item.name }}</t-checkbox>
+            </div>
+          </template>
+          <template #lists="{ row }">
+            <div v-for="item in row.lists" :key="item.id">
+              <t-checkbox v-model="item.hasRole">{{ item.name }}</t-checkbox>
+            </div>
+          </template>
+          <template #links="{ row }">
+            <div v-for="item in row.links" :key="item.id">
+              <t-checkbox v-model="item.hasRole">{{ item.name }}</t-checkbox>
+            </div>
+          </template>
+        </t-table>
+      </t-form-item>
+    </t-form>
+    <s-button
+      class="m-t-20px"
+      confirmText="保存"
+      @confirm="submitHandler"
+      @cancel="router.back()"
+    ></s-button>
+  </div>
+</template>
+<script setup name="AddRole">
+import { ref, computed, reactive } from 'vue';
+import { getAllMenuResource, addRole, getRoleDetail } from '@/api/user';
+import { useRequest } from 'vue-request';
+import { useRoute, useRouter } from 'vue-router';
+import { MessagePlugin } from 'tdesign-vue-next';
+const route = useRoute();
+const router = useRouter();
+const formRef = ref(null);
+const formData = reactive({
+  name: '',
+});
+const curRoleBindIds = ref([]);
+const rules = {
+  name: [
+    {
+      required: true,
+      message: '请选择角色',
+    },
+  ],
+};
+let loading = ref(false);
+let treeFlatArr = ref([]);
+
+const getDetail = async () => {
+  let id = route.query.id;
+  loading.value = true;
+  if (id) {
+    let roleDetail = await getRoleDetail({ roleId: id });
+    formData.name = roleDetail.name;
+    curRoleBindIds.value = JSON.parse(roleDetail.privilegeIds || '[]').map(
+      (item) => String(item)
+    );
+  }
+  let treeData = await getAllMenuResource();
+  treeFlatArr.value = treeToArr(treeData || []);
+  loading.value = false;
+};
+getDetail();
+
+const columns = [
+  { colKey: 'level1', title: '一级页面' },
+  { colKey: 'level2', title: '二级页面' },
+  { colKey: 'level3', title: '三级页面' },
+  { colKey: 'page', title: '页面', width: 80, align: 'center' },
+  { colKey: 'conditions', title: '查询条件', width: 160 },
+  { colKey: 'buttons', title: '功能按钮', width: 160 },
+  { colKey: 'lists', title: '列表展示', width: 150 },
+  { colKey: 'links', title: '操作列', width: 150 },
+];
+const initCheckBoxValue = (arr) => {
+  return arr.map((item) => {
+    item.hasRole = curRoleBindIds.value.includes(item.id) ? true : false;
+    return item;
+  });
+};
+const treeToArr = (tree, arr = [], level = 1) => {
+  for (let i = 0; i < tree.length; i++) {
+    let l = level;
+    arr.push({
+      ...tree[i],
+      level: l,
+      level1: l == 1 ? tree[i].name : '',
+      level2: l == 2 ? tree[i].name : '',
+      level3: l == 3 ? tree[i].name : '',
+      hasRole: curRoleBindIds.value.includes(tree[i].id) ? true : false,
+      conditions: tree[i].conditions
+        ? initCheckBoxValue(tree[i].conditions)
+        : [],
+      buttons: tree[i].buttons ? initCheckBoxValue(tree[i].buttons) : [],
+      links: tree[i].links ? initCheckBoxValue(tree[i].links) : [],
+      lists: tree[i].lists ? initCheckBoxValue(tree[i].lists) : [],
+    });
+    l++;
+    if (tree[i].children && tree[i].children.length) {
+      treeToArr(tree[i].children, arr, l);
+    }
+  }
+  return arr;
+};
+const getHasRoleIds = (data, arr = []) => {
+  for (let i = 0; i < data.length; i++) {
+    if (data[i].hasRole) {
+      arr.push(data[i].id);
+    }
+    getHasRoleIds(data[i].children || [], arr);
+    getHasRoleIds(data[i].conditions || [], arr);
+    getHasRoleIds(data[i].buttons || [], arr);
+    getHasRoleIds(data[i].lists || [], arr);
+    getHasRoleIds(data[i].links || [], arr);
+  }
+  return Array.from(new Set(arr));
+};
+const addHandler = () => {
+  let checkIds = getHasRoleIds(treeFlatArr.value);
+  addRole({
+    name: formData.name,
+    privilegeIds: checkIds,
+    id: route.query?.id || undefined,
+  }).then(() => {
+    MessagePlugin.success('操作成功');
+    router.back();
+  });
+};
+const submitHandler = () => {
+  formRef.value.validate().then(async (result) => {
+    if (result === true) {
+      addHandler();
+    }
+  });
+};
+</script>

+ 7 - 24
src/views/user/auth-manage/role-manage/index.vue

@@ -2,14 +2,7 @@
   <div class="role h-full">
     <div class="flex-1 page-wrap">
       <div class="btn-group">
-        <t-button
-          theme="success"
-          @click="
-            curRow = null;
-            showAddRoleDialog = true;
-          "
-          >新增角色</t-button
-        >
+        <t-button theme="success" @click="toAddRole({})">新增角色</t-button>
       </div>
       <t-table
         size="small"
@@ -27,11 +20,6 @@
       >
       </t-table>
     </div>
-    <AddRoleDialog
-      v-model:visible="showAddRoleDialog"
-      :curRow="curRow"
-      @success="addSuccess"
-    ></AddRoleDialog>
   </div>
 </template>
 
@@ -39,10 +27,12 @@
 import { reactive, ref } from 'vue';
 import { DialogPlugin, MessagePlugin } from 'tdesign-vue-next';
 import useFetchTable from '@/hooks/useFetchTable';
-import AddRoleDialog from './add-role-dialog.vue';
 import { getRoleList, deleteRole } from '@/api/user';
-const showAddRoleDialog = ref(false);
-const curRow = ref(null);
+import { useRouter } from 'vue-router';
+const router = useRouter();
+const toAddRole = (params) => {
+  router.push({ name: 'AddRole', query: params });
+};
 const columns = [
   { colKey: 'name', title: '角色名称' },
   {
@@ -58,8 +48,7 @@ const columns = [
             hover="color"
             onClick={(e) => {
               e.stopPropagation();
-              curRow.value = row;
-              showAddRoleDialog.value = true;
+              toAddRole({ id: row.id });
             }}
           >
             修改
@@ -102,12 +91,6 @@ const {
   onChange,
   search,
 } = useFetchTable(getRoleList);
-
-const addSuccess = () => {
-  showAddRoleDialog.value = false;
-  MessagePlugin.success('操作成功');
-  search();
-};
 </script>
 
 <style></style>