diff --git a/index.html b/index.html index 5bd3857..12e3b00 100644 --- a/index.html +++ b/index.html @@ -31,7 +31,7 @@ /> - + @@ -39,8 +39,9 @@ - - + + + diff --git a/public/css/custom-dark.css b/public/css/custom-dark.css new file mode 100644 index 0000000..5be91ed --- /dev/null +++ b/public/css/custom-dark.css @@ -0,0 +1,6 @@ +/* 여기에 dark css 작성 */ + + +.display-block { + display: block !important; +} \ No newline at end of file diff --git a/public/css/custom.css b/public/css/custom.css index 568171a..9f01857 100644 --- a/public/css/custom.css +++ b/public/css/custom.css @@ -1,4 +1,4 @@ -/* 여기에 css 작성 */ +/* 여기에 light css 작성 */ .display-block { diff --git a/src/App.vue b/src/App.vue index f8850a3..1d5f928 100644 --- a/src/App.vue +++ b/src/App.vue @@ -8,35 +8,6 @@ diff --git a/src/common/axios-interceptor.js b/src/common/axios-interceptor.js index b0bd0a0..c286882 100644 --- a/src/common/axios-interceptor.js +++ b/src/common/axios-interceptor.js @@ -1,7 +1,7 @@ import axios from "axios"; import router from "@/router/index"; -const $ = axios.create({ +const $api = axios.create({ baseURL: import.meta.env.VITE_API_URL, timeout: 300000, }) @@ -10,7 +10,7 @@ const $ = axios.create({ * Default Content-Type : json * form 사용 시 옵션에 { isFromDate : true } 추가 */ -$.interceptors.request.use( +$api.interceptors.request.use( function (config) { let contentType = 'application/json'; @@ -28,7 +28,7 @@ $.interceptors.request.use( ); // 응답 인터셉터 추가하기 -$.interceptors.response.use( +$api.interceptors.response.use( function (response) { // 2xx 범위에 있는 상태 코드는 이 함수를 트리거 합니다. // 응답 데이터가 있는 작업 수행 @@ -38,4 +38,4 @@ $.interceptors.response.use( // 응답 오류가 있는 작업 수행 return Promise.reject(error); }); -export default $; +export default $api; diff --git a/src/components/calendar/SampleCalendar.vue b/src/components/calendar/SampleCalendar.vue index 71d68eb..f3362f3 100644 --- a/src/components/calendar/SampleCalendar.vue +++ b/src/components/calendar/SampleCalendar.vue @@ -73,10 +73,11 @@ import FormInput from '../input/FormInput.vue'; import FlatPickr from 'vue-flatpickr-component'; import 'flatpickr/dist/flatpickr.min.css' import '@/assets/css/app-calendar.css' -// 기본만 넣었는데 기능 추가해야할듯 +import { useThemeStore } from '@s/darkmode'; -//dark모드 유무 -const isDarkMode = ref(false); +const { isDarkMode } = useThemeStore(); + +// 기본만 넣었는데 기능 추가해야할듯 const key1 = 'vrkIY7jUy82WRHIozbBtz4n2L89jAqNDY%2B4DmNpmasTex%2FNDySN7FDYwBqPj1p%2BxXLg13BzYfgHPt6eipWhH8Q%3D%3D'; const key2 = 'vrkIY7jUy82WRHIozbBtz4n2L89jAqNDY+4DmNpmasTex/NDySN7FDYwBqPj1p+xXLg13BzYfgHPt6eipWhH8Q=='; @@ -256,7 +257,7 @@ const calendarOptions = reactive({ }); const loadFlatpickrTheme = async () => { - if (isDarkMode.value) { + if (isDarkMode) { await import('flatpickr/dist/themes/dark.css'); } else { await import('flatpickr/dist/themes/light.css'); @@ -265,10 +266,6 @@ const loadFlatpickrTheme = async () => { onMounted(async () => { - const storedStyle = localStorage.getItem('templateCustomizer-vertical-menu-template--Style'); - if (storedStyle === 'dark') { - isDarkMode.value = true; - } await loadFlatpickrTheme(); fetchData(); }); @@ -292,7 +289,22 @@ onMounted(async () => { display: none; } -.flatpickr-calendar{ - width: 100% !important; + +.pt-2.px-3 { + position: relative; /* Flatpickr의 위치 기준이 될 부모 */ +} + +.flatpickr-calendar { + position: relative !important; /* 부모 내부에 맞게 그리기 */ + display: block !important; + width: 100% !important; /* 부모 크기에 맞게 확장 */ + height: auto !important; /* 내용에 따라 높이 조정 */ + z-index: 1; /* 부모 위에만 표시 */ +} + +.flatpickr-rContainer,.dayContainer,.flatpickr-days{ + display: inline-block !important; + width: 100% !important; /* 부모 안에서 전체 너비 차지 */ + box-sizing: border-box !important; /* 패딩 포함 계산 */ } diff --git a/src/components/editor/QEditor.vue b/src/components/editor/QEditor.vue index c141fea..405f621 100644 --- a/src/components/editor/QEditor.vue +++ b/src/components/editor/QEditor.vue @@ -53,6 +53,7 @@ import Quill from 'quill'; import 'quill/dist/quill.snow.css'; import { onMounted, ref, watch, defineEmits } from 'vue'; +import $api from '@/common/axios-interceptor'; const editor = ref(null); const font = ref('nanum-gothic'); @@ -90,7 +91,91 @@ onMounted(() => { quillInstance.format('font', font.value); quillInstance.format('size', fontSize.value); }); + + // 이미지 업로드 및 삭제 감지 로직 + // 아직 서버에 실험 안해봄 ***********처리부탁*********** + let imageUrls = new Set(); + + quillInstance.getModule('toolbar').addHandler('image', () => { + selectLocalImage(); + }); + + quillInstance.on('text-change', (delta, oldDelta, source) => { + emit('update:data', quillInstance.root.innerHTML); + delta.ops.forEach(op => { + if (op.insert && typeof op.insert === 'object' && op.insert.image) { + const imageUrl = op.insert.image; + imageUrls.add(imageUrl); + } else if (op.delete) { + checkForDeletedImages(); + } + }); + }); + + function selectLocalImage() { + const input = document.createElement('input'); + input.setAttribute('type', 'file'); + input.setAttribute('accept', 'image/*'); + input.click(); + + input.onchange = () => { + const file = input.files[0]; + if (file) { + const reader = new FileReader(); + reader.onload = async (e) => { + const range = quillInstance.getSelection(); + const base64Image = e.target.result; + + try { + const serverImageUrl = await uploadImageToServer(base64Image); + quillInstance.insertEmbed(range.index, 'image', serverImageUrl); + imageUrls.add(serverImageUrl); + } catch (error) { + console.error('이미지 업로드 중 오류 발생:', error); + } + }; + reader.readAsDataURL(file); + } + }; + } + + + function checkForDeletedImages() { + const editorImages = document.querySelectorAll('#editor img'); + const currentImages = new Set(Array.from(editorImages).map(img => img.src)); + + imageUrls.forEach(url => { + if (!currentImages.has(url)) { + imageUrls.delete(url); + removeImageFromServer(url); + } + }); + } + + async function uploadImageToServer(base64Image) { + try { + const response = await $api.post('/img/upload', { + image: base64Image, + }); + return response.data.url; // 서버에서 반환한 이미지 URL + } catch (error) { + console.error('서버 업로드 중 오류 발생:', error); + throw error; + } + } + + async function removeImageFromServer(imageUrl) { + try { + await $api.delete('/img/delete', { + data: { url: imageUrl }, + }); + console.log(`서버에서 이미지 삭제: ${imageUrl}`); + } catch (error) { + console.error('서버 이미지 삭제 중 오류 발생:', error); + } + } }); + diff --git a/src/main.js b/src/main.js index b546ff8..14f4f3b 100644 --- a/src/main.js +++ b/src/main.js @@ -10,6 +10,14 @@ pinia.use(piniaPersist) const app = createApp(App) app.use(router) -.use(pinia) -.use(dayjs) -.mount('#app') + .use(pinia) + .use(dayjs) + .mount('#app') + + +if (import.meta.env.MODE === "prod") { + const console = window.console || {}; + console.log = function no_console() { }; // console log 막기 + console.warn = function no_console() { }; // console warning 막기 + console.error = function () { }; // console error 막기 +} diff --git a/src/stores/darkmode.js b/src/stores/darkmode.js new file mode 100644 index 0000000..c33546d --- /dev/null +++ b/src/stores/darkmode.js @@ -0,0 +1,47 @@ +import { ref, computed } from 'vue'; +import { defineStore } from 'pinia'; + +export const useThemeStore = defineStore('theme', () => { + const theme = ref('light'); // 초기 상태는 light + + const isDarkMode = computed(() => theme.value === 'dark'); + + const customClass = '.custom-css'; // custom.css 경로 + const coreClass = '.core-css'; + const themeClass = '.theme-css'; + + const switchToDarkMode = () => { + theme.value = 'dark'; + document.documentElement.classList.add('dark-style'); + document.documentElement.classList.remove('light-style'); + document.querySelector(customClass).setAttribute('href', '/css/custom-dark.css'); + document.querySelector(coreClass).setAttribute('href', '/vendor/css/rtl/core-dark.css'); + document.querySelector(themeClass).setAttribute('href', '/vendor/css/rtl/theme-default-dark.css'); + }; + + const switchToLightMode = () => { + theme.value = 'light'; + document.documentElement.classList.remove('dark-style'); + document.documentElement.classList.add('light-style'); + document.querySelector(customClass).setAttribute('href', '/css/custom.css'); + document.querySelector(coreClass).setAttribute('href', '/vendor/css/rtl/core.css'); + document.querySelector(themeClass).setAttribute('href', '/vendor/css/rtl/theme-default.css'); + }; + + return { + theme, + isDarkMode, + switchToDarkMode, + switchToLightMode, + }; +}, { + persist: { + enabled: true, + strategies: [ + { + storage: localStorage + } + ], + } + +});