Task Management Web App
创建于:2024年10月7日
使用 OpenAI 01 Mini 回答 OpenAI01
创建于:2024年10月7日
使用 OpenAI 01 Mini 回答 OpenAI01
Anlegen und verwalten
Ich überlege eine Web-App, die Aufgaben mit Titeln, Textfeldern und benutzerdefinierten Feldern erstellt, anzeigt und bearbeitet, lokal mit IndexedDB speichert.
Erstellen des Codes
Ich erarbeite einen HTML-Datei, der JavaScript für die Funktionalität und IndexedDB für Speicherung nutzt. Abschnittsweise strukturiert, inklusive Erklärungen.
Creating a web application with the specified features using plain JavaScript and IndexedDB is a comprehensive task. Below is a complete implementation contained within a single HTML file. This file includes all necessary HTML, CSS, and JavaScript code to achieve the desired functionality.
HTML Structure: Defines the layout of the application, including the task table, buttons for creating tasks and custom fields, and popups for input forms.
CSS Styling: Provides basic styling for the application to make it user-friendly and visually organized.
JavaScript Functionality:
tasks
and customFields
.Save the following code into an .html
file (e.g., task_manager.html
) and open it in your web browser to run the application.
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Task Manager App</title> <style> body { font-family: Arial, sans-serif; margin: 20px; } h1 { text-align: center; } #controls { margin-bottom: 20px; text-align: center; } #controls button { margin: 0 10px; padding: 10px 20px; } #filterSection { margin-bottom: 20px; text-align: center; } #filterSection input, #filterSection select { margin: 0 5px; padding: 5px; } table { width: 100%; border-collapse: collapse; margin-top: 10px; } th, td { border: 1px solid #ccc; padding: 8px; text-align: left; } th { background-color: #f4f4f4; } .action-btn { margin-right: 5px; padding: 5px 10px; } /* Popup styling */ .popup { display: none; position: fixed; z-index: 1; left: 0; top: 0; width: 100%; height: 100%; overflow: auto; background-color: rgba(0,0,0,0.4); } .popup-content { background-color: #fff; margin: 10% auto; padding: 20px; border: 1px solid #888; width: 50%; } .close { color: #aaa; float: right; font-size: 24px; font-weight: bold; cursor: pointer; } .form-group { margin-bottom: 15px; } label { display: block; margin-bottom: 5px; } input[type="text"], textarea, select, input[type="number"], input[type="date"] { width: 100%; padding: 8px; box-sizing: border-box; } .custom-select-options { margin-top: 10px; } .custom-select-options input { width: 80%; margin-right: 5px; } .custom-select-options button { padding: 5px 10px; } </style> </head> <body> <h1>Task Manager App</h1> <div id="controls"> <button id="createTaskBtn">Create Task</button> <button id="createFieldBtn">Create Custom Field</button> </div> <div id="filterSection"> <input type="text" id="textFilter" placeholder="Filter by text"> <select id="fieldFilter"> <option value="">Filter by custom field</option> </select> <button id="applyFilterBtn">Apply Filter</button> <button id="clearFilterBtn">Clear Filter</button> </div> <table id="taskTable"> <thead> <tr id="tableHeader"> <th>Title</th> <th>Description</th> <th>Status</th> <th>Actions</th> <!-- Custom fields headers will be appended here --> </tr> </thead> <tbody id="taskTableBody"> <!-- Tasks will be populated here --> </tbody> </table> <!-- Popup for creating tasks --> <div id="taskPopup" class="popup"> <div class="popup-content"> <span class="close" id="closeTaskPopup">×</span> <h2>Create Task</h2> <form id="taskForm"> <div class="form-group"> <label for="taskTitle">Title:</label> <input type="text" id="taskTitle" required> </div> <div class="form-group"> <label for="taskDescription">Description:</label> <textarea id="taskDescription" rows="4" required></textarea> </div> <div id="customFieldsSection"> <!-- Dynamic custom fields will be appended here --> </div> <button type="submit">Create Task</button> </form> </div> </div> <!-- Popup for creating custom fields --> <div id="fieldPopup" class="popup"> <div class="popup-content"> <span class="close" id="closeFieldPopup">×</span> <h2>Create Custom Field</h2> <form id="fieldForm"> <div class="form-group"> <label for="fieldName">Field Name:</label> <input type="text" id="fieldName" required> </div> <div class="form-group"> <label for="fieldType">Field Type:</label> <select id="fieldType" required> <option value="">Select type</option> <option value="string">String</option> <option value="number">Number</option> <option value="date">Date</option> <option value="singleSelect">Single Select</option> </select> </div> <div class="form-group" id="singleSelectOptions" style="display: none;"> <label>Options for Single Select (comma separated):</label> <input type="text" id="singleSelectInput" placeholder="e.g., Option1, Option2"> </div> <button type="submit">Create Field</button> </form> </div> </div> <script> // Initialize IndexedDB let db; const request = indexedDB.open('TaskManagerDB', 1); request.onupgradeneeded = function(event) { db = event.target.result; const taskStore = db.createObjectStore('tasks', { keyPath: 'id', autoIncrement: true }); taskStore.createIndex('title', 'title', { unique: false }); const fieldStore = db.createObjectStore('customFields', { keyPath: 'id', autoIncrement: true }); fieldStore.createIndex('name', 'name', { unique: true }); }; request.onsuccess = function(event) { db = event.target.result; loadCustomFields(); loadTasks(); }; request.onerror = function(event) { console.error('Database error:', event.target.errorCode); }; // Utility function to get all custom fields function getAllCustomFields() { return new Promise((resolve, reject) => { const transaction = db.transaction(['customFields'], 'readonly'); const store = transaction.objectStore('customFields'); const request = store.getAll(); request.onsuccess = function() { resolve(request.result); }; request.onerror = function() { reject(request.error); }; }); } // Load custom fields and update the filter dropdown and task form async function loadCustomFields() { const customFields = await getAllCustomFields(); const fieldFilter = document.getElementById('fieldFilter'); // Clear existing options fieldFilter.innerHTML = '<option value="">Filter by custom field</option>'; customFields.forEach(field => { const option = document.createElement('option'); option.value = field.id; option.textContent = field.name; fieldFilter.appendChild(option); }); // Update task form with custom fields const customFieldsSection = document.getElementById('customFieldsSection'); customFieldsSection.innerHTML = '<h3>Custom Fields</h3>'; customFields.forEach(field => { const div = document.createElement('div'); div.className = 'form-group'; const label = document.createElement('label'); label.textContent = field.name + ':'; div.appendChild(label); let input; switch(field.type) { case 'string': input = document.createElement('input'); input.type = 'text'; break; case 'number': input = document.createElement('input'); input.type = 'number'; break; case 'date': input = document.createElement('input'); input.type = 'date'; break; case 'singleSelect': input = document.createElement('select'); const defaultOption = document.createElement('option'); defaultOption.value = ''; defaultOption.textContent = 'Select an option'; input.appendChild(defaultOption); field.options.forEach(opt => { const option = document.createElement('option'); option.value = opt; option.textContent = opt; input.appendChild(option); }); break; } input.id = 'custom_' + field.id; div.appendChild(input); customFieldsSection.appendChild(div); }); // Update table headers with custom fields const tableHeader = document.getElementById('tableHeader'); // Remove existing custom field headers const existingCustomHeaders = document.querySelectorAll('.custom-header'); existingCustomHeaders.forEach(th => th.remove()); // Add new custom field headers customFields.forEach(field => { const th = document.createElement('th'); th.textContent = field.name; th.className = 'custom-header'; tableHeader.insertBefore(th, tableHeader.lastElementChild); }); } // Load tasks from IndexedDB and display them async function loadTasks() { const transaction = db.transaction(['tasks', 'customFields'], 'readonly'); const taskStore = transaction.objectStore('tasks'); const request = taskStore.getAll(); request.onsuccess = async function() { const tasks = request.result; const customFields = await getAllCustomFields(); displayTasks(tasks, customFields); }; } // Display tasks in the table function displayTasks(tasks, customFields, filters = {}) { const tbody = document.getElementById('taskTableBody'); tbody.innerHTML = ''; tasks.forEach(task => { // Apply filters let show = true; if(filters.text) { const textMatch = task.title.toLowerCase().includes(filters.text.toLowerCase()) || task.description.toLowerCase().includes(filters.text.toLowerCase()); if(!textMatch) show = false; } if(filters.fieldId && filters.fieldValue) { if(!task.customFields || task.customFields[filters.fieldId] !== filters.fieldValue) { show = false; } } if(show) { const tr = document.createElement('tr'); const titleTd = document.createElement('td'); titleTd.textContent = task.title; tr.appendChild(titleTd); const descTd = document.createElement('td'); descTd.textContent = task.description; tr.appendChild(descTd); const statusTd = document.createElement('td'); statusTd.textContent = task.status || 'Open'; tr.appendChild(statusTd); // Add custom fields customFields.forEach(field => { const customTd = document.createElement('td'); customTd.textContent = task.customFields ? task.customFields[field.id] || '' : ''; tr.appendChild(customTd); }); const actionsTd = document.createElement('td'); // Close/Delete buttons if(task.status !== 'Closed') { const closeBtn = document.createElement('button'); closeBtn.textContent = 'Close'; closeBtn.className = 'action-btn'; closeBtn.onclick = () => closeTask(task.id); actionsTd.appendChild(closeBtn); } const deleteBtn = document.createElement('button'); deleteBtn.textContent = 'Delete'; deleteBtn.className = 'action-btn'; deleteBtn.onclick = () => deleteTask(task.id); actionsTd.appendChild(deleteBtn); tr.appendChild(actionsTd); tbody.appendChild(tr); } }); } // Create Task Popup const taskPopup = document.getElementById('taskPopup'); const createTaskBtn = document.getElementById('createTaskBtn'); const closeTaskPopup = document.getElementById('closeTaskPopup'); createTaskBtn.onclick = function() { taskPopup.style.display = 'block'; }; closeTaskPopup.onclick = function() { taskPopup.style.display = 'none'; document.getElementById('taskForm').reset(); }; window.onclick = function(event) { if(event.target == taskPopup) { taskPopup.style.display = 'none'; document.getElementById('taskForm').reset(); } if(event.target == fieldPopup) { fieldPopup.style.display = 'none'; document.getElementById('fieldForm').reset(); document.getElementById('singleSelectOptions').style.display = 'none'; } }; // Handle Task Form Submission const taskForm = document.getElementById('taskForm'); taskForm.onsubmit = function(e) { e.preventDefault(); const title = document.getElementById('taskTitle').value.trim(); const description = document.getElementById('taskDescription').value.trim(); if(!title || !description) { alert('Please fill in all required fields.'); return; } getAllCustomFields().then(customFields => { let customFieldsData = {}; customFields.forEach(field => { const value = document.getElementById('custom_' + field.id).value; if(value) { customFieldsData[field.id] = value; } }); const transaction = db.transaction(['tasks'], 'readwrite'); const store = transaction.objectStore('tasks'); const task = { title, description, status: 'Open', customFields: customFieldsData }; store.add(task).onsuccess = function() { loadTasks(); taskPopup.style.display = 'none'; taskForm.reset(); }; }); }; // Delete Task function deleteTask(id) { if(confirm('Are you sure you want to delete this task?')) { const transaction = db.transaction(['tasks'], 'readwrite'); const store = transaction.objectStore('tasks'); store.delete(id).onsuccess = function() { loadTasks(); }; } } // Close Task function closeTask(id) { const transaction = db.transaction(['tasks'], 'readwrite'); const store = transaction.objectStore('tasks'); const getRequest = store.get(id); getRequest.onsuccess = function() { const task = getRequest.result; task.status = 'Closed'; store.put(task).onsuccess = function() { loadTasks(); }; }; } // Create Custom Field Popup const fieldPopup = document.getElementById('fieldPopup'); const createFieldBtn = document.getElementById('createFieldBtn'); const closeFieldPopup = document.getElementById('closeFieldPopup'); const fieldTypeSelect = document.getElementById('fieldType'); createFieldBtn.onclick = function() { fieldPopup.style.display = 'block'; }; closeFieldPopup.onclick = function() { fieldPopup.style.display = 'none'; document.getElementById('fieldForm').reset(); document.getElementById('singleSelectOptions').style.display = 'none'; }; // Show/hide single select options based on field type fieldTypeSelect.onchange = function() { if(this.value === 'singleSelect') { document.getElementById('singleSelectOptions').style.display = 'block'; } else { document.getElementById('singleSelectOptions').style.display = 'none'; } }; // Handle Field Form Submission const fieldForm = document.getElementById('fieldForm'); fieldForm.onsubmit = function(e) { e.preventDefault(); const name = document.getElementById('fieldName').value.trim(); const type = document.getElementById('fieldType').value; if(!name || !type) { alert('Please fill in all required fields.'); return; } let options = []; if(type === 'singleSelect') { const optionsInput = document.getElementById('singleSelectInput').value.trim(); if(!optionsInput) { alert('Please provide options for single select.'); return; } options = optionsInput.split(',').map(opt => opt.trim()).filter(opt => opt); if(options.length === 0) { alert('Please provide valid options.'); return; } } const transaction = db.transaction(['customFields'], 'readwrite'); const store = transaction.objectStore('customFields'); const field = { name, type, options: type === 'singleSelect' ? options : [] }; store.add(field).onsuccess = function() { loadCustomFields(); loadTasks(); fieldPopup.style.display = 'none'; fieldForm.reset(); document.getElementById('singleSelectOptions').style.display = 'none'; }; store.onerror = function() { alert('Field name must be unique.'); }; }; // Filtering const applyFilterBtn = document.getElementById('applyFilterBtn'); const clearFilterBtn = document.getElementById('clearFilterBtn'); applyFilterBtn.onclick = async function() { const text = document.getElementById('textFilter').value.trim(); const fieldId = document.getElementById('fieldFilter').value; const fieldValue = fieldId ? prompt('Enter value for the selected custom field to filter:') : null; const filters = {}; if(text) filters.text = text; if(fieldId && fieldValue !== null) { filters.fieldId = parseInt(fieldId); filters.fieldValue = fieldValue.trim(); } const transaction = db.transaction(['tasks', 'customFields'], 'readonly'); const taskStore = transaction.objectStore('tasks'); const request = taskStore.getAll(); request.onsuccess = async function() { const tasks = request.result; const customFields = await getAllCustomFields(); displayTasks(tasks, customFields, filters); }; }; clearFilterBtn.onclick = function() { document.getElementById('textFilter').value = ''; document.getElementById('fieldFilter').value = ''; loadTasks(); }; </script> </body> </html>
The application uses IndexedDB to store tasks and custom fields locally in the user's browser.
let db; const request = indexedDB.open('TaskManagerDB', 1); request.onupgradeneeded = function(event) { db = event.target.result; const taskStore = db.createObjectStore('tasks', { keyPath: 'id', autoIncrement: true }); taskStore.createIndex('title', 'title', { unique: false }); const fieldStore = db.createObjectStore('customFields', { keyPath: 'id', autoIncrement: true }); fieldStore.createIndex('name', 'name', { unique: true }); };
tasks
: Stores task objects with properties like title
, description
, status
, and customFields
.customFields
: Stores custom field definitions with name
, type
, and options
(for single select).Users can create custom fields of types: string, number, date, and single select. For single select fields, users provide the options.
fieldForm.onsubmit = function(e) { e.preventDefault(); const name = document.getElementById('fieldName').value.trim(); const type = document.getElementById('fieldType').value; // Validation and options handling const field = { name, type, options: type === 'singleSelect' ? options : [] }; // Add to IndexedDB };
singleSelect
, it also collects the available options.customFields
object store.Users can create tasks by providing a title, description, and filling out any custom fields.
taskForm.onsubmit = function(e) { e.preventDefault(); const title = document.getElementById('taskTitle').value.trim(); const description = document.getElementById('taskDescription').value.trim(); // Collect custom field values const task = { title, description, status: 'Open', customFields: customFieldsData }; // Add to IndexedDB };
tasks
object store.Tasks are displayed in a table view, including any custom fields.
function displayTasks(tasks, customFields, filters = {}) { // Iterate over tasks and create table rows // Include title, description, status, custom fields, and action buttons }
Close
and Delete
).Users can filter tasks based on text input or specific custom field values.
applyFilterBtn.onclick = async function() { const text = document.getElementById('textFilter').value.trim(); const fieldId = document.getElementById('fieldFilter').value; const fieldValue = fieldId ? prompt('Enter value for the selected custom field to filter:') : null; // Apply filters and display filtered tasks };
Popups are used for creating tasks and custom fields. They are displayed and hidden based on user interactions.
createTaskBtn.onclick = function() { taskPopup.style.display = 'block'; }; closeTaskPopup.onclick = function() { taskPopup.style.display = 'none'; document.getElementById('taskForm').reset(); };
×
) or outside the popup closes it and resets the form.Create Custom Fields:
Create Tasks:
Manage Tasks:
Filter Tasks:
Data Persistence: The application uses IndexedDB to store data locally in the browser. Clearing the browser data or using a different browser/device will result in loss of stored tasks and custom fields.
Unique Field Names: Custom field names must be unique. Attempting to create a field with an existing name will result in an error.
Single HTML File: The entire application is contained within a single HTML file with embedded CSS and JavaScript, adhering to the implementation requirements.
Browser Compatibility: Ensure that your browser supports IndexedDB. Most modern browsers do, but older versions might not.
Feel free to customize and enhance the application further based on your specific needs!