diff --git a/miniprogram/api/bx.js b/miniprogram/api/bx.js index 1f3e4aeb5..98a81bdff 100644 --- a/miniprogram/api/bx.js +++ b/miniprogram/api/bx.js @@ -1,4 +1,4 @@ -import { bxRequest, bxUploadFile } from '@/utils/bxRequest' +import { bxRequest, bxUploadFile, bxUploadInvoice } from '@/utils/bxRequest' export const advanceApi = { list: (params) => bxRequest({ url: '/advances/', method: 'GET', params }), @@ -34,11 +34,19 @@ export const expenseItemApi = { export const invoiceWalletApi = { mineSummary: (params) => - bxRequest({ url: '/invoice-wallets/mine/summary', method: 'GET', params }) + bxRequest({ url: '/invoice-wallets/mine/summary', method: 'GET', params }), + list: (params) => bxRequest({ url: '/invoice-wallets', method: 'GET', params }) } export const invoiceApi = { - list: (params) => bxRequest({ url: '/invoices', method: 'GET', params }) + list: (params) => bxRequest({ url: '/invoices', method: 'GET', params }), + /** + * 上传发票(multipart)。uni-app 用 uni.uploadFile,单次一张图。 + * @param {{ amount: number|string, remark?: string, filePath: string, targetUserId?: string|number }} opts + */ + upload: (opts) => bxUploadInvoice(opts), + delete: (id) => bxRequest({ url: `/invoices/${id}`, method: 'DELETE' }), + ledgers: (params) => bxRequest({ url: '/invoice-ledgers', method: 'GET', params }) } /** 附件:申请页图片上传所需最小集合 */ diff --git a/miniprogram/pages/bx/invoice/admin.vue b/miniprogram/pages/bx/invoice/admin.vue index 33495d608..22411551c 100644 --- a/miniprogram/pages/bx/invoice/admin.vue +++ b/miniprogram/pages/bx/invoice/admin.vue @@ -1,7 +1,612 @@ + + diff --git a/miniprogram/pages/bx/invoice/mine.vue b/miniprogram/pages/bx/invoice/mine.vue index 775992565..855e71828 100644 --- a/miniprogram/pages/bx/invoice/mine.vue +++ b/miniprogram/pages/bx/invoice/mine.vue @@ -1,7 +1,579 @@ + + diff --git a/miniprogram/styles/bx-theme.scss b/miniprogram/styles/bx-theme.scss index 399e5b771..d18af4949 100644 --- a/miniprogram/styles/bx-theme.scss +++ b/miniprogram/styles/bx-theme.scss @@ -1283,3 +1283,233 @@ .bx-page .all-list-card__bottom { justify-content: flex-end; } + +/* —— 发票夹 wallet summary / cards —— */ +.bx-page .wallet-summary.card, +.bx-page .wallet-summary { + margin: 12px 16px 8px; + padding: 14px 16px; + background: var(--surface); + border-radius: var(--r); + box-shadow: var(--shadow); +} + +.bx-page .wallet-summary__row { + display: flex; + justify-content: space-between; + align-items: center; + padding: 4px 0; + font-size: 14px; + color: var(--text-sec); +} + +.bx-page .wallet-summary__val { + font-weight: 700; + color: var(--text); + font-variant-numeric: tabular-nums; +} + +.bx-page .wallet-summary__row--em .wallet-summary__val, +.bx-page .wallet-summary__row--em b { + color: var(--primary); + font-size: 18px; +} + +.bx-page .wallet-summary__meta { + margin-top: 8px; + font-size: 12px; + color: var(--text-muted); +} + +.bx-page .wallet-actions { + padding: 0 16px 8px; +} + +.bx-page .wallet-list { + padding-top: 5px; + padding-bottom: 16px; + box-sizing: border-box; +} + +.bx-page .invoice-wallet-card { + position: relative; + overflow: hidden; + padding-top: 20px; +} + +.bx-page .invoice-wallet-card--static:active { + transform: none; +} + +.bx-page .invoice-wallet-card__index { + position: absolute; + top: 0; + left: 0; + z-index: 1; + min-width: 28px; + height: 22px; + padding: 0 8px; + display: inline-flex; + align-items: center; + justify-content: center; + border-radius: 0 0 10px 0; + background: var(--primary-dark); + color: #fff; + font-size: 12px; + font-weight: 700; + line-height: 1; + font-variant-numeric: tabular-nums; + box-sizing: border-box; +} + +.bx-page .invoice-wallet-card__subs { + display: flex; + flex-direction: column; + gap: 6px; + margin: 4px 0 10px; +} + +.bx-page .invoice-wallet-card__sub { + display: flex; + justify-content: space-between; + gap: 12px; + font-size: 13px; + line-height: 1.4; +} + +.bx-page .invoice-wallet-card__sub .k { + flex-shrink: 0; + color: var(--text-muted); +} + +.bx-page .invoice-wallet-card__sub .v { + min-width: 0; + text-align: right; + color: var(--text); + word-break: break-word; +} + +.bx-page .invoice-wallet-card__att-label { + margin-bottom: 6px; + font-size: 12px; + color: var(--text-muted); +} + +.bx-page .invoice-wallet-card__att-empty { + margin: 0; + font-size: 12px; + color: var(--text-muted); +} + +.bx-page .advance-att-grid { + margin-top: 0; +} + +.bx-page .advance-att-tile:active { + opacity: 0.92; +} + +.bx-page .att-preview-loading { + width: 100%; + height: 100%; + display: flex; + align-items: center; + justify-content: center; + background: var(--bg); + color: var(--text-muted); + font-size: 12px; +} + +.bx-page .att-file-icon.att-doc-thumb { + width: 100%; + height: 100%; + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + gap: 4px; + padding: 6px 4px; + box-sizing: border-box; +} + +.bx-page .att-doc-thumb__ext { + font-size: 11px; + font-weight: 800; + color: var(--primary-dark); + letter-spacing: 0.02em; +} + +.bx-page .att-doc-thumb__name { + font-size: 10px; + color: var(--text-muted); + text-align: center; + line-height: 1.25; + overflow: hidden; + text-overflow: ellipsis; + display: -webkit-box; + -webkit-line-clamp: 2; + -webkit-box-orient: vertical; + word-break: break-word; +} + +.bx-page .invoice-admin-card__bottom { + justify-content: flex-end; + margin-top: 10px; +} + +.bx-page .empty-state--embed { + padding: 28px 16px; + min-height: 120px; +} + +.bx-page .att-img.att-add { + display: flex; + align-items: center; + justify-content: center; + border-style: dashed; + color: var(--text-muted); + font-size: 28px; + line-height: 1; +} + +.bx-page .form-hint { + display: block; + font-size: 11px; + color: var(--text-muted); + margin: 0 0 8px; + line-height: 1.4; +} + +.bx-page .bx-upload-sheet .dialog-form-body { + padding: 8px 16px 4px; +} + +.bx-page .bx-upload-sheet .form-item { + margin-bottom: 12px; +} + +.bx-page .bx-upload-sheet .form-label { + display: block; + font-size: 13px; + color: var(--text-sec); + margin-bottom: 6px; +} + +.bx-page .bx-upload-sheet .form-input { + width: 100%; + height: 40px; + padding: 0 12px; + border: 1px solid var(--border); + border-radius: var(--rs); + background: var(--bg); + box-sizing: border-box; + font-size: 14px; +} + +.bx-page .bx-upload-sheet .form-input-prefix { + position: relative; +} + +.bx-page .bx-upload-sheet .form-input-prefix .form-input { + padding-left: 28px; +} diff --git a/miniprogram/utils/bxRequest.js b/miniprogram/utils/bxRequest.js index 7be078f01..229721a98 100644 --- a/miniprogram/utils/bxRequest.js +++ b/miniprogram/utils/bxRequest.js @@ -171,4 +171,73 @@ export function bxUploadFile(filePath, opts = {}) { }) } +/** + * 发票 multipart 上传(POST /invoices,name=files) + * 对齐 bx-mobile FormData:amount / remark / files / targetUserId + * @param {{ amount: number|string, remark?: string, filePath: string, targetUserId?: string|number, showError?: boolean }} opts + */ +export function bxUploadInvoice(opts = {}) { + const showError = opts.showError !== false + const token = uni.getStorageSync(TOKEN_KEY) || '' + const filePath = opts.filePath + if (!filePath) { + return Promise.reject(new Error('请选择发票文件')) + } + const formData = { + amount: String(opts.amount ?? '') + } + if (opts.remark != null && String(opts.remark).trim()) { + formData.remark = String(opts.remark).trim() + } + if (opts.targetUserId != null && opts.targetUserId !== '') { + formData.targetUserId = String(opts.targetUserId) + } + const url = `${API_BASE}/invoices` + + return new Promise((resolve, reject) => { + uni.uploadFile({ + url, + filePath, + name: 'files', + formData, + header: { + ...(token ? { Authorization: `Bearer ${token}` } : {}) + }, + success: (res) => { + const status = res.statusCode + let body = res.data + if (typeof body === 'string') { + try { + body = JSON.parse(body) + } catch (e) { + body = {} + } + } + if (status === 401) { + onUnauthorized() + reject(new Error('登录已过期')) + return + } + if (status < 200 || status >= 300) { + const message = parseDetailMessage(body) || `上传失败(${status})` + if (showError) uni.showToast({ title: String(message).slice(0, 40), icon: 'none' }) + reject(new Error(message)) + return + } + if (body && body.code !== undefined && body.code !== 0) { + const message = body.msg || '上传失败' + if (showError) uni.showToast({ title: String(message).slice(0, 40), icon: 'none' }) + reject(new Error(message)) + return + } + resolve(body) + }, + fail: (err) => { + if (showError) uni.showToast({ title: '上传失败', icon: 'none' }) + reject(err) + } + }) + }) +} + export default bxRequest