需求更改开发中

This commit is contained in:
2025-10-20 10:15:13 +08:00
parent d1b3357a8e
commit a60603acd0
30 changed files with 2863 additions and 704 deletions

View File

@@ -5,35 +5,25 @@
<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 />
</el-form-item>
<el-form-item label="功耗" prop="powerDissipation">
<el-input
v-model="form.powerDissipation"
placeholder="示例0.01"
inputmode="decimal"
@input="handleNumeric('powerDissipation')"
>
<template slot="append">kw/h</template>
</el-input>
</el-form-item>
<el-form-item label="机器成本价格" prop="cost">
<el-input
v-model="form.cost"
placeholder="请输入成本USDT"
inputmode="decimal"
@input="handleNumeric('cost')"
>
<template slot="append">USDT</template>
</el-input>
<el-input v-model="form.productName" disabled style="width: 50%;" />
</el-form-item>
<el-form-item label="矿机型号">
<el-input v-model="form.type" placeholder="示例:龍珠" :maxlength="20" @input="handleTypeInput" />
<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
@@ -41,16 +31,65 @@
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">
@@ -69,17 +108,77 @@
<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="挖矿账户" min-width="160" />
<el-table-column prop="miner" label="机器编号" min-width="160" />
<el-table-column label="价格(USDT)" min-width="220">
<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: 70%;"
style="width: 100%;"
>
<template slot="append">USDT</template>
</el-input>
@@ -87,7 +186,21 @@
</template>
</el-table-column>
<el-table-column label="矿机型号" min-width="200">
<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"
@@ -95,11 +208,11 @@
@input="handleRowTypeInput(scope.$index)"
@blur="handleRowTypeBlur(scope.$index)"
:maxlength="20"
style="width: 70%;"
style="width: 100%;"
/>
</template>
</el-table-column>
<el-table-column label="上下架状态" min-width="120">
<el-table-column label="上下架状态" width="100">
<template #default="scope">
<el-button
:type="scope.row.state === 0 ? 'success' : 'info'"
@@ -151,7 +264,8 @@ export default {
theoryPower: null,
type: '',
unit: 'TH/S',
cost: ''
cost: '',
maxLeaseDays: ''
},
confirmVisible: false,
rules: {
@@ -210,6 +324,21 @@ export default {
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: [
// {
@@ -293,6 +422,10 @@ export default {
saving: false,
lastCostBaseline: 0,
lastTypeBaseline: '',
lastMaxLeaseDaysBaseline: 0,
lastPowerDissipationBaseline: 0,
lastTheoryPowerBaseline: 0,
lastUnitBaseline: 'TH/S',
params:{
cost:353400,
powerDissipation:0.01,
@@ -364,6 +497,13 @@ export default {
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) {
@@ -427,14 +567,164 @@ export default {
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 // 默认上架
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 ?? '')
@@ -537,7 +827,7 @@ export default {
console.log('机器列表数据:', this.machineOptions)
} catch (e) {
console.error('获取机器列表失败', e)
this.$message.error('获取机器列表失败,请重试')
} finally {
this.machinesLoading = false
}
@@ -584,6 +874,14 @@ export default {
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
@@ -600,12 +898,17 @@ export default {
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
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
}))
}
@@ -616,9 +919,7 @@ export default {
this.$message.success('添加成功')
this.confirmVisible = false
this.$router.back()
} else {
this.$message.error(res?.msg || '添加失败')
}
}
} catch (e) {
console.error('添加出售机器失败', e)
console.log('添加失败')
@@ -631,6 +932,10 @@ export default {
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()
}
@@ -642,6 +947,11 @@ export default {
.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; }
@@ -649,11 +959,11 @@ export default {
.product-machine-add :deep(.el-form-item__content) {
justify-content: flex-start;
}
.product-machine-add :deep(.el-input),
/* .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;