polarvo-layout 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (60) hide show
  1. package/README.md +70 -0
  2. package/package.json +41 -0
  3. package/src/components/FastMenu/DesignFastMenu.vue +83 -0
  4. package/src/components/FastMenu/DisplayFastMenu.vue +65 -0
  5. package/src/components/FastMenu/HistoryFastMenu.vue +12 -0
  6. package/src/components/FastMenu/LayoutFastMenu.vue +98 -0
  7. package/src/components/IconBar/IconBar.vue +220 -0
  8. package/src/components/Layout/BaseLayout.vue +119 -0
  9. package/src/components/Layout/CanvasContainer.vue +45 -0
  10. package/src/components/Layout/FreeLayout.vue +246 -0
  11. package/src/components/Layout/GridLayout.vue +146 -0
  12. package/src/components/Layout/PolarLayout.vue +5 -0
  13. package/src/components/MiniMenu/LayoutMiniMenu.vue +62 -0
  14. package/src/components/SideBar/ElementSideBar.vue +76 -0
  15. package/src/components/SideBar/LayoutSettingSideBar.vue +90 -0
  16. package/src/components/SideBar/LayoutSideBar.vue +89 -0
  17. package/src/configs/index.js +23 -0
  18. package/src/configs/resources/displayResource.js +27 -0
  19. package/src/configs/resources/dropResource.js +23 -0
  20. package/src/configs/resources/elementResource.js +54 -0
  21. package/src/configs/resources/index.js +10 -0
  22. package/src/configs/resources/layoutResource.js +82 -0
  23. package/src/core/engines/DisplayEngine.js +171 -0
  24. package/src/core/engines/FreeDropEngine.js +701 -0
  25. package/src/core/engines/GridDropEngine.js +461 -0
  26. package/src/core/engines/HistoryEngine.js +69 -0
  27. package/src/core/engines/LayoutEngine.js +240 -0
  28. package/src/core/managers/ApiManager.js +21 -0
  29. package/src/core/managers/EngineManager.js +648 -0
  30. package/src/core/managers/EventBus.js +277 -0
  31. package/src/icons/action/LockIcon.vue +52 -0
  32. package/src/icons/action/RedoIcon.vue +16 -0
  33. package/src/icons/action/UndoIcon.vue +16 -0
  34. package/src/icons/action/UnlockIcon.vue +52 -0
  35. package/src/icons/display/DesktopIcon.vue +18 -0
  36. package/src/icons/display/MobileIcon.vue +16 -0
  37. package/src/icons/display/TabletIcon.vue +16 -0
  38. package/src/icons/history/RedoIcon.vue +16 -0
  39. package/src/icons/history/UndoIcon.vue +16 -0
  40. package/src/icons/layout/LeftRightSplitIcon.vue +6 -0
  41. package/src/icons/layout/MainBottomSplitIcon.vue +7 -0
  42. package/src/icons/layout/MainTopSplitIcon.vue +7 -0
  43. package/src/icons/layout/NoSplitIcon.vue +5 -0
  44. package/src/icons/layout/ThreeColumnSplitIcon.vue +7 -0
  45. package/src/icons/layout/ThreeRowSplitIcon.vue +7 -0
  46. package/src/icons/layout/TopBottomSplitIcon.vue +6 -0
  47. package/src/icons/menu/AddIcon.vue +23 -0
  48. package/src/icons/menu/LayoutIcon.vue +17 -0
  49. package/src/icons/menu/ThemeIcon.vue +21 -0
  50. package/src/index.js +32 -0
  51. package/src/library/DisplayLibrary.js +122 -0
  52. package/src/library/FreeDropLibrary.js +152 -0
  53. package/src/library/GridDropLibrary.js +151 -0
  54. package/src/library/HistoryLibrary.js +61 -0
  55. package/src/library/LayoutLibrary.js +199 -0
  56. package/src/library/index.js +50 -0
  57. package/src/styles.scss +5 -0
  58. package/src/utils/directives/click-outside.js +14 -0
  59. package/src/utils/directives/index.js +1 -0
  60. package/src/utils/index.js +1 -0
package/README.md ADDED
@@ -0,0 +1,70 @@
1
+ <!-- 폴라보 사용 레이아웃 라이브러리 -->
2
+ * components 폴더: 컴포넌트
3
+ * core 폴더: 핵심 로직
4
+ * utils 폴더: 기타 로직
5
+
6
+ # 로컬에서 라이브러리 테스트하기
7
+ 1) 라이브러리(polavo-layout)에서 ```npm link```
8
+ 2) 라이브러리를 사용하려는 프로젝트에서 ```npm link polavo-layout```
9
+
10
+ # 폴더구조
11
+ src/
12
+ ├── index.js # 메인 엔트리 포인트
13
+ ├── core/
14
+ │ ├── engines/ # 엔진별 핵심 로직
15
+ │ │ ├── LayoutEngine.js
16
+ │ │ ├── DisplayEngine.js
17
+ │ │ ├── FreeDropEngine.js
18
+ │ │ └── GridDropEngine.js
19
+ │ │ └── HistoryEngine.js
20
+ │ ├── managers/ # 엔진 관리 및 통신
21
+ │ │ ├── EngineManager.js
22
+ │ │ ├── ApiManager.js
23
+ │ │ └── EventBus.js
24
+ ├── library/ # 반응형 래퍼
25
+ │ ├── LayoutLibrary.js
26
+ │ ├── DisplayLibrary.js
27
+ │ ├── FreeDropLibrary.js
28
+ │ ├── GridDropLibrary.js
29
+ │ ├── HistoryLibrary.js
30
+ │ └── index.js
31
+ ├── configs/ # 설정
32
+ │ ├── index.js
33
+ │ ├── resources/ # 기본 설정
34
+ │ │ ├── layoutResource.js
35
+ │ │ ├── displayResource.js
36
+ │ │ ├── elementResource.js
37
+ │ │ ├── dropResource.js
38
+ │ │ └── index.js
39
+ ├── components/ # vue 컴포넌트
40
+ │ ├── Layout/
41
+ │ │ ├── CanvasContainer.vue
42
+ │ │ ├── PolarLayout.vue
43
+ │ │ ├── BaseLayout.vue
44
+ │ │ ├── FreeLayout.vue
45
+ │ │ └── GridLayout.vue
46
+ │ ├── IconBar/
47
+ │ │ └── IconBar.vue
48
+ │ ├── SideBar/
49
+ │ │ ├── ElementSideBar.vue
50
+ │ │ ├── LayoutSideBar.vue
51
+ │ │ └── LayoutSettingSideBar.vue
52
+ │ ├── FastMenu/
53
+ │ │ ├── LayoutFastMenu.vue
54
+ │ │ ├── DisplayFastMenu.vue
55
+ │ │ ├── DesignFastMenu.vue
56
+ │ │ └── HistoryFastMenu.vue
57
+ │ └── MiniMenu/
58
+ │ │ └── LayoutMiniMenu.vue
59
+ ├── utils/ # 유틸리티
60
+ │ ├── directives/
61
+ │ │ ├── click-outside.js
62
+ │ │ └── index.js
63
+ │ └── injex.js
64
+ ├── icons/ # 아이콘
65
+ │ ├── layout/
66
+ │ ├── menu/
67
+ │ ├── display/
68
+ │ ├── action/
69
+ │ └── history/
70
+ └── styles.scss
package/package.json ADDED
@@ -0,0 +1,41 @@
1
+ {
2
+ "name": "polarvo-layout",
3
+ "version": "1.0.0",
4
+ "type": "module",
5
+ "author": "unigence <unigencelab@gmail.com>",
6
+ "repository": {
7
+ "type": "git",
8
+ "url": "https://github.com/unigenceLab/polavo-layout.git"
9
+ },
10
+ "sideEffects": [
11
+ "**/*.css"
12
+ ],
13
+ "exports": {
14
+ ".": "./src/index.js",
15
+ "./styles.scss": "./src/styles.scss"
16
+ },
17
+ "files": [
18
+ "src"
19
+ ],
20
+ "peerDependencies": {
21
+ "@fortawesome/fontawesome-svg-core": "^1.2.36",
22
+ "@fortawesome/free-solid-svg-icons": "^6.7.2",
23
+ "@fortawesome/vue-fontawesome": "^3.0.8",
24
+ "tailwindcss": "^3.4.17",
25
+ "vue": "^3.4.0"
26
+ },
27
+ "devDependencies": {
28
+ "@fortawesome/fontawesome-svg-core": "^1.2.36",
29
+ "@fortawesome/free-solid-svg-icons": "^6.7.2",
30
+ "@fortawesome/vue-fontawesome": "^3.0.8",
31
+ "tailwindcss": "^3.4.17"
32
+ },
33
+ "scripts": {
34
+ "pack:local": "npm pack"
35
+ },
36
+ "dependencies": {
37
+ "@popperjs/core": "^2.11.8",
38
+ "lodash-es": "^4.17.22",
39
+ "v-calendar": "^3.1.2"
40
+ }
41
+ }
@@ -0,0 +1,83 @@
1
+ <template>
2
+ <!-- editor - sub right -->
3
+ <div id="fast-menu" class="flex items-center gap-2">
4
+ <div
5
+ class="text-sm px-2 py-0.5 rounded-md cursor-pointer hover:opacity-80"
6
+ :class="{ 'bg-blue-50 text-blue-700 font-semibold': activeDesign === 'option' }"
7
+ @click="clickDesignBar('option')"
8
+ >
9
+ 옵션설정
10
+ </div>
11
+ <div
12
+ class="text-sm px-2 py-0.5 rounded-md cursor-pointer hover:opacity-80"
13
+ :class="{ 'bg-blue-50 text-blue-700 font-semibold': activeDesign === 'design' }"
14
+ @click="clickDesignBar('design')"
15
+ >
16
+ 디자인설정
17
+ </div>
18
+ <div class="text-sm px-2 py-0.5 rounded-md cursor-pointer hover:opacity-80">더보기</div>
19
+ </div>
20
+ </template>
21
+
22
+ <script setup>
23
+ import { toRefs, computed, inject, watch } from 'vue';
24
+
25
+ const props = defineProps({
26
+ polavo: {
27
+ type: Object,
28
+ required: true,
29
+ },
30
+ });
31
+ const { activeDesign } = toRefs(props.polavo.display.state);
32
+ const { setActiveDesign } = props.polavo.display;
33
+
34
+ const { activeData, activeSection } = toRefs(props.polavo.layout.state);
35
+ const sectionMode = computed(() => activeData.value?.mode);
36
+
37
+ const { activeElement: freeActiveElement } = toRefs(props.polavo.freeDrop.state);
38
+ const { activeElement: gridActiveElement } = toRefs(props.polavo.gridDrop.state);
39
+
40
+ const activeElement = computed(() => {
41
+ if (sectionMode.value === 'free') {
42
+ return freeActiveElement.value;
43
+ } else if (sectionMode.value === 'grid') {
44
+ return gridActiveElement.value;
45
+ }
46
+ return null;
47
+ });
48
+
49
+ const selectedElement = inject('selectedElement');
50
+ const selectedChildProps = inject('selectedChildProps');
51
+
52
+ // const emits = defineEmits(['click:designBar']);
53
+ function clickDesignBar(designMenu) {
54
+ if (designMenu == 'option') {
55
+ designMenu = activeElement.value?.type === 'inputForm' ? 'option-form' : 'option-default';
56
+ }
57
+
58
+ // selectedElement의 props 기본값 세팅
59
+ if (selectedElement.value) {
60
+ setComponentDefaults(selectedElement.value);
61
+ }
62
+
63
+ setActiveDesign(designMenu);
64
+ }
65
+
66
+
67
+ function setComponentDefaults(element) {
68
+ const child = props.polavo.components.getElement(activeSection.value, element.id);
69
+ if (!child) return;
70
+ selectedChildProps.value = Object.keys(child.$props);
71
+
72
+ //click한 컴포넌트의 props 객체 조회
73
+ const childProps = child.$options.props;
74
+
75
+ // props에 default 항목이 있는 경우, 함수인지 아닌지 확인 후 default 값 설정
76
+ Object.entries(childProps).forEach(([key, propInfo]) => {
77
+ if (propInfo.default !== undefined && (element[key] === undefined || element[key] === null)) {
78
+ // 함수인 경우 실행하여 기본값 설정
79
+ selectedElement.value[key] = typeof propInfo.default === 'function' ? propInfo.default() : propInfo.default;
80
+ }
81
+ });
82
+ }
83
+ </script>
@@ -0,0 +1,65 @@
1
+ <template>
2
+ <!-- editor - title left -->
3
+ <div id="fast-menu" class="flex items-center gap-4">
4
+ <div class="flex gap-2 text-gray-400">
5
+ <font-awesome-icon
6
+ :icon="['fas', 'display']"
7
+ class="cursor-pointer icon hover:opacity-70 active:text-blue-600"
8
+ :class="{ active: displayMode === 'desktop' }"
9
+ @click="setDisplayMode('desktop')"
10
+ />
11
+ <font-awesome-icon
12
+ :icon="['fas', 'mobile-screen']"
13
+ class="cursor-pointer icon hover:opacity-70 active:text-blue-600"
14
+ :class="{ active: displayMode === 'mobile' }"
15
+ @click="setDisplayMode('mobile')"
16
+ />
17
+ </div>
18
+ <div id="divider" class="w-px bg-gray-200 h-4"></div>
19
+ <div class="text-sm text-gray-400">
20
+ <input
21
+ type="number"
22
+ name="px"
23
+ aria-label="픽셀 단위 크기"
24
+ min="600"
25
+ max="2400"
26
+ step="50"
27
+ :value="displaySize.px"
28
+ class="border-b-2 pr-1 border-white focus:outline-none focus:border-b-2 focus:border-blue-500"
29
+ @change="setDisplaySize('px', $event.target.value)"
30
+ />
31
+ px /
32
+ <input
33
+ type="number"
34
+ name="percent"
35
+ aria-label="퍼센트 단위 크기"
36
+ min="0"
37
+ max="100"
38
+ step="10"
39
+ :value="displaySize.percent"
40
+ class="border-b-2 pr-1 border-white focus:outline-none focus:border-b-2 focus:border-blue-500"
41
+ @change="setDisplaySize('percent', $event.target.value)"
42
+ />
43
+ %
44
+ </div>
45
+ </div>
46
+ </template>
47
+
48
+ <script setup>
49
+ import { toRefs } from 'vue';
50
+
51
+ const props = defineProps({
52
+ polavo: {
53
+ type: Object,
54
+ required: true,
55
+ },
56
+ });
57
+ const { displayMode, displaySize } = toRefs(props.polavo.display.state);
58
+ const { setDisplayMode, setDisplaySize } = props.polavo.display;
59
+ </script>
60
+
61
+ <style scoped>
62
+ .icon.active {
63
+ color: #1e40af; /* text-blue-800 */
64
+ }
65
+ </style>
@@ -0,0 +1,12 @@
1
+ <template>
2
+ <!-- editor - title right -->
3
+ <div id="fast-menu" class="flex gap-2 text-gray-400">
4
+ <UndoIcon class="size-5 cursor-pointer hover:opacity-70 active:text-blue-600" />
5
+ <RedoIcon class="size-5 cursor-pointer hover:opacity-70 active:text-blue-600" />
6
+ </div>
7
+ </template>
8
+
9
+ <script setup>
10
+ import UndoIcon from '../../icons//history/UndoIcon.vue';
11
+ import RedoIcon from '../../icons/history/RedoIcon.vue';
12
+ </script>
@@ -0,0 +1,98 @@
1
+ <template>
2
+ <!-- editor - sub left -->
3
+ <div id="fast-menu" class="flex items-center gap-2" v-if="activeSection">
4
+ <div class="flex items-center gap-2 text-gray-400">
5
+ <div class="text-sm">{{ activeSection }}</div>
6
+ <button
7
+ class="mode text-xs border px-2 py-1 rounded-md hover:opacity-80"
8
+ :class="sectionMode"
9
+ @click="setSectionMode(sectionMode === 'grid' ? 'free' : 'grid')"
10
+ >
11
+ {{ sectionMode == 'grid' ? '그리드 모드' : '자유 모드' }}
12
+ </button>
13
+ </div>
14
+ <div id="divider" class="w-px bg-gray-200 h-4"></div>
15
+ <div>
16
+ <template v-if="sectionMode === 'free'">
17
+ <div class="flex items-center gap-2 text-gray-400">
18
+ <div class="text-sm">보조선 표시</div>
19
+ <input
20
+ type="checkbox"
21
+ :checked="activeData?.config?.showGuideLine"
22
+ @change="setSectionConfig('guideLine', $event.target.checked)"
23
+ />
24
+ </div>
25
+ </template>
26
+ <template v-else-if="sectionMode === 'grid'">
27
+ <div class="flex items-center gap-2 text-gray-400">
28
+ <div class="flex items-center gap-1">
29
+ <label class="text-sm" for="row-count">행 개수</label>
30
+ <input
31
+ type="number"
32
+ id="row-count"
33
+ min="1"
34
+ max="50"
35
+ class="text-sm text-center w-6 border-b-2 border-white focus:outline-none focus:border-b-2 focus:border-blue-500"
36
+ :value="activeData?.config?.gridRows"
37
+ @change="setSectionConfig('row', $event.target.value)"
38
+ />
39
+ </div>
40
+ <div class="flex items-center gap-1">
41
+ <label class="text-sm" for="column-count">열 개수</label>
42
+ <input
43
+ type="number"
44
+ id="column-count"
45
+ min="1"
46
+ max="12"
47
+ class="text-sm text-center w-6 border-b-2 border-white focus:outline-none focus:border-b-2 focus:border-blue-500"
48
+ :value="activeData?.config?.gridColumns"
49
+ @change="setSectionConfig('column', $event.target.value)"
50
+ />
51
+ </div>
52
+ <div class="flex items-center gap-1">
53
+ <label class="text-sm" for="gap-size">갭 크기</label>
54
+ <input
55
+ type="number"
56
+ id="gap-size"
57
+ min="0"
58
+ max="12"
59
+ class="text-sm text-center w-6 border-b-2 border-white focus:outline-none focus:border-b-2 focus:border-blue-500"
60
+ :value="gapSize"
61
+ @change="setGapSize($event.target.value)"
62
+ />
63
+ </div>
64
+ </div>
65
+ </template>
66
+ </div>
67
+ </div>
68
+ </template>
69
+
70
+ <script setup>
71
+ import { toRefs, computed } from 'vue';
72
+
73
+ const props = defineProps({
74
+ polavo: {
75
+ type: Object,
76
+ required: true,
77
+ },
78
+ });
79
+
80
+ const { activeSection, activeData, gapSize } = toRefs(props.polavo.layout.state);
81
+ const { setGapSize, setSectionMode, setSectionConfig } = props.polavo.layout;
82
+ const sectionMode = computed(() => activeData.value?.mode);
83
+ </script>
84
+
85
+ <style scoped lang="scss">
86
+ button.mode {
87
+ &.free {
88
+ background-color: #e0e7ff;
89
+ border-color: #6366f1;
90
+ color: #3730a3;
91
+ }
92
+ &.grid {
93
+ background-color: #d1fae5;
94
+ border-color: #10b981;
95
+ color: #065f46;
96
+ }
97
+ }
98
+ </style>
@@ -0,0 +1,220 @@
1
+ <template>
2
+ <aside id="icon-bar" class="flex flex-col items-center justify-between w-16 h-fit bg-white border-r border-gray-200">
3
+ <div
4
+ class="icon-wrapper logo border-b border-gray-200 text-2xl text-blue-800 p-0 w-full"
5
+ @click="openIconBar = !openIconBar"
6
+ >
7
+ <font-awesome-icon :icon="['fas', 'p']" class="h-16" />
8
+ </div>
9
+
10
+ <!-- 아이콘바 활성화 -->
11
+ <div class="h-[calc(100vh-61px)] flex flex-col justify-between" v-if="openIconBar">
12
+ <!-- 위쪽 아이콘 그룹 -->
13
+ <div class="top-side flex flex-col items-center gap-2 mt-4">
14
+ <div class="icon-group">
15
+ <div
16
+ class="icon-wrapper w-14 text-2xl text-gray-400 p-2"
17
+ title="프로젝트 관리"
18
+ :class="{ active: activeMenu === 'home' }"
19
+ @click="$router.push('/console/home')"
20
+ >
21
+ <font-awesome-icon :icon="['fas', 'folder']" />
22
+ <div class="text-xs text-center col-span-1 w-max">프로젝트</div>
23
+ </div>
24
+ </div>
25
+
26
+ <!-- 에디터 화면 아이콘 그룹 -->
27
+ <template v-if="isEditorPage">
28
+ <div id="divider" class="h-px bg-gray-200 w-8"></div>
29
+ <div class="icon-group">
30
+ <!-- 👂 내부설정에 테마설정 포함 -->
31
+ <div
32
+ class="icon-wrapper text-2xl text-gray-400 p-2 hover"
33
+ title="내부설정"
34
+ :class="{ active: activeMenu === 'projectSetting' }"
35
+ @click="polavo.display.setActiveMenu('projectSetting')"
36
+ >
37
+ <font-awesome-icon :icon="['fas', 'computer']" />
38
+ <div class="text-xs text-center col-span-1 w-max">내부설정</div>
39
+ </div>
40
+ <div
41
+ class="icon-wrapper text-2xl text-gray-400 p-2 hover"
42
+ title="DB관리"
43
+ :class="{ active: activeMenu === 'dbSetting' }"
44
+ @click="polavo.display.setActiveMenu('dbSetting')"
45
+ >
46
+ <font-awesome-icon :icon="['fas', 'database']" />
47
+ <div class="text-xs text-center col-span-1 w-max">DB관리</div>
48
+ </div>
49
+ <div
50
+ class="icon-wrapper text-2xl text-gray-400 p-2 hover"
51
+ title="화면관리"
52
+ :class="{ active: activeMenu === 'screenSetting' }"
53
+ @click="polavo.display.setActiveMenu('screenSetting')"
54
+ >
55
+ <font-awesome-icon :icon="['far', 'window-restore']" />
56
+ <div class="text-xs text-center col-span-1 w-max">화면관리</div>
57
+ </div>
58
+ </div>
59
+
60
+ <!-- 화면 선택 시 아이콘 그룹 -->
61
+ <template v-if="isScreen">
62
+ <div id="divider" class="h-px bg-gray-200 w-8"></div>
63
+ <div class="icon-group">
64
+ <div
65
+ class="icon-wrapper text-2xl text-gray-400 p-2 hover"
66
+ title="레이아웃"
67
+ :class="{ active: activeMenu === 'layout' }"
68
+ @click="polavo.display.setActiveMenu('layout')"
69
+ >
70
+ <font-awesome-icon :icon="['fas', 'table-columns']" />
71
+ <div class="text-xs text-center col-span-1 w-max">레이아웃</div>
72
+ </div>
73
+ <div
74
+ v-if="activeSection"
75
+ class="icon-wrapper text-2xl text-gray-400 p-2 hover"
76
+ title="요소"
77
+ :class="{ active: activeMenu === 'element' }"
78
+ @click="polavo.display.setActiveMenu('element')"
79
+ >
80
+ <font-awesome-icon :icon="['fas', 'shapes']" />
81
+ <div class="text-xs text-center col-span-1 w-max">요소</div>
82
+ </div>
83
+ </div>
84
+ </template>
85
+ </template>
86
+ </div>
87
+
88
+ <!-- 아래쪽 아이콘 그룹 -->
89
+ <div class="bottom-side flex flex-col items-center gap-2 mb-6">
90
+ <div
91
+ class="icon-wrapper text-2xl text-gray-400 p-2 hover"
92
+ title="환경설정"
93
+ :class="{ active: activeMenu === 'setting' }"
94
+ @click="$router.push('/console/setting')"
95
+ >
96
+ <font-awesome-icon :icon="['fas', 'gear']" />
97
+ <div class="text-xs text-center col-span-1 w-max">환경설정</div>
98
+ </div>
99
+ <!-- 계정설정 -->
100
+ <div class="relative" v-click-outside="() => (toggleUser = false)">
101
+ <div
102
+ class="icon-wrapper user w-12 bg-gray-200 text-xl text-gray-400 rounded-full p-3 hover"
103
+ :class="{ active: toggleUser }"
104
+ title="계정설정"
105
+ @click="toggleUser = !toggleUser"
106
+ >
107
+ <font-awesome-icon :icon="['fas', 'user']" />
108
+ </div>
109
+
110
+ <!-- 계정설정 토글메뉴 -->
111
+ <div
112
+ v-if="toggleUser"
113
+ class="bubble z-10 absolute -top-1/2 left-full ml-3 py-2 px-1 bg-white border border-gray-200 shadow-lg w-max text-center rounded-lg"
114
+ >
115
+ <ul class="space-y-1">
116
+ <li class="hover:bg-gray-100 py-1 px-6 cursor-pointer">계정설정</li>
117
+ <li class="hover:bg-gray-100 py-1 px-6 cursor-pointer" @click="logout()">로그아웃</li>
118
+ </ul>
119
+ </div>
120
+ </div>
121
+ </div>
122
+ </div>
123
+ </aside>
124
+ </template>
125
+
126
+ <script setup>
127
+ import { ref, toRefs } from 'vue';
128
+ import { clickOutside } from '../../utils/index.js';
129
+
130
+ const props = defineProps({
131
+ polavo: {
132
+ type: Object,
133
+ required: true,
134
+ },
135
+ isEditorPage: {
136
+ type: Boolean,
137
+ default: false,
138
+ },
139
+ isScreen: {
140
+ type: Boolean,
141
+ default: false,
142
+ },
143
+ });
144
+ const { activeMenu } = toRefs(props.polavo.display.state);
145
+ const { activeSection } = toRefs(props.polavo.layout.state);
146
+
147
+ // 디렉티브 등록
148
+ const vClickOutside = clickOutside;
149
+
150
+ // 상태 변수
151
+ const openIconBar = ref(true); // 아이콘바 오픈여부
152
+ const toggleUser = ref(false); // 유저 정보 오픈여부
153
+
154
+ // 라이브러리
155
+ import { useRouter } from 'vue-router';
156
+ const router = useRouter();
157
+
158
+ async function logout() {
159
+ const result = await props.polavo.userApi.logout();
160
+ if (result) {
161
+ // 로그아웃 성공 시 라우터 이동
162
+ //서브도메인 제거하기위해 아래와 같이 코드 짰습니다
163
+ router.push({ path: '/console/login' });
164
+ }
165
+ }
166
+ </script>
167
+
168
+ <style scoped lang="scss">
169
+ .icon-wrapper {
170
+ aspect-ratio: 1 / 1; /* aspect-square */
171
+ display: grid; /* grid */
172
+ grid-template-rows: 1fr auto; /* grid-rows-[1fr_auto] */
173
+ place-items: center; /* place-items-center */
174
+ justify-content: center; /* justify-center */
175
+ gap: 0.25rem; /* gap-1 */
176
+ cursor: pointer; /* cursor-pointer */
177
+
178
+ &:hover:not(.logo):not(.active) {
179
+ width: 100%;
180
+ background-color: #f3f4f6; /* hover:bg-gray-100 */
181
+ border-radius: 0.5rem; /* rounded-lg */
182
+ color: #4b5563; /* text-gray-600 */
183
+ }
184
+
185
+ &:hover.user:not(.active),
186
+ &.active.user {
187
+ width: 100%;
188
+ background-color: #f3f4f6; /* bg-gray-100 */
189
+ border-radius: 9999px; /* rounded-full */
190
+ color: #4b5563; /* text-gray-600 */
191
+ }
192
+
193
+ &.active:not(.user) {
194
+ width: 100%;
195
+ background-color: #eff6ff; /* bg-blue-50 */
196
+ border-radius: 0.5rem; /* rounded-lg */
197
+ color: #1e40af; /* text-blue-800 */
198
+ }
199
+ }
200
+
201
+ .icon-group {
202
+ display: flex;
203
+ flex-direction: column;
204
+ align-items: center;
205
+ gap: 0.5rem; /* gap-2 */
206
+ padding-top: 0.5rem; /* py-2 */
207
+ }
208
+
209
+ .bubble::before {
210
+ content: '';
211
+ position: absolute;
212
+ top: 50%;
213
+ left: -8px;
214
+ transform: translateY(-50%);
215
+ border-width: 4px 8px 4px 0;
216
+ border-style: solid;
217
+ border-color: transparent white transparent transparent;
218
+ filter: drop-shadow(-1px 0 1px rgba(0, 0, 0, 0.1));
219
+ }
220
+ </style>