需求更改开发中
This commit is contained in:
@@ -46,4 +46,26 @@ export function getOrdersByStatusForSeller(data) {
|
||||
method: 'post',
|
||||
data
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
//结算前链和币种查询
|
||||
export function getChainAndListForSeller(data) {
|
||||
return request({
|
||||
url: `/lease/shop/getChainAndListForSeller`,
|
||||
method: 'post',
|
||||
data
|
||||
})
|
||||
}
|
||||
|
||||
//获取实时币价
|
||||
export function getCoinPrice(data) {
|
||||
return request({
|
||||
url: `/lease/order/info/getCoinPrice`,
|
||||
method: 'post',
|
||||
data
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -27,6 +27,17 @@ export function deleteBatchGoods(data) {
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
// 批量删除购物车中已下架商品
|
||||
export function deleteBatchGoodsForIsDelete(data) {
|
||||
return request({
|
||||
url: `/lease/shopping/cart/deleteBatchGoodsForIsDelete`,
|
||||
method: 'post',
|
||||
data
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -92,3 +92,18 @@ export function deleteShopConfig(data) {
|
||||
}
|
||||
|
||||
|
||||
|
||||
// 批量删除购物车中已下架商品
|
||||
export function deleteBatchGoodsForIsDelete(data) {
|
||||
return request({
|
||||
url: `/lease/shopping/cart/deleteBatchGoodsForIsDelete`,
|
||||
method: 'post',
|
||||
data
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -11,30 +11,109 @@ export function getWalletInfo(data) {
|
||||
|
||||
//余额提现
|
||||
export function withdrawBalance(data) {
|
||||
return request({
|
||||
url: `/lease/user/withdrawBalance`,
|
||||
method: 'post',
|
||||
data
|
||||
})
|
||||
}
|
||||
return request({
|
||||
url: `/lease/user/withdrawBalance`,
|
||||
method: 'post',
|
||||
data
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
//余额充值记录
|
||||
export function balanceRechargeList(data) {
|
||||
return request({
|
||||
url: `/lease/user/balanceRechargeList`,
|
||||
method: 'post',
|
||||
data
|
||||
})
|
||||
}
|
||||
return request({
|
||||
url: `/lease/user/balanceRechargeList`,
|
||||
method: 'post',
|
||||
data
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
//提现记录
|
||||
//提现记录
|
||||
export function balanceWithdrawList(data) {
|
||||
return request({
|
||||
url: `/lease/user/balanceWithdrawList`,
|
||||
method: 'post',
|
||||
data
|
||||
})
|
||||
}
|
||||
return request({
|
||||
url: `/lease/user/balanceWithdrawList`,
|
||||
method: 'post',
|
||||
data
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
// 卖家收款记录
|
||||
export function sellerReceiptList(data) {
|
||||
return request({
|
||||
url: `/lease/user/balancePayList`,
|
||||
method: 'post',
|
||||
data
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
//钱包绑定
|
||||
export function addWalletShopConfig(data) {
|
||||
return request({
|
||||
url: `/lease/shop/addShopConfig`,
|
||||
method: 'post',
|
||||
data
|
||||
})
|
||||
}
|
||||
|
||||
//获取支持的链和币种
|
||||
export function getChainAndList(data) {
|
||||
return request({
|
||||
url: `/lease/shop/getChainAndList`,
|
||||
method: 'post',
|
||||
data
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
//获取钱包绑定列表
|
||||
export function getShopConfig(data) {
|
||||
return request({
|
||||
url: `/lease/shop/getShopConfig`,
|
||||
method: 'post',
|
||||
data
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
//创建钱包
|
||||
export function bindWallet(data) {
|
||||
return request({
|
||||
url: `/lease/user/bindWallet`,
|
||||
method: 'post',
|
||||
data
|
||||
})
|
||||
}
|
||||
|
||||
//资金流水
|
||||
export function transactionRecord(data) {
|
||||
return request({
|
||||
url: `/lease/user/transactionRecord`,
|
||||
method: 'post',
|
||||
data
|
||||
})
|
||||
}
|
||||
|
||||
//钱包的最近交易
|
||||
export function getRecentlyTransaction(data) {
|
||||
return request({
|
||||
url: `/lease/user/getRecentlyTransaction`,
|
||||
method: 'post',
|
||||
data
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -9,7 +9,7 @@ import './utils/loginInfo.js';
|
||||
// 全局输入防表情守卫(极简、无侵入)
|
||||
import { initNoEmojiGuard } from './utils/noEmojiGuard.js';
|
||||
|
||||
// console.log = ()=>{} //全局关闭打印
|
||||
console.log = ()=>{} //全局关闭打印
|
||||
|
||||
|
||||
Vue.config.productionTip = false
|
||||
|
||||
@@ -62,7 +62,7 @@ export const accountRoutes = [
|
||||
path: '/account',
|
||||
name: 'account',
|
||||
component: () => import('../views/account/index.vue'),
|
||||
redirect: '/account/wallet',
|
||||
redirect: '/account/shops',
|
||||
meta: {
|
||||
title: '个人中心',
|
||||
description: '管理个人资料和店铺',
|
||||
@@ -99,6 +99,16 @@ export const accountRoutes = [
|
||||
allAuthority: ['all']
|
||||
}
|
||||
},
|
||||
{
|
||||
path: 'receipt-record',
|
||||
name: 'accountReceiptRecord',
|
||||
component: () => import('../views/account/receiptRecord.vue'),
|
||||
meta: {
|
||||
title: '收款记录',
|
||||
description: '卖家收款流水记录',
|
||||
allAuthority: ['all']
|
||||
}
|
||||
},
|
||||
{
|
||||
path: 'shop-new',
|
||||
name: 'accountShopNew',
|
||||
@@ -159,6 +169,16 @@ export const accountRoutes = [
|
||||
allAuthority: ['all']
|
||||
}
|
||||
},
|
||||
{
|
||||
path: 'funds-flow',
|
||||
name: 'accountFundsFlow',
|
||||
component: () => import('../views/account/fundsFlow.vue'),
|
||||
meta: {
|
||||
title: '资金流水',
|
||||
description: '充值/提现/消费记录切换查看',
|
||||
allAuthority: ['all']
|
||||
}
|
||||
},
|
||||
{
|
||||
path: 'purchased-detail/:orderItemId',
|
||||
name: 'PurchasedDetail',
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
<el-table-column prop="payCoin" label="币种" min-width="100" />
|
||||
<el-table-column prop="address" label="收款地址" min-width="240" />
|
||||
<el-table-column prop="leaseTime" label="租赁天数" min-width="100" />
|
||||
<el-table-column prop="price" label="单价(USDT)" min-width="240" />
|
||||
<el-table-column prop="price" label="售价(USDT)" min-width="240" />
|
||||
</el-table>
|
||||
</template>
|
||||
</el-table-column>
|
||||
|
||||
442
power_leasing/src/views/account/fundsFlow.vue
Normal file
442
power_leasing/src/views/account/fundsFlow.vue
Normal file
@@ -0,0 +1,442 @@
|
||||
<template>
|
||||
<div class="funds-page">
|
||||
<h3 class="title" style="margin-bottom: 18px; text-align: left;">资金流水</h3>
|
||||
<div class="tabs-card">
|
||||
<el-tabs v-model="active" @tab-click="handleTab">
|
||||
<el-tab-pane label="充值记录" name="recharge">
|
||||
<div class="list-wrap">
|
||||
<div class="list-header">
|
||||
<span class="list-title">全部充值 ({{ rechargeRows.length }})</span>
|
||||
<el-button type="primary" size="small" @click="loadRecharge">刷新</el-button>
|
||||
</div>
|
||||
<div class="record-list" v-loading="loading.recharge">
|
||||
<div v-for="(row, idx) in rechargeRows" :key="getRowKey(row, idx)" class="record-item" :class="statusClass(row.status)" @click="toggleExpand('recharge', row, idx)">
|
||||
<div class="item-main">
|
||||
<div class="item-left">
|
||||
<div class="amount">+ {{ formatTrunc(row.amount, 2) }} {{ row.fromSymbol || 'USDT' }}</div>
|
||||
<div class="chain">{{ formatChain(row.fromChain) }}</div>
|
||||
</div>
|
||||
<div class="item-right">
|
||||
<div class="status"><el-tag :type="getRechargeStatusType(row.status)" size="small">{{ getRechargeStatusText(row.status) }}</el-tag></div>
|
||||
<div class="time">{{ formatFullTime(row.createTime) }}</div>
|
||||
</div>
|
||||
</div>
|
||||
<div v-show="isExpanded('recharge', row, idx)" class="expand-panel">
|
||||
<div class="expand-grid">
|
||||
<div class="expand-item"><span class="label">充值地址</span><span class="value mono-ellipsis" :title="row.fromAddress">{{ row.fromAddress }}</span></div>
|
||||
<div class="expand-item" v-if="row.txHash"><span class="label">交易哈希</span><span class="value mono-ellipsis" :title="row.txHash">{{ row.txHash }}</span></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div v-if="!rechargeRows.length" class="empty">暂无充值记录</div>
|
||||
</div>
|
||||
</div>
|
||||
</el-tab-pane>
|
||||
<el-tab-pane label="提现记录" name="withdraw">
|
||||
<div class="list-wrap">
|
||||
<div class="list-header">
|
||||
<span class="list-title">全部提现 ({{ withdrawRows.length }})</span>
|
||||
<el-button type="primary" size="small" @click="loadWithdraw">刷新</el-button>
|
||||
</div>
|
||||
<div class="record-list" v-loading="loading.withdraw">
|
||||
<div v-for="(row, idx) in withdrawRows" :key="getRowKey(row, idx)" class="record-item" :class="statusClass(row.status)" @click="toggleExpand('withdraw', row, idx)">
|
||||
<div class="item-main">
|
||||
<div class="item-left">
|
||||
<div class="amount">- {{ formatTrunc(row.amount, 2) }} {{ row.toSymbol || 'USDT' }}</div>
|
||||
<div class="chain">{{ formatChain(row.toChain) }}</div>
|
||||
</div>
|
||||
<div class="item-right">
|
||||
<div class="status"><el-tag :type="getWithdrawStatusType(row.status)" size="small">{{ getWithdrawStatusText(row.status) }}</el-tag></div>
|
||||
<div class="time">{{ formatFullTime(row.createTime) }}</div>
|
||||
</div>
|
||||
</div>
|
||||
<div v-show="isExpanded('withdraw', row, idx)" class="expand-panel">
|
||||
<div class="expand-grid">
|
||||
<div class="expand-item"><span class="label">收款地址</span><span class="value mono-ellipsis" :title="row.toAddress">{{ row.toAddress }}</span></div>
|
||||
<div class="expand-item" v-if="row.txHash"><span class="label">交易哈希</span><span class="value mono-ellipsis" :title="row.txHash">{{ row.txHash }}</span></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div v-if="!withdrawRows.length" class="empty">暂无提现记录</div>
|
||||
</div>
|
||||
</div>
|
||||
</el-tab-pane>
|
||||
<el-tab-pane label="消费记录" name="consume">
|
||||
<div class="list-wrap">
|
||||
<div class="list-header">
|
||||
<span class="list-title">全部消费 ({{ consumeRows.length }})</span>
|
||||
<el-button type="primary" size="small" @click="loadConsume">刷新</el-button>
|
||||
</div>
|
||||
<div class="record-list" v-loading="loading.consume">
|
||||
<div v-for="(row, idx) in consumeRows" :key="getRowKey(row, idx)" class="record-item" :class="statusClass(row.status)" @click="toggleExpand('consume', row, idx)">
|
||||
<div class="item-main">
|
||||
<div class="item-left">
|
||||
<div class="amount">- {{ formatTrunc(row.realAmount, 2) }} {{ (row.fromSymbol || 'USDT').toUpperCase() }}</div>
|
||||
<div class="chain">{{ formatChain(row.fromChain) }}</div>
|
||||
</div>
|
||||
<div class="item-right">
|
||||
<div class="status"><el-tag :type="getPayStatusType(row.status)" size="small">{{ getPayStatusText(row.status) }}</el-tag></div>
|
||||
<div class="time">{{ formatFullTime(row.createTime || row.time) }}</div>
|
||||
</div>
|
||||
</div>
|
||||
<div v-show="isExpanded('consume', row, idx)" class="expand-panel">
|
||||
<div class="expand-grid">
|
||||
<div class="expand-item"><span class="label">订单号</span><span class="value mono">{{ row.orderId || '' }}</span></div>
|
||||
<div class="expand-item"><span class="label">支付地址</span><span class="value mono-ellipsis" :title="row.fromAddress">{{ row.fromAddress || '' }}</span></div>
|
||||
<div class="expand-item"><span class="label">收款地址</span><span class="value mono-ellipsis" :title="row.toAddress">{{ row.toAddress || '' }}</span></div>
|
||||
<div class="expand-item" v-if="row.txHash"><span class="label">交易哈希</span><span class="value mono-ellipsis" :title="row.txHash">{{ row.txHash }}</span></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div v-if="!consumeRows.length" class="empty">暂无消费记录</div>
|
||||
</div>
|
||||
</div>
|
||||
</el-tab-pane>
|
||||
</el-tabs>
|
||||
|
||||
|
||||
<el-row>
|
||||
<el-col :span="24" style="display: flex; justify-content: center">
|
||||
<el-pagination
|
||||
style="margin: 0 auto; margin-top: 10px"
|
||||
@size-change="handleSizeChange"
|
||||
@current-change="handleCurrentChange"
|
||||
:current-page.sync="currentPage"
|
||||
:page-sizes="pageSizes"
|
||||
:page-size="pagination.pageSize"
|
||||
layout="total, sizes, prev, pager, next, jumper"
|
||||
:total="total"
|
||||
>
|
||||
</el-pagination>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { transactionRecord } from '../../api/wallet'
|
||||
|
||||
export default {
|
||||
name: 'AccountFundsFlow',
|
||||
data() {
|
||||
return {
|
||||
active: 'recharge',
|
||||
loading: { recharge: false, withdraw: false, consume: false },
|
||||
rechargeRows: [
|
||||
|
||||
// {
|
||||
// createTime: '2024-01-15 14:30:25',
|
||||
// amount: 100,
|
||||
// fromAddress: 'djdddksfhsfj',
|
||||
// fromChain: 'tron',
|
||||
// fromSymbol: 'USDT',
|
||||
// status: 2,
|
||||
// id: 1,
|
||||
// txHash: 'TQn9Y2khEsLJW1ChVWFMSMeRDow5KcbLSE',
|
||||
// updateTime: '2024-01-15 14:30:25',
|
||||
// },
|
||||
// {
|
||||
// createTime: '2024-01-15 14:30:25',
|
||||
// amount: 100,
|
||||
// fromAddress: 'djdddksfhsfj',
|
||||
// fromChain: 'tron',
|
||||
// fromSymbol: 'USDT',
|
||||
// status: 2,
|
||||
// id: 1,
|
||||
// txHash: 'TQn9Y2khEsLJW1ChVWFMSMeRDow5KcbLSE',
|
||||
// updateTime: '2024-01-15 14:30:25',
|
||||
// },
|
||||
|
||||
],
|
||||
withdrawRows: [
|
||||
// {
|
||||
// createTime: '2024-01-15 14:30:25',
|
||||
// amount: 100,
|
||||
// toAddress: 'djdddksfhsfj',
|
||||
// toChain: 'tron',
|
||||
// toSymbol: 'USDT',
|
||||
// status: 2,
|
||||
// id: 1,
|
||||
// txHash: 'TQn9Y2khEsLJW1ChVWFMSMeRDow5KcbLSE',
|
||||
// updateTime: '2024-01-15 14:30:25',
|
||||
// },
|
||||
// {
|
||||
|
||||
// createTime: '2024-01-15 14:30:25',
|
||||
// amount: 100,
|
||||
// toAddress: 'djdddksfhsfj',
|
||||
// toChain: 'tron',
|
||||
// toSymbol: 'USDT',
|
||||
// status: 2,
|
||||
// id: 1,
|
||||
// txHash: 'TQn9Y2khEsLJW1ChVWFMSMeRDow5KcbLSE',
|
||||
// updateTime: '2024-01-15 14:30:25',
|
||||
// },
|
||||
|
||||
],
|
||||
consumeRows: [
|
||||
|
||||
// {
|
||||
// orderId: '1234567890',
|
||||
// fromAddress: 'djdddksfhsfj',
|
||||
// fromChain: 'tron',
|
||||
// fromSymbol: 'USDT',
|
||||
// createTime: '2024-01-15 14:30:25',
|
||||
// amount: 100,
|
||||
// toAddress: 'djdddksfhsfj',
|
||||
// toChain: 'tron',
|
||||
// toSymbol: 'USDT',
|
||||
// status: 2,
|
||||
// id: 1,
|
||||
// txHash: 'TQn9Y2khEsLJW1ChVWFMSMeRDow5KcbLSE',
|
||||
// updateTime: '2024-01-15 14:30:25',
|
||||
// },
|
||||
// {
|
||||
// orderId: '1234567890',
|
||||
// fromAddress: 'djdddksfhsfj',
|
||||
// fromChain: 'tron',
|
||||
// fromSymbol: 'USDT',
|
||||
// createTime: '2024-01-15 14:30:25',
|
||||
// realAmount: 100, //后端告知只有消费记录金额用这个字段
|
||||
// toAddress: 'djdddksfhsfj',
|
||||
// toChain: 'tron',
|
||||
// toSymbol: 'USDT',
|
||||
// status: 2,
|
||||
// id: 1,
|
||||
// txHash: 'TQn9Y2khEsLJW1ChVWFMSMeRDow5KcbLSE',
|
||||
// updateTime: '2024-01-15 14:30:25',
|
||||
// },
|
||||
],
|
||||
// 手风琴展开集合
|
||||
expandedKeys: new Set(),
|
||||
total: 0,
|
||||
pageSizes: [10, 20, 50],
|
||||
currentPage: 1,
|
||||
// 分页和筛选参数
|
||||
pagination: {
|
||||
pageNum: 1,
|
||||
pageSize: 10,
|
||||
status: 2,
|
||||
|
||||
},
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
const tab = (this.$route && this.$route.query && this.$route.query.tab) || 'recharge'
|
||||
if (['recharge', 'withdraw', 'consume'].includes(tab)) this.active = tab
|
||||
// 初始化按当前 Tab 的状态加载列表
|
||||
this.pagination.status = this.getStatusByTab(this.active)
|
||||
this.loadList()
|
||||
},
|
||||
methods: {
|
||||
/**
|
||||
* 处理 Tab 切换:清空展开状态,确保手风琴行为
|
||||
* @param {any} pane - 当前 pane(Element UI 传入)
|
||||
* @param {Event} evt - 事件对象
|
||||
*/
|
||||
handleTab(pane, evt) {
|
||||
this.expandedKeys.clear()
|
||||
// 触发视图刷新
|
||||
this.expandedKeys = new Set(this.expandedKeys)
|
||||
// 按 Tab 切换刷新对应数据
|
||||
const tabName = (pane && pane.name) || this.active
|
||||
this.pagination.status = this.getStatusByTab(tabName)
|
||||
this.pagination.pageNum = 1
|
||||
this.currentPage = 1
|
||||
this.loadList()
|
||||
},
|
||||
|
||||
/**
|
||||
* 生成唯一键,优先使用后端提供的稳定字段,其次回退到索引
|
||||
* @param {Record<string, any>} row - 当前行数据
|
||||
* @param {number} [idx] - v-for 提供的索引
|
||||
* @returns {string}
|
||||
*/
|
||||
getRowKey(row, idx) {
|
||||
const indexPart = idx != null ? `#${idx}` : ''
|
||||
if (!row) return String(idx != null ? idx : '')
|
||||
const stable = row.__key || row.id || row.txHash || row.orderId || `${row.createTime || ''}-${row.updateTime || ''}`
|
||||
// 始终附加索引,避免重复 id/txHash 导致多条记录共用同一键
|
||||
if (stable == null || stable === '') return String(idx != null ? idx : '')
|
||||
return `${String(stable)}${indexPart}`
|
||||
},
|
||||
/**
|
||||
* 判断当前行是否展开(手风琴:全局仅允许一个展开)
|
||||
* @param {string} type - 列表类型:recharge/withdraw/consume
|
||||
* @param {Record<string, any>} row - 当前行数据
|
||||
* @param {number} [idx] - 行索引
|
||||
* @returns {boolean}
|
||||
*/
|
||||
isExpanded(type, row, idx) {
|
||||
const key = `${type}-${this.getRowKey(row, idx)}`
|
||||
return this.expandedKeys.has(key)
|
||||
},
|
||||
/**
|
||||
* 切换展开(手风琴):若点击同一行则收起,否则清空后仅展开当前行
|
||||
* @param {string} type - 列表类型
|
||||
* @param {Record<string, any>} row - 当前行数据
|
||||
* @param {number} [idx] - 行索引
|
||||
*/
|
||||
toggleExpand(type, row, idx) {
|
||||
const key = `${type}-${this.getRowKey(row, idx)}`
|
||||
if (this.expandedKeys.has(key)) {
|
||||
this.expandedKeys.clear()
|
||||
} else {
|
||||
this.expandedKeys.clear()
|
||||
this.expandedKeys.add(key)
|
||||
}
|
||||
// 触发视图刷新
|
||||
this.expandedKeys = new Set(this.expandedKeys)
|
||||
},
|
||||
|
||||
|
||||
// 统一加载:根据 pagination.status 请求并填充对应列表
|
||||
async loadList() {
|
||||
const status = Number(this.pagination.status)
|
||||
const typeKey = this.getTypeKeyByStatus(status)
|
||||
if (!typeKey) return
|
||||
this.loading[typeKey] = true
|
||||
try {
|
||||
const res = await transactionRecord({
|
||||
pageNum: this.pagination.pageNum,
|
||||
pageSize: this.pagination.pageSize,
|
||||
status
|
||||
})
|
||||
const rows = res?.rows || res?.data?.rows || []
|
||||
this.total = res?.total || res?.data?.total || (Array.isArray(rows) ? rows.length : 0)
|
||||
const mapped = (Array.isArray(rows) ? rows : []).map((r, i) => ({
|
||||
...r,
|
||||
__key: r.id || r.txHash || r.orderId || `${i}`
|
||||
}))
|
||||
if (status === 2) this.rechargeRows = mapped
|
||||
else if (status === 1) this.withdrawRows = mapped
|
||||
else this.consumeRows = mapped
|
||||
// 刷新时收起展开
|
||||
this.expandedKeys.clear()
|
||||
this.expandedKeys = new Set(this.expandedKeys)
|
||||
} finally {
|
||||
this.loading[typeKey] = false
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* 包装:根据传入状态加载,并同步 Tab/分页重置
|
||||
* @param {0|1|2} status - 0 支付,1 提现,2 充值
|
||||
*/
|
||||
loadByStatus(status) {
|
||||
this.pagination.status = status
|
||||
this.active = this.getTabByStatus(status)
|
||||
this.pagination.pageNum = 1
|
||||
this.currentPage = 1
|
||||
return this.loadList()
|
||||
},
|
||||
|
||||
// 兼容现有按钮点击
|
||||
loadRecharge() { return this.loadByStatus(2) },
|
||||
loadWithdraw() { return this.loadByStatus(1) },
|
||||
loadConsume() { return this.loadByStatus(0) },
|
||||
|
||||
statusClass(status) { return { 0: 'failed', 1: 'success', 2: 'pending' }[status] || 'neutral' },
|
||||
getRechargeStatusType(s) { return ({ 0: 'danger', 1: 'success', 2: 'warning' })[s] || 'info' },
|
||||
getRechargeStatusText(s) { return ({ 0: '充值失败', 1: '充值成功', 2: '充值中', 3: '证书校验失败' })[s] || '未知' },
|
||||
getWithdrawStatusType(s) { return ({ 0: 'danger', 1: 'success', 2: 'warning' })[s] || 'info' },
|
||||
getWithdrawStatusText(s) { return ({ 0: '提现失败', 1: '提现成功', 2: '提现中', 3: '证书校验失败' })[s] || '未知' },
|
||||
getPayStatusType(s) { return ({ 0: 'danger', 1: 'success', 2: 'warning', 3: 'danger' })[s] || 'info' },
|
||||
getPayStatusText(s) { return ({ 0: '支付失败', 1: '支付成功', 2: '待校验', 3: '证书校验失败' })[s] || '未知' },
|
||||
|
||||
formatChain(chain) {
|
||||
const map = { tron: 'Tron (TRC20)', ethereum: 'Ethereum (ERC20)', bsc: 'BSC (BEP20)', polygon: 'Polygon (MATIC)' }
|
||||
return map[chain] || chain
|
||||
},
|
||||
formatFullTime(time) { if (!time) return ''; try { return new Date(time).toLocaleString('zh-CN') } catch (e) { return String(time) } },
|
||||
formatTime(time) { return this.formatFullTime(time) },
|
||||
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}`
|
||||
},
|
||||
handleSizeChange(val) {
|
||||
console.log(`每页 ${val} 条`);
|
||||
this.pagination.pageSize = val;
|
||||
this.pagination.pageNum = 1;
|
||||
this.currentPage = 1;
|
||||
this.loadList();
|
||||
},
|
||||
handleCurrentChange(val) {
|
||||
console.log(`当前页: ${val}`);
|
||||
this.pagination.pageNum = val;
|
||||
this.loadList();
|
||||
},
|
||||
|
||||
/**
|
||||
* Tab 名称转状态码
|
||||
* @param {string} tabName - 'recharge' | 'withdraw' | 'consume'
|
||||
* @returns {0|1|2}
|
||||
*/
|
||||
getStatusByTab(tabName) {
|
||||
if (tabName === 'recharge') return 2
|
||||
if (tabName === 'withdraw') return 1
|
||||
return 0
|
||||
},
|
||||
/**
|
||||
* 状态码转 Tab 名称
|
||||
* @param {number} status - 0|1|2
|
||||
* @returns {'recharge'|'withdraw'|'consume'}
|
||||
*/
|
||||
getTabByStatus(status) {
|
||||
if (Number(status) === 2) return 'recharge'
|
||||
if (Number(status) === 1) return 'withdraw'
|
||||
return 'consume'
|
||||
},
|
||||
/**
|
||||
* 状态码转 loading/列表 key
|
||||
* @param {number} status - 0|1|2
|
||||
* @returns {'recharge'|'withdraw'|'consume'|''}
|
||||
*/
|
||||
getTypeKeyByStatus(status) {
|
||||
if (Number(status) === 2) return 'recharge'
|
||||
if (Number(status) === 1) return 'withdraw'
|
||||
if (Number(status) === 0) return 'consume'
|
||||
return ''
|
||||
},
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.funds-page { padding: 8px; }
|
||||
.tabs-card { background: #fff; border: 1px solid #eee; border-radius: 10px; padding: 12px; }
|
||||
.list-wrap { padding: 6px 0; }
|
||||
.list-header { display: flex; justify-content: space-between; align-items: center; margin-bottom: 8px; padding-bottom: 8px; border-bottom: 1px solid #f0f0f0; }
|
||||
.list-title { font-size: 14px; font-weight: 600; color: #333; }
|
||||
.record-list { display: flex; flex-direction: column; gap: 10px; max-height: 62vh; overflow-y: auto; }
|
||||
.record-item { background: #f8f9fa; border-radius: 8px; padding: 12px; border: 1px solid transparent; transition: all .15s ease; }
|
||||
.record-item:hover { background: #eef2f7; border-color: #409eff; box-shadow: 0 4px 12px rgba(64,158,255,.12); transform: translateY(-1px); }
|
||||
.record-item.pending { border-left: 4px solid #e6a23c; }
|
||||
.record-item.success { border-left: 4px solid #67c23a; }
|
||||
.record-item.failed { border-left: 4px solid #f56c6c; }
|
||||
.item-main { display: flex; justify-content: space-between; align-items: center; }
|
||||
.item-left .amount { font-size: 16px; font-weight: 700; color: #111; margin-bottom: 2px; }
|
||||
.item-left .chain { font-size: 12px; color: #666; }
|
||||
.item-right { text-align: right; }
|
||||
.status { margin-bottom: 2px; }
|
||||
.time { font-size: 12px; color: #999; }
|
||||
.expand-panel { background: #fff; border: 1px dashed #e5e7eb; border-radius: 8px; padding: 10px; margin-top: 8px; }
|
||||
.expand-grid { display: grid; grid-template-columns: 1fr 1fr; gap: 10px 24px; }
|
||||
.expand-item { display: grid; grid-template-columns: 80px 1fr; gap: 6px; align-items: center; }
|
||||
.label { color: #666; font-size: 13px; text-align: right; }
|
||||
.value { color: #333; font-size: 13px; text-align: left; }
|
||||
.mono { font-family: "Monaco", "Menlo", monospace; }
|
||||
.mono-ellipsis { font-family: "Monaco", "Menlo", monospace; max-width: 480px; display: inline-block; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
||||
.empty { text-align: center; color: #999; padding: 20px 0; }
|
||||
</style>
|
||||
|
||||
|
||||
@@ -13,67 +13,34 @@
|
||||
<div class="user-email" :title="userEmail || '未登录'">{{ userEmail || '未登录' }}</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="user-role">
|
||||
<button>买家相关</button>
|
||||
<button>卖家相关</button>
|
||||
<div class="user-role" role="group" aria-label="导航分组切换">
|
||||
<button
|
||||
class="role-button"
|
||||
:class="{ active: activeRole === 'buyer' }"
|
||||
@click="handleClickRole('buyer')"
|
||||
@keydown.enter.prevent="handleClickRole('buyer')"
|
||||
@keydown.space.prevent="handleClickRole('buyer')"
|
||||
:aria-pressed="activeRole === 'buyer'"
|
||||
tabindex="0"
|
||||
>买家相关</button>
|
||||
<button
|
||||
class="role-button"
|
||||
:class="{ active: activeRole === 'seller' }"
|
||||
@click="handleClickRole('seller')"
|
||||
@keydown.enter.prevent="handleClickRole('seller')"
|
||||
@keydown.space.prevent="handleClickRole('seller')"
|
||||
:aria-pressed="activeRole === 'seller'"
|
||||
tabindex="0"
|
||||
>卖家相关</button>
|
||||
</div>
|
||||
|
||||
<router-link
|
||||
to="/account/wallet"
|
||||
v-for="item in displayedLinks"
|
||||
:key="item.to"
|
||||
:to="item.to"
|
||||
class="side-link"
|
||||
active-class="active"
|
||||
>我的钱包</router-link
|
||||
>
|
||||
<!-- <router-link
|
||||
to="/account/shop-new"
|
||||
class="side-link"
|
||||
active-class="active"
|
||||
>新增店铺</router-link
|
||||
> -->
|
||||
<router-link
|
||||
to="/account/shop-config"
|
||||
class="side-link"
|
||||
active-class="active"
|
||||
>钱包绑定</router-link
|
||||
>
|
||||
<router-link
|
||||
to="/account/shops"
|
||||
class="side-link"
|
||||
active-class="active"
|
||||
>我的店铺</router-link
|
||||
>
|
||||
<router-link
|
||||
to="/account/products"
|
||||
class="side-link"
|
||||
active-class="active"
|
||||
>商品列表</router-link
|
||||
>
|
||||
<router-link
|
||||
to="/account/purchased"
|
||||
class="side-link"
|
||||
active-class="active"
|
||||
>已购商品</router-link
|
||||
>
|
||||
<router-link
|
||||
to="/account/seller-orders"
|
||||
class="side-link"
|
||||
active-class="active"
|
||||
>已售出订单</router-link>
|
||||
<router-link
|
||||
to="/account/orders"
|
||||
class="side-link"
|
||||
active-class="active"
|
||||
>已购买订单列表</router-link>
|
||||
|
||||
<router-link
|
||||
to="/account/rechargeRecord"
|
||||
class="side-link"
|
||||
active-class="active"
|
||||
>充值记录</router-link>
|
||||
<router-link
|
||||
to="/account/withdrawalHistory"
|
||||
class="side-link"
|
||||
active-class="active"
|
||||
>提现记录</router-link>
|
||||
>{{ item.label }}</router-link>
|
||||
</nav>
|
||||
</aside>
|
||||
|
||||
@@ -92,6 +59,26 @@ export default {
|
||||
return {
|
||||
activeIndex: '1',
|
||||
userEmail: '',
|
||||
// 导航分组:buyer(买家) / seller(卖家);默认卖家
|
||||
activeRole: 'seller',
|
||||
// 买家侧导航
|
||||
buyerLinks: [
|
||||
{ label: '我的钱包', to: '/account/wallet' },
|
||||
{ label: '已购商品', to: '/account/purchased' },
|
||||
{ label: '订单列表', to: '/account/orders' },
|
||||
// { label: '充值记录', to: '/account/rechargeRecord' },
|
||||
// { label: '提现记录', to: '/account/withdrawalHistory' },
|
||||
{ label: '资金流水', to: '/account/funds-flow' },
|
||||
],
|
||||
// 卖家侧导航
|
||||
sellerLinks: [
|
||||
// { label: '我的钱包', to: '/account/wallet' },
|
||||
{ label: '我的店铺', to: '/account/shops' },
|
||||
{ label: '商品列表', to: '/account/products' },
|
||||
{ label: '已售出订单', to: '/account/seller-orders' },
|
||||
{ label: '收款记录', to: '/account/receipt-record' },
|
||||
|
||||
],
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
@@ -103,6 +90,13 @@ export default {
|
||||
const email = (this.userEmail || '').trim()
|
||||
return email ? email[0].toUpperCase() : '?'
|
||||
},
|
||||
/**
|
||||
* 根据当前分组返回展示的导航链接
|
||||
* @returns {{label:string,to:string}[]}
|
||||
*/
|
||||
displayedLinks() {
|
||||
return this.activeRole === 'buyer' ? this.buyerLinks : this.sellerLinks
|
||||
},
|
||||
},
|
||||
mounted() {
|
||||
const getVal = (key) => {
|
||||
@@ -112,6 +106,23 @@ export default {
|
||||
}
|
||||
const val = getVal('userName') || getVal('userEmail') || ''
|
||||
this.userEmail = typeof val === 'string' ? val : String(val)
|
||||
// 恢复上次选择的导航分组(如无则默认 seller)
|
||||
const savedRole = getVal('accountActiveRole')
|
||||
if (savedRole === 'buyer' || savedRole === 'seller') {
|
||||
this.activeRole = savedRole
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
/**
|
||||
* 点击切换导航分组
|
||||
* @param {('buyer'|'seller')} role 要切换到的分组
|
||||
* @returns {void}
|
||||
*/
|
||||
handleClickRole(role) {
|
||||
if (role !== 'buyer' && role !== 'seller') return
|
||||
this.activeRole = role
|
||||
try { localStorage.setItem('accountActiveRole', JSON.stringify(role)) } catch (e) {}
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
@@ -156,6 +167,30 @@ export default {
|
||||
flex-direction: column;
|
||||
gap: 8px;
|
||||
}
|
||||
/* 分组切换按钮样式 */
|
||||
.user-role {
|
||||
display: flex;
|
||||
gap: 8px;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
.role-button {
|
||||
appearance: none;
|
||||
background: #f6f8fa;
|
||||
border: 1px solid #e5e7eb;
|
||||
border-radius: 6px;
|
||||
padding: 6px 10px;
|
||||
color: #2c3e50;
|
||||
cursor: pointer;
|
||||
}
|
||||
.role-button.active {
|
||||
background: #42b983;
|
||||
border-color: #42b983;
|
||||
color: #fff;
|
||||
}
|
||||
.role-button:focus {
|
||||
outline: 2px solid #42b98333;
|
||||
outline-offset: 2px;
|
||||
}
|
||||
/* 用户信息卡片:置于导航最前,展示邮箱与首字母头像 */
|
||||
.user-info-card {
|
||||
display: flex;
|
||||
|
||||
@@ -2,6 +2,30 @@
|
||||
<div class="panel" >
|
||||
<h2 class="panel-title">我的店铺</h2>
|
||||
<div class="panel-body">
|
||||
<!-- 店铺层级说明 -->
|
||||
<el-card class="guide-card" shadow="never" style="margin-bottom: 16px;">
|
||||
<div slot="header" class="guide-header">店铺层级说明</div>
|
||||
<div class="guide-content">
|
||||
<p class="hierarchy">层级结构:店铺 → 商品 → 出售机器</p>
|
||||
<ol class="guide-steps">
|
||||
<li>
|
||||
<b>店铺(唯一)</b>:每个用户在平台<strong>仅能创建一个店铺</strong>。创建成功后,
|
||||
请在本页点击 <b>钱包绑定</b>,配置自己的收款地址(支持不同链与币种)。
|
||||
</li>
|
||||
<li>
|
||||
<b>商品</b>:完成钱包绑定后,即可在“我的店铺”页面 <b>创建商品</b>。
|
||||
商品可按 <b>币种</b> 进行分类管理,创建的商品会在商城对买家展示。
|
||||
商品可理解为“不同算法、币种的机器集合分类”。
|
||||
</li>
|
||||
<li>
|
||||
<b>出售机器</b>:创建商品后,请进入 <b>商品列表</b> 为该商品 <b>添加出售机器明细</b>。
|
||||
必须添加出售机器,否则买家无法下单。买家点击某个商品后,会看到该商品下的机器明细并进行选购。
|
||||
</li>
|
||||
</ol>
|
||||
<div class="guide-note">提示:建议先创建店铺 → 完成钱包绑定 → 创建商品 → 添加出售机器的顺序,避免漏配导致无法收款或无法下单。</div>
|
||||
</div>
|
||||
</el-card>
|
||||
|
||||
<el-card v-if="loaded && hasShop" class="shop-card" shadow="hover">
|
||||
<div class="shop-row">
|
||||
<div class="shop-cover">
|
||||
@@ -26,26 +50,48 @@
|
||||
</el-button>
|
||||
<el-button size="small" type="danger" @click="handleDelete">删除店铺</el-button>
|
||||
<el-button size="small" type="success" @click="handleAddProduct">新增商品</el-button>
|
||||
<el-button size="small" type="success" @click="handleWalletBind">钱包绑定</el-button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</el-card>
|
||||
|
||||
<!-- 店铺配置表格 -->
|
||||
<!-- <el-card v-if="loaded && hasShop" class="shop-config-card" shadow="never" style="margin-top: 16px;">
|
||||
<el-card v-if="loaded && hasShop" class="shop-config-card" shadow="never" style="margin-top: 16px;">
|
||||
<div slot="header" class="clearfix">
|
||||
<span>店铺配置</span>
|
||||
<span>已绑定钱包</span>
|
||||
</div>
|
||||
<el-table :data="shopConfigs" border style="width: 100%">
|
||||
<el-table-column prop="id" label="ID" width="80" />
|
||||
<el-table-column label="商品" width="120">
|
||||
<template slot-scope="scope">{{ scope.row.productId === 0 ? '所有商品' : scope.row.productId }}</template>
|
||||
<el-table-column prop="chain" label="链" width="140" />
|
||||
<el-table-column label="支付币种" >
|
||||
<template slot-scope="scope">
|
||||
<div class="coin-list">
|
||||
<template v-if="Array.isArray(scope.row.children) && scope.row.children.length">
|
||||
<el-tooltip
|
||||
v-for="(c, idx) in scope.row.children"
|
||||
:key="idx"
|
||||
:content="String(c && c.payCoin ? c.payCoin : '').toUpperCase()"
|
||||
placement="top"
|
||||
>
|
||||
<img
|
||||
v-if="c && c.image"
|
||||
class="coin-img"
|
||||
:src="c.image"
|
||||
:alt="(c.payCoin || '').toUpperCase()"
|
||||
/>
|
||||
</el-tooltip>
|
||||
</template>
|
||||
<template v-else>
|
||||
{{ String(scope.row.payCoin || '').toUpperCase() }}
|
||||
</template>
|
||||
</div>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="payType" label="币种类型" width="120">
|
||||
<!-- <el-table-column prop="payType" label="币种类型" width="120">
|
||||
<template slot-scope="scope">{{ scope.row.payType === 1 ? '稳定币' : '虚拟币' }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="payCoin" label="支付币种" width="140" />
|
||||
<el-table-column prop="payAddress" label="收款钱包地址" />
|
||||
</el-table-column> -->
|
||||
|
||||
<el-table-column prop="payAddress" label="收款钱包地址" show-overflow-tooltip />
|
||||
<el-table-column label="操作" width="180" fixed="right">
|
||||
<template slot-scope="scope">
|
||||
<el-button type="text" @click="handleEditConfig(scope.row)">修改</el-button>
|
||||
@@ -54,7 +100,7 @@
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
</el-card> -->
|
||||
</el-card>
|
||||
|
||||
<div v-else-if="loaded && !hasShop" class="no-shop">
|
||||
<el-empty description="暂无店铺">
|
||||
@@ -84,18 +130,35 @@
|
||||
<el-button type="primary" @click="submitEdit">保存</el-button>
|
||||
</span>
|
||||
</el-dialog>
|
||||
<!-- 修改店铺配置弹窗 -->
|
||||
<!-- 修改钱包绑定配置弹窗:参数保持与列表一致 -->
|
||||
<el-dialog title="修改配置" :visible.sync="visibleConfigEdit" width="560px">
|
||||
<div class="row">
|
||||
<label class="label">适用商品</label>
|
||||
<el-select v-model="configForm.productId" placeholder="请选择商品">
|
||||
<el-option :value="0" label="所有商品" />
|
||||
<el-option v-for="p in productOptions" :key="p.id" :value="p.id" :label="`${p.id} - ${p.name}`" />
|
||||
<label class="label">支付链</label>
|
||||
<el-select v-model="configForm.chain" placeholder="请选择链">
|
||||
<el-option v-for="c in chainOptions" :key="c.value" :value="c.value" :label="c.label" />
|
||||
</el-select>
|
||||
</div>
|
||||
<div class="row">
|
||||
<label class="label">收款地址</label>
|
||||
<el-input v-model="configForm.payAddress" placeholder="请输入钱包地址" />
|
||||
<label class="label">支付币种</label>
|
||||
<el-select
|
||||
class="input"
|
||||
size="middle"
|
||||
ref="screen"
|
||||
v-model="configForm.payCoin"
|
||||
placeholder="请选择币种"
|
||||
>
|
||||
<el-option
|
||||
v-for="item in editCoinOptions"
|
||||
:key="item.value"
|
||||
:label="item.label"
|
||||
:value="item.value"
|
||||
>
|
||||
<div style="display: flex; align-items: center">
|
||||
<img v-if="item.imgUrl" :src="item.imgUrl" style="float: left; width: 20px" />
|
||||
<span style="float: left; margin-left: 5px">{{ item.label }}</span>
|
||||
</div>
|
||||
</el-option>
|
||||
</el-select>
|
||||
</div>
|
||||
<div class="row">
|
||||
<label class="label">币种类型</label>
|
||||
@@ -105,29 +168,8 @@
|
||||
</el-radio-group>
|
||||
</div>
|
||||
<div class="row">
|
||||
<label class="label">支付币种</label>
|
||||
|
||||
<el-select
|
||||
class="input"
|
||||
size="middle"
|
||||
ref="screen"
|
||||
v-model="configForm.payCoin"
|
||||
placeholder="请选择"
|
||||
>
|
||||
<el-option
|
||||
v-for="item in coinOptions"
|
||||
:key="item.value"
|
||||
:label="item.label"
|
||||
:value="item.value"
|
||||
>
|
||||
<div style="display: flex; align-items: center">
|
||||
<img :src="item.imgUrl" style="float: left; width: 20px" />
|
||||
<span style="float: left; margin-left: 5px">
|
||||
{{ item.label }}</span
|
||||
>
|
||||
</div>
|
||||
</el-option>
|
||||
</el-select>
|
||||
<label class="label">钱包地址</label>
|
||||
<el-input v-model="configForm.payAddress" placeholder="请输入钱包地址" />
|
||||
</div>
|
||||
<span slot="footer" class="dialog-footer">
|
||||
<el-button @click="visibleConfigEdit=false">取消</el-button>
|
||||
@@ -139,9 +181,10 @@
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { getMyShop, updateShop, deleteShop, queryShop, closeShop, getShopConfig ,updateShopConfig,deleteShopConfig} from '@/api/shops'
|
||||
import { getMyShop, updateShop, deleteShop, queryShop, closeShop ,updateShopConfig,deleteShopConfig} from '@/api/shops'
|
||||
|
||||
import { coinList } from '@/utils/coinList'
|
||||
import { getShopConfig } from '@/api/wallet'
|
||||
|
||||
|
||||
export default {
|
||||
@@ -163,9 +206,16 @@ export default {
|
||||
// 店铺配置列表
|
||||
shopConfigs: [],
|
||||
visibleConfigEdit: false,
|
||||
configForm: { id: '', payAddress: '', payCoin: '', payType: 0, productId: 0 },
|
||||
configForm: { id: '', chain: '', payAddress: '', payCoin: '', payType: 0 },
|
||||
productOptions: [],
|
||||
coinOptions: coinList || [],
|
||||
// 支付链选项(可与后端接口对齐后替换为动态)
|
||||
chainOptions: [
|
||||
{ label: 'Tron (TRC20)', value: 'tron' },
|
||||
{ label: 'Ethereum (ERC20)', value: 'ethereum' },
|
||||
{ label: 'BSC (BEP20)', value: 'bsc' },
|
||||
{ label: 'Nexa', value: 'nexa' },
|
||||
],
|
||||
shopLoading: false
|
||||
}
|
||||
},
|
||||
@@ -189,6 +239,19 @@ export default {
|
||||
},
|
||||
canCreateShop() {
|
||||
return !this.hasShop
|
||||
},
|
||||
/**
|
||||
* 弹窗可选币种:稳定币/虚拟币分流
|
||||
*/
|
||||
editCoinOptions() {
|
||||
if (Number(this.configForm.payType) === 1) {
|
||||
return [
|
||||
{ label: 'USDT', value: 'usdt' },
|
||||
{ label: 'USDC', value: 'usdc' },
|
||||
{ label: 'BUSD', value: 'busd' },
|
||||
]
|
||||
}
|
||||
return this.coinOptions
|
||||
}
|
||||
},
|
||||
created() {
|
||||
@@ -230,7 +293,7 @@ export default {
|
||||
del: !!res.data.del,
|
||||
state: Number(res.data.state || 0)
|
||||
}
|
||||
// 同步加载店铺配置
|
||||
// 同步加载钱包绑定
|
||||
this.fetchShopConfigs(res.data.id)
|
||||
} else {
|
||||
// 当接口返回错误或没有数据时,重置店铺状态
|
||||
@@ -257,8 +320,9 @@ export default {
|
||||
}
|
||||
|
||||
try {
|
||||
const res = await getShopConfig(shopId)
|
||||
const res = await getShopConfig({id:shopId})
|
||||
if (res && (res.code === 0 || res.code === 200) && Array.isArray(res.data)) {
|
||||
// 直接使用后端返回的数据;children: [{payCoin,image}]
|
||||
this.shopConfigs = res.data
|
||||
} else {
|
||||
this.shopConfigs = []
|
||||
@@ -286,7 +350,13 @@ export default {
|
||||
}
|
||||
},
|
||||
handleEditConfig(row) {
|
||||
this.configForm = { ...row }
|
||||
this.configForm = {
|
||||
id: row.id,
|
||||
chain: row.chain || '',
|
||||
payCoin: row.payCoin || '',
|
||||
payType: typeof row.payType === 'number' ? row.payType : Number(row.payType || 0),
|
||||
payAddress: row.payAddress || '',
|
||||
}
|
||||
this.visibleConfigEdit = true
|
||||
|
||||
},
|
||||
@@ -295,8 +365,23 @@ export default {
|
||||
},
|
||||
|
||||
submitConfigEdit() {
|
||||
this.updateShopConfig(this.configForm)
|
||||
|
||||
// 基础校验
|
||||
if (!this.configForm.chain) {
|
||||
this.$message.warning('请选择支付链')
|
||||
return
|
||||
}
|
||||
if (!this.configForm.payCoin) {
|
||||
this.$message.warning('请选择支付币种')
|
||||
return
|
||||
}
|
||||
const addr = (this.configForm.payAddress || '').trim()
|
||||
if (!addr) {
|
||||
this.$message.warning('请输入钱包地址')
|
||||
return
|
||||
}
|
||||
const { productId, ...rest } = this.configForm
|
||||
const payload = { ...rest, payType: Number(this.configForm.payType || 0) }
|
||||
this.updateShopConfig(payload)
|
||||
},
|
||||
async handleOpenEdit() {
|
||||
try {
|
||||
@@ -470,7 +555,26 @@ export default {
|
||||
path: '/account/product-new',
|
||||
query: { shopId: this.shop.id }
|
||||
})
|
||||
},
|
||||
/**
|
||||
* 跳转到钱包绑定页面
|
||||
*/
|
||||
handleWalletBind() {
|
||||
if (!this.hasShop) {
|
||||
this.$message({
|
||||
message: '请先创建店铺',
|
||||
type: 'warning',
|
||||
showClose: true
|
||||
})
|
||||
return
|
||||
}
|
||||
this.$router.push('/account/shop-config')
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
</script>
|
||||
@@ -485,6 +589,16 @@ export default {
|
||||
.desc { color: #666; }
|
||||
.meta { color: #999; display: flex; gap: 16px; font-size: 12px; }
|
||||
.actions { margin-top: 8px; display: flex; gap: 8px; }
|
||||
.guide-card { border: 1px solid #eef2f7; border-radius: 10px; }
|
||||
.guide-header { text-align: center; font-weight: 700; color: #2c3e50; background: #f9fafb; border-bottom: 1px solid #eef2f7; padding: 10px 12px; border-radius: 10px 10px 0 0; }
|
||||
.guide-content { padding: 4px 6px; text-align: left; }
|
||||
.guide-card .hierarchy { margin: 0 0 8px 0; color: #111827; font-weight: 700; font-size: 14px; }
|
||||
.guide-steps { margin: 0; padding-left: 18px; color: #374151; }
|
||||
.guide-steps li { line-height: 1.9; margin: 6px 0; }
|
||||
.guide-steps b { color: #111827; }
|
||||
.guide-note { margin-top: 10px; color: #6b7280; font-size: 13px; background: #f9fafb; border: 1px dashed #e5e7eb; padding: 8px 10px; border-radius: 8px; }
|
||||
.coin-list { display: flex; align-items: center; gap: 8px; }
|
||||
.coin-img { width: 20px; height: 20px; border-radius: 4px; display: inline-block; }
|
||||
</style>
|
||||
|
||||
<style>
|
||||
|
||||
@@ -79,8 +79,14 @@
|
||||
<el-table-column prop="user" label="挖矿账户" min-width="80" />
|
||||
<el-table-column prop="id" label="矿机ID" min-width="60" />
|
||||
<el-table-column prop="miner" label="机器编号" min-width="100" />
|
||||
<el-table-column label="实时算力">
|
||||
<template #default="scope">{{ scope.row.computingPower }} {{ scope.row.unit || '' }}</template>
|
||||
<el-table-column label="实际算力" width="100">
|
||||
<template slot="header">
|
||||
<el-tooltip content="实际算力为该机器在本矿池过去24H的平均算力" effect="dark" placement="top">
|
||||
<i class="el-icon-question label-help" aria-label="帮助" tabindex="0"></i>
|
||||
</el-tooltip>
|
||||
<span>实际算力</span>
|
||||
</template>
|
||||
<template slot-scope="scope">{{ scope.row.computingPower }} {{ scope.row.unit || '' }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="理论算力" min-width="140">
|
||||
<template #default="scope">
|
||||
@@ -128,8 +134,20 @@
|
||||
/>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="单价(USDT)" min-width="140">
|
||||
<template #default="scope">
|
||||
<el-table-column label="售价(USDT)" min-width="140">
|
||||
<template slot="header">
|
||||
<el-tooltip effect="dark" placement="top">
|
||||
<div slot="content">
|
||||
卖家最终收款金额 = 机器售价 × 波动率<br/>
|
||||
波动率规则:<br/>
|
||||
1)0% - 5%(包含5%):波动率 = 1(按售价结算)<br/>
|
||||
2)5%以上:波动率 = 实际算力 / 理论算力,且不会超过 1,即最终结算时不会超过机器售价
|
||||
</div>
|
||||
<i class="el-icon-question label-help" aria-label="帮助" tabindex="0"></i>
|
||||
</el-tooltip>
|
||||
<span>售价(USDT)</span>
|
||||
</template>
|
||||
<template slot-scope="scope">
|
||||
<el-input
|
||||
v-model="scope.row.price"
|
||||
size="small"
|
||||
@@ -144,6 +162,22 @@
|
||||
</el-input>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="最大租赁天数(天)" min-width="140">
|
||||
<template #default="scope">
|
||||
<el-input
|
||||
v-model="scope.row.maxLeaseDays"
|
||||
size="small"
|
||||
inputmode="numeric"
|
||||
:disabled="isRowDisabled(scope.row)"
|
||||
@input="handleMaxLeaseDaysInput(scope.$index)"
|
||||
@blur="handleMaxLeaseDaysBlur(scope.$index)"
|
||||
:class="{ 'changed-input': isCellChanged(scope.row, 'maxLeaseDays') }"
|
||||
style="max-width: 260px;"
|
||||
>
|
||||
<template slot="append">天</template>
|
||||
</el-input>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="上下架" min-width="140">
|
||||
<template #default="scope">
|
||||
<el-switch
|
||||
@@ -309,6 +343,7 @@ export default {
|
||||
powerDissipation: String(row.powerDissipation ?? ''),
|
||||
type: String(row.type ?? ''),
|
||||
price: String(row.price ?? ''),
|
||||
maxLeaseDays: String(row.maxLeaseDays ?? ''),
|
||||
}
|
||||
}
|
||||
this.fieldSnapshot = snapshot
|
||||
@@ -443,6 +478,30 @@ export default {
|
||||
this.$set(this.machineList, index, row)
|
||||
}
|
||||
},
|
||||
handleMaxLeaseDaysInput(index) {
|
||||
const rowItem = this.machineList && this.machineList[index]
|
||||
if (!rowItem || this.isRowDisabled(rowItem)) return
|
||||
let v = String(this.machineList[index].maxLeaseDays ?? '')
|
||||
v = v.replace(/\D/g, '')
|
||||
if (v.length > 3) v = v.slice(0, 3)
|
||||
const row = { ...this.machineList[index], maxLeaseDays: v }
|
||||
this.$set(this.machineList, index, row)
|
||||
},
|
||||
handleMaxLeaseDaysBlur(index) {
|
||||
const raw = String(this.machineList[index].maxLeaseDays ?? '')
|
||||
if (!/^\d{1,3}$/.test(raw)) {
|
||||
this.$message.warning('最大租赁天数需为 1-365 的整数')
|
||||
const row = { ...this.machineList[index], maxLeaseDays: '' }
|
||||
this.$set(this.machineList, index, row)
|
||||
return
|
||||
}
|
||||
const n = Number(raw)
|
||||
if (!Number.isInteger(n) || n < 1 || n > 365) {
|
||||
this.$message.warning('最大租赁天数需为 1-365 的整数')
|
||||
const row = { ...this.machineList[index], maxLeaseDays: '' }
|
||||
this.$set(this.machineList, index, row)
|
||||
}
|
||||
},
|
||||
handleTheoryPowerBlur(index) {
|
||||
const raw = String(this.machineList[index].theoryPower ?? '')
|
||||
const pattern = /^\d{1,6}(\.\d{1,4})?$/
|
||||
@@ -511,6 +570,7 @@ export default {
|
||||
const priceRaw = String(row.price ?? '')
|
||||
const typeRaw = String(row.type ?? '')
|
||||
const dissRaw = String(row.powerDissipation ?? '')
|
||||
const daysRaw = String(row.maxLeaseDays ?? '')
|
||||
|
||||
if (!theoryRaw || Number(theoryRaw) <= 0 || !powerPattern.test(theoryRaw)) {
|
||||
this.$message.warning(`第${i + 1}行(机器:${rowLabel}) 理论算力必须大于0,整数最多6位,小数最多4位`)
|
||||
@@ -524,6 +584,10 @@ export default {
|
||||
this.$message.warning(`第${i + 1}行(机器:${rowLabel}) 单价必须大于0,整数最多12位,小数最多2位`)
|
||||
return
|
||||
}
|
||||
if (!/^\d{1,3}$/.test(daysRaw) || !Number.isInteger(Number(daysRaw)) || Number(daysRaw) < 1 || Number(daysRaw) > 365) {
|
||||
this.$message.warning(`第${i + 1}行(机器:${rowLabel}) 最大租赁天数需为 1-365 的整数`)
|
||||
return
|
||||
}
|
||||
// 型号允许为空,但如果填写则不能全空格
|
||||
if (typeRaw && isOnlySpaces(typeRaw)) {
|
||||
this.$message.warning(`第${i + 1}行(机器:${rowLabel}) 型号不能全是空格`)
|
||||
@@ -538,6 +602,7 @@ export default {
|
||||
state: Number(m.state ?? 0),
|
||||
theoryPower: Number(m.theoryPower ?? 0),
|
||||
type: m.type || '',
|
||||
maxLeaseDays: Number(m.maxLeaseDays ?? 0),
|
||||
unit: m.unit || ''
|
||||
}))
|
||||
|
||||
@@ -575,6 +640,8 @@ export default {
|
||||
.split { width: 8px; }
|
||||
.empty-text { color: #909399; text-align: center; padding: 12px 0; }
|
||||
|
||||
.label-help { margin-left: 4px; color: #909399; cursor: help; }
|
||||
|
||||
|
||||
</style>
|
||||
<style>
|
||||
@@ -587,5 +654,13 @@ export default {
|
||||
.changed-input input.el-input__inner {
|
||||
border-color: #f56c6c !important;
|
||||
}
|
||||
|
||||
.el-input.is-disabled .el-input__inner{
|
||||
color: #000 !important;
|
||||
}
|
||||
|
||||
.el-textarea.is-disabled .el-textarea__inner{
|
||||
color: #000 !important;
|
||||
}
|
||||
</style>
|
||||
|
||||
|
||||
@@ -5,35 +5,25 @@
|
||||
<h2 class="title">添加出售机器</h2>
|
||||
</div>
|
||||
|
||||
<el-alert
|
||||
class="notice-alert"
|
||||
type="warning"
|
||||
show-icon
|
||||
:closable="false"
|
||||
title="新增出售机器必须在 M2pool 有挖矿算力记录才能添加出租"
|
||||
description="建议稳定在 M2pool 矿池挖矿 24 小时之后,再添加出售该机器"
|
||||
/>
|
||||
|
||||
<el-card shadow="never" class="form-card">
|
||||
<el-form ref="machineForm" :model="form" :rules="rules" label-width="160px" size="small">
|
||||
<el-form-item label="商品名称">
|
||||
<el-input v-model="form.productName" disabled />
|
||||
</el-form-item>
|
||||
<el-form-item label="功耗" prop="powerDissipation">
|
||||
<el-input
|
||||
v-model="form.powerDissipation"
|
||||
placeholder="示例:0.01"
|
||||
inputmode="decimal"
|
||||
@input="handleNumeric('powerDissipation')"
|
||||
>
|
||||
<template slot="append">kw/h</template>
|
||||
</el-input>
|
||||
</el-form-item>
|
||||
<el-form-item label="机器成本价格" prop="cost">
|
||||
<el-input
|
||||
v-model="form.cost"
|
||||
placeholder="请输入成本(USDT)"
|
||||
inputmode="decimal"
|
||||
@input="handleNumeric('cost')"
|
||||
>
|
||||
<template slot="append">USDT</template>
|
||||
</el-input>
|
||||
<el-input v-model="form.productName" disabled style="width: 50%;" />
|
||||
</el-form-item>
|
||||
|
||||
|
||||
|
||||
<el-form-item label="矿机型号">
|
||||
<el-input v-model="form.type" placeholder="示例:龍珠" :maxlength="20" @input="handleTypeInput" />
|
||||
<el-input style="width: 50%;" v-model="form.type" placeholder="示例:龍珠" :maxlength="20" @input="handleTypeInput" />
|
||||
</el-form-item>
|
||||
<el-form-item label="理论算力" prop="theoryPower">
|
||||
<el-input
|
||||
@@ -41,16 +31,65 @@
|
||||
placeholder="请输入单机理论算力"
|
||||
inputmode="decimal"
|
||||
@input="handleNumeric('theoryPower')"
|
||||
style="width: 50%;"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item label="算力单位" prop="unit">
|
||||
<el-select v-model="form.unit" placeholder="请选择算力单位">
|
||||
<el-option label="KH/S" value="KH/S" />
|
||||
<el-option label="MH/S" value="MH/S" />
|
||||
<el-option label="GH/S" value="GH/S" />
|
||||
<el-option label="TH/S" value="TH/S" />
|
||||
<el-option label="PH/S" value="PH/S" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="最大租赁天数" prop="maxLeaseDays">
|
||||
<el-input
|
||||
v-model="form.maxLeaseDays"
|
||||
placeholder="1-365"
|
||||
inputmode="numeric"
|
||||
@input="handleNumeric('maxLeaseDays')"
|
||||
style="width: 50%;"
|
||||
>
|
||||
<template slot="append">天</template>
|
||||
</el-input>
|
||||
</el-form-item>
|
||||
<el-form-item label="功耗" prop="powerDissipation">
|
||||
<el-input
|
||||
v-model="form.powerDissipation"
|
||||
placeholder="示例:0.01"
|
||||
inputmode="decimal"
|
||||
@input="handleNumeric('powerDissipation')"
|
||||
style="width: 50%;"
|
||||
>
|
||||
<template slot="append">kw/h</template>
|
||||
</el-input>
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item label="统一售价" prop="cost">
|
||||
<span slot="label">
|
||||
统一售价
|
||||
<el-tooltip effect="dark" placement="top">
|
||||
<div slot="content">
|
||||
卖家最终收款金额 = 机器售价 × 波动率<br/>
|
||||
波动率规则:<br/>
|
||||
1)0% - 5%(包含5%):波动率 = 1(按售价结算)<br/>
|
||||
2)5%以上:波动率 = 实际算力 / 理论算力,且不会超过 1,即最终结算时不会超过机器售价
|
||||
</div>
|
||||
<i class="el-icon-question label-help" aria-label="帮助" tabindex="0"></i>
|
||||
</el-tooltip>
|
||||
</span>
|
||||
<el-input
|
||||
v-model="form.cost"
|
||||
placeholder="请输入成本(USDT)"
|
||||
inputmode="decimal"
|
||||
@input="handleNumeric('cost')"
|
||||
style="width: 50%;"
|
||||
>
|
||||
<template slot="append">USDT</template>
|
||||
</el-input>
|
||||
</el-form-item>
|
||||
|
||||
|
||||
<el-form-item label="选择挖矿账户">
|
||||
<el-select v-model="selectedMiner" filterable clearable placeholder="请选择挖矿账户" @change="handleMinerChange" :loading="minersLoading">
|
||||
@@ -69,17 +108,77 @@
|
||||
<el-card shadow="never" class="form-card" v-if="selectedMachineRows.length">
|
||||
<div slot="header" class="section-title">已选择机器</div>
|
||||
<el-table :data="selectedMachineRows" border stripe style="width: 100%">
|
||||
<el-table-column prop="user" label="挖矿账户" min-width="160" />
|
||||
<el-table-column prop="miner" label="机器编号" min-width="160" />
|
||||
<el-table-column label="价格(USDT)" min-width="220">
|
||||
<el-table-column prop="user" label="挖矿账户" />
|
||||
<el-table-column prop="miner" label="机器编号" />
|
||||
<el-table-column prop="realPower" label="实际算力(MH/S)">
|
||||
<template slot="header">
|
||||
<el-tooltip content="实际算力为该机器在本矿池过去24H的平均算力" effect="dark" placement="top">
|
||||
<i class="el-icon-question" style="margin-right: 4px; color: #909399;" aria-label="帮助" tabindex="0"></i>
|
||||
</el-tooltip>
|
||||
<span>实际算力(MH/S)</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="功耗(kw/h)" min-width="120">
|
||||
<template #default="scope">
|
||||
<el-input
|
||||
v-model="scope.row.powerDissipation"
|
||||
placeholder="示例:0.01"
|
||||
inputmode="decimal"
|
||||
@input="handleRowPowerDissipationInput(scope.$index)"
|
||||
@blur="handleRowPowerDissipationBlur(scope.$index)"
|
||||
style="width: 100%;"
|
||||
>
|
||||
<template slot="append">kw/h</template>
|
||||
</el-input>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="理论算力" min-width="160">
|
||||
<template #default="scope">
|
||||
<div style="display: flex; align-items: center; gap: 8px;">
|
||||
<el-input
|
||||
v-model="scope.row.theoryPower"
|
||||
placeholder="理论算力"
|
||||
inputmode="decimal"
|
||||
@input="handleRowTheoryPowerInput(scope.$index)"
|
||||
@blur="handleRowTheoryPowerBlur(scope.$index)"
|
||||
style="width: 100%"
|
||||
/>
|
||||
<el-select
|
||||
v-model="scope.row.unit"
|
||||
placeholder="单位"
|
||||
style="width:150px;"
|
||||
@change="val => handleRowUnitChange(scope.$index, val)"
|
||||
>
|
||||
<el-option label="KH/S" value="KH/S" />
|
||||
<el-option label="MH/S" value="MH/S" />
|
||||
<el-option label="GH/S" value="GH/S" />
|
||||
<el-option label="TH/S" value="TH/S" />
|
||||
<el-option label="PH/S" value="PH/S" />
|
||||
</el-select>
|
||||
</div>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="售价(USDT)" min-width="160">
|
||||
<template slot="header">
|
||||
<el-tooltip effect="dark" placement="top">
|
||||
<div slot="content">
|
||||
卖家最终收款金额 = 机器售价 × 波动率<br/>
|
||||
波动率规则:<br/>
|
||||
1)0% - 5%(包含5%):波动率 = 1(按售价结算)<br/>
|
||||
2)5%以上:波动率 = 实际算力 / 理论算力,且不会超过 1,即最终结算时不会超过机器售价
|
||||
</div>
|
||||
<i class="el-icon-question label-help" aria-label="帮助" tabindex="0"></i>
|
||||
</el-tooltip>
|
||||
<span>售价(USDT)</span>
|
||||
</template>
|
||||
<template slot-scope="scope">
|
||||
<el-input
|
||||
v-model="scope.row.price"
|
||||
placeholder="价格"
|
||||
inputmode="decimal"
|
||||
@input="handleRowPriceInput(scope.$index)"
|
||||
@blur="handleRowPriceBlur(scope.$index)"
|
||||
style="width: 70%;"
|
||||
style="width: 100%;"
|
||||
>
|
||||
<template slot="append">USDT</template>
|
||||
</el-input>
|
||||
@@ -87,7 +186,21 @@
|
||||
</template>
|
||||
|
||||
</el-table-column>
|
||||
<el-table-column label="矿机型号" min-width="200">
|
||||
<el-table-column label="最大租赁天数(天)" min-width="120">
|
||||
<template #default="scope">
|
||||
<el-input
|
||||
v-model="scope.row.maxLeaseDays"
|
||||
placeholder="1-365"
|
||||
inputmode="numeric"
|
||||
@input="handleRowMaxLeaseDaysInput(scope.$index)"
|
||||
@blur="handleRowMaxLeaseDaysBlur(scope.$index)"
|
||||
style="width: 100%;"
|
||||
>
|
||||
<template slot="append">天</template>
|
||||
</el-input>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="矿机型号">
|
||||
<template #default="scope">
|
||||
<el-input
|
||||
v-model="scope.row.type"
|
||||
@@ -95,11 +208,11 @@
|
||||
@input="handleRowTypeInput(scope.$index)"
|
||||
@blur="handleRowTypeBlur(scope.$index)"
|
||||
:maxlength="20"
|
||||
style="width: 70%;"
|
||||
style="width: 100%;"
|
||||
/>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="上下架状态" min-width="120">
|
||||
<el-table-column label="上下架状态" width="100">
|
||||
<template #default="scope">
|
||||
<el-button
|
||||
:type="scope.row.state === 0 ? 'success' : 'info'"
|
||||
@@ -151,7 +264,8 @@ export default {
|
||||
theoryPower: null,
|
||||
type: '',
|
||||
unit: 'TH/S',
|
||||
cost: ''
|
||||
cost: '',
|
||||
maxLeaseDays: ''
|
||||
},
|
||||
confirmVisible: false,
|
||||
rules: {
|
||||
@@ -210,6 +324,21 @@ export default {
|
||||
trigger: 'blur'
|
||||
}
|
||||
]
|
||||
,
|
||||
maxLeaseDays: [
|
||||
{ required: true, message: '请填写最大租赁天数', trigger: 'blur' },
|
||||
{
|
||||
validator: (rule, value, callback) => {
|
||||
const raw = String(value ?? '')
|
||||
if (!raw) { callback(new Error('请填写最大租赁天数')); return }
|
||||
if (!/^\d{1,3}$/.test(raw)) { callback(new Error('仅允许整数,范围 1-365')); return }
|
||||
const n = Number(raw)
|
||||
if (!Number.isInteger(n) || n < 1 || n > 365) { callback(new Error('范围需在 1-365 天')); return }
|
||||
callback()
|
||||
},
|
||||
trigger: 'blur'
|
||||
}
|
||||
]
|
||||
},
|
||||
miners: [
|
||||
// {
|
||||
@@ -293,6 +422,10 @@ export default {
|
||||
saving: false,
|
||||
lastCostBaseline: 0,
|
||||
lastTypeBaseline: '',
|
||||
lastMaxLeaseDaysBaseline: 0,
|
||||
lastPowerDissipationBaseline: 0,
|
||||
lastTheoryPowerBaseline: 0,
|
||||
lastUnitBaseline: 'TH/S',
|
||||
params:{
|
||||
cost:353400,
|
||||
powerDissipation:0.01,
|
||||
@@ -364,6 +497,13 @@ export default {
|
||||
decPart = decPart.slice(0, 4)
|
||||
}
|
||||
v = decPart.length ? `${intPart}.${decPart}` : (endsWithDot ? `${intPart}.` : intPart)
|
||||
} else if (key === 'maxLeaseDays') {
|
||||
// 最大租赁天数:仅整数,范围 1-365,输入阶段限制为最多3位数字
|
||||
v = v.replace(/\D/g, '')
|
||||
if (v.length > 3) v = v.slice(0, 3)
|
||||
this.form[key] = v
|
||||
this.syncMaxLeaseDaysToRows()
|
||||
return
|
||||
} else {
|
||||
// 其他:最多6位小数(保持原有逻辑)
|
||||
if (firstDot !== -1) {
|
||||
@@ -427,14 +567,164 @@ export default {
|
||||
user: m.user,
|
||||
coin: m.coin,
|
||||
miner: m.miner,
|
||||
realPower: m.realPower,
|
||||
price: existed ? existed.price : this.form.cost,
|
||||
powerDissipation: existed && existed.powerDissipation !== undefined ? existed.powerDissipation : this.form.powerDissipation,
|
||||
theoryPower: existed && existed.theoryPower !== undefined ? existed.theoryPower : this.form.theoryPower,
|
||||
unit: existed && existed.unit ? existed.unit : this.form.unit,
|
||||
type: existed ? existed.type : this.form.type,
|
||||
state: existed ? existed.state : 0 // 默认上架
|
||||
state: existed ? existed.state : 0, // 默认上架
|
||||
maxLeaseDays: existed && existed.maxLeaseDays !== undefined ? existed.maxLeaseDays : this.form.maxLeaseDays
|
||||
})
|
||||
}
|
||||
})
|
||||
this.selectedMachineRows = nextRows
|
||||
},
|
||||
/**
|
||||
* 同步顶部功耗到行(行未自定义或无效则跟随)
|
||||
*/
|
||||
syncPowerDissipationToRows() {
|
||||
const newVal = Number(this.form.powerDissipation)
|
||||
if (!Number.isFinite(newVal)) return
|
||||
const oldBaseline = this.lastPowerDissipationBaseline
|
||||
this.selectedMachineRows = this.selectedMachineRows.map(row => {
|
||||
const rowNum = Number(row.powerDissipation)
|
||||
if (!Number.isFinite(rowNum) || rowNum === oldBaseline) {
|
||||
return { ...row, powerDissipation: newVal }
|
||||
}
|
||||
return row
|
||||
})
|
||||
this.lastPowerDissipationBaseline = newVal
|
||||
},
|
||||
/**
|
||||
* 同步顶部理论算力到行(行未自定义或无效则跟随)
|
||||
*/
|
||||
syncTheoryPowerToRows() {
|
||||
const newVal = Number(this.form.theoryPower)
|
||||
if (!Number.isFinite(newVal)) return
|
||||
const oldBaseline = this.lastTheoryPowerBaseline
|
||||
this.selectedMachineRows = this.selectedMachineRows.map(row => {
|
||||
const rowNum = Number(row.theoryPower)
|
||||
if (!Number.isFinite(rowNum) || rowNum === oldBaseline) {
|
||||
return { ...row, theoryPower: newVal }
|
||||
}
|
||||
return row
|
||||
})
|
||||
this.lastTheoryPowerBaseline = newVal
|
||||
},
|
||||
/**
|
||||
* 同步顶部单位到行(行未自定义或等于旧基线时跟随)
|
||||
*/
|
||||
syncUnitToRows() {
|
||||
const newUnit = this.form.unit
|
||||
if (!newUnit) return
|
||||
const oldBaseline = this.lastUnitBaseline
|
||||
this.selectedMachineRows = this.selectedMachineRows.map(row => {
|
||||
const rowUnit = row.unit
|
||||
if (!rowUnit || rowUnit === oldBaseline) {
|
||||
return { ...row, unit: newUnit }
|
||||
}
|
||||
return row
|
||||
})
|
||||
this.lastUnitBaseline = newUnit
|
||||
},
|
||||
/**
|
||||
* 行内功耗输入(限制:整数最多6位,小数最多4位)
|
||||
*/
|
||||
handleRowPowerDissipationInput(index) {
|
||||
let v = String(this.selectedMachineRows[index].powerDissipation ?? '')
|
||||
v = v.replace(/[^0-9.]/g, '')
|
||||
const firstDot = v.indexOf('.')
|
||||
if (firstDot !== -1) {
|
||||
v = v.slice(0, firstDot + 1) + v.slice(firstDot + 1).replace(/\./g, '')
|
||||
}
|
||||
const parts = v.split('.')
|
||||
let intPart = parts[0] || ''
|
||||
let decPart = parts[1] || ''
|
||||
if (intPart.length > 6) intPart = intPart.slice(0, 6)
|
||||
if (decPart) decPart = decPart.slice(0, 4)
|
||||
v = decPart.length ? `${intPart}.${decPart}` : intPart
|
||||
this.$set(this.selectedMachineRows[index], 'powerDissipation', v)
|
||||
},
|
||||
/**
|
||||
* 行内功耗校验
|
||||
*/
|
||||
handleRowPowerDissipationBlur(index) {
|
||||
const raw = String(this.selectedMachineRows[index].powerDissipation ?? '')
|
||||
const pattern = /^\d{1,6}(\.\d{1,4})?$/
|
||||
if (!raw || Number(raw) <= 0 || !pattern.test(raw)) {
|
||||
this.$message.warning('功耗需大于0,整数最多6位,小数最多4位')
|
||||
this.$set(this.selectedMachineRows[index], 'powerDissipation', '')
|
||||
}
|
||||
},
|
||||
/**
|
||||
* 行内理论算力输入(限制:整数最多6位,小数最多4位)
|
||||
*/
|
||||
handleRowTheoryPowerInput(index) {
|
||||
let v = String(this.selectedMachineRows[index].theoryPower ?? '')
|
||||
v = v.replace(/[^0-9.]/g, '')
|
||||
const firstDot = v.indexOf('.')
|
||||
if (firstDot !== -1) {
|
||||
v = v.slice(0, firstDot + 1) + v.slice(firstDot + 1).replace(/\./g, '')
|
||||
}
|
||||
const parts = v.split('.')
|
||||
let intPart = parts[0] || ''
|
||||
let decPart = parts[1] || ''
|
||||
if (intPart.length > 6) intPart = intPart.slice(0, 6)
|
||||
if (decPart) decPart = decPart.slice(0, 4)
|
||||
v = decPart.length ? `${intPart}.${decPart}` : intPart
|
||||
this.$set(this.selectedMachineRows[index], 'theoryPower', v)
|
||||
},
|
||||
/**
|
||||
* 行内理论算力校验
|
||||
*/
|
||||
handleRowTheoryPowerBlur(index) {
|
||||
const raw = String(this.selectedMachineRows[index].theoryPower ?? '')
|
||||
const pattern = /^\d{1,6}(\.\d{1,4})?$/
|
||||
if (!raw || Number(raw) <= 0 || !pattern.test(raw)) {
|
||||
this.$message.warning('理论算力需大于0,整数最多6位,小数最多4位')
|
||||
this.$set(this.selectedMachineRows[index], 'theoryPower', '')
|
||||
}
|
||||
},
|
||||
/**
|
||||
* 行内单位变更
|
||||
*/
|
||||
handleRowUnitChange(index, value) {
|
||||
this.$set(this.selectedMachineRows[index], 'unit', value)
|
||||
},
|
||||
syncMaxLeaseDaysToRows() {
|
||||
const raw = this.form.maxLeaseDays
|
||||
const n = Number(raw)
|
||||
if (!Number.isInteger(n)) return
|
||||
const oldBaseline = this.lastMaxLeaseDaysBaseline
|
||||
this.selectedMachineRows = this.selectedMachineRows.map(row => {
|
||||
const rowNum = Number(row.maxLeaseDays)
|
||||
if (!Number.isInteger(rowNum) || rowNum === oldBaseline) {
|
||||
return { ...row, maxLeaseDays: n }
|
||||
}
|
||||
return row
|
||||
})
|
||||
this.lastMaxLeaseDaysBaseline = n
|
||||
},
|
||||
handleRowMaxLeaseDaysInput(index) {
|
||||
let v = String(this.selectedMachineRows[index].maxLeaseDays ?? '')
|
||||
v = v.replace(/\D/g, '')
|
||||
if (v.length > 3) v = v.slice(0, 3)
|
||||
this.$set(this.selectedMachineRows[index], 'maxLeaseDays', v)
|
||||
},
|
||||
handleRowMaxLeaseDaysBlur(index) {
|
||||
const raw = String(this.selectedMachineRows[index].maxLeaseDays ?? '')
|
||||
if (!/^\d{1,3}$/.test(raw)) {
|
||||
this.$message.warning('最大租赁天数需为 1-365 的整数')
|
||||
this.$set(this.selectedMachineRows[index], 'maxLeaseDays', '')
|
||||
return
|
||||
}
|
||||
const n = Number(raw)
|
||||
if (!Number.isInteger(n) || n < 1 || n > 365) {
|
||||
this.$message.warning('最大租赁天数需为 1-365 的整数')
|
||||
this.$set(this.selectedMachineRows[index], 'maxLeaseDays', '')
|
||||
}
|
||||
},
|
||||
handleRowPriceInput(index) {
|
||||
// 价格输入:整数最多12位,小数最多2位;允许尾随小数点
|
||||
let v = String(this.selectedMachineRows[index].price ?? '')
|
||||
@@ -537,7 +827,7 @@ export default {
|
||||
console.log('机器列表数据:', this.machineOptions)
|
||||
} catch (e) {
|
||||
console.error('获取机器列表失败', e)
|
||||
this.$message.error('获取机器列表失败,请重试')
|
||||
|
||||
} finally {
|
||||
this.machinesLoading = false
|
||||
}
|
||||
@@ -584,6 +874,14 @@ export default {
|
||||
this.$message.warning(`第${i + 1}行(机器:${label}) 价格必须大于0`)
|
||||
return
|
||||
}
|
||||
// 校验:逐行最大租赁天数 1-365
|
||||
const rawDays = String((row && row.maxLeaseDays) ?? '')
|
||||
const n = Number(rawDays)
|
||||
if (!/^\d{1,3}$/.test(rawDays) || !Number.isInteger(n) || n < 1 || n > 365) {
|
||||
const label = (row && (row.miner || row.user)) || i + 1
|
||||
this.$message.warning(`第${i + 1}行(机器:${label}) 最大租赁天数需为 1-365 的整数`)
|
||||
return
|
||||
}
|
||||
}
|
||||
// 通过所有预校验后,弹出确认框
|
||||
this.confirmVisible = true
|
||||
@@ -600,12 +898,17 @@ export default {
|
||||
type: this.form.type,
|
||||
unit: this.form.unit,
|
||||
cost: this.form.cost,
|
||||
maxLeaseDays: this.form.maxLeaseDays,
|
||||
productMachineURDVos: this.selectedMachineRows.map(r => ({
|
||||
miner: r.miner,
|
||||
price: Number(r.price) || 0,
|
||||
state: r.state || 0,
|
||||
type: r.type || this.form.type,
|
||||
user: r.user
|
||||
user: r.user,
|
||||
maxLeaseDays: Number(r.maxLeaseDays) || Number(this.form.maxLeaseDays) || 0,
|
||||
powerDissipation: Number(r.powerDissipation) || Number(this.form.powerDissipation) || 0,
|
||||
theoryPower: Number(r.theoryPower) || Number(this.form.theoryPower) || 0,
|
||||
unit: r.unit || this.form.unit
|
||||
}))
|
||||
}
|
||||
|
||||
@@ -616,9 +919,7 @@ export default {
|
||||
this.$message.success('添加成功')
|
||||
this.confirmVisible = false
|
||||
this.$router.back()
|
||||
} else {
|
||||
this.$message.error(res?.msg || '添加失败')
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
console.error('添加出售机器失败', e)
|
||||
console.log('添加失败')
|
||||
@@ -631,6 +932,10 @@ export default {
|
||||
watch: {
|
||||
'form.cost': function() { this.syncCostToRows() },
|
||||
'form.type': function() { this.updateMachineType() },
|
||||
'form.maxLeaseDays': function() { this.syncMaxLeaseDaysToRows() },
|
||||
'form.powerDissipation': function() { this.syncPowerDissipationToRows() },
|
||||
'form.theoryPower': function() { this.syncTheoryPowerToRows() },
|
||||
'form.unit': function() { this.syncUnitToRows() },
|
||||
selectedMachines() {
|
||||
this.updateSelectedMachineRows()
|
||||
}
|
||||
@@ -642,6 +947,11 @@ export default {
|
||||
.product-machine-add { padding: 8px; }
|
||||
.header { display: flex; align-items: center; gap: 12px; margin-bottom: 8px; }
|
||||
.title { margin: 0; font-size: 18px; font-weight: 600; }
|
||||
.notice-alert { margin-bottom: 12px; }
|
||||
.notice-alert :deep(.el-alert__content) { text-align: left; }
|
||||
.notice-alert :deep(.el-alert__title),
|
||||
.notice-alert :deep(.el-alert__description) { text-align: left; }
|
||||
.label-help { margin-left: 4px; color: #909399; cursor: help; }
|
||||
.form-card { margin-bottom: 12px; }
|
||||
.actions { text-align: right; }
|
||||
|
||||
@@ -649,11 +959,11 @@ export default {
|
||||
.product-machine-add :deep(.el-form-item__content) {
|
||||
justify-content: flex-start;
|
||||
}
|
||||
.product-machine-add :deep(.el-input),
|
||||
/* .product-machine-add :deep(.el-input),
|
||||
.product-machine-add :deep(.el-select),
|
||||
.product-machine-add :deep(.el-textarea) {
|
||||
width: 50%;
|
||||
}
|
||||
} */
|
||||
.product-machine-add :deep(.el-input-group__append) {
|
||||
background: #f5f7fa;
|
||||
color: #606266;
|
||||
|
||||
@@ -27,7 +27,7 @@
|
||||
<el-form-item label="商品类型" prop="type" class="align-like-input">
|
||||
<el-radio-group v-model="form.type">
|
||||
<el-radio :label="0">矿机</el-radio>
|
||||
<el-radio :label="1">算力</el-radio>
|
||||
<!-- <el-radio :label="1">算力</el-radio> -->
|
||||
</el-radio-group>
|
||||
</el-form-item>
|
||||
|
||||
@@ -274,7 +274,7 @@ export default {
|
||||
type: 'success',
|
||||
showClose: true
|
||||
})
|
||||
this.$router.push('/account/shops')
|
||||
this.$router.push('/account/products')
|
||||
}else {
|
||||
this.$message({
|
||||
message: res && res.msg ? res.msg : '创建失败',
|
||||
|
||||
@@ -29,8 +29,8 @@
|
||||
stripe
|
||||
style="width: 100%"
|
||||
>
|
||||
<el-table-column prop="id" label="ID" width="80" />
|
||||
<el-table-column prop="name" label="名称" min-width="160" />
|
||||
<!-- <el-table-column prop="id" label="ID" width="80" /> -->
|
||||
<el-table-column prop="name" label="名称" min-width="100" />
|
||||
<el-table-column prop="coin" label="币种" width="100" />
|
||||
<el-table-column prop="priceRange" label="价格范围" width="150" />
|
||||
<!-- <el-table-column label="算力" min-width="140">
|
||||
@@ -48,6 +48,9 @@
|
||||
</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
|
||||
<el-table-column prop="saleNumber" label="已售数量" min-width="60" />
|
||||
<el-table-column prop="totalMachineNumber" label="该商品总机器数量" min-width="60" />
|
||||
<el-table-column prop="state" label="状态" width="100">
|
||||
<template #default="scope">
|
||||
<el-tag :type="scope.row.state === 1 ? 'info' : 'success'">
|
||||
|
||||
@@ -56,7 +56,7 @@
|
||||
<el-card class="section" style="margin-top: 12px;">
|
||||
<div class="sub-title">收益信息</div>
|
||||
<div class="row">
|
||||
<span class="label">当前实时算力:</span>
|
||||
<span class="label">当前实际算力:</span>
|
||||
<span class="value strong">{{ detail.currentComputingPower || '0' }}</span>
|
||||
</div>
|
||||
<div class="row">
|
||||
|
||||
326
power_leasing/src/views/account/receiptRecord.vue
Normal file
326
power_leasing/src/views/account/receiptRecord.vue
Normal file
@@ -0,0 +1,326 @@
|
||||
<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>
|
||||
|
||||
|
||||
@@ -306,36 +306,36 @@ export default {
|
||||
|
||||
// 充值记录数据
|
||||
rechargeRecords: [
|
||||
// {
|
||||
// address: "TQn9Y2khEsLJW1ChVWFMSMeRDow5KcbLSE",
|
||||
// amount: 100,
|
||||
// fromSymbol: "USDT",
|
||||
// fromChain: "tron",
|
||||
// status: 2,
|
||||
// createTime: "2024-01-15 14:30:25",
|
||||
// id: 1,
|
||||
// txHash: "TQn9Y2khEsLJW1ChVWFMSMeRDow5KcbLSE",
|
||||
// },
|
||||
// {
|
||||
// address: "TQn9Y2khEsLJW1ChVWFMSMeRDow5KcbLSE",
|
||||
// amount: 100,
|
||||
// fromSymbol: "USDT",
|
||||
// fromChain: "tron",
|
||||
// status: 2,
|
||||
// createTime: "2024-01-15 14:30:25",
|
||||
// id: 2,
|
||||
// txHash: "TQn9Y2khEsLJW1ChVWFMSMeRDow5KcbLSE",
|
||||
// },
|
||||
// {
|
||||
// address: "TQn9Y2khEsLJW1ChVWFMSMeRDow5KcbLSE",
|
||||
// amount: 100,
|
||||
// fromSymbol: "USDT",
|
||||
// fromChain: "tron",
|
||||
// status: 2,
|
||||
// createTime: "2024-01-15 14:30:25",
|
||||
// id: 3,
|
||||
// txHash: "TQn9Y2khEsLJW1ChVWFMSMeRDow5KcbLSE",
|
||||
// },
|
||||
{
|
||||
address: "TQn9Y2khEsLJW1ChVWFMSMeRDow5KcbLSE",
|
||||
amount: 100,
|
||||
fromSymbol: "USDT",
|
||||
fromChain: "tron",
|
||||
status: 2,
|
||||
createTime: "2024-01-15 14:30:25",
|
||||
id: 1,
|
||||
txHash: "TQn9Y2khEsLJW1ChVWFMSMeRDow5KcbLSE",
|
||||
},
|
||||
{
|
||||
address: "TQn9Y2khEsLJW1ChVWFMSMeRDow5KcbLSE",
|
||||
amount: 100,
|
||||
fromSymbol: "USDT",
|
||||
fromChain: "tron",
|
||||
status: 2,
|
||||
createTime: "2024-01-15 14:30:25",
|
||||
id: 2,
|
||||
txHash: "TQn9Y2khEsLJW1ChVWFMSMeRDow5KcbLSE",
|
||||
},
|
||||
{
|
||||
address: "TQn9Y2khEsLJW1ChVWFMSMeRDow5KcbLSE",
|
||||
amount: 100,
|
||||
fromSymbol: "USDT",
|
||||
fromChain: "tron",
|
||||
status: 2,
|
||||
createTime: "2024-01-15 14:30:25",
|
||||
id: 3,
|
||||
txHash: "TQn9Y2khEsLJW1ChVWFMSMeRDow5KcbLSE",
|
||||
},
|
||||
// {
|
||||
// address: "TQn9Y2khEsLJW1ChVWFMSMeRDow5KcbLSE",
|
||||
// amount: 100,
|
||||
|
||||
@@ -1,45 +1,34 @@
|
||||
<template>
|
||||
<div class="panel">
|
||||
<h2 class="panel-title page-title">钱包绑定</h2>
|
||||
<div class="panel-body">
|
||||
<div class="panel-body" v-loading="loading">
|
||||
<el-form :model="form" label-width="120px" class="config-form">
|
||||
<el-form-item label="适用商品">
|
||||
<el-select v-model="form.productId" placeholder="请选择商品">
|
||||
<el-option :value="0" label="全部商品" />
|
||||
<el-option
|
||||
v-for="p in productOptions"
|
||||
:key="p.id"
|
||||
:value="p.id"
|
||||
:label="`${p.id} - ${p.name}`"
|
||||
/>
|
||||
</el-select>
|
||||
<el-form-item label="选择链">
|
||||
<el-cascader style="width: 420px;" @change="handleChange" v-model="value" :options="options"> </el-cascader>
|
||||
|
||||
</el-form-item>
|
||||
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item label="收款钱包地址">
|
||||
<el-input
|
||||
v-model="form.payAddress"
|
||||
placeholder="示例:nexa:nqtsq5g50jkkmklvjyaflg46c4nwuy46z9gzswqe3l0csc7g"
|
||||
/>
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item label="币种类型">
|
||||
<el-radio-group v-model="form.payType" class="radio-group">
|
||||
<el-radio :label="0">虚拟币</el-radio>
|
||||
<!-- <el-radio :label="0">虚拟币</el-radio> -->
|
||||
<el-radio :label="1">稳定币</el-radio>
|
||||
</el-radio-group>
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item label="支付币种">
|
||||
<el-select v-model="form.payCoin" placeholder="请选择支付币种" filterable clearable>
|
||||
<el-option v-for="c in coinOptions" :key="c" :label="c" :value="c" />
|
||||
</el-select>
|
||||
|
||||
|
||||
<el-form-item label="收款钱包地址">
|
||||
|
||||
<el-input
|
||||
v-model="form.payAddress"
|
||||
placeholder="请输入"
|
||||
/>
|
||||
</el-form-item>
|
||||
|
||||
|
||||
|
||||
<el-form-item>
|
||||
<el-button type="primary" @click="handleSave">保存配置</el-button>
|
||||
<el-button @click="handleReset">重置</el-button>
|
||||
<el-button style="width: 200px;" type="primary" @click="handleSave">确认绑定</el-button>
|
||||
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</div>
|
||||
@@ -47,113 +36,222 @@
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { addShopConfig ,getMyShop} from '@/api/shops'
|
||||
import { getMyShop } from "@/api/shops";
|
||||
import { getChainAndList, addWalletShopConfig } from "../../api/wallet";
|
||||
|
||||
// 币种集合
|
||||
const VIRTUAL_COINS = ['nexa', 'rxd', 'dgbo', 'dgbq', 'dgbs', 'alph', 'enx', 'grs', 'mona']
|
||||
const STABLE_COINS = ['usdt', 'usdc', 'busd']
|
||||
const VIRTUAL_COINS = [
|
||||
"nexa",
|
||||
"rxd",
|
||||
"dgbo",
|
||||
"dgbq",
|
||||
"dgbs",
|
||||
"alph",
|
||||
"enx",
|
||||
"grs",
|
||||
"mona",
|
||||
];
|
||||
const STABLE_COINS = ["usdt", "usdc", "busd"];
|
||||
|
||||
export default {
|
||||
name: 'AccountShopConfig',
|
||||
name: "AccountShopConfig",
|
||||
data() {
|
||||
return {
|
||||
VIRTUAL_COINS,
|
||||
STABLE_COINS,
|
||||
productOptions: [],
|
||||
form: {
|
||||
payAddress: 'nexa:nqtsq5g50jkkmklvjyaflg46c4nwuy46z9gzswqe3l0csc7g',
|
||||
payCoin: '',
|
||||
payType: 0, // 0 虚拟币 1 稳定币
|
||||
productId: 0, // 0 代表所有商品
|
||||
shopId: 0
|
||||
chain: "",
|
||||
payAddress: "",
|
||||
payCoin: "",
|
||||
payType: 1, // 0 虚拟币 1 稳定币
|
||||
|
||||
},
|
||||
shop: {
|
||||
id: 0,
|
||||
name: '',
|
||||
image: '',
|
||||
description: '',
|
||||
name: "",
|
||||
image: "",
|
||||
description: "",
|
||||
del: true,
|
||||
state: 0
|
||||
state: 0,
|
||||
},
|
||||
}
|
||||
value: "",
|
||||
options: [
|
||||
// {
|
||||
// value: "Tron (TRC20)",
|
||||
// label: "Tron (TRC20)",
|
||||
// children: [
|
||||
// {
|
||||
// value: "USDT",
|
||||
// label: "USDT (TRC20)",
|
||||
// },
|
||||
// {
|
||||
// value: "币种2",
|
||||
// label: "币种2",
|
||||
// },
|
||||
// ],
|
||||
// },
|
||||
// {
|
||||
// value: "ETH",
|
||||
// label: "ETH",
|
||||
// children: [
|
||||
// {
|
||||
// value: "USDT",
|
||||
// label: "USDT (TRC20)",
|
||||
// },
|
||||
// ],
|
||||
// },
|
||||
],
|
||||
loading: false,
|
||||
};
|
||||
},
|
||||
mounted() {
|
||||
this.fetchMyShop()
|
||||
|
||||
this.getChainAndList();
|
||||
},
|
||||
methods: {
|
||||
async fetchMyShop() {
|
||||
try {
|
||||
const res = await getMyShop()
|
||||
// 预期格式:{"code":0,"data":{"del":true,"description":"","id":0,"image":"","name":"","state":0},"msg":""}
|
||||
if (res && (res.code === 0 || res.code === 200) && res.data) {
|
||||
this.shop = {
|
||||
id: res.data.id,
|
||||
name: res.data.name,
|
||||
image: res.data.image,
|
||||
description: res.data.description,
|
||||
del: !!res.data.del,
|
||||
state: Number(res.data.state || 0)
|
||||
}
|
||||
this.form.shopId = this.shop.id
|
||||
} else {
|
||||
this.$message.warning(res.msg || '未获取到店铺数据')
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('获取店铺信息失败:', error)
|
||||
|
||||
} finally {
|
||||
this.loaded = true
|
||||
/**
|
||||
* 根据选择的链校验钱包地址格式(参考钱包页面规则)
|
||||
* - tron: 以 T 开头的 34 位字符
|
||||
* - ethereum/bsc/polygon 等 EVM: 0x 开头 + 40 位十六进制
|
||||
* - 其他:长度 > 10 的宽松校验
|
||||
*/
|
||||
validateAddressByChain(chain, address) {
|
||||
const c = String(chain || '').toLowerCase()
|
||||
const addr = String(address || '').trim()
|
||||
if (!addr) return { ok: false, message: '请输入收款地址' }
|
||||
|
||||
if (c.includes('tron') || c === 'tron') {
|
||||
const ok = /^T[A-Za-z1-9]{33}$/.test(addr)
|
||||
return ok ? { ok: true } : { ok: false, message: '请输入正确的收款地址格式(TRON)' }
|
||||
}
|
||||
|
||||
if (
|
||||
c.includes('ethereum') || c === 'ethereum' ||
|
||||
c.includes('eth') ||
|
||||
c.includes('bsc') || c === 'bsc' ||
|
||||
c.includes('polygon') || c === 'polygon' ||
|
||||
c.includes('erc') || c.includes('bep')
|
||||
) {
|
||||
const ok = /^0x[a-fA-F0-9]{40}$/.test(addr)
|
||||
return ok ? { ok: true } : { ok: false, message: '请输入正确的收款地址格式(EVM)' }
|
||||
}
|
||||
|
||||
if (addr.length <= 10) {
|
||||
return { ok: false, message: '请输入正确的收款地址格式' }
|
||||
}
|
||||
return { ok: true }
|
||||
},
|
||||
async getChainAndList() {
|
||||
this.loading = true;
|
||||
const res = await getChainAndList();
|
||||
if (res && (res.code === 0 || res.code === 200) && res.data) {
|
||||
this.options = this.toUpperOptions(res.data);
|
||||
}
|
||||
|
||||
this.loading = false;
|
||||
},
|
||||
/**
|
||||
* 将级联选项的 label 文本统一转为大写(不修改 value)
|
||||
* @param {Array} list
|
||||
* @returns {Array}
|
||||
*/
|
||||
toUpperOptions(list) {
|
||||
const arr = Array.isArray(list) ? list : []
|
||||
return arr.map(item => {
|
||||
const next = { ...item }
|
||||
const src = (item && (item.label != null ? item.label : item.value)) || ''
|
||||
next.label = String(src).toUpperCase()
|
||||
if (Array.isArray(item && item.children)) {
|
||||
next.children = this.toUpperOptions(item.children)
|
||||
}
|
||||
return next
|
||||
})
|
||||
},
|
||||
async FetchAddWalletShopConfig(params) {
|
||||
this.loading = true;
|
||||
const res = await addWalletShopConfig(params);
|
||||
if (res && (res.code === 0 || res.code === 200)) {
|
||||
this.$message.success("绑定成功");
|
||||
this.$router.push("/account/shops");
|
||||
}
|
||||
this.loading = false;
|
||||
},
|
||||
|
||||
async addShopConfig(params) {
|
||||
const res = await addShopConfig(params)
|
||||
if (res && (res.code === 0 || res.code === 200)) {
|
||||
this.$message.success('已保存配置(示例)')
|
||||
} else {
|
||||
this.$message.error(res.msg || '保存失败')
|
||||
}
|
||||
handleChange(value) {
|
||||
console.log(value);
|
||||
|
||||
this.form.payCoin = value[1];
|
||||
this.form.chain = value[0];
|
||||
|
||||
},
|
||||
|
||||
handleSave() {
|
||||
this.form.shopId = this.shop.id
|
||||
if (!this.form.shopId) {
|
||||
this.$message.warning(`未查询到店铺信息`)
|
||||
return
|
||||
}
|
||||
this.addShopConfig(this.form)
|
||||
|
||||
this.form.chain =this.value[0]
|
||||
this.form.payCoin = this.value[1]
|
||||
|
||||
if (!this.form.chain) {
|
||||
this.$message.warning("请选择链");
|
||||
return;
|
||||
}
|
||||
if (!this.form.payCoin) {
|
||||
this.$message.warning("请选择币种");
|
||||
return;
|
||||
}
|
||||
if (!this.form.payAddress) {
|
||||
this.$message.warning("请输入钱包地址");
|
||||
return;
|
||||
}
|
||||
|
||||
// 链路格式校验
|
||||
const { ok, message } = this.validateAddressByChain(this.form.chain, this.form.payAddress)
|
||||
if (!ok) {
|
||||
this.$message.warning(message || '钱包地址格式不正确')
|
||||
return
|
||||
}
|
||||
|
||||
this.FetchAddWalletShopConfig(this.form);
|
||||
},
|
||||
handleReset() {
|
||||
this.form = { payAddress: '', payCoin: '', payType: 0, productId: 0 }
|
||||
}
|
||||
this.form = { chain: "", payAddress: "", payCoin: "", payType: 0 };
|
||||
},
|
||||
},
|
||||
computed: {
|
||||
// 根据币种类型动态展示可选币种
|
||||
coinOptions() {
|
||||
return this.form.payType === 1 ? STABLE_COINS : VIRTUAL_COINS
|
||||
}
|
||||
return this.form.payType === 1 ? STABLE_COINS : VIRTUAL_COINS;
|
||||
},
|
||||
},
|
||||
watch: {
|
||||
'form.payType'(val) {
|
||||
"form.payType"(val) {
|
||||
// 切换类型时清空已选币种,避免类型与币种不匹配
|
||||
this.form.payCoin = ''
|
||||
}
|
||||
}
|
||||
}
|
||||
this.form.payCoin = "";
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.page-title { text-align: left; margin-bottom: 16px; font-size: 20px; padding-left: 4px; }
|
||||
.page-title {
|
||||
text-align: left;
|
||||
margin-bottom: 16px;
|
||||
font-size: 20px;
|
||||
padding-left: 4px;
|
||||
}
|
||||
.config-form {
|
||||
max-width: 720px;
|
||||
margin: 0;
|
||||
background: #fff;
|
||||
padding: 8px 12px;
|
||||
}
|
||||
.config-form .el-form-item { margin-bottom: 18px; }
|
||||
.config-form .el-form-item {
|
||||
margin-bottom: 18px;
|
||||
}
|
||||
.config-form .el-select,
|
||||
.config-form .el-input { width: 420px; }
|
||||
.config-form .el-input {
|
||||
width: 420px;
|
||||
}
|
||||
.radio-group {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
@@ -163,6 +261,10 @@ export default {
|
||||
padding-left: 12px; /* 留出与输入框一致的内边距感 */
|
||||
box-sizing: border-box;
|
||||
}
|
||||
.tip { color: #999; font-size: 12px; margin-top: 6px; }
|
||||
.tip {
|
||||
color: #999;
|
||||
font-size: 12px;
|
||||
margin-top: 6px;
|
||||
}
|
||||
</style>
|
||||
|
||||
|
||||
@@ -1,44 +1,46 @@
|
||||
<template>
|
||||
<div class="wallet-container">
|
||||
<!-- 钱包余额卡片 -->
|
||||
<div class="wallet-card">
|
||||
|
||||
<div class="wallet-toolbar" role="region" aria-label="钱包操作">
|
||||
<el-button
|
||||
type="primary"
|
||||
class="create-wallet-btn"
|
||||
@click="openCreateWallet"
|
||||
>
|
||||
<i class="el-icon-plus" style="margin-right:6px;"></i>充值
|
||||
</el-button>
|
||||
</div>
|
||||
<!-- 多个钱包余额卡片 -->
|
||||
<section class="wallet-card-section">
|
||||
<div class="wallet-card" v-for="w in walletList" :key="w.id">
|
||||
<div class="wallet-header">
|
||||
<h2 class="wallet-title"> <i class="el-icon-wallet"></i> 我的钱包</h2>
|
||||
<h2 class="wallet-title">
|
||||
<i class="el-icon-wallet"></i> 我的钱包
|
||||
<el-tag size="mini" effect="dark" style="margin-left:8px;">
|
||||
{{ (w.fromChain || w.chain || '').toUpperCase() }} {{ (w.fromSymbol || w.coin || '').toUpperCase() }}
|
||||
</el-tag>
|
||||
</h2>
|
||||
<div class="wallet-balance">
|
||||
<div class="balance-item">
|
||||
<span class="balance-label">可用余额</span>
|
||||
<span class="balance-amount">{{ walletBalance }} USDT</span>
|
||||
<span class="balance-amount">{{ (w.walletBalance || w.balance || 0) }} {{ displaySymbol(w) }}</span>
|
||||
</div>
|
||||
<div class="balance-item">
|
||||
<span class="balance-label">冻结余额</span>
|
||||
<span class="balance-amount frozen">{{ blockedBalance }} USDT</span>
|
||||
<span class="balance-amount frozen">{{ (w.blockedBalance || 0) }} {{ displaySymbol(w) }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 操作按钮区域 -->
|
||||
<div class="wallet-actions">
|
||||
<el-button
|
||||
type="primary"
|
||||
size="large"
|
||||
class="action-btn recharge-btn"
|
||||
@click="handleRecharge"
|
||||
>
|
||||
<!-- <i class="el-icon-plus"></i> -->
|
||||
充值
|
||||
</el-button>
|
||||
<el-button
|
||||
type="success"
|
||||
size="large"
|
||||
class="action-btn withdraw-btn"
|
||||
@click="handleWithdraw"
|
||||
size="mini"
|
||||
class="withdraw-inline-btn"
|
||||
@click="handleWithdraw(w)"
|
||||
>
|
||||
<!-- <i class="el-icon-minus"></i> -->
|
||||
提现
|
||||
</el-button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</section>
|
||||
<!-- 交易记录区域 -->
|
||||
<div class="transaction-section">
|
||||
<h3 class="section-title">最近交易</h3>
|
||||
@@ -73,6 +75,18 @@
|
||||
<!-- 钱包地址区域 -->
|
||||
<div class="wallet-address-section">
|
||||
<h4 class="section-title">钱包地址</h4>
|
||||
<div class="charge-meta">
|
||||
<el-tag size="small" effect="dark" type="warning" class="meta-tag">
|
||||
<i class="el-icon-link"></i>
|
||||
<span class="meta-title">充值链:</span>
|
||||
<span class="meta-val">{{ (WalletData.fromChain || WalletData.chain || '').toString().toUpperCase() }}</span>
|
||||
</el-tag>
|
||||
<el-tag size="small" effect="dark" type="warning" class="meta-tag">
|
||||
<i class="el-icon-coin"></i>
|
||||
<span class="meta-title">充值币种:</span>
|
||||
<span class="meta-val">{{ (WalletData.fromSymbol || WalletData.coin || '').toString().toUpperCase() }}</span>
|
||||
</el-tag>
|
||||
</div>
|
||||
<div class="address-container">
|
||||
<el-input
|
||||
v-model="WalletData.fromAddress"
|
||||
@@ -89,7 +103,7 @@
|
||||
复制
|
||||
</el-button>
|
||||
</div>
|
||||
<p class="address-tip">请向此地址转账USDT,到账后余额将自动更新</p>
|
||||
<p class="address-tip">请向此地址转账非{{ displaySymbol(WalletData) }}资产,否则资产将无法找回.</p>
|
||||
</div>
|
||||
|
||||
<!-- 二维码区域 -->
|
||||
@@ -99,7 +113,7 @@
|
||||
<div class="qr-code" ref="qrCodeRef">
|
||||
<!-- 二维码将在这里生成 -->
|
||||
</div>
|
||||
<p class="qr-tip">使用支持USDT的钱包扫描二维码</p>
|
||||
<p class="qr-tip">使用支持{{ displaySymbol(WalletData) }}的钱包扫描二维码</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -107,8 +121,8 @@
|
||||
<div class="recharge-notice">
|
||||
<h4 class="section-title">充值说明</h4>
|
||||
<ul class="notice-list">
|
||||
<li>暂时仅支持USDT (TRC20) 网络转账</li>
|
||||
<li>最小充值金额:10 USDT</li>
|
||||
<li>充值后请耐心等待余额更新或在资金流水页面查看最新充值记录</li>
|
||||
<li>最小充值金额:10 {{ displaySymbol(WalletData) }}</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
@@ -126,38 +140,22 @@
|
||||
@close="resetWithdrawForm"
|
||||
>
|
||||
<el-form :model="withdrawForm" :rules="withdrawRules" ref="withdrawForm" label-width="120px">
|
||||
<!-- 选择链 -->
|
||||
<el-form-item label="选择链" prop="chain">
|
||||
<el-select
|
||||
v-model="withdrawForm.toChain"
|
||||
placeholder="请选择区块链网络"
|
||||
<!-- 提现链(只读展示当前钱包链) -->
|
||||
<el-form-item label="提现链">
|
||||
<el-input
|
||||
:value="(WalletData.fromChain || WalletData.chain || withdrawForm.toChain || '').toString().toUpperCase()"
|
||||
:disabled="true"
|
||||
style="width: 100%"
|
||||
@change="onChainChange"
|
||||
>
|
||||
<el-option
|
||||
v-for="chain in chainOptions"
|
||||
:key="chain.value"
|
||||
:label="chain.label"
|
||||
:value="chain.value"
|
||||
/>
|
||||
</el-select>
|
||||
/>
|
||||
</el-form-item>
|
||||
|
||||
<!-- 选择币种 -->
|
||||
<el-form-item label="选择币种" prop="token">
|
||||
<el-select
|
||||
v-model="withdrawForm.toSymbol"
|
||||
placeholder="请选择提现币种"
|
||||
<!-- 提现币种(只读展示当前钱包币种) -->
|
||||
<el-form-item label="提现币种">
|
||||
<el-input
|
||||
:value="displayWithdrawSymbol"
|
||||
:disabled="true"
|
||||
style="width: 100%"
|
||||
:disabled="!withdrawForm.toChain"
|
||||
>
|
||||
<el-option
|
||||
v-for="token in availableTokens"
|
||||
:key="token.value"
|
||||
:label="token.label"
|
||||
:value="token.value"
|
||||
/>
|
||||
</el-select>
|
||||
/>
|
||||
</el-form-item>
|
||||
|
||||
<!-- 提现金额 -->
|
||||
@@ -169,14 +167,14 @@
|
||||
inputmode="decimal"
|
||||
@input="handleAmountInput"
|
||||
>
|
||||
<template slot="append">{{ withdrawForm.toSymbol || 'USDT' }}</template>
|
||||
<template slot="append">{{ displayWithdrawSymbol }}</template>
|
||||
</el-input>
|
||||
<div class="balance-info">
|
||||
<div class="balance-detail">
|
||||
<span>可用余额:{{ walletBalance }} USDT</span>
|
||||
<span>可用余额:{{ (WalletData.walletBalance || WalletData.balance || 0) }} {{ displayWithdrawSymbol }}</span>
|
||||
</div>
|
||||
<div class="balance-detail frozen-info">
|
||||
<span>冻结余额:{{ blockedBalance }} USDT</span>
|
||||
<span>冻结余额:{{ (WalletData.blockedBalance || 0) }} {{ displayWithdrawSymbol }}</span>
|
||||
<span class="frozen-tip">(购买机器下单后冻结,不可提现)</span>
|
||||
</div>
|
||||
</div>
|
||||
@@ -190,10 +188,10 @@
|
||||
style="width: 100%"
|
||||
:disabled="true"
|
||||
>
|
||||
<template slot="append">{{ withdrawForm.toSymbol || 'USDT' }}</template>
|
||||
<template slot="append">{{ displayWithdrawSymbol }}</template>
|
||||
</el-input>
|
||||
<div class="fee-info">
|
||||
网络手续费:{{ withdrawForm.fee || '0.00' }} {{ withdrawForm.toSymbol || 'USDT' }}
|
||||
网络手续费:{{ withdrawForm.fee || '0.00' }} {{ displayWithdrawSymbol }}
|
||||
</div>
|
||||
</el-form-item>
|
||||
|
||||
@@ -205,10 +203,10 @@
|
||||
style="width: 100%"
|
||||
:disabled="true"
|
||||
>
|
||||
<template slot="append">{{ withdrawForm.toSymbol || 'USDT' }}</template>
|
||||
<template slot="append">{{ displayWithdrawSymbol }}</template>
|
||||
</el-input>
|
||||
<div class="actual-amount-info">
|
||||
实际到账:{{ actualAmount }} {{ withdrawForm.toSymbol || 'USDT' }}
|
||||
实际到账:{{ actualAmount }} {{ displayWithdrawSymbol }}
|
||||
</div>
|
||||
</el-form-item>
|
||||
|
||||
@@ -252,18 +250,45 @@
|
||||
<el-button type="primary" @click="confirmWithdraw" :loading="withdrawLoading">确认提现</el-button>
|
||||
</div>
|
||||
</el-dialog>
|
||||
|
||||
<!-- 链上充值 对话框 -->
|
||||
<el-dialog
|
||||
title="链上充值"
|
||||
:visible.sync="createDialogVisible"
|
||||
width="520px"
|
||||
>
|
||||
<el-form label-width="120px">
|
||||
<el-form-item label="选择充值链/币种">
|
||||
<el-cascader
|
||||
v-model="createValue"
|
||||
:options="options"
|
||||
style="width: 100%"
|
||||
|
||||
/>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<div slot="footer" class="dialog-footer">
|
||||
<el-button @click="createDialogVisible = false">取消</el-button>
|
||||
<el-button type="primary" @click="confirmCreateWallet" :loading="createLoading">确定</el-button>
|
||||
</div>
|
||||
</el-dialog>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { getWalletInfo, withdrawBalance ,balanceRechargeList,balanceWithdrawList} from '@/api/wallet'
|
||||
import { getWalletInfo, withdrawBalance ,balanceRechargeList,balanceWithdrawList,getRecentlyTransaction} from '@/api/wallet'
|
||||
|
||||
import { getChainAndList,bindWallet } from "../../api/wallet";
|
||||
export default {
|
||||
name: 'WalletPage',
|
||||
data() {
|
||||
return {
|
||||
// 钱包余额
|
||||
// 单钱包旧字段(兼容保留,不再直接渲染)
|
||||
walletBalance: 0,
|
||||
blockedBalance: 0, // 冻结余额
|
||||
// 多钱包列表
|
||||
walletList: [],
|
||||
WalletData: {},
|
||||
// 充值对话框相关
|
||||
rechargeDialogVisible: false,
|
||||
@@ -308,7 +333,12 @@ export default {
|
||||
// { label: 'BSC (BEP20)', value: 'bsc' },
|
||||
// { label: 'Polygon (MATIC)', value: 'polygon' }
|
||||
],
|
||||
|
||||
options: [],
|
||||
loading: false,
|
||||
// 新建钱包弹窗
|
||||
createDialogVisible: false,
|
||||
createLoading: false,
|
||||
createValue: [],
|
||||
// 币种选项(根据链动态变化)
|
||||
tokenOptions: {
|
||||
tron: [
|
||||
@@ -335,30 +365,30 @@ export default {
|
||||
|
||||
// 最近交易记录
|
||||
recentTransactions: [
|
||||
{
|
||||
id: 1,
|
||||
type: '充值',
|
||||
amount: 500.00,
|
||||
time: '2024-01-15 14:30'
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
type: '购买商品',
|
||||
amount: -89.50,
|
||||
time: '2024-01-14 10:20'
|
||||
},
|
||||
{
|
||||
id: 3,
|
||||
type: '提现',
|
||||
amount: -200.00,
|
||||
time: '2024-01-13 16:45'
|
||||
},
|
||||
{
|
||||
id: 4,
|
||||
type: '充值',
|
||||
amount: 1000.00,
|
||||
time: '2024-01-12 09:15'
|
||||
}
|
||||
// {
|
||||
// id: 1,
|
||||
// type: '充值',
|
||||
// amount: 500.00,
|
||||
// time: '2024-01-15 14:30'
|
||||
// },
|
||||
// {
|
||||
// id: 2,
|
||||
// type: '购买商品',
|
||||
// amount: -89.50,
|
||||
// time: '2024-01-14 10:20'
|
||||
// },
|
||||
// {
|
||||
// id: 3,
|
||||
// type: '提现',
|
||||
// amount: -200.00,
|
||||
// time: '2024-01-13 16:45'
|
||||
// },
|
||||
// {
|
||||
// id: 4,
|
||||
// type: '充值',
|
||||
// amount: 1000.00,
|
||||
// time: '2024-01-12 09:15'
|
||||
// }
|
||||
]
|
||||
}
|
||||
},
|
||||
@@ -390,15 +420,119 @@ export default {
|
||||
const available = parseFloat(this.walletBalance) || 0
|
||||
const blocked = parseFloat(this.blockedBalance) || 0
|
||||
return (available + blocked).toFixed(2)
|
||||
},
|
||||
/**
|
||||
* 提现展示单位(始终大写):优先当前 WalletData 的 fromSymbol/coin
|
||||
*/
|
||||
displayWithdrawSymbol() {
|
||||
const sym = (this.WalletData && (this.WalletData.fromSymbol || this.WalletData.coin || this.withdrawForm.toSymbol)) || ''
|
||||
return String(sym).toUpperCase()
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
this.fetchWalletInfo()
|
||||
// 初始化手续费
|
||||
this.updateFeeByChain()
|
||||
this.getChainAndList()
|
||||
this.fetchRecentlyTransaction()
|
||||
},
|
||||
|
||||
methods: {
|
||||
/**
|
||||
* 统一获取钱包展示单位优先级:fromSymbol > toSymbol > coin > 'USDT'
|
||||
*/
|
||||
displaySymbol(w) {
|
||||
const sym = (w && (w.fromSymbol || w.toSymbol || w.coin)) || ''
|
||||
return String(sym).toUpperCase()
|
||||
},
|
||||
openCreateWallet() {
|
||||
this.createDialogVisible = true
|
||||
if (!Array.isArray(this.options) || this.options.length === 0) {
|
||||
this.getChainAndList()
|
||||
}
|
||||
},
|
||||
async confirmCreateWallet() {
|
||||
const val = this.createValue || []
|
||||
if (!Array.isArray(val) || val.length < 2) {
|
||||
this.$message.warning('请先选择链与币种')
|
||||
return
|
||||
}
|
||||
const [chain, coin] = val
|
||||
if (!chain || !coin) {
|
||||
this.$message.warning('请选择完整的链与币种')
|
||||
return
|
||||
}
|
||||
try {
|
||||
this.createLoading = true
|
||||
const res = await bindWallet({ chain, coin })
|
||||
if (res && (res.code === 0 || res.code === 200)) {
|
||||
|
||||
// 后端会返回充值相关信息(地址/二维码等),直接展示充值弹窗
|
||||
const data = res.data
|
||||
if (data) {
|
||||
const walletInfo = Array.isArray(data) ? (data[0] || {}) : data
|
||||
this.WalletData = walletInfo
|
||||
this.rechargeDialogVisible = true
|
||||
this.qrCodeGenerated = false
|
||||
this.$nextTick(() => { this.generateQRCode() })
|
||||
}
|
||||
// 同步刷新钱包列表(异步,不影响弹窗展示)
|
||||
this.fetchWalletInfo()
|
||||
// 关闭链/币种选择弹窗
|
||||
this.createDialogVisible = false
|
||||
this.createValue = []
|
||||
}
|
||||
} catch (e) {
|
||||
console.error('获取充值信息失败', e)
|
||||
} finally {
|
||||
this.createLoading = false
|
||||
}
|
||||
},
|
||||
async getChainAndList() {
|
||||
this.loading = true;
|
||||
const res = await getChainAndList();
|
||||
if (res && (res.code === 0 || res.code === 200) && res.data) {
|
||||
this.options = res.data;
|
||||
}
|
||||
|
||||
this.loading = false;
|
||||
},
|
||||
/**
|
||||
* 获取最近交易记录(充值/提现/支付)
|
||||
*/
|
||||
async fetchRecentlyTransaction() {
|
||||
try {
|
||||
const res = await getRecentlyTransaction()
|
||||
if (res && (res.code === 0 || res.code === 200)) {
|
||||
const rows = Array.isArray(res.data) ? res.data : []
|
||||
const mapped = rows.map((r, idx) => {
|
||||
const rawAmt = Number(r && r.amount)
|
||||
const amt = Number.isFinite(rawAmt) ? rawAmt : 0
|
||||
const type = Number(r && r.type)
|
||||
const signAmt = (type === 1) ? Math.abs(amt) : -Math.abs(amt) // 1 充值为正,0 支付/2 提现为负
|
||||
const typeLabel = type === 1 ? '充值' : (type === 2 ? '提现' : '支付')
|
||||
return {
|
||||
id: `${r && r.updateTime || ''}-${idx}`,
|
||||
type: typeLabel,
|
||||
amount: Number(signAmt.toFixed(2)),
|
||||
time: this.formatApiTime(r && r.updateTime)
|
||||
}
|
||||
})
|
||||
this.recentTransactions = mapped
|
||||
}
|
||||
} catch (e) {
|
||||
// 忽略错误,保留本地占位
|
||||
// console.error('获取最近交易失败', e)
|
||||
}
|
||||
},
|
||||
/**
|
||||
* 将后端时间格式 2025-10-16T07:57:28 转换为 2025-10-16 07:57:28
|
||||
*/
|
||||
formatApiTime(value) {
|
||||
const s = String(value || '')
|
||||
if (!s) return ''
|
||||
return s.replace('T', ' ').replace('Z', '')
|
||||
},
|
||||
/**
|
||||
* 将金额字符串转换为“分”为单位的整数
|
||||
*/
|
||||
@@ -433,9 +567,26 @@ export default {
|
||||
const res = await getWalletInfo(params)
|
||||
|
||||
if (res && (res.code === 0 || res.code === 200)) {
|
||||
this.walletBalance = res.data.balance
|
||||
this.blockedBalance = res.data.blockedBalance || 0
|
||||
this.WalletData = res.data
|
||||
// 兼容两种返回:对象或数组
|
||||
const data = res.data
|
||||
if (Array.isArray(data)) {
|
||||
this.walletList = data
|
||||
// 兜底设置第一个钱包到旧字段,兼容现有充值二维码逻辑
|
||||
const first = data[0] || {}
|
||||
this.walletBalance = first.walletBalance || first.balance || 0
|
||||
this.blockedBalance = first.blockedBalance || 0
|
||||
this.WalletData = first
|
||||
} else if (data && typeof data === 'object') {
|
||||
this.walletList = [data]
|
||||
this.walletBalance = data.walletBalance || data.balance || 0
|
||||
this.blockedBalance = data.blockedBalance || 0
|
||||
this.WalletData = data
|
||||
} else {
|
||||
this.walletList = []
|
||||
this.walletBalance = 0
|
||||
this.blockedBalance = 0
|
||||
this.WalletData = {}
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('获取钱包信息失败:', error)
|
||||
@@ -500,9 +651,13 @@ export default {
|
||||
/**
|
||||
* 打开充值对话框
|
||||
*/
|
||||
handleRecharge() {
|
||||
|
||||
handleRecharge(wallet) {
|
||||
// 切换到被点击钱包的数据作为当前充值来源
|
||||
if (wallet && typeof wallet === 'object') {
|
||||
this.WalletData = wallet
|
||||
}
|
||||
this.rechargeDialogVisible = true
|
||||
this.qrCodeGenerated = false
|
||||
this.$nextTick(() => {
|
||||
this.generateQRCode()
|
||||
})
|
||||
@@ -511,7 +666,17 @@ export default {
|
||||
/**
|
||||
* 打开提现对话框
|
||||
*/
|
||||
handleWithdraw() {
|
||||
handleWithdraw(wallet) {
|
||||
// 若需要也可根据点击卡片切换默认链/币种
|
||||
if (wallet) {
|
||||
// 同步当前选中的钱包,驱动只读展示链与币种
|
||||
this.WalletData = wallet
|
||||
const chain = wallet.fromChain || wallet.chain || this.withdrawForm.toChain
|
||||
const symbol = wallet.fromSymbol || wallet.coin || this.withdrawForm.toSymbol
|
||||
this.withdrawForm.toChain = chain
|
||||
this.withdrawForm.toSymbol = symbol
|
||||
this.updateFeeByChain()
|
||||
}
|
||||
this.withdrawDialogVisible = true
|
||||
},
|
||||
|
||||
@@ -642,8 +807,8 @@ export default {
|
||||
try {
|
||||
// 调用后台提现API,添加谷歌验证码参数
|
||||
const res = await withdrawBalance({
|
||||
toChain: this.withdrawForm.toChain,
|
||||
toSymbol: this.withdrawForm.toSymbol,
|
||||
toChain: (this.WalletData && (this.WalletData.fromChain || this.WalletData.chain)) || this.withdrawForm.toChain,
|
||||
toSymbol: (this.WalletData && (this.WalletData.fromSymbol || this.WalletData.coin)) || this.withdrawForm.toSymbol,
|
||||
amount: parseFloat(this.withdrawForm.amount),
|
||||
toAddress: this.withdrawForm.toAddress,
|
||||
code: this.withdrawForm.googleCode // 添加谷歌验证码
|
||||
@@ -864,12 +1029,37 @@ export default {
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
/* 顶部工具栏 */
|
||||
.wallet-toolbar {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
.create-wallet-btn {
|
||||
background: linear-gradient(135deg, #409eff 0%, #36cfc9 100%);
|
||||
border: none;
|
||||
color: #fff;
|
||||
font-weight: 600;
|
||||
border-radius: 8px;
|
||||
box-shadow: 0 6px 18px rgba(64, 158, 255, 0.25);
|
||||
}
|
||||
.create-wallet-btn:hover {
|
||||
filter: brightness(1.05);
|
||||
}
|
||||
|
||||
.wallet-card-section{
|
||||
max-height: 600px;
|
||||
/* height: 600px; */
|
||||
overflow-y: auto;
|
||||
padding: 8px;
|
||||
}
|
||||
|
||||
/* 钱包卡片样式 */
|
||||
.wallet-card {
|
||||
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
||||
border-radius: 16px;
|
||||
padding: 24px;
|
||||
margin-bottom: 24px;
|
||||
border-radius: 12px;
|
||||
padding: 16px;
|
||||
margin-bottom: 12px;
|
||||
color: white;
|
||||
box-shadow: 0 8px 32px rgba(102, 126, 234, 0.3);
|
||||
}
|
||||
@@ -878,11 +1068,11 @@ export default {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-bottom: 24px;
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
|
||||
.wallet-title {
|
||||
font-size: 24px;
|
||||
font-size: 18px;
|
||||
font-weight: 700;
|
||||
margin: 0;
|
||||
}
|
||||
@@ -890,32 +1080,34 @@ export default {
|
||||
.wallet-balance {
|
||||
text-align: right;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 8px;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
gap: 16px;
|
||||
}
|
||||
|
||||
.balance-item {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: flex-end;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.balance-label {
|
||||
display: block;
|
||||
font-size: 14px;
|
||||
opacity: 0.8;
|
||||
margin-bottom: 4px;
|
||||
display: inline-block;
|
||||
font-size: 16px;
|
||||
opacity: 0.85;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.balance-amount {
|
||||
font-size: 32px;
|
||||
font-size: 20px;
|
||||
font-weight: 700;
|
||||
font-family: 'Monaco', 'Menlo', monospace;
|
||||
}
|
||||
|
||||
.balance-amount.frozen {
|
||||
font-size: 24px;
|
||||
opacity: 0.8;
|
||||
font-size: 20px;
|
||||
opacity: 0.9;
|
||||
color: #ffa940;
|
||||
}
|
||||
|
||||
@@ -923,16 +1115,21 @@ export default {
|
||||
.wallet-actions {
|
||||
display: flex;
|
||||
gap: 16px;
|
||||
justify-content: right;
|
||||
}
|
||||
|
||||
.action-btn {
|
||||
flex: 1;
|
||||
height: 48px;
|
||||
font-size: 16px;
|
||||
width: 100px;
|
||||
height: 30px;
|
||||
|
||||
font-size: 13px;
|
||||
font-weight: 600;
|
||||
border-radius: 8px;
|
||||
border: none;
|
||||
transition: all 0.3s ease;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.recharge-btn {
|
||||
@@ -957,6 +1154,17 @@ export default {
|
||||
transform: translateY(-2px);
|
||||
}
|
||||
|
||||
/* 内联提现按钮(余额行末尾) */
|
||||
.withdraw-inline-btn {
|
||||
background: rgba(255, 255, 255, 0.2);
|
||||
color: white;
|
||||
border: 2px solid rgba(255, 255, 255, 0.3);
|
||||
}
|
||||
.withdraw-inline-btn:hover {
|
||||
background: rgba(255, 255, 255, 0.3);
|
||||
transform: translateY(-1px);
|
||||
}
|
||||
|
||||
/* 交易记录区域 */
|
||||
.transaction-section {
|
||||
background: white;
|
||||
@@ -1119,6 +1327,18 @@ export default {
|
||||
background-color: #f8f9fa;
|
||||
}
|
||||
|
||||
/* 充值链/币种信息 */
|
||||
.charge-meta {
|
||||
display: flex;
|
||||
gap: 8px;
|
||||
align-items: center;
|
||||
margin-bottom: 10px;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
.meta-tag { border-radius: 14px; }
|
||||
.meta-title { margin-left: 4px; opacity: .9; }
|
||||
.meta-val { margin-left: 2px; font-weight: 700; letter-spacing: .3px; }
|
||||
|
||||
.copy-btn {
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
@@ -31,86 +31,87 @@
|
||||
>
|
||||
<el-table-column type="expand" width="46">
|
||||
<template #default="shopScope">
|
||||
<!-- 中层:商品分组列表(按店铺) -->
|
||||
<el-table
|
||||
:ref="'productTable-' + shopScope.row.id"
|
||||
:data="shopScope.row.shoppingCartInfoDtoList || []"
|
||||
border size="small" style="width: 100%"
|
||||
:row-key="'id'"
|
||||
:header-cell-style="{ textAlign: 'left' }"
|
||||
:cell-style="{ textAlign: 'left' }"
|
||||
@selection-change="sels => handleGroupSelectionChangeForShop(shopScope.row, sels)"
|
||||
@expand-change="(row, expandedRows) => handleProductExpandChange(shopScope.row, row, expandedRows)"
|
||||
>
|
||||
<el-table-column type="selection" width="46" />
|
||||
<el-table-column type="expand" width="46">
|
||||
<template #default="outer">
|
||||
<!-- 内层:机器列表 -->
|
||||
<el-table :data="outer.row.productMachineDtoList" size="small" border style="width: 100%"
|
||||
:row-key="'id'"
|
||||
:ref="'innerTable-' + outer.row.id"
|
||||
@selection-change="sels => handleInnerSelectionChange(outer.row, sels)"
|
||||
:header-cell-style="{ textAlign: 'left' }" :cell-style="{ textAlign: 'left' }">
|
||||
<el-table-column type="selection" width="46" />
|
||||
<el-table-column prop="miner" label="机器编号" min-width="160" />
|
||||
<el-table-column prop="algorithm" label="算法" min-width="140" />
|
||||
<el-table-column prop="powerDissipation" label="功耗(kw/h)" min-width="140" />
|
||||
<el-table-column prop="theoryPower" label="理论算力" min-width="140" />
|
||||
<el-table-column prop="theoryIncome" min-width="200">
|
||||
<template #header>单机理论收入(每日)({{ outer.row.coin || '' }})</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="theoryUsdtIncome" label="单机理论收入(每日/USDT)" min-width="200" />
|
||||
<el-table-column prop="state" label="状态" min-width="100" >
|
||||
<template #default="scope">
|
||||
<el-tag :type="scope.row.state === 1 ? 'info' : 'success'">
|
||||
{{ scope.row.state === 1 ? '下架' : '上架' }}
|
||||
</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="price" label="单价(USDT)" min-width="120" />
|
||||
<el-table-column label="租赁天数(天)" min-width="140">
|
||||
<template #default="scope">
|
||||
<el-input-number
|
||||
v-model="scope.row.leaseTime"
|
||||
:min="1"
|
||||
:max="365"
|
||||
:precision="0"
|
||||
:step="1"
|
||||
size="mini"
|
||||
controls-position="right"
|
||||
@change="handleLeaseTimeChange(scope.row)"
|
||||
@input="handleLeaseTimeInput(scope.row, $event)"
|
||||
/>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="机器总价(USDT)" min-width="160">
|
||||
<template #default="scope">{{ (Number(scope.row.price || 0) * Number(scope.row.leaseTime || 1)).toFixed(2) }}</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
<!-- 机器列表(直接挂在店铺下) -->
|
||||
<el-table :data="shopScope.row.productMachineDtoList || []" size="small" border style="width: 100%"
|
||||
:row-key="'id'" reserve-selection
|
||||
:ref="'innerTable-' + shopScope.row.id"
|
||||
@selection-change="sels => handleShopInnerSelectionChange(shopScope.row, sels)"
|
||||
:header-cell-style="{ textAlign: 'left' }" :cell-style="{ textAlign: 'left' }">
|
||||
<el-table-column type="selection" width="46" :selectable="isRowSelectable" />
|
||||
<el-table-column prop="name" label="商品名称" />
|
||||
<el-table-column prop="miner" label="机器编号" />
|
||||
<el-table-column prop="algorithm" label="算法" />
|
||||
|
||||
<el-table-column prop="powerDissipation" label="功耗(kw/h)" />
|
||||
<el-table-column prop="theoryPower" label="理论算力" >
|
||||
<template #default="scope">{{ scope.row.theoryPower }} <span v-show="scope.row.theoryPower">{{ scope.row.unit }}</span></template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="computingPower" label="实际算力">
|
||||
<template #default="scope">{{ scope.row.computingPower }} <span v-show="scope.row.computingPower">{{ scope.row.unit }}</span></template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="theoryIncome" label="单机理论收入(每日/币种)">
|
||||
<template #default="scope">{{ scope.row.theoryIncome }} <span v-show="scope.row.coin">{{ toUpperText(scope.row.coin) }}</span></template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="theoryUsdtIncome" label="单机理论收入(每日/USDT)"/>
|
||||
<!-- <el-table-column prop="state" label="状态" min-width="100" >
|
||||
<template #default="scope">
|
||||
<el-tag :type="scope.row.state === 1 ? 'info' : 'success'">
|
||||
{{ scope.row.state === 1 ? '下架' : '上架' }}
|
||||
</el-tag>
|
||||
</template>
|
||||
</el-table-column> -->
|
||||
<el-table-column prop="price" label="单价(USDT)" width="100">
|
||||
<template #default="scope">
|
||||
<span class="price-strong">{{ formatTrunc(scope.row.price, 2) }}</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="name" label="商品名称" min-width="160" />
|
||||
<el-table-column prop="coin" label="币种" min-width="120" />
|
||||
<el-table-column label="机器数量" min-width="120">
|
||||
<template #default="scope">{{ (scope.row.productMachineDtoList || []).length }}</template>
|
||||
<el-table-column label="租赁天数" width="145">
|
||||
<template #default="scope">
|
||||
<el-input-number
|
||||
v-model="scope.row.leaseTime"
|
||||
:min="1"
|
||||
:max="getRowMaxLeaseDaysLocal(scope.row)"
|
||||
:precision="0"
|
||||
:step="1"
|
||||
size="mini"
|
||||
controls-position="right"
|
||||
@change="handleLeaseTimeChange(scope.row)"
|
||||
@input="handleLeaseTimeInput(scope.row, $event)"
|
||||
/>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="总价(USDT)" min-width="140">
|
||||
<template #default="scope"><span class="price-strong">{{ calcGroupTotal(scope.row).toFixed(2) }}</span></template>
|
||||
|
||||
<el-table-column label="最大可租(天)" min-width="60">
|
||||
<template #default="scope">{{ scope.row.maxLeaseDays != null ? scope.row.maxLeaseDays : '' }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="机器状态" width="110">
|
||||
<template #default="scope">
|
||||
<el-tag :type="(Number(scope.row.del) === 1 || Number(scope.row.state) === 1) ? 'info' : 'success'">
|
||||
{{ (Number(scope.row.del) === 1 || Number(scope.row.state) === 1) ? '下架' : '上架' }}
|
||||
</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="机器总价(USDT)" min-width="100">
|
||||
<template #default="scope"><span class="price-strong">{{ formatTrunc(Number(scope.row.price || 0) * Number(scope.row.leaseTime || 1), 2) }}</span></template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="name" label="店铺名称" min-width="220" />
|
||||
<el-table-column label="商品数" min-width="120">
|
||||
<template #default="scope">{{ (scope.row.shoppingCartInfoDtoList || []).length }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="机器总数" min-width="120">
|
||||
<el-table-column prop="name" label="店铺名称" />
|
||||
<el-table-column prop="totalMachine" label="机器总数" />
|
||||
<!-- <el-table-column label="机器总数" min-width="120">
|
||||
<template #default="scope">{{ countMachines(scope.row) }}</template>
|
||||
</el-table-column> -->
|
||||
<el-table-column label="总价(USDT)" >
|
||||
<template #default="scope"><span class="price-strong">{{ formatTrunc(computeShopTotal(scope.row), 2) }}</span></template>
|
||||
</el-table-column>
|
||||
<el-table-column label="总价(USDT)" min-width="140">
|
||||
<template #default="scope"><span class="price-strong">{{ computeShopTotal(scope.row).toFixed(2) }}</span></template>
|
||||
|
||||
<el-table-column label="支付方式">
|
||||
<template #default="scope">
|
||||
<img v-for="(item,index ) in scope.row.payConfigList" :key="index" :src="item.payCoinImage" :alt="item.payChain" :title="formatPayTooltip(item)" style="width: 20px; height: 20px; margin-right: 10px;" />
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="操作" min-width="160">
|
||||
<el-table-column label="操作" >
|
||||
<template #default="scope">
|
||||
<el-button type="primary" size="mini" :loading="creatingOrder" :disabled="creatingOrder" @click="handleCheckoutShop(scope.row)">结算该店铺订单</el-button>
|
||||
</template>
|
||||
@@ -119,10 +120,11 @@
|
||||
<div class="summary-actions" style="margin-top:16px;display:flex;gap:12px;justify-content:flex-end;">
|
||||
<div class="summary-inline" style="color:#666;">
|
||||
已选机器:<b>{{ selectedMachineCount }}</b> 台
|
||||
<span style="margin-left:12px;">金额合计(USDT):<b>{{ selectedTotal.toFixed(2) }}</b></span>
|
||||
<span style="margin-left:12px;">金额合计(USDT):<b>{{ formatTrunc(selectedTotal, 2) }}</b></span>
|
||||
</div>
|
||||
<div class="actions-inline" style="display:flex;gap:12px;">
|
||||
<el-button type="danger" :disabled="!selectedMachineCount" @click="handleRemoveSelectedMachines">删除所选机器</el-button>
|
||||
<el-button type="warning" plain :loading="clearOffLoading" @click="handleClearOffShelf">清除已下架商品</el-button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -135,9 +137,11 @@
|
||||
<el-table-column prop="machineId" label="机器ID" min-width="100" />
|
||||
<el-table-column prop="user" label="账户" min-width="120" />
|
||||
<el-table-column prop="miner" label="机器编号" min-width="160" />
|
||||
<el-table-column prop="price" label="单价(USDT)" min-width="120" />
|
||||
<el-table-column prop="price" min-width="120">
|
||||
<template #header>单价({{ payCoinSymbol || 'USDT' }})</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
<div style="margin-top:12px;text-align:right;">总金额(USDT):<b>{{ confirmDialog.total.toFixed(2) }}</b></div>
|
||||
<div style="margin-top:12px;text-align:right;">总金额({{ payCoinSymbol || 'USDT' }}):<b>{{ formatTrunc(confirmDialog.total, 2) }}</b></div>
|
||||
</div>
|
||||
<template #footer>
|
||||
<el-button @click="confirmDialog.visible=false">取消</el-button>
|
||||
@@ -145,6 +149,23 @@
|
||||
</template>
|
||||
</el-dialog>
|
||||
|
||||
<!-- 支付链/币种选择弹窗 -->
|
||||
<el-dialog :visible.sync="payDialog.visible" width="520px" title="选择支付链/币种" :close-on-click-modal="false">
|
||||
<el-form label-width="120px">
|
||||
<el-form-item label="链/币种">
|
||||
<el-cascader
|
||||
v-model="payDialog.value"
|
||||
:options="options"
|
||||
style="width: 100%"
|
||||
/>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<template #footer>
|
||||
<el-button @click="payDialog.visible=false">取消</el-button>
|
||||
<el-button type="primary" :loading="payDialog.loading" @click="handlePayConfirm">下一步</el-button>
|
||||
</template>
|
||||
</el-dialog>
|
||||
|
||||
<!-- 结算成功提示弹窗 -->
|
||||
<el-dialog :visible.sync="successDialog.visible" width="480px" :close-on-click-modal="false" :close-on-press-escape="false" @close="handleCloseSuccessDialog">
|
||||
<div style="text-align:center; padding: 20px 0;">
|
||||
@@ -235,8 +256,9 @@
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { getGoodsList, deleteBatchGoods as apiDeleteBatchGoods} from '../../api/shoppingCart'
|
||||
import { addOrders,cancelOrder,getOrdersByIds,getOrdersByStatus } from '../../api/order'
|
||||
import { getGoodsList, deleteBatchGoods as apiDeleteBatchGoods ,deleteBatchGoodsForIsDelete} from '../../api/shoppingCart'
|
||||
import { addOrders,cancelOrder,getOrdersByIds,getOrdersByStatus , getChainAndListForSeller,getCoinPrice } from '../../api/order'
|
||||
|
||||
export default {
|
||||
name: 'Cart',
|
||||
data() {
|
||||
@@ -245,7 +267,8 @@ export default {
|
||||
shops: [], // 店铺数组,每个店铺下有 shoppingCartInfoDtoList
|
||||
groups: [], // 兼容旧结构,保留
|
||||
selectedGroups: [],
|
||||
selectedMachinesMap: {}, // { groupId: Set(machineId) }
|
||||
// 新结构:按店铺选择机器 { shopId: Set(machineId) }
|
||||
selectedMachinesMap: {},
|
||||
confirmDialog: { visible: false, items: [], count: 0, total: 0 },
|
||||
expandedGroupKeys: [],
|
||||
expandedShopKeys: [],
|
||||
@@ -262,7 +285,14 @@ export default {
|
||||
code: '',
|
||||
error: '',
|
||||
loading: false
|
||||
}
|
||||
},
|
||||
// 支付选择
|
||||
options: [],
|
||||
payDialog: { visible: false, value: [], loading: false },
|
||||
selectedChain: '',
|
||||
selectedCoin: '',
|
||||
selectedPrice: 0
|
||||
,clearOffLoading: false
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
@@ -302,6 +332,10 @@ export default {
|
||||
isGoogleCodeValid() {
|
||||
const code = this.googleCodeDialog.code
|
||||
return /^\d{6}$/.test(code)
|
||||
},
|
||||
// 支付币种展示用(大写)
|
||||
payCoinSymbol() {
|
||||
return (this.selectedCoin || '').toUpperCase()
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
@@ -311,10 +345,22 @@ export default {
|
||||
'noticeDialog.visible'(val) {
|
||||
if (val) {
|
||||
this.startNoticeCountdown()
|
||||
// 弹窗打开时也立即恢复子表勾选(避免视觉上丢失)
|
||||
this.$nextTick(() => this.reapplySelectionsForPendingShop())
|
||||
} else if (this.noticeTimer) {
|
||||
try { clearInterval(this.noticeTimer) } catch (e) { /* noop */ }
|
||||
this.noticeTimer = null
|
||||
}
|
||||
},
|
||||
'confirmDialog.visible'(val) {
|
||||
// 打开或关闭任一阶段都尝试恢复一次视觉勾选
|
||||
this.$nextTick(() => this.reapplySelectionsForPendingShop())
|
||||
},
|
||||
'payDialog.visible'(val) {
|
||||
this.$nextTick(() => this.reapplySelectionsForPendingShop())
|
||||
},
|
||||
'googleCodeDialog.visible'(val) {
|
||||
this.$nextTick(() => this.reapplySelectionsForPendingShop())
|
||||
}
|
||||
},
|
||||
beforeDestroy() {
|
||||
@@ -322,54 +368,78 @@ export default {
|
||||
this.noticeTimer = null
|
||||
},
|
||||
methods: {
|
||||
// 获取所有商品分组(兼容:旧结构 this.groups;新结构 this.shops 下的 shoppingCartInfoDtoList)
|
||||
getAllGroups() {
|
||||
if (Array.isArray(this.groups) && this.groups.length) return this.groups
|
||||
const flat = []
|
||||
const shops = Array.isArray(this.shops) ? this.shops : []
|
||||
shops.forEach(shop => {
|
||||
const arr = Array.isArray(shop.shoppingCartInfoDtoList) ? shop.shoppingCartInfoDtoList : []
|
||||
arr.forEach(g => flat.push(g))
|
||||
})
|
||||
return flat
|
||||
// 选择框可选逻辑:下架(或删除)机器不可选择
|
||||
isRowSelectable(row, index) {
|
||||
return !(Number(row && row.del) === 1 || Number(row && row.state) === 1)
|
||||
},
|
||||
// 获取本地最大可租赁天数,默认 365
|
||||
getRowMaxLeaseDaysLocal(row) {
|
||||
const raw = row && row.maxLeaseDays
|
||||
const n = Number(raw)
|
||||
if (!Number.isFinite(n)) return 365
|
||||
if (n < 1) return 1
|
||||
if (n > 365) return 365
|
||||
return Math.floor(n)
|
||||
},
|
||||
/**
|
||||
* 精确裁剪数字到指定位数(不四舍五入)
|
||||
* @param {number|string} value - 原始值
|
||||
* @param {number} decimals - 小数位数,默认2
|
||||
* @returns {string}
|
||||
*/
|
||||
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
|
||||
// 使用 toLocaleString 保留精度但不四舍五入,通过补零对齐位数
|
||||
const str = String(truncated)
|
||||
if (d === 0) return str
|
||||
const [intPart, decPart = ''] = str.split('.')
|
||||
const padded = decPart.padEnd(d, '0')
|
||||
return `${intPart}.${padded}`
|
||||
},
|
||||
//获取支持的链和币种
|
||||
async fetchChainAndListForSeller(shopId) {
|
||||
this.loading = true;
|
||||
const res = await getChainAndListForSeller({ id: shopId });
|
||||
if (res && (res.code === 0 || res.code === 200) && res.data) {
|
||||
this.options = this.toUpperOptions(res.data);
|
||||
}
|
||||
|
||||
this.loading = false;
|
||||
},
|
||||
// 将级联选项的显示文本统一转为大写(不修改传给后端的 value)
|
||||
toUpperOptions(list) {
|
||||
const arr = Array.isArray(list) ? list : []
|
||||
return arr.map(item => {
|
||||
const next = { ...item }
|
||||
const labelSrc = (item && (item.label != null ? item.label : item.value)) || ''
|
||||
next.label = String(labelSrc).toUpperCase()
|
||||
if (Array.isArray(item && item.children)) {
|
||||
next.children = this.toUpperOptions(item.children)
|
||||
}
|
||||
return next
|
||||
})
|
||||
},
|
||||
// 获取所有商品分组(兼容保留,现直接返回空,因已移除中间商品层)
|
||||
getAllGroups() { return [] },
|
||||
// 店铺总价 = 累加其所有商品的 (机器单价 × 租赁天数)
|
||||
computeShopTotal(shop) {
|
||||
const groups = Array.isArray(shop && shop.shoppingCartInfoDtoList) ? shop.shoppingCartInfoDtoList : []
|
||||
let total = 0
|
||||
groups.forEach(g => {
|
||||
const list = Array.isArray(g.productMachineDtoList) ? g.productMachineDtoList : []
|
||||
list.forEach(m => {
|
||||
const price = Number(m.price || 0)
|
||||
const days = Number(m.leaseTime || 1)
|
||||
total += price * days
|
||||
})
|
||||
})
|
||||
return total
|
||||
const list = Array.isArray(shop && shop.productMachineDtoList) ? shop.productMachineDtoList : []
|
||||
return list.reduce((sum, m) => sum + Number(m.price || 0) * Number(m.leaseTime || 1), 0)
|
||||
},
|
||||
// 组装批量删除的请求体:数组 [{ machineId, productId }]
|
||||
buildDeletePayload() {
|
||||
const payload = []
|
||||
const groups = this.getAllGroups()
|
||||
const hasMachineSelection = this.selectedMachineCount > 0
|
||||
if (hasMachineSelection) {
|
||||
groups.forEach(g => {
|
||||
const set = this.selectedMachinesMap[g.id]
|
||||
if (!set || set.size === 0) return
|
||||
const list = Array.isArray(g.productMachineDtoList) ? g.productMachineDtoList : []
|
||||
list.forEach(m => {
|
||||
if (set.has(m.id)) {
|
||||
payload.push({ machineId: m.id, productId: g.productId })
|
||||
}
|
||||
})
|
||||
})
|
||||
} else if (this.selectedGroups && this.selectedGroups.length) {
|
||||
// 未选机器但选了商品分组:删除该分组下的所有机器
|
||||
this.selectedGroups.forEach(g => {
|
||||
const list = Array.isArray(g.productMachineDtoList) ? g.productMachineDtoList : []
|
||||
list.forEach(m => payload.push({ machineId: m.id, productId: g.productId }))
|
||||
})
|
||||
}
|
||||
const shops = Array.isArray(this.shops) ? this.shops : []
|
||||
shops.forEach(shop => {
|
||||
const set = this.selectedMachinesMap[shop.id]
|
||||
if (!set || set.size === 0) return
|
||||
const list = Array.isArray(shop.productMachineDtoList) ? shop.productMachineDtoList : []
|
||||
list.forEach(m => { if (set.has(m.id)) payload.push({ machineId: m.id, productId: m.productId }) })
|
||||
})
|
||||
return payload.filter(it => it && (it.machineId != null))
|
||||
},
|
||||
async fetchAddOrders(orderInfoVoList, googleCode) {
|
||||
@@ -377,6 +447,9 @@ export default {
|
||||
// 按照新的传参结构:{code: 谷歌验证码, orderInfoVoList: [之前传参的数组]}
|
||||
const payload = {
|
||||
code: googleCode,
|
||||
chain: this.selectedChain,
|
||||
coin: this.selectedCoin,
|
||||
price: this.selectedPrice,
|
||||
orderInfoVoList: orderInfoVoList
|
||||
}
|
||||
const res = await addOrders(payload)
|
||||
@@ -394,6 +467,32 @@ export default {
|
||||
return { code: -1, msg: '网络异常' }
|
||||
}
|
||||
},
|
||||
// 清除已下架/删除商品
|
||||
async handleClearOffShelf() {
|
||||
if (this.clearOffLoading) return
|
||||
this.clearOffLoading = true
|
||||
try {
|
||||
const res = await deleteBatchGoodsForIsDelete()
|
||||
if (res && Number(res.code) === 200) {
|
||||
this.$message({ message: '已清除下架商品', type: 'success', showClose: true })
|
||||
await this.fetchGetGoodsList()
|
||||
} else {
|
||||
this.$message({ message: (res && res.msg) || '清除失败', type: 'error', showClose: true })
|
||||
}
|
||||
} catch (e) {
|
||||
this.$message({ message: '网络异常', type: 'error', showClose: true })
|
||||
} finally {
|
||||
this.clearOffLoading = false
|
||||
}
|
||||
},
|
||||
/**
|
||||
* 将文本安全地转为大写
|
||||
* @param {string|number|null|undefined} value 原值
|
||||
* @returns {string} 大写字符串
|
||||
*/
|
||||
toUpperText(value) {
|
||||
return value == null ? '' : String(value).toUpperCase()
|
||||
},
|
||||
// 外层展开/收起同步到 expandedGroupKeys,确保点击箭头生效
|
||||
handleOuterExpandChange(row, expandedRows) {
|
||||
// ElTable 会把当前已展开行数组给我们,这里只需要记录其 id 列表即可
|
||||
@@ -412,6 +511,11 @@ export default {
|
||||
? expandedRows.map(r => r && (r.id != null ? String(r.id) : undefined)).filter(Boolean)
|
||||
: []
|
||||
this.expandedShopKeys = keys
|
||||
// 展开时恢复该店铺下已选中的机器勾选
|
||||
const isExpanded = keys.includes(String(row.id))
|
||||
if (isExpanded) {
|
||||
this.$nextTick(() => this.applyInnerSelectionFromSet(row))
|
||||
}
|
||||
} catch (e) {
|
||||
this.expandedShopKeys = []
|
||||
}
|
||||
@@ -439,21 +543,15 @@ export default {
|
||||
}
|
||||
|
||||
// 如果是店铺结构
|
||||
if (rawRows.length && rawRows[0].shoppingCartInfoDtoList) {
|
||||
if (rawRows.length && rawRows[0].productMachineDtoList) {
|
||||
const withShopKeys = rawRows.map((shop, sIdx) => ({
|
||||
...shop,
|
||||
id: shop.id != null ? String(shop.id) : `shop-${sIdx}`,
|
||||
shoppingCartInfoDtoList: (shop.shoppingCartInfoDtoList || []).map((g, gIdx) => ({
|
||||
...g,
|
||||
id: g.id != null ? String(g.id) : (g.productId != null ? `p-${g.productId}` : `g-${sIdx}-${gIdx}`)
|
||||
}))
|
||||
id: shop.id != null ? String(shop.id) : `shop-${sIdx}`
|
||||
}))
|
||||
this.shops = withShopKeys
|
||||
// 清空旧结构数据
|
||||
this.groups = []
|
||||
this.expandedGroupKeys = []
|
||||
// 同步数量:统计所有店铺下所有机器数量
|
||||
const count = withShopKeys.reduce((s, shop) => s + (shop.shoppingCartInfoDtoList || []).reduce((ss, g) => ss + ((Array.isArray(g.productMachineDtoList) ? g.productMachineDtoList.length : 0)), 0), 0)
|
||||
const count = withShopKeys.reduce((s, shop) => s + ((Array.isArray(shop.productMachineDtoList) ? shop.productMachineDtoList.length : 0)), 0)
|
||||
window.dispatchEvent(new CustomEvent('cart-updated', { detail: { count } }))
|
||||
return
|
||||
}
|
||||
@@ -481,71 +579,13 @@ export default {
|
||||
}
|
||||
},
|
||||
|
||||
handleGroupSelectionChange(selection) {
|
||||
this.selectedGroups = selection
|
||||
const selectedIdSet = new Set(selection.map(s => s.id))
|
||||
|
||||
// 1) 展开被选中的商品,收起取消选择的商品
|
||||
const nextExpanded = new Set(this.expandedGroupKeys)
|
||||
this.groups.forEach(g => {
|
||||
if (selectedIdSet.has(g.id)) nextExpanded.add(g.id)
|
||||
else nextExpanded.delete(g.id)
|
||||
})
|
||||
this.expandedGroupKeys = Array.from(nextExpanded)
|
||||
|
||||
// 2) 待子表渲染完成后,勾选/清空子表的机器
|
||||
this.$nextTick(() => {
|
||||
this.groups.forEach(g => {
|
||||
const inner = this.$refs['innerTable-' + g.id]
|
||||
const list = Array.isArray(g.productMachineDtoList) ? g.productMachineDtoList : []
|
||||
const shouldSelectAll = selectedIdSet.has(g.id)
|
||||
// 自定义 ref 可能还未渲染,做空判
|
||||
if (inner && typeof inner.clearSelection === 'function') {
|
||||
try { inner.clearSelection() } catch (e) { /* ignore clear selection error */ }
|
||||
if (shouldSelectAll) {
|
||||
list.forEach(m => { try { inner.toggleRowSelection(m, true) } catch (e) { /* ignore toggle selection error */ } })
|
||||
this.$set(this.selectedMachinesMap, g.id, new Set(list.map(m => m.id)))
|
||||
} else {
|
||||
this.$set(this.selectedMachinesMap, g.id, new Set())
|
||||
}
|
||||
} else {
|
||||
// 即使未渲染,也同步 map,等渲染后由其它交互刷新
|
||||
this.$set(this.selectedMachinesMap, g.id, shouldSelectAll ? new Set(list.map(m => m.id)) : new Set())
|
||||
}
|
||||
})
|
||||
})
|
||||
},
|
||||
handleGroupSelectionChange() { /* 中间层已移除,保留空实现以兼容旧调用 */ },
|
||||
// 选中某店铺下的商品分组:自动展开并全选其机器;取消则收起并清空
|
||||
handleGroupSelectionChangeForShop(shop, selection) {
|
||||
const table = this.$refs['productTable-' + (shop && shop.id)]
|
||||
const groups = Array.isArray(shop && shop.shoppingCartInfoDtoList) ? shop.shoppingCartInfoDtoList : []
|
||||
const selectedIdSet = new Set(Array.isArray(selection) ? selection.map(s => s && s.id) : [])
|
||||
|
||||
this.$nextTick(() => {
|
||||
// 1) 展开/收起对应商品行
|
||||
if (table && typeof table.toggleRowExpansion === 'function') {
|
||||
try {
|
||||
groups.forEach(g => {
|
||||
const shouldExpand = selectedIdSet.has(g.id)
|
||||
table.toggleRowExpansion(g, shouldExpand)
|
||||
})
|
||||
} catch (e) { /* ignore expand error */ }
|
||||
}
|
||||
|
||||
// 2) 勾选/清空子表机器
|
||||
groups.forEach(g => {
|
||||
const list = Array.isArray(g.productMachineDtoList) ? g.productMachineDtoList : []
|
||||
const shouldSelectAll = selectedIdSet.has(g.id)
|
||||
// 先同步数据集,随后确保 UI 复选框勾选
|
||||
this.$set(this.selectedMachinesMap, g.id, shouldSelectAll ? new Set(list.map(m => m && m.id)) : new Set())
|
||||
this.applyInnerSelection(g, shouldSelectAll)
|
||||
})
|
||||
})
|
||||
},
|
||||
handleGroupSelectionChangeForShop() { /* 已移除商品层,保留空实现 */ },
|
||||
// 尝试对子表应用选择,若未渲染则重试几次
|
||||
applyInnerSelection(group, shouldSelectAll, retry = 0) {
|
||||
const inner = this.$refs['innerTable-' + group.id]
|
||||
const list = Array.isArray(group.productMachineDtoList) ? group.productMachineDtoList : []
|
||||
applyInnerSelection(shop, shouldSelectAll, retry = 0) {
|
||||
const inner = this.$refs['innerTable-' + shop.id]
|
||||
const list = Array.isArray(shop.productMachineDtoList) ? shop.productMachineDtoList : []
|
||||
if (inner && typeof inner.clearSelection === 'function') {
|
||||
try { inner.clearSelection() } catch (e) { /* ignore */ }
|
||||
if (shouldSelectAll) {
|
||||
@@ -554,11 +594,40 @@ export default {
|
||||
return
|
||||
}
|
||||
if (retry >= 5) return
|
||||
this.$nextTick(() => this.applyInnerSelection(group, shouldSelectAll, retry + 1))
|
||||
this.$nextTick(() => this.applyInnerSelection(shop, shouldSelectAll, retry + 1))
|
||||
},
|
||||
handleInnerSelectionChange(group, selections) {
|
||||
const selIds = new Set(selections.map(s => s.id))
|
||||
this.$set(this.selectedMachinesMap, group.id, selIds)
|
||||
/**
|
||||
* 根据已保存的 selectedMachinesMap 重新应用某店铺子表的勾选(仅勾选之前勾选的机器)
|
||||
*/
|
||||
applyInnerSelectionFromSet(shop, retry = 0) {
|
||||
if (!shop) return
|
||||
const inner = this.$refs['innerTable-' + shop.id]
|
||||
const list = Array.isArray(shop.productMachineDtoList) ? shop.productMachineDtoList : []
|
||||
const set = this.selectedMachinesMap[shop.id]
|
||||
if (inner && typeof inner.clearSelection === 'function') {
|
||||
try { inner.clearSelection() } catch (e) { /* ignore */ }
|
||||
if (set && set.size) {
|
||||
list.forEach(m => {
|
||||
if (set.has(m.id)) {
|
||||
try { inner.toggleRowSelection(m, true) } catch (e) { /* ignore */ }
|
||||
}
|
||||
})
|
||||
}
|
||||
return
|
||||
}
|
||||
if (retry >= 5) return
|
||||
this.$nextTick(() => this.applyInnerSelectionFromSet(shop, retry + 1))
|
||||
},
|
||||
/**
|
||||
* 在结算流程被用户中断/失败返回时,恢复列表中的勾选 UI 状态
|
||||
*/
|
||||
reapplySelectionsForPendingShop() {
|
||||
const shop = this.pendingCheckoutShop && this.pendingCheckoutShop.shop
|
||||
if (shop) this.applyInnerSelectionFromSet(shop)
|
||||
},
|
||||
handleShopInnerSelectionChange(shop, selections) {
|
||||
const selIds = new Set((selections || []).map(s => s.id))
|
||||
this.$set(this.selectedMachinesMap, shop.id, selIds)
|
||||
},
|
||||
toggleSelectAll() {
|
||||
const table = this.$refs.outerTable
|
||||
@@ -582,35 +651,60 @@ export default {
|
||||
const groups = Array.isArray(shop && shop.shoppingCartInfoDtoList) ? shop.shoppingCartInfoDtoList : []
|
||||
return groups.reduce((s, g) => s + ((Array.isArray(g.productMachineDtoList) ? g.productMachineDtoList.length : 0)), 0)
|
||||
},
|
||||
/**
|
||||
* 结算当前店铺:
|
||||
* - 如果店铺未展开,则默认包含该店铺下的全部机器进入下一步流程;
|
||||
* - 如果店铺已展开,则仅在勾选了机器的情况下进入下一步,否则给出提示。
|
||||
* @param {Record<string, any>} shop 当前店铺行
|
||||
*/
|
||||
async handleCheckoutShop(shop) {
|
||||
if (!shop) return
|
||||
const groups = Array.isArray(shop.shoppingCartInfoDtoList) ? shop.shoppingCartInfoDtoList : []
|
||||
// 仅根据当前店铺下“已选中的机器”构建下单参数:[{ leaseTime, machineId, productId, shopId }]
|
||||
const shopId = shop.id
|
||||
const machines = Array.isArray(shop.productMachineDtoList) ? shop.productMachineDtoList : []
|
||||
if (machines.length === 0) {
|
||||
this.$message({ message: '该店铺暂无可结算的机器', type: 'warning', showClose: true })
|
||||
return
|
||||
}
|
||||
|
||||
// 是否已展开:仅在已展开时校验是否有选中机器
|
||||
const isExpanded = Array.isArray(this.expandedShopKeys) && this.expandedShopKeys.includes(String(shopId))
|
||||
const payload = []
|
||||
groups.forEach(g => {
|
||||
const list = Array.isArray(g.productMachineDtoList) ? g.productMachineDtoList : []
|
||||
const selectedSet = this.selectedMachinesMap[g.id]
|
||||
if (!selectedSet || selectedSet.size === 0) return
|
||||
list.forEach(m => {
|
||||
|
||||
if (isExpanded) {
|
||||
const selectedSet = this.selectedMachinesMap[shopId]
|
||||
if (!selectedSet || selectedSet.size === 0) {
|
||||
this.$message({
|
||||
message: '请先在该店铺下勾选要结算的机器',
|
||||
type: 'warning',
|
||||
showClose: true
|
||||
})
|
||||
return
|
||||
}
|
||||
machines.forEach(m => {
|
||||
if (selectedSet.has(m.id)) {
|
||||
payload.push({
|
||||
leaseTime: Number(m.leaseTime || 1),
|
||||
machineId: m.id,
|
||||
productId: g.productId,
|
||||
shopId: shop.id
|
||||
productId: m.productId,
|
||||
shopId: shopId
|
||||
})
|
||||
}
|
||||
})
|
||||
})
|
||||
if (!payload.length) {
|
||||
this.$message({
|
||||
message: '请先在该店铺下勾选要结算的机器',
|
||||
type: 'warning',
|
||||
showClose: true
|
||||
} else {
|
||||
// 未展开:默认使用全部机器
|
||||
machines.forEach(m => {
|
||||
payload.push({
|
||||
leaseTime: Number(m.leaseTime || 1),
|
||||
machineId: m.id,
|
||||
productId: m.productId,
|
||||
shopId: shopId
|
||||
})
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
|
||||
// 先根据当前店铺请求可选支付链/币种
|
||||
await this.fetchChainAndListForSeller(shopId)
|
||||
|
||||
// 保存待结算的店铺信息,弹出购物须知弹窗
|
||||
this.pendingCheckoutShop = { shop, payload }
|
||||
this.noticeDialog.visible = true
|
||||
@@ -625,20 +719,23 @@ export default {
|
||||
this.creatingOrder = true
|
||||
try {
|
||||
const res = await this.fetchAddOrders(payload, googleCode)
|
||||
if (!res || Number(res.code) !== 200) {
|
||||
|
||||
return
|
||||
let ok = false
|
||||
if (res && Number(res.code) === 200) {
|
||||
const dataStr = String(res.data || '')
|
||||
ok = dataStr.includes('成功')
|
||||
}
|
||||
|
||||
// 检查返回数据是否包含"成功"字样
|
||||
const dataStr = String(res.data || '')
|
||||
if (dataStr.includes('成功')) {
|
||||
if (ok) {
|
||||
// 结算成功后重新获取购物车数据,更新购物车数量
|
||||
await this.fetchGetGoodsList()
|
||||
this.successDialog.visible = true
|
||||
}
|
||||
} else {
|
||||
// 失败或未成功,恢复之前的勾选 UI
|
||||
this.reapplySelectionsForPendingShop()
|
||||
}
|
||||
} catch (e) {
|
||||
console.log('网络错误,请重试')
|
||||
// 异常也恢复勾选
|
||||
this.reapplySelectionsForPendingShop()
|
||||
} finally {
|
||||
this.creatingOrder = false
|
||||
this.pendingCheckoutShop = null
|
||||
@@ -648,15 +745,15 @@ export default {
|
||||
// 若有选中机器,则以选中机器为准;否则当做选中整个商品分组
|
||||
let items = []
|
||||
if (this.selectedMachineCount) {
|
||||
this.groups.forEach(g => {
|
||||
const set = this.selectedMachinesMap[g.id]
|
||||
(this.shops || []).forEach(shop => {
|
||||
const set = this.selectedMachinesMap[shop.id]
|
||||
if (!set || set.size === 0) return
|
||||
const list = Array.isArray(g.productMachineDtoList) ? g.productMachineDtoList : []
|
||||
const list = Array.isArray(shop.productMachineDtoList) ? shop.productMachineDtoList : []
|
||||
list.forEach(m => {
|
||||
if (set.has(m.id)) {
|
||||
items.push({
|
||||
product: g.name || '',
|
||||
coin: g.coin || '',
|
||||
product: shop.name || '',
|
||||
coin: this.toUpperText(m.coin),
|
||||
machineId: m.id,
|
||||
user: m.user,
|
||||
miner: m.miner,
|
||||
@@ -665,18 +762,6 @@ export default {
|
||||
}
|
||||
})
|
||||
})
|
||||
} else if (this.selectedGroups.length) {
|
||||
items = this.selectedGroups.flatMap(g => {
|
||||
const list = Array.isArray(g.productMachineDtoList) ? g.productMachineDtoList : []
|
||||
return list.map(m => ({
|
||||
product: g.name || '',
|
||||
coin: g.coin || '',
|
||||
machineId: m.id,
|
||||
user: m.user,
|
||||
miner: m.miner,
|
||||
price: Number(m.price || 0)
|
||||
}))
|
||||
})
|
||||
} else {
|
||||
this.$message({
|
||||
message: '请先选择商品或机器',
|
||||
@@ -766,7 +851,48 @@ export default {
|
||||
return
|
||||
}
|
||||
this.noticeDialog.visible = false
|
||||
// 用户确认须知后,弹出确认结算弹窗
|
||||
// 进入下一步前,确保一次恢复(避免刚关闭后消失)
|
||||
this.$nextTick(() => this.reapplySelectionsForPendingShop())
|
||||
// 用户确认须知后,先选择支付链/币种
|
||||
this.openPaySelectDialog()
|
||||
},
|
||||
openPaySelectDialog() {
|
||||
this.payDialog.visible = true
|
||||
// 打开支付选择时再恢复一次(背景表格常被重渲染)
|
||||
this.$nextTick(() => this.reapplySelectionsForPendingShop())
|
||||
if (!Array.isArray(this.options) || !this.options.length) {
|
||||
this.fetchChainAndListForSeller()
|
||||
}
|
||||
},
|
||||
async handlePayConfirm() {
|
||||
const val = this.payDialog.value || []
|
||||
if (!Array.isArray(val) || val.length < 2) {
|
||||
this.$message.warning('请选择支付链和币种')
|
||||
return
|
||||
}
|
||||
// 关闭前先恢复一次(避免选择时表格刷新导致视觉丢勾)
|
||||
this.$nextTick(() => this.reapplySelectionsForPendingShop())
|
||||
this.selectedChain = val[0]
|
||||
this.selectedCoin = val[1]
|
||||
// USDT 不需要请求实时币价,也不需要换算
|
||||
if (String(this.selectedCoin).toUpperCase() === 'USDT') {
|
||||
this.selectedPrice = 0
|
||||
} else {
|
||||
this.payDialog.loading = true
|
||||
try {
|
||||
const res = await getCoinPrice({ coin: this.selectedCoin })
|
||||
const price = (res && (res.data && (res.data.price || res.data))) || res.price || 0
|
||||
this.selectedPrice = Number(price || 0)
|
||||
} catch (e) {
|
||||
this.selectedPrice = 0
|
||||
} finally {
|
||||
this.payDialog.loading = false
|
||||
}
|
||||
}
|
||||
this.payDialog.visible = false
|
||||
// 关闭后也再恢复一次
|
||||
this.$nextTick(() => this.reapplySelectionsForPendingShop())
|
||||
// 选择完成,进入确认明细
|
||||
this.showConfirmDialog()
|
||||
},
|
||||
// 显示确认结算弹窗
|
||||
@@ -774,32 +900,29 @@ export default {
|
||||
if (!this.pendingCheckoutShop) return
|
||||
|
||||
const { shop, payload } = this.pendingCheckoutShop
|
||||
const groups = Array.isArray(shop.shoppingCartInfoDtoList) ? shop.shoppingCartInfoDtoList : []
|
||||
// 打开确认前,确保一次恢复
|
||||
this.$nextTick(() => this.reapplySelectionsForPendingShop())
|
||||
const list = Array.isArray(shop.productMachineDtoList) ? shop.productMachineDtoList : []
|
||||
|
||||
// 通过 payload 中 machineId 来还原确认明细(仅选中的机器)
|
||||
const selectedByGroupId = new Map()
|
||||
payload.forEach(p => {
|
||||
const arr = selectedByGroupId.get(p.productId) || []
|
||||
arr.push(p.machineId)
|
||||
selectedByGroupId.set(p.productId, arr)
|
||||
})
|
||||
const selectedIds = new Set(payload.map(p => p.machineId))
|
||||
|
||||
const items = []
|
||||
groups.forEach(g => {
|
||||
const list = Array.isArray(g.productMachineDtoList) ? g.productMachineDtoList : []
|
||||
const selectedIds = new Set(selectedByGroupId.get(g.productId) || [])
|
||||
list.forEach(m => {
|
||||
if (selectedIds.has(m.id)) {
|
||||
items.push({
|
||||
product: g.name || '',
|
||||
coin: g.coin || '',
|
||||
machineId: m.id,
|
||||
user: m.user,
|
||||
miner: m.miner,
|
||||
price: Number(m.price || 0) * Number(m.leaseTime || 1)
|
||||
})
|
||||
}
|
||||
})
|
||||
list.forEach(m => {
|
||||
if (selectedIds.has(m.id)) {
|
||||
const usdtPrice = Number(m.price || 0) * Number(m.leaseTime || 1)
|
||||
const isUSDT = String(this.selectedCoin).toUpperCase() === 'USDT'
|
||||
const displayPrice = !isUSDT && this.selectedPrice > 0 ? (usdtPrice / this.selectedPrice) : usdtPrice
|
||||
items.push({
|
||||
product: shop.name || '',
|
||||
coin: this.toUpperText(m.coin),
|
||||
machineId: m.id,
|
||||
user: m.user,
|
||||
miner: m.miner,
|
||||
price: Number(displayPrice || 0)
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
this.confirmDialog.items = items
|
||||
@@ -857,6 +980,8 @@ export default {
|
||||
this.googleCodeDialog.error = ''
|
||||
this.googleCodeDialog.loading = false
|
||||
// 清除待结算信息
|
||||
// 先恢复勾选,再清空
|
||||
this.reapplySelectionsForPendingShop()
|
||||
this.pendingCheckoutShop = null
|
||||
},
|
||||
// 处理租赁天数输入变化
|
||||
@@ -927,6 +1052,9 @@ export default {
|
||||
if (!table || !product) return false
|
||||
const selectedRows = table.selection || []
|
||||
return Array.isArray(selectedRows) && selectedRows.some(r => r && r.id === product.id)
|
||||
},
|
||||
formatPayTooltip(item) {
|
||||
return `${item.payChain} - ${this.toUpperText(item.payCoin)}`
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -134,7 +134,8 @@ export default {
|
||||
console.log(res)
|
||||
if (res && res.code === 200) {
|
||||
console.log(res.data, 'res.rows');
|
||||
const list = Array.isArray(res.data) ? res.data : []
|
||||
this.paymentMethodList = res.data.payConfigList || []
|
||||
const list =res.data.machineRangeInfoList || []
|
||||
const withKeys = list.map((group, idx) => {
|
||||
const fallbackId = `grp-${idx}`
|
||||
const groupId = group.id || group.onlyKey || (group.productMachineRangeGroupDto && group.productMachineRangeGroupDto.id)
|
||||
@@ -312,6 +313,12 @@ export default {
|
||||
|
||||
// 手动切换选择(自定义checkbox与 selectedMap 同步),并维护每行的 _selected 状态
|
||||
handleManualSelect(parentRow, row, checked) {
|
||||
// 禁用:已售出或售出中的机器不可选择
|
||||
if (row && (row.saleState === 1 || row.saleState === 2)) {
|
||||
this.$message.warning('该机器已售出或售出中,无法选择')
|
||||
this.$set(row, '_selected', false)
|
||||
return
|
||||
}
|
||||
const key = parentRow.id
|
||||
const list = (this.selectedMap[key] && [...this.selectedMap[key]]) || []
|
||||
const idx = list.findIndex(it => it && it.id === row.id)
|
||||
@@ -322,8 +329,9 @@ export default {
|
||||
},
|
||||
|
||||
// 为子表中已在购物车的行添加只读样式,并阻止点击取消
|
||||
getInnerRowClass() {
|
||||
return ''
|
||||
handleGetInnerRowClass({ row }) {
|
||||
if (!row) return ''
|
||||
return (row.saleState === 1 || row.saleState === 2) ? 'sold-row' : ''
|
||||
},
|
||||
|
||||
/**
|
||||
@@ -410,11 +418,15 @@ export default {
|
||||
handleOpenAddToCartDialog() {
|
||||
// 扫描当前所有系列下被勾选的机器
|
||||
const groups = Array.isArray(this.productListData) ? this.productListData : []
|
||||
const picked = groups.flatMap(g => Array.isArray(g.productMachines) ? g.productMachines.filter(m => !!m && !!m._selected) : [])
|
||||
const pickedAll = groups.flatMap(g => Array.isArray(g.productMachines) ? g.productMachines.filter(m => !!m && !!m._selected) : [])
|
||||
const picked = pickedAll.filter(m => m && (m.saleState === 0 || m.saleState === undefined || m.saleState === null))
|
||||
if (!picked.length) {
|
||||
this.$message.warning('请先勾选至少一台矿机')
|
||||
return
|
||||
}
|
||||
if (picked.length < pickedAll.length) {
|
||||
this.$message.warning('部分机器已售出或售出中,已自动为您排除')
|
||||
}
|
||||
// 使用弹窗中的固定快照,避免后续清空勾选影响弹窗显示
|
||||
this.confirmAddDialog.items = picked.slice()
|
||||
this.confirmAddDialog.visible = true
|
||||
|
||||
@@ -7,10 +7,35 @@
|
||||
|
||||
<div v-else-if="product" class="detail-container">
|
||||
<h2 style="margin: 10px; text-align: left;margin-top: 28px;">商品详情-选择矿机</h2>
|
||||
<section class="pay-methods" aria-label="支付方式">
|
||||
<div class="pay-label" tabindex="0" aria-label="支付方式标签">支付方式:</div>
|
||||
<ul class="pay-list" role="list" aria-label="支付方式列表">
|
||||
<li
|
||||
v-for="(item, index) in paymentMethodList"
|
||||
:key="index"
|
||||
class="pay-item"
|
||||
:aria-label="`支付方式: ${item.payChain}`"
|
||||
>
|
||||
<el-tooltip :content="formatPayTooltip(item)" placement="top" :open-delay="80">
|
||||
<img
|
||||
class="pay-icon"
|
||||
:src="item.payCoinImage"
|
||||
:alt="`${item.payChain} 支付`"
|
||||
:title="item.payChain"
|
||||
tabindex="0"
|
||||
role="img"
|
||||
@keydown.enter.prevent="handlePayIconKeyDown(item)"
|
||||
@keydown.space.prevent="handlePayIconKeyDown(item)"
|
||||
/>
|
||||
</el-tooltip>
|
||||
</li>
|
||||
</ul>
|
||||
</section>
|
||||
<section class="productList">
|
||||
<!-- 产品列表(可展开) -->
|
||||
<el-table
|
||||
ref="seriesTable"
|
||||
class="series-table"
|
||||
:data="productListData"
|
||||
row-key="id"
|
||||
:expand-row-keys="expandedRowKeys"
|
||||
@@ -24,57 +49,78 @@
|
||||
<el-table-column type="expand" width="46">
|
||||
<template #default="outer">
|
||||
<!-- 子表格:展开后显示该行的多个可选条目(来自 productMachines) -->
|
||||
<el-table :data="outer.row.productMachines" size="small" style="width: 100%" :show-header="true" :ref="'innerTable-' + outer.row.id" :row-key="'id'" :reserve-selection="false" :header-cell-style="{ textAlign: 'left' }" :cell-style="{ textAlign: 'left' }">
|
||||
<el-table :data="outer.row.productMachines" size="small" style="width: 100%" :show-header="true" :ref="'innerTable-' + outer.row.id" :row-key="'id'" :reserve-selection="false" :header-cell-style="{ textAlign: 'left' }" :cell-style="{ textAlign: 'left' }" :row-class-name="handleGetInnerRowClass">
|
||||
<el-table-column width="46">
|
||||
<template #default="scope">
|
||||
<el-checkbox v-model="scope.row._selected" @change="checked => handleManualSelect(outer.row, scope.row, checked)" />
|
||||
<el-checkbox
|
||||
v-model="scope.row._selected"
|
||||
:disabled="scope.row.saleState === 1 || scope.row.saleState === 2"
|
||||
:title="(scope.row.saleState === 1 || scope.row.saleState === 2) ? '该机器已售出或售出中,无法选择' : ''"
|
||||
@change="checked => handleManualSelect(outer.row, scope.row, checked)"
|
||||
/>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<!-- 调整列顺序,使“理论算力”“实际算力”与上层对齐,并且指定相同宽度 -->
|
||||
<el-table-column prop="theoryPower" label="理论算力" width="280" header-align="left" align="left" />
|
||||
<el-table-column label="实际算力" width="230" header-align="left" align="left">
|
||||
<!-- 列宽精简,避免横向滚动 -->
|
||||
<el-table-column prop="theoryPower" label="理论算力" min-width="160" header-align="left" align="left" show-overflow-tooltip>
|
||||
<template #default="scope">{{ scope.row.theoryPower }} {{ scope.row.unit }}</template>
|
||||
</el-table-column>
|
||||
|
||||
<el-table-column label="实际算力" min-width="160" header-align="left" align="left" show-overflow-tooltip>
|
||||
<template #default="scope">{{ scope.row.computingPower }} {{ scope.row.unit }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="powerDissipation" label="功耗(kw/h)" width="230" header-align="left" align="left" />
|
||||
<el-table-column prop="algorithm" label="算法" width="180" header-align="left" align="left" />
|
||||
<el-table-column prop="powerDissipation" label="功耗(kw/h)" min-width="140" header-align="left" align="left" />
|
||||
<el-table-column prop="algorithm" label="算法" min-width="120" header-align="left" align="left" />
|
||||
|
||||
<el-table-column prop="theoryIncome" width="220" header-align="left" align="left">
|
||||
<template #header>单机理论收入(每日) <span v-show="outer.row.coin">({{ outer.row.coin || '' }})</span></template>
|
||||
<el-table-column prop="theoryIncome" min-width="160" header-align="left" align="left" show-overflow-tooltip>
|
||||
<template #header>单机理论收入(每日) <span v-show="outer.row.productMachines[0].coin">({{outer.row.productMachines[0].coin.toUpperCase() }})</span></template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="theoryUsdtIncome" label="单机理论收入(每日/USDT)" width="240" header-align="left" align="left" />
|
||||
<el-table-column prop="theoryUsdtIncome" label="单机理论收入(每日/USDT)" min-width="170" header-align="left" align="left" />
|
||||
<!-- 矿机型号置于最后,不影响上层对齐 -->
|
||||
<el-table-column prop="type" label="矿机型号" header-align="left" align="left" />
|
||||
<el-table-column label="租赁天数(天)" width="200" header-align="left" align="left">
|
||||
<el-table-column prop="type" label="矿机型号" header-align="left" align="left" min-width="120" />
|
||||
<el-table-column label="最大可租赁(天)" min-width="140" header-align="left" align="left">
|
||||
<template #default="scope">{{ getRowMaxLeaseDays(scope.row) }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="租赁天数(天)" min-width="150" header-align="left" align="left">
|
||||
<template #default="scope">
|
||||
<el-input-number
|
||||
v-model="scope.row.leaseTime"
|
||||
:min="1"
|
||||
:max="365"
|
||||
:max="getRowMaxLeaseDays(scope.row)"
|
||||
:step="1"
|
||||
:precision="0"
|
||||
size="mini"
|
||||
:disabled="scope.row.saleState === 1 || scope.row.saleState === 2"
|
||||
controls-position="right"
|
||||
@change="val => handleLeaseDaysChange(scope.row, val)"
|
||||
/>
|
||||
</template>
|
||||
</el-table-column>
|
||||
|
||||
<el-table-column prop="saleState" label="售出状态" header-align="left" align="left" min-width="110">
|
||||
<template #default="scope">
|
||||
<el-tag :type="scope.row.saleState === 0 ? 'info' : (scope.row.saleState === 1 ? 'danger' : 'warning')">
|
||||
{{ scope.row.saleState === 0 ? '未售出' : (scope.row.saleState === 1 ? '已售出' : '售出中') }}
|
||||
</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
</template>
|
||||
</el-table-column>
|
||||
|
||||
<!-- 外层与内层列宽保持一致:160/160/160/120/160 -->
|
||||
<el-table-column label="价格" header-align="left" align="left">
|
||||
<template slot-scope="scope">{{ scope.row.productMachineRangeGroupDto && scope.row.productMachineRangeGroupDto.price }}</template>
|
||||
<!-- 外层列宽同样收紧,避免横向滚动 -->
|
||||
<el-table-column label="价格 (USDT)" header-align="left" align="left" min-width="120">
|
||||
<template slot-scope="scope">{{ scope.row.productMachineRangeGroupDto && scope.row.productMachineRangeGroupDto.price }} </template>
|
||||
</el-table-column>
|
||||
|
||||
<el-table-column label="理论算力范围" width="280" header-align="left" align="left">
|
||||
<el-table-column label="理论算力范围" min-width="220" header-align="left" align="left" show-overflow-tooltip>
|
||||
<template slot-scope="scope">{{ scope.row.productMachineRangeGroupDto && scope.row.productMachineRangeGroupDto.theoryPowerRange }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="实际算力范围" width="230" header-align="left" align="left">
|
||||
<el-table-column label="实际算力范围" min-width="200" header-align="left" align="left" show-overflow-tooltip>
|
||||
<template slot-scope="scope">{{ scope.row.productMachineRangeGroupDto && scope.row.productMachineRangeGroupDto.computingPowerRange }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="功耗范围" width="230" header-align="left" align="left">
|
||||
<el-table-column label="功耗范围" min-width="160" header-align="left" align="left">
|
||||
<template slot-scope="scope">{{ scope.row.productMachineRangeGroupDto && scope.row.productMachineRangeGroupDto.powerRange }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="数量" width="180" header-align="left" align="left">
|
||||
<el-table-column label="数量" min-width="100" header-align="left" align="left">
|
||||
<template slot-scope="scope">{{ scope.row.productMachineRangeGroupDto && scope.row.productMachineRangeGroupDto.number }}</template>
|
||||
</el-table-column>
|
||||
|
||||
@@ -125,7 +171,66 @@ import Index from './index'
|
||||
export default {
|
||||
name: 'ProductDetail',
|
||||
mixins: [Index],
|
||||
|
||||
methods: {
|
||||
/**
|
||||
* 获取行的最大可租赁天数
|
||||
* 规则:优先 row.maxLeaseDays;否则回退 365;保证区间 [1, 365]
|
||||
*/
|
||||
getRowMaxLeaseDays(row) {
|
||||
const raw = (row && (row.maxLeaseDays || row.maxLeaseDay || row.max_lease_days)) || 365
|
||||
const n = Number(raw)
|
||||
if (!Number.isFinite(n)) return 365
|
||||
if (n < 1) return 1
|
||||
if (n > 365) return 365
|
||||
return Math.floor(n)
|
||||
},
|
||||
/**
|
||||
* 限制并校验租赁天数:区间 [1, max],并取整
|
||||
*/
|
||||
handleLeaseDaysChange(row, value) {
|
||||
const max = this.getRowMaxLeaseDays(row)
|
||||
let v = Number(value)
|
||||
if (!Number.isFinite(v)) v = 1
|
||||
if (v < 1) v = 1
|
||||
if (v > max) v = max
|
||||
v = Math.floor(v)
|
||||
this.$set(row, 'leaseTime', v)
|
||||
},
|
||||
/**
|
||||
* 组合 tooltip 展示内容:payChain - payCoin
|
||||
* @param {Object} item - 支付方式对象
|
||||
* @returns {string}
|
||||
*/
|
||||
formatPayTooltip(item) {
|
||||
try {
|
||||
if (!item) return ''
|
||||
const chain = (item.payChain || '').toString().trim()
|
||||
const coin = (item.payCoin || '').toString().trim()
|
||||
if (chain && coin) return `${chain} - ${coin}`
|
||||
return chain || coin || ''
|
||||
} catch (err) {
|
||||
// eslint-disable-next-line no-console
|
||||
console.error('formatPayTooltip error:', err)
|
||||
return ''
|
||||
}
|
||||
},
|
||||
/**
|
||||
* 支付方式图标键盘可达性处理
|
||||
* @param {Object} item - 支付方式对象
|
||||
*/
|
||||
handlePayIconKeyDown(item) {
|
||||
// 仅用于可达性保障与监控打点,避免无事件时的报错
|
||||
try {
|
||||
if (!item) return;
|
||||
// 简单输出,后续可接入埋点系统
|
||||
// eslint-disable-next-line no-console
|
||||
console.debug('[pay-icon-keydown]', item.payChain);
|
||||
} catch (err) {
|
||||
// eslint-disable-next-line no-console
|
||||
console.error('handlePayIconKeyDown error:', err);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
@@ -146,6 +251,11 @@ export default {
|
||||
border-color: #dcdfe6;
|
||||
}
|
||||
|
||||
/* 内层表:已售出/售出中行禁用态高亮 */
|
||||
:deep(.sold-row) {
|
||||
background: #fff5f5;
|
||||
}
|
||||
|
||||
|
||||
.loading {
|
||||
text-align: center;
|
||||
@@ -240,10 +350,89 @@ export default {
|
||||
color: #e74c3c;
|
||||
}
|
||||
|
||||
/* 外层系列行:整行可点击的视觉提示 */
|
||||
/* 支付方式区域(视觉更友好 + 可达性) */
|
||||
.pay-methods {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
padding: 12px 16px;
|
||||
margin: 8px 10px 16px 10px;
|
||||
background: #f8fafc;
|
||||
border: 1px solid #eef2f7;
|
||||
border-radius: 8px;
|
||||
}
|
||||
|
||||
.pay-label {
|
||||
color: #34495e;
|
||||
font-size: 14px;
|
||||
font-weight: 600;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.pay-list {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
flex-wrap: wrap;
|
||||
gap: 10px 12px;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
list-style: none;
|
||||
}
|
||||
|
||||
.pay-item {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.pay-icon {
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
display: block;
|
||||
border-radius: 4px;
|
||||
transition: transform 0.15s ease, box-shadow 0.15s ease;
|
||||
}
|
||||
|
||||
.pay-icon:hover {
|
||||
transform: translateY(-1px);
|
||||
}
|
||||
|
||||
.pay-icon:focus {
|
||||
outline: none;
|
||||
box-shadow: 0 0 0 3px rgba(25, 118, 210, 0.2);
|
||||
}
|
||||
|
||||
/* 外层系列行:整行可点击 + 视觉增强 */
|
||||
:deep(.series-clickable-row) {
|
||||
cursor: pointer;
|
||||
}
|
||||
:deep(.series-clickable-row > td) {
|
||||
background: #f9fbff; /* 淡蓝背景区分层级 */
|
||||
padding-top: 14px; /* 增加行高 */
|
||||
padding-bottom: 14px;
|
||||
border-bottom: 1px solid #eef2f7; /* 更柔和的分割线 */
|
||||
}
|
||||
:deep(.series-clickable-row:hover > td) {
|
||||
background: #f0f6ff; /* 悬浮更亮一些 */
|
||||
}
|
||||
/* 展开的内层区域容器样式:与外层形成卡片层级 */
|
||||
:deep(.el-table__expanded-cell) {
|
||||
background: #ffffff;
|
||||
}
|
||||
:deep(.el-table__expanded-cell .el-table) {
|
||||
background: #fff;
|
||||
border: 1px solid #eef2f7;
|
||||
border-radius: 8px;
|
||||
/* 优化:表格内容自适应宽度,减少横向滚动 */
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
/* 列表标题上色:与上方支付方式背景做区分 */
|
||||
/* 仅给最外层系列表头上色;内层子表不受影响 */
|
||||
.series-table :deep(.el-table__header th) {
|
||||
background: #f9fbff;
|
||||
color: #34495e;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.quantity-section {
|
||||
display: flex;
|
||||
@@ -251,6 +440,9 @@ export default {
|
||||
gap: 16px;
|
||||
}
|
||||
|
||||
/* 租赁天数单元格:提示 + 输入并排 */
|
||||
/* 已移除行内提示样式,保留空位便于将来复用(不影响现有布局) */
|
||||
|
||||
.quantity-label {
|
||||
font-size: 16px;
|
||||
color: #666;
|
||||
|
||||
@@ -59,9 +59,11 @@
|
||||
<h4>商品: {{ product.name }}</h4>
|
||||
<p style="font-size: 16px;margin-top: 10px;font-weight: bold;">算法: {{ product.algorithm }}</p>
|
||||
<div class="product-footer">
|
||||
|
||||
<span class="product-price">价格: {{ formatPriceRange(product.priceRange) }}</span> <span style="color: #999;font-size: 12px;">USDT</span>
|
||||
|
||||
<div class="price-wrap">
|
||||
<span class="product-price">价格: {{ formatPriceRange(product.priceRange) }}</span>
|
||||
<span class="unit">USDT</span>
|
||||
</div>
|
||||
<span class="product-sold" aria-label="已售数量">已售:{{ product && product.saleNumber != null ? product.saleNumber : 0 }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -176,9 +178,9 @@ export default {
|
||||
width: 100%;
|
||||
}
|
||||
.product-footer {
|
||||
// display: flex;
|
||||
// justify-content: space-between;
|
||||
// align-items: center;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-top: 8px;
|
||||
}
|
||||
.product-price {
|
||||
@@ -189,6 +191,9 @@ export default {
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
.price-wrap { display: inline-flex; align-items: baseline; gap: 6px; }
|
||||
.unit { color: #999; font-size: 12px; }
|
||||
.product-sold { color: #64748b; font-size: 12px; }
|
||||
.add-cart-btn {
|
||||
background: #42b983;
|
||||
color: #fff;
|
||||
|
||||
Reference in New Issue
Block a user