每周更新

This commit is contained in:
2025-12-05 16:24:20 +08:00
parent 485226d9dc
commit cbefb964d4
21 changed files with 2546 additions and 700 deletions

View File

@@ -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>