ch3chi-commons-vue 1.8.0 → 1.8.1

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 (61) hide show
  1. package/package.json +2 -1
  2. package/src/api/ApiService.ts +869 -0
  3. package/src/auth/AuthorizationService.ts +138 -0
  4. package/src/auth/PermissionDescriptor.ts +99 -0
  5. package/src/auth/keys.ts +5 -0
  6. package/src/components/CAlert.vue +188 -0
  7. package/src/components/CAlertDefine.ts +20 -0
  8. package/src/components/CBSToast.vue +119 -0
  9. package/src/components/CGlobalSpinner.vue +84 -0
  10. package/src/components/CImage.vue +67 -0
  11. package/src/components/CRowCheckBox.vue +75 -0
  12. package/src/components/CRowTextInput.vue +27 -0
  13. package/src/components/CTable.vue +524 -0
  14. package/src/components/CTableDefine.ts +566 -0
  15. package/src/components/CTableTD.vue +28 -0
  16. package/src/components/HasPermission.vue +28 -0
  17. package/src/components/form/CChangePasswordFormField.vue +146 -0
  18. package/src/components/form/CCheckBoxFormField.vue +91 -0
  19. package/src/components/form/CCheckBoxPlatFormField.vue +94 -0
  20. package/src/components/form/CDateFormField.vue +149 -0
  21. package/src/components/form/CDateQueryField.vue +111 -0
  22. package/src/components/form/CDateRangeFormField.vue +138 -0
  23. package/src/components/form/CFilePickerFormField.vue +471 -0
  24. package/src/components/form/CRadioFormField.vue +62 -0
  25. package/src/components/form/CRadioPlatFormField.vue +67 -0
  26. package/src/components/form/CSelectFormField.vue +175 -0
  27. package/src/components/form/CTextAreaFormField.vue +84 -0
  28. package/src/components/form/CTextInputFormField.vue +99 -0
  29. package/src/components/form/CTinyMCEEditorFormField.vue +99 -0
  30. package/src/components/form/SCTextInputFormField.vue +129 -0
  31. package/src/composables/useCheckBoxFormField.ts +126 -0
  32. package/src/composables/useRadioFormField.ts +106 -0
  33. package/src/directive/CBootstrapDirective.ts +83 -0
  34. package/src/directive/CDateFormatterDirective.ts +37 -0
  35. package/src/directive/CFTurnstileDirective.ts +46 -0
  36. package/src/directive/CFormDirective.ts +57 -0
  37. package/src/directive/PermissionDirective.ts +102 -0
  38. package/src/env.d.ts +19 -0
  39. package/src/index.ts +83 -0
  40. package/src/model/BSFieldStyleConfig.ts +349 -0
  41. package/src/model/BaseDictionary.ts +86 -0
  42. package/src/model/BaseFormDataModel.ts +623 -0
  43. package/src/model/BaseListViewModel.ts +392 -0
  44. package/src/model/CBSModalViewModel.ts +91 -0
  45. package/src/model/CFileDataModel.ts +181 -0
  46. package/src/model/CImageViewModel.ts +34 -0
  47. package/src/model/CMenuItem.ts +199 -0
  48. package/src/model/EmailReceiverDataModel.ts +149 -0
  49. package/src/model/EmptyDataModel.ts +25 -0
  50. package/src/model/FormOptions.ts +112 -0
  51. package/src/model/LoginDataModel.ts +51 -0
  52. package/src/model/PasswordDataModel.ts +70 -0
  53. package/src/model/QueryParameter.ts +310 -0
  54. package/src/model/SessionUser.ts +110 -0
  55. package/src/model/ShowMessageDataModel.ts +69 -0
  56. package/src/model/TokenUser.ts +157 -0
  57. package/src/stores/FormDataStore.ts +73 -0
  58. package/src/stores/ViewStore.ts +701 -0
  59. package/src/stores/VueSessionStoreInstaller.ts +22 -0
  60. package/src/types/turnstile.d.ts +8 -0
  61. package/src/utils/CToolUtils.ts +133 -0
@@ -0,0 +1,701 @@
1
+ import {ComponentPublicInstance, toRaw} from 'vue'
2
+ import {defineStore, DefineStoreOptions} from 'pinia'
3
+ import CAlert from "../components/CAlert.vue";
4
+ import {CAlertModalData, CAlertModalType} from "../components/CAlertDefine";
5
+ import CBSToast from "../components/CBSToast.vue";
6
+ import CGlobalSpinner from "../components/CGlobalSpinner.vue";
7
+ import {SessionUser} from "../model/SessionUser";
8
+ import {ApiService} from "../api/ApiService";
9
+ import _ from "lodash";
10
+ import {AuthorizationService} from "../auth/AuthorizationService";
11
+ import {CMenuItem} from "../model/CMenuItem";
12
+ import {ShowMessageType} from "../model/ShowMessageDataModel";
13
+ import {v4 as uuidv4} from 'uuid';
14
+ import {AccessToken} from "../model/TokenUser";
15
+ import {BaseDictionary, CBaseDictionary} from "../model/BaseDictionary";
16
+
17
+ // ~ ----------------------------------------------------------
18
+
19
+ export type RouterNavigationType = 'push' | 'back' | 'forward' | 'unknown';
20
+
21
+ /**
22
+ * 全局視圖相關的狀態
23
+ */
24
+ export type GlobalViewState = {
25
+ navigationType: RouterNavigationType;
26
+ isSidebarOpen: boolean;
27
+ mainBSModal: ComponentPublicInstance<typeof CAlert> | null;
28
+ toastView: ComponentPublicInstance<typeof CBSToast> | null;
29
+ globalSpinner: ComponentPublicInstance<typeof CGlobalSpinner> | null;
30
+ version: string;
31
+ }
32
+
33
+ /**
34
+ * 全局視圖相關的行為
35
+ */
36
+ export interface GlobalViewActions {
37
+ routerNavigationType(type: RouterNavigationType): void;
38
+
39
+ toggleSidebar(): void;
40
+
41
+ showModal(data: CAlertModalData): void;
42
+
43
+ showModalError(data: CAlertModalData): void;
44
+
45
+ showModelAlert(data: CAlertModalData): void;
46
+
47
+ showModalConfirm(data: CAlertModalData): void;
48
+
49
+ hideModal(): void;
50
+
51
+ addToast(data: CAlertModalData): void;
52
+
53
+ showSpinner(): void;
54
+
55
+ hideSpinner(): void;
56
+
57
+ setVersion(version: string): void;
58
+ }
59
+
60
+ /**
61
+ * 全局視圖相關的 getters
62
+ */
63
+ export interface GlobalViewGetters {
64
+ // no getters
65
+ }
66
+
67
+ /**
68
+ * 全局視圖 store 定義類型
69
+ */
70
+ export type GlobalViewStoreOptionDefine = Omit<DefineStoreOptions<'view', GlobalViewState, GlobalViewGetters, GlobalViewActions>, 'id'>;
71
+
72
+ /**
73
+ * 全局視圖 store 定義
74
+ */
75
+ export const globalViewStoreOptions: GlobalViewStoreOptionDefine = {
76
+ state: (): GlobalViewState => ({
77
+ navigationType: 'unknown' as RouterNavigationType,
78
+ isSidebarOpen: true,
79
+ mainBSModal: null as ComponentPublicInstance<typeof CAlert> | null,
80
+ toastView: null as ComponentPublicInstance<typeof CBSToast> | null,
81
+ globalSpinner: null as ComponentPublicInstance<typeof CGlobalSpinner> | null,
82
+ version: ''
83
+ }),
84
+ actions: {
85
+ routerNavigationType(type: RouterNavigationType) {
86
+ this.navigationType = type;
87
+ },
88
+ toggleSidebar() {
89
+ this.isSidebarOpen = !this.isSidebarOpen
90
+ },
91
+ showModal(data: CAlertModalData) {
92
+ this.mainBSModal?.show({
93
+ ...data,
94
+ type: CAlertModalType.Info
95
+ })
96
+ },
97
+ showModalError(data: CAlertModalData) {
98
+ this.mainBSModal?.show({
99
+ ...data,
100
+ type: CAlertModalType.Error
101
+ })
102
+ },
103
+ showModelAlert(data: CAlertModalData) {
104
+ this.mainBSModal?.show({
105
+ ...data,
106
+ type: CAlertModalType.Alert
107
+ })
108
+ },
109
+ showModalConfirm(data: CAlertModalData) {
110
+ this.mainBSModal?.show({
111
+ ...data,
112
+ type: CAlertModalType.Confirm
113
+ })
114
+ },
115
+ hideModal() {
116
+ this.mainBSModal?.hide()
117
+ },
118
+ addToast(data: CAlertModalData) {
119
+ this.toastView?.addToast(data);
120
+ },
121
+ showSpinner() {
122
+ this.globalSpinner?.show();
123
+ },
124
+ hideSpinner() {
125
+ this.globalSpinner?.hide();
126
+ },
127
+ setVersion(version: string) {
128
+ this.version = version;
129
+ // 把 version 資訊,儲存到 localStorage
130
+ localStorage.setItem('app_version', version);
131
+ }
132
+ },
133
+ getters: {}
134
+ };
135
+
136
+ /**
137
+ * 保存視圖相關的狀態,例如側邊欄開啟狀態、模態框、Toast、全局加載等
138
+ */
139
+ export const useViewStore = defineStore('view', globalViewStoreOptions);
140
+
141
+ // 防止 Vite/rollup tree-shake 掉 type export
142
+ export const __GlobalViewStoreDefine__ = null as unknown as
143
+ GlobalViewState &
144
+ GlobalViewActions &
145
+ GlobalViewGetters &
146
+ GlobalViewStoreOptionDefine;
147
+
148
+ // ~ ----------------------------------------------------------
149
+ // 使用者會話相關的 store 定義
150
+
151
+ /**
152
+ * 使用者會話相關的狀態
153
+ */
154
+ export type UserSessionState<T extends SessionUser = SessionUser> = {
155
+ user: T | null;
156
+ token?: AccessToken | null;
157
+ isAuth: boolean;
158
+ sessionCheckTimer: number | null;
159
+ shouldRedirectToLogin: boolean | null;
160
+ shouldRedirectToMessage?: boolean;
161
+ redirectMessageType?: ShowMessageType | null;
162
+ }
163
+
164
+ /**
165
+ * 使用者會話相關的行為
166
+ */
167
+ export interface UserSessionActions<T extends SessionUser = SessionUser> {
168
+ // 保存使用者資訊
169
+ saveUser(user: T | null): void;
170
+
171
+ // 保存 token 資訊
172
+ saveToken(token: AccessToken | null): void;
173
+
174
+ // 設定是否已驗證通過
175
+ setAuthenticated(isAuth: boolean): void;
176
+
177
+ // 檢查目前的 session 是否有效
178
+ checkSessionIsValid(): Promise<boolean>;
179
+
180
+ // 使用自訂的 helper 函式驗證 session
181
+ validateSession(helper: () => Promise<T>): Promise<boolean>;
182
+
183
+ // 使用自訂的 helper 函式驗證 token session
184
+ refreshToken(helper: () => Promise<AccessToken>): Promise<boolean>;
185
+
186
+ // 開始定時檢查 session
187
+ startSessionCheck(): void;
188
+
189
+ // 停止定時檢查 session
190
+ stopSessionCheck(): void;
191
+
192
+ // 檢查是否有指定的權限
193
+ hasPermission(needPermission: string | string[]): boolean;
194
+
195
+ // 登出
196
+ logout(): void;
197
+
198
+ // 手動點擊登出
199
+ triggerManualLogout: () => void;
200
+
201
+ // 重置 ShouldRedirectToLogin 狀態
202
+ resetShouldRedirectToLogin: () => void;
203
+
204
+ // token 驗證失敗時觸發重定向,屬於自動行為
205
+ triggerAuthFailedRedirect: () => void;
206
+ // 重置重定向狀態
207
+ resetAuthFailedRedirect: () => void;
208
+ }
209
+
210
+ /**
211
+ * 使用者會話相關的 getters
212
+ */
213
+ export interface UserSessionGetters<T extends SessionUser = SessionUser> {
214
+ // 取得目前使用者物件
215
+ currentUser(state: UserSessionState<T>): T | null;
216
+
217
+ // 取得目前存取權杖
218
+ currentToken(state: UserSessionState<T>): AccessToken | null;
219
+
220
+ // 是否已驗證通過
221
+ isAuthenticated(state: UserSessionState<T>): boolean;
222
+
223
+ // 取得使用者 UID
224
+ userUid(state: UserSessionState<T>): string | undefined;
225
+
226
+ // 取得使用者帳號
227
+ account(state: UserSessionState<T>): string | undefined;
228
+
229
+ // 取得使用者名稱
230
+ userName(state: UserSessionState<T>): string | undefined;
231
+
232
+ // 取得使用者電子郵件
233
+ email(state: UserSessionState<T>): string | undefined;
234
+
235
+ // 取得使用者角色代碼
236
+ roleCode(state: UserSessionState<T>): string | undefined;
237
+
238
+ // 取得使用者權限列表
239
+ permissions(state: UserSessionState<T>): string[] | undefined;
240
+
241
+ // 取得使用者選單項目
242
+ menuItems(): CMenuItem[];
243
+ }
244
+
245
+ /**
246
+ * 使用者會話 store 定義類型
247
+ */
248
+ export type UserSessionStoreOptionDefine<T extends SessionUser = SessionUser> = Omit<DefineStoreOptions<'session', UserSessionState<T>, UserSessionGetters<T>, UserSessionActions<T>>, 'id'>;
249
+
250
+ /**
251
+ * 使用者會話 store 定義
252
+ * 此方式僅適用於 SessionUser 類型,只使用 session 當作帳號權限驗證
253
+ */
254
+ export const userSessionStoreOptions: UserSessionStoreOptionDefine<SessionUser> = {
255
+ state: (): UserSessionState<SessionUser> => ({
256
+ user: null as SessionUser | null, // 使用者資訊
257
+ isAuth: false, // 是否已登入
258
+ sessionCheckTimer: null as number | null, // 用於定時檢查會話的計時器
259
+ shouldRedirectToLogin: false // 是否應該重定向到登入頁面
260
+ }),
261
+ actions: {
262
+ saveUser(user: SessionUser | null) {
263
+ this.user = user;
264
+ this.isAuth = !!user; // 如果 user 不為 null,則 isAuth 為 true
265
+ },
266
+ saveToken(token: AccessToken | null) {
267
+ },
268
+ setAuthenticated(isAuth: boolean) {
269
+ this.isAuth = isAuth;
270
+ },
271
+ // 檢查目前的 session 是否有效
272
+ async checkSessionIsValid(): Promise<boolean> {
273
+ // 呼叫後端 API 以確認 session 是否仍然有效
274
+ const resp = await ApiService.call({endpointKey: 'me'});
275
+ if (!resp.isOk()) {
276
+ return false;
277
+ }
278
+ if (_.isNil(this.user)) {
279
+ this.saveUser(new SessionUser().load(resp.data));
280
+ } else {
281
+ this.user?.merge(resp.data);
282
+ }
283
+ this.isAuth = true;
284
+ return true;
285
+ },
286
+ async validateSession(helper: () => Promise<SessionUser>): Promise<boolean> {
287
+ return helper().then((user) => {
288
+ return !!user;
289
+ });
290
+ },
291
+ async refreshToken(helper: () => Promise<AccessToken>) {
292
+ return Promise.reject('Not implemented');
293
+ },
294
+ // 開始定時檢查 session
295
+ startSessionCheck() {
296
+ if (this.sessionCheckTimer) {
297
+ return;
298
+ }
299
+ this.sessionCheckTimer = window.setInterval(async () => {
300
+ const isValid = this.checkSessionIsValid();
301
+ if (!isValid) {
302
+ this.logout();
303
+ }
304
+ }, 10 * 1000) // 每 5 分鐘檢查一次
305
+ },
306
+ stopSessionCheck() {
307
+ if (this.sessionCheckTimer) {
308
+ clearInterval(this.sessionCheckTimer)
309
+ this.sessionCheckTimer = null
310
+ }
311
+ },
312
+ hasPermission(needPermission: string | string[]) {
313
+ return true;
314
+ },
315
+ logout() {
316
+ this.user = null;
317
+ this.isAuth = false;
318
+ this.stopSessionCheck();
319
+ },
320
+ triggerManualLogout () {
321
+ this.logout();
322
+ this.shouldRedirectToLogin = true;
323
+ },
324
+ resetShouldRedirectToLogin() {
325
+ this.shouldRedirectToLogin = false;
326
+ },
327
+ triggerAuthFailedRedirect() {
328
+ },
329
+ resetAuthFailedRedirect() {
330
+ }
331
+ },
332
+ getters: {
333
+ currentUser: (state): SessionUser | null => {
334
+ return state.user;
335
+ },
336
+ currentToken: (state): AccessToken | null => {
337
+ return state.token || null;
338
+ },
339
+ isAuthenticated: (state) => {
340
+ return state.isAuth;
341
+ },
342
+ userUid: (state) => {
343
+ return _.get(state.user, 'sub', '');
344
+ },
345
+ account: (state) => {
346
+ return _.get(state.user, 'account', '');
347
+ },
348
+ userName: (state) => {
349
+ return _.get(state.user, 'name', '');
350
+ },
351
+ email: (state) => {
352
+ return _.get(state.user, 'email', '');
353
+ },
354
+ roleCode: (state) => {
355
+ return _.get(state.user, 'roleCode', '');
356
+ },
357
+ permissions: (state) => {
358
+ return _.get(state.user, 'permissions', []);
359
+ },
360
+ menuItems(): CMenuItem[] {
361
+ return AuthorizationService.provideMenuByPermission({
362
+ roleCode: this.roleCode || '',
363
+ permissions: toRaw(this.permissions) || []
364
+ });
365
+ },
366
+ }
367
+ }
368
+
369
+ /**
370
+ * 持久化設定選項
371
+ */
372
+ export interface PersistOptions<T extends SessionUser = SessionUser> {
373
+ enabled?: boolean;
374
+ key?: string;
375
+ storage?: Storage;
376
+ paths?: string[];
377
+ userConstructor?: new () => T;
378
+ serializer?: {
379
+ serialize: (state: any) => string;
380
+ deserialize: (str: string) => any;
381
+ };
382
+ // 事件回調
383
+ onLogout?: (user: T | null, token: AccessToken | null) => void;
384
+ onLogin?: (user: T) => void;
385
+ }
386
+
387
+ /**
388
+ * 創建使用者會話 store 選項的工廠函數,支援自訂的 SessionUser 類型
389
+ * @param persistOptions
390
+ */
391
+ export function createUserSessionOptions<T extends SessionUser = SessionUser>(
392
+ persistOptions?: PersistOptions<T>
393
+ ): UserSessionStoreOptionDefine<T> {
394
+ // 定義 user class 建構子
395
+ const UserClassConstructor = persistOptions?.userConstructor || (SessionUser as new () => T);
396
+
397
+ // 使用者會話 store 定義
398
+ const options: UserSessionStoreOptionDefine<T> = {
399
+ state: (): UserSessionState<T> => ({
400
+ user: null as T | null,
401
+ token: null as AccessToken | null,
402
+ isAuth: false,
403
+ sessionCheckTimer: null as number | null,
404
+ shouldRedirectToLogin: false,
405
+ shouldRedirectToMessage: false,
406
+ redirectMessageType: null,
407
+ }),
408
+ actions: {
409
+ saveUser(user: T | null) {
410
+ this.user = user as any;
411
+
412
+ // 當 user 不為 null 時,觸發 onLogin 回調
413
+ if (user && persistOptions?.onLogin) {
414
+ persistOptions.onLogin(toRaw(user));
415
+ }
416
+ },
417
+ saveToken(token: AccessToken | null) {
418
+ this.token = token || null;
419
+ },
420
+ setAuthenticated(isAuth: boolean) {
421
+ this.isAuth = isAuth;
422
+ },
423
+ async checkSessionIsValid(): Promise<boolean> {
424
+ const resp = await ApiService.call({endpointKey: 'me'});
425
+ if (!resp.isOk()) {
426
+ return false;
427
+ }
428
+ if (_.isNil(this.user)) {
429
+ this.saveUser(new SessionUser().load(resp.data) as T);
430
+ } else {
431
+ this.user?.merge(resp.data);
432
+ }
433
+ this.isAuth = true;
434
+ return true;
435
+ },
436
+ async validateSession(helper: () => Promise<T>): Promise<boolean> {
437
+ if (_.isNil(this.user)) {
438
+ this.isAuth = false;
439
+ return Promise.resolve(false);
440
+ }
441
+ return helper()
442
+ .then((userData) => {
443
+ const r = !!userData;``
444
+ this.isAuth = r;
445
+ this.user!.loadFromValidateSession?.(userData as Record<string, any>);
446
+ return r;
447
+ });
448
+ },
449
+ async refreshToken(helper: () => Promise<AccessToken>) {
450
+ return helper().then((tokenData) => {
451
+ const isValid = !!tokenData && !tokenData.isExpired();
452
+ this.isAuth = isValid;
453
+ if (isValid) {
454
+ this.saveToken(tokenData);
455
+ }
456
+ return isValid;
457
+ });
458
+ },
459
+ startSessionCheck() {
460
+ },
461
+ stopSessionCheck() {
462
+ },
463
+ hasPermission(needPermission: string | string[]) {
464
+ // 取得目前的 permissions
465
+ const permissions = toRaw(this.permissions) || [];
466
+ if(_.isEmpty(permissions)) {
467
+ return false;
468
+ }
469
+ const neededPermissions = new Set(_.castArray(permissions));
470
+ if (_.isEmpty(neededPermissions)) {
471
+ return false;
472
+ }
473
+ // 檢查是否有任一需要的權限
474
+ return _.some(permissions, perm => neededPermissions.has(perm));
475
+ },
476
+ logout() {
477
+ // 觸發登出前的事件,讓外部可以進行額外處理
478
+ const beforeLogoutEvent = new CustomEvent('user:before-logout', {
479
+ detail: { user: this.user, token: this.token }
480
+ });
481
+ window.dispatchEvent(beforeLogoutEvent);
482
+
483
+ // 執行自定義的 onLogout 回調
484
+ if (persistOptions?.onLogout) {
485
+ persistOptions.onLogout(toRaw(this.user) as T, toRaw(this.token) || null);
486
+ }
487
+
488
+ // 執行登出邏輯
489
+ this.user = null;
490
+ this.token = null;
491
+ this.isAuth = false;
492
+
493
+ // 觸發登出後的事件
494
+ const afterLogoutEvent = new CustomEvent('user:after-logout', {
495
+ detail: { timestamp: Date.now() }
496
+ });
497
+ window.dispatchEvent(afterLogoutEvent);
498
+ },
499
+ triggerManualLogout() {
500
+ this.logout();
501
+ this.shouldRedirectToLogin = true;
502
+ },
503
+ resetShouldRedirectToLogin() {
504
+ this.shouldRedirectToLogin = false;
505
+ },
506
+ triggerAuthFailedRedirect() {
507
+ this.logout();
508
+ this.shouldRedirectToMessage = true;
509
+ this.redirectMessageType = ShowMessageType.AUTH_EXPIRED;
510
+ },
511
+ resetAuthFailedRedirect() {
512
+ this.shouldRedirectToMessage = false;
513
+ this.redirectMessageType = null;
514
+ }
515
+ },
516
+ getters: {
517
+ currentUser: (state: UserSessionState<T>): T | null => {
518
+ return state.user as T | null;
519
+ },
520
+ currentToken: (state: UserSessionState<T>): AccessToken | null => {
521
+ return state.token || null;
522
+ },
523
+ isAuthenticated: (state: UserSessionState<T>) => {
524
+ return state.isAuth;
525
+ },
526
+ userUid: (state: UserSessionState<T>) => {
527
+ return _.get(state.user, 'userUid', '');
528
+ },
529
+ account: (state: UserSessionState<T>) => {
530
+ return _.get(state.user, 'account', '');
531
+ },
532
+ userName: (state: UserSessionState<T>) => {
533
+ return _.get(state.user, 'name', '');
534
+ },
535
+ email: (state: UserSessionState<T>) => {
536
+ return _.get(state.user, 'email', '');
537
+ },
538
+ roleCode: (state: UserSessionState<T>) => {
539
+ return _.get(state.user, 'roleCode', '');
540
+ },
541
+ permissions: (state: UserSessionState<T>) => {
542
+ return _.get(state.user, 'permissions', []);
543
+ },
544
+ menuItems(): CMenuItem[] {
545
+ return AuthorizationService.provideMenuByPermission({
546
+ roleCode: this.roleCode || '',
547
+ permissions: toRaw(this.permissions) || []
548
+ });
549
+ },
550
+ } as any,
551
+ // 添加持久化設定
552
+ ...(persistOptions?.enabled && {
553
+ persist: {
554
+ key: persistOptions.key || `user-session-${uuidv4()}`,
555
+ storage: persistOptions.storage || localStorage,
556
+ paths: persistOptions.paths || ['user', 'token', 'isAuth'],
557
+ serializer: persistOptions.serializer || {
558
+ serialize: (state: UserSessionState<T>): string => {
559
+ return JSON.stringify({
560
+ user: state.user?.toJSON(),
561
+ token: state.token?.toJSON(),
562
+ isAuth: state.isAuth
563
+ });
564
+ },
565
+ deserialize: (str: string): Partial<UserSessionState<T>> => {
566
+ const data = JSON.parse(str);
567
+ return {
568
+ user: data.user ? new UserClassConstructor().loadJSON(data.user) as T : null,
569
+ token: data.token ? new AccessToken().loadJSON(data.token) : null,
570
+ isAuth: data.isAuth
571
+ };
572
+ }
573
+ }
574
+ }
575
+ })
576
+ };
577
+ return options;
578
+ }
579
+ /**
580
+ * 創建使用者會話 store 的工廠函數,支援自訂的 SessionUser 類型
581
+ */
582
+ export function createUserSessionStore<T extends SessionUser = SessionUser>(
583
+ storeId: string = 'session',
584
+ persistOptions?: PersistOptions<T>
585
+ ) {
586
+ const options: UserSessionStoreOptionDefine<T> = createUserSessionOptions<T>(persistOptions);
587
+ return defineStore(storeId, options);
588
+ }
589
+
590
+ /**
591
+ * 預設的使用者會話 store(使用 SessionUser)
592
+ */
593
+ export const useUserSessionStore = createUserSessionStore<SessionUser>();
594
+
595
+
596
+ // 防止 Vite/rollup tree-shake 掉 type export
597
+ export const __UserSessionTypes__ = null as unknown as
598
+ UserSessionState &
599
+ UserSessionGetters &
600
+ UserSessionActions &
601
+ UserSessionStoreOptionDefine &
602
+ PersistOptions;
603
+
604
+ // ~ ----------------------------------------------------------
605
+ // ~ Dictionary Store 定義
606
+
607
+ export interface DictionaryPersistOptions<T extends BaseDictionary = CBaseDictionary> {
608
+ enabled?: boolean;
609
+ key?: string;
610
+ storage?: Storage;
611
+ paths?: string[];
612
+ dictConstructor?: new () => T;
613
+ serializer?: {
614
+ serialize: (state: any) => string;
615
+ deserialize: (str: string) => any;
616
+ };
617
+ }
618
+
619
+ export type DictionaryState<T extends BaseDictionary = CBaseDictionary> = {
620
+ dictionary: T | null;
621
+ }
622
+
623
+ export interface DictionaryActions<T extends BaseDictionary = CBaseDictionary> {
624
+ setDictionary(dict: T): void;
625
+ loadAll(): void;
626
+ loadDictionary(key: string): void;
627
+ }
628
+
629
+ export interface DictionaryGetters<T extends BaseDictionary = CBaseDictionary> {
630
+ dict(): T | null;
631
+ }
632
+
633
+ /**
634
+ * Dictionary store 定義類型
635
+ */
636
+ export type DictionaryStoreOptionDefine<T extends BaseDictionary = CBaseDictionary> = Omit<DefineStoreOptions<'dictionary', DictionaryState<T>, DictionaryGetters<T>, DictionaryActions<T>>, 'id'>;
637
+
638
+ /**
639
+ * 創建 Dictionary store 選項的工廠函數
640
+ * @param dictConstructor
641
+ */
642
+ export function createDictionaryStoreOptions<T extends BaseDictionary = CBaseDictionary>(
643
+ options : {
644
+ dictionary?: T,
645
+ persistOptions?: DictionaryPersistOptions<T>
646
+ }
647
+ ): DictionaryStoreOptionDefine<T> {
648
+
649
+ const dictConstructor = options.persistOptions?.dictConstructor || (CBaseDictionary as new () => T);
650
+
651
+ return {
652
+ state: (): DictionaryState<T> => ({
653
+ dictionary: options.dictionary as T | null
654
+ }),
655
+ actions: {
656
+ setDictionary(dict: T | null) {
657
+ this.dictionary = dict as any;
658
+ },
659
+ loadAll() {
660
+ this.dictionary?.loadAll();
661
+ },
662
+ async loadDictionary(key: string) {
663
+ await this.dictionary?.loadDictionary(key);
664
+ }
665
+ },
666
+ getters: {
667
+ dict(): T | null {
668
+ return toRaw(this.dictionary) as T || null;
669
+ }
670
+ },
671
+ // 添加持久化設定
672
+ ...(options.persistOptions?.enabled && {
673
+ persist: {
674
+ key: options.persistOptions.key || `dictionary-store`,
675
+ storage: options.persistOptions.storage || localStorage,
676
+ paths: options.persistOptions.paths || ['dictionary'],
677
+ serializer: options.persistOptions.serializer || {
678
+ serialize: (state: DictionaryState<T>): string => {
679
+ return JSON.stringify({
680
+ dictionary: state.dictionary
681
+ });
682
+ },
683
+ deserialize: (str: string): Partial<DictionaryState<T>> => {
684
+ const data = JSON.parse(str);
685
+ return {
686
+ dictionary: new dictConstructor().loadFromStore(data.dictionary) as T
687
+ };
688
+ }
689
+ }
690
+ }
691
+ })
692
+ };
693
+ }
694
+
695
+ export const __DictionaryStoreTypes__ = null as unknown as
696
+ DictionaryState &
697
+ DictionaryActions &
698
+ DictionaryGetters &
699
+ DictionaryStoreOptionDefine;
700
+
701
+ // ~ ----------------------------------------------------------