Files
sunct 3c3bf53ae4 docs(website): 添加简记memo产品原型和UI设计规范文档
- 新增「简记memo」一体化小程序产品原型设计文档
- 新增简记memo完整版UI视觉设计规范和界面细节
- 添加IDEA项目配置文件.gitignore
- 创建404页面HTML文件,包含响应式布局和错误提示
- 添加关于页面HTML文件,展示品牌介绍和团队信息
- 实现AES加解密工具函数,支持请求体加密
- 添加用户协议页面基础框架
2026-07-31 14:12:32 +08:00

510 lines
32 KiB
HTML

<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>个人中心 - 简记memo</title>
<link rel="stylesheet" href="https://unpkg.com/element-ui@2.15.14/lib/theme-chalk/index.css">
<style>
* { margin: 0; padding: 0; box-sizing: border-box; }
body { font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif; background: #f8fafc; }
.nav-link.active { background: rgba(79, 158, 222, 0.1); color: #4F9EDE; }
.menu-card { transition: all 0.2s; cursor: pointer; }
.menu-card:hover { transform: translateY(-2px); box-shadow: 0 8px 24px rgba(0,0,0,0.08); }
.stat-card { transition: all 0.2s; }
.stat-card:hover { transform: translateY(-2px); }
.avatar-option { cursor: pointer; transition: all 0.2s; border: 2px solid transparent; }
.avatar-option:hover { transform: scale(1.05); }
.avatar-option.selected { border-color: #4F9EDE; box-shadow: 0 0 0 3px rgba(79, 158, 222, 0.2); }
</style>
</head>
<body>
<div id="app">
<!-- 公共头部 -->
<div id="webHeader"></div>
<div style="margin-top: 56px; min-height: calc(100vh - 56px - 40px); padding: 24px;">
<div style="max-width: 1200px; margin: 0 auto;">
<!-- 左右布局 -->
<div style="display: grid; grid-template-columns: 380px 1fr; gap: 24px;">
<!-- 左侧:用户信息 -->
<div style="display: flex; flex-direction: column; gap: 16px;">
<!-- 用户信息卡片 -->
<div style="background: linear-gradient(135deg, #4F9EDE 0%, #3A8BC9 100%); border-radius: 12px; padding: 32px 24px; color: #fff; text-align: center;">
<div id="userAvatarContainer" style="width: 100px; height: 100px; border-radius: 50%; background: rgba(255,255,255,0.2); display: flex; align-items: center; justify-content: center; margin: 0 auto 20px; font-size: 40px; font-weight: 600; cursor: pointer; position: relative; overflow: hidden;" onclick="showAvatarModal()">
U
<div style="position: absolute; bottom: 0; left: 0; right: 0; height: 28px; background: rgba(0,0,0,0.3); display: flex; align-items: center; justify-content: center; font-size: 12px;">更换</div>
</div>
<h2 style="font-size: 22px; font-weight: 700; margin-bottom: 6px;" id="userNickname">用户</h2>
<p style="font-size: 14px; opacity: 0.9; margin-bottom: 8px;" id="userEmail">未绑定邮箱</p>
<p style="font-size: 13px; opacity: 0.8;" id="userSignature">还没有个性签名</p>
<button onclick="showEditModal()" style="margin-top: 20px; padding: 10px 24px; background: rgba(255,255,255,0.2); border: 1px solid rgba(255,255,255,0.3); border-radius: 8px; font-size: 13px; color: #fff; cursor: pointer;">
编辑资料
</button>
</div>
<!-- 快捷入口 -->
<div style="background: #fff; border-radius: 12px; padding: 20px;">
<h3 style="font-size: 14px; font-weight: 600; color: #1e293b; margin-bottom: 16px;">快捷入口</h3>
<div style="display: flex; flex-direction: column; gap: 8px;">
<div onclick="navigateTo('/web/calendar')" class="menu-card" style="display: flex; align-items: center; gap: 12px; padding: 14px; border-radius: 10px; background: #f8fafc;">
<div style="width: 40px; height: 40px; background: linear-gradient(135deg, rgba(79, 158, 222, 0.1) 0%, rgba(79, 158, 222, 0.05) 100%); border-radius: 10px; display: flex; align-items: center; justify-content: center;">
<svg style="width: 20px; height: 20px; fill: none; stroke: #4F9EDE;" viewBox="0 0 24 24"><rect x="3" y="4" width="18" height="18" rx="2" ry="2"/><line x1="16" y1="2" x2="16" y2="6"/><line x1="8" y1="2" x2="8" y2="6"/><line x1="3" y1="10" x2="21" y2="10"/></svg>
</div>
<div style="flex: 1;">
<div style="font-size: 14px; font-weight: 600; color: #1e293b;">日程管理</div>
<div style="font-size: 12px; color: #64748b;">管理每日待办任务</div>
</div>
<svg style="width: 16px; height: 16px; fill: none; stroke: #94a3b8;" viewBox="0 0 24 24"><path d="M9 5l7 7-7 7"/></svg>
</div>
<div onclick="navigateTo('/web/growth')" class="menu-card" style="display: flex; align-items: center; gap: 12px; padding: 14px; border-radius: 10px; background: #f8fafc;">
<div style="width: 40px; height: 40px; background: linear-gradient(135deg, rgba(245, 158, 11, 0.1) 0%, rgba(245, 158, 11, 0.05) 100%); border-radius: 10px; display: flex; align-items: center; justify-content: center;">
<svg style="width: 20px; height: 20px; fill: none; stroke: #f59e0b;" viewBox="0 0 24 24"><path d="M6 9H4.5a2.5 2.5 0 0 1 0-5H6"/><path d="M18 9h1.5a2.5 2.5 0 0 0 0-5H18"/><path d="M4 22h16"/><path d="M10 14.66V17c0 .55-.47.98-.97 1.21C7.85 18.75 7 20.24 7 22"/><path d="M14 14.66V17c0 .55.47.98.97 1.21C16.15 18.75 17 20.24 17 22"/><path d="M18 2H6v7a6 6 0 0 0 12 0V2Z"/></svg>
</div>
<div style="flex: 1;">
<div style="font-size: 14px; font-weight: 600; color: #1e293b;">成长中心</div>
<div style="font-size: 12px; color: #64748b;">查看等级与成就</div>
</div>
<svg style="width: 16px; height: 16px; fill: none; stroke: #94a3b8;" viewBox="0 0 24 24"><path d="M9 5l7 7-7 7"/></svg>
</div>
</div>
</div>
<!-- 设置 -->
<div style="background: #fff; border-radius: 12px; padding: 8px;">
<div onclick="navigateTo('/about')" style="display: flex; align-items: center; justify-content: space-between; padding: 14px 16px; cursor: pointer; border-radius: 8px; transition: all 0.15s;" onmouseover="this.style.background='#f8fafc'" onmouseout="this.style.background='transparent'">
<div style="display: flex; align-items: center; gap: 12px;">
<svg style="width: 18px; height: 18px; fill: none; stroke: #64748b;" viewBox="0 0 24 24"><circle cx="12" cy="12" r="10"/><line x1="12" y1="16" x2="12" y2="12"/><line x1="12" y1="8" x2="12.01" y2="8"/></svg>
<span style="font-size: 14px; color: #1e293b;">关于我们</span>
</div>
<svg style="width: 16px; height: 16px; fill: none; stroke: #94a3b8;" viewBox="0 0 24 24"><path d="M9 5l7 7-7 7"/></svg>
</div>
<div onclick="navigateTo('/privacy')" style="display: flex; align-items: center; justify-content: space-between; padding: 14px 16px; cursor: pointer; border-radius: 8px; transition: all 0.15s;" onmouseover="this.style.background='#f8fafc'" onmouseout="this.style.background='transparent'">
<div style="display: flex; align-items: center; gap: 12px;">
<svg style="width: 18px; height: 18px; fill: none; stroke: #64748b;" viewBox="0 0 24 24"><rect x="3" y="11" width="18" height="11" rx="2" ry="2"/><path d="M7 11V7a5 5 0 0 1 10 0v4"/></svg>
<span style="font-size: 14px; color: #1e293b;">隐私政策</span>
</div>
<svg style="width: 16px; height: 16px; fill: none; stroke: #94a3b8;" viewBox="0 0 24 24"><path d="M9 5l7 7-7 7"/></svg>
</div>
</div>
</div>
<!-- 右侧:统计数据 -->
<div style="display: flex; flex-direction: column; gap: 16px;">
<!-- 统计卡片 -->
<div style="display: grid; grid-template-columns: repeat(4, 1fr); gap: 16px;">
<div class="stat-card" style="background: #fff; border-radius: 12px; padding: 24px; text-align: center;">
<div style="font-size: 32px; font-weight: 700; color: #4F9EDE;" id="statTodoTotal">0</div>
<div style="font-size: 13px; color: #64748b; margin-top: 6px;">总待办</div>
</div>
<div class="stat-card" style="background: #fff; border-radius: 12px; padding: 24px; text-align: center;">
<div style="font-size: 32px; font-weight: 700; color: #22c55e;" id="statTodoDone">0</div>
<div style="font-size: 13px; color: #64748b; margin-top: 6px;">已完成</div>
</div>
<div class="stat-card" style="background: #fff; border-radius: 12px; padding: 24px; text-align: center;">
<div style="font-size: 32px; font-weight: 700; color: #f59e0b;" id="statLevel">1</div>
<div style="font-size: 13px; color: #64748b; margin-top: 6px;">当前等级</div>
</div>
<div class="stat-card" style="background: #fff; border-radius: 12px; padding: 24px; text-align: center;">
<div style="font-size: 32px; font-weight: 700; color: #8b5cf6;" id="statExp">0</div>
<div style="font-size: 13px; color: #64748b; margin-top: 6px;">经验值</div>
</div>
</div>
<!-- 成长进度 -->
<div style="background: #fff; border-radius: 12px; padding: 24px;">
<h3 style="font-size: 16px; font-weight: 600; color: #1e293b; margin-bottom: 20px;">成长进度</h3>
<div style="display: flex; align-items: center; gap: 20px;">
<div style="width: 80px; height: 80px; background: linear-gradient(135deg, #4F9EDE 0%, #3A8BC9 100%); border-radius: 50%; display: flex; align-items: center; justify-content: center; color: #fff; font-size: 24px; font-weight: 700;">
Lv.<span id="growthLevel">1</span>
</div>
<div style="flex: 1;">
<div style="display: flex; align-items: center; justify-content: space-between; margin-bottom: 10px;">
<span style="font-size: 15px; font-weight: 600; color: #1e293b;">当前等级</span>
<span style="font-size: 13px; color: #64748b;" id="growthExpText">0 / 100 经验</span>
</div>
<div style="background: #e2e8f0; border-radius: 4px; height: 10px; overflow: hidden;">
<div id="growthExpBar" style="height: 100%; background: linear-gradient(135deg, #4F9EDE 0%, #3A8BC9 100%); border-radius: 4px; transition: width 0.5s; width: 0%;"></div>
</div>
<p style="font-size: 12px; color: #94a3b8; margin-top: 8px;" id="growthRemain">还需 100 经验升级</p>
</div>
</div>
</div>
<!-- 最近成就 -->
<div style="background: #fff; border-radius: 12px; padding: 24px; flex: 1;">
<div style="display: flex; align-items: center; justify-content: space-between; margin-bottom: 20px;">
<h3 style="font-size: 16px; font-weight: 600; color: #1e293b;">我的成就</h3>
<div style="display: flex; align-items: center; gap: 12px;">
<select id="profileAchFilter" onchange="filterProfileAchievements()" style="padding: 6px 12px; border: 1px solid #e2e8f0; border-radius: 6px; font-size: 13px; color: #475569; background: #fff; cursor: pointer;">
<option value="all">全部分类</option>
</select>
</div>
</div>
<div id="recentAchievements" style="display: grid; grid-template-columns: repeat(auto-fill, minmax(180px, 1fr)); gap: 16px;">
<!-- 动态渲染 -->
</div>
</div>
</div>
</div>
</div>
</div>
<!-- 公共底部 -->
<div id="webFooter"></div>
</div>
<!-- 编辑资料弹窗 -->
<div id="editModal" style="display: none; position: fixed; top: 0; left: 0; right: 0; bottom: 0; background: rgba(0,0,0,0.5); z-index: 1000; align-items: center; justify-content: center; padding: 24px;">
<div style="background: #fff; border-radius: 16px; padding: 28px; width: 100%; max-width: 400px;">
<h3 style="font-size: 18px; font-weight: 600; color: #1e293b; margin-bottom: 24px;">编辑资料</h3>
<div style="margin-bottom: 20px;">
<label style="display: block; font-size: 13px; font-weight: 500; color: #475569; margin-bottom: 8px;">昵称</label>
<input type="text" id="editNickname" placeholder="请输入昵称" style="width: 100%; padding: 12px 16px; border: 1px solid #e2e8f0; border-radius: 10px; font-size: 14px; outline: none;" onfocus="this.style.borderColor='#4F9EDE'" onblur="this.style.borderColor='#e2e8f0'">
</div>
<div style="margin-bottom: 28px;">
<label style="display: block; font-size: 13px; font-weight: 500; color: #475569; margin-bottom: 8px;">签名</label>
<textarea id="editSignature" placeholder="请输入个性签名" rows="3" style="width: 100%; padding: 12px 16px; border: 1px solid #e2e8f0; border-radius: 10px; font-size: 14px; resize: none; outline: none;" onfocus="this.style.borderColor='#4F9EDE'" onblur="this.style.borderColor='#e2e8f0'"></textarea>
</div>
<div style="display: flex; justify-content: flex-end; gap: 12px;">
<button onclick="closeEditModal()" style="padding: 10px 20px; border: 1px solid #e2e8f0; border-radius: 8px; font-size: 14px; color: #475569; cursor: pointer; background: #fff;">取消</button>
<button onclick="saveProfile()" style="padding: 10px 20px; background: linear-gradient(135deg, #4F9EDE 0%, #3A8BC9 100%); border: none; border-radius: 8px; font-size: 14px; color: #fff; cursor: pointer;">保存</button>
</div>
</div>
</div>
<!-- 头像选择弹窗 -->
<div id="avatarModal" style="display: none; position: fixed; top: 0; left: 0; right: 0; bottom: 0; background: rgba(0,0,0,0.5); z-index: 1000; align-items: center; justify-content: center; padding: 24px;">
<div style="background: #fff; border-radius: 16px; padding: 28px; width: 100%; max-width: 480px;">
<h3 style="font-size: 18px; font-weight: 600; color: #1e293b; margin-bottom: 24px;">选择头像</h3>
<div id="avatarGrid" style="display: grid; grid-template-columns: repeat(4, 1fr); gap: 16px; margin-bottom: 24px;">
<!-- 动态生成头像选项 -->
</div>
<div style="display: flex; justify-content: flex-end; gap: 12px;">
<button onclick="closeAvatarModal()" style="padding: 10px 20px; border: 1px solid #e2e8f0; border-radius: 8px; font-size: 14px; color: #475569; cursor: pointer; background: #fff;">取消</button>
<button onclick="saveAvatar()" style="padding: 10px 20px; background: linear-gradient(135deg, #4F9EDE 0%, #3A8BC9 100%); border: none; border-radius: 8px; font-size: 14px; color: #fff; cursor: pointer;">确认</button>
</div>
</div>
</div>
<script src="https://unpkg.com/vue@2.6.14/dist/vue.min.js"></script>
<script src="https://unpkg.com/element-ui@2.15.14/lib/index.js"></script>
<script src="/static/js/config.js"></script>
<script src="/static/js/toast.js"></script>
<script src="/static/js/auth.js"></script>
<script src="/static/js/web-components.js"></script>
<script>
// 默认头像列表
const DEFAULT_AVATARS = [
{ url: '/static/avatar/ginger_cat.jpg', name: 'ginger_cat', label: '橘猫' },
{ url: '/static/avatar/ragdoll.jpg', name: 'ragdoll', label: '布偶猫' },
{ url: '/static/avatar/hamster.jpg', name: 'hamster', label: '小仓鼠' },
{ url: '/static/avatar/dragon.jpg', name: 'dragon', label: '龙' },
{ url: '/static/avatar/pony.jpg', name: 'pony', label: '小马' },
{ url: '/static/avatar/lamb.jpg', name: 'lamb', label: '小羊' },
{ url: '/static/avatar/corgi.jpg', name: 'corgi', label: '柯基' },
{ url: '/static/avatar/piglet.jpg', name: 'piglet', label: '小猪' },
];
let selectedAvatarUrl = '';
document.addEventListener('DOMContentLoaded', function() {
// 初始化公共头部底部
initWebPage('profile');
// 加载页面数据
loadUserInfo();
loadStats();
loadGrowthInfo();
renderAvatarGrid();
});
function navigateTo(url) {
window.location.href = url;
}
function loadUserInfo() {
const token = localStorage.getItem('user_token');
const nickname = localStorage.getItem('user_nickname') || '用户';
const avatar = localStorage.getItem('user_avatar');
const email = localStorage.getItem('user_email');
document.getElementById('userNickname').textContent = nickname;
// 优先显示localStorage中的邮箱
if (email) {
document.getElementById('userEmail').textContent = email;
}
const avatarContainer = document.getElementById('userAvatarContainer');
if (avatar) {
avatarContainer.innerHTML = `<img src="${avatar}" style="width: 100%; height: 100%; border-radius: 50%; object-fit: cover;"><div style="position: absolute; bottom: 0; left: 0; right: 0; height: 28px; background: rgba(0,0,0,0.3); display: flex; align-items: center; justify-content: center; font-size: 12px;">更换</div>`;
selectedAvatarUrl = avatar;
} else {
avatarContainer.textContent = nickname.charAt(0).toUpperCase();
}
fetch('/api/web/user/info', { headers: { 'token': token } })
.then(res => res.json())
.then(data => {
if (data.code === 200) {
const user = data.data;
document.getElementById('userNickname').textContent = user.nickname || '用户';
// 接口返回的邮箱优先
if (user.email) {
document.getElementById('userEmail').textContent = user.email;
localStorage.setItem('user_email', user.email);
}
if (user.signature) {
document.getElementById('userSignature').textContent = user.signature;
}
if (user.avatar) {
const container = document.getElementById('userAvatarContainer');
container.innerHTML = `<img src="${user.avatar}" style="width: 100%; height: 100%; border-radius: 50%; object-fit: cover;"><div style="position: absolute; bottom: 0; left: 0; right: 0; height: 28px; background: rgba(0,0,0,0.3); display: flex; align-items: center; justify-content: center; font-size: 12px;">更换</div>`;
localStorage.setItem('user_avatar', user.avatar);
selectedAvatarUrl = user.avatar;
}
localStorage.setItem('user_nickname', user.nickname || '用户');
}
});
}
function loadStats() {
const token = localStorage.getItem('user_token');
// 加载成长信息
fetch('/api/web/growth/info', { headers: { 'token': token } })
.then(res => res.json())
.then(data => {
if (data.code === 200) {
document.getElementById('statLevel').textContent = data.data.level || 1;
document.getElementById('statExp').textContent = data.data.total_exp || 0;
}
});
// 加载待办统计
fetch('/api/web/todo/list', {
method: 'POST',
headers: { 'Content-Type': 'application/json', 'token': token },
body: JSON.stringify({})
})
.then(res => res.json())
.then(data => {
if (data.code === 200) {
const todos = data.data || [];
const total = todos.length;
const done = todos.filter(t => t.done === 1 || t.status === 1).length;
document.getElementById('statTodoTotal').textContent = total;
document.getElementById('statTodoDone').textContent = done;
}
});
}
function loadGrowthInfo() {
const token = localStorage.getItem('user_token');
fetch('/api/web/growth/info', { headers: { 'token': token } })
.then(res => res.json())
.then(data => {
if (data.code === 200) {
const g = data.data;
document.getElementById('growthLevel').textContent = g.level || 1;
// 直接使用后端返回的字段
const currentExp = g.currentExp || 0;
const expToNext = g.nextLevelExp || 100;
const progress = g.levelProgress || 0;
document.getElementById('growthExpText').textContent = currentExp + ' / ' + expToNext + ' 经验';
document.getElementById('growthRemain').textContent = '还需 ' + expToNext + ' 经验升级';
document.getElementById('growthExpBar').style.width = Math.min(progress, 100) + '%';
// 更新统计数据
document.getElementById('statLevel').textContent = g.level || 1;
document.getElementById('statExp').textContent = g.experience || 0;
// 渲染成就
renderProfileAchievements(g.achievements?.list || []);
}
});
}
// 存储成就数据用于筛选
let profileAchievementsData = [];
let profileAchCategories = [];
// 渲染个人中心成就
function renderProfileAchievements(unlockedList) {
const token = localStorage.getItem('user_token');
// 获取成就分类
fetch('/api/web/growth/rules', { headers: { 'token': token } })
.then(res => res.json())
.then(data => {
if (data.code === 200) {
const sections = data.data.sections;
const achSection = sections.find(s => s.type === 'achievement_system');
if (achSection && achSection.categories) {
profileAchCategories = achSection.categories;
// 更新分类下拉框 - 使用小写字段名
const select = document.getElementById('profileAchFilter');
if (select) {
select.innerHTML = '<option value="all">全部分类</option>' +
profileAchCategories.map(c => `<option value="${c.key || c.Key}">${c.name || c.Name}</option>`).join('');
}
}
// 按稀有度排序(传说 > 史诗 > 稀有 > 高级 > 中级 > 初级)
const rarityOrder = { '传说': 6, '史诗': 5, '稀有': 4, '高级': 3, '中级': 2, '初级': 1 };
profileAchievementsData = (unlockedList || []).sort((a, b) => {
return (rarityOrder[b.rarityName || b.rarity] || 0) - (rarityOrder[a.rarityName || a.rarity] || 0);
});
renderProfileAchList('all');
}
})
.catch(err => {
console.error('加载成就分类失败:', err);
// 即使没有分类,也尝试渲染成就
profileAchievementsData = unlockedList || [];
renderProfileAchList('all');
});
}
function filterProfileAchievements() {
const filter = document.getElementById('profileAchFilter').value;
renderProfileAchList(filter);
}
function renderProfileAchList(filter) {
const container = document.getElementById('recentAchievements');
let filtered = profileAchievementsData;
if (filter !== 'all') {
filtered = profileAchievementsData.filter(ach => ach.category === filter);
}
if (filtered.length === 0) {
container.innerHTML = '<div style="text-align: center; padding: 40px; color: #94a3b8; grid-column: 1/-1;"><span style="font-size: 48px; display: block; margin-bottom: 12px;">🏆</span><p>' + (filter === 'all' ? '暂无成就,继续努力吧!' : '该分类暂无成就') + '</p></div>';
return;
}
let html = '';
filtered.forEach(ach => {
const color = ach.rarityColor || '#4F9EDE';
const icon = ach.icon || '🏆';
const name = ach.name || '未知成就';
const desc = ach.description || ach.desc || '';
const rarityName = ach.rarityName || ach.rarity || '';
html += `
<div style="text-align: center; padding: 16px; border-radius: 12px; background: linear-gradient(135deg, rgba(79,158,222,0.08) 0%, rgba(79,158,222,0.02) 100%);">
<div style="width: 40px; height: 40px; border-radius: 10px; display: flex; align-items: center; justify-content: center; margin: 0 auto 10px; background: ${color}20;">
<span style="font-size: 20px;">${icon}</span>
</div>
<h4 style="font-size: 13px; font-weight: 600; color: ${color}; margin-bottom: 2px;">${name}</h4>
${desc ? '<p style="font-size: 11px; color: #64748b; margin-bottom: 4px;">' + desc + '</p>' : ''}
${rarityName ? '<span style="display: inline-block; padding: 2px 8px; background: ' + color + '15; color: ' + color + '; font-size: 10px; border-radius: 4px;">' + rarityName + '</span>' : ''}
</div>
`;
});
container.innerHTML = html;
}
function renderAvatarGrid() {
const container = document.getElementById('avatarGrid');
let html = '';
DEFAULT_AVATARS.forEach(avatar => {
const isSelected = selectedAvatarUrl === avatar.url;
html += `
<div class="avatar-option ${isSelected ? 'selected' : ''}" onclick="selectAvatar('${avatar.url}', this)" style="border-radius: 50%; overflow: hidden; width: 80px; height: 80px; margin: 0 auto;">
<img src="${avatar.url}" style="width: 100%; height: 100%; object-fit: cover;" alt="${avatar.label}">
</div>
`;
});
container.innerHTML = html;
}
function selectAvatar(url, element) {
selectedAvatarUrl = url;
document.querySelectorAll('.avatar-option').forEach(el => el.classList.remove('selected'));
element.classList.add('selected');
}
function showAvatarModal() {
renderAvatarGrid();
document.getElementById('avatarModal').style.display = 'flex';
}
function closeAvatarModal() {
document.getElementById('avatarModal').style.display = 'none';
}
function saveAvatar() {
if (!selectedAvatarUrl) {
$toast.error('请选择头像');
return;
}
const token = localStorage.getItem('user_token');
fetch('/api/web/user/edit', {
method: 'POST',
headers: { 'Content-Type': 'application/json', 'token': token },
body: JSON.stringify({ avatar: selectedAvatarUrl })
})
.then(res => res.json())
.then(data => {
if (data.code === 200) {
$toast.success('头像更换成功');
localStorage.setItem('user_avatar', selectedAvatarUrl);
loadUserInfo();
closeAvatarModal();
} else {
$toast.error(data.msg || '更换失败');
}
});
}
function showEditModal() {
const nickname = document.getElementById('userNickname').textContent;
const signature = document.getElementById('userSignature').textContent;
document.getElementById('editNickname').value = nickname;
document.getElementById('editSignature').value = signature === '还没有个性签名' ? '' : signature;
document.getElementById('editModal').style.display = 'flex';
}
function closeEditModal() {
document.getElementById('editModal').style.display = 'none';
}
function saveProfile() {
const token = localStorage.getItem('user_token');
const nickname = document.getElementById('editNickname').value;
const signature = document.getElementById('editSignature').value;
if (!nickname) {
$toast.error('请输入昵称');
return;
}
fetch('/api/web/user/edit', {
method: 'POST',
headers: { 'Content-Type': 'application/json', 'token': token },
body: JSON.stringify({ nickname, signature })
})
.then(res => res.json())
.then(data => {
if (data.code === 200) {
$toast.success('保存成功');
closeEditModal();
document.getElementById('userNickname').textContent = nickname;
document.getElementById('userSignature').textContent = signature || '还没有个性签名';
localStorage.setItem('user_nickname', nickname);
} else {
$toast.error(data.msg || '保存失败');
}
});
}
</script>
</body>
</html>