Merge branch 'main' of http://192.168.0.251:3000/localnet/localhost-front
All checks were successful
LocalNet_front/pipeline/head This commit looks good

This commit is contained in:
yoon 2025-03-31 12:22:03 +09:00
commit 09fd2df838
8 changed files with 90 additions and 71 deletions

BIN
public/img/icons/Crown.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 229 KiB

View File

@ -1,5 +1,5 @@
<template> <template>
<div class="row gx-2 mb-4"> <div class="row gx-2 mb-10 mt-1">
<div class="col-3"> <div class="col-3">
<div class="ratio ratio-1x1"> <div class="ratio ratio-1x1">
<!-- 오전 반차 버튼 --> <!-- 오전 반차 버튼 -->

View File

@ -3,7 +3,16 @@
<label :for="inputId" class="col-md-2 col-form-label">{{ title }}</label> <label :for="inputId" class="col-md-2 col-form-label">{{ title }}</label>
<div class="col-md-10"> <div class="col-md-10">
<label :for="inputId" class="btn btn-label-primary">파일 선택</label> <label :for="inputId" class="btn btn-label-primary">파일 선택</label>
<input class="form-control" type="file" style="display: none" :id="inputId" ref="fileInput" @change="changeHandler" multiple /> <input
class="form-control"
type="file"
style="display: none"
:id="inputId"
ref="fileInput"
:key="autoIncrement"
@change="changeHandler"
multiple
/>
<div v-if="showError" class="text-danger mt-1"> <div v-if="showError" class="text-danger mt-1">
{{ errorMessage }} {{ errorMessage }}
</div> </div>
@ -33,10 +42,14 @@
required: false, required: false,
}, },
}); });
//:key="autoIncrement" .
const inputId = computed(() => props.name || 'defaultFileInput'); const inputId = computed(() => props.name || 'defaultFileInput');
const emits = defineEmits(['update:data', 'update:isValid']); const emits = defineEmits(['update:data', 'update:isValid']);
const autoIncrement = ref(props.autoIncrement);
const MAX_TOTAL_SIZE = 5 * 1024 * 1024; // 5MB const MAX_TOTAL_SIZE = 5 * 1024 * 1024; // 5MB
const MAX_FILE_COUNT = 5; // const MAX_FILE_COUNT = 5; //
const ALLOWED_FILE_TYPES = []; // const ALLOWED_FILE_TYPES = []; //

View File

@ -46,7 +46,7 @@ const myRemainingQuota = computed(() => {
return props.remainingVacationData?.[myUserId.value] ?? 0; return props.remainingVacationData?.[myUserId.value] ?? 0;
}); });
const isGiftButtonDisabled = computed(() => { const isGiftButtonDisabled = computed(() => {
return myRemainingQuota.value <= 0; return myRemainingQuota.value < 0;
}); });
// //
const fetchSentVacationCount = async () => { const fetchSentVacationCount = async () => {

View File

@ -1,5 +1,4 @@
<template> <template>
<div class="">
<ul class="row gx-2 mb-0 list-inline "> <ul class="row gx-2 mb-0 list-inline ">
<li <li
v-for="(user, index) in sortedUserList" v-for="(user, index) in sortedUserList"
@ -10,7 +9,13 @@
data-bs-placement="top" data-bs-placement="top"
:aria-label="user.MEMBERSEQ" :aria-label="user.MEMBERSEQ"
> >
<div class="ratio ratio-1x1 mb-0 profile-list"> <div class="ratio ratio-1x1 mb-0 profile-list position-relative">
<img
v-if="user.MEMBERSEQ === employeeId"
src="/img/icons/Crown.png"
alt="Crown"
class="start-50 translate-middle crown-icon"
/>
<img <img
class="rounded-circle profile-img" class="rounded-circle profile-img"
:src="getUserProfileImage(user.MEMBERPRF)" :src="getUserProfileImage(user.MEMBERPRF)"
@ -25,7 +30,6 @@
</span> </span>
</li> </li>
</ul> </ul>
</div>
</template> </template>
<script setup> <script setup>
@ -95,4 +99,10 @@ borderStyle: "solid",
</script> </script>
<style scoped> <style scoped>
.crown-icon {
width: 90%;
height: 70%;
z-index: 0;
top: -7%
}
</style> </style>

View File

@ -1,6 +1,6 @@
<template> <template>
<div class="card mb-6" :class="{'disabled-class': data.localVote.LOCVOTDDT && (topVoters.length == 1 || data.localVote.LOCVOTRES || voteResult == 0)}"> <div class="card mb-6" >
<div class="card-body" v-if="!data.localVote.LOCVOTDEL" > <div class="card-body " :class="{'disabled-class': data.localVote.LOCVOTDDT && (topVoters.length == 1 || data.localVote.LOCVOTRES || voteResult == 0)}" v-if="!data.localVote.LOCVOTDEL" >
<h5 class="card-title mb-1"> <h5 class="card-title mb-1">
<div class="list-unstyled users-list d-flex align-items-center gap-1"> <div class="list-unstyled users-list d-flex align-items-center gap-1">
<img <img

View File

@ -25,6 +25,7 @@
title="첨부파일" title="첨부파일"
name="files" name="files"
:is-alert="attachFilesAlert" :is-alert="attachFilesAlert"
:key="autoIncrement"
@update:data="handleFileUpload" @update:data="handleFileUpload"
@update:isValid="isFileValid = $event" @update:isValid="isFileValid = $event"
/> />
@ -99,6 +100,7 @@
// //
const title = ref(''); const title = ref('');
const content = ref(''); const content = ref('');
const autoIncrement = ref(0);
// //
const titleAlert = ref(false); const titleAlert = ref(false);
@ -129,7 +131,7 @@
const isFirstContentUpdate = ref(true); const isFirstContentUpdate = ref(true);
// //
const handleEditorDataUpdate = (data) => { const handleEditorDataUpdate = data => {
content.value = data; content.value = data;
if (isFirstContentUpdate.value) { if (isFirstContentUpdate.value) {
@ -139,37 +141,13 @@ const handleEditorDataUpdate = (data) => {
} }
}; };
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) .map(op => op.insert.trim())
.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(() => {
@ -181,11 +159,13 @@ const handleEditorDataUpdate = (data) => {
delFileIdx.value.length > 0 || // delFileIdx.value.length > 0 || //
!isSameFiles( !isSameFiles(
attachFiles.value.filter(f => f.id), // (id ) attachFiles.value.filter(f => f.id), // (id )
originalFiles.value originalFiles.value,
); );
return isTitleChanged || isContentChanged || isFilesChanged; return isTitleChanged || isContentChanged || isFilesChanged;
}); });
watch(isChanged, val => {
console.log('🔄 isChanged changed:', val);
});
// //
function isSameFiles(current, original) { function isSameFiles(current, original) {
@ -195,10 +175,7 @@ const handleEditorDataUpdate = (data) => {
const sortedOriginal = [...original].sort((a, b) => a.id - b.id); const sortedOriginal = [...original].sort((a, b) => a.id - b.id);
return sortedCurrent.every((file, idx) => { return sortedCurrent.every((file, idx) => {
return ( return file.id === sortedOriginal[idx].id && file.name === sortedOriginal[idx].name;
file.id === sortedOriginal[idx].id &&
file.name === sortedOriginal[idx].name
);
}); });
} }
@ -235,6 +212,16 @@ const handleEditorDataUpdate = (data) => {
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;
}; };
@ -305,6 +292,8 @@ const handleEditorDataUpdate = (data) => {
} }
fileError.value = ''; fileError.value = '';
attachFiles.value = [...attachFiles.value, ...validFiles].slice(0, maxFiles); attachFiles.value = [...attachFiles.value, ...validFiles].slice(0, maxFiles);
autoIncrement.value++;
}; };
const removeFile = (index, file) => { const removeFile = (index, file) => {
@ -314,6 +303,7 @@ const handleEditorDataUpdate = (data) => {
if (attachFiles.value.length <= maxFiles) { if (attachFiles.value.length <= maxFiles) {
fileError.value = ''; fileError.value = '';
} }
autoIncrement.value++;
}; };
watch(attachFiles, () => { watch(attachFiles, () => {
@ -388,7 +378,6 @@ const handleEditorDataUpdate = (data) => {
onMounted(async () => { onMounted(async () => {
if (currentBoardId.value) { if (currentBoardId.value) {
fetchBoardDetails(); fetchBoardDetails();
} else { } else {
console.error('잘못된 게시물 ID:', currentBoardId.value); console.error('잘못된 게시물 ID:', currentBoardId.value);
} }

View File

@ -69,6 +69,7 @@
title="첨부파일" title="첨부파일"
name="files" name="files"
:is-alert="attachFilesAlert" :is-alert="attachFilesAlert"
:key="autoIncrement"
@update:data="handleFileUpload" @update:data="handleFileUpload"
@update:isValid="isFileValid = $event" @update:isValid="isFileValid = $event"
/> />
@ -154,6 +155,8 @@
} }
}; };
const autoIncrement = ref(0);
onMounted(() => { onMounted(() => {
fetchCategories(); fetchCategories();
}); });
@ -178,8 +181,11 @@
fileError.value = `최대 ${maxFiles}개의 파일만 업로드할 수 있습니다.`; fileError.value = `최대 ${maxFiles}개의 파일만 업로드할 수 있습니다.`;
return; return;
} }
fileError.value = ''; fileError.value = '';
attachFiles.value = [...attachFiles.value, ...validFiles].slice(0, maxFiles); attachFiles.value = [...attachFiles.value, ...validFiles].slice(0, maxFiles);
autoIncrement.value++;
}; };
const removeFile = index => { const removeFile = index => {
@ -187,6 +193,7 @@
if (attachFiles.value.length <= maxFiles) { if (attachFiles.value.length <= maxFiles) {
fileError.value = ''; fileError.value = '';
} }
autoIncrement.value++;
}; };
watch(attachFiles, () => { watch(attachFiles, () => {