每周更新
This commit is contained in:
@@ -22,48 +22,74 @@
|
||||
<el-radio label="GPU">GPU</el-radio>
|
||||
</el-radio-group>
|
||||
</el-form-item>
|
||||
<!-- ASIC:币种与算法(支持多个,逗号分隔) -->
|
||||
<el-form-item label="币种(多个用逗号隔开)" prop="coinsInput" :required="form.machineCategory === 'ASIC'">
|
||||
<el-input
|
||||
v-model="form.coinsInput"
|
||||
placeholder="例如:USDT, BTC, ETH"
|
||||
style="width: 50%;"
|
||||
@input="handleCoinsInput"
|
||||
/>
|
||||
<!-- ASIC:币种/算法/理论算力/单位 同行,支持多行动态增删 -->
|
||||
<el-form-item
|
||||
v-if="form.machineCategory === 'ASIC'"
|
||||
label="币种/算法/算力/单位"
|
||||
prop="coinAndAlgoList"
|
||||
:required="true"
|
||||
>
|
||||
<div class="coin-algo-rows">
|
||||
<div
|
||||
class="coin-algo-line"
|
||||
v-for="(row, idx) in form.coinAndAlgoList"
|
||||
:key="idx"
|
||||
>
|
||||
<el-input
|
||||
v-model="row.coin"
|
||||
placeholder="币种"
|
||||
class="coin-input"
|
||||
@input="handleCoinInput(idx)"
|
||||
/>
|
||||
<el-input
|
||||
v-model="row.algorithm"
|
||||
placeholder="算法"
|
||||
class="algo-input"
|
||||
@input="handleAlgorithmInput(idx)"
|
||||
/>
|
||||
<el-input
|
||||
v-model="row.theoryPower"
|
||||
placeholder="理论算力"
|
||||
inputmode="decimal"
|
||||
class="power-input"
|
||||
@input="handleCoinRowTheoryInput(idx)"
|
||||
/>
|
||||
<el-select
|
||||
v-model="row.unit"
|
||||
placeholder="单位"
|
||||
class="unit-select"
|
||||
@change="handleCoinRowUnitChange(idx, $event)"
|
||||
>
|
||||
<el-option label="KH/S" value="KH/S" />
|
||||
<el-option label="MH/S" value="MH/S" />
|
||||
<el-option label="GH/S" value="GH/S" />
|
||||
<el-option label="TH/S" value="TH/S" />
|
||||
<el-option label="PH/S" value="PH/S" />
|
||||
</el-select>
|
||||
<el-button
|
||||
class="op-btn"
|
||||
type="primary"
|
||||
icon="el-icon-plus"
|
||||
circle
|
||||
@click="handleAddCoinAlgoRow"
|
||||
:aria-label="'新增一行'"
|
||||
/>
|
||||
<el-button
|
||||
v-if="form.coinAndAlgoList.length > 1"
|
||||
class="op-btn"
|
||||
icon="el-icon-minus"
|
||||
circle
|
||||
@click="handleRemoveCoinAlgoRow(idx)"
|
||||
:aria-label="'删除该行'"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</el-form-item>
|
||||
<el-form-item label="算法(多个用逗号隔开)" prop="algorithmsInput" :required="form.machineCategory === 'ASIC'">
|
||||
<el-input
|
||||
v-model="form.algorithmsInput"
|
||||
placeholder="例如:SHA-256, ETHASH"
|
||||
style="width: 50%;"
|
||||
@input="handleAlgorithmsInput"
|
||||
/>
|
||||
</el-form-item>
|
||||
<div style="text-align:left; color:#909399; font-size:12px; margin:-6px 0 10px 160px;">
|
||||
输入多个用逗号隔开
|
||||
</div>
|
||||
|
||||
<el-form-item label="矿机型号" prop="type" :required="true">
|
||||
<el-input style="width: 50%;" v-model="form.type" placeholder="示例:龍珠" :maxlength="20" @input="handleTypeInput" />
|
||||
</el-form-item>
|
||||
<el-form-item label="理论算力" prop="theoryPower">
|
||||
<el-input
|
||||
v-model="form.theoryPower"
|
||||
placeholder="请输入单机理论算力"
|
||||
inputmode="decimal"
|
||||
@input="handleNumeric('theoryPower')"
|
||||
style="width: 50%;"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item label="算力单位" prop="unit">
|
||||
<el-select v-model="form.unit" placeholder="请选择算力单位">
|
||||
<el-option label="KH/S" value="KH/S" />
|
||||
<el-option label="MH/S" value="MH/S" />
|
||||
<el-option label="GH/S" value="GH/S" />
|
||||
<el-option label="TH/S" value="TH/S" />
|
||||
<el-option label="PH/S" value="PH/S" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<!-- 理论算力与单位已合并到上面的同行多行输入 -->
|
||||
<el-form-item label="最大租赁天数" prop="maxLeaseDays">
|
||||
<el-input
|
||||
v-model="form.maxLeaseDays"
|
||||
@@ -197,6 +223,7 @@
|
||||
<script>
|
||||
import { addSingleOrBatchMachine ,downloadClient,addAsicMachine} from '../../api/machine'
|
||||
import { getPayTypes } from '../../api/products'
|
||||
import request from '../../utils/request'
|
||||
export default {
|
||||
name: 'AccountProductMachineAdd',
|
||||
data() {
|
||||
@@ -209,13 +236,12 @@ export default {
|
||||
machineCategory: 'ASIC',
|
||||
/** 出售机器数量(仅 ASIC 模式使用) */
|
||||
sellCount: '',
|
||||
/** ASIC 模式下币种/算法输入(逗号分隔的原始文本) */
|
||||
coinsInput: '',
|
||||
algorithmsInput: '',
|
||||
/** ASIC:币种/算法/理论算力/单位 行编辑(最多10行) */
|
||||
coinAndAlgoList: [
|
||||
{ coin: '', algorithm: '', theoryPower: '', unit: 'TH/S' }
|
||||
],
|
||||
powerDissipation: null,
|
||||
theoryPower: null,
|
||||
type: '',
|
||||
unit: 'TH/S',
|
||||
cost: '',
|
||||
costMap: {}, // { 'CHAIN-COIN': '123.45' }
|
||||
maxLeaseDays: ''
|
||||
@@ -245,48 +271,8 @@ export default {
|
||||
trigger: 'blur'
|
||||
}
|
||||
],
|
||||
coinsInput: [
|
||||
{
|
||||
validator: (rule, value, callback) => {
|
||||
if (String(this.form.machineCategory).toUpperCase() !== 'ASIC') { callback(); return }
|
||||
const s = String(value || '').trim()
|
||||
if (!s) { callback(new Error('请输入币种,多个用逗号隔开')); return }
|
||||
// 禁止汉字,且仅允许英数字与逗号/空格/连字符
|
||||
if (/[\u4e00-\u9fa5]/.test(s)) { callback(new Error('币种不允许输入汉字')); return }
|
||||
// 逐项校验(英文、数字,长度1-10)
|
||||
const tokens = s.split(/[,\s,、]+/).map(i => i.trim()).filter(Boolean)
|
||||
const pattern = /^[A-Za-z0-9]{1,10}$/
|
||||
for (let i = 0; i < tokens.length; i += 1) {
|
||||
if (!pattern.test(tokens[i])) {
|
||||
callback(new Error(`币种“${tokens[i]}”格式非法,仅允许字母或数字`))
|
||||
return
|
||||
}
|
||||
}
|
||||
callback()
|
||||
},
|
||||
trigger: 'blur'
|
||||
}
|
||||
],
|
||||
algorithmsInput: [
|
||||
{
|
||||
validator: (rule, value, callback) => {
|
||||
if (String(this.form.machineCategory).toUpperCase() !== 'ASIC') { callback(); return }
|
||||
const s = String(value || '').trim()
|
||||
if (!s) { callback(new Error('请输入算法,多个用逗号隔开')); return }
|
||||
if (/[\u4e00-\u9fa5]/.test(s)) { callback(new Error('算法不允许输入汉字')); return }
|
||||
// 逐项校验(字母/数字/连字符,2-20)
|
||||
const tokens = s.split(/[,\s,、]+/).map(i => i.trim()).filter(Boolean)
|
||||
const pattern = /^[A-Za-z0-9-]{2,20}$/
|
||||
for (let i = 0; i < tokens.length; i += 1) {
|
||||
if (!pattern.test(tokens[i])) {
|
||||
callback(new Error(`算法“${tokens[i]}”格式非法,仅允许字母、数字或“-”`))
|
||||
return
|
||||
}
|
||||
}
|
||||
callback()
|
||||
},
|
||||
trigger: 'blur'
|
||||
}
|
||||
coinAndAlgoList: [
|
||||
{ validator: (rule, value, callback) => this.validateCoinAlgoRows(rule, value, callback), trigger: 'blur' }
|
||||
],
|
||||
sellCount: [
|
||||
{
|
||||
@@ -316,21 +302,6 @@ export default {
|
||||
trigger: 'blur'
|
||||
}
|
||||
],
|
||||
theoryPower: [
|
||||
{ required: true, message: '理论算力不能为空', trigger: 'blur' },
|
||||
{
|
||||
validator: (rule, value, callback) => {
|
||||
const str = String(value || '')
|
||||
if (!str) { callback(new Error('理论算力不能为空')); return }
|
||||
const pattern = /^\d{1,6}(\.\d{1,4})?$/
|
||||
if (!pattern.test(str)) { callback(new Error('理论算力整数最多6位,小数最多4位')); return }
|
||||
if (Number(str) <= 0) { callback(new Error('理论算力必须大于0')); return }
|
||||
callback()
|
||||
},
|
||||
trigger: 'blur'
|
||||
}
|
||||
],
|
||||
unit: [ { required: true, message: '请选择算力单位', trigger: 'change' } ],
|
||||
cost: [
|
||||
{
|
||||
validator(rule, value, callback) {
|
||||
@@ -510,6 +481,109 @@ export default {
|
||||
this.getPayTypes()
|
||||
},
|
||||
methods: {
|
||||
/** ASIC 行校验:币种/算法/理论算力/单位 */
|
||||
validateCoinAlgoRows(rule, value, callback) {
|
||||
try {
|
||||
const rows = Array.isArray(this.form.coinAndAlgoList) ? this.form.coinAndAlgoList : []
|
||||
if (!rows.length) { callback(new Error('请至少添加一行币种/算法/算力/单位')); return }
|
||||
const coinPattern = /^[A-Za-z0-9]{1,10}$/
|
||||
const algoPattern = /^[A-Za-z0-9-]{2,20}$/
|
||||
const powerPattern = /^\d{1,6}(\.\d{1,4})?$/
|
||||
for (let i = 0; i < rows.length; i += 1) {
|
||||
const r = rows[i] || {}
|
||||
const coin = String(r.coin || '').trim()
|
||||
const algo = String(r.algorithm || '').trim()
|
||||
const power = String(r.theoryPower || '').trim()
|
||||
const unit = String(r.unit || '').trim()
|
||||
if (!coin) { callback(new Error(`第 ${i + 1} 行:请输入币种`)); return }
|
||||
if (!coinPattern.test(coin)) { callback(new Error(`第 ${i + 1} 行:币种仅允许字母或数字,1-10 位`)); return }
|
||||
if (!algo) { callback(new Error(`第 ${i + 1} 行:请输入算法`)); return }
|
||||
if (!algoPattern.test(algo)) { callback(new Error(`第 ${i + 1} 行:算法仅允许字母/数字/“-”,2-20 位`)); return }
|
||||
if (!power || !powerPattern.test(power) || Number(power) <= 0) {
|
||||
callback(new Error(`第 ${i + 1} 行:理论算力需大于0,整数最多6位,小数最多4位`)); return
|
||||
}
|
||||
if (!unit) { callback(new Error(`第 ${i + 1} 行:请选择算力单位`)); return }
|
||||
}
|
||||
callback()
|
||||
} catch (e) {
|
||||
callback(new Error('请检查币种/算法/算力/单位填写'))
|
||||
}
|
||||
},
|
||||
/** 行:币种输入过滤(去中文,仅字母数字) */
|
||||
handleCoinInput(index) {
|
||||
const r = this.form.coinAlgoRows[index]
|
||||
let v = String(r.coin || '')
|
||||
v = v.replace(/[\u4e00-\u9fa5]/g, '').replace(/[^A-Za-z0-9]/g, '')
|
||||
this.$set(this.form.coinAlgoRows[index], 'coin', v.toUpperCase())
|
||||
},
|
||||
/** 行:算法输入过滤(去中文,仅字母数字与-) */
|
||||
handleAlgorithmInput(index) {
|
||||
const r = this.form.coinAlgoRows[index]
|
||||
let v = String(r.algorithm || '')
|
||||
v = v.replace(/[\u4e00-\u9fa5]/g, '').replace(/[^A-Za-z0-9-]/g, '')
|
||||
this.$set(this.form.coinAlgoRows[index], 'algorithm', v.toUpperCase())
|
||||
},
|
||||
/** 行:理论算力输入限制(6整数4小数) */
|
||||
handleCoinRowTheoryInput(index) {
|
||||
let v = String(this.form.coinAndAlgoList[index].theoryPower ?? '')
|
||||
v = v.replace(/[^0-9.]/g, '')
|
||||
const firstDot = v.indexOf('.')
|
||||
if (firstDot !== -1) {
|
||||
v = v.slice(0, firstDot + 1) + v.slice(firstDot + 1).replace(/\./g, '')
|
||||
}
|
||||
const endsWithDot = v.endsWith('.')
|
||||
const parts = v.split('.')
|
||||
let intPart = parts[0] || ''
|
||||
let decPart = parts[1] || ''
|
||||
if (intPart.length > 6) intPart = intPart.slice(0, 6)
|
||||
if (decPart) decPart = decPart.slice(0, 4)
|
||||
v = decPart.length ? `${intPart}.${decPart}` : (endsWithDot ? `${intPart}.` : intPart)
|
||||
this.$set(this.form.coinAndAlgoList[index], 'theoryPower', v)
|
||||
},
|
||||
/** 行:单位变更 */
|
||||
handleCoinRowUnitChange(index, value) {
|
||||
this.$set(this.form.coinAndAlgoList[index], 'unit', value)
|
||||
},
|
||||
/** 新增一行 */
|
||||
handleAddCoinAlgoRow() {
|
||||
if (this.form.coinAndAlgoList.length >= 10) {
|
||||
this.$message.warning('最多添加 10 行')
|
||||
return
|
||||
}
|
||||
const last = (this.form.coinAndAlgoList[this.form.coinAndAlgoList.length - 1]) || { unit: 'TH/S' }
|
||||
this.form.coinAndAlgoList.push({ coin: '', algorithm: '', theoryPower: '', unit: last.unit || 'TH/S' })
|
||||
},
|
||||
/** 删除一行 */
|
||||
handleRemoveCoinAlgoRow(index) {
|
||||
if (this.form.coinAndAlgoList.length <= 1) return
|
||||
this.form.coinAndAlgoList.splice(index, 1)
|
||||
},
|
||||
/** 从多行聚合 coin CSV */
|
||||
buildCoinCsvFromRows() {
|
||||
const set = new Set()
|
||||
const rows = Array.isArray(this.form.coinAndAlgoList) ? this.form.coinAndAlgoList : []
|
||||
rows.forEach(r => {
|
||||
const token = String(r.coin || '')
|
||||
.split(/[,\s,、]+/)
|
||||
.map(s => s.trim().toUpperCase())
|
||||
.filter(Boolean)
|
||||
token.forEach(t => set.add(t))
|
||||
})
|
||||
return Array.from(set).join(',')
|
||||
},
|
||||
/** 从多行聚合 algorithm CSV */
|
||||
buildAlgoCsvFromRows() {
|
||||
const set = new Set()
|
||||
const rows = Array.isArray(this.form.coinAndAlgoList) ? this.form.coinAndAlgoList : []
|
||||
rows.forEach(r => {
|
||||
const token = String(r.algorithm || '')
|
||||
.split(/[,\s,、]+/)
|
||||
.map(s => s.trim().toUpperCase())
|
||||
.filter(Boolean)
|
||||
token.forEach(t => set.add(t))
|
||||
})
|
||||
return Array.from(set).join(',')
|
||||
},
|
||||
/** 实时过滤币种输入中的中文字符(仅保留英文/数字/分隔符) */
|
||||
handleCoinsInput() {
|
||||
let v = String(this.form.coinsInput || '')
|
||||
@@ -629,27 +703,31 @@ export default {
|
||||
*/
|
||||
handleDownloadClient() {
|
||||
// 走后端接口下载客户端程序
|
||||
downloadClient()
|
||||
.then((res) => {
|
||||
// 处理 blob 下载(兼容封装返回 data 或直接返回 Blob)
|
||||
const data = (res && res.data) ? res.data : res
|
||||
const blob = data instanceof Blob ? data : new Blob([data], { type: 'application/octet-stream' })
|
||||
const url = window.URL.createObjectURL(blob)
|
||||
const a = document.createElement('a')
|
||||
a.style.display = 'none'
|
||||
a.href = url
|
||||
// 默认文件名(可由后端 Content-Disposition 提供,这里简单兜底)
|
||||
a.download = 'gpu-client.zip'
|
||||
document.body.appendChild(a)
|
||||
a.click()
|
||||
document.body.removeChild(a)
|
||||
window.URL.revokeObjectURL(url)
|
||||
this.$message.success('客户端下载已开始')
|
||||
this.hasDownloadedClient = true
|
||||
})
|
||||
.catch(() => {
|
||||
this.$message.error('下载失败,请稍后重试')
|
||||
|
||||
|
||||
let userEmail =JSON.parse(localStorage.getItem('leasEmail'))
|
||||
if (!userEmail) {
|
||||
// 弹出确认框,用户确认后跳转到外部网站
|
||||
this.$confirm('获取用户信息失败,请重新进入网站?', '提示', {
|
||||
confirmButtonText: '确认',
|
||||
cancelButtonText: '取消',
|
||||
type: 'warning'
|
||||
}).then(() => {
|
||||
// 用户点击确认,跳转到外部网站
|
||||
window.open('https://test.m2pool.com/', '_blank')
|
||||
}).catch(() => {
|
||||
// 用户点击取消,不执行任何操作
|
||||
})
|
||||
return
|
||||
}
|
||||
console.log(userEmail,'userEmail');
|
||||
|
||||
this.downloadUrl = ` ${request.defaults.baseURL}/lease/user/downloadClient?userEmail=${userEmail}`
|
||||
let a = document.createElement(`a`)
|
||||
a.href = this.downloadUrl
|
||||
a.click()
|
||||
|
||||
|
||||
},
|
||||
/**
|
||||
* GPU 客户端已启动:跳转至商品列表
|
||||
@@ -1109,8 +1187,8 @@ export default {
|
||||
}
|
||||
// 统一售价与最大租赁天数已在表单级校验中处理,无需逐机校验
|
||||
// 组装确认数据并弹框
|
||||
const coinStr = this.normalizeCsv(this.form.coinsInput || this.form.coin, true)
|
||||
const algoStr = this.normalizeCsv(this.form.algorithmsInput, true)
|
||||
const coinStr = this.buildCoinCsvFromRows()
|
||||
const algoStr = this.buildAlgoCsvFromRows()
|
||||
this.confirmData = {
|
||||
coin: coinStr || '-',
|
||||
algorithm: algoStr || '-',
|
||||
@@ -1125,15 +1203,18 @@ export default {
|
||||
this.saving = true
|
||||
try {
|
||||
// 统一售卖新增接口参数
|
||||
const list = (this.form.coinAndAlgoList || []).map(r => ({
|
||||
coin: String(r.coin || '').toUpperCase().trim(),
|
||||
algorithm: String(r.algorithm || '').toUpperCase().trim(),
|
||||
theoryPower: Number(r.theoryPower) || 0,
|
||||
unit: r.unit
|
||||
}))
|
||||
const payload = {
|
||||
// 逗号分隔(中英文逗号都兼容),统一为英文逗号并大写
|
||||
coin: this.normalizeCsv(this.form.coinsInput || this.form.coin, true),
|
||||
algorithm: this.normalizeCsv(this.form.algorithmsInput, true),
|
||||
coinAndAlgoList: list,
|
||||
maxLeaseDays: Number(this.form.maxLeaseDays) || 0,
|
||||
name: this.form.type,
|
||||
powerDissipation: Number(this.form.powerDissipation) || 0,
|
||||
theoryPower: Number(this.form.theoryPower) || 0,
|
||||
unit: this.form.unit,
|
||||
saleNumbers: Number(this.form.sellCount) || 0,
|
||||
priceList: this.buildPriceList()
|
||||
}
|
||||
@@ -1229,5 +1310,13 @@ export default {
|
||||
.price-multi { gap: 6px; }
|
||||
.price-items { gap: 6px; }
|
||||
.cost-multi { gap: 6px; }
|
||||
/* ASIC 币种/算法/算力/单位 多行 */
|
||||
.coin-algo-rows { display: grid; gap: 8px; width: 100%; }
|
||||
.coin-algo-line { display: flex; align-items: center; gap: 8px; }
|
||||
.coin-algo-line .coin-input { width: 18%; min-width: 140px; }
|
||||
.coin-algo-line .algo-input { width: 24%; min-width: 160px; }
|
||||
.coin-algo-line .power-input { width: 20%; min-width: 140px; }
|
||||
.coin-algo-line .unit-select { width: 16%; min-width: 120px; }
|
||||
.coin-algo-line .op-btn { flex: 0 0 auto; }
|
||||
</style>
|
||||
|
||||
|
||||
Reference in New Issue
Block a user