最新需求修改中

This commit is contained in:
2025-11-28 15:30:36 +08:00
parent 868632400a
commit 485226d9dc
8 changed files with 2070 additions and 738 deletions

View File

@@ -28,6 +28,7 @@
v-model="form.coinsInput"
placeholder="例如USDT, BTC, ETH"
style="width: 50%;"
@input="handleCoinsInput"
/>
</el-form-item>
<el-form-item label="算法(多个用逗号隔开)" prop="algorithmsInput" :required="form.machineCategory === 'ASIC'">
@@ -35,13 +36,14 @@
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="矿机型号">
<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">
@@ -84,7 +86,11 @@
</el-input>
</el-form-item>
<el-form-item label="统一售价" prop="cost" :required="true">
<el-form-item
label="统一售价"
:prop="(payTypeDefs && payTypeDefs.length) ? 'costMap' : 'cost'"
:required="true"
>
<span slot="label">统一售价</span>
<!-- 若商品定义了多个结算币种则按链-币种动态生成多个售价输入否则回退为旧的 USDT 单价 -->
<div v-if="payTypeDefs && payTypeDefs.length" class="cost-multi">
@@ -136,15 +142,33 @@
<el-dialog
title="请确认上架信息"
:visible.sync="confirmVisible"
width="400px"
width="560px"
>
<div>
<p>请仔细确认已选择机器列表价格及相关参数定义</p>
<p style="text-align: left;">机器上架后一经售出在机器出售期间不能修改价格及机器参数</p>
<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>
</div>
<span slot="footer" class="dialog-footer">
<el-button @click="confirmVisible = false">取消</el-button>
<el-button type="primary" :loading="saving" @click="doSubmit">确认上架已选择机器</el-button>
<el-button type="primary" :loading="saving" @click="doSubmit">确认提交</el-button>
</span>
</el-dialog>
<!-- GPU 引导弹窗 -->
@@ -171,8 +195,8 @@
</template>
<script>
import { addSingleOrBatchMachine } from '../../api/machine'
import { addSingleOrBatchMachine ,downloadClient,addAsicMachine} from '../../api/machine'
import { getPayTypes } from '../../api/products'
export default {
name: 'AccountProductMachineAdd',
data() {
@@ -197,14 +221,47 @@ export default {
maxLeaseDays: ''
},
confirmVisible: false,
confirmData: {
coin: '',
algorithm: '',
maxLeaseDays: '',
saleNumbers: '',
priceList: []
},
rules: {
productName: [ { required: true, message: '商品名称不能为空', trigger: 'change' } ],
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'
}
],
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'
@@ -216,6 +273,16 @@ export default {
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'
@@ -400,6 +467,8 @@ export default {
clientDownloadUrl: process.env.VUE_APP_GPU_CLIENT_URL || '',
/** 是否点击过下载客户端(用于控制“已启动客户端”按钮禁用态) */
hasDownloadedClient: false,
/** 支持的支付方式定义(用于动态渲染统一售价输入组) */
payTypeDefs: [],
params:{
cost:353400,
powerDissipation:0.01,
@@ -435,8 +504,88 @@ export default {
if (this.rules && this.rules.cost) {
this.$set(this.rules, 'cost', [{ validator: this.validateCost, trigger: 'blur' }])
}
// 多币种价格专用校验
this.$set(this.rules, 'costMap', [{ validator: this.validateCostMap, trigger: 'blur' }])
this.getPayTypes()
},
methods: {
/** 实时过滤币种输入中的中文字符(仅保留英文/数字/分隔符) */
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
}
},
/**
* ASIC 模式:出售机器数量输入,仅允许 0-9999 的整数
*/
@@ -479,24 +628,35 @@ export default {
* 下载 GPU 客户端
*/
handleDownloadClient() {
const url = (this.clientDownloadUrl || '').trim()
if (!url) {
this.$message.warning('暂无客户端下载地址,请联系管理员')
return
}
try {
window.open(url, '_blank')
} catch (e) {
this.$message.error('打开下载链接失败,请稍后重试')
}
// 点击下载后即可启用“已启动客户端”按钮
this.hasDownloadedClient = true
// 走后端接口下载客户端程序
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('下载失败,请稍后重试')
})
},
/**
* GPU 客户端已启动:跳转至商品列表
*/
handleGpuClientStarted() {
this.$router.push('/productList')
// 跳转到“个人中心-商品列表”页面
this.$router.push('/account/products')
},
/**
* GPU 弹窗关闭:自动恢复为 ASIC
@@ -507,9 +667,9 @@ export default {
},
/** 统一售价校验:多结算币种时跳过,单价时按 USDT 校验 */
validateCost(rule, value, callback) {
// 多支付方式:逐个校验 costMap
if (Array.isArray(this.payTypeDefs) && this.payTypeDefs.length > 0) {
callback()
return
return this.validateCostMap(rule, value, callback)
}
const str = String(value || '')
if (!str) { callback(new Error('请填写机器成本USDT')); return }
@@ -518,6 +678,25 @@ export default {
if (Number(str) <= 0) { callback(new Error('成本必须大于 0')); return }
callback()
},
// 多支付方式下的价格校验:要求每个支付方式都需填写有效价格
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('价格填写有误,请检查'))
}
},
/** 解析路由参数中的支付方式,生成标准定义 */
initPayTypesFromRoute() {
this.payTypeDefs = []
@@ -895,10 +1074,10 @@ export default {
} catch (e) {
return
}
if (!this.form.productId) {
this.$message.warning('缺少商品ID')
return
}
// if (!this.form.productId) {
// this.$message.warning('缺少商品ID')
// return
// }
// 现在统一按出售数量提交GPU 模式不在本页提交)
{
// ASIC校验出售机器数量允许 0-9999为 0 则提示)
@@ -929,47 +1108,41 @@ export default {
return
}
// 统一售价与最大租赁天数已在表单级校验中处理,无需逐机校验
// 通过所有预校验后,弹出确认
// 组装确认数据并弹
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()
}
this.confirmVisible = true
}
,
async doSubmit() {
const [user, coin] = ['','']
this.saving = true
try {
// 若是多结算币种,组装 priceList否则沿用单价字段
const count = Number(this.form.sellCount)
const productMachineURDVos = Array.from({ length: count }).map(() => ({
price: this.payTypeDefs && this.payTypeDefs.length ? undefined : (Number(this.form.cost) || 0),
priceList: this.payTypeDefs && this.payTypeDefs.length ? this.payTypeDefs.map(d => ({
chain: d.chain,
coin: d.coin,
price: Number(this.form.costMap && this.form.costMap[d.key]) || 0
})) : undefined,
state: 0,
type: this.form.type,
// 统一售卖新增接口参数
const payload = {
// 逗号分隔(中英文逗号都兼容),统一为英文逗号并大写
coin: this.normalizeCsv(this.form.coinsInput || this.form.coin, true),
algorithm: this.normalizeCsv(this.form.algorithmsInput, true),
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
}))
const payload = {
productId: this.form.productId,
// 逗号分隔:后台若需要数组可在服务端拆分
coin: (this.form.coinsInput || this.form.coin || '').toString(),
algorithm: (this.form.algorithmsInput || '').toString(),
powerDissipation: this.form.powerDissipation,
theoryPower: this.form.theoryPower,
type: this.form.type,
unit: this.form.unit,
cost: this.payTypeDefs && this.payTypeDefs.length ? Number(this.form.costMap && this.form.costMap[this.payTypeDefs[0].key]) || 0 : this.form.cost,
maxLeaseDays: this.form.maxLeaseDays,
productMachineURDVos
saleNumbers: Number(this.form.sellCount) || 0,
priceList: this.buildPriceList()
}
// 过滤空价目
payload.priceList = (payload.priceList || []).filter(p => Number(p.price) > 0)
console.log(payload,"请求参数")
const res = await addSingleOrBatchMachine(payload)
const res = await addAsicMachine(payload)
if (res && (res.code === 0 || res.code === 200)) {
this.$message({
@@ -979,7 +1152,7 @@ export default {
type: 'success'
})
this.confirmVisible = false
this.$router.back()
this.$router.push('/account/products')
}
} catch (e) {
console.error('添加出售机器失败', e)