Переглянути джерело

feat: 退出登录;修改密码;头像显示;

Gaokun Wang 3 тижнів тому
батько
коміт
a6eafe8f4c

+ 1 - 0
.eslintrc-auto-import.json

@@ -6,6 +6,7 @@
     "DirectiveBinding": true,
     "EffectScope": true,
     "ElMessage": true,
+    "ElMessageBox": true,
     "ExtractDefaultPropTypes": true,
     "ExtractPropTypes": true,
     "ExtractPublicPropTypes": true,

+ 1 - 0
src/api/interface/system/user.ts

@@ -32,6 +32,7 @@ export interface UserVO extends BaseEntity {
   nickName: string
   userType: string
   email: string
+  isLock: string
   phoneNumber: string
   gender: string
   avatar: string

+ 67 - 0
src/layouts/components/AppTools/Avatar.vue

@@ -0,0 +1,67 @@
+<template>
+  <el-dropdown trigger="click">
+    <div class="avatar">
+      <el-avatar v-if="userStore.user.avatar" :src="userStore.user.avatar" />
+      <el-avatar v-else icon="UserFilled" />
+    </div>
+    <template #dropdown>
+      <el-dropdown-menu>
+        <el-dropdown-item @click="openDialog('passwordRef')">
+          <el-icon>
+            <Edit />
+          </el-icon>
+          修改密码
+        </el-dropdown-item>
+        <el-dropdown-item divided @click="logout">
+          <el-icon>
+            <SwitchButton />
+          </el-icon>
+          退出登录
+        </el-dropdown-item>
+      </el-dropdown-menu>
+    </template>
+  </el-dropdown>
+  <PasswordDialog ref="passwordRef" />
+</template>
+
+<script setup lang="ts" name="Avatar">
+import { LOGIN_URL } from '@/constants'
+import router from '@/router'
+import { useUserStore } from '@/stores'
+import PasswordDialog from '@/views/system/user/components/PasswordDialog.vue'
+const userStore = useUserStore()
+// 退出登录
+const logout = () => {
+  ElMessageBox.confirm('您是否确认退出登录?', '温馨提示', {
+    confirmButtonText: '确定',
+    cancelButtonText: '取消',
+    type: 'warning'
+  }).then(async () => {
+    // 1.执行退出登录接口
+    await userStore.userLogout()
+
+    // 2.重定向到登陆页
+    router.replace(LOGIN_URL)
+    ElMessage.success('退出登录成功!')
+  })
+}
+const passwordRef = ref<InstanceType<typeof PasswordDialog>>()
+
+const openDialog = (ref: string) => {
+  passwordRef.value?.openDialog()
+}
+</script>
+
+<style scoped lang="scss">
+.avatar {
+  width: 40px;
+  height: 40px;
+  overflow: hidden;
+  cursor: pointer;
+  border-radius: 50%;
+  img {
+    width: 100%;
+    height: 100%;
+  }
+}
+</style>

+ 1 - 3
src/layouts/components/AppTools/index.vue

@@ -1,12 +1,10 @@
 <template>
   <div class="tool-bar">
     <span class="username" style="margin-right: 8px">{{ userStore.user.userName }}</span>
-    <el-avatar :icon="UserFilled" />
+    <Avatar />
   </div>
 </template>
 <script lang="tsx" setup name="AppTools">
-import { UserFilled } from '@element-plus/icons-vue'
-
 import { useUserStore } from '@/stores'
 const userStore = useUserStore()
 </script>

+ 4 - 1
src/layouts/components/index.scss

@@ -9,7 +9,6 @@
   .logo-text {
     font-size: 21.5px;
     font-weight: bold;
-    color: var(--el-header-logo-text-color);
     white-space: nowrap;
   }
 }
@@ -17,4 +16,8 @@
   display: flex;
   align-items: center;
   justify-content: center;
+  .username {
+    font-size: 15px;
+    white-space: nowrap;
+  }
 }

+ 1 - 0
src/stores/interface/index.ts

@@ -28,6 +28,7 @@ export type UserInfo = {
   account: string
   userName: string
   orgId: string
+  avatar: string
   nickname: string
   userType: string
 }

+ 3 - 4
src/stores/modules/user.ts

@@ -11,6 +11,7 @@ export const useUserStore = defineStore('eco-user', {
     user: {
       userId: undefined,
       account: '',
+      avatar: '',
       userName: '',
       orgId: '',
       nickname: '',
@@ -46,9 +47,7 @@ export const useUserStore = defineStore('eco-user', {
         LoginApi.logout()
           .then(({ code, msg }) => {
             if (code === 200) {
-              this.token = ''
-              this.roleCodes = []
-              this.permissionCodes = []
+              this.clear()
               resolve({ code, msg })
             }
           })
@@ -82,7 +81,7 @@ export const useUserStore = defineStore('eco-user', {
     },
     clear() {
       this.token = ''
-      this.user = { userId: undefined, account: '', userName: '', orgId: '', nickname: '', userType: '' }
+      this.user = { userId: undefined, account: '', userName: '', orgId: '', nickname: '', userType: '', avatar: '' }
       this.permissionCodes = []
       this.roleCodes = []
     }

+ 2 - 0
src/types/auto-components.d.ts

@@ -17,6 +17,7 @@ declare module 'vue' {
     AppTabs: typeof import('./../layouts/components/AppTabs/index.vue')['default']
     AppTools: typeof import('./../layouts/components/AppTools/index.vue')['default']
     AppTransverse: typeof import('./../layouts/container/AppTransverse/index.vue')['default']
+    Avatar: typeof import('./../layouts/components/AppTools/Avatar.vue')['default']
     ColSetting: typeof import('./../components/ProTable/ColSetting.vue')['default']
     ConfigDrawer: typeof import('./../views/system/config/components/ConfigDrawer.vue')['default']
     copy: typeof import('./../views/system/user/components/AddRoleDialog copy.vue')['default']
@@ -83,6 +84,7 @@ declare module 'vue' {
     OrgDrawer: typeof import('./../views/system/org/components/OrgDrawer.vue')['default']
     OrgForm: typeof import('./../views/system/org/components/orgForm.vue')['default']
     Pagination: typeof import('./../components/ProTable/Pagination.vue')['default']
+    PasswordDialog: typeof import('./../views/system/user/components/PasswordDialog.vue')['default']
     PermissionsDrawer: typeof import('./../views/system/role/components/PermissionsDrawer.vue')['default']
     PositionDrawer: typeof import('./../views/system/position/components/PositionDrawer.vue')['default']
     ProTable: typeof import('./../components/ProTable/index.vue')['default']

+ 1 - 0
src/types/auto-imports.d.ts

@@ -339,6 +339,7 @@ declare module 'vue' {
   interface ComponentCustomProperties {
     readonly EffectScope: UnwrapRef<(typeof import('vue'))['EffectScope']>
     readonly ElMessage: UnwrapRef<(typeof import('element-plus/es'))['ElMessage']>
+    readonly ElMessageBox: UnwrapRef<(typeof import('element-plus/es'))['ElMessageBox']>
     readonly acceptHMRUpdate: UnwrapRef<(typeof import('pinia'))['acceptHMRUpdate']>
     readonly asyncComputed: UnwrapRef<(typeof import('@vueuse/core'))['asyncComputed']>
     readonly autoResetRef: UnwrapRef<(typeof import('@vueuse/core'))['autoResetRef']>

+ 3 - 2
src/views/login/index.vue

@@ -5,10 +5,11 @@
       <div class="login-form">
         <el-form ref="loginFormRef" :model="loginForm" :rules="loginRules" size="large" label-position="top">
           <el-form-item prop="account" label="账号">
-            <el-input v-model="loginForm.account" placeholder="账号: superadmin"> </el-input>
+            <el-input v-model="loginForm.account" placeholder="账号: superadmin" clearable> </el-input>
           </el-form-item>
           <el-form-item prop="password" style="margin-bottom: 0" label="密码">
-            <el-input v-model="loginForm.password" type="password" placeholder="密码: admin123" autocomplete="new-password"> </el-input>
+            <el-input v-model="loginForm.password" type="password" placeholder="密码: admin123" autocomplete="new-password" show-password clearable>
+            </el-input>
           </el-form-item>
         </el-form>
         <div class="login-btn">

+ 92 - 0
src/views/system/user/components/PasswordDialog.vue

@@ -0,0 +1,92 @@
+<template>
+  <el-dialog v-model="visible" append-to-body title="修改密码" width="500px" draggable>
+    <el-form ref="ruleFormRef" label-width="100px" label-suffix=" :" :rules="rules" :model="formData" @submit.enter.prevent="handleSubmit">
+      <el-form-item label="原密码" prop="oldPassword">
+        <el-input v-model="formData.oldPassword" type="password" placeholder="请填写原密码" show-password clearable />
+      </el-form-item>
+      <el-form-item label="新密码" prop="password">
+        <el-input v-model="formData.password" type="password" placeholder="请填写新密码" show-password clearable />
+      </el-form-item>
+      <el-form-item label="确认新密码" prop="newPwdConfirm">
+        <el-input v-model="formData.newPwdConfirm" type="password" placeholder="请填写确认新密码" show-password clearable />
+      </el-form-item>
+    </el-form>
+    <template #footer>
+      <span class="dialog-footer">
+        <el-button @click="visible = false">取消</el-button>
+        <el-button type="primary" @click="handleSubmit">确认</el-button>
+      </span>
+    </template>
+  </el-dialog>
+</template>
+
+<script setup lang="ts" name="PasswordDialog">
+import UserApi from '@/api/module/system/user'
+import { LOGIN_URL } from '@/constants'
+import { ResultEnum } from '@/enums/HttpEnum'
+import router from '@/router'
+import { useUserStore } from '@/stores'
+const validateOldNewPassword = (rule: any, value: any, callback: (error?: string | Error) => void) => {
+  if (value === formData.value.oldPassword) {
+    callback(new Error('旧密码和新密码不能相同'))
+    return
+  }
+  callback()
+}
+
+const validateConfirmPassword = (rule: any, value: any, callback: (error?: string | Error) => void) => {
+  if (value !== formData.value.password) {
+    callback(new Error('新密码和确认密码不一致'))
+    return
+  }
+  callback()
+}
+
+const rules = reactive({
+  oldPassword: [{ required: true, message: '请填写原密码' }],
+  password: [
+    { required: true, message: '请填写新密码' },
+    { validator: validateOldNewPassword, trigger: 'blur' }
+  ],
+  newPwdConfirm: [
+    { required: true, message: '请填写确认新密码' },
+    { validator: validateConfirmPassword, trigger: 'blur' }
+  ]
+})
+
+const formData = ref({
+  oldPassword: '',
+  password: '',
+  newPwdConfirm: ''
+})
+
+const ruleFormRef = ref()
+const handleSubmit = () => {
+  ruleFormRef.value?.validate(async (valid: boolean) => {
+    if (!valid) return
+    try {
+      const { code } = await UserApi.modifyPassword({ oldPassword: formData.value.oldPassword, password: formData.value.password })
+      if (code === ResultEnum.SUCCESS) {
+        ElMessage.success({ message: `修改密码成功,请重新登录!` })
+        visible.value = false
+        useUserStore().clear()
+        router.replace(LOGIN_URL)
+      }
+    } catch (error) {
+      console.log(error)
+    }
+  })
+}
+
+const visible = ref(false)
+const openDialog = () => {
+  visible.value = true
+  formData.value = {
+    oldPassword: '',
+    password: '',
+    newPwdConfirm: ''
+  }
+}
+
+defineExpose({ openDialog })
+</script>

+ 7 - 3
src/views/system/user/index.vue

@@ -22,8 +22,8 @@
         <template #operation="{ row }">
           <div class="operation-group">
             <el-button type="primary" link icon="EditPen" @click="openDrawer('编辑', row)"> 编辑 </el-button>
-            <el-button v-if="row.userId !== '1'" type="primary" link icon="Delete" @click="deleteRow(row)"> 删除 </el-button>
-            <div>
+            <el-button v-if="row.isLock !== '1'" type="primary" link icon="Delete" @click="deleteRow(row)"> 删除 </el-button>
+            <div class="group" v-if="row.isLock !== '1'">
               <el-dropdown trigger="click">
                 <el-button type="primary" link icon="DArrowRight"> 更多 </el-button>
                 <template #dropdown>
@@ -69,7 +69,7 @@ const changeTree = (val: string) => {
 
 // 表格配置项
 const columns: ColumnProps<UserVO>[] = [
-  { type: 'selection', width: 60, selectable: row => row.userId !== '1' },
+  { type: 'selection', width: 60, selectable: row => row.isLock !== '1' },
   { prop: 'account', label: '账号', width: 130 },
   { prop: 'userName', label: '姓名', width: 120 },
   { prop: 'nickName', label: '昵称', width: 120 },
@@ -157,6 +157,10 @@ const openDialog = (title: string, row: Partial<UserBO> = {}) => {
 .operation-group {
   display: flex;
   justify-content: center;
+  .group {
+    display: flex;
+    justify-content: center;
+  }
 }
 .operation-group > * {
   margin-left: 8px;