Compare commits
4 Commits
power-leas
...
power-leas
| Author | SHA1 | Date | |
|---|---|---|---|
| a02c287715 | |||
| c7cee78798 | |||
| 50e5ce8d08 | |||
| bea1aa8e4c |
@@ -87,3 +87,13 @@ export function getMachineInfoById(data) {
|
||||
}
|
||||
|
||||
|
||||
// 查获取商城商品支持的支付方式
|
||||
export function getPayTypes(data) {
|
||||
return request({
|
||||
url: `/lease/product/getPayTypes`,
|
||||
method: 'post',
|
||||
data
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -56,43 +56,43 @@ export function closeShop(id) {
|
||||
|
||||
// 根据 店铺id 查询店铺商品配置信息列表
|
||||
export function getShopConfig(id) {
|
||||
return request({
|
||||
url: `/lease/shop/getShopConfig`,
|
||||
method: 'post',
|
||||
data: { id }
|
||||
})
|
||||
}
|
||||
return request({
|
||||
url: `/lease/shop/getShopConfig`,
|
||||
method: 'post',
|
||||
data: { id }
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
// 新增商铺配置
|
||||
// 新增商铺配置
|
||||
export function addShopConfig(data) {
|
||||
return request({
|
||||
url: `/lease/shop/addShopConfig`,
|
||||
method: 'post',
|
||||
data
|
||||
})
|
||||
}
|
||||
return request({
|
||||
url: `/lease/shop/addShopConfig`,
|
||||
method: 'post',
|
||||
data
|
||||
})
|
||||
}
|
||||
|
||||
// 根据配置id 修改配置
|
||||
// 根据配置id 修改配置
|
||||
export function updateShopConfig(data) {
|
||||
return request({
|
||||
url: `/lease/shop/updateShopConfig`,
|
||||
method: 'post',
|
||||
data
|
||||
})
|
||||
}
|
||||
return request({
|
||||
url: `/lease/shop/updateShopConfig`,
|
||||
method: 'post',
|
||||
data
|
||||
})
|
||||
}
|
||||
|
||||
// 根据配置id 删除配置
|
||||
// 根据配置id 删除配置
|
||||
export function deleteShopConfig(data) {
|
||||
return request({
|
||||
url: `/lease/shop/deleteShopConfig`,
|
||||
method: 'post',
|
||||
data
|
||||
})
|
||||
}
|
||||
return request({
|
||||
url: `/lease/shop/deleteShopConfig`,
|
||||
method: 'post',
|
||||
data
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
// 钱包配置(用于修改卖家钱包地址)----获取链(一级)和币(二级) 下拉列表(获取本系统支持的链和币种)
|
||||
|
||||
// 钱包配置(用于修改卖家钱包地址)----获取链(一级)和币(二级) 下拉列表(获取本系统支持的链和币种)
|
||||
export function getChainAndCoin(data) {
|
||||
return request({
|
||||
url: `/lease/shop/getChainAndCoin`,
|
||||
|
||||
@@ -106,6 +106,28 @@ export function getRecentlyTransaction(data) {
|
||||
})
|
||||
}
|
||||
|
||||
//绑定钱包前查询商品列表
|
||||
export function getProductListForShopWalletConfig(data) {
|
||||
return request({
|
||||
url: `/lease/product/getProductListForShopWalletConfig`,
|
||||
method: 'post',
|
||||
data
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
//设置之前商品列表的新链的机器价格
|
||||
export function updateProductListForShopWalletConfig(data) {
|
||||
return request({
|
||||
url: `/lease/product/updateProductListForShopWalletConfig`,
|
||||
method: 'post',
|
||||
data
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
41
power_leasing/src/utils/amount.js
Normal file
41
power_leasing/src/utils/amount.js
Normal file
@@ -0,0 +1,41 @@
|
||||
// 金额截断显示工具(不补0、不四舍五入)
|
||||
// 规则:
|
||||
// - USDT: 最多6位小数
|
||||
// - ETH: 最多8位小数
|
||||
// - 其他币种: 最多6位小数
|
||||
// 返回 { text, truncated, full }
|
||||
|
||||
export function getMaxDecimalsByCoin() {
|
||||
// 全站统一:最多 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);
|
||||
}
|
||||
|
||||
// 默认 6 位截断(非币种语境也可复用)
|
||||
export function truncateTo6(value) {
|
||||
return truncateAmountRaw(value, 6);
|
||||
}
|
||||
|
||||
@@ -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 '' }
|
||||
@@ -204,16 +304,7 @@ export default {
|
||||
});
|
||||
return
|
||||
}
|
||||
try {
|
||||
const curPath = (this.$route && this.$route.path) || ''
|
||||
const from = curPath.indexOf('/account/orders') === 0 ? 'buyer' : (curPath.indexOf('/account/seller-orders') === 0 ? 'seller' : '')
|
||||
try { if (from) sessionStorage.setItem('orderDetailFrom', from) } catch (e) {}
|
||||
if (from) {
|
||||
this.$router.push({ path: `/account/order-detail/${id}`, query: { from } })
|
||||
} else {
|
||||
this.$router.push(`/account/order-detail/${id}`)
|
||||
}
|
||||
} catch (e) {
|
||||
try { this.$router.push(`/account/order-detail/${id}`) } catch (e) {
|
||||
this.$message({
|
||||
message: '无法跳转到详情页',
|
||||
type: 'error',
|
||||
@@ -253,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>
|
||||
|
||||
|
||||
|
||||
@@ -37,21 +37,22 @@
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="价格范围">
|
||||
<!-- <el-form-item label="价格范围">
|
||||
<el-input :value="product && product.priceRange" disabled />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
|
||||
<el-col :span="12">
|
||||
</el-form-item> -->
|
||||
<el-form-item label="类型">
|
||||
<el-input :value="product && (product.type === 1 ? '算力套餐' : '挖矿机器')" disabled />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
|
||||
<el-col :span="12">
|
||||
<el-form-item label="状态">
|
||||
<el-input :value="product && (product.state === 1 ? '下架' : '上架')" disabled />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
|
||||
</el-col>
|
||||
|
||||
<!-- <el-col :span="24">
|
||||
<el-form-item label="图片">
|
||||
@@ -64,7 +65,7 @@
|
||||
|
||||
<el-col :span="24">
|
||||
<el-form-item label="描述">
|
||||
<el-input type="textarea" :rows="3" :value="product && product.description" disabled />
|
||||
<el-input type="textarea" :rows="3" :value="product && product.description" disabled />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
@@ -76,9 +77,9 @@
|
||||
<div slot="header" class="section-title">机器组合</div>
|
||||
<div v-if="machineList && machineList.length">
|
||||
<el-table :data="machineList" border stripe style="width: 100%">
|
||||
<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 prop="user" label="挖矿账户" />
|
||||
<el-table-column prop="id" label="矿机ID" />
|
||||
<el-table-column prop="miner" label="机器编号" />
|
||||
<el-table-column label="实际算力" width="100">
|
||||
<template slot="header">
|
||||
<el-tooltip content="实际算力为该机器在本矿池过去24H的平均算力" effect="dark" placement="top">
|
||||
@@ -100,11 +101,15 @@
|
||||
:class="{ 'changed-input': isCellChanged(scope.row, 'theoryPower') }"
|
||||
style="max-width: 260px;"
|
||||
>
|
||||
<template slot="append">{{ scope.row.unit || '' }}</template>
|
||||
<template slot="append">
|
||||
<el-select v-model="scope.row.unit" size="mini" :disabled="isRowDisabled(scope.row)" class="append-select append-select--unit" style="width: 90px;">
|
||||
<el-option v-for="u in unitOptions" :key="u" :label="u" :value="u" />
|
||||
</el-select>
|
||||
</template>
|
||||
</el-input>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="功耗(kw/h)" min-width="140">
|
||||
<el-table-column label="功耗(kw/h)" >
|
||||
<template #default="scope">
|
||||
<el-input
|
||||
v-model="scope.row.powerDissipation"
|
||||
@@ -116,16 +121,16 @@
|
||||
:class="{ 'changed-input': isCellChanged(scope.row, 'powerDissipation') }"
|
||||
style="max-width: 260px;"
|
||||
>
|
||||
<template slot="append">kw/h</template>
|
||||
<!-- <template slot="append">kw/h</template> -->
|
||||
</el-input>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="型号" min-width="140">
|
||||
<el-table-column label="型号" >
|
||||
<template #default="scope">
|
||||
<el-input
|
||||
v-model="scope.row.type"
|
||||
size="small"
|
||||
placeholder="矿机型号"
|
||||
|
||||
:maxlength="20"
|
||||
:disabled="isRowDisabled(scope.row)"
|
||||
@input="handleTypeCell(scope.$index)"
|
||||
@@ -134,7 +139,7 @@
|
||||
/>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="售价(USDT)" min-width="140">
|
||||
<el-table-column label="售价" width="188">
|
||||
<template slot="header">
|
||||
<el-tooltip effect="dark" placement="top">
|
||||
<div slot="content">
|
||||
@@ -145,11 +150,11 @@
|
||||
</div>
|
||||
<i class="el-icon-question label-help" aria-label="帮助" tabindex="0"></i>
|
||||
</el-tooltip>
|
||||
<span>售价(USDT)</span>
|
||||
<span>售价(按结算币种)</span>
|
||||
</template>
|
||||
<template slot-scope="scope">
|
||||
<el-input
|
||||
v-model="scope.row.price"
|
||||
v-model="scope.row._priceEditing"
|
||||
size="small"
|
||||
inputmode="decimal"
|
||||
:disabled="isRowDisabled(scope.row)"
|
||||
@@ -158,11 +163,20 @@
|
||||
:class="{ 'changed-input': isCellChanged(scope.row, 'price') }"
|
||||
style="max-width: 260px;"
|
||||
>
|
||||
<template slot="append">USDT</template>
|
||||
<template slot="append">
|
||||
<el-select v-model="scope.row._selectedPayIndex" size="mini" @change="handlePayTypeChange(scope.$index)" class="append-select append-select--coin" style="width:120px;">
|
||||
<el-option
|
||||
v-for="(pt, i) in (scope.row.priceList || [])"
|
||||
:key="pt.payTypeId || i"
|
||||
:label="[String(pt.chain||'').toUpperCase(), String(pt.coin||'').toUpperCase()].filter(Boolean).join('-')"
|
||||
:value="i"
|
||||
/>
|
||||
</el-select>
|
||||
</template>
|
||||
</el-input>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="最大租赁天数(天)" min-width="140">
|
||||
<el-table-column label="最大租赁天数(天)" width="100">
|
||||
<template #default="scope">
|
||||
<el-input
|
||||
v-model="scope.row.maxLeaseDays"
|
||||
@@ -248,6 +262,8 @@ export default {
|
||||
// 可编辑字段快照(用于变更高亮)
|
||||
fieldSnapshot: {},
|
||||
updateLoading:false,
|
||||
// 算力单位选项(与新增出售机器页面保持一致)
|
||||
unitOptions: ['KH/S','MH/S','GH/S','TH/S','PH/S'],
|
||||
}
|
||||
},
|
||||
|
||||
@@ -269,6 +285,15 @@ export default {
|
||||
|
||||
},
|
||||
methods: {
|
||||
/** 结算币种切换时,更新当前编辑价格 */
|
||||
handlePayTypeChange(index) {
|
||||
const row = this.machineList && this.machineList[index]
|
||||
if (!row) return
|
||||
const sel = Number(row._selectedPayIndex || 0)
|
||||
const list = Array.isArray(row.priceList) ? row.priceList : []
|
||||
const target = list[sel] || {}
|
||||
this.$set(this.machineList, index, { ...row, _priceEditing: String(target.price ?? '') })
|
||||
},
|
||||
/**
|
||||
* 判断行是否不可编辑(已售出则禁用)
|
||||
* @param {Object} row - 当前行数据
|
||||
@@ -304,7 +329,13 @@ export default {
|
||||
|
||||
|
||||
if (res && res.code === 200) {
|
||||
this.machineList =res.rows
|
||||
const rows = Array.isArray(res.rows) ? res.rows : []
|
||||
this.machineList = rows.map(r => {
|
||||
const list = Array.isArray(r.priceList) ? r.priceList : []
|
||||
const sel = 0
|
||||
const first = list[sel] || {}
|
||||
return { ...r, _selectedPayIndex: sel, _priceEditing: String(first.price ?? '') }
|
||||
})
|
||||
this.refreshStateSnapshot()
|
||||
this.refreshFieldSnapshot()
|
||||
}
|
||||
@@ -338,11 +369,15 @@ export default {
|
||||
for (let i = 0; i < list.length; i += 1) {
|
||||
const row = list[i]
|
||||
if (!row || typeof row.id === 'undefined') continue
|
||||
const priceMap = {}
|
||||
if (Array.isArray(row.priceList)) {
|
||||
row.priceList.forEach(p => { if (p) priceMap[String(p.payTypeId ?? '')] = String(p.price ?? '') })
|
||||
}
|
||||
snapshot[row.id] = {
|
||||
theoryPower: String(row.theoryPower ?? ''),
|
||||
powerDissipation: String(row.powerDissipation ?? ''),
|
||||
type: String(row.type ?? ''),
|
||||
price: String(row.price ?? ''),
|
||||
priceMap,
|
||||
maxLeaseDays: String(row.maxLeaseDays ?? ''),
|
||||
}
|
||||
}
|
||||
@@ -358,6 +393,14 @@ export default {
|
||||
isCellChanged(row, key) {
|
||||
if (!row || typeof row.id === 'undefined') return false
|
||||
const snap = this.fieldSnapshot[row.id] || {}
|
||||
if (key === 'price') {
|
||||
const sel = Number(row._selectedPayIndex || 0)
|
||||
const pt = Array.isArray(row.priceList) && row.priceList[sel] ? row.priceList[sel] : null
|
||||
const pid = String(pt && pt.payTypeId ? pt.payTypeId : sel)
|
||||
const cur = String(pt && pt.price != null ? pt.price : '')
|
||||
const ori = String((snap.priceMap && snap.priceMap[pid]) || '')
|
||||
return cur !== ori
|
||||
}
|
||||
const current = String(row[key] ?? '')
|
||||
const original = String(snap[key] ?? '')
|
||||
return current !== original
|
||||
@@ -438,7 +481,7 @@ export default {
|
||||
// - 功耗:6 位整数 + 4 位小数
|
||||
// - 价格:12 位整数 + 2 位小数
|
||||
// - 其他:保持原逻辑(6 位小数)
|
||||
let v = String(this.machineList[index][key] ?? '')
|
||||
let v = String(key === 'price' ? (this.machineList[index]._priceEditing ?? '') : (this.machineList[index][key] ?? ''))
|
||||
v = v.replace(/[^0-9.]/g, '')
|
||||
const firstDot = v.indexOf('.')
|
||||
if (firstDot !== -1) {
|
||||
@@ -460,22 +503,35 @@ export default {
|
||||
if (intPart.length > 12) { intPart = intPart.slice(0, 12) }
|
||||
if (decPart) { decPart = decPart.slice(0, 2) }
|
||||
v = decPart.length ? `${intPart}.${decPart}` : (endsWithDot ? `${intPart}.` : intPart)
|
||||
// 同步到当前选中结算币种的价格
|
||||
this.$set(this.machineList[index], '_priceEditing', v)
|
||||
const row = this.machineList[index]
|
||||
const sel = Number(row._selectedPayIndex || 0)
|
||||
if (Array.isArray(row.priceList) && row.priceList[sel]) {
|
||||
this.$set(row.priceList[sel], 'price', v)
|
||||
}
|
||||
} else {
|
||||
if (firstDot !== -1) {
|
||||
const [i, d] = v.split('.')
|
||||
v = i + '.' + (d ? d.slice(0, 6) : '')
|
||||
}
|
||||
}
|
||||
const row = { ...this.machineList[index], [key]: v }
|
||||
this.$set(this.machineList, index, row)
|
||||
if (key !== 'price') {
|
||||
const row = { ...this.machineList[index], [key]: v }
|
||||
this.$set(this.machineList, index, row)
|
||||
}
|
||||
},
|
||||
handlePriceBlur(index) {
|
||||
const raw = String(this.machineList[index].price ?? '')
|
||||
const raw = String(this.machineList[index]._priceEditing ?? '')
|
||||
const pattern = /^\d{1,12}(\.\d{1,2})?$/
|
||||
if (!raw || Number(raw) <= 0 || !pattern.test(raw)) {
|
||||
this.$message.warning('单价必须大于0,整数最多12位,小数最多2位')
|
||||
const row = { ...this.machineList[index], price: '' }
|
||||
this.$set(this.machineList, index, row)
|
||||
this.$set(this.machineList[index], '_priceEditing', '')
|
||||
const row = this.machineList[index]
|
||||
const sel = Number(row._selectedPayIndex || 0)
|
||||
if (Array.isArray(row.priceList) && row.priceList[sel]) {
|
||||
this.$set(row.priceList[sel], 'price', '')
|
||||
}
|
||||
}
|
||||
},
|
||||
handleMaxLeaseDaysInput(index) {
|
||||
@@ -567,7 +623,7 @@ export default {
|
||||
const row = this.machineList[i]
|
||||
const rowLabel = row && (row.miner || row.id || i + 1)
|
||||
const theoryRaw = String(row.theoryPower ?? '')
|
||||
const priceRaw = String(row.price ?? '')
|
||||
const priceRaw = String(row._priceEditing ?? '')
|
||||
const typeRaw = String(row.type ?? '')
|
||||
const dissRaw = String(row.powerDissipation ?? '')
|
||||
const daysRaw = String(row.maxLeaseDays ?? '')
|
||||
@@ -598,7 +654,7 @@ export default {
|
||||
const payload = this.machineList.map(m => ({
|
||||
id: m.id,
|
||||
powerDissipation: Number(m.powerDissipation ?? 0),
|
||||
price: Number(m.price ?? 0),
|
||||
priceList: Array.isArray(m.priceList) ? m.priceList.map(p => ({ ...p, price: Number(p && p.price != null && p.price !== '' ? p.price : 0) })) : [],
|
||||
state: Number(m.state ?? 0),
|
||||
theoryPower: Number(m.theoryPower ?? 0),
|
||||
type: m.type || '',
|
||||
@@ -641,17 +697,51 @@ export default {
|
||||
.empty-text { color: #909399; text-align: center; padding: 12px 0; }
|
||||
|
||||
.label-help { margin-left: 4px; color: #909399; cursor: help; }
|
||||
|
||||
/* ::v-deep .el-form-item__content{
|
||||
margin-left: 52px !important;
|
||||
} */
|
||||
|
||||
</style>
|
||||
<style>
|
||||
.el-input-group__append, .el-input-group__prepend{
|
||||
padding: 0 5px !important;
|
||||
}
|
||||
.account-product-detail .el-table .el-input,
|
||||
.account-product-detail .el-table .el-textarea{
|
||||
width: 94% !important; /* 仅限制表格内输入宽度,避免影响上面的基础信息区域 */
|
||||
}
|
||||
/* 基础信息表单保持满宽,确保“描述”与上方输入左侧对齐 */
|
||||
.account-product-detail .detail-form .el-input,
|
||||
.account-product-detail .detail-form .el-textarea{
|
||||
width: 100% !important;
|
||||
}
|
||||
|
||||
/* 让追加区裁剪内部元素,避免 el-select 下拉箭头溢出到单元格外 */
|
||||
.el-input-group__append,
|
||||
.el-input-group__prepend{
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
/* 追加在输入框右侧的下拉(单位/结算币种)细节优化 */
|
||||
.append-select .el-input__inner{
|
||||
/* 预留更多箭头空间,避免被右侧裁剪 */
|
||||
padding-right: 28px;
|
||||
height: 30px;
|
||||
line-height: 30px;
|
||||
}
|
||||
.append-select .el-select__caret{
|
||||
right: 10px; /* 箭头往内侧移动,防止被裁切 */
|
||||
transform: scale(.85); /* 缩小箭头,保证完全显示 */
|
||||
}
|
||||
.append-select .el-input__icon{
|
||||
line-height: 30px; /* 垂直居中,避免上下被裁切 */
|
||||
}
|
||||
|
||||
/* 变化高亮:为输入框外层添加红色边框,视觉醒目但不改变布局 */
|
||||
.changed-input .el-input__inner,
|
||||
.changed-input input.el-input__inner {
|
||||
.changed-input input.el-input__inner,
|
||||
/* 带有 append 时,同步高亮右侧追加区的边框,保证整体连贯 */
|
||||
.changed-input .el-input-group__append {
|
||||
border-color: #f56c6c !important;
|
||||
}
|
||||
|
||||
|
||||
@@ -56,8 +56,7 @@
|
||||
</el-form-item>
|
||||
<el-form-item label="功耗" prop="powerDissipation">
|
||||
<el-input
|
||||
v-model="form.powerDissipation"
|
||||
placeholder="示例:0.01"
|
||||
v-model="form.powerDissipation"
|
||||
inputmode="decimal"
|
||||
@input="handleNumeric('powerDissipation')"
|
||||
style="width: 50%;"
|
||||
@@ -79,7 +78,22 @@
|
||||
<i class="el-icon-question label-help" aria-label="帮助" tabindex="0"></i>
|
||||
</el-tooltip>
|
||||
</span>
|
||||
<!-- 若商品定义了多个结算币种,则按链-币种动态生成多个售价输入;否则回退为旧的 USDT 单价 -->
|
||||
<div v-if="payTypeDefs && payTypeDefs.length" class="cost-multi">
|
||||
<div v-for="pt in payTypeDefs" :key="pt.key" class="cost-item">
|
||||
<el-input
|
||||
v-model="form.costMap[pt.key]"
|
||||
placeholder="请输入价格"
|
||||
inputmode="decimal"
|
||||
@input="val => handleCostMapInput(pt.key, val)"
|
||||
style="width: 50%;"
|
||||
>
|
||||
<template slot="append">{{ pt.label }}</template>
|
||||
</el-input>
|
||||
</div>
|
||||
</div>
|
||||
<el-input
|
||||
v-else
|
||||
v-model="form.cost"
|
||||
placeholder="请输入成本(USDT)"
|
||||
inputmode="decimal"
|
||||
@@ -158,7 +172,7 @@
|
||||
</div>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="售价(USDT)" min-width="160">
|
||||
<el-table-column label="售价(按结算币种)" min-width="220">
|
||||
<template slot="header">
|
||||
<el-tooltip effect="dark" placement="top">
|
||||
<div slot="content">
|
||||
@@ -169,20 +183,35 @@
|
||||
</div>
|
||||
<i class="el-icon-question label-help" aria-label="帮助" tabindex="0"></i>
|
||||
</el-tooltip>
|
||||
<span>售价(USDT)</span>
|
||||
<span>售价(按结算币种)</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: 100%;"
|
||||
>
|
||||
<template slot="append">USDT</template>
|
||||
</el-input>
|
||||
|
||||
<div class="price-multi">
|
||||
<div v-if="payTypeDefs && payTypeDefs.length" class="price-items">
|
||||
<div v-for="pt in payTypeDefs" :key="pt.key" class="price-item">
|
||||
<el-input
|
||||
v-model="scope.row.priceMap[pt.key]"
|
||||
placeholder="价格"
|
||||
inputmode="decimal"
|
||||
@input="() => handleRowPriceMapInput(scope.$index, pt.key)"
|
||||
@blur="() => handleRowPriceMapBlur(scope.$index, pt.key)"
|
||||
>
|
||||
<template slot="append">{{ pt.label }}</template>
|
||||
</el-input>
|
||||
</div>
|
||||
</div>
|
||||
<el-input
|
||||
v-else
|
||||
v-model="scope.row.price"
|
||||
placeholder="价格"
|
||||
inputmode="decimal"
|
||||
@input="handleRowPriceInput(scope.$index)"
|
||||
@blur="handleRowPriceBlur(scope.$index)"
|
||||
style="width: 100%;"
|
||||
>
|
||||
<template slot="append">USDT</template>
|
||||
</el-input>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
</el-table-column>
|
||||
@@ -265,6 +294,7 @@ export default {
|
||||
type: '',
|
||||
unit: 'TH/S',
|
||||
cost: '',
|
||||
costMap: {}, // { 'CHAIN-COIN': '123.45' }
|
||||
maxLeaseDays: ''
|
||||
},
|
||||
confirmVisible: false,
|
||||
@@ -301,15 +331,18 @@ export default {
|
||||
],
|
||||
unit: [ { required: true, message: '请选择算力单位', trigger: 'change' } ],
|
||||
cost: [
|
||||
{ required: true, message: '请填写机器成本(USDT)', trigger: 'blur' },
|
||||
{
|
||||
validator: (rule, value, callback) => {
|
||||
validator(rule, value, callback) {
|
||||
// 若为多结算币种模式,跳过此校验(统一售价由每种币种的输入框承担)
|
||||
if (Array.isArray(this.payTypeDefs) && this.payTypeDefs.length > 0) {
|
||||
callback()
|
||||
return
|
||||
}
|
||||
const str = String(value || '')
|
||||
if (!str) {
|
||||
callback(new Error('请填写机器成本(USDT)'))
|
||||
return
|
||||
}
|
||||
// 整数最多12位,小数最多2位
|
||||
const pattern = /^\d{1,12}(\.\d{1,2})?$/
|
||||
if (!pattern.test(str)) {
|
||||
callback(new Error('成本整数最多12位,小数最多2位'))
|
||||
@@ -421,6 +454,7 @@ export default {
|
||||
selectedMachineRows: [],
|
||||
saving: false,
|
||||
lastCostBaseline: 0,
|
||||
lastCostMapBaseline: {}, // { key: number }
|
||||
lastTypeBaseline: '',
|
||||
lastMaxLeaseDaysBaseline: 0,
|
||||
lastPowerDissipationBaseline: 0,
|
||||
@@ -455,10 +489,58 @@ export default {
|
||||
}
|
||||
},
|
||||
created() {
|
||||
this.initPayTypesFromRoute()
|
||||
this.fetchMiners()
|
||||
this.lastTypeBaseline = this.form.type
|
||||
// 绑定基于组件实例的校验器,避免 this 丢失
|
||||
if (this.rules && this.rules.cost) {
|
||||
this.$set(this.rules, 'cost', [{ validator: this.validateCost, trigger: 'blur' }])
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
/** 统一售价校验:多结算币种时跳过,单价时按 USDT 校验 */
|
||||
validateCost(rule, value, callback) {
|
||||
if (Array.isArray(this.payTypeDefs) && this.payTypeDefs.length > 0) {
|
||||
callback()
|
||||
return
|
||||
}
|
||||
const str = String(value || '')
|
||||
if (!str) { callback(new Error('请填写机器成本(USDT)')); return }
|
||||
const pattern = /^\d{1,12}(\.\d{1,2})?$/
|
||||
if (!pattern.test(str)) { callback(new Error('成本整数最多12位,小数最多2位')); return }
|
||||
if (Number(str) <= 0) { callback(new Error('成本必须大于 0')); return }
|
||||
callback()
|
||||
},
|
||||
/** 解析路由参数中的支付方式,生成标准定义 */
|
||||
initPayTypesFromRoute() {
|
||||
this.payTypeDefs = []
|
||||
try {
|
||||
const raw = this.$route.query.payTypes
|
||||
if (!raw) return
|
||||
const arr = JSON.parse(decodeURIComponent(raw))
|
||||
if (!Array.isArray(arr)) return
|
||||
const defs = []
|
||||
arr.forEach(it => {
|
||||
const chain = String(it && it.chain ? it.chain : '').toUpperCase()
|
||||
const coin = String(it && it.coin ? it.coin : '').toUpperCase()
|
||||
if (!chain && !coin) return
|
||||
const key = [chain, coin].filter(Boolean).join('-')
|
||||
const label = key
|
||||
defs.push({ chain, coin, key, label })
|
||||
})
|
||||
// 去重
|
||||
const map = new Map()
|
||||
defs.forEach(d => { if (!map.has(d.key)) map.set(d.key, d) })
|
||||
this.payTypeDefs = Array.from(map.values())
|
||||
// 初始化统一售价映射
|
||||
const initCostMap = {}
|
||||
this.payTypeDefs.forEach(d => { initCostMap[d.key] = '' })
|
||||
this.form.costMap = initCostMap
|
||||
this.lastCostMapBaseline = { ...initCostMap }
|
||||
} catch (e) {
|
||||
this.payTypeDefs = []
|
||||
}
|
||||
},
|
||||
handleBack() {
|
||||
this.$router.back()
|
||||
},
|
||||
@@ -516,6 +598,36 @@ export default {
|
||||
this.syncCostToRows()
|
||||
}
|
||||
},
|
||||
/** 顶部多结算币种统一售价输入 */
|
||||
handleCostMapInput(key, val) {
|
||||
// 价格输入:整数最多12位,小数最多2位;允许尾随小数点
|
||||
let v = String(val ?? this.form.costMap[key] ?? '')
|
||||
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 endsWithDot = v.endsWith('.')
|
||||
const parts = v.split('.')
|
||||
let intPart = parts[0] || ''
|
||||
let decPart = parts[1] || ''
|
||||
if (intPart.length > 12) intPart = intPart.slice(0, 12)
|
||||
if (decPart) decPart = decPart.slice(0, 2)
|
||||
v = decPart.length ? `${intPart}.${decPart}` : (endsWithDot ? `${intPart}.` : intPart)
|
||||
this.$set(this.form.costMap, key, v)
|
||||
|
||||
// 同步到行:仅当行对应价格未设置或等于旧基线
|
||||
const oldBaseline = Number(this.lastCostMapBaseline[key] ?? NaN)
|
||||
this.selectedMachineRows = this.selectedMachineRows.map(row => {
|
||||
const cur = Number((row.priceMap && row.priceMap[key]) ?? NaN)
|
||||
const shouldFollow = !Number.isFinite(cur) || cur === oldBaseline
|
||||
const nextPriceMap = { ...(row.priceMap || {}) }
|
||||
if (shouldFollow) nextPriceMap[key] = v
|
||||
return { ...row, priceMap: nextPriceMap }
|
||||
})
|
||||
const num = Number(v)
|
||||
if (Number.isFinite(num)) this.$set(this.lastCostMapBaseline, key, num)
|
||||
},
|
||||
/**
|
||||
* 顶部矿机型号输入:限制20字符
|
||||
*/
|
||||
@@ -563,18 +675,24 @@ export default {
|
||||
if (m) {
|
||||
// 若已存在,沿用已编辑的价格、型号和状态
|
||||
const existed = this.selectedMachineRows.find(r => r.miner === minerId)
|
||||
const existedPriceMap = existed && existed.priceMap ? existed.priceMap : null
|
||||
const defaultPriceMap = {}
|
||||
if (this.payTypeDefs && this.payTypeDefs.length) {
|
||||
this.payTypeDefs.forEach(d => { defaultPriceMap[d.key] = this.form.costMap[d.key] })
|
||||
}
|
||||
nextRows.push({
|
||||
user: m.user,
|
||||
coin: m.coin,
|
||||
miner: m.miner,
|
||||
realPower: m.realPower,
|
||||
price: existed ? existed.price : this.form.cost,
|
||||
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, // 默认上架
|
||||
maxLeaseDays: existed && existed.maxLeaseDays !== undefined ? existed.maxLeaseDays : this.form.maxLeaseDays
|
||||
maxLeaseDays: existed && existed.maxLeaseDays !== undefined ? existed.maxLeaseDays : this.form.maxLeaseDays,
|
||||
priceMap: existedPriceMap || defaultPriceMap
|
||||
})
|
||||
}
|
||||
})
|
||||
@@ -638,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)
|
||||
},
|
||||
/**
|
||||
@@ -667,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)
|
||||
},
|
||||
/**
|
||||
@@ -742,6 +862,38 @@ export default {
|
||||
v = decPart.length ? `${intPart}.${decPart}` : (endsWithDot ? `${intPart}.` : intPart)
|
||||
this.$set(this.selectedMachineRows[index], 'price', v)
|
||||
},
|
||||
/** 行内多结算币种价格输入 */
|
||||
handleRowPriceMapInput(index, key) {
|
||||
// 价格输入:整数最多12位,小数最多2位;允许尾随小数点
|
||||
const row = this.selectedMachineRows[index]
|
||||
const map = { ...(row.priceMap || {}) }
|
||||
let v = String(map[key] ?? '')
|
||||
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 endsWithDot = v.endsWith('.')
|
||||
const parts = v.split('.')
|
||||
let intPart = parts[0] || ''
|
||||
let decPart = parts[1] || ''
|
||||
if (intPart.length > 12) intPart = intPart.slice(0, 12)
|
||||
if (decPart) decPart = decPart.slice(0, 2)
|
||||
v = decPart.length ? `${intPart}.${decPart}` : (endsWithDot ? `${intPart}.` : intPart)
|
||||
map[key] = v
|
||||
this.$set(this.selectedMachineRows[index], 'priceMap', map)
|
||||
},
|
||||
handleRowPriceMapBlur(index, key) {
|
||||
const row = this.selectedMachineRows[index]
|
||||
const raw = String((row.priceMap && row.priceMap[key]) ?? '')
|
||||
const pattern = /^\d{1,12}(\.\d{1,2})?$/
|
||||
if (!raw || Number(raw) <= 0 || !pattern.test(raw)) {
|
||||
this.$message.warning('价格必须大于0,整数最多12位,小数最多2位')
|
||||
const map = { ...(row.priceMap || {}) }
|
||||
map[key] = ''
|
||||
this.$set(this.selectedMachineRows[index], 'priceMap', map)
|
||||
}
|
||||
},
|
||||
handleRowPriceBlur(index) {
|
||||
const raw = String(this.selectedMachineRows[index].price ?? '')
|
||||
const pattern = /^\d{1,12}(\.\d{1,2})?$/
|
||||
@@ -865,14 +1017,27 @@ export default {
|
||||
this.$message.warning('存在行的矿机型号全是空格,请修正后再试')
|
||||
return
|
||||
}
|
||||
// 校验:已选择机器的价格必须大于0
|
||||
// 校验:价格与最大租赁天数
|
||||
for (let i = 0; i < this.selectedMachineRows.length; i += 1) {
|
||||
const row = this.selectedMachineRows[i]
|
||||
const priceNum = Number(row && row.price)
|
||||
if (!Number.isFinite(priceNum) || priceNum <= 0) {
|
||||
const label = (row && (row.miner || row.user)) || i + 1
|
||||
this.$message.warning(`第${i + 1}行(机器:${label}) 价格必须大于0`)
|
||||
return
|
||||
if (this.payTypeDefs && this.payTypeDefs.length) {
|
||||
for (let j = 0; j < this.payTypeDefs.length; j += 1) {
|
||||
const def = this.payTypeDefs[j]
|
||||
const raw = String(row && row.priceMap ? row.priceMap[def.key] : '')
|
||||
const num = Number(raw)
|
||||
if (!/^\d{1,12}(\.\d{1,2})?$/.test(raw) || !Number.isFinite(num) || num <= 0) {
|
||||
const label = (row && (row.miner || row.user)) || i + 1
|
||||
this.$message.warning(`第${i + 1}行(机器:${label}) 价格(${def.label})必须大于0,整数最多12位,小数最多2位`)
|
||||
return
|
||||
}
|
||||
}
|
||||
} else {
|
||||
const priceNum = Number(row && row.price)
|
||||
if (!Number.isFinite(priceNum) || priceNum <= 0) {
|
||||
const label = (row && (row.miner || row.user)) || i + 1
|
||||
this.$message.warning(`第${i + 1}行(机器:${label}) 价格必须大于0`)
|
||||
return
|
||||
}
|
||||
}
|
||||
// 校验:逐行最大租赁天数 1-365
|
||||
const rawDays = String((row && row.maxLeaseDays) ?? '')
|
||||
@@ -891,17 +1056,23 @@ export default {
|
||||
const [user, coin] = this.selectedMiner.split('|')
|
||||
this.saving = true
|
||||
try {
|
||||
// 若是多结算币种,组装 priceList;否则沿用单价字段
|
||||
const payload = {
|
||||
productId: this.form.productId,
|
||||
powerDissipation: this.form.powerDissipation,
|
||||
theoryPower: this.form.theoryPower,
|
||||
type: this.form.type,
|
||||
unit: this.form.unit,
|
||||
cost: this.form.cost,
|
||||
cost: this.payTypeDefs && this.payTypeDefs.length ? Number(this.form.costMap && this.form.costMap[this.payTypeDefs[0].key]) || 0 : this.form.cost,
|
||||
maxLeaseDays: this.form.maxLeaseDays,
|
||||
productMachineURDVos: this.selectedMachineRows.map(r => ({
|
||||
miner: r.miner,
|
||||
price: Number(r.price) || 0,
|
||||
price: this.payTypeDefs && this.payTypeDefs.length ? undefined : (Number(r.price) || 0),
|
||||
priceList: this.payTypeDefs && this.payTypeDefs.length ? this.payTypeDefs.map(d => ({
|
||||
chain: d.chain,
|
||||
coin: d.coin,
|
||||
price: Number(r.priceMap && r.priceMap[d.key]) || 0
|
||||
})) : undefined,
|
||||
state: r.state || 0,
|
||||
type: r.type || this.form.type,
|
||||
user: r.user,
|
||||
@@ -937,6 +1108,20 @@ export default {
|
||||
,
|
||||
watch: {
|
||||
'form.cost': function() { this.syncCostToRows() },
|
||||
// 当统一售价映射变化时手动同步(深度监听)
|
||||
form: {
|
||||
deep: true,
|
||||
handler(val, oldVal) {
|
||||
// 仅在 costMap 深度变化且有多支付类型时,做轻量同步(不覆盖用户已手改的值)
|
||||
if (!this.payTypeDefs || !this.payTypeDefs.length) return
|
||||
if (!val || !val.costMap) return
|
||||
Object.keys(val.costMap).forEach(k => {
|
||||
if (oldVal && oldVal.costMap && val.costMap[k] !== oldVal.costMap[k]) {
|
||||
this.handleCostMapInput(k, val.costMap[k])
|
||||
}
|
||||
})
|
||||
}
|
||||
},
|
||||
'form.type': function() { this.updateMachineType() },
|
||||
'form.maxLeaseDays': function() { this.syncMaxLeaseDaysToRows() },
|
||||
'form.powerDissipation': function() { this.syncPowerDissipationToRows() },
|
||||
@@ -980,5 +1165,37 @@ export default {
|
||||
text-align: left;
|
||||
padding-left: 18px !important;
|
||||
}
|
||||
|
||||
/* 多结算币种价格输入的布局优化 */
|
||||
.cost-multi { display: grid; gap: 8px; }
|
||||
.cost-item { display: flex; align-items: center; }
|
||||
.price-multi { display: grid; gap: 8px; }
|
||||
.price-items { display: grid; gap: 8px; }
|
||||
/* 让 链-币种 附加区同宽、居中显示,整体对齐 */
|
||||
.price-item :deep(.el-input-group__append),
|
||||
.cost-item :deep(.el-input-group__append){
|
||||
width: 110px;
|
||||
min-width: 110px;
|
||||
text-align: center;
|
||||
padding: 0 8px;
|
||||
background: #f8fafc;
|
||||
color: #606266;
|
||||
}
|
||||
/* 缩小输入高度并保持垂直居中 */
|
||||
.price-item :deep(.el-input__inner),
|
||||
.cost-item :deep(.el-input__inner){
|
||||
height: 30px;
|
||||
line-height: 30px;
|
||||
}
|
||||
/* 让组内附加区高度与输入一致 */
|
||||
.price-item :deep(.el-input-group__append),
|
||||
.cost-item :deep(.el-input-group__append){
|
||||
height: 30px;
|
||||
line-height: 30px;
|
||||
}
|
||||
/* 略微收紧间距,让整体更紧凑 */
|
||||
.price-multi { gap: 6px; }
|
||||
.price-items { gap: 6px; }
|
||||
.cost-multi { gap: 6px; }
|
||||
</style>
|
||||
|
||||
|
||||
@@ -30,18 +30,32 @@
|
||||
style="width: 100%"
|
||||
>
|
||||
<!-- <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 prop="name" label="名称" />
|
||||
<el-table-column prop="coin" label="币种" />
|
||||
<el-table-column label="支持结算方式" >
|
||||
<template #default="scope">
|
||||
<div class="paytypes">
|
||||
<el-tooltip
|
||||
v-for="(pt, idx) in (scope.row.payTypes || [])"
|
||||
:key="idx"
|
||||
:content="formatPayType(pt)"
|
||||
placement="top"
|
||||
:open-delay="80"
|
||||
>
|
||||
<img :src="pt.image" :alt="formatPayType(pt)" class="paytype-icon" />
|
||||
</el-tooltip>
|
||||
</div>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<!-- <el-table-column label="算力" min-width="140">
|
||||
<template #default="scope">
|
||||
<span>{{ scope.row.power }} {{ scope.row.unit }}</span>
|
||||
</template>
|
||||
</el-table-column> -->
|
||||
<el-table-column prop="algorithm" label="算法" min-width="120" />
|
||||
<el-table-column prop="algorithm" label="算法" />
|
||||
<!-- <el-table-column prop="electricityBill" label="电费" width="100" /> -->
|
||||
<!-- <el-table-column prop="incomeRate" label="收益率" width="100" /> -->
|
||||
<el-table-column prop="type" label="商品类型" width="130">
|
||||
<el-table-column prop="type" label="商品类型" >
|
||||
<template #default="scope">
|
||||
<el-tag :type="scope.row.type === 1 ? 'success' : 'warning'">
|
||||
{{ scope.row.type === 1 ? '算力套餐' : '挖矿机器' }}
|
||||
@@ -49,9 +63,9 @@
|
||||
</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">
|
||||
<el-table-column prop="saleNumber" label="已售数量" />
|
||||
<el-table-column prop="totalMachineNumber" label="该商品总机器数量" />
|
||||
<el-table-column prop="state" label="状态" >
|
||||
<template #default="scope">
|
||||
<el-tag :type="scope.row.state === 1 ? 'info' : 'success'">
|
||||
{{ scope.row.state === 1 ? '下架' : '上架' }}
|
||||
@@ -149,6 +163,17 @@ export default {
|
||||
this.fetchTableData()
|
||||
},
|
||||
methods: {
|
||||
/** 格式化支持结算币种的展示,如 TRON-USDT */
|
||||
formatPayType(item) {
|
||||
try {
|
||||
const chain = (item && item.chain ? String(item.chain) : '').toUpperCase()
|
||||
const coin = (item && item.coin ? String(item.coin) : '').toUpperCase()
|
||||
if (chain && coin) return `${chain}-${coin}`
|
||||
return chain || coin || ''
|
||||
} catch (e) {
|
||||
return ''
|
||||
}
|
||||
},
|
||||
/** 初始化筛选选项 */
|
||||
initOptions() {
|
||||
try {
|
||||
@@ -372,7 +397,22 @@ export default {
|
||||
this.$message.warning('缺少商品ID')
|
||||
return
|
||||
}
|
||||
this.$router.push({ path: '/account/product-machine-add', query: { productId: row.id, coin: row.coin, name: row.name } })
|
||||
let payTypesParam = ''
|
||||
try {
|
||||
const pts = Array.isArray(row.payTypes) ? row.payTypes : []
|
||||
payTypesParam = encodeURIComponent(JSON.stringify(pts))
|
||||
} catch (e) {
|
||||
payTypesParam = ''
|
||||
}
|
||||
this.$router.push({
|
||||
path: '/account/product-machine-add',
|
||||
query: {
|
||||
productId: row.id,
|
||||
coin: row.coin,
|
||||
name: row.name,
|
||||
payTypes: payTypesParam
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -416,7 +456,12 @@ export default {
|
||||
}
|
||||
|
||||
::v-deep .el-form-item__content{
|
||||
text-align: left;
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
/* 支持结算币种的小图标样式 */
|
||||
.paytypes { display: inline-flex; align-items: center; gap: 8px; }
|
||||
.paytype-icon { width: 22px; height: 22px; border-radius: 4px; display: inline-block; }
|
||||
|
||||
</style>
|
||||
|
||||
|
||||
@@ -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; }
|
||||
@@ -307,7 +326,7 @@ export default {
|
||||
.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-label { color: #666; font-size: 13px; text-align: left; }
|
||||
.detail-value { color: #333; font-size: 13px; text-align: left; }
|
||||
.detail-value.address { font-family: "Monaco", "Menlo", monospace; word-break: break-all; }
|
||||
|
||||
|
||||
@@ -58,13 +58,61 @@
|
||||
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<!-- 绑定前预检测弹窗:若存在关联商品,先提示用户再继续绑定 -->
|
||||
<el-dialog :visible.sync="preCheck.visible" width="80vw" :close-on-click-modal="false" title="检测到关联商品" @close="handlePreCheckClose">
|
||||
<div style="margin-bottom:10px;">
|
||||
<el-alert
|
||||
type="warning"
|
||||
:closable="false"
|
||||
show-icon
|
||||
description="检测到以下商品与本次绑定的链/币相关。继续绑定后,可能需要为这些商品配置该新链下的价格。是否继续?"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<p style="color: red; font-size: 12px; margin-top: 6px;text-align: right;">* 请填写每个商品对应币种的价格,商品包含机器统一设置价格,如需单台修改请在商品列表-详情页操作</p>
|
||||
<el-table :data="preCheck.rows" height="360" border :header-cell-style="{ textAlign: 'left' }" :cell-style="{ textAlign: 'left' }">
|
||||
<el-table-column label="商品名称" min-width="160">
|
||||
<template #default="scope">{{ scope.row.name || scope.row.productName || scope.row.title || scope.row.product || '-' }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="链" min-width="120">
|
||||
<template #default> {{ (form.chain || '').toUpperCase() }} </template>
|
||||
</el-table-column>
|
||||
<el-table-column label="币种" min-width="120">
|
||||
<template #default> {{ form.payCoin.split(',').map(s=>s.trim().toUpperCase()).join(',') }} </template>
|
||||
</el-table-column>
|
||||
<el-table-column label="总矿机数" min-width="100">
|
||||
<template #default="scope">{{ scope.row.totalMachineNumber != null ? scope.row.totalMachineNumber : (scope.row.total || scope.row.totalMachines || '-') }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="商品状态" min-width="100">
|
||||
<template #default="scope">{{ Number(scope.row.state) === 1 ? '下架' : '上架' }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column v-for="sym in coinsForBind" :key="'price-'+sym" :label="sym + ' 价格'" min-width="160">
|
||||
<template #default="scope">
|
||||
<el-input
|
||||
v-model="preCheck.rowPrices[getRowKey(scope.row, scope.$index)][sym]"
|
||||
size="mini"
|
||||
class="price-input"
|
||||
placeholder="请输入"
|
||||
inputmode="decimal"
|
||||
>
|
||||
<template #append>{{ sym }}</template>
|
||||
</el-input>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
|
||||
<template #footer>
|
||||
<el-button @click="preCheck.visible = false">取消</el-button>
|
||||
<el-button type="primary" :disabled="!canSubmitPreCheck" @click="handleConfirmBindAfterPreview">继续绑定</el-button>
|
||||
</template>
|
||||
</el-dialog>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { getMyShop } from "@/api/shops";
|
||||
import { getChainAndList, addWalletShopConfig } from "../../api/wallet";
|
||||
import { getChainAndList, addWalletShopConfig,getProductListForShopWalletConfig,updateProductListForShopWalletConfig } from "../../api/wallet";
|
||||
|
||||
export default {
|
||||
name: "AccountShopConfig",
|
||||
@@ -116,6 +164,8 @@ export default {
|
||||
// },
|
||||
],
|
||||
loading: false,
|
||||
// 绑定前预检测弹窗数据
|
||||
preCheck: { visible: false, rows: [], prices: {}, rowPrices: {} },
|
||||
};
|
||||
},
|
||||
mounted() {
|
||||
@@ -282,14 +332,159 @@ export default {
|
||||
return
|
||||
}
|
||||
|
||||
this.FetchAddWalletShopConfig(this.form);
|
||||
// 新增步骤:绑定前预检测商品列表
|
||||
this.preCheckBeforeBind()
|
||||
},
|
||||
/**
|
||||
* 绑定前预检测:若接口返回有关联商品,则弹窗展示;否则直接走绑定流程
|
||||
*/
|
||||
async preCheckBeforeBind() {
|
||||
try {
|
||||
this.loading = true
|
||||
const params = { chain: this.form.chain, payCoin: this.form.payCoin }
|
||||
const res = await getProductListForShopWalletConfig(params)
|
||||
const rows = Array.isArray(res && res.data) ? res.data : (Array.isArray(res && res.rows) ? res.rows : [])
|
||||
if (rows && rows.length) {
|
||||
this.preCheck.rows = rows
|
||||
// 初始化各币种价格输入
|
||||
const coins = (this.form.payCoin || '').split(',').map(s => s.trim().toUpperCase()).filter(Boolean)
|
||||
const map = {}
|
||||
coins.forEach(c => { if (!(c in this.preCheck.prices)) map[c] = '' })
|
||||
this.preCheck.prices = { ...map, ...this.preCheck.prices }
|
||||
// 初始化每行的价格容器
|
||||
this.preCheck.rowPrices = this.preCheck.rowPrices || {}
|
||||
this.preCheck.rows.forEach((r, idx) => {
|
||||
const key = this.getRowKey(r, idx)
|
||||
if (!this.preCheck.rowPrices[key]) this.$set(this.preCheck.rowPrices, key, {})
|
||||
coins.forEach(c => { if (!(c in this.preCheck.rowPrices[key])) this.$set(this.preCheck.rowPrices[key], c, '') })
|
||||
})
|
||||
this.preCheck.visible = true
|
||||
} else {
|
||||
// 无关联商品,直接绑定并设置(机器列表为空)
|
||||
await this.submitBindWithPrice([])
|
||||
}
|
||||
} catch (e) {
|
||||
// 接口异常不阻塞绑定流程
|
||||
await this.submitBindWithPrice([])
|
||||
} finally {
|
||||
this.loading = false
|
||||
}
|
||||
},
|
||||
handleConfirmBindAfterPreview() {
|
||||
// 校验价格必填
|
||||
const coins = (this.form.payCoin || '').split(',').map(s => s.trim().toUpperCase()).filter(Boolean)
|
||||
// 逐行校验
|
||||
for (let i = 0; i < this.preCheck.rows.length; i++) {
|
||||
const row = this.preCheck.rows[i]
|
||||
const key = this.getRowKey(row, i)
|
||||
const priceMap = (this.preCheck.rowPrices && this.preCheck.rowPrices[key]) || {}
|
||||
for (const c of coins) {
|
||||
const v = priceMap[c]
|
||||
if (!v || Number(v) <= 0) {
|
||||
this.$message.warning(`请填写第 ${i + 1} 行 ${c} 的价格`)
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
const groups = this.collectMachineGroups(this.preCheck.rows)
|
||||
this.preCheck.visible = false
|
||||
this.submitBindWithPrice(groups)
|
||||
},
|
||||
/** 收集每一行对应的机器ID分组(兼容不同返回结构) */
|
||||
collectMachineGroups(rows) {
|
||||
const groups = []
|
||||
const pushId = (arr, id) => { if (id != null && id !== '') arr.push(id) };
|
||||
(rows || []).forEach((r, idx) => {
|
||||
const ids = []
|
||||
// 兼容多种返回结构,优先使用接口包含的 machineList(每台矿机对象含 productMachineId)
|
||||
if (Array.isArray(r && r.machineList)) r.machineList.forEach(m => pushId(ids, m && (m.productMachineId != null ? m.productMachineId : m.id)))
|
||||
if (Array.isArray(r && r.productMachineIdList)) r.productMachineIdList.forEach(id => pushId(ids, id))
|
||||
if (r && r.productMachineId != null) pushId(ids, r.productMachineId)
|
||||
if (Array.isArray(r && r.productMachineDtoList)) r.productMachineDtoList.forEach(m => pushId(ids, (m && (m.productMachineId != null ? m.productMachineId : m.id))))
|
||||
if (Array.isArray(r && r.machines)) r.machines.forEach(m => pushId(ids, (m && (m.productMachineId != null ? m.productMachineId : m.id))))
|
||||
if (Array.isArray(r && r.items)) r.items.forEach(m => pushId(ids, (m && (m.productMachineId != null ? m.productMachineId : m.id))))
|
||||
const key = this.getRowKey(r, idx)
|
||||
groups.push({ key, machineIds: ids })
|
||||
})
|
||||
return groups
|
||||
},
|
||||
/** 生成某一行的 key(优先 productId/id) */
|
||||
getRowKey(row, index) {
|
||||
if (row && row.productId != null) return String(row.productId)
|
||||
if (row && row.id != null) return `p-${row.id}`
|
||||
return `idx-${index}`
|
||||
},
|
||||
/** 提交绑定:使用 updateProductListForShopWalletConfig 完成绑定与价格设置 */
|
||||
async submitBindWithPrice(machineGroups) {
|
||||
try {
|
||||
this.loading = true
|
||||
const coins = (this.form.payCoin || '').split(',').map(s => s.trim().toUpperCase()).filter(Boolean)
|
||||
const list = []
|
||||
if (Array.isArray(machineGroups) && machineGroups.length) {
|
||||
machineGroups.forEach(g => {
|
||||
const priceMap = (this.preCheck.rowPrices && this.preCheck.rowPrices[g.key]) || {}
|
||||
const priceStr = coins.map(c => priceMap[c] || '').join(',');
|
||||
(g.machineIds || []).forEach(id => { list.push({ productMachineId: id, price: priceStr }) })
|
||||
})
|
||||
}
|
||||
const payload = {
|
||||
chain: this.form.chain,
|
||||
symbol: this.form.payCoin,
|
||||
payAddress: this.form.payAddress,
|
||||
productMachineForWalletConfigVoList: list
|
||||
}
|
||||
const res = await updateProductListForShopWalletConfig(payload)
|
||||
if (res && (res.code === 0 || res.code === 200)) {
|
||||
|
||||
this.preCheck.visible = false
|
||||
this.resetPreCheckPrices()
|
||||
this.$message.success('绑定成功')
|
||||
this.$router.push('/account/shops')
|
||||
}else{
|
||||
this.preCheck.visible = true
|
||||
}
|
||||
} catch (e) {
|
||||
// 错误交由全局拦截或简单提示
|
||||
} finally {
|
||||
this.loading = false
|
||||
}
|
||||
},
|
||||
handleReset() {
|
||||
this.form = { chain: "", payAddress: "", payCoin: "" };
|
||||
this.value = []
|
||||
},
|
||||
// 清空预检测中的价格输入
|
||||
resetPreCheckPrices() {
|
||||
try {
|
||||
this.preCheck.prices = {}
|
||||
this.preCheck.rowPrices = {}
|
||||
} catch (e) { /* noop */ }
|
||||
},
|
||||
// 弹窗关闭时清空价格输入
|
||||
handlePreCheckClose() {
|
||||
this.resetPreCheckPrices()
|
||||
},
|
||||
},
|
||||
computed: {
|
||||
coinsForBind() {
|
||||
return (this.form.payCoin || '').split(',').map(s => s.trim().toUpperCase()).filter(Boolean)
|
||||
},
|
||||
canSubmitPreCheck() {
|
||||
if (!this.preCheck || !this.preCheck.visible) return false
|
||||
const coins = this.coinsForBind
|
||||
if (!coins.length) return false
|
||||
// 所有行都需要填写
|
||||
for (let i = 0; i < (this.preCheck.rows || []).length; i++) {
|
||||
const row = this.preCheck.rows[i]
|
||||
const key = this.getRowKey(row, i)
|
||||
const priceMap = (this.preCheck.rowPrices && this.preCheck.rowPrices[key]) || {}
|
||||
for (const c of coins) {
|
||||
const v = priceMap[c]
|
||||
if (!v || Number(v) <= 0) return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
},
|
||||
/**
|
||||
* 已选择币种的可读展示(中文顿号分隔)
|
||||
*/
|
||||
@@ -353,5 +548,11 @@ export default {
|
||||
.selected-coins { display: flex; flex-wrap: wrap; gap: 8px; min-height: 32px; align-items: center; margin-left: 79px;}
|
||||
.selected-coins .el-tag { border-radius: 4px; }
|
||||
.selected-coins .placeholder { color: #c0c4cc; }
|
||||
|
||||
/* 价格输入框获得焦点时高亮为红色,提示用户输入 */
|
||||
.price-input :deep(.el-input__inner:focus) {
|
||||
border-color: #f56c6c !important;
|
||||
box-shadow: 0 0 0 1px #f56c6c inset;
|
||||
}
|
||||
</style>
|
||||
|
||||
|
||||
@@ -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,22 +38,99 @@
|
||||
: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="算法" />
|
||||
|
||||
<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 prop="powerDissipation" label="功耗(kw/h)">
|
||||
<template #default="scope">
|
||||
<span class="num-strong">
|
||||
<el-tooltip
|
||||
v-if="formatNum6(scope.row.powerDissipation).truncated"
|
||||
:content="formatNum6(scope.row.powerDissipation).full"
|
||||
placement="top"
|
||||
>
|
||||
<span>
|
||||
{{ formatNum6(scope.row.powerDissipation).text }}
|
||||
<i class="el-icon-more amount-more"></i>
|
||||
</span>
|
||||
</el-tooltip>
|
||||
<span v-else>{{ formatNum6(scope.row.powerDissipation).text }}</span>
|
||||
</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="theoryPower" label="理论算力">
|
||||
<template #default="scope">
|
||||
<span class="num-strong">
|
||||
<el-tooltip
|
||||
v-if="formatNum6(scope.row.theoryPower).truncated"
|
||||
:content="formatNum6(scope.row.theoryPower).full"
|
||||
placement="top"
|
||||
>
|
||||
<span>
|
||||
{{ formatNum6(scope.row.theoryPower).text }}
|
||||
<i class="el-icon-more amount-more"></i>
|
||||
</span>
|
||||
</el-tooltip>
|
||||
<span v-else>{{ formatNum6(scope.row.theoryPower).text }}</span>
|
||||
</span>
|
||||
<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>
|
||||
<template #default="scope">
|
||||
<span class="num-strong">
|
||||
<el-tooltip
|
||||
v-if="formatNum6(scope.row.computingPower).truncated"
|
||||
:content="formatNum6(scope.row.computingPower).full"
|
||||
placement="top"
|
||||
>
|
||||
<span>
|
||||
{{ formatNum6(scope.row.computingPower).text }}
|
||||
<i class="el-icon-more amount-more"></i>
|
||||
</span>
|
||||
</el-tooltip>
|
||||
<span v-else>{{ formatNum6(scope.row.computingPower).text }}</span>
|
||||
</span>
|
||||
<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>
|
||||
<template #default="scope">
|
||||
<span class="num-strong">
|
||||
<el-tooltip
|
||||
v-if="formatNum6(scope.row.theoryIncome).truncated"
|
||||
:content="formatNum6(scope.row.theoryIncome).full"
|
||||
placement="top"
|
||||
>
|
||||
<span>
|
||||
{{ formatNum6(scope.row.theoryIncome).text }}
|
||||
<i class="el-icon-more amount-more"></i>
|
||||
</span>
|
||||
</el-tooltip>
|
||||
<span v-else>{{ formatNum6(scope.row.theoryIncome).text }}</span>
|
||||
</span>
|
||||
<span v-show="scope.row.coin"> {{ toUpperText(scope.row.coin) }} </span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="theoryUsdtIncome" label="单机理论收入(每日/USDT)">
|
||||
<template #default="scope">
|
||||
<span class="num-strong">
|
||||
<el-tooltip
|
||||
v-if="formatNum6(scope.row.theoryUsdtIncome).truncated"
|
||||
:content="formatNum6(scope.row.theoryUsdtIncome).full"
|
||||
placement="top"
|
||||
>
|
||||
<span>
|
||||
{{ formatNum6(scope.row.theoryUsdtIncome).text }}
|
||||
<i class="el-icon-more amount-more"></i>
|
||||
</span>
|
||||
</el-tooltip>
|
||||
<span v-else>{{ formatNum6(scope.row.theoryUsdtIncome).text }}</span>
|
||||
</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'">
|
||||
@@ -60,9 +138,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,65 +187,209 @@
|
||||
</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;">
|
||||
已选机器:<b>{{ selectedMachineCount }}</b> 台
|
||||
<span style="margin-left:12px;">金额合计(USDT):<b>{{ formatTrunc(selectedTotal, 2) }}</b></span>
|
||||
<span style="margin-left:12px;">金额合计(USDT):</span>
|
||||
<span class="price-strong">
|
||||
<el-tooltip
|
||||
v-if="formatAmount(selectedTotal, 'USDT').truncated"
|
||||
:content="formatAmount(selectedTotal, 'USDT').full"
|
||||
placement="top"
|
||||
>
|
||||
<span>
|
||||
{{ formatAmount(selectedTotal, 'USDT').text }}
|
||||
<i class="el-icon-more amount-more"></i>
|
||||
</span>
|
||||
</el-tooltip>
|
||||
<span v-else>{{ formatAmount(selectedTotal, 'USDT').text }}</span>
|
||||
</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>
|
||||
<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
|
||||
: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>
|
||||
</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>
|
||||
</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 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>
|
||||
<template v-if="grp.coinSymbol">
|
||||
<span v-if="grp.enough"
|
||||
style="color:#16a34a;font-weight:600;">
|
||||
已满足起付额
|
||||
{{ formatAmount(grp.deductibleAmount || 0, grp.coinSymbol).text }}
|
||||
</span>
|
||||
<span v-else
|
||||
style="color:#ef4444;font-weight:600;">
|
||||
金额不足最低起付额
|
||||
{{ formatAmount(grp.deductibleAmount || 0, grp.coinSymbol).text }}
|
||||
,收取手续费
|
||||
{{ formatAmount(grp.fee || 0, grp.coinSymbol).text }}
|
||||
</span>
|
||||
</template>
|
||||
</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>单价({{ 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>小计({{ 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>
|
||||
<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,24 +397,7 @@
|
||||
</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秒后才可关闭 -->
|
||||
<el-dialog :visible.sync="noticeDialog.visible" width="680px" title="下单须知" :show-close="false" :close-on-click-modal="false" :close-on-press-escape="false">
|
||||
@@ -268,6 +491,7 @@
|
||||
<script>
|
||||
import { getGoodsList, deleteBatchGoods as apiDeleteBatchGoods ,deleteBatchGoodsForIsDelete} from '../../api/shoppingCart'
|
||||
import { addOrders,cancelOrder,getOrdersByIds,getOrdersByStatus , getChainAndListForSeller,getCoinPrice } from '../../api/order'
|
||||
import { truncateAmountByCoin, truncateTo6 } from '../../utils/amount'
|
||||
|
||||
export default {
|
||||
name: 'Cart',
|
||||
@@ -279,7 +503,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 +513,8 @@ export default {
|
||||
noticeTimer: null,
|
||||
// 待结算的店铺信息
|
||||
pendingCheckoutShop: null,
|
||||
// 多店铺统一结算的临时选择
|
||||
pendingCheckoutAll: null,
|
||||
// 谷歌验证码弹窗
|
||||
googleCodeDialog: {
|
||||
visible: false,
|
||||
@@ -301,7 +527,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 +607,23 @@ 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)
|
||||
},
|
||||
/**
|
||||
* 数值格式化(最多6位小数,截断不四舍五入,不补0)
|
||||
* @param {number|string} value
|
||||
* @returns {{text:string,truncated:boolean,full:string}}
|
||||
*/
|
||||
formatNum6(value) {
|
||||
return truncateTo6(value)
|
||||
},
|
||||
/**
|
||||
* 将金额转为整分整数,避免浮点误差
|
||||
* @param {number|string} v
|
||||
@@ -476,6 +721,140 @@ export default {
|
||||
},
|
||||
// 获取所有商品分组(兼容保留,现直接返回空,因已移除中间商品层)
|
||||
getAllGroups() { return [] },
|
||||
// 生成店铺的支付方式选项,基于 totalPriceList
|
||||
getShopPayOptions(shop) {
|
||||
// 下拉渲染严格以 payConfigList 为准(接口定义的可选支付方式)
|
||||
const cfg = Array.isArray(shop && shop.payConfigList) ? shop.payConfigList : []
|
||||
return cfg.map(c => {
|
||||
const chain = c && c.payChain ? String(c.payChain) : ''
|
||||
const coin = c && c.payCoin ? String(c.payCoin) : ''
|
||||
const key = `${chain}|${coin}`
|
||||
return {
|
||||
label: `${chain} - ${this.toUpperText(coin)}`,
|
||||
value: key,
|
||||
icon: c && c.payCoinImage ? c.payCoinImage : ''
|
||||
}
|
||||
})
|
||||
},
|
||||
// 判断某机器在当前店铺选择的支付方式下是否有可用单价
|
||||
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 +909,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 +1007,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 +1021,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 +1196,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 +1253,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 +1358,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()
|
||||
}
|
||||
// 已取消支付方式选择步骤,此函数保留空实现以兼容旧调用
|
||||
return
|
||||
},
|
||||
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,26 +1402,16 @@ 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),
|
||||
user: m.user,
|
||||
coin: this.toUpperText(this.selectedCoin || ''),
|
||||
user: m.user,
|
||||
miner: m.miner,
|
||||
unitPrice: Number(unitPrice || 0),
|
||||
leaseTime: leaseDays,
|
||||
@@ -1044,10 +1419,115 @@ 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 cfgList = Array.isArray(shop && shop.payConfigList) ? shop.payConfigList : []
|
||||
const cfgHit = cfgList.find(c => String(c && c.payChain).toUpperCase() === String(chain).toUpperCase()
|
||||
&& String(c && c.payCoin).toUpperCase() === String(coin).toUpperCase())
|
||||
const deductibleAmount = Number((cfgHit && cfgHit.deductibleAmount) || 0)
|
||||
const fee = Number((cfgHit && cfgHit.fee) || 0)
|
||||
const groupSubtotal = items.reduce((sum, it) => sum + Number(it.subtotal || 0), 0)
|
||||
const enough = groupSubtotal >= deductibleAmount || deductibleAmount <= 0
|
||||
const grp = {
|
||||
shopId: shop.id,
|
||||
shopName: shop.name || '',
|
||||
coinSymbol,
|
||||
payLabel,
|
||||
items,
|
||||
deductibleAmount,
|
||||
fee,
|
||||
enough,
|
||||
groupSubtotal
|
||||
}
|
||||
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) {
|
||||
let tCents = items.reduce((acc, it) => centsAdd(acc, it.subtotal || 0), 0)
|
||||
// 若未满足起付额,合计中加入手续费
|
||||
if (!enough && fee > 0) tCents = tCents + this.toCents(fee)
|
||||
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 cfgList = Array.isArray(shop && shop.payConfigList) ? shop.payConfigList : []
|
||||
const cfgHit = cfgList.find(c => String(c && c.payChain).toUpperCase() === String(chain).toUpperCase()
|
||||
&& String(c && c.payCoin).toUpperCase() === String(coin).toUpperCase())
|
||||
const deductibleAmount = Number((cfgHit && cfgHit.deductibleAmount) || 0)
|
||||
const fee = Number((cfgHit && cfgHit.fee) || 0)
|
||||
const rows = []
|
||||
const list = Array.isArray(shop.productMachineDtoList) ? shop.productMachineDtoList : []
|
||||
let groupSubtotal = 0
|
||||
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
|
||||
})
|
||||
groupSubtotal += subtotal
|
||||
const prev = totalsCentsByCoin.get(coinSymbol) || 0
|
||||
totalsCentsByCoin.set(coinSymbol, prev + this.toCents(subtotal))
|
||||
count += 1
|
||||
})
|
||||
if (rows.length) {
|
||||
const enough = groupSubtotal >= deductibleAmount || deductibleAmount <= 0
|
||||
// 未满足起付额:总额加上手续费
|
||||
if (!enough && fee > 0) {
|
||||
const prev = totalsCentsByCoin.get(coinSymbol) || 0
|
||||
totalsCentsByCoin.set(coinSymbol, prev + this.toCents(fee))
|
||||
}
|
||||
groups.push({
|
||||
shopId: shop.id,
|
||||
shopName: shop.name || '',
|
||||
coinSymbol,
|
||||
payLabel,
|
||||
items: rows,
|
||||
deductibleAmount,
|
||||
fee,
|
||||
enough,
|
||||
groupSubtotal
|
||||
})
|
||||
}
|
||||
})
|
||||
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 +2013,27 @@ export default {
|
||||
.dialog-footer {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.amount-more {
|
||||
font-size: 12px;
|
||||
color: #94a3b8; /* slate-400 */
|
||||
margin-left: 4px;
|
||||
}
|
||||
|
||||
.num-strong {
|
||||
font-weight: inherit;
|
||||
color: inherit;
|
||||
}
|
||||
|
||||
|
||||
::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>
|
||||
@@ -1,8 +1,9 @@
|
||||
|
||||
import { getProductById } from '../../utils/productService'
|
||||
import { addToCart } from '../../utils/cartManager'
|
||||
import { getMachineInfo } from '../../api/products'
|
||||
import { getMachineInfo, getPayTypes } from '../../api/products'
|
||||
import { addCart, getGoodsList } from '../../api/shoppingCart'
|
||||
import { truncateAmountByCoin, truncateTo6 } from '../../utils/amount'
|
||||
|
||||
export default {
|
||||
name: 'ProductDetail',
|
||||
@@ -13,9 +14,39 @@ export default {
|
||||
// 默认展开的行keys
|
||||
expandedRowKeys: [],
|
||||
selectedMap: {},
|
||||
// 新接口:单层矿机列表 & 支付方式
|
||||
machineList: [],
|
||||
paymentMethodList: [],
|
||||
// 筛选状态
|
||||
selectedPayKey: null,
|
||||
filters: {
|
||||
chain: '',
|
||||
coin: '',
|
||||
minPrice: null,
|
||||
maxPrice: null,
|
||||
minPower: null,
|
||||
maxPower: null,
|
||||
minPowerDissipation: null,
|
||||
maxPowerDissipation: null,
|
||||
unit: 'GH/S'
|
||||
},
|
||||
// 实际算力单位选项
|
||||
powerUnitOptions: ['KH/S', 'MH/S', 'GH/S', 'TH/S', 'PH/S'],
|
||||
// 排序状态:true 升序,false 降序
|
||||
sortStates: {
|
||||
priceSort: true,
|
||||
powerSort: true,
|
||||
powerDissipationSort: true
|
||||
},
|
||||
// 当前激活的排序字段(仅当用户点击后才会传参)
|
||||
activeSortField: '',
|
||||
// 首次进入时是否已按价格币种设置过支付方式筛选默认值
|
||||
payFilterDefaultApplied: false,
|
||||
params: {
|
||||
id: "",
|
||||
|
||||
pageNum: 1,
|
||||
pageSize: 10,
|
||||
|
||||
|
||||
},
|
||||
confirmAddDialog: {
|
||||
@@ -105,13 +136,17 @@ export default {
|
||||
// number:2001,
|
||||
// cost:"1000",//价格
|
||||
// },
|
||||
|
||||
|
||||
],
|
||||
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
|
||||
@@ -119,7 +154,8 @@ 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('商品不存在')
|
||||
this.product = false
|
||||
@@ -127,6 +163,110 @@ 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)
|
||||
},
|
||||
// 数值格式化:最多6位小数,截断不补0
|
||||
formatNum6(value) {
|
||||
return truncateTo6(value)
|
||||
},
|
||||
/**
|
||||
* 首次加载时,将“支付方式筛选”的默认选中值设为与价格列币种一致,
|
||||
* 并同步 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]
|
||||
if (raw === null || raw === undefined || raw === '') return
|
||||
const n = Number(raw)
|
||||
if (Number.isFinite(n) && n > 0) q[name] = n
|
||||
}
|
||||
// 支付方式条件:有值才传
|
||||
if (this.filters.chain && String(this.filters.chain).trim()) q.chain = String(this.filters.chain).trim()
|
||||
if (this.filters.coin && String(this.filters.coin).trim()) q.coin = String(this.filters.coin).trim()
|
||||
if (this.filters.unit && String(this.filters.unit).trim()) q.unit = String(this.filters.unit).trim()
|
||||
addNum(this.filters, 'minPrice', 'minPrice')
|
||||
addNum(this.filters, 'maxPrice', 'maxPrice')
|
||||
addNum(this.filters, 'minPower', 'minPower')
|
||||
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
|
||||
},
|
||||
// 拉取支付方式
|
||||
async fetchPayTypes() {
|
||||
try {
|
||||
const res = await getPayTypes({ productId: this.params.id })
|
||||
// 接口示例:{ code: 0, data: [ { payChain, payCoin, payCoinImage, shopId } ], msg: '' }
|
||||
if (res && (res.code === 0 || res.code === 200)) {
|
||||
const list = Array.isArray(res.data) ? res.data : []
|
||||
this.paymentMethodList = list
|
||||
// 支付方式加载后尝试设置默认筛选
|
||||
this.ensureDefaultPayFilterSelection()
|
||||
}
|
||||
} catch (e) {
|
||||
// 忽略错误,保持页面可用
|
||||
this.paymentMethodList = []
|
||||
}
|
||||
},
|
||||
|
||||
async fetchGetMachineInfo(params) {
|
||||
this.productDetailLoading = true
|
||||
@@ -134,31 +274,29 @@ export default {
|
||||
console.log(res)
|
||||
if (res && res.code === 200) {
|
||||
console.log(res.data, 'res.rows');
|
||||
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)
|
||||
const firstMachineId = Array.isArray(group.productMachines) && group.productMachines.length > 0 ? group.productMachines[0].id : undefined
|
||||
// 为机器行设置默认租赁天数为1,并确保未选中状态
|
||||
const normalizedMachines = Array.isArray(group.productMachines)
|
||||
? group.productMachines.map(m => ({
|
||||
...m,
|
||||
leaseTime: (m && m.leaseTime && Number(m.leaseTime) > 0) ? Number(m.leaseTime) : 1,
|
||||
_selected: false // 确保所有机器行初始状态为未选中
|
||||
}))
|
||||
: []
|
||||
return { ...group, id: groupId || (firstMachineId ? `m-${firstMachineId}` : fallbackId), productMachines: normalizedMachines }
|
||||
})
|
||||
|
||||
this.productListData = withKeys
|
||||
if (this.productListData.length && (!this.expandedRowKeys || !this.expandedRowKeys.length)) {
|
||||
this.expandedRowKeys = [this.productListData[0].id]
|
||||
}
|
||||
// 产品机器加载完成后,依据购物车集合执行一次本地禁用与勾选
|
||||
this.total = res.total||0;
|
||||
// 新数据结构:机器为扁平 rows 列表;仅当后端返回有效支付方式时才覆盖,避免清空 getPayTypes 的结果
|
||||
try {
|
||||
const payList = res && res.data && res.data.payConfigList
|
||||
if (Array.isArray(payList) && payList.length) {
|
||||
this.paymentMethodList = payList
|
||||
}
|
||||
} catch (e) { /* keep existing paymentMethodList */ }
|
||||
const rows = (res && res.data && (res.data.rows || res.data.list)) || (res && res.rows) || []
|
||||
const normalized = (Array.isArray(rows) ? rows : []).map((m, idx) => ({
|
||||
...m,
|
||||
id: m && (m.id !== undefined && m.id !== null) ? m.id : `m-${idx}`,
|
||||
leaseTime: (m && m.leaseTime && Number(m.leaseTime) > 0) ? Number(m.leaseTime) : 1,
|
||||
_selected: false
|
||||
}))
|
||||
this.machineList = normalized
|
||||
// 清空旧的两层结构数据,避免误用
|
||||
this.productListData = []
|
||||
this.expandedRowKeys = []
|
||||
// 机器加载后尝试设置默认筛选
|
||||
this.ensureDefaultPayFilterSelection()
|
||||
this.$nextTick(() => {
|
||||
this.machinesLoaded = true
|
||||
// 已取消与购物车对比:不再自动禁用或勾选
|
||||
})
|
||||
}
|
||||
|
||||
@@ -175,17 +313,17 @@ export default {
|
||||
|
||||
if (!this.product) {
|
||||
this.$message({
|
||||
message: '商品不存在',
|
||||
type: 'error',
|
||||
showClose: true
|
||||
message: '商品不存在',
|
||||
type: 'error',
|
||||
showClose: true
|
||||
})
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('加载商品详情失败:', error)
|
||||
this.$message({
|
||||
message: '加载商品详情失败,请稍后重试',
|
||||
type: 'error',
|
||||
showClose: true
|
||||
message: '加载商品详情失败,请稍后重试',
|
||||
type: 'error',
|
||||
showClose: true
|
||||
})
|
||||
} finally {
|
||||
this.loading = false
|
||||
@@ -194,7 +332,7 @@ export default {
|
||||
//加入购物车
|
||||
async fetchAddCart(params) {
|
||||
const res = await addCart(params)
|
||||
|
||||
|
||||
return res
|
||||
},
|
||||
//查询购物车列表
|
||||
@@ -296,7 +434,7 @@ export default {
|
||||
},
|
||||
|
||||
// 已取消对比购物车的自动勾选/禁用逻辑
|
||||
autoSelectAndDisable() {},
|
||||
autoSelectAndDisable() { },
|
||||
|
||||
// 选择器可选控制:已在购物车中的机器不可再选
|
||||
isSelectable(row, index) {
|
||||
@@ -387,7 +525,7 @@ export default {
|
||||
variant.quantity = 1
|
||||
} catch (error) {
|
||||
console.error('添加到购物车失败:', error)
|
||||
|
||||
|
||||
}
|
||||
},
|
||||
// 统一加入购物车
|
||||
@@ -411,7 +549,7 @@ export default {
|
||||
this.selectedMap = {}
|
||||
} catch (e) {
|
||||
console.error('统一加入购物车失败', e)
|
||||
|
||||
|
||||
}
|
||||
},
|
||||
// 打开确认弹窗:以当前界面勾选(_selected)为准,并在打开后清空左侧勾选状态
|
||||
@@ -474,14 +612,14 @@ export default {
|
||||
})
|
||||
this.$nextTick(() => this.autoSelectAndDisable())
|
||||
} catch (e) { /* noop */ }
|
||||
|
||||
|
||||
this.$message({
|
||||
message: `已加入 ${allSelected.length} 台矿机到购物车`,
|
||||
type: 'success',
|
||||
duration: 3000,
|
||||
showClose: true,
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
this.confirmAddDialog.visible = false
|
||||
// 清空选中映射,然后重新加载数据(数据加载时会自动设置 _selected: false)
|
||||
this.selectedMap = {}
|
||||
@@ -503,9 +641,12 @@ export default {
|
||||
// 取消所有商品勾选(内层表格的自定义 checkbox)
|
||||
clearAllSelections() {
|
||||
try {
|
||||
// 清空选中映射
|
||||
// 清空选中映射(遗留字段)
|
||||
this.selectedMap = {}
|
||||
// 遍历所有系列与机器,复位 _selected
|
||||
if (Array.isArray(this.machineList) && this.machineList.length) {
|
||||
this.machineList.forEach(m => { if (m) this.$set(m, '_selected', false) })
|
||||
return
|
||||
}
|
||||
const groups = Array.isArray(this.productListData) ? this.productListData : []
|
||||
groups.forEach(g => {
|
||||
const list = Array.isArray(g.productMachines) ? g.productMachines : []
|
||||
@@ -587,6 +728,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());
|
||||
},
|
||||
}
|
||||
}
|
||||
@@ -16,12 +16,13 @@
|
||||
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"
|
||||
:src="getPayImageUrl(item)"
|
||||
:alt="`${(item.payChain || '').toUpperCase()} ${(item.payCoin || '').toUpperCase()}`.trim()"
|
||||
:title="formatPayTooltip(item)"
|
||||
tabindex="0"
|
||||
role="img"
|
||||
@keydown.enter.prevent="handlePayIconKeyDown(item)"
|
||||
@@ -31,99 +32,251 @@
|
||||
</li>
|
||||
</ul>
|
||||
</section>
|
||||
<!-- 筛选栏 -->
|
||||
<section class="filter-bar" aria-label="筛选条件">
|
||||
<div class="filter-grid">
|
||||
<!-- 支付方式筛选:选择即触发查询 -->
|
||||
<div class="filter-cell">
|
||||
<label class="filter-title" for="payFilter">支付方式筛选</label>
|
||||
<el-select
|
||||
id="payFilter"
|
||||
v-model="selectedPayKey"
|
||||
placeholder="全部"
|
||||
clearable
|
||||
filterable
|
||||
size="small"
|
||||
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"
|
||||
:label="formatPayTooltip(opt)"
|
||||
:value="`${opt.payChain || ''}|${opt.payCoin || ''}`"
|
||||
>
|
||||
<div class="pay-opt">
|
||||
<img :src="getPayImageUrl(opt)" class="pay-icon" alt="" />
|
||||
<span>{{ (opt.payChain || '').toUpperCase() }} - {{ (opt.payCoin || '').toUpperCase() }}</span>
|
||||
</div>
|
||||
</el-option>
|
||||
</el-select>
|
||||
</div>
|
||||
|
||||
<!-- 价格区间 -->
|
||||
<div class="filter-cell center-title">
|
||||
<label class="filter-title">单价区间<span v-if="getPriceCoinSymbol()">({{ getPriceCoinSymbol() }})</span></label>
|
||||
<div class="range-controls">
|
||||
<el-input-number v-model="filters.minPrice" :min="0" :step="1" :precision="0" :controls="false" size="small" class="filter-control" />
|
||||
<span class="filter-sep">-</span>
|
||||
<el-input-number v-model="filters.maxPrice" :min="0" :step="1" :precision="0" :controls="false" size="small" class="filter-control" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 实际算力区间 -->
|
||||
<div class="filter-cell center-title">
|
||||
<label class="filter-title">实际算力</label>
|
||||
<div class="range-controls">
|
||||
<el-input-number v-model="filters.minPower" :min="0" :step="0.1" :precision="2" :controls="false" size="small" class="filter-control" />
|
||||
<span class="filter-sep">-</span>
|
||||
<el-input-number v-model="filters.maxPower" :min="0" :step="0.1" :precision="2" :controls="false" size="small" class="filter-control" />
|
||||
<el-select v-model="filters.unit" placeholder="单位" size="small" class="filter-control" style="max-width: 140px;">
|
||||
<el-option v-for="u in powerUnitOptions" :key="u" :label="u" :value="u" />
|
||||
</el-select>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 功耗区间:第二行左侧,占两列 -->
|
||||
<div class="filter-cell filter-cell--span-2 center-title">
|
||||
<label class="filter-title">功耗(kw/h)</label>
|
||||
<div class="range-controls">
|
||||
<el-input-number v-model="filters.minPowerDissipation" :min="0" :step="0.1" :precision="2" :controls="false" size="small" class="filter-control" />
|
||||
<span class="filter-sep">-</span>
|
||||
<el-input-number v-model="filters.maxPowerDissipation" :min="0" :step="0.1" :precision="2" :controls="false" size="small" class="filter-control" />
|
||||
<div class="filter-actions-inline">
|
||||
<el-button type="primary" size="small" @click="handleSearchFilters" aria-label="执行筛选">筛选查询</el-button>
|
||||
<el-button size="small" @click="handleResetFilters" aria-label="重置筛选">重置</el-button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 操作区已合并到功耗区间后面 -->
|
||||
</div>
|
||||
</section>
|
||||
<section class="productList">
|
||||
<!-- 产品列表(可展开) -->
|
||||
<!-- 单层产品列表 -->
|
||||
<el-table
|
||||
ref="seriesTable"
|
||||
ref="machineTable"
|
||||
class="series-table"
|
||||
:data="productListData"
|
||||
:data="machineList"
|
||||
row-key="id"
|
||||
:expand-row-keys="expandedRowKeys"
|
||||
@expand-change="handleExpandChange"
|
||||
@row-click="handleSeriesRowClick"
|
||||
:row-class-name="handleGetSeriesRowClassName"
|
||||
:row-class-name="handleGetRowClass"
|
||||
:header-cell-style="{ textAlign: 'left' }"
|
||||
:cell-style="{ textAlign: 'left' }"
|
||||
style="width: 100%"
|
||||
>
|
||||
<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' }" :row-class-name="handleGetInnerRowClass">
|
||||
<el-table-column width="46">
|
||||
<template #default="scope">
|
||||
<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="理论算力" 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)" 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" 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)" min-width="170" 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="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>
|
||||
<el-table-column width="46">
|
||||
<template #default="scope">
|
||||
<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 => handleManualSelectFlat(scope.row, checked)"
|
||||
/>
|
||||
</template>
|
||||
</el-table-column>
|
||||
|
||||
<!-- 外层列宽同样收紧,避免横向滚动 -->
|
||||
<el-table-column label="价格 (USDT)" header-align="left" align="left" min-width="120">
|
||||
<template slot-scope="scope"><span class="price-strong">{{ scope.row.productMachineRangeGroupDto && scope.row.productMachineRangeGroupDto.price }}</span></template>
|
||||
|
||||
|
||||
<el-table-column prop="theoryPower" label="理论算力" header-align="left" align="left" show-overflow-tooltip>
|
||||
<template #default="scope">
|
||||
<span class="num-strong">
|
||||
<el-tooltip
|
||||
v-if="formatNum6(scope.row.theoryPower).truncated"
|
||||
:content="formatNum6(scope.row.theoryPower).full"
|
||||
placement="top"
|
||||
>
|
||||
<span>
|
||||
{{ formatNum6(scope.row.theoryPower).text }}
|
||||
<i class="el-icon-more amount-more"></i>
|
||||
</span>
|
||||
</el-tooltip>
|
||||
<span v-else>{{ formatNum6(scope.row.theoryPower).text }}</span>
|
||||
</span>
|
||||
{{ scope.row.unit }}
|
||||
</template>
|
||||
</el-table-column>
|
||||
|
||||
<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 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">
|
||||
<span class="num-strong">
|
||||
<el-tooltip
|
||||
v-if="formatNum6(scope.row.computingPower).truncated"
|
||||
:content="formatNum6(scope.row.computingPower).full"
|
||||
placement="top"
|
||||
>
|
||||
<span>
|
||||
{{ formatNum6(scope.row.computingPower).text }}
|
||||
<i class="el-icon-more amount-more"></i>
|
||||
</span>
|
||||
</el-tooltip>
|
||||
<span v-else>{{ formatNum6(scope.row.computingPower).text }}</span>
|
||||
</span>
|
||||
{{ scope.row.unit }}
|
||||
</template>
|
||||
</el-table-column>
|
||||
<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 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 label="功耗范围" min-width="160" header-align="left" align="left">
|
||||
<template slot-scope="scope">{{ scope.row.productMachineRangeGroupDto && scope.row.productMachineRangeGroupDto.powerRange }}</template>
|
||||
<el-table-column prop="algorithm" label="算法" header-align="left" align="left" />
|
||||
|
||||
<el-table-column prop="theoryIncome" header-align="left" align="left" show-overflow-tooltip>
|
||||
<template #header>
|
||||
单机理论收入(每日)
|
||||
<span v-if="getFirstCoinSymbol()">({{ getFirstCoinSymbol() }})</span>
|
||||
</template>
|
||||
<template #default="scope">
|
||||
<span class="num-strong">
|
||||
<el-tooltip
|
||||
v-if="formatNum6(scope.row.theoryIncome).truncated"
|
||||
:content="formatNum6(scope.row.theoryIncome).full"
|
||||
placement="top"
|
||||
>
|
||||
<span>
|
||||
{{ formatNum6(scope.row.theoryIncome).text }}
|
||||
<i class="el-icon-more amount-more"></i>
|
||||
</span>
|
||||
</el-tooltip>
|
||||
<span v-else>{{ formatNum6(scope.row.theoryIncome).text }}</span>
|
||||
</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<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 prop="theoryUsdtIncome" label="单机理论收入(每日/USDT)" header-align="left" align="left">
|
||||
<template #default="scope">
|
||||
<span class="num-strong">
|
||||
<el-tooltip
|
||||
v-if="formatNum6(scope.row.theoryUsdtIncome).truncated"
|
||||
:content="formatNum6(scope.row.theoryUsdtIncome).full"
|
||||
placement="top"
|
||||
>
|
||||
<span>
|
||||
{{ formatNum6(scope.row.theoryUsdtIncome).text }}
|
||||
<i class="el-icon-more amount-more"></i>
|
||||
</span>
|
||||
</el-tooltip>
|
||||
<span v-else>{{ formatNum6(scope.row.theoryUsdtIncome).text }}</span>
|
||||
</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
|
||||
<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="租赁天数(天)" header-align="left" align="left">
|
||||
<template #default="scope">
|
||||
<el-input-number
|
||||
v-model="scope.row.leaseTime"
|
||||
:min="1"
|
||||
: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="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>
|
||||
</el-table-column>
|
||||
<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 ? '已售出' : '售出中') }}
|
||||
</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
|
||||
</el-table>
|
||||
</section>
|
||||
|
||||
@@ -136,18 +289,81 @@
|
||||
<div>
|
||||
<el-table :data="confirmAddDialog.items" height="360" border stripe :header-cell-style="{ textAlign: 'left' }" :cell-style="{ textAlign: 'left' }">
|
||||
<el-table-column prop="theoryPower" label="理论算力" header-align="left" align="left">
|
||||
<template #default="scope">{{ scope.row.theoryPower }} {{ scope.row.unit }}</template>
|
||||
<template #default="scope">
|
||||
<span class="num-strong">
|
||||
<el-tooltip
|
||||
v-if="formatNum6(scope.row.theoryPower).truncated"
|
||||
:content="formatNum6(scope.row.theoryPower).full"
|
||||
placement="top"
|
||||
>
|
||||
<span>
|
||||
{{ formatNum6(scope.row.theoryPower).text }}
|
||||
<i class="el-icon-more amount-more"></i>
|
||||
</span>
|
||||
</el-tooltip>
|
||||
<span v-else>{{ formatNum6(scope.row.theoryPower).text }}</span>
|
||||
</span>
|
||||
{{ scope.row.unit }}
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="实际算力" header-align="left" align="left">
|
||||
<template #default="scope">{{ scope.row.computingPower }} {{ scope.row.unit }}</template>
|
||||
<template #default="scope">
|
||||
<span class="num-strong">
|
||||
<el-tooltip
|
||||
v-if="formatNum6(scope.row.computingPower).truncated"
|
||||
:content="formatNum6(scope.row.computingPower).full"
|
||||
placement="top"
|
||||
>
|
||||
<span>
|
||||
{{ formatNum6(scope.row.computingPower).text }}
|
||||
<i class="el-icon-more amount-more"></i>
|
||||
</span>
|
||||
</el-tooltip>
|
||||
<span v-else>{{ formatNum6(scope.row.computingPower).text }}</span>
|
||||
</span>
|
||||
{{ scope.row.unit }}
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="algorithm" label="算法" width="120" header-align="left" align="left" />
|
||||
<el-table-column prop="powerDissipation" label="功耗(kw/h)" header-align="left" align="left" />
|
||||
<el-table-column prop="powerDissipation" label="功耗(kw/h)" header-align="left" align="left">
|
||||
<template #default="scope">
|
||||
<span class="num-strong">
|
||||
<el-tooltip
|
||||
v-if="formatNum6(scope.row.powerDissipation).truncated"
|
||||
:content="formatNum6(scope.row.powerDissipation).full"
|
||||
placement="top"
|
||||
>
|
||||
<span>
|
||||
{{ formatNum6(scope.row.powerDissipation).text }}
|
||||
<i class="el-icon-more amount-more"></i>
|
||||
</span>
|
||||
</el-tooltip>
|
||||
<span v-else>{{ formatNum6(scope.row.powerDissipation).text }}</span>
|
||||
</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="租赁天数(天)" header-align="left" align="left">
|
||||
<template #default="scope">{{ Number(scope.row.leaseTime || 1) }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="price" label="单价(USDT)" header-align="left" align="left">
|
||||
<template #default="scope"><span class="price-strong">{{ scope.row.price }}</span></template>
|
||||
<el-table-column prop="price" header-align="left" align="left">
|
||||
<template #header>
|
||||
单价 <span v-if="getPriceCoinSymbol()">({{ getPriceCoinSymbol() }})</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>
|
||||
@@ -157,7 +373,22 @@
|
||||
<el-button type="primary" @click="handleConfirmAddSelectedToCart">确认加入</el-button>
|
||||
</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">
|
||||
@@ -187,6 +418,105 @@ export default {
|
||||
if (n > 365) return 365
|
||||
return Math.floor(n)
|
||||
},
|
||||
/**
|
||||
* 处理支付方式图片 URL(去除服务端可能带入的换行/空白)
|
||||
* @param {Object} item
|
||||
*/
|
||||
getPayImageUrl(item) {
|
||||
try {
|
||||
const src = (item && item.payCoinImage) ? String(item.payCoinImage) : ''
|
||||
return src.trim()
|
||||
} catch (e) {
|
||||
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
|
||||
*/
|
||||
handlePayFilterChange(val) {
|
||||
try {
|
||||
const s = typeof val === 'string' ? val : ''
|
||||
if (!s) {
|
||||
this.filters.chain = ''
|
||||
this.filters.coin = ''
|
||||
} else {
|
||||
const [chain, coin] = s.split('|')
|
||||
this.filters.chain = (chain || '').trim()
|
||||
this.filters.coin = (coin || '').trim()
|
||||
}
|
||||
this.handleSearchFilters()
|
||||
} catch (e) { /* noop */ }
|
||||
},
|
||||
/**
|
||||
* 组合筛选参数并请求数据
|
||||
*/
|
||||
handleSearchFilters() {
|
||||
const params = this.buildQueryParams()
|
||||
this.fetchGetMachineInfo(params)
|
||||
},
|
||||
/**
|
||||
* 重置筛选
|
||||
*/
|
||||
handleResetFilters() {
|
||||
this.selectedPayKey = null
|
||||
this.filters = {
|
||||
chain: '',
|
||||
coin: '',
|
||||
minPrice: null,
|
||||
maxPrice: null,
|
||||
minPower: null,
|
||||
maxPower: null,
|
||||
minPowerDissipation: null,
|
||||
maxPowerDissipation: null,
|
||||
unit: 'GH/S'
|
||||
}
|
||||
this.handleSearchFilters()
|
||||
},
|
||||
/**
|
||||
* 获取列表第一个条目的币种,安全返回大写字符串
|
||||
* 用于表头显示币种,避免空数组时报错
|
||||
*/
|
||||
getFirstCoinSymbol() {
|
||||
try {
|
||||
const list = Array.isArray(this.machineList) ? this.machineList : []
|
||||
const coin = list.length && list[0] && list[0].coin ? String(list[0].coin) : ''
|
||||
return coin ? coin.toUpperCase() : ''
|
||||
} catch (e) {
|
||||
return ''
|
||||
}
|
||||
},
|
||||
/**
|
||||
* 获取价格单位(优先读取每行的 payCoin 字段)
|
||||
*/
|
||||
getPriceCoinSymbol() {
|
||||
try {
|
||||
const list = Array.isArray(this.machineList) ? this.machineList : []
|
||||
// 寻找第一个存在 payCoin 的条目
|
||||
const item = list.find(it => it && it.payCoin)
|
||||
const unit = item && item.payCoin ? String(item.payCoin) : ''
|
||||
return unit ? unit.toUpperCase() : ''
|
||||
} catch (e) {
|
||||
return ''
|
||||
}
|
||||
},
|
||||
/**
|
||||
* 限制并校验租赁天数:区间 [1, max],并取整
|
||||
*/
|
||||
@@ -232,6 +562,52 @@ export default {
|
||||
// eslint-disable-next-line no-console
|
||||
console.error('handlePayIconKeyDown error:', err);
|
||||
}
|
||||
},
|
||||
/**
|
||||
* 单层:切换勾选
|
||||
* @param {Object} row - 当前机器行
|
||||
* @param {boolean} checked - 勾选状态
|
||||
*/
|
||||
handleManualSelectFlat(row, checked) {
|
||||
try {
|
||||
if (!row) return
|
||||
if (row.saleState === 1 || row.saleState === 2) {
|
||||
this.$message.warning('该机器已售出或售出中,无法选择')
|
||||
this.$set(row, '_selected', false)
|
||||
return
|
||||
}
|
||||
this.$set(row, '_selected', !!checked)
|
||||
} catch (e) {
|
||||
// eslint-disable-next-line no-console
|
||||
console.error('handleManualSelectFlat error:', e)
|
||||
}
|
||||
},
|
||||
/**
|
||||
* 单层:行样式(售出态高亮)
|
||||
*/
|
||||
handleGetRowClass({ row }) {
|
||||
if (!row) return ''
|
||||
return (row.saleState === 1 || row.saleState === 2) ? 'sold-row' : ''
|
||||
},
|
||||
/**
|
||||
* 覆盖 mixin 的多层版本:基于单层勾选打开确认弹窗
|
||||
*/
|
||||
handleOpenAddToCartDialog() {
|
||||
const list = Array.isArray(this.machineList) ? this.machineList : []
|
||||
const pickedAll = list.filter(it => !!it && !!it._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
|
||||
this.$nextTick(() => {
|
||||
try { (this.machineList || []).forEach(m => this.$set(m, '_selected', false)) } catch (e) { /* noop */ }
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -358,6 +734,11 @@ export default {
|
||||
color: #e74c3c;
|
||||
}
|
||||
|
||||
.num-strong {
|
||||
font-weight: inherit;
|
||||
color: inherit;
|
||||
}
|
||||
|
||||
/* 支付方式区域(视觉更友好 + 可达性) */
|
||||
.pay-methods {
|
||||
display: flex;
|
||||
@@ -400,6 +781,9 @@ export default {
|
||||
transition: transform 0.15s ease, box-shadow 0.15s ease;
|
||||
}
|
||||
|
||||
.pay-item-inner { display: inline-flex; align-items: center; gap: 8px; }
|
||||
.pay-text { font-size: 12px; color: #2c3e50; }
|
||||
|
||||
.pay-icon:hover {
|
||||
transform: translateY(-1px);
|
||||
}
|
||||
@@ -409,6 +793,76 @@ export default {
|
||||
box-shadow: 0 0 0 3px rgba(25, 118, 210, 0.2);
|
||||
}
|
||||
|
||||
/* 筛选栏样式 */
|
||||
.filter-bar {
|
||||
background: #ffffff;
|
||||
border: 1px solid #eef2f7;
|
||||
border-radius: 8px;
|
||||
padding: 12px 16px;
|
||||
margin: 0 10px 16px 10px;
|
||||
}
|
||||
.filter-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(3, minmax(260px, 1fr));
|
||||
gap: 14px 18px;
|
||||
align-items: end;
|
||||
}
|
||||
.filter-cell {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: start;
|
||||
gap: 6px;
|
||||
}
|
||||
.filter-cell.center-title .filter-title { text-align: center; }
|
||||
|
||||
.filter-title {
|
||||
font-size: 14px;
|
||||
color: #34495E;
|
||||
font-weight: 600;
|
||||
margin-bottom: 8px;
|
||||
|
||||
}
|
||||
.filter-control {
|
||||
width: 100%;
|
||||
max-width: 320px;
|
||||
}
|
||||
.range-controls {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
}
|
||||
.range-controls :deep(.el-input-number) { width: 150px; }
|
||||
.pay-opt { display: inline-flex; align-items: center; gap: 8px; }
|
||||
.filter-sep {
|
||||
color: #9aa4b2;
|
||||
}
|
||||
.filter-actions {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
grid-column: 2 / 3; /* 放到中间这一格 */
|
||||
}
|
||||
|
||||
.filter-actions-inline {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
margin-left: 12px;
|
||||
}
|
||||
|
||||
@media (max-width: 1200px) {
|
||||
.filter-grid { grid-template-columns: repeat(2, minmax(220px, 1fr)); }
|
||||
.filter-cell--span-2 { grid-column: 1 / span 1; }
|
||||
.filter-actions { grid-column: 1 / -1; justify-content: flex-end; }
|
||||
}
|
||||
|
||||
@media (max-width: 768px) {
|
||||
.filter-grid {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
.filter-actions { grid-column: 1 / 2; justify-content: flex-end; }
|
||||
}
|
||||
|
||||
/* 外层系列行:整行可点击 + 视觉增强 */
|
||||
:deep(.series-clickable-row) {
|
||||
cursor: pointer;
|
||||
@@ -551,4 +1005,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>
|
||||
@@ -60,11 +60,22 @@
|
||||
<h4>商品: {{ product.name }}</h4>
|
||||
<p style="font-size: 16px;margin-top: 10px;font-weight: bold;">算法: {{ product.algorithm }}</p>
|
||||
<div class="product-footer">
|
||||
<div class="price-wrap">
|
||||
<span class="product-price">价格: {{ formatPriceRange(product.priceRange) }}</span>
|
||||
<span class="unit">USDT</span>
|
||||
<div class="paytypes">
|
||||
<span class="paytypes-label">支付方式:</span>
|
||||
<el-tooltip
|
||||
v-for="(pt, idx) in (product.payTypes || [])"
|
||||
:key="idx"
|
||||
:content="formatPayType(pt)"
|
||||
placement="top"
|
||||
:open-delay="80"
|
||||
>
|
||||
<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>
|
||||
<span class="product-sold" aria-label="已售数量">已售:{{ product && product.saleNumber != null ? product.saleNumber : 0 }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -89,6 +100,19 @@ export default {
|
||||
|
||||
mounted() {},
|
||||
methods: {
|
||||
/**
|
||||
* 将 payType 显示为 CHAIN-COIN 文本
|
||||
*/
|
||||
formatPayType(item) {
|
||||
try {
|
||||
const chain = (item && item.chain ? String(item.chain) : '').toUpperCase()
|
||||
const coin = (item && item.coin ? String(item.coin) : '').toUpperCase()
|
||||
if (chain && coin) return `${chain}-${coin}`
|
||||
return chain || coin || ''
|
||||
} catch (e) {
|
||||
return ''
|
||||
}
|
||||
},
|
||||
/**
|
||||
* 处理商品点击 - 跳转到详情页
|
||||
*/
|
||||
@@ -184,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;
|
||||
@@ -195,6 +229,9 @@ export default {
|
||||
.price-wrap { display: inline-flex; align-items: baseline; gap: 6px; }
|
||||
.unit { color: #999; font-size: 12px; }
|
||||
.product-sold { color: #64748b; font-size: 12px; }
|
||||
.paytypes { display: inline-flex; align-items: center; gap: 8px; }
|
||||
.paytype-icon { width: 22px; height: 22px; border-radius: 4px; display: inline-block; }
|
||||
.paytypes-label { color: #64748b; font-size: 12px; }
|
||||
.add-cart-btn {
|
||||
background: #42b983;
|
||||
color: #fff;
|
||||
|
||||
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
power_leasing/test/css/app.ca4b7f36.css
Normal file
1
power_leasing/test/css/app.ca4b7f36.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.d49ccc2c.js"></script><link href="/css/chunk-vendors.10dd4e95.css" rel="stylesheet"><link href="/css/app.ca4b7f36.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
2
power_leasing/test/js/app.d49ccc2c.js
Normal file
2
power_leasing/test/js/app.d49ccc2c.js
Normal file
File diff suppressed because one or more lines are too long
1
power_leasing/test/js/app.d49ccc2c.js.map
Normal file
1
power_leasing/test/js/app.d49ccc2c.js.map
Normal file
File diff suppressed because one or more lines are too long
Reference in New Issue
Block a user