Files
webs/power_leasing/src/views/account/productMachineAdd.vue
2025-10-31 14:09:58 +08:00

985 lines
36 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<template>
<div class="product-machine-add">
<div class="header">
<el-button type="text" @click="handleBack">返回</el-button>
<h2 class="title">添加出售机器</h2>
</div>
<el-alert
class="notice-alert"
type="warning"
show-icon
:closable="false"
title="新增出售机器必须在 M2pool 有挖矿算力记录才能添加出租"
description="建议稳定在 M2pool 矿池挖矿 24 小时之后,再添加出售该机器"
/>
<el-card shadow="never" class="form-card">
<el-form ref="machineForm" :model="form" :rules="rules" label-width="160px" size="small">
<el-form-item label="商品名称">
<el-input v-model="form.productName" disabled style="width: 50%;" />
</el-form-item>
<el-form-item label="矿机型号">
<el-input style="width: 50%;" v-model="form.type" placeholder="示例:龍珠" :maxlength="20" @input="handleTypeInput" />
</el-form-item>
<el-form-item label="理论算力" prop="theoryPower">
<el-input
v-model="form.theoryPower"
placeholder="请输入单机理论算力"
inputmode="decimal"
@input="handleNumeric('theoryPower')"
style="width: 50%;"
/>
</el-form-item>
<el-form-item label="算力单位" prop="unit">
<el-select v-model="form.unit" placeholder="请选择算力单位">
<el-option label="KH/S" value="KH/S" />
<el-option label="MH/S" value="MH/S" />
<el-option label="GH/S" value="GH/S" />
<el-option label="TH/S" value="TH/S" />
<el-option label="PH/S" value="PH/S" />
</el-select>
</el-form-item>
<el-form-item label="最大租赁天数" prop="maxLeaseDays">
<el-input
v-model="form.maxLeaseDays"
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
v-model="form.powerDissipation"
placeholder="示例0.01"
inputmode="decimal"
@input="handleNumeric('powerDissipation')"
style="width: 50%;"
>
<template slot="append">kw/h</template>
</el-input>
</el-form-item>
<el-form-item label="统一售价" prop="cost">
<span slot="label">
统一售价
<el-tooltip effect="dark" placement="top">
<div slot="content">
卖家最终收款金额 = 机器售价 × 波动率<br/>
波动率规则<br/>
10% - 5%包含5%波动率 = 1按售价结算<br/>
25%以上波动率 = 实际算力 / 理论算力且不会超过 1,即最终结算时不会超过机器售价
</div>
<i class="el-icon-question label-help" aria-label="帮助" tabindex="0"></i>
</el-tooltip>
</span>
<el-input
v-model="form.cost"
placeholder="请输入成本USDT"
inputmode="decimal"
@input="handleNumeric('cost')"
style="width: 50%;"
>
<template slot="append">USDT</template>
</el-input>
</el-form-item>
<el-form-item label="选择挖矿账户">
<el-select v-model="selectedMiner" filterable clearable placeholder="请选择挖矿账户" @change="handleMinerChange" :loading="minersLoading">
<el-option v-for="m in miners" :key="m.user + '_' + m.coin" :label="m.user + '' + m.coin + ''" :value="m.user + '|' + m.coin" />
</el-select>
</el-form-item>
<el-form-item label="选择机器(可多选)">
<el-select v-model="selectedMachines" multiple filterable collapse-tags placeholder="请选择机器" :loading="machinesLoading" :disabled="!selectedMiner">
<el-option v-for="m in machineOptions" :key="m.user + '_' + m.miner" :label="m.miner + '' + m.user + ''" :value="m.miner" />
</el-select>
</el-form-item>
</el-form>
</el-card>
<!-- 已选择机器列表 -->
<el-card shadow="never" class="form-card" v-if="selectedMachineRows.length">
<div slot="header" class="section-title">已选择机器</div>
<el-table :data="selectedMachineRows" border stripe style="width: 100%">
<el-table-column prop="user" label="挖矿账户" />
<el-table-column prop="miner" label="机器编号" />
<el-table-column prop="realPower" label="实际算力(MH/S)">
<template slot="header">
<el-tooltip content="实际算力为该机器在本矿池过去24H的平均算力" effect="dark" placement="top">
<i class="el-icon-question" style="margin-right: 4px; color: #909399;" aria-label="帮助" tabindex="0"></i>
</el-tooltip>
<span>实际算力(MH/S)</span>
</template>
</el-table-column>
<el-table-column label="功耗(kw/h)" min-width="120">
<template #default="scope">
<el-input
v-model="scope.row.powerDissipation"
placeholder="示例0.01"
inputmode="decimal"
@input="handleRowPowerDissipationInput(scope.$index)"
@blur="handleRowPowerDissipationBlur(scope.$index)"
style="width: 100%;"
>
<template slot="append">kw/h</template>
</el-input>
</template>
</el-table-column>
<el-table-column label="理论算力" min-width="160">
<template #default="scope">
<div style="display: flex; align-items: center; gap: 8px;">
<el-input
v-model="scope.row.theoryPower"
placeholder="理论算力"
inputmode="decimal"
@input="handleRowTheoryPowerInput(scope.$index)"
@blur="handleRowTheoryPowerBlur(scope.$index)"
style="width: 100%"
/>
<el-select
v-model="scope.row.unit"
placeholder="单位"
style="width:150px;"
@change="val => handleRowUnitChange(scope.$index, val)"
>
<el-option label="KH/S" value="KH/S" />
<el-option label="MH/S" value="MH/S" />
<el-option label="GH/S" value="GH/S" />
<el-option label="TH/S" value="TH/S" />
<el-option label="PH/S" value="PH/S" />
</el-select>
</div>
</template>
</el-table-column>
<el-table-column label="售价(USDT)" min-width="160">
<template slot="header">
<el-tooltip effect="dark" placement="top">
<div slot="content">
卖家最终收款金额 = 机器售价 × 波动率<br/>
波动率规则<br/>
10% - 5%包含5%波动率 = 1按售价结算<br/>
25%以上波动率 = 实际算力 / 理论算力且不会超过 1,即最终结算时不会超过机器售价
</div>
<i class="el-icon-question label-help" aria-label="帮助" tabindex="0"></i>
</el-tooltip>
<span>售价(USDT)</span>
</template>
<template slot-scope="scope">
<el-input
v-model="scope.row.price"
placeholder="价格"
inputmode="decimal"
@input="handleRowPriceInput(scope.$index)"
@blur="handleRowPriceBlur(scope.$index)"
style="width: 100%;"
>
<template slot="append">USDT</template>
</el-input>
</template>
</el-table-column>
<el-table-column label="最大租赁天数(天)" min-width="120">
<template #default="scope">
<el-input
v-model="scope.row.maxLeaseDays"
placeholder="1-365"
inputmode="numeric"
@input="handleRowMaxLeaseDaysInput(scope.$index)"
@blur="handleRowMaxLeaseDaysBlur(scope.$index)"
style="width: 100%;"
>
<template slot="append"></template>
</el-input>
</template>
</el-table-column>
<el-table-column label="矿机型号">
<template #default="scope">
<el-input
v-model="scope.row.type"
placeholder="矿机型号"
@input="handleRowTypeInput(scope.$index)"
@blur="handleRowTypeBlur(scope.$index)"
:maxlength="20"
style="width: 100%;"
/>
</template>
</el-table-column>
<el-table-column label="上下架状态" width="100">
<template #default="scope">
<el-button
:type="scope.row.state === 0 ? 'success' : 'info'"
size="mini"
@click="handleToggleState(scope.$index)"
>
{{ scope.row.state === 0 ? '上架' : '下架' }}
</el-button>
</template>
</el-table-column>
</el-table>
</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"
width="400px"
>
<div>
<p>请仔细确认已选择机器列表价格及相关参数定义</p>
<p style="text-align: left;">机器上架后一经售出在机器出售期间不能修改价格及机器参数</p>
</div>
<span slot="footer" class="dialog-footer">
<el-button @click="confirmVisible = false">取消</el-button>
<el-button type="primary" :loading="saving" @click="doSubmit">确认上架已选择机器</el-button>
</span>
</el-dialog>
</div>
</template>
<script>
import { getUserMinersList, getUserMachineList, addSingleOrBatchMachine } from '../../api/machine'
export default {
name: 'AccountProductMachineAdd',
data() {
return {
form: {
productId: Number(this.$route.query.productId) || null,
coin: this.$route.query.coin || '',
productName: this.$route.query.name || '',
powerDissipation: null,
theoryPower: null,
type: '',
unit: 'TH/S',
cost: '',
maxLeaseDays: ''
},
confirmVisible: false,
rules: {
productName: [ { required: true, message: '商品名称不能为空', trigger: 'change' } ],
coin: [ { required: true, message: '币种不能为空', trigger: 'change' } ],
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: [
{ required: true, message: '请填写机器成本USDT', trigger: 'blur' },
{
validator: (rule, value, callback) => {
const str = String(value || '')
if (!str) {
callback(new Error('请填写机器成本USDT'))
return
}
// 整数最多12位小数最多2位
const pattern = /^\d{1,12}(\.\d{1,2})?$/
if (!pattern.test(str)) {
callback(new Error('成本整数最多12位小数最多2位'))
return
}
if (Number(str) <= 0) {
callback(new Error('成本必须大于 0'))
return
}
callback()
},
trigger: 'blur'
}
]
,
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'
}
]
},
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,
lastTypeBaseline: '',
lastMaxLeaseDaysBaseline: 0,
lastPowerDissipationBaseline: 0,
lastTheoryPowerBaseline: 0,
lastUnitBaseline: 'TH/S',
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() {
this.fetchMiners()
this.lastTypeBaseline = this.form.type
},
methods: {
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)
} 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
} else {
// 其他最多6位小数保持原有逻辑
if (firstDot !== -1) {
const [intPart, decPart] = v.split('.')
v = intPart + '.' + (decPart ? decPart.slice(0, 6) : '')
}
}
this.form[key] = v
if (key === 'cost') {
this.syncCostToRows()
}
},
/**
* 顶部矿机型号输入限制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() {
// 当外层矿机型号变动时,同步更新机器列表中的型号
// 但如果用户手动改过某行型号,则不覆盖
this.selectedMachineRows = this.selectedMachineRows.map(row => {
// 如果该行型号为空或等于旧型号,则更新为新型号
if (!row.type || row.type === this.lastTypeBaseline) {
return { ...row, type: this.form.type }
}
return row
})
this.lastTypeBaseline = this.form.type
},
updateSelectedMachineRows() {
// 依据 selectedMachines 与 machineOptions 同步生成行数据
const map = new Map()
this.machineOptions.forEach(m => {
map.set(m.miner, m)
})
const nextRows = []
this.selectedMachines.forEach(minerId => {
const m = map.get(minerId)
if (m) {
// 若已存在,沿用已编辑的价格、型号和状态
const existed = this.selectedMachineRows.find(r => r.miner === minerId)
nextRows.push({
user: m.user,
coin: m.coin,
miner: m.miner,
realPower: m.realPower,
price: existed ? existed.price : this.form.cost,
powerDissipation: existed && existed.powerDissipation !== undefined ? existed.powerDissipation : this.form.powerDissipation,
theoryPower: existed && existed.theoryPower !== undefined ? existed.theoryPower : this.form.theoryPower,
unit: existed && existed.unit ? existed.unit : this.form.unit,
type: existed ? existed.type : this.form.type,
state: existed ? existed.state : 0, // 默认上架
maxLeaseDays: existed && existed.maxLeaseDays !== undefined ? existed.maxLeaseDays : this.form.maxLeaseDays
})
}
})
this.selectedMachineRows = nextRows
},
/**
* 同步顶部功耗到行(行未自定义或无效则跟随)
*/
syncPowerDissipationToRows() {
const newVal = Number(this.form.powerDissipation)
if (!Number.isFinite(newVal)) return
const oldBaseline = this.lastPowerDissipationBaseline
this.selectedMachineRows = this.selectedMachineRows.map(row => {
const rowNum = Number(row.powerDissipation)
if (!Number.isFinite(rowNum) || rowNum === oldBaseline) {
return { ...row, powerDissipation: newVal }
}
return row
})
this.lastPowerDissipationBaseline = newVal
},
/**
* 同步顶部理论算力到行(行未自定义或无效则跟随)
*/
syncTheoryPowerToRows() {
const newVal = Number(this.form.theoryPower)
if (!Number.isFinite(newVal)) return
const oldBaseline = this.lastTheoryPowerBaseline
this.selectedMachineRows = this.selectedMachineRows.map(row => {
const rowNum = Number(row.theoryPower)
if (!Number.isFinite(rowNum) || rowNum === oldBaseline) {
return { ...row, theoryPower: newVal }
}
return row
})
this.lastTheoryPowerBaseline = newVal
},
/**
* 同步顶部单位到行(行未自定义或等于旧基线时跟随)
*/
syncUnitToRows() {
const newUnit = this.form.unit
if (!newUnit) return
const oldBaseline = this.lastUnitBaseline
this.selectedMachineRows = this.selectedMachineRows.map(row => {
const rowUnit = row.unit
if (!rowUnit || rowUnit === oldBaseline) {
return { ...row, unit: newUnit }
}
return row
})
this.lastUnitBaseline = newUnit
},
/**
* 行内功耗输入限制整数最多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, '')
}
const parts = v.split('.')
let intPart = parts[0] || ''
let decPart = parts[1] || ''
if (intPart.length > 6) intPart = intPart.slice(0, 6)
if (decPart) decPart = decPart.slice(0, 4)
v = decPart.length ? `${intPart}.${decPart}` : intPart
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, '')
}
const parts = v.split('.')
let intPart = parts[0] || ''
let decPart = parts[1] || ''
if (intPart.length > 6) intPart = intPart.slice(0, 6)
if (decPart) decPart = decPart.slice(0, 4)
v = decPart.length ? `${intPart}.${decPart}` : intPart
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', '')
}
},
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)
},
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)
} finally {
this.machinesLoading = false
}
},
async handleSave() {
// 表单校验(除矿机型号外其他必填)
try {
const ok = await this.$refs.machineForm.validate()
if (!ok) {
return
}
} catch (e) {
return
}
if (!this.form.productId) {
this.$message.warning('缺少商品ID')
return
}
if (!this.selectedMiner) {
this.$message.warning('请先选择挖矿账户')
return
}
if (!this.selectedMachines.length) {
this.$message.warning('请至少选择一台机器')
return
}
// 校验:矿机型号不可全空格(允许为空或包含空格的正常文本)
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
}
// 校验已选择机器的价格必须大于0
for (let i = 0; i < this.selectedMachineRows.length; i += 1) {
const row = this.selectedMachineRows[i]
const priceNum = Number(row && row.price)
if (!Number.isFinite(priceNum) || priceNum <= 0) {
const label = (row && (row.miner || row.user)) || i + 1
this.$message.warning(`${i + 1}行(机器:${label}) 价格必须大于0`)
return
}
// 校验:逐行最大租赁天数 1-365
const rawDays = String((row && row.maxLeaseDays) ?? '')
const n = Number(rawDays)
if (!/^\d{1,3}$/.test(rawDays) || !Number.isInteger(n) || n < 1 || n > 365) {
const label = (row && (row.miner || row.user)) || i + 1
this.$message.warning(`${i + 1}行(机器:${label}) 最大租赁天数需为 1-365 的整数`)
return
}
}
// 通过所有预校验后,弹出确认框
this.confirmVisible = true
}
,
async doSubmit() {
const [user, coin] = this.selectedMiner.split('|')
this.saving = true
try {
const payload = {
productId: this.form.productId,
powerDissipation: this.form.powerDissipation,
theoryPower: this.form.theoryPower,
type: this.form.type,
unit: this.form.unit,
cost: this.form.cost,
maxLeaseDays: this.form.maxLeaseDays,
productMachineURDVos: this.selectedMachineRows.map(r => ({
miner: r.miner,
price: Number(r.price) || 0,
state: r.state || 0,
type: r.type || this.form.type,
user: r.user,
maxLeaseDays: Number(r.maxLeaseDays) || Number(this.form.maxLeaseDays) || 0,
powerDissipation: Number(r.powerDissipation) || Number(this.form.powerDissipation) || 0,
theoryPower: Number(r.theoryPower) || Number(this.form.theoryPower) || 0,
unit: r.unit || this.form.unit
}))
}
console.log(payload,"请求参数")
const res = await addSingleOrBatchMachine(payload)
if (res && (res.code === 0 || res.code === 200)) {
this.$message({
message: '添加成功',
duration: 3000,
showClose: true,
type: 'success'
})
this.confirmVisible = false
this.$router.back()
}
} catch (e) {
console.error('添加出售机器失败', e)
console.log('添加失败')
} finally {
this.saving = false
}
}
}
,
watch: {
'form.cost': function() { this.syncCostToRows() },
'form.type': function() { this.updateMachineType() },
'form.maxLeaseDays': function() { this.syncMaxLeaseDaysToRows() },
'form.powerDissipation': function() { this.syncPowerDissipationToRows() },
'form.theoryPower': function() { this.syncTheoryPowerToRows() },
'form.unit': function() { this.syncUnitToRows() },
selectedMachines() {
this.updateSelectedMachineRows()
}
}
}
</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; }
.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; }
.form-card { margin-bottom: 12px; }
.actions { text-align: right; }
/* 统一左对齐,控件宽度 50% */
.product-machine-add :deep(.el-form-item__content) {
justify-content: flex-start;
}
/* .product-machine-add :deep(.el-input),
.product-machine-add :deep(.el-select),
.product-machine-add :deep(.el-textarea) {
width: 50%;
} */
.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;
}
</style>