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