class TodoComponent extends HTMLElement { constructor() { super(); this.attachShadow({ mode: 'open' }); this.todos = []; this.selectedProject = null; this.selectedSubproject = null; this.expandedProjects = new Set(); } async fetchTodos() { try { const response = await fetch('/api/todos'); if (!response.ok) { throw new Error('获取待办事项失败'); } this.todos = await response.json(); this.render(); } catch (error) { console.error('获取待办事项时发生错误:', error); this.showError('获取待办事项失败,请稍后重试'); } } showError(message) { const errorDiv = document.createElement('div'); errorDiv.className = 'error-message'; errorDiv.textContent = message; this.shadowRoot.appendChild(errorDiv); } // 构建项目树结构 buildProjectTree() { const tree = {}; this.todos.forEach(todo => { const project = todo.project || '其它'; const subproject = todo.subproject || '其它'; if (!tree[project]) { tree[project] = { name: project, subprojects: {} }; } if (!tree[project].subprojects[subproject]) { tree[project].subprojects[subproject] = { name: subproject, todos: [] }; } tree[project].subprojects[subproject].todos.push(todo); }); return tree; } // 渲染项目树 renderProjectTree(tree) { const treeContainer = document.createElement('div'); treeContainer.className = 'project-tree'; Object.values(tree).forEach(project => { const projectNode = document.createElement('div'); projectNode.className = 'project-node'; const projectHeader = document.createElement('div'); projectHeader.className = 'project-header'; projectHeader.innerHTML = `
${project.name} (${Object.values(project.subprojects).reduce((sum, sub) => sum + sub.todos.length, 0)})
`; projectHeader.addEventListener('click', () => { this.selectedProject = project.name; this.selectedSubproject = null; this.render(); }); const expandButton = projectHeader.querySelector('.expand-icon'); expandButton.addEventListener('click', (e) => { e.stopPropagation(); if (this.expandedProjects.has(project.name)) { this.expandedProjects.delete(project.name); } else { this.expandedProjects.add(project.name); } this.render(); }); const subprojectsContainer = document.createElement('div'); subprojectsContainer.className = 'subprojects-container'; subprojectsContainer.style.display = this.expandedProjects.has(project.name) ? 'block' : 'none'; Object.values(project.subprojects).forEach(subproject => { const subprojectNode = document.createElement('div'); subprojectNode.className = 'subproject-node'; subprojectNode.innerHTML = ` ${subproject.name} (${subproject.todos.length}) `; subprojectNode.addEventListener('click', (e) => { e.stopPropagation(); this.selectedProject = project.name; this.selectedSubproject = subproject.name; this.render(); }); subprojectsContainer.appendChild(subprojectNode); }); projectNode.appendChild(projectHeader); projectNode.appendChild(subprojectsContainer); treeContainer.appendChild(projectNode); }); return treeContainer; } // 过滤待办事项 filterTodos() { if (!this.selectedProject) { return this.todos; } return this.todos.filter(todo => { const matchesProject = todo.project === this.selectedProject; if (!this.selectedSubproject) { return matchesProject; } return matchesProject && todo.subproject === this.selectedSubproject; }); } render() { // 清空现有内容 this.shadowRoot.innerHTML = ''; // 添加样式 const style = document.createElement('style'); style.textContent = ` .container { display: flex; height: 100%; font-family: Arial, sans-serif; } .project-tree { width: 250px; background: #f8f9fa; border-right: 1px solid #e9ecef; padding: 15px; overflow-y: auto; } .todo-container { flex: 1; padding: 20px; overflow-y: auto; } .project-node { margin-bottom: 10px; } .project-header { padding: 8px; background: #e9ecef; border-radius: 4px; cursor: pointer; } .project-header-content { display: flex; align-items: center; gap: 8px; } .project-header:hover { background: #dee2e6; } .expand-icon { font-size: 12px; transition: transform 0.2s; color: #495057; } .expand-icon.expanded { transform: rotate(90deg); } .subprojects-container { margin-left: 20px; margin-top: 5px; } .subproject-node { padding: 6px 8px; cursor: pointer; border-radius: 4px; display: flex; justify-content: space-between; align-items: center; } .subproject-node:hover { background: #e9ecef; } .todo-count { font-size: 0.9em; color: #6c757d; } .todo-item { background: #fff; border: 1px solid #e9ecef; border-radius: 8px; padding: 15px; margin-bottom: 15px; box-shadow: 0 2px 4px rgba(0,0,0,0.05); } .todo-item h3 { margin: 0 0 10px 0; color: #212529; } .todo-item p { margin: 5px 0; color: #495057; } .todo-item .status { display: inline-block; padding: 4px 8px; border-radius: 4px; font-size: 0.9em; margin-top: 10px; } .todo-item .status.completed { background: #d4edda; color: #155724; } .todo-item .status.pending { background: #fff3cd; color: #856404; } .todo-item .meta { font-size: 0.85em; color: #6c757d; margin-top: 10px; border-top: 1px solid #e9ecef; padding-top: 10px; } .error-message { color: #721c24; padding: 10px; background: #f8d7da; border-radius: 4px; margin: 10px; } .selected { background: #cfe2ff !important; } `; this.shadowRoot.appendChild(style); // 创建主容器 const container = document.createElement('div'); container.className = 'container'; // 构建并渲染项目树 const tree = this.buildProjectTree(); const treeContainer = this.renderProjectTree(tree); container.appendChild(treeContainer); // 创建待办事项列表容器 const todosContainer = document.createElement('div'); todosContainer.className = 'todo-container'; // 渲染过滤后的待办事项 const filteredTodos = this.filterTodos(); filteredTodos.forEach(todo => { const todoElement = document.createElement('div'); todoElement.className = 'todo-item'; // 格式化日期 const createdDate = new Date(todo.created_at).toLocaleString('zh-CN'); const scheDate = new Date(todo.sche_time).toLocaleString('zh-CN'); const completeDate = todo.complete_time ? new Date(todo.complete_time).toLocaleString('zh-CN') : '未完成'; todoElement.innerHTML = `

${todo.title}

${todo.text || '无详细描述'}

项目:${todo.project}

子项目:${todo.subproject}

执行人:${todo.exeuser || '未分配'}

计划时间:${scheDate}

${todo.completed ? '已完成' : '进行中'}

创建人:${todo.adduser}

创建时间:${createdDate}

完成时间:${completeDate}

`; todosContainer.appendChild(todoElement); }); container.appendChild(todosContainer); this.shadowRoot.appendChild(container); } connectedCallback() { // 组件被添加到DOM时获取数据 this.fetchTodos(); } } customElements.define('todo-component', TodoComponent);