Merge branch 'main' into vacation

This commit is contained in:
dyhj625 2025-04-08 09:52:07 +09:00
commit 9af35ff2d8
15 changed files with 142 additions and 35 deletions

View File

@ -819,3 +819,7 @@ input:checked + .slider:before {
.font-bold { .font-bold {
font-weight: bold; font-weight: bold;
} }
.pointer {
cursor: pointer;
}

View File

@ -27,10 +27,16 @@
<!-- 수정, 삭제 버튼 --> <!-- 수정, 삭제 버튼 -->
<template v-if="!isDeletedComment && (unknown || isCommentAuthor || isAuthor)"> <template v-if="!isDeletedComment && (unknown || isCommentAuthor || isAuthor)">
<div class="float-end ms-1"> <div class="float-end ms-1">
<slot name="gobackBtn"></slot>
<EditButton @click.stop="editClick" :is-pushed="isEditPushed" /> <EditButton @click.stop="editClick" :is-pushed="isEditPushed" />
<DeleteButton :class="'ms-1'" @click.stop="deleteClick" :is-pushed="isDeletePushed" /> <DeleteButton :class="'ms-1'" @click.stop="deleteClick" :is-pushed="isDeletePushed" />
</div> </div>
</template> </template>
<template v-else>
<div class="float-end ms-1">
<slot name="gobackBtn"></slot>
</div>
</template>
<!-- 좋아요, 싫어요 버튼 (댓글에서만 표시) --> <!-- 좋아요, 싫어요 버튼 (댓글에서만 표시) -->
<BoardRecommendBtn <BoardRecommendBtn

View File

@ -4,7 +4,7 @@
<div class="row g-0"> <div class="row g-0">
<div class="col-3 border-end text-center" id="app-calendar-sidebar"> <div class="col-3 border-end text-center" id="app-calendar-sidebar">
<div class="card-body"> <div class="card-body">
<img v-if="user" :src="`${baseUrl}upload/img/profile/${user.profile}`" alt="Profile Image" class="w-px-50 h-px-50 rounded-circle" @error="$event.target.src = '/img/icons/icon.png'"/> <img v-if="user" :src="`${baseUrl}upload/img/profile/${user.profile}`" alt="Profile Image" class="w-px-50 h-px-50 rounded-circle object-fit-contain" @error="$event.target.src = '/img/icons/icon.png'"/>
<p class="mt-2 fw-bold"> <p class="mt-2 fw-bold">
{{ user.name }} {{ user.name }}
</p> </p>
@ -60,7 +60,7 @@
<div class="row my-2 d-flex align-items-center"> <div class="row my-2 d-flex align-items-center">
<div class="col-4"> <div class="col-4">
<img :src="`${baseUrl}upload/img/profile/${commuter.profile}`" <img :src="`${baseUrl}upload/img/profile/${commuter.profile}`"
class="rounded-circle me-2 w-px-50 h-px-50" class="me-2 w-px-50 h-px-50 rounded-circle object-fit-contain"
@error="$event.target.src = '/img/icons/icon.png'"> @error="$event.target.src = '/img/icons/icon.png'">
<span class="fw-bold">{{ commuter.memberName }}</span> <span class="fw-bold">{{ commuter.memberName }}</span>
@ -399,7 +399,7 @@ const loadCommuters = async () => {
// //
const profileImg = document.createElement('img'); const profileImg = document.createElement('img');
profileImg.src = `${baseUrl}upload/img/profile/${commuter.profile}`; profileImg.src = `${baseUrl}upload/img/profile/${commuter.profile}`;
profileImg.className = 'rounded-circle w-px-20 h-px-20 mx-1 mb-1 position-relative z-5 m-auto'; profileImg.className = 'rounded-circle w-px-20 h-px-20 mx-1 mb-1 position-relative z-5 m-auto object-fit-contain';
profileImg.style.border = `2px solid ${commuter.projctcolor}`; profileImg.style.border = `2px solid ${commuter.projctcolor}`;
profileImg.onerror = () => { profileImg.src = '/img/icons/icon.png'; }; profileImg.onerror = () => { profileImg.src = '/img/icons/icon.png'; };

View File

@ -23,7 +23,7 @@
<img <img
:src="`${baseUrl}upload/img/profile/${commuter.profile}`" :src="`${baseUrl}upload/img/profile/${commuter.profile}`"
alt="User Profile" alt="User Profile"
class="rounded-circle" class="rounded-circle object-fit-contain"
:class="isCurrentUser(commuter) ? 'cursor-pointer' : ''" :class="isCurrentUser(commuter) ? 'cursor-pointer' : ''"
:draggable="isCurrentUser(commuter)" :draggable="isCurrentUser(commuter)"
@dragstart="isCurrentUser(commuter) ? dragStart($event, post) : null" @dragstart="isCurrentUser(commuter) ? dragStart($event, post) : null"

View File

@ -25,6 +25,7 @@
:dinnerList="dinnerList" :dinnerList="dinnerList"
:teaTimeList="teaTimeList" :teaTimeList="teaTimeList"
:workShopList="workShopList" :workShopList="workShopList"
@handle-click-vacation="handleClickVacation"
/> />
</div> </div>
</div> </div>
@ -60,13 +61,14 @@
</template> </template>
<script setup> <script setup>
import { inject, onMounted, reactive, ref, watch } from 'vue'; import { inject, onMounted, reactive, ref, watch, nextTick } from 'vue';
import { fetchHolidays } from '@c/calendar/holiday'; import { fetchHolidays } from '@c/calendar/holiday';
import { useUserInfoStore } from '@/stores/useUserInfoStore'; import { useUserInfoStore } from '@/stores/useUserInfoStore';
import { useProjectStore } from '@/stores/useProjectStore'; import { useProjectStore } from '@/stores/useProjectStore';
import { useToastStore } from '@s/toastStore'; import { useToastStore } from '@s/toastStore';
import { useWeatherStore } from '@/stores/useWeatherStore'; import { useWeatherStore } from '@/stores/useWeatherStore';
import { storeToRefs } from 'pinia'; import { storeToRefs } from 'pinia';
import router from '@/router';
import FullCalendar from '@fullcalendar/vue3'; import FullCalendar from '@fullcalendar/vue3';
import dayGridPlugin from '@fullcalendar/daygrid'; import dayGridPlugin from '@fullcalendar/daygrid';
import interactionPlugin from '@fullcalendar/interaction'; import interactionPlugin from '@fullcalendar/interaction';
@ -342,7 +344,7 @@
useFilterEventList(month, day); useFilterEventList(month, day);
}; };
// //
const colorToday = e => { const colorToday = e => {
if (todayEL != null && !todayEL.classList.contains('fc-day-today')) todayEL.classList.add('fc-day-today'); if (todayEL != null && !todayEL.classList.contains('fc-day-today')) todayEL.classList.add('fc-day-today');
}; };
@ -504,6 +506,8 @@
selectAllow: selectInfo => isSelectableDate(selectInfo.start), selectAllow: selectInfo => isSelectableDate(selectInfo.start),
dateClick: handleDateClick, dateClick: handleDateClick,
dayCellDidMount: arg => { dayCellDidMount: arg => {
//
addWeatherInfo(arg);
const dateCell = arg.el; const dateCell = arg.el;
// //
@ -534,10 +538,51 @@
}, },
}); });
//
const addWeatherInfo = arg => {
const dateStr = $common.dateFormatter(arg.date, 'YMD');
//
const theDayWeatherInfo = dailyWeatherList.value.find(weather => weather.date === dateStr);
if (theDayWeatherInfo) {
let weatherIconUrl = `https://openweathermap.org/img/wn/${theDayWeatherInfo.icon}.png`;
if (theDayWeatherInfo.icon === '01d' || theDayWeatherInfo.icon === '01n') {
weatherIconUrl = '/img/icons/sunny-custom.png';
}
//
const weatherEl = document.createElement('img');
weatherEl.src = weatherIconUrl;
weatherEl.alt = theDayWeatherInfo.description;
weatherEl.className = 'weather-icon';
weatherEl.style.width = '28px';
weatherEl.style.height = '28px';
//
const dayTopEl = arg.el.querySelector('.fc-daygrid-day-top');
dayTopEl.classList.add('align-items-center');
dayTopEl.prepend(weatherEl); //
}
};
//
watch(dailyWeatherList, async () => {
await nextTick(); // DOM
document.querySelectorAll('.fc-daygrid-day').forEach(dayCell => {
addWeatherInfo({
el: dayCell,
date: dayCell.dataset.date,
});
});
});
const handleWheelEvent = e => { const handleWheelEvent = e => {
handleCloseModal(); handleCloseModal();
}; };
const handleClickVacation = () => {
router.push({ path: 'Vacation' });
};
// ( ) // ( )
watch( watch(
() => fullCalendarRef.value?.getApi().currentData.viewTitle, () => fullCalendarRef.value?.getApi().currentData.viewTitle,
@ -564,12 +609,6 @@
param.append('month', month); param.append('month', month);
param.append('day', day); param.append('day', day);
// if (!weatherStore.dailyWeatherList?.length) {
// await weatherStore.getWeatherInfo();
// //dailyWeatherList.value = weatherStore.dailyWeatherList;
// console.log('dailyWeatherList.value: ', dailyWeatherList.value);
// }
// //
await fetchCategoryList(); await fetchCategoryList();
await fetchEventList(param); await fetchEventList(param);

View File

@ -10,6 +10,8 @@
(category.CMNCODVAL === 300205 && teaTimeList?.length) || (category.CMNCODVAL === 300205 && teaTimeList?.length) ||
(category.CMNCODVAL === 300206 && workShopList?.length) (category.CMNCODVAL === 300206 && workShopList?.length)
" "
@click="category.CMNCODVAL == 300202 ? $emit('handleClickVacation') : ''"
:class="category.CMNCODVAL == 300202 ? 'pointer' : ''"
class="border border-2 mt-3 card p-2" class="border border-2 mt-3 card p-2"
> >
<div class="row g-2 position-relative"> <div class="row g-2 position-relative">
@ -100,6 +102,8 @@
type: Array, type: Array,
}, },
}); });
defineEmits(['handleClickVacation']);
</script> </script>
<style scoped> <style scoped>

View File

@ -14,7 +14,7 @@
:data-bs-original-title="getTooltipTitle(user)" :data-bs-original-title="getTooltipTitle(user)"
> >
<img <img
class="rounded-circle user-avatar border border-3" class="user-avatar border border-3 rounded-circle object-fit-contain"
:class="{ 'grayscaleImg': isUserDisabled(user) }" :class="{ 'grayscaleImg': isUserDisabled(user) }"
:src="`${baseUrl}upload/img/profile/${user.MEMBERPRF}`" :src="`${baseUrl}upload/img/profile/${user.MEMBERPRF}`"
:style="`border-color: ${user.usercolor} !important;`" :style="`border-color: ${user.usercolor} !important;`"

View File

@ -33,7 +33,7 @@
</div> </div>
</div> </div>
</h5> </h5>
<h5 class="mb-1">{{ data.localVote.LOCVOTTTL }} <h5 class="mb-0">{{ data.localVote.LOCVOTTTL }}
<i v-if="yesVotetotal != '0'" class="bx bxs-check-circle link-success"></i> <i v-if="yesVotetotal != '0'" class="bx bxs-check-circle link-success"></i>
</h5> </h5>
<small >{{ data.localVote.formatted_LOCVOTRDT }} ~ {{ data.localVote.formatted_LOCVOTEDT }}</small> <small >{{ data.localVote.formatted_LOCVOTRDT }} ~ {{ data.localVote.formatted_LOCVOTEDT }}</small>

View File

@ -49,7 +49,7 @@
@keyup="ValidHandler('title')" @keyup="ValidHandler('title')"
/> />
<div> <div>
<QEditor class="" @keyup="ValidHandler('content')" @update:data="handleContentUpdate" @update:imageUrls="imageUrls = $event" :is-alert="wordContentAlert" :initialData="contentValue"/> <QEditor class="q-editor-container" @keyup="ValidHandler('content')" @update:data="handleContentUpdate" @update:imageUrls="imageUrls = $event" :is-alert="wordContentAlert" :initialData="contentValue"/>
<div class="text-end mt-5"> <div class="text-end mt-5">
<button class="btn btn-primary" @click="saveWord" :disabled="titleValue ? !changed : false"> <button class="btn btn-primary" @click="saveWord" :disabled="titleValue ? !changed : false">
<i class="bx bx-check"></i> <i class="bx bx-check"></i>
@ -232,3 +232,11 @@ const handleCategoryFocusout = (value) => {
}; };
</script> </script>
<style>
.q-editor-container {
max-width: 100%; /* 영역이 넘치지 않게 */
overflow: auto; /* 넘치는 내용은 스크롤로 처리 */
word-wrap: break-word; /* 긴 단어는 자동으로 줄바꿈 */
white-space: normal; /* 내용이 길어지면 자동으로 줄바꿈 */
}
</style>

View File

@ -177,7 +177,7 @@
v-if="user" v-if="user"
:src="`${baseUrl}upload/img/profile/${user.profile}`" :src="`${baseUrl}upload/img/profile/${user.profile}`"
alt="Profile Image" alt="Profile Image"
class="w-px-40 h-px-40 rounded-circle border border-3" class="w-px-40 h-px-40 rounded-circle border border-3 object-fit-contain"
:style="`border-color: ${user.usercolor} !important;`" :style="`border-color: ${user.usercolor} !important;`"
@error="$event.target.src = '/img/icons/icon.png'" @error="$event.target.src = '/img/icons/icon.png'"
/> />

View File

@ -8,7 +8,7 @@ const routes = [
path: '/', path: '/',
name: 'Home', name: 'Home',
component: () => import('@v/MainView.vue'), component: () => import('@v/MainView.vue'),
meta: { requiresAuth: true } meta: { requiresAuth: true },
}, },
{ {
path: '/board', path: '/board',
@ -22,6 +22,7 @@ const routes = [
}, },
{ {
path: 'write', path: 'write',
name: 'BoardWrite',
component: () => import('@v/board/BoardWrite.vue'), component: () => import('@v/board/BoardWrite.vue'),
}, },
{ {
@ -40,12 +41,13 @@ const routes = [
path: '/mypage', path: '/mypage',
name: 'MyPage', name: 'MyPage',
component: () => import('@v/mypage/MyPage.vue'), component: () => import('@v/mypage/MyPage.vue'),
meta: { requiresAuth: true } meta: { requiresAuth: true },
}, },
{ {
path: '/wordDict', path: '/wordDict',
name: 'WordDict',
component: () => import('@v/wordDict/wordDict.vue'), component: () => import('@v/wordDict/wordDict.vue'),
meta: { requiresAuth: true } meta: { requiresAuth: true },
}, },
{ {
path: '/login', path: '/login',
@ -67,36 +69,43 @@ const routes = [
}, },
{ {
path: '/vacation', path: '/vacation',
name: 'Vacation',
component: () => import('@v/vacation/VacationManagement.vue'), component: () => import('@v/vacation/VacationManagement.vue'),
meta: { requiresAuth: true } meta: { requiresAuth: true },
}, },
{ {
path: '/voteboard', path: '/voteboard',
name: 'VoteBoard',
component: () => import('@v/voteboard/TheVoteBoard.vue'), component: () => import('@v/voteboard/TheVoteBoard.vue'),
meta: { requiresAuth: true }, meta: { requiresAuth: true },
children: [ children: [
{ {
path: '', path: '',
name: 'VoteBoardList',
component: () => import('@v/voteboard/voteBoardList.vue'), component: () => import('@v/voteboard/voteBoardList.vue'),
}, },
{ {
path: 'write', path: 'write',
name: 'VoteboardWrite',
component: () => import('@v/voteboard/voteboardWrite.vue'), component: () => import('@v/voteboard/voteboardWrite.vue'),
}, },
], ],
}, },
{ {
path: '/projectlist', path: '/projectlist',
name: 'Projectlist',
component: () => import('@v/projectlist/TheProjectList.vue'), component: () => import('@v/projectlist/TheProjectList.vue'),
meta: { requiresAuth: true } meta: { requiresAuth: true },
}, },
{ {
path: '/commuters', path: '/commuters',
name: 'Commuters',
component: () => import('@v/commuters/TheCommuters.vue'), component: () => import('@v/commuters/TheCommuters.vue'),
meta: { requiresAuth: true } meta: { requiresAuth: true },
}, },
{ {
path: '/authorization', path: '/authorization',
name: 'Authorization',
component: () => import('@v/admin/TheAuthorization.vue'), component: () => import('@v/admin/TheAuthorization.vue'),
meta: { requiresAuth: true }, meta: { requiresAuth: true },
}, },

View File

@ -72,7 +72,6 @@ export const useWeatherStore = defineStore('weather', () => {
}); });
}; };
// 로컬스토리지 캐시 포함한 로직
const getWeatherInfoWithCache = async () => { const getWeatherInfoWithCache = async () => {
const now = new Date(); const now = new Date();
const pad = n => String(n).padStart(2, '0'); const pad = n => String(n).padStart(2, '0');
@ -86,17 +85,21 @@ export const useWeatherStore = defineStore('weather', () => {
return; return;
} }
// 캐시 삭제 try {
const { weather: w, dailyWeatherList: d } = await getWeatherInfo();
// 기존 캐시 삭제
Object.keys(localStorage).forEach(k => { Object.keys(localStorage).forEach(k => {
if (k.startsWith('weather_')) localStorage.removeItem(k); if (k.startsWith('weather_')) localStorage.removeItem(k);
}); });
try {
const { weather: w, dailyWeatherList: d } = await getWeatherInfo();
localStorage.setItem(key, JSON.stringify({ weather: w, dailyWeatherList: d })); localStorage.setItem(key, JSON.stringify({ weather: w, dailyWeatherList: d }));
} catch (e) { } catch (e) {
// 오류 시 기존 로컬스토리지 값 중 가장 최신 값 사용 console.error('날씨 API 호출 실패, 캐시 fallback 시도 중...');
const oldKey = Object.keys(localStorage).filter(k => k.startsWith('weather_')).sort().pop(); const oldKey = Object.keys(localStorage)
.filter(k => k.startsWith('weather_'))
.sort()
.pop();
if (oldKey) { if (oldKey) {
const fallback = JSON.parse(localStorage.getItem(oldKey)); const fallback = JSON.parse(localStorage.getItem(oldKey));
weather.value = fallback.weather; weather.value = fallback.weather;

View File

@ -190,9 +190,9 @@
searchText: searchText.value, searchText: searchText.value,
showNotice: showNotices.value, showNotice: showNotices.value,
}; };
//localStorage.removeItem
// //
//localStorage.setItem(`boardList_${seq}`, JSON.stringify(query)); localStorage.setItem(`boardList_${seq}`, JSON.stringify(query));
}; };
// //
@ -384,5 +384,4 @@
position: relative; position: relative;
top: -1px; top: -1px;
} }
</style> </style>

View File

@ -21,7 +21,14 @@
:is-delete-pushed="isDeletePushed" :is-delete-pushed="isDeletePushed"
@editClick="editClick" @editClick="editClick"
@deleteClick="deleteClick" @deleteClick="deleteClick"
/> >
<!-- 목록으로 버튼 -->
<template #gobackBtn>
<button class="btn btn-label-primary btn-icon me-1" @click="goList">
<i class="bx bx-left-arrow-alt"></i>
</button>
</template>
</BoardProfile>
<!-- 비밀번호 입력창 (익명일 경우) --> <!-- 비밀번호 입력창 (익명일 경우) -->
<div v-if="isPassword && unknown" class="mt-3 w-px-200 ms-auto"> <div v-if="isPassword && unknown" class="mt-3 w-px-200 ms-auto">
@ -451,6 +458,7 @@
passwordAlert.value = ''; passwordAlert.value = '';
commentAlert.value = ''; commentAlert.value = '';
await fetchComments(); await fetchComments();
activeCommentBtnClass();
} else { } else {
alert('댓글 작성을 실패했습니다.'); alert('댓글 작성을 실패했습니다.');
} }
@ -496,6 +504,9 @@
const isUnknown = unknown?.unknown ?? false; const isUnknown = unknown?.unknown ?? false;
if (isUnknown) { if (isUnknown) {
closeAllEditTextareas();
closeAllPasswordAreas();
activeCommentBtnClass();
togglePassword('delete'); togglePassword('delete');
} else { } else {
deletePost(); deletePost();
@ -581,6 +592,8 @@
// //
const deleteComment = async comment => { const deleteComment = async comment => {
acitveButtonType(); //
closeAllEditTextareas();
const isMyComment = comment.authorId === currentUserId.value; const isMyComment = comment.authorId === currentUserId.value;
// //
@ -686,6 +699,7 @@
isEditPushed.value = false; isEditPushed.value = false;
isDeletePushed.value = false; isDeletePushed.value = false;
lastClickedButton.value = ''; lastClickedButton.value = '';
isPassword.value = false;
} }
}; };
@ -877,6 +891,16 @@
} }
}; };
//
const goList = () => {
//
const getFilter = localStorage.getItem(`boardList_${currentBoardId.value}`);
router.push({
name: 'BoardList',
query: getFilter ? JSON.parse(getFilter) : '',
});
};
// ( ) // ( )
const handleCommentDeleted = deletedCommentId => { const handleCommentDeleted = deletedCommentId => {
// //

View File

@ -31,6 +31,7 @@
<!-- 단어 목록 --> <!-- 단어 목록 -->
<ul v-if="total > 0" class="ms-3 list-unstyled" style="overflow-x: hidden; word-wrap: break-word;"> <ul v-if="total > 0" class="ms-3 list-unstyled" style="overflow-x: hidden; word-wrap: break-word;">
<DictCard <DictCard
class="DictCard q-editor-container"
v-for="item in wordList" v-for="item in wordList"
:key="item.WRDDICSEQ" :key="item.WRDDICSEQ"
:item="item" :item="item"
@ -330,5 +331,15 @@ import { useRoute } from 'vue-router';
top: 5px; top: 5px;
height: fit-content; height: fit-content;
} }
.DictCard {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(300px, 1fr));
gap: 1rem;
}
.q-editor-container {
max-width: 100%; /* 영역이 넘치지 않게 */
overflow: auto; /* 넘치는 내용은 스크롤로 처리 */
word-wrap: break-word; /* 긴 단어는 자동으로 줄바꿈 */
white-space: normal; /* 내용이 길어지면 자동으로 줄바꿈 */
}
</style> </style>