jvs-ppt-ui 0.0.17 → 0.0.18

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.
@@ -0,0 +1,239 @@
1
+ import { ref, computed, onMounted, onUnmounted } from 'vue'
2
+ import { useSlides } from '../../composables/useSlides'
3
+
4
+ /**
5
+ * 读取指定名称的 Cookie
6
+ */
7
+ export function getCookieValue(cname: string): string {
8
+ if (typeof document === 'undefined' || !document.cookie) return ''
9
+ const name = cname + '='
10
+ const parts = document.cookie.split(';')
11
+ for (let i = 0; i < parts.length; i++) {
12
+ const item = parts[i]?.trim()
13
+ if (item && item.indexOf(name) === 0) {
14
+ try {
15
+ return decodeURIComponent(item.substring(name.length))
16
+ } catch {
17
+ return item.substring(name.length)
18
+ }
19
+ }
20
+ }
21
+ return ''
22
+ }
23
+
24
+ /**
25
+ * 从 Storage 中读取指定 key(兼容 jvs- 前缀包装格式与原始字符串格式)
26
+ */
27
+ function readStorageKey(storage: Storage | undefined, key: string): string {
28
+ if (!storage) return ''
29
+ try {
30
+ const raw = storage.getItem(key)
31
+ if (!raw) return ''
32
+ try {
33
+ const parsed = JSON.parse(raw)
34
+ if (parsed && typeof parsed === 'object' && 'content' in parsed) {
35
+ return typeof parsed.content === 'string' ? parsed.content.trim() : ''
36
+ }
37
+ if (typeof parsed === 'string') return parsed.trim()
38
+ } catch {
39
+ return raw.trim()
40
+ }
41
+ } catch {
42
+ return ''
43
+ }
44
+ return ''
45
+ }
46
+
47
+ /**
48
+ * 提取 jvs_session_v3 登录凭证
49
+ * 判定顺序:
50
+ * 1. document.cookie 中的 jvs_session_v3
51
+ * 2. localStorage / sessionStorage 中的 jvs_session_v3、jvs-jvs_session_v3、jvs-sessionV3、sessionV3
52
+ * 3. materialHeaders 中显式传入的 jvs_session_v3
53
+ */
54
+ export function resolveJvsSessionV3(extraHeaders?: Record<string, string>): string {
55
+ if (typeof window === 'undefined') return ''
56
+
57
+ // 1. Cookie: jvs_session_v3
58
+ const fromCookie = getCookieValue('jvs_session_v3')
59
+ if (fromCookie && fromCookie.trim()) {
60
+ return fromCookie.trim()
61
+ }
62
+
63
+ // 2. LocalStorage / SessionStorage
64
+ const candidateKeys = ['jvs_session_v3', 'jvs-jvs_session_v3', 'jvs-sessionV3', 'sessionV3']
65
+ for (const key of candidateKeys) {
66
+ const localVal = readStorageKey(window.localStorage, key)
67
+ if (localVal) return localVal
68
+ const sessionVal = readStorageKey(window.sessionStorage, key)
69
+ if (sessionVal) return sessionVal
70
+ }
71
+
72
+ // 3. 外部注入的 Headers
73
+ if (extraHeaders) {
74
+ const headerVal = extraHeaders['jvs_session_v3'] || extraHeaders['JVS_SESSION_V3']
75
+ if (typeof headerVal === 'string' && headerVal.trim()) {
76
+ return headerVal.trim()
77
+ }
78
+ }
79
+
80
+ return ''
81
+ }
82
+
83
+ /**
84
+ * 提取 DAM 服务基础路径(默认 /jvs-dam)
85
+ */
86
+ export function resolveDamBaseUrl(configuredUrl?: string): string {
87
+ if (configuredUrl && configuredUrl.trim()) {
88
+ const clean = configuredUrl.trim()
89
+ const damIdx = clean.indexOf('/jvs-dam')
90
+ if (damIdx >= 0) {
91
+ return clean.slice(0, damIdx + '/jvs-dam'.length)
92
+ }
93
+ }
94
+ return '/jvs-dam'
95
+ }
96
+
97
+ export type DamMainTab = 'search' | 'mine' | 'create'
98
+
99
+ /**
100
+ * 跨组件共享的素材库顶层 Tab 状态(支持画布占位符「AI生成」直接跳转至「创建素材」)
101
+ */
102
+ const sharedMaterialMainTab = ref<DamMainTab>('search')
103
+
104
+ export function setSharedMaterialMainTab(tab: DamMainTab) {
105
+ sharedMaterialMainTab.value = tab
106
+ }
107
+
108
+ /**
109
+ * DAM 登录态与通用请求工具 Composable
110
+ */
111
+ export function useDamAuth() {
112
+ const { materialApiUrl, materialHeaders } = useSlides()
113
+ const sessionTick = ref(0)
114
+ let pollTimer: ReturnType<typeof setInterval> | null = null
115
+
116
+ const refreshSessionState = () => {
117
+ sessionTick.value++
118
+ }
119
+
120
+ onMounted(() => {
121
+ window.addEventListener('storage', refreshSessionState)
122
+ window.addEventListener('focus', refreshSessionState)
123
+ // 定期轻量同步 Cookie 中 jvs_session_v3 的变化
124
+ pollTimer = setInterval(refreshSessionState, 2000)
125
+ })
126
+
127
+ onUnmounted(() => {
128
+ window.removeEventListener('storage', refreshSessionState)
129
+ window.removeEventListener('focus', refreshSessionState)
130
+ if (pollTimer) {
131
+ clearInterval(pollTimer)
132
+ pollTimer = null
133
+ }
134
+ })
135
+
136
+ const sessionV3 = computed(() => {
137
+ // 依赖 sessionTick 以感知 cookie 变化
138
+ void sessionTick.value
139
+ return resolveJvsSessionV3(materialHeaders.value)
140
+ })
141
+
142
+ /** 是否已登录(仅当 jvs_session_v3 存在有效内容时为 true) */
143
+ const isDamLoggedIn = computed(() => Boolean(sessionV3.value))
144
+
145
+ const damBaseUrl = computed(() => resolveDamBaseUrl(materialApiUrl.value))
146
+
147
+ /**
148
+ * 构造携带鉴权信息的 Headers
149
+ */
150
+ function buildDamHeaders(isJson = true): Record<string, string> {
151
+ const headers: Record<string, string> = {
152
+ 'jvs-rule-ua': encodeURI('2.4'),
153
+ ...(materialHeaders.value || {})
154
+ }
155
+ if (isJson) {
156
+ headers['Content-Type'] = 'application/json;charset=utf-8'
157
+ }
158
+ const token = sessionV3.value || readStorageKey(window?.localStorage, 'jvs-access_token')
159
+ if (token && !headers['Authorization'] && !headers['authorization']) {
160
+ headers['Authorization'] = `Bearer ${token}`
161
+ }
162
+ const tenantId = readStorageKey(window?.localStorage, 'jvs-tenantId')
163
+ if (tenantId && !headers['tenant-id']) {
164
+ headers['tenant-id'] = tenantId
165
+ }
166
+ return headers
167
+ }
168
+
169
+ /**
170
+ * 统一安全的 JSON 请求封装
171
+ */
172
+ async function damFetchJson<T = any>(
173
+ pathOrUrl: string,
174
+ options: RequestInit & { params?: Record<string, any> } = {}
175
+ ): Promise<T> {
176
+ const base = damBaseUrl.value
177
+ let fullUrl = pathOrUrl.startsWith('http') || pathOrUrl.startsWith('/jvs-')
178
+ ? pathOrUrl
179
+ : `${base}${pathOrUrl.startsWith('/') ? '' : '/'}${pathOrUrl}`
180
+
181
+ if (options.params) {
182
+ const searchParams = new URLSearchParams()
183
+ Object.entries(options.params).forEach(([k, v]) => {
184
+ if (v !== undefined && v !== null && v !== '') {
185
+ if (Array.isArray(v)) {
186
+ if (v.length > 0) searchParams.append(k, v.join(','))
187
+ } else {
188
+ searchParams.append(k, String(v))
189
+ }
190
+ }
191
+ })
192
+ const qs = searchParams.toString()
193
+ if (qs) {
194
+ fullUrl += (fullUrl.includes('?') ? '&' : '?') + qs
195
+ }
196
+ }
197
+
198
+ const isFormData = typeof FormData !== 'undefined' && options.body instanceof FormData
199
+ const mergedHeaders = {
200
+ ...buildDamHeaders(!isFormData),
201
+ ...((options.headers as Record<string, string>) || {})
202
+ }
203
+ if (isFormData) {
204
+ delete mergedHeaders['Content-Type']
205
+ delete mergedHeaders['content-type']
206
+ }
207
+
208
+ const response = await fetch(fullUrl, {
209
+ ...options,
210
+ credentials: 'include',
211
+ headers: mergedHeaders
212
+ })
213
+
214
+ const rawText = await response.text()
215
+ if (!response.ok) {
216
+ throw new Error(`请求异常 (${response.status})`)
217
+ }
218
+ const trimmed = rawText.trim()
219
+ if (trimmed.startsWith('<') || trimmed.toLowerCase().startsWith('<!doctype')) {
220
+ throw new Error('接口返回了非 JSON 格式响应')
221
+ }
222
+ try {
223
+ return JSON.parse(rawText) as T
224
+ } catch (err: any) {
225
+ throw new Error(`数据解析失败: ${err.message}`)
226
+ }
227
+ }
228
+
229
+ return {
230
+ sessionV3,
231
+ isDamLoggedIn,
232
+ damBaseUrl,
233
+ sharedMaterialMainTab,
234
+ setSharedMaterialMainTab,
235
+ refreshSessionState,
236
+ buildDamHeaders,
237
+ damFetchJson
238
+ }
239
+ }