
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
File name
Commit message
Commit date
<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 class="insert-form mb-50" @submit.prevent>
<dl>
<dd>
<label for="sj" class="require">제목</label>
<div class="wfull"><input type="text" id="sj" placeholder="제목을 입력하세요." v-model="requestDTO.sj"></div>
</dd>
<div class="hr"></div>
<dd>
<label for="prdctnYear">생산연도</label>
<input type="text" id="prdctnYear" placeholder="생산연도를 입력하세요" v-model="requestDTO.prdctnYear">
</dd>
<div class="hr"></div>
<dd>
<label for="adres">주소</label>
<div class="wfull"><input type="text" id="adres" placeholder="주소를 입력하세요" v-model="requestDTO.adres"></div>
</dd>
<div class="hr"></div>
<dd>
<label for="text">내용</label>
<div class="wfull">
<EditorComponent v-model:contents="requestDTO.cn" />
</div>
</dd>
<div class="hr"></div>
<dd>
<label for="category" class="flex align-center">
<p>카테고리</p><button type="button" class="category-add" @click="fnToggleModal">추가하기</button>
</label>
<ul class="category">
<li v-for="(item, idx) of selectedCtgries" :key="idx">{{ item.ctgryNm }} <button type="button" class="cancel" @click="fnDelCtgry(item.ctgryId)"><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>첨부파일은 건당 최대 10GB를 초과할 수 없습니다.</span>
</div>
</li>
<li class="file-insert">
<input type="file" id="fileInput" class="file-input" multiple accept="image/jpeg,image/png,image/gif,image/jpg" @change="handleFileSelect">
<label for="fileInput" class="file-label mb-20" @dragover.prevent="handleDragOver" @dragleave.prevent="handleDragLeave" @drop.prevent="handleDrop" :class="{ 'drag-over': isDragging }">
<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">
<div v-if="requestDTO.files.length === 0 && multipartFiles.length === 0">선택된 파일이 없습니다.</div>
<!-- 새로 추가된 파일 목록 -->
<div v-for="(file, idx) of multipartFiles" :key="idx" class="flex-sp-bw mb-5 file-wrap">
<div class="file-name">
<img src="/client/resources/images/icon/imgicon.png" alt="fileicon">
<p>{{ file.name }}</p>
</div>
<button type="button" class="cancel" @click="fnDelFile('new', idx)"><b>✕</b></button>
</div>
<!-- 기존 등록된 파일 목록 -->
<div v-for="(file, idx) of requestDTO.files" :key="idx" class="flex-sp-bw mb-5 file-wrap">
<div class="file-name">
<img src="/client/resources/images/icon/imgicon.png" alt="fileicon">
<p>{{ file.fileNm }}</p>
</div>
<button type="button" class="cancel" @click="fnDelFile('old', file.fileId)"><b>✕</b></button>
</div>
</div>
</li>
</ul>
</dd>
</dl>
</form>
<div class="btn-group flex-center">
<button type="button" class="cancel" @click="fnMoveTo('list')">취소</button>
<button type="button" class="register" @click="submitForm">
<span v-if="$isEmpty(pageId)">등록</span>
<span v-else>수정</span>
</button>
</div>
</div>
<CategorySelectModal v-if="isModalOpen" :selectedCtgries="selectedCtgries" @toggleModal="fnToggleModal" @addCtgries="fnAddCtgries" />
</template>
<script>
import { DoubleLeftOutlined, LeftOutlined, RightOutlined, DoubleRightOutlined } from '@ant-design/icons-vue';
// COMPONENT
import EditorComponent from '../../../component/editor/EditorComponent.vue';
import CategorySelectModal from '../../../component/modal/CategorySelectModal.vue';
// API
import { findDcryProc, saveDcry, updateDcry } from '@/resources/api/dcry';
export default {
components: {
DoubleLeftOutlined,
LeftOutlined,
RightOutlined,
DoubleRightOutlined,
EditorComponent,
CategorySelectModal,
},
data() {
return {
// 아이콘 경로
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',
pageId: null,
isModalOpen: false,
isDragging: false,
fileNames: [],
// 등록/수정 요청 객체
requestDTO: {
dcryId: null,
sj: null,
cn: null,
adres: null,
prdctnYear: null,
ty: 'P',
fileId: null,
files: [],
ctgryIds: [],
},
multipartFiles: [],
selectedCtgries: [], // 카테고리 목록
};
},
created() {
this.pageId = this.$route.query.id;
if (!this.$isEmpty(this.pageId)) {
this.fnFindDcry(); // 상세 조회
}
},
methods: {
// 상세 조회
async fnFindDcry() {
try {
const response = await findDcryProc(this.pageId);
if (response.data.data.dcry.ty !== 'P') {
alert('올바른 접근이 아닙니다.');
this.fnMoveTo('list'); // 목록으로 이동
}
this.copyToDcryReqDTO(response.data.data.dcry);
} catch (error) {
alert('조회중 오류가 발생했습니다.');
this.fnMoveTo('list'); // 목록으로 이동
if (error.response) {
alert(error.response.data.message);
}
console.error(error.message);
}
},
// dcry > requestDTO
copyToDcryReqDTO(dcry) {
const copyFields = Object.keys(this.requestDTO).filter(key => key !== 'dcryId' && key !== 'ty' && key !== 'files');
copyFields.forEach(field => {
this.requestDTO[field] = this.$isEmpty(dcry[field]) ? null : dcry[field];
});
this.requestDTO.ty = 'P'; // 사진기록물
this.requestDTO.files = dcry.files.length > 0 ? dcry.files : []; // 기존 첨부파일
this.multipartFiles = [];
this.selectedCtgries = dcry.ctgrys.length > 0 ? dcry.ctgrys : [];
console.log(this.requestDTO);
},
// 카테고리 모달 열기/닫기
fnToggleModal() {
this.isModalOpen = !this.isModalOpen;
},
// 카테고리 등록
fnAddCtgries(selectedCtgries) {
this.selectedCtgries = [...this.selectedCtgries, ...selectedCtgries];
this.fnToggleModal(); // 카테고리 모달 닫기
},
// 카테고리 삭제
fnDelCtgry(id) {
this.selectedCtgries = this.selectedCtgries.filter(item => item.ctgryId !== id);
},
// 드래그 앤 드롭 이벤트 핸들러
handleDragOver(event) {
this.isDragging = true;
},
handleDragLeave(event) {
this.isDragging = false;
},
handleDrop(event) {
this.isDragging = false;
const files = event.dataTransfer.files;
if (files.length > 0) {
this.processFiles(files);
}
},
handleFileSelect(event) {
const files = event.target.files;
if (files.length > 0) {
this.processFiles(files);
}
},
// 파일 업로드 처리 함수
processFiles(files) {
const allowedTypes = ['jpg', 'jpeg', 'png', 'gif']; // 이미지 파일만 허용
const maxSize = 10 * 1024 * 1024 * 1024; // 10GB
for (let file of files) {
const fileType = file.name.split('.').pop().toLowerCase();
// 파일 타입 검증
if (!allowedTypes.includes(fileType)) {
alert(`${file.name} 파일은 허용되지 않는 형식입니다. 이미지 파일(jpg, jpeg, png, gif)만 업로드 가능합니다.`);
return;
}
// 파일 크기 제한 검증
if (file.size > maxSize) {
alert(`${file.name} 파일이 10GB를 초과합니다.`);
return;
}
this.multipartFiles.push(file);
}
},
// 파일 삭제
fnDelFile(type, separator) {
if (type === 'new') {
this.multipartFiles.splice(separator, 1);
} else if (type === 'old') {
this.requestDTO.files = this.requestDTO.files.filter(item => item.fileId !== separator);
}
},
// 등록
async submitForm() {
// 유효성 검사
if (!this.requestDTO.sj) {
alert("제목을 입력해 주세요.");
return;
}
let count = this.multipartFiles.length
if (!this.$isEmpty(this.pageId)) {
count += this.requestDTO.files.length
}
if (count == 0) {
alert("파일을 1개 이상 첨부해 주세요.");
return;
}
try {
const formData = new FormData();
// 텍스트 데이터 추가
formData.append('sj', this.requestDTO.sj);
formData.append('cn', this.requestDTO.cn);
formData.append('adres', this.requestDTO.adres);
formData.append('prdctnYear', this.requestDTO.prdctnYear);
formData.append('ty', this.requestDTO.ty);
// 게시물 아이디
if (!this.$isEmpty(this.pageId)) {
formData.append('dcryId', this.pageId);
}
// 파일 아이디
if (!this.$isEmpty(this.requestDTO.fileId)) {
formData.append('fileId', this.requestDTO.fileId);
}
// 카테고리 Ids 추가
if (this.selectedCtgries && this.selectedCtgries.length > 0) {
for (let ctgry of this.selectedCtgries) {
formData.append("ctgryIds", ctgry.ctgryId);
}
}
// 파일 추가
for (let file of this.multipartFiles) {
formData.append("multipartFiles", file);
}
// 기존파일 수정
if (!this.$isEmpty(this.pageId) && this.requestDTO.files.length > 0) {
for (let file of this.requestDTO.files) {
formData.append("files", file.fileId);
}
}
// API 통신
const response = this.$isEmpty(this.pageId) ? await saveDcry(formData) : await updateDcry(formData);
let id = response.data.data.dcryId;
alert(this.$isEmpty(this.pageId) ? "등록되었습니다." : "수정되었습니다.");
// 상세 페이지로 이동
this.fnMoveTo('view', id);
} catch (error) {
if (error.response) {
alert(error.response.data.message);
} else {
alert("에러가 발생했습니다.");
}
console.error(error.message);
};
},
// 페이지 이동
fnMoveTo(type, id) {
const routes = {
'list': { name: 'PicHistorySearch' },
'view': { name: 'PicHistoryDetail', query: { id } },
'edit': { name: 'PicHistoryInsert', query: this.$isEmpty(id) ? {} : { id } },
};
if (routes[type]) {
this.$router.push(routes[type]);
} else {
alert("올바르지 않은 경로를 요청하여 목록으로 이동합니다.");
this.$router.push(routes['list']);
}
},
}
};
</script>
<style>
.file-label {
border: 2px dashed #ddd;
border-radius: 8px;
padding: 20px;
text-align: center;
cursor: pointer;
transition: all 0.3s ease;
}
.file-label:hover {
border-color: #aaa;
background-color: #f9f9f9;
}
.file-label.drag-over {
border-color: #1890ff;
background-color: rgba(24, 144, 255, 0.1);
}
</style>