@pubinfo/core 2.1.0 → 2.1.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.
- package/dist/{AppSetting-Dzj6YBgW.js → AppSetting-CmT5_15W.js} +15 -15
- package/dist/{HCheckList.vue_vue_type_script_setup_true_lang-OWv01eXk.js → HCheckList.vue_vue_type_script_setup_true_lang-CHzkJth7.js} +1 -1
- package/dist/{HToggle-BnESuuPG.js → HToggle-DpDYLh8y.js} +1 -1
- package/dist/HeaderThinMenu-D6jF8yl1.js +4 -0
- package/dist/{PreferencesContent-DBKQJ8Ob.js → PreferencesContent-AXqWatOF.js} +4 -4
- package/dist/{SettingBreadcrumb-DmP1nJLZ.js → SettingBreadcrumb-CBWdS_nO.js} +3 -3
- package/dist/{SettingCopyright-C8_F7BB2.js → SettingCopyright-D5Jhdu1J.js} +2 -2
- package/dist/{SettingEnableTransition-BeJMq6ny.js → SettingEnableTransition-DQJozSZH.js} +2 -2
- package/dist/{SettingHome-yGmbxzJm.js → SettingHome-DAwn9Ypx.js} +2 -2
- package/dist/{SettingMenu-yWnqZqmz.js → SettingMenu-DEQRAtWl.js} +3 -3
- package/dist/{SettingMode-B8fgUhvi.js → SettingMode-bh3I8UBZ.js} +1 -1
- package/dist/{SettingNavSearch--yQEXqEL.js → SettingNavSearch-iYc-eRzY.js} +2 -2
- package/dist/{SettingOther-CSs1_D9L.js → SettingOther-C2TS6okv.js} +2 -2
- package/dist/{SettingPage-Pm7KblGG.js → SettingPage-B2_SNyn5.js} +2 -2
- package/dist/{SettingTabbar-DM5hdlQf.js → SettingTabbar-BETdKJxz.js} +3 -3
- package/dist/{SettingThemes-DqygGgDK.js → SettingThemes-ComNCP3P.js} +12 -11
- package/dist/{SettingToolbar-BP-yWiKo.js → SettingToolbar-D0DTBbbb.js} +2 -2
- package/dist/{SettingTopbar-Dd_SXy2e.js → SettingTopbar-BcO5Hcxm.js} +3 -3
- package/dist/{SettingWidthMode-DOtJg5uB.js → SettingWidthMode-wzTMq96A.js} +1 -1
- package/dist/built-in/pinia-plugin/plugins/persist.d.ts +2 -2
- package/dist/built-in/pinia-plugin/plugins/persistedstate/index.d.ts +3 -0
- package/dist/built-in/pinia-plugin/plugins/persistedstate/persistedstate.d.ts +15 -0
- package/dist/built-in/pinia-plugin/plugins/persistedstate/types.d.ts +150 -0
- package/dist/built-in/pinia-plugin/plugins/persistedstate/utils.d.ts +17 -0
- package/dist/core/ctx.d.ts +1 -0
- package/dist/core/interface.d.ts +26 -1
- package/dist/core/utils/index.d.ts +2 -0
- package/dist/features/composables/index.d.ts +1 -0
- package/dist/features/composables/partyLogin.d.ts +7 -1
- package/dist/{index-npW0xuOi.js → index-6W8u4oWQ.js} +1 -1
- package/dist/{index-igxVB1m3.js → index-B9i7R1pn.js} +2 -2
- package/dist/{index-C88VclMA.js → index-BXLF9xfN.js} +16300 -15834
- package/dist/{index-CnbD4YWA.js → index-C5X0cH7a.js} +3 -3
- package/dist/{index--xVOZLmn.js → index-CMSPnrUx.js} +1 -1
- package/dist/index-DSKHePRb.js +4 -0
- package/dist/{index-D8kKHmiD.js → index-FATjHAwl.js} +1 -1
- package/dist/{index-BSTB6EhQ.js → index-_VKoUSGo.js} +1 -1
- package/dist/index.d.ts +2 -1
- package/dist/index.js +13 -12
- package/dist/{pick-l2RrF3SE.js → pick-BvMRfqim.js} +1 -1
- package/dist/utils/index.d.ts +0 -1
- package/package.json +9 -8
- package/src/built-in/pinia-plugin/index.ts +2 -2
- package/src/built-in/pinia-plugin/plugins/persist.ts +15 -4
- package/src/built-in/pinia-plugin/plugins/persistedstate/README.md +551 -0
- package/src/built-in/pinia-plugin/plugins/persistedstate/index.ts +3 -0
- package/src/built-in/pinia-plugin/plugins/persistedstate/persistedstate.ts +575 -0
- package/src/built-in/pinia-plugin/plugins/persistedstate/types.ts +162 -0
- package/src/built-in/pinia-plugin/plugins/persistedstate/utils.ts +46 -0
- package/src/core/ctx.ts +24 -1
- package/src/core/interface.ts +31 -1
- package/src/core/resolver/icon.ts +1 -1
- package/src/core/utils/index.ts +2 -0
- package/src/features/composables/index.ts +1 -0
- package/src/features/composables/partyLogin.ts +180 -38
- package/src/features/context/index.ts +1 -1
- package/src/index.ts +7 -5
- package/src/utils/index.ts +0 -1
- package/src/utils/proxy.ts +1 -1
- package/types/index.d.ts +1 -0
- package/types/pinia.d.ts +94 -0
- package/dist/HeaderThinMenu-Co2S6vIB.js +0 -4
- package/dist/index-GSKGoyv_.js +0 -4
- /package/dist/{utils → core/utils}/global.d.ts +0 -0
- /package/dist/core/{utils.d.ts → utils/utils.d.ts} +0 -0
- /package/src/{utils → core/utils}/global.ts +0 -0
- /package/src/core/{utils.ts → utils/utils.ts} +0 -0
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
import type { PiniaPluginContext, StateTree } from 'pinia';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* 存储区域类型:本地存储或会话存储
|
|
5
|
+
*/
|
|
6
|
+
export type StorageArea = 'local' | 'session';
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* 类似 Storage 的接口,用于抽象存储操作
|
|
10
|
+
*/
|
|
11
|
+
export interface StorageLike {
|
|
12
|
+
/** 获取指定键的值 */
|
|
13
|
+
getItem: (key: string) => string | null
|
|
14
|
+
/** 设置指定键的值 */
|
|
15
|
+
setItem: (key: string, value: string) => void
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* 序列化器接口,用于状态的序列化和反序列化
|
|
20
|
+
*/
|
|
21
|
+
export interface Serializer {
|
|
22
|
+
/** 将状态对象序列化为字符串 */
|
|
23
|
+
serialize: (data: StateTree) => string
|
|
24
|
+
/** 将字符串反序列化为状态对象 */
|
|
25
|
+
deserialize: (data: string) => StateTree
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* 持久化配置接口
|
|
30
|
+
*/
|
|
31
|
+
export interface Persistence<State extends StateTree = StateTree> {
|
|
32
|
+
/** 存储键名 */
|
|
33
|
+
key: string
|
|
34
|
+
/** 是否开启调试模式 */
|
|
35
|
+
debug: boolean
|
|
36
|
+
/** 存储实例 */
|
|
37
|
+
storage: StorageLike
|
|
38
|
+
/** 序列化器 */
|
|
39
|
+
serializer: Serializer
|
|
40
|
+
/** 恢复数据前的钩子 */
|
|
41
|
+
beforeHydrate?: (context: PiniaPluginContext) => void
|
|
42
|
+
/** 恢复数据后的钩子 */
|
|
43
|
+
afterHydrate?: (context: PiniaPluginContext) => void
|
|
44
|
+
/** 要持久化的状态字段(白名单) */
|
|
45
|
+
pick?: string[]
|
|
46
|
+
/** 不需要持久化的状态字段(黑名单) */
|
|
47
|
+
omit?: string[]
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* 插件基础配置选项
|
|
52
|
+
*/
|
|
53
|
+
export interface BasePluginOptions {
|
|
54
|
+
/** 存储实例 */
|
|
55
|
+
storage?: StorageLike
|
|
56
|
+
/** 序列化器 */
|
|
57
|
+
serializer?: Serializer
|
|
58
|
+
/** 是否开启调试模式 */
|
|
59
|
+
debug?: boolean
|
|
60
|
+
/** 存储键名生成函数 */
|
|
61
|
+
key?: (storeKey: string) => string
|
|
62
|
+
/** 是否自动为所有 store 启用持久化 */
|
|
63
|
+
auto?: boolean
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* 持久化选项类型(提供给 store 使用)
|
|
68
|
+
*/
|
|
69
|
+
export type PersistenceOptions<State extends StateTree = StateTree> = Partial<Omit<Persistence<State>, 'key'>> & {
|
|
70
|
+
/** 自定义存储键名 */
|
|
71
|
+
key?: string
|
|
72
|
+
};
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* 扩展的持久化选项,包含存储区域配置
|
|
76
|
+
*/
|
|
77
|
+
export type ExtendedPersistenceOptions<State extends StateTree = StateTree> = PersistenceOptions<State> & {
|
|
78
|
+
/** 存储区域类型 */
|
|
79
|
+
storageArea?: StorageArea
|
|
80
|
+
};
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* 自定义存储事件详情
|
|
84
|
+
*/
|
|
85
|
+
export interface CustomStorageEventDetail {
|
|
86
|
+
/** 变化的键名,null 表示清空操作 */
|
|
87
|
+
key: string | null
|
|
88
|
+
/** 新值 */
|
|
89
|
+
newValue: string | null
|
|
90
|
+
/** 旧值 */
|
|
91
|
+
oldValue: string | null
|
|
92
|
+
/** 存储区域 */
|
|
93
|
+
storageArea: StorageArea
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
/**
|
|
97
|
+
* 标准化后的全局配置选项
|
|
98
|
+
*/
|
|
99
|
+
export interface NormalizedGlobalOptions {
|
|
100
|
+
/** 键名生成函数 */
|
|
101
|
+
key: (storeKey: string) => string
|
|
102
|
+
/** 序列化器 */
|
|
103
|
+
serializer: Serializer
|
|
104
|
+
/** 存储实例 */
|
|
105
|
+
storage?: StorageLike
|
|
106
|
+
/** 存储区域 */
|
|
107
|
+
storageArea?: StorageArea
|
|
108
|
+
/** 是否开启调试 */
|
|
109
|
+
debug: boolean
|
|
110
|
+
/** 是否自动持久化 */
|
|
111
|
+
auto: boolean
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
/**
|
|
115
|
+
* 持久化条目,包含完整的配置信息
|
|
116
|
+
*/
|
|
117
|
+
export interface PersistenceEntry<State extends StateTree = StateTree> {
|
|
118
|
+
/** 存储键名 */
|
|
119
|
+
key: string
|
|
120
|
+
/** 存储实例 */
|
|
121
|
+
storage: StorageLike
|
|
122
|
+
/** 序列化器 */
|
|
123
|
+
serializer: Serializer
|
|
124
|
+
/** 存储区域 */
|
|
125
|
+
storageArea?: StorageArea
|
|
126
|
+
/** 完整的存储键名(用于同步监听) */
|
|
127
|
+
fullKey?: string
|
|
128
|
+
/** 是否开启调试 */
|
|
129
|
+
debug: boolean
|
|
130
|
+
/** 恢复前钩子 */
|
|
131
|
+
beforeHydrate?: (context: PiniaPluginContext) => void
|
|
132
|
+
/** 恢复后钩子 */
|
|
133
|
+
afterHydrate?: (context: PiniaPluginContext) => void
|
|
134
|
+
/** 白名单字段 */
|
|
135
|
+
pick?: PersistenceOptions<State>['pick']
|
|
136
|
+
/** 黑名单字段 */
|
|
137
|
+
omit?: PersistenceOptions<State>['omit']
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
/**
|
|
141
|
+
* 存储绑定,关联 store 和持久化配置
|
|
142
|
+
*/
|
|
143
|
+
export interface StorageBinding<State extends StateTree = StateTree> {
|
|
144
|
+
/** Pinia 插件上下文 */
|
|
145
|
+
context: PiniaPluginContext
|
|
146
|
+
/** 持久化条目 */
|
|
147
|
+
entry: PersistenceEntry<State>
|
|
148
|
+
/** 跳过持久化次数(用于避免循环) */
|
|
149
|
+
skipPersist: number
|
|
150
|
+
/** 停止订阅的函数 */
|
|
151
|
+
stopSubscription: () => void
|
|
152
|
+
/** 初始状态快照 */
|
|
153
|
+
initialState: StateTree
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
/**
|
|
157
|
+
* 同步持久化状态插件的配置选项
|
|
158
|
+
*/
|
|
159
|
+
export interface SyncedPersistedStateOptions extends BasePluginOptions {
|
|
160
|
+
/** 存储区域 */
|
|
161
|
+
storageArea?: StorageArea
|
|
162
|
+
}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import type { CustomStorageEventDetail, StorageArea } from './types';
|
|
2
|
+
|
|
3
|
+
/** 自定义事件名称,用于跨标签页通信 */
|
|
4
|
+
export const CUSTOM_EVENT = 'pubinfo:persistedstate:local';
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* 调度微任务,优先使用原生 queueMicrotask
|
|
8
|
+
*/
|
|
9
|
+
export const scheduleMicrotask = typeof queueMicrotask === 'function'
|
|
10
|
+
? queueMicrotask
|
|
11
|
+
: (fn: () => void) => {
|
|
12
|
+
Promise.resolve().then(fn);
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* 解析存储区域类型
|
|
17
|
+
* @param storage - Storage 实例
|
|
18
|
+
* @returns 存储区域类型或 undefined
|
|
19
|
+
*/
|
|
20
|
+
export function resolveStorageArea(storage: Storage | null | undefined): StorageArea | undefined {
|
|
21
|
+
if (!storage || typeof window === 'undefined') {
|
|
22
|
+
return undefined;
|
|
23
|
+
}
|
|
24
|
+
try {
|
|
25
|
+
if (storage === window.localStorage) {
|
|
26
|
+
return 'local';
|
|
27
|
+
}
|
|
28
|
+
if (storage === window.sessionStorage) {
|
|
29
|
+
return 'session';
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
catch {
|
|
33
|
+
return undefined;
|
|
34
|
+
}
|
|
35
|
+
return undefined;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* 分发本地 storage 变更事件
|
|
40
|
+
*/
|
|
41
|
+
export function dispatchLocalMutation(detail: CustomStorageEventDetail) {
|
|
42
|
+
if (typeof window === 'undefined' || typeof window.CustomEvent !== 'function' || typeof window.dispatchEvent !== 'function') {
|
|
43
|
+
return;
|
|
44
|
+
}
|
|
45
|
+
window.dispatchEvent(new CustomEvent<CustomStorageEventDetail>(CUSTOM_EVENT, { detail }));
|
|
46
|
+
}
|
package/src/core/ctx.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { createHooks } from 'hookable';
|
|
2
|
-
import { getPubinfoNamespace } from '
|
|
2
|
+
import { getPubinfoNamespace } from './utils';
|
|
3
3
|
|
|
4
4
|
const HOOKS_KEY = '__pubinfo_core_hooks__';
|
|
5
5
|
type HookStore = ReturnType<typeof createHooks>;
|
|
@@ -21,3 +21,26 @@ export async function callHookAsync<T>(hookname: string, data: T) {
|
|
|
21
21
|
data,
|
|
22
22
|
);
|
|
23
23
|
}
|
|
24
|
+
|
|
25
|
+
export function callHookSync<T>(hookname: string, data: T) {
|
|
26
|
+
return hooks.callHookWith(
|
|
27
|
+
(hookFns, args) => {
|
|
28
|
+
let res = args[0] as T;
|
|
29
|
+
if (!hookFns.length) {
|
|
30
|
+
return res;
|
|
31
|
+
}
|
|
32
|
+
for (const fn of hookFns) {
|
|
33
|
+
const output = fn(res);
|
|
34
|
+
if (output && typeof (output as PromiseLike<unknown>).then === 'function') {
|
|
35
|
+
throw new Error(`[hooks] "${hookname}" 当前仅支持同步回调`);
|
|
36
|
+
}
|
|
37
|
+
if (typeof output !== 'undefined') {
|
|
38
|
+
res = output as T;
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
return res;
|
|
42
|
+
},
|
|
43
|
+
hookname,
|
|
44
|
+
data,
|
|
45
|
+
);
|
|
46
|
+
}
|
package/src/core/interface.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import type { AdapterCreateOptions, AlovaAxiosRequestConfig } from '@alova/adapter-axios';
|
|
2
2
|
import type { Alova, AlovaGenerics, AlovaOptions, Method, ResponseCompleteHandler, ResponseErrorHandler } from 'alova';
|
|
3
3
|
import type { AxiosResponse, AxiosResponseHeaders } from 'axios';
|
|
4
|
-
import type { Pinia } from 'pinia';
|
|
4
|
+
import type { Pinia, StateTree } from 'pinia';
|
|
5
5
|
import type { App, Component } from 'vue';
|
|
6
6
|
import type { Router, RouterOptions as VueRouterOptions } from 'vue-router';
|
|
7
7
|
import type { MenuData, RouteData } from '@/features';
|
|
@@ -100,6 +100,12 @@ export interface Hooks {
|
|
|
100
100
|
|
|
101
101
|
/** 路由数据转换 */
|
|
102
102
|
'route:transform': (routeData: RouteData[]) => RouteData[] | Promise<RouteData[]>
|
|
103
|
+
|
|
104
|
+
/** Pinia 持久化写入前处理(需同步返回) */
|
|
105
|
+
'pinia:persist:write': (payload: PiniaPersistWriteHookPayload) => PiniaPersistWriteHookPayload
|
|
106
|
+
|
|
107
|
+
/** Pinia 持久化读取前处理(需同步返回) */
|
|
108
|
+
'pinia:persist:read': (payload: PiniaPersistReadHookPayload) => PiniaPersistReadHookPayload
|
|
103
109
|
}
|
|
104
110
|
|
|
105
111
|
// overwrite alova
|
|
@@ -110,3 +116,27 @@ export interface RespondedHandlerRecord<AG extends AlovaGenerics> {
|
|
|
110
116
|
onError?: ResponseErrorHandler<AG>
|
|
111
117
|
onComplete?: ResponseCompleteHandler<AG>
|
|
112
118
|
}
|
|
119
|
+
|
|
120
|
+
export interface PiniaPersistHookBasePayload {
|
|
121
|
+
storeId: string
|
|
122
|
+
/** 实际存储键 */
|
|
123
|
+
key: string
|
|
124
|
+
/** local 或 session */
|
|
125
|
+
storageArea?: 'local' | 'session'
|
|
126
|
+
/** 仅在 localStorage 同步时可用 */
|
|
127
|
+
fullKey?: string
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
export interface PiniaPersistWriteHookPayload extends PiniaPersistHookBasePayload {
|
|
131
|
+
/** 序列化后的字符串 */
|
|
132
|
+
value: string
|
|
133
|
+
/** 过滤后的状态对象 */
|
|
134
|
+
state: StateTree
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
export interface PiniaPersistReadHookPayload extends PiniaPersistHookBasePayload {
|
|
138
|
+
/** 从存储读取的原始字符串 */
|
|
139
|
+
value: string
|
|
140
|
+
/** 实际读取数据的键 */
|
|
141
|
+
sourceKey?: string
|
|
142
|
+
}
|
|
@@ -1,28 +1,36 @@
|
|
|
1
|
+
import type { Pinia } from 'pinia';
|
|
1
2
|
import type { PartyLoginConfig, PartyLoginParamItem } from '../components/PartyLogin/type';
|
|
3
|
+
import { getActivePinia } from 'pinia';
|
|
2
4
|
import { ref } from 'vue';
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
useKeepAliveStore,
|
|
7
|
-
useMenuStore,
|
|
8
|
-
useRouteStore,
|
|
9
|
-
useSettingsStore,
|
|
10
|
-
useTabbarStore,
|
|
11
|
-
useUserStore,
|
|
12
|
-
} from '@/features/stores';
|
|
13
|
-
|
|
14
|
-
// Store 名称映射表
|
|
15
|
-
const STORE_MAP: Record<string, () => any> = {
|
|
16
|
-
user: useUserStore,
|
|
17
|
-
settings: useSettingsStore,
|
|
18
|
-
menu: useMenuStore,
|
|
19
|
-
route: useRouteStore,
|
|
20
|
-
tabbar: useTabbarStore,
|
|
21
|
-
iframe: useIframeStore,
|
|
22
|
-
keepAlive: useKeepAliveStore,
|
|
23
|
-
favorites: useFavoritesStore,
|
|
5
|
+
|
|
6
|
+
export {
|
|
7
|
+
PartyLoginConfig,
|
|
24
8
|
};
|
|
25
9
|
|
|
10
|
+
const SUPPORTED_STORE_IDS = new Set([
|
|
11
|
+
'user',
|
|
12
|
+
'settings',
|
|
13
|
+
'menu',
|
|
14
|
+
'route',
|
|
15
|
+
'tabbar',
|
|
16
|
+
'iframe',
|
|
17
|
+
'keepAlive',
|
|
18
|
+
'favorites',
|
|
19
|
+
]);
|
|
20
|
+
|
|
21
|
+
const missingPiniaWarnings = new Set<string>();
|
|
22
|
+
|
|
23
|
+
function warnMissingPinia(storeName: string) {
|
|
24
|
+
if (missingPiniaWarnings.has(storeName)) {
|
|
25
|
+
return;
|
|
26
|
+
}
|
|
27
|
+
missingPiniaWarnings.add(storeName);
|
|
28
|
+
console.warn(
|
|
29
|
+
`[usePartyLogin] Pinia instance is not ready when trying to access the "${storeName}" store. `
|
|
30
|
+
+ 'Provide a Pinia instance via usePartyLogin(..., { pinia }) or ensure Pinia is registered before calling this hook.',
|
|
31
|
+
);
|
|
32
|
+
}
|
|
33
|
+
|
|
26
34
|
/**
|
|
27
35
|
* 根据路径获取对象的嵌套值
|
|
28
36
|
* @param obj 目标对象
|
|
@@ -48,6 +56,11 @@ function getNestedValue(obj: any, path: string): any {
|
|
|
48
56
|
return result ?? '';
|
|
49
57
|
}
|
|
50
58
|
|
|
59
|
+
function getStoreFromPinia(pinia: Pinia | undefined, storeName: string) {
|
|
60
|
+
const storeMap = (pinia as any)?._s as Map<string, any> | undefined;
|
|
61
|
+
return storeMap?.get(storeName);
|
|
62
|
+
}
|
|
63
|
+
|
|
51
64
|
/**
|
|
52
65
|
* 解析参数值
|
|
53
66
|
*
|
|
@@ -95,6 +108,7 @@ function getNestedValue(obj: any, path: string): any {
|
|
|
95
108
|
function resolveParamValue(
|
|
96
109
|
item: PartyLoginParamItem,
|
|
97
110
|
previousResponse?: Record<string, any>,
|
|
111
|
+
pinia?: Pinia,
|
|
98
112
|
): string | number | boolean {
|
|
99
113
|
switch (item.type) {
|
|
100
114
|
case 'string':
|
|
@@ -116,13 +130,23 @@ function resolveParamValue(
|
|
|
116
130
|
}
|
|
117
131
|
|
|
118
132
|
try {
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
133
|
+
if (!SUPPORTED_STORE_IDS.has(storeName)) {
|
|
134
|
+
console.warn(`Store "${storeName}" is not supported in usePartyLogin.`);
|
|
135
|
+
return '';
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
const activePinia = pinia ?? getActivePinia();
|
|
139
|
+
if (!activePinia) {
|
|
140
|
+
warnMissingPinia(storeName);
|
|
141
|
+
return '';
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
const store = getStoreFromPinia(activePinia, storeName);
|
|
145
|
+
if (!store) {
|
|
146
|
+
warnMissingStore(storeName);
|
|
122
147
|
return '';
|
|
123
148
|
}
|
|
124
149
|
|
|
125
|
-
const store = useStore();
|
|
126
150
|
return getNestedValue(store, fieldPath);
|
|
127
151
|
}
|
|
128
152
|
catch (error) {
|
|
@@ -151,12 +175,13 @@ function resolveParamValue(
|
|
|
151
175
|
function buildParams(
|
|
152
176
|
params: PartyLoginParamItem[],
|
|
153
177
|
previousResponse?: Record<string, any>,
|
|
178
|
+
pinia?: Pinia,
|
|
154
179
|
): Record<string, any> {
|
|
155
180
|
const result: Record<string, any> = {};
|
|
156
181
|
|
|
157
182
|
params.forEach((item) => {
|
|
158
183
|
if (item.key) {
|
|
159
|
-
result[item.key] = resolveParamValue(item, previousResponse);
|
|
184
|
+
result[item.key] = resolveParamValue(item, previousResponse, pinia);
|
|
160
185
|
}
|
|
161
186
|
});
|
|
162
187
|
|
|
@@ -173,16 +198,112 @@ function buildOAuthUrl(
|
|
|
173
198
|
baseUrl: string,
|
|
174
199
|
params: PartyLoginParamItem[],
|
|
175
200
|
previousResponse?: Record<string, any>,
|
|
201
|
+
pinia?: Pinia,
|
|
176
202
|
): string {
|
|
177
|
-
const queryParams = buildParams(params, previousResponse);
|
|
203
|
+
const queryParams = buildParams(params, previousResponse, pinia);
|
|
204
|
+
const entries = Object.entries(queryParams);
|
|
205
|
+
|
|
206
|
+
if (entries.length === 0) {
|
|
207
|
+
return safeDecodeUrl(baseUrl);
|
|
208
|
+
}
|
|
178
209
|
|
|
179
|
-
const
|
|
180
|
-
|
|
181
|
-
|
|
210
|
+
const searchParams = new URLSearchParams();
|
|
211
|
+
entries.forEach(([key, value]) => {
|
|
212
|
+
searchParams.append(key, String(value));
|
|
182
213
|
});
|
|
214
|
+
const serializedParams = searchParams.toString();
|
|
215
|
+
|
|
216
|
+
const hashIndex = baseUrl.indexOf('#');
|
|
217
|
+
const hasHash = hashIndex >= 0;
|
|
218
|
+
const basePart = hasHash ? baseUrl.slice(0, hashIndex) : baseUrl;
|
|
219
|
+
const hashContent = hasHash ? baseUrl.slice(hashIndex + 1) : '';
|
|
220
|
+
|
|
221
|
+
if (hasHash && shouldAppendParamsToHash(hashContent)) {
|
|
222
|
+
const hashWithQuery = appendParamsToHash(hashContent, serializedParams);
|
|
223
|
+
const finalUrl = `${basePart}#${hashWithQuery}`;
|
|
224
|
+
return safeDecodeUrl(finalUrl);
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
const baseWithQuery = appendParamsToBaseUrl(basePart, searchParams, serializedParams);
|
|
228
|
+
const finalUrl = hasHash ? `${baseWithQuery}#${hashContent}` : baseWithQuery;
|
|
183
229
|
|
|
184
230
|
// 解码 URL,将编码的中文转换为可读形式
|
|
185
|
-
return
|
|
231
|
+
return safeDecodeUrl(finalUrl);
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
function shouldAppendParamsToHash(hashContent: string): boolean {
|
|
235
|
+
if (!hashContent) {
|
|
236
|
+
return false;
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
const trimmed = hashContent.trim();
|
|
240
|
+
if (!trimmed) {
|
|
241
|
+
return false;
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
return trimmed.startsWith('/')
|
|
245
|
+
|| trimmed.startsWith('!/')
|
|
246
|
+
|| trimmed.startsWith('?');
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
function appendParamsToHash(hashContent: string, serializedParams: string): string {
|
|
250
|
+
if (!serializedParams) {
|
|
251
|
+
return hashContent;
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
if (!hashContent) {
|
|
255
|
+
return `?${serializedParams}`;
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
const separator = resolveQuerySeparator(hashContent);
|
|
259
|
+
return `${hashContent}${separator}${serializedParams}`;
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
function appendParamsToBaseUrl(
|
|
263
|
+
baseUrl: string,
|
|
264
|
+
params: URLSearchParams,
|
|
265
|
+
serializedParams: string,
|
|
266
|
+
): string {
|
|
267
|
+
if (!serializedParams) {
|
|
268
|
+
return baseUrl;
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
if (!baseUrl) {
|
|
272
|
+
return `?${serializedParams}`;
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
try {
|
|
276
|
+
const url = new URL(baseUrl);
|
|
277
|
+
params.forEach((value, key) => {
|
|
278
|
+
url.searchParams.append(key, value);
|
|
279
|
+
});
|
|
280
|
+
return url.toString();
|
|
281
|
+
}
|
|
282
|
+
catch {
|
|
283
|
+
const separator = resolveQuerySeparator(baseUrl);
|
|
284
|
+
return `${baseUrl}${separator}${serializedParams}`;
|
|
285
|
+
}
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
function resolveQuerySeparator(baseUrl: string): string {
|
|
289
|
+
if (!baseUrl) {
|
|
290
|
+
return '?';
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
if (!baseUrl.includes('?')) {
|
|
294
|
+
return '?';
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
return /[?&]$/.test(baseUrl) ? '' : '&';
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
function safeDecodeUrl(value: string): string {
|
|
301
|
+
try {
|
|
302
|
+
return decodeURIComponent(value);
|
|
303
|
+
}
|
|
304
|
+
catch {
|
|
305
|
+
return value;
|
|
306
|
+
}
|
|
186
307
|
}
|
|
187
308
|
|
|
188
309
|
/**
|
|
@@ -278,13 +399,27 @@ function deepParseJSON(obj: any): any {
|
|
|
278
399
|
return obj;
|
|
279
400
|
}
|
|
280
401
|
|
|
402
|
+
export interface UsePartyLoginOptions {
|
|
403
|
+
execute?: boolean
|
|
404
|
+
pinia?: Pinia
|
|
405
|
+
}
|
|
406
|
+
|
|
281
407
|
/**
|
|
282
408
|
* Party Login Hook
|
|
283
409
|
* 处理第三方登录的多步骤流程
|
|
284
410
|
* @param config Party Login 配置
|
|
285
411
|
* @param targetUrl 目标跳转 URL (来自 meta.link 或 meta.iframe)
|
|
286
412
|
*/
|
|
287
|
-
export async function usePartyLogin(
|
|
413
|
+
export async function usePartyLogin(
|
|
414
|
+
config: PartyLoginConfig,
|
|
415
|
+
targetUrl: string,
|
|
416
|
+
options: UsePartyLoginOptions = {},
|
|
417
|
+
) {
|
|
418
|
+
const {
|
|
419
|
+
execute: shouldAutoExecute = true,
|
|
420
|
+
pinia: injectedPinia,
|
|
421
|
+
} = options;
|
|
422
|
+
|
|
288
423
|
const url = ref<string>('');
|
|
289
424
|
const loading = ref(false);
|
|
290
425
|
const error = ref<Error | null>(null);
|
|
@@ -319,12 +454,13 @@ export async function usePartyLogin(config: PartyLoginConfig, targetUrl: string)
|
|
|
319
454
|
}
|
|
320
455
|
|
|
321
456
|
const { url: requestUrl, method, params } = step1.request;
|
|
457
|
+
const activePinia = injectedPinia ?? getActivePinia();
|
|
322
458
|
|
|
323
459
|
// 构建第一步请求的各类参数(此时还没有 previousResponse,所以传 undefined)
|
|
324
|
-
const queryParams = params.query ? buildParams(params.query) : {};
|
|
325
|
-
const bodyParams = params.body ? buildParams(params.body) : {};
|
|
326
|
-
const headerParams = params.header ? buildParams(params.header) : {};
|
|
327
|
-
const cookieParams = params.cookie ? buildParams(params.cookie) : {};
|
|
460
|
+
const queryParams = params.query ? buildParams(params.query, undefined, activePinia) : {};
|
|
461
|
+
const bodyParams = params.body ? buildParams(params.body, undefined, activePinia) : {};
|
|
462
|
+
const headerParams = params.header ? buildParams(params.header, undefined, activePinia) : {};
|
|
463
|
+
const cookieParams = params.cookie ? buildParams(params.cookie, undefined, activePinia) : {};
|
|
328
464
|
|
|
329
465
|
// 构建请求头
|
|
330
466
|
const headers: Record<string, string> = {};
|
|
@@ -397,6 +533,7 @@ export async function usePartyLogin(config: PartyLoginConfig, targetUrl: string)
|
|
|
397
533
|
targetUrl,
|
|
398
534
|
step2.oauth.params,
|
|
399
535
|
responseData,
|
|
536
|
+
activePinia,
|
|
400
537
|
);
|
|
401
538
|
|
|
402
539
|
url.value = oauthUrl;
|
|
@@ -411,8 +548,10 @@ export async function usePartyLogin(config: PartyLoginConfig, targetUrl: string)
|
|
|
411
548
|
}
|
|
412
549
|
}
|
|
413
550
|
|
|
414
|
-
|
|
415
|
-
|
|
551
|
+
if (shouldAutoExecute) {
|
|
552
|
+
// 自动执行
|
|
553
|
+
await execute();
|
|
554
|
+
}
|
|
416
555
|
|
|
417
556
|
return {
|
|
418
557
|
url,
|
|
@@ -421,3 +560,6 @@ export async function usePartyLogin(config: PartyLoginConfig, targetUrl: string)
|
|
|
421
560
|
execute,
|
|
422
561
|
};
|
|
423
562
|
}
|
|
563
|
+
function warnMissingStore(storeName: string) {
|
|
564
|
+
console.warn(`[usePartyLogin] Store "${storeName}" is not registered in Pinia.`);
|
|
565
|
+
}
|
package/src/index.ts
CHANGED
|
@@ -107,19 +107,21 @@ export {
|
|
|
107
107
|
readProjectModules,
|
|
108
108
|
} from './core';
|
|
109
109
|
export * from './core/interface';
|
|
110
|
+
export {
|
|
111
|
+
clearPersistedState,
|
|
112
|
+
createContext,
|
|
113
|
+
getPersistedState,
|
|
114
|
+
getPubinfoNamespace,
|
|
115
|
+
setPersistedState,
|
|
116
|
+
} from './core/utils';
|
|
110
117
|
|
|
111
118
|
export * from './features';
|
|
112
119
|
|
|
113
120
|
export {
|
|
114
121
|
cleanup,
|
|
115
122
|
cleanupWithoutUser,
|
|
116
|
-
clearPersistedState,
|
|
117
|
-
createContext,
|
|
118
123
|
createRawContext,
|
|
119
|
-
getPersistedState,
|
|
120
|
-
getPubinfoNamespace,
|
|
121
124
|
publicKeyEncryption,
|
|
122
|
-
setPersistedState,
|
|
123
125
|
storage,
|
|
124
126
|
wrapProxy,
|
|
125
127
|
} from './utils';
|
package/src/utils/index.ts
CHANGED
package/src/utils/proxy.ts
CHANGED