지금 tailtales라는 특수동물 커뮤니티를 미니 프로젝트를 만들고 있는데,
이번 게시판 작성에서는 VueQuill 이라는 텍스트 에디터를 한번 적용해보려고 한다.
😑기존의 게시판 폼

<template>
<Breadcrumb breadcrumb="board" />
<h2 class="text-xl font-semibold leading-tight text-gray-700">글 작성</h2>
<div class="mt-5">
<form @submit.prevent="submitForm" class="space-y-4">
<div>
<label for="category" class="block text-gray-700 text-sm font-bold mb-2 sr-only">카테고리:</label>
<select id="category" v-model="formData.category" class="shadow appearance-none border rounded w-full py-2 px-3 text-gray-700 leading-tight focus:outline-none focus:shadow-outline">
<option value="" disabled selected>카테고리</option>
<option v-for="option in categoryOptions" :key="option.value" :value="option.value">
{{ option.label }}
</option>
</select>
</div>
<div>
<input type="text" id="title" v-model="formData.title" placeholder="제목을 입력하세요" required class="shadow appearance-none border rounded w-full py-2 px-3 text-gray-700 leading-tight focus:outline-none focus:shadow-outline">
</div>
<div>
<textarea id="content" v-model="formData.content" rows="10" required class="shadow appearance-none border rounded w-full py-2 px-3 text-gray-700 leading-tight focus:outline-none focus:shadow-outline"></textarea>
</div>
<div class="bg-gray-100 p-2 rounded-md mb-4 flex items-center space-x-2">
<label for="fileInput" class="flex items-center cursor-pointer">
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 6v6m0 0v6m0-6h6m-6 0H6"></path></svg>
첨부파일
</label>
<input type="file" id="fileInput" @change="handleFileChange" class="hidden" multiple>
<div v-if="formData.files && formData.files.length > 0" class="mt-2">
<div class="flex space-x-2">
<div v-for="(file, index) in formData.files" :key="index" class="relative">
<img v-if="file.preview" :src="file.preview" alt="미리보기" class="w-20 h-20 object-cover rounded border">
<span v-else>{{ file.name }}</span>
<button type="button" @click="removeFile(index)" class="absolute top-0 right-0 bg-gray-300 hover:bg-gray-400 text-gray-800 text-xs rounded-full w-5 h-5 flex items-center justify-center">
<svg class="w-3 h-3" fill="none" stroke="currentColor" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12"></path></svg>
</button>
</div>
</div>
</div>
</div>
<div class="flex justify-end space-x-2">
<button type="submit" class="px-4 py-2 font-medium tracking-wide text-white capitalize transition-colors duration-200 transform bg-indigo-600 rounded-md hover:bg-indigo-500 focus:outline-none focus:bg-indigo-500">완료</button>
</div>
</form>
</div>
</template>
<script lang="ts" setup>
import Breadcrumb from '../../partials/AppBreadcrumb.vue';
import { ref } from 'vue';
interface UploadedFile {
file: File;
name: string;
preview?: string;
}
const categoryOptions = ref([
{ value: 'notice', label: '공지' },
{ value: 'free', label: '자유게시판' },
{ value: 'question', label: '질문' },
]);
const formData = ref({
category: '',
title: '',
content: '',
files: [] as UploadedFile[], // 변경: 여러 파일 저장을 위한 배열
});
const handleFileChange = (event: Event) => {
const target = event.target as HTMLInputElement;
const files = Array.from(target.files || []);
formData.value.files = []; // 기존 파일 목록 초기화
files.forEach((file: File) => {
const uploadedFile: UploadedFile = { file: file, name: file.name }; // 파일 이름 저장
formData.value.files.push(uploadedFile);
if (file.type.startsWith('image/')) {
uploadedFile.preview = URL.createObjectURL(file);
}
});
};
const removeFile = (index: number) => {
formData.value.files.splice(index, 1);
};
const submitForm = () => {
console.log(formData.value);
alert('글이 작성되었습니다!');
formData.value.title = '';
formData.value.content = '';
formData.value.files = [];
};
</script>
- 그냥 단순히 글을 작성하고 첨부파일을 따로 첨부받고 있어 글자를 꾸민다거나 중간중간 이미지 삽입 등을 할 수 없음!
그럼 순서대로 적용해보자!
NPM
우선 터미널에 라이브러리 설치하기
npm install @vueup/vue-quill@latest --save
Main.ts
그리고 main파일에 아래 코드를 추가해 import 해준다.
여기서 snow는 테마 중 하나라서 원하는 걸 넣으면 됨. (bubble테마도 있다.)
import { QuillEditor } from "@vueup/vue-quill";
import '@vueup/vue-quill/dist/vue-quill.snow.css';
app.component("QuilEditor", QuillEditor);
toolbarOptions.ts
텍스트 에디터를 커스텀해 사용하기 위해 option 값들을 정한 파일을 만들어준다.
import { ref } from 'vue';
const toolbarOptions = ref([
[{ 'header': [1, 2, 3, 4, 5, 6, false] }],
[{ 'font': [] }],
[{ 'align': [] }],
[{ 'indent': '-1'}, { 'indent': '+1' }],
['bold', 'italic', 'underline', 'strike'],
['blockquote', 'code-block'],
[{ 'script': 'sub'}, { 'script': 'super' }],
[{ 'list': 'ordered'}, { 'list': 'bullet' }],
[{ 'size': ['small', false, 'large', 'huge'] }],
[{ 'color': [] }, { 'background': [] }],
['link', 'image', 'video'],
['clean']
]);
export default toolbarOptions;
AppBoardWrite.vue
기존의 <textarea>태그를 QuilEditor로 바꿔주고 마운트 됐을 때 getQuill() 메서드를 호출해서 사용
<templete>
<QuillEditor :toolbar="toolbarOptions" theme="snow" v-model="formData.content" ref="quillEditorRef"/>
</templete>
<script lang="ts" setup>
import { QuillEditor } from '@vueup/vue-quill';
import toolbarOptions from '@/hooks/toolbarOptions';
const quillEditorRef = ref<InstanceType<typeof QuillEditor> | null>(null);
const quillInstance = ref<any | null>(null);
onMounted(() => {
if (quillEditorRef.value) {
quillInstance.value = quillEditorRef.value.getQuill();
}
});
</script>
😊 바뀐 게시판 폼

AppBoardWrite.vue
formData.content에 html태그 그대로 저장되게 해놔서 나중에 조회할 때는 v-html속성에 넣어줘야 함
<template>
<Breadcrumb breadcrumb="board" />
<h2 class="text-xl font-semibold leading-tight text-gray-700">글 작성</h2>
<div class="mt-5">
<form @submit.prevent="submitForm" class="space-y-4">
<div>
<select id="category" v-model="formData.category" class="shadow appearance-none border rounded w-full py-2 px-3 text-gray-700 leading-tight focus:outline-none focus:shadow-outline">
<option value="" disabled selected>카테고리</option>
<option v-for="option in categoryOptions" :key="option.value" :value="option.value">
{{ option.label }}
</option>
</select>
</div>
<div>
<input type="text" id="title" v-model="formData.title" placeholder="제목을 입력하세요" required class="shadow appearance-none border rounded w-full py-2 px-3 text-gray-700 leading-tight focus:outline-none focus:shadow-outline">
</div>
<div class="shadow appearance-none border rounded w-full 2xl:h-72 md:h-64 py-3 px-3 bg-white">
<div class="md:h-4/6 2xl:h-5/6"><QuillEditor :toolbar="toolbarOptions" theme="snow" v-model="formData.content" ref="quillEditorRef"/></div>
</div>
<div class="flex justify-end space-x-2">
<button type="submit" class="px-4 py-2 font-medium tracking-wide text-white capitalize transition-colors duration-200 transform bg-indigo-600 rounded-md hover:bg-indigo-500 focus:outline-none focus:bg-indigo-500">완료</button>
</div>
</form>
</div>
</template>
<script lang="ts" setup>
import { QuillEditor } from '@vueup/vue-quill';
import Breadcrumb from '../../partials/AppBreadcrumb.vue';
import { ref, onMounted } from 'vue';
import toolbarOptions from '@/hooks/toolbarOptions';
const categoryOptions = ref([
{ value: 'notice', label: '공지' },
{ value: 'free', label: '자유게시판' },
{ value: 'question', label: '질문' },
]);
const formData = ref({
category: '',
title: '',
content: '',
});
const quillEditorRef = ref<InstanceType<typeof QuillEditor> | null>(null);
const quillInstance = ref<any | null>(null);
onMounted(() => {
if (quillEditorRef.value) {
quillInstance.value = quillEditorRef.value.getQuill();
console.log('Quill Instance:', quillInstance.value);
}
});
const getEditorHTML = () => {
if (quillInstance.value) {
formData.value.content = quillInstance.value.root.innerHTML; // 여전히 root.innerHTML로 접근 가능한지 확인 필요
console.log('HTML Content:', formData.value.content);
}
};
const submitForm = () => {
getEditorHTML();
console.log(formData.value);
alert('글이 작성되었습니다!');
formData.value.title = '';
formData.value.content = '';
};
</script>
참고 - https://vueup.github.io/vue-quill/
VueQuill
VueQuill Rich Text Editor for Vue 3
vueup.github.io