From a5de813c823f1021edcda3d7c64fa60b2b65a574 Mon Sep 17 00:00:00 2001 From: Cccc_ Date: Mon, 5 Jan 2026 20:53:25 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E6=94=AF=E6=8C=81=E5=88=9B=E5=BB=BA?= =?UTF-8?q?=E4=BB=BB=E5=8A=A1=E6=97=B6=E8=87=AA=E5=AE=9A=E4=B9=89crontab?= =?UTF-8?q?=E5=B9=B6=E6=B8=85=E7=90=86=E5=86=97=E4=BD=99=E4=BB=A3=E7=A0=81?= =?UTF-8?q?=20-=20=E6=B7=BB=E5=8A=A0=E5=88=9B=E5=BB=BA=E4=BB=BB=E5=8A=A1?= =?UTF-8?q?=E6=97=B6=E7=9A=84crontab=E7=BC=96=E8=BE=91=E6=8E=A7=E4=BB=B6?= =?UTF-8?q?=20-=20=E4=BF=AE=E5=A4=8D=E5=88=9B=E5=BB=BA=E4=BB=BB=E5=8A=A1?= =?UTF-8?q?=E6=8C=89=E9=92=AE=E7=8A=B6=E6=80=81=E9=87=8D=E7=BD=AE=E9=97=AE?= =?UTF-8?q?=E9=A2=98=20-=20=E5=88=9B=E5=BB=BA=E4=BB=BB=E5=8A=A1=E5=90=8E?= =?UTF-8?q?=E8=87=AA=E5=8A=A8=E5=8A=A0=E8=BD=BD=E5=88=B0=E8=B0=83=E5=BA=A6?= =?UTF-8?q?=E5=99=A8=20-=20=E5=88=A0=E9=99=A4=E5=BA=9F=E5=BC=83=E7=9A=84?= =?UTF-8?q?=E6=89=8B=E5=8A=A8=E5=88=9B=E5=BB=BA=E4=BB=BB=E5=8A=A1API?= =?UTF-8?q?=E5=92=8C=E7=9B=B8=E5=85=B3=E4=BB=A3=E7=A0=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- backend/api/tasks.py | 34 ++------------------------ backend/api/templates.py | 4 +++- backend/schemas/template.py | 1 + backend/services/template_service.py | 15 +++++++++--- frontend/src/api/index.js | 5 ---- frontend/src/stores/task.js | 19 --------------- frontend/src/stores/template.js | 3 ++- frontend/src/views/TasksView.vue | 36 +++++++++++++++------------- 8 files changed, 40 insertions(+), 77 deletions(-) diff --git a/backend/api/tasks.py b/backend/api/tasks.py index 55dbea4..975de47 100644 --- a/backend/api/tasks.py +++ b/backend/api/tasks.py @@ -5,7 +5,7 @@ from datetime import datetime, timedelta from pydantic import BaseModel, Field from backend.models import get_db, User -from backend.schemas.task import TaskCreate, TaskUpdate, TaskResponse +from backend.schemas.task import TaskUpdate, TaskResponse from backend.services.task_service import TaskService from backend.dependencies import get_current_user @@ -16,37 +16,7 @@ class CronValidateRequest(BaseModel): """Cron 表达式验证请求""" cron_expression: str = Field(..., min_length=9, description="Crontab 表达式") - -@router.post("/", response_model=TaskResponse, status_code=status.HTTP_201_CREATED, summary="创建打卡任务") -async def create_task( - task_data: TaskCreate, - current_user: User = Depends(get_current_user), - db: Session = Depends(get_db) -): - """ - 创建新的打卡任务(基于模板) - - 现在的任务创建流程: - 1. 管理员在后台创建模板(包含完整的 payload_config) - 2. 用户基于模板创建任务,填写字段值 - 3. 系统自动生成完整的 payload_config - - 注意:直接创建任务的方式已废弃,请使用模板接口。 - """ - try: - task = TaskService.create_task(current_user.id, task_data, db) - return task - except ValueError as e: - raise HTTPException( - status_code=status.HTTP_400_BAD_REQUEST, - detail=str(e) - ) - except Exception as e: - raise HTTPException( - status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, - detail=f"创建任务失败: {str(e)}" - ) - +# create_task_from_template: 已在 templates.py 中定义 @router.get("/", response_model=List[TaskResponse], summary="获取当前用户的任务列表") async def get_tasks( diff --git a/backend/api/templates.py b/backend/api/templates.py index 58c20e0..f1ef39d 100644 --- a/backend/api/templates.py +++ b/backend/api/templates.py @@ -200,6 +200,7 @@ async def create_task_from_template( - **thread_id**: 接龙项目 ID - **field_values**: 用户填写的字段值 - **task_name**: 任务名称(可选) + - **cron_expression**: Cron 表达式(可选,默认每天 20:00) """ task = TemplateService.create_task_from_template( template_id=request.template_id, @@ -207,6 +208,7 @@ async def create_task_from_template( field_values=request.field_values, user_id=current_user.id, task_name=request.task_name, - db=db + db=db, + cron_expression=request.cron_expression ) return task diff --git a/backend/schemas/template.py b/backend/schemas/template.py index 30ac8bf..9338aa9 100644 --- a/backend/schemas/template.py +++ b/backend/schemas/template.py @@ -137,6 +137,7 @@ class TaskFromTemplateRequest(BaseModel): thread_id: str = Field(..., min_length=1, description="接龙项目 ID") field_values: Dict[str, Any] = Field(default_factory=dict, description="用户填写的字段值") task_name: Optional[str] = Field(None, max_length=100, description="任务名称(可选)") + cron_expression: Optional[str] = Field("0 20 * * *", description="Cron 表达式(可选,默认每天 20:00)") class TemplatePreviewResponse(BaseModel): diff --git a/backend/services/template_service.py b/backend/services/template_service.py index fa067d5..7d81274 100644 --- a/backend/services/template_service.py +++ b/backend/services/template_service.py @@ -503,7 +503,8 @@ class TemplateService: field_values: Dict[str, Any], user_id: int, task_name: Optional[str], - db: Session + db: Session, + cron_expression: Optional[str] = "0 20 * * *" ) -> CheckInTask: """ 从模板创建打卡任务 @@ -515,6 +516,7 @@ class TemplateService: user_id: 用户 ID task_name: 任务名称(可选) db: 数据库会话 + cron_expression: Cron 表达式(可选,默认每天 20:00) Returns: 创建的任务对象 @@ -544,19 +546,26 @@ class TemplateService: signature = payload.get('Signature', 'Unknown') task_name = f"{template.name} - {signature}" - # 创建任务(只存储 payload_config,不再需要 thread_id 和 email) + # 创建任务(包含 cron_expression) try: task = CheckInTask( user_id=user_id, payload_config=json.dumps(payload, ensure_ascii=False), name=task_name, - is_active=True + is_active=True, + cron_expression=cron_expression or "0 20 * * *" ) db.add(task) db.commit() db.refresh(task) logger.info(f"从模板创建任务成功: {task.name} (ID: {task.id}, 模板: {template.name}, ThreadId: {thread_id})") + + # 如果任务启用且包含 cron_expression,立即添加到调度器 + if task.is_scheduled_enabled: + from backend.services.task_service import TaskService + TaskService._reload_scheduler_for_task(task, db) + return task except Exception as e: diff --git a/frontend/src/api/index.js b/frontend/src/api/index.js index d766773..5d09624 100644 --- a/frontend/src/api/index.js +++ b/frontend/src/api/index.js @@ -89,11 +89,6 @@ export const taskAPI = { return client.get('/api/tasks', { params }); }, - // 创建任务 - createTask: taskData => { - return client.post('/api/tasks', taskData); - }, - // 获取任务详情 getTask: taskId => { return client.get(`/api/tasks/${taskId}`); diff --git a/frontend/src/stores/task.js b/frontend/src/stores/task.js index 15ce825..ecbefef 100644 --- a/frontend/src/stores/task.js +++ b/frontend/src/stores/task.js @@ -46,25 +46,6 @@ export const useTaskStore = defineStore('task', { } }, - // 创建新任务 - async createTask(taskData) { - this.loading = true; - this.error = null; - try { - const newTask = await api.task.createTask(taskData); - this.tasks.unshift(newTask); // 添加到列表开头 - return newTask; - } catch (error) { - // 解析后端错误信息 - let errorMsg = error.message || '创建任务失败'; - - this.error = errorMsg; - throw new Error(errorMsg); - } finally { - this.loading = false; - } - }, - // 更新任务 async updateTask(taskId, taskData) { this.loading = true; diff --git a/frontend/src/stores/template.js b/frontend/src/stores/template.js index 7a2e951..dac9e72 100644 --- a/frontend/src/stores/template.js +++ b/frontend/src/stores/template.js @@ -132,7 +132,7 @@ export const useTemplateStore = defineStore('template', { } }, - async createTaskFromTemplate(templateId, threadId, fieldValues, taskName = null) { + async createTaskFromTemplate(templateId, threadId, fieldValues, taskName = null, cronExpression = '0 20 * * *') { this.loading = true; this.error = null; try { @@ -141,6 +141,7 @@ export const useTemplateStore = defineStore('template', { thread_id: threadId, field_values: fieldValues, task_name: taskName, + cron_expression: cronExpression, }); return task; } catch (error) { diff --git a/frontend/src/views/TasksView.vue b/frontend/src/views/TasksView.vue index 352606b..4672a31 100644 --- a/frontend/src/views/TasksView.vue +++ b/frontend/src/views/TasksView.vue @@ -13,7 +13,7 @@ type="primary" size="large" class="shadow-md3-3" - @click="showCreateDialog = true" + @click="openCreateDialog" >