- 新增「简记memo」一体化小程序产品原型设计文档 - 新增简记memo完整版UI视觉设计规范和界面细节 - 添加IDEA项目配置文件.gitignore - 创建404页面HTML文件,包含响应式布局和错误提示 - 添加关于页面HTML文件,展示品牌介绍和团队信息 - 实现AES加解密工具函数,支持请求体加密 - 添加用户协议页面基础框架
258 lines
16 KiB
HTML
258 lines
16 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; }
|
|
.achievement-card { transition: all 0.2s; }
|
|
.achievement-card:hover { transform: translateY(-2px); box-shadow: 0 4px 12px rgba(0,0,0,0.08); }
|
|
.level-item { transition: all 0.15s; }
|
|
.level-item:hover { background: #f1f5f9; }
|
|
</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: 360px 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 style="width: 100px; height: 100px; background: rgba(255,255,255,0.2); border-radius: 50%; display: flex; align-items: center; justify-content: center; margin: 0 auto 16px; position: relative;">
|
|
<div style="font-size: 36px; font-weight: 700;" id="userLevel">Lv.1</div>
|
|
<div style="position: absolute; bottom: -6px; right: -6px; width: 32px; height: 32px; background: #fbbf24; border-radius: 50%; display: flex; align-items: center; justify-content: center; font-size: 14px;">⭐</div>
|
|
</div>
|
|
<h2 style="font-size: 20px; font-weight: 600; margin-bottom: 4px;">成长等级</h2>
|
|
<p style="font-size: 13px; opacity: 0.9; margin-bottom: 20px;" id="expInfo">0 / 100 经验</p>
|
|
|
|
<div style="background: rgba(255,255,255,0.2); border-radius: 4px; height: 8px; overflow: hidden;">
|
|
<div id="expBar" style="height: 100%; background: #fff; border-radius: 4px; transition: width 0.5s; width: 0%;"></div>
|
|
</div>
|
|
<p style="font-size: 12px; opacity: 0.85; margin-top: 8px;" id="expRemain">还需 100 经验升级</p>
|
|
</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: grid; grid-template-columns: 1fr 1fr; gap: 12px;">
|
|
<div style="text-align: center; padding: 16px; background: #f8fafc; border-radius: 8px;">
|
|
<div style="font-size: 24px; font-weight: 700; color: #4F9EDE;" id="statTotalExp">0</div>
|
|
<div style="font-size: 12px; color: #64748b; margin-top: 4px;">累计经验</div>
|
|
</div>
|
|
<div style="text-align: center; padding: 16px; background: #f8fafc; border-radius: 8px;">
|
|
<div style="font-size: 24px; font-weight: 700; color: #f59e0b;" id="statAchievements">0</div>
|
|
<div style="font-size: 12px; color: #64748b; margin-top: 4px;">获得成就</div>
|
|
</div>
|
|
<div style="text-align: center; padding: 16px; background: #f8fafc; border-radius: 8px;">
|
|
<div style="font-size: 24px; font-weight: 700; color: #22c55e;" id="statDays">0</div>
|
|
<div style="font-size: 12px; color: #64748b; margin-top: 4px;">连续记录</div>
|
|
</div>
|
|
<div style="text-align: center; padding: 16px; background: #f8fafc; border-radius: 8px;">
|
|
<div style="font-size: 24px; font-weight: 700; color: #8b5cf6;" id="statTasks">0</div>
|
|
<div style="font-size: 12px; color: #64748b; margin-top: 4px;">完成任务</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- 等级列表 -->
|
|
<div style="background: #fff; border-radius: 12px; padding: 20px; flex: 1;">
|
|
<h3 style="font-size: 14px; font-weight: 600; color: #1e293b; margin-bottom: 16px;">等级列表</h3>
|
|
<div id="levelList" style="display: flex; flex-direction: column; gap: 8px; max-height: 400px; overflow-y: auto;">
|
|
<!-- 动态生成 -->
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- 右侧:成就展示 -->
|
|
<div style="background: #fff; border-radius: 12px; padding: 24px;">
|
|
<div style="display: flex; align-items: center; justify-content: space-between; margin-bottom: 24px;">
|
|
<h3 style="font-size: 18px; font-weight: 600; color: #1e293b;">我的成就</h3>
|
|
<div style="display: flex; align-items: center; gap: 12px;">
|
|
<select id="achievementFilter" onchange="filterAchievements()" style="padding: 6px 12px; border: 1px solid #e2e8f0; border-radius: 6px; font-size: 13px; color: #475569; background: #fff; cursor: pointer;">
|
|
<option value="all">全部</option>
|
|
<option value="unlocked">已获取</option>
|
|
<option value="locked">未获取</option>
|
|
</select>
|
|
<span style="font-size: 13px; color: #4F9EDE;" id="achievementProgress">0 / 8</span>
|
|
</div>
|
|
</div>
|
|
|
|
<div id="achievementsGrid" style="display: grid; grid-template-columns: repeat(auto-fill, minmax(200px, 1fr)); gap: 16px;">
|
|
<!-- 动态渲染 -->
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- 公共底部 -->
|
|
<div id="webFooter"></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>
|
|
document.addEventListener('DOMContentLoaded', function() {
|
|
// 初始化公共头部底部
|
|
initWebPage('growth');
|
|
// 加载成长信息
|
|
loadGrowthInfo();
|
|
});
|
|
|
|
function loadGrowthInfo() {
|
|
const token = localStorage.getItem('user_token');
|
|
if (!token) return;
|
|
|
|
fetch('/api/web/growth/info', {
|
|
headers: { 'token': token }
|
|
})
|
|
.then(res => res.json())
|
|
.then(data => {
|
|
if (data.code === 200) {
|
|
const g = data.data;
|
|
|
|
// 等级信息(使用后端返回数据)
|
|
document.getElementById('userLevel').textContent = 'Lv.' + g.level;
|
|
document.getElementById('expInfo').textContent = g.levelProgress + '%';
|
|
document.getElementById('expRemain').textContent = '还需 ' + g.nextLevelExp + ' 经验升级';
|
|
document.getElementById('expBar').style.width = Math.min(g.levelProgress, 100) + '%';
|
|
|
|
// 统计数据
|
|
document.getElementById('statTotalExp').textContent = g.experience || 0;
|
|
document.getElementById('statAchievements').textContent = g.achievements?.unlocked || 0;
|
|
document.getElementById('statDays').textContent = g.streakDays || 0;
|
|
document.getElementById('statTasks').textContent = g.todayProgress?.tasksCompleted || 0;
|
|
|
|
// 成就进度
|
|
const totalAch = g.achievements?.total || 0;
|
|
const unlockedAch = g.achievements?.unlocked || 0;
|
|
document.getElementById('achievementProgress').textContent = unlockedAch + ' / ' + totalAch;
|
|
|
|
// 渲染成就列表
|
|
renderAchievements(g.achievements?.list || [], totalAch);
|
|
|
|
// 渲染等级列表(使用后端数据)
|
|
renderLevelListFromAPI(g.level);
|
|
}
|
|
})
|
|
.catch(err => console.error('加载成长信息失败:', err));
|
|
}
|
|
|
|
// 渲染成就列表
|
|
// 存储成就数据用于过滤
|
|
let allAchievementsData = [];
|
|
let unlockedAchievementIds = [];
|
|
|
|
function renderAchievements(unlockedList, total) {
|
|
const container = document.getElementById('achievementsGrid');
|
|
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.achievements) {
|
|
allAchievementsData = achSection.achievements;
|
|
unlockedAchievementIds = unlockedList.map(a => a.id || a.achievement_id);
|
|
|
|
renderAchievementList('all');
|
|
}
|
|
}
|
|
});
|
|
}
|
|
|
|
function filterAchievements() {
|
|
const filter = document.getElementById('achievementFilter').value;
|
|
renderAchievementList(filter);
|
|
}
|
|
|
|
function renderAchievementList(filter) {
|
|
const container = document.getElementById('achievementsGrid');
|
|
let html = '';
|
|
|
|
let filteredAchievements = allAchievementsData;
|
|
if (filter === 'unlocked') {
|
|
filteredAchievements = allAchievementsData.filter(ach => unlockedAchievementIds.includes(ach.id));
|
|
} else if (filter === 'locked') {
|
|
filteredAchievements = allAchievementsData.filter(ach => !unlockedAchievementIds.includes(ach.id));
|
|
}
|
|
|
|
filteredAchievements.forEach(ach => {
|
|
const isUnlocked = unlockedAchievementIds.includes(ach.id);
|
|
const color = ach.rarityColor || '#9CA3AF';
|
|
const icon = ach.icon || '🏆';
|
|
const name = ach.name || '未知成就';
|
|
const desc = ach.description || ach.desc || '';
|
|
const rarityName = ach.rarityName || '';
|
|
|
|
html += `
|
|
<div class="achievement-card" style="text-align: center; padding: 20px; border-radius: 12px; background: ${isUnlocked ? 'linear-gradient(135deg, rgba(79,158,222,0.08) 0%, rgba(79,158,222,0.02) 100%)' : '#f8fafc'}; ${isUnlocked ? '' : 'opacity: 0.6;'}">
|
|
<div style="width: 48px; height: 48px; border-radius: 12px; display: flex; align-items: center; justify-content: center; margin: 0 auto 12px; background: ${isUnlocked ? color : '#e2e8f0'}20;">
|
|
<span style="font-size: 24px;">${isUnlocked ? icon : '🔒'}</span>
|
|
</div>
|
|
<h4 style="font-size: 14px; font-weight: 600; color: ${isUnlocked ? color : '#1e293b'}; margin-bottom: 4px;">${name}</h4>
|
|
${desc ? '<p style="font-size: 12px; color: #64748b; margin-bottom: 4px;">' + desc + '</p>' : ''}
|
|
${rarityName ? '<span style="display: inline-block; padding: 2px 8px; background: ' + color + '15; color: ' + color + '; font-size: 11px; border-radius: 4px;">' + rarityName + '</span>' : ''}
|
|
${isUnlocked ? '<span style="display: inline-block; margin-top: 6px; padding: 2px 8px; background: #22c55e15; color: #22c55e; font-size: 11px; border-radius: 4px;">✓ 已解锁</span>' : ''}
|
|
</div>
|
|
`;
|
|
});
|
|
|
|
if (filteredAchievements.length === 0) {
|
|
html = '<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 === 'unlocked' ? '暂无已解锁成就' : filter === 'locked' ? '所有成就已解锁!' : '暂无成就数据') + '</p></div>';
|
|
}
|
|
|
|
container.innerHTML = html;
|
|
}
|
|
|
|
function renderLevelListFromAPI(currentLevel) {
|
|
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 levelSection = sections.find(s => s.type === 'level_system');
|
|
if (levelSection && levelSection.levels) {
|
|
const container = document.getElementById('levelList');
|
|
let html = '';
|
|
levelSection.levels.forEach(item => {
|
|
const isCurrent = item.level === currentLevel;
|
|
html += `
|
|
<div style="display: flex; align-items: center; gap: 12px; padding: 12px; border-radius: 8px; ${isCurrent ? 'background: linear-gradient(135deg, rgba(79,158,222,0.1) 0%, rgba(79,158,222,0.05) 100%); border: 1px solid rgba(79,158,222,0.2);' : 'background: #f8fafc;'}">
|
|
<div style="width: 36px; height: 36px; border-radius: 6px; display: flex; align-items: center; justify-content: center; font-size: 14px; font-weight: 700; background: ${isCurrent ? 'linear-gradient(135deg, #4F9EDE 0%, #3A8BC9 100%)' : '#e2e8f0'}; color: ${isCurrent ? '#fff' : '#64748b'};">Lv.${item.level}</div>
|
|
<div style="flex: 1;">
|
|
<div style="font-size: 13px; font-weight: 600; color: ${isCurrent ? '#4F9EDE' : '#1e293b'};">${item.icon} ${item.title}</div>
|
|
<div style="font-size: 12px; color: #64748b;">${item.exp} 经验</div>
|
|
</div>
|
|
${isCurrent ? '<span style="font-size: 11px; color: #4F9EDE; background: rgba(79,158,222,0.1); padding: 2px 8px; border-radius: 4px;">当前</span>' : ''}
|
|
</div>
|
|
`;
|
|
});
|
|
container.innerHTML = html;
|
|
}
|
|
}
|
|
});
|
|
}
|
|
</script>
|
|
</body>
|
|
</html>
|