feat: 支付凭证
All checks were successful
构建并部署前端到测试环境 / build-and-deploy (push) Successful in 4m22s

This commit is contained in:
sexygoat
2026-04-23 18:35:29 +08:00
parent cd2ca96873
commit aaa648a86d
6 changed files with 143 additions and 0 deletions

View File

@@ -34,6 +34,24 @@ export interface GetUploadUrlResponse {
expires_in: number
}
/**
* 批量获取文件下载预签名 URL 请求
*/
export interface BatchDownloadUrlsRequest {
/** 文件路径标识列表,最多 50 个 */
file_keys: string[]
}
/**
* 批量获取文件下载预签名 URL 响应
*/
export interface BatchDownloadUrlsResponse {
/** 文件下载地址映射key 为 file_key */
urls: Record<string, string>
/** URL 有效期(秒) */
expires_in: number
}
export class StorageService extends BaseService {
/**
* 获取文件上传预签名 URL
@@ -100,4 +118,18 @@ export class StorageService extends BaseService {
throw error
}
}
/**
* 批量获取文件下载预签名 URL
* 用于获取私有文件的临时下载地址
* @param data 请求参数
*/
static batchDownloadUrls(
data: BatchDownloadUrlsRequest
): Promise<BaseResponse<BatchDownloadUrlsResponse>> {
return this.post<BaseResponse<BatchDownloadUrlsResponse>>(
'/api/admin/storage/batch-download-urls',
data
)
}
}

View File

@@ -0,0 +1,55 @@
<template>
<ElImageViewer
v-if="visible"
:url-list="[url]"
:initial-index="0"
hide-on-click-modal
@close="handleClose"
/>
</template>
<script setup lang="ts">
import { ref, watch } from 'vue'
import { ElImageViewer } from 'element-plus'
import { StorageService } from '@/api/modules'
interface Props {
fileKey?: string
}
const props = defineProps<Props>()
const emit = defineEmits<{
close: []
}>()
const visible = ref(false)
const url = ref('')
watch(
() => props.fileKey,
async (val) => {
if (val) {
url.value = ''
try {
const res = await StorageService.batchDownloadUrls({
file_keys: [val]
})
if (res.code === 0 && res.data?.urls?.[val]) {
url.value = res.data.urls[val]
visible.value = true
}
} catch (error) {
console.error('获取支付凭证失败:', error)
}
}
},
{ immediate: true }
)
const handleClose = () => {
visible.value = false
url.value = ''
emit('close')
}
</script>

View File

@@ -63,6 +63,7 @@ export interface Order {
purchase_remark?: string // 购买备注
operator_name?: string // 操作者
items: OrderItem[] | null
payment_voucher_key?: string // 支付凭证(线下支付时才有值)
created_at: string
updated_at: string
}

View File

@@ -139,6 +139,7 @@ declare module 'vue' {
MenuStyleSettings: typeof import('./../components/core/layouts/art-settings-panel/widget/MenuStyleSettings.vue')['default']
OperatorSelect: typeof import('./../components/business/OperatorSelect.vue')['default']
PackageSelector: typeof import('./../components/business/PackageSelector.vue')['default']
PaymentVoucherDialog: typeof import('./../components/business/PaymentVoucherDialog.vue')['default']
RealnamePolicyDialog: typeof import('./../components/device/RealnamePolicyDialog.vue')['default']
RouterLink: typeof import('vue-router')['RouterLink']
RouterView: typeof import('vue-router')['RouterView']

View File

@@ -159,6 +159,12 @@
</div>
</template>
</ElDialog>
<!-- 支付凭证预览 -->
<PaymentVoucherDialog
:file-key="paymentVoucherFileKey"
@close="paymentVoucherFileKey = ''"
/>
</ElCard>
</div>
</ArtTableFullScreen>
@@ -196,6 +202,7 @@
import { formatDateTime } from '@/utils/business/format'
import { RoutesAlias } from '@/router/routesAlias'
import { useAuth } from '@/composables/useAuth'
import PaymentVoucherDialog from '@/components/business/PaymentVoucherDialog.vue'
defineOptions({ name: 'AgentRechargeList' })
@@ -210,6 +217,7 @@
const createDialogVisible = ref(false)
const confirmPayDialogVisible = ref(false)
const currentRecharge = ref<AgentRecharge | null>(null)
const paymentVoucherFileKey = ref('')
// 搜索表单初始值
const initialSearchState: AgentRechargeQueryParams = {
@@ -751,6 +759,19 @@
const getActions = (row: AgentRecharge) => {
const actions: any[] = []
// 线下支付且有凭证可以查看
if (
row.payment_method === 'offline' &&
row.payment_voucher_key &&
hasAuth('agent_recharge:view_payment_voucher')
) {
actions.push({
label: '查看支付凭证',
handler: () => handleViewPaymentVoucher(row),
type: 'primary'
})
}
// 待支付且线下转账的订单可以确认支付
if (
row.status === 1 &&
@@ -766,6 +787,12 @@
return actions
}
// 查看支付凭证
const handleViewPaymentVoucher = (row: AgentRecharge) => {
if (!row.payment_voucher_key) return
paymentVoucherFileKey.value = row.payment_voucher_key
}
</script>
<style scoped lang="scss">

View File

@@ -300,6 +300,12 @@
</div>
</template>
</ElDialog>
<!-- 支付凭证预览 -->
<PaymentVoucherDialog
:file-key="paymentVoucherFileKey"
@close="paymentVoucherFileKey = ''"
/>
</ElCard>
</div>
</ArtTableFullScreen>
@@ -337,6 +343,7 @@
import { useUserStore } from '@/store/modules/user'
import { formatDateTime } from '@/utils/business/format'
import { RoutesAlias } from '@/router/routesAlias'
import PaymentVoucherDialog from '@/components/business/PaymentVoucherDialog.vue'
defineOptions({ name: 'OrderList' })
@@ -350,6 +357,7 @@
const createDialogVisible = ref(false)
const detailDialogVisible = ref(false)
const currentOrder = ref<Order | null>(null)
const paymentVoucherFileKey = ref('')
// 搜索表单初始值
const initialSearchState: OrderQueryParams = {
@@ -1199,6 +1207,19 @@
const getActions = (row: Order) => {
const actions: any[] = []
// 线下支付且有凭证可以查看
if (
row.payment_method === 'offline' &&
row.payment_voucher_key &&
hasAuth('orders:view_payment_voucher')
) {
actions.push({
label: '查看支付凭证',
handler: () => handleViewPaymentVoucher(row),
type: 'primary'
})
}
// 只有待支付的订单可以删除
if (row.payment_status === 1 && hasAuth('orders:delete')) {
actions.push({
@@ -1210,6 +1231,12 @@
return actions
}
// 查看支付凭证
const handleViewPaymentVoucher = (row: Order) => {
if (!row.payment_voucher_key) return
paymentVoucherFileKey.value = row.payment_voucher_key
}
</script>
<style scoped lang="scss">