diff --git a/.gitignore b/.gitignore
index c09cea9..f07c258 100644
--- a/.gitignore
+++ b/.gitignore
@@ -54,6 +54,6 @@ coverage
.cache
.temp
.tmp
-
+.eslintcache
# OS files
Thumbs.db
diff --git a/.trae/rules/rule.md b/.trae/rules/rule.md
deleted file mode 100644
index 80d5c94..0000000
--- a/.trae/rules/rule.md
+++ /dev/null
@@ -1,12 +0,0 @@
----
-alwaysApply: false
-description: 任务完成前的检查
----
-
-1.每次完成任务之前必须跑一遍测试(比如说npm系的typecheck/lint)
-
-2.每次完成任务之前必须跑一遍格式化(比如说npm系的prettier)
-
-3.任务完成时详细输出你所做的事情和需要用户确认的问题(如有)
-
-4.每次规划任务必须按照步骤来,每个步骤结束后遵循第三条规定。
diff --git a/.trae/specs/migrate-to-tauri/checklist.md b/.trae/specs/migrate-to-tauri/checklist.md
deleted file mode 100644
index 3ab71e6..0000000
--- a/.trae/specs/migrate-to-tauri/checklist.md
+++ /dev/null
@@ -1,82 +0,0 @@
-# Checklist
-
-## Phase 1: 项目初始化
-- [x] Tauri 项目在 SecScore-Rust 目录下正确创建
-- [x] Cargo.toml 包含所有必要依赖
-- [x] tauri.conf.json 配置正确
-- [x] 前端代码成功复制并配置
-- [x] 开发服务器可以正常启动
-
-## Phase 2: 数据库层
-- [x] SQLite 数据库可以正常创建和连接
-- [x] PostgreSQL 数据库可以正常连接
-- [x] 数据库迁移可以正常执行
-- [x] 所有数据库表结构与原项目一致
-- [x] 数据库连接切换功能正常
-
-## Phase 3: 核心服务
-- [x] 设置服务:所有设置项可以正常读写
-- [x] 设置服务:设置变更可以正确通知前端
-- [x] 安全服务:密码加密解密正确
-- [x] 权限服务:权限检查逻辑正确
-- [x] 认证服务:登录登出功能正常
-- [x] 认证服务:密码设置和恢复功能正常
-- [x] 主题服务:内置主题正确加载
-- [x] 主题服务:自定义主题可以保存和删除
-- [x] 主题服务:主题切换可以正确通知前端
-- [x] 自动评分服务:规则可以正确添加和执行
-- [x] 日志服务:日志可以正确写入和查询
-- [x] 数据服务:数据导入导出功能正常
-
-## Phase 4: Tauri Commands
-- [x] 学生相关 Commands 返回数据格式与原项目一致
-- [x] 标签相关 Commands 返回数据格式与原项目一致
-- [x] 原因相关 Commands 返回数据格式与原项目一致
-- [x] 事件相关 Commands 返回数据格式与原项目一致
-- [x] 结算相关 Commands 返回数据格式与原项目一致
-- [x] 设置相关 Commands 返回数据格式与原项目一致
-- [x] 认证相关 Commands 返回数据格式与原项目一致
-- [x] 主题相关 Commands 返回数据格式与原项目一致
-- [x] 自动评分相关 Commands 返回数据格式与原项目一致
-- [x] 日志相关 Commands 返回数据格式与原项目一致
-- [x] 数据导入导出 Commands 返回数据格式与原项目一致
-- [x] 窗口相关 Commands 返回数据格式与原项目一致
-- [x] 数据库连接相关 Commands 返回数据格式与原项目一致
-- [x] 文件系统相关 Commands 返回数据格式与原项目一致
-- [x] HTTP 服务器相关 Commands 返回数据格式与原项目一致
-- [x] 应用相关 Commands 返回数据格式与原项目一致
-
-## Phase 5: 系统功能
-- [x] 系统托盘图标正确显示
-- [x] 托盘菜单功能正常
-- [x] 双击托盘可以显示主窗口
-- [x] URL 协议可以正确注册和处理
-- [x] 单实例运行检测正常
-
-## Phase 6: 前端适配
-- [x] 所有 window.api 调用已替换为 Tauri invoke
-- [x] 事件监听(theme_updated, settings_changed 等)正常工作
-- [x] 窗口控制按钮功能正常
-- [x] 窗口拖拽区域正常工作
-- [x] Electron 特定代码已移除
-
-## Phase 7: 构建与测试
-- [ ] 开发环境可以正常运行
-- [ ] 生产构建成功
-- [ ] Windows 安装包可以正常安装和运行
-- [ ] 所有 UI 功能与原项目完全一致
-- [ ] 学生管理功能正常
-- [ ] 积分操作功能正常
-- [ ] 排行榜功能正常
-- [ ] 结算功能正常
-- [ ] 原因管理功能正常
-- [ ] 标签管理功能正常
-- [ ] 设置功能正常
-- [ ] 认证功能正常
-- [ ] 主题切换功能正常
-- [ ] 自动评分功能正常
-- [ ] 数据导入导出功能正常
-- [ ] 日志查看功能正常
-- [ ] 窗口控制功能正常
-- [ ] 系统托盘功能正常
-- [ ] URL 协议功能正常
diff --git a/.trae/specs/migrate-to-tauri/spec.md b/.trae/specs/migrate-to-tauri/spec.md
deleted file mode 100644
index be9f822..0000000
--- a/.trae/specs/migrate-to-tauri/spec.md
+++ /dev/null
@@ -1,399 +0,0 @@
-# SecScore Electron 到 Tauri 迁移规格
-
-## Why
-将现有的 Electron 项目迁移到 Rust + Tauri 框架,以获得更小的打包体积、更好的性能和更低的资源消耗,同时保持所有现有功能和 UI 逻辑完全一致。
-
-## What Changes
-- **BREAKING**: 后端从 Node.js/TypeScript 迁移到 Rust
-- **BREAKING**: 框架从 Electron 迁移到 Tauri 2.x
-- 前端 React 代码保持不变,仅修改 IPC 通信层
-- 数据库层从 TypeORM + better-sqlite3 迁移到 Rust 的 SeaORM + Rusqlite
-- 所有后端服务用 Rust 重写,功能实现保持一致
-
-## Impact
-- Affected specs: 整个后端架构
-- Affected code:
- - `src/main/` - 全部重写为 Rust
- - `src/preload/` - 替换为 Tauri commands
- - `src/renderer/` - 仅修改 API 调用层
- - `src/shared/` - 部分逻辑迁移到 Rust
-
-## ADDED Requirements
-
-### Requirement: Tauri 项目初始化
-系统 SHALL 在 `SecScore-Rust` 目录下创建 Tauri 2.x 项目结构,包含:
-- Rust 后端 (src-tauri/)
-- React 前端 (复用现有前端代码)
-- Tauri 配置文件
-
-#### Scenario: 项目结构创建
-- **WHEN** 初始化 Tauri 项目
-- **THEN** 应创建标准 Tauri 2.x 项目结构
-- **AND** 前端使用现有的 React + Ant Design 技术栈
-
-### Requirement: 数据库层迁移
-系统 SHALL 使用 Rust 实现数据库功能,支持 SQLite 和 PostgreSQL:
-
-#### Scenario: SQLite 支持
-- **WHEN** 未配置 PostgreSQL 连接字符串
-- **THEN** 使用 SQLite 作为默认数据库
-- **AND** 数据库文件路径与原项目一致
-
-#### Scenario: PostgreSQL 支持
-- **WHEN** 配置了有效的 PostgreSQL 连接字符串
-- **THEN** 系统应能连接到 PostgreSQL 数据库
-- **AND** 支持连接测试和切换
-
-#### Scenario: 数据库迁移
-- **WHEN** 系统启动
-- **THEN** 自动运行数据库迁移
-- **AND** 表结构与原项目完全一致
-
-### Requirement: 学生管理功能
-系统 SHALL 提供完整的学生管理功能:
-
-#### Scenario: 查询学生列表
-- **WHEN** 调用查询学生接口
-- **THEN** 返回所有学生信息,包含 id、name、score、tags
-- **AND** 按 score DESC, name ASC 排序
-
-#### Scenario: 创建学生
-- **WHEN** 调用创建学生接口
-- **THEN** 创建新学生记录,初始分数为 0
-- **AND** 返回新创建的学生 ID
-
-#### Scenario: 更新学生
-- **WHEN** 调用更新学生接口
-- **THEN** 更新指定学生的信息
-- **AND** 自动更新 updated_at 时间戳
-
-#### Scenario: 删除学生
-- **WHEN** 调用删除学生接口
-- **THEN** 删除学生及其关联的积分记录
-- **AND** 使用事务确保数据一致性
-
-#### Scenario: 导入学生名单
-- **WHEN** 调用导入学生接口
-- **THEN** 批量创建学生记录
-- **AND** 跳过已存在的学生
-
-### Requirement: 积分事件管理
-系统 SHALL 提供完整的积分事件管理功能:
-
-#### Scenario: 创建积分事件
-- **WHEN** 调用创建积分事件接口
-- **THEN** 创建积分记录并更新学生分数
-- **AND** 使用事务确保数据一致性
-- **AND** 自动生成 UUID
-
-#### Scenario: 撤销积分事件
-- **WHEN** 调用撤销积分事件接口
-- **THEN** 撤销积分记录并回退学生分数
-- **AND** 已结算的记录不可撤销
-
-#### Scenario: 查询积分事件
-- **WHEN** 调用查询积分事件接口
-- **THEN** 返回指定条件的积分记录
-- **AND** 支持按学生、时间范围筛选
-
-#### Scenario: 查询排行榜
-- **WHEN** 调用查询排行榜接口
-- **THEN** 返回指定时间范围的排行榜数据
-- **AND** 支持 today、week、month 三种范围
-
-### Requirement: 原因管理功能
-系统 SHALL 提供完整的积分原因管理功能:
-
-#### Scenario: 查询原因列表
-- **WHEN** 调用查询原因接口
-- **THEN** 返回所有积分原因
-- **AND** 按 category ASC, content ASC 排序
-
-#### Scenario: 创建原因
-- **WHEN** 调用创建原因接口
-- **THEN** 创建新的积分原因
-- **AND** 默认 is_system 为 0
-
-#### Scenario: 更新原因
-- **WHEN** 调用更新原因接口
-- **THEN** 更新指定原因的信息
-
-#### Scenario: 删除原因
-- **WHEN** 调用删除原因接口
-- **THEN** 删除指定原因
-
-### Requirement: 结算功能
-系统 SHALL 提供完整的结算功能:
-
-#### Scenario: 查询结算列表
-- **WHEN** 调用查询结算接口
-- **THEN** 返回所有结算记录
-- **AND** 包含每个结算的事件数量
-
-#### Scenario: 创建结算
-- **WHEN** 调用创建结算接口
-- **THEN** 将所有未结算事件标记为已结算
-- **AND** 重置所有学生分数为 0
-- **AND** 使用事务确保数据一致性
-
-#### Scenario: 查询结算排行榜
-- **WHEN** 调用查询结算排行榜接口
-- **THEN** 返回指定结算的排行榜数据
-
-### Requirement: 标签管理功能
-系统 SHALL 提供完整的学生标签管理功能:
-
-#### Scenario: 查询所有标签
-- **WHEN** 调用查询标签接口
-- **THEN** 返回所有标签列表
-
-#### Scenario: 创建标签
-- **WHEN** 调用创建标签接口
-- **THEN** 创建新标签或返回已存在的标签
-
-#### Scenario: 删除标签
-- **WHEN** 调用删除标签接口
-- **THEN** 删除指定标签及其关联
-
-#### Scenario: 更新学生标签
-- **WHEN** 调用更新学生标签接口
-- **THEN** 更新指定学生的标签关联
-
-### Requirement: 设置管理功能
-系统 SHALL 提供完整的设置管理功能:
-
-#### Scenario: 获取所有设置
-- **WHEN** 调用获取所有设置接口
-- **THEN** 返回所有设置项
-
-#### Scenario: 获取单个设置
-- **WHEN** 调用获取设置接口
-- **THEN** 返回指定设置项的值
-
-#### Scenario: 设置值
-- **WHEN** 调用设置值接口
-- **THEN** 更新指定设置项
-- **AND** 通知所有窗口设置变更
-
-#### Scenario: 设置项定义
-- **AND** 支持以下设置项:
- - is_wizard_completed: boolean
- - log_level: string (debug/info/warn/error)
- - window_zoom: number
- - themes_custom: json array
- - auto_score_enabled: boolean
- - auto_score_rules: json array
- - current_theme_id: string
- - pg_connection_string: string
- - pg_connection_status: json object
-
-### Requirement: 认证与安全功能
-系统 SHALL 提供完整的认证与安全功能:
-
-#### Scenario: 获取认证状态
-- **WHEN** 调用获取认证状态接口
-- **THEN** 返回当前权限级别和密码设置状态
-
-#### Scenario: 登录
-- **WHEN** 调用登录接口
-- **THEN** 验证密码并设置权限级别
-- **AND** 支持 admin 和 points 两种密码
-
-#### Scenario: 登出
-- **WHEN** 调用登出接口
-- **THEN** 重置权限级别为默认值
-
-#### Scenario: 设置密码
-- **WHEN** 调用设置密码接口
-- **THEN** 加密存储密码
-- **AND** 自动生成恢复字符串
-
-#### Scenario: 恢复密码
-- **WHEN** 调用恢复密码接口
-- **THEN** 使用恢复字符串重置所有密码
-
-#### Scenario: 加密实现
-- **WHEN** 存储敏感数据
-- **THEN** 使用 AES-256-CBC 加密
-- **AND** 使用应用数据目录派生的密钥
-
-### Requirement: 主题管理功能
-系统 SHALL 提供完整的主题管理功能:
-
-#### Scenario: 获取主题列表
-- **WHEN** 调用获取主题列表接口
-- **THEN** 返回内置主题和自定义主题
-
-#### Scenario: 获取当前主题
-- **WHEN** 调用获取当前主题接口
-- **THEN** 返回当前激活的主题配置
-
-#### Scenario: 设置主题
-- **WHEN** 调用设置主题接口
-- **THEN** 切换到指定主题
-- **AND** 通知所有窗口主题变更
-
-#### Scenario: 保存自定义主题
-- **WHEN** 调用保存主题接口
-- **THEN** 保存自定义主题配置
-- **AND** 不允许覆盖内置主题
-
-#### Scenario: 删除自定义主题
-- **WHEN** 调用删除主题接口
-- **THEN** 删除指定的自定义主题
-- **AND** 不允许删除内置主题
-
-### Requirement: 自动评分功能
-系统 SHALL 提供完整的自动评分功能:
-
-#### Scenario: 规则管理
-- **WHEN** 管理自动评分规则
-- **THEN** 支持添加、更新、删除、排序规则
-
-#### Scenario: 规则执行
-- **WHEN** 规则触发条件满足
-- **THEN** 自动执行规则动作
-- **AND** 记录执行时间
-
-#### Scenario: 触发器类型
-- **AND** 支持以下触发器:
- - interval_time_passed: 定时触发
- - student_has_tag: 学生标签触发
-
-#### Scenario: 动作类型
-- **AND** 支持以下动作:
- - add_score: 添加积分
- - add_tag: 添加标签
-
-### Requirement: 数据导入导出功能
-系统 SHALL 提供完整的数据导入导出功能:
-
-#### Scenario: 导出数据
-- **WHEN** 调用导出数据接口
-- **THEN** 导出所有数据为 JSON 格式
-
-#### Scenario: 导入数据
-- **WHEN** 调用导入数据接口
-- **THEN** 从 JSON 导入数据
-- **AND** 重新加载设置
-
-### Requirement: 日志管理功能
-系统 SHALL 提供完整的日志管理功能:
-
-#### Scenario: 查询日志
-- **WHEN** 调用查询日志接口
-- **THEN** 返回最近的日志记录
-
-#### Scenario: 清除日志
-- **WHEN** 调用清除日志接口
-- **THEN** 删除所有日志文件
-
-#### Scenario: 设置日志级别
-- **WHEN** 调用设置日志级别接口
-- **THEN** 更新日志级别
-
-#### Scenario: 日志文件
-- **AND** 日志文件按日期滚动
-- **AND** 保留最近 30 天的日志
-
-### Requirement: 窗口管理功能
-系统 SHALL 提供完整的窗口管理功能:
-
-#### Scenario: 窗口控制
-- **WHEN** 调用窗口控制接口
-- **THEN** 支持最小化、最大化、关闭操作
-
-#### Scenario: 窗口状态
-- **WHEN** 查询窗口状态
-- **THEN** 返回窗口最大化状态
-
-#### Scenario: 窗口缩放
-- **WHEN** 设置窗口缩放
-- **THEN** 应用缩放比例到所有窗口
-
-#### Scenario: 窗口导航
-- **WHEN** 调用导航接口
-- **THEN** 在窗口中导航到指定路由
-
-### Requirement: 系统托盘功能
-系统 SHALL 提供系统托盘功能:
-
-#### Scenario: 托盘初始化
-- **WHEN** 应用启动
-- **THEN** 创建系统托盘图标
-
-#### Scenario: 托盘菜单
-- **WHEN** 右键点击托盘图标
-- **THEN** 显示菜单:显示主窗口、重启应用、关闭应用
-
-#### Scenario: 双击托盘
-- **WHEN** 双击托盘图标
-- **THEN** 显示主窗口
-
-### Requirement: URL 协议功能
-系统 SHALL 提供自定义 URL 协议功能:
-
-#### Scenario: 协议注册
-- **WHEN** 应用启动(非开发模式)
-- **THEN** 注册 secscore:// 协议
-
-#### Scenario: 协议处理
-- **WHEN** 收到协议 URL
-- **THEN** 解析并导航到对应路由
-- **AND** 支持路由:home、students、score、leaderboard、settlements、reasons、settings
-
-### Requirement: 单实例运行
-系统 SHALL 确保应用单实例运行:
-
-#### Scenario: 单实例检查
-- **WHEN** 启动第二个实例
-- **THEN** 激活已有实例并退出
-
-### Requirement: 前端 API 适配
-系统 SHALL 保持前端 API 调用接口不变:
-
-#### Scenario: API 兼容
-- **WHEN** 前端调用 window.api
-- **THEN** 返回与原项目相同格式的响应
-- **AND** 所有接口签名保持一致
-
-### Requirement: 文件系统功能
-系统 SHALL 提供配置文件管理功能:
-
-#### Scenario: 获取配置目录结构
-- **WHEN** 调用获取配置目录结构接口
-- **THEN** 返回 automatic 和 script 目录路径
-
-#### Scenario: 读写 JSON 文件
-- **WHEN** 调用读写 JSON 接口
-- **THEN** 在指定目录读写 JSON 文件
-
-#### Scenario: 读写文本文件
-- **WHEN** 调用读写文本接口
-- **THEN** 在指定目录读写文本文件
-
-#### Scenario: 文件列表
-- **WHEN** 调用文件列表接口
-- **THEN** 返回指定目录的文件列表
-
-### Requirement: HTTP 服务器功能
-系统 SHALL 提供可选的 HTTP API 服务:
-
-#### Scenario: 启动服务器
-- **WHEN** 调用启动服务器接口
-- **THEN** 启动 HTTP 服务器
-- **AND** 支持自定义端口和 CORS 配置
-
-#### Scenario: 停止服务器
-- **WHEN** 调用停止服务器接口
-- **THEN** 停止 HTTP 服务器
-
-#### Scenario: 服务器状态
-- **WHEN** 调用服务器状态接口
-- **THEN** 返回服务器运行状态
-
-## MODIFIED Requirements
-无修改的需求,所有功能保持原有实现逻辑。
-
-## REMOVED Requirements
-无移除的需求。
diff --git a/.trae/specs/migrate-to-tauri/tasks.md b/.trae/specs/migrate-to-tauri/tasks.md
deleted file mode 100644
index 58f671d..0000000
--- a/.trae/specs/migrate-to-tauri/tasks.md
+++ /dev/null
@@ -1,244 +0,0 @@
-# Tasks
-
-## Phase 1: 项目初始化
-
-- [x] Task 1: 创建 Tauri 项目结构
- - [x] SubTask 1.1: 在 SecScore-Rust 目录初始化 Tauri 2.x 项目
- - [x] SubTask 1.2: 配置 Cargo.toml 依赖(tauri, serde, tokio, sqlx, sea-orm 等)
- - [x] SubTask 1.3: 配置 tauri.conf.json 基本设置
- - [x] SubTask 1.4: 复制前端代码到新项目
- - [x] SubTask 1.5: 配置前端构建脚本
-
-## Phase 2: 数据库层实现
-
-- [x] Task 2: 实现数据库实体和迁移
- - [x] SubTask 2.1: 定义数据库实体结构(Student, Reason, ScoreEvent, Settlement, Setting, Tag, StudentTag)
- - [x] SubTask 2.2: 实现 SQLite 数据库连接
- - [x] SubTask 2.3: 实现 PostgreSQL 数据库连接
- - [x] SubTask 2.4: 实现数据库迁移逻辑
- - [x] SubTask 2.5: 实现连接切换功能
-
-- [x] Task 3: 实现数据库仓储层
- - [x] SubTask 3.1: 实现 StudentRepository(查询、创建、更新、删除、导入)
- - [x] SubTask 3.2: 实现 EventRepository(查询、创建、删除、排行榜)
- - [x] SubTask 3.3: 实现 ReasonRepository(查询、创建、更新、删除)
- - [x] SubTask 3.4: 实现 SettlementRepository(查询、创建、排行榜)
- - [x] SubTask 3.5: 实现 TagRepository(查询、创建、删除、学生标签关联)
-
-## Phase 3: 核心服务实现
-
-- [x] Task 4: 实现设置服务
- - [x] SubTask 4.1: 定义设置项结构和默认值
- - [x] SubTask 4.2: 实现设置缓存机制
- - [x] SubTask 4.3: 实现设置的增删改查
- - [x] SubTask 4.4: 实现设置变更通知
-
-- [x] Task 5: 实现安全服务
- - [x] SubTask 5.1: 实现 AES-256-CBC 加密解密
- - [x] SubTask 5.2: 实现密码验证
- - [x] SubTask 5.3: 实现恢复字符串生成
-
-- [x] Task 6: 实现权限服务
- - [x] SubTask 6.1: 定义权限级别(admin, points, view)
- - [x] SubTask 6.2: 实现权限检查逻辑
- - [x] SubTask 6.3: 实现权限状态管理
-
-- [x] Task 7: 实现认证服务
- - [x] SubTask 7.1: 实现登录/登出功能
- - [x] SubTask 7.2: 实现密码设置功能
- - [x] SubTask 7.3: 实现恢复功能
-
-- [x] Task 8: 实现主题服务
- - [x] SubTask 8.1: 定义内置主题
- - [x] SubTask 8.2: 实现主题管理功能
- - [x] SubTask 8.3: 实现主题变更通知
-
-- [x] Task 9: 实现自动评分服务
- - [x] SubTask 9.1: 定义规则结构
- - [x] SubTask 9.2: 实现规则引擎
- - [x] SubTask 9.3: 实现触发器逻辑(定时、标签)
- - [x] SubTask 9.4: 实现动作执行(加分、加标签)
- - [x] SubTask 9.5: 实现规则调度
-
-- [x] Task 10: 实现日志服务
- - [x] SubTask 10.1: 配置日志框架
- - [x] SubTask 10.2: 实现日志文件滚动
- - [x] SubTask 10.3: 实现日志查询和清除
-
-- [x] Task 11: 实现数据服务
- - [x] SubTask 11.1: 实现数据导出为 JSON
- - [x] SubTask 11.2: 实现数据导入
-
-## Phase 4: Tauri Commands 实现
-
-- [x] Task 12: 实现学生相关 Commands
- - [x] SubTask 12.1: db_student_query
- - [x] SubTask 12.2: db_student_create
- - [x] SubTask 12.3: db_student_update
- - [x] SubTask 12.4: db_student_delete
- - [x] SubTask 12.5: db_student_import_from_xlsx
-
-- [x] Task 13: 实现标签相关 Commands
- - [x] SubTask 13.1: tags_get_all
- - [x] SubTask 13.2: tags_get_by_student
- - [x] SubTask 13.3: tags_create
- - [x] SubTask 13.4: tags_delete
- - [x] SubTask 13.5: tags_update_student_tags
-
-- [x] Task 14: 实现原因相关 Commands
- - [x] SubTask 14.1: db_reason_query
- - [x] SubTask 14.2: db_reason_create
- - [x] SubTask 14.3: db_reason_update
- - [x] SubTask 14.4: db_reason_delete
-
-- [x] Task 15: 实现事件相关 Commands
- - [x] SubTask 15.1: db_event_query
- - [x] SubTask 15.2: db_event_create
- - [x] SubTask 15.3: db_event_delete
- - [x] SubTask 15.4: db_event_query_by_student
- - [x] SubTask 15.5: db_leaderboard_query
-
-- [x] Task 16: 实现结算相关 Commands
- - [x] SubTask 16.1: db_settlement_query
- - [x] SubTask 16.2: db_settlement_create
- - [x] SubTask 16.3: db_settlement_leaderboard
-
-- [x] Task 17: 实现设置相关 Commands
- - [x] SubTask 17.1: settings_get_all
- - [x] SubTask 17.2: settings_get
- - [x] SubTask 17.3: settings_set
- - [x] SubTask 17.4: settings_changed 事件
-
-- [x] Task 18: 实现认证相关 Commands
- - [x] SubTask 18.1: auth_get_status
- - [x] SubTask 18.2: auth_login
- - [x] SubTask 18.3: auth_logout
- - [x] SubTask 18.4: auth_set_passwords
- - [x] SubTask 18.5: auth_generate_recovery
- - [x] SubTask 18.6: auth_reset_by_recovery
- - [x] SubTask 18.7: auth_clear_all
-
-- [x] Task 19: 实现主题相关 Commands
- - [x] SubTask 19.1: theme_list
- - [x] SubTask 19.2: theme_current
- - [x] SubTask 19.3: theme_set
- - [x] SubTask 19.4: theme_save
- - [x] SubTask 19.5: theme_delete
- - [x] SubTask 19.6: theme_updated 事件
-
-- [x] Task 20: 实现自动评分相关 Commands
- - [x] SubTask 20.1: auto_score_get_rules
- - [x] SubTask 20.2: auto_score_add_rule
- - [x] SubTask 20.3: auto_score_update_rule
- - [x] SubTask 20.4: auto_score_delete_rule
- - [x] SubTask 20.5: auto_score_toggle_rule
- - [x] SubTask 20.6: auto_score_get_status
- - [x] SubTask 20.7: auto_score_sort_rules
-
-- [x] Task 21: 实现日志相关 Commands
- - [x] SubTask 21.1: log_query
- - [x] SubTask 21.2: log_clear
- - [x] SubTask 21.3: log_set_level
- - [x] SubTask 21.4: log_write
-
-- [x] Task 22: 实现数据导入导出 Commands
- - [x] SubTask 22.1: data_export_json
- - [x] SubTask 22.2: data_import_json
-
-- [x] Task 23: 实现窗口相关 Commands
- - [x] SubTask 23.1: window_minimize
- - [x] SubTask 23.2: window_maximize
- - [x] SubTask 23.3: window_close
- - [x] SubTask 23.4: window_is_maximized
- - [x] SubTask 23.5: window_toggle_devtools
- - [x] SubTask 23.6: window_resize
- - [x] SubTask 23.7: window_maximized_changed 事件
- - [x] SubTask 23.8: app_navigate 事件
-
-- [x] Task 24: 实现数据库连接相关 Commands
- - [x] SubTask 24.1: db_test_connection
- - [x] SubTask 24.2: db_switch_connection
- - [x] SubTask 24.3: db_get_status
- - [x] SubTask 24.4: db_sync
-
-- [x] Task 25: 实现文件系统相关 Commands
- - [x] SubTask 25.1: fs_get_config_structure
- - [x] SubTask 25.2: fs_read_json
- - [x] SubTask 25.3: fs_write_json
- - [x] SubTask 25.4: fs_read_text
- - [x] SubTask 25.5: fs_write_text
- - [x] SubTask 25.6: fs_delete_file
- - [x] SubTask 25.7: fs_list_files
- - [x] SubTask 25.8: fs_file_exists
-
-- [x] Task 26: 实现 HTTP 服务器相关 Commands
- - [x] SubTask 26.1: http_server_start
- - [x] SubTask 26.2: http_server_stop
- - [x] SubTask 26.3: http_server_status
-
-- [x] Task 27: 实现应用相关 Commands
- - [x] SubTask 27.1: app_register_url_protocol
-
-## Phase 5: 系统功能实现
-
-- [x] Task 28: 实现系统托盘
- - [x] SubTask 28.1: 创建托盘图标
- - [x] SubTask 28.2: 实现托盘菜单
- - [x] SubTask 28.3: 实现双击显示窗口
-
-- [x] Task 29: 实现 URL 协议处理
- - [x] SubTask 29.1: 注册 secscore:// 协议
- - [x] SubTask 29.2: 处理协议 URL 并导航
-
-- [x] Task 30: 实现单实例运行
- - [x] SubTask 30.1: 检测已有实例
- - [x] SubTask 30.2: 激活已有实例
-
-## Phase 6: 前端适配
-
-- [x] Task 31: 修改前端 API 调用层
- - [x] SubTask 31.1: 创建 Tauri invoke 封装
- - [x] SubTask 31.2: 修改所有 window.api 调用为 Tauri invoke
- - [x] SubTask 31.3: 实现事件监听(theme_updated, settings_changed 等)
- - [x] SubTask 31.4: 移除 Electron 特定代码
-
-- [x] Task 32: 适配窗口控制
- - [x] SubTask 32.1: 修改标题栏组件使用 Tauri API
- - [x] SubTask 32.2: 实现窗口拖拽区域
-
-## Phase 7: 构建与测试
-
-- [ ] Task 33: 配置构建脚本
- - [ ] SubTask 33.1: 配置开发环境脚本
- - [ ] SubTask 33.2: 配置生产构建脚本
- - [ ] SubTask 33.3: 配置 Windows 打包
-
-- [ ] Task 34: 功能验证
- - [ ] SubTask 34.1: 验证所有数据库操作
- - [ ] SubTask 34.2: 验证所有 UI 功能
- - [ ] SubTask 34.3: 验证主题切换
- - [ ] SubTask 34.4: 验证认证流程
- - [ ] SubTask 34.5: 验证自动评分
- - [ ] SubTask 34.6: 验证数据导入导出
- - [ ] SubTask 34.7: 验证系统托盘
- - [ ] SubTask 34.8: 验证 URL 协议
-
-# Task Dependencies
-- [Task 2] depends on [Task 1]
-- [Task 3] depends on [Task 2]
-- [Task 4] depends on [Task 2]
-- [Task 5] depends on [Task 4]
-- [Task 6] depends on [Task 5]
-- [Task 7] depends on [Task 5, Task 6]
-- [Task 8] depends on [Task 4]
-- [Task 9] depends on [Task 3]
-- [Task 10] depends on [Task 1]
-- [Task 11] depends on [Task 3]
-- [Task 12-27] depend on [Task 3-11]
-- [Task 28] depends on [Task 1]
-- [Task 29] depends on [Task 1]
-- [Task 30] depends on [Task 1]
-- [Task 31] depends on [Task 12-27]
-- [Task 32] depends on [Task 31]
-- [Task 33] depends on [Task 32]
-- [Task 34] depends on [Task 33]
diff --git a/agents.md b/agents.md
deleted file mode 100644
index 4791322..0000000
--- a/agents.md
+++ /dev/null
@@ -1,15 +0,0 @@
-# Agent Collaboration Rules
-
-## 分支定位
-- 当前分支(`SecScore-tauri`)主要用于推进 Tauri 版本开发与维护。
-- 不以 Electron 版本作为该分支的实现目标。
-
-## 提交规范
-- 每次代码改动都必须使用中文 `commit message`。
-- 完成提交后必须推送到远程仓库对应分支(`git push`)。
-
-## 执行流程
-1. 完成代码修改。
-2. 自检并确保可构建/可运行。
-3. 使用中文提交信息执行 `git commit`。
-4. 立即执行 `git push` 推送远程。
diff --git a/scripts/ios-sim.js b/scripts/ios-sim.js
index 5099603..d611147 100755
--- a/scripts/ios-sim.js
+++ b/scripts/ios-sim.js
@@ -1,127 +1,127 @@
#!/usr/bin/env node
-const { execSync, spawn } = require('node:child_process');
-const net = require('node:net');
-const readline = require('node:readline');
+const { execSync, spawn } = require("node:child_process")
+const net = require("node:net")
+const readline = require("node:readline")
function getAvailableDevices() {
- const raw = execSync('xcrun simctl list devices available -j', {
- encoding: 'utf8',
- stdio: ['ignore', 'pipe', 'pipe'],
- });
- const parsed = JSON.parse(raw);
- const devicesByRuntime = parsed.devices || {};
- const all = [];
+ const raw = execSync("xcrun simctl list devices available -j", {
+ encoding: "utf8",
+ stdio: ["ignore", "pipe", "pipe"],
+ })
+ const parsed = JSON.parse(raw)
+ const devicesByRuntime = parsed.devices || {}
+ const all = []
for (const [runtime, devices] of Object.entries(devicesByRuntime)) {
for (const device of devices) {
- if (!device.isAvailable) continue;
- if (device.name.includes('iPad') || device.name.includes('iPhone')) {
+ if (!device.isAvailable) continue
+ if (device.name.includes("iPad") || device.name.includes("iPhone")) {
all.push({
name: device.name,
udid: device.udid,
state: device.state,
runtime,
- });
+ })
}
}
}
- all.sort((a, b) => a.name.localeCompare(b.name) || a.runtime.localeCompare(b.runtime));
- return all;
+ all.sort((a, b) => a.name.localeCompare(b.name) || a.runtime.localeCompare(b.runtime))
+ return all
}
function promptChoice(max) {
const rl = readline.createInterface({
input: process.stdin,
output: process.stdout,
- });
+ })
return new Promise((resolve, reject) => {
- rl.question('请输入要使用的机型编号: ', (answer) => {
- rl.close();
- const idx = Number(answer.trim());
+ rl.question("请输入要使用的机型编号: ", (answer) => {
+ rl.close()
+ const idx = Number(answer.trim())
if (!Number.isInteger(idx) || idx < 1 || idx > max) {
- reject(new Error(`无效编号: ${answer}`));
- return;
+ reject(new Error(`无效编号: ${answer}`))
+ return
}
- resolve(idx - 1);
- });
- });
+ resolve(idx - 1)
+ })
+ })
}
-function isPortListening(port, host = '127.0.0.1') {
+function isPortListening(port, host = "127.0.0.1") {
return new Promise((resolve) => {
- const socket = new net.Socket();
- let settled = false;
+ const socket = new net.Socket()
+ let settled = false
const finalize = (result) => {
- if (settled) return;
- settled = true;
- socket.destroy();
- resolve(result);
- };
+ if (settled) return
+ settled = true
+ socket.destroy()
+ resolve(result)
+ }
- socket.setTimeout(500);
- socket.once('connect', () => finalize(true));
- socket.once('timeout', () => finalize(false));
- socket.once('error', () => finalize(false));
- socket.connect(port, host);
- });
+ socket.setTimeout(500)
+ socket.once("connect", () => finalize(true))
+ socket.once("timeout", () => finalize(false))
+ socket.once("error", () => finalize(false))
+ socket.connect(port, host)
+ })
}
async function hasListeningDevServer(port) {
const checks = await Promise.all([
- isPortListening(port, '127.0.0.1'),
- isPortListening(port, '::1'),
- isPortListening(port, 'localhost'),
- ]);
- return checks.some(Boolean);
+ isPortListening(port, "127.0.0.1"),
+ isPortListening(port, "::1"),
+ isPortListening(port, "localhost"),
+ ])
+ return checks.some(Boolean)
}
async function main() {
- const devices = getAvailableDevices();
+ const devices = getAvailableDevices()
if (devices.length === 0) {
- throw new Error('未找到可用 iOS 模拟器,请先在 Xcode 安装模拟器运行时。');
+ throw new Error("未找到可用 iOS 模拟器,请先在 Xcode 安装模拟器运行时。")
}
- console.log('可用 iOS 模拟器:');
+ console.log("可用 iOS 模拟器:")
devices.forEach((d, i) => {
- const runtime = d.runtime.replace('com.apple.CoreSimulator.SimRuntime.', '');
- console.log(`${i + 1}. ${d.name} | ${runtime} | ${d.state} | ${d.udid}`);
- });
+ const runtime = d.runtime.replace("com.apple.CoreSimulator.SimRuntime.", "")
+ console.log(`${i + 1}. ${d.name} | ${runtime} | ${d.state} | ${d.udid}`)
+ })
- const pickedIndex = await promptChoice(devices.length);
- const picked = devices[pickedIndex];
- console.log(`\n已选择: ${picked.name} (${picked.runtime})`);
+ const pickedIndex = await promptChoice(devices.length)
+ const picked = devices[pickedIndex]
+ console.log(`\n已选择: ${picked.name} (${picked.runtime})`)
- const args = ['tauri', 'ios', 'dev', picked.name];
- const tauriDevPort = Number(process.env.TAURI_DEV_PORT || 1420);
- const hasExistingDevServer = await hasListeningDevServer(tauriDevPort);
+ const args = ["tauri", "ios", "dev", picked.name]
+ const tauriDevPort = Number(process.env.TAURI_DEV_PORT || 1420)
+ const hasExistingDevServer = await hasListeningDevServer(tauriDevPort)
if (hasExistingDevServer) {
- console.log(`检测到 ${tauriDevPort} 端口已有 dev server,复用现有服务并跳过 beforeDevCommand。`);
+ console.log(`检测到 ${tauriDevPort} 端口已有 dev server,复用现有服务并跳过 beforeDevCommand。`)
args.push(
- '--config',
+ "--config",
JSON.stringify({
build: {
beforeDevCommand: `echo using-existing-dev-server-on-${tauriDevPort}`,
},
})
- );
+ )
}
- const child = spawn('pnpm', args, {
- stdio: 'inherit',
- shell: process.platform === 'win32',
- });
+ const child = spawn("pnpm", args, {
+ stdio: "inherit",
+ shell: process.platform === "win32",
+ })
- child.on('exit', (code) => {
- process.exit(code ?? 1);
- });
+ child.on("exit", (code) => {
+ process.exit(code ?? 1)
+ })
}
main().catch((err) => {
- console.error(`执行失败: ${err.message}`);
- process.exit(1);
-});
+ console.error(`执行失败: ${err.message}`)
+ process.exit(1)
+})
diff --git a/scripts/tauri-dev.cjs b/scripts/tauri-dev.cjs
index d1ad7f0..608c935 100644
--- a/scripts/tauri-dev.cjs
+++ b/scripts/tauri-dev.cjs
@@ -1,21 +1,24 @@
-const net = require('node:net')
-const { spawn } = require('node:child_process')
+const net = require("node:net")
+const { spawn } = require("node:child_process")
+const { writeFileSync, unlinkSync, existsSync } = require("node:fs")
+const { join } = require("node:path")
+const os = require("node:os")
-const START_PORT = Number.parseInt(process.env.TAURI_DEV_PORT_START || '1420', 10)
+const START_PORT = Number.parseInt(process.env.TAURI_DEV_PORT_START || "1420", 10)
function canUsePort(port) {
return new Promise((resolve) => {
const server = net.createServer()
- server.once('error', () => {
+ server.once("error", () => {
resolve(false)
})
- server.once('listening', () => {
+ server.once("listening", () => {
server.close(() => resolve(true))
})
- server.listen(port, '127.0.0.1')
+ server.listen(port, "127.0.0.1")
})
}
@@ -38,27 +41,42 @@ async function main() {
console.log(`[tauri:dev] 使用端口 ${port}`)
- const extraArgs = process.argv.slice(2)
- const pnpmCmd = process.platform === 'win32' ? 'pnpm.cmd' : 'pnpm'
- const child = spawn(
- pnpmCmd,
- ['tauri', 'dev', '-c', JSON.stringify(overrideConfig), ...extraArgs],
- {
- stdio: 'inherit',
- env: process.env,
- }
- )
+ const tempDir = os.tmpdir()
+ const tempConfigPath = join(tempDir, `tauri-config-${Date.now()}.json`)
- child.on('exit', (code, signal) => {
- if (signal) {
- process.kill(process.pid, signal)
- return
+ try {
+ writeFileSync(tempConfigPath, JSON.stringify(overrideConfig), "utf-8")
+
+ const extraArgs = process.argv.slice(2)
+ const cmd = "pnpm"
+ const args = ["tauri", "dev", "--config", tempConfigPath, ...extraArgs]
+
+ const child = spawn(cmd, args, {
+ stdio: "inherit",
+ env: process.env,
+ shell: true,
+ windowsHide: false,
+ })
+
+ child.on("exit", (code, signal) => {
+ if (existsSync(tempConfigPath)) {
+ unlinkSync(tempConfigPath)
+ }
+ if (signal) {
+ process.kill(process.pid, signal)
+ return
+ }
+ process.exit(code ?? 1)
+ })
+ } catch (error) {
+ if (existsSync(tempConfigPath)) {
+ unlinkSync(tempConfigPath)
}
- process.exit(code ?? 1)
- })
+ throw error
+ }
}
main().catch((error) => {
- console.error('[tauri:dev] 启动失败:', error)
+ console.error("[tauri:dev] 启动失败:", error)
process.exit(1)
})
diff --git a/src-tauri/tauri.conf.json b/src-tauri/tauri.conf.json
index 3bade52..323ba90 100644
--- a/src-tauri/tauri.conf.json
+++ b/src-tauri/tauri.conf.json
@@ -42,10 +42,7 @@
"bundle": {
"active": true,
"targets": "all",
- "icon": [
- "icons/icon.png",
- "icons/icon.ico"
- ],
+ "icon": ["icons/icon.png", "icons/icon.ico"],
"windows": {
"certificateThumbprint": null,
"digestAlgorithm": "sha256",
diff --git a/src/assets/main.css b/src/assets/main.css
index 9fc0c22..37c3c53 100644
--- a/src/assets/main.css
+++ b/src/assets/main.css
@@ -7,11 +7,10 @@
--ss-safe-area-bottom: env(safe-area-inset-bottom, 0px);
--ss-safe-area-left: env(safe-area-inset-left, 0px);
--ss-font-family:
- "PingFang SC", "PingFangTC-Regular", "Hiragino Sans GB", "Hiragino Sans",
- "STHeiti", "Heiti SC", "Noto Sans CJK SC", "Noto Sans SC", "Source Han Sans SC",
- "Microsoft YaHei UI", "Microsoft YaHei", "微软雅黑", -apple-system, BlinkMacSystemFont,
- "Segoe UI", Roboto, "Helvetica Neue", Arial, "Apple Color Emoji", "Segoe UI Emoji",
- sans-serif;
+ "PingFang SC", "PingFangTC-Regular", "Hiragino Sans GB", "Hiragino Sans", "STHeiti", "Heiti SC",
+ "Noto Sans CJK SC", "Noto Sans SC", "Source Han Sans SC", "Microsoft YaHei UI",
+ "Microsoft YaHei", "微软雅黑", -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto,
+ "Helvetica Neue", Arial, "Apple Color Emoji", "Segoe UI Emoji", sans-serif;
}
@supports (height: 100dvh) {
diff --git a/src/components/BoardManager.tsx b/src/components/BoardManager.tsx
index 4538812..4a7eee9 100644
--- a/src/components/BoardManager.tsx
+++ b/src/components/BoardManager.tsx
@@ -211,11 +211,15 @@ const normalizeBoards = (input: unknown): BoardConfig[] => {
const normalizedLists = lists.length > 0 ? lists : [createDefaultList()]
const fallbackLayout = createLeafForList(normalizedLists[0].id)
- const layout = board?.layout && typeof board.layout === "object" ? (board.layout as LayoutNode) : fallbackLayout
+ const layout =
+ board?.layout && typeof board.layout === "object"
+ ? (board.layout as LayoutNode)
+ : fallbackLayout
return syncBoardLayout({
id: typeof board?.id === "string" && board.id.trim() ? board.id : makeId(),
- name: typeof board?.name === "string" && board.name.trim() ? board.name.trim() : "未命名看板",
+ name:
+ typeof board?.name === "string" && board.name.trim() ? board.name.trim() : "未命名看板",
lists: normalizedLists,
layout,
})
@@ -283,7 +287,9 @@ const toStudentCards = (rows: any[]): BoardStudentCardData[] => {
if (cards.every((item) => item.score === undefined)) return cards
- return cards.sort((a, b) => (b.score ?? Number.MIN_SAFE_INTEGER) - (a.score ?? Number.MIN_SAFE_INTEGER))
+ return cards.sort(
+ (a, b) => (b.score ?? Number.MIN_SAFE_INTEGER) - (a.score ?? Number.MIN_SAFE_INTEGER)
+ )
}
const getAvatarText = (name: string): string => {
@@ -312,7 +318,11 @@ const getAvatarColor = (name: string): string => {
return palette[hash % palette.length]
}
-const replaceLayoutNode = (node: LayoutNode, targetId: string, replacement: LayoutNode): LayoutNode => {
+const replaceLayoutNode = (
+ node: LayoutNode,
+ targetId: string,
+ replacement: LayoutNode
+): LayoutNode => {
if (node.id === targetId) return replacement
if (node.type === "leaf") return node
return {
@@ -551,8 +561,13 @@ ORDER BY reward_points DESC, score DESC`,
useEffect(() => {
const onDataUpdated = (e: Event) => {
const detail = (e as CustomEvent<{ category?: string }>).detail
- if (!detail?.category || detail.category === "all") runAllInBoard(activeBoard).catch(() => void 0)
- if (detail.category === "students" || detail.category === "events" || detail.category === "reasons") {
+ if (!detail?.category || detail.category === "all")
+ runAllInBoard(activeBoard).catch(() => void 0)
+ if (
+ detail.category === "students" ||
+ detail.category === "events" ||
+ detail.category === "reasons"
+ ) {
runAllInBoard(activeBoard).catch(() => void 0)
}
}
@@ -595,7 +610,9 @@ ORDER BY reward_points DESC, score DESC`,
const updateBoardName = (boardId: string, name: string) => {
mutateBoards((prev) =>
- prev.map((board) => (board.id === boardId ? { ...board, name: name || t("board.untitledBoard") } : board))
+ prev.map((board) =>
+ board.id === boardId ? { ...board, name: name || t("board.untitledBoard") } : board
+ )
)
}
@@ -727,7 +744,8 @@ ORDER BY reward_points DESC, score DESC`,
dataIndex: key,
key,
ellipsis: true,
- render: (value: any) => (value === null || value === undefined || value === "" ? "-" : String(value)),
+ render: (value: any) =>
+ value === null || value === undefined || value === "" ? "-" : String(value),
}))
: []
@@ -738,7 +756,11 @@ ORDER BY reward_points DESC, score DESC`,
dataSource={rows}
columns={columns}
loading={Boolean(runningIds[list.id])}
- locale={{ emptyText: