관리자 모달 프레임 공통화
This commit is contained in:
44
components/admin/AdminModalFrame.vue
Normal file
44
components/admin/AdminModalFrame.vue
Normal file
@@ -0,0 +1,44 @@
|
|||||||
|
<script setup>
|
||||||
|
const props = defineProps({
|
||||||
|
/** 모달 제목 요소 ID */
|
||||||
|
labelledBy: {
|
||||||
|
type: String,
|
||||||
|
required: true
|
||||||
|
},
|
||||||
|
/** 오버레이에 추가할 화면별 클래스 */
|
||||||
|
rootClass: {
|
||||||
|
type: String,
|
||||||
|
default: ''
|
||||||
|
},
|
||||||
|
/** 패널에 추가할 화면별 클래스 */
|
||||||
|
panelClass: {
|
||||||
|
type: String,
|
||||||
|
default: ''
|
||||||
|
},
|
||||||
|
/** 오버레이 z-index 클래스 */
|
||||||
|
zClass: {
|
||||||
|
type: String,
|
||||||
|
default: 'z-[60]'
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
const emit = defineEmits(['close'])
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div
|
||||||
|
class="admin-modal-frame fixed inset-0 grid place-items-center bg-black/40 px-5 py-8"
|
||||||
|
:class="[zClass, rootClass]"
|
||||||
|
role="dialog"
|
||||||
|
aria-modal="true"
|
||||||
|
:aria-labelledby="props.labelledBy"
|
||||||
|
@click.self="emit('close')"
|
||||||
|
>
|
||||||
|
<section
|
||||||
|
class="admin-modal-frame__panel max-h-[90vh] w-full overflow-y-auto rounded border border-line bg-white p-5 shadow-xl"
|
||||||
|
:class="panelClass"
|
||||||
|
>
|
||||||
|
<slot />
|
||||||
|
</section>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
@@ -189,6 +189,7 @@
|
|||||||
| components/admin/AdminMemberForm.vue | 관리자 멤버 추가/수정 폼(Ghost형 3분할, 기존 회원 보기/수정 모드 분리, 요약 1fr·입력 2fr, 원형 썸네일 hover 등록·변경·삭제, 기본 정보·레이블·관리자 노트·활동 요약, 설정 메뉴의 비밀번호 변경·멤버 삭제 모달, 저장 토스트, 미저장 변경사항 이탈 확인) |
|
| components/admin/AdminMemberForm.vue | 관리자 멤버 추가/수정 폼(Ghost형 3분할, 기존 회원 보기/수정 모드 분리, 요약 1fr·입력 2fr, 원형 썸네일 hover 등록·변경·삭제, 기본 정보·레이블·관리자 노트·활동 요약, 설정 메뉴의 비밀번호 변경·멤버 삭제 모달, 저장 토스트, 미저장 변경사항 이탈 확인) |
|
||||||
| components/admin/AdminUnsavedChangesModal.vue | 관리자 편집 화면 공통 미저장 변경사항 이탈 확인 모달(상단 40px 위치) |
|
| components/admin/AdminUnsavedChangesModal.vue | 관리자 편집 화면 공통 미저장 변경사항 이탈 확인 모달(상단 40px 위치) |
|
||||||
| components/admin/AdminConfirmModal.vue | 관리자 공통 확인 모달(제목·본문 슬롯·확인/취소 버튼, primary/danger 톤) |
|
| components/admin/AdminConfirmModal.vue | 관리자 공통 확인 모달(제목·본문 슬롯·확인/취소 버튼, primary/danger 톤) |
|
||||||
|
| components/admin/AdminModalFrame.vue | 관리자 공통 일반 모달 프레임(오버레이, dialog 접근성 속성, 기본 패널 스타일, 바깥 클릭 닫기) |
|
||||||
| components/admin/AdminToast.vue | 관리자 공통 우측 상단 토스트 표시(성공·오류·안내 톤, 화면별 상단 위치 조정) |
|
| components/admin/AdminToast.vue | 관리자 공통 우측 상단 토스트 표시(성공·오류·안내 톤, 화면별 상단 위치 조정) |
|
||||||
| components/admin/AdminSearchInput.vue | 관리자 공통 검색 입력(돋보기 아이콘, IME 검색 이벤트 전달, 외부 focus/select 노출) |
|
| components/admin/AdminSearchInput.vue | 관리자 공통 검색 입력(돋보기 아이콘, IME 검색 이벤트 전달, 외부 focus/select 노출) |
|
||||||
| components/admin/AdminSelectInput.vue | 관리자 공통 select 입력(문자·숫자 옵션 값, 기본 필터·정렬 옵션, chevron 아이콘, `v-model` 전달) |
|
| components/admin/AdminSelectInput.vue | 관리자 공통 select 입력(문자·숫자 옵션 값, 기본 필터·정렬 옵션, chevron 아이콘, `v-model` 전달) |
|
||||||
|
|||||||
@@ -9,7 +9,7 @@
|
|||||||
- `components/admin/AdminPostForm.vue`: 태그 입력, 대표 이미지 선택, 발행/초안 저장 상태, 우측 설정 패널 책임을 분리.
|
- `components/admin/AdminPostForm.vue`: 태그 입력, 대표 이미지 선택, 발행/초안 저장 상태, 우측 설정 패널 책임을 분리.
|
||||||
- `pages/admin/media/index.vue`: 미디어 목록, 폴더 트리, 상세 모달, 재생성 액션을 작은 단위로 분리.
|
- `pages/admin/media/index.vue`: 미디어 목록, 폴더 트리, 상세 모달, 재생성 액션을 작은 단위로 분리.
|
||||||
- 관리자 UI 공통 컴포넌트 정리.
|
- 관리자 UI 공통 컴포넌트 정리.
|
||||||
- 모달 패널, 필터 팝오버의 radius·shadow·z-index·색상 규칙을 공통화.
|
- 필터 팝오버의 radius·shadow·z-index·색상 규칙을 공통화.
|
||||||
- 멤버 고급 필터와 설정 화면 select 입력의 공통 컴포넌트 적용 범위 검토.
|
- 멤버 고급 필터와 설정 화면 select 입력의 공통 컴포넌트 적용 범위 검토.
|
||||||
- 최근 브라우저 회귀 테스트 추가.
|
- 최근 브라우저 회귀 테스트 추가.
|
||||||
- 태그 추천 선택 후 다음 태그 후보가 포커스 해제 없이 유지되는지 확인.
|
- 태그 추천 선택 후 다음 태그 후보가 포커스 해제 없이 유지되는지 확인.
|
||||||
|
|||||||
@@ -1,5 +1,10 @@
|
|||||||
# 업데이트 이력
|
# 업데이트 이력
|
||||||
|
|
||||||
|
## v1.11.11
|
||||||
|
|
||||||
|
- 관리자 일반 모달 오버레이·패널을 공용 `AdminModalFrame` 컴포넌트로 분리.
|
||||||
|
- 미디어 폴더 생성과 네비게이션 생성·페이지 섹션·페이지 순서·추천 사이트 편집 모달의 반복 wrapper 제거.
|
||||||
|
|
||||||
## v1.11.10
|
## v1.11.10
|
||||||
|
|
||||||
- 관리자 페이지네이션 계산 로직을 `lib/admin-pagination.js` 순수 함수로 분리.
|
- 관리자 페이지네이션 계산 로직을 `lib/admin-pagination.js` 순수 함수로 분리.
|
||||||
|
|||||||
4
package-lock.json
generated
4
package-lock.json
generated
@@ -1,12 +1,12 @@
|
|||||||
{
|
{
|
||||||
"name": "sori.studio",
|
"name": "sori.studio",
|
||||||
"version": "1.11.10",
|
"version": "1.11.11",
|
||||||
"lockfileVersion": 3,
|
"lockfileVersion": 3,
|
||||||
"requires": true,
|
"requires": true,
|
||||||
"packages": {
|
"packages": {
|
||||||
"": {
|
"": {
|
||||||
"name": "sori.studio",
|
"name": "sori.studio",
|
||||||
"version": "1.11.10",
|
"version": "1.11.11",
|
||||||
"hasInstallScript": true,
|
"hasInstallScript": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@nuxtjs/tailwindcss": "^6.14.0",
|
"@nuxtjs/tailwindcss": "^6.14.0",
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "sori.studio",
|
"name": "sori.studio",
|
||||||
"version": "1.11.10",
|
"version": "1.11.11",
|
||||||
"private": true,
|
"private": true,
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"imports": {
|
"imports": {
|
||||||
|
|||||||
@@ -1663,15 +1663,13 @@ watch(() => mediaPagination.value.page, (page) => {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div
|
<AdminModalFrame
|
||||||
v-if="isCreateFolderModalOpen"
|
v-if="isCreateFolderModalOpen"
|
||||||
class="admin-media__folder-create-modal fixed inset-0 z-[60] grid place-items-center bg-black/40 px-5 py-8"
|
labelled-by="media-folder-modal-title"
|
||||||
role="dialog"
|
root-class="admin-media__folder-create-modal"
|
||||||
aria-modal="true"
|
panel-class="admin-media__folder-create-panel max-w-sm"
|
||||||
aria-labelledby="media-folder-modal-title"
|
@close="closeCreateFolderModal"
|
||||||
@click.self="closeCreateFolderModal"
|
|
||||||
>
|
>
|
||||||
<section class="admin-media__folder-create-panel w-full max-w-sm rounded border border-line bg-white p-5 shadow-xl">
|
|
||||||
<h3 id="media-folder-modal-title" class="text-lg font-semibold text-ink">
|
<h3 id="media-folder-modal-title" class="text-lg font-semibold text-ink">
|
||||||
새 폴더
|
새 폴더
|
||||||
</h3>
|
</h3>
|
||||||
@@ -1701,8 +1699,7 @@ watch(() => mediaPagination.value.page, (page) => {
|
|||||||
만들기
|
만들기
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</AdminModalFrame>
|
||||||
</div>
|
|
||||||
|
|
||||||
<div
|
<div
|
||||||
v-if="selectedMedia"
|
v-if="selectedMedia"
|
||||||
|
|||||||
@@ -2101,15 +2101,13 @@ const saveNavigation = async () => {
|
|||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div
|
<AdminModalFrame
|
||||||
v-if="navCreateModalType"
|
v-if="navCreateModalType"
|
||||||
class="admin-navigation__create-modal fixed inset-0 z-[60] grid place-items-center bg-black/40 px-5 py-8"
|
labelled-by="admin-navigation-create-title"
|
||||||
role="dialog"
|
root-class="admin-navigation__create-modal"
|
||||||
aria-modal="true"
|
panel-class="admin-navigation__create-modal-panel max-w-lg"
|
||||||
aria-labelledby="admin-navigation-create-title"
|
@close="closeNavCreateModal"
|
||||||
@click.self="closeNavCreateModal"
|
|
||||||
>
|
>
|
||||||
<section class="admin-navigation__create-modal-panel max-h-[90vh] w-full max-w-lg overflow-y-auto rounded border border-line bg-white p-5 shadow-xl">
|
|
||||||
<div class="admin-navigation__create-modal-head">
|
<div class="admin-navigation__create-modal-head">
|
||||||
<h3 id="admin-navigation-create-title" class="text-lg font-semibold text-ink">
|
<h3 id="admin-navigation-create-title" class="text-lg font-semibold text-ink">
|
||||||
{{ navCreateModalTitle }}
|
{{ navCreateModalTitle }}
|
||||||
@@ -2179,18 +2177,15 @@ const saveNavigation = async () => {
|
|||||||
추가
|
추가
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</AdminModalFrame>
|
||||||
</div>
|
|
||||||
|
|
||||||
<div
|
<AdminModalFrame
|
||||||
v-if="pageSectionCreateModalOpen"
|
v-if="pageSectionCreateModalOpen"
|
||||||
class="admin-navigation__page-section-create-modal fixed inset-0 z-[60] grid place-items-center bg-black/40 px-5 py-8"
|
labelled-by="admin-navigation-page-section-create-title"
|
||||||
role="dialog"
|
root-class="admin-navigation__page-section-create-modal"
|
||||||
aria-modal="true"
|
panel-class="admin-navigation__page-section-create-modal-panel max-w-lg"
|
||||||
aria-labelledby="admin-navigation-page-section-create-title"
|
@close="closePageSectionCreateModal"
|
||||||
@click.self="closePageSectionCreateModal"
|
|
||||||
>
|
>
|
||||||
<section class="admin-navigation__page-section-create-modal-panel max-h-[90vh] w-full max-w-lg overflow-y-auto rounded border border-line bg-white p-5 shadow-xl">
|
|
||||||
<div class="admin-navigation__page-section-create-modal-head">
|
<div class="admin-navigation__page-section-create-modal-head">
|
||||||
<h3 id="admin-navigation-page-section-create-title" class="text-lg font-semibold text-ink">
|
<h3 id="admin-navigation-page-section-create-title" class="text-lg font-semibold text-ink">
|
||||||
페이지 섹션 추가
|
페이지 섹션 추가
|
||||||
@@ -2226,8 +2221,7 @@ const saveNavigation = async () => {
|
|||||||
{{ savingPageSectionId === 'new' ? '추가 중' : '추가' }}
|
{{ savingPageSectionId === 'new' ? '추가 중' : '추가' }}
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</AdminModalFrame>
|
||||||
</div>
|
|
||||||
|
|
||||||
<AdminConfirmModal
|
<AdminConfirmModal
|
||||||
:open="recommendedDiscardModalOpen"
|
:open="recommendedDiscardModalOpen"
|
||||||
@@ -2254,15 +2248,13 @@ const saveNavigation = async () => {
|
|||||||
</p>
|
</p>
|
||||||
</AdminConfirmModal>
|
</AdminConfirmModal>
|
||||||
|
|
||||||
<div
|
<AdminModalFrame
|
||||||
v-if="pageOrderModalSection"
|
v-if="pageOrderModalSection"
|
||||||
class="admin-navigation__page-order-modal fixed inset-0 z-[60] grid place-items-center bg-black/40 px-5 py-8"
|
labelled-by="admin-navigation-page-order-title"
|
||||||
role="dialog"
|
root-class="admin-navigation__page-order-modal"
|
||||||
aria-modal="true"
|
panel-class="admin-navigation__page-order-modal-panel max-w-4xl"
|
||||||
aria-labelledby="admin-navigation-page-order-title"
|
@close="closePageOrderModal"
|
||||||
@click.self="closePageOrderModal"
|
|
||||||
>
|
>
|
||||||
<section class="admin-navigation__page-order-modal-panel max-h-[90vh] w-full max-w-4xl overflow-y-auto rounded border border-line bg-white p-5 shadow-xl">
|
|
||||||
<div class="admin-navigation__page-order-modal-head flex items-start justify-between gap-4">
|
<div class="admin-navigation__page-order-modal-head flex items-start justify-between gap-4">
|
||||||
<div>
|
<div>
|
||||||
<h3 id="admin-navigation-page-order-title" class="text-lg font-semibold text-ink">
|
<h3 id="admin-navigation-page-order-title" class="text-lg font-semibold text-ink">
|
||||||
@@ -2347,18 +2339,15 @@ const saveNavigation = async () => {
|
|||||||
{{ savingPageOrder ? '저장 중' : '저장' }}
|
{{ savingPageOrder ? '저장 중' : '저장' }}
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</AdminModalFrame>
|
||||||
</div>
|
|
||||||
|
|
||||||
<div
|
<AdminModalFrame
|
||||||
v-if="recommendedEditId && recommendedEditDraft"
|
v-if="recommendedEditId && recommendedEditDraft"
|
||||||
class="admin-navigation__recommended-modal fixed inset-0 z-[60] grid place-items-center bg-black/40 px-5 py-8"
|
labelled-by="admin-navigation-recommended-edit-title"
|
||||||
role="dialog"
|
root-class="admin-navigation__recommended-modal"
|
||||||
aria-modal="true"
|
panel-class="admin-navigation__recommended-modal-panel max-w-lg"
|
||||||
aria-labelledby="admin-navigation-recommended-edit-title"
|
@close="requestCloseRecommendedEdit"
|
||||||
@click.self="requestCloseRecommendedEdit"
|
|
||||||
>
|
>
|
||||||
<section class="admin-navigation__recommended-modal-panel max-h-[90vh] w-full max-w-lg overflow-y-auto rounded border border-line bg-white p-5 shadow-xl">
|
|
||||||
<div class="admin-navigation__recommended-modal-head">
|
<div class="admin-navigation__recommended-modal-head">
|
||||||
<h3 id="admin-navigation-recommended-edit-title" class="text-lg font-semibold text-ink">
|
<h3 id="admin-navigation-recommended-edit-title" class="text-lg font-semibold text-ink">
|
||||||
추천 사이트 편집
|
추천 사이트 편집
|
||||||
@@ -2458,7 +2447,7 @@ const saveNavigation = async () => {
|
|||||||
class="min-h-24 resize-y rounded border border-line px-3 py-2 font-mono text-sm outline-none focus:border-[#8e9cac]"
|
class="min-h-24 resize-y rounded border border-line px-3 py-2 font-mono text-sm outline-none focus:border-[#8e9cac]"
|
||||||
placeholder="/uploads/…"
|
placeholder="/uploads/…"
|
||||||
/>
|
/>
|
||||||
<span class="text-[11px] pl-2 text-muted">비워 두면 서버 캐시 파비콘을 사용합니다.</span>
|
<span class="pl-2 text-[11px] text-muted">비워 두면 서버 캐시 파비콘을 사용합니다.</span>
|
||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -2499,8 +2488,7 @@ const saveNavigation = async () => {
|
|||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</AdminModalFrame>
|
||||||
</div>
|
|
||||||
|
|
||||||
<AdminToast :toast="toast" />
|
<AdminToast :toast="toast" />
|
||||||
</section>
|
</section>
|
||||||
|
|||||||
Reference in New Issue
Block a user