Merge branch 'main' into mypage

This commit is contained in:
dyhj625 2025-04-01 09:46:30 +09:00
commit 7a0df53121
10 changed files with 113 additions and 46 deletions

View File

@ -582,7 +582,7 @@
/* commuters */
.commuter-list {
max-height: 358px;
max-height: 450px;
overflow-y: auto;
scrollbar-width: none;
}

View File

@ -2,6 +2,7 @@
<!-- 뒤로가기 -->
<button
@click="goBack"
:disabled="!canGoBack"
:class="{ 'shifted': showButton }"
class="back-btn rounded-pill btn-icon btn-secondary position-fixed shadow z-5 border-0">
<i class='bx bx-chevron-left'></i>
@ -18,29 +19,54 @@
</template>
<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 canGoBack = ref(false);
const route = useRoute();
const router = useRouter();
const loginPage = "/login"; //
//
const handleScroll = () => {
showButton.value = window.scrollY > 200;
};
//
const scrollToTop = () => {
window.scrollTo({ top: 0, behavior: "smooth" });
};
//
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(() => {
window.addEventListener("scroll", handleScroll);
updateCanGoBack();
});
// `canGoBack`
watch(route, () => {
updateCanGoBack();
});
//
onUnmounted(() => {
window.removeEventListener("scroll", handleScroll);
});
</script>
</script>

View File

@ -1,5 +1,5 @@
<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="row g-0">
<div class="col-3 border-end text-center" id="app-calendar-sidebar">
@ -70,6 +70,8 @@
출근 :
<MapPopover
:address="commuter.projectAddress"
:is-visible="visiblePopover.type === 'project' && visiblePopover.index === index"
@update-visible="updatePopover('project', index)"
v-if="commuter.projectAddress"
>
<template #trigger>
@ -90,6 +92,8 @@
퇴근 :
<MapPopover
:address="commuter.leaveProjectAddress"
:is-visible="visiblePopover.type === 'leave' && visiblePopover.index === index"
@update-visible="updatePopover('leave', index)"
v-if="commuter.leaveProjectAddress"
>
<template #trigger>
@ -154,6 +158,12 @@ const checkedInProject = ref(null);
const isModalOpen = ref(false);
const visiblePopover = ref({
type: null, // 'project' 'leave'
index: null //
});
const commuters = ref([]);
const monthlyCommuters = ref([]);
@ -429,6 +439,17 @@ const closeModal = () => {
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(() => {
return monthlyCommuters.value.filter(commuter =>
commuter.COMMUTDAY === eventDate.value

View File

@ -34,7 +34,8 @@
<div class="d-flex flex-sm-row align-items-center pb-2">
<MapPopover
:address="address"
:ref="mapIconRef"
:is-visible="isMapVisible"
@update-visible="updatePopover"
>
<template #trigger>
<div class="d-flex align-items-center cursor-pointer">
@ -253,7 +254,7 @@ const emit = defineEmits(['update']);
const isModalOpen = ref(false);
const logData = ref([]);
const mapIconRef = ref(null);
const isMapVisible = ref(null);
//
const isEditModalOpen = ref(false);
@ -291,6 +292,9 @@ const openEndDatePicker = () => {
}
};
const updatePopover = (visible) => {
isMapVisible.value = visible;
};
//
const handleEditUserListUpdate = (userLists) => {

View File

@ -49,10 +49,15 @@
address: {
type: String,
required: true
},
isVisible: {
type: Boolean,
required: false
}
});
const isVisible = ref(false);
const emit = defineEmits(['update-visible']);
const coordinates = ref(null);
const map = ref(null);
@ -79,7 +84,7 @@
};
const togglePopover = () => {
isVisible.value = !isVisible.value;
emit('update-visible', !props.isVisible);
};
const onLoadKakaoMap = (mapRef) => {

View File

@ -48,7 +48,6 @@
:is-alert="passwordcheckAlert"
@update:data="passwordcheck = $event"
@update:alert="passwordcheckAlert = $event"
@blur="checkPw"
:value="passwordcheck"
/>
<span v-if="passwordcheckError" class="invalid-feedback d-block">{{ passwordcheckError }}</span>
@ -299,17 +298,6 @@
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 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 () => {

View File

@ -11,7 +11,7 @@
alt="user"
/>
<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">
<h6 class="mb-1">{{ data.localVote.MEMBERNAM }}</h6>
<!-- 투표완료시 -->

View File

@ -29,7 +29,7 @@
<div class="d-flex align-items-start">
<!-- 최초 작성자 -->
<div class="d-flex flex-wrap align-items-center me-4">
<div class="avatar avatar-sm me-2">
<div class="avatar me-2">
<img
class="rounded-circle user-avatar"
:src="getProfileImage(item.author.profileImage)"
@ -48,7 +48,7 @@
v-if="item.author.createdAt !== item.lastEditor.updatedAt"
class="d-flex flex-wrap align-items-center"
>
<div class="avatar avatar-sm me-2">
<div class="avatar me-2">
<img
class="rounded-circle user-avatar"
:src="getProfileImage(item.lastEditor.profileImage)"

View File

@ -167,7 +167,8 @@
v-if="user"
:src="`${baseUrl}upload/img/profile/${user.profile}`"
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'"
/>
</a>
@ -250,7 +251,6 @@
import { useRouter } from 'vue-router';
import { useThemeStore } from '@s/darkmode';
import { computed, onMounted, ref, watch } from 'vue';
import $api from '@api';
const baseUrl = import.meta.env.VITE_SERVER;
@ -312,7 +312,6 @@
await userStore.userInfo();
user.value = userStore.user;
await projectStore.loadAllProjectLists();
//

View File

@ -141,13 +141,34 @@
}
};
function extractPlainText(delta) {
if (!delta || !Array.isArray(delta.ops)) return '';
return delta.ops
function isDeltaChanged(current, original) {
const Delta = Quill.import('delta');
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')
.map(op => op.insert.trim())
.join(' ')
.trim();
.map(op => op.insert)
.join('');
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(() => {
@ -212,16 +233,6 @@
contentLoaded.value = true;
};
watch(
content,
val => {
if (contentLoaded.value && !originalPlainText.value) {
originalPlainText.value = extractPlainText(val);
}
},
{ immediate: true },
);
const handleUpdateEditorImg = item => {
editorUploadedImgList.value = item;
};