fix: 调整奖励兑换页面交互细节

This commit is contained in:
JSR
2026-04-17 20:50:03 +08:00
parent 0bcd41a704
commit b458a10deb
+77 -8
View File
@@ -35,6 +35,14 @@ export const RewardExchange: React.FC<{ canEdit: boolean }> = ({ canEdit }) => {
const [exchangeMode, setExchangeMode] = useState(false) const [exchangeMode, setExchangeMode] = useState(false)
const [selectedStudent, setSelectedStudent] = useState<StudentItem | null>(null) const [selectedStudent, setSelectedStudent] = useState<StudentItem | null>(null)
const [chooseRewardVisible, setChooseRewardVisible] = useState(false) const [chooseRewardVisible, setChooseRewardVisible] = useState(false)
const [modalMorphStage, setModalMorphStage] = useState<"idle" | "measuring" | "enter">("idle")
const [modalMorphVars, setModalMorphVars] = useState<{
fromX: number
fromY: number
scaleX: number
scaleY: number
} | null>(null)
const [originCardRect, setOriginCardRect] = useState<DOMRect | null>(null)
const [messageApi, contextHolder] = message.useMessage() const [messageApi, contextHolder] = message.useMessage()
const emitDataUpdated = () => { const emitDataUpdated = () => {
@@ -90,16 +98,75 @@ export const RewardExchange: React.FC<{ canEdit: boolean }> = ({ canEdit }) => {
return rewards.filter((r) => r.cost_points <= selectedStudent.reward_points) return rewards.filter((r) => r.cost_points <= selectedStudent.reward_points)
}, [rewards, selectedStudent]) }, [rewards, selectedStudent])
const handleStudentClick = (student: StudentItem) => { const handleStudentClick = (student: StudentItem, event: React.MouseEvent<HTMLDivElement>) => {
if (!exchangeMode) return if (!exchangeMode) return
if (!canEdit) { if (!canEdit) {
messageApi.error(t("common.readOnly")) messageApi.error(t("common.readOnly"))
return return
} }
const cardBody = event.currentTarget.querySelector(".ant-card-body") as HTMLElement | null
const sourceRect = (cardBody ?? event.currentTarget).getBoundingClientRect()
setOriginCardRect(sourceRect)
setModalMorphVars(null)
setModalMorphStage("measuring")
setSelectedStudent(student) setSelectedStudent(student)
setChooseRewardVisible(true) setChooseRewardVisible(true)
} }
useEffect(() => {
if (!chooseRewardVisible || !originCardRect || modalMorphStage !== "measuring") {
return
}
const frame = window.requestAnimationFrame(() => {
const modalEl = document.querySelector(".ss-reward-morph-modal.ant-modal") as HTMLElement | null
if (!modalEl) {
setModalMorphStage("idle")
return
}
const modalRect = modalEl.getBoundingClientRect()
const fromX =
originCardRect.left + originCardRect.width / 2 - (modalRect.left + modalRect.width / 2)
const fromY =
originCardRect.top + originCardRect.height / 2 - (modalRect.top + modalRect.height / 2)
const scaleX = Math.min(Math.max(originCardRect.width / modalRect.width, 0.28), 1)
const scaleY = Math.min(Math.max(originCardRect.height / modalRect.height, 0.2), 1)
setModalMorphVars({ fromX, fromY, scaleX, scaleY })
window.requestAnimationFrame(() => {
setModalMorphStage("enter")
})
})
return () => {
window.cancelAnimationFrame(frame)
}
}, [chooseRewardVisible, modalMorphStage, originCardRect])
const resetModalState = () => {
setChooseRewardVisible(false)
setSelectedStudent(null)
setModalMorphStage("idle")
setModalMorphVars(null)
setOriginCardRect(null)
}
const modalClassName =
modalMorphStage === "measuring"
? "ss-reward-morph-modal is-preparing"
: modalMorphStage === "enter"
? "ss-reward-morph-modal is-enter"
: "ss-reward-morph-modal"
const modalStyle = {
"--ss-modal-from-x": `${modalMorphVars?.fromX ?? 0}px`,
"--ss-modal-from-y": `${modalMorphVars?.fromY ?? 0}px`,
"--ss-modal-scale-x": `${modalMorphVars?.scaleX ?? 1}`,
"--ss-modal-scale-y": `${modalMorphVars?.scaleY ?? 1}`,
} as React.CSSProperties
const handleRedeem = async (reward: RewardItem) => { const handleRedeem = async (reward: RewardItem) => {
if (!(window as any).api || !selectedStudent) return if (!(window as any).api || !selectedStudent) return
if (!canEdit) { if (!canEdit) {
@@ -120,8 +187,7 @@ export const RewardExchange: React.FC<{ canEdit: boolean }> = ({ canEdit }) => {
points: reward.cost_points, points: reward.cost_points,
}) })
) )
setChooseRewardVisible(false) resetModalState()
setSelectedStudent(null)
setExchangeMode(false) setExchangeMode(false)
fetchData() fetchData()
emitDataUpdated() emitDataUpdated()
@@ -199,7 +265,7 @@ export const RewardExchange: React.FC<{ canEdit: boolean }> = ({ canEdit }) => {
<Card <Card
key={student.id} key={student.id}
hoverable={exchangeMode} hoverable={exchangeMode}
onClick={() => handleStudentClick(student)} onClick={(event) => handleStudentClick(student, event)}
style={{ style={{
cursor: exchangeMode ? "pointer" : "default", cursor: exchangeMode ? "pointer" : "default",
border: exchangeMode border: exchangeMode
@@ -234,11 +300,14 @@ export const RewardExchange: React.FC<{ canEdit: boolean }> = ({ canEdit }) => {
<Modal <Modal
title={t("rewardExchange.chooseRewardTitle", { name: selectedStudent?.name || "" })} title={t("rewardExchange.chooseRewardTitle", { name: selectedStudent?.name || "" })}
open={chooseRewardVisible} open={chooseRewardVisible}
onCancel={() => { onCancel={resetModalState}
setChooseRewardVisible(false)
setSelectedStudent(null)
}}
footer={null} footer={null}
className={modalClassName}
rootClassName="ss-reward-morph-root"
wrapClassName="ss-reward-morph-wrap"
style={modalStyle}
transitionName="ss-reward-noop-motion"
maskTransitionName="ss-reward-noop-motion"
destroyOnHidden destroyOnHidden
> >
{!selectedStudent ? null : ( {!selectedStudent ? null : (