From 4201d656b1c117bfdfec38eca96a85ac6c613a58 Mon Sep 17 00:00:00 2001 From: wuzilong <704541015@qq.com> Date: Wed, 16 Sep 2026 21:11:59 +0800 Subject: [PATCH] feat(miniprogram): add invoice thumb helper utilities. Co-authored-by: Cursor --- miniprogram/scripts/test-bx-invoice-thumb.mjs | 53 +++++++++++ miniprogram/utils/bxInvoiceThumb.js | 94 +++++++++++++++++++ 2 files changed, 147 insertions(+) create mode 100644 miniprogram/scripts/test-bx-invoice-thumb.mjs create mode 100644 miniprogram/utils/bxInvoiceThumb.js diff --git a/miniprogram/scripts/test-bx-invoice-thumb.mjs b/miniprogram/scripts/test-bx-invoice-thumb.mjs new file mode 100644 index 000000000..66d2fb59b --- /dev/null +++ b/miniprogram/scripts/test-bx-invoice-thumb.mjs @@ -0,0 +1,53 @@ +import assert from 'node:assert/strict' +import { + isImageFileType, + fileExtLabel, + formatInvoiceDateYmd, + buildAttachItemsFromInvoiceRows, + mergeInvoicePicksByDraftKeys +} from '../utils/bxInvoiceThumb.js' + +assert.equal(isImageFileType('image/jpeg'), true) +assert.equal(isImageFileType('application/pdf'), false) +assert.equal(fileExtLabel('a.PDF'), 'PDF') +assert.equal(formatInvoiceDateYmd('2026-09-16T12:00:00'), '2026-09-16') +assert.equal(formatInvoiceDateYmd(''), '—') + +const rows = [ + { + id: 'inv1', + amount: 100, + createTime: '2026-09-01T10:00:00', + attachments: [ + { id: 'a1', fileName: 'x.jpg', fileType: 'image/jpeg' }, + { id: 'a2', fileName: 'y.pdf', fileType: 'application/pdf' } + ] + } +] +const items = buildAttachItemsFromInvoiceRows(rows) +assert.equal(items.length, 2) +assert.equal(items[0].key, 'inv1:a1') +assert.equal(items[1].fileName, 'y.pdf') + +const prev = [ + { + key: 'inv9:a9', + invoiceId: 'inv9', + attachmentId: 'a9', + amount: 9, + createTime: '2026-08-01', + fileName: 'old.jpg', + fileType: 'image/jpeg', + previewUrl: 'http://old' + } +] +const merged = mergeInvoicePicksByDraftKeys( + ['inv9:a9', 'inv1:a1'], + items, + prev +) +assert.equal(merged.length, 2) +assert.ok(merged.some((p) => p.key === 'inv9:a9' && p.previewUrl === 'http://old')) +assert.ok(merged.some((p) => p.key === 'inv1:a1')) + +console.log('bxInvoiceThumb ok') diff --git a/miniprogram/utils/bxInvoiceThumb.js b/miniprogram/utils/bxInvoiceThumb.js new file mode 100644 index 000000000..2764aee73 --- /dev/null +++ b/miniprogram/utils/bxInvoiceThumb.js @@ -0,0 +1,94 @@ +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) +}