mirror of
https://github.com/SECTL/SecScore.git
synced 2026-07-19 19:04:23 +08:00
feat: 主页支持一键撤销上一步积分操作
This commit is contained in:
+73
-3
@@ -1,6 +1,6 @@
|
||||
import React, { useState, useEffect, useCallback, useMemo, useRef } from "react"
|
||||
import { Card, Space, Button, Tag, Input, Select, Modal, Drawer, message, InputNumber, Divider } from "antd"
|
||||
import { SearchOutlined, DeleteOutlined } from "@ant-design/icons"
|
||||
import { SearchOutlined, DeleteOutlined, UndoOutlined } from "@ant-design/icons"
|
||||
import { useTranslation } from "react-i18next"
|
||||
import { match, pinyin } from "pinyin-pro"
|
||||
import { getAvatarFromExtraJson } from "../utils/studentAvatar"
|
||||
@@ -23,6 +23,17 @@ interface reason {
|
||||
category: string
|
||||
}
|
||||
|
||||
interface scoreEvent {
|
||||
id: number
|
||||
uuid: string
|
||||
student_name: string
|
||||
reason_content: string
|
||||
delta: number
|
||||
val_prev: number
|
||||
val_curr: number
|
||||
event_time: string
|
||||
}
|
||||
|
||||
type SortType = "alphabet" | "surname" | "score"
|
||||
type LayoutType = "grouped" | "squareGrid"
|
||||
type SearchKeyboardLayout = "t9" | "qwerty26"
|
||||
@@ -82,6 +93,8 @@ export const Home: React.FC<HomeProps> = ({ canEdit, isPortraitMode = false }) =
|
||||
const [customScore, setCustomScore] = useState<number | undefined>(undefined)
|
||||
const [reasonContent, setReasonContent] = useState("")
|
||||
const [submitLoading, setSubmitLoading] = useState(false)
|
||||
const [undoLoading, setUndoLoading] = useState(false)
|
||||
const [latestEvent, setLatestEvent] = useState<scoreEvent | null>(null)
|
||||
const [messageApi, contextHolder] = message.useMessage()
|
||||
const [quickActionStudentId, setQuickActionStudentId] = useState<number | null>(null)
|
||||
const longPressTimerRef = useRef<number | null>(null)
|
||||
@@ -126,17 +139,33 @@ export const Home: React.FC<HomeProps> = ({ canEdit, isPortraitMode = false }) =
|
||||
if (!silent) setLoading(false)
|
||||
}, [])
|
||||
|
||||
const fetchLatestEvent = useCallback(async () => {
|
||||
if (!(window as any).api) return
|
||||
const res = await (window as any).api.queryEvents({ limit: 1 })
|
||||
if (res.success) {
|
||||
const latest = Array.isArray(res.data) && res.data.length > 0 ? (res.data[0] as scoreEvent) : null
|
||||
setLatestEvent(latest)
|
||||
}
|
||||
}, [])
|
||||
|
||||
useEffect(() => {
|
||||
fetchData()
|
||||
fetchLatestEvent()
|
||||
const onDataUpdated = (e: any) => {
|
||||
const category = e?.detail?.category
|
||||
if (category === "students" || category === "reasons" || category === "all") {
|
||||
if (
|
||||
category === "events" ||
|
||||
category === "students" ||
|
||||
category === "reasons" ||
|
||||
category === "all"
|
||||
) {
|
||||
fetchData(true)
|
||||
fetchLatestEvent()
|
||||
}
|
||||
}
|
||||
window.addEventListener("ss:data-updated", onDataUpdated as any)
|
||||
return () => window.removeEventListener("ss:data-updated", onDataUpdated as any)
|
||||
}, [fetchData])
|
||||
}, [fetchData, fetchLatestEvent])
|
||||
|
||||
useEffect(() => {
|
||||
const api = (window as any).api
|
||||
@@ -434,6 +463,7 @@ export const Home: React.FC<HomeProps> = ({ canEdit, isPortraitMode = false }) =
|
||||
prev.map((s) => (s.id === student.id ? { ...s, score: s.score + delta } : s))
|
||||
)
|
||||
|
||||
fetchLatestEvent()
|
||||
emitDataUpdated("events")
|
||||
} else {
|
||||
messageApi.error(res.message || t("home.submitFailed"))
|
||||
@@ -441,6 +471,30 @@ export const Home: React.FC<HomeProps> = ({ canEdit, isPortraitMode = false }) =
|
||||
setSubmitLoading(false)
|
||||
}
|
||||
|
||||
const handleUndoLastEvent = async () => {
|
||||
if (!(window as any).api) return
|
||||
if (!canEdit) {
|
||||
messageApi.error(t("common.readOnly"))
|
||||
return
|
||||
}
|
||||
if (!latestEvent) {
|
||||
messageApi.warning(t("home.undoUnavailable"))
|
||||
return
|
||||
}
|
||||
|
||||
setUndoLoading(true)
|
||||
const res = await (window as any).api.deleteEvent(latestEvent.uuid)
|
||||
if (res.success) {
|
||||
messageApi.success(t("home.undoLastSuccess"))
|
||||
fetchData(true)
|
||||
fetchLatestEvent()
|
||||
emitDataUpdated("events")
|
||||
} else {
|
||||
messageApi.error((res as any).message || t("score.undoFailed"))
|
||||
}
|
||||
setUndoLoading(false)
|
||||
}
|
||||
|
||||
const handleSubmit = async () => {
|
||||
if (!selectedStudent) return
|
||||
|
||||
@@ -1858,6 +1912,22 @@ export const Home: React.FC<HomeProps> = ({ canEdit, isPortraitMode = false }) =
|
||||
{ value: "squareGrid", label: t("home.layoutBy.squareGrid") },
|
||||
]}
|
||||
/>
|
||||
<Button
|
||||
icon={<UndoOutlined />}
|
||||
onClick={handleUndoLastEvent}
|
||||
loading={undoLoading}
|
||||
disabled={!canEdit || !latestEvent}
|
||||
title={
|
||||
latestEvent
|
||||
? t("home.undoLastHint", {
|
||||
name: latestEvent.student_name,
|
||||
delta: latestEvent.delta > 0 ? `+${latestEvent.delta}` : latestEvent.delta,
|
||||
})
|
||||
: t("home.undoUnavailable")
|
||||
}
|
||||
>
|
||||
{t("home.undoLastAction")}
|
||||
</Button>
|
||||
</Space>
|
||||
</div>
|
||||
|
||||
|
||||
Reference in New Issue
Block a user