feat: 优化管理后台页面UI、修复TS编译错误、新增人生事件模块
- 优化 AI 配置列表页面:重构统计卡片、搜索表单、表格列展示 - 修复 3 处 TypeScript TS6133 编译错误,恢复构建 - 新增管理员修改密码和重置密码功能 - 优化小程序多个页面样式和交互 - 人生事件模块完善 Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
Vendored
+258
@@ -0,0 +1,258 @@
|
||||
import { state } from './data.js';
|
||||
import { RegistrationWizard, AppPages } from './components.js';
|
||||
|
||||
const DOM = {
|
||||
onboarding: document.getElementById('onboarding'),
|
||||
wizardContent: document.getElementById('wizard-content'),
|
||||
mainContent: document.getElementById('main-content'),
|
||||
pageRender: document.getElementById('page-render'),
|
||||
bottomNav: document.getElementById('bottom-nav'),
|
||||
musicToggle: document.getElementById('music-toggle'),
|
||||
bgMusic: document.getElementById('bg-music'),
|
||||
musicDisc: document.getElementById('music-disc'),
|
||||
profileBtn: document.getElementById('profile-btn')
|
||||
};
|
||||
|
||||
function initStars() {
|
||||
const starContainer = document.getElementById('stars');
|
||||
starContainer.innerHTML = '';
|
||||
for (let i = 0; i < 60; i++) {
|
||||
const star = document.createElement('div');
|
||||
star.className = 'star';
|
||||
const size = Math.random() * 3 + 1;
|
||||
const xMove = (Math.random() - 0.5) * 100;
|
||||
const yMove = (Math.random() - 0.5) * 100;
|
||||
const duration = 15 + Math.random() * 20;
|
||||
const delay = Math.random() * -20;
|
||||
const opacity = 0.2 + Math.random() * 0.5;
|
||||
|
||||
star.style.width = `${size}px`;
|
||||
star.style.height = `${size}px`;
|
||||
star.style.left = `${Math.random() * 100}%`;
|
||||
star.style.top = `${Math.random() * 100}%`;
|
||||
star.style.setProperty('--x', `${xMove}px`);
|
||||
star.style.setProperty('--y', `${yMove}px`);
|
||||
star.style.setProperty('--duration', `${duration}s`);
|
||||
star.style.setProperty('--opacity', opacity);
|
||||
star.style.animationDelay = `${delay}s`;
|
||||
|
||||
starContainer.appendChild(star);
|
||||
}
|
||||
}
|
||||
|
||||
function updateWizard() {
|
||||
DOM.wizardContent.innerHTML = `
|
||||
<div class="flex-1 overflow-y-auto custom-scroll">
|
||||
${RegistrationWizard.renderStep(state.onboardingStep, state.user)}
|
||||
</div>
|
||||
<div class="pt-6 flex justify-between items-center">
|
||||
<div class="flex gap-1.5">
|
||||
${[0, 1, 2, 3, 4].map(i => `<div class="w-6 h-1 rounded-full transition-all duration-700 ${i === state.onboardingStep ? 'bg-purple-400 w-10 shadow-[0_0_12px_rgba(168,85,247,0.6)]' : 'bg-white/10'}"></div>`).join('')}
|
||||
</div>
|
||||
<button id="next-step" class="px-10 py-3.5 bg-gradient-to-r from-purple-500 to-violet-400 rounded-2xl text-white text-sm font-bold shadow-xl shadow-purple-500/10 active:scale-95 transition-all">
|
||||
${state.onboardingStep === 4 ? '同步星海' : '继续'}
|
||||
</button>
|
||||
</div>
|
||||
`;
|
||||
lucide.createIcons();
|
||||
|
||||
document.getElementById('next-step').addEventListener('click', () => {
|
||||
saveCurrentStepData();
|
||||
if (state.onboardingStep < 4) {
|
||||
state.onboardingStep++;
|
||||
gsap.fromTo('#wizard-content > div:first-child', { opacity: 0, y: 20 }, { opacity: 1, y: 0, duration: 0.5, ease: "power2.out" });
|
||||
updateWizard();
|
||||
} else {
|
||||
completeOnboarding();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function saveCurrentStepData() {
|
||||
const s = state.onboardingStep;
|
||||
if (s === 0) {
|
||||
state.user.nickname = document.getElementById('nickname').value;
|
||||
state.user.gender = document.getElementById('gender').value;
|
||||
state.user.zodiac = document.getElementById('zodiac').value;
|
||||
state.user.mbti = document.getElementById('mbti').value;
|
||||
state.user.hobbies = document.getElementById('hobbies').value.split(',').map(v => v.trim()).filter(v => v);
|
||||
} else if (s === 1) {
|
||||
state.user.childhood = document.getElementById('childhood').value;
|
||||
state.user.childhoodDate = document.getElementById('childhood-date').value;
|
||||
} else if (s === 2) {
|
||||
state.user.happyMoment = document.getElementById('happy-moment').value;
|
||||
state.user.happyDate = document.getElementById('happy-date').value;
|
||||
} else if (s === 3) {
|
||||
state.user.lowPoint = document.getElementById('low-point').value;
|
||||
state.user.lowDate = document.getElementById('low-date').value;
|
||||
} else if (s === 4) {
|
||||
state.user.aspirations = document.getElementById('aspirations').value;
|
||||
}
|
||||
}
|
||||
|
||||
function completeOnboarding() {
|
||||
gsap.to(DOM.onboarding, {
|
||||
opacity: 0,
|
||||
duration: 1.5,
|
||||
ease: "power3.inOut",
|
||||
onComplete: () => {
|
||||
DOM.onboarding.style.display = 'none';
|
||||
DOM.mainContent.style.display = 'flex';
|
||||
DOM.bottomNav.style.display = 'flex';
|
||||
switchTab('record');
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function switchTab(tab) {
|
||||
document.querySelectorAll('.nav-item').forEach(el => {
|
||||
el.classList.toggle('active', el.dataset.tab === tab);
|
||||
});
|
||||
|
||||
let content = "";
|
||||
if (tab === 'record') content = AppPages.record(state.events);
|
||||
if (tab === 'script') content = AppPages.script(state.scripts, state.isGenerating, state.scriptConfig, state.npcConfig, state.user, state.customPersonas);
|
||||
if (tab === 'path') content = AppPages.path(state.currentPath);
|
||||
if (tab === 'profile') content = AppPages.profile(state.user);
|
||||
|
||||
DOM.pageRender.innerHTML = content;
|
||||
lucide.createIcons();
|
||||
|
||||
if (tab === 'record') {
|
||||
document.getElementById('save-event').addEventListener('click', handleSaveEvent);
|
||||
}
|
||||
if (tab === 'script') {
|
||||
document.getElementById('gen-script')?.addEventListener('click', handleGenerateScript);
|
||||
|
||||
|
||||
document.getElementById('edit-name')?.addEventListener('input', (e) => state.user.nickname = e.target.value);
|
||||
document.getElementById('edit-zodiac')?.addEventListener('change', (e) => state.user.zodiac = e.target.value);
|
||||
document.getElementById('edit-mbti')?.addEventListener('change', (e) => state.user.mbti = e.target.value);
|
||||
document.getElementById('edit-job')?.addEventListener('input', (e) => state.user.profession = e.target.value);
|
||||
|
||||
|
||||
document.getElementById('add-persona-btn')?.addEventListener('click', () => {
|
||||
const name = document.getElementById('npc-name').value;
|
||||
const role = document.getElementById('npc-role').value;
|
||||
if (name) {
|
||||
state.customPersonas.push({ name, role, id: Date.now() });
|
||||
switchTab('script');
|
||||
}
|
||||
});
|
||||
|
||||
document.querySelectorAll('.delete-persona-btn').forEach(btn => {
|
||||
btn.addEventListener('click', (e) => {
|
||||
const idx = e.currentTarget.dataset.idx;
|
||||
state.customPersonas.splice(idx, 1);
|
||||
switchTab('script');
|
||||
});
|
||||
});
|
||||
|
||||
document.getElementById('npc-name')?.addEventListener('input', (e) => state.npcConfig.name = e.target.value);
|
||||
document.getElementById('npc-role')?.addEventListener('change', (e) => state.npcConfig.role = e.target.value);
|
||||
document.getElementById('npc-relation')?.addEventListener('change', (e) => state.npcConfig.relation = e.target.value);
|
||||
document.getElementById('npc-desc')?.addEventListener('input', (e) => state.npcConfig.desc = e.target.value);
|
||||
|
||||
document.querySelectorAll('.style-btn').forEach(btn => {
|
||||
btn.addEventListener('click', () => {
|
||||
state.scriptConfig.style = btn.dataset.val;
|
||||
switchTab('script');
|
||||
});
|
||||
});
|
||||
document.querySelectorAll('.length-btn').forEach(btn => {
|
||||
btn.addEventListener('click', () => {
|
||||
state.scriptConfig.length = btn.dataset.val;
|
||||
switchTab('script');
|
||||
});
|
||||
});
|
||||
|
||||
document.querySelectorAll('.select-script').forEach(btn => {
|
||||
btn.addEventListener('click', (e) => {
|
||||
const id = e.currentTarget.dataset.id;
|
||||
state.currentPath = state.scripts.find(s => s.id == id);
|
||||
switchTab('path');
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
function handleSaveEvent() {
|
||||
const title = document.getElementById('event-title').value;
|
||||
const time = document.getElementById('event-time').value;
|
||||
const content = document.getElementById('event-content').value;
|
||||
|
||||
if (!title || !content) return;
|
||||
|
||||
const newEvent = {
|
||||
id: Date.now(),
|
||||
title,
|
||||
time: time || new Date().toISOString().split('T')[0],
|
||||
content,
|
||||
aiReply: "星河守望者正在解读这段紫色波频...",
|
||||
isNew: true
|
||||
};
|
||||
|
||||
state.events.unshift(newEvent);
|
||||
switchTab('record');
|
||||
|
||||
setTimeout(() => {
|
||||
const fullReply = `这段记忆碎片在星海中漾起涟漪。你在${title}中展现的特质,正在重新定义你人生OS的底层代码。继续保持这份觉知。`;
|
||||
state.events[0].aiReply = fullReply;
|
||||
state.events[0].isNew = false;
|
||||
switchTab('record');
|
||||
}, 1500);
|
||||
}
|
||||
|
||||
function handleGenerateScript() {
|
||||
const theme = document.getElementById('script-theme').value;
|
||||
if (!theme) return;
|
||||
|
||||
if (state.isGenerating) return;
|
||||
state.isGenerating = true;
|
||||
switchTab('script');
|
||||
|
||||
setTimeout(() => {
|
||||
const { name, role, relation, desc } = state.npcConfig;
|
||||
const npcFrag = name ? `,在 ${name}(你的${role},关系:${relation})的陪伴下,` : ",";
|
||||
|
||||
const script = {
|
||||
id: Date.now(),
|
||||
title: `《${theme}》· 星源篇`,
|
||||
summary: `基于你(${state.user.nickname},一名${state.user.profession})过往的紫气东来,在${state.scriptConfig.style}的世界观中${npcFrag}你将重新定义自我。${desc ? '命运齿轮已然转动:' + desc : ''}`,
|
||||
persona: state.user.mbti || "追光者",
|
||||
steps: [
|
||||
{ task: "紫微入命", desc: `在"${theme}"的意志中开启新的人生循环。`, done: true },
|
||||
{ task: "能量共振", desc: name ? `与 ${name} 达成灵魂契约。` : "在孤独中寻找真理,掌握世界的运行法则。", done: true },
|
||||
{ task: "代码重写", desc: "在最为艰难的时刻,将过去的所有记录化作突围的力量。", done: false },
|
||||
{ task: "最终归宿", desc: "达成人生OS的最高成就,实现与理想自我的合一。", done: false }
|
||||
]
|
||||
};
|
||||
state.scripts.unshift(script);
|
||||
state.isGenerating = false;
|
||||
switchTab('script');
|
||||
}, 2500);
|
||||
}
|
||||
|
||||
DOM.musicToggle.addEventListener('click', () => {
|
||||
if (state.isPlaying) {
|
||||
DOM.bgMusic.pause();
|
||||
DOM.musicDisc.classList.remove('animate-spin-slow');
|
||||
DOM.musicToggle.classList.add('opacity-40');
|
||||
} else {
|
||||
DOM.bgMusic.play();
|
||||
DOM.musicDisc.classList.add('animate-spin-slow');
|
||||
DOM.musicToggle.classList.remove('opacity-40');
|
||||
}
|
||||
state.isPlaying = !state.isPlaying;
|
||||
});
|
||||
|
||||
DOM.bottomNav.addEventListener('click', (e) => {
|
||||
const btn = e.target.closest('button');
|
||||
if (btn) switchTab(btn.dataset.tab);
|
||||
});
|
||||
|
||||
DOM.profileBtn.addEventListener('click', () => switchTab('profile'));
|
||||
|
||||
initStars();
|
||||
updateWizard();
|
||||
lucide.createIcons();
|
||||
Reference in New Issue
Block a user