refactor(frontend-api): 统一 useApiBase 并移除 localhost:8000 硬编码

This commit is contained in:
2977094657
2026-02-28 18:37:02 +08:00
Unverified
parent 8a101b4c5e
commit b5200713b3
14 changed files with 85 additions and 86 deletions
+2 -2
View File
@@ -8,7 +8,7 @@
<div>
<h4 class="text-red-800 font-semibold">API连接问题</h4>
<p class="text-red-700 text-sm mt-1">{{ appStore.apiMessage || '无法连接到后端服务' }}</p>
<p class="text-red-600 text-xs mt-2">请确保后端服务正在运行 (端口: 8000)</p>
<p class="text-red-600 text-xs mt-2">请确保后端服务正在运行</p>
</div>
</div>
</div>
@@ -18,4 +18,4 @@
import { useAppStore } from '~/stores/app'
const appStore = useAppStore()
</script>
</script>
+2 -2
View File
@@ -101,13 +101,13 @@ const props = defineProps({
message: { type: Object, default: null },
})
const mediaBase = process.client ? 'http://localhost:8000' : ''
const apiBase = useApiBase()
const normalizeMaybeUrl = (u) => {
const raw = String(u || '').trim()
if (!raw) return ''
if (/^https?:\/\//i.test(raw) || /^blob:/i.test(raw) || /^data:/i.test(raw)) return raw
if (/^\/api\//i.test(raw)) return `${mediaBase}${raw}`
if (/^\/api\//i.test(raw)) return `${apiBase}${raw.slice(4)}`
return raw
}
@@ -137,8 +137,7 @@ const topGroup = computed(() => {
return o && typeof o === 'object' && typeof o.displayName === 'string' ? o : null
})
// Keep the same behavior as the chat page: media (including avatars) comes from backend :8000 in dev.
const mediaBase = process.client ? 'http://localhost:8000' : ''
const apiBase = useApiBase()
const resolveMediaUrl = (value) => {
const raw = String(value || '').trim()
if (!raw) return ''
@@ -147,13 +146,13 @@ const resolveMediaUrl = (value) => {
try {
const host = new URL(raw).hostname.toLowerCase()
if (host.endsWith('.qpic.cn') || host.endsWith('.qlogo.cn')) {
return `${mediaBase}/api/chat/media/proxy_image?url=${encodeURIComponent(raw)}`
return `${apiBase}/chat/media/proxy_image?url=${encodeURIComponent(raw)}`
}
} catch {}
return raw
}
// Most backend fields are like "/api/...", so just prefix.
return `${mediaBase}${raw.startsWith('/') ? '' : '/'}${raw}`
if (/^\/api\//i.test(raw)) return `${apiBase}${raw.slice(4)}`
return raw.startsWith('/') ? raw : `/${raw}`
}
const topContactAvatarUrl = computed(() => {
@@ -308,7 +308,7 @@ const indexBuild = computed(() => {
})
// Media URL resolving (same behavior as other wrapped components)
const mediaBase = process.client ? 'http://localhost:8000' : ''
const apiBase = useApiBase()
const resolveMediaUrl = (value) => {
const raw = String(value || '').trim()
if (!raw) return ''
@@ -316,12 +316,13 @@ const resolveMediaUrl = (value) => {
try {
const host = new URL(raw).hostname.toLowerCase()
if (host.endsWith('.qpic.cn') || host.endsWith('.qlogo.cn')) {
return `${mediaBase}/api/chat/media/proxy_image?url=${encodeURIComponent(raw)}`
return `${apiBase}/chat/media/proxy_image?url=${encodeURIComponent(raw)}`
}
} catch {}
return raw
}
return `${mediaBase}${raw.startsWith('/') ? '' : '/'}${raw}`
if (/^\/api\//i.test(raw)) return `${apiBase}${raw.slice(4)}`
return raw.startsWith('/') ? raw : `/${raw}`
}
const avatarFallback = (name) => {
@@ -270,7 +270,7 @@ const props = defineProps({
const nfInt = new Intl.NumberFormat('zh-CN', { maximumFractionDigits: 0 })
const formatInt = (n) => nfInt.format(Math.round(Number(n) || 0))
const mediaBase = process.client ? 'http://localhost:8000' : ''
const apiBase = useApiBase()
const resolveMediaUrl = (value, opts = { backend: false }) => {
const raw = String(value || '').trim()
if (!raw) return ''
@@ -278,13 +278,15 @@ const resolveMediaUrl = (value, opts = { backend: false }) => {
try {
const host = new URL(raw).hostname.toLowerCase()
if (host.endsWith('.qpic.cn') || host.endsWith('.qlogo.cn')) {
return `${mediaBase}/api/chat/media/proxy_image?url=${encodeURIComponent(raw)}`
return `${apiBase}/chat/media/proxy_image?url=${encodeURIComponent(raw)}`
}
} catch {}
return raw
}
if (opts.backend || raw.startsWith('/api/')) {
return `${mediaBase}${raw.startsWith('/') ? '' : '/'}${raw}`
if (/^\/api\//i.test(raw)) return `${apiBase}${raw.slice(4)}`
if (opts.backend) {
const origin = apiBase.endsWith('/api') ? apiBase.slice(0, -4) : apiBase
return `${origin}${raw.startsWith('/') ? '' : '/'}${raw}`
}
return raw.startsWith('/') ? raw : `/${raw}`
}
@@ -166,7 +166,7 @@ const formatScore = (n) => {
}
const clampPct = (n) => Math.max(0, Math.min(100, Math.round(Number(n || 0) * 100)))
const mediaBase = process.client ? 'http://localhost:8000' : ''
const apiBase = useApiBase()
const resolveMediaUrl = (value) => {
const raw = String(value || '').trim()
if (!raw) return ''
@@ -174,12 +174,13 @@ const resolveMediaUrl = (value) => {
try {
const host = new URL(raw).hostname.toLowerCase()
if (host.endsWith('.qpic.cn') || host.endsWith('.qlogo.cn')) {
return `${mediaBase}/api/chat/media/proxy_image?url=${encodeURIComponent(raw)}`
return `${apiBase}/chat/media/proxy_image?url=${encodeURIComponent(raw)}`
}
} catch {}
return raw
}
return `${mediaBase}${raw.startsWith('/') ? '' : '/'}${raw}`
if (/^\/api\//i.test(raw)) return `${apiBase}${raw.slice(4)}`
return raw.startsWith('/') ? raw : `/${raw}`
}
const avatarFallback = (name) => {
@@ -944,6 +944,8 @@ const formatDurationZh = (seconds) => {
return h > 0 ? `${d}${h}小时` : `${d}`
}
const apiBase = useApiBase()
const resolveMediaUrl = (value, opts = { backend: false }) => {
const raw = String(value || '').trim()
if (!raw) return ''
@@ -952,13 +954,12 @@ const resolveMediaUrl = (value, opts = { backend: false }) => {
try {
const host = new URL(raw).hostname.toLowerCase()
if (host.endsWith('.qpic.cn') || host.endsWith('.qlogo.cn')) {
// Keep same-origin `/api` so Nuxt devProxy / backend-mounted UI both work.
return `/api/chat/media/proxy_image?url=${encodeURIComponent(raw)}`
return `${apiBase}/chat/media/proxy_image?url=${encodeURIComponent(raw)}`
}
} catch {}
return raw
}
// Keep `/api/...` as same-origin (avoid hardcoding backend host like `localhost:8000`).
if (/^\/api\//i.test(raw)) return `${apiBase}${raw.slice(4)}`
return raw.startsWith('/') ? raw : `/${raw}`
}
@@ -78,8 +78,7 @@ const onAvatarError = () => { avatarOk.value = false }
const displayNameShown = computed(() => String(props.displayName || props.maskedName || '').trim())
// Keep the same behavior as the chat page: media (including avatars) comes from backend :8000 in dev.
const mediaBase = process.client ? 'http://localhost:8000' : ''
const apiBase = useApiBase()
const resolveMediaUrl = (value) => {
const raw = String(value || '').trim()
if (!raw) return ''
@@ -88,13 +87,13 @@ const resolveMediaUrl = (value) => {
try {
const host = new URL(raw).hostname.toLowerCase()
if (host.endsWith('.qpic.cn') || host.endsWith('.qlogo.cn')) {
return `${mediaBase}/api/chat/media/proxy_image?url=${encodeURIComponent(raw)}`
return `${apiBase}/chat/media/proxy_image?url=${encodeURIComponent(raw)}`
}
} catch {}
return raw
}
// Most backend fields are like "/api/...", so just prefix.
return `${mediaBase}${raw.startsWith('/') ? '' : '/'}${raw}`
if (/^\/api\//i.test(raw)) return `${apiBase}${raw.slice(4)}`
return raw.startsWith('/') ? raw : `/${raw}`
}
const resolvedAvatarUrl = computed(() => resolveMediaUrl(props.avatarUrl))
+1 -6
View File
@@ -1,15 +1,10 @@
// API请求组合式函数
export const useApi = () => {
const config = useRuntimeConfig()
const baseURL = useApiBase()
// 基础请求函数
const request = async (url, options = {}) => {
try {
// Default to same-origin `/api` so Nuxt devProxy / backend-mounted UI both work.
// Override via `NUXT_PUBLIC_API_BASE`, e.g. `http://127.0.0.1:8000/api`.
const apiBase = String(config?.public?.apiBase || '').trim()
const baseURL = (apiBase ? apiBase : '/api').replace(/\/$/, '')
const response = await $fetch(url, {
baseURL,
...options,
+30 -30
View File
@@ -3630,8 +3630,8 @@ const startExportPolling = (exportId) => {
if (!exportId) return
if (process.client && typeof window !== 'undefined' && typeof EventSource !== 'undefined') {
const base = 'http://localhost:8000'
const url = `${base}/api/chat/exports/${encodeURIComponent(String(exportId))}/events`
const apiBase = useApiBase()
const url = `${apiBase}/chat/exports/${encodeURIComponent(String(exportId))}/events`
try {
exportEventSource = new EventSource(url)
exportEventSource.onmessage = (ev) => {
@@ -3889,8 +3889,8 @@ watch(
)
const getExportDownloadUrl = (exportId) => {
const base = process.client ? 'http://localhost:8000' : ''
return `${base}/api/chat/exports/${encodeURIComponent(String(exportId || ''))}/download`
const apiBase = useApiBase()
return `${apiBase}/chat/exports/${encodeURIComponent(String(exportId || ''))}/download`
}
const startChatExport = async () => {
@@ -6089,7 +6089,7 @@ const normalizeMessage = (msg) => {
const sender = isSent ? '我' : (msg.senderDisplayName || msg.senderUsername || selectedContact.value?.name || '')
const fallbackAvatar = (!isSent && !selectedContact.value?.isGroup) ? (selectedContact.value?.avatar || null) : null
const mediaBase = process.client ? 'http://localhost:8000' : ''
const apiBase = useApiBase()
const normalizeMaybeUrl = (u) => (typeof u === 'string' ? u.trim() : '')
const isUsableMediaUrl = (u) => {
const v = normalizeMaybeUrl(u)
@@ -6121,7 +6121,7 @@ const normalizeMessage = (msg) => {
try {
const host = new URL(u).hostname.toLowerCase()
if (host.endsWith('.qpic.cn') || host.endsWith('.qlogo.cn')) {
return `${mediaBase}/api/chat/media/proxy_image?url=${encodeURIComponent(u)}`
return `${apiBase}/chat/media/proxy_image?url=${encodeURIComponent(u)}`
}
} catch {}
return u
@@ -6129,15 +6129,15 @@ const normalizeMessage = (msg) => {
const fromUsername = String(msg.fromUsername || '').trim()
const fromAvatar = fromUsername
? `${mediaBase}/api/chat/avatar?account=${encodeURIComponent(selectedAccount.value || '')}&username=${encodeURIComponent(fromUsername)}`
? `${apiBase}/chat/avatar?account=${encodeURIComponent(selectedAccount.value || '')}&username=${encodeURIComponent(fromUsername)}`
: (() => {
// App/web link shares may not provide `fromUsername` (sourceusername), so we don't have a WeChat avatar.
// Fall back to a best-effort website favicon fetched via backend.
const href = String(msg.url || '').trim()
return href ? `${mediaBase}/api/chat/media/favicon?url=${encodeURIComponent(href)}` : ''
return href ? `${apiBase}/chat/media/favicon?url=${encodeURIComponent(href)}` : ''
})()
const localEmojiUrl = msg.emojiMd5 ? `${mediaBase}/api/chat/media/emoji?account=${encodeURIComponent(selectedAccount.value || '')}&md5=${encodeURIComponent(msg.emojiMd5)}&username=${encodeURIComponent(selectedContact.value?.username || '')}` : ''
const localEmojiUrl = msg.emojiMd5 ? `${apiBase}/chat/media/emoji?account=${encodeURIComponent(selectedAccount.value || '')}&md5=${encodeURIComponent(msg.emojiMd5)}&username=${encodeURIComponent(selectedContact.value?.username || '')}` : ''
const localImageUrl = (() => {
if (!msg.imageMd5 && !msg.imageFileId) return ''
const parts = [
@@ -6146,7 +6146,7 @@ const normalizeMessage = (msg) => {
msg.imageFileId ? `file_id=${encodeURIComponent(msg.imageFileId)}` : '',
`username=${encodeURIComponent(selectedContact.value?.username || '')}`,
].filter(Boolean)
return `${mediaBase}/api/chat/media/image?${parts.join('&')}`
return `${apiBase}/chat/media/image?${parts.join('&')}`
})()
const normalizedImageUrl = (() => {
const cur = (isUsableMediaUrl(msg.imageUrl) ? normalizeMaybeUrl(msg.imageUrl) : '')
@@ -6165,7 +6165,7 @@ const normalizeMessage = (msg) => {
msg.videoThumbFileId ? `file_id=${encodeURIComponent(msg.videoThumbFileId)}` : '',
`username=${encodeURIComponent(selectedContact.value?.username || '')}`,
].filter(Boolean)
return `${mediaBase}/api/chat/media/video_thumb?${parts.join('&')}`
return `${apiBase}/chat/media/video_thumb?${parts.join('&')}`
})()
const localVideoUrl = (() => {
@@ -6176,7 +6176,7 @@ const normalizeMessage = (msg) => {
msg.videoFileId ? `file_id=${encodeURIComponent(msg.videoFileId)}` : '',
`username=${encodeURIComponent(selectedContact.value?.username || '')}`,
].filter(Boolean)
return `${mediaBase}/api/chat/media/video?${parts.join('&')}`
return `${apiBase}/chat/media/video?${parts.join('&')}`
})()
const normalizedVideoThumbUrl = (isUsableMediaUrl(msg.videoThumbUrl) ? normalizeMaybeUrl(msg.videoThumbUrl) : '') || localVideoThumbUrl
@@ -6186,7 +6186,7 @@ const normalizeMessage = (msg) => {
if (msg.voiceUrl) return msg.voiceUrl
if (!serverIdStr) return ''
if (String(msg.renderType || '') !== 'voice') return ''
return `${mediaBase}/api/chat/media/voice?account=${encodeURIComponent(selectedAccount.value || '')}&server_id=${encodeURIComponent(serverIdStr)}`
return `${apiBase}/chat/media/voice?account=${encodeURIComponent(selectedAccount.value || '')}&server_id=${encodeURIComponent(serverIdStr)}`
})()
const remoteFromServer = (
@@ -6228,7 +6228,7 @@ const normalizeMessage = (msg) => {
const quoteServerIdStr = String(msg.quoteServerId || '').trim()
const quoteTypeStr = String(msg.quoteType || '').trim()
const quoteVoiceUrl = quoteServerIdStr
? `${mediaBase}/api/chat/media/voice?account=${encodeURIComponent(selectedAccount.value || '')}&server_id=${encodeURIComponent(quoteServerIdStr)}`
? `${apiBase}/chat/media/voice?account=${encodeURIComponent(selectedAccount.value || '')}&server_id=${encodeURIComponent(quoteServerIdStr)}`
: ''
const quoteImageUrl = (() => {
if (!quoteServerIdStr) return ''
@@ -6239,7 +6239,7 @@ const normalizeMessage = (msg) => {
`server_id=${encodeURIComponent(quoteServerIdStr)}`,
convUsername ? `username=${encodeURIComponent(convUsername)}` : ''
].filter(Boolean)
return parts.length ? `${mediaBase}/api/chat/media/image?${parts.join('&')}` : ''
return parts.length ? `${apiBase}/chat/media/image?${parts.join('&')}` : ''
})()
const quoteThumbUrl = (() => {
const raw = isUsableMediaUrl(msg.quoteThumbUrl) ? normalizeMaybeUrl(msg.quoteThumbUrl) : ''
@@ -6249,7 +6249,7 @@ const normalizeMessage = (msg) => {
try {
const host = new URL(raw).hostname.toLowerCase()
if (host.endsWith('.qpic.cn') || host.endsWith('.qlogo.cn')) {
return `${mediaBase}/api/chat/media/proxy_image?url=${encodeURIComponent(raw)}`
return `${apiBase}/chat/media/proxy_image?url=${encodeURIComponent(raw)}`
}
} catch {}
return raw
@@ -6744,7 +6744,7 @@ const formatChatHistoryVideoDuration = (value) => {
}
const normalizeChatHistoryRecordItem = (rec) => {
const mediaBase = process.client ? 'http://localhost:8000' : ''
const apiBase = useApiBase()
const account = encodeURIComponent(selectedAccount.value || '')
const username = encodeURIComponent(selectedContact.value?.username || '')
@@ -6768,7 +6768,7 @@ const normalizeChatHistoryRecordItem = (rec) => {
})()
if (fileId) {
previewCandidates.push(
`${mediaBase}/api/chat/media/image?account=${account}&file_id=${encodeURIComponent(fileId)}&username=${username}`
`${apiBase}/chat/media/image?account=${account}&file_id=${encodeURIComponent(fileId)}&username=${username}`
)
}
@@ -6782,7 +6782,7 @@ const normalizeChatHistoryRecordItem = (rec) => {
srcServerId ? `server_id=${encodeURIComponent(srcServerId)}` : '',
`username=${username}`
].filter(Boolean)
previewCandidates.push(`${mediaBase}/api/chat/media/image?${previewParts.join('&')}`)
previewCandidates.push(`${apiBase}/chat/media/image?${previewParts.join('&')}`)
}
out._linkPreviewCandidates = previewCandidates
@@ -6793,8 +6793,8 @@ const normalizeChatHistoryRecordItem = (rec) => {
const fromUsername = String(out.fromUsername || '').trim()
out.fromUsername = fromUsername
out.fromAvatar = fromUsername
? `${mediaBase}/api/chat/avatar?account=${account}&username=${encodeURIComponent(fromUsername)}`
: (linkUrl ? `${mediaBase}/api/chat/media/favicon?url=${encodeURIComponent(linkUrl)}` : '')
? `${apiBase}/chat/avatar?account=${account}&username=${encodeURIComponent(fromUsername)}`
: (linkUrl ? `${apiBase}/chat/media/favicon?url=${encodeURIComponent(linkUrl)}` : '')
out._fromAvatarLast = out.fromAvatar
out._fromAvatarImgOk = false
out._fromAvatarImgError = false
@@ -6804,17 +6804,17 @@ const normalizeChatHistoryRecordItem = (rec) => {
out.videoDuration = String(out.duration || '').trim()
const thumbCandidates = []
if (out.videoMd5) {
thumbCandidates.push(`${mediaBase}/api/chat/media/video_thumb?account=${account}&md5=${encodeURIComponent(out.videoMd5)}&username=${username}`)
thumbCandidates.push(`${apiBase}/chat/media/video_thumb?account=${account}&md5=${encodeURIComponent(out.videoMd5)}&username=${username}`)
}
if (out.videoThumbMd5 && out.videoThumbMd5 !== out.videoMd5) {
thumbCandidates.push(`${mediaBase}/api/chat/media/video_thumb?account=${account}&md5=${encodeURIComponent(out.videoThumbMd5)}&username=${username}`)
thumbCandidates.push(`${apiBase}/chat/media/video_thumb?account=${account}&md5=${encodeURIComponent(out.videoThumbMd5)}&username=${username}`)
}
out._videoThumbCandidates = thumbCandidates
out._videoThumbCandidateIndex = 0
out._videoThumbError = false
out.videoThumbUrl = thumbCandidates[0] || ''
out.videoUrl = out.videoMd5
? `${mediaBase}/api/chat/media/video?account=${account}&md5=${encodeURIComponent(out.videoMd5)}&username=${username}`
? `${apiBase}/chat/media/video?account=${account}&md5=${encodeURIComponent(out.videoMd5)}&username=${username}`
: ''
if (!out.content || /^\[.+\]$/.test(String(out.content || '').trim())) out.content = '[视频]'
} else if (out.renderType === 'emoji') {
@@ -6823,7 +6823,7 @@ const normalizeChatHistoryRecordItem = (rec) => {
const remoteAesKey = String(out.aeskey || '').trim()
out.emojiRemoteUrl = remoteEmojiUrl
out.emojiUrl = out.emojiMd5
? `${mediaBase}/api/chat/media/emoji?account=${account}&md5=${encodeURIComponent(out.emojiMd5)}&username=${username}${remoteEmojiUrl ? `&emoji_url=${encodeURIComponent(remoteEmojiUrl)}` : ''}${remoteAesKey ? `&aes_key=${encodeURIComponent(remoteAesKey)}` : ''}`
? `${apiBase}/chat/media/emoji?account=${account}&md5=${encodeURIComponent(out.emojiMd5)}&username=${username}${remoteEmojiUrl ? `&emoji_url=${encodeURIComponent(remoteEmojiUrl)}` : ''}${remoteAesKey ? `&aes_key=${encodeURIComponent(remoteAesKey)}` : ''}`
: ''
if (!out.content || /^\[.+\]$/.test(String(out.content || '').trim())) out.content = '[表情]'
} else if (out.renderType === 'image') {
@@ -6835,7 +6835,7 @@ const normalizeChatHistoryRecordItem = (rec) => {
srcServerId ? `server_id=${encodeURIComponent(srcServerId)}` : '',
`username=${username}`
].filter(Boolean)
out.imageUrl = imgParts.length ? `${mediaBase}/api/chat/media/image?${imgParts.join('&')}` : ''
out.imageUrl = imgParts.length ? `${apiBase}/chat/media/image?${imgParts.join('&')}` : ''
if (!out.content || /^\[.+\]$/.test(String(out.content || '').trim())) out.content = '[图片]'
}
@@ -7190,7 +7190,7 @@ const resolveChatHistoryLinkRecord = async (rec) => {
const content = String(resp.content || '').trim()
const url = String(resp.url || '').trim()
const from = String(resp.from || '').trim()
const mediaBase = process.client ? 'http://localhost:8000' : ''
const apiBase = useApiBase()
const normalizePreviewUrl = (u) => {
const raw = String(u || '').trim()
if (!raw) return ''
@@ -7199,7 +7199,7 @@ const resolveChatHistoryLinkRecord = async (rec) => {
try {
const host = new URL(raw).hostname.toLowerCase()
if (host.endsWith('.qpic.cn') || host.endsWith('.qlogo.cn')) {
return `${mediaBase}/api/chat/media/proxy_image?url=${encodeURIComponent(raw)}`
return `${apiBase}/chat/media/proxy_image?url=${encodeURIComponent(raw)}`
}
} catch {}
return raw
@@ -7214,8 +7214,8 @@ const resolveChatHistoryLinkRecord = async (rec) => {
const fromUsername = String(resp.fromUsername || '').trim()
if (fromUsername) rec.fromUsername = fromUsername
const fromAvatarUrl = fromUsername
? `${mediaBase}/api/chat/avatar?account=${encodeURIComponent(selectedAccount.value || '')}&username=${encodeURIComponent(fromUsername)}`
: (url ? `${mediaBase}/api/chat/media/favicon?url=${encodeURIComponent(url)}` : '')
? `${apiBase}/chat/avatar?account=${encodeURIComponent(selectedAccount.value || '')}&username=${encodeURIComponent(fromUsername)}`
: (url ? `${apiBase}/chat/media/favicon?url=${encodeURIComponent(url)}` : '')
if (fromAvatarUrl) {
const last = String(rec._fromAvatarLast || '').trim()
rec.fromAvatar = fromAvatarUrl
+4 -2
View File
@@ -775,7 +775,8 @@ const handleDecrypt = async () => {
const params = new URLSearchParams()
params.set('key', formData.key)
params.set('db_storage_path', formData.db_storage_path)
const url = `http://localhost:8000/api/decrypt_stream?${params.toString()}`
const apiBase = useApiBase()
const url = `${apiBase}/decrypt_stream?${params.toString()}`
dbDecryptProgress.message = '连接中...'
const eventSource = new EventSource(url)
@@ -904,7 +905,8 @@ const decryptAllImages = async () => {
if (mediaAccount.value) params.set('account', mediaAccount.value)
if (mediaKeys.xor_key) params.set('xor_key', mediaKeys.xor_key)
if (mediaKeys.aes_key) params.set('aes_key', mediaKeys.aes_key)
const url = `http://localhost:8000/api/media/decrypt_all_stream?${params.toString()}`
const apiBase = useApiBase()
const url = `${apiBase}/media/decrypt_all_stream?${params.toString()}`
// 使用EventSource接收SSE
const eventSource = new EventSource(url)
+2 -2
View File
@@ -257,13 +257,13 @@ watch(selectedAccount, async () => {
await loadSessions()
})
const mediaBase = process.client ? 'http://localhost:8000' : ''
const apiBase = useApiBase()
const normalizeMaybeUrl = (u) => {
const raw = String(u || '').trim()
if (!raw) return ''
if (/^https?:\/\//i.test(raw) || /^blob:/i.test(raw) || /^data:/i.test(raw)) return raw
if (/^\/api\//i.test(raw)) return `${mediaBase}${raw}`
if (/^\/api\//i.test(raw)) return `${apiBase}${raw.slice(4)}`
return raw
}
+16 -16
View File
@@ -794,7 +794,7 @@ const filteredSnsUsers = computed(() => {
const pageSize = 20
const mediaBase = process.client ? 'http://localhost:8000' : ''
const apiBase = useApiBase()
// 朋友圈导出(HTML 离线 ZIP
const exportJob = ref(null)
@@ -835,8 +835,7 @@ const startSnsExportPolling = (exportId) => {
if (!exportId) return
if (process.client && typeof window !== 'undefined' && typeof EventSource !== 'undefined') {
const base = 'http://localhost:8000'
const url = `${base}/api/sns/exports/${encodeURIComponent(String(exportId))}/events`
const url = `${apiBase}/sns/exports/${encodeURIComponent(String(exportId))}/events`
try {
exportEventSource = new EventSource(url)
exportEventSource.onmessage = (ev) => {
@@ -867,8 +866,7 @@ const downloadSnsExport = (exportId) => {
if (!process.client) return
const id = String(exportId || '').trim()
if (!id) return
const base = 'http://localhost:8000'
const url = `${base}/api/sns/exports/${encodeURIComponent(id)}/download`
const url = `${apiBase}/sns/exports/${encodeURIComponent(id)}/download`
window.open(url, '_blank', 'noopener,noreferrer')
}
@@ -1109,7 +1107,7 @@ const selfInfo = ref({ wxid: '', nickname: '' })
const loadSelfInfo = async () => {
if (!selectedAccount.value) return
try {
const resp = await $fetch(`${mediaBase}/api/sns/self_info?account=${encodeURIComponent(selectedAccount.value)}`)
const resp = await $fetch(`${apiBase}/sns/self_info?account=${encodeURIComponent(selectedAccount.value)}`)
if (resp && resp.wxid) {
selfInfo.value = resp
}
@@ -1145,7 +1143,7 @@ const selectSnsUser = async (username) => {
const getArticleThumbProxyUrl = (contentUrl) => {
const u = String(contentUrl || '').trim()
if (!u) return ''
return `${mediaBase}/api/sns/article_thumb?url=${encodeURIComponent(u)}`
return `${apiBase}/sns/article_thumb?url=${encodeURIComponent(u)}`
}
const guessOfficialAccountNameFromTitle = (title) => {
@@ -1443,7 +1441,7 @@ const postAvatarUrl = (username) => {
const acc = String(selectedAccount.value || '').trim()
const u = String(username || '').trim()
if (!acc || !u) return ''
return `${mediaBase}/api/chat/avatar?account=${encodeURIComponent(acc)}&username=${encodeURIComponent(u)}`
return `${apiBase}/chat/avatar?account=${encodeURIComponent(acc)}&username=${encodeURIComponent(u)}`
}
const cleanLikeName = (v) => String(v ?? '').replace(/\u00A0/g, ' ').trim()
@@ -1460,7 +1458,7 @@ const normalizeMediaUrl = (u) => {
try {
const host = new URL(raw).hostname.toLowerCase()
if (host.endsWith('.qpic.cn') || host.endsWith('.qlogo.cn')) {
return `${mediaBase}/api/chat/media/proxy_image?url=${encodeURIComponent(raw)}`
return `${apiBase}/chat/media/proxy_image?url=${encodeURIComponent(raw)}`
}
} catch {}
return raw
@@ -1515,8 +1513,10 @@ const getSnsMediaUrl = (post, m, idx, rawUrl) => {
if (!raw) return ''
const rawLower = raw.toLowerCase()
// If backend already provides a local media endpoint, keep it as-is.
if (rawLower.startsWith('/api/') || rawLower.startsWith('blob:') || rawLower.startsWith('data:')) return raw
// If backend already provides a local media endpoint, rewrite it to the effective API base
// (so web builds with a custom API port still work).
if (rawLower.startsWith('/api/')) return `${apiBase}${raw.slice(4)}`
if (rawLower.startsWith('blob:') || rawLower.startsWith('data:')) return raw
// For Moments images/thumbnails, prefer a backend endpoint that can decrypt local cache.
if (/^https?:\/\//i.test(raw)) {
@@ -1568,7 +1568,7 @@ const getSnsMediaUrl = (post, m, idx, rawUrl) => {
// Bump this when changing backend matching logic to avoid stale cached wrong images.
parts.set('v', '9')
parts.set('url', raw)
return `${mediaBase}/api/sns/media?${parts.toString()}`
return `${apiBase}/sns/media?${parts.toString()}`
}
} catch {}
}
@@ -1589,7 +1589,7 @@ const getSnsVideoUrl = (postId, mediaId) => {
// 本地缓存视频
const acc = String(selectedAccount.value || '').trim()
if (!acc || !postId || !mediaId) return ''
return `${mediaBase}/api/sns/video?account=${encodeURIComponent(acc)}&post_id=${encodeURIComponent(postId)}&media_id=${encodeURIComponent(mediaId)}`
return `${apiBase}/sns/video?account=${encodeURIComponent(acc)}&post_id=${encodeURIComponent(postId)}&media_id=${encodeURIComponent(mediaId)}`
}
const getSnsRemoteVideoSrc = (post, m) => {
@@ -1610,7 +1610,7 @@ const getSnsRemoteVideoSrc = (post, m) => {
// When cache is disabled, bust browser caching so backend really downloads+decrypts each time.
if (!snsUseCache.value) parts.set('_t', String(Date.now()))
parts.set('v', '1')
return `${mediaBase}/api/sns/video_remote?${parts.toString()}`
return `${apiBase}/sns/video_remote?${parts.toString()}`
}
const localVideoStatus = ref({})
@@ -1726,7 +1726,7 @@ const getLivePhotoVideoSrc = (post, m, idx = 0) => {
if (!snsUseCache.value) parts.set('_t', String(Date.now()))
// Version bump for frontend cache busting when endpoint changes.
parts.set('v', '1')
return `${mediaBase}/api/sns/video_remote?${parts.toString()}`
return `${apiBase}/sns/video_remote?${parts.toString()}`
}
// 图片预览 + 候选匹配选择
@@ -2114,7 +2114,7 @@ const getProxyExternalUrl = (url) => {
// 目前难以计算enc,代理获取封面图(thumbnail
const u = String(url || '').trim()
if (!u) return ''
return `${mediaBase}/api/chat/media/proxy_image?url=${encodeURIComponent(u)}`
return `${apiBase}/chat/media/proxy_image?url=${encodeURIComponent(u)}`
}
+2 -3
View File
@@ -89,8 +89,8 @@ export const useChatRealtimeStore = defineStore('chatRealtime', () => {
if (!account) return
if (typeof EventSource === 'undefined') return
const base = 'http://localhost:8000'
const url = `${base}/api/chat/realtime/stream?account=${encodeURIComponent(account)}`
const apiBase = useApiBase()
const url = `${apiBase}/chat/realtime/stream?account=${encodeURIComponent(account)}`
try {
eventSource = new EventSource(url)
@@ -223,4 +223,3 @@ export const useChatRealtimeStore = defineStore('chatRealtime', () => {
toggle,
}
})