
File name
Commit message
Commit date
File name
Commit message
Commit date
File name
Commit message
Commit date
File name
Commit message
Commit date
File name
Commit message
Commit date
<template>
<div class="container" :style="isLoading ? { cursor: 'wait' } : {}">
<!-- 로딩 시 화면을 덮는 반투명 오버레이 -->
<div v-if="isLoading" class="loading-overlay">
<div class="loading-div">
<span>LOADING </span>
<span class="anima">.</span>
<span class="anima">.</span>
<span class="anima">.</span>
</div>
</div>
<div class="page-titleZone flex justify-between align-center align-start">
<p class="main-title flex80"> 데이터 활용관리 {{ !pageId ? "등록" : "수정" }} </p>
</div>
<div class="content-wrap content flex100">
<div class="custom-tab content-box">
<div class="flex justify-start align-center no-gutter content-box">
<div class="column-nav flex5">
<ul>
<li class="cursor" v-for="(item, idx) in navList" :key="idx" @click="fnChangeTab('nav', item.id)">
<a :class="{ activeTab: activeTab === item.id }">
<svg-icon type="mdi" class="mb5" :path="item.iconPath" />
<p>{{ item.name }}</p>
</a>
</li>
</ul>
</div>
<div class="content-box flex justify-between align-start flex95 no-gutter">
<div :class="{
'tab-zone flex align-start': true,
flex30: !isTabZoneOpen,
}">
<div class="content-box" v-show="!isTabZoneOpen">
<!-- 페이지정보탭 -->
<div class="content-box tab-box pd10 overflow-y" v-show="activeTab === 'pageInfo'">
<div class="content-titleZone">
<div class="flex justify-between align-center">
<p class="box-title">페이지 정보</p>
</div>
</div>
<table class="form-table">
<tr>
<th>제목</th>
<td>
<input type="text" class="full-input" v-model="customTitle" />
</td>
</tr>
<tr>
<th>공개여부</th>
<td>
<div class="input-container flex">
<label class="radio-label">
<input type="radio" name="public_at" class="custom-radiobox" :value="true" v-model="public_at" />
<span>공개</span>
</label>
<label class="radio-label">
<input type="radio" name="public_at" class="custom-radiobox" :value="false" v-model="public_at" />
<span>비공개</span>
</label>
</div>
</td>
</tr>
<tr>
<th>설명</th>
<td>
<textarea class="content-box" cols="30" rows="35" v-model="customComment" @input="customComment = $event.target.value"></textarea>
</td>
</tr>
</table>
</div>
<!-- 레이아웃 및 컴포넌트 탭 -->
<div class="content-box flex justify-between tab-box" v-show="activeTab === 'viewSetting'">
<div class="content-list layout flex50">
<div class="layout-tree content-box">
<div class="content-titleZone">
<div class="flex justify-between align-center">
<p class="box-title">레이아웃 및 컴포넌트</p>
<div>
<button id="horizontal-btn" @click="addSplitterLayout('horizontal')" title="수직레이아웃">
<img :src="verticalImg" />
</button>
<button id="vertical-btn" @click="addSplitterLayout('vertical')" title="수평레이아웃">
<img :src="horizontalImg" />
</button>
<button id="split-delete-btn" @click="deleteSplitterLayout" title="삭제">
<svg-icon type="mdi" :path="trashPath" :color="'#aaa'" />
</button>
<button class="attribute-btn" @click="fnAttributeToggle">
<svg-icon type="mdi" :path="dotPath" :color="'#aaa'" />
<div class="attribute-modal" v-show="isAttributeOpen">
<ul>
<li class="flex align-center">
<svg-icon type="mdi" :path="tablePath" />
<p class="ml5">테이블 추가</p>
</li>
<li class="flex align-center">
<svg-icon type="mdi" :path="chartPath" />
<p class="ml5">차트 추가</p>
</li>
</ul>
</div>
</button>
</div>
</div>
</div>
<ul>
<TreeItem :splitInfo="splitInfo" :selectLayout="currentLayout" @onChange="fnSelectLayout" />
</ul>
</div>
</div>
<div class="layout-option flex-column flex50">
<div v-if="!$isEmpty(currentLayout)">
<div class="tabnav3">
<ul class="flex justify-start align-center">
<template v-for="(item, idx) of optionList" :key="idx">
<li class="cursor" v-show="item.useAt" @click="fnChangeTab('opt', item.id)">
<p :class="{ activeOption: activeOption === item.id }">{{ item.name }}</p>
</li>
</template>
</ul>
</div>
<div v-if="activeOption === 'LAYOUT'">
<StyleSheet :styleSheet="currentLayout.styleSheet" />
</div>
<div v-if="activeOption === 'TITLE'">
<div class="table-zone">
<p class="object-title mb5">타이틀 사용 여부</p>
<div class="input-container flex">
<label class="radio-label">
<input type="radio" name="titleUseAt" class="custom-radiobox" :value="true" v-model="isUseSj" @change="fnChangeUseSj">
<span>사용</span>
</label>
<label class="radio-label">
<input type="radio" name="titleUseAt" class="custom-radiobox" :value="false" v-model="isUseSj" @change="fnChangeUseSj">
<span>미사용</span>
</label>
</div>
<div v-if="currentLayout.useSj">
<div>
<p class="object-title mb5">메인타이틀</p>
<input type="text" class="full-input" v-model="currentLayout.layoutSj.main_sj" />
<FontOption :fontStyle="currentLayout.layoutSj.styleSheetMain.fontStyle" :title="'메인'" />
</div>
<div>
<p class="object-title mb5">서브타이틀</p>
<input type="text" class="full-input" v-model="currentLayout.layoutSj.sub_sj" />
<FontOption :fontStyle="currentLayout.layoutSj.styleSheetSub.fontStyle" :title="'서브'" />
</div>
</div>
</div>
</div>
<div v-if="activeOption === 'COMPONENT'">
<StyleSheet :styleSheet="currentLayout.styleSheet" />
</div>
</div>
</div>
</div>
<!-- 데이터탭 -->
<div class="content-box tab-box" v-show="activeTab === 'dataset'">
<div class="data-set" style="border-bottom: 1px solid #ddd">
<div class="content-box flex-column pd10">
<div class="content-titleZone">
<div class="flex justify-between align-center">
<p class="box-title">데이터 목록</p>
<button type="button" class="blue-border-btn small-btn" @click="fnCreateJobGroup">데이터 추가</button>
</div>
</div>
<div class="data-list">
<div v-for="(item, idx) of jobGroupList" :key="idx" @click="fnSelectJobGroup(item)">
<div :class="{
'item flex justify-between align-center': true,
mb5: idx < jobGroupList.length - 1,
selectData: item.group_nm == currentJobGroup.group_nm,
}">
<p class="flex align-center">
<svg-icon type="mdi" class="mr5" :width="20" :height="20" :path="dataPath" />
<span>{{ item.group_nm }}</span>
</p>
<div>
<button class="blue-border-btn set-btn" @click.stop="" @click="fnModalOpen(item, idx)">설정</button>
<button class="red-border-btn set-btn" @click.stop="" @click="deleteDiagram(item)">삭제</button>
</div>
</div>
</div>
</div>
</div>
</div>
<!-- 컬럼 정보 -->
<div class="column-list flex no-gutter">
<div class="content-box flex50 pd10" style="border-right: 1px solid #ddd">
<div class="flex100 content-box">
<p class="box-title mb10">컬럼정보</p>
<div class="overflow-y" style="height: calc(100% - 22px)">
<template v-if="currentDataTable.columnDatas.length > 0">
<div v-for="(column, idx) of this.currentDataTable.columnDatas" :key="idx" :class="{
item: true,
mb5: idx < currentDataTable.columnDatas.length - 1,
}" draggable="true" @dragstart="startDrag(column)">
<p>
<b>{{ column.displyColumnNm }}</b> ({{ column.columnNm }}) : [{{ column.dataTy }}]
</p>
</div>
</template>
<div v-else>
<p style="font-size: 1.3rem"> 컬럼 데이터가 존재하지 않습니다. </p>
</div>
</div>
</div>
</div>
<div class="content-box flex50">
<div class="content-box flex-column no-gutter pd10 overflow-y">
<div class="flex30 mb10">
<div class="editor-box content-box pd10">
<p class="object-title mb5">항목(Category)</p>
<div class="overflow-y" style="height: calc(100% - 22px)" @drop.prevent="onDrop('xdata')" @dragenter.prevent @dragover.prevent>
<template v-if="dataX.length > 0">
<div v-for="(column, indx) of dataX" :key="indx" :class="{
'item flex justify-between align-center': true,
mb5: idx < dataX.length - 1,
}">
<p> {{ column.displyColumnNm }} : [{{ column.dataTy }}] </p>
<div>
<button class="red-border-btn set-btn" @click="deleteItem(column, 'x')"> 삭제 </button>
</div>
</div>
</template>
<div class="item mb5" v-else>
<p>NO_DATA</p>
</div>
</div>
</div>
</div>
<div class="flex40 mb10">
<div class="editor-box content-box pd10">
<div class="flex justify-between align-center">
<p class="object-title mb5">값(Value)</p>
<select v-model="currentCalc" @change="fnChangeCalc">
<option v-for="(item, idx) of calcList" :key="idx" :value="item.key">{{ item.value }}</option>
</select>
</div>
<div class="overflow-y" style="height: calc(100% - 22px)" @drop.prevent="onDrop('ydata')" @dragenter.prevent @dragover.prevent>
<template v-if="dataY.length > 0">
<div v-for="(column, indx) of dataY" :key="indx" :class="{
'item flex justify-between align-center': true,
mb5: idx < dataY.length - 1,
}">
<p> {{ column.displyColumnNm }} : [{{ column.dataTy }}] </p>
<button class="red-border-btn set-btn" @click="deleteItem(column, 'y')"> 삭제 </button>
</div>
</template>
<div class="item mb5" v-else>
<p>NO_DATA</p>
</div>
</div>
</div>
</div>
<div class="flex20 mb10">
<div class="editor-box content-box pd10">
<p class="object-title mb5">다음으로 색상 지정</p>
<ul class="overflow-y" style="height: calc(100% - 16px)">
<li class="item mb5">
<p>column</p>
</li>
</ul>
</div>
</div>
</div>
</div>
</div>
</div>
<div class="content-box flex justify-between tab-box" v-show="activeTab === 'component'">
<div class="content-box overflow-y">
<ul class="flex justify-center">
<li v-for="(cmpnt, idx) in chartComponent" :key="idx" class="flex5 mt10 cursor" @click="
fnCreateComponent(idx, cmpnt.type, cmpnt.id, cmpnt.name)
">
<div :class="{
'img-wrap border': true,
active: this.activeIndex === idx,
}">
<img :src="cmpnt.src" alt="" />
<div class="img-hover">
<span>{{ cmpnt.name }}</span>
</div>
</div>
</li>
</ul>
</div>
</div>
</div>
<button class="custom-toggle" :style="isTabZoneOpen ? { right: '-25px' } : { right: '-24px' }" @click="fnTabZoneToggle">
<svg-icon type="mdi" :path="arrowPath" :color="'#333'" />
</button>
</div>
<!-- 미리보기 -->
<div :class="{
'preview-zone': true,
flex100: isTabZoneOpen,
flex70: !isTabZoneOpen,
}">
<div id="splitter-container" ref="splitterContainer" :class="{
'splitter-panel-custom pl10': true,
active: clickElement === 'splitter-container',
}">
<SplitterLayout :splitInfo="splitInfo" :selectLayout="currentLayout" @onChange="fnSelectLayout" />
</div>
<div class="flex justify-end" id="buttonZone">
<button class="blue-btn small-btn" @click="fnInsert">
<span v-if="!pageId">등록</span>
<span v-else>수정</span>
</button>
<button class="blue-border-btn small-btn" @click="fnInit"> 초기화 </button>
<button class="darkg-btn small-btn" @click="fnGotoList">취소</button>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
<!-- 잡그룹관리 모달 -->
<div class="modal-wrapper" v-if="isModalOpen">
<div class="modal-container">
<div class="modal-title flex justify-between align-center">
<h2>잡그룹 관리</h2>
<button class="close-btn" @click="fnModalClose">
<svg-icon type="mdi" :width="20" :height="20" :path="closePath" />
</button>
</div>
<div class="modal-content-monthly mb10">
<JobGroupManagement :jobGroup="currentJobGroup" />
</div>
<div class="modal-end flex justify-end">
<button class="blue-btn small-btn" @click="fnSaveJobGroup"> 저장 </button>
<button class="blue-border-btn small-btn" @click="fnModalClose"> 취소 </button>
</div>
</div>
</div>
</template>
<script>
import _ from "lodash";
import axios from "axios";
// icon
import SvgIcon from "@jamescoyle/vue-icon";
import {
mdiChevronLeft,
mdiChevronRight,
mdiLandPlots,
mdiRhombusSplit,
mdiDatabaseCogOutline,
mdiInformationSlabCircleOutline,
mdiDotsVertical,
mdiTableLarge,
mdiChartLine,
mdiDatabase,
mdiTrashCanOutline,
mdiClose,
} from "@mdi/js";
import SplitterLayout from "../../component/SplitterLayout.vue";
import TreeItem from "../../component/treeMenu/LayoutTree.vue";
import StyleSheet from "../../component/style/StyleSheetComponent.vue";
import BackgroundOption from "../../component/style/BackgroundOption.vue";
import CellOption from "../../component/style/CellOption.vue";
import FontOption from "../../component/style/FontOption.vue";
import chartDataTransform from "../../component/chart/chartDataTransform.js";
import html2canvas from "html2canvas";
import DataComponent from "../../component/flowComponent/DataComponent.vue";
import JobGroupManagement from "../../component/flowComponent/JobGroupManagement.vue";
import { v4 as uuidv4 } from 'uuid';
export default {
components: {
SvgIcon,
SplitterLayout,
TreeItem,
BackgroundOption,
CellOption,
FontOption,
StyleSheet,
DataComponent,
JobGroupManagement,
},
data() {
return {
isLoading: true,
// icon
closePath: mdiClose,
arrowPath: mdiChevronLeft,
dotPath: mdiDotsVertical,
tablePath: mdiTableLarge,
chartPath: mdiChartLine,
dataPath: mdiDatabase,
trashPath: mdiTrashCanOutline,
horizontalImg: require("../../../resources/img/icon/hor_b.png"),
verticalImg: require("../../../resources/img/icon/ver_b.png"),
navList: [
{ id: "pageInfo", name: "페이지 정보", iconPath: mdiInformationSlabCircleOutline, },
{ id: "viewSetting", name: "레이아웃", iconPath: mdiLandPlots },
{ id: "component", name: "컴포넌트", iconPath: mdiRhombusSplit },
{ id: "dataset", name: "데이터", iconPath: mdiDatabaseCogOutline },
],
optionList: [],
chartComponent: [
{
src: require("../../../resources/img/chartIcon/column_chart_v.png"),
type: "chart",
id: "column_chart_v",
name: "세로형 막대 차트",
},
{
src: require("../../../resources/img/chartIcon/column_chart_h.png"),
type: "chart",
id: "column_chart_h",
name: "가로형 막대 차트",
},
{
src: require("../../../resources/img/chartIcon/clustered_chart_v.png"),
type: "chart",
id: "clustered_chart_v",
name: "세로형 클러스터 차트",
},
{
src: require("../../../resources/img/chartIcon/clustered_chart_h.png"),
type: "chart",
id: "clustered_chart_h",
name: "가로형 클러스터 차트",
},
{
src: require("../../../resources/img/chartIcon/stacked_bar_chart.png"),
type: "chart",
id: "stacked_bar_chart",
name: "적층 막대 차트",
},
{
src: require("../../../resources/img/chartIcon/line_chart.png"),
type: "chart",
id: "line_chart",
name: "선 차트",
},
{
src: require("../../../resources/img/chartIcon/mix_chart.png"),
type: "chart",
id: "mix_chart",
name: "혼합 차트",
},
{
src: require("../../../resources/img/chartIcon/pie_chart.png"),
type: "chart",
id: "pie_chart",
name: "파이 차트",
},
{
src: require("../../../resources/img/chartIcon/dounet_chart.png"),
type: "chart",
id: "dounet_chart",
name: "도넛 차트",
},
{
src: require("../../../resources/img/chartIcon/semicircle_chart.png"),
type: "chart",
id: "semicircle_chart",
name: "반원 차트",
},
{
src: require("../../../resources/img/chartIcon/bubble_chart.png"),
type: "chart",
id: "bubble_chart",
name: "버블 차트",
},
{
src: require("../../../resources/img/chartIcon/word_chart.png"),
type: "chart",
id: "word_chart",
name: "단어 차트",
},
{
src: require("../../../resources/img/chartIcon/stacked_column_chart.png"),
type: "chart",
id: "stacked_column_chart",
name: "세로형 적층 막대 차트",
},
{
src: require("../../../resources/img/chartIcon/node_line_chart.png"),
type: "chart",
id: "node_line_chart",
name: "노드 라인 차트",
},
{
src: require("../../../resources/img/chartIcon/liin_chart_back.png"),
type: "chart",
id: "liin_chart_back",
name: "라인 백그라운드 차트",
},
],
calcList: [
{ key: "default", value: "기본" },
{ key: "sum", value: "합계" },
{ key: "avg", value: "평균" },
{ key: "min", value: "최솟값" },
{ key: "max", value: "최댓값" },
],
pageId: this.$route.query.page_id,
isModalOpen: false,
isTabZoneOpen: false,
isAttributeOpen: false,
isUseSj: false,
activeTab: "pageInfo",
activeOption: "LAYOUT",
// 페이지정보 탭
customTitle: null, // 제목
public_at: true, // 공개여부
customComment: null, // 설명
activeIndex: null,
originSplitInfo: {},
splitInfo: _.cloneDeep(this.$getDefaultJobGroup().customSplitter),
jobGroupList: [],
// 현재 작업 객체
currentLayout: {},
currentJobGroup: Object.assign({}, this.$getDefaultObject().jobGroup),
currentJobGroupIdx: null,
currentCalc: "default",
currentDataTable: Object.assign({}, this.$getDefaultObject().dataTable),
dragData: {},
dataX: [],
dataY: [],
};
},
created() {
this.activeTab = "pageInfo";
if (this.$isEmpty(this.pageId)) {
this.init();
this.isLoading = false;
} else {
this.getCustomData(); // 기존 데이터 가져오기
}
},
computed: {},
watch: {
// 컴포넌트 사용 여부
'currentLayout.component'(newVal, oldVal) {
if (!this.$isEmpty(newVal)) {
this.currentLayout.se = 'component'
}
},
},
methods: {
// 초기화
async init() {
this.activeIndex = null;
// 페이지 정보 초기화
this.customTitle = null;
this.customComment = null;
this.public_at = true;
// 레이아웃 초기화
let splitInfo = _.cloneDeep(this.$getDefaultJobGroup().customSplitter);
splitInfo.se = "splitter";
splitInfo.depth = 0;
splitInfo.layout_nm = "LAY_" + this.generateShortUUID();
splitInfo.position_idx = 1;
splitInfo.styleSheet.borderStyle = Object.assign({}, this.$getDefaultJobGroup().borderStyle);
splitInfo.styleSheet.background_style = Object.assign({}, this.$getDefaultJobGroup().background_style);
this.splitInfo = splitInfo;
this.fnSelectLayout(splitInfo); // 레이아웃 선택
// 데이터 초기화
this.jobGroupList = [];
},
// 현재 컬럼 정보 초기화
initColumnInfo() {
// 현재 데이터테이블 변경
if (this.currentJobGroup.jobItms.length === 0) {
this.currentDataTable = Object.assign({}, this.$getDefaultObject().dataTable);
} else {
const index = this.currentJobGroup.jobItms.length - 1;
this.currentDataTable = this.currentJobGroup.jobItms[index].dataTable;
}
// 컬럼 정보 초기화
this.dragData = {};
this.dataX = [];
this.dataY = [];
this.currentCalc = "default";
// 컴포넌트가 있는 경우 컴포넌트 데이터 초기화
let component = this.currentLayout.component;
if (component != null) {
component.component_itm.dataTable = this.currentJobGroup.dataTable;
component.component_itm.data_list = [];
component.jobInfo[0] = this.currentJobGroup;
}
},
// 기존 데이터 가져오기
getCustomData() {
const vm = this;
axios({
url: "/custom/customPageOneDataselect/" + vm.pageId,
method: "get",
headers: { "Content-Type": "application/json; charset=UTF-8" },
})
.then((response) => {
// 페이지정보
let resPageInfo = response.data.resultData.pageInfo;
vm.customTitle = resPageInfo.ttl;
vm.customComment = resPageInfo.cn;
vm.public_at = resPageInfo.public_at;
// 레이아웃
vm.splitInfo = response.data.resultData.splitterInfo;
this.fnSelectLayout(vm.splitInfo); // 레이아웃 선택
// 데이터 목록
vm.jobGroupList = response.data.resultData.jobGroupList;
let originData = {
customTitle: resPageInfo.ttl,
customComment: resPageInfo.cn,
public_at: resPageInfo.public_at,
splitInfo: response.data.resultData.splitterInfo,
jobGroupList: response.data.resultData.jobGroupList,
}
vm.originSplitInfo = _.cloneDeep(originData);
this.isLoading = false;
})
.catch((error) => {
vm.$showAlert("오류", "데이터 현황관리 차트 연결 오류, 관리자에게 문의하세요.");
vm.$router.push({ path: "/customSelectList.page" });
});
},
// 현재 탭 변경
fnChangeTab(type, value) {
if (type == "nav") {
this.activeTab = value;
} else if (type == "opt") {
this.activeOption = value;
}
},
// 탭설정패널 여닫기 버튼 동작
fnTabZoneToggle() {
this.isTabZoneOpen = !this.isTabZoneOpen;
// 아이콘 변경
if (this.isTabZoneOpen) {
this.arrowPath = mdiChevronRight;
} else {
this.arrowPath = mdiChevronLeft;
}
},
// 레이아웃 및 컴포넌트 더보기 버튼 동작
fnAttributeToggle() {
this.isAttributeOpen = !this.isAttributeOpen;
},
// 타이틀 사용 여부
async fnChangeUseSj() {
const isCheck = await this.$showConfirm("경고", "타이틀 사용 여부 변경 시 작업 중인 데이터가 삭제됩니다.<br>타이틀을 사용 여부를 변경 하시겠습니까?");
if (isCheck) {
this.currentLayout.useSj = this.isUseSj;
this.currentLayout.se = 'splitter';
if (this.currentLayout.useSj) {
this.currentLayout.component = null; // 컴포넌트 초기화
// 현재 레이아웃에 따른 레이아웃 탭 옵션
this.optionList = [
{ id: "LAYOUT", name: "레이아웃", useAt: true },
{ id: "TITLE", name: "타이틀", useAt: this.currentLayout.children.length == 0 },
{ id: "COMPONENT", name: "컴포넌트", useAt: !this.$isEmpty(this.currentLayout.component) },
];
} else {
this.currentLayout.layoutSj = _.cloneDeep(this.$getDefaultObject().layoutSj); // 타이틀 초기화
}
} else {
this.isUseSj = this.currentLayout.useSj;
}
},
// 유효성 검사 - 컴포넌트 추가
validationByCreateComponent(id) {
if (id == "bubble_chart") {
this.$showAlert("메세지", "현재 지원하지 않는 컴포넌트입니다.");
return true;
}
if (this.currentLayout == null) {
this.$showAlert("메세지", "컴포넌트를 추가할 레이아웃을 먼저 선택해 주세요.");
return true;
}
if (this.currentLayout.children.length > 0) {
this.$showAlert("메세지", "선택된 레이아웃이 분할되어 있습니다.<br>선택된 레이아웃 내부를 하나로 합치거나 다른 레이아웃을 선택해 주세요.");
return true;
}
return false;
},
// 컴포넌트 추가
async fnCreateComponent(index, type, id, name) {
// 유효성 검사
if (this.validationByCreateComponent(id)) {
return;
};
let message;
if (this.currentLayout.component == null) {
message = name + " 컴포넌트를 추가하시겠습니까?";
} else {
message = "선택된 레이아웃이 비어있지 않아, 컴포넌트를 변경할 경우 모든 데이터가 초기화됩니다.<br>선택한 컴포넌트로 덮어쓰기 하시겠습니까?";
}
let isCheck = await this.$showConfirm("메세지", message);
if (!isCheck) {
return;
}
this.activeIndex = index;
let chartData = _.cloneDeep(this.$getDefaultJobGroup().componentData);
chartData.chart_knd = id;
chartData.component_nm = "ChartCmmn";
chartData.component_type = type;
let component = _.cloneDeep(this.$getDefaultJobGroup().component);
component.component_itm = chartData;
this.currentLayout.se = "component";
this.currentLayout.component = component;
this.currentJobGroup = Object.assign({}, this.$getDefaultObject().jobGroup);
this.initColumnInfo(); // 현재 컬럼 정보 초기화
},
// 레이아웃 선택
fnSelectLayout(layout) {
// 현재 레이아웃 변경
this.currentLayout = layout;
// 현재 레이아웃에 따른 레이아웃 탭 옵션
this.optionList = [
{ id: "LAYOUT", name: "레이아웃", useAt: true },
{ id: "TITLE", name: "타이틀", useAt: layout.children.length == 0 },
{ id: "COMPONENT", name: "컴포넌트", useAt: !this.$isEmpty(layout.component) },
];
this.fnChangeTab("opt", "LAYOUT"); // 탭 변경
// 현재 레이아웃에 컴포넌트가 있는 경우
this.activeIndex = null; // 초기화
let component = layout.component;
if (component != null) {
// 컴포넌트 선택
for (let i = 0; i < this.chartComponent.length; i++) {
if (this.chartComponent[i].id === component.component_itm.chart_knd) {
this.activeIndex = i;
break;
}
}
// 컬럼 정보 변경
this.dataX = component.component_itm.categoryAxis;
this.dataY = component.component_itm.valueAxis;
this.currentCalc = component.component_itm.chart_cal;
// 현재 잡그룹 변경
if (component.jobInfo.length > 0) {
this.currentJobGroup = component.jobInfo[0];
// 현재 데이터테이블 변경
if (this.currentJobGroup.jobItms.length > 0) {
const index = this.currentJobGroup.jobItms.length - 1;
this.currentDataTable = this.currentJobGroup.jobItms[index].dataTable;
} else {
this.currentDataTable = Object.assign({}, this.$getDefaultObject().dataTable);
}
return;
}
}
this.currentJobGroup = Object.assign({}, this.$getDefaultObject().jobGroup);
this.initColumnInfo(); // 현재 컬럼 정보 초기화
},
findLoop(item) {
if (item.se == this.currentLayout.se) {
item = this.currentLayout;
return;
} else {
for (let next of item.children) {
this.findLoop(next);
}
}
},
// 레이아웃 추가
async addSplitterLayout(type) {
// 유효성 검사
if (this.currentLayout.layout_nm == null) {
this.$showAlert("메세지", "선택된 레이아웃이 없습니다.");
return;
}
let isCheck = await this.$showConfirm("메세지", "선택된 레이아웃을 분할하시겠습니까?");
if (!isCheck) {
return;
}
let copy = _.cloneDeep(this.currentLayout);
copy.parents_splitter_id = this.currentLayout.layout_nm;
copy.depth = this.currentLayout.depth + 1;
copy.indx = 0;
copy.layout_nm = "LAY_" + this.generateShortUUID();
let newItem = _.cloneDeep(this.$getDefaultJobGroup().customSplitter);
newItem.se = "splitter";
newItem.parents_splitter_id = this.currentLayout.layout_nm;
newItem.depth = this.currentLayout.depth + 1;
newItem.indx = 1;
newItem.layout_nm = "LAY_" + this.generateShortUUID();
newItem.styleSheet.borderStyle = _.cloneDeep(this.$getDefaultJobGroup().borderStyle);
newItem.styleSheet.background_style = _.cloneDeep(this.$getDefaultJobGroup().background_style);
this.currentLayout.se = "splitter";
this.currentLayout.useSj = false;
this.currentLayout.layoutSj = Object.assign({}, this.$getDefaultJobGroup.layoutSj);
this.currentLayout.layout_type = type;
this.currentLayout.layout_size1 = 50;
this.currentLayout.layout_size2 = 50;
this.currentLayout.component = null;
this.currentLayout.children = [];
this.currentLayout.children.push(copy);
this.currentLayout.children.push(newItem);
},
// 등록-수정
fnInsert() {
let vm = this;
// 유효성 검사
if (vm.$isEmpty(vm.customTitle)) {
vm.$showAlert("메세지", "제목은 필수입력입니다.");
return;
}
if (vm.splitInfo.component == null && vm.splitInfo.children.length == 0) {
vm.$showAlert("메세지", "레이아웃 또는 차트를 추가해야합니다.");
return;
}
let customPageVO = {
ttl: vm.customTitle,
cn: vm.customComment,
splitInfo: vm.splitInfo,
public_at: vm.public_at,
jobFlow: { jobGroup: vm.jobGroupList },
}
let url = "/custom/insert";
if (!vm.pageId) {
customPageVO["wrt_id"] = this.$store.state.loginUser.user_id;
} else {
url = "/custom/customUpdate";
customPageVO["page_id"] = vm.pageId;
customPageVO["mdfcn_id"] = this.$store.state.loginUser.user_id;
}
// 캡처할 요소
let element = vm.$refs.splitterContainer;
html2canvas(element).then((canvas) => {
canvas.toBlob(function (blob) {
let formData = new FormData();
formData.append("img", blob, "capture.png");
formData.append("customPage", JSON.stringify(customPageVO));
axios({
url: url,
method: "post",
headers: { "Content-Type": "multipart/form-data; charset=UTF-8" },
data: formData,
})
.then((response) => {
if (response.data.checkMessage.success) {
const successMsg = vm.pageId ? "페이지 수정에 성공하였습니다." : "페이지 등록에 성공하였습니다.";
vm.$showAlert("메세지", successMsg);
const pageId = vm.pageId || response.data.resultData.pageId;
vm.$router.push({
path: "/customSelectOne.page",
query: { page_id: pageId },
});
} else {
const failMsg = vm.pageId ? "수정에 실패하였습니다." : "등록에 실패하였습니다.";
vm.$showAlert("메세지", failMsg);
}
})
.catch((error) => {
if (error.response && error.response.data && error.response.data.code == "COMPONENT_DATA_NOT_FOUND") {
vm.$showAlert("메세지", "데이터를 추가하지 않은 컴포넌트가 존재합니다.");
} else {
vm.$showAlert("메세지", "요청 처리 중 오류가 발생했습니다.");
}
});
});
});
},
// 아이디 만들기
generateShortUUID() {
const uuid = uuidv4();
return uuid.replace(/-/g, '').substring(0, 8);
},
// 초기화 버튼 동작
async fnInit() {
let isCheck = await this.$showConfirm("초기화", "초기화 하시겠습니까? 데이터가 모두 삭제됩니다.");
if (isCheck) {
if (this.$isEmpty(this.pageId)) {
this.init();
} else {
// 페이지정보
this.customTitle = this.originSplitInfo.customTitle;
this.customComment = this.originSplitInfo.customComment;
this.public_at = this.originSplitInfo.public_at;
// 레이아웃
this.splitInfo = this.originSplitInfo.splitInfo;
this.fnSelectLayout(this.splitInfo); // 레이아웃 선택
// 데이터 목록
this.jobGroupList = this.originSplitInfo.jobGroupList;
}
}
},
// 컬럼정보 비우기
fnInitColInfo() {
this.currentJobGroup = Object.assign({}, this.$getDefaultObject().jobGroup);
this.initColumnInfo(); // 현재 컬럼 정보 초기화
},
// 레이아웃 삭제
async deleteSplitterLayout() {
if (!this.currentLayout.layout_nm) {
this.$showAlert("메세지", "삭제할 레이아웃이 없습니다.");
return;
}
if (this.currentLayout.layout_nm == this.splitInfo.layout_nm) {
this.$showAlert("메세지", "최상위 레이아웃은 삭제할 수 없습니다.");
return;
}
let isCheck = await this.$showConfirm(
"레이아웃 삭제",
"선택한 레이아웃을 삭제하시겠습니까?"
);
if (!isCheck) {
return;
}
let parentLayout = this.findParentLayout(this.splitInfo, this.currentLayout.parents_splitter_id);
if (parentLayout.children.length > 0) {
let copy = _.cloneDeep(parentLayout.children.filter((item) => item !== this.currentLayout));
if (copy.length == 1) {
parentLayout.se = copy[0].se;
parentLayout.type = copy[0].type;
parentLayout.min_width = copy[0].min_width;
parentLayout.min_height = copy[0].min_height;
parentLayout.layout_size1 = copy[0].layout_size1;
parentLayout.layout_size2 = copy[0].layout_size2;
parentLayout.layout_type = copy[0].layout_type;
parentLayout.styleSheet = copy[0].styleSheet;
parentLayout.children = copy[0].children;
parentLayout.component = copy[0].component;
parentLayout.active = copy[0].active;
parentLayout.layoutSj = copy[0].layoutSj;
parentLayout.sizes = copy[0].sizes;
parentLayout.useSj = copy[0].useSj;
return;
}
}
parentLayout.children = parentLayout.children.filter((item) => item !== this.currentLayout);
},
// 부모 요소 검색
findParentLayout(layout, parentId) {
let result = null;
if (layout.layout_nm === parentId) {
result = layout;
} else {
if (layout.children.length > 0) {
for (let child of layout.children) {
result = this.findParentLayout(child, parentId);
if (result != null) {
break;
}
}
}
}
return result;
},
// 취소 (데이터활용관리 목록으로 이동)
async fnGotoList() {
let isCheck = await this.$showConfirm("경고", "취소할 경우 작성 중인 내용이 사라집니다.<br>취소하시겠습니까?");
if (isCheck) {
this.$router.push({ path: "/customSelectList.page" });
}
},
// 잡그룹 추가
async fnCreateJobGroup() {
let component = this.currentLayout.component;
if (component != null) {
if (!this.$isEmpty(component.component_itm.data_list)) {
let isCheck = await this.$showConfirm("경고", "컴포넌트 컬럼 설정 후, 새 데이터를 추가할 경우 기존 정보가 삭제됩니다.<br>새 데이터를 추가하시겠습니까?");
if (!isCheck) {
return;
}
}
}
let jobGroup = Object.assign({}, this.$getDefaultObject().jobGroup);
jobGroup.group_nm = "데이터" + (this.jobGroupList.length + 1);
this.jobGroupList.push(jobGroup);
this.currentJobGroup = jobGroup;
this.initColumnInfo(); // 현재 컬럼 정보 초기화
},
// 잡그룹 선택
async fnSelectJobGroup(jobGroup) {
let component = this.currentLayout.component;
if (component != null) {
if (jobGroup.jobItms.length > 0) {
let index = jobGroup.jobItms.length - 1;
if (component.component_itm.dataTable == jobGroup.jobItms[index].dataTable) {
return;
}
}
if (!this.$isEmpty(component.component_itm.data_list)) {
let isCheck = await this.$showConfirm("경고", "컴포넌트 컬럼 설정 후, 다른 데이터를 선택할 경우 기존 정보가 삭제됩니다.<br>다른 데이터를 선택하시겠습니까?");
if (!isCheck) {
return;
}
}
}
this.currentJobGroup = jobGroup;
this.initColumnInfo(); // 현재 컬럼 정보 초기화
},
// 잡그룹관리 모달 열기
fnModalOpen(jobGroup, idx) {
this.currentJobGroup = _.cloneDeep(jobGroup);
this.currentJobGroupIdx = idx;
this.isModalOpen = true;
},
// 잡그룹관리 모달 닫기
fnModalClose() {
this.isModalOpen = false;
},
// 잡그룹관리 저장
fnSaveJobGroup() {
this.jobGroupList[this.currentJobGroupIdx] = this.currentJobGroup;
this.fnSelectJobGroup(this.currentJobGroup); // 잡그룹 선택 (저장한 정보 선택)
this.fnModalClose(); // 노드설정 모달 닫기
},
// 드래그 이벤트
startDrag(data) {
this.dragData = data;
},
// 값 계산법 변경
fnChangeCalc() {
this.currentLayout.component.component_itm.chart_cal = this.currentCalc;
this.onChangeChartData(); // 차트 데이터 수정
},
// 드랍 이벤트
async onDrop(type) {
// 레이아웃인 경우 경고 후 실행 취소
if (this.currentLayout.children.length > 0) {
this.$showAlert("경고", "레이아웃은 설정할 수 없습니다.");
return;
}
// 컴포넌트가 설정되지 않은 경우 경고 후 실행 취소
if (this.$isEmpty(this.currentLayout.component)) {
this.$showAlert("경고", "컴포넌트 등록 후 설정할 수 있습니다.");
return;
}
if (type == "xdata") {
if (this.dataX.length > 0) {
let isCheck = await this.$showConfirm(
"경고",
"데이터를 변경하시겠습니까?"
);
if (!isCheck) {
return;
}
}
this.dataX = []; // 초기화
this.dataX.push(this.dragData);
this.currentLayout.component.component_itm.categoryAxis = this.dataX;
} else if (type == "ydata") {
if (this.dragData.dataTy === "STRING") {
this.$showAlert(
"메세지",
"값(Value)에는 숫자 데이터만 사용 가능합니다."
);
return;
}
this.dataY.push(this.dragData);
this.currentLayout.component.component_itm.valueAxis = this.dataY;
}
this.onChangeChartData(); // 차트 데이터 수정
},
// 차트 데이터 수정
onChangeChartData() {
let dataList = chartDataTransform.createData(
this.currentDataTable,
this.currentLayout.component.component_itm.categoryAxis,
this.currentLayout.component.component_itm.valueAxis,
"null",
this.currentLayout.component.component_itm.chart_cal
);
this.currentLayout.component.component_itm.data_list = dataList;
this.currentLayout.component.jobInfo[0] = this.currentJobGroup;
},
// 아이템 삭제
deleteItem(column, category) {
if (category == "x") {
for (let i = 0; i < this.dataX.length; i++) {
if (this.dataX[i].orginlColumnNm == column.orginlColumnNm) {
this.dataX.splice(i, 1);
}
}
} else if (category == "y") {
for (let i = 0; i < this.dataY.length; i++) {
if (this.dataY[i].orginlColumnNm == column.orginlColumnNm) {
this.dataY.splice(i, 1);
}
}
}
},
// 잡그룹 삭제
async deleteDiagram(item) {
let isCheck = await this.$showConfirm(
"삭제",
"선택한 데이터를 삭제하시겠습니까?\n관련된 데이터도 함께 삭제됩니다."
);
if (!isCheck) {
return;
}
// splitInfo에 적용했던 데이터가 있는지 확인하여 삭제 (재귀)
if (this.splitInfo.children.length > 0) {
this.RecursiveFunc(this.splitInfo.children, item);
}
if (this.splitInfo.component != null) {
if (this.splitInfo.component.jobInfo[0].group_nm == item.group_nm) {
this.splitInfo.component = null;
this.splitInfo.se = "splitter";
}
}
this.jobGroupList = this.jobGroupList.filter(
(jobItem) => jobItem !== item
);
this.currentJobGroup = Object.assign({}, this.$getDefaultObject().jobGroup);
this.initColumnInfo(); // 현재 컬럼 정보 초기화
},
// 재귀함수
RecursiveFunc(children, item) {
for (let i = 0; i < children.length; i++) {
if (children[i].children.length > 0) {
this.RecursiveFunc(children[i].children, item);
} else {
if (children[i].component != null) {
// children[i].jobInfo 와 item이 같은지 확인하여 삭제
if (children[i].component.jobInfo[0].group_nm == item.group_nm) {
children[i].component = null;
children[i].se = "splitter";
}
}
}
}
},
},
};
</script>
<style scoped>
#splitter-container {
width: 100%;
height: calc(100% - 50px);
margin-bottom: 20px;
}
#SplitterLayout {
width: 100%;
height: 100%;
}
#splitter-container.active {
border: 3px dotted red;
}
.padding-1 {
padding: 1rem;
}
#customComment {
height: 99px;
}
.table-padding {
padding: 15px 0;
}
.paletteLi {
display: inline-block;
margin-right: 10px;
}
.img-wrap {
position: relative;
}
.img-wrap:hover .img-hover {
display: flex;
}
.img-wrap .img-hover {
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
background-color: #213f9999;
display: none;
align-items: center;
justify-content: center;
}
.img-wrap .img-hover span {
padding: 10px;
text-align: center;
font-size: 1.5em;
color: #ffffff;
word-break: keep-all;
}
.selectData {
border: solid 2px #5d5c5c;
color: #ffffff;
background-color: #ff9e29;
}
</style>