每周更新
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"
|
:key="nav.path"
|
||||||
:to="nav.path"
|
:to="nav.path"
|
||||||
class="nav-btn"
|
class="nav-btn"
|
||||||
active-class="active"
|
:class="{ active: isNavActive(nav.path) }"
|
||||||
:title="nav.description"
|
:title="nav.description"
|
||||||
>
|
>
|
||||||
<span class="nav-icon">{{ nav.icon }}</span>
|
<span class="nav-icon">{{ nav.icon }}</span>
|
||||||
@@ -30,9 +30,16 @@
|
|||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- 已登录:显示用户邮箱和退出按钮 -->
|
<!-- 已登录:显示用户邮箱、安全设置和退出按钮 -->
|
||||||
<div v-else class="user-info">
|
<div v-else class="user-info">
|
||||||
<span class="user-email">{{ userEmail }}</span>
|
<span class="user-email">{{ userEmail }}</span>
|
||||||
|
<router-link
|
||||||
|
to="/account/security-settings"
|
||||||
|
class="security-link"
|
||||||
|
active-class="active"
|
||||||
|
>
|
||||||
|
安全设置
|
||||||
|
</router-link>
|
||||||
<el-button
|
<el-button
|
||||||
type="text"
|
type="text"
|
||||||
size="small"
|
size="small"
|
||||||
@@ -113,6 +120,19 @@ export default {
|
|||||||
window.removeEventListener('login-status-changed', this.handleLoginStatusChanged)
|
window.removeEventListener('login-status-changed', this.handleLoginStatusChanged)
|
||||||
},
|
},
|
||||||
methods: {
|
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() {
|
loadCart() {
|
||||||
this.cart = readCart()
|
this.cart = readCart()
|
||||||
},
|
},
|
||||||
@@ -438,6 +458,36 @@ export default {
|
|||||||
color: #2c3e50;
|
color: #2c3e50;
|
||||||
font-size: 14px;
|
font-size: 14px;
|
||||||
font-weight: 600;
|
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>
|
||||||
<div v-show="isExpanded('consume', row, idx)" class="expand-panel">
|
<div v-show="isExpanded('consume', row, idx)" class="expand-panel">
|
||||||
<div class="expand-grid">
|
<div class="expand-grid">
|
||||||
<div class="expand-item"><span class="label">订单号</span><span class="value mono">{{ row.orderId || '' }}</span></div>
|
<div class="expand-item">
|
||||||
<div class="expand-item"><span class="label">支付地址</span><span class="value mono-ellipsis" :title="row.fromAddress">{{ row.fromAddress || '' }}</span></div>
|
<span class="label">订单号</span>
|
||||||
<div class="expand-item"><span class="label">收款地址</span><span class="value mono-ellipsis" :title="row.toAddress">{{ row.toAddress || '' }}</span></div>
|
<div class="value value-row">
|
||||||
<div class="expand-item" v-if="row.txHash"><span class="label">交易哈希</span><span class="value mono-ellipsis" :title="row.txHash">{{ row.txHash }}</span></div>
|
<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>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -16,20 +16,20 @@
|
|||||||
<div class="user-role" role="group" aria-label="导航分组切换">
|
<div class="user-role" role="group" aria-label="导航分组切换">
|
||||||
<button
|
<button
|
||||||
class="role-button"
|
class="role-button"
|
||||||
:class="{ active: activeRole === 'buyer' }"
|
:class="{ active: activeRole === 'buyer' && !isSecuritySettingsPage }"
|
||||||
@click="handleClickRole('buyer')"
|
@click="handleClickRole('buyer')"
|
||||||
@keydown.enter.prevent="handleClickRole('buyer')"
|
@keydown.enter.prevent="handleClickRole('buyer')"
|
||||||
@keydown.space.prevent="handleClickRole('buyer')"
|
@keydown.space.prevent="handleClickRole('buyer')"
|
||||||
:aria-pressed="activeRole === 'buyer'"
|
:aria-pressed="activeRole === 'buyer' && !isSecuritySettingsPage"
|
||||||
tabindex="0"
|
tabindex="0"
|
||||||
>买家相关</button>
|
>买家相关</button>
|
||||||
<button
|
<button
|
||||||
class="role-button"
|
class="role-button"
|
||||||
:class="{ active: activeRole === 'seller' }"
|
:class="{ active: activeRole === 'seller' && !isSecuritySettingsPage }"
|
||||||
@click="handleClickRole('seller')"
|
@click="handleClickRole('seller')"
|
||||||
@keydown.enter.prevent="handleClickRole('seller')"
|
@keydown.enter.prevent="handleClickRole('seller')"
|
||||||
@keydown.space.prevent="handleClickRole('seller')"
|
@keydown.space.prevent="handleClickRole('seller')"
|
||||||
:aria-pressed="activeRole === 'seller'"
|
:aria-pressed="activeRole === 'seller' && !isSecuritySettingsPage"
|
||||||
tabindex="0"
|
tabindex="0"
|
||||||
>卖家相关</button>
|
>卖家相关</button>
|
||||||
</div>
|
</div>
|
||||||
@@ -68,7 +68,6 @@ export default {
|
|||||||
// { label: '充值记录', to: '/account/rechargeRecord' },
|
// { label: '充值记录', to: '/account/rechargeRecord' },
|
||||||
// { label: '提现记录', to: '/account/withdrawalHistory' },
|
// { label: '提现记录', to: '/account/withdrawalHistory' },
|
||||||
{ label: '资金流水', to: '/account/funds-flow' },
|
{ label: '资金流水', to: '/account/funds-flow' },
|
||||||
{ label: '安全设置', to: '/account/security-settings' },
|
|
||||||
],
|
],
|
||||||
// 卖家侧导航
|
// 卖家侧导航
|
||||||
sellerLinks: [
|
sellerLinks: [
|
||||||
@@ -77,7 +76,6 @@ export default {
|
|||||||
{ label: '商品列表', to: '/account/products' },
|
{ label: '商品列表', to: '/account/products' },
|
||||||
{ label: '已售出订单', to: '/account/seller-orders' },
|
{ label: '已售出订单', to: '/account/seller-orders' },
|
||||||
{ label: '资金流水', to: '/account/seller-funds-flow' },
|
{ label: '资金流水', to: '/account/seller-funds-flow' },
|
||||||
{ label: '安全设置', to: '/account/security-settings' },
|
|
||||||
],
|
],
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -97,6 +95,14 @@ export default {
|
|||||||
displayedLinks() {
|
displayedLinks() {
|
||||||
return this.activeRole === 'buyer' ? this.buyerLinks : this.sellerLinks
|
return this.activeRole === 'buyer' ? this.buyerLinks : this.sellerLinks
|
||||||
},
|
},
|
||||||
|
/**
|
||||||
|
* 判断当前是否在安全设置页面
|
||||||
|
* @returns {boolean}
|
||||||
|
*/
|
||||||
|
isSecuritySettingsPage() {
|
||||||
|
const path = (this.$route && this.$route.path) || ''
|
||||||
|
return path === '/account/security-settings'
|
||||||
|
},
|
||||||
},
|
},
|
||||||
mounted() {
|
mounted() {
|
||||||
const getVal = (key) => {
|
const getVal = (key) => {
|
||||||
@@ -179,7 +185,10 @@ export default {
|
|||||||
'/account/shop-config'
|
'/account/shop-config'
|
||||||
]
|
]
|
||||||
// 安全设置页面买家和卖家都可见,不参与分组判断
|
// 安全设置页面买家和卖家都可见,不参与分组判断
|
||||||
|
// 在安全设置页面时,清除分组高亮(不设置 activeRole)
|
||||||
if (path === '/account/security-settings') {
|
if (path === '/account/security-settings') {
|
||||||
|
// 清除分组高亮,让所有分组按钮都不高亮
|
||||||
|
this.activeRole = null
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
const shouldBuyer = buyerPrefixes.some(p => path.indexOf(p) === 0)
|
const shouldBuyer = buyerPrefixes.some(p => path.indexOf(p) === 0)
|
||||||
|
|||||||
@@ -229,12 +229,25 @@
|
|||||||
</span>
|
</span>
|
||||||
</el-dialog>
|
</el-dialog>
|
||||||
<!-- 修改钱包绑定配置弹窗:参数保持与列表一致 -->
|
<!-- 修改钱包绑定配置弹窗:参数保持与列表一致 -->
|
||||||
<el-dialog title="修改配置" :visible.sync="visibleConfigEdit" width="560px">
|
<el-dialog title="修改配置" :visible.sync="visibleConfigEdit" width="560px" @close="handleConfigEditClose">
|
||||||
|
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<label class="label">钱包地址</label>
|
<label class="label">钱包地址</label>
|
||||||
<el-input v-model="configForm.payAddress" placeholder="请输入钱包地址" />
|
<el-input v-model="configForm.payAddress" placeholder="请输入钱包地址" />
|
||||||
</div>
|
</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">
|
<span slot="footer" class="dialog-footer">
|
||||||
<el-button @click="visibleConfigEdit=false">取消</el-button>
|
<el-button @click="visibleConfigEdit=false">取消</el-button>
|
||||||
<el-button type="primary" @click="submitConfigEdit">确认修改</el-button>
|
<el-button type="primary" @click="submitConfigEdit">确认修改</el-button>
|
||||||
@@ -272,7 +285,7 @@ export default {
|
|||||||
// 店铺配置列表
|
// 店铺配置列表
|
||||||
shopConfigs: [],
|
shopConfigs: [],
|
||||||
visibleConfigEdit: false,
|
visibleConfigEdit: false,
|
||||||
configForm: { id: '', chainLabel: '', chainValue: '', payAddress: '', payCoins: [], payCoin: '' },
|
configForm: { id: '', chainLabel: '', chainValue: '', payAddress: '', payCoins: [], payCoin: '', googleCode: '' },
|
||||||
productOptions: [],
|
productOptions: [],
|
||||||
coinOptions: coinList || [],
|
coinOptions: coinList || [],
|
||||||
editCoinOptionsApi: [],
|
editCoinOptionsApi: [],
|
||||||
@@ -554,8 +567,14 @@ export default {
|
|||||||
if (!Number.isFinite(amtInt) || amtInt <= 0) { callback(new Error('请输入有效的金额')); return }
|
if (!Number.isFinite(amtInt) || amtInt <= 0) { callback(new Error('请输入有效的金额')); return }
|
||||||
const feeInt = this.toScaledInt(this.withdrawForm.fee)
|
const feeInt = this.toScaledInt(this.withdrawForm.fee)
|
||||||
const balanceInt = this.toScaledInt((this.currentWithdrawRow && this.currentWithdrawRow.balance) || 0)
|
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 }
|
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 }
|
if (amtInt < 1000000) { callback(new Error('最小提现金额为 1')); return }
|
||||||
callback()
|
callback()
|
||||||
},
|
},
|
||||||
@@ -701,7 +720,8 @@ export default {
|
|||||||
chainValue: d.value || '',
|
chainValue: d.value || '',
|
||||||
payAddress: d.address || '',
|
payAddress: d.address || '',
|
||||||
payCoins: preSelected,
|
payCoins: preSelected,
|
||||||
payCoin: preSelected.join(',')
|
payCoin: preSelected.join(','),
|
||||||
|
googleCode: ''
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// 回退:使用行内已有数据
|
// 回退:使用行内已有数据
|
||||||
@@ -715,7 +735,8 @@ export default {
|
|||||||
chainValue: row.chain || '',
|
chainValue: row.chain || '',
|
||||||
payAddress: row.payAddress || '',
|
payAddress: row.payAddress || '',
|
||||||
payCoins,
|
payCoins,
|
||||||
payCoin: payCoins.join(',')
|
payCoin: payCoins.join(','),
|
||||||
|
googleCode: ''
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
this.visibleConfigEdit = true
|
this.visibleConfigEdit = true
|
||||||
@@ -727,19 +748,42 @@ export default {
|
|||||||
this.deleteShopConfig({id:row.id})
|
this.deleteShopConfig({id:row.id})
|
||||||
},
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 处理谷歌验证码输入(仅允许数字)
|
||||||
|
*/
|
||||||
|
handleConfigGoogleCodeInput(v) {
|
||||||
|
this.configForm.googleCode = String(v || '').replace(/\D/g, '')
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
* 修改配置弹窗关闭时清空验证码
|
||||||
|
*/
|
||||||
|
handleConfigEditClose() {
|
||||||
|
this.configForm.googleCode = ''
|
||||||
|
},
|
||||||
/**
|
/**
|
||||||
* 提交配置修改
|
* 提交配置修改
|
||||||
*/
|
*/
|
||||||
async submitConfigEdit() {
|
async submitConfigEdit() {
|
||||||
// 仅校验钱包地址
|
// 校验钱包地址
|
||||||
const addr = (this.configForm.payAddress || '').trim()
|
const addr = (this.configForm.payAddress || '').trim()
|
||||||
if (!addr) {
|
if (!addr) {
|
||||||
this.$message.warning('请输入钱包地址')
|
this.$message.warning('请输入钱包地址')
|
||||||
return
|
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}
|
* @type {string}
|
||||||
*/
|
*/
|
||||||
let encryptedPayAddress = addr
|
let encryptedPayAddress = addr
|
||||||
@@ -761,7 +805,8 @@ export default {
|
|||||||
const payload = {
|
const payload = {
|
||||||
id: this.configForm.id,
|
id: this.configForm.id,
|
||||||
chain: this.configForm.chainValue || this.configForm.chainLabel || '',
|
chain: this.configForm.chainValue || this.configForm.chainLabel || '',
|
||||||
payAddress: encryptedPayAddress
|
payAddress: encryptedPayAddress,
|
||||||
|
gcode: googleCode
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
const res = await updateShopConfigV2(payload)
|
const res = await updateShopConfigV2(payload)
|
||||||
|
|||||||
@@ -301,18 +301,38 @@
|
|||||||
v-for="(row, idx) in editDialog.form.coinAndAlgoList"
|
v-for="(row, idx) in editDialog.form.coinAndAlgoList"
|
||||||
:key="'edit-ca-' + idx"
|
:key="'edit-ca-' + idx"
|
||||||
>
|
>
|
||||||
<el-input
|
<el-select
|
||||||
v-model="row.coin"
|
v-model="row.coin"
|
||||||
placeholder="币种"
|
placeholder="请选择币种"
|
||||||
class="coin-input"
|
class="coin-input"
|
||||||
@input="editHandleCoinInput(idx)"
|
@change="editHandleCoinChange(idx, $event)"
|
||||||
/>
|
:loading="loadingCoins"
|
||||||
<el-input
|
filterable
|
||||||
|
clearable
|
||||||
|
>
|
||||||
|
<el-option
|
||||||
|
v-for="coin in coinOptions"
|
||||||
|
:key="coin"
|
||||||
|
:label="coin"
|
||||||
|
:value="coin"
|
||||||
|
/>
|
||||||
|
</el-select>
|
||||||
|
<el-select
|
||||||
v-model="row.algorithm"
|
v-model="row.algorithm"
|
||||||
placeholder="算法"
|
placeholder="请选择算法"
|
||||||
class="algo-input"
|
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
|
<el-input
|
||||||
v-model="row.theoryPower"
|
v-model="row.theoryPower"
|
||||||
placeholder="理论算力"
|
placeholder="理论算力"
|
||||||
@@ -417,7 +437,7 @@
|
|||||||
* - 支持查看详情、修改、删除
|
* - 支持查看详情、修改、删除
|
||||||
*/
|
*/
|
||||||
import { updateProduct,deleteMachine, deleteProduct, getMachineInfo,getShopMachineListForSeller,getPayTypes, updateGpuMachine,updateAsicMachine} from '../../api/products'
|
import { updateProduct,deleteMachine, deleteProduct, getMachineInfo,getShopMachineListForSeller,getPayTypes, updateGpuMachine,updateAsicMachine} from '../../api/products'
|
||||||
|
import { getSupportCoin,getSupportAlgo } from '../../api/machine'
|
||||||
// 本页用户偏好存储键:记住矿机种类(ASIC/GPU)
|
// 本页用户偏好存储键:记住矿机种类(ASIC/GPU)
|
||||||
const MACHINE_TYPE_KEY = 'account_products_machine_type'
|
const MACHINE_TYPE_KEY = 'account_products_machine_type'
|
||||||
|
|
||||||
@@ -438,6 +458,12 @@ export default {
|
|||||||
total: 0,
|
total: 0,
|
||||||
},
|
},
|
||||||
coinOptions: [],
|
coinOptions: [],
|
||||||
|
/** 算法选项映射 { coin: [algo1, algo2, ...] } */
|
||||||
|
algoOptionsMap: {},
|
||||||
|
/** 加载币种状态 */
|
||||||
|
loadingCoins: false,
|
||||||
|
/** 加载算法状态映射 { index: boolean } */
|
||||||
|
loadingAlgos: {},
|
||||||
editDialog: {
|
editDialog: {
|
||||||
visible: false,
|
visible: false,
|
||||||
saving: false,
|
saving: false,
|
||||||
@@ -537,19 +563,96 @@ export default {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
/** 编辑弹窗:币种输入过滤(仅字母数字,转大写) */
|
/**
|
||||||
editHandleCoinInput(index) {
|
* 加载支持的币种列表
|
||||||
const r = this.editDialog.form.coinAndAlgoList[index]
|
*/
|
||||||
let v = String(r.coin || '')
|
async loadSupportCoins() {
|
||||||
v = v.replace(/[\u4e00-\u9fa5]/g, '').replace(/[^A-Za-z0-9]/g, '')
|
this.loadingCoins = true
|
||||||
this.$set(this.editDialog.form.coinAndAlgoList[index], 'coin', v.toUpperCase())
|
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]
|
* @param {number} index - 行索引
|
||||||
let v = String(r.algorithm || '')
|
* @param {string} coin - 选择的币种
|
||||||
v = v.replace(/[\u4e00-\u9fa5]/g, '').replace(/[^A-Za-z0-9-]/g, '')
|
*/
|
||||||
this.$set(this.editDialog.form.coinAndAlgoList[index], 'algorithm', v.toUpperCase())
|
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小数) */
|
/** 编辑弹窗:理论算力限制(6整数+4小数) */
|
||||||
editHandleRowTheoryInput(index) {
|
editHandleRowTheoryInput(index) {
|
||||||
@@ -580,8 +683,11 @@ export default {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
const last = list[list.length - 1] || { unit: 'TH/S' }
|
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 })
|
list.push({ coin: '', algorithm: '', theoryPower: '', unit: last.unit || 'TH/S', coinAndPowerId: null })
|
||||||
this.$set(this.editDialog.form, 'coinAndAlgoList', list)
|
this.$set(this.editDialog.form, 'coinAndAlgoList', list)
|
||||||
|
// 初始化新行的加载状态
|
||||||
|
this.$set(this.loadingAlgos, newIndex, false)
|
||||||
},
|
},
|
||||||
/** 编辑弹窗:删除一行(至少保留1行) */
|
/** 编辑弹窗:删除一行(至少保留1行) */
|
||||||
editHandleRemoveRow(index) {
|
editHandleRemoveRow(index) {
|
||||||
@@ -1243,6 +1349,16 @@ export default {
|
|||||||
})
|
})
|
||||||
this.editDialog.form = form
|
this.editDialog.form = form
|
||||||
this.editDialog.visible = true
|
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 }}
|
{{ getStatusText }}
|
||||||
</span>
|
</span>
|
||||||
<el-button
|
<el-button
|
||||||
:type="getButtonType"
|
type="text"
|
||||||
@click="handleButtonClick"
|
@click="handleButtonClick"
|
||||||
:loading="loading"
|
:loading="loading"
|
||||||
class="security-btn"
|
class="security-btn two-factor-btn"
|
||||||
>
|
>
|
||||||
{{ getButtonText }}
|
{{ getButtonText }}
|
||||||
</el-button>
|
</el-button>
|
||||||
@@ -28,6 +28,56 @@
|
|||||||
<div class="security-divider"></div>
|
<div class="security-divider"></div>
|
||||||
</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
|
<el-dialog
|
||||||
title="开启双重验证 - 步骤 1/2"
|
title="开启双重验证 - 步骤 1/2"
|
||||||
@@ -263,13 +313,160 @@
|
|||||||
</el-button>
|
</el-button>
|
||||||
</span>
|
</span>
|
||||||
</el-dialog>
|
</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>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import { getBindInfo, bindGoogle, sendOpenGoogleCode,getGoogleStatus,closeStepTwo,sendCloseGoogleCode,openStepTwo } from '../../api/verification'
|
import { getBindInfo, bindGoogle, sendOpenGoogleCode,getGoogleStatus,closeStepTwo,sendCloseGoogleCode,openStepTwo } from '../../api/verification'
|
||||||
import { rsaEncrypt } from '../../utils/rsaEncrypt'
|
import { rsaEncrypt } from '../../utils/rsaEncrypt'
|
||||||
|
import { closeAccount, sendCloseAccount,sendUpdatePwdCode,updatePasswordInCenter} from '../../api/user'
|
||||||
export default {
|
export default {
|
||||||
name: 'SecuritySettings',
|
name: 'SecuritySettings',
|
||||||
data() {
|
data() {
|
||||||
@@ -294,6 +491,19 @@ export default {
|
|||||||
callback()
|
callback()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 确认密码验证
|
||||||
|
*/
|
||||||
|
const validateConfirmPassword = (rule, value, callback) => {
|
||||||
|
if (!value) {
|
||||||
|
callback(new Error('请再次输入新密码'))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
// 注意:这里需要在 data() 返回后,通过 this.changePasswordForm.password 访问
|
||||||
|
// 但由于这是在 data() 函数内部,无法访问 this,所以需要在 methods 中定义
|
||||||
|
callback()
|
||||||
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
isEnabled: false, // 是否已开启双重验证
|
isEnabled: false, // 是否已开启双重验证
|
||||||
loading: false,
|
loading: false,
|
||||||
@@ -302,6 +512,9 @@ export default {
|
|||||||
step2Visible: false,
|
step2Visible: false,
|
||||||
closeDialogVisible: false, // 关闭双重验证弹窗
|
closeDialogVisible: false, // 关闭双重验证弹窗
|
||||||
openDialogVisible: false, // 开启双重验证弹窗
|
openDialogVisible: false, // 开启双重验证弹窗
|
||||||
|
deleteAccountDialogVisible: false, // 注销账号弹窗
|
||||||
|
changePasswordDialogVisible: false, // 修改密码弹窗
|
||||||
|
userEmail: '', // 用户邮箱
|
||||||
qrCodeUrl: '',
|
qrCodeUrl: '',
|
||||||
secretKey: '',
|
secretKey: '',
|
||||||
sendingCode: false,
|
sendingCode: false,
|
||||||
@@ -313,6 +526,14 @@ export default {
|
|||||||
sendingOpenCode: false, // 发送开启验证码的 loading
|
sendingOpenCode: false, // 发送开启验证码的 loading
|
||||||
openCountdown: 0, // 开启验证码倒计时
|
openCountdown: 0, // 开启验证码倒计时
|
||||||
openCountdownTimer: null, // 开启验证码倒计时定时器
|
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
|
closing: false, // 关闭双重验证的 loading
|
||||||
opening: false, // 开启双重验证的 loading
|
opening: false, // 开启双重验证的 loading
|
||||||
submitting: false,
|
submitting: false,
|
||||||
@@ -329,6 +550,16 @@ export default {
|
|||||||
emailCode: '',
|
emailCode: '',
|
||||||
googleCode: ''
|
googleCode: ''
|
||||||
},
|
},
|
||||||
|
deleteAccountForm: {
|
||||||
|
emailCode: '',
|
||||||
|
googleCode: ''
|
||||||
|
},
|
||||||
|
changePasswordForm: {
|
||||||
|
emailCode: '',
|
||||||
|
password: '',
|
||||||
|
confirmPassword: '',
|
||||||
|
googleCode: ''
|
||||||
|
},
|
||||||
verifyRules: {
|
verifyRules: {
|
||||||
password: [
|
password: [
|
||||||
{ required: true, validator: validatePassword, trigger: 'blur' }
|
{ required: true, validator: validatePassword, trigger: 'blur' }
|
||||||
@@ -362,6 +593,17 @@ export default {
|
|||||||
{ pattern: /^\d{6}$/, message: '请输入6位数字', trigger: 'blur' }
|
{ 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 关闭
|
googleStatus: 1 // 谷歌验证状态:0 开启;1 未绑定;2 关闭
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -439,6 +681,7 @@ export default {
|
|||||||
},
|
},
|
||||||
mounted() {
|
mounted() {
|
||||||
this.check2FAStatus()
|
this.check2FAStatus()
|
||||||
|
this.loadUserEmail()
|
||||||
},
|
},
|
||||||
beforeDestroy() {
|
beforeDestroy() {
|
||||||
if (this.countdownTimer) {
|
if (this.countdownTimer) {
|
||||||
@@ -450,6 +693,9 @@ export default {
|
|||||||
if (this.openCountdownTimer) {
|
if (this.openCountdownTimer) {
|
||||||
clearInterval(this.openCountdownTimer)
|
clearInterval(this.openCountdownTimer)
|
||||||
}
|
}
|
||||||
|
if (this.deleteAccountCountdownTimer) {
|
||||||
|
clearInterval(this.deleteAccountCountdownTimer)
|
||||||
|
}
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
/**
|
/**
|
||||||
@@ -878,6 +1124,260 @@ export default {
|
|||||||
*/
|
*/
|
||||||
handleCannotGetGoogleCode() {
|
handleCannotGetGoogleCode() {
|
||||||
this.$message.info('请确保已正确扫描二维码或输入密钥,并检查时间同步')
|
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);
|
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 {
|
.security-divider {
|
||||||
height: 1px;
|
height: 1px;
|
||||||
background: #e4e7ed;
|
background: #e4e7ed;
|
||||||
@@ -1161,5 +1703,9 @@ export default {
|
|||||||
justify-content: flex-end;
|
justify-content: flex-end;
|
||||||
gap: 12px;
|
gap: 12px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.email-display {
|
||||||
|
background-color: #f5f7fa;
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
|
|||||||
@@ -1011,16 +1011,29 @@ export default {
|
|||||||
callback(new Error('请输入有效的金额'))
|
callback(new Error('请输入有效的金额'))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// 手续费与总需求按相同精度计算
|
// 手续费与总需求按相同精度计算
|
||||||
const feeInt = this.toScaledInt(this.withdrawForm.fee)
|
const feeInt = this.toScaledInt(this.withdrawForm.fee)
|
||||||
const totalRequired = amountInt + feeInt
|
const totalRequired = amountInt + feeInt
|
||||||
|
|
||||||
// 钱包余额转分(使用当前选中钱包的可用余额)
|
// 钱包总余额(可用余额 + 冻结余额)
|
||||||
const availableBalance = this.WalletData && (this.WalletData.walletBalance || this.WalletData.balance) || 0
|
const availableBalance = this.WalletData && (this.WalletData.walletBalance || this.WalletData.balance) || 0
|
||||||
const balanceInt = this.toScaledInt(availableBalance)
|
const blockedBalance = this.WalletData && (this.WalletData.blockedBalance || 0) || 0
|
||||||
if (totalRequired > balanceInt) {
|
const totalBalance = parseFloat(availableBalance) + parseFloat(blockedBalance)
|
||||||
|
const totalBalanceInt = this.toScaledInt(totalBalance)
|
||||||
|
|
||||||
|
// 提现金额可以等于可用余额,但提现金额+手续费不能超过钱包总余额
|
||||||
|
if (totalRequired > totalBalanceInt) {
|
||||||
const totalText = this.formatDec6FromInt(totalRequired)
|
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
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1037,6 +1050,13 @@ export default {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 实际到账金额(提现金额 - 手续费)必须大于0
|
||||||
|
const actualInt = amountInt - feeInt
|
||||||
|
if (actualInt <= 0) {
|
||||||
|
callback(new Error('提现金额扣除手续费后必须大于0'))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
callback()
|
callback()
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|||||||
@@ -278,7 +278,7 @@
|
|||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('发送验证码失败:', error)
|
console.error('发送验证码失败:', error)
|
||||||
this.$message.error(error.message || '发送验证码失败,请重试')
|
|
||||||
} finally {
|
} finally {
|
||||||
this.sendingCode = false
|
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