@kine-design/crud 0.0.1-beta.2 → 0.0.1-beta.21

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 (118) hide show
  1. package/.vlaude/last-session-id +1 -0
  2. package/components/crudPage/KCrudPage.tsx +178 -0
  3. package/components/crudPage/crudPage.css +64 -0
  4. package/components/crudPage/index.ts +10 -0
  5. package/components/editableTable/KEditableTable.tsx +281 -0
  6. package/components/editableTable/editableTable.css +268 -0
  7. package/components/editableTable/index.ts +10 -0
  8. package/components/formPage/KApprovalDialog.tsx +142 -0
  9. package/components/formPage/KFormCard.tsx +65 -0
  10. package/components/formPage/KFormPage.tsx +128 -0
  11. package/components/formPage/KMasterDetailPage.tsx +205 -0
  12. package/components/formPage/KStickyActionBar.tsx +33 -0
  13. package/components/formPage/formPage.css +629 -0
  14. package/components/formPage/index.ts +14 -0
  15. package/components/layout/KContent.tsx +20 -0
  16. package/components/layout/KHeader.tsx +37 -0
  17. package/components/layout/KLayout.tsx +82 -0
  18. package/components/layout/KSider.tsx +80 -0
  19. package/components/layout/index.ts +18 -0
  20. package/components/layout/layout.css +262 -0
  21. package/components/login/KLoginPage.tsx +129 -0
  22. package/components/login/index.ts +10 -0
  23. package/components/login/login.css +118 -0
  24. package/components/navMenu/KNavMenu.tsx +175 -0
  25. package/components/navMenu/index.ts +2 -0
  26. package/components/navMenu/navMenu.css +197 -0
  27. package/components/pageHeader/KPageHeader.tsx +85 -0
  28. package/components/pageHeader/index.ts +9 -0
  29. package/components/pageHeader/pageHeader.css +93 -0
  30. package/components/searchTable/KSearchTable.tsx +138 -0
  31. package/components/searchTable/index.ts +10 -0
  32. package/components/searchTable/searchTable.css +121 -0
  33. package/components/upload/KFileList.tsx +95 -0
  34. package/components/upload/KImageUpload.tsx +286 -0
  35. package/components/upload/KUpload.tsx +206 -0
  36. package/components/upload/index.ts +13 -0
  37. package/components/upload/types.ts +26 -0
  38. package/components/upload/upload.css +345 -0
  39. package/composables/auth/authGuard.ts +128 -0
  40. package/composables/auth/index.ts +23 -0
  41. package/composables/auth/types.ts +109 -0
  42. package/composables/auth/useAuth.ts +278 -0
  43. package/composables/auth/vCan.ts +95 -0
  44. package/composables/defineRepository.ts +224 -0
  45. package/composables/error/createErrorHandler.ts +46 -0
  46. package/composables/error/defaultFeedbackHandler.ts +76 -0
  47. package/composables/error/dispatchError.ts +70 -0
  48. package/composables/error/index.ts +32 -0
  49. package/composables/error/types.ts +57 -0
  50. package/composables/error/useErrorHandler.ts +41 -0
  51. package/composables/form/index.ts +18 -0
  52. package/composables/form/renderFormField.tsx +119 -0
  53. package/composables/form/types.ts +129 -0
  54. package/composables/form/useFormPage.ts +183 -0
  55. package/composables/index.ts +62 -0
  56. package/composables/page/index.ts +11 -0
  57. package/composables/page/types.ts +62 -0
  58. package/composables/page/useCrudPage.ts +88 -0
  59. package/composables/request/composables.ts +206 -0
  60. package/composables/request/controlGate.ts +143 -0
  61. package/composables/request/createRequest.ts +173 -0
  62. package/composables/request/index.ts +71 -0
  63. package/composables/request/orchestrator.ts +145 -0
  64. package/composables/request/requestBuilder.ts +418 -0
  65. package/composables/request/transport/fetchTransport.ts +79 -0
  66. package/composables/request/transport/xhrTransport.ts +100 -0
  67. package/composables/request/types.ts +226 -0
  68. package/composables/request/upload.ts +146 -0
  69. package/composables/router/createRouterGuard.ts +134 -0
  70. package/composables/router/defineCrudRoutes.ts +116 -0
  71. package/composables/router/index.ts +22 -0
  72. package/composables/router/types.ts +128 -0
  73. package/composables/router/useMenuFromRoutes.ts +109 -0
  74. package/composables/router/useTabStore.ts +183 -0
  75. package/composables/search/index.ts +11 -0
  76. package/composables/search/useAutoCompleteSearch.ts +161 -0
  77. package/composables/setupCrud.ts +43 -0
  78. package/composables/storage/createStorageAdapter.ts +72 -0
  79. package/composables/storage/index.ts +13 -0
  80. package/composables/storage/types.ts +30 -0
  81. package/composables/storage/useStorage.ts +108 -0
  82. package/composables/store/defineUserStore.ts +122 -0
  83. package/composables/store/index.ts +11 -0
  84. package/composables/types.ts +118 -0
  85. package/dist/components/crudPage/KCrudPage.d.ts +14 -0
  86. package/dist/components/crudPage/index.d.ts +9 -0
  87. package/dist/components/editableTable/KEditableTable.d.ts +146 -0
  88. package/dist/components/editableTable/index.d.ts +10 -0
  89. package/dist/components/formPage/KApprovalDialog.d.ts +99 -0
  90. package/dist/components/formPage/KFormCard.d.ts +49 -0
  91. package/dist/components/formPage/KFormPage.d.ts +14 -0
  92. package/dist/components/formPage/KMasterDetailPage.d.ts +14 -0
  93. package/dist/components/formPage/KStickyActionBar.d.ts +16 -0
  94. package/dist/components/formPage/index.d.ts +14 -0
  95. package/dist/components/layout/KLayout.d.ts +7 -4
  96. package/dist/composables/auth/useAuth.d.ts +5 -5
  97. package/dist/composables/error/types.d.ts +2 -1
  98. package/dist/composables/form/index.d.ts +12 -0
  99. package/dist/composables/form/renderFormField.d.ts +11 -0
  100. package/dist/composables/form/types.d.ts +104 -0
  101. package/dist/composables/form/useFormPage.d.ts +38 -0
  102. package/dist/composables/index.d.ts +2 -0
  103. package/dist/composables/page/index.d.ts +10 -0
  104. package/dist/composables/page/types.d.ts +61 -0
  105. package/dist/composables/page/useCrudPage.d.ts +14 -0
  106. package/dist/composables/request/createRequest.d.ts +2 -0
  107. package/dist/composables/request/requestBuilder.d.ts +2 -0
  108. package/dist/composables/search/index.d.ts +10 -0
  109. package/dist/composables/search/useAutoCompleteSearch.d.ts +50 -0
  110. package/dist/crud.css +2499 -663
  111. package/dist/crud.js +11512 -2910
  112. package/dist/index.d.ts +11 -0
  113. package/dist/setup.d.ts +2 -2
  114. package/index.ts +144 -0
  115. package/package.json +20 -19
  116. package/setup.ts +288 -0
  117. package/tsconfig.json +12 -0
  118. package/vite.config.build.ts +52 -0
@@ -0,0 +1,13 @@
1
+ /**
2
+ * @description upload 组件 barrel export
3
+ * @author 阿怪
4
+ * @date 2026/2/26
5
+ * @version v0.0.1
6
+ *
7
+ * 江湖的业务千篇一律,复杂的代码好几百行。
8
+ */
9
+
10
+ export { default as KUpload } from './KUpload';
11
+ export { default as KImageUpload } from './KImageUpload';
12
+ export { default as KFileList } from './KFileList';
13
+ export type { UploadFile, UploadRequest } from './types';
@@ -0,0 +1,26 @@
1
+ /**
2
+ * @description 上传组件类型定义
3
+ * @author 阿怪
4
+ * @date 2026/2/26
5
+ * @version v0.0.1
6
+ *
7
+ * 江湖的业务千篇一律,复杂的代码好几百行。
8
+ */
9
+
10
+ export interface UploadFile {
11
+ uid: string;
12
+ name: string;
13
+ size: number;
14
+ type: string;
15
+ status: 'pending' | 'uploading' | 'success' | 'error';
16
+ percent: number;
17
+ url?: string;
18
+ thumbUrl?: string;
19
+ raw?: File;
20
+ response?: unknown;
21
+ }
22
+
23
+ export type UploadRequest = (
24
+ file: File,
25
+ onProgress: (percent: number) => void,
26
+ ) => Promise<{ url: string }>;
@@ -0,0 +1,345 @@
1
+ /**
2
+ * @description 上传组件样式 — Phosphor sci-fi 暗色主题
3
+ * @author 阿怪
4
+ * @date 2026/2/26
5
+ * @version v0.0.1
6
+ */
7
+
8
+ /* ── 隐藏原生 input ── */
9
+ .k-upload-input {
10
+ display: none;
11
+ }
12
+
13
+ /* ══════════════════════════════════════════
14
+ KUpload — 基础上传区域
15
+ ══════════════════════════════════════════ */
16
+
17
+ .k-upload {
18
+ position: relative;
19
+ display: inline-flex;
20
+ align-items: center;
21
+ justify-content: center;
22
+ width: 100%;
23
+ min-height: 80px;
24
+ border-radius: var(--kine-radius-sm);
25
+ background: var(--kine-color-bg-secondary);
26
+ cursor: pointer;
27
+ transition:
28
+ border-color var(--kine-motion-duration-fast) var(--kine-motion-easing-default),
29
+ background var(--kine-motion-duration-fast) var(--kine-motion-easing-default);
30
+ box-sizing: border-box;
31
+ }
32
+
33
+ /* 拖拽模式:虚线边框 */
34
+ .k-upload-drag {
35
+ border: 1px dashed var(--kine-color-border-default);
36
+ }
37
+
38
+ .k-upload-drag:hover {
39
+ border-color: var(--kine-color-accent-default);
40
+ background: color-mix(in srgb, var(--kine-color-accent-default) 6%, var(--kine-color-bg-secondary));
41
+ }
42
+
43
+ /* 拖拽悬停态 */
44
+ .k-upload-drag-over {
45
+ border-color: var(--kine-color-accent-default) !important;
46
+ background: color-mix(in srgb, var(--kine-color-accent-default) 10%, var(--kine-color-bg-secondary)) !important;
47
+ box-shadow: inset 0 0 0 1px var(--kine-color-accent-default);
48
+ }
49
+
50
+ /* 禁用态 */
51
+ .k-upload-disabled {
52
+ opacity: var(--kine-opacity-muted);
53
+ cursor: not-allowed;
54
+ pointer-events: none;
55
+ }
56
+
57
+ /* 默认占位内容 */
58
+ .k-upload-placeholder {
59
+ display: flex;
60
+ flex-direction: column;
61
+ align-items: center;
62
+ gap: var(--kine-spacing-3);
63
+ pointer-events: none;
64
+ }
65
+
66
+ .k-upload-icon {
67
+ font-size: var(--kine-font-size-4xl);
68
+ color: var(--kine-color-text-secondary);
69
+ line-height: 1;
70
+ }
71
+
72
+ .k-upload-text {
73
+ font-family: var(--kine-font-family-mono);
74
+ font-size: var(--kine-font-size-sm);
75
+ color: var(--kine-color-text-secondary);
76
+ }
77
+
78
+ /* ══════════════════════════════════════════
79
+ KImageUpload — 图片网格
80
+ ══════════════════════════════════════════ */
81
+
82
+ .k-image-upload {
83
+ display: block;
84
+ }
85
+
86
+ .k-image-upload-grid {
87
+ display: flex;
88
+ flex-wrap: wrap;
89
+ gap: var(--kine-spacing-4);
90
+ }
91
+
92
+ /* 图片卡片(预览 + 添加占位共用) */
93
+ .k-image-upload-card {
94
+ position: relative;
95
+ border-radius: var(--kine-radius-sm);
96
+ overflow: hidden;
97
+ border: 1px solid var(--kine-color-border-default);
98
+ background: var(--kine-color-bg-secondary);
99
+ flex-shrink: 0;
100
+ box-sizing: border-box;
101
+ }
102
+
103
+ /* 添加卡片 */
104
+ .k-image-upload-card--add {
105
+ display: flex;
106
+ align-items: center;
107
+ justify-content: center;
108
+ border-style: dashed;
109
+ cursor: pointer;
110
+ transition:
111
+ border-color var(--kine-motion-duration-fast) var(--kine-motion-easing-default),
112
+ background var(--kine-motion-duration-fast) var(--kine-motion-easing-default);
113
+ }
114
+
115
+ .k-image-upload-card--add:hover {
116
+ border-color: var(--kine-color-accent-default);
117
+ background: color-mix(in srgb, var(--kine-color-accent-default) 6%, var(--kine-color-bg-secondary));
118
+ }
119
+
120
+ .k-image-upload-add-icon {
121
+ font-size: 28px; /* 28px 无精确 token,保留 */
122
+ color: var(--kine-color-text-secondary);
123
+ line-height: 1;
124
+ pointer-events: none;
125
+ }
126
+
127
+ /* 错误态卡片边框 */
128
+ .k-image-upload-card--error {
129
+ border-color: var(--kine-color-semantic-error);
130
+ }
131
+
132
+ /* 预览图 */
133
+ .k-image-upload-preview {
134
+ width: 100%;
135
+ height: 100%;
136
+ object-fit: cover;
137
+ display: block;
138
+ }
139
+
140
+ /* 上传进度覆盖层 */
141
+ .k-image-upload-overlay {
142
+ position: absolute;
143
+ inset: 0;
144
+ display: flex;
145
+ flex-direction: column;
146
+ align-items: center;
147
+ justify-content: flex-end;
148
+ background: rgba(0, 0, 0, 0.45);
149
+ }
150
+
151
+ .k-image-upload-overlay--error {
152
+ justify-content: center;
153
+ font-family: var(--kine-font-family-mono);
154
+ font-size: var(--kine-font-size-sm);
155
+ color: var(--kine-color-semantic-error);
156
+ }
157
+
158
+ .k-image-upload-progress {
159
+ position: absolute;
160
+ bottom: 0;
161
+ left: 0;
162
+ right: 0;
163
+ background: var(--kine-color-accent-default);
164
+ box-shadow: 0 0 var(--kine-shadow-glow-radius) var(--kine-color-accent-default);
165
+ transition: height var(--kine-motion-duration-fast) var(--kine-motion-easing-default);
166
+ opacity: 0.6;
167
+ }
168
+
169
+ .k-image-upload-percent {
170
+ position: relative;
171
+ z-index: 1;
172
+ font-family: var(--kine-font-family-mono);
173
+ font-size: var(--kine-font-size-sm);
174
+ color: #ffffff;
175
+ padding-bottom: var(--kine-spacing-2);
176
+ }
177
+
178
+ /* 删除按钮(悬停卡片时显示) */
179
+ .k-image-upload-remove {
180
+ position: absolute;
181
+ top: var(--kine-spacing-1);
182
+ right: var(--kine-spacing-1);
183
+ width: 20px;
184
+ height: 20px;
185
+ display: none;
186
+ align-items: center;
187
+ justify-content: center;
188
+ background: rgba(0, 0, 0, 0.6);
189
+ border: none;
190
+ border-radius: var(--kine-radius-xs);
191
+ color: #ffffff;
192
+ font-size: var(--kine-font-size-xl);
193
+ cursor: pointer;
194
+ line-height: 1;
195
+ z-index: 10;
196
+ padding: 0;
197
+ }
198
+
199
+ .k-image-upload-card:hover .k-image-upload-remove {
200
+ display: flex;
201
+ }
202
+
203
+ .k-image-upload-remove:hover {
204
+ background: var(--kine-color-semantic-error);
205
+ }
206
+
207
+ /* ══════════════════════════════════════════
208
+ KFileList — 文件列表
209
+ ══════════════════════════════════════════ */
210
+
211
+ .k-file-list {
212
+ list-style: none;
213
+ margin: 0;
214
+ padding: 0;
215
+ display: flex;
216
+ flex-direction: column;
217
+ gap: var(--kine-spacing-2);
218
+ width: 100%;
219
+ }
220
+
221
+ .k-file-list-item {
222
+ display: grid;
223
+ grid-template-columns: 20px 1fr auto;
224
+ align-items: center;
225
+ column-gap: var(--kine-spacing-4);
226
+ padding: var(--kine-spacing-3) var(--kine-spacing-4);
227
+ border-radius: var(--kine-radius-xs);
228
+ background: var(--kine-color-bg-secondary);
229
+ border: 1px solid var(--kine-color-border-default);
230
+ font-family: var(--kine-font-family-mono);
231
+ transition: border-color var(--kine-motion-duration-fast) var(--kine-motion-easing-default);
232
+ position: relative;
233
+ }
234
+
235
+ /* 上传中时需要显示进度条,需要多行 */
236
+ .k-file-list-item--uploading {
237
+ grid-template-columns: 20px 1fr auto;
238
+ grid-template-rows: auto 4px;
239
+ }
240
+
241
+ /* 上传中删除按钮固定在第一行 */
242
+ .k-file-list-item--uploading .k-file-list-remove {
243
+ grid-row: 1;
244
+ grid-column: 3;
245
+ }
246
+
247
+ .k-file-list-item--success {
248
+ border-color: color-mix(in srgb, var(--kine-color-semantic-success) 30%, var(--kine-color-border-default));
249
+ }
250
+
251
+ .k-file-list-item--error {
252
+ border-color: color-mix(in srgb, var(--kine-color-semantic-error) 40%, var(--kine-color-border-default));
253
+ }
254
+
255
+ /* 文件图标 */
256
+ .k-file-list-icon {
257
+ font-size: var(--kine-font-size-md);
258
+ text-align: center;
259
+ flex-shrink: 0;
260
+ }
261
+
262
+ .k-file-list-item--success .k-file-list-icon {
263
+ color: var(--kine-color-semantic-success);
264
+ }
265
+
266
+ .k-file-list-item--error .k-file-list-icon {
267
+ color: var(--kine-color-semantic-error);
268
+ }
269
+
270
+ .k-file-list-item--uploading .k-file-list-icon,
271
+ .k-file-list-item--pending .k-file-list-icon {
272
+ color: var(--kine-color-text-secondary);
273
+ }
274
+
275
+ /* 文件信息 */
276
+ .k-file-list-info {
277
+ display: flex;
278
+ flex-direction: column;
279
+ gap: var(--kine-spacing-1);
280
+ overflow: hidden;
281
+ }
282
+
283
+ .k-file-list-name {
284
+ font-size: var(--kine-font-size-sm);
285
+ color: var(--kine-color-text-primary);
286
+ white-space: nowrap;
287
+ overflow: hidden;
288
+ text-overflow: ellipsis;
289
+ }
290
+
291
+ .k-file-list-link {
292
+ color: var(--kine-color-accent-default);
293
+ text-decoration: none;
294
+ }
295
+
296
+ .k-file-list-link:hover {
297
+ color: var(--kine-color-accent-hover);
298
+ text-decoration: underline;
299
+ }
300
+
301
+ .k-file-list-meta {
302
+ font-size: calc(var(--kine-font-size-sm) - 1px);
303
+ color: var(--kine-color-text-secondary);
304
+ }
305
+
306
+ /* 进度条(位于第二行,跨满三列) */
307
+ .k-file-list-progress-track {
308
+ grid-column: 1 / -1;
309
+ height: 3px;
310
+ border-radius: var(--kine-radius-xs);
311
+ background: var(--kine-color-bg-tertiary);
312
+ overflow: hidden;
313
+ margin-top: var(--kine-spacing-1);
314
+ }
315
+
316
+ .k-file-list-progress-fill {
317
+ height: 100%;
318
+ border-radius: var(--kine-radius-xs);
319
+ background: var(--kine-color-accent-default);
320
+ box-shadow: 0 0 var(--kine-shadow-glow-radius) var(--kine-color-accent-default);
321
+ transition: width var(--kine-motion-duration-fast) var(--kine-motion-easing-default);
322
+ }
323
+
324
+ /* 删除按钮 */
325
+ .k-file-list-remove {
326
+ display: flex;
327
+ align-items: center;
328
+ justify-content: center;
329
+ width: 20px;
330
+ height: 20px;
331
+ padding: 0;
332
+ background: transparent;
333
+ border: none;
334
+ border-radius: var(--kine-radius-xs);
335
+ color: var(--kine-color-text-secondary);
336
+ font-size: var(--kine-font-size-2xl);
337
+ cursor: pointer;
338
+ line-height: 1;
339
+ flex-shrink: 0;
340
+ transition: color var(--kine-motion-duration-fast) var(--kine-motion-easing-default);
341
+ }
342
+
343
+ .k-file-list-remove:hover {
344
+ color: var(--kine-color-semantic-error);
345
+ }
@@ -0,0 +1,128 @@
1
+ /**
2
+ * @description vue-router 路由守卫工厂,业务项目自行安装
3
+ * @author 阿怪
4
+ * @date 2026/2/26
5
+ * @version v0.0.1
6
+ *
7
+ * 江湖的业务千篇一律,复杂的代码好几百行。
8
+ */
9
+
10
+ import type { AuthReturn } from './types';
11
+
12
+ // ────────────────────────────────────────────────────────────────────────────
13
+ // 局部 vue-router 类型声明(不引入 vue-router 依赖)
14
+ // ────────────────────────────────────────────────────────────────────────────
15
+
16
+ /** 最小化 RouteLocationNormalized,只声明本文件用到的字段 */
17
+ interface RouteLocation {
18
+ path: string;
19
+ meta: RouteMeta;
20
+ }
21
+
22
+ /**
23
+ * 路由 meta 类型扩展。
24
+ * 业务项目可在 vue-router 模块声明中覆盖 RouteMeta 以获得完整类型提示:
25
+ *
26
+ * ```ts
27
+ * declare module 'vue-router' {
28
+ * interface RouteMeta {
29
+ * requiresAuth?: boolean;
30
+ * can?: { resource: string; action: string };
31
+ * }
32
+ * }
33
+ * ```
34
+ */
35
+ interface RouteMeta {
36
+ /** 是否需要登录,默认 true(undefined 视为 true)*/
37
+ requiresAuth?: boolean;
38
+ /** 路由级权限检查,等同于 can(resource, action) */
39
+ can?: { resource: string; action: string };
40
+ [key: string]: unknown;
41
+ }
42
+
43
+ /** 导航结果:undefined 表示放行,string 表示跳转目标路径 */
44
+ type NavigationResult = string | undefined;
45
+
46
+ /** beforeEach 守卫函数签名(与 vue-router NavigationGuardWithThis 兼容) */
47
+ export type BeforeEachGuard = (
48
+ to: RouteLocation,
49
+ from: RouteLocation,
50
+ ) => NavigationResult | Promise<NavigationResult>;
51
+
52
+ // ────────────────────────────────────────────────────────────────────────────
53
+ // createAuthGuard 选项
54
+ // ────────────────────────────────────────────────────────────────────────────
55
+
56
+ export interface AuthGuardOptions {
57
+ /**
58
+ * 未登录时跳转路径,默认 '/login'。
59
+ * 设为 false 时完全交由 onUnauthorized 处理,不执行跳转。
60
+ */
61
+ loginPath?: string | false;
62
+ /**
63
+ * 权限不足时跳转路径,默认 '/403'。
64
+ * 设为 false 时阻止导航但不跳转(返回 false)。
65
+ */
66
+ forbiddenPath?: string | false;
67
+ }
68
+
69
+ // ────────────────────────────────────────────────────────────────────────────
70
+ // createAuthGuard
71
+ // ────────────────────────────────────────────────────────────────────────────
72
+
73
+ /**
74
+ * 创建路由守卫函数,由业务项目自行注册到 vue-router。
75
+ *
76
+ * 守卫逻辑顺序:
77
+ * 1. 若路由声明了 meta.can,检查权限;无权限跳 forbiddenPath
78
+ * 2. 若路由 meta.requiresAuth !== false 且未登录,执行 onUnauthorized 并跳 loginPath
79
+ *
80
+ * @example
81
+ * ```ts
82
+ * // router/index.ts
83
+ * import { useAuth, createAuthGuard } from '@kine-design/crud';
84
+ *
85
+ * // 注意:必须在 Vue App 已 provide auth 后调用 useAuth()
86
+ * const guard = createAuthGuard(useAuth(), { loginPath: '/login' });
87
+ * router.beforeEach(guard);
88
+ * ```
89
+ *
90
+ * @param auth useAuth() 返回值
91
+ * @param options 守卫选项
92
+ */
93
+ export function createAuthGuard(
94
+ auth: AuthReturn,
95
+ options?: AuthGuardOptions,
96
+ ): BeforeEachGuard {
97
+ const loginPath = options?.loginPath ?? '/login';
98
+ const forbiddenPath = options?.forbiddenPath ?? '/403';
99
+
100
+ return (to: RouteLocation): NavigationResult => {
101
+ const meta = to.meta;
102
+
103
+ // ── 1. 登录检查(requiresAuth 默认为 true)──────────────────────────────
104
+ // 必须先检查登录状态,未登录时不应检查权限(permissions 为空会误判)
105
+ const requiresAuth = meta.requiresAuth !== false;
106
+ if (requiresAuth && !auth.isAuthenticated.value) {
107
+ // loginPath 为 false 表示完全由业务层的 onUnauthorized 处理
108
+ if (loginPath === false) return undefined;
109
+ // 避免重定向到登录页自身导致死循环
110
+ if (to.path === loginPath) return undefined;
111
+ return loginPath;
112
+ }
113
+
114
+ // ── 2. 路由级权限检查(已登录后才检查)────────────────────────────────────
115
+ if (meta.can) {
116
+ const { resource, action } = meta.can;
117
+ if (!auth.can(resource, action)) {
118
+ // 权限不足
119
+ if (forbiddenPath === false) return undefined;
120
+ if (to.path === forbiddenPath) return undefined;
121
+ return forbiddenPath;
122
+ }
123
+ }
124
+
125
+ // 放行
126
+ return undefined;
127
+ };
128
+ }
@@ -0,0 +1,23 @@
1
+ /**
2
+ * @description auth 模块 barrel export
3
+ * @author 阿怪
4
+ * @date 2026/2/26
5
+ * @version v0.0.1
6
+ *
7
+ * 江湖的业务千篇一律,复杂的代码好几百行。
8
+ */
9
+
10
+ export { createAuth, useAuth } from './useAuth';
11
+ export { createAuthGuard } from './authGuard';
12
+ export { createVCan } from './vCan';
13
+
14
+ export type {
15
+ Permission,
16
+ PermissionScope,
17
+ UserInfo,
18
+ AuthState,
19
+ AuthOptions,
20
+ AuthReturn,
21
+ } from './types';
22
+ export type { AuthGuardOptions, BeforeEachGuard } from './authGuard';
23
+ export type { CanBinding, CanBindingObject } from './vCan';
@@ -0,0 +1,109 @@
1
+ /**
2
+ * @description 权限系统类型定义
3
+ * @author 阿怪
4
+ * @date 2026/2/26
5
+ * @version v0.0.1
6
+ *
7
+ * 江湖的业务千篇一律,复杂的代码好几百行。
8
+ */
9
+
10
+ // ────────────────────────────────────────────────────────────────────────────
11
+ // 权限域类型
12
+ // ────────────────────────────────────────────────────────────────────────────
13
+
14
+ /**
15
+ * 权限范围层级,从低到高:own < department < all
16
+ * '*' 表示通配,匹配任意 scope
17
+ */
18
+ export type PermissionScope = 'own' | 'department' | 'all' | '*';
19
+
20
+ /** 单条权限记录 */
21
+ export interface Permission {
22
+ /** 资源标识,如 'order'、'user'、'report' */
23
+ resource: string;
24
+ /** 操作标识,如 'read'、'create'、'update'、'delete','*' 表示所有操作 */
25
+ action: string;
26
+ /** 数据范围,不传时表示不受范围限制 */
27
+ scope?: PermissionScope;
28
+ }
29
+
30
+ // ────────────────────────────────────────────────────────────────────────────
31
+ // 用户信息
32
+ // ────────────────────────────────────────────────────────────────────────────
33
+
34
+ /** 当前登录用户信息 */
35
+ export interface UserInfo {
36
+ id: string | number;
37
+ username: string;
38
+ /** 业务扩展字段 */
39
+ [key: string]: unknown;
40
+ }
41
+
42
+ // ────────────────────────────────────────────────────────────────────────────
43
+ // Auth 状态
44
+ // ────────────────────────────────────────────────────────────────────────────
45
+
46
+ /** Auth 响应式状态(内部使用) */
47
+ export interface AuthState {
48
+ user: UserInfo | null;
49
+ permissions: Permission[];
50
+ token: string | null;
51
+ }
52
+
53
+ // ────────────────────────────────────────────────────────────────────────────
54
+ // 初始化选项
55
+ // ────────────────────────────────────────────────────────────────────────────
56
+
57
+ /** createAuth 初始化选项 */
58
+ export interface AuthOptions {
59
+ /**
60
+ * 获取当前用户信息及权限列表。
61
+ * login() 和应用初始化时均会调用此函数。
62
+ */
63
+ fetchUser: () => Promise<{ user: UserInfo; permissions: Permission[]; token: string }>;
64
+ /**
65
+ * 未登录时的处理回调(通常用于跳转登录页)。
66
+ * 不传时什么都不做。
67
+ */
68
+ onUnauthorized?: () => void;
69
+ /**
70
+ * 持久化存储类型。
71
+ * - 'local':使用 localStorage,支持跨 tab 同步
72
+ * - 'session':使用 sessionStorage
73
+ * - false(默认):纯内存,不持久化
74
+ */
75
+ storage?: 'local' | 'session' | false;
76
+ }
77
+
78
+ // ────────────────────────────────────────────────────────────────────────────
79
+ // useAuth 返回类型
80
+ // ────────────────────────────────────────────────────────────────────────────
81
+
82
+ import type { ComputedRef } from 'vue';
83
+
84
+ /** useAuth() 的返回值类型 */
85
+ export interface AuthReturn {
86
+ /** 当前用户,未登录时为 null */
87
+ user: ComputedRef<UserInfo | null>;
88
+ /** 当前权限列表 */
89
+ permissions: ComputedRef<Permission[]>;
90
+ /** 当前 token,未登录时为 null */
91
+ token: ComputedRef<string | null>;
92
+ /** 是否已认证(user 非 null 且 token 非 null) */
93
+ isAuthenticated: ComputedRef<boolean>;
94
+ /**
95
+ * 检查是否有指定权限。
96
+ * @param resource 资源标识
97
+ * @param action 操作标识
98
+ * @param scope 数据范围(不传时不检查 scope 层级)
99
+ */
100
+ can: (resource: string, action: string, scope?: PermissionScope) => boolean;
101
+ /** 调用 fetchUser 完成登录,更新 auth 状态 */
102
+ login: () => Promise<void>;
103
+ /** 直接注入登录结果到 auth 状态(供 defineUserStore 等外部流程使用) */
104
+ loginWith: (result: { user: UserInfo; permissions: Permission[]; token: string }) => void;
105
+ /** 清除 auth 状态(登出) */
106
+ logout: () => void;
107
+ /** 从 storage 恢复认证状态,供应用启动时调用 */
108
+ restore: () => Promise<void>;
109
+ }