363 lines
11 KiB
Vue
363 lines
11 KiB
Vue
<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>
|