Files
tuanwei-admin/src/views/gxmu/utils/attachment.ts
T
weicw c81e24c977 feat(gxmu): 审核列表「查看数据」按申报表分组展示并新增上传材料页签
- 详情弹窗改为「申报表 / 上传材料」两个页签,按模块配置分组渲染字段
- 明细数组(团队成员、指导老师、评审成员、团员记录、成果归档等)渲染为表格
- 新增附件解析工具与只读附件列表组件,兼容单地址、JSON 数组、对象数组
- 未来学术之星展示经费/中期/结题/成果归档材料,挑战杯拉取 gxmu_tzbcy_material
- 未在配置中声明的字段兜底进「其他信息」,避免数据遗漏
2026-11-01 17:06:07 +08:00

119 lines
3.1 KiB
TypeScript

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<string, unknown>;
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;
}