mirror of
https://github.com/SECTL/SecScore.git
synced 2026-07-19 21:14:21 +08:00
fix: 调整奖励兑换页面交互细节
This commit is contained in:
@@ -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 : (
|
||||||
|
|||||||
Reference in New Issue
Block a user