Files
SAFEKISO/safekiso_admin/base/pages/board/[...boardId]/new.vue
2026-04-07 14:50:23 +09:00

311 lines
9.2 KiB
Vue

<template>
<div>
<!-- Page head goes here -->
<div class="px-4 py-5 sm:px-6">
<div
class="flex justify-between items-center flex-wrap sm:flex-nowrap"
>
<div class="">
<h3 class="text-lg leading-6 font-medium text-gray-900">
{{ pageTitle }}
</h3>
<p class="mt-1 text-sm text-gray-500">
{{ pageDescription }}
</p>
</div>
<div class="flex-shrink-0">
<button
v-for="(headingAction, index) in headingActions"
:key="headingAction"
type="button"
:class="index > 0 ? 'ml-3' : ''"
class="btn btn-sm btn-primary"
@click="doHeadingAction(headingAction)"
>
{{ headingAction }}
</button>
</div>
</div>
</div>
<div class="max-w-7xl mx-auto sm:px-4 lg:px-4">
<!-- Content goes here -->
<input
id="title"
v-model="title"
type="text"
name="title"
placeholder="제목을 입력하세요."
class="mb-3 shadow-sm focus:ring-indigo-500 focus:border-indigo-500 block w-full sm:text-sm border-gray-300 rounded-md"
/>
<quill-editor
v-model:content="content"
class="min-h-[30rem]"
:modules="modules"
:toolbar="toolbarOptions"
placeholder="본문을 입력하세요."
@ready="onEditorReady($event)"
/>
<base-attachment-ctl1
v-if="attachmentEnabled"
:attachments="attachments"
:read-only-flag="false"
:update-attachments="updateAttachments"
:board-id="bid"
:secure-enabled="false"
/>
<div class="mt-5 flex justify-between items-center flex-wrap">
<div class="ml-4 mt-4"></div>
<div class="ml-4 mt-4 flex-shrink-0">
<button
type="button"
class="mr-3 inline-flex items-center rounded-md border border-transparent bg-red-600 px-3 py-2 text-sm font-medium leading-4 text-white shadow-sm hover:bg-red-700 focus:outline-none focus:ring-2 focus:ring-red-500 focus:ring-offset-2"
@click="doFooterAction('취소')"
>
{{ '취소' }}
</button>
<button
type="button"
class="inline-flex items-center rounded-md border border-transparent bg-indigo-600 px-3 py-2 text-sm font-medium leading-4 text-white shadow-sm hover:bg-indigo-700 focus:outline-none focus:ring-2 focus:ring-indigo-500 focus:ring-offset-2"
@click="doFooterAction('저장')"
>
{{ '저장' }}
</button>
</div>
</div>
</div>
</div>
</template>
<script setup lang="ts">
import BlotFormatter from 'quill-blot-formatter/dist/BlotFormatter';
definePageMeta({
// middleware: 'check-auth-user',
});
const route = useRoute();
console.log('huk params = ', route.params);
const bid = ref('');
if (route.params.boardId instanceof Array) {
if (route.params.boardId.length != 1) {
throwError('$404');
} else {
console.log('huk 3');
bid.value = route.params.boardId[0];
}
} else {
throwError('$404');
}
console.log('huk bid = ', bid.value);
const modules = {
name: 'blotFormatter',
module: BlotFormatter,
options: {},
};
const responseJson = await _crossCtl.doComm('select', 'board:info', {
hero: bid.value,
});
console.log('responseJson = ', responseJson);
const boardInfo = ref({});
const attachmentEnabled = ref(false);
if (responseJson['responseCode'] != 200) {
alert(responseJson['responseMessage']);
} else {
if (responseJson['data'].length > 0) {
boardInfo.value = responseJson['data'][0];
attachmentEnabled.value =
responseJson['data'][0]['attachment_enabled'] == 1;
console.log('attachmentEnabled.value=', attachmentEnabled.value);
}
}
const pageTitle = boardInfo.value['title'] + ' - ' + '새글 작성';
const pageDescription = '새로운 글을 작성 합니다.';
// 해당 페이지 우측 상단에 표시될 액션 버튼들
const headingActions = [];
const title = ref('');
const content = ref({ ops: [] });
const attachments = ref([]);
const status = ref(0);
function updateAttachments(newAttachments) {
console.log('newAttachments=', newAttachments);
attachments.value = newAttachments;
}
const toolbarOptions = [
['bold', 'italic', 'underline', 'strike'], // toggled buttons
['blockquote', 'code-block'],
// [{ header: 1 }, { header: 2 }], // custom button values
[{ list: 'ordered' }, { list: 'bullet' }],
[{ script: 'sub' }, { script: 'super' }], // superscript/subscript
[{ indent: '-1' }, { indent: '+1' }], // outdent/indent
// [{ direction: 'rtl' }], // text direction
// [{ size: ['small', false, 'large', 'huge'] }], // custom dropdown
[{ header: [1, 2, 3, 4, 5, 6, false] }],
[{ color: [] }, { background: [] }], // dropdown with defaults from theme
[{ font: [] }],
[{ align: [] }],
['link', 'video', 'image'],
['clean'], // remove formatting button
];
let quill = null;
function selectLocalImage() {
const input = document.createElement('input');
input.setAttribute('type', 'file');
input.click();
// Listen upload local image and save to server
input.onchange = () => {
const file = input.files[0];
// file type is only image.
if (/^image\//.test(file.type)) {
console.warn('upload images...');
saveToServer(file);
} else {
console.warn('You could only upload images.');
}
};
}
function selectLocalFiles() {
const input = document.createElement('input');
input.setAttribute('type', 'file');
input.click();
// Listen upload local image and save to server
input.onchange = () => {
console.log('we got file(s) : ', input.files);
};
}
async function saveToServer(file: File) {
const formData = new FormData();
formData.append('upload-file', file, file.name);
// save it
formData.append('target', 'just');
formData.append('attachedTo', bid.value);
console.log('formData=', formData);
const responseJson = await _crossCtl.doUpload('just', formData);
console.log('responseJson=', responseJson);
if (responseJson['responseCode'] == 200) {
insertToEditor(
// _crossCtl.config['API_BASE_URL'].replace('/api/', '') +
responseJson['files'][0]['localUrl']
);
} else {
}
}
function insertToEditor(url: string) {
// push image url to rich editor.
const range = quill.getSelection();
quill.insertEmbed(range.index, 'image', url);
}
function onEditorReady(e) {
console.log('onEditorReady() e = ', e);
quill = e;
// quill editor add image handler
e.getModule('toolbar').addHandler('image', () => {
selectLocalImage();
});
}
function doFooterAction(tag) {
console.log('on doFooterAction(), tag=', tag);
switch (tag) {
case '저장':
// console.log('quill.root.innerHTML=', quill.root.innerHTML);
// console.log('content=', content.value);
updateContent();
break;
case '취소':
// router.back();
navigateTo('/board/' + bid.value + '/list', { replace: true });
break;
}
}
async function updateContent() {
if (title.value.trim() == '') {
alert('제목을 입력해 주세요.');
return;
}
let emptyContent = true;
console.log('content.value = ', content.value);
console.log('quill.root.innerHTML = ', quill.root.innerHTML);
for (let i = 0; i < content.value['ops'].length; i++) {
console.log("content.value['ops'][i] = ", content.value['ops'][i]);
if (typeof content.value['ops'][i]['insert'] == 'string') {
if (content.value['ops'][i]['insert'].trim() != '') {
emptyContent = false;
break;
}
} else {
emptyContent = false;
break;
}
}
if (emptyContent == true) {
alert('본문을 입력해 주세요.');
return;
}
const responseJson = await _crossCtl.doComm('insert', 'board', {
boardId: bid.value,
title: title.value,
content: quill.root.innerHTML,
attachments: attachments.value,
status: status.value,
});
console.log('responseJson=', responseJson);
if (responseJson['responseCode'] != 200) {
alert(responseJson['responseMessage']);
} else {
// router.back();
navigateTo('/board/' + bid.value + '/list', { replace: true });
}
}
// refresh();
</script>