Skip to content

Commit e4d9ee0

Browse files
committed
feat: 间隔时间加分可以按照多少天、月、分钟后加分
fix: 修复若干问题 下雨了好害怕我靠
1 parent a930e48 commit e4d9ee0

14 files changed

Lines changed: 508 additions & 84 deletions

File tree

src-tauri/src/commands/board.rs

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
use parking_lot::RwLock;
2-
use serde::Deserialize;
32
use sea_orm::{ConnectionTrait, DatabaseConnection, Statement};
3+
use serde::Deserialize;
44
use serde_json::{Map, Value as JsonValue};
55
use sqlx::{Column, Row, SqlitePool};
66
use std::collections::HashSet;
@@ -155,7 +155,10 @@ async fn get_board_configs_raw(conn: &DatabaseConnection) -> Result<Option<Strin
155155
}
156156
}
157157

158-
async fn upsert_board_configs_raw(conn: &DatabaseConnection, config_json: &str) -> Result<(), String> {
158+
async fn upsert_board_configs_raw(
159+
conn: &DatabaseConnection,
160+
config_json: &str,
161+
) -> Result<(), String> {
159162
let config_escaped = escape_sql(config_json);
160163
let now_escaped = escape_sql(&now_iso());
161164
let sql = format!(

src-tauri/src/commands/database.rs

Lines changed: 13 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1150,7 +1150,10 @@ pub async fn db_switch_connection(
11501150
let logger = state_guard.logger.read();
11511151
logger.log(
11521152
LogLevel::Info,
1153-
&format!("Database switch requested, connection_string length: {}", connection_string.len()),
1153+
&format!(
1154+
"Database switch requested, connection_string length: {}",
1155+
connection_string.len()
1156+
),
11541157
Some("database"),
11551158
None,
11561159
);
@@ -1225,17 +1228,15 @@ pub async fn db_switch_connection(
12251228
connection_string
12261229
};
12271230

1228-
let conn = create_sqlite_connection(&path)
1229-
.await
1230-
.map_err(|e| {
1231-
logger.log(
1232-
LogLevel::Error,
1233-
&format!("SQLite connection failed: {}", e),
1234-
Some("database"),
1235-
None,
1236-
);
1237-
e.to_string()
1238-
})?;
1231+
let conn = create_sqlite_connection(&path).await.map_err(|e| {
1232+
logger.log(
1233+
LogLevel::Error,
1234+
&format!("SQLite connection failed: {}", e),
1235+
Some("database"),
1236+
None,
1237+
);
1238+
e.to_string()
1239+
})?;
12391240
run_migration(&conn, DatabaseType::SQLite)
12401241
.await
12411242
.map_err(|e| {

src-tauri/src/commands/student.rs

Lines changed: 15 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -313,7 +313,9 @@ async fn post_banyou_action<T: DeserializeOwned>(
313313
params: serde_json::Value,
314314
) -> Result<T, String> {
315315
let timestamp = chrono::Utc::now().timestamp_millis();
316-
let url = format!("https://care.seewo.com/app/apis.json?action={action}&timestamp={timestamp}&isAjax=1");
316+
let url = format!(
317+
"https://care.seewo.com/app/apis.json?action={action}&timestamp={timestamp}&isAjax=1"
318+
);
317319
let payload = serde_json::json!({
318320
"action": action,
319321
"params": params,
@@ -371,7 +373,11 @@ async fn post_banyou_action<T: DeserializeOwned>(
371373
"body_preview": first_n_chars(&body, 500),
372374
})),
373375
);
374-
return Err(format!("班优接口请求失败({} HTTP {})", action, status.as_u16()));
376+
return Err(format!(
377+
"班优接口请求失败({} HTTP {})",
378+
action,
379+
status.as_u16()
380+
));
375381
}
376382

377383
let parsed: BanYouApiResponse<T> = serde_json::from_str(&body).map_err(|e| {
@@ -993,7 +999,9 @@ pub async fn student_fetch_banyou_classroom_detail(
993999
(Vec::new(), None)
9941000
};
9951001

996-
let final_team_plan_id = params.team_plan_id.or_else(|| team_plans.first().map(|p| p.team_plan_id));
1002+
let final_team_plan_id = params
1003+
.team_plan_id
1004+
.or_else(|| team_plans.first().map(|p| p.team_plan_id));
9971005

9981006
let group_data = if let Some(team_plan_id) = final_team_plan_id {
9991007
match post_banyou_action::<BanYouGroupFetchData>(
@@ -1032,7 +1040,10 @@ pub async fn student_fetch_banyou_classroom_detail(
10321040
let detail = BanYouClassroomDetailData {
10331041
medals,
10341042
students: students_data.students,
1035-
teams: group_data.as_ref().map(|d| d.teams.clone()).unwrap_or_default(),
1043+
teams: group_data
1044+
.as_ref()
1045+
.map(|d| d.teams.clone())
1046+
.unwrap_or_default(),
10361047
ungrouped_students: group_data
10371048
.as_ref()
10381049
.map(|d| d.students.clone())

src-tauri/src/lib.rs

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -292,9 +292,10 @@ fn setup_database(app: &mut App) -> Result<(), Box<dyn std::error::Error>> {
292292
*db_guard = Some(active_conn);
293293
}
294294

295-
state_guard.initialize().await.map_err(|e| {
296-
format!("Failed to initialize app state: {}", e)
297-
})?;
295+
state_guard
296+
.initialize()
297+
.await
298+
.map_err(|e| format!("Failed to initialize app state: {}", e))?;
298299

299300
Ok::<_, Box<dyn std::error::Error>>(())
300301
});

src-tauri/src/services/auto_score.rs

Lines changed: 65 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
use chrono::{DateTime, Utc};
1+
use chrono::{DateTime, Months, Utc};
22
use serde::{Deserialize, Serialize};
33
use std::collections::HashMap;
44
use tauri::{AppHandle, Emitter};
@@ -15,6 +15,20 @@ pub struct AutoScoreAction {
1515
pub value: Option<String>,
1616
}
1717

18+
#[derive(Debug, Clone, Serialize, Deserialize)]
19+
#[serde(rename_all = "lowercase")]
20+
enum IntervalUnit {
21+
Minute,
22+
Day,
23+
Month,
24+
}
25+
26+
#[derive(Debug, Clone, Serialize, Deserialize)]
27+
struct IntervalTriggerValue {
28+
amount: i64,
29+
unit: IntervalUnit,
30+
}
31+
1832
#[derive(Debug, Clone, Serialize, Deserialize)]
1933
pub struct AutoScoreRule {
2034
pub id: i32,
@@ -171,23 +185,17 @@ impl AutoScoreService {
171185

172186
for trigger in &rule.triggers {
173187
if trigger.event == "interval_time_passed" {
174-
let minutes = trigger
175-
.value
188+
let interval = parse_interval_trigger_value(trigger.value.as_ref());
189+
let base_time = rule
190+
.last_executed
176191
.as_ref()
177-
.and_then(|v| v.parse::<i64>().ok())
178-
.unwrap_or(30);
179-
let interval_ms = minutes * 60 * 1000;
180-
181-
if let Some(last_executed_str) = &rule.last_executed {
182-
if let Ok(last_executed) = DateTime::parse_from_rfc3339(last_executed_str) {
183-
let last_executed_utc: DateTime<Utc> = last_executed.with_timezone(&Utc);
184-
let next_execute_time =
185-
last_executed_utc + chrono::Duration::milliseconds(interval_ms);
186-
let delay_ms = (next_execute_time - now).num_milliseconds();
187-
return Some(delay_ms.max(0));
188-
}
189-
}
190-
return Some(interval_ms);
192+
.and_then(|value| DateTime::parse_from_rfc3339(value).ok())
193+
.map(|value| value.with_timezone(&Utc))
194+
.unwrap_or(now);
195+
196+
let next_execute_time = add_interval_to_time(base_time, &interval)?;
197+
let delay_ms = (next_execute_time - now).num_milliseconds();
198+
return Some(delay_ms.max(0));
191199
}
192200
}
193201
None
@@ -197,3 +205,43 @@ impl AutoScoreService {
197205
let _ = app_handle.emit("auto-score:rulesChanged", &self.rules);
198206
}
199207
}
208+
209+
fn parse_interval_trigger_value(value: Option<&String>) -> IntervalTriggerValue {
210+
if let Some(raw_value) = value {
211+
if let Ok(minutes) = raw_value.parse::<i64>() {
212+
if minutes > 0 {
213+
return IntervalTriggerValue {
214+
amount: minutes,
215+
unit: IntervalUnit::Minute,
216+
};
217+
}
218+
}
219+
220+
if let Ok(parsed_value) = serde_json::from_str::<IntervalTriggerValue>(raw_value) {
221+
if parsed_value.amount > 0 {
222+
return parsed_value;
223+
}
224+
}
225+
}
226+
227+
IntervalTriggerValue {
228+
amount: 30,
229+
unit: IntervalUnit::Minute,
230+
}
231+
}
232+
233+
fn add_interval_to_time(
234+
base_time: DateTime<Utc>,
235+
interval: &IntervalTriggerValue,
236+
) -> Option<DateTime<Utc>> {
237+
match interval.unit {
238+
IntervalUnit::Minute => {
239+
base_time.checked_add_signed(chrono::Duration::minutes(interval.amount))
240+
}
241+
IntervalUnit::Day => base_time.checked_add_signed(chrono::Duration::days(interval.amount)),
242+
IntervalUnit::Month => {
243+
let months = u32::try_from(interval.amount).ok()?;
244+
base_time.checked_add_months(Months::new(months))
245+
}
246+
}
247+
}

src-tauri/src/services/settings.rs

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -431,9 +431,7 @@ impl SettingsService {
431431
"settings",
432432
];
433433
return items.iter().all(|item| {
434-
item.as_str()
435-
.map(|s| allowed.contains(&s))
436-
.unwrap_or(false)
434+
item.as_str().map(|s| allowed.contains(&s)).unwrap_or(false)
437435
});
438436
}
439437
false

src/assets/main.css

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -92,6 +92,9 @@ select {
9292

9393
.ss-sidebar .ant-menu-item {
9494
color: var(--ss-sidebar-text, var(--ss-text-main));
95+
height: 40px !important;
96+
line-height: 42px !important;
97+
border-radius: 6px !important;
9598
}
9699

97100
.ss-sidebar .ant-menu-item-selected {

src/components/AutoScoreManager.tsx

Lines changed: 41 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -14,9 +14,10 @@ import {
1414
Tag,
1515
message,
1616
} from "antd"
17-
import { Utils as QbUtils, type ImmutableTree } from "@react-awesome-query-builder/antd"
17+
import { type ImmutableTree } from "@react-awesome-query-builder/antd"
1818
import type { ColumnsType } from "antd/es/table"
1919
import { useTranslation } from "react-i18next"
20+
import { fetchAllTags } from "./TagEditorDialog"
2021
import { ActionEditor } from "./AutoScoreManagerPage/ActionEditor"
2122
import { TriggerRuleBuilder } from "./AutoScoreManagerPage/TriggerRuleBuilder"
2223
import {
@@ -30,13 +31,19 @@ import {
3031
triggersToQueryTree,
3132
type ActionDraft,
3233
type AutoScoreRule,
34+
type AutoScoreTagOption,
3335
} from "./AutoScoreManagerPage/AutoScoreUtils"
3436

3537
interface StudentItem {
3638
id: number
3739
name: string
3840
}
3941

42+
interface TagItem {
43+
id: number
44+
name: string
45+
}
46+
4047
interface RuleFormValues {
4148
name?: string
4249
studentNames?: string[]
@@ -50,10 +57,20 @@ function AutoScoreManager({ canEdit }: AutoScoreManagerProps): React.JSX.Element
5057
const { t, i18n } = useTranslation()
5158
const [form] = Form.useForm<RuleFormValues>()
5259
const [messageApi, contextHolder] = message.useMessage()
60+
const [tags, setTags] = useState<TagItem[]>([])
61+
62+
const tagOptions = useMemo<AutoScoreTagOption[]>(
63+
() =>
64+
tags.map((tag) => ({
65+
label: tag.name,
66+
value: tag.name,
67+
})),
68+
[tags]
69+
)
5370

5471
const triggerConfig = useMemo(
55-
() => createTriggerQueryConfig(t),
56-
[i18n.resolvedLanguage, i18n.language]
72+
() => createTriggerQueryConfig(t, tagOptions),
73+
[i18n.resolvedLanguage, i18n.language, tagOptions]
5774
)
5875
const [triggerTree, setTriggerTree] = useState<ImmutableTree>(() =>
5976
createEmptyTriggerTree(triggerConfig)
@@ -67,10 +84,6 @@ function AutoScoreManager({ canEdit }: AutoScoreManagerProps): React.JSX.Element
6784
const [currentPage, setCurrentPage] = useState(1)
6885
const [pageSize, setPageSize] = useState(10)
6986

70-
useEffect(() => {
71-
setTriggerTree((prevTree) => QbUtils.checkTree(prevTree, triggerConfig))
72-
}, [triggerConfig])
73-
7487
useEffect(() => {
7588
const maxPage = Math.max(1, Math.ceil(rules.length / pageSize))
7689
if (currentPage > maxPage) {
@@ -103,6 +116,19 @@ function AutoScoreManager({ canEdit }: AutoScoreManagerProps): React.JSX.Element
103116
}
104117
}
105118

119+
const fetchTags = async () => {
120+
if (!canEdit) return
121+
122+
try {
123+
const tagList = await fetchAllTags()
124+
if (Array.isArray(tagList)) {
125+
setTags(tagList)
126+
}
127+
} catch {
128+
void 0
129+
}
130+
}
131+
106132
const fetchRules = async () => {
107133
const api = (window as any).api
108134
if (!api || !canEdit) return
@@ -124,6 +150,7 @@ function AutoScoreManager({ canEdit }: AutoScoreManagerProps): React.JSX.Element
124150

125151
useEffect(() => {
126152
if (!canEdit) return
153+
fetchTags().catch(() => void 0)
127154
fetchStudents().catch(() => void 0)
128155
fetchRules().catch(() => void 0)
129156
}, [canEdit])
@@ -387,10 +414,15 @@ function AutoScoreManager({ canEdit }: AutoScoreManagerProps): React.JSX.Element
387414
config={triggerConfig}
388415
value={triggerTree}
389416
canEdit={canEdit}
390-
onChange={(nextTree, config) => setTriggerTree(QbUtils.checkTree(nextTree, config))}
417+
onChange={(nextTree) => setTriggerTree(nextTree)}
391418
/>
392419

393-
<ActionEditor value={actionDrafts} canEdit={canEdit} onChange={setActionDrafts} />
420+
<ActionEditor
421+
value={actionDrafts}
422+
tagOptions={tagOptions}
423+
canEdit={canEdit}
424+
onChange={setActionDrafts}
425+
/>
394426

395427
<div style={{ marginBottom: "24px", display: "flex", gap: "12px" }}>
396428
<Button type="primary" loading={saving} disabled={!canEdit} onClick={() => handleSubmit().catch(() => void 0)}>

0 commit comments

Comments
 (0)