This commit is contained in:
2026-04-07 14:50:23 +09:00
commit b4e485502b
4778 changed files with 2017091 additions and 0 deletions

View 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>

View File

@@ -0,0 +1,77 @@
<!--
This example requires Tailwind CSS v2.0+
This example requires some changes to your config:
```
// tailwind.config.js
module.exports = {
// ...
plugins: [
// ...
require('@tailwindcss/forms'),
],
}
```
-->
<template>
<div>
<div class="mt-5 md:mt-0 md:col-span-2">
<form action="#" method="POST">
<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"
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"
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">
<button
type="submit"
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"
>
Save
</button>
</div>
</div>
</form>
</div>
</div>
</template>

View File

@@ -0,0 +1,187 @@
<!-- This example requires Tailwind CSS v2.0+ -->
<template>
<div class="">
<div class="container mx-auto">
<div class="min-w-full border rounded lg:grid lg:grid-cols-3">
<div class="hidden lg:col-span-3 lg:block">
<div class="flex flex-col min-h-screen w-full">
<div
class="relative flex items-center p-3 border-b border-gray-300"
>
<img
class="object-cover w-10 h-10 rounded-full"
src="https://cdn.pixabay.com/photo/2018/01/15/07/51/woman-3083383__340.jpg"
alt="username"
/>
<span class="block ml-2 font-bold text-gray-600">
채팅 상황에서의 API 사용 {{ ' ' }}
<p class="text-xs">
{{
messageList[messageList.length - 1][
'elapsedServer'
] != ''
? messageList[
messageList.length - 1
]['elapsedServer'] +
' / ' +
messageList[
messageList.length - 1
]['elapsed']
: ''
}}
</p>
</span>
<span
class="absolute w-3 h-3 bg-green-600 rounded-full left-10 top-3"
>
</span>
</div>
<div class="flex-grow bg-gray-50 justify-center">
<div
class="relative w-full p-6 overflow-y-auto max-h-fit"
>
<ul class="space-y-2">
<li
v-for="message in messageList"
:key="message['serial']"
class="flex"
:class="
message['left'] == true
? 'justify-start'
: 'justify-end'
"
>
<div
class="relative max-w-xl px-4 py-2 text-gray-700 rounded shadow"
>
<span class="block">{{
message['text']
}}</span>
</div>
</li>
</ul>
</div>
</div>
<div
class="flex items-center justify-between w-full p-3 border-t border-gray-300"
>
<input
v-model="justEnteredText"
type="text"
placeholder="Message"
class="block w-full py-2 pl-4 mx-3 bg-gray-100 rounded-full outline-none focus:text-gray-700"
name="message"
required
@keyup.enter="checkChatInput"
/>
<button type="button" @click="checkChatInput">
<svg
class="w-5 h-5 text-gray-500 origin-center transform rotate-90"
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 20 20"
fill="currentColor"
>
<path
d="M10.894 2.553a1 1 0 00-1.788 0l-7 14a1 1 0 001.169 1.409l5-1.429A1 1 0 009 15.571V11a1 1 0 112 0v4.571a1 1 0 00.725.962l5 1.428a1 1 0 001.17-1.408l-7-14z"
/>
</svg>
</button>
</div>
<div
class="flex items-center justify-end w-full p-3 border-t border-gray-300"
></div>
</div>
</div>
</div>
</div>
</div>
</template>
<script setup lang="ts">
definePageMeta({
middleware: 'check-auth-user',
});
let tmpIdx = 0;
const messageList = ref([
{
serial: tmpIdx++,
left: true,
text: '안녕하세요?',
elapsedServer: '',
elapsed: '',
},
{
serial: tmpIdx++,
left: false,
text: '테스트 하러 들어 왔습니다.',
elapsedServer: '',
elapsed: '',
},
{
serial: tmpIdx++,
left: true,
text: '테스트 하면 이 문장이죠.',
elapsedServer: '',
elapsed: '',
},
{
serial: tmpIdx++,
left: true,
text: 'Lorem ipsum dolor sit, amet consectetur adipisicing elit.',
elapsedServer: '',
elapsed: '',
},
]);
const justEnteredText = ref('');
const inPregressFlag = ref(false);
const elispe = ref(0);
async function checkChatInput() {
console.log('huk');
if (justEnteredText.value == '') {
return;
}
if (inPregressFlag.value == true) {
return;
}
inPregressFlag.value = true;
const startTime = performance.now();
const responseJson = await _crossCtl.doFilter('', {
text: justEnteredText.value,
mode: 'filter',
});
const endTime = performance.now();
inPregressFlag.value = false;
elispe.value = endTime - startTime;
console.log('responseJson=', responseJson);
if (responseJson['Status']['Code'] == 2000) {
messageList.value.push({
serial: tmpIdx++,
left: false,
text: responseJson['Filtered'],
elapsedServer: responseJson['Elapsed'].replace('0 s, ', ''),
elapsed: elispe.value.toFixed(2) + 'ms',
});
justEnteredText.value = '';
} else {
alert(responseJson['Status']['Message']);
}
}
</script>

View File

@@ -0,0 +1,45 @@
<!-- This example requires Tailwind CSS v2.0+ -->
<template>
<div class="pb-8 px-4 sm:px-6 lg:px-8">
<div class="sm:flex sm:items-center">
<div class="sm:flex-auto">
<h1 class="text-xl font-semibold text-gray-900">
API 테스트 기능
</h1>
<p class="mt-2 text-sm text-gray-700">
API 키가 실제 서비스 예에서 사용되는 것을 있습니다.
</p>
</div>
<div class="mt-4 sm:mt-0 sm:ml-16 sm:flex-none"></div>
</div>
<nav class="mt-5 space-y-1" aria-label="Sidebar">
<a
v-for="item in navigation"
:key="item.name"
href="javascript:void(0)"
:class="[
'text-gray-600 hover:bg-gray-100 hover:text-gray-900',
'flex items-center px-3 py-2 text-sm font-medium rounded-md',
]"
:aria-current="'page'"
@click="$router.push(item.href)"
>
<span class="truncate">
{{ item.name }}
</span>
</a>
</nav>
</div>
</template>
<script setup lang="ts">
definePageMeta({
middleware: 'check-auth-op',
});
const navigation = [
{ name: 'API 키 테스트 게시판', href: '/admin/lab/board' },
{ name: 'API 키 테스트 웹 채팅', href: '/admin/lab/chatting' },
];
</script>

View File

@@ -0,0 +1,79 @@
<!-- This example requires Tailwind CSS v2.0+ -->
<template>
<div class="pb-8 px-4 sm:px-6 lg:px-8">
<div class="sm:flex sm:items-center">
<div class="sm:flex-auto">
<h1 class="text-xl font-semibold text-gray-900">
통계 테스트 페이지
</h1>
<p class="mt-2 text-sm text-gray-700">
통계 개발과 기능 테스트를 위해 만들어진 임시 페이지입니다.
</p>
</div>
<div class="mt-4 sm:mt-0 sm:ml-16 sm:flex-none"></div>
</div>
<nav class="mt-5 space-y-1" aria-label="Sidebar">
<a
v-for="item in navigation"
:key="item.name"
href="javascript:void(0)"
:class="[
'text-gray-600 hover:bg-gray-100 hover:text-gray-900',
'flex items-center px-3 py-2 text-sm font-medium rounded-md',
]"
:aria-current="'page'"
@click="doAction(item.actionTag)"
>
<span class="truncate">
{{ item.name }}
</span>
</a>
</nav>
</div>
</template>
<script setup lang="ts">
definePageMeta({
middleware: 'check-auth-op',
});
const navigation = [
{ name: 'hello?', actionTag: 'hello' },
{ name: '2022년 통계 가공', actionTag: '2022' },
{ name: '2022년 9월 통계 가공', actionTag: '202209' },
{ name: '2022년 9월 27일 통계 가공', actionTag: '20220927' },
];
async function doAction(tag) {
let responseJson = null;
switch (tag) {
case '2022':
responseJson = await _crossCtl.doComm('local/lab', 'makestat', {
termTag: 'year',
dateTag: '2022',
});
console.log('responseJson=', responseJson);
break;
case '202209':
responseJson = await _crossCtl.doComm('local/lab', 'makestat', {
termTag: 'month',
dateTag: '202209',
});
console.log('responseJson=', responseJson);
break;
case '20220927':
responseJson = await _crossCtl.doComm('local/lab', 'makestat', {
termTag: 'day',
dateTag: '20220927',
});
console.log('responseJson=', responseJson);
break;
case 'hello':
responseJson = await _crossCtl.doComm('local/lab', 'hello', {});
console.log('responseJson=', responseJson);
break;
}
}
</script>