초기 PM 소스 전체 업로드
This commit is contained in:
444
views/main/composition-tab.html
Normal file
444
views/main/composition-tab.html
Normal file
@@ -0,0 +1,444 @@
|
||||
|
||||
<!DOCTYPE html>
|
||||
<html lang="ko">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>구성</title>
|
||||
<link rel="stylesheet" href="/main/css/reset.css" />
|
||||
|
||||
<style>
|
||||
@import url("https://cdn.jsdelivr.net/gh/orioncactus/pretendard@v1.3.9/dist/web/static/pretendard.min.css");
|
||||
|
||||
* {
|
||||
font-family: 'Pretendard Variable', 'Pretendard';
|
||||
font-size: 1rem;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
html, body {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
margin: 0;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.composition-modal{
|
||||
display: flex;
|
||||
position: fixed;
|
||||
inset: 0;
|
||||
width: 100vw;
|
||||
height: 100vh;
|
||||
background: transparent;
|
||||
align-items: stretch;
|
||||
justify-content: stretch;
|
||||
}
|
||||
|
||||
.composition-modal .modal-wrap{
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
max-width: none;
|
||||
max-height: none;
|
||||
margin: 0;
|
||||
border-radius: 0;
|
||||
border: 0;
|
||||
overflow: hidden;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.composition-modal .modal-wrap h3 {
|
||||
font-size: 0.875rem;
|
||||
font-weight: 500;
|
||||
line-height: 1.25rem;
|
||||
letter-spacing: -0.0175rem;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.composition-modal .modal-wrap h5 {
|
||||
font-size: 0.75rem;
|
||||
font-weight: 500;
|
||||
line-height: 1.25rem;
|
||||
letter-spacing: -0.0175rem;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.composition-modal .modal-wrap h6 {
|
||||
font-size: 0.75rem;
|
||||
font-weight: 300;
|
||||
line-height: 1.25rem;
|
||||
letter-spacing: -0.0175rem;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.composition-modal .modal-wrap .head {
|
||||
display: flex;
|
||||
padding: 0.5rem 0.5rem 0.5rem 1rem;
|
||||
border-bottom: 0.0625rem solid #ddd;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
background: #fff;
|
||||
flex: 0 0 auto;
|
||||
}
|
||||
|
||||
.composition-modal .modal-wrap .head i {
|
||||
min-width: 1rem;
|
||||
min-height: 1rem;
|
||||
background: url(/main/img/archive/close.svg) no-repeat center / contain;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.composition-modal .modal-wrap > ul {
|
||||
display: flex;
|
||||
flex: 1 1 auto;
|
||||
overflow: auto;
|
||||
line-height: 1.25rem;
|
||||
letter-spacing: -0.0175rem;
|
||||
}
|
||||
|
||||
.composition-modal .modal-wrap > ul > li {
|
||||
padding: 0.5rem 1rem 0 1rem;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 0.25rem;
|
||||
color: var(--primary-lv-6, #1E5149);
|
||||
min-width: 14.5rem;
|
||||
max-width: 14.5rem;
|
||||
border-right: 0.0625rem solid #eee;
|
||||
}
|
||||
|
||||
.composition-modal .modal-wrap > ul > li:last-child {
|
||||
padding-right: 0.5rem;
|
||||
}
|
||||
|
||||
.composition-modal .modal-wrap > ul > li > ul {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 0.5rem;
|
||||
}
|
||||
|
||||
.composition-modal .modal-wrap > ul > li > ul > li {
|
||||
border-bottom: 0.0625rem solid var(--primary-lv-0, #E9EEED);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 0.25rem;
|
||||
}
|
||||
|
||||
.composition-modal .modal-wrap > ul > li > ul > li:last-child {
|
||||
padding-bottom: 0.5rem;
|
||||
border: none;
|
||||
}
|
||||
|
||||
.composition-modal .modal-wrap > ul > li > ul > li > ul {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 0.25rem;
|
||||
color: #111;
|
||||
padding-bottom: 0.25rem;
|
||||
}
|
||||
|
||||
.composition-modal .modal-wrap > ul > li > ul > li > ul > li {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.25rem;
|
||||
}
|
||||
|
||||
.composition-modal .modal-wrap > ul > li > ul > li > ul > li > i {
|
||||
min-width: 1rem;
|
||||
min-height: 1rem;
|
||||
background: url(/main/img/archive/folder-bullet.svg) no-repeat center / contain;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.composition-modal .modal-wrap > ul > li > ul > li > ul > li > h6:last-child {
|
||||
color: var(--primary-lv-3, #789792);
|
||||
margin-left: auto;
|
||||
min-width: 1.625rem;
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
.composition-modal .modal-wrap > ul > li:nth-child(even) {
|
||||
background-color: #f6f8f8;
|
||||
}
|
||||
|
||||
._scrollbar {
|
||||
scrollbar-width: thin;
|
||||
scrollbar-color: #888 #f1f1f1;
|
||||
}
|
||||
|
||||
._scrollbar::-webkit-scrollbar { height: 8px; width: 8px; }
|
||||
._scrollbar::-webkit-scrollbar-track { background: #f1f1f1; }
|
||||
._scrollbar::-webkit-scrollbar-thumb { background: #888; border-radius: 4px; }
|
||||
._scrollbar::-webkit-scrollbar-thumb:hover { background: #555; }
|
||||
|
||||
.loading {
|
||||
padding: 1rem;
|
||||
color: #666;
|
||||
}
|
||||
|
||||
/*hover*/
|
||||
.composition-modal .modal-wrap > ul > li > h3:hover,
|
||||
.composition-modal .modal-wrap > ul > li > ul > li > ul > li:hover {
|
||||
background-color: rgba(30, 81, 73, 0.08);
|
||||
transition: background-color 0.2s;
|
||||
}
|
||||
|
||||
.composition-modal .modal-wrap > ul > li > ul > li > ul > li:hover h6 {
|
||||
color: #1E5149;
|
||||
font-weight: 500;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<article class="composition-modal">
|
||||
<div class="modal-wrap">
|
||||
<div class="head">
|
||||
<h3>구성</h3>
|
||||
<i id="btnClose"></i>
|
||||
</div>
|
||||
|
||||
<ul class="_scrollbar" id="composition-list">
|
||||
<li class="loading">데이터를 불러오는 중...</li>
|
||||
</ul>
|
||||
</div>
|
||||
</article>
|
||||
|
||||
<script src="https://cdn.jsdelivr.net/npm/axios/dist/axios.min.js"></script>
|
||||
<script>
|
||||
// 탭 닫기
|
||||
document.getElementById('btnClose')?.addEventListener('click', () => window.close());
|
||||
|
||||
let userInfoString, storageType, allTreeObject, path_name;
|
||||
let isDataReceived = false;
|
||||
|
||||
// 부모 창으로부터 데이터 수신
|
||||
window.addEventListener('message', (event) => {
|
||||
|
||||
if (event.origin !== window.location.origin) return;
|
||||
|
||||
if (event.data.type === 'INIT_DATA') {
|
||||
const { data } = event.data;
|
||||
|
||||
userInfoString = data.userInfoString;
|
||||
storageType = data.storageType;
|
||||
allTreeObject = data.allTreeObject;
|
||||
path_name = data.path_name;
|
||||
|
||||
isDataReceived = true;
|
||||
|
||||
// 데이터 수신 후 렌더링
|
||||
renderCompositionData();
|
||||
}
|
||||
});
|
||||
|
||||
// 정렬 함수
|
||||
function naturalSort(a, b) {
|
||||
const regex = /(\d+)|(\D+)/g;
|
||||
const aParts = String(a).match(regex) || [];
|
||||
const bParts = String(b).match(regex) || [];
|
||||
|
||||
for (let i = 0; i < Math.min(aParts.length, bParts.length); i++) {
|
||||
const aPart = aParts[i];
|
||||
const bPart = bParts[i];
|
||||
|
||||
const aNum = Number(aPart);
|
||||
const bNum = Number(bPart);
|
||||
|
||||
if (!Number.isNaN(aNum) && !Number.isNaN(bNum)) {
|
||||
const diff = aNum - bNum;
|
||||
if (diff !== 0) return diff;
|
||||
} else {
|
||||
if (aPart !== bPart) return aPart.localeCompare(bPart);
|
||||
}
|
||||
}
|
||||
return aParts.length - bParts.length;
|
||||
}
|
||||
|
||||
// 구성 데이터 렌더링
|
||||
async function renderCompositionData() {
|
||||
const listWrap = document.getElementById('composition-list');
|
||||
listWrap.innerHTML = '';
|
||||
|
||||
if (!allTreeObject || !userInfoString) {
|
||||
listWrap.innerHTML = '<li class="loading">데이터를 불러올 수 없습니다.</li>';
|
||||
return;
|
||||
}
|
||||
|
||||
const user = JSON.parse(userInfoString);
|
||||
const tabList = Object.keys(allTreeObject.folder || {}).sort(naturalSort);
|
||||
|
||||
for (const tab of tabList) {
|
||||
const getTreeObjectParams = {
|
||||
userInfoString: JSON.stringify(user),
|
||||
storageType: storageType,
|
||||
resourcePath: tab
|
||||
};
|
||||
|
||||
try {
|
||||
const getTreeObjectRes = await axios.get(`${path_name}/getTreeObject`, {
|
||||
params: { params: getTreeObjectParams }
|
||||
});
|
||||
|
||||
if (getTreeObjectRes.data.message === 'getTreeObject_success') {
|
||||
const treeData = getTreeObjectRes.data.currentTreeObject;
|
||||
|
||||
const tabLi = document.createElement('li');
|
||||
|
||||
const tabTitle = document.createElement('h3');
|
||||
tabTitle.textContent = tab;
|
||||
tabTitle.style.cursor = 'pointer';
|
||||
// 탭 클릭 시 해당 탭으로 이동
|
||||
tabTitle.addEventListener('click', () => {
|
||||
navigateToPath(tab);
|
||||
});
|
||||
tabLi.appendChild(tabTitle);
|
||||
|
||||
const categoryUl = document.createElement('ul');
|
||||
|
||||
if (treeData && treeData.folder && Object.keys(treeData.folder).length > 0) {
|
||||
const categories = Object.keys(treeData.folder).sort(naturalSort);
|
||||
|
||||
categories.forEach(category => {
|
||||
const categoryLi = document.createElement('li');
|
||||
|
||||
const categoryTitle = document.createElement('h5');
|
||||
categoryTitle.textContent = category;
|
||||
categoryLi.appendChild(categoryTitle);
|
||||
|
||||
const folderUl = document.createElement('ul');
|
||||
|
||||
const folders = treeData.folder[category] || {};
|
||||
const folderObj = folders.child?.folder || {};
|
||||
const folderKeys = Object.keys(folderObj).sort(naturalSort);
|
||||
|
||||
if (folderKeys.length === 0) {
|
||||
// 빈 폴더 처리
|
||||
} else {
|
||||
folderKeys.forEach(folderName => {
|
||||
const folderData = folderObj[folderName];
|
||||
let fileCount = 0;
|
||||
|
||||
fileCount += Object.keys(folderData.child?.file || {}).length;
|
||||
|
||||
if (folderData.child?.folder) {
|
||||
Object.keys(folderData.child.folder).forEach(subFolderName => {
|
||||
const subFolder = folderData.child.folder[subFolderName];
|
||||
if (subFolder.child?.file) {
|
||||
fileCount += Object.keys(subFolder.child.file).length;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
const folderLi = document.createElement('li');
|
||||
folderLi.style.cursor = 'pointer';
|
||||
|
||||
// 폴더 전체 클릭 가능하도록
|
||||
folderLi.addEventListener('click', () => {
|
||||
navigateToPath(`${tab}/${category}/${folderName}`);
|
||||
});
|
||||
|
||||
const folderIcon = document.createElement('i');
|
||||
folderLi.appendChild(folderIcon);
|
||||
|
||||
const folderNameEl = document.createElement('h6');
|
||||
folderNameEl.textContent = folderName;
|
||||
folderLi.appendChild(folderNameEl);
|
||||
|
||||
const folderCountEl = document.createElement('h6');
|
||||
folderCountEl.textContent = fileCount > 0 ? String(fileCount) : '-';
|
||||
folderLi.appendChild(folderCountEl);
|
||||
|
||||
folderUl.appendChild(folderLi);
|
||||
});
|
||||
}
|
||||
|
||||
categoryLi.appendChild(folderUl);
|
||||
categoryUl.appendChild(categoryLi);
|
||||
});
|
||||
} else {
|
||||
// 탭은 있는데 폴더가 비어있는 경우
|
||||
const emptyCategoryLi = document.createElement('li');
|
||||
const emptyCategoryTitle = document.createElement('h5');
|
||||
emptyCategoryTitle.textContent = '-';
|
||||
emptyCategoryLi.appendChild(emptyCategoryTitle);
|
||||
|
||||
const emptyFolderUl = document.createElement('ul');
|
||||
const emptyFolderLi = document.createElement('li');
|
||||
|
||||
const emptyIcon = document.createElement('i');
|
||||
emptyFolderLi.appendChild(emptyIcon);
|
||||
|
||||
const emptyName = document.createElement('h6');
|
||||
emptyName.textContent = '-';
|
||||
emptyFolderLi.appendChild(emptyName);
|
||||
|
||||
const emptyCount = document.createElement('h6');
|
||||
emptyCount.textContent = '-';
|
||||
emptyFolderLi.appendChild(emptyCount);
|
||||
|
||||
emptyFolderUl.appendChild(emptyFolderLi);
|
||||
emptyCategoryLi.appendChild(emptyFolderUl);
|
||||
categoryUl.appendChild(emptyCategoryLi);
|
||||
}
|
||||
|
||||
tabLi.appendChild(categoryUl);
|
||||
listWrap.appendChild(tabLi);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('데이터 로드 오류:', error);
|
||||
}
|
||||
}
|
||||
|
||||
// 높이 동기화
|
||||
setTimeout(() => {
|
||||
const allLis = listWrap.querySelectorAll(':scope > li');
|
||||
if (allLis.length === 0) return;
|
||||
|
||||
let maxHeight = 0;
|
||||
allLis.forEach(li => {
|
||||
const height = li.scrollHeight;
|
||||
if (height > maxHeight) maxHeight = height;
|
||||
});
|
||||
|
||||
allLis.forEach(li => {
|
||||
li.style.minHeight = `${maxHeight}px`;
|
||||
});
|
||||
}, 0);
|
||||
}
|
||||
|
||||
// 원본 탭에 경로 이동 요청
|
||||
function navigateToPath(path) {
|
||||
if (!window.opener || window.opener.closed) {
|
||||
alert('원본 페이지를 찾을 수 없습니다.');
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
// postMessage로 경로 이동 요청 전송
|
||||
window.opener.postMessage({
|
||||
type: 'NAVIGATE_TO_PATH',
|
||||
path: path
|
||||
}, window.location.origin);
|
||||
|
||||
} catch (error) {
|
||||
console.error('페이지 이동 오류:', error);
|
||||
alert('페이지 이동 중 오류가 발생했습니다.');
|
||||
}
|
||||
}
|
||||
|
||||
// 페이지 로드 시 데이터가 아직 수신되지 않았다면 대기
|
||||
window.addEventListener('load', () => {
|
||||
// 일정 시간 후에도 데이터가 수신되지 않으면 에러 메시지 표시
|
||||
setTimeout(() => {
|
||||
if (!isDataReceived) {
|
||||
document.getElementById('composition-list').innerHTML =
|
||||
'<li class="loading">데이터를 불러올 수 없습니다. 페이지를 새로고침해주세요.</li>';
|
||||
}
|
||||
}, 3000);
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
Reference in New Issue
Block a user