/** * 去掉 base64 字符串上可能重复的 data URL 前缀(部分原生壳返回带前缀的内容) */ export function normalizeBase64(content: string): string { if (!content) return '' const trimmed = content.trim() const match = trimmed.match(/^data:image\/[\w+.-]+;base64,(.+)$/i) return match ? match[1] : trimmed } /** 将裸 base64 或已带前缀的内容统一为可展示的 data URL */ export function toDataUrl(content: string, mimeType = 'image/jpeg'): string { const raw = normalizeBase64(content) if (!raw) return '' return `data:${mimeType};base64,${raw}` } /** * 将后端返回的 f_downloadpath 转为 WebView 可加载的完整地址。 * 相对路径在部分客户 APK 中无法被 van-uploader / VanImage 正确解析。 */ export function resolveResourceUrl(url?: string): string { if (!url) return '' if (url.startsWith('data:') || url.startsWith('blob:')) return url if (/^https?:\/\//i.test(url)) return url const prefix = import.meta.env.VITE_APP_RESOURCE_PATH || '' if (prefix && url.startsWith('/')) return `${prefix.replace(/\/$/, '')}${url}` if (url.startsWith('/') && typeof window !== 'undefined') return `${window.location.origin}${url}` return url } /** * 规范化附件路径为 f_downloadpath(相对路径)。 */ export function getPreviewPath(url?: string): string { if (!url) return '' const trimmed = url.trim() if (trimmed.startsWith('/resource/')) return trimmed if (/^https?:\/\//i.test(trimmed)) { try { const pathname = new URL(trimmed).pathname if (pathname.startsWith('/resource/')) return pathname } catch { // ignore } } return trimmed } /** 拉取附件二进制,供 vue-office 等客户端预览使用 */ export async function fetchResourceArrayBuffer(url: string): Promise { const response = await fetch(url, { credentials: 'include' }) if (!response.ok) throw new Error(`文件加载失败(${response.status})`) return response.arrayBuffer() } /** 拉取纯文本附件 */ export async function fetchResourceText(url: string): Promise { const response = await fetch(url, { credentials: 'include' }) if (!response.ok) throw new Error(`文件加载失败(${response.status})`) return response.text() }