diff --git a/XNSimHtml/components/todo-component.js b/XNSimHtml/components/todo-component.js
index a37c0c8..86e5a48 100644
--- a/XNSimHtml/components/todo-component.js
+++ b/XNSimHtml/components/todo-component.js
@@ -1,3 +1,8 @@
+import TodoService from './todo/todo-service.js';
+import TodoModal from './todo/todo-modal.js';
+import TodoTree from './todo/todo-tree.js';
+import TodoList from './todo/todo-list.js';
+
class TodoComponent extends HTMLElement {
static {
// 添加静态初始化标志
@@ -13,14 +18,13 @@ class TodoComponent extends HTMLElement {
this.expandedProjects = new Set();
this.users = [];
this.currentUser = null;
- this.editingTodo = null; // 当前正在编辑的待办事项
- this.isInitialized = false; // 添加初始化标志
- this.showCompleted = true; // 添加显示已完成的状态变量
+ this.editingTodo = null;
+ this.isInitialized = false;
+ this.showCompleted = true;
}
// 初始化组件
async initialize() {
- // 检查是否正在初始化
if (TodoComponent.isInitializing) {
return;
}
@@ -49,11 +53,7 @@ class TodoComponent extends HTMLElement {
async fetchTodos() {
try {
- const response = await fetch('/api/todos');
- if (!response.ok) {
- throw new Error('获取待办事项失败');
- }
- this.todos = await response.json();
+ this.todos = await TodoService.fetchTodos();
} catch (error) {
console.error('获取待办事项时发生错误:', error);
this.showError('获取待办事项失败,请稍后重试');
@@ -62,12 +62,7 @@ class TodoComponent extends HTMLElement {
async fetchUsers() {
try {
- const response = await fetch('/api/users');
- if (!response.ok) {
- throw new Error('获取用户数据失败');
- }
- const data = await response.json();
- this.users = data.users || [];
+ this.users = await TodoService.fetchUsers();
} catch (error) {
console.error('获取用户数据时发生错误:', error);
this.showError('获取用户数据失败,请稍后重试');
@@ -95,247 +90,10 @@ class TodoComponent extends HTMLElement {
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';
-
- // 创建树内容容器
- 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';
-
- const projectHeader = document.createElement('div');
- projectHeader.className = 'project-header';
- projectHeader.innerHTML = `
-
- `;
-
- 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);
- 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';
- showAllButton.textContent = '显示全部';
- showAllButton.addEventListener('click', () => {
- this.selectedProject = null;
- this.selectedSubproject = null;
- this.render();
- });
- bottomControls.appendChild(showAllButton);
-
- // 将树内容和底部控制区域添加到树容器
- treeContainer.appendChild(treeContent);
- treeContainer.appendChild(bottomControls);
-
- 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;
- });
- }
-
- // 根据复选框状态筛选
- if (!this.showCompleted) {
- filteredTodos = filteredTodos.filter(todo => !todo.completed);
- }
-
- // 根据用户等级进行筛选
- 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;
- });
- }
-
- // 显示编辑计划时间对话框
- 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 { modal, style } = TodoModal.createModal('executor');
// 获取当前用户等级
const currentUserLevel = this.currentUser.access_level || 0;
@@ -367,21 +125,11 @@ class TodoComponent extends HTMLElement {
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
- })
+ await TodoService.updateTodo(this.editingTodo.id, {
+ ...this.editingTodo,
+ exeuser: executor
});
- if (!response.ok) {
- throw new Error('更新执行人失败');
- }
-
// 更新本地数据
this.editingTodo.exeuser = executor;
this.render();
@@ -397,9 +145,45 @@ class TodoComponent extends HTMLElement {
document.body.appendChild(modal);
}
+ // 显示编辑计划时间对话框
+ showEditScheduleModal(todo) {
+ this.editingTodo = todo;
+ const { modal, style } = TodoModal.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 {
+ await TodoService.updateTodo(this.editingTodo.id, {
+ ...this.editingTodo,
+ sche_time: scheduleTime
+ });
+
+ // 更新本地数据
+ this.editingTodo.sche_time = scheduleTime;
+ 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 { modal, style } = TodoModal.createModal('new-todo');
// 设置计划时间的默认值为当前时间
const scheduleInput = modal.querySelector('#schedule');
@@ -428,7 +212,7 @@ class TodoComponent extends HTMLElement {
});
// 获取所有项目和子项目
- const projectTree = this.buildProjectTree();
+ const projectTree = TodoTree.buildProjectTree(this.todos);
const projectInput = modal.querySelector('#project');
const subprojectInput = modal.querySelector('#subproject');
const projectOptions = modal.querySelector('#project-options');
@@ -524,29 +308,18 @@ class TodoComponent extends HTMLElement {
}
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' // 添加初始状态
- })
+ await TodoService.createTodo({
+ 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();
@@ -561,706 +334,57 @@ class TodoComponent extends HTMLElement {
document.body.appendChild(modal);
}
- // 创建模态对话框
- createModal(type) {
- // 创建样式
- 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-group input[type="text"],
- .form-group select {
- width: 100%;
- 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;
- border: none;
- border-radius: 4px;
- cursor: pointer;
- font-size: 14px;
- }
- .save-button {
- background-color: #7986E7;
- color: white;
- }
- .save-button:hover {
- background-color: #6875D6;
- }
- .cancel-button {
- background-color: #6c757d;
- color: white;
- }
- .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;
- }
- .show-all-button {
- width: 100%;
- 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;
- }
- .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;
- }
- .description-header {
- display: flex;
- justify-content: space-between;
- align-items: center;
- margin-bottom: 8px;
- }
- .description-actions {
- display: flex;
- gap: 4px;
- }
- .description-content {
- white-space: pre-wrap;
- line-height: 1.5;
- max-height: 100px;
- overflow: hidden;
- transition: max-height 0.3s ease-out;
- position: relative;
- text-indent: 0; /* 移除缩进 */
- }
- .description-content.expanded {
- max-height: 2000px;
- }
- .description-content:not(.expanded) {
- max-height: 1.5em;
- white-space: nowrap;
- overflow: hidden;
- text-overflow: ellipsis;
- text-indent: 0; /* 移除缩进 */
- }
- .description-content::after {
- content: '';
- position: absolute;
- bottom: 0;
- left: 0;
- right: 0;
- height: 40px;
- background: linear-gradient(transparent, white);
- pointer-events: none;
- opacity: 1;
- transition: opacity 0.3s ease-out;
- }
- .description-content.expanded::after {
- opacity: 0;
- }
- .toggle-button {
- background: none;
- border: none;
- cursor: pointer;
- padding: 4px;
- border-radius: 4px;
- transition: all 0.2s;
- display: flex;
- align-items: center;
- justify-content: center;
- }
- .toggle-button:hover {
- background-color: rgba(121, 134, 231, 0.1);
- }
- .toggle-icon {
- transition: transform 0.3s ease;
- width: 16px;
- height: 16px;
- display: inline-block;
- }
- .toggle-button.expanded .toggle-icon {
- transform: rotate(180deg);
- }
- .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;
- }
- .schedule-link {
- color: #007bff;
- text-decoration: none;
- cursor: pointer;
- }
- .schedule-link:hover {
- text-decoration: underline;
- }
- .add-todo-button {
- margin-bottom: 15px;
- padding: 8px 16px;
- background-color: #7986E7;
- color: white;
- border: none;
- border-radius: 4px;
- cursor: pointer;
- font-size: 14px;
- transition: background-color 0.2s;
- width: 100%;
- }
- .add-todo-button:hover {
- background-color: #6875D6;
- }
- .add-todo-button:active {
- background-color: #5A67D8;
- }
- .todo-actions {
- display: flex;
- align-items: center;
- gap: 8px;
- }
- .edit-button, .delete-button, .complete-button {
- background: none;
- border: none;
- cursor: pointer;
- padding: 4px;
- border-radius: 4px;
- transition: all 0.2s;
- display: flex;
- align-items: center;
- justify-content: center;
- }
- .action-icon {
- width: 16px;
- height: 16px;
- object-fit: contain;
- }
- .edit-button:hover {
- background-color: rgba(121, 134, 231, 0.1);
- }
- .delete-button:hover {
- background-color: rgba(220, 53, 69, 0.1);
- }
- .complete-button:hover {
- background-color: rgba(40, 167, 69, 0.1);
- }
- `;
-
- const modal = document.createElement('div');
- modal.className = 'modal';
-
- // 根据类型创建不同的模态框内容
- if (type === 'executor') {
- modal.innerHTML = `
-
-
-
-
-
-
-
-
-
-
- `;
- } else if (type === 'schedule') {
- modal.innerHTML = `
-
- `;
- } else if (type === 'new-todo') {
- modal.innerHTML = `
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- `;
- } else if (type === 'edit-title') {
- modal.innerHTML = `
-
- `;
- } else if (type === 'edit-description') {
- modal.innerHTML = `
-
- `;
+ // 过滤待办事项
+ filterTodos() {
+ if (!this.currentUser) {
+ console.log('未获取到当前用户信息,无法进行筛选');
+ return [];
}
- // 添加关闭按钮事件
- const closeButton = modal.querySelector('.close-button');
- closeButton.addEventListener('click', () => {
- modal.remove();
- style.remove();
+ // 首先根据项目/子项目筛选
+ 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;
+ });
+ }
+
+ // 根据复选框状态筛选
+ if (!this.showCompleted) {
+ filteredTodos = filteredTodos.filter(todo => !todo.completed);
+ }
+
+ // 根据用户等级进行筛选
+ 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;
});
-
- // 添加取消按钮事件
- const cancelButton = modal.querySelector('.cancel-button');
- cancelButton.addEventListener('click', () => {
- modal.remove();
- style.remove();
- });
-
- // 将样式添加到文档头部
- document.head.appendChild(style);
-
- return { modal, style };
}
render() {
@@ -1276,10 +400,10 @@ class TodoComponent extends HTMLElement {
style.textContent = `
.container {
display: flex;
- height: calc(100vh - 120px); /* 增加边距,减小高度 */
+ height: calc(100vh - 120px);
font-family: Arial, sans-serif;
margin: 10px;
- overflow: hidden; /* 防止出现双滚动条 */
+ overflow: hidden;
}
.project-tree {
width: 250px;
@@ -1290,13 +414,13 @@ class TodoComponent extends HTMLElement {
flex-direction: column;
position: relative;
height: 100%;
- box-sizing: border-box; /* 确保padding不会增加总高度 */
+ box-sizing: border-box;
}
.tree-content {
flex: 1;
overflow-y: auto;
- margin-bottom: 100px; /* 为底部固定区域留出空间 */
- padding-right: 5px; /* 为滚动条留出空间 */
+ margin-bottom: 100px;
+ padding-right: 5px;
}
.todo-container {
flex: 1;
@@ -1315,7 +439,6 @@ class TodoComponent extends HTMLElement {
border-top: 1px solid #e9ecef;
box-sizing: border-box;
}
- /* 自定义滚动条样式 */
.tree-content::-webkit-scrollbar,
.todo-container::-webkit-scrollbar {
width: 6px;
@@ -1481,7 +604,7 @@ class TodoComponent extends HTMLElement {
overflow: hidden;
transition: max-height 0.3s ease-out;
position: relative;
- text-indent: 0; /* 移除缩进 */
+ text-indent: 0;
}
.description-content.expanded {
max-height: 2000px;
@@ -1491,7 +614,7 @@ class TodoComponent extends HTMLElement {
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
- text-indent: 0; /* 移除缩进 */
+ text-indent: 0;
}
.description-content::after {
content: '';
@@ -1627,100 +750,49 @@ class TodoComponent extends HTMLElement {
container.className = 'container';
// 构建并渲染项目树
- const tree = this.buildProjectTree();
- const treeContainer = this.renderProjectTree(tree);
+ const tree = TodoTree.buildProjectTree(this.todos);
+ const treeContainer = TodoTree.renderProjectTree(tree, {
+ expandedProjects: this.expandedProjects,
+ selectedProject: this.selectedProject,
+ selectedSubproject: this.selectedSubproject,
+ showCompleted: this.showCompleted,
+ onProjectSelect: (project) => {
+ this.selectedProject = project;
+ this.selectedSubproject = null;
+ this.render();
+ },
+ onSubprojectSelect: (project, subproject) => {
+ this.selectedProject = project;
+ this.selectedSubproject = subproject;
+ this.render();
+ },
+ onExpandToggle: (project) => {
+ if (this.expandedProjects.has(project)) {
+ this.expandedProjects.delete(project);
+ } else {
+ this.expandedProjects.add(project);
+ }
+ this.render();
+ },
+ onShowCompletedChange: (show) => {
+ this.showCompleted = show;
+ this.render();
+ },
+ onShowAll: () => {
+ this.selectedProject = null;
+ this.selectedSubproject = null;
+ this.render();
+ },
+ onAddTodo: () => this.showNewTodoModal()
+ });
- // 创建待办事项列表容器
- 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.completed ? `
-
- 完成时间:
- ${completeDate}
-
- ` : ''}
-
-
-
${todo.text || '无详细描述'}
-
-
- `;
-
- // 添加执行人链接点击事件
- const executorLink = todoElement.querySelector('.executor-link');
- executorLink.addEventListener('click', (e) => {
- e.preventDefault();
- this.showEditExecutorModal(todo);
- });
-
- // 添加计划时间链接点击事件
- const scheduleLink = todoElement.querySelector('.schedule-link');
- scheduleLink.addEventListener('click', (e) => {
- e.preventDefault();
- this.showEditScheduleModal(todo);
- });
-
- // 添加编辑按钮事件
- const editButton = todoElement.querySelector('.edit-button');
- editButton.addEventListener('click', () => {
- const { modal, style } = this.createModal('edit-title');
+ const todosContainer = TodoList.renderTodoList(filteredTodos, {
+ onEditExecutor: (todo) => this.showEditExecutorModal(todo),
+ onEditSchedule: (todo) => this.showEditScheduleModal(todo),
+ onEditTitle: (todo) => {
+ const { modal, style } = TodoModal.createModal('edit-title');
const titleInput = modal.querySelector('#title');
titleInput.value = todo.title;
@@ -1733,21 +805,11 @@ class TodoComponent extends HTMLElement {
}
try {
- const response = await fetch(`/api/todos/${todo.id}`, {
- method: 'PUT',
- headers: {
- 'Content-Type': 'application/json'
- },
- body: JSON.stringify({
- ...todo,
- title: newTitle
- })
+ await TodoService.updateTodo(todo.id, {
+ ...todo,
+ title: newTitle
});
- if (!response.ok) {
- throw new Error('更新标题失败');
- }
-
await this.fetchTodos();
this.render();
modal.remove();
@@ -1759,12 +821,9 @@ class TodoComponent extends HTMLElement {
});
document.body.appendChild(modal);
- });
-
- // 添加描述编辑按钮事件
- const descriptionEditButton = todoElement.querySelector('.todo-description .edit-button');
- descriptionEditButton.addEventListener('click', () => {
- const { modal, style } = this.createModal('edit-description');
+ },
+ onEditDescription: (todo) => {
+ const { modal, style } = TodoModal.createModal('edit-description');
const textInput = modal.querySelector('#text');
textInput.value = todo.text || '';
@@ -1773,21 +832,11 @@ class TodoComponent extends HTMLElement {
const newText = textInput.value;
try {
- const response = await fetch(`/api/todos/${todo.id}`, {
- method: 'PUT',
- headers: {
- 'Content-Type': 'application/json'
- },
- body: JSON.stringify({
- ...todo,
- text: newText
- })
+ await TodoService.updateTodo(todo.id, {
+ ...todo,
+ text: newText
});
- if (!response.ok) {
- throw new Error('更新描述失败');
- }
-
await this.fetchTodos();
this.render();
modal.remove();
@@ -1799,21 +848,11 @@ class TodoComponent extends HTMLElement {
});
document.body.appendChild(modal);
- });
-
- // 添加删除按钮事件
- const deleteButton = todoElement.querySelector('.delete-button');
- deleteButton.addEventListener('click', async () => {
+ },
+ onDelete: async (todo) => {
if (confirm('确定要删除这条待办事项吗?')) {
try {
- const response = await fetch(`/api/todos/${todo.id}`, {
- method: 'DELETE'
- });
-
- if (!response.ok) {
- throw new Error('删除待办事项失败');
- }
-
+ await TodoService.deleteTodo(todo.id);
await this.fetchTodos();
this.render();
} catch (error) {
@@ -1821,30 +860,17 @@ class TodoComponent extends HTMLElement {
this.showError('删除待办事项失败,请稍后重试');
}
}
- });
-
- // 添加完成/取消完成按钮事件
- const completeButton = todoElement.querySelector('.complete-button');
- completeButton.addEventListener('click', async () => {
+ },
+ onComplete: async (todo) => {
const action = todo.completed ? '取消完成' : '标记完成';
if (confirm(`确定要${action}这条待办事项吗?`)) {
try {
- const response = await fetch(`/api/todos/${todo.id}`, {
- method: 'PUT',
- headers: {
- 'Content-Type': 'application/json'
- },
- body: JSON.stringify({
- ...todo,
- completed: !todo.completed,
- complete_time: !todo.completed ? new Date().toISOString() : null
- })
+ await TodoService.updateTodo(todo.id, {
+ ...todo,
+ completed: !todo.completed,
+ complete_time: !todo.completed ? new Date().toISOString() : null
});
- if (!response.ok) {
- throw new Error(`${action}待办事项失败`);
- }
-
await this.fetchTodos();
this.render();
} catch (error) {
@@ -1852,34 +878,18 @@ class TodoComponent extends HTMLElement {
this.showError(`${action}待办事项失败,请稍后重试`);
}
}
- });
-
- // 添加展开/收起按钮事件
- const toggleButton = todoElement.querySelector('.toggle-button');
- const descriptionContent = todoElement.querySelector('.description-content');
- const toggleIcon = toggleButton.querySelector('.toggle-icon');
-
- // 移除可能存在的旧事件监听器
- const newToggleButton = toggleButton.cloneNode(true);
- toggleButton.parentNode.replaceChild(newToggleButton, toggleButton);
-
- // 添加新的事件监听器
- newToggleButton.addEventListener('click', function(e) {
- e.preventDefault();
- e.stopPropagation();
-
+ },
+ onToggleDescription: (descriptionContent, toggleButton) => {
const isExpanded = descriptionContent.classList.contains('expanded');
if (isExpanded) {
descriptionContent.classList.remove('expanded');
- newToggleButton.classList.remove('expanded');
+ toggleButton.classList.remove('expanded');
} else {
descriptionContent.classList.add('expanded');
- newToggleButton.classList.add('expanded');
+ toggleButton.classList.add('expanded');
}
- });
-
- todosContainer.appendChild(todoElement);
+ }
});
container.appendChild(treeContainer);
diff --git a/XNSimHtml/components/todo/todo-list.js b/XNSimHtml/components/todo/todo-list.js
new file mode 100644
index 0000000..3602796
--- /dev/null
+++ b/XNSimHtml/components/todo/todo-list.js
@@ -0,0 +1,142 @@
+class TodoList {
+ static renderTodoList(todos, {
+ onEditExecutor,
+ onEditSchedule,
+ onEditTitle,
+ onEditDescription,
+ onDelete,
+ onComplete,
+ onToggleDescription
+ }) {
+ const todosContainer = document.createElement('div');
+ todosContainer.className = 'todo-container';
+
+ todos.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.completed ? `
+
+ 完成时间:
+ ${completeDate}
+
+ ` : ''}
+
+
+
${todo.text || '无详细描述'}
+
+
+ `;
+
+ // 添加执行人链接点击事件
+ const executorLink = todoElement.querySelector('.executor-link');
+ executorLink.addEventListener('click', (e) => {
+ e.preventDefault();
+ onEditExecutor(todo);
+ });
+
+ // 添加计划时间链接点击事件
+ const scheduleLink = todoElement.querySelector('.schedule-link');
+ scheduleLink.addEventListener('click', (e) => {
+ e.preventDefault();
+ onEditSchedule(todo);
+ });
+
+ // 添加编辑按钮事件
+ const editButton = todoElement.querySelector('.edit-button');
+ editButton.addEventListener('click', () => {
+ onEditTitle(todo);
+ });
+
+ // 添加描述编辑按钮事件
+ const descriptionEditButton = todoElement.querySelector('.todo-description .edit-button');
+ descriptionEditButton.addEventListener('click', () => {
+ onEditDescription(todo);
+ });
+
+ // 添加删除按钮事件
+ const deleteButton = todoElement.querySelector('.delete-button');
+ deleteButton.addEventListener('click', () => {
+ onDelete(todo);
+ });
+
+ // 添加完成/取消完成按钮事件
+ const completeButton = todoElement.querySelector('.complete-button');
+ completeButton.addEventListener('click', () => {
+ onComplete(todo);
+ });
+
+ // 添加展开/收起按钮事件
+ const toggleButton = todoElement.querySelector('.toggle-button');
+ const descriptionContent = todoElement.querySelector('.description-content');
+ const toggleIcon = toggleButton.querySelector('.toggle-icon');
+
+ // 移除可能存在的旧事件监听器
+ const newToggleButton = toggleButton.cloneNode(true);
+ toggleButton.parentNode.replaceChild(newToggleButton, toggleButton);
+
+ // 添加新的事件监听器
+ newToggleButton.addEventListener('click', function(e) {
+ e.preventDefault();
+ e.stopPropagation();
+ onToggleDescription(descriptionContent, newToggleButton);
+ });
+
+ todosContainer.appendChild(todoElement);
+ });
+
+ return todosContainer;
+ }
+}
+
+export default TodoList;
diff --git a/XNSimHtml/components/todo/todo-modal.js b/XNSimHtml/components/todo/todo-modal.js
new file mode 100644
index 0000000..d57d377
--- /dev/null
+++ b/XNSimHtml/components/todo/todo-modal.js
@@ -0,0 +1,414 @@
+class TodoModal {
+ static createModal(type) {
+ // 创建样式
+ 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-group input[type="text"],
+ .form-group select {
+ width: 100%;
+ 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;
+ }
+ .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;
+ }
+ .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;
+ border: none;
+ border-radius: 4px;
+ cursor: pointer;
+ font-size: 14px;
+ }
+ .save-button {
+ background-color: #7986E7;
+ color: white;
+ }
+ .save-button:hover {
+ background-color: #6875D6;
+ }
+ .cancel-button {
+ background-color: #6c757d;
+ color: white;
+ }
+ .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;
+ }
+ `;
+
+ const modal = document.createElement('div');
+ modal.className = 'modal';
+
+ // 根据类型创建不同的模态框内容
+ if (type === 'executor') {
+ modal.innerHTML = `
+
+
+
+
+
+
+
+
+
+
+ `;
+ } else if (type === 'schedule') {
+ modal.innerHTML = `
+
+ `;
+ } else if (type === 'new-todo') {
+ modal.innerHTML = `
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ `;
+ } else if (type === 'edit-title') {
+ modal.innerHTML = `
+
+ `;
+ } else if (type === 'edit-description') {
+ 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();
+ });
+
+ // 将样式添加到文档头部
+ document.head.appendChild(style);
+
+ return { modal, style };
+ }
+}
+
+export default TodoModal;
diff --git a/XNSimHtml/components/todo/todo-service.js b/XNSimHtml/components/todo/todo-service.js
new file mode 100644
index 0000000..3198809
--- /dev/null
+++ b/XNSimHtml/components/todo/todo-service.js
@@ -0,0 +1,61 @@
+class TodoService {
+ static async fetchTodos() {
+ const response = await fetch('/api/todos');
+ if (!response.ok) {
+ throw new Error('获取待办事项失败');
+ }
+ return await response.json();
+ }
+
+ static async fetchUsers() {
+ const response = await fetch('/api/users');
+ if (!response.ok) {
+ throw new Error('获取用户数据失败');
+ }
+ const data = await response.json();
+ return data.users || [];
+ }
+
+ static async updateTodo(todoId, todoData) {
+ const response = await fetch(`/api/todos/${todoId}`, {
+ method: 'PUT',
+ headers: {
+ 'Content-Type': 'application/json'
+ },
+ body: JSON.stringify(todoData)
+ });
+
+ if (!response.ok) {
+ throw new Error('更新待办事项失败');
+ }
+ return await response.json();
+ }
+
+ static async createTodo(todoData) {
+ const response = await fetch('/api/todos', {
+ method: 'POST',
+ headers: {
+ 'Content-Type': 'application/json'
+ },
+ body: JSON.stringify(todoData)
+ });
+
+ if (!response.ok) {
+ const errorData = await response.json();
+ throw new Error(errorData.message || '创建待办失败');
+ }
+ return await response.json();
+ }
+
+ static async deleteTodo(todoId) {
+ const response = await fetch(`/api/todos/${todoId}`, {
+ method: 'DELETE'
+ });
+
+ if (!response.ok) {
+ throw new Error('删除待办事项失败');
+ }
+ }
+}
+
+export default TodoService;
diff --git a/XNSimHtml/components/todo/todo-tree.js b/XNSimHtml/components/todo/todo-tree.js
new file mode 100644
index 0000000..f087d75
--- /dev/null
+++ b/XNSimHtml/components/todo/todo-tree.js
@@ -0,0 +1,139 @@
+class TodoTree {
+ static buildProjectTree(todos) {
+ const tree = {};
+ 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;
+ }
+
+ static renderProjectTree(tree, {
+ expandedProjects,
+ selectedProject,
+ selectedSubproject,
+ showCompleted,
+ onProjectSelect,
+ onSubprojectSelect,
+ onExpandToggle,
+ onShowCompletedChange,
+ onShowAll,
+ onAddTodo
+ }) {
+ 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', onAddTodo);
+ treeContent.appendChild(addTodoButton);
+
+ 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 = `
+
+ `;
+
+ projectHeader.addEventListener('click', () => {
+ onProjectSelect(project.name);
+ });
+
+ const expandButton = projectHeader.querySelector('.expand-icon');
+ expandButton.addEventListener('click', (e) => {
+ e.stopPropagation();
+ onExpandToggle(project.name);
+ });
+
+ const subprojectsContainer = document.createElement('div');
+ subprojectsContainer.className = 'subprojects-container';
+ subprojectsContainer.style.display = expandedProjects.has(project.name) ? 'block' : 'none';
+
+ Object.values(project.subprojects).forEach(subproject => {
+ const subprojectNode = document.createElement('div');
+ subprojectNode.className = 'subproject-node';
+ if (selectedProject === project.name && selectedSubproject === subproject.name) {
+ subprojectNode.classList.add('selected');
+ }
+ subprojectNode.innerHTML = `
+ ${subproject.name}
+ (${subproject.todos.length})
+ `;
+
+ subprojectNode.addEventListener('click', (e) => {
+ e.stopPropagation();
+ onSubprojectSelect(project.name, subproject.name);
+ });
+
+ subprojectsContainer.appendChild(subprojectNode);
+ });
+
+ projectNode.appendChild(projectHeader);
+ projectNode.appendChild(subprojectsContainer);
+ 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) => {
+ onShowCompletedChange(e.target.checked);
+ });
+
+ bottomControls.appendChild(showCompletedContainer);
+
+ // 添加显示全部按钮
+ const showAllButton = document.createElement('button');
+ showAllButton.className = 'show-all-button';
+ showAllButton.textContent = '显示全部';
+ showAllButton.addEventListener('click', onShowAll);
+ bottomControls.appendChild(showAllButton);
+
+ // 将树内容和底部控制区域添加到树容器
+ treeContainer.appendChild(treeContent);
+ treeContainer.appendChild(bottomControls);
+
+ return treeContainer;
+ }
+}
+
+export default TodoTree;