结算逻辑修改完成,起付额判定待处理
This commit is contained in:
38
power_leasing/src/utils/amount.js
Normal file
38
power_leasing/src/utils/amount.js
Normal file
@@ -0,0 +1,38 @@
|
||||
// 金额截断显示工具(不补0、不四舍五入)
|
||||
// 规则:
|
||||
// - USDT: 最多6位小数
|
||||
// - ETH: 最多8位小数
|
||||
// - 其他币种: 最多6位小数
|
||||
// 返回 { text, truncated, full }
|
||||
|
||||
export function getMaxDecimalsByCoin(coin) {
|
||||
const c = (coin || '').toString().toUpperCase();
|
||||
if (c === 'ETH') return 8;
|
||||
// USDT 与其他币种都按 6 位
|
||||
return 6;
|
||||
}
|
||||
|
||||
export function truncateAmountRaw(value, maxDecimals) {
|
||||
if (value === null || value === undefined) {
|
||||
return { text: '0', truncated: false, full: '0' };
|
||||
}
|
||||
const raw = String(value);
|
||||
if (!raw) return { text: '0', truncated: false, full: '0' };
|
||||
// 非数字字符串直接返回原值
|
||||
if (!/^-?\d+(\.\d+)?$/.test(raw)) {
|
||||
return { text: raw, truncated: false, full: raw };
|
||||
}
|
||||
const isNegative = raw.startsWith('-');
|
||||
const abs = isNegative ? raw.slice(1) : raw;
|
||||
const [intPart, decPart = ''] = abs.split('.');
|
||||
const keep = decPart.slice(0, Math.max(0, maxDecimals));
|
||||
const truncated = decPart.length > maxDecimals;
|
||||
const text = (isNegative ? '-' : '') + (keep ? `${intPart}.${keep}` : intPart);
|
||||
return { text, truncated, full: raw };
|
||||
}
|
||||
|
||||
export function truncateAmountByCoin(value, coin) {
|
||||
const max = getMaxDecimalsByCoin(coin);
|
||||
return truncateAmountRaw(value, max);
|
||||
}
|
||||
|
||||
@@ -10,7 +10,23 @@
|
||||
<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">
|
||||
<template #default="scope">
|
||||
<span class="value strong">
|
||||
<el-tooltip
|
||||
v-if="formatAmount(scope.row.price, scope.row.payCoin || 'USDT').truncated"
|
||||
:content="formatAmount(scope.row.price, scope.row.payCoin || 'USDT').full"
|
||||
placement="top"
|
||||
>
|
||||
<span>
|
||||
{{ formatAmount(scope.row.price, scope.row.payCoin || 'USDT').text }}
|
||||
<i class="el-icon-more amount-more"></i>
|
||||
</span>
|
||||
</el-tooltip>
|
||||
<span v-else>{{ formatAmount(scope.row.price, scope.row.payCoin || 'USDT').text }}</span>
|
||||
</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
</template>
|
||||
</el-table-column>
|
||||
@@ -26,7 +42,21 @@
|
||||
<template #default="scope">{{ Array.isArray(scope.row && scope.row.orderItemDtoList) ? scope.row.orderItemDtoList.length : 0 }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="总金额(USDT)" min-width="140">
|
||||
<template #default="scope"><span class="value strong">{{ (scope.row && scope.row.totalPrice) != null ? scope.row.totalPrice : '—' }}</span></template>
|
||||
<template #default="scope">
|
||||
<span class="value strong">
|
||||
<el-tooltip
|
||||
v-if="formatAmount(scope.row && scope.row.totalPrice, 'USDT').truncated"
|
||||
:content="formatAmount(scope.row && scope.row.totalPrice, 'USDT').full"
|
||||
placement="top"
|
||||
>
|
||||
<span>
|
||||
{{ formatAmount(scope.row && scope.row.totalPrice, 'USDT').text }}
|
||||
<i class="el-icon-more amount-more"></i>
|
||||
</span>
|
||||
</el-tooltip>
|
||||
<span v-else>{{ formatAmount(scope.row && scope.row.totalPrice, 'USDT').text }}</span>
|
||||
</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column min-width="180">
|
||||
<template #header>
|
||||
@@ -43,11 +73,37 @@
|
||||
</el-tooltip>
|
||||
</template>
|
||||
<template #default="scope">
|
||||
<span class="value strong">{{ (scope.row && scope.row.payAmount) != null ? scope.row.payAmount : '—' }}</span>
|
||||
<span class="value strong">
|
||||
<el-tooltip
|
||||
v-if="formatAmount(scope.row && scope.row.payAmount, 'USDT').truncated"
|
||||
:content="formatAmount(scope.row && scope.row.payAmount, 'USDT').full"
|
||||
placement="top"
|
||||
>
|
||||
<span>
|
||||
{{ formatAmount(scope.row && scope.row.payAmount, 'USDT').text }}
|
||||
<i class="el-icon-more amount-more"></i>
|
||||
</span>
|
||||
</el-tooltip>
|
||||
<span v-else>{{ formatAmount(scope.row && scope.row.payAmount, 'USDT').text }}</span>
|
||||
</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="待支付金额(USDT)" min-width="140">
|
||||
<template #default="scope"><span class="value strong">{{ (scope.row && scope.row.noPayAmount) != null ? scope.row.noPayAmount : '—' }}</span></template>
|
||||
<template #default="scope">
|
||||
<span class="value strong">
|
||||
<el-tooltip
|
||||
v-if="formatAmount(scope.row && scope.row.noPayAmount, 'USDT').truncated"
|
||||
:content="formatAmount(scope.row && scope.row.noPayAmount, 'USDT').full"
|
||||
placement="top"
|
||||
>
|
||||
<span>
|
||||
{{ formatAmount(scope.row && scope.row.noPayAmount, 'USDT').text }}
|
||||
<i class="el-icon-more amount-more"></i>
|
||||
</span>
|
||||
</el-tooltip>
|
||||
<span v-else>{{ formatAmount(scope.row && scope.row.noPayAmount, 'USDT').text }}</span>
|
||||
</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="操作" min-width="280" fixed="right">
|
||||
<template #default="scope">
|
||||
@@ -79,7 +135,21 @@
|
||||
|
||||
<el-dialog :visible.sync="dialogVisible" width="520px" title="请扫码支付">
|
||||
<div style="text-align:left; margin-bottom:12px; color:#666;">
|
||||
<div style="margin-bottom:6px;">总金额(USDT):<b>{{ paymentDialog.totalPrice }}</b></div>
|
||||
<div style="margin-bottom:6px;">总金额(USDT):
|
||||
<b>
|
||||
<el-tooltip
|
||||
v-if="formatAmount(paymentDialog.totalPrice, 'USDT').truncated"
|
||||
:content="formatAmount(paymentDialog.totalPrice, 'USDT').full"
|
||||
placement="top"
|
||||
>
|
||||
<span>
|
||||
{{ formatAmount(paymentDialog.totalPrice, 'USDT').text }}
|
||||
<i class="el-icon-more amount-more"></i>
|
||||
</span>
|
||||
</el-tooltip>
|
||||
<span v-else>{{ formatAmount(paymentDialog.totalPrice, 'USDT').text }}</span>
|
||||
</b>
|
||||
</div>
|
||||
<div style="margin-bottom:6px;display:flex;align-items:center;gap:6px;">
|
||||
<el-tooltip placement="top" effect="dark">
|
||||
<div slot="content">
|
||||
@@ -90,9 +160,35 @@
|
||||
<i class="el-icon-question" style="color:#909399;" aria-label="说明" role="img"></i>
|
||||
</el-tooltip>
|
||||
<span>已支付金额(USDT):</span>
|
||||
<b class="value strong">{{ paymentDialog.payAmount }}</b>
|
||||
<b class="value strong">
|
||||
<el-tooltip
|
||||
v-if="formatAmount(paymentDialog.payAmount, 'USDT').truncated"
|
||||
:content="formatAmount(paymentDialog.payAmount, 'USDT').full"
|
||||
placement="top"
|
||||
>
|
||||
<span>
|
||||
{{ formatAmount(paymentDialog.payAmount, 'USDT').text }}
|
||||
<i class="el-icon-more amount-more"></i>
|
||||
</span>
|
||||
</el-tooltip>
|
||||
<span v-else>{{ formatAmount(paymentDialog.payAmount, 'USDT').text }}</span>
|
||||
</b>
|
||||
</div>
|
||||
<div style="margin-bottom:6px;">待支付金额(USDT):
|
||||
<b class="value strong">
|
||||
<el-tooltip
|
||||
v-if="formatAmount(paymentDialog.noPayAmount, 'USDT').truncated"
|
||||
:content="formatAmount(paymentDialog.noPayAmount, 'USDT').full"
|
||||
placement="top"
|
||||
>
|
||||
<span>
|
||||
{{ formatAmount(paymentDialog.noPayAmount, 'USDT').text }}
|
||||
<i class="el-icon-more amount-more"></i>
|
||||
</span>
|
||||
</el-tooltip>
|
||||
<span v-else>{{ formatAmount(paymentDialog.noPayAmount, 'USDT').text }}</span>
|
||||
</b>
|
||||
</div>
|
||||
<div style="margin-bottom:6px;">待支付金额(USDT):<b class="value strong">{{ paymentDialog.noPayAmount }}</b></div>
|
||||
<!-- <div style="word-break:break-all;">收款地址:<code>{{ orderDialog.address }}</code></div> -->
|
||||
</div>
|
||||
<div style="text-align:center;">
|
||||
@@ -111,6 +207,7 @@
|
||||
|
||||
<script>
|
||||
import { addOrders } from '../../api/order'
|
||||
import { truncateAmountByCoin } from '../../utils/amount'
|
||||
export default {
|
||||
name: 'OrderList',
|
||||
props: {
|
||||
@@ -133,6 +230,9 @@ export default {
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
formatAmount(value, coin) {
|
||||
return truncateAmountByCoin(value, coin)
|
||||
},
|
||||
buildQrSrc(img) {
|
||||
if (!img) return ''
|
||||
try { const s = String(img).trim(); return s.startsWith('data:') ? s : `data:image/png;base64,${s}` } catch (e) { return '' }
|
||||
@@ -244,6 +344,7 @@ export default {
|
||||
.empty { color: #888; padding: 24px; text-align: center; }
|
||||
.value.mono { font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace; word-break: break-all; }
|
||||
.value.strong { font-weight: 700; color: #e74c3c; }
|
||||
.amount-more { font-size: 12px; color: #94a3b8; margin-left: 4px; }
|
||||
</style>
|
||||
|
||||
|
||||
|
||||
@@ -13,7 +13,21 @@
|
||||
<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">+ {{ formatDec6(row.amount) }} {{ (row.fromSymbol || 'USDT').toUpperCase() }}</div>
|
||||
<div class="amount">
|
||||
<el-tooltip
|
||||
v-if="formatAmount(row.amount, row.fromSymbol).truncated"
|
||||
:content="`${formatAmount(row.amount, row.fromSymbol).full} ${(row.fromSymbol || 'USDT').toUpperCase()}`"
|
||||
placement="top"
|
||||
>
|
||||
<span>
|
||||
+ {{ formatAmount(row.amount, row.fromSymbol).text }} {{ (row.fromSymbol || 'USDT').toUpperCase() }}
|
||||
<i class="el-icon-more amount-more"></i>
|
||||
</span>
|
||||
</el-tooltip>
|
||||
<span v-else>
|
||||
+ {{ formatAmount(row.amount, row.fromSymbol).text }} {{ (row.fromSymbol || 'USDT').toUpperCase() }}
|
||||
</span>
|
||||
</div>
|
||||
<div class="chain">{{ formatChain(row.fromChain) }}</div>
|
||||
</div>
|
||||
<div class="item-right">
|
||||
@@ -54,7 +68,21 @@
|
||||
<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">- {{ formatDec6(row.amount) }} {{ (row.toSymbol || 'USDT').toUpperCase() }}</div>
|
||||
<div class="amount">
|
||||
<el-tooltip
|
||||
v-if="formatAmount(row.amount, row.toSymbol).truncated"
|
||||
:content="`${formatAmount(row.amount, row.toSymbol).full} ${(row.toSymbol || 'USDT').toUpperCase()}`"
|
||||
placement="top"
|
||||
>
|
||||
<span>
|
||||
- {{ formatAmount(row.amount, row.toSymbol).text }} {{ (row.toSymbol || 'USDT').toUpperCase() }}
|
||||
<i class="el-icon-more amount-more"></i>
|
||||
</span>
|
||||
</el-tooltip>
|
||||
<span v-else>
|
||||
- {{ formatAmount(row.amount, row.toSymbol).text }} {{ (row.toSymbol || 'USDT').toUpperCase() }}
|
||||
</span>
|
||||
</div>
|
||||
<div class="chain">{{ formatChain(row.toChain) }}</div>
|
||||
</div>
|
||||
<div class="item-right">
|
||||
@@ -95,7 +123,21 @@
|
||||
<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">- {{ formatDec6(row.realAmount) }} {{ (row.fromSymbol || 'USDT').toUpperCase() }}</div>
|
||||
<div class="amount">
|
||||
<el-tooltip
|
||||
v-if="formatAmount(row.realAmount, row.fromSymbol).truncated"
|
||||
:content="`${formatAmount(row.realAmount, row.fromSymbol).full} ${(row.fromSymbol || 'USDT').toUpperCase()}`"
|
||||
placement="top"
|
||||
>
|
||||
<span>
|
||||
- {{ formatAmount(row.realAmount, row.fromSymbol).text }} {{ (row.fromSymbol || 'USDT').toUpperCase() }}
|
||||
<i class="el-icon-more amount-more"></i>
|
||||
</span>
|
||||
</el-tooltip>
|
||||
<span v-else>
|
||||
- {{ formatAmount(row.realAmount, row.fromSymbol).text }} {{ (row.fromSymbol || 'USDT').toUpperCase() }}
|
||||
</span>
|
||||
</div>
|
||||
<div class="chain">{{ formatChain(row.fromChain) }}</div>
|
||||
</div>
|
||||
<div class="item-right">
|
||||
@@ -140,6 +182,7 @@
|
||||
|
||||
<script>
|
||||
import { transactionRecord } from '../../api/wallet'
|
||||
import { truncateAmountByCoin } from '../../utils/amount'
|
||||
|
||||
export default {
|
||||
name: 'AccountFundsFlow',
|
||||
@@ -254,6 +297,12 @@ export default {
|
||||
this.loadList()
|
||||
},
|
||||
methods: {
|
||||
/**
|
||||
* 金额格式化(不补0、不四舍五入)
|
||||
*/
|
||||
formatAmount(value, coin) {
|
||||
return truncateAmountByCoin(value, coin)
|
||||
},
|
||||
/**
|
||||
* 处理 Tab 切换:清空展开状态,确保手风琴行为
|
||||
* @param {any} pane - 当前 pane(Element UI 传入)
|
||||
@@ -399,22 +448,7 @@ export default {
|
||||
* @param {number|string} value
|
||||
* @returns {string}
|
||||
*/
|
||||
formatDec6(value) {
|
||||
if (value === null || value === undefined || value === '') return '0'
|
||||
let s = String(value)
|
||||
// 展开科学计数法为普通小数,避免 1e-7 之类展示
|
||||
if (/e/i.test(s)) {
|
||||
const n = Number(value)
|
||||
if (!Number.isFinite(n)) return '0'
|
||||
s = n.toFixed(20).replace(/\.0+$/, '').replace(/(\.\d*?)0+$/, '$1')
|
||||
}
|
||||
const m = s.match(/^(-?)(\d+)(?:\.(\d+))?$/)
|
||||
if (!m) return s
|
||||
let intPart = m[2]
|
||||
let decPart = m[3] || ''
|
||||
if (decPart.length > 6) decPart = decPart.slice(0, 6)
|
||||
return decPart ? `${intPart}.${decPart}` : intPart
|
||||
},
|
||||
// 删除旧的 formatDec6,统一使用 formatAmount
|
||||
handleSizeChange(val) {
|
||||
console.log(`每页 ${val} 条`);
|
||||
this.pagination.pageSize = val;
|
||||
@@ -517,6 +551,7 @@ export default {
|
||||
.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; }
|
||||
.amount-more { font-size: 12px; color: #94a3b8; margin-left: 4px; }
|
||||
</style>
|
||||
|
||||
|
||||
|
||||
@@ -756,12 +756,13 @@ export default {
|
||||
if (firstDot !== -1) {
|
||||
v = v.slice(0, firstDot + 1) + v.slice(firstDot + 1).replace(/\./g, '')
|
||||
}
|
||||
const endsWithDot = v.endsWith('.')
|
||||
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
|
||||
v = decPart.length ? `${intPart}.${decPart}` : (endsWithDot ? `${intPart}.` : intPart)
|
||||
this.$set(this.selectedMachineRows[index], 'powerDissipation', v)
|
||||
},
|
||||
/**
|
||||
@@ -785,12 +786,13 @@ export default {
|
||||
if (firstDot !== -1) {
|
||||
v = v.slice(0, firstDot + 1) + v.slice(firstDot + 1).replace(/\./g, '')
|
||||
}
|
||||
const endsWithDot = v.endsWith('.')
|
||||
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
|
||||
v = decPart.length ? `${intPart}.${decPart}` : (endsWithDot ? `${intPart}.` : intPart)
|
||||
this.$set(this.selectedMachineRows[index], 'theoryPower', v)
|
||||
},
|
||||
/**
|
||||
|
||||
@@ -58,13 +58,45 @@
|
||||
<el-table-column
|
||||
prop="estimatedEndIncome"
|
||||
label="预计总收益"
|
||||
min-width="120"
|
||||
/>
|
||||
min-width="140"
|
||||
>
|
||||
<template #default="scope">
|
||||
<span class="value strong">
|
||||
<el-tooltip
|
||||
v-if="formatAmount(scope.row.estimatedEndIncome, scope.row.coin || 'USDT').truncated"
|
||||
:content="formatAmount(scope.row.estimatedEndIncome, scope.row.coin || 'USDT').full"
|
||||
placement="top"
|
||||
>
|
||||
<span>
|
||||
{{ formatAmount(scope.row.estimatedEndIncome, scope.row.coin || 'USDT').text }}
|
||||
<i class="el-icon-more amount-more"></i>
|
||||
</span>
|
||||
</el-tooltip>
|
||||
<span v-else>{{ formatAmount(scope.row.estimatedEndIncome, scope.row.coin || 'USDT').text }}</span>
|
||||
</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column
|
||||
prop="estimatedEndUsdtIncome"
|
||||
label="预计USDT总收益"
|
||||
min-width="160"
|
||||
/>
|
||||
>
|
||||
<template #default="scope">
|
||||
<span class="value strong">
|
||||
<el-tooltip
|
||||
v-if="formatAmount(scope.row.estimatedEndUsdtIncome, 'USDT').truncated"
|
||||
:content="formatAmount(scope.row.estimatedEndUsdtIncome, 'USDT').full"
|
||||
placement="top"
|
||||
>
|
||||
<span>
|
||||
{{ formatAmount(scope.row.estimatedEndUsdtIncome, 'USDT').text }}
|
||||
<i class="el-icon-more amount-more"></i>
|
||||
</span>
|
||||
</el-tooltip>
|
||||
<span v-else>{{ formatAmount(scope.row.estimatedEndUsdtIncome, 'USDT').text }}</span>
|
||||
</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="startTime" label="开始时间" min-width="160" >
|
||||
<template #default="scope">
|
||||
<span>{{ formatDateTime(scope.row.startTime) }}</span>
|
||||
@@ -109,6 +141,7 @@
|
||||
<script>
|
||||
import { getOwnedList } from "../../api/products";
|
||||
import { coinList } from "../../utils/coinList";
|
||||
import { truncateAmountByCoin } from "../../utils/amount";
|
||||
|
||||
export default {
|
||||
name: "AccountPurchased",
|
||||
@@ -131,6 +164,9 @@ export default {
|
||||
this.fetchTableData(this.pagination);
|
||||
},
|
||||
methods: {
|
||||
formatAmount(value, coin) {
|
||||
return truncateAmountByCoin(value, coin);
|
||||
},
|
||||
async fetchTableData(params) {
|
||||
this.loading = true;
|
||||
try {
|
||||
@@ -230,5 +266,6 @@ export default {
|
||||
justify-content: flex-end;
|
||||
margin-top: 12px;
|
||||
}
|
||||
.amount-more { font-size: 12px; color: #94a3b8; margin-left: 4px; }
|
||||
</style>
|
||||
|
||||
|
||||
@@ -79,7 +79,21 @@
|
||||
</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>
|
||||
<span class="amount-green">
|
||||
<el-tooltip
|
||||
v-if="formatAmount(scope.row.realAmount, scope.row.coin || scope.row.toSymbol || 'USDT').truncated"
|
||||
:content="`+${formatAmount(scope.row.realAmount, scope.row.coin || scope.row.toSymbol || 'USDT').full}`"
|
||||
placement="top"
|
||||
>
|
||||
<span>
|
||||
+{{ formatAmount(scope.row.realAmount, scope.row.coin || scope.row.toSymbol || 'USDT').text }}
|
||||
<i class="el-icon-more amount-more"></i>
|
||||
</span>
|
||||
</el-tooltip>
|
||||
<span v-else>
|
||||
+{{ formatAmount(scope.row.realAmount, scope.row.coin || scope.row.toSymbol || 'USDT').text }}
|
||||
</span>
|
||||
</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="收款链" min-width="120">
|
||||
@@ -132,6 +146,7 @@
|
||||
|
||||
<script>
|
||||
import { sellerReceiptList } from '../../api/wallet'
|
||||
import { truncateAmountByCoin } from '../../utils/amount'
|
||||
|
||||
export default {
|
||||
name: 'AccountReceiptRecord',
|
||||
@@ -180,6 +195,9 @@ export default {
|
||||
this.rows = this.withKeys(this.rows)
|
||||
},
|
||||
methods: {
|
||||
formatAmount(value, coin) {
|
||||
return truncateAmountByCoin(value, coin)
|
||||
},
|
||||
withKeys(list) {
|
||||
const arr = Array.isArray(list) ? list : []
|
||||
return arr.map((it, idx) => ({
|
||||
@@ -299,6 +317,7 @@ export default {
|
||||
.empty-icon { font-size: 48px; margin-bottom: 8px; }
|
||||
.amount-green { color: #16a34a; font-weight: 700; }
|
||||
.amount-red { color: #ef4444; font-weight: 700; }
|
||||
.amount-more { font-size: 12px; color: #94a3b8; margin-left: 4px; }
|
||||
.type-green { color: #16a34a; }
|
||||
.type-red { color: #ef4444; }
|
||||
.pagination { display: flex; justify-content: flex-end; margin-top: 8px; }
|
||||
|
||||
@@ -17,6 +17,7 @@
|
||||
</div>
|
||||
|
||||
<div v-else class="cart-content">
|
||||
<p style="color:#9E44F1;font-size: 14px;margin-bottom: 10px;">注意:各店铺支持多种支付方式,请选择店铺支付方式后提交订单结算</p>
|
||||
<!-- 外层:店铺列表 -->
|
||||
<el-table
|
||||
ref="shopTable"
|
||||
@@ -27,9 +28,9 @@
|
||||
:expand-row-keys="expandedShopKeys"
|
||||
:header-cell-style="{ textAlign: 'left' }"
|
||||
:cell-style="{ textAlign: 'left' }"
|
||||
@expand-change="handleShopExpandChange"
|
||||
@expand-change="handleGuardExpand"
|
||||
>
|
||||
<el-table-column type="expand" width="46">
|
||||
<el-table-column type="expand" width="46" :expandable="() => false">
|
||||
<template #default="shopScope">
|
||||
<!-- 机器列表(直接挂在店铺下) -->
|
||||
<el-table :data="shopScope.row.productMachineDtoList || []" size="small" border style="width: 100%"
|
||||
@@ -37,7 +38,7 @@
|
||||
: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 type="selection" width="46" :selectable="row => isRowSelectableByShop(shopScope.row, row)" />
|
||||
<el-table-column prop="name" label="商品名称" />
|
||||
<el-table-column prop="miner" label="机器编号" />
|
||||
<el-table-column prop="algorithm" label="算法" />
|
||||
@@ -60,9 +61,27 @@
|
||||
</el-tag>
|
||||
</template>
|
||||
</el-table-column> -->
|
||||
<el-table-column prop="price" label="单价(USDT)" width="100">
|
||||
<el-table-column prop="price" width="120">
|
||||
<template #header>单价({{ getSelectedCoinSymbolForShop(shopScope.row) || 'USDT' }})</template>
|
||||
<template #default="scope">
|
||||
<span class="price-strong">{{ formatTrunc(scope.row.price, 2) }}</span>
|
||||
<template v-if="getMachineUnitPriceBySelection(shopScope.row, scope.row) != null">
|
||||
<span class="price-strong">
|
||||
<el-tooltip
|
||||
v-if="formatAmount(getMachineUnitPriceBySelection(shopScope.row, scope.row), getSelectedCoinSymbolForShop(shopScope.row)).truncated"
|
||||
:content="formatAmount(getMachineUnitPriceBySelection(shopScope.row, scope.row), getSelectedCoinSymbolForShop(shopScope.row)).full"
|
||||
placement="top"
|
||||
>
|
||||
<span>
|
||||
{{ formatAmount(getMachineUnitPriceBySelection(shopScope.row, scope.row), getSelectedCoinSymbolForShop(shopScope.row)).text }}
|
||||
<i class="el-icon-more amount-more"></i>
|
||||
</span>
|
||||
</el-tooltip>
|
||||
<span v-else>
|
||||
{{ formatAmount(getMachineUnitPriceBySelection(shopScope.row, scope.row), getSelectedCoinSymbolForShop(shopScope.row)).text }}
|
||||
</span>
|
||||
</span>
|
||||
</template>
|
||||
<template v-else>-</template>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="租赁天数" width="145">
|
||||
@@ -91,34 +110,91 @@
|
||||
</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 min-width="120">
|
||||
<template #header>机器总价({{ getSelectedCoinSymbolForShop(shopScope.row) || 'USDT' }})</template>
|
||||
<template #default="scope">
|
||||
<template v-if="getMachineUnitPriceBySelection(shopScope.row, scope.row) != null">
|
||||
<span class="price-strong">
|
||||
<el-tooltip
|
||||
v-if="formatAmount(getMachineUnitPriceBySelection(shopScope.row, scope.row) * Number(scope.row.leaseTime || 1), getSelectedCoinSymbolForShop(shopScope.row)).truncated"
|
||||
:content="formatAmount(getMachineUnitPriceBySelection(shopScope.row, scope.row) * Number(scope.row.leaseTime || 1), getSelectedCoinSymbolForShop(shopScope.row)).full"
|
||||
placement="top"
|
||||
>
|
||||
<span>
|
||||
{{ formatAmount(getMachineUnitPriceBySelection(shopScope.row, scope.row) * Number(scope.row.leaseTime || 1), getSelectedCoinSymbolForShop(shopScope.row)).text }}
|
||||
<i class="el-icon-more amount-more"></i>
|
||||
</span>
|
||||
</el-tooltip>
|
||||
<span v-else>
|
||||
{{ formatAmount(getMachineUnitPriceBySelection(shopScope.row, scope.row) * Number(scope.row.leaseTime || 1), getSelectedCoinSymbolForShop(shopScope.row)).text }}
|
||||
</span>
|
||||
</span>
|
||||
</template>
|
||||
<template v-else>-</template>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<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 prop="totalPrice" label="总价(USDT)">
|
||||
|
||||
<el-table-column prop="totalPrice">
|
||||
<template #header>
|
||||
总价({{ getSelectedCoinSymbolForShopHeader() }})
|
||||
</template>
|
||||
<template #default="scope">
|
||||
<span class="price-strong">{{ computeShopTotalDisplay(scope.row) }}</span>
|
||||
<span class="price-strong">
|
||||
<el-tooltip
|
||||
v-if="formatAmount(displayShopTotalBySelection(scope.row), getSelectedCoinSymbolForShop(scope.row)).truncated"
|
||||
:content="formatAmount(displayShopTotalBySelection(scope.row), getSelectedCoinSymbolForShop(scope.row)).full"
|
||||
placement="top"
|
||||
>
|
||||
<span>
|
||||
{{ formatAmount(displayShopTotalBySelection(scope.row), getSelectedCoinSymbolForShop(scope.row)).text }}
|
||||
<i class="el-icon-more amount-more"></i>
|
||||
</span>
|
||||
</el-tooltip>
|
||||
<span v-else>
|
||||
{{ formatAmount(displayShopTotalBySelection(scope.row), getSelectedCoinSymbolForShop(scope.row)).text }}
|
||||
</span>
|
||||
</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
|
||||
|
||||
<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="操作" >
|
||||
<template #default="scope">
|
||||
<el-button type="primary" size="mini" :loading="creatingOrder" :disabled="creatingOrder" @click="handleCheckoutShop(scope.row)">结算该店铺订单</el-button>
|
||||
<el-select
|
||||
v-model="paySelectionMap[scope.row.id]"
|
||||
placeholder="请选择"
|
||||
size="mini"
|
||||
style="min-width: 180px;"
|
||||
@change="val => handleShopPayChange(scope.row, val)"
|
||||
>
|
||||
<template #prefix>
|
||||
<img
|
||||
v-if="getSelectedPayIcon(scope.row)"
|
||||
:src="getSelectedPayIcon(scope.row)"
|
||||
:alt="getSelectedCoinSymbolForShop(scope.row)"
|
||||
style="width:16px;height:16px;margin-right:6px;border-radius:3px;"
|
||||
/>
|
||||
</template>
|
||||
<el-option
|
||||
v-for="(opt, idx) in getShopPayOptions(scope.row)"
|
||||
:key="idx"
|
||||
:value="opt.value"
|
||||
:label="opt.label"
|
||||
>
|
||||
<div style="display:flex;align-items:center;gap:8px;">
|
||||
<img :src="opt.icon" :alt="opt.label" style="width:18px;height:18px;border-radius:3px;" />
|
||||
<span>{{ opt.label }}</span>
|
||||
</div>
|
||||
</el-option>
|
||||
</el-select>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<!-- 操作列移除单店铺结算按钮,统一使用底部“结算选中机器” -->
|
||||
</el-table>
|
||||
<div class="summary-actions" style="margin-top:16px;display:flex;gap:12px;justify-content:flex-end;">
|
||||
<div class="summary-inline" style="color:#666;">
|
||||
@@ -128,28 +204,86 @@
|
||||
<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>
|
||||
<el-button type="primary" :disabled="!selectedMachineCount" @click="handleCheckoutSelected">结算选中机器</el-button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<el-dialog :visible.sync="confirmDialog.visible" width="80vw" :close-on-click-modal="false" :title="`确认结算该店铺订单(共 ${confirmDialog.count} 台机器)`">
|
||||
<el-dialog :visible.sync="confirmDialog.visible" width="80vw" :close-on-click-modal="false" :title="`确认结算(共 ${confirmDialog.count} 台机器)`">
|
||||
<div>
|
||||
<el-table :data="confirmDialog.items" height="360" border stripe
|
||||
<div v-for="grp in confirmDialog.shops" :key="grp.shopId" style="margin-bottom: 18px;">
|
||||
<div style="display:flex;align-items:center;justify-content:space-between;margin:8px 0 6px 0;">
|
||||
<div style="font-weight:600;color:#2c3e50;">
|
||||
店铺:{{ grp.shopName || grp.shopId }}
|
||||
<span style="margin-left:12px;color:#666;font-weight:400;">支付方式:{{ grp.payLabel }}</span>
|
||||
</div>
|
||||
</div>
|
||||
<el-table :data="grp.items" max-height="260" border stripe
|
||||
:header-cell-style="{ textAlign: 'left' }" :cell-style="{ textAlign: 'left' }">
|
||||
<el-table-column prop="product" label="商品" min-width="160" />
|
||||
<el-table-column prop="coin" label="币种" 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="unitPrice" min-width="140">
|
||||
<template #header>单价({{ payCoinSymbol || 'USDT' }})</template>
|
||||
<template #default="scope"><span class="price-strong">{{ formatTrunc(scope.row.unitPrice, 2) }}</span></template>
|
||||
<template #header>单价({{ grp.coinSymbol || 'USDT' }})</template>
|
||||
<template #default="scope">
|
||||
<span class="price-strong">
|
||||
<el-tooltip
|
||||
v-if="formatAmount(scope.row.unitPrice, grp.coinSymbol).truncated"
|
||||
:content="formatAmount(scope.row.unitPrice, grp.coinSymbol).full"
|
||||
placement="top"
|
||||
>
|
||||
<span>
|
||||
{{ formatAmount(scope.row.unitPrice, grp.coinSymbol).text }}
|
||||
<i class="el-icon-more amount-more"></i>
|
||||
</span>
|
||||
</el-tooltip>
|
||||
<span v-else>{{ formatAmount(scope.row.unitPrice, grp.coinSymbol).text }}</span>
|
||||
</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="leaseTime" label="租赁天数" min-width="120" />
|
||||
<el-table-column prop="subtotal" min-width="140">
|
||||
<template #header>小计({{ payCoinSymbol || 'USDT' }})</template>
|
||||
<template #default="scope"><span class="price-strong">{{ formatTrunc(scope.row.subtotal, 2) }}</span></template>
|
||||
<template #header>小计({{ grp.coinSymbol || 'USDT' }})</template>
|
||||
<template #default="scope">
|
||||
<span class="price-strong">
|
||||
<el-tooltip
|
||||
v-if="formatAmount(scope.row.subtotal, grp.coinSymbol).truncated"
|
||||
:content="formatAmount(scope.row.subtotal, grp.coinSymbol).full"
|
||||
placement="top"
|
||||
>
|
||||
<span>
|
||||
{{ formatAmount(scope.row.subtotal, grp.coinSymbol).text }}
|
||||
<i class="el-icon-more amount-more"></i>
|
||||
</span>
|
||||
</el-tooltip>
|
||||
<span v-else>{{ formatAmount(scope.row.subtotal, grp.coinSymbol).text }}</span>
|
||||
</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
<div style="margin-top:12px;text-align:right;">总金额({{ payCoinSymbol || 'USDT' }}):<span class="price-strong">{{ formatTrunc(confirmDialog.total, 2) }}</span></div>
|
||||
</div>
|
||||
<div style="margin-top:12px;text-align:right;">
|
||||
<span style="margin-right:8px;">总金额:</span>
|
||||
<template v-if="Object.keys(confirmDialog.totalsByCoin || {}).length">
|
||||
<span v-for="(amt, coin) in confirmDialog.totalsByCoin" :key="coin" style="margin-left:12px;">
|
||||
{{ coin }}:
|
||||
<span class="price-strong">
|
||||
<el-tooltip
|
||||
v-if="formatAmount(amt, coin).truncated"
|
||||
:content="formatAmount(amt, coin).full"
|
||||
placement="top"
|
||||
>
|
||||
<span>
|
||||
{{ formatAmount(amt, coin).text }}
|
||||
<i class="el-icon-more amount-more"></i>
|
||||
</span>
|
||||
</el-tooltip>
|
||||
<span v-else>{{ formatAmount(amt, coin).text }}</span>
|
||||
</span>
|
||||
</span>
|
||||
</template>
|
||||
<template v-else>-</template>
|
||||
</div>
|
||||
</div>
|
||||
<template #footer>
|
||||
<el-button @click="confirmDialog.visible=false">取消</el-button>
|
||||
@@ -157,23 +291,6 @@
|
||||
</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>
|
||||
|
||||
|
||||
|
||||
<!-- 购物须知(测试版) - 必须勾选并等待5秒后才可关闭 -->
|
||||
@@ -268,6 +385,7 @@
|
||||
<script>
|
||||
import { getGoodsList, deleteBatchGoods as apiDeleteBatchGoods ,deleteBatchGoodsForIsDelete} from '../../api/shoppingCart'
|
||||
import { addOrders,cancelOrder,getOrdersByIds,getOrdersByStatus , getChainAndListForSeller,getCoinPrice } from '../../api/order'
|
||||
import { truncateAmountByCoin } from '../../utils/amount'
|
||||
|
||||
export default {
|
||||
name: 'Cart',
|
||||
@@ -279,7 +397,7 @@ export default {
|
||||
selectedGroups: [],
|
||||
// 新结构:按店铺选择机器 { shopId: Set(machineId) }
|
||||
selectedMachinesMap: {},
|
||||
confirmDialog: { visible: false, items: [], count: 0, total: 0 },
|
||||
confirmDialog: { visible: false, shops: [], count: 0, totalsByCoin: {} },
|
||||
expandedGroupKeys: [],
|
||||
expandedShopKeys: [],
|
||||
creatingOrder: false,
|
||||
@@ -289,6 +407,8 @@ export default {
|
||||
noticeTimer: null,
|
||||
// 待结算的店铺信息
|
||||
pendingCheckoutShop: null,
|
||||
// 多店铺统一结算的临时选择
|
||||
pendingCheckoutAll: null,
|
||||
// 谷歌验证码弹窗
|
||||
googleCodeDialog: {
|
||||
visible: false,
|
||||
@@ -301,7 +421,9 @@ export default {
|
||||
payDialog: { visible: false, value: [], loading: false },
|
||||
selectedChain: '',
|
||||
selectedCoin: '',
|
||||
selectedPrice: 0
|
||||
selectedPrice: 0,
|
||||
// 每个店铺的支付方式选择:map<shopId, 'chain|coin'>
|
||||
paySelectionMap: {}
|
||||
,clearOffLoading: false,
|
||||
settlementSuccessfulVisible: false
|
||||
}
|
||||
@@ -379,6 +501,15 @@ export default {
|
||||
this.noticeTimer = null
|
||||
},
|
||||
methods: {
|
||||
/**
|
||||
* 金额格式化(不补0、不四舍五入),根据币种限定小数位
|
||||
* @param {number|string} value
|
||||
* @param {string} coin
|
||||
* @returns {{text:string,truncated:boolean,full:string}}
|
||||
*/
|
||||
formatAmount(value, coin) {
|
||||
return truncateAmountByCoin(value, coin)
|
||||
},
|
||||
/**
|
||||
* 将金额转为整分整数,避免浮点误差
|
||||
* @param {number|string} v
|
||||
@@ -476,6 +607,146 @@ export default {
|
||||
},
|
||||
// 获取所有商品分组(兼容保留,现直接返回空,因已移除中间商品层)
|
||||
getAllGroups() { return [] },
|
||||
// 生成店铺的支付方式选项,基于 totalPriceList
|
||||
getShopPayOptions(shop) {
|
||||
const list = Array.isArray(shop && shop.totalPriceList) ? shop.totalPriceList : []
|
||||
// 优先使用 payConfigList 提供的 icon,以链+币种去匹配
|
||||
const icons = new Map()
|
||||
const cfg = Array.isArray(shop && shop.payConfigList) ? shop.payConfigList : []
|
||||
cfg.forEach(c => {
|
||||
const key = `${c.payChain || ''}|${c.payCoin || ''}`
|
||||
icons.set(key, c.payCoinImage || '')
|
||||
})
|
||||
return list.map(it => {
|
||||
const chain = (it && it.chain) || ''
|
||||
const coin = (it && it.coin) || ''
|
||||
const key = `${chain}|${coin}`
|
||||
return {
|
||||
label: `${chain} - ${this.toUpperText(coin)}`,
|
||||
value: key,
|
||||
icon: icons.get(key) || ''
|
||||
}
|
||||
})
|
||||
},
|
||||
// 判断某机器在当前店铺选择的支付方式下是否有可用单价
|
||||
hasMachinePriceForSelection(shop, machine) {
|
||||
if (!shop || !machine) return false
|
||||
const key = this.paySelectionMap[shop.id] || ''
|
||||
const [chain, coin] = String(key).split('|')
|
||||
const list = Array.isArray(machine.priceList) ? machine.priceList : []
|
||||
return list.some(it => String(it.chain) === chain && String(it.coin) === coin)
|
||||
},
|
||||
// 获取店铺当前选择的币种符号(大写)
|
||||
getSelectedCoinSymbolForShop(shop) {
|
||||
const key = this.paySelectionMap[shop ? shop.id : undefined]
|
||||
if (!key) return ''
|
||||
const parts = String(key).split('|')
|
||||
return this.toUpperText(parts[1])
|
||||
},
|
||||
// 外层表头:取第一个店铺的币种显示在“总价”标题后面
|
||||
getSelectedCoinSymbolForShopHeader() {
|
||||
const first = Array.isArray(this.shops) && this.shops.length ? this.shops[0] : null
|
||||
if (!first) return ''
|
||||
// 确保默认已设置
|
||||
this.ensureDefaultPaySelection(first)
|
||||
return this.getSelectedCoinSymbolForShop(first)
|
||||
},
|
||||
// 获取当前店铺选中的支付方式图标(用于 select prefix)
|
||||
getSelectedPayIcon(shop) {
|
||||
if (!shop) return ''
|
||||
this.ensureDefaultPaySelection(shop)
|
||||
const key = this.paySelectionMap[shop.id] || ''
|
||||
const [chain, coin] = String(key).split('|')
|
||||
const cfg = Array.isArray(shop && shop.payConfigList) ? shop.payConfigList : []
|
||||
const hit = cfg.find(c => String(c.payChain) === chain && String(c.payCoin) === coin)
|
||||
return hit && hit.payCoinImage ? hit.payCoinImage : ''
|
||||
},
|
||||
// 默认设置店铺支付方式为第一个选项
|
||||
ensureDefaultPaySelection(shop) {
|
||||
if (!shop) return
|
||||
const list = this.getShopPayOptions(shop)
|
||||
if (list.length && !this.paySelectionMap[shop.id]) {
|
||||
this.$set(this.paySelectionMap, shop.id, list[0].value)
|
||||
}
|
||||
},
|
||||
// 处理店铺支付方式变化
|
||||
handleShopPayChange(shop, val) {
|
||||
if (!shop) return
|
||||
this.$set(this.paySelectionMap, shop.id, val)
|
||||
// 清理已选择但在当前支付方式下无价格的机器,并刷新视觉勾选
|
||||
const set = this.selectedMachinesMap[shop.id]
|
||||
if (set && set.size) {
|
||||
const list = Array.isArray(shop.productMachineDtoList) ? shop.productMachineDtoList : []
|
||||
list.forEach(m => {
|
||||
if (set.has(m.id) && !this.hasMachinePriceForSelection(shop, m)) {
|
||||
set.delete(m.id)
|
||||
}
|
||||
})
|
||||
// 重新应用到表格勾选
|
||||
this.$nextTick(() => this.applyInnerSelectionFromSet(shop))
|
||||
}
|
||||
},
|
||||
// 根据店铺选择展示其总价(来自 totalPriceList)
|
||||
displayShopTotalBySelection(shop) {
|
||||
if (!shop) return 0
|
||||
// 确保有默认选择
|
||||
this.ensureDefaultPaySelection(shop)
|
||||
// 若用户修改过任意机器的租赁天数,则按当前租期+所选币种实时汇总
|
||||
if (this.isShopLeaseChanged(shop)) {
|
||||
try {
|
||||
const machines = Array.isArray(shop.productMachineDtoList) ? shop.productMachineDtoList : []
|
||||
let totalCents = 0
|
||||
machines.forEach(m => {
|
||||
const unit = this.getMachineUnitPriceBySelection(shop, m)
|
||||
if (unit != null) {
|
||||
const days = Math.max(1, Math.floor(Number(m.leaseTime || 1)))
|
||||
totalCents += this.toCents(unit) * days
|
||||
}
|
||||
})
|
||||
return totalCents / 100
|
||||
} catch (e) {
|
||||
/* noop fallthrough to backend value */
|
||||
}
|
||||
}
|
||||
// 未修改租期:优先使用后端 totalPriceList 当前币种价格
|
||||
const key = this.paySelectionMap[shop.id] || ''
|
||||
const [chain, coin] = key.split('|')
|
||||
const list = Array.isArray(shop.totalPriceList) ? shop.totalPriceList : []
|
||||
const hit = list.find(it => String(it.chain) === chain && String(it.coin) === coin)
|
||||
if (hit && hit.price != null) return Number(hit.price || 0)
|
||||
// 兜底:若没命中,返回总价或0
|
||||
return Number(shop.totalPrice || 0)
|
||||
},
|
||||
// 是否有任意机器的租期被用户修改
|
||||
isShopLeaseChanged(shop) {
|
||||
try {
|
||||
const list = Array.isArray(shop && shop.productMachineDtoList) ? shop.productMachineDtoList : []
|
||||
return list.some(m => {
|
||||
const orig = (m && m._origLeaseTime != null) ? Number(m._origLeaseTime) : Number(m && m.leaseTime)
|
||||
const cur = Math.max(1, Math.floor(Number(m && m.leaseTime) || 1))
|
||||
return orig !== cur
|
||||
})
|
||||
} catch (e) {
|
||||
return false
|
||||
}
|
||||
},
|
||||
// 根据店铺选择获取机器的单价(来自 priceList)
|
||||
getMachineUnitPriceBySelection(shop, machine) {
|
||||
if (!shop || !machine) return Number(machine.price || 0)
|
||||
this.ensureDefaultPaySelection(shop)
|
||||
const key = this.paySelectionMap[shop.id] || ''
|
||||
const [chain, coin] = key.split('|')
|
||||
const list = Array.isArray(machine.priceList) ? machine.priceList : []
|
||||
const hit = list.find(it => String(it.chain) === chain && String(it.coin) === coin)
|
||||
if (hit && hit.price != null) return Number(hit.price || 0)
|
||||
// 查不到则返回 null(用于界面展示和禁用)
|
||||
return null
|
||||
},
|
||||
// 对应店铺下勾选列是否可点:同时判断上下架与是否有价格
|
||||
isRowSelectableByShop(shop, row) {
|
||||
if (!this.isOnShelf(row)) return false
|
||||
return this.hasMachinePriceForSelection(shop, row)
|
||||
},
|
||||
// 店铺总价(精确到分):优先用后端 totalPrice;若用户改动租期则实时按分计算
|
||||
computeShopTotal(shop) {
|
||||
if (!shop) return 0
|
||||
@@ -530,9 +801,6 @@ export default {
|
||||
// 按照新的传参结构:{code: 谷歌验证码, orderInfoVoList: [之前传参的数组]}
|
||||
const payload = {
|
||||
code: googleCode,
|
||||
chain: this.selectedChain,
|
||||
coin: this.selectedCoin,
|
||||
price: this.selectedPrice,
|
||||
orderInfoVoList: orderInfoVoList
|
||||
}
|
||||
const res = await addOrders(payload)
|
||||
@@ -631,6 +899,10 @@ export default {
|
||||
...shop,
|
||||
id: shop.id != null ? String(shop.id) : `shop-${sIdx}`
|
||||
}))
|
||||
// 为每个店铺设置默认支付方式
|
||||
try {
|
||||
withShopKeys.forEach(sp => this.ensureDefaultPaySelection(sp))
|
||||
} catch (e) { /* noop */ }
|
||||
// 记录每台机器的原始租期,便于判断是否被修改
|
||||
try {
|
||||
withShopKeys.forEach(sp => {
|
||||
@@ -641,6 +913,10 @@ export default {
|
||||
this.shops = withShopKeys
|
||||
this.groups = []
|
||||
this.expandedGroupKeys = []
|
||||
// 默认展开所有店铺
|
||||
try {
|
||||
this.expandedShopKeys = withShopKeys.map(sp => String(sp.id))
|
||||
} catch (e) { this.expandedShopKeys = [] }
|
||||
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
|
||||
@@ -812,12 +1088,36 @@ export default {
|
||||
},
|
||||
// 实际执行结算的方法
|
||||
async executeCheckout(googleCode) {
|
||||
if (!this.pendingCheckoutShop) return
|
||||
if (!this.pendingCheckoutShop && !this.pendingCheckoutAll) return
|
||||
|
||||
const { shop, payload } = this.pendingCheckoutShop
|
||||
// 聚合 payload:支持单店铺或多店铺
|
||||
let payloadAll = []
|
||||
if (this.pendingCheckoutAll && this.pendingCheckoutAll.length) {
|
||||
this.pendingCheckoutAll.forEach(({ shop, items }) => {
|
||||
(items || []).forEach(m => {
|
||||
const sel = this.paySelectionMap[shop.id] || ''
|
||||
const [chain, coin] = String(sel).split('|')
|
||||
payloadAll.push({
|
||||
leaseTime: Number(m.leaseTime || 1),
|
||||
machineId: m.id,
|
||||
productId: m.productId,
|
||||
shopId: shop.id,
|
||||
chain,
|
||||
coin
|
||||
})
|
||||
})
|
||||
})
|
||||
} else if (this.pendingCheckoutShop) {
|
||||
const { payload } = this.pendingCheckoutShop
|
||||
payloadAll = (Array.isArray(payload) ? payload : []).map(p => {
|
||||
const sel = this.paySelectionMap[p.shopId] || this.paySelectionMap[this.pendingCheckoutShop.shop.id] || ''
|
||||
const [chain, coin] = String(sel).split('|')
|
||||
return { ...p, chain, coin }
|
||||
})
|
||||
}
|
||||
this.creatingOrder = true
|
||||
try {
|
||||
const res = await this.fetchAddOrders(payload, googleCode)
|
||||
const res = await this.fetchAddOrders(payloadAll, googleCode)
|
||||
let ok = false
|
||||
if (res && Number(res.code) === 200) {
|
||||
const dataStr = String(res.data || '')
|
||||
@@ -845,41 +1145,32 @@ export default {
|
||||
} finally {
|
||||
this.creatingOrder = false
|
||||
this.pendingCheckoutShop = null
|
||||
this.pendingCheckoutAll = null
|
||||
}
|
||||
},
|
||||
handleCheckoutSelected() {
|
||||
// 若有选中机器,则以选中机器为准;否则当做选中整个商品分组
|
||||
let items = []
|
||||
if (this.selectedMachineCount) {
|
||||
(this.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)) {
|
||||
items.push({
|
||||
product: shop.name || '',
|
||||
coin: this.toUpperText(m.coin),
|
||||
machineId: m.id,
|
||||
user: m.user,
|
||||
miner: m.miner,
|
||||
price: Number(m.price || 0)
|
||||
})
|
||||
}
|
||||
})
|
||||
})
|
||||
} else {
|
||||
this.$message({
|
||||
message: '请先选择商品或机器',
|
||||
type: 'warning',
|
||||
showClose: true
|
||||
})
|
||||
if (!this.selectedMachineCount) {
|
||||
this.$message({ message: '请先勾选要结算的机器', type: 'warning', showClose: true })
|
||||
return
|
||||
}
|
||||
this.confirmDialog.items = items
|
||||
this.confirmDialog.count = items.length
|
||||
this.confirmDialog.total = items.reduce((s, i) => s + i.price, 0)
|
||||
this.confirmDialog.visible = true
|
||||
const shops = Array.isArray(this.shops) ? this.shops : []
|
||||
const picked = []
|
||||
shops.forEach(shop => {
|
||||
const set = this.selectedMachinesMap[shop.id]
|
||||
if (!set || !set.size) return
|
||||
const list = Array.isArray(shop.productMachineDtoList) ? shop.productMachineDtoList : []
|
||||
const items = list.filter(m => set.has(m.id) && this.isOnShelf(m))
|
||||
if (items.length) picked.push({ shop, items })
|
||||
})
|
||||
if (!picked.length) {
|
||||
this.$message({ message: '未找到可结算的上架机器', type: 'warning', showClose: true })
|
||||
return
|
||||
}
|
||||
this.pendingCheckoutAll = picked
|
||||
// 打开须知弹窗
|
||||
this.noticeDialog.visible = true
|
||||
this.noticeDialog.checked = false
|
||||
this.startNoticeCountdown()
|
||||
},
|
||||
handleRemoveSelectedMachines() {
|
||||
const payload = this.buildDeletePayload()
|
||||
@@ -959,49 +1250,35 @@ export default {
|
||||
this.noticeDialog.visible = false
|
||||
// 进入下一步前,确保一次恢复(避免刚关闭后消失)
|
||||
this.$nextTick(() => this.reapplySelectionsForPendingShop())
|
||||
// 用户确认须知后,先选择支付链/币种
|
||||
this.openPaySelectDialog()
|
||||
// 若是统一结算,构建全量确认信息;否则构建单店铺确认
|
||||
if (this.pendingCheckoutAll && this.pendingCheckoutAll.length) {
|
||||
this.showConfirmDialogAll()
|
||||
} else {
|
||||
// 用户确认须知后,直接按购物车中的店铺支付方式进行结算确认
|
||||
try {
|
||||
const shop = this.pendingCheckoutShop && this.pendingCheckoutShop.shop
|
||||
if (shop) {
|
||||
const key = this.paySelectionMap[shop.id] || ''
|
||||
const [chain, coin] = String(key).split('|')
|
||||
this.selectedChain = chain || ''
|
||||
this.selectedCoin = coin || ''
|
||||
} else {
|
||||
this.selectedChain = ''
|
||||
this.selectedCoin = ''
|
||||
}
|
||||
} catch (e) {
|
||||
this.selectedChain = ''
|
||||
this.selectedCoin = ''
|
||||
}
|
||||
this.showConfirmDialog()
|
||||
}
|
||||
},
|
||||
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()
|
||||
},
|
||||
// 显示确认结算弹窗
|
||||
|
||||
// 显示确认结算弹窗(支持多店铺结构,这里按当前店铺构建一个分组)
|
||||
showConfirmDialog() {
|
||||
if (!this.pendingCheckoutShop) return
|
||||
|
||||
@@ -1017,25 +1294,15 @@ export default {
|
||||
const items = []
|
||||
list.forEach(m => {
|
||||
if (selectedIds.has(m.id) && this.isOnShelf(m)) {
|
||||
// 单价(同币种显示,若非USDT按实时价换算 用现在的价格除以后端返回的this.selectedPrice)
|
||||
const baseUnit = Number(m.price || 0)
|
||||
// 使用购物车中已选支付方式对应的单价
|
||||
const baseUnit = this.getMachineUnitPriceBySelection(shop, m)
|
||||
if (baseUnit == null) return
|
||||
const leaseDays = Math.max(1, Math.floor(Number(m.leaseTime || 1)))
|
||||
const isUSDT = String(this.selectedCoin).toUpperCase() === 'USDT'
|
||||
console.log('baseUnit', baseUnit)
|
||||
console.log('selectedPrice', this.selectedPrice)
|
||||
const unitPrice = !isUSDT && this.selectedPrice > 0 ? (baseUnit / this.selectedPrice) : baseUnit
|
||||
|
||||
console.log('baseUnit / this.selectedPrice', baseUnit / this.selectedPrice);
|
||||
|
||||
console.log('unitPrice', unitPrice ,'leaseDays', leaseDays)
|
||||
console.log(`unitPrice * leaseDays`,unitPrice * leaseDays);
|
||||
console.log(unitPrice*this.selectedPrice,"客服付款");
|
||||
|
||||
|
||||
const subtotal = unitPrice * leaseDays
|
||||
const unitPrice = Number(baseUnit || 0)
|
||||
const subtotal = Number(unitPrice) * leaseDays
|
||||
items.push({
|
||||
product: shop.name || '',
|
||||
coin: this.toUpperText(m.coin),
|
||||
coin: this.toUpperText(this.selectedCoin || ''),
|
||||
user: m.user,
|
||||
miner: m.miner,
|
||||
unitPrice: Number(unitPrice || 0),
|
||||
@@ -1045,9 +1312,83 @@ export default {
|
||||
}
|
||||
})
|
||||
|
||||
this.confirmDialog.items = items
|
||||
// 构建对话框分组数据
|
||||
const key = this.paySelectionMap[shop.id] || ''
|
||||
const [chain, coin] = String(key).split('|')
|
||||
const coinSymbol = this.toUpperText(coin || '')
|
||||
const payLabel = `${chain} - ${coinSymbol}`
|
||||
const grp = {
|
||||
shopId: shop.id,
|
||||
shopName: shop.name || '',
|
||||
coinSymbol,
|
||||
payLabel,
|
||||
items
|
||||
}
|
||||
this.confirmDialog.shops = [grp]
|
||||
this.confirmDialog.count = items.length
|
||||
this.confirmDialog.total = items.reduce((s, i) => s + i.subtotal, 0)
|
||||
// 汇总各币种合计
|
||||
const totals = {}
|
||||
const centsAdd = (acc, v) => (acc + this.toCents(v))
|
||||
if (coinSymbol) {
|
||||
const tCents = items.reduce((acc, it) => centsAdd(acc, it.subtotal || 0), 0)
|
||||
totals[coinSymbol] = Number(this.centsToText(tCents))
|
||||
}
|
||||
this.confirmDialog.totalsByCoin = totals
|
||||
this.confirmDialog.visible = true
|
||||
},
|
||||
// 多店铺:生成确认明细与多币种总额
|
||||
showConfirmDialogAll() {
|
||||
// 以当前勾选状态实时构建,避免中途数据变化造成空白
|
||||
const groups = []
|
||||
const totalsCentsByCoin = new Map()
|
||||
let count = 0
|
||||
const shops = Array.isArray(this.shops) ? this.shops : []
|
||||
shops.forEach(shop => {
|
||||
const set = this.selectedMachinesMap[shop.id]
|
||||
if (!set || !set.size) return
|
||||
const key = this.paySelectionMap[shop.id] || ''
|
||||
const [chain, coin] = String(key).split('|')
|
||||
const coinSymbol = this.toUpperText(coin || '')
|
||||
const payLabel = `${chain} - ${coinSymbol}`
|
||||
const rows = []
|
||||
const list = Array.isArray(shop.productMachineDtoList) ? shop.productMachineDtoList : []
|
||||
list.forEach(m => {
|
||||
if (!set.has(m.id) || !this.isOnShelf(m)) return
|
||||
const baseUnit = this.getMachineUnitPriceBySelection(shop, m)
|
||||
if (baseUnit == null) return
|
||||
const leaseDays = Math.max(1, Math.floor(Number(m.leaseTime || 1)))
|
||||
const unitPrice = Number(baseUnit || 0)
|
||||
const subtotal = Number(unitPrice) * leaseDays
|
||||
rows.push({
|
||||
product: shop.name || '',
|
||||
coin: coinSymbol,
|
||||
user: m.user,
|
||||
miner: m.miner,
|
||||
unitPrice,
|
||||
leaseTime: leaseDays,
|
||||
subtotal
|
||||
})
|
||||
const prev = totalsCentsByCoin.get(coinSymbol) || 0
|
||||
totalsCentsByCoin.set(coinSymbol, prev + this.toCents(subtotal))
|
||||
count += 1
|
||||
})
|
||||
if (rows.length) {
|
||||
groups.push({
|
||||
shopId: shop.id,
|
||||
shopName: shop.name || '',
|
||||
coinSymbol,
|
||||
payLabel,
|
||||
items: rows
|
||||
})
|
||||
}
|
||||
})
|
||||
const totalsObj = {}
|
||||
totalsCentsByCoin.forEach((val, coin) => {
|
||||
totalsObj[coin] = Number(this.centsToText(val))
|
||||
})
|
||||
this.confirmDialog.shops = groups
|
||||
this.confirmDialog.count = count
|
||||
this.confirmDialog.totalsByCoin = totalsObj
|
||||
this.confirmDialog.visible = true
|
||||
},
|
||||
// 显示谷歌验证码输入框
|
||||
@@ -1533,4 +1874,22 @@ export default {
|
||||
.dialog-footer {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.amount-more {
|
||||
font-size: 12px;
|
||||
color: #94a3b8; /* slate-400 */
|
||||
margin-left: 4px;
|
||||
}
|
||||
|
||||
|
||||
::v-deep .el-input__prefix, .el-input__suffix{
|
||||
top:24%;
|
||||
}
|
||||
::v-deep .el-input--mini .el-input__icon{
|
||||
line-height: 0px;
|
||||
}
|
||||
/* 禁用展开箭头的点击(彻底防止用户折叠) */
|
||||
::v-deep .el-table .el-table__expand-icon {
|
||||
pointer-events: none;
|
||||
}
|
||||
</style>
|
||||
@@ -3,6 +3,7 @@ import { getProductById } from '../../utils/productService'
|
||||
import { addToCart } from '../../utils/cartManager'
|
||||
import { getMachineInfo, getPayTypes } from '../../api/products'
|
||||
import { addCart, getGoodsList } from '../../api/shoppingCart'
|
||||
import { truncateAmountByCoin } from '../../utils/amount'
|
||||
|
||||
export default {
|
||||
name: 'ProductDetail',
|
||||
@@ -28,8 +29,20 @@ export default {
|
||||
minPowerDissipation: null,
|
||||
maxPowerDissipation: null
|
||||
},
|
||||
// 排序状态:true 升序,false 降序
|
||||
sortStates: {
|
||||
priceSort: true,
|
||||
powerSort: true,
|
||||
powerDissipationSort: true
|
||||
},
|
||||
// 当前激活的排序字段(仅当用户点击后才会传参)
|
||||
activeSortField: '',
|
||||
// 首次进入时是否已按价格币种设置过支付方式筛选默认值
|
||||
payFilterDefaultApplied: false,
|
||||
params: {
|
||||
id: "",
|
||||
pageNum: 1,
|
||||
pageSize: 10,
|
||||
|
||||
|
||||
},
|
||||
@@ -122,11 +135,15 @@ export default {
|
||||
// },
|
||||
|
||||
],
|
||||
productDetailLoading:false
|
||||
productDetailLoading: false,
|
||||
pageSizes: [10, 20, 50],
|
||||
currentPage: 1,
|
||||
total: 0,
|
||||
|
||||
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
console.log(this.$route.params.id, "i叫哦附加费")
|
||||
if (this.$route.params.id) {
|
||||
this.params.id = this.$route.params.id
|
||||
this.product = true
|
||||
@@ -134,7 +151,7 @@ export default {
|
||||
if (this.productListData && this.productListData.length) {
|
||||
this.expandedRowKeys = [this.productListData[0].id]
|
||||
}
|
||||
this.fetchGetMachineInfo(this.params)
|
||||
this.fetchGetMachineInfo(this.params)//priceSort 价格,powerSort 算力,功耗powerDissipationSort 布尔类型,true 升序,false降序
|
||||
this.fetchPayTypes()
|
||||
} else {
|
||||
this.$message.error('商品不存在')
|
||||
@@ -143,9 +160,64 @@ export default {
|
||||
this.fetchGetGoodsList()
|
||||
},
|
||||
methods: {
|
||||
// 行币种:优先行内 payCoin > coin,其次取全局表头币种
|
||||
getRowCoin(row) {
|
||||
try {
|
||||
const c = (row && (row.payCoin || row.coin)) || this.getPriceCoinSymbol() || ''
|
||||
return String(c).toUpperCase()
|
||||
} catch (e) { return '' }
|
||||
},
|
||||
// 金额格式化:不补0、不四舍五入;返回 {text,truncated,full}
|
||||
formatAmount(value, coin) {
|
||||
return truncateAmountByCoin(value, coin)
|
||||
},
|
||||
/**
|
||||
* 首次加载时,将“支付方式筛选”的默认选中值设为与价格列币种一致,
|
||||
* 并同步 filters.chain/filters.coin;仅执行一次,不触发额外查询。
|
||||
*/
|
||||
ensureDefaultPayFilterSelection() {
|
||||
try {
|
||||
if (this.payFilterDefaultApplied) return
|
||||
const payList = Array.isArray(this.paymentMethodList) ? this.paymentMethodList : []
|
||||
if (!payList.length) return
|
||||
const coinSymbol = (this.getPriceCoinSymbol && this.getPriceCoinSymbol()) || ''
|
||||
if (!coinSymbol) return
|
||||
const hit = payList.find(it => String(it && it.payCoin).toUpperCase() === String(coinSymbol).toUpperCase())
|
||||
if (!hit) return
|
||||
const key = `${hit.payChain || ''}|${hit.payCoin || ''}`
|
||||
this.selectedPayKey = key
|
||||
this.filters.chain = String(hit.payChain || '').trim()
|
||||
this.filters.coin = String(hit.payCoin || '').trim()
|
||||
this.payFilterDefaultApplied = true
|
||||
} catch (e) { /* noop */ }
|
||||
},
|
||||
// 切换排序:field in ['priceSort','powerSort','powerDissipationSort']
|
||||
handleToggleSort(field) {
|
||||
try {
|
||||
if (!this.sortStates) this.sortStates = {}
|
||||
if (this.activeSortField !== field) {
|
||||
// 切换到新的字段:默认从升序开始(true)
|
||||
// 先将其它字段复位为升序(▲)
|
||||
Object.keys(this.sortStates).forEach(k => { this.sortStates[k] = true })
|
||||
this.activeSortField = field
|
||||
// 后端默认升序,首次点击应为降序
|
||||
this.sortStates[field] = false
|
||||
} else {
|
||||
// 同一字段:升降序切换
|
||||
this.sortStates[field] = !this.sortStates[field]
|
||||
}
|
||||
const params = this.buildQueryParams()
|
||||
this.fetchGetMachineInfo(params)
|
||||
} catch (e) { /* noop */ }
|
||||
},
|
||||
// 组合查询参数(带上商品 id 与筛选条件)
|
||||
buildQueryParams() {
|
||||
const q = { id: this.params.id }
|
||||
// 分页参数始终透传
|
||||
try {
|
||||
if (this.params && this.params.pageNum != null) q.pageNum = this.params.pageNum
|
||||
if (this.params && this.params.pageSize != null) q.pageSize = this.params.pageSize
|
||||
} catch (e) { /* noop */ }
|
||||
// 仅当用户真实填写(>0)时才传参;默认/空值不传
|
||||
const addNum = (obj, key, name) => {
|
||||
const raw = obj[key]
|
||||
@@ -162,6 +234,13 @@ export default {
|
||||
addNum(this.filters, 'maxPower', 'maxPower')
|
||||
addNum(this.filters, 'minPowerDissipation', 'minPowerDissipation')
|
||||
addNum(this.filters, 'maxPowerDissipation', 'maxPowerDissipation')
|
||||
// 排序参数:仅在用户点击某一列后传当前列
|
||||
try {
|
||||
if (this.activeSortField) {
|
||||
const s = this.sortStates || {}
|
||||
q[this.activeSortField] = !!s[this.activeSortField]
|
||||
}
|
||||
} catch (e) { /* noop */ }
|
||||
return q
|
||||
},
|
||||
// 拉取支付方式
|
||||
@@ -172,6 +251,8 @@ export default {
|
||||
if (res && (res.code === 0 || res.code === 200)) {
|
||||
const list = Array.isArray(res.data) ? res.data : []
|
||||
this.paymentMethodList = list
|
||||
// 支付方式加载后尝试设置默认筛选
|
||||
this.ensureDefaultPayFilterSelection()
|
||||
}
|
||||
} catch (e) {
|
||||
// 忽略错误,保持页面可用
|
||||
@@ -185,6 +266,7 @@ export default {
|
||||
console.log(res)
|
||||
if (res && res.code === 200) {
|
||||
console.log(res.data, 'res.rows');
|
||||
this.total = res.total||0;
|
||||
// 新数据结构:机器为扁平 rows 列表;仅当后端返回有效支付方式时才覆盖,避免清空 getPayTypes 的结果
|
||||
try {
|
||||
const payList = res && res.data && res.data.payConfigList
|
||||
@@ -203,6 +285,8 @@ export default {
|
||||
// 清空旧的两层结构数据,避免误用
|
||||
this.productListData = []
|
||||
this.expandedRowKeys = []
|
||||
// 机器加载后尝试设置默认筛选
|
||||
this.ensureDefaultPayFilterSelection()
|
||||
this.$nextTick(() => {
|
||||
this.machinesLoaded = true
|
||||
})
|
||||
@@ -342,7 +426,7 @@ export default {
|
||||
},
|
||||
|
||||
// 已取消对比购物车的自动勾选/禁用逻辑
|
||||
autoSelectAndDisable() {},
|
||||
autoSelectAndDisable() { },
|
||||
|
||||
// 选择器可选控制:已在购物车中的机器不可再选
|
||||
isSelectable(row, index) {
|
||||
@@ -636,6 +720,21 @@ export default {
|
||||
console.error('添加到购物车失败:', error)
|
||||
this.$message.error('添加到购物车失败,请稍后重试')
|
||||
}
|
||||
}
|
||||
},
|
||||
handleSizeChange(val) {
|
||||
console.log(`每页 ${val} 条`);
|
||||
this.params.pageSize = val;
|
||||
this.params.pageNum = 1;
|
||||
this.currentPage = 1;
|
||||
// 携带当前激活的排序字段
|
||||
this.fetchGetMachineInfo(this.buildQueryParams());
|
||||
|
||||
},
|
||||
handleCurrentChange(val) {
|
||||
console.log(`当前页: ${val}`);
|
||||
this.params.pageNum = val;
|
||||
// 携带当前激活的排序字段
|
||||
this.fetchGetMachineInfo(this.buildQueryParams());
|
||||
},
|
||||
}
|
||||
}
|
||||
@@ -48,6 +48,14 @@
|
||||
class="filter-control"
|
||||
@change="handlePayFilterChange"
|
||||
>
|
||||
<template #prefix>
|
||||
<img
|
||||
v-if="getSelectedPayIcon()"
|
||||
:src="getSelectedPayIcon()"
|
||||
alt=""
|
||||
style="width:16px;height:16px;border-radius:3px;margin-right:6px;"
|
||||
/>
|
||||
</template>
|
||||
<el-option
|
||||
v-for="(opt, i) in paymentMethodList"
|
||||
:key="i"
|
||||
@@ -124,30 +132,43 @@
|
||||
|
||||
|
||||
|
||||
<el-table-column prop="theoryPower" label="理论算力" min-width="160" header-align="left" align="left" show-overflow-tooltip>
|
||||
<el-table-column prop="theoryPower" label="理论算力" 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>
|
||||
<el-table-column header-align="left" align="left" show-overflow-tooltip>
|
||||
<template #header>
|
||||
<span class="sortable" :class="{ active: activeSortField==='powerSort' }" @click="handleToggleSort('powerSort')">
|
||||
实际算力
|
||||
<i class="sort-arrow" :class="[(sortStates && sortStates.powerSort) ? 'asc' : 'desc', activeSortField==='powerSort' ? 'active' : '']"></i>
|
||||
</span>
|
||||
</template>
|
||||
<template #default="scope">{{ scope.row.computingPower }} {{ scope.row.unit }}</template>
|
||||
</el-table-column>
|
||||
|
||||
<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="powerDissipation" header-align="left" align="left">
|
||||
<template #header>
|
||||
<span class="sortable" :class="{ active: activeSortField==='powerDissipationSort' }" @click="handleToggleSort('powerDissipationSort')">
|
||||
功耗(kw/h)
|
||||
<i class="sort-arrow" :class="[(sortStates && sortStates.powerDissipationSort) ? 'asc' : 'desc', activeSortField==='powerDissipationSort' ? 'active' : '']"></i>
|
||||
</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="algorithm" label="算法" header-align="left" align="left" />
|
||||
|
||||
<el-table-column prop="theoryIncome" width="120" header-align="left" align="left" show-overflow-tooltip>
|
||||
<el-table-column prop="theoryIncome" header-align="left" align="left" show-overflow-tooltip>
|
||||
<template #header>
|
||||
单机理论收入(每日)
|
||||
<span v-if="getFirstCoinSymbol()">({{ getFirstCoinSymbol() }})</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="theoryUsdtIncome" label="单机理论收入(每日/USDT)" width="120" header-align="left" align="left" />
|
||||
<el-table-column prop="theoryUsdtIncome" label="单机理论收入(每日/USDT)" 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">
|
||||
<el-table-column prop="type" label="矿机型号" header-align="left" align="left" />
|
||||
<el-table-column label="最大可租赁(天)" 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">
|
||||
<el-table-column label="租赁天数(天)" header-align="left" align="left">
|
||||
<template #default="scope">
|
||||
<el-input-number
|
||||
v-model="scope.row.leaseTime"
|
||||
@@ -161,14 +182,31 @@
|
||||
@change="val => handleLeaseDaysChange(scope.row, val)"
|
||||
/>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="price" header-align="left" align="left" min-width="120">
|
||||
</el-table-column >
|
||||
<el-table-column prop="price" header-align="left" align="center" >
|
||||
<template #header>
|
||||
<span class="sortable" :class="{ active: activeSortField==='priceSort' }" @click="handleToggleSort('priceSort')">
|
||||
单价 <span v-if="getPriceCoinSymbol()">({{ getPriceCoinSymbol() }})</span>
|
||||
<i class="sort-arrow" :class="[(sortStates && sortStates.priceSort) ? 'asc' : 'desc', activeSortField==='priceSort' ? 'active' : '']"></i>
|
||||
</span>
|
||||
</template>
|
||||
<template #default="scope">
|
||||
<span class="price-strong">
|
||||
<el-tooltip
|
||||
v-if="formatAmount(scope.row.price, getRowCoin(scope.row)).truncated"
|
||||
:content="formatAmount(scope.row.price, getRowCoin(scope.row)).full"
|
||||
placement="top"
|
||||
>
|
||||
<span>
|
||||
{{ formatAmount(scope.row.price, getRowCoin(scope.row)).text }}
|
||||
<i class="el-icon-more amount-more"></i>
|
||||
</span>
|
||||
</el-tooltip>
|
||||
<span v-else>{{ formatAmount(scope.row.price, getRowCoin(scope.row)).text }}</span>
|
||||
</span>
|
||||
</template>
|
||||
<template #default="scope"><span class="price-strong">{{ scope.row.price }}</span></template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="saleState" label="售出状态" header-align="left" align="left" min-width="110">
|
||||
<el-table-column prop="saleState" label="售出状态" width="110" header-align="left" align="left" >
|
||||
<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 ? '已售出' : '售出中') }}
|
||||
@@ -201,7 +239,21 @@
|
||||
<template #header>
|
||||
单价 <span v-if="getPriceCoinSymbol()">({{ getPriceCoinSymbol() }})</span>
|
||||
</template>
|
||||
<template #default="scope"><span class="price-strong">{{ scope.row.price }}</span></template>
|
||||
<template #default="scope">
|
||||
<span class="price-strong">
|
||||
<el-tooltip
|
||||
v-if="formatAmount(scope.row.price, getRowCoin(scope.row)).truncated"
|
||||
:content="formatAmount(scope.row.price, getRowCoin(scope.row)).full"
|
||||
placement="top"
|
||||
>
|
||||
<span>
|
||||
{{ formatAmount(scope.row.price, getRowCoin(scope.row)).text }}
|
||||
<i class="el-icon-more amount-more"></i>
|
||||
</span>
|
||||
</el-tooltip>
|
||||
<span v-else>{{ formatAmount(scope.row.price, getRowCoin(scope.row)).text }}</span>
|
||||
</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
|
||||
</el-table>
|
||||
@@ -212,6 +264,21 @@
|
||||
</template>
|
||||
</el-dialog>
|
||||
|
||||
<el-row style="margin-bottom: 20px;">
|
||||
<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="params.pageSize"
|
||||
layout="total, sizes, prev, pager, next, jumper"
|
||||
:total="total"
|
||||
>
|
||||
</el-pagination>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</div>
|
||||
|
||||
<div v-else class="not-found">
|
||||
@@ -253,6 +320,23 @@ export default {
|
||||
return ''
|
||||
}
|
||||
},
|
||||
/**
|
||||
* 当前下拉框选中值对应的图标(用于 el-select 前缀展示)
|
||||
* @returns {string}
|
||||
*/
|
||||
getSelectedPayIcon() {
|
||||
try {
|
||||
const key = this.selectedPayKey
|
||||
if (!key) return ''
|
||||
const [chain, coin] = String(key).split('|')
|
||||
const list = Array.isArray(this.paymentMethodList) ? this.paymentMethodList : []
|
||||
const hit = list.find(
|
||||
it => String(it && it.payChain).toUpperCase() === String(chain).toUpperCase()
|
||||
&& String(it && it.payCoin).toUpperCase() === String(coin).toUpperCase()
|
||||
)
|
||||
return this.getPayImageUrl(hit)
|
||||
} catch (e) { return '' }
|
||||
},
|
||||
/**
|
||||
* 支付方式下拉变更:选择/清空即触发请求
|
||||
* @param {{payChain?: string, payCoin?: string}|null} val
|
||||
@@ -805,4 +889,46 @@ export default {
|
||||
font-size: 16px;
|
||||
}
|
||||
}
|
||||
|
||||
/* 排序表头视觉样式 */
|
||||
.sortable {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
cursor: pointer;
|
||||
color: #334155; /* slate-700 */
|
||||
}
|
||||
.sortable:hover {
|
||||
color: #1e293b; /* slate-800 */
|
||||
}
|
||||
.sort-arrow {
|
||||
width: 0;
|
||||
height: 0;
|
||||
border-left: 5px solid transparent;
|
||||
border-right: 5px solid transparent;
|
||||
}
|
||||
.sort-arrow.asc {
|
||||
border-bottom: 7px solid #64748b; /* slate-500 */
|
||||
}
|
||||
.sort-arrow.desc {
|
||||
border-top: 7px solid #64748b;
|
||||
}
|
||||
.sortable.active { color: #2563eb; } /* 蓝色高亮 */
|
||||
.sort-arrow.active.sort-arrow.asc { border-bottom-color: #2563eb; }
|
||||
.sort-arrow.active.sort-arrow.desc { border-top-color: #2563eb; }
|
||||
|
||||
.amount-more {
|
||||
font-size: 12px;
|
||||
color: #94a3b8; /* slate-400 */
|
||||
margin-left: 4px;
|
||||
}
|
||||
|
||||
|
||||
::v-deep .el-input__prefix, .el-input__suffix{
|
||||
top:24%;
|
||||
}
|
||||
|
||||
::v-deep .el-input--mini .el-input__icon{
|
||||
line-height: 0px;
|
||||
}
|
||||
</style>
|
||||
@@ -72,7 +72,10 @@
|
||||
<img :src="pt.image" :alt="formatPayType(pt)" class="paytype-icon" />
|
||||
</el-tooltip>
|
||||
</div>
|
||||
<div class="right-meta">
|
||||
<span class="product-sold" aria-label="已售数量">已售:{{ product && product.saleNumber != null ? product.saleNumber : 0 }}</span>
|
||||
<span class="shop-name">店铺:{{ product && (product.shopName || product.name) }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -205,6 +208,16 @@ export default {
|
||||
align-items: center;
|
||||
margin-top: 8px;
|
||||
}
|
||||
.right-meta{
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: flex-end;
|
||||
gap: 4px;
|
||||
}
|
||||
.shop-name{
|
||||
color: #64748b;
|
||||
font-size: 12px; /* 与已售一致 */
|
||||
}
|
||||
.product-price {
|
||||
color: #e53e3e;
|
||||
font-weight: bold;
|
||||
|
||||
Binary file not shown.
1
power_leasing/test/css/app.c82f27e9.css
Normal file
1
power_leasing/test/css/app.c82f27e9.css
Normal file
File diff suppressed because one or more lines are too long
@@ -1 +1 @@
|
||||
<!doctype html><html lang=""><head><meta charset="utf-8"><meta http-equiv="X-UA-Compatible" content="IE=edge"><meta name="viewport" content="width=device-width,initial-scale=1"><link rel="icon" href="/favicon.ico"><title>power_leasing</title><script defer="defer" src="/js/chunk-vendors.f4da7ffe.js"></script><script defer="defer" src="/js/app.c7605e06.js"></script><link href="/css/chunk-vendors.10dd4e95.css" rel="stylesheet"><link href="/css/app.4475c0cd.css" rel="stylesheet"></head><body><noscript><strong>We're sorry but power_leasing doesn't work properly without JavaScript enabled. Please enable it to continue.</strong></noscript><div id="app"></div></body></html>
|
||||
<!doctype html><html lang=""><head><meta charset="utf-8"><meta http-equiv="X-UA-Compatible" content="IE=edge"><meta name="viewport" content="width=device-width,initial-scale=1"><link rel="icon" href="/favicon.ico"><title>power_leasing</title><script defer="defer" src="/js/chunk-vendors.f4da7ffe.js"></script><script defer="defer" src="/js/app.d22501a6.js"></script><link href="/css/chunk-vendors.10dd4e95.css" rel="stylesheet"><link href="/css/app.c82f27e9.css" rel="stylesheet"></head><body><noscript><strong>We're sorry but power_leasing doesn't work properly without JavaScript enabled. Please enable it to continue.</strong></noscript><div id="app"></div></body></html>
|
||||
2
power_leasing/test/js/app.d22501a6.js
Normal file
2
power_leasing/test/js/app.d22501a6.js
Normal file
File diff suppressed because one or more lines are too long
1
power_leasing/test/js/app.d22501a6.js.map
Normal file
1
power_leasing/test/js/app.d22501a6.js.map
Normal file
File diff suppressed because one or more lines are too long
Reference in New Issue
Block a user