export interface AttachmentFile { name: string; url: string; } const FILE_EXT_PATTERN = /\.(pdf|docx?|xlsx?|pptx?|csv|txt|zip|rar|7z|jpe?g|png|gif|webp|bmp|svg|mp4|mp3|wav)$/i; /** * 从文件地址中取出展示用的文件名 */ export function getAttachmentName(url?: string) { if (!url) { return '未命名文件'; } const cleanUrl = url.split('?')[0]; const name = cleanUrl.split('/').pop() || cleanUrl; try { return decodeURIComponent(name); } catch (e) { return name; } } /** * 判断字符串是否像文件地址(http(s)、协议相对地址、带扩展名的相对路径) */ export function isFileUrl(value?: string) { const text = value?.trim(); if (!text) { return false; } if (/^(https?:)?\/\//i.test(text)) { return true; } return FILE_EXT_PATTERN.test(text) && !/\s/.test(text); } export function isImageFile(url?: string) { const text = (url || '').split('?')[0]; return /\.(jpe?g|png|gif|webp|bmp|svg)$/i.test(text); } const normalizeEntry = (item: unknown): AttachmentFile | null => { if (typeof item === 'string') { return isFileUrl(item) ? { name: getAttachmentName(item), url: item.trim() } : null; } if (item && typeof item === 'object') { const record = item as Record; const rawUrl = [ record.url, record.fileUrl, record.downloadUrl, record.path ].find((value) => typeof value === 'string' && isFileUrl(value as string)); if (typeof rawUrl !== 'string') { return null; } const rawName = [record.name, record.fileName, record.originalName].find( (value) => typeof value === 'string' && (value as string).trim() ); return { name: (rawName as string | undefined) || getAttachmentName(rawUrl), url: rawUrl.trim() }; } return null; }; /** * 把后端存储的附件字段解析成文件列表。 * 兼容:单个地址、JSON 数组字符串、对象数组(name/url、fileName/fileUrl 等) */ export function parseAttachments(value: unknown): AttachmentFile[] { if (value === null || value === undefined || value === '') { return []; } if (typeof value === 'string') { const text = value.trim(); if (!text) { return []; } if (text.startsWith('[') || text.startsWith('{')) { try { return parseAttachments(JSON.parse(text)); } catch (e) { // 解析失败时按普通地址处理 } } const single = normalizeEntry(text); return single ? [single] : []; } if (Array.isArray(value)) { return value .map((item) => normalizeEntry(item)) .filter((item): item is AttachmentFile => !!item); } const single = normalizeEntry(value); return single ? [single] : []; } /** * 把附件字段解析结果转换成图片地址列表 */ export function parseAttachmentImages(value: unknown) { const urls: string[] = []; parseAttachments(value).forEach((item) => { if (isImageFile(item.url)) { urls.push(item.url); } }); if (!urls.length && typeof value === 'string' && isImageFile(value)) { urls.push(value); } return urls; }