You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
OneAuth/ui/page/profile.html

460 lines
11 KiB
HTML

11 months ago
<!doctype html>
<html>
<head>
<title>个人信息修改</title>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
3 weeks ago
<meta name="description" content="个人信息修改页面" details="查看和修改用户的基本信息、联系方式等">
11 months ago
</head>
<style>
body {
3 weeks ago
padding: var(--spacing-lg);
background-color: var(--bg-color-secondary);
7 months ago
}
.profile-container {
max-width: 1000px;
7 months ago
margin: 0 auto;
7 months ago
}
.profile-header {
11 months ago
display: flex;
7 months ago
align-items: center;
3 weeks ago
gap: var(--spacing-sm);
margin-bottom: var(--spacing-lg);
7 months ago
}
.profile-title {
font-size: 24px;
font-weight: 600;
3 weeks ago
color: var(--text-color);
7 months ago
}
/* 用户头像卡片 */
.avatar-section {
3 weeks ago
background: linear-gradient(135deg, var(--color-primary), color-mix(in srgb, var(--color-primary), black 20%));
7 months ago
color: white;
3 weeks ago
border-radius: var(--radius-lg);
7 months ago
padding: 32px 24px;
3 weeks ago
margin-bottom: var(--spacing-lg);
7 months ago
text-align: center;
3 weeks ago
box-shadow: var(--shadow-md);
7 months ago
}
.avatar-container {
position: relative;
display: inline-block;
3 weeks ago
margin-bottom: var(--spacing-md);
7 months ago
}
.avatar-preview {
width: 80px;
height: 80px;
border-radius: 50%;
object-fit: cover;
border: 4px solid rgba(255, 255, 255, 0.3);
transition: all 0.2s ease;
}
.avatar-placeholder {
width: 80px;
height: 80px;
border-radius: 50%;
background: rgba(255, 255, 255, 0.2);
display: flex;
align-items: center;
justify-content: center;
font-size: 32px;
font-weight: 600;
border: 4px solid rgba(255, 255, 255, 0.3);
}
.avatar-info h3 {
font-size: 20px;
font-weight: 600;
margin: 0 0 4px 0;
}
.avatar-info p {
font-size: 14px;
opacity: 0.9;
margin: 0;
}
/* 表单样式 */
.profile-section {
3 weeks ago
background: var(--bg-color);
border-radius: var(--radius-lg);
padding: var(--spacing-lg);
margin-bottom: var(--spacing-lg);
border: 1px solid var(--border-color);
box-shadow: var(--shadow-sm);
7 months ago
}
.section-header {
display: flex;
align-items: center;
3 weeks ago
gap: var(--spacing-md);
margin-bottom: var(--spacing-lg);
border-bottom: 1px solid var(--border-color);
padding-bottom: var(--spacing-md);
7 months ago
}
.section-icon {
width: 24px;
height: 24px;
3 weeks ago
color: var(--color-primary);
7 months ago
font-size: 18px;
}
.section-title {
font-size: 18px;
font-weight: 600;
3 weeks ago
color: var(--text-color);
7 months ago
margin: 0;
11 months ago
}
.form-group {
3 weeks ago
margin-bottom: var(--spacing-lg);
7 months ago
}
.form-group:last-child {
margin-bottom: 0;
11 months ago
}
7 months ago
.form-label {
11 months ago
display: block;
7 months ago
font-size: 14px;
font-weight: 500;
3 weeks ago
color: var(--text-color);
7 months ago
margin-bottom: 8px;
11 months ago
}
7 months ago
.form-description {
font-size: 12px;
3 weeks ago
color: var(--text-color-secondary);
7 months ago
margin-top: 4px;
line-height: 1.4;
}
/* 头像输入特殊样式 */
.avatar-input-group {
display: flex;
3 weeks ago
gap: var(--spacing-md);
7 months ago
align-items: flex-start;
}
.avatar-input {
flex: 1;
}
.avatar-preview-small {
width: 48px;
height: 48px;
3 weeks ago
border-radius: var(--radius-md);
11 months ago
object-fit: cover;
3 weeks ago
border: 2px solid var(--border-color);
7 months ago
flex-shrink: 0;
11 months ago
}
7 months ago
.avatar-placeholder-small {
width: 48px;
height: 48px;
3 weeks ago
border-radius: var(--radius-md);
background: var(--bg-color-secondary);
7 months ago
display: flex;
align-items: center;
justify-content: center;
3 weeks ago
color: var(--text-color-secondary);
7 months ago
font-size: 14px;
3 weeks ago
border: 2px solid var(--border-color);
7 months ago
flex-shrink: 0;
}
/* 保存按钮区域 */
.save-section {
3 weeks ago
background: var(--bg-color);
border-radius: var(--radius-lg);
7 months ago
padding: 20px 24px;
display: flex;
justify-content: space-between;
align-items: center;
3 weeks ago
border: 1px solid var(--border-color);
box-shadow: var(--shadow-sm);
position: sticky;
bottom: var(--spacing-lg);
z-index: 100;
7 months ago
}
.save-info {
3 weeks ago
color: var(--text-color-secondary);
7 months ago
font-size: 14px;
}
.save-info.changed {
3 weeks ago
color: var(--color-primary);
7 months ago
font-weight: 500;
}
/* 加载状态 */
.loading-overlay {
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: rgba(255, 255, 255, 0.8);
display: flex;
align-items: center;
justify-content: center;
3 weeks ago
border-radius: var(--radius-lg);
7 months ago
z-index: 10;
}
.loading-spinner {
3 weeks ago
color: var(--color-primary);
7 months ago
font-size: 24px;
}
.section-loading {
position: relative;
3 weeks ago
min-height: 200px;
7 months ago
}
@media (max-width: 768px) {
.profile-container {
3 weeks ago
padding: 0;
7 months ago
}
3 weeks ago
7 months ago
.save-section {
flex-direction: column;
gap: 12px;
text-align: center;
3 weeks ago
bottom: 0;
border-radius: var(--radius-lg) var(--radius-lg) 0 0;
}
.avatar-input-group {
flex-direction: column;
7 months ago
}
11 months ago
}
</style>
<body>
7 months ago
<div class="profile-container">
<!-- Header -->
<div class="profile-header">
<h1 class="profile-title">个人信息</h1>
3 weeks ago
<v-btn icon variant="ghost" :click="loadUserData" title="刷新">
7 months ago
<i class="fa-solid fa-rotate-right"></i>
3 weeks ago
</v-btn>
7 months ago
</div>
11 months ago
7 months ago
<!-- Avatar Section -->
<div class="avatar-section">
<div class="avatar-container">
<img v-if="user.icon" :src="user.icon" class="avatar-preview" alt="用户头像">
<div v-else class="avatar-placeholder">
{{ user.username ? user.username.charAt(0).toUpperCase() : 'U' }}
</div>
</div>
<div class="avatar-info">
<h3>{{ user.nickname || user.username || '未设置昵称' }}</h3>
<p>用户ID: {{ user.id }}</p>
</div>
</div>
11 months ago
7 months ago
<!-- Basic Info Section -->
3 weeks ago
<div class="profile-section section-loading">
<div v-if="isLoading" class="loading-overlay">
<i class="fa-solid fa-spinner fa-spin loading-spinner"></i>
</div>
7 months ago
<div class="section-header">
<i class="fa-solid fa-user section-icon"></i>
<h2 class="section-title">基本信息</h2>
</div>
7 months ago
<div class="form-group">
<label class="form-label">用户名</label>
3 weeks ago
<v-input v:value="user.username" placeholder="请输入用户名" :disabled="isLoading"></v-input>
7 months ago
<div class="form-description">用户名用于登录,建议使用英文或数字</div>
</div>
7 months ago
<div class="form-group">
<label class="form-label">昵称</label>
3 weeks ago
<v-input v:value="user.nickname" placeholder="请输入昵称" :disabled="isLoading"></v-input>
7 months ago
<div class="form-description">昵称将在页面中显示,可以使用中文</div>
</div>
<div class="form-group">
<label class="form-label">头像URL</label>
<div class="avatar-input-group">
<div class="avatar-input">
3 weeks ago
<v-input v:value="user.icon" placeholder="请输入头像图片URL" :disabled="isLoading"></v-input>
7 months ago
<div class="form-description">输入图片链接地址支持jpg、png、gif格式</div>
</div>
<img v-if="user.icon" :src="user.icon" class="avatar-preview-small" alt="头像预览">
<div v-else class="avatar-placeholder-small"></div>
</div>
</div>
</div>
7 months ago
<!-- Contact Info Section -->
3 weeks ago
<div class="profile-section section-loading">
<div v-if="isLoading" class="loading-overlay">
<i class="fa-solid fa-spinner fa-spin loading-spinner"></i>
</div>
7 months ago
<div class="section-header">
<i class="fa-solid fa-address-book section-icon"></i>
<h2 class="section-title">联系方式</h2>
</div>
11 months ago
7 months ago
<div class="form-group">
<label class="form-label">电子邮箱</label>
3 weeks ago
<v-input v:value="user.email" placeholder="请输入电子邮箱" :disabled="isLoading"></v-input>
7 months ago
<div class="form-description">用于接收重要通知和找回密码</div>
</div>
<div class="form-group">
<label class="form-label">手机号码</label>
3 weeks ago
<v-input v:value="user.phone" placeholder="请输入手机号码" :disabled="isLoading"></v-input>
7 months ago
<div class="form-description">用于接收验证码和安全提醒</div>
</div>
</div>
<!-- Save Section -->
3 weeks ago
<div class="save-section">
7 months ago
<div class="save-info" :class="{ changed: hasChanges }">
{{ hasChanges ? '您有未保存的更改' : '所有信息已保存' }}
</div>
3 weeks ago
<v-btn :click="saveProfile" :disabled="!hasChanges || isSaving" :loading="isSaving">
7 months ago
{{ isSaving ? '保存中...' : '保存修改' }}
3 weeks ago
</v-btn>
7 months ago
</div>
</div>
11 months ago
</body>
<script setup>
// 初始化用户数据
user = {
7 months ago
id: $G.token?.body()?.uid,
username: '',
nickname: "",
7 months ago
icon: "",
email: "",
phone: "",
status: 0
7 months ago
}
// 原始用户数据,用于比较变更
originalUser = {}
7 months ago
// UI状态
isLoading = false
isSaving = false
hasChanges = false
// 检查是否有变更
checkForChanges = () => {
7 months ago
let changes = [
user.username !== originalUser.username,
user.nickname !== originalUser.nickname,
user.icon !== originalUser.icon,
user.email !== originalUser.email,
7 months ago
user.phone !== originalUser.phone
7 months ago
]
hasChanges = changes.some(change => change)
7 months ago
}
// 加载用户数据
loadUserData = async () => {
isLoading = true
const response = await $axios.get("/api/user/" + user.id).catch(error => {
console.log(error)
3 weeks ago
$message.error("加载用户数据失败")
})
3 weeks ago
if (response) {
3 weeks ago
// 确保所有字段都有值,避免 undefined
user = {
id: response.id,
username: response.username || "",
nickname: response.nickname || "",
icon: response.icon || "",
email: response.email || "",
phone: response.phone || "",
status: response.status || 0
}
// 保存原始数据
originalUser = JSON.parse(JSON.stringify(user))
hasChanges = false
}
3 weeks ago
isLoading = false
7 months ago
}
// 保存修改
saveProfile = async () => {
3 weeks ago
checkForChanges()
if (!hasChanges || isSaving) return
3 weeks ago
isSaving = true
// 准备更新数据
const updateData = {
username: user.username || null,
nickname: user.nickname || null,
icon: user.icon || null,
email: user.email || null,
phone: user.phone || null
}
// 发送更新请求
const response = await $axios.patch("/api/user/" + user.id, updateData).catch(error => {
$message.error("保存失败: " + error.message || "未知错误")
})
if (response) {
// 更新本地数据
user = {
...user,
username: response.username || user.username,
nickname: response.nickname || user.nickname,
icon: response.icon || user.icon,
email: response.email || user.email,
phone: response.phone || user.phone
}
3 weeks ago
// 更新原始数据
originalUser = JSON.parse(JSON.stringify(user))
hasChanges = false
$message.success("个人信息更新成功!")
}
3 weeks ago
isSaving = false
7 months ago
}
</script>
<script>
7 months ago
// 页面加载时获取用户数据
$data.loadUserData()
// 监听用户数据变化
$watch(() => {
7 months ago
checkForChanges()
7 months ago
})
// 页面离开前提醒未保存的更改
window.addEventListener('beforeunload', (event) => {
if ($data.hasChanges) {
event.preventDefault()
event.returnValue = '您有未保存的更改,确定要离开页面吗?'
return event.returnValue
}
})
</script>
3 weeks ago
</html>