용어집 에디터 이미지 매핑 및 삭제 로직 추가

This commit is contained in:
nevermoregb 2025-03-24 15:45:52 +09:00
parent 52d520f5e4
commit 51793fcde3
3 changed files with 482 additions and 420 deletions

View File

@ -2,7 +2,7 @@
<li class="card p-5 mb-2"> <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()"
:dataList="cateList" :dataList="cateList"
@addWord="editWord" @addWord="editWord"
:NumValue="item.WRDDICSEQ" :NumValue="item.WRDDICSEQ"
@ -12,53 +12,48 @@
:isDisabled="true" :isDisabled="true"
:showEditBtn="true" :showEditBtn="true"
@toggleEdit="toggleEdit" @toggleEdit="toggleEdit"
/> @update:deleteImgIndexList="handleDeleteEditorImg"
@update:uploadedImgList="handleUpdateEditorImg"
/>
<div v-else> <div v-else>
<div class="d-flex align-items-center justify-content-between"> <div class="d-flex align-items-center justify-content-between">
<div class="d-flex align-items-center"> <div class="d-flex align-items-center">
<span class="btn btn-primary pe-none">{{ item.category }}</span> <span class="btn btn-primary pe-none">{{ item.category }}</span>
<strong class="mx-2 w-75">{{ item.WRDDICTTL }}</strong> <strong class="mx-2 w-75">{{ item.WRDDICTTL }}</strong>
</div> </div>
<EditBtn <EditBtn @click="toggleEdit" :isToggleEnabled="true" :isActive="writeStore.isItemActive(item.WRDDICSEQ)" />
@click="toggleEdit"
:isToggleEnabled="true"
:isActive="writeStore.isItemActive(item.WRDDICSEQ)"
/>
</div> </div>
<p class="mt-5 dict-content-wrap" v-html="$common.contentToHtml(item.WRDDICCON)"></p> <p class="mt-5 dict-content-wrap" v-html="$common.contentToHtml(item.WRDDICCON)"></p>
<div class="d-flex align-items-start"> <div class="d-flex align-items-start">
<!-- 최초 작성자 --> <!-- 최초 작성자 -->
<div class="d-flex flex-wrap align-items-center me-4"> <div class="d-flex flex-wrap align-items-center me-4">
<div class="avatar avatar-sm me-2"> <div class="avatar avatar-sm me-2">
<img <img
class="rounded-circle user-avatar" class="rounded-circle user-avatar"
:src="getProfileImage(item.author.profileImage)" :src="getProfileImage(item.author.profileImage)"
alt="최초 작성자" alt="최초 작성자"
:style="{ borderColor: item.author.color }" :style="{ borderColor: item.author.color }"
@error="setDefaultImage" @error="setDefaultImage"
/> />
</div> </div>
<div> <div>
<p class="mb-0 small fw-medium">{{ $common.dateFormatter(item.author.createdAt) }}</p> <p class="mb-0 small fw-medium">{{ $common.dateFormatter(item.author.createdAt) }}</p>
</div> </div>
</div> </div>
<!-- 최근 작성자 (조건부) --> <!-- 최근 작성자 (조건부) -->
<div <div v-if="item.author.createdAt !== item.lastEditor.updatedAt" class="d-flex flex-wrap align-items-center">
v-if="item.author.createdAt !== item.lastEditor.updatedAt"
class="d-flex flex-wrap align-items-center"
>
<div class="avatar avatar-sm me-2"> <div class="avatar avatar-sm me-2">
<img <img
class="rounded-circle user-avatar" class="rounded-circle user-avatar"
:src="getProfileImage(item.lastEditor.profileImage)" :src="getProfileImage(item.lastEditor.profileImage)"
alt="최근 작성자" alt="최근 작성자"
:style="{ borderColor: item.lastEditor.color }" :style="{ borderColor: item.lastEditor.color }"
@error="setDefaultImage" @error="setDefaultImage"
/> />
</div> </div>
<div> <div>
<p class="mb-0 small fw-medium">{{ $common.dateFormatter(item.lastEditor.updatedAt) }}</p> <p class="mb-0 small fw-medium">{{ $common.dateFormatter(item.lastEditor.updatedAt) }}</p>
</div> </div>
</div> </div>
</div> </div>
@ -67,115 +62,137 @@
</template> </template>
<script setup> <script setup>
import axios from "@api"; import axios from '@api';
import { useToastStore } from '@s/toastStore'; import { useToastStore } from '@s/toastStore';
import { getCurrentInstance, nextTick, ref } from 'vue'; import { getCurrentInstance, nextTick, 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';
import { useUserInfoStore } from '@s/useUserInfoStore'; 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 writeButton = ref(null);
const editorDeleteImgList = ref([]);
const editorUploadedImgList = ref([]);
// //
const userStore = useUserInfoStore(); const userStore = useUserInfoStore();
const toastStore = useToastStore(); const toastStore = useToastStore();
const { appContext } = getCurrentInstance(); const { appContext } = getCurrentInstance();
const $common = appContext.config.globalProperties.$common; const $common = appContext.config.globalProperties.$common;
// Props // Props
const props = defineProps({ const props = defineProps({
item: { item: {
type: Object, type: Object,
required: true required: true,
}, },
cateList: { cateList: {
type: Array, type: Array,
required: false, required: false,
} },
});
// cateList emit
const emit = defineEmits(['update:cateList','refreshWordList', 'updateChecked']);
//
const editWord = (data) => {
if (!data.id) {
console.error('❌ 수정할 데이터의 ID가 없습니다.');
toastStore.onToast('수정할 용어의 ID가 필요합니다.', 'e');
return;
}
axios.patch('worddict/updateWord', {
WRDDICSEQ: data.id,
WRDDICCAT: data.category,
WRDDICTTL: data.title,
WRDDICCON: $common.deltaAsJson(data.content),
})
.then((res) => {
if (res.data.data === 1) {
toastStore.onToast('용어가 수정되었습니다.', 's');
writeStore.closeAll();
if (writeButton.value) {
writeButton.value.resetButton();
}
emit('refreshWordList',data.category);
} else {
console.warn('⚠️ 서버 응답이 예상과 다릅니다:', res.data);
toastStore.onToast('용어 수정이 정상적으로 처리되지 않았습니다.', 'e');
}
})
.catch((err) => {
console.error('❌ 용어 수정 중 오류 발생:', err.response?.data || err.message);
toastStore.onToast(`용어 수정 실패: ${err.response?.data?.message || '알 수 없는 오류'}`, 'e');
}); });
};
const baseUrl = $api.defaults.baseURL.replace(/api\/$/, ''); // cateList emit
const emit = defineEmits(['update:cateList', 'refreshWordList', 'updateChecked', 'update:deleteImgIndexList']);
// //
const defaultProfile = "/img/icons/icon.png"; const editWord = data => {
if (!data.id) {
console.error('❌ 수정할 데이터의 ID가 없습니다.');
toastStore.onToast('수정할 용어의 ID가 필요합니다.', 'e');
return;
}
const getProfileImage = (profilePath) => { const param = {
return profilePath && profilePath.trim() ? `${baseUrl}upload/img/profile/${profilePath}` : defaultProfile; WRDDICSEQ: data.id,
}; WRDDICCAT: data.category,
WRDDICTTL: data.title,
WRDDICCON: $common.deltaAsJson(data.content),
};
const setDefaultImage = (event) => { //
event.target.src = defaultProfile; if (editorUploadedImgList.value && editorUploadedImgList.value.length > 0) {
}; param.editorUploadedImgList = [...editorUploadedImgList.value];
}
const toggleEdit = async () => { //
writeStore.toggleItem(props.item.WRDDICSEQ); if (editorDeleteImgList.value && editorDeleteImgList.value.length > 0) {
}; param.editorDeleteImgList = [...editorDeleteImgList.value];
}
axios
.patch('worddict/updateWord', param)
.then(res => {
if (res.data.data === 1) {
toastStore.onToast('용어가 수정되었습니다.', 's');
writeStore.closeAll();
if (writeButton.value) {
writeButton.value.resetButton();
}
emit('refreshWordList', data.category);
} else {
console.warn('⚠️ 서버 응답이 예상과 다릅니다:', res.data);
toastStore.onToast('용어 수정이 정상적으로 처리되지 않았습니다.', 'e');
}
})
.catch(err => {
console.error('❌ 용어 수정 중 오류 발생:', err.response?.data || err.message);
toastStore.onToast(`용어 수정 실패: ${err.response?.data?.message || '알 수 없는 오류'}`, 'e');
});
};
// ,
const handleDeleteEditorImg = item => {
editorDeleteImgList.value = item;
};
//
const handleUpdateEditorImg = item => {
editorUploadedImgList.value = item;
};
const baseUrl = $api.defaults.baseURL.replace(/api\/$/, '');
//
const defaultProfile = '/img/icons/icon.png';
const getProfileImage = profilePath => {
return profilePath && profilePath.trim() ? `${baseUrl}upload/img/profile/${profilePath}` : defaultProfile;
};
const setDefaultImage = event => {
event.target.src = defaultProfile;
};
const toggleEdit = async () => {
writeStore.toggleItem(props.item.WRDDICSEQ);
};
</script> </script>
<style scoped> <style scoped>
.avatar { .avatar {
cursor: default; cursor: default;
} }
.user-avatar { .user-avatar {
border: 3px solid; border: 3px solid;
padding: 0.1px; padding: 0.1px;
} }
.edit-btn { .edit-btn {
position: absolute; position: absolute;
right: 0.7rem; right: 0.7rem;
top: 1.2rem; top: 1.2rem;
} }
.admin-chk { .admin-chk {
position: absolute; position: absolute;
left: -0.5rem; left: -0.5rem;
top: -0.5rem; top: -0.5rem;
--bs-form-check-bg: #fff; --bs-form-check-bg: #fff;
} }
</style> </style>

View File

@ -1,5 +1,5 @@
<template> <template>
<div v-if="dataList.length > 0" > <div v-if="dataList.length > 0">
<FormSelect <FormSelect
name="cate" name="cate"
title="카테고리" title="카테고리"
@ -11,17 +11,17 @@
:is-essential="false" :is-essential="false"
:is-btn="true" :is-btn="true"
> >
<template v-slot:append> <template v-slot:append>
<div> <div>
<PlusBtn v-if="!showInput && !isDisabled" @click="toggleInput"/> <PlusBtn v-if="!showInput && !isDisabled" @click="toggleInput" />
<EditBtn <EditBtn
v-if="showEditBtn" v-if="showEditBtn"
@click="$emit('toggleEdit')" @click="$emit('toggleEdit')"
:isToggleEnabled="true" :isToggleEnabled="true"
:isActive="writeStore.isItemActive(NumValue)" :isActive="writeStore.isItemActive(NumValue)"
/> />
</div> </div>
</template> </template>
</FormSelect> </FormSelect>
</div> </div>
<div v-if="dataList.length === 0 || showInput"> <div v-if="dataList.length === 0 || showInput">
@ -29,195 +29,201 @@
class="justify-content-end" class="justify-content-end"
ref="categoryInputRef" ref="categoryInputRef"
title="새 카테고리" title="새 카테고리"
:isLabel="dataList.length === 0 ?true : false" :isLabel="dataList.length === 0 ? true : false"
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>
<FormInput <FormInput
title="용어" title="용어"
type="text" type="text"
name="word" name="word"
:is-essential="true" :is-essential="true"
:is-alert="wordTitleAlert" :is-alert="wordTitleAlert"
:modelValue="titleValue" :modelValue="titleValue"
@update:modelValue="wordTitle = $event" @update:modelValue="wordTitle = $event"
:disabled="isDisabled" :disabled="isDisabled"
@keyup="ValidHandler('title')" @keyup="ValidHandler('title')"
/>
<div>
<QEditor
class=""
@keyup="ValidHandler('content')"
@update:data="handleContentUpdate"
@update:deleteImgIndexList="$emit('update:deleteImgIndexList', $event)"
@update:uploadedImgList="$emit('update:uploadedImgList', $event)"
@update:imageUrls="imageUrls = $event"
:is-alert="wordContentAlert"
:initialData="contentValue"
/> />
<div> <div class="text-end mt-5">
<QEditor class="" @keyup="ValidHandler('content')" @update:data="handleContentUpdate" @update:imageUrls="imageUrls = $event" :is-alert="wordContentAlert" :initialData="contentValue"/> <button class="btn btn-primary" @click="saveWord">
<div class="text-end mt-5"> <i class="bx bx-check"></i>
<button class="btn btn-primary" @click="saveWord"> </button>
<i class="bx bx-check"></i>
</button>
</div>
</div> </div>
</div>
</template> </template>
<script setup> <script setup>
import { defineProps, computed, ref, defineEmits } from 'vue'; import { defineProps, computed, ref, defineEmits } from 'vue';
import QEditor from '@/components/editor/QEditor.vue'; 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 EditBtn from '../button/EditBtn.vue'; import EditBtn from '../button/EditBtn.vue';
import { useWriteVisibleStore } from '@s/writeVisible'; import { useWriteVisibleStore } from '@s/writeVisible';
const writeStore = useWriteVisibleStore(); const writeStore = useWriteVisibleStore();
const emit = defineEmits(['close','addCategory','addWord', 'toggleEdit']); const emit = defineEmits(['close', 'addCategory', 'addWord', 'toggleEdit', 'update:deleteImgIndexList', 'update:uploadedImgList']);
// //
const wordTitle = ref(''); const wordTitle = ref('');
const addCategory = ref(''); const addCategory = ref('');
const content = ref(''); const content = ref('');
const imageUrls = ref([]); const imageUrls = ref([]);
// Vaildation // Vaildation
const wordTitleAlert = ref(false); const wordTitleAlert = ref(false);
const wordContentAlert = ref(false); const wordContentAlert = ref(false);
const addCategoryAlert = ref(false); const addCategoryAlert = ref(false);
// //
const selectCategory = ref(''); const selectCategory = ref('');
// //
const computedTitle = computed(() => const computedTitle = computed(() => (wordTitle.value === '' ? props.titleValue : wordTitle.value));
wordTitle.value === '' ? props.titleValue : wordTitle.value
);
// //
const selectedCategory = computed(() => const selectedCategory = computed(() => (selectCategory.value === '' ? props.formValue : selectCategory.value));
selectCategory.value === '' ? props.formValue : selectCategory.value
); // ref
const categoryInputRef = ref(null);
// ref const props = defineProps({
const categoryInputRef = ref(null); dataList: {
type: Array,
default: () => [],
},
NumValue: {
type: Number,
},
formValue: {
type: [String, Number],
},
titleValue: {
type: String,
},
contentValue: {
type: String,
},
isDisabled: {
type: Boolean,
default: false,
},
showEditBtn: {
type: Boolean,
default: false,
},
});
const props = defineProps({ //
dataList: { const showInput = ref(false);
type: Array,
default: () => []
},
NumValue : {
type: Number
},
formValue : {
type:[String, Number]
},
titleValue : {
type:String,
},contentValue : {
type:String,
},
isDisabled: {
type: Boolean,
default: false
},
showEditBtn: {
type: Boolean,
default: false
}
});
// //
const showInput = ref(false); const toggleInput = () => {
showInput.value = !showInput.value;
//
const toggleInput = () => {
showInput.value = !showInput.value;
};
const onChange = (newValue) => {
selectCategory.value = newValue.target.value;
};
const ValidHandler = (field) => {
if(field == 'title'){
wordTitleAlert.value = false;
}
if(field == 'content'){
wordContentAlert.value = false;
}
}
const handleContentUpdate = (newContent) => {
content.value = newContent;
ValidHandler("content"); //
};
//
const saveWord = () => {
let valid = true;
//validation
let computedTitleTrim;
if(computedTitle.value != undefined){
computedTitleTrim = computedTitle.value.trim()
}
//
if(computedTitleTrim == undefined || computedTitleTrim == ''){
wordTitleAlert.value = true;
valid = false;
} else {
wordTitleAlert.value = false;
}
//
let inserts = [];
if (inserts.length === 0 && content.value?.ops?.length > 0) {
inserts = content.value.ops.map(op =>
typeof op.insert === 'string' ? op.insert.trim() : op.insert
);
}
//
if(content.value == '' || inserts.join('') === ''){
wordContentAlert.value = true;
valid = false;
}else{
wordContentAlert.value = false;
}
const wordData = {
id: props.NumValue || null,
title: computedTitle.value.trim(),
category: selectedCategory.value,
content: content.value,
}; };
if(valid){
emit('addWord', wordData, addCategory.value.trim() === ''
? (isNaN(selectedCategory.value) ? selectedCategory.value : Number(selectedCategory.value))
: addCategory.value);
}
}
// focusout const onChange = newValue => {
const handleCategoryFocusout = (value) => { selectCategory.value = newValue.target.value;
if (!value || value.trim() === '') { };
return;
}
const valueTrim = value.trim();
const existingCategory = props.dataList.find(item => item.label === valueTrim);
if (existingCategory) { const ValidHandler = field => {
addCategoryAlert.value = true; if (field == 'title') {
wordTitleAlert.value = false;
}
if (field == 'content') {
wordContentAlert.value = false;
}
};
const handleContentUpdate = newContent => {
content.value = newContent;
ValidHandler('content'); //
};
// focus //
setTimeout(() => { const saveWord = () => {
const inputElement = categoryInputRef.value?.$el?.querySelector('input'); let valid = true;
if (inputElement) { //validation
inputElement.focus(); let computedTitleTrim;
}
}, 0);
} else {
addCategoryAlert.value = false;
}
};
if (computedTitle.value != undefined) {
computedTitleTrim = computedTitle.value.trim();
}
//
if (computedTitleTrim == undefined || computedTitleTrim == '') {
wordTitleAlert.value = true;
valid = false;
} else {
wordTitleAlert.value = false;
}
//
let inserts = [];
if (inserts.length === 0 && content.value?.ops?.length > 0) {
inserts = content.value.ops.map(op => (typeof op.insert === 'string' ? op.insert.trim() : op.insert));
}
//
if (content.value == '' || inserts.join('') === '') {
wordContentAlert.value = true;
valid = false;
} else {
wordContentAlert.value = false;
}
const wordData = {
id: props.NumValue || null,
title: computedTitle.value.trim(),
category: selectedCategory.value,
content: content.value,
};
if (valid) {
emit(
'addWord',
wordData,
addCategory.value.trim() === ''
? isNaN(selectedCategory.value)
? selectedCategory.value
: Number(selectedCategory.value)
: addCategory.value,
);
}
};
// focusout
const handleCategoryFocusout = value => {
if (!value || value.trim() === '') {
return;
}
const valueTrim = value.trim();
const existingCategory = props.dataList.find(item => item.label === valueTrim);
if (existingCategory) {
addCategoryAlert.value = true;
// focus
setTimeout(() => {
const inputElement = categoryInputRef.value?.$el?.querySelector('input');
if (inputElement) {
inputElement.focus();
}
}, 0);
} else {
addCategoryAlert.value = false;
}
};
</script> </script>

View File

@ -1,55 +1,73 @@
<template> <template>
<div class="container-xxl flex-grow-1 container-p-y d-flex"> <div class="container-xxl flex-grow-1 container-p-y d-flex">
<!-- 메인 컨텐츠 --> <!-- 메인 컨텐츠 -->
<div class="flex-grow-1"> <div class="flex-grow-1">
<!-- 타이틀, 검색 --> <!-- 타이틀, 검색 -->
<SearchBar @update:data="search"/> <SearchBar @update:data="search" />
<div class="d-flex"> <div class="d-flex">
<!-- 단어 갯수, 작성하기 --> <!-- 단어 갯수, 작성하기 -->
<!-- 왼쪽 사이드바 --> <!-- 왼쪽 사이드바 -->
<div class="sidebar position-sticky" style="top: 100px; max-width: 250px; min-width: 250px;"> <div class="sidebar position-sticky" style="top: 100px; max-width: 250px; min-width: 250px">
<WriteButton ref="writeButton" @click="writeStore.toggleItem(999999)" :isToggleEnabled="true" <WriteButton
:isActive="writeStore.activeItemId === 999999"/> ref="writeButton"
<!-- --> @click="writeStore.toggleItem(999999)"
<DictAlphabetFilter @update:data="handleSelectedAlphabetChange" :indexCategory="indexCategory" :selectedAl="selectedAlphabet" /> :isToggleEnabled="true"
<!-- 카테고리 --> :isActive="writeStore.activeItemId === 999999"
<div v-if="cateList.length" class="mt-3"> />
<CategoryBtn :lists="cateList" @update:data="handleSelectedCategoryChange" :showAll="true" :selectedCategory="selectedCategory" /> <!-- -->
</div> <DictAlphabetFilter
</div> @update:data="handleSelectedAlphabetChange"
:indexCategory="indexCategory"
<!-- 용어 리스트 컨텐츠 --> :selectedAl="selectedAlphabet"
<div class="flex-grow-1"> />
<!-- 작성 --> <!-- 카테고리 -->
<div v-if="writeStore.isItemActive(999999)" class="ms-3 card p-5 mb-2"> <div v-if="cateList.length" class="mt-3">
<DictWrite @close="writeStore.closeAll()" :dataList="cateList" @addWord="addWord"/> <CategoryBtn
</div> :lists="cateList"
<!-- 용어 리스트 --> @update:data="handleSelectedCategoryChange"
<div> :showAll="true"
<!-- 에러 메시지 --> :selectedCategory="selectedCategory"
<div v-if="error" class="fw-bold text-danger">{{ error }}</div>
<!-- 단어 목록 -->
<ul v-if="total > 0" class="ms-3 list-unstyled">
<DictCard
v-for="item in wordList"
:key="item.WRDDICSEQ"
:item="item"
v-model:cateList="cateList"
@refreshWordList="refreshWordList"
@updateChecked="updateCheckedItems"
/> />
</ul> </div>
<!-- 데이터가 없을 --> </div>
<div v-if="total == 0" class="text-center mt-5">용어를 선택 / 작성해 주세요</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"
@update:deleteImgIndexList="handleDeleteEditorImg"
@update:uploadedImgList="handleUpdateEditorImg"
/>
</div>
<!-- 용어 리스트 -->
<div>
<!-- 에러 메시지 -->
<div v-if="error" class="fw-bold text-danger">{{ error }}</div>
<!-- 단어 목록 -->
<ul v-if="total > 0" class="ms-3 list-unstyled">
<DictCard
v-for="item in wordList"
:key="item.WRDDICSEQ"
:item="item"
v-model:cateList="cateList"
@refreshWordList="refreshWordList"
@updateChecked="updateCheckedItems"
/>
</ul>
<!-- 데이터가 없을 -->
<div v-if="total == 0" class="text-center mt-5">용어를 선택 / 작성해 주세요</div>
</div>
</div> </div>
</div> </div>
</div> </div>
</div> </div>
</div> <button v-if="isAnyChecked" class="btn btn-danger admin-del-btn" @click="deleteCheckedItems">
<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>
<script setup> <script setup>
@ -64,7 +82,7 @@
import commonApi from '@/common/commonApi'; import commonApi from '@/common/commonApi';
import { useToastStore } from '@s/toastStore'; import { useToastStore } from '@s/toastStore';
import { useWriteVisibleStore } from '@s/writeVisible'; import { useWriteVisibleStore } from '@s/writeVisible';
import LoadingSpinner from "@v/LoadingPage.vue"; import LoadingSpinner from '@v/LoadingPage.vue';
// //
const writeStore = useWriteVisibleStore(); const writeStore = useWriteVisibleStore();
@ -86,7 +104,7 @@
// //
const { cateList } = commonApi({ const { cateList } = commonApi({
loadCateList: true loadCateList: true,
}); });
const selectedCategory = ref(''); const selectedCategory = ref('');
@ -96,7 +114,6 @@
// name // name
const checkedNames = ref([]); const checkedNames = ref([]);
// //
const selectedAlphabet = ref(''); const selectedAlphabet = ref('');
@ -106,31 +123,45 @@
// //
const indexCategory = ref([]); const indexCategory = ref([]);
const editorDeleteImgList = ref([]);
const editorUploadedImgList = ref([]);
// //
onMounted(() => { onMounted(() => {
getIndex(); getIndex();
writeStore.closeAll(); writeStore.closeAll();
}); });
const refreshWordList = (category) => { // ,
const handleDeleteEditorImg = item => {
editorDeleteImgList.value = item;
};
//
const handleUpdateEditorImg = item => {
editorUploadedImgList.value = item;
};
const refreshWordList = category => {
selectedCategory.value = category; selectedCategory.value = category;
getwordList(searchText.value, selectedAlphabet.value, selectedCategory.value); getwordList(searchText.value, selectedAlphabet.value, selectedCategory.value);
}; };
// //
const getwordList = (searchKeyword='', indexKeyword='', category='') => { const getwordList = (searchKeyword = '', indexKeyword = '', category = '') => {
axios.get('worddict/getWordList',{ axios
// .get('worddict/getWordList', {
params: { //
searchKeyword : searchKeyword, params: {
indexKeyword :indexKeyword, searchKeyword: searchKeyword,
category : category indexKeyword: indexKeyword,
} category: category,
},
}) })
.then(res => { .then(res => {
// //
wordList.value = res.data.data.data; wordList.value = res.data.data.data;
// //
total.value = res.data.data.total; total.value = res.data.data.total;
}) })
.catch(err => { .catch(err => {
@ -140,21 +171,21 @@
}; };
// //
const getIndex = () => { const getIndex = () => {
axios.get('worddict/getIndexCategory').then(res=>{ axios.get('worddict/getIndexCategory').then(res => {
if(res.data.status ="OK"){ if ((res.data.status = 'OK')) {
indexCategory.value = res.data.data; indexCategory.value = res.data.data;
} }
}) });
} };
// //
const search = (e) => { const search = e => {
searchText.value = e.trim(); searchText.value = e.trim();
getwordList(searchText.value, selectedAlphabet.value, selectedCategory.value); getwordList(searchText.value, selectedAlphabet.value, selectedCategory.value);
}; };
// //
const handleSelectedAlphabetChange = (newAlphabet) => { const handleSelectedAlphabetChange = newAlphabet => {
selectedAlphabet.value = newAlphabet; selectedAlphabet.value = newAlphabet;
if (newAlphabet !== null) { if (newAlphabet !== null) {
getwordList(searchText.value, selectedAlphabet.value, selectedCategory.value); getwordList(searchText.value, selectedAlphabet.value, selectedCategory.value);
@ -165,28 +196,28 @@
}; };
// //
const handleSelectedCategoryChange = (category) => { const handleSelectedCategoryChange = category => {
selectedCategory.value = category; selectedCategory.value = category;
if (category !== null ) { if (category !== null) {
getwordList(searchText.value, selectedAlphabet.value, selectedCategory.value); getwordList(searchText.value, selectedAlphabet.value, selectedCategory.value);
if(category == 'all'){ if (category == 'all') {
getwordList(searchText.value, selectedAlphabet.value, ''); getwordList(searchText.value, selectedAlphabet.value, '');
} }
} else { } else {
wordList.value = []; wordList.value = [];
total.value = 0; total.value = 0;
} }
} };
// //
const addWord = (wordData, data) => { const addWord = (wordData, data) => {
let category = null; let category = null;
let newCodName = ''; let newCodName = '';
// //
if(typeof(data) == 'number'){ if (typeof data == 'number') {
category = data; category = data;
newCodName = ''; newCodName = '';
}else{ } else {
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;
newCodName = data.trim(); newCodName = data.trim();
@ -194,32 +225,42 @@
sendWordRequest(category, wordData, newCodName); sendWordRequest(category, wordData, newCodName);
}; };
const sendWordRequest = (category, wordData, data) => { const sendWordRequest = (category, wordData, data) => {
console.log(category,'category') console.log(category, 'category');
const payload = { const payload = {
WRDDICCAT: category, WRDDICCAT: category,
WRDDICTTL: wordData.title, WRDDICTTL: wordData.title,
WRDDICCON: $common.deltaAsJson(wordData.content), WRDDICCON: $common.deltaAsJson(wordData.content),
}; };
payload.CMNCODNAM = data; payload.CMNCODNAM = data;
axios.post('worddict/insertWord', payload).then(res => {
if (res.data.status === 'OK') {
toastStore.onToast('용어가 등록 되었습니다.', 's');
writeStore.closeAll();
if (writeButton.value) {
writeButton.value.resetButton();
}
selectedCategory.value = category;
getwordList(searchText.value, selectedAlphabet.value, selectedCategory.value);
getIndex();
if(res.data.data == '2'){
const newCategory = { label: data, value: category };
cateList.value = [...cateList.value,newCategory];
}
selectedAlphabet.value = '';
}
});
};
//
if (editorUploadedImgList.value && editorUploadedImgList.value.length > 0) {
payload.editorUploadedImgList = [...editorUploadedImgList.value];
}
//
if (editorDeleteImgList.value && editorDeleteImgList.value.length > 0) {
payload.editorDeleteImgList = [...editorDeleteImgList.value];
}
axios.post('worddict/insertWord', payload).then(res => {
if (res.data.status === 'OK') {
toastStore.onToast('용어가 등록 되었습니다.', 's');
writeStore.closeAll();
if (writeButton.value) {
writeButton.value.resetButton();
}
selectedCategory.value = category;
getwordList(searchText.value, selectedAlphabet.value, selectedCategory.value);
getIndex();
if (res.data.data == '2') {
const newCategory = { label: data, value: category };
cateList.value = [...cateList.value, newCategory];
}
selectedAlphabet.value = '';
}
});
};
// //
const updateCheckedItems = (checked, id, name) => { const updateCheckedItems = (checked, id, name) => {
@ -236,47 +277,45 @@
// //
const deleteCheckedItems = () => { const deleteCheckedItems = () => {
axios
.patch('worddict/deleteword', {
idList: Object.values(checkedNames.value),
})
.then(res => {
if (res.data.status == 'OK') {
toastStore.onToast('용어 삭제가 완료되었습니다.', 's');
writeStore.closeAll();
getwordList();
axios.patch('worddict/deleteword', { //
idList: Object.values(checkedNames.value) checkedItems.value = [];
}) checkedNames.value = [];
.then(res => { }
if (res.data.status == 'OK') { })
toastStore.onToast('용어 삭제가 완료되었습니다.', 's'); .catch(error => {
writeStore.closeAll(); toastStore.onToast('오류가 발생했습니다. 다시 시도해주세요.', 'e');
getwordList(); });
//
checkedItems.value = [];
checkedNames.value = [];
}
})
.catch(error => {
toastStore.onToast('오류가 발생했습니다. 다시 시도해주세요.', 'e');
});
}; };
</script> </script>
<style scoped> <style scoped>
.admin-del-btn { .admin-del-btn {
position: fixed; position: fixed;
right: 1.5rem; right: 1.5rem;
bottom: 1.5rem; bottom: 1.5rem;
width: 3rem; width: 3rem;
height: 3rem; height: 3rem;
} }
.sidebar { .sidebar {
position: sticky; position: sticky;
top: 5px; top: 5px;
height: fit-content; height: fit-content;
} }
.DictCard { .DictCard {
display: grid; display: grid;
grid-template-columns: repeat(auto-fill, minmax(300px, 1fr)); grid-template-columns: repeat(auto-fill, minmax(300px, 1fr));
gap: 1rem; gap: 1rem;
} }
</style> </style>