Merge branch 'main' into mypage
This commit is contained in:
commit
7a0df53121
@ -582,7 +582,7 @@
|
|||||||
|
|
||||||
/* commuters */
|
/* commuters */
|
||||||
.commuter-list {
|
.commuter-list {
|
||||||
max-height: 358px;
|
max-height: 450px;
|
||||||
overflow-y: auto;
|
overflow-y: auto;
|
||||||
scrollbar-width: none;
|
scrollbar-width: none;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -2,6 +2,7 @@
|
|||||||
<!-- 뒤로가기 -->
|
<!-- 뒤로가기 -->
|
||||||
<button
|
<button
|
||||||
@click="goBack"
|
@click="goBack"
|
||||||
|
:disabled="!canGoBack"
|
||||||
:class="{ 'shifted': showButton }"
|
:class="{ 'shifted': showButton }"
|
||||||
class="back-btn rounded-pill btn-icon btn-secondary position-fixed shadow z-5 border-0">
|
class="back-btn rounded-pill btn-icon btn-secondary position-fixed shadow z-5 border-0">
|
||||||
<i class='bx bx-chevron-left'></i>
|
<i class='bx bx-chevron-left'></i>
|
||||||
@ -18,29 +19,54 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup>
|
<script setup>
|
||||||
import { ref, onMounted, onUnmounted } from "vue";
|
import { ref, onMounted, onUnmounted, watch } from "vue";
|
||||||
|
import { useRoute, useRouter } from "vue-router";
|
||||||
|
|
||||||
const showButton = ref(false);
|
const showButton = ref(false);
|
||||||
|
const canGoBack = ref(false);
|
||||||
|
const route = useRoute();
|
||||||
|
const router = useRouter();
|
||||||
|
const loginPage = "/login"; // 로그인 페이지 경로
|
||||||
|
|
||||||
|
// 스크롤 이벤트 핸들러
|
||||||
const handleScroll = () => {
|
const handleScroll = () => {
|
||||||
showButton.value = window.scrollY > 200;
|
showButton.value = window.scrollY > 200;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// 최상단으로 스크롤 이동
|
||||||
const scrollToTop = () => {
|
const scrollToTop = () => {
|
||||||
window.scrollTo({ top: 0, behavior: "smooth" });
|
window.scrollTo({ top: 0, behavior: "smooth" });
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// 뒤로 가기 처리
|
||||||
const goBack = () => {
|
const goBack = () => {
|
||||||
window.history.back();
|
if (canGoBack.value) {
|
||||||
|
router.back();
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// 뒤로 가기 가능 여부 확인 함수
|
||||||
|
const updateCanGoBack = () => {
|
||||||
|
const historyBack = router.options.history.state.back;
|
||||||
|
const previousPage = document.referrer;
|
||||||
|
|
||||||
|
canGoBack.value = !!historyBack && historyBack !== loginPage && !previousPage.includes(loginPage);
|
||||||
|
};
|
||||||
|
|
||||||
|
// 마운트 시 한 번 실행
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
window.addEventListener("scroll", handleScroll);
|
window.addEventListener("scroll", handleScroll);
|
||||||
|
updateCanGoBack();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// 라우트가 변경될 때마다 `canGoBack` 업데이트
|
||||||
|
watch(route, () => {
|
||||||
|
updateCanGoBack();
|
||||||
|
});
|
||||||
|
|
||||||
|
// 언마운트 시 이벤트 제거
|
||||||
onUnmounted(() => {
|
onUnmounted(() => {
|
||||||
window.removeEventListener("scroll", handleScroll);
|
window.removeEventListener("scroll", handleScroll);
|
||||||
});
|
});
|
||||||
</script>
|
|
||||||
|
|
||||||
|
</script>
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="container-xxl flex-grow-1 container-p-y pb-0">
|
<div class="container-xxl flex-grow-1 container-p-y">
|
||||||
<div class="card app-calendar-wrapper">
|
<div class="card app-calendar-wrapper">
|
||||||
<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">
|
||||||
@ -70,6 +70,8 @@
|
|||||||
출근 :
|
출근 :
|
||||||
<MapPopover
|
<MapPopover
|
||||||
:address="commuter.projectAddress"
|
:address="commuter.projectAddress"
|
||||||
|
:is-visible="visiblePopover.type === 'project' && visiblePopover.index === index"
|
||||||
|
@update-visible="updatePopover('project', index)"
|
||||||
v-if="commuter.projectAddress"
|
v-if="commuter.projectAddress"
|
||||||
>
|
>
|
||||||
<template #trigger>
|
<template #trigger>
|
||||||
@ -90,6 +92,8 @@
|
|||||||
퇴근 :
|
퇴근 :
|
||||||
<MapPopover
|
<MapPopover
|
||||||
:address="commuter.leaveProjectAddress"
|
:address="commuter.leaveProjectAddress"
|
||||||
|
:is-visible="visiblePopover.type === 'leave' && visiblePopover.index === index"
|
||||||
|
@update-visible="updatePopover('leave', index)"
|
||||||
v-if="commuter.leaveProjectAddress"
|
v-if="commuter.leaveProjectAddress"
|
||||||
>
|
>
|
||||||
<template #trigger>
|
<template #trigger>
|
||||||
@ -154,6 +158,12 @@ const checkedInProject = ref(null);
|
|||||||
|
|
||||||
const isModalOpen = ref(false);
|
const isModalOpen = ref(false);
|
||||||
|
|
||||||
|
const visiblePopover = ref({
|
||||||
|
type: null, // 'project' 또는 'leave'
|
||||||
|
index: null // 팝오버 인덱스
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
const commuters = ref([]);
|
const commuters = ref([]);
|
||||||
const monthlyCommuters = ref([]);
|
const monthlyCommuters = ref([]);
|
||||||
|
|
||||||
@ -429,6 +439,17 @@ const closeModal = () => {
|
|||||||
isModalOpen.value = false;
|
isModalOpen.value = false;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// MapPopover에서 visible 상태 변경 이벤트 처리
|
||||||
|
const updatePopover = (popoverType, index) => {
|
||||||
|
if (visiblePopover.value.type === popoverType && visiblePopover.value.index === index) {
|
||||||
|
// 같은 팝오버를 클릭하면 닫기
|
||||||
|
visiblePopover.value = { type: null, index: null };
|
||||||
|
} else {
|
||||||
|
// 다른 팝오버를 클릭하면 기존 것 닫고 새로운 것 열기
|
||||||
|
visiblePopover.value = { type: popoverType, index: index };
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
const selectedDateCommuters = computed(() => {
|
const selectedDateCommuters = computed(() => {
|
||||||
return monthlyCommuters.value.filter(commuter =>
|
return monthlyCommuters.value.filter(commuter =>
|
||||||
commuter.COMMUTDAY === eventDate.value
|
commuter.COMMUTDAY === eventDate.value
|
||||||
|
|||||||
@ -34,7 +34,8 @@
|
|||||||
<div class="d-flex flex-sm-row align-items-center pb-2">
|
<div class="d-flex flex-sm-row align-items-center pb-2">
|
||||||
<MapPopover
|
<MapPopover
|
||||||
:address="address"
|
:address="address"
|
||||||
:ref="mapIconRef"
|
:is-visible="isMapVisible"
|
||||||
|
@update-visible="updatePopover"
|
||||||
>
|
>
|
||||||
<template #trigger>
|
<template #trigger>
|
||||||
<div class="d-flex align-items-center cursor-pointer">
|
<div class="d-flex align-items-center cursor-pointer">
|
||||||
@ -253,7 +254,7 @@ const emit = defineEmits(['update']);
|
|||||||
const isModalOpen = ref(false);
|
const isModalOpen = ref(false);
|
||||||
const logData = ref([]);
|
const logData = ref([]);
|
||||||
|
|
||||||
const mapIconRef = ref(null);
|
const isMapVisible = ref(null);
|
||||||
|
|
||||||
// 수정 모달 상태
|
// 수정 모달 상태
|
||||||
const isEditModalOpen = ref(false);
|
const isEditModalOpen = ref(false);
|
||||||
@ -291,6 +292,9 @@ const openEndDatePicker = () => {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const updatePopover = (visible) => {
|
||||||
|
isMapVisible.value = visible;
|
||||||
|
};
|
||||||
|
|
||||||
// 사용자 목록 업데이트 핸들러
|
// 사용자 목록 업데이트 핸들러
|
||||||
const handleEditUserListUpdate = (userLists) => {
|
const handleEditUserListUpdate = (userLists) => {
|
||||||
|
|||||||
@ -49,10 +49,15 @@
|
|||||||
address: {
|
address: {
|
||||||
type: String,
|
type: String,
|
||||||
required: true
|
required: true
|
||||||
|
},
|
||||||
|
isVisible: {
|
||||||
|
type: Boolean,
|
||||||
|
required: false
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
const isVisible = ref(false);
|
const emit = defineEmits(['update-visible']);
|
||||||
|
|
||||||
const coordinates = ref(null);
|
const coordinates = ref(null);
|
||||||
const map = ref(null);
|
const map = ref(null);
|
||||||
|
|
||||||
@ -79,7 +84,7 @@
|
|||||||
};
|
};
|
||||||
|
|
||||||
const togglePopover = () => {
|
const togglePopover = () => {
|
||||||
isVisible.value = !isVisible.value;
|
emit('update-visible', !props.isVisible);
|
||||||
};
|
};
|
||||||
|
|
||||||
const onLoadKakaoMap = (mapRef) => {
|
const onLoadKakaoMap = (mapRef) => {
|
||||||
|
|||||||
@ -48,7 +48,6 @@
|
|||||||
:is-alert="passwordcheckAlert"
|
:is-alert="passwordcheckAlert"
|
||||||
@update:data="passwordcheck = $event"
|
@update:data="passwordcheck = $event"
|
||||||
@update:alert="passwordcheckAlert = $event"
|
@update:alert="passwordcheckAlert = $event"
|
||||||
@blur="checkPw"
|
|
||||||
:value="passwordcheck"
|
:value="passwordcheck"
|
||||||
/>
|
/>
|
||||||
<span v-if="passwordcheckError" class="invalid-feedback d-block">{{ passwordcheckError }}</span>
|
<span v-if="passwordcheckError" class="invalid-feedback d-block">{{ passwordcheckError }}</span>
|
||||||
@ -299,17 +298,6 @@
|
|||||||
postcode.value = addressData.postcode; // 우편번호
|
postcode.value = addressData.postcode; // 우편번호
|
||||||
};
|
};
|
||||||
|
|
||||||
// 비밀번호 확인 체크
|
|
||||||
const checkPw = async () => {
|
|
||||||
if (password.value !== passwordcheck.value) {
|
|
||||||
passwordcheckError.value = '비밀번호가 일치하지 않습니다.';
|
|
||||||
passwordcheckErrorAlert.value = true;
|
|
||||||
} else {
|
|
||||||
passwordcheckError.value = '';
|
|
||||||
passwordcheckErrorAlert.value = false;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
// 색상 중복체크
|
// 색상 중복체크
|
||||||
const checkColorDuplicate = async () => {
|
const checkColorDuplicate = async () => {
|
||||||
const response = await $api.get(`/user/checkColor?memberCol=${color.value}`);
|
const response = await $api.get(`/user/checkColor?memberCol=${color.value}`);
|
||||||
@ -358,6 +346,19 @@
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// 비밀번호 확인
|
||||||
|
watch([password, passwordcheck], ([newPassword, newPasswordCheck]) => {
|
||||||
|
if (newPassword && newPasswordCheck) {
|
||||||
|
if (newPassword !== newPasswordCheck) {
|
||||||
|
passwordcheckError.value = '비밀번호가 일치하지 않습니다.';
|
||||||
|
passwordcheckErrorAlert.value = true;
|
||||||
|
} else {
|
||||||
|
passwordcheckError.value = '';
|
||||||
|
passwordcheckErrorAlert.value = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
// 회원가입
|
// 회원가입
|
||||||
const handleSubmit = async () => {
|
const handleSubmit = async () => {
|
||||||
|
|||||||
@ -11,7 +11,7 @@
|
|||||||
alt="user"
|
alt="user"
|
||||||
/>
|
/>
|
||||||
<div class="w-100">
|
<div class="w-100">
|
||||||
<div class="d-flex justify-content-between">
|
<div class="d-flex justify-content-between align-items-center">
|
||||||
<div class="user-info">
|
<div class="user-info">
|
||||||
<h6 class="mb-1">{{ data.localVote.MEMBERNAM }}</h6>
|
<h6 class="mb-1">{{ data.localVote.MEMBERNAM }}</h6>
|
||||||
<!-- 투표완료시 -->
|
<!-- 투표완료시 -->
|
||||||
|
|||||||
@ -29,7 +29,7 @@
|
|||||||
<div class="d-flex align-items-start">
|
<div class="d-flex align-items-start">
|
||||||
<!-- 최초 작성자 -->
|
<!-- 최초 작성자 -->
|
||||||
<div class="d-flex flex-wrap align-items-center me-4">
|
<div class="d-flex flex-wrap align-items-center me-4">
|
||||||
<div class="avatar avatar-sm me-2">
|
<div class="avatar me-2">
|
||||||
<img
|
<img
|
||||||
class="rounded-circle user-avatar"
|
class="rounded-circle user-avatar"
|
||||||
:src="getProfileImage(item.author.profileImage)"
|
:src="getProfileImage(item.author.profileImage)"
|
||||||
@ -48,7 +48,7 @@
|
|||||||
v-if="item.author.createdAt !== item.lastEditor.updatedAt"
|
v-if="item.author.createdAt !== item.lastEditor.updatedAt"
|
||||||
class="d-flex flex-wrap align-items-center"
|
class="d-flex flex-wrap align-items-center"
|
||||||
>
|
>
|
||||||
<div class="avatar avatar-sm me-2">
|
<div class="avatar me-2">
|
||||||
<img
|
<img
|
||||||
class="rounded-circle user-avatar"
|
class="rounded-circle user-avatar"
|
||||||
:src="getProfileImage(item.lastEditor.profileImage)"
|
:src="getProfileImage(item.lastEditor.profileImage)"
|
||||||
|
|||||||
@ -167,7 +167,8 @@
|
|||||||
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"
|
class="w-px-40 h-px-40 rounded-circle border border-3"
|
||||||
|
:style="`border-color: ${user.usercolor} !important;`"
|
||||||
@error="$event.target.src = '/img/icons/icon.png'"
|
@error="$event.target.src = '/img/icons/icon.png'"
|
||||||
/>
|
/>
|
||||||
</a>
|
</a>
|
||||||
@ -250,7 +251,6 @@
|
|||||||
import { useRouter } from 'vue-router';
|
import { useRouter } from 'vue-router';
|
||||||
import { useThemeStore } from '@s/darkmode';
|
import { useThemeStore } from '@s/darkmode';
|
||||||
import { computed, onMounted, ref, watch } from 'vue';
|
import { computed, onMounted, ref, watch } from 'vue';
|
||||||
import $api from '@api';
|
|
||||||
|
|
||||||
const baseUrl = import.meta.env.VITE_SERVER;
|
const baseUrl = import.meta.env.VITE_SERVER;
|
||||||
|
|
||||||
@ -312,7 +312,6 @@
|
|||||||
await userStore.userInfo();
|
await userStore.userInfo();
|
||||||
user.value = userStore.user;
|
user.value = userStore.user;
|
||||||
|
|
||||||
|
|
||||||
await projectStore.loadAllProjectLists();
|
await projectStore.loadAllProjectLists();
|
||||||
|
|
||||||
// 사용자가 참여하고 있는 프로젝트 목록
|
// 사용자가 참여하고 있는 프로젝트 목록
|
||||||
|
|||||||
@ -141,13 +141,34 @@
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
function extractPlainText(delta) {
|
function isDeltaChanged(current, original) {
|
||||||
if (!delta || !Array.isArray(delta.ops)) return '';
|
const Delta = Quill.import('delta');
|
||||||
return delta.ops
|
const currentDelta = new Delta(current || []);
|
||||||
|
const originalDelta = new Delta(original || []);
|
||||||
|
|
||||||
|
const diff = originalDelta.diff(currentDelta);
|
||||||
|
if (!diff || diff.ops.length === 0) return false;
|
||||||
|
|
||||||
|
// 텍스트만 비교해서 완전 동일한지 확인
|
||||||
|
const getPlainText = delta =>
|
||||||
|
(delta.ops || [])
|
||||||
.filter(op => typeof op.insert === 'string')
|
.filter(op => typeof op.insert === 'string')
|
||||||
.map(op => op.insert.trim())
|
.map(op => op.insert)
|
||||||
.join(' ')
|
.join('');
|
||||||
.trim();
|
|
||||||
|
const getImages = delta =>
|
||||||
|
(delta.ops || []).filter(op => typeof op.insert === 'object' && op.insert.image).map(op => op.insert.image);
|
||||||
|
|
||||||
|
const textCurrent = getPlainText(currentDelta);
|
||||||
|
const textOriginal = getPlainText(originalDelta);
|
||||||
|
|
||||||
|
const imgsCurrent = getImages(currentDelta);
|
||||||
|
const imgsOriginal = getImages(originalDelta);
|
||||||
|
|
||||||
|
const textEqual = textCurrent === textOriginal;
|
||||||
|
const imageEqual = JSON.stringify(imgsCurrent) === JSON.stringify(imgsOriginal);
|
||||||
|
|
||||||
|
return !(textEqual && imageEqual); // 둘 다 같아야 false
|
||||||
}
|
}
|
||||||
|
|
||||||
const isChanged = computed(() => {
|
const isChanged = computed(() => {
|
||||||
@ -212,16 +233,6 @@
|
|||||||
contentLoaded.value = true;
|
contentLoaded.value = true;
|
||||||
};
|
};
|
||||||
|
|
||||||
watch(
|
|
||||||
content,
|
|
||||||
val => {
|
|
||||||
if (contentLoaded.value && !originalPlainText.value) {
|
|
||||||
originalPlainText.value = extractPlainText(val);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{ immediate: true },
|
|
||||||
);
|
|
||||||
|
|
||||||
const handleUpdateEditorImg = item => {
|
const handleUpdateEditorImg = item => {
|
||||||
editorUploadedImgList.value = item;
|
editorUploadedImgList.value = item;
|
||||||
};
|
};
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user