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

599 lines
13 KiB
HTML

7 months ago
<!doctype html>
<html>
<head>
<title>个人信息修改</title>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
</head>
<style>
body {
3 months ago
padding: 24px;
}
.profile-container {
max-width: 1000px;
3 months ago
margin: 0 auto;
3 months ago
}
.profile-header {
7 months ago
display: flex;
3 months ago
align-items: center;
gap: 8px;
margin-bottom: 24px;
}
.profile-title {
font-size: 24px;
font-weight: 600;
color: #333;
}
.refresh-btn {
background: none;
border: none;
color: #666;
font-size: 16px;
cursor: pointer;
padding: 4px;
border-radius: 4px;
transition: all 0.2s ease;
}
.refresh-btn:hover {
color: #6c5ce7;
background: #f8f7ff;
}
/* 用户头像卡片 */
.avatar-section {
background: linear-gradient(135deg, #6c5ce7, #5a4fcf);
color: white;
border-radius: 12px;
padding: 32px 24px;
margin-bottom: 24px;
text-align: center;
}
.avatar-container {
position: relative;
display: inline-block;
margin-bottom: 16px;
}
.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 {
background: white;
border-radius: 12px;
padding: 24px;
margin-bottom: 24px;
border: 1px solid #e0e0e0;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.04);
}
.section-header {
display: flex;
align-items: center;
gap: 12px;
margin-bottom: 20px;
}
.section-icon {
width: 24px;
height: 24px;
color: #6c5ce7;
font-size: 18px;
}
.section-title {
font-size: 18px;
font-weight: 600;
color: #333;
margin: 0;
7 months ago
}
.form-group {
3 months ago
margin-bottom: 20px;
}
.form-group:last-child {
margin-bottom: 0;
7 months ago
}
3 months ago
.form-label {
7 months ago
display: block;
3 months ago
font-size: 14px;
font-weight: 500;
color: #333;
margin-bottom: 8px;
7 months ago
}
3 months ago
.form-input {
7 months ago
width: 100%;
3 months ago
padding: 12px 16px;
border: 2px solid #e0e0e0;
border-radius: 8px;
font-size: 16px;
color: #333;
background: white;
transition: all 0.2s ease;
7 months ago
}
3 months ago
.form-input:focus {
outline: none;
border-color: #6c5ce7;
box-shadow: 0 0 0 2px rgba(108, 92, 231, 0.1);
}
.form-input:disabled {
background: #f8f9fa;
color: #666;
cursor: not-allowed;
}
.form-description {
font-size: 12px;
color: #666;
margin-top: 4px;
line-height: 1.4;
}
/* 头像输入特殊样式 */
.avatar-input-group {
display: flex;
gap: 12px;
align-items: flex-start;
}
.avatar-input {
flex: 1;
}
.avatar-preview-small {
width: 48px;
height: 48px;
border-radius: 8px;
7 months ago
object-fit: cover;
3 months ago
border: 2px solid #e0e0e0;
flex-shrink: 0;
7 months ago
}
3 months ago
.avatar-placeholder-small {
width: 48px;
height: 48px;
border-radius: 8px;
background: #f0f0f0;
display: flex;
align-items: center;
justify-content: center;
color: #999;
font-size: 14px;
border: 2px solid #e0e0e0;
flex-shrink: 0;
}
/* 保存按钮区域 */
.save-section {
background: #f8f9fa;
border-radius: 12px;
padding: 20px 24px;
display: flex;
justify-content: space-between;
align-items: center;
border: 1px solid #e0e0e0;
}
.save-info {
color: #666;
font-size: 14px;
}
.save-info.changed {
color: #6c5ce7;
font-weight: 500;
}
.save-btn {
padding: 12px 24px;
background: #6c5ce7;
7 months ago
color: white;
border: none;
3 months ago
border-radius: 8px;
7 months ago
font-size: 16px;
3 months ago
font-weight: 500;
cursor: pointer;
transition: all 0.2s ease;
opacity: 0.5;
pointer-events: none;
}
.save-btn.enabled {
opacity: 1;
pointer-events: auto;
}
.save-btn:hover.enabled {
background: #5a4fcf;
transform: translateY(-1px);
7 months ago
}
3 months ago
.save-btn.saving {
background: #28a745;
cursor: not-allowed;
7 months ago
}
3 months ago
/* 消息提示 */
.message {
position: fixed;
top: 20px;
right: 20px;
padding: 12px 20px;
border-radius: 8px;
7 months ago
font-size: 14px;
3 months ago
font-weight: 500;
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
transform: translateX(400px);
transition: transform 0.3s ease;
z-index: 1000;
}
.message.show {
transform: translateX(0);
}
.message.error {
background: #dc3545;
color: white;
}
.message.success {
background: #28a745;
color: white;
}
/* 加载状态 */
.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;
border-radius: 12px;
z-index: 10;
}
.loading-spinner {
color: #6c5ce7;
font-size: 24px;
}
.section-loading {
position: relative;
}
@media (max-width: 768px) {
.profile-container {
padding: 0 12px;
}
.profile-section {
padding: 20px 16px;
}
.avatar-section {
padding: 24px 16px;
}
.avatar-input-group {
flex-direction: column;
}
.save-section {
flex-direction: column;
gap: 12px;
text-align: center;
}
7 months ago
}
</style>
<body>
3 months ago
<div class="profile-container">
<!-- Header -->
<div class="profile-header">
<h1 class="profile-title">个人信息</h1>
<button class="refresh-btn" @click="loadUserData">
<i class="fa-solid fa-rotate-right"></i>
</button>
</div>
7 months ago
3 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>
7 months ago
3 months ago
<!-- Basic Info Section -->
<div class="profile-section section-loading" v-if="!isLoading">
<div class="section-header">
<i class="fa-solid fa-user section-icon"></i>
<h2 class="section-title">基本信息</h2>
</div>
3 months ago
<div class="form-group">
<label class="form-label">用户名</label>
3 months ago
<input type="text" class="form-input" v:value="user.username" placeholder="请输入用户名">
3 months ago
<div class="form-description">用户名用于登录,建议使用英文或数字</div>
</div>
3 months ago
<div class="form-group">
<label class="form-label">昵称</label>
3 months ago
<input type="text" class="form-input" v:value="user.nickname" placeholder="请输入昵称">
3 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 months ago
<input type="text" class="form-input" v:value="user.icon" placeholder="请输入头像图片URL">
3 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>
3 months ago
<!-- Contact Info Section -->
<div class="profile-section section-loading" v-if="!isLoading">
<div class="section-header">
<i class="fa-solid fa-address-book section-icon"></i>
<h2 class="section-title">联系方式</h2>
</div>
7 months ago
3 months ago
<div class="form-group">
<label class="form-label">电子邮箱</label>
3 months ago
<input type="email" class="form-input" v:value="user.email" placeholder="请输入电子邮箱">
3 months ago
<div class="form-description">用于接收重要通知和找回密码</div>
</div>
<div class="form-group">
<label class="form-label">手机号码</label>
3 months ago
<input type="tel" class="form-input" v:value="user.phone" placeholder="请输入手机号码">
3 months ago
<div class="form-description">用于接收验证码和安全提醒</div>
</div>
</div>
<!-- Loading State -->
<div class="profile-section" v-if="isLoading">
<div class="loading-overlay">
<i class="fa-solid fa-spinner fa-spin loading-spinner"></i>
</div>
<div style="height: 200px;"></div>
</div>
<!-- Save Section -->
<div class="save-section" v-if="!isLoading">
<div class="save-info" :class="{ changed: hasChanges }">
{{ hasChanges ? '您有未保存的更改' : '所有信息已保存' }}
</div>
<button class="save-btn" :class="{ enabled: hasChanges && !isSaving, saving: isSaving }" @click="saveProfile"
:disabled="!hasChanges || isSaving">
{{ isSaving ? '保存中...' : '保存修改' }}
</button>
</div>
<!-- Messages -->
<div class="message error" :class="{ show: showErrorMessage }">
<i class="fa-solid fa-exclamation-circle"></i>
{{ errorMessage }}
</div>
<div class="message success" :class="{ show: showSuccessMessage }">
<i class="fa-solid fa-check-circle"></i>
{{ successMessage }}
</div>
</div>
7 months ago
</body>
<script setup>
// 初始化用户数据
user = {
3 months ago
id: $G.token?.body()?.uid,
username: '',
nickname: "",
3 months ago
icon: "",
email: "",
phone: "",
status: 0
3 months ago
}
// 原始用户数据,用于比较变更
originalUser = {}
3 months ago
// UI状态
errorMessage = ""
successMessage = ""
isLoading = false
isSaving = false
hasChanges = false
showErrorMessage = false
showSuccessMessage = false
// 更新字段
updateField = (field, value) => {
user[field] = value
}
// 检查是否有变更
checkForChanges = () => {
3 months ago
let changes = [
user.username !== originalUser.username,
user.nickname !== originalUser.nickname,
user.icon !== originalUser.icon,
user.email !== originalUser.email,
3 months ago
user.phone !== originalUser.phone
3 months ago
]
hasChanges = changes.some(change => change)
3 months ago
}
// 显示错误消息
showError = (message) => {
errorMessage = message
showErrorMessage = true
setTimeout(() => {
showErrorMessage = false
}, 5000)
}
// 显示成功消息
showSuccess = (message) => {
successMessage = message
showSuccessMessage = true
setTimeout(() => {
showSuccessMessage = false
}, 3000)
}
// 加载用户数据
loadUserData = async () => {
isLoading = true
const response = await $axios.get("/api/user/" + user.id).catch(error => {
console.log(error)
})
if (response) {
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
}
try {
} catch (error) {
3 months ago
showError("加载用户数据失败: " + error.message)
} finally {
3 months ago
isLoading = false
}
3 months ago
}
// 保存修改
saveProfile = async () => {
try {
3 months ago
checkForChanges()
3 months ago
if (!hasChanges || isSaving) return
errorMessage = ""
successMessage = ""
isSaving = true
// 准备更新数据
const updateData = {
username: user.username || null,
nickname: user.nickname || null,
icon: user.icon || null,
email: user.email || null,
phone: user.phone || null
3 months ago
}
// 发送更新请求
3 months ago
const response = await $axios.patch("/api/user/" + user.id, updateData)
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 months ago
}
// 更新原始数据
originalUser = JSON.parse(JSON.stringify(user))
hasChanges = false
showSuccess("个人信息更新成功!")
}
} catch (error) {
3 months ago
showError("保存失败: " + error)
} finally {
3 months ago
isSaving = false
}
3 months ago
}
</script>
<script>
3 months ago
// 页面加载时获取用户数据
$data.loadUserData()
// 监听用户数据变化
$watch(() => {
3 months ago
checkForChanges()
3 months ago
})
// 页面离开前提醒未保存的更改
window.addEventListener('beforeunload', (event) => {
if ($data.hasChanges) {
event.preventDefault()
event.returnValue = '您有未保存的更改,确定要离开页面吗?'
return event.returnValue
}
})
</script>
7 months ago
</html>