wangxx 4 mēneši atpakaļ
vecāks
revīzija
4ce3059135

+ 60 - 0
src/plugins/auth.js

@@ -0,0 +1,60 @@
1
+import useUserStore from '@/store/modules/user'
2
+
3
+function authPermission(permission) {
4
+  const all_permission = "*:*:*"
5
+  const permissions = useUserStore().permissions
6
+  if (permission && permission.length > 0) {
7
+    return permissions.some(v => {
8
+      return all_permission === v || v === permission
9
+    })
10
+  } else {
11
+    return false
12
+  }
13
+}
14
+
15
+function authRole(role) {
16
+  const super_admin = "admin"
17
+  const roles = useUserStore().roles
18
+  if (role && role.length > 0) {
19
+    return roles.some(v => {
20
+      return super_admin === v || v === role
21
+    })
22
+  } else {
23
+    return false
24
+  }
25
+}
26
+
27
+export default {
28
+  // 验证用户是否具备某权限
29
+  hasPermi(permission) {
30
+    return authPermission(permission)
31
+  },
32
+  // 验证用户是否含有指定权限,只需包含其中一个
33
+  hasPermiOr(permissions) {
34
+    return permissions.some(item => {
35
+      return authPermission(item)
36
+    })
37
+  },
38
+  // 验证用户是否含有指定权限,必须全部拥有
39
+  hasPermiAnd(permissions) {
40
+    return permissions.every(item => {
41
+      return authPermission(item)
42
+    })
43
+  },
44
+  // 验证用户是否具备某角色
45
+  hasRole(role) {
46
+    return authRole(role)
47
+  },
48
+  // 验证用户是否含有指定角色,只需包含其中一个
49
+  hasRoleOr(roles) {
50
+    return roles.some(item => {
51
+      return authRole(item)
52
+    })
53
+  },
54
+  // 验证用户是否含有指定角色,必须全部拥有
55
+  hasRoleAnd(roles) {
56
+    return roles.every(item => {
57
+      return authRole(item)
58
+    })
59
+  }
60
+}

+ 79 - 0
src/plugins/cache.js

@@ -0,0 +1,79 @@
1
+const sessionCache = {
2
+  set (key, value) {
3
+    if (!sessionStorage) {
4
+      return
5
+    }
6
+    if (key != null && value != null) {
7
+      sessionStorage.setItem(key, value)
8
+    }
9
+  },
10
+  get (key) {
11
+    if (!sessionStorage) {
12
+      return null
13
+    }
14
+    if (key == null) {
15
+      return null
16
+    }
17
+    return sessionStorage.getItem(key)
18
+  },
19
+  setJSON (key, jsonValue) {
20
+    if (jsonValue != null) {
21
+      this.set(key, JSON.stringify(jsonValue))
22
+    }
23
+  },
24
+  getJSON (key) {
25
+    const value = this.get(key)
26
+    if (value != null) {
27
+      return JSON.parse(value)
28
+    }
29
+    return null
30
+  },
31
+  remove (key) {
32
+    sessionStorage.removeItem(key)
33
+  }
34
+}
35
+const localCache = {
36
+  set (key, value) {
37
+    if (!localStorage) {
38
+      return
39
+    }
40
+    if (key != null && value != null) {
41
+      localStorage.setItem(key, value)
42
+    }
43
+  },
44
+  get (key) {
45
+    if (!localStorage) {
46
+      return null
47
+    }
48
+    if (key == null) {
49
+      return null
50
+    }
51
+    return localStorage.getItem(key)
52
+  },
53
+  setJSON (key, jsonValue) {
54
+    if (jsonValue != null) {
55
+      this.set(key, JSON.stringify(jsonValue))
56
+    }
57
+  },
58
+  getJSON (key) {
59
+    const value = this.get(key)
60
+    if (value != null) {
61
+      return JSON.parse(value)
62
+    }
63
+    return null
64
+  },
65
+  remove (key) {
66
+    localStorage.removeItem(key)
67
+  }
68
+}
69
+
70
+export default {
71
+  /**
72
+   * 会话级缓存
73
+   */
74
+  session: sessionCache,
75
+  /**
76
+   * 本地缓存
77
+   */
78
+  local: localCache
79
+}

+ 79 - 0
src/plugins/download.js

@@ -0,0 +1,79 @@
1
+import axios from 'axios'
2
+import { ElLoading, ElMessage } from 'element-plus'
3
+import { saveAs } from 'file-saver'
4
+import { getToken } from '@/utils/auth'
5
+import errorCode from '@/utils/errorCode'
6
+import { blobValidate } from '@/utils/ruoyi'
7
+
8
+const baseURL = import.meta.env.VITE_APP_BASE_API
9
+let downloadLoadingInstance
10
+
11
+export default {
12
+  name(name, isDelete = true) {
13
+    var url = baseURL + "/common/download?fileName=" + encodeURIComponent(name) + "&delete=" + isDelete
14
+    axios({
15
+      method: 'get',
16
+      url: url,
17
+      responseType: 'blob',
18
+      headers: { 'Authorization': 'Bearer ' + getToken() }
19
+    }).then((res) => {
20
+      const isBlob = blobValidate(res.data)
21
+      if (isBlob) {
22
+        const blob = new Blob([res.data])
23
+        this.saveAs(blob, decodeURIComponent(res.headers['download-filename']))
24
+      } else {
25
+        this.printErrMsg(res.data)
26
+      }
27
+    })
28
+  },
29
+  resource(resource) {
30
+    var url = baseURL + "/common/download/resource?resource=" + encodeURIComponent(resource)
31
+    axios({
32
+      method: 'get',
33
+      url: url,
34
+      responseType: 'blob',
35
+      headers: { 'Authorization': 'Bearer ' + getToken() }
36
+    }).then((res) => {
37
+      const isBlob = blobValidate(res.data)
38
+      if (isBlob) {
39
+        const blob = new Blob([res.data])
40
+        this.saveAs(blob, decodeURIComponent(res.headers['download-filename']))
41
+      } else {
42
+        this.printErrMsg(res.data)
43
+      }
44
+    })
45
+  },
46
+  zip(url, name) {
47
+    var url = baseURL + url
48
+    downloadLoadingInstance = ElLoading.service({ text: "正在下载数据,请稍候", background: "rgba(0, 0, 0, 0.7)", })
49
+    axios({
50
+      method: 'get',
51
+      url: url,
52
+      responseType: 'blob',
53
+      headers: { 'Authorization': 'Bearer ' + getToken() }
54
+    }).then((res) => {
55
+      const isBlob = blobValidate(res.data)
56
+      if (isBlob) {
57
+        const blob = new Blob([res.data], { type: 'application/zip' })
58
+        this.saveAs(blob, name)
59
+      } else {
60
+        this.printErrMsg(res.data)
61
+      }
62
+      downloadLoadingInstance.close()
63
+    }).catch((r) => {
64
+      console.error(r)
65
+      ElMessage.error('下载文件出现错误,请联系管理员!')
66
+      downloadLoadingInstance.close()
67
+    })
68
+  },
69
+  saveAs(text, name, opts) {
70
+    saveAs(text, name, opts)
71
+  },
72
+  async printErrMsg(data) {
73
+    const resText = await data.text()
74
+    const rspObj = JSON.parse(resText)
75
+    const errMsg = errorCode[rspObj.code] || rspObj.msg || errorCode['default']
76
+    ElMessage.error(errMsg)
77
+  }
78
+}
79
+

+ 18 - 0
src/plugins/index.js

@@ -0,0 +1,18 @@
1
+import tab from './tab'
2
+import auth from './auth'
3
+import cache from './cache'
4
+import modal from './modal'
5
+import download from './download'
6
+
7
+export default function installPlugins(app){
8
+  // 页签操作
9
+  app.config.globalProperties.$tab = tab
10
+  // 认证对象
11
+  app.config.globalProperties.$auth = auth
12
+  // 缓存对象
13
+  app.config.globalProperties.$cache = cache
14
+  // 模态框对象
15
+  app.config.globalProperties.$modal = modal
16
+  // 下载文件
17
+  app.config.globalProperties.$download = download
18
+}

+ 82 - 0
src/plugins/modal.js

@@ -0,0 +1,82 @@
1
+import { ElMessage, ElMessageBox, ElNotification, ElLoading } from 'element-plus'
2
+
3
+let loadingInstance
4
+
5
+export default {
6
+  // 消息提示
7
+  msg(content) {
8
+    ElMessage.info(content)
9
+  },
10
+  // 错误消息
11
+  msgError(content) {
12
+    ElMessage.error(content)
13
+  },
14
+  // 成功消息
15
+  msgSuccess(content) {
16
+    ElMessage.success(content)
17
+  },
18
+  // 警告消息
19
+  msgWarning(content) {
20
+    ElMessage.warning(content)
21
+  },
22
+  // 弹出提示
23
+  alert(content) {
24
+    ElMessageBox.alert(content, "系统提示")
25
+  },
26
+  // 错误提示
27
+  alertError(content) {
28
+    ElMessageBox.alert(content, "系统提示", { type: 'error' })
29
+  },
30
+  // 成功提示
31
+  alertSuccess(content) {
32
+    ElMessageBox.alert(content, "系统提示", { type: 'success' })
33
+  },
34
+  // 警告提示
35
+  alertWarning(content) {
36
+    ElMessageBox.alert(content, "系统提示", { type: 'warning' })
37
+  },
38
+  // 通知提示
39
+  notify(content) {
40
+    ElNotification.info(content)
41
+  },
42
+  // 错误通知
43
+  notifyError(content) {
44
+    ElNotification.error(content)
45
+  },
46
+  // 成功通知
47
+  notifySuccess(content) {
48
+    ElNotification.success(content)
49
+  },
50
+  // 警告通知
51
+  notifyWarning(content) {
52
+    ElNotification.warning(content)
53
+  },
54
+  // 确认窗体
55
+  confirm(content) {
56
+    return ElMessageBox.confirm(content, "系统提示", {
57
+      confirmButtonText: '确定',
58
+      cancelButtonText: '取消',
59
+      type: "warning",
60
+    })
61
+  },
62
+  // 提交内容
63
+  prompt(content) {
64
+    return ElMessageBox.prompt(content, "系统提示", {
65
+      confirmButtonText: '确定',
66
+      cancelButtonText: '取消',
67
+      type: "warning",
68
+    })
69
+  },
70
+  // 打开遮罩层
71
+  loading(content) {
72
+    loadingInstance = ElLoading.service({
73
+      lock: true,
74
+      text: content,
75
+      background: "rgba(0, 0, 0, 0.7)",
76
+    })
77
+  },
78
+  // 关闭遮罩层
79
+  closeLoading() {
80
+    loadingInstance.close()
81
+  }
82
+}

+ 71 - 0
src/plugins/tab.js

@@ -0,0 +1,71 @@
1
+import useTagsViewStore from '@/store/modules/tagsView'
2
+import router from '@/router'
3
+
4
+export default {
5
+  // 刷新当前tab页签
6
+  refreshPage(obj) {
7
+    const { path, query, matched } = router.currentRoute.value
8
+    if (obj === undefined) {
9
+      matched.forEach((m) => {
10
+        if (m.components && m.components.default && m.components.default.name) {
11
+          if (!['Layout', 'ParentView'].includes(m.components.default.name)) {
12
+            obj = { name: m.components.default.name, path: path, query: query }
13
+          }
14
+        }
15
+      })
16
+    }
17
+    return useTagsViewStore().delCachedView(obj).then(() => {
18
+      const { path, query } = obj
19
+      router.replace({
20
+        path: '/redirect' + path,
21
+        query: query
22
+      })
23
+    })
24
+  },
25
+  // 关闭当前tab页签,打开新页签
26
+  closeOpenPage(obj) {
27
+    useTagsViewStore().delView(router.currentRoute.value)
28
+    if (obj !== undefined) {
29
+      return router.push(obj)
30
+    }
31
+  },
32
+  // 关闭指定tab页签
33
+  closePage(obj) {
34
+    if (obj === undefined) {
35
+      return useTagsViewStore().delView(router.currentRoute.value).then(({ visitedViews }) => {
36
+        const latestView = visitedViews.slice(-1)[0]
37
+        if (latestView) {
38
+          return router.push(latestView.fullPath)
39
+        }
40
+        return router.push('/')
41
+      })
42
+    }
43
+    return useTagsViewStore().delView(obj)
44
+  },
45
+  // 关闭所有tab页签
46
+  closeAllPage() {
47
+    return useTagsViewStore().delAllViews()
48
+  },
49
+  // 关闭左侧tab页签
50
+  closeLeftPage(obj) {
51
+    return useTagsViewStore().delLeftTags(obj || router.currentRoute.value)
52
+  },
53
+  // 关闭右侧tab页签
54
+  closeRightPage(obj) {
55
+    return useTagsViewStore().delRightTags(obj || router.currentRoute.value)
56
+  },
57
+  // 关闭其他tab页签
58
+  closeOtherPage(obj) {
59
+    return useTagsViewStore().delOthersViews(obj || router.currentRoute.value)
60
+  },
61
+  // 打开tab页签
62
+  openPage(title, url, params) {
63
+    const obj = { path: url, meta: { title: title } }
64
+    useTagsViewStore().addView(obj)
65
+    return router.push({ path: url, query: params })
66
+  },
67
+  // 修改tab页签
68
+  updatePage(obj) {
69
+    return useTagsViewStore().updateVisitedView(obj)
70
+  }
71
+}

+ 210 - 0
src/router/index.js

@@ -0,0 +1,210 @@
1
+import { createWebHistory, createRouter } from 'vue-router'
2
+/* Layout */
3
+import Layout from '@/layout'
4
+
5
+/**
6
+ * Note: 路由配置项
7
+ *
8
+ * hidden: true                     // 当设置 true 的时候该路由不会再侧边栏出现 如401,login等页面,或者如一些编辑页面/edit/1
9
+ * alwaysShow: true                 // 当你一个路由下面的 children 声明的路由大于1个时,自动会变成嵌套的模式--如组件页面
10
+ *                                  // 只有一个时,会将那个子路由当做根路由显示在侧边栏--如引导页面
11
+ *                                  // 若你想不管路由下面的 children 声明的个数都显示你的根路由
12
+ *                                  // 你可以设置 alwaysShow: true,这样它就会忽略之前定义的规则,一直显示根路由
13
+ * redirect: noRedirect             // 当设置 noRedirect 的时候该路由在面包屑导航中不可被点击
14
+ * name:'router-name'               // 设定路由的名字,一定要填写不然使用<keep-alive>时会出现各种问题
15
+ * query: '{"id": 1, "name": "ry"}' // 访问路由的默认传递参数
16
+ * roles: ['admin', 'common']       // 访问路由的角色权限
17
+ * permissions: ['a:a:a', 'b:b:b']  // 访问路由的菜单权限
18
+ * meta : {
19
+    noCache: true                   // 如果设置为true,则不会被 <keep-alive> 缓存(默认 false)
20
+    title: 'title'                  // 设置该路由在侧边栏和面包屑中展示的名字
21
+    icon: 'svg-name'                // 设置该路由的图标,对应路径src/assets/icons/svg
22
+    breadcrumb: false               // 如果设置为false,则不会在breadcrumb面包屑中显示
23
+    activeMenu: '/system/user'      // 当路由设置了该属性,则会高亮相对应的侧边栏。
24
+  }
25
+ */
26
+
27
+// 公共路由
28
+export const constantRoutes = [
29
+  {
30
+    path: '/redirect',
31
+    component: Layout,
32
+    hidden: true,
33
+    children: [
34
+      {
35
+        path: '/redirect/:path(.*)',
36
+        component: () => import('@/views/redirect/index.vue')
37
+      }
38
+    ]
39
+  },
40
+  {
41
+    path: '/login',
42
+    component: () => import('@/views/login'),
43
+    hidden: true
44
+  },
45
+  {
46
+    path: '/register',
47
+    component: () => import('@/views/register'),
48
+    hidden: true
49
+  },
50
+  {
51
+    path: "/:pathMatch(.*)*",
52
+    component: () => import('@/views/error/404'),
53
+    hidden: true
54
+  },
55
+  {
56
+    path: '/401',
57
+    component: () => import('@/views/error/401'),
58
+    hidden: true
59
+  },
60
+  // {
61
+  //   path: '',
62
+  //   component: Layout,
63
+  //   redirect: '/index',
64
+  //   children: [
65
+  //     {
66
+  //       path: '/index',
67
+  //       component: () => import('@/views/index'),
68
+  //       name: 'Index',
69
+  //       meta: { title: '首页', icon: 'dashboard', affix: true }
70
+  //     }
71
+  //   ]
72
+  // },
73
+  {
74
+    path: '/user',
75
+    component: Layout,
76
+    hidden: true,
77
+    redirect: 'noredirect',
78
+    children: [
79
+      {
80
+        path: 'profile/:activeTab?',
81
+        component: () => import('@/views/system/user/profile/index'),
82
+        name: 'Profile',
83
+        meta: { title: '个人中心', icon: 'user' }
84
+      }
85
+    ]
86
+  },
87
+  {
88
+    path: '',
89
+    component: Layout,
90
+    redirect: '/index',
91
+    meta: { title: '监控中心', icon: 'dashboard', affix: true },
92
+    children: [
93
+      {
94
+        path: '/index',
95
+        component: () => import('@/views/dataBigScreen/dashboard'),
96
+        name: 'Index',
97
+        meta: { title: '智慧大屏监控中心', icon: 'dashboard' }
98
+      },
99
+      // {
100
+      //   path: '/dashboard-work',
101
+      //   component: () => import('@/views/dataBigScreen/dashboard-work'),
102
+      //   meta: { title: '工作质量管理大屏', icon: 'dashboard' }
103
+      // },
104
+      // {
105
+      //   path: '/dashboard-attendance',
106
+      //   component: () => import('@/views/dataBigScreen/attendance'),
107
+      //   meta: { title: '机场安检员考勤管理系统', icon: 'dashboard' }
108
+      // },
109
+      // {
110
+	    //   path: '/examQuestionStatistics',
111
+	    //   component: () => import('@/views/dataBigScreen/examQuestionStatistics'),
112
+	    //   name: 'examQuestionStatistics',
113
+	    //   meta: { title: '答题统计', icon: 'dashboard' }
114
+	    // },
115
+      // {
116
+	    //   path: '/abilityPortrait',
117
+	    //   component: () => import('@/views/dataBigScreen/abilityPortrait'),
118
+	    //   name: 'abilityPortrait',
119
+	    //   meta: { title: '能力画像显示大屏', icon: 'dashboard' }
120
+	    // }
121
+    ]
122
+  },
123
+]
124
+
125
+// 动态路由,基于用户权限动态去加载
126
+export const dynamicRoutes = [
127
+  {
128
+    path: '/system/user-auth',
129
+    component: Layout,
130
+    hidden: true,
131
+    permissions: ['system:user:edit'],
132
+    children: [
133
+      {
134
+        path: 'role/:userId(\\d+)',
135
+        component: () => import('@/views/system/user/authRole'),
136
+        name: 'AuthRole',
137
+        meta: { title: '分配角色', activeMenu: '/system/user' }
138
+      }
139
+    ]
140
+  },
141
+  {
142
+    path: '/system/role-auth',
143
+    component: Layout,
144
+    hidden: true,
145
+    permissions: ['system:role:edit'],
146
+    children: [
147
+      {
148
+        path: 'user/:roleId(\\d+)',
149
+        component: () => import('@/views/system/role/authUser'),
150
+        name: 'AuthUser',
151
+        meta: { title: '分配用户', activeMenu: '/system/role' }
152
+      }
153
+    ]
154
+  },
155
+  {
156
+    path: '/system/dict-data',
157
+    component: Layout,
158
+    hidden: true,
159
+    permissions: ['system:dict:list'],
160
+    children: [
161
+      {
162
+        path: 'index/:dictId(\\d+)',
163
+        component: () => import('@/views/system/dict/data'),
164
+        name: 'Data',
165
+        meta: { title: '字典数据', activeMenu: '/system/dict' }
166
+      }
167
+    ]
168
+  },
169
+  {
170
+    path: '/monitor/job-log',
171
+    component: Layout,
172
+    hidden: true,
173
+    permissions: ['monitor:job:list'],
174
+    children: [
175
+      {
176
+        path: 'index/:jobId(\\d+)',
177
+        component: () => import('@/views/monitor/job/log'),
178
+        name: 'JobLog',
179
+        meta: { title: '调度日志', activeMenu: '/monitor/job' }
180
+      }
181
+    ]
182
+  },
183
+  {
184
+    path: '/tool/gen-edit',
185
+    component: Layout,
186
+    hidden: true,
187
+    permissions: ['tool:gen:edit'],
188
+    children: [
189
+      {
190
+        path: 'index/:tableId(\\d+)',
191
+        component: () => import('@/views/tool/gen/editTable'),
192
+        name: 'GenEdit',
193
+        meta: { title: '修改生成配置', activeMenu: '/tool/gen' }
194
+      }
195
+    ]
196
+  }
197
+]
198
+
199
+const router = createRouter({
200
+  history: createWebHistory(),
201
+  routes: constantRoutes,
202
+  scrollBehavior(to, from, savedPosition) {
203
+    if (savedPosition) {
204
+      return savedPosition
205
+    }
206
+    return { top: 0 }
207
+  },
208
+})
209
+
210
+export default router

+ 3 - 0
src/store/index.js

@@ -0,0 +1,3 @@
1
+const store = createPinia()
2
+
3
+export default store

+ 46 - 0
src/store/modules/app.js

@@ -0,0 +1,46 @@
1
+import Cookies from 'js-cookie'
2
+
3
+const useAppStore = defineStore(
4
+  'app',
5
+  {
6
+    state: () => ({
7
+      sidebar: {
8
+        opened: Cookies.get('sidebarStatus') ? !!+Cookies.get('sidebarStatus') : true,
9
+        withoutAnimation: false,
10
+        hide: false
11
+      },
12
+      device: 'desktop',
13
+      size: Cookies.get('size') || 'default'
14
+    }),
15
+    actions: {
16
+      toggleSideBar(withoutAnimation) {
17
+        if (this.sidebar.hide) {
18
+          return false
19
+        }
20
+        this.sidebar.opened = !this.sidebar.opened
21
+        this.sidebar.withoutAnimation = withoutAnimation
22
+        if (this.sidebar.opened) {
23
+          Cookies.set('sidebarStatus', 1)
24
+        } else {
25
+          Cookies.set('sidebarStatus', 0)
26
+        }
27
+      },
28
+      closeSideBar({ withoutAnimation }) {
29
+        Cookies.set('sidebarStatus', 0)
30
+        this.sidebar.opened = false
31
+        this.sidebar.withoutAnimation = withoutAnimation
32
+      },
33
+      toggleDevice(device) {
34
+        this.device = device
35
+      },
36
+      setSize(size) {
37
+        this.size = size
38
+        Cookies.set('size', size)
39
+      },
40
+      toggleSideBarHide(status) {
41
+        this.sidebar.hide = status
42
+      }
43
+    }
44
+  })
45
+
46
+export default useAppStore

+ 57 - 0
src/store/modules/dict.js

@@ -0,0 +1,57 @@
1
+const useDictStore = defineStore(
2
+  'dict',
3
+  {
4
+    state: () => ({
5
+      dict: new Array()
6
+    }),
7
+    actions: {
8
+      // 获取字典
9
+      getDict(_key) {
10
+        if (_key == null && _key == "") {
11
+          return null
12
+        }
13
+        try {
14
+          for (let i = 0; i < this.dict.length; i++) {
15
+            if (this.dict[i].key == _key) {
16
+              return this.dict[i].value
17
+            }
18
+          }
19
+        } catch (e) {
20
+          return null
21
+        }
22
+      },
23
+      // 设置字典
24
+      setDict(_key, value) {
25
+        if (_key !== null && _key !== "") {
26
+          this.dict.push({
27
+            key: _key,
28
+            value: value
29
+          })
30
+        }
31
+      },
32
+      // 删除字典
33
+      removeDict(_key) {
34
+        var bln = false
35
+        try {
36
+          for (let i = 0; i < this.dict.length; i++) {
37
+            if (this.dict[i].key == _key) {
38
+              this.dict.splice(i, 1)
39
+              return true
40
+            }
41
+          }
42
+        } catch (e) {
43
+          bln = false
44
+        }
45
+        return bln
46
+      },
47
+      // 清空字典
48
+      cleanDict() {
49
+        this.dict = new Array()
50
+      },
51
+      // 初始字典
52
+      initDict() {
53
+      }
54
+    }
55
+  })
56
+
57
+export default useDictStore

+ 131 - 0
src/store/modules/permission.js

@@ -0,0 +1,131 @@
1
+import auth from '@/plugins/auth'
2
+import router, { constantRoutes, dynamicRoutes } from '@/router'
3
+import { getRouters } from '@/api/menu'
4
+import Layout from '@/layout/index'
5
+import ParentView from '@/components/ParentView'
6
+import InnerLink from '@/layout/components/InnerLink'
7
+
8
+// 匹配views里面所有的.vue文件
9
+const modules = import.meta.glob('./../../views/**/*.vue')
10
+
11
+const usePermissionStore = defineStore(
12
+  'permission',
13
+  {
14
+    state: () => ({
15
+      routes: [],
16
+      addRoutes: [],
17
+      defaultRoutes: [],
18
+      topbarRouters: [],
19
+      sidebarRouters: []
20
+    }),
21
+    actions: {
22
+      setRoutes(routes) {
23
+        this.addRoutes = routes
24
+        this.routes = constantRoutes.concat(routes)
25
+      },
26
+      setDefaultRoutes(routes) {
27
+        this.defaultRoutes = constantRoutes.concat(routes)
28
+      },
29
+      setTopbarRoutes(routes) {
30
+        this.topbarRouters = routes
31
+      },
32
+      setSidebarRouters(routes) {
33
+        this.sidebarRouters = routes
34
+      },
35
+      generateRoutes(roles) {
36
+        return new Promise(resolve => {
37
+          // 向后端请求路由数据
38
+          getRouters().then(res => {
39
+            const sdata = JSON.parse(JSON.stringify(res.data))
40
+            const rdata = JSON.parse(JSON.stringify(res.data))
41
+            const defaultData = JSON.parse(JSON.stringify(res.data))
42
+            const sidebarRoutes = filterAsyncRouter(sdata)
43
+            const rewriteRoutes = filterAsyncRouter(rdata, false, true)
44
+            const defaultRoutes = filterAsyncRouter(defaultData)
45
+            const asyncRoutes = filterDynamicRoutes(dynamicRoutes)
46
+            asyncRoutes.forEach(route => { router.addRoute(route) })
47
+            this.setRoutes(rewriteRoutes)
48
+            this.setSidebarRouters(constantRoutes.concat(sidebarRoutes))
49
+            this.setDefaultRoutes(sidebarRoutes)
50
+            this.setTopbarRoutes(defaultRoutes)
51
+            resolve(rewriteRoutes)
52
+          })
53
+        })
54
+      }
55
+    }
56
+  })
57
+
58
+// 遍历后台传来的路由字符串,转换为组件对象
59
+function filterAsyncRouter(asyncRouterMap, lastRouter = false, type = false, parentName) {
60
+  return asyncRouterMap.filter(route => {
61
+    if (type && route.children) {
62
+      route.children = filterChildren(route.children)
63
+    }
64
+    if (route.component) {
65
+      // Layout ParentView 组件特殊处理
66
+      if (route.component === 'Layout') {
67
+        route.component = Layout
68
+      } else if (route.component === 'ParentView') {
69
+        route.component = ParentView
70
+      } else if (route.component === 'InnerLink') {
71
+        route.component = InnerLink
72
+      } else {
73
+        route.component = loadView(route.component)
74
+      }
75
+    }
76
+    if (parentName) {
77
+      // 子页面name 拼接上父页面name 防止重复 现页面name与子页面path公用一个
78
+      route.name = parentName + route.name
79
+    }
80
+    if (route.children != null && route.children && route.children.length) {
81
+      route.children = filterAsyncRouter(route.children, route, type, route.name)
82
+    } else {
83
+      delete route['children']
84
+      delete route['redirect']
85
+    }
86
+    return true
87
+  })
88
+}
89
+
90
+function filterChildren(childrenMap, lastRouter = false) {
91
+  var children = []
92
+  childrenMap.forEach(el => {
93
+    el.path = lastRouter ? lastRouter.path + '/' + el.path : el.path
94
+    if (el.children && el.children.length && el.component === 'ParentView') {
95
+      children = children.concat(filterChildren(el.children, el))
96
+    } else {
97
+      children.push(el)
98
+    }
99
+  })
100
+  return children
101
+}
102
+
103
+// 动态路由遍历,验证是否具备权限
104
+export function filterDynamicRoutes(routes) {
105
+  const res = []
106
+  routes.forEach(route => {
107
+    if (route.permissions) {
108
+      if (auth.hasPermiOr(route.permissions)) {
109
+        res.push(route)
110
+      }
111
+    } else if (route.roles) {
112
+      if (auth.hasRoleOr(route.roles)) {
113
+        res.push(route)
114
+      }
115
+    }
116
+  })
117
+  return res
118
+}
119
+
120
+export const loadView = (view) => {
121
+  let res
122
+  for (const path in modules) {
123
+    const dir = path.split('views/')[1].split('.vue')[0]
124
+    if (dir === view) {
125
+      res = () => modules[path]()
126
+    }
127
+  }
128
+  return res
129
+}
130
+
131
+export default usePermissionStore

+ 51 - 0
src/store/modules/settings.js

@@ -0,0 +1,51 @@
1
+import defaultSettings from '@/settings'
2
+import { useDark, useToggle } from '@vueuse/core'
3
+import { useDynamicTitle } from '@/utils/dynamicTitle'
4
+
5
+const isDark = useDark()
6
+const toggleDark = useToggle(isDark)
7
+
8
+const { sideTheme, showSettings, topNav, tagsView, tagsIcon, fixedHeader, sidebarLogo, dynamicTitle, footerVisible, footerContent } = defaultSettings
9
+
10
+const storageSetting = JSON.parse(localStorage.getItem('layout-setting')) || ''
11
+
12
+const useSettingsStore = defineStore(
13
+  'settings',
14
+  {
15
+    state: () => ({
16
+      title: '',
17
+      theme: storageSetting.theme || '#409EFF',
18
+      sideTheme: storageSetting.sideTheme || sideTheme,
19
+      showSettings: showSettings,
20
+      topNav: storageSetting.topNav === undefined ? topNav : storageSetting.topNav,
21
+      tagsView: storageSetting.tagsView === undefined ? tagsView : storageSetting.tagsView,
22
+      tagsIcon: storageSetting.tagsIcon === undefined ? tagsIcon : storageSetting.tagsIcon,
23
+      fixedHeader: storageSetting.fixedHeader === undefined ? fixedHeader : storageSetting.fixedHeader,
24
+      sidebarLogo: storageSetting.sidebarLogo === undefined ? sidebarLogo : storageSetting.sidebarLogo,
25
+      dynamicTitle: storageSetting.dynamicTitle === undefined ? dynamicTitle : storageSetting.dynamicTitle,
26
+      footerVisible: storageSetting.footerVisible === undefined ? footerVisible : storageSetting.footerVisible,
27
+      footerContent: footerContent,
28
+      isDark: isDark.value
29
+    }),
30
+    actions: {
31
+      // 修改布局设置
32
+      changeSetting(data) {
33
+        const { key, value } = data
34
+        if (this.hasOwnProperty(key)) {
35
+          this[key] = value
36
+        }
37
+      },
38
+      // 设置网页标题
39
+      setTitle(title) {
40
+        this.title = title
41
+        useDynamicTitle()
42
+      },
43
+      // 切换暗黑模式
44
+      toggleTheme() {
45
+        this.isDark = !this.isDark
46
+        toggleDark()
47
+      }
48
+    }
49
+  })
50
+
51
+export default useSettingsStore

+ 182 - 0
src/store/modules/tagsView.js

@@ -0,0 +1,182 @@
1
+const useTagsViewStore = defineStore(
2
+  'tags-view',
3
+  {
4
+    state: () => ({
5
+      visitedViews: [],
6
+      cachedViews: [],
7
+      iframeViews: []
8
+    }),
9
+    actions: {
10
+      addView(view) {
11
+        this.addVisitedView(view)
12
+        this.addCachedView(view)
13
+      },
14
+      addIframeView(view) {
15
+        if (this.iframeViews.some(v => v.path === view.path)) return
16
+        this.iframeViews.push(
17
+          Object.assign({}, view, {
18
+            title: view.meta.title || 'no-name'
19
+          })
20
+        )
21
+      },
22
+      addVisitedView(view) {
23
+        if (this.visitedViews.some(v => v.path === view.path)) return
24
+        this.visitedViews.push(
25
+          Object.assign({}, view, {
26
+            title: view.meta.title || 'no-name'
27
+          })
28
+        )
29
+      },
30
+      addCachedView(view) {
31
+        if (this.cachedViews.includes(view.name)) return
32
+        if (!view.meta.noCache) {
33
+          this.cachedViews.push(view.name)
34
+        }
35
+      },
36
+      delView(view) {
37
+        return new Promise(resolve => {
38
+          this.delVisitedView(view)
39
+          this.delCachedView(view)
40
+          resolve({
41
+            visitedViews: [...this.visitedViews],
42
+            cachedViews: [...this.cachedViews]
43
+          })
44
+        })
45
+      },
46
+      delVisitedView(view) {
47
+        return new Promise(resolve => {
48
+          for (const [i, v] of this.visitedViews.entries()) {
49
+            if (v.path === view.path) {
50
+              this.visitedViews.splice(i, 1)
51
+              break
52
+            }
53
+          }
54
+          this.iframeViews = this.iframeViews.filter(item => item.path !== view.path)
55
+          resolve([...this.visitedViews])
56
+        })
57
+      },
58
+      delIframeView(view) {
59
+        return new Promise(resolve => {
60
+          this.iframeViews = this.iframeViews.filter(item => item.path !== view.path)
61
+          resolve([...this.iframeViews])
62
+        })
63
+      },
64
+      delCachedView(view) {
65
+        return new Promise(resolve => {
66
+          const index = this.cachedViews.indexOf(view.name)
67
+          index > -1 && this.cachedViews.splice(index, 1)
68
+          resolve([...this.cachedViews])
69
+        })
70
+      },
71
+      delOthersViews(view) {
72
+        return new Promise(resolve => {
73
+          this.delOthersVisitedViews(view)
74
+          this.delOthersCachedViews(view)
75
+          resolve({
76
+            visitedViews: [...this.visitedViews],
77
+            cachedViews: [...this.cachedViews]
78
+          })
79
+        })
80
+      },
81
+      delOthersVisitedViews(view) {
82
+        return new Promise(resolve => {
83
+          this.visitedViews = this.visitedViews.filter(v => {
84
+            return v.meta.affix || v.path === view.path
85
+          })
86
+          this.iframeViews = this.iframeViews.filter(item => item.path === view.path)
87
+          resolve([...this.visitedViews])
88
+        })
89
+      },
90
+      delOthersCachedViews(view) {
91
+        return new Promise(resolve => {
92
+          const index = this.cachedViews.indexOf(view.name)
93
+          if (index > -1) {
94
+            this.cachedViews = this.cachedViews.slice(index, index + 1)
95
+          } else {
96
+            this.cachedViews = []
97
+          }
98
+          resolve([...this.cachedViews])
99
+        })
100
+      },
101
+      delAllViews(view) {
102
+        return new Promise(resolve => {
103
+          this.delAllVisitedViews(view)
104
+          this.delAllCachedViews(view)
105
+          resolve({
106
+            visitedViews: [...this.visitedViews],
107
+            cachedViews: [...this.cachedViews]
108
+          })
109
+        })
110
+      },
111
+      delAllVisitedViews(view) {
112
+        return new Promise(resolve => {
113
+          const affixTags = this.visitedViews.filter(tag => tag.meta.affix)
114
+          this.visitedViews = affixTags
115
+          this.iframeViews = []
116
+          resolve([...this.visitedViews])
117
+        })
118
+      },
119
+      delAllCachedViews(view) {
120
+        return new Promise(resolve => {
121
+          this.cachedViews = []
122
+          resolve([...this.cachedViews])
123
+        })
124
+      },
125
+      updateVisitedView(view) {
126
+        for (let v of this.visitedViews) {
127
+          if (v.path === view.path) {
128
+            v = Object.assign(v, view)
129
+            break
130
+          }
131
+        }
132
+      },
133
+      delRightTags(view) {
134
+        return new Promise(resolve => {
135
+          const index = this.visitedViews.findIndex(v => v.path === view.path)
136
+          if (index === -1) {
137
+            return
138
+          }
139
+          this.visitedViews = this.visitedViews.filter((item, idx) => {
140
+            if (idx <= index || (item.meta && item.meta.affix)) {
141
+              return true
142
+            }
143
+            const i = this.cachedViews.indexOf(item.name)
144
+            if (i > -1) {
145
+              this.cachedViews.splice(i, 1)
146
+            }
147
+            if(item.meta.link) {
148
+              const fi = this.iframeViews.findIndex(v => v.path === item.path)
149
+              this.iframeViews.splice(fi, 1)
150
+            }
151
+            return false
152
+          })
153
+          resolve([...this.visitedViews])
154
+        })
155
+      },
156
+      delLeftTags(view) {
157
+        return new Promise(resolve => {
158
+          const index = this.visitedViews.findIndex(v => v.path === view.path)
159
+          if (index === -1) {
160
+            return
161
+          }
162
+          this.visitedViews = this.visitedViews.filter((item, idx) => {
163
+            if (idx >= index || (item.meta && item.meta.affix)) {
164
+              return true
165
+            }
166
+            const i = this.cachedViews.indexOf(item.name)
167
+            if (i > -1) {
168
+              this.cachedViews.splice(i, 1)
169
+            }
170
+            if(item.meta.link) {
171
+              const fi = this.iframeViews.findIndex(v => v.path === item.path)
172
+              this.iframeViews.splice(fi, 1)
173
+            }
174
+            return false
175
+          })
176
+          resolve([...this.visitedViews])
177
+        })
178
+      }
179
+    }
180
+  })
181
+
182
+export default useTagsViewStore

+ 91 - 0
src/store/modules/user.js

@@ -0,0 +1,91 @@
1
+import router from '@/router'
2
+import { ElMessageBox, } from 'element-plus'
3
+import { login, logout, getInfo } from '@/api/login'
4
+import { getToken, setToken, removeToken } from '@/utils/auth'
5
+import { isHttp, isEmpty } from "@/utils/validate"
6
+import defAva from '@/assets/images/profile.jpg'
7
+
8
+const useUserStore = defineStore(
9
+  'user',
10
+  {
11
+    state: () => ({
12
+      token: getToken(),
13
+      id: '',
14
+      name: '',
15
+      nickName: '',
16
+      avatar: '',
17
+      roles: [],
18
+      permissions: []
19
+    }),
20
+    actions: {
21
+      // 登录
22
+      login(userInfo) {
23
+        const username = userInfo.username.trim()
24
+        const password = userInfo.password
25
+        const code = userInfo.code
26
+        const uuid = userInfo.uuid
27
+        return new Promise((resolve, reject) => {
28
+          login(username, password, code, uuid).then(res => {
29
+            setToken(res.token)
30
+            this.token = res.token
31
+            resolve()
32
+          }).catch(error => {
33
+            reject(error)
34
+          })
35
+        })
36
+      },
37
+      // 获取用户信息
38
+      getInfo() {
39
+        return new Promise((resolve, reject) => {
40
+          getInfo().then(res => {
41
+            const user = res.user
42
+            let avatar = user.avatar || ""
43
+            if (!isHttp(avatar)) {
44
+              avatar = (isEmpty(avatar)) ? defAva : import.meta.env.VITE_APP_BASE_API + avatar
45
+            }
46
+            if (res.roles && res.roles.length > 0) { // 验证返回的roles是否是一个非空数组
47
+              this.roles = res.roles
48
+              this.permissions = res.permissions
49
+            } else {
50
+              this.roles = ['ROLE_DEFAULT']
51
+            }
52
+            this.id = user.userId
53
+            this.name = user.userName
54
+            this.nickName = user.nickName
55
+            this.avatar = avatar
56
+            /* 初始密码提示 */
57
+            if(res.isDefaultModifyPwd) {
58
+              ElMessageBox.confirm('您的密码还是初始密码,请修改密码!',  '安全提示', {  confirmButtonText: '确定',  cancelButtonText: '取消',  type: 'warning' }).then(() => {
59
+                router.push({ name: 'Profile', params: { activeTab: 'resetPwd' } })
60
+              }).catch(() => {})
61
+            }
62
+            /* 过期密码提示 */
63
+            if(!res.isDefaultModifyPwd && res.isPasswordExpired) {
64
+              ElMessageBox.confirm('您的密码已过期,请尽快修改密码!',  '安全提示', {  confirmButtonText: '确定',  cancelButtonText: '取消',  type: 'warning' }).then(() => {
65
+                router.push({ name: 'Profile', params: { activeTab: 'resetPwd' } })
66
+              }).catch(() => {})
67
+            }
68
+            resolve(res)
69
+          }).catch(error => {
70
+            reject(error)
71
+          })
72
+        })
73
+      },
74
+      // 退出系统
75
+      logOut() {
76
+        return new Promise((resolve, reject) => {
77
+          logout(this.token).then(() => {
78
+            this.token = ''
79
+            this.roles = []
80
+            this.permissions = []
81
+            removeToken()
82
+            resolve()
83
+          }).catch(error => {
84
+            reject(error)
85
+          })
86
+        })
87
+      }
88
+    }
89
+  })
90
+
91
+export default useUserStore