This commit is contained in:
khj0414 2025-02-07 14:45:17 +09:00
parent 854c0c91e9
commit f40e3ed7d1
3 changed files with 43 additions and 22 deletions

View File

@ -57,25 +57,32 @@ import Quill from 'quill';
import 'quill/dist/quill.snow.css'; import 'quill/dist/quill.snow.css';
import { onMounted, ref, watch, defineEmits, defineProps } from 'vue'; import { onMounted, ref, watch, defineEmits, defineProps } from 'vue';
import $api from '@api'; import $api from '@api';
const props = defineProps({ const props = defineProps({
isAlert: { isAlert: {
type: Boolean, type: Boolean,
default: false, default: false,
}, },
}); });
const editor = ref(null);
const font = ref('nanum-gothic'); const editor = ref(null); // DOM
const fontSize = ref('16px'); const font = ref('nanum-gothic'); //
const fontSize = ref('16px'); //
const emit = defineEmits(['update:data']); const emit = defineEmits(['update:data']);
onMounted(() => { onMounted(() => {
//
const Font = Quill.import('formats/font'); const Font = Quill.import('formats/font');
Font.whitelist = ['nanum-gothic', 'd2coding', 'consolas', 'serif', 'monospace']; Font.whitelist = ['nanum-gothic', 'd2coding', 'consolas', 'serif', 'monospace'];
Quill.register(Font, true); Quill.register(Font, true);
//
const Size = Quill.import('attributors/style/size'); const Size = Quill.import('attributors/style/size');
Size.whitelist = ['12px', '14px', '16px', '18px', '24px', '32px', '48px']; Size.whitelist = ['12px', '14px', '16px', '18px', '24px', '32px', '48px'];
Quill.register(Size, true); Quill.register(Size, true);
// Quill
const quillInstance = new Quill(editor.value, { const quillInstance = new Quill(editor.value, {
theme: 'snow', theme: 'snow',
placeholder: '내용을 입력해주세요...', placeholder: '내용을 입력해주세요...',
@ -86,74 +93,84 @@ onMounted(() => {
syntax: true, syntax: true,
}, },
}); });
//
quillInstance.format('font', font.value); quillInstance.format('font', font.value);
quillInstance.format('size', fontSize.value); quillInstance.format('size', fontSize.value);
//
quillInstance.on('text-change', () => { quillInstance.on('text-change', () => {
const delta = quillInstance.getContents(); // Get Delta format const delta = quillInstance.getContents(); // Delta
emit('update:data', delta); emit('update:data', delta);
}); });
//
watch([font, fontSize], () => { watch([font, fontSize], () => {
quillInstance.format('font', font.value); quillInstance.format('font', font.value);
quillInstance.format('size', fontSize.value); quillInstance.format('size', fontSize.value);
}); });
// Handle image upload //
let imageUrls = new Set(); let imageUrls = new Set(); // URL
quillInstance.getModule('toolbar').addHandler('image', () => { quillInstance.getModule('toolbar').addHandler('image', () => {
selectLocalImage(); selectLocalImage(); //
}); });
//
quillInstance.on('text-change', (delta, oldDelta, source) => { quillInstance.on('text-change', (delta, oldDelta, source) => {
// Emit Delta when content changes
emit('update:data', quillInstance.getContents()); emit('update:data', quillInstance.getContents());
delta.ops.forEach(op => { delta.ops.forEach(op => {
if (op.insert && typeof op.insert === 'object' && op.insert.image) { if (op.insert && typeof op.insert === 'object' && op.insert.image) {
const imageUrl = op.insert.image; const imageUrl = op.insert.image; // URL
imageUrls.add(imageUrl); imageUrls.add(imageUrl); // URL
} else if (op.delete) { } else if (op.delete) {
checkForDeletedImages(); checkForDeletedImages(); //
} }
}); });
}); });
//
async function selectLocalImage() { async function selectLocalImage() {
const input = document.createElement('input'); const input = document.createElement('input');
input.setAttribute('type', 'file'); input.setAttribute('type', 'file');
input.setAttribute('accept', 'image/*'); input.setAttribute('accept', 'image/*');
input.click(); input.click(); //
input.onchange = () => { input.onchange = () => {
const file = input.files[0]; const file = input.files[0];
if (file) { if (file) {
const formData = new FormData(); const formData = new FormData();
formData.append('file', file); formData.append('file', file);
// URL
uploadImageToServer(formData).then(serverImageUrl => { uploadImageToServer(formData).then(serverImageUrl => {
const baseUrl = $api.defaults.baseURL.replace(/api\/$/, ''); const baseUrl = $api.defaults.baseURL.replace(/api\/$/, '');
const fullImageUrl = `${baseUrl}${serverImageUrl.replace(/\\/g, '/')}`; const fullImageUrl = `${baseUrl}${serverImageUrl.replace(/\\/g, '/')}`;
const range = quillInstance.getSelection(); const range = quillInstance.getSelection();
quillInstance.insertEmbed(range.index, 'image', fullImageUrl); quillInstance.insertEmbed(range.index, 'image', fullImageUrl); //
imageUrls.add(fullImageUrl); imageUrls.add(fullImageUrl); // URL
}).catch(e => { }).catch(e => {
toastStore.onToast('잠시후 다시 시도해주세요.', 'e'); toastStore.onToast('잠시후 다시 시도해주세요.', 'e');
}); });
} }
}; };
} }
//
async function uploadImageToServer(formData) { async function uploadImageToServer(formData) {
try { try {
const response = await $api.post('quilleditor/upload', formData, { isFormData: true }); const response = await $api.post('quilleditor/upload', formData, { isFormData: true });
const imageUrl = response.data.data; const imageUrl = response.data.data;
return imageUrl; return imageUrl; // URL
} catch (error) { } catch (error) {
toastStore.onToast('잠시후 다시 시도해주세요.', 'e'); toastStore.onToast('잠시후 다시 시도해주세요.', 'e');
throw error; throw error;
} }
} }
//
function checkForDeletedImages() { function checkForDeletedImages() {
const editorImages = document.querySelectorAll('#editor img'); const editorImages = document.querySelectorAll('#editor img');
const currentImages = new Set(Array.from(editorImages).map(img => img.src)); const currentImages = new Set(Array.from(editorImages).map(img => img.src)); //
imageUrls.forEach(url => { imageUrls.forEach(url => {
if (!currentImages.has(url)) { if (!currentImages.has(url)) {

View File

@ -14,7 +14,7 @@
> >
<img <img
class="rounded-circle user-avatar" class="rounded-circle user-avatar"
:src="`http://localhost:10325/upload/img/profile/${user.MEMBERPRF}`" :src="`${baseUrl}upload/img/profile/${user.MEMBERPRF}`"
alt="user" alt="user"
:style="{ borderColor: user.usercolor}" :style="{ borderColor: user.usercolor}"
/> />
@ -25,10 +25,12 @@
<script setup> <script setup>
import { onMounted, ref, nextTick } from 'vue'; import { onMounted, ref, nextTick } from 'vue';
import { useUserStore } from '@s/userList'; import { useUserStore } from '@s/userList';
import $api from '@api';
const emit = defineEmits(); const emit = defineEmits();
const userStore = useUserStore(); const userStore = useUserStore();
const userList = ref([]); const userList = ref([]);
const baseUrl = $api.defaults.baseURL.replace(/api\/$/, '');
// //
onMounted(async () => { onMounted(async () => {

View File

@ -33,18 +33,20 @@
<script setup> <script setup>
import EditBtn from '@/components/button/EditBtn.vue'; import EditBtn from '@/components/button/EditBtn.vue';
import $api from '@api';
// Props // Props
defineProps({ const props = defineProps({
item: { item: {
type: Object, type: Object,
required: true, required: true,
}, },
}); });
const baseUrl = $api.defaults.baseURL.replace(/api\/$/, '');
// //
const formatDate = (dateString) => new Date(dateString).toLocaleString(); const formatDate = (dateString) => new Date(dateString).toLocaleString();
// //
const getProfileImage = (imagePath) => const getProfileImage = (imagePath) =>
imagePath ? `/img/avatars/${imagePath}` : '/img/avatars/default-Profile.jpg'; imagePath ? `${baseUrl}upload/img/profile/${imagePath}` : '/img/avatars/default-Profile.jpg';
</script> </script>