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-weight: bold;
}
.pointer {
cursor: pointer;
}

View File

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

View File

@ -4,7 +4,7 @@
<div class="row g-0">
<div class="col-3 border-end text-center" id="app-calendar-sidebar">
<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">
{{ user.name }}
</p>
@ -60,7 +60,7 @@
<div class="row my-2 d-flex align-items-center">
<div class="col-4">
<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'">
<span class="fw-bold">{{ commuter.memberName }}</span>
@ -399,7 +399,7 @@ const loadCommuters = async () => {
//
const profileImg = document.createElement('img');
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.onerror = () => { profileImg.src = '/img/icons/icon.png'; };

View File

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

View File

@ -25,6 +25,7 @@
:dinnerList="dinnerList"
:teaTimeList="teaTimeList"
:workShopList="workShopList"
@handle-click-vacation="handleClickVacation"
/>
</div>
</div>
@ -60,13 +61,14 @@
</template>
<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 { useUserInfoStore } from '@/stores/useUserInfoStore';
import { useProjectStore } from '@/stores/useProjectStore';
import { useToastStore } from '@s/toastStore';
import { useWeatherStore } from '@/stores/useWeatherStore';
import { storeToRefs } from 'pinia';
import router from '@/router';
import FullCalendar from '@fullcalendar/vue3';
import dayGridPlugin from '@fullcalendar/daygrid';
import interactionPlugin from '@fullcalendar/interaction';
@ -342,7 +344,7 @@
useFilterEventList(month, day);
};
//
//
const colorToday = e => {
if (todayEL != null && !todayEL.classList.contains('fc-day-today')) todayEL.classList.add('fc-day-today');
};
@ -504,6 +506,8 @@
selectAllow: selectInfo => isSelectableDate(selectInfo.start),
dateClick: handleDateClick,
dayCellDidMount: arg => {
//
addWeatherInfo(arg);
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 => {
handleCloseModal();
};
const handleClickVacation = () => {
router.push({ path: 'Vacation' });
};
// ( )
watch(
() => fullCalendarRef.value?.getApi().currentData.viewTitle,
@ -564,12 +609,6 @@
param.append('month', month);
param.append('day', day);
// if (!weatherStore.dailyWeatherList?.length) {
// await weatherStore.getWeatherInfo();
// //dailyWeatherList.value = weatherStore.dailyWeatherList;
// console.log('dailyWeatherList.value: ', dailyWeatherList.value);
// }
//
await fetchCategoryList();
await fetchEventList(param);

View File

@ -10,6 +10,8 @@
(category.CMNCODVAL === 300205 && teaTimeList?.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"
>
<div class="row g-2 position-relative">
@ -100,6 +102,8 @@
type: Array,
},
});
defineEmits(['handleClickVacation']);
</script>
<style scoped>

View File

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

View File

@ -33,7 +33,7 @@
</div>
</div>
</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>
</h5>
<small >{{ data.localVote.formatted_LOCVOTRDT }} ~ {{ data.localVote.formatted_LOCVOTEDT }}</small>

View File

@ -49,7 +49,7 @@
@keyup="ValidHandler('title')"
/>
<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">
<button class="btn btn-primary" @click="saveWord" :disabled="titleValue ? !changed : false">
<i class="bx bx-check"></i>
@ -232,3 +232,11 @@ const handleCategoryFocusout = (value) => {
};
</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"
:src="`${baseUrl}upload/img/profile/${user.profile}`"
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;`"
@error="$event.target.src = '/img/icons/icon.png'"
/>

View File

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

View File

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

View File

@ -190,9 +190,9 @@
searchText: searchText.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;
top: -1px;
}
</style>

View File

@ -21,7 +21,14 @@
:is-delete-pushed="isDeletePushed"
@editClick="editClick"
@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">
@ -451,6 +458,7 @@
passwordAlert.value = '';
commentAlert.value = '';
await fetchComments();
activeCommentBtnClass();
} else {
alert('댓글 작성을 실패했습니다.');
}
@ -496,6 +504,9 @@
const isUnknown = unknown?.unknown ?? false;
if (isUnknown) {
closeAllEditTextareas();
closeAllPasswordAreas();
activeCommentBtnClass();
togglePassword('delete');
} else {
deletePost();
@ -581,6 +592,8 @@
//
const deleteComment = async comment => {
acitveButtonType(); //
closeAllEditTextareas();
const isMyComment = comment.authorId === currentUserId.value;
//
@ -686,6 +699,7 @@
isEditPushed.value = false;
isDeletePushed.value = false;
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 => {
//

View File

@ -31,6 +31,7 @@
<!-- 단어 목록 -->
<ul v-if="total > 0" class="ms-3 list-unstyled" style="overflow-x: hidden; word-wrap: break-word;">
<DictCard
class="DictCard q-editor-container"
v-for="item in wordList"
:key="item.WRDDICSEQ"
:item="item"
@ -330,5 +331,15 @@ import { useRoute } from 'vue-router';
top: 5px;
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>