Files
SAFEKISO/safekiso_admin/pages/admin/lab/board.vue
2026-04-07 14:50:23 +09:00

488 lines
23 KiB
Vue

<template>
<div>
<div class="space-y-6 lg:col-start-1 lg:col-span-2">
<!-- Description list-->
<section aria-labelledby="applicant-indivation-title">
<div class="bg-white shadow sm:rounded-lg">
<div class="px-4 py-5 sm:px-6">
<h2
id="applicant-indivation-title"
class="text-lg leading-6 font-medium text-gray-900"
>
게시판에 글을 작성하는 화면에서 API 사용
</h2>
<p class="mt-1 max-w-2xl text-sm text-gray-500">
문제가 있는 표현을 고지하고 수정하기 전에는 저장을
하지 못하게 합니다.
</p>
</div>
<div class="mt-5 md:mt-0 md:col-span-2">
<div>
<div
class="shadow sm:rounded-md sm:overflow-hidden"
>
<div
class="px-4 py-5 bg-white space-y-6 sm:p-6"
>
<div class="grid grid-cols-3 gap-6">
<div class="col-span-3 sm:col-span-3">
<label
for="company-website"
class="block text-sm font-medium text-gray-700"
>
제목
</label>
<div
class="mt-1 flex rounded-md shadow-sm"
>
<input
id="company-website"
v-model="boardTitle"
type="text"
name="company-website"
class="focus:ring-indigo-500 focus:border-indigo-500 flex-1 block w-full rounded-none rounded-r-md sm:text-sm border-gray-300"
placeholder=""
/>
</div>
</div>
</div>
<div>
<label
for="about"
class="block text-sm font-medium text-gray-700"
>
내용
</label>
<div class="mt-1">
<textarea
id="about"
v-model="boardText"
name="about"
rows="7"
class="shadow-sm focus:ring-indigo-500 focus:border-indigo-500 mt-1 block w-full sm:text-sm border border-gray-300 rounded-md"
placeholder=""
/>
</div>
<p class="mt-2 text-sm text-gray-500">
모욕적인 표현이 포함된 경우 저장을
하실 없습니다.
</p>
</div>
</div>
<div
class="px-4 py-3 bg-gray-50 text-right sm:px-6"
>
<SwitchGroup
as="div"
class="flex items-center"
>
<Switch
v-model="boardErrorEnabled"
:class="[
boardErrorEnabled
? 'bg-indigo-600'
: 'bg-gray-200',
'relative inline-flex flex-shrink-0 h-6 w-11 border-2 border-transparent rounded-full cursor-pointer transition-colors ease-in-out duration-200 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500',
]"
>
<span
aria-hidden="true"
:class="[
boardErrorEnabled
? 'translate-x-5'
: 'translate-x-0',
'pointer-events-none inline-block h-5 w-5 rounded-full bg-white shadow transform ring-0 transition ease-in-out duration-200',
]"
/>
</Switch>
<SwitchLabel as="span" class="ml-3">
<span
class="text-sm font-medium text-gray-900"
>API 오류 시뮬레이션
</span>
</SwitchLabel>
</SwitchGroup>
<button
type="button"
class="inline-flex justify-center py-2 px-4 border border-transparent shadow-sm text-sm font-medium rounded-md text-white bg-indigo-600 hover:bg-indigo-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500"
@click="handleBoard"
>
전송
</button>
</div>
</div>
</div>
</div>
</div>
</section>
<!-- Comments-->
<section aria-labelledby="notes-title">
<div class="bg-white shadow sm:rounded-lg sm:overflow-hidden">
<div class="divide-y divide-gray-200">
<div class="px-4 py-5 sm:px-6">
<h2
id="notes-title"
class="text-lg font-medium text-gray-900"
>
댓글 작성 화면에서의 API 사용
</h2>
</div>
<div class="px-4 py-6 sm:px-6">
<ul role="list" class="space-y-8">
<li
v-for="comment in comments"
:key="comment.id"
>
<div class="flex space-x-3">
<div class="flex-shrink-0">
<img
class="h-10 w-10 rounded-full"
:src="`https://images.unsplash.com/photo-${comment.imageId}?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=divat&fit=facearea&facepad=2&w=256&h=256&q=80`"
alt=""
/>
</div>
<div>
<div class="text-sm">
<a
href="#"
class="font-medium text-gray-900"
>{{ comment.name }}</a
>
</div>
<div
class="mt-1 text-sm text-gray-700"
>
<p>
{{ comment.body }}
</p>
</div>
<div class="mt-2 text-sm space-x-2">
<span
class="text-gray-500 font-medium"
>{{ comment.date }}</span
>
</div>
</div>
</div>
</li>
</ul>
</div>
</div>
<div class="bg-gray-50 px-4 py-6 sm:px-6">
<div class="flex space-x-3">
<div class="flex-shrink-0">
<img
class="h-10 w-10 rounded-full"
:src="user.imageUrl"
alt=""
/>
</div>
<div class="min-w-0 flex-1">
<div action="#">
<div>
<label for="comment" class="sr-only"
>About</label
>
<textarea
id="comment"
v-model="commentMessage"
name="comment"
rows="3"
class="shadow-sm block w-full focus:ring-blue-500 focus:border-blue-500 sm:text-sm border border-gray-300 rounded-md"
placeholder="댓글 본문을 입력해 주세요."
/>
</div>
<div
class="mt-3 flex items-center justify-between"
>
<a
href="javascript:void(0)"
class="group inline-flex items-start text-sm space-x-2 text-gray-500 hover:text-gray-900"
>
<QuestionMarkCircleIcon
class="flex-shrink-0 h-5 w-5 text-gray-400 group-hover:text-gray-500"
aria-hidden="true"
/>
<span>
모욕적인 표현이 포함된 경우
게시할 없습니다.
</span>
</a>
<SwitchGroup
as="div"
class="flex items-center"
>
<Switch
v-model="commentErrorEnabled"
:class="[
commentErrorEnabled
? 'bg-indigo-600'
: 'bg-gray-200',
'relative inline-flex flex-shrink-0 h-6 w-11 border-2 border-transparent rounded-full cursor-pointer transition-colors ease-in-out duration-200 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500',
]"
>
<span
aria-hidden="true"
:class="[
commentErrorEnabled
? 'translate-x-5'
: 'translate-x-0',
'pointer-events-none inline-block h-5 w-5 rounded-full bg-white shadow transform ring-0 transition ease-in-out duration-200',
]"
/>
</Switch>
<SwitchLabel as="span" class="ml-3">
<span
class="text-sm font-medium text-gray-900"
>API 오류 시뮬레이션
</span>
</SwitchLabel>
</SwitchGroup>
<button
type="button"
class="inline-flex items-center justify-center px-4 py-2 border border-transparent text-sm font-medium rounded-md shadow-sm text-white bg-blue-600 hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500"
@click="handleComment"
>
전송
</button>
</div>
</div>
</div>
</div>
</div>
</div>
</section>
</div>
<!-- Global notification live region, render this permanently at the end of the document -->
<div
aria-live="assertive"
class="fixed inset-0 flex items-end px-4 py-6 pointer-events-none sm:p-6 sm:items-start"
>
<div
class="w-full flex flex-col items-center space-y-4 sm:items-end"
>
<!-- Notification panel, dynamically insert this into the live region when it needs to be displayed -->
<transition
enter-active-class="transform ease-out duration-300 transition"
enter-from-class="translate-y-2 opacity-0 sm:translate-y-0 sm:translate-x-2"
enter-to-class="translate-y-0 opacity-100 sm:translate-x-0"
leave-active-class="transition ease-in duration-100"
leave-from-class="opacity-100"
leave-to-class="opacity-0"
>
<div
v-if="notiShow"
class="max-w-sm w-full bg-white shadow-lg rounded-lg pointer-events-auto ring-1 ring-black ring-opacity-5 overflow-hidden"
>
<div class="p-4">
<div class="flex items-start">
<div class="flex-shrink-0">
<CheckCircleIcon
v-if="isGoodFlag"
class="h-6 w-6 text-green-400"
aria-hidden="true"
/>
<ExclamationCircleIcon
v-else
class="h-6 w-6 text-red-400"
aria-hidden="true"
/>
</div>
<div class="ml-3 w-0 flex-1 pt-0.5">
<p
class="text-sm font-medium text-gray-900"
>
{{ notiTitle }}
</p>
<p class="mt-1 text-sm text-gray-500">
{{ notiMessage }}
</p>
</div>
<div class="ml-4 flex-shrink-0 flex">
<button
type="button"
class="bg-white rounded-md inline-flex text-gray-400 hover:text-gray-500 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500"
@click="notiShow = false"
>
<span class="sr-only">Close</span>
<XIcon
class="h-5 w-5"
aria-hidden="true"
/>
</button>
</div>
</div>
</div>
</div>
</transition>
</div>
</div>
</div>
</template>
<script setup>
import { XMarkIcon, QuestionMarkCircleIcon } from '@heroicons/vue/24/solid';
import {
CheckCircleIcon,
ExclamationCircleIcon,
} from '@heroicons/vue/24/outline';
import { Switch, SwitchGroup, SwitchLabel } from '@headlessui/vue';
const user = {
name: 'Whitney Francis',
email: 'whitney@example.com',
imageUrl:
'https://images.unsplash.com/photo-1517365830460-955ce3ccd263?ixlib=rb-=eyJhcHBfaWQiOjEyMDd9&auto=divat&fit=facearea&facepad=8&w=256&h=256&q=80',
};
let commentSerial = 0;
const boardErrorEnabled = ref(false);
const commentErrorEnabled = ref(false);
const comments = ref([
{
id: ++commentSerial,
name: '김재순',
date: '하루 전',
imageId: '1506794778202-cad84cf45f1d',
body: '짜장면이 맛이 있느냐 짬뽕이 맛이 있느냐 논쟁 같은 느낌인데요? ',
},
]);
const notiShow = ref(false);
const isGoodFlag = ref(false);
const notiTitle = ref('');
const notiMessage = ref('');
const notiTimer = null;
function showNotifications(isGood, title, message) {
isGoodFlag.value = isGood;
notiTitle.value = title;
notiMessage.value = message;
notiShow.value = true;
if (notiTimer != null) {
clearTimeout(notiTimer);
notiTimer = null;
}
notiTimer = setTimeout(() => {
notiShow.value = false;
}, 8000);
}
const boardTitle = ref('');
const boardText = ref('');
const commentMessage = ref('');
const missingParts = 'e3';
async function handleFilter(source, at, errorSimulFlag, cb) {
const responseJson = await _crossCtl.doFilter(
'' + (errorSimulFlag == false ? '' : missingParts),
{
text: source,
mode: 'quick',
}
);
// console.log('responseJson=', responseJson);
if (responseJson['Status']['Code'] == 2000) {
// Detected
if (responseJson['Detected'].length == 0) {
cb(null);
} else {
showNotifications(
false,
'잘못된 표현',
at +
'에 이 표현은 쓸 수 없습니다. : ' +
responseJson['Detected'][0][1]
);
cb(false);
}
} else {
showNotifications(
false,
'오류',
'API 호출 오류 : ' + responseJson['Status']['Message']
);
cb(true);
}
}
function handleBoard() {
// console.log(boardTitle.value);
// console.log(boardText.value);
if (boardTitle.value.trim() == '') {
showNotifications(false, '오류', '제목을 입력해 주세요.');
return;
}
if (boardText.value.trim() == '') {
showNotifications(false, '오류', '본문을 입력해 주세요.');
return;
}
handleFilter(
boardTitle.value,
'게시판 제목',
boardErrorEnabled.value,
function (error) {
if (error == null) {
handleFilter(
boardText.value,
'게시판 본문',
boardErrorEnabled.value,
function (error) {
if (error == null) {
showNotifications(
true,
'게시 가능',
'입력하신 제목과 내용에 문제가 없습니다.'
);
}
}
);
}
}
);
}
function handleComment() {
if (commentMessage.value == '') {
return;
}
handleFilter(
commentMessage.value,
'댓글',
commentErrorEnabled.value,
function (error) {
console.log('error=', error);
if (error == null) {
comments.value.push({
id: ++commentSerial,
name: '당신',
date: '조금 전',
imageId: '1517365830460-955ce3ccd263',
body: commentMessage.value,
});
commentMessage.value = '';
} else if (error == true) {
} else {
}
}
);
}
</script>