From ce9567d1da0c6db3c1c985bccc715bb484decfab Mon Sep 17 00:00:00 2001 From: zhongyingmin Date: Tue, 18 Aug 2026 17:24:43 +0800 Subject: [PATCH] =?UTF-8?q?refactor:=20=E6=A8=AA=E5=B1=8F=E6=8F=90?= =?UTF-8?q?=E7=A4=BA=E4=B8=8E=E7=BC=A9=E6=94=BE=E6=94=B9=E4=B8=BA=E7=BB=84?= =?UTF-8?q?=E4=BB=B6=E6=A0=87=E7=AD=BE=EF=BC=88OrientationGuard=EF=BC=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude Opus 4.8 (1M context) --- src/App.tsx | 18 +++----- .../common/LandscapePrompt/index.ts | 1 - src/components/common/index.ts | 1 - src/course-runtime/canvas/useCanvasScale.ts | 22 +++++++--- .../orientation/OrientationGuard.tsx} | 16 ++++++- .../orientation/OrientationGuard.types.ts | 6 +++ src/course-runtime/orientation/index.ts | 3 ++ .../orientation}/useOrientation.ts | 0 src/pages/Components/DragDropDemo.tsx | 4 +- src/pages/Components/index.tsx | 4 +- src/utils/index.ts | 2 - src/utils/useScale.ts | 43 ------------------- 12 files changed, 50 insertions(+), 70 deletions(-) delete mode 100644 src/components/common/LandscapePrompt/index.ts rename src/{components/common/LandscapePrompt/LandscapePrompt.tsx => course-runtime/orientation/OrientationGuard.tsx} (61%) create mode 100644 src/course-runtime/orientation/OrientationGuard.types.ts create mode 100644 src/course-runtime/orientation/index.ts rename src/{utils => course-runtime/orientation}/useOrientation.ts (100%) delete mode 100644 src/utils/useScale.ts diff --git a/src/App.tsx b/src/App.tsx index 0d72b33..60a5b63 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -1,17 +1,13 @@ import { useEffect } from "react"; import { Outlet } from "react-router-dom"; +import { appConfig } from "@/app.config"; import { ToastProvider, ToastViewport } from "@/components/ui/toast"; import { CourseCanvas, useCourseCanvas } from "@/course-runtime/canvas"; +import { OrientationGuard } from "@/course-runtime/orientation"; import { Navigation } from "./components/business"; -import { LandscapePrompt } from "./components/common"; import { registerAppPortalRoot } from "./components/common/canvasPortal"; import { useUserStore } from "./stores"; -import { - DESIGN_HEIGHT, - DESIGN_WIDTH, - isVideoAjaxAvailable, - useOrientation, -} from "./utils"; +import { isVideoAjaxAvailable } from "./utils"; function CourseShell() { const { portalContainer } = useCourseCanvas(); @@ -30,8 +26,6 @@ function CourseShell() { } function App() { - const isPortrait = useOrientation(); - useEffect(() => { const params = new URLSearchParams(window.location.search); const courseId = params.get("courseId") ?? params.get("courseid"); @@ -69,10 +63,10 @@ function App() { return ( <> - {isPortrait && } + diff --git a/src/components/common/LandscapePrompt/index.ts b/src/components/common/LandscapePrompt/index.ts deleted file mode 100644 index de3275f..0000000 --- a/src/components/common/LandscapePrompt/index.ts +++ /dev/null @@ -1 +0,0 @@ -export { LandscapePrompt } from "./LandscapePrompt"; diff --git a/src/components/common/index.ts b/src/components/common/index.ts index 9c24490..cae59bd 100644 --- a/src/components/common/index.ts +++ b/src/components/common/index.ts @@ -31,5 +31,4 @@ export type { SpotlightController, } from "./GuideSpotlight"; export { defineSpotlight, GuideSpotlight } from "./GuideSpotlight"; -export { LandscapePrompt } from "./LandscapePrompt"; export { RouteError, RouteFallback } from "./RouteFallback"; diff --git a/src/course-runtime/canvas/useCanvasScale.ts b/src/course-runtime/canvas/useCanvasScale.ts index ee19078..b32ad3d 100644 --- a/src/course-runtime/canvas/useCanvasScale.ts +++ b/src/course-runtime/canvas/useCanvasScale.ts @@ -6,17 +6,25 @@ interface CanvasScaleOptions { maxScale: number; } +function getViewportSize() { + if (typeof window === "undefined") { + return null; + } + return { + width: window.visualViewport?.width ?? window.innerWidth, + height: window.visualViewport?.height ?? window.innerHeight, + }; +} + function calculateScale({ width, height, maxScale, }: CanvasScaleOptions): number { if (typeof window === "undefined") return maxScale; - return Math.min( - window.innerWidth / width, - window.innerHeight / height, - maxScale, - ); + const viewport = getViewportSize(); + if (!viewport) return maxScale; + return Math.min(viewport.width / width, viewport.height / height, maxScale); } export function useCanvasScale({ @@ -33,11 +41,15 @@ export function useCanvasScale({ setScale(calculateScale({ width, height, maxScale })); updateScale(); window.addEventListener("resize", updateScale); + window.addEventListener("orientationchange", updateScale); window.visualViewport?.addEventListener("resize", updateScale); + window.visualViewport?.addEventListener("scroll", updateScale); return () => { window.removeEventListener("resize", updateScale); + window.removeEventListener("orientationchange", updateScale); window.visualViewport?.removeEventListener("resize", updateScale); + window.visualViewport?.removeEventListener("scroll", updateScale); }; }, [height, maxScale, width]); diff --git a/src/components/common/LandscapePrompt/LandscapePrompt.tsx b/src/course-runtime/orientation/OrientationGuard.tsx similarity index 61% rename from src/components/common/LandscapePrompt/LandscapePrompt.tsx rename to src/course-runtime/orientation/OrientationGuard.tsx index 97a7d8d..a4c7ce9 100644 --- a/src/components/common/LandscapePrompt/LandscapePrompt.tsx +++ b/src/course-runtime/orientation/OrientationGuard.tsx @@ -1,4 +1,7 @@ -export function LandscapePrompt() { +import type { OrientationGuardProps } from "./OrientationGuard.types"; +import { useOrientation } from "./useOrientation"; + +function DefaultLandscapePrompt() { return (
@@ -17,9 +20,18 @@ export function LandscapePrompt() {

请旋转屏幕

- 为获得最佳体验,请横屏浏览 + 为获得最佳体验,请横屏浏览

); } + +export function OrientationGuard({ renderPrompt }: OrientationGuardProps) { + const isPortrait = useOrientation(); + + if (!isPortrait) return null; + if (renderPrompt) return <>{renderPrompt()}; + + return ; +} diff --git a/src/course-runtime/orientation/OrientationGuard.types.ts b/src/course-runtime/orientation/OrientationGuard.types.ts new file mode 100644 index 0000000..104d7eb --- /dev/null +++ b/src/course-runtime/orientation/OrientationGuard.types.ts @@ -0,0 +1,6 @@ +import type { ReactNode } from "react"; + +export interface OrientationGuardProps { + /** 竖屏时渲染的自定义提示;不传则用内置的横屏提示 */ + renderPrompt?: () => ReactNode; +} diff --git a/src/course-runtime/orientation/index.ts b/src/course-runtime/orientation/index.ts new file mode 100644 index 0000000..a06129a --- /dev/null +++ b/src/course-runtime/orientation/index.ts @@ -0,0 +1,3 @@ +export { OrientationGuard } from "./OrientationGuard"; +export type { OrientationGuardProps } from "./OrientationGuard.types"; +export { useOrientation } from "./useOrientation"; diff --git a/src/utils/useOrientation.ts b/src/course-runtime/orientation/useOrientation.ts similarity index 100% rename from src/utils/useOrientation.ts rename to src/course-runtime/orientation/useOrientation.ts diff --git a/src/pages/Components/DragDropDemo.tsx b/src/pages/Components/DragDropDemo.tsx index bce1119..39d1b2e 100644 --- a/src/pages/Components/DragDropDemo.tsx +++ b/src/pages/Components/DragDropDemo.tsx @@ -6,8 +6,8 @@ import { Droppable, } from "@/components/course-interactions/drag-drop"; import { Button } from "@/components/ui/button"; +import { useCourseCanvas } from "@/course-runtime/canvas"; import { cn } from "@/lib/cn"; -import { useScale } from "@/utils"; import type { DragDemoItem, DragDemoZone, @@ -153,7 +153,7 @@ const renderPlacedItem = (item: DragDemoItem) => ( ); export function DragDropDemo() { - const scale = useScale(); + const { scale } = useCourseCanvas(); const [dropResult, setDropResult] = useState(null); const [placedItemIds, setPlacedItemIds] = useState>( {}, diff --git a/src/pages/Components/index.tsx b/src/pages/Components/index.tsx index e257fc3..8797164 100644 --- a/src/pages/Components/index.tsx +++ b/src/pages/Components/index.tsx @@ -5,8 +5,8 @@ import { Button } from "@/components/ui/button"; import { Overlay, type OverlayProps } from "@/components/ui/overlay"; import { Portal } from "@/components/ui/portal"; import { useToast } from "@/components/ui/toast"; +import { useCourseCanvas } from "@/course-runtime/canvas"; import { useSpotlightStore } from "@/stores"; -import { useScale } from "@/utils"; import { DragDropDemo } from "./DragDropDemo"; type OverlayDemoKey = @@ -113,7 +113,7 @@ export default function Components() { ? customRootRef : getAppPortalRoot; const toast = useToast(); - const scale = useScale(); + const { scale } = useCourseCanvas(); const startGuideSpotlight = () => { useSpotlightStore.getState().startSpotlight({ diff --git a/src/utils/index.ts b/src/utils/index.ts index 1385745..9a1ac78 100644 --- a/src/utils/index.ts +++ b/src/utils/index.ts @@ -1,6 +1,4 @@ export * from "./assets"; export * from "./lazyRoute"; export * from "./tailwind"; -export * from "./useOrientation"; -export * from "./useScale"; export * from "./yiban"; diff --git a/src/utils/useScale.ts b/src/utils/useScale.ts deleted file mode 100644 index 920a954..0000000 --- a/src/utils/useScale.ts +++ /dev/null @@ -1,43 +0,0 @@ -import { useCallback, useEffect, useState } from "react"; -import { appConfig } from "@/app.config"; - -export const DESIGN_WIDTH = appConfig.design.width; -export const DESIGN_HEIGHT = appConfig.design.height; - -function getViewportSize() { - if (typeof window === "undefined") { - return { width: DESIGN_WIDTH, height: DESIGN_HEIGHT }; - } - return { - width: window.visualViewport?.width ?? window.innerWidth, - height: window.visualViewport?.height ?? window.innerHeight, - }; -} - -export function useScale() { - const calculateScale = useCallback(() => { - const viewport = getViewportSize(); - return Math.min( - viewport.width / DESIGN_WIDTH, - viewport.height / DESIGN_HEIGHT, - appConfig.scale.max, - ); - }, []); - const [scale, setScale] = useState(calculateScale); - - useEffect(() => { - const updateScale = () => setScale(calculateScale()); - window.addEventListener("resize", updateScale); - window.addEventListener("orientationchange", updateScale); - window.visualViewport?.addEventListener("resize", updateScale); - window.visualViewport?.addEventListener("scroll", updateScale); - return () => { - window.removeEventListener("resize", updateScale); - window.removeEventListener("orientationchange", updateScale); - window.visualViewport?.removeEventListener("resize", updateScale); - window.visualViewport?.removeEventListener("scroll", updateScale); - }; - }, [calculateScale]); - - return scale; -}