[perf] Refactor component

This commit is contained in:
He
2026-06-16 19:14:39 +08:00
parent b86716e952
commit 89acd1f261
8 changed files with 137 additions and 165 deletions
+55 -83
View File
@@ -1,23 +1,25 @@
"use client"
import { useState, useCallback } from "react"
import { useState, useCallback, useMemo } from "react"
export type Mode = "single" | "multi"
function getRandomDistinct(total: number, count: number, forbidSet: Set<number> = new Set()): number[] | null {
if (count > total - forbidSet.size) return null
const result: number[] = []
const selected = new Set<number>()
let attempts = 0
while (selected.size < count && attempts < 100000) {
const rand = Math.floor(Math.random() * total) + 1
if (!forbidSet.has(rand) && !selected.has(rand)) {
selected.add(rand)
result.push(rand)
}
attempts++
// 构建可用数字数组
const available: number[] = []
for (let i = 1; i <= total; i++) {
if (!forbidSet.has(i)) available.push(i)
}
return selected.size === count ? result : null
if (count > available.length) return null
// Fisher-Yates 洗牌算法
for (let i = available.length - 1; i > 0; i--) {
const j = Math.floor(Math.random() * (i + 1))
;[available[i], available[j]] = [available[j], available[i]]
}
return available.slice(0, count)
}
function generateGroups(
@@ -54,9 +56,9 @@ function generateGroups(
export interface RollCallState {
mode: Mode
totalTasks: number | string
pickCount: number | string
rounds: number | string
totalTasks: number
pickCount: number
rounds: number
allowRepeat: boolean
// Multi mode
groups: { numbers: number[]; colorIndex: number }[]
@@ -72,14 +74,13 @@ export interface RollCallState {
// Common
isRolling: boolean
errorMsg: string
placeholder: string | undefined
}
export interface RollCallActions {
setMode: (v: Mode) => void
setTotalTasks: (v: number | string) => void
setPickCount: (v: number | string) => void
setRounds: (v: number | string) => void
setTotalTasks: (v: number) => void
setPickCount: (v: number) => void
setRounds: (v: number) => void
setAllowRepeat: (v: boolean) => void
setSingleAllowRepeat: (v: boolean) => void
handleRoll: () => void
@@ -113,9 +114,9 @@ export const COLORS = [
export function useRollCallLogic(): [RollCallState, RollCallActions] {
const [mode, setMode] = useState<Mode>("single")
const [totalTasks, setTotalTasks] = useState<number | string>(35)
const [pickCount, setPickCount] = useState<number | string>(1)
const [rounds, setRounds] = useState<number | string>(1)
const [totalTasks, setTotalTasks] = useState<number>(35)
const [pickCount, setPickCount] = useState<number>(1)
const [rounds, setRounds] = useState<number>(1)
const [allowRepeat, setAllowRepeat] = useState(false)
// Multi mode state
const [groups, setGroups] = useState<{ numbers: number[]; colorIndex: number }[]>([])
@@ -123,7 +124,6 @@ export function useRollCallLogic(): [RollCallState, RollCallActions] {
const [usedAll, setUsedAll] = useState<Set<number>>(new Set())
const [hasResult, setHasResult] = useState(false)
// Single mode state
const [selectedRecords, setSelectedRecords] = useState<{ number: number; colorIndex: number }[]>([])
const [singleBatches, setSingleBatches] = useState<{ numbers: number[]; colorIndex: number; batchNumber: number }[]>([])
const [singleHighlighted, setSingleHighlighted] = useState<number[]>([])
const [isSingleComplete, setIsSingleComplete] = useState(false)
@@ -131,39 +131,38 @@ export function useRollCallLogic(): [RollCallState, RollCallActions] {
// Common state
const [isRolling, setIsRolling] = useState(false)
const [errorMsg, setErrorMsg] = useState("")
const [placeholder, setPlaceholder] = useState<string | undefined>()
// 从 singleBatches 计算 selectedRecords,消除数据冗余
const selectedRecords = useMemo(() => {
return singleBatches.flatMap(batch =>
batch.numbers.map(n => ({ number: n, colorIndex: batch.colorIndex }))
)
}, [singleBatches])
const resetAllState = useCallback(() => {
setHighlighted(new Set())
setUsedAll(new Set())
setGroups([])
setHasResult(false)
setSingleBatches([])
setSingleHighlighted([])
setIsSingleComplete(false)
setErrorMsg("")
}, [])
const handleModeChange = useCallback((newMode: Mode) => {
setMode(newMode)
setHighlighted(new Set())
setUsedAll(new Set())
setGroups([])
setHasResult(false)
setSelectedRecords([])
setSingleBatches([])
setSingleHighlighted([])
setIsSingleComplete(false)
setErrorMsg("")
setPlaceholder(undefined)
}, [])
resetAllState()
}, [resetAllState])
const handleTotalChange = useCallback((v: number | string) => {
const handleTotalChange = useCallback((v: number) => {
setTotalTasks(v)
setHighlighted(new Set())
setUsedAll(new Set())
setGroups([])
setHasResult(false)
setSelectedRecords([])
setSingleBatches([])
setSingleHighlighted([])
setIsSingleComplete(false)
setErrorMsg("")
setPlaceholder(undefined)
}, [])
resetAllState()
}, [resetAllState])
const handleRoundsChange = useCallback((v: number | string) => {
const handleRoundsChange = useCallback((v: number) => {
setRounds(v)
if (typeof v === 'number' && v < 2) setAllowRepeat(false)
if (v < 2) setAllowRepeat(false)
setHighlighted(new Set())
setGroups([])
setHasResult(false)
@@ -173,21 +172,11 @@ export function useRollCallLogic(): [RollCallState, RollCallActions] {
const handleRoll = useCallback(async () => {
setErrorMsg("")
// 检查空值
if (totalTasks === "" || pickCount === "" || rounds === "") {
setErrorMsg("请填写所有参数")
return
}
const total = totalTasks as number
const pick = pickCount as number
const roundCount = rounds as number
setIsRolling(true)
await new Promise((r) => setTimeout(r, 240))
const effectiveRepeat = roundCount < 2 ? false : allowRepeat
const result = generateGroups(total, pick, roundCount, effectiveRepeat)
const effectiveRepeat = rounds < 2 ? false : allowRepeat
const result = generateGroups(totalTasks, pickCount, rounds, effectiveRepeat)
if (!result.success) {
setErrorMsg(result.errorMsg)
@@ -205,37 +194,25 @@ export function useRollCallLogic(): [RollCallState, RollCallActions] {
}
setHasResult(true)
setIsRolling(false)
setPlaceholder(undefined)
}, [totalTasks, pickCount, rounds, allowRepeat])
const handleSingleRoll = useCallback(async () => {
setErrorMsg("")
// 检查空值
if (totalTasks === "" || pickCount === "") {
setErrorMsg("请填写所有参数")
return
}
const total = totalTasks as number
const pick = pickCount as number
setIsRolling(true)
setIsSingleComplete(false)
await new Promise((r) => setTimeout(r, 240))
const usedNumbers = new Set(selectedRecords.map(r => r.number))
// 如果不允许重复,检查剩余人数
if (!singleAllowRepeat && pick > total - usedNumbers.size) {
setErrorMsg(`剩余可选人数不足 ${pick}`)
if (!singleAllowRepeat && pickCount > totalTasks - usedNumbers.size) {
setErrorMsg(`剩余可选人数不足 ${pickCount}`)
setIsRolling(false)
return
}
// 允许重复时,不需要检查人数
const forbidSet = singleAllowRepeat ? new Set<number>() : usedNumbers
const nums = getRandomDistinct(total, pick, forbidSet)
const nums = getRandomDistinct(totalTasks, pickCount, forbidSet)
if (!nums) {
setErrorMsg("抽取失败,人数不足")
setIsRolling(false)
@@ -244,10 +221,8 @@ export function useRollCallLogic(): [RollCallState, RollCallActions] {
const newColorIndex = singleBatches.length % COLORS.length
const newBatchNumber = singleBatches.length + 1
const newRecords = [...selectedRecords, ...nums.map(n => ({ number: n, colorIndex: newColorIndex }))]
const newBatch = { numbers: nums, colorIndex: newColorIndex, batchNumber: newBatchNumber }
setSelectedRecords(newRecords)
setSingleBatches([...singleBatches, newBatch])
setSingleHighlighted(nums)
setIsSingleComplete(true)
@@ -255,7 +230,6 @@ export function useRollCallLogic(): [RollCallState, RollCallActions] {
}, [totalTasks, pickCount, selectedRecords, singleBatches, singleAllowRepeat])
const resetSingle = useCallback(() => {
setSelectedRecords([])
setSingleBatches([])
setSingleHighlighted([])
setIsSingleComplete(false)
@@ -267,7 +241,6 @@ export function useRollCallLogic(): [RollCallState, RollCallActions] {
setHighlighted(new Set())
setUsedAll(new Set())
setHasResult(false)
setPlaceholder(undefined)
}, [])
const getBallState = useCallback((n: number): "idle" | "highlighted" | "used" => {
@@ -322,13 +295,12 @@ export function useRollCallLogic(): [RollCallState, RollCallActions] {
singleAllowRepeat,
isRolling,
errorMsg,
placeholder,
}
const actions: RollCallActions = {
setMode: handleModeChange,
setTotalTasks: handleTotalChange,
setPickCount: (v: number | string) => { setPickCount(v); setErrorMsg("") },
setPickCount: (v: number) => { setPickCount(v); setErrorMsg("") },
setRounds: handleRoundsChange,
setAllowRepeat,
setSingleAllowRepeat,