박정하 박정하 04-02
250402 이슈내역 수정
@c90bd05824ee9255e75ed22997f2c9d3725539ff
client/views/component/common/AlertModal.vue
--- client/views/component/common/AlertModal.vue
+++ client/views/component/common/AlertModal.vue
@@ -2,12 +2,12 @@
   <div v-show="isModalOpen" class="modal-wrapper">
     <div class="modal-container small-modal">
       <div class="modal-title text-ct">
-        <h2>{{ title }}</h2>
+        <h2>{{ modalTitle }}</h2>
       </div>
       <div class="modal-content-monthly">
-        <p class="alert-write text-ct" v-html="message">
+        <p class="alert-write text-ct" v-html="modalMessage">
         </p>
-        <div v-if="radioAt">
+        <div v-if="modalType === 'radio'">
           <p>원본파일 : </p>
           <label></label>
           <p>동작 : </p>
@@ -17,15 +17,14 @@
         </div>
       </div>
       <div class="modal-end flex justify-center" style="flex-wrap: nowrap;">
-        <button class="blue-btn large-btn" id="confirmOk" @click="closeModal" @keyup.enter="closeModal">확인</button>
-        <button class="gray-btn large-btn" id="confirmCancel" @click="closeModal" v-show="confirmAt">취소</button>
-        <button class="gray-btn large-btn" id="confirmRadioCancel" @click="closeModal" v-show="radioAt">취소</button>
+        <button class="blue-btn large-btn" @click="onConfirm" @keyup.enter="onConfirm" autofocus v-if="isModalOpen">확인</button>
+        <button class="gray-btn large-btn" @click="onCancel" v-show="modalType === 'confirm'">취소</button>
+        <button class="gray-btn large-btn" @click="onRadioCancel" v-show="modalType === 'radio'">취소</button>
       </div>
     </div>
   </div>
 </template>
 <script>
-
 export default {
   props: {
     title: {
@@ -40,112 +39,112 @@
   data() {
     return {
       isModalOpen: false,
-      activeTab: 'tab1',
-      modalType: 'tab-modal',
-      title: this.title,
-      message: this.message,
-      confirmAt: false,
-      radioAt: false,
+      modalType: 'alert', // 'alert', 'confirm', 'radio' 중 하나
+      modalTitle: this.title,
+      modalMessage: this.message,
       moveOrCopy: {
-        type: null,
-        checkBox: null
+        type: "move",
+        checkBox: false
       },
+      currentPromise: null
     }
   },
   methods: {
-    // 탭 변경
-    showTab: function (tabName) {
-      this.activeTab = tabName;
-    },
-
-    // 닫기
-    closeModal: function () {
+    // 모달 닫기
+    closeModal() {
       this.isModalOpen = false;
-      this.confirmAt = false;
-      this.radioAt = false;
+      this.modalType = 'alert';
     },
 
-    // 모달 호출
-    showModal: function () {
-
+    // 일반 모달 표시
+    showModal() {
+      this.modalType = 'alert';
       this.isModalOpen = true;
-      document.getElementById("confirmOk").focus()
     },
 
-    // confirm 호출
-    showConfirm: async function () {
-      this.confirmAt = true;
-      this.isModalOpen = true;
-      document.getElementById("confirmOk").focus()
-      const promise = new Promise((resolve, reject) => {
-        document.getElementById("confirmCancel").addEventListener("click", async () => {
-          resolve('cancel')
-        });
+    // 확인 버튼 클릭 핸들러
+    onConfirm() {
+      if (this.modalType === 'alert') {
+        // 단순 알림 모달은 그냥 닫기
+        this.closeModal();
+      } else if (this.modalType === 'confirm') {
+        // confirm 모달은 true 반환
+        this.resolvePromise(true);
+      } else if (this.modalType === 'radio') {
+        // radio 모달은 현재 선택된 값 반환
+        this.resolvePromise(this.moveOrCopy);
+      }
+    },
 
-        document.getElementById("confirmOk").addEventListener("click", async () => {
-          resolve('ok')
-        });
+    // 취소 버튼 클릭 핸들러 (일반 confirm용)
+    onCancel() {
+      this.resolvePromise(false);
+    },
+
+    // 취소 버튼 클릭 핸들러 (라디오 confirm용)
+    onRadioCancel() {
+      this.resolvePromise({
+        type: "cancel",
+        checkBox: false
+      });
+    },
+
+    // Promise 해결 및 모달 닫기
+    resolvePromise(value) {
+      if (this.currentPromise && this.currentPromise.resolve) {
+        this.currentPromise.resolve(value);
+        this.currentPromise = null;
+      }
+      this.closeModal();
+    },
+
+    // Promise 생성
+    createPromise() {
+      let resolve, reject;
+      const promise = new Promise((res, rej) => {
+        resolve = res;
+        reject = rej;
       });
 
-      return promise.then(
-        (id) => {
-          if (id == 'cancel') {
-            return false;
-          } else if (id == 'ok') {
-            return true;
-          }
-        }
-      );
+      this.currentPromise = { promise, resolve, reject };
+      return promise;
     },
 
-    // radio confirm 호출
-    showRadioConfirm: async function () {
-      this.radioAt = true;
+    // confirm 모달 표시 (확인/취소)
+    async showConfirm() {
+      this.modalType = 'confirm';
+      this.isModalOpen = true;
+      return this.createPromise();
+    },
+
+    // radio confirm 모달 표시 (라디오 옵션)
+    async showRadioConfirm() {
+      this.modalType = 'radio';
       this.isModalOpen = true;
       this.moveOrCopy.type = "move";
       this.moveOrCopy.checkBox = false;
-
-      document.getElementById("confirmOk").focus()
-      const promise = new Promise((resolve, reject) => {
-        document.getElementById("confirmRadioCancel").addEventListener("click", async () => {
-          resolve('cancel')
-        });
-
-        document.getElementById("confirmOk").addEventListener("click", async () => {
-          resolve('ok')
-        });
-      });
-
-      return promise.then(
-        (id) => {
-          if (id == 'cancel') {
-            this.moveOrCopy.type = "cancel"
-            this.moveOrCopy.checkBox = false
-            return this.moveOrCopy;
-          } else if (id == 'ok') {
-            return this.moveOrCopy;
-          }
-        }
-      );
+      return this.createPromise();
     },
 
-    setTitle: function (Title) {
-      this.title = Title;
+    // 제목 설정
+    setTitle(title) {
+      this.modalTitle = title;
     },
 
-    setMessage: function (message) {
-      this.message = message;
+    // 메시지 설정
+    setMessage(message) {
+      this.modalMessage = message;
     },
-
   },
   watch: {
-
-  },
-  computed: {
-
-  },
-  components: {
-
-  },
+    // props로 전달된 title이 변경되면 modalTitle 업데이트
+    title(newVal) {
+      this.modalTitle = newVal;
+    },
+    // props로 전달된 message가 변경되면 modalMessage 업데이트
+    message(newVal) {
+      this.modalMessage = newVal;
+    }
+  }
 }
 </script>
(파일 끝에 줄바꿈 문자 없음)
client/views/component/connection/itm/fileDataRead.vue
--- client/views/component/connection/itm/fileDataRead.vue
+++ client/views/component/connection/itm/fileDataRead.vue
@@ -1,163 +1,64 @@
 <template>
   <div>
-    <div
-      v-show="!preview"
-      class="data_control flexCenter mt10"
-      style="display: flex; justify-content: space-evenly"
-    >
+    <div v-show="!preview" class="data_control flexCenter mt10" style="display: flex; justify-content: space-evenly">
       <div class="col-4 fl alignC">
         <label for="datastart"> 데이터 시작 행 </label>
         <div class="flexBox" style="align-items: center !important">
-          <input
-            type="text"
-            name="datastart"
-            v-model="rowIndex"
-            @blur="updateRowIndex"
-            @keyup.enter="updateRowIndex"
-          />
+          <input type="text" name="datastart" v-model="rowIndex" @blur="updateRowIndex" @keyup.enter="updateRowIndex" />
           <div class="updownbox flexBox">
-            <input
-              type="button"
-              value="▲"
-              class="input_up"
-              @click="dataTableInfo.startRowIndex++"
-            />
-            <input
-              type="button"
-              value="▼"
-              class="input_down"
-              @click="dataTableInfo.startRowIndex--"
-            />
+            <input type="button" value="▲" class="input_up" @click="dataTableInfo.startRowIndex++" />
+            <input type="button" value="▼" class="input_down" @click="dataTableInfo.startRowIndex--" />
           </div>
         </div>
       </div>
       <div class="col-4 fl alignL">
         <label for="dataend"> 데이터 시작 열 </label>
         <div class="flexBox" style="align-items: center !important">
-          <input
-            type="text"
-            name="dataend"
-            v-model="cellIndex"
-            @blur="updateCellIndex"
-            @keyup.enter="updateCellIndex"
-          />
+          <input type="text" name="dataend" v-model="cellIndex" @blur="updateCellIndex" @keyup.enter="updateCellIndex" />
           <div class="updownbox flexBox">
-            <input
-              type="button"
-              value="▲"
-              class="input_up"
-              @click="dataTableInfo.startCellIndex++"
-            />
-            <input
-              type="button"
-              value="▼"
-              class="input_down"
-              @click="dataTableInfo.startCellIndex--"
-            />
+            <input type="button" value="▲" class="input_up" @click="dataTableInfo.startCellIndex++" />
+            <input type="button" value="▼" class="input_down" @click="dataTableInfo.startCellIndex--" />
           </div>
         </div>
       </div>
     </div>
-    <div class="datatableInfoBox" style="max-height: 550px; overflow: auto">
+    <div class="datatableInfoBox" style="min-height: 550px; overflow: auto">
       <div class="table-zone">
-        <!-- <div class="list-info flex justify-between align-center">
-                    <div class="count-zone">
-                        <p>총 <span>40</span>건 중 <span>01</span>건 선택</p>
-                    </div>
-                </div> -->
         <table class="list-table">
           <!-- col 꼭 너비 기재해야함! 그래야 100%로 차지함 -->
           <colgroup>
             <col style="width: 10%" />
-            <col
-              v-for="(info, i) in dataTableInfo.changeColumnDatas"
-              :key="i"
-              :style="changeWidth()"
-            />
+            <col v-for="(info, i) in dataTableInfo.changeColumnDatas" :key="i" :style="changeWidth()" />
           </colgroup>
           <thead>
             <tr>
               <th>No</th>
-              <th
-                v-for="(info, i) in dataTableInfo.changeColumnDatas"
-                :key="i"
-                :style="
-                  i < dataTableInfo.startCellIndex
-                    ? 'background-color: #dfdfdf;'
-                    : null
-                "
-              >
-                [{{ i + 1 }}]
-              </th>
+              <th v-for="(info, i) in dataTableInfo.changeColumnDatas" :key="i" :style="i < dataTableInfo.startCellIndex
+                ? 'background-color: #dfdfdf;'
+                : null
+                "> [{{ i + 1 }}] </th>
             </tr>
           </thead>
-          <!-- <tbody>
-                        <tr>
-                            <th :class="{ selected: getRowDataColumnIndex < 0 }">[1]</th>
-                            <td :class="{
-                            selected: getRowDataColumnIndex < 0
-                            , disabled: getStartRowIndex >= 0 || dataTableInfo.startCellIndex > j
-                        }"
-                                v-for="(info, j) in dataTableInfo.changeColumnDatas">
-                                {{ dataTableInfo.columnDatas[j].displyColumnNm }}
-                            </td>
-                        </tr>
-                        <tr v-for="(cells, i) in dataTableInfo.rowData" v-if="i < viewCount">
-                            <th :class="{ selected: getRowDataColumnIndex == i }">[{{ i + 2 }}]</th>
-                            <td :class="{
-                            selected: getRowDataColumnIndex == i
-                            , disabled: getStartRowIndex > i || dataTableInfo.startCellIndex > j
-                        }"
-                                v-for="(value, j) in cells" :title="value">
-                                {{ value }}
-                            </td>
-                        </tr>
-                        <tr v-if="postList === 0">
-                            <td colspan="5" class="no-list">검색조건에 해당하는 데이터가 없습니다.</td>
-                        </tr>
-                    </tbody> -->
           <tbody>
             <tr>
               <th>[1]</th>
-              <td
-                v-for="(info, j) in dataTableInfo.changeColumnDatas"
-                :key="j"
-                :style="
-                  j < dataTableInfo.startCellIndex
-                    ? 'background-color: #dfdfdf;'
-                    : 'background-color: #fdd2d2;'
-                "
-              >
-                {{ dataTableInfo.columnDatas[j].displyColumnNm }}
-              </td>
+              <td v-for="(info, j) in dataTableInfo.changeColumnDatas" :key="j" :style="j < dataTableInfo.startCellIndex
+                ? 'background-color: #dfdfdf;'
+                : 'background-color: #fdd2d2;'
+                "> {{ dataTableInfo.columnDatas[j].displyColumnNm }} </td>
             </tr>
-            <tr
-              v-for="(cells, i) in dataTableInfo.rowData"
-              :key="i"
-              :style="
-                i + 1 < dataTableInfo.startRowIndex
-                  ? 'background-color: #dfdfdf;'
-                  : null
-              "
-            >
+            <tr v-for="(cells, i) in dataTableInfo.rowData" :key="i" :style="i + 1 < dataTableInfo.startRowIndex
+              ? 'background-color: #dfdfdf;'
+              : null
+              ">
               <th>[{{ i + 2 }}]</th>
-              <td
-                v-for="(value, j) in cells"
-                :key="j"
-                :title="value"
-                :style="
-                  j < dataTableInfo.startCellIndex
-                    ? 'background-color: #dfdfdf;'
-                    : null
-                "
-              >
-                {{ value }}
-              </td>
+              <td v-for="(value, j) in cells" :key="j" :title="value" :style="j < dataTableInfo.startCellIndex
+                ? 'background-color: #dfdfdf;'
+                : null
+                "> {{ value }} </td>
             </tr>
             <tr v-if="postList === 0">
-              <td colspan="5" class="no-list">
-                검색조건에 해당하는 데이터가 없습니다.
-              </td>
+              <td colspan="5" class="no-list"> 검색조건에 해당하는 데이터가 없습니다. </td>
             </tr>
           </tbody>
         </table>
@@ -165,8 +66,6 @@
     </div>
   </div>
 </template>
-
-
 <script>
 import _ from "lodash";
 
client/views/component/connection/modalContents/DbRead.vue
--- client/views/component/connection/modalContents/DbRead.vue
+++ client/views/component/connection/modalContents/DbRead.vue
@@ -1,6 +1,6 @@
 <template>
   <div class="modal-content-monthly">
-    <div v-if="currentPage == 1">
+    <template v-if="currentPage == 1">
       <div class="table-zone">
         <table class="form-table">
           <colgroup>
@@ -103,134 +103,106 @@
           </tbody>
         </table>
       </div>
-    </div>
-    <div v-if="currentPage == 2">
-      <Splitter style="min-height: 60dvh; border: 0px solid #e5e7eb">
-        <SplitterPanel class="flex align-items-center justify-content-center" :size="20" :minSize="10">
-          <div class="content-box">
-            <div class="file-zone">
-              <div class="content-titleZone" style="height: 60px">
-                <p class="box-title">테이블 정보</p>
+    </template>
+    <template v-if="currentPage == 2">
+      <div class="flex content-box" style="min-height: 60dvh; flex-wrap: nowrap;">
+        <div class="content-box pd10" style="width: 20%;">
+          <div class="content-titleZone flex justy justify-between align-center" style="height: 45px">
+            <p class="box-title">데이터베이스 정보</p>
+          </div>
+          <div class="content-zone2">
+            <ul class="content-list" v-if="tableList.length > 0">
+              <li class="cursor" v-for="(item, indx) in tableList" :key="indx">
+                <a @click="getTableData(item)" :class="{
+                  'file-list': true,
+                  selected: selectTable === item,
+                }">
+                  <div class="flex align-center">
+                    <p> {{ item.tableNmKr != null && item.tableNmKr != "" ? item.tableNmKr : item.tableNm }} </p>
+                  </div>
+                </a>
+              </li>
+            </ul>
+          </div>
+        </div>
+        <div class="content-box" style="width: 80%;">
+          <div class="flex-column">
+            <div class="content-box pd10" style="height: 50%;">
+              <div class="content-titleZone flex justy justify-between align-center" style="height: 45px">
+                <p class="box-title">쿼리 작업</p>
+                <button class="icon-btn" @click="executeQuery" title="실행">
+                  <svg-icon type="mdi" :path="playPath" :color="'#fbbe28'"></svg-icon>
+                </button>
               </div>
-              <div class="content-zone2">
-                <ul class="content-list" v-if="tableList.length > 0">
-                  <li class="cursor" v-for="(item, indx) in tableList" :key="indx">
-                    <a @click="getTableData(item)" :class="{
-                      'file-list': true,
-                      selected: selectTable === item,
-                    }">
-                      <div class="flex align-center">
-                        <p> {{ item.tableNmKr != null && item.tableNmKr != "" ? item.tableNmKr : item.tableNm }} </p>
-                      </div>
-                    </a>
-                  </li>
-                </ul>
+              <div class="flex" style="height: calc(100% - 60px)">
+                <textarea style="resize: none; width: 100%; height: 100%; padding: 10px;" v-model="jobItm.itm.query"></textarea>
               </div>
             </div>
-          </div>
-        </SplitterPanel>
-        <SplitterPanel class="flex align-items-center justify-content-center" :size="80">
-          <div class="content-box">
-            <div class="content-titleZone" style="height: 60px">
-              <div class="flex justify-between aling-center">
-                <p class="box-title">쿼리 작업</p>
-                <div>
-                  <button class="icon-btn" @click="executeQuery" title="실행">
-                    <svg-icon type="mdi" :path="playPath" :color="'#fbbe28'"></svg-icon>
-                  </button>
+            <div class="content-box pd10" style="height: 50%;">
+              <ul class="tab-nav flex justify-start">
+                <li @click="showTab('tab1')">
+                  <a href="#tab01" :class="{ activeTab: activeTab === 'tab1' }">작업결과</a>
+                </li>
+                <li @click="showTab('tab2')">
+                  <a href="#tab02" :class="{ activeTab: activeTab === 'tab2' }">작업log</a>
+                </li>
+              </ul>
+              <div v-show="activeTab === 'tab1'" class="content-box" style="height: calc(100% - 45px); padding: 10px;">
+                <div class="count-zone mb10" v-if="jobItm.dataTable.columnDatas.length > 0">
+                  <p> 총 <span>{{ jobItm.dataTable.totalRows }}</span>건 중 <span>{{ jobItm.dataTable.rowData.length }}</span>건 조회 </p>
+                </div>
+                <div style="height: calc(100% - 15px); overflow: auto;">
+                  <table class="list-table">
+                    <thead>
+                      <tr v-if="jobItm.dataTable.columnDatas.length > 0">
+                        <th v-for="(itm, indx) in jobItm.dataTable
+                          .columnDatas" :key="indx" style="min-width: 150px !important"> {{ itm.columnNm }} <label class="check-label">
+                            <input type="checkbox" class="custom-checkbox" v-model="itm.pkAt" />
+                          </label>
+                        </th>
+                      </tr>
+                    </thead>
+                    <tbody v-if="jobItm.dataTable.rowData.length > 0">
+                      <tr v-for="(row, rows) in jobItm.dataTable.rowData" :key="rows">
+                        <td v-for="(itm, indx) in row" :key="indx" style="overflow: hidden; white-space: nowrap; text-overflow: ellipsis;"> {{ itm }} </td>
+                      </tr>
+                    </tbody>
+                  </table>
+                </div>
+              </div>
+              <div v-show="activeTab === 'tab2'" class=" content-box" style="height: calc(100% - 45px); padding: 10px;">
+                <div style="height: calc(100% - 15px); overflow: auto;">
+                  <table class="list-table">
+                    <colgroup>
+                      <col width="10%" />
+                      <col width="60%" />
+                      <col width="20%" />
+                      <col width="10%" />
+                    </colgroup>
+                    <thead>
+                      <tr>
+                        <th>No</th>
+                        <th>접속시간</th>
+                        <th>접속결과</th>
+                        <th>접속내용</th>
+                      </tr>
+                    </thead>
+                    <tbody>
+                      <tr v-for="(itm, indx) in executeMessage" :key="indx">
+                        <td>{{ indx + 1 }}</td>
+                        <td>{{ itm.message }}</td>
+                        <td>{{ itm.time }}</td>
+                        <td>{{ itm.result }}</td>
+                      </tr>
+                    </tbody>
+                  </table>
                 </div>
               </div>
             </div>
-            <div class="content-zone">
-              <Splitter style="height: 100%" layout="vertical">
-                <SplitterPanel class="flex align-items-center justify-content-center" :size="50" :minSize="20">
-                  <textarea style="
-                      resize: none;
-                      max-width: 100%;
-                      max-height: 100%;
-                      padding: 10px;
-                    " v-model="jobItm.itm.query"></textarea>
-                </SplitterPanel>
-                <SplitterPanel class="align-items-center justify-content-center" :size="50">
-                  <ul class="tab-nav flex justify-start">
-                    <li @click="showTab('tab1')">
-                      <a href="#tab01" :class="{ activeTab: activeTab === 'tab1' }">작업결과</a>
-                    </li>
-                    <li @click="showTab('tab2')">
-                      <a href="#tab02" :class="{ activeTab: activeTab === 'tab2' }">작업log</a>
-                    </li>
-                  </ul>
-                  <div v-show="activeTab === 'tab1'" style="
-                      height: calc(100% - 60px);
-                      overflow: auto;
-                      padding: 10px;
-                    ">
-                    <div class="count-zone" v-if="jobItm.dataTable.columnDatas.length > 0">
-                      <p> 총 <span>{{ jobItm.dataTable.totalRows }}</span>건 중 <span>{{ jobItm.dataTable.rowData.length }}</span>건 조회 </p>
-                    </div>
-                    <div class="table-zone">
-                      <table class="list-table">
-                        <!-- col 꼭 너비 기재해야함! 그래야 100%로 차지함 -->
-                        <thead>
-                          <tr v-if="jobItm.dataTable.columnDatas.length > 0">
-                            <th v-for="(itm, indx) in jobItm.dataTable
-                              .columnDatas" :key="indx" style="min-width: 150px !important"> {{ itm.columnNm }} <label class="check-label">
-                                <input type="checkbox" class="custom-checkbox" v-model="itm.pkAt" />
-                              </label>
-                            </th>
-                          </tr>
-                        </thead>
-                        <tbody v-if="jobItm.dataTable.rowData.length > 0">
-                          <tr v-for="(row, rows) in jobItm.dataTable.rowData" :key="rows">
-                            <td v-for="(itm, indx) in row" :key="indx" style="
-                                overflow: hidden;
-                                white-space: nowrap;
-                                text-overflow: ellipsis;
-                              "> {{ itm }} </td>
-                          </tr>
-                        </tbody>
-                      </table>
-                    </div>
-                  </div>
-                  <div v-show="activeTab === 'tab2'" style="
-                      height: calc(100% - 60px);
-                      overflow: auto;
-                      padding: 10px;
-                    ">
-                    <div class="table-zone">
-                      <table class="list-table">
-                        <colgroup>
-                          <col width="10%" />
-                          <col width="10%" />
-                          <col width="" />
-                          <col width="10%" />
-                        </colgroup>
-                        <thead>
-                          <tr>
-                            <th>No</th>
-                            <th>접속시간</th>
-                            <th>접속결과</th>
-                            <th>접속내용</th>
-                          </tr>
-                        </thead>
-                        <tbody>
-                          <tr v-for="(itm, indx) in executeMessage" :key="indx">
-                            <td>{{ indx + 1 }}</td>
-                            <td>{{ itm.time }}</td>
-                            <td>{{ itm.message }}</td>
-                            <td>{{ itm.result }}</td>
-                          </tr>
-                        </tbody>
-                      </table>
-                    </div>
-                  </div>
-                </SplitterPanel>
-              </Splitter>
-            </div>
           </div>
-        </SplitterPanel>
-      </Splitter>
-    </div>
+        </div>
+      </div>
+    </template>
   </div>
   <DBConSearch :openPopup="openSearchModal" @modalclose="dbConSearchOpen" @selectItm="selectDbcon" />
 </template>
@@ -514,4 +486,4 @@
   overflow-y: auto;
   overflow-x: hidden;
 }
-</style>
(파일 끝에 줄바꿈 문자 없음)
+</style>
client/views/component/connection/modalContents/FileRead.vue
--- client/views/component/connection/modalContents/FileRead.vue
+++ client/views/component/connection/modalContents/FileRead.vue
@@ -1,11 +1,11 @@
 <template>
   <template v-if="currentPage == 1">
     <div class="modal-content-monthly">
-      <FileManagementMain type="modal" @onSelected="fnChangeTarget" />
+      <div style="height: 100%; min-height: 60dvh;">
+        <FileManagementMain type="modal" @onSelected="fnChangeTarget" />
+      </div>
     </div>
-    <div
-      style="margin-bottom: 15px; padding-top: 15px; border-top: 1px solid #eee"
-    >
+    <div style="margin-bottom: 15px; padding-top: 15px; border-top: 1px solid #eee">
       <table class="form-table">
         <colgroup>
           <col style="width: 20%" />
@@ -34,17 +34,13 @@
           <thead>
             <tr>
               <th>No</th>
-              <th v-for="(rowKey, index) of rowKeys" :key="index">
-                {{ rowKey }}
-              </th>
+              <th v-for="(rowKey, index) of rowKeys" :key="index"> {{ rowKey }} </th>
             </tr>
           </thead>
           <tbody>
             <tr v-for="(items, indexI) of rowData" :key="indexI">
               <td>{{ indexI + 1 }}</td>
-              <td v-for="(item, indexJ) of items" :key="indexJ">
-                {{ item }}
-              </td>
+              <td v-for="(item, indexJ) of items" :key="indexJ"> {{ item }} </td>
             </tr>
           </tbody>
         </template>
@@ -55,7 +51,6 @@
     </div>
   </template>
 </template>
-
 <script>
 import axios from "axios";
 import FileManagementMain from "../../../pages/data/filemanger/FileManagementMain.vue";
@@ -84,9 +79,9 @@
     };
   },
 
-  created() {},
+  created() { },
 
-  mounted() {},
+  mounted() { },
 
   watch: {
     jobItem(value) {
client/views/component/dataComponent/DbConnectionSearchModal.vue
--- client/views/component/dataComponent/DbConnectionSearchModal.vue
+++ client/views/component/dataComponent/DbConnectionSearchModal.vue
@@ -13,18 +13,15 @@
             <div class="flex justify-end flex100">
               <div class="search-bar">
                 <div class="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" />
+                  <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"
-                    :class="{ 'date-placeholder': false }" data-placeholder="날짜 선택" v-model="search_date.value2" />
+                  <input type="date" name="end-date" id="end-date" class="square-date" :class="{ 'date-placeholder': false }" data-placeholder="날짜 선택" v-model="search_date.value2" />
                   <select name="" id="searchItm1" class="square-select" v-model="search_data.key">
                     <option value="conect_nm">연계명</option>
                     <option value="conect_ip">접속IP</option>
                   </select>
                   <div class="search-square">
-                    <input type="text" class="square-input" placeholder="Search" v-model="search_data.value"
-                      v-on:keyup.enter="searchData()" />
+                    <input type="text" class="square-input" placeholder="Search" v-model="search_data.value" v-on:keyup.enter="searchData()" />
                     <button class="square-button" @click="searchData()">
                       <svg-icon type="mdi" :path="this.$getIconPath()" class="square-icon"></svg-icon>
                     </button>
@@ -36,9 +33,7 @@
           <div class="table-zone flex90">
             <div class="list-info flex justify-between align-center">
               <div class="count-zone">
-                <p>
-                  총 <span>{{ search.totalRows }}</span>건
-                </p>
+                <p> 총 <span>{{ search.totalRows }}</span>건 </p>
               </div>
               <div class="cunt-selectZone">
                 <select id="pageCount" v-model="search.perPage">
@@ -84,15 +79,12 @@
                   <td>{{ itm.creatId }}</td>
                   <td>{{ $filters.dateTime(itm.creatDt) }}</td>
                   <td>
-                    <button class="blue-border-btn small-btn" @click="selectItm(itm)">
-                      선택
-                    </button>
+                    <button class="blue-border-btn small-btn" @click="selectItm(itm)"> 선택 </button>
                   </td>
                 </tr>
               </tbody>
             </table>
-            <PaginationButton v-model:currentPage="search.currentPage" :perPage="search.perPage"
-              :totalCount="search.totalRows" :maxRange="5" :click="searchData" />
+            <PaginationButton v-model:currentPage="search.currentPage" :perPage="search.perPage" :totalCount="search.totalRows" :maxRange="5" :click="searchData" />
           </div>
         </div>
       </div>
@@ -102,14 +94,10 @@
     </div>
   </div>
 </template>
-
 <script>
-import CodeList from "../../component/common/Component_CodeList.vue";
 import axios from "axios";
 import SvgIcon from "@jamescoyle/vue-icon";
-import PageNavigation from "../../component/PageNavigation.vue";
 import PaginationButton from "../../component/PaginationButton.vue";
-import moment from "moment";
 
 export default {
   name: "dbcon-search",
@@ -185,8 +173,6 @@
     },
   },
   computed: {
-    CodeList: CodeList,
-    PageNavigation: PageNavigation,
     SvgIcon: SvgIcon,
     PaginationButton: PaginationButton,
   },
 
client/views/component/modal/connection/DbReadModal.vue (added)
+++ client/views/component/modal/connection/DbReadModal.vue
@@ -0,0 +1,593 @@
+<template>
+  <div class="modal-wrapper">
+    <div class="modal-container large-modal">
+      <div class="modal-title flex justify-between align-center">
+        <h2>데이터베이스 읽기</h2>
+        <button class="close-btn" @click="$emit('onClose')">
+          <svg-icon type="mdi" :width="20" :height="20" :path="closePath"></svg-icon>
+        </button>
+      </div>
+      <div class="modal-content-monthly">
+        <!-- 페이지 1: 데이터베이스 연결 설정 -->
+        <template v-if="currentPage == 1">
+          <!-- 데이터베이스 정보 -->
+          <div class="content-titleZone flex justy justify-between align-center">
+            <p class="box-title">데이터베이스 정보</p>
+          </div>
+          <table class="form-table">
+            <colgroup>
+              <col style="width: 20%" />
+              <col style="width: 80%" />
+            </colgroup>
+            <tbody>
+              <tr>
+                <th>연계정보 타입</th>
+                <td>
+                  <div class="input-container flex">
+                    <label class="radio-label">
+                      <input type="radio" name="radio" :value="false" class="custom-radiobox" @change="onConnectionTypeChange(false)" v-model="jobItm.itm_option_bool" />
+                      <span>직접입력</span>
+                    </label>
+                    <label class="radio-label">
+                      <input type="radio" name="radio" :value="true" class="custom-radiobox" @change="onConnectionTypeChange(true)" v-model="jobItm.itm_option_bool" />
+                      <span>불러오기</span>
+                    </label>
+                  </div>
+                </td>
+              </tr>
+              <!-- 연계정보 불러오기 선택 시 -->
+              <tr v-show="jobItm.itm_option_bool">
+                <th>연계정보</th>
+                <td>
+                  <input type="text" class="half-input" disabled :value="getConnectionDisplayText()" />
+                  <button class="blue-border-btn small-btn" @click="dbConSearchOpen(true)">검색</button>
+                </td>
+              </tr>
+              <tr>
+                <th>DMBS</th>
+                <td>
+                  <select id="databaseType" @change="successAt = false" class="square-select half-input" v-model="currentConnectionDB.databaseType" :disabled="jobItm.itm_option_bool">
+                    <option v-for="(itm, index) in databaseTypeList" :key="index" :value="itm.key">{{ itm.value }}</option>
+                  </select>
+                </td>
+              </tr>
+              <tr>
+                <th>IP</th>
+                <td>
+                  <input id="conectIp" type="text" @input="successAt = false" class="half-input" v-model="currentConnectionDB.conectIp" placeholder="ex. 127.0.0.1" :disabled="jobItm.itm_option_bool" />
+                </td>
+              </tr>
+              <tr>
+                <th>PORT</th>
+                <td>
+                  <input id="conectPort" type="text" @input="successAt = false" class="half-input" v-model="currentConnectionDB.conectPort" :disabled="jobItm.itm_option_bool" />
+                </td>
+              </tr>
+              <tr>
+                <th>DB 명</th>
+                <td>
+                  <input id="databaseNm" type="text" @input="successAt = false" class="half-input" v-model="currentConnectionDB.databaseNm" placeholder="데이터베이스명 OR 스키마명" :disabled="jobItm.itm_option_bool" />
+                </td>
+              </tr>
+              <tr>
+                <th>접속ID</th>
+                <td>
+                  <input type="text" class="half-input" @input="successAt = false" v-model="currentConnectionDB.userId" placeholder="접속 ID" :disabled="jobItm.itm_option_bool" />
+                </td>
+              </tr>
+              <tr>
+                <th>접속PW</th>
+                <td>
+                  <input type="password" class="half-input" @input="successAt = false" v-model="currentConnectionDB.userPassword" placeholder="접속 PW" autocomplete="new-password" :disabled="jobItm.itm_option_bool" />
+                </td>
+              </tr>
+            </tbody>
+          </table>
+          <!-- 데이터베이스 연결 결과 -->
+          <div class="content-titleZone flex justy justify-between align-center mt20">
+            <p class="box-title">데이터베이스 연결 결과</p>
+            <button class="blue-border-btn small-btn" @click="dataBaseConnectionCheck">접속확인</button>
+          </div>
+          <div class="table-zone">
+            <table class="list-table">
+              <colgroup>
+                <col width="10%" />
+                <col width="70%" />
+                <col width="20%" />
+              </colgroup>
+              <thead>
+                <tr>
+                  <th>No</th>
+                  <th>접속결과</th>
+                  <th>접속시간</th>
+                </tr>
+              </thead>
+              <tbody>
+                <tr v-for="(itm, idx) in resultMessage" :key="idx">
+                  <td>{{ idx + 1 }}</td>
+                  <td>{{ itm.message }}</td>
+                  <td>{{ itm.time }}</td>
+                </tr>
+              </tbody>
+            </table>
+          </div>
+        </template>
+        <!-- 페이지 2: 쿼리 관리 -->
+        <template v-if="currentPage == 2">
+          <div class="flex content-box" style="min-height: 60dvh; flex-wrap: nowrap;">
+            <div class="content-box pd10" style="width: 20%;">
+              <div class="content-titleZone flex justy justify-between align-center" style="height: 45px">
+                <p class="box-title">데이터베이스 정보</p>
+              </div>
+              <div class="content-zone2">
+                <ul class="content-list" v-if="tableList.length > 0">
+                  <li class="cursor" v-for="(item, indx) in tableList" :key="indx">
+                    <a @click="getTableData(item)" :class="{
+                      'file-list': true,
+                      selected: selectTable === item,
+                    }">
+                      <div class="flex align-center">
+                        <p>{{ getTableDisplayName(item) }}</p>
+                      </div>
+                    </a>
+                  </li>
+                </ul>
+              </div>
+            </div>
+            <div class="content-box" style="width: 80%;">
+              <div class="flex-column">
+                <div class="content-box pd10" style="height: 50%;">
+                  <div class="content-titleZone flex justy justify-between align-center" style="height: 45px">
+                    <p class="box-title">쿼리 작업</p>
+                    <button class="icon-btn" @click="executeQuery" title="실행">
+                      <svg-icon type="mdi" :path="playPath" :color="'#fbbe28'"></svg-icon>
+                    </button>
+                  </div>
+                  <div class="flex" style="height: calc(100% - 60px)">
+                    <textarea style="resize: none; width: 100%; height: 100%; padding: 10px;" v-model="jobItm.itm.query"></textarea>
+                  </div>
+                </div>
+                <div class="content-box pd10" style="height: 50%;">
+                  <ul class="tab-nav flex justify-start">
+                    <li @click="showTab('tab1')">
+                      <a href="#tab01" :class="{ activeTab: activeTab === 'tab1' }">작업결과</a>
+                    </li>
+                    <li @click="showTab('tab2')">
+                      <a href="#tab02" :class="{ activeTab: activeTab === 'tab2' }">작업log</a>
+                    </li>
+                  </ul>
+                  <div v-show="activeTab === 'tab1'" class="content-box" style="height: calc(100% - 45px); padding: 10px;">
+                    <div class="count-zone mb10" v-if="jobItm.dataTable.columnDatas.length > 0">
+                      <p>총 <span>{{ jobItm.dataTable.totalRows }}</span>건 중 <span>{{ jobItm.dataTable.rowData.length }}</span>건 조회</p>
+                    </div>
+                    <div style="height: calc(100% - 15px); overflow: auto;">
+                      <table class="list-table">
+                        <thead>
+                          <tr v-if="jobItm.dataTable.columnDatas.length > 0">
+                            <th v-for="(itm, indx) in jobItm.dataTable.columnDatas" :key="indx" style="min-width: 150px !important"> {{ itm.columnNm }} <label class="check-label">
+                                <input type="checkbox" class="custom-checkbox" v-model="itm.pkAt" />
+                              </label>
+                            </th>
+                          </tr>
+                        </thead>
+                        <tbody v-if="jobItm.dataTable.rowData.length > 0">
+                          <tr v-for="(row, rows) in jobItm.dataTable.rowData" :key="rows">
+                            <td v-for="(itm, indx) in row" :key="indx" style="overflow: hidden; white-space: nowrap; text-overflow: ellipsis;"> {{ itm }} </td>
+                          </tr>
+                        </tbody>
+                      </table>
+                    </div>
+                  </div>
+                  <div v-show="activeTab === 'tab2'" class=" content-box" style="height: calc(100% - 45px); padding: 10px;">
+                    <div style="height: calc(100% - 15px); overflow: auto;">
+                      <table class="list-table">
+                        <colgroup>
+                          <col width="10%" />
+                          <col width="60%" />
+                          <col width="20%" />
+                          <col width="10%" />
+                        </colgroup>
+                        <thead>
+                          <tr>
+                            <th>No</th>
+                            <th>접속내용</th>
+                            <th>접속시간</th>
+                            <th>접속결과</th>
+                          </tr>
+                        </thead>
+                        <tbody>
+                          <tr v-for="(itm, indx) in executeMessage" :key="indx">
+                            <td>{{ indx + 1 }}</td>
+                            <td>{{ itm.message }}</td>
+                            <td>{{ itm.time }}</td>
+                            <td>{{ itm.result }}</td>
+                          </tr>
+                        </tbody>
+                      </table>
+                    </div>
+                  </div>
+                </div>
+              </div>
+            </div>
+          </div>
+        </template>
+      </div>
+      <!-- 모달 하단 버튼 영역 -->
+      <div class="modal-end flex justify-end">
+        <button class="blue-border-btn small-btn" v-if="currentPage == 2" @click="moveTo('prev')">이전</button>
+        <button class="blue-border-btn small-btn" v-if="currentPage == 1" @click="moveTo('next')">다음</button>
+        <button class="blue-btn small-btn" v-if="isDataSet" @click="fnSave">등록</button>
+        <button class="blue-border-btn small-btn" @click="$emit('onClose')">취소</button>
+      </div>
+    </div>
+  </div>
+  <DBConSearch :openPopup="openSearchModal" @modalclose="dbConSearchOpen" @selectItm="selectDbcon" />
+</template>
+<script>
+import _ from "lodash";
+import axios from "axios";
+
+// icon용 svg import
+import SvgIcon from "@jamescoyle/vue-icon";
+import { mdiMagnify, mdiClose, mdiPlay } from "@mdi/js";
+
+// 컴포넌트
+import DBConSearch from "../../dataComponent/DbConnectionSearchModal.vue";
+
+export default {
+  name: "DatabaseConnectionModal",
+
+  components: {
+    SvgIcon,
+    DBConSearch
+  },
+
+  props: {
+    currentJobItm: String,
+    jobItem: Object
+  },
+
+  data() {
+    return {
+      // icon용 svg path
+      searchPath: mdiMagnify,
+      closePath: mdiClose,
+      playPath: mdiPlay,
+
+      // 페이지 관련
+      currentPage: 1,
+      activeTab: "tab1",
+
+      // 상태 관리
+      successAt: false, // 연결 성공 여부
+      isDataSet: false, // 데이터 설정 여부 추가
+
+      // 데이터 객체들
+      jobItm: {
+        itm: null,
+        itm_option_bool: false,
+        dataTable: {
+          columnDatas: [],
+          rowData: [],
+          totalRows: 0,
+          perPage: 10,
+          query: ""
+        }
+      },
+
+      // 연계정보 관리
+      openSearchModal: false,
+      linkConnectionDB: {},
+      inputConnectionDB: {
+        databaseType: "",
+        conectIp: "",
+        conectPort: "",
+        databaseNm: "",
+        userId: "",
+        userPassword: ""
+      },
+      // 현재 표시되는 연결 정보 (직접입력/불러오기 모두 대응)
+      currentConnectionDB: {
+        databaseType: "",
+        conectIp: "",
+        conectPort: "",
+        databaseNm: "",
+        userId: "",
+        userPassword: ""
+      },
+
+      // 데이터 목록
+      databaseTypeList: [],
+      tableList: [],
+      selectTable: {},
+
+      // 로그 메시지
+      resultMessage: [],
+      executeMessage: []
+    };
+  },
+
+  watch: {
+    jobItem: {
+      handler(newVal) {
+        if (newVal) {
+          this.jobItm = _.cloneDeep(newVal);
+          if (this.jobItm.itm_option_bool) {
+            this.linkConnectionDB = this.jobItm.itm || {};
+            this.currentConnectionDB = _.cloneDeep(this.linkConnectionDB);
+          } else {
+            this.inputConnectionDB = this.jobItm.itm || {};
+            this.currentConnectionDB = _.cloneDeep(this.inputConnectionDB);
+          }
+        }
+      },
+      immediate: true
+    },
+  },
+
+  created() {
+    this.initializeComponent();
+  },
+
+  mounted() {
+    this.init();
+  },
+
+  methods: {
+    // 초기화 함수들
+    initializeComponent() {
+      if (!this.$isEmpty(this.currentJobItm)) {
+        let currentJobItm = JSON.parse(this.currentJobItm);
+        if (!this.$isEmpty(currentJobItm)) {
+          this.jobItm = _.cloneDeep(currentJobItm);
+        }
+      }
+
+      if (!this.jobItm.itm) {
+        this.jobItm.itm = _.cloneDeep(this.$getDefaultJobGroup().connectionDb || {});
+      }
+    },
+
+    async init() {
+      this.databaseTypeList = await this.$getDataBaseTypeList();
+
+      if (this.jobItm == null) {
+        this.jobItm = _.cloneDeep(this.$getDefaultJobGroup().node || {});
+        this.linkConnectionDB = _.cloneDeep(this.$getDefaultJobGroup().connectionDb || {});
+        this.inputConnectionDB = _.cloneDeep(this.$getDefaultJobGroup().connectionDb || {});
+        this.jobItm.itm = _.cloneDeep(this.$getDefaultJobGroup().connectionDb || {});
+      } else {
+        if (this.jobItm.itm_option_bool) {
+          this.linkConnectionDB = _.cloneDeep(this.jobItm.itm) || {};
+          this.currentConnectionDB = _.cloneDeep(this.linkConnectionDB);
+        } else {
+          this.inputConnectionDB = _.cloneDeep(this.jobItm.itm) || {};
+          this.currentConnectionDB = _.cloneDeep(this.inputConnectionDB);
+        }
+      }
+
+      if (!this.inputConnectionDB.databaseType && this.databaseTypeList.MARIADB) {
+        this.inputConnectionDB.databaseType = this.databaseTypeList.MARIADB.key;
+        if (!this.jobItm.itm_option_bool) {
+          this.currentConnectionDB.databaseType = this.databaseTypeList.MARIADB.key;
+        }
+      }
+    },
+
+    // 연결 타입 변경 시(직접입력/불러오기) 입력 필드 업데이트
+    onConnectionTypeChange(isImport) {
+      this.successAt = false;
+      if (isImport) {
+        // 불러오기 선택 시
+        this.currentConnectionDB = _.cloneDeep(this.linkConnectionDB);
+      } else {
+        // 직접입력 선택 시
+        this.currentConnectionDB = _.cloneDeep(this.inputConnectionDB);
+      }
+    },
+
+    // 유틸리티 함수들
+    getConnectionDisplayText() {
+      return this.linkConnectionDB.conectNm
+        ? `${this.linkConnectionDB.conectNm}(${this.linkConnectionDB.conectIp})`
+        : "";
+    },
+
+    getTableDisplayName(item) {
+      return item.tableNmKr && item.tableNmKr !== "" ? item.tableNmKr : item.tableNm;
+    },
+
+    // 데이터베이스 연결 관리
+    dataBaseConnectionCheck() {
+      if (this.jobItm.itm_option_bool) {
+        // 불러오기 모드일 때 linkConnectionDB 사용
+        this.jobItm.itm = _.cloneDeep(this.linkConnectionDB);
+      } else {
+        // 직접입력 모드일 때 currentConnectionDB에서 업데이트된 값 가져옴
+        this.inputConnectionDB = _.cloneDeep(this.currentConnectionDB);
+        this.jobItm.itm = _.cloneDeep(this.inputConnectionDB);
+      }
+
+      this.jobItm.itm.loadAt = this.jobItm.itm_option_bool;
+
+      if (this.jobItm.itm.type) {
+        delete this.jobItm.itm.type;
+      }
+
+      axios({
+        url: "/data/dataBaseConnectionCheck.json",
+        method: "post",
+        headers: { "Content-Type": "application/json; charset=UTF-8" },
+        data: this.jobItm.itm
+      })
+        .then(response => {
+          const checkMessage = response.data.checkMessage || {};
+          this.successAt = checkMessage.success === true;
+          this.$emit("onDfltSetChange", checkMessage.success);
+          this.$showAlert("결과내용", checkMessage.message);
+
+          this.resultMessage.push({
+            time: this.$getFullTime(),
+            message: checkMessage.message,
+            result: checkMessage.success ? "접속성공" : "접속실패"
+          });
+        })
+        .catch(error => {
+          console.error("Database connection check error:", error);
+          this.successAt = false;
+          this.$emit("onDfltSetChange", false);
+        });
+    },
+
+    // 연계정보 관리
+    dbConSearchOpen(val) {
+      this.openSearchModal = val;
+    },
+
+    selectDbcon(dbcon) {
+      this.linkConnectionDB = _.cloneDeep(dbcon);
+      // 불러오기 모드이면 현재 표시되는 필드 업데이트
+      if (this.jobItm.itm_option_bool) {
+        this.currentConnectionDB = _.cloneDeep(this.linkConnectionDB);
+      }
+    },
+
+    getTableData(item) {
+      this.selectTable = item;
+
+      const requestData = {
+        dataset: { ...item, perPage: this.jobItm.dataTable.perPage },
+        connectionDB: { ...this.jobItm.itm, creatDt: null }
+      };
+
+      axios({
+        url: "/data/getTableData.json",
+        method: "post",
+        headers: { "Content-Type": "application/json; charset=UTF-8" },
+        data: requestData
+      })
+        .then(response => {
+          if (response.data.checkMessage.success) {
+            this.jobItm.dataTable = response.data.resultData.dataTable;
+            this.jobItm.itm_id = this.jobItm.itm.dbConectId;
+            this.jobItm.itm.query = this.jobItm.dataTable.query;
+            this.isDataSet = true;
+          } else {
+            this.isDataSet = false;
+          }
+
+          const checkMessage = response.data.resultData.dataTable.checkMessage || {};
+          this.executeMessage.push({
+            time: this.$getFullTime(),
+            message: checkMessage.message,
+            result: checkMessage.success ? "성공" : "실패"
+          });
+        })
+        .catch(error => {
+          console.error("Get table data error:", error);
+          this.isDataSet = false;
+        });
+    },
+
+    executeQuery() {
+      const requestData = {
+        dataTable: {
+          ...this.jobItm.dataTable,
+          query: this.jobItm.itm.query
+        },
+        connectionDB: {
+          ...this.jobItm.itm,
+          creatDt: null
+        }
+      };
+
+      axios({
+        url: "/data/getTableDataByQuery.json",
+        method: "post",
+        headers: { "Content-Type": "application/json; charset=UTF-8" },
+        data: requestData
+      })
+        .then(response => {
+          if (response.data.checkMessage.success) {
+            this.jobItm.dataTable = response.data.resultData.dataTable;
+            this.jobItm.itm_id = this.jobItm.itm.dbConectId;
+            this.jobItm.itm.query = this.jobItm.dataTable.query;
+            this.isDataSet = true;
+          } else {
+            this.isDataSet = false;
+          }
+
+          const checkMessage = response.data.resultData.dataTable.checkMessage || {};
+          this.executeMessage.push({
+            time: this.$getFullTime(),
+            message: checkMessage.message,
+            result: checkMessage.success ? "성공" : "실패"
+          });
+        })
+        .catch(error => {
+          console.error("Execute query error:", error);
+          this.isDataSet = false;
+        });
+    },
+
+    showTab(tabName) {
+      this.activeTab = tabName;
+    },
+
+    moveTo(type) {
+      if (type === 'prev') {
+        this.currentPage = 1;
+        this.tableList = []; // 초기화
+        this.jobItm.dataTable = {
+          columnDatas: [],
+          rowData: [],
+          totalRows: 0,
+          perPage: 10,
+          query: ""
+        }; // 초기화
+      } else if (type === 'next') {
+        if (!this.successAt) {
+          this.$showAlert("경고", "접속에 성공한 경우에만 진행할 수 있습니다.");
+        } else {
+          this.getDbConnectionTableList(); // 테이블 및 쿼리 관리
+        }
+      } else {
+        this.$showAlert("오류", "올바르지 않은 요청입니다.");
+      }
+    },
+
+    // 테이블 및 쿼리 관리
+    getDbConnectionTableList() {
+      axios({
+        url: "/data/selectTableList.json",
+        method: "post",
+        headers: { "Content-Type": "application/json; charset=UTF-8" },
+        data: this.jobItm.itm
+      })
+        .then(response => {
+          if (response.data.checkMessage.success) {
+            this.tableList = response.data.resultData.tableList || [];
+            this.currentPage = 2;
+          }
+        })
+        .catch(error => {
+          console.error("Get table list error:", error);
+          this.tableList = [];
+        });
+    },
+
+    // 저장
+    fnSave() {
+      this.$emit("onSave", this.jobItm);
+    }
+  }
+};
+</script>
+<style>
+.content-zone2 {
+  height: calc(100% - 57px);
+  max-height: calc(100% - 57px);
+  overflow-y: auto;
+  overflow-x: hidden;
+}
+</style>(파일 끝에 줄바꿈 문자 없음)
client/views/component/treeMenu/FileTreeModal.vue
--- client/views/component/treeMenu/FileTreeModal.vue
+++ client/views/component/treeMenu/FileTreeModal.vue
@@ -6,14 +6,12 @@
           <h2>폴더 선택</h2>
           <button class="close-btn" @click="closeModal()"><svg-icon type="mdi" :width="20" :height="20" :path="closePath"></svg-icon></button>
         </div>
-        <div>
-          <h2>현재 경로</h2>
-          <p>
-            <span>{{ selectedPath }}</span>
-          </p>
-        </div>
       </div>
       <div class="modal-content-monthly">
+        <div class="content-titleZone flex justify-between align-center align-start">
+          <p class="box-title">현재 경로</p>
+          <p>{{ selectedPath }}</p>
+        </div>
         <TreeItem :connection="modalConnection" :closeModal="isCloseModal" v-for="(item, idx) in modalNodes" :item="item" :idx="item.id" :key="idx" @selectFolder="selectFolder" @selectItem="handleSelectItem" />
       </div>
       <div class="modal-end flex justify-end">
@@ -28,7 +26,6 @@
   </div>
 </template>
 <script>
-import axios from 'axios';
 import SvgIcon from '@jamescoyle/vue-icon';
 import TreeItem from './FileTree.vue';
 import { mdiClose } from '@mdi/js';
client/views/pages/AppRouter.js
--- client/views/pages/AppRouter.js
+++ client/views/pages/AppRouter.js
@@ -73,16 +73,17 @@
   { path: "/push.page", name: "Push", component: Push },
   { path: "/insertDBConnection.page", name: "InsertDBConnection", component: InsertDBConnection },
   { path: "/DBConnectionDetail.page", name: "DBConnectionDetail", component: DBConnectionDetail },
-  { path: "/openApiList.page", name: "OpenApiList", component: OpenApiList },
-  { path: "/openApiInsert.page", name: "OpenApiInsert", component: OpenApiInsert },
-  { path: "/openApiListOne.page", name: "OpenApiSelectListOne", component: OpenApiSelectListOne },
-  { path: "/openApiKeyList.page", name: "OpenApiKeyList", component: OpenApiKeyList },
   { path: "/gisInfoList.page", name: "GisInfoList", component: GisInfoList },
   { path: "/gisInfoInsert.page", name: "GisInfoInsert", component: GisInfoInsert },
   { path: "/gisInfoListOne.page", name: "GisInfoSelectListOne", component: GisInfoSelectListOne },
   { path: "/myPage.page", name: "myPage", component: myPage },
   { path: "/myPagePwd.page", name: "myPagePwd", component: myPagePwd },
   { path: "/login.page", name: "Login", component: Login },
+  // OPEN API
+  { path: "/openApiList.page", name: "OpenApiList", component: OpenApiList },
+  { path: "/openApiInsert.page", name: "OpenApiInsert", component: OpenApiInsert },
+  { path: "/openApiListOne.page", name: "OpenApiSelectListOne", component: OpenApiSelectListOne },
+  { path: "/openApiKeyList.page", name: "OpenApiKeyList", component: OpenApiKeyList },
   // 데이터활용관리
   { path: "/customSelectList.page", name: "CustomSelectList", component: CustomSelectList },
   { path: "/customSelectOne.page", name: "CustomSelectOne", component: CustomSelectOne },
client/views/pages/data/filemanger/FileManagementMain.vue
--- client/views/pages/data/filemanger/FileManagementMain.vue
+++ client/views/pages/data/filemanger/FileManagementMain.vue
@@ -1210,32 +1210,38 @@
     async fileConfirm(type) {
       const vm = this;
 
-      if (
-        !(await vm.$showConfirm(
-          "파일 " + type,
-          "선택한 파일을 " + type + " 하시겠습니까?"
-        ))
-      ) {
+      if (!(await vm.$showConfirm("파일 " + type, "선택한 파일을 " + type + " 하시겠습니까?"))) {
         return;
       }
 
       let file = vm.selectedFiles[0];
       let index = file.path.lastIndexOf("/");
 
-      if (file.path.substring(0, index) === file.movePath) {
-        vm.$showAlert(
-          "파일 " + type,
-          "현재 파일 경로와 동일합니다. 다른 경로를 선택해주세요."
-        );
+      // modalSeletedNode가 없으면 오류 메시지 표시
+      if (!vm.modalSeletedNode) {
+        vm.$showAlert("파일 " + type, "대상 폴더를 선택해주세요.");
         return;
       }
 
+      // 현재 경로와 대상 경로가 같은지 확인
+      if (file.path.substring(0, index) === vm.modalSeletedNode) {
+        vm.$showAlert("파일 " + type, "현재 파일 경로와 동일합니다. 다른 경로를 선택해주세요.");
+        return;
+      }
+
+      let result;
       if (type === "복사") {
-        vm.copy(vm.selectedFiles, "confirm", false);
+        result = await vm.copy(vm.selectedFiles, "confirm", false);
       } else {
-        vm.move(vm.selectedFiles, [], "confirm", false);
+        result = await vm.move(vm.selectedFiles, [], "confirm", false);
+      }
+
+      // 작업이 취소되었거나 실패한 경우 추가 메시지를 표시하지 않음
+      if (result === false) {
+        return;
       }
     },
+
     async move(fileList, removeFolderList, type, check) {
       const vm = this;
 
@@ -1265,7 +1271,15 @@
             "파일 이동",
             response.data.checkMessage.message
           );
-          vm.move(
+
+          // 취소 버튼 클릭 시 처리
+          if (moveOrCopy && moveOrCopy.type === "cancel") {
+            vm.closeTreeModal();
+            return false; // 취소 시 false 반환
+          }
+
+          // 계속 진행
+          return vm.move(
             returnFileList,
             folderList,
             moveOrCopy.type,
@@ -1283,6 +1297,7 @@
           }
           vm.$showAlert("파일 이동", response.data.checkMessage.message);
           vm.closeTreeModal();
+          return true; // 성공 시 true 반환
         }
       } catch (error) {
         vm.isLoading = false; // 로딩 해제
@@ -1290,6 +1305,7 @@
         vm.checkAll = false;
         vm.selectedFiles = [];
         vm.closeTreeModal();
+        return false; // 오류 시 false 반환
       }
     },
 
@@ -1316,23 +1332,29 @@
             "파일 복사",
             response.data.checkMessage.message
           );
-          if (!moveOrCopy) {
-            return;
-          } else {
-            vm.copy(returnFileList, moveOrCopy.type, moveOrCopy.checkBox);
+
+          // 취소 버튼 클릭 시 처리
+          if (moveOrCopy && moveOrCopy.type === "cancel") {
+            vm.closeTreeModal();
+            return false; // 취소 시 false 반환
           }
+
+          // 계속 진행
+          return vm.copy(returnFileList, moveOrCopy.type, moveOrCopy.checkBox);
         } else {
           vm.$showAlert("파일 복사", response.data.checkMessage.message);
           vm.checkAll = false;
           vm.selectedFiles = [];
           vm.closeTreeModal();
+          return true; // 성공 시 true 반환
         }
       } catch (error) {
         vm.isLoading = false; // 로딩 해제
-        vm.$showAlert("파일 복사", "파일 이동 오류, 관리자에게 문의하세요.");
+        vm.$showAlert("파일 복사", "파일 복사 오류, 관리자에게 문의하세요.");
         vm.checkAll = false;
         vm.selectedFiles = [];
         vm.closeTreeModal();
+        return false; // 오류 시 false 반환
       }
     },
 
Add a comment
List