新首页侧栏增加教程与图片编辑按钮
This commit is contained in:
parent
df71015401
commit
d147524fc3
@ -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]"
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user