refactor: 优化安全设置

This commit is contained in:
2024-04-27 13:25:10 +08:00
parent 0ef78ba95a
commit 136f07c8e4
22 changed files with 303 additions and 388 deletions

View File

@@ -7,7 +7,6 @@ export interface UserInfo {
email: string email: string
phone: string phone: string
avatar: string avatar: string
pwdResetTime: string
registrationDate: string registrationDate: string
deptName: string deptName: string
roles: string[] roles: string[]

View File

@@ -0,0 +1,5 @@
<svg width="48" height="48" viewBox="0 0 48 48" fill="none" xmlns="http://www.w3.org/2000/svg">
<rect x="4" y="8" width="40" height="32" fill="#4785FF"/>
<path d="M4 8.00098H44V14.001C32.63 24.2339 15.37 24.2339 4 14.001V8.00098Z" fill="#94C2FF"/>
<circle cx="24" cy="23" r="4" fill="white"/>
</svg>

After

Width:  |  Height:  |  Size: 300 B

View File

@@ -1,6 +0,0 @@
<svg width="48" height="48" viewBox="0 0 48 48" fill="none" xmlns="http://www.w3.org/2000/svg">
<rect width="48" height="48" rx="24" fill="#F6F7FB"/>
<rect x="12.334" y="14.667" width="23.3333" height="18.6667" fill="#86909C"/>
<path d="M12.334 14.667H35.6673V18.167C29.0348 24.1362 18.9665 24.1362 12.334 18.167V14.667Z" fill="#C3C7CE"/>
<circle cx="23.9993" cy="23.4163" r="2.33333" fill="white"/>
</svg>

Before

Width:  |  Height:  |  Size: 407 B

View File

@@ -1,6 +0,0 @@
<svg width="48" height="48" viewBox="0 0 48 48" fill="none" xmlns="http://www.w3.org/2000/svg">
<rect width="48" height="48" rx="24" fill="#F6F7FB"/>
<rect x="12.334" y="14.667" width="23.3333" height="18.6667" fill="#4785FF"/>
<path d="M12.334 14.667H35.6673V18.167C29.0348 24.1362 18.9665 24.1362 12.334 18.167V14.667Z" fill="#94C2FF"/>
<circle cx="23.9993" cy="23.4163" r="2.33333" fill="white"/>
</svg>

Before

Width:  |  Height:  |  Size: 407 B

View File

Before

Width:  |  Height:  |  Size: 332 B

After

Width:  |  Height:  |  Size: 332 B

View File

@@ -0,0 +1,6 @@
<svg width="48" height="48" viewBox="0 0 48 48" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M9 3H39V45H9V3Z" fill="#4785FF"/>
<path d="M9 31H39V45H9V31Z" fill="#94C2FF"/>
<circle cx="24" cy="38" r="4" fill="white"/>
<path d="M18 7H30V10H18V7Z" fill="white"/>
</svg>

After

Width:  |  Height:  |  Size: 279 B

View File

@@ -1 +0,0 @@
<svg viewBox="0 0 48 48" fill="none" stroke="currentColor" stroke-width="3" stroke-linecap="butt" stroke-linejoin="miter"><path d="M6.707 34.284a1 1 0 010-1.414l5.657-5.657a1 1 0 011.414 0l4.95 4.95s3.535-1.414 7.778-5.657c4.243-4.243 5.657-7.778 5.657-7.778l-4.95-4.95a1 1 0 010-1.414l5.657-5.657a1 1 0 011.414 0l6.01 6.01s3.183 7.425-8.485 19.092c-11.667 11.668-19.092 8.485-19.092 8.485l-6.01-6.01z" /></svg>

Before

Width:  |  Height:  |  Size: 411 B

View File

Before

Width:  |  Height:  |  Size: 491 B

After

Width:  |  Height:  |  Size: 491 B

View File

@@ -40,8 +40,8 @@ export const constantRoutes: RouteRecordRaw[] = [
children: [ children: [
{ {
path: '/home', path: '/home',
component: () => import('@/views/home/index.vue'),
name: 'Home', name: 'Home',
component: () => import('@/views/home/index.vue'),
meta: { title: '首页', icon: 'dashboard', affix: true, hidden: false } meta: { title: '首页', icon: 'dashboard', affix: true, hidden: false }
} }
] ]
@@ -56,29 +56,19 @@ export const constantRoutes: RouteRecordRaw[] = [
path: '/setting', path: '/setting',
name: 'Setting', name: 'Setting',
component: Layout, component: Layout,
redirect: '/setting/profile',
meta: { hidden: true }, meta: { hidden: true },
children: [ children: [
{ {
path: '/setting', path: '/setting/profile',
name: 'Setting', name: 'SettingProfile',
component: () => import('@/views/setting/index.vue'), component: () => import('@/views/setting/profile/index.vue'),
redirect: '', meta: { title: '账号管理', showInTabs: false }
meta: { hidden: true }, },
children: [ {
{ path: '/setting/security',
path: '/setting/profile', name: 'SettingSecurity',
component: () => import('@/views/setting/profile/index.vue'), component: () => import('@/views/setting/security/index.vue'),
name: 'Profile', meta: { title: '安全设置', showInTabs: false }
meta: { title: '账号管理', hidden: false, showInTabs: false }
},
{
path: '/setting/security',
component: () => import('@/views/setting/security/index.vue'),
name: 'Security',
meta: { title: '安全设置', hidden: false, showInTabs: false }
}
]
} }
] ]
} }

View File

@@ -18,13 +18,18 @@ import { resetHasRouteFlag } from '@/router/permission'
import getAvatar from '@/utils/avatar' import getAvatar from '@/utils/avatar'
const storeSetup = () => { const storeSetup = () => {
const userInfo = reactive<Pick<UserInfo, 'id' | 'nickname' | 'avatar' | 'email' | 'phone' | 'registrationDate'>>({ const userInfo = reactive<UserInfo>({
id: '', id: '',
username: '',
nickname: '', nickname: '',
avatar: '', gender: 0,
email: '', email: '',
phone: '', phone: '',
registrationDate: '' avatar: '',
registrationDate: '',
deptName: '',
roles: [],
permissions: []
}) })
const name = computed(() => userInfo.nickname) const name = computed(() => userInfo.nickname)
const avatar = computed(() => userInfo.avatar) const avatar = computed(() => userInfo.avatar)
@@ -90,12 +95,8 @@ const storeSetup = () => {
// 获取用户信息 // 获取用户信息
const getInfo = async () => { const getInfo = async () => {
const res = await getUserInfoApi() const res = await getUserInfoApi()
userInfo.id = res.data.id Object.assign(userInfo, res.data)
userInfo.nickname = res.data.nickname
userInfo.avatar = getAvatar(res.data.avatar, res.data.gender) userInfo.avatar = getAvatar(res.data.avatar, res.data.gender)
userInfo.email = res.data.email
userInfo.phone = res.data.phone
userInfo.registrationDate = res.data.registrationDate
if (res.data.roles && res.data.roles.length) { if (res.data.roles && res.data.roles.length) {
roles.value = res.data.roles roles.value = res.data.roles
permissions.value = res.data.permissions permissions.value = res.data.permissions

View File

@@ -269,6 +269,81 @@
border: none; border: none;
background: linear-gradient(180deg, rgba(232, 244, 255, 0.5), hsla(0, 0%, 100%, 0)); background: linear-gradient(180deg, rgba(232, 244, 255, 0.5), hsla(0, 0%, 100%, 0));
} }
.item {
align-items: center;
display: flex;
margin-bottom: 20px;
.icon-wrapper {
align-items: center;
background: var(--color-neutral-2);
border-radius: 50%;
display: flex;
height: 48px;
justify-content: center;
width: 48px;
}
.info {
flex: 1 1;
margin: 0 16px;
&-top {
margin-bottom: 4px;
.label {
font-weight: 500;
line-height: 22px;
margin-right: 12px;
}
.bind {
font-size: 12px;
font-weight: 500;
line-height: 20px;
}
}
&-desc {
color: #86909c;
font-size: 12px;
font-weight: 400;
line-height: 20px;
.value {
color: #4e5969;
}
}
}
.btn-wrapper {
align-self: flex-start;
.btn {
height: 28px;
width: 56px;
}
}
}
.detail {
display: flex;
font-size: 12px;
justify-content: flex-start;
margin: -5px 0 0 64px;
.sub-text-wrapper {
width: 100%;
border-left: 2px solid var(--color-fill-4);
padding-left: 12px;
.sub-text {
color: #4e5969;
font-weight: 400;
line-height: 20px;
margin-bottom: 8px;
&-value {
background: var(--color-neutral-2);
padding: 1px 5px;
margin: 0 5px;
border-radius: 3px;
}
}
.arco-link.link {
font-size: 12px;
padding: 0;
}
}
}
} }
// 通用描述 // 通用描述

View File

@@ -7,7 +7,7 @@
</div> </div>
</template> </template>
<script setup lang="ts"></script> <script lang="ts" setup></script>
<style lang="scss" scoped> <style lang="scss" scoped>
.login-bg { .login-bg {

View File

@@ -1,45 +0,0 @@
<template>
<div class="setting" :class="{ 'setting--h5': !isDesktop }">
<div class="setting__main">
<div class="setting__main__content">
<ParentView></ParentView>
</div>
</div>
</div>
</template>
<script setup lang="ts">
import { useDevice } from '@/hooks'
defineOptions({ name: 'Setting' })
const { isDesktop } = useDevice()
</script>
<style lang="scss" scoped>
.setting {
flex: 1;
box-sizing: border-box;
overflow: hidden;
display: flex;
flex-direction: column;
&__main {
flex: 1;
overflow: hidden;
display: flex;
&__content {
flex: 1;
height: 100%;
padding: $margin;
box-sizing: border-box;
overflow: hidden;
overflow-y: auto;
}
}
}
.setting--h5 {
flex-direction: column;
}
</style>

View File

@@ -16,8 +16,11 @@
</section> </section>
<footer> <footer>
<a-descriptions column="4" size="large"> <a-descriptions column="4" size="large">
<a-descriptions-item label="性别" :span="4"> <a-descriptions-item :span="4">
{{ userInfo.nickname }} <template #label> <icon-user /><span style="margin-left: 5px">用户名</span></template>
{{ userInfo.username }}
<icon-man v-if="userInfo.gender === 1" style="color: #19bbf1" />
<icon-woman v-else-if="userInfo.gender === 2" style="color: #fa7fa9" />
</a-descriptions-item> </a-descriptions-item>
<a-descriptions-item :span="4"> <a-descriptions-item :span="4">
<template #label> <icon-phone /><span style="margin-left: 5px">手机</span></template> <template #label> <icon-phone /><span style="margin-left: 5px">手机</span></template>
@@ -29,11 +32,11 @@
</a-descriptions-item> </a-descriptions-item>
<a-descriptions-item :span="4"> <a-descriptions-item :span="4">
<template #label> <icon-mind-mapping /><span style="margin-left: 5px">部门</span></template> <template #label> <icon-mind-mapping /><span style="margin-left: 5px">部门</span></template>
{{ userInfo.nickname }} {{ userInfo.deptName }}
</a-descriptions-item> </a-descriptions-item>
<a-descriptions-item :span="4"> <a-descriptions-item :span="4">
<template #label> <icon-user-group /><span style="margin-left: 5px">角色</span></template> <template #label> <icon-user-group /><span style="margin-left: 5px">角色</span></template>
{{ userInfo.nickname }} {{ userInfo.roles.join('') }}
</a-descriptions-item> </a-descriptions-item>
</a-descriptions> </a-descriptions>
</footer> </footer>
@@ -47,6 +50,7 @@
import { updateUserBaseInfo } from '@/apis' import { updateUserBaseInfo } from '@/apis'
import VerifyModel from '../components/VerifyModel.vue' import VerifyModel from '../components/VerifyModel.vue'
import { useUserStore } from '@/stores' import { useUserStore } from '@/stores'
const userStore = useUserStore() const userStore = useUserStore()
const userInfo = computed(() => userStore.userInfo) const userInfo = computed(() => userStore.userInfo)
const verifyModelRef = ref<InstanceType<typeof VerifyModel>>() const verifyModelRef = ref<InstanceType<typeof VerifyModel>>()

View File

@@ -1,26 +1,25 @@
<template> <template>
<a-card title="登录方式" bordered class="gradient-card"> <a-card title="登录方式" bordered class="gradient-card">
<div class="mode-list"> <div v-for="item in modeList" :key="item.title">
<div v-for="item in modeList" :key="item.title" class="mode-item"> <div class="item">
<div class="mode-item-box"> <div class="icon-wrapper"><GiSvgIcon :name="item.icon" :size="26" /></div>
<div class="mode-item-box__icon"> <div class="info">
<GiSvgIcon :name="item.icon" :size="48" /> <div class="info-top">
<span class="label">{{ item.title }}</span>
<span class="bind">
<icon-check-circle-fill v-if="item.status" :size="14" class="success" />
<icon-exclamation-circle-fill v-else :size="14" class="warning" />
<span style="font-size: 12px" :class="item.status ? 'success' : 'warning'">{{
item.status ? '已绑定' : '未绑定'
}}</span>
</span>
</div> </div>
<div class="mode-item-box__content"> <div class="info-desc">
<div class="title"> <span class="value">{{ item.value }}</span>
<div>{{ item.title }}</div> {{ item.subtitle }}
<div style="margin-left: 10px">
<icon-check-circle-fill v-if="item.status" :size="14" class="success" />
<icon-exclamation-circle-fill v-else :size="14" class="warning" />
<span style="font-size: 12px" :class="item.status ? 'success' : 'warning'">{{
item.status ? '已绑定' : '未绑定'
}}</span>
</div>
</div>
<div class="mode-item-box__subtitle">{{ item.subtitle }}</div>
</div> </div>
</div> </div>
<div> <div class="btn-wrapper">
<a-button <a-button
v-if="item.jumpMode == 'modal'" v-if="item.jumpMode == 'modal'"
class="btn" class="btn"
@@ -46,7 +45,7 @@
<script setup lang="ts"> <script setup lang="ts">
import { socialAuth, getSocialAccount, unbindSocialAccount } from '@/apis' import { socialAuth, getSocialAccount, unbindSocialAccount } from '@/apis'
import type { ModeItem } from './type' import type { ModeItem } from '../type'
import { useUserStore } from '@/stores' import { useUserStore } from '@/stores'
import VerifyModel from '../components/VerifyModel.vue' import VerifyModel from '../components/VerifyModel.vue'
@@ -62,16 +61,18 @@ const modeList = ref<ModeItem[]>([])
modeList.value = [ modeList.value = [
{ {
title: '绑定手机', title: '绑定手机',
icon: userInfo.value.phone ? 'tel' : 'tel-unbind', icon: 'phone-color',
subtitle: `${userInfo.value.phone || '绑定后'},可通过手机验证码快捷登录`, value: `${userInfo.value.phone + ' ' || '绑定后'}`,
subtitle: `可通过手机验证码快捷登录`,
type: 'phone', type: 'phone',
jumpMode: 'modal', jumpMode: 'modal',
status: !!userInfo.value.phone status: !!userInfo.value.phone
}, },
{ {
title: '绑定邮箱', title: '绑定邮箱',
icon: userInfo.value.email ? 'mail' : 'mail-unbind', icon: 'email-color',
subtitle: `${userInfo.value.email || '绑定后'},可通过邮箱验证码进行登录`, value: `${userInfo.value.email + ' ' || '绑定后'}`,
subtitle: `可通过邮箱验证码进行登录`,
type: 'email', type: 'email',
jumpMode: 'modal', jumpMode: 'modal',
status: !!userInfo.value.email status: !!userInfo.value.email
@@ -79,7 +80,7 @@ modeList.value = [
{ {
title: '绑定 Gitee', title: '绑定 Gitee',
icon: 'gitee', icon: 'gitee',
subtitle: '绑定后,可通过 Gitee 进行登录', subtitle: `${socialList.value.some((el) => el == 'gitee') ? '' : '绑定后,'}可通过 Gitee 进行登录`,
jumpMode: 'link', jumpMode: 'link',
type: 'gitee', type: 'gitee',
status: socialList.value.some((el) => el == 'gitee') status: socialList.value.some((el) => el == 'gitee')
@@ -87,7 +88,7 @@ modeList.value = [
{ {
title: '绑定 GitHub', title: '绑定 GitHub',
icon: 'github', icon: 'github',
subtitle: '绑定后,可通过 GitHub 进行登录', subtitle: `${socialList.value.some((el) => el == 'gitee') ? '' : '绑定后,'}可通过 GitHub 进行登录`,
type: 'github', type: 'github',
jumpMode: 'link', jumpMode: 'link',
status: socialList.value.some((el) => el == 'github') status: socialList.value.some((el) => el == 'github')
@@ -118,34 +119,4 @@ onMounted(() => {
}) })
</script> </script>
<style lang="scss" scoped> <style lang="scss" scoped></style>
.mode-list {
.mode-item {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 20px;
&-box {
display: flex;
align-items: center;
&__icon {
margin-right: 10px;
}
&__content {
div {
line-height: 26px;
}
.title {
display: flex;
align-items: center;
}
}
}
.btn {
height: 28px;
margin-left: 10px;
width: 56px;
}
}
}
</style>

View File

@@ -15,7 +15,7 @@
import LeftBox from './LeftBox.vue' import LeftBox from './LeftBox.vue'
import RightBox from './RightBox.vue' import RightBox from './RightBox.vue'
defineOptions({ name: 'Profile' }) defineOptions({ name: 'SettingProfile' })
</script> </script>
<style lang="scss" scoped> <style lang="scss" scoped>

View File

@@ -1,99 +1,49 @@
<template> <template>
<Card> <a-card title="账号保护" bordered class="gradient-card">
<template #header> 账号保护 </template> <div v-for="item in modeList" :key="item.title">
<template #body> <div class="item">
<div class="mode-item" v-for="item in modeList" :key="item.title"> <div class="icon-wrapper"><GiSvgIcon :name="item.icon" :size="26" /></div>
<div class="mode-item-content"> <div class="info">
<div class="icon"><GiSvgIcon :name="item.icon" :size="36" /></div> <div class="info-top">
<div> <span class="label">{{ item.title }}</span>
<div style="font-size: 14px; font-weight: 500; line-height: 28px; display: flex; align-items: center"> <span class="bind">
<span>{{ item.title }}</span> <icon-check-circle-fill v-if="item.status" :size="14" class="success" />
<div style="margin-left: 10px"> <icon-exclamation-circle-fill v-else :size="14" class="warning" />
<GiSvgIcon :name="item.status ? 'success' : 'warning'" :size="14" /><span <span style="font-size: 12px" :class="item.status ? 'success' : 'warning'">{{
style="margin-left: 5px; font-size: 12px" item.status ? '已开启' : '未开启'
>{{ item.status ? '已开启' : '未开启' }}</span }}</span>
> </span>
</div> </div>
</div> <div class="info-desc">
<div style="font-size: 12px">{{ item.subtitle }}</div> {{ item.subtitle }}
</div> </div>
</div> </div>
<div> <div class="btn-wrapper">
<a-button disabled>未开放</a-button> <a-switch disabled />
</div> </div>
</div> </div>
<div> </div>
<div class="content_title"> </a-card>
<div class="icon"><GiSvgIcon name="login-protect" :size="36" /></div>
<div>
<div style="font-size: 14px; font-weight: 500; line-height: 28px">操作保护</div>
<div style="font-size: 12px">进行敏感操作时需进行二次身份校验</div>
</div>
</div>
<div class="content_Box">
<p>可使用<span class="subTitle">手机号</span>进行二次身份验证</p>
<p>未设置密码有效期</p>
<p>敏感操作二次身份验证后<span class="subTitle">10</span>分钟内不需要再次进行验证</p>
<p class="link_btn">修改规则(未开发)</p>
</div>
</div>
</template>
</Card>
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import Card from '../components/Card.vue' import type { ModeItem } from '../type'
interface ModeItem {
title: string
icon: string
subtitle: string
status: boolean
}
const modeList = ref<ModeItem[]>([]) const modeList = ref<ModeItem[]>([])
modeList.value = [ modeList.value = [
{ title: '登录保护', icon: 'login-protect', subtitle: '开启登录保护后,账号登录需进行二次身份验证', status: false } {
title: '登录保护',
icon: 'protect',
subtitle: '开启登录保护后,账号登录需进行二次身份验证',
status: false
},
{
title: '操作保护',
icon: 'protect',
subtitle: '进行敏感操作时需进行二次身份校验',
status: false
}
] ]
</script> </script>
<style scoped lang="scss">
.mode-item { <style lang="scss" scoped></style>
display: flex;
justify-content: space-between;
margin-bottom: 20px;
.mode-item-content {
display: flex;
align-items: center;
.icon {
margin-right: 10px;
}
}
}
.content_title {
display: flex;
align-items: center;
.icon {
margin-right: 10px;
}
}
.content_Box {
border-left: 1px solid #ccc;
margin-left: 40px;
padding-left: 10px;
font-size: 12px;
margin-top: 20px;
& > p {
margin: 10px;
}
}
.link_btn {
cursor: pointer;
color: #007aff;
&:hover {
color: rgba($color: #007aff, $alpha: 0.8);
}
}
.subTitle {
background: var(--color-neutral-2);
padding: 1px 5px;
margin: 0px 5px;
border-radius: 3px;
}
</style>

View File

@@ -1,59 +1,78 @@
<template> <template>
<Card style="height: 100%"> <a-card title="基本设置" bordered class="gradient-card">
<template #header>基本设置</template> <div v-for="item in modeList" :key="item.title">
<template #body> <div class="item">
<div class="mode-item" v-for="item in modeList" :key="item.title"> <div class="icon-wrapper"><GiSvgIcon :name="item.icon" :size="26" /></div>
<div class="mode-item-content"> <div class="info">
<div class="icon"><GiSvgIcon :name="item.icon" :size="36" /></div> <div class="info-top">
<div> <span class="label">{{ item.title }}</span>
<div style="font-size: 14px; font-weight: 500; line-height: 28px; display: flex; align-items: center"> <span class="bind">
<span>{{ item.title }}</span> <icon-check-circle-fill v-if="item.status" :size="14" class="success" />
<div style="margin-left: 10px"> <icon-exclamation-circle-fill v-else :size="14" class="warning" />
<GiSvgIcon :name="item.status ? 'success' : 'warning'" :size="14" /><span <span style="font-size: 12px" :class="item.status ? 'success' : 'warning'">{{
style="margin-left: 5px; font-size: 12px" item.status ? '已绑定' : '未绑定'
>{{ item.status ? '已开启' : '未开启' }}</span }}</span>
> </span>
</div> </div>
</div> <div class="info-desc">
<div style="font-size: 12px">{{ item.subtitle }}</div> <span class="value">{{ item.value }}</span>
{{ item.subtitle }}
</div> </div>
</div> </div>
<div> <div class="btn-wrapper">
<a-button @click="openVerifyModel(item.type)">修改</a-button> <a-button class="btn" :type="item.status ? 'secondary' : 'primary'" @click="onUpdate(item.type)">
{{ item.status ? '修改' : '绑定' }}
</a-button>
</div> </div>
</div> </div>
</template> </div>
</Card> </a-card>
<VerifyModel ref="verifyModelRef" /> <VerifyModel ref="verifyModelRef" />
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import Card from '../components/Card.vue' import type { ModeItem } from '../type'
import VerifyModel from '../components/VerifyModel.vue' import VerifyModel from '../components/VerifyModel.vue'
interface ModeItem { import { useUserStore } from '@/stores'
title: string
icon: string const userStore = useUserStore()
subtitle: string const userInfo = computed(() => userStore.userInfo)
type: 'phone' | 'email'
status: boolean
}
const modeList = ref<ModeItem[]>([]) const modeList = ref<ModeItem[]>([])
modeList.value = [ modeList.value = [
{ title: '绑定手机号', icon: 'tel', subtitle: '+86******88888可通过手机验证码快捷登录', type: 'phone', status: true }, {
{ title: '绑定邮箱', icon: 'mail', subtitle: '邮箱可用于身份验证、密码找回、通知接收', type: 'email', status: true } title: '安全手机',
icon: 'phone-color',
value: `${userInfo.value.phone + ' ' || '手机号'}`,
subtitle: `可用于身份验证、密码找回、通知接收`,
type: 'phone',
status: !!userInfo.value.phone
},
{
title: '安全邮箱',
icon: 'email-color',
value: `${userInfo.value.email + ' ' || '邮箱'}`,
subtitle: `可用于身份验证、密码找回、通知接收`,
type: 'email',
status: !!userInfo.value.email
}
] ]
const verifyModelRef = ref<InstanceType<typeof VerifyModel>>() const verifyModelRef = ref<InstanceType<typeof VerifyModel>>()
const openVerifyModel = (type: 'phone' | 'email') => { // 修改
const onUpdate = (type: string) => {
verifyModelRef.value?.open(type) verifyModelRef.value?.open(type)
} }
</script> </script>
<style lang="scss" scoped> <style lang="scss" scoped>
.mode-item { .mode-item {
display: flex; display: flex;
justify-content: space-between; justify-content: space-between;
align-items: center;
margin-bottom: 20px; margin-bottom: 20px;
.mode-item-content { .mode-item-content {
display: flex; display: flex;
align-items: center;
.icon { .icon {
margin-right: 10px; margin-right: 10px;
} }

View File

@@ -1,61 +1,38 @@
<template> <template>
<Card> <a-card title="密码策略" bordered class="gradient-card">
<template #header> 密码策略 </template> <div class="item">
<template #body> <div class="icon-wrapper"><GiSvgIcon name="password" :size="26" /></div>
<div class="content_title"> <div class="info">
<div class="icon"><GiSvgIcon name="password" :size="36" /></div> <div class="info-top">
<div> <span class="label">登录密码</span>
<div style="font-size: 14px; font-weight: 500; line-height: 28px">登录密码</div>
<div style="font-size: 12px">为了您的账号安全建议定期修改密码</div>
</div> </div>
<div class="info-desc">为了您的账号安全建议定期修改密码</div>
</div> </div>
<div class="content_Box"> <div class="btn-wrapper">
<p> <a-button class="btn">修改</a-button>
密码至少包含 <span class="subTitle">大写字母</span><span class="subTitle">小写字母</span
><span class="subTitle">数字</span><span class="subTitle">特殊字符</span>3
</p>
<p>限制密码长度至少为<span class="subTitle">8</span></p>
<p>未设置密码有效期</p>
<p>新密码不能与历史前<span class="subTitle">3</span>次密码重复</p>
<p>1小时内密码错误可重试 <span class="subTitle">5</span></p>
<p>超过错误密码重试次数账号将被锁定<span class="subTitle">60</span>分钟</p>
<p class="link_btn">修改规则(未开发)</p>
</div> </div>
</template> </div>
</Card> <div class="detail">
<div class="sub-text-wrapper">
<div class="sub-text">
密码至少包含
<span class="sub-text-value">大写字母</span>
<span class="sub-text-value">大写字母</span>
<span class="sub-text-value">小写字母</span>
<span class="sub-text-value">数字</span>
<span class="sub-text-value">特殊字符</span>3
</div>
<div class="sub-text">限制密码长度至少为<span class="sub-text-value">6</span></div>
<div class="sub-text">未设置密码有效期</div>
<div class="sub-text">新密码不能与历史前<span class="sub-text-value">N</span>次密码重复</div>
<div class="sub-text">1小时内密码错误可重试<span class="sub-text-value">N</span></div>
<div class="sub-text">超过错误密码重试次数账号将被锁定<span class="sub-text-value">N</span>分钟</div>
<a-link class="link">修改规则未开放</a-link>
</div>
</div>
</a-card>
</template> </template>
<script setup lang="ts">
import Card from '../components/Card.vue' <script lang="ts" setup></script>
</script>
<style scoped lang="scss"> <style lang="scss" scoped></style>
.content_title {
display: flex;
align-items: center;
.icon {
margin-right: 10px;
}
}
.content_Box {
border-left: 1px solid #ccc;
margin-left: 40px;
padding-left: 10px;
font-size: 12px;
margin-top: 20px;
& > p {
margin: 10px;
}
}
.link_btn {
cursor: pointer;
color: #007aff;
&:hover {
color: rgba($color: #007aff, $alpha: 0.8);
}
}
.subTitle {
background: var(--color-neutral-2);
padding: 1px 5px;
margin: 0px 5px;
border-radius: 3px;
}
</style>

View File

@@ -1,48 +1,24 @@
<template> <template>
<Card style="height: 100%"> <a-card title="登录会话设置" bordered class="gradient-card">
<template #header> 登录会话设置 </template> <div class="item">
<template #body> <div class="icon-wrapper"><GiSvgIcon name="message-color" :size="26" /></div>
<div class="content_title"> <div class="info">
<div class="icon"><GiSvgIcon name="login-status" :size="36" /></div> <div class="info-top">
<div> <span class="label">登录态保持时间设置</span>
<div style="font-size: 14px; font-weight: 500; line-height: 28px">登录态保持时间设置</div>
<div style="font-size: 12px">保持登录状态的限制</div>
</div> </div>
<div class="info-desc">保持登录状态的限制</div>
</div> </div>
<div class="content_Box"> </div>
<p>操作登录会话保持120分钟超时登录会话将失效</p> <div class="detail">
<p>登录会话最大保持0天超时登录会话将失效</p> <div class="sub-text-wrapper">
<p class="link_btn">修改规则(未开发)</p> <div class="sub-text">无操作登录会话保持<span class="sub-text-value">30</span>分钟超时登录会话将失效</div>
<div class="sub-text">登录会话最大保持<span class="sub-text-value">0</span>超时登录会话将失效</div>
<a-link class="link">修改规则未开放</a-link class="link">
</div> </div>
</template> </div>
</Card> </a-card>
</template> </template>
<script setup lang="ts">
import Card from '../components/Card.vue' <script lang="ts" setup></script>
</script>
<style scoped lang="scss"> <style lang="scss" scoped></style>
.content_title {
display: flex;
align-items: center;
.icon {
margin-right: 10px;
}
}
.content_Box {
border-left: 1px solid #ccc;
margin-left: 40px;
padding-left: 10px;
font-size: 12px;
margin-top: 20px;
& > p {
margin: 10px;
}
}
.link_btn {
cursor: pointer;
color: #007aff;
&:hover {
color: rgba($color: #007aff, $alpha: 0.8);
}
}
</style>

View File

@@ -1,21 +1,21 @@
<template> <template>
<div class="page"> <div class="gi_page">
<div class="flex_box"> <a-row wrap :gutter="16">
<div class="flex_item_container"> <a-col :xs="24" :sm="24" :md="12" :lg="12" :xl="12" :xxl="12">
<BasicsSetting /> <BasicsSetting />
</div> </a-col>
<div class="flex_item_container"> <a-col :xs="24" :sm="24" :md="12" :lg="12" :xl="12" :xxl="12">
<SessionSetting /> <SessionSetting />
</div> </a-col>
</div> </a-row>
<div class="flex_box"> <a-row wrap :gutter="16" style="margin-top: 16px">
<div class="flex_item_container"> <a-col :xs="24" :sm="24" :md="12" :lg="12" :xl="12" :xxl="12">
<PasswordPolicy /> <PasswordPolicy />
</div> </a-col>
<div class="flex_item_container"> <a-col :xs="24" :sm="24" :md="12" :lg="12" :xl="12" :xxl="12">
<AccountProtection /> <AccountProtection />
</div> </a-col>
</div> </a-row>
</div> </div>
</template> </template>
@@ -25,20 +25,19 @@ import BasicsSetting from './BasicsSetting.vue'
import SessionSetting from './SessionSetting.vue' import SessionSetting from './SessionSetting.vue'
import PasswordPolicy from './PasswordPolicy.vue' import PasswordPolicy from './PasswordPolicy.vue'
import AccountProtection from './AccountProtection.vue' import AccountProtection from './AccountProtection.vue'
defineOptions({ name: 'Security' })
defineOptions({ name: 'SettingSecurity' })
const route = useRoute() const route = useRoute()
const form = reactive({ name: '' }) const form = reactive({ name: '' })
</script> </script>
<style lang="scss" scoped> <style lang="scss" scoped>
.page { .gi_page {
padding: $padding;
background-color: var(--color-bg-1); background-color: var(--color-bg-1);
.flex_box { .flex_box {
display: flex; display: flex;
margin-bottom: 20px; margin-bottom: 20px;
height: 100%;
.flex_item_container { .flex_item_container {
width: 100%; width: 100%;
height: 100%; height: 100%;

View File

@@ -2,7 +2,8 @@ export interface ModeItem {
title: string title: string
icon: string icon: string
subtitle: string subtitle: string
value?: string
type: 'phone' | 'email' | 'gitee' | 'github' type: 'phone' | 'email' | 'gitee' | 'github'
jumpMode: 'link' | 'modal' jumpMode?: 'link' | 'modal'
status: boolean status: boolean
} }