Merge branch 'main' into project-list

This commit is contained in:
yoon 2025-02-20 15:21:56 +09:00
commit b0239bacce
6 changed files with 128 additions and 30 deletions

View File

@ -12,6 +12,7 @@
v-model="inputValue" v-model="inputValue"
:maxLength="maxlength" :maxLength="maxlength"
:placeholder="title" :placeholder="title"
:disabled="disabled"
/> />
<div class="invalid-feedback" :class="isAlert ? 'display-block' : ''"> <div class="invalid-feedback" :class="isAlert ? 'display-block' : ''">
{{ title }} 확인해주세요. {{ title }} 확인해주세요.
@ -60,6 +61,10 @@ const props = defineProps({
default: true, default: true,
required: false, required: false,
}, },
disabled: {
type: Boolean,
default: false,
}
}); });
// Emits // Emits

View File

@ -5,7 +5,7 @@
<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'">
<select class="form-select" :id="name" v-model="selectData"> <select class="form-select" :id="name" v-model="selectData" :disabled="disabled">
<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">
{{ isCommon ? item.label : item }} {{ isCommon ? item.label : item }}
</option> </option>
@ -63,6 +63,10 @@ const props = defineProps({
type: Boolean, type: Boolean,
default: false, default: false,
required: false, required: false,
},
disabled: {
type: Boolean,
default: false
} }
}); });

View File

@ -43,6 +43,9 @@ const selectAlphabet = (alphabet) => {
</script> </script>
<style scoped> <style scoped>
.btn {
min-width: 56px;
}
@media (max-width: 768px) { @media (max-width: 768px) {
.alphabet-list { .alphabet-list {

View File

@ -13,7 +13,15 @@
/> />
<div v-else> <div v-else>
<div class="d-flex align-items-center"> <input
v-if="userStore.user.role == 'ROLE_ADMIN'"
type="checkbox"
class="form-check-input admin-chk"
:name="item.WRDDICSEQ"
id=""
@change="toggleCheck($event)"
>
<div class="d-flex align-ite-center">
<div class="w-100 d-flex align-items-center"> <div class="w-100 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>
@ -34,7 +42,10 @@
</div> </div>
</div> </div>
</div> </div>
<div class="d-flex justify-content-between flex-wrap gap-2 mb-2"> <div
v-if="item.author.createdAt !== item.lastEditor.updatedAt"
class="d-flex justify-content-between flex-wrap gap-2 mb-2"
>
<div class="d-flex flex-wrap align-items-center mb-50"> <div class="d-flex flex-wrap align-items-center mb-50">
<div class="avatar avatar-sm me-2"> <div class="avatar avatar-sm me-2">
<img <img
@ -64,6 +75,11 @@ 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';
//
const userStore = useUserInfoStore();
const toastStore = useToastStore(); const toastStore = useToastStore();
const { appContext } = getCurrentInstance(); const { appContext } = getCurrentInstance();
@ -87,7 +103,7 @@ const localCateList = ref([...props.cateList]);
const selectedCategory = ref(''); const selectedCategory = ref('');
// cateList emit // cateList emit
const emit = defineEmits(['update:cateList','refreshWordList']); const emit = defineEmits(['update:cateList','refreshWordList', 'updateChecked']);
// //
const isWriteVisible = ref(false); const isWriteVisible = ref(false);
@ -171,6 +187,12 @@ const formatDate = (dateString) => new Date(dateString).toLocaleString();
// //
const getProfileImage = (imagePath) => const getProfileImage = (imagePath) =>
imagePath ? `${baseUrl}upload/img/profile/${imagePath}` : '/img/avatars/default-Profile.jpg'; imagePath ? `${baseUrl}upload/img/profile/${imagePath}` : '/img/avatars/default-Profile.jpg';
//
const toggleCheck = (event) => {
emit('updateChecked', event.target.checked, props.item.WRDDICSEQ, event.target.name);
};
</script> </script>
<style scoped> <style scoped>
@ -185,4 +207,12 @@ const getProfileImage = (imagePath) =>
right: 0.7rem; right: 0.7rem;
top: 1.2rem; top: 1.2rem;
} }
.admin-chk {
position: absolute;
left: -0.5rem;
top: -0.5rem;
--bs-form-check-bg: #fff;
}
</style> </style>

View File

@ -10,10 +10,11 @@
@update:data="selectCategory = $event" @update:data="selectCategory = $event"
@change="onChange" @change="onChange"
:value="formValue" :value="formValue"
:disabled="isDisabled"
/> />
</div> </div>
<div class="col-2 btn-margin"> <div class="col-2 btn-margin">
<PlusBtn @click="toggleInput"/> <PlusBtn v-if="userStore.user.role == 'ROLE_ADMIN'" @click="toggleInput"/>
</div> </div>
</div> </div>
@ -37,6 +38,7 @@
:is-alert="wordTitleAlert" :is-alert="wordTitleAlert"
:modelValue="titleValue" :modelValue="titleValue"
@update:modelValue="wordTitle = $event" @update:modelValue="wordTitle = $event"
:disabled="isDisabled"
/> />
</div> </div>
<div> <div>
@ -56,6 +58,13 @@ 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 { 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']);
@ -73,6 +82,11 @@ const addCategoryAlert = ref(false);
// //
const selectCategory = ref(''); const selectCategory = ref('');
//
const computedTitle = computed(() =>
wordTitle.value === '' ? props.titleValue : wordTitle.value
);
// //
const selectedCategory = computed(() => const selectedCategory = computed(() =>
selectCategory.value === '' ? props.formValue : selectCategory.value selectCategory.value === '' ? props.formValue : selectCategory.value
@ -104,15 +118,18 @@ const toggleInput = () => {
showInput.value = !showInput.value; showInput.value = !showInput.value;
}; };
// //
const saveInput = () => { const saveInput = () => {
if(addCategory.value == ''){ if(addCategory.value == ''){
addCategoryAlert.value = true; addCategoryAlert.value = true;
return; return;
}else {
addCategoryAlert.value = false;
} }
// console.log(' !',addCategory.value); // console.log(' !',addCategory.value);
emit('addCategory', addCategory.value); emit('addCategory', addCategory.value);
showInput.value = false; // showInput.value = false;
}; };
const onChange = (newValue) => { const onChange = (newValue) => {
@ -124,7 +141,7 @@ const saveWord = () => {
//validation //validation
// //
if(wordTitle.value == ''){ if(computedTitle.value == '' || computedTitle.length == 0){
wordTitleAlert.value = true; wordTitleAlert.value = true;
return; return;
} }
@ -137,7 +154,7 @@ const saveWord = () => {
const wordData = { const wordData = {
id: props.NumValue || null, id: props.NumValue || null,
title: wordTitle.value, title: computedTitle.value,
category: selectedCategory.value, category: selectedCategory.value,
content: content.value, content: content.value,
}; };

View File

@ -1,6 +1,6 @@
<template> <template>
<div class="container-xxl flex-grow-1 container-p-y"> <div class="container-xxl flex-grow-1 container-p-y">
<!-- {{ userStore.user.role == 'ROLE_ADMIN' ? '관리자' : '일반인'}} -->
<div class="card p-5"> <div class="card p-5">
<!-- 타이틀, 검색 --> <!-- 타이틀, 검색 -->
<div class="row"> <div class="row">
@ -34,32 +34,37 @@
</div> </div>
</div> </div>
<!-- 용어 리스트 --> <!-- 용어 리스트 -->
<div class="mt-10"> <div class="mt-10">
<!-- 로딩 중일 --> <!-- 로딩 중일 -->
<div v-if="loading">로딩 중...</div> <div v-if="loading">로딩 중...</div>
<!-- 에러 메시지 --> <!-- 에러 메시지 -->
<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="px-0 list-unstyled">
<DictCard <DictCard
v-for="item in wordList" v-for="item in wordList"
:key="item.WRDDICSEQ" :key="item.WRDDICSEQ"
:item="item" :item="item"
:cateList="cateList" :cateList="cateList"
@refreshWordList="getwordList" @refreshWordList="getwordList"
/> @updateChecked="updateCheckedItems"
</ul> />
</ul>
<!-- 데이터가 없을 --> <!-- 데이터가 없을 -->
<div v-else-if="!loading && !error" class="card p-5 text-center">용어집의 용어가 없습니다.</div> <div v-else-if="!loading && !error" class="card p-5 text-center">용어집의 용어가 없습니다.</div>
</div> </div>
</div> </div>
<button v-if="isAnyChecked" class="btn btn-danger admin-del-btn">
<i class="bx bx-trash"></i>
</button>
</template> </template>
<script setup> <script setup>
@ -73,6 +78,10 @@
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';
//
const userStore = useUserInfoStore();
const { appContext } = getCurrentInstance(); const { appContext } = getCurrentInstance();
const $common = appContext.config.globalProperties.$common; const $common = appContext.config.globalProperties.$common;
@ -97,6 +106,11 @@
const selectedCategory = ref(''); const selectedCategory = ref('');
const selectCategory = ref(''); const selectCategory = ref('');
//
const checkedItems = ref([]);
// name
const checkedNames = ref([]);
// //
const selectedAlphabet = ref(''); const selectedAlphabet = ref('');
@ -163,7 +177,6 @@
const addCategory = (data) =>{ const addCategory = (data) =>{
const lastCategory = cateList.value[cateList.value.length - 1]; const lastCategory = cateList.value[cateList.value.length - 1];
const newValue = lastCategory ? lastCategory.value + 1 : 600101; const newValue = lastCategory ? lastCategory.value + 1 : 600101;
axios.post('worddict/insertCategory',{ axios.post('worddict/insertCategory',{
CMNCODNAM: data CMNCODNAM: data
}).then(res => { }).then(res => {
@ -172,6 +185,8 @@
const newCategory = { label: data, value: newValue }; const newCategory = { label: data, value: newValue };
cateList.value = [newCategory, ...cateList.value]; cateList.value = [newCategory, ...cateList.value];
selectedCategory.value = newCategory.value; selectedCategory.value = newCategory.value;
} else if(res.data.message == '이미 존재하는 카테고리명입니다.') {
toastStore.onToast(res.data.message, 'e');
} }
}) })
} }
@ -190,10 +205,34 @@
}) })
}; };
//
const updateCheckedItems = (checked, id, name) => {
if (checked) {
checkedItems.value.push(id);
checkedNames.value.push(name);
} else {
checkedItems.value = checkedItems.value.filter(item => item !== id);
checkedNames.value = checkedNames.value.filter(item => item !== name);
}
// name
console.log("현재 체크된 name 값:", checkedNames.value);
};
const isAnyChecked = computed(() => checkedItems.value.length > 0);
</script> </script>
<style scoped> <style scoped>
.admin-del-btn {
position: fixed;
right: 1.5rem;
bottom: 1.5rem;
width: 3rem;
height: 3rem;
}
.error { .error {
color: red; color: red;
font-weight: bold; font-weight: bold;