create-jnrs-template-vue 1.1.6 → 1.1.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/jnrs-template-vue/.env.development +4 -5
- package/jnrs-template-vue/.env.production +1 -1
- package/jnrs-template-vue/components.d.ts +4 -0
- package/jnrs-template-vue/package.json +1 -1
- package/jnrs-template-vue/public/system/menu.json +1 -2
- package/jnrs-template-vue/src/api/request.ts +4 -1
- package/jnrs-template-vue/src/api/system/index.ts +19 -15
- package/jnrs-template-vue/src/directives/permissions.ts +28 -0
- package/jnrs-template-vue/src/layout/SideMenu.vue +2 -4
- package/jnrs-template-vue/src/layout/TopHeader.vue +10 -6
- package/jnrs-template-vue/src/locales/index.ts +10 -3
- package/jnrs-template-vue/src/main.ts +3 -0
- package/jnrs-template-vue/src/router/index.ts +35 -6
- package/jnrs-template-vue/src/router/routes.ts +28 -28
- package/jnrs-template-vue/src/stores/mock.ts +4 -2
- package/jnrs-template-vue/src/utils/permissions.ts +16 -0
- package/jnrs-template-vue/src/views/common/403.vue +3 -7
- package/jnrs-template-vue/src/views/common/404.vue +3 -7
- package/jnrs-template-vue/src/views/home/index.vue +52 -18
- package/jnrs-template-vue/src/views/login/index.vue +27 -6
- package/jnrs-template-vue/src/views/system/dict/index.vue +13 -15
- package/jnrs-template-vue/src/views/system/menu/index.vue +22 -24
- package/jnrs-template-vue/src/views/system/mine/baseInfo.vue +2 -2
- package/jnrs-template-vue/src/views/system/mine/securitySettings.vue +2 -2
- package/jnrs-template-vue/src/views/system/role/index.vue +23 -9
- package/jnrs-template-vue/viteMockServe/index.ts +22 -6
- package/jnrs-template-vue/viteMockServe/{loginRes.json → loginRes_admin.json} +4 -4
- package/jnrs-template-vue/viteMockServe/loginRes_user.json +713 -0
- package/jnrs-template-vue/viteMockServe/roleRes.json +37 -0
- package/package.json +1 -1
- package/jnrs-template-vue/.env.example +0 -14
- package/jnrs-template-vue/src/types/index.d.ts +0 -6
|
@@ -3,11 +3,10 @@ ENV = 'development'
|
|
|
3
3
|
# 是否是 mock 模式
|
|
4
4
|
VITE_USE_MOCK = true
|
|
5
5
|
|
|
6
|
-
#
|
|
7
|
-
|
|
8
|
-
VITE_BASE_URL = '192.168.1.120:5001'
|
|
6
|
+
# 后端接口基地址 - 开发环境
|
|
7
|
+
VITE_BASE_URL = '192.168.1.120:6001'
|
|
9
8
|
|
|
10
|
-
#
|
|
9
|
+
# 应用运行主机(置空默认为 localhost)
|
|
11
10
|
VITE_APP_HOST = ''
|
|
12
11
|
# 应用运行端口
|
|
13
|
-
VITE_APP_PORT =
|
|
12
|
+
VITE_APP_PORT = 5788
|
|
@@ -17,6 +17,7 @@ declare module 'vue' {
|
|
|
17
17
|
ElButtonGroup: typeof import('element-plus/es')['ElButtonGroup']
|
|
18
18
|
ElCard: typeof import('element-plus/es')['ElCard']
|
|
19
19
|
ElCascader: typeof import('element-plus/es')['ElCascader']
|
|
20
|
+
ElCol: typeof import('element-plus/es')['ElCol']
|
|
20
21
|
ElColorPicker: typeof import('element-plus/es')['ElColorPicker']
|
|
21
22
|
ElContainer: typeof import('element-plus/es')['ElContainer']
|
|
22
23
|
ElDatePickerPanel: typeof import('element-plus/es')['ElDatePickerPanel']
|
|
@@ -32,6 +33,9 @@ declare module 'vue' {
|
|
|
32
33
|
ElMenuItem: typeof import('element-plus/es')['ElMenuItem']
|
|
33
34
|
ElOption: typeof import('element-plus/es')['ElOption']
|
|
34
35
|
ElPopover: typeof import('element-plus/es')['ElPopover']
|
|
36
|
+
ElRadio: typeof import('element-plus/es')['ElRadio']
|
|
37
|
+
ElRadioGroup: typeof import('element-plus/es')['ElRadioGroup']
|
|
38
|
+
ElRow: typeof import('element-plus/es')['ElRow']
|
|
35
39
|
ElSelect: typeof import('element-plus/es')['ElSelect']
|
|
36
40
|
ElSubMenu: typeof import('element-plus/es')['ElSubMenu']
|
|
37
41
|
ElSwitch: typeof import('element-plus/es')['ElSwitch']
|
|
@@ -10,6 +10,7 @@ import { ElMessage } from 'element-plus'
|
|
|
10
10
|
import { createAxiosInstance } from '@jnrs/core/axios'
|
|
11
11
|
import type { BusinessRequest, BusinessResponse } from '@jnrs/core/axios'
|
|
12
12
|
import { useMockStore, useAuthStore } from '@/stores'
|
|
13
|
+
import { handleRouter } from '@jnrs/vue-core/router'
|
|
13
14
|
|
|
14
15
|
const axiosInstance = createAxiosInstance({
|
|
15
16
|
handleMessageFn: ElMessage,
|
|
@@ -17,7 +18,9 @@ const axiosInstance = createAxiosInstance({
|
|
|
17
18
|
const { token } = useAuthStore()
|
|
18
19
|
return token
|
|
19
20
|
},
|
|
20
|
-
handleNoAuthFn: () => {
|
|
21
|
+
handleNoAuthFn: () => {
|
|
22
|
+
handleRouter({ name: 'Login' })
|
|
23
|
+
}
|
|
21
24
|
})
|
|
22
25
|
|
|
23
26
|
/**
|
|
@@ -1,19 +1,7 @@
|
|
|
1
1
|
import { request } from '../request'
|
|
2
|
-
import type { Dict, DictItem, User } from '@jnrs/shared'
|
|
2
|
+
import type { Dict, DictItem, User, Role } from '@jnrs/shared'
|
|
3
3
|
import type { MenuItem } from '@jnrs/vue-core'
|
|
4
4
|
|
|
5
|
-
// 登录结果
|
|
6
|
-
export interface LoginResult extends User {
|
|
7
|
-
token: string
|
|
8
|
-
dict: Dict
|
|
9
|
-
}
|
|
10
|
-
|
|
11
|
-
// 修改密码
|
|
12
|
-
interface PasswordChange {
|
|
13
|
-
userId: number
|
|
14
|
-
password: string
|
|
15
|
-
}
|
|
16
|
-
|
|
17
5
|
// 菜单
|
|
18
6
|
export const MenuApi = (): Promise<MenuItem[]> => {
|
|
19
7
|
return request({
|
|
@@ -23,12 +11,15 @@ export const MenuApi = (): Promise<MenuItem[]> => {
|
|
|
23
11
|
})
|
|
24
12
|
}
|
|
25
13
|
|
|
14
|
+
// 登录
|
|
26
15
|
interface LoginParams {
|
|
27
16
|
account: string
|
|
28
17
|
password: string
|
|
29
18
|
}
|
|
30
|
-
|
|
31
|
-
|
|
19
|
+
export interface LoginResult extends User {
|
|
20
|
+
token: string
|
|
21
|
+
dict: Dict
|
|
22
|
+
}
|
|
32
23
|
export const LoginApi = (data: LoginParams): Promise<LoginResult> => {
|
|
33
24
|
return request({
|
|
34
25
|
url: '/api/auth/login',
|
|
@@ -55,6 +46,10 @@ export const UserInfoApi = (): Promise<User> => {
|
|
|
55
46
|
}
|
|
56
47
|
|
|
57
48
|
// 修改密码
|
|
49
|
+
interface PasswordChange {
|
|
50
|
+
userId: number
|
|
51
|
+
password: string
|
|
52
|
+
}
|
|
58
53
|
export const PasswordChangeApi = (data: PasswordChange) => {
|
|
59
54
|
return request({
|
|
60
55
|
url: '/api/auth/change-password',
|
|
@@ -87,3 +82,12 @@ export const DictChangeApi = (data: DictItem) => {
|
|
|
87
82
|
data
|
|
88
83
|
})
|
|
89
84
|
}
|
|
85
|
+
|
|
86
|
+
// 获取角色列表
|
|
87
|
+
export const RoleApi = (): Promise<Role[]> => {
|
|
88
|
+
return request({
|
|
89
|
+
url: '/api/role-manager',
|
|
90
|
+
method: 'get',
|
|
91
|
+
noAuth: true
|
|
92
|
+
})
|
|
93
|
+
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import type { Directive } from 'vue'
|
|
2
|
+
import { hasPermission } from '@/utils/permissions'
|
|
3
|
+
|
|
4
|
+
export type PermissionsDirective = Directive<HTMLElement, string[]>
|
|
5
|
+
|
|
6
|
+
declare module 'vue' {
|
|
7
|
+
export interface ComponentCustomProperties {
|
|
8
|
+
vPermissions: PermissionsDirective
|
|
9
|
+
}
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export const permissions = {
|
|
13
|
+
mounted(el, binding) {
|
|
14
|
+
const { value, modifiers } = binding
|
|
15
|
+
const hasPerm = hasPermission(value)
|
|
16
|
+
|
|
17
|
+
if (!hasPerm) {
|
|
18
|
+
if (modifiers.display) {
|
|
19
|
+
el.style.display = 'none'
|
|
20
|
+
} else if (modifiers.disabled) {
|
|
21
|
+
el.setAttribute('disabled', 'disabled')
|
|
22
|
+
el.classList.add('disabled')
|
|
23
|
+
} else {
|
|
24
|
+
el.remove()
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
} satisfies PermissionsDirective
|
|
@@ -3,9 +3,7 @@ import SideMenuItem from './SideMenuItem.vue'
|
|
|
3
3
|
import { storeToRefs } from 'pinia'
|
|
4
4
|
import { useRoute } from '@jnrs/vue-core/router'
|
|
5
5
|
import { useSystemStore, useMenuStore } from '@/stores'
|
|
6
|
-
import { useI18n } from 'vue-i18n'
|
|
7
6
|
|
|
8
|
-
const { t } = useI18n()
|
|
9
7
|
const systemStore = useSystemStore()
|
|
10
8
|
const { menuCollapse } = storeToRefs(systemStore)
|
|
11
9
|
const { toggleCollapse } = systemStore
|
|
@@ -18,7 +16,7 @@ const route = useRoute()
|
|
|
18
16
|
<el-aside class="sideMenu">
|
|
19
17
|
<div class="logo" :class="{ logo_collapse: menuCollapse }">
|
|
20
18
|
<img class="logo_img" src="@/assets/images/common/jnrs-white.svg" alt="jnrs" />
|
|
21
|
-
<span class="logo_text">{{ t('main.title') }}</span>
|
|
19
|
+
<span class="logo_text">{{ $t('main.title') }}</span>
|
|
22
20
|
</div>
|
|
23
21
|
<el-icon
|
|
24
22
|
class="collapseBtn"
|
|
@@ -136,7 +134,7 @@ $mainFontColor: rgba(255, 255, 255, 0.8);
|
|
|
136
134
|
|
|
137
135
|
.leftSide_menu {
|
|
138
136
|
border: none;
|
|
139
|
-
min-width:
|
|
137
|
+
min-width: 200px;
|
|
140
138
|
--el-menu-text-color: $mainFontColor;
|
|
141
139
|
--el-menu-active-color: $mainFontColor;
|
|
142
140
|
--el-menu-bg-color: none;
|
|
@@ -4,13 +4,14 @@ import { ref, computed } from 'vue'
|
|
|
4
4
|
import { storeToRefs } from 'pinia'
|
|
5
5
|
import { ElMessageBox } from 'element-plus'
|
|
6
6
|
import { handleRouter } from '@jnrs/vue-core/router'
|
|
7
|
-
import { useSystemStore, useAuthStore } from '@/stores'
|
|
7
|
+
import { useSystemStore, useAuthStore, useMenuStore } from '@/stores'
|
|
8
8
|
import { useAvatar } from '@/composables/common/useAvatar'
|
|
9
9
|
import { LogoutApi } from '@/api/system'
|
|
10
10
|
import { getDictLabel, getDictColor } from '@/utils/common'
|
|
11
11
|
|
|
12
12
|
const { avatar } = useAvatar()
|
|
13
|
-
const { userInfo,
|
|
13
|
+
const { userInfo, clearAuth } = useAuthStore()
|
|
14
|
+
const { clearMenu } = useMenuStore()
|
|
14
15
|
|
|
15
16
|
const roleLabel = computed(() => getDictLabel('role', userInfo?.role || ''))
|
|
16
17
|
const roleColor = computed(() => getDictColor('role', userInfo?.role || ''))
|
|
@@ -28,7 +29,8 @@ const handleLogout = async () => {
|
|
|
28
29
|
type: 'warning'
|
|
29
30
|
})
|
|
30
31
|
await LogoutApi()
|
|
31
|
-
await
|
|
32
|
+
await clearAuth()
|
|
33
|
+
await clearMenu()
|
|
32
34
|
handleRouter({ name: 'Login' }, 'replace')
|
|
33
35
|
} catch {}
|
|
34
36
|
}
|
|
@@ -73,7 +75,7 @@ const showGlobalSetting = () => {
|
|
|
73
75
|
<div class="userMenu_dropdown">
|
|
74
76
|
<img class="userMenu_dropdown_avatar" :src="avatar" alt="avatar" />
|
|
75
77
|
<b>
|
|
76
|
-
{{ userInfo?.name }}
|
|
78
|
+
<span>{{ userInfo?.name }}</span>
|
|
77
79
|
<span class="userMenu_roleName" :style="{ color: roleColor }" v-if="userInfo?.role">[{{ roleLabel }}]</span>
|
|
78
80
|
</b>
|
|
79
81
|
<div class="loginDateTime" v-if="userInfo?.loginDateTime">
|
|
@@ -152,7 +154,9 @@ $topHoverSize: 35px;
|
|
|
152
154
|
transition: all 0.25s ease;
|
|
153
155
|
cursor: pointer;
|
|
154
156
|
&:hover {
|
|
155
|
-
|
|
157
|
+
span {
|
|
158
|
+
color: var(--jnrs-color-primary) !important;
|
|
159
|
+
}
|
|
156
160
|
.userMenu_icon {
|
|
157
161
|
color: var(--jnrs-color-primary);
|
|
158
162
|
}
|
|
@@ -188,7 +192,7 @@ $topHoverSize: 35px;
|
|
|
188
192
|
}
|
|
189
193
|
}
|
|
190
194
|
.userMenu_roleName {
|
|
191
|
-
|
|
195
|
+
margin-left: 4px;
|
|
192
196
|
color: var(--jnrs-color-primary);
|
|
193
197
|
}
|
|
194
198
|
}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { createI18n } from 'vue-i18n'
|
|
2
|
-
import
|
|
2
|
+
import zhCn from './zhCn'
|
|
3
3
|
import en from './en'
|
|
4
|
+
import { zhCn as vueCore_zhCn, en as vueCore_en } from '@jnrs/vue-core/locales'
|
|
4
5
|
|
|
5
6
|
const i18n = createI18n({
|
|
6
7
|
legacy: false, // 启用 Composition API 模式
|
|
@@ -8,8 +9,14 @@ const i18n = createI18n({
|
|
|
8
9
|
locale: 'zhCn',
|
|
9
10
|
fallbackLocale: 'en',
|
|
10
11
|
messages: {
|
|
11
|
-
zhCn:
|
|
12
|
-
|
|
12
|
+
zhCn: {
|
|
13
|
+
...zhCn,
|
|
14
|
+
...vueCore_zhCn
|
|
15
|
+
},
|
|
16
|
+
en: {
|
|
17
|
+
...en,
|
|
18
|
+
...vueCore_en
|
|
19
|
+
}
|
|
13
20
|
}
|
|
14
21
|
})
|
|
15
22
|
|
|
@@ -10,9 +10,12 @@ import * as ElementPlusIconsVue from '@element-plus/icons-vue'
|
|
|
10
10
|
import App from './App.vue'
|
|
11
11
|
import { router } from './router'
|
|
12
12
|
import locales from './locales/index'
|
|
13
|
+
import { permissions } from './directives/permissions'
|
|
13
14
|
|
|
14
15
|
const app = createApp(App)
|
|
15
16
|
|
|
17
|
+
app.directive('permissions', permissions)
|
|
18
|
+
|
|
16
19
|
// element-plus 自动引入所有图标
|
|
17
20
|
for (const [key, component] of Object.entries(ElementPlusIconsVue)) {
|
|
18
21
|
app.component(key, component)
|
|
@@ -1,12 +1,35 @@
|
|
|
1
1
|
import { LAYOUT_NAME, GLOBAL_COMPONENT, routes } from './routes'
|
|
2
2
|
import { createVueRouter } from '@jnrs/vue-core/router'
|
|
3
3
|
import type { FileModules, RouteLocationNormalizedGeneric } from '@jnrs/vue-core/router'
|
|
4
|
+
import type { MenuItem } from '@jnrs/vue-core'
|
|
4
5
|
import { useAuthStore, useMenuStore } from '@/stores'
|
|
5
6
|
import { MenuApi } from '@/api/system'
|
|
6
|
-
import {
|
|
7
|
+
import { hasMenuViewPermission } from '@/utils/permissions'
|
|
7
8
|
|
|
8
9
|
const fileModules = import.meta.glob('/src/views/**/*.vue') as FileModules
|
|
9
10
|
|
|
11
|
+
/**
|
|
12
|
+
* 递归过滤菜单树,仅保留当前用户有权限访问的节点
|
|
13
|
+
* TODO 如果是后端返回的数据就没必要判断了,后端会判断
|
|
14
|
+
*/
|
|
15
|
+
function filterMenuByPermission(menus: MenuItem[]): MenuItem[] {
|
|
16
|
+
return menus
|
|
17
|
+
.map((item) => {
|
|
18
|
+
const children = item.children ? filterMenuByPermission(item.children) : []
|
|
19
|
+
|
|
20
|
+
const hasSelfPermission = hasMenuViewPermission(item.meta?.permissions)
|
|
21
|
+
|
|
22
|
+
if (hasSelfPermission) {
|
|
23
|
+
return { ...item, ...(children.length > 0 ? { children } : {}) }
|
|
24
|
+
}
|
|
25
|
+
if (children.length > 0) {
|
|
26
|
+
return { ...item, children }
|
|
27
|
+
}
|
|
28
|
+
return null
|
|
29
|
+
})
|
|
30
|
+
.filter(Boolean) as MenuItem[]
|
|
31
|
+
}
|
|
32
|
+
|
|
10
33
|
const router = createVueRouter({
|
|
11
34
|
options: {
|
|
12
35
|
routes
|
|
@@ -19,22 +42,28 @@ const router = createVueRouter({
|
|
|
19
42
|
if (to.meta.noAuth) return true
|
|
20
43
|
|
|
21
44
|
// 页面刷新处理:未[身份验证]时,重定向到[身份验证]页
|
|
22
|
-
const { hasAuthenticated
|
|
45
|
+
const { hasAuthenticated } = useAuthStore()
|
|
23
46
|
if (!hasAuthenticated) {
|
|
24
47
|
return { name: 'Login', query: { redirect: to.path }, replace: true }
|
|
25
48
|
}
|
|
26
49
|
|
|
27
50
|
// 校验访问权限
|
|
28
|
-
if (to.meta.permissions && !
|
|
51
|
+
if (to.meta.permissions && !hasMenuViewPermission(to.meta.permissions)) {
|
|
29
52
|
return { name: '403' }
|
|
30
53
|
}
|
|
31
54
|
|
|
32
|
-
// 页面刷新处理:菜单未获取时,先获取菜单,再重定向当前路由,即重新开始进入handleBeforeEach
|
|
55
|
+
// 页面刷新处理:菜单未获取时,先获取菜单,再重定向当前路由,即重新开始进入 handleBeforeEach
|
|
33
56
|
const { hasFetchedAsyncMenus, asyncSetMenus } = useMenuStore()
|
|
34
57
|
if (!hasFetchedAsyncMenus) {
|
|
35
58
|
try {
|
|
36
|
-
const
|
|
37
|
-
|
|
59
|
+
const menuRes = await MenuApi()
|
|
60
|
+
/**
|
|
61
|
+
* 校验用户的菜单访问权限,进行菜单筛选
|
|
62
|
+
* TODO 如果是后端返回的数据就没必要校验筛选了,后端会筛选
|
|
63
|
+
*/
|
|
64
|
+
const perRes = filterMenuByPermission(menuRes)
|
|
65
|
+
|
|
66
|
+
await asyncSetMenus(perRes)
|
|
38
67
|
return to.fullPath
|
|
39
68
|
} catch (error) {
|
|
40
69
|
throw error
|
|
@@ -10,25 +10,37 @@ export const LAYOUT_NAME = 'Layout'
|
|
|
10
10
|
export const GLOBAL_COMPONENT = import('@/layout/BlankLayout.vue')
|
|
11
11
|
|
|
12
12
|
export const routes = [
|
|
13
|
-
{
|
|
14
|
-
path: '/login',
|
|
15
|
-
component: () => import('@/layout/BlankLayout.vue'),
|
|
16
|
-
meta: { title: '登录' },
|
|
17
|
-
children: [
|
|
18
|
-
{
|
|
19
|
-
path: '',
|
|
20
|
-
name: 'Login',
|
|
21
|
-
meta: { title: '登录', noAuth: true },
|
|
22
|
-
component: () => import('@/views/login/index.vue')
|
|
23
|
-
}
|
|
24
|
-
]
|
|
25
|
-
},
|
|
26
13
|
// {
|
|
27
|
-
// name: 'Login',
|
|
28
14
|
// path: '/login',
|
|
29
|
-
// component: () => import('@/
|
|
30
|
-
// meta: { title: '登录'
|
|
15
|
+
// component: () => import('@/layout/BlankLayout.vue'),
|
|
16
|
+
// meta: { title: '登录' },
|
|
17
|
+
// children: [
|
|
18
|
+
// {
|
|
19
|
+
// path: '',
|
|
20
|
+
// name: 'Login',
|
|
21
|
+
// meta: { title: '登录', noAuth: true },
|
|
22
|
+
// component: () => import('@/views/login/index.vue')
|
|
23
|
+
// }
|
|
24
|
+
// ]
|
|
31
25
|
// },
|
|
26
|
+
{
|
|
27
|
+
name: '403',
|
|
28
|
+
path: '/403',
|
|
29
|
+
component: () => import('@/views/common/403.vue'),
|
|
30
|
+
meta: { title: '403', noAuth: true, global: true }
|
|
31
|
+
},
|
|
32
|
+
{
|
|
33
|
+
name: '404',
|
|
34
|
+
path: '/:pathMatch(.*)*',
|
|
35
|
+
component: () => import('@/views/common/404.vue'),
|
|
36
|
+
meta: { title: '404', global: true }
|
|
37
|
+
},
|
|
38
|
+
{
|
|
39
|
+
name: 'Login',
|
|
40
|
+
path: '/login',
|
|
41
|
+
component: () => import('@/views/login/index.vue'),
|
|
42
|
+
meta: { title: '登录', noAuth: true, global: true }
|
|
43
|
+
},
|
|
32
44
|
{
|
|
33
45
|
name: LAYOUT_NAME,
|
|
34
46
|
path: '/',
|
|
@@ -45,17 +57,5 @@ export const routes = [
|
|
|
45
57
|
component: () => import('@/views/home/index.vue')
|
|
46
58
|
}
|
|
47
59
|
]
|
|
48
|
-
},
|
|
49
|
-
{
|
|
50
|
-
name: '403',
|
|
51
|
-
path: '/403',
|
|
52
|
-
component: () => import('@/views/common/403.vue'),
|
|
53
|
-
meta: { title: '403', noAuth: true, global: true }
|
|
54
|
-
},
|
|
55
|
-
{
|
|
56
|
-
name: '404',
|
|
57
|
-
path: '/:pathMatch(.*)*',
|
|
58
|
-
component: () => import('@/views/common/404.vue'),
|
|
59
|
-
meta: { title: '404', global: true }
|
|
60
60
|
}
|
|
61
61
|
]
|
|
@@ -1,13 +1,15 @@
|
|
|
1
1
|
import { defineStore } from 'pinia'
|
|
2
|
+
import { ref } from 'vue'
|
|
2
3
|
|
|
3
4
|
/**
|
|
4
5
|
* Mock仓库
|
|
5
6
|
* @returns isMock 是否使用mock数据
|
|
6
7
|
*/
|
|
7
8
|
export const useMockStore = defineStore(
|
|
8
|
-
'mock',
|
|
9
|
+
'jnrs-app-mock',
|
|
9
10
|
() => {
|
|
10
|
-
|
|
11
|
+
// 是否使用mock数据
|
|
12
|
+
const isMock = ref(import.meta.env.VITE_USE_MOCK === 'true')
|
|
11
13
|
|
|
12
14
|
return { isMock }
|
|
13
15
|
},
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { hasPermissionWithSuffix } from '@jnrs/shared'
|
|
2
|
+
import { useAuthStore } from '@/stores'
|
|
3
|
+
|
|
4
|
+
// 菜单查看权限
|
|
5
|
+
export function hasMenuViewPermission(requiredPerms: string[] | undefined) {
|
|
6
|
+
const { userInfo } = useAuthStore()
|
|
7
|
+
const userPerms = userInfo?.permissions || []
|
|
8
|
+
return hasPermissionWithSuffix(requiredPerms, userPerms, ':view', 'some')
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
// 按钮操作权限
|
|
12
|
+
export function hasPermission(requiredPerms: string[] | undefined) {
|
|
13
|
+
const { userInfo } = useAuthStore()
|
|
14
|
+
const userPerms = userInfo?.permissions || []
|
|
15
|
+
return hasPermissionWithSuffix(requiredPerms, userPerms)
|
|
16
|
+
}
|
|
@@ -1,11 +1,9 @@
|
|
|
1
1
|
<script setup lang="ts">
|
|
2
2
|
import { router } from '@/router'
|
|
3
|
-
import {
|
|
4
|
-
|
|
5
|
-
const route = useRoute()
|
|
3
|
+
import { handleRouter } from '@jnrs/vue-core/router'
|
|
6
4
|
|
|
7
5
|
const goLogin = () => {
|
|
8
|
-
handleRouter({ name: 'Login'
|
|
6
|
+
handleRouter({ name: 'Login' }, 'replace')
|
|
9
7
|
}
|
|
10
8
|
const goHome = () => {
|
|
11
9
|
handleRouter({ path: '/' }, 'replace')
|
|
@@ -22,9 +20,7 @@ const goBack = () => {
|
|
|
22
20
|
<h3>抱歉,您访问的资源权限受限</h3>
|
|
23
21
|
<div class="main_mid_btn">
|
|
24
22
|
<el-button @click="goLogin" style="margin-right: 20px">重新登录</el-button>
|
|
25
|
-
<el-button type="primary" plain @click="goHome" style="margin-right: 20px">
|
|
26
|
-
返回首页
|
|
27
|
-
</el-button>
|
|
23
|
+
<el-button type="primary" plain @click="goHome" style="margin-right: 20px">返回首页</el-button>
|
|
28
24
|
<el-button type="primary" @click="goBack">返回上一页</el-button>
|
|
29
25
|
</div>
|
|
30
26
|
</div>
|
|
@@ -1,11 +1,9 @@
|
|
|
1
1
|
<script setup lang="ts">
|
|
2
2
|
import { router } from '@/router'
|
|
3
|
-
import {
|
|
4
|
-
|
|
5
|
-
const route = useRoute()
|
|
3
|
+
import { handleRouter } from '@jnrs/vue-core/router'
|
|
6
4
|
|
|
7
5
|
const goLogin = () => {
|
|
8
|
-
handleRouter({ name: 'Login'
|
|
6
|
+
handleRouter({ name: 'Login' }, 'replace')
|
|
9
7
|
}
|
|
10
8
|
const goHome = () => {
|
|
11
9
|
handleRouter({ path: '/' }, 'replace')
|
|
@@ -22,9 +20,7 @@ const goBack = () => {
|
|
|
22
20
|
<h3>抱歉,您访问的页面不存在</h3>
|
|
23
21
|
<div class="main_mid_btn">
|
|
24
22
|
<el-button @click="goLogin" style="margin-right: 20px">重新登录</el-button>
|
|
25
|
-
<el-button type="primary" plain @click="goHome" style="margin-right: 20px">
|
|
26
|
-
返回首页
|
|
27
|
-
</el-button>
|
|
23
|
+
<el-button type="primary" plain @click="goHome" style="margin-right: 20px">返回首页</el-button>
|
|
28
24
|
<el-button type="primary" @click="goBack">返回上一页</el-button>
|
|
29
25
|
</div>
|
|
30
26
|
</div>
|
|
@@ -1,13 +1,18 @@
|
|
|
1
1
|
<script setup lang="ts">
|
|
2
2
|
import { ref, onMounted } from 'vue'
|
|
3
|
+
import { storeToRefs } from 'pinia'
|
|
3
4
|
import { ElMessage } from 'element-plus'
|
|
4
5
|
import { MenuApi, LoginApi, UserInfoApi } from '@/api/system'
|
|
5
6
|
import { NotFoundApi, NoAuth } from '@/api/mock'
|
|
6
7
|
import { handleRouter } from '@jnrs/vue-core/router'
|
|
7
8
|
import type { MenuItem } from '@jnrs/vue-core'
|
|
9
|
+
import { hasPermission } from '@/utils/permissions'
|
|
10
|
+
import { useMockStore } from '@/stores'
|
|
11
|
+
|
|
12
|
+
const { isMock } = storeToRefs(useMockStore())
|
|
8
13
|
|
|
9
14
|
const loginParams = ref({
|
|
10
|
-
account: '
|
|
15
|
+
account: 'user',
|
|
11
16
|
password: '123456'
|
|
12
17
|
})
|
|
13
18
|
|
|
@@ -15,9 +20,7 @@ const routeOptions = ref<MenuItem[]>([])
|
|
|
15
20
|
const currentRoute = ref('')
|
|
16
21
|
const datePicker = ref(new Date())
|
|
17
22
|
|
|
18
|
-
onMounted(() => {
|
|
19
|
-
handleInfoApi()
|
|
20
|
-
})
|
|
23
|
+
onMounted(() => {})
|
|
21
24
|
|
|
22
25
|
const handleInfoApi = async () => {
|
|
23
26
|
try {
|
|
@@ -32,7 +35,6 @@ const handleMenuApi = async () => {
|
|
|
32
35
|
try {
|
|
33
36
|
const res = await MenuApi()
|
|
34
37
|
routeOptions.value = res
|
|
35
|
-
console.log(res)
|
|
36
38
|
} catch (error) {
|
|
37
39
|
console.log(error)
|
|
38
40
|
}
|
|
@@ -71,29 +73,52 @@ const handleRouteChange = () => {
|
|
|
71
73
|
name: currentRoute.value
|
|
72
74
|
})
|
|
73
75
|
}
|
|
74
|
-
|
|
75
|
-
// import { MyButton, MyModal } from '../src' // 直接导入本地源码
|
|
76
|
-
// import { useUserStore } from '../src'
|
|
77
76
|
</script>
|
|
78
77
|
|
|
79
78
|
<template>
|
|
80
79
|
<div>
|
|
81
|
-
<
|
|
80
|
+
<h3>Playground - 功能测试</h3>
|
|
81
|
+
<p>权限测试(admin 账号拥有全部权限,请用非 admin 账号测试该功能)</p>
|
|
82
|
+
<ul>
|
|
83
|
+
<li>
|
|
84
|
+
<span>有权限:(期望:正常显示按钮)</span>
|
|
85
|
+
<el-button type="primary" size="small" v-permissions="['mine:view']">添加</el-button>
|
|
86
|
+
</li>
|
|
87
|
+
<li>
|
|
88
|
+
<span>无权限:(期望:按钮禁用 -> UI 组件:使用 UI 框架提供的禁用属性)</span>
|
|
89
|
+
<el-button type="primary" size="small" :disabled="!hasPermission(['test:del'])">删除</el-button>
|
|
90
|
+
</li>
|
|
91
|
+
<li>
|
|
92
|
+
<span>无权限:(期望:按钮禁用 -> 原生元素:添加 disabled 属性和类名)</span>
|
|
93
|
+
<button size="small" v-permissions.disabled="['test:del']">删除</button>
|
|
94
|
+
</li>
|
|
95
|
+
<li>
|
|
96
|
+
<span>无权限:(期望:按钮不可见 -> 添加样式 display:none)</span>
|
|
97
|
+
<button size="small" v-permissions.display="['test:del']">删除</button>
|
|
98
|
+
</li>
|
|
99
|
+
<li>
|
|
100
|
+
<span>无权限:(期望:按钮不可见 -> remove HTMLElement)</span>
|
|
101
|
+
<button size="small" v-permissions="['test:del']">删除</button>
|
|
102
|
+
</li>
|
|
103
|
+
</ul>
|
|
82
104
|
<p>网络请求测试</p>
|
|
105
|
+
<div>
|
|
106
|
+
<span>服务器类型:</span>
|
|
107
|
+
<el-switch v-model="isMock" active-text="Mock 服务器" inactive-text="后端服务器" />
|
|
108
|
+
</div>
|
|
83
109
|
<el-button-group>
|
|
84
|
-
<el-button type="primary" size="small" @click="handleMenuApi"
|
|
110
|
+
<el-button type="primary" size="small" @click="handleMenuApi">获取完整菜单数据</el-button>
|
|
111
|
+
<el-button type="primary" size="small" @click="handleInfoApi">获取用户数据</el-button>
|
|
85
112
|
<el-button type="primary" size="small" @click="handleNotFoundApi">404</el-button>
|
|
86
113
|
<el-button type="primary" size="small" @click="handleNoAuth()">暂无权限</el-button>
|
|
87
|
-
<el-button type="primary" size="small" @click="handleNoAuth(false)">
|
|
88
|
-
暂无权限(不显示Msg)
|
|
89
|
-
</el-button>
|
|
114
|
+
<el-button type="primary" size="small" @click="handleNoAuth(false)">暂无权限(不显示 Message)</el-button>
|
|
90
115
|
</el-button-group>
|
|
91
|
-
<el-input v-model="loginParams.account" size="small" style="width:
|
|
116
|
+
<el-input v-model="loginParams.account" size="small" style="width: 200px">
|
|
92
117
|
<template #append>
|
|
93
|
-
<el-button size="small" @click="handleLogin"
|
|
118
|
+
<el-button size="small" @click="handleLogin">验证登录接口</el-button>
|
|
94
119
|
</template>
|
|
95
120
|
</el-input>
|
|
96
|
-
<p
|
|
121
|
+
<p>完整路由测试(先点击按钮,获取完整菜单数据)</p>
|
|
97
122
|
<el-cascader
|
|
98
123
|
v-model="currentRoute"
|
|
99
124
|
:options="routeOptions"
|
|
@@ -111,8 +136,17 @@ const handleRouteChange = () => {
|
|
|
111
136
|
</el-cascader>
|
|
112
137
|
<p>Element 组件测试</p>
|
|
113
138
|
<el-date-picker-panel v-model="datePicker" />
|
|
114
|
-
<!-- <MyButton type="primary">测试按钮</MyButton>
|
|
115
|
-
<MyModal v-model="show">内容</MyModal> -->
|
|
116
139
|
</div>
|
|
117
140
|
<router-view></router-view>
|
|
118
141
|
</template>
|
|
142
|
+
|
|
143
|
+
<style scoped lang="scss">
|
|
144
|
+
h3 {
|
|
145
|
+
color: var(--jnrs-color-primary);
|
|
146
|
+
}
|
|
147
|
+
p {
|
|
148
|
+
margin: 24px 0 8px;
|
|
149
|
+
color: var(--jnrs-background-primary);
|
|
150
|
+
background: var(--jnrs-font-primary);
|
|
151
|
+
}
|
|
152
|
+
</style>
|