2026-01-26 13:47:53 +08:00
|
|
|
|
<script setup lang="ts">
|
2026-03-23 11:41:09 +08:00
|
|
|
|
import { computed, onMounted, ref } from 'vue'
|
2026-02-04 15:41:51 +08:00
|
|
|
|
import { useRouter, useRoute } from 'vue-router'
|
2026-02-04 14:29:59 +08:00
|
|
|
|
import { ElMessageBox, ElMessage } from 'element-plus'
|
2026-03-23 11:41:09 +08:00
|
|
|
|
import { InfoFilled, SwitchButton, UserFilled, Lock, User, ArrowDown } from '@element-plus/icons-vue'
|
2026-02-04 14:29:59 +08:00
|
|
|
|
import { useUserStore } from '@/stores/user'
|
2026-04-17 12:48:30 +08:00
|
|
|
|
import { getMyProfile, changeMyPassword, updateMyEmail } from '@/api/auth'
|
2026-02-04 14:29:59 +08:00
|
|
|
|
|
|
|
|
|
|
const router = useRouter()
|
2026-03-23 11:41:09 +08:00
|
|
|
|
const route = useRoute()
|
2026-02-04 14:29:59 +08:00
|
|
|
|
const userStore = useUserStore()
|
|
|
|
|
|
|
2026-03-23 11:41:09 +08:00
|
|
|
|
// 计算属性:判断当前是否是登录页
|
2026-02-04 15:41:51 +08:00
|
|
|
|
const isLoginPage = computed(() => {
|
|
|
|
|
|
return route.path === '/login'
|
|
|
|
|
|
})
|
|
|
|
|
|
|
2026-02-26 16:21:35 +08:00
|
|
|
|
// 页面加载时刷新权限
|
2026-08-03 09:22:50 +08:00
|
|
|
|
onMounted(() => {
|
2026-02-26 16:21:35 +08:00
|
|
|
|
if (userStore.token) {
|
|
|
|
|
|
userStore.refreshUserPermissions()
|
|
|
|
|
|
}
|
2026-08-03 09:22:50 +08:00
|
|
|
|
// 当 Vue 根组件挂载完毕,确保 Dify 图标一定会被加载
|
2026-05-21 14:09:57 +08:00
|
|
|
|
if (typeof (window as any).initDifyChatbot === 'function') {
|
|
|
|
|
|
(window as any).initDifyChatbot()
|
|
|
|
|
|
}
|
2026-02-26 16:21:35 +08:00
|
|
|
|
})
|
|
|
|
|
|
|
2026-03-23 11:41:09 +08:00
|
|
|
|
// ================================================================
|
|
|
|
|
|
// 个人中心 / 修改密码
|
|
|
|
|
|
// ================================================================
|
|
|
|
|
|
const profileDialogVisible = ref(false)
|
|
|
|
|
|
const profileLoading = ref(false)
|
|
|
|
|
|
const passwordLoading = ref(false)
|
|
|
|
|
|
|
|
|
|
|
|
interface ProfileData {
|
|
|
|
|
|
id: number
|
|
|
|
|
|
username: string
|
|
|
|
|
|
display_name: string
|
|
|
|
|
|
department: string
|
2026-04-17 12:48:30 +08:00
|
|
|
|
email: string
|
2026-03-23 11:41:09 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
const profileForm = ref<ProfileData>({
|
|
|
|
|
|
id: 0,
|
|
|
|
|
|
username: '',
|
|
|
|
|
|
display_name: '',
|
2026-04-17 12:48:30 +08:00
|
|
|
|
department: '',
|
|
|
|
|
|
email: ''
|
2026-03-23 11:41:09 +08:00
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
|
|
const passwordForm = ref({
|
|
|
|
|
|
new_password: '',
|
|
|
|
|
|
confirm_password: ''
|
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
|
|
const passwordFormRef = ref()
|
|
|
|
|
|
|
2026-04-17 12:48:30 +08:00
|
|
|
|
// ================================================================
|
|
|
|
|
|
// 绑定/修改邮箱
|
|
|
|
|
|
// ================================================================
|
|
|
|
|
|
const emailDialogVisible = ref(false)
|
|
|
|
|
|
const emailLoading = ref(false)
|
|
|
|
|
|
const emailFormRef = ref()
|
|
|
|
|
|
|
|
|
|
|
|
interface EmailForm {
|
|
|
|
|
|
email: string
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
const emailForm = ref<EmailForm>({
|
|
|
|
|
|
email: ''
|
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
|
|
const emailRules = {
|
|
|
|
|
|
email: [
|
|
|
|
|
|
{ required: true, message: '请输入邮箱地址', trigger: 'blur' },
|
|
|
|
|
|
{ type: 'email', message: '请输入正确的邮箱格式', trigger: ['blur', 'change'] }
|
|
|
|
|
|
]
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 打开邮箱弹窗
|
|
|
|
|
|
const openEmailDialog = () => {
|
|
|
|
|
|
emailForm.value.email = profileForm.value.email || ''
|
|
|
|
|
|
emailDialogVisible.value = true
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 提交邮箱修改
|
|
|
|
|
|
const submitEmailUpdate = async () => {
|
|
|
|
|
|
const formRef = emailFormRef.value
|
|
|
|
|
|
if (!formRef) return
|
|
|
|
|
|
|
|
|
|
|
|
await formRef.validate(async (valid: boolean) => {
|
|
|
|
|
|
if (valid) {
|
|
|
|
|
|
emailLoading.value = true
|
|
|
|
|
|
try {
|
|
|
|
|
|
await updateMyEmail({ email: emailForm.value.email })
|
|
|
|
|
|
ElMessage.success('邮箱绑定成功')
|
|
|
|
|
|
emailDialogVisible.value = false
|
|
|
|
|
|
// 刷新个人资料
|
|
|
|
|
|
openProfileDialog()
|
|
|
|
|
|
} catch (e: any) {
|
|
|
|
|
|
ElMessage.error(e?.response?.data?.msg || e?.message || '绑定失败')
|
|
|
|
|
|
} finally {
|
|
|
|
|
|
emailLoading.value = false
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
})
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 重置表单
|
|
|
|
|
|
const resetEmailForm = () => {
|
|
|
|
|
|
emailFormRef.value?.resetFields()
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-03-23 11:41:09 +08:00
|
|
|
|
// 打开个人中心弹窗
|
|
|
|
|
|
const openProfileDialog = async () => {
|
|
|
|
|
|
profileDialogVisible.value = true
|
|
|
|
|
|
profileLoading.value = true
|
2026-03-23 11:52:30 +08:00
|
|
|
|
passwordForm.value = { new_password: '', confirm_password: '' }
|
2026-03-23 11:41:09 +08:00
|
|
|
|
|
|
|
|
|
|
try {
|
2026-03-23 11:52:30 +08:00
|
|
|
|
// 【修复】axios 拦截器已解包 response.data,
|
|
|
|
|
|
// res 本身已是 { msg, data: { id, username, display_name, department } },
|
|
|
|
|
|
// 故直接取 res.data 即可,多跳一层 res.data.data 会取到 undefined
|
2026-03-23 11:41:09 +08:00
|
|
|
|
const res: any = await getMyProfile()
|
2026-03-23 11:52:30 +08:00
|
|
|
|
const payload = res.data || res
|
|
|
|
|
|
if (payload && payload.data) {
|
|
|
|
|
|
profileForm.value = payload.data
|
|
|
|
|
|
} else if (payload && payload.username) {
|
|
|
|
|
|
// 兜底:响应已经是平铺结构
|
|
|
|
|
|
profileForm.value = payload
|
2026-03-23 11:41:09 +08:00
|
|
|
|
}
|
|
|
|
|
|
} catch (e: any) {
|
|
|
|
|
|
ElMessage.error(e?.message || '获取个人资料失败')
|
|
|
|
|
|
} finally {
|
|
|
|
|
|
profileLoading.value = false
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-03-23 11:52:30 +08:00
|
|
|
|
// 提交修改密码(无需旧密码,JWT 已证明身份)
|
2026-03-23 11:41:09 +08:00
|
|
|
|
const submitPasswordChange = async () => {
|
2026-03-23 11:52:30 +08:00
|
|
|
|
const { new_password, confirm_password } = passwordForm.value
|
2026-03-23 11:41:09 +08:00
|
|
|
|
|
2026-03-23 11:52:30 +08:00
|
|
|
|
if (!new_password || !confirm_password) {
|
|
|
|
|
|
ElMessage.warning('请填写新密码和确认密码')
|
2026-03-23 11:41:09 +08:00
|
|
|
|
return
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if (new_password.length < 6) {
|
|
|
|
|
|
ElMessage.warning('新密码长度不能少于6位')
|
|
|
|
|
|
return
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if (new_password !== confirm_password) {
|
|
|
|
|
|
ElMessage.warning('新密码与确认密码不一致')
|
|
|
|
|
|
return
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
passwordLoading.value = true
|
|
|
|
|
|
try {
|
|
|
|
|
|
const res: any = await changeMyPassword({
|
|
|
|
|
|
new_password,
|
|
|
|
|
|
confirm_password
|
|
|
|
|
|
})
|
2026-03-23 11:52:30 +08:00
|
|
|
|
const msg = res?.msg || '修改成功'
|
2026-03-23 11:41:09 +08:00
|
|
|
|
ElMessage.success(msg)
|
2026-03-23 11:52:30 +08:00
|
|
|
|
profileDialogVisible.value = false
|
|
|
|
|
|
setTimeout(() => {
|
|
|
|
|
|
userStore.logout()
|
|
|
|
|
|
router.replace('/login')
|
|
|
|
|
|
}, 1500)
|
2026-03-23 11:41:09 +08:00
|
|
|
|
} catch (e: any) {
|
|
|
|
|
|
const errMsg = e?.response?.data?.msg || e?.message || '修改失败'
|
|
|
|
|
|
ElMessage.error(errMsg)
|
|
|
|
|
|
} finally {
|
|
|
|
|
|
passwordLoading.value = false
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// ================================================================
|
|
|
|
|
|
// 退出登录
|
|
|
|
|
|
// ================================================================
|
2026-02-04 14:29:59 +08:00
|
|
|
|
const handleLogout = () => {
|
|
|
|
|
|
ElMessageBox.confirm(
|
|
|
|
|
|
'确定要退出系统吗?',
|
|
|
|
|
|
'提示',
|
|
|
|
|
|
{
|
|
|
|
|
|
confirmButtonText: '确定退出',
|
|
|
|
|
|
cancelButtonText: '取消',
|
|
|
|
|
|
type: 'warning',
|
|
|
|
|
|
}
|
|
|
|
|
|
)
|
|
|
|
|
|
.then(async () => {
|
|
|
|
|
|
userStore.logout()
|
2026-03-23 11:41:09 +08:00
|
|
|
|
ElMessage({ type: 'success', message: '已安全退出' })
|
2026-05-20 09:09:33 +08:00
|
|
|
|
// 直接原生跳转,重置一切
|
|
|
|
|
|
window.location.href = '/login'
|
2026-02-04 14:29:59 +08:00
|
|
|
|
})
|
2026-03-23 11:41:09 +08:00
|
|
|
|
.catch(() => {})
|
2026-02-04 14:29:59 +08:00
|
|
|
|
}
|
2026-01-26 13:47:53 +08:00
|
|
|
|
</script>
|
|
|
|
|
|
|
|
|
|
|
|
<template>
|
2026-01-26 17:00:12 +08:00
|
|
|
|
<div class="app-wrapper">
|
2026-02-04 15:41:51 +08:00
|
|
|
|
<header v-if="!isLoginPage" class="app-header">
|
2026-01-26 17:00:12 +08:00
|
|
|
|
<div class="logo-container">
|
|
|
|
|
|
<router-link to="/" class="home-link">
|
2026-01-27 15:50:23 +08:00
|
|
|
|
<img src="@/assets/iris.png" class="logo" alt="Logo" />
|
2026-05-12 14:07:26 +08:00
|
|
|
|
<span class="system-title">MOM</span>
|
2026-01-26 17:00:12 +08:00
|
|
|
|
</router-link>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
|
|
<div class="header-right">
|
2026-03-23 11:41:09 +08:00
|
|
|
|
<!-- 用户下拉菜单 -->
|
|
|
|
|
|
<el-dropdown trigger="click" @command="(cmd: string) => { if (cmd === 'profile') openProfileDialog(); if (cmd === 'logout') handleLogout(); }">
|
|
|
|
|
|
<div class="user-profile">
|
|
|
|
|
|
<el-avatar :size="32" :icon="UserFilled" class="user-avatar" />
|
|
|
|
|
|
<span class="user-name">{{ userStore.username || '管理员' }}</span>
|
|
|
|
|
|
<el-icon class="dropdown-arrow"><ArrowDown /></el-icon>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<template #dropdown>
|
|
|
|
|
|
<el-dropdown-menu>
|
|
|
|
|
|
<el-dropdown-item command="profile">
|
|
|
|
|
|
<el-icon><User /></el-icon> 个人中心
|
|
|
|
|
|
</el-dropdown-item>
|
|
|
|
|
|
<el-dropdown-item command="logout" divided>
|
|
|
|
|
|
<el-icon><SwitchButton /></el-icon> 退出登录
|
|
|
|
|
|
</el-dropdown-item>
|
|
|
|
|
|
</el-dropdown-menu>
|
|
|
|
|
|
</template>
|
|
|
|
|
|
</el-dropdown>
|
2026-01-26 17:00:12 +08:00
|
|
|
|
</div>
|
|
|
|
|
|
</header>
|
|
|
|
|
|
|
|
|
|
|
|
<main class="app-content">
|
|
|
|
|
|
<router-view />
|
|
|
|
|
|
</main>
|
|
|
|
|
|
|
2026-02-04 15:41:51 +08:00
|
|
|
|
<footer v-if="!isLoginPage" class="app-footer">
|
2026-01-26 17:00:12 +08:00
|
|
|
|
<span class="version-tag">
|
|
|
|
|
|
<el-icon style="vertical-align: middle; margin-right: 4px"><InfoFilled /></el-icon>
|
2026-09-21 14:26:07 +08:00
|
|
|
|
当前版本:V3.90
|
2026-01-26 17:00:12 +08:00
|
|
|
|
</span>
|
|
|
|
|
|
</footer>
|
2026-03-23 11:41:09 +08:00
|
|
|
|
|
|
|
|
|
|
<!-- ================================================================ -->
|
|
|
|
|
|
<!-- 个人中心 / 修改密码 弹窗 -->
|
|
|
|
|
|
<!-- 【严格脱敏】不展示系统角色字段 -->
|
|
|
|
|
|
<!-- ================================================================ -->
|
|
|
|
|
|
<el-dialog
|
|
|
|
|
|
v-model="profileDialogVisible"
|
|
|
|
|
|
title="个人中心"
|
|
|
|
|
|
width="480px"
|
2026-06-04 15:16:16 +08:00
|
|
|
|
:close-on-click-modal="false"
|
2026-03-23 11:41:09 +08:00
|
|
|
|
destroy-on-close
|
|
|
|
|
|
class="profile-dialog"
|
|
|
|
|
|
>
|
|
|
|
|
|
<div v-loading="profileLoading">
|
|
|
|
|
|
<!-- 个人信息(只读) -->
|
|
|
|
|
|
<div class="profile-info-section">
|
|
|
|
|
|
<div class="section-title">
|
|
|
|
|
|
<el-icon><User /></el-icon> 个人信息
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<el-descriptions :column="1" border class="profile-descriptions">
|
|
|
|
|
|
<el-descriptions-item label="姓名/账号">
|
|
|
|
|
|
<el-tag type="info" effect="plain">{{ profileForm.display_name || '-' }}</el-tag>
|
|
|
|
|
|
<span class="account-hint">(账号: {{ profileForm.username || '-' }})</span>
|
|
|
|
|
|
</el-descriptions-item>
|
|
|
|
|
|
<el-descriptions-item label="所属部门">
|
|
|
|
|
|
<el-tag type="success" effect="light">{{ profileForm.department || '-' }}</el-tag>
|
|
|
|
|
|
</el-descriptions-item>
|
|
|
|
|
|
</el-descriptions>
|
|
|
|
|
|
<!-- 【严格脱敏】系统角色字段已移除,不在此展示 -->
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
2026-04-17 12:48:30 +08:00
|
|
|
|
<div style="margin: 16px 0; text-align: center;">
|
|
|
|
|
|
<el-button type="primary" plain @click="openEmailDialog">
|
|
|
|
|
|
绑定/修改邮箱
|
|
|
|
|
|
</el-button>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
2026-03-23 11:41:09 +08:00
|
|
|
|
<el-divider>
|
|
|
|
|
|
<el-icon><Lock /></el-icon> 修改密码
|
|
|
|
|
|
</el-divider>
|
|
|
|
|
|
|
2026-03-23 11:52:30 +08:00
|
|
|
|
<!-- 修改密码表单(无需旧密码,JWT 已验证身份) -->
|
2026-03-23 11:41:09 +08:00
|
|
|
|
<el-form
|
|
|
|
|
|
ref="passwordFormRef"
|
|
|
|
|
|
:model="passwordForm"
|
|
|
|
|
|
label-width="110px"
|
|
|
|
|
|
class="password-form"
|
|
|
|
|
|
>
|
|
|
|
|
|
<el-form-item label="新密码" prop="new_password">
|
|
|
|
|
|
<el-input
|
|
|
|
|
|
v-model="passwordForm.new_password"
|
|
|
|
|
|
type="password"
|
|
|
|
|
|
placeholder="请输入新密码(至少6位)"
|
|
|
|
|
|
show-password
|
|
|
|
|
|
clearable
|
|
|
|
|
|
/>
|
|
|
|
|
|
</el-form-item>
|
|
|
|
|
|
|
|
|
|
|
|
<el-form-item label="确认新密码" prop="confirm_password">
|
|
|
|
|
|
<el-input
|
|
|
|
|
|
v-model="passwordForm.confirm_password"
|
|
|
|
|
|
type="password"
|
|
|
|
|
|
placeholder="请再次输入新密码"
|
|
|
|
|
|
show-password
|
|
|
|
|
|
clearable
|
|
|
|
|
|
/>
|
|
|
|
|
|
</el-form-item>
|
|
|
|
|
|
</el-form>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
|
|
<template #footer>
|
|
|
|
|
|
<div class="dialog-footer">
|
|
|
|
|
|
<el-button @click="profileDialogVisible = false" :disabled="passwordLoading">
|
|
|
|
|
|
取消
|
|
|
|
|
|
</el-button>
|
|
|
|
|
|
<el-button
|
|
|
|
|
|
type="primary"
|
|
|
|
|
|
:loading="passwordLoading"
|
|
|
|
|
|
@click="submitPasswordChange"
|
|
|
|
|
|
class="confirm-btn"
|
|
|
|
|
|
>
|
|
|
|
|
|
<el-icon v-if="!passwordLoading"><Lock /></el-icon>
|
|
|
|
|
|
确认修改
|
|
|
|
|
|
</el-button>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</template>
|
|
|
|
|
|
</el-dialog>
|
2026-04-17 12:48:30 +08:00
|
|
|
|
|
|
|
|
|
|
<!-- 绑定/修改邮箱弹窗 -->
|
2026-06-04 15:16:16 +08:00
|
|
|
|
<el-dialog v-model="emailDialogVisible" title="绑定/修改邮箱" width="400px" :close-on-click-modal="false" @close="resetEmailForm">
|
2026-04-17 12:48:30 +08:00
|
|
|
|
<el-form :model="emailForm" :rules="emailRules" ref="emailFormRef" label-width="80px">
|
|
|
|
|
|
<el-form-item label="新邮箱" prop="email">
|
|
|
|
|
|
<el-input v-model="emailForm.email" placeholder="请输入有效邮箱地址" />
|
|
|
|
|
|
</el-form-item>
|
|
|
|
|
|
</el-form>
|
|
|
|
|
|
<template #footer>
|
|
|
|
|
|
<el-button @click="emailDialogVisible = false">取消</el-button>
|
|
|
|
|
|
<el-button type="primary" :loading="emailLoading" @click="submitEmailUpdate">确认</el-button>
|
|
|
|
|
|
</template>
|
|
|
|
|
|
</el-dialog>
|
2026-01-26 13:47:53 +08:00
|
|
|
|
</div>
|
|
|
|
|
|
</template>
|
|
|
|
|
|
|
2026-01-26 17:00:12 +08:00
|
|
|
|
<style>
|
|
|
|
|
|
.app-wrapper {
|
|
|
|
|
|
display: flex;
|
|
|
|
|
|
flex-direction: column;
|
2026-02-04 14:29:59 +08:00
|
|
|
|
height: 100vh;
|
|
|
|
|
|
width: 100vw;
|
2026-01-27 15:50:23 +08:00
|
|
|
|
overflow: hidden;
|
2026-02-04 14:29:59 +08:00
|
|
|
|
background-color: #f5f7fa;
|
2026-01-26 17:00:12 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.app-header {
|
|
|
|
|
|
height: 60px;
|
|
|
|
|
|
background-color: #ffffff;
|
|
|
|
|
|
border-bottom: 1px solid #dcdfe6;
|
|
|
|
|
|
display: flex;
|
|
|
|
|
|
align-items: center;
|
|
|
|
|
|
justify-content: space-between;
|
|
|
|
|
|
padding: 0 20px;
|
2026-02-04 14:29:59 +08:00
|
|
|
|
box-shadow: 0 1px 4px rgba(0, 21, 41, 0.08);
|
|
|
|
|
|
flex-shrink: 0;
|
|
|
|
|
|
z-index: 1000;
|
2026-01-26 17:00:12 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.logo-container {
|
|
|
|
|
|
display: flex;
|
|
|
|
|
|
align-items: center;
|
2026-02-04 14:29:59 +08:00
|
|
|
|
height: 100%;
|
2026-01-26 17:00:12 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.home-link {
|
|
|
|
|
|
display: flex;
|
|
|
|
|
|
align-items: center;
|
2026-02-04 14:29:59 +08:00
|
|
|
|
gap: 12px;
|
2026-01-26 17:00:12 +08:00
|
|
|
|
text-decoration: none;
|
|
|
|
|
|
cursor: pointer;
|
2026-02-04 14:29:59 +08:00
|
|
|
|
height: 100%;
|
2026-01-27 15:50:23 +08:00
|
|
|
|
user-select: none;
|
2026-01-26 17:00:12 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2026-01-26 13:47:53 +08:00
|
|
|
|
.logo {
|
2026-02-04 14:29:59 +08:00
|
|
|
|
height: 32px;
|
2026-01-26 17:00:12 +08:00
|
|
|
|
width: auto;
|
2026-02-04 14:29:59 +08:00
|
|
|
|
object-fit: contain;
|
2026-01-26 17:00:12 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.system-title {
|
2026-02-04 14:29:59 +08:00
|
|
|
|
font-size: 18px;
|
2026-01-27 15:50:23 +08:00
|
|
|
|
font-weight: 600;
|
2026-01-26 17:00:12 +08:00
|
|
|
|
color: #303133;
|
2026-02-04 14:29:59 +08:00
|
|
|
|
letter-spacing: 0.5px;
|
|
|
|
|
|
white-space: nowrap;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.header-right {
|
|
|
|
|
|
display: flex;
|
|
|
|
|
|
align-items: center;
|
|
|
|
|
|
gap: 12px;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.user-profile {
|
|
|
|
|
|
display: flex;
|
|
|
|
|
|
align-items: center;
|
|
|
|
|
|
gap: 8px;
|
2026-03-23 11:41:09 +08:00
|
|
|
|
cursor: pointer;
|
|
|
|
|
|
padding: 4px 8px;
|
|
|
|
|
|
border-radius: 8px;
|
|
|
|
|
|
transition: background-color 0.2s;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.user-profile:hover {
|
|
|
|
|
|
background-color: #f0f2f5;
|
2026-02-04 14:29:59 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.user-avatar {
|
|
|
|
|
|
background-color: #409eff;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.user-name {
|
|
|
|
|
|
font-size: 14px;
|
|
|
|
|
|
color: #606266;
|
|
|
|
|
|
font-weight: 500;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-03-23 11:41:09 +08:00
|
|
|
|
.dropdown-arrow {
|
|
|
|
|
|
font-size: 12px;
|
|
|
|
|
|
color: #909399;
|
|
|
|
|
|
margin-left: 2px;
|
2026-01-26 13:47:53 +08:00
|
|
|
|
}
|
2026-01-26 17:00:12 +08:00
|
|
|
|
|
|
|
|
|
|
.app-content {
|
2026-02-04 14:29:59 +08:00
|
|
|
|
flex: 1;
|
|
|
|
|
|
min-height: 0;
|
|
|
|
|
|
width: 100%;
|
2026-01-27 15:50:23 +08:00
|
|
|
|
position: relative;
|
2026-02-04 14:29:59 +08:00
|
|
|
|
overflow: hidden;
|
2026-01-26 13:47:53 +08:00
|
|
|
|
}
|
2026-01-26 17:00:12 +08:00
|
|
|
|
|
|
|
|
|
|
.app-footer {
|
2026-02-04 14:29:59 +08:00
|
|
|
|
height: 30px;
|
2026-01-27 15:50:23 +08:00
|
|
|
|
background-color: #f0f2f5;
|
|
|
|
|
|
border-top: 1px solid #e4e7ed;
|
2026-01-26 17:00:12 +08:00
|
|
|
|
display: flex;
|
|
|
|
|
|
align-items: center;
|
2026-01-27 15:50:23 +08:00
|
|
|
|
justify-content: center;
|
2026-02-04 14:29:59 +08:00
|
|
|
|
flex-shrink: 0;
|
2026-01-26 17:00:12 +08:00
|
|
|
|
font-size: 12px;
|
|
|
|
|
|
color: #909399;
|
2026-01-27 15:50:23 +08:00
|
|
|
|
z-index: 1000;
|
2026-01-26 17:00:12 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.version-tag {
|
|
|
|
|
|
display: flex;
|
|
|
|
|
|
align-items: center;
|
2026-01-26 13:47:53 +08:00
|
|
|
|
}
|
2026-03-23 11:41:09 +08:00
|
|
|
|
|
|
|
|
|
|
/* 个人中心弹窗样式 */
|
|
|
|
|
|
.profile-info-section {
|
|
|
|
|
|
margin-bottom: 8px;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.section-title {
|
|
|
|
|
|
display: flex;
|
|
|
|
|
|
align-items: center;
|
|
|
|
|
|
gap: 6px;
|
|
|
|
|
|
font-size: 14px;
|
|
|
|
|
|
font-weight: 600;
|
|
|
|
|
|
color: #303133;
|
|
|
|
|
|
margin-bottom: 12px;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.profile-descriptions .account-hint {
|
|
|
|
|
|
margin-left: 8px;
|
|
|
|
|
|
font-size: 12px;
|
|
|
|
|
|
color: #909399;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.password-form {
|
|
|
|
|
|
margin-top: 8px;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.dialog-footer {
|
|
|
|
|
|
display: flex;
|
|
|
|
|
|
justify-content: flex-end;
|
|
|
|
|
|
gap: 10px;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.confirm-btn {
|
|
|
|
|
|
display: flex;
|
|
|
|
|
|
align-items: center;
|
|
|
|
|
|
gap: 4px;
|
|
|
|
|
|
}
|
2026-02-26 16:21:35 +08:00
|
|
|
|
</style>
|