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.
Files changed (60) hide show
  1. package/.env.development +3 -0
  2. package/.env.production +3 -0
  3. package/bin/cli.js +103 -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,119 @@
1
+ // ==============================================
2
+ // Transitions - 过渡动画
3
+ // ==============================================
4
+
5
+ // 淡入淡出
6
+ .fade-enter-active,
7
+ .fade-leave-active {
8
+ transition: opacity var(--transition-duration);
9
+ }
10
+
11
+ .fade-enter-from,
12
+ .fade-leave-to {
13
+ opacity: 0;
14
+ }
15
+
16
+ // 淡入淡出 + 缩放
17
+ .fade-scale-enter-active,
18
+ .fade-scale-leave-active {
19
+ transition: all var(--transition-duration);
20
+ }
21
+
22
+ .fade-scale-enter-from,
23
+ .fade-scale-leave-to {
24
+ opacity: 0;
25
+ transform: scale(0.9);
26
+ }
27
+
28
+ // 淡入淡出 + 滑动
29
+ .fade-transform-enter-active,
30
+ .fade-transform-leave-active {
31
+ transition: all var(--transition-duration-fast);
32
+ }
33
+
34
+ .fade-transform-enter-from {
35
+ opacity: 0;
36
+ transform: translateX(-10px);
37
+ }
38
+
39
+ .fade-transform-leave-to {
40
+ opacity: 0;
41
+ transform: translateX(10px);
42
+ }
43
+
44
+ // 向下滑入
45
+ .slide-down-enter-active,
46
+ .slide-down-leave-active {
47
+ transition: all var(--transition-duration);
48
+ }
49
+
50
+ .slide-down-enter-from,
51
+ .slide-down-leave-to {
52
+ opacity: 0;
53
+ transform: translateY(-20px);
54
+ }
55
+
56
+ // 向上滑入
57
+ .slide-up-enter-active,
58
+ .slide-up-leave-active {
59
+ transition: all var(--transition-duration);
60
+ }
61
+
62
+ .slide-up-enter-from,
63
+ .slide-up-leave-to {
64
+ opacity: 0;
65
+ transform: translateY(20px);
66
+ }
67
+
68
+ // 向左滑入
69
+ .slide-left-enter-active,
70
+ .slide-left-leave-active {
71
+ transition: all var(--transition-duration);
72
+ }
73
+
74
+ .slide-left-enter-from,
75
+ .slide-left-leave-to {
76
+ opacity: 0;
77
+ transform: translateX(20px);
78
+ }
79
+
80
+ // 向右滑入
81
+ .slide-right-enter-active,
82
+ .slide-right-leave-active {
83
+ transition: all var(--transition-duration);
84
+ }
85
+
86
+ .slide-right-enter-from,
87
+ .slide-right-leave-to {
88
+ opacity: 0;
89
+ transform: translateX(-20px);
90
+ }
91
+
92
+ // 缩放
93
+ .zoom-enter-active,
94
+ .zoom-leave-active {
95
+ transition: all var(--transition-duration);
96
+ }
97
+
98
+ .zoom-enter-from,
99
+ .zoom-leave-to {
100
+ opacity: 0;
101
+ transform: scale(0.5);
102
+ }
103
+
104
+ // 列表动画
105
+ .list-enter-active,
106
+ .list-leave-active {
107
+ transition: all var(--transition-duration);
108
+ }
109
+
110
+ .list-enter-from,
111
+ .list-leave-to {
112
+ opacity: 0;
113
+ transform: translateY(30px);
114
+ }
115
+
116
+ // 侧边栏折叠动画
117
+ .collapse-transition {
118
+ transition: width var(--transition-duration), padding var(--transition-duration);
119
+ }
@@ -0,0 +1,45 @@
1
+ // ==============================================
2
+ // SCSS Variables - 用于样式计算
3
+ // ==============================================
4
+
5
+ // 主题色
6
+ $color-primary: #409eff;
7
+ $color-success: #67c23a;
8
+ $color-warning: #e6a23c;
9
+ $color-danger: #f56c6c;
10
+ $color-info: #909399;
11
+
12
+ // 文字颜色
13
+ $color-text-primary: #303133;
14
+ $color-text-regular: #606266;
15
+ $color-text-secondary: #909399;
16
+
17
+ // 边框颜色
18
+ $color-border: #dcdfe6;
19
+ $color-border-light: #e4e7ed;
20
+ $color-border-lighter: #ebeef5;
21
+
22
+ // 背景颜色
23
+ $bg-color: #ffffff;
24
+ $bg-color-page: #f2f3f5;
25
+
26
+ // 字体
27
+ $font-size-base: 14px;
28
+ $font-size-small: 12px;
29
+
30
+ // 圆角
31
+ $border-radius-base: 4px;
32
+ $border-radius-large: 8px;
33
+
34
+ // 间距
35
+ $spacing-xs: 4px;
36
+ $spacing-sm: 8px;
37
+ $spacing-md: 16px;
38
+ $spacing-lg: 24px;
39
+
40
+ // 布局
41
+ $sidebar-width: 210px;
42
+ $sidebar-collapsed-width: 64px;
43
+ $header-height: 50px;
44
+ $tabs-height: 40px;
45
+ $footer-height: 30px;
@@ -0,0 +1,187 @@
1
+ // ==============================================
2
+ // 全局样式入口
3
+ // ==============================================
4
+
5
+ // CSS 变量(根选择器)
6
+ @use 'root';
7
+
8
+ // Reset
9
+ @use 'reset';
10
+
11
+ // Transitions
12
+ @use 'transition';
13
+
14
+ // Dark Theme Override
15
+ @use 'dark';
16
+
17
+ // ==============================================
18
+ // 全局通用样式
19
+ // ==============================================
20
+
21
+ // 文字截断
22
+ .text-ellipsis {
23
+ overflow: hidden;
24
+ text-overflow: ellipsis;
25
+ white-space: nowrap;
26
+ }
27
+
28
+ // 多行截断
29
+ .text-ellipsis-2 {
30
+ display: -webkit-box;
31
+ -webkit-line-clamp: 2;
32
+ -webkit-box-orient: vertical;
33
+ overflow: hidden;
34
+ }
35
+
36
+ // Flex 布局
37
+ .flex {
38
+ display: flex;
39
+ }
40
+
41
+ .flex-center {
42
+ display: flex;
43
+ align-items: center;
44
+ justify-content: center;
45
+ }
46
+
47
+ .flex-between {
48
+ display: flex;
49
+ align-items: center;
50
+ justify-content: space-between;
51
+ }
52
+
53
+ .flex-wrap {
54
+ flex-wrap: wrap;
55
+ }
56
+
57
+ .flex-1 {
58
+ flex: 1;
59
+ }
60
+
61
+ // 文字对齐
62
+ .text-left {
63
+ text-align: left;
64
+ }
65
+
66
+ .text-center {
67
+ text-align: center;
68
+ }
69
+
70
+ .text-right {
71
+ text-align: right;
72
+ }
73
+
74
+ // 颜色
75
+ .text-primary {
76
+ color: var(--color-primary);
77
+ }
78
+
79
+ .text-success {
80
+ color: var(--color-success);
81
+ }
82
+
83
+ .text-warning {
84
+ color: var(--color-warning);
85
+ }
86
+
87
+ .text-danger {
88
+ color: var(--color-danger);
89
+ }
90
+
91
+ .text-info {
92
+ color: var(--color-info);
93
+ }
94
+
95
+ // 背景
96
+ .bg-primary {
97
+ background-color: var(--color-primary);
98
+ }
99
+
100
+ .bg-success {
101
+ background-color: var(--color-success);
102
+ }
103
+
104
+ .bg-warning {
105
+ background-color: var(--color-warning);
106
+ }
107
+
108
+ .bg-danger {
109
+ background-color: var(--color-danger);
110
+ }
111
+
112
+ // 间距
113
+ .mt-10 {
114
+ margin-top: 10px;
115
+ }
116
+
117
+ .mt-20 {
118
+ margin-top: 20px;
119
+ }
120
+
121
+ .mb-10 {
122
+ margin-bottom: 10px;
123
+ }
124
+
125
+ .mb-20 {
126
+ margin-bottom: 20px;
127
+ }
128
+
129
+ .ml-10 {
130
+ margin-left: 10px;
131
+ }
132
+
133
+ .mr-10 {
134
+ margin-right: 10px;
135
+ }
136
+
137
+ .p-10 {
138
+ padding: 10px;
139
+ }
140
+
141
+ .p-20 {
142
+ padding: 20px;
143
+ }
144
+
145
+ // 卡片容器
146
+ .card {
147
+ background-color: var(--bg-color);
148
+ border-radius: var(--border-radius-base);
149
+ box-shadow: var(--box-shadow-light);
150
+ padding: var(--spacing-md);
151
+ }
152
+
153
+ // 页面容器
154
+ .page-container {
155
+ padding: var(--spacing-md);
156
+ min-height: 100%;
157
+ }
158
+
159
+ // 搜索栏
160
+ .search-bar {
161
+ display: flex;
162
+ flex-wrap: wrap;
163
+ gap: var(--spacing-sm);
164
+ margin-bottom: var(--spacing-md);
165
+ }
166
+
167
+ // 工具栏
168
+ .toolbar {
169
+ display: flex;
170
+ align-items: center;
171
+ justify-content: space-between;
172
+ margin-bottom: var(--spacing-md);
173
+ }
174
+
175
+ // 表格容器
176
+ .table-container {
177
+ background-color: var(--bg-color);
178
+ border-radius: var(--border-radius-base);
179
+ padding: var(--spacing-md);
180
+ }
181
+
182
+ // 分页容器
183
+ .pagination-container {
184
+ display: flex;
185
+ justify-content: flex-end;
186
+ margin-top: var(--spacing-md);
187
+ }
@@ -0,0 +1,17 @@
1
+ <script setup lang="ts">
2
+ </script>
3
+
4
+ <template>
5
+ <div class="footer">
6
+ <span>Copyright © 2024 Xto Demo. All Rights Reserved.</span>
7
+ </div>
8
+ </template>
9
+
10
+ <style lang="scss" scoped>
11
+ .footer {
12
+ width: 100%;
13
+ text-align: center;
14
+ font-size: 12px;
15
+ color: var(--color-text-secondary);
16
+ }
17
+ </style>
@@ -0,0 +1,335 @@
1
+ <script setup lang="ts">
2
+ /**
3
+ * 头部组件
4
+ * 使用统一的 useApp hooks
5
+ */
6
+ import { ref, computed, onMounted, onUnmounted } from 'vue'
7
+ import { useApp } from '@/composables'
8
+ import { useAppStore } from '@/stores/app'
9
+
10
+ const appStore = useAppStore()
11
+ const { logout, userName, userInfo } = useApp()
12
+
13
+ const dropdownVisible = ref(false)
14
+ const dropdownRef = ref<HTMLElement | null>(null)
15
+ const isFullscreen = ref(false)
16
+
17
+ // 用户显示名称
18
+ const userDisplayName = computed(() => userName.value || '用户')
19
+
20
+ // 切换折叠
21
+ const toggleCollapse = () => {
22
+ appStore.toggleCollapse()
23
+ }
24
+
25
+ // 切换主题
26
+ const toggleTheme = () => {
27
+ appStore.toggleTheme()
28
+ }
29
+
30
+ // 切换全屏
31
+ const toggleFullscreen = () => {
32
+ if (!document.fullscreenElement) {
33
+ document.documentElement.requestFullscreen()
34
+ } else {
35
+ document.exitFullscreen()
36
+ }
37
+ }
38
+
39
+ // 监听全屏变化
40
+ const handleFullscreenChange = () => {
41
+ isFullscreen.value = !!document.fullscreenElement
42
+ }
43
+
44
+ // 切换下拉菜单
45
+ const toggleDropdown = () => {
46
+ dropdownVisible.value = !dropdownVisible.value
47
+ }
48
+
49
+ // 关闭下拉菜单
50
+ const closeDropdown = () => {
51
+ dropdownVisible.value = false
52
+ }
53
+
54
+ // 退出登录
55
+ const handleLogout = async () => {
56
+ closeDropdown()
57
+ await logout()
58
+ }
59
+
60
+ // 点击外部关闭下拉菜单
61
+ const handleClickOutside = (event: MouseEvent) => {
62
+ if (dropdownRef.value && !dropdownRef.value.contains(event.target as Node)) {
63
+ closeDropdown()
64
+ }
65
+ }
66
+
67
+ onMounted(() => {
68
+ document.addEventListener('click', handleClickOutside)
69
+ document.addEventListener('fullscreenchange', handleFullscreenChange)
70
+ })
71
+
72
+ onUnmounted(() => {
73
+ document.removeEventListener('click', handleClickOutside)
74
+ document.removeEventListener('fullscreenchange', handleFullscreenChange)
75
+ })
76
+ </script>
77
+
78
+ <template>
79
+ <div class="header">
80
+ <!-- 左侧 -->
81
+ <div class="header__left">
82
+ <div class="header__collapse" @click="toggleCollapse">
83
+ <span>☰</span>
84
+ </div>
85
+ </div>
86
+
87
+ <!-- 右侧 -->
88
+ <div class="header__right">
89
+ <!-- 全屏切换 -->
90
+ <div class="header__action" @click="toggleFullscreen" :title="isFullscreen ? '退出全屏' : '全屏'">
91
+ <svg v-if="isFullscreen" viewBox="0 0 24 24" width="16" height="16" fill="currentColor">
92
+ <path d="M5 16h3v3h2v-5H5v2zm3-8H5v2h5V5H8v3zm6 11h2v-3h3v-2h-5v5zm2-11V5h-2v5h5V8h-3z"/>
93
+ </svg>
94
+ <svg v-else viewBox="0 0 24 24" width="16" height="16" fill="currentColor">
95
+ <path d="M7 14H5v5h5v-2H7v-3zm-2-4h2V7h3V5H5v5zm12 7h-3v2h5v-5h-2v3zM14 5v2h3v3h2V5h-5z"/>
96
+ </svg>
97
+ </div>
98
+
99
+ <!-- 主题切换 -->
100
+ <div class="header__action" @click="toggleTheme" title="切换主题">
101
+ <span v-if="appStore.isDark">🌙</span>
102
+ <span v-else>☀️</span>
103
+ </div>
104
+
105
+ <!-- 用户头像 -->
106
+ <div class="header__user" ref="dropdownRef">
107
+ <div class="header__user-trigger" @click.stop="toggleDropdown">
108
+ <div class="header__avatar">
109
+ <span>{{ userDisplayName.charAt(0) }}</span>
110
+ </div>
111
+ <span class="header__user-name">{{ userDisplayName }}</span>
112
+ <span class="header__user-arrow" :class="{ 'is-active': dropdownVisible }">▼</span>
113
+ </div>
114
+
115
+ <!-- 下拉菜单 -->
116
+ <Transition name="dropdown">
117
+ <div v-if="dropdownVisible" class="header__dropdown">
118
+ <div class="header__dropdown-header">
119
+ <div class="header__dropdown-avatar">
120
+ <span>{{ userDisplayName.charAt(0) }}</span>
121
+ </div>
122
+ <div class="header__dropdown-info">
123
+ <div class="header__dropdown-name">{{ userDisplayName }}</div>
124
+ <div class="header__dropdown-role">{{ userInfo?.departmentName || '' }}</div>
125
+ </div>
126
+ </div>
127
+ <div class="header__dropdown-divider"></div>
128
+ <div class="header__dropdown-menu">
129
+ <div class="header__dropdown-item header__dropdown-item--danger" @click="handleLogout">
130
+ <span class="header__dropdown-icon">🚪</span>
131
+ <span>退出登录</span>
132
+ </div>
133
+ </div>
134
+ </div>
135
+ </Transition>
136
+ </div>
137
+ </div>
138
+ </div>
139
+ </template>
140
+
141
+ <style lang="scss" scoped>
142
+ .header {
143
+ display: flex;
144
+ align-items: center;
145
+ justify-content: space-between;
146
+ padding: 0 20px;
147
+ height: 100%;
148
+
149
+ &__left {
150
+ display: flex;
151
+ align-items: center;
152
+ gap: 15px;
153
+ }
154
+
155
+ &__collapse {
156
+ width: 24px;
157
+ height: 24px;
158
+ display: flex;
159
+ align-items: center;
160
+ justify-content: center;
161
+ cursor: pointer;
162
+ font-size: 18px;
163
+ color: var(--color-text-regular);
164
+
165
+ &:hover {
166
+ color: var(--color-primary);
167
+ }
168
+ }
169
+
170
+ &__right {
171
+ display: flex;
172
+ align-items: center;
173
+ gap: 15px;
174
+ }
175
+
176
+ &__action {
177
+ width: 32px;
178
+ height: 32px;
179
+ display: flex;
180
+ align-items: center;
181
+ justify-content: center;
182
+ cursor: pointer;
183
+ border-radius: var(--border-radius-base);
184
+ font-size: 16px;
185
+
186
+ &:hover {
187
+ background-color: var(--color-fill);
188
+ }
189
+ }
190
+
191
+ &__user {
192
+ position: relative;
193
+
194
+ &-trigger {
195
+ display: flex;
196
+ align-items: center;
197
+ gap: 8px;
198
+ cursor: pointer;
199
+ padding: 4px 8px;
200
+ border-radius: var(--border-radius-base);
201
+ transition: background-color 0.2s;
202
+
203
+ &:hover {
204
+ background-color: var(--color-fill);
205
+ }
206
+ }
207
+
208
+ &-name {
209
+ font-size: 14px;
210
+ color: var(--color-text-primary);
211
+ }
212
+
213
+ &-arrow {
214
+ font-size: 10px;
215
+ color: var(--color-text-secondary);
216
+ transition: transform 0.2s;
217
+
218
+ &.is-active {
219
+ transform: rotate(180deg);
220
+ }
221
+ }
222
+ }
223
+
224
+ &__avatar {
225
+ width: 32px;
226
+ height: 32px;
227
+ border-radius: 50%;
228
+ background: linear-gradient(135deg, var(--color-primary), var(--color-primary-light-3));
229
+ display: flex;
230
+ align-items: center;
231
+ justify-content: center;
232
+ color: #fff;
233
+ font-size: 14px;
234
+ font-weight: 500;
235
+ }
236
+
237
+ &__dropdown {
238
+ position: absolute;
239
+ top: calc(100% + 8px);
240
+ right: 0;
241
+ min-width: 200px;
242
+ background-color: var(--bg-color);
243
+ border-radius: var(--border-radius-base);
244
+ box-shadow: var(--box-shadow);
245
+ overflow: hidden;
246
+ z-index: 100;
247
+
248
+ &-header {
249
+ display: flex;
250
+ align-items: center;
251
+ gap: 12px;
252
+ padding: 16px;
253
+ }
254
+
255
+ &-avatar {
256
+ width: 40px;
257
+ height: 40px;
258
+ border-radius: 50%;
259
+ background: linear-gradient(135deg, var(--color-primary), var(--color-primary-light-3));
260
+ display: flex;
261
+ align-items: center;
262
+ justify-content: center;
263
+ color: #fff;
264
+ font-size: 16px;
265
+ font-weight: 500;
266
+ }
267
+
268
+ &-info {
269
+ flex: 1;
270
+ }
271
+
272
+ &-name {
273
+ font-size: 14px;
274
+ font-weight: 500;
275
+ color: var(--color-text-primary);
276
+ }
277
+
278
+ &-role {
279
+ font-size: 12px;
280
+ color: var(--color-text-secondary);
281
+ margin-top: 2px;
282
+ }
283
+
284
+ &-divider {
285
+ height: 1px;
286
+ background-color: var(--color-border-lighter);
287
+ }
288
+
289
+ &-menu {
290
+ padding: 8px 0;
291
+ }
292
+
293
+ &-item {
294
+ display: flex;
295
+ align-items: center;
296
+ gap: 10px;
297
+ padding: 10px 16px;
298
+ cursor: pointer;
299
+ font-size: 14px;
300
+ color: var(--color-text-regular);
301
+ transition: all 0.2s;
302
+
303
+ &:hover {
304
+ background-color: var(--color-fill);
305
+ color: var(--color-text-primary);
306
+ }
307
+
308
+ &--danger {
309
+ color: var(--color-danger);
310
+
311
+ &:hover {
312
+ background-color: var(--color-danger-light);
313
+ color: var(--color-danger);
314
+ }
315
+ }
316
+ }
317
+
318
+ &-icon {
319
+ font-size: 16px;
320
+ }
321
+ }
322
+ }
323
+
324
+ // 下拉动画
325
+ .dropdown-enter-active,
326
+ .dropdown-leave-active {
327
+ transition: all 0.2s ease;
328
+ }
329
+
330
+ .dropdown-enter-from,
331
+ .dropdown-leave-to {
332
+ opacity: 0;
333
+ transform: translateY(-10px);
334
+ }
335
+ </style>