Files
one-pipe-system/src/views/asset-management/asset-information/components/WalletTransactionCard.vue
sexygoat a5e76313cb
All checks were successful
构建并部署前端到测试环境 / build-and-deploy (push) Successful in 2m9s
fix: bug
2026-04-23 14:59:05 +08:00

363 lines
11 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<template>
<ElCard shadow="never" class="info-card wallet-info" v-loading="walletLoading">
<template #header>
<div class="card-header wallet-header">
<div class="header-left">
<span class="wallet-title">钱包</span>
<ElTag v-if="walletInfo" :type="getWalletStatusType(walletInfo.status)" size="small">
{{ walletInfo.status_text || '-' }}
</ElTag>
<template v-if="walletInfo">
<ElDivider direction="vertical" />
<span class="balance-item"
>💰 总余额 <strong>{{ formatAmount(walletInfo.balance) }}</strong></span
>
<ElDivider direction="vertical" />
<span class="balance-item"
> 可用 <strong>{{ formatAmount(walletInfo.available_balance) }}</strong></span
>
<ElDivider direction="vertical" />
<span class="balance-item"
>🔒 冻结 <strong>{{ formatAmount(walletInfo.frozen_balance) }}</strong></span
>
</template>
</div>
<div v-if="!props.boundDeviceId" class="filter-section">
<ElSelect
v-model="filterForm.transaction_type"
placeholder="交易类型"
clearable
style="width: 150px"
@change="handleFilterChange"
>
<ElOption label="充值" value="recharge" />
<ElOption label="扣款" value="deduct" />
<ElOption label="退款" value="refund" />
</ElSelect>
<ElDatePicker
v-model="filterForm.date_range"
type="datetimerange"
range-separator=""
start-placeholder="开始时间"
end-placeholder="结束时间"
style="width: 360px"
/>
<ElButton type="primary" @click="handleQuery">查询</ElButton>
<ElButton @click="handleReset">重置</ElButton>
<ElTag type="info" size="small"> {{ total }} </ElTag>
</div>
</div>
</template>
<!-- 绑定设备提示 -->
<ElEmpty v-if="props.boundDeviceId" :image-size="100">
<template #description>
<span>该卡已绑定设备请点击 </span>
<ElButton
type="primary"
link
style="font-size: 15px; font-weight: 600"
@click="emit('navigateToDevice', props.boundDeviceNo!)"
>{{ props.boundDeviceNo }}</ElButton
>
<span> 进行查看</span>
</template>
</ElEmpty>
<div v-else class="wallet-table-wrapper">
<ElTable
v-if="transactionList && transactionList.length > 0"
:data="transactionList"
class="wallet-table"
border
v-loading="loading"
>
<ElTableColumn label="交易类型" width="100" align="center">
<template #default="scope">
<ElTag :type="getTransactionTypeTag(scope.row.transaction_type)" size="small">
{{ scope.row.transaction_type_text }}
</ElTag>
</template>
</ElTableColumn>
<ElTableColumn label="变动金额" width="120" align="right">
<template #default="scope">
<span
:style="{
color: scope.row.amount > 0 ? '#67c23a' : '#f56c6c',
fontWeight: 600
}"
>
{{ formatAmount(scope.row.amount) }}
</span>
</template>
</ElTableColumn>
<ElTableColumn label="变动前余额" width="120" align="right">
<template #default="scope">
{{ formatAmount(scope.row.balance_before) }}
</template>
</ElTableColumn>
<ElTableColumn label="变动后余额" width="120" align="right">
<template #default="scope">
{{ formatAmount(scope.row.balance_after) }}
</template>
</ElTableColumn>
<ElTableColumn label="关联业务" width="100" align="center">
<template #default="scope">
<ElTag v-if="scope.row.reference_type" type="info" size="small">
{{ scope.row.reference_type === 'recharge' ? '充值' : '订单' }}
</ElTag>
<span v-else>--</span>
</template>
</ElTableColumn>
<ElTableColumn prop="reference_no" label="业务编号" min-width="180" show-overflow-tooltip>
<template #default="scope">
<span v-if="scope.row.reference_no" class="reference-no-link">
{{ scope.row.reference_no }}
</span>
<span v-else>--</span>
</template>
</ElTableColumn>
<ElTableColumn prop="remark" label="备注" min-width="150" show-overflow-tooltip>
<template #default="scope">
{{ scope.row.remark || '-' }}
</template>
</ElTableColumn>
<ElTableColumn label="创建时间" width="170" show-overflow-tooltip>
<template #default="scope">
{{ formatDateTime(scope.row.created_at) }}
</template>
</ElTableColumn>
</ElTable>
<ElEmpty v-else-if="!loading" description="暂无钱包流水数据" :image-size="100" />
<!-- 分页器 -->
<div v-if="total > 0" class="pagination-wrapper">
<ElPagination
v-model:current-page="pagination.page"
v-model:page-size="pagination.page_size"
:total="total"
:page-sizes="[10, 20, 50, 100]"
layout="total, sizes, prev, pager, next, jumper"
@size-change="handlePageSizeChange"
@current-change="handlePageChange"
/>
</div>
</div>
</ElCard>
</template>
<script setup lang="ts">
import { ref, watch } from 'vue'
import {
ElCard,
ElTable,
ElTableColumn,
ElTag,
ElButton,
ElSelect,
ElOption,
ElDatePicker,
ElPagination,
ElEmpty,
ElMessage,
ElDivider
} from 'element-plus'
import { useAssetFormatters } from '../composables/useAssetFormatters'
import { formatDateTime } from '@/utils/business/format'
import { AssetService } from '@/api/modules'
interface WalletInfo {
balance: number
available_balance: number
frozen_balance: number
status: number
status_text?: string
}
interface Props {
assetIdentifier: string
walletInfo: WalletInfo | null
walletLoading?: boolean
boundDeviceId?: number
boundDeviceNo?: string
}
const props = withDefaults(defineProps<Props>(), {
walletLoading: false,
boundDeviceId: undefined,
boundDeviceNo: ''
})
const emit = defineEmits<{
(e: 'navigateToDevice', deviceNo: string): void
}>()
const { formatAmount, getTransactionTypeTag, getWalletStatusType } = useAssetFormatters()
// State
const loading = ref(false)
const transactionList = ref<any[]>([])
const total = ref(0)
const pagination = ref({ page: 1, page_size: 20 })
const filterForm = ref<{
transaction_type: string | null
date_range: [Date, Date] | null
}>({
transaction_type: null,
date_range: null
})
const queryParams = ref({
page: 1,
page_size: 20,
transaction_type: null as string | null,
start_time: null as string | null,
end_time: null as string | null
})
const loadTransactions = async () => {
if (!props.assetIdentifier) return
try {
loading.value = true
const response = await AssetService.getWalletTransactions(
props.assetIdentifier,
queryParams.value
)
if (response.code === 0 && response.data) {
transactionList.value = response.data.list || []
total.value = response.data.total || 0
}
} catch (error: any) {
if (error?.response?.status === 403) {
ElMessage.warning('企业账号禁止查看钱包流水')
} else {
console.error('获取钱包流水失败:', error)
}
} finally {
loading.value = false
}
}
const handleQuery = () => {
queryParams.value.page = 1
pagination.value.page = 1
queryParams.value.transaction_type = filterForm.value.transaction_type
if (filterForm.value.date_range && filterForm.value.date_range.length === 2) {
queryParams.value.start_time = filterForm.value.date_range[0].toISOString()
queryParams.value.end_time = filterForm.value.date_range[1].toISOString()
} else {
queryParams.value.start_time = null
queryParams.value.end_time = null
}
loadTransactions()
}
const handleFilterChange = () => handleQuery()
const handleReset = () => {
filterForm.value = { transaction_type: null, date_range: null }
queryParams.value = {
page: 1,
page_size: 20,
transaction_type: null,
start_time: null,
end_time: null
}
pagination.value = { page: 1, page_size: 20 }
loadTransactions()
}
const handlePageSizeChange = (size: number) => {
queryParams.value.page_size = size
queryParams.value.page = 1
pagination.value.page = 1
loadTransactions()
}
const handlePageChange = (page: number) => {
queryParams.value.page = page
loadTransactions()
}
// 仅监听 assetIdentifier 变化加载数据(去掉 onMounted 避免重复调用)
watch(
() => props.assetIdentifier,
(newVal) => {
if (newVal) handleReset()
},
{ immediate: true }
)
</script>
<style scoped lang="scss">
.info-card {
&.wallet-info {
.wallet-header {
display: flex;
flex-wrap: wrap;
gap: 16px;
align-items: center;
justify-content: space-between;
.header-left {
display: flex;
flex-wrap: wrap;
gap: 8px;
align-items: center;
.wallet-title {
font-size: 15px;
font-weight: 500;
}
.balance-item {
font-size: 13px;
color: var(--el-text-color-regular);
strong {
font-weight: 600;
color: var(--el-text-color-primary);
}
}
}
.filter-section {
display: flex;
flex-wrap: wrap;
gap: 10px;
align-items: center;
}
}
.wallet-table-wrapper {
.wallet-table {
:deep(.el-table__header) {
th {
font-weight: 600;
color: var(--el-text-color-primary, #374151);
}
}
.reference-no-link {
color: var(--el-color-primary);
text-decoration: underline;
text-decoration-style: dashed;
text-underline-offset: 2px;
cursor: pointer;
&:hover {
color: var(--el-color-primary-light-3);
text-decoration-style: solid;
}
}
}
.pagination-wrapper {
display: flex;
justify-content: flex-end;
margin-top: 16px;
}
}
}
}
</style>