xto-fronted 0.1.2 → 0.1.3
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/.env.development +3 -0
- package/.env.production +3 -0
- package/bin/cli.js +104 -0
- package/index.html +13 -0
- package/package.json +16 -3
- package/public/vite.svg +10 -0
- package/src/App.vue +20 -0
- package/src/api/auth.ts +35 -0
- package/src/api/menu.ts +13 -0
- package/src/api/system.ts +65 -0
- package/src/api/user.ts +12 -0
- package/src/assets/styles/_dark.scss +407 -0
- package/src/assets/styles/_reset.scss +126 -0
- package/src/assets/styles/_root.scss +140 -0
- package/src/assets/styles/_transition.scss +119 -0
- package/src/assets/styles/_variables.scss +45 -0
- package/src/assets/styles/index.scss +187 -0
- package/src/components/Layout/Footer.vue +17 -0
- package/src/components/Layout/Header.vue +335 -0
- package/src/components/Layout/Sidebar.vue +213 -0
- package/src/components/Layout/Tabs.vue +20 -0
- package/src/components/Layout/index.vue +62 -0
- package/src/composables/index.ts +9 -0
- package/src/composables/useApp.ts +170 -0
- package/src/composables/useAuth.ts +70 -0
- package/src/composables/useForm.ts +79 -0
- package/src/composables/useMenu.ts +141 -0
- package/src/composables/useTable.ts +97 -0
- package/src/config/index.ts +19 -0
- package/src/directives/permission.ts +41 -0
- package/src/enums/index.ts +63 -0
- package/src/env.d.ts +17 -0
- package/src/index.ts +44 -0
- package/src/main.ts +34 -0
- package/src/router/dynamicRoutes.ts +163 -0
- package/src/router/index.ts +71 -0
- package/src/router/staticRoutes.ts +43 -0
- package/src/stores/app.ts +145 -0
- package/src/stores/auth.ts +45 -0
- package/src/stores/index.ts +15 -0
- package/src/stores/menu.ts +158 -0
- package/src/stores/user.ts +41 -0
- package/src/types/api.d.ts +103 -0
- package/src/types/global.d.ts +45 -0
- package/src/types/router.d.ts +48 -0
- package/src/types/xto.d.ts +149 -0
- package/src/utils/auth.ts +86 -0
- package/src/utils/permission.ts +30 -0
- package/src/utils/request.ts +126 -0
- package/src/utils/storage.ts +72 -0
- package/src/views/dashboard/index.vue +32 -0
- package/src/views/error/403.vue +57 -0
- package/src/views/error/404.vue +57 -0
- package/src/views/login/index.vue +141 -0
- package/src/views/system/menu/index.vue +32 -0
- package/src/views/system/role/index.vue +32 -0
- package/src/views/system/user/index.vue +32 -0
- package/tsconfig.json +26 -0
- package/tsconfig.node.json +11 -0
- package/vite.config.ts +139 -0
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 静态路由
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import type { RouteRecordRaw } from 'vue-router'
|
|
6
|
+
|
|
7
|
+
export const staticRoutes: RouteRecordRaw[] = [
|
|
8
|
+
{
|
|
9
|
+
path: '/login',
|
|
10
|
+
name: 'Login',
|
|
11
|
+
component: () => import('@/views/login/index.vue'),
|
|
12
|
+
meta: {
|
|
13
|
+
title: '登录',
|
|
14
|
+
hidden: true
|
|
15
|
+
}
|
|
16
|
+
},
|
|
17
|
+
{
|
|
18
|
+
path: '/404',
|
|
19
|
+
name: 'NotFound',
|
|
20
|
+
component: () => import('@/views/error/404.vue'),
|
|
21
|
+
meta: {
|
|
22
|
+
title: '404',
|
|
23
|
+
hidden: true
|
|
24
|
+
}
|
|
25
|
+
},
|
|
26
|
+
{
|
|
27
|
+
path: '/403',
|
|
28
|
+
name: 'Forbidden',
|
|
29
|
+
component: () => import('@/views/error/403.vue'),
|
|
30
|
+
meta: {
|
|
31
|
+
title: '403',
|
|
32
|
+
hidden: true
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
]
|
|
36
|
+
|
|
37
|
+
export const errorRoute: RouteRecordRaw = {
|
|
38
|
+
path: '/:pathMatch(.*)*',
|
|
39
|
+
redirect: '/404',
|
|
40
|
+
meta: {
|
|
41
|
+
hidden: true
|
|
42
|
+
}
|
|
43
|
+
}
|
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 应用状态
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { defineStore } from 'pinia'
|
|
6
|
+
import { ref, computed, watch } from 'vue'
|
|
7
|
+
import { local } from '@/utils/storage'
|
|
8
|
+
|
|
9
|
+
export type ThemeMode = 'light' | 'dark'
|
|
10
|
+
export type LayoutMode = 'sidebar' | 'top' | 'mix'
|
|
11
|
+
|
|
12
|
+
export const useAppStore = defineStore('app', () => {
|
|
13
|
+
// 状态
|
|
14
|
+
const isDark = ref<boolean>(local.get<boolean>('isDark') || false)
|
|
15
|
+
const theme = ref<ThemeMode>(local.get<ThemeMode>('theme') || 'light')
|
|
16
|
+
const layout = ref<LayoutMode>(local.get<LayoutMode>('layout') || 'sidebar')
|
|
17
|
+
const isCollapsed = ref<boolean>(local.get<boolean>('isCollapsed') || false)
|
|
18
|
+
const showTabs = ref<boolean>(local.get<boolean>('showTabs') ?? true)
|
|
19
|
+
const showFooter = ref<boolean>(local.get<boolean>('showFooter') ?? true)
|
|
20
|
+
const showBreadcrumb = ref<boolean>(local.get<boolean>('showBreadcrumb') ?? true)
|
|
21
|
+
const primaryColor = ref<string>(local.get<string>('primaryColor') || '#409eff')
|
|
22
|
+
const cachedViews = ref<string[]>([])
|
|
23
|
+
|
|
24
|
+
// 计算属性
|
|
25
|
+
const themeClass = computed(() => (isDark.value ? 'dark' : 'light'))
|
|
26
|
+
|
|
27
|
+
// 切换主题
|
|
28
|
+
const toggleTheme = () => {
|
|
29
|
+
isDark.value = !isDark.value
|
|
30
|
+
theme.value = isDark.value ? 'dark' : 'light'
|
|
31
|
+
updateTheme()
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
// 设置主题
|
|
35
|
+
const setTheme = (mode: ThemeMode) => {
|
|
36
|
+
theme.value = mode
|
|
37
|
+
isDark.value = mode === 'dark'
|
|
38
|
+
updateTheme()
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
// 更新主题
|
|
42
|
+
const updateTheme = () => {
|
|
43
|
+
const html = document.documentElement
|
|
44
|
+
if (isDark.value) {
|
|
45
|
+
html.classList.add('dark')
|
|
46
|
+
} else {
|
|
47
|
+
html.classList.remove('dark')
|
|
48
|
+
}
|
|
49
|
+
local.set('isDark', isDark.value)
|
|
50
|
+
local.set('theme', theme.value)
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
// 切换菜单折叠
|
|
54
|
+
const toggleCollapse = () => {
|
|
55
|
+
isCollapsed.value = !isCollapsed.value
|
|
56
|
+
local.set('isCollapsed', isCollapsed.value)
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
// 设置布局
|
|
60
|
+
const setLayout = (mode: LayoutMode) => {
|
|
61
|
+
layout.value = mode
|
|
62
|
+
local.set('layout', mode)
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
// 切换标签页
|
|
66
|
+
const toggleTabs = () => {
|
|
67
|
+
showTabs.value = !showTabs.value
|
|
68
|
+
local.set('showTabs', showTabs.value)
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
// 切换底部
|
|
72
|
+
const toggleFooter = () => {
|
|
73
|
+
showFooter.value = !showFooter.value
|
|
74
|
+
local.set('showFooter', showFooter.value)
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
// 切换面包屑
|
|
78
|
+
const toggleBreadcrumb = () => {
|
|
79
|
+
showBreadcrumb.value = !showBreadcrumb.value
|
|
80
|
+
local.set('showBreadcrumb', showBreadcrumb.value)
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
// 设置主题色
|
|
84
|
+
const setPrimaryColor = (color: string) => {
|
|
85
|
+
primaryColor.value = color
|
|
86
|
+
document.documentElement.style.setProperty('--color-primary', color)
|
|
87
|
+
local.set('primaryColor', color)
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
// 添加缓存页面
|
|
91
|
+
const addCachedView = (name: string) => {
|
|
92
|
+
if (!cachedViews.value.includes(name)) {
|
|
93
|
+
cachedViews.value.push(name)
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
// 移除缓存页面
|
|
98
|
+
const removeCachedView = (name: string) => {
|
|
99
|
+
const index = cachedViews.value.indexOf(name)
|
|
100
|
+
if (index > -1) {
|
|
101
|
+
cachedViews.value.splice(index, 1)
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
// 清除缓存页面
|
|
106
|
+
const clearCachedViews = () => {
|
|
107
|
+
cachedViews.value = []
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
// 初始化主题
|
|
111
|
+
const initTheme = () => {
|
|
112
|
+
updateTheme()
|
|
113
|
+
if (primaryColor.value !== '#409eff') {
|
|
114
|
+
document.documentElement.style.setProperty('--color-primary', primaryColor.value)
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
// 监听主题变化
|
|
119
|
+
watch(isDark, updateTheme)
|
|
120
|
+
|
|
121
|
+
return {
|
|
122
|
+
isDark,
|
|
123
|
+
theme,
|
|
124
|
+
layout,
|
|
125
|
+
isCollapsed,
|
|
126
|
+
showTabs,
|
|
127
|
+
showFooter,
|
|
128
|
+
showBreadcrumb,
|
|
129
|
+
primaryColor,
|
|
130
|
+
cachedViews,
|
|
131
|
+
themeClass,
|
|
132
|
+
toggleTheme,
|
|
133
|
+
toggleCollapse,
|
|
134
|
+
setTheme,
|
|
135
|
+
setLayout,
|
|
136
|
+
toggleTabs,
|
|
137
|
+
toggleFooter,
|
|
138
|
+
toggleBreadcrumb,
|
|
139
|
+
setPrimaryColor,
|
|
140
|
+
addCachedView,
|
|
141
|
+
removeCachedView,
|
|
142
|
+
clearCachedViews,
|
|
143
|
+
initTheme
|
|
144
|
+
}
|
|
145
|
+
})
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 认证状态
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { defineStore } from 'pinia'
|
|
6
|
+
import { ref, computed } from 'vue'
|
|
7
|
+
import { getLoginInfo, setLoginInfo, clearToken, hasToken } from '@/utils/auth'
|
|
8
|
+
import type { LoginInfo } from '@/utils/auth'
|
|
9
|
+
|
|
10
|
+
export const useAuthStore = defineStore('auth', () => {
|
|
11
|
+
// 状态
|
|
12
|
+
const loginInfo = ref<LoginInfo | null>(getLoginInfo())
|
|
13
|
+
const isLoggedIn = computed(() => hasToken())
|
|
14
|
+
|
|
15
|
+
// 设置登录信息
|
|
16
|
+
const login = (data: Record<string, unknown>) => {
|
|
17
|
+
setLoginInfo(data)
|
|
18
|
+
loginInfo.value = getLoginInfo()
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
// 登出
|
|
22
|
+
const logout = () => {
|
|
23
|
+
loginInfo.value = null
|
|
24
|
+
clearToken()
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
// 获取 access token
|
|
28
|
+
const accessToken = computed(() => loginInfo.value?.accessToken || null)
|
|
29
|
+
|
|
30
|
+
// 获取 token type
|
|
31
|
+
const tokenType = computed(() => loginInfo.value?.tokenType || 'Bearer')
|
|
32
|
+
|
|
33
|
+
// 获取 code
|
|
34
|
+
const code = computed(() => loginInfo.value?.code || null)
|
|
35
|
+
|
|
36
|
+
return {
|
|
37
|
+
loginInfo,
|
|
38
|
+
isLoggedIn,
|
|
39
|
+
accessToken,
|
|
40
|
+
tokenType,
|
|
41
|
+
code,
|
|
42
|
+
login,
|
|
43
|
+
logout
|
|
44
|
+
}
|
|
45
|
+
})
|
|
@@ -0,0 +1,158 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 菜单状态
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { defineStore } from 'pinia'
|
|
6
|
+
import { ref, computed } from 'vue'
|
|
7
|
+
import type { MenuItem, RemoteMenuItem } from '@/types/api'
|
|
8
|
+
import { local } from '@/utils/storage'
|
|
9
|
+
|
|
10
|
+
export const useMenuStore = defineStore('menu', () => {
|
|
11
|
+
// 存储 key
|
|
12
|
+
const MENU_LIST_KEY = 'menu_list'
|
|
13
|
+
const MENU_BTN_LIST_KEY = 'menu_btn_list'
|
|
14
|
+
|
|
15
|
+
// 状态
|
|
16
|
+
const menuList = ref<MenuItem[]>(local.get<MenuItem[]>(MENU_LIST_KEY) || [])
|
|
17
|
+
const menuBtnListMap = ref<Record<string, MenuItem[]>>(local.get<Record<string, MenuItem[]>>(MENU_BTN_LIST_KEY) || {})
|
|
18
|
+
|
|
19
|
+
// 首页菜单信息
|
|
20
|
+
const indexMenu: MenuItem = {
|
|
21
|
+
code: 'home',
|
|
22
|
+
name: '首页',
|
|
23
|
+
icon: 'home',
|
|
24
|
+
closable: false,
|
|
25
|
+
default: false,
|
|
26
|
+
out: false,
|
|
27
|
+
path: '/dashboard',
|
|
28
|
+
title: '首页'
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
// 计算属性
|
|
32
|
+
const hasMenu = computed(() => menuList.value.length > 0)
|
|
33
|
+
|
|
34
|
+
// 获取首页地址
|
|
35
|
+
const index = computed(() => {
|
|
36
|
+
function recursion(menuList: MenuItem[]): string | null {
|
|
37
|
+
if (menuList && menuList.length > 0) {
|
|
38
|
+
for (let i = 0; i < menuList.length; i++) {
|
|
39
|
+
const menu = menuList[i]
|
|
40
|
+
if (menu.default) {
|
|
41
|
+
return menu.path
|
|
42
|
+
} else {
|
|
43
|
+
const url = recursion(menu.children || [])
|
|
44
|
+
if (url) {
|
|
45
|
+
return url
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
return null
|
|
51
|
+
}
|
|
52
|
+
const indexUrl = recursion(menuList.value)
|
|
53
|
+
return indexUrl || indexMenu.path
|
|
54
|
+
})
|
|
55
|
+
|
|
56
|
+
// 解析后端菜单数据
|
|
57
|
+
const parseMenuData = (remoteMenuList: RemoteMenuItem[], parentMenuUrl?: string) => {
|
|
58
|
+
const localMenuList: MenuItem[] = []
|
|
59
|
+
|
|
60
|
+
if (!remoteMenuList || remoteMenuList.length <= 0) {
|
|
61
|
+
return localMenuList
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
remoteMenuList.forEach(remoteMenu => {
|
|
65
|
+
if (remoteMenu.type === 1) {
|
|
66
|
+
// 按钮权限
|
|
67
|
+
const btn: MenuItem = {
|
|
68
|
+
code: remoteMenu.menuCode,
|
|
69
|
+
name: remoteMenu.menuName,
|
|
70
|
+
path: '',
|
|
71
|
+
title: remoteMenu.menuName
|
|
72
|
+
}
|
|
73
|
+
const parentUrl = parentMenuUrl || ''
|
|
74
|
+
let btnList = menuBtnListMap.value[parentUrl] || []
|
|
75
|
+
btnList.push(btn)
|
|
76
|
+
menuBtnListMap.value[parentUrl] = btnList
|
|
77
|
+
} else {
|
|
78
|
+
// 菜单权限
|
|
79
|
+
const children: MenuItem[] = []
|
|
80
|
+
let menuPath = remoteMenu.menuUrl
|
|
81
|
+
|
|
82
|
+
// 处理外链
|
|
83
|
+
let isOut = remoteMenu.isOut || false
|
|
84
|
+
if (!isOut && remoteMenu.menuUrl) {
|
|
85
|
+
if (remoteMenu.menuUrl.startsWith('http')) {
|
|
86
|
+
menuPath = '/iframe/' + encodeURIComponent(menuPath)
|
|
87
|
+
} else if (remoteMenu.menuUrl.startsWith('keep-alive:')) {
|
|
88
|
+
menuPath = '/iframe/keep-alive/' + encodeURIComponent(menuPath.split('keep-alive:')[1])
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
const menu: MenuItem = {
|
|
93
|
+
code: remoteMenu.menuCode,
|
|
94
|
+
name: remoteMenu.menuName,
|
|
95
|
+
path: menuPath,
|
|
96
|
+
icon: remoteMenu.icon,
|
|
97
|
+
closable: remoteMenu.closable,
|
|
98
|
+
default: remoteMenu.isDefault,
|
|
99
|
+
out: isOut,
|
|
100
|
+
children: children,
|
|
101
|
+
title: remoteMenu.menuName
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
localMenuList.push(menu)
|
|
105
|
+
|
|
106
|
+
// 递归处理子菜单
|
|
107
|
+
if (remoteMenu.children && remoteMenu.children.length > 0) {
|
|
108
|
+
const childMenus = parseMenuData(remoteMenu.children, remoteMenu.menuUrl)
|
|
109
|
+
menu.children = childMenus
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
})
|
|
113
|
+
|
|
114
|
+
return localMenuList
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
// 设置菜单
|
|
118
|
+
const setMenuList = (menus: MenuItem[]) => {
|
|
119
|
+
menuList.value = menus
|
|
120
|
+
local.set(MENU_LIST_KEY, menus)
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
// 设置菜单(从后端数据)
|
|
124
|
+
const setMenuFromRemote = (remoteMenus: RemoteMenuItem[]) => {
|
|
125
|
+
// 清除旧数据
|
|
126
|
+
clearMenu()
|
|
127
|
+
|
|
128
|
+
// 添加首页
|
|
129
|
+
menuList.value.push(indexMenu)
|
|
130
|
+
|
|
131
|
+
// 解析后端菜单
|
|
132
|
+
const parsedMenus = parseMenuData(remoteMenus)
|
|
133
|
+
menuList.value.push(...parsedMenus)
|
|
134
|
+
|
|
135
|
+
// 保存到本地存储
|
|
136
|
+
local.set(MENU_LIST_KEY, menuList.value)
|
|
137
|
+
local.set(MENU_BTN_LIST_KEY, menuBtnListMap.value)
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
// 清除菜单
|
|
141
|
+
const clearMenu = () => {
|
|
142
|
+
menuList.value = []
|
|
143
|
+
menuBtnListMap.value = {}
|
|
144
|
+
local.remove(MENU_LIST_KEY)
|
|
145
|
+
local.remove(MENU_BTN_LIST_KEY)
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
return {
|
|
149
|
+
menuList,
|
|
150
|
+
menuBtnListMap,
|
|
151
|
+
hasMenu,
|
|
152
|
+
index,
|
|
153
|
+
indexMenu,
|
|
154
|
+
setMenuList,
|
|
155
|
+
setMenuFromRemote,
|
|
156
|
+
clearMenu
|
|
157
|
+
}
|
|
158
|
+
})
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 用户状态
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { defineStore } from 'pinia'
|
|
6
|
+
import { ref, computed } from 'vue'
|
|
7
|
+
import type { UserInfo } from '@/types/api'
|
|
8
|
+
import { local } from '@/utils/storage'
|
|
9
|
+
|
|
10
|
+
export const useUserStore = defineStore('user', () => {
|
|
11
|
+
// 状态
|
|
12
|
+
const userInfo = ref<UserInfo | null>(local.get<UserInfo>('user_info'))
|
|
13
|
+
|
|
14
|
+
// 计算属性
|
|
15
|
+
const isLoggedIn = computed(() => !!userInfo.value)
|
|
16
|
+
const userName = computed(() => userInfo.value?.userName || '')
|
|
17
|
+
const avatar = computed(() => userInfo.value?.avatar || '')
|
|
18
|
+
const userId = computed(() => userInfo.value?.userId)
|
|
19
|
+
|
|
20
|
+
// 设置用户信息
|
|
21
|
+
const setUserInfo = (info: UserInfo) => {
|
|
22
|
+
userInfo.value = info
|
|
23
|
+
local.set('user_info', info)
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
// 清除用户信息
|
|
27
|
+
const clearUserInfo = () => {
|
|
28
|
+
userInfo.value = null
|
|
29
|
+
local.remove('user_info')
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
return {
|
|
33
|
+
userInfo,
|
|
34
|
+
isLoggedIn,
|
|
35
|
+
userName,
|
|
36
|
+
avatar,
|
|
37
|
+
userId,
|
|
38
|
+
setUserInfo,
|
|
39
|
+
clearUserInfo
|
|
40
|
+
}
|
|
41
|
+
})
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* API 通用类型定义
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
// 基础响应
|
|
6
|
+
export interface ApiResponse<T = unknown> {
|
|
7
|
+
code: number
|
|
8
|
+
data: T
|
|
9
|
+
message: string
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
// 分页请求参数
|
|
13
|
+
export interface PageParams {
|
|
14
|
+
page: number
|
|
15
|
+
pageSize: number
|
|
16
|
+
[key: string]: unknown
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
// 分页响应
|
|
20
|
+
export interface PageResponse<T> {
|
|
21
|
+
list: T[]
|
|
22
|
+
total: number
|
|
23
|
+
page: number
|
|
24
|
+
pageSize: number
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
// 用户信息(来自后端)
|
|
28
|
+
export interface UserInfo {
|
|
29
|
+
uId: string
|
|
30
|
+
appId: string
|
|
31
|
+
userId: string
|
|
32
|
+
userName: string
|
|
33
|
+
departmentName?: string
|
|
34
|
+
email?: string
|
|
35
|
+
mobilePhone?: string
|
|
36
|
+
positionName?: string
|
|
37
|
+
avatar?: string
|
|
38
|
+
workNo?: string
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
// 登录请求
|
|
42
|
+
export interface LoginParams {
|
|
43
|
+
uid: string
|
|
44
|
+
password: string
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
// 登录响应
|
|
48
|
+
export interface LoginResult {
|
|
49
|
+
access_token: string
|
|
50
|
+
refresh_token: string
|
|
51
|
+
expires_time: string
|
|
52
|
+
refresh_time: string
|
|
53
|
+
token_type: string
|
|
54
|
+
code: string
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
// 后端菜单项
|
|
58
|
+
export interface RemoteMenuItem {
|
|
59
|
+
menuCode: string
|
|
60
|
+
menuName: string
|
|
61
|
+
menuUrl: string
|
|
62
|
+
icon?: string
|
|
63
|
+
closable?: boolean
|
|
64
|
+
isDefault?: boolean
|
|
65
|
+
isOut?: boolean
|
|
66
|
+
type: number // 0: 菜单, 1: 按钮
|
|
67
|
+
children?: RemoteMenuItem[]
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
// 前端菜单项
|
|
71
|
+
export interface MenuItem {
|
|
72
|
+
code: string
|
|
73
|
+
name: string
|
|
74
|
+
path: string
|
|
75
|
+
component?: string
|
|
76
|
+
redirect?: string
|
|
77
|
+
icon?: string
|
|
78
|
+
title: string
|
|
79
|
+
hidden?: boolean
|
|
80
|
+
keepAlive?: boolean
|
|
81
|
+
affix?: boolean
|
|
82
|
+
default?: boolean
|
|
83
|
+
out?: boolean
|
|
84
|
+
closable?: boolean
|
|
85
|
+
children?: MenuItem[]
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
// 按钮权限
|
|
89
|
+
export interface ButtonPermission {
|
|
90
|
+
code: string
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
// 角色信息
|
|
94
|
+
export interface RoleInfo {
|
|
95
|
+
id: number | string
|
|
96
|
+
name: string
|
|
97
|
+
code: string
|
|
98
|
+
description?: string
|
|
99
|
+
status: number
|
|
100
|
+
permissions: string[]
|
|
101
|
+
createTime?: string
|
|
102
|
+
updateTime?: string
|
|
103
|
+
}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 全局类型定义
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
// 通用对象类型
|
|
6
|
+
export type AnyObject = Record<string, unknown>
|
|
7
|
+
|
|
8
|
+
// 通用函数类型
|
|
9
|
+
export type AnyFunction = (...args: unknown[]) => unknown
|
|
10
|
+
|
|
11
|
+
// 值类型
|
|
12
|
+
export type ValueOf<T> = T[keyof T]
|
|
13
|
+
|
|
14
|
+
// 可空类型
|
|
15
|
+
export type Nullable<T> = T | null
|
|
16
|
+
|
|
17
|
+
// 可选类型
|
|
18
|
+
export type Optional<T> = T | undefined
|
|
19
|
+
|
|
20
|
+
// 深度可选
|
|
21
|
+
export type DeepPartial<T> = {
|
|
22
|
+
[P in keyof T]?: T[P] extends object ? DeepPartial<T[P]> : T[P]
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
// 深度必需
|
|
26
|
+
export type DeepRequired<T> = {
|
|
27
|
+
[P in keyof T]-?: T[P] extends object ? DeepRequired<T[P]> : T[P]
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
// 深度只读
|
|
31
|
+
export type DeepReadonly<T> = {
|
|
32
|
+
readonly [P in keyof T]: T[P] extends object ? DeepReadonly<T[P]> : T[P]
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
// 提取数组元素类型
|
|
36
|
+
export type ArrayElement<T> = T extends readonly (infer E)[] ? E : never
|
|
37
|
+
|
|
38
|
+
// 提取 Promise 值类型
|
|
39
|
+
export type Awaited<T> = T extends Promise<infer U> ? U : T
|
|
40
|
+
|
|
41
|
+
// 排除 null 和 undefined
|
|
42
|
+
export type NonNullable<T> = T extends null | undefined ? never : T
|
|
43
|
+
|
|
44
|
+
// 枚举值类型
|
|
45
|
+
export type EnumValue<T extends Record<string, string | number>> = T[keyof T]
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 路由类型定义
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import 'vue-router'
|
|
6
|
+
|
|
7
|
+
declare module 'vue-router' {
|
|
8
|
+
interface RouteMeta {
|
|
9
|
+
// 页面标题
|
|
10
|
+
title?: string
|
|
11
|
+
// 图标
|
|
12
|
+
icon?: string
|
|
13
|
+
// 是否隐藏菜单
|
|
14
|
+
hidden?: boolean
|
|
15
|
+
// 是否缓存页面
|
|
16
|
+
keepAlive?: boolean
|
|
17
|
+
// 是否固定在 tabs 中
|
|
18
|
+
affix?: boolean
|
|
19
|
+
// 权限标识
|
|
20
|
+
permissions?: string[]
|
|
21
|
+
// 角色标识
|
|
22
|
+
roles?: string[]
|
|
23
|
+
// 面包屑
|
|
24
|
+
breadcrumb?: boolean
|
|
25
|
+
// 当前激活菜单
|
|
26
|
+
activeMenu?: string
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
// 路由项
|
|
31
|
+
export interface AppRoute {
|
|
32
|
+
path: string
|
|
33
|
+
name?: string
|
|
34
|
+
redirect?: string
|
|
35
|
+
component?: () => Promise<unknown>
|
|
36
|
+
meta?: RouteMeta
|
|
37
|
+
children?: AppRoute[]
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
// 动态路由
|
|
41
|
+
export interface DynamicRoute {
|
|
42
|
+
path: string
|
|
43
|
+
name: string
|
|
44
|
+
component: string
|
|
45
|
+
redirect?: string
|
|
46
|
+
meta?: RouteMeta
|
|
47
|
+
children?: DynamicRoute[]
|
|
48
|
+
}
|