You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在Vue 3的TipTap编辑器中实现图片上传(Laravel Inertia环境)

Laravel Inertia + Vue3 + TipTap 图片上传至数据库实现方案

一、后端 Laravel 实现

1. 定义上传路由

在 routes/web.php 中添加图片上传接口路由:

use App\Http\Controllers\UploadImageController;

Route::post('/upload-image', [UploadImageController::class, 'store'])->name('upload.image');

2. 编写上传控制器

创建 app/Http/Controllers/UploadImageController.php,处理图片验证、存储及响应:

<?php

namespace App\Http\Controllers;

use Illuminate\Http\Request;
use Illuminate\Support\Facades\Storage;

class UploadImageController extends Controller
{
    public function store(Request $request)
    {
        // 验证图片格式和大小
        $request->validate([
            'image' => 'required|image|max:2048', // 限制最大2MB
        ]);

        $image = $request->file('image');
        // 将图片存储到public磁盘的images目录
        $path = $image->store('images', 'public');

        // 若需存入数据库,可创建Image模型并保存记录
        // $imageModel = \App\Models\Image::create([
        //     'path' => $path,
        //     'original_name' => $image->getClientOriginalName(),
        // ]);

        // 返回图片访问URL
        return response()->json([
            'url' => Storage::url($path),
            // 若存了模型,可额外返回模型ID等字段
            // 'id' => $imageModel->id,
        ]);
    }
}

二、前端 Vue3 实现

1. 安装依赖

执行命令安装 TipTap 相关扩展:

npm install @tiptap/vue-3 @tiptap/extension-image @tiptap/extension-dropcursor

2. 编写 TipTap 编辑器组件

创建 Editor.vue 组件,配置图片上传逻辑(支持拖拽、粘贴上传):

<template>
  <div class="editor-wrapper">
    <EditorContent :editor="editor" />
  </div>
</template>

<script setup>
import { useEditor, EditorContent } from '@tiptap/vue-3'
import Image from '@tiptap/extension-image'
import Dropcursor from '@tiptap/extension-dropcursor'
import axios from 'axios'

// 初始化编辑器
const editor = useEditor({
  extensions: [
    Dropcursor.configure({
      color: '#2563eb',
      width: 2,
    }),
    Image.configure({
      inline: false,
      allowBase64: false, // 禁用base64,强制上传到服务器
      // 自定义上传方法
      uploadFile: async (file) => {
        const formData = new FormData()
        formData.append('image', file)

        try {
          const response = await axios.post('/upload-image', formData, {
            headers: {
              'Content-Type': 'multipart/form-data',
              'X-CSRF-TOKEN': document.querySelector('meta[name="csrf-token"]').content,
            },
          })
          return response.data.url
        } catch (err) {
          console.error('图片上传失败:', err)
          throw err
        }
      },
    }),
  ],
  content: '',
  // 编辑器内容更新回调,可传递给父组件
  onUpdate: ({ editor }) => {
    emit('update:content', editor.getHTML())
  },
})

// 处理粘贴图片
editor.value?.on('paste', (event) => {
  const items = Array.from(event.clipboardData?.items || [])
  const imageItem = items.find(item => item.type.startsWith('image/'))

  if (imageItem) {
    event.preventDefault()
    const file = imageItem.getAsFile()
    if (file) {
      // 先插入临时预览图
      editor.value?.chain().focus().setImage({ src: URL.createObjectURL(file) }).run()
      // 上传后替换为真实URL
      Image.options.uploadFile(file).then(url => {
        editor.value?.chain().focus().setImage({ src: url }).run()
      })
    }
  }
})

// 处理拖拽图片
editor.value?.on('drop', (event) => {
  event.preventDefault()
  const files = Array.from(event.dataTransfer?.files || [])
  const imageFile = files.find(file => file.type.startsWith('image/'))

  if (imageFile) {
    editor.value?.chain().focus().setImage({ src: URL.createObjectURL(imageFile) }).run()
    Image.options.uploadFile(imageFile).then(url => {
      editor.value?.chain().focus().setImage({ src: url }).run()
    })
  }
})
</script>

<style scoped>
.editor-wrapper {
  border: 1px solid #e5e7eb;
  border-radius: 6px;
  padding: 1rem;
}

.ProseMirror {
  min-height: 350px;
  outline: none;
  padding: 0.5rem;
}

.ProseMirror img {
  max-width: 100%;
  height: auto;
  margin: 0.5rem 0;
}
</style>

3. 在父组件中使用编辑器

在需要编辑器的页面组件中引入并使用:

<template>
  <div class="form-container">
    <Editor @update:content="updateContent" />
    <button 
      class="mt-4 px-4 py-2 bg-blue-600 text-white rounded"
      @click="submitForm"
    >
      提交内容
    </button>
  </div>
</template>

<script setup>
import Editor from './Editor.vue'
import { useForm } from '@inertiajs/vue3'

const form = useForm({
  content: '',
})

const updateContent = (content) => {
  form.content = content
}

const submitForm = () => {
  form.post('/submit-content', {
    onSuccess: () => {
      alert('内容提交成功')
    },
    onError: () => {
      alert('提交失败,请重试')
    },
  })
}
</script>

内容的提问来源于stack exchange,提问作者Alexande Petrenko

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.18 15:07:21