VueQuillはVue3用のリッチテキストエディタを簡単に実装できるコンポーネントです。
さらに quill-image-uploaderを使用することで、画像を Base64ではなくサーバーにアップロードできます。
このガイドでは、以下の内容を説明します
- VueQuill と Quill-image-uploader のセットアップ
- Laravel を用いた画像アップロード機能の構築
- Vue コンポーネントの構成例
- Laravel コントローラーの処理例
1. 必要なパッケージのインストール
以下のパッケージをインストールします。
terminal
npm install @vueup/vue-quill@latest --save
npm install quill-image-uploader --save
2. VueQuill のセットアップ
QuillEditor.vue
<template>
<div>
<div>
<QuillEditor
v-model:content="localContent"
:options="editorOptions"
contentType="html"
@update:content="handleContentChange"
/>
</div>
</div>
</template>
<script setup lang="ts">
import { ref, onMounted, watch } from 'vue'
import Quill from 'quill'
import { QuillEditor } from '@vueup/vue-quill'
import QuillImageUploader from 'Quill-image-uploader'
import axios from 'axios';
Quill.register('modules/imageUploader', QuillImageUploader)
import '@vueup/vue-quill/dist/vue-quill.snow.css';
const props = defineProps({
initialContent: {
type: String,
default: ''
},
content: {
type: String,
default: ''
},
uploadUrl: {
type: String,
default: '/hogehoge'
}
})
const emit = defineEmits([
'update:content',
'image-uploaded'
])
const localContent = ref(props.initialContent)
const editorOptions = {
theme: 'snow',
modules: {
toolbar: {
//ツールバーカスタム可能
container: [['bold', 'italic', 'underline'], ['image']],
}
//画像をstorageに保存処理
imageUploader: {
upload: async (file) => {
try {
const formData = new FormData();
formData.append('image', file);
const response = await axios.post(props.uploadUrl, formData, {
});
if (response.data && response.data.filePath) {
emit('image-uploaded', {
fileName: file.name,
filePath: response.data.filePath
});
return response.data.filePath;
}
} catch (error) {
console.error('画像アップロードエラー:', error);
throw error;
}
}
}
}
}
// コンテンツ更新時のハンドラー
const handleContentChange = (newContent) => {
emit('update:content', newContent)
}
watch(() => props.content, (newContent) => {
if (newContent !== localContent.value) {
localContent.value = newContent
}
})
onMounted(() => {
// 初期値がある場合は設定
if (props.initialContent) {
localContent.value = props.initialContent
}
})
</script>
ツールバーのカスタム参考サイト
3. エディターをフォームに統合
Form.vue
<template>
<div>
<QuillEditor
v-model:content="editorContent"
:initial-content="initialContent"
@update:content="handleContentUpdate"
@image-uploaded="handleImageUpload"/>
<div>
<v-btn @click.prevent="handleCancel">
キャンセル
</v-btn>
<v-btn. click.prevent="onClick">
作成
</v-btn>
</div>
</template>
<script setup lang="ts">
import { ref, computed, PropType } from 'vue'
import { router } from '@inertiajs/vue3';
import QuillEditor from '@/Components/QuillEditor.vue'
import '@vueup/vue-quill/dist/vue-quill.snow.css';
import 'quill/dist/quill.snow.css';
const props = defineProps({
contents: {
type: String as PropType<string | null>,
default: ''
}
})
// 初期値を計算する
const initialContent = computed(() => {
return props.contents ?? ''
})
const editorContent = ref<string | null>(props.contents ?? null)
const uploadedImages = ref<Array<{ fileName: string, filePath: string }>>([])
// キャンセルボタンを押した時の処理
const handleCancel = () => {
editorContent.value = props.contents ?? ''
}
const handleImageUpload = (imageData: { fileName: string, filePath: string }) => {
uploadedImages.value.push(imageData)
}
const handleContentUpdate = (newContent) => {
editorContent.value = newContent
}
const onClick = () => {
router.post(route('post先URL'), {
content: editorContent.value ?? '',
})
}
</script>
4. Laravel サイドの処理
EditorController
namespace App\Http\Controllers;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Storage;
class EditorController extends Controller
{
public function edit()
{
$contents = EditorContent::first();
return Inertia::render('Editor', ['contents' => $contents->content]);
}
public function uploadImage(Request $request)
{
$request->validate(['image' => 'required|image|mimes:jpeg,png,jpg,gif']);
if ($request->hasFile('image')) {
$file = $request->file('image');
$fileName = uniqid() . '.' . $file->getClientOriginalExtension();
$path = $file->storeAs('images/hoge', $fileName, 'public');
//ファイル名をvueにreturn
return response()->json(['filePath' => Storage::url($path)]);
}
return response()->json(['error' => 'アップロードに失敗しました'], 400);
}
public function create(Request $request)
{
//updateOrCreate
EditorContent::updateOrCreate(['key' => hoge], ['content' => $request->content]);
return redirect()->route('editor.edit');
}
}
imageUploaderはプレーンなjsonを受け取りファイル名をbass64から受け取ったファイル名に上書きするみたいなのでaxiosを使用。
(勉強中のため他に良い方法があるかも)
5. エディターコンテンツを表示
index.vue
<template>
<div class="ql-editor" v-html="content"></div>
</template>
<script setup>
import '@vueup/vue-quill/dist/vue-quill.snow.css';
</script>
6.終わり
VueQuillは簡単に導入できるが、画像がbass64にエンコードされDBが見づらくなるので解決するために、quill-image-uploaderを使用してみました。
画像は挿入するだけで、配置やサイズの変更は出来ないのでモジュール追加が必要です。
参考になれば幸いです。