feat(miniprogram): add invoice thumb helper utilities.
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
1 parent
ea4d990abb
commit
4201d656b1
2 files changed
+147
No files matched your search
@@ -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')
|
||||
@@ -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)
|
||||
}
|
||||
Reference in new issue
Block a user