每周更新
This commit is contained in:
@@ -71,6 +71,39 @@ export function updatePassword(data) {
|
||||
}
|
||||
|
||||
|
||||
//注销账户
|
||||
export function closeAccount(data) {
|
||||
return request({
|
||||
url: `/lease/auth/closeAccount`,
|
||||
method: 'post',
|
||||
data
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
//注销邮箱验证码
|
||||
export function sendCloseAccount(data) {
|
||||
return request({
|
||||
url: `/lease/auth/sendCloseAccount`,
|
||||
method: 'post',
|
||||
data
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
//个人中心修改密码
|
||||
export function updatePasswordInCenter(data) {
|
||||
return request({
|
||||
url: `/lease/auth/updatePasswordInCenter`,
|
||||
method: 'post',
|
||||
data
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
:key="nav.path"
|
||||
:to="nav.path"
|
||||
class="nav-btn"
|
||||
active-class="active"
|
||||
:class="{ active: isNavActive(nav.path) }"
|
||||
:title="nav.description"
|
||||
>
|
||||
<span class="nav-icon">{{ nav.icon }}</span>
|
||||
@@ -30,9 +30,16 @@
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<!-- 已登录:显示用户邮箱和退出按钮 -->
|
||||
<!-- 已登录:显示用户邮箱、安全设置和退出按钮 -->
|
||||
<div v-else class="user-info">
|
||||
<span class="user-email">{{ userEmail }}</span>
|
||||
<router-link
|
||||
to="/account/security-settings"
|
||||
class="security-link"
|
||||
active-class="active"
|
||||
>
|
||||
安全设置
|
||||
</router-link>
|
||||
<el-button
|
||||
type="text"
|
||||
size="small"
|
||||
@@ -113,6 +120,19 @@ export default {
|
||||
window.removeEventListener('login-status-changed', this.handleLoginStatusChanged)
|
||||
},
|
||||
methods: {
|
||||
/**
|
||||
* 判断导航按钮是否应该高亮
|
||||
* 在安全设置页面时,个人中心不应该高亮
|
||||
*/
|
||||
isNavActive(path) {
|
||||
const currentPath = (this.$route && this.$route.path) || ''
|
||||
// 如果是安全设置页面,个人中心不高亮
|
||||
if (currentPath === '/account/security-settings' && path === '/account') {
|
||||
return false
|
||||
}
|
||||
// 其他情况使用 Vue Router 的默认匹配逻辑(包含匹配)
|
||||
return currentPath === path || (path !== '/' && currentPath.startsWith(path + '/'))
|
||||
},
|
||||
loadCart() {
|
||||
this.cart = readCart()
|
||||
},
|
||||
@@ -438,6 +458,36 @@ export default {
|
||||
color: #2c3e50;
|
||||
font-size: 14px;
|
||||
font-weight: 600;
|
||||
margin-right: 12px;
|
||||
}
|
||||
|
||||
/* 安全设置链接样式 - 与导航按钮一致 */
|
||||
.security-link {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
color: #2c3e50;
|
||||
font-size: 16px;
|
||||
font-weight: 600;
|
||||
text-decoration: none;
|
||||
padding: 12px 20px;
|
||||
margin-right: 8px;
|
||||
border-radius: 8px;
|
||||
transition: all 0.3s ease;
|
||||
cursor: pointer;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.security-link:hover {
|
||||
background: #f5f7ff;
|
||||
color: #667eea;
|
||||
transform: translateY(-2px);
|
||||
}
|
||||
|
||||
.security-link.active {
|
||||
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
||||
color: #fff;
|
||||
box-shadow: 0 4px 12px rgba(102, 126, 234, 0.3);
|
||||
transform: translateY(-2px);
|
||||
}
|
||||
|
||||
/* 退出按钮样式 */
|
||||
|
||||
@@ -147,10 +147,34 @@
|
||||
</div>
|
||||
<div v-show="isExpanded('consume', row, idx)" class="expand-panel">
|
||||
<div class="expand-grid">
|
||||
<div class="expand-item"><span class="label">订单号</span><span class="value mono">{{ row.orderId || '' }}</span></div>
|
||||
<div class="expand-item"><span class="label">支付地址</span><span class="value mono-ellipsis" :title="row.fromAddress">{{ row.fromAddress || '' }}</span></div>
|
||||
<div class="expand-item"><span class="label">收款地址</span><span class="value mono-ellipsis" :title="row.toAddress">{{ row.toAddress || '' }}</span></div>
|
||||
<div class="expand-item" v-if="row.txHash"><span class="label">交易哈希</span><span class="value mono-ellipsis" :title="row.txHash">{{ row.txHash }}</span></div>
|
||||
<div class="expand-item">
|
||||
<span class="label">订单号</span>
|
||||
<div class="value value-row">
|
||||
<span class="mono-ellipsis" :title="row.orderId">{{ row.orderId || '' }}</span>
|
||||
<el-button type="text" size="mini" icon="el-icon-document-copy" @click.stop="handleCopy(row.orderId, '订单号')">复制</el-button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="expand-item">
|
||||
<span class="label">支付地址</span>
|
||||
<div class="value value-row">
|
||||
<span class="mono-ellipsis" :title="row.fromAddress">{{ row.fromAddress || '' }}</span>
|
||||
<el-button type="text" size="mini" icon="el-icon-document-copy" @click.stop="handleCopy(row.fromAddress, '支付地址')">复制</el-button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="expand-item">
|
||||
<span class="label">收款地址</span>
|
||||
<div class="value value-row">
|
||||
<span class="mono-ellipsis" :title="row.toAddress">{{ row.toAddress || '' }}</span>
|
||||
<el-button type="text" size="mini" icon="el-icon-document-copy" @click.stop="handleCopy(row.toAddress, '收款地址')">复制</el-button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="expand-item" v-if="row.txHash">
|
||||
<span class="label">交易哈希</span>
|
||||
<div class="value value-row">
|
||||
<span class="mono-ellipsis" :title="row.txHash">{{ row.txHash }}</span>
|
||||
<el-button type="text" size="mini" icon="el-icon-document-copy" @click.stop="handleCopy(row.txHash, '交易哈希')">复制</el-button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -16,20 +16,20 @@
|
||||
<div class="user-role" role="group" aria-label="导航分组切换">
|
||||
<button
|
||||
class="role-button"
|
||||
:class="{ active: activeRole === 'buyer' }"
|
||||
:class="{ active: activeRole === 'buyer' && !isSecuritySettingsPage }"
|
||||
@click="handleClickRole('buyer')"
|
||||
@keydown.enter.prevent="handleClickRole('buyer')"
|
||||
@keydown.space.prevent="handleClickRole('buyer')"
|
||||
:aria-pressed="activeRole === 'buyer'"
|
||||
:aria-pressed="activeRole === 'buyer' && !isSecuritySettingsPage"
|
||||
tabindex="0"
|
||||
>买家相关</button>
|
||||
<button
|
||||
class="role-button"
|
||||
:class="{ active: activeRole === 'seller' }"
|
||||
:class="{ active: activeRole === 'seller' && !isSecuritySettingsPage }"
|
||||
@click="handleClickRole('seller')"
|
||||
@keydown.enter.prevent="handleClickRole('seller')"
|
||||
@keydown.space.prevent="handleClickRole('seller')"
|
||||
:aria-pressed="activeRole === 'seller'"
|
||||
:aria-pressed="activeRole === 'seller' && !isSecuritySettingsPage"
|
||||
tabindex="0"
|
||||
>卖家相关</button>
|
||||
</div>
|
||||
@@ -68,7 +68,6 @@ export default {
|
||||
// { label: '充值记录', to: '/account/rechargeRecord' },
|
||||
// { label: '提现记录', to: '/account/withdrawalHistory' },
|
||||
{ label: '资金流水', to: '/account/funds-flow' },
|
||||
{ label: '安全设置', to: '/account/security-settings' },
|
||||
],
|
||||
// 卖家侧导航
|
||||
sellerLinks: [
|
||||
@@ -77,7 +76,6 @@ export default {
|
||||
{ label: '商品列表', to: '/account/products' },
|
||||
{ label: '已售出订单', to: '/account/seller-orders' },
|
||||
{ label: '资金流水', to: '/account/seller-funds-flow' },
|
||||
{ label: '安全设置', to: '/account/security-settings' },
|
||||
],
|
||||
}
|
||||
},
|
||||
@@ -97,6 +95,14 @@ export default {
|
||||
displayedLinks() {
|
||||
return this.activeRole === 'buyer' ? this.buyerLinks : this.sellerLinks
|
||||
},
|
||||
/**
|
||||
* 判断当前是否在安全设置页面
|
||||
* @returns {boolean}
|
||||
*/
|
||||
isSecuritySettingsPage() {
|
||||
const path = (this.$route && this.$route.path) || ''
|
||||
return path === '/account/security-settings'
|
||||
},
|
||||
},
|
||||
mounted() {
|
||||
const getVal = (key) => {
|
||||
@@ -179,7 +185,10 @@ export default {
|
||||
'/account/shop-config'
|
||||
]
|
||||
// 安全设置页面买家和卖家都可见,不参与分组判断
|
||||
// 在安全设置页面时,清除分组高亮(不设置 activeRole)
|
||||
if (path === '/account/security-settings') {
|
||||
// 清除分组高亮,让所有分组按钮都不高亮
|
||||
this.activeRole = null
|
||||
return
|
||||
}
|
||||
const shouldBuyer = buyerPrefixes.some(p => path.indexOf(p) === 0)
|
||||
|
||||
@@ -229,12 +229,25 @@
|
||||
</span>
|
||||
</el-dialog>
|
||||
<!-- 修改钱包绑定配置弹窗:参数保持与列表一致 -->
|
||||
<el-dialog title="修改配置" :visible.sync="visibleConfigEdit" width="560px">
|
||||
<el-dialog title="修改配置" :visible.sync="visibleConfigEdit" width="560px" @close="handleConfigEditClose">
|
||||
|
||||
<div class="row">
|
||||
<label class="label">钱包地址</label>
|
||||
<el-input v-model="configForm.payAddress" placeholder="请输入钱包地址" />
|
||||
</div>
|
||||
<div class="row">
|
||||
<label class="label">谷歌验证码</label>
|
||||
<el-input
|
||||
v-model="configForm.googleCode"
|
||||
placeholder="请输入6位谷歌验证码"
|
||||
maxlength="6"
|
||||
@input="handleConfigGoogleCodeInput"
|
||||
>
|
||||
<template slot="prepend">
|
||||
<i class="el-icon-key"></i>
|
||||
</template>
|
||||
</el-input>
|
||||
</div>
|
||||
<span slot="footer" class="dialog-footer">
|
||||
<el-button @click="visibleConfigEdit=false">取消</el-button>
|
||||
<el-button type="primary" @click="submitConfigEdit">确认修改</el-button>
|
||||
@@ -272,7 +285,7 @@ export default {
|
||||
// 店铺配置列表
|
||||
shopConfigs: [],
|
||||
visibleConfigEdit: false,
|
||||
configForm: { id: '', chainLabel: '', chainValue: '', payAddress: '', payCoins: [], payCoin: '' },
|
||||
configForm: { id: '', chainLabel: '', chainValue: '', payAddress: '', payCoins: [], payCoin: '', googleCode: '' },
|
||||
productOptions: [],
|
||||
coinOptions: coinList || [],
|
||||
editCoinOptionsApi: [],
|
||||
@@ -554,8 +567,14 @@ export default {
|
||||
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 > balanceInt) { callback(new Error('提现金额不能大于可用余额')); return }
|
||||
// 提现金额必须大于手续费
|
||||
if (amtInt <= feeInt) { callback(new Error('提现金额必须大于手续费')); return }
|
||||
// 实际到账金额(提现金额 - 手续费)必须大于0
|
||||
const actualInt = amtInt - feeInt
|
||||
if (actualInt <= 0) { callback(new Error('提现金额扣除手续费后必须大于0')); return }
|
||||
// 最小提现金额为 1
|
||||
if (amtInt < 1000000) { callback(new Error('最小提现金额为 1')); return }
|
||||
callback()
|
||||
},
|
||||
@@ -701,7 +720,8 @@ export default {
|
||||
chainValue: d.value || '',
|
||||
payAddress: d.address || '',
|
||||
payCoins: preSelected,
|
||||
payCoin: preSelected.join(',')
|
||||
payCoin: preSelected.join(','),
|
||||
googleCode: ''
|
||||
}
|
||||
} else {
|
||||
// 回退:使用行内已有数据
|
||||
@@ -715,7 +735,8 @@ export default {
|
||||
chainValue: row.chain || '',
|
||||
payAddress: row.payAddress || '',
|
||||
payCoins,
|
||||
payCoin: payCoins.join(',')
|
||||
payCoin: payCoins.join(','),
|
||||
googleCode: ''
|
||||
}
|
||||
}
|
||||
this.visibleConfigEdit = true
|
||||
@@ -727,19 +748,42 @@ export default {
|
||||
this.deleteShopConfig({id:row.id})
|
||||
},
|
||||
|
||||
/**
|
||||
* 处理谷歌验证码输入(仅允许数字)
|
||||
*/
|
||||
handleConfigGoogleCodeInput(v) {
|
||||
this.configForm.googleCode = String(v || '').replace(/\D/g, '')
|
||||
},
|
||||
/**
|
||||
* 修改配置弹窗关闭时清空验证码
|
||||
*/
|
||||
handleConfigEditClose() {
|
||||
this.configForm.googleCode = ''
|
||||
},
|
||||
/**
|
||||
* 提交配置修改
|
||||
*/
|
||||
async submitConfigEdit() {
|
||||
// 仅校验钱包地址
|
||||
// 校验钱包地址
|
||||
const addr = (this.configForm.payAddress || '').trim()
|
||||
if (!addr) {
|
||||
this.$message.warning('请输入钱包地址')
|
||||
return
|
||||
}
|
||||
|
||||
// 校验谷歌验证码
|
||||
const googleCode = String(this.configForm.googleCode || '').trim()
|
||||
if (!googleCode) {
|
||||
this.$message.warning('请输入谷歌验证码')
|
||||
return
|
||||
}
|
||||
if (!/^\d{6}$/.test(googleCode)) {
|
||||
this.$message.warning('谷歌验证码必须是6位数字')
|
||||
return
|
||||
}
|
||||
|
||||
/**
|
||||
* 使用 RSA 加密钱包地址(与“钱包绑定”页面保持一致:同步优先,异步兜底)
|
||||
* 使用 RSA 加密钱包地址(与"钱包绑定"页面保持一致:同步优先,异步兜底)
|
||||
* @type {string}
|
||||
*/
|
||||
let encryptedPayAddress = addr
|
||||
@@ -761,7 +805,8 @@ export default {
|
||||
const payload = {
|
||||
id: this.configForm.id,
|
||||
chain: this.configForm.chainValue || this.configForm.chainLabel || '',
|
||||
payAddress: encryptedPayAddress
|
||||
payAddress: encryptedPayAddress,
|
||||
gcode: googleCode
|
||||
}
|
||||
try {
|
||||
const res = await updateShopConfigV2(payload)
|
||||
|
||||
@@ -301,18 +301,38 @@
|
||||
v-for="(row, idx) in editDialog.form.coinAndAlgoList"
|
||||
:key="'edit-ca-' + idx"
|
||||
>
|
||||
<el-input
|
||||
<el-select
|
||||
v-model="row.coin"
|
||||
placeholder="币种"
|
||||
placeholder="请选择币种"
|
||||
class="coin-input"
|
||||
@input="editHandleCoinInput(idx)"
|
||||
/>
|
||||
<el-input
|
||||
@change="editHandleCoinChange(idx, $event)"
|
||||
:loading="loadingCoins"
|
||||
filterable
|
||||
clearable
|
||||
>
|
||||
<el-option
|
||||
v-for="coin in coinOptions"
|
||||
:key="coin"
|
||||
:label="coin"
|
||||
:value="coin"
|
||||
/>
|
||||
</el-select>
|
||||
<el-select
|
||||
v-model="row.algorithm"
|
||||
placeholder="算法"
|
||||
placeholder="请选择算法"
|
||||
class="algo-input"
|
||||
@input="editHandleAlgorithmInput(idx)"
|
||||
/>
|
||||
:loading="loadingAlgos[idx]"
|
||||
:disabled="!row.coin"
|
||||
filterable
|
||||
clearable
|
||||
>
|
||||
<el-option
|
||||
v-for="algo in (algoOptionsMap[row.coin] || [])"
|
||||
:key="algo"
|
||||
:label="algo"
|
||||
:value="algo"
|
||||
/>
|
||||
</el-select>
|
||||
<el-input
|
||||
v-model="row.theoryPower"
|
||||
placeholder="理论算力"
|
||||
@@ -417,7 +437,7 @@
|
||||
* - 支持查看详情、修改、删除
|
||||
*/
|
||||
import { updateProduct,deleteMachine, deleteProduct, getMachineInfo,getShopMachineListForSeller,getPayTypes, updateGpuMachine,updateAsicMachine} from '../../api/products'
|
||||
|
||||
import { getSupportCoin,getSupportAlgo } from '../../api/machine'
|
||||
// 本页用户偏好存储键:记住矿机种类(ASIC/GPU)
|
||||
const MACHINE_TYPE_KEY = 'account_products_machine_type'
|
||||
|
||||
@@ -438,6 +458,12 @@ export default {
|
||||
total: 0,
|
||||
},
|
||||
coinOptions: [],
|
||||
/** 算法选项映射 { coin: [algo1, algo2, ...] } */
|
||||
algoOptionsMap: {},
|
||||
/** 加载币种状态 */
|
||||
loadingCoins: false,
|
||||
/** 加载算法状态映射 { index: boolean } */
|
||||
loadingAlgos: {},
|
||||
editDialog: {
|
||||
visible: false,
|
||||
saving: false,
|
||||
@@ -537,19 +563,96 @@ export default {
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
/** 编辑弹窗:币种输入过滤(仅字母数字,转大写) */
|
||||
editHandleCoinInput(index) {
|
||||
const r = this.editDialog.form.coinAndAlgoList[index]
|
||||
let v = String(r.coin || '')
|
||||
v = v.replace(/[\u4e00-\u9fa5]/g, '').replace(/[^A-Za-z0-9]/g, '')
|
||||
this.$set(this.editDialog.form.coinAndAlgoList[index], 'coin', v.toUpperCase())
|
||||
/**
|
||||
* 加载支持的币种列表
|
||||
*/
|
||||
async loadSupportCoins() {
|
||||
this.loadingCoins = true
|
||||
try {
|
||||
const res = await getSupportCoin()
|
||||
if (res && (res.code === 0 || res.code === 200)) {
|
||||
const data = res.data || []
|
||||
// 处理返回的数据,可能是数组或对象
|
||||
if (Array.isArray(data)) {
|
||||
this.coinOptions = data.map(item => {
|
||||
// 如果是对象,取 coin 字段;如果是字符串,直接使用
|
||||
return typeof item === 'string' ? item : (item.coin || item.name || item)
|
||||
}).filter(Boolean)
|
||||
} else if (data && typeof data === 'object') {
|
||||
// 如果是对象,尝试提取币种列表
|
||||
this.coinOptions = Object.keys(data).map(key => {
|
||||
const item = data[key]
|
||||
return typeof item === 'string' ? item : (item.coin || item.name || key)
|
||||
}).filter(Boolean)
|
||||
}
|
||||
// 去重并排序
|
||||
this.coinOptions = [...new Set(this.coinOptions)].sort()
|
||||
}
|
||||
} catch (e) {
|
||||
console.error('加载币种列表失败', e)
|
||||
} finally {
|
||||
this.loadingCoins = false
|
||||
}
|
||||
},
|
||||
/** 编辑弹窗:算法输入过滤(仅字母数字和-,转大写) */
|
||||
editHandleAlgorithmInput(index) {
|
||||
const r = this.editDialog.form.coinAndAlgoList[index]
|
||||
let v = String(r.algorithm || '')
|
||||
v = v.replace(/[\u4e00-\u9fa5]/g, '').replace(/[^A-Za-z0-9-]/g, '')
|
||||
this.$set(this.editDialog.form.coinAndAlgoList[index], 'algorithm', v.toUpperCase())
|
||||
/**
|
||||
* 币种选择变化处理(编辑弹窗)
|
||||
* @param {number} index - 行索引
|
||||
* @param {string} coin - 选择的币种
|
||||
*/
|
||||
async editHandleCoinChange(index, coin) {
|
||||
// 清空当前行的算法选择
|
||||
this.$set(this.editDialog.form.coinAndAlgoList[index], 'algorithm', '')
|
||||
// 如果选择了币种,加载对应的算法列表
|
||||
if (coin) {
|
||||
await this.editLoadAlgorithmsForCoin(coin, index)
|
||||
}
|
||||
},
|
||||
/**
|
||||
* 加载指定币种支持的算法列表(编辑弹窗)
|
||||
* @param {string} coin - 币种名称
|
||||
* @param {number} index - 行索引(用于显示加载状态)
|
||||
*/
|
||||
async editLoadAlgorithmsForCoin(coin, index) {
|
||||
if (!coin) return
|
||||
|
||||
// 如果已经加载过该币种的算法,直接返回
|
||||
if (this.algoOptionsMap[coin] && this.algoOptionsMap[coin].length > 0) {
|
||||
return
|
||||
}
|
||||
|
||||
// 设置加载状态
|
||||
this.$set(this.loadingAlgos, index, true)
|
||||
|
||||
try {
|
||||
const res = await getSupportAlgo(coin)
|
||||
if (res && (res.code === 0 || res.code === 200)) {
|
||||
const data = res.data || []
|
||||
let algorithms = []
|
||||
|
||||
// 处理返回的数据,可能是数组或对象
|
||||
if (Array.isArray(data)) {
|
||||
algorithms = data.map(item => {
|
||||
// 如果是对象,取 algorithm 或 algo 字段;如果是字符串,直接使用
|
||||
return typeof item === 'string' ? item : (item.algorithm || item.algo || item.name || item)
|
||||
}).filter(Boolean)
|
||||
} else if (data && typeof data === 'object') {
|
||||
// 如果是对象,尝试提取算法列表
|
||||
algorithms = Object.keys(data).map(key => {
|
||||
const item = data[key]
|
||||
return typeof item === 'string' ? item : (item.algorithm || item.algo || item.name || key)
|
||||
}).filter(Boolean)
|
||||
}
|
||||
|
||||
// 去重并排序,保存到映射中
|
||||
this.$set(this.algoOptionsMap, coin, [...new Set(algorithms)].sort())
|
||||
}
|
||||
} catch (e) {
|
||||
console.error(`加载币种 ${coin} 的算法列表失败`, e)
|
||||
// 设置空数组,避免重复请求
|
||||
this.$set(this.algoOptionsMap, coin, [])
|
||||
} finally {
|
||||
this.$set(this.loadingAlgos, index, false)
|
||||
}
|
||||
},
|
||||
/** 编辑弹窗:理论算力限制(6整数+4小数) */
|
||||
editHandleRowTheoryInput(index) {
|
||||
@@ -580,8 +683,11 @@ export default {
|
||||
return
|
||||
}
|
||||
const last = list[list.length - 1] || { unit: 'TH/S' }
|
||||
const newIndex = list.length
|
||||
list.push({ coin: '', algorithm: '', theoryPower: '', unit: last.unit || 'TH/S', coinAndPowerId: null })
|
||||
this.$set(this.editDialog.form, 'coinAndAlgoList', list)
|
||||
// 初始化新行的加载状态
|
||||
this.$set(this.loadingAlgos, newIndex, false)
|
||||
},
|
||||
/** 编辑弹窗:删除一行(至少保留1行) */
|
||||
editHandleRemoveRow(index) {
|
||||
@@ -1243,6 +1349,16 @@ export default {
|
||||
})
|
||||
this.editDialog.form = form
|
||||
this.editDialog.visible = true
|
||||
// 打开弹窗时加载币种列表
|
||||
this.loadSupportCoins()
|
||||
// 如果已有币种数据,预加载对应的算法列表
|
||||
if (form.coinAndAlgoList && form.coinAndAlgoList.length > 0) {
|
||||
form.coinAndAlgoList.forEach((row, idx) => {
|
||||
if (row.coin) {
|
||||
this.editLoadAlgorithmsForCoin(row.coin, idx)
|
||||
}
|
||||
})
|
||||
}
|
||||
},
|
||||
|
||||
/** 保存编辑 */
|
||||
|
||||
@@ -16,10 +16,10 @@
|
||||
{{ getStatusText }}
|
||||
</span>
|
||||
<el-button
|
||||
:type="getButtonType"
|
||||
type="text"
|
||||
@click="handleButtonClick"
|
||||
:loading="loading"
|
||||
class="security-btn"
|
||||
class="security-btn two-factor-btn"
|
||||
>
|
||||
{{ getButtonText }}
|
||||
</el-button>
|
||||
@@ -28,6 +28,56 @@
|
||||
<div class="security-divider"></div>
|
||||
</div>
|
||||
|
||||
<!-- 修改密码 -->
|
||||
<div class="security-item-wrapper">
|
||||
<div class="security-item">
|
||||
<div class="security-left">
|
||||
<div class="security-icon">
|
||||
<i class="el-icon-edit"></i>
|
||||
</div>
|
||||
<div class="security-info">
|
||||
<div class="security-title">修改密码</div>
|
||||
<p class="security-desc">定期修改密码可以提高账户安全性,建议使用强密码并定期更换。</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="security-right">
|
||||
<el-button
|
||||
type="text"
|
||||
@click="handleChangePassword"
|
||||
class="security-btn change-password-btn"
|
||||
>
|
||||
修改
|
||||
</el-button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="security-divider"></div>
|
||||
</div>
|
||||
|
||||
<!-- 注销账号 -->
|
||||
<div class="security-item-wrapper">
|
||||
<div class="security-item">
|
||||
<div class="security-left">
|
||||
<div class="security-icon">
|
||||
<i class="el-icon-warning"></i>
|
||||
</div>
|
||||
<div class="security-info">
|
||||
<div class="security-title">注销账号</div>
|
||||
<p class="security-desc">注销账号将永久删除您的账户和所有相关数据,此操作不可恢复,请谨慎操作。</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="security-right">
|
||||
<el-button
|
||||
type="text"
|
||||
@click="handleDeleteAccount"
|
||||
class="security-btn delete-account-btn"
|
||||
>
|
||||
注销
|
||||
</el-button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="security-divider"></div>
|
||||
</div>
|
||||
|
||||
<!-- 第一步:显示二维码和密钥 -->
|
||||
<el-dialog
|
||||
title="开启双重验证 - 步骤 1/2"
|
||||
@@ -263,13 +313,160 @@
|
||||
</el-button>
|
||||
</span>
|
||||
</el-dialog>
|
||||
|
||||
<!-- 修改密码弹窗 -->
|
||||
<el-dialog
|
||||
title="修改密码"
|
||||
:visible.sync="changePasswordDialogVisible"
|
||||
width="500px"
|
||||
:close-on-click-modal="false"
|
||||
@close="handleChangePasswordDialogClose"
|
||||
>
|
||||
<el-form
|
||||
ref="changePasswordForm"
|
||||
:model="changePasswordForm"
|
||||
:rules="changePasswordRules"
|
||||
label-position="top"
|
||||
>
|
||||
<el-form-item label="用户邮箱">
|
||||
<el-input
|
||||
:value="userEmail"
|
||||
readonly
|
||||
disabled
|
||||
class="email-display"
|
||||
/>
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item label="邮箱验证码" prop="emailCode">
|
||||
<div class="code-input-group">
|
||||
<el-input
|
||||
v-model="changePasswordForm.emailCode"
|
||||
placeholder="请输入邮箱验证码"
|
||||
class="code-input"
|
||||
maxlength="10"
|
||||
clearable
|
||||
/>
|
||||
<el-button
|
||||
type="primary"
|
||||
@click="handleSendChangePasswordCode"
|
||||
:loading="sendingChangePasswordCode"
|
||||
:disabled="changePasswordCountdown > 0"
|
||||
>
|
||||
{{ changePasswordCountdown > 0 ? `${changePasswordCountdown}秒后重试` : '获取验证码' }}
|
||||
</el-button>
|
||||
</div>
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item label="新密码" prop="password">
|
||||
<el-input
|
||||
v-model="changePasswordForm.password"
|
||||
type="password"
|
||||
placeholder="请输入新密码(8-32位)"
|
||||
show-password
|
||||
clearable
|
||||
/>
|
||||
<!-- 密码规则提示 -->
|
||||
<div class="password-tip">
|
||||
<i class="el-icon-info"></i>
|
||||
<span>密码需包含大小写字母、数字和特殊字符,长度8-32位</span>
|
||||
</div>
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item label="确认新密码" prop="confirmPassword">
|
||||
<el-input
|
||||
v-model="changePasswordForm.confirmPassword"
|
||||
type="password"
|
||||
placeholder="请再次输入新密码"
|
||||
show-password
|
||||
clearable
|
||||
/>
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item label="谷歌验证码" prop="googleCode">
|
||||
<el-input
|
||||
v-model="changePasswordForm.googleCode"
|
||||
placeholder="请输入6位动态口令"
|
||||
maxlength="6"
|
||||
@input="handleChangePasswordGoogleCodeInput"
|
||||
clearable
|
||||
/>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<span slot="footer" class="dialog-footer">
|
||||
<el-button @click="changePasswordDialogVisible = false">取消</el-button>
|
||||
<el-button type="primary" @click="handleConfirmChangePassword" :loading="changingPassword">
|
||||
确认修改
|
||||
</el-button>
|
||||
</span>
|
||||
</el-dialog>
|
||||
|
||||
<!-- 注销账号弹窗 -->
|
||||
<el-dialog
|
||||
title="注销账号"
|
||||
:visible.sync="deleteAccountDialogVisible"
|
||||
width="500px"
|
||||
:close-on-click-modal="false"
|
||||
@close="handleDeleteAccountDialogClose"
|
||||
>
|
||||
<el-form
|
||||
ref="deleteAccountForm"
|
||||
:model="deleteAccountForm"
|
||||
:rules="deleteAccountRules"
|
||||
label-position="top"
|
||||
>
|
||||
<el-form-item label="用户邮箱">
|
||||
<el-input
|
||||
:value="userEmail"
|
||||
readonly
|
||||
disabled
|
||||
class="email-display"
|
||||
/>
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item label="邮箱验证码" prop="emailCode">
|
||||
<div class="code-input-group">
|
||||
<el-input
|
||||
v-model="deleteAccountForm.emailCode"
|
||||
placeholder="请输入邮箱验证码"
|
||||
class="code-input"
|
||||
maxlength="10"
|
||||
clearable
|
||||
/>
|
||||
<el-button
|
||||
type="primary"
|
||||
@click="handleSendDeleteAccountCode"
|
||||
:loading="sendingDeleteAccountCode"
|
||||
:disabled="deleteAccountCountdown > 0"
|
||||
>
|
||||
{{ deleteAccountCountdown > 0 ? `${deleteAccountCountdown}秒后重试` : '获取验证码' }}
|
||||
</el-button>
|
||||
</div>
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item label="谷歌验证码" prop="googleCode">
|
||||
<el-input
|
||||
v-model="deleteAccountForm.googleCode"
|
||||
placeholder="请输入6位动态口令"
|
||||
maxlength="6"
|
||||
@input="handleDeleteAccountGoogleCodeInput"
|
||||
clearable
|
||||
/>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<span slot="footer" class="dialog-footer">
|
||||
<el-button @click="deleteAccountDialogVisible = false">取消</el-button>
|
||||
<el-button type="danger" @click="handleConfirmDeleteAccount" :loading="deletingAccount">
|
||||
确定注销
|
||||
</el-button>
|
||||
</span>
|
||||
</el-dialog>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { getBindInfo, bindGoogle, sendOpenGoogleCode,getGoogleStatus,closeStepTwo,sendCloseGoogleCode,openStepTwo } from '../../api/verification'
|
||||
import { rsaEncrypt } from '../../utils/rsaEncrypt'
|
||||
|
||||
import { closeAccount, sendCloseAccount,sendUpdatePwdCode,updatePasswordInCenter} from '../../api/user'
|
||||
export default {
|
||||
name: 'SecuritySettings',
|
||||
data() {
|
||||
@@ -294,6 +491,19 @@ export default {
|
||||
callback()
|
||||
}
|
||||
|
||||
/**
|
||||
* 确认密码验证
|
||||
*/
|
||||
const validateConfirmPassword = (rule, value, callback) => {
|
||||
if (!value) {
|
||||
callback(new Error('请再次输入新密码'))
|
||||
return
|
||||
}
|
||||
// 注意:这里需要在 data() 返回后,通过 this.changePasswordForm.password 访问
|
||||
// 但由于这是在 data() 函数内部,无法访问 this,所以需要在 methods 中定义
|
||||
callback()
|
||||
}
|
||||
|
||||
return {
|
||||
isEnabled: false, // 是否已开启双重验证
|
||||
loading: false,
|
||||
@@ -302,6 +512,9 @@ export default {
|
||||
step2Visible: false,
|
||||
closeDialogVisible: false, // 关闭双重验证弹窗
|
||||
openDialogVisible: false, // 开启双重验证弹窗
|
||||
deleteAccountDialogVisible: false, // 注销账号弹窗
|
||||
changePasswordDialogVisible: false, // 修改密码弹窗
|
||||
userEmail: '', // 用户邮箱
|
||||
qrCodeUrl: '',
|
||||
secretKey: '',
|
||||
sendingCode: false,
|
||||
@@ -313,6 +526,14 @@ export default {
|
||||
sendingOpenCode: false, // 发送开启验证码的 loading
|
||||
openCountdown: 0, // 开启验证码倒计时
|
||||
openCountdownTimer: null, // 开启验证码倒计时定时器
|
||||
sendingDeleteAccountCode: false, // 发送注销账号验证码的 loading
|
||||
deleteAccountCountdown: 0, // 注销账号验证码倒计时
|
||||
deleteAccountCountdownTimer: null, // 注销账号验证码倒计时定时器
|
||||
deletingAccount: false, // 注销账号的 loading
|
||||
sendingChangePasswordCode: false, // 发送修改密码验证码的 loading
|
||||
changePasswordCountdown: 0, // 修改密码验证码倒计时
|
||||
changePasswordCountdownTimer: null, // 修改密码验证码倒计时定时器
|
||||
changingPassword: false, // 修改密码的 loading
|
||||
closing: false, // 关闭双重验证的 loading
|
||||
opening: false, // 开启双重验证的 loading
|
||||
submitting: false,
|
||||
@@ -329,6 +550,16 @@ export default {
|
||||
emailCode: '',
|
||||
googleCode: ''
|
||||
},
|
||||
deleteAccountForm: {
|
||||
emailCode: '',
|
||||
googleCode: ''
|
||||
},
|
||||
changePasswordForm: {
|
||||
emailCode: '',
|
||||
password: '',
|
||||
confirmPassword: '',
|
||||
googleCode: ''
|
||||
},
|
||||
verifyRules: {
|
||||
password: [
|
||||
{ required: true, validator: validatePassword, trigger: 'blur' }
|
||||
@@ -362,6 +593,17 @@ export default {
|
||||
{ pattern: /^\d{6}$/, message: '请输入6位数字', trigger: 'blur' }
|
||||
]
|
||||
},
|
||||
deleteAccountRules: {
|
||||
emailCode: [
|
||||
{ required: true, message: '请输入邮箱验证码', trigger: 'blur' },
|
||||
{ min: 1, max: 10, message: '验证码长度为1-10位', trigger: 'blur' }
|
||||
],
|
||||
googleCode: [
|
||||
{ required: true, message: '请输入谷歌验证码', trigger: 'blur' },
|
||||
{ pattern: /^\d{6}$/, message: '请输入6位数字', trigger: 'blur' }
|
||||
]
|
||||
},
|
||||
changePasswordRules: {},
|
||||
googleStatus: 1 // 谷歌验证状态:0 开启;1 未绑定;2 关闭
|
||||
}
|
||||
},
|
||||
@@ -439,6 +681,7 @@ export default {
|
||||
},
|
||||
mounted() {
|
||||
this.check2FAStatus()
|
||||
this.loadUserEmail()
|
||||
},
|
||||
beforeDestroy() {
|
||||
if (this.countdownTimer) {
|
||||
@@ -450,6 +693,9 @@ export default {
|
||||
if (this.openCountdownTimer) {
|
||||
clearInterval(this.openCountdownTimer)
|
||||
}
|
||||
if (this.deleteAccountCountdownTimer) {
|
||||
clearInterval(this.deleteAccountCountdownTimer)
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
/**
|
||||
@@ -878,6 +1124,260 @@ export default {
|
||||
*/
|
||||
handleCannotGetGoogleCode() {
|
||||
this.$message.info('请确保已正确扫描二维码或输入密钥,并检查时间同步')
|
||||
},
|
||||
/**
|
||||
* 修改密码 - 显示弹窗
|
||||
*/
|
||||
handleChangePassword() {
|
||||
if (!this.userEmail) {
|
||||
this.$message.warning('无法获取用户邮箱,请重新登录')
|
||||
return
|
||||
}
|
||||
this.changePasswordDialogVisible = true
|
||||
},
|
||||
/**
|
||||
* 发送修改密码的邮箱验证码
|
||||
*/
|
||||
async handleSendChangePasswordCode() {
|
||||
if (this.changePasswordCountdown > 0) return
|
||||
if (!this.userEmail) {
|
||||
this.$message.warning('无法获取用户邮箱,请重新登录')
|
||||
return
|
||||
}
|
||||
|
||||
this.sendingChangePasswordCode = true
|
||||
try {
|
||||
const res = await sendUpdatePwdCode({ email: this.userEmail })
|
||||
if (res && (res.code === 0 || res.code === 200)) {
|
||||
this.$message.success('验证码已发送到您的邮箱')
|
||||
this.startChangePasswordCountdown()
|
||||
} else {
|
||||
this.$message.error(res?.message || res?.msg || '发送验证码失败')
|
||||
}
|
||||
} catch (e) {
|
||||
console.error('发送验证码失败', e)
|
||||
this.$message.error('发送验证码失败,请稍后重试')
|
||||
} finally {
|
||||
this.sendingChangePasswordCode = false
|
||||
}
|
||||
},
|
||||
/**
|
||||
* 开始修改密码验证码倒计时
|
||||
*/
|
||||
startChangePasswordCountdown() {
|
||||
this.changePasswordCountdown = 60
|
||||
this.changePasswordCountdownTimer = setInterval(() => {
|
||||
this.changePasswordCountdown--
|
||||
if (this.changePasswordCountdown <= 0) {
|
||||
clearInterval(this.changePasswordCountdownTimer)
|
||||
this.changePasswordCountdownTimer = null
|
||||
}
|
||||
}, 1000)
|
||||
},
|
||||
/**
|
||||
* 修改密码弹窗的谷歌验证码输入(仅数字)
|
||||
*/
|
||||
handleChangePasswordGoogleCodeInput(val) {
|
||||
this.changePasswordForm.googleCode = val.replace(/\D/g, '').slice(0, 6)
|
||||
},
|
||||
/**
|
||||
* 确认密码验证(用于表单验证规则)
|
||||
*/
|
||||
validateConfirmPassword(rule, value, callback) {
|
||||
if (!value) {
|
||||
callback(new Error('请再次输入新密码'))
|
||||
return
|
||||
}
|
||||
if (value !== this.changePasswordForm.password) {
|
||||
callback(new Error('两次输入的密码不一致'))
|
||||
return
|
||||
}
|
||||
callback()
|
||||
},
|
||||
/**
|
||||
* 确认修改密码
|
||||
*/
|
||||
async handleConfirmChangePassword() {
|
||||
try {
|
||||
const valid = await this.$refs.changePasswordForm.validate()
|
||||
if (!valid) return
|
||||
|
||||
this.changingPassword = true
|
||||
|
||||
// 对密码进行 RSA 加密
|
||||
const encryptedPassword = await rsaEncrypt(this.changePasswordForm.password)
|
||||
if (!encryptedPassword) {
|
||||
this.$message.error('密码加密失败,请稍后重试')
|
||||
this.changingPassword = false
|
||||
return
|
||||
}
|
||||
|
||||
const params = {
|
||||
code: this.changePasswordForm.emailCode, // 邮箱验证码
|
||||
email: this.userEmail, // 邮箱
|
||||
password: encryptedPassword, // RSA 加密后的新密码
|
||||
gcode: this.changePasswordForm.googleCode // 谷歌验证码
|
||||
}
|
||||
|
||||
const res = await updatePasswordInCenter(params)
|
||||
if (res && (res.code === 0 || res.code === 200)) {
|
||||
this.$message.success('密码修改成功')
|
||||
this.changePasswordDialogVisible = false
|
||||
this.handleChangePasswordDialogClose()
|
||||
} else {
|
||||
this.$message.error(res?.message || res?.msg || '修改密码失败,请检查输入信息')
|
||||
}
|
||||
} catch (e) {
|
||||
console.error('修改密码失败', e)
|
||||
this.$message.error('修改密码失败,请稍后重试')
|
||||
} finally {
|
||||
this.changingPassword = false
|
||||
}
|
||||
},
|
||||
/**
|
||||
* 修改密码弹窗关闭时的处理
|
||||
*/
|
||||
handleChangePasswordDialogClose() {
|
||||
this.changePasswordForm = {
|
||||
emailCode: '',
|
||||
password: '',
|
||||
confirmPassword: '',
|
||||
googleCode: ''
|
||||
}
|
||||
this.$refs.changePasswordForm && this.$refs.changePasswordForm.clearValidate()
|
||||
},
|
||||
/**
|
||||
* 加载用户邮箱
|
||||
*/
|
||||
loadUserEmail() {
|
||||
try {
|
||||
const getVal = (key) => {
|
||||
const raw = localStorage.getItem(key)
|
||||
if (raw == null) return null
|
||||
try { return JSON.parse(raw) } catch (e) { return raw }
|
||||
}
|
||||
const val = getVal('leasEmail') || ''
|
||||
this.userEmail = typeof val === 'string' ? val : String(val)
|
||||
} catch (e) {
|
||||
console.error('读取用户邮箱失败', e)
|
||||
this.userEmail = ''
|
||||
}
|
||||
},
|
||||
/**
|
||||
* 注销账号 - 显示弹窗
|
||||
*/
|
||||
handleDeleteAccount() {
|
||||
if (!this.userEmail) {
|
||||
this.$message.warning('无法获取用户邮箱,请重新登录')
|
||||
return
|
||||
}
|
||||
this.deleteAccountDialogVisible = true
|
||||
},
|
||||
/**
|
||||
* 发送注销账号的邮箱验证码
|
||||
*/
|
||||
async handleSendDeleteAccountCode() {
|
||||
if (this.deleteAccountCountdown > 0) return
|
||||
if (!this.userEmail) {
|
||||
this.$message.warning('无法获取用户邮箱,请重新登录')
|
||||
return
|
||||
}
|
||||
|
||||
this.sendingDeleteAccountCode = true
|
||||
try {
|
||||
const res = await sendCloseAccount({ email: this.userEmail })
|
||||
if (res && (res.code === 0 || res.code === 200)) {
|
||||
this.$message.success('验证码已发送到您的邮箱')
|
||||
this.startDeleteAccountCountdown()
|
||||
} else {
|
||||
this.$message.error(res?.message || res?.msg || '发送验证码失败')
|
||||
}
|
||||
} catch (e) {
|
||||
console.error('发送验证码失败', e)
|
||||
this.$message.error('发送验证码失败,请稍后重试')
|
||||
} finally {
|
||||
this.sendingDeleteAccountCode = false
|
||||
}
|
||||
},
|
||||
/**
|
||||
* 开始注销账号验证码倒计时
|
||||
*/
|
||||
startDeleteAccountCountdown() {
|
||||
this.deleteAccountCountdown = 60
|
||||
this.deleteAccountCountdownTimer = setInterval(() => {
|
||||
this.deleteAccountCountdown--
|
||||
if (this.deleteAccountCountdown <= 0) {
|
||||
clearInterval(this.deleteAccountCountdownTimer)
|
||||
this.deleteAccountCountdownTimer = null
|
||||
}
|
||||
}, 1000)
|
||||
},
|
||||
/**
|
||||
* 注销账号弹窗的谷歌验证码输入(仅数字)
|
||||
*/
|
||||
handleDeleteAccountGoogleCodeInput(val) {
|
||||
this.deleteAccountForm.googleCode = val.replace(/\D/g, '').slice(0, 6)
|
||||
},
|
||||
/**
|
||||
* 确认注销账号
|
||||
*/
|
||||
async handleConfirmDeleteAccount() {
|
||||
try {
|
||||
const valid = await this.$refs.deleteAccountForm.validate()
|
||||
if (!valid) return
|
||||
|
||||
// 二次确认,提示用户不能恢复账号及相关信息
|
||||
this.$confirm(
|
||||
'注销账号将永久删除您的账户和所有相关数据,包括订单、余额、店铺等所有信息,此操作不可恢复。确定要继续吗?',
|
||||
'警告',
|
||||
{
|
||||
confirmButtonText: '确定注销',
|
||||
cancelButtonText: '取消',
|
||||
type: 'warning',
|
||||
dangerouslyUseHTMLString: false
|
||||
}
|
||||
).then(async () => {
|
||||
this.deletingAccount = true
|
||||
try {
|
||||
const params = {
|
||||
eCode: this.deleteAccountForm.emailCode, // 邮箱验证码
|
||||
gCode: this.deleteAccountForm.googleCode // 谷歌验证码
|
||||
}
|
||||
|
||||
const res = await closeAccount(params)
|
||||
if (res && (res.code === 0 || res.code === 200)) {
|
||||
this.$message.success('账号已成功注销')
|
||||
this.deleteAccountDialogVisible = false
|
||||
this.handleDeleteAccountDialogClose()
|
||||
// 清除登录信息并跳转到登录页
|
||||
localStorage.removeItem('leasToken')
|
||||
localStorage.removeItem('leasEmail')
|
||||
localStorage.removeItem('userInfo')
|
||||
window.dispatchEvent(new CustomEvent('login-status-changed'))
|
||||
this.$router.push('/login')
|
||||
}
|
||||
} catch (e) {
|
||||
console.error('注销账号失败', e)
|
||||
this.$message.error('注销失败,请稍后重试')
|
||||
} finally {
|
||||
this.deletingAccount = false
|
||||
}
|
||||
}).catch(() => {
|
||||
// 用户取消
|
||||
})
|
||||
} catch (e) {
|
||||
console.error('表单验证失败', e)
|
||||
}
|
||||
},
|
||||
/**
|
||||
* 注销账号弹窗关闭时的处理
|
||||
*/
|
||||
handleDeleteAccountDialogClose() {
|
||||
this.deleteAccountForm = {
|
||||
emailCode: '',
|
||||
googleCode: ''
|
||||
}
|
||||
this.$refs.deleteAccountForm && this.$refs.deleteAccountForm.clearValidate()
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -986,6 +1486,48 @@ export default {
|
||||
box-shadow: 0 4px 12px rgba(245, 108, 108, 0.3);
|
||||
}
|
||||
|
||||
/* 双重验证按钮样式 - 淡紫色背景,紫色字体 */
|
||||
.two-factor-btn {
|
||||
background: #f5f7ff !important;
|
||||
color: #667eea !important;
|
||||
border: 1px solid #f5f7ff !important;
|
||||
}
|
||||
|
||||
.two-factor-btn:hover {
|
||||
background: #f5f7ff !important;
|
||||
color: #667eea !important;
|
||||
transform: translateY(-2px);
|
||||
box-shadow: 0 4px 12px rgba(102, 126, 234, 0.2);
|
||||
}
|
||||
|
||||
/* 修改密码按钮样式 - 淡紫色背景,紫色字体 */
|
||||
.change-password-btn {
|
||||
background: #f5f7ff !important;
|
||||
color: #667eea !important;
|
||||
border: 1px solid #f5f7ff !important;
|
||||
}
|
||||
|
||||
.change-password-btn:hover {
|
||||
background: #f5f7ff !important;
|
||||
color: #667eea !important;
|
||||
transform: translateY(-2px);
|
||||
box-shadow: 0 4px 12px rgba(102, 126, 234, 0.2);
|
||||
}
|
||||
|
||||
/* 注销账号按钮样式 - 淡紫色背景,紫色字体 */
|
||||
.delete-account-btn {
|
||||
background: #f5f7ff !important;
|
||||
color: #667eea !important;
|
||||
border: 1px solid #f5f7ff !important;
|
||||
}
|
||||
|
||||
.delete-account-btn:hover {
|
||||
background: #f5f7ff !important;
|
||||
color: #667eea !important;
|
||||
transform: translateY(-2px);
|
||||
box-shadow: 0 4px 12px rgba(102, 126, 234, 0.2);
|
||||
}
|
||||
|
||||
.security-divider {
|
||||
height: 1px;
|
||||
background: #e4e7ed;
|
||||
@@ -1161,5 +1703,9 @@ export default {
|
||||
justify-content: flex-end;
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.email-display {
|
||||
background-color: #f5f7fa;
|
||||
}
|
||||
</style>
|
||||
|
||||
|
||||
@@ -1011,16 +1011,29 @@ export default {
|
||||
callback(new Error('请输入有效的金额'))
|
||||
return
|
||||
}
|
||||
|
||||
// 手续费与总需求按相同精度计算
|
||||
const feeInt = this.toScaledInt(this.withdrawForm.fee)
|
||||
const totalRequired = amountInt + feeInt
|
||||
|
||||
// 钱包余额转分(使用当前选中钱包的可用余额)
|
||||
// 钱包总余额(可用余额 + 冻结余额)
|
||||
const availableBalance = this.WalletData && (this.WalletData.walletBalance || this.WalletData.balance) || 0
|
||||
const balanceInt = this.toScaledInt(availableBalance)
|
||||
if (totalRequired > balanceInt) {
|
||||
const blockedBalance = this.WalletData && (this.WalletData.blockedBalance || 0) || 0
|
||||
const totalBalance = parseFloat(availableBalance) + parseFloat(blockedBalance)
|
||||
const totalBalanceInt = this.toScaledInt(totalBalance)
|
||||
|
||||
// 提现金额可以等于可用余额,但提现金额+手续费不能超过钱包总余额
|
||||
if (totalRequired > totalBalanceInt) {
|
||||
const totalText = this.formatDec6FromInt(totalRequired)
|
||||
callback(new Error(`提现金额加上手续费(${totalText} USDT)不能超过钱包余额`))
|
||||
callback(new Error(`提现金额加上手续费(${totalText} ${this.displayWithdrawSymbol})不能超过钱包余额`))
|
||||
return
|
||||
}
|
||||
|
||||
// 可用余额(用于判断提现金额是否超过可用余额)
|
||||
const availableBalanceInt = this.toScaledInt(availableBalance)
|
||||
// 允许提现金额等于可用余额,但不能大于
|
||||
if (amountInt > availableBalanceInt) {
|
||||
callback(new Error('提现金额不能大于可用余额'))
|
||||
return
|
||||
}
|
||||
|
||||
@@ -1037,6 +1050,13 @@ export default {
|
||||
return
|
||||
}
|
||||
|
||||
// 实际到账金额(提现金额 - 手续费)必须大于0
|
||||
const actualInt = amountInt - feeInt
|
||||
if (actualInt <= 0) {
|
||||
callback(new Error('提现金额扣除手续费后必须大于0'))
|
||||
return
|
||||
}
|
||||
|
||||
callback()
|
||||
},
|
||||
|
||||
|
||||
@@ -278,7 +278,7 @@
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('发送验证码失败:', error)
|
||||
this.$message.error(error.message || '发送验证码失败,请重试')
|
||||
|
||||
} finally {
|
||||
this.sendingCode = false
|
||||
}
|
||||
|
||||
Binary file not shown.
1
power_leasing/test/css/app.c0e6f336.css
Normal file
1
power_leasing/test/css/app.c0e6f336.css
Normal file
File diff suppressed because one or more lines are too long
@@ -1 +1 @@
|
||||
<!doctype html><html lang=""><head><meta charset="utf-8"><meta http-equiv="X-UA-Compatible" content="IE=edge"><meta name="viewport" content="width=device-width,initial-scale=1"><link rel="icon" href="/favicon.ico"><title>power_leasing</title><script defer="defer" src="/js/chunk-vendors.4369320b.js"></script><script defer="defer" src="/js/app.cc5f454d.js"></script><link href="/css/chunk-vendors.10dd4e95.css" rel="stylesheet"><link href="/css/app.395f1e08.css" rel="stylesheet"></head><body><noscript><strong>We're sorry but power_leasing doesn't work properly without JavaScript enabled. Please enable it to continue.</strong></noscript><div id="app"></div></body></html>
|
||||
<!doctype html><html lang=""><head><meta charset="utf-8"><meta http-equiv="X-UA-Compatible" content="IE=edge"><meta name="viewport" content="width=device-width,initial-scale=1"><link rel="icon" href="/favicon.ico"><title>power_leasing</title><script defer="defer" src="/js/chunk-vendors.4369320b.js"></script><script defer="defer" src="/js/app.7bd6edb2.js"></script><link href="/css/chunk-vendors.10dd4e95.css" rel="stylesheet"><link href="/css/app.c0e6f336.css" rel="stylesheet"></head><body><noscript><strong>We're sorry but power_leasing doesn't work properly without JavaScript enabled. Please enable it to continue.</strong></noscript><div id="app"></div></body></html>
|
||||
2
power_leasing/test/js/app.7bd6edb2.js
Normal file
2
power_leasing/test/js/app.7bd6edb2.js
Normal file
File diff suppressed because one or more lines are too long
1
power_leasing/test/js/app.7bd6edb2.js.map
Normal file
1
power_leasing/test/js/app.7bd6edb2.js.map
Normal file
File diff suppressed because one or more lines are too long
Reference in New Issue
Block a user