每周更新
This commit is contained in:
@@ -7,8 +7,8 @@ NODE_ENV = production
|
||||
ENV = 'staging'
|
||||
|
||||
# 测试环境
|
||||
VUE_APP_BASE_API = 'http://10.168.2.220:8888'
|
||||
# VUE_APP_BASE_API = 'https://test.m2pool.com/api/'
|
||||
# VUE_APP_BASE_API = 'http://10.168.2.220:8888'
|
||||
VUE_APP_BASE_API = 'https://test.m2pool.com/api/'
|
||||
VUE_APP_BASE_URL = 'https://test.m2pool.com/'
|
||||
|
||||
|
||||
|
||||
@@ -67,7 +67,7 @@ export function downloadClient() {
|
||||
return request({
|
||||
url: `/lease/user/downloadClient`,
|
||||
method: 'get',
|
||||
|
||||
responseType: 'blob' // 关键:必须设置为 blob 才能正确下载二进制文件
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
@@ -12,44 +12,44 @@ export function addOrders(data) {
|
||||
|
||||
//取消订单
|
||||
export function cancelOrder(data) {
|
||||
return request({
|
||||
url: `/lease/order/info/cancelOrder`,
|
||||
method: 'post',
|
||||
data
|
||||
})
|
||||
}
|
||||
return request({
|
||||
url: `/lease/order/info/cancelOrder`,
|
||||
method: 'post',
|
||||
data
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
//根据订单id查询订单信息
|
||||
//根据订单id查询订单信息
|
||||
export function getOrdersByIds(data) {
|
||||
return request({
|
||||
url: `/lease/order/info/getOrdersByIds`,
|
||||
method: 'post',
|
||||
data
|
||||
})
|
||||
}
|
||||
return request({
|
||||
url: `/lease/order/info/getOrdersByIds`,
|
||||
method: 'post',
|
||||
data
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
//查询订单列表(买家侧)
|
||||
|
||||
//查询订单列表(买家侧)
|
||||
export function getOrdersByStatus(data) {
|
||||
return request({
|
||||
url: `/lease/order/info/getOrdersByStatus`,
|
||||
method: 'post',
|
||||
data
|
||||
})
|
||||
}
|
||||
return request({
|
||||
url: `/lease/order/info/getOrdersByStatus`,
|
||||
method: 'post',
|
||||
data
|
||||
})
|
||||
}
|
||||
|
||||
//查询订单列表(卖家侧)
|
||||
//查询订单列表(卖家侧)
|
||||
export function getOrdersByStatusForSeller(data) {
|
||||
return request({
|
||||
url: `/lease/order/info/getOrdersByStatusForSeller`,
|
||||
method: 'post',
|
||||
data
|
||||
})
|
||||
}
|
||||
return request({
|
||||
url: `/lease/order/info/getOrdersByStatusForSeller`,
|
||||
method: 'post',
|
||||
data
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
//结算前链和币种查询
|
||||
//结算前链和币种查询
|
||||
export function getChainAndListForSeller(data) {
|
||||
return request({
|
||||
url: `/lease/shop/getChainAndListForSeller`,
|
||||
@@ -58,14 +58,43 @@ export function getChainAndListForSeller(data) {
|
||||
})
|
||||
}
|
||||
|
||||
//获取实时币价
|
||||
export function getCoinPrice(data) {
|
||||
return request({
|
||||
url: `/lease/order/info/getCoinPrice`,
|
||||
method: 'post',
|
||||
data
|
||||
})
|
||||
}
|
||||
//获取实时币价
|
||||
export function getCoinPrice(data) {
|
||||
return request({
|
||||
url: `/lease/order/info/getCoinPrice`,
|
||||
method: 'post',
|
||||
data
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
//获取支持的算法币种
|
||||
export function getMachineSupportCoinAndAlgorithm(data) {
|
||||
return request({
|
||||
url: `/lease/v2/order/info/getMachineSupportCoinAndAlgorithm`,
|
||||
method: 'post',
|
||||
data
|
||||
})
|
||||
}
|
||||
|
||||
//获取支持的矿池 和模型
|
||||
export function getMachineSupportPool(data) {
|
||||
return request({
|
||||
url: `/lease/v2/order/info/getMachineSupportPool`,
|
||||
method: 'post',
|
||||
data
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
//创建订单
|
||||
export function addOrdersV2(data) {
|
||||
return request({
|
||||
url: `/lease/v2/order/info/addOrdersV2`,
|
||||
method: 'post',
|
||||
data
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -37,6 +37,39 @@ export function deleteBatchGoodsForIsDelete(data) {
|
||||
})
|
||||
}
|
||||
|
||||
//购物车列表V2
|
||||
export function getGoodsListV2(data) {
|
||||
return request({
|
||||
url: `/lease/v2/shopping/cart/getGoodsListV2`,
|
||||
method: 'post',
|
||||
data
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
//批量删除购物车中已下架商品
|
||||
export function deleteBatchGoodsForIsDeleteV2(data) {
|
||||
return request({
|
||||
url: `/lease/v2/shopping/cart/deleteBatchGoodsForIsDeleteV2`,
|
||||
method: 'post',
|
||||
data
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
|
||||
//批批量删除购物车中商品
|
||||
export function deleteBatchGoodsV2(data) {
|
||||
return request({
|
||||
url: `/lease/v2/shopping/cart/deleteBatchGoodsV2`,
|
||||
method: 'post',
|
||||
data
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -102,6 +102,16 @@ export function getChainAndCoin(data) {
|
||||
}
|
||||
|
||||
|
||||
// 卖家绑定钱包明细
|
||||
export function getShopConfigV2(data) {
|
||||
return request({
|
||||
url: `/lease/v2/shop/getShopConfigV2`,
|
||||
method: 'post',
|
||||
data
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -125,6 +125,39 @@ export function updateProductListForShopWalletConfig(data) {
|
||||
})
|
||||
}
|
||||
|
||||
// 卖家绑定钱包明细
|
||||
export function getShopConfigV2(data) {
|
||||
return request({
|
||||
url: `/lease/v2/shop/getShopConfigV2`,
|
||||
method: 'post',
|
||||
data
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
// 卖家提现
|
||||
export function withdrawBalanceForSeller(data) {
|
||||
return request({
|
||||
url: `/lease/v2/shop/withdrawBalanceForSeller`,
|
||||
method: 'post',
|
||||
data
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
// 修改钱包配置
|
||||
export function balanceWithdrawListV2(data) {
|
||||
return request({
|
||||
url: `/lease/v2/shop/balanceWithdrawList`,
|
||||
method: 'post',
|
||||
data
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -109,6 +109,16 @@ export const accountRoutes = [
|
||||
allAuthority: ['all']
|
||||
}
|
||||
},
|
||||
{
|
||||
path: 'withdraw-record',
|
||||
name: 'accountWithdrawRecord',
|
||||
component: () => import('../views/account/withdrawRecord.vue'),
|
||||
meta: {
|
||||
title: '提现记录',
|
||||
description: '卖家提现流水记录',
|
||||
allAuthority: ['all']
|
||||
}
|
||||
},
|
||||
{
|
||||
path: 'shop-new',
|
||||
name: 'accountShopNew',
|
||||
|
||||
@@ -256,6 +256,19 @@ service.interceptors.response.use(res => {
|
||||
// 请求完成后移除
|
||||
const requestKey = getRequestKey(res.config);
|
||||
pendingRequestMap.delete(requestKey);
|
||||
|
||||
// 特殊处理:如果是 blob 类型响应(文件下载),直接返回原始响应对象
|
||||
// 因为 blob 数据不是 JSON,不能解析 res.data.code
|
||||
if (res.config.responseType === 'blob' || res.data instanceof Blob) {
|
||||
// 检查响应状态码
|
||||
if (res.status >= 200 && res.status < 300) {
|
||||
return res // 返回完整响应对象,包含 headers 等信息
|
||||
} else {
|
||||
// blob 响应但状态码异常,尝试读取错误信息
|
||||
return Promise.reject(new Error(`下载失败,状态码: ${res.status}`))
|
||||
}
|
||||
}
|
||||
|
||||
// 未设置状态码则默认成功状态
|
||||
const code = res.data.code || 200;
|
||||
// 获取错误信息
|
||||
|
||||
@@ -76,6 +76,7 @@ export default {
|
||||
{ label: '商品列表', to: '/account/products' },
|
||||
{ label: '已售出订单', to: '/account/seller-orders' },
|
||||
{ label: '收款记录', to: '/account/receipt-record' },
|
||||
{ label: '提现记录', to: '/account/withdraw-record' },
|
||||
|
||||
],
|
||||
}
|
||||
@@ -170,6 +171,7 @@ export default {
|
||||
'/account/product-machine-add',
|
||||
'/account/seller-orders',
|
||||
'/account/receipt-record',
|
||||
'/account/withdraw-record',
|
||||
'/account/shop-config'
|
||||
]
|
||||
const shouldBuyer = buyerPrefixes.some(p => path.indexOf(p) === 0)
|
||||
|
||||
@@ -22,7 +22,7 @@
|
||||
必须添加出售机器,否则买家无法下单。买家点击某个商品后,会看到该商品下的机器明细并进行选购。
|
||||
</li>
|
||||
</ol>
|
||||
<div class="guide-note">提示:建议先创建店铺 → 完成钱包绑定 → 创建商品 → 添加出售机器的顺序,避免漏配导致无法收款或无法下单。</div>
|
||||
<div class="guide-note">提示:建议先创建店铺 → 完成钱包绑定 → 创建商品的顺序,避免漏配导致无法收款或无法下单。</div>
|
||||
</div>
|
||||
</el-card>
|
||||
|
||||
@@ -61,8 +61,8 @@
|
||||
<span>已绑定钱包</span>
|
||||
</div>
|
||||
<el-table :data="shopConfigs" border style="width: 100%">
|
||||
<el-table-column prop="chain" label="链" width="140" />
|
||||
<el-table-column label="支付币种" >
|
||||
<el-table-column prop="chain" label="链" width="120" />
|
||||
<el-table-column label="支付币种" width="120" >
|
||||
<template slot-scope="scope">
|
||||
<div class="coin-list">
|
||||
<template v-if="Array.isArray(scope.row.children) && scope.row.children.length">
|
||||
@@ -89,10 +89,18 @@
|
||||
<!-- <el-table-column prop="payType" label="币种类型" width="120">
|
||||
<template slot-scope="scope">{{ scope.row.payType === 1 ? '稳定币' : '虚拟币' }}</template>
|
||||
</el-table-column> -->
|
||||
|
||||
|
||||
<el-table-column prop="payAddress" label="收款钱包地址" show-overflow-tooltip />
|
||||
<el-table-column label="操作" width="180" fixed="right">
|
||||
<el-table-column label="余额" >
|
||||
<template slot-scope="scope">
|
||||
<span class="balance-num">{{ formatAmount(scope.row) }}</span>
|
||||
<span class="balance-unit"> {{ formatCoin(scope.row) }}</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="操作" width="240" fixed="right">
|
||||
<template slot-scope="scope">
|
||||
<el-button type="text" style="color:#409EFF" @click="handleWithdraw(scope.row)">提现</el-button>
|
||||
<el-divider direction="vertical"></el-divider>
|
||||
<el-button type="text" @click="handleEditConfig(scope.row)">修改</el-button>
|
||||
<el-divider direction="vertical"></el-divider>
|
||||
<el-button type="text" style="color:#e74c3c" @click="handleDeleteConfig(scope.row)">删除</el-button>
|
||||
@@ -108,6 +116,85 @@
|
||||
</div>
|
||||
|
||||
<el-empty v-else description="正在加载店铺信息..." />
|
||||
|
||||
<!-- 提现对话框(仅使用本页行数据) -->
|
||||
<el-dialog
|
||||
:title="withdrawDialogTitle"
|
||||
:visible.sync="withdrawDialogVisible"
|
||||
width="720px"
|
||||
:close-on-click-modal="false"
|
||||
:close-on-press-escape="false"
|
||||
>
|
||||
<el-form :model="withdrawForm" :rules="withdrawRules" ref="withdrawForm" label-width="120px">
|
||||
<!-- 提现链 -->
|
||||
<el-form-item label="提现链">
|
||||
<el-input :value="String((currentWithdrawRow.chain || '')).toUpperCase()" :disabled="true" />
|
||||
</el-form-item>
|
||||
<!-- 提现币种 -->
|
||||
<el-form-item label="提现币种">
|
||||
<el-input :value="displayWithdrawSymbol" :disabled="true" />
|
||||
</el-form-item>
|
||||
<!-- 提现金额 -->
|
||||
<el-form-item label="提现金额" prop="amount">
|
||||
<el-input
|
||||
v-model="withdrawForm.amount"
|
||||
placeholder="请输入提现金额"
|
||||
inputmode="decimal"
|
||||
@input="handleAmountInput"
|
||||
>
|
||||
<template slot="append">{{ displayWithdrawSymbol }}</template>
|
||||
</el-input>
|
||||
<div class="balance-info">
|
||||
可用余额: {{ availableWithdrawBalance }} {{ displayWithdrawSymbol }}
|
||||
</div>
|
||||
</el-form-item>
|
||||
<!-- 手续费 -->
|
||||
<el-form-item label="手续费">
|
||||
<el-input v-model="withdrawForm.fee" :disabled="true">
|
||||
<template slot="append">{{ displayWithdrawSymbol }}</template>
|
||||
</el-input>
|
||||
<div class="fee-info">网络手续费: {{ withdrawForm.fee || '0' }} {{ displayWithdrawSymbol }}</div>
|
||||
</el-form-item>
|
||||
<!-- 实际到账 -->
|
||||
<el-form-item label="实际到账">
|
||||
<el-input :value="actualAmount" :disabled="true">
|
||||
<template slot="append">{{ displayWithdrawSymbol }}</template>
|
||||
</el-input>
|
||||
<div class="actual-amount-info">实际到账: {{ actualAmount }} {{ displayWithdrawSymbol }}</div>
|
||||
</el-form-item>
|
||||
<!-- 收款地址 -->
|
||||
<el-form-item label="收款地址" prop="toAddress">
|
||||
<el-input
|
||||
v-model="withdrawForm.toAddress"
|
||||
placeholder="请输入收款钱包地址"
|
||||
:disabled="!withdrawAddressEditable"
|
||||
ref="withdrawToAddressInput"
|
||||
>
|
||||
<template slot="append">
|
||||
<el-button type="text" @click="handleEditAddressClick">修改</el-button>
|
||||
</template>
|
||||
</el-input>
|
||||
<div class="address-tip">请确认地址正确,错误地址将导致资产丢失</div>
|
||||
</el-form-item>
|
||||
<!-- 谷歌验证码 -->
|
||||
<el-form-item label="谷歌验证码" prop="googleCode">
|
||||
<el-input
|
||||
v-model="withdrawForm.googleCode"
|
||||
placeholder="请输入6位谷歌验证码"
|
||||
maxlength="6"
|
||||
@input="handleGoogleCodeInput"
|
||||
>
|
||||
<template slot="prepend">
|
||||
<i class="el-icon-key"></i>
|
||||
</template>
|
||||
</el-input>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<div slot="footer" class="dialog-footer">
|
||||
<el-button @click="withdrawDialogVisible = false">取消</el-button>
|
||||
<el-button type="primary" :loading="withdrawLoading" @click="confirmWithdraw">确认提现</el-button>
|
||||
</div>
|
||||
</el-dialog>
|
||||
|
||||
<!-- 修改店铺弹窗 -->
|
||||
<el-dialog title="修改店铺" :visible.sync="visibleEdit" width="520px">
|
||||
@@ -139,43 +226,7 @@
|
||||
</el-dialog>
|
||||
<!-- 修改钱包绑定配置弹窗:参数保持与列表一致 -->
|
||||
<el-dialog title="修改配置" :visible.sync="visibleConfigEdit" width="560px">
|
||||
<div class="row">
|
||||
<label class="label">支付链</label>
|
||||
<el-input v-model="configForm.chainLabel" placeholder="-" disabled />
|
||||
</div>
|
||||
<div class="row">
|
||||
<label class="label">支付币种</label>
|
||||
<el-select
|
||||
class="input"
|
||||
size="middle"
|
||||
v-model="configForm.payCoins"
|
||||
multiple
|
||||
collapse-tags
|
||||
filterable
|
||||
placeholder="请选择币种"
|
||||
>
|
||||
<el-option
|
||||
v-for="item in editCoinOptions"
|
||||
:key="item.value"
|
||||
:label="item.label"
|
||||
:value="item.value"
|
||||
/>
|
||||
</el-select>
|
||||
</div>
|
||||
<div class="row">
|
||||
<label class="label">已选择币种</label>
|
||||
<div class="selected-coin-list">
|
||||
<el-tag
|
||||
v-for="c in selectedCoinLabels"
|
||||
:key="c"
|
||||
type="warning"
|
||||
effect="light"
|
||||
closable
|
||||
@close="removeSelectedCoin(c)"
|
||||
>{{ c }}</el-tag>
|
||||
<span v-if="!selectedCoinLabels.length" style="color:#c0c4cc">未选择</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
<label class="label">钱包地址</label>
|
||||
<el-input v-model="configForm.payAddress" placeholder="请输入钱包地址" />
|
||||
@@ -193,7 +244,7 @@
|
||||
import { getMyShop, updateShop, deleteShop, queryShop, closeShop ,updateShopConfig,deleteShopConfig,getChainAndCoin} from '@/api/shops'
|
||||
|
||||
import { coinList } from '@/utils/coinList'
|
||||
import { getShopConfig } from '@/api/wallet'
|
||||
import { getShopConfig,getShopConfigV2 ,withdrawBalanceForSeller,updateShopConfigV2} from '@/api/wallet'
|
||||
|
||||
|
||||
export default {
|
||||
@@ -227,7 +278,19 @@ export default {
|
||||
{ label: 'BSC (BEP20)', value: 'bsc' },
|
||||
{ label: 'Nexa', value: 'nexa' },
|
||||
],
|
||||
shopLoading: false
|
||||
shopLoading: false,
|
||||
/* 提现弹窗状态(仅本页使用) */
|
||||
withdrawDialogVisible: false,
|
||||
withdrawLoading: false,
|
||||
currentWithdrawRow: {},
|
||||
withdrawForm: {
|
||||
amount: '',
|
||||
toAddress: '',
|
||||
fee: '0.00',
|
||||
googleCode: ''
|
||||
},
|
||||
withdrawAddressEditable: false,
|
||||
withdrawRules: {}
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
@@ -261,12 +324,199 @@ export default {
|
||||
selectedCoinLabels() {
|
||||
const map = new Map((this.editCoinOptions || []).map(o => [String(o.value), String(o.label).toUpperCase()]))
|
||||
return (this.configForm.payCoins || []).map(v => map.get(String(v)) || String(v).toUpperCase())
|
||||
},
|
||||
/* 提现弹窗标题:如 USDT提现 */
|
||||
withdrawDialogTitle() {
|
||||
const sym = String((this.currentWithdrawRow && this.currentWithdrawRow.payCoin) || '').toUpperCase() || 'USDT'
|
||||
return `${sym}提现`
|
||||
},
|
||||
/* 提现币种(大写) */
|
||||
displayWithdrawSymbol() {
|
||||
return String((this.currentWithdrawRow && this.currentWithdrawRow.payCoin) || '').toUpperCase()
|
||||
},
|
||||
/* 可用余额(最多6位小数显示) */
|
||||
availableWithdrawBalance() {
|
||||
const n = Number((this.currentWithdrawRow && this.currentWithdrawRow.balance) || 0)
|
||||
return this.formatDec6(n)
|
||||
},
|
||||
/* 实际到账金额 = 可用余额 - 手续费(只显示可用余额,不展示总余额/冻结) */
|
||||
actualAmount() {
|
||||
const amountInt = this.toScaledInt(this.withdrawForm.amount)
|
||||
const feeInt = this.toScaledInt(this.withdrawForm.fee)
|
||||
if (!Number.isFinite(amountInt) || !Number.isFinite(feeInt)) return '0'
|
||||
const res = amountInt - feeInt
|
||||
return res > 0 ? this.formatDec6FromInt(res) : '0'
|
||||
}
|
||||
},
|
||||
created() {
|
||||
this.fetchMyShop()
|
||||
},
|
||||
methods: {
|
||||
/** 余额展示:带币种单位 */
|
||||
formatBalance(row) {
|
||||
try {
|
||||
const num = Number(row && row.balance)
|
||||
const valid = Number.isFinite(num)
|
||||
const coin = String(row && row.payCoin ? row.payCoin : '').toUpperCase()
|
||||
if (!valid) return '-'
|
||||
const text = String(num)
|
||||
return coin ? `${text} ${coin}` : text
|
||||
} catch (e) {
|
||||
return '-'
|
||||
}
|
||||
},
|
||||
/** 仅数字部分(用于红色显示) */
|
||||
formatAmount(row) {
|
||||
try {
|
||||
const num = Number(row && row.balance)
|
||||
if (!Number.isFinite(num)) return '-'
|
||||
return String(num)
|
||||
} catch (e) {
|
||||
return '-'
|
||||
}
|
||||
},
|
||||
/** 仅币种单位(大写) */
|
||||
formatCoin(row) {
|
||||
return String(row && row.payCoin ? row.payCoin : '').toUpperCase()
|
||||
},
|
||||
/** 打开提现对话框(行数据驱动) */
|
||||
async handleWithdraw(row) {
|
||||
this.currentWithdrawRow = row || {}
|
||||
const fee = Number(row && (row.serviceCharge != null ? row.serviceCharge : row.charge))
|
||||
this.withdrawForm.fee = Number.isFinite(fee) ? this.formatDec6(fee) : '0.00'
|
||||
this.withdrawForm.amount = ''
|
||||
// 收款地址默认填充为当前行地址,且禁用编辑
|
||||
this.withdrawForm.toAddress = row && row.payAddress ? row.payAddress : ''
|
||||
this.withdrawForm.googleCode = ''
|
||||
this.withdrawAddressEditable = false
|
||||
// 初始化校验规则
|
||||
this.withdrawRules = {
|
||||
amount: [
|
||||
{ required: true, message: '请输入提现金额', trigger: 'blur' },
|
||||
{ validator: this.validateWithdrawAmount, trigger: 'blur' }
|
||||
],
|
||||
// 地址为只读已填,不再要求用户输入
|
||||
googleCode: [
|
||||
{ required: true, message: '请输入谷歌验证码', trigger: 'blur' },
|
||||
{ validator: this.validateGoogleCode, trigger: 'blur' }
|
||||
]
|
||||
}
|
||||
this.withdrawDialogVisible = true
|
||||
},
|
||||
/* 点击“修改”启用收款地址编辑并聚焦 */
|
||||
handleEditAddressClick() {
|
||||
this.withdrawAddressEditable = true
|
||||
this.$nextTick(() => {
|
||||
const input = this.$refs.withdrawToAddressInput
|
||||
if (input && input.focus) input.focus()
|
||||
})
|
||||
},
|
||||
/* 提现金额输入(<=6位小数) */
|
||||
handleAmountInput(v) {
|
||||
let s = String(v || '')
|
||||
s = s.replace(/[^0-9.]/g, '')
|
||||
const i = s.indexOf('.')
|
||||
if (i !== -1) {
|
||||
s = s.slice(0, i + 1) + s.slice(i + 1).replace(/\./g, '')
|
||||
const [intPart, decPart = ''] = s.split('.')
|
||||
s = intPart + '.' + decPart.slice(0, 6)
|
||||
}
|
||||
this.withdrawForm.amount = s
|
||||
},
|
||||
/* 谷歌验证码仅数字 */
|
||||
handleGoogleCodeInput(v) {
|
||||
this.withdrawForm.googleCode = String(v || '').replace(/\D/g, '')
|
||||
},
|
||||
/* 确认提现 - 调用后端卖家提现接口 */
|
||||
confirmWithdraw() {
|
||||
this.$refs.withdrawForm.validate(async (valid) => {
|
||||
if (!valid) return
|
||||
this.withdrawLoading = true
|
||||
try {
|
||||
const row = this.currentWithdrawRow || {}
|
||||
const payload = {
|
||||
toChain: row.chain,
|
||||
toSymbol: row.payCoin,
|
||||
amount: Number(this.withdrawForm.amount),
|
||||
toAddress: this.withdrawForm.toAddress,
|
||||
fromAddress: row.payAddress || this.withdrawForm.toAddress || '',
|
||||
code: this.withdrawForm.googleCode,
|
||||
serviceCharge: Number(this.withdrawForm.fee) || 0
|
||||
}
|
||||
const res = await withdrawBalanceForSeller(payload)
|
||||
if (res && (res.code === 0 || res.code === 200)) {
|
||||
this.$message.success('提现申请已提交,请等待处理')
|
||||
this.withdrawDialogVisible = false
|
||||
this.fetchShopConfigs(this.shop.id)
|
||||
}
|
||||
} catch (e) {
|
||||
console.error('卖家提现失败', e)
|
||||
} finally {
|
||||
this.withdrawLoading = false
|
||||
}
|
||||
})
|
||||
},
|
||||
/* 工具:最多6位小数显示 */
|
||||
formatDec6(value) {
|
||||
if (value === null || value === undefined || value === '') return '0'
|
||||
let s = String(value)
|
||||
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
|
||||
},
|
||||
/* 工具:金额字符串 -> 10^6 精度整数 */
|
||||
toScaledInt(amountStr, decimals = 6) {
|
||||
if (amountStr === null || amountStr === undefined) return 0
|
||||
const normalized = String(amountStr).trim()
|
||||
if (normalized === '') return 0
|
||||
const re = new RegExp(`^\\d+(?:\\.(\\d{0,${decimals}}))?$`)
|
||||
const match = normalized.match(re)
|
||||
if (!match) {
|
||||
const n = Number(normalized)
|
||||
if (!Number.isFinite(n)) return 0
|
||||
const scale = Math.pow(10, decimals)
|
||||
return Math.round(n * scale)
|
||||
}
|
||||
const [intPart, decPartRaw] = normalized.split('.')
|
||||
const decPart = (decPartRaw || '').padEnd(decimals, '0').slice(0, decimals)
|
||||
const scale = Math.pow(10, decimals)
|
||||
return Number(intPart) * scale + Number(decPart)
|
||||
},
|
||||
/* 工具:10^6 精度整数 -> 最多6位小数字符串 */
|
||||
formatDec6FromInt(intVal) {
|
||||
const sign = intVal < 0 ? '-' : ''
|
||||
const abs = Math.abs(intVal)
|
||||
const scale = Math.pow(10, 6)
|
||||
const intPart = Math.floor(abs / scale)
|
||||
const decPart = String(abs % scale).padStart(6, '0')
|
||||
const s = `${sign}${intPart}.${decPart}`
|
||||
return s.replace(/\.0+$/, '').replace(/(\.\d*?)0+$/, '$1')
|
||||
},
|
||||
/* 校验:提现金额 */
|
||||
validateWithdrawAmount(rule, value, callback) {
|
||||
const amtInt = this.toScaledInt(value)
|
||||
if (!Number.isFinite(amtInt) || amtInt <= 0) { callback(new Error('请输入有效的金额')); return }
|
||||
const feeInt = this.toScaledInt(this.withdrawForm.fee)
|
||||
const balanceInt = this.toScaledInt((this.currentWithdrawRow && this.currentWithdrawRow.balance) || 0)
|
||||
if (amtInt >= balanceInt) { callback(new Error('提现金额必须小于可用余额')); return }
|
||||
if (amtInt <= feeInt) { callback(new Error('提现金额必须大于手续费')); return }
|
||||
if (amtInt < 1000000) { callback(new Error('最小提现金额为 1')); return }
|
||||
callback()
|
||||
},
|
||||
/* 校验:谷歌验证码 */
|
||||
validateGoogleCode(rule, value, callback) {
|
||||
const v = String(value || '')
|
||||
if (!/^\d{6}$/.test(v)) { callback(new Error('谷歌验证码必须是6位数字')); return }
|
||||
callback()
|
||||
},
|
||||
/**
|
||||
* 手续费率显示:最多6位小数,去除多余的0;空值显示为 '-'
|
||||
*/
|
||||
@@ -362,7 +612,7 @@ export default {
|
||||
}
|
||||
|
||||
try {
|
||||
const res = await getShopConfig({id:shopId})
|
||||
const res = await getShopConfigV2({id:shopId})
|
||||
if (res && (res.code === 0 || res.code === 200) && Array.isArray(res.data)) {
|
||||
// 直接使用后端返回的数据;children: [{payCoin,image}]
|
||||
this.shopConfigs = res.data
|
||||
@@ -430,15 +680,7 @@ export default {
|
||||
},
|
||||
|
||||
submitConfigEdit() {
|
||||
// 基础校验
|
||||
if (!this.configForm.chainLabel && !this.configForm.chainValue) {
|
||||
this.$message.warning('请选择支付链')
|
||||
return
|
||||
}
|
||||
if (!this.configForm.payCoins || this.configForm.payCoins.length === 0) {
|
||||
this.$message.warning('请选择支付币种')
|
||||
return
|
||||
}
|
||||
// 仅校验钱包地址
|
||||
const addr = (this.configForm.payAddress || '').trim()
|
||||
if (!addr) {
|
||||
this.$message.warning('请输入钱包地址')
|
||||
@@ -446,11 +688,17 @@ export default {
|
||||
}
|
||||
const payload = {
|
||||
id: this.configForm.id,
|
||||
chain: this.configForm.chainValue || this.configForm.chainLabel,
|
||||
payCoin: (this.configForm.payCoins || []).join(','),
|
||||
chain: this.configForm.chainValue || this.configForm.chainLabel || '',
|
||||
payAddress: this.configForm.payAddress
|
||||
}
|
||||
this.updateShopConfig(payload)
|
||||
;(async () => {
|
||||
const res = await updateShopConfigV2(payload)
|
||||
if (res && (res.code === 0 || res.code === 200)) {
|
||||
this.$message.success('保存成功')
|
||||
this.visibleConfigEdit = false
|
||||
this.fetchShopConfigs(this.shop.id)
|
||||
}
|
||||
})()
|
||||
},
|
||||
removeSelectedCoin(labelUpper) {
|
||||
const label = String(labelUpper || '').toLowerCase()
|
||||
@@ -690,6 +938,14 @@ export default {
|
||||
.guide-note { margin-top: 10px; color: #6b7280; font-size: 13px; background: #f9fafb; border: 1px dashed #e5e7eb; padding: 8px 10px; border-radius: 8px; }
|
||||
.coin-list { display: flex; align-items: center; gap: 8px; }
|
||||
.coin-img { width: 20px; height: 20px; border-radius: 4px; display: inline-block; }
|
||||
/* 提现弹窗样式(与钱包页统一) */
|
||||
.balance-info { font-size: 12px; color: #666; margin-top: 4px; text-align: left; }
|
||||
.fee-info { font-size: 12px; color: #e6a23c; margin-top: 4px; text-align: left; }
|
||||
.actual-amount-info { font-size: 12px; color: #67c23a; margin-top: 4px; text-align: left; font-weight: 500; }
|
||||
.address-tip { font-size: 12px; color: #f56c6c; margin-top: 4px; line-height: 1.4; text-align: left; }
|
||||
/* 余额数字红色显示 */
|
||||
.balance-num { color: #ff4d4f; font-weight: 600; }
|
||||
.balance-unit { color: #606266; }
|
||||
</style>
|
||||
|
||||
<style>
|
||||
|
||||
@@ -22,48 +22,74 @@
|
||||
<el-radio label="GPU">GPU</el-radio>
|
||||
</el-radio-group>
|
||||
</el-form-item>
|
||||
<!-- ASIC:币种与算法(支持多个,逗号分隔) -->
|
||||
<el-form-item label="币种(多个用逗号隔开)" prop="coinsInput" :required="form.machineCategory === 'ASIC'">
|
||||
<el-input
|
||||
v-model="form.coinsInput"
|
||||
placeholder="例如:USDT, BTC, ETH"
|
||||
style="width: 50%;"
|
||||
@input="handleCoinsInput"
|
||||
/>
|
||||
<!-- ASIC:币种/算法/理论算力/单位 同行,支持多行动态增删 -->
|
||||
<el-form-item
|
||||
v-if="form.machineCategory === 'ASIC'"
|
||||
label="币种/算法/算力/单位"
|
||||
prop="coinAndAlgoList"
|
||||
:required="true"
|
||||
>
|
||||
<div class="coin-algo-rows">
|
||||
<div
|
||||
class="coin-algo-line"
|
||||
v-for="(row, idx) in form.coinAndAlgoList"
|
||||
:key="idx"
|
||||
>
|
||||
<el-input
|
||||
v-model="row.coin"
|
||||
placeholder="币种"
|
||||
class="coin-input"
|
||||
@input="handleCoinInput(idx)"
|
||||
/>
|
||||
<el-input
|
||||
v-model="row.algorithm"
|
||||
placeholder="算法"
|
||||
class="algo-input"
|
||||
@input="handleAlgorithmInput(idx)"
|
||||
/>
|
||||
<el-input
|
||||
v-model="row.theoryPower"
|
||||
placeholder="理论算力"
|
||||
inputmode="decimal"
|
||||
class="power-input"
|
||||
@input="handleCoinRowTheoryInput(idx)"
|
||||
/>
|
||||
<el-select
|
||||
v-model="row.unit"
|
||||
placeholder="单位"
|
||||
class="unit-select"
|
||||
@change="handleCoinRowUnitChange(idx, $event)"
|
||||
>
|
||||
<el-option label="KH/S" value="KH/S" />
|
||||
<el-option label="MH/S" value="MH/S" />
|
||||
<el-option label="GH/S" value="GH/S" />
|
||||
<el-option label="TH/S" value="TH/S" />
|
||||
<el-option label="PH/S" value="PH/S" />
|
||||
</el-select>
|
||||
<el-button
|
||||
class="op-btn"
|
||||
type="primary"
|
||||
icon="el-icon-plus"
|
||||
circle
|
||||
@click="handleAddCoinAlgoRow"
|
||||
:aria-label="'新增一行'"
|
||||
/>
|
||||
<el-button
|
||||
v-if="form.coinAndAlgoList.length > 1"
|
||||
class="op-btn"
|
||||
icon="el-icon-minus"
|
||||
circle
|
||||
@click="handleRemoveCoinAlgoRow(idx)"
|
||||
:aria-label="'删除该行'"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</el-form-item>
|
||||
<el-form-item label="算法(多个用逗号隔开)" prop="algorithmsInput" :required="form.machineCategory === 'ASIC'">
|
||||
<el-input
|
||||
v-model="form.algorithmsInput"
|
||||
placeholder="例如:SHA-256, ETHASH"
|
||||
style="width: 50%;"
|
||||
@input="handleAlgorithmsInput"
|
||||
/>
|
||||
</el-form-item>
|
||||
<div style="text-align:left; color:#909399; font-size:12px; margin:-6px 0 10px 160px;">
|
||||
输入多个用逗号隔开
|
||||
</div>
|
||||
|
||||
<el-form-item label="矿机型号" prop="type" :required="true">
|
||||
<el-input style="width: 50%;" v-model="form.type" placeholder="示例:龍珠" :maxlength="20" @input="handleTypeInput" />
|
||||
</el-form-item>
|
||||
<el-form-item label="理论算力" prop="theoryPower">
|
||||
<el-input
|
||||
v-model="form.theoryPower"
|
||||
placeholder="请输入单机理论算力"
|
||||
inputmode="decimal"
|
||||
@input="handleNumeric('theoryPower')"
|
||||
style="width: 50%;"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item label="算力单位" prop="unit">
|
||||
<el-select v-model="form.unit" placeholder="请选择算力单位">
|
||||
<el-option label="KH/S" value="KH/S" />
|
||||
<el-option label="MH/S" value="MH/S" />
|
||||
<el-option label="GH/S" value="GH/S" />
|
||||
<el-option label="TH/S" value="TH/S" />
|
||||
<el-option label="PH/S" value="PH/S" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<!-- 理论算力与单位已合并到上面的同行多行输入 -->
|
||||
<el-form-item label="最大租赁天数" prop="maxLeaseDays">
|
||||
<el-input
|
||||
v-model="form.maxLeaseDays"
|
||||
@@ -197,6 +223,7 @@
|
||||
<script>
|
||||
import { addSingleOrBatchMachine ,downloadClient,addAsicMachine} from '../../api/machine'
|
||||
import { getPayTypes } from '../../api/products'
|
||||
import request from '../../utils/request'
|
||||
export default {
|
||||
name: 'AccountProductMachineAdd',
|
||||
data() {
|
||||
@@ -209,13 +236,12 @@ export default {
|
||||
machineCategory: 'ASIC',
|
||||
/** 出售机器数量(仅 ASIC 模式使用) */
|
||||
sellCount: '',
|
||||
/** ASIC 模式下币种/算法输入(逗号分隔的原始文本) */
|
||||
coinsInput: '',
|
||||
algorithmsInput: '',
|
||||
/** ASIC:币种/算法/理论算力/单位 行编辑(最多10行) */
|
||||
coinAndAlgoList: [
|
||||
{ coin: '', algorithm: '', theoryPower: '', unit: 'TH/S' }
|
||||
],
|
||||
powerDissipation: null,
|
||||
theoryPower: null,
|
||||
type: '',
|
||||
unit: 'TH/S',
|
||||
cost: '',
|
||||
costMap: {}, // { 'CHAIN-COIN': '123.45' }
|
||||
maxLeaseDays: ''
|
||||
@@ -245,48 +271,8 @@ export default {
|
||||
trigger: 'blur'
|
||||
}
|
||||
],
|
||||
coinsInput: [
|
||||
{
|
||||
validator: (rule, value, callback) => {
|
||||
if (String(this.form.machineCategory).toUpperCase() !== 'ASIC') { callback(); return }
|
||||
const s = String(value || '').trim()
|
||||
if (!s) { callback(new Error('请输入币种,多个用逗号隔开')); return }
|
||||
// 禁止汉字,且仅允许英数字与逗号/空格/连字符
|
||||
if (/[\u4e00-\u9fa5]/.test(s)) { callback(new Error('币种不允许输入汉字')); return }
|
||||
// 逐项校验(英文、数字,长度1-10)
|
||||
const tokens = s.split(/[,\s,、]+/).map(i => i.trim()).filter(Boolean)
|
||||
const pattern = /^[A-Za-z0-9]{1,10}$/
|
||||
for (let i = 0; i < tokens.length; i += 1) {
|
||||
if (!pattern.test(tokens[i])) {
|
||||
callback(new Error(`币种“${tokens[i]}”格式非法,仅允许字母或数字`))
|
||||
return
|
||||
}
|
||||
}
|
||||
callback()
|
||||
},
|
||||
trigger: 'blur'
|
||||
}
|
||||
],
|
||||
algorithmsInput: [
|
||||
{
|
||||
validator: (rule, value, callback) => {
|
||||
if (String(this.form.machineCategory).toUpperCase() !== 'ASIC') { callback(); return }
|
||||
const s = String(value || '').trim()
|
||||
if (!s) { callback(new Error('请输入算法,多个用逗号隔开')); return }
|
||||
if (/[\u4e00-\u9fa5]/.test(s)) { callback(new Error('算法不允许输入汉字')); return }
|
||||
// 逐项校验(字母/数字/连字符,2-20)
|
||||
const tokens = s.split(/[,\s,、]+/).map(i => i.trim()).filter(Boolean)
|
||||
const pattern = /^[A-Za-z0-9-]{2,20}$/
|
||||
for (let i = 0; i < tokens.length; i += 1) {
|
||||
if (!pattern.test(tokens[i])) {
|
||||
callback(new Error(`算法“${tokens[i]}”格式非法,仅允许字母、数字或“-”`))
|
||||
return
|
||||
}
|
||||
}
|
||||
callback()
|
||||
},
|
||||
trigger: 'blur'
|
||||
}
|
||||
coinAndAlgoList: [
|
||||
{ validator: (rule, value, callback) => this.validateCoinAlgoRows(rule, value, callback), trigger: 'blur' }
|
||||
],
|
||||
sellCount: [
|
||||
{
|
||||
@@ -316,21 +302,6 @@ export default {
|
||||
trigger: 'blur'
|
||||
}
|
||||
],
|
||||
theoryPower: [
|
||||
{ required: true, message: '理论算力不能为空', trigger: 'blur' },
|
||||
{
|
||||
validator: (rule, value, callback) => {
|
||||
const str = String(value || '')
|
||||
if (!str) { callback(new Error('理论算力不能为空')); return }
|
||||
const pattern = /^\d{1,6}(\.\d{1,4})?$/
|
||||
if (!pattern.test(str)) { callback(new Error('理论算力整数最多6位,小数最多4位')); return }
|
||||
if (Number(str) <= 0) { callback(new Error('理论算力必须大于0')); return }
|
||||
callback()
|
||||
},
|
||||
trigger: 'blur'
|
||||
}
|
||||
],
|
||||
unit: [ { required: true, message: '请选择算力单位', trigger: 'change' } ],
|
||||
cost: [
|
||||
{
|
||||
validator(rule, value, callback) {
|
||||
@@ -510,6 +481,109 @@ export default {
|
||||
this.getPayTypes()
|
||||
},
|
||||
methods: {
|
||||
/** ASIC 行校验:币种/算法/理论算力/单位 */
|
||||
validateCoinAlgoRows(rule, value, callback) {
|
||||
try {
|
||||
const rows = Array.isArray(this.form.coinAndAlgoList) ? this.form.coinAndAlgoList : []
|
||||
if (!rows.length) { callback(new Error('请至少添加一行币种/算法/算力/单位')); return }
|
||||
const coinPattern = /^[A-Za-z0-9]{1,10}$/
|
||||
const algoPattern = /^[A-Za-z0-9-]{2,20}$/
|
||||
const powerPattern = /^\d{1,6}(\.\d{1,4})?$/
|
||||
for (let i = 0; i < rows.length; i += 1) {
|
||||
const r = rows[i] || {}
|
||||
const coin = String(r.coin || '').trim()
|
||||
const algo = String(r.algorithm || '').trim()
|
||||
const power = String(r.theoryPower || '').trim()
|
||||
const unit = String(r.unit || '').trim()
|
||||
if (!coin) { callback(new Error(`第 ${i + 1} 行:请输入币种`)); return }
|
||||
if (!coinPattern.test(coin)) { callback(new Error(`第 ${i + 1} 行:币种仅允许字母或数字,1-10 位`)); return }
|
||||
if (!algo) { callback(new Error(`第 ${i + 1} 行:请输入算法`)); return }
|
||||
if (!algoPattern.test(algo)) { callback(new Error(`第 ${i + 1} 行:算法仅允许字母/数字/“-”,2-20 位`)); return }
|
||||
if (!power || !powerPattern.test(power) || Number(power) <= 0) {
|
||||
callback(new Error(`第 ${i + 1} 行:理论算力需大于0,整数最多6位,小数最多4位`)); return
|
||||
}
|
||||
if (!unit) { callback(new Error(`第 ${i + 1} 行:请选择算力单位`)); return }
|
||||
}
|
||||
callback()
|
||||
} catch (e) {
|
||||
callback(new Error('请检查币种/算法/算力/单位填写'))
|
||||
}
|
||||
},
|
||||
/** 行:币种输入过滤(去中文,仅字母数字) */
|
||||
handleCoinInput(index) {
|
||||
const r = this.form.coinAlgoRows[index]
|
||||
let v = String(r.coin || '')
|
||||
v = v.replace(/[\u4e00-\u9fa5]/g, '').replace(/[^A-Za-z0-9]/g, '')
|
||||
this.$set(this.form.coinAlgoRows[index], 'coin', v.toUpperCase())
|
||||
},
|
||||
/** 行:算法输入过滤(去中文,仅字母数字与-) */
|
||||
handleAlgorithmInput(index) {
|
||||
const r = this.form.coinAlgoRows[index]
|
||||
let v = String(r.algorithm || '')
|
||||
v = v.replace(/[\u4e00-\u9fa5]/g, '').replace(/[^A-Za-z0-9-]/g, '')
|
||||
this.$set(this.form.coinAlgoRows[index], 'algorithm', v.toUpperCase())
|
||||
},
|
||||
/** 行:理论算力输入限制(6整数4小数) */
|
||||
handleCoinRowTheoryInput(index) {
|
||||
let v = String(this.form.coinAndAlgoList[index].theoryPower ?? '')
|
||||
v = v.replace(/[^0-9.]/g, '')
|
||||
const firstDot = v.indexOf('.')
|
||||
if (firstDot !== -1) {
|
||||
v = v.slice(0, firstDot + 1) + v.slice(firstDot + 1).replace(/\./g, '')
|
||||
}
|
||||
const 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}` : (endsWithDot ? `${intPart}.` : intPart)
|
||||
this.$set(this.form.coinAndAlgoList[index], 'theoryPower', v)
|
||||
},
|
||||
/** 行:单位变更 */
|
||||
handleCoinRowUnitChange(index, value) {
|
||||
this.$set(this.form.coinAndAlgoList[index], 'unit', value)
|
||||
},
|
||||
/** 新增一行 */
|
||||
handleAddCoinAlgoRow() {
|
||||
if (this.form.coinAndAlgoList.length >= 10) {
|
||||
this.$message.warning('最多添加 10 行')
|
||||
return
|
||||
}
|
||||
const last = (this.form.coinAndAlgoList[this.form.coinAndAlgoList.length - 1]) || { unit: 'TH/S' }
|
||||
this.form.coinAndAlgoList.push({ coin: '', algorithm: '', theoryPower: '', unit: last.unit || 'TH/S' })
|
||||
},
|
||||
/** 删除一行 */
|
||||
handleRemoveCoinAlgoRow(index) {
|
||||
if (this.form.coinAndAlgoList.length <= 1) return
|
||||
this.form.coinAndAlgoList.splice(index, 1)
|
||||
},
|
||||
/** 从多行聚合 coin CSV */
|
||||
buildCoinCsvFromRows() {
|
||||
const set = new Set()
|
||||
const rows = Array.isArray(this.form.coinAndAlgoList) ? this.form.coinAndAlgoList : []
|
||||
rows.forEach(r => {
|
||||
const token = String(r.coin || '')
|
||||
.split(/[,\s,、]+/)
|
||||
.map(s => s.trim().toUpperCase())
|
||||
.filter(Boolean)
|
||||
token.forEach(t => set.add(t))
|
||||
})
|
||||
return Array.from(set).join(',')
|
||||
},
|
||||
/** 从多行聚合 algorithm CSV */
|
||||
buildAlgoCsvFromRows() {
|
||||
const set = new Set()
|
||||
const rows = Array.isArray(this.form.coinAndAlgoList) ? this.form.coinAndAlgoList : []
|
||||
rows.forEach(r => {
|
||||
const token = String(r.algorithm || '')
|
||||
.split(/[,\s,、]+/)
|
||||
.map(s => s.trim().toUpperCase())
|
||||
.filter(Boolean)
|
||||
token.forEach(t => set.add(t))
|
||||
})
|
||||
return Array.from(set).join(',')
|
||||
},
|
||||
/** 实时过滤币种输入中的中文字符(仅保留英文/数字/分隔符) */
|
||||
handleCoinsInput() {
|
||||
let v = String(this.form.coinsInput || '')
|
||||
@@ -629,27 +703,31 @@ export default {
|
||||
*/
|
||||
handleDownloadClient() {
|
||||
// 走后端接口下载客户端程序
|
||||
downloadClient()
|
||||
.then((res) => {
|
||||
// 处理 blob 下载(兼容封装返回 data 或直接返回 Blob)
|
||||
const data = (res && res.data) ? res.data : res
|
||||
const blob = data instanceof Blob ? data : new Blob([data], { type: 'application/octet-stream' })
|
||||
const url = window.URL.createObjectURL(blob)
|
||||
const a = document.createElement('a')
|
||||
a.style.display = 'none'
|
||||
a.href = url
|
||||
// 默认文件名(可由后端 Content-Disposition 提供,这里简单兜底)
|
||||
a.download = 'gpu-client.zip'
|
||||
document.body.appendChild(a)
|
||||
a.click()
|
||||
document.body.removeChild(a)
|
||||
window.URL.revokeObjectURL(url)
|
||||
this.$message.success('客户端下载已开始')
|
||||
this.hasDownloadedClient = true
|
||||
})
|
||||
.catch(() => {
|
||||
this.$message.error('下载失败,请稍后重试')
|
||||
|
||||
|
||||
let userEmail =JSON.parse(localStorage.getItem('leasEmail'))
|
||||
if (!userEmail) {
|
||||
// 弹出确认框,用户确认后跳转到外部网站
|
||||
this.$confirm('获取用户信息失败,请重新进入网站?', '提示', {
|
||||
confirmButtonText: '确认',
|
||||
cancelButtonText: '取消',
|
||||
type: 'warning'
|
||||
}).then(() => {
|
||||
// 用户点击确认,跳转到外部网站
|
||||
window.open('https://test.m2pool.com/', '_blank')
|
||||
}).catch(() => {
|
||||
// 用户点击取消,不执行任何操作
|
||||
})
|
||||
return
|
||||
}
|
||||
console.log(userEmail,'userEmail');
|
||||
|
||||
this.downloadUrl = ` ${request.defaults.baseURL}/lease/user/downloadClient?userEmail=${userEmail}`
|
||||
let a = document.createElement(`a`)
|
||||
a.href = this.downloadUrl
|
||||
a.click()
|
||||
|
||||
|
||||
},
|
||||
/**
|
||||
* GPU 客户端已启动:跳转至商品列表
|
||||
@@ -1109,8 +1187,8 @@ export default {
|
||||
}
|
||||
// 统一售价与最大租赁天数已在表单级校验中处理,无需逐机校验
|
||||
// 组装确认数据并弹框
|
||||
const coinStr = this.normalizeCsv(this.form.coinsInput || this.form.coin, true)
|
||||
const algoStr = this.normalizeCsv(this.form.algorithmsInput, true)
|
||||
const coinStr = this.buildCoinCsvFromRows()
|
||||
const algoStr = this.buildAlgoCsvFromRows()
|
||||
this.confirmData = {
|
||||
coin: coinStr || '-',
|
||||
algorithm: algoStr || '-',
|
||||
@@ -1125,15 +1203,18 @@ export default {
|
||||
this.saving = true
|
||||
try {
|
||||
// 统一售卖新增接口参数
|
||||
const list = (this.form.coinAndAlgoList || []).map(r => ({
|
||||
coin: String(r.coin || '').toUpperCase().trim(),
|
||||
algorithm: String(r.algorithm || '').toUpperCase().trim(),
|
||||
theoryPower: Number(r.theoryPower) || 0,
|
||||
unit: r.unit
|
||||
}))
|
||||
const payload = {
|
||||
// 逗号分隔(中英文逗号都兼容),统一为英文逗号并大写
|
||||
coin: this.normalizeCsv(this.form.coinsInput || this.form.coin, true),
|
||||
algorithm: this.normalizeCsv(this.form.algorithmsInput, true),
|
||||
coinAndAlgoList: list,
|
||||
maxLeaseDays: Number(this.form.maxLeaseDays) || 0,
|
||||
name: this.form.type,
|
||||
powerDissipation: Number(this.form.powerDissipation) || 0,
|
||||
theoryPower: Number(this.form.theoryPower) || 0,
|
||||
unit: this.form.unit,
|
||||
saleNumbers: Number(this.form.sellCount) || 0,
|
||||
priceList: this.buildPriceList()
|
||||
}
|
||||
@@ -1229,5 +1310,13 @@ export default {
|
||||
.price-multi { gap: 6px; }
|
||||
.price-items { gap: 6px; }
|
||||
.cost-multi { gap: 6px; }
|
||||
/* ASIC 币种/算法/算力/单位 多行 */
|
||||
.coin-algo-rows { display: grid; gap: 8px; width: 100%; }
|
||||
.coin-algo-line { display: flex; align-items: center; gap: 8px; }
|
||||
.coin-algo-line .coin-input { width: 18%; min-width: 140px; }
|
||||
.coin-algo-line .algo-input { width: 24%; min-width: 160px; }
|
||||
.coin-algo-line .power-input { width: 20%; min-width: 140px; }
|
||||
.coin-algo-line .unit-select { width: 16%; min-width: 120px; }
|
||||
.coin-algo-line .op-btn { flex: 0 0 auto; }
|
||||
</style>
|
||||
|
||||
|
||||
@@ -51,7 +51,7 @@
|
||||
|
||||
<!-- 售价展示币种选择(影响 ASIC 表格“售价”列展示) -->
|
||||
<div
|
||||
v-if="payTypes && payTypes.length"
|
||||
v-if="listParams.type === 0 && payTypes && payTypes.length"
|
||||
class="price-select-bar"
|
||||
style="margin:8px 0 4px; display:flex; justify-content:flex-end; align-items:center;"
|
||||
>
|
||||
@@ -103,16 +103,24 @@
|
||||
</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<!-- 币种(过长省略,hover 显示) -->
|
||||
<el-table-column prop="coin" label="币种" min-width="100" show-overflow-tooltip />
|
||||
<!-- 算法(过长省略,hover 显示) -->
|
||||
<el-table-column prop="algorithm" label="算法" min-width="100" show-overflow-tooltip />
|
||||
<!-- 币种(来自 coinAndAlgoList,多项用逗号拼接;兜底 row.coin) -->
|
||||
<el-table-column label="币种" min-width="140" show-overflow-tooltip>
|
||||
<template #default="scope">
|
||||
<div class="ellipsis-cell">{{ getRowCoinText(scope.row) }}</div>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<!-- 算法(来自 coinAndAlgoList,多项用逗号拼接;兜底 row.algorithm) -->
|
||||
<el-table-column label="算法" min-width="160" show-overflow-tooltip>
|
||||
<template #default="scope">
|
||||
<div class="ellipsis-cell">{{ getRowAlgorithmText(scope.row) }}</div>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<!-- 矿机型号 -->
|
||||
<el-table-column prop="name" label="矿机型号" />
|
||||
<!-- 理论算力(附带单位) -->
|
||||
<el-table-column label="理论算力">
|
||||
<el-table-column label="理论算力" min-width="170" show-overflow-tooltip>
|
||||
<template #default="scope">
|
||||
<span>{{ getTheoryText(scope.row) }}</span>
|
||||
<div class="ellipsis-cell">{{ getTheoryText(scope.row) }}</div>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<!-- 功耗(kw/h) -->
|
||||
@@ -272,30 +280,76 @@
|
||||
<el-dialog
|
||||
:visible.sync="editDialog.visible"
|
||||
:close-on-click-modal="false"
|
||||
width="720px"
|
||||
width="70VW"
|
||||
:title="'编辑商品 - ' + ((editDialog.form && editDialog.form.name) ? editDialog.form.name : '')"
|
||||
>
|
||||
<el-form
|
||||
v-if="editDialog.form"
|
||||
:model="editDialog.form"
|
||||
label-width="120px"
|
||||
label-width="160px"
|
||||
ref="editForm"
|
||||
class="edit-form"
|
||||
>
|
||||
<el-form-item label="矿机型号">
|
||||
<el-input v-model.trim="editDialog.form.name" maxlength="60" />
|
||||
</el-form-item>
|
||||
<el-form-item label="币种(逗号隔开)">
|
||||
<el-input
|
||||
v-model.trim="editDialog.form.coin"
|
||||
placeholder="例如:USDT,ETH"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item label="算法(逗号隔开)">
|
||||
<el-input
|
||||
v-model.trim="editDialog.form.algorithm"
|
||||
placeholder="例如:SHA256,ETHASH"
|
||||
/>
|
||||
<!-- 编辑:币种/算法/理论算力/单位(可增删,最多10行) -->
|
||||
<el-form-item label="币种/算法/算力/单位">
|
||||
<div class="coin-algo-rows">
|
||||
<div
|
||||
class="coin-algo-line"
|
||||
v-for="(row, idx) in editDialog.form.coinAndAlgoList"
|
||||
:key="'edit-ca-' + idx"
|
||||
>
|
||||
<el-input
|
||||
v-model="row.coin"
|
||||
placeholder="币种"
|
||||
class="coin-input"
|
||||
@input="editHandleCoinInput(idx)"
|
||||
/>
|
||||
<el-input
|
||||
v-model="row.algorithm"
|
||||
placeholder="算法"
|
||||
class="algo-input"
|
||||
@input="editHandleAlgorithmInput(idx)"
|
||||
/>
|
||||
<el-input
|
||||
v-model="row.theoryPower"
|
||||
placeholder="理论算力"
|
||||
inputmode="decimal"
|
||||
class="power-input"
|
||||
@input="editHandleRowTheoryInput(idx)"
|
||||
/>
|
||||
<el-select
|
||||
v-model="row.unit"
|
||||
placeholder="单位"
|
||||
class="unit-select"
|
||||
@change="editHandleRowUnitChange(idx, $event)"
|
||||
>
|
||||
<el-option label="KH/S" value="KH/S" />
|
||||
<el-option label="MH/S" value="MH/S" />
|
||||
<el-option label="GH/S" value="GH/S" />
|
||||
<el-option label="TH/S" value="TH/S" />
|
||||
<el-option label="PH/S" value="PH/S" />
|
||||
</el-select>
|
||||
<el-button
|
||||
class="op-btn"
|
||||
type="primary"
|
||||
icon="el-icon-plus"
|
||||
circle
|
||||
@click="editHandleAddRow"
|
||||
:aria-label="'新增一行'"
|
||||
/>
|
||||
<el-button
|
||||
v-if="(editDialog.form.coinAndAlgoList || []).length > 1"
|
||||
class="op-btn"
|
||||
icon="el-icon-minus"
|
||||
circle
|
||||
@click="editHandleRemoveRow(idx)"
|
||||
:aria-label="'删除该行'"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</el-form-item>
|
||||
<el-form-item label="最大租赁天数">
|
||||
<el-input
|
||||
@@ -319,24 +373,7 @@
|
||||
style="width: 200px"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item label="理论算力">
|
||||
<div style="display:flex; gap:12px; align-items:center;">
|
||||
<el-input
|
||||
v-model="editDialog.form.theoryPower"
|
||||
placeholder="理论算力"
|
||||
@input="editDialog.form.theoryPower = (String(editDialog.form.theoryPower||'').replace(/[^\d.]/g,''))"
|
||||
style="width: 220px"
|
||||
/>
|
||||
<el-select v-model="editDialog.form.unit" placeholder="单位" style="width: 120px">
|
||||
<el-option label="H/S" value="H/S" />
|
||||
<el-option label="KH/S" value="KH/S" />
|
||||
<el-option label="MH/S" value="MH/S" />
|
||||
<el-option label="GH/S" value="GH/S" />
|
||||
<el-option label="TH/S" value="TH/S" />
|
||||
<el-option label="PH/S" value="PH/S" />
|
||||
</el-select>
|
||||
</div>
|
||||
</el-form-item>
|
||||
<!-- 理论算力与单位已合并到上方多行编辑 -->
|
||||
<el-form-item label="出售数量(台)">
|
||||
<el-input
|
||||
v-model="editDialog.form.saleNumbers"
|
||||
@@ -500,6 +537,89 @@ export default {
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
/** 编辑弹窗:币种输入过滤(仅字母数字,转大写) */
|
||||
editHandleCoinInput(index) {
|
||||
const r = this.editDialog.form.coinAndAlgoList[index]
|
||||
let v = String(r.coin || '')
|
||||
v = v.replace(/[\u4e00-\u9fa5]/g, '').replace(/[^A-Za-z0-9]/g, '')
|
||||
this.$set(this.editDialog.form.coinAndAlgoList[index], 'coin', v.toUpperCase())
|
||||
},
|
||||
/** 编辑弹窗:算法输入过滤(仅字母数字和-,转大写) */
|
||||
editHandleAlgorithmInput(index) {
|
||||
const r = this.editDialog.form.coinAndAlgoList[index]
|
||||
let v = String(r.algorithm || '')
|
||||
v = v.replace(/[\u4e00-\u9fa5]/g, '').replace(/[^A-Za-z0-9-]/g, '')
|
||||
this.$set(this.editDialog.form.coinAndAlgoList[index], 'algorithm', v.toUpperCase())
|
||||
},
|
||||
/** 编辑弹窗:理论算力限制(6整数+4小数) */
|
||||
editHandleRowTheoryInput(index) {
|
||||
let v = String(this.editDialog.form.coinAndAlgoList[index].theoryPower ?? '')
|
||||
v = v.replace(/[^0-9.]/g, '')
|
||||
const firstDot = v.indexOf('.')
|
||||
if (firstDot !== -1) {
|
||||
v = v.slice(0, firstDot + 1) + v.slice(firstDot + 1).replace(/\./g, '')
|
||||
}
|
||||
const 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}` : (endsWithDot ? `${intPart}.` : intPart)
|
||||
this.$set(this.editDialog.form.coinAndAlgoList[index], 'theoryPower', v)
|
||||
},
|
||||
/** 编辑弹窗:单位变更 */
|
||||
editHandleRowUnitChange(index, value) {
|
||||
this.$set(this.editDialog.form.coinAndAlgoList[index], 'unit', value)
|
||||
},
|
||||
/** 编辑弹窗:新增一行(最多10行) */
|
||||
editHandleAddRow() {
|
||||
const list = this.editDialog.form.coinAndAlgoList || []
|
||||
if (list.length >= 10) {
|
||||
this.$message.warning('最多添加 10 行')
|
||||
return
|
||||
}
|
||||
const last = list[list.length - 1] || { unit: 'TH/S' }
|
||||
list.push({ coin: '', algorithm: '', theoryPower: '', unit: last.unit || 'TH/S', coinAndPowerId: null })
|
||||
this.$set(this.editDialog.form, 'coinAndAlgoList', list)
|
||||
},
|
||||
/** 编辑弹窗:删除一行(至少保留1行) */
|
||||
editHandleRemoveRow(index) {
|
||||
const list = this.editDialog.form.coinAndAlgoList || []
|
||||
if (list.length <= 1) return
|
||||
list.splice(index, 1)
|
||||
this.$set(this.editDialog.form, 'coinAndAlgoList', list)
|
||||
},
|
||||
/** 行:币种(来自 coinAndAlgoList,去重后用中文逗号拼接;兜底 row.coin) */
|
||||
getRowCoinText(row) {
|
||||
try {
|
||||
const list = Array.isArray(row && row.coinAndAlgoList) ? row.coinAndAlgoList : []
|
||||
if (list.length) {
|
||||
const coins = list.map(i => String(i && i.coin ? i.coin : '').trim()).filter(Boolean)
|
||||
const uniq = Array.from(new Set(coins))
|
||||
if (uniq.length) return uniq.join(',')
|
||||
}
|
||||
const fallback = String(row && row.coin ? row.coin : '').trim()
|
||||
return fallback || '-'
|
||||
} catch (e) {
|
||||
return String(row && row.coin ? row.coin : '').trim() || '-'
|
||||
}
|
||||
},
|
||||
/** 行:算法(来自 coinAndAlgoList,去重后用中文逗号拼接;兜底 row.algorithm) */
|
||||
getRowAlgorithmText(row) {
|
||||
try {
|
||||
const list = Array.isArray(row && row.coinAndAlgoList) ? row.coinAndAlgoList : []
|
||||
if (list.length) {
|
||||
const algos = list.map(i => String(i && i.slogithm ? i.slogithm : (i && i.algorithm ? i.algorithm : '')).trim()).filter(Boolean)
|
||||
const uniq = Array.from(new Set(algos))
|
||||
if (uniq.length) return uniq.join(',')
|
||||
}
|
||||
const fallback = String(row && row.algorithm ? row.algorithm : '').trim()
|
||||
return fallback || '-'
|
||||
} catch (e) {
|
||||
return String(row && row.algorithm ? row.algorithm : '').trim() || '-'
|
||||
}
|
||||
},
|
||||
/** 从首行 priceList 推断表头单位(优先按 selectedPayKey 匹配) */
|
||||
computeUnitFromFirstRow() {
|
||||
try {
|
||||
@@ -655,12 +775,29 @@ export default {
|
||||
return '-'
|
||||
}
|
||||
},
|
||||
/** 展示理论算力(带单位) */
|
||||
/** 展示理论算力(来自 coinAndAlgoList:每项“值 单位”,多项以中文逗号拼接;兜底 row.theoryPower + row.unit) */
|
||||
getTheoryText(row) {
|
||||
const val = row && row.theoryPower != null ? String(row.theoryPower) : ''
|
||||
if (!val) return '-'
|
||||
const unit = (row && row.unit ? String(row.unit) : '').trim().toUpperCase()
|
||||
return unit ? `${val} ${unit}` : val
|
||||
try {
|
||||
const list = Array.isArray(row && row.coinAndAlgoList) ? row.coinAndAlgoList : []
|
||||
if (list.length) {
|
||||
const parts = list.map(i => {
|
||||
const power = (i && i.theoryPower != null) ? String(i.theoryPower) : ''
|
||||
const unit = (i && (i.unit || i.Unit)) ? String(i.unit || i.Unit).trim().toUpperCase() : ''
|
||||
const text = power ? (unit ? `${power} ${unit}` : power) : ''
|
||||
return text
|
||||
}).filter(Boolean)
|
||||
if (parts.length) return parts.join(', ')
|
||||
}
|
||||
const val = row && row.theoryPower != null ? String(row.theoryPower) : ''
|
||||
if (!val) return '-'
|
||||
const unit = (row && row.unit ? String(row.unit) : '').trim().toUpperCase()
|
||||
return unit ? `${val} ${unit}` : val
|
||||
} catch (e) {
|
||||
const val = row && row.theoryPower != null ? String(row.theoryPower) : ''
|
||||
if (!val) return '-'
|
||||
const unit = (row && row.unit ? String(row.unit) : '').trim().toUpperCase()
|
||||
return unit ? `${val} ${unit}` : val
|
||||
}
|
||||
},
|
||||
/** 展示功耗(kw/h) */
|
||||
getPowerDissText(row) {
|
||||
@@ -1014,27 +1151,41 @@ export default {
|
||||
|
||||
/** 编辑 */
|
||||
handleEdit(row) {
|
||||
// coinAndAlgoList:从后端 coinAndAlgoList 转换;若没有则用旧字段构造单行
|
||||
const srcList = Array.isArray(row.coinAndAlgoList) ? row.coinAndAlgoList : []
|
||||
const coinAndAlgoList = srcList.length
|
||||
? srcList.map(it => ({
|
||||
coin: String(it && (it.coin || '')).trim(),
|
||||
algorithm: String(it && (it.slogithm || it.algorithm || '')).trim(),
|
||||
theoryPower: (it && it.theoryPower != null) ? String(it.theoryPower) : '',
|
||||
unit: String(it && (it.unit || '')).trim() || 'TH/S',
|
||||
coinAndPowerId: it && (it.coinAndPowerId != null) ? it.coinAndPowerId : null
|
||||
}))
|
||||
: [{
|
||||
coin: String(row.coin || '').trim(),
|
||||
algorithm: String(row.algorithm || '').trim(),
|
||||
theoryPower: (row && row.theoryPower != null) ? String(row.theoryPower) : '',
|
||||
unit: String(row.unit || 'TH/S').trim(),
|
||||
coinAndPowerId: null
|
||||
}]
|
||||
const form = {
|
||||
id: row.id,
|
||||
name: row.name || '',
|
||||
coin: row.coin || '',
|
||||
algorithm: row.algorithm || '',
|
||||
coinAndAlgoList,
|
||||
maxLeaseDays: row.maxLeaseDays || '',
|
||||
powerDissipation: row.powerDissipation || row.powerDissipation || '',
|
||||
saleNumbers: row.saleNumbers || '',
|
||||
theoryPower: row.theoryPower || '',
|
||||
unit: row.unit || 'GH/S',
|
||||
state: (row && (row.state === 0 || row.state === 1)) ? row.state : 0
|
||||
}
|
||||
// 构建可编辑的价格列表:以支持的支付方式为模板
|
||||
const srcList = Array.isArray(row.priceList) ? row.priceList : []
|
||||
const priceSrc = Array.isArray(row.priceList) ? row.priceList : []
|
||||
const template = (this.payTypes || []).map(pt => ({
|
||||
chain: (pt.payChain || pt.chain || '').toString(),
|
||||
coin: (pt.payCoin || pt.coin || '').toString(),
|
||||
payTypeId: pt.payTypeId || pt.id || 0
|
||||
}))
|
||||
this.editDialog.priceList = template.map(t => {
|
||||
const hit = srcList.find(p =>
|
||||
const hit = priceSrc.find(p =>
|
||||
String(p.chain || p.payChain || '') === t.chain &&
|
||||
String(p.coin || p.payCoin || '') === t.coin
|
||||
)
|
||||
@@ -1062,16 +1213,40 @@ export default {
|
||||
const f = this.editDialog.form
|
||||
// 基础校验
|
||||
if (!String(f.name || '').trim()) { this.$message.warning('矿机型号不能为空'); return }
|
||||
if (!String(f.coin || '').trim()) { this.$message.warning('币种不能为空'); return }
|
||||
if (!String(f.algorithm || '').trim()) { this.$message.warning('算法不能为空'); return }
|
||||
// 校验 coinAndAlgoList
|
||||
const list = Array.isArray(f.coinAndAlgoList) ? f.coinAndAlgoList : []
|
||||
if (!list.length) { this.$message.warning('请至少添加一行币种/算法/算力/单位'); return }
|
||||
const coinPattern = /^[A-Za-z0-9]{1,10}$/
|
||||
const algoPattern = /^[A-Za-z0-9-]{2,20}$/
|
||||
const powerPattern = /^\d{1,6}(\.\d{1,4})?$/
|
||||
for (let i = 0; i < list.length; i += 1) {
|
||||
const r = list[i] || {}
|
||||
const coin = String(r.coin || '').trim()
|
||||
const algo = String(r.algorithm || '').trim()
|
||||
const power = String(r.theoryPower || '').trim()
|
||||
const unit = String(r.unit || '').trim()
|
||||
if (!coin) { this.$message.warning(`第 ${i + 1} 行:请输入币种`); return }
|
||||
if (!coinPattern.test(coin)) { this.$message.warning(`第 ${i + 1} 行:币种仅允许字母或数字,1-10 位`); return }
|
||||
if (!algo) { this.$message.warning(`第 ${i + 1} 行:请输入算法`); return }
|
||||
if (!algoPattern.test(algo)) { this.$message.warning(`第 ${i + 1} 行:算法仅允许字母/数字/“-”,2-20 位`); return }
|
||||
if (!power || !powerPattern.test(power) || Number(power) <= 0) {
|
||||
this.$message.warning(`第 ${i + 1} 行:理论算力需大于0,整数最多6位,小数最多4位`); return
|
||||
}
|
||||
if (!unit) { this.$message.warning(`第 ${i + 1} 行:请选择算力单位`); return }
|
||||
}
|
||||
const days = parseInt(String(f.maxLeaseDays || '').replace(/[^\d]/g, ''), 10)
|
||||
if (!(Number.isInteger(days) && days >= 1 && days <= 365)) { this.$message.warning('最大租赁天数需为1-365的整数'); return }
|
||||
const sale = parseInt(String(f.saleNumbers || '').replace(/[^\d]/g, ''), 10)
|
||||
if (!Number.isInteger(sale) || sale < 0) { this.$message.warning('出售数量应为非负整数'); return }
|
||||
const payload = {
|
||||
const payload = {
|
||||
id: f.id,
|
||||
algorithm: String(f.algorithm || '').trim(),
|
||||
coin: String(f.coin || '').trim(),
|
||||
coinAndAlgoList: (f.coinAndAlgoList || []).map(it => ({
|
||||
coin: String(it.coin || '').trim().toUpperCase(),
|
||||
algorithm: String(it.algorithm || '').trim().toUpperCase(),
|
||||
theoryPower: Number(it.theoryPower) || 0,
|
||||
unit: it.unit,
|
||||
coinAndPowerId: it.coinAndPowerId || null
|
||||
})),
|
||||
maxLeaseDays: days,
|
||||
name: String(f.name || '').trim(),
|
||||
powerDissipation: Number(String(f.powerDissipation || '0').replace(/[^\d.]/g, '')) || 0,
|
||||
@@ -1083,8 +1258,6 @@ export default {
|
||||
productMachineId: p.productMachineId || f.id || 0
|
||||
})),
|
||||
saleNumbers: sale,
|
||||
theoryPower: Number(String(f.theoryPower || '0').replace(/[^\d.]/g, '')) || 0,
|
||||
unit: String(f.unit || '').trim() || 'GH/S',
|
||||
state: (f && (f.credentials ? f.state : f.state)) ?? 0
|
||||
}
|
||||
// 价格至少有一个填写
|
||||
@@ -1247,6 +1420,13 @@ export default {
|
||||
padding-left: 12px; /* 对齐到上方 el-input 的内边距视觉效果 */
|
||||
}
|
||||
|
||||
/* 编辑弹窗:左侧标签不换行(长文本保持单行,可溢出隐藏省略) */
|
||||
.edit-form :deep(.el-form-item__label) {
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
::v-deep .el-form-item__content{
|
||||
text-align: left;
|
||||
}
|
||||
@@ -1294,6 +1474,24 @@ export default {
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
/* 表格单元格文本溢出省略,hover 显示完整(依赖列的 show-overflow-tooltip) */
|
||||
.ellipsis-cell {
|
||||
display: block;
|
||||
width: 100%;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
/* 编辑弹窗:币种/算法/算力/单位 多行布局 */
|
||||
.coin-algo-rows { display: grid; gap: 8px; width: 100%; }
|
||||
.coin-algo-line { display: flex; align-items: center; gap: 8px; }
|
||||
.coin-algo-line .coin-input { width: 18%; min-width: 140px; }
|
||||
.coin-algo-line .algo-input { width: 24%; min-width: 160px; }
|
||||
.coin-algo-line .power-input { width: 20%; min-width: 140px; }
|
||||
.coin-algo-line .unit-select { width: 16%; min-width: 120px; }
|
||||
.coin-algo-line .op-btn { flex: 0 0 auto; }
|
||||
|
||||
::v-deep .el-input__prefix, .el-input__suffix{
|
||||
top:24%;
|
||||
}
|
||||
|
||||
201
power_leasing/src/views/account/withdrawRecord.vue
Normal file
201
power_leasing/src/views/account/withdrawRecord.vue
Normal file
@@ -0,0 +1,201 @@
|
||||
<template>
|
||||
<div class="receipt-page">
|
||||
<div class="card" aria-label="提现记录" tabindex="0">
|
||||
<div class="card-header">
|
||||
<h3 class="card-title">提现记录</h3>
|
||||
</div>
|
||||
|
||||
<div v-if="loading" class="loading">
|
||||
<i class="el-icon-loading" aria-label="加载中" role="img"></i>
|
||||
加载中...
|
||||
</div>
|
||||
|
||||
<div v-else>
|
||||
<div class="table-wrap">
|
||||
<el-table
|
||||
:data="rows"
|
||||
border
|
||||
stripe
|
||||
size="small"
|
||||
class="withdraw-table"
|
||||
:header-cell-style="{ textAlign: 'left' }"
|
||||
:cell-style="{ textAlign: 'left' }"
|
||||
>
|
||||
<el-table-column label="申请时间" width="140">
|
||||
<template #default="scope">{{ formatFullTime(scope.row.createTime) }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="提现金额" width="70" show-overflow-tooltip align="right">
|
||||
<template #default="scope">
|
||||
<span class="amount-red">
|
||||
<el-tooltip
|
||||
v-if="formatAmount(scope.row.amount, scope.row.coin || scope.row.toSymbol || 'USDT').truncated"
|
||||
:content="`-${formatAmount(scope.row.amount, scope.row.coin || scope.row.toSymbol || 'USDT').full}`"
|
||||
placement="top"
|
||||
>
|
||||
<span>
|
||||
-{{ formatAmount(scope.row.amount, 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.amount, scope.row.coin || scope.row.toSymbol || 'USDT').text }}
|
||||
</span>
|
||||
</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="手续费" width="70" show-overflow-tooltip align="right">
|
||||
<template #default="scope">
|
||||
<span class="mono">{{ formatAmount(scope.row.serviceCharge, scope.row.coin || scope.row.toSymbol || 'USDT').text }}</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="提现链" width="100" show-overflow-tooltip>
|
||||
<template #default="scope">{{ formatChain(scope.row.toChain || scope.row.chain) }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="币种" width="80" show-overflow-tooltip>
|
||||
<template #default="scope">{{ String(scope.row.coin || scope.row.toSymbol || '').toUpperCase() }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="收款地址" min-width="320" show-overflow-tooltip>
|
||||
<template #default="scope">
|
||||
<el-tooltip :content="scope.row.toAddress" placement="top">
|
||||
<span class="mono-ellipsis">{{ scope.row.toAddress }}</span>
|
||||
</el-tooltip>
|
||||
<el-button type="text" size="mini" @click.stop="copy(scope.row.toAddress)">复制</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="交易HASH" width="300" show-overflow-tooltip>
|
||||
<template #default="scope">
|
||||
<el-tooltip :content="scope.row.txHash" placement="top">
|
||||
<span class="mono-ellipsis">{{ scope.row.txHash }}</span>
|
||||
</el-tooltip>
|
||||
<el-button type="text" size="mini" @click.stop="copy(scope.row.txHash)" v-if="scope.row.txHash">复制</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="状态" width="90">
|
||||
<template #default="scope">
|
||||
<el-tag :type="getStatusType(scope.row.status)" size="small">{{ getStatusText(scope.row.status) }}</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="状态更新时间" width="140">
|
||||
<template #default="scope">{{ formatFullTime(scope.row.updateTime) }}</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
</div>
|
||||
|
||||
<div v-if="!rows.length" class="empty">
|
||||
<div class="empty-icon">🏧</div>
|
||||
<div class="empty-text">暂无提现记录</div>
|
||||
</div>
|
||||
|
||||
<div class="pagination">
|
||||
<el-pagination
|
||||
background
|
||||
layout="prev, pager, next, jumper"
|
||||
:current-page.sync="pageNum"
|
||||
:page-size="pageSize"
|
||||
:total="total"
|
||||
@current-change="fetchList"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { balanceWithdrawListV2 } from '../../api/wallet'
|
||||
import { truncateAmountByCoin } from '../../utils/amount'
|
||||
|
||||
export default {
|
||||
name: 'AccountWithdrawRecord',
|
||||
data() {
|
||||
return {
|
||||
loading: false,
|
||||
rows: [],
|
||||
pageNum: 1,
|
||||
pageSize: 20,
|
||||
total: 0
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
this.fetchList()
|
||||
},
|
||||
methods: {
|
||||
formatAmount(value, coin) {
|
||||
return truncateAmountByCoin(value, coin)
|
||||
},
|
||||
formatFullTime(time) {
|
||||
if (!time) return ''
|
||||
try {
|
||||
return `${time.split('T')[0]} ${time.split('T')[1].split('.')[0]}`
|
||||
} catch (e) {
|
||||
return time
|
||||
}
|
||||
},
|
||||
formatChain(chain) {
|
||||
const map = { tron: 'Tron (TRC20)', ethereum: 'Ethereum (ERC20)', bsc: 'BSC (BEP20)', polygon: 'Polygon', ETH: 'ETH', TRON: 'TRON' }
|
||||
const k = typeof chain === 'string' ? chain.toLowerCase() : chain
|
||||
return map[k] || chain || '-'
|
||||
},
|
||||
getStatusType(status) {
|
||||
const map = { 0: 'danger', 1: 'success', 2: 'warning', 3: 'danger' }
|
||||
return map[status] || 'info'
|
||||
},
|
||||
getStatusText(status) {
|
||||
const map = { 0: '失败', 1: '成功', 2: '处理中', 3: '校验失败' }
|
||||
return map[status] || '未知'
|
||||
},
|
||||
copy(text) {
|
||||
if (!text) return
|
||||
try {
|
||||
if (navigator.clipboard && navigator.clipboard.writeText) {
|
||||
navigator.clipboard.writeText(text)
|
||||
this.$message.success('已复制')
|
||||
return
|
||||
}
|
||||
} catch (e) {}
|
||||
const area = document.createElement('textarea')
|
||||
area.value = text
|
||||
document.body.appendChild(area)
|
||||
area.select()
|
||||
try { document.execCommand('copy'); this.$message.success('已复制') } catch (e) {}
|
||||
document.body.removeChild(area)
|
||||
},
|
||||
async fetchList() {
|
||||
this.loading = true
|
||||
try {
|
||||
const res = await balanceWithdrawListV2({ pageNum: this.pageNum, pageSize: this.pageSize })
|
||||
const data = res && (res.data || res)
|
||||
const list = Array.isArray(data && data.rows) ? data.rows : (Array.isArray(data) ? data : [])
|
||||
this.rows = list
|
||||
const total = Number(data && (data.total != null ? data.total : res.total))
|
||||
this.total = Number.isFinite(total) ? total : 0
|
||||
} catch (e) {
|
||||
this.rows = []
|
||||
this.total = 0
|
||||
} finally {
|
||||
this.loading = false
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.receipt-page { margin: 0; box-sizing: border-box; overflow-x: hidden; max-width: 100%; }
|
||||
.card { background: #fff; border: 1px solid #eee; border-radius: 10px; padding: 12px; box-shadow: 0 4px 18px rgba(0,0,0,0.04); overflow-x: hidden; }
|
||||
:deep(.withdraw-table) { width: 100%; }
|
||||
:deep(.withdraw-table .el-table__header-wrapper table),
|
||||
:deep(.withdraw-table .el-table__body-wrapper table) { table-layout: fixed; width: 100%; }
|
||||
.table-wrap { width: 100%; overflow-x: hidden; }
|
||||
:deep(.withdraw-table .cell) { white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
|
||||
.card-header { display: flex; align-items: center; justify-content: space-between; margin-bottom: 8px; }
|
||||
.card-title { margin: 0; font-size: 18px; font-weight: 700; color: #2c3e50; }
|
||||
.loading { text-align: center; color: #666; padding: 40px 0; }
|
||||
.empty { text-align: center; color: #999; padding: 40px 0; }
|
||||
.empty-icon { font-size: 48px; margin-bottom: 8px; }
|
||||
.amount-more { font-size: 12px; color: #94a3b8; margin-left: 4px; }
|
||||
.amount-red { color: #ef4444; font-weight: 700; }
|
||||
.mono-ellipsis { font-family: "Monaco", "Menlo", monospace; max-width: 360px; display: inline-block; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; vertical-align: middle; }
|
||||
.pagination { display: flex; justify-content: flex-end; margin-top: 8px; }
|
||||
</style>
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -2,6 +2,7 @@
|
||||
import { getProductById } from '../../utils/productService'
|
||||
import { getMachineInfo, getPayTypes,getShopMachineList,addGoodsV2 } from '../../api/products'
|
||||
import { truncateAmountByCoin, truncateTo6 } from '../../utils/amount'
|
||||
import { getGoodsListV2 } from '../../api/shoppingCart'
|
||||
|
||||
export default {
|
||||
name: 'ProductDetail',
|
||||
@@ -162,218 +163,7 @@ export default {
|
||||
this.machineType = savedType
|
||||
}
|
||||
}catch(e){/* noop */}
|
||||
let arr={
|
||||
"meta": {
|
||||
"pageNum": 1, // 当前页码(后端分页用,可选)
|
||||
"pageSize": 10, // 每页条数(后端分页用,可选)
|
||||
"total": 123 // 总条数(后端分页用,可选)
|
||||
},
|
||||
"columns": [
|
||||
{
|
||||
"key": "model", // 列字段名:与 rows 中同名字段映射
|
||||
"label": "型号", // 表头显示文本
|
||||
"type": "text", // 列类型:text/amount/hashrate/days
|
||||
"fixed": "left", // 是否左固定列:left/right/不传
|
||||
"width": 100 // 列宽(可选)
|
||||
},
|
||||
{
|
||||
"key": "price", // 价格列字段名
|
||||
"label": "价格", // 表头显示文本
|
||||
"type": "amount", // 金额类型:仅渲染数值;单位来自行级 priceList.coin
|
||||
"width": 100 // 列宽(可选)
|
||||
},
|
||||
|
||||
// 动态币种/算法算力列(示例:仅第一列带注释,其余同结构)
|
||||
{
|
||||
"key": "XTM", // 币种/算法代码作为列 key
|
||||
"label": "XTM", // 表头显示名
|
||||
"type": "hashrate", // 列类型:算力
|
||||
"unit": "MH/s", // 单元格单位(表头不显示单位)
|
||||
"icon": "https://cdn.xxx/coin/xtm.png", // 表头图标(可选)
|
||||
|
||||
},
|
||||
{
|
||||
"key": "NXNA",
|
||||
"label": "NXNA",
|
||||
"type": "hashrate",
|
||||
"unit": "MH/s",
|
||||
"icon": "https://cdn.xxx/coin/nxna.png"
|
||||
},
|
||||
{
|
||||
"key": "CLORE",
|
||||
"label": "CLORE",
|
||||
"type": "hashrate",
|
||||
"unit": "MH/s",
|
||||
"icon": "https://cdn.xxx/coin/clore.png"
|
||||
},
|
||||
{
|
||||
"key": "CFX",
|
||||
"label": "CFX",
|
||||
"type": "hashrate",
|
||||
"unit": "MH/s",
|
||||
"icon": "https://cdn.xxx/coin/cfx.png"
|
||||
},
|
||||
{
|
||||
"key": "IRON",
|
||||
"label": "IRON",
|
||||
"type": "hashrate",
|
||||
"unit": "MH/s",
|
||||
"icon": "https://cdn.xxx/coin/iron.png"
|
||||
},
|
||||
{
|
||||
"key": "NEXA",
|
||||
"label": "NEXA",
|
||||
"type": "hashrate",
|
||||
"unit": "MH/s",
|
||||
"icon": "https://cdn.xxx/coin/nexa.png"
|
||||
},
|
||||
{
|
||||
"key": "KLS",
|
||||
"label": "KLS",
|
||||
"type": "hashrate",
|
||||
"unit": "MH/s",
|
||||
"icon": "https://cdn.xxx/coin/kls.png"
|
||||
},
|
||||
{
|
||||
"key": "RVN",
|
||||
"label": "RVN",
|
||||
"type": "hashrate",
|
||||
"unit": "MH/s",
|
||||
"icon": "https://cdn.xxx/coin/rvn.png"
|
||||
},
|
||||
{
|
||||
"key": "ERG",
|
||||
"label": "ERG",
|
||||
"type": "hashrate",
|
||||
"unit": "MH/s",
|
||||
"icon": "https://cdn.xxx/coin/erg.png"
|
||||
},
|
||||
{
|
||||
"key": "XEL",
|
||||
"label": "XEL",
|
||||
"type": "hashrate",
|
||||
"unit": "kH/s",
|
||||
"icon": "https://cdn.xxx/coin/xel.png"
|
||||
},
|
||||
|
||||
// 尾部汇总列
|
||||
{
|
||||
"key": "monthIncome",
|
||||
"label": "最大月收益",
|
||||
"type": "amount",
|
||||
"currency": "USDT",//固定显示单位
|
||||
"period": "month",
|
||||
"width": 110
|
||||
},
|
||||
{
|
||||
"key": "maxLeaseDays",
|
||||
"label": "最大租赁天数",
|
||||
"type": "days",
|
||||
"width": 80
|
||||
}
|
||||
],
|
||||
"rows": [
|
||||
{
|
||||
"id": "gpu_100_hbm3", // 唯一标识(加入购物车传 id)
|
||||
"model": "H100 80GB HBM3", // 型号,对应 columns.key = model
|
||||
"price": 142160.54, // 行价格基准值(当无 priceList 时展示)
|
||||
"priceList": [ // 价格列表:随支付方式变更价格与单位
|
||||
{
|
||||
"chain": "TRON", // 支付链
|
||||
"coin": "USDT", // 币种(价格单位来源)
|
||||
"price": 142160.54 // 该支付方式下的单价
|
||||
},
|
||||
{ "chain": "TRON", "coin": "NEXA", "price": 142050.00 },
|
||||
{ "chain": "ETH", "coin": "USDT", "price": 142100.00 },
|
||||
{ "chain": "ETH", "coin": "ETH", "price": 142000.00 }
|
||||
],
|
||||
"saleNumbers": 120, // 总机器数(仅 ASIC 展示,后端只读返回)
|
||||
"saleOutNumbers": 18, // 已售数量(仅 ASIC 展示,后端只读返回)
|
||||
|
||||
"XTM": 255.0, // 对应列 key 的算力数值(可为 null)
|
||||
"NXNA": 255.0,
|
||||
"CLORE": 343.0,
|
||||
"CFX": null,
|
||||
"IRON": null,
|
||||
"NEXA": null,
|
||||
"KLS": null,
|
||||
"RVN": 255.0,
|
||||
"ERG": 900.0,
|
||||
"XEL": null,
|
||||
|
||||
"monthIncome": 425.01, // 最大月收益(列定义 currency=USDT,固定以 USDT 展示)
|
||||
"maxLeaseDays": 10368 // 最大租赁天数(单位:天)
|
||||
},
|
||||
{
|
||||
"id": "gpu_rtx_5090",
|
||||
"model": "RTX 5090",
|
||||
"price": 14216.05,
|
||||
"priceList": [
|
||||
{ "chain": "TRON", "coin": "USDT", "price": 14216.05 },
|
||||
{ "chain": "TRON", "coin": "NEXA", "price": 14199.00 },
|
||||
{ "chain": "ETH", "coin": "USDT", "price": 14180.00 },
|
||||
{ "chain": "ETH", "coin": "ETH", "price": 14150.00 }
|
||||
],
|
||||
"saleNumbers": 80,
|
||||
"saleOutNumbers": 22,
|
||||
|
||||
"XTM": 28.7,
|
||||
"NXNA": 100.5,
|
||||
"CLORE": 100.5,
|
||||
"CFX": 210.0,
|
||||
"IRON": 133.5,
|
||||
"NEXA": 450.0,
|
||||
"KLS": 133.5,
|
||||
"RVN": 100.5,
|
||||
"ERG": 575.0,
|
||||
"XEL": 120.0,
|
||||
|
||||
"monthIncome": 267.84,
|
||||
"maxLeaseDays": 1645
|
||||
},
|
||||
{
|
||||
"id": "gpu_rtx_4090",
|
||||
"model": "RTX 4090",
|
||||
"price": 12083.65,
|
||||
"priceList": [
|
||||
{ "chain": "TRON", "coin": "USDT", "price": 12083.65 },
|
||||
{ "chain": "TRON", "coin": "NEXA", "price": 12050.00 },
|
||||
{ "chain": "ETH", "coin": "USDT", "price": 12020.00 },
|
||||
{ "chain": "ETH", "coin": "ETH", "price": 11999.00 }
|
||||
],
|
||||
"saleNumbers": 64,
|
||||
"saleOutNumbers": 9,
|
||||
|
||||
"XTM": 16.6,
|
||||
"NXNA": 65.0,
|
||||
"CLORE": 65.0,
|
||||
"CFX": 130.0,
|
||||
"IRON": 82.5,
|
||||
"NEXA": 320.0,
|
||||
"KLS": 82.5,
|
||||
"RVN": 65.0,
|
||||
"ERG": 265.0,
|
||||
"XEL": 40.6,
|
||||
|
||||
"monthIncome": 155.00,
|
||||
"maxLeaseDays": 2418
|
||||
}
|
||||
]
|
||||
}
|
||||
// 模拟数据写入动态表格(仅演示)
|
||||
try{
|
||||
this.dynamicMeta = arr.meta || {}
|
||||
this.dynamicColumns = Array.isArray(arr.columns) ? arr.columns : []
|
||||
this.dynamicRows = (Array.isArray(arr.rows) ? arr.rows : []).map(r => ({
|
||||
saleNumbers: 0,
|
||||
saleOutNumbers: 0,
|
||||
leaseTime: 1,
|
||||
purchaseQuantity: 0,
|
||||
...r
|
||||
}))
|
||||
// 根据价格列表设置默认支付方式,确保筛选框与价格展示一致
|
||||
this.ensureDefaultPayFilterFromPrices()
|
||||
}catch(e){/* noop */}
|
||||
// 不再使用本地模拟数据,动态表格完全依赖后端返回的 columns/rows
|
||||
// 仅当路由携带 shopId 时,才发起店铺商品请求
|
||||
const routeShopId =
|
||||
(this.$route && this.$route.params && (this.$route.params.shopId || this.$route.params.id)) ||
|
||||
@@ -569,7 +359,17 @@ export default {
|
||||
},
|
||||
// 切换矿机种类:0-ASIC,1-GPU
|
||||
handleMachineTypeChange(){
|
||||
// 变更类型后,重新请求数据
|
||||
// 切换前清空所有已勾选状态与确认弹窗
|
||||
try {
|
||||
if (Array.isArray(this.dynamicRows)) {
|
||||
this.dynamicRows.forEach(r => { if (r) this.$set(r, '_selected', false) })
|
||||
}
|
||||
if (this.confirmAddDialog) {
|
||||
this.confirmAddDialog.items = []
|
||||
this.confirmAddDialog.visible = false
|
||||
}
|
||||
} catch (e) { /* noop */ }
|
||||
// 变更类型后,重新请求数据与支付方式
|
||||
this.fetchGetMachineInfo(this.buildQueryParams())
|
||||
this.fetchPayTypes()
|
||||
// 本地记住用户选择
|
||||
@@ -690,19 +490,32 @@ export default {
|
||||
this.productDetailLoading = true
|
||||
// 改为使用店铺机器列表接口
|
||||
const res = await getShopMachineList(params)
|
||||
console.log(res)
|
||||
if (res && res.code === 200) {
|
||||
console.log(res.data, 'res.rows');
|
||||
this.total = res.total||0;
|
||||
if (res && (res.code === 200 || res.code === 0)) {
|
||||
const root = (res && res.data) ? res.data : res
|
||||
const columns = Array.isArray(root.columns) ? root.columns : (Array.isArray(res.columns) ? res.columns : [])
|
||||
const rows = Array.isArray(root.rows) ? root.rows : (Array.isArray(res.rows) ? res.rows : [])
|
||||
const total = Number(root.total != null ? root.total : (res.total != null ? res.total : 0))
|
||||
this.total = Number.isFinite(total) ? total : 0
|
||||
// 动态表格:列与行
|
||||
this.dynamicColumns = columns
|
||||
this.dynamicRows = rows.map(r => ({
|
||||
saleNumbers: 0,
|
||||
saleOutNumbers: 0,
|
||||
leaseTime: 1,
|
||||
purchaseQuantity: 1,
|
||||
_selected: false,
|
||||
...r
|
||||
}))
|
||||
// 根据 rows 的 priceList 设置默认支付方式
|
||||
this.ensureDefaultPayFilterFromPrices()
|
||||
// 若后端同步返回支付方式,刷新本地支付方式
|
||||
try {
|
||||
const payList = res && res.data && res.data.payConfigList
|
||||
const payList = root && root.payConfigList
|
||||
if (Array.isArray(payList) && payList.length) {
|
||||
this.paymentMethodList = payList
|
||||
this.ensureDefaultPayFilterSelection()
|
||||
}
|
||||
} catch (e) { /* noop */ }
|
||||
// 动态表格数据(如后端有对应 rows/columns,可在此接入)
|
||||
// 此处保留现有动态表格的模拟/替换逻辑,不再维护旧表格数据结构
|
||||
}
|
||||
|
||||
this.productDetailLoading = false
|
||||
@@ -742,7 +555,7 @@ export default {
|
||||
},
|
||||
//查询购物车列表
|
||||
async fetchGetGoodsList(params) {
|
||||
const res = await getGoodsList(params)
|
||||
const res = await getGoodsListV2(params)
|
||||
// 统计当前商品在购物车中已有的机器ID,用于禁用和默认勾选
|
||||
try {
|
||||
const productId = this.params && this.params.id ? Number(this.params.id) : Number(this.$route.params.id)
|
||||
@@ -862,6 +675,16 @@ export default {
|
||||
this.$set(row, '_selected', false)
|
||||
return
|
||||
}
|
||||
// 无价格:不可选择
|
||||
try {
|
||||
const hasPrice = (Array.isArray(row && row.priceList) && row.priceList.some(it => it && it.price !== null && it.price !== undefined))
|
||||
|| (row && row.price !== null && row.price !== undefined && row.price !== '')
|
||||
if (!hasPrice) {
|
||||
this.$message.warning('该机器暂无价格,无法选择')
|
||||
this.$set(row, '_selected', false)
|
||||
return
|
||||
}
|
||||
} catch (e) { /* noop */ }
|
||||
const key = parentRow.id
|
||||
const list = (this.selectedMap[key] && [...this.selectedMap[key]]) || []
|
||||
const idx = list.findIndex(it => it && it.id === row.id)
|
||||
@@ -968,7 +791,7 @@ export default {
|
||||
this.confirmAddDialog.items = picked.map(r => ({
|
||||
...r,
|
||||
leaseTime: Number(r.leaseTime || 1),
|
||||
purchaseQuantity: Number(r.purchaseQuantity || 0)
|
||||
purchaseQuantity: Number(r.purchaseQuantity || 1)
|
||||
}))
|
||||
this.confirmAddDialog.visible = true
|
||||
},
|
||||
@@ -992,11 +815,15 @@ export default {
|
||||
return obj
|
||||
})
|
||||
const res = await addGoodsV2(payload)
|
||||
if (!res || !(res.code === 0 || res.code === 200)) {
|
||||
this.$message.error('部分商品加入购物车失败,请重试')
|
||||
} else {
|
||||
this.$message.success(`已加入 ${items.length} 台矿机到购物车`)
|
||||
if (res && (res.code === 0 || res.code === 200)) {
|
||||
this.$message({
|
||||
message: `已加入 ${items.length} 台矿机到购物车`,
|
||||
type: 'success',
|
||||
duration: 3000,
|
||||
showClose: true
|
||||
})
|
||||
}
|
||||
|
||||
this.confirmAddDialog.visible = false
|
||||
// 清空勾选
|
||||
try {
|
||||
|
||||
@@ -116,7 +116,8 @@
|
||||
<template #default="{ row }">
|
||||
<el-checkbox
|
||||
v-model="row._selected"
|
||||
:title="'选择该矿机'"
|
||||
:title="isRowDisabled(row) ? (row && (row.saleState === 1 || row.saleState === 2) ? '该机器已售出或售出中,无法选择' : '该机器暂无价格,无法选择') : '选择该矿机'"
|
||||
:disabled="isRowDisabled(row)"
|
||||
@change="checked => handleManualSelectFlat(row, checked)"
|
||||
/>
|
||||
</template>
|
||||
@@ -143,7 +144,7 @@
|
||||
</div>
|
||||
</template>
|
||||
<template #default="{ row }">
|
||||
<span class="num-strong">
|
||||
<span :class="getCellClass(col)">
|
||||
<el-tooltip
|
||||
v-if="formatDynamicCell(row, col).truncated"
|
||||
:content="formatDynamicCell(row, col).full"
|
||||
@@ -170,13 +171,40 @@
|
||||
<!-- 租赁天数:始终显示,用户手动填写 -->
|
||||
<el-table-column prop="leaseTime" label="租赁天数(天)">
|
||||
<template #default="scope">
|
||||
<el-input-number class="input-full" v-model="scope.row.leaseTime" :min="1" :precision="0" :step="1" :controls="false" size="mini" />
|
||||
<el-input-number
|
||||
class="input-full"
|
||||
v-model="scope.row.leaseTime"
|
||||
:min="1"
|
||||
:max="getRowMaxLeaseDays(scope.row)"
|
||||
:precision="0"
|
||||
:step="1"
|
||||
:controls="false"
|
||||
size="mini"
|
||||
@change="val => handleLeaseDaysChange(scope.row, val)"
|
||||
/>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<!-- ASIC 专用:购买数量,用户输入 -->
|
||||
<el-table-column v-if="machineType === 0" prop="purchaseQuantity" label="购买数量">
|
||||
<template #default="scope">
|
||||
<el-input-number class="input-full" v-model="scope.row.purchaseQuantity" :min="0" :precision="0" :step="1" :controls="false" size="mini" />
|
||||
<el-input-number
|
||||
class="input-full"
|
||||
v-model="scope.row.purchaseQuantity"
|
||||
:min="1"
|
||||
:max="getRowMaxPurchase(scope.row)"
|
||||
:precision="0"
|
||||
:step="1"
|
||||
:controls="false"
|
||||
size="mini"
|
||||
:disabled="getRowMaxPurchase(scope.row) <= 0"
|
||||
@change="val => handlePurchaseQuantityChange(scope.row, val)"
|
||||
/>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<!-- 总价:ASIC=价格*天数*购买数量;GPU=价格*天数 -->
|
||||
<el-table-column prop="totalAmount" label="总价" header-align="left" align="left">
|
||||
<template #default="scope">
|
||||
<span class="price-strong">{{ formatConfirmTotalText(scope.row) }}</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
@@ -187,7 +215,7 @@
|
||||
:visible.sync="dynamicSearch.visible"
|
||||
width="420px"
|
||||
>
|
||||
<div style="display:flex;gap:10px;align-items:center;">
|
||||
<div class="dynamic-search-bar" style="display:flex;gap:10px;align-items:center;">
|
||||
<el-input
|
||||
v-model="dynamicSearch.keyword"
|
||||
placeholder="输入币种代码或算法关键词"
|
||||
@@ -196,10 +224,6 @@
|
||||
/>
|
||||
<el-button type="primary" @click="handleConfirmDynamicSearch">搜索</el-button>
|
||||
</div>
|
||||
<template #footer>
|
||||
<el-button @click="dynamicSearch.visible=false">取消</el-button>
|
||||
<el-button type="primary" @click="handleConfirmDynamicSearch">确定</el-button>
|
||||
</template>
|
||||
</el-dialog>
|
||||
</section>
|
||||
|
||||
@@ -209,7 +233,7 @@
|
||||
</div>
|
||||
|
||||
<!-- 确认加入购物车弹窗 -->
|
||||
<el-dialog :visible.sync="confirmAddDialog.visible" width="50vw" :title="`确认加入购物车(共 ${confirmAddDialog.items.length} 台)`">
|
||||
<el-dialog :visible.sync="confirmAddDialog.visible" width="70vw" :title="`确认加入购物车(共 ${confirmAddDialog.items.length} 台)`">
|
||||
<div>
|
||||
<el-table :data="confirmAddDialog.items" height="360" border stripe :header-cell-style="{ textAlign: 'left' }" :cell-style="{ textAlign: 'left' }">
|
||||
<el-table-column prop="model" label="型号" header-align="left" align="left" />
|
||||
@@ -233,6 +257,11 @@
|
||||
<el-table-column v-if="machineType === 0" prop="purchaseQuantity" label="购买数量" header-align="left" align="left">
|
||||
<template #default="scope">{{ Number(scope.row.purchaseQuantity || 0) }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="totalAmount" label="总价" header-align="left" align="left">
|
||||
<template #default="scope">
|
||||
<span class="price-strong">{{ formatConfirmTotalText(scope.row) }}</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
</div>
|
||||
<template #footer>
|
||||
@@ -273,6 +302,157 @@ export default {
|
||||
name: 'ProductDetail',
|
||||
mixins: [Index],
|
||||
methods: {
|
||||
/**
|
||||
* 判断列是否为价格/金额相关列,以控制样式为红色
|
||||
* - 优先依据 col.type === 'amount'
|
||||
* - 其次依据 key 命名包含 'price' / 'amount'
|
||||
* - 再次依据 label 文案包含 '价' / '金额'
|
||||
* @param {Object} col
|
||||
* @returns {string} CSS 类名
|
||||
*/
|
||||
getCellClass(col) {
|
||||
try {
|
||||
if (!col) return 'num-strong'
|
||||
if (String(col.type).toLowerCase() === 'amount') return 'price-strong'
|
||||
const key = String(col.key || '').toLowerCase()
|
||||
if (key.includes('price') || key.includes('amount')) return 'price-strong'
|
||||
const label = String(col.label || '')
|
||||
if (label.includes('价') || label.includes('金额')) return 'price-strong'
|
||||
return 'num-strong'
|
||||
} catch (e) {
|
||||
return 'num-strong'
|
||||
}
|
||||
},
|
||||
/**
|
||||
* 纯字符串方式相乘,避免浮点误差
|
||||
* @param {Array<string|number>} values
|
||||
* @returns {string} 不带单位的结果字符串,精确还原小数位
|
||||
*/
|
||||
multiplyAsDecimal(values) {
|
||||
const toIntScale = (n) => {
|
||||
const s = String(n || 0).trim()
|
||||
if (!s.includes('.')) return { int: BigInt(s || '0'), scale: 0 }
|
||||
const [w, f] = s.split('.')
|
||||
const frac = (f || '').replace(/[^0-9]/g, '')
|
||||
const whole = (w || '0').replace(/[^0-9-]/g, '')
|
||||
const sign = whole.startsWith('-') ? '-' : ''
|
||||
const intStr = (sign ? whole.slice(1) : whole) + frac
|
||||
const safe = intStr.replace(/^0+(?=\d)/, '') || '0'
|
||||
return { int: BigInt(sign + safe), scale: frac.length }
|
||||
}
|
||||
let acc = 1n
|
||||
let scale = 0
|
||||
for (const v of values) {
|
||||
const { int, scale: sc } = toIntScale(v)
|
||||
acc = acc * int
|
||||
scale += sc
|
||||
}
|
||||
const neg = acc < 0n
|
||||
const absStr = (neg ? (-acc) : acc).toString()
|
||||
if (scale === 0) return (neg ? '-' : '') + absStr
|
||||
const pad = scale - absStr.length
|
||||
const s = pad > 0 ? ('0'.repeat(pad) + absStr) : absStr
|
||||
const i = s.length - scale
|
||||
let out = s.slice(0, i) + '.' + s.slice(i)
|
||||
// 去除多余的前导/尾随零
|
||||
out = out.replace(/^(-?)0+(?=\d)/, '$1')
|
||||
out = out.replace(/\.?0+$/, '')
|
||||
if (out.startsWith('.')) out = '0' + out
|
||||
if (out === '' || out === '-') out = '0'
|
||||
return (neg ? '-' : '') + out
|
||||
},
|
||||
/**
|
||||
* 截断到最多6位小数(不四舍五入)
|
||||
* @param {string} s
|
||||
* @param {number} maxDecimals
|
||||
* @returns {string}
|
||||
*/
|
||||
truncateDecimalString(s, maxDecimals = 6) {
|
||||
const str = String(s || '0')
|
||||
if (!str.includes('.')) return str
|
||||
const [w, f] = str.split('.')
|
||||
if (f.length <= maxDecimals) return str
|
||||
return `${w}.${f.slice(0, maxDecimals)}`
|
||||
},
|
||||
/**
|
||||
* 确认弹窗“总价”展示(附币种)
|
||||
* ASIC: price*leaseTime*purchaseQuantity;GPU: price*leaseTime
|
||||
* 采用字符串整数相乘,避免精度问题;展示最多6位小数,截断不四舍五入
|
||||
*/
|
||||
formatConfirmTotalText(row) {
|
||||
try {
|
||||
const price = this.getDisplayPrice ? this.getDisplayPrice(row) : (row && row.price)
|
||||
const coin = (this.getDisplayPriceCoin && this.getDisplayPriceCoin(row)) || ''
|
||||
const lease = Number(row && row.leaseTime) || 1
|
||||
const nums = [price, lease]
|
||||
if (this.machineType === 0) {
|
||||
const qty = Number(row && row.purchaseQuantity) || 1
|
||||
nums.push(qty)
|
||||
}
|
||||
const mul = this.multiplyAsDecimal(nums)
|
||||
const text = this.truncateDecimalString(mul, 6)
|
||||
const unit = (coin || '').toString().toUpperCase()
|
||||
return unit ? `${text} ${unit}` : text
|
||||
} catch (e) {
|
||||
return '—'
|
||||
}
|
||||
},
|
||||
/**
|
||||
* 获取该行可购买的最大数量(<= 总机器数)
|
||||
* @param {Object} row
|
||||
* @returns {number}
|
||||
*/
|
||||
getRowMaxPurchase(row) {
|
||||
try {
|
||||
const n = Number(row && row.saleNumbers)
|
||||
if (!Number.isFinite(n) || n < 0) return 0
|
||||
return Math.floor(n)
|
||||
} catch (e) { return 0 }
|
||||
},
|
||||
/**
|
||||
* 购买数量变更时,强制校验区间 [1, max] 且取整
|
||||
* @param {Object} row
|
||||
* @param {number} value
|
||||
*/
|
||||
handlePurchaseQuantityChange(row, value) {
|
||||
try {
|
||||
const max = this.getRowMaxPurchase(row)
|
||||
let v = Number(value)
|
||||
if (!Number.isFinite(v)) v = 1
|
||||
if (v < 1) v = 1
|
||||
if (max > 0 && v > max) v = max
|
||||
v = Math.floor(v)
|
||||
this.$set(row, 'purchaseQuantity', v)
|
||||
} catch (e) {
|
||||
this.$set(row, 'purchaseQuantity', 1)
|
||||
}
|
||||
},
|
||||
/**
|
||||
* 行是否存在任意价格(用于禁用勾选)
|
||||
* 规则:
|
||||
* - 若存在 priceList,任一项的 price 非 null/undefined 即视为有价格
|
||||
* - 否则回退 row.price 是否有效
|
||||
*/
|
||||
hasAnyPrice(row) {
|
||||
try {
|
||||
if (!row) return false
|
||||
if (Array.isArray(row.priceList) && row.priceList.length) {
|
||||
return row.priceList.some(it => it && it.price !== null && it.price !== undefined)
|
||||
}
|
||||
const v = row.price
|
||||
return v !== null && v !== undefined && v !== ''
|
||||
} catch (e) { return false }
|
||||
},
|
||||
/**
|
||||
* 该行是否应禁用选择:已售出/售出中 或 无价格
|
||||
*/
|
||||
isRowDisabled(row) {
|
||||
try {
|
||||
if (!row) return true
|
||||
if (row.saleState === 1 || row.saleState === 2) return true
|
||||
return !this.hasAnyPrice(row)
|
||||
} catch (e) { return true }
|
||||
},
|
||||
/**
|
||||
* 获取行的最大可租赁天数
|
||||
* 规则:优先 row.maxLeaseDays;否则回退 365;保证区间 [1, 365]
|
||||
@@ -343,18 +523,9 @@ export default {
|
||||
* 重置筛选
|
||||
*/
|
||||
handleResetFilters() {
|
||||
this.selectedPayKey = null
|
||||
this.filters = {
|
||||
chain: '',
|
||||
coin: '',
|
||||
minPrice: null,
|
||||
maxPrice: null,
|
||||
minPower: null,
|
||||
maxPower: null,
|
||||
minPowerDissipation: null,
|
||||
maxPowerDissipation: null,
|
||||
unit: 'GH/S'
|
||||
}
|
||||
// 仅重置“单价区间”的值,不影响支付方式筛选及其它条件
|
||||
this.filters.minPrice = null
|
||||
this.filters.maxPrice = null
|
||||
this.handleSearchFilters()
|
||||
},
|
||||
/**
|
||||
@@ -730,6 +901,24 @@ export default {
|
||||
.col-icon { width: 16px; height: 16px; border-radius: 3px; }
|
||||
.col-unit { color: #94a3b8; font-size: 12px; }
|
||||
.more-action { margin-left: 8px; color: #2563eb; font-weight: 600; }
|
||||
.more-action {
|
||||
font-size: 12px;
|
||||
padding: 0 4px;
|
||||
height: auto;
|
||||
line-height: 1;
|
||||
border: none;
|
||||
}
|
||||
.more-action:hover {
|
||||
color: #1d4ed8;
|
||||
text-decoration: underline;
|
||||
}
|
||||
.dynamic-search-bar :deep(.el-input__inner) {
|
||||
font-size: 12px;
|
||||
}
|
||||
.el-dialog__title {
|
||||
font-size: 16px !important;
|
||||
font-weight: 600;
|
||||
}
|
||||
.input-full { width: 100%; }
|
||||
:deep(.el-input-number.input-full) { width: 100%; }
|
||||
|
||||
|
||||
Binary file not shown.
1
power_leasing/test/css/app.5ed3e526.css
Normal file
1
power_leasing/test/css/app.5ed3e526.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.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>
|
||||
<!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.551d07c7.js"></script><link href="/css/chunk-vendors.10dd4e95.css" rel="stylesheet"><link href="/css/app.5ed3e526.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.551d07c7.js
Normal file
2
power_leasing/test/js/app.551d07c7.js
Normal file
File diff suppressed because one or more lines are too long
1
power_leasing/test/js/app.551d07c7.js.map
Normal file
1
power_leasing/test/js/app.551d07c7.js.map
Normal file
File diff suppressed because one or more lines are too long
Reference in New Issue
Block a user