jvs-ppt-ui 0.0.18 → 0.0.20

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/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "jvs-ppt-ui",
3
3
  "private": false,
4
- "version": "0.0.18",
4
+ "version": "0.0.20",
5
5
  "type": "module",
6
6
  "main": "./dist/lib/jvs-ppt-ui.umd.js",
7
7
  "module": "./dist/lib/jvs-ppt-ui.es.js",
@@ -106,9 +106,14 @@ const subTypeNameMap: Record<StockSubType, string> = {
106
106
  Videos: '高清视频'
107
107
  }
108
108
 
109
- // 顶层 3-Tab 状态(检索 / 我的素材 / 创建素材)与 jvs_session_v3 鉴权
109
+ // 顶层 3-Tab 状态(联网搜索 / AI生成 / 我的空间)与 jvs_session_v3 鉴权
110
110
  export type MaterialMainTab = 'search' | 'mine' | 'create'
111
- const { isDamLoggedIn, sharedMaterialMainTab: activeMainTab, refreshSessionState } = useDamAuth()
111
+ const {
112
+ isDamLoggedIn,
113
+ sharedMaterialMainTab: activeMainTab,
114
+ refreshSessionState,
115
+ probeRemoteSessionV3
116
+ } = useDamAuth()
112
117
 
113
118
  const myAssetsRef = ref<InstanceType<typeof MyAssetsPanel> | null>(null)
114
119
  const createPanelRef = ref<InstanceType<typeof CreateMaterialPanel> | null>(null)
@@ -119,9 +124,9 @@ const guestToastText = ref('请先登录后使用该功能')
119
124
  let guestToastTimer: ReturnType<typeof setTimeout> | null = null
120
125
 
121
126
  function showGuestAuthToast(tabName: string) {
122
- guestToastText.value = tabName === '我的素材'
123
- ? '请先登录后查看「我的素材」'
124
- : '请先登录后使用「创建素材」'
127
+ guestToastText.value = tabName === '我的空间'
128
+ ? '请先登录后查看「我的空间」'
129
+ : '请先登录后使用「AI生成」'
125
130
  guestToastVisible.value = true
126
131
  if (guestToastTimer) clearTimeout(guestToastTimer)
127
132
  guestToastTimer = setTimeout(() => {
@@ -129,11 +134,14 @@ function showGuestAuthToast(tabName: string) {
129
134
  }, 2200)
130
135
  }
131
136
 
132
- function handleSwitchMainTab(tab: MaterialMainTab) {
137
+ async function handleSwitchMainTab(tab: MaterialMainTab) {
133
138
  refreshSessionState()
134
139
  if (tab !== 'search' && !isDamLoggedIn.value) {
135
- showGuestAuthToast(tab === 'mine' ? '我的素材' : '创建素材')
136
- return
140
+ const verified = await probeRemoteSessionV3(true)
141
+ if (!verified && !isDamLoggedIn.value) {
142
+ showGuestAuthToast(tab === 'mine' ? '我的空间' : 'AI生成')
143
+ return
144
+ }
137
145
  }
138
146
  activeMainTab.value = tab
139
147
  }
@@ -350,16 +358,19 @@ function openCustomListPreview(item: MaterialItem, list: MaterialItem[], e?: Eve
350
358
  previewVisible.value = true
351
359
  }
352
360
 
353
- // 将「检索」或「我的素材」中的图片一键送入「创建素材」作为参考图
354
- function handleSendToCreateRef(item: MaterialItem, e?: Event) {
361
+ // 将「联网搜索」或「我的空间」中的图片一键送入「AI生成」作为参考图
362
+ async function handleSendToCreateRef(item: MaterialItem, e?: Event) {
355
363
  if (e) {
356
364
  e.stopPropagation()
357
365
  e.preventDefault()
358
366
  }
359
367
  refreshSessionState()
360
368
  if (!isDamLoggedIn.value) {
361
- showGuestAuthToast('创建素材')
362
- return
369
+ const verified = await probeRemoteSessionV3(true)
370
+ if (!verified && !isDamLoggedIn.value) {
371
+ showGuestAuthToast('AI生成')
372
+ return
373
+ }
363
374
  }
364
375
  const thumbUrl = getMaterialThumb(item)
365
376
  if (!thumbUrl) {
@@ -936,7 +947,7 @@ watch([() => materialApiUrl?.value, () => materialPageApiUrl?.value, () => mater
936
947
  </button>
937
948
  </div>
938
949
 
939
- <!-- 顶层 3-Tab 分段切换器(检索 | 我的素材 | 创建素材) -->
950
+ <!-- 顶层 3-Tab 分段切换器(联网搜索 | AI生成 | 我的空间) -->
940
951
  <div v-if="activeMainTab !== 'search' || viewMode === 'overview'" class="material-top-tabs-wrap">
941
952
  <div class="material-top-segmented">
942
953
  <button
@@ -945,22 +956,23 @@ watch([() => materialApiUrl?.value, () => materialPageApiUrl?.value, () => mater
945
956
  :class="{ active: activeMainTab === 'search' }"
946
957
  @click="handleSwitchMainTab('search')"
947
958
  >
948
- <span>检索</span>
959
+ <span>联网搜索</span>
949
960
  </button>
950
961
 
951
962
  <button
952
963
  type="button"
953
964
  class="material-main-tab-btn"
954
965
  :class="{
955
- active: activeMainTab === 'mine',
966
+ active: activeMainTab === 'create',
956
967
  'is-disabled': !isDamLoggedIn
957
968
  }"
958
- :title="!isDamLoggedIn ? '请先登录后查看我的素材' : '查看我的项目与个人素材'"
959
- @click="handleSwitchMainTab('mine')"
969
+ :title="!isDamLoggedIn ? '请先登录后使用AI生成' : 'AI 生成图片或视频素材'"
970
+ @click="handleSwitchMainTab('create')"
960
971
  >
961
- <span>我的素材</span>
972
+ <span>AI生成</span>
973
+ <span v-if="isDamLoggedIn" class="tab-ai-spark">✦</span>
962
974
  <svg
963
- v-if="!isDamLoggedIn"
975
+ v-else
964
976
  class="tab-lock-svg"
965
977
  viewBox="0 0 24 24"
966
978
  width="11"
@@ -978,16 +990,15 @@ watch([() => materialApiUrl?.value, () => materialPageApiUrl?.value, () => mater
978
990
  type="button"
979
991
  class="material-main-tab-btn"
980
992
  :class="{
981
- active: activeMainTab === 'create',
993
+ active: activeMainTab === 'mine',
982
994
  'is-disabled': !isDamLoggedIn
983
995
  }"
984
- :title="!isDamLoggedIn ? '请先登录后使用创建素材' : 'AI 生成图片或视频素材'"
985
- @click="handleSwitchMainTab('create')"
996
+ :title="!isDamLoggedIn ? '请先登录后查看我的空间' : '查看我的项目与个人素材'"
997
+ @click="handleSwitchMainTab('mine')"
986
998
  >
987
- <span>创建素材</span>
988
- <span v-if="isDamLoggedIn" class="tab-ai-spark">✦</span>
999
+ <span>我的空间</span>
989
1000
  <svg
990
- v-else
1001
+ v-if="!isDamLoggedIn"
991
1002
  class="tab-lock-svg"
992
1003
  viewBox="0 0 24 24"
993
1004
  width="11"
@@ -1089,7 +1100,7 @@ watch([() => materialApiUrl?.value, () => materialPageApiUrl?.value, () => mater
1089
1100
  v-if="isDamLoggedIn && cat.subType !== 'Videos' && !isVideoMaterial(item) && getMaterialThumb(item)"
1090
1101
  type="button"
1091
1102
  class="overview-card-ref-btn"
1092
- title="作为参考图发送到「创建素材」"
1103
+ title="作为参考图发送到「AI生成」"
1093
1104
  @click.stop.prevent="handleSendToCreateRef(item, $event)"
1094
1105
  >
1095
1106
  @参考
@@ -1240,7 +1251,7 @@ watch([() => materialApiUrl?.value, () => materialPageApiUrl?.value, () => mater
1240
1251
  v-if="isDamLoggedIn && filterParams.activeSubType !== 'Videos' && !isVideoMaterial(item) && getMaterialThumb(item)"
1241
1252
  type="button"
1242
1253
  class="detail-card-ref-btn"
1243
- title="作为参考图发送到「创建素材」"
1254
+ title="作为参考图发送到「AI生成」"
1244
1255
  @click.stop.prevent="handleSendToCreateRef(item, $event)"
1245
1256
  >
1246
1257
  @参考
@@ -522,20 +522,13 @@ function extractUploadedUrl(res: any): string {
522
522
  async function handleFileUploadChange(e: Event) {
523
523
  const input = e.target as HTMLInputElement
524
524
  const files = input.files ? Array.from(input.files) : []
525
- input.value = ''
526
525
  if (files.length === 0) return
527
526
 
528
527
  isUploading.value = true
529
528
  try {
530
- // 确保获取到默认项目 ID
531
- let targetProjId = selectedProjectId.value
532
- if (!targetProjId) {
533
- if (projects.value.length === 0) {
534
- await fetchProjectsAndFolders()
535
- }
536
- const personal = projects.value.find(p => p.type === 'PERSONAL') || projects.value[0]
537
- targetProjId = personal?.id || ''
538
- }
529
+ // 若用户已在下拉树中选择了具体项目则传入,否则交由后端 /jvs-dam/upload/file 自动归入当前用户的个人项目
530
+ const personal = projects.value.find(p => p.type === 'PERSONAL') || projects.value[0]
531
+ const targetProjId = selectedProjectId.value || personal?.id || ''
539
532
 
540
533
  let uploadedCount = 0
541
534
  for (const file of files) {
@@ -544,6 +537,7 @@ async function handleFileUploadChange(e: Event) {
544
537
  formData.append('file', file)
545
538
  formData.append('path', '/dam/media/')
546
539
  formData.append('fileName', file.name)
540
+ formData.append('name', file.name)
547
541
  formData.append('id', String(Date.now()))
548
542
  if (targetProjId) formData.append('projectId', targetProjId)
549
543
  if (selectedFolderId.value) formData.append('folderId', selectedFolderId.value)
@@ -556,25 +550,6 @@ async function handleFileUploadChange(e: Event) {
556
550
  const fileUrl = extractUploadedUrl(uploadRes)
557
551
  if (fileUrl) {
558
552
  uploadedCount++
559
- // 同步登记为数字资产(若后端 upload/file 未自动入库 asset 表,则补偿调用 POST /asset)
560
- if (targetProjId) {
561
- try {
562
- await damFetchJson('/asset', {
563
- method: 'POST',
564
- body: JSON.stringify({
565
- projectId: targetProjId,
566
- folderId: selectedFolderId.value || undefined,
567
- name: file.name,
568
- assetType: isVid ? 'VIDEO' : 'IMAGE',
569
- sourceType: 'UPLOAD',
570
- fileUrl,
571
- fileExtension: file.name.split('.').pop()?.toLowerCase() || (isVid ? 'mp4' : 'png')
572
- })
573
- })
574
- } catch {
575
- // 忽略重复登记异常
576
- }
577
- }
578
553
  }
579
554
  }
580
555
 
@@ -588,6 +563,7 @@ async function handleFileUploadChange(e: Event) {
588
563
  notify(`上传素材失败: ${err.message || '网络异常'}`)
589
564
  } finally {
590
565
  isUploading.value = false
566
+ input.value = ''
591
567
  }
592
568
  }
593
569
 
@@ -44,24 +44,63 @@ function readStorageKey(storage: Storage | undefined, key: string): string {
44
44
  return ''
45
45
  }
46
46
 
47
+ /**
48
+ * 向 Storage 写入 jvs- 标准包装格式数据(对齐 jvs-material-ui / jvs-narrivo-ai-ui 的 setStore 规范)
49
+ */
50
+ function writeJvsStore(name: string, content: string): void {
51
+ if (typeof window === 'undefined' || !window.localStorage || !name) return
52
+ try {
53
+ const payload = JSON.stringify({
54
+ dataType: typeof content,
55
+ content,
56
+ datetime: Date.now()
57
+ })
58
+ window.localStorage.setItem(`jvs-${name}`, payload)
59
+ if (window.sessionStorage) {
60
+ window.sessionStorage.setItem(`jvs-${name}`, payload)
61
+ }
62
+ } catch {
63
+ // 忽略隐私模式或配额异常
64
+ }
65
+ }
66
+
67
+ function removeJvsStore(name: string): void {
68
+ if (typeof window === 'undefined') return
69
+ try {
70
+ window.localStorage?.removeItem(`jvs-${name}`)
71
+ window.localStorage?.removeItem(name)
72
+ window.sessionStorage?.removeItem(`jvs-${name}`)
73
+ window.sessionStorage?.removeItem(name)
74
+ } catch {
75
+ // ignore
76
+ }
77
+ }
78
+
47
79
  /**
48
80
  * 提取 jvs_session_v3 登录凭证
49
81
  * 判定顺序:
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
82
+ * 1. document.cookie 中的 jvs_session_v3 或 Set-Cookie(非 HttpOnly 场景)
83
+ * 2. localStorage / sessionStorage 中的 jvs_session_v3、jvs-jvs_session_v3、jvs-sessionV3、sessionV3、jvs-access_token
84
+ * 3. materialHeaders 中显式传入的 jvs_session_v3 或 Authorization
53
85
  */
54
86
  export function resolveJvsSessionV3(extraHeaders?: Record<string, string>): string {
55
87
  if (typeof window === 'undefined') return ''
56
88
 
57
- // 1. Cookie: jvs_session_v3
58
- const fromCookie = getCookieValue('jvs_session_v3')
89
+ // 1. Cookie: jvs_session_v3(若未开启 HttpOnly 可直接读取)
90
+ const fromCookie = getCookieValue('jvs_session_v3') || getCookieValue('Set-Cookie')
59
91
  if (fromCookie && fromCookie.trim()) {
60
92
  return fromCookie.trim()
61
93
  }
62
94
 
63
95
  // 2. LocalStorage / SessionStorage
64
- const candidateKeys = ['jvs_session_v3', 'jvs-jvs_session_v3', 'jvs-sessionV3', 'sessionV3']
96
+ const candidateKeys = [
97
+ 'jvs_session_v3',
98
+ 'jvs-jvs_session_v3',
99
+ 'jvs-sessionV3',
100
+ 'sessionV3',
101
+ 'jvs-access_token',
102
+ 'access_token'
103
+ ]
65
104
  for (const key of candidateKeys) {
66
105
  const localVal = readStorageKey(window.localStorage, key)
67
106
  if (localVal) return localVal
@@ -71,7 +110,11 @@ export function resolveJvsSessionV3(extraHeaders?: Record<string, string>): stri
71
110
 
72
111
  // 3. 外部注入的 Headers
73
112
  if (extraHeaders) {
74
- const headerVal = extraHeaders['jvs_session_v3'] || extraHeaders['JVS_SESSION_V3']
113
+ const headerVal =
114
+ extraHeaders['jvs_session_v3'] ||
115
+ extraHeaders['JVS_SESSION_V3'] ||
116
+ (extraHeaders['Authorization'] ? extraHeaders['Authorization'].replace(/^Bearer\s+/i, '') : '') ||
117
+ (extraHeaders['authorization'] ? extraHeaders['authorization'].replace(/^Bearer\s+/i, '') : '')
75
118
  if (typeof headerVal === 'string' && headerVal.trim()) {
76
119
  return headerVal.trim()
77
120
  }
@@ -80,6 +123,21 @@ export function resolveJvsSessionV3(extraHeaders?: Record<string, string>): stri
80
123
  return ''
81
124
  }
82
125
 
126
+ /**
127
+ * 提取服务网关 Origin 前缀(如配置了完整域名则复用该域名,否则使用当前站点相对路径 '')
128
+ */
129
+ export function resolveGatewayOrigin(configuredUrl?: string): string {
130
+ if (configuredUrl && /^https?:\/\//i.test(configuredUrl.trim())) {
131
+ try {
132
+ const u = new URL(configuredUrl.trim())
133
+ return u.origin
134
+ } catch {
135
+ return ''
136
+ }
137
+ }
138
+ return ''
139
+ }
140
+
83
141
  /**
84
142
  * 提取 DAM 服务基础路径(默认 /jvs-dam)
85
143
  */
@@ -101,6 +159,16 @@ export type DamMainTab = 'search' | 'mine' | 'create'
101
159
  */
102
160
  const sharedMaterialMainTab = ref<DamMainTab>('search')
103
161
 
162
+ /**
163
+ * 针对主域 .bctools.cn 下 HttpOnly Cookie (jvs_session_v3) 的服务端换取/探测状态
164
+ * 说明:当 jvs_session_v3 设置了 HttpOnly=true 时,前端 document.cookie 无法直接读取,
165
+ * 需通过同域/跨子域携带 Cookie 请求 /jvs-system/login/token(对齐 jvs-material-ui / jvs-narrivo-ai-ui)换取 sessionV3。
166
+ */
167
+ const remoteSessionToken = ref<string>('')
168
+ const remoteCookieSessionVerified = ref<boolean>(false)
169
+ let inFlightSessionProbe: Promise<boolean> | null = null
170
+ let hasInitialProbed = false
171
+
104
172
  export function setSharedMaterialMainTab(tab: DamMainTab) {
105
173
  sharedMaterialMainTab.value = tab
106
174
  }
@@ -113,20 +181,157 @@ export function useDamAuth() {
113
181
  const sessionTick = ref(0)
114
182
  let pollTimer: ReturnType<typeof setInterval> | null = null
115
183
 
184
+ const damBaseUrl = computed(() => resolveDamBaseUrl(materialApiUrl.value))
185
+ const gatewayOrigin = computed(() => resolveGatewayOrigin(materialApiUrl.value))
186
+
187
+ /**
188
+ * 通过服务端接口换取/校验主域 (.bctools.cn) 下的 HttpOnly jvs_session_v3 Cookie
189
+ */
190
+ async function probeRemoteSessionV3(force = false): Promise<boolean> {
191
+ if (typeof window === 'undefined') return false
192
+
193
+ const localToken = resolveJvsSessionV3(materialHeaders.value) || remoteSessionToken.value
194
+ if (localToken && !force && hasInitialProbed) {
195
+ return true
196
+ }
197
+
198
+ if (inFlightSessionProbe) {
199
+ return inFlightSessionProbe
200
+ }
201
+
202
+ inFlightSessionProbe = (async () => {
203
+ hasInitialProbed = true
204
+ const origin = gatewayOrigin.value
205
+
206
+ // 1. 标准 JVS 换取方式:请求 /jvs-system/login/token(浏览器自动携带 .bctools.cn 下的 HttpOnly jvs_session_v3)
207
+ try {
208
+ const tokenResp = await fetch(`${origin}/jvs-system/login/token`, {
209
+ method: 'GET',
210
+ credentials: 'include',
211
+ headers: {
212
+ 'jvs-rule-ua': encodeURI('2.4'),
213
+ ...(materialHeaders.value || {})
214
+ }
215
+ })
216
+
217
+ if (tokenResp.ok) {
218
+ const text = (await tokenResp.text()).trim()
219
+ if (text && !text.startsWith('<')) {
220
+ const parsed = JSON.parse(text)
221
+ const code = parsed?.code ?? parsed?.data?.code
222
+ const tokenData = parsed?.data?.data !== undefined ? parsed.data.data : parsed?.data
223
+
224
+ if (code === 0) {
225
+ const tokenStr =
226
+ typeof tokenData === 'string'
227
+ ? tokenData.trim()
228
+ : typeof tokenData?.access_token === 'string'
229
+ ? tokenData.access_token.trim()
230
+ : ''
231
+
232
+ if (tokenStr) {
233
+ remoteSessionToken.value = tokenStr
234
+ remoteCookieSessionVerified.value = true
235
+ writeJvsStore('sessionV3', tokenStr)
236
+ writeJvsStore('access_token', tokenStr)
237
+ sessionTick.value++
238
+
239
+ // 若本地尚无 tenantId,顺带拉取当前租户 ID
240
+ if (!readStorageKey(window.localStorage, 'jvs-tenantId')) {
241
+ fetch(`${origin}/jvs-system/index/this/tenant`, {
242
+ method: 'GET',
243
+ credentials: 'include',
244
+ headers: {
245
+ 'jvs-rule-ua': encodeURI('2.4'),
246
+ Authorization: `Bearer ${tokenStr}`
247
+ }
248
+ })
249
+ .then(r => r.json())
250
+ .then(tRes => {
251
+ const tData = tRes?.data?.data || tRes?.data
252
+ if (tData?.id) {
253
+ writeJvsStore('tenantId', String(tData.id))
254
+ }
255
+ })
256
+ .catch(() => {})
257
+ }
258
+
259
+ return true
260
+ } else {
261
+ // /jvs-system/login/token 明确返回 code=0 但无 token,说明主域未登录或已退出
262
+ remoteSessionToken.value = ''
263
+ remoteCookieSessionVerified.value = false
264
+ removeJvsStore('sessionV3')
265
+ removeJvsStore('access_token')
266
+ sessionTick.value++
267
+ return false
268
+ }
269
+ }
270
+ }
271
+ }
272
+ } catch {
273
+ // 忽略 /jvs-system/login/token 网络或未代理异常,继续尝试步骤 2
274
+ }
275
+
276
+ // 2. 兜底探测:若当前站点未代理 /jvs-system,但代理了 /jvs-dam 且网关直接透传 .bctools.cn 的 HttpOnly Cookie
277
+ try {
278
+ const probeResp = await fetch(`${damBaseUrl.value}/project/my`, {
279
+ method: 'GET',
280
+ credentials: 'include',
281
+ headers: buildDamHeaders(false)
282
+ })
283
+ if (probeResp.ok) {
284
+ const text = (await probeResp.text()).trim()
285
+ if (text && !text.startsWith('<')) {
286
+ const parsed = JSON.parse(text)
287
+ const code = parsed?.code ?? parsed?.data?.code
288
+ if (code === 0 || code === 200) {
289
+ remoteCookieSessionVerified.value = true
290
+ sessionTick.value++
291
+ return true
292
+ }
293
+ }
294
+ }
295
+ } catch {
296
+ // ignore
297
+ }
298
+
299
+ return Boolean(resolveJvsSessionV3(materialHeaders.value))
300
+ })()
301
+
302
+ try {
303
+ return await inFlightSessionProbe
304
+ } finally {
305
+ inFlightSessionProbe = null
306
+ }
307
+ }
308
+
116
309
  const refreshSessionState = () => {
117
310
  sessionTick.value++
311
+ if (!resolveJvsSessionV3(materialHeaders.value) && !remoteSessionToken.value && !remoteCookieSessionVerified.value) {
312
+ void probeRemoteSessionV3(false)
313
+ }
314
+ }
315
+
316
+ const handleWindowFocus = () => {
317
+ sessionTick.value++
318
+ if (!resolveJvsSessionV3(materialHeaders.value) && !remoteSessionToken.value && !remoteCookieSessionVerified.value) {
319
+ void probeRemoteSessionV3(false)
320
+ }
118
321
  }
119
322
 
120
323
  onMounted(() => {
121
324
  window.addEventListener('storage', refreshSessionState)
122
- window.addEventListener('focus', refreshSessionState)
123
- // 定期轻量同步 Cookie 中 jvs_session_v3 的变化
124
- pollTimer = setInterval(refreshSessionState, 2000)
325
+ window.addEventListener('focus', handleWindowFocus)
326
+ // 立即探测一次主域 (.bctools.cn) HttpOnly jvs_session_v3 登录态
327
+ void probeRemoteSessionV3(false)
328
+ // 定期轻量同步状态变化
329
+ pollTimer = setInterval(refreshSessionState, 3000)
125
330
  })
126
331
 
127
332
  onUnmounted(() => {
128
333
  window.removeEventListener('storage', refreshSessionState)
129
- window.removeEventListener('focus', refreshSessionState)
334
+ window.removeEventListener('focus', handleWindowFocus)
130
335
  if (pollTimer) {
131
336
  clearInterval(pollTimer)
132
337
  pollTimer = null
@@ -134,15 +339,15 @@ export function useDamAuth() {
134
339
  })
135
340
 
136
341
  const sessionV3 = computed(() => {
137
- // 依赖 sessionTick 以感知 cookie 变化
138
342
  void sessionTick.value
139
- return resolveJvsSessionV3(materialHeaders.value)
343
+ return remoteSessionToken.value || resolveJvsSessionV3(materialHeaders.value)
140
344
  })
141
345
 
142
- /** 是否已登录(仅当 jvs_session_v3 存在有效内容时为 true) */
143
- const isDamLoggedIn = computed(() => Boolean(sessionV3.value))
144
-
145
- const damBaseUrl = computed(() => resolveDamBaseUrl(materialApiUrl.value))
346
+ /** 是否已登录(jvs_session_v3 存在有效内容,或通过主域 HttpOnly Cookie 换取/验证通过) */
347
+ const isDamLoggedIn = computed(() => {
348
+ void sessionTick.value
349
+ return Boolean(sessionV3.value || remoteCookieSessionVerified.value)
350
+ })
146
351
 
147
352
  /**
148
353
  * 构造携带鉴权信息的 Headers
@@ -233,6 +438,7 @@ export function useDamAuth() {
233
438
  sharedMaterialMainTab,
234
439
  setSharedMaterialMainTab,
235
440
  refreshSessionState,
441
+ probeRemoteSessionV3,
236
442
  buildDamHeaders,
237
443
  damFetchJson
238
444
  }