新首页侧边栏增加功能提示并修复教程播放
This commit is contained in:
parent
d147524fc3
commit
9cf1762f69
@ -335,6 +335,8 @@ export function LovartHome({
|
||||
const [isLoggingIn, setIsLoggingIn] = useState(false);
|
||||
const [showImageEditor, setShowImageEditor] = useState(false);
|
||||
const [editorImageUrl, setEditorImageUrl] = useState("");
|
||||
const [tutorialDialogOpen, setTutorialDialogOpen] = useState(false);
|
||||
const [currentTutorialVideo, setCurrentTutorialVideo] = useState<TutorialVideo | null>(null);
|
||||
const [typedPreviewText, setTypedPreviewText] = useState("");
|
||||
const [previewCursorVisible, setPreviewCursorVisible] = useState(true);
|
||||
const projectSectionRef = useRef<HTMLDivElement | null>(null);
|
||||
@ -576,6 +578,31 @@ export function LovartHome({
|
||||
.sort((a, b) => a.order - b.order),
|
||||
[navLinksData?.links]
|
||||
);
|
||||
const tutorialVideos = useMemo(() => {
|
||||
const configured = (tutorialVideosData?.videos || [])
|
||||
.filter((video) => video.isEnabled !== false && Boolean(video.videoUrl))
|
||||
.sort((a, b) => a.order - b.order);
|
||||
if (configured.length > 0) {
|
||||
return configured;
|
||||
}
|
||||
|
||||
const fallbackUrl = String(siteSettingsData?.tutorialVideoUrl || "").trim();
|
||||
if (!fallbackUrl) {
|
||||
return [] as TutorialVideo[];
|
||||
}
|
||||
|
||||
return [
|
||||
{
|
||||
id: "fallback-tutorial",
|
||||
title: "功能教程",
|
||||
videoUrl: fallbackUrl,
|
||||
coverUrl: null,
|
||||
description: "默认教程视频",
|
||||
order: 0,
|
||||
isEnabled: true,
|
||||
},
|
||||
];
|
||||
}, [siteSettingsData?.tutorialVideoUrl, tutorialVideosData?.videos]);
|
||||
|
||||
const canUseGoogleLogin = Boolean(
|
||||
authSettingsData?.isGoogleEnabled && authSettingsData?.isGoogleConfigured
|
||||
@ -913,24 +940,13 @@ export function LovartHome({
|
||||
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) {
|
||||
const handleOpenTutorial = (video: TutorialVideo) => {
|
||||
if (!video?.videoUrl) {
|
||||
toast.info("暂无教程视频");
|
||||
return;
|
||||
}
|
||||
|
||||
window.open(tutorialUrl, "_blank", "noopener,noreferrer");
|
||||
setCurrentTutorialVideo(video);
|
||||
setTutorialDialogOpen(true);
|
||||
};
|
||||
|
||||
const handleCreateDulupayOrder = async (packageId: string) => {
|
||||
@ -1290,6 +1306,7 @@ export function LovartHome({
|
||||
type="button"
|
||||
onClick={openNewCanvas}
|
||||
className="flex h-[52px] w-[52px] items-center justify-center rounded-full border border-black/10 bg-white shadow"
|
||||
title="新建项目"
|
||||
>
|
||||
<div className="flex h-8 w-8 items-center justify-center rounded-full bg-black text-white">
|
||||
<Plus className="h-4 w-4" />
|
||||
@ -1301,6 +1318,7 @@ export function LovartHome({
|
||||
type="button"
|
||||
className="flex h-10 w-10 items-center justify-center rounded-full bg-black/5"
|
||||
onClick={() => window.scrollTo({ top: 0, behavior: "smooth" })}
|
||||
title="首页"
|
||||
>
|
||||
<Home className="h-[18px] w-[18px]" />
|
||||
</button>
|
||||
@ -1308,6 +1326,7 @@ export function LovartHome({
|
||||
type="button"
|
||||
className="flex h-10 w-10 items-center justify-center rounded-full hover:bg-black/5"
|
||||
onClick={openAllProjectsInPage}
|
||||
title="项目列表"
|
||||
>
|
||||
<Folder className="h-[18px] w-[18px]" />
|
||||
</button>
|
||||
@ -1315,6 +1334,7 @@ export function LovartHome({
|
||||
type="button"
|
||||
className="flex h-10 w-10 items-center justify-center rounded-full hover:bg-black/5"
|
||||
onClick={() => toast.info("个人主页模块后续开放")}
|
||||
title="个人主页"
|
||||
>
|
||||
<User className="h-[18px] w-[18px]" />
|
||||
</button>
|
||||
@ -1322,18 +1342,63 @@ export function LovartHome({
|
||||
type="button"
|
||||
className="flex h-10 w-10 items-center justify-center rounded-full hover:bg-black/5"
|
||||
onClick={() => toast.info("帮助中心后续开放")}
|
||||
title="帮助中心"
|
||||
>
|
||||
<Info className="h-[18px] w-[18px]" />
|
||||
</button>
|
||||
{siteSettingsData?.tutorialEnabled ? (
|
||||
<button
|
||||
type="button"
|
||||
className="flex h-10 w-10 items-center justify-center rounded-full hover:bg-black/5"
|
||||
onClick={handleOpenTutorial}
|
||||
title="视频教程"
|
||||
>
|
||||
<PlayCircle className="h-[18px] w-[18px]" />
|
||||
</button>
|
||||
<HoverCard openDelay={80} closeDelay={100}>
|
||||
<HoverCardTrigger asChild>
|
||||
<button
|
||||
type="button"
|
||||
className="flex h-10 w-10 items-center justify-center rounded-full hover:bg-black/5"
|
||||
title="视频教程"
|
||||
>
|
||||
<PlayCircle className="h-[18px] w-[18px]" />
|
||||
</button>
|
||||
</HoverCardTrigger>
|
||||
<HoverCardContent side="right" align="start" className="w-[300px] rounded-2xl p-2">
|
||||
<div className="px-2 pb-1 pt-1 text-xs font-medium text-black/50">视频教程</div>
|
||||
<div className="max-h-[380px] space-y-1 overflow-y-auto">
|
||||
{tutorialVideos.length > 0 ? (
|
||||
tutorialVideos.map((video) => (
|
||||
<button
|
||||
key={video.id}
|
||||
type="button"
|
||||
className="flex w-full items-center gap-2.5 rounded-xl p-2 text-left hover:bg-black/[0.04]"
|
||||
onClick={() => handleOpenTutorial(video)}
|
||||
>
|
||||
<div className="h-10 w-16 flex-shrink-0 overflow-hidden rounded-lg bg-black/[0.06]">
|
||||
{video.coverUrl ? (
|
||||
<img
|
||||
src={video.coverUrl}
|
||||
alt={video.title}
|
||||
className="h-full w-full object-cover"
|
||||
/>
|
||||
) : (
|
||||
<div className="flex h-full w-full items-center justify-center">
|
||||
<PlayCircle className="h-4 w-4 text-black/45" />
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
<div className="min-w-0 flex-1">
|
||||
<div className="truncate text-[13px] font-medium text-black/85">
|
||||
{video.title}
|
||||
</div>
|
||||
{video.description ? (
|
||||
<div className="truncate text-[11px] text-black/45">
|
||||
{video.description}
|
||||
</div>
|
||||
) : null}
|
||||
</div>
|
||||
</button>
|
||||
))
|
||||
) : (
|
||||
<div className="px-2 py-3 text-[12px] text-black/45">暂无教程视频</div>
|
||||
)}
|
||||
</div>
|
||||
</HoverCardContent>
|
||||
</HoverCard>
|
||||
) : null}
|
||||
<button
|
||||
type="button"
|
||||
@ -1410,6 +1475,7 @@ export function LovartHome({
|
||||
type="button"
|
||||
className="flex h-10 w-10 items-center justify-center rounded-full hover:bg-black/5"
|
||||
onClick={() => router.push("/settings")}
|
||||
title="系统设置"
|
||||
>
|
||||
<Settings className="h-[18px] w-[18px]" />
|
||||
</button>
|
||||
@ -1737,9 +1803,46 @@ export function LovartHome({
|
||||
imageUrl={editorImageUrl}
|
||||
onConfirm={handleImageEditorConfirm}
|
||||
onCancel={handleImageEditorCancel}
|
||||
onHomeClick={handleImageEditorCancel}
|
||||
/>
|
||||
) : null}
|
||||
|
||||
<Dialog
|
||||
open={tutorialDialogOpen}
|
||||
onOpenChange={(open) => {
|
||||
setTutorialDialogOpen(open);
|
||||
if (!open) {
|
||||
setCurrentTutorialVideo(null);
|
||||
}
|
||||
}}
|
||||
>
|
||||
<DialogContent className="w-[92vw] max-w-[980px] overflow-hidden rounded-[20px] border-black/10 bg-black p-0 text-white">
|
||||
<DialogHeader className="border-b border-white/10 px-4 py-3">
|
||||
<DialogTitle className="truncate text-[14px] font-medium text-white">
|
||||
{currentTutorialVideo?.title || "功能教程"}
|
||||
</DialogTitle>
|
||||
</DialogHeader>
|
||||
<div className="aspect-video w-full bg-black">
|
||||
{currentTutorialVideo?.videoUrl ? (
|
||||
<video
|
||||
key={currentTutorialVideo.videoUrl}
|
||||
className="h-full w-full"
|
||||
controls
|
||||
autoPlay
|
||||
preload="metadata"
|
||||
src={currentTutorialVideo.videoUrl}
|
||||
poster={currentTutorialVideo.coverUrl || undefined}
|
||||
/>
|
||||
) : null}
|
||||
</div>
|
||||
{currentTutorialVideo?.description ? (
|
||||
<div className="px-4 pb-4 pt-3 text-[12px] text-white/70">
|
||||
{currentTutorialVideo.description}
|
||||
</div>
|
||||
) : null}
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
|
||||
{loginDialogOpen ? (
|
||||
<div
|
||||
className="fixed inset-0 z-50 flex items-center justify-center bg-black/85 p-4 backdrop-blur-[1px]"
|
||||
|
||||
@ -31,6 +31,7 @@ interface ImageAnnotatorProps {
|
||||
imageUrl?: string;
|
||||
onConfirm: (annotatedImageUrl: string) => void;
|
||||
onCancel: () => void;
|
||||
onHomeClick?: () => void;
|
||||
initialResizeMode?: boolean; // 是否直接进入尺寸调整模式
|
||||
}
|
||||
|
||||
@ -48,7 +49,7 @@ const COLORS = [
|
||||
|
||||
const LINE_WIDTHS = [2, 4, 6, 8];
|
||||
|
||||
export function ImageAnnotator({ imageUrl, onConfirm, onCancel, initialResizeMode = false }: ImageAnnotatorProps) {
|
||||
export function ImageAnnotator({ imageUrl, onConfirm, onCancel, onHomeClick, initialResizeMode = false }: ImageAnnotatorProps) {
|
||||
const canvasRef = useRef<HTMLCanvasElement>(null);
|
||||
const containerRef = useRef<HTMLDivElement>(null);
|
||||
const [ctx, setCtx] = useState<CanvasRenderingContext2D | null>(null);
|
||||
@ -1384,7 +1385,13 @@ export function ImageAnnotator({ imageUrl, onConfirm, onCancel, initialResizeMod
|
||||
<Link
|
||||
href="/"
|
||||
className="flex items-center gap-2 hover:opacity-80 transition-opacity"
|
||||
onClick={(e) => e.stopPropagation()}
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
if (onHomeClick) {
|
||||
e.preventDefault();
|
||||
onHomeClick();
|
||||
}
|
||||
}}
|
||||
>
|
||||
{siteLogoDark || siteLogo ? (
|
||||
<>
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user