
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">
<p class="main-title flex80">스케줄 관리</p>
<PageNavigation />
</div>
<div class="content-wrap">
<div class="content content-box flex-column no-gutter">
<div class="row flex50">
<div class="flex100">
<div class="content-titleZone flex justify-between align-center">
<p class="box-title">스케줄 목록</p>
<div class="search-bar flex justify-end align-center">
<input type="date" name="start-date" id="start-date" class="square-date" :class="{ 'date-placeholder': false }" data-placeholder="날짜 선택" v-model="search_date.value" />
<span class="coupler">~</span>
<input type="date" name="end-date" id="end-date" class="square-date ml5" :class="{ 'date-placeholder': false }" data-placeholder="날짜 선택" v-model="search_date.value2" />
<select class="square-select ml5" v-model="search_data1.value">
<option :value="null">상태</option>
<option :value="true">진행</option>
<option :value="false">중단</option>
</select>
<select class="square-select ml5" v-model="search_data2.key">
<option :value="null">전체</option>
<option value="jg.group_nm">제목</option>
<option value="si.creat_id">작성자</option>
</select>
<div class="search-square">
<input type="text" class="square-input" placeholder="Search" v-model="search_data2.value" v-on:keyup.enter="fnSelectScheduleList()" />
<button class="square-button blue-btn" @click="fnSelectScheduleList()">
<svg-icon type="mdi" :path="this.$getIconPath()" class="square-icon"></svg-icon>
</button>
</div>
</div>
</div>
<div class="table-zone">
<table class="list-table">
<colgroup>
<col style="width: 5%" />
<col style="width: 5%" />
<col style="width: 25%" />
<col style="width: 15%" />
<col style="width: 20%" />
<col style="width: 20%" />
<col style="width: 15%" />
</colgroup>
<thead>
<tr>
<th>
<input type="checkbox" v-model="isChkAll" @change="fnChkAll" />
</th>
<th>번호</th>
<th>제목</th>
<th>작성자</th>
<th>반복주기</th>
<th>등록일</th>
<th>상태</th>
</tr>
</thead>
<tbody>
<template v-if="scheduleList.length > 0">
<tr v-for="(item, idx) in scheduleList" :key="item" @click="fnSelectSchedule(item.schdulId)">
<td>
<input type="checkbox" :value="item" v-model="selectList" @click.stop="" @change="fnChangeCheckList" />
</td>
<td> {{ search.totalRows - idx - (search.currentPage - 1) * search.perPage }} </td>
<td>{{ item.groupNm }}</td>
<td>{{ item.creatId }}</td>
<td>{{ item.cronChrctr }}</td>
<td>{{ $getFullTime(item.creatDt) }}</td>
<td>
<div v-if="item.schdulSttus" class="flex justify-center align-center state green icon-area">
<svg-icon type="mdi" :width="21" :height="21" :path="checkPath" class="mr5"></svg-icon>
<span>실행</span>
</div>
<div v-else class="flex justify-center align-center state red">
<svg-icon type="mdi" :width="21" :height="21" :path="xPath" class="mr5"></svg-icon>
<span>중단</span>
</div>
</td>
</tr>
</template>
<tr v-else>
<td colspan="7">등록된 데이터가 없습니다.</td>
</tr>
</tbody>
</table>
<PaginationButton v-model:currentPage="search.currentPage" :perPage="search.perPage" :totalCount="search.totalRows" :maxRange="5" :click="fnSelectScheduleList" />
</div>
<div class="flex justify-end">
<button class="blue-border-btn small-btn" @click="fnChkSttus(true)"> 선택 실행 </button>
<button class="blue-border-btn small-btn" @click="fnChkSttus(false)"> 선택 중단 </button>
<button class="red-border-btn small-btn" @click="fnChkDel"> 선택 삭제 </button>
</div>
</div>
</div>
<div class="row flex50">
<div class="flex justify-between align-start content-box">
<div class="left-content flex30">
<div class="border pd10">
<div class="content-titleZone">
<p class="box-title">스케줄 등록</p>
</div>
<ScheduleForm :schedule="currentSchedule" @get-edit-scheduling="fnUpdateSchedule" />
<div class="flex justify-end" id="buttonZone">
<button class="blue-btn small-btn" id="registerButton" @click="fnAddSchedule">
<span v-if="$isEmpty(currentSchedule.schdulId)">등록</span>
<span v-else>수정</span>
</button>
<button class="blue-border-btn small-btn" id="resetButton" @click="fnInitSchedule">
<span v-if="$isEmpty(currentSchedule.schdulId)"> 초기화 </span>
<span v-else>취소</span>
</button>
</div>
</div>
</div>
<div class="right-content flex70">
<div class="content-box form-box">
<DiagramList v-if="editMode === 'vueFlow'" :currentDiagram="currentSchedule.diagram" @onUpdate="fnUpdateDiagram" @onChange="fnChangeEditMode" @initDiagram="fnInitDiagram" @onChangeLoad="fnChangeLoading" />
<DiagramLogList v-if="editMode === 'logList'" :logSearch="logSearch" :logList="logList" @onChange="fnChangeEditMode" @updateSearchVO="fnSelectScheduleLogList" />
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</template>
<script>
import _ from "lodash";
import axios from "axios";
// icon용 svg import
import SvgIcon from "@jamescoyle/vue-icon";
import { mdiMagnify, mdiCheckCircle, mdiCloseCircle } from "@mdi/js";
// 컴포넌트 import
import PageNavigation from "../../component/PageNavigation.vue";
import PaginationButton from "../../component/PaginationButton.vue";
import ScheduleForm from "../../component/scheduleComponent/ScheduleForm.vue";
import DiagramList from "../../component/scheduleComponent/DiagramList.vue";
import DiagramLogList from "../../component/scheduleComponent/DiagramLogList.vue";
export default {
components: {
// 공통
SvgIcon,
PageNavigation,
PaginationButton,
// 일반
ScheduleForm,
DiagramList,
DiagramLogList,
},
data() {
return {
isLoading: false,
// svg
searchPath: mdiMagnify,
checkPath: mdiCheckCircle,
xPath: mdiCloseCircle,
// 검색
search: this.$getDefaultSerchVO(),
search_date: this.$getDefaultSerchItem("si.creat_dt", "dates"),
search_data1: this.$getDefaultSerchItem("si.schdul_sttus", "string"),
search_data2: this.$getDefaultSerchItem(null, "string"),
// 목록
isChkAll: false,
scheduleList: [],
selectList: [],
// 다이어그램 뷰어
editMode: "vueFlow",
originSchedule: {},
currentSchedule: {},
// 스케줄 로그
logList: {},
logSearch: {},
};
},
created() {
let schedule = Object.assign({}, this.$getDefaultJobGroup().schedule);
this.originSchedule = _.cloneDeep(schedule);
this.currentSchedule = _.cloneDeep(schedule);
this.fnSelectScheduleList(); // 스케줄 목록 조회
},
mounted() { },
watch: {
currentSchedule: {
handler(newValue, oldValue) {
if (newValue.schdulId != oldValue.schdulId) {
this.initScheduleLogSearch(); // 스케줄 로그 서치 객체 초기화
this.fnSelectScheduleLogList(this.logSearch); // 스케줄 로그 목록 조회
}
},
deep: true,
},
},
methods: {
// 스케줄 목록 조회
fnSelectScheduleList() {
const vm = this;
// 세팅
vm.search.searchObjectList = []; // 초기화
vm.search.searchObjectList.push(vm.search_date);
vm.search.searchObjectList.push(vm.search_data1);
vm.search.searchObjectList.push(vm.search_data2);
// 실행
axios({
url: "/schedule/list",
method: "post",
headers: { "Content-Type": "application/json; charset=UTF-8" },
data: vm.search,
})
.then((response) => {
if (
response.data.checkMessage.status == 200 &&
response.data.checkMessage.success
) {
this.search = response.data.resultData.searchVO;
this.scheduleList = response.data.resultData.list;
} else {
vm.$showAlert(
"에러 발생",
"요청을 제대로 처리하지 못했습니다. 관리자에게 문의해 주세요."
);
}
})
.catch((error) => {
vm.$showAlert(
"에러 발생",
"에러가 발생했습니다. 관리자에게 문의해 주세요."
);
});
},
// 스케줄 개별 조회
fnSelectSchedule(scheduleId) {
const vm = this;
this.isLoading = true;
axios({
url: "/schedule/getSchedule/" + scheduleId,
method: "get",
headers: { "Content-Type": "application/json; charset=UTF-8" },
})
.then((response) => {
if (
response.data.checkMessage.status == 200 &&
response.data.checkMessage.success
) {
let schedule = response.data.resultData.scheduleVO;
vm.originSchedule = _.cloneDeep(schedule);
vm.currentSchedule = _.cloneDeep(schedule);
vm.isLoading = false;
} else {
vm.isLoading = false;
vm.$showAlert(
"에러 발생",
"요청을 제대로 처리하지 못했습니다. 관리자에게 문의해 주세요."
);
}
})
.catch((error) => {
vm.isLoading = false;
vm.$showAlert(
"에러 발생",
"에러가 발생했습니다. 관리자에게 문의해 주세요."
);
});
},
// 스케줄 등록
fnAddSchedule() {
if (!this.insertValidation()) {
return
}
const vm = this;
axios({
url: "/schedule",
method: "post",
headers: { "Content-Type": "application/json; charset=UTF-8" },
data: vm.currentSchedule,
})
.then((response) => {
if (
response.data.checkMessage.status == 200 &&
response.data.checkMessage.success
) {
let message = "스케줄을 등록했습니다.";
if (!vm.$isEmpty(vm.currentSchedule.schdulId)) {
message = "스케줄을 수정했습니다.";
}
vm.$showAlert("알림", message);
vm.fnSelectScheduleList(); // 스케줄 목록 조회
vm.fnSelectSchedule(response.data.resultData.schdulId); // 현재 스케줄을 등록한 내용으로 변경
} else {
vm.$showAlert(
"에러 발생",
response.data.checkMessage.message
);
}
})
.catch((error) => {
vm.$showAlert(
"에러 발생",
"에러가 발생했습니다. 관리자에게 문의해 주세요."
);
});
},
// 스케줄 선택 실행
fnChkSttus(schdulSttus) {
const vm = this;
// 세팅
for (let schedule of vm.selectList) {
schedule.schdulSttus = schdulSttus;
}
// 실행
axios({
url: "/schedule/updateStatus",
method: "post",
headers: { "Content-Type": "application/json; charset=UTF-8" },
data: vm.selectList,
})
.then((response) => {
if (
response.data.checkMessage.status == 200 &&
response.data.checkMessage.success
) {
vm.$showAlert("알림", "선택한 스케줄을 수정했습니다.");
vm.fnInitSchedule(); // 스케줄 초기화
} else {
vm.$showAlert(
"에러 발생",
"요청을 제대로 처리하지 못했습니다. 관리자에게 문의해 주세요."
);
}
})
.catch((error) => {
vm.$showAlert(
"에러 발생",
"에러가 발생했습니다. 관리자에게 문의해 주세요."
);
});
},
// 스케줄 선택 삭제
async fnChkDel() {
const vm = this;
let check = await vm.$showConfirm(
"데이터 삭제",
"선택한 데이터를 삭제하시겠습니까?"
);
if (!check) {
return;
}
await axios({
url: "/schedule/delete",
method: "post",
headers: { "Content-Type": "application/json; charset=UTF-8" },
data: vm.selectList,
})
.then((response) => {
if (
response.data.checkMessage.status == 200 &&
response.data.checkMessage.success
) {
vm.$showAlert("알림", "선택한 스케줄을 삭제했습니다.");
vm.fnInitSchedule(); // 스케줄 초기화
} else {
vm.$showAlert(
"에러 발생",
"요청을 제대로 처리하지 못했습니다. 관리자에게 문의해 주세요."
);
}
})
.catch((error) => {
vm.$showAlert(
"에러 발생",
"에러가 발생했습니다. 관리자에게 문의해 주세요."
);
});
},
// 전체 선택
fnChkAll() {
this.selectList = []; // 초기화
if (this.isChkAll) {
this.selectList = this.scheduleList;
}
},
// 개별 선택
fnChangeCheckList() {
if (this.selectList.length == this.scheduleList.length) {
this.isChkAll = true;
} else {
this.isChkAll = false;
}
},
// 스케줄 초기화
fnInitSchedule() {
this.currentSchedule = Object.assign(
{},
this.$getDefaultJobGroup().schedule
);
this.fnSelectScheduleList(); // 스케줄 목록 조회
},
// 다이어그램 초기화
fnInitDiagram() {
this.currentSchedule.diagram = this.originSchedule.diagram;
},
// 스케줄 폼 데이터 업데이트
fnUpdateSchedule(title, dc, getCronStr, getCron, isCyclical) {
this.currentSchedule.groupNm = title;
this.currentSchedule.schdulCn = dc;
this.currentSchedule.cronChrctr = getCronStr;
this.currentSchedule.cron = getCron;
this.currentSchedule.cycleAt = isCyclical;
},
// 스케줄 다이어그램 데이터 업데이트
fnUpdateDiagram(diagram) {
this.currentSchedule.diagram = diagram;
},
// 보기 변경
fnChangeEditMode(mode) {
this.editMode = mode;
},
// 스케줄 로그 searchVO 초기화
initScheduleLogSearch() {
let search = this.$getDefaultSerchVO();
let search_date = this.$getDefaultSerchItem("execut_de", "dates");
let search_data1 = this.$getDefaultSerchItem("process_type", "string");
let search_data2 = this.$getDefaultSerchItem("schdul_id", "string");
search_data2.value = this.currentSchedule.schdulId;
search.searchObjectList = []; // 초기화
search.searchObjectList.push(search_date);
search.searchObjectList.push(search_data1);
search.searchObjectList.push(search_data2);
this.logSearch = search;
},
// 스케줄 로그 목록 조회
fnSelectScheduleLogList(searchVO) {
const vm = this;
axios({
url: "/scheduleLog/list",
method: "post",
headers: { "Content-Type": "application/json; charset=UTF-8" },
data: searchVO,
})
.then((response) => {
if (
response.data.checkMessage.status == 200 &&
response.data.checkMessage.success
) {
this.logSearch = response.data.resultData.searchVO;
this.logList = response.data.resultData.list;
} else {
vm.$showAlert(
"에러 발생",
"요청을 제대로 처리하지 못했습니다. 관리자에게 문의해 주세요."
);
}
})
.catch((error) => {
vm.$showAlert(
"에러 발생",
"에러가 발생했습니다. 관리자에게 문의해 주세요."
);
});
},
// #유효성 검사
// 등록용 유효성 검사
insertValidation() {
// 제목 유효성 검사
if (this.$isEmpty(this.currentSchedule.groupNm)) {
this.$showAlert("검증 오류", "다이어그램 데이터가 제목이 올바르지 않습니다.");
return false;
}
// 내용 유효성 검사
if (this.$isEmpty(this.currentSchedule.schdulCn)) {
this.$showAlert("검증 오류", "다이어그램 데이터가 내용이 올바르지 않습니다.");
return false;
}
// 엣지가 없는 노드 검사
if (!this.validationNoEdge()) {
return false;
}
return true;
},
// 노드 검증
validationNode(node) {
const nodeId = node.id;
const diagram = this.currentSchedule.diagram;
// 노드가 소스로 사용된 엣지 확인
const isSource = diagram.edges.some(edge => edge.source === nodeId);
// 노드가 타겟으로 사용된 엣지 확인
const isTarget = diagram.edges.some(edge => edge.target === nodeId);
// 소스와 타겟 둘 다 연결되지 않은 노드인 경우 해당 노드 반환
if (isSource && isTarget) {
return null;
} else {
return node;
}
},
// 엣지가 없는 노드 또는 불완전하게 연결된 노드 검사 및 삭제 처리
async validationNoEdge() {
let errorNodes = [];
for (let isnode of this.currentSchedule.diagram.nodes) {
if (isnode.type !== 'start' && isnode.type !== 'end') {
if (!isnode.isSetup) {
this.$showAlert("경고", "다이어그램에 노드가 올바르지 않습니다.");
return false;
}
}
}
for (let node of this.currentSchedule.diagram.nodes) {
let newNode = this.validationNode(node);
if (newNode != null) {
if (newNode.type !== 'start' && newNode.type !== 'end') {
errorNodes.push(newNode);
} else if (errorNodes.length >= 0 && this.currentSchedule.diagram.nodes.length == 2) {
this.$showAlert("경고", newNode.type + "에 연결된 노드가 없습니다.")
}
}
}
if (errorNodes.length > 0) {
let isCheck = await this.$showConfirm("경고", "노드가 전부 연결되지 않았습니다.\n연결되지 않은 노드를 삭제하고 저장하시겠습니까?");
if (isCheck) {
// 확인을 누른 경우, 불완전 연결 노드 삭제
// 주의: node.id를 사용해야 합니다 (nodeId가 아님)
this.currentSchedule.diagram.nodes = this.currentSchedule.diagram.nodes.filter(node =>
!errorNodes.some(errorNode => errorNode.id === node.id)
);
// 관련된 엣지도 함께 삭제
// this.currentSchedule.diagram.edges를 사용해야 합니다 (diagram은 정의되지 않음)
this.currentSchedule.diagram.edges = this.currentSchedule.diagram.edges.filter(edge =>
!errorNodes.some(errorNode =>
edge.source === errorNode.id || edge.target === errorNode.id
)
);
return false;
}
}
return errorNodes.length === 0; // 모든 노드가 올바르게 연결되어 있으면 true 반환
},
},
// 로더 상태 변경
fnChangeLoading(status) {
this.isLoading = status;
}
};
</script>