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>
|
|
|
|
|
|
<h2 class="title">添加出售机器</h2>
|
|
|
|
|
|
</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"
|
|
|
|
|
|
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">
|
|
|
|
|
|
<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="矿机种类">
|
|
|
|
|
|
<el-radio-group v-model="form.machineCategory" @change="handleMachineCategoryChange">
|
|
|
|
|
|
<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-11-21 16:23:46 +08:00
|
|
|
|
<!-- ASIC:币种与算法(支持多个,逗号分隔) -->
|
|
|
|
|
|
<el-form-item label="币种(多个用逗号隔开)" prop="coinsInput" :required="form.machineCategory === 'ASIC'">
|
|
|
|
|
|
<el-input
|
|
|
|
|
|
v-model="form.coinsInput"
|
|
|
|
|
|
placeholder="例如:USDT, BTC, ETH"
|
|
|
|
|
|
style="width: 50%;"
|
2025-11-28 15:30:36 +08:00
|
|
|
|
@input="handleCoinsInput"
|
2025-11-21 16:23:46 +08:00
|
|
|
|
/>
|
|
|
|
|
|
</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%;"
|
2025-11-28 15:30:36 +08:00
|
|
|
|
@input="handleAlgorithmsInput"
|
2025-11-21 16:23:46 +08:00
|
|
|
|
/>
|
|
|
|
|
|
</el-form-item>
|
|
|
|
|
|
<div style="text-align:left; color:#909399; font-size:12px; margin:-6px 0 10px 160px;">
|
|
|
|
|
|
输入多个用逗号隔开
|
|
|
|
|
|
</div>
|
2025-09-26 16:40:38 +08:00
|
|
|
|
|
2025-11-28 15:30:36 +08:00
|
|
|
|
<el-form-item label="矿机型号" prop="type" :required="true">
|
2025-10-20 10:15:13 +08:00
|
|
|
|
<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>
|
|
|
|
|
|
<el-form-item label="理论算力" prop="theoryPower">
|
|
|
|
|
|
<el-input
|
|
|
|
|
|
v-model="form.theoryPower"
|
|
|
|
|
|
placeholder="请输入单机理论算力"
|
|
|
|
|
|
inputmode="decimal"
|
|
|
|
|
|
@input="handleNumeric('theoryPower')"
|
2025-10-20 10:15:13 +08:00
|
|
|
|
style="width: 50%;"
|
2025-09-26 16:40:38 +08:00
|
|
|
|
/>
|
|
|
|
|
|
</el-form-item>
|
|
|
|
|
|
<el-form-item label="算力单位" prop="unit">
|
|
|
|
|
|
<el-select v-model="form.unit" placeholder="请选择算力单位">
|
2025-10-20 10:15:13 +08:00
|
|
|
|
<el-option label="KH/S" value="KH/S" />
|
2025-09-26 16:40:38 +08:00
|
|
|
|
<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>
|
2025-10-20 10:15:13 +08:00
|
|
|
|
<el-form-item label="最大租赁天数" prop="maxLeaseDays">
|
|
|
|
|
|
<el-input
|
|
|
|
|
|
v-model="form.maxLeaseDays"
|
|
|
|
|
|
placeholder="1-365"
|
|
|
|
|
|
inputmode="numeric"
|
|
|
|
|
|
@input="handleNumeric('maxLeaseDays')"
|
|
|
|
|
|
style="width: 50%;"
|
|
|
|
|
|
>
|
|
|
|
|
|
<template slot="append">天</template>
|
|
|
|
|
|
</el-input>
|
|
|
|
|
|
</el-form-item>
|
|
|
|
|
|
<el-form-item label="功耗" prop="powerDissipation">
|
|
|
|
|
|
<el-input
|
2025-11-07 16:30:03 +08:00
|
|
|
|
v-model="form.powerDissipation"
|
2025-10-20 10:15:13 +08:00
|
|
|
|
inputmode="decimal"
|
|
|
|
|
|
@input="handleNumeric('powerDissipation')"
|
|
|
|
|
|
style="width: 50%;"
|
|
|
|
|
|
>
|
|
|
|
|
|
<template slot="append">kw/h</template>
|
|
|
|
|
|
</el-input>
|
|
|
|
|
|
</el-form-item>
|
|
|
|
|
|
|
2025-11-28 15:30:36 +08:00
|
|
|
|
<el-form-item
|
|
|
|
|
|
label="统一售价"
|
|
|
|
|
|
:prop="(payTypeDefs && payTypeDefs.length) ? 'costMap' : 'cost'"
|
|
|
|
|
|
: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"
|
|
|
|
|
|
@input="val => handleCostMapInput(pt.key, val)"
|
|
|
|
|
|
style="width: 50%;"
|
|
|
|
|
|
>
|
|
|
|
|
|
<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')"
|
|
|
|
|
|
style="width: 50%;"
|
|
|
|
|
|
>
|
|
|
|
|
|
<template slot="append">USDT</template>
|
|
|
|
|
|
</el-input>
|
|
|
|
|
|
</el-form-item>
|
|
|
|
|
|
|
2025-09-26 16:40:38 +08:00
|
|
|
|
|
2025-11-21 16:23:46 +08:00
|
|
|
|
<!-- 出售机器数量(统一使用,GPU 仅作引导不在本页提交) -->
|
|
|
|
|
|
<el-form-item label="出售机器数量(台)" prop="sellCount" :required="true">
|
|
|
|
|
|
<el-input
|
|
|
|
|
|
v-model="form.sellCount"
|
|
|
|
|
|
placeholder="0 - 9999"
|
|
|
|
|
|
inputmode="numeric"
|
|
|
|
|
|
style="width: 50%;"
|
|
|
|
|
|
@input="handleSellCountInput"
|
|
|
|
|
|
@blur="handleSellCountBlur"
|
|
|
|
|
|
/>
|
2025-09-26 16:40:38 +08:00
|
|
|
|
</el-form-item>
|
|
|
|
|
|
</el-form>
|
|
|
|
|
|
</el-card>
|
|
|
|
|
|
|
|
|
|
|
|
<div class="actions">
|
|
|
|
|
|
<el-button @click="handleBack">取消</el-button>
|
|
|
|
|
|
<el-button type="primary" :loading="saving" @click="handleSave">确认添加</el-button>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
|
|
<!-- 上架确认弹窗 -->
|
|
|
|
|
|
<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-11-28 15:30:36 +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>
|
|
|
|
|
|
<el-table
|
|
|
|
|
|
:data="confirmData.priceList"
|
|
|
|
|
|
border
|
|
|
|
|
|
size="mini"
|
|
|
|
|
|
style="width:100%;"
|
|
|
|
|
|
>
|
|
|
|
|
|
<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>
|
|
|
|
|
|
<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-11-28 15:30:36 +08:00
|
|
|
|
<el-button type="primary" :loading="saving" @click="doSubmit">确认提交</el-button>
|
2025-09-26 16:40:38 +08:00
|
|
|
|
</span>
|
|
|
|
|
|
</el-dialog>
|
2025-11-21 16:23:46 +08:00
|
|
|
|
<!-- GPU 引导弹窗 -->
|
|
|
|
|
|
<el-dialog title="GPU 客户端指引" :visible.sync="gpuDialogVisible" width="520px" @close="handleGpuDialogClosed">
|
|
|
|
|
|
<div style="text-align:left; line-height:1.8;">
|
|
|
|
|
|
<div style="display:flex; align-items:center; gap:12px; margin-bottom:12px;">
|
|
|
|
|
|
<el-button type="primary" @click="handleDownloadClient">下载客户端</el-button>
|
|
|
|
|
|
<el-button type="success" :disabled="!hasDownloadedClient" @click="handleGpuClientStarted">已启动客户端</el-button>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<div style="color:#555;">
|
|
|
|
|
|
<div style="margin-bottom:6px; font-weight:600;">注意事项:</div>
|
|
|
|
|
|
<ol style="padding-left:18px;">
|
|
|
|
|
|
<li>请直接下载客户端后并启动,客户端显示 GPU 信息后,启动完成点击“已启动客户端”按钮(若未完全启动点击按钮会创建失败)。</li>
|
|
|
|
|
|
<li>涉及多台主机,每个主机都需要配置相同客户端。</li>
|
|
|
|
|
|
<li>客户端身份信息和本网站身份信息一致。</li>
|
|
|
|
|
|
</ol>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<span slot="footer" class="dialog-footer">
|
|
|
|
|
|
<el-button @click="gpuDialogVisible=false">关闭</el-button>
|
|
|
|
|
|
</span>
|
|
|
|
|
|
</el-dialog>
|
2025-09-26 16:40:38 +08:00
|
|
|
|
</div>
|
|
|
|
|
|
</template>
|
|
|
|
|
|
|
|
|
|
|
|
<script>
|
2025-11-28 15:30:36 +08:00
|
|
|
|
import { addSingleOrBatchMachine ,downloadClient,addAsicMachine} from '../../api/machine'
|
|
|
|
|
|
import { getPayTypes } from '../../api/products'
|
2025-09-26 16:40:38 +08:00
|
|
|
|
export default {
|
|
|
|
|
|
name: 'AccountProductMachineAdd',
|
|
|
|
|
|
data() {
|
|
|
|
|
|
return {
|
|
|
|
|
|
form: {
|
|
|
|
|
|
productId: Number(this.$route.query.productId) || null,
|
|
|
|
|
|
coin: this.$route.query.coin || '',
|
|
|
|
|
|
productName: this.$route.query.name || '',
|
2025-11-21 16:23:46 +08:00
|
|
|
|
/** 矿机种类:ASIC 或 GPU,默认 ASIC */
|
|
|
|
|
|
machineCategory: 'ASIC',
|
|
|
|
|
|
/** 出售机器数量(仅 ASIC 模式使用) */
|
|
|
|
|
|
sellCount: '',
|
|
|
|
|
|
/** ASIC 模式下币种/算法输入(逗号分隔的原始文本) */
|
|
|
|
|
|
coinsInput: '',
|
|
|
|
|
|
algorithmsInput: '',
|
2025-09-26 16:40:38 +08:00
|
|
|
|
powerDissipation: null,
|
|
|
|
|
|
theoryPower: null,
|
|
|
|
|
|
type: '',
|
|
|
|
|
|
unit: 'TH/S',
|
2025-10-20 10:15:13 +08:00
|
|
|
|
cost: '',
|
2025-11-07 16:30:03 +08:00
|
|
|
|
costMap: {}, // { 'CHAIN-COIN': '123.45' }
|
2025-10-20 10:15:13 +08:00
|
|
|
|
maxLeaseDays: ''
|
2025-09-26 16:40:38 +08:00
|
|
|
|
},
|
|
|
|
|
|
confirmVisible: false,
|
2025-11-28 15:30:36 +08:00
|
|
|
|
confirmData: {
|
|
|
|
|
|
coin: '',
|
|
|
|
|
|
algorithm: '',
|
|
|
|
|
|
maxLeaseDays: '',
|
|
|
|
|
|
saleNumbers: '',
|
|
|
|
|
|
priceList: []
|
|
|
|
|
|
},
|
2025-09-26 16:40:38 +08:00
|
|
|
|
rules: {
|
|
|
|
|
|
productName: [ { required: true, message: '商品名称不能为空', trigger: 'change' } ],
|
2025-11-28 15:30:36 +08:00
|
|
|
|
type: [
|
|
|
|
|
|
{ required: true, message: '矿机型号不能为空', trigger: 'blur' },
|
|
|
|
|
|
{
|
|
|
|
|
|
validator: (rule, value, callback) => {
|
|
|
|
|
|
const s = String(value || '')
|
|
|
|
|
|
// 不允许全是空格
|
|
|
|
|
|
if (s && s.trim().length === 0) {
|
|
|
|
|
|
callback(new Error('矿机型号不能全是空格'))
|
|
|
|
|
|
return
|
|
|
|
|
|
}
|
|
|
|
|
|
callback()
|
|
|
|
|
|
},
|
|
|
|
|
|
trigger: 'blur'
|
|
|
|
|
|
}
|
|
|
|
|
|
],
|
2025-11-21 16:23:46 +08:00
|
|
|
|
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 }
|
2025-11-28 15:30:36 +08:00
|
|
|
|
// 禁止汉字,且仅允许英数字与逗号/空格/连字符
|
|
|
|
|
|
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
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
2025-11-21 16:23:46 +08:00
|
|
|
|
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 }
|
2025-11-28 15:30:36 +08:00
|
|
|
|
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
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
2025-11-21 16:23:46 +08:00
|
|
|
|
callback()
|
|
|
|
|
|
},
|
|
|
|
|
|
trigger: 'blur'
|
|
|
|
|
|
}
|
|
|
|
|
|
],
|
|
|
|
|
|
sellCount: [
|
|
|
|
|
|
{
|
|
|
|
|
|
validator: (rule, value, callback) => {
|
|
|
|
|
|
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()
|
|
|
|
|
|
},
|
|
|
|
|
|
trigger: 'blur'
|
|
|
|
|
|
}
|
|
|
|
|
|
],
|
2025-09-26 16:40:38 +08:00
|
|
|
|
powerDissipation: [
|
|
|
|
|
|
{ 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'
|
|
|
|
|
|
}
|
|
|
|
|
|
],
|
|
|
|
|
|
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: [
|
|
|
|
|
|
{
|
2025-11-07 16:30:03 +08:00
|
|
|
|
validator(rule, value, callback) {
|
|
|
|
|
|
// 若为多结算币种模式,跳过此校验(统一售价由每种币种的输入框承担)
|
|
|
|
|
|
if (Array.isArray(this.payTypeDefs) && this.payTypeDefs.length > 0) {
|
|
|
|
|
|
callback()
|
|
|
|
|
|
return
|
|
|
|
|
|
}
|
2025-09-26 16:40:38 +08:00
|
|
|
|
const str = String(value || '')
|
|
|
|
|
|
if (!str) {
|
|
|
|
|
|
callback(new Error('请填写机器成本(USDT)'))
|
|
|
|
|
|
return
|
|
|
|
|
|
}
|
|
|
|
|
|
const pattern = /^\d{1,12}(\.\d{1,2})?$/
|
|
|
|
|
|
if (!pattern.test(str)) {
|
|
|
|
|
|
callback(new Error('成本整数最多12位,小数最多2位'))
|
|
|
|
|
|
return
|
|
|
|
|
|
}
|
|
|
|
|
|
if (Number(str) <= 0) {
|
|
|
|
|
|
callback(new Error('成本必须大于 0'))
|
|
|
|
|
|
return
|
|
|
|
|
|
}
|
|
|
|
|
|
callback()
|
|
|
|
|
|
},
|
|
|
|
|
|
trigger: 'blur'
|
|
|
|
|
|
}
|
|
|
|
|
|
]
|
2025-10-20 10:15:13 +08:00
|
|
|
|
,
|
|
|
|
|
|
maxLeaseDays: [
|
|
|
|
|
|
{ required: true, message: '请填写最大租赁天数', trigger: 'blur' },
|
|
|
|
|
|
{
|
|
|
|
|
|
validator: (rule, value, callback) => {
|
|
|
|
|
|
const raw = String(value ?? '')
|
|
|
|
|
|
if (!raw) { callback(new Error('请填写最大租赁天数')); return }
|
|
|
|
|
|
if (!/^\d{1,3}$/.test(raw)) { callback(new Error('仅允许整数,范围 1-365')); return }
|
|
|
|
|
|
const n = Number(raw)
|
|
|
|
|
|
if (!Number.isInteger(n) || n < 1 || n > 365) { callback(new Error('范围需在 1-365 天')); return }
|
|
|
|
|
|
callback()
|
|
|
|
|
|
},
|
|
|
|
|
|
trigger: 'blur'
|
|
|
|
|
|
}
|
|
|
|
|
|
]
|
2025-09-26 16:40:38 +08:00
|
|
|
|
},
|
|
|
|
|
|
miners: [
|
|
|
|
|
|
// {
|
|
|
|
|
|
// "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"
|
|
|
|
|
|
// },
|
|
|
|
|
|
],
|
|
|
|
|
|
minersLoading: false,
|
|
|
|
|
|
selectedMiner: '', // 格式 user|coin
|
|
|
|
|
|
machineOptions: [
|
|
|
|
|
|
// {
|
|
|
|
|
|
// "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"
|
|
|
|
|
|
// },
|
|
|
|
|
|
],
|
|
|
|
|
|
machinesLoading: false,
|
|
|
|
|
|
selectedMachines: [],
|
|
|
|
|
|
selectedMachineRows: [],
|
|
|
|
|
|
saving: false,
|
|
|
|
|
|
lastCostBaseline: 0,
|
2025-11-07 16:30:03 +08:00
|
|
|
|
lastCostMapBaseline: {}, // { key: number }
|
2025-09-26 16:40:38 +08:00
|
|
|
|
lastTypeBaseline: '',
|
2025-10-20 10:15:13 +08:00
|
|
|
|
lastMaxLeaseDaysBaseline: 0,
|
|
|
|
|
|
lastPowerDissipationBaseline: 0,
|
|
|
|
|
|
lastTheoryPowerBaseline: 0,
|
|
|
|
|
|
lastUnitBaseline: 'TH/S',
|
2025-11-21 16:23:46 +08:00
|
|
|
|
/** GPU 引导弹窗可见性 */
|
|
|
|
|
|
gpuDialogVisible: false,
|
|
|
|
|
|
/** GPU 客户端下载地址(可通过环境变量配置:VUE_APP_GPU_CLIENT_URL) */
|
|
|
|
|
|
clientDownloadUrl: process.env.VUE_APP_GPU_CLIENT_URL || '',
|
|
|
|
|
|
/** 是否点击过下载客户端(用于控制“已启动客户端”按钮禁用态) */
|
|
|
|
|
|
hasDownloadedClient: false,
|
2025-11-28 15:30:36 +08:00
|
|
|
|
/** 支持的支付方式定义(用于动态渲染统一售价输入组) */
|
|
|
|
|
|
payTypeDefs: [],
|
2025-09-26 16:40:38 +08:00
|
|
|
|
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
|
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
|
|
]
|
|
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
},
|
|
|
|
|
|
created() {
|
2025-11-07 16:30:03 +08:00
|
|
|
|
this.initPayTypesFromRoute()
|
2025-09-26 16:40:38 +08:00
|
|
|
|
this.lastTypeBaseline = this.form.type
|
2025-11-07 16:30:03 +08:00
|
|
|
|
// 绑定基于组件实例的校验器,避免 this 丢失
|
|
|
|
|
|
if (this.rules && this.rules.cost) {
|
|
|
|
|
|
this.$set(this.rules, 'cost', [{ validator: this.validateCost, trigger: 'blur' }])
|
|
|
|
|
|
}
|
2025-11-28 15:30:36 +08:00
|
|
|
|
// 多币种价格专用校验
|
|
|
|
|
|
this.$set(this.rules, 'costMap', [{ validator: this.validateCostMap, trigger: 'blur' }])
|
|
|
|
|
|
|
|
|
|
|
|
this.getPayTypes()
|
2025-09-26 16:40:38 +08:00
|
|
|
|
},
|
|
|
|
|
|
methods: {
|
2025-11-28 15:30:36 +08:00
|
|
|
|
/** 实时过滤币种输入中的中文字符(仅保留英文/数字/分隔符) */
|
|
|
|
|
|
handleCoinsInput() {
|
|
|
|
|
|
let v = String(this.form.coinsInput || '')
|
|
|
|
|
|
// 去除中文
|
|
|
|
|
|
v = v.replace(/[\u4e00-\u9fa5]/g, '')
|
|
|
|
|
|
this.form.coinsInput = v
|
|
|
|
|
|
},
|
|
|
|
|
|
/** 实时过滤算法输入中的中文字符(仅保留英文/数字/连字符/分隔符) */
|
|
|
|
|
|
handleAlgorithmsInput() {
|
|
|
|
|
|
let v = String(this.form.algorithmsInput || '')
|
|
|
|
|
|
v = v.replace(/[\u4e00-\u9fa5]/g, '')
|
|
|
|
|
|
this.form.algorithmsInput = v
|
|
|
|
|
|
},
|
|
|
|
|
|
/** 将输入按中英文逗号、空格分割,去空,统一英文逗号连接;可选:统一大写 */
|
|
|
|
|
|
normalizeCsv(input, upper = true) {
|
|
|
|
|
|
const arr = String(input || '')
|
|
|
|
|
|
.split(/[,\s,、]+/)
|
|
|
|
|
|
.map(s => s.trim())
|
|
|
|
|
|
.filter(Boolean)
|
|
|
|
|
|
const list = upper ? arr.map(s => s.toUpperCase()) : arr
|
|
|
|
|
|
return list.join(',')
|
|
|
|
|
|
},
|
|
|
|
|
|
/** 从 payTypeDefs 与 costMap 生成价格列表 */
|
|
|
|
|
|
buildPriceList() {
|
|
|
|
|
|
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)
|
|
|
|
|
|
// 允许为空/非数字则不加入
|
|
|
|
|
|
if (!Number.isFinite(priceNum) || priceNum <= 0) return
|
|
|
|
|
|
list.push({
|
|
|
|
|
|
chain: d.chain,
|
|
|
|
|
|
coin: d.coin,
|
|
|
|
|
|
price: priceNum
|
|
|
|
|
|
})
|
|
|
|
|
|
})
|
|
|
|
|
|
return list
|
|
|
|
|
|
},
|
|
|
|
|
|
async getPayTypes() {
|
|
|
|
|
|
try {
|
|
|
|
|
|
const res = await getPayTypes()
|
|
|
|
|
|
// 期望结构:{ code:200, data:[ { payChain, payCoin, payCoinImage } ] }
|
|
|
|
|
|
if (res && (res.code === 0 || res.code === 200)) {
|
|
|
|
|
|
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)
|
|
|
|
|
|
defs.push({
|
|
|
|
|
|
chain,
|
|
|
|
|
|
coin,
|
|
|
|
|
|
key,
|
|
|
|
|
|
label: key,
|
|
|
|
|
|
image: it && it.payCoinImage ? String(it.payCoinImage) : ''
|
|
|
|
|
|
})
|
|
|
|
|
|
})
|
|
|
|
|
|
// 根据接口结果渲染“统一售价”输入组
|
|
|
|
|
|
this.payTypeDefs = defs
|
|
|
|
|
|
const nextCostMap = {}
|
|
|
|
|
|
this.payTypeDefs.forEach(d => {
|
|
|
|
|
|
// 保留已输入的数值;否则置空
|
|
|
|
|
|
nextCostMap[d.key] = (this.form.costMap && this.form.costMap[d.key]) || ''
|
|
|
|
|
|
})
|
|
|
|
|
|
this.form.costMap = nextCostMap
|
|
|
|
|
|
}
|
|
|
|
|
|
} catch (e) {
|
|
|
|
|
|
// 忽略错误,维持当前 payTypeDefs
|
|
|
|
|
|
}
|
|
|
|
|
|
},
|
2025-11-21 16:23:46 +08:00
|
|
|
|
/**
|
|
|
|
|
|
* ASIC 模式:出售机器数量输入,仅允许 0-9999 的整数
|
|
|
|
|
|
*/
|
|
|
|
|
|
handleSellCountInput() {
|
|
|
|
|
|
let v = String(this.form.sellCount ?? '')
|
|
|
|
|
|
// 仅数字
|
|
|
|
|
|
v = v.replace(/\D/g, '')
|
|
|
|
|
|
// 限制最多4位
|
|
|
|
|
|
if (v.length > 4) v = v.slice(0, 4)
|
|
|
|
|
|
// 限制最大 9999
|
|
|
|
|
|
if (v) {
|
|
|
|
|
|
const n = Number(v)
|
|
|
|
|
|
if (n > 9999) v = '9999'
|
|
|
|
|
|
}
|
|
|
|
|
|
this.form.sellCount = v
|
|
|
|
|
|
},
|
|
|
|
|
|
handleSellCountBlur() {
|
|
|
|
|
|
const raw = String(this.form.sellCount ?? '')
|
|
|
|
|
|
if (raw === '') return
|
|
|
|
|
|
const n = Number(raw)
|
|
|
|
|
|
if (!Number.isInteger(n) || n < 0 || n > 9999) {
|
|
|
|
|
|
this.$message.warning('出售机器数量需为 0-9999 的整数')
|
|
|
|
|
|
this.form.sellCount = ''
|
|
|
|
|
|
}
|
|
|
|
|
|
},
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 矿机种类变更:若选择 GPU,弹出引导弹窗;ASIC 则保持当前页面逻辑
|
|
|
|
|
|
* @param {string} val
|
|
|
|
|
|
*/
|
|
|
|
|
|
handleMachineCategoryChange(val) {
|
|
|
|
|
|
if (String(val).toUpperCase() === 'GPU') {
|
|
|
|
|
|
this.gpuDialogVisible = true
|
|
|
|
|
|
// 首次打开时禁用“已启动客户端”按钮
|
|
|
|
|
|
this.hasDownloadedClient = false
|
|
|
|
|
|
} else {
|
|
|
|
|
|
this.gpuDialogVisible = false
|
|
|
|
|
|
}
|
|
|
|
|
|
},
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 下载 GPU 客户端
|
|
|
|
|
|
*/
|
|
|
|
|
|
handleDownloadClient() {
|
2025-11-28 15:30:36 +08:00
|
|
|
|
// 走后端接口下载客户端程序
|
|
|
|
|
|
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('下载失败,请稍后重试')
|
|
|
|
|
|
})
|
2025-11-21 16:23:46 +08:00
|
|
|
|
},
|
|
|
|
|
|
/**
|
|
|
|
|
|
* GPU 客户端已启动:跳转至商品列表
|
|
|
|
|
|
*/
|
|
|
|
|
|
handleGpuClientStarted() {
|
2025-11-28 15:30:36 +08:00
|
|
|
|
// 跳转到“个人中心-商品列表”页面
|
|
|
|
|
|
this.$router.push('/account/products')
|
2025-11-21 16:23:46 +08:00
|
|
|
|
},
|
|
|
|
|
|
/**
|
|
|
|
|
|
* GPU 弹窗关闭:自动恢复为 ASIC
|
|
|
|
|
|
*/
|
|
|
|
|
|
handleGpuDialogClosed() {
|
|
|
|
|
|
this.form.machineCategory = 'ASIC'
|
|
|
|
|
|
this.gpuDialogVisible = false
|
|
|
|
|
|
},
|
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-11-28 15:30:36 +08:00
|
|
|
|
return this.validateCostMap(rule, value, callback)
|
2025-11-07 16:30:03 +08:00
|
|
|
|
}
|
|
|
|
|
|
const str = String(value || '')
|
|
|
|
|
|
if (!str) { callback(new Error('请填写机器成本(USDT)')); return }
|
|
|
|
|
|
const pattern = /^\d{1,12}(\.\d{1,2})?$/
|
|
|
|
|
|
if (!pattern.test(str)) { callback(new Error('成本整数最多12位,小数最多2位')); return }
|
|
|
|
|
|
if (Number(str) <= 0) { callback(new Error('成本必须大于 0')); return }
|
|
|
|
|
|
callback()
|
|
|
|
|
|
},
|
2025-11-28 15:30:36 +08:00
|
|
|
|
// 多支付方式下的价格校验:要求每个支付方式都需填写有效价格
|
|
|
|
|
|
validateCostMap(rule, value, callback) {
|
|
|
|
|
|
try {
|
|
|
|
|
|
const defs = Array.isArray(this.payTypeDefs) ? this.payTypeDefs : []
|
|
|
|
|
|
if (!defs.length) { callback(); return }
|
|
|
|
|
|
const pattern = /^\d{1,12}(\.\d{1,2})?$/
|
|
|
|
|
|
for (let i = 0; i < defs.length; i += 1) {
|
|
|
|
|
|
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 }
|
|
|
|
|
|
}
|
|
|
|
|
|
callback()
|
|
|
|
|
|
} catch (e) {
|
|
|
|
|
|
callback(new Error('价格填写有误,请检查'))
|
|
|
|
|
|
}
|
|
|
|
|
|
},
|
2025-11-07 16:30:03 +08:00
|
|
|
|
/** 解析路由参数中的支付方式,生成标准定义 */
|
|
|
|
|
|
initPayTypesFromRoute() {
|
|
|
|
|
|
this.payTypeDefs = []
|
|
|
|
|
|
try {
|
|
|
|
|
|
const raw = this.$route.query.payTypes
|
|
|
|
|
|
if (!raw) return
|
|
|
|
|
|
const arr = JSON.parse(decodeURIComponent(raw))
|
|
|
|
|
|
if (!Array.isArray(arr)) return
|
|
|
|
|
|
const defs = []
|
|
|
|
|
|
arr.forEach(it => {
|
|
|
|
|
|
const chain = String(it && it.chain ? it.chain : '').toUpperCase()
|
|
|
|
|
|
const coin = String(it && it.coin ? it.coin : '').toUpperCase()
|
|
|
|
|
|
if (!chain && !coin) return
|
|
|
|
|
|
const key = [chain, coin].filter(Boolean).join('-')
|
|
|
|
|
|
const label = key
|
|
|
|
|
|
defs.push({ chain, coin, key, label })
|
|
|
|
|
|
})
|
|
|
|
|
|
// 去重
|
|
|
|
|
|
const map = new Map()
|
|
|
|
|
|
defs.forEach(d => { if (!map.has(d.key)) map.set(d.key, d) })
|
|
|
|
|
|
this.payTypeDefs = Array.from(map.values())
|
|
|
|
|
|
// 初始化统一售价映射
|
|
|
|
|
|
const initCostMap = {}
|
|
|
|
|
|
this.payTypeDefs.forEach(d => { initCostMap[d.key] = '' })
|
|
|
|
|
|
this.form.costMap = initCostMap
|
|
|
|
|
|
this.lastCostMapBaseline = { ...initCostMap }
|
|
|
|
|
|
} catch (e) {
|
|
|
|
|
|
this.payTypeDefs = []
|
|
|
|
|
|
}
|
|
|
|
|
|
},
|
2025-09-26 16:40:38 +08:00
|
|
|
|
handleBack() {
|
|
|
|
|
|
this.$router.back()
|
|
|
|
|
|
},
|
|
|
|
|
|
handleNumeric(key) {
|
|
|
|
|
|
// 仅允许数字和一个小数点
|
|
|
|
|
|
let v = String(this.form[key] ?? '')
|
|
|
|
|
|
// 清理非法字符
|
|
|
|
|
|
v = v.replace(/[^0-9.]/g, '')
|
|
|
|
|
|
// 保留第一个小数点
|
|
|
|
|
|
const firstDot = v.indexOf('.')
|
|
|
|
|
|
if (firstDot !== -1) {
|
|
|
|
|
|
v = v.slice(0, firstDot + 1) + v.slice(firstDot + 1).replace(/\./g, '')
|
|
|
|
|
|
}
|
|
|
|
|
|
const endsWithDot = v.endsWith('.')
|
|
|
|
|
|
if (key === 'cost') {
|
|
|
|
|
|
// 成本:整数最多12位,小数最多2位
|
|
|
|
|
|
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)
|
|
|
|
|
|
} else if (key === 'powerDissipation' || key === 'theoryPower') {
|
|
|
|
|
|
// 功耗/理论算力:整数最多6位,小数最多4位
|
|
|
|
|
|
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)
|
2025-10-20 10:15:13 +08:00
|
|
|
|
} else if (key === 'maxLeaseDays') {
|
|
|
|
|
|
// 最大租赁天数:仅整数,范围 1-365,输入阶段限制为最多3位数字
|
|
|
|
|
|
v = v.replace(/\D/g, '')
|
|
|
|
|
|
if (v.length > 3) v = v.slice(0, 3)
|
|
|
|
|
|
this.form[key] = v
|
|
|
|
|
|
this.syncMaxLeaseDaysToRows()
|
|
|
|
|
|
return
|
2025-09-26 16:40:38 +08:00
|
|
|
|
} else {
|
|
|
|
|
|
// 其他:最多6位小数(保持原有逻辑)
|
|
|
|
|
|
if (firstDot !== -1) {
|
|
|
|
|
|
const [intPart, decPart] = v.split('.')
|
|
|
|
|
|
v = intPart + '.' + (decPart ? decPart.slice(0, 6) : '')
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
this.form[key] = v
|
|
|
|
|
|
},
|
2025-11-07 16:30:03 +08:00
|
|
|
|
/** 顶部多结算币种统一售价输入 */
|
|
|
|
|
|
handleCostMapInput(key, val) {
|
|
|
|
|
|
// 价格输入:整数最多12位,小数最多2位;允许尾随小数点
|
|
|
|
|
|
let v = String(val ?? this.form.costMap[key] ?? '')
|
|
|
|
|
|
v = v.replace(/[^0-9.]/g, '')
|
|
|
|
|
|
const firstDot = v.indexOf('.')
|
|
|
|
|
|
if (firstDot !== -1) {
|
|
|
|
|
|
v = v.slice(0, firstDot + 1) + v.slice(firstDot + 1).replace(/\./g, '')
|
|
|
|
|
|
}
|
|
|
|
|
|
const endsWithDot = v.endsWith('.')
|
|
|
|
|
|
const parts = v.split('.')
|
|
|
|
|
|
let intPart = parts[0] || ''
|
|
|
|
|
|
let decPart = parts[1] || ''
|
|
|
|
|
|
if (intPart.length > 12) intPart = intPart.slice(0, 12)
|
|
|
|
|
|
if (decPart) decPart = decPart.slice(0, 2)
|
|
|
|
|
|
v = decPart.length ? `${intPart}.${decPart}` : (endsWithDot ? `${intPart}.` : intPart)
|
|
|
|
|
|
this.$set(this.form.costMap, key, v)
|
|
|
|
|
|
},
|
2025-09-26 16:40:38 +08:00
|
|
|
|
/**
|
|
|
|
|
|
* 顶部矿机型号输入:限制20字符
|
|
|
|
|
|
*/
|
|
|
|
|
|
handleTypeInput() {
|
|
|
|
|
|
if (typeof this.form.type === 'string' && this.form.type.length > 20) {
|
|
|
|
|
|
this.form.type = this.form.type.slice(0, 20)
|
|
|
|
|
|
}
|
|
|
|
|
|
},
|
|
|
|
|
|
syncCostToRows() {
|
|
|
|
|
|
const newCost = Number(this.form.cost)
|
|
|
|
|
|
if (!Number.isFinite(newCost)) {
|
|
|
|
|
|
return
|
|
|
|
|
|
}
|
|
|
|
|
|
const oldBaseline = this.lastCostBaseline
|
|
|
|
|
|
this.selectedMachineRows = this.selectedMachineRows.map(row => {
|
|
|
|
|
|
const priceNum = Number(row.price)
|
|
|
|
|
|
if (!Number.isFinite(priceNum) || priceNum === oldBaseline) {
|
|
|
|
|
|
return { ...row, price: newCost }
|
|
|
|
|
|
}
|
|
|
|
|
|
return row
|
|
|
|
|
|
})
|
|
|
|
|
|
this.lastCostBaseline = newCost
|
|
|
|
|
|
},
|
|
|
|
|
|
updateMachineType() {
|
2025-11-21 16:23:46 +08:00
|
|
|
|
// 仅记录最近一次外层输入,避免无用同步逻辑
|
2025-09-26 16:40:38 +08:00
|
|
|
|
this.lastTypeBaseline = this.form.type
|
|
|
|
|
|
},
|
2025-10-20 10:15:13 +08:00
|
|
|
|
/**
|
|
|
|
|
|
* 行内功耗输入(限制:整数最多6位,小数最多4位)
|
|
|
|
|
|
*/
|
|
|
|
|
|
handleRowPowerDissipationInput(index) {
|
|
|
|
|
|
let v = String(this.selectedMachineRows[index].powerDissipation ?? '')
|
|
|
|
|
|
v = v.replace(/[^0-9.]/g, '')
|
|
|
|
|
|
const firstDot = v.indexOf('.')
|
|
|
|
|
|
if (firstDot !== -1) {
|
|
|
|
|
|
v = v.slice(0, firstDot + 1) + v.slice(firstDot + 1).replace(/\./g, '')
|
|
|
|
|
|
}
|
2025-11-14 16:17:36 +08:00
|
|
|
|
const endsWithDot = v.endsWith('.')
|
2025-10-20 10:15:13 +08:00
|
|
|
|
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)
|
2025-11-14 16:17:36 +08:00
|
|
|
|
v = decPart.length ? `${intPart}.${decPart}` : (endsWithDot ? `${intPart}.` : intPart)
|
2025-10-20 10:15:13 +08:00
|
|
|
|
this.$set(this.selectedMachineRows[index], 'powerDissipation', v)
|
|
|
|
|
|
},
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 行内功耗校验
|
|
|
|
|
|
*/
|
|
|
|
|
|
handleRowPowerDissipationBlur(index) {
|
|
|
|
|
|
const raw = String(this.selectedMachineRows[index].powerDissipation ?? '')
|
|
|
|
|
|
const pattern = /^\d{1,6}(\.\d{1,4})?$/
|
|
|
|
|
|
if (!raw || Number(raw) <= 0 || !pattern.test(raw)) {
|
|
|
|
|
|
this.$message.warning('功耗需大于0,整数最多6位,小数最多4位')
|
|
|
|
|
|
this.$set(this.selectedMachineRows[index], 'powerDissipation', '')
|
|
|
|
|
|
}
|
|
|
|
|
|
},
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 行内理论算力输入(限制:整数最多6位,小数最多4位)
|
|
|
|
|
|
*/
|
|
|
|
|
|
handleRowTheoryPowerInput(index) {
|
|
|
|
|
|
let v = String(this.selectedMachineRows[index].theoryPower ?? '')
|
|
|
|
|
|
v = v.replace(/[^0-9.]/g, '')
|
|
|
|
|
|
const firstDot = v.indexOf('.')
|
|
|
|
|
|
if (firstDot !== -1) {
|
|
|
|
|
|
v = v.slice(0, firstDot + 1) + v.slice(firstDot + 1).replace(/\./g, '')
|
|
|
|
|
|
}
|
2025-11-14 16:17:36 +08:00
|
|
|
|
const endsWithDot = v.endsWith('.')
|
2025-10-20 10:15:13 +08:00
|
|
|
|
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)
|
2025-11-14 16:17:36 +08:00
|
|
|
|
v = decPart.length ? `${intPart}.${decPart}` : (endsWithDot ? `${intPart}.` : intPart)
|
2025-10-20 10:15:13 +08:00
|
|
|
|
this.$set(this.selectedMachineRows[index], 'theoryPower', v)
|
|
|
|
|
|
},
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 行内理论算力校验
|
|
|
|
|
|
*/
|
|
|
|
|
|
handleRowTheoryPowerBlur(index) {
|
|
|
|
|
|
const raw = String(this.selectedMachineRows[index].theoryPower ?? '')
|
|
|
|
|
|
const pattern = /^\d{1,6}(\.\d{1,4})?$/
|
|
|
|
|
|
if (!raw || Number(raw) <= 0 || !pattern.test(raw)) {
|
|
|
|
|
|
this.$message.warning('理论算力需大于0,整数最多6位,小数最多4位')
|
|
|
|
|
|
this.$set(this.selectedMachineRows[index], 'theoryPower', '')
|
|
|
|
|
|
}
|
|
|
|
|
|
},
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 行内单位变更
|
|
|
|
|
|
*/
|
|
|
|
|
|
handleRowUnitChange(index, value) {
|
|
|
|
|
|
this.$set(this.selectedMachineRows[index], 'unit', value)
|
|
|
|
|
|
},
|
|
|
|
|
|
syncMaxLeaseDaysToRows() {
|
|
|
|
|
|
const raw = this.form.maxLeaseDays
|
|
|
|
|
|
const n = Number(raw)
|
|
|
|
|
|
if (!Number.isInteger(n)) return
|
|
|
|
|
|
const oldBaseline = this.lastMaxLeaseDaysBaseline
|
|
|
|
|
|
this.selectedMachineRows = this.selectedMachineRows.map(row => {
|
|
|
|
|
|
const rowNum = Number(row.maxLeaseDays)
|
|
|
|
|
|
if (!Number.isInteger(rowNum) || rowNum === oldBaseline) {
|
|
|
|
|
|
return { ...row, maxLeaseDays: n }
|
|
|
|
|
|
}
|
|
|
|
|
|
return row
|
|
|
|
|
|
})
|
|
|
|
|
|
this.lastMaxLeaseDaysBaseline = n
|
|
|
|
|
|
},
|
|
|
|
|
|
handleRowMaxLeaseDaysInput(index) {
|
|
|
|
|
|
let v = String(this.selectedMachineRows[index].maxLeaseDays ?? '')
|
|
|
|
|
|
v = v.replace(/\D/g, '')
|
|
|
|
|
|
if (v.length > 3) v = v.slice(0, 3)
|
|
|
|
|
|
this.$set(this.selectedMachineRows[index], 'maxLeaseDays', v)
|
|
|
|
|
|
},
|
|
|
|
|
|
handleRowMaxLeaseDaysBlur(index) {
|
|
|
|
|
|
const raw = String(this.selectedMachineRows[index].maxLeaseDays ?? '')
|
|
|
|
|
|
if (!/^\d{1,3}$/.test(raw)) {
|
|
|
|
|
|
this.$message.warning('最大租赁天数需为 1-365 的整数')
|
|
|
|
|
|
this.$set(this.selectedMachineRows[index], 'maxLeaseDays', '')
|
|
|
|
|
|
return
|
|
|
|
|
|
}
|
|
|
|
|
|
const n = Number(raw)
|
|
|
|
|
|
if (!Number.isInteger(n) || n < 1 || n > 365) {
|
|
|
|
|
|
this.$message.warning('最大租赁天数需为 1-365 的整数')
|
|
|
|
|
|
this.$set(this.selectedMachineRows[index], 'maxLeaseDays', '')
|
|
|
|
|
|
}
|
|
|
|
|
|
},
|
2025-09-26 16:40:38 +08:00
|
|
|
|
handleRowPriceInput(index) {
|
|
|
|
|
|
// 价格输入:整数最多12位,小数最多2位;允许尾随小数点
|
|
|
|
|
|
let v = String(this.selectedMachineRows[index].price ?? '')
|
|
|
|
|
|
v = v.replace(/[^0-9.]/g, '')
|
|
|
|
|
|
const firstDot = v.indexOf('.')
|
|
|
|
|
|
if (firstDot !== -1) {
|
|
|
|
|
|
v = v.slice(0, firstDot + 1) + v.slice(firstDot + 1).replace(/\./g, '')
|
|
|
|
|
|
}
|
|
|
|
|
|
const endsWithDot = v.endsWith('.')
|
|
|
|
|
|
const parts = v.split('.')
|
|
|
|
|
|
let intPart = parts[0] || ''
|
|
|
|
|
|
let decPart = parts[1] || ''
|
|
|
|
|
|
if (intPart.length > 12) { intPart = intPart.slice(0, 12) }
|
|
|
|
|
|
if (decPart) { decPart = decPart.slice(0, 2) }
|
|
|
|
|
|
v = decPart.length ? `${intPart}.${decPart}` : (endsWithDot ? `${intPart}.` : intPart)
|
|
|
|
|
|
this.$set(this.selectedMachineRows[index], 'price', v)
|
|
|
|
|
|
},
|
2025-11-07 16:30:03 +08:00
|
|
|
|
/** 行内多结算币种价格输入 */
|
|
|
|
|
|
handleRowPriceMapInput(index, key) {
|
|
|
|
|
|
// 价格输入:整数最多12位,小数最多2位;允许尾随小数点
|
|
|
|
|
|
const row = this.selectedMachineRows[index]
|
|
|
|
|
|
const map = { ...(row.priceMap || {}) }
|
|
|
|
|
|
let v = String(map[key] ?? '')
|
|
|
|
|
|
v = v.replace(/[^0-9.]/g, '')
|
|
|
|
|
|
const firstDot = v.indexOf('.')
|
|
|
|
|
|
if (firstDot !== -1) {
|
|
|
|
|
|
v = v.slice(0, firstDot + 1) + v.slice(firstDot + 1).replace(/\./g, '')
|
|
|
|
|
|
}
|
|
|
|
|
|
const endsWithDot = v.endsWith('.')
|
|
|
|
|
|
const parts = v.split('.')
|
|
|
|
|
|
let intPart = parts[0] || ''
|
|
|
|
|
|
let decPart = parts[1] || ''
|
|
|
|
|
|
if (intPart.length > 12) intPart = intPart.slice(0, 12)
|
|
|
|
|
|
if (decPart) decPart = decPart.slice(0, 2)
|
|
|
|
|
|
v = decPart.length ? `${intPart}.${decPart}` : (endsWithDot ? `${intPart}.` : intPart)
|
|
|
|
|
|
map[key] = v
|
|
|
|
|
|
this.$set(this.selectedMachineRows[index], 'priceMap', map)
|
|
|
|
|
|
},
|
|
|
|
|
|
handleRowPriceMapBlur(index, key) {
|
|
|
|
|
|
const row = this.selectedMachineRows[index]
|
|
|
|
|
|
const raw = String((row.priceMap && row.priceMap[key]) ?? '')
|
|
|
|
|
|
const pattern = /^\d{1,12}(\.\d{1,2})?$/
|
|
|
|
|
|
if (!raw || Number(raw) <= 0 || !pattern.test(raw)) {
|
|
|
|
|
|
this.$message.warning('价格必须大于0,整数最多12位,小数最多2位')
|
|
|
|
|
|
const map = { ...(row.priceMap || {}) }
|
|
|
|
|
|
map[key] = ''
|
|
|
|
|
|
this.$set(this.selectedMachineRows[index], 'priceMap', map)
|
|
|
|
|
|
}
|
|
|
|
|
|
},
|
2025-09-26 16:40:38 +08:00
|
|
|
|
handleRowPriceBlur(index) {
|
|
|
|
|
|
const raw = String(this.selectedMachineRows[index].price ?? '')
|
|
|
|
|
|
const pattern = /^\d{1,12}(\.\d{1,2})?$/
|
|
|
|
|
|
if (!raw || Number(raw) <= 0 || !pattern.test(raw)) {
|
|
|
|
|
|
this.$message.warning('价格必须大于0,整数最多12位,小数最多2位')
|
|
|
|
|
|
this.$set(this.selectedMachineRows[index], 'price', '')
|
|
|
|
|
|
}
|
|
|
|
|
|
},
|
|
|
|
|
|
handleRowTypeInput(index) {
|
|
|
|
|
|
// 处理矿机型号输入
|
|
|
|
|
|
const raw = String(this.selectedMachineRows[index].type || '')
|
|
|
|
|
|
const v = raw.length > 20 ? raw.slice(0, 20) : raw
|
|
|
|
|
|
this.$set(this.selectedMachineRows[index], 'type', v)
|
|
|
|
|
|
},
|
|
|
|
|
|
handleRowTypeBlur(index) {
|
|
|
|
|
|
const raw = this.selectedMachineRows[index].type
|
|
|
|
|
|
const isOnlySpaces = (v) => typeof v === 'string' && v.length > 0 && v.trim().length === 0
|
|
|
|
|
|
if (isOnlySpaces(raw)) {
|
|
|
|
|
|
this.$message.warning('矿机型号不能全是空格')
|
|
|
|
|
|
this.$set(this.selectedMachineRows[index], 'type', '')
|
|
|
|
|
|
}
|
|
|
|
|
|
},
|
|
|
|
|
|
handleToggleState(index) {
|
|
|
|
|
|
// 切换上下架状态:0上架,1下架
|
|
|
|
|
|
const currentState = this.selectedMachineRows[index].state
|
|
|
|
|
|
this.$set(this.selectedMachineRows[index], 'state', currentState === 0 ? 1 : 0)
|
|
|
|
|
|
},
|
|
|
|
|
|
async fetchMiners() {
|
|
|
|
|
|
this.minersLoading = true
|
|
|
|
|
|
try {
|
|
|
|
|
|
// 按商品币种筛选挖矿账户
|
|
|
|
|
|
const res = await getUserMinersList({ coin: this.form.coin || "" })
|
|
|
|
|
|
const data = res?.data
|
|
|
|
|
|
let list = []
|
|
|
|
|
|
if (Array.isArray(data)) {
|
|
|
|
|
|
list = data
|
|
|
|
|
|
} else if (data && typeof data === 'object') {
|
|
|
|
|
|
// 现在的结构是 { coin: [ { user, coin }, ... ], coin2: [...] }
|
|
|
|
|
|
Object.keys(data).forEach(coinKey => {
|
|
|
|
|
|
const arr = Array.isArray(data[coinKey]) ? data[coinKey] : []
|
|
|
|
|
|
arr.forEach(item => {
|
|
|
|
|
|
if (item && item.user && item.coin) {
|
|
|
|
|
|
list.push({ user: item.user, coin: item.coin, miner: item.miner || null })
|
|
|
|
|
|
}
|
|
|
|
|
|
})
|
|
|
|
|
|
})
|
|
|
|
|
|
} else if (data && data.additionalProperties1) {
|
|
|
|
|
|
list = [data.additionalProperties1]
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 如页面带了 product coin,则仅展示该币种的账户
|
|
|
|
|
|
if (this.form.coin) {
|
|
|
|
|
|
list = list.filter(i => i.coin === this.form.coin)
|
|
|
|
|
|
}
|
|
|
|
|
|
this.miners = list
|
|
|
|
|
|
} catch (e) {
|
|
|
|
|
|
console.error('获取挖矿账户失败', e)
|
|
|
|
|
|
} finally {
|
|
|
|
|
|
this.minersLoading = false
|
|
|
|
|
|
}
|
|
|
|
|
|
},
|
|
|
|
|
|
async handleMinerChange(val) {
|
|
|
|
|
|
this.selectedMachines = []
|
|
|
|
|
|
if (!val) {
|
|
|
|
|
|
this.machineOptions = []
|
|
|
|
|
|
return
|
|
|
|
|
|
}
|
|
|
|
|
|
const [user, coin] = val.split('|')
|
|
|
|
|
|
this.machinesLoading = true
|
|
|
|
|
|
try {
|
|
|
|
|
|
// 按照API文档要求,传递 userMinerVo 对象
|
|
|
|
|
|
const userMinerVo = {
|
|
|
|
|
|
coin: coin,
|
|
|
|
|
|
user: user
|
|
|
|
|
|
}
|
|
|
|
|
|
const res = await getUserMachineList(userMinerVo)
|
|
|
|
|
|
const data = res?.data || []
|
|
|
|
|
|
this.machineOptions = Array.isArray(data) ? data : []
|
|
|
|
|
|
|
|
|
|
|
|
// 调试信息
|
|
|
|
|
|
console.log('选择挖矿账户:', { user, coin })
|
|
|
|
|
|
console.log('获取机器列表响应:', res)
|
|
|
|
|
|
console.log('机器列表数据:', this.machineOptions)
|
|
|
|
|
|
} catch (e) {
|
|
|
|
|
|
console.error('获取机器列表失败', e)
|
2025-10-20 10:15:13 +08:00
|
|
|
|
|
2025-09-26 16:40:38 +08:00
|
|
|
|
} finally {
|
|
|
|
|
|
this.machinesLoading = false
|
|
|
|
|
|
}
|
|
|
|
|
|
},
|
|
|
|
|
|
async handleSave() {
|
|
|
|
|
|
// 表单校验(除矿机型号外其他必填)
|
|
|
|
|
|
try {
|
|
|
|
|
|
const ok = await this.$refs.machineForm.validate()
|
|
|
|
|
|
if (!ok) {
|
|
|
|
|
|
return
|
|
|
|
|
|
}
|
|
|
|
|
|
} catch (e) {
|
|
|
|
|
|
return
|
|
|
|
|
|
}
|
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 则提示)
|
|
|
|
|
|
const raw = String(this.form.sellCount ?? '')
|
|
|
|
|
|
if (raw === '') {
|
|
|
|
|
|
this.$message.warning('请输入出售机器数量')
|
|
|
|
|
|
return
|
|
|
|
|
|
}
|
|
|
|
|
|
const n = Number(raw)
|
|
|
|
|
|
if (!Number.isInteger(n) || n < 0 || n > 9999) {
|
|
|
|
|
|
this.$message.warning('出售机器数量需为 0-9999 的整数')
|
|
|
|
|
|
return
|
|
|
|
|
|
}
|
|
|
|
|
|
if (n === 0) {
|
|
|
|
|
|
this.$message.warning('出售机器数量为 0,无需提交')
|
|
|
|
|
|
return
|
|
|
|
|
|
}
|
2025-09-26 16:40:38 +08:00
|
|
|
|
}
|
|
|
|
|
|
// 校验:矿机型号不可全空格(允许为空或包含空格的正常文本)
|
|
|
|
|
|
const isOnlySpaces = (v) => typeof v === 'string' && v.length > 0 && v.trim().length === 0
|
|
|
|
|
|
if (isOnlySpaces(this.form.type)) {
|
|
|
|
|
|
this.$message.warning('矿机型号不能全是空格')
|
|
|
|
|
|
return
|
|
|
|
|
|
}
|
|
|
|
|
|
const invalidTypeRowIndex = this.selectedMachineRows.findIndex(r => isOnlySpaces(r.type))
|
|
|
|
|
|
if (invalidTypeRowIndex !== -1) {
|
|
|
|
|
|
this.$message.warning('存在行的矿机型号全是空格,请修正后再试')
|
|
|
|
|
|
return
|
|
|
|
|
|
}
|
2025-11-21 16:23:46 +08:00
|
|
|
|
// 统一售价与最大租赁天数已在表单级校验中处理,无需逐机校验
|
2025-11-28 15:30:36 +08:00
|
|
|
|
// 组装确认数据并弹框
|
|
|
|
|
|
const coinStr = this.normalizeCsv(this.form.coinsInput || this.form.coin, true)
|
|
|
|
|
|
const algoStr = this.normalizeCsv(this.form.algorithmsInput, true)
|
|
|
|
|
|
this.confirmData = {
|
|
|
|
|
|
coin: coinStr || '-',
|
|
|
|
|
|
algorithm: algoStr || '-',
|
|
|
|
|
|
maxLeaseDays: this.form.maxLeaseDays,
|
|
|
|
|
|
saleNumbers: this.form.sellCount,
|
|
|
|
|
|
priceList: this.buildPriceList()
|
|
|
|
|
|
}
|
2025-09-26 16:40:38 +08:00
|
|
|
|
this.confirmVisible = true
|
|
|
|
|
|
}
|
|
|
|
|
|
,
|
|
|
|
|
|
async doSubmit() {
|
|
|
|
|
|
this.saving = true
|
|
|
|
|
|
try {
|
2025-11-28 15:30:36 +08:00
|
|
|
|
// 统一售卖新增接口参数
|
|
|
|
|
|
const payload = {
|
|
|
|
|
|
// 逗号分隔(中英文逗号都兼容),统一为英文逗号并大写
|
|
|
|
|
|
coin: this.normalizeCsv(this.form.coinsInput || this.form.coin, true),
|
|
|
|
|
|
algorithm: this.normalizeCsv(this.form.algorithmsInput, true),
|
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,
|
|
|
|
|
|
theoryPower: Number(this.form.theoryPower) || 0,
|
2025-09-26 16:40:38 +08:00
|
|
|
|
unit: this.form.unit,
|
2025-11-28 15:30:36 +08:00
|
|
|
|
saleNumbers: Number(this.form.sellCount) || 0,
|
|
|
|
|
|
priceList: this.buildPriceList()
|
2025-09-26 16:40:38 +08:00
|
|
|
|
}
|
2025-11-28 15:30:36 +08:00
|
|
|
|
// 过滤空价目
|
|
|
|
|
|
payload.priceList = (payload.priceList || []).filter(p => Number(p.price) > 0)
|
2025-09-26 16:40:38 +08:00
|
|
|
|
|
|
|
|
|
|
console.log(payload,"请求参数")
|
|
|
|
|
|
|
2025-11-28 15:30:36 +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({
|
|
|
|
|
|
message: '添加成功',
|
|
|
|
|
|
duration: 3000,
|
|
|
|
|
|
showClose: true,
|
|
|
|
|
|
type: 'success'
|
|
|
|
|
|
})
|
2025-09-26 16:40:38 +08:00
|
|
|
|
this.confirmVisible = false
|
2025-11-28 15:30:36 +08:00
|
|
|
|
this.$router.push('/account/products')
|
2025-10-20 10:15:13 +08:00
|
|
|
|
}
|
2025-09-26 16:40:38 +08:00
|
|
|
|
} catch (e) {
|
|
|
|
|
|
console.error('添加出售机器失败', e)
|
|
|
|
|
|
console.log('添加失败')
|
|
|
|
|
|
} finally {
|
|
|
|
|
|
this.saving = false
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
2025-11-21 16:23:46 +08:00
|
|
|
|
|
2025-09-26 16:40:38 +08:00
|
|
|
|
}
|
|
|
|
|
|
</script>
|
|
|
|
|
|
|
|
|
|
|
|
<style scoped>
|
|
|
|
|
|
.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; }
|
2025-10-20 10:15:13 +08:00
|
|
|
|
.notice-alert { margin-bottom: 12px; }
|
|
|
|
|
|
.notice-alert :deep(.el-alert__content) { text-align: left; }
|
|
|
|
|
|
.notice-alert :deep(.el-alert__title),
|
|
|
|
|
|
.notice-alert :deep(.el-alert__description) { text-align: left; }
|
|
|
|
|
|
.label-help { margin-left: 4px; color: #909399; cursor: help; }
|
2025-09-26 16:40:38 +08:00
|
|
|
|
.form-card { margin-bottom: 12px; }
|
2025-11-21 16:23:46 +08:00
|
|
|
|
.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;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
::v-deep .el-form-item__content{
|
|
|
|
|
|
text-align: left;
|
|
|
|
|
|
padding-left: 18px !important;
|
|
|
|
|
|
}
|
2025-11-07 16:30:03 +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; }
|
|
|
|
|
|
/* 让 链-币种 附加区同宽、居中显示,整体对齐 */
|
|
|
|
|
|
.price-item :deep(.el-input-group__append),
|
|
|
|
|
|
.cost-item :deep(.el-input-group__append){
|
|
|
|
|
|
width: 110px;
|
|
|
|
|
|
min-width: 110px;
|
|
|
|
|
|
text-align: center;
|
|
|
|
|
|
padding: 0 8px;
|
|
|
|
|
|
background: #f8fafc;
|
|
|
|
|
|
color: #606266;
|
|
|
|
|
|
}
|
|
|
|
|
|
/* 缩小输入高度并保持垂直居中 */
|
|
|
|
|
|
.price-item :deep(.el-input__inner),
|
|
|
|
|
|
.cost-item :deep(.el-input__inner){
|
|
|
|
|
|
height: 30px;
|
|
|
|
|
|
line-height: 30px;
|
|
|
|
|
|
}
|
|
|
|
|
|
/* 让组内附加区高度与输入一致 */
|
|
|
|
|
|
.price-item :deep(.el-input-group__append),
|
|
|
|
|
|
.cost-item :deep(.el-input-group__append){
|
|
|
|
|
|
height: 30px;
|
|
|
|
|
|
line-height: 30px;
|
|
|
|
|
|
}
|
|
|
|
|
|
/* 略微收紧间距,让整体更紧凑 */
|
|
|
|
|
|
.price-multi { gap: 6px; }
|
|
|
|
|
|
.price-items { gap: 6px; }
|
|
|
|
|
|
.cost-multi { gap: 6px; }
|
2025-09-26 16:40:38 +08:00
|
|
|
|
</style>
|
|
|
|
|
|
|