Files
webs/power_leasing/src/views/account/receiptRecord.vue

327 lines
12 KiB
Vue
Raw Normal View History

2025-10-20 10:15:13 +08:00
<template>
<div class="receipt-page">
<div class="card" aria-label="收款记录" tabindex="0">
<div class="card-header">
<h3 class="card-title">收款记录</h3>
<!-- <div class="card-actions">
<el-date-picker
v-model="range"
type="datetimerange"
range-separator="至"
start-placeholder="开始时间"
end-placeholder="结束时间"
size="small"
:clearable="true"
@change="handleRangeChange"
/>
<el-input
v-model="keyword"
size="small"
placeholder="订单号/备注搜索"
clearable
class="search-input"
@keyup.enter.native="fetchList"
/>
<el-button type="primary" size="small" @click="fetchList">查询</el-button>
</div> -->
</div>
<div v-if="loading" class="loading">
<i class="el-icon-loading" aria-label="加载中" role="img"></i>
加载中...
</div>
<div v-else>
<el-table
ref="receiptTable"
:data="rows"
border
stripe
size="small"
style="width: 100%"
:row-key="getRowKey"
:expand-row-keys="expandedRowKeys"
:row-class-name="getRowClassName"
@row-click="handleRowClick"
@expand-change="handleExpandChange"
:header-cell-style="{ textAlign: 'left' }"
:cell-style="{ textAlign: 'left' }"
>
<el-table-column type="expand" width="46">
<template #default="scope">
<div class="detail-panel">
<div class="detail-grid">
<div class="detail-item">
<span class="detail-label">订单号</span>
<span class="detail-value mono">{{ scope.row.orderId || '-' }}</span>
</div>
<div class="detail-item">
<span class="detail-label">付款链</span>
<span class="detail-value"><span class="badge">{{ formatChain(scope.row.fromChain) || '-' }}</span></span>
</div>
<div class="detail-item">
<span class="detail-label">付款币种</span>
<span class="detail-value"><span class="badge badge-blue">{{ String((scope.row.fromSymbol || scope.row.coin) || '') .toUpperCase() }}</span></span>
</div>
<div class="detail-item detail-item-full">
<span class="detail-label">付款地址</span>
<span class="detail-value address">
<span class="mono-ellipsis" :title="scope.row.fromAddress">{{ scope.row.fromAddress || '-' }}</span>
<el-button type="text" size="mini" @click.stop="copy(scope.row.fromAddress)" v-if="scope.row.fromAddress">复制</el-button>
</span>
</div>
</div>
</div>
</template>
</el-table-column>
<el-table-column label="支付时间" min-width="160">
<template #default="scope">{{ formatFullTime(scope.row.createTime) }}</template>
</el-table-column>
<el-table-column label="收款金额(USDT)" min-width="160" align="right">
<template #default="scope">
<span class="amount-green">+{{ formatTrunc(scope.row.realAmount, 2) }}</span>
</template>
</el-table-column>
<el-table-column label="收款链" min-width="120">
<template #default="scope">{{ formatChain(scope.row.toChain) }}</template>
</el-table-column>
<el-table-column label="收款币种" min-width="100">
<template #default="scope">{{ String(scope.row.coin || '').toUpperCase() }}</template>
</el-table-column>
<el-table-column label="收款地址" min-width="260">
<template #default="scope">
<span class="mono-ellipsis" :title="scope.row.toAddress">{{ scope.row.toAddress }}</span>
<el-button type="text" size="mini" @click.stop="copy(scope.row.toAddress)">复制</el-button>
</template>
</el-table-column>
<el-table-column label="交易HASH" min-width="260">
<template #default="scope">
<span class="mono-ellipsis" :title="scope.row.txHash">{{ scope.row.txHash }}</span>
<el-button type="text" size="mini" @click.stop="copy(scope.row.txHash)" v-if="scope.row.txHash">复制</el-button>
</template>
</el-table-column>
<el-table-column label="支付状态" min-width="120">
<template #default="scope">
<el-tag :type="getStatusType(scope.row.status)" size="small">{{ getStatusText(scope.row.status) }}</el-tag>
</template>
</el-table-column>
<el-table-column label="状态更新时间" min-width="160">
<template #default="scope">{{ formatFullTime(scope.row.updateTime) }}</template>
</el-table-column>
</el-table>
<div v-if="!rows.length" class="empty">
<div class="empty-icon">💳</div>
<div class="empty-text">暂无收款记录</div>
</div>
<div class="pagination">
<el-pagination
background
layout="prev, pager, next, jumper"
:current-page.sync="page"
:page-size="pageSize"
:total="total"
@current-change="fetchList"
/>
</div>
</div>
</div>
</div>
</template>
<script>
import { sellerReceiptList } from '../../api/wallet'
export default {
name: 'AccountReceiptRecord',
data() {
return {
loading: false,
rows: [
{
orderId: '1234567890',
fromChain: 'tron',
fromSymbol: 'USDT',
fromAddress: 'TQn9Y2khEsLJW1ChVWFMSMeRDow5KcbLSE',
toChain: 'tron',
coin: 'USDT',
toAddress: 'TQn9Y2khEsLJW1ChVWFMSMeRDow5KcbLSE',
txHash: 'TQn9Y2khEsLJW1ChVWFMSMeRDow5KcbLSE',
status: 2,
updateTime: '2024-01-15 14:30:25',
realAmount: 100,
},
{
orderId: '1234567890',
fromChain: 'tron',
fromSymbol: 'USDT',
fromAddress: 'TQn9Y2khEsLJW1ChVWFMSMeRDow5KcbLSE',
toChain: 'tron',
coin: 'USDT',
toAddress: 'TQn9Y2khEsLJW1ChVWFMSMeRDow5KcbLSE',
txHash: 'TQn9Y2khEsLJW1ChVWFMSMeRDow5KcbLSE',
status: 1,
updateTime: '2024-01-15 14:30:25',
realAmount: 106,
}
],
page: 1,
pageSize: 10,
total: 0,
range: [],
keyword: '',
expandedRowKeys: []
}
},
mounted() {
this.fetchList()
// 为示例数据补充唯一行键,避免展开联动
this.rows = this.withKeys(this.rows)
},
methods: {
withKeys(list) {
const arr = Array.isArray(list) ? list : []
return arr.map((it, idx) => ({
...it,
__rowKey: it && it.__rowKey ? it.__rowKey : `${it && (it.txHash || it.orderId || it.updateTime || '')}_${idx}`
}))
},
getRowKey(row) { return row && row.__rowKey },
handleRowClick(row) {
const key = this.getRowKey(row)
const isOpen = this.expandedRowKeys.includes(key)
// 手风琴:只保留当前点击行
this.expandedRowKeys = isOpen ? [] : [key]
},
handleExpandChange(row, expandedRows) {
// 由内置展开事件触发时,同步 keys确保手风琴
if (!Array.isArray(expandedRows)) {
this.expandedRowKeys = []
return
}
this.expandedRowKeys = expandedRows.length ? [this.getRowKey(expandedRows[expandedRows.length - 1])] : []
},
getRowClassName() { return 'clickable-row' },
/**
* 精确裁剪数字到指定位数不四舍五入
*/
formatTrunc(value, decimals = 2) {
const num = Number(value)
if (!Number.isFinite(num)) return '0'
const d = Math.max(0, Number(decimals) || 0)
const factor = Math.pow(10, d)
const truncated = Math.trunc(num * factor) / factor
const str = String(truncated)
if (d === 0) return str
const [intPart, decPart = ''] = str.split('.')
const padded = decPart.padEnd(d, '0')
return `${intPart}.${padded}`
},
formatFullTime(time) {
if (!time) return ''
try {
return `${time.split('T')[0]} ${time.split('T')[1].split('.')[0]}`
} catch (e) {
console.log(e,"时间");
return time
}
},
formatChain(chain) {
const map = { tron: 'Tron (TRC20)', ethereum: 'Ethereum (ERC20)', bsc: 'BSC (BEP20)', polygon: 'Polygon' }
return map[chain] || chain || '-'
},
getStatusType(status) {
const map = { 0: 'danger', 1: 'success', 2: 'warning', 3: 'danger' }
return map[status] || 'info'
},
getStatusText(status) {
const map = { 0: '支付失败', 1: '支付成功', 2: '待校验', 3: '证书校验失败' }
return map[status] || '未知'
},
copy(text) {
if (!text) return
try {
if (navigator.clipboard && navigator.clipboard.writeText) {
navigator.clipboard.writeText(text)
this.$message.success('已复制')
return
}
} catch (e) {}
const area = document.createElement('textarea')
area.value = text
document.body.appendChild(area)
area.select()
try { document.execCommand('copy'); this.$message.success('已复制') } catch (e) {}
document.body.removeChild(area)
},
handleRangeChange() {
this.page = 1
},
async fetchList() {
this.loading = true
try {
const params = {
page: this.page,
pageSize: this.pageSize,
// keyword: this.keyword || undefined,
// startTime: Array.isArray(this.range) && this.range[0] ? new Date(this.range[0]).getTime() : undefined,
// endTime: Array.isArray(this.range) && this.range[1] ? new Date(this.range[1]).getTime() : undefined
}
const res = await sellerReceiptList(params)
const data = res && (res.data || res)
const list = Array.isArray(data && data.rows) ? data.rows : (Array.isArray(data) ? data : [])
this.rows = this.withKeys(list)
this.total = res.total
} catch (e) {
this.rows = []
this.total = 0
} finally {
this.loading = false
}
}
}
}
</script>
<style scoped>
.receipt-page { padding: 4px; }
.card { background: #fff; border: 1px solid #eee; border-radius: 10px; padding: 12px; box-shadow: 0 4px 18px rgba(0,0,0,0.04); }
.card-header { display: flex; align-items: center; justify-content: space-between; margin-bottom: 8px; }
.card-title { margin: 0; font-size: 18px; font-weight: 700; color: #2c3e50; }
.card-actions { display: flex; align-items: center; gap: 8px; }
.search-input { width: 220px; }
.loading { text-align: center; color: #666; padding: 40px 0; }
.empty { text-align: center; color: #999; padding: 40px 0; }
.empty-icon { font-size: 48px; margin-bottom: 8px; }
.amount-green { color: #16a34a; font-weight: 700; }
.amount-red { color: #ef4444; font-weight: 700; }
.type-green { color: #16a34a; }
.type-red { color: #ef4444; }
.pagination { display: flex; justify-content: flex-end; margin-top: 8px; }
/* 展开详情样式 */
.detail-grid { display: grid; grid-template-columns: 1fr 1fr; gap: 12px 24px; padding: 8px 4px; }
.detail-item { display: grid; grid-template-columns: 90px 1fr; align-items: center; gap: 8px; }
.detail-item-full { grid-column: 1 / -1; }
.detail-label { color: #666; font-size: 13px; text-align: right; }
.detail-value { color: #333; font-size: 13px; text-align: left; }
.detail-value.address { font-family: "Monaco", "Menlo", monospace; word-break: break-all; }
/* 单行等宽省略 */
.mono-ellipsis { font-family: "Monaco", "Menlo", monospace; max-width: 360px; display: inline-block; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; vertical-align: middle; }
/* 可点击行的轻交互提示 */
.clickable-row:hover > td { background: #f8fafc !important; cursor: pointer; }
/* 展开面板视觉优化 */
.detail-panel { background: #f9fafb; border: 1px dashed #e5e7eb; border-radius: 8px; padding: 12px; }
.mono { font-family: "Monaco", "Menlo", monospace; }
.badge { display: inline-block; padding: 2px 8px; border-radius: 12px; background: #eef2ff; color: #3b82f6; font-size: 12px; line-height: 18px; }
.badge-blue { background: #eff6ff; color: #2563eb; }
</style>