mirror of
https://github.com/chansee97/nova-admin.git
synced 2025-09-18 03:39:58 +08:00
feat: 完善个人中心
This commit is contained in:
parent
c168e92135
commit
41d4e9a0d5
@ -48,3 +48,14 @@ export function updateUser(id: number, data: Partial<Entity.User>) {
|
||||
export function deleteUser(id: number) {
|
||||
return request.Delete<Api.Response<boolean>>(`/user/${id}`)
|
||||
}
|
||||
/**
|
||||
* 更新用户密码
|
||||
* Patch /user/password
|
||||
*/
|
||||
export interface UserPasswordParams {
|
||||
oldPassword: string
|
||||
newPassword: string
|
||||
}
|
||||
export function updateUserPassword(data: UserPasswordParams) {
|
||||
return request.Patch<Api.Response<boolean>>(`/user/password`, data)
|
||||
}
|
||||
|
@ -0,0 +1,43 @@
|
||||
<script setup lang="ts">
|
||||
// 通知设置
|
||||
const settings = ref({
|
||||
systemUpdateEmail: true,
|
||||
securityAlertEmail: true,
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="space-y-4">
|
||||
<!-- 邮件通知 -->
|
||||
<n-card title="邮件通知" size="small">
|
||||
<n-list>
|
||||
<n-list-item>
|
||||
<n-thing>
|
||||
<template #header>
|
||||
系统更新通知
|
||||
</template>
|
||||
<template #description>
|
||||
系统有重要更新时发送邮件通知
|
||||
</template>
|
||||
</n-thing>
|
||||
<template #suffix>
|
||||
<n-switch v-model:value="settings.systemUpdateEmail" />
|
||||
</template>
|
||||
</n-list-item>
|
||||
<n-list-item>
|
||||
<n-thing>
|
||||
<template #header>
|
||||
安全警报
|
||||
</template>
|
||||
<template #description>
|
||||
检测到安全威胁时立即发送邮件
|
||||
</template>
|
||||
</n-thing>
|
||||
<template #suffix>
|
||||
<n-switch v-model:value="settings.securityAlertEmail" />
|
||||
</template>
|
||||
</n-list-item>
|
||||
</n-list>
|
||||
</n-card>
|
||||
</div>
|
||||
</template>
|
@ -61,7 +61,6 @@ async function updateUserInfo() {
|
||||
:model="formData"
|
||||
label-placement="left"
|
||||
label-width="80px"
|
||||
class="max-w-400px"
|
||||
>
|
||||
<n-grid :cols="1">
|
||||
<n-grid-item>
|
||||
|
@ -0,0 +1,47 @@
|
||||
<script setup lang="ts">
|
||||
// 偏好设置
|
||||
const preferences = ref({
|
||||
aiAssistant: false,
|
||||
predictiveLoading: false,
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="space-y-4">
|
||||
<!-- 实验性功能 -->
|
||||
<n-card title="实验性功能" size="small">
|
||||
<n-alert type="warning" style="margin-bottom: 16px;">
|
||||
以下功能仍在测试阶段,可能会影响系统稳定性
|
||||
</n-alert>
|
||||
|
||||
<n-list>
|
||||
<n-list-item>
|
||||
<n-thing>
|
||||
<template #header>
|
||||
AI 助手
|
||||
</template>
|
||||
<template #description>
|
||||
启用智能助手功能,提供操作建议
|
||||
</template>
|
||||
</n-thing>
|
||||
<template #suffix>
|
||||
<n-switch v-model:value="preferences.aiAssistant" />
|
||||
</template>
|
||||
</n-list-item>
|
||||
<n-list-item>
|
||||
<n-thing>
|
||||
<template #header>
|
||||
预测性加载
|
||||
</template>
|
||||
<template #description>
|
||||
根据使用习惯预加载可能需要的内容
|
||||
</template>
|
||||
</n-thing>
|
||||
<template #suffix>
|
||||
<n-switch v-model:value="preferences.predictiveLoading" />
|
||||
</template>
|
||||
</n-list-item>
|
||||
</n-list>
|
||||
</n-card>
|
||||
</div>
|
||||
</template>
|
162
src/views/build-in/user-center/components/SecuritySettings.vue
Normal file
162
src/views/build-in/user-center/components/SecuritySettings.vue
Normal file
@ -0,0 +1,162 @@
|
||||
<script setup lang="ts">
|
||||
import { ref } from 'vue'
|
||||
import type { FormInst, FormRules } from 'naive-ui'
|
||||
import { useMessage } from 'naive-ui'
|
||||
import { updateUserPassword } from '@/api/system/user'
|
||||
|
||||
const message = useMessage()
|
||||
const formRef = ref<FormInst | null>(null)
|
||||
const loading = ref(false)
|
||||
|
||||
// 密码表单
|
||||
const passwordForm = ref({
|
||||
oldPassword: '',
|
||||
newPassword: '',
|
||||
confirmPassword: '',
|
||||
})
|
||||
|
||||
// 安全设置
|
||||
const loginNotificationEnabled = ref(true)
|
||||
|
||||
// 表单验证规则
|
||||
const passwordRules: FormRules = {
|
||||
oldPassword: [
|
||||
{ required: true, message: '请输入当前密码', trigger: 'blur' },
|
||||
],
|
||||
newPassword: [
|
||||
{ required: true, message: '请输入新密码', trigger: 'blur' },
|
||||
{ min: 6, message: '密码长度不能少于6位', trigger: 'blur' },
|
||||
],
|
||||
confirmPassword: [
|
||||
{ required: true, message: '请确认新密码', trigger: 'blur' },
|
||||
{
|
||||
validator: (rule, value) => {
|
||||
return value === passwordForm.value.newPassword
|
||||
},
|
||||
message: '两次输入的密码不一致',
|
||||
trigger: 'blur',
|
||||
},
|
||||
],
|
||||
}
|
||||
|
||||
// 更新密码
|
||||
async function handleUpdatePassword() {
|
||||
try {
|
||||
await formRef.value?.validate()
|
||||
loading.value = true
|
||||
|
||||
await updateUserPassword({
|
||||
oldPassword: passwordForm.value.oldPassword,
|
||||
newPassword: passwordForm.value.newPassword,
|
||||
})
|
||||
|
||||
message.success('密码更新成功')
|
||||
resetForm()
|
||||
}
|
||||
finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
// 重置表单
|
||||
function resetForm() {
|
||||
passwordForm.value = {
|
||||
oldPassword: '',
|
||||
newPassword: '',
|
||||
confirmPassword: '',
|
||||
}
|
||||
formRef.value?.restoreValidation()
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="space-y-4">
|
||||
<!-- 密码更新 -->
|
||||
<n-card title="密码更新" size="small">
|
||||
<n-form
|
||||
ref="formRef"
|
||||
:model="passwordForm"
|
||||
:rules="passwordRules"
|
||||
label-placement="left"
|
||||
label-width="120px"
|
||||
require-mark-placement="right-hanging"
|
||||
>
|
||||
<n-form-item label="当前密码" path="oldPassword">
|
||||
<n-input
|
||||
v-model:value="passwordForm.oldPassword"
|
||||
type="password"
|
||||
placeholder="请输入当前密码"
|
||||
show-password-on="click"
|
||||
/>
|
||||
</n-form-item>
|
||||
|
||||
<n-form-item label="新密码" path="newPassword">
|
||||
<n-input
|
||||
v-model:value="passwordForm.newPassword"
|
||||
type="password"
|
||||
placeholder="请输入新密码"
|
||||
show-password-on="click"
|
||||
/>
|
||||
</n-form-item>
|
||||
|
||||
<n-form-item label="确认新密码" path="confirmPassword">
|
||||
<n-input
|
||||
v-model:value="passwordForm.confirmPassword"
|
||||
type="password"
|
||||
placeholder="请再次输入新密码"
|
||||
show-password-on="click"
|
||||
/>
|
||||
</n-form-item>
|
||||
|
||||
<n-form-item label=" ">
|
||||
<n-space>
|
||||
<n-button
|
||||
type="primary"
|
||||
:loading="loading"
|
||||
@click="handleUpdatePassword"
|
||||
>
|
||||
更新密码
|
||||
</n-button>
|
||||
<n-button @click="resetForm">
|
||||
重置
|
||||
</n-button>
|
||||
</n-space>
|
||||
</n-form-item>
|
||||
</n-form>
|
||||
</n-card>
|
||||
|
||||
<!-- 账户安全 -->
|
||||
<n-card title="账户安全" size="small">
|
||||
<n-list>
|
||||
<n-list-item>
|
||||
<n-thing>
|
||||
<template #header>
|
||||
登录设备管理
|
||||
</template>
|
||||
<template #description>
|
||||
管理已登录的设备,可以远程注销可疑设备
|
||||
</template>
|
||||
</n-thing>
|
||||
<template #suffix>
|
||||
<n-button size="small" secondary>
|
||||
查看设备
|
||||
</n-button>
|
||||
</template>
|
||||
</n-list-item>
|
||||
<n-list-item>
|
||||
<n-thing>
|
||||
<template #header>
|
||||
登录通知
|
||||
</template>
|
||||
<template #description>
|
||||
新设备登录时发送邮件通知
|
||||
</template>
|
||||
</n-thing>
|
||||
<template #suffix>
|
||||
<n-switch v-model:value="loginNotificationEnabled" />
|
||||
</template>
|
||||
</n-list-item>
|
||||
</n-list>
|
||||
</n-card>
|
||||
</div>
|
||||
</template>
|
@ -1,13 +1,65 @@
|
||||
<script setup lang="ts">
|
||||
import { onMounted, ref } from 'vue'
|
||||
import { computed, h, onMounted, ref } from 'vue'
|
||||
import { storeToRefs } from 'pinia'
|
||||
import { useAuthStore } from '@/store'
|
||||
import PersonalInfo from './components/PersonalInfo.vue'
|
||||
import SecuritySettings from './components/SecuritySettings.vue'
|
||||
import NotificationSettings from './components/NotificationSettings.vue'
|
||||
import PreferenceSettings from './components/PreferenceSettings.vue'
|
||||
import IconUser from '~icons/icon-park-outline/user'
|
||||
import IconLock from '~icons/icon-park-outline/lock'
|
||||
import IconRemind from '~icons/icon-park-outline/remind'
|
||||
import IconSetting from '~icons/icon-park-outline/setting-one'
|
||||
|
||||
const authStore = useAuthStore()
|
||||
const { userInfo } = storeToRefs(authStore)
|
||||
|
||||
// 当前选中的标签页
|
||||
const activeTab = ref('profile')
|
||||
// 当前选中的菜单项
|
||||
const activeMenu = ref('profile')
|
||||
|
||||
// 页面配置 - 统一管理菜单项、标题和组件
|
||||
const pageConfig = [
|
||||
{
|
||||
key: 'profile',
|
||||
label: '个人信息',
|
||||
title: '个人信息',
|
||||
icon: IconUser,
|
||||
component: PersonalInfo,
|
||||
},
|
||||
{
|
||||
key: 'security',
|
||||
label: '安全设置',
|
||||
title: '安全设置',
|
||||
icon: IconLock,
|
||||
component: SecuritySettings,
|
||||
},
|
||||
{
|
||||
key: 'notification',
|
||||
label: '通知设置',
|
||||
title: '通知设置',
|
||||
icon: IconRemind,
|
||||
component: NotificationSettings,
|
||||
},
|
||||
{
|
||||
key: 'preference',
|
||||
label: '偏好设置',
|
||||
title: '偏好设置',
|
||||
icon: IconSetting,
|
||||
component: PreferenceSettings,
|
||||
},
|
||||
]
|
||||
|
||||
// 菜单选项 - 从配置中提取
|
||||
const menuOptions = pageConfig.map(({ key, label, icon }) => ({
|
||||
key,
|
||||
label,
|
||||
icon: () => h(icon),
|
||||
}))
|
||||
|
||||
// 当前页面配置 - 根据激活的菜单项获取
|
||||
const currentPageConfig = computed(() => {
|
||||
return pageConfig.find(config => config.key === activeMenu.value)
|
||||
})
|
||||
|
||||
// 获取问候语
|
||||
function getGreeting() {
|
||||
@ -34,83 +86,46 @@ onMounted(async () => {
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<n-space vertical>
|
||||
<!-- 欢迎标题 -->
|
||||
<n-card>
|
||||
<n-flex align="center" justify="space-between">
|
||||
<div>
|
||||
<n-h2>
|
||||
<n-card>
|
||||
<n-flex :wrap="false" style="height: 100%;">
|
||||
<!-- 左侧区域 -->
|
||||
<div class="w-[220px] border-r border-[var(--n-border-color)] flex flex-col">
|
||||
<!-- 左上:头像和招呼 -->
|
||||
<n-flex
|
||||
align="center" justify="center" vertical
|
||||
class="p-3 border-b border-[var(--n-border-color)]"
|
||||
:size="8"
|
||||
>
|
||||
<n-avatar
|
||||
round
|
||||
:size="80"
|
||||
:src="userInfo?.avatar || `https://api.dicebear.com/9.x/adventurer-neutral/svg?seed=${userInfo!.username}`"
|
||||
/>
|
||||
<n-h3 class="m-0">
|
||||
{{ getGreeting() }},{{ userInfo?.nickName || userInfo.username }}
|
||||
</n-h2>
|
||||
<n-p class="text-sm opacity-90 m-0">
|
||||
</n-h3>
|
||||
<n-text>
|
||||
欢迎使用个人中心
|
||||
</n-p>
|
||||
</div>
|
||||
<n-avatar
|
||||
round
|
||||
:size="64"
|
||||
:src="userInfo?.avatar || `https://api.dicebear.com/9.x/adventurer-neutral/svg?seed=${userInfo!.username}`"
|
||||
</n-text>
|
||||
</n-flex>
|
||||
|
||||
<!-- 左下:菜单 -->
|
||||
<n-menu
|
||||
v-model:value="activeMenu"
|
||||
class="flex-1"
|
||||
:options="menuOptions"
|
||||
:icon-size="16"
|
||||
/>
|
||||
</n-flex>
|
||||
</n-card>
|
||||
</div>
|
||||
|
||||
<!-- 主要内容区域 -->
|
||||
<n-card>
|
||||
<n-tabs
|
||||
v-model:value="activeTab"
|
||||
type="line"
|
||||
placement="left"
|
||||
tab-style="min-height: 30px;"
|
||||
pane-style="padding-left: 1rem;"
|
||||
>
|
||||
<n-tab-pane name="profile">
|
||||
<template #tab>
|
||||
<icon-park-outline-user class="mr-2" />
|
||||
个人信息
|
||||
</template>
|
||||
<n-h5>
|
||||
个人信息
|
||||
</n-h5>
|
||||
<n-divider />
|
||||
<PersonalInfo />
|
||||
</n-tab-pane>
|
||||
|
||||
<n-tab-pane name="security">
|
||||
<template #tab>
|
||||
<icon-park-outline-lock class="mr-2" />
|
||||
安全设置
|
||||
</template>
|
||||
<n-h5>
|
||||
安全设置
|
||||
</n-h5>
|
||||
<n-divider />
|
||||
<n-empty description="功能开发中..." />
|
||||
</n-tab-pane>
|
||||
|
||||
<n-tab-pane name="notification">
|
||||
<template #tab>
|
||||
<icon-park-outline-remind class="mr-2" />
|
||||
通知设置
|
||||
</template>
|
||||
<n-h5>
|
||||
通知设置
|
||||
</n-h5>
|
||||
<n-divider />
|
||||
<n-empty description="功能开发中..." />
|
||||
</n-tab-pane>
|
||||
|
||||
<n-tab-pane name="preference">
|
||||
<template #tab>
|
||||
<icon-park-outline-setting-one class="mr-2" />
|
||||
偏好设置
|
||||
</template>
|
||||
<n-h5>
|
||||
偏好设置
|
||||
</n-h5>
|
||||
<n-divider />
|
||||
<n-empty description="功能开发中..." />
|
||||
</n-tab-pane>
|
||||
</n-tabs>
|
||||
</n-card>
|
||||
</n-space>
|
||||
<!-- 右侧功能区域 -->
|
||||
<div class="flex-1 p-2">
|
||||
<n-h4 class="flex items-center gap-2">
|
||||
<component :is="currentPageConfig!.icon" /> {{ currentPageConfig!.title }}
|
||||
</n-h4>
|
||||
<n-divider />
|
||||
<component :is="currentPageConfig!.component" />
|
||||
</div>
|
||||
</n-flex>
|
||||
</n-card>
|
||||
</template>
|
||||
|
Loading…
x
Reference in New Issue
Block a user