2025-09-26 16:40:38 +08:00
|
|
|
|
<template>
|
|
|
|
|
|
<div class="product-machine-add">
|
|
|
|
|
|
<div class="header">
|
|
|
|
|
|
<el-button type="text" @click="handleBack">返回</el-button>
|
2025-12-19 15:40:53 +08:00
|
|
|
|
<h2 class="title">创建商品</h2>
|
2025-09-26 16:40:38 +08:00
|
|
|
|
</div>
|
|
|
|
|
|
|
2025-11-21 16:23:46 +08:00
|
|
|
|
<!-- <el-alert
|
2025-10-20 10:15:13 +08:00
|
|
|
|
class="notice-alert"
|
|
|
|
|
|
type="warning"
|
|
|
|
|
|
show-icon
|
|
|
|
|
|
:closable="false"
|
2025-12-19 15:40:53 +08:00
|
|
|
|
title="新增商品必须在 M2pool 有挖矿算力记录才能添加出租"
|
|
|
|
|
|
description="建议稳定在 M2pool 矿池挖矿 24 小时之后,再创建该商品"
|
2025-11-21 16:23:46 +08:00
|
|
|
|
/> -->
|
2025-10-20 10:15:13 +08:00
|
|
|
|
|
2025-09-26 16:40:38 +08:00
|
|
|
|
<el-card shadow="never" class="form-card">
|
2025-12-12 15:33:23 +08:00
|
|
|
|
<el-form
|
|
|
|
|
|
ref="machineForm"
|
|
|
|
|
|
:model="form"
|
|
|
|
|
|
:rules="rules"
|
|
|
|
|
|
label-width="160px"
|
|
|
|
|
|
size="small"
|
|
|
|
|
|
>
|
2025-11-21 16:23:46 +08:00
|
|
|
|
<el-form-item label="矿机种类">
|
2025-12-12 15:33:23 +08:00
|
|
|
|
<el-radio-group
|
|
|
|
|
|
v-model="form.machineCategory"
|
|
|
|
|
|
@change="handleMachineCategoryChange"
|
|
|
|
|
|
>
|
2025-11-21 16:23:46 +08:00
|
|
|
|
<el-radio label="ASIC">ASIC</el-radio>
|
|
|
|
|
|
<el-radio label="GPU">GPU</el-radio>
|
|
|
|
|
|
</el-radio-group>
|
2025-09-26 16:40:38 +08:00
|
|
|
|
</el-form-item>
|
2025-12-12 15:33:23 +08:00
|
|
|
|
<!-- GPU 引导内容 -->
|
|
|
|
|
|
<div v-if="form.machineCategory === 'GPU'" class="gpu-guide-section">
|
|
|
|
|
|
<el-card shadow="never" class="gpu-guide-card">
|
|
|
|
|
|
<div class="gpu-guide-content">
|
|
|
|
|
|
<div class="gpu-guide-title">注意事项:</div>
|
|
|
|
|
|
<ol class="gpu-guide-list">
|
|
|
|
|
|
<li>
|
|
|
|
|
|
GPU商品需先点击下方按钮,下载并在GPU所在主机启动客户端,在下载包中会有启动客户端的操作指引文档
|
|
|
|
|
|
</li>
|
|
|
|
|
|
<li>
|
|
|
|
|
|
成功在GPU主机启动客户端后,GPU信息会自行添加至商品列表中,点击下方前往商品列表按钮可以前往该页面
|
|
|
|
|
|
</li>
|
|
|
|
|
|
<li>
|
|
|
|
|
|
客户端和您的卖家账号绑定,如果您需要在本卖家账号<span v-if="userEmail"> ({{ userEmail }})</span>添加多个GPU商品,可通过下列两种方法实现:<br />
|
|
|
|
|
|
方法一:点击下方下载客户端按钮,将下载好的完整客户端包复制到不同的GPU主机并且启动,启动后客户端所在主机的GPU数据会自动添加到商品列表中
|
|
|
|
|
|
(推荐) <br />
|
|
|
|
|
|
方法二:在不同的客户端主机登陆您的卖家账号,进入本页面,点击下载对应操作系统客户端,成功启动客户端后也可自动添加GPU数据到商品列表中
|
|
|
|
|
|
</li>
|
|
|
|
|
|
<li>目前只支持NVIDIA系列GPU</li>
|
|
|
|
|
|
</ol>
|
|
|
|
|
|
<div class="gpu-guide-buttons">
|
|
|
|
|
|
<el-button type="primary" @click="handleDownloadClient('windows')"
|
|
|
|
|
|
>Windows 客户端下载</el-button
|
|
|
|
|
|
>
|
|
|
|
|
|
<el-button type="primary" @click="handleDownloadClient('linux')"
|
|
|
|
|
|
>Linux 客户端下载</el-button
|
|
|
|
|
|
>
|
|
|
|
|
|
<el-button type="success" @click="handleGpuClientStarted"
|
|
|
|
|
|
>前往商品列表</el-button
|
|
|
|
|
|
>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</el-card>
|
|
|
|
|
|
</div>
|
2025-12-05 16:24:20 +08:00
|
|
|
|
<!-- 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"
|
|
|
|
|
|
>
|
2025-12-12 15:33:23 +08:00
|
|
|
|
<el-select
|
2025-12-05 16:24:20 +08:00
|
|
|
|
v-model="row.coin"
|
2025-12-12 15:33:23 +08:00
|
|
|
|
placeholder="请选择币种"
|
2025-12-05 16:24:20 +08:00
|
|
|
|
class="coin-input"
|
2025-12-12 15:33:23 +08:00
|
|
|
|
@change="handleCoinChange(idx, $event)"
|
|
|
|
|
|
:loading="loadingCoins"
|
|
|
|
|
|
filterable
|
|
|
|
|
|
clearable
|
|
|
|
|
|
>
|
|
|
|
|
|
<el-option
|
|
|
|
|
|
v-for="coin in coinOptions"
|
|
|
|
|
|
:key="coin"
|
|
|
|
|
|
:label="coin"
|
|
|
|
|
|
:value="coin"
|
|
|
|
|
|
/>
|
|
|
|
|
|
</el-select>
|
|
|
|
|
|
<el-select
|
2025-12-05 16:24:20 +08:00
|
|
|
|
v-model="row.algorithm"
|
2025-12-12 15:33:23 +08:00
|
|
|
|
placeholder="请选择算法"
|
2025-12-05 16:24:20 +08:00
|
|
|
|
class="algo-input"
|
2025-12-12 15:33:23 +08:00
|
|
|
|
:loading="loadingAlgos[idx]"
|
|
|
|
|
|
:disabled="!row.coin"
|
|
|
|
|
|
filterable
|
|
|
|
|
|
clearable
|
|
|
|
|
|
>
|
|
|
|
|
|
<el-option
|
|
|
|
|
|
v-for="algo in (algoOptionsMap[row.coin] || [])"
|
|
|
|
|
|
:key="algo"
|
|
|
|
|
|
:label="algo"
|
|
|
|
|
|
:value="algo"
|
|
|
|
|
|
/>
|
|
|
|
|
|
</el-select>
|
2025-12-05 16:24:20 +08:00
|
|
|
|
<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>
|
2025-11-21 16:23:46 +08:00
|
|
|
|
</el-form-item>
|
2025-09-26 16:40:38 +08:00
|
|
|
|
|
2025-12-12 15:33:23 +08:00
|
|
|
|
<el-form-item v-if="form.machineCategory === 'ASIC'" label="矿机型号" prop="type" :required="true">
|
|
|
|
|
|
<el-input
|
|
|
|
|
|
style="width: 50%"
|
|
|
|
|
|
v-model="form.type"
|
|
|
|
|
|
placeholder="示例:龍珠"
|
|
|
|
|
|
:maxlength="20"
|
|
|
|
|
|
@input="handleTypeInput"
|
|
|
|
|
|
/>
|
2025-09-26 16:40:38 +08:00
|
|
|
|
</el-form-item>
|
2025-12-05 16:24:20 +08:00
|
|
|
|
<!-- 理论算力与单位已合并到上面的同行多行输入 -->
|
2025-12-12 15:33:23 +08:00
|
|
|
|
<el-form-item v-if="form.machineCategory === 'ASIC'" label="最大租赁天数" prop="maxLeaseDays">
|
2025-10-20 10:15:13 +08:00
|
|
|
|
<el-input
|
|
|
|
|
|
v-model="form.maxLeaseDays"
|
|
|
|
|
|
placeholder="1-365"
|
|
|
|
|
|
inputmode="numeric"
|
|
|
|
|
|
@input="handleNumeric('maxLeaseDays')"
|
2025-12-12 15:33:23 +08:00
|
|
|
|
style="width: 50%"
|
2025-10-20 10:15:13 +08:00
|
|
|
|
>
|
|
|
|
|
|
<template slot="append">天</template>
|
|
|
|
|
|
</el-input>
|
|
|
|
|
|
</el-form-item>
|
2025-12-12 15:33:23 +08:00
|
|
|
|
<el-form-item v-if="form.machineCategory === 'ASIC'" label="功耗" prop="powerDissipation">
|
2025-10-20 10:15:13 +08:00
|
|
|
|
<el-input
|
2025-12-12 15:33:23 +08:00
|
|
|
|
v-model="form.powerDissipation"
|
2025-10-20 10:15:13 +08:00
|
|
|
|
inputmode="decimal"
|
|
|
|
|
|
@input="handleNumeric('powerDissipation')"
|
2025-12-12 15:33:23 +08:00
|
|
|
|
style="width: 50%"
|
2025-10-20 10:15:13 +08:00
|
|
|
|
>
|
|
|
|
|
|
<template slot="append">kw/h</template>
|
|
|
|
|
|
</el-input>
|
|
|
|
|
|
</el-form-item>
|
2025-12-12 15:33:23 +08:00
|
|
|
|
|
2025-11-28 15:30:36 +08:00
|
|
|
|
<el-form-item
|
2025-12-12 15:33:23 +08:00
|
|
|
|
v-if="form.machineCategory === 'ASIC'"
|
2025-11-28 15:30:36 +08:00
|
|
|
|
label="统一售价"
|
2025-12-12 15:33:23 +08:00
|
|
|
|
:prop="payTypeDefs && payTypeDefs.length ? 'costMap' : 'cost'"
|
2025-11-28 15:30:36 +08:00
|
|
|
|
:required="true"
|
|
|
|
|
|
>
|
2025-11-21 16:23:46 +08:00
|
|
|
|
<span slot="label">统一售价</span>
|
2025-11-07 16:30:03 +08:00
|
|
|
|
<!-- 若商品定义了多个结算币种,则按链-币种动态生成多个售价输入;否则回退为旧的 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"
|
2025-12-12 15:33:23 +08:00
|
|
|
|
@input="(val) => handleCostMapInput(pt.key, val)"
|
|
|
|
|
|
style="width: 50%"
|
2025-11-07 16:30:03 +08:00
|
|
|
|
>
|
|
|
|
|
|
<template slot="append">{{ pt.label }}</template>
|
|
|
|
|
|
</el-input>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
2025-10-20 10:15:13 +08:00
|
|
|
|
<el-input
|
2025-11-07 16:30:03 +08:00
|
|
|
|
v-else
|
2025-10-20 10:15:13 +08:00
|
|
|
|
v-model="form.cost"
|
|
|
|
|
|
placeholder="请输入成本(USDT)"
|
|
|
|
|
|
inputmode="decimal"
|
|
|
|
|
|
@input="handleNumeric('cost')"
|
2025-12-12 15:33:23 +08:00
|
|
|
|
style="width: 50%"
|
2025-10-20 10:15:13 +08:00
|
|
|
|
>
|
|
|
|
|
|
<template slot="append">USDT</template>
|
|
|
|
|
|
</el-input>
|
|
|
|
|
|
</el-form-item>
|
2025-12-12 15:33:23 +08:00
|
|
|
|
|
2025-11-21 16:23:46 +08:00
|
|
|
|
<!-- 出售机器数量(统一使用,GPU 仅作引导不在本页提交) -->
|
2025-12-12 15:33:23 +08:00
|
|
|
|
<el-form-item
|
|
|
|
|
|
v-if="form.machineCategory === 'ASIC'"
|
|
|
|
|
|
label="出售机器数量(台)"
|
|
|
|
|
|
prop="sellCount"
|
|
|
|
|
|
:required="true"
|
|
|
|
|
|
>
|
2025-11-21 16:23:46 +08:00
|
|
|
|
<el-input
|
|
|
|
|
|
v-model="form.sellCount"
|
|
|
|
|
|
placeholder="0 - 9999"
|
|
|
|
|
|
inputmode="numeric"
|
2025-12-12 15:33:23 +08:00
|
|
|
|
style="width: 50%"
|
2025-11-21 16:23:46 +08:00
|
|
|
|
@input="handleSellCountInput"
|
|
|
|
|
|
@blur="handleSellCountBlur"
|
|
|
|
|
|
/>
|
2025-09-26 16:40:38 +08:00
|
|
|
|
</el-form-item>
|
|
|
|
|
|
</el-form>
|
|
|
|
|
|
</el-card>
|
|
|
|
|
|
|
2025-12-12 15:33:23 +08:00
|
|
|
|
<div v-if="form.machineCategory === 'ASIC'" class="actions">
|
2025-09-26 16:40:38 +08:00
|
|
|
|
<el-button @click="handleBack">取消</el-button>
|
2025-12-12 15:33:23 +08:00
|
|
|
|
<el-button type="primary" :loading="saving" @click="handleSave"
|
2025-12-19 15:40:53 +08:00
|
|
|
|
>确认创建</el-button
|
2025-12-12 15:33:23 +08:00
|
|
|
|
>
|
2025-09-26 16:40:38 +08:00
|
|
|
|
</div>
|
2025-12-12 15:33:23 +08:00
|
|
|
|
|
2025-09-26 16:40:38 +08:00
|
|
|
|
<!-- 上架确认弹窗 -->
|
|
|
|
|
|
<el-dialog
|
|
|
|
|
|
title="请确认上架信息"
|
|
|
|
|
|
:visible.sync="confirmVisible"
|
2025-11-28 15:30:36 +08:00
|
|
|
|
width="560px"
|
2025-09-26 16:40:38 +08:00
|
|
|
|
>
|
2025-12-12 15:33:23 +08:00
|
|
|
|
<div style="text-align: left; line-height: 1.9">
|
|
|
|
|
|
<div>
|
|
|
|
|
|
币种:<b>{{ confirmData.coin }}</b>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<div>
|
|
|
|
|
|
算法:<b>{{ confirmData.algorithm }}</b>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<div>
|
|
|
|
|
|
最大租赁天数:<b>{{ confirmData.maxLeaseDays || "-" }}</b>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<div>
|
|
|
|
|
|
出售机器数量:<b>{{ confirmData.saleNumbers || "-" }}</b>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<div style="margin-top: 8px">售价:</div>
|
2025-11-28 15:30:36 +08:00
|
|
|
|
<el-table
|
|
|
|
|
|
:data="confirmData.priceList"
|
|
|
|
|
|
border
|
|
|
|
|
|
size="mini"
|
2025-12-12 15:33:23 +08:00
|
|
|
|
style="width: 100%"
|
2025-11-28 15:30:36 +08:00
|
|
|
|
>
|
|
|
|
|
|
<el-table-column prop="chain" label="链" width="120" />
|
|
|
|
|
|
<el-table-column prop="coin" label="币种" width="120" />
|
|
|
|
|
|
<el-table-column label="价格">
|
|
|
|
|
|
<template slot-scope="scope">
|
|
|
|
|
|
{{ scope.row.price }}
|
|
|
|
|
|
</template>
|
|
|
|
|
|
</el-table-column>
|
|
|
|
|
|
</el-table>
|
2025-12-12 15:33:23 +08:00
|
|
|
|
<p style="color: #666; margin-top: 12px">
|
|
|
|
|
|
请仔细确认以上参数无误后提交。
|
|
|
|
|
|
</p>
|
2025-09-26 16:40:38 +08:00
|
|
|
|
</div>
|
|
|
|
|
|
<span slot="footer" class="dialog-footer">
|
|
|
|
|
|
<el-button @click="confirmVisible = false">取消</el-button>
|
2025-12-12 15:33:23 +08:00
|
|
|
|
<el-button type="primary" :loading="saving" @click="doSubmit"
|
|
|
|
|
|
>确认提交</el-button
|
|
|
|
|
|
>
|
2025-11-21 16:23:46 +08:00
|
|
|
|
</span>
|
|
|
|
|
|
</el-dialog>
|
2025-09-26 16:40:38 +08:00
|
|
|
|
</div>
|
|
|
|
|
|
</template>
|
|
|
|
|
|
|
|
|
|
|
|
<script>
|
2025-12-12 15:33:23 +08:00
|
|
|
|
import {
|
|
|
|
|
|
addSingleOrBatchMachine,
|
|
|
|
|
|
downloadClient,
|
|
|
|
|
|
addAsicMachine,getSupportCoin,getSupportAlgo
|
|
|
|
|
|
} from "../../api/machine";
|
|
|
|
|
|
import { getPayTypes } from "../../api/products";
|
|
|
|
|
|
import request from "../../utils/request";
|
2025-09-26 16:40:38 +08:00
|
|
|
|
export default {
|
2025-12-12 15:33:23 +08:00
|
|
|
|
name: "AccountProductMachineAdd",
|
2025-09-26 16:40:38 +08:00
|
|
|
|
data() {
|
|
|
|
|
|
return {
|
|
|
|
|
|
form: {
|
|
|
|
|
|
productId: Number(this.$route.query.productId) || null,
|
2025-12-12 15:33:23 +08:00
|
|
|
|
coin: this.$route.query.coin || "",
|
|
|
|
|
|
productName: this.$route.query.name || "",
|
2025-11-21 16:23:46 +08:00
|
|
|
|
/** 矿机种类:ASIC 或 GPU,默认 ASIC */
|
2025-12-12 15:33:23 +08:00
|
|
|
|
machineCategory: "ASIC",
|
2025-11-21 16:23:46 +08:00
|
|
|
|
/** 出售机器数量(仅 ASIC 模式使用) */
|
2025-12-12 15:33:23 +08:00
|
|
|
|
sellCount: "",
|
2025-12-05 16:24:20 +08:00
|
|
|
|
/** ASIC:币种/算法/理论算力/单位 行编辑(最多10行) */
|
|
|
|
|
|
coinAndAlgoList: [
|
2025-12-12 15:33:23 +08:00
|
|
|
|
{ coin: "", algorithm: "", theoryPower: "", unit: "TH/S" },
|
2025-12-05 16:24:20 +08:00
|
|
|
|
],
|
2025-09-26 16:40:38 +08:00
|
|
|
|
powerDissipation: null,
|
2025-12-12 15:33:23 +08:00
|
|
|
|
type: "",
|
|
|
|
|
|
cost: "",
|
2025-11-07 16:30:03 +08:00
|
|
|
|
costMap: {}, // { 'CHAIN-COIN': '123.45' }
|
2025-12-12 15:33:23 +08:00
|
|
|
|
maxLeaseDays: "",
|
2025-09-26 16:40:38 +08:00
|
|
|
|
},
|
|
|
|
|
|
confirmVisible: false,
|
2025-11-28 15:30:36 +08:00
|
|
|
|
confirmData: {
|
2025-12-12 15:33:23 +08:00
|
|
|
|
coin: "",
|
|
|
|
|
|
algorithm: "",
|
|
|
|
|
|
maxLeaseDays: "",
|
|
|
|
|
|
saleNumbers: "",
|
|
|
|
|
|
priceList: [],
|
2025-11-28 15:30:36 +08:00
|
|
|
|
},
|
2025-09-26 16:40:38 +08:00
|
|
|
|
rules: {
|
2025-12-12 15:33:23 +08:00
|
|
|
|
productName: [
|
|
|
|
|
|
{ required: true, message: "商品名称不能为空", trigger: "change" },
|
|
|
|
|
|
],
|
2025-11-28 15:30:36 +08:00
|
|
|
|
type: [
|
2025-12-12 15:33:23 +08:00
|
|
|
|
{ required: true, message: "矿机型号不能为空", trigger: "blur" },
|
2025-11-28 15:30:36 +08:00
|
|
|
|
{
|
|
|
|
|
|
validator: (rule, value, callback) => {
|
2025-12-12 15:33:23 +08:00
|
|
|
|
const s = String(value || "");
|
2025-11-28 15:30:36 +08:00
|
|
|
|
// 不允许全是空格
|
|
|
|
|
|
if (s && s.trim().length === 0) {
|
2025-12-12 15:33:23 +08:00
|
|
|
|
callback(new Error("矿机型号不能全是空格"));
|
|
|
|
|
|
return;
|
2025-11-28 15:30:36 +08:00
|
|
|
|
}
|
2025-12-12 15:33:23 +08:00
|
|
|
|
callback();
|
2025-11-28 15:30:36 +08:00
|
|
|
|
},
|
2025-12-12 15:33:23 +08:00
|
|
|
|
trigger: "blur",
|
|
|
|
|
|
},
|
2025-11-28 15:30:36 +08:00
|
|
|
|
],
|
2025-12-05 16:24:20 +08:00
|
|
|
|
coinAndAlgoList: [
|
2025-12-12 15:33:23 +08:00
|
|
|
|
{
|
|
|
|
|
|
validator: (rule, value, callback) =>
|
|
|
|
|
|
this.validateCoinAlgoRows(rule, value, callback),
|
|
|
|
|
|
trigger: "blur",
|
|
|
|
|
|
},
|
2025-11-21 16:23:46 +08:00
|
|
|
|
],
|
|
|
|
|
|
sellCount: [
|
|
|
|
|
|
{
|
|
|
|
|
|
validator: (rule, value, callback) => {
|
2025-12-12 15:33:23 +08:00
|
|
|
|
if (this.form.machineCategory !== "ASIC") {
|
|
|
|
|
|
callback();
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
const raw = String(value ?? "");
|
|
|
|
|
|
if (raw === "") {
|
|
|
|
|
|
callback(new Error("请输入出售机器数量"));
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
if (!/^\d{1,4}$/.test(raw)) {
|
|
|
|
|
|
callback(new Error("请输入 0-9999 的整数"));
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
const n = Number(raw);
|
|
|
|
|
|
if (!Number.isInteger(n) || n < 0 || n > 9999) {
|
|
|
|
|
|
callback(new Error("范围需在 0-9999"));
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
callback();
|
2025-11-21 16:23:46 +08:00
|
|
|
|
},
|
2025-12-12 15:33:23 +08:00
|
|
|
|
trigger: "blur",
|
|
|
|
|
|
},
|
2025-11-21 16:23:46 +08:00
|
|
|
|
],
|
2025-09-26 16:40:38 +08:00
|
|
|
|
powerDissipation: [
|
2025-12-12 15:33:23 +08:00
|
|
|
|
{ required: true, message: "功耗不能为空", trigger: "blur" },
|
2025-09-26 16:40:38 +08:00
|
|
|
|
{
|
|
|
|
|
|
validator: (rule, value, callback) => {
|
2025-12-12 15:33:23 +08:00
|
|
|
|
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();
|
2025-09-26 16:40:38 +08:00
|
|
|
|
},
|
2025-12-12 15:33:23 +08:00
|
|
|
|
trigger: "blur",
|
|
|
|
|
|
},
|
2025-09-26 16:40:38 +08:00
|
|
|
|
],
|
|
|
|
|
|
cost: [
|
|
|
|
|
|
{
|
2025-11-07 16:30:03 +08:00
|
|
|
|
validator(rule, value, callback) {
|
|
|
|
|
|
// 若为多结算币种模式,跳过此校验(统一售价由每种币种的输入框承担)
|
2025-12-12 15:33:23 +08:00
|
|
|
|
if (
|
|
|
|
|
|
Array.isArray(this.payTypeDefs) &&
|
|
|
|
|
|
this.payTypeDefs.length > 0
|
|
|
|
|
|
) {
|
|
|
|
|
|
callback();
|
|
|
|
|
|
return;
|
2025-11-07 16:30:03 +08:00
|
|
|
|
}
|
2025-12-12 15:33:23 +08:00
|
|
|
|
const str = String(value || "");
|
2025-09-26 16:40:38 +08:00
|
|
|
|
if (!str) {
|
2025-12-12 15:33:23 +08:00
|
|
|
|
callback(new Error("请填写机器成本(USDT)"));
|
|
|
|
|
|
return;
|
2025-09-26 16:40:38 +08:00
|
|
|
|
}
|
2025-12-12 15:33:23 +08:00
|
|
|
|
const pattern = /^\d{1,12}(\.\d{1,2})?$/;
|
2025-09-26 16:40:38 +08:00
|
|
|
|
if (!pattern.test(str)) {
|
2025-12-12 15:33:23 +08:00
|
|
|
|
callback(new Error("成本整数最多12位,小数最多2位"));
|
|
|
|
|
|
return;
|
2025-09-26 16:40:38 +08:00
|
|
|
|
}
|
|
|
|
|
|
if (Number(str) <= 0) {
|
2025-12-12 15:33:23 +08:00
|
|
|
|
callback(new Error("成本必须大于 0"));
|
|
|
|
|
|
return;
|
2025-09-26 16:40:38 +08:00
|
|
|
|
}
|
2025-12-12 15:33:23 +08:00
|
|
|
|
callback();
|
2025-09-26 16:40:38 +08:00
|
|
|
|
},
|
2025-12-12 15:33:23 +08:00
|
|
|
|
trigger: "blur",
|
|
|
|
|
|
},
|
|
|
|
|
|
],
|
2025-10-20 10:15:13 +08:00
|
|
|
|
maxLeaseDays: [
|
2025-12-12 15:33:23 +08:00
|
|
|
|
{ required: true, message: "请填写最大租赁天数", trigger: "blur" },
|
2025-10-20 10:15:13 +08:00
|
|
|
|
{
|
|
|
|
|
|
validator: (rule, value, callback) => {
|
2025-12-12 15:33:23 +08:00
|
|
|
|
const raw = String(value ?? "");
|
|
|
|
|
|
if (!raw) {
|
|
|
|
|
|
callback(new Error("请填写最大租赁天数"));
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
if (!/^\d{1,3}$/.test(raw)) {
|
|
|
|
|
|
callback(new Error("仅允许整数,范围 1-365"));
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
const n = Number(raw);
|
|
|
|
|
|
if (!Number.isInteger(n) || n < 1 || n > 365) {
|
|
|
|
|
|
callback(new Error("范围需在 1-365 天"));
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
callback();
|
2025-10-20 10:15:13 +08:00
|
|
|
|
},
|
2025-12-12 15:33:23 +08:00
|
|
|
|
trigger: "blur",
|
|
|
|
|
|
},
|
|
|
|
|
|
],
|
2025-09-26 16:40:38 +08:00
|
|
|
|
},
|
|
|
|
|
|
miners: [
|
2025-12-12 15:33:23 +08:00
|
|
|
|
// {
|
|
|
|
|
|
// "user": "lx_888",
|
|
|
|
|
|
// "miner": null,
|
|
|
|
|
|
// "coin": "nexa"
|
|
|
|
|
|
// },
|
|
|
|
|
|
// {
|
|
|
|
|
|
// "user": "lx999",
|
|
|
|
|
|
// "miner": null,
|
|
|
|
|
|
// "coin": "nexa"
|
|
|
|
|
|
// },
|
|
|
|
|
|
// {
|
|
|
|
|
|
// "user": "lx88",
|
|
|
|
|
|
// "miner": null,
|
|
|
|
|
|
// "coin": "nexa"
|
|
|
|
|
|
// },
|
|
|
|
|
|
// {
|
|
|
|
|
|
// "user": "lx6666",
|
|
|
|
|
|
// "miner": null,
|
|
|
|
|
|
// "coin": "nexa"
|
|
|
|
|
|
// },
|
|
|
|
|
|
// {
|
|
|
|
|
|
// "user": "lx_999",
|
|
|
|
|
|
// "miner": null,
|
|
|
|
|
|
// "coin": "nexa"
|
|
|
|
|
|
// },
|
|
|
|
|
|
// {
|
|
|
|
|
|
// "user": "Lx_6966",
|
|
|
|
|
|
// "miner": null,
|
|
|
|
|
|
// "coin": "nexa"
|
|
|
|
|
|
// },
|
|
|
|
|
|
// {
|
|
|
|
|
|
// "user": "LX_666",
|
|
|
|
|
|
// "miner": null,
|
|
|
|
|
|
// "coin": "nexa"
|
|
|
|
|
|
// },
|
2025-09-26 16:40:38 +08:00
|
|
|
|
],
|
|
|
|
|
|
minersLoading: false,
|
2025-12-12 15:33:23 +08:00
|
|
|
|
selectedMiner: "", // 格式 user|coin
|
2025-09-26 16:40:38 +08:00
|
|
|
|
machineOptions: [
|
2025-12-12 15:33:23 +08:00
|
|
|
|
// {
|
|
|
|
|
|
// "user": "lx_888",
|
|
|
|
|
|
// "miner": `iusfhufhu`,
|
|
|
|
|
|
// "coin": "nexa"
|
|
|
|
|
|
// },
|
|
|
|
|
|
// {
|
|
|
|
|
|
// "user": "lx999",
|
|
|
|
|
|
// "miner": `iusfhufhu2`,
|
|
|
|
|
|
// "coin": "nexa"
|
|
|
|
|
|
// },
|
|
|
|
|
|
// {
|
|
|
|
|
|
// "user": "lx88",
|
|
|
|
|
|
// "miner": `iusfhufhu3`,
|
|
|
|
|
|
// "coin": "nexa"
|
|
|
|
|
|
// },
|
|
|
|
|
|
// {
|
|
|
|
|
|
// "user": "lx6666",
|
|
|
|
|
|
// "miner": `iusfhufhu4`,
|
|
|
|
|
|
// "coin": "nexa"
|
|
|
|
|
|
// },
|
|
|
|
|
|
// {
|
|
|
|
|
|
// "user": "lx_999",
|
|
|
|
|
|
// "miner": `iusfhufhu5`,
|
|
|
|
|
|
// "coin": "nexa"
|
|
|
|
|
|
// },
|
|
|
|
|
|
// {
|
|
|
|
|
|
// "user": "Lx_6966",
|
|
|
|
|
|
// "miner": `iusfhufhu6`,
|
|
|
|
|
|
// "coin": "nexa"
|
|
|
|
|
|
// },
|
|
|
|
|
|
// {
|
|
|
|
|
|
// "user": "LX_666",
|
|
|
|
|
|
// "miner": `iusfhufhu7`,
|
|
|
|
|
|
// "coin": "nexa"
|
|
|
|
|
|
// },
|
2025-09-26 16:40:38 +08:00
|
|
|
|
],
|
|
|
|
|
|
machinesLoading: false,
|
|
|
|
|
|
selectedMachines: [],
|
|
|
|
|
|
selectedMachineRows: [],
|
|
|
|
|
|
saving: false,
|
|
|
|
|
|
lastCostBaseline: 0,
|
2025-11-07 16:30:03 +08:00
|
|
|
|
lastCostMapBaseline: {}, // { key: number }
|
2025-12-12 15:33:23 +08:00
|
|
|
|
lastTypeBaseline: "",
|
2025-10-20 10:15:13 +08:00
|
|
|
|
lastMaxLeaseDaysBaseline: 0,
|
|
|
|
|
|
lastPowerDissipationBaseline: 0,
|
|
|
|
|
|
lastTheoryPowerBaseline: 0,
|
2025-12-12 15:33:23 +08:00
|
|
|
|
lastUnitBaseline: "TH/S",
|
2025-11-21 16:23:46 +08:00
|
|
|
|
/** GPU 引导弹窗可见性 */
|
|
|
|
|
|
gpuDialogVisible: false,
|
|
|
|
|
|
/** GPU 客户端下载地址(可通过环境变量配置:VUE_APP_GPU_CLIENT_URL) */
|
2025-12-12 15:33:23 +08:00
|
|
|
|
clientDownloadUrl: process.env.VUE_APP_GPU_CLIENT_URL || "",
|
2025-11-21 16:23:46 +08:00
|
|
|
|
/** 是否点击过下载客户端(用于控制“已启动客户端”按钮禁用态) */
|
|
|
|
|
|
hasDownloadedClient: false,
|
2025-11-28 15:30:36 +08:00
|
|
|
|
/** 支持的支付方式定义(用于动态渲染统一售价输入组) */
|
|
|
|
|
|
payTypeDefs: [],
|
2025-12-12 15:33:23 +08:00
|
|
|
|
/** 币种选项列表 */
|
|
|
|
|
|
coinOptions: [],
|
|
|
|
|
|
/** 算法选项映射 { coin: [algo1, algo2, ...] } */
|
|
|
|
|
|
algoOptionsMap: {},
|
|
|
|
|
|
/** 加载币种状态 */
|
|
|
|
|
|
loadingCoins: false,
|
|
|
|
|
|
/** 加载算法状态映射 { index: boolean } */
|
|
|
|
|
|
loadingAlgos: {},
|
|
|
|
|
|
params: {
|
|
|
|
|
|
cost: 353400,
|
|
|
|
|
|
powerDissipation: 0.01,
|
|
|
|
|
|
theoryPower: 1000,
|
|
|
|
|
|
type: "",
|
|
|
|
|
|
unit: "TH/S",
|
|
|
|
|
|
productId: 1,
|
|
|
|
|
|
productMachineURDVos: [
|
|
|
|
|
|
{
|
|
|
|
|
|
user: "lx_888",
|
|
|
|
|
|
miner: "iusfhufhu",
|
|
|
|
|
|
price: 353400,
|
|
|
|
|
|
type: "",
|
|
|
|
|
|
state: 0,
|
|
|
|
|
|
},
|
|
|
|
|
|
{
|
|
|
|
|
|
user: "lx_888",
|
|
|
|
|
|
miner: "iusfhufhu2",
|
|
|
|
|
|
price: 353400,
|
|
|
|
|
|
type: "",
|
|
|
|
|
|
state: 0,
|
|
|
|
|
|
},
|
|
|
|
|
|
],
|
|
|
|
|
|
},
|
|
|
|
|
|
userEmail:"",
|
|
|
|
|
|
};
|
2025-09-26 16:40:38 +08:00
|
|
|
|
},
|
|
|
|
|
|
created() {
|
2025-12-12 15:33:23 +08:00
|
|
|
|
this.initPayTypesFromRoute();
|
|
|
|
|
|
this.lastTypeBaseline = this.form.type;
|
2025-11-07 16:30:03 +08:00
|
|
|
|
// 绑定基于组件实例的校验器,避免 this 丢失
|
|
|
|
|
|
if (this.rules && this.rules.cost) {
|
2025-12-12 15:33:23 +08:00
|
|
|
|
this.$set(this.rules, "cost", [
|
|
|
|
|
|
{ validator: this.validateCost, trigger: "blur" },
|
|
|
|
|
|
]);
|
2025-11-07 16:30:03 +08:00
|
|
|
|
}
|
2025-11-28 15:30:36 +08:00
|
|
|
|
// 多币种价格专用校验
|
2025-12-12 15:33:23 +08:00
|
|
|
|
this.$set(this.rules, "costMap", [
|
|
|
|
|
|
{ validator: this.validateCostMap, trigger: "blur" },
|
|
|
|
|
|
]);
|
|
|
|
|
|
|
|
|
|
|
|
this.getPayTypes();
|
|
|
|
|
|
this.loadSupportCoins();
|
2025-11-28 15:30:36 +08:00
|
|
|
|
|
2025-12-12 15:33:23 +08:00
|
|
|
|
this.userEmail = JSON.parse(localStorage.getItem("leasEmail")) || "";
|
2025-09-26 16:40:38 +08:00
|
|
|
|
},
|
|
|
|
|
|
methods: {
|
2025-12-05 16:24:20 +08:00
|
|
|
|
/** ASIC 行校验:币种/算法/理论算力/单位 */
|
|
|
|
|
|
validateCoinAlgoRows(rule, value, callback) {
|
|
|
|
|
|
try {
|
2025-12-12 15:33:23 +08:00
|
|
|
|
const rows = Array.isArray(this.form.coinAndAlgoList)
|
|
|
|
|
|
? this.form.coinAndAlgoList
|
|
|
|
|
|
: [];
|
|
|
|
|
|
if (!rows.length) {
|
|
|
|
|
|
callback(new Error("请至少添加一行币种/算法/算力/单位"));
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
const powerPattern = /^\d{1,6}(\.\d{1,4})?$/;
|
2025-12-05 16:24:20 +08:00
|
|
|
|
for (let i = 0; i < rows.length; i += 1) {
|
2025-12-12 15:33:23 +08:00
|
|
|
|
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 (!algo) {
|
|
|
|
|
|
callback(new Error(`第 ${i + 1} 行:请选择算法`));
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
2025-12-05 16:24:20 +08:00
|
|
|
|
if (!power || !powerPattern.test(power) || Number(power) <= 0) {
|
2025-12-12 15:33:23 +08:00
|
|
|
|
callback(
|
|
|
|
|
|
new Error(
|
|
|
|
|
|
`第 ${i + 1} 行:理论算力需大于0,整数最多6位,小数最多4位`
|
|
|
|
|
|
)
|
|
|
|
|
|
);
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
if (!unit) {
|
|
|
|
|
|
callback(new Error(`第 ${i + 1} 行:请选择算力单位`));
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
callback();
|
|
|
|
|
|
} catch (e) {
|
|
|
|
|
|
callback(new Error("请检查币种/算法/算力/单位填写"));
|
|
|
|
|
|
}
|
|
|
|
|
|
},
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 加载支持的币种列表
|
|
|
|
|
|
*/
|
|
|
|
|
|
async loadSupportCoins() {
|
|
|
|
|
|
this.loadingCoins = true;
|
|
|
|
|
|
try {
|
|
|
|
|
|
const res = await getSupportCoin();
|
|
|
|
|
|
if (res && (res.code === 0 || res.code === 200)) {
|
|
|
|
|
|
const data = res.data || [];
|
|
|
|
|
|
// 处理返回的数据,可能是数组或对象
|
|
|
|
|
|
if (Array.isArray(data)) {
|
|
|
|
|
|
this.coinOptions = data.map(item => {
|
|
|
|
|
|
// 如果是对象,取 coin 字段;如果是字符串,直接使用
|
|
|
|
|
|
return typeof item === 'string' ? item : (item.coin || item.name || item);
|
|
|
|
|
|
}).filter(Boolean);
|
|
|
|
|
|
} else if (data && typeof data === 'object') {
|
|
|
|
|
|
// 如果是对象,尝试提取币种列表
|
|
|
|
|
|
this.coinOptions = Object.keys(data).map(key => {
|
|
|
|
|
|
const item = data[key];
|
|
|
|
|
|
return typeof item === 'string' ? item : (item.coin || item.name || key);
|
|
|
|
|
|
}).filter(Boolean);
|
2025-12-05 16:24:20 +08:00
|
|
|
|
}
|
2025-12-12 15:33:23 +08:00
|
|
|
|
// 去重并排序
|
|
|
|
|
|
this.coinOptions = [...new Set(this.coinOptions)].sort();
|
2025-12-05 16:24:20 +08:00
|
|
|
|
}
|
|
|
|
|
|
} catch (e) {
|
2025-12-12 15:33:23 +08:00
|
|
|
|
console.error("加载币种列表失败", e);
|
|
|
|
|
|
this.$message.error("加载币种列表失败,请稍后重试");
|
|
|
|
|
|
} finally {
|
|
|
|
|
|
this.loadingCoins = false;
|
2025-12-05 16:24:20 +08:00
|
|
|
|
}
|
|
|
|
|
|
},
|
2025-12-12 15:33:23 +08:00
|
|
|
|
/**
|
|
|
|
|
|
* 币种选择变化处理
|
|
|
|
|
|
* @param {number} index - 行索引
|
|
|
|
|
|
* @param {string} coin - 选择的币种
|
|
|
|
|
|
*/
|
|
|
|
|
|
async handleCoinChange(index, coin) {
|
|
|
|
|
|
// 清空当前行的算法选择
|
|
|
|
|
|
this.$set(this.form.coinAndAlgoList[index], "algorithm", "");
|
|
|
|
|
|
// 如果选择了币种,加载对应的算法列表
|
|
|
|
|
|
if (coin) {
|
|
|
|
|
|
await this.loadAlgorithmsForCoin(coin, index);
|
|
|
|
|
|
}
|
2025-12-05 16:24:20 +08:00
|
|
|
|
},
|
2025-12-12 15:33:23 +08:00
|
|
|
|
/**
|
|
|
|
|
|
* 加载指定币种支持的算法列表
|
|
|
|
|
|
* @param {string} coin - 币种名称
|
|
|
|
|
|
* @param {number} index - 行索引(用于显示加载状态)
|
|
|
|
|
|
*/
|
|
|
|
|
|
async loadAlgorithmsForCoin(coin, index) {
|
|
|
|
|
|
if (!coin) return;
|
|
|
|
|
|
|
|
|
|
|
|
// 如果已经加载过该币种的算法,直接返回
|
|
|
|
|
|
if (this.algoOptionsMap[coin] && this.algoOptionsMap[coin].length > 0) {
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 设置加载状态
|
|
|
|
|
|
this.$set(this.loadingAlgos, index, true);
|
|
|
|
|
|
|
|
|
|
|
|
try {
|
|
|
|
|
|
const res = await getSupportAlgo(coin);
|
|
|
|
|
|
if (res && (res.code === 0 || res.code === 200)) {
|
|
|
|
|
|
const data = res.data || [];
|
|
|
|
|
|
let algorithms = [];
|
|
|
|
|
|
|
|
|
|
|
|
// 处理返回的数据,可能是数组或对象
|
|
|
|
|
|
if (Array.isArray(data)) {
|
|
|
|
|
|
algorithms = data.map(item => {
|
|
|
|
|
|
// 如果是对象,取 algorithm 或 algo 字段;如果是字符串,直接使用
|
|
|
|
|
|
return typeof item === 'string' ? item : (item.algorithm || item.algo || item.name || item);
|
|
|
|
|
|
}).filter(Boolean);
|
|
|
|
|
|
} else if (data && typeof data === 'object') {
|
|
|
|
|
|
// 如果是对象,尝试提取算法列表
|
|
|
|
|
|
algorithms = Object.keys(data).map(key => {
|
|
|
|
|
|
const item = data[key];
|
|
|
|
|
|
return typeof item === 'string' ? item : (item.algorithm || item.algo || item.name || key);
|
|
|
|
|
|
}).filter(Boolean);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 去重并排序,保存到映射中
|
|
|
|
|
|
this.$set(this.algoOptionsMap, coin, [...new Set(algorithms)].sort());
|
|
|
|
|
|
}
|
|
|
|
|
|
} catch (e) {
|
|
|
|
|
|
console.error(`加载币种 ${coin} 的算法列表失败`, e);
|
|
|
|
|
|
this.$message.error(`加载算法列表失败,请稍后重试`);
|
|
|
|
|
|
// 设置空数组,避免重复请求
|
|
|
|
|
|
this.$set(this.algoOptionsMap, coin, []);
|
|
|
|
|
|
} finally {
|
|
|
|
|
|
this.$set(this.loadingAlgos, index, false);
|
|
|
|
|
|
}
|
2025-12-05 16:24:20 +08:00
|
|
|
|
},
|
|
|
|
|
|
/** 行:理论算力输入限制(6整数4小数) */
|
|
|
|
|
|
handleCoinRowTheoryInput(index) {
|
2025-12-12 15:33:23 +08:00
|
|
|
|
let v = String(this.form.coinAndAlgoList[index].theoryPower ?? "");
|
|
|
|
|
|
v = v.replace(/[^0-9.]/g, "");
|
|
|
|
|
|
const firstDot = v.indexOf(".");
|
2025-12-05 16:24:20 +08:00
|
|
|
|
if (firstDot !== -1) {
|
2025-12-12 15:33:23 +08:00
|
|
|
|
v = v.slice(0, firstDot + 1) + v.slice(firstDot + 1).replace(/\./g, "");
|
2025-12-05 16:24:20 +08:00
|
|
|
|
}
|
2025-12-12 15:33:23 +08:00
|
|
|
|
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);
|
2025-12-05 16:24:20 +08:00
|
|
|
|
},
|
|
|
|
|
|
/** 行:单位变更 */
|
|
|
|
|
|
handleCoinRowUnitChange(index, value) {
|
2025-12-12 15:33:23 +08:00
|
|
|
|
this.$set(this.form.coinAndAlgoList[index], "unit", value);
|
2025-12-05 16:24:20 +08:00
|
|
|
|
},
|
|
|
|
|
|
/** 新增一行 */
|
|
|
|
|
|
handleAddCoinAlgoRow() {
|
|
|
|
|
|
if (this.form.coinAndAlgoList.length >= 10) {
|
2025-12-12 15:33:23 +08:00
|
|
|
|
this.$message.warning("最多添加 10 行");
|
|
|
|
|
|
return;
|
2025-12-05 16:24:20 +08:00
|
|
|
|
}
|
2025-12-12 15:33:23 +08:00
|
|
|
|
const last = this.form.coinAndAlgoList[
|
|
|
|
|
|
this.form.coinAndAlgoList.length - 1
|
|
|
|
|
|
] || { unit: "TH/S" };
|
|
|
|
|
|
const newIndex = this.form.coinAndAlgoList.length;
|
|
|
|
|
|
this.form.coinAndAlgoList.push({
|
|
|
|
|
|
coin: "",
|
|
|
|
|
|
algorithm: "",
|
|
|
|
|
|
theoryPower: "",
|
|
|
|
|
|
unit: last.unit || "TH/S",
|
|
|
|
|
|
});
|
|
|
|
|
|
// 初始化新行的加载状态
|
|
|
|
|
|
this.$set(this.loadingAlgos, newIndex, false);
|
2025-12-05 16:24:20 +08:00
|
|
|
|
},
|
|
|
|
|
|
/** 删除一行 */
|
|
|
|
|
|
handleRemoveCoinAlgoRow(index) {
|
2025-12-12 15:33:23 +08:00
|
|
|
|
if (this.form.coinAndAlgoList.length <= 1) return;
|
|
|
|
|
|
this.form.coinAndAlgoList.splice(index, 1);
|
2025-12-05 16:24:20 +08:00
|
|
|
|
},
|
|
|
|
|
|
/** 从多行聚合 coin CSV */
|
|
|
|
|
|
buildCoinCsvFromRows() {
|
2025-12-12 15:33:23 +08:00
|
|
|
|
const set = new Set();
|
|
|
|
|
|
const rows = Array.isArray(this.form.coinAndAlgoList)
|
|
|
|
|
|
? this.form.coinAndAlgoList
|
|
|
|
|
|
: [];
|
|
|
|
|
|
rows.forEach((r) => {
|
|
|
|
|
|
const token = String(r.coin || "")
|
2025-12-05 16:24:20 +08:00
|
|
|
|
.split(/[,\s,、]+/)
|
2025-12-12 15:33:23 +08:00
|
|
|
|
.map((s) => s.trim().toUpperCase())
|
|
|
|
|
|
.filter(Boolean);
|
|
|
|
|
|
token.forEach((t) => set.add(t));
|
|
|
|
|
|
});
|
|
|
|
|
|
return Array.from(set).join(",");
|
2025-12-05 16:24:20 +08:00
|
|
|
|
},
|
|
|
|
|
|
/** 从多行聚合 algorithm CSV */
|
|
|
|
|
|
buildAlgoCsvFromRows() {
|
2025-12-12 15:33:23 +08:00
|
|
|
|
const set = new Set();
|
|
|
|
|
|
const rows = Array.isArray(this.form.coinAndAlgoList)
|
|
|
|
|
|
? this.form.coinAndAlgoList
|
|
|
|
|
|
: [];
|
|
|
|
|
|
rows.forEach((r) => {
|
|
|
|
|
|
const token = String(r.algorithm || "")
|
2025-12-05 16:24:20 +08:00
|
|
|
|
.split(/[,\s,、]+/)
|
2025-12-12 15:33:23 +08:00
|
|
|
|
.map((s) => s.trim().toUpperCase())
|
|
|
|
|
|
.filter(Boolean);
|
|
|
|
|
|
token.forEach((t) => set.add(t));
|
|
|
|
|
|
});
|
|
|
|
|
|
return Array.from(set).join(",");
|
2025-12-05 16:24:20 +08:00
|
|
|
|
},
|
2025-11-28 15:30:36 +08:00
|
|
|
|
/** 实时过滤币种输入中的中文字符(仅保留英文/数字/分隔符) */
|
|
|
|
|
|
handleCoinsInput() {
|
2025-12-12 15:33:23 +08:00
|
|
|
|
let v = String(this.form.coinsInput || "");
|
2025-11-28 15:30:36 +08:00
|
|
|
|
// 去除中文
|
2025-12-12 15:33:23 +08:00
|
|
|
|
v = v.replace(/[\u4e00-\u9fa5]/g, "");
|
|
|
|
|
|
this.form.coinsInput = v;
|
2025-11-28 15:30:36 +08:00
|
|
|
|
},
|
|
|
|
|
|
/** 实时过滤算法输入中的中文字符(仅保留英文/数字/连字符/分隔符) */
|
|
|
|
|
|
handleAlgorithmsInput() {
|
2025-12-12 15:33:23 +08:00
|
|
|
|
let v = String(this.form.algorithmsInput || "");
|
|
|
|
|
|
v = v.replace(/[\u4e00-\u9fa5]/g, "");
|
|
|
|
|
|
this.form.algorithmsInput = v;
|
2025-11-28 15:30:36 +08:00
|
|
|
|
},
|
|
|
|
|
|
/** 将输入按中英文逗号、空格分割,去空,统一英文逗号连接;可选:统一大写 */
|
|
|
|
|
|
normalizeCsv(input, upper = true) {
|
2025-12-12 15:33:23 +08:00
|
|
|
|
const arr = String(input || "")
|
2025-11-28 15:30:36 +08:00
|
|
|
|
.split(/[,\s,、]+/)
|
2025-12-12 15:33:23 +08:00
|
|
|
|
.map((s) => s.trim())
|
|
|
|
|
|
.filter(Boolean);
|
|
|
|
|
|
const list = upper ? arr.map((s) => s.toUpperCase()) : arr;
|
|
|
|
|
|
return list.join(",");
|
2025-11-28 15:30:36 +08:00
|
|
|
|
},
|
|
|
|
|
|
/** 从 payTypeDefs 与 costMap 生成价格列表 */
|
|
|
|
|
|
buildPriceList() {
|
2025-12-12 15:33:23 +08:00
|
|
|
|
const list = [];
|
|
|
|
|
|
const defs = Array.isArray(this.payTypeDefs) ? this.payTypeDefs : [];
|
|
|
|
|
|
defs.forEach((d) => {
|
|
|
|
|
|
const key = d.key;
|
|
|
|
|
|
const priceRaw = this.form.costMap ? this.form.costMap[key] : "";
|
|
|
|
|
|
const priceNum = Number(priceRaw);
|
2025-11-28 15:30:36 +08:00
|
|
|
|
// 允许为空/非数字则不加入
|
2025-12-12 15:33:23 +08:00
|
|
|
|
if (!Number.isFinite(priceNum) || priceNum <= 0) return;
|
2025-11-28 15:30:36 +08:00
|
|
|
|
list.push({
|
|
|
|
|
|
chain: d.chain,
|
|
|
|
|
|
coin: d.coin,
|
2025-12-12 15:33:23 +08:00
|
|
|
|
price: priceNum,
|
|
|
|
|
|
});
|
|
|
|
|
|
});
|
|
|
|
|
|
return list;
|
2025-11-28 15:30:36 +08:00
|
|
|
|
},
|
|
|
|
|
|
async getPayTypes() {
|
|
|
|
|
|
try {
|
2025-12-12 15:33:23 +08:00
|
|
|
|
const res = await getPayTypes();
|
2025-11-28 15:30:36 +08:00
|
|
|
|
// 期望结构:{ code:200, data:[ { payChain, payCoin, payCoinImage } ] }
|
|
|
|
|
|
if (res && (res.code === 0 || res.code === 200)) {
|
2025-12-12 15:33:23 +08:00
|
|
|
|
const list = Array.isArray(res.data) ? res.data : [];
|
|
|
|
|
|
const defs = [];
|
|
|
|
|
|
const seen = new Set();
|
|
|
|
|
|
list.forEach((it) => {
|
|
|
|
|
|
const chain = String(
|
|
|
|
|
|
it && it.payChain ? it.payChain : ""
|
|
|
|
|
|
).toUpperCase();
|
|
|
|
|
|
const coin = String(
|
|
|
|
|
|
it && it.payCoin ? it.payCoin : ""
|
|
|
|
|
|
).toUpperCase();
|
|
|
|
|
|
if (!chain && !coin) return;
|
|
|
|
|
|
const key = [chain, coin].filter(Boolean).join("-");
|
|
|
|
|
|
if (seen.has(key)) return;
|
|
|
|
|
|
seen.add(key);
|
2025-11-28 15:30:36 +08:00
|
|
|
|
defs.push({
|
|
|
|
|
|
chain,
|
|
|
|
|
|
coin,
|
|
|
|
|
|
key,
|
|
|
|
|
|
label: key,
|
2025-12-12 15:33:23 +08:00
|
|
|
|
image: it && it.payCoinImage ? String(it.payCoinImage) : "",
|
|
|
|
|
|
});
|
|
|
|
|
|
});
|
2025-11-28 15:30:36 +08:00
|
|
|
|
// 根据接口结果渲染“统一售价”输入组
|
2025-12-12 15:33:23 +08:00
|
|
|
|
this.payTypeDefs = defs;
|
|
|
|
|
|
const nextCostMap = {};
|
|
|
|
|
|
this.payTypeDefs.forEach((d) => {
|
2025-11-28 15:30:36 +08:00
|
|
|
|
// 保留已输入的数值;否则置空
|
2025-12-12 15:33:23 +08:00
|
|
|
|
nextCostMap[d.key] =
|
|
|
|
|
|
(this.form.costMap && this.form.costMap[d.key]) || "";
|
|
|
|
|
|
});
|
|
|
|
|
|
this.form.costMap = nextCostMap;
|
2025-11-28 15:30:36 +08:00
|
|
|
|
}
|
|
|
|
|
|
} catch (e) {
|
|
|
|
|
|
// 忽略错误,维持当前 payTypeDefs
|
|
|
|
|
|
}
|
|
|
|
|
|
},
|
2025-11-21 16:23:46 +08:00
|
|
|
|
/**
|
|
|
|
|
|
* ASIC 模式:出售机器数量输入,仅允许 0-9999 的整数
|
|
|
|
|
|
*/
|
|
|
|
|
|
handleSellCountInput() {
|
2025-12-12 15:33:23 +08:00
|
|
|
|
let v = String(this.form.sellCount ?? "");
|
2025-11-21 16:23:46 +08:00
|
|
|
|
// 仅数字
|
2025-12-12 15:33:23 +08:00
|
|
|
|
v = v.replace(/\D/g, "");
|
2025-11-21 16:23:46 +08:00
|
|
|
|
// 限制最多4位
|
2025-12-12 15:33:23 +08:00
|
|
|
|
if (v.length > 4) v = v.slice(0, 4);
|
2025-11-21 16:23:46 +08:00
|
|
|
|
// 限制最大 9999
|
|
|
|
|
|
if (v) {
|
2025-12-12 15:33:23 +08:00
|
|
|
|
const n = Number(v);
|
|
|
|
|
|
if (n > 9999) v = "9999";
|
2025-11-21 16:23:46 +08:00
|
|
|
|
}
|
2025-12-12 15:33:23 +08:00
|
|
|
|
this.form.sellCount = v;
|
2025-11-21 16:23:46 +08:00
|
|
|
|
},
|
|
|
|
|
|
handleSellCountBlur() {
|
2025-12-12 15:33:23 +08:00
|
|
|
|
const raw = String(this.form.sellCount ?? "");
|
|
|
|
|
|
if (raw === "") return;
|
|
|
|
|
|
const n = Number(raw);
|
2025-11-21 16:23:46 +08:00
|
|
|
|
if (!Number.isInteger(n) || n < 0 || n > 9999) {
|
2025-12-12 15:33:23 +08:00
|
|
|
|
this.$message.warning("出售机器数量需为 0-9999 的整数");
|
|
|
|
|
|
this.form.sellCount = "";
|
2025-11-21 16:23:46 +08:00
|
|
|
|
}
|
|
|
|
|
|
},
|
|
|
|
|
|
/**
|
2025-12-12 15:33:23 +08:00
|
|
|
|
* 矿机种类变更
|
2025-11-21 16:23:46 +08:00
|
|
|
|
* @param {string} val
|
|
|
|
|
|
*/
|
|
|
|
|
|
handleMachineCategoryChange(val) {
|
2025-12-12 15:33:23 +08:00
|
|
|
|
// GPU 引导内容已直接显示在页面上,无需弹窗
|
2025-11-21 16:23:46 +08:00
|
|
|
|
},
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 下载 GPU 客户端
|
|
|
|
|
|
*/
|
2025-12-12 15:33:23 +08:00
|
|
|
|
handleDownloadClient(types) {
|
2025-11-28 15:30:36 +08:00
|
|
|
|
// 走后端接口下载客户端程序
|
2025-12-05 16:24:20 +08:00
|
|
|
|
|
2025-12-12 15:33:23 +08:00
|
|
|
|
let userEmail = JSON.parse(localStorage.getItem("leasEmail"));
|
2025-12-05 16:24:20 +08:00
|
|
|
|
if (!userEmail) {
|
|
|
|
|
|
// 弹出确认框,用户确认后跳转到外部网站
|
2025-12-12 15:33:23 +08:00
|
|
|
|
this.$confirm("获取用户信息失败,请重新进入网站?", "提示", {
|
|
|
|
|
|
confirmButtonText: "确认",
|
|
|
|
|
|
cancelButtonText: "取消",
|
|
|
|
|
|
type: "warning",
|
2025-11-28 15:30:36 +08:00
|
|
|
|
})
|
2025-12-12 15:33:23 +08:00
|
|
|
|
.then(() => {
|
|
|
|
|
|
// 用户点击确认,跳转到外部网站
|
|
|
|
|
|
window.open("https://test.m2pool.com/", "_blank");
|
|
|
|
|
|
})
|
|
|
|
|
|
.catch(() => {
|
|
|
|
|
|
// 用户点击取消,不执行任何操作
|
|
|
|
|
|
});
|
|
|
|
|
|
return;
|
2025-12-05 16:24:20 +08:00
|
|
|
|
}
|
2025-12-12 15:33:23 +08:00
|
|
|
|
console.log(userEmail, "userEmail");
|
2025-12-05 16:24:20 +08:00
|
|
|
|
|
2025-12-12 15:33:23 +08:00
|
|
|
|
this.downloadUrl = ` ${request.defaults.baseURL}/lease/user/downloadClient?userEmail=${userEmail}&type=${types}`;
|
|
|
|
|
|
let a = document.createElement(`a`);
|
|
|
|
|
|
a.href = this.downloadUrl;
|
|
|
|
|
|
a.click();
|
2025-11-21 16:23:46 +08:00
|
|
|
|
},
|
|
|
|
|
|
/**
|
|
|
|
|
|
* GPU 客户端已启动:跳转至商品列表
|
|
|
|
|
|
*/
|
|
|
|
|
|
handleGpuClientStarted() {
|
2025-11-28 15:30:36 +08:00
|
|
|
|
// 跳转到“个人中心-商品列表”页面
|
2025-12-12 15:33:23 +08:00
|
|
|
|
this.$router.push("/account/products");
|
2025-11-21 16:23:46 +08:00
|
|
|
|
},
|
2025-11-07 16:30:03 +08:00
|
|
|
|
/** 统一售价校验:多结算币种时跳过,单价时按 USDT 校验 */
|
|
|
|
|
|
validateCost(rule, value, callback) {
|
2025-11-28 15:30:36 +08:00
|
|
|
|
// 多支付方式:逐个校验 costMap
|
2025-11-07 16:30:03 +08:00
|
|
|
|
if (Array.isArray(this.payTypeDefs) && this.payTypeDefs.length > 0) {
|
2025-12-12 15:33:23 +08:00
|
|
|
|
return this.validateCostMap(rule, value, callback);
|
|
|
|
|
|
}
|
|
|
|
|
|
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;
|
2025-11-07 16:30:03 +08:00
|
|
|
|
}
|
2025-12-12 15:33:23 +08:00
|
|
|
|
callback();
|
2025-11-07 16:30:03 +08:00
|
|
|
|
},
|
2025-11-28 15:30:36 +08:00
|
|
|
|
// 多支付方式下的价格校验:要求每个支付方式都需填写有效价格
|
|
|
|
|
|
validateCostMap(rule, value, callback) {
|
|
|
|
|
|
try {
|
2025-12-12 15:33:23 +08:00
|
|
|
|
const defs = Array.isArray(this.payTypeDefs) ? this.payTypeDefs : [];
|
|
|
|
|
|
if (!defs.length) {
|
|
|
|
|
|
callback();
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
const pattern = /^\d{1,12}(\.\d{1,2})?$/;
|
2025-11-28 15:30:36 +08:00
|
|
|
|
for (let i = 0; i < defs.length; i += 1) {
|
2025-12-12 15:33:23 +08:00
|
|
|
|
const d = defs[i];
|
|
|
|
|
|
const key = d.key;
|
|
|
|
|
|
const v =
|
|
|
|
|
|
this.form && this.form.costMap
|
|
|
|
|
|
? String(this.form.costMap[key] || "")
|
|
|
|
|
|
: "";
|
|
|
|
|
|
if (!v) {
|
|
|
|
|
|
callback(new Error(`请填写 ${d.label} 的价格`));
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
if (!pattern.test(v)) {
|
|
|
|
|
|
callback(new Error(`${d.label} 价格整数最多12位,小数最多2位`));
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
if (Number(v) <= 0) {
|
|
|
|
|
|
callback(new Error(`${d.label} 价格必须大于0`));
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
2025-11-28 15:30:36 +08:00
|
|
|
|
}
|
2025-12-12 15:33:23 +08:00
|
|
|
|
callback();
|
2025-11-28 15:30:36 +08:00
|
|
|
|
} catch (e) {
|
2025-12-12 15:33:23 +08:00
|
|
|
|
callback(new Error("价格填写有误,请检查"));
|
2025-11-28 15:30:36 +08:00
|
|
|
|
}
|
|
|
|
|
|
},
|
2025-11-07 16:30:03 +08:00
|
|
|
|
/** 解析路由参数中的支付方式,生成标准定义 */
|
|
|
|
|
|
initPayTypesFromRoute() {
|
2025-12-12 15:33:23 +08:00
|
|
|
|
this.payTypeDefs = [];
|
2025-11-07 16:30:03 +08:00
|
|
|
|
try {
|
2025-12-12 15:33:23 +08:00
|
|
|
|
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 });
|
|
|
|
|
|
});
|
2025-11-07 16:30:03 +08:00
|
|
|
|
// 去重
|
2025-12-12 15:33:23 +08:00
|
|
|
|
const map = new Map();
|
|
|
|
|
|
defs.forEach((d) => {
|
|
|
|
|
|
if (!map.has(d.key)) map.set(d.key, d);
|
|
|
|
|
|
});
|
|
|
|
|
|
this.payTypeDefs = Array.from(map.values());
|
2025-11-07 16:30:03 +08:00
|
|
|
|
// 初始化统一售价映射
|
2025-12-12 15:33:23 +08:00
|
|
|
|
const initCostMap = {};
|
|
|
|
|
|
this.payTypeDefs.forEach((d) => {
|
|
|
|
|
|
initCostMap[d.key] = "";
|
|
|
|
|
|
});
|
|
|
|
|
|
this.form.costMap = initCostMap;
|
|
|
|
|
|
this.lastCostMapBaseline = { ...initCostMap };
|
2025-11-07 16:30:03 +08:00
|
|
|
|
} catch (e) {
|
2025-12-12 15:33:23 +08:00
|
|
|
|
this.payTypeDefs = [];
|
2025-11-07 16:30:03 +08:00
|
|
|
|
}
|
|
|
|
|
|
},
|
2025-09-26 16:40:38 +08:00
|
|
|
|
handleBack() {
|
2025-12-12 15:33:23 +08:00
|
|
|
|
this.$router.back();
|
2025-09-26 16:40:38 +08:00
|
|
|
|
},
|
|
|
|
|
|
handleNumeric(key) {
|
|
|
|
|
|
// 仅允许数字和一个小数点
|
2025-12-12 15:33:23 +08:00
|
|
|
|
let v = String(this.form[key] ?? "");
|
2025-09-26 16:40:38 +08:00
|
|
|
|
// 清理非法字符
|
2025-12-12 15:33:23 +08:00
|
|
|
|
v = v.replace(/[^0-9.]/g, "");
|
2025-09-26 16:40:38 +08:00
|
|
|
|
// 保留第一个小数点
|
2025-12-12 15:33:23 +08:00
|
|
|
|
const firstDot = v.indexOf(".");
|
2025-09-26 16:40:38 +08:00
|
|
|
|
if (firstDot !== -1) {
|
2025-12-12 15:33:23 +08:00
|
|
|
|
v = v.slice(0, firstDot + 1) + v.slice(firstDot + 1).replace(/\./g, "");
|
2025-09-26 16:40:38 +08:00
|
|
|
|
}
|
2025-12-12 15:33:23 +08:00
|
|
|
|
const endsWithDot = v.endsWith(".");
|
|
|
|
|
|
if (key === "cost") {
|
2025-09-26 16:40:38 +08:00
|
|
|
|
// 成本:整数最多12位,小数最多2位
|
2025-12-12 15:33:23 +08:00
|
|
|
|
const parts = v.split(".");
|
|
|
|
|
|
let intPart = parts[0] || "";
|
|
|
|
|
|
let decPart = parts[1] || "";
|
2025-09-26 16:40:38 +08:00
|
|
|
|
if (intPart.length > 12) {
|
2025-12-12 15:33:23 +08:00
|
|
|
|
intPart = intPart.slice(0, 12);
|
2025-09-26 16:40:38 +08:00
|
|
|
|
}
|
|
|
|
|
|
if (decPart) {
|
2025-12-12 15:33:23 +08:00
|
|
|
|
decPart = decPart.slice(0, 2);
|
2025-09-26 16:40:38 +08:00
|
|
|
|
}
|
2025-12-12 15:33:23 +08:00
|
|
|
|
v = decPart.length
|
|
|
|
|
|
? `${intPart}.${decPart}`
|
|
|
|
|
|
: endsWithDot
|
|
|
|
|
|
? `${intPart}.`
|
|
|
|
|
|
: intPart;
|
|
|
|
|
|
} else if (key === "powerDissipation" || key === "theoryPower") {
|
2025-09-26 16:40:38 +08:00
|
|
|
|
// 功耗/理论算力:整数最多6位,小数最多4位
|
2025-12-12 15:33:23 +08:00
|
|
|
|
const parts = v.split(".");
|
|
|
|
|
|
let intPart = parts[0] || "";
|
|
|
|
|
|
let decPart = parts[1] || "";
|
2025-09-26 16:40:38 +08:00
|
|
|
|
if (intPart.length > 6) {
|
2025-12-12 15:33:23 +08:00
|
|
|
|
intPart = intPart.slice(0, 6);
|
2025-09-26 16:40:38 +08:00
|
|
|
|
}
|
|
|
|
|
|
if (decPart) {
|
2025-12-12 15:33:23 +08:00
|
|
|
|
decPart = decPart.slice(0, 4);
|
2025-09-26 16:40:38 +08:00
|
|
|
|
}
|
2025-12-12 15:33:23 +08:00
|
|
|
|
v = decPart.length
|
|
|
|
|
|
? `${intPart}.${decPart}`
|
|
|
|
|
|
: endsWithDot
|
|
|
|
|
|
? `${intPart}.`
|
|
|
|
|
|
: intPart;
|
|
|
|
|
|
} else if (key === "maxLeaseDays") {
|
2025-10-20 10:15:13 +08:00
|
|
|
|
// 最大租赁天数:仅整数,范围 1-365,输入阶段限制为最多3位数字
|
2025-12-12 15:33:23 +08:00
|
|
|
|
v = v.replace(/\D/g, "");
|
|
|
|
|
|
if (v.length > 3) v = v.slice(0, 3);
|
|
|
|
|
|
this.form[key] = v;
|
|
|
|
|
|
this.syncMaxLeaseDaysToRows();
|
|
|
|
|
|
return;
|
2025-09-26 16:40:38 +08:00
|
|
|
|
} else {
|
|
|
|
|
|
// 其他:最多6位小数(保持原有逻辑)
|
|
|
|
|
|
if (firstDot !== -1) {
|
2025-12-12 15:33:23 +08:00
|
|
|
|
const [intPart, decPart] = v.split(".");
|
|
|
|
|
|
v = intPart + "." + (decPart ? decPart.slice(0, 6) : "");
|
2025-09-26 16:40:38 +08:00
|
|
|
|
}
|
|
|
|
|
|
}
|
2025-12-12 15:33:23 +08:00
|
|
|
|
this.form[key] = v;
|
2025-09-26 16:40:38 +08:00
|
|
|
|
},
|
2025-11-07 16:30:03 +08:00
|
|
|
|
/** 顶部多结算币种统一售价输入 */
|
|
|
|
|
|
handleCostMapInput(key, val) {
|
|
|
|
|
|
// 价格输入:整数最多12位,小数最多2位;允许尾随小数点
|
2025-12-12 15:33:23 +08:00
|
|
|
|
let v = String(val ?? this.form.costMap[key] ?? "");
|
|
|
|
|
|
v = v.replace(/[^0-9.]/g, "");
|
|
|
|
|
|
const firstDot = v.indexOf(".");
|
2025-11-07 16:30:03 +08:00
|
|
|
|
if (firstDot !== -1) {
|
2025-12-12 15:33:23 +08:00
|
|
|
|
v = v.slice(0, firstDot + 1) + v.slice(firstDot + 1).replace(/\./g, "");
|
2025-11-07 16:30:03 +08:00
|
|
|
|
}
|
2025-12-12 15:33:23 +08:00
|
|
|
|
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);
|
2025-11-07 16:30:03 +08:00
|
|
|
|
},
|
2025-09-26 16:40:38 +08:00
|
|
|
|
/**
|
|
|
|
|
|
* 顶部矿机型号输入:限制20字符
|
|
|
|
|
|
*/
|
|
|
|
|
|
handleTypeInput() {
|
2025-12-12 15:33:23 +08:00
|
|
|
|
if (typeof this.form.type === "string" && this.form.type.length > 20) {
|
|
|
|
|
|
this.form.type = this.form.type.slice(0, 20);
|
2025-09-26 16:40:38 +08:00
|
|
|
|
}
|
|
|
|
|
|
},
|
|
|
|
|
|
syncCostToRows() {
|
2025-12-12 15:33:23 +08:00
|
|
|
|
const newCost = Number(this.form.cost);
|
2025-09-26 16:40:38 +08:00
|
|
|
|
if (!Number.isFinite(newCost)) {
|
2025-12-12 15:33:23 +08:00
|
|
|
|
return;
|
2025-09-26 16:40:38 +08:00
|
|
|
|
}
|
2025-12-12 15:33:23 +08:00
|
|
|
|
const oldBaseline = this.lastCostBaseline;
|
|
|
|
|
|
this.selectedMachineRows = this.selectedMachineRows.map((row) => {
|
|
|
|
|
|
const priceNum = Number(row.price);
|
2025-09-26 16:40:38 +08:00
|
|
|
|
if (!Number.isFinite(priceNum) || priceNum === oldBaseline) {
|
2025-12-12 15:33:23 +08:00
|
|
|
|
return { ...row, price: newCost };
|
2025-09-26 16:40:38 +08:00
|
|
|
|
}
|
2025-12-12 15:33:23 +08:00
|
|
|
|
return row;
|
|
|
|
|
|
});
|
|
|
|
|
|
this.lastCostBaseline = newCost;
|
2025-09-26 16:40:38 +08:00
|
|
|
|
},
|
|
|
|
|
|
updateMachineType() {
|
2025-11-21 16:23:46 +08:00
|
|
|
|
// 仅记录最近一次外层输入,避免无用同步逻辑
|
2025-12-12 15:33:23 +08:00
|
|
|
|
this.lastTypeBaseline = this.form.type;
|
2025-09-26 16:40:38 +08:00
|
|
|
|
},
|
2025-10-20 10:15:13 +08:00
|
|
|
|
/**
|
|
|
|
|
|
* 行内功耗输入(限制:整数最多6位,小数最多4位)
|
|
|
|
|
|
*/
|
|
|
|
|
|
handleRowPowerDissipationInput(index) {
|
2025-12-12 15:33:23 +08:00
|
|
|
|
let v = String(this.selectedMachineRows[index].powerDissipation ?? "");
|
|
|
|
|
|
v = v.replace(/[^0-9.]/g, "");
|
|
|
|
|
|
const firstDot = v.indexOf(".");
|
2025-10-20 10:15:13 +08:00
|
|
|
|
if (firstDot !== -1) {
|
2025-12-12 15:33:23 +08:00
|
|
|
|
v = v.slice(0, firstDot + 1) + v.slice(firstDot + 1).replace(/\./g, "");
|
2025-10-20 10:15:13 +08:00
|
|
|
|
}
|
2025-12-12 15:33:23 +08:00
|
|
|
|
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.selectedMachineRows[index], "powerDissipation", v);
|
2025-10-20 10:15:13 +08:00
|
|
|
|
},
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 行内功耗校验
|
|
|
|
|
|
*/
|
|
|
|
|
|
handleRowPowerDissipationBlur(index) {
|
2025-12-12 15:33:23 +08:00
|
|
|
|
const raw = String(
|
|
|
|
|
|
this.selectedMachineRows[index].powerDissipation ?? ""
|
|
|
|
|
|
);
|
|
|
|
|
|
const pattern = /^\d{1,6}(\.\d{1,4})?$/;
|
2025-10-20 10:15:13 +08:00
|
|
|
|
if (!raw || Number(raw) <= 0 || !pattern.test(raw)) {
|
2025-12-12 15:33:23 +08:00
|
|
|
|
this.$message.warning("功耗需大于0,整数最多6位,小数最多4位");
|
|
|
|
|
|
this.$set(this.selectedMachineRows[index], "powerDissipation", "");
|
2025-10-20 10:15:13 +08:00
|
|
|
|
}
|
|
|
|
|
|
},
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 行内理论算力输入(限制:整数最多6位,小数最多4位)
|
|
|
|
|
|
*/
|
|
|
|
|
|
handleRowTheoryPowerInput(index) {
|
2025-12-12 15:33:23 +08:00
|
|
|
|
let v = String(this.selectedMachineRows[index].theoryPower ?? "");
|
|
|
|
|
|
v = v.replace(/[^0-9.]/g, "");
|
|
|
|
|
|
const firstDot = v.indexOf(".");
|
2025-10-20 10:15:13 +08:00
|
|
|
|
if (firstDot !== -1) {
|
2025-12-12 15:33:23 +08:00
|
|
|
|
v = v.slice(0, firstDot + 1) + v.slice(firstDot + 1).replace(/\./g, "");
|
2025-10-20 10:15:13 +08:00
|
|
|
|
}
|
2025-12-12 15:33:23 +08:00
|
|
|
|
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.selectedMachineRows[index], "theoryPower", v);
|
2025-10-20 10:15:13 +08:00
|
|
|
|
},
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 行内理论算力校验
|
|
|
|
|
|
*/
|
|
|
|
|
|
handleRowTheoryPowerBlur(index) {
|
2025-12-12 15:33:23 +08:00
|
|
|
|
const raw = String(this.selectedMachineRows[index].theoryPower ?? "");
|
|
|
|
|
|
const pattern = /^\d{1,6}(\.\d{1,4})?$/;
|
2025-10-20 10:15:13 +08:00
|
|
|
|
if (!raw || Number(raw) <= 0 || !pattern.test(raw)) {
|
2025-12-12 15:33:23 +08:00
|
|
|
|
this.$message.warning("理论算力需大于0,整数最多6位,小数最多4位");
|
|
|
|
|
|
this.$set(this.selectedMachineRows[index], "theoryPower", "");
|
2025-10-20 10:15:13 +08:00
|
|
|
|
}
|
|
|
|
|
|
},
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 行内单位变更
|
|
|
|
|
|
*/
|
|
|
|
|
|
handleRowUnitChange(index, value) {
|
2025-12-12 15:33:23 +08:00
|
|
|
|
this.$set(this.selectedMachineRows[index], "unit", value);
|
2025-10-20 10:15:13 +08:00
|
|
|
|
},
|
|
|
|
|
|
syncMaxLeaseDaysToRows() {
|
2025-12-12 15:33:23 +08:00
|
|
|
|
const raw = this.form.maxLeaseDays;
|
|
|
|
|
|
const n = Number(raw);
|
|
|
|
|
|
if (!Number.isInteger(n)) return;
|
|
|
|
|
|
const oldBaseline = this.lastMaxLeaseDaysBaseline;
|
|
|
|
|
|
this.selectedMachineRows = this.selectedMachineRows.map((row) => {
|
|
|
|
|
|
const rowNum = Number(row.maxLeaseDays);
|
2025-10-20 10:15:13 +08:00
|
|
|
|
if (!Number.isInteger(rowNum) || rowNum === oldBaseline) {
|
2025-12-12 15:33:23 +08:00
|
|
|
|
return { ...row, maxLeaseDays: n };
|
2025-10-20 10:15:13 +08:00
|
|
|
|
}
|
2025-12-12 15:33:23 +08:00
|
|
|
|
return row;
|
|
|
|
|
|
});
|
|
|
|
|
|
this.lastMaxLeaseDaysBaseline = n;
|
2025-10-20 10:15:13 +08:00
|
|
|
|
},
|
|
|
|
|
|
handleRowMaxLeaseDaysInput(index) {
|
2025-12-12 15:33:23 +08:00
|
|
|
|
let v = String(this.selectedMachineRows[index].maxLeaseDays ?? "");
|
|
|
|
|
|
v = v.replace(/\D/g, "");
|
|
|
|
|
|
if (v.length > 3) v = v.slice(0, 3);
|
|
|
|
|
|
this.$set(this.selectedMachineRows[index], "maxLeaseDays", v);
|
2025-10-20 10:15:13 +08:00
|
|
|
|
},
|
|
|
|
|
|
handleRowMaxLeaseDaysBlur(index) {
|
2025-12-12 15:33:23 +08:00
|
|
|
|
const raw = String(this.selectedMachineRows[index].maxLeaseDays ?? "");
|
2025-10-20 10:15:13 +08:00
|
|
|
|
if (!/^\d{1,3}$/.test(raw)) {
|
2025-12-12 15:33:23 +08:00
|
|
|
|
this.$message.warning("最大租赁天数需为 1-365 的整数");
|
|
|
|
|
|
this.$set(this.selectedMachineRows[index], "maxLeaseDays", "");
|
|
|
|
|
|
return;
|
2025-10-20 10:15:13 +08:00
|
|
|
|
}
|
2025-12-12 15:33:23 +08:00
|
|
|
|
const n = Number(raw);
|
2025-10-20 10:15:13 +08:00
|
|
|
|
if (!Number.isInteger(n) || n < 1 || n > 365) {
|
2025-12-12 15:33:23 +08:00
|
|
|
|
this.$message.warning("最大租赁天数需为 1-365 的整数");
|
|
|
|
|
|
this.$set(this.selectedMachineRows[index], "maxLeaseDays", "");
|
2025-10-20 10:15:13 +08:00
|
|
|
|
}
|
|
|
|
|
|
},
|
2025-09-26 16:40:38 +08:00
|
|
|
|
handleRowPriceInput(index) {
|
|
|
|
|
|
// 价格输入:整数最多12位,小数最多2位;允许尾随小数点
|
2025-12-12 15:33:23 +08:00
|
|
|
|
let v = String(this.selectedMachineRows[index].price ?? "");
|
|
|
|
|
|
v = v.replace(/[^0-9.]/g, "");
|
|
|
|
|
|
const firstDot = v.indexOf(".");
|
2025-09-26 16:40:38 +08:00
|
|
|
|
if (firstDot !== -1) {
|
2025-12-12 15:33:23 +08:00
|
|
|
|
v = v.slice(0, firstDot + 1) + v.slice(firstDot + 1).replace(/\./g, "");
|
2025-09-26 16:40:38 +08:00
|
|
|
|
}
|
2025-12-12 15:33:23 +08:00
|
|
|
|
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.selectedMachineRows[index], "price", v);
|
2025-09-26 16:40:38 +08:00
|
|
|
|
},
|
2025-11-07 16:30:03 +08:00
|
|
|
|
/** 行内多结算币种价格输入 */
|
|
|
|
|
|
handleRowPriceMapInput(index, key) {
|
|
|
|
|
|
// 价格输入:整数最多12位,小数最多2位;允许尾随小数点
|
2025-12-12 15:33:23 +08:00
|
|
|
|
const row = this.selectedMachineRows[index];
|
|
|
|
|
|
const map = { ...(row.priceMap || {}) };
|
|
|
|
|
|
let v = String(map[key] ?? "");
|
|
|
|
|
|
v = v.replace(/[^0-9.]/g, "");
|
|
|
|
|
|
const firstDot = v.indexOf(".");
|
2025-11-07 16:30:03 +08:00
|
|
|
|
if (firstDot !== -1) {
|
2025-12-12 15:33:23 +08:00
|
|
|
|
v = v.slice(0, firstDot + 1) + v.slice(firstDot + 1).replace(/\./g, "");
|
2025-11-07 16:30:03 +08:00
|
|
|
|
}
|
2025-12-12 15:33:23 +08:00
|
|
|
|
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);
|
2025-11-07 16:30:03 +08:00
|
|
|
|
},
|
|
|
|
|
|
handleRowPriceMapBlur(index, key) {
|
2025-12-12 15:33:23 +08:00
|
|
|
|
const row = this.selectedMachineRows[index];
|
|
|
|
|
|
const raw = String((row.priceMap && row.priceMap[key]) ?? "");
|
|
|
|
|
|
const pattern = /^\d{1,12}(\.\d{1,2})?$/;
|
2025-11-07 16:30:03 +08:00
|
|
|
|
if (!raw || Number(raw) <= 0 || !pattern.test(raw)) {
|
2025-12-12 15:33:23 +08:00
|
|
|
|
this.$message.warning("价格必须大于0,整数最多12位,小数最多2位");
|
|
|
|
|
|
const map = { ...(row.priceMap || {}) };
|
|
|
|
|
|
map[key] = "";
|
|
|
|
|
|
this.$set(this.selectedMachineRows[index], "priceMap", map);
|
2025-11-07 16:30:03 +08:00
|
|
|
|
}
|
|
|
|
|
|
},
|
2025-09-26 16:40:38 +08:00
|
|
|
|
handleRowPriceBlur(index) {
|
2025-12-12 15:33:23 +08:00
|
|
|
|
const raw = String(this.selectedMachineRows[index].price ?? "");
|
|
|
|
|
|
const pattern = /^\d{1,12}(\.\d{1,2})?$/;
|
2025-09-26 16:40:38 +08:00
|
|
|
|
if (!raw || Number(raw) <= 0 || !pattern.test(raw)) {
|
2025-12-12 15:33:23 +08:00
|
|
|
|
this.$message.warning("价格必须大于0,整数最多12位,小数最多2位");
|
|
|
|
|
|
this.$set(this.selectedMachineRows[index], "price", "");
|
2025-09-26 16:40:38 +08:00
|
|
|
|
}
|
|
|
|
|
|
},
|
|
|
|
|
|
handleRowTypeInput(index) {
|
|
|
|
|
|
// 处理矿机型号输入
|
2025-12-12 15:33:23 +08:00
|
|
|
|
const raw = String(this.selectedMachineRows[index].type || "");
|
|
|
|
|
|
const v = raw.length > 20 ? raw.slice(0, 20) : raw;
|
|
|
|
|
|
this.$set(this.selectedMachineRows[index], "type", v);
|
2025-09-26 16:40:38 +08:00
|
|
|
|
},
|
|
|
|
|
|
handleRowTypeBlur(index) {
|
2025-12-12 15:33:23 +08:00
|
|
|
|
const raw = this.selectedMachineRows[index].type;
|
|
|
|
|
|
const isOnlySpaces = (v) =>
|
|
|
|
|
|
typeof v === "string" && v.length > 0 && v.trim().length === 0;
|
2025-09-26 16:40:38 +08:00
|
|
|
|
if (isOnlySpaces(raw)) {
|
2025-12-12 15:33:23 +08:00
|
|
|
|
this.$message.warning("矿机型号不能全是空格");
|
|
|
|
|
|
this.$set(this.selectedMachineRows[index], "type", "");
|
2025-09-26 16:40:38 +08:00
|
|
|
|
}
|
|
|
|
|
|
},
|
|
|
|
|
|
handleToggleState(index) {
|
|
|
|
|
|
// 切换上下架状态:0上架,1下架
|
2025-12-12 15:33:23 +08:00
|
|
|
|
const currentState = this.selectedMachineRows[index].state;
|
|
|
|
|
|
this.$set(
|
|
|
|
|
|
this.selectedMachineRows[index],
|
|
|
|
|
|
"state",
|
|
|
|
|
|
currentState === 0 ? 1 : 0
|
|
|
|
|
|
);
|
2025-09-26 16:40:38 +08:00
|
|
|
|
},
|
|
|
|
|
|
async fetchMiners() {
|
2025-12-12 15:33:23 +08:00
|
|
|
|
this.minersLoading = true;
|
2025-09-26 16:40:38 +08:00
|
|
|
|
try {
|
|
|
|
|
|
// 按商品币种筛选挖矿账户
|
2025-12-12 15:33:23 +08:00
|
|
|
|
const res = await getUserMinersList({ coin: this.form.coin || "" });
|
|
|
|
|
|
const data = res?.data;
|
|
|
|
|
|
let list = [];
|
2025-09-26 16:40:38 +08:00
|
|
|
|
if (Array.isArray(data)) {
|
2025-12-12 15:33:23 +08:00
|
|
|
|
list = data;
|
|
|
|
|
|
} else if (data && typeof data === "object") {
|
2025-09-26 16:40:38 +08:00
|
|
|
|
// 现在的结构是 { coin: [ { user, coin }, ... ], coin2: [...] }
|
2025-12-12 15:33:23 +08:00
|
|
|
|
Object.keys(data).forEach((coinKey) => {
|
|
|
|
|
|
const arr = Array.isArray(data[coinKey]) ? data[coinKey] : [];
|
|
|
|
|
|
arr.forEach((item) => {
|
2025-09-26 16:40:38 +08:00
|
|
|
|
if (item && item.user && item.coin) {
|
2025-12-12 15:33:23 +08:00
|
|
|
|
list.push({
|
|
|
|
|
|
user: item.user,
|
|
|
|
|
|
coin: item.coin,
|
|
|
|
|
|
miner: item.miner || null,
|
|
|
|
|
|
});
|
2025-09-26 16:40:38 +08:00
|
|
|
|
}
|
2025-12-12 15:33:23 +08:00
|
|
|
|
});
|
|
|
|
|
|
});
|
2025-09-26 16:40:38 +08:00
|
|
|
|
} else if (data && data.additionalProperties1) {
|
2025-12-12 15:33:23 +08:00
|
|
|
|
list = [data.additionalProperties1];
|
2025-09-26 16:40:38 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 如页面带了 product coin,则仅展示该币种的账户
|
|
|
|
|
|
if (this.form.coin) {
|
2025-12-12 15:33:23 +08:00
|
|
|
|
list = list.filter((i) => i.coin === this.form.coin);
|
2025-09-26 16:40:38 +08:00
|
|
|
|
}
|
2025-12-12 15:33:23 +08:00
|
|
|
|
this.miners = list;
|
2025-09-26 16:40:38 +08:00
|
|
|
|
} catch (e) {
|
2025-12-12 15:33:23 +08:00
|
|
|
|
console.error("获取挖矿账户失败", e);
|
2025-09-26 16:40:38 +08:00
|
|
|
|
} finally {
|
2025-12-12 15:33:23 +08:00
|
|
|
|
this.minersLoading = false;
|
2025-09-26 16:40:38 +08:00
|
|
|
|
}
|
|
|
|
|
|
},
|
|
|
|
|
|
async handleMinerChange(val) {
|
2025-12-12 15:33:23 +08:00
|
|
|
|
this.selectedMachines = [];
|
2025-09-26 16:40:38 +08:00
|
|
|
|
if (!val) {
|
2025-12-12 15:33:23 +08:00
|
|
|
|
this.machineOptions = [];
|
|
|
|
|
|
return;
|
2025-09-26 16:40:38 +08:00
|
|
|
|
}
|
2025-12-12 15:33:23 +08:00
|
|
|
|
const [user, coin] = val.split("|");
|
|
|
|
|
|
this.machinesLoading = true;
|
2025-09-26 16:40:38 +08:00
|
|
|
|
try {
|
|
|
|
|
|
// 按照API文档要求,传递 userMinerVo 对象
|
|
|
|
|
|
const userMinerVo = {
|
|
|
|
|
|
coin: coin,
|
2025-12-12 15:33:23 +08:00
|
|
|
|
user: user,
|
|
|
|
|
|
};
|
|
|
|
|
|
const res = await getUserMachineList(userMinerVo);
|
|
|
|
|
|
const data = res?.data || [];
|
|
|
|
|
|
this.machineOptions = Array.isArray(data) ? data : [];
|
|
|
|
|
|
|
2025-09-26 16:40:38 +08:00
|
|
|
|
// 调试信息
|
2025-12-12 15:33:23 +08:00
|
|
|
|
console.log("选择挖矿账户:", { user, coin });
|
|
|
|
|
|
console.log("获取机器列表响应:", res);
|
|
|
|
|
|
console.log("机器列表数据:", this.machineOptions);
|
2025-09-26 16:40:38 +08:00
|
|
|
|
} catch (e) {
|
2025-12-12 15:33:23 +08:00
|
|
|
|
console.error("获取机器列表失败", e);
|
2025-09-26 16:40:38 +08:00
|
|
|
|
} finally {
|
2025-12-12 15:33:23 +08:00
|
|
|
|
this.machinesLoading = false;
|
2025-09-26 16:40:38 +08:00
|
|
|
|
}
|
|
|
|
|
|
},
|
|
|
|
|
|
async handleSave() {
|
|
|
|
|
|
// 表单校验(除矿机型号外其他必填)
|
|
|
|
|
|
try {
|
2025-12-12 15:33:23 +08:00
|
|
|
|
const ok = await this.$refs.machineForm.validate();
|
2025-09-26 16:40:38 +08:00
|
|
|
|
if (!ok) {
|
2025-12-12 15:33:23 +08:00
|
|
|
|
return;
|
2025-09-26 16:40:38 +08:00
|
|
|
|
}
|
|
|
|
|
|
} catch (e) {
|
2025-12-12 15:33:23 +08:00
|
|
|
|
return;
|
2025-09-26 16:40:38 +08:00
|
|
|
|
}
|
2025-11-28 15:30:36 +08:00
|
|
|
|
// if (!this.form.productId) {
|
|
|
|
|
|
// this.$message.warning('缺少商品ID')
|
|
|
|
|
|
// return
|
|
|
|
|
|
// }
|
2025-11-21 16:23:46 +08:00
|
|
|
|
// 现在统一按出售数量提交(GPU 模式不在本页提交)
|
|
|
|
|
|
{
|
|
|
|
|
|
// ASIC:校验出售机器数量(允许 0-9999;为 0 则提示)
|
2025-12-12 15:33:23 +08:00
|
|
|
|
const raw = String(this.form.sellCount ?? "");
|
|
|
|
|
|
if (raw === "") {
|
|
|
|
|
|
this.$message.warning("请输入出售机器数量");
|
|
|
|
|
|
return;
|
2025-11-21 16:23:46 +08:00
|
|
|
|
}
|
2025-12-12 15:33:23 +08:00
|
|
|
|
const n = Number(raw);
|
2025-11-21 16:23:46 +08:00
|
|
|
|
if (!Number.isInteger(n) || n < 0 || n > 9999) {
|
2025-12-12 15:33:23 +08:00
|
|
|
|
this.$message.warning("出售机器数量需为 0-9999 的整数");
|
|
|
|
|
|
return;
|
2025-11-21 16:23:46 +08:00
|
|
|
|
}
|
|
|
|
|
|
if (n === 0) {
|
2025-12-12 15:33:23 +08:00
|
|
|
|
this.$message.warning("出售机器数量为 0,无需提交");
|
|
|
|
|
|
return;
|
2025-11-21 16:23:46 +08:00
|
|
|
|
}
|
2025-09-26 16:40:38 +08:00
|
|
|
|
}
|
|
|
|
|
|
// 校验:矿机型号不可全空格(允许为空或包含空格的正常文本)
|
2025-12-12 15:33:23 +08:00
|
|
|
|
const isOnlySpaces = (v) =>
|
|
|
|
|
|
typeof v === "string" && v.length > 0 && v.trim().length === 0;
|
2025-09-26 16:40:38 +08:00
|
|
|
|
if (isOnlySpaces(this.form.type)) {
|
2025-12-12 15:33:23 +08:00
|
|
|
|
this.$message.warning("矿机型号不能全是空格");
|
|
|
|
|
|
return;
|
2025-09-26 16:40:38 +08:00
|
|
|
|
}
|
2025-12-12 15:33:23 +08:00
|
|
|
|
const invalidTypeRowIndex = this.selectedMachineRows.findIndex((r) =>
|
|
|
|
|
|
isOnlySpaces(r.type)
|
|
|
|
|
|
);
|
2025-09-26 16:40:38 +08:00
|
|
|
|
if (invalidTypeRowIndex !== -1) {
|
2025-12-12 15:33:23 +08:00
|
|
|
|
this.$message.warning("存在行的矿机型号全是空格,请修正后再试");
|
|
|
|
|
|
return;
|
2025-09-26 16:40:38 +08:00
|
|
|
|
}
|
2025-11-21 16:23:46 +08:00
|
|
|
|
// 统一售价与最大租赁天数已在表单级校验中处理,无需逐机校验
|
2025-11-28 15:30:36 +08:00
|
|
|
|
// 组装确认数据并弹框
|
2025-12-12 15:33:23 +08:00
|
|
|
|
const coinStr = this.buildCoinCsvFromRows();
|
|
|
|
|
|
const algoStr = this.buildAlgoCsvFromRows();
|
2025-11-28 15:30:36 +08:00
|
|
|
|
this.confirmData = {
|
2025-12-12 15:33:23 +08:00
|
|
|
|
coin: coinStr || "-",
|
|
|
|
|
|
algorithm: algoStr || "-",
|
2025-11-28 15:30:36 +08:00
|
|
|
|
maxLeaseDays: this.form.maxLeaseDays,
|
|
|
|
|
|
saleNumbers: this.form.sellCount,
|
2025-12-12 15:33:23 +08:00
|
|
|
|
priceList: this.buildPriceList(),
|
|
|
|
|
|
};
|
|
|
|
|
|
this.confirmVisible = true;
|
|
|
|
|
|
},
|
2025-09-26 16:40:38 +08:00
|
|
|
|
async doSubmit() {
|
2025-12-12 15:33:23 +08:00
|
|
|
|
this.saving = true;
|
2025-09-26 16:40:38 +08:00
|
|
|
|
try {
|
2025-11-28 15:30:36 +08:00
|
|
|
|
// 统一售卖新增接口参数
|
2025-12-12 15:33:23 +08:00
|
|
|
|
const list = (this.form.coinAndAlgoList || []).map((r) => ({
|
|
|
|
|
|
coin: String(r.coin || "")
|
|
|
|
|
|
.toUpperCase()
|
|
|
|
|
|
.trim(),
|
|
|
|
|
|
algorithm: String(r.algorithm || "")
|
|
|
|
|
|
.toUpperCase()
|
|
|
|
|
|
.trim(),
|
2025-12-05 16:24:20 +08:00
|
|
|
|
theoryPower: Number(r.theoryPower) || 0,
|
2025-12-12 15:33:23 +08:00
|
|
|
|
unit: r.unit,
|
|
|
|
|
|
}));
|
2025-11-28 15:30:36 +08:00
|
|
|
|
const payload = {
|
|
|
|
|
|
// 逗号分隔(中英文逗号都兼容),统一为英文逗号并大写
|
2025-12-05 16:24:20 +08:00
|
|
|
|
coinAndAlgoList: list,
|
2025-11-21 16:23:46 +08:00
|
|
|
|
maxLeaseDays: Number(this.form.maxLeaseDays) || 0,
|
2025-11-28 15:30:36 +08:00
|
|
|
|
name: this.form.type,
|
2025-11-21 16:23:46 +08:00
|
|
|
|
powerDissipation: Number(this.form.powerDissipation) || 0,
|
2025-11-28 15:30:36 +08:00
|
|
|
|
saleNumbers: Number(this.form.sellCount) || 0,
|
2025-12-12 15:33:23 +08:00
|
|
|
|
priceList: this.buildPriceList(),
|
|
|
|
|
|
};
|
2025-11-28 15:30:36 +08:00
|
|
|
|
// 过滤空价目
|
2025-12-12 15:33:23 +08:00
|
|
|
|
payload.priceList = (payload.priceList || []).filter(
|
|
|
|
|
|
(p) => Number(p.price) > 0
|
|
|
|
|
|
);
|
2025-09-26 16:40:38 +08:00
|
|
|
|
|
2025-12-12 15:33:23 +08:00
|
|
|
|
console.log(payload, "请求参数");
|
2025-09-26 16:40:38 +08:00
|
|
|
|
|
2025-12-12 15:33:23 +08:00
|
|
|
|
const res = await addAsicMachine(payload);
|
2025-09-26 16:40:38 +08:00
|
|
|
|
if (res && (res.code === 0 || res.code === 200)) {
|
2025-10-31 14:09:58 +08:00
|
|
|
|
this.$message({
|
2025-12-19 15:40:53 +08:00
|
|
|
|
message: "创建成功",
|
2025-10-31 14:09:58 +08:00
|
|
|
|
duration: 3000,
|
|
|
|
|
|
showClose: true,
|
2025-12-12 15:33:23 +08:00
|
|
|
|
type: "success",
|
|
|
|
|
|
});
|
|
|
|
|
|
this.confirmVisible = false;
|
|
|
|
|
|
this.$router.push("/account/products");
|
|
|
|
|
|
}
|
2025-09-26 16:40:38 +08:00
|
|
|
|
} catch (e) {
|
2025-12-19 15:40:53 +08:00
|
|
|
|
console.error("创建商品失败", e);
|
|
|
|
|
|
console.log("创建失败");
|
2025-09-26 16:40:38 +08:00
|
|
|
|
} finally {
|
2025-12-12 15:33:23 +08:00
|
|
|
|
this.saving = false;
|
2025-09-26 16:40:38 +08:00
|
|
|
|
}
|
2025-12-12 15:33:23 +08:00
|
|
|
|
},
|
|
|
|
|
|
},
|
|
|
|
|
|
};
|
2025-09-26 16:40:38 +08:00
|
|
|
|
</script>
|
|
|
|
|
|
|
|
|
|
|
|
<style scoped>
|
2025-12-12 15:33:23 +08:00
|
|
|
|
.product-machine-add {
|
|
|
|
|
|
padding: 8px;
|
|
|
|
|
|
}
|
|
|
|
|
|
.header {
|
|
|
|
|
|
display: flex;
|
|
|
|
|
|
align-items: center;
|
|
|
|
|
|
gap: 12px;
|
|
|
|
|
|
margin-bottom: 8px;
|
|
|
|
|
|
}
|
|
|
|
|
|
.title {
|
|
|
|
|
|
margin: 0;
|
|
|
|
|
|
font-size: 18px;
|
|
|
|
|
|
font-weight: 600;
|
|
|
|
|
|
}
|
|
|
|
|
|
.notice-alert {
|
|
|
|
|
|
margin-bottom: 12px;
|
|
|
|
|
|
}
|
|
|
|
|
|
.notice-alert :deep(.el-alert__content) {
|
|
|
|
|
|
text-align: left;
|
|
|
|
|
|
}
|
2025-10-20 10:15:13 +08:00
|
|
|
|
.notice-alert :deep(.el-alert__title),
|
2025-12-12 15:33:23 +08:00
|
|
|
|
.notice-alert :deep(.el-alert__description) {
|
|
|
|
|
|
text-align: left;
|
|
|
|
|
|
}
|
|
|
|
|
|
.label-help {
|
|
|
|
|
|
margin-left: 4px;
|
|
|
|
|
|
color: #909399;
|
|
|
|
|
|
cursor: help;
|
|
|
|
|
|
}
|
|
|
|
|
|
.form-card {
|
|
|
|
|
|
margin-bottom: 12px;
|
|
|
|
|
|
}
|
|
|
|
|
|
.actions {
|
|
|
|
|
|
text-align: left;
|
|
|
|
|
|
}
|
2025-09-26 16:40:38 +08:00
|
|
|
|
|
|
|
|
|
|
/* 统一左对齐,控件宽度 50% */
|
|
|
|
|
|
.product-machine-add :deep(.el-form-item__content) {
|
|
|
|
|
|
justify-content: flex-start;
|
|
|
|
|
|
}
|
2025-10-20 10:15:13 +08:00
|
|
|
|
/* .product-machine-add :deep(.el-input),
|
2025-09-26 16:40:38 +08:00
|
|
|
|
.product-machine-add :deep(.el-select),
|
|
|
|
|
|
.product-machine-add :deep(.el-textarea) {
|
|
|
|
|
|
width: 50%;
|
2025-10-20 10:15:13 +08:00
|
|
|
|
} */
|
2025-09-26 16:40:38 +08:00
|
|
|
|
.product-machine-add :deep(.el-input-group__append) {
|
|
|
|
|
|
background: #f5f7fa;
|
|
|
|
|
|
color: #606266;
|
|
|
|
|
|
border-left: 1px solid #dcdfe6;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-12-12 15:33:23 +08:00
|
|
|
|
::v-deep .el-form-item__content {
|
|
|
|
|
|
text-align: left;
|
|
|
|
|
|
padding-left: 18px !important;
|
2025-09-26 16:40:38 +08:00
|
|
|
|
}
|
2025-11-07 16:30:03 +08:00
|
|
|
|
|
|
|
|
|
|
/* 多结算币种价格输入的布局优化 */
|
2025-12-12 15:33:23 +08:00
|
|
|
|
.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;
|
|
|
|
|
|
}
|
2025-11-07 16:30:03 +08:00
|
|
|
|
/* 让 链-币种 附加区同宽、居中显示,整体对齐 */
|
|
|
|
|
|
.price-item :deep(.el-input-group__append),
|
2025-12-12 15:33:23 +08:00
|
|
|
|
.cost-item :deep(.el-input-group__append) {
|
2025-11-07 16:30:03 +08:00
|
|
|
|
width: 110px;
|
|
|
|
|
|
min-width: 110px;
|
|
|
|
|
|
text-align: center;
|
|
|
|
|
|
padding: 0 8px;
|
|
|
|
|
|
background: #f8fafc;
|
|
|
|
|
|
color: #606266;
|
|
|
|
|
|
}
|
|
|
|
|
|
/* 缩小输入高度并保持垂直居中 */
|
|
|
|
|
|
.price-item :deep(.el-input__inner),
|
2025-12-12 15:33:23 +08:00
|
|
|
|
.cost-item :deep(.el-input__inner) {
|
2025-11-07 16:30:03 +08:00
|
|
|
|
height: 30px;
|
|
|
|
|
|
line-height: 30px;
|
|
|
|
|
|
}
|
|
|
|
|
|
/* 让组内附加区高度与输入一致 */
|
|
|
|
|
|
.price-item :deep(.el-input-group__append),
|
2025-12-12 15:33:23 +08:00
|
|
|
|
.cost-item :deep(.el-input-group__append) {
|
2025-11-07 16:30:03 +08:00
|
|
|
|
height: 30px;
|
|
|
|
|
|
line-height: 30px;
|
|
|
|
|
|
}
|
|
|
|
|
|
/* 略微收紧间距,让整体更紧凑 */
|
2025-12-12 15:33:23 +08:00
|
|
|
|
.price-multi {
|
|
|
|
|
|
gap: 6px;
|
|
|
|
|
|
}
|
|
|
|
|
|
.price-items {
|
|
|
|
|
|
gap: 6px;
|
|
|
|
|
|
}
|
|
|
|
|
|
.cost-multi {
|
|
|
|
|
|
gap: 6px;
|
|
|
|
|
|
}
|
2025-12-05 16:24:20 +08:00
|
|
|
|
/* ASIC 币种/算法/算力/单位 多行 */
|
2025-12-12 15:33:23 +08:00
|
|
|
|
.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;
|
|
|
|
|
|
}
|
|
|
|
|
|
/* GPU 引导区域样式 */
|
|
|
|
|
|
.gpu-guide-section {
|
|
|
|
|
|
margin-bottom: 12px;
|
|
|
|
|
|
margin-left: 86px;
|
|
|
|
|
|
}
|
|
|
|
|
|
.gpu-guide-card {
|
|
|
|
|
|
padding: 20px;
|
|
|
|
|
|
background: #f9fafb;
|
|
|
|
|
|
width: 85%;
|
|
|
|
|
|
}
|
|
|
|
|
|
.gpu-guide-content {
|
|
|
|
|
|
text-align: left;
|
|
|
|
|
|
line-height: 1.7;
|
|
|
|
|
|
font-size: 15px;
|
|
|
|
|
|
color: #555;
|
|
|
|
|
|
}
|
|
|
|
|
|
.gpu-guide-title {
|
|
|
|
|
|
margin-bottom: 8px;
|
|
|
|
|
|
font-weight: 600;
|
|
|
|
|
|
font-size: 16px;
|
|
|
|
|
|
color: #333;
|
|
|
|
|
|
}
|
|
|
|
|
|
.gpu-guide-list {
|
|
|
|
|
|
padding-left: 18px;
|
|
|
|
|
|
margin: 0;
|
|
|
|
|
|
font-size: 15px;
|
|
|
|
|
|
line-height: 1.8;
|
|
|
|
|
|
}
|
|
|
|
|
|
.gpu-guide-list li {
|
|
|
|
|
|
margin-bottom: 8px;
|
|
|
|
|
|
}
|
|
|
|
|
|
.gpu-guide-list li:last-child {
|
|
|
|
|
|
margin-bottom: 0;
|
|
|
|
|
|
}
|
|
|
|
|
|
.gpu-guide-buttons {
|
|
|
|
|
|
display: flex;
|
|
|
|
|
|
align-items: center;
|
|
|
|
|
|
gap: 12px;
|
|
|
|
|
|
margin-top: 16px;
|
|
|
|
|
|
}
|
2025-09-26 16:40:38 +08:00
|
|
|
|
</style>
|
|
|
|
|
|
|