diff --git a/components/home/lovart-home.tsx b/components/home/lovart-home.tsx index 5bee5f718..e11fbb1bf 100644 --- a/components/home/lovart-home.tsx +++ b/components/home/lovart-home.tsx @@ -12,6 +12,7 @@ import { Briefcase, ChevronDown, Clapperboard, + Edit, Folder, Heart, Home, @@ -23,6 +24,7 @@ import { Monitor, Palette, Plus, + PlayCircle, Settings, Sparkles, Star, @@ -46,6 +48,7 @@ import { getCanvasLaunchStorageKey, } from "@/lib/canvas-launch"; import type { MembershipConfig } from "@/lib/membership/config"; +import { ImageAnnotator } from "@/components/image-annotator"; type HomeUser = { id: string; @@ -103,6 +106,16 @@ type NavLink = { toolboxGroup: number; }; +type TutorialVideo = { + id: string; + title: string; + videoUrl: string; + coverUrl?: string | null; + description?: string | null; + order: number; + isEnabled: boolean; +}; + type PublicAuthSettings = { isDingTalkEnabled?: boolean; isGoogleEnabled?: boolean; @@ -320,6 +333,8 @@ export function LovartHome({ const [loginEmail, setLoginEmail] = useState(""); const [loginPassword, setLoginPassword] = useState(""); const [isLoggingIn, setIsLoggingIn] = useState(false); + const [showImageEditor, setShowImageEditor] = useState(false); + const [editorImageUrl, setEditorImageUrl] = useState(""); const [typedPreviewText, setTypedPreviewText] = useState(""); const [previewCursorVisible, setPreviewCursorVisible] = useState(true); const projectSectionRef = useRef(null); @@ -380,6 +395,8 @@ export function LovartHome({ toolbox1Enabled?: boolean; toolbox2Name?: string | null; toolbox2Enabled?: boolean; + tutorialEnabled?: boolean; + tutorialVideoUrl?: string | null; pointsPackages?: PointsPackage[]; dulupayConfig?: { enabled?: boolean; @@ -387,6 +404,11 @@ export function LovartHome({ }; }>("/api/site-settings", fetcher); + const { data: tutorialVideosData } = useSWR<{ videos: TutorialVideo[] }>( + siteSettingsData?.tutorialEnabled ? "/api/tutorial-videos" : null, + fetcher + ); + const { data: authSettingsData } = useSWR( "/api/auth-settings", fetcher @@ -821,6 +843,96 @@ export function LovartHome({ window.location.href = "/api/auth/dingtalk/login"; }; + const handleOpenImageEditor = () => { + if (!isAuthenticated) { + openLoginDialog(); + return; + } + setEditorImageUrl(""); + setShowImageEditor(true); + }; + + const handleImageEditorConfirm = async (annotatedImageUrl: string) => { + let finalUrl = annotatedImageUrl; + + if (annotatedImageUrl.startsWith("data:")) { + try { + const response = await fetch(annotatedImageUrl); + const blob = await response.blob(); + const formData = new FormData(); + formData.append("file", blob, `edited-${Date.now()}.png`); + const uploadResponse = await fetch("/api/files/upload", { + method: "POST", + body: formData, + }); + + if (!uploadResponse.ok) { + toast.error("上传失败"); + return; + } + + const result = await uploadResponse.json(); + finalUrl = result.url; + } catch (_error) { + toast.error("上传失败"); + return; + } + } + + try { + const response = await fetch(finalUrl); + const blob = await response.blob(); + const extension = + blob.type === "image/jpeg" + ? "jpg" + : blob.type === "image/webp" + ? "webp" + : "png"; + const file = new File([blob], `edited-${Date.now()}.${extension}`, { + type: blob.type || "image/png", + }); + + setFiles((current) => [ + ...current, + { + id: `${Date.now()}-${Math.random().toString(36).slice(2, 8)}`, + file, + previewUrl: finalUrl, + }, + ]); + setShowImageEditor(false); + setEditorImageUrl(""); + toast.success("已添加到对话框"); + } catch (_error) { + toast.error("图片处理失败,请重试"); + } + }; + + const handleImageEditorCancel = () => { + setShowImageEditor(false); + setEditorImageUrl(""); + }; + + const handleOpenTutorial = () => { + if (!isAuthenticated) { + openLoginDialog(); + return; + } + + const firstTutorial = + tutorialVideosData?.videos + ?.filter((video) => video.isEnabled !== false && video.videoUrl) + ?.sort((a, b) => a.order - b.order)?.[0] || null; + const tutorialUrl = firstTutorial?.videoUrl || siteSettingsData?.tutorialVideoUrl || ""; + + if (!tutorialUrl) { + toast.info("暂无教程视频"); + return; + } + + window.open(tutorialUrl, "_blank", "noopener,noreferrer"); + }; + const handleCreateDulupayOrder = async (packageId: string) => { if (!isAuthenticated) { openLoginDialog(); @@ -1213,6 +1325,24 @@ export function LovartHome({ > + {siteSettingsData?.tutorialEnabled ? ( + + ) : null} + {siteSettingsData?.toolbox1Enabled !== false && toolbox1Links.length > 0 ? ( @@ -1602,6 +1732,14 @@ export function LovartHome({ + {showImageEditor ? ( + + ) : null} + {loginDialogOpen ? (