| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419 |
- <template>
- <div class="admin--admins">
- <div class="admin--page-header">
- <h3>관리자 관리</h3>
- <button @click="openCreateModal" class="admin--btn-small admin--btn-small-primary">
- + 관리자 추가
- </button>
- </div>
- <!-- 검색 영역 -->
- <div class="admin--search-box">
- <div class="admin--search-form">
- <select v-model="filterRole" @change="loadAdmins" class="admin--form-select admin--search-select">
- <option value="">전체 역할</option>
- <option value="super_admin">슈퍼 관리자</option>
- <option value="admin">일반 관리자</option>
- </select>
- <select v-model="filterStatus" @change="loadAdmins" class="admin--form-select admin--search-select">
- <option value="">전체 상태</option>
- <option value="active">활성</option>
- <option value="inactive">비활성</option>
- </select>
- <input
- v-model="searchQuery"
- type="text"
- placeholder="아이디, 이름으로 검색"
- @keyup.enter="loadAdmins"
- class="admin--form-input admin--search-input"
- />
- <button @click="loadAdmins" class="admin--btn-small admin--btn-small-primary">
- 검색
- </button>
- <button @click="resetSearch" class="admin--btn-small admin--btn-small-secondary">
- 초기화
- </button>
- </div>
- </div>
- <!-- 관리자 목록 테이블 -->
- <div class="admin--table-container">
- <table class="admin--table">
- <thead>
- <tr>
- <th>ID</th>
- <th>아이디</th>
- <th>이름</th>
- <th>이메일</th>
- <th>역할</th>
- <th>상태</th>
- <th>생성일</th>
- <th>관리</th>
- </tr>
- </thead>
- <tbody>
- <tr v-if="loading">
- <td colspan="8" class="admin--loading">로딩 중...</td>
- </tr>
- <tr v-else-if="admins.length === 0">
- <td colspan="8" class="admin--no-data">관리자가 없습니다.</td>
- </tr>
- <tr v-else v-for="admin in admins" :key="admin.id">
- <td>{{ admin.id }}</td>
- <td>{{ admin.username }}</td>
- <td>{{ admin.name }}</td>
- <td>{{ admin.email }}</td>
- <td>
- <span :class="['admin--badge', getRoleBadgeClass(admin.role)]">
- {{ getRoleLabel(admin.role) }}
- </span>
- </td>
- <td>
- <span :class="['admin--badge', getStatusBadgeClass(admin.status)]">
- {{ getStatusLabel(admin.status) }}
- </span>
- </td>
- <td>{{ formatDate(admin.created_at) }}</td>
- <td>
- <div class="admin--table-actions">
- <button @click="openEditModal(admin)" class="admin--btn-sm admin--btn-edit">
- 수정
- </button>
- <button @click="openPasswordModal(admin)" class="admin--btn-sm admin--btn-warning">
- 비밀번호
- </button>
- <button
- @click="confirmDeleteAdmin(admin)"
- class="admin--btn-sm admin--btn-delete"
- :disabled="admin.id === currentAdminId"
- >
- 삭제
- </button>
- </div>
- </td>
- </tr>
- </tbody>
- </table>
- </div>
- <!-- 페이지네이션 -->
- <div class="admin--pagination" v-if="totalPages > 1">
- <button
- @click="changePage(currentPage - 1)"
- :disabled="currentPage === 1"
- class="admin--btn-page"
- >
- 이전
- </button>
- <span class="admin--page-info">
- {{ currentPage }} / {{ totalPages }}
- </span>
- <button
- @click="changePage(currentPage + 1)"
- :disabled="currentPage === totalPages"
- class="admin--btn-page"
- >
- 다음
- </button>
- </div>
- <!-- 관리자 추가/수정 모달 -->
- <AdminModal
- v-if="showModal"
- :admin="selectedAdmin"
- @close="closeModal"
- @saved="handleSaved"
- />
- <!-- 비밀번호 변경 모달 -->
- <PasswordModal
- v-if="showPasswordModal"
- :admin="selectedAdmin"
- @close="closePasswordModal"
- @saved="handlePasswordChanged"
- />
- <!-- 알림 모달 -->
- <AdminAlertModal
- v-if="alertModal.show"
- :title="alertModal.title"
- :message="alertModal.message"
- :type="alertModal.type"
- @confirm="handleAlertConfirm"
- @cancel="handleAlertCancel"
- @close="closeAlertModal"
- />
- </div>
- </template>
- <script setup>
- import { ref, onMounted, computed } from 'vue'
- import AdminAlertModal from '~/components/admin/AdminAlertModal.vue'
- import AdminModal from '~/components/admin/AdminModal.vue'
- import PasswordModal from '~/components/admin/PasswordModal.vue'
- definePageMeta({
- layout: 'admin',
- middleware: ['auth']
- })
- const { get, del } = useApi()
- // 현재 로그인한 관리자 ID
- const currentAdminId = computed(() => {
- if (typeof window === 'undefined') return null
- const user = localStorage.getItem('admin_user')
- if (!user) return null
- try {
- return JSON.parse(user).id
- } catch {
- return null
- }
- })
- // 데이터
- const admins = ref([])
- const loading = ref(false)
- const searchQuery = ref('')
- const filterRole = ref('')
- const filterStatus = ref('')
- // 페이지네이션
- const currentPage = ref(1)
- const totalPages = ref(1)
- const perPage = ref(10)
- // 모달
- const showModal = ref(false)
- const showPasswordModal = ref(false)
- const selectedAdmin = ref(null)
- // 알림 모달
- const alertModal = ref({
- show: false,
- title: '알림',
- message: '',
- type: 'alert',
- onConfirm: null
- })
- // 알림 모달 표시
- const showAlert = (message, title = '알림') => {
- alertModal.value = {
- show: true,
- title,
- message,
- type: 'alert',
- onConfirm: null
- }
- }
- // 확인 모달 표시
- const showConfirm = (message, onConfirm, title = '확인') => {
- alertModal.value = {
- show: true,
- title,
- message,
- type: 'confirm',
- onConfirm
- }
- }
- // 알림 모달 닫기
- const closeAlertModal = () => {
- alertModal.value.show = false
- }
- // 알림 모달 확인
- const handleAlertConfirm = () => {
- if (alertModal.value.onConfirm) {
- alertModal.value.onConfirm()
- }
- closeAlertModal()
- }
- // 알림 모달 취소
- const handleAlertCancel = () => {
- closeAlertModal()
- }
- // 관리자 목록 로드
- const loadAdmins = async () => {
- loading.value = true
- try {
- const params = {
- page: currentPage.value,
- per_page: perPage.value
- }
- if (searchQuery.value) {
- params.search = searchQuery.value
- }
- if (filterRole.value) {
- params.role = filterRole.value
- }
- if (filterStatus.value) {
- params.status = filterStatus.value
- }
- console.log('[Admins] 검색 파라미터:', params)
- const { data, error } = await get('/admin', { params })
- if (error) {
- console.error('[Admins] 목록 로드 실패:', error)
- return
- }
- // API 응답: { success: true, data: { items, total_pages }, message }
- if (data?.success && data?.data) {
- admins.value = data.data.items || []
- totalPages.value = data.data.total_pages || 1
- console.log('[Admins] 목록 로드 성공:', data.data)
- }
- } finally {
- loading.value = false
- }
- }
- // 페이지 변경
- const changePage = (page) => {
- if (page < 1 || page > totalPages.value) return
- currentPage.value = page
- loadAdmins()
- }
- // 검색 초기화
- const resetSearch = () => {
- searchQuery.value = ''
- filterRole.value = ''
- filterStatus.value = ''
- currentPage.value = 1
- loadAdmins()
- }
- // 모달 열기/닫기
- const openCreateModal = () => {
- selectedAdmin.value = null
- showModal.value = true
- }
- const openEditModal = (admin) => {
- selectedAdmin.value = { ...admin }
- showModal.value = true
- }
- const closeModal = () => {
- showModal.value = false
- selectedAdmin.value = null
- }
- const handleSaved = (message) => {
- closeModal()
- loadAdmins()
- if (message) {
- showAlert(message, '성공')
- }
- }
- // 비밀번호 모달
- const openPasswordModal = (admin) => {
- selectedAdmin.value = { ...admin }
- showPasswordModal.value = true
- }
- const closePasswordModal = () => {
- showPasswordModal.value = false
- selectedAdmin.value = null
- }
- const handlePasswordChanged = (message) => {
- closePasswordModal()
- if (message) {
- showAlert(message, '성공')
- }
- }
- // 관리자 삭제 확인
- const confirmDeleteAdmin = (admin) => {
- if (admin.id === currentAdminId.value) {
- showAlert('본인 계정은 삭제할 수 없습니다.', '경고')
- return
- }
- showConfirm(
- `${admin.name} (${admin.username}) 관리자를 삭제하시겠습니까?`,
- () => deleteAdmin(admin),
- '관리자 삭제'
- )
- }
- // 관리자 삭제
- const deleteAdmin = async (admin) => {
- const { data, error } = await del(`/admin/${admin.id}`)
- if (error) {
- showAlert('관리자 삭제에 실패했습니다.', '오류')
- console.error('[Admins] 삭제 실패:', error)
- return
- }
- if (data?.success) {
- showAlert('관리자가 삭제되었습니다.', '성공')
- loadAdmins()
- }
- }
- // 유틸리티 함수
- const getRoleLabel = (role) => {
- const labels = {
- super_admin: '슈퍼 관리자',
- admin: '일반 관리자'
- }
- return labels[role] || role
- }
- const getRoleBadgeClass = (role) => {
- return role === 'super_admin' ? 'admin--badge-danger' : 'admin--badge-primary'
- }
- const getStatusLabel = (status) => {
- const labels = {
- active: '활성',
- inactive: '비활성'
- }
- return labels[status] || status
- }
- const getStatusBadgeClass = (status) => {
- return status === 'active' ? 'admin--badge-success' : 'admin--badge-secondary'
- }
- const formatDate = (dateString) => {
- if (!dateString) return '-'
- const date = new Date(dateString)
- return date.toLocaleString('ko-KR', {
- year: 'numeric',
- month: '2-digit',
- day: '2-digit',
- hour: '2-digit',
- minute: '2-digit'
- })
- }
- onMounted(() => {
- loadAdmins()
- })
- </script>
- <style scoped>
- .admin--search-box .admin--form-input,
- .admin--search-box .admin--search-input,
- .admin--search-box .admin--form-select,
- .admin--search-box .admin--search-select {
- border: 1px solid var(--admin-border-color) !important;
- height: 33px !important;
- padding: 6px 14px !important;
- font-size: 13px !important;
- }
- </style>
|