class TodoComponent extends HTMLElement { static { // 添加静态初始化标志 this.isInitializing = false; } constructor() { super(); this.attachShadow({ mode: 'open' }); this.todos = []; this.selectedProject = null; this.selectedSubproject = null; this.expandedProjects = new Set(); this.users = []; this.currentUser = null; this.editingTodo = null; // 当前正在编辑的待办事项 this.isInitialized = false; // 添加初始化标志 } // 初始化组件 async initialize() { // 检查是否正在初始化 if (TodoComponent.isInitializing) { return; } if (this.isInitialized) { return; } TodoComponent.isInitializing = true; try { await Promise.all([ this.fetchTodos(), this.fetchUsers(), this.getCurrentUser() ]); this.isInitialized = true; this.render(); } catch (error) { console.error('初始化组件时发生错误:', error); this.showError('初始化失败,请刷新页面重试'); } finally { TodoComponent.isInitializing = false; } } async fetchTodos() { try { const response = await fetch('/api/todos'); if (!response.ok) { throw new Error('获取待办事项失败'); } this.todos = await response.json(); } catch (error) { console.error('获取待办事项时发生错误:', error); this.showError('获取待办事项失败,请稍后重试'); } } async fetchUsers() { try { const response = await fetch('/api/users'); if (!response.ok) { throw new Error('获取用户数据失败'); } const data = await response.json(); this.users = data.users || []; } catch (error) { console.error('获取用户数据时发生错误:', error); this.showError('获取用户数据失败,请稍后重试'); } } getCurrentUser() { try { const userStr = localStorage.getItem('userInfo'); if (userStr) { this.currentUser = JSON.parse(userStr); } else { console.log('未找到登录用户信息'); } } 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); }); // 添加显示全部按钮 const showAllButton = document.createElement('button'); showAllButton.className = 'show-all-button'; showAllButton.textContent = '显示全部'; showAllButton.addEventListener('click', () => { this.selectedProject = null; this.selectedSubproject = null; this.render(); }); treeContainer.appendChild(showAllButton); return treeContainer; } // 过滤待办事项 filterTodos() { if (!this.currentUser) { console.log('未获取到当前用户信息,无法进行筛选'); return []; } // 首先根据项目/子项目筛选 let filteredTodos = this.todos; if (this.selectedProject) { filteredTodos = filteredTodos.filter(todo => { const matchesProject = todo.project === this.selectedProject; if (!this.selectedSubproject) { return matchesProject; } return matchesProject && todo.subproject === this.selectedSubproject; }); } // 根据用户等级进行筛选 const userLevel = this.currentUser.level || 0; // 等级4可以看到所有待办事项 if (userLevel === 4) { return filteredTodos; } // 等级3不能看到创建人和执行人都是等级4的待办事项 if (userLevel === 3) { return filteredTodos.filter(todo => { const creator = this.users.find(u => u.username === todo.adduser); const executor = this.users.find(u => u.username === todo.exeuser); // 如果找不到用户信息,默认显示 if (!creator || !executor) return true; // 只要创建人或执行人不是等级4,就显示 return !(creator.level === 4 && executor.level === 4); }); } // 等级2或更低只能看到创建人或执行人是自己的待办事项 return filteredTodos.filter(todo => { return todo.adduser === this.currentUser.username || todo.exeuser === this.currentUser.username; }); } // 创建模态对话框 createModal() { // 创建样式 const style = document.createElement('style'); style.textContent = ` .modal { position: fixed; top: 0; left: 0; width: 100%; height: 100%; background-color: rgba(0, 0, 0, 0.5); display: flex; justify-content: center; align-items: center; z-index: 9999; } .modal-content { background-color: white; border-radius: 8px; width: 400px; max-width: 90%; box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1); position: relative; margin: auto; } .modal-header { padding: 15px; border-bottom: 1px solid #e9ecef; display: flex; justify-content: space-between; align-items: center; } .modal-header h3 { margin: 0; color: #212529; } .close-button { background: none; border: none; font-size: 1.5em; color: #6c757d; cursor: pointer; padding: 0; line-height: 1; } .modal-body { padding: 15px; } .modal-footer { padding: 15px; border-top: 1px solid #e9ecef; display: flex; justify-content: flex-end; gap: 10px; } .form-group { margin-bottom: 15px; } .form-group label { display: block; margin-bottom: 5px; color: #495057; } .form-control { width: 100%; padding: 8px; border: 1px solid #ced4da; border-radius: 4px; font-size: 14px; } .save-button, .cancel-button { padding: 8px 16px; border: none; border-radius: 4px; cursor: pointer; font-size: 14px; } .save-button { background-color: #007bff; color: white; } .save-button:hover { background-color: #0056b3; } .cancel-button { background-color: #6c757d; color: white; } .cancel-button:hover { background-color: #5a6268; } `; document.head.appendChild(style); const modal = document.createElement('div'); modal.className = 'modal'; modal.innerHTML = ` `; // 添加关闭按钮事件 const closeButton = modal.querySelector('.close-button'); closeButton.addEventListener('click', () => { modal.remove(); style.remove(); // 移除样式 }); // 添加取消按钮事件 const cancelButton = modal.querySelector('.cancel-button'); cancelButton.addEventListener('click', () => { modal.remove(); style.remove(); // 移除样式 }); // 添加保存按钮事件 const saveButton = modal.querySelector('.save-button'); saveButton.addEventListener('click', async () => { const executor = modal.querySelector('#executor').value; if (executor && this.editingTodo) { try { const response = await fetch(`/api/todos/${this.editingTodo.id}`, { method: 'PUT', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ ...this.editingTodo, exeuser: executor }) }); if (!response.ok) { throw new Error('更新执行人失败'); } // 更新本地数据 this.editingTodo.exeuser = executor; this.render(); modal.remove(); style.remove(); // 移除样式 } catch (error) { console.error('更新执行人时发生错误:', error); this.showError('更新执行人失败,请稍后重试'); } } }); return modal; } // 显示编辑执行人对话框 showEditExecutorModal(todo) { this.editingTodo = todo; const modal = this.createModal(); // 获取当前用户等级 const currentUserLevel = this.currentUser.access_level || 0; // 填充用户下拉列表 const executorSelect = modal.querySelector('#executor'); // 筛选符合条件的用户 const availableUsers = this.users.filter(user => user && user.access_level > 0 && user.access_level <= currentUserLevel ); // 添加用户选项 availableUsers.forEach(user => { const option = document.createElement('option'); option.value = user.username; option.textContent = `${user.username} (${user.full_name})`; if (user.username === todo.exeuser) { option.selected = true; } executorSelect.appendChild(option); }); document.body.appendChild(modal); } render() { if (!this.isInitialized) { return; } // 清空现有内容 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; display: flex; flex-direction: column; } .show-all-button { margin-top: 15px; padding: 8px 16px; background-color: #007bff; color: white; border: none; border-radius: 4px; cursor: pointer; font-size: 14px; transition: background-color 0.2s; } .show-all-button:hover { background-color: #0056b3; } .show-all-button:active { background-color: #004085; } .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-header { display: flex; justify-content: space-between; align-items: center; margin-bottom: 12px; padding-bottom: 8px; border-bottom: 1px solid #e9ecef; } .todo-header h3 { margin: 0; color: #212529; font-size: 1.1em; } .todo-content { color: #495057; } .todo-info { margin-bottom: 8px; font-size: 0.9em; display: flex; align-items: center; flex-wrap: wrap; gap: 8px; } .todo-info .label { color: #6c757d; } .todo-info .value { color: #212529; } .todo-description { margin-top: 12px; padding-top: 12px; border-top: 1px solid #e9ecef; color: #495057; font-size: 0.95em; } .status { display: inline-block; padding: 4px 8px; border-radius: 4px; font-size: 0.9em; } .status.completed { background: #d4edda; color: #155724; } .status.pending { background: #fff3cd; color: #856404; } .error-message { color: #721c24; padding: 10px; background: #f8d7da; border-radius: 4px; margin: 10px; } .selected { background: #cfe2ff !important; } .executor-link { color: #007bff; text-decoration: none; cursor: pointer; } .executor-link:hover { text-decoration: underline; } `; 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.project}-${todo.subproject}

${todo.completed ? '已完成' : '进行中'}
执行人: ${todo.exeuser || '未分配'} 创建人: ${todo.adduser}
计划时间: ${scheDate} 创建时间: ${createdDate}
${todo.completed ? `
完成时间: ${completeDate}
` : ''}
${todo.text || '无详细描述'}
`; // 添加执行人链接点击事件 const executorLink = todoElement.querySelector('.executor-link'); executorLink.addEventListener('click', (e) => { e.preventDefault(); this.showEditExecutorModal(todo); }); todosContainer.appendChild(todoElement); }); container.appendChild(todosContainer); this.shadowRoot.appendChild(container); } connectedCallback() { this.initialize(); } } customElements.define('todo-component', TodoComponent);