xto-fronted 0.1.2 → 0.1.4
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 +103 -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,149 @@
|
|
|
1
|
+
declare module '@xto/core' {
|
|
2
|
+
export const version: string
|
|
3
|
+
export function install(app: any): void
|
|
4
|
+
}
|
|
5
|
+
|
|
6
|
+
declare module '@xto/core/es/index/index.mjs' {
|
|
7
|
+
export * from '@xto/core'
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
declare module '@xto/core/es/theme/index.mjs' {
|
|
11
|
+
export const theme: any
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
declare module '@xto/core/es/hooks/index.mjs' {
|
|
15
|
+
export const hooks: any
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
declare module '@xto/core/es/utils/index.mjs' {
|
|
19
|
+
export const utils: any
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
declare module '@xto/base' {
|
|
23
|
+
import { DefineComponent } from 'vue'
|
|
24
|
+
export const Button: DefineComponent<any, any, any>
|
|
25
|
+
export const Loading: DefineComponent<any, any, any>
|
|
26
|
+
export const Icon: DefineComponent<any, any, any>
|
|
27
|
+
export const Avatar: DefineComponent<any, any, any>
|
|
28
|
+
export const Badge: DefineComponent<any, any, any>
|
|
29
|
+
export const Tag: DefineComponent<any, any, any>
|
|
30
|
+
export const Divider: DefineComponent<any, any, any>
|
|
31
|
+
export const Space: DefineComponent<any, any, any>
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
declare module '@xto/base/es/style.css' {
|
|
35
|
+
const content: string
|
|
36
|
+
export default content
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
declare module '@xto/form' {
|
|
40
|
+
import { DefineComponent } from 'vue'
|
|
41
|
+
export const Input: DefineComponent<any, any, any>
|
|
42
|
+
export const InputNumber: DefineComponent<any, any, any>
|
|
43
|
+
export const Select: DefineComponent<any, any, any>
|
|
44
|
+
export const Checkbox: DefineComponent<any, any, any>
|
|
45
|
+
export const Radio: DefineComponent<any, any, any>
|
|
46
|
+
export const Switch: DefineComponent<any, any, any>
|
|
47
|
+
export const DatePicker: DefineComponent<any, any, any>
|
|
48
|
+
export const Form: DefineComponent<any, any, any>
|
|
49
|
+
export const FormItem: DefineComponent<any, any, any>
|
|
50
|
+
export const Upload: DefineComponent<any, any, any>
|
|
51
|
+
export const FormRule: any
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
declare module '@xto/form/es/style.css' {
|
|
55
|
+
const content: string
|
|
56
|
+
export default content
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
declare module '@xto/data' {
|
|
60
|
+
import { DefineComponent } from 'vue'
|
|
61
|
+
export const Table: DefineComponent<any, any, any>
|
|
62
|
+
export const Pagination: DefineComponent<any, any, any>
|
|
63
|
+
export const Tree: DefineComponent<any, any, any>
|
|
64
|
+
export const TreeNode: DefineComponent<any, any, any>
|
|
65
|
+
export const List: DefineComponent<any, any, any>
|
|
66
|
+
export const Card: DefineComponent<any, any, any>
|
|
67
|
+
export const Descriptions: DefineComponent<any, any, any>
|
|
68
|
+
export const Tag: DefineComponent<any, any, any>
|
|
69
|
+
export const Progress: DefineComponent<any, any, any>
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
declare module '@xto/data/es/style.css' {
|
|
73
|
+
const content: string
|
|
74
|
+
export default content
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
declare module '@xto/feedback' {
|
|
78
|
+
import { DefineComponent } from 'vue'
|
|
79
|
+
export const Message: {
|
|
80
|
+
success: (msg: string) => void
|
|
81
|
+
error: (msg: string) => void
|
|
82
|
+
warning: (msg: string) => void
|
|
83
|
+
info: (msg: string) => void
|
|
84
|
+
}
|
|
85
|
+
export const Notification: {
|
|
86
|
+
success: (options: any) => void
|
|
87
|
+
error: (options: any) => void
|
|
88
|
+
warning: (options: any) => void
|
|
89
|
+
info: (options: any) => void
|
|
90
|
+
}
|
|
91
|
+
export const Modal: DefineComponent<any, any, any>
|
|
92
|
+
export const Drawer: DefineComponent<any, any, any>
|
|
93
|
+
export const Popconfirm: DefineComponent<any, any, any>
|
|
94
|
+
export const Tooltip: DefineComponent<any, any, any>
|
|
95
|
+
export const Popover: DefineComponent<any, any, any>
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
declare module '@xto/feedback/es/style.css' {
|
|
99
|
+
const content: string
|
|
100
|
+
export default content
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
declare module '@xto/navigation' {
|
|
104
|
+
import { DefineComponent } from 'vue'
|
|
105
|
+
export const Menu: DefineComponent<any, any, any>
|
|
106
|
+
export const MenuItem: DefineComponent<any, any, any>
|
|
107
|
+
export const SubMenu: DefineComponent<any, any, any>
|
|
108
|
+
export const Tabs: DefineComponent<any, any, any>
|
|
109
|
+
export const TabPane: DefineComponent<any, any, any>
|
|
110
|
+
export const Breadcrumb: DefineComponent<any, any, any>
|
|
111
|
+
export const BreadcrumbItem: DefineComponent<any, any, any>
|
|
112
|
+
export const Dropdown: DefineComponent<any, any, any>
|
|
113
|
+
export const DropdownMenu: DefineComponent<any, any, any>
|
|
114
|
+
export const DropdownItem: DefineComponent<any, any, any>
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
declare module '@xto/navigation/es/style.css' {
|
|
118
|
+
const content: string
|
|
119
|
+
export default content
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
declare module '@xto/layout' {
|
|
123
|
+
import { DefineComponent } from 'vue'
|
|
124
|
+
export const Layout: DefineComponent<any, any, any>
|
|
125
|
+
export const Header: DefineComponent<any, any, any>
|
|
126
|
+
export const Sider: DefineComponent<any, any, any>
|
|
127
|
+
export const Content: DefineComponent<any, any, any>
|
|
128
|
+
export const Footer: DefineComponent<any, any, any>
|
|
129
|
+
export const Container: DefineComponent<any, any, any>
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
declare module '@xto/layout/es/style.css' {
|
|
133
|
+
const content: string
|
|
134
|
+
export default content
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
declare module '@xto/business' {
|
|
138
|
+
import { DefineComponent } from 'vue'
|
|
139
|
+
export const LoginForm: DefineComponent<any, any, any>
|
|
140
|
+
export const RegisterForm: DefineComponent<any, any, any>
|
|
141
|
+
export const SearchResult: DefineComponent<any, any, any>
|
|
142
|
+
export const Comment: DefineComponent<any, any, any>
|
|
143
|
+
export const Statistic: DefineComponent<any, any, any>
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
declare module '@xto/business/es/style.css' {
|
|
147
|
+
const content: string
|
|
148
|
+
export default content
|
|
149
|
+
}
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Token 管理
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { local } from './storage'
|
|
6
|
+
|
|
7
|
+
const TOKEN_KEY = 'login_info'
|
|
8
|
+
const USER_INFO_KEY = 'user_info'
|
|
9
|
+
|
|
10
|
+
export interface LoginInfo {
|
|
11
|
+
accessToken: string
|
|
12
|
+
refreshToken: string
|
|
13
|
+
expiresTime: string
|
|
14
|
+
refreshTime: string
|
|
15
|
+
tokenType: string
|
|
16
|
+
code: string
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export interface TokenInfo {
|
|
20
|
+
token: string
|
|
21
|
+
refreshToken: string
|
|
22
|
+
expireTime: number
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
// 获取登录信息
|
|
26
|
+
export const getLoginInfo = (): LoginInfo | null => {
|
|
27
|
+
return local.get<LoginInfo>(TOKEN_KEY)
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
// 设置登录信息
|
|
31
|
+
export const setLoginInfo = (data: Record<string, unknown>): void => {
|
|
32
|
+
const loginInfo: LoginInfo = {
|
|
33
|
+
accessToken: data['access_token'] as string,
|
|
34
|
+
refreshToken: data['refresh_token'] as string,
|
|
35
|
+
expiresTime: data['expires_time'] as string,
|
|
36
|
+
refreshTime: data['refresh_time'] as string,
|
|
37
|
+
tokenType: data['token_type'] as string,
|
|
38
|
+
code: data['code'] as string
|
|
39
|
+
}
|
|
40
|
+
local.set(TOKEN_KEY, loginInfo)
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
// 获取 access token
|
|
44
|
+
export const getToken = (): string | null => {
|
|
45
|
+
const loginInfo = getLoginInfo()
|
|
46
|
+
return loginInfo?.accessToken || null
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
// 获取 token type
|
|
50
|
+
export const getTokenType = (): string => {
|
|
51
|
+
const loginInfo = getLoginInfo()
|
|
52
|
+
return loginInfo?.tokenType || 'Bearer'
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
// 获取 code
|
|
56
|
+
export const getCode = (): string | null => {
|
|
57
|
+
const loginInfo = getLoginInfo()
|
|
58
|
+
return loginInfo?.code || null
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
// 清除登录信息
|
|
62
|
+
export const clearToken = (): void => {
|
|
63
|
+
local.remove(TOKEN_KEY)
|
|
64
|
+
local.remove(USER_INFO_KEY)
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
// 检查是否有 token
|
|
68
|
+
export const hasToken = (): boolean => {
|
|
69
|
+
const token = getToken()
|
|
70
|
+
return !!token
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
// 设置用户信息
|
|
74
|
+
export const setUserInfo = (userInfo: Record<string, unknown>): void => {
|
|
75
|
+
local.set(USER_INFO_KEY, userInfo)
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
// 获取用户信息
|
|
79
|
+
export const getUserInfo = (): Record<string, unknown> | null => {
|
|
80
|
+
return local.get<Record<string, unknown>>(USER_INFO_KEY)
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
// 清除用户信息
|
|
84
|
+
export const clearUserInfo = (): void => {
|
|
85
|
+
local.remove(USER_INFO_KEY)
|
|
86
|
+
}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 权限工具函数
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { useMenuStore } from '@/stores/menu'
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* 检查是否有权限(基于菜单按钮权限)
|
|
9
|
+
* @param permission 权限标识
|
|
10
|
+
*/
|
|
11
|
+
export function hasPermission(permission: string | string[]): boolean {
|
|
12
|
+
const menuStore = useMenuStore()
|
|
13
|
+
const currentPath = window.location.pathname
|
|
14
|
+
const btnList = menuStore.menuBtnListMap[currentPath] || []
|
|
15
|
+
const btnCodes = btnList.map(item => item.code)
|
|
16
|
+
|
|
17
|
+
if (Array.isArray(permission)) {
|
|
18
|
+
return permission.some(p => btnCodes.includes(p))
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
return btnCodes.includes(permission)
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* 检查是否是管理员
|
|
26
|
+
*/
|
|
27
|
+
export function isAdmin(): boolean {
|
|
28
|
+
// 暂时返回 true,后续可根据实际需求实现
|
|
29
|
+
return true
|
|
30
|
+
}
|
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Axios 请求封装
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import axios, { AxiosInstance, AxiosRequestConfig, AxiosResponse, InternalAxiosRequestConfig } from 'axios'
|
|
6
|
+
import { getToken, getTokenType, clearToken } from './auth'
|
|
7
|
+
import { Message } from '@xto/feedback'
|
|
8
|
+
import config from '@/config'
|
|
9
|
+
|
|
10
|
+
// 响应数据接口
|
|
11
|
+
export interface ApiResponse<T = unknown> {
|
|
12
|
+
code: number
|
|
13
|
+
data: T
|
|
14
|
+
message: string
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
// 分页参数
|
|
18
|
+
export interface PageParams {
|
|
19
|
+
page: number
|
|
20
|
+
pageSize: number
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
// 分页响应
|
|
24
|
+
export interface PageResponse<T> {
|
|
25
|
+
list: T[]
|
|
26
|
+
total: number
|
|
27
|
+
page: number
|
|
28
|
+
pageSize: number
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
// 创建 axios 实例
|
|
32
|
+
const createRequest = (): AxiosInstance => {
|
|
33
|
+
const instance = axios.create({
|
|
34
|
+
baseURL: config.baseUrl,
|
|
35
|
+
timeout: 15000,
|
|
36
|
+
headers: {
|
|
37
|
+
'Content-Type': 'application/json'
|
|
38
|
+
}
|
|
39
|
+
})
|
|
40
|
+
|
|
41
|
+
// 请求拦截器
|
|
42
|
+
instance.interceptors.request.use(
|
|
43
|
+
(config: InternalAxiosRequestConfig) => {
|
|
44
|
+
const token = getToken()
|
|
45
|
+
if (token) {
|
|
46
|
+
const tokenType = getTokenType()
|
|
47
|
+
config.headers.Authorization = `${tokenType} ${token}`
|
|
48
|
+
}
|
|
49
|
+
return config
|
|
50
|
+
},
|
|
51
|
+
(error) => {
|
|
52
|
+
return Promise.reject(error)
|
|
53
|
+
}
|
|
54
|
+
)
|
|
55
|
+
|
|
56
|
+
// 响应拦截器
|
|
57
|
+
instance.interceptors.response.use(
|
|
58
|
+
(response: AxiosResponse<ApiResponse>) => {
|
|
59
|
+
const { data } = response
|
|
60
|
+
|
|
61
|
+
// 成功
|
|
62
|
+
if (data.code === 200 || data.code === 0) {
|
|
63
|
+
return response
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
// 业务错误
|
|
67
|
+
Message.error(data.message || '请求失败')
|
|
68
|
+
return Promise.reject(new Error(data.message || '请求失败'))
|
|
69
|
+
},
|
|
70
|
+
(error) => {
|
|
71
|
+
const { response } = error
|
|
72
|
+
|
|
73
|
+
if (response) {
|
|
74
|
+
switch (response.status) {
|
|
75
|
+
case 401:
|
|
76
|
+
case 403:
|
|
77
|
+
Message.error('登录已过期,请重新登录')
|
|
78
|
+
clearToken()
|
|
79
|
+
window.location.href = '/login'
|
|
80
|
+
break
|
|
81
|
+
case 404:
|
|
82
|
+
Message.error('请求资源不存在')
|
|
83
|
+
break
|
|
84
|
+
case 500:
|
|
85
|
+
Message.error('服务器错误')
|
|
86
|
+
break
|
|
87
|
+
default:
|
|
88
|
+
Message.error(response.data?.message || '请求失败')
|
|
89
|
+
}
|
|
90
|
+
} else {
|
|
91
|
+
Message.error('网络连接失败')
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
return Promise.reject(error)
|
|
95
|
+
}
|
|
96
|
+
)
|
|
97
|
+
|
|
98
|
+
return instance
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
const request = createRequest()
|
|
102
|
+
|
|
103
|
+
// 请求方法
|
|
104
|
+
export const http = {
|
|
105
|
+
get<T = unknown>(url: string, config?: AxiosRequestConfig): Promise<ApiResponse<T>> {
|
|
106
|
+
return request.get(url, config).then((res) => res.data)
|
|
107
|
+
},
|
|
108
|
+
|
|
109
|
+
post<T = unknown>(url: string, data?: unknown, config?: AxiosRequestConfig): Promise<ApiResponse<T>> {
|
|
110
|
+
return request.post(url, data, config).then((res) => res.data)
|
|
111
|
+
},
|
|
112
|
+
|
|
113
|
+
put<T = unknown>(url: string, data?: unknown, config?: AxiosRequestConfig): Promise<ApiResponse<T>> {
|
|
114
|
+
return request.put(url, data, config).then((res) => res.data)
|
|
115
|
+
},
|
|
116
|
+
|
|
117
|
+
patch<T = unknown>(url: string, data?: unknown, config?: AxiosRequestConfig): Promise<ApiResponse<T>> {
|
|
118
|
+
return request.patch(url, data, config).then((res) => res.data)
|
|
119
|
+
},
|
|
120
|
+
|
|
121
|
+
delete<T = unknown>(url: string, config?: AxiosRequestConfig): Promise<ApiResponse<T>> {
|
|
122
|
+
return request.delete(url, config).then((res) => res.data)
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
export default request
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 本地存储封装
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import config from '@/config'
|
|
6
|
+
|
|
7
|
+
// toboyu-cloud 格式的 key 前缀
|
|
8
|
+
const getPrefix = (): string => {
|
|
9
|
+
return `tooyu-cloud:${config.appId}:`
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
interface StorageWrapper {
|
|
13
|
+
get<T>(key: string): T | null
|
|
14
|
+
set(key: string, value: unknown): void
|
|
15
|
+
remove(key: string): void
|
|
16
|
+
clear(): void
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
const createStorage = (storage: globalThis.Storage): StorageWrapper => {
|
|
20
|
+
return {
|
|
21
|
+
get<T>(key: string): T | null {
|
|
22
|
+
const prefix = getPrefix()
|
|
23
|
+
const value = storage.getItem(prefix + key)
|
|
24
|
+
if (!value) return null
|
|
25
|
+
try {
|
|
26
|
+
return JSON.parse(value) as T
|
|
27
|
+
} catch {
|
|
28
|
+
return value as unknown as T
|
|
29
|
+
}
|
|
30
|
+
},
|
|
31
|
+
set(key: string, value: unknown): void {
|
|
32
|
+
const prefix = getPrefix()
|
|
33
|
+
if (value === null || value === undefined) {
|
|
34
|
+
storage.removeItem(prefix + key)
|
|
35
|
+
return
|
|
36
|
+
}
|
|
37
|
+
const stringValue = typeof value === 'string' ? value : JSON.stringify(value)
|
|
38
|
+
storage.setItem(prefix + key, stringValue)
|
|
39
|
+
},
|
|
40
|
+
remove(key: string): void {
|
|
41
|
+
const prefix = getPrefix()
|
|
42
|
+
storage.removeItem(prefix + key)
|
|
43
|
+
},
|
|
44
|
+
clear(): void {
|
|
45
|
+
const prefix = getPrefix()
|
|
46
|
+
const keys = Object.keys(storage)
|
|
47
|
+
keys.forEach(key => {
|
|
48
|
+
if (key.startsWith(prefix)) {
|
|
49
|
+
storage.removeItem(key)
|
|
50
|
+
}
|
|
51
|
+
})
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
export const localStorageUtil = createStorage(window.localStorage)
|
|
57
|
+
export const sessionStorageUtil = createStorage(window.sessionStorage)
|
|
58
|
+
|
|
59
|
+
// 快捷方法
|
|
60
|
+
export const local = {
|
|
61
|
+
get: localStorageUtil.get,
|
|
62
|
+
set: localStorageUtil.set,
|
|
63
|
+
remove: localStorageUtil.remove,
|
|
64
|
+
clear: localStorageUtil.clear
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
export const session = {
|
|
68
|
+
get: sessionStorageUtil.get,
|
|
69
|
+
set: sessionStorageUtil.set,
|
|
70
|
+
remove: sessionStorageUtil.remove,
|
|
71
|
+
clear: sessionStorageUtil.clear
|
|
72
|
+
}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
</script>
|
|
3
|
+
|
|
4
|
+
<template>
|
|
5
|
+
<div class="dashboard">
|
|
6
|
+
<div class="dashboard__placeholder">
|
|
7
|
+
<h2>首页</h2>
|
|
8
|
+
<p>功能开发中...</p>
|
|
9
|
+
</div>
|
|
10
|
+
</div>
|
|
11
|
+
</template>
|
|
12
|
+
|
|
13
|
+
<style lang="scss" scoped>
|
|
14
|
+
.dashboard {
|
|
15
|
+
padding: 20px;
|
|
16
|
+
height: 100%;
|
|
17
|
+
|
|
18
|
+
&__placeholder {
|
|
19
|
+
display: flex;
|
|
20
|
+
flex-direction: column;
|
|
21
|
+
align-items: center;
|
|
22
|
+
justify-content: center;
|
|
23
|
+
height: 100%;
|
|
24
|
+
color: var(--color-text-secondary);
|
|
25
|
+
|
|
26
|
+
h2 {
|
|
27
|
+
margin-bottom: 10px;
|
|
28
|
+
color: var(--color-text-primary);
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
</style>
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
import { useRouter } from 'vue-router'
|
|
3
|
+
import { Button } from '@xto/base'
|
|
4
|
+
|
|
5
|
+
const router = useRouter()
|
|
6
|
+
|
|
7
|
+
const goBack = () => {
|
|
8
|
+
router.push('/')
|
|
9
|
+
}
|
|
10
|
+
</script>
|
|
11
|
+
|
|
12
|
+
<template>
|
|
13
|
+
<div class="error-page">
|
|
14
|
+
<div class="error-page__content">
|
|
15
|
+
<div class="error-page__code">403</div>
|
|
16
|
+
<div class="error-page__title">无访问权限</div>
|
|
17
|
+
<div class="error-page__desc">抱歉,您没有权限访问此页面</div>
|
|
18
|
+
<Button type="primary" @click="goBack">返回首页</Button>
|
|
19
|
+
</div>
|
|
20
|
+
</div>
|
|
21
|
+
</template>
|
|
22
|
+
|
|
23
|
+
<style lang="scss" scoped>
|
|
24
|
+
.error-page {
|
|
25
|
+
width: 100%;
|
|
26
|
+
height: 100vh;
|
|
27
|
+
display: flex;
|
|
28
|
+
align-items: center;
|
|
29
|
+
justify-content: center;
|
|
30
|
+
background-color: var(--bg-color-page);
|
|
31
|
+
|
|
32
|
+
&__content {
|
|
33
|
+
text-align: center;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
&__code {
|
|
37
|
+
font-size: 120px;
|
|
38
|
+
font-weight: 600;
|
|
39
|
+
color: var(--color-warning);
|
|
40
|
+
line-height: 1;
|
|
41
|
+
margin-bottom: 20px;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
&__title {
|
|
45
|
+
font-size: 24px;
|
|
46
|
+
font-weight: 500;
|
|
47
|
+
color: var(--color-text-primary);
|
|
48
|
+
margin-bottom: 10px;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
&__desc {
|
|
52
|
+
font-size: 14px;
|
|
53
|
+
color: var(--color-text-secondary);
|
|
54
|
+
margin-bottom: 30px;
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
</style>
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
import { useRouter } from 'vue-router'
|
|
3
|
+
import { Button } from '@xto/base'
|
|
4
|
+
|
|
5
|
+
const router = useRouter()
|
|
6
|
+
|
|
7
|
+
const goBack = () => {
|
|
8
|
+
router.push('/')
|
|
9
|
+
}
|
|
10
|
+
</script>
|
|
11
|
+
|
|
12
|
+
<template>
|
|
13
|
+
<div class="error-page">
|
|
14
|
+
<div class="error-page__content">
|
|
15
|
+
<div class="error-page__code">404</div>
|
|
16
|
+
<div class="error-page__title">页面不存在</div>
|
|
17
|
+
<div class="error-page__desc">抱歉,您访问的页面不存在或已被删除</div>
|
|
18
|
+
<Button type="primary" @click="goBack">返回首页</Button>
|
|
19
|
+
</div>
|
|
20
|
+
</div>
|
|
21
|
+
</template>
|
|
22
|
+
|
|
23
|
+
<style lang="scss" scoped>
|
|
24
|
+
.error-page {
|
|
25
|
+
width: 100%;
|
|
26
|
+
height: 100vh;
|
|
27
|
+
display: flex;
|
|
28
|
+
align-items: center;
|
|
29
|
+
justify-content: center;
|
|
30
|
+
background-color: var(--bg-color-page);
|
|
31
|
+
|
|
32
|
+
&__content {
|
|
33
|
+
text-align: center;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
&__code {
|
|
37
|
+
font-size: 120px;
|
|
38
|
+
font-weight: 600;
|
|
39
|
+
color: var(--color-primary);
|
|
40
|
+
line-height: 1;
|
|
41
|
+
margin-bottom: 20px;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
&__title {
|
|
45
|
+
font-size: 24px;
|
|
46
|
+
font-weight: 500;
|
|
47
|
+
color: var(--color-text-primary);
|
|
48
|
+
margin-bottom: 10px;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
&__desc {
|
|
52
|
+
font-size: 14px;
|
|
53
|
+
color: var(--color-text-secondary);
|
|
54
|
+
margin-bottom: 30px;
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
</style>
|