사원리스트트
All checks were successful
LocalNet_front/pipeline/head This commit looks good

This commit is contained in:
dyhj625 2025-04-11 15:31:44 +09:00
parent 3a0b09624b
commit cb5e274ac1
3 changed files with 331 additions and 5 deletions

View File

@ -80,12 +80,12 @@
<div class="text-truncate">Authorization</div> <div class="text-truncate">Authorization</div>
</RouterLink> </RouterLink>
</li> </li>
<!-- <li class="menu-item" :class="$route.path.includes('/sample') ? 'active' : ''"> <li class="menu-item" :class="$route.path.includes('/people') ? 'active' : ''">
<RouterLink class="menu-link" to="/sample"> <i class="bi "></i> <RouterLink class="menu-link" to="/people"> <i class="bi "></i>
<i class="menu-icon tf-icons bx bx-calendar"></i> <i class="menu-icon icon-base bi bi-people-fill"></i>
<div class="text-truncate">Sample</div> <div class="text-truncate">people</div>
</RouterLink> </RouterLink>
</li> --> </li>
</ul> </ul>
</aside> </aside>
<!-- / Menu --> <!-- / Menu -->
@ -94,6 +94,7 @@
<script setup> <script setup>
import { computed } from "vue"; import { computed } from "vue";
import { useUserInfoStore } from '@s/useUserInfoStore'; import { useUserInfoStore } from '@s/useUserInfoStore';
import "bootstrap-icons/font/bootstrap-icons.css";
const userStore = useUserInfoStore(); const userStore = useUserInfoStore();
const allowedUserId = 1; // ID (!!) const allowedUserId = 1; // ID (!!)

View File

@ -109,6 +109,12 @@ const routes = [
component: () => import('@v/admin/TheAuthorization.vue'), component: () => import('@v/admin/TheAuthorization.vue'),
meta: { requiresAuth: true }, meta: { requiresAuth: true },
}, },
{
path: '/people',
name: 'people',
component: () => import('@v/people/PeopleList.vue'),
meta: { requiresAuth: true },
},
{ {
path: '/error/400', path: '/error/400',
name: 'Error400', name: 'Error400',

View File

@ -0,0 +1,319 @@
<template>
<div class="container-xxl flex-grow-1 container-p-y">
<div class="card">
<!-- 사원 목록이 없을 경우 표시 -->
<div v-if="allUserList.length === 0" class="text-center my-4">
<p class="text-muted">등록된 사원이 없습니다.</p>
</div>
<!-- 사원 카드 리스트 영역 -->
<div class="card-body">
<div class="card-list">
<div
v-for="(person, index) in allUserList"
:key="index"
class="person-card"
@click="openModal(person)"
>
<div>
<img
style="cursor: auto;"
class="rounded-circle user-avatar"
:src="getProfileImage(person.MEMBERPRF)"
:style="{ borderColor: person.usercolor }"
@error="setDefaultImage"
/>
</div>
<div class="card-body">
<h3 class="person-name">{{ person.MEMBERNAM }}</h3>
<p class="person-email">{{ person.MEMBERIDS }}@local-host.co.kr</p>
<p class="person-phone">{{ person.MEMBERTEL }}</p>
<small>
{{ person.MEMBERARR }} {{ person.MEMBERDTL }}
</small>
</div>
</div>
</div>
</div>
<!-- 상세보기 Modal -->
<div v-if="showModal" class="modal-overlay" @click.self="closeModal">
<div class="modal-content">
<button class="close-btn" @click="closeModal">×</button>
<div class="modal-body">
<img
style="cursor: auto;"
class="rounded-circle modal-img object-fit-cover"
:src="getProfileImage(selectedPerson.MEMBERPRF)"
:style="{ borderColor: selectedPerson.usercolor }"
@error="setDefaultImage"
/>
<h4>{{ selectedPerson.MEMBERNAM }}</h4>
<p>{{ selectedPerson.MEMBERIDS }}@local-host.co.kr</p>
<p>{{ selectedPerson.MEMBERTEL }}</p>
<p>{{ selectedPerson.MEMBERARR }} {{ selectedPerson.MEMBERDTL }}</p>
<hr />
<!-- 추가 정보: 사용자가 속한 프로젝트 목록 -->
<h5>소속 프로젝트</h5>
<div v-if="memberProjects.length > 0">
<ul>
<li
v-for="(project, idx) in memberProjects"
:key="idx"
class="project-item"
>
<span class="project-name">{{ project.PROJCTNAM }}</span>
<span class="project-period">
{{ project.userStartDate }} ~ {{ project.userEndtDate }}
</span>
</li>
</ul>
</div>
<div v-else>
<p>소속된 프로젝트가 없습니다.</p>
</div>
</div>
</div>
</div>
</div>
</div>
</template>
<script>
import axios from '@api' // API Axios
import { ref, onMounted } from 'vue'
import SearchBar from '@c/search/SearchBar.vue'
export default {
name: 'PeopleList',
components: { SearchBar },
setup() {
const allUserList = ref([]) //
const user = ref({}) // ( )
const showModal = ref(false) //
const selectedPerson = ref({}) //
const memberProjects = ref([]) //
onMounted(async () => {
try {
const response = await axios.get('user/allUserList')
allUserList.value = response.data.data.allUserList
user.value = response.data.data.user
} catch (error) {
console.error('사원 목록 조회 실패:', error)
}
})
const baseUrl = axios.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
}
// API ,
// "project/period/{projctSeq}"
// MEMBERSEQ .
const fetchMemberProjects = async (memberSeq) => {
try {
const res = await axios.get(`project/${memberSeq}`)
let projects = res.data.data
const projectsWithPeriod = await Promise.all(
projects.map(async (project) => {
try {
const periodRes = await axios.get(`project/period/${project.PROJCTSEQ}`)
if (periodRes.data.data && periodRes.data.data.length > 0) {
const matchingPeriod = periodRes.data.data.find(
item => item.MEMBERSEQ == selectedPerson.value.MEMBERSEQ
)
if (matchingPeriod) {
project.userStartDate = matchingPeriod.projectStartDate
project.userEndtDate = matchingPeriod.projectEndDate
} else {
project.userStartDate = ""
project.userEndtDate = ""
}
}
} catch (err) {
console.error("프로젝트 기간 조회 실패:", project.PROJCTSEQ, err)
project.userStartDate = ""
project.userEndtDate = ""
}
return project
})
)
memberProjects.value = projectsWithPeriod
} catch (error) {
console.error('프로젝트 조회 실패:', error)
memberProjects.value = []
}
}
const openModal = (person) => {
selectedPerson.value = person
fetchMemberProjects(person.MEMBERSEQ)
showModal.value = true
}
const closeModal = () => {
showModal.value = false
}
return {
allUserList,
user,
showModal,
selectedPerson,
memberProjects,
openModal,
closeModal,
getProfileImage,
defaultProfile,
setDefaultImage
}
}
}
</script>
<style scoped>
.container-xxl {
padding: 1rem;
}
.card-list {
display: flex;
flex-wrap: wrap;
gap: 1rem;
justify-content: center;
}
.person-card {
width: 280px;
border: 1px solid #eee;
border-radius: 8px;
overflow: hidden;
cursor: pointer;
background: #fff;
transition: box-shadow 0.2s ease-in-out;
}
.person-card:hover {
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.15);
}
.person-card .card-header {
width: 100%;
height: 120px;
overflow: hidden;
display: flex;
align-items: center;
justify-content: center;
background: #f8f9fa;
}
.user-avatar {
max-width: 100%;
max-height: 100%;
object-fit: cover;
border-radius: 50%;
}
.person-card .card-body {
padding: 0.75rem;
}
.person-name {
margin: 0;
font-size: 1.1rem;
font-weight: 600;
}
.person-email,
.person-phone {
margin: 0;
font-size: 0.9rem;
color: #555;
}
/* 모달 스타일 */
.modal-overlay {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 111%;
background-color: rgba(0, 0, 0, 0.4);
display: flex;
justify-content: center;
align-items: center;
z-index: 999;
}
.modal-content {
position: relative;
width: 400px;
background: #fff;
padding: 1.5rem;
border-radius: 8px;
animation: slideDown 0.3s ease forwards;
}
.close-btn {
background: transparent;
border: none;
font-size: 1.5rem;
position: absolute;
top: 0.5rem;
right: 0.5rem;
cursor: pointer;
}
.modal-body {
text-align: center;
}
.modal-img {
width: 50%;
height: 50%;
border-radius: 50%;
margin-bottom: 1rem;
object-fit: cover;
}
/* 프로젝트 리스트 스타일 */
.project-item {
display: flex;
align-items: center;
list-style: none;
font-size: 0.9rem;
padding: 0.25rem 0;
}
.project-name {
font-weight: 600;
}
.project-period {
font-size: 1rem;
color: #888;
margin-left: 10px; /* 기간 텍스트를 프로젝트 이름과 조금 더 가깝게 배치 */
}
@keyframes slideDown {
0% {
transform: translateY(-15%);
opacity: 0;
}
100% {
transform: translateY(0);
opacity: 1;
}
}
</style>