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
File name
Commit message
Commit date
File name
Commit message
Commit date
<template>
<div :class="{
'content': true,
' overflow-y': this.$route.path.startsWith('/kdm')
}">
<div class="admin-page-title mb30" v-if="this.$route.path.startsWith('/kdm')">
<p>{{ bbsMng.bbsNm }}</p>
</div>
<div :class="{ 'top-banner banner': true, [bannerId]: true }" v-else-if="this.$route.path.startsWith('/aidt')">
<div class="flex-column align-center justify-center content">
<h1 class="banner-title text-ct white">{{ $getMenuInfo().menuNm }}</h1>
<p class="box-title text-ct white">{{ $getMenuInfo().menuCn }}</p>
</div>
</div>
<PageNavigationBar v-if="this.$route.path.startsWith('/aidt')"/>
<div :class="{ 'w1280 pt100 pb100': this.$route.path.startsWith('/aidt') }">
<div class="table-zone">
<div class="pb15">
<p class="text-rg small-text"><span class="red-text">*</span>필수사항입니다.</p>
</div>
<table class="form-table mb30">
<colgroup>
<col width="10%" />
<col width="90%" />
</colgroup>
<tbody>
<tr>
<th class=" text-rg">
<span>제목</span>
</th>
<td><input type="text" class="full-input" placeholder="제목을 입력하세요." v-model="bbsCn.bbsTtl"></td>
</tr>
<tr class="border-t">
<th class="text-rg ">
<span>내용</span>
</th>
</tr>
<tr>
<td colspan="4">
<textarea name="smart" id="smart" style="width:100%; height: 35rem;"></textarea>
</td>
</tr>
<tr>
<th class=" text-rg">
<span>영상 URL</span>
</th>
<td colspan="4">
<div class="gd-6">
<input type="text" class="full-input" placeholder="youtube URL을 입력해주세요."
v-model="bbsCn.vdoUrlAddr" />
</div>
</td>
</tr>
<tr>
<th class=" text-rg">
썸네일
</th>
<td colspan="2">
<div class="gd-12 pr0">
<div class="flex align-center">
<div class="gd-2 pl0 pr0">
<label for="thumbnail" class="large-btn blue-border-btn text-ct">파일찾기</label>
<input type="file" id="thumbnail" ref="thumbnail" @change="fnThumbnailInsert"
accept="image/*">
</div>
<div class="gd-12 pl0">
<div>
<ul v-if="imgFileList.length > 0" style="max-height: 20rem;" class="pt10 pb10 overflow-y">
<li v-for="(file, index) in imgFileList" :key="index"
class="pl10 pr10 border radius">
<div v-if="file['fileId'] != null"
class="flex justify-between align-center">
<p>{{ file["fileNm"] }}.{{ file["extnNm"] }}</p>
<button class="icon-btn"
@click="fnImgFileDelete(file, index)">X</button>
</div>
<div v-else class="flex justify-between align-center">
<p>{{ file.name }}</p>
<button class="icon-btn"
@click="fnImgFileDelete(file, index)">X</button>
</div>
<svg-icon type="mdi" :iconPath="iconPath"></svg-icon>
</li>
</ul>
</div>
</div>
</div>
</div>
</td>
</tr>
<tr v-if="bbsMng.atchFileUseYn === 'Y'" class="border-t">
<th class=" text-rg">
첨부파일
</th>
<td colspan="2">
<div class="gd-12 pr0">
<div class="flex align-center">
<div class="gd-2 pl0 pr0">
<label for="file" class="large-btn blue-border-btn text-ct">파일찾기</label>
<input type="file" id="file" ref="file" @change="fnFileInsert" multiple>
</div>
<div class="gd-12 pl0">
<div>
<ul v-if="fileList.length > 0" style="max-height: 20rem;" class="pt10 pb10 overflow-y">
<li v-for="(file, idx) in fileList" :key="idx" class="pl10 pr10 border radius">
<div v-if="file['fileId'] != null"
class="flex justify-between align-center">
<p> {{ file["fileNm"] }}.{{ file["extnNm"] }}</p>
<button class="icon-btn"
@click="fnFileDelete(file, idx)">X</button>
</div>
<div v-else class="flex justify-between align-center">
{{ file.name }}
<button class="icon-btn"
@click="fnFileDelete(file, idx)">X</button>
</div>
</li>
</ul>
</div>
</div>
</div>
</div>
</td>
</tr>
<tr v-if="bbsMng.ntcUseYn === 'Y'" class="border-t">
<th class=" text-rg">
공지글
</th>
<td colspan="3">
<div class="flex align-center no-gutters">
<div class="gd-4">
<input type="radio" name="notice" id="y" class="mr5" value="Y"
v-model="bbsCn.ntcYn">
<label for="y">사용</label>
</div>
<div class="gd-4">
<input type="radio" name="notice" id="n" class="mr5" value="N"
v-model="bbsCn.ntcYn">
<label for="n">미사용</label>
</div>
</div>
</td>
</tr>
<tr v-if="bbsCn.ntcYn === 'Y'" class="border-t">
<th class=" text-rg">
공지글 게시기간
</th>
<td colspan="3">
<div class="flex align-center no-gutters">
<div class="gd-4">
<input type="date" class="full-input ml0" v-model="bbsCn.ntcBgngDt"
@change="checkDateValidity('ntcBgngDt', $event)">
</div>
<div class="pd10">-</div>
<div class="gd-4">
<input type="date" class="full-input ml0" v-model="bbsCn.ntcEndDt"
@change="checkDateValidity('ntcEndDt', $event)">
</div>
</div>
</td>
</tr>
<tr v-if="bbsMng.prvtPstUseYn === 'Y'" class="border-t">
<th class=" text-rg">
비밀글
</th>
<td colspan="3">
<div class="flex align-center no-gutters">
<div class="gd-4">
<input type="radio" name="secret" id="y" class="mr5" value="Y"
v-model="bbsCn.scretBltbYn">
<label for="y">사용</label>
</div>
<div class="gd-4">
<input type="radio" name="secret" id="n" class="mr5" value="N"
v-model="bbsCn.scretBltbYn">
<label for="n">미사용</label>
</div>
</div>
</td>
</tr>
</tbody>
</table>
</div>
<div class="flex justify-end align-center no-gutters">
<div class="gd-1 mr10">
<button class="large-btn blue-btn" @click="fnInsert">{{ bbsCn.bbsId == null ? '등록' : '수정'
}}</button>
</div>
<div class="gd-1">
<button class="large-btn gray-btn" @click="fnCancel">취소</button>
</div>
</div>
</div>
</div>
</template>
<script>
import { mdiClose } from '@mdi/js';
import { findByBbsCn } from '../../../../../../resources/api/bbsCn.js'
import queryParams from '../../../../../../resources/js/queryParams';
import axios from "axios";
import PageNavigationBar from '../../../../../component/pagenavigationbar/PageNavigationBar.vue';
export default {
mixins: [queryParams],
data() {
return {
youtubeUrl: '',
thumbnailUrl: '',
file: null,
iconPath: mdiClose,
path: this.$store.state.path,
pageAuth: this.$store.state.pageAuth,
bbsMngId: null,
bbsCn: {},
bbsMng: {},
// 파일 업로드
files: null,
fileList: [],
deleteFileList: [],
// 파일 업로드
imgFile: null,
imgFileList: [],
deleteImgFileList: [],
//스마트 에디터
oEditors: [],
bannerId: null
}
},
created() {
this.fnBbsIdExtraction();
this.fnSelectOne();
},
methods: {
generateThumbnailUrl() {
const videoId = this.extractVideoId(this.youtubeUrl);
//https://youtu.be/ 뒤의 text
this.thumbnailUrl = videoId
},
extractVideoId(url) {
const regex = /https:\/\/youtu.be\/(.*)/;
const match = url.match(regex);
return match ? match[1] : null;
},
// 추가 수정 필요
// 목록으로 이동
fnCancel() {
if (!confirm(this.$getCmmnMessage('cnf008'))) {
return;
}
if (this.bbsCn.bbsId == null || this.bbsCn.bbsId == 0) {
this.$router.push({
path: this.path + '/list.page',
});
} else {
this.$router.push({
path: this.path + '/view.page',
query: {
pageId: this.bbsCn.bbsId
},
});
}
},
// 상세조회
async fnSelectOne() {
try {
const params = {
'bbsId': this.$route.query.pageId,
'bbsMngId': this.bbsMngId
}
const res = await findByBbsCn(params);
if (res.status == 200) {
this.bbsCn = res.data.data.bbsCn;
this.bbsMng = res.data.data.bbsMng;
this.fileList = res.data.data.fileList;
this.imgFileList = res.data.data.imgFileList;
if( this.bbsCn.ntcBgngDt != null){
this.bbsCn.ntcBgngDt = this.$filters.date(this.bbsCn.ntcBgngDt);
this.bbsCn.ntcEndDt = this.$filters.date(this.bbsCn.ntcEndDt);
}
this.bannerIdAdd();
this.createEditor();
}
} catch (error) {
// alert('에러가 발생했습니다.\n시스템관리자에게 문의하세요.');
alert(this.$getCmmnMessage('err005'));
}
},
// 썸네일 등록
fnThumbnailInsert(event) {
const file = event.target.files[0];
if (file && file.type.startsWith('image/')) {
if (this.imgFileList.length > 0) {
if (this.imgFileList[0].fileId != null) {
this.deleteFileList.push(this.imgFileList[0]);
}
}
this.imgFileList[0] = file;
} else {
// 이미지 파일이 아닐 경우, 사용자에게 경고
alert('이미지 파일만 등록할 수 있습니다.');
}
},
// 썸네일 삭제
fnImgFileDelete(file, index) {
if (file["fileId"] != null) {
this.deleteImgFileList.push(file);
}
this.imgFileList = [];
},
// 첨부파일 등록
fnFileInsert() {
// files 개수 만큼 반복해서 type, size 체크
this.files = this.$refs.file.files;
for (let i = 0; i < this.files.length; i++) {
const file = this.files[i];
const extnNm = file.name.split('.').pop();
if (this.bbsMng.fileExtnNmList.length >= 1) {
if (!this.bbsMng.fileExtnNmList.includes(extnNm)) {
alert('첨부파일 확장자를 확인해주세요.');
return;
}
}
if (this.bbsMng.fileSzLmt != 0 && this.bbsMng.fileSzLmt != null) {
if (file.size > this.bbsMng.fileSzLmt) {
alert('첨부파일 용량을 확인해주세요.');
return;
}
}
}
this.fileList = [...this.fileList, ...Array.from(this.files)];
},
// 첨부파일 삭제
fnFileDelete(file, index) {
if (file["fileId"] != null) {
this.deleteFileList.push(file);
}
this.fileList.splice(index, 1);
},
fnInsert() {
if (this.bbsCn.bbsId == null || this.bbsCn.bbsId == 0) {
this.fnSave();
} else {
this.fnUpdate();
}
},
// 등록
async fnSave() {
if (!this.Validation()) {
return;
}
// 폼데이터 생성
this.bbsCn.bbsMngId = this.bbsMngId;
var formData = new FormData();
const paramsToBlob = new Blob([JSON.stringify(this.bbsCn)], {
type: "application/json; charset=UTF-8",
});
formData.append("params", paramsToBlob);
for (const file of this.fileList) {
formData.append("multipartFileList", file);
}
for (const imgFile of this.imgFileList) {
formData.append("multipartImgList", imgFile);
}
// axios 호출
await axios({
url: "/bbsCn/saveBbsCn.file",
method: "post",
headers: {
"Content-Type": "multipart/form-data; charset=UTF-8",
Authorization: this.$store.state.authorization,
},
data: formData,
}).then((response) => {
const bbsId = response.data.data.bbsId;
alert('게시글이 등록되었습니다.');
this.$router.push({
// name: 'BoardManagementSelectListOne',
path: this.path + '/view.page',
query: {
pageId: bbsId
},
});
}).catch((error) => {
alert("게시글 등록에 실패하였습니다.\n관리자에게 문의해주세요.");
});
},
// 수정
async fnUpdate() {
if (!this.Validation()) {
return;
}
// 폼데이터 생성
this.bbsCn.bbsMngId = this.bbsMngId;
var formData = new FormData();
const paramsToBlob = new Blob([JSON.stringify(this.bbsCn)], {
type: "application/json; charset=UTF-8",
});
formData.append("params", paramsToBlob);
const deleteFileListToBlob = new Blob(
[JSON.stringify(this.deleteFileList)],
{
type: "application/json; charset=UTF-8",
}
);
formData.append("deleteFileList", deleteFileListToBlob);
// 추가 첨부파일
for (const file of this.fileList) {
if (file["fileId"] == null) {
formData.append("multipartFileList", file);
}
}
// 이미지 파일
const deleteImgFileListToBlob = new Blob(
[JSON.stringify(this.deleteImgFileList)],
{
type: "application/json; charset=UTF-8",
}
);
formData.append("deleteImgFileList", deleteImgFileListToBlob);
for (const imgFile of this.imgFileList) {
formData.append("multipartImgList", imgFile);
}
// axios 호출
axios({
url: "/bbsCn/updateBbsCn.file",
method: "post",
headers: {
"Content-Type": "multipart/form-data; charset=UTF-8",
Authorization: this.$store.state.authorization,
},
data: formData,
}).then((response) => {
const bbsId = response.data.data;
this.$router.push({
// name: 'BoardManagementSelectListOne',
path: this.path + '/view.page',
query: {
pageId: bbsId
},
});
}).catch((error) => {
// alert("에러가 발생했습니다.\n관리자에게 문의해주세요.");\
alert(this.$getCmmnMessage('err005'));
});
},
// 유효성 검사
Validation() {
if (this.bbsCn.bbsTtl == null || this.bbsCn.bbsTtl == '') {
alert('게시판 제목을 입력해주세요.');
return false;
}
const oEditors = this.oEditors;
oEditors.getById["smart"].exec("UPDATE_CONTENTS_FIELD", []);
// 스마트에디터의 iframe에 있는 내용을 textarea로.
this.bbsCn.bbsCn = document.getElementById("smart").value;
// 내용 null검사
if (!this.tagChecked() && (this.isEmpty(this.bbsCn.bbsCn) || this.removeHtmlAndSpace(this.bbsCn.bbsCn) === '')) {
alert("내용을 입력하세요.");
document.getElementById("smart").focus();
return false;
}
// url null 검사
if (this.bbsCn.vdoUrlAddr == null || this.bbsCn.vdoUrlAddr == '') {
alert('영상 URL을 입력해주세요.');
return false;
}
if (this.bbsCn.ntcYn === 'Y' && (this.bbsCn.ntcBgngDt === null || this.bbsCn.ntcEndDt === null)) {
alert('공지기간을 올바르게 설정해주세요.');
return false;
}
return true;
},
// 태그 체크
tagChecked() {
const tag = this.bbsCn.bbsCn;
if (tag.indexOf("<iframe") != -1) {
return true;
} else {
return false;
}
},
/**
* 빈 객체 여부
*/
isEmpty: function (data) {
if (
data === undefined ||
data === null ||
data === "" ||
data.length === 0 ||
(data.constructor == Object && Object.keys(data).length === 0)
) {
return true;
} else {
return false;
}
},
removeHtmlAndSpace: function (str) {
return str.replace(/<[^>]*>/g, '') // HTML 태그 제거
.replace(/ /gi, ' ') // 를 공백으로 변환
.replace(/\s/g, ''); // 모든 공백 제거
},
// 공지기간 유효성 체크
checkDateValidity(changeDate, event) {
const val = event.target.value; // 변경된 날짜 값
// 시작일 변경 시
if (changeDate === 'ntcBgngDt') {
if (this.bbsCn.ntcEndDt !== null && this.bbsCn.ntcEndDt < val) {
alert('시작일은 종료일보다 클 수 없습니다.');
this.bbsCn.ntcBgngDt = null; // 유효하지 않은 경우, 시작일을 초기화
} else {
this.bbsCn.ntcBgngDt = val;
}
}
// 종료일 변경 시
else if (changeDate === 'ntcEndDt') {
if (this.bbsCn.ntcBgngDt !== null && this.bbsCn.ntcBgngDt > val) {
alert('종료일은 시작일보다 작을 수 없습니다.');
this.bbsCn.ntcEndDt = null // 유효하지 않은 경우, 종료일을 초기화
}
else {
this.bbsCn.ntcEndDt = val;
}
}
},
// 에디터 생성
createEditor() {
// 스마트 에디터 적용
let vm = this;
const oEditors = this.oEditors;
nhn.husky.EZCreator.createInIFrame({
oAppRef: oEditors,
elPlaceHolder: "smart",
sSkinURI: "/client/smarteditor2-2.8.2.3/SmartEditor2Skin.html",
htParams: {
bUseToolbar: true, // 툴바 사용 여부 (true:사용/ false:사용하지 않음)
bSkipXssFilter: true,
bUseVerticalResizer: true,
bUseModeChanger: true
},
fOnAppLoad: function () {
oEditors.getById["smart"].exec("PASTE_HTML", [vm.bbsCn.bbsCn]);
},
fCreator: "createSEditor2"
});
},
bannerIdAdd() {
if (this.bbsMng) {
const bbsId = this.bbsMngId.slice(-3);
this.bannerId = `banner${bbsId}`;
} else {
return;
}
},
},
watch: {
youtubeUrl() {
this.generateThumbnailUrl()
}
},
computed: {
},
components: {
'PageNavigationBar': PageNavigationBar
},
mounted() {
}
}
</script>
<style scoped>
.del-btn {
background-color: rgb(241, 199, 199);
height: 20px;
color: #fff;
border: none;
padding: 5px;
cursor: pointer;
}
</style>