mirror of
https://github.com/SECTL/SecScore.git
synced 2026-07-21 20:29:03 +08:00
修复快速设置学生丢失并新增重置入口
This commit is contained in:
@@ -1,7 +1,11 @@
|
|||||||
use parking_lot::RwLock;
|
use parking_lot::RwLock;
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
use tauri::State;
|
use tauri::{AppHandle, Emitter, State};
|
||||||
|
|
||||||
|
use crate::db::connection::{create_sqlite_connection, DatabaseType};
|
||||||
|
use crate::db::migration::Migration;
|
||||||
|
use crate::services::settings::{SettingsKey, SettingsValue, SettingsService};
|
||||||
|
use crate::services::{AutoScoreService, ThemeService};
|
||||||
use crate::services::permission::PermissionLevel;
|
use crate::services::permission::PermissionLevel;
|
||||||
use crate::state::AppState;
|
use crate::state::AppState;
|
||||||
|
|
||||||
@@ -17,6 +21,25 @@ fn check_admin_permission(state: &Arc<RwLock<AppState>>) -> Result<(), String> {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn sqlite_db_path(app_handle: &AppHandle) -> Result<String, String> {
|
||||||
|
if cfg!(all(debug_assertions, desktop)) {
|
||||||
|
return Ok("data.sql".to_string());
|
||||||
|
}
|
||||||
|
|
||||||
|
let app_data_dir = app_handle
|
||||||
|
.path()
|
||||||
|
.app_data_dir()
|
||||||
|
.map_err(|e| format!("Failed to get app data directory: {}", e))?;
|
||||||
|
let data_dir = app_data_dir.join("data");
|
||||||
|
std::fs::create_dir_all(&data_dir)
|
||||||
|
.map_err(|e| format!("Failed to create data directory: {}", e))?;
|
||||||
|
let db_path = data_dir.join("data.sql");
|
||||||
|
db_path
|
||||||
|
.to_str()
|
||||||
|
.map(|s| s.to_string())
|
||||||
|
.ok_or_else(|| "Invalid sqlite database path".to_string())
|
||||||
|
}
|
||||||
|
|
||||||
#[tauri::command]
|
#[tauri::command]
|
||||||
pub async fn data_export_json(
|
pub async fn data_export_json(
|
||||||
state: State<'_, Arc<RwLock<AppState>>>,
|
state: State<'_, Arc<RwLock<AppState>>>,
|
||||||
@@ -60,3 +83,101 @@ pub async fn data_import_json(
|
|||||||
))
|
))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[tauri::command]
|
||||||
|
pub async fn data_reset_all(
|
||||||
|
app_handle: AppHandle,
|
||||||
|
state: State<'_, Arc<RwLock<AppState>>>,
|
||||||
|
) -> Result<IpcResponse<()>, String> {
|
||||||
|
check_admin_permission(&state)?;
|
||||||
|
|
||||||
|
let sqlite_path = sqlite_db_path(&app_handle)?;
|
||||||
|
let sqlite_conn = create_sqlite_connection(&sqlite_path)
|
||||||
|
.await
|
||||||
|
.map_err(|e| format!("Failed to open sqlite database: {}", e))?;
|
||||||
|
|
||||||
|
let (active_conn, active_db_type) = {
|
||||||
|
let state_guard = state.read();
|
||||||
|
let active_conn = state_guard
|
||||||
|
.db
|
||||||
|
.read()
|
||||||
|
.clone()
|
||||||
|
.ok_or_else(|| "Database not connected".to_string())?;
|
||||||
|
let db_type = {
|
||||||
|
let settings = state_guard.settings.read();
|
||||||
|
match settings.get_value(SettingsKey::PgConnectionStatus) {
|
||||||
|
SettingsValue::Json(json) => match json.get("type").and_then(|value| value.as_str()) {
|
||||||
|
Some("postgresql") => DatabaseType::PostgreSQL,
|
||||||
|
_ => DatabaseType::SQLite,
|
||||||
|
},
|
||||||
|
_ => DatabaseType::SQLite,
|
||||||
|
}
|
||||||
|
};
|
||||||
|
(active_conn, db_type)
|
||||||
|
};
|
||||||
|
|
||||||
|
if active_db_type == DatabaseType::PostgreSQL {
|
||||||
|
Migration::reset_database(&active_conn, false)
|
||||||
|
.await
|
||||||
|
.map_err(|e| format!("Failed to reset PostgreSQL database: {}", e))?;
|
||||||
|
}
|
||||||
|
|
||||||
|
Migration::reset_database(&sqlite_conn, true)
|
||||||
|
.await
|
||||||
|
.map_err(|e| format!("Failed to reset SQLite database: {}", e))?;
|
||||||
|
|
||||||
|
{
|
||||||
|
let state_guard = state.read();
|
||||||
|
|
||||||
|
{
|
||||||
|
let mut db = state_guard.db.write();
|
||||||
|
*db = Some(sqlite_conn.clone());
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
let mut settings = state_guard.settings.write();
|
||||||
|
*settings = SettingsService::new();
|
||||||
|
settings.attach_db(Some(sqlite_conn.clone()));
|
||||||
|
settings
|
||||||
|
.initialize()
|
||||||
|
.await
|
||||||
|
.map_err(|e| format!("Failed to reinitialize settings: {}", e))?;
|
||||||
|
settings
|
||||||
|
.set_value(
|
||||||
|
SettingsKey::PgConnectionStatus,
|
||||||
|
SettingsValue::Json(serde_json::json!({
|
||||||
|
"connected": true,
|
||||||
|
"type": "sqlite"
|
||||||
|
})),
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
.map_err(|e| format!("Failed to restore sqlite status: {}", e))?;
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
let mut permissions = state_guard.permissions.write();
|
||||||
|
permissions.clear_all_permissions();
|
||||||
|
permissions.update_password_status(false, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
let mut theme = state_guard.theme.write();
|
||||||
|
*theme = ThemeService::new();
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
let mut auto_score = state_guard.auto_score.write();
|
||||||
|
*auto_score = AutoScoreService::new();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let _ = app_handle.emit(
|
||||||
|
"settings:changed",
|
||||||
|
serde_json::json!({
|
||||||
|
"key": "is_wizard_completed",
|
||||||
|
"value": false
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
|
Ok(IpcResponse::success_empty())
|
||||||
|
}
|
||||||
|
|||||||
@@ -90,6 +90,7 @@ pub fn run() {
|
|||||||
log_write,
|
log_write,
|
||||||
data_export_json,
|
data_export_json,
|
||||||
data_import_json,
|
data_import_json,
|
||||||
|
data_reset_all,
|
||||||
window_minimize,
|
window_minimize,
|
||||||
window_maximize,
|
window_maximize,
|
||||||
window_close,
|
window_close,
|
||||||
|
|||||||
@@ -32,6 +32,27 @@ interface reasonItem {
|
|||||||
delta: number
|
delta: number
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const mergeStudents = (prev: studentItem[], names: string[]): studentItem[] => {
|
||||||
|
const existing = new Set(prev.map((item) => item.name))
|
||||||
|
const next = [...prev]
|
||||||
|
|
||||||
|
names.forEach((rawName) => {
|
||||||
|
const name = rawName.trim()
|
||||||
|
if (!name || existing.has(name)) return
|
||||||
|
next.push({ name })
|
||||||
|
existing.add(name)
|
||||||
|
})
|
||||||
|
|
||||||
|
return next
|
||||||
|
}
|
||||||
|
|
||||||
|
const mergeReasons = (prev: reasonItem[], nextReason: reasonItem): reasonItem[] => {
|
||||||
|
if (prev.some((item) => item.content === nextReason.content)) {
|
||||||
|
return prev
|
||||||
|
}
|
||||||
|
return [...prev, nextReason]
|
||||||
|
}
|
||||||
|
|
||||||
const deepClone = <T,>(v: T): T => JSON.parse(JSON.stringify(v)) as T
|
const deepClone = <T,>(v: T): T => JSON.parse(JSON.stringify(v)) as T
|
||||||
|
|
||||||
const withTimeout = async (promise: Promise<any>, ms: number, timeoutMessage: string): Promise<any> => {
|
const withTimeout = async (promise: Promise<any>, ms: number, timeoutMessage: string): Promise<any> => {
|
||||||
@@ -215,25 +236,25 @@ export const OOBE: React.FC<oobeProps> = ({ visible, onComplete }) => {
|
|||||||
|
|
||||||
const existing = new Set(students.map((s) => s.name))
|
const existing = new Set(students.map((s) => s.name))
|
||||||
const added = new Set<string>()
|
const added = new Set<string>()
|
||||||
const newStudents: studentItem[] = []
|
const newStudentNames: string[] = []
|
||||||
|
|
||||||
lines.forEach((name) => {
|
lines.forEach((name) => {
|
||||||
if (existing.has(name) || added.has(name)) return
|
if (existing.has(name) || added.has(name)) return
|
||||||
newStudents.push({ name })
|
newStudentNames.push(name)
|
||||||
added.add(name)
|
added.add(name)
|
||||||
})
|
})
|
||||||
|
|
||||||
if (newStudents.length === 0) {
|
if (newStudentNames.length === 0) {
|
||||||
showOobeMessage("warning", t("oobe.steps.students.studentExists"))
|
showOobeMessage("warning", t("oobe.steps.students.studentExists"))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
setStudents([...students, ...newStudents])
|
setStudents((prev) => mergeStudents(prev, newStudentNames))
|
||||||
setNewStudentName("")
|
setNewStudentName("")
|
||||||
}
|
}
|
||||||
|
|
||||||
const removeStudent = (name: string) => {
|
const removeStudent = (name: string) => {
|
||||||
setStudents(students.filter((s) => s.name !== name))
|
setStudents((prev) => prev.filter((s) => s.name !== name))
|
||||||
}
|
}
|
||||||
|
|
||||||
const addReason = () => {
|
const addReason = () => {
|
||||||
@@ -243,13 +264,13 @@ export const OOBE: React.FC<oobeProps> = ({ visible, onComplete }) => {
|
|||||||
messageApi.warning(t("oobe.steps.reasons.reasonExists"))
|
messageApi.warning(t("oobe.steps.reasons.reasonExists"))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
setReasons([...reasons, { content, delta: newReasonDelta }])
|
setReasons((prev) => mergeReasons(prev, { content, delta: newReasonDelta }))
|
||||||
setNewReasonContent("")
|
setNewReasonContent("")
|
||||||
setNewReasonDelta(1)
|
setNewReasonDelta(1)
|
||||||
}
|
}
|
||||||
|
|
||||||
const removeReason = (content: string) => {
|
const removeReason = (content: string) => {
|
||||||
setReasons(reasons.filter((r) => r.content !== content))
|
setReasons((prev) => prev.filter((r) => r.content !== content))
|
||||||
}
|
}
|
||||||
|
|
||||||
const handleFileImport = useCallback(
|
const handleFileImport = useCallback(
|
||||||
@@ -269,15 +290,17 @@ export const OOBE: React.FC<oobeProps> = ({ visible, onComplete }) => {
|
|||||||
typeof item === "string" ? item : item.name || item.student_name
|
typeof item === "string" ? item : item.name || item.student_name
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
const newStudents = studentList
|
const newStudentNames = studentList
|
||||||
.filter((name) => name && typeof name === "string")
|
.filter((name) => name && typeof name === "string")
|
||||||
.filter((name) => !students.some((s) => s.name === name))
|
.map((name) => name.trim())
|
||||||
.map((name) => ({ name: name.trim() }))
|
.filter(Boolean)
|
||||||
if (newStudents.length > 0) {
|
const mergedStudents = mergeStudents(students, newStudentNames)
|
||||||
setStudents([...students, ...newStudents])
|
const importedCount = mergedStudents.length - students.length
|
||||||
|
if (importedCount > 0) {
|
||||||
|
setStudents((prev) => mergeStudents(prev, newStudentNames))
|
||||||
showOobeMessage(
|
showOobeMessage(
|
||||||
"success",
|
"success",
|
||||||
t("oobe.steps.students.importSuccess", { count: newStudents.length })
|
t("oobe.steps.students.importSuccess", { count: importedCount })
|
||||||
)
|
)
|
||||||
} else {
|
} else {
|
||||||
showOobeMessage("info", t("oobe.steps.students.noNewStudents"))
|
showOobeMessage("info", t("oobe.steps.students.noNewStudents"))
|
||||||
@@ -300,14 +323,13 @@ export const OOBE: React.FC<oobeProps> = ({ visible, onComplete }) => {
|
|||||||
names.push(name.trim())
|
names.push(name.trim())
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
const newStudents = names
|
const mergedStudents = mergeStudents(students, names)
|
||||||
.filter((name) => !students.some((s) => s.name === name))
|
const importedCount = mergedStudents.length - students.length
|
||||||
.map((name) => ({ name }))
|
if (importedCount > 0) {
|
||||||
if (newStudents.length > 0) {
|
setStudents((prev) => mergeStudents(prev, names))
|
||||||
setStudents([...students, ...newStudents])
|
|
||||||
showOobeMessage(
|
showOobeMessage(
|
||||||
"success",
|
"success",
|
||||||
t("oobe.steps.students.importSuccess", { count: newStudents.length })
|
t("oobe.steps.students.importSuccess", { count: importedCount })
|
||||||
)
|
)
|
||||||
} else {
|
} else {
|
||||||
showOobeMessage("info", t("oobe.steps.students.noNewStudents"))
|
showOobeMessage("info", t("oobe.steps.students.noNewStudents"))
|
||||||
|
|||||||
@@ -75,6 +75,8 @@ export const Settings: React.FC<{ permission: permissionLevel }> = ({ permission
|
|||||||
|
|
||||||
const [clearDialogVisible, setClearDialogVisible] = useState(false)
|
const [clearDialogVisible, setClearDialogVisible] = useState(false)
|
||||||
const [clearLoading, setClearLoading] = useState(false)
|
const [clearLoading, setClearLoading] = useState(false)
|
||||||
|
const [resetDialogVisible, setResetDialogVisible] = useState(false)
|
||||||
|
const [resetLoading, setResetLoading] = useState(false)
|
||||||
|
|
||||||
const importInputRef = useRef<HTMLInputElement | null>(null)
|
const importInputRef = useRef<HTMLInputElement | null>(null)
|
||||||
|
|
||||||
@@ -339,6 +341,30 @@ export const Settings: React.FC<{ permission: permissionLevel }> = ({ permission
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const openResetDialog = () => {
|
||||||
|
setResetDialogVisible(true)
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleConfirmResetAll = async () => {
|
||||||
|
if (!(window as any).api?.dataResetAll) return
|
||||||
|
setResetLoading(true)
|
||||||
|
try {
|
||||||
|
const res = await (window as any).api.dataResetAll()
|
||||||
|
if (res.success) {
|
||||||
|
messageApi.success(t("settings.data.resetSuccess"))
|
||||||
|
window.dispatchEvent(new CustomEvent("ss:data-updated", { detail: { category: "all" } }))
|
||||||
|
window.setTimeout(() => window.location.reload(), 300)
|
||||||
|
} else {
|
||||||
|
messageApi.error(res.message || t("settings.data.resetFailed"))
|
||||||
|
}
|
||||||
|
} catch (e: any) {
|
||||||
|
messageApi.error(e?.message || t("settings.data.resetFailed"))
|
||||||
|
} finally {
|
||||||
|
setResetLoading(false)
|
||||||
|
setResetDialogVisible(false)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const confirmSettlement = () => {
|
const confirmSettlement = () => {
|
||||||
setSettleDialogVisible(true)
|
setSettleDialogVisible(true)
|
||||||
}
|
}
|
||||||
@@ -953,6 +979,24 @@ export const Settings: React.FC<{ permission: permissionLevel }> = ({ permission
|
|||||||
</Space>
|
</Space>
|
||||||
</Card>
|
</Card>
|
||||||
|
|
||||||
|
<Card
|
||||||
|
title={t("settings.data.quickSetupReset")}
|
||||||
|
style={{
|
||||||
|
backgroundColor: "var(--ss-card-bg)",
|
||||||
|
color: "var(--ss-text-main)",
|
||||||
|
marginBottom: "16px",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Space align="center">
|
||||||
|
<Button danger disabled={!canAdmin} loading={resetLoading} onClick={openResetDialog}>
|
||||||
|
{t("settings.data.resetAllAndReenter")}
|
||||||
|
</Button>
|
||||||
|
<div style={{ fontSize: "12px", color: "var(--ss-text-secondary)" }}>
|
||||||
|
{t("settings.data.resetHint")}
|
||||||
|
</div>
|
||||||
|
</Space>
|
||||||
|
</Card>
|
||||||
|
|
||||||
<Card
|
<Card
|
||||||
style={{
|
style={{
|
||||||
backgroundColor: "var(--ss-card-bg)",
|
backgroundColor: "var(--ss-card-bg)",
|
||||||
@@ -1361,6 +1405,24 @@ export const Settings: React.FC<{ permission: permissionLevel }> = ({ permission
|
|||||||
>
|
>
|
||||||
清空后将关闭保护(无密码时默认视为管理权限)。
|
清空后将关闭保护(无密码时默认视为管理权限)。
|
||||||
</Modal>
|
</Modal>
|
||||||
|
|
||||||
|
<Modal
|
||||||
|
title={t("settings.data.confirmResetAll")}
|
||||||
|
open={resetDialogVisible}
|
||||||
|
onCancel={() => !resetLoading && setResetDialogVisible(false)}
|
||||||
|
onOk={handleConfirmResetAll}
|
||||||
|
confirmLoading={resetLoading}
|
||||||
|
okText={t("settings.data.resetAllAndReenter")}
|
||||||
|
okButtonProps={{ danger: true }}
|
||||||
|
cancelText={t("common.cancel")}
|
||||||
|
>
|
||||||
|
<div style={{ display: "flex", flexDirection: "column", gap: "8px" }}>
|
||||||
|
<div>{t("settings.data.resetConfirm1")}</div>
|
||||||
|
<div style={{ color: "var(--ss-text-secondary)", fontSize: "12px" }}>
|
||||||
|
{t("settings.data.resetConfirm2")}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</Modal>
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -37,17 +37,17 @@
|
|||||||
"none": "None"
|
"none": "None"
|
||||||
},
|
},
|
||||||
"oobe": {
|
"oobe": {
|
||||||
"title": "Welcome to SecScore",
|
"title": "SecScore Quick Setup",
|
||||||
"subtitle": "Education Points Management Expert",
|
"subtitle": "Finish the essentials in a few steps and start using the app",
|
||||||
"step": "Step {{current}}/{{total}}",
|
"step": "Step {{current}}/{{total}}",
|
||||||
"skip": "Skip",
|
"skip": "Skip",
|
||||||
"steps": {
|
"steps": {
|
||||||
"entry": {
|
"entry": {
|
||||||
"title": "Get Started",
|
"title": "Get Started",
|
||||||
"description": "Choose how you want to proceed",
|
"description": "Choose how you want to proceed",
|
||||||
"enterOobe": "Enter OOBE",
|
"enterOobe": "Enter Quick Setup",
|
||||||
"connectPostgresAutoSync": "Connect PostgreSQL with Auto Sync",
|
"connectPostgresAutoSync": "Connect PostgreSQL with Auto Sync",
|
||||||
"skipDirect": "Skip OOBE and Enter"
|
"skipDirect": "Skip Quick Setup and Enter"
|
||||||
},
|
},
|
||||||
"postgresql": {
|
"postgresql": {
|
||||||
"title": "Connect PostgreSQL",
|
"title": "Connect PostgreSQL",
|
||||||
@@ -233,6 +233,9 @@
|
|||||||
"settlement": "Settlement",
|
"settlement": "Settlement",
|
||||||
"settlementAndRestart": "Settle and Restart",
|
"settlementAndRestart": "Settle and Restart",
|
||||||
"settlementHint": "Mark current records as a phase and reset all student points to zero.",
|
"settlementHint": "Mark current records as a phase and reset all student points to zero.",
|
||||||
|
"quickSetupReset": "Quick Setup Reset",
|
||||||
|
"resetAllAndReenter": "Delete all data and re-enter Quick Setup",
|
||||||
|
"resetHint": "Delete students, reasons, records, rewards, tags, and all settings, then return to Quick Setup.",
|
||||||
"importExport": "Import / Export",
|
"importExport": "Import / Export",
|
||||||
"exportJson": "Export JSON",
|
"exportJson": "Export JSON",
|
||||||
"importJson": "Import JSON",
|
"importJson": "Import JSON",
|
||||||
@@ -253,10 +256,15 @@
|
|||||||
"exportSuccess": "Export successful",
|
"exportSuccess": "Export successful",
|
||||||
"importSuccess": "Import successful, refreshing",
|
"importSuccess": "Import successful, refreshing",
|
||||||
"importFailed": "Import failed",
|
"importFailed": "Import failed",
|
||||||
|
"resetSuccess": "Reset complete, reopening Quick Setup",
|
||||||
|
"resetFailed": "Reset failed",
|
||||||
"clearFailed": "Clear failed",
|
"clearFailed": "Clear failed",
|
||||||
"confirmSettlement": "Confirm settlement and restart?",
|
"confirmSettlement": "Confirm settlement and restart?",
|
||||||
"settlementConfirm1": "This will archive current unsettled records as a phase and reset all student points to zero.",
|
"settlementConfirm1": "This will archive current unsettled records as a phase and reset all student points to zero.",
|
||||||
"settlementConfirm2": "Student list remains unchanged; settlement history available in 'Settlements' page.",
|
"settlementConfirm2": "Student list remains unchanged; settlement history available in 'Settlements' page.",
|
||||||
|
"confirmResetAll": "Delete all data and re-enter Quick Setup?",
|
||||||
|
"resetConfirm1": "This will delete students, reasons, score records, rewards, tags, and all settings. This cannot be undone.",
|
||||||
|
"resetConfirm2": "After reset, the app will reload and open Quick Setup directly.",
|
||||||
"settlementSuccess": "Settlement successful, points restarted",
|
"settlementSuccess": "Settlement successful, points restarted",
|
||||||
"settlementFailed": "Settlement failed",
|
"settlementFailed": "Settlement failed",
|
||||||
"settle": "Settle",
|
"settle": "Settle",
|
||||||
|
|||||||
@@ -37,17 +37,17 @@
|
|||||||
"none": "无"
|
"none": "无"
|
||||||
},
|
},
|
||||||
"oobe": {
|
"oobe": {
|
||||||
"title": "欢迎使用 SecScore",
|
"title": "SecScore 快速设置",
|
||||||
"subtitle": "教育积分管理专家",
|
"subtitle": "几步完成基础配置,马上开始使用",
|
||||||
"step": "第 {{current}}/{{total}} 步",
|
"step": "第 {{current}}/{{total}} 步",
|
||||||
"skip": "跳过",
|
"skip": "跳过",
|
||||||
"steps": {
|
"steps": {
|
||||||
"entry": {
|
"entry": {
|
||||||
"title": "开始配置",
|
"title": "开始配置",
|
||||||
"description": "请选择接下来要进行的操作",
|
"description": "请选择接下来要进行的操作",
|
||||||
"enterOobe": "进入OOBE",
|
"enterOobe": "进入快速设置",
|
||||||
"connectPostgresAutoSync": "连接PostgreSQL自动同步",
|
"connectPostgresAutoSync": "连接PostgreSQL自动同步",
|
||||||
"skipDirect": "跳过OOBE,直接进入"
|
"skipDirect": "跳过快速设置,直接进入"
|
||||||
},
|
},
|
||||||
"postgresql": {
|
"postgresql": {
|
||||||
"title": "连接 PostgreSQL",
|
"title": "连接 PostgreSQL",
|
||||||
@@ -233,6 +233,9 @@
|
|||||||
"settlement": "结算",
|
"settlement": "结算",
|
||||||
"settlementAndRestart": "结算并重新开始",
|
"settlementAndRestart": "结算并重新开始",
|
||||||
"settlementHint": "将当前未结算记录划分为一个阶段,并将所有学生积分清零。",
|
"settlementHint": "将当前未结算记录划分为一个阶段,并将所有学生积分清零。",
|
||||||
|
"quickSetupReset": "快速设置重置",
|
||||||
|
"resetAllAndReenter": "删除所有数据并重新进入快速设置",
|
||||||
|
"resetHint": "删除学生、理由、记录、奖励、标签和全部设置,然后重新进入快速设置。",
|
||||||
"importExport": "导入 / 导出",
|
"importExport": "导入 / 导出",
|
||||||
"exportJson": "导出 JSON",
|
"exportJson": "导出 JSON",
|
||||||
"importJson": "导入 JSON",
|
"importJson": "导入 JSON",
|
||||||
@@ -253,10 +256,15 @@
|
|||||||
"exportSuccess": "导出成功",
|
"exportSuccess": "导出成功",
|
||||||
"importSuccess": "导入成功,正在刷新",
|
"importSuccess": "导入成功,正在刷新",
|
||||||
"importFailed": "导入失败",
|
"importFailed": "导入失败",
|
||||||
|
"resetSuccess": "已重置,正在重新进入快速设置",
|
||||||
|
"resetFailed": "重置失败",
|
||||||
"clearFailed": "清空失败",
|
"clearFailed": "清空失败",
|
||||||
"confirmSettlement": "确认结算并重新开始?",
|
"confirmSettlement": "确认结算并重新开始?",
|
||||||
"settlementConfirm1": "将把当前未结算的积分记录归档为一个阶段,并将所有学生当前积分清零。",
|
"settlementConfirm1": "将把当前未结算的积分记录归档为一个阶段,并将所有学生当前积分清零。",
|
||||||
"settlementConfirm2": "学生名单不变;结算后的历史在「结算历史」页面查看。",
|
"settlementConfirm2": "学生名单不变;结算后的历史在「结算历史」页面查看。",
|
||||||
|
"confirmResetAll": "确认删除所有数据并重新进入快速设置?",
|
||||||
|
"resetConfirm1": "这会删除学生、理由、积分记录、奖励、标签以及全部设置,且不可恢复。",
|
||||||
|
"resetConfirm2": "重置后应用将重新加载,并直接进入快速设置。",
|
||||||
"settlementSuccess": "结算成功,已重新开始积分",
|
"settlementSuccess": "结算成功,已重新开始积分",
|
||||||
"settlementFailed": "结算失败",
|
"settlementFailed": "结算失败",
|
||||||
"settle": "结算",
|
"settle": "结算",
|
||||||
|
|||||||
@@ -205,6 +205,7 @@ const api = {
|
|||||||
exportDataJson: (): Promise<{ success: boolean; data: string }> => invoke("data_export_json"),
|
exportDataJson: (): Promise<{ success: boolean; data: string }> => invoke("data_export_json"),
|
||||||
importDataJson: (jsonText: string): Promise<{ success: boolean }> =>
|
importDataJson: (jsonText: string): Promise<{ success: boolean }> =>
|
||||||
invoke("data_import_json", { jsonText }),
|
invoke("data_import_json", { jsonText }),
|
||||||
|
dataResetAll: (): Promise<{ success: boolean }> => invoke("data_reset_all"),
|
||||||
|
|
||||||
// Window
|
// Window
|
||||||
windowMinimize: (): Promise<void> => invoke("window_minimize"),
|
windowMinimize: (): Promise<void> => invoke("window_minimize"),
|
||||||
|
|||||||
Reference in New Issue
Block a user