// ==UserScript== // @name TAPD批量创建任务 // @namespace http://tampermonkey.net/ // @version 0.1 // @description 在TAPD故事页面快速创建关联任务 // @author GZY // @match https://www.tapd.cn/*/prong/stories/view/* // @grant none // @run-at document-end // @license MIT // @downloadURL https://update.greasyfork.icu/scripts/519919/TAPD%E6%89%B9%E9%87%8F%E5%88%9B%E5%BB%BA%E4%BB%BB%E5%8A%A1.user.js // @updateURL https://update.greasyfork.icu/scripts/519919/TAPD%E6%89%B9%E9%87%8F%E5%88%9B%E5%BB%BA%E4%BB%BB%E5%8A%A1.meta.js // ==/UserScript== (function() { 'use strict'; // 在文件开头添加配置和工具模块 const CONFIG = { API_BASE_URL: 'https://www.tapd.cn', DEFAULT_EFFORT: 0.5, ANIMATION_DURATION: 300, MAX_RETRY_COUNT: 3, TASK_TYPES: ['开发', '自测', '联调', '沟通'], STORAGE_KEY: 'TAPD_HELPER_SETTINGS', DEFAULT_TASK_TYPES: ['沟通', '联调', '自测', '开发'], // 默认匹配顺序 DEFAULT_TASKS: [ // 添加默认任务配置 { suffix: '开发', effortRatio: 1 }, { suffix: '自测', effortRatio: 0.3 }, { suffix: '联调', effortRatio: 0.5 } ] }; // 日志系统 const Logger = { debug(msg, ...args) { console.debug(`[TAPD Helper] ${msg}`, ...args); }, error(msg, ...args) { console.error(`[TAPD Helper] ${msg}`, ...args); } }; // 统一错误处理 function handleError(error, context) { Logger.error(`Error in ${context}:`, error); showNotification('错误', error.message || '操作失败,请稍后重试', true); } // 防抖函数 function debounce(fn, delay = 300) { let timer = null; return function(...args) { clearTimeout(timer); timer = setTimeout(() => fn.apply(this, args), delay); } } // 输入验证 function validateTaskInput(task) { if (!task.name?.trim()) { throw new Error('任务名称不能为空'); } if (task.effort < 0) { throw new Error('工时必须是非负数'); } } // XSS防护 function sanitizeHTML(str) { const div = document.createElement('div'); div.textContent = str; return div.innerHTML; } // 状态管理 const TaskManager = { state: { tasks: [], isProcessing: false, currentModal: null }, setState(newState) { this.state = { ...this.state, ...newState }; this.notifyUpdate(); }, notifyUpdate() { if (this.state.tasks.length > 0) { updatePreview( this.state.tasks[0].name, this.state.tasks[0].effort ); } } }; // 加载指示器 function showLoadingIndicator() { const existingIndicator = document.querySelector('.loading-indicator'); if (existingIndicator) { return existingIndicator; } const indicator = document.createElement('div'); indicator.className = 'loading-indicator'; indicator.innerHTML = `
处理中...
`; document.body.appendChild(indicator); return indicator; } // 更新加载指示器文本 function updateLoadingText(text) { const loadingIndicator = document.querySelector('.loading-indicator'); if (loadingIndicator) { const textElement = loadingIndicator.querySelector('.loading-text'); if (textElement) { textElement.textContent = text; } } } // 操作确认 function confirmAction(message, tasks = null) { return new Promise((resolve) => { const modal = document.createElement('div'); modal.className = 'confirm-modal'; // 获取全局设置中的任务类型 const settings = getSettings(); const taskTypes = settings.taskTypes || CONFIG.DEFAULT_TASK_TYPES; // 修改任务预览的显示方式,使用全局设置的任务类型 const tasksPreview = tasks ? `

处理后的任务状态:

${tasks.map((task, index) => `
${sanitizeHTML(task.name || '未命名任务')}
类型:
复杂度:
功能点:
`).join('')}
总任务数:${tasks.length}
总工时:${tasks.reduce((sum, t) => sum + (t.effort || 0), 0)}
` : ''; modal.innerHTML = `

${sanitizeHTML(message)}

${tasksPreview}
`; // 添加新的样式 const styleSheet = document.getElementById('tapd-task-styles'); if (styleSheet && tasks) { styleSheet.textContent += ` .confirm-tasks-preview { margin: 15px 0; max-height: 400px; overflow-y: auto; } .confirm-task-item { padding: 12px; border-bottom: 1px solid #e8e8e8; background: #fff; } .confirm-task-name { font-weight: 500; margin-bottom: 8px; color: #333; } .confirm-task-info { display: flex; flex-wrap: wrap; gap: 16px; } .task-field { display: flex; align-items: center; gap: 4px; padding: 4px 8px; border-radius: 4px; background: #f5f5f5; } .task-field-new { background: #e6f7ff; border: 1px solid #91d5ff; } .field-label { color: #666; font-size: 13px; } .field-value { color: #333; font-weight: 500; font-size: 13px; } .confirm-tasks-summary { margin-top: 12px; padding: 12px; background: #f5f5f5; border-radius: 4px; display: flex; justify-content: space-between; font-size: 13px; color: #333; } .field-select { appearance: none; -webkit-appearance: none; border: none; background: transparent; font-size: 13px; color: #333; font-weight: 500; padding: 2px 20px 2px 4px; margin-left: 4px; cursor: pointer; outline: none; position: relative; background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='12' viewBox='0 0 12 12'%3E%3Cpath fill='%23666' d='M6 8.825L1.175 4 2.238 2.938 6 6.7l3.763-3.762L10.825 4z'/%3E%3C/svg%3E"); background-repeat: no-repeat; background-position: right 4px center; background-size: 12px; } .field-select:hover { color: #1890ff; background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='12' viewBox='0 0 12 12'%3E%3Cpath fill='%231890ff' d='M6 8.825L1.175 4 2.238 2.938 6 6.7l3.763-3.762L10.825 4z'/%3E%3C/svg%3E"); } .field-select:focus { box-shadow: 0 0 0 2px rgba(24,144,255,0.2); border-radius: 2px; } .task-field-new .field-select { color: #1890ff; background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='12' viewBox='0 0 12 12'%3E%3Cpath fill='%231890ff' d='M6 8.825L1.175 4 2.238 2.938 6 6.7l3.763-3.762L10.825 4z'/%3E%3C/svg%3E"); } .task-field { min-width: 130px; transition: all 0.3s ease; border: 1px solid transparent; } .task-field:hover { border-color: #e6f7ff; background: #f8f8f8; } .task-field-new { background: #e6f7ff; border: 1px solid #91d5ff; } .task-field-new:hover { background: #e6f7ff; border-color: #69c0ff; } /* 优化选项样式 */ .field-select option { padding: 8px 12px; background: white; color: #333; } /* 优化任务信息布局 */ .confirm-task-info { display: flex; flex-wrap: wrap; gap: 12px; padding: 4px 0; } /* 优化任务名称样式 */ .confirm-task-name { font-weight: 500; margin-bottom: 12px; color: #333; padding: 0 4px; } /* 优化任务项样式 */ .confirm-task-item { padding: 16px; border-bottom: 1px solid #e8e8e8; background: #fff; transition: all 0.3s ease; } .confirm-task-item:hover { background: #fafafa; } .confirm-task-item:last-child { border-bottom: none; } `; } // 添加选择框变更事件监听 if (tasks) { const content = modal.querySelector('.confirm-content'); content.addEventListener('change', (e) => { if (e.target.classList.contains('field-select')) { const taskItem = e.target.closest('.confirm-task-item'); const taskIndex = parseInt(taskItem.dataset.taskIndex); const field = e.target.dataset.field; const value = e.target.value; // 更新任务数据 if (field === 'type') { tasks[taskIndex].processed_type = value; } else if (field === 'complexity') { tasks[taskIndex].processed_complexity = value; } else if (field === 'points') { tasks[taskIndex].processed_points = value; } } }); } const handleClose = (result) => { modal.style.animation = 'fadeIn 0.3s ease reverse'; setTimeout(() => { modal.remove(); resolve(result ? tasks : false); // 返回修改后的任务数据 }, 280); }; modal.querySelector('.confirm-yes').onclick = () => handleClose(true); modal.querySelector('.confirm-no').onclick = () => handleClose(false); document.body.appendChild(modal); }); } // API 模块 const TAPD = { API: { async createTask(taskName, taskEffort, beginDate, dueDate) { try { const storyId = extractLastNumber(window.location.href); const workspaceId = window.location.href.match(/tapd\.cn\/(\d+)/)?.[1]; if (!storyId || !workspaceId) { throw new Error('无法从当前URL中提取必要的ID信息'); } validateTaskInput({ name: taskName, effort: taskEffort }); const baseUrl = `${CONFIG.API_BASE_URL}/${workspaceId}/prong/tasks/quick_add_task`; const requestUrl = `${baseUrl}/${storyId}?is_from_story_view=true`; const settings = getSettings(); const formData = new URLSearchParams({ 'data[Task][name]': taskName, 'data[Task][effort]': taskEffort, 'data[Task][owner]': settings.taskOwner || '高子阳' }); if (beginDate) formData.append('data[Task][begin]', beginDate); if (dueDate) formData.append('data[Task][due]', dueDate); const response = await fetch(requestUrl, { method: 'POST', headers: { 'Content-Type': 'application/x-www-form-urlencoded' }, body: formData, credentials: 'include' }); if (!response.ok) { throw new Error(`创建任务 "${taskName}" 失败`); } return await response.json(); } catch (error) { handleError(error, 'createTask'); throw error; } }, async updateTaskField(taskId, field, value) { try { const workspaceId = window.location.href.match(/tapd\.cn\/(\d+)/)?.[1]; const url = `${CONFIG.API_BASE_URL}/${workspaceId}/prong/tasks/inline_update_intab?r=${Date.now()}`; const formData = new FormData(); formData.append('data[id]', taskId); formData.append('data[field]', field); formData.append('data[value]', value); const response = await fetch(url, { method: 'POST', credentials: 'include', body: formData, headers: { 'X-Requested-With': 'XMLHttpRequest' } }); if (!response.ok) { throw new Error(`更新失败: ${response.statusText}`); } const result = await response.text(); if (result.includes('error') || result.includes('失败')) { throw new Error(`更新失败: ${result}`); } Logger.debug(`成功更新任务 ${taskId} 的 ${field} 为 ${value}`); } catch (error) { handleError(error, 'updateTaskField'); throw error; } } } }; // 注入样式 function injectStyles() { const styles = ` .task-modal-overlay { position: fixed; top: 0; left: 0; right: 0; bottom: 0; background-color: rgba(0, 0, 0, 0.5); display: flex; justify-content: center; align-items: center; z-index: 10000; opacity: 0; animation: fadeIn 0.3s ease forwards; overflow-y: auto; pointer-events: none; } .task-modal { background: white; padding: 20px; border-radius: 8px; width: 400px; box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1); opacity: 0; transform: translateY(-20px); animation: modalSlideIn 0.3s ease forwards; pointer-events: auto; } .task-modal h2 { margin: 0 0 20px 0; color: #333; } .task-form-group { margin-bottom: 15px; } .task-form-group label { display: block; margin-bottom: 5px; color: #666; } .task-form-group input { width: 100%; padding: 8px; border: 1px solid #ddd; border-radius: 4px; box-sizing: border-box; } .task-modal-buttons { display: flex; justify-content: flex-end; gap: 10px; margin-top: 20px; } .task-modal-button { padding: 8px 16px; border: none; border-radius: 4px; cursor: pointer; } .task-modal-submit { background-color: #1890ff; color: white; } .task-modal-submit:hover { background-color: #40a9ff; } .task-modal-cancel { background-color: #f0f0f0; color: #666; } .task-modal-cancel:hover { background-color: #e8e8e8; } .task-preview { margin-top: 15px; padding: 10px; background: #f5f5f5; border-radius: 4px; } .task-preview-item { position: relative; padding: 10px; background: #fff; border: 1px solid #e8e8e8; border-left: 3px solid #1890ff; border-radius: 8px; margin-bottom: 12px; display: flex; justify-content: space-between; align-items: center; transition: all 0.3s; } .task-preview-item:hover { box-shadow: 0 2px 8px rgba(0,0,0,0.09); } .task-display-content { flex: 1; display: flex; flex-direction: column; gap: 10px; } .task-name { font-size: 13px; flex: 1; display: flex; align-items: center; } .task-effort { font-size: 13px; min-width: 60px; } .task-edit-content { flex: 1; padding-right: 16px; } .task-edit-row { display: flex; align-items: center; margin-bottom: 8px; } .task-edit-row:last-child { margin-bottom: 0; } .task-edit-row label { min-width: 70px; color: #666; font-size: 14px; } .task-edit-input { flex: 1; padding: 4px 8px; border: 1px solid #d9d9d9; border-radius: 4px; font-size: 14px; transition: all 0.3s; } .task-edit-input:hover { border-color: #40a9ff; } .task-edit-input:focus { border-color: #40a9ff; box-shadow: 0 0 0 2px rgba(24,144,255,0.2); outline: none; } .effort-input { width: 100px !important; flex: none !important; } .task-actions { display: flex; gap: 8px; opacity: 0.6; transition: opacity 0.3s; } .task-preview-item:hover .task-actions { opacity: 1; } .task-action-btn { cursor: pointer; padding: 4px; border-radius: 4px; transition: all 0.3s; font-size: 16px; } .task-action-btn:hover { background-color: #f5f5f5; } .add-task-button { display: flex; align-items: center; justify-content: center; padding: 12px; border: 1px dashed #d9d9d9; border-radius: 4px; color: #1890ff; cursor: pointer; transition: all 0.3s; margin-top: 16px; } .add-task-button:hover { border-color: #40a9ff; background-color: #e6f7ff; } .create-task-button { position: fixed; right: 50px; bottom: 50px; width: 40px; height: 40px; padding: 0; margin: 0; border: none; background: none; cursor: pointer; z-index: 9999; } .create-task-button:hover { transform: scale(1.1); } .create-task-button img { width: 100%; height: 100%; object-fit: contain; } .date-input { width: 100%; padding: 8px; border: 1px solid #ddd; border-radius: 4px; cursor: pointer; font-family: inherit; font-size: inherit; color: inherit; } .date-input:hover { border-color: #40a9ff; } .date-input:focus { outline: none; border-color: #1890ff; box-shadow: 0 0 0 2px rgba(24,144,255,0.2); } /* 移除日期输入框的默认图标(仅在某些浏览器中生效) */ .date-input::-webkit-calendar-picker-indicator { background: transparent; cursor: pointer; width: 100%; height: 100%; position: absolute; top: 0; left: 0; right: 0; bottom: 0; } .date-picker-modal { position: fixed; top: 50%; left: 50%; transform: translate(-50%, calc(-50% - 20px)); background: white; padding: 20px; border-radius: 8px; box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1); z-index: 10001; opacity: 0; animation: datePickerSlideIn 0.3s ease forwards; } .date-picker-overlay { position: fixed; top: 0; left: 0; right: 0; bottom: 0; background: rgba(0, 0, 0, 0.5); z-index: 10000; opacity: 0; animation: fadeIn 0.3s ease forwards; } .date-picker-content { display: flex; gap: 10px; margin-bottom: 15px; } .date-picker-select { padding: 8px; border: 1px solid #ddd; border-radius: 4px; width: 80px; } .date-picker-buttons { display: flex; justify-content: flex-end; gap: 10px; } .date-picker-button { padding: 8px 16px; border: none; border-radius: 4px; cursor: pointer; } .date-picker-confirm { background-color: #1890ff; color: white; } .date-picker-cancel { background-color: #f0f0f0; color: #666; } /* 弹窗动画修复 */ @keyframes fadeIn { from { opacity: 0; } to { opacity: 1; } } @keyframes modalSlideIn { from { opacity: 0; transform: translateY(0); } to { opacity: 1; transform: translateY(0); } } @keyframes datePickerSlideIn { from { opacity: 0; transform: translate(-50%, calc(-50% - 20px)); } to { opacity: 1; transform: translate(-50%, -50%); } } /* 加载状态样式 */ .loading-button { position: relative; pointer-events: none; opacity: 0.7; } .loading-button::after { content: ''; position: absolute; width: 16px; height: 16px; top: 50%; right: 10px; transform: translateY(-50%); border: 2px solid #ffffff; border-top-color: transparent; border-radius: 50%; animation: rotate 0.8s linear infinite; } @keyframes rotate { 0% { transform: translateY(-50%) rotate(0deg); } 100% { transform: translateY(-50%) rotate(360deg); } } /* 优化按钮过渡效果 */ .task-modal-button { transition: all 0.3s ease; } .task-modal-submit:hover { background-color: #40a9ff; } .task-modal-cancel:hover { background-color: #e8e8e8; } .task-preview-row { display: flex; align-items: center; margin-bottom: 8px; } .task-preview-row label { min-width: 70px; margin-right: 8px; } .task-edit-input { flex: 1; padding: 4px 8px; border: 1px solid #ddd; border-radius: 4px; font-size: inherit; } .task-edit-input:hover { border-color: #40a9ff; } .task-edit-input:focus { outline: none; border-color: #1890ff; box-shadow: 0 0 0 2px rgba(24,144,255,0.2); } .task-edit-input[type="number"] { width: 80px; flex: none; } /* 添加菜单样式 */ .task-menu { position: fixed; right: 50px; bottom: 100px; background: white; border-radius: 8px; box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1); padding: 8px 0; z-index: 9999; display: none; min-width: 150px; opacity: 0; transform: translateY(10px); transition: opacity 0.3s, transform 0.3s; } .task-menu.show { display: block; opacity: 1; transform: translateY(0); } .task-menu-item { padding: 10px 16px; cursor: pointer; transition: background-color 0.3s; color: #333; font-size: 14px; display: flex; align-items: center; gap: 8px; } .task-menu-item:hover { background-color: #f5f5f5; } .task-menu-item i { font-size: 16px; color: #1890ff; } /* 加载指示器样式 */ .loading-indicator { position: fixed; top: 0; left: 0; right: 0; bottom: 0; background: rgba(0, 0, 0, 0.65); display: flex; flex-direction: column; justify-content: center; align-items: center; z-index: 10001; backdrop-filter: blur(2px); animation: fadeIn 0.3s ease; } .loading-spinner { width: 40px; height: 40px; border: 3px solid rgba(255, 255, 255, 0.3); border-top: 3px solid #ffffff; border-radius: 50%; animation: spin 1s linear infinite; margin-bottom: 15px; } .loading-text { color: white; font-size: 14px; padding: 8px 16px; background: rgba(0, 0, 0, 0.5); border-radius: 4px; max-width: 80%; text-align: center; } @keyframes spin { 0% { transform: rotate(0deg); } 100% { transform: rotate(360deg); } } /* 优化确认弹窗样式 */ .confirm-modal { position: fixed; top: 0; left: 0; right: 0; bottom: 0; background: rgba(0, 0, 0, 0.65); display: flex; justify-content: center; align-items: center; z-index: 10001; backdrop-filter: blur(2px); animation: fadeIn 0.3s ease; } .confirm-content { background: white; padding: 24px; border-radius: 8px; min-width: 320px; max-width: 90%; box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15); transform: translateY(-20px); animation: slideIn 0.3s ease forwards; } .confirm-content p { margin: 0 0 20px 0; font-size: 14px; color: #333; line-height: 1.5; } .confirm-buttons { display: flex; justify-content: flex-end; gap: 12px; } .confirm-yes, .confirm-no { padding: 8px 16px; border: none; border-radius: 4px; cursor: pointer; font-size: 14px; transition: all 0.3s ease; } .confirm-yes { background-color: #1890ff; color: white; } .confirm-yes:hover { background-color: #40a9ff; } .confirm-no { background-color: #f0f0f0; color: #666; } .confirm-no:hover { background-color: #d9d9d9; } @keyframes slideIn { from { opacity: 0; transform: translateY(-20px); } to { opacity: 1; transform: translateY(0); } } .task-type-item { position: relative; margin-bottom: 16px; } .type-error-message { color: #ff4d4f; font-size: 12px; margin-top: 4px; margin-bottom: 8px; } .task-type.invalid { border-color: #ff4d4f !important; background-color: #fff2f0; } `; const styleSheet = document.createElement('style'); styleSheet.id = 'tapd-task-styles'; styleSheet.textContent = styles; document.head.appendChild(styleSheet); } // 创建弹窗 function createModal() { const modal = document.createElement('div'); modal.className = 'task-modal-overlay'; modal.innerHTML = `

批量创建任务

将创建以下任务:
`; return modal; } // 创建按钮 function createButton() { const button = document.createElement('button'); button.className = 'create-task-button'; button.innerHTML = ` 菜单 `; const menu = document.createElement('div'); menu.className = 'task-menu'; menu.innerHTML = `
🚀 快速创建任务
一键处理任务
⚙️ 全局设置
`; document.body.appendChild(button); document.body.appendChild(menu); // 点击按钮显示/隐藏菜单 button.addEventListener('click', (e) => { e.stopPropagation(); menu.classList.toggle('show'); }); // 点击菜单项 menu.addEventListener('click', (e) => { const menuItem = e.target.closest('.task-menu-item'); if (!menuItem) return; const action = menuItem.dataset.action; menu.classList.remove('show'); if (action === 'create') { showTaskModal(); } else if (action === 'process') { processAllTasks(); } else if (action === 'settings') { showSettingsModal(); } }); // 点击其他区域关闭菜单 document.addEventListener('click', () => { menu.classList.remove('show'); }); return button; } // 更新预览 function updatePreview(taskName, taskEffort, beginDate, dueDate) { if (!taskName) { document.getElementById('taskPreview').innerHTML = ''; return; } // 初始化时创建默认任务 if (!window.customTasks) { const settings = getSettings(); // 使用设置中的任务配置或默认配置 const tasksConfig = settings.defaultTasks || CONFIG.DEFAULT_TASKS; window.customTasks = tasksConfig.map(task => ({ name: `${taskName}-${task.suffix}`, effort: taskEffort * task.effortRatio, isEditing: false, isDefault: true })); } else { // 只更新默认任务的属性 const settings = getSettings(); const tasksConfig = settings.defaultTasks || CONFIG.DEFAULT_TASKS; window.customTasks.forEach((task, index) => { if (task.isDefault && index < tasksConfig.length) { const defaultTask = tasksConfig[index]; task.name = `${taskName}-${defaultTask.suffix}`; task.effort = taskEffort * defaultTask.effortRatio; } }); } let tasks = window.customTasks; function renderTasks() { const previewHtml = tasks.map((task, index) => `
${task.isEditing ? `
` : `
名称:${task.name}
工时:${task.effort}
`}
✏️ 🗑️ 📋
`).join(''); const addButtonHtml = `
+ 添加任务
`; document.getElementById('taskPreview').innerHTML = previewHtml + addButtonHtml; } // 编辑任务 window.handleEdit = (index) => { tasks[index].isEditing = !tasks[index].isEditing; renderTasks(); }; // 删除任务 window.handleDelete = (index) => { if (tasks[index].isDefault) { showNotification('提示', '默认任务不能删除', true); return; } tasks = tasks.filter((_, i) => i !== index); window.customTasks = tasks; renderTasks(); }; // 复制任务 window.handleCopy = (index) => { const newTask = { ...tasks[index], name: tasks[index].name + '-副本', isDefault: false, isEditing: false }; tasks.push(newTask); window.customTasks = tasks; renderTasks(); }; // 添加新任务 window.handleAdd = () => { const newTask = { name: taskName + '-新任务', effort: taskEffort, isEditing: true, isDefault: false }; tasks.push(newTask); window.customTasks = tasks; renderTasks(); }; // 初始渲染 renderTasks(); // 监听输入变化 document.addEventListener('change', (e) => { if (e.target.classList.contains('task-edit-input')) { const index = parseInt(e.target.dataset.index); const type = e.target.dataset.type; const value = e.target.value; if (type === 'name') { tasks[index].name = value; } else if (type === 'effort') { tasks[index].effort = parseFloat(value) || 0; } tasks[index].isEditing = false; renderTasks(); } }); return tasks; } // 批量创建任务 async function batchCreateTasks(tasks, beginDate, dueDate) { const loadingIndicator = showLoadingIndicator(); try { TaskManager.setState({ isProcessing: true }); for (const task of tasks) { updateLoadingText(`创建任务 ${task.name} 中...`); await TAPD.API.createTask(task.name, task.effort, beginDate, dueDate); await new Promise(resolve => setTimeout(resolve, 500)); } updateLoadingText('所有任务创建完成!'); showNotification('成功', '所有任务创建完成!'); setTimeout(() => { window.location.reload(); }, 1000); } catch (error) { handleError(error, 'batchCreateTasks'); } finally { TaskManager.setState({ isProcessing: false }); loadingIndicator.remove(); } } // 显示任务创建弹窗 function showTaskModal() { const modal = createModal(); document.body.appendChild(modal); const taskNameInput = modal.querySelector('#taskName'); const taskEffortInput = modal.querySelector('#taskEffort'); const taskBeginInput = modal.querySelector('#taskBegin'); const taskDueInput = modal.querySelector('#taskDue'); // 更新预览处理 function updatePreviewHandler() { const taskName = taskNameInput.value.trim(); if (!taskName) { document.getElementById('taskPreview').innerHTML = ''; return; } updatePreview( taskName, taskEffortInput.value, taskBeginInput.value, taskDueInput.value ); } // 添加输入事件监听 taskNameInput.addEventListener('input', updatePreviewHandler); taskEffortInput.addEventListener('input', updatePreviewHandler); taskBeginInput.addEventListener('change', updatePreviewHandler); taskDueInput.addEventListener('change', updatePreviewHandler); // 添加日期选择器点击事件 taskBeginInput.addEventListener('click', () => { createDatePickerModal(taskBeginInput.value, (date) => { taskBeginInput.value = date; updatePreviewHandler(); }); }); taskDueInput.addEventListener('click', () => { createDatePickerModal(taskDueInput.value, (date) => { taskDueInput.value = date; updatePreviewHandler(); }); }); // 表单提交处理 const form = modal.querySelector('#taskForm'); form.addEventListener('submit', async (e) => { e.preventDefault(); const loadingIndicator = showLoadingIndicator(); const submitButton = form.querySelector('.task-modal-submit'); submitButton.textContent = '创建中...'; updateLoadingText('创建任务中...'); submitButton.disabled = true; try { const taskName = taskNameInput.value.trim(); const taskEffort = taskEffortInput.value; const beginDate = taskBeginInput.value; const dueDate = taskDueInput.value; // 获取预览中最新的任务数据 const tasks = updatePreview(taskName, taskEffort, beginDate, dueDate); modal.remove(); await batchCreateTasks(tasks, beginDate, dueDate); } catch (error) { submitButton.textContent = '创建全部'; submitButton.disabled = false; showNotification('错误', error.message, true); } }); // 取消按钮处理 const cancelButton = modal.querySelector('.task-modal-cancel'); cancelButton.addEventListener('click', () => { modal.remove(); }); // 点击遮罩层关闭 modal.addEventListener('click', (e) => { if (e.target === modal) { modal.remove(); } }); } // 从 URL 中提取最后一个数字序列 function extractLastNumber(url) { const matches = url.match(/(\d+)(?!.*\d)/); return matches ? matches[0] : null; } // 显示通知 function showNotification(title, message, isError = false) { const notification = document.createElement('div'); notification.style.cssText = ` position: fixed; top: 20px; left: 50%; transform: translate(-50%, 0); padding: 12px 24px; background-color: ${isError ? '#ff4d4f' : '#52c41a'}; color: white; border-radius: 4px; z-index: 10000; box-shadow: 0 2px 8px rgba(0,0,0,0.15); min-width: 200px; text-align: center; opacity: 0; animation: notificationSlideIn 0.3s ease forwards; `; notification.textContent = `${title}: ${message}`; document.body.appendChild(notification); // 添加动画样式 const styleSheet = document.getElementById('tapd-task-styles'); if (styleSheet) { styleSheet.textContent += ` @keyframes notificationSlideIn { from { opacity: 0; transform: translate(-50%, -20px); } to { opacity: 1; transform: translate(-50%, 0); } } @keyframes notificationSlideOut { from { opacity: 1; transform: translate(-50%, 0); } to { opacity: 0; transform: translate(-50%, -20px); } } `; } setTimeout(() => { notification.style.animation = 'notificationSlideOut 0.3s ease forwards'; setTimeout(() => notification.remove(), 300); }, 3000); } // 添加日期选择器弹窗创建函数 function createDatePickerModal(initialDate, onConfirm) { const overlay = document.createElement('div'); overlay.className = 'date-picker-overlay'; const modal = document.createElement('div'); modal.className = 'date-picker-modal'; const currentDate = initialDate ? new Date(initialDate) : new Date(); const year = currentDate.getFullYear(); const month = currentDate.getMonth() + 1; const day = currentDate.getDate(); // 创建年份选项(前后5年) const yearOptions = Array.from({ length: 11 }, (_, i) => year - 5 + i) .map(y => ``) .join(''); // 创建月份选项 const monthOptions = Array.from({ length: 12 }, (_, i) => i + 1) .map(m => ``) .join(''); // 创建日期选项 const daysInMonth = new Date(year, month, 0).getDate(); const dayOptions = Array.from({ length: daysInMonth }, (_, i) => i + 1) .map(d => ``) .join(''); modal.innerHTML = `
`; overlay.appendChild(modal); document.body.appendChild(overlay); // 更新日期选项 function updateDays() { const yearSelect = modal.querySelector('#yearSelect'); const monthSelect = modal.querySelector('#monthSelect'); const daySelect = modal.querySelector('#daySelect'); const selectedYear = parseInt(yearSelect.value); const selectedMonth = parseInt(monthSelect.value); const daysInSelectedMonth = new Date(selectedYear, selectedMonth, 0).getDate(); const currentDay = parseInt(daySelect.value); daySelect.innerHTML = Array.from({ length: daysInSelectedMonth }, (_, i) => i + 1) .map(d => ``) .join(''); } // 事件监听 modal.querySelector('#yearSelect').addEventListener('change', updateDays); modal.querySelector('#monthSelect').addEventListener('change', updateDays); // 修改关闭函数 function closeDatePicker() { overlay.style.animation = 'fadeIn 0.3s ease reverse'; modal.style.animation = 'slideIn 0.3s ease reverse'; setTimeout(() => { overlay.remove(); }, 300); } // 修改事件监听 modal.querySelector('.date-picker-confirm').addEventListener('click', () => { const year = modal.querySelector('#yearSelect').value; const month = modal.querySelector('#monthSelect').value.padStart(2, '0'); const day = modal.querySelector('#daySelect').value.padStart(2, '0'); onConfirm(`${year}-${month}-${day}`); closeDatePicker(); }); modal.querySelector('.date-picker-cancel').addEventListener('click', closeDatePicker); overlay.addEventListener('click', (e) => { if (e.target === overlay) { closeDatePicker(); } }); } // 处理任务函数 async function processAllTasks() { const loadingIndicator = showLoadingIndicator(); try { TaskManager.setState({ isProcessing: true }); Logger.debug('开始获取任务列表...'); updateLoadingText('获取任务列表中...'); const storyId = extractLastNumber(window.location.href); const workspaceId = window.location.href.match(/tapd\.cn\/(\d+)/)?.[1]; if (!storyId || !workspaceId) { throw new Error('无法从当前URL中提取必要的ID信息'); } // 存储所有页面的任务 let allTasks = []; let currentPage = 1; let totalPages = 1; // 获取第一页并检查是否有分页 const firstPageUrl = `https://www.tapd.cn/${workspaceId}/prong/tasks/task_list_common?story_id=${storyId}&page=1&time=${Date.now()}`; const firstPageResponse = await fetch(firstPageUrl, { credentials: 'include' }); if (!firstPageResponse.ok) { throw new Error('获取任务列表失败'); } const firstPageHtml = await firstPageResponse.text(); const firstPageDoc = new DOMParser().parseFromString(firstPageHtml, 'text/html'); // 检查是否有分页并获取总页数 const pagerDiv = firstPageDoc.querySelector('#simple_pager_div'); if (pagerDiv && pagerDiv.innerHTML.trim()) { const currentPageSpan = pagerDiv.querySelector('.current-page'); if (currentPageSpan) { const pageInfo = currentPageSpan.textContent.trim().split('/'); if (pageInfo.length === 2) { totalPages = parseInt(pageInfo[1]); Logger.debug(`检测到分页,共 ${totalPages} 页`); } } } // 解析第一页的任务 const firstPageTasks = parseTasks(firstPageDoc); allTasks = allTasks.concat(firstPageTasks); // 如果有多页,获取剩余页面的任务 if (totalPages > 1) { for (let page = 2; page <= totalPages; page++) { updateLoadingText(`获取第 ${page}/${totalPages} 页任务列表...`); Logger.debug(`正在获取第 ${page} 页`); const pageUrl = `https://www.tapd.cn/${workspaceId}/prong/tasks/task_list_common?story_id=${storyId}&page=${page}&time=${Date.now()}`; const pageResponse = await fetch(pageUrl, { credentials: 'include' }); if (!pageResponse.ok) { throw new Error(`获取第 ${page} 页任务列表失败`); } const pageHtml = await pageResponse.text(); const pageDoc = new DOMParser().parseFromString(pageHtml, 'text/html'); const pageTasks = parseTasks(pageDoc); allTasks = allTasks.concat(pageTasks); // 添加延迟避免请求过快 await new Promise(resolve => setTimeout(resolve, 500)); } } // 检查是否有需要处理的任务 const fieldMapping = window.TAPD_FIELD_MAPPING; if (!fieldMapping) { throw new Error('未找到字段映射信息'); } const tasksNeedProcess = allTasks.some(task => !task[fieldMapping.taskType] || !task[fieldMapping.complexity] || !task[fieldMapping.points] ); if (!tasksNeedProcess) { showNotification('提示', '没有需要处理的任务'); return; } // 获取设置和任务类型配置 const settings = getSettings(); const taskTypes = settings.taskTypes || CONFIG.DEFAULT_TASK_TYPES; // 预处理任务数据 const processedTasks = allTasks.filter(task => (!task[fieldMapping.taskType] || !task[fieldMapping.complexity] || !task[fieldMapping.points]) && task.name ).map(task => { const result = { ...task }; // 计算任务类型 if (!task[fieldMapping.taskType]) { const name = (task.name || '').toLowerCase(); let matched = false; for (const type of taskTypes) { const typePattern = type.toLowerCase(); if (name.includes(typePattern)) { result.processed_type = type; matched = true; break; } } if (!matched) { result.processed_type = '开发'; Logger.debug(`任务 "${task.name}" 未匹配到任何类型,使用默认类型:开发`); } } // 计算复杂度 if (!task[fieldMapping.complexity]) { result.processed_complexity = task.effort > 2 ? '3' : '1'; } // 计算功能点数 if (!task[fieldMapping.points]) { result.processed_points = '1'; } return result; }); // 检查是否有有效的任务需要处理 if (processedTasks.length === 0) { showNotification('提示', '没有需要处理的有效任务'); return; } // 显示确认弹窗 const updatedTasks = await confirmAction( `发现 ${processedTasks.length} 个任务需要处理,是否继续?`, processedTasks ); if (!updatedTasks) { return; } // 更新任务字段 const newLoadingIndicator = showLoadingIndicator(); Logger.debug('开始更新任务字段...'); updateLoadingText('更新任务字段中...'); await updateTaskFields(updatedTasks); Logger.debug('所有任务字段更新完成'); updateLoadingText('任务字段更新完成'); showNotification('成功', '所有任务字段已更新'); setTimeout(() => { window.location.reload(); }, 1000); } catch (error) { handleError(error, 'processAllTasks'); } finally { TaskManager.setState({ isProcessing: false }); if (document.querySelector('.loading-indicator')) { document.querySelector('.loading-indicator').remove(); } } } // 新增解析任务的辅助函数 function parseTasks(doc) { const tasks = []; const form = doc.querySelector('#quick_add_task'); if (!form) return tasks; // 从表头获取字段映射 const fieldMapping = {}; const headers = form.querySelectorAll('th[data-editable-field]'); headers.forEach(header => { const field = header.getAttribute('data-editable-field'); if (field === 'custom_field_one' || field === 'custom_field_two' || field === 'custom_field_three' || field === 'custom_field_five' || field === 'custom_field_six' ) { const title = header.querySelector('a')?.textContent?.trim() || ''; fieldMapping[title] = field; } }); // 记录字段映射到全局配置 if (!window.TAPD_FIELD_MAPPING) { window.TAPD_FIELD_MAPPING = { taskType: fieldMapping['任务类型'] || 'custom_field_one', complexity: fieldMapping['复杂度'] || 'custom_field_two', points: fieldMapping['功能点数'] || 'custom_field_three' }; Logger.debug('字段映射:', window.TAPD_FIELD_MAPPING); } const rows = form.querySelectorAll('tbody tr'); rows.forEach(row => { const task = { id: row.getAttribute('id'), name: row.querySelector('.td_name a')?.textContent?.trim(), status: row.querySelector('.checkable-capsule-item')?.textContent?.trim(), effort: parseFloat(row.querySelector('.td_effort')?.textContent) || 0, progress: row.querySelector('.td_progress')?.textContent?.trim(), owner: row.querySelector('.td_owner')?.textContent?.trim(), begin: row.querySelector('.td_begin')?.textContent?.trim(), due: row.querySelector('.td_due')?.textContent?.trim(), created: row.querySelector('.td_created')?.textContent?.trim(), effort_completed: parseFloat(row.querySelector('.td_effort_completed')?.textContent) || 0, exceed: parseFloat(row.querySelector('.td_exceed')?.textContent) || 0, completed: row.querySelector('.td_completed')?.textContent?.trim() }; // 使用字段映射获取自定义字段值 task[window.TAPD_FIELD_MAPPING.taskType] = row.querySelector(`.td_${window.TAPD_FIELD_MAPPING.taskType}`)?.textContent?.trim(); task[window.TAPD_FIELD_MAPPING.complexity] = row.querySelector(`.td_${window.TAPD_FIELD_MAPPING.complexity}`)?.textContent?.trim(); task[window.TAPD_FIELD_MAPPING.points] = row.querySelector(`.td_${window.TAPD_FIELD_MAPPING.points}`)?.textContent?.trim(); tasks.push(task); }); return tasks; } // 初始化 function init() { injectStyles(); createButton(); } // 确保 DOM 加载完成后再初始化 if (document.readyState === 'loading') { document.addEventListener('DOMContentLoaded', init); } else { init(); } // 在processAllTasks函数中,解析完tasks数据后添加: async function updateTaskFields(tasks) { const fieldMapping = window.TAPD_FIELD_MAPPING; if (!fieldMapping) { throw new Error('未找到字段映射信息'); } for(const task of tasks) { try { // 1. 处理任务类型 if(!task[fieldMapping.taskType]) { const taskType = task.processed_type || '开发'; await updateTaskField(task.id, fieldMapping.taskType, taskType); Logger.debug(`设置任务 "${task.name}" 的类型为:${taskType}`); } // 2. 处理复杂度 if(!task[fieldMapping.complexity]) { const complexity = task.processed_complexity || (task.effort > 2 ? '3' : '1'); await updateTaskField(task.id, fieldMapping.complexity, complexity); } // 3. 处理功能点数 if(!task[fieldMapping.points]) { const points = task.processed_points || '1'; await updateTaskField(task.id, fieldMapping.points, points); } } catch(error) { console.error(`更新任务 ${task.id} 失败:`, error); showNotification('错误', `更新任务 ${task.id} 失败`, true); } } } // 发送更新请求的函数 async function updateTaskField(taskId, field, value) { const workspaceId = window.location.href.match(/tapd\.cn\/(\d+)/)?.[1]; const url = `https://www.tapd.cn/${workspaceId}/prong/tasks/inline_update_intab?r=${Date.now()}`; const formData = new FormData(); formData.append('data[id]', taskId); formData.append('data[field]', field); formData.append('data[value]', value); const response = await fetch(url, { method: 'POST', credentials: 'include', body: formData, headers: { // 不需要设置 Content-Type,让浏览器自动处理 'X-Requested-With': 'XMLHttpRequest' } }); if (!response.ok) { throw new Error(`更新失败: ${response.statusText}`); } const result = await response.text(); if(result.includes('error') || result.includes('失败')) { throw new Error(`更新失败: ${result}`); } console.log(`成功更新任务 ${taskId} 的 ${field} 为 ${value}`); } // 获取设置 function getSettings() { const settings = localStorage.getItem(CONFIG.STORAGE_KEY); return settings ? JSON.parse(settings) : { taskOwner: '高子阳', defaultTasks: CONFIG.DEFAULT_TASKS, // 使用 CONFIG 中的默认配置 taskTypes: CONFIG.DEFAULT_TASK_TYPES }; } // 保存设置 function saveSettings(settings) { localStorage.setItem(CONFIG.STORAGE_KEY, JSON.stringify(settings)); } // 创建设置弹窗 function showSettingsModal() { const currentSettings = getSettings(); const modal = document.createElement('div'); modal.className = 'task-modal-overlay'; modal.innerHTML = `

全局设置

${currentSettings.taskTypes.map((type, index) => `
${index + 1}
`).join('')}
${currentSettings.defaultTasks.map((task, index) => `
${index > 0 ? ` ` : `
`}
`).join('')}

👉 设置将保存在浏览器本地存储中

👉 这些设置将应用于所有新创建的任务

`; // 添加样式 const styleSheet = document.getElementById('tapd-task-styles'); if (styleSheet) { styleSheet.textContent += ` .settings-modal { max-width: 600px; } .default-task-item { margin-bottom: 10px; } .task-input-group { display: flex; gap: 10px; align-items: center; } .task-suffix { flex: 2; } .task-ratio { flex: 1; width: 80px; } .remove-task-btn { background: none; border: none; cursor: pointer; padding: 5px; font-size: 14px; } .add-default-task-btn { margin-top: 10px; padding: 8px 16px; background: #f0f0f0; border: none; border-radius: 4px; cursor: pointer; width: 100%; text-align: center; } .add-default-task-btn:hover { background: #e0e0e0; } .remove-task-btn-placeholder { width: 29px; height: 28px; } .task-type-item { margin-bottom: 10px; } .task-type-order { width: 24px; text-align: center; color: #666; } .move-type-btn { padding: 4px 8px; background: none; border: 1px solid #ddd; border-radius: 4px; cursor: pointer; } .move-type-btn:disabled { opacity: 0.5; cursor: not-allowed; } .move-type-btn:not(:disabled):hover { background: #f0f0f0; } .add-type-btn { margin-top: 10px; padding: 8px 16px; background: #f0f0f0; border: none; border-radius: 4px; cursor: pointer; width: 100%; text-align: center; } .add-type-btn:hover { background: #e0e0e0; } .remove-type-btn { background: none; border: none; cursor: pointer; padding: 5px; font-size: 14px; opacity: 0.7; } .remove-type-btn:hover:not(:disabled) { opacity: 1; } .remove-type-btn:disabled { cursor: not-allowed; opacity: 0.3; } `; } document.body.appendChild(modal); // 添加输入事件监听 function addInputListener(input) { input.addEventListener('input', function() { const item = this.closest('.task-type-item'); const index = parseInt(item.dataset.index); validateTaskTypeInput(this, index + 1); updateTaskTypeOrder(); }); } // 为所有现有的任务类型输入框添加监听 const taskTypeInputs = modal.querySelectorAll('.task-type'); taskTypeInputs.forEach(input => addInputListener(input)); window.validateTaskTypeInput = function(input, index) { const item = input.closest('.task-type-item'); const inputGroup = item.querySelector('.task-input-group'); const isEmpty = !input.value.trim(); input.classList.toggle('invalid', isEmpty); // 移除旧的错误提示 const oldErrorDiv = item.querySelector('.type-error-message'); if (oldErrorDiv) { oldErrorDiv.remove(); } // 添加新的错误提示 if (isEmpty) { const errorDiv = document.createElement('div'); errorDiv.className = 'type-error-message'; errorDiv.textContent = `序号 ${index} 的任务类型不能为空`; // 将错误提示插入到 input-group 后面 inputGroup.insertAdjacentElement('afterend', errorDiv); } }; // 添加任务按钮处理函数 window.handleAddDefaultTask = () => { const container = document.getElementById('defaultTasksContainer'); const index = container.children.length; const newTaskHtml = `
`; container.insertAdjacentHTML('beforeend', newTaskHtml); }; // 删除任务按钮处理函数 window.handleRemoveTask = (index) => { const container = document.getElementById('defaultTasksContainer'); const taskItem = container.querySelector(`[data-index="${index}"]`); if (taskItem) { taskItem.remove(); } }; // 添加移动任务类型顺序的处理函数 window.handleMoveType = (index, direction) => { const container = document.getElementById('taskTypesContainer'); const items = container.querySelectorAll('.task-type-item'); if (direction === 'up' && index > 0) { container.insertBefore(items[index], items[index - 1]); } else if (direction === 'down' && index < items.length - 1) { container.insertBefore(items[index + 1], items[index]); } // 更新序号和按钮状态 updateTaskTypeOrder(); }; // 更新任务类型序号和按钮状态 window.updateTaskTypeOrder = function() { const items = document.querySelectorAll('.task-type-item'); const totalItems = items.length; items.forEach((item, index) => { item.querySelector('.task-type-order').textContent = index + 1; item.dataset.index = index; const upBtn = item.querySelector('.move-type-btn[onclick*="up"]'); const downBtn = item.querySelector('.move-type-btn[onclick*="down"]'); const removeBtn = item.querySelector('.remove-type-btn'); // 更新按钮状态 upBtn.disabled = index === 0; downBtn.disabled = index === totalItems - 1; removeBtn.disabled = totalItems <= 1; // 更新按钮事件 upBtn.setAttribute('onclick', `handleMoveType(${index}, 'up')`); downBtn.setAttribute('onclick', `handleMoveType(${index}, 'down')`); removeBtn.setAttribute('onclick', `handleRemoveType(${index})`); // 更新按钮样式 upBtn.classList.toggle('disabled', upBtn.disabled); downBtn.classList.toggle('disabled', downBtn.disabled); removeBtn.classList.toggle('disabled', removeBtn.disabled); // 重新验证当前输入 const input = item.querySelector('.task-type'); validateTaskTypeInput(input, index + 1); }); // 更新保存按钮状态 const form = items[0]?.closest('form'); if (form) { const submitBtn = form.querySelector('.task-modal-submit'); const hasErrors = form.querySelectorAll('.type-error-message').length > 0; submitBtn.disabled = hasErrors; submitBtn.classList.toggle('disabled', hasErrors); } }; // 表单提交处理 const form = modal.querySelector('#settingsForm'); form.addEventListener('submit', async (e) => { e.preventDefault(); const taskTypes = Array.from(form.querySelectorAll('.task-type')) .map(input => input.value.trim()); const settings = { taskOwner: form.querySelector('#taskOwner').value.trim(), defaultTasks: Array.from(form.querySelectorAll('.default-task-item')).map(item => ({ suffix: item.querySelector('.task-suffix').value.trim(), effortRatio: parseFloat(item.querySelector('.task-ratio').value) || 0 })), taskTypes }; try { saveSettings(settings); showNotification('成功', '设置已保存'); modal.remove(); } catch (error) { showNotification('错误', '保存设置失败', true); } }); // 取消按钮处理 const cancelButton = modal.querySelector('.task-modal-cancel'); cancelButton.addEventListener('click', () => { modal.remove(); }); // 点击遮罩层关闭 modal.addEventListener('click', (e) => { if (e.target === modal) { modal.remove(); } }); } // 添加新的处理函数 window.handleAddType = () => { const container = document.getElementById('taskTypesContainer'); const items = container.querySelectorAll('.task-type-item'); // 检查现有任务类型是否有空值 let hasEmptyType = false; items.forEach((item, idx) => { if (!item.querySelector('.task-type').value.trim()) { hasEmptyType = true; showNotification('提示', `请先填写序号 ${idx + 1} 的任务类型`, true); } }); if (hasEmptyType) return; // 更新倒数第二个元素的下箭头状态 if (items.length > 0) { const previousLastItem = items[items.length - 1]; const previousLastDownBtn = previousLastItem.querySelector('.move-type-btn[onclick*="down"]'); previousLastDownBtn.disabled = false; previousLastDownBtn.classList.remove('disabled'); } const index = items.length; const newTypeHtml = `
${index + 1}
`; container.insertAdjacentHTML('beforeend', newTypeHtml); // 为新添加的输入框添加事件监听 const newItem = container.lastElementChild; const newInput = newItem.querySelector('.task-type'); // 添加输入事件监听 newInput.addEventListener('input', function() { validateTaskTypeInput(this, parseInt(newItem.dataset.index) + 1); updateTaskTypeOrder(); }); // 添加焦点事件,自动聚焦到新添加的输入框 newInput.focus(); // 立即验证新添加的输入框 validateTaskTypeInput(newInput, index + 1); updateTaskTypeOrder(); }; // 添加删除任务类型的处理函数 window.handleRemoveType = (index) => { const container = document.getElementById('taskTypesContainer'); const items = container.querySelectorAll('.task-type-item'); // 不允许删除最后一个任务类型 if (items.length <= 1) { showNotification('提示', '至少需要保留一个任务类型', true); return; } // 删除前记录是否是第一个或最后一个元素 const isFirst = index === 0; const isLast = index === items.length - 1; items[index].remove(); // 如果删除的是第一个或最后一个,需要立即更新相邻元素的状态 if (isFirst || isLast) { const newItems = container.querySelectorAll('.task-type-item'); if (isFirst && newItems.length > 0) { // 更新新的第一个元素 const firstItem = newItems[0]; const upBtn = firstItem.querySelector('.move-type-btn[onclick*="up"]'); upBtn.disabled = true; } if (isLast && newItems.length > 0) { // 更新新的最后一个元素 const lastItem = newItems[newItems.length - 1]; const downBtn = lastItem.querySelector('.move-type-btn[onclick*="down"]'); downBtn.disabled = true; } } updateTaskTypeOrder(); // 更新所有项的序号和按钮状态 }; })();