diff --git a/XNSimHtml/components/todo-component.js b/XNSimHtml/components/todo-component.js index 95ac0de..46f6369 100644 --- a/XNSimHtml/components/todo-component.js +++ b/XNSimHtml/components/todo-component.js @@ -1,4 +1,9 @@ class TodoComponent extends HTMLElement { + static { + // 添加静态初始化标志 + this.isInitializing = false; + } + constructor() { super(); this.attachShadow({ mode: 'open' }); @@ -6,6 +11,39 @@ class TodoComponent extends HTMLElement { 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() { @@ -15,13 +53,40 @@ class TodoComponent extends HTMLElement { throw new Error('获取待办事项失败'); } this.todos = await response.json(); - this.render(); } 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'; @@ -117,25 +182,273 @@ class TodoComponent extends HTMLElement { 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.selectedProject) { - return this.todos; + 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; - return this.todos.filter(todo => { - const matchesProject = todo.project === this.selectedProject; - if (!this.selectedSubproject) { - return matchesProject; - } - return matchesProject && todo.subproject === this.selectedSubproject; + // 等级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 = ''; @@ -153,6 +466,25 @@ class TodoComponent extends HTMLElement { 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; @@ -211,36 +543,57 @@ class TodoComponent extends HTMLElement { 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-header { + display: flex; + justify-content: space-between; + align-items: center; + margin-bottom: 12px; + padding-bottom: 8px; + border-bottom: 1px solid #e9ecef; } - .todo-item p { - margin: 5px 0; + .todo-header h3 { + margin: 0; + color: #212529; + font-size: 1.1em; + } + .todo-content { color: #495057; } - .todo-item .status { + .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; - margin-top: 10px; } - .todo-item .status.completed { + .status.completed { background: #d4edda; color: #155724; } - .todo-item .status.pending { + .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; @@ -251,6 +604,14 @@ class TodoComponent extends HTMLElement { .selected { background: #cfe2ff !important; } + .executor-link { + color: #007bff; + text-decoration: none; + cursor: pointer; + } + .executor-link:hover { + text-decoration: underline; + } `; this.shadowRoot.appendChild(style); @@ -277,24 +638,48 @@ class TodoComponent extends HTMLElement { 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') : '未完成'; + new Date(todo.complete_time).toLocaleString('zh-CN') : ''; todoElement.innerHTML = ` -${todo.text || '无详细描述'}
-项目:${todo.project}
-子项目:${todo.subproject}
-执行人:${todo.exeuser || '未分配'}
-计划时间:${scheDate}
-