first
This commit is contained in:
487
safekiso_admin/pages/admin/lab/board.vue
Normal file
487
safekiso_admin/pages/admin/lab/board.vue
Normal file
@@ -0,0 +1,487 @@
|
||||
<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>
|
||||
Reference in New Issue
Block a user