프론트엔드/Nuxt
VUE + Quill.js 에디터 #Editor #에디터 #vue
짱구를왜말려?
2023. 6. 14. 16:48
반응형
SMALL
1. cdn 연결하기
* nuxt ssr일 경우 nuxt.config.js가 아니라 default.vue 레이아웃에다 직접 링크 넣어야됨
(다른 레이아웃 파일 사용 시 그 파일에다 삽입)
@ default.vue
<template>
<div id="app">
<header-vue />
<pop />
<section class="flex">
<aside class="left-side"></aside>
<transition name="page" mode="out-in">
<Nuxt />
</transition>
</section>
<footer-vue />
</div>
</template>
<script>
export default {
head() {
return {
link: [
{rel: 'stylesheet', type: 'text/css', href: '//cdn.quilljs.com/1.3.4/quill.snow.css'},
],
script: [
{
src: '//cdn.quilljs.com/1.3.6/quill.min.js',
defer: true
},
],
}
},
}
</script>
2. 프론트 세팅
@ InputEditor.js
<template>
<div class="ql-editor">
<div id="editor" ref="editor" style="height:400px;"></div>
<input type="file" id="getFile" accept="image/*" style="position: absolute; z-index:-1; opacity:0; left:-1000px; bottom:-1000px;" @change="changeImg">
</div>
</template>
<script>
export default {
components: {},
props: {
default: "",
required: {
default: true
},
},
data() {
return {
value: this.default,
editor: "",
}
},
methods: {
changeImg(event){
let formData = new FormData();
formData.append("file", event.target.files[0]);
this.$axios.post("/api/images", formData)
.then(response => {
let url = response.data.data;
const range = this.editor.getSelection();
this.editor.insertEmbed(range.index, 'image', url);
/*this.editor.root.innerHTML += `<img src="${response.data.data}" alt=""/>`
return response.data;*/
});
},
changeContents() {
this.$emit("change", this.editor.root.innerHTML);
},
imageHandler() {
document.getElementById('getFile').click();
}
},
mounted() {
let self = this;
const toolbarOptions = [
['bold', 'italic', 'underline', 'strike'],
['blockquote', 'code-block'],
[{'header': 1}, {'header': 2}],
[{'list': 'ordered'}, {'list': 'bullet'}],
[{'script': 'sub'}, {'script': 'super'}],
[{'indent': '-1'}, {'indent': '+1'}],
[{'direction': 'rtl'}],
[{'size': ['small', false, 'large', 'huge']}],
[{'header': [1, 2, 3, 4, 5, 6, false]}],
[{'color': []}, {'background': []}],
[{'font': []}],
[{'align': []}],
['clean', 'image']
]
this.editor = new Quill(this.$refs.editor, {
modules: {
toolbar: {
container: toolbarOptions,
handlers: {
'image' : self.imageHandler
}
}
},
theme: 'snow',
})
this.editor.on("text-change", () => {
this.changeContents();
})
this.editor.pasteHTML(this.default);
// this.$refs.editor.quill.setContents(this.default);
// this.$store.commit('setQuillInstance', quill)
},
watch: {
value: function (value, oldValue) {
this.$emit("change", value);
}
}
}
</script>
@ example.vue
<template>
<input-editor :default="form.description" @change="(data) => {form.description = data}"/>
</template>
<script>
import Form from "@/utils/Form";
export default {
data(){
return {
form: new Form(this.$axios, {
description: "",
})
}
},
}
</script>
3. 백엔드 세팅
@ Image.php (migration도 만들되, 컬럼은 id만 있으면 됨)
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
use Spatie\MediaLibrary\HasMedia;
use Spatie\MediaLibrary\InteractsWithMedia;
class Image extends Model implements HasMedia
{
use HasFactory, InteractsWithMedia;
protected $appends = [
"file",
];
public function registerMediaCollections():void
{
$this->addMediaCollection('file')->singleFile();
}
public function getFileAttribute()
{
if($this->hasMedia('file')) {
$media = $this->getMedia('file')[0];
return [
"name" => $media->file_name,
"url" => $media->getFullUrl()
];
}
return null;
}
}
@ api.php
Route::post("/images", [\App\Http\Controllers\Api\ImageController::class, "store"]);
@ Api/ImageController.php
<?php
namespace App\Http\Controllers\Api;
use App\Http\Controllers\Controller;
use App\Models\Image;
use Illuminate\Http\Request;
class ImageController extends ApiController
{
public function store(Request $request)
{
$request->validate([
"file" => "required"
]);
$image = Image::create();
$image->addMedia($request->file)->toMediaCollection("file", "s3");
$media = $image->getMedia('file')[0];
return $this->respondSuccessfully($media->getFullUrl());
}
}
LIST