Files
webs/power_leasing/src/views/account/receiptRecord.vue
2025-10-20 10:15:13 +08:00

327 lines
12 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>
<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>