|
|
@ -18,6 +18,11 @@ |
|
|
|
<Avatar :src="userInfo.headImg" :alt="userInfo.name">{{ userInfo.name }}</Avatar> |
|
|
|
<template #overlay> |
|
|
|
<Menu> |
|
|
|
<Menu.Item v-if="userInfo.id"> |
|
|
|
<div class="flex items-center" @click="showProfile"> |
|
|
|
<user-outlined /> 个人中心 |
|
|
|
</div> |
|
|
|
</Menu.Item> |
|
|
|
<Menu.Item> |
|
|
|
<div v-if="userInfo.id" class="flex items-center" @click.prevent="doLogout"> |
|
|
|
<poweroff-outlined /> {{ $t('layout.header.dropdownItemLoginOut') }} |
|
|
@ -29,6 +34,59 @@ |
|
|
|
</Menu> |
|
|
|
</template> |
|
|
|
</Dropdown> |
|
|
|
|
|
|
|
<!-- 添加个人中心抽屉 --> |
|
|
|
<a-drawer |
|
|
|
v-model:visible="profileVisible" |
|
|
|
title="个人中心" |
|
|
|
placement="right" |
|
|
|
width="500" |
|
|
|
:closable="true" |
|
|
|
class="profile-drawer" |
|
|
|
> |
|
|
|
<div class="profile-content"> |
|
|
|
<!-- 头部背景区域 --> |
|
|
|
<div class="profile-header"> |
|
|
|
<div class="avatar-wrapper"> |
|
|
|
<a-avatar :size="100" :src="userInfo.headImg"> |
|
|
|
{{ userInfo.name?.charAt(0) }} |
|
|
|
</a-avatar> |
|
|
|
<h2>{{ userInfo.name }}</h2> |
|
|
|
<div class="role-tag"> |
|
|
|
<crown-outlined v-if="userInfo.isAdmin === 1" /> |
|
|
|
<user-outlined v-else /> |
|
|
|
<span>{{ userInfo.isAdmin === 1 ? '系统管理员' : '普通用户' }}</span> |
|
|
|
</div> |
|
|
|
</div> |
|
|
|
</div> |
|
|
|
|
|
|
|
<!-- 信息卡片 --> |
|
|
|
<a-card class="info-card" :bordered="false"> |
|
|
|
<template #title> <profile-outlined /> 基本信息 </template> |
|
|
|
<a-descriptions :column="1"> |
|
|
|
<a-descriptions-item label="用户名"> |
|
|
|
<user-outlined class="info-icon" /> {{ userInfo.name }} |
|
|
|
</a-descriptions-item> |
|
|
|
<a-descriptions-item label="手机号"> |
|
|
|
<mobile-outlined class="info-icon" /> {{ userInfo.phone || '未设置' }} |
|
|
|
</a-descriptions-item> |
|
|
|
<a-descriptions-item label="邮箱"> |
|
|
|
<mail-outlined class="info-icon" /> {{ userInfo.email || '未设置' }} |
|
|
|
</a-descriptions-item> |
|
|
|
<a-descriptions-item label="注册时间"> |
|
|
|
<calendar-outlined class="info-icon" /> {{ userInfo.createTime }} |
|
|
|
</a-descriptions-item> |
|
|
|
</a-descriptions> |
|
|
|
|
|
|
|
<div class="action-buttons"> |
|
|
|
<a-button type="primary" @click="handleResetPassword"> |
|
|
|
<template #icon><edit-outlined /></template> |
|
|
|
修改密码 |
|
|
|
</a-button> |
|
|
|
</div> |
|
|
|
</a-card> |
|
|
|
</div> |
|
|
|
</a-drawer> |
|
|
|
</a-space> |
|
|
|
</div> |
|
|
|
</div> |
|
|
@ -123,6 +181,29 @@ |
|
|
|
</div> |
|
|
|
</div> |
|
|
|
</div> |
|
|
|
|
|
|
|
<a-modal |
|
|
|
v-model:visible="passwordModalVisible" |
|
|
|
title="修改密码" |
|
|
|
@ok="handlePasswordSubmit" |
|
|
|
@cancel="handlePasswordCancel" |
|
|
|
:confirmLoading="confirmLoading" |
|
|
|
> |
|
|
|
<a-form |
|
|
|
ref="passwordFormRef" |
|
|
|
:model="passwordForm" |
|
|
|
:rules="passwordRules" |
|
|
|
:label-col="{ span: 6 }" |
|
|
|
:wrapper-col="{ span: 16 }" |
|
|
|
> |
|
|
|
<a-form-item label="原密码" name="password"> |
|
|
|
<a-input-password v-model:value="passwordForm.password" placeholder="请输入原密码" /> |
|
|
|
</a-form-item> |
|
|
|
<a-form-item label="新密码" name="newPassword"> |
|
|
|
<a-input-password v-model:value="passwordForm.newPassword" placeholder="请输入新密码" /> |
|
|
|
</a-form-item> |
|
|
|
</a-form> |
|
|
|
</a-modal> |
|
|
|
</template> |
|
|
|
|
|
|
|
<script setup lang="tsx"> |
|
|
@ -137,6 +218,10 @@ |
|
|
|
BookOutlined, |
|
|
|
PoweroffOutlined, |
|
|
|
QuestionCircleOutlined, |
|
|
|
CrownOutlined, |
|
|
|
ProfileOutlined, |
|
|
|
MobileOutlined, |
|
|
|
CalendarOutlined, |
|
|
|
} from '@ant-design/icons-vue'; |
|
|
|
import { useRouter, useRoute } from 'vue-router'; |
|
|
|
import { Avatar, Menu, Dropdown, Modal, message } from 'ant-design-vue'; |
|
|
@ -146,7 +231,9 @@ |
|
|
|
import { fetchKnowledgeBaseList, findOneById } from '@/api/knowledgeBase'; |
|
|
|
import { DictEnum } from '@/enums/dictEnum'; |
|
|
|
import { getDictionaryByTypeName } from '@/utils/dict'; |
|
|
|
|
|
|
|
import { EditOutlined } from '@ant-design/icons-vue'; // 添加图标引入 |
|
|
|
import { resetPwd } from '@/api/login'; |
|
|
|
// 初始化数据 |
|
|
|
const router = useRouter(); |
|
|
|
const route = useRoute(); |
|
|
|
|
|
|
@ -174,6 +261,72 @@ |
|
|
|
]); |
|
|
|
|
|
|
|
const searchResults = ref<API.KnowledgeBaseType[]>([]); |
|
|
|
|
|
|
|
// 添加个人中心相关状态和方法 |
|
|
|
const profileVisible = ref(false); |
|
|
|
|
|
|
|
// 添加在其他 ref 声明附近 |
|
|
|
const passwordModalVisible = ref(false); |
|
|
|
const confirmLoading = ref(false); |
|
|
|
const passwordFormRef = ref(); |
|
|
|
const passwordForm = ref({ |
|
|
|
password: '', |
|
|
|
newPassword: '', |
|
|
|
}); |
|
|
|
|
|
|
|
// 表单验证规则 |
|
|
|
const passwordRules = { |
|
|
|
password: [{ required: true, message: '请输入原密码', trigger: 'blur' }], |
|
|
|
newPassword: [{ required: true, message: '请输入新密码', trigger: 'blur' }], |
|
|
|
}; |
|
|
|
|
|
|
|
// 修改 handleResetPassword 方法 |
|
|
|
const handleResetPassword = () => { |
|
|
|
passwordModalVisible.value = true; |
|
|
|
}; |
|
|
|
|
|
|
|
// 添加处理提交方法 |
|
|
|
const handlePasswordSubmit = async () => { |
|
|
|
try { |
|
|
|
await passwordFormRef.value.validate(); |
|
|
|
confirmLoading.value = true; |
|
|
|
|
|
|
|
// TODO: 调用修改密码API |
|
|
|
const res = await resetPwd(passwordForm.value); |
|
|
|
|
|
|
|
message.success('密码修改成功'); |
|
|
|
passwordModalVisible.value = false; |
|
|
|
passwordForm.value = { |
|
|
|
password: '', |
|
|
|
newPassword: '', |
|
|
|
}; |
|
|
|
profileVisible.value = false; |
|
|
|
await userStore.logout(); |
|
|
|
|
|
|
|
keepAliveStore.clear(); |
|
|
|
// 移除标签页 |
|
|
|
localStorage.clear(); |
|
|
|
await nextTick(); |
|
|
|
userInfo.value = { |
|
|
|
name: '访客', |
|
|
|
}; |
|
|
|
} catch (error) { |
|
|
|
console.error('表单验证失败:', error); |
|
|
|
} finally { |
|
|
|
confirmLoading.value = false; |
|
|
|
} |
|
|
|
}; |
|
|
|
|
|
|
|
// 添加取消方法 |
|
|
|
const handlePasswordCancel = () => { |
|
|
|
passwordModalVisible.value = false; |
|
|
|
passwordFormRef.value?.resetFields(); |
|
|
|
}; |
|
|
|
|
|
|
|
const showProfile = () => { |
|
|
|
profileVisible.value = true; |
|
|
|
}; |
|
|
|
|
|
|
|
let searchTimer: NodeJS.Timeout | null = null; |
|
|
|
|
|
|
|
// 处理输入事件 |
|
|
@ -288,6 +441,105 @@ |
|
|
|
</script> |
|
|
|
|
|
|
|
<style lang="less" scoped> |
|
|
|
.profile-drawer { |
|
|
|
:deep(.ant-drawer-header) { |
|
|
|
border-bottom: none; |
|
|
|
padding: 16px 24px; |
|
|
|
|
|
|
|
.ant-drawer-title { |
|
|
|
font-size: 18px; |
|
|
|
font-weight: 600; |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
.profile-content { |
|
|
|
.profile-header { |
|
|
|
margin: -24px -24px 24px; |
|
|
|
padding: 40px 24px; |
|
|
|
background: linear-gradient(135deg, #1890ff 0%, #096dd9 100%); |
|
|
|
// border-bottom-left-radius: 30px; |
|
|
|
|
|
|
|
.avatar-wrapper { |
|
|
|
text-align: center; |
|
|
|
|
|
|
|
:deep(.ant-avatar) { |
|
|
|
border: 4px solid rgba(255, 255, 255, 0.2); |
|
|
|
margin-bottom: 16px; |
|
|
|
} |
|
|
|
|
|
|
|
h2 { |
|
|
|
color: white; |
|
|
|
margin: 8px 0; |
|
|
|
font-size: 24px; |
|
|
|
} |
|
|
|
|
|
|
|
.role-tag { |
|
|
|
display: inline-flex; |
|
|
|
align-items: center; |
|
|
|
background: rgba(255, 255, 255, 0.1); |
|
|
|
padding: 4px 12px; |
|
|
|
border-radius: 20px; |
|
|
|
color: white; |
|
|
|
font-size: 14px; |
|
|
|
|
|
|
|
.anticon { |
|
|
|
margin-right: 6px; |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
.info-card { |
|
|
|
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.05); |
|
|
|
border-radius: 12px; |
|
|
|
|
|
|
|
:deep(.ant-card-head) { |
|
|
|
border-bottom: 1px solid #f0f0f0; |
|
|
|
padding: 0 24px; |
|
|
|
|
|
|
|
.ant-card-head-title { |
|
|
|
font-size: 16px; |
|
|
|
font-weight: 500; |
|
|
|
|
|
|
|
.anticon { |
|
|
|
margin-right: 8px; |
|
|
|
color: #1890ff; |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
.ant-descriptions-item { |
|
|
|
padding: 12px 0; |
|
|
|
|
|
|
|
.ant-descriptions-item-label { |
|
|
|
color: #666; |
|
|
|
width: 80px; |
|
|
|
} |
|
|
|
|
|
|
|
.info-icon { |
|
|
|
margin-right: 8px; |
|
|
|
color: #1890ff; |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
.action-buttons { |
|
|
|
text-align: center; |
|
|
|
margin-top: 24px; |
|
|
|
|
|
|
|
.ant-btn { |
|
|
|
min-width: 120px; |
|
|
|
border-radius: 20px; |
|
|
|
height: 36px; |
|
|
|
|
|
|
|
&:hover { |
|
|
|
transform: translateY(-2px); |
|
|
|
box-shadow: 0 4px 12px rgba(24, 144, 255, 0.15); |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
.entrance-container { |
|
|
|
min-height: 100vh; |
|
|
|
background: linear-gradient(135deg, #f5f7fa 0%, #c3cfe2 100%); |
|
|
|