新首页侧栏增加教程与图片编辑按钮
Some checks failed
Lint / build (20) (push) Has been cancelled
Playwright Tests / test (push) Has been cancelled

This commit is contained in:
Your Name 2026-02-15 01:25:01 +08:00
parent df71015401
commit d147524fc3

View File

@ -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<HTMLDivElement | null>(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<PublicAuthSettings>(
"/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({
>
<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>
) : null}
<button
type="button"
className="flex h-10 w-10 items-center justify-center rounded-full hover:bg-black/5"
onClick={handleOpenImageEditor}
title="图片编辑"
>
<Edit className="h-[18px] w-[18px]" />
</button>
{siteSettingsData?.toolbox1Enabled !== false && toolbox1Links.length > 0 ? (
<HoverCard openDelay={80} closeDelay={80}>
<HoverCardTrigger asChild>
@ -1602,6 +1732,14 @@ export function LovartHome({
<ArrowUp className="h-4 w-4" />
</button>
{showImageEditor ? (
<ImageAnnotator
imageUrl={editorImageUrl}
onConfirm={handleImageEditorConfirm}
onCancel={handleImageEditorCancel}
/>
) : null}
{loginDialogOpen ? (
<div
className="fixed inset-0 z-50 flex items-center justify-center bg-black/85 p-4 backdrop-blur-[1px]"