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.
Files changed (60) hide show
  1. package/.env.development +3 -0
  2. package/.env.production +3 -0
  3. package/bin/cli.js +104 -0
  4. package/index.html +13 -0
  5. package/package.json +16 -3
  6. package/public/vite.svg +10 -0
  7. package/src/App.vue +20 -0
  8. package/src/api/auth.ts +35 -0
  9. package/src/api/menu.ts +13 -0
  10. package/src/api/system.ts +65 -0
  11. package/src/api/user.ts +12 -0
  12. package/src/assets/styles/_dark.scss +407 -0
  13. package/src/assets/styles/_reset.scss +126 -0
  14. package/src/assets/styles/_root.scss +140 -0
  15. package/src/assets/styles/_transition.scss +119 -0
  16. package/src/assets/styles/_variables.scss +45 -0
  17. package/src/assets/styles/index.scss +187 -0
  18. package/src/components/Layout/Footer.vue +17 -0
  19. package/src/components/Layout/Header.vue +335 -0
  20. package/src/components/Layout/Sidebar.vue +213 -0
  21. package/src/components/Layout/Tabs.vue +20 -0
  22. package/src/components/Layout/index.vue +62 -0
  23. package/src/composables/index.ts +9 -0
  24. package/src/composables/useApp.ts +170 -0
  25. package/src/composables/useAuth.ts +70 -0
  26. package/src/composables/useForm.ts +79 -0
  27. package/src/composables/useMenu.ts +141 -0
  28. package/src/composables/useTable.ts +97 -0
  29. package/src/config/index.ts +19 -0
  30. package/src/directives/permission.ts +41 -0
  31. package/src/enums/index.ts +63 -0
  32. package/src/env.d.ts +17 -0
  33. package/src/index.ts +44 -0
  34. package/src/main.ts +34 -0
  35. package/src/router/dynamicRoutes.ts +163 -0
  36. package/src/router/index.ts +71 -0
  37. package/src/router/staticRoutes.ts +43 -0
  38. package/src/stores/app.ts +145 -0
  39. package/src/stores/auth.ts +45 -0
  40. package/src/stores/index.ts +15 -0
  41. package/src/stores/menu.ts +158 -0
  42. package/src/stores/user.ts +41 -0
  43. package/src/types/api.d.ts +103 -0
  44. package/src/types/global.d.ts +45 -0
  45. package/src/types/router.d.ts +48 -0
  46. package/src/types/xto.d.ts +149 -0
  47. package/src/utils/auth.ts +86 -0
  48. package/src/utils/permission.ts +30 -0
  49. package/src/utils/request.ts +126 -0
  50. package/src/utils/storage.ts +72 -0
  51. package/src/views/dashboard/index.vue +32 -0
  52. package/src/views/error/403.vue +57 -0
  53. package/src/views/error/404.vue +57 -0
  54. package/src/views/login/index.vue +141 -0
  55. package/src/views/system/menu/index.vue +32 -0
  56. package/src/views/system/role/index.vue +32 -0
  57. package/src/views/system/user/index.vue +32 -0
  58. package/tsconfig.json +26 -0
  59. package/tsconfig.node.json +11 -0
  60. 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>