236 lines
7.2 KiB
TypeScript
236 lines
7.2 KiB
TypeScript
/**
|
||
* CRM 附件解析与下载 — 门户 MinIO(fileUrl 即存储键/URL,不再走 CRM /resource/oss/listByIds)
|
||
*/
|
||
import {
|
||
downloadAttachmentFile,
|
||
openAttachmentPreviewInNewTab
|
||
} from '@/utils/download-attachment'
|
||
|
||
export type CrmAttachmentRef = {
|
||
ossId: string
|
||
category?: string
|
||
}
|
||
|
||
export type CrmAttachmentFile = {
|
||
ossId: string
|
||
name: string
|
||
url: string
|
||
category?: string
|
||
}
|
||
|
||
/** 解析 attachmentFiles 字段(JSON 数组或逗号分隔 URL) */
|
||
export function parseCrmAttachmentField(
|
||
fileStr: string | null | undefined
|
||
): CrmAttachmentRef[] {
|
||
const raw = (fileStr || '').trim()
|
||
if (!raw) return []
|
||
|
||
if (raw.startsWith('[')) {
|
||
try {
|
||
const parsed = JSON.parse(raw)
|
||
if (Array.isArray(parsed)) {
|
||
return parsed.map((item: unknown) => {
|
||
if (typeof item === 'string') {
|
||
return { ossId: item, category: 'other' }
|
||
}
|
||
const obj = item as Record<string, unknown>
|
||
return {
|
||
ossId: String(obj.ossId || obj.url || ''),
|
||
category: (obj.category as string) || 'other'
|
||
}
|
||
}).filter((a) => a.ossId)
|
||
}
|
||
} catch {
|
||
// fall through
|
||
}
|
||
}
|
||
|
||
return raw
|
||
.split(',')
|
||
.map((s) => s.trim())
|
||
.filter(Boolean)
|
||
.map((ossId) => ({ ossId, category: 'other' }))
|
||
}
|
||
|
||
/** 从 ossId/url 推导展示文件名(完整 URL 与相对路径均取最后一段) */
|
||
export function crmAttachmentDisplayName(ossId: string, index = 0): string {
|
||
const id = (ossId || '').trim()
|
||
if (!id) return `附件${index + 1}`
|
||
|
||
// 含路径分隔符时取末段(兼容 http(s) URL 与 MinIO 相对键如 crm/contract/.../uuid.pdf)
|
||
if (id.includes('/')) {
|
||
const seg = (id.split('/').pop() || '').split('?')[0]
|
||
try {
|
||
return decodeURIComponent(seg) || `附件${index + 1}`
|
||
} catch {
|
||
return seg || `附件${index + 1}`
|
||
}
|
||
}
|
||
|
||
if (id.length > 12) return `附件_${id.substring(0, 8)}`
|
||
return id
|
||
}
|
||
|
||
/** 将解析结果转为 UI 文件项(门户直接存 MinIO URL,无需 listByIds) */
|
||
export function resolveCrmAttachmentFiles(
|
||
refs: CrmAttachmentRef[]
|
||
): CrmAttachmentFile[] {
|
||
return refs.map((att, index) => {
|
||
const ossId = att.ossId
|
||
return {
|
||
ossId,
|
||
name: crmAttachmentDisplayName(ossId, index),
|
||
url: ossId,
|
||
category: att.category || 'other'
|
||
}
|
||
})
|
||
}
|
||
|
||
/** 兼容旧 loadAttachmentInfos:批量解析并写入 cache Map */
|
||
export function fillAttachmentInfoCache(
|
||
ossIds: string[],
|
||
cache: Map<string, { name: string; url: string }>
|
||
): void {
|
||
ossIds.forEach((id, index) => {
|
||
const key = (id || '').trim()
|
||
if (!key || cache.has(key)) return
|
||
cache.set(key, {
|
||
name: crmAttachmentDisplayName(key, index),
|
||
url: key
|
||
})
|
||
})
|
||
}
|
||
|
||
export type ContractAttachmentItem = {
|
||
uid: string
|
||
name: string
|
||
ossId?: string
|
||
url?: string
|
||
localPreview?: string
|
||
status: 'uploading' | 'success' | 'error'
|
||
category: 'single_seal' | 'double_seal' | 'other'
|
||
}
|
||
|
||
/** 合同附件列表 → 保存 API 用的 JSON 字符串 */
|
||
export function serializeContractAttachments(items: ContractAttachmentItem[]): string {
|
||
const payload = items
|
||
.filter((file) => file.status === 'success' && file.ossId)
|
||
.map((file) => ({
|
||
ossId: String(file.ossId),
|
||
category: file.category || 'other'
|
||
}))
|
||
return payload.length ? JSON.stringify(payload) : ''
|
||
}
|
||
|
||
/** 合同附件列表 → uploadAttachments 接口 payload */
|
||
export function mapContractAttachmentsPayload(items: ContractAttachmentItem[]) {
|
||
return items
|
||
.filter((file) => file.status === 'success' && file.ossId)
|
||
.map((file) => ({
|
||
ossId: String(file.ossId),
|
||
category: file.category || 'other'
|
||
}))
|
||
}
|
||
|
||
/** MinIO 相对路径 → 浏览器可访问 URL */
|
||
export function resolveCrmAttachmentUrl(fileUrl: string): string {
|
||
const trimmed = String(fileUrl || '').trim()
|
||
if (!trimmed) return ''
|
||
if (/^https?:\/\//i.test(trimmed)) return trimmed
|
||
const base = String(import.meta.env.VITE_MINIO_BASE_URL || '').replace(/\/$/, '')
|
||
return base ? `${base}/${trimmed.replace(/^\//, '')}` : trimmed
|
||
}
|
||
|
||
export function isCrmImageFile(file: { name?: string; url?: string; ossId?: string }): boolean {
|
||
const name = file.name || ''
|
||
if (/\.(jpg|jpeg|png|gif|bmp|webp)$/i.test(name)) return true
|
||
const src = file.url || file.ossId || ''
|
||
return /\.(jpg|jpeg|png|gif|bmp|webp)(\?|$)/i.test(src)
|
||
}
|
||
|
||
export function isCrmPdfFile(file: { name?: string; url?: string; ossId?: string }): boolean {
|
||
const name = file.name || ''
|
||
if (/\.pdf$/i.test(name)) return true
|
||
const src = file.url || file.ossId || ''
|
||
return /\.pdf(\?|$)/i.test(src)
|
||
}
|
||
|
||
export function getCrmFileExt(fileName: string): string {
|
||
const ext = fileName.split('.').pop()?.toUpperCase()
|
||
return ext || 'FILE'
|
||
}
|
||
|
||
const CRM_ATTACHMENT_CATEGORY_LABELS: Record<string, string> = {
|
||
single_seal: '单章附件',
|
||
double_seal: '双章附件',
|
||
other: '其他附件'
|
||
}
|
||
|
||
/** 附件分类 → 展示文案 */
|
||
export function getCrmAttachmentCategoryLabel(category?: string): string {
|
||
return CRM_ATTACHMENT_CATEGORY_LABELS[category || 'other'] || CRM_ATTACHMENT_CATEGORY_LABELS.other
|
||
}
|
||
|
||
/** 下载 CRM 附件(fileUrl / ossId 均为 MinIO 路径或完整 URL) */
|
||
export async function downloadCrmAttachment(file: {
|
||
ossId?: string
|
||
url?: string
|
||
name?: string
|
||
fileName?: string
|
||
}) {
|
||
const fileUrl = String(file.url || file.ossId || '').trim()
|
||
if (!fileUrl) return
|
||
const fileName = String(file.fileName || file.name || '').trim() || crmAttachmentDisplayName(fileUrl)
|
||
await downloadAttachmentFile({ fileUrl, fileName }, { fullFileUrl: resolveCrmAttachmentUrl })
|
||
}
|
||
|
||
/** CRM 附件在新标签页预览(图片 HTML 内嵌 / PDF 预签名直开) */
|
||
export async function openCrmAttachmentPreviewInNewTab(file: {
|
||
ossId?: string
|
||
url?: string
|
||
name?: string
|
||
fileName?: string
|
||
}) {
|
||
const fileUrl = String(file.url || file.ossId || '').trim()
|
||
if (!fileUrl) return false
|
||
const fileName =
|
||
String(file.fileName || file.name || '').trim() || crmAttachmentDisplayName(fileUrl)
|
||
const kind = isCrmImageFile({ name: fileName, url: fileUrl, ossId: fileUrl })
|
||
? 'image'
|
||
: isCrmPdfFile({ name: fileName, url: fileUrl, ossId: fileUrl })
|
||
? 'pdf'
|
||
: 'other'
|
||
return openAttachmentPreviewInNewTab(
|
||
{ fileUrl, fileName },
|
||
{ fullFileUrl: resolveCrmAttachmentUrl, kind }
|
||
)
|
||
}
|
||
|
||
/** el-upload 回显:从 attachmentFiles 字符串构建 file-list */
|
||
export function buildUploadFileListFromField(
|
||
fileStr: string | null | undefined
|
||
): Array<Record<string, unknown>> {
|
||
const refs = parseCrmAttachmentField(fileStr)
|
||
return refs.map((att, index) => ({
|
||
uid: `existing_${index}_${Date.now()}`,
|
||
name: crmAttachmentDisplayName(att.ossId, index),
|
||
url: att.ossId,
|
||
ossId: att.ossId,
|
||
category: att.category || 'other',
|
||
status: 'success'
|
||
}))
|
||
}
|
||
|
||
/** 从 el-upload file-list 收集 attachmentFiles JSON(供保存 API) */
|
||
export function serializeUploadAttachments(
|
||
files: Array<{ status?: string; ossId?: string; url?: string; category?: string }>
|
||
): string {
|
||
const items = files
|
||
.filter((f) => f.status === 'success' && (f.ossId || f.url))
|
||
.map((f) => ({
|
||
ossId: String(f.ossId || f.url),
|
||
category: f.category || 'other'
|
||
}))
|
||
return items.length ? JSON.stringify(items) : ''
|
||
}
|