Commit 88360d46 by wusiyi

feat: 安全设置页面

parent 1fa922b0
...@@ -82,7 +82,7 @@ const routes = [ ...@@ -82,7 +82,7 @@ const routes = [
path: '/saas/sysSecuritySettings', path: '/saas/sysSecuritySettings',
component: () => import('@/views/system/sysSecuritySettings.vue'), component: () => import('@/views/system/sysSecuritySettings.vue'),
name: 'system_security_settings', name: 'system_security_settings',
meta: { title: '安全设置' } meta: { title: '安全设置中心' }
}, },
{ {
path: '/saas/countryCode', path: '/saas/countryCode',
......
...@@ -436,7 +436,7 @@ export default { ...@@ -436,7 +436,7 @@ export default {
{ {
id: 7, id: 7,
path: '', path: '',
label: '安全设置', label: '安全设置中心',
icon: 'el-icon-lock', icon: 'el-icon-lock',
index: '/saas/sysSecuritySettings', index: '/saas/sysSecuritySettings',
children: [] children: []
......
<template> <template>
<div class="wraper"> <div class="wraper" v-loading="loading">
<div class="card-content"> <!-- list -->
<div v-if="!loading" class="card-content">
<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" class="input-value">{{ formData.phone }}</el-col> <el-col :span="12" class="input-value">{{ formData.phone }}</el-col>
<el-col :span="6" class="action"> <el-col :span="6" class="action">
<el-button type="text">解绑</el-button> <span v-if="formData.phone">
<el-button type="text">换绑</el-button> <el-button type="text" @click="disConnect('phone')">解绑</el-button>
<el-button type="text" @click="reConnect('phone', 'change')">
换绑
</el-button>
</span>
<span v-else>
<el-button type="text" style="color: orange" @click="bind('phone')">
绑定手机号
</el-button>
</span>
</el-col> </el-col>
</el-row> </el-row>
<el-divider /> <el-divider />
...@@ -16,8 +26,22 @@ ...@@ -16,8 +26,22 @@
{{ formData.mailbox }} {{ formData.mailbox }}
</el-col> </el-col>
<el-col :span="6" class="action"> <el-col :span="6" class="action">
<el-button type="text">解绑</el-button> <span v-if="formData.mailbox">
<el-button type="text">换绑</el-button> <el-button type="text" @click="disConnect('mailbox')">
解绑
</el-button>
<el-button type="text" @click="reConnect('mailbox', 'change')">
换绑
</el-button>
</span>
<span v-else>
<el-button
type="text"
style="color: orange"
@click="bind('mailbox')">
绑定邮箱
</el-button>
</span>
</el-col> </el-col>
</el-row> </el-row>
<el-divider /> <el-divider />
...@@ -25,10 +49,388 @@ ...@@ -25,10 +49,388 @@
<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"></el-col>
<el-col :span="6" class="action"> <el-col :span="6" class="action">
<el-button type="text">修改密码</el-button> <el-button type="text" @click="disConnect('superPassword')">
修改超级密码
</el-button>
</el-col> </el-col>
</el-row> </el-row>
</div> </div>
<!-- 解绑弹窗 -->
<el-dialog
:visible.sync="disconnectDialog"
:title="'解绑' + currentTargetName"
width="400px"
class="disconnectDialog"
:destroy-on-close="true"
@close="resetForm">
<el-form
:model="bindForm"
:rules="bindRules"
ref="bindForm"
label-width="80px">
<el-form-item
label="手机号"
prop="phone"
v-if="currentMethod === 'phone'">
<el-input
v-model="bindForm.phone"
placeholder="请输入手机号"
clearable
disabled
maxlength="11"
size="small" />
</el-form-item>
<el-form-item
label="邮箱"
prop="mailbox"
v-if="currentMethod === 'mailbox'">
<el-input
v-model="bindForm.mailbox"
placeholder="请输入邮箱"
clearable
disabled
size="small" />
</el-form-item>
<el-form-item
label="验证码"
prop="code"
v-if="currentMethod !== 'superPassword'">
<div style="display: flex; align-items: center; gap: 10px">
<el-input
v-model="bindForm.code"
placeholder="请输入验证码"
clearable
maxlength="6"
size="small" />
<el-button
size="small"
@click="sendCode"
:loading="sendCodeLoading">
{{ sendCodeLoading ? `${countdown}s后重发` : '发送验证码' }}
</el-button>
</div>
</el-form-item>
<el-form-item
label="超级密码"
prop="superPassword"
v-if="currentMethod === 'superPassword'">
<el-input
v-model="bindForm.superPassword"
placeholder="请输入超级密码"
clearable
show-password
size="small" />
</el-form-item>
</el-form>
<div v-if="dialogType !== 'change'" slot="footer" class="dialog-footer">
<el-button @click="disconnectDialog = false" size="small">
取消
</el-button>
<el-button type="primary" @click="submitUnbind" size="small">
确定
</el-button>
</div>
</el-dialog>
<!-- 换绑弹窗 -->
<el-dialog
:visible.sync="reconnectDialog"
:title="'换绑' + currentTargetName"
width="400px"
class="disconnectDialog"
:destroy-on-close="true"
@close="resetForm">
<el-form
:model="bindForm"
:rules="bindRules"
ref="bindForm"
label-width="80px">
<el-form-item
label="手机号"
prop="phone"
v-if="currentMethod === 'phone'">
<el-input
v-model="bindForm.phone"
placeholder="请输入手机号"
clearable
disabled
maxlength="11"
size="small" />
</el-form-item>
<el-form-item
label="邮箱"
prop="mailbox"
v-if="currentMethod === 'mailbox'">
<el-input
v-model="bindForm.mailbox"
placeholder="请输入邮箱"
clearable
disabled
size="small" />
</el-form-item>
<el-form-item
label="验证码"
prop="code"
v-if="currentMethod !== 'superPassword'">
<div style="display: flex; align-items: center; gap: 10px">
<el-input
v-model="bindForm.code"
placeholder="请输入验证码"
clearable
maxlength="6"
size="small" />
<el-button
size="small"
@click="sendCode"
:loading="sendCodeLoading">
{{ sendCodeLoading ? `${countdown}s后重发` : '发送验证码' }}
</el-button>
</div>
</el-form-item>
<el-form-item
label="超级密码"
prop="superPassword"
v-if="currentMethod === 'superPassword'">
<el-input
v-model="bindForm.superPassword"
placeholder="请输入超级密码"
clearable
show-password
size="small" />
</el-form-item>
</el-form>
<div>
<el-divider>其他验证方式</el-divider>
<div
style="
display: flex;
flex-direction: column;
align-items: center;
gap: 10px;
width: 80%;
margin: 0 auto;
">
<el-button
icon="el-icon-mobile-phone"
type="warning"
size="small"
v-if="currentMethod !== 'phone' && formData.phone"
@click="currentMethod = 'phone'"
style="width: 100%">
手机验证
</el-button>
<el-button
icon="el-icon-message"
type="success"
size="small"
v-if="currentMethod !== 'mailbox' && formData.mailbox"
@click="currentMethod = 'mailbox'"
style="width: 100%">
邮箱验证
</el-button>
<el-button
icon="el-icon-lock"
type="info"
size="small"
v-if="currentMethod !== 'superPassword'"
@click="currentMethod = 'superPassword'"
style="width: 100%">
超级密码验证
</el-button>
</div>
</div>
<div slot="footer" class="dialog-footer">
<el-button type="primary" @click="check" size="small">下一步</el-button>
</div>
</el-dialog>
<!-- 绑定弹窗 -->
<el-dialog
:visible.sync="bindDialog"
:title="'绑定' + currentTargetName"
width="400px"
class="disconnectDialog"
:destroy-on-close="true"
@close="resetForm">
<el-form
:model="bindForm"
:rules="bindRules"
ref="bindForm"
label-width="100px">
<el-form-item label="手机号" prop="phone">
<el-input
v-model="bindForm.phone"
placeholder="请输入手机号"
clearable
maxlength="11"
size="small" />
</el-form-item>
<el-form-item label="邮箱" prop="mailbox">
<el-input
v-model="bindForm.mailbox"
placeholder="请输入邮箱"
clearable
size="small" />
</el-form-item>
<el-form-item label="验证码" prop="code">
<div style="display: flex; align-items: center; gap: 10px">
<el-input
v-model="bindForm.code"
placeholder="请输入验证码"
clearable
maxlength="6"
size="small" />
<el-button
size="small"
@click="sendCode"
:loading="sendCodeLoading">
{{ sendCodeLoading ? `${countdown}s后重发` : '发送验证码' }}
</el-button>
</div>
</el-form-item>
</el-form>
<div slot="footer" class="dialog-footer">
<el-button @click="bindDialog = false" size="small">取消</el-button>
<el-button type="primary" @click="submitBind" size="small">
确定
</el-button>
</div>
</el-dialog>
<!-- 修改超级密码弹窗 -->
<el-dialog
:visible.sync="resetPasswordDialog"
title="修改超级密码"
width="400px"
class="resetPasswordDialog"
:destroy-on-close="true"
@close="resetForm">
<el-form
:model="bindForm"
:rules="bindRules"
ref="bindForm"
label-width="110px">
<el-form-item
label="手机号"
prop="phone"
v-if="currentMethod === 'phone'">
<el-input
v-model="bindForm.phone"
placeholder="请输入手机号"
clearable
disabled
maxlength="11"
size="small" />
</el-form-item>
<el-form-item
label="邮箱"
prop="mailbox"
v-if="currentMethod === 'mailbox'">
<el-input
v-model="bindForm.mailbox"
placeholder="请输入邮箱"
clearable
disabled
size="small" />
</el-form-item>
<el-form-item
label="验证码"
prop="code"
v-if="currentMethod !== 'superPassword'">
<div style="display: flex; align-items: center; gap: 10px">
<el-input
v-model="bindForm.code"
placeholder="请输入验证码"
clearable
maxlength="6"
size="small" />
<el-button
size="small"
@click="sendCode"
:loading="sendCodeLoading">
{{ sendCodeLoading ? `${countdown}s后重发` : '发送验证码' }}
</el-button>
</div>
</el-form-item>
<el-form-item
label="原超级密码"
prop="superPassword"
v-if="currentMethod === 'superPassword'">
<el-input
v-model="bindForm.superPassword"
placeholder="请输入原超级密码"
clearable
show-password
size="small" />
</el-form-item>
<el-form-item label="新超级密码" prop="newSuperPassword1">
<el-input
v-model="bindForm.newSuperPassword1"
placeholder="请输入新超级密码"
clearable
show-password
size="small" />
</el-form-item>
<el-form-item label="确认超级密码" prop="newSuperPassword2">
<el-input
v-model="bindForm.newSuperPassword2"
placeholder="请再次输入新超级密码"
clearable
show-password
size="small" />
</el-form-item>
</el-form>
<el-divider>其他验证方式</el-divider>
<div
style="
display: flex;
flex-direction: column;
align-items: center;
gap: 10px;
width: 80%;
margin: 0 auto;
">
<el-button
icon="el-icon-mobile-phone"
type="warning"
size="small"
v-if="currentMethod !== 'phone' && bindForm.phone"
@click="currentMethod = 'phone'"
style="width: 100%">
手机验证
</el-button>
<el-button
icon="el-icon-message"
type="success"
size="small"
v-if="currentMethod !== 'mailbox' && bindForm.mailbox"
@click="currentMethod = 'mailbox'"
style="width: 100%">
邮箱验证
</el-button>
<el-button
icon="el-icon-lock"
type="info"
size="small"
v-if="currentMethod !== 'superPassword'"
@click="currentMethod = 'superPassword'"
style="width: 100%">
超级密码验证
</el-button>
</div>
<div slot="footer" class="dialog-footer">
<el-button @click="resetPasswordDialog = false" size="small">
取消
</el-button>
<el-button type="primary" @click="submitPassword" size="small">
确定
</el-button>
</div>
</el-dialog>
</div> </div>
</template> </template>
...@@ -36,22 +438,176 @@ ...@@ -36,22 +438,176 @@
import axios from '../../common/api/axios' import axios from '../../common/api/axios'
export default { export default {
name: 'sysSecuritySettings', name: 'sysSecuritySettings',
computed: {
// 获取当前解绑/换绑方式配置
currentMethodConfig() {
return this.currentMethod && this.methodMap[this.currentMethod]
? this.methodMap[this.currentMethod]
: {}
},
// 获取当前解绑/换绑对象名称
currentTargetName() {
return this.currentTarget && this.methodMap[this.currentTarget]
? this.methodMap[this.currentTarget].name
: ''
}
},
data() { data() {
return { return {
// list展示
formData: { formData: {
phone: '', phone: '',
mailbox: '', mailbox: '',
superPassword: '' superPassword: ''
}, },
isFirstLoad: true loading: true, // 页面loading
sendCodeLoading: false, // 发送验证码loading
countdown: 0, // 倒计时秒数
disconnectDialog: false, // 解绑弹窗
bindDialog: false, // 绑定弹窗
reconnectDialog: false,
resetPasswordDialog: false, // 修改超级密码弹窗
code: '', // 验证码
inputValue: '', // 输入的邮箱/手机号
currentTarget: '', // 当前解绑/换绑对象
currentMethod: '', // 当前解绑/换绑方式
dialogType: 'unbind', // 对话框类型
// 换绑方式map
methodMap: {
phone: { name: '手机号', method: '手机' },
mailbox: { name: '邮箱', method: '邮箱' }
},
bindForm: {
phone: '',
mailbox: '',
code: '',
superPassword: '',
newSuperPassword1: '',
newSuperPassword2: ''
},
bindRules: {
phone: [],
mailbox: [],
code: [
{ required: true, message: '请输入验证码', trigger: 'blur' },
{
pattern: /^[0-9]{6}$/,
message: '请输入6位数字验证码',
trigger: 'blur'
}
],
superPassword: [
{ required: true, message: '请输入超级密码', trigger: 'blur' }
],
newSuperPassword1: [
{ required: true, message: '请输入新超级密码', trigger: 'blur' }
],
newSuperPassword2: [
{ required: true, message: '请再次输入新超级密码', trigger: 'blur' },
{
validator: (rule, value, callback) => {
if (value !== this.bindForm.newSuperPassword1) {
callback(new Error('两次输入的密码不一致'))
} else {
callback()
}
},
trigger: 'blur'
}
]
},
bindRulesForBind: {
phone: [
{ required: true, message: '请输入手机号', trigger: 'blur' },
{
pattern: /^1[3-9]\d{9}$/,
message: '请输入正确的手机号',
trigger: 'blur'
}
],
mailbox: [
{ required: true, message: '请输入邮箱', trigger: 'blur' },
{ type: 'email', message: '请输入正确的邮箱', trigger: 'blur' }
],
code: [
{ required: true, message: '请输入验证码', trigger: 'blur' },
{
pattern: /^[0-9]{6}$/,
message: '请输入6位数字验证码',
trigger: 'blur'
}
],
superPassword: [
{ required: true, message: '请输入超级密码', trigger: 'blur' }
],
newSuperPassword1: [
{ required: true, message: '请输入新超级密码', trigger: 'blur' }
],
newSuperPassword2: [
{ required: true, message: '请再次输入新超级密码', trigger: 'blur' },
{
validator: (rule, value, callback) => {
if (value !== this.bindForm.newSuperPassword1) {
callback(new Error('两次输入的密码不一致'))
} else {
callback()
}
},
trigger: 'blur'
}
]
},
bindRulesForPassword: {
phone: [
{ required: true, message: '请输入手机号', trigger: 'blur' },
{
pattern: /^1[3-9]\d{9}$/,
message: '请输入正确的手机号',
trigger: 'blur'
}
],
mailbox: [
{ required: true, message: '请输入邮箱', trigger: 'blur' },
{ type: 'email', message: '请输入正确的邮箱', trigger: 'blur' }
],
code: [
{ required: true, message: '请输入验证码', trigger: 'blur' },
{
pattern: /^[0-9]{6}$/,
message: '请输入6位数字验证码',
trigger: 'blur'
}
],
superPassword: [
{ required: true, message: '请输入原超级密码', trigger: 'blur' }
],
newSuperPassword1: [
{ required: true, message: '请输入新超级密码', trigger: 'blur' }
],
newSuperPassword2: [
{ required: true, message: '请再次输入新超级密码', trigger: 'blur' },
{
validator: (rule, value, callback) => {
if (value !== this.bindForm.newSuperPassword1) {
callback(new Error('两次输入的密码不一致'))
} else {
callback()
}
},
trigger: 'blur'
}
]
}
} }
}, },
mounted() { mounted() {
this.getList() this.getList()
}, },
methods: { methods: {
// 获取数据 // 获取数据
async getList() { async getList() {
this.loading = true
axios axios
.post('sysSecuritySettings/list_page', { .post('sysSecuritySettings/list_page', {
pageSize: 100, pageSize: 100,
...@@ -63,11 +619,12 @@ export default { ...@@ -63,11 +619,12 @@ export default {
res.data.records && res.data.records &&
res.data.records.length > 0 res.data.records.length > 0
) { ) {
if (this.isFirstLoad) { this.formData = { ...res.data.records[0] }
this.formData = { ...res.data.records[0] }
this.isFirstLoad = false
}
} }
this.loading = false
})
.catch(() => {
this.loading = false
}) })
}, },
// 提交修改 // 提交修改
...@@ -83,8 +640,256 @@ export default { ...@@ -83,8 +640,256 @@ export default {
} catch (error) { } catch (error) {
console.log(error) console.log(error)
} }
},
// 解绑
disConnect(method, type = 'unbind') {
this.currentMethod = method
this.currentTarget = method
this.dialogType = type
// 重置表单
this.bindForm = {
phone: this.formData.phone,
mailbox: this.formData.mailbox,
code: '',
superPassword: '',
newSuperPassword1: '',
newSuperPassword2: ''
}
if (method === 'superPassword') {
// 切换为修改超级密码的校验规则
this.bindRules = { ...this.bindRulesForPassword }
this.resetPasswordDialog = true
} else {
// 解绑/换绑校验规则
this.bindRules = {
phone: [],
mailbox: [],
code: this.bindRulesForBind.code
}
this.disconnectDialog = true
}
this.sendCodeLoading = false
this.countdown = 0
},
// 换绑
reConnect(method, type = 'unbind') {
this.currentMethod = method
this.currentTarget = method
this.dialogType = type
// 重置表单
this.bindForm = {
phone: this.formData.phone,
mailbox: this.formData.mailbox,
code: '',
superPassword: ''
}
// 切换为换绑校验规则(不校验手机号和邮箱)
this.bindRules = {
phone: [],
mailbox: [],
code: this.bindRulesForBind.code
// 其他规则可按需补充
}
this.sendCodeLoading = false
this.countdown = 0
this.reconnectDialog = true
},
// 绑定
bind(method) {
this.bindDialog = true
this.currentMethod = method
this.currentTarget = method
// 重置表单
this.bindForm = {
phone: '',
mailbox: '',
code: ''
}
// 切换为绑定校验规则
this.bindRules = { ...this.bindRulesForBind }
this.sendCodeLoading = false
this.countdown = 0
},
// 发送验证码
async sendCode() {
// 校验格式
this.$refs.bindForm.validateField(
this.currentMethod,
async (errorMessage) => {
if (errorMessage) {
return
}
// 开始loading
this.sendCodeLoading = true
this.countdown = 60
this.$message.success('验证码发送成功')
// 开始倒计时
const timer = setInterval(() => {
this.countdown--
if (this.countdown <= 0) {
clearInterval(timer)
this.sendCodeLoading = false
}
}, 1000)
// 发送验证码请求
try {
if (this.currentMethod === 'phone') {
const url = '/sysSecuritySettings/sendPhoneCode'
const res = await axios.post(url, {
phone: this.bindForm.phone
})
if (res.code !== 200) {
this.sendCodeLoading = false
this.countdown = 0
return
}
} else {
const url = 'sysSecuritySettings/sendEmailCode?email='
const res = await axios.get(url + this.bindForm.mailbox)
if (res.code !== 200) {
this.sendCodeLoading = false
this.countdown = 0
return
}
}
} catch (error) {
this.sendCodeLoading = false
this.countdown = 0
}
}
)
},
// 绑定
async submitBind() {
this.$refs.bindForm.validate(async (valid) => {
if (valid) {
try {
const filteredForm = Object.fromEntries(
Object.entries(this.bindForm).filter(
([_, v]) => v !== '' && v !== null && v !== undefined
)
)
const url = '/sysSecuritySettings/changeBinding'
const res = await axios.post(url, {
...filteredForm,
checkType: this.currentMethod,
unbindType: this.currentTarget
})
if (res.code !== 200) {
return
}
this.getList()
this.$message.success('绑定成功')
this.bindDialog = false
} catch (error) {
console.log(error)
}
}
})
},
// 解绑
async submitUnbind() {
this.$refs.bindForm.validate(async (valid) => {
if (valid) {
try {
const url =
this.dialogType === 'change'
? '/sysSecuritySettings/changeBinding'
: '/sysSecuritySettings/unbind'
const res = await axios.post(url, {
...this.bindForm,
checkType: this.currentMethod,
unbindType: this.currentTarget
})
if (res.code !== 200) {
this.$message.error(
this.dialogType === 'change' ? '换绑失败' : '解绑失败'
)
} else {
this.$message.success(
this.dialogType === 'change' ? '换绑成功' : '解绑成功'
)
}
this.getList()
this.disconnectDialog = false
} catch (error) {
console.log(error)
}
}
})
},
resetForm() {
this.bindForm = {
phone: '',
mailbox: '',
code: '',
superPassword: '',
newSuperPassword1: '',
newSuperPassword2: ''
}
this.sendCodeLoading = false
this.countdown = 0
},
// 修改超级密码
async submitPassword() {
this.$refs.bindForm.validate(async (valid) => {
if (valid) {
try {
const filteredForm = Object.fromEntries(
Object.entries(this.bindForm).filter(
([_, v]) => v !== '' && v !== null && v !== undefined
)
)
const url = '/sysSecuritySettings/updatePassword'
const res = await axios.post(url, {
...filteredForm,
checkType: this.currentMethod
})
if (res.code !== 200) {
return
}
this.getList()
this.$message.success('密码修改成功')
this.resetPasswordDialog = false
} catch (error) {
console.log(error)
}
}
})
},
// 换绑校验验证码
async check() {
this.$refs.bindForm.validate(async (valid) => {
if (valid) {
try {
const filteredForm = Object.fromEntries(
Object.entries(this.bindForm).filter(
([_, v]) => v !== '' && v !== null && v !== undefined
)
)
const url = '/sysSecuritySettings/check'
const res = await axios.post(url, {
...filteredForm,
checkType: this.currentMethod,
unbindType: this.currentTarget
})
if (res.code !== 200) {
return
}
this.reconnectDialog = false
this.bindDialog = true
} catch (error) {
console.log(error)
}
}
})
} }
} },
watch: {}
} }
</script> </script>
...@@ -120,7 +925,7 @@ export default { ...@@ -120,7 +925,7 @@ export default {
::v-deep .el-divider { ::v-deep .el-divider {
background-color: #dcdfe6c2; background-color: #dcdfe6c2;
} }
::v-deep .el-divider--horizontal { .card-content ::v-deep .el-divider--horizontal {
margin: 5px; margin: 5px;
} }
...@@ -128,6 +933,28 @@ export default { ...@@ -128,6 +933,28 @@ export default {
flex: 1; flex: 1;
display: flex; display: flex;
flex-direction: column; flex-direction: column;
margin: 200px 0 0 0; margin: 100px 0 400px 0;
border: 1px solid rgb(235, 232, 232);
padding: 60px;
border-radius: 30px;
box-shadow: 0 8px 24px 0 rgba(0, 0, 0, 0.12),
0 1.5px 6px 0 rgba(0, 0, 0, 0.08);
}
::v-deep .el-dialog {
padding: 20px 10px;
border-radius: 5px;
}
.disconnectDialog ::v-deep .el-divider--horizontal {
margin-top: 30px;
width: 80%;
margin-left: auto;
margin-right: auto;
}
.disconnectDialog ::v-deep .el-divider__text {
color: #8b8a8a;
}
.dialog-footer {
margin-top: 10px;
} }
</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