diff --git a/Release/database/XNSim.db b/Release/database/XNSim.db index 08d879e..1e28a18 100644 Binary files a/Release/database/XNSim.db and b/Release/database/XNSim.db differ diff --git a/XNSimHtml/assets/icons/png/cancel_b.png b/XNSimHtml/assets/icons/png/cancel_b.png new file mode 100644 index 0000000..5d19dbe Binary files /dev/null and b/XNSimHtml/assets/icons/png/cancel_b.png differ diff --git a/XNSimHtml/assets/icons/png/complete_b.png b/XNSimHtml/assets/icons/png/complete_b.png new file mode 100644 index 0000000..70bc05c Binary files /dev/null and b/XNSimHtml/assets/icons/png/complete_b.png differ diff --git a/XNSimHtml/components/todo-component.js b/XNSimHtml/components/todo-component.js index 46f6369..906cf0a 100644 --- a/XNSimHtml/components/todo-component.js +++ b/XNSimHtml/components/todo-component.js @@ -15,6 +15,7 @@ class TodoComponent extends HTMLElement { this.currentUser = null; this.editingTodo = null; // 当前正在编辑的待办事项 this.isInitialized = false; // 添加初始化标志 + this.showCompleted = true; // 添加显示已完成的状态变量 } // 初始化组件 @@ -123,6 +124,17 @@ class TodoComponent extends HTMLElement { const treeContainer = document.createElement('div'); treeContainer.className = 'project-tree'; + // 创建树内容容器 + const treeContent = document.createElement('div'); + treeContent.className = 'tree-content'; + + // 添加新增待办按钮 + const addTodoButton = document.createElement('button'); + addTodoButton.className = 'add-todo-button'; + addTodoButton.textContent = '新增待办'; + addTodoButton.addEventListener('click', () => this.showNewTodoModal()); + treeContent.appendChild(addTodoButton); + Object.values(tree).forEach(project => { const projectNode = document.createElement('div'); projectNode.className = 'project-node'; @@ -179,9 +191,30 @@ class TodoComponent extends HTMLElement { projectNode.appendChild(projectHeader); projectNode.appendChild(subprojectsContainer); - treeContainer.appendChild(projectNode); + treeContent.appendChild(projectNode); }); + // 创建底部控制区域 + const bottomControls = document.createElement('div'); + bottomControls.className = 'bottom-controls'; + + // 添加显示已完成复选框 + const showCompletedContainer = document.createElement('div'); + showCompletedContainer.className = 'show-completed-container'; + showCompletedContainer.innerHTML = ` + + + `; + + // 添加复选框事件监听 + const checkbox = showCompletedContainer.querySelector('input'); + checkbox.addEventListener('change', (e) => { + this.showCompleted = e.target.checked; + this.render(); + }); + + bottomControls.appendChild(showCompletedContainer); + // 添加显示全部按钮 const showAllButton = document.createElement('button'); showAllButton.className = 'show-all-button'; @@ -191,7 +224,11 @@ class TodoComponent extends HTMLElement { this.selectedSubproject = null; this.render(); }); - treeContainer.appendChild(showAllButton); + bottomControls.appendChild(showAllButton); + + // 将树内容和底部控制区域添加到树容器 + treeContainer.appendChild(treeContent); + treeContainer.appendChild(bottomControls); return treeContainer; } @@ -215,6 +252,11 @@ class TodoComponent extends HTMLElement { }); } + // 根据复选框状态筛选 + if (!this.showCompleted) { + filteredTodos = filteredTodos.filter(todo => !todo.completed); + } + // 根据用户等级进行筛选 const userLevel = this.currentUser.level || 0; @@ -244,8 +286,283 @@ class TodoComponent extends HTMLElement { }); } + // 显示编辑计划时间对话框 + showEditScheduleModal(todo) { + this.editingTodo = todo; + const { modal, style } = this.createModal('schedule'); + + // 设置日期时间选择器的初始值 + const scheduleInput = modal.querySelector('#schedule'); + const scheduleDate = new Date(todo.sche_time); + scheduleInput.value = scheduleDate.toISOString().slice(0, 16); + + // 添加保存按钮事件 + const saveButton = modal.querySelector('.save-button'); + saveButton.addEventListener('click', async () => { + const scheduleTime = scheduleInput.value; + if (scheduleTime && this.editingTodo) { + try { + const response = await fetch(`/api/todos/${this.editingTodo.id}`, { + method: 'PUT', + headers: { + 'Content-Type': 'application/json' + }, + body: JSON.stringify({ + ...this.editingTodo, + sche_time: scheduleTime + }) + }); + + if (!response.ok) { + throw new Error('更新计划时间失败'); + } + + // 更新本地数据 + this.editingTodo.sche_time = scheduleTime; + this.render(); + modal.remove(); + style.remove(); + } catch (error) { + console.error('更新计划时间时发生错误:', error); + this.showError('更新计划时间失败,请稍后重试'); + } + } + }); + + document.body.appendChild(modal); + } + + // 显示编辑执行人对话框 + showEditExecutorModal(todo) { + this.editingTodo = todo; + const { modal, style } = this.createModal('executor'); + + // 获取当前用户等级 + 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); + }); + + // 添加保存按钮事件 + const saveButton = modal.querySelector('.save-button'); + saveButton.addEventListener('click', async () => { + const executor = executorSelect.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('更新执行人失败,请稍后重试'); + } + } + }); + + document.body.appendChild(modal); + } + + // 显示新增待办对话框 + showNewTodoModal() { + const { modal, style } = this.createModal('new-todo'); + + // 设置计划时间的默认值为当前时间 + const scheduleInput = modal.querySelector('#schedule'); + const now = new Date(); + scheduleInput.value = now.toISOString().slice(0, 16); + + // 获取当前用户等级 + 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})`; + executorSelect.appendChild(option); + }); + + // 获取所有项目和子项目 + const projectTree = this.buildProjectTree(); + const projectInput = modal.querySelector('#project'); + const subprojectInput = modal.querySelector('#subproject'); + const projectOptions = modal.querySelector('#project-options'); + const subprojectOptions = modal.querySelector('#subproject-options'); + + // 填充项目选项 + Object.keys(projectTree).forEach(project => { + const option = document.createElement('div'); + option.className = 'select-option'; + option.textContent = project; + option.addEventListener('click', () => { + projectInput.value = project; + projectOptions.style.display = 'none'; + // 更新子项目选项 + updateSubprojectOptions(project); + }); + projectOptions.appendChild(option); + }); + + // 更新子项目选项的函数 + const updateSubprojectOptions = (selectedProject) => { + subprojectOptions.innerHTML = ''; + const subprojects = projectTree[selectedProject]?.subprojects || {}; + Object.keys(subprojects).forEach(subproject => { + const option = document.createElement('div'); + option.className = 'select-option'; + option.textContent = subproject; + option.addEventListener('click', () => { + subprojectInput.value = subproject; + subprojectOptions.style.display = 'none'; + }); + subprojectOptions.appendChild(option); + }); + }; + + // 项目输入框事件处理 + projectInput.addEventListener('focus', () => { + projectOptions.style.display = 'block'; + subprojectOptions.style.display = 'none'; + }); + + projectInput.addEventListener('input', () => { + const value = projectInput.value.toLowerCase(); + const options = projectOptions.querySelectorAll('.select-option'); + options.forEach(option => { + const text = option.textContent.toLowerCase(); + option.style.display = text.includes(value) ? 'block' : 'none'; + }); + projectOptions.style.display = 'block'; + }); + + // 子项目输入框事件处理 + subprojectInput.addEventListener('focus', () => { + if (projectInput.value) { + subprojectOptions.style.display = 'block'; + projectOptions.style.display = 'none'; + } + }); + + subprojectInput.addEventListener('input', () => { + if (projectInput.value) { + const value = subprojectInput.value.toLowerCase(); + const options = subprojectOptions.querySelectorAll('.select-option'); + options.forEach(option => { + const text = option.textContent.toLowerCase(); + option.style.display = text.includes(value) ? 'block' : 'none'; + }); + subprojectOptions.style.display = 'block'; + } + }); + + // 点击外部关闭下拉列表 + document.addEventListener('click', (e) => { + if (!e.target.closest('.custom-select')) { + projectOptions.style.display = 'none'; + subprojectOptions.style.display = 'none'; + } + }); + + // 添加保存按钮事件 + const saveButton = modal.querySelector('.save-button'); + saveButton.addEventListener('click', async () => { + const project = projectInput.value; + const subproject = subprojectInput.value; + const title = modal.querySelector('#title').value; + const text = modal.querySelector('#text').value; + const scheduleTime = modal.querySelector('#schedule').value; + const executor = modal.querySelector('#executor').value; + + if (!project || !subproject || !scheduleTime || !executor || !title) { + this.showError('请填写必填项'); + return; + } + + try { + const response = await fetch('/api/todos', { + method: 'POST', + headers: { + 'Content-Type': 'application/json' + }, + body: JSON.stringify({ + project, + subproject, + title, + text, + sche_time: scheduleTime, + exeuser: executor, + adduser: this.currentUser.username, + completed: false, + status: 'pending' // 添加初始状态 + }) + }); + + if (!response.ok) { + const errorData = await response.json(); + throw new Error(errorData.message || '创建待办失败'); + } + + // 重新加载待办列表并渲染 + await this.fetchTodos(); + this.render(); + modal.remove(); + style.remove(); + } catch (error) { + console.error('创建待办时发生错误:', error); + this.showError(error.message || '创建待办失败,请稍后重试'); + } + }); + + document.body.appendChild(modal); + } + // 创建模态对话框 - createModal() { + createModal(type) { // 创建样式 const style = document.createElement('style'); style.textContent = ` @@ -308,12 +625,93 @@ class TodoComponent extends HTMLElement { margin-bottom: 5px; color: #495057; } - .form-control { + .form-group input[type="text"], + .form-group select { width: 100%; - padding: 8px; - border: 1px solid #ced4da; + padding: 8px 10px; + border: 1px solid #ddd; border-radius: 4px; font-size: 14px; + margin-bottom: 5px; + background-color: white; + } + .form-group input[type="text"]:focus, + .form-group select:focus { + border-color: #7986E7; + outline: none; + box-shadow: 0 0 0 2px rgba(121, 134, 231, 0.2); + } + .form-group select:disabled { + background-color: #f8f9fa; + cursor: not-allowed; + } + .form-group select[contenteditable="true"] { + cursor: text; + } + /* 添加 datalist 相关样式 */ + .form-group input[list] { + width: 100%; + padding: 8px 10px; + border: 1px solid #ddd; + border-radius: 4px; + font-size: 14px; + margin-bottom: 5px; + background-color: white; + } + .form-group input[list]:focus { + border-color: #7986E7; + outline: none; + box-shadow: 0 0 0 2px rgba(121, 134, 231, 0.2); + } + .form-group datalist { + width: 100%; + padding: 8px 10px; + border: 1px solid #ddd; + border-radius: 4px; + font-size: 14px; + background-color: white; + } + /* 自定义 datalist 下拉列表样式 */ + .form-group input[list]::-webkit-calendar-picker-indicator { + display: none; + } + .form-group input[list]::-webkit-list-button { + display: none; + } + .form-group input[list]::-webkit-datetime-edit { + padding: 0; + } + .form-group input[list]::-webkit-datetime-edit-fields-wrapper { + padding: 0; + } + .form-group input[list]::-webkit-datetime-edit-text { + padding: 0 2px; + } + .form-group input[list]::-webkit-datetime-edit-hour-field, + .form-group input[list]::-webkit-datetime-edit-minute-field, + .form-group input[list]::-webkit-datetime-edit-second-field, + .form-group input[list]::-webkit-datetime-edit-ampm-field { + padding: 0 2px; + } + /* 自定义下拉列表样式 */ + .form-group input[list] + datalist { + position: absolute; + background: white; + border: 1px solid #ddd; + border-radius: 4px; + box-shadow: 0 2px 4px rgba(0,0,0,0.1); + max-height: 200px; + overflow-y: auto; + z-index: 1000; + } + .form-group input[list] + datalist option { + padding: 8px 10px; + cursor: pointer; + font-size: 14px; + color: #333; + } + .form-group input[list] + datalist option:hover { + background-color: #f5f5f5; } .save-button, .cancel-button { padding: 8px 16px; @@ -323,11 +721,11 @@ class TodoComponent extends HTMLElement { font-size: 14px; } .save-button { - background-color: #007bff; + background-color: #7986E7; color: white; } .save-button:hover { - background-color: #0056b3; + background-color: #6875D6; } .cancel-button { background-color: #6c757d; @@ -336,112 +734,272 @@ class TodoComponent extends HTMLElement { .cancel-button:hover { background-color: #5a6268; } + /* 日期时间选择器特定样式 */ + input[type="datetime-local"] { + font-family: inherit; + color: #333; + } + input[type="datetime-local"]::-webkit-calendar-picker-indicator { + cursor: pointer; + padding: 4px; + margin-right: 4px; + opacity: 0.6; + } + input[type="datetime-local"]::-webkit-calendar-picker-indicator:hover { + opacity: 1; + } + input[type="datetime-local"]::-webkit-datetime-edit { + padding: 0; + } + input[type="datetime-local"]::-webkit-datetime-edit-fields-wrapper { + padding: 0; + } + input[type="datetime-local"]::-webkit-datetime-edit-text { + padding: 0 2px; + } + input[type="datetime-local"]::-webkit-datetime-edit-hour-field, + input[type="datetime-local"]::-webkit-datetime-edit-minute-field, + input[type="datetime-local"]::-webkit-datetime-edit-second-field, + input[type="datetime-local"]::-webkit-datetime-edit-ampm-field { + padding: 0 2px; + } + .form-group textarea { + width: 100%; + min-height: 100px; + padding: 8px 10px; + border: 1px solid #ddd; + border-radius: 4px; + font-size: 14px; + resize: vertical; + } + .form-group textarea:focus { + border-color: #7986E7; + outline: none; + box-shadow: 0 0 0 2px rgba(121, 134, 231, 0.2); + } + /* 自定义下拉列表样式 */ + .custom-select { + position: relative; + width: 100%; + } + .select-options { + display: none; + position: absolute; + top: 100%; + left: 0; + right: 0; + background: white; + border: 1px solid #7986E7; + border-radius: 4px; + box-shadow: 0 4px 8px rgba(121, 134, 231, 0.2); + max-height: 200px; + overflow-y: auto; + z-index: 1000; + margin-top: 2px; + } + .select-option { + padding: 8px 10px; + cursor: pointer; + font-size: 14px; + color: #333; + transition: all 0.2s ease; + } + .select-option:hover { + background-color: #7986E7; + color: white; + } + .select-option:active { + background-color: #6875D6; + } + /* 添加滚动条样式 */ + .select-options::-webkit-scrollbar { + width: 6px; + } + .select-options::-webkit-scrollbar-track { + background: #f1f1f1; + border-radius: 3px; + } + .select-options::-webkit-scrollbar-thumb { + background: #7986E7; + border-radius: 3px; + } + .select-options::-webkit-scrollbar-thumb:hover { + background: #6875D6; + } + .show-completed-container { + margin: 10px 0; + padding: 8px; + display: flex; + align-items: center; + gap: 8px; + background: #f8f9fa; + border-radius: 4px; + } + .show-completed-container input[type="checkbox"] { + cursor: pointer; + width: 16px; + height: 16px; + } + .show-completed-container label { + cursor: pointer; + user-select: none; + color: #495057; + font-size: 14px; + } + .show-completed-container:hover { + background: #e9ecef; + } `; - document.head.appendChild(style); - + const modal = document.createElement('div'); modal.className = 'modal'; - modal.innerHTML = ` -