Files
PM_test/views/main/composition-tab.html
2026-06-12 17:14:03 +09:00

444 lines
16 KiB
HTML

<!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>