Files
happy-life-star/开心APP网页代码v1.1/wnD97OS/topic_tracker.js
T
2025-07-26 00:37:18 +08:00

320 lines
15 KiB
JavaScript

document.addEventListener('DOMContentLoaded', () => {
const TOPICS_STORAGE_KEY = 'kaixinapp_user_topics_v4';
let nextUserTopicId = 1;
let aiTopics = [{
id: "ai-1",
date: "2025年7月14日",
title: "关于职业发展的思考",
summary: "近期你在日记和聊天中多次提到对目前工作的倦怠感,并探索学习新技能(如编程、设计)的可能性。",
keywords: ["职业倦怠", "新技能", "转行", "自我提升"],
timeline: [
{
stage_name: "探索阶段 (前期)",
stage_icon: "search",
items: [
{ id: 2, date: '2025-07-10', content: '搜索了在线编程课程,感觉眼花缭乱。' },
{ id: 3, date: '2025-07-12', content: '在日记里写下了对目前工作的厌烦,感觉陷入了瓶颈。' }
]
},
{
stage_name: "调研阶段 (当前)",
stage_icon: "clipboard-list",
items: [
{ id: 1, date: '2025-07-14', content: '和开开聊了关于转行做设计师的可能性,开开给了一些建议。' }
]
}
],
next_suggestion: '尝试接触一些免费的设计工具(如Figma、Canva),完成一个名片设计或海报制作的小项目。这能帮你评估自己对设计工作的实际兴趣和基本感觉。'
}, {
id: "ai-2",
date: "2025年7月10日",
title: "周末出游计划",
summary: "你似乎在计划一个短途旅行,多次查询关于海边城市的天气和美食推荐。",
keywords: ["旅行", "海边", "美食", "放松"],
timeline: [
{
stage_name: "萌芽阶段 (前期)",
stage_icon: "sprout",
items: [
{ id: 2, date: '2025-07-09', content: '天气好热,突然想去海边玩。' }
]
},
{
stage_name: "计划阶段 (当前)",
stage_icon: "map",
items: [
{ id: 1, date: '2025-07-10', content: '问开开哪个海边城市人少又好玩,它推荐了几个小众地点。' },
]
}
],
next_suggestion: '可以开始查看交通和住宿了,早点预定选择更多哦!如果需要,开开可以帮你对比价格。'
}];
let userTopics = [];
const aiSummaryContainer = document.getElementById('ai-summary-list');
const userTopicsListContainer = document.getElementById('user-topics-list');
const newTopicForm = document.getElementById('new-topic-form');
const modal = document.getElementById('topic-detail-modal');
const modalTitle = document.getElementById('modal-topic-title');
const modalDate = document.getElementById('modal-topic-date');
const closeModalBtn = document.getElementById('close-modal-btn');
const addEntryForm = document.getElementById('add-entry-form');
let activeTopicId = null;
function loadTopicsFromStorage() {
const stored = localStorage.getItem(TOPICS_STORAGE_KEY);
if (stored) {
userTopics = JSON.parse(stored);
const maxId = userTopics.reduce((max, t) => Math.max(max, parseInt(t.id.split('-')[1])), 0);
nextUserTopicId = maxId + 1;
} else {
userTopics = [{
id: `user-${nextUserTopicId++}`,
title: "暑期健身计划",
date: "2025年7月15日",
keywords: ["健康", "运动", "自律"],
timeline: [{
stage_name: "启动阶段 (当前)",
stage_icon: "rocket",
items: [
{ id: 1, date: '2025-07-15', content: "今天制定了计划:每周至少三次有氧运动,两次力量训练。记录每日饮食,控制热量摄入。" }
]
}],
next_suggestion: '找一个伙伴一起监督,或者使用App记录进程,增加成就感。'
}];
}
}
function saveTopicsToStorage() {
localStorage.setItem(TOPICS_STORAGE_KEY, JSON.stringify(userTopics));
}
function renderAiTopics() {
if (!aiSummaryContainer) return;
aiSummaryContainer.innerHTML = aiTopics.map(topic => `
<div data-topic-id="${topic.id}" class="bg-white p-6 rounded-xl shadow-md border border-gray-200/80 cursor-pointer hover:shadow-lg hover:border-tech-blue/50 transition-all duration-300">
<p class="text-sm text-text-medium mb-2">${topic.date}</p>
<h3 class="text-xl font-bold text-text-dark mb-3">${topic.title}</h3>
<p class="text-text-medium mb-4 text-sm">${topic.summary}</p>
<div class="flex flex-wrap gap-2">
${topic.keywords.map(k => `<span class="inline-block bg-tech-blue/10 text-tech-blue text-xs font-medium px-2.5 py-0.5 rounded-full">${k}</span>`).join('')}
</div>
</div>`).join('');
aiSummaryContainer.querySelectorAll('[data-topic-id]').forEach(el => el.addEventListener('click', () => openModal(el.dataset.topicId)));
}
function renderUserTopics() {
if (!userTopicsListContainer) return;
const getLastEntryContent = (topic) => {
if (!topic.timeline || topic.timeline.length === 0) return "暂无内容";
const lastStage = topic.timeline[topic.timeline.length - 1];
if (!lastStage.items || lastStage.items.length === 0) return "暂无内容";
return lastStage.items[lastStage.items.length - 1].content;
};
userTopicsListContainer.innerHTML = userTopics.length === 0 ? `<p class="text-text-medium text-center italic text-sm py-4">还没有创建话题哦</p>` :
userTopics.map(topic => `
<div class="bg-light-gray p-4 rounded-lg group transition-colors duration-200 hover:bg-gray-200/60">
<div class="flex justify-between items-start">
<div data-topic-id="${topic.id}" class="cursor-pointer flex-1 min-w-0 pr-2">
<h4 class="font-semibold text-text-dark group-hover:text-tech-blue transition-colors truncate">${topic.title}</h4>
<p class="text-sm text-text-medium mt-1 truncate">${getLastEntryContent(topic)}</p>
</div>
<button data-delete-id="${topic.id}" class="text-gray-400 hover:text-red-500 transition-colors opacity-0 group-hover:opacity-100 flex-shrink-0" title="删除话题">
<i data-lucide="trash-2" class="w-4 h-4"></i>
</button>
</div>
</div>`).join('');
userTopicsListContainer.querySelectorAll('[data-topic-id]').forEach(el => el.addEventListener('click', () => openModal(el.dataset.topicId)));
userTopicsListContainer.querySelectorAll('[data-delete-id]').forEach(el => el.addEventListener('click', (e) => {
e.stopPropagation();
if(confirm('确定要删除这个话题吗?此操作无法撤销。')) {
deleteTopic(el.dataset.deleteId);
}
}));
if (typeof lucide !== 'undefined') lucide.createIcons();
}
function deleteTopic(topicId) {
userTopics = userTopics.filter(t => t.id !== topicId);
saveTopicsToStorage();
renderUserTopics();
}
function openModal(topicId) {
const topic = [...aiTopics, ...userTopics].find(t => t.id === topicId);
if (!topic) return;
activeTopicId = topicId;
const isUserTopic = activeTopicId.startsWith('user-');
document.getElementById('add-entry-form').style.display = isUserTopic ? 'flex' : 'none';
document.querySelector('#add-entry-form').previousElementSibling.style.display = isUserTopic ? 'block' : 'none';
modalTitle.textContent = topic.title;
modalDate.textContent = `创建于 ${topic.date}`;
renderTopicTimeline(topic);
modal.classList.remove('hidden');
modal.classList.add('flex');
}
function renderTopicTimeline(topic){
const timelineContainer = document.getElementById('modal-timeline-container');
const suggestionContainer = document.getElementById('modal-suggestion-container');
timelineContainer.innerHTML = '';
suggestionContainer.innerHTML = '';
const isUserTopic = topic.id.startsWith('user-');
if(topic.timeline && topic.timeline.length > 0) {
topic.timeline.forEach((stage, stageIndex) => {
const isCurrentStage = stageIndex === topic.timeline.length - 1;
const stageHtml = `
<div class="stage-group">
<div class="flex items-center space-x-3 mb-4">
<div class="w-10 h-10 ${isCurrentStage ? 'bg-tech-blue/20' : 'bg-gray-200'} rounded-full flex items-center justify-center flex-shrink-0">
<i data-lucide="${stage.stage_icon || 'flag'}" class="w-5 h-5 ${isCurrentStage ? 'text-tech-blue' : 'text-gray-600'}"></i>
</div>
<h3 class="text-xl font-bold ${isCurrentStage ? 'text-text-dark' : 'text-gray-500'}">${stage.stage_name}</h3>
${isCurrentStage ? '<span class="text-xs bg-tech-blue text-white font-bold py-0.5 px-2 rounded-full">当前</span>' : ''}
</div>
<div class="relative border-l-2 ${isCurrentStage ? 'border-tech-blue/30' : 'border-gray-300'} ml-5 pl-10 space-y-6">
${stage.items.sort((a,b) => new Date(b.date) - new Date(a.date)).map(entry => `
<div class="relative group">
<div class="absolute -left-[45px] top-1.5 w-4 h-4 bg-white border-2 ${isCurrentStage ? 'border-tech-blue' : 'border-gray-400'} rounded-full"></div>
<div class="bg-white p-4 rounded-xl shadow-sm border">
<div class="flex justify-between items-center">
<p class="text-sm text-text-medium mb-1 font-medium">${new Date(entry.date).toLocaleDateString('zh-CN', { year: 'numeric', month: 'long', day: 'numeric' })}</p>
${isUserTopic ? `<button data-delete-entry-id="${entry.id}" class="text-gray-400 hover:text-red-500 opacity-0 group-hover:opacity-100 transition-opacity p-1 -mr-1" title="删除此条目"><i data-lucide="x" class="w-4 h-4"></i></button>` : ''}
</div>
<p class="text-text-dark text-sm">${entry.content.replace(/\\n/g, '<br>')}</p>
</div>
</div>
`).join('')}
</div>
</div>
`;
timelineContainer.innerHTML += stageHtml;
});
}
if (topic.next_suggestion) {
suggestionContainer.innerHTML = `
<div class="flex items-start bg-amber-50 p-4 rounded-lg border border-amber-200">
<i data-lucide="lightbulb" class="w-6 h-6 text-amber-500 mr-3 mt-1 flex-shrink-0"></i>
<div>
<h4 class="font-bold text-amber-800">开开的下一步建议</h4>
<p class="text-sm text-amber-700 mt-1">${topic.next_suggestion}</p>
</div>
</div>`;
}
timelineContainer.querySelectorAll('[data-delete-entry-id]').forEach(btn => {
btn.addEventListener('click', () => deleteTopicEntry(btn.dataset.deleteEntryId));
});
if (typeof lucide !== 'undefined') lucide.createIcons();
}
function deleteTopicEntry(entryId) {
const topic = userTopics.find(t => t.id === activeTopicId);
if (!topic) return;
const totalEntries = topic.timeline.reduce((acc, stage) => acc + stage.items.length, 0);
if (totalEntries <= 1) {
alert('每个话题至少需要一条记录。如不需此话题,可直接删除整个话题卡片。');
return;
}
topic.timeline.forEach(stage => {
stage.items = stage.items.filter(entry => entry.id.toString() !== entryId.toString());
});
topic.timeline = topic.timeline.filter(stage => stage.items.length > 0);
saveTopicsToStorage();
renderTopicTimeline(topic);
}
function closeModal() {
activeTopicId = null;
modal.classList.add('hidden');
modal.classList.remove('flex');
}
newTopicForm.addEventListener('submit', (e) => {
e.preventDefault();
const title = e.target.elements.title.value.trim();
const content = e.target.elements.content.value.trim();
if (title && content) {
const newTopic = {
id: `user-${nextUserTopicId++}`,
title: title,
date: new Date().toLocaleDateString('zh-CN'),
keywords: ["自定义"],
timeline: [{
stage_name: "启动阶段 (当前)",
stage_icon: "rocket",
items: [{ id: 1, date: new Date().toISOString().split('T')[0], content: content }]
}],
next_suggestion: '将大目标分解成几个可执行的小步骤吧!'
};
userTopics.unshift(newTopic);
saveTopicsToStorage();
renderUserTopics();
e.target.reset();
}
});
addEntryForm.addEventListener('submit', (e) => {
e.preventDefault();
const contentEl = e.target.elements['new-entry-content'];
const content = contentEl.value.trim();
if (!content || !activeTopicId) return;
const topic = userTopics.find(t => t.id === activeTopicId);
if(!topic) return;
let maxId = 0;
topic.timeline.forEach(stage => {
maxId = Math.max(maxId, ...stage.items.map(item => item.id));
});
const newEntry = {
id: maxId + 1,
date: new Date().toISOString().split('T')[0],
content: content
};
if (topic.timeline.length > 0) {
topic.timeline[topic.timeline.length - 1].items.push(newEntry);
} else {
topic.timeline.push({
stage_name: "新进展 (当前)",
stage_icon: "plus",
items: [newEntry]
})
}
saveTopicsToStorage();
renderTopicTimeline(topic);
contentEl.value = '';
});
closeModalBtn.addEventListener('click', closeModal);
modal.addEventListener('click', (e) => {
if (e.target === modal) closeModal();
});
loadTopicsFromStorage();
renderAiTopics();
renderUserTopics();
if (typeof lucide !== 'undefined') {
lucide.createIcons();
}
});