mirror of
https://github.com/SECTL/SecScore.git
synced 2026-07-19 21:14:21 +08:00
feat: 看板学生列表支持总分与加扣分显示切换
This commit is contained in:
@@ -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}
|
||||||
|
|||||||
@@ -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",
|
||||||
|
|||||||
@@ -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天扣分",
|
||||||
|
|||||||
Reference in New Issue
Block a user