Compare commits
1 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 59a50a0c97 |
@@ -1,5 +1,10 @@
|
|||||||
# 업데이트 요약
|
# 업데이트 요약
|
||||||
|
|
||||||
|
## v1.1.8
|
||||||
|
|
||||||
|
- 로고·파비콘 파일명 접미사를 년월+랜덤 문자열로 줄임.
|
||||||
|
- 태그 추가 버튼을 일반 태그 영역으로 옮기고, 메인 태그 순서는 드래그 후 자동 저장되도록 개선.
|
||||||
|
|
||||||
## v1.1.7
|
## v1.1.7
|
||||||
|
|
||||||
- 사이트 로고와 파비콘을 교체할 때 새 고유 URL로 저장해 운영 환경에서 이전 이미지가 캐시에 남는 문제를 줄임.
|
- 사이트 로고와 파비콘을 교체할 때 새 고유 URL로 저장해 운영 환경에서 이전 이미지가 캐시에 남는 문제를 줄임.
|
||||||
|
|||||||
@@ -1,5 +1,11 @@
|
|||||||
# 의사결정 이력
|
# 의사결정 이력
|
||||||
|
|
||||||
|
## 2026-05-15 v1.1.8
|
||||||
|
|
||||||
|
### 태그 순서 저장을 드롭 즉시 자동화
|
||||||
|
|
||||||
|
메인 태그 정렬은 드래그 자체가 명확한 저장 의도를 가진 조작이므로 별도의 `정렬 저장` 버튼을 두면 화면의 책임이 나뉘어 보인다. 태그 추가 버튼도 화면 전체 제목 옆에 있으면 메인 태그 추가처럼 보일 수 있어, 새 태그가 기본적으로 일반 태그로 생성되는 현재 구조에 맞춰 일반 태그 섹션 헤더 오른쪽으로 옮겼다. 순서 저장 중에는 추가 드래그를 잠시 막아 서버 순서와 화면 순서가 어긋나지 않게 한다.
|
||||||
|
|
||||||
## 2026-05-15 v1.1.7
|
## 2026-05-15 v1.1.7
|
||||||
|
|
||||||
### 사이트 로고 파일명을 교체마다 고유하게 저장
|
### 사이트 로고 파일명을 교체마다 고유하게 저장
|
||||||
|
|||||||
@@ -116,7 +116,7 @@
|
|||||||
| pages/admin/pages/[id].vue | 페이지 수정, 저장/삭제 토스트 |
|
| pages/admin/pages/[id].vue | 페이지 수정, 저장/삭제 토스트 |
|
||||||
| pages/admin/media/index.vue | 미디어 관리, **미디어 라이브러리/썸네일** 탭, 검색은 파일명·게시물/페이지 사용처 제목만, 라이브러리: 폴더 트리·드래그 이동 등, 현재 사이트 설정 로고·파비콘은 사용 중 파일로 잠금, 썸네일: 미참조 파일 삭제·이름 변경 가능, 상세 모달(연결 회원·폴더 편집·**다운로드**) |
|
| pages/admin/media/index.vue | 미디어 관리, **미디어 라이브러리/썸네일** 탭, 검색은 파일명·게시물/페이지 사용처 제목만, 라이브러리: 폴더 트리·드래그 이동 등, 현재 사이트 설정 로고·파비콘은 사용 중 파일로 잠금, 썸네일: 미참조 파일 삭제·이름 변경 가능, 상세 모달(연결 회원·폴더 편집·**다운로드**) |
|
||||||
| pages/admin/navigation/index.vue | 메뉴 관리: 상단/하단 탭, 테이블+행 드래그(태그 메인과 동일 톤), `useAdminToast` |
|
| pages/admin/navigation/index.vue | 메뉴 관리: 상단/하단 탭, 테이블+행 드래그(태그 메인과 동일 톤), `useAdminToast` |
|
||||||
| pages/admin/tags/index.vue | 태그 관리(메인 태그 드래그 정렬·일반 강등, 일반 태그 검색/메인 전환/삭제), 액션 피드백 토스트, 순서 변경 시에만 정렬 저장 활성 |
|
| pages/admin/tags/index.vue | 태그 관리(메인 태그 드래그 정렬 자동 저장·일반 강등, 일반 태그 검색/메인 전환/삭제, 일반 태그 헤더의 태그 추가 버튼), 액션 피드백 토스트 |
|
||||||
| pages/admin/tags/new.vue | 태그 생성 |
|
| pages/admin/tags/new.vue | 태그 생성 |
|
||||||
| pages/admin/tags/[id].vue | 태그 수정 |
|
| pages/admin/tags/[id].vue | 태그 수정 |
|
||||||
| pages/admin/settings/index.vue | 사이트 설정(이름, 설명, URL, 1:1 로고 업로드·고유 URL 파비콘 생성, 저작권 문구) |
|
| pages/admin/settings/index.vue | 사이트 설정(이름, 설명, URL, 1:1 로고 업로드·고유 URL 파비콘 생성, 저작권 문구) |
|
||||||
@@ -201,7 +201,7 @@
|
|||||||
| server/routes/admin/api/tags/reorder.put.js | 관리자 메인 태그 순서 일괄 저장 API |
|
| server/routes/admin/api/tags/reorder.put.js | 관리자 메인 태그 순서 일괄 저장 API |
|
||||||
| server/routes/admin/api/settings.get.js | 관리자 사이트 설정 조회 API |
|
| server/routes/admin/api/settings.get.js | 관리자 사이트 설정 조회 API |
|
||||||
| server/routes/admin/api/settings.put.js | 관리자 사이트 설정 수정 API |
|
| server/routes/admin/api/settings.put.js | 관리자 사이트 설정 수정 API |
|
||||||
| server/routes/admin/api/settings/logo.post.js | 관리자 사이트 로고 업로드 API(`/uploads/system/logo-*.webp`, `/uploads/system/favicon-*.png` 생성, `시스템` 미디어 메타 저장) |
|
| server/routes/admin/api/settings/logo.post.js | 관리자 사이트 로고 업로드 API(`/uploads/system/logo-YYYYMM-*.webp`, `/uploads/system/favicon-YYYYMM-*.png` 생성, `시스템` 미디어 메타 저장) |
|
||||||
| server/routes/admin/api/navigation.get.js | 관리자 네비게이션 목록 API |
|
| server/routes/admin/api/navigation.get.js | 관리자 네비게이션 목록 API |
|
||||||
| server/routes/admin/api/navigation.put.js | 관리자 네비게이션 일괄 저장 API |
|
| server/routes/admin/api/navigation.put.js | 관리자 네비게이션 일괄 저장 API |
|
||||||
| server/routes/admin/api/members.get.js | 관리자 멤버 목록 API |
|
| server/routes/admin/api/members.get.js | 관리자 멤버 목록 API |
|
||||||
|
|||||||
10
docs/spec.md
10
docs/spec.md
@@ -445,8 +445,8 @@ components/content/
|
|||||||
> 공개 `GET /api/tags`는 `managed`(메인 태그)만 반환한다.
|
> 공개 `GET /api/tags`는 `managed`(메인 태그)만 반환한다.
|
||||||
> 관리자 태그 목록 응답은 각 태그의 `postCount`, `lastUsedAt`, `updatedAt`을 포함한다.
|
> 관리자 태그 목록 응답은 각 태그의 `postCount`, `lastUsedAt`, `updatedAt`을 포함한다.
|
||||||
> 관리자 태그 목록은 `managed` 우선, `sort_order ASC, 최근 사용/수정 DESC, name ASC` 기준으로 정렬한다.
|
> 관리자 태그 목록은 `managed` 우선, `sort_order ASC, 최근 사용/수정 DESC, name ASC` 기준으로 정렬한다.
|
||||||
> 메인 태그 순서 저장은 드래그 순서를 받아 `sort_order`를 순차 값으로 다시 저장한다.
|
> 메인 태그 순서 저장은 드래그 드롭 직후 자동으로 실행되며, 드래그 순서를 받아 `sort_order`를 순차 값으로 다시 저장한다.
|
||||||
> 메인 태그 순서가 서버에서 불러온 순서와 같을 때는 `정렬 저장` 버튼이 비활성화된다.
|
> 메인 태그 순서 저장 중에는 추가 드래그를 잠시 막고 저장 상태를 표시한다.
|
||||||
> 관리자 태그 추가 화면에서 직접 생성한 태그는 기본적으로 `general`(일반 태그)로 생성하고, 태그 관리 화면의 일반 태그 목록에 바로 표시한다.
|
> 관리자 태그 추가 화면에서 직접 생성한 태그는 기본적으로 `general`(일반 태그)로 생성하고, 태그 관리 화면의 일반 태그 목록에 바로 표시한다.
|
||||||
> 게시물 작성에서 새로 생기는 태그는 기본적으로 `general`(일반 태그)로 생성한다.
|
> 게시물 작성에서 새로 생기는 태그는 기본적으로 `general`(일반 태그)로 생성한다.
|
||||||
> 메인 태그는 목록에서 `일반 태그로 변경` 액션으로 강등하며, 일반 태그는 배지형 전체 목록에서 확인·필터·최근 사용순·많이 사용순·이름순 정렬·메인 전환·삭제를 수행한다.
|
> 메인 태그는 목록에서 `일반 태그로 변경` 액션으로 강등하며, 일반 태그는 배지형 전체 목록에서 확인·필터·최근 사용순·많이 사용순·이름순 정렬·메인 전환·삭제를 수행한다.
|
||||||
@@ -550,7 +550,7 @@ components/content/
|
|||||||
|
|
||||||
- 사이트 설정은 `site_settings` 테이블의 단일 레코드로 관리한다.
|
- 사이트 설정은 `site_settings` 테이블의 단일 레코드로 관리한다.
|
||||||
- 관리자는 사이트 이름, 설명, 사이트 URL, 로고 이미지, 저작권 문구를 수정할 수 있다.
|
- 관리자는 사이트 이름, 설명, 사이트 URL, 로고 이미지, 저작권 문구를 수정할 수 있다.
|
||||||
- 로고 이미지는 1:1 비율로 저장하며 `/admin/api/settings/logo` 업로드 시 `/uploads/system/logo-*.webp`와 `/uploads/system/favicon-*.png`를 고유 파일명으로 함께 생성한다. 같은 URL 덮어쓰기로 인한 브라우저·운영 정적 캐시 문제를 피하기 위해 로고 교체마다 새 URL을 저장한다.
|
- 로고 이미지는 1:1 비율로 저장하며 `/admin/api/settings/logo` 업로드 시 `/uploads/system/logo-YYYYMM-random.webp`와 `/uploads/system/favicon-YYYYMM-random.png`를 고유 파일명으로 함께 생성한다. 같은 URL 덮어쓰기로 인한 브라우저·운영 정적 캐시 문제를 피하기 위해 로고 교체마다 새 URL을 저장한다.
|
||||||
- 공개 헤더와 오른쪽 사이드바는 공개 사이트 설정 API 값을 사용한다.
|
- 공개 헤더와 오른쪽 사이드바는 공개 사이트 설정 API 값을 사용한다.
|
||||||
- 공개 헤더와 오른쪽 사이드바는 `logo_url`이 있으면 이미지 로고를 표시하고, 없으면 `logo_text` fallback을 쓴다. `favicon_url`은 head의 icon 링크로 연결한다.
|
- 공개 헤더와 오른쪽 사이드바는 `logo_url`이 있으면 이미지 로고를 표시하고, 없으면 `logo_text` fallback을 쓴다. `favicon_url`은 head의 icon 링크로 연결한다.
|
||||||
- DB 연결이 없는 환경에서는 환경 변수와 기본값 기반 설정을 사용한다.
|
- DB 연결이 없는 환경에서는 환경 변수와 기본값 기반 설정을 사용한다.
|
||||||
@@ -612,8 +612,8 @@ components/content/
|
|||||||
/uploads/posts/YYYY/MM/filename.webp
|
/uploads/posts/YYYY/MM/filename.webp
|
||||||
/uploads/pages/YYYY/MM/filename.webp
|
/uploads/pages/YYYY/MM/filename.webp
|
||||||
/uploads/members/avatars/YYYY/MM/filename.webp
|
/uploads/members/avatars/YYYY/MM/filename.webp
|
||||||
/uploads/system/logo-YYYYMMDDTHHMMSS-random.webp
|
/uploads/system/logo-YYYYMM-random.webp
|
||||||
/uploads/system/favicon-YYYYMMDDTHHMMSS-random.png
|
/uploads/system/favicon-YYYYMM-random.png
|
||||||
```
|
```
|
||||||
|
|
||||||
- 관리자 에디터 이미지 업로드 API는 디스크상 `public/uploads/posts/YYYY/MM/`에 저장하지만, DB가 있을 때 `media_metadata`에는 논리 폴더 **`미분류`**로 기록한다. 메타가 없을 때도 서버 목록에서는 `posts/...` 경로를 **`미분류`**로 표시한다(디스크 1단계 `posts`와 이중 표기하지 않음). 저장 파일명은 업로드 원본 파일명(안전 문자만 유지)을 우선 쓰고, 같은 월 디렉터리에 동일 이름이 있으면 `이름-2`, `이름-3` 식으로 넘버링한다.
|
- 관리자 에디터 이미지 업로드 API는 디스크상 `public/uploads/posts/YYYY/MM/`에 저장하지만, DB가 있을 때 `media_metadata`에는 논리 폴더 **`미분류`**로 기록한다. 메타가 없을 때도 서버 목록에서는 `posts/...` 경로를 **`미분류`**로 표시한다(디스크 1단계 `posts`와 이중 표기하지 않음). 저장 파일명은 업로드 원본 파일명(안전 문자만 유지)을 우선 쓰고, 같은 월 디렉터리에 동일 이름이 있으면 `이름-2`, `이름-3` 식으로 넘버링한다.
|
||||||
|
|||||||
@@ -1,5 +1,13 @@
|
|||||||
# 업데이트 이력
|
# 업데이트 이력
|
||||||
|
|
||||||
|
## v1.1.8
|
||||||
|
|
||||||
|
- 사이트 로고·파비콘 고유 파일명 접미사를 년월+랜덤 문자열 형식으로 간소화.
|
||||||
|
- 관리자 태그 관리 화면의 `태그 추가` 버튼을 일반 태그 섹션 헤더 오른쪽으로 이동.
|
||||||
|
- 메인 태그 `정렬 저장` 버튼을 제거하고 드래그 드롭 직후 자동 저장되도록 수정.
|
||||||
|
- 메인 태그 순서 자동 저장 중 추가 드래그를 막고 저장 상태를 표시하도록 정리.
|
||||||
|
- 패키지 버전 `1.1.8`로 갱신.
|
||||||
|
|
||||||
## v1.1.7
|
## v1.1.7
|
||||||
|
|
||||||
- 사이트 로고 업로드가 고정 `/uploads/system/logo.webp` 덮어쓰기 대신 고유 파일명 URL을 저장하도록 수정.
|
- 사이트 로고 업로드가 고정 `/uploads/system/logo.webp` 덮어쓰기 대신 고유 파일명 URL을 저장하도록 수정.
|
||||||
|
|||||||
4
package-lock.json
generated
4
package-lock.json
generated
@@ -1,12 +1,12 @@
|
|||||||
{
|
{
|
||||||
"name": "sori.studio",
|
"name": "sori.studio",
|
||||||
"version": "1.1.7",
|
"version": "1.1.8",
|
||||||
"lockfileVersion": 3,
|
"lockfileVersion": 3,
|
||||||
"requires": true,
|
"requires": true,
|
||||||
"packages": {
|
"packages": {
|
||||||
"": {
|
"": {
|
||||||
"name": "sori.studio",
|
"name": "sori.studio",
|
||||||
"version": "1.1.7",
|
"version": "1.1.8",
|
||||||
"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.1.7",
|
"version": "1.1.8",
|
||||||
"private": true,
|
"private": true,
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"imports": {
|
"imports": {
|
||||||
|
|||||||
@@ -54,7 +54,7 @@ const filteredGeneralTags = computed(() => {
|
|||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
|
||||||
/** 서버 기준 메인 태그 id 순서(정렬 저장 버튼 활성 비교용) */
|
/** 서버 기준 메인 태그 id 순서(자동 저장 필요 여부 비교용) */
|
||||||
const baselineManagedTagIds = ref([])
|
const baselineManagedTagIds = ref([])
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -77,7 +77,7 @@ const refreshTagsFromServer = async () => {
|
|||||||
resetManagedOrderBaseline()
|
resetManagedOrderBaseline()
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 메인 태그 드래그 순서가 기준선과 다른지 여부
|
* 메인 태그 드래그 순서가 서버 기준선과 다른지 여부
|
||||||
* @returns {boolean} 변경 여부
|
* @returns {boolean} 변경 여부
|
||||||
*/
|
*/
|
||||||
const isManagedOrderDirty = computed(() => {
|
const isManagedOrderDirty = computed(() => {
|
||||||
@@ -116,6 +116,11 @@ const showToast = (type, message) => {
|
|||||||
* @returns {void}
|
* @returns {void}
|
||||||
*/
|
*/
|
||||||
const handleDragStart = (event, tagId) => {
|
const handleDragStart = (event, tagId) => {
|
||||||
|
if (savingOrder.value) {
|
||||||
|
event.preventDefault()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
if (!event.dataTransfer) {
|
if (!event.dataTransfer) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -130,6 +135,10 @@ const handleDragStart = (event, tagId) => {
|
|||||||
* @returns {void}
|
* @returns {void}
|
||||||
*/
|
*/
|
||||||
const handleDragOver = (event, tagId) => {
|
const handleDragOver = (event, tagId) => {
|
||||||
|
if (savingOrder.value) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
event.preventDefault()
|
event.preventDefault()
|
||||||
dragOverTagId.value = tagId
|
dragOverTagId.value = tagId
|
||||||
}
|
}
|
||||||
@@ -172,15 +181,17 @@ const moveManagedTag = (sourceId, targetId) => {
|
|||||||
* 관리용 태그 드롭 처리
|
* 관리용 태그 드롭 처리
|
||||||
* @param {DragEvent} event - 드래그 이벤트
|
* @param {DragEvent} event - 드래그 이벤트
|
||||||
* @param {string} targetId - 대상 태그 ID
|
* @param {string} targetId - 대상 태그 ID
|
||||||
* @returns {void}
|
* @returns {Promise<void>}
|
||||||
*/
|
*/
|
||||||
const handleDrop = (event, targetId) => {
|
const handleDrop = async (event, targetId) => {
|
||||||
event.preventDefault()
|
event.preventDefault()
|
||||||
if (!draggingTagId.value) {
|
if (!draggingTagId.value || savingOrder.value) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
moveManagedTag(draggingTagId.value, targetId)
|
moveManagedTag(draggingTagId.value, targetId)
|
||||||
handleDragEnd()
|
handleDragEnd()
|
||||||
|
await nextTick()
|
||||||
|
await saveManagedOrder()
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -204,9 +215,10 @@ const saveManagedOrder = async () => {
|
|||||||
|
|
||||||
tags.value = [...reordered]
|
tags.value = [...reordered]
|
||||||
await refreshTagsFromServer()
|
await refreshTagsFromServer()
|
||||||
showToast('success', '메인 태그 순서가 저장되었습니다.')
|
showToast('success', '메인 태그 순서가 자동 저장되었습니다.')
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
showToast('error', error?.data?.message || '정렬 순서를 저장하지 못했습니다.')
|
showToast('error', error?.data?.message || '정렬 순서를 저장하지 못했습니다.')
|
||||||
|
await refreshTagsFromServer()
|
||||||
} finally {
|
} finally {
|
||||||
savingOrder.value = false
|
savingOrder.value = false
|
||||||
}
|
}
|
||||||
@@ -328,7 +340,7 @@ onBeforeUnmount(() => {
|
|||||||
|
|
||||||
<template>
|
<template>
|
||||||
<section class="admin-tags bg-paper p-6">
|
<section class="admin-tags bg-paper p-6">
|
||||||
<div class="admin-tags__header flex items-center justify-between gap-4">
|
<div class="admin-tags__header">
|
||||||
<div>
|
<div>
|
||||||
<p class="admin-tags__eyebrow text-xs font-semibold uppercase text-muted">
|
<p class="admin-tags__eyebrow text-xs font-semibold uppercase text-muted">
|
||||||
Tags
|
Tags
|
||||||
@@ -337,9 +349,6 @@ onBeforeUnmount(() => {
|
|||||||
태그 관리
|
태그 관리
|
||||||
</h1>
|
</h1>
|
||||||
</div>
|
</div>
|
||||||
<NuxtLink class="admin-tags__new rounded bg-[#15171a] px-4 py-2 text-sm font-semibold text-white" to="/admin/tags/new">
|
|
||||||
태그 추가
|
|
||||||
</NuxtLink>
|
|
||||||
</div>
|
</div>
|
||||||
<p class="mt-3 text-xs text-muted">
|
<p class="mt-3 text-xs text-muted">
|
||||||
메인 태그는 홈페이지 카테고리 영역에서 사용되며 드래그 정렬이 가능합니다. 일반 태그는 아래 목록에서 확인하고 필요할 때 메인 태그로 전환할 수 있습니다.
|
메인 태그는 홈페이지 카테고리 영역에서 사용되며 드래그 정렬이 가능합니다. 일반 태그는 아래 목록에서 확인하고 필요할 때 메인 태그로 전환할 수 있습니다.
|
||||||
@@ -348,14 +357,10 @@ onBeforeUnmount(() => {
|
|||||||
<div class="admin-tags__table mt-6 overflow-hidden border border-line">
|
<div class="admin-tags__table mt-6 overflow-hidden border border-line">
|
||||||
<div class="flex items-center justify-between border-b border-line bg-[#f7f7f5] px-4 py-2.5">
|
<div class="flex items-center justify-between border-b border-line bg-[#f7f7f5] px-4 py-2.5">
|
||||||
<p class="text-xs font-semibold uppercase text-muted">메인 태그</p>
|
<p class="text-xs font-semibold uppercase text-muted">메인 태그</p>
|
||||||
<button
|
<span v-if="savingOrder" class="inline-flex items-center gap-2 text-xs font-semibold text-muted">
|
||||||
class="rounded border border-line bg-white px-3 py-1.5 text-xs font-semibold disabled:opacity-50"
|
<span class="size-3 animate-spin rounded-full border-2 border-line border-t-[#15171a]" />
|
||||||
type="button"
|
저장 중
|
||||||
:disabled="savingOrder || managedTags.length === 0 || !isManagedOrderDirty"
|
</span>
|
||||||
@click="saveManagedOrder"
|
|
||||||
>
|
|
||||||
{{ savingOrder ? '저장 중' : '정렬 저장' }}
|
|
||||||
</button>
|
|
||||||
</div>
|
</div>
|
||||||
<table class="admin-tags__table-inner w-full border-collapse text-left text-sm">
|
<table class="admin-tags__table-inner w-full border-collapse text-left text-sm">
|
||||||
<thead class="admin-tags__table-head bg-[#f5f5f2] text-xs uppercase text-muted">
|
<thead class="admin-tags__table-head bg-[#f5f5f2] text-xs uppercase text-muted">
|
||||||
@@ -372,12 +377,13 @@ onBeforeUnmount(() => {
|
|||||||
<tr
|
<tr
|
||||||
v-for="(tag, index) in managedTags"
|
v-for="(tag, index) in managedTags"
|
||||||
:key="tag.id"
|
:key="tag.id"
|
||||||
class="admin-tags__row cursor-move"
|
class="admin-tags__row"
|
||||||
:class="[
|
:class="[
|
||||||
dragOverTagId === tag.id ? 'bg-[#f9f9f7]' : '',
|
dragOverTagId === tag.id ? 'bg-[#f9f9f7]' : '',
|
||||||
draggingTagId === tag.id ? 'opacity-50' : ''
|
draggingTagId === tag.id ? 'opacity-50' : '',
|
||||||
|
savingOrder ? 'cursor-not-allowed opacity-60' : 'cursor-move'
|
||||||
]"
|
]"
|
||||||
draggable="true"
|
:draggable="!savingOrder"
|
||||||
@dragstart="handleDragStart($event, tag.id)"
|
@dragstart="handleDragStart($event, tag.id)"
|
||||||
@dragover="handleDragOver($event, tag.id)"
|
@dragover="handleDragOver($event, tag.id)"
|
||||||
@drop="handleDrop($event, tag.id)"
|
@drop="handleDrop($event, tag.id)"
|
||||||
@@ -422,8 +428,11 @@ onBeforeUnmount(() => {
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="admin-tags__table mt-8 overflow-hidden border border-line">
|
<div class="admin-tags__table mt-8 overflow-hidden border border-line">
|
||||||
<div class="border-b border-line bg-[#f7f7f5] px-4 py-2.5">
|
<div class="flex items-center justify-between gap-3 border-b border-line bg-[#f7f7f5] px-4 py-2.5">
|
||||||
<p class="text-xs font-semibold uppercase text-muted">일반 태그</p>
|
<p class="text-xs font-semibold uppercase text-muted">일반 태그</p>
|
||||||
|
<NuxtLink class="admin-tags__new rounded bg-[#15171a] px-3 py-1.5 text-xs font-semibold text-white" to="/admin/tags/new">
|
||||||
|
태그 추가
|
||||||
|
</NuxtLink>
|
||||||
</div>
|
</div>
|
||||||
<div class="space-y-3 bg-white p-4">
|
<div class="space-y-3 bg-white p-4">
|
||||||
<div class="flex flex-col gap-3 xl:flex-row xl:items-center xl:justify-between">
|
<div class="flex flex-col gap-3 xl:flex-row xl:items-center xl:justify-between">
|
||||||
|
|||||||
@@ -35,9 +35,13 @@ const clampNumber = (value, minimum, maximum) => {
|
|||||||
* 시스템 로고 파일명에 사용할 짧은 고유 접미사를 만든다.
|
* 시스템 로고 파일명에 사용할 짧은 고유 접미사를 만든다.
|
||||||
* @returns {string} 파일명 접미사
|
* @returns {string} 파일명 접미사
|
||||||
*/
|
*/
|
||||||
const createSystemAssetSuffix = () => `${new Date().toISOString()
|
const createSystemAssetSuffix = () => {
|
||||||
.replace(/[-:]/g, '')
|
const now = new Date()
|
||||||
.replace(/\.\d{3}Z$/g, '')}-${Math.random().toString(36).slice(2, 8)}`
|
const year = now.getFullYear()
|
||||||
|
const month = String(now.getMonth() + 1).padStart(2, '0')
|
||||||
|
|
||||||
|
return `${year}${month}-${Math.random().toString(36).slice(2, 8)}`
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 사이트 로고 업로드 API
|
* 사이트 로고 업로드 API
|
||||||
|
|||||||
Reference in New Issue
Block a user