export function isImageFileType(fileType) { return !!(fileType && String(fileType).startsWith('image/')) } export function fileExtLabel(name) { const ext = String(name || '').split('.').pop() return ext ? ext.toUpperCase() : 'FILE' } /** 展示用 YYYY-MM-DD;无有效日期返回 — */ export function formatInvoiceDateYmd(value) { if (value == null || value === '') return '—' const s = String(value).trim() const m = s.match(/^(\d{4}-\d{2}-\d{2})/) if (m) return m[1] const d = new Date(s) if (Number.isNaN(d.getTime())) return '—' const y = d.getFullYear() const mo = String(d.getMonth() + 1).padStart(2, '0') const day = String(d.getDate()).padStart(2, '0') return `${y}-${mo}-${day}` } function invoiceFileList(inv) { const raw = Array.isArray(inv?.attachments) ? inv.attachments : [] if (raw.length) { return raw.map((a) => ({ id: String(a.id || a.attachmentId || ''), fileName: a.fileName || a.file_name || '发票附件', fileType: a.fileType || a.file_type || '' })) } const aid = inv?.attachmentId || inv?.attachment_id if (aid) { return [ { id: String(aid), fileName: inv.fileName || inv.file_name || '发票附件', fileType: inv.fileType || inv.file_type || '' } ] } return [] } /** * 发票列表行 → 附件粒度选项 * @returns {{ key, invoiceId, attachmentId, amount, createTime, fileName, fileType, previewUrl, previewLoading }[]} */ export function buildAttachItemsFromInvoiceRows(rows) { const items = [] for (const row of rows || []) { const createTime = row.createTime || row.create_time || '' for (const file of invoiceFileList(row)) { if (!file.id) continue items.push({ key: `${row.id}:${file.id}`, invoiceId: String(row.id), attachmentId: file.id, amount: row.amount, createTime, fileName: file.fileName, fileType: file.fileType, previewUrl: '', previewLoading: false }) } } return items } /** * 确定勾选:已加载项优先;draft 中仍勾选但不在已加载列表的旧已选项保留(防分页冲掉) */ export function mergeInvoicePicksByDraftKeys(draftKeys, attachItems, previousPicks) { const set = new Set(draftKeys || []) const fromLoaded = (attachItems || []).filter((i) => set.has(i.key)) const loadedKeys = new Set(fromLoaded.map((i) => i.key)) const fromPrev = (previousPicks || []).filter( (p) => set.has(p.key) && !loadedKeys.has(p.key) ) const byKey = new Map() for (const p of fromPrev) byKey.set(p.key, { ...p }) for (const i of fromLoaded) { const prev = byKey.get(i.key) byKey.set(i.key, { ...i, previewUrl: i.previewUrl || prev?.previewUrl || '' }) } return Array.from(set) .map((k) => byKey.get(k)) .filter(Boolean) }