Merge branch 'main' of http://192.168.0.251:3000/localnet/localhost-front
This commit is contained in:
commit
5d4c90e604
@ -1,6 +1,70 @@
|
|||||||
/* 여기에 light css 작성 */
|
/* 여기에 light css 작성 */
|
||||||
|
|
||||||
|
/* 에러페이지 */
|
||||||
|
/* 전체 화면을 덮는 스타일 */
|
||||||
|
.error-page {
|
||||||
|
position: fixed;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
width: 100vw;
|
||||||
|
height: 100vh;
|
||||||
|
background: #fff;
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
text-align: center;
|
||||||
|
color: #000;
|
||||||
|
font-family: 'Poppins', sans-serif;
|
||||||
|
z-index: 9999 !important;
|
||||||
|
}
|
||||||
|
/* 오류 메시지 컨텐츠 */
|
||||||
|
.error-content {
|
||||||
|
text-align: center;
|
||||||
|
animation: fadeIn 0.8s ease-in-out;
|
||||||
|
}
|
||||||
|
/* 에러 이미지 */
|
||||||
|
.error-image {
|
||||||
|
width: 280px; /* 이미지 크기 */
|
||||||
|
margin-bottom: 20px;
|
||||||
|
}
|
||||||
|
/* 에러 코드 스타일 */
|
||||||
|
.error-content h1 {
|
||||||
|
font-size: 6rem;
|
||||||
|
font-weight: bold;
|
||||||
|
color: #ff8c00; /* 오렌지 */
|
||||||
|
text-shadow: 2px 2px 8px rgba(255, 140, 0, 0.3);
|
||||||
|
margin-bottom: 60px;
|
||||||
|
}
|
||||||
|
/* 홈으로 돌아가기 버튼 */
|
||||||
|
.home-btn {
|
||||||
|
display: inline-block;
|
||||||
|
padding: 10px 28px;
|
||||||
|
font-size: 1rem;
|
||||||
|
font-weight: bold;
|
||||||
|
text-decoration: none;
|
||||||
|
color: #fff;
|
||||||
|
background: rgba(105, 108, 255, 0.9);
|
||||||
|
border-radius: 30px;
|
||||||
|
transition: 0.3s ease-in-out;
|
||||||
|
box-shadow: 0 4px 15px rgba(105, 108, 255, 0.5);
|
||||||
|
}
|
||||||
|
/* 버튼 호버 효과 */
|
||||||
|
.home-btn:hover {
|
||||||
|
background: linear-gradient(90deg, orange, #ff8c00);
|
||||||
|
box-shadow: 0 0 20px rgba(255, 140, 0, 1);
|
||||||
|
transform: scale(1.05);
|
||||||
|
}
|
||||||
|
/* 페이드 인 애니메이션 */
|
||||||
|
@keyframes fadeIn {
|
||||||
|
from {
|
||||||
|
opacity: 0;
|
||||||
|
transform: translateY(-10px);
|
||||||
|
}
|
||||||
|
to {
|
||||||
|
opacity: 1;
|
||||||
|
transform: translateY(0);
|
||||||
|
}
|
||||||
|
}
|
||||||
/* 휴가 */
|
/* 휴가 */
|
||||||
|
|
||||||
.fc-daygrid-event {
|
.fc-daygrid-event {
|
||||||
|
|||||||
Binary file not shown.
|
Before Width: | Height: | Size: 212 KiB |
@ -51,6 +51,10 @@ $api.interceptors.response.use(
|
|||||||
// 오류 응답 처리
|
// 오류 응답 처리
|
||||||
if (error.response) {
|
if (error.response) {
|
||||||
switch (error.response.status) {
|
switch (error.response.status) {
|
||||||
|
case 400:
|
||||||
|
toastStore.onToast('잘못된 요청입니다.', 'e');
|
||||||
|
router.push('/error/400'); // 🚀 400 에러 발생 시 자동 이동
|
||||||
|
break;
|
||||||
case 401:
|
case 401:
|
||||||
if (!error.config.headers.isLogin) {
|
if (!error.config.headers.isLogin) {
|
||||||
// toastStore.onToast('인증이 필요합니다.', 'e');
|
// toastStore.onToast('인증이 필요합니다.', 'e');
|
||||||
@ -62,9 +66,11 @@ $api.interceptors.response.use(
|
|||||||
break;
|
break;
|
||||||
case 404:
|
case 404:
|
||||||
toastStore.onToast('요청한 페이지를 찾을 수 없습니다.', 'e');
|
toastStore.onToast('요청한 페이지를 찾을 수 없습니다.', 'e');
|
||||||
|
router.push('/error/404');
|
||||||
break;
|
break;
|
||||||
case 500:
|
case 500:
|
||||||
toastStore.onToast('서버 오류가 발생했습니다.', 'e');
|
toastStore.onToast('서버 오류가 발생했습니다.', 'e');
|
||||||
|
router.push('/error/500');
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
toastStore.onToast('알 수 없는 오류가 발생했습니다.', 'e');
|
toastStore.onToast('알 수 없는 오류가 발생했습니다.', 'e');
|
||||||
|
|||||||
@ -5,6 +5,7 @@
|
|||||||
<span v-if="isEssential" class="text-danger">*</span>
|
<span v-if="isEssential" class="text-danger">*</span>
|
||||||
</label>
|
</label>
|
||||||
<div class="col-md-10">
|
<div class="col-md-10">
|
||||||
|
<div class="d-flex align-items-center">
|
||||||
<input
|
<input
|
||||||
:id="name"
|
:id="name"
|
||||||
class="form-control"
|
class="form-control"
|
||||||
@ -18,15 +19,15 @@
|
|||||||
@focusout="$emit('focusout', modelValue)"
|
@focusout="$emit('focusout', modelValue)"
|
||||||
@input="handleInput"
|
@input="handleInput"
|
||||||
/>
|
/>
|
||||||
|
</div>
|
||||||
<div class="invalid-feedback" :class="isAlert ? 'd-block' : ''">{{ title }}을 확인해주세요.</div>
|
<div class="invalid-feedback" :class="isAlert ? 'd-block' : ''">{{ title }}을 확인해주세요.</div>
|
||||||
<!-- 카테고리 중복 -->
|
|
||||||
<div class="invalid-feedback" :class="isCateAlert ? 'd-block' : ''">카테고리 중복입니다.</div>
|
<div class="invalid-feedback" :class="isCateAlert ? 'd-block' : ''">카테고리 중복입니다.</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup>
|
<script setup>
|
||||||
import { ref, watch } from 'vue';
|
import { ref, watch } from 'vue';
|
||||||
|
|
||||||
// Props 정의
|
// Props 정의
|
||||||
const props = defineProps({
|
const props = defineProps({
|
||||||
@ -78,6 +79,11 @@
|
|||||||
default: '',
|
default: '',
|
||||||
required: false,
|
required: false,
|
||||||
},
|
},
|
||||||
|
isBtn: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false,
|
||||||
|
required: false,
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
// Emits 정의
|
// Emits 정의
|
||||||
|
|||||||
@ -10,6 +10,9 @@
|
|||||||
{{ isCommon ? item.label : item }}
|
{{ isCommon ? item.label : item }}
|
||||||
</option>
|
</option>
|
||||||
</select>
|
</select>
|
||||||
|
<div v-if="isBtn" class="ms-2">
|
||||||
|
<slot name="append"></slot>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div v-if="isColor && selected"
|
<div v-if="isColor && selected"
|
||||||
class="w-px-40 h-px-30"
|
class="w-px-40 h-px-30"
|
||||||
@ -70,6 +73,11 @@ const props = defineProps({
|
|||||||
default: true,
|
default: true,
|
||||||
required: false,
|
required: false,
|
||||||
},
|
},
|
||||||
|
isBtn: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false,
|
||||||
|
required: false,
|
||||||
|
},
|
||||||
isCommon: {
|
isCommon: {
|
||||||
type: Boolean,
|
type: Boolean,
|
||||||
default: false,
|
default: false,
|
||||||
|
|||||||
@ -1,5 +1,6 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="card mb-6" :class="{ 'disabled-class': data.localVote.LOCVOTDDT && (topVoters.length == 1 || data.localVote.LOCVOTRES || voteResult == 0)}">
|
<div v-if="data.voteMembers.some(item => item.MEMBERSEQ === userStore.user.id)"
|
||||||
|
class="card mb-6" :class="{ 'disabled-class': data.localVote.LOCVOTDDT && (topVoters.length == 1 || data.localVote.LOCVOTRES || voteResult == 0)}">
|
||||||
<div class="card-body" v-if="!data.localVote.LOCVOTDEL" >
|
<div class="card-body" v-if="!data.localVote.LOCVOTDEL" >
|
||||||
<h5 class="card-title mb-1">
|
<h5 class="card-title mb-1">
|
||||||
<div class="list-unstyled users-list d-flex align-items-center gap-1">
|
<div class="list-unstyled users-list d-flex align-items-center gap-1">
|
||||||
@ -10,7 +11,6 @@
|
|||||||
@error="$event.target.src = '/img/icons/icon.png'"
|
@error="$event.target.src = '/img/icons/icon.png'"
|
||||||
alt="user"
|
alt="user"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<div class="w-100">
|
<div class="w-100">
|
||||||
<div class="d-flex justify-content-between">
|
<div class="d-flex justify-content-between">
|
||||||
<div class="user-info">
|
<div class="user-info">
|
||||||
@ -23,20 +23,21 @@
|
|||||||
<button
|
<button
|
||||||
v-if="!data.localVote.LOCVOTDDT"
|
v-if="!data.localVote.LOCVOTDDT"
|
||||||
type="button"
|
type="button"
|
||||||
class="btn btn-label-danger btn-icon"
|
class="btn btn-label-danger btn-icon m-1"
|
||||||
@click="endBtn(data.localVote.LOCVOTSEQ)"
|
@click="endBtn(data.localVote.LOCVOTSEQ)"
|
||||||
><i class="bx bx-power-off"></i>
|
><i class="bx bx-power-off"></i>
|
||||||
</button>
|
</button>
|
||||||
<DeleteBtn v-if="!data.localVote.LOCVOTDDT" @click="voteDelete(data.localVote.LOCVOTSEQ)" />
|
<DeleteBtn v-if="!data.localVote.LOCVOTDDT" @click="voteDelete(data.localVote.LOCVOTSEQ)" />
|
||||||
</div>
|
</div>
|
||||||
<p v-if="data.localVote.LOCVOTDDT" class="btn-icon btn-danger rounded-2"><i class="bx bx-power-off"></i></p>
|
<p v-if="data.localVote.LOCVOTDDT" class="btn-icon btn-danger rounded-2"><i class="bx bx-power-off"></i></p>
|
||||||
<i v-if="yesVotetotal != '0'" class="bx bxs-check-circle link-success"></i>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</h5>
|
</h5>
|
||||||
<h5 class="mb-1">{{ data.localVote.LOCVOTTTL }}</h5>
|
<h5 class="mb-1">{{ data.localVote.LOCVOTTTL }}
|
||||||
|
<i v-if="yesVotetotal != '0'" class="bx bxs-check-circle link-success"></i>
|
||||||
|
</h5>
|
||||||
<small >{{ data.localVote.formatted_LOCVOTRDT }} ~ {{ data.localVote.formatted_LOCVOTEDT }}</small>
|
<small >{{ data.localVote.formatted_LOCVOTRDT }} ~ {{ data.localVote.formatted_LOCVOTEDT }}</small>
|
||||||
<!-- 투표안했을시-->
|
<!-- 투표안했을시-->
|
||||||
<div v-if="data.localVote.LOCVOTDDT && voteResult == 0">
|
<div v-if="data.localVote.LOCVOTDDT && voteResult == 0">
|
||||||
@ -50,9 +51,7 @@
|
|||||||
:data="data.voteDetails"
|
:data="data.voteDetails"
|
||||||
:voteInfo="data.localVote"
|
:voteInfo="data.localVote"
|
||||||
:total="data.voteDetails.length "/>
|
:total="data.voteDetails.length "/>
|
||||||
|
|
||||||
<small v-if="yesVotetotal != 0 && !data.localVote.LOCVOTDDT">투표 완료 : 종료시 투표 결과가 나타납니다.</small>
|
<small v-if="yesVotetotal != 0 && !data.localVote.LOCVOTDDT">투표 완료 : 종료시 투표 결과가 나타납니다.</small>
|
||||||
|
|
||||||
<!-- 투표 결과 -->
|
<!-- 투표 결과 -->
|
||||||
<div v-if="data.localVote.LOCVOTDDT" class="mt-3">
|
<div v-if="data.localVote.LOCVOTDDT" class="mt-3">
|
||||||
<vote-result-list :data="topVoters" @randomList="randomList" :randomResultNum="data.localVote.LOCVOTRES" :locvotreg="data.localVote.LOCVOTREG"/>
|
<vote-result-list :data="topVoters" @randomList="randomList" :randomResultNum="data.localVote.LOCVOTRES" :locvotreg="data.localVote.LOCVOTREG"/>
|
||||||
@ -96,6 +95,7 @@ const yesVotetotal = computed(() => {
|
|||||||
return props.data.voteDetails.reduce((sum, item) => sum + item.yesvote, 0);
|
return props.data.voteDetails.reduce((sum, item) => sum + item.yesvote, 0);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
// 가장 많은 투표를 받은 항목들 (1위)
|
// 가장 많은 투표를 받은 항목들 (1위)
|
||||||
const topVoters = computed(() => {
|
const topVoters = computed(() => {
|
||||||
// 가장 높은 VOTE_COUNT 찾기
|
// 가장 높은 VOTE_COUNT 찾기
|
||||||
|
|||||||
@ -1,9 +1,8 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="card-text">
|
<div class="card-text">
|
||||||
<div class="demo-inline-spacing mt-4">
|
<div class="demo-inline-spacing">
|
||||||
<!-- 투표리스트 -->
|
<!-- 투표리스트 -->
|
||||||
<div v-for="(item, index) in data"
|
<div v-for="(item, index) in data" :key="index">
|
||||||
:key="index">
|
|
||||||
<vote-card-check-list
|
<vote-card-check-list
|
||||||
:data="item"
|
:data="item"
|
||||||
:multiIs = voteInfo.LOCVOTMUL
|
:multiIs = voteInfo.LOCVOTMUL
|
||||||
@ -11,32 +10,32 @@
|
|||||||
@update:selectedValues="updateCheckedNames"
|
@update:selectedValues="updateCheckedNames"
|
||||||
/>
|
/>
|
||||||
<div v-if="voteInfo.LOCVOTADD ==='1' && index === data.length - 1">
|
<div v-if="voteInfo.LOCVOTADD ==='1' && index === data.length - 1">
|
||||||
<div v-for="(item, index) in itemList" :key="index" class="d-flex align-items-start mt-2">
|
<div v-for="(item, index) in itemList" :key="index" class=" mt-2">
|
||||||
<div class="flex-grow-1 me-2 ">
|
|
||||||
<form-input
|
<form-input
|
||||||
:title="'항목 ' + (index + data.length + 1)"
|
:title="'항목 ' + (index + data.length + 1)"
|
||||||
:name="'content' + index"
|
:name="'content' + index"
|
||||||
:is-essential="false"
|
:is-essential="false"
|
||||||
:is-alert="contentAlerts[index]"
|
:is-alert="contentAlerts[index]"
|
||||||
v-model="item.content"
|
v-model="item.content"
|
||||||
/>
|
:is-btn="true"
|
||||||
<link-input v-model="item.url" />
|
>
|
||||||
</div>
|
<template v-slot:append>
|
||||||
<delete-btn @click="removeItem(index)" />
|
<delete-btn @click="removeItem(index)" />
|
||||||
|
</template>
|
||||||
|
</form-input>
|
||||||
|
<link-input v-model="item.url" class="mb-1"/>
|
||||||
</div>
|
</div>
|
||||||
<div class="mb-4 d-flex justify-content mt-3">
|
<div class="d-flex justify-content">
|
||||||
<plus-btn @click="addItem" :disabled="total >= 10" class="mb-2" />
|
<plus-btn @click="addItem" :disabled="total >= 10" class="m-1" />
|
||||||
<button class="btn btn-primary btn-icon mb-3" @click="addContentSave(item.LOCVOTSEQ)" :disabled="isSaveDisabled">
|
<button class="btn btn-primary btn-icon mt-1" @click="addContentSave(item.LOCVOTSEQ)" :disabled="isSaveDisabled">
|
||||||
<i class="bx bx-check"></i>
|
<i class="bx bx-check"></i>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
<save-btn class="btn-sm mt-2" @click="selectVote"/>
|
<save-btn class="mt-2" @click="selectVote"/>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup>
|
<script setup>
|
||||||
|
|||||||
@ -1,34 +1,34 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="d-flex align-items-center">
|
<div class="mb-2 row">
|
||||||
|
<!-- 링크 아이콘 -->
|
||||||
|
<label for="name" class="col-md-2 col-form-label">
|
||||||
|
<div class="d-flex align-items-center">
|
||||||
<!-- 링크 아이콘 -->
|
<!-- 링크 아이콘 -->
|
||||||
<i class="bx bx-link-alt me-2" @click="togglePopover"></i>
|
<i class="bx bx-link-alt me-2" @click="togglePopover"></i>
|
||||||
|
|
||||||
<!-- 링크 입력창 (옆으로 나오게) -->
|
|
||||||
<div
|
|
||||||
v-if="isPopoverVisible"
|
|
||||||
class="d-flex"
|
|
||||||
>
|
|
||||||
<input
|
|
||||||
v-model="link"
|
|
||||||
placeholder="URL을 입력해주세요"
|
|
||||||
class="form-control"
|
|
||||||
style="min-width: 500px;"
|
|
||||||
/>
|
|
||||||
<save-btn class="btn-sm" @click="saveLink"/>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- 등록된 링크, 입력창이 보이지 않고 등록된 링크만 보일 때 -->
|
<!-- 등록된 링크, 입력창이 보이지 않고 등록된 링크만 보일 때 -->
|
||||||
<span v-if="isLinkSaved && !isPopoverVisible" class="ms-2">
|
<span v-if="isLinkSaved && !isPopoverVisible" class="ms-2">
|
||||||
<a :href="formattedLink" class="d-inline-block text-truncate" target="_blank" rel="noopener noreferrer">
|
<a :href="formattedLink" class="d-inline-block text-truncate" target="_blank" rel="noopener noreferrer">
|
||||||
{{ link }}
|
{{ link }}
|
||||||
</a>
|
</a>
|
||||||
</span>
|
</span>
|
||||||
|
</div>
|
||||||
|
</label>
|
||||||
|
<!-- 링크 입력창 (옆으로 나오게) -->
|
||||||
|
<div v-if="isPopoverVisible" class="col-md-10">
|
||||||
|
<div class="d-flex align-items-center">
|
||||||
|
<input
|
||||||
|
v-model="link"
|
||||||
|
placeholder="URL을 입력해주세요"
|
||||||
|
class="form-control me-2"
|
||||||
|
/>
|
||||||
|
<save-btn class="btn-icon" @click="saveLink"/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup>
|
<script setup>
|
||||||
import SaveBtn from '@c/button/SaveBtn.vue'
|
import SaveBtn from '@c/button/SaveBtn.vue'
|
||||||
|
|
||||||
import { ref, computed } from "vue";
|
import { ref, computed } from "vue";
|
||||||
|
|
||||||
const props = defineProps({
|
const props = defineProps({
|
||||||
|
|||||||
@ -11,7 +11,6 @@
|
|||||||
<div v-if="isRandom === false && randomResultNum">
|
<div v-if="isRandom === false && randomResultNum">
|
||||||
<vote-result-card :randomResultNum="randomResultNum"/>
|
<vote-result-card :randomResultNum="randomResultNum"/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<button v-if="isRandom" class="btn btn-primary" type="button" disabled="">
|
<button v-if="isRandom" class="btn btn-primary" type="button" disabled="">
|
||||||
<span class="spinner-grow me-1" role="status" aria-hidden="true"></span>
|
<span class="spinner-grow me-1" role="status" aria-hidden="true"></span>
|
||||||
random..
|
random..
|
||||||
|
|||||||
@ -2,7 +2,7 @@
|
|||||||
<div class="d-flex align-items-center">
|
<div class="d-flex align-items-center">
|
||||||
<!--투표한 사람 목록 -->
|
<!--투표한 사람 목록 -->
|
||||||
<div class="d-flex align-items-center gap-2">
|
<div class="d-flex align-items-center gap-2">
|
||||||
<i class='bx bxs-user-check link-info'></i>
|
<i class='bx bxs-user-check link-info fa-3x'></i>
|
||||||
<vote-complete-user-list
|
<vote-complete-user-list
|
||||||
v-for="(item, index) in voetedUsers"
|
v-for="(item, index) in voetedUsers"
|
||||||
:key="index"
|
:key="index"
|
||||||
@ -11,7 +11,7 @@
|
|||||||
</div>
|
</div>
|
||||||
<!-- 투표안한 사람 목록 -->
|
<!-- 투표안한 사람 목록 -->
|
||||||
<div class="d-flex align-items-center gap-2 ms-auto">
|
<div class="d-flex align-items-center gap-2 ms-auto">
|
||||||
<i class='bx bxs-user-x link-danger'></i>
|
<i class='bx bxs-user-x link-danger fa-3x'></i>
|
||||||
<vote-in-complete-user-list
|
<vote-in-complete-user-list
|
||||||
v-for="(item, index) in noVoetedUsers"
|
v-for="(item, index) in noVoetedUsers"
|
||||||
:key="index"
|
:key="index"
|
||||||
|
|||||||
@ -1,29 +1,18 @@
|
|||||||
<template>
|
<template>
|
||||||
<ul class="d-flex p-0 mb-0">
|
<!-- <ul class="d-flex p-0 mb-0 flex-wrap">
|
||||||
<li class="d-flex">
|
<li class="d-flex">
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
class="alphabet-btn"
|
class="alphabet-btn"
|
||||||
:class="{ active: selectedAl === 'all' }"
|
:class="{ active: selectedAl === 'all' }"
|
||||||
@click="selectAlphabet('all')"
|
@click="selectAlphabet('all')"
|
||||||
> 전체 ({{ totalCount}})
|
> 전체 ({{ totalCount }})
|
||||||
</button>
|
</button>
|
||||||
<span class="divider">|</span>
|
|
||||||
</li>
|
</li>
|
||||||
<li v-for="(char, index) in koreanChars" :key="char.CHARACTER_" class="d-flex">
|
</ul> -->
|
||||||
<button
|
<div v-for="(group, groupIndex) in chunkedKoreanChars" :key="'ko-group-' + groupIndex">
|
||||||
type="button"
|
|
||||||
class="alphabet-btn"
|
|
||||||
:class="{ active: selectedAl === char.CHARACTER_ }"
|
|
||||||
@click="selectAlphabet(char.CHARACTER_)"
|
|
||||||
>
|
|
||||||
{{ char.CHARACTER_ }} ({{ char.COUNT }})
|
|
||||||
</button>
|
|
||||||
<span v-if="index !== koreanChars.length - 1" class="divider">|</span>
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
<ul class="d-flex p-0 mb-0">
|
<ul class="d-flex p-0 mb-0">
|
||||||
<li v-for="(char, index) in englishChars" :key="char.CHARACTER_" class="d-flex">
|
<li v-for="(char, index) in group" :key="char.CHARACTER_" class="d-flex">
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
class="alphabet-btn"
|
class="alphabet-btn"
|
||||||
@ -32,9 +21,25 @@
|
|||||||
>
|
>
|
||||||
{{ char.CHARACTER_ }} ({{ char.COUNT }})
|
{{ char.CHARACTER_ }} ({{ char.COUNT }})
|
||||||
</button>
|
</button>
|
||||||
<span v-if="index !== englishChars.length - 1" class="divider">|</span>
|
<span v-if="index !== group.length - 1" class="divider">|</span>
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
|
</div>
|
||||||
|
<div v-for="(group, groupIndex) in chunkedEnglishChars" :key="'en-group-' + groupIndex">
|
||||||
|
<ul class="d-flex p-0 mb-0">
|
||||||
|
<li v-for="(char, index) in group" :key="char.CHARACTER_" class="d-flex">
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
class="alphabet-btn"
|
||||||
|
:class="{ active: selectedAl === char.CHARACTER_ }"
|
||||||
|
@click="selectAlphabet(char.CHARACTER_)"
|
||||||
|
>
|
||||||
|
{{ char.CHARACTER_ }} ({{ char.COUNT }})
|
||||||
|
</button>
|
||||||
|
<span v-if="index !== group.length - 1" class="divider">|</span>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup>
|
<script setup>
|
||||||
@ -47,7 +52,7 @@ const props = defineProps({
|
|||||||
},
|
},
|
||||||
selectedAl: {
|
selectedAl: {
|
||||||
type: String,
|
type: String,
|
||||||
default : '',
|
default: '',
|
||||||
required: false,
|
required: false,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
@ -56,14 +61,24 @@ const selectedAlphabet = ref(props.selectedAl);
|
|||||||
const totalCount = computed(() => {
|
const totalCount = computed(() => {
|
||||||
return props.indexCategory.reduce((sum, item) => sum + item.COUNT, 0);
|
return props.indexCategory.reduce((sum, item) => sum + item.COUNT, 0);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const chunkArray = (arr, size) => {
|
||||||
|
return arr.reduce((acc, _, i) => {
|
||||||
|
if (i % size === 0) acc.push(arr.slice(i, i + size));
|
||||||
|
return acc;
|
||||||
|
}, []);
|
||||||
|
};
|
||||||
|
|
||||||
const koreanChars = computed(() => {
|
const koreanChars = computed(() => {
|
||||||
return props.indexCategory.filter(char => /[ㄱ-ㅎ가-힣]/.test(char.CHARACTER_));
|
return props.indexCategory.filter(char => /[ㄱ-ㅎ가-힣]/.test(char.CHARACTER_));
|
||||||
});
|
});
|
||||||
|
|
||||||
const englishChars = computed(() => {
|
const englishChars = computed(() => {
|
||||||
return props.indexCategory.filter(char => /^[a-zA-Z]$/.test(char.CHARACTER_));
|
return props.indexCategory.filter(char => /^[a-zA-Z]$/.test(char.CHARACTER_));
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const chunkedKoreanChars = computed(() => chunkArray(koreanChars.value, 5));
|
||||||
|
const chunkedEnglishChars = computed(() => chunkArray(englishChars.value, 5));
|
||||||
|
|
||||||
const emit = defineEmits();
|
const emit = defineEmits();
|
||||||
const selectAlphabet = (alphabet) => {
|
const selectAlphabet = (alphabet) => {
|
||||||
selectedAlphabet.value = selectedAlphabet.value === alphabet ? null : alphabet;
|
selectedAlphabet.value = selectedAlphabet.value === alphabet ? null : alphabet;
|
||||||
@ -81,6 +96,7 @@ const selectAlphabet = (alphabet) => {
|
|||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
width: 70%;
|
width: 70%;
|
||||||
height: 40px;
|
height: 40px;
|
||||||
|
transition: color 0.3s ease, font-size 0.3s ease; /* Smooth transition for color */
|
||||||
}
|
}
|
||||||
|
|
||||||
.alphabet-btn:hover {
|
.alphabet-btn:hover {
|
||||||
@ -90,10 +106,16 @@ const selectAlphabet = (alphabet) => {
|
|||||||
.alphabet-btn.active {
|
.alphabet-btn.active {
|
||||||
color: #0d6efd;
|
color: #0d6efd;
|
||||||
text-decoration: underline;
|
text-decoration: underline;
|
||||||
|
font-size: 13px; /* Keep font size fixed in active state */
|
||||||
}
|
}
|
||||||
|
|
||||||
.divider {
|
.divider {
|
||||||
color: #bbb;
|
color: #bbb;
|
||||||
font-size: 14px;
|
font-size: 14px;
|
||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.flex-wrap {
|
||||||
|
flex-wrap: wrap;
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
<template>
|
<template>
|
||||||
<li class="mt-5 card p-5">
|
<li class="card p-5 mb-2">
|
||||||
<DictWrite
|
<DictWrite
|
||||||
v-if="writeStore.isItemActive(item.WRDDICSEQ)"
|
v-if="writeStore.isItemActive(item.WRDDICSEQ)"
|
||||||
@close="writeStore.closeAll();"
|
@close="writeStore.closeAll();"
|
||||||
@ -64,7 +64,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="edit-btn" v-if="userStore.user.role !== 'ROLE_ADMIN'">
|
<div class="edit-btn">
|
||||||
<EditBtn ref="writeButton" @click="writeStore.toggleItem(item.WRDDICSEQ)" :isToggleEnabled="true"
|
<EditBtn ref="writeButton" @click="writeStore.toggleItem(item.WRDDICSEQ)" :isToggleEnabled="true"
|
||||||
:isActive="writeStore.activeItemId === item.WRDDICSEQ"/>
|
:isActive="writeStore.activeItemId === item.WRDDICSEQ"/>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@ -1,39 +1,31 @@
|
|||||||
<template>
|
<template>
|
||||||
<div>
|
<div>
|
||||||
<div class="row">
|
<FormSelect class="me-5"
|
||||||
<div class="col-10">
|
|
||||||
<FormSelect
|
|
||||||
name="cate"
|
name="cate"
|
||||||
title="카테고리 선택"
|
title="카테고리"
|
||||||
:data="dataList"
|
:data="dataList"
|
||||||
:is-common="true"
|
:is-common="true"
|
||||||
@update:data="selectCategory = $event"
|
@update:data="selectCategory = $event"
|
||||||
@change="onChange"
|
@change="onChange"
|
||||||
:value="formValue"
|
:value="formValue"
|
||||||
:disabled="isDisabled"
|
|
||||||
:is-essential="false"
|
:is-essential="false"
|
||||||
/>
|
/>
|
||||||
</div>
|
<div v-if="!isDisabled" class="add-btn">
|
||||||
<div class="col-2 btn-margin" v-if="!isDisabled">
|
|
||||||
<PlusBtn @click="toggleInput"/>
|
<PlusBtn @click="toggleInput"/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="row" v-if="showInput">
|
<div v-if="showInput">
|
||||||
<div class="col-10">
|
<FormInput class="me-5"
|
||||||
<FormInput
|
|
||||||
ref="categoryInputRef"
|
ref="categoryInputRef"
|
||||||
title="카테고리 입력"
|
title="새 카테고리"
|
||||||
name="카테고리"
|
name="새 카테고리"
|
||||||
@update:modelValue="addCategory = $event"
|
@update:modelValue="addCategory = $event"
|
||||||
:is-cate-alert="addCategoryAlert"
|
:is-cate-alert="addCategoryAlert"
|
||||||
@focusout="handleCategoryFocusout(addCategory)"
|
@focusout="handleCategoryFocusout(addCategory)"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
<FormInput class="me-5"
|
||||||
</div>
|
|
||||||
<div class="dict-w">
|
|
||||||
<FormInput
|
|
||||||
title="용어"
|
title="용어"
|
||||||
type="text"
|
type="text"
|
||||||
name="word"
|
name="word"
|
||||||
@ -44,9 +36,8 @@
|
|||||||
:disabled="isDisabled"
|
:disabled="isDisabled"
|
||||||
@keyup="ValidHandler('title')"
|
@keyup="ValidHandler('title')"
|
||||||
/>
|
/>
|
||||||
</div>
|
|
||||||
<div>
|
<div>
|
||||||
<QEditor @keyup="ValidHandler('content')" @update:data="handleContentUpdate" @update:imageUrls="imageUrls = $event" :is-alert="wordContentAlert" :initialData="contentValue"/>
|
<QEditor class="" @keyup="ValidHandler('content')" @update:data="handleContentUpdate" @update:imageUrls="imageUrls = $event" :is-alert="wordContentAlert" :initialData="contentValue"/>
|
||||||
<div class="text-end mt-5">
|
<div class="text-end mt-5">
|
||||||
<button class="btn btn-primary" @click="saveWord">
|
<button class="btn btn-primary" @click="saveWord">
|
||||||
<i class="bx bx-check"></i>
|
<i class="bx bx-check"></i>
|
||||||
@ -189,8 +180,9 @@ const saveWord = () => {
|
|||||||
|
|
||||||
// 카테고리 focusout 이벤트 핸들러 추가
|
// 카테고리 focusout 이벤트 핸들러 추가
|
||||||
const handleCategoryFocusout = (value) => {
|
const handleCategoryFocusout = (value) => {
|
||||||
const valueTrim = value.trim();
|
|
||||||
|
|
||||||
|
const valueTrim = value.trim();
|
||||||
|
if(value){
|
||||||
const existingCategory = props.dataList.find(item => item.label === valueTrim);
|
const existingCategory = props.dataList.find(item => item.label === valueTrim);
|
||||||
|
|
||||||
// 카테고리 입력시 공백
|
// 카테고리 입력시 공백
|
||||||
@ -220,6 +212,7 @@ const handleCategoryFocusout = (value) => {
|
|||||||
} else {
|
} else {
|
||||||
addCategoryAlert.value = false;
|
addCategoryAlert.value = false;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
@ -235,4 +228,9 @@ const handleCategoryFocusout = (value) => {
|
|||||||
margin-top: 2.5rem
|
margin-top: 2.5rem
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
.add-btn {
|
||||||
|
position: absolute;
|
||||||
|
right: 0.7rem;
|
||||||
|
top: 1.2rem;
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@ -89,10 +89,12 @@ const routes = [
|
|||||||
component: () => import('@v/admin/TheAuthorization.vue'),
|
component: () => import('@v/admin/TheAuthorization.vue'),
|
||||||
meta: { requiresAuth: true }
|
meta: { requiresAuth: true }
|
||||||
},
|
},
|
||||||
|
{ path: "/error/400", name: "Error400", component: () => import('@v/error/Error400.vue') },
|
||||||
|
{ path: "/error/500", name: "Error500", component: () => import('@v/error/Error500.vue') },
|
||||||
{
|
{
|
||||||
path: "/:anything(.*)",
|
path: "/:anything(.*)",
|
||||||
component: () => import('@v/ErrorPage.vue'),
|
name: "Error404", component: () => import('@v/error/Error404.vue')
|
||||||
}
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
const router = createRouter({
|
const router = createRouter({
|
||||||
@ -126,4 +128,21 @@ router.beforeEach(async (to, from, next) => {
|
|||||||
next();
|
next();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
import axios from 'axios';
|
||||||
|
|
||||||
|
axios.interceptors.response.use(
|
||||||
|
response => response,
|
||||||
|
error => {
|
||||||
|
const status = error.response?.status;
|
||||||
|
|
||||||
|
if (status === 400) {
|
||||||
|
router.push({ name: 'Error400' });
|
||||||
|
} else if (status === 500) {
|
||||||
|
router.push({ name: 'Error500' });
|
||||||
|
}
|
||||||
|
|
||||||
|
return Promise.reject(error);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
export default router
|
export default router
|
||||||
|
|||||||
45
src/stores/useBoardAccessStore.js
Normal file
45
src/stores/useBoardAccessStore.js
Normal file
@ -0,0 +1,45 @@
|
|||||||
|
/*
|
||||||
|
작성자 : 박성용
|
||||||
|
작성일 : 2025-03-14
|
||||||
|
수정자 :
|
||||||
|
수정일 :
|
||||||
|
설명 : 게시글 수정 시 비밀번호 적재용.
|
||||||
|
*/
|
||||||
|
import { ref, computed, watch } from 'vue';
|
||||||
|
import { defineStore } from 'pinia';
|
||||||
|
|
||||||
|
export const useBoardAccessStore = defineStore(
|
||||||
|
'access',
|
||||||
|
() => {
|
||||||
|
const password = ref('');
|
||||||
|
|
||||||
|
// watch(password, newValue => {
|
||||||
|
// localStorage.setItem('tempPassword', JSON.stringify(newValue.value));
|
||||||
|
// });
|
||||||
|
|
||||||
|
if (localStorage.getItem('tempPassword')) {
|
||||||
|
// 저장된 값을 불러와 상태에 할당
|
||||||
|
const tempPassword = localStorage.getItem('tempPassword');
|
||||||
|
if (typeof tempPassword === 'String') password.value = tempPassword;
|
||||||
|
}
|
||||||
|
|
||||||
|
function setBoardPassword(input) {
|
||||||
|
password.value = input;
|
||||||
|
if (typeof input === 'String') localStorage.setItem('tempPassword', input);
|
||||||
|
}
|
||||||
|
|
||||||
|
function $reset() {
|
||||||
|
password.value = '';
|
||||||
|
localStorage.removeItem('tempPassword');
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
password,
|
||||||
|
setBoardPassword,
|
||||||
|
$reset,
|
||||||
|
};
|
||||||
|
},
|
||||||
|
{
|
||||||
|
persist: true,
|
||||||
|
},
|
||||||
|
);
|
||||||
@ -1,13 +0,0 @@
|
|||||||
<template>
|
|
||||||
Error
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script>
|
|
||||||
export default {
|
|
||||||
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<style>
|
|
||||||
|
|
||||||
</style>
|
|
||||||
@ -85,11 +85,13 @@
|
|||||||
import { ref, onMounted, computed, watch, inject } from 'vue';
|
import { ref, onMounted, computed, watch, inject } from 'vue';
|
||||||
import { useRoute, useRouter } from 'vue-router';
|
import { useRoute, useRouter } from 'vue-router';
|
||||||
import { useToastStore } from '@s/toastStore';
|
import { useToastStore } from '@s/toastStore';
|
||||||
|
import { useBoardAccessStore } from '@s/useBoardAccessStore';
|
||||||
import axios from '@api';
|
import axios from '@api';
|
||||||
|
|
||||||
// 공통
|
// 공통
|
||||||
const $common = inject('common');
|
const $common = inject('common');
|
||||||
const toastStore = useToastStore();
|
const toastStore = useToastStore();
|
||||||
|
const accessStore = useBoardAccessStore();
|
||||||
|
|
||||||
// 상태 변수
|
// 상태 변수
|
||||||
const title = ref('');
|
const title = ref('');
|
||||||
@ -117,22 +119,29 @@
|
|||||||
|
|
||||||
// 게시물 데이터 로드
|
// 게시물 데이터 로드
|
||||||
const fetchBoardDetails = async () => {
|
const fetchBoardDetails = async () => {
|
||||||
try {
|
// 수정 데이터 전송
|
||||||
const response = await axios.get(`board/${currentBoardId.value}`);
|
const password = accessStore.password;
|
||||||
const data = response.data.data;
|
const params = {
|
||||||
|
password: password || '',
|
||||||
|
};
|
||||||
|
//const response = await axios.get(`board/${currentBoardId.value}`);
|
||||||
|
const { data } = await axios.post(`board/${currentBoardId.value}`, params);
|
||||||
|
|
||||||
|
if (data.code !== 200) {
|
||||||
|
toastStore.onToast(data.message, 'e');
|
||||||
|
router.go(-1);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const boardData = data.data;
|
||||||
// 기존 첨부파일 추가
|
// 기존 첨부파일 추가
|
||||||
if (data.hasAttachment && data.attachments.length > 0) {
|
if (boardData.hasAttachment && boardData.attachments.length > 0) {
|
||||||
attachFiles.value = addDisplayFileName([...data.attachments]);
|
attachFiles.value = addDisplayFileName([...boardData.attachments]);
|
||||||
}
|
}
|
||||||
|
|
||||||
// 데이터 설정
|
// 데이터 설정
|
||||||
title.value = data.title || '제목 없음';
|
title.value = boardData.title || '제목 없음';
|
||||||
content.value = data.content || '내용 없음';
|
content.value = boardData.content || '내용 없음';
|
||||||
contentLoaded.value = true;
|
contentLoaded.value = true;
|
||||||
} catch (error) {
|
|
||||||
console.error('게시물 가져오기 오류:', error.response || error.message);
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
// 기존 첨부파일명을 노출
|
// 기존 첨부파일명을 노출
|
||||||
@ -144,6 +153,7 @@
|
|||||||
|
|
||||||
// 목록 페이지로 이동
|
// 목록 페이지로 이동
|
||||||
const goList = () => {
|
const goList = () => {
|
||||||
|
accessStore.$reset();
|
||||||
router.push('/board');
|
router.push('/board');
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -218,7 +228,6 @@
|
|||||||
const updateBoard = async () => {
|
const updateBoard = async () => {
|
||||||
if (checkValidation()) return;
|
if (checkValidation()) return;
|
||||||
|
|
||||||
try {
|
|
||||||
// 수정 데이터 전송
|
// 수정 데이터 전송
|
||||||
const boardData = {
|
const boardData = {
|
||||||
LOCBRDTTL: title.value.trim(),
|
LOCBRDTTL: title.value.trim(),
|
||||||
@ -244,17 +253,17 @@
|
|||||||
formData.append('files', file);
|
formData.append('files', file);
|
||||||
});
|
});
|
||||||
|
|
||||||
await axios.put(`board/${currentBoardId.value}`, formData, { isFormData: true });
|
const { data } = await axios.put(`board/${currentBoardId.value}`, formData, { isFormData: true });
|
||||||
|
if (data.code === 200 && data.data === true) {
|
||||||
toastStore.onToast('게시물이 수정되었습니다.', 's');
|
toastStore.onToast('게시물이 수정되었습니다.', 's');
|
||||||
goList();
|
goList();
|
||||||
} catch (error) {
|
} else {
|
||||||
console.error('게시물 수정 중 오류 발생:', error);
|
toastStore.onToast('게시물 수정에 실패했습니다.', 'e');
|
||||||
toastStore.onToast('게시물 수정에 실패했습니다.');
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// 컴포넌트 마운트 시 데이터 로드
|
// 컴포넌트 마운트 시 데이터 로드
|
||||||
onMounted(() => {
|
onMounted(async () => {
|
||||||
if (currentBoardId.value) {
|
if (currentBoardId.value) {
|
||||||
fetchBoardDetails();
|
fetchBoardDetails();
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@ -139,6 +139,7 @@
|
|||||||
import { useRoute, useRouter } from 'vue-router';
|
import { useRoute, useRouter } from 'vue-router';
|
||||||
import { useUserInfoStore } from '@/stores/useUserInfoStore';
|
import { useUserInfoStore } from '@/stores/useUserInfoStore';
|
||||||
import { useToastStore } from '@s/toastStore';
|
import { useToastStore } from '@s/toastStore';
|
||||||
|
import { useBoardAccessStore } from '@s/useBoardAccessStore';
|
||||||
import axios from '@api';
|
import axios from '@api';
|
||||||
|
|
||||||
const $common = inject('common');
|
const $common = inject('common');
|
||||||
@ -161,6 +162,8 @@
|
|||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const userStore = useUserInfoStore();
|
const userStore = useUserInfoStore();
|
||||||
const toastStore = useToastStore();
|
const toastStore = useToastStore();
|
||||||
|
const accessStore = useBoardAccessStore();
|
||||||
|
|
||||||
const currentBoardId = ref(Number(route.params.id));
|
const currentBoardId = ref(Number(route.params.id));
|
||||||
const unknown = computed(() => profileName.value === '익명');
|
const unknown = computed(() => profileName.value === '익명');
|
||||||
const currentUserId = computed(() => userStore?.user?.id); // 현재 로그인한 사용자 id
|
const currentUserId = computed(() => userStore?.user?.id); // 현재 로그인한 사용자 id
|
||||||
@ -242,21 +245,25 @@
|
|||||||
|
|
||||||
// 게시물 상세 데이터 불러오기
|
// 게시물 상세 데이터 불러오기
|
||||||
const fetchBoardDetails = async () => {
|
const fetchBoardDetails = async () => {
|
||||||
const response = await axios.get(`board/${currentBoardId.value}`);
|
const { data } = await axios.get(`board/${currentBoardId.value}`);
|
||||||
const data = response.data.data;
|
if (data?.data) {
|
||||||
|
const boardData = data.data;
|
||||||
profileName.value = data.author || '익명';
|
profileName.value = boardData.author || '익명';
|
||||||
authorId.value = data.authorId;
|
authorId.value = boardData.authorId;
|
||||||
boardTitle.value = data.title || '제목 없음';
|
boardTitle.value = boardData.title || '제목 없음';
|
||||||
boardContent.value = data.content || '';
|
boardContent.value = boardData.content || '';
|
||||||
profileImg.value = data.profileImg || '';
|
profileImg.value = boardData.profileImg || '';
|
||||||
date.value = data.date || '';
|
date.value = boardData.date || '';
|
||||||
views.value = data.cnt || 0;
|
views.value = boardData.cnt || 0;
|
||||||
likes.value = data.likeCount || 0;
|
likes.value = boardData.likeCount || 0;
|
||||||
dislikes.value = data.dislikeCount || 0;
|
dislikes.value = boardData.dislikeCount || 0;
|
||||||
attachment.value = data.hasAttachment || null;
|
attachment.value = boardData.hasAttachment || null;
|
||||||
commentNum.value = data.commentCount || 0;
|
commentNum.value = boardData.commentCount || 0;
|
||||||
attachments.value = data.attachments || [];
|
attachments.value = boardData.attachments || [];
|
||||||
|
} else {
|
||||||
|
toastStore.onToast(data.message, 'e');
|
||||||
|
router.back();
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// 좋아요, 싫어요
|
// 좋아요, 싫어요
|
||||||
@ -603,20 +610,23 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const response = await axios.post(`board/${currentBoardId.value}/password`, {
|
const { data } = await axios.post(`board/${currentBoardId.value}/password`, {
|
||||||
LOCBRDPWD: password.value,
|
LOCBRDPWD: password.value,
|
||||||
LOCBRDSEQ: currentBoardId.value,
|
LOCBRDSEQ: currentBoardId.value,
|
||||||
});
|
});
|
||||||
|
|
||||||
if (response.data.code === 200 && response.data.data === true) {
|
if (data.code === 200 && data.data === true) {
|
||||||
|
accessStore.setBoardPassword(password.value);
|
||||||
boardPasswordAlert.value = '';
|
boardPasswordAlert.value = '';
|
||||||
isPassword.value = false;
|
isPassword.value = false;
|
||||||
|
|
||||||
if (lastClickedButton.value === 'edit') {
|
if (lastClickedButton.value === 'edit') {
|
||||||
router.push({ name: 'BoardEdit', params: { id: currentBoardId.value } });
|
router.push({ name: 'BoardEdit', params: { id: currentBoardId.value } });
|
||||||
|
return;
|
||||||
} else if (lastClickedButton.value === 'delete') {
|
} else if (lastClickedButton.value === 'delete') {
|
||||||
await deletePost();
|
await deletePost();
|
||||||
}
|
}
|
||||||
|
accessStore.$reset();
|
||||||
lastClickedButton.value = null;
|
lastClickedButton.value = null;
|
||||||
} else {
|
} else {
|
||||||
boardPasswordAlert.value = '비밀번호가 일치하지 않습니다.';
|
boardPasswordAlert.value = '비밀번호가 일치하지 않습니다.';
|
||||||
|
|||||||
12
src/views/error/Error400.vue
Normal file
12
src/views/error/Error400.vue
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
<template>
|
||||||
|
<div class="error-container">
|
||||||
|
<div class="error-content">
|
||||||
|
<img src="/img/illustrations/page-misc-error-dark.png" alt="Error Illustration" class="error-image" />
|
||||||
|
<h1>400</h1>
|
||||||
|
<RouterLink to="/" class="home-btn">홈으로 돌아가기</RouterLink>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
</style>
|
||||||
13
src/views/error/Error404.vue
Normal file
13
src/views/error/Error404.vue
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
<template>
|
||||||
|
<div class="error-page">
|
||||||
|
<div class="error-content">
|
||||||
|
<img src="/img/illustrations/page-misc-error-light.png" alt="Error Illustration" class="error-image" />
|
||||||
|
<h1>404</h1>
|
||||||
|
<RouterLink to="/" class="home-btn">홈으로 돌아가기</RouterLink>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
|
||||||
|
</style>
|
||||||
16
src/views/error/Error500.vue
Normal file
16
src/views/error/Error500.vue
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
<template>
|
||||||
|
<div class="error-page">
|
||||||
|
<div class="error-content">
|
||||||
|
<img src="/img/illustrations/page-misc-error-dark.png" alt="Error Illustration" class="error-image" />
|
||||||
|
<h1>500</h1>
|
||||||
|
<RouterLink to="/" class="home-btn">HOME</RouterLink>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup>
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
|
||||||
|
</style>
|
||||||
@ -1,13 +1,20 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="container-xxl flex-grow-1 container-p-y">
|
<div class="container-xxl flex-grow-1 container-p-y">
|
||||||
<div class="row g-3">
|
<div class="">
|
||||||
<div class="mt-8">
|
|
||||||
<!-- 투표 작성 -->
|
<!-- 투표 작성 -->
|
||||||
<WriteBtn @click="voteWrite" />
|
<WriteBtn @click="voteWrite" />
|
||||||
|
|
||||||
<!-- 투표마감/투표중 셀렉트 -->
|
<!-- 투표마감/투표중 셀렉트 -->
|
||||||
<FormSelect class="col-3" name="cate" :isLabel="false" title="투표상태" :data="categoryList" @update:data="category = $event" @change="selectHandler"/>
|
<div v-for="(item, index) in categoryList" @change="selectHandler"
|
||||||
|
:key="index"
|
||||||
|
class="form-check form-check-inline" >
|
||||||
|
<input class="form-check-input" type="radio" name="cate" :id="'inlineRadio' + index"
|
||||||
|
:value="index" v-model="category">
|
||||||
|
<label class="form-check-label" :for="'inlineRadio' + index">{{ item }}</label>
|
||||||
|
</div>
|
||||||
|
|
||||||
<!-- 내가한 투표 보기 -->
|
<!-- 내가한 투표 보기 -->
|
||||||
<div class="p-4">
|
<div class="">
|
||||||
<div class="form-check">
|
<div class="form-check">
|
||||||
<input class="form-check-input" type="checkbox" value="" id="defaultCheck1" v-model="ischeked" @change="changeCheck">
|
<input class="form-check-input" type="checkbox" value="" id="defaultCheck1" v-model="ischeked" @change="changeCheck">
|
||||||
<label class="form-check-label" for="defaultCheck1"> 내가 한 투표 </label>
|
<label class="form-check-label" for="defaultCheck1"> 내가 한 투표 </label>
|
||||||
@ -15,7 +22,7 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- 투표리스트 -->
|
<!-- 투표리스트 -->
|
||||||
<div v-if="voteListCardData.length == 0 " class="mt66">등록된 투표가 없습니다.</div>
|
<div v-if="voteListCardData.length == 0 " >투표가 없습니다.</div>
|
||||||
<vote-list
|
<vote-list
|
||||||
:data="voteListCardData"
|
:data="voteListCardData"
|
||||||
@addContents="addContents"
|
@addContents="addContents"
|
||||||
@ -27,7 +34,7 @@
|
|||||||
@randomList="randomList"
|
@randomList="randomList"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<!-- 페이지네이션 -->
|
<!-- 페이지네이션 -->
|
||||||
<div class="row g-3">
|
<div class="row g-3">
|
||||||
<div class="mt-8">
|
<div class="mt-8">
|
||||||
@ -39,7 +46,6 @@
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
</template>
|
</template>
|
||||||
<script setup>
|
<script setup>
|
||||||
import { getCurrentInstance, onMounted, ref } from 'vue';
|
import { getCurrentInstance, onMounted, ref } from 'vue';
|
||||||
|
|||||||
@ -31,8 +31,7 @@
|
|||||||
@change="ValidHandlerendDate"
|
@change="ValidHandlerendDate"
|
||||||
/>
|
/>
|
||||||
<!-- 항목 입력 반복 -->
|
<!-- 항목 입력 반복 -->
|
||||||
<div v-for="(item, index) in itemList" :key="index" class="d-flex align-items-start ">
|
<div v-for="(item, index) in itemList" :key="index">
|
||||||
<div class="flex-grow-1 me-2 ">
|
|
||||||
<form-input
|
<form-input
|
||||||
:title="'항목 ' + (index + 1)"
|
:title="'항목 ' + (index + 1)"
|
||||||
:name="'content' + index"
|
:name="'content' + index"
|
||||||
@ -40,11 +39,14 @@
|
|||||||
:is-alert="contentAlerts[index]"
|
:is-alert="contentAlerts[index]"
|
||||||
v-model="item.content"
|
v-model="item.content"
|
||||||
@keyup="ValidHandler('content' + (index + 1))"
|
@keyup="ValidHandler('content' + (index + 1))"
|
||||||
/>
|
:is-btn="true"
|
||||||
<link-input v-model="item.url" class="mb-1"/>
|
>
|
||||||
</div>
|
<template v-slot:append>
|
||||||
<!-- delete-btn을 오른쪽으로 정렬 -->
|
|
||||||
<delete-btn @click="removeItem(index)" :disabled="index < 2" />
|
<delete-btn @click="removeItem(index)" :disabled="index < 2" />
|
||||||
|
</template>
|
||||||
|
</form-input>
|
||||||
|
|
||||||
|
<link-input v-model="item.url" class="mb-1"/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<plus-btn @click="addItem" :disabled="itemList.length >= 10" class="mb-3" />
|
<plus-btn @click="addItem" :disabled="itemList.length >= 10" class="mb-3" />
|
||||||
|
|||||||
@ -1,30 +1,36 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="container-xxl flex-grow-1 container-p-y">
|
<div class="container-xxl flex-grow-1 container-p-y d-flex">
|
||||||
<div >
|
<!-- 메인 컨텐츠 -->
|
||||||
|
<div class="flex-grow-1">
|
||||||
<!-- 타이틀, 검색 -->
|
<!-- 타이틀, 검색 -->
|
||||||
<SearchBar @update:data="search"/>
|
<SearchBar @update:data="search"/>
|
||||||
|
<div class="d-flex">
|
||||||
<!-- 단어 갯수, 작성하기 -->
|
<!-- 단어 갯수, 작성하기 -->
|
||||||
|
<!-- 왼쪽 사이드바 -->
|
||||||
|
<div class="sidebar position-sticky" style="top: 100px; max-width: 250px; min-width: 250px;">
|
||||||
<WriteButton ref="writeButton" @click="writeStore.toggleItem(999999)" :isToggleEnabled="true"/>
|
<WriteButton ref="writeButton" @click="writeStore.toggleItem(999999)" :isToggleEnabled="true"/>
|
||||||
<!-- ㄱ ㄴ ㄷ ㄹ -->
|
<!-- ㄱ ㄴ ㄷ ㄹ -->
|
||||||
<DictAlphabetFilter @update:data="handleSelectedAlphabetChange" :indexCategory="indexCategory" :selectedAl="selectedAlphabet" />
|
<DictAlphabetFilter @update:data="handleSelectedAlphabetChange" :indexCategory="indexCategory" :selectedAl="selectedAlphabet" />
|
||||||
<!-- 카테고리 -->
|
<!-- 카테고리 -->
|
||||||
<div v-if="cateList.length">
|
<div v-if="cateList.length" class="mt-3">
|
||||||
<CategoryBtn :lists="cateList" @update:data="handleSelectedCategoryChange" :showAll="true" :selectedCategory="selectedCategory" />
|
<CategoryBtn :lists="cateList" @update:data="handleSelectedCategoryChange" :showAll="true" :selectedCategory="selectedCategory" />
|
||||||
</div>
|
</div>
|
||||||
<!-- 작성 -->
|
|
||||||
<div v-if="writeStore.isItemActive(999999)" class="mt-5 card p-5">
|
|
||||||
<DictWrite @close="writeStore.closeAll()" :dataList="cateList" @addWord="addWord"/>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<!-- 용어 리스트 컨텐츠 -->
|
||||||
|
<div class="flex-grow-1">
|
||||||
|
<!-- 작성 -->
|
||||||
|
<div v-if="writeStore.isItemActive(999999)" class="ms-3 card p-5 mb-2">
|
||||||
|
<DictWrite @close="writeStore.closeAll()" :dataList="cateList" @addWord="addWord"/>
|
||||||
|
</div>
|
||||||
<!-- 용어 리스트 -->
|
<!-- 용어 리스트 -->
|
||||||
<div >
|
<div>
|
||||||
<!-- 로딩 중일 때 -->
|
<!-- 로딩 중일 때 -->
|
||||||
<LoadingSpinner v-if="loading"/>
|
<LoadingSpinner v-if="loading"/>
|
||||||
<!-- 에러 메시지 -->
|
<!-- 에러 메시지 -->
|
||||||
<div v-if="error" class="error">{{ error }}</div>
|
<div v-if="error" class="error">{{ error }}</div>
|
||||||
<!-- 단어 목록 -->
|
<!-- 단어 목록 -->
|
||||||
<ul v-if="total > 0" class="px-0 list-unstyled">
|
<ul v-if="total > 0" class="ms-3 list-unstyled">
|
||||||
<DictCard
|
<DictCard
|
||||||
v-for="item in wordList"
|
v-for="item in wordList"
|
||||||
:key="item.WRDDICSEQ"
|
:key="item.WRDDICSEQ"
|
||||||
@ -35,14 +41,15 @@
|
|||||||
/>
|
/>
|
||||||
</ul>
|
</ul>
|
||||||
<!-- 데이터가 없을 때 -->
|
<!-- 데이터가 없을 때 -->
|
||||||
<div v-if="total == 0" class="text-center mt-5">용어를 선택 / 작성해 주세요 </div>
|
<div v-if="total == 0" class="text-center mt-5">용어를 선택 / 작성해 주세요</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
<button v-if="isAnyChecked" class="btn btn-danger admin-del-btn" @click="deleteCheckedItems">
|
</div>
|
||||||
|
</div>
|
||||||
|
<button v-if="isAnyChecked" class="btn btn-danger admin-del-btn" @click="deleteCheckedItems">
|
||||||
<i class="bx bx-trash"></i>
|
<i class="bx bx-trash"></i>
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
@ -255,7 +262,6 @@
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
|
|
||||||
.admin-del-btn {
|
.admin-del-btn {
|
||||||
position: fixed;
|
position: fixed;
|
||||||
right: 1.5rem;
|
right: 1.5rem;
|
||||||
@ -263,11 +269,15 @@
|
|||||||
width: 3rem;
|
width: 3rem;
|
||||||
height: 3rem;
|
height: 3rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
.error {
|
.error {
|
||||||
color: red;
|
color: red;
|
||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
}
|
}
|
||||||
|
.sidebar {
|
||||||
|
position: sticky;
|
||||||
|
top: 5px;
|
||||||
|
height: fit-content;
|
||||||
|
}
|
||||||
|
|
||||||
@media (max-width: 768px) {
|
@media (max-width: 768px) {
|
||||||
.title {
|
.title {
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user