每周更新
This commit is contained in:
@@ -22,7 +22,7 @@
|
||||
必须添加出售机器,否则买家无法下单。买家点击某个商品后,会看到该商品下的机器明细并进行选购。
|
||||
</li>
|
||||
</ol>
|
||||
<div class="guide-note">提示:建议先创建店铺 → 完成钱包绑定 → 创建商品 → 添加出售机器的顺序,避免漏配导致无法收款或无法下单。</div>
|
||||
<div class="guide-note">提示:建议先创建店铺 → 完成钱包绑定 → 创建商品的顺序,避免漏配导致无法收款或无法下单。</div>
|
||||
</div>
|
||||
</el-card>
|
||||
|
||||
@@ -61,8 +61,8 @@
|
||||
<span>已绑定钱包</span>
|
||||
</div>
|
||||
<el-table :data="shopConfigs" border style="width: 100%">
|
||||
<el-table-column prop="chain" label="链" width="140" />
|
||||
<el-table-column label="支付币种" >
|
||||
<el-table-column prop="chain" label="链" width="120" />
|
||||
<el-table-column label="支付币种" width="120" >
|
||||
<template slot-scope="scope">
|
||||
<div class="coin-list">
|
||||
<template v-if="Array.isArray(scope.row.children) && scope.row.children.length">
|
||||
@@ -89,10 +89,18 @@
|
||||
<!-- <el-table-column prop="payType" label="币种类型" width="120">
|
||||
<template slot-scope="scope">{{ scope.row.payType === 1 ? '稳定币' : '虚拟币' }}</template>
|
||||
</el-table-column> -->
|
||||
|
||||
|
||||
<el-table-column prop="payAddress" label="收款钱包地址" show-overflow-tooltip />
|
||||
<el-table-column label="操作" width="180" fixed="right">
|
||||
<el-table-column label="余额" >
|
||||
<template slot-scope="scope">
|
||||
<span class="balance-num">{{ formatAmount(scope.row) }}</span>
|
||||
<span class="balance-unit"> {{ formatCoin(scope.row) }}</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="操作" width="240" fixed="right">
|
||||
<template slot-scope="scope">
|
||||
<el-button type="text" style="color:#409EFF" @click="handleWithdraw(scope.row)">提现</el-button>
|
||||
<el-divider direction="vertical"></el-divider>
|
||||
<el-button type="text" @click="handleEditConfig(scope.row)">修改</el-button>
|
||||
<el-divider direction="vertical"></el-divider>
|
||||
<el-button type="text" style="color:#e74c3c" @click="handleDeleteConfig(scope.row)">删除</el-button>
|
||||
@@ -108,6 +116,85 @@
|
||||
</div>
|
||||
|
||||
<el-empty v-else description="正在加载店铺信息..." />
|
||||
|
||||
<!-- 提现对话框(仅使用本页行数据) -->
|
||||
<el-dialog
|
||||
:title="withdrawDialogTitle"
|
||||
:visible.sync="withdrawDialogVisible"
|
||||
width="720px"
|
||||
:close-on-click-modal="false"
|
||||
:close-on-press-escape="false"
|
||||
>
|
||||
<el-form :model="withdrawForm" :rules="withdrawRules" ref="withdrawForm" label-width="120px">
|
||||
<!-- 提现链 -->
|
||||
<el-form-item label="提现链">
|
||||
<el-input :value="String((currentWithdrawRow.chain || '')).toUpperCase()" :disabled="true" />
|
||||
</el-form-item>
|
||||
<!-- 提现币种 -->
|
||||
<el-form-item label="提现币种">
|
||||
<el-input :value="displayWithdrawSymbol" :disabled="true" />
|
||||
</el-form-item>
|
||||
<!-- 提现金额 -->
|
||||
<el-form-item label="提现金额" prop="amount">
|
||||
<el-input
|
||||
v-model="withdrawForm.amount"
|
||||
placeholder="请输入提现金额"
|
||||
inputmode="decimal"
|
||||
@input="handleAmountInput"
|
||||
>
|
||||
<template slot="append">{{ displayWithdrawSymbol }}</template>
|
||||
</el-input>
|
||||
<div class="balance-info">
|
||||
可用余额: {{ availableWithdrawBalance }} {{ displayWithdrawSymbol }}
|
||||
</div>
|
||||
</el-form-item>
|
||||
<!-- 手续费 -->
|
||||
<el-form-item label="手续费">
|
||||
<el-input v-model="withdrawForm.fee" :disabled="true">
|
||||
<template slot="append">{{ displayWithdrawSymbol }}</template>
|
||||
</el-input>
|
||||
<div class="fee-info">网络手续费: {{ withdrawForm.fee || '0' }} {{ displayWithdrawSymbol }}</div>
|
||||
</el-form-item>
|
||||
<!-- 实际到账 -->
|
||||
<el-form-item label="实际到账">
|
||||
<el-input :value="actualAmount" :disabled="true">
|
||||
<template slot="append">{{ displayWithdrawSymbol }}</template>
|
||||
</el-input>
|
||||
<div class="actual-amount-info">实际到账: {{ actualAmount }} {{ displayWithdrawSymbol }}</div>
|
||||
</el-form-item>
|
||||
<!-- 收款地址 -->
|
||||
<el-form-item label="收款地址" prop="toAddress">
|
||||
<el-input
|
||||
v-model="withdrawForm.toAddress"
|
||||
placeholder="请输入收款钱包地址"
|
||||
:disabled="!withdrawAddressEditable"
|
||||
ref="withdrawToAddressInput"
|
||||
>
|
||||
<template slot="append">
|
||||
<el-button type="text" @click="handleEditAddressClick">修改</el-button>
|
||||
</template>
|
||||
</el-input>
|
||||
<div class="address-tip">请确认地址正确,错误地址将导致资产丢失</div>
|
||||
</el-form-item>
|
||||
<!-- 谷歌验证码 -->
|
||||
<el-form-item label="谷歌验证码" prop="googleCode">
|
||||
<el-input
|
||||
v-model="withdrawForm.googleCode"
|
||||
placeholder="请输入6位谷歌验证码"
|
||||
maxlength="6"
|
||||
@input="handleGoogleCodeInput"
|
||||
>
|
||||
<template slot="prepend">
|
||||
<i class="el-icon-key"></i>
|
||||
</template>
|
||||
</el-input>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<div slot="footer" class="dialog-footer">
|
||||
<el-button @click="withdrawDialogVisible = false">取消</el-button>
|
||||
<el-button type="primary" :loading="withdrawLoading" @click="confirmWithdraw">确认提现</el-button>
|
||||
</div>
|
||||
</el-dialog>
|
||||
|
||||
<!-- 修改店铺弹窗 -->
|
||||
<el-dialog title="修改店铺" :visible.sync="visibleEdit" width="520px">
|
||||
@@ -139,43 +226,7 @@
|
||||
</el-dialog>
|
||||
<!-- 修改钱包绑定配置弹窗:参数保持与列表一致 -->
|
||||
<el-dialog title="修改配置" :visible.sync="visibleConfigEdit" width="560px">
|
||||
<div class="row">
|
||||
<label class="label">支付链</label>
|
||||
<el-input v-model="configForm.chainLabel" placeholder="-" disabled />
|
||||
</div>
|
||||
<div class="row">
|
||||
<label class="label">支付币种</label>
|
||||
<el-select
|
||||
class="input"
|
||||
size="middle"
|
||||
v-model="configForm.payCoins"
|
||||
multiple
|
||||
collapse-tags
|
||||
filterable
|
||||
placeholder="请选择币种"
|
||||
>
|
||||
<el-option
|
||||
v-for="item in editCoinOptions"
|
||||
:key="item.value"
|
||||
:label="item.label"
|
||||
:value="item.value"
|
||||
/>
|
||||
</el-select>
|
||||
</div>
|
||||
<div class="row">
|
||||
<label class="label">已选择币种</label>
|
||||
<div class="selected-coin-list">
|
||||
<el-tag
|
||||
v-for="c in selectedCoinLabels"
|
||||
:key="c"
|
||||
type="warning"
|
||||
effect="light"
|
||||
closable
|
||||
@close="removeSelectedCoin(c)"
|
||||
>{{ c }}</el-tag>
|
||||
<span v-if="!selectedCoinLabels.length" style="color:#c0c4cc">未选择</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
<label class="label">钱包地址</label>
|
||||
<el-input v-model="configForm.payAddress" placeholder="请输入钱包地址" />
|
||||
@@ -193,7 +244,7 @@
|
||||
import { getMyShop, updateShop, deleteShop, queryShop, closeShop ,updateShopConfig,deleteShopConfig,getChainAndCoin} from '@/api/shops'
|
||||
|
||||
import { coinList } from '@/utils/coinList'
|
||||
import { getShopConfig } from '@/api/wallet'
|
||||
import { getShopConfig,getShopConfigV2 ,withdrawBalanceForSeller,updateShopConfigV2} from '@/api/wallet'
|
||||
|
||||
|
||||
export default {
|
||||
@@ -227,7 +278,19 @@ export default {
|
||||
{ label: 'BSC (BEP20)', value: 'bsc' },
|
||||
{ label: 'Nexa', value: 'nexa' },
|
||||
],
|
||||
shopLoading: false
|
||||
shopLoading: false,
|
||||
/* 提现弹窗状态(仅本页使用) */
|
||||
withdrawDialogVisible: false,
|
||||
withdrawLoading: false,
|
||||
currentWithdrawRow: {},
|
||||
withdrawForm: {
|
||||
amount: '',
|
||||
toAddress: '',
|
||||
fee: '0.00',
|
||||
googleCode: ''
|
||||
},
|
||||
withdrawAddressEditable: false,
|
||||
withdrawRules: {}
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
@@ -261,12 +324,199 @@ export default {
|
||||
selectedCoinLabels() {
|
||||
const map = new Map((this.editCoinOptions || []).map(o => [String(o.value), String(o.label).toUpperCase()]))
|
||||
return (this.configForm.payCoins || []).map(v => map.get(String(v)) || String(v).toUpperCase())
|
||||
},
|
||||
/* 提现弹窗标题:如 USDT提现 */
|
||||
withdrawDialogTitle() {
|
||||
const sym = String((this.currentWithdrawRow && this.currentWithdrawRow.payCoin) || '').toUpperCase() || 'USDT'
|
||||
return `${sym}提现`
|
||||
},
|
||||
/* 提现币种(大写) */
|
||||
displayWithdrawSymbol() {
|
||||
return String((this.currentWithdrawRow && this.currentWithdrawRow.payCoin) || '').toUpperCase()
|
||||
},
|
||||
/* 可用余额(最多6位小数显示) */
|
||||
availableWithdrawBalance() {
|
||||
const n = Number((this.currentWithdrawRow && this.currentWithdrawRow.balance) || 0)
|
||||
return this.formatDec6(n)
|
||||
},
|
||||
/* 实际到账金额 = 可用余额 - 手续费(只显示可用余额,不展示总余额/冻结) */
|
||||
actualAmount() {
|
||||
const amountInt = this.toScaledInt(this.withdrawForm.amount)
|
||||
const feeInt = this.toScaledInt(this.withdrawForm.fee)
|
||||
if (!Number.isFinite(amountInt) || !Number.isFinite(feeInt)) return '0'
|
||||
const res = amountInt - feeInt
|
||||
return res > 0 ? this.formatDec6FromInt(res) : '0'
|
||||
}
|
||||
},
|
||||
created() {
|
||||
this.fetchMyShop()
|
||||
},
|
||||
methods: {
|
||||
/** 余额展示:带币种单位 */
|
||||
formatBalance(row) {
|
||||
try {
|
||||
const num = Number(row && row.balance)
|
||||
const valid = Number.isFinite(num)
|
||||
const coin = String(row && row.payCoin ? row.payCoin : '').toUpperCase()
|
||||
if (!valid) return '-'
|
||||
const text = String(num)
|
||||
return coin ? `${text} ${coin}` : text
|
||||
} catch (e) {
|
||||
return '-'
|
||||
}
|
||||
},
|
||||
/** 仅数字部分(用于红色显示) */
|
||||
formatAmount(row) {
|
||||
try {
|
||||
const num = Number(row && row.balance)
|
||||
if (!Number.isFinite(num)) return '-'
|
||||
return String(num)
|
||||
} catch (e) {
|
||||
return '-'
|
||||
}
|
||||
},
|
||||
/** 仅币种单位(大写) */
|
||||
formatCoin(row) {
|
||||
return String(row && row.payCoin ? row.payCoin : '').toUpperCase()
|
||||
},
|
||||
/** 打开提现对话框(行数据驱动) */
|
||||
async handleWithdraw(row) {
|
||||
this.currentWithdrawRow = row || {}
|
||||
const fee = Number(row && (row.serviceCharge != null ? row.serviceCharge : row.charge))
|
||||
this.withdrawForm.fee = Number.isFinite(fee) ? this.formatDec6(fee) : '0.00'
|
||||
this.withdrawForm.amount = ''
|
||||
// 收款地址默认填充为当前行地址,且禁用编辑
|
||||
this.withdrawForm.toAddress = row && row.payAddress ? row.payAddress : ''
|
||||
this.withdrawForm.googleCode = ''
|
||||
this.withdrawAddressEditable = false
|
||||
// 初始化校验规则
|
||||
this.withdrawRules = {
|
||||
amount: [
|
||||
{ required: true, message: '请输入提现金额', trigger: 'blur' },
|
||||
{ validator: this.validateWithdrawAmount, trigger: 'blur' }
|
||||
],
|
||||
// 地址为只读已填,不再要求用户输入
|
||||
googleCode: [
|
||||
{ required: true, message: '请输入谷歌验证码', trigger: 'blur' },
|
||||
{ validator: this.validateGoogleCode, trigger: 'blur' }
|
||||
]
|
||||
}
|
||||
this.withdrawDialogVisible = true
|
||||
},
|
||||
/* 点击“修改”启用收款地址编辑并聚焦 */
|
||||
handleEditAddressClick() {
|
||||
this.withdrawAddressEditable = true
|
||||
this.$nextTick(() => {
|
||||
const input = this.$refs.withdrawToAddressInput
|
||||
if (input && input.focus) input.focus()
|
||||
})
|
||||
},
|
||||
/* 提现金额输入(<=6位小数) */
|
||||
handleAmountInput(v) {
|
||||
let s = String(v || '')
|
||||
s = s.replace(/[^0-9.]/g, '')
|
||||
const i = s.indexOf('.')
|
||||
if (i !== -1) {
|
||||
s = s.slice(0, i + 1) + s.slice(i + 1).replace(/\./g, '')
|
||||
const [intPart, decPart = ''] = s.split('.')
|
||||
s = intPart + '.' + decPart.slice(0, 6)
|
||||
}
|
||||
this.withdrawForm.amount = s
|
||||
},
|
||||
/* 谷歌验证码仅数字 */
|
||||
handleGoogleCodeInput(v) {
|
||||
this.withdrawForm.googleCode = String(v || '').replace(/\D/g, '')
|
||||
},
|
||||
/* 确认提现 - 调用后端卖家提现接口 */
|
||||
confirmWithdraw() {
|
||||
this.$refs.withdrawForm.validate(async (valid) => {
|
||||
if (!valid) return
|
||||
this.withdrawLoading = true
|
||||
try {
|
||||
const row = this.currentWithdrawRow || {}
|
||||
const payload = {
|
||||
toChain: row.chain,
|
||||
toSymbol: row.payCoin,
|
||||
amount: Number(this.withdrawForm.amount),
|
||||
toAddress: this.withdrawForm.toAddress,
|
||||
fromAddress: row.payAddress || this.withdrawForm.toAddress || '',
|
||||
code: this.withdrawForm.googleCode,
|
||||
serviceCharge: Number(this.withdrawForm.fee) || 0
|
||||
}
|
||||
const res = await withdrawBalanceForSeller(payload)
|
||||
if (res && (res.code === 0 || res.code === 200)) {
|
||||
this.$message.success('提现申请已提交,请等待处理')
|
||||
this.withdrawDialogVisible = false
|
||||
this.fetchShopConfigs(this.shop.id)
|
||||
}
|
||||
} catch (e) {
|
||||
console.error('卖家提现失败', e)
|
||||
} finally {
|
||||
this.withdrawLoading = false
|
||||
}
|
||||
})
|
||||
},
|
||||
/* 工具:最多6位小数显示 */
|
||||
formatDec6(value) {
|
||||
if (value === null || value === undefined || value === '') return '0'
|
||||
let s = String(value)
|
||||
if (/e/i.test(s)) {
|
||||
const n = Number(value)
|
||||
if (!Number.isFinite(n)) return '0'
|
||||
s = n.toFixed(20).replace(/\.0+$/, '').replace(/(\.\d*?)0+$/, '$1')
|
||||
}
|
||||
const m = s.match(/^(-?)(\d+)(?:\.(\d+))?$/)
|
||||
if (!m) return s
|
||||
let intPart = m[2]
|
||||
let decPart = m[3] || ''
|
||||
if (decPart.length > 6) decPart = decPart.slice(0, 6)
|
||||
return decPart ? `${intPart}.${decPart}` : intPart
|
||||
},
|
||||
/* 工具:金额字符串 -> 10^6 精度整数 */
|
||||
toScaledInt(amountStr, decimals = 6) {
|
||||
if (amountStr === null || amountStr === undefined) return 0
|
||||
const normalized = String(amountStr).trim()
|
||||
if (normalized === '') return 0
|
||||
const re = new RegExp(`^\\d+(?:\\.(\\d{0,${decimals}}))?$`)
|
||||
const match = normalized.match(re)
|
||||
if (!match) {
|
||||
const n = Number(normalized)
|
||||
if (!Number.isFinite(n)) return 0
|
||||
const scale = Math.pow(10, decimals)
|
||||
return Math.round(n * scale)
|
||||
}
|
||||
const [intPart, decPartRaw] = normalized.split('.')
|
||||
const decPart = (decPartRaw || '').padEnd(decimals, '0').slice(0, decimals)
|
||||
const scale = Math.pow(10, decimals)
|
||||
return Number(intPart) * scale + Number(decPart)
|
||||
},
|
||||
/* 工具:10^6 精度整数 -> 最多6位小数字符串 */
|
||||
formatDec6FromInt(intVal) {
|
||||
const sign = intVal < 0 ? '-' : ''
|
||||
const abs = Math.abs(intVal)
|
||||
const scale = Math.pow(10, 6)
|
||||
const intPart = Math.floor(abs / scale)
|
||||
const decPart = String(abs % scale).padStart(6, '0')
|
||||
const s = `${sign}${intPart}.${decPart}`
|
||||
return s.replace(/\.0+$/, '').replace(/(\.\d*?)0+$/, '$1')
|
||||
},
|
||||
/* 校验:提现金额 */
|
||||
validateWithdrawAmount(rule, value, callback) {
|
||||
const amtInt = this.toScaledInt(value)
|
||||
if (!Number.isFinite(amtInt) || amtInt <= 0) { callback(new Error('请输入有效的金额')); return }
|
||||
const feeInt = this.toScaledInt(this.withdrawForm.fee)
|
||||
const balanceInt = this.toScaledInt((this.currentWithdrawRow && this.currentWithdrawRow.balance) || 0)
|
||||
if (amtInt >= balanceInt) { callback(new Error('提现金额必须小于可用余额')); return }
|
||||
if (amtInt <= feeInt) { callback(new Error('提现金额必须大于手续费')); return }
|
||||
if (amtInt < 1000000) { callback(new Error('最小提现金额为 1')); return }
|
||||
callback()
|
||||
},
|
||||
/* 校验:谷歌验证码 */
|
||||
validateGoogleCode(rule, value, callback) {
|
||||
const v = String(value || '')
|
||||
if (!/^\d{6}$/.test(v)) { callback(new Error('谷歌验证码必须是6位数字')); return }
|
||||
callback()
|
||||
},
|
||||
/**
|
||||
* 手续费率显示:最多6位小数,去除多余的0;空值显示为 '-'
|
||||
*/
|
||||
@@ -362,7 +612,7 @@ export default {
|
||||
}
|
||||
|
||||
try {
|
||||
const res = await getShopConfig({id:shopId})
|
||||
const res = await getShopConfigV2({id:shopId})
|
||||
if (res && (res.code === 0 || res.code === 200) && Array.isArray(res.data)) {
|
||||
// 直接使用后端返回的数据;children: [{payCoin,image}]
|
||||
this.shopConfigs = res.data
|
||||
@@ -430,15 +680,7 @@ export default {
|
||||
},
|
||||
|
||||
submitConfigEdit() {
|
||||
// 基础校验
|
||||
if (!this.configForm.chainLabel && !this.configForm.chainValue) {
|
||||
this.$message.warning('请选择支付链')
|
||||
return
|
||||
}
|
||||
if (!this.configForm.payCoins || this.configForm.payCoins.length === 0) {
|
||||
this.$message.warning('请选择支付币种')
|
||||
return
|
||||
}
|
||||
// 仅校验钱包地址
|
||||
const addr = (this.configForm.payAddress || '').trim()
|
||||
if (!addr) {
|
||||
this.$message.warning('请输入钱包地址')
|
||||
@@ -446,11 +688,17 @@ export default {
|
||||
}
|
||||
const payload = {
|
||||
id: this.configForm.id,
|
||||
chain: this.configForm.chainValue || this.configForm.chainLabel,
|
||||
payCoin: (this.configForm.payCoins || []).join(','),
|
||||
chain: this.configForm.chainValue || this.configForm.chainLabel || '',
|
||||
payAddress: this.configForm.payAddress
|
||||
}
|
||||
this.updateShopConfig(payload)
|
||||
;(async () => {
|
||||
const res = await updateShopConfigV2(payload)
|
||||
if (res && (res.code === 0 || res.code === 200)) {
|
||||
this.$message.success('保存成功')
|
||||
this.visibleConfigEdit = false
|
||||
this.fetchShopConfigs(this.shop.id)
|
||||
}
|
||||
})()
|
||||
},
|
||||
removeSelectedCoin(labelUpper) {
|
||||
const label = String(labelUpper || '').toLowerCase()
|
||||
@@ -690,6 +938,14 @@ export default {
|
||||
.guide-note { margin-top: 10px; color: #6b7280; font-size: 13px; background: #f9fafb; border: 1px dashed #e5e7eb; padding: 8px 10px; border-radius: 8px; }
|
||||
.coin-list { display: flex; align-items: center; gap: 8px; }
|
||||
.coin-img { width: 20px; height: 20px; border-radius: 4px; display: inline-block; }
|
||||
/* 提现弹窗样式(与钱包页统一) */
|
||||
.balance-info { font-size: 12px; color: #666; margin-top: 4px; text-align: left; }
|
||||
.fee-info { font-size: 12px; color: #e6a23c; margin-top: 4px; text-align: left; }
|
||||
.actual-amount-info { font-size: 12px; color: #67c23a; margin-top: 4px; text-align: left; font-weight: 500; }
|
||||
.address-tip { font-size: 12px; color: #f56c6c; margin-top: 4px; line-height: 1.4; text-align: left; }
|
||||
/* 余额数字红色显示 */
|
||||
.balance-num { color: #ff4d4f; font-weight: 600; }
|
||||
.balance-unit { color: #606266; }
|
||||
</style>
|
||||
|
||||
<style>
|
||||
|
||||
Reference in New Issue
Block a user