优化调整
This commit is contained in:
@@ -0,0 +1,319 @@
|
||||
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();
|
||||
}
|
||||
});
|
||||
Reference in New Issue
Block a user