mirror of
				https://github.com/continew-org/continew-admin-ui.git
				synced 2025-11-04 10:57:08 +08:00 
			
		
		
		
	refactor: 优化个人中心部分代码
This commit is contained in:
		@@ -8,7 +8,7 @@ export interface UserInfo {
 | 
				
			|||||||
  phone: string
 | 
					  phone: string
 | 
				
			||||||
  avatar: string
 | 
					  avatar: string
 | 
				
			||||||
  pwdResetTime: string
 | 
					  pwdResetTime: string
 | 
				
			||||||
  passwordExpired: boolean
 | 
					  pwdExpired: boolean
 | 
				
			||||||
  registrationDate: string
 | 
					  registrationDate: string
 | 
				
			||||||
  deptName: string
 | 
					  deptName: string
 | 
				
			||||||
  roles: string[]
 | 
					  roles: string[]
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -19,12 +19,12 @@ export function updateUserPassword(data: { oldPassword: string; newPassword: str
 | 
				
			|||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/** @desc 修改手机号 */
 | 
					/** @desc 修改手机号 */
 | 
				
			||||||
export function updateUserPhone(data: { newPhone: string; captcha: string; currentPassword: string }) {
 | 
					export function updateUserPhone(data: { phone: string; captcha: string; oldPassword: string }) {
 | 
				
			||||||
  return http.patch(`${BASE_URL}/phone`, data)
 | 
					  return http.patch(`${BASE_URL}/phone`, data)
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/** @desc 修改邮箱 */
 | 
					/** @desc 修改邮箱 */
 | 
				
			||||||
export function updateUserEmail(data: { newEmail: string; captcha: string; currentPassword: string }) {
 | 
					export function updateUserEmail(data: { email: string; captcha: string; oldPassword: string }) {
 | 
				
			||||||
  return http.patch(`${BASE_URL}/email`, data)
 | 
					  return http.patch(`${BASE_URL}/email`, data)
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 
 | 
				
			|||||||
| 
		 Before Width: | Height: | Size: 846 B After Width: | Height: | Size: 846 B  | 
@@ -51,7 +51,7 @@
 | 
				
			|||||||
        </a-row>
 | 
					        </a-row>
 | 
				
			||||||
        <template #content>
 | 
					        <template #content>
 | 
				
			||||||
          <a-doption @click="router.push('/setting/profile')">
 | 
					          <a-doption @click="router.push('/setting/profile')">
 | 
				
			||||||
            <span>账号管理</span>
 | 
					            <span>个人中心</span>
 | 
				
			||||||
          </a-doption>
 | 
					          </a-doption>
 | 
				
			||||||
          <a-divider :margin="0" />
 | 
					          <a-divider :margin="0" />
 | 
				
			||||||
          <a-doption @click="logout">
 | 
					          <a-doption @click="logout">
 | 
				
			||||||
@@ -98,16 +98,14 @@ const logout = () => {
 | 
				
			|||||||
    }
 | 
					    }
 | 
				
			||||||
  })
 | 
					  })
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
onMounted(() => {
 | 
					
 | 
				
			||||||
  checkPasswordExpired()
 | 
					 | 
				
			||||||
})
 | 
					 | 
				
			||||||
const checkPasswordExpired = () => {
 | 
					const checkPasswordExpired = () => {
 | 
				
			||||||
  if (!userStore.passwordExpiredShow || !userStore.userInfo.passwordExpired) {
 | 
					  if (!userStore.pwdExpiredShow || !userStore.userInfo.pwdExpired) {
 | 
				
			||||||
    return
 | 
					    return
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
  Modal.confirm({
 | 
					  Modal.confirm({
 | 
				
			||||||
    title: '提示',
 | 
					    title: '提示',
 | 
				
			||||||
    content: '密码已过期,是否去修改?',
 | 
					    content: '密码已过期,需要跳转到修改密码页面?',
 | 
				
			||||||
    hideCancel: false,
 | 
					    hideCancel: false,
 | 
				
			||||||
    closable: true,
 | 
					    closable: true,
 | 
				
			||||||
    onBeforeOk: async () => {
 | 
					    onBeforeOk: async () => {
 | 
				
			||||||
@@ -120,10 +118,14 @@ const checkPasswordExpired = () => {
 | 
				
			|||||||
    },
 | 
					    },
 | 
				
			||||||
    onCancel: () => {
 | 
					    onCancel: () => {
 | 
				
			||||||
      // 当前登录会话不再提示
 | 
					      // 当前登录会话不再提示
 | 
				
			||||||
      userStore.passwordExpiredShow = false
 | 
					      userStore.pwdExpiredShow = false
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
  })
 | 
					  })
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					onMounted(() => {
 | 
				
			||||||
 | 
					  checkPasswordExpired()
 | 
				
			||||||
 | 
					})
 | 
				
			||||||
</script>
 | 
					</script>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
<style lang="scss" scoped>
 | 
					<style lang="scss" scoped>
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -61,7 +61,7 @@ export const constantRoutes: RouteRecordRaw[] = [
 | 
				
			|||||||
        path: '/setting/profile',
 | 
					        path: '/setting/profile',
 | 
				
			||||||
        name: 'SettingProfile',
 | 
					        name: 'SettingProfile',
 | 
				
			||||||
        component: () => import('@/views/setting/profile/index.vue'),
 | 
					        component: () => import('@/views/setting/profile/index.vue'),
 | 
				
			||||||
        meta: { title: '账号管理', showInTabs: false }
 | 
					        meta: { title: '个人中心', showInTabs: false }
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
    ]
 | 
					    ]
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -27,7 +27,7 @@ const storeSetup = () => {
 | 
				
			|||||||
    phone: '',
 | 
					    phone: '',
 | 
				
			||||||
    avatar: '',
 | 
					    avatar: '',
 | 
				
			||||||
    pwdResetTime: '',
 | 
					    pwdResetTime: '',
 | 
				
			||||||
    passwordExpired: false,
 | 
					    pwdExpired: false,
 | 
				
			||||||
    registrationDate: '',
 | 
					    registrationDate: '',
 | 
				
			||||||
    deptName: '',
 | 
					    deptName: '',
 | 
				
			||||||
    roles: [],
 | 
					    roles: [],
 | 
				
			||||||
@@ -37,7 +37,7 @@ const storeSetup = () => {
 | 
				
			|||||||
  const avatar = computed(() => userInfo.avatar)
 | 
					  const avatar = computed(() => userInfo.avatar)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  const token = ref(getToken() || '')
 | 
					  const token = ref(getToken() || '')
 | 
				
			||||||
  const passwordExpiredShow = ref<boolean>(true)
 | 
					  const pwdExpiredShow = ref<boolean>(true)
 | 
				
			||||||
  const roles = ref<string[]>([]) // 当前用户角色
 | 
					  const roles = ref<string[]>([]) // 当前用户角色
 | 
				
			||||||
  const permissions = ref<string[]>([]) // 当前角色权限标识集合
 | 
					  const permissions = ref<string[]>([]) // 当前角色权限标识集合
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -91,7 +91,7 @@ const storeSetup = () => {
 | 
				
			|||||||
  const logoutCallBack = async () => {
 | 
					  const logoutCallBack = async () => {
 | 
				
			||||||
    roles.value = []
 | 
					    roles.value = []
 | 
				
			||||||
    permissions.value = []
 | 
					    permissions.value = []
 | 
				
			||||||
    passwordExpiredShow.value = true
 | 
					    pwdExpiredShow.value = true
 | 
				
			||||||
    resetToken()
 | 
					    resetToken()
 | 
				
			||||||
    resetRouter()
 | 
					    resetRouter()
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
@@ -114,7 +114,7 @@ const storeSetup = () => {
 | 
				
			|||||||
    token,
 | 
					    token,
 | 
				
			||||||
    roles,
 | 
					    roles,
 | 
				
			||||||
    permissions,
 | 
					    permissions,
 | 
				
			||||||
    passwordExpiredShow,
 | 
					    pwdExpiredShow,
 | 
				
			||||||
    accountLogin,
 | 
					    accountLogin,
 | 
				
			||||||
    emailLogin,
 | 
					    emailLogin,
 | 
				
			||||||
    phoneLogin,
 | 
					    phoneLogin,
 | 
				
			||||||
@@ -127,5 +127,5 @@ const storeSetup = () => {
 | 
				
			|||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export const useUserStore = defineStore('user', storeSetup, {
 | 
					export const useUserStore = defineStore('user', storeSetup, {
 | 
				
			||||||
  persist: { paths: ['token', 'roles', 'permissions', 'passwordExpiredShow'], storage: localStorage }
 | 
					  persist: { paths: ['token', 'roles', 'permissions', 'pwdExpiredShow'], storage: localStorage }
 | 
				
			||||||
})
 | 
					})
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -19,15 +19,21 @@
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
<script setup lang="ts">
 | 
					<script setup lang="ts">
 | 
				
			||||||
// import { getSmsCaptcha, getEmailCaptcha, updateUserEmail, updateUserPhone } from '@/apis'
 | 
					// import { getSmsCaptcha, getEmailCaptcha, updateUserEmail, updateUserPhone } from '@/apis'
 | 
				
			||||||
 | 
					import { updateUserPassword } from '@/apis'
 | 
				
			||||||
import { Message } from '@arco-design/web-vue'
 | 
					import { Message } from '@arco-design/web-vue'
 | 
				
			||||||
// import { encryptByRsa } from '@/utils/encrypt'
 | 
					import { encryptByRsa } from '@/utils/encrypt'
 | 
				
			||||||
import { useUserStore } from '@/stores'
 | 
					import { useUserStore } from '@/stores'
 | 
				
			||||||
import { type Columns, GiForm } from '@/components/GiForm'
 | 
					import { type Columns, GiForm } from '@/components/GiForm'
 | 
				
			||||||
import { useForm } from '@/hooks'
 | 
					import { useForm } from '@/hooks'
 | 
				
			||||||
import * as Regexp from '@/utils/regexp'
 | 
					import * as Regexp from '@/utils/regexp'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const userStore = useUserStore()
 | 
				
			||||||
 | 
					const userInfo = computed(() => userStore.userInfo)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const verifyType = ref()
 | 
					const verifyType = ref()
 | 
				
			||||||
const title = computed(() => (verifyType.value === 'phone' ? '修改手机号' : '修改邮箱'))
 | 
					const title = computed(
 | 
				
			||||||
 | 
					  () => `修改${verifyType.value === 'phone' ? '手机号' : verifyType.value === 'email' ? '邮箱' : '密码'}`
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
const formRef = ref<InstanceType<typeof GiForm>>()
 | 
					const formRef = ref<InstanceType<typeof GiForm>>()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const options: Options = {
 | 
					const options: Options = {
 | 
				
			||||||
@@ -39,7 +45,7 @@ const options: Options = {
 | 
				
			|||||||
const columns: Columns = [
 | 
					const columns: Columns = [
 | 
				
			||||||
  {
 | 
					  {
 | 
				
			||||||
    label: '手机号',
 | 
					    label: '手机号',
 | 
				
			||||||
    field: 'newPhone',
 | 
					    field: 'phone',
 | 
				
			||||||
    type: 'input',
 | 
					    type: 'input',
 | 
				
			||||||
    rules: [
 | 
					    rules: [
 | 
				
			||||||
      { required: true, message: '请输入手机号' },
 | 
					      { required: true, message: '请输入手机号' },
 | 
				
			||||||
@@ -65,21 +71,50 @@ const columns: Columns = [
 | 
				
			|||||||
    label: '验证码',
 | 
					    label: '验证码',
 | 
				
			||||||
    field: 'captcha',
 | 
					    field: 'captcha',
 | 
				
			||||||
    type: 'input',
 | 
					    type: 'input',
 | 
				
			||||||
    rules: [{ required: true, message: '请输入验证码' }]
 | 
					    rules: [{ required: true, message: '请输入验证码' }],
 | 
				
			||||||
 | 
					    hide: () => {
 | 
				
			||||||
 | 
					      return !['phone', 'email'].includes(verifyType.value)
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
  },
 | 
					  },
 | 
				
			||||||
  {
 | 
					  {
 | 
				
			||||||
    label: '当前密码',
 | 
					    label: '当前密码',
 | 
				
			||||||
    field: 'currentPassword',
 | 
					    field: 'oldPassword',
 | 
				
			||||||
    type: 'input',
 | 
					    type: 'input-password',
 | 
				
			||||||
    rules: [{ required: true, message: '请输入当前密码' }]
 | 
					    rules: [{ required: true, message: '请输入当前密码' }],
 | 
				
			||||||
 | 
					    hide: () => {
 | 
				
			||||||
 | 
					      return !userInfo.value.pwdResetTime
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  },
 | 
				
			||||||
 | 
					  {
 | 
				
			||||||
 | 
					    label: '新密码',
 | 
				
			||||||
 | 
					    field: 'newPassword',
 | 
				
			||||||
 | 
					    type: 'input-password',
 | 
				
			||||||
 | 
					    rules: [{ required: true, message: '请输入新密码' }],
 | 
				
			||||||
 | 
					    hide: () => {
 | 
				
			||||||
 | 
					      return verifyType.value !== 'password'
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  },
 | 
				
			||||||
 | 
					  {
 | 
				
			||||||
 | 
					    label: '确认新密码',
 | 
				
			||||||
 | 
					    field: 'rePassword',
 | 
				
			||||||
 | 
					    type: 'input-password',
 | 
				
			||||||
 | 
					    rules: [{ required: true, message: '请再次输入新密码' }],
 | 
				
			||||||
 | 
					    props: {
 | 
				
			||||||
 | 
					      placeholder: '请再次输入新密码'
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					    hide: () => {
 | 
				
			||||||
 | 
					      return verifyType.value !== 'password'
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
]
 | 
					]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const { form, resetForm } = useForm({
 | 
					const { form, resetForm } = useForm({
 | 
				
			||||||
  newPhone: '',
 | 
					  phone: '',
 | 
				
			||||||
 | 
					  email: '',
 | 
				
			||||||
  captcha: '',
 | 
					  captcha: '',
 | 
				
			||||||
  currentPassword: '',
 | 
					  oldPassword: '',
 | 
				
			||||||
  email: ''
 | 
					  newPassword: '',
 | 
				
			||||||
 | 
					  rePassword: ''
 | 
				
			||||||
})
 | 
					})
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// 重置
 | 
					// 重置
 | 
				
			||||||
@@ -112,7 +147,7 @@ const onCaptcha = async () => {
 | 
				
			|||||||
    captchaBtnName.value = '发送中...'
 | 
					    captchaBtnName.value = '发送中...'
 | 
				
			||||||
    if (verifyType.value === 'phone') {
 | 
					    if (verifyType.value === 'phone') {
 | 
				
			||||||
      // await getSmsCaptcha({
 | 
					      // await getSmsCaptcha({
 | 
				
			||||||
      //   phone: form.newPhone
 | 
					      //   phone: form.phone
 | 
				
			||||||
      // })
 | 
					      // })
 | 
				
			||||||
    } else if (verifyType.value === 'email') {
 | 
					    } else if (verifyType.value === 'email') {
 | 
				
			||||||
      // await getEmailCaptcha({
 | 
					      // await getEmailCaptcha({
 | 
				
			||||||
@@ -138,24 +173,36 @@ const onCaptcha = async () => {
 | 
				
			|||||||
  }
 | 
					  }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const userStore = useUserStore()
 | 
					 | 
				
			||||||
// 保存
 | 
					// 保存
 | 
				
			||||||
const save = async () => {
 | 
					const save = async () => {
 | 
				
			||||||
  const isInvalid = await formRef.value?.formRef?.validate()
 | 
					  const isInvalid = await formRef.value?.formRef?.validate()
 | 
				
			||||||
  if (isInvalid) return false
 | 
					  if (isInvalid) return false
 | 
				
			||||||
  try {
 | 
					  try {
 | 
				
			||||||
    if (verifyType.value === 'phone') {
 | 
					    if (verifyType.value === 'phone') {
 | 
				
			||||||
      // await updateUserEmail({
 | 
					      // await updateUserPhone({
 | 
				
			||||||
      //   newEmail: form.email,
 | 
					      //   phone: form.phone,
 | 
				
			||||||
      //   captcha: form.captcha,
 | 
					      //   captcha: form.captcha,
 | 
				
			||||||
      //   currentPassword: encryptByRsa(form.currentPassword) as string
 | 
					      //   oldPassword: encryptByRsa(form.oldPassword) as string
 | 
				
			||||||
      // })
 | 
					      // })
 | 
				
			||||||
    } else if (verifyType.value === 'email') {
 | 
					    } else if (verifyType.value === 'email') {
 | 
				
			||||||
      // await updateUserPhone({
 | 
					      // await updateUserEmail({
 | 
				
			||||||
      //   newPhone: form.email,
 | 
					      //   email: form.email,
 | 
				
			||||||
      //   captcha: form.captcha,
 | 
					      //   captcha: form.captcha,
 | 
				
			||||||
      //   currentPassword: encryptByRsa(form.currentPassword) as string
 | 
					      //   oldPassword: encryptByRsa(form.oldPassword) as string
 | 
				
			||||||
      // })
 | 
					      // })
 | 
				
			||||||
 | 
					    } else if (verifyType.value === 'password') {
 | 
				
			||||||
 | 
					      if (form.newPassword !== form.rePassword) {
 | 
				
			||||||
 | 
					        Message.error('两次新密码不一致')
 | 
				
			||||||
 | 
					        return false
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					      if (form.newPassword === form.oldPassword) {
 | 
				
			||||||
 | 
					        Message.error('新密码与旧密码不能相同')
 | 
				
			||||||
 | 
					        return false
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					      await updateUserPassword({
 | 
				
			||||||
 | 
					        oldPassword: encryptByRsa(form.oldPassword) || '',
 | 
				
			||||||
 | 
					        newPassword: encryptByRsa(form.newPassword) || ''
 | 
				
			||||||
 | 
					      })
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
    Message.success('修改成功')
 | 
					    Message.success('修改成功')
 | 
				
			||||||
    // 修改成功后,重新获取用户信息
 | 
					    // 修改成功后,重新获取用户信息
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,178 +0,0 @@
 | 
				
			|||||||
<template>
 | 
					 | 
				
			||||||
  <a-card title="密码策略" bordered class="gradient-card">
 | 
					 | 
				
			||||||
    <div class="item">
 | 
					 | 
				
			||||||
      <div class="icon-wrapper"><GiSvgIcon name="password" :size="26" /></div>
 | 
					 | 
				
			||||||
      <div class="info">
 | 
					 | 
				
			||||||
        <div class="info-top">
 | 
					 | 
				
			||||||
          <span class="label">登录密码</span>
 | 
					 | 
				
			||||||
        </div>
 | 
					 | 
				
			||||||
        <div class="info-desc">为了您的账号安全,建议定期修改密码</div>
 | 
					 | 
				
			||||||
      </div>
 | 
					 | 
				
			||||||
      <div class="btn-wrapper">
 | 
					 | 
				
			||||||
        <a-button class="btn" @click="onUpdate">修改</a-button>
 | 
					 | 
				
			||||||
      </div>
 | 
					 | 
				
			||||||
    </div>
 | 
					 | 
				
			||||||
    <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" v-if="securityConfig.password_special_char.value == 1">特殊字符</span>
 | 
					 | 
				
			||||||
        </div>
 | 
					 | 
				
			||||||
        <div class="sub-text" v-if="securityConfig.password_contain_name.value == 1">
 | 
					 | 
				
			||||||
          密码不能包含<span class="sub-text-value">正反序用户名</span>
 | 
					 | 
				
			||||||
        </div>
 | 
					 | 
				
			||||||
        <div class="sub-text">
 | 
					 | 
				
			||||||
          密码长度至少
 | 
					 | 
				
			||||||
          <span class="sub-text-value">
 | 
					 | 
				
			||||||
            {{ securityConfig.password_min_length.value }}
 | 
					 | 
				
			||||||
          </span>
 | 
					 | 
				
			||||||
          位
 | 
					 | 
				
			||||||
        </div>
 | 
					 | 
				
			||||||
        <div class="sub-text">
 | 
					 | 
				
			||||||
          <div v-if="securityConfig.password_expiration_days.value == 0">未设置密码有效期</div>
 | 
					 | 
				
			||||||
          <div v-else>
 | 
					 | 
				
			||||||
            密码有效期
 | 
					 | 
				
			||||||
            <span class="sub-text-value">
 | 
					 | 
				
			||||||
              {{ securityConfig.password_expiration_days.value }}
 | 
					 | 
				
			||||||
            </span>
 | 
					 | 
				
			||||||
            天
 | 
					 | 
				
			||||||
          </div>
 | 
					 | 
				
			||||||
        </div>
 | 
					 | 
				
			||||||
        <div class="sub-text">
 | 
					 | 
				
			||||||
          连续密码错误可重试
 | 
					 | 
				
			||||||
          <span class="sub-text-value">
 | 
					 | 
				
			||||||
            {{ securityConfig.password_error_count.value }}
 | 
					 | 
				
			||||||
          </span>
 | 
					 | 
				
			||||||
          次
 | 
					 | 
				
			||||||
        </div>
 | 
					 | 
				
			||||||
        <div class="sub-text">
 | 
					 | 
				
			||||||
          超过错误密码重试次数账号将被锁定
 | 
					 | 
				
			||||||
          <span class="sub-text-value">
 | 
					 | 
				
			||||||
            {{ securityConfig.password_lock_minutes.value }}
 | 
					 | 
				
			||||||
          </span>
 | 
					 | 
				
			||||||
          分钟
 | 
					 | 
				
			||||||
        </div>
 | 
					 | 
				
			||||||
      </div>
 | 
					 | 
				
			||||||
    </div>
 | 
					 | 
				
			||||||
  </a-card>
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  <a-modal v-model:visible="visible" title="修改密码" @before-ok="save" @close="reset">
 | 
					 | 
				
			||||||
    <GiForm ref="formRef" v-model="form" :options="options" :columns="columns" />
 | 
					 | 
				
			||||||
  </a-modal>
 | 
					 | 
				
			||||||
</template>
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
<script lang="ts" setup>
 | 
					 | 
				
			||||||
import { listOption, type OptionResp, type SecurityConfigResp, updateUserPassword } from '@/apis'
 | 
					 | 
				
			||||||
import { Message } from '@arco-design/web-vue'
 | 
					 | 
				
			||||||
import { encryptByRsa } from '@/utils/encrypt'
 | 
					 | 
				
			||||||
import { type Columns, GiForm } from '@/components/GiForm'
 | 
					 | 
				
			||||||
import { useForm } from '@/hooks'
 | 
					 | 
				
			||||||
import { useUserStore } from '@/stores'
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
const userStore = useUserStore()
 | 
					 | 
				
			||||||
const userInfo = computed(() => userStore.userInfo)
 | 
					 | 
				
			||||||
const formRef = ref<InstanceType<typeof GiForm>>()
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
const options: Options = {
 | 
					 | 
				
			||||||
  form: {},
 | 
					 | 
				
			||||||
  col: { xs: 24, sm: 24, md: 24, lg: 24, xl: 24, xxl: 24 },
 | 
					 | 
				
			||||||
  btns: { hide: true }
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
const columns: Columns = [
 | 
					 | 
				
			||||||
  {
 | 
					 | 
				
			||||||
    label: '当前密码',
 | 
					 | 
				
			||||||
    field: 'oldPassword',
 | 
					 | 
				
			||||||
    type: 'input-password',
 | 
					 | 
				
			||||||
    rules: [{ required: true, message: '密码长度不正确', maxLength: 32, minLength: 6 }],
 | 
					 | 
				
			||||||
    hide: () => {
 | 
					 | 
				
			||||||
      return userInfo.pwdResetTime
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
  },
 | 
					 | 
				
			||||||
  {
 | 
					 | 
				
			||||||
    label: '新密码',
 | 
					 | 
				
			||||||
    field: 'newPassword',
 | 
					 | 
				
			||||||
    type: 'input-password',
 | 
					 | 
				
			||||||
    rules: [{ required: true, message: '密码长度不正确', maxLength: 32, minLength: 6 }]
 | 
					 | 
				
			||||||
  },
 | 
					 | 
				
			||||||
  {
 | 
					 | 
				
			||||||
    label: '确认新密码',
 | 
					 | 
				
			||||||
    field: 'rePassword',
 | 
					 | 
				
			||||||
    type: 'input-password',
 | 
					 | 
				
			||||||
    rules: [{ required: true, message: '密码长度不正确', maxLength: 32, minLength: 6 }],
 | 
					 | 
				
			||||||
    props: {
 | 
					 | 
				
			||||||
      placeholder: '请再次输入新密码'
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
]
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
const { form, resetForm } = useForm({
 | 
					 | 
				
			||||||
  oldPassword: '',
 | 
					 | 
				
			||||||
  newPassword: '',
 | 
					 | 
				
			||||||
  rePassword: ''
 | 
					 | 
				
			||||||
})
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
// 重置
 | 
					 | 
				
			||||||
const reset = () => {
 | 
					 | 
				
			||||||
  formRef.value?.formRef?.resetFields()
 | 
					 | 
				
			||||||
  resetForm()
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
const visible = ref(false)
 | 
					 | 
				
			||||||
// 修改
 | 
					 | 
				
			||||||
const onUpdate = async () => {
 | 
					 | 
				
			||||||
  reset()
 | 
					 | 
				
			||||||
  visible.value = true
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
// 保存
 | 
					 | 
				
			||||||
const save = async () => {
 | 
					 | 
				
			||||||
  const isInvalid = await formRef.value?.formRef?.validate()
 | 
					 | 
				
			||||||
  if (isInvalid) return false
 | 
					 | 
				
			||||||
  if (form.newPassword !== form.rePassword) {
 | 
					 | 
				
			||||||
    Message.error('两次新密码不一致')
 | 
					 | 
				
			||||||
    return false
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
  if (form.newPassword === form.oldPassword) {
 | 
					 | 
				
			||||||
    Message.error('新密码与旧密码不能相同')
 | 
					 | 
				
			||||||
    return false
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
  try {
 | 
					 | 
				
			||||||
    await updateUserPassword({
 | 
					 | 
				
			||||||
      oldPassword: encryptByRsa(form.oldPassword) || '',
 | 
					 | 
				
			||||||
      newPassword: encryptByRsa(form.newPassword) || ''
 | 
					 | 
				
			||||||
    })
 | 
					 | 
				
			||||||
    Message.success('修改成功')
 | 
					 | 
				
			||||||
    return true
 | 
					 | 
				
			||||||
  } catch (error) {
 | 
					 | 
				
			||||||
    return false
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
const securityConfig = ref<SecurityConfigResp>({
 | 
					 | 
				
			||||||
  password_contain_name: {},
 | 
					 | 
				
			||||||
  password_error_count: {},
 | 
					 | 
				
			||||||
  password_expiration_days: {},
 | 
					 | 
				
			||||||
  password_lock_minutes: {},
 | 
					 | 
				
			||||||
  password_min_length: {},
 | 
					 | 
				
			||||||
  password_special_char: {},
 | 
					 | 
				
			||||||
  password_update_interval: {}
 | 
					 | 
				
			||||||
})
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
// 查询列表数据
 | 
					 | 
				
			||||||
const getDataList = async () => {
 | 
					 | 
				
			||||||
  const { data } = await listOption({ code: Object.keys(securityConfig.value) })
 | 
					 | 
				
			||||||
  securityConfig.value = data.reduce((obj: SecurityConfigResp, option: OptionResp) => {
 | 
					 | 
				
			||||||
    obj[option.code] = { ...option, value: parseInt(option.value) }
 | 
					 | 
				
			||||||
    return obj
 | 
					 | 
				
			||||||
  }, {})
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
onMounted(() => {
 | 
					 | 
				
			||||||
  getDataList()
 | 
					 | 
				
			||||||
})
 | 
					 | 
				
			||||||
</script>
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
<style lang="scss" scoped></style>
 | 
					 | 
				
			||||||
							
								
								
									
										109
									
								
								src/views/setting/profile/Security.vue
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										109
									
								
								src/views/setting/profile/Security.vue
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,109 @@
 | 
				
			|||||||
 | 
					<template>
 | 
				
			||||||
 | 
					  <a-card title="安全设置" bordered class="gradient-card">
 | 
				
			||||||
 | 
					    <div v-for="item in modeList" :key="item.title">
 | 
				
			||||||
 | 
					      <div class="item">
 | 
				
			||||||
 | 
					        <div class="icon-wrapper"><GiSvgIcon :name="item.icon" :size="26" /></div>
 | 
				
			||||||
 | 
					        <div class="info">
 | 
				
			||||||
 | 
					          <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.statusString }}</span>
 | 
				
			||||||
 | 
					            </span>
 | 
				
			||||||
 | 
					          </div>
 | 
				
			||||||
 | 
					          <div class="info-desc">
 | 
				
			||||||
 | 
					            <span class="value">{{ item.value }}</span>
 | 
				
			||||||
 | 
					            {{ item.subtitle }}
 | 
				
			||||||
 | 
					          </div>
 | 
				
			||||||
 | 
					        </div>
 | 
				
			||||||
 | 
					        <div class="btn-wrapper">
 | 
				
			||||||
 | 
					          <a-button
 | 
				
			||||||
 | 
					            v-if="item.jumpMode == 'modal'"
 | 
				
			||||||
 | 
					            class="btn"
 | 
				
			||||||
 | 
					            :type="item.status ? 'secondary' : 'primary'"
 | 
				
			||||||
 | 
					            @click="onUpdate(item.type, item.status)"
 | 
				
			||||||
 | 
					          >
 | 
				
			||||||
 | 
					            {{ ['password'].includes(item.type) || item.status ? '修改' : '绑定' }}
 | 
				
			||||||
 | 
					          </a-button>
 | 
				
			||||||
 | 
					        </div>
 | 
				
			||||||
 | 
					      </div>
 | 
				
			||||||
 | 
					    </div>
 | 
				
			||||||
 | 
					  </a-card>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  <VerifyModel ref="verifyModelRef" />
 | 
				
			||||||
 | 
					</template>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					<script lang="ts" setup>
 | 
				
			||||||
 | 
					import { listOption, type OptionResp, type SecurityConfigResp } from '@/apis'
 | 
				
			||||||
 | 
					import type { ModeItem } from '../type'
 | 
				
			||||||
 | 
					import VerifyModel from '../components/VerifyModel.vue'
 | 
				
			||||||
 | 
					import { useUserStore } from '@/stores'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const userStore = useUserStore()
 | 
				
			||||||
 | 
					const userInfo = computed(() => userStore.userInfo)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const modeList = ref<ModeItem[]>([])
 | 
				
			||||||
 | 
					modeList.value = [
 | 
				
			||||||
 | 
					  {
 | 
				
			||||||
 | 
					    title: '安全手机',
 | 
				
			||||||
 | 
					    icon: 'phone-color',
 | 
				
			||||||
 | 
					    value: `${userInfo.value.phone + ' ' || '手机号'}`,
 | 
				
			||||||
 | 
					    subtitle: `可用于身份验证、密码找回、通知接收`,
 | 
				
			||||||
 | 
					    type: 'phone',
 | 
				
			||||||
 | 
					    jumpMode: 'modal',
 | 
				
			||||||
 | 
					    status: !!userInfo.value.phone,
 | 
				
			||||||
 | 
					    statusString: userInfo.value.phone ? '已绑定' : '未绑定'
 | 
				
			||||||
 | 
					  },
 | 
				
			||||||
 | 
					  {
 | 
				
			||||||
 | 
					    title: '安全邮箱',
 | 
				
			||||||
 | 
					    icon: 'email-color',
 | 
				
			||||||
 | 
					    value: `${userInfo.value.email + ' ' || '邮箱'}`,
 | 
				
			||||||
 | 
					    subtitle: `可用于身份验证、密码找回、通知接收`,
 | 
				
			||||||
 | 
					    type: 'email',
 | 
				
			||||||
 | 
					    jumpMode: 'modal',
 | 
				
			||||||
 | 
					    status: !!userInfo.value.email,
 | 
				
			||||||
 | 
					    statusString: userInfo.value.email ? '已绑定' : '未绑定'
 | 
				
			||||||
 | 
					  },
 | 
				
			||||||
 | 
					  {
 | 
				
			||||||
 | 
					    title: '登录密码',
 | 
				
			||||||
 | 
					    icon: 'password-color',
 | 
				
			||||||
 | 
					    subtitle: userInfo.value.pwdResetTime ? `为了您的账号安全,建议定期修改密码` : '请设置密码,可通过账号+密码登录',
 | 
				
			||||||
 | 
					    type: 'password',
 | 
				
			||||||
 | 
					    jumpMode: 'modal',
 | 
				
			||||||
 | 
					    status: !!userInfo.value.pwdResetTime,
 | 
				
			||||||
 | 
					    statusString: userInfo.value.pwdResetTime ? '已设置' : '未设置'
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const verifyModelRef = ref<InstanceType<typeof VerifyModel>>()
 | 
				
			||||||
 | 
					// 修改
 | 
				
			||||||
 | 
					const onUpdate = (type: string) => {
 | 
				
			||||||
 | 
					  verifyModelRef.value?.open(type)
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const securityConfig = ref<SecurityConfigResp>({
 | 
				
			||||||
 | 
					  password_contain_name: {},
 | 
				
			||||||
 | 
					  password_error_count: {},
 | 
				
			||||||
 | 
					  password_expiration_days: {},
 | 
				
			||||||
 | 
					  password_lock_minutes: {},
 | 
				
			||||||
 | 
					  password_min_length: {},
 | 
				
			||||||
 | 
					  password_special_char: {},
 | 
				
			||||||
 | 
					  password_update_interval: {}
 | 
				
			||||||
 | 
					})
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// 查询列表数据
 | 
				
			||||||
 | 
					const getDataList = async () => {
 | 
				
			||||||
 | 
					  const { data } = await listOption({ code: Object.keys(securityConfig.value) })
 | 
				
			||||||
 | 
					  securityConfig.value = data.reduce((obj: SecurityConfigResp, option: OptionResp) => {
 | 
				
			||||||
 | 
					    obj[option.code] = { ...option, value: parseInt(option.value) }
 | 
				
			||||||
 | 
					    return obj
 | 
				
			||||||
 | 
					  }, {})
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					onMounted(() => {
 | 
				
			||||||
 | 
					  getDataList()
 | 
				
			||||||
 | 
					})
 | 
				
			||||||
 | 
					</script>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					<style lang="scss" scoped></style>
 | 
				
			||||||
@@ -1,5 +1,5 @@
 | 
				
			|||||||
<template>
 | 
					<template>
 | 
				
			||||||
  <a-card title="登录方式" bordered class="gradient-card">
 | 
					  <a-card title="第三方账号" bordered class="gradient-card">
 | 
				
			||||||
    <div v-for="item in modeList" :key="item.title">
 | 
					    <div v-for="item in modeList" :key="item.title">
 | 
				
			||||||
      <div class="item">
 | 
					      <div class="item">
 | 
				
			||||||
        <div class="icon-wrapper"><GiSvgIcon :name="item.icon" :size="26" /></div>
 | 
					        <div class="icon-wrapper"><GiSvgIcon :name="item.icon" :size="26" /></div>
 | 
				
			||||||
@@ -55,24 +55,6 @@ const userInfo = computed(() => userStore.userInfo)
 | 
				
			|||||||
const socialList = ref<any>([])
 | 
					const socialList = ref<any>([])
 | 
				
			||||||
const modeList = ref<ModeItem[]>([])
 | 
					const modeList = ref<ModeItem[]>([])
 | 
				
			||||||
modeList.value = [
 | 
					modeList.value = [
 | 
				
			||||||
  {
 | 
					 | 
				
			||||||
    title: '绑定手机',
 | 
					 | 
				
			||||||
    icon: 'phone-color',
 | 
					 | 
				
			||||||
    value: `${userInfo.value.phone + ' ' || '绑定后,'}`,
 | 
					 | 
				
			||||||
    subtitle: `可通过手机验证码快捷登录`,
 | 
					 | 
				
			||||||
    type: 'phone',
 | 
					 | 
				
			||||||
    jumpMode: 'modal',
 | 
					 | 
				
			||||||
    status: !!userInfo.value.phone
 | 
					 | 
				
			||||||
  },
 | 
					 | 
				
			||||||
  {
 | 
					 | 
				
			||||||
    title: '绑定邮箱',
 | 
					 | 
				
			||||||
    icon: 'email-color',
 | 
					 | 
				
			||||||
    value: `${userInfo.value.email + ' ' || '绑定后,'}`,
 | 
					 | 
				
			||||||
    subtitle: `可通过邮箱验证码进行登录`,
 | 
					 | 
				
			||||||
    type: 'email',
 | 
					 | 
				
			||||||
    jumpMode: 'modal',
 | 
					 | 
				
			||||||
    status: !!userInfo.value.email
 | 
					 | 
				
			||||||
  },
 | 
					 | 
				
			||||||
  {
 | 
					  {
 | 
				
			||||||
    title: '绑定 Gitee',
 | 
					    title: '绑定 Gitee',
 | 
				
			||||||
    icon: 'gitee',
 | 
					    icon: 'gitee',
 | 
				
			||||||
@@ -17,9 +17,9 @@
 | 
				
			|||||||
</template>
 | 
					</template>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
<script setup lang="ts">
 | 
					<script setup lang="ts">
 | 
				
			||||||
import LeftBox from './LeftBox.vue'
 | 
					import LeftBox from './BasicInfo.vue'
 | 
				
			||||||
import RightBox from './RightBox.vue'
 | 
					import RightBox from './Social.vue'
 | 
				
			||||||
import PasswordPolicy from './PasswordPolicy.vue'
 | 
					import PasswordPolicy from './Security.vue'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
defineOptions({ name: 'SettingProfile' })
 | 
					defineOptions({ name: 'SettingProfile' })
 | 
				
			||||||
</script>
 | 
					</script>
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -6,4 +6,5 @@ export interface ModeItem {
 | 
				
			|||||||
  type: 'phone' | 'email' | 'gitee' | 'github'
 | 
					  type: 'phone' | 'email' | 'gitee' | 'github'
 | 
				
			||||||
  jumpMode?: 'link' | 'modal'
 | 
					  jumpMode?: 'link' | 'modal'
 | 
				
			||||||
  status: boolean
 | 
					  status: boolean
 | 
				
			||||||
 | 
					  statusString: string
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
		Reference in New Issue
	
	Block a user