박정하 박정하 03-25
250325 박정하 사진 기록물 등록 1차
@87623fa3adfc61e6de6e14dc5891d1fb67b328c2
client/resources/api/category.js
--- client/resources/api/category.js
+++ client/resources/api/category.js
@@ -1,6 +1,11 @@
 import apiClient from "./index";
 
 // 카테고리 목록 조회 (검색조건 없음)
-export const findAllCategoryProc = () => {
+export const findAllByNullProc = () => {
   return apiClient.get(`/category/findAllByNull.json`);
+}
+
+// 카테고리 목록 조회 (검색조건 있음)
+export const findAllCategoryProc = () => {
+  return apiClient.get(`/category/findAllCategory.json`);
 }
(파일 끝에 줄바꿈 문자 없음)
 
client/resources/css/ckeditor.css (added)
+++ client/resources/css/ckeditor.css
@@ -0,0 +1,9 @@
+.ck.ck-editor {
+  width: 100%;
+}
+
+.ck.ck-content {
+  width: 100%;
+  height: 425px;
+  box-sizing: border-box;
+}(파일 끝에 줄바꿈 문자 없음)
 
client/views/component/EditorComponent.vue (added)
+++ client/views/component/EditorComponent.vue
@@ -0,0 +1,172 @@
+<template>
+  <ckeditor v-if="editor && config" :model-value="editorContent" :editor="editor" :config="config" @update:model-value="updateContents" />
+</template>
+<script>
+/**
+ * This configuration was generated using the CKEditor 5 Builder. You can modify it anytime using this link:
+ * https://ckeditor.com/ckeditor-5/builder/#installation/NoNgNARATAdAzPCkCMAGKIAcmCsrdwghzICcUqqyyALESDnKXFKW6djTdkhANYB7JKjDBkYEZIlhkAXUggAZmxwBjVRFlA==
+ */
+
+import { Ckeditor } from '@ckeditor/ckeditor5-vue';
+
+import {
+  ClassicEditor,
+  Alignment,
+  AutoLink,
+  BlockQuote,
+  Bold,
+  Essentials,
+  FontBackgroundColor,
+  FontColor,
+  FontFamily,
+  FontSize,
+  HorizontalLine,
+  ImageEditing,
+  ImageUtils,
+  Indent,
+  IndentBlock,
+  Italic,
+  Link,
+  Paragraph,
+  RemoveFormat,
+  Strikethrough,
+  Subscript,
+  Superscript,
+  Table,
+  TableCaption,
+  TableCellProperties,
+  TableColumnResize,
+  TableProperties,
+  TableToolbar,
+  TextPartLanguage,
+  Underline,
+} from 'ckeditor5';
+
+import translations from 'ckeditor5/translations/ko.js';
+
+import 'ckeditor5/ckeditor5.css';
+
+/**
+ * Create a free account with a trial: https://portal.ckeditor.com/checkout?plan=free
+ */
+const LICENSE_KEY = 'GPL'; // or <YOUR_LICENSE_KEY>.
+
+export default {
+  name: 'EditorComponent',
+  components: {
+    Ckeditor
+  },
+  props: {
+    contents: {
+      type: String,
+      default: ''
+    }
+  },
+  emits: ['update:contents'],
+  data() {
+    return {
+      isLayoutReady: false,
+      editor: ClassicEditor,
+    };
+  },
+  computed: {
+    editorContent() {
+      // contents가 null 또는 undefined인 경우 빈 문자열 반환
+      return this.contents || '';
+    },
+    config() {
+      if (!this.isLayoutReady) {
+        return null;
+      }
+
+      return {
+        toolbar: {
+          items: [
+            'fontSize',
+            'fontFamily',
+            'fontColor',
+            'fontBackgroundColor',
+            '|',
+            'bold',
+            'italic',
+            'underline',
+            'strikethrough',
+            'subscript',
+            'superscript',
+            'removeFormat',
+            '|',
+            'horizontalLine',
+            'link',
+            'insertTable',
+            'blockQuote',
+            '|',
+            'alignment',
+            '|',
+            'outdent',
+            'indent'
+          ],
+          shouldNotGroupWhenFull: true
+        },
+        plugins: [
+          Alignment,
+          AutoLink,
+          BlockQuote,
+          Bold,
+          Essentials,
+          FontBackgroundColor,
+          FontColor,
+          FontFamily,
+          FontSize,
+          HorizontalLine,
+          ImageEditing,
+          ImageUtils,
+          Indent,
+          IndentBlock,
+          Italic,
+          Link,
+          Paragraph,
+          RemoveFormat,
+          Strikethrough,
+          Subscript,
+          Superscript,
+          Table,
+          TableCaption,
+          TableCellProperties,
+          TableColumnResize,
+          TableProperties,
+          TableToolbar,
+          TextPartLanguage,
+          Underline,
+        ],
+        fontFamily: {
+          supportAllValues: true
+        },
+        fontSize: {
+          options: [10, 12, 14, 'default', 18, 20, 22],
+          supportAllValues: true
+        },
+        language: 'ko',
+        licenseKey: LICENSE_KEY,
+        link: {
+          addTargetToExternalLinks: true,
+          defaultProtocol: 'https://',
+        },
+        placeholder: '내용을 입력하세요.',
+        table: {
+          contentToolbar: ['tableColumn', 'tableRow', 'mergeTableCells', 'tableProperties', 'tableCellProperties']
+        },
+        translations: [translations],
+      };
+    }
+  },
+  mounted() {
+    this.isLayoutReady = true;
+  },
+  methods: {
+    updateContents(data) {
+      // 에디터 내용이 변경될 때 부모 컴포넌트에 알림
+      this.$emit('update:contents', data);
+    }
+  }
+};
+</script>(파일 끝에 줄바꿈 문자 없음)
 
client/views/component/modal/CategorySelectModal.vue (added)
+++ client/views/component/modal/CategorySelectModal.vue
@@ -0,0 +1,66 @@
+<template>
+  <div class="modal-overlay" @click="closeModal">
+    <div class="modal-content" @click.stop>
+      <div class="flex-sp-bw mb-20">
+        <h2>카테고리 조회</h2>
+        <button @click="closeModal" class="closebtn">✕</button>
+      </div>
+      <div class="modal-search flex-center mb-20">
+        <input type="text" placeholder="카테고리명을 입력하세요.">
+        <button class="search-btn"><img :src="searchicon" alt="">
+          <p>검색</p>
+        </button>
+      </div>
+      <table class="mb-10">
+        <thead>
+          <tr>
+            <th>카테고리 항목</th>
+            <th>선택</th>
+          </tr>
+        </thead>
+        <tbody>
+          <tr v-for="item in items" :key="item.id">
+            <!-- Category 칼럼 -->
+            <td> {{ item.category }} </td>
+            <!-- Checkbox 칼럼 -->
+            <td>
+              <input type="checkbox" v-model="item.selected" />
+            </td>
+          </tr>
+        </tbody>
+      </table>
+      <div class="flex-end mb-30"><button class="register-b " @click="registerCategories">등록</button></div>
+      <div class="pagination">
+        <!-- Previous and Next Page Buttons -->
+        <button>
+          <DoubleLeftOutlined />
+        </button>
+        <button @click="previousPage" :disabled="currentPage === 1">
+          <LeftOutlined />
+        </button>
+        <button class="page-number clicked">1</button>
+        <button @click="nextPage" :disabled="currentPage === totalPages">
+          <RightOutlined />
+        </button>
+        <button>
+          <DoubleRightOutlined />
+        </button>
+      </div>
+    </div>
+  </div>
+</template>
+<script>
+// 모달 사용 용례
+// selectedCtgries: 부모 요소에 저장된 '선택한 카테고리' 목록
+// @toggleModal: 모달 열고 닫는 함수 (호출만 해도 됨/열려있는 경우 닫힘, 닫혀있는 경우 열림)
+export default {
+  name: 'CategorySelectModal',
+  props: {
+    selectedCtgries: {
+      type: Array,
+      default: [],
+    }
+  },
+}
+</script>
+<style></style>(파일 끝에 줄바꿈 문자 없음)
client/views/index.html
--- client/views/index.html
+++ client/views/index.html
@@ -5,12 +5,6 @@
   <meta http-equiv="X-UA-Compatible" content="IE=edge">
   <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
   <meta name="description" content="Node Vue Web">
-  <link rel="stylesheet" href="../client/resources/css/common/common.css">
-  <link rel="stylesheet" href="../client/resources/css/common/font.css">
-  <link rel="stylesheet" href="../client/resources/css/user/board.css">
-  <link rel="stylesheet" href="../client/resources/css/user/layout.css">
-  <link rel="stylesheet" href="../client/resources/css/user/main.css">
-  <link rel="stylesheet" href="../client/resources/css/user/sub.css">
   <title>구미시 디지털 아카이브</title>
 </head>
 <body>
client/views/index.js
--- client/views/index.js
+++ client/views/index.js
@@ -4,6 +4,15 @@
 import Store from "./pages/AppStore.js";
 import Router from "./pages/AppRouter.js";
 
+// Style import
+import '../resources/css/common/common.css';
+import '../resources/css/common/font.css';
+import '../resources/css/user/board.css';
+import '../resources/css/user/layout.css';
+import '../resources/css/user/main.css';
+import '../resources/css/user/sub.css';
+import '../resources/css/ckeditor.css';
+
 async function initVueApp() {
   const vue = createApp(App)
     .use(Router)
client/views/pages/user/PicHistoryInsert.vue
--- client/views/pages/user/PicHistoryInsert.vue
+++ client/views/pages/user/PicHistoryInsert.vue
@@ -1,221 +1,174 @@
 <template>
-    <div class="content">
-        <div class="sub-title-area mb-30">
-            <h2>사진 기록물</h2>
-            <div class="breadcrumb-list">
-                <ul>
-                    <li><img :src="homeicon" alt="Home Icon">
-                        <p>기록물</p>
-                    </li>
-                    <li><img :src="righticon" alt=""></li>
-                    <li>사진 기록물</li>
-                </ul>
-            </div>
-        </div>
-        <form action="" class="insert-form mb-50">
-            <dl>
-                <dd>
-                    <label for="id" class="require">제목</label>
-                    <div class="wfull"><input type="text" id="id" value="" placeholder="제목을 입력하세요."></div>
-                </dd>
-                <div class="hr"></div>
-                <dd>
-                    <label for="year">생산연도</label>
-                    <input type="text" id="year" value="" placeholder="생산연도를 입력하세요">
-
-                </dd>
-                <div class="hr"></div>
-                <dd>
-                    <label for="address">주소</label>
-                    <div class="wfull"><input type="text" id="address" value="" placeholder="주소를 입력하세요"></div>
-
-                </dd>
-                <div class="hr"></div>
-                <dd>
-                    <label for="text">내용</label>
-                    <textarea name="" id=""></textarea>
-
-                </dd>
-                <div class="hr"></div>
-                <dd>
-                    <label for="category" class="flex align-center">
-                        <p>카테고리</p><button type="button" class="category-add" @click="openModal">추가하기</button>
-                    </label>
-                    <ul class="category">
-                        <li v-for="(category, index) in selectedCategories" :key="index" >
-                            {{ category }}
-                            <button type="button" class="cancel" @click="removeCategory(index)"><b>✕</b></button>
-                        </li>
-                    </ul>
-
-                </dd>
-                <div class="hr"></div>
-                <dd>
-                    <label for="file" class="require">파일</label>
-                    <ul class="wfull">
-                        <li class="flex align-center">
-                            <p>파일첨부</p>
-                            <div class="invalid-feedback"><img :src="erroricon" alt=""><span>첨부파일은 10건까지 등록 가능하며, 건당 최대
-                                    100MB를 초과할 수 없습니다.</span></div>
-                        </li>
-                        <li class="file-insert">
-                            <input type="file" id="fileInput" class="file-input" multiple @change="showFileNames">
-                            <label for="fileInput" class="file-label mb-20">
-                                <div class="flex-center align-center"><img :src="fileicon" alt="">
-                                    <p>파일첨부하기</p>
-                                </div>
-                                <p>파일을 첨부하시려면 이 영역으로 파일을 끌고 오거나 클릭해주세요</p>
-                            </label>
-                            <p class="mb-10">파일목록</p>
-                            <div id="fileNames" class="file-names">
-                                <span v-if="fileNames.length === 0">선택된 파일이 없습니다.</span>
-                                <div v-for="(file, index) in fileNames" :key="index" class="flex-sp-bw mb-5 file-wrap">
-                                    <div class="file-name">
-                                        <!-- Corrected here: Use file.icon instead of fileicons.img -->
-                                        <img :src="file.icon" alt="fileicon">
-                                        <p>{{ file.name }}</p>
-                                    </div>
-                                    <button type="button" class="cancel" @click="removeFile(index)"><b>✕</b></button>
-                                </div>
-                            </div>
-                        </li>
-
-                    </ul>
-
-                </dd>
-            </dl>
-        </form>
-
-        <div class="btn-group flex-center">
-            <button class="cancel">취소</button>
-            <button class="register">등록</button>
-        </div>
-        <div v-if="isModalOpen" class="modal-overlay" @click="closeModal">
-            <div class="modal-content" @click.stop>
-                <div class="flex-sp-bw mb-20">
-                    <h2>카테고리 조회</h2>
-                    <button @click="closeModal" class="closebtn">✕</button>
+  <div class="content">
+    <div class="sub-title-area mb-30">
+      <h2>사진 기록물</h2>
+      <div class="breadcrumb-list">
+        <ul>
+          <li><img :src="homeicon" alt="Home Icon">
+            <p>기록물</p>
+          </li>
+          <li><img :src="righticon" alt=""></li>
+          <li>사진 기록물</li>
+        </ul>
+      </div>
+    </div>
+    <form action="" class="insert-form mb-50">
+      <dl>
+        <dd>
+          <label for="id" class="require">제목</label>
+          <div class="wfull"><input type="text" id="id" placeholder="제목을 입력하세요."></div>
+        </dd>
+        <div class="hr"></div>
+        <dd>
+          <label for="year">생산연도</label>
+          <input type="text" id="year" placeholder="생산연도를 입력하세요">
+        </dd>
+        <div class="hr"></div>
+        <dd>
+          <label for="address">주소</label>
+          <div class="wfull"><input type="text" id="address" placeholder="주소를 입력하세요"></div>
+        </dd>
+        <div class="hr"></div>
+        <dd>
+          <label for="text">내용</label>
+          <div class="wfull">
+            <EditorComponent :contents="insertDTO.cn" />
+          </div>
+        </dd>
+        <div class="hr"></div>
+        <dd>
+          <label for="category" class="flex align-center">
+            <p>카테고리</p><button type="button" class="category-add" @click="openModal">추가하기</button>
+          </label>
+          <ul class="category">
+            <li v-for="(category, index) in selectedCtgries" :key="index"> {{ category }} <button type="button" class="cancel" @click="removeCategory(index)"><b>✕</b></button>
+            </li>
+          </ul>
+        </dd>
+        <div class="hr"></div>
+        <dd>
+          <label for="file" class="require">파일</label>
+          <ul class="wfull">
+            <li class="flex align-center">
+              <p>파일첨부</p>
+              <div class="invalid-feedback"><img :src="erroricon" alt=""><span>첨부파일은 10건까지 등록 가능하며, 건당 최대 100MB를 초과할 수 없습니다.</span></div>
+            </li>
+            <li class="file-insert">
+              <input type="file" id="fileInput" class="file-input" multiple @change="showFileNames">
+              <label for="fileInput" class="file-label mb-20">
+                <div class="flex-center align-center"><img :src="fileicon" alt="">
+                  <p>파일첨부하기</p>
                 </div>
-                <div class="modal-search flex-center mb-20">
-                    <input type="text" placeholder="카테고리명을 입력하세요.">
-                    <button class="search-btn"><img :src="searchicon" alt="">
-                        <p>검색</p>
-                    </button>
+                <p>파일을 첨부하시려면 이 영역으로 파일을 끌고 오거나 클릭해주세요</p>
+              </label>
+              <p class="mb-10">파일목록</p>
+              <div id="fileNames" class="file-names">
+                <span v-if="fileNames.length === 0">선택된 파일이 없습니다.</span>
+                <div v-for="(file, index) in fileNames" :key="index" class="flex-sp-bw mb-5 file-wrap">
+                  <div class="file-name">
+                    <!-- Corrected here: Use file.icon instead of fileicons.img -->
+                    <img :src="file.icon" alt="fileicon">
+                    <p>{{ file.name }}</p>
+                  </div>
+                  <button type="button" class="cancel" @click="removeFile(index)"><b>✕</b></button>
                 </div>
-                <table class="mb-10">
-                    <thead>
-                        <tr>
-                            <th>카테고리 항목</th>
-                            <th>선택</th>
-                        </tr>
-                    </thead>
-                    <tbody>
-                        <tr v-for="item in items" :key="item.id">
-                            <!-- Category 칼럼 -->
-                            <td>
-                                {{ item.category }}
-                            </td>
-                            <!-- Checkbox 칼럼 -->
-                            <td>
-                                <input type="checkbox" v-model="item.selected" />
-                            </td>
-                        </tr>
-                    </tbody>
-                </table>
-                <div class="flex-end mb-30"><button class="register-b " @click="registerCategories">등록</button></div>
-                <div class="pagination">
-    
-                    <!-- Previous and Next Page Buttons -->
-                    <button>
-                        <DoubleLeftOutlined />
-                    </button>
-                    <button @click="previousPage" :disabled="currentPage === 1">
-                        <LeftOutlined />
-                    </button>
-                    <button class="page-number clicked">1</button>
-                    <button @click="nextPage" :disabled="currentPage === totalPages">
-                        <RightOutlined />
-                    </button>
-                    <button>
-                        <DoubleRightOutlined />
-                    </button>
-                </div>
-            </div>
-        </div>
-  </div>
-  <div v-if="isModalOpen" class="modal-overlay" @click="closeModal">
-    <div class="modal-content" @click.stop>
-      <h2>모달 제목</h2>
-      <p>모달의 내용이 여기에 들어갑니다.</p>
-      <button @click="closeModal">닫기</button>
+              </div>
+            </li>
+          </ul>
+        </dd>
+      </dl>
+    </form>
+    <div class="btn-group flex-center">
+      <button class="cancel">취소</button>
+      <button class="register">등록</button>
     </div>
   </div>
+  <CategorySelectModal v-if="isModalOpen" :selectedCtgries="selectedCtgries" @toggleModal="fnToggleModal" />
 </template>
 <script>
 import { DoubleLeftOutlined, LeftOutlined, RightOutlined, DoubleRightOutlined } from '@ant-design/icons-vue';
+// COMPONENT
+import EditorComponent from '../../component/EditorComponent.vue';
+import CategorySelectModal from '../../component/modal/CategorySelectModal.vue';
 
 export default {
-    components: {
-        DoubleLeftOutlined,
-        LeftOutlined,
-        RightOutlined,
-        DoubleRightOutlined,
-    },
+  components: {
+    DoubleLeftOutlined,
+    LeftOutlined,
+    RightOutlined,
+    DoubleRightOutlined,
+    EditorComponent, CategorySelectModal,
+  },
 
-    data() {
-        return {
-            items: [
-                { id: 1, category: '카테고리 1', selected: false },
-                { id: 2, category: '카테고리 2', selected: false },
-                { id: 3, category: '카테고리 3', selected: false },
-            ],
-            isModalOpen: false,
-            // Define the image sources
-            homeicon: 'client/resources/images/icon/home.png',
-            erroricon: 'client/resources/images/icon/error.png',
-            righticon: 'client/resources/images/icon/right.png',
-            fileicon: 'client/resources/images/icon/file.png',
-            searchicon: 'client/resources/images/icon/search.png',
-            fileNames: [],
-            selectedCategories: [],
-        };
+  data() {
+    return {
+      // Define the image sources
+      homeicon: 'client/resources/images/icon/home.png',
+      erroricon: 'client/resources/images/icon/error.png',
+      righticon: 'client/resources/images/icon/right.png',
+      fileicon: 'client/resources/images/icon/file.png',
+      searchicon: 'client/resources/images/icon/search.png',
+
+      isModalOpen: false,
+
+      items: [
+        { id: 1, category: '카테고리 1', selected: false },
+        { id: 2, category: '카테고리 2', selected: false },
+        { id: 3, category: '카테고리 3', selected: false },
+      ],
+      fileNames: [],
+      insertDTO: {
+        sj: null, //제목
+        cn: null, //내용
+        adres: null, // 주소
+        prdctnYear: null, // 생산연도
+        ty: 'P', // 타입 ( P: 사진, V: 영상 )
+        multipartFiles: null, // 첨부파일 정보
+        ctgryIds: null, // 카테고리 정보
+      },
+
+      files: [],
+      selectedCtgries: [], // 카테고리 목록
+    };
+  },
+  computed: {
+    filteredItems() {
+      // This could be modified to support filtering based on searchQuery
+      return this.items.filter(item =>
+        item.category.includes(this.searchQuery)
+      );
+    }
+  },
+  created() {
+  },
+  methods: {
+    registerCategories() {
+      // Add selected categories to the displayed list
+      this.selectedCtgries = this.items
+        .filter(item => item.selected)
+        .map(item => item.category);
+      this.closeModal(); // Close modal after registration
     },
-    computed: {
-        filteredItems() {
-            // This could be modified to support filtering based on searchQuery
-            return this.items.filter(item =>
-                item.category.includes(this.searchQuery)
-            );
-        }
+    removeCategory(index) {
+      // Remove category from the list
+      this.selectedCtgries.splice(index, 1);
     },
-    methods: {
-        registerCategories() {
-            // Add selected categories to the displayed list
-            this.selectedCategories = this.items
-                .filter(item => item.selected)
-                .map(item => item.category);
-            this.closeModal(); // Close modal after registration
-        },
-        removeCategory(index) {
-            // Remove category from the list
-            this.selectedCategories.splice(index, 1);
-        },
-        searchCategories() {
-            // You can implement search logic if needed
-        },
-        nextPage() {
-            if (this.currentPage < this.totalPages) {
-                this.currentPage++;
-            }
-        },
-        previousPage() {
-            if (this.currentPage > 1) {
-                this.currentPage--;
-            }
-        },
-        showFileNames(event) {
-            const files = event.target.files;
-            this.fileNames = [];  // Clear previous file names
+    searchCategories() {
+      // You can implement search logic if needed
+    },
+    nextPage() {
+      if (this.currentPage < this.totalPages) {
+        this.currentPage++;
+      }
+    },
+    previousPage() {
+      if (this.currentPage > 1) {
+        this.currentPage--;
+      }
+    },
+    showFileNames(event) {
+      const files = event.target.files;
+      this.fileNames = [];  // Clear previous file names
 
       for (let i = 0; i < files.length; i++) {
         const file = files[i];
@@ -253,7 +206,7 @@
     // 모달 닫기
     closeModal() {
       this.isModalOpen = false;
-    }
+    },
   }
 };
 </script>
client/views/pages/user/TotalSearch.vue
--- client/views/pages/user/TotalSearch.vue
+++ client/views/pages/user/TotalSearch.vue
@@ -123,7 +123,7 @@
 <script>
 // 통합 검색
 import { findAllDatas } from "../../../resources/api/main";
-import { findAllCategoryProc } from "../../../resources/api/category"; // 카테고리 목록 검색
+import { findAllByNullProc } from "../../../resources/api/category"; // 카테고리 목록 검색
 
 export default {
   data() {
@@ -202,7 +202,7 @@
     // 카테고리 목록 조회
     async fnFindCategorys() {
       try {
-        const response = await findAllCategoryProc();
+        const response = await findAllByNullProc();
         this.categorys = response.data.data.ctgry;
       } catch (error) {
         this.categorys = []; // 카테고리 목록 초기화
package.json
--- package.json
+++ package.json
@@ -4,8 +4,10 @@
     "@ant-design/icons-vue": "^7.0.1",
     "@babel/cli": "^7.22.10",
     "@babel/core": "^7.22.10",
+    "@ckeditor/ckeditor5-vue": "^7.3.0",
     "axios": "^1.6.8",
     "babel-loader": "^9.1.3",
+    "ckeditor5": "^44.3.0",
     "css-loader": "6.7.1",
     "express": "4.18.1",
     "express-http-proxy": "^2.0.0",
@@ -13,7 +15,7 @@
     "fs": "0.0.1-security",
     "new-line": "^1.1.1",
     "swiper": "^11.2.5",
-    "vue": "3.2.40",
+    "vue": "3.5.13",
     "vue-loader": "^17.2.2",
     "vue-router": "^4.3.2",
     "vue3-youtube": "^0.1.9",
Add a comment
List