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/dist/lib/jvs-ppt-ui.es.js +19196 -19125
- package/dist/lib/jvs-ppt-ui.umd.js +98 -98
- package/dist/lib/style.css +1 -1
- package/package.json +1 -1
- package/src/components/TemplateGallery.vue +38 -27
- package/src/components/material/MyAssetsPanel.vue +5 -29
- package/src/components/material/useDamAuth.ts +223 -17
package/package.json
CHANGED
|
@@ -106,9 +106,14 @@ const subTypeNameMap: Record<StockSubType, string> = {
|
|
|
106
106
|
Videos: '高清视频'
|
|
107
107
|
}
|
|
108
108
|
|
|
109
|
-
// 顶层 3-Tab
|
|
109
|
+
// 顶层 3-Tab 状态(联网搜索 / AI生成 / 我的空间)与 jvs_session_v3 鉴权
|
|
110
110
|
export type MaterialMainTab = 'search' | 'mine' | 'create'
|
|
111
|
-
const {
|
|
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
|
-
|
|
136
|
-
|
|
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
|
-
|
|
362
|
-
|
|
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
|
|
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 === '
|
|
966
|
+
active: activeMainTab === 'create',
|
|
956
967
|
'is-disabled': !isDamLoggedIn
|
|
957
968
|
}"
|
|
958
|
-
:title="!isDamLoggedIn ? '
|
|
959
|
-
@click="handleSwitchMainTab('
|
|
969
|
+
:title="!isDamLoggedIn ? '请先登录后使用AI生成' : 'AI 生成图片或视频素材'"
|
|
970
|
+
@click="handleSwitchMainTab('create')"
|
|
960
971
|
>
|
|
961
|
-
<span
|
|
972
|
+
<span>AI生成</span>
|
|
973
|
+
<span v-if="isDamLoggedIn" class="tab-ai-spark">✦</span>
|
|
962
974
|
<svg
|
|
963
|
-
v-
|
|
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 === '
|
|
993
|
+
active: activeMainTab === 'mine',
|
|
982
994
|
'is-disabled': !isDamLoggedIn
|
|
983
995
|
}"
|
|
984
|
-
:title="!isDamLoggedIn ? '
|
|
985
|
-
@click="handleSwitchMainTab('
|
|
996
|
+
:title="!isDamLoggedIn ? '请先登录后查看我的空间' : '查看我的项目与个人素材'"
|
|
997
|
+
@click="handleSwitchMainTab('mine')"
|
|
986
998
|
>
|
|
987
|
-
<span
|
|
988
|
-
<span v-if="isDamLoggedIn" class="tab-ai-spark">✦</span>
|
|
999
|
+
<span>我的空间</span>
|
|
989
1000
|
<svg
|
|
990
|
-
v-
|
|
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
|
-
//
|
|
531
|
-
|
|
532
|
-
|
|
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 = [
|
|
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 =
|
|
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',
|
|
123
|
-
//
|
|
124
|
-
|
|
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',
|
|
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
|
-
/**
|
|
143
|
-
const isDamLoggedIn = computed(() =>
|
|
144
|
-
|
|
145
|
-
|
|
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
|
}
|