Commit d8fb0797 by wusiyi

feat: 新增安全设置中心页面

parent 65d2a8c7
...@@ -51,13 +51,25 @@ ...@@ -51,13 +51,25 @@
<el-divider /> <el-divider />
<el-row class="input-group" align="middle" :gutter="20"> <el-row class="input-group" align="middle" :gutter="20">
<el-col :span="6" class="input-label">超级密码</el-col> <el-col :span="6" class="input-label">超级密码</el-col>
<el-col :span="12"></el-col> <el-col :span="12" class="input-value">
{{ securityData.superPassword ? '******' : '' }}
</el-col>
<el-col :span="6" class="action"> <el-col :span="6" class="action">
<span v-if="securityData.superPassword">
<el-button <el-button
type="text" type="text"
@click="openDialog('superPassword', 'password')"> @click="openDialog('superPassword', 'password')">
修改超级密码 修改超级密码
</el-button> </el-button>
</span>
<span v-else>
<el-button
type="text"
style="color: orange"
@click="handleAddSuperPassword">
设置超级密码
</el-button>
</span>
</el-col> </el-col>
</el-row> </el-row>
</div> </div>
...@@ -68,12 +80,17 @@ ...@@ -68,12 +80,17 @@
width="400px" width="400px"
class="dialog" class="dialog"
:destroy-on-close="true" :destroy-on-close="true"
:close-on-click-modal="false"
@close="resetForm"> @close="resetForm">
<el-form <el-form
:model="dialogForm" :model="dialogForm"
:rules="dialogRules" :rules="dialogRules"
ref="dialogForm" ref="dialogForm"
:label-width="dialogType === 'password' ? '110px' : '80px'"> :label-width="
dialogType === 'password' || dialogType === 'addPassword'
? '110px'
: '80px'
">
<el-form-item <el-form-item
v-if="currentMethod === 'phone'" v-if="currentMethod === 'phone'"
label="手机号" label="手机号"
...@@ -110,14 +127,18 @@ ...@@ -110,14 +127,18 @@
size="small" /> size="small" />
<el-button <el-button
size="small" size="small"
@click="sendCode" @click="sendVerificationCode"
:loading="sendCodeLoading"> :loading="countdownConfig[currentMethod]?.loading">
{{ sendCodeLoading ? `${countdown}s后重发` : '发送验证码' }} {{
countdownConfig[currentMethod]?.loading
? `${countdownConfig[currentMethod].countdown}s后重发`
: '发送验证码'
}}
</el-button> </el-button>
</div> </div>
</el-form-item> </el-form-item>
<el-form-item <el-form-item
v-if="currentMethod === 'superPassword'" v-if="dialogType === 'password' || currentMethod === 'superPassword'"
:label="dialogType === 'password' ? '原超级密码' : '超级密码'" :label="dialogType === 'password' ? '原超级密码' : '超级密码'"
prop="superPassword"> prop="superPassword">
<el-input <el-input
...@@ -130,7 +151,7 @@ ...@@ -130,7 +151,7 @@
size="small" /> size="small" />
</el-form-item> </el-form-item>
<el-form-item <el-form-item
v-if="dialogType === 'password'" v-if="dialogType === 'password' || dialogType === 'addPassword'"
label="新超级密码" label="新超级密码"
prop="newSuperPassword1"> prop="newSuperPassword1">
<el-input <el-input
...@@ -141,7 +162,7 @@ ...@@ -141,7 +162,7 @@
size="small" /> size="small" />
</el-form-item> </el-form-item>
<el-form-item <el-form-item
v-if="dialogType === 'password'" v-if="dialogType === 'password' || dialogType === 'addPassword'"
label="确认超级密码" label="确认超级密码"
prop="newSuperPassword2"> prop="newSuperPassword2">
<el-input <el-input
...@@ -152,9 +173,7 @@ ...@@ -152,9 +173,7 @@
size="small" /> size="small" />
</el-form-item> </el-form-item>
</el-form> </el-form>
<el-divider v-if="dialogType === 'change' || dialogType === 'password'"> <el-divider v-if="dialogType !== 'bind'">其他验证方式</el-divider>
其他验证方式
</el-divider>
<div v-if="dialogType !== 'bind'" class="check"> <div v-if="dialogType !== 'bind'" class="check">
<el-button <el-button
icon="el-icon-mobile-phone" icon="el-icon-mobile-phone"
...@@ -178,7 +197,11 @@ ...@@ -178,7 +197,11 @@
icon="el-icon-lock" icon="el-icon-lock"
type="info" type="info"
size="small" size="small"
v-if="currentMethod !== 'superPassword'" v-if="
currentMethod !== 'superPassword' &&
dialogType !== 'addPassword' &&
securityData.superPassword
"
@click="currentMethod = 'superPassword'" @click="currentMethod = 'superPassword'"
style="width: 100%"> style="width: 100%">
超级密码验证 超级密码验证
...@@ -201,15 +224,9 @@ export default { ...@@ -201,15 +224,9 @@ export default {
name: 'sysSecuritySettings', name: 'sysSecuritySettings',
data() { data() {
return { return {
securityData: { securityData: { phone: '', mailbox: '', superPassword: '' },
phone: '',
mailbox: '',
superPassword: ''
},
loading: true, loading: true,
dialogVisible: false, dialogVisible: false,
sendCodeLoading: false,
countdown: 0,
currentTarget: '', currentTarget: '',
currentMethod: '', currentMethod: '',
dialogType: '', dialogType: '',
...@@ -221,37 +238,47 @@ export default { ...@@ -221,37 +238,47 @@ export default {
newSuperPassword1: '', newSuperPassword1: '',
newSuperPassword2: '' newSuperPassword2: ''
}, },
countdownConfig: {
phone: { countdown: 0, loading: false, timer: null },
mailbox: { countdown: 0, loading: false, timer: null }
},
methodMap: { methodMap: {
phone: { name: '手机号', method: '手机' }, phone: { name: '手机号', method: '手机' },
mailbox: { name: '邮箱', method: '邮箱' } mailbox: { name: '邮箱', method: '邮箱' },
superPassword: { name: '超级密码', method: '超级密码' }
}, },
dialogRules: { dialogRules: {}
phone: [],
mailbox: [],
code: [],
superPassword: [],
newSuperPassword1: [],
newSuperPassword2: []
}
} }
}, },
computed: { computed: {
dialogTitle() { dialogTitle() {
if (
this.dialogType === 'change' &&
this.currentTarget === 'superPassword'
) {
return '身份验证'
}
const targetName = this.methodMap[this.currentTarget]?.name || '超级密码' const targetName = this.methodMap[this.currentTarget]?.name || '超级密码'
return `${ const actionMap = {
this.dialogType === 'bind' bind: this.securityData[this.currentTarget]
? '绑定' ? `绑定新${targetName}`
: this.dialogType === 'unbind' : `绑定${targetName}`,
? '解绑' unbind: `解绑${targetName}`,
: this.dialogType === 'change' change: `验证原${targetName}`,
? '换绑' password: '修改超级密码',
: '修改超级密码' addPassword: '新增超级密码'
}${this.dialogType !== 'password' ? targetName : ''}` }
return actionMap[this.dialogType] || '操作'
} }
}, },
mounted() { mounted() {
this.getList() this.getList()
}, },
watch: {
currentMethod() {
this.$nextTick(() => this.$refs.dialogForm?.clearValidate())
}
},
methods: { methods: {
validateNewPassword(rule, value, callback) { validateNewPassword(rule, value, callback) {
if (value !== this.dialogForm.newSuperPassword1) { if (value !== this.dialogForm.newSuperPassword1) {
...@@ -276,9 +303,23 @@ export default { ...@@ -276,9 +303,23 @@ export default {
this.loading = false this.loading = false
} }
}, },
openDialog(method, type) { handleAddSuperPassword() {
this.currentMethod = method const verificationMethod =
this.currentTarget = method this.securityData.phone || this.securityData.mailbox
if (!verificationMethod) {
this.$message.warning('请先绑定手机或邮箱,才能进行此操作')
return
}
this.openDialog(verificationMethod, 'addPassword')
},
openDialog(method, type, targetOverride = null) {
this.currentMethod =
type === 'addPassword'
? this.securityData.phone
? 'phone'
: 'mailbox'
: method
this.currentTarget = targetOverride || method
this.dialogType = type this.dialogType = type
this.dialogForm = { this.dialogForm = {
phone: type === 'bind' ? '' : this.securityData.phone || '', phone: type === 'bind' ? '' : this.securityData.phone || '',
...@@ -288,8 +329,36 @@ export default { ...@@ -288,8 +329,36 @@ export default {
newSuperPassword1: '', newSuperPassword1: '',
newSuperPassword2: '' newSuperPassword2: ''
} }
// 动态设置验证规则,仅为可见字段应用规则 this.dialogRules = this.getDialogRules(this.currentMethod, type)
this.dialogRules = { this.dialogVisible = true
this.$nextTick(() => this.$refs.dialogForm?.clearValidate())
},
getDialogRules(method, type) {
if (type === 'addPassword') {
return {
code: [
{ required: true, message: '请输入验证码', trigger: 'blur' },
{
pattern: /^[0-9]{6}$/,
message: '请输入6位数字验证码',
trigger: 'blur'
}
],
newSuperPassword1: [
{ required: true, message: '请输入新超级密码', trigger: 'blur' }
],
newSuperPassword2: [
{
required: true,
message: '请再次输入新超级密码',
trigger: 'blur'
},
{ validator: this.validateNewPassword, trigger: 'blur' }
]
}
}
const rules = {
phone: phone:
type === 'bind' && method === 'phone' type === 'bind' && method === 'phone'
? [ ? [
...@@ -306,42 +375,32 @@ export default { ...@@ -306,42 +375,32 @@ export default {
? [ ? [
{ required: true, message: '请输入邮箱', trigger: 'blur' }, { required: true, message: '请输入邮箱', trigger: 'blur' },
{ {
pattern: /^[a-zA-Z0-9_.+-]+@[a-zA-Z0-9-]+\.[a-zA-Z0-9-.]+$/, pattern:
/^[a-zA-Z0-9_.+-]+@[a-zA-Z0-9-]+\.(com|cn|net|org|edu|gov|io)$/,
message: '请输入正确的邮箱', message: '请输入正确的邮箱',
trigger: 'blur' trigger: 'blur'
} }
] ]
: [], : [],
code: code: [
method !== 'superPassword' && method !== 'password'
? [
{ required: true, message: '请输入验证码', trigger: 'blur' }, { required: true, message: '请输入验证码', trigger: 'blur' },
{ {
pattern: /^[0-9]{6}$/, pattern: /^[0-9]{6}$/,
message: '请输入6位数字验证码', message: '请输入6位数字验证码',
trigger: 'blur' trigger: 'blur'
} }
] ],
: [], superPassword: [
superPassword:
type !== 'bind'
? [
{ {
required: true, required: true,
message: `请输入${ message: `请输入${type === 'password' ? '原超级密码' : '超级密码'}`,
type === 'password' ? '原超级密码' : '超级密码'
}`,
trigger: 'blur' trigger: 'blur'
} }
] ],
: [], newSuperPassword1: [
newSuperPassword1: { required: true, message: '请输入新超级密码', trigger: 'blur' }
type === 'password' ],
? [{ required: true, message: '请输入新超级密码', trigger: 'blur' }] newSuperPassword2: [
: [],
newSuperPassword2:
type === 'password'
? [
{ {
required: true, required: true,
message: '请再次输入新超级密码', message: '请再次输入新超级密码',
...@@ -349,54 +408,51 @@ export default { ...@@ -349,54 +408,51 @@ export default {
}, },
{ validator: this.validateNewPassword, trigger: 'blur' } { validator: this.validateNewPassword, trigger: 'blur' }
] ]
: []
} }
this.sendCodeLoading = false return rules
this.countdown = 0
this.dialogVisible = true
// 延迟清除验证状态,防止初始验证
this.$nextTick(() => {
this.$refs.dialogForm.clearValidate()
})
}, },
async sendCode() { async sendVerificationCode() {
this.$refs.dialogForm.validateField(this.currentMethod, async (error) => { this.$refs.dialogForm.validateField(this.currentMethod, async (error) => {
if (error) return if (error) return
this.sendCodeLoading = true const config = this.countdownConfig[this.currentMethod]
this.countdown = 60 if (!config) return
this.$message.success('验证码发送成功') if (config.timer) clearTimeout(config.timer)
config.loading = true
const countdown = () => { config.countdown = 60
if (this.countdown > 0) { const countdownFn = () => {
this.countdown-- if (config.countdown > 0) {
setTimeout(countdown, 1000) config.countdown--
config.timer = setTimeout(countdownFn, 1000)
} else { } else {
this.sendCodeLoading = false config.loading = false
config.timer = null
} }
} }
countdown() countdownFn()
try { try {
const url = const url =
this.currentMethod === 'phone' this.currentMethod === 'phone'
? '/sysSecuritySettings/sendPhoneCode' ? '/sysSecuritySettings/sendPhoneCode'
: '/sysSecuritySettings/sendEmailCode?email=' + : `/sysSecuritySettings/sendEmailCode?email=${this.dialogForm.mailbox}&operateType=${this.dialogTitle}`
this.dialogForm.mailbox
const res = await axios({ const res = await axios({
method: this.currentMethod === 'phone' ? 'post' : 'get', method: this.currentMethod === 'phone' ? 'post' : 'get',
url, url,
data: data:
this.currentMethod === 'phone' this.currentMethod === 'phone'
? { phone: this.dialogForm.phone } ? {
phone: this.dialogForm.phone,
operateType: this.dialogTitle
}
: null : null
}) })
if (res.code !== 200) { if (res.code !== 200) throw new Error('发送失败')
this.sendCodeLoading = false this.$message.success('验证码发送成功')
this.countdown = 0
}
} catch (error) { } catch (error) {
this.sendCodeLoading = false config.loading = false
this.countdown = 0 config.countdown = 0
if (config.timer) clearTimeout(config.timer)
config.timer = null
this.$message.error('验证码发送失败')
} }
}) })
}, },
...@@ -404,65 +460,53 @@ export default { ...@@ -404,65 +460,53 @@ export default {
this.$refs.dialogForm.validate(async (valid) => { this.$refs.dialogForm.validate(async (valid) => {
if (!valid) return if (!valid) return
try { try {
const actionConfig = {
bind: {
url: '/sysSecuritySettings/changeBinding',
message: '绑定成功'
},
unbind: { url: '/sysSecuritySettings/unbind', message: '解绑成功' },
change: { url: '/sysSecuritySettings/check', message: '验证成功' },
password: {
url: '/sysSecuritySettings/updatePassword',
message: '密码修改成功'
},
addPassword: {
url: '/sysSecuritySettings/updatePassword',
message: '密码修改成功'
}
}
const { url, message } = actionConfig[this.dialogType]
const filteredForm = Object.fromEntries( const filteredForm = Object.fromEntries(
Object.entries(this.dialogForm).filter( Object.entries(this.dialogForm).filter(([, v]) => v)
([_, v]) => v !== '' && v !== null && v !== undefined
)
) )
let url, successMessage
switch (this.dialogType) {
case 'bind':
url = '/sysSecuritySettings/changeBinding'
successMessage = '绑定成功'
break
case 'unbind':
url = '/sysSecuritySettings/unbind'
successMessage = '解绑成功'
break
case 'change':
url = '/sysSecuritySettings/check'
successMessage = '验证成功'
break
case 'password':
url = '/sysSecuritySettings/updatePassword'
successMessage = '密码修改成功'
break
}
const res = await axios.post(url, { const res = await axios.post(url, {
...filteredForm, ...filteredForm,
checkType: this.currentMethod, checkType: this.currentMethod,
unbindType: this.currentTarget unbindType: this.currentTarget
}) })
if (res.code !== 200) { if (res.code !== 200) throw new Error('操作失败')
this.$message.error( this.$message.success(message)
successMessage if (this.dialogType !== 'change') this.getList()
? `${successMessage.replace('成功', '失败')}`
: '操作失败'
)
return
}
if (successMessage) {
this.$message.success(successMessage)
if (this.dialogType !== 'change') {
this.getList()
}
}
this.dialogVisible = false this.dialogVisible = false
if (this.dialogType === 'change') { if (this.dialogType === 'change') {
this.openDialog(this.currentTarget, 'bind') // 不再跳转下一步
} }
} catch (error) { } catch (error) {
console.error(error) this.$message.error('操作失败')
} }
}) })
}, },
resetForm() { resetForm() {
this.$refs.dialogForm.resetFields() this.$refs.dialogForm?.resetFields()
Object.keys(this.dialogForm).forEach((key) => { this.dialogForm = {
this.dialogForm[key] = '' phone: '',
}) mailbox: '',
this.sendCodeLoading = false code: '',
this.countdown = 0 superPassword: '',
newSuperPassword1: '',
newSuperPassword2: ''
}
} }
} }
} }
...@@ -479,7 +523,6 @@ export default { ...@@ -479,7 +523,6 @@ export default {
} }
.input-group { .input-group {
margin: 10px 0; margin: 10px 0;
width: 400px;
display: flex; display: flex;
align-items: center; align-items: center;
} }
...@@ -497,6 +540,7 @@ export default { ...@@ -497,6 +540,7 @@ export default {
text-align: right; text-align: right;
} }
.card-content { .card-content {
width: 600px;
flex: 1; flex: 1;
display: flex; display: flex;
flex-direction: column; flex-direction: column;
...@@ -507,29 +551,28 @@ export default { ...@@ -507,29 +551,28 @@ export default {
box-shadow: 0 8px 24px 0 rgba(0, 0, 0, 0.12), box-shadow: 0 8px 24px 0 rgba(0, 0, 0, 0.12),
0 1.5px 6px 0 rgba(0, 0, 0, 0.08); 0 1.5px 6px 0 rgba(0, 0, 0, 0.08);
} }
::v-deep .el-divider { .el-divider {
background-color: #dcdfe6c2; background-color: #dcdfe6c2;
} }
.card-content ::v-deep .el-divider--horizontal { .card-content .el-divider--horizontal {
margin: 5px; margin: 5px;
} }
::v-deep .el-dialog { .el-dialog {
padding: 20px 15px 20px 10px; padding: 20px 15px 20px 10px;
border-radius: 5px; border-radius: 5px;
} }
.dialog ::v-deep .el-divider--horizontal { .dialog .el-divider--horizontal {
margin-top: 30px; margin-top: 30px;
width: 80%; width: 80%;
margin-left: auto; margin-left: auto;
margin-right: auto; margin-right: auto;
} }
.dialog ::v-deep .el-divider__text { .dialog .el-divider__text {
color: #8b8a8a; color: #8b8a8a;
} }
.dialog-footer { .dialog-footer {
margin-top: 10px; margin-top: 10px;
} }
.check { .check {
display: flex; display: flex;
flex-direction: column; flex-direction: column;
...@@ -538,8 +581,7 @@ export default { ...@@ -538,8 +581,7 @@ export default {
width: 80%; width: 80%;
margin: 0 auto; margin: 0 auto;
} }
.check ::v-deep .el-button + .el-button, .check .el-button + .el-button {
.el-button + .el-dropdown { margin-left: 0;
margin-left: 0px;
} }
</style> </style>
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment