feat: 看板学生列表支持总分与加扣分显示切换

This commit is contained in:
JSR
2026-03-27 20:47:28 +08:00
parent 95b0b29c3a
commit 24e909a2ff
3 changed files with 85 additions and 9 deletions
+73 -9
View File
@@ -19,6 +19,7 @@ import { DeleteOutlined, EditOutlined, PlusOutlined, ReloadOutlined } from "@ant
import { useTranslation } from "react-i18next" import { useTranslation } from "react-i18next"
type BoardStudentViewMode = "list" | "card" | "grid" type BoardStudentViewMode = "list" | "card" | "grid"
type BoardScoreDisplayMode = "total" | "split"
type SplitDirection = "horizontal" | "vertical" type SplitDirection = "horizontal" | "vertical"
interface StudentListConfig { interface StudentListConfig {
@@ -26,6 +27,7 @@ interface StudentListConfig {
name: string name: string
sql: string sql: string
viewMode: BoardStudentViewMode viewMode: BoardStudentViewMode
scoreDisplayMode: BoardScoreDisplayMode
} }
interface LayoutLeafNode { interface LayoutLeafNode {
@@ -67,6 +69,8 @@ interface BoardStudentCardData {
key: string key: string
name: string name: string
score?: number score?: number
addScore?: number
deductScore?: number
rewardPoints?: number rewardPoints?: number
weekChange?: number weekChange?: number
weekDeducted?: number weekDeducted?: number
@@ -95,18 +99,31 @@ const makeId = () =>
const clampRatio = (value: number) => Math.max(0.15, Math.min(0.85, value)) const clampRatio = (value: number) => Math.max(0.15, Math.min(0.85, value))
const getDefaultSql = () => `SELECT const getDefaultSql = () => `WITH score_stat AS (
name AS student_name, SELECT
score, student_name,
reward_points SUM(CASE WHEN delta > 0 THEN delta ELSE 0 END) AS add_score,
FROM students SUM(CASE WHEN delta < 0 THEN -delta ELSE 0 END) AS deduct_score
ORDER BY score DESC` FROM score_events
WHERE settlement_id IS NULL
GROUP BY student_name
)
SELECT
s.name AS student_name,
s.score,
s.reward_points,
COALESCE(ss.add_score, 0) AS add_score,
COALESCE(ss.deduct_score, 0) AS deduct_score
FROM students s
LEFT JOIN score_stat ss ON ss.student_name = s.name
ORDER BY s.score DESC`
const createDefaultList = (): StudentListConfig => ({ const createDefaultList = (): StudentListConfig => ({
id: makeId(), id: makeId(),
name: "学生积分榜", name: "学生积分榜",
sql: getDefaultSql(), sql: getDefaultSql(),
viewMode: "card", viewMode: "card",
scoreDisplayMode: "total",
}) })
const createLeafForList = (listId: string): LayoutLeafNode => ({ const createLeafForList = (listId: string): LayoutLeafNode => ({
@@ -205,6 +222,10 @@ const normalizeBoards = (input: unknown): BoardConfig[] => {
list?.viewMode === "list" || list?.viewMode === "card" || list?.viewMode === "grid" list?.viewMode === "list" || list?.viewMode === "card" || list?.viewMode === "grid"
? list.viewMode ? list.viewMode
: "card", : "card",
scoreDisplayMode:
list?.scoreDisplayMode === "total" || list?.scoreDisplayMode === "split"
? list.scoreDisplayMode
: "total",
})) }))
.filter((list: StudentListConfig) => list.sql.trim()) .filter((list: StudentListConfig) => list.sql.trim())
: [] : []
@@ -289,6 +310,10 @@ const toStudentCards = (rows: any[]): BoardStudentCardData[] => {
key: `${name}-${index}`, key: `${name}-${index}`,
name, name,
score: parseNumber(data.score), score: parseNumber(data.score),
addScore: parseNumber(data.add_score ?? data.addScore ?? data.plus_score ?? data.plusScore),
deductScore: parseNumber(
data.deduct_score ?? data.deductScore ?? data.minus_score ?? data.minusScore
),
rewardPoints: parseNumber(data.reward_points ?? data.rewardPoints), rewardPoints: parseNumber(data.reward_points ?? data.rewardPoints),
weekChange: parseNumber(data.week_change ?? data.range_change ?? data.change), weekChange: parseNumber(data.week_change ?? data.range_change ?? data.change),
weekDeducted: parseNumber(data.week_deducted ?? data.deducted), weekDeducted: parseNumber(data.week_deducted ?? data.deducted),
@@ -540,6 +565,7 @@ ORDER BY reward_points DESC, score DESC`,
name: item.name, name: item.name,
sql: item.sql, sql: item.sql,
viewMode: item.viewMode, viewMode: item.viewMode,
scoreDisplayMode: item.scoreDisplayMode,
})) }))
) )
}, [activeBoard]) }, [activeBoard])
@@ -672,6 +698,7 @@ ORDER BY reward_points DESC, score DESC`,
name: t("board.newList"), name: t("board.newList"),
sql: getDefaultSql(), sql: getDefaultSql(),
viewMode: "card", viewMode: "card",
scoreDisplayMode: "total",
} }
const findLeaf = (node: LayoutNode): LayoutLeafNode | null => { const findLeaf = (node: LayoutNode): LayoutLeafNode | null => {
@@ -796,8 +823,22 @@ ORDER BY reward_points DESC, score DESC`,
const avatarColor = getAvatarColor(item.name) const avatarColor = getAvatarColor(item.name)
const avatarText = getAvatarText(item.name) const avatarText = getAvatarText(item.name)
const rankBadge = index === 0 ? "🥇" : index === 1 ? "🥈" : index === 2 ? "🥉" : null const rankBadge = index === 0 ? "🥇" : index === 1 ? "🥈" : index === 2 ? "🥉" : null
const primaryMetric = const useSplitScore = list.scoreDisplayMode === "split"
item.score !== undefined const primaryMetric = useSplitScore
? item.addScore !== undefined
? { label: t("board.metrics.addScore"), value: item.addScore }
: item.deductScore !== undefined
? { label: t("board.metrics.deductScore"), value: item.deductScore }
: item.rewardPoints !== undefined
? { label: t("board.metrics.rewardPoints"), value: item.rewardPoints }
: item.weekChange !== undefined
? { label: t("board.metrics.weekChange"), value: item.weekChange }
: item.weekDeducted !== undefined
? { label: t("board.metrics.weekDeducted"), value: item.weekDeducted }
: item.answeredCount !== undefined
? { label: t("board.metrics.todayAnswered"), value: item.answeredCount }
: null
: item.score !== undefined
? { label: t("board.metrics.totalScore"), value: item.score } ? { label: t("board.metrics.totalScore"), value: item.score }
: item.rewardPoints !== undefined : item.rewardPoints !== undefined
? { label: t("board.metrics.rewardPoints"), value: item.rewardPoints } ? { label: t("board.metrics.rewardPoints"), value: item.rewardPoints }
@@ -950,12 +991,23 @@ ORDER BY reward_points DESC, score DESC`,
{item.name} {item.name}
</div> </div>
<div style={{ marginTop: 4, display: "flex", flexWrap: "wrap", gap: 6 }}> <div style={{ marginTop: 4, display: "flex", flexWrap: "wrap", gap: 6 }}>
{item.score !== undefined && ( {list.scoreDisplayMode === "total" && item.score !== undefined && (
<Tag color={item.score >= 0 ? "success" : "error"} style={{ margin: 0 }}> <Tag color={item.score >= 0 ? "success" : "error"} style={{ margin: 0 }}>
{t("board.metrics.totalScore")}:{" "} {t("board.metrics.totalScore")}:{" "}
{item.score > 0 ? `+${item.score}` : item.score} {item.score > 0 ? `+${item.score}` : item.score}
</Tag> </Tag>
)} )}
{list.scoreDisplayMode === "split" && item.addScore !== undefined && (
<Tag color="success" style={{ margin: 0 }}>
{t("board.metrics.addScore")}:{" "}
{item.addScore > 0 ? `+${item.addScore}` : item.addScore}
</Tag>
)}
{list.scoreDisplayMode === "split" && item.deductScore !== undefined && (
<Tag color="error" style={{ margin: 0 }}>
{t("board.metrics.deductScore")}: {item.deductScore}
</Tag>
)}
{item.rewardPoints !== undefined && ( {item.rewardPoints !== undefined && (
<Tag color="processing" style={{ margin: 0 }}> <Tag color="processing" style={{ margin: 0 }}>
{t("board.metrics.rewardPoints")}: {item.rewardPoints} {t("board.metrics.rewardPoints")}: {item.rewardPoints}
@@ -1305,6 +1357,18 @@ ORDER BY reward_points DESC, score DESC`,
]} ]}
disabled={!canManage} disabled={!canManage}
/> />
<Select
style={{ width: 180 }}
value={editingList.scoreDisplayMode}
onChange={(scoreDisplayMode: BoardScoreDisplayMode) =>
updateList(activeBoard.id, editingList.id, { scoreDisplayMode })
}
options={[
{ value: "total", label: t("board.scoreDisplayModes.total") },
{ value: "split", label: t("board.scoreDisplayModes.split") },
]}
disabled={!canManage}
/>
</Space> </Space>
<Input.TextArea <Input.TextArea
value={editingList.sql} value={editingList.sql}
+6
View File
@@ -660,8 +660,14 @@
"card": "Card View", "card": "Card View",
"grid": "Grid View" "grid": "Grid View"
}, },
"scoreDisplayModes": {
"total": "Show Total Score",
"split": "Show Add & Deduct"
},
"metrics": { "metrics": {
"totalScore": "Score", "totalScore": "Score",
"addScore": "Added",
"deductScore": "Deducted",
"rewardPoints": "Reward", "rewardPoints": "Reward",
"weekChange": "7d", "weekChange": "7d",
"weekDeducted": "7d Deduct", "weekDeducted": "7d Deduct",
+6
View File
@@ -660,8 +660,14 @@
"card": "卡片视图", "card": "卡片视图",
"grid": "网格视图" "grid": "网格视图"
}, },
"scoreDisplayModes": {
"total": "显示总分",
"split": "显示加分与扣分"
},
"metrics": { "metrics": {
"totalScore": "总分", "totalScore": "总分",
"addScore": "加分",
"deductScore": "扣分",
"rewardPoints": "奖励分", "rewardPoints": "奖励分",
"weekChange": "近7天", "weekChange": "近7天",
"weekDeducted": "近7天扣分", "weekDeducted": "近7天扣分",