全栈开发

nextjs使用wangEditor

1、富文本编辑器

RichEditor.tsx

"use client";

import "@wangeditor-next/editor/dist/css/style.css";
import { Editor, Toolbar } from "@wangeditor-next/editor-for-react";
import { IDomEditor, IEditorConfig, IToolbarConfig } from "@wangeditor-next/editor";
import { useEffect, useState } from "react";

interface RichEditorProps {
  content: string;
  onChange: (html: string) => void;
}

export default function RichEditor({ content, onChange }: RichEditorProps) {
  const [editor, setEditor] = useState<IDomEditor | null>(null);

  const toolbarConfig: Partial<IToolbarConfig> = {};
  const editorConfig: Partial<IEditorConfig> = {
    placeholder: "请输入内容...",
    autoFocus: false,
  };

  const handleCreated = (editorInstance: IDomEditor) => {
    setEditor(editorInstance);
    editorInstance.setHtml(content || "<p></p>");
  };

  const handleChange = (editorInstance: IDomEditor) => {
    const html = editorInstance.getHtml();
    onChange(html);
  };

  useEffect(() => {
    return () => {
      if (editor) {
        editor.destroy();
        setEditor(null);
      }
    };
  }, [editor]);

  return (
    <div style={{ border: "1px solid #ccc", zIndex: 100 }}>
      <Toolbar
        editor={editor}
        defaultConfig={toolbarConfig}
        mode="default"
        style={{ borderBottom: "1px solid #ccc" }}
      />
      <Editor
        value={content}
        defaultConfig={editorConfig}
        onCreated={handleCreated}
        onChange={handleChange}
        mode="default"
        style={{ height: "500px", overflowY: "hidden" }}
      />
    </div>
  );
}

2、封装进表单组件

"use client";

import { FormProvider, useForm } from "react-hook-form";
import { zodResolver } from "@hookform/resolvers/zod";
import {
    createDocumentDefaultValues,
    createDocumentSchema,
    CreateDocumentSchema,
} from "@/app/(protected)/dashboard/documents/_types/form-schema";
import { ControlledInput } from "@/components/controlled/controlled-input";
import dynamic from "next/dynamic";
import { Button } from "@/components/ui/button";
import { useEffect, useState } from "react";

const RichEditor = dynamic(() => import("@/components/rich-editor"), {
    ssr: false,
    loading: () => <p className="text-sm text-muted-foreground">加载编辑器...</p>,
});

interface DocumentFormProps {
    mode: "create" | "edit";
    initialData?: {
        title: string;
        content: string;
    };
    isPending?: boolean;
    onSubmit: (data: { title: string; content: string }) => void;
}

export function DocumentForm({
    mode,
    initialData,
    isPending,
    onSubmit,
}: DocumentFormProps) {
    const form = useForm<CreateDocumentSchema>({
        defaultValues: createDocumentDefaultValues,
        resolver: zodResolver(createDocumentSchema),
    });

    const [content, setContent] = useState("");

    /** 编辑模式:回填 */
    useEffect(() => {
        if (mode === "edit" && initialData) {
            form.reset({ title: initialData.title });
            setTimeout(() => {
                setContent(initialData.content)
            }, 50);
        }
    }, [mode, initialData, form]);

    const handleSubmit = (data: CreateDocumentSchema) => {
        onSubmit({
            title: data.title,
            content,
        });
    };

    return (
        <FormProvider {...form}>
            <form
                onSubmit={form.handleSubmit(handleSubmit)}
                className="space-y-6 max-w-4xl mx-auto"
            >
                {/* 标题 */}
                <ControlledInput<CreateDocumentSchema>
                    name="title"
                    label="标题"
                    placeholder="请输入文档标题"
                />

                {/* 内容 */}
                <div className="space-y-2">
                    <label className="text-sm font-medium">内容</label>
                    <RichEditor content={content} onChange={setContent} />
                </div>

                <div className="flex justify-end">
                    <Button type="submit" disabled={isPending}>
                        {mode === "edit" ? "更新文档" : "创建文档"}
                    </Button>
                </div>
            </form>
        </FormProvider>
    );
}

这里的form-schema.ts

import { requiredStringSchema } from "@/lib/zod-schemas";
import z from "zod";

// 创建文档表单
export const createDocumentSchema = z.object({
    title: requiredStringSchema,
});

export type CreateDocumentSchema = z.infer<typeof createDocumentSchema>;

export const createDocumentDefaultValues: CreateDocumentSchema = {
    title: "",
};

// 更新文档表单
export const updateDocumentSchema = z.object({
    id: z.string(),
    title: requiredStringSchema,
});

export type UpdateDocumentSchema = z.infer<typeof updateDocumentSchema>;

export const updateDocumentDefaultValues: UpdateDocumentSchema = {
    id: "",
    title: "",
};