mirror of
https://github.com/SECTL/SecScore.git
synced 2026-07-19 21:14:21 +08:00
feat: 首页支持多选全选与批量积分
This commit is contained in:
+212
-34
@@ -89,6 +89,8 @@ export const Home: React.FC<HomeProps> = ({ canEdit, isPortraitMode = false }) =
|
||||
const searchAreaRef = useRef<HTMLDivElement>(null)
|
||||
|
||||
const [selectedStudent, setSelectedStudent] = useState<student | null>(null)
|
||||
const [batchMode, setBatchMode] = useState(false)
|
||||
const [selectedStudentIds, setSelectedStudentIds] = useState<number[]>([])
|
||||
const [operationVisible, setOperationVisible] = useState(false)
|
||||
const [customScore, setCustomScore] = useState<number | undefined>(undefined)
|
||||
const [reasonContent, setReasonContent] = useState("")
|
||||
@@ -280,6 +282,14 @@ export const Home: React.FC<HomeProps> = ({ canEdit, isPortraitMode = false }) =
|
||||
}
|
||||
}, [])
|
||||
|
||||
useEffect(() => {
|
||||
setSelectedStudentIds((prev) => {
|
||||
if (prev.length === 0) return prev
|
||||
const validIds = new Set(students.map((s) => s.id))
|
||||
return prev.filter((id) => validIds.has(id))
|
||||
})
|
||||
}, [students])
|
||||
|
||||
const t9KeyRows = [
|
||||
[
|
||||
{ digit: "2", letters: "ABC" },
|
||||
@@ -430,6 +440,12 @@ export const Home: React.FC<HomeProps> = ({ canEdit, isPortraitMode = false }) =
|
||||
})
|
||||
}, [reasons])
|
||||
|
||||
const selectedStudents = useMemo(() => {
|
||||
if (selectedStudentIds.length === 0) return []
|
||||
const idSet = new Set(selectedStudentIds)
|
||||
return students.filter((s) => idSet.has(s.id))
|
||||
}, [students, selectedStudentIds])
|
||||
|
||||
const getAvatarColor = (name: string) => {
|
||||
const colors = [
|
||||
"#FF6B6B",
|
||||
@@ -463,26 +479,34 @@ export const Home: React.FC<HomeProps> = ({ canEdit, isPortraitMode = false }) =
|
||||
messageApi.error(t("common.readOnly"))
|
||||
return
|
||||
}
|
||||
if (batchMode) {
|
||||
setSelectedStudentIds((prev) =>
|
||||
prev.includes(student.id) ? prev.filter((id) => id !== student.id) : [...prev, student.id]
|
||||
)
|
||||
return
|
||||
}
|
||||
setSelectedStudent(student)
|
||||
setCustomScore(undefined)
|
||||
setReasonContent("")
|
||||
setOperationVisible(true)
|
||||
}
|
||||
|
||||
const performSubmit = async (student: student, delta: number, content: string) => {
|
||||
const performSubmit = async (targetStudents: student[], delta: number, content: string) => {
|
||||
if (!(window as any).api) return
|
||||
if (!canEdit) {
|
||||
messageApi.error(t("common.readOnly"))
|
||||
return
|
||||
}
|
||||
if (targetStudents.length === 0) {
|
||||
messageApi.warning(t("home.selectStudentFirst"))
|
||||
return
|
||||
}
|
||||
|
||||
setSubmitLoading(true)
|
||||
logHome("performSubmit:start", {
|
||||
student: student.name,
|
||||
delta,
|
||||
content,
|
||||
localScoreBefore: student.score,
|
||||
})
|
||||
logHome("performSubmit:start", { studentCount: targetStudents.length, delta, content })
|
||||
let successCount = 0
|
||||
|
||||
for (const student of targetStudents) {
|
||||
const res = await (window as any).api.createEvent({
|
||||
student_name: student.name,
|
||||
reason_content: content,
|
||||
@@ -495,23 +519,40 @@ export const Home: React.FC<HomeProps> = ({ canEdit, isPortraitMode = false }) =
|
||||
success: Boolean(res?.success),
|
||||
message: (res as any)?.message,
|
||||
})
|
||||
if (res.success) successCount += 1
|
||||
}
|
||||
|
||||
if (res.success) {
|
||||
if (successCount > 0) {
|
||||
if (targetStudents.length === 1) {
|
||||
const student = targetStudents[0]
|
||||
messageApi.success(
|
||||
delta > 0
|
||||
? t("home.scoreAdded", { name: student.name, points: Math.abs(delta) })
|
||||
: t("home.scoreDeducted", { name: student.name, points: Math.abs(delta) })
|
||||
)
|
||||
} else if (successCount === targetStudents.length) {
|
||||
messageApi.success(t("home.batchSuccess", { count: successCount }))
|
||||
} else {
|
||||
messageApi.warning(
|
||||
t("home.batchPartial", { success: successCount, total: targetStudents.length })
|
||||
)
|
||||
}
|
||||
|
||||
setSelectedStudentIds([])
|
||||
setBatchMode(false)
|
||||
setSelectedStudent(null)
|
||||
setOperationVisible(false)
|
||||
setCustomScore(undefined)
|
||||
setReasonContent("")
|
||||
setQuickActionStudentId(null)
|
||||
fetchData(true)
|
||||
fetchLatestEvent()
|
||||
emitDataUpdated("events")
|
||||
logHome("performSubmit:afterSuccessRefreshDispatched", {
|
||||
student: student.name,
|
||||
delta,
|
||||
})
|
||||
logHome("performSubmit:afterSuccessRefreshDispatched", { studentCount: targetStudents.length, delta })
|
||||
} else {
|
||||
messageApi.error(res.message || t("home.submitFailed"))
|
||||
messageApi.warning(
|
||||
t("home.batchPartial", { success: successCount, total: targetStudents.length })
|
||||
)
|
||||
}
|
||||
setSubmitLoading(false)
|
||||
}
|
||||
@@ -541,7 +582,11 @@ export const Home: React.FC<HomeProps> = ({ canEdit, isPortraitMode = false }) =
|
||||
}
|
||||
|
||||
const handleSubmit = async () => {
|
||||
if (!selectedStudent) return
|
||||
const targets = batchMode ? selectedStudents : selectedStudent ? [selectedStudent] : []
|
||||
if (targets.length === 0) {
|
||||
messageApi.warning(t("home.selectStudentFirst"))
|
||||
return
|
||||
}
|
||||
|
||||
const delta = customScore
|
||||
if (delta === undefined || !Number.isFinite(delta)) {
|
||||
@@ -556,12 +601,16 @@ export const Home: React.FC<HomeProps> = ({ canEdit, isPortraitMode = false }) =
|
||||
: delta < 0
|
||||
? t("home.deductPoints")
|
||||
: t("home.pointsChange"))
|
||||
await performSubmit(selectedStudent, delta, content)
|
||||
await performSubmit(targets, delta, content)
|
||||
}
|
||||
|
||||
const handleReasonSelect = (reason: reason) => {
|
||||
if (!selectedStudent) return
|
||||
performSubmit(selectedStudent, reason.delta, reason.content)
|
||||
const targets = batchMode ? selectedStudents : selectedStudent ? [selectedStudent] : []
|
||||
if (targets.length === 0) {
|
||||
messageApi.warning(t("home.selectStudentFirst"))
|
||||
return
|
||||
}
|
||||
performSubmit(targets, reason.delta, reason.content)
|
||||
}
|
||||
|
||||
const cancelLongPress = () => {
|
||||
@@ -596,10 +645,49 @@ export const Home: React.FC<HomeProps> = ({ canEdit, isPortraitMode = false }) =
|
||||
|
||||
const handleQuickAdjust = (student: student, delta: number) => {
|
||||
const content = delta > 0 ? t("home.addPoints") : t("home.deductPoints")
|
||||
performSubmit(student, delta, content)
|
||||
performSubmit([student], delta, content)
|
||||
setQuickActionStudentId(null)
|
||||
}
|
||||
|
||||
const handleSelectAllStudents = () => {
|
||||
setSelectedStudentIds(students.map((s) => s.id))
|
||||
}
|
||||
|
||||
const handleClearSelectedStudents = () => {
|
||||
setSelectedStudentIds([])
|
||||
}
|
||||
|
||||
const handleEnterBatchMode = () => {
|
||||
if (!canEdit) {
|
||||
messageApi.error(t("common.readOnly"))
|
||||
return
|
||||
}
|
||||
setBatchMode(true)
|
||||
setSelectedStudent(null)
|
||||
setOperationVisible(false)
|
||||
setQuickActionStudentId(null)
|
||||
}
|
||||
|
||||
const handleExitBatchMode = () => {
|
||||
setBatchMode(false)
|
||||
setSelectedStudentIds([])
|
||||
}
|
||||
|
||||
const handleOpenBatchOperation = () => {
|
||||
if (!canEdit) {
|
||||
messageApi.error(t("common.readOnly"))
|
||||
return
|
||||
}
|
||||
if (selectedStudents.length === 0) {
|
||||
messageApi.warning(t("home.selectStudentFirst"))
|
||||
return
|
||||
}
|
||||
setSelectedStudent(null)
|
||||
setCustomScore(undefined)
|
||||
setReasonContent("")
|
||||
setOperationVisible(true)
|
||||
}
|
||||
|
||||
const renderStudentCard = (student: student, index: number) => {
|
||||
const avatarText = getDisplayText(student.name)
|
||||
const avatarColor = getAvatarColor(student.name)
|
||||
@@ -612,6 +700,7 @@ export const Home: React.FC<HomeProps> = ({ canEdit, isPortraitMode = false }) =
|
||||
}
|
||||
|
||||
const isQuickActionMode = quickActionStudentId === student.id
|
||||
const isSelected = selectedStudentIds.includes(student.id)
|
||||
|
||||
return (
|
||||
<div
|
||||
@@ -627,15 +716,20 @@ export const Home: React.FC<HomeProps> = ({ canEdit, isPortraitMode = false }) =
|
||||
openOperation(student)
|
||||
}}
|
||||
onMouseDown={(e) => {
|
||||
if (batchMode) return
|
||||
if (e.button !== 0) return
|
||||
startLongPress(student)
|
||||
}}
|
||||
onMouseUp={cancelLongPress}
|
||||
onMouseLeave={cancelLongPress}
|
||||
onTouchStart={() => startLongPress(student)}
|
||||
onTouchStart={() => {
|
||||
if (batchMode) return
|
||||
startLongPress(student)
|
||||
}}
|
||||
onTouchEnd={cancelLongPress}
|
||||
onTouchCancel={cancelLongPress}
|
||||
onContextMenu={(e) => {
|
||||
if (batchMode) return
|
||||
e.preventDefault()
|
||||
openQuickAction(student)
|
||||
}}
|
||||
@@ -646,10 +740,13 @@ export const Home: React.FC<HomeProps> = ({ canEdit, isPortraitMode = false }) =
|
||||
backgroundColor: "var(--ss-card-bg)",
|
||||
transition: "all 0.2s cubic-bezier(0.38, 0, 0.24, 1)",
|
||||
border: isQuickActionMode
|
||||
? "1px solid var(--ant-color-primary, #1677ff)"
|
||||
: isSelected
|
||||
? "1px solid var(--ant-color-primary, #1677ff)"
|
||||
: "1px solid var(--ss-border-color)",
|
||||
overflow: "visible",
|
||||
boxShadow: isQuickActionMode ? "0 8px 18px rgba(22, 119, 255, 0.18)" : undefined,
|
||||
boxShadow:
|
||||
isQuickActionMode || isSelected ? "0 8px 18px rgba(22, 119, 255, 0.18)" : undefined,
|
||||
}}
|
||||
styles={{ body: { padding: isPortraitMode ? "10px 12px" : "12px" } }}
|
||||
>
|
||||
@@ -787,6 +884,11 @@ export const Home: React.FC<HomeProps> = ({ canEdit, isPortraitMode = false }) =
|
||||
<div
|
||||
style={{ display: "flex", alignItems: "center", gap: "4px", marginTop: "2px" }}
|
||||
>
|
||||
{isSelected && (
|
||||
<Tag color="processing" style={{ marginInlineEnd: 0 }}>
|
||||
{t("home.selected")}
|
||||
</Tag>
|
||||
)}
|
||||
<Tag
|
||||
color={student.score > 0 ? "success" : student.score < 0 ? "error" : "default"}
|
||||
style={{ fontWeight: "bold" }}
|
||||
@@ -806,6 +908,7 @@ export const Home: React.FC<HomeProps> = ({ canEdit, isPortraitMode = false }) =
|
||||
const avatarText = getDisplayText(student.name)
|
||||
const avatarColor = getAvatarColor(student.name)
|
||||
const isQuickActionMode = quickActionStudentId === student.id
|
||||
const isSelected = selectedStudentIds.includes(student.id)
|
||||
|
||||
return (
|
||||
<div
|
||||
@@ -821,15 +924,20 @@ export const Home: React.FC<HomeProps> = ({ canEdit, isPortraitMode = false }) =
|
||||
openOperation(student)
|
||||
}}
|
||||
onMouseDown={(e) => {
|
||||
if (batchMode) return
|
||||
if (e.button !== 0) return
|
||||
startLongPress(student)
|
||||
}}
|
||||
onMouseUp={cancelLongPress}
|
||||
onMouseLeave={cancelLongPress}
|
||||
onTouchStart={() => startLongPress(student)}
|
||||
onTouchStart={() => {
|
||||
if (batchMode) return
|
||||
startLongPress(student)
|
||||
}}
|
||||
onTouchEnd={cancelLongPress}
|
||||
onTouchCancel={cancelLongPress}
|
||||
onContextMenu={(e) => {
|
||||
if (batchMode) return
|
||||
e.preventDefault()
|
||||
openQuickAction(student)
|
||||
}}
|
||||
@@ -838,7 +946,8 @@ export const Home: React.FC<HomeProps> = ({ canEdit, isPortraitMode = false }) =
|
||||
position: "relative",
|
||||
padding: "8px 10px",
|
||||
borderBottom: isLast ? "none" : "1px solid var(--ss-border-color)",
|
||||
background: isQuickActionMode ? "rgba(22, 119, 255, 0.06)" : "transparent",
|
||||
background:
|
||||
isQuickActionMode || isSelected ? "rgba(22, 119, 255, 0.06)" : "transparent",
|
||||
transition: "background-color 160ms ease",
|
||||
}}
|
||||
>
|
||||
@@ -919,12 +1028,19 @@ export const Home: React.FC<HomeProps> = ({ canEdit, isPortraitMode = false }) =
|
||||
</Button>
|
||||
</Space>
|
||||
) : (
|
||||
<Space size={4}>
|
||||
{isSelected && (
|
||||
<Tag color="processing" style={{ marginInlineEnd: 0 }}>
|
||||
{t("home.selected")}
|
||||
</Tag>
|
||||
)}
|
||||
<Tag
|
||||
color={student.score > 0 ? "success" : student.score < 0 ? "error" : "default"}
|
||||
style={{ fontWeight: "bold", marginInlineEnd: 0 }}
|
||||
>
|
||||
{student.score > 0 ? `+${student.score}` : student.score}
|
||||
</Tag>
|
||||
</Space>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
@@ -935,6 +1051,7 @@ export const Home: React.FC<HomeProps> = ({ canEdit, isPortraitMode = false }) =
|
||||
const avatarText = getDisplayText(student.name)
|
||||
const avatarColor = getAvatarColor(student.name)
|
||||
const isQuickActionMode = quickActionStudentId === student.id
|
||||
const isSelected = selectedStudentIds.includes(student.id)
|
||||
|
||||
let rankBadge: string | null = null
|
||||
if (sortType === "score" && !searchKeyword) {
|
||||
@@ -957,15 +1074,20 @@ export const Home: React.FC<HomeProps> = ({ canEdit, isPortraitMode = false }) =
|
||||
openOperation(student)
|
||||
}}
|
||||
onMouseDown={(e) => {
|
||||
if (batchMode) return
|
||||
if (e.button !== 0) return
|
||||
startLongPress(student)
|
||||
}}
|
||||
onMouseUp={cancelLongPress}
|
||||
onMouseLeave={cancelLongPress}
|
||||
onTouchStart={() => startLongPress(student)}
|
||||
onTouchStart={() => {
|
||||
if (batchMode) return
|
||||
startLongPress(student)
|
||||
}}
|
||||
onTouchEnd={cancelLongPress}
|
||||
onTouchCancel={cancelLongPress}
|
||||
onContextMenu={(e) => {
|
||||
if (batchMode) return
|
||||
e.preventDefault()
|
||||
openQuickAction(student)
|
||||
}}
|
||||
@@ -990,10 +1112,13 @@ export const Home: React.FC<HomeProps> = ({ canEdit, isPortraitMode = false }) =
|
||||
backgroundColor: "var(--ss-card-bg)",
|
||||
transition: "all 0.2s cubic-bezier(0.38, 0, 0.24, 1)",
|
||||
border: isQuickActionMode
|
||||
? "1px solid var(--ant-color-primary, #1677ff)"
|
||||
: isSelected
|
||||
? "1px solid var(--ant-color-primary, #1677ff)"
|
||||
: "1px solid var(--ss-border-color)",
|
||||
overflow: "visible",
|
||||
boxShadow: isQuickActionMode ? "0 8px 18px rgba(22, 119, 255, 0.18)" : undefined,
|
||||
boxShadow:
|
||||
isQuickActionMode || isSelected ? "0 8px 18px rgba(22, 119, 255, 0.18)" : undefined,
|
||||
}}
|
||||
styles={{ body: { height: "100%", padding: "8px" } }}
|
||||
>
|
||||
@@ -1111,6 +1236,11 @@ export const Home: React.FC<HomeProps> = ({ canEdit, isPortraitMode = false }) =
|
||||
>
|
||||
{student.score > 0 ? `+${student.score}` : student.score}
|
||||
</Tag>
|
||||
{isSelected && (
|
||||
<Tag color="processing" style={{ marginInlineEnd: 0 }}>
|
||||
{t("home.selected")}
|
||||
</Tag>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
@@ -1502,7 +1632,10 @@ export const Home: React.FC<HomeProps> = ({ canEdit, isPortraitMode = false }) =
|
||||
)
|
||||
}
|
||||
|
||||
const operationPanelContent = selectedStudent && (
|
||||
const operationTargets = batchMode ? selectedStudents : selectedStudent ? [selectedStudent] : []
|
||||
const isBatchOperation = operationTargets.length > 1 || (batchMode && operationTargets.length > 0)
|
||||
|
||||
const operationPanelContent = operationTargets.length > 0 && (
|
||||
<div
|
||||
style={{
|
||||
display: "flex",
|
||||
@@ -1525,7 +1658,7 @@ export const Home: React.FC<HomeProps> = ({ canEdit, isPortraitMode = false }) =
|
||||
}}
|
||||
>
|
||||
<div style={{ display: "flex", alignItems: "center", gap: "12px", minWidth: 0, flex: 1 }}>
|
||||
{selectedStudent.avatarUrl ? (
|
||||
{!isBatchOperation && selectedStudent?.avatarUrl ? (
|
||||
<img
|
||||
src={selectedStudent.avatarUrl}
|
||||
alt={selectedStudent.name}
|
||||
@@ -1537,13 +1670,13 @@ export const Home: React.FC<HomeProps> = ({ canEdit, isPortraitMode = false }) =
|
||||
border: "1px solid var(--ss-border-color)",
|
||||
}}
|
||||
/>
|
||||
) : (
|
||||
) : !isBatchOperation ? (
|
||||
<div
|
||||
style={{
|
||||
width: "32px",
|
||||
height: "32px",
|
||||
borderRadius: "50%",
|
||||
backgroundColor: getAvatarColor(selectedStudent.name),
|
||||
backgroundColor: getAvatarColor(selectedStudent?.name || ""),
|
||||
color: "white",
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
@@ -1552,8 +1685,10 @@ export const Home: React.FC<HomeProps> = ({ canEdit, isPortraitMode = false }) =
|
||||
fontWeight: "bold",
|
||||
}}
|
||||
>
|
||||
{getDisplayText(selectedStudent.name)}
|
||||
{getDisplayText(selectedStudent?.name || "")}
|
||||
</div>
|
||||
) : (
|
||||
<Tag color="processing">{t("home.batchMode")}</Tag>
|
||||
)}
|
||||
<span
|
||||
style={{
|
||||
@@ -1564,22 +1699,30 @@ export const Home: React.FC<HomeProps> = ({ canEdit, isPortraitMode = false }) =
|
||||
whiteSpace: "nowrap",
|
||||
}}
|
||||
>
|
||||
{selectedStudent.name}
|
||||
{!isBatchOperation
|
||||
? selectedStudent?.name
|
||||
: t("home.selectedCount", { count: operationTargets.length })}
|
||||
</span>
|
||||
</div>
|
||||
{!isBatchOperation && selectedStudent && (
|
||||
<div style={{ display: "flex", alignItems: "center", gap: "8px", flexShrink: 0 }}>
|
||||
<span style={{ color: "var(--ss-text-secondary)", fontSize: "13px" }}>
|
||||
{t("home.currentScore")}:
|
||||
</span>
|
||||
<Tag
|
||||
color={
|
||||
selectedStudent.score > 0 ? "success" : selectedStudent.score < 0 ? "error" : "default"
|
||||
selectedStudent.score > 0
|
||||
? "success"
|
||||
: selectedStudent.score < 0
|
||||
? "error"
|
||||
: "default"
|
||||
}
|
||||
style={{ fontWeight: "bold" }}
|
||||
>
|
||||
{selectedStudent.score > 0 ? `+${selectedStudent.score}` : selectedStudent.score}
|
||||
</Tag>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{groupedReasons.length > 0 && (
|
||||
@@ -1769,7 +1912,9 @@ export const Home: React.FC<HomeProps> = ({ canEdit, isPortraitMode = false }) =
|
||||
{t("home.preview")}:
|
||||
</div>
|
||||
<div style={{ fontSize: "15px" }}>
|
||||
{selectedStudent.name}{" "}
|
||||
{isBatchOperation
|
||||
? t("home.selectedCount", { count: operationTargets.length })
|
||||
: selectedStudent?.name}{" "}
|
||||
<span
|
||||
style={{
|
||||
fontWeight: "bold",
|
||||
@@ -1973,6 +2118,31 @@ export const Home: React.FC<HomeProps> = ({ canEdit, isPortraitMode = false }) =
|
||||
>
|
||||
{t("home.undoLastAction")}
|
||||
</Button>
|
||||
{!batchMode ? (
|
||||
<Button onClick={handleEnterBatchMode} disabled={!canEdit}>
|
||||
{t("home.multiSelect")}
|
||||
</Button>
|
||||
) : (
|
||||
<Space size={8} wrap>
|
||||
<Button onClick={handleSelectAllStudents} disabled={!canEdit || students.length === 0}>
|
||||
{t("home.selectAll")}
|
||||
</Button>
|
||||
<Button
|
||||
onClick={handleClearSelectedStudents}
|
||||
disabled={!canEdit || selectedStudentIds.length === 0}
|
||||
>
|
||||
{t("home.clearSelected")}
|
||||
</Button>
|
||||
<Button
|
||||
type="primary"
|
||||
onClick={handleOpenBatchOperation}
|
||||
disabled={!canEdit || selectedStudentIds.length === 0}
|
||||
>
|
||||
{t("home.batchOperate")}
|
||||
</Button>
|
||||
<Button onClick={handleExitBatchMode}>{t("common.cancel")}</Button>
|
||||
</Space>
|
||||
)}
|
||||
</Space>
|
||||
</div>
|
||||
|
||||
@@ -2016,7 +2186,11 @@ export const Home: React.FC<HomeProps> = ({ canEdit, isPortraitMode = false }) =
|
||||
{isPortraitMode ? (
|
||||
<Drawer
|
||||
title={
|
||||
<div data-tauri-drag-region>{t("home.operationTitle", { name: selectedStudent?.name })}</div>
|
||||
<div data-tauri-drag-region>
|
||||
{isBatchOperation
|
||||
? t("home.operationTitleBatch", { count: operationTargets.length })
|
||||
: t("home.operationTitle", { name: selectedStudent?.name })}
|
||||
</div>
|
||||
}
|
||||
className="ss-operation-drawer"
|
||||
placement="bottom"
|
||||
@@ -2041,7 +2215,11 @@ export const Home: React.FC<HomeProps> = ({ canEdit, isPortraitMode = false }) =
|
||||
</Drawer>
|
||||
) : (
|
||||
<Modal
|
||||
title={t("home.operationTitle", { name: selectedStudent?.name })}
|
||||
title={
|
||||
isBatchOperation
|
||||
? t("home.operationTitleBatch", { count: operationTargets.length })
|
||||
: t("home.operationTitle", { name: selectedStudent?.name })
|
||||
}
|
||||
open={operationVisible}
|
||||
onCancel={() => setOperationVisible(false)}
|
||||
onOk={handleSubmit}
|
||||
|
||||
@@ -396,10 +396,21 @@
|
||||
"studentCount": "{{count}} students",
|
||||
"operationTitle": "Points Operation: {{name}}",
|
||||
"submitOperation": "Submit Operation",
|
||||
"operationTitleBatch": "Points Operation: {{count}} Selected",
|
||||
"undoLastAction": "Undo Last",
|
||||
"undoLastHint": "Undo latest: {{name}} {{delta}}",
|
||||
"undoUnavailable": "No points operation available to undo",
|
||||
"undoLastSuccess": "Last points operation has been undone",
|
||||
"multiSelect": "Multi-select",
|
||||
"batchMode": "Batch Mode",
|
||||
"selectAll": "Select All",
|
||||
"clearSelected": "Clear Selected",
|
||||
"batchOperate": "Batch Points",
|
||||
"selected": "Selected",
|
||||
"selectedCount": "{{count}} selected",
|
||||
"batchSuccess": "Submitted points for {{count}} students",
|
||||
"batchPartial": "Successfully submitted for {{success}}/{{total}} students",
|
||||
"selectStudentFirst": "Please select student(s) first",
|
||||
"addPoints": "Add Points",
|
||||
"deductPoints": "Deduct Points",
|
||||
"pointsChange": "Points Change",
|
||||
|
||||
@@ -396,10 +396,21 @@
|
||||
"studentCount": "{{count}} 人",
|
||||
"operationTitle": "积分操作:{{name}}",
|
||||
"submitOperation": "提交操作",
|
||||
"operationTitleBatch": "积分操作:已选 {{count}} 人",
|
||||
"undoLastAction": "撤销上一步",
|
||||
"undoLastHint": "撤销上一条:{{name}} {{delta}}",
|
||||
"undoUnavailable": "当前没有可撤销的积分操作",
|
||||
"undoLastSuccess": "已撤销上一步积分操作",
|
||||
"multiSelect": "多选",
|
||||
"batchMode": "多选模式",
|
||||
"selectAll": "全选",
|
||||
"clearSelected": "清空已选",
|
||||
"batchOperate": "批量积分",
|
||||
"selected": "已选",
|
||||
"selectedCount": "已选 {{count}} 人",
|
||||
"batchSuccess": "已为 {{count}} 名学生提交积分",
|
||||
"batchPartial": "成功提交 {{success}}/{{total}} 名学生积分",
|
||||
"selectStudentFirst": "请先选择学生",
|
||||
"addPoints": "加分",
|
||||
"deductPoints": "扣分",
|
||||
"pointsChange": "积分变更",
|
||||
|
||||
Reference in New Issue
Block a user