Merge branch 'main' of http://192.168.0.251:3000/localnet/localhost-front
6
.env.dev
@ -1,6 +1,6 @@
|
|||||||
VITE_DOMAIN = https://192.168.0.251:5173/
|
VITE_DOMAIN = https://192.168.0.251:5173/
|
||||||
# VITE_LOGIN_URL = http://localhost:10325/ms/
|
# VITE_LOGIN_URL = http://localhost:10325/ms/
|
||||||
# VITE_FILE_URL = http://localhost:10325/ms/
|
VITE_SERVER = https://192.168.0.251:10300/
|
||||||
VITE_API_URL = https://192.168.0.251:10325/api/
|
VITE_API_URL = https://192.168.0.251:10300/api/
|
||||||
VITE_TEST_URL = https://192.168.0.251:10325/test/
|
VITE_TEST_URL = https://192.168.0.251:10300/test/
|
||||||
VITE_KAKAO_MAP_KEY=6f092e8f45ee81186bb6d8408f66a492
|
VITE_KAKAO_MAP_KEY=6f092e8f45ee81186bb6d8408f66a492
|
||||||
@ -1,6 +1,6 @@
|
|||||||
VITE_DOMAIN = http://localhost:5173/
|
VITE_DOMAIN = http://localhost:5173/
|
||||||
# VITE_LOGIN_URL = http://localhost:10325/ms/
|
# VITE_LOGIN_URL = http://localhost:10325/ms/
|
||||||
# VITE_FILE_URL = http://localhost:10325/ms/
|
VITE_SERVER = http://localhost:10325/
|
||||||
VITE_API_URL = http://localhost:10325/api/
|
VITE_API_URL = http://localhost:10325/api/
|
||||||
VITE_TEST_URL = http://localhost:10325/test/
|
VITE_TEST_URL = http://localhost:10325/test/
|
||||||
VITE_KAKAO_MAP_KEY=6f092e8f45ee81186bb6d8408f66a492
|
VITE_KAKAO_MAP_KEY=6f092e8f45ee81186bb6d8408f66a492
|
||||||
@ -32,7 +32,6 @@
|
|||||||
|
|
||||||
<link rel="stylesheet" href="/css/font.css" />
|
<link rel="stylesheet" href="/css/font.css" />
|
||||||
|
|
||||||
|
|
||||||
<!-- Icons -->
|
<!-- Icons -->
|
||||||
<link rel="stylesheet" href="/vendor/fonts/boxicons.css" />
|
<link rel="stylesheet" href="/vendor/fonts/boxicons.css" />
|
||||||
<link rel="stylesheet" href="/vendor/fonts/fontawesome.css" />
|
<link rel="stylesheet" href="/vendor/fonts/fontawesome.css" />
|
||||||
|
|||||||
41
jenkinsfile
Normal file
@ -0,0 +1,41 @@
|
|||||||
|
pipeline {
|
||||||
|
agent any
|
||||||
|
tools {
|
||||||
|
nodejs 'nodejs22'
|
||||||
|
}
|
||||||
|
stages {
|
||||||
|
stage('Build'){
|
||||||
|
steps {
|
||||||
|
bat 'npm install'
|
||||||
|
bat 'npm ci'
|
||||||
|
bat 'npm run build'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
stage('Deploy') {
|
||||||
|
steps {
|
||||||
|
// 로컬 Nginx 서버에 빌드 파일 배포
|
||||||
|
bat '''
|
||||||
|
:: 루트경로 설정
|
||||||
|
set NGINX_ROOT=C:\\nginx\\html
|
||||||
|
|
||||||
|
:: 기존 빌드 삭제
|
||||||
|
if exist "%NGINX_ROOT%\\dist" rmdir /s /q "%NGINX_ROOT%\\dist"
|
||||||
|
|
||||||
|
:: 빌드 파일 복사
|
||||||
|
xcopy /s /y dist\\* "%NGINX_ROOT%\\dist\\"
|
||||||
|
'''
|
||||||
|
}
|
||||||
|
}
|
||||||
|
stage('Restart Server!') {
|
||||||
|
steps {
|
||||||
|
// 로컬 Nginx 서버 재실행
|
||||||
|
bat '''
|
||||||
|
net stop localnginx
|
||||||
|
ping -n 5 127.0.0.1 > nul
|
||||||
|
net start localnginx
|
||||||
|
ping -n 5 127.0.0.1 > nul
|
||||||
|
'''
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -6,7 +6,7 @@
|
|||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "vite --host 0.0.0.0 --mode dev",
|
"dev": "vite --host 0.0.0.0 --mode dev",
|
||||||
"mine": "vite --host 0.0.0.0 --mode mine",
|
"mine": "vite --host 0.0.0.0 --mode mine",
|
||||||
"build": "vite build --mode prod",
|
"build": "vite build --mode dev",
|
||||||
"preview": "vite preview",
|
"preview": "vite preview",
|
||||||
"lint": "eslint . --fix",
|
"lint": "eslint . --fix",
|
||||||
"format": "prettier --write src/"
|
"format": "prettier --write src/"
|
||||||
|
|||||||
@ -3,22 +3,33 @@
|
|||||||
|
|
||||||
/* 휴가 */
|
/* 휴가 */
|
||||||
|
|
||||||
.fc-daygrid-day-events {
|
|
||||||
max-height: 100px !important;
|
|
||||||
overflow-y: auto !important;
|
|
||||||
}
|
|
||||||
/* 이벤트 선 없게 */
|
/* 이벤트 선 없게 */
|
||||||
.fc-event {
|
.fc-event {
|
||||||
border: none;
|
border: none;
|
||||||
}
|
}
|
||||||
/* 오전전반차 그래프 */
|
/* 오전 반차 그래프 (왼쪽 절반) */
|
||||||
.fc-daygrid-event.half-day-am {
|
.fc-daygrid-event.half-day-am {
|
||||||
width: calc(50% - 4px) !important;
|
width: 50% !important;
|
||||||
|
height: 8px !important;
|
||||||
|
border-radius: 2px !important;
|
||||||
|
font-size: 0px !important;
|
||||||
}
|
}
|
||||||
/* 오후반차 그래프프 */
|
/* 오후 반차 그래프 (오른쪽 절반) */
|
||||||
.fc-daygrid-event.half-day-pm {
|
.fc-daygrid-event.half-day-pm {
|
||||||
width: calc(50% - 4px) !important;
|
width: 50% !important;
|
||||||
margin-left: auto !important
|
height: 8px !important;
|
||||||
|
margin-left: auto !important;
|
||||||
|
border-radius: 2px !important;
|
||||||
|
font-size: 0px !important;
|
||||||
|
}
|
||||||
|
/* 연차 그래프 (풀풀) */
|
||||||
|
.fc-daygrid-event.full-day {
|
||||||
|
width: 100% !important;
|
||||||
|
height: 8px !important;
|
||||||
|
margin-left: auto !important;
|
||||||
|
border-radius: 2px !important;
|
||||||
|
font-size: 0px !important;
|
||||||
}
|
}
|
||||||
/* 공휴일,일요일 색상 */
|
/* 공휴일,일요일 색상 */
|
||||||
.fc-day-sun .fc-daygrid-day-number,
|
.fc-day-sun .fc-daygrid-day-number,
|
||||||
@ -39,8 +50,8 @@
|
|||||||
.flatpickr-calendar:after {
|
.flatpickr-calendar:after {
|
||||||
display: none !important;
|
display: none !important;
|
||||||
}
|
}
|
||||||
/* 기본 스타일은 그대로 두고, 데이트피커 인풋의 추가 스타일 정의 */
|
/* 기본 스타일은 그대로 두고, 데이트피커 인풋의 추가 스타일 정의 */
|
||||||
.fc-toolbar-title {
|
.fc-toolbar-title {
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
}
|
}
|
||||||
/* 클릭 가능한 날짜 (오늘 + 미래) */
|
/* 클릭 가능한 날짜 (오늘 + 미래) */
|
||||||
@ -84,7 +95,6 @@ opacity: 0.6; /* 흐려 보이게 */
|
|||||||
|
|
||||||
|
|
||||||
/* 본인 모달 */
|
/* 본인 모달 */
|
||||||
|
|
||||||
/* 닫기 버튼 */
|
/* 닫기 버튼 */
|
||||||
.close-btn {
|
.close-btn {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
@ -109,7 +119,6 @@ opacity: 0.6; /* 흐려 보이게 */
|
|||||||
|
|
||||||
|
|
||||||
/* 선물하기 모달 */
|
/* 선물하기 모달 */
|
||||||
|
|
||||||
/* 연차 개수 버튼 */
|
/* 연차 개수 버튼 */
|
||||||
.count-btn {
|
.count-btn {
|
||||||
font-size: 18px;
|
font-size: 18px;
|
||||||
@ -127,7 +136,6 @@ opacity: 0.6; /* 흐려 보이게 */
|
|||||||
background: #cccccc;
|
background: #cccccc;
|
||||||
cursor: not-allowed;
|
cursor: not-allowed;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* 버튼 컨테이너 (우측 정렬) */
|
/* 버튼 컨테이너 (우측 정렬) */
|
||||||
.custom-button-container {
|
.custom-button-container {
|
||||||
display: flex;
|
display: flex;
|
||||||
@ -141,18 +149,17 @@ opacity: 0.6; /* 흐려 보이게 */
|
|||||||
padding: 10px; /* 크기 조정 */
|
padding: 10px; /* 크기 조정 */
|
||||||
cursor: pointer; /* 클릭 가능하도록 변경 */
|
cursor: pointer; /* 클릭 가능하도록 변경 */
|
||||||
}
|
}
|
||||||
|
|
||||||
/* 아이콘 색상 변경 (기본) */
|
/* 아이콘 색상 변경 (기본) */
|
||||||
.custom-button i {
|
.custom-button i {
|
||||||
color: #282538; /* 기본 아이콘 색상 */
|
color: #282538; /* 기본 아이콘 색상 */
|
||||||
font-size: 25px; /* 아이콘 크기 */
|
font-size: 25px; /* 아이콘 크기 */
|
||||||
}
|
}
|
||||||
|
|
||||||
/* 버튼 호버 효과 */
|
/* 버튼 호버 효과 */
|
||||||
.custom-button:hover i {
|
.custom-button:hover i {
|
||||||
color: #ff0800; /* 호버 시 아이콘 색상 변경 */
|
color: #ff0800; /* 호버 시 아이콘 색상 변경 */
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
.grayscaleImg {
|
.grayscaleImg {
|
||||||
filter: grayscale(100%);
|
filter: grayscale(100%);
|
||||||
}
|
}
|
||||||
|
|||||||
BIN
public/img/mbti/enfj.png
Normal file
|
After Width: | Height: | Size: 8.6 KiB |
BIN
public/img/mbti/enfp.png
Normal file
|
After Width: | Height: | Size: 11 KiB |
BIN
public/img/mbti/entj.png
Normal file
|
After Width: | Height: | Size: 9.1 KiB |
BIN
public/img/mbti/entp.png
Normal file
|
After Width: | Height: | Size: 9.5 KiB |
BIN
public/img/mbti/esfj.png
Normal file
|
After Width: | Height: | Size: 12 KiB |
BIN
public/img/mbti/esfp.png
Normal file
|
After Width: | Height: | Size: 10 KiB |
BIN
public/img/mbti/est.png
Normal file
|
After Width: | Height: | Size: 9.5 KiB |
BIN
public/img/mbti/estp.png
Normal file
|
After Width: | Height: | Size: 10 KiB |
BIN
public/img/mbti/infj.png
Normal file
|
After Width: | Height: | Size: 11 KiB |
BIN
public/img/mbti/infp.png
Normal file
|
After Width: | Height: | Size: 10 KiB |
BIN
public/img/mbti/intj.png
Normal file
|
After Width: | Height: | Size: 9.8 KiB |
BIN
public/img/mbti/intp.png
Normal file
|
After Width: | Height: | Size: 11 KiB |
BIN
public/img/mbti/isfj.png
Normal file
|
After Width: | Height: | Size: 7.5 KiB |
BIN
public/img/mbti/isfp.png
Normal file
|
After Width: | Height: | Size: 11 KiB |
BIN
public/img/mbti/istj.png
Normal file
|
After Width: | Height: | Size: 8.6 KiB |
BIN
public/img/mbti/istp.png
Normal file
|
After Width: | Height: | Size: 10 KiB |
@ -7,12 +7,6 @@
|
|||||||
//-----------------
|
//-----------------
|
||||||
let menu, animate;
|
let menu, animate;
|
||||||
|
|
||||||
<<<<<<< HEAD
|
|
||||||
var menu, animate;
|
|
||||||
(function () {
|
|
||||||
// Initialize menu
|
|
||||||
//-----------------
|
|
||||||
=======
|
|
||||||
let layoutMenuEl = document.querySelectorAll('#layout-menu');
|
let layoutMenuEl = document.querySelectorAll('#layout-menu');
|
||||||
layoutMenuEl.forEach(function (element) {
|
layoutMenuEl.forEach(function (element) {
|
||||||
menu = new Menu(element, {
|
menu = new Menu(element, {
|
||||||
@ -23,7 +17,6 @@ var menu, animate;
|
|||||||
window.Helpers.scrollToActive((animate = false));
|
window.Helpers.scrollToActive((animate = false));
|
||||||
window.Helpers.mainMenu = menu;
|
window.Helpers.mainMenu = menu;
|
||||||
});
|
});
|
||||||
>>>>>>> board-comment
|
|
||||||
|
|
||||||
// Initialize menu togglers and bind click on each
|
// Initialize menu togglers and bind click on each
|
||||||
let menuToggler = document.querySelectorAll('.layout-menu-toggle');
|
let menuToggler = document.querySelectorAll('.layout-menu-toggle');
|
||||||
@ -118,5 +111,7 @@ var menu, animate;
|
|||||||
// If current layout is vertical and current window screen is > small
|
// If current layout is vertical and current window screen is > small
|
||||||
|
|
||||||
// Auto update menu collapsed/expanded based on the themeConfig
|
// Auto update menu collapsed/expanded based on the themeConfig
|
||||||
window.Helpers.setCollapsed(true, false);
|
// 250304 pc 화면에서 메뉴바 고정을 위해 false 처리
|
||||||
|
window.Helpers.setCollapsed(false, false);
|
||||||
|
//window.Helpers.setCollapsed(true, false);
|
||||||
})();
|
})();
|
||||||
|
|||||||
@ -73,6 +73,14 @@ const common = {
|
|||||||
seconds: zeroFormat(date.getSeconds()),
|
seconds: zeroFormat(date.getSeconds()),
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
|
||||||
|
isNotEmpty(obj) {
|
||||||
|
if (obj === null || obj === undefined) return false;
|
||||||
|
if (typeof obj === 'string' && obj.trim() === '') return false;
|
||||||
|
if ((Array.isArray(obj) || obj === Object(obj)) && Object.keys(obj).length === 0) return false;
|
||||||
|
|
||||||
|
return true;
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
|
|||||||
@ -11,43 +11,42 @@
|
|||||||
:isLike="!isLike"
|
:isLike="!isLike"
|
||||||
:isCommentPassword="isCommentPassword"
|
:isCommentPassword="isCommentPassword"
|
||||||
:isCommentProfile="true"
|
:isCommentProfile="true"
|
||||||
@editClick="aaaa"
|
@editClick="handleEditClick"
|
||||||
@deleteClick="$emit('deleteClick', comment)"
|
@deleteClick="$emit('deleteClick', comment)"
|
||||||
@updateReaction="handleUpdateReaction"
|
@updateReaction="handleUpdateReaction"
|
||||||
/>
|
/>
|
||||||
<!-- <P>Commentpassssss: {{isCommentPassword}}</P> -->
|
|
||||||
<!-- :author="true" -->
|
|
||||||
<!-- 댓글 비밀번호 입력창 (익명일 경우) -->
|
<!-- 댓글 비밀번호 입력창 (익명일 경우) -->
|
||||||
<div v-if="isCommentPassword === comment.commentId && unknown" class="mt-3 w-25 ms-auto">
|
<div v-if="currentPasswordCommentId === comment.commentId && unknown" class="mt-3 w-25 ms-auto">
|
||||||
<div class="input-group">
|
<div class="input-group">
|
||||||
<input
|
<input
|
||||||
type="password"
|
type="password"
|
||||||
class="form-control"
|
class="form-control"
|
||||||
v-model="password"
|
:value="password"
|
||||||
placeholder="비밀번호 입력"
|
placeholder="비밀번호 입력"
|
||||||
|
@input="$emit('update:password', $event.target.value.trim())"
|
||||||
/>
|
/>
|
||||||
<button class="btn btn-primary" @click="logPasswordAndEmit">확인</button>
|
<button class="btn btn-primary" @click="logPasswordAndEmit">확인</button>
|
||||||
</div>
|
</div>
|
||||||
<span v-if="passwordCommentAlert" class="invalid-feedback d-block text-start">{{ passwordCommentAlert }}</span>
|
<span v-if="passwordCommentAlert" class="invalid-feedback d-block text-start">{{ passwordCommentAlert }}</span>
|
||||||
</div>
|
</div>
|
||||||
<!-- <p>authorId:{{ comment.authorId }}</p>
|
|
||||||
<p>코멘트 비교: {{comment.isCommentAuthor}}</p> -->
|
|
||||||
|
|
||||||
|
|
||||||
<div class="mt-6">
|
<div class="mt-6">
|
||||||
<template v-if="comment.isEditTextarea">
|
<template v-if="comment.isEditTextarea">
|
||||||
<textarea v-model="localEditedContent" class="form-control"></textarea>
|
<textarea v-model="localEditedContent" class="form-control"></textarea>
|
||||||
<div class="mt-2 d-flex justify-content-end">
|
<div class="mt-2 d-flex justify-content-end">
|
||||||
<!-- <button class="btn btn-secondary me-2" @click="$emit('cancelEdit', comment)">취소</button> -->
|
|
||||||
<!-- <button class="btn btn-primary" @click="submitEdit">수정</button> -->
|
|
||||||
<SaveBtn class="btn btn-primary" @click="submitEdit"></SaveBtn>
|
<SaveBtn class="btn btn-primary" @click="submitEdit"></SaveBtn>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<template v-else>
|
<template v-else>
|
||||||
<p class="m-0">{{ comment.content }}</p>
|
<p class="m-0">{{ comment.content }}</p>
|
||||||
</template>
|
</template>
|
||||||
</div>
|
</div>
|
||||||
|
<!-- <p>현재 isDeleted 값: {{ isDeleted }}</p> -->
|
||||||
|
|
||||||
|
<!-- <template v-if="isDeleted">
|
||||||
|
<p class="m-0 text-muted">댓글이 삭제되었습니다.</p>
|
||||||
|
</template> -->
|
||||||
<PlusButton v-if="isPlusButton" @click="toggleComment" class="mt-6"/>
|
<PlusButton v-if="isPlusButton" @click="toggleComment" class="mt-6"/>
|
||||||
<BoardCommentArea v-if="isComment" :unknown="unknown" @submitComment="submitComment"/>
|
<BoardCommentArea v-if="isComment" :unknown="unknown" @submitComment="submitComment"/>
|
||||||
|
|
||||||
@ -58,10 +57,6 @@
|
|||||||
:key="child.commentId"
|
:key="child.commentId"
|
||||||
class="mt-8 pt-6 ps-10 border-top"
|
class="mt-8 pt-6 ps-10 border-top"
|
||||||
>
|
>
|
||||||
<!-- <p>대댓글 데이터(JSON): {{ JSON.stringify(child, null, 2) }}</p> -->
|
|
||||||
<!-- <p>comment child: {{ comment.children }}</p> -->
|
|
||||||
<!-- :unknown="child.author === '익명'" -->
|
|
||||||
<p>child.isCommentPassword: {{ child.isCommentPassword }}</p>
|
|
||||||
<BoardComment
|
<BoardComment
|
||||||
:comment="child"
|
:comment="child"
|
||||||
:unknown="child.author === '익명'"
|
:unknown="child.author === '익명'"
|
||||||
@ -70,12 +65,17 @@
|
|||||||
:isCommentProfile="true"
|
:isCommentProfile="true"
|
||||||
:isCommentAuthor="child.isCommentAuthor"
|
:isCommentAuthor="child.isCommentAuthor"
|
||||||
:isCommentPassword="isCommentPassword"
|
:isCommentPassword="isCommentPassword"
|
||||||
@editClick="$emit('editClick', $event)"
|
:currentPasswordCommentId="currentPasswordCommentId"
|
||||||
|
:passwordCommentAlert="passwordCommentAlert"
|
||||||
|
:password="password"
|
||||||
|
@editClick="handleReplyEditClick"
|
||||||
@deleteClick="$emit('deleteClick', child)"
|
@deleteClick="$emit('deleteClick', child)"
|
||||||
@submitEdit="(comment, editedContent) => $emit('submitEdit', comment, editedContent)"
|
@submitEdit="(comment, editedContent) => $emit('submitEdit', comment, editedContent)"
|
||||||
@cancelEdit="$emit('cancelEdit', child)"
|
@cancelEdit="$emit('cancelEdit', child)"
|
||||||
@submitComment="submitComment"
|
@submitComment="submitComment"
|
||||||
@updateReaction="handleUpdateReaction"
|
@updateReaction="handleUpdateReaction"
|
||||||
|
@submitPassword="$emit('submitPassword', child, password)"
|
||||||
|
@update:password="$emit('update:password', $event)"
|
||||||
/>
|
/>
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
@ -114,20 +114,29 @@ const props = defineProps({
|
|||||||
type: Boolean,
|
type: Boolean,
|
||||||
default: false
|
default: false
|
||||||
},
|
},
|
||||||
|
isDeleted: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false
|
||||||
|
},
|
||||||
isCommentPassword: {
|
isCommentPassword: {
|
||||||
type: Boolean,
|
type: Boolean,
|
||||||
default: false,
|
default: false,
|
||||||
},
|
},
|
||||||
passwordCommentAlert: {
|
passwordCommentAlert: {
|
||||||
type: String,
|
type: String,
|
||||||
default: false
|
default: ''
|
||||||
}
|
},
|
||||||
|
currentPasswordCommentId: {
|
||||||
|
type: Number
|
||||||
|
},
|
||||||
|
password:{
|
||||||
|
type: String
|
||||||
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
// emits 정의
|
// emits 정의
|
||||||
const emit = defineEmits(['submitComment', 'updateReaction', 'editClick', 'deleteClick', 'submitPassword', 'submitEdit', 'cancelEdit']);
|
const emit = defineEmits(['submitComment', 'updateReaction', 'editClick', 'deleteClick', 'submitPassword', 'submitEdit', 'cancelEdit', 'update:password']);
|
||||||
|
|
||||||
const password = ref('');
|
|
||||||
const localEditedContent = ref(props.comment.content);
|
const localEditedContent = ref(props.comment.content);
|
||||||
|
|
||||||
// 댓글 입력 창 토글
|
// 댓글 입력 창 토글
|
||||||
@ -154,8 +163,7 @@ const handleUpdateReaction = (reactionData) => {
|
|||||||
|
|
||||||
// 비밀번호 확인
|
// 비밀번호 확인
|
||||||
const logPasswordAndEmit = () => {
|
const logPasswordAndEmit = () => {
|
||||||
emit('submitPassword', props.comment, password.value);
|
emit('submitPassword', props.comment, props.password);
|
||||||
password.value = "";
|
|
||||||
};
|
};
|
||||||
|
|
||||||
watch(() => props.comment.isEditTextarea, (newVal) => {
|
watch(() => props.comment.isEditTextarea, (newVal) => {
|
||||||
@ -164,13 +172,26 @@ watch(() => props.comment.isEditTextarea, (newVal) => {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// watch(() => props.comment.isDeleted, () => {
|
||||||
|
// console.log("BoardComment - isDeleted 상태 변경됨:", newVal);
|
||||||
|
|
||||||
|
// if (newVal) {
|
||||||
|
// localEditedContent.value = "댓글이 삭제되었습니다."; // UI 반영
|
||||||
|
// props.comment.isEditTextarea = false;
|
||||||
|
// }
|
||||||
|
// });
|
||||||
|
|
||||||
// 수정버튼
|
// 수정버튼
|
||||||
const submitEdit = () => {
|
const submitEdit = () => {
|
||||||
emit('submitEdit', props.comment, localEditedContent.value);
|
emit('submitEdit', props.comment, localEditedContent.value);
|
||||||
};
|
};
|
||||||
|
|
||||||
const aaaa = () => {
|
const handleEditClick = () => {
|
||||||
emit('editClick', props.comment);
|
emit('editClick', props.comment);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const handleReplyEditClick = (comment) => {
|
||||||
|
emit('editClick', comment);
|
||||||
|
}
|
||||||
|
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@ -11,13 +11,9 @@
|
|||||||
</div> -->
|
</div> -->
|
||||||
<!-- 텍스트박스 -->
|
<!-- 텍스트박스 -->
|
||||||
<div class="w-100">
|
<div class="w-100">
|
||||||
<textarea
|
<textarea class="form-control" placeholder="댓글 달기" rows="3" v-model="comment"></textarea>
|
||||||
class="form-control"
|
|
||||||
placeholder="댓글 달기"
|
|
||||||
rows="3"
|
|
||||||
v-model="comment"
|
|
||||||
></textarea>
|
|
||||||
<span v-if="commentAlert" class="invalid-feedback d-block text-start ms-2">{{ commentAlert }}</span>
|
<span v-if="commentAlert" class="invalid-feedback d-block text-start ms-2">{{ commentAlert }}</span>
|
||||||
|
<span v-else class="invalid-feedback d-block text-start ms-2">{{ textAlert }}</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@ -26,12 +22,7 @@
|
|||||||
<div class="d-flex flex-wrap align-items-center">
|
<div class="d-flex flex-wrap align-items-center">
|
||||||
<!-- 익명 체크박스 (익명게시판일 경우에만)-->
|
<!-- 익명 체크박스 (익명게시판일 경우에만)-->
|
||||||
<div v-if="unknown" class="form-check form-check-inline mb-0 me-4">
|
<div v-if="unknown" class="form-check form-check-inline mb-0 me-4">
|
||||||
<input
|
<input class="form-check-input" type="checkbox" id="inlineCheckbox1" v-model="isCheck" />
|
||||||
class="form-check-input"
|
|
||||||
type="checkbox"
|
|
||||||
id="inlineCheckbox1"
|
|
||||||
v-model="isCheck"
|
|
||||||
/>
|
|
||||||
<label class="form-check-label" for="inlineCheckbox1">익명</label>
|
<label class="form-check-label" for="inlineCheckbox1">익명</label>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@ -46,15 +37,13 @@
|
|||||||
placeholder="비밀번호 입력"
|
placeholder="비밀번호 입력"
|
||||||
/>
|
/>
|
||||||
<span v-if="passwordAlert" class="invalid-feedback d-block text-start ms-2">{{ passwordAlert }}</span>
|
<span v-if="passwordAlert" class="invalid-feedback d-block text-start ms-2">{{ passwordAlert }}</span>
|
||||||
|
<span v-else class="invalid-feedback d-block text-start ms-2">{{ passwordAlert2 }}</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- 답변 쓰기 버튼 -->
|
<!-- 답변 쓰기 버튼 -->
|
||||||
<div class="ms-auto mt-3 mt-md-0">
|
<div class="ms-auto mt-3 mt-md-0">
|
||||||
<button class="btn btn-primary" @click="handleCommentSubmit">
|
<SaveBtn class="btn btn-primary" @click="handleCommentSubmit"></SaveBtn>
|
||||||
<!-- <i class="icon-base bx bx-check"></i> -->
|
|
||||||
확인
|
|
||||||
</button>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -62,7 +51,8 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup>
|
<script setup>
|
||||||
import { ref, defineEmits, defineProps, computed, watch } from 'vue';
|
import { ref, defineEmits, defineProps, watch, inject } from 'vue';
|
||||||
|
import SaveBtn from '../button/SaveBtn.vue';
|
||||||
|
|
||||||
const props = defineProps({
|
const props = defineProps({
|
||||||
unknown: {
|
unknown: {
|
||||||
@ -71,39 +61,68 @@ const props = defineProps({
|
|||||||
},
|
},
|
||||||
parentId: {
|
parentId: {
|
||||||
type: Number,
|
type: Number,
|
||||||
default: 0
|
default: 0,
|
||||||
},
|
},
|
||||||
passwordAlert: {
|
passwordAlert: {
|
||||||
type: String,
|
type: String,
|
||||||
default: false
|
default: '',
|
||||||
},
|
},
|
||||||
commentAlert: {
|
commentAlert: {
|
||||||
type: String,
|
type: String,
|
||||||
default: false
|
default: '',
|
||||||
}
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const $common = inject('common');
|
||||||
const comment = ref('');
|
const comment = ref('');
|
||||||
const password = ref('');
|
const password = ref('');
|
||||||
const isCheck = ref(props.unknown);
|
const isCheck = ref(false);
|
||||||
|
const textAlert = ref('');
|
||||||
|
const passwordAlert2 = ref('');
|
||||||
|
|
||||||
const emit = defineEmits(['submitComment']);
|
const emit = defineEmits(['submitComment']);
|
||||||
const LOCBRDTYP = isCheck.value ? '300102' : null;
|
|
||||||
function handleCommentSubmit() {
|
const handleCommentSubmit = () => {
|
||||||
|
if (!$common.isNotEmpty(comment.value)) {
|
||||||
|
textAlert.value = '댓글을 입력하세요';
|
||||||
|
return false;
|
||||||
|
} else {
|
||||||
|
textAlert.value = '';
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isCheck.value && !$common.isNotEmpty(password.value)) {
|
||||||
|
passwordAlert2.value = '비밀번호를 입력하세요';
|
||||||
|
return false;
|
||||||
|
} else {
|
||||||
|
passwordAlert2.value = '';
|
||||||
|
}
|
||||||
|
|
||||||
|
// 댓글 제출
|
||||||
emit('submitComment', {
|
emit('submitComment', {
|
||||||
comment: comment.value,
|
comment: comment.value,
|
||||||
password: isCheck.value ? password.value : '',
|
password: isCheck.value ? password.value : '',
|
||||||
isCheck: isCheck.value,
|
isCheck: isCheck.value,
|
||||||
LOCBRDTYP, // 익명일 경우 '300102' 설정
|
LOCBRDTYP: isCheck.value ? '300102' : null, // 익명일 경우 '300102' 설정
|
||||||
});
|
});
|
||||||
}
|
|
||||||
|
|
||||||
watch(() => props.passwordAlert, () => {
|
// 제출 후 입력 필드 리셋
|
||||||
if (!props.passwordAlert) {
|
resetCommentForm();
|
||||||
comment.value = '';
|
};
|
||||||
password.value = '';
|
|
||||||
|
// 입력 필드 리셋 함수 추가
|
||||||
|
const resetCommentForm = () => {
|
||||||
|
comment.value = '';
|
||||||
|
password.value = '';
|
||||||
|
isCheck.value = false;
|
||||||
|
};
|
||||||
|
|
||||||
|
watch(
|
||||||
|
() => props.passwordAlert,
|
||||||
|
() => {
|
||||||
|
if (!props.passwordAlert) {
|
||||||
|
resetCommentForm();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
});
|
);
|
||||||
|
|
||||||
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|||||||
@ -10,16 +10,19 @@
|
|||||||
:comment="comment"
|
:comment="comment"
|
||||||
:isCommentAuthor="comment.isCommentAuthor"
|
:isCommentAuthor="comment.isCommentAuthor"
|
||||||
:isEditTextarea="comment.isEditTextarea"
|
:isEditTextarea="comment.isEditTextarea"
|
||||||
|
:isDeleted="isDeleted"
|
||||||
:isCommentPassword="isCommentPassword"
|
:isCommentPassword="isCommentPassword"
|
||||||
:passwordCommentAlert="passwordCommentAlert"
|
:passwordCommentAlert="passwordCommentAlert || ''"
|
||||||
|
:currentPasswordCommentId="currentPasswordCommentId"
|
||||||
|
:password="password"
|
||||||
@editClick="handleEditClick"
|
@editClick="handleEditClick"
|
||||||
@deleteClick="handleDeleteClick"
|
@deleteClick="handleDeleteClick"
|
||||||
@submitPassword="submitPassword"
|
@submitPassword="submitPassword"
|
||||||
@submitComment="submitComment"
|
@submitComment="submitComment"
|
||||||
@commentDeleted="handleCommentDeleted"
|
|
||||||
@submitEdit="handleSubmitEdit"
|
@submitEdit="handleSubmitEdit"
|
||||||
@cancelEdit="handleCancelEdit"
|
@cancelEdit="handleCancelEdit"
|
||||||
@updateReaction="(reactionData) => handleUpdateReaction(reactionData, comment.commentId, comment.boardId)"
|
@updateReaction="(reactionData) => handleUpdateReaction(reactionData, comment.commentId, comment.boardId)"
|
||||||
|
@update:password="updatePassword"
|
||||||
/>
|
/>
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
@ -51,13 +54,23 @@ const props = defineProps({
|
|||||||
type: Boolean,
|
type: Boolean,
|
||||||
default: false,
|
default: false,
|
||||||
},
|
},
|
||||||
|
isDeleted: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false,
|
||||||
|
},
|
||||||
passwordCommentAlert: {
|
passwordCommentAlert: {
|
||||||
type: String,
|
type: String,
|
||||||
default: false
|
default: ''
|
||||||
}
|
},
|
||||||
|
currentPasswordCommentId: {
|
||||||
|
type: Number
|
||||||
|
},
|
||||||
|
password:{
|
||||||
|
type: String
|
||||||
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
const emit = defineEmits(['submitComment', 'updateReaction', 'editClick', 'deleteClick', 'submitPassword', 'clearPassword']);
|
const emit = defineEmits(['submitComment', 'updateReaction', 'editClick', 'deleteClick', 'submitPassword', 'clearPassword','submitEdit', 'update:password']);
|
||||||
|
|
||||||
const submitComment = (replyData) => {
|
const submitComment = (replyData) => {
|
||||||
emit('submitComment', replyData);
|
emit('submitComment', replyData);
|
||||||
@ -78,7 +91,11 @@ const submitPassword = (comment, password) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const handleEditClick = (comment) => {
|
const handleEditClick = (comment) => {
|
||||||
emit('editClick', comment);
|
if (comment.parentId) {
|
||||||
|
emit('editClick', comment); // 대댓글
|
||||||
|
} else {
|
||||||
|
emit('editClick', comment); // 댓글
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleSubmitEdit = (comment, editedContent) => {
|
const handleSubmitEdit = (comment, editedContent) => {
|
||||||
@ -100,4 +117,8 @@ const handleCancelEdit = (comment) => {
|
|||||||
emit('cancelEdit', comment); // 댓글 수정 취소
|
emit('cancelEdit', comment); // 댓글 수정 취소
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const updatePassword = (newPassword) => {
|
||||||
|
emit('update:password', newPassword);
|
||||||
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@ -2,8 +2,9 @@
|
|||||||
<div class="d-flex align-items-center flex-wrap">
|
<div class="d-flex align-items-center flex-wrap">
|
||||||
<div class="d-flex align-items-center">
|
<div class="d-flex align-items-center">
|
||||||
<div v-if="!unknown" class="avatar me-2">
|
<div v-if="!unknown" class="avatar me-2">
|
||||||
<img src="/img/avatars/2.png" alt="Avatar" class="rounded-circle" />
|
<img :src="getProfileImage(profilePath)" alt="Avatar" class="rounded-circle" />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="me-2">
|
<div class="me-2">
|
||||||
<h6 class="mb-0">{{ profileName }}</h6>
|
<h6 class="mb-0">{{ profileName }}</h6>
|
||||||
<div class="profile-detail">
|
<div class="profile-detail">
|
||||||
@ -22,8 +23,7 @@
|
|||||||
<!-- 버튼 영역 -->
|
<!-- 버튼 영역 -->
|
||||||
<div class="ms-auto text-end">
|
<div class="ms-auto text-end">
|
||||||
<!-- 수정, 삭제 버튼 -->
|
<!-- 수정, 삭제 버튼 -->
|
||||||
<!-- <template v-if="isAuthor || showDetail"> -->
|
<template v-if="!isDeletedComment && (unknown || isCommentAuthor || isAuthor)">
|
||||||
<template v-if="unknown || isCommentAuthor || isAuthor">
|
|
||||||
<EditButton @click.stop="editClick" />
|
<EditButton @click.stop="editClick" />
|
||||||
<DeleteButton @click.stop="deleteClick" />
|
<DeleteButton @click.stop="deleteClick" />
|
||||||
</template>
|
</template>
|
||||||
@ -34,18 +34,23 @@
|
|||||||
:boardId="boardId"
|
:boardId="boardId"
|
||||||
:comment="comment"
|
:comment="comment"
|
||||||
@updateReaction="handleUpdateReaction"
|
@updateReaction="handleUpdateReaction"
|
||||||
>
|
/>
|
||||||
</BoardRecommendBtn>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup>
|
<script setup>
|
||||||
import { ref, defineProps, defineEmits } from 'vue';
|
import { computed, defineProps, defineEmits } from 'vue';
|
||||||
import DeleteButton from '../button/DeleteBtn.vue';
|
import DeleteButton from '../button/DeleteBtn.vue';
|
||||||
import EditButton from '../button/EditBtn.vue';
|
import EditButton from '../button/EditBtn.vue';
|
||||||
import BoardRecommendBtn from '../button/BoardRecommendBtn.vue';
|
import BoardRecommendBtn from '../button/BoardRecommendBtn.vue';
|
||||||
|
|
||||||
|
// 기본 프로필 이미지 경로
|
||||||
|
const defaultProfile = "/img/icons/icon.png";
|
||||||
|
|
||||||
|
// 서버의 이미지 경로 (Vue 환경 변수 사용 가능)
|
||||||
|
const baseUrl = "http://localhost:10325/"; // API 서버 URL
|
||||||
|
|
||||||
// Props 정의
|
// Props 정의
|
||||||
const props = defineProps({
|
const props = defineProps({
|
||||||
comment: {
|
comment: {
|
||||||
@ -64,6 +69,10 @@ const props = defineProps({
|
|||||||
type: String,
|
type: String,
|
||||||
default: '',
|
default: '',
|
||||||
},
|
},
|
||||||
|
profilePath: {
|
||||||
|
type: String,
|
||||||
|
default: '',
|
||||||
|
},
|
||||||
unknown: {
|
unknown: {
|
||||||
type: Boolean,
|
type: Boolean,
|
||||||
default: true,
|
default: true,
|
||||||
@ -72,16 +81,15 @@ const props = defineProps({
|
|||||||
type: Boolean,
|
type: Boolean,
|
||||||
default: true,
|
default: true,
|
||||||
},
|
},
|
||||||
// 게시글의 작성자 여부를 확인 : 현재 로그인한 사용자가 이 게시글의 작성자인지 여부
|
|
||||||
isAuthor: {
|
isAuthor: {
|
||||||
type: Boolean,
|
type: Boolean,
|
||||||
default: false,
|
default: false,
|
||||||
},
|
},
|
||||||
isCommentAuthor: Boolean, // 댓글 작성자인지 여부
|
isCommentAuthor: Boolean,
|
||||||
isCommentProfile: Boolean, // 현재 컴포넌트가 댓글용인지 여부
|
isCommentProfile: Boolean,
|
||||||
date: {
|
date: {
|
||||||
type: String,
|
type: String,
|
||||||
required: true,
|
required: '',
|
||||||
},
|
},
|
||||||
views: {
|
views: {
|
||||||
type: Number,
|
type: Number,
|
||||||
@ -99,6 +107,11 @@ const props = defineProps({
|
|||||||
|
|
||||||
const emit = defineEmits(['updateReaction', 'editClick', 'deleteClick']);
|
const emit = defineEmits(['updateReaction', 'editClick', 'deleteClick']);
|
||||||
|
|
||||||
|
const isDeletedComment = computed(() => {
|
||||||
|
return props.comment?.content === '삭제된 댓글입니다' &&
|
||||||
|
props.comment?.updateAtRaw !== props.comment?.createdAtRaw;
|
||||||
|
});
|
||||||
|
|
||||||
// 수정
|
// 수정
|
||||||
const editClick = () => {
|
const editClick = () => {
|
||||||
emit('editClick', { ...props.comment, unknown: props.unknown });
|
emit('editClick', { ...props.comment, unknown: props.unknown });
|
||||||
@ -109,38 +122,19 @@ const deleteClick = () => {
|
|||||||
emit('deleteClick', { ...props.comment, unknown: props.unknown });
|
emit('deleteClick', { ...props.comment, unknown: props.unknown });
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// 좋아요/싫어요 업데이트
|
||||||
const handleUpdateReaction = (reactionData) => {
|
const handleUpdateReaction = (reactionData) => {
|
||||||
emit("updateReaction", {
|
emit("updateReaction", {
|
||||||
boardId: props.boardId,
|
boardId: props.boardId,
|
||||||
commentId: props.comment?.commentId,
|
commentId: props.comment?.commentId,
|
||||||
...reactionData,
|
...reactionData,
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// 프로필 이미지 경로 설정
|
||||||
|
const getProfileImage = (profilePath) => {
|
||||||
|
return profilePath && profilePath.trim()
|
||||||
|
? `${baseUrl}upload/img/profile/${profilePath}`
|
||||||
|
: defaultProfile;
|
||||||
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped>
|
|
||||||
.profile-detail span ~ span {
|
|
||||||
margin-left: 5px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.ms-auto button + button {
|
|
||||||
margin-left: 5px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.btn.author {
|
|
||||||
height: 30px;
|
|
||||||
}
|
|
||||||
|
|
||||||
@media screen and (max-width: 450px) {
|
|
||||||
.btn-area {
|
|
||||||
margin-top: 10px;
|
|
||||||
width: 100%;
|
|
||||||
}
|
|
||||||
|
|
||||||
.btn.author {
|
|
||||||
height: 30px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
|
|||||||
@ -13,7 +13,7 @@ import { ref, computed } from 'vue';
|
|||||||
const props = defineProps({
|
const props = defineProps({
|
||||||
comment: {
|
comment: {
|
||||||
type: Object,
|
type: Object,
|
||||||
required: true,
|
default: () => ({}),
|
||||||
},
|
},
|
||||||
likeClicked : {
|
likeClicked : {
|
||||||
type : Boolean,
|
type : Boolean,
|
||||||
@ -36,7 +36,7 @@ const props = defineProps({
|
|||||||
required: true,
|
required: true,
|
||||||
},
|
},
|
||||||
commentId: {
|
commentId: {
|
||||||
type: Number,
|
type: [Number, null],
|
||||||
default: null,
|
default: null,
|
||||||
},
|
},
|
||||||
likeCount: {
|
likeCount: {
|
||||||
|
|||||||
@ -1,13 +1,30 @@
|
|||||||
<template>
|
<template>
|
||||||
<button class="btn btn-label-primary btn-icon">
|
<button class="btn btn-label-primary btn-icon" @click="toggleText">
|
||||||
<i class="bx bx-edit-alt"></i>
|
<i :class="buttonClass"></i>
|
||||||
</button>
|
</button>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script setup>
|
||||||
export default {
|
import { ref, defineProps } from 'vue';
|
||||||
name: 'EditButton',
|
|
||||||
methods: {
|
const props = defineProps({
|
||||||
},
|
isToggleEnabled: {
|
||||||
};
|
type: Boolean,
|
||||||
|
default: false,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const buttonClass = ref("bx bx-edit-alt");
|
||||||
|
|
||||||
|
const toggleText = () => {
|
||||||
|
if (props.isToggleEnabled) {
|
||||||
|
buttonClass.value = buttonClass.value === "bx bx-edit-alt" ? "bx bx-x" : "bx bx-edit-alt";
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const resetButton = () => {
|
||||||
|
buttonClass.value = "bx bx-edit-alt";
|
||||||
|
};
|
||||||
|
|
||||||
|
defineExpose({ resetButton });
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@ -20,7 +20,8 @@
|
|||||||
|
|
||||||
<!-- 저장 버튼 -->
|
<!-- 저장 버튼 -->
|
||||||
<div class="save-button-container">
|
<div class="save-button-container">
|
||||||
<button class="btn btn-success" @click="addVacationRequests" :disabled="isDisabled">
|
<button class="btn btn-success" @click="addVacationRequests"
|
||||||
|
:class="{ active: !isDisabled, disabled: isDisabled }">
|
||||||
✔
|
✔
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
@ -28,26 +29,28 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup>
|
<script setup>
|
||||||
import { defineEmits, ref, defineProps } from "vue";
|
import { defineEmits, ref, defineProps, watch } from "vue";
|
||||||
|
|
||||||
const props = defineProps({
|
const props = defineProps({
|
||||||
isDisabled: Boolean
|
isDisabled: Boolean,
|
||||||
|
selectedDate: String // 날짜 선택 값을 props로 받음
|
||||||
});
|
});
|
||||||
|
|
||||||
const emit = defineEmits(["toggleHalfDay", "addVacationRequests", "resetHalfDay"]);
|
const emit = defineEmits(["toggleHalfDay", "addVacationRequests", "resetHalfDay"]);
|
||||||
const halfDayType = ref(null);
|
const halfDayType = ref(null);
|
||||||
|
|
||||||
const toggleHalfDay = (type) => {
|
const toggleHalfDay = (type) => {
|
||||||
halfDayType.value = type;
|
halfDayType.value = halfDayType.value === type ? null : type;
|
||||||
|
|
||||||
emit("toggleHalfDay", halfDayType.value);
|
emit("toggleHalfDay", halfDayType.value);
|
||||||
|
|
||||||
// ✅ 버튼 클릭 후 1초 후 자동 비활성화
|
|
||||||
setTimeout(() => {
|
|
||||||
halfDayType.value = null;
|
|
||||||
}, 1000);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// `selectedDate`가 변경되면 반차 선택 초기화
|
||||||
|
watch(() => props.selectedDate, (newDate) => {
|
||||||
|
if (newDate) {
|
||||||
|
resetHalfDay();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
// 날짜 선택 후 반차 버튼 상태 초기화
|
// 날짜 선택 후 반차 버튼 상태 초기화
|
||||||
const resetHalfDay = () => {
|
const resetHalfDay = () => {
|
||||||
halfDayType.value = null;
|
halfDayType.value = null;
|
||||||
@ -89,24 +92,24 @@ defineExpose({ resetHalfDay });
|
|||||||
|
|
||||||
/* 선택된 (눌린) 버튼 */
|
/* 선택된 (눌린) 버튼 */
|
||||||
.btn.active {
|
.btn.active {
|
||||||
border: 3px solid #fff; /* 흰색 테두리 강조 */
|
border: 3px solid #ff0000; /* 붉은색 테두리 적용 */
|
||||||
box-shadow: 0px 4px 15px rgba(0, 0, 0, 0.3);
|
box-shadow: 0px 4px 15px rgba(0, 0, 0, 0.3);
|
||||||
transform: scale(1.1);
|
transform: scale(1.1);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* AM 버튼 (선택된 상태) */
|
/* AM 버튼 (선택된 상태) */
|
||||||
.btn-warning.active {
|
.btn-warning.active {
|
||||||
background-color: #ffca2c !important; /* 진한 노란색 */
|
background-color: #ffca2c !important;
|
||||||
color: black;
|
color: black;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* PM 버튼 (선택된 상태) */
|
/* PM 버튼 (선택된 상태) */
|
||||||
.btn-info.active {
|
.btn-info.active {
|
||||||
background-color: #0b5ed7 !important; /* 진한 파란색 */
|
background-color: #0b5ed7 !important;
|
||||||
color: white;
|
color: white;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* ✔ 버튼 */
|
/* ✔ 버튼 기본 (비활성화일 때 기본 녹색) */
|
||||||
.btn-success {
|
.btn-success {
|
||||||
font-size: 24px;
|
font-size: 24px;
|
||||||
width: 50px;
|
width: 50px;
|
||||||
@ -116,18 +119,26 @@ defineExpose({ resetHalfDay });
|
|||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
transition: all 0.2s ease-in-out;
|
transition: all 0.2s ease-in-out;
|
||||||
|
background-color: #871919 !important;
|
||||||
|
color: white;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* ✔ 버튼 마우스 오버 */
|
/* ✔ 버튼 활성화 */
|
||||||
.btn-success:hover {
|
.btn-success.active {
|
||||||
background-color: #198754;
|
background-color: #ff0000 !important;
|
||||||
box-shadow: 0px 4px 10px rgba(25, 135, 84, 0.4);
|
color: white !important;
|
||||||
|
border: 3px solid #eb9f9f !important;
|
||||||
|
box-shadow: 0px 4px 15px rgba(0, 0, 0, 0.3);
|
||||||
transform: scale(1.1);
|
transform: scale(1.1);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* ✔ 버튼 클릭 */
|
/* ✔ 버튼 비활성화 */
|
||||||
.btn-success:active {
|
.btn-success.disabled {
|
||||||
transform: scale(0.95);
|
background-color: #bbb8b8 !important;
|
||||||
box-shadow: 0px 2px 5px rgba(25, 135, 84, 0.2);
|
color: white !important;
|
||||||
|
cursor: not-allowed !important;
|
||||||
|
box-shadow: none;
|
||||||
|
transform: none;
|
||||||
|
opacity: 0.5;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@ -1,9 +1,30 @@
|
|||||||
<template>
|
<template>
|
||||||
<button class="btn btn-label-primary btn-icon float-end">
|
<button class="btn btn-label-primary btn-icon float-end" @click="toggleText">
|
||||||
<i class="bx bx-edit"></i>
|
<i :class="buttonClass"></i>
|
||||||
</button>
|
</button>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup>
|
<script setup>
|
||||||
|
import { ref, defineProps, defineExpose } from 'vue';
|
||||||
|
|
||||||
</script>
|
const props = defineProps({
|
||||||
|
isToggleEnabled: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const buttonClass = ref("bx bx-edit");
|
||||||
|
|
||||||
|
const toggleText = () => {
|
||||||
|
if (props.isToggleEnabled) {
|
||||||
|
buttonClass.value = buttonClass.value === "bx bx-edit" ? "bx bx-x" : "bx bx-edit";
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const resetButton = () => {
|
||||||
|
buttonClass.value = "bx bx-edit";
|
||||||
|
};
|
||||||
|
|
||||||
|
defineExpose({ resetButton });
|
||||||
|
</script>
|
||||||
@ -1,11 +1,12 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="mb-4 row">
|
<div class="mb-4 row">
|
||||||
<label :for="name" 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">
|
||||||
<input
|
<input
|
||||||
class="form-control"
|
class="form-control"
|
||||||
type="file"
|
type="file"
|
||||||
:id="name"
|
:id="inputId"
|
||||||
|
ref="fileInput"
|
||||||
@change="changeHandler"
|
@change="changeHandler"
|
||||||
multiple
|
multiple
|
||||||
/>
|
/>
|
||||||
@ -21,7 +22,7 @@ import { ref ,computed} from 'vue';
|
|||||||
import { fileMsg } from '@/common/msgEnum';
|
import { fileMsg } from '@/common/msgEnum';
|
||||||
|
|
||||||
// Props
|
// Props
|
||||||
const prop = defineProps({
|
const props = defineProps({
|
||||||
title: {
|
title: {
|
||||||
type: String,
|
type: String,
|
||||||
default: '라벨',
|
default: '라벨',
|
||||||
@ -29,7 +30,7 @@ const prop = defineProps({
|
|||||||
},
|
},
|
||||||
name: {
|
name: {
|
||||||
type: String,
|
type: String,
|
||||||
default: 'nameplz',
|
default: 'fileInput',
|
||||||
required: true,
|
required: true,
|
||||||
},
|
},
|
||||||
isAlert: {
|
isAlert: {
|
||||||
@ -38,12 +39,13 @@ const prop = defineProps({
|
|||||||
required: false,
|
required: false,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
const inputId = computed(() => props.name || 'defaultFileInput');
|
||||||
|
|
||||||
const emits = defineEmits(['update:data', 'update:isValid']);
|
const emits = defineEmits(['update:data', 'update:isValid']);
|
||||||
|
|
||||||
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 = ['image/jpeg', 'image/png', 'application/pdf']; // 허용된 파일 유형
|
const ALLOWED_FILE_TYPES = []; // 모든 파일을 허용
|
||||||
|
|
||||||
const showError = ref(false);
|
const showError = ref(false);
|
||||||
const fileMsgKey = ref(''); // 에러 메시지 키
|
const fileMsgKey = ref(''); // 에러 메시지 키
|
||||||
@ -51,9 +53,12 @@ const fileMsgKey = ref(''); // 에러 메시지 키
|
|||||||
const changeHandler = (event) => {
|
const changeHandler = (event) => {
|
||||||
const files = Array.from(event.target.files);
|
const files = Array.from(event.target.files);
|
||||||
const totalSize = files.reduce((sum, file) => sum + file.size, 0);
|
const totalSize = files.reduce((sum, file) => sum + file.size, 0);
|
||||||
const invalidFiles = files.filter(file => !ALLOWED_FILE_TYPES.includes(file.type));
|
|
||||||
|
|
||||||
// 파일 검증 로직
|
// ALLOWED_FILE_TYPES가 비어있으면 모든 파일 허용
|
||||||
|
const invalidFiles = ALLOWED_FILE_TYPES.length > 0
|
||||||
|
? files.filter(file => !ALLOWED_FILE_TYPES.includes(file.type))
|
||||||
|
: [];
|
||||||
|
|
||||||
if (totalSize > MAX_TOTAL_SIZE) {
|
if (totalSize > MAX_TOTAL_SIZE) {
|
||||||
showError.value = true;
|
showError.value = true;
|
||||||
fileMsgKey.value = 'FileMaxSizeMsg';
|
fileMsgKey.value = 'FileMaxSizeMsg';
|
||||||
|
|||||||
@ -15,6 +15,7 @@
|
|||||||
:disabled="disabled"
|
:disabled="disabled"
|
||||||
:min="min"
|
:min="min"
|
||||||
@focusout="$emit('focusout', modelValue)"
|
@focusout="$emit('focusout', modelValue)"
|
||||||
|
@input="handleInput"
|
||||||
/>
|
/>
|
||||||
<div class="invalid-feedback" :class="isAlert ? 'd-block' : ''">
|
<div class="invalid-feedback" :class="isAlert ? 'd-block' : ''">
|
||||||
{{ title }}을 확인해주세요.
|
{{ title }}을 확인해주세요.
|
||||||
@ -92,11 +93,6 @@ const inputValue = ref(props.modelValue);
|
|||||||
// 부모로 데이터 업데이트
|
// 부모로 데이터 업데이트
|
||||||
watch(inputValue, (newValue) => {
|
watch(inputValue, (newValue) => {
|
||||||
emits('update:modelValue', newValue);
|
emits('update:modelValue', newValue);
|
||||||
|
|
||||||
// 값이 입력될 때 `alert`를 false로 설정
|
|
||||||
if (newValue.trim() !== '') {
|
|
||||||
emits('update:alert', false);
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
|
|
||||||
// 초기값 동기화
|
// 초기값 동기화
|
||||||
@ -106,6 +102,13 @@ watch(() => props.modelValue, (newValue) => {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const handleInput = (event) => {
|
||||||
|
const newValue = event.target.value.slice(0, props.maxlength);
|
||||||
|
|
||||||
|
if (newValue.trim() !== '') {
|
||||||
|
emits('update:alert', false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|||||||
@ -4,19 +4,30 @@
|
|||||||
{{ title }}
|
{{ title }}
|
||||||
<span :class="isEssential ? 'link-danger' : 'none'">*</span>
|
<span :class="isEssential ? 'link-danger' : 'none'">*</span>
|
||||||
</label>
|
</label>
|
||||||
<div :class="isRow ? 'col-md-10' : 'col-md-12'">
|
<div :class="isRow ? 'col-md-10' : 'col-md-12'" class="d-flex gap-2 align-items-center">
|
||||||
<select class="form-select" :id="name" v-model="selectData" :disabled="disabled">
|
<select class="form-select" :id="name" v-model="selectData" :disabled="disabled" :style="isColor ? { color: selected } : {}">
|
||||||
<option v-for="(item, i) in data" :key="i" :value="isCommon ? item.value : i">
|
<option v-for="(item, i) in data" :key="i" :value="isCommon ? item.value : i" :style="isColor ? { color: item.label } : {}">
|
||||||
{{ isCommon ? item.label : item }}
|
{{ isCommon ? item.label : item }}
|
||||||
</option>
|
</option>
|
||||||
</select>
|
</select>
|
||||||
|
|
||||||
|
<div v-if="isColor && selected"
|
||||||
|
class="w-px-40 h-px-30"
|
||||||
|
:style="{backgroundColor: selected}">
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<img v-if="isMbti && selected"
|
||||||
|
role="img"
|
||||||
|
class="w-px-30 h-px-40"
|
||||||
|
:src="`/img/mbti/${selected.toLowerCase()}.png`"
|
||||||
|
alt="MBTI image"/>
|
||||||
</div>
|
</div>
|
||||||
<div v-if="isAlert" class="invalid-feedback">{{ title }}을 확인해주세요.</div>
|
<div v-if="isAlert" class="invalid-feedback">{{ title }}을 확인해주세요.</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup>
|
<script setup>
|
||||||
import { ref, watch } from 'vue';
|
import { computed, ref, watch } from 'vue';
|
||||||
|
|
||||||
const props = defineProps({
|
const props = defineProps({
|
||||||
title: {
|
title: {
|
||||||
@ -67,7 +78,17 @@ const props = defineProps({
|
|||||||
disabled: {
|
disabled: {
|
||||||
type: Boolean,
|
type: Boolean,
|
||||||
default: false
|
default: false
|
||||||
}
|
},
|
||||||
|
isColor: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false,
|
||||||
|
required: false,
|
||||||
|
},
|
||||||
|
isMbti: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false,
|
||||||
|
required: false,
|
||||||
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
const emit = defineEmits(['update:data']);
|
const emit = defineEmits(['update:data']);
|
||||||
@ -93,4 +114,13 @@ watch(() => props.data, (newData) => {
|
|||||||
watch(selectData, (newValue) => {
|
watch(selectData, (newValue) => {
|
||||||
emit('update:data', newValue);
|
emit('update:data', newValue);
|
||||||
});
|
});
|
||||||
</script>
|
|
||||||
|
|
||||||
|
const selected = computed(() => {
|
||||||
|
const selectedItem = props.data.find(item =>
|
||||||
|
props.isCommon ? item.value === selectData.value : props.data.indexOf(item) === selectData.value
|
||||||
|
);
|
||||||
|
return selectedItem ? selectedItem.label : null;
|
||||||
|
});
|
||||||
|
|
||||||
|
</script>
|
||||||
|
|||||||
@ -38,7 +38,7 @@
|
|||||||
<div class="ms-12 position-relative">
|
<div class="ms-12 position-relative">
|
||||||
{{ address }} {{ addressdtail }}
|
{{ address }} {{ addressdtail }}
|
||||||
<!-- 팝오버 -->
|
<!-- 팝오버 -->
|
||||||
<div v-if="isPopoverVisible" class="position-absolute w-100 map text-end">
|
<div v-if="isPopoverVisible" class="position-absolute map ">
|
||||||
<button type="button" class="btn-close popover-close" @click.stop="isPopoverVisible = !isPopoverVisible"></button>
|
<button type="button" class="btn-close popover-close" @click.stop="isPopoverVisible = !isPopoverVisible"></button>
|
||||||
<div class="card">
|
<div class="card">
|
||||||
<div class="card-body p-1">
|
<div class="card-body p-1">
|
||||||
@ -46,8 +46,7 @@
|
|||||||
v-if="coordinates"
|
v-if="coordinates"
|
||||||
:lat="coordinates.lat"
|
:lat="coordinates.lat"
|
||||||
:lng="coordinates.lng"
|
:lng="coordinates.lng"
|
||||||
:draggable="false"
|
class="w-px-200 h-px-200"
|
||||||
class="w-100 h-px-200"
|
|
||||||
>
|
>
|
||||||
<KakaoMapMarker
|
<KakaoMapMarker
|
||||||
:lat="coordinates.lat"
|
:lat="coordinates.lat"
|
||||||
@ -310,6 +309,18 @@ const closeEditModal = () => {
|
|||||||
isEditModalOpen.value = false;
|
isEditModalOpen.value = false;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// 변경된 내용 있는지 확인
|
||||||
|
const hasChanges = computed(() => {
|
||||||
|
return selectedProject.value.PROJCTNAM !== props.title ||
|
||||||
|
selectedProject.value.PROJCTSTR !== props.strdate ||
|
||||||
|
selectedProject.value.PROJCTEND !== props.enddate ||
|
||||||
|
selectedProject.value.PROJCTZIP !== props.addressZip ||
|
||||||
|
selectedProject.value.PROJCTARR !== props.address ||
|
||||||
|
selectedProject.value.PROJCTDTL !== props.addressdtail ||
|
||||||
|
selectedProject.value.PROJCTDES !== props.description ||
|
||||||
|
selectedProject.value.PROJCTCOL !== props.projctCol;
|
||||||
|
});
|
||||||
|
|
||||||
// 프로젝트 수정
|
// 프로젝트 수정
|
||||||
const handleUpdate = () => {
|
const handleUpdate = () => {
|
||||||
nameAlert.value = selectedProject.value.PROJCTNAM.trim() === '';
|
nameAlert.value = selectedProject.value.PROJCTNAM.trim() === '';
|
||||||
@ -318,6 +329,11 @@ const handleUpdate = () => {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!hasChanges.value) {
|
||||||
|
toastStore.onToast('변경된 내용이 없습니다.', 'e');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
$api.patch('project/update', {
|
$api.patch('project/update', {
|
||||||
projctSeq: selectedProject.value.PROJCTSEQ,
|
projctSeq: selectedProject.value.PROJCTSEQ,
|
||||||
projctNam: selectedProject.value.PROJCTNAM,
|
projctNam: selectedProject.value.PROJCTNAM,
|
||||||
@ -336,7 +352,6 @@ const handleUpdate = () => {
|
|||||||
closeEditModal();
|
closeEditModal();
|
||||||
// 상위 컴포넌트에 업데이트 알림
|
// 상위 컴포넌트에 업데이트 알림
|
||||||
emit('update');
|
emit('update');
|
||||||
window.location.reload()
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|||||||
@ -49,6 +49,7 @@
|
|||||||
:is-essential="true"
|
:is-essential="true"
|
||||||
:is-label="true"
|
:is-label="true"
|
||||||
:is-common="true"
|
:is-common="true"
|
||||||
|
:is-color="true"
|
||||||
:data="colorList"
|
:data="colorList"
|
||||||
@update:data="color = $event"
|
@update:data="color = $event"
|
||||||
/>
|
/>
|
||||||
@ -100,7 +101,7 @@
|
|||||||
import { computed, ref, watch, onMounted, inject } from 'vue';
|
import { computed, ref, watch, onMounted, inject } from 'vue';
|
||||||
import SearchBar from '@c/search/SearchBar.vue';
|
import SearchBar from '@c/search/SearchBar.vue';
|
||||||
import ProjectCard from '@c/list/ProjectCard.vue';
|
import ProjectCard from '@c/list/ProjectCard.vue';
|
||||||
import CategoryBtn from '@c/category/CategoryBtn.vue';
|
import CategoryBtn from '@/components/category/CategoryBtn.vue';
|
||||||
import WriteBtn from '@c/button/WriteBtn.vue';
|
import WriteBtn from '@c/button/WriteBtn.vue';
|
||||||
import CenterModal from '@c/modal/CenterModal.vue';
|
import CenterModal from '@c/modal/CenterModal.vue';
|
||||||
import FormSelect from '@c/input/FormSelect.vue';
|
import FormSelect from '@c/input/FormSelect.vue';
|
||||||
|
|||||||
@ -68,20 +68,21 @@
|
|||||||
:is-alert="passwordcheckAlert"
|
:is-alert="passwordcheckAlert"
|
||||||
@update:data="passwordcheck = $event"
|
@update:data="passwordcheck = $event"
|
||||||
@update:alert="passwordcheckAlert = $event"
|
@update:alert="passwordcheckAlert = $event"
|
||||||
@blur="checkPw"
|
@input="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>
|
||||||
|
|
||||||
<div class="d-grid gap-2 mt-5 mb-5">
|
<div class="d-flex gap-2 mt-7 mb-3">
|
||||||
<SaveBtn @click="handleNewPassword" />
|
<BackBtn class=" w-50" @click="handleback"/>
|
||||||
<p v-if="pwErrMsg" class="invalid-feedback d-block mb-0">{{ pwErrMsg }}</p>
|
<SaveBtn class="w-50" @click="handleNewPassword" />
|
||||||
</div>
|
</div>
|
||||||
|
<p v-if="pwErrMsg" class="invalid-feedback d-block mb-0">{{ pwErrMsg }}</p>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup>
|
<script setup>
|
||||||
import { ref } from 'vue';
|
import { ref, watch } from 'vue';
|
||||||
import $api from '@api';
|
import $api from '@api';
|
||||||
import commonApi from '@/common/commonApi';
|
import commonApi from '@/common/commonApi';
|
||||||
import { useRouter } from 'vue-router';
|
import { useRouter } from 'vue-router';
|
||||||
@ -112,7 +113,7 @@
|
|||||||
|
|
||||||
const passwordAlert = ref(false);
|
const passwordAlert = ref(false);
|
||||||
const passwordcheckAlert = ref(false);
|
const passwordcheckAlert = ref(false);
|
||||||
const passwordcheckErrorAlert = ref(false);
|
const passwordMismatch = ref(false);
|
||||||
|
|
||||||
const { pwhintList } = commonApi({
|
const { pwhintList } = commonApi({
|
||||||
loadPwhint: true,
|
loadPwhint: true,
|
||||||
@ -127,6 +128,13 @@
|
|||||||
router.push('/login');
|
router.push('/login');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 비밀번호와 비밀번호 확인이 변경될 때마다 유효성 검사
|
||||||
|
watch([password, passwordcheck], () => {
|
||||||
|
if (passwordcheck.value !== '') {
|
||||||
|
checkPw();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
// 아이디, 생년월일, 비밀번호 힌트, 답변이 일치하는 member 재설정 input 보이기
|
// 아이디, 생년월일, 비밀번호 힌트, 답변이 일치하는 member 재설정 input 보이기
|
||||||
const handleSubmit = async () => {
|
const handleSubmit = async () => {
|
||||||
userCheckMsg.value = '';
|
userCheckMsg.value = '';
|
||||||
@ -153,13 +161,13 @@
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const checkPw = async () => {
|
const checkPw = () => {
|
||||||
if (password.value !== passwordcheck.value) {
|
if (password.value !== passwordcheck.value) {
|
||||||
passwordcheckError.value = '비밀번호가 일치하지 않습니다.';
|
passwordcheckError.value = '비밀번호가 일치하지 않습니다.';
|
||||||
passwordcheckErrorAlert.value = true;
|
passwordMismatch.value = true;
|
||||||
} else {
|
} else {
|
||||||
passwordcheckError.value = '';
|
passwordcheckError.value = '';
|
||||||
passwordcheckErrorAlert.value = false;
|
passwordMismatch.value = false;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -168,8 +176,10 @@
|
|||||||
pwErrMsg.value = '';
|
pwErrMsg.value = '';
|
||||||
passwordAlert.value = password.value.trim() === '';
|
passwordAlert.value = password.value.trim() === '';
|
||||||
passwordcheckAlert.value = passwordcheck.value.trim() === '';
|
passwordcheckAlert.value = passwordcheck.value.trim() === '';
|
||||||
|
|
||||||
checkPw();
|
checkPw();
|
||||||
if (passwordAlert.value || passwordcheckAlert.value || passwordcheckErrorAlert.value) {
|
|
||||||
|
if (passwordAlert.value || passwordcheckAlert.value || passwordMismatch.value) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -192,6 +202,5 @@
|
|||||||
toastStore.onToast('비밀번호가 재설정 되었습니다.', 's');
|
toastStore.onToast('비밀번호가 재설정 되었습니다.', 's');
|
||||||
router.push('/login');
|
router.push('/login');
|
||||||
}
|
}
|
||||||
|
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@ -92,6 +92,7 @@
|
|||||||
:is-row="false"
|
:is-row="false"
|
||||||
:is-label="true"
|
:is-label="true"
|
||||||
:is-common="true"
|
:is-common="true"
|
||||||
|
:is-color="true"
|
||||||
:data="colorList"
|
:data="colorList"
|
||||||
@update:data="color = $event"
|
@update:data="color = $event"
|
||||||
class="w-50"
|
class="w-50"
|
||||||
@ -118,6 +119,7 @@
|
|||||||
:is-row="false"
|
:is-row="false"
|
||||||
:is-label="true"
|
:is-label="true"
|
||||||
:is-common="true"
|
:is-common="true"
|
||||||
|
:is-mbti="true"
|
||||||
:data="mbtiList"
|
:data="mbtiList"
|
||||||
@update:data="mbti = $event"
|
@update:data="mbti = $event"
|
||||||
class="w-50"
|
class="w-50"
|
||||||
|
|||||||
@ -1,7 +1,7 @@
|
|||||||
<template>
|
<template>
|
||||||
<ul class="list-unstyled users-list d-flex align-items-center gap-1">
|
<ul class="list-unstyled users-list d-flex align-items-center gap-1">
|
||||||
<li
|
<li
|
||||||
v-for="(user, index) in userList"
|
v-for="(user, index) in sortedUserList"
|
||||||
:key="index"
|
:key="index"
|
||||||
class="avatar pull-up"
|
class="avatar pull-up"
|
||||||
:class="{ 'opacity-100': isUserDisabled(user) }"
|
:class="{ 'opacity-100': isUserDisabled(user) }"
|
||||||
@ -25,7 +25,7 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup>
|
<script setup>
|
||||||
import { onMounted, ref, nextTick } from 'vue';
|
import { onMounted, ref, nextTick, computed } from 'vue';
|
||||||
import { useUserStore } from '@s/userList';
|
import { useUserStore } from '@s/userList';
|
||||||
import $api from '@api';
|
import $api from '@api';
|
||||||
|
|
||||||
@ -41,6 +41,19 @@ const props = defineProps({
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// 활성화된 회원을 앞으로 정렬하는 computed 속성
|
||||||
|
const sortedUserList = computed(() => {
|
||||||
|
return [...userList.value].sort((a, b) => {
|
||||||
|
const aDisabled = isUserDisabled(a);
|
||||||
|
const bDisabled = isUserDisabled(b);
|
||||||
|
|
||||||
|
// 활성화된 사용자가 먼저 오도록 정렬
|
||||||
|
if (!aDisabled && bDisabled) return -1;
|
||||||
|
if (aDisabled && !bDisabled) return 1;
|
||||||
|
return 0;
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
// 사용자의 프로젝트 참여 상태 확인
|
// 사용자의 프로젝트 참여 상태 확인
|
||||||
const fetchProjectParticipation = async () => {
|
const fetchProjectParticipation = async () => {
|
||||||
if (props.projctSeq) {
|
if (props.projctSeq) {
|
||||||
@ -79,25 +92,31 @@ const isUserDisabled = (user) => {
|
|||||||
|
|
||||||
// 클릭 시 활성화/비활성화 및 DB 업데이트
|
// 클릭 시 활성화/비활성화 및 DB 업데이트
|
||||||
const toggleDisable = async (index) => {
|
const toggleDisable = async (index) => {
|
||||||
const user = userList.value[index];
|
const user = sortedUserList.value[index];
|
||||||
if (user) {
|
if (user) {
|
||||||
const newParticipationStatus = props.projctSeq
|
const newParticipationStatus = props.projctSeq
|
||||||
? user.PROJCTYON === '1'
|
? user.PROJCTYON === '1'
|
||||||
: !user.disabled;
|
: !user.disabled;
|
||||||
|
|
||||||
if (props.projctSeq) {
|
if (props.projctSeq) {
|
||||||
const response = await $api.patch('project/updateYon', {
|
const response = await $api.patch('project/updateYon', {
|
||||||
memberSeq: user.MEMBERSEQ,
|
memberSeq: user.MEMBERSEQ,
|
||||||
projctSeq: props.projctSeq,
|
projctSeq: props.projctSeq,
|
||||||
projctYon: newParticipationStatus ? '0' : '1'
|
projctYon: newParticipationStatus ? '0' : '1'
|
||||||
});
|
});
|
||||||
|
|
||||||
if (response.status === 200) {
|
if (response.status === 200) {
|
||||||
user.PROJCTYON = newParticipationStatus ? '0' : '1';
|
// 원래 userList에서 해당 사용자를 찾아 업데이트
|
||||||
|
const originalIndex = userList.value.findIndex(u => u.MEMBERSEQ === user.MEMBERSEQ);
|
||||||
|
if (originalIndex !== -1) {
|
||||||
|
userList.value[originalIndex].PROJCTYON = newParticipationStatus ? '0' : '1';
|
||||||
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
user.disabled = newParticipationStatus;
|
// 원래 userList에서 해당 사용자를 찾아 업데이트
|
||||||
emitUserListUpdate();
|
const originalIndex = userList.value.findIndex(u => u.MEMBERSEQ === user.MEMBERSEQ);
|
||||||
|
if (originalIndex !== -1) {
|
||||||
|
userList.value[originalIndex].disabled = newParticipationStatus;
|
||||||
|
emitUserListUpdate();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|||||||
@ -18,7 +18,6 @@
|
|||||||
type="checkbox"
|
type="checkbox"
|
||||||
class="form-check-input admin-chk"
|
class="form-check-input admin-chk"
|
||||||
:name="item.WRDDICSEQ"
|
:name="item.WRDDICSEQ"
|
||||||
id=""
|
|
||||||
@change="toggleCheck($event)"
|
@change="toggleCheck($event)"
|
||||||
>
|
>
|
||||||
<div class="d-flex align-ite-center">
|
<div class="d-flex align-ite-center">
|
||||||
@ -66,7 +65,7 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="edit-btn" v-if="userStore.user.role !== 'ROLE_ADMIN'">
|
<div class="edit-btn" v-if="userStore.user.role !== 'ROLE_ADMIN'">
|
||||||
<EditBtn @click="writeStore.toggleItem(item.WRDDICSEQ)" />
|
<EditBtn ref="writeButton" @click="writeStore.toggleItem(item.WRDDICSEQ)" :isToggleEnabled="true"/>
|
||||||
</div>
|
</div>
|
||||||
</li>
|
</li>
|
||||||
</template>
|
</template>
|
||||||
@ -74,7 +73,7 @@
|
|||||||
<script setup>
|
<script setup>
|
||||||
import axios from "@api";
|
import axios from "@api";
|
||||||
import { useToastStore } from '@s/toastStore';
|
import { useToastStore } from '@s/toastStore';
|
||||||
import { ref, toRefs, getCurrentInstance, } from 'vue';
|
import { getCurrentInstance, ref } from 'vue';
|
||||||
import EditBtn from '@/components/button/EditBtn.vue';
|
import EditBtn from '@/components/button/EditBtn.vue';
|
||||||
import $api from '@api';
|
import $api from '@api';
|
||||||
import DictWrite from './DictWrite.vue';
|
import DictWrite from './DictWrite.vue';
|
||||||
@ -82,6 +81,7 @@ import { useUserInfoStore } from '@s/useUserInfoStore';
|
|||||||
import { useWriteVisibleStore } from '@s/writeVisible';
|
import { useWriteVisibleStore } from '@s/writeVisible';
|
||||||
|
|
||||||
const writeStore = useWriteVisibleStore();
|
const writeStore = useWriteVisibleStore();
|
||||||
|
const writeButton = ref(null);
|
||||||
|
|
||||||
// 유저 구분
|
// 유저 구분
|
||||||
const userStore = useUserInfoStore();
|
const userStore = useUserInfoStore();
|
||||||
@ -103,68 +103,12 @@ const props = defineProps({
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
// 카테고리
|
|
||||||
// const localCateList = ref([...props.cateList]);
|
|
||||||
// 선택 카테고리
|
|
||||||
// const selectedCategory = ref('');
|
|
||||||
|
|
||||||
// cateList emit
|
// cateList emit
|
||||||
const emit = defineEmits(['update:cateList','refreshWordList', 'updateChecked']);
|
const emit = defineEmits(['update:cateList','refreshWordList', 'updateChecked']);
|
||||||
|
|
||||||
// 글 수정 상태
|
|
||||||
// const isWriteVisible = ref(false);
|
|
||||||
|
|
||||||
|
|
||||||
// 글 수정 toggle
|
|
||||||
// const toggleWriteVisible = () => {
|
|
||||||
// isWriteVisible.value = !isWriteVisible.value;
|
|
||||||
// };
|
|
||||||
|
|
||||||
//카테고리 등록 수정
|
|
||||||
// const addCategory = (data) => {
|
|
||||||
// try {
|
|
||||||
// const lastCategory = localCateList.value.length > 0
|
|
||||||
// ? localCateList.value[localCateList.value.length - 1]
|
|
||||||
// : null;
|
|
||||||
// const newValue = lastCategory ? lastCategory.value + 1 : 600101;
|
|
||||||
|
|
||||||
// // // console.log('lastCategory', lastCategory);
|
|
||||||
// // // console.log('newValue', newValue);
|
|
||||||
|
|
||||||
// axios.post('worddict/insertCategory', {
|
|
||||||
// CMNCODNAM: data
|
|
||||||
// }).then(res => {
|
|
||||||
// if(res.data.data === 1){
|
|
||||||
// toastStore.onToast('카테고리가 추가 등록 되었습니다.', 's');
|
|
||||||
// const newCategory = { label: data, value: newValue };
|
|
||||||
// localCateList.value = [newCategory, ...localCateList.value];
|
|
||||||
// selectedCategory.value = newCategory.value;
|
|
||||||
|
|
||||||
// // // console.log('newCategory', newCategory);
|
|
||||||
// // // console.log('localCateList.value', localCateList.value);
|
|
||||||
// // // console.log('selectedCategory.value', selectedCategory.value);
|
|
||||||
|
|
||||||
// // // 부모에게 전달
|
|
||||||
// emit('update:cateList', localCateList.value);
|
|
||||||
// } else if(res.data.message == '이미 존재하는 카테고리명입니다.') {
|
|
||||||
// toastStore.onToast(res.data.message, 'e');
|
|
||||||
// }
|
|
||||||
// }).catch(err => {
|
|
||||||
// console.error('카테고리 추가 중 오류:', err);
|
|
||||||
// });
|
|
||||||
// } catch (err) {
|
|
||||||
// console.error('카테고리 추가 함수 오류:', err);
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
|
|
||||||
|
|
||||||
// 용어집 수정
|
// 용어집 수정
|
||||||
const editWord = (data) => {
|
const editWord = (data) => {
|
||||||
// console.log('📌 수정할 데이터:', data);
|
|
||||||
// console.log('📌 수정할 데이터:', data.id);
|
|
||||||
// console.log('📌 수정할 데이터:', data.category);
|
|
||||||
// console.log('📌 수정할 데이터:', data.title);
|
|
||||||
// console.log('📌 수정할 데이터:', $common.deltaAsJson(data.content));
|
|
||||||
|
|
||||||
if (!data.id) {
|
if (!data.id) {
|
||||||
console.error('❌ 수정할 데이터의 ID가 없습니다.');
|
console.error('❌ 수정할 데이터의 ID가 없습니다.');
|
||||||
@ -181,8 +125,10 @@ const editWord = (data) => {
|
|||||||
.then((res) => {
|
.then((res) => {
|
||||||
if (res.data.data === 1) {
|
if (res.data.data === 1) {
|
||||||
toastStore.onToast('✅ 용어가 수정되었습니다.', 's');
|
toastStore.onToast('✅ 용어가 수정되었습니다.', 's');
|
||||||
// isWriteVisible.value = false;
|
|
||||||
writeStore.closeAll();
|
writeStore.closeAll();
|
||||||
|
if (writeButton.value) {
|
||||||
|
writeButton.value.resetButton();
|
||||||
|
}
|
||||||
emit('refreshWordList');
|
emit('refreshWordList');
|
||||||
} else {
|
} else {
|
||||||
console.warn('⚠️ 서버 응답이 예상과 다릅니다:', res.data);
|
console.warn('⚠️ 서버 응답이 예상과 다릅니다:', res.data);
|
||||||
@ -216,10 +162,13 @@ const toggleCheck = (event) => {
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
/* 동그란 테두리 설정 */
|
.avatar {
|
||||||
|
cursor: default;
|
||||||
|
}
|
||||||
|
|
||||||
.user-avatar {
|
.user-avatar {
|
||||||
border: 3px solid; /* 테두리 */
|
border: 3px solid;
|
||||||
padding: 0.1px; /* 테두리와 이미지 사이의 간격 */
|
padding: 0.1px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.edit-btn {
|
.edit-btn {
|
||||||
|
|||||||
@ -29,11 +29,6 @@
|
|||||||
@focusout="handleCategoryFocusout(addCategory)"
|
@focusout="handleCategoryFocusout(addCategory)"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<!-- <div class="col-2 btn-margin">
|
|
||||||
<button class="btn btn-primary btn-icon" @click="saveInput">
|
|
||||||
<i class="bx bx-check"></i>
|
|
||||||
</button>
|
|
||||||
</div> -->
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="dict-w">
|
<div class="dict-w">
|
||||||
@ -65,14 +60,6 @@ import QEditor from '@/components/editor/QEditor.vue';
|
|||||||
import FormInput from '@/components/input/FormInput.vue';
|
import FormInput from '@/components/input/FormInput.vue';
|
||||||
import FormSelect from '@/components/input/FormSelect.vue';
|
import FormSelect from '@/components/input/FormSelect.vue';
|
||||||
import PlusBtn from '../button/PlusBtn.vue';
|
import PlusBtn from '../button/PlusBtn.vue';
|
||||||
// import { clearConfig } from 'dompurify';
|
|
||||||
// import { useUserInfoStore } from '@s/useUserInfoStore';
|
|
||||||
|
|
||||||
// 유저 구분
|
|
||||||
// const userStore = useUserInfoStore();
|
|
||||||
|
|
||||||
// 유저 상태에 따른 disabled
|
|
||||||
// const isDisabled = computed(() => userStore.user.role !== 'ROLE_ADMIN');
|
|
||||||
|
|
||||||
const emit = defineEmits(['close','addCategory','addWord']);
|
const emit = defineEmits(['close','addCategory','addWord']);
|
||||||
|
|
||||||
@ -133,20 +120,6 @@ const toggleInput = () => {
|
|||||||
showInput.value = !showInput.value;
|
showInput.value = !showInput.value;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
// 카테고리 저장
|
|
||||||
// const saveInput = () => {
|
|
||||||
// if(addCategory.value == ''){
|
|
||||||
// addCategoryAlert.value = true;
|
|
||||||
// return;
|
|
||||||
// }else {
|
|
||||||
// addCategoryAlert.value = false;
|
|
||||||
// }
|
|
||||||
// console.log('입력값 저장됨!',addCategory.value);
|
|
||||||
// emit('addCategory', addCategory.value);
|
|
||||||
// showInput.value = false;
|
|
||||||
// };
|
|
||||||
|
|
||||||
const onChange = (newValue) => {
|
const onChange = (newValue) => {
|
||||||
selectCategory.value = newValue.target.value;
|
selectCategory.value = newValue.target.value;
|
||||||
};
|
};
|
||||||
@ -199,11 +172,9 @@ const handleCategoryFocusout = (value) => {
|
|||||||
const valueTrim = value.trim();
|
const valueTrim = value.trim();
|
||||||
|
|
||||||
const existingCategory = props.dataList.find(item => item.label === valueTrim);
|
const existingCategory = props.dataList.find(item => item.label === valueTrim);
|
||||||
// console.log('existingCategory', existingCategory);
|
|
||||||
|
|
||||||
// 카테고리 입력시 공백
|
// 카테고리 입력시 공백
|
||||||
if(valueTrim == ''){
|
if(valueTrim == ''){
|
||||||
//alert('공백 ㄴㄴ');
|
|
||||||
addCategoryAlert.value = true;
|
addCategoryAlert.value = true;
|
||||||
|
|
||||||
// 공백시 강제 focus
|
// 공백시 강제 focus
|
||||||
|
|||||||
@ -32,17 +32,17 @@
|
|||||||
|
|
||||||
window.isDarkStyle = window.Helpers.isDarkStyle();
|
window.isDarkStyle = window.Helpers.isDarkStyle();
|
||||||
|
|
||||||
const loadScript = src => {
|
const loadScript = src => {
|
||||||
const script = document.createElement('script');
|
const script = document.createElement('script');
|
||||||
script.src = src;
|
script.src = src;
|
||||||
script.type = 'text/javascript';
|
script.type = 'text/javascript';
|
||||||
script.async = true;
|
script.async = true;
|
||||||
document.body.appendChild(script);
|
document.body.appendChild(script);
|
||||||
};
|
};
|
||||||
nextTick(async () => {
|
nextTick(async () => {
|
||||||
await wait(200);
|
await wait(200);
|
||||||
loadScript('/vendor/js/menu.js');
|
loadScript('/vendor/js/menu.js');
|
||||||
// loadScript('/js/main.js');
|
loadScript('/js/main.js');
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
<style></style>
|
<style></style>
|
||||||
|
|||||||
@ -23,9 +23,10 @@
|
|||||||
<span class="app-brand-text demo menu-text fw-bold ms-2">LOCALNET</span>
|
<span class="app-brand-text demo menu-text fw-bold ms-2">LOCALNET</span>
|
||||||
</a>
|
</a>
|
||||||
|
|
||||||
<a href="javascript:void(0);" class="layout-menu-toggle menu-link text-large ms-auto">
|
<!-- 메뉴 토글바 -->
|
||||||
|
<!-- <a href="javascript:void(0);" class="layout-menu-toggle menu-link text-large ms-auto">
|
||||||
<i class="bx bx-chevron-left bx-sm d-flex align-items-center justify-content-center"></i>
|
<i class="bx bx-chevron-left bx-sm d-flex align-items-center justify-content-center"></i>
|
||||||
</a>
|
</a> -->
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="menu-inner-shadow"></div>
|
<div class="menu-inner-shadow"></div>
|
||||||
|
|||||||
@ -241,7 +241,7 @@
|
|||||||
|
|
||||||
const user = ref(null);
|
const user = ref(null);
|
||||||
//const baseUrl = $api.defaults.baseURL.replace(/api\/$/, '');
|
//const baseUrl = $api.defaults.baseURL.replace(/api\/$/, '');
|
||||||
const baseUrl = import.meta.env.BASE_URL;
|
const baseUrl = import.meta.env.VITE_SERVER;
|
||||||
|
|
||||||
const authStore = useAuthStore();
|
const authStore = useAuthStore();
|
||||||
const userStore = useUserInfoStore();
|
const userStore = useUserInfoStore();
|
||||||
|
|||||||
@ -18,8 +18,8 @@
|
|||||||
<option value="50">50개씩</option>
|
<option value="50">50개씩</option>
|
||||||
</select>
|
</select>
|
||||||
|
|
||||||
<!-- 셀렉트 박스 -->
|
<!-- 셀렉트 박스 -->
|
||||||
<select class="form-select w-25 w-md-100" v-model="selectedOrder" @change="handleSortChange">
|
<select class="form-select w-25 w-md-100" v-model="selectedOrder" @change="handleSortChange">
|
||||||
<option value="date">최신날짜</option>
|
<option value="date">최신날짜</option>
|
||||||
<option value="views">조회수</option>
|
<option value="views">조회수</option>
|
||||||
</select>
|
</select>
|
||||||
@ -59,8 +59,9 @@
|
|||||||
<td class="text-center">공지</td>
|
<td class="text-center">공지</td>
|
||||||
<td class="cursor-pointer">
|
<td class="cursor-pointer">
|
||||||
📌 {{ notice.title }}
|
📌 {{ notice.title }}
|
||||||
|
<span v-if="notice.commentCount" class="comment-count">[ {{ notice.commentCount }} ]</span>
|
||||||
<i v-if="notice.img" class="bi bi-image me-1"></i>
|
<i v-if="notice.img" class="bi bi-image me-1"></i>
|
||||||
<i v-if="notice.hasAttachment" class="bi bi-paperclip"></i>
|
<i v-if="Array.isArray(notice.hasAttachment) && notice.hasAttachment.length > 0" class="bi bi-paperclip"></i>
|
||||||
<span v-if="isNewPost(notice.rawDate)" class="badge bg-danger text-white ms-2 fs-tiny">N</span>
|
<span v-if="isNewPost(notice.rawDate)" class="badge bg-danger text-white ms-2 fs-tiny">N</span>
|
||||||
</td>
|
</td>
|
||||||
<td class="text-center">{{ notice.author }}</td>
|
<td class="text-center">{{ notice.author }}</td>
|
||||||
@ -76,8 +77,9 @@
|
|||||||
<td class="text-center">{{ post.id }}</td>
|
<td class="text-center">{{ post.id }}</td>
|
||||||
<td class="cursor-pointer">
|
<td class="cursor-pointer">
|
||||||
{{ post.title }}
|
{{ post.title }}
|
||||||
|
<span v-if="post.commentCount" class="comment-count">[ {{ post.commentCount }} ]</span>
|
||||||
<i v-if="post.img" class="bi bi-image me-1"></i>
|
<i v-if="post.img" class="bi bi-image me-1"></i>
|
||||||
<i v-if="post.hasAttachment" class="bi bi-paperclip"></i>
|
<i v-if="Array.isArray(post.hasAttachment) && post.hasAttachment.length > 0" class="bi bi-paperclip"></i>
|
||||||
<span v-if="isNewPost(post.rawDate)" class="badge bg-danger text-white ms-2 fs-tiny">N</span>
|
<span v-if="isNewPost(post.rawDate)" class="badge bg-danger text-white ms-2 fs-tiny">N</span>
|
||||||
</td>
|
</td>
|
||||||
<td class="text-center">{{ post.author }}</td>
|
<td class="text-center">{{ post.author }}</td>
|
||||||
@ -195,11 +197,7 @@ const fetchGeneralPosts = async (page = 1) => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
if (data?.data) {
|
if (data?.data) {
|
||||||
// console.log(data)
|
const totalPosts = data.data.total;
|
||||||
const totalPosts = data.data.total; // 전체 게시물 개수 받아오기
|
|
||||||
|
|
||||||
// console.log('📌 API 응답 데이터:', data.data);
|
|
||||||
|
|
||||||
generalList.value = data.data.list.map((post, index) => ({
|
generalList.value = data.data.list.map((post, index) => ({
|
||||||
realId: post.id,
|
realId: post.id,
|
||||||
id: totalPosts - ((page - 1) * selectedSize.value) - index,
|
id: totalPosts - ((page - 1) * selectedSize.value) - index,
|
||||||
@ -208,8 +206,9 @@ const fetchGeneralPosts = async (page = 1) => {
|
|||||||
rawDate: post.date,
|
rawDate: post.date,
|
||||||
date: formatDate(post.date), // 날짜 변환 적용
|
date: formatDate(post.date), // 날짜 변환 적용
|
||||||
views: post.cnt || 0,
|
views: post.cnt || 0,
|
||||||
hasAttachment: post.hasAttachment || false,
|
hasAttachment: post.hasAttachment,
|
||||||
img: post.firstImageUrl || null
|
img: post.firstImageUrl || null,
|
||||||
|
commentCount : post.commentCount
|
||||||
}));
|
}));
|
||||||
|
|
||||||
pagination.value = {
|
pagination.value = {
|
||||||
@ -248,8 +247,9 @@ const fetchNoticePosts = async () => {
|
|||||||
date: formatDate(post.date),
|
date: formatDate(post.date),
|
||||||
rawDate: post.date,
|
rawDate: post.date,
|
||||||
views: post.cnt || 0,
|
views: post.cnt || 0,
|
||||||
hasAttachment: post.hasAttachment || false,
|
hasAttachment: post.hasAttachment,
|
||||||
img: post.firstImageUrl || null
|
img: post.firstImageUrl || null,
|
||||||
|
commentCount : post.commentCount
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
@ -289,4 +289,12 @@ onMounted(() => {
|
|||||||
width: 100% !important;
|
width: 100% !important;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
/* 댓글 개수 스타일 */
|
||||||
|
.comment-count {
|
||||||
|
font-size: 0.9rem; /* 글씨 크기 증가 */
|
||||||
|
font-weight: bold;
|
||||||
|
color: #ff5733; /* 강조 색상 (붉은 계열) */
|
||||||
|
border-radius: 4px; /* 둥근 모서리 */
|
||||||
|
padding: 2px 6px; /* 내부 패딩 */
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@ -17,46 +17,35 @@
|
|||||||
:isAuthor="isAuthor"
|
:isAuthor="isAuthor"
|
||||||
@editClick="editClick"
|
@editClick="editClick"
|
||||||
@deleteClick="deleteClick"
|
@deleteClick="deleteClick"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<!-- 비밀번호 입력창 (익명일 경우) -->
|
|
||||||
<div v-if="isPassword && unknown" class="mt-3 w-25 ms-auto">
|
|
||||||
<div class="input-group">
|
|
||||||
<input
|
|
||||||
type="password"
|
|
||||||
class="form-control"
|
|
||||||
v-model="password"
|
|
||||||
placeholder="비밀번호 입력"
|
|
||||||
/>
|
|
||||||
<button class="btn btn-primary" @click="submitPassword">확인</button>
|
|
||||||
</div>
|
|
||||||
<span v-if="passwordAlert" class="invalid-feedback d-block text-start">{{ passwordAlert }}</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- 게시글 내용 -->
|
<!-- 게시글 내용 -->
|
||||||
<div class="card-body">
|
<div class="card-body">
|
||||||
|
|
||||||
<div class="d-flex justify-content-between align-items-center flex-wrap mb-6 gap-2">
|
<div class="d-flex justify-content-between align-items-center flex-wrap mb-6 gap-2">
|
||||||
<!-- 제목 섹션 -->
|
<!-- 제목 섹션 -->
|
||||||
<div class="me-1">
|
<div class="me-1">
|
||||||
<h5 class="mb-4">{{ boardTitle }}</h5>
|
<h5 class="mb-4">{{ boardTitle }}</h5>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- 첨부파일 섹션 -->
|
<!-- 첨부파일 다운로드 버튼 -->
|
||||||
<div v-if="attachment" class="btn-group">
|
<div v-if="attachments.length" class="btn-group">
|
||||||
<button type="button" class="btn btn-label-secondary dropdown-toggle" data-bs-toggle="dropdown" aria-expanded="false">
|
<button type="button" class="btn btn-label-secondary dropdown-toggle" data-bs-toggle="dropdown" aria-expanded="false">
|
||||||
<i class="fa-solid fa-download me-2"></i>
|
<i class="fa-solid fa-download me-2"></i>
|
||||||
첨부파일
|
첨부파일 ({{ attachments.length }}개)
|
||||||
<!-- (<span class="attachment-num">{{ dropdownItems.length }}</span>) -->
|
|
||||||
</button>
|
</button>
|
||||||
<!-- <ul class="dropdown-menu">
|
<ul class="dropdown-menu">
|
||||||
<li v-for="(item, index) in dropdownItems" :key="index">
|
<li v-for="(attachment, index) in attachments" :key="index">
|
||||||
<a class="dropdown-item" href="javascript:void(0);">
|
<a
|
||||||
{{ item.label }}
|
class="dropdown-item"
|
||||||
|
href="#"
|
||||||
|
@click.prevent="downloadFile(attachment)"
|
||||||
|
>
|
||||||
|
{{ attachment.originalName }}.{{ attachment.extension }}
|
||||||
</a>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
</ul> -->
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@ -74,21 +63,8 @@
|
|||||||
:likeClicked="likeClicked"
|
:likeClicked="likeClicked"
|
||||||
:dislikeClicked="dislikeClicked"
|
:dislikeClicked="dislikeClicked"
|
||||||
@updateReaction="handleUpdateReaction"
|
@updateReaction="handleUpdateReaction"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<!-- <p>현재 로그인한 사용자 ID: {{ currentUserId }}</p>
|
|
||||||
<p>게시글 작성자: {{ authorId }}</p>
|
|
||||||
<p>isAuthor 값: {{ isAuthor }}</p> -->
|
|
||||||
<!-- <p>use이미지:{{userStore.user.img}}</p> -->
|
|
||||||
<!-- <img :src="`http://localhost:10325/upload/img/profile/${userStore.user.profile}`" alt="Profile Image" class="w-px-40 h-auto rounded-circle"/> -->
|
|
||||||
|
|
||||||
|
|
||||||
<!-- 첨부파일 목록 -->
|
|
||||||
<!-- <ul v-if="attachments.length" class="attachments mt-4 list-unstyled">
|
|
||||||
<li v-for="(attachment, index) in attachments" :key="index" class="mb-2">
|
|
||||||
<a :href="attachment.url" target="_blank" class="text-decoration-none">{{ attachment.name }}</a>
|
|
||||||
</li>
|
|
||||||
</ul> -->
|
|
||||||
|
|
||||||
<!-- 댓글 입력 영역 -->
|
<!-- 댓글 입력 영역 -->
|
||||||
<BoardCommentArea
|
<BoardCommentArea
|
||||||
@ -98,7 +74,6 @@
|
|||||||
:passwordAlert="passwordAlert"
|
:passwordAlert="passwordAlert"
|
||||||
@submitComment="handleCommentSubmit"
|
@submitComment="handleCommentSubmit"
|
||||||
/>
|
/>
|
||||||
<!-- <BoardCommentArea :profileName="profileName" :unknown="unknown" /> -->
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- 댓글 목록 -->
|
<!-- 댓글 목록 -->
|
||||||
@ -106,9 +81,12 @@
|
|||||||
<BoardCommentList
|
<BoardCommentList
|
||||||
:unknown="unknown"
|
:unknown="unknown"
|
||||||
:comments="commentsWithAuthStatus"
|
:comments="commentsWithAuthStatus"
|
||||||
:isCommentPassword="Boolean(isCommentPassword)"
|
:isCommentPassword="isCommentPassword"
|
||||||
:isEditTextarea="isEditTextarea"
|
:isEditTextarea="isEditTextarea"
|
||||||
|
:isDeleted="isDeleted"
|
||||||
:passwordCommentAlert="passwordCommentAlert"
|
:passwordCommentAlert="passwordCommentAlert"
|
||||||
|
:currentPasswordCommentId="currentPasswordCommentId"
|
||||||
|
:password="password"
|
||||||
@editClick="editComment"
|
@editClick="editComment"
|
||||||
@deleteClick="deleteComment"
|
@deleteClick="deleteComment"
|
||||||
@updateReaction="handleCommentReaction"
|
@updateReaction="handleCommentReaction"
|
||||||
@ -117,6 +95,7 @@
|
|||||||
@commentDeleted="handleCommentDeleted"
|
@commentDeleted="handleCommentDeleted"
|
||||||
@cancelEdit="handleCancelEdit"
|
@cancelEdit="handleCancelEdit"
|
||||||
@submitEdit="handleSubmitEdit"
|
@submitEdit="handleSubmitEdit"
|
||||||
|
@update:password="updatePassword"
|
||||||
/>
|
/>
|
||||||
<Pagination
|
<Pagination
|
||||||
v-if="pagination.pages"
|
v-if="pagination.pages"
|
||||||
@ -173,20 +152,50 @@ const commentsWithAuthStatus = computed(() => {
|
|||||||
isCommentAuthor: reply.authorId === currentUserId.value,
|
isCommentAuthor: reply.authorId === currentUserId.value,
|
||||||
}))
|
}))
|
||||||
}));
|
}));
|
||||||
// console.log("✅ commentsWithAuthStatus 업데이트됨:", updatedComments);
|
|
||||||
return updatedComments;
|
return updatedComments;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const attachments = ref([]);
|
||||||
|
// 첨부파일 다운로드 URL 생성
|
||||||
|
const downloadFile = async (attachment) => {
|
||||||
|
try {
|
||||||
|
const response = await axios.get(`board/download`, {
|
||||||
|
params: { path: attachment.path },
|
||||||
|
responseType: 'blob'
|
||||||
|
});
|
||||||
|
|
||||||
|
// Blob에서 파일 다운로드 링크 생성
|
||||||
|
const url = window.URL.createObjectURL(new Blob([response.data]));
|
||||||
|
const link = document.createElement('a');
|
||||||
|
link.href = url;
|
||||||
|
link.setAttribute('download', attachment.originalName + '.' + attachment.extension);
|
||||||
|
document.body.appendChild(link);
|
||||||
|
link.click();
|
||||||
|
link.remove();
|
||||||
|
window.URL.revokeObjectURL(url);
|
||||||
|
} catch (error) {
|
||||||
|
console.error('파일 다운로드 오류:', error);
|
||||||
|
alert('파일 다운로드 중 오류가 발생했습니다.');
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
const password = ref('');
|
const password = ref('');
|
||||||
const passwordAlert = ref("");
|
const passwordAlert = ref("");
|
||||||
const passwordCommentAlert = ref("");
|
const passwordCommentAlert = ref("");
|
||||||
const isPassword = ref(false);
|
const isPassword = ref(false);
|
||||||
const isCommentPassword = ref(false);
|
const isCommentPassword = ref(false);
|
||||||
|
const currentPasswordCommentId = ref(null);
|
||||||
const lastClickedButton = ref("");
|
const lastClickedButton = ref("");
|
||||||
const lastCommentClickedButton = ref("");
|
const lastCommentClickedButton = ref("");
|
||||||
const isEditTextarea = ref(false);
|
const isEditTextarea = ref(false);
|
||||||
const commentAlert = ref('')
|
const isDeleted = ref(true);
|
||||||
|
const commentAlert = ref('');
|
||||||
|
|
||||||
|
const updatePassword = (newPassword) => {
|
||||||
|
password.value = newPassword;
|
||||||
|
};
|
||||||
|
|
||||||
const pagination = ref({
|
const pagination = ref({
|
||||||
currentPage: 1,
|
currentPage: 1,
|
||||||
@ -210,21 +219,10 @@ const fetchBoardDetails = async () => {
|
|||||||
const response = await axios.get(`board/${currentBoardId.value}`);
|
const response = await axios.get(`board/${currentBoardId.value}`);
|
||||||
const data = response.data.data;
|
const data = response.data.data;
|
||||||
|
|
||||||
// console.log(data)
|
|
||||||
|
|
||||||
// API 응답 데이터 반영
|
|
||||||
// const boardDetail = data.boardDetail || {};
|
|
||||||
|
|
||||||
profileName.value = data.author || '익명';
|
profileName.value = data.author || '익명';
|
||||||
|
|
||||||
// 익명확인하고 싶을때
|
authorId.value = data.authorId;
|
||||||
// profileName.value = '익명';
|
|
||||||
|
|
||||||
// console.log("📌 게시글 작성자:", profileName.value); // 작성자 이름 출력
|
|
||||||
// console.log("🔍 익명 여부 (unknown.value):", unknown.value); // 익명 여부 확인
|
|
||||||
|
|
||||||
|
|
||||||
authorId.value = data.authorId; //게시글 작성자 id
|
|
||||||
boardTitle.value = data.title || '제목 없음';
|
boardTitle.value = data.title || '제목 없음';
|
||||||
boardContent.value = data.content || '';
|
boardContent.value = data.content || '';
|
||||||
date.value = data.date || '';
|
date.value = data.date || '';
|
||||||
@ -233,6 +231,7 @@ const fetchBoardDetails = async () => {
|
|||||||
dislikes.value = data.dislikeCount || 0;
|
dislikes.value = data.dislikeCount || 0;
|
||||||
attachment.value = data.hasAttachment || null;
|
attachment.value = data.hasAttachment || null;
|
||||||
commentNum.value = data.commentCount || 0;
|
commentNum.value = data.commentCount || 0;
|
||||||
|
attachments.value = data.attachments || [];
|
||||||
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
alert('게시물 데이터를 불러오는 중 오류가 발생했습니다.');
|
alert('게시물 데이터를 불러오는 중 오류가 발생했습니다.');
|
||||||
@ -245,7 +244,6 @@ const handleUpdateReaction = async ({ boardId, commentId, isLike, isDislike }) =
|
|||||||
await axios.post(`/board/${boardId}/${commentId}/reaction`, {
|
await axios.post(`/board/${boardId}/${commentId}/reaction`, {
|
||||||
LOCBRDSEQ: boardId, // 게시글 id
|
LOCBRDSEQ: boardId, // 게시글 id
|
||||||
LOCCMTSEQ: commentId, //댓글 id
|
LOCCMTSEQ: commentId, //댓글 id
|
||||||
// MEMBERSEQ: 1, // 멤버아이디 지금은 1 나중에 수정해야함
|
|
||||||
LOCGOBGOD: isLike ? 'T' : 'F',
|
LOCGOBGOD: isLike ? 'T' : 'F',
|
||||||
LOCGOBBAD: isDislike ? 'T' : 'F'
|
LOCGOBBAD: isDislike ? 'T' : 'F'
|
||||||
});
|
});
|
||||||
@ -258,13 +256,9 @@ const handleUpdateReaction = async ({ boardId, commentId, isLike, isDislike }) =
|
|||||||
|
|
||||||
likeClicked.value = isLike;
|
likeClicked.value = isLike;
|
||||||
dislikeClicked.value = isDislike;
|
dislikeClicked.value = isDislike;
|
||||||
// console.log(updatedData)
|
|
||||||
|
|
||||||
// console.log("갱신된 데이터:", updatedData);
|
|
||||||
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
alert('오류가 발생했습니다.');
|
alert('오류가 발생했습니다.');
|
||||||
// console.log('반응을 업데이트하는 중 오류가 발생했습니다.');
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -281,13 +275,10 @@ const handleCommentReaction = async ({ boardId, commentId, isLike, isDislike })
|
|||||||
LOCGOBBAD: isDislike ? 'T' : 'F'
|
LOCGOBBAD: isDislike ? 'T' : 'F'
|
||||||
});
|
});
|
||||||
|
|
||||||
// console.log("댓글 좋아요 API 응답 데이터:", response.data);
|
|
||||||
|
|
||||||
await fetchComments();
|
await fetchComments();
|
||||||
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
alert('오류가 발생했습니다.');
|
alert('오류가 발생했습니다.');
|
||||||
// console.log('댓글 반응을 업데이트하는 중 오류 발생');
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -301,9 +292,6 @@ const fetchComments = async (page = 1) => {
|
|||||||
page
|
page
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
// console.log(response.data.data)
|
|
||||||
|
|
||||||
const commentsList = response.data.data.list.map(comment => ({
|
const commentsList = response.data.data.list.map(comment => ({
|
||||||
commentId: comment.LOCCMTSEQ, // 댓글 ID
|
commentId: comment.LOCCMTSEQ, // 댓글 ID
|
||||||
boardId: comment.LOCBRDSEQ,
|
boardId: comment.LOCBRDSEQ,
|
||||||
@ -318,8 +306,12 @@ const fetchComments = async (page = 1) => {
|
|||||||
createdAtRaw: new Date(comment.LOCCMTRDT), // 정렬용
|
createdAtRaw: new Date(comment.LOCCMTRDT), // 정렬용
|
||||||
createdAt: formattedDate(comment.LOCCMTRDT), // 표시용
|
createdAt: formattedDate(comment.LOCCMTRDT), // 표시용
|
||||||
children: [], // 대댓글을 담을 배열
|
children: [], // 대댓글을 담을 배열
|
||||||
|
updateAtRaw: comment.LOCCMTUDT,
|
||||||
|
|
||||||
}));
|
}));
|
||||||
|
|
||||||
|
commentsList.sort((a, b) => b.createdAtRaw - a.createdAtRaw);
|
||||||
|
|
||||||
for (const comment of commentsList) {
|
for (const comment of commentsList) {
|
||||||
if (!comment.commentId) continue;
|
if (!comment.commentId) continue;
|
||||||
|
|
||||||
@ -327,8 +319,6 @@ const fetchComments = async (page = 1) => {
|
|||||||
params: { LOCCMTPNT: comment.commentId }
|
params: { LOCCMTPNT: comment.commentId }
|
||||||
});
|
});
|
||||||
|
|
||||||
// console.log(`대댓글 데이터 (${comment.commentId}의 대댓글):`, replyResponse.data);
|
|
||||||
|
|
||||||
if (replyResponse.data.data) {
|
if (replyResponse.data.data) {
|
||||||
comment.children = replyResponse.data.data.map(reply => ({
|
comment.children = replyResponse.data.data.map(reply => ({
|
||||||
author: reply.author || '익명',
|
author: reply.author || '익명',
|
||||||
@ -368,23 +358,19 @@ const fetchComments = async (page = 1) => {
|
|||||||
navigateLastPage: response.data.data.navigateLastPage // 페이지네이션에서 마지막 페이지 번호
|
navigateLastPage: response.data.data.navigateLastPage // 페이지네이션에서 마지막 페이지 번호
|
||||||
};
|
};
|
||||||
|
|
||||||
// console.log("📌 댓글 목록:", comments.value);
|
|
||||||
|
|
||||||
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
alert('오류가 발생했습니다.');
|
alert('오류가 발생했습니다.');
|
||||||
// alert('댓글 목록 불러오기 오류:', error);
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// 댓글 작성
|
// 댓글 작성
|
||||||
const handleCommentSubmit = async (data, isCheck) => {
|
const handleCommentSubmit = async (data) => {
|
||||||
if (!data) {
|
if (!data) {
|
||||||
console.error("handleCommentSubmit: data가 undefined입니다.");
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const { comment, password } = data;
|
const { comment, password, isCheck } = data;
|
||||||
|
|
||||||
if (!comment || comment.trim() === "") {
|
if (!comment || comment.trim() === "") {
|
||||||
commentAlert.value = '댓글을 입력해주세요.';
|
commentAlert.value = '댓글을 입력해주세요.';
|
||||||
@ -404,25 +390,22 @@ const handleCommentSubmit = async (data, isCheck) => {
|
|||||||
LOCCMTRPY: comment,
|
LOCCMTRPY: comment,
|
||||||
LOCCMTPWD: isCheck ? password : '',
|
LOCCMTPWD: isCheck ? password : '',
|
||||||
LOCCMTPNT: 1,
|
LOCCMTPNT: 1,
|
||||||
LOCBRDTYP: unknown.value ? "300102" : null
|
LOCBRDTYP: isCheck ? "300102" : null
|
||||||
});
|
});
|
||||||
|
|
||||||
if (response.status === 200) {
|
if (response.status === 200) {
|
||||||
// console.log('댓글 작성 성공:', response.data.message);
|
|
||||||
passwordAlert.value = '';
|
passwordAlert.value = '';
|
||||||
commentAlert.value = '';
|
commentAlert.value = '';
|
||||||
await fetchComments();
|
await fetchComments();
|
||||||
} else {
|
} else {
|
||||||
// console.error('댓글 작성 실패:', response.data.message);
|
|
||||||
alert("댓글 작성을 실패했습니다.")
|
alert("댓글 작성을 실패했습니다.")
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
// console.error('댓글 작성 중 오류 발생:', error);
|
|
||||||
alert("오류가 발생했습니다.")
|
alert("오류가 발생했습니다.")
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// 대댓글 추가 (부모 `BoardCommentList`로부터 이벤트 받아서 처리)
|
// 대댓글 추가
|
||||||
const handleCommentReply = async (reply) => {
|
const handleCommentReply = async (reply) => {
|
||||||
try {
|
try {
|
||||||
const response = await axios.post(`board/${currentBoardId.value}/comment`, {
|
const response = await axios.post(`board/${currentBoardId.value}/comment`, {
|
||||||
@ -434,22 +417,17 @@ const handleCommentReply = async (reply) => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
if (response.status === 200) {
|
if (response.status === 200) {
|
||||||
if (response.data.code === 200) { // 서버 응답 코드도 확인
|
if (response.data.code === 200) {
|
||||||
// console.log('대댓글 작성 성공:', response.data);
|
await fetchComments();
|
||||||
await fetchComments(); // 댓글 목록 새로고침
|
|
||||||
} else {
|
} else {
|
||||||
// console.log('대댓글 작성 실패 - 서버 응답:', response.data);
|
|
||||||
alert('대댓글 작성을 실패했습니다.');
|
alert('대댓글 작성을 실패했습니다.');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
// console.error('대댓글 작성 중 오류 발생:', error);
|
|
||||||
if (error.response) {
|
if (error.response) {
|
||||||
// console.error('서버 응답 에러:', error.response.data);
|
|
||||||
alert("오류가 발생했습니다.");
|
alert("오류가 발생했습니다.");
|
||||||
}
|
}
|
||||||
alert("오류가 발생했습니다.");
|
alert("오류가 발생했습니다.");
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -486,45 +464,69 @@ const findCommentById = (commentId, commentsList) => {
|
|||||||
return null;
|
return null;
|
||||||
};
|
};
|
||||||
|
|
||||||
// 댓글 수정 버튼 클릭
|
// 댓글 수정(대댓글 포함)
|
||||||
const editComment = (comment) => {
|
const editComment = (comment) => {
|
||||||
|
password.value = '';
|
||||||
|
passwordCommentAlert.value = '';
|
||||||
|
currentPasswordCommentId.value = null;
|
||||||
|
|
||||||
// 부모 또는 대댓글을 찾아서 가져오기
|
|
||||||
const targetComment = findCommentById(comment.commentId, comments.value);
|
const targetComment = findCommentById(comment.commentId, comments.value);
|
||||||
|
|
||||||
if (!targetComment) {
|
if (!targetComment) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 댓글 작성자가 현재 로그인한 사용자와 동일한지 확인
|
|
||||||
const isMyComment = comment.authorId === currentUserId.value;
|
const isMyComment = comment.authorId === currentUserId.value;
|
||||||
const isAnonymous = comment.author === "익명";
|
const isAnonymous = comment.author === "익명";
|
||||||
|
|
||||||
if (isMyComment) {
|
if (isMyComment) {
|
||||||
// 본인 댓글이면 바로 수정 모드 활성화
|
if (targetComment.isEditTextarea) {
|
||||||
targetComment.isEditTextarea = true;
|
// 수정창이 열려 있는 상태에서 다시 수정 버튼을 누르면 초기화
|
||||||
} else if (isAnonymous) {
|
targetComment.isEditTextarea = false;
|
||||||
// 익명 댓글이면 비밀번호 입력창 띄우기
|
currentPasswordCommentId.value = comment.commentId;
|
||||||
toggleCommentPassword(comment, "edit");
|
} else {
|
||||||
|
// 다른 모든 댓글의 수정창 닫기
|
||||||
|
closeAllEditTextareas();
|
||||||
|
|
||||||
|
// 현재 댓글만 수정 모드 활성화
|
||||||
|
targetComment.isEditTextarea = true;
|
||||||
|
}
|
||||||
|
} else if (isAnonymous) {
|
||||||
|
if (currentPasswordCommentId.value === comment.commentId) {
|
||||||
|
// 이미 비밀번호 입력 중이면 유지
|
||||||
|
return;
|
||||||
|
} else {
|
||||||
|
// 다른 모든 댓글의 수정창 닫기
|
||||||
|
closeAllEditTextareas();
|
||||||
|
|
||||||
|
// 비밀번호 입력
|
||||||
|
targetComment.isEditTextarea = false;
|
||||||
|
toggleCommentPassword(comment, "edit");
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
// console.log("다른 사용자 댓글 - 수정 불가");
|
|
||||||
alert("수정이 불가능합니다");
|
alert("수정이 불가능합니다");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 모든 댓글의 수정 창 닫기
|
||||||
|
const closeAllEditTextareas = () => {
|
||||||
|
comments.value.forEach(comment => {
|
||||||
|
comment.isEditTextarea = false;
|
||||||
|
comment.children.forEach(reply => {
|
||||||
|
reply.isEditTextarea = false;
|
||||||
|
});
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
// 댓글 삭제 버튼 클릭
|
// 댓글 삭제 버튼 클릭
|
||||||
const deleteComment = async (comment) => {
|
const deleteComment = async (comment) => {
|
||||||
// 익명 사용자인 경우
|
|
||||||
const isMyComment = comment.authorId === currentUserId.value;
|
const isMyComment = comment.authorId === currentUserId.value;
|
||||||
|
|
||||||
if (unknown.value && !isMyComment) {
|
if (unknown.value && !isMyComment) {
|
||||||
if (comment.isEditTextarea) {
|
if (comment.isEditTextarea) {
|
||||||
// 현재 수정 중이라면 수정 모드를 끄고, 삭제 비밀번호 입력창을 띄움
|
|
||||||
comment.isEditTextarea = false;
|
comment.isEditTextarea = false;
|
||||||
comment.isCommentPassword = true;
|
comment.isCommentPassword = true;
|
||||||
} else {
|
} else {
|
||||||
// 수정 중이 아니면 기존의 삭제 비밀번호 입력창을 띄우는 로직 실행
|
|
||||||
toggleCommentPassword(comment, "delete");
|
toggleCommentPassword(comment, "delete");
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
@ -532,19 +534,21 @@ const deleteComment = async (comment) => {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// 익명 비밀번호 창 토글
|
// 익명 댓글 비밀번호 창 토글
|
||||||
const toggleCommentPassword = (comment, button) => {
|
const toggleCommentPassword = (comment, button) => {
|
||||||
if (lastCommentClickedButton.value === button && isCommentPassword.value === comment.commentId) {
|
if (lastCommentClickedButton.value === button && currentPasswordCommentId.value === comment.commentId) {
|
||||||
isCommentPassword.value = false; // 비밀번호 창 닫기
|
currentPasswordCommentId.value = null; // 비밀번호 창 닫기
|
||||||
|
password.value = '';
|
||||||
|
passwordCommentAlert.value = '';
|
||||||
} else {
|
} else {
|
||||||
isCommentPassword.value = comment.commentId; // 비밀번호 창 열기
|
currentPasswordCommentId.value = comment.commentId; // 비밀번호 창 열기
|
||||||
|
password.value = '';
|
||||||
|
passwordCommentAlert.value = '';
|
||||||
}
|
}
|
||||||
|
|
||||||
lastCommentClickedButton.value = button;
|
lastCommentClickedButton.value = button;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
const togglePassword = (button) => {
|
const togglePassword = (button) => {
|
||||||
if (lastClickedButton.value === button) {
|
if (lastClickedButton.value === button) {
|
||||||
isPassword.value = !isPassword.value;
|
isPassword.value = !isPassword.value;
|
||||||
@ -556,16 +560,15 @@ const togglePassword = (button) => {
|
|||||||
|
|
||||||
// 게시글 비밀번호 제출
|
// 게시글 비밀번호 제출
|
||||||
const submitPassword = async () => {
|
const submitPassword = async () => {
|
||||||
if (!password.value) {
|
if (!password.value.trim()) {
|
||||||
passwordAlert.value = "비밀번호를 입력해주세요.";
|
passwordAlert.value = "비밀번호를 입력해주세요.";
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
// console.log("📌 요청 시작: submitPassword 실행됨");
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const response = await axios.post(`board/${currentBoardId.value}/password`, {
|
const response = await axios.post(`board/${currentBoardId.value}/password`, {
|
||||||
LOCBRDPWD: password.value,
|
LOCBRDPWD: password.value,
|
||||||
LOCBRDSEQ: 288, // 나중에 현재 게시글 ID 사용해야함
|
LOCBRDSEQ: 288,
|
||||||
});
|
});
|
||||||
|
|
||||||
if (response.data.code === 200 && response.data.data === true) {
|
if (response.data.code === 200 && response.data.data === true) {
|
||||||
@ -582,8 +585,6 @@ const submitPassword = async () => {
|
|||||||
passwordAlert.value = "비밀번호가 일치하지 않습니다.";
|
passwordAlert.value = "비밀번호가 일치하지 않습니다.";
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
// console.log("📌 전체 오류:", error);
|
|
||||||
|
|
||||||
if (error.response) {
|
if (error.response) {
|
||||||
if (error.response.status === 401) {
|
if (error.response.status === 401) {
|
||||||
passwordAlert.value = "비밀번호가 일치하지 않습니다.";
|
passwordAlert.value = "비밀번호가 일치하지 않습니다.";
|
||||||
@ -598,34 +599,40 @@ const submitPassword = async () => {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// 댓글 삭제 (비밀번호 확인 후)
|
// 댓글 (비밀번호 확인 후)
|
||||||
const submitCommentPassword = async (comment, password) => {
|
const submitCommentPassword = async (comment, password) => {
|
||||||
|
|
||||||
// console.log("비밀번호 :", password);
|
|
||||||
// console.log("댓글 ID:", comment.commentId);
|
|
||||||
|
|
||||||
if (!password) {
|
if (!password) {
|
||||||
passwordCommentAlert.value = "비밀번호를 입력해주세요.";
|
passwordCommentAlert.value = "비밀번호를 입력해주세요.";
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const targetComment = findCommentById(comment.commentId, comments.value);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// console.log('서버로 비밀번호 확인 요청')
|
|
||||||
const response = await axios.post(`board/comment/${comment.commentId}/password`, {
|
const response = await axios.post(`board/comment/${comment.commentId}/password`, {
|
||||||
LOCCMTPWD: password,
|
LOCCMTPWD: password,
|
||||||
LOCCMTSEQ: comment.commentId,
|
LOCCMTSEQ: comment.commentId,
|
||||||
});
|
});
|
||||||
// console.log("✅ 서버 응답 데이터:", response.data);
|
|
||||||
|
|
||||||
if (response.data.code === 200 && response.data.data === true) {
|
if (response.data.code === 200 && response.data.data === true) {
|
||||||
passwordCommentAlert.value = "";
|
passwordCommentAlert.value = "";
|
||||||
comment.isCommentPassword = false;
|
comment.isCommentPassword = false;
|
||||||
|
|
||||||
|
// 수정
|
||||||
if (lastCommentClickedButton.value === "edit") {
|
if (lastCommentClickedButton.value === "edit") {
|
||||||
comment.isEditTextarea = true;
|
|
||||||
passwordCommentAlert.value = "";
|
|
||||||
|
|
||||||
// handleSubmitEdit(comment, comment.content);
|
if (targetComment) {
|
||||||
|
// 다른 모든 댓글의 수정 창 닫기
|
||||||
|
closeAllEditTextareas();
|
||||||
|
|
||||||
|
targetComment.isEditTextarea = true;
|
||||||
|
passwordCommentAlert.value = "";
|
||||||
|
currentPasswordCommentId.value = null;
|
||||||
|
|
||||||
|
} else {
|
||||||
|
alert("수정 취소를 실패했습니다.");
|
||||||
|
}
|
||||||
|
//삭제
|
||||||
} else if (lastCommentClickedButton.value === "delete") {
|
} else if (lastCommentClickedButton.value === "delete") {
|
||||||
passwordCommentAlert.value = "";
|
passwordCommentAlert.value = "";
|
||||||
|
|
||||||
@ -633,14 +640,12 @@ const submitCommentPassword = async (comment, password) => {
|
|||||||
}
|
}
|
||||||
lastCommentClickedButton.value = null;
|
lastCommentClickedButton.value = null;
|
||||||
} else {
|
} else {
|
||||||
// console.log("❌ 비밀번호가 틀림");
|
|
||||||
passwordCommentAlert.value = "비밀번호가 일치하지 않습니다.";
|
passwordCommentAlert.value = "비밀번호가 일치하지 않습니다.";
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
// console.log("🚨 서버 요청 중 오류 발생:", error.response?.data || error);
|
if (error.response?.status === 401) {
|
||||||
// if (error.response?.status === 401) {
|
passwordCommentAlert.value = "비밀번호가 일치하지 않습니다";
|
||||||
// console.log("❌ 401 오류: 인증 실패 (비밀번호 불일치)");
|
}
|
||||||
// }
|
|
||||||
passwordCommentAlert.value = "비밀번호가 일치하지 않습니다";
|
passwordCommentAlert.value = "비밀번호가 일치하지 않습니다";
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@ -672,24 +677,30 @@ const deletePost = async () => {
|
|||||||
// 댓글 삭제 (대댓글 포함)
|
// 댓글 삭제 (대댓글 포함)
|
||||||
const deleteReplyComment = async (comment) => {
|
const deleteReplyComment = async (comment) => {
|
||||||
if (!confirm("정말 이 댓글을 삭제하시겠습니까?")) return;
|
if (!confirm("정말 이 댓글을 삭제하시겠습니까?")) return;
|
||||||
// console.log("댓글 ID:", comment);
|
|
||||||
|
const targetComment = findCommentById(comment.commentId, comments.value);
|
||||||
|
|
||||||
|
// console.log('잘되니?',comment)
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const response = await axios.delete(`board/comment/${comment.commentId}`, {
|
const response = await axios.delete(`board/comment/${comment.commentId}`, {
|
||||||
data: { LOCCMTSEQ: comment.commentId }
|
data: { LOCCMTSEQ: comment.commentId }
|
||||||
});
|
});
|
||||||
|
|
||||||
// console.log("서버 응답:", response.data);
|
|
||||||
|
|
||||||
if (response.data.code === 200) {
|
if (response.data.code === 200) {
|
||||||
// console.log("댓글 삭제 성공!");
|
|
||||||
await fetchComments();
|
await fetchComments();
|
||||||
|
|
||||||
|
if (targetComment) {
|
||||||
|
// console.log('타겟',targetComment)
|
||||||
|
// ✅ 댓글 내용만 "삭제된 댓글입니다."로 변경하고, 구조는 유지
|
||||||
|
targetComment.content = "댓글이 삭제되었습니다.";
|
||||||
|
targetComment.author = "알 수 없음"; // 익명 처리
|
||||||
|
targetComment.isDeleted = true; // ✅ 삭제 상태를 추가
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
// console.log("댓글 삭제 실패:", response.data.message);
|
|
||||||
alert("댓글 삭제에 실패했습니다.");
|
alert("댓글 삭제에 실패했습니다.");
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
// console.log("댓글 삭제 중 오류 발생:", error);
|
|
||||||
alert("댓글 삭제 중 오류가 발생했습니다.");
|
alert("댓글 삭제 중 오류가 발생했습니다.");
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@ -702,9 +713,6 @@ const handleSubmitEdit = async (comment, editedContent) => {
|
|||||||
LOCCMTRPY: editedContent
|
LOCCMTRPY: editedContent
|
||||||
});
|
});
|
||||||
|
|
||||||
// 수정 성공 시 업데이트
|
|
||||||
// comment.content = editedContent;
|
|
||||||
// comment.isEditTextarea = false; f
|
|
||||||
if (response.status === 200) {
|
if (response.status === 200) {
|
||||||
|
|
||||||
const targetComment = findCommentById(comment.commentId, comments.value);
|
const targetComment = findCommentById(comment.commentId, comments.value);
|
||||||
@ -712,15 +720,12 @@ const handleSubmitEdit = async (comment, editedContent) => {
|
|||||||
targetComment.content = editedContent; // 댓글 내용 업데이트
|
targetComment.content = editedContent; // 댓글 내용 업데이트
|
||||||
targetComment.isEditTextarea = false; // 수정 모드 닫기
|
targetComment.isEditTextarea = false; // 수정 모드 닫기
|
||||||
} else {
|
} else {
|
||||||
// console.warn("❌ 수정할 댓글을 찾을 수 없음");
|
|
||||||
alert("수정할 댓글을 찾을 수 없습니다.");
|
alert("수정할 댓글을 찾을 수 없습니다.");
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// console.log("❌ 댓글 수정 실패:", response.data);
|
|
||||||
alert("댓글 수정 실패했습니다.");
|
alert("댓글 수정 실패했습니다.");
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
// console.error("댓글 수정 중 오류 발생:", error);
|
|
||||||
alert("댓글 수정 중 오류 발생했습니다.");
|
alert("댓글 수정 중 오류 발생했습니다.");
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@ -730,10 +735,8 @@ const handleCancelEdit = (comment) => {
|
|||||||
const targetComment = findCommentById(comment.commentId, comments.value);
|
const targetComment = findCommentById(comment.commentId, comments.value);
|
||||||
|
|
||||||
if (targetComment) {
|
if (targetComment) {
|
||||||
// console.log("✅ 원본 데이터 찾음, 수정 취소 처리 가능:", targetComment);
|
|
||||||
targetComment.isEditTextarea = false;
|
targetComment.isEditTextarea = false;
|
||||||
} else {
|
} else {
|
||||||
// console.error("❌ 원본 데이터 찾을 수 없음, 수정 취소 실패");
|
|
||||||
alert("수정 취소를 실패했습니다.");
|
alert("수정 취소를 실패했습니다.");
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@ -756,7 +759,7 @@ const handleCommentDeleted = (deletedCommentId) => {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 2대댓글 삭제
|
// 대댓글 삭제
|
||||||
for (let parent of comments.value) {
|
for (let parent of comments.value) {
|
||||||
const childIndex = parent.children.findIndex(child => child.commentId === deletedCommentId);
|
const childIndex = parent.children.findIndex(child => child.commentId === deletedCommentId);
|
||||||
if (childIndex !== -1) {
|
if (childIndex !== -1) {
|
||||||
@ -764,11 +767,8 @@ const handleCommentDeleted = (deletedCommentId) => {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// console.error("❌ 삭제할 댓글을 찾을 수 없음:", deletedCommentId);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
// 날짜
|
// 날짜
|
||||||
const formattedDate = (dateString) => {
|
const formattedDate = (dateString) => {
|
||||||
if (!dateString) return "날짜 없음";
|
if (!dateString) return "날짜 없음";
|
||||||
|
|||||||
@ -15,6 +15,8 @@
|
|||||||
:is-essential="true"
|
:is-essential="true"
|
||||||
:is-alert="titleAlert"
|
:is-alert="titleAlert"
|
||||||
v-model="title"
|
v-model="title"
|
||||||
|
@update:alert="titleAlert = $event"
|
||||||
|
@input="validateTitle"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<!-- 카테고리 선택 -->
|
<!-- 카테고리 선택 -->
|
||||||
@ -32,16 +34,19 @@
|
|||||||
:id="`category-${index}`"
|
:id="`category-${index}`"
|
||||||
:value="category.CMNCODVAL"
|
:value="category.CMNCODVAL"
|
||||||
v-model="categoryValue"
|
v-model="categoryValue"
|
||||||
|
@change="categoryAlert = false"
|
||||||
/>
|
/>
|
||||||
<label class="form-check-label" :for="`category-${index}`">
|
<label class="form-check-label" :for="`category-${index}`">
|
||||||
{{ category.CMNCODNAM }}
|
{{ category.CMNCODNAM }}
|
||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="invalid-feedback" :class="categoryAlert ? 'display-block' : ''">카테고리를 선택해주세요.</div>
|
<div class="invalid-feedback" :class="categoryAlert ? 'd-block' : 'd-none'">
|
||||||
|
카테고리를 선택해주세요.
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- 비밀번호 필드 -->
|
<!-- 비밀번호 필드 (익명게시판 선택 시 활성화) -->
|
||||||
<div v-if="categoryValue === 300102" class="mb-4">
|
<div v-if="categoryValue === 300102" class="mb-4">
|
||||||
<FormInput
|
<FormInput
|
||||||
title="비밀번호"
|
title="비밀번호"
|
||||||
@ -50,26 +55,42 @@
|
|||||||
:is-essential="true"
|
:is-essential="true"
|
||||||
:is-alert="passwordAlert"
|
:is-alert="passwordAlert"
|
||||||
v-model="password"
|
v-model="password"
|
||||||
|
@update:alert="passwordAlert = $event"
|
||||||
|
@input="validatePassword"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<!-- 첨부파일 업로드 -->
|
||||||
<FormFile
|
<FormFile
|
||||||
title="첨부파일"
|
title="첨부파일"
|
||||||
name="files"
|
name="files"
|
||||||
:is-alert="attachFilesAlert"
|
:is-alert="attachFilesAlert"
|
||||||
@update:data="attachFiles = $event"
|
@update:data="handleFileUpload"
|
||||||
@update:isValid="isFileValid = $event"
|
@update:isValid="isFileValid = $event"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
|
<!-- 실시간 반영된 파일 개수 표시 -->
|
||||||
|
<p class="text-muted mt-1">첨부파일: {{ fileCount }} / 5개</p>
|
||||||
|
<p v-if="fileError" class="text-danger">{{ fileError }}</p>
|
||||||
|
|
||||||
|
<ul class="list-group mt-2" v-if="attachFiles.length">
|
||||||
|
<li v-for="(file, index) in attachFiles" :key="index" class="list-group-item d-flex justify-content-between align-items-center">
|
||||||
|
{{ file.name }}
|
||||||
|
<button class="close-btn" @click="removeFile(index)">✖</button>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
<!-- 내용 입력 (에디터) -->
|
||||||
<div class="mb-4">
|
<div class="mb-4">
|
||||||
<label for="html5-tel-input" class="col-md-2 col-form-label">
|
<label class="col-md-2 col-form-label">
|
||||||
내용
|
내용 <span class="text-danger">*</span>
|
||||||
<span class="text-danger">*</span>
|
|
||||||
<div class="invalid-feedback" :class="contentAlert ? 'display-block' : ''">내용을 확인해주세요.</div>
|
|
||||||
</label>
|
</label>
|
||||||
<div class="col-md-12">
|
<div class="col-md-12">
|
||||||
<QEditor @update:data="content = $event" />
|
<QEditor @update:data="content = $event" />
|
||||||
</div>
|
</div>
|
||||||
|
<div class="invalid-feedback mt-1" :class="contentAlert ? 'd-block' : 'd-none'">
|
||||||
|
내용을 입력해주세요.
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="mb-4 d-flex justify-content-end">
|
<div class="mb-4 d-flex justify-content-end">
|
||||||
@ -83,23 +104,22 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup>
|
<script setup>
|
||||||
|
import { ref, onMounted, getCurrentInstance, watch, computed } from 'vue';
|
||||||
import QEditor from '@c/editor/QEditor.vue';
|
import QEditor from '@c/editor/QEditor.vue';
|
||||||
import FormInput from '@c/input/FormInput.vue';
|
import FormInput from '@c/input/FormInput.vue';
|
||||||
import FormFile from '@c/input/FormFile.vue';
|
import FormFile from '@c/input/FormFile.vue';
|
||||||
import { getCurrentInstance, ref, onMounted } from 'vue';
|
import SaveButton from '@c/button/SaveBtn.vue';
|
||||||
|
import BackButton from '@c/button/BackBtn.vue';
|
||||||
|
import { useToastStore } from '@s/toastStore';
|
||||||
import router from '@/router';
|
import router from '@/router';
|
||||||
import axios from '@api';
|
import axios from '@api';
|
||||||
import SaveButton from '@c/button/SaveBtn.vue';
|
|
||||||
import BackButton from '@c/button/BackBtn.vue'
|
|
||||||
import { useToastStore } from '@s/toastStore';
|
|
||||||
|
|
||||||
const toastStore = useToastStore();
|
const toastStore = useToastStore();
|
||||||
const categoryList = ref([]);
|
const categoryList = ref([]);
|
||||||
const title = ref('');
|
const title = ref('');
|
||||||
const password = ref('');
|
const password = ref('');
|
||||||
const categoryValue = ref(null);
|
const categoryValue = ref(null);
|
||||||
const content = ref('');
|
const content = ref({ ops: [] });
|
||||||
const attachFiles = ref(null);
|
|
||||||
const isFileValid = ref(true);
|
const isFileValid = ref(true);
|
||||||
|
|
||||||
const titleAlert = ref(false);
|
const titleAlert = ref(false);
|
||||||
@ -108,17 +128,18 @@ const contentAlert = ref(false);
|
|||||||
const categoryAlert = ref(false);
|
const categoryAlert = ref(false);
|
||||||
const attachFilesAlert = ref(false);
|
const attachFilesAlert = ref(false);
|
||||||
|
|
||||||
const { appContext } = getCurrentInstance();
|
const attachFiles = ref([]);
|
||||||
const $common = appContext.config.globalProperties.$common;
|
const maxFiles = 5;
|
||||||
|
const maxSize = 10 * 1024 * 1024;
|
||||||
|
const fileError = ref('');
|
||||||
|
|
||||||
const fetchCategories = async () => {
|
const fetchCategories = async () => {
|
||||||
try {
|
try {
|
||||||
const response = await axios.get('board/categories');
|
const response = await axios.get('board/categories');
|
||||||
categoryList.value = response.data.data;
|
categoryList.value = response.data.data;
|
||||||
// "자유" 카테고리 찾기 (CMNCODNAM이 '자유'인 것 선택)
|
|
||||||
const freeCategory = categoryList.value.find(category => category.CMNCODNAM === '자유');
|
const freeCategory = categoryList.value.find(category => category.CMNCODNAM === '자유');
|
||||||
if (freeCategory) {
|
if (freeCategory) {
|
||||||
categoryValue.value = freeCategory.CMNCODVAL; // 기본 선택값 설정
|
categoryValue.value = freeCategory.CMNCODVAL;
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('카테고리 불러오기 오류:', error);
|
console.error('카테고리 불러오기 오류:', error);
|
||||||
@ -129,15 +150,67 @@ onMounted(() => {
|
|||||||
fetchCategories();
|
fetchCategories();
|
||||||
});
|
});
|
||||||
|
|
||||||
const goList = () => {
|
const fileCount = computed(() => attachFiles.value.length);
|
||||||
router.push('/board');
|
|
||||||
|
const handleFileUpload = (files) => {
|
||||||
|
const validFiles = files.filter(file => file.size <= maxSize);
|
||||||
|
if (files.some(file => file.size > maxSize)) {
|
||||||
|
fileError.value = '파일 크기가 10MB를 초과할 수 없습니다.';
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (attachFiles.value.length + validFiles.length > maxFiles) {
|
||||||
|
fileError.value = `최대 ${maxFiles}개의 파일만 업로드할 수 있습니다.`;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
fileError.value = '';
|
||||||
|
attachFiles.value = [...attachFiles.value, ...validFiles].slice(0, maxFiles);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const removeFile = (index) => {
|
||||||
|
attachFiles.value.splice(index, 1);
|
||||||
|
if (attachFiles.value.length <= maxFiles) {
|
||||||
|
fileError.value = '';
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
watch(attachFiles, () => {
|
||||||
|
isFileValid.value = attachFiles.value.length <= maxFiles;
|
||||||
|
});
|
||||||
|
|
||||||
|
const validateTitle = () => {
|
||||||
|
titleAlert.value = title.value.trim().length === 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
const validatePassword = () => {
|
||||||
|
if (categoryValue.value === 300102) {
|
||||||
|
password.value = password.value.replace(/\s/g, ''); // 공백 제거
|
||||||
|
passwordAlert.value = password.value.length === 0;
|
||||||
|
} else {
|
||||||
|
passwordAlert.value = false;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const validateContent = () => {
|
||||||
|
if (!content.value?.ops?.length) {
|
||||||
|
contentAlert.value = true;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 이미지 포함 여부 확인
|
||||||
|
const hasImage = content.value.ops.some(op => op.insert && typeof op.insert === 'object' && op.insert.image);
|
||||||
|
// 텍스트 포함 여부 확인
|
||||||
|
const hasText = content.value.ops.some(op => typeof op.insert === 'string' && op.insert.trim().length > 0);
|
||||||
|
|
||||||
|
// 텍스트 또는 이미지가 하나라도 있으면 유효한 내용
|
||||||
|
contentAlert.value = !(hasText || hasImage);
|
||||||
|
};
|
||||||
|
|
||||||
|
/** 글쓰기 */
|
||||||
const write = async () => {
|
const write = async () => {
|
||||||
titleAlert.value = !title.value;
|
validateTitle();
|
||||||
passwordAlert.value = categoryValue.value === 300102 && !password.value;
|
validatePassword();
|
||||||
contentAlert.value = !content.value;
|
validateContent();
|
||||||
categoryAlert.value = !categoryValue.value;
|
categoryAlert.value = categoryValue.value == null;
|
||||||
|
|
||||||
if (titleAlert.value || passwordAlert.value || contentAlert.value || categoryAlert.value || !isFileValid.value) {
|
if (titleAlert.value || passwordAlert.value || contentAlert.value || categoryAlert.value || !isFileValid.value) {
|
||||||
return;
|
return;
|
||||||
@ -146,16 +219,17 @@ const write = async () => {
|
|||||||
try {
|
try {
|
||||||
const boardData = {
|
const boardData = {
|
||||||
LOCBRDTTL: title.value,
|
LOCBRDTTL: title.value,
|
||||||
LOCBRDCON: $common.deltaAsJson(content.value),
|
LOCBRDCON: JSON.stringify(content.value), // Delta 포맷을 JSON으로 변환
|
||||||
LOCBRDPWD: categoryValue.value === 300102 ? password.value : null,
|
LOCBRDPWD: categoryValue.value === 300102 ? password.value : null,
|
||||||
LOCBRDTYP: categoryValue.value
|
LOCBRDTYP: categoryValue.value
|
||||||
};
|
};
|
||||||
|
|
||||||
const { data: boardResponse } = await axios.post('board', boardData);
|
const { data: boardResponse } = await axios.post('board', boardData);
|
||||||
const boardId = boardResponse.data;
|
const boardId = boardResponse.data;
|
||||||
|
// 첨부파일 업로드 (비동기 병렬 처리)
|
||||||
if (attachFiles.value && attachFiles.value.length > 0) {
|
if (attachFiles.value && attachFiles.value.length > 0) {
|
||||||
for (const file of attachFiles.value) {
|
await Promise.all(attachFiles.value.map(async (file) => {
|
||||||
|
console.log(file);
|
||||||
const formData = new FormData();
|
const formData = new FormData();
|
||||||
const fileNameWithoutExt = file.name.replace(/\.[^/.]+$/, '');
|
const fileNameWithoutExt = file.name.replace(/\.[^/.]+$/, '');
|
||||||
|
|
||||||
@ -163,15 +237,12 @@ const write = async () => {
|
|||||||
formData.append('CMNFLEORG', fileNameWithoutExt);
|
formData.append('CMNFLEORG', fileNameWithoutExt);
|
||||||
formData.append('CMNFLEEXT', file.name.split('.').pop());
|
formData.append('CMNFLEEXT', file.name.split('.').pop());
|
||||||
formData.append('CMNFLESIZ', file.size);
|
formData.append('CMNFLESIZ', file.size);
|
||||||
formData.append('CMNFLEPAT', 'boardfile');
|
formData.append('file', file); // 📌 실제 파일 추가
|
||||||
formData.append('file', file);
|
|
||||||
|
|
||||||
await axios.post(`board/${boardId}/attachments`, formData, {
|
await axios.post(`board/${boardId}/attachments`, formData,
|
||||||
headers: {
|
{ isFormData : true }
|
||||||
'Content-Type': 'multipart/form-data',
|
);
|
||||||
},
|
}));
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
toastStore.onToast('게시물이 작성되었습니다.', 's');
|
toastStore.onToast('게시물이 작성되었습니다.', 's');
|
||||||
@ -181,4 +252,14 @@ const write = async () => {
|
|||||||
toastStore.onToast('게시물 작성 중 오류가 발생했습니다.', 'e');
|
toastStore.onToast('게시물 작성 중 오류가 발생했습니다.', 'e');
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/** 목록으로 이동 */
|
||||||
|
const goList = () => {
|
||||||
|
router.push('/board');
|
||||||
|
};
|
||||||
|
|
||||||
|
/** `content` 변경 감지하여 자동 유효성 검사 실행 */
|
||||||
|
watch(content, () => {
|
||||||
|
validateContent();
|
||||||
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@ -7,9 +7,11 @@
|
|||||||
<div class="sidebar-content">
|
<div class="sidebar-content">
|
||||||
<div class="sidebar-actions text-center my-3">
|
<div class="sidebar-actions text-center my-3">
|
||||||
<HalfDayButtons
|
<HalfDayButtons
|
||||||
@toggleHalfDay="toggleHalfDay"
|
ref="halfDayButtonsRef"
|
||||||
@addVacationRequests="saveVacationChanges"
|
@toggleHalfDay="toggleHalfDay"
|
||||||
:isDisabled="!hasChanges"
|
@addVacationRequests="saveVacationChanges"
|
||||||
|
:isDisabled="!hasChanges"
|
||||||
|
:selectedDate="selectedDate"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<ProfileList
|
<ProfileList
|
||||||
@ -58,28 +60,31 @@
|
|||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup>
|
<script setup>
|
||||||
import { reactive, ref, onMounted, nextTick, computed, watch } from "vue";
|
import { reactive, ref, onMounted, nextTick, computed, watch, onBeforeUnmount } from "vue";
|
||||||
import axios from "@api";
|
import axios from "@api";
|
||||||
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";
|
||||||
// Flatpickr 및 MonthSelect 플러그인 임포트
|
// Flatpickr 및 MonthSelect 플러그인 임포트
|
||||||
import flatpickr from "flatpickr";
|
import flatpickr from "flatpickr";
|
||||||
import monthSelectPlugin from "flatpickr/dist/plugins/monthSelect/index";
|
import monthSelectPlugin from "flatpickr/dist/plugins/monthSelect/index";
|
||||||
import "flatpickr/dist/flatpickr.min.css";
|
import "flatpickr/dist/flatpickr.min.css";
|
||||||
import "flatpickr/dist/plugins/monthSelect/style.css";
|
import "flatpickr/dist/plugins/monthSelect/style.css";
|
||||||
|
|
||||||
import "@/assets/css/app-calendar.css";
|
import "@/assets/css/app-calendar.css";
|
||||||
import "bootstrap-icons/font/bootstrap-icons.css";
|
import "bootstrap-icons/font/bootstrap-icons.css";
|
||||||
import HalfDayButtons from "@c/button/HalfDayButtons.vue";
|
import HalfDayButtons from "@c/button/HalfDayButtons.vue";
|
||||||
import ProfileList from "@c/vacation/ProfileList.vue";
|
import ProfileList from "@c/vacation/ProfileList.vue";
|
||||||
import VacationModal from "@c/modal/VacationModal.vue";
|
import VacationModal from "@c/modal/VacationModal.vue";
|
||||||
import VacationGrantModal from "@c/modal/VacationGrantModal.vue";
|
import VacationGrantModal from "@c/modal/VacationGrantModal.vue";
|
||||||
import { useUserStore } from "@s/userList";
|
import { useUserStore } from "@s/userList";
|
||||||
import { useUserInfoStore } from "@s/useUserInfoStore";
|
import { useUserInfoStore } from "@s/useUserInfoStore";
|
||||||
import { fetchHolidays } from "@c/calendar/holiday.js";
|
import { fetchHolidays } from "@c/calendar/holiday.js";
|
||||||
import { useToastStore } from '@s/toastStore';
|
import { useToastStore } from '@s/toastStore';
|
||||||
|
import { useRouter } from "vue-router";
|
||||||
|
|
||||||
|
const router = useRouter();
|
||||||
|
|
||||||
const toastStore = useToastStore();
|
const toastStore = useToastStore();
|
||||||
const userStore = useUserInfoStore();
|
const userStore = useUserInfoStore();
|
||||||
@ -90,7 +95,7 @@
|
|||||||
const receivedVacations = ref([]);
|
const receivedVacations = ref([]);
|
||||||
const isModalOpen = ref(false);
|
const isModalOpen = ref(false);
|
||||||
const remainingVacationData = ref({});
|
const remainingVacationData = ref({});
|
||||||
|
const selectedDate = ref(null);
|
||||||
const lastRemainingYear = ref(new Date().getFullYear());
|
const lastRemainingYear = ref(new Date().getFullYear());
|
||||||
const lastRemainingMonth = ref(String(new Date().getMonth() + 1).padStart(2, "0"));
|
const lastRemainingMonth = ref(String(new Date().getMonth() + 1).padStart(2, "0"));
|
||||||
const isGrantModalOpen = ref(false);
|
const isGrantModalOpen = ref(false);
|
||||||
@ -106,6 +111,39 @@
|
|||||||
const fetchedEvents = ref([]);
|
const fetchedEvents = ref([]);
|
||||||
const halfDayButtonsRef = ref(null);
|
const halfDayButtonsRef = ref(null);
|
||||||
|
|
||||||
|
// 페이지 이동 시 변경 사항 확인
|
||||||
|
router.beforeEach((to, from, next) => {
|
||||||
|
if (hasChanges.value) {
|
||||||
|
const answer = window.confirm("저장하지 않은 변경 사항이 있습니다. 이동하시겠습니까?");
|
||||||
|
if (!answer) {
|
||||||
|
return next(false); // 이동 취소
|
||||||
|
}
|
||||||
|
}
|
||||||
|
next();
|
||||||
|
});
|
||||||
|
|
||||||
|
onBeforeUnmount(() => {
|
||||||
|
window.removeEventListener("beforeunload", preventUnsavedChanges);
|
||||||
|
});
|
||||||
|
|
||||||
|
function preventUnsavedChanges(event) {
|
||||||
|
if (hasChanges.value) {
|
||||||
|
event.preventDefault();
|
||||||
|
event.returnValue = ""; // 대부분의 브라우저에서 경고 메시지 표시됨
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// `selectedDates` 변경 시 반차 버튼 초기화
|
||||||
|
watch(
|
||||||
|
() => Array.from(selectedDates.value.keys()), // 선택된 날짜 리스트 감시
|
||||||
|
(newKeys) => {
|
||||||
|
if (halfDayButtonsRef.value) {
|
||||||
|
halfDayButtonsRef.value.resetHalfDay();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{ deep: true }
|
||||||
|
);
|
||||||
|
|
||||||
// 데이트피커 인풋 ref
|
// 데이트피커 인풋 ref
|
||||||
const calendarDatepicker = ref(null);
|
const calendarDatepicker = ref(null);
|
||||||
let fpInstance = null;
|
let fpInstance = null;
|
||||||
@ -192,6 +230,7 @@ function handleDateClick(info) {
|
|||||||
await fetchRemainingVacation();
|
await fetchRemainingVacation();
|
||||||
const currentYear = new Date().getFullYear();
|
const currentYear = new Date().getFullYear();
|
||||||
await fetchVacationHistory(currentYear);
|
await fetchVacationHistory(currentYear);
|
||||||
|
window.addEventListener("beforeunload", preventUnsavedChanges);
|
||||||
|
|
||||||
// Flatpickr 초기화 (달 선택 모드)
|
// Flatpickr 초기화 (달 선택 모드)
|
||||||
fpInstance = flatpickr(calendarDatepicker.value, {
|
fpInstance = flatpickr(calendarDatepicker.value, {
|
||||||
|
|||||||
@ -13,8 +13,7 @@
|
|||||||
|
|
||||||
<!-- 단어 갯수, 작성하기 -->
|
<!-- 단어 갯수, 작성하기 -->
|
||||||
<div class="mt-4">
|
<div class="mt-4">
|
||||||
단어 : {{ total }}
|
<WriteButton ref="writeButton" @click="writeStore.toggleItem(999999)" :isToggleEnabled="true"/>
|
||||||
<WriteButton @click="writeStore.toggleItem(999999)" />
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- ㄱ ㄴ ㄷ ㄹ -->
|
<!-- ㄱ ㄴ ㄷ ㄹ -->
|
||||||
@ -67,7 +66,7 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup>
|
<script setup>
|
||||||
import { ref, watchEffect, computed, onMounted, getCurrentInstance, toRaw } from 'vue';
|
import { ref, computed, onMounted, getCurrentInstance, toRaw } from 'vue';
|
||||||
import axios from '@api';
|
import axios from '@api';
|
||||||
import SearchBar from '@c/search/SearchBar.vue';
|
import SearchBar from '@c/search/SearchBar.vue';
|
||||||
import WriteButton from '@c/button/WriteBtn.vue';
|
import WriteButton from '@c/button/WriteBtn.vue';
|
||||||
@ -77,14 +76,11 @@
|
|||||||
import DictAlphabetFilter from '@/components/wordDict/DictAlphabetFilter.vue';
|
import DictAlphabetFilter from '@/components/wordDict/DictAlphabetFilter.vue';
|
||||||
import commonApi from '@/common/commonApi';
|
import commonApi from '@/common/commonApi';
|
||||||
import { useToastStore } from '@s/toastStore';
|
import { useToastStore } from '@s/toastStore';
|
||||||
import { useUserInfoStore } from '@s/useUserInfoStore';
|
|
||||||
import { useWriteVisibleStore } from '@s/writeVisible';
|
import { useWriteVisibleStore } from '@s/writeVisible';
|
||||||
|
|
||||||
// 작성창 구분
|
// 작성창 구분
|
||||||
const writeStore = useWriteVisibleStore();
|
const writeStore = useWriteVisibleStore();
|
||||||
|
const writeButton = ref(null);
|
||||||
// 유저 구분
|
|
||||||
// const userStore = useUserInfoStore();
|
|
||||||
|
|
||||||
const { appContext } = getCurrentInstance();
|
const { appContext } = getCurrentInstance();
|
||||||
const $common = appContext.config.globalProperties.$common;
|
const $common = appContext.config.globalProperties.$common;
|
||||||
@ -107,7 +103,6 @@
|
|||||||
});
|
});
|
||||||
|
|
||||||
const selectedCategory = ref('');
|
const selectedCategory = ref('');
|
||||||
const selectCategory = ref('');
|
|
||||||
|
|
||||||
// 체크박스 체크된 갯수
|
// 체크박스 체크된 갯수
|
||||||
const checkedItems = ref([]);
|
const checkedItems = ref([]);
|
||||||
@ -121,9 +116,6 @@
|
|||||||
// 검색
|
// 검색
|
||||||
const searchText = ref('');
|
const searchText = ref('');
|
||||||
|
|
||||||
// 작성
|
|
||||||
// const isWriteVisible = ref(false);
|
|
||||||
|
|
||||||
// 데이터 로드
|
// 데이터 로드
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
getwordList();
|
getwordList();
|
||||||
@ -168,34 +160,8 @@
|
|||||||
// 카테고리 선택
|
// 카테고리 선택
|
||||||
const handleSelectedCategoryChange = (category) => {
|
const handleSelectedCategoryChange = (category) => {
|
||||||
selectedCategory.value = category;
|
selectedCategory.value = category;
|
||||||
getwordList(searchText.value, selectedAlphabet.value, selectedCategory.value)
|
getwordList(searchText.value, selectedAlphabet.value, selectedCategory.value);
|
||||||
}
|
}
|
||||||
|
|
||||||
// 작성 toggle
|
|
||||||
// const toggleWriteForm = () => {
|
|
||||||
// isWriteVisible.value = !isWriteVisible.value;
|
|
||||||
// };
|
|
||||||
|
|
||||||
//카테고리 등록
|
|
||||||
// const addCategory = (data) =>{
|
|
||||||
// const lastCategory = cateList.value[cateList.value.length - 1];
|
|
||||||
// const newValue = lastCategory ? lastCategory.value + 1 : 600101;
|
|
||||||
// const newCategory = { label: data, value: newValue };
|
|
||||||
// cateList.value = [newCategory, ...cateList.value];
|
|
||||||
// selectedCategory.value = newCategory.value;
|
|
||||||
//// axios.post('worddict/insertCategory',{
|
|
||||||
//// CMNCODNAM: data
|
|
||||||
//// }).then(res => {
|
|
||||||
//// if(res.data.data == '1'){
|
|
||||||
//// toastStore.onToast('카테고리가 추가 등록 되었습니다.', 's');
|
|
||||||
//// const newCategory = { label: data, value: newValue };
|
|
||||||
//// cateList.value = [newCategory, ...cateList.value];
|
|
||||||
//// selectedCategory.value = newCategory.value;
|
|
||||||
//// } else if(res.data.message == '이미 존재하는 카테고리명입니다.') {
|
|
||||||
//// toastStore.onToast(res.data.message, 'e');
|
|
||||||
//// }
|
|
||||||
//// })
|
|
||||||
// }
|
|
||||||
|
|
||||||
// 용어집 등록
|
// 용어집 등록
|
||||||
const addWord = (wordData, data) => {
|
const addWord = (wordData, data) => {
|
||||||
@ -208,9 +174,9 @@
|
|||||||
category = existingCategory.label == '' ? wordData.category : existingCategory.value;
|
category = existingCategory.label == '' ? wordData.category : existingCategory.value;
|
||||||
} else {
|
} else {
|
||||||
//카테고리 없을시 카테고리 와 용어 둘다 저장
|
//카테고리 없을시 카테고리 와 용어 둘다 저장
|
||||||
// console.log('카테고리 없음');
|
|
||||||
const lastCategory = cateList.value[cateList.value.length - 1];
|
const lastCategory = cateList.value[cateList.value.length - 1];
|
||||||
category = lastCategory ? lastCategory.value + 1 : 600101;
|
category = lastCategory ? lastCategory.value + 1 : 600101;
|
||||||
|
|
||||||
}
|
}
|
||||||
sendWordRequest(category, wordData, data, !existingCategory);
|
sendWordRequest(category, wordData, data, !existingCategory);
|
||||||
};
|
};
|
||||||
@ -227,8 +193,10 @@
|
|||||||
axios.post('worddict/insertWord', payload).then(res => {
|
axios.post('worddict/insertWord', payload).then(res => {
|
||||||
if (res.data.status === 'OK') {
|
if (res.data.status === 'OK') {
|
||||||
toastStore.onToast('용어가 등록 되었습니다.', 's');
|
toastStore.onToast('용어가 등록 되었습니다.', 's');
|
||||||
// isWriteVisible.value = false;
|
|
||||||
writeStore.closeAll();
|
writeStore.closeAll();
|
||||||
|
if (writeButton.value) {
|
||||||
|
writeButton.value.resetButton();
|
||||||
|
}
|
||||||
getwordList();
|
getwordList();
|
||||||
const newCategory = { label: data, value: category };
|
const newCategory = { label: data, value: category };
|
||||||
cateList.value = [newCategory, ...cateList.value];
|
cateList.value = [newCategory, ...cateList.value];
|
||||||
@ -238,8 +206,10 @@
|
|||||||
axios.post('worddict/insertWord', payload).then(res => {
|
axios.post('worddict/insertWord', payload).then(res => {
|
||||||
if (res.data.status === 'OK') {
|
if (res.data.status === 'OK') {
|
||||||
toastStore.onToast('용어가 등록 되었습니다.', 's');
|
toastStore.onToast('용어가 등록 되었습니다.', 's');
|
||||||
// isWriteVisible.value = false;
|
|
||||||
writeStore.closeAll();
|
writeStore.closeAll();
|
||||||
|
if (writeButton.value) {
|
||||||
|
writeButton.value.resetButton();
|
||||||
|
}
|
||||||
getwordList();
|
getwordList();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@ -256,16 +226,12 @@
|
|||||||
checkedItems.value = checkedItems.value.filter(item => item !== id);
|
checkedItems.value = checkedItems.value.filter(item => item !== id);
|
||||||
checkedNames.value = checkedNames.value.filter(item => item !== name);
|
checkedNames.value = checkedNames.value.filter(item => item !== name);
|
||||||
}
|
}
|
||||||
|
|
||||||
// 콘솔에 현재 체크된 name 값 출력
|
|
||||||
// console.log("현재 체크된 name 값:", checkedNames.value);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const isAnyChecked = computed(() => checkedItems.value.length > 0);
|
const isAnyChecked = computed(() => checkedItems.value.length > 0);
|
||||||
|
|
||||||
// 용어집 삭제
|
// 용어집 삭제
|
||||||
const deleteCheckedItems = () => {
|
const deleteCheckedItems = () => {
|
||||||
// console.log("현재 체크된 name 값:", Object.values(checkedNames.value));
|
|
||||||
|
|
||||||
axios.patch('worddict/deleteword', {
|
axios.patch('worddict/deleteword', {
|
||||||
idList: Object.values(checkedNames.value)
|
idList: Object.values(checkedNames.value)
|
||||||
@ -273,7 +239,6 @@
|
|||||||
.then(res => {
|
.then(res => {
|
||||||
if (res.data.status == 'OK') {
|
if (res.data.status == 'OK') {
|
||||||
toastStore.onToast('용어 삭제가 완료되었습니다.', 's');
|
toastStore.onToast('용어 삭제가 완료되었습니다.', 's');
|
||||||
// isWriteVisible.value = false;
|
|
||||||
writeStore.closeAll();
|
writeStore.closeAll();
|
||||||
getwordList();
|
getwordList();
|
||||||
|
|
||||||
@ -283,7 +248,6 @@
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
.catch(error => {
|
.catch(error => {
|
||||||
// console.error('삭제 요청 중 오류 발생:', error);
|
|
||||||
toastStore.onToast('오류가 발생했습니다. 다시 시도해주세요.', 'e');
|
toastStore.onToast('오류가 발생했습니다. 다시 시도해주세요.', 'e');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||