diff --git a/src/config.py b/src/config.py index 21b4024..d042d39 100644 --- a/src/config.py +++ b/src/config.py @@ -10,6 +10,7 @@ load_dotenv() # --- File Paths & Directories --- STATE_FILE = "xianyu_state.json" IMAGE_SAVE_DIR = "images" +CONFIG_FILE = "config.json" os.makedirs(IMAGE_SAVE_DIR, exist_ok=True) # 任务隔离的临时图片目录前缀 diff --git a/src/file_operator.py b/src/file_operator.py new file mode 100644 index 0000000..1f2c2d6 --- /dev/null +++ b/src/file_operator.py @@ -0,0 +1,46 @@ +import aiofiles +from pathlib import Path + + +class FileOperator: + def __init__(self, filepath: str): + self.filepath = filepath + + async def read(self) -> str | None: + """ + 读取 + """ + try: + async with aiofiles.open(self.filepath, 'r', encoding='utf-8') as f: + content_str = await f.read() + if content_str.strip(): + return content_str + else: + return None + except FileNotFoundError: + print(f"文件 {self.filepath} 不存在") + return None + except PermissionError: + print(f"错误:没有权限读取文件 {self.filepath}") + return None + except Exception as e: + print(f"读取文件 {self.filepath} 时发生错误: {e}") + return None + + async def write(self, content: str) -> bool: + """ + 写入 + """ + try: + Path(self.filepath).parent.mkdir(parents=True, exist_ok=True) + + async with aiofiles.open(self.filepath, 'w', encoding='utf-8') as f: + await f.write(content) + return True + + except PermissionError: + print(f"错误:没有权限写入文件 {self.filepath}") + return False + except Exception as e: + print(f"写入文件 {self.filepath} 时发生错误: {e}") + return False diff --git a/src/task.py b/src/task.py new file mode 100644 index 0000000..cc567ce --- /dev/null +++ b/src/task.py @@ -0,0 +1,95 @@ +import json + +from pydantic import BaseModel +from typing import Optional + +from src.config import CONFIG_FILE +from src.file_operator import FileOperator + + +class Task(BaseModel): + task_name: str + enabled: bool + keyword: str + description: str + max_pages: int + personal_only: bool + min_price: Optional[str] = None + max_price: Optional[str] = None + cron: Optional[str] = None + ai_prompt_base_file: str + ai_prompt_criteria_file: str + is_running: Optional[bool] = False + + +class TaskUpdate(BaseModel): + task_name: Optional[str] = None + enabled: Optional[bool] = None + keyword: Optional[str] = None + description: Optional[str] = None + max_pages: Optional[int] = None + personal_only: Optional[bool] = None + min_price: Optional[str] = None + max_price: Optional[str] = None + cron: Optional[str] = None + ai_prompt_base_file: Optional[str] = None + ai_prompt_criteria_file: Optional[str] = None + is_running: Optional[bool] = None + + +async def add_task(task: Task) -> bool: + config_file_op = FileOperator(CONFIG_FILE) + + config_data_str = await config_file_op.read() + config_data = json.loads(config_data_str) if config_data_str else [] + config_data.append(task) + + return await config_file_op.write(json.dumps(config_data, ensure_ascii=False, indent=2)) + + +async def update_task(task_id: int, task: Task) -> bool: + config_file_op = FileOperator(CONFIG_FILE) + + config_data_str = await config_file_op.read() + + if not config_data_str: + return False + + config_data = json.loads(config_data_str) + + if len(config_data) <= task_id: + return False + + config_data[task_id] = task + + return await config_file_op.write(json.dumps(config_data, ensure_ascii=False, indent=2)) + + +async def get_task(task_id: int) -> Task | None: + config_file_op = FileOperator(CONFIG_FILE) + config_data_str = await config_file_op.read() + + if not config_data_str: + return None + + config_data = json.loads(config_data_str) + if len(config_data) <= task_id: + return None + + return config_data[task_id] + + +async def remove_task(task_id: int) -> bool: + config_file_op = FileOperator(CONFIG_FILE) + config_data_str = await config_file_op.read() + if not config_data_str: + return True + + config_data = json.loads(config_data_str) + + if len(config_data) <= task_id: + return True + + config_data.pop(task_id) + + return await config_file_op.write(json.dumps(config_data, ensure_ascii=False, indent=2)) diff --git a/static/css/style.css b/static/css/style.css index 6472596..8524a6f 100644 --- a/static/css/style.css +++ b/static/css/style.css @@ -279,6 +279,21 @@ h2 { color: #ff4d4f; } +.criteria { + display: flex; + align-items: center; + justify-content: start; + gap: 10px; +} + +.refresh-criteria { + display: flex; + background: transparent; + border: none; + padding: 0; + cursor: pointer; +} + /* Toggle Switch */ .switch { position: relative; diff --git a/static/js/main.js b/static/js/main.js index 1b9d6b9..1143eb8 100644 --- a/static/js/main.js +++ b/static/js/main.js @@ -682,6 +682,8 @@ document.addEventListener('DOMContentLoaded', function() { return '
没有找到任何任务。请点击右上角“创建新任务”来添加一个。
'; } + const refreshBtn = '' + const tableHeader = `