项目中心-支付模块
This commit is contained in:
@@ -10,20 +10,25 @@ import type {
|
||||
InvoiceTitle,
|
||||
InvoiceTitlePayload,
|
||||
PayMethod,
|
||||
PayOrderResponse,
|
||||
Payment,
|
||||
PaymentResult,
|
||||
} from '@/types/business/finance'
|
||||
import {
|
||||
applyInvoice as applyInvoiceApi,
|
||||
createAlipayPageOrder as createAlipayPageOrderApi,
|
||||
createInvoiceTitle,
|
||||
createPayment as createPaymentApi,
|
||||
createWechatNativeOrder as createWechatNativeOrderApi,
|
||||
deleteInvoiceTitle,
|
||||
exportReport as exportReportApi,
|
||||
getBillDetail,
|
||||
getBillList,
|
||||
getCheckoutBills,
|
||||
getFinanceStats,
|
||||
getInvoiceList,
|
||||
getInvoiceTitles,
|
||||
getPlanPayResult,
|
||||
getPayResult,
|
||||
setDefaultTitle as setDefaultTitleApi,
|
||||
switchPaymentMethod as switchPaymentMethodApi,
|
||||
@@ -53,6 +58,13 @@ export const useFinanceStore = defineStore('finance', () => {
|
||||
const payment = ref<Payment | null>(null)
|
||||
const result = ref<PaymentResult | null>(null)
|
||||
|
||||
// —— 收银台(确认支付页)
|
||||
/** 收银台待支付账单(由路由 `?ids=` 拉取) */
|
||||
const checkoutBills = ref<Bill[]>([])
|
||||
const checkoutLoading = ref(false)
|
||||
/** 最近一次下单响应(微信 codeUrl / 支付宝跳转地址) */
|
||||
const payOrder = ref<PayOrderResponse | null>(null)
|
||||
|
||||
// —— 发票
|
||||
const invoices = ref<Invoice[]>([])
|
||||
const invoiceTotal = ref(0)
|
||||
@@ -108,8 +120,48 @@ export const useFinanceStore = defineStore('finance', () => {
|
||||
payment.value = await switchPaymentMethodApi(billId, method)
|
||||
}
|
||||
|
||||
async function fetchResult(billId: string): Promise<void> {
|
||||
result.value = await getPayResult(billId)
|
||||
async function fetchResult(billId: string, requirementId = ''): Promise<void> {
|
||||
result.value = requirementId
|
||||
? await getPlanPayResult(requirementId, billId)
|
||||
: await getPayResult(billId)
|
||||
}
|
||||
|
||||
// —— 收银台(确认支付)
|
||||
/**
|
||||
* 拉取收银台订单明细。
|
||||
*
|
||||
* <p>入参是**需求 id**(项目详情路由 `:id`):数据走项目详情那个现成接口
|
||||
* `GET /web/requirementPool/get/finance/{需求id}` 的 `billPlans`,不另开端点。
|
||||
* `ids` 为可选的期次 id(`vm_bill_plan.id`),用于精确指定付哪一期;
|
||||
* 不传则取该需求下状态为「待支付」的期。</p>
|
||||
*/
|
||||
async function loadCheckout(requirementId: string, ids: string[] = []): Promise<void> {
|
||||
checkoutLoading.value = true
|
||||
try {
|
||||
checkoutBills.value = await getCheckoutBills(requirementId, ids)
|
||||
} finally {
|
||||
checkoutLoading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 微信 Native 下单(拿到 codeUrl 后由页面渲染二维码)。
|
||||
*
|
||||
* @param billIds 本笔支付覆盖的账单 id。后端据此落 `vm_finance_payment` 并把它写进
|
||||
* `request_payload`,支付成功回调再为每张账单落 `vm_finance_payment_bill`。
|
||||
*/
|
||||
async function payByWechatNative(orderNo: string, amount: number, billIds: string[]): Promise<void> {
|
||||
payOrder.value = await createWechatNativeOrderApi(orderNo, amount, billIds)
|
||||
}
|
||||
|
||||
/** 支付宝电脑网站支付下单(拿到跳转地址后由页面跳转) */
|
||||
async function payByAlipayPage(orderNo: string, amount: number, billIds: string[]): Promise<void> {
|
||||
payOrder.value = await createAlipayPageOrderApi(orderNo, amount, billIds)
|
||||
}
|
||||
|
||||
/** 清空下单响应(关闭二维码弹窗时调用) */
|
||||
function clearPayOrder(): void {
|
||||
payOrder.value = null
|
||||
}
|
||||
|
||||
// —— 发票
|
||||
@@ -178,6 +230,9 @@ export const useFinanceStore = defineStore('finance', () => {
|
||||
currentBill,
|
||||
payment,
|
||||
result,
|
||||
checkoutBills,
|
||||
checkoutLoading,
|
||||
payOrder,
|
||||
invoices,
|
||||
invoiceTotal,
|
||||
invoiceQuery,
|
||||
@@ -192,6 +247,10 @@ export const useFinanceStore = defineStore('finance', () => {
|
||||
createPayment,
|
||||
switchPaymentMethod,
|
||||
fetchResult,
|
||||
loadCheckout,
|
||||
payByWechatNative,
|
||||
payByAlipayPage,
|
||||
clearPayOrder,
|
||||
fetchInvoices,
|
||||
setInvoiceQuery,
|
||||
searchInvoices,
|
||||
|
||||
114
src/stores/modules/oss.ts
Normal file
114
src/stores/modules/oss.ts
Normal file
@@ -0,0 +1,114 @@
|
||||
import { defineStore } from 'pinia'
|
||||
import { ref } from 'vue'
|
||||
import { fetchOssPreviewBlob } from '@/api/oss'
|
||||
import { resolvePreviewKind, PREVIEW_TEXT_MAX_BYTES, type PreviewKind } from '@/utils/filePreview'
|
||||
import type { FileInfo } from '@/types/api'
|
||||
|
||||
/**
|
||||
* OSS 文件预览全局状态。
|
||||
*
|
||||
* <p>所有 `FileList` 的「预览」统一走这里,弹一个全局 `FilePreviewDialog`:
|
||||
* 有 `ossId` 的文件经同源后端接口取 blob 内联渲染(绕开桶强制下载);
|
||||
* 无 `ossId` 的图片可退化为公网直链(子资源渲染不受强制下载影响),其余提示「不支持在线预览」。</p>
|
||||
*/
|
||||
export const useOssStore = defineStore('oss', () => {
|
||||
const visible = ref(false)
|
||||
const file = ref<FileInfo | null>(null)
|
||||
const kind = ref<PreviewKind>('unsupported')
|
||||
const objectUrl = ref<string | null>(null)
|
||||
const textContent = ref<string | null>(null)
|
||||
const loading = ref(false)
|
||||
const error = ref('')
|
||||
/** 文本类文件超过预览上限被截断渲染 */
|
||||
const truncated = ref(false)
|
||||
/** 被截断文件的实际大小(字节) */
|
||||
const totalBytes = ref(0)
|
||||
|
||||
function revokeObjectUrl(): void {
|
||||
if (objectUrl.value && objectUrl.value.startsWith('blob:')) {
|
||||
URL.revokeObjectURL(objectUrl.value)
|
||||
}
|
||||
objectUrl.value = null
|
||||
}
|
||||
|
||||
async function openPreview(target: FileInfo): Promise<void> {
|
||||
closePreview()
|
||||
file.value = target
|
||||
visible.value = true
|
||||
loading.value = true
|
||||
error.value = ''
|
||||
textContent.value = null
|
||||
truncated.value = false
|
||||
totalBytes.value = 0
|
||||
|
||||
const previewKind = resolvePreviewKind(target.name)
|
||||
kind.value = previewKind
|
||||
|
||||
// 不支持内联渲染(office / 未知类型):直接提示,等用户下载
|
||||
if (previewKind === 'unsupported') {
|
||||
loading.value = false
|
||||
return
|
||||
}
|
||||
|
||||
// 无 ossId:图片可走公网直链(<img> 子资源渲染不受强制下载影响),其余无法预览
|
||||
if (target.ossId === undefined || target.ossId === null) {
|
||||
if (previewKind === 'image' && target.url) {
|
||||
objectUrl.value = target.url
|
||||
loading.value = false
|
||||
return
|
||||
}
|
||||
error.value = '该文件暂不支持在线预览,请使用下载'
|
||||
loading.value = false
|
||||
return
|
||||
}
|
||||
|
||||
try {
|
||||
const blob = await fetchOssPreviewBlob(target.ossId)
|
||||
if (previewKind === 'text') {
|
||||
// 整文件塞进 <pre> 会卡死浏览器(如 5MB+ 的 .sql/.log),
|
||||
// 仅读取前 PREVIEW_TEXT_MAX_BYTES 渲染,超出截断并提示下载
|
||||
const total = blob.size
|
||||
if (total > PREVIEW_TEXT_MAX_BYTES) {
|
||||
textContent.value = await blob.slice(0, PREVIEW_TEXT_MAX_BYTES).text()
|
||||
truncated.value = true
|
||||
totalBytes.value = total
|
||||
} else {
|
||||
textContent.value = await blob.text()
|
||||
}
|
||||
} else {
|
||||
// 图片 / PDF / 音视频由浏览器原生处理,5MB 量级毫无压力
|
||||
objectUrl.value = URL.createObjectURL(blob)
|
||||
}
|
||||
loading.value = false
|
||||
} catch {
|
||||
error.value = '预览加载失败,请使用下载'
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
function closePreview(): void {
|
||||
revokeObjectUrl()
|
||||
visible.value = false
|
||||
file.value = null
|
||||
kind.value = 'unsupported'
|
||||
textContent.value = null
|
||||
truncated.value = false
|
||||
totalBytes.value = 0
|
||||
loading.value = false
|
||||
error.value = ''
|
||||
}
|
||||
|
||||
return {
|
||||
visible,
|
||||
file,
|
||||
kind,
|
||||
objectUrl,
|
||||
textContent,
|
||||
loading,
|
||||
error,
|
||||
truncated,
|
||||
totalBytes,
|
||||
openPreview,
|
||||
closePreview,
|
||||
}
|
||||
})
|
||||
Reference in New Issue
Block a user