first
This commit is contained in:
157
inspond-nuxt-safekiso/base/components/BaseAttachmentCtl1.vue
Normal file
157
inspond-nuxt-safekiso/base/components/BaseAttachmentCtl1.vue
Normal file
@@ -0,0 +1,157 @@
|
||||
<template>
|
||||
<div
|
||||
v-if="
|
||||
(attachments.length > 0 && readOnlyFlag == true) ||
|
||||
readOnlyFlag == false
|
||||
"
|
||||
class="mt-3 sm:col-span-2"
|
||||
>
|
||||
<dt class="text-sm font-medium text-gray-500">
|
||||
<p v-if="readOnlyFlag" class="font-medium">첨부된 파일</p>
|
||||
<a
|
||||
v-else
|
||||
href="javascript:void(0)"
|
||||
class="font-medium text-blue-600 hover:text-blue-500"
|
||||
@click="addFiles()"
|
||||
>
|
||||
파일 첨부
|
||||
</a>
|
||||
</dt>
|
||||
<dd class="mt-1 text-sm text-gray-900">
|
||||
<ul
|
||||
role="list"
|
||||
class="border border-gray-200 rounded-md divide-y divide-gray-200"
|
||||
>
|
||||
<li
|
||||
v-for="attachment in attachments"
|
||||
:key="attachment.name"
|
||||
class="pl-3 pr-4 py-3 flex items-center justify-between text-sm"
|
||||
>
|
||||
<div class="w-0 flex-1 flex items-center">
|
||||
<PaperClipIcon
|
||||
class="flex-shrink-0 h-5 w-5 text-gray-400"
|
||||
aria-hidden="true"
|
||||
/>
|
||||
<span class="ml-2 flex-1 w-0 truncate">
|
||||
{{ attachment.name }} {{ ', ' }}
|
||||
{{ _utils.formatBytes(attachment.size, 2) }}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<div v-if="readOnlyFlag" class="ml-4 flex-shrink-0">
|
||||
<a
|
||||
:href="
|
||||
_crossCtl.config['API_BASE_URL'].replace(
|
||||
'/api/',
|
||||
''
|
||||
) + attachment.localUrl
|
||||
"
|
||||
:download="attachment.name"
|
||||
target="_blank"
|
||||
class="font-medium text-blue-600 hover:text-blue-500"
|
||||
>
|
||||
다운로드
|
||||
</a>
|
||||
</div>
|
||||
<div v-else class="ml-4 flex-shrink-0">
|
||||
<a
|
||||
href="javascript:void(0)"
|
||||
class="font-medium text-red-600 hover:text-red-500"
|
||||
@click="rmvFile(attachment)"
|
||||
>
|
||||
삭제
|
||||
</a>
|
||||
</div>
|
||||
</li>
|
||||
</ul>
|
||||
</dd>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { stringLiteral } from '@babel/types';
|
||||
import { PaperClipIcon } from '@heroicons/vue/24/solid';
|
||||
const props = defineProps({
|
||||
attachments: {
|
||||
type: Array<{ name: ''; localUrl: ''; size: 0; type: 'text/html' }>,
|
||||
default: [],
|
||||
},
|
||||
readOnlyFlag: { type: Boolean, required: true },
|
||||
updateAttachments: {
|
||||
type: Function,
|
||||
required: false,
|
||||
default: () => {
|
||||
void 0;
|
||||
},
|
||||
},
|
||||
boardId: { type: String, required: false, default: null },
|
||||
secureEnabled: { type: Boolean, required: false, default: false },
|
||||
});
|
||||
|
||||
// console.log('huk props = ', props);
|
||||
|
||||
const currentDomain = ref('');
|
||||
|
||||
if (process.client) {
|
||||
currentDomain.value = _utils.getDomain(window.location.href);
|
||||
|
||||
console.log('currentDomain.value=', currentDomain.value);
|
||||
} else {
|
||||
console.log('server?');
|
||||
}
|
||||
|
||||
function addFiles() {
|
||||
const input = document.createElement('input');
|
||||
input.setAttribute('type', 'file');
|
||||
input.setAttribute('multiple', 'multiple');
|
||||
|
||||
input.click();
|
||||
|
||||
// Listen upload local image and save to server
|
||||
input.onchange = () => {
|
||||
if (input.files.length > 0) {
|
||||
console.log('we got file(s) : ', input.files);
|
||||
uploadFiles(input.files);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
async function uploadFiles(files) {
|
||||
const formData = new FormData();
|
||||
|
||||
for (let i = 0; i < files.length; i++) {
|
||||
formData.append('upload-file', files[i], files[i].name);
|
||||
}
|
||||
|
||||
formData.append('target', 'just');
|
||||
if (props.boardId != null) {
|
||||
formData.append('attachedTo', props.boardId);
|
||||
}
|
||||
formData.append('secureEnabled', props.secureEnabled.toString());
|
||||
|
||||
console.log('formData=', formData);
|
||||
|
||||
const responseJson = await _crossCtl.doUpload('just', formData);
|
||||
|
||||
console.log('responseJson=', responseJson);
|
||||
|
||||
if (responseJson['responseCode'] == 200) {
|
||||
props.updateAttachments([
|
||||
...props.attachments,
|
||||
...responseJson['files'],
|
||||
]);
|
||||
} else {
|
||||
alert('upload error : ' + responseJson['responseMessage']);
|
||||
}
|
||||
}
|
||||
|
||||
function rmvFile(target) {
|
||||
const tmpAry = [];
|
||||
for (let i = 0; i < props.attachments.length; i++) {
|
||||
if (props.attachments[i] != target) {
|
||||
tmpAry.push(props.attachments[i]);
|
||||
}
|
||||
}
|
||||
props.updateAttachments(tmpAry);
|
||||
}
|
||||
</script>
|
||||
40
inspond-nuxt-safekiso/base/components/BaseAvater1.vue
Normal file
40
inspond-nuxt-safekiso/base/components/BaseAvater1.vue
Normal file
@@ -0,0 +1,40 @@
|
||||
<template>
|
||||
<div>
|
||||
<span
|
||||
v-if="photoUrl == ''"
|
||||
class="inline-block rounded-full overflow-hidden bg-gray-100"
|
||||
:style="'height:' + photoSize + 'rem; width:' + photoSize + 'rem'"
|
||||
>
|
||||
<svg
|
||||
class="h-full w-full text-gray-300"
|
||||
fill="currentColor"
|
||||
viewBox="0 0 24 24"
|
||||
>
|
||||
<path
|
||||
d="M24 20.993V24H0v-2.996A14.977 14.977 0 0112.004 15c4.904 0 9.26 2.354 11.996 5.993zM16.002 8.999a4 4 0 11-8 0 4 4 0 018 0z"
|
||||
></path>
|
||||
</svg>
|
||||
</span>
|
||||
<img
|
||||
v-else
|
||||
class="'inline-block rounded-full border'"
|
||||
:style="'height:' + photoSize + 'rem; width:' + photoSize + 'rem'"
|
||||
:src="photoUrl"
|
||||
:alt="imageAlt"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
const props = defineProps({
|
||||
imageSize: { type: Number, default: 12 },
|
||||
imageAlt: { type: String, default: '' },
|
||||
imageUrl: { type: String, default: '' },
|
||||
});
|
||||
|
||||
const photoSize = ref(props.imageSize);
|
||||
const photoUrl = ref(props.imageUrl);
|
||||
const imageAlt = ref(props.imageAlt);
|
||||
</script>
|
||||
|
||||
<style lang="css" scoped></style>
|
||||
139
inspond-nuxt-safekiso/base/components/BaseBoardList1.vue
Normal file
139
inspond-nuxt-safekiso/base/components/BaseBoardList1.vue
Normal file
@@ -0,0 +1,139 @@
|
||||
<template>
|
||||
<div class="flex flex-col">
|
||||
<div class="-my-2 -mx-4 overflow-x-auto sm:-mx-6 lg:-mx-8">
|
||||
<div
|
||||
class="inline-block min-w-full py-2 align-middle md:px-6 lg:px-8"
|
||||
>
|
||||
<table class="min-w-full divide-y divide-gray-300">
|
||||
<thead>
|
||||
<tr>
|
||||
<th
|
||||
v-for="(heading, index) in headings"
|
||||
:key="index"
|
||||
:width="
|
||||
heading['widthRatio'] != ''
|
||||
? heading['widthRatio'] + '%'
|
||||
: '100%'
|
||||
"
|
||||
:class="
|
||||
index == 0
|
||||
? 'whitespace-nowrap py-3.5 pl-4 pr-3 text-left text-sm font-semibold text-gray-900 sm:pl-6 md:pl-0'
|
||||
: 'whitespace-nowrap py-3.5 px-3 text-left text-sm font-semibold text-gray-900'
|
||||
"
|
||||
scope="col"
|
||||
>
|
||||
{{ heading['title'] }}
|
||||
</th>
|
||||
|
||||
<th
|
||||
v-for="(action, index) in actions"
|
||||
:key="index + headings.length"
|
||||
:width="actions.length * 1"
|
||||
class="relative py-3.5 pl-3 pr-4 sm:pr-6 md:pr-0"
|
||||
scope="col"
|
||||
>
|
||||
<span class="sr-only">{{ action }}</span>
|
||||
</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody class="divide-y divide-gray-200">
|
||||
<tr v-for="(item, itemIndex) in data" :key="itemIndex">
|
||||
<td
|
||||
v-for="(heading, index) in headings"
|
||||
:key="index"
|
||||
:class="
|
||||
index == 0
|
||||
? 'max-w-0 whitespace-nowrap py-4 pl-4 pr-3 text-sm font-medium text-gray-900 sm:pl-6 md:pl-0'
|
||||
: 'w-full whitespace-nowrap py-4 px-3 text-sm text-gray-500'
|
||||
"
|
||||
>
|
||||
<div v-if="index == 0" class="">
|
||||
<a
|
||||
href="javascript:void(0)"
|
||||
class="hover:bg-gray-50"
|
||||
@click="
|
||||
doAction('보기', item[actionKey])
|
||||
"
|
||||
><div class="truncate">
|
||||
{{
|
||||
columnFilter
|
||||
? columnFilter(
|
||||
heading['key'],
|
||||
item[heading['key']]
|
||||
)
|
||||
: item[heading['key']]
|
||||
}}
|
||||
</div>
|
||||
</a>
|
||||
</div>
|
||||
<div v-else>
|
||||
{{
|
||||
columnFilter
|
||||
? columnFilter(
|
||||
heading['key'],
|
||||
item[heading['key']]
|
||||
)
|
||||
: item[heading['key']]
|
||||
}}
|
||||
</div>
|
||||
</td>
|
||||
|
||||
<td
|
||||
class="relative whitespace-nowrap py-4 pl-3 pr-4 text-right text-sm font-medium sm:pr-6 md:pr-0"
|
||||
>
|
||||
<a
|
||||
v-for="(action, index) in actions"
|
||||
:key="index"
|
||||
href="javascript:void(0)"
|
||||
:class="index != 0 ? 'ml-3' : ''"
|
||||
class="text-indigo-600 hover:text-indigo-900"
|
||||
@click="doAction(action, item[actionKey])"
|
||||
>{{ action
|
||||
}}<span class="sr-only"
|
||||
>, {{ item['serial'] }}</span
|
||||
></a
|
||||
>
|
||||
</td>
|
||||
</tr>
|
||||
<tr v-if="data.length == 0">
|
||||
<td
|
||||
:colspan="headings.length"
|
||||
class="whitespace-nowrap py-4 px-3 text-sm text-gray-500"
|
||||
>
|
||||
{{ 'No Data to display...' }}
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
const people = [
|
||||
{
|
||||
name: 'Lindsay Walton',
|
||||
title: 'Front-end Developer',
|
||||
email: 'lindsay.walton@example.com 아주 긴 아지 긴 아주 긴 이런 저런 긴 길 저런 길 갈 골 갈',
|
||||
role: 'Member',
|
||||
},
|
||||
// More people...
|
||||
];
|
||||
const props = defineProps({
|
||||
headings: { type: Array<object>, required: true },
|
||||
actions: { type: Array<string>, default: [] },
|
||||
data: { type: Array<object>, required: true },
|
||||
actionKey: { type: String, default: 'serial' },
|
||||
columnFilter: {
|
||||
type: Function,
|
||||
default: (key: string, val: string) => val,
|
||||
},
|
||||
doAction: {
|
||||
type: Function,
|
||||
default: (key: string) => {
|
||||
return key;
|
||||
},
|
||||
},
|
||||
});
|
||||
</script>
|
||||
73
inspond-nuxt-safekiso/base/components/BaseBoardView1.vue
Normal file
73
inspond-nuxt-safekiso/base/components/BaseBoardView1.vue
Normal file
@@ -0,0 +1,73 @@
|
||||
<template>
|
||||
<div class="bg-white shadow overflow-hidden sm:rounded-lg">
|
||||
<div
|
||||
class="px-4 py-5 sm:px-6 flex justify-between items-center flex-wrap sm:flex-nowrap"
|
||||
>
|
||||
<div>
|
||||
<h3 class="text-lg leading-6 font-medium text-gray-900">
|
||||
{{ title }}
|
||||
</h3>
|
||||
</div>
|
||||
<div>
|
||||
<div class="flex items-start">
|
||||
<div
|
||||
class="flex-shrink-0 inline-flex rounded-full border-2 border-white"
|
||||
>
|
||||
<BaseAvater1
|
||||
:image-size="2"
|
||||
:image-url="profileUrl"
|
||||
:image-alt="name + '의 프로필 사진'"
|
||||
/>
|
||||
</div>
|
||||
<div class="ml-4">
|
||||
<div class="text-base font-medium">{{ name }}</div>
|
||||
<div class="text-base text-xs">{{ created }}</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="border-t border-gray-200 px-4 py-5 sm:px-6 min-h-[15rem]">
|
||||
<div
|
||||
ref="myCoolDiv"
|
||||
class="prose mt-3 space-y-0 max-w-none"
|
||||
v-html="content"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref } from 'vue';
|
||||
|
||||
const props = defineProps({
|
||||
name: { type: String, required: true },
|
||||
profileUrl: { type: String, required: true },
|
||||
title: { type: String, required: true },
|
||||
content: { type: String, required: true },
|
||||
flags: { type: Array<string>, default: [] },
|
||||
attachments: { type: Array<string>, default: [] },
|
||||
hitCount: { type: Number, required: true },
|
||||
likeCount: { type: Number, required: true },
|
||||
dislikeCount: { type: Number, required: true },
|
||||
commentCount: { type: Number, required: true },
|
||||
reportCount: { type: Number, required: true },
|
||||
status: { type: Number, required: true },
|
||||
updated: { type: String, required: true },
|
||||
created: { type: String, required: true },
|
||||
});
|
||||
|
||||
const myCoolDiv = ref(null);
|
||||
|
||||
watch(myCoolDiv, () => {
|
||||
if (myCoolDiv.value.childNodes[0].tagName == 'IFRAME') {
|
||||
myCoolDiv.value.childNodes[0].classList.add('xl:w-[1243px]');
|
||||
myCoolDiv.value.childNodes[0].classList.add('xl:h-[621.5px]');
|
||||
myCoolDiv.value.childNodes[0].classList.add('lg:w-[1243px]');
|
||||
myCoolDiv.value.childNodes[0].classList.add('lg:h-[621.5px]');
|
||||
myCoolDiv.value.childNodes[0].classList.add('md:w-[900px]');
|
||||
myCoolDiv.value.childNodes[0].classList.add('md:h-[400px]');
|
||||
myCoolDiv.value.childNodes[0].classList.add('w-[310px]');
|
||||
myCoolDiv.value.childNodes[0].classList.add('h-[250px]');
|
||||
}
|
||||
});
|
||||
</script>
|
||||
372
inspond-nuxt-safekiso/base/components/BaseCommentCtl1.vue
Normal file
372
inspond-nuxt-safekiso/base/components/BaseCommentCtl1.vue
Normal file
@@ -0,0 +1,372 @@
|
||||
<template>
|
||||
<!-- Comments-->
|
||||
<section aria-labelledby="notes-title">
|
||||
<div
|
||||
class="border border-gray-300 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"
|
||||
>
|
||||
{{ title }}
|
||||
</h2>
|
||||
</div>
|
||||
<div class="px-4 py-6 sm:px-6">
|
||||
<ul role="list" class="space-y-8">
|
||||
<li
|
||||
v-for="commentItem in listData"
|
||||
:key="commentItem.serial"
|
||||
>
|
||||
<div class="flex space-x-3">
|
||||
<div class="flex-shrink-0 items-center">
|
||||
<BaseAvater1
|
||||
:image-size="2"
|
||||
:image-url="commentItem.profile_url"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<div class="text-sm">
|
||||
<a
|
||||
:href="
|
||||
'/user/profile/' +
|
||||
commentItem.pid
|
||||
"
|
||||
class="font-medium text-gray-900"
|
||||
>{{ commentItem.nick }}</a
|
||||
>
|
||||
</div>
|
||||
<div
|
||||
class="mt-1 text-sm text-gray-700 break-all"
|
||||
>
|
||||
<p>{{ commentItem.comment }}</p>
|
||||
</div>
|
||||
<div class="mt-2 text-sm space-x-2">
|
||||
<span
|
||||
class="text-gray-500 font-medium"
|
||||
>{{
|
||||
$dayjs(
|
||||
commentItem.created
|
||||
).fromNow()
|
||||
}}</span
|
||||
>
|
||||
{{ ' ' }}
|
||||
<span
|
||||
v-if="!readOnlyFlag"
|
||||
class="text-gray-500 font-medium"
|
||||
>·</span
|
||||
>
|
||||
{{ ' ' }}
|
||||
|
||||
<span
|
||||
v-if="commentItem.like_count != 0"
|
||||
class="text-gray-500 font-medium"
|
||||
>{{
|
||||
'좋아요 ' +
|
||||
_utils.formatNumberInBytesStyle(
|
||||
commentItem.like_count,
|
||||
0
|
||||
) +
|
||||
'개'
|
||||
}}</span
|
||||
>
|
||||
<span
|
||||
v-if="
|
||||
commentItem.dislike_count != 0
|
||||
"
|
||||
class="text-gray-500 font-medium"
|
||||
>{{
|
||||
'싫어요 ' +
|
||||
_utils.formatNumberInBytesStyle(
|
||||
commentItem.dislike_count,
|
||||
0
|
||||
) +
|
||||
'개'
|
||||
}}</span
|
||||
>
|
||||
<span
|
||||
v-if="commentItem.report_count != 0"
|
||||
class="text-gray-500 font-medium"
|
||||
>{{
|
||||
'신고 ' +
|
||||
_utils.formatNumberInBytesStyle(
|
||||
commentItem.report_count,
|
||||
0
|
||||
) +
|
||||
'개'
|
||||
}}</span
|
||||
>
|
||||
|
||||
<button
|
||||
v-if="
|
||||
commentItem.myFlag == true &&
|
||||
!readOnlyFlag
|
||||
"
|
||||
type="button"
|
||||
class="text-gray-900 font-medium"
|
||||
@click="
|
||||
doAction(
|
||||
'delete',
|
||||
commentItem.cid
|
||||
)
|
||||
"
|
||||
>
|
||||
삭제
|
||||
</button>
|
||||
<button
|
||||
v-if="!readOnlyFlag"
|
||||
type="button"
|
||||
class="text-gray-900 font-medium"
|
||||
@click="
|
||||
doAction(
|
||||
'like',
|
||||
commentItem.cid
|
||||
)
|
||||
"
|
||||
>
|
||||
좋아요
|
||||
</button>
|
||||
<button
|
||||
v-if="!readOnlyFlag"
|
||||
type="button"
|
||||
class="text-gray-900 font-medium"
|
||||
@click="
|
||||
doAction(
|
||||
'dislike',
|
||||
commentItem.cid
|
||||
)
|
||||
"
|
||||
>
|
||||
싫어요
|
||||
</button>
|
||||
|
||||
<button
|
||||
v-if="!readOnlyFlag"
|
||||
type="button"
|
||||
class="text-gray-900 font-medium"
|
||||
@click="
|
||||
doAction(
|
||||
'report',
|
||||
commentItem.cid
|
||||
)
|
||||
"
|
||||
>
|
||||
신고
|
||||
</button>
|
||||
|
||||
<button
|
||||
v-if="!readOnlyFlag"
|
||||
type="button"
|
||||
class="text-gray-900 font-medium"
|
||||
@click="
|
||||
doAction(
|
||||
'cancel',
|
||||
commentItem.cid
|
||||
)
|
||||
"
|
||||
>
|
||||
신고 취소
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</li>
|
||||
<li v-if="listData.length == 0">
|
||||
<div>등록된 댓글이 없습니다.</div>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
<BasePagination1
|
||||
class="mb-5 px-4 sm:px-6"
|
||||
:total-page-count="totalPageCount"
|
||||
:current-page-number="currentPageNumber"
|
||||
:page-size="pageSize"
|
||||
:records-total="recordsTotal"
|
||||
:page-move="pageMove"
|
||||
/>
|
||||
</div>
|
||||
<div v-if="!readOnlyFlag" class="bg-gray-50 px-4 py-6 sm:px-6">
|
||||
<div class="flex space-x-3">
|
||||
<div class="flex-shrink-0">
|
||||
<BaseUserProfileImage :image-size="2" />
|
||||
</div>
|
||||
<div class="min-w-0 flex-1">
|
||||
<form>
|
||||
<div>
|
||||
<label for="comment" class="sr-only">{{
|
||||
title
|
||||
}}</label>
|
||||
<textarea
|
||||
id="comment"
|
||||
v-model="comment"
|
||||
style="resize: none"
|
||||
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="#"
|
||||
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>
|
||||
<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="addComment()"
|
||||
>
|
||||
댓글 등록
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { QuestionMarkCircleIcon } from '@heroicons/vue/24/solid';
|
||||
|
||||
const comment = ref('');
|
||||
|
||||
const props = defineProps({
|
||||
tid: { type: String, required: true },
|
||||
title: { type: String, default: '댓글' },
|
||||
readOnlyFlag: { type: Boolean, required: true },
|
||||
});
|
||||
|
||||
// console.log('huk props = ', props);
|
||||
|
||||
const title = ref(props.title);
|
||||
|
||||
console.log('title = ', title.value);
|
||||
|
||||
const listData = ref([]);
|
||||
|
||||
const readOnlyFlag = ref(props.readOnlyFlag);
|
||||
|
||||
const totalPageCount = ref(1);
|
||||
const currentPageNumber = ref(Number.MAX_SAFE_INTEGER);
|
||||
const pageSize = ref(3);
|
||||
const recordsTotal = ref(0);
|
||||
|
||||
function pageMove(targetPageIdex) {
|
||||
// console.log('on pageMove(), targetPageIdex=', targetPageIdex);
|
||||
currentPageNumber.value = targetPageIdex;
|
||||
refresh();
|
||||
}
|
||||
|
||||
async function addComment() {
|
||||
if (comment.value.trim() != '') {
|
||||
const responseJson = await _crossCtl.doComm('insert', 'comment', {
|
||||
hero: props.tid,
|
||||
comment: comment.value,
|
||||
for: 'board',
|
||||
});
|
||||
|
||||
if (responseJson['responseCode'] != 200) {
|
||||
alert(responseJson['responseMessage']);
|
||||
} else {
|
||||
console.log('responseJson=', responseJson);
|
||||
comment.value = '';
|
||||
refresh();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async function refresh() {
|
||||
if (props.tid != '') {
|
||||
const responseJson = await _crossCtl.doComm('list', 'comment:active', {
|
||||
hero: props.tid,
|
||||
start: (currentPageNumber.value - 1) * pageSize.value,
|
||||
length: pageSize.value,
|
||||
});
|
||||
|
||||
if (responseJson['responseCode'] != 200) {
|
||||
alert(responseJson['responseMessage']);
|
||||
} else {
|
||||
console.log('responseJson=', responseJson);
|
||||
currentPageNumber.value = responseJson['currentPageNumber'];
|
||||
totalPageCount.value = responseJson['totalPageCount'];
|
||||
pageSize.value = parseInt(responseJson['pageSize']);
|
||||
recordsTotal.value = responseJson['recordsTotal'];
|
||||
|
||||
listData.value = responseJson['data'];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (props.tid != '' && _crossCtl.isAuthenticated) {
|
||||
const responseJson = await _crossCtl.doComm('list', 'like', {
|
||||
hero: props.tid,
|
||||
start: 0,
|
||||
length: -1,
|
||||
});
|
||||
|
||||
if (responseJson['responseCode'] != 200) {
|
||||
alert(responseJson['responseMessage']);
|
||||
} else {
|
||||
console.log('like responseJson=', responseJson);
|
||||
refresh();
|
||||
}
|
||||
}
|
||||
|
||||
async function doAction(tag, target) {
|
||||
console.log('in doAction(), tag =', tag, ', target =', target);
|
||||
let tmpResponseJson = null;
|
||||
switch (tag) {
|
||||
case 'like':
|
||||
case 'dislike':
|
||||
tmpResponseJson = await _crossCtl.doComm('update', 'like', {
|
||||
domain: props.tid,
|
||||
hero: target,
|
||||
for: 'comment',
|
||||
tag: tag,
|
||||
});
|
||||
refresh();
|
||||
break;
|
||||
case 'report':
|
||||
tmpResponseJson = await _crossCtl.doComm('update', 'report', {
|
||||
domain: props.tid,
|
||||
hero: target,
|
||||
for: 'comment',
|
||||
tag: tag,
|
||||
});
|
||||
refresh();
|
||||
break;
|
||||
case 'cancel':
|
||||
tmpResponseJson = await _crossCtl.doComm('update', 'report', {
|
||||
domain: props.tid,
|
||||
hero: target,
|
||||
for: 'comment',
|
||||
tag: tag,
|
||||
});
|
||||
refresh();
|
||||
|
||||
break;
|
||||
case 'delete':
|
||||
tmpResponseJson = await _crossCtl.doComm('delete', 'comment', {
|
||||
hero: target,
|
||||
tid: props.tid,
|
||||
from: 'board',
|
||||
});
|
||||
refresh();
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
</script>
|
||||
25
inspond-nuxt-safekiso/base/components/BaseFaqItem1.vue
Normal file
25
inspond-nuxt-safekiso/base/components/BaseFaqItem1.vue
Normal file
@@ -0,0 +1,25 @@
|
||||
<template>
|
||||
<div>
|
||||
<dt class="text-lg leading-6 font-medium text-gray-900">
|
||||
{{ item.question }}
|
||||
</dt>
|
||||
|
||||
<dd
|
||||
class="mt-2 text-base text-gray-500"
|
||||
style="
|
||||
white-space: pre-wrap;
|
||||
word-wrap: break-word;
|
||||
font-family: inherit;
|
||||
"
|
||||
v-html="item.answer.replace(/(?:\r\n|\r|\n)/g, '<br />')"
|
||||
></dd>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
const props = defineProps({
|
||||
item: { type: Object, required: true },
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="css" scoped></style>
|
||||
141
inspond-nuxt-safekiso/base/components/BaseList1.vue
Normal file
141
inspond-nuxt-safekiso/base/components/BaseList1.vue
Normal file
@@ -0,0 +1,141 @@
|
||||
<template>
|
||||
<div class="pb-8 px-0 sm:px-0 lg:px-0">
|
||||
<div
|
||||
class="-mx-4 mt-8 overflow-hidden shadow ring-1 ring-black ring-opacity-5 sm:-mx-6 md:mx-0 md:rounded-lg"
|
||||
>
|
||||
<table class="min-w-full divide-y divide-gray-300">
|
||||
<thead class="bg-gray-50">
|
||||
<tr>
|
||||
<th
|
||||
v-for="(heading, index) in headings"
|
||||
:key="index"
|
||||
scope="col"
|
||||
:class="heading['class']"
|
||||
>
|
||||
{{ heading['title'] }}
|
||||
</th>
|
||||
|
||||
<th
|
||||
v-for="(action, index) in actions"
|
||||
:key="index + headings.length"
|
||||
scope="col"
|
||||
class="relative py-3.5 pl-3 pr-4 sm:pr-6"
|
||||
>
|
||||
<span class="sr-only">{{ action }}</span>
|
||||
</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody class="divide-y divide-gray-200 bg-white">
|
||||
<tr v-if="data.length == 0">
|
||||
<td>
|
||||
<div class="py-3.5 pl-3 pr-4 sm:pr-6">
|
||||
{{ noDataMessage }}
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
<tr v-for="(item, itemIndex) in data" :key="itemIndex">
|
||||
<td
|
||||
v-for="(heading, index) in headings"
|
||||
:key="index"
|
||||
:class="heading['subClass']"
|
||||
>
|
||||
{{
|
||||
columnFilter
|
||||
? columnFilter(
|
||||
heading['key'],
|
||||
item[heading['key']]
|
||||
)
|
||||
: item[heading['key']]
|
||||
}}
|
||||
|
||||
<dl
|
||||
v-for="(hItem, hItemIndex) in heading[
|
||||
'hiddenInfo'
|
||||
]['dts']"
|
||||
:key="hItemIndex"
|
||||
:class="heading['hiddenInfo']['headClass']"
|
||||
>
|
||||
<dt
|
||||
:class="
|
||||
heading['hiddenInfo']['dts'][
|
||||
hItemIndex
|
||||
]['class']
|
||||
"
|
||||
>
|
||||
{{
|
||||
heading['hiddenInfo']['dts'][
|
||||
hItemIndex
|
||||
]['title']
|
||||
}}
|
||||
</dt>
|
||||
<dd
|
||||
:class="
|
||||
heading['hiddenInfo']['dds'][
|
||||
hItemIndex
|
||||
]['class']
|
||||
"
|
||||
>
|
||||
{{
|
||||
columnFilter
|
||||
? columnFilter(
|
||||
heading['hiddenInfo']['dds'][
|
||||
hItemIndex
|
||||
]['key'],
|
||||
item[
|
||||
heading['hiddenInfo'][
|
||||
'dds'
|
||||
][hItemIndex]['key']
|
||||
]
|
||||
)
|
||||
: item[
|
||||
heading['hiddenInfo']['dds'][
|
||||
hItemIndex
|
||||
]['key']
|
||||
]
|
||||
}}
|
||||
</dd>
|
||||
</dl>
|
||||
</td>
|
||||
|
||||
<td
|
||||
class="py-4 pl-3 pr-4 text-right text-sm font-medium sm:pr-6"
|
||||
>
|
||||
<a
|
||||
v-for="(action, index) in actions"
|
||||
:key="index"
|
||||
href="javascript:void(0)"
|
||||
class="text-indigo-600 hover:text-indigo-900"
|
||||
@click="doAction(action, item[actionKey])"
|
||||
>{{ action
|
||||
}}<span class="sr-only"
|
||||
>, {{ item['serial'] }}</span
|
||||
></a
|
||||
>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
const props = defineProps({
|
||||
headings: { type: Array<object>, required: true },
|
||||
actions: { type: Array<string>, default: [] },
|
||||
keys: { type: Array<string>, default: [] },
|
||||
data: { type: Array<object>, required: true },
|
||||
actionKey: { type: String, default: 'serial' },
|
||||
columnFilter: {
|
||||
type: Function,
|
||||
default: (key: string, val: string) => val,
|
||||
},
|
||||
doAction: {
|
||||
type: Function,
|
||||
default: (key: string) => {
|
||||
return key;
|
||||
},
|
||||
},
|
||||
noDataMessage: { type: String, default: '검색된 데이터가 없습니다.' },
|
||||
});
|
||||
</script>
|
||||
125
inspond-nuxt-safekiso/base/components/BaseMainDataList2.vue
Normal file
125
inspond-nuxt-safekiso/base/components/BaseMainDataList2.vue
Normal file
@@ -0,0 +1,125 @@
|
||||
<template>
|
||||
<div class="flex flex-col overflow-hidden rounded-lg shadow-lg">
|
||||
<div
|
||||
v-if="isLoading"
|
||||
class="flex flex-1 flex-col justify-between bg-white p-6"
|
||||
>
|
||||
<div class="flex-1 relative">
|
||||
<div class="flex border-b-2 border-indigo-500 pb-2">
|
||||
<p class="text-sm font-medium text-indigo-600">
|
||||
<a
|
||||
href="javascript:void(0)"
|
||||
@click="pageMove(pageTitle[0].id, '')"
|
||||
>{{
|
||||
pageTitle[0]?.title ?? pageTitle[0]?.title
|
||||
}} </a
|
||||
>
|
||||
</p>
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
stroke-width="1.5"
|
||||
stroke="currentColor"
|
||||
class="w-6 h-6 text-indigo-500 absolute right-0 cursor-pointer"
|
||||
@click="pageMove(pageTitle[0].id, '')"
|
||||
>
|
||||
<path
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
d="M12 6v12m6-6H6"
|
||||
/>
|
||||
</svg>
|
||||
</div>
|
||||
<a
|
||||
v-for="list in dataList"
|
||||
:key="list.serial"
|
||||
class="mt-2 block"
|
||||
>
|
||||
<p
|
||||
class="mt-3 text-base text-gray-500 cursor-pointer"
|
||||
@click="pageMove(pageTitle[0].id, list)"
|
||||
>
|
||||
{{
|
||||
list.title.length >= 20
|
||||
? list.title.substr(0, 20) + '...'
|
||||
: list.title.substr(0, 20)
|
||||
}}
|
||||
</p>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
<div v-else class="flex flex-1 flex-col justify-between bg-white p-6">
|
||||
<div class="flex-1 relative">
|
||||
<div class="flex pb-2 bg-sky-100 animate-pulse rounded-lg">
|
||||
<p class="text-sm font-medium text-gray-600">
|
||||
<a href="javascript:void(0)"> </a>
|
||||
</p>
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
stroke-width="1.5"
|
||||
stroke="currentColor"
|
||||
class="hidden w-6 h-6 text-indigo-500 absolute right-0 cursor-pointer"
|
||||
>
|
||||
<path
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
d="M12 6v12m6-6H6"
|
||||
/>
|
||||
</svg>
|
||||
</div>
|
||||
<a class="mt-2 block">
|
||||
<p
|
||||
class="mt-3 bg-slate-100 animate-pulse rounded-lg cursor-pointer w-3/4"
|
||||
>
|
||||
|
||||
</p>
|
||||
<p
|
||||
class="mt-3 bg-slate-100 animate-pulse rounded-lg cursor-pointer w-full"
|
||||
>
|
||||
|
||||
</p>
|
||||
<p
|
||||
class="mt-3 bg-slate-100 animate-pulse rounded-lg cursor-pointer w-2/3"
|
||||
>
|
||||
|
||||
</p>
|
||||
<p
|
||||
class="mt-3 bg-slate-100 animate-pulse rounded-lg cursor-pointer w-3/4"
|
||||
>
|
||||
|
||||
</p>
|
||||
<p
|
||||
class="mt-3 bg-slate-100 animate-pulse rounded-lg cursor-pointer w-3/4"
|
||||
>
|
||||
|
||||
</p>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
const props = defineProps({
|
||||
dataList: { type: Object, required: true },
|
||||
pageTitle: { type: Object, required: true },
|
||||
isLoading: { type: Boolean, default: false },
|
||||
});
|
||||
|
||||
const bordPage = ['preach', 'programme', 'youtube'];
|
||||
|
||||
function pageMove(pageName, detail) {
|
||||
if (pageName === 'notice') {
|
||||
navigateTo('/support/' + pageName);
|
||||
} else if (bordPage.includes(pageName) == true) {
|
||||
if (detail == '') {
|
||||
navigateTo('/board/' + pageName + '/list');
|
||||
} else {
|
||||
navigateTo('/board/' + pageName + '/view/' + detail.cid);
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
131
inspond-nuxt-safekiso/base/components/BaseModal1.vue
Normal file
131
inspond-nuxt-safekiso/base/components/BaseModal1.vue
Normal file
@@ -0,0 +1,131 @@
|
||||
<!-- This example requires Tailwind CSS v2.0+ -->
|
||||
<template>
|
||||
<TransitionRoot as="template" :show="open">
|
||||
<Dialog as="div" class="relative z-10" @close="open = false">
|
||||
<div
|
||||
class="fixed inset-0 bg-gray-500 bg-opacity-75 transition-opacity"
|
||||
/>
|
||||
|
||||
<div class="fixed z-10 inset-0 overflow-y-auto">
|
||||
<div
|
||||
class="flex items-end sm:items-center justify-center min-h-full p-4 text-center sm:p-0"
|
||||
>
|
||||
<DialogPanel
|
||||
class="relative bg-white rounded-lg text-left overflow-hidden shadow-xl transform transition-all sm:my-8 sm:max-w-lg sm:w-full"
|
||||
>
|
||||
<div class="bg-white px-4 pt-5 pb-4 sm:p-6 sm:pb-4">
|
||||
<div class="sm:flex sm:items-start">
|
||||
<div
|
||||
:class="
|
||||
currentModalInfo['type'] == 'ok'
|
||||
? 'mx-auto flex-shrink-0 flex items-center justify-center h-12 w-12 rounded-full bg-green-100 sm:mx-0 sm:h-10 sm:w-10'
|
||||
: currentModalInfo['type'] ==
|
||||
'error'
|
||||
? 'mx-auto flex-shrink-0 flex items-center justify-center h-12 w-12 rounded-full bg-red-100 sm:mx-0 sm:h-10 sm:w-10'
|
||||
: 'mx-auto flex-shrink-0 flex items-center justify-center h-12 w-12 rounded-full bg-gray-100 sm:mx-0 sm:h-10 sm:w-10'
|
||||
"
|
||||
>
|
||||
<CheckIcon
|
||||
v-if="currentModalInfo['type'] == 'ok'"
|
||||
class="h-6 w-6 text-green-600"
|
||||
aria-hidden="true"
|
||||
/>
|
||||
<ExclamationTriangleIcon
|
||||
v-else-if="
|
||||
currentModalInfo['type'] == 'error'
|
||||
"
|
||||
class="h-6 w-6 text-red-600"
|
||||
aria-hidden="true"
|
||||
/>
|
||||
<ExclamationCircleIcon
|
||||
v-else
|
||||
class="h-6 w-6 text-gray-600"
|
||||
aria-hidden="true"
|
||||
/>
|
||||
</div>
|
||||
<div
|
||||
class="mt-3 text-center sm:mt-0 sm:ml-4 sm:text-left"
|
||||
>
|
||||
<DialogTitle
|
||||
as="h3"
|
||||
class="text-lg leading-6 font-medium text-gray-900"
|
||||
>
|
||||
{{ currentModalInfo['title'] }}
|
||||
</DialogTitle>
|
||||
<div class="mt-2">
|
||||
<p class="text-sm text-gray-500">
|
||||
{{ currentModalInfo['message'] }}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="bg-gray-50 px-4 py-3 sm:px-6 sm:flex sm:flex-row-reverse"
|
||||
>
|
||||
<button
|
||||
type="button"
|
||||
:class="
|
||||
currentModalInfo['type'] == 'ok'
|
||||
? 'w-full inline-flex justify-center rounded-md border border-transparent shadow-sm px-4 py-2 bg-indigo-600 text-base font-medium text-white hover:bg-indigo-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500 sm:ml-3 sm:w-auto sm:text-sm'
|
||||
: currentModalInfo['type'] == 'error'
|
||||
? 'w-full inline-flex justify-center rounded-md border border-transparent shadow-sm px-4 py-2 bg-red-600 text-base font-medium text-white hover:bg-red-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-red-500 sm:ml-3 sm:w-auto sm:text-sm'
|
||||
: 'w-full inline-flex justify-center rounded-md border border-transparent shadow-sm px-4 py-2 bg-gray-600 text-base font-medium text-white hover:bg-gray-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-gray-500 sm:ml-3 sm:w-auto sm:text-sm'
|
||||
"
|
||||
@click="
|
||||
_crossCtl.onModalClosed(
|
||||
currentModalInfo.serial,
|
||||
0
|
||||
)
|
||||
"
|
||||
>
|
||||
{{ currentModalInfo['btnTexts'][0] }}
|
||||
</button>
|
||||
<button
|
||||
v-if="currentModalInfo['btnCount'] > 1"
|
||||
ref="cancelButtonRef"
|
||||
type="button"
|
||||
:class="
|
||||
currentModalInfo['type'] == 'ok'
|
||||
? 'mt-3 w-full inline-flex justify-center rounded-md border border-gray-300 shadow-sm px-4 py-2 bg-white text-base font-medium text-gray-700 hover:bg-gray-50 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500 sm:mt-0 sm:ml-3 sm:w-auto sm:text-sm'
|
||||
: currentModalInfo['type'] == 'error'
|
||||
? 'mt-3 w-full inline-flex justify-center rounded-md border border-gray-300 shadow-sm px-4 py-2 bg-white text-base font-medium text-gray-700 hover:bg-gray-50 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500 sm:mt-0 sm:ml-3 sm:w-auto sm:text-sm'
|
||||
: 'mt-3 w-full inline-flex justify-center rounded-md border border-gray-300 shadow-sm px-4 py-2 bg-white text-base font-medium text-gray-700 hover:bg-gray-50 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500 sm:mt-0 sm:ml-3 sm:w-auto sm:text-sm'
|
||||
"
|
||||
@click="
|
||||
_crossCtl.onModalClosed(
|
||||
currentModalInfo.serial,
|
||||
1
|
||||
)
|
||||
"
|
||||
>
|
||||
{{ currentModalInfo['btnTexts'][1] }}
|
||||
</button>
|
||||
</div>
|
||||
</DialogPanel>
|
||||
</div>
|
||||
</div>
|
||||
</Dialog>
|
||||
</TransitionRoot>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { computed } from 'vue';
|
||||
import {
|
||||
Dialog,
|
||||
DialogPanel,
|
||||
DialogTitle,
|
||||
TransitionRoot,
|
||||
} from '@headlessui/vue';
|
||||
import {
|
||||
CheckIcon,
|
||||
ExclamationTriangleIcon,
|
||||
ExclamationCircleIcon,
|
||||
} from '@heroicons/vue/24/outline';
|
||||
|
||||
const currentModalInfo = _crossCtl.currentModalInfo;
|
||||
|
||||
const open = computed(() => {
|
||||
return currentModalInfo.value['serial'] !== -1;
|
||||
});
|
||||
</script>
|
||||
222
inspond-nuxt-safekiso/base/components/BaseNavSideBar1.vue
Normal file
222
inspond-nuxt-safekiso/base/components/BaseNavSideBar1.vue
Normal file
@@ -0,0 +1,222 @@
|
||||
<!-- This example requires Tailwind CSS v2.0+ -->
|
||||
<template>
|
||||
<div class="flex-1 h-0 pt-5 pb-4 overflow-y-auto bg-white">
|
||||
<div class="flex-shrink-0 flex items-center px-4">
|
||||
<a href="javascript:void(0)" @click="router.push('/')">
|
||||
<!--{{ siteName }}-->
|
||||
<img
|
||||
src="/kiso_ci_1.png"
|
||||
alt="KISO CI"
|
||||
width="500"
|
||||
height="600"
|
||||
/>
|
||||
</a>
|
||||
</div>
|
||||
<nav class="flex-1 mt-5 px-2 space-y-1 bg-white" aria-label="Sidebar">
|
||||
<template v-for="item in currentMenu['main']" :key="item.idx">
|
||||
<div v-if="!item.subs">
|
||||
<a
|
||||
href="javascript:void(0)"
|
||||
:class="[
|
||||
item['current']
|
||||
? 'bg-gray-100 text-gray-900'
|
||||
: 'bg-white text-gray-600 hover:bg-gray-50 hover:text-gray-900',
|
||||
'group w-full flex items-center pl-2 py-2 text-sm font-medium rounded-md',
|
||||
]"
|
||||
@click="handleItemClick(item)"
|
||||
>
|
||||
<component
|
||||
:is="item['icon']"
|
||||
:class="[
|
||||
item['current']
|
||||
? 'text-gray-500'
|
||||
: 'text-gray-400 group-hover:text-gray-500',
|
||||
'mr-3 flex-shrink-0 h-6 w-6',
|
||||
]"
|
||||
aria-hidden="true"
|
||||
/>
|
||||
{{ item['title'] }}
|
||||
</a>
|
||||
</div>
|
||||
<Disclosure
|
||||
v-else
|
||||
v-slot="{ open, close }"
|
||||
as="div"
|
||||
class="space-y-1"
|
||||
>
|
||||
<DisclosureButton
|
||||
:class="[
|
||||
item['current']
|
||||
? 'bg-gray-100 text-gray-900'
|
||||
: 'bg-white text-gray-600 hover:bg-gray-50 hover:text-gray-900',
|
||||
'group w-full flex items-center pl-2 pr-1 py-2 text-left text-sm font-medium rounded-md',
|
||||
]"
|
||||
>
|
||||
<component
|
||||
:is="item['icon']"
|
||||
class="mr-3 flex-shrink-0 h-6 w-6 text-gray-400 group-hover:text-gray-500"
|
||||
aria-hidden="true"
|
||||
/>
|
||||
<span class="flex-1">
|
||||
{{ item.title }}
|
||||
</span>
|
||||
<svg
|
||||
:class="[
|
||||
open
|
||||
? 'text-gray-400 rotate-90'
|
||||
: 'text-gray-300',
|
||||
'ml-3 flex-shrink-0 h-5 w-5 transform group-hover:text-gray-400 transition-colors ease-in-out duration-150',
|
||||
]"
|
||||
viewBox="0 0 20 20"
|
||||
aria-hidden="true"
|
||||
>
|
||||
<path d="M6 6L14 10L6 14V6Z" fill="currentColor" />
|
||||
</svg>
|
||||
</DisclosureButton>
|
||||
<DisclosurePanel class="space-y-1">
|
||||
<a
|
||||
v-for="subItem in item.subs"
|
||||
:key="subItem['idx']"
|
||||
href="javascript:void(0)"
|
||||
:class="[
|
||||
subItem['current']
|
||||
? 'bg-gray-100 text-gray-900'
|
||||
: 'bg-white text-gray-600 hover:text-gray-900 hover:bg-gray-50',
|
||||
'group w-full flex items-center pl-10 pr-2 py-2 text-sm font-medium rounded-md ',
|
||||
]"
|
||||
@click="handleSubItemClick(subItem)"
|
||||
>
|
||||
{{ subItem['title'] }}
|
||||
</a>
|
||||
</DisclosurePanel>
|
||||
<button
|
||||
v-show="false"
|
||||
:ref="(el) => (elements[item.idx] = el)"
|
||||
:data-id="item.idx"
|
||||
@click="doClose(close)"
|
||||
></button>
|
||||
<DisclosureStateEmitter
|
||||
:show="open"
|
||||
@show="hideOther(item.idx)"
|
||||
/>
|
||||
</Disclosure>
|
||||
</template>
|
||||
</nav>
|
||||
</div>
|
||||
|
||||
<div class="flex-shrink-0 flex bg-white p-4">
|
||||
<a href="#" class="flex-shrink-0 group block">
|
||||
<div class="flex items-center">
|
||||
<div class="space-y-1">
|
||||
<h3
|
||||
id="projects-headline"
|
||||
class="px-3 text-xs font-semibold text-gray-500 uppercase tracking-wider"
|
||||
></h3>
|
||||
<div
|
||||
class="space-y-1"
|
||||
role="group"
|
||||
aria-labelledby="projects-headline"
|
||||
>
|
||||
<a
|
||||
href="javascript:void(0)"
|
||||
class="group flex items-center px-3 py-2 text-sm font-medium text-gray-600 rounded-md hover:text-gray-900 hover:bg-gray-50"
|
||||
@click="doSignInAndOut()"
|
||||
>
|
||||
<span class="truncate">
|
||||
{{ isAuthenticated ? '로그아웃' : '로그인' }}
|
||||
</span>
|
||||
</a>
|
||||
|
||||
<a
|
||||
v-for="item in currentMenu['sub']"
|
||||
:key="item.idx"
|
||||
href="javascript:void(0)"
|
||||
class="group flex items-center px-3 py-2 text-sm font-medium text-gray-600 rounded-md hover:text-gray-900 hover:bg-gray-50"
|
||||
@click="handleItemClick(item)"
|
||||
>
|
||||
<span class="truncate">
|
||||
{{ item.title }}
|
||||
</span>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</a>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { Disclosure, DisclosureButton, DisclosurePanel } from '@headlessui/vue';
|
||||
const props = defineProps({
|
||||
onMove: {
|
||||
type: Function,
|
||||
required: true,
|
||||
},
|
||||
});
|
||||
|
||||
const isAuthenticated = _crossCtl.isAuthenticated;
|
||||
|
||||
const siteName = ref(_siteConfig.siteName);
|
||||
|
||||
const router = useRouter();
|
||||
|
||||
const currentMenu = _crossCtl.menu;
|
||||
|
||||
const elements = ref([]);
|
||||
|
||||
async function doSignInAndOut() {
|
||||
if (_crossCtl.isAuthenticated.value) {
|
||||
const response = await _crossCtl.doComm('signout', '', {});
|
||||
console.log('response=', response);
|
||||
if (response['responseCode'] == 200) {
|
||||
_crossCtl.setUserInfo({
|
||||
isAdmin: false,
|
||||
isApproved: false,
|
||||
isAuthenticated: false,
|
||||
isHighLeveled: false,
|
||||
isOp: false,
|
||||
isSuperOp: false,
|
||||
});
|
||||
_crossCtl.setUserProfile({});
|
||||
isAuthenticated.value = false;
|
||||
window.location.href = '/';
|
||||
} else {
|
||||
alert(response['responseMessage']);
|
||||
}
|
||||
} else {
|
||||
console.log("go user login");
|
||||
navigateTo({
|
||||
path: '/user/signin',
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
function handleItemClick(item) {
|
||||
hideOther(Number.MAX_SAFE_INTEGER);
|
||||
_crossCtl.moveToMenuItem(item);
|
||||
props.onMove();
|
||||
}
|
||||
|
||||
function handleSubItemClick(item) {
|
||||
_crossCtl.moveToMenuItem(item);
|
||||
props.onMove();
|
||||
}
|
||||
|
||||
function hideOther(id) {
|
||||
let targetEl = null;
|
||||
const items = elements.value.filter((elm) => {
|
||||
if (elm.getAttribute('data-id') == id.toString()) {
|
||||
targetEl = elm;
|
||||
}
|
||||
return elm.getAttribute('data-id') !== id.toString();
|
||||
// return true;
|
||||
});
|
||||
items.forEach((elm) => elm.click());
|
||||
|
||||
// targetEl.click();
|
||||
}
|
||||
|
||||
function doClose(close) {
|
||||
close();
|
||||
}
|
||||
</script>
|
||||
31
inspond-nuxt-safekiso/base/components/BaseNoticeItem1.vue
Normal file
31
inspond-nuxt-safekiso/base/components/BaseNoticeItem1.vue
Normal file
@@ -0,0 +1,31 @@
|
||||
<template>
|
||||
<div
|
||||
class="min-w-0 p-4 bg-white rounded-lg shadow-xs dark:bg-gray-800 border-2 m-5"
|
||||
>
|
||||
<div class="ml-4">
|
||||
<dt class="mt-3 text-lg leading-6 font-medium text-gray-900">
|
||||
{{ item.title }}
|
||||
</dt>
|
||||
<dd
|
||||
class="mt-9 text-base text-gray-500"
|
||||
v-html="
|
||||
_utils
|
||||
.escapeHtml(item.detail)
|
||||
.replace(/(?:\r\n|\r|\n)/g, '<br />')
|
||||
"
|
||||
></dd>
|
||||
</div>
|
||||
|
||||
<p class="text-right text-xs">
|
||||
{{ $customFormat(item.created) }}
|
||||
</p>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
const props = defineProps({
|
||||
item: { type: Object, required: true },
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="css" scoped></style>
|
||||
112
inspond-nuxt-safekiso/base/components/BasePageFooter1.vue
Normal file
112
inspond-nuxt-safekiso/base/components/BasePageFooter1.vue
Normal file
@@ -0,0 +1,112 @@
|
||||
<template>
|
||||
<footer
|
||||
class="footer px-10 py-4 bg-base-200 text-base-content border-base-300"
|
||||
>
|
||||
<div class="items-center grid-flow-col">
|
||||
<svg
|
||||
width="24"
|
||||
height="24"
|
||||
viewBox="0 0 24 24"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
fill-rule="evenodd"
|
||||
clip-rule="evenodd"
|
||||
class="fill-current"
|
||||
>
|
||||
<path
|
||||
d="M22.672 15.226l-2.432.811.841 2.515c.33 1.019-.209 2.127-1.23 2.456-1.15.325-2.148-.321-2.463-1.226l-.84-2.518-5.013 1.677.84 2.517c.391 1.203-.434 2.542-1.831 2.542-.88 0-1.601-.564-1.86-1.314l-.842-2.516-2.431.809c-1.135.328-2.145-.317-2.463-1.229-.329-1.018.211-2.127 1.231-2.456l2.432-.809-1.621-4.823-2.432.808c-1.355.384-2.558-.59-2.558-1.839 0-.817.509-1.582 1.327-1.846l2.433-.809-.842-2.515c-.33-1.02.211-2.129 1.232-2.458 1.02-.329 2.13.209 2.461 1.229l.842 2.515 5.011-1.677-.839-2.517c-.403-1.238.484-2.553 1.843-2.553.819 0 1.585.509 1.85 1.326l.841 2.517 2.431-.81c1.02-.33 2.131.211 2.461 1.229.332 1.018-.21 2.126-1.23 2.456l-2.433.809 1.622 4.823 2.433-.809c1.242-.401 2.557.484 2.557 1.838 0 .819-.51 1.583-1.328 1.847m-8.992-6.428l-5.01 1.675 1.619 4.828 5.011-1.674-1.62-4.829z"
|
||||
></path>
|
||||
</svg>
|
||||
<p>{{ copyrightName }} <br />{{ siteSlogan }}</p>
|
||||
</div>
|
||||
<div class="md:place-self-center md:justify-self-end">
|
||||
<div class="grid grid-flow-col gap-4">
|
||||
<a v-for="item in snsLinks" :key="item.tag" :href="item.url">
|
||||
<component
|
||||
:is="socialLogs[item.tag].icon"
|
||||
v-if="socialLogs[item.tag] != undefined"
|
||||
class="h-6 w-6"
|
||||
aria-hidden="true"
|
||||
/>
|
||||
<span v-else class="">{{ item.tag }}</span>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</footer>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
const copyrightName = ref(_siteConfig.copyrightName);
|
||||
const siteSlogan = ref(_siteConfig.siteSlogan);
|
||||
const snsLinks = ref(_siteConfig.snsLinks);
|
||||
|
||||
const socialLogs = {
|
||||
facebook: {
|
||||
name: 'Facebook',
|
||||
href: '#',
|
||||
icon: defineComponent({
|
||||
render: () =>
|
||||
h('svg', { fill: 'currentColor', viewBox: '0 0 24 24' }, [
|
||||
h('path', {
|
||||
'fill-rule': 'evenodd',
|
||||
d: 'M22 12c0-5.523-4.477-10-10-10S2 6.477 2 12c0 4.991 3.657 9.128 8.438 9.878v-6.987h-2.54V12h2.54V9.797c0-2.506 1.492-3.89 3.777-3.89 1.094 0 2.238.195 2.238.195v2.46h-1.26c-1.243 0-1.63.771-1.63 1.562V12h2.773l-.443 2.89h-2.33v6.988C18.343 21.128 22 16.991 22 12z',
|
||||
'clip-rule': 'evenodd',
|
||||
}),
|
||||
]),
|
||||
}),
|
||||
},
|
||||
instagram: {
|
||||
name: 'Instagram',
|
||||
href: '#',
|
||||
icon: defineComponent({
|
||||
render: () =>
|
||||
h('svg', { fill: 'currentColor', viewBox: '0 0 24 24' }, [
|
||||
h('path', {
|
||||
'fill-rule': 'evenodd',
|
||||
d: 'M12.315 2c2.43 0 2.784.013 3.808.06 1.064.049 1.791.218 2.427.465a4.902 4.902 0 011.772 1.153 4.902 4.902 0 011.153 1.772c.247.636.416 1.363.465 2.427.048 1.067.06 1.407.06 4.123v.08c0 2.643-.012 2.987-.06 4.043-.049 1.064-.218 1.791-.465 2.427a4.902 4.902 0 01-1.153 1.772 4.902 4.902 0 01-1.772 1.153c-.636.247-1.363.416-2.427.465-1.067.048-1.407.06-4.123.06h-.08c-2.643 0-2.987-.012-4.043-.06-1.064-.049-1.791-.218-2.427-.465a4.902 4.902 0 01-1.772-1.153 4.902 4.902 0 01-1.153-1.772c-.247-.636-.416-1.363-.465-2.427-.047-1.024-.06-1.379-.06-3.808v-.63c0-2.43.013-2.784.06-3.808.049-1.064.218-1.791.465-2.427a4.902 4.902 0 011.153-1.772A4.902 4.902 0 015.45 2.525c.636-.247 1.363-.416 2.427-.465C8.901 2.013 9.256 2 11.685 2h.63zm-.081 1.802h-.468c-2.456 0-2.784.011-3.807.058-.975.045-1.504.207-1.857.344-.467.182-.8.398-1.15.748-.35.35-.566.683-.748 1.15-.137.353-.3.882-.344 1.857-.047 1.023-.058 1.351-.058 3.807v.468c0 2.456.011 2.784.058 3.807.045.975.207 1.504.344 1.857.182.466.399.8.748 1.15.35.35.683.566 1.15.748.353.137.882.3 1.857.344 1.054.048 1.37.058 4.041.058h.08c2.597 0 2.917-.01 3.96-.058.976-.045 1.505-.207 1.858-.344.466-.182.8-.398 1.15-.748.35-.35.566-.683.748-1.15.137-.353.3-.882.344-1.857.048-1.055.058-1.37.058-4.041v-.08c0-2.597-.01-2.917-.058-3.96-.045-.976-.207-1.505-.344-1.858a3.097 3.097 0 00-.748-1.15 3.098 3.098 0 00-1.15-.748c-.353-.137-.882-.3-1.857-.344-1.023-.047-1.351-.058-3.807-.058zM12 6.865a5.135 5.135 0 110 10.27 5.135 5.135 0 010-10.27zm0 1.802a3.333 3.333 0 100 6.666 3.333 3.333 0 000-6.666zm5.338-3.205a1.2 1.2 0 110 2.4 1.2 1.2 0 010-2.4z',
|
||||
'clip-rule': 'evenodd',
|
||||
}),
|
||||
]),
|
||||
}),
|
||||
},
|
||||
twitter: {
|
||||
name: 'Twitter',
|
||||
href: '#',
|
||||
icon: defineComponent({
|
||||
render: () =>
|
||||
h('svg', { fill: 'currentColor', viewBox: '0 0 24 24' }, [
|
||||
h('path', {
|
||||
d: 'M8.29 20.251c7.547 0 11.675-6.253 11.675-11.675 0-.178 0-.355-.012-.53A8.348 8.348 0 0022 5.92a8.19 8.19 0 01-2.357.646 4.118 4.118 0 001.804-2.27 8.224 8.224 0 01-2.605.996 4.107 4.107 0 00-6.993 3.743 11.65 11.65 0 01-8.457-4.287 4.106 4.106 0 001.27 5.477A4.072 4.072 0 012.8 9.713v.052a4.105 4.105 0 003.292 4.022 4.095 4.095 0 01-1.853.07 4.108 4.108 0 003.834 2.85A8.233 8.233 0 012 18.407a11.616 11.616 0 006.29 1.84',
|
||||
}),
|
||||
]),
|
||||
}),
|
||||
},
|
||||
gitHub: {
|
||||
name: 'GitHub',
|
||||
href: '#',
|
||||
icon: defineComponent({
|
||||
render: () =>
|
||||
h('svg', { fill: 'currentColor', viewBox: '0 0 24 24' }, [
|
||||
h('path', {
|
||||
'fill-rule': 'evenodd',
|
||||
d: 'M12 2C6.477 2 2 6.484 2 12.017c0 4.425 2.865 8.18 6.839 9.504.5.092.682-.217.682-.483 0-.237-.008-.868-.013-1.703-2.782.605-3.369-1.343-3.369-1.343-.454-1.158-1.11-1.466-1.11-1.466-.908-.62.069-.608.069-.608 1.003.07 1.531 1.032 1.531 1.032.892 1.53 2.341 1.088 2.91.832.092-.647.35-1.088.636-1.338-2.22-.253-4.555-1.113-4.555-4.951 0-1.093.39-1.988 1.029-2.688-.103-.253-.446-1.272.098-2.65 0 0 .84-.27 2.75 1.026A9.564 9.564 0 0112 6.844c.85.004 1.705.115 2.504.337 1.909-1.296 2.747-1.027 2.747-1.027.546 1.379.202 2.398.1 2.651.64.7 1.028 1.595 1.028 2.688 0 3.848-2.339 4.695-4.566 4.943.359.309.678.92.678 1.855 0 1.338-.012 2.419-.012 2.747 0 .268.18.58.688.482A10.019 10.019 0 0022 12.017C22 6.484 17.522 2 12 2z',
|
||||
'clip-rule': 'evenodd',
|
||||
}),
|
||||
]),
|
||||
}),
|
||||
},
|
||||
dribbble: {
|
||||
name: 'Dribbble',
|
||||
href: '#',
|
||||
icon: defineComponent({
|
||||
render: () =>
|
||||
h('svg', { fill: 'currentColor', viewBox: '0 0 24 24' }, [
|
||||
h('path', {
|
||||
'fill-rule': 'evenodd',
|
||||
d: 'M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10c5.51 0 10-4.48 10-10S17.51 2 12 2zm6.605 4.61a8.502 8.502 0 011.93 5.314c-.281-.054-3.101-.629-5.943-.271-.065-.141-.12-.293-.184-.445a25.416 25.416 0 00-.564-1.236c3.145-1.28 4.577-3.124 4.761-3.362zM12 3.475c2.17 0 4.154.813 5.662 2.148-.152.216-1.443 1.941-4.48 3.08-1.399-2.57-2.95-4.675-3.189-5A8.687 8.687 0 0112 3.475zm-3.633.803a53.896 53.896 0 013.167 4.935c-3.992 1.063-7.517 1.04-7.896 1.04a8.581 8.581 0 014.729-5.975zM3.453 12.01v-.26c.37.01 4.512.065 8.775-1.215.25.477.477.965.694 1.453-.109.033-.228.065-.336.098-4.404 1.42-6.747 5.303-6.942 5.629a8.522 8.522 0 01-2.19-5.705zM12 20.547a8.482 8.482 0 01-5.239-1.8c.152-.315 1.888-3.656 6.703-5.337.022-.01.033-.01.054-.022a35.318 35.318 0 011.823 6.475 8.4 8.4 0 01-3.341.684zm4.761-1.465c-.086-.52-.542-3.015-1.659-6.084 2.679-.423 5.022.271 5.314.369a8.468 8.468 0 01-3.655 5.715z',
|
||||
'clip-rule': 'evenodd',
|
||||
}),
|
||||
]),
|
||||
}),
|
||||
},
|
||||
};
|
||||
</script>
|
||||
289
inspond-nuxt-safekiso/base/components/BasePagination1.vue
Normal file
289
inspond-nuxt-safekiso/base/components/BasePagination1.vue
Normal file
@@ -0,0 +1,289 @@
|
||||
<template>
|
||||
<div
|
||||
class="pt-5 px-4 py-3 flex items-center justify-between border-t border-gray-200 sm:px-6"
|
||||
>
|
||||
<div class="flex-1 flex justify-between sm:hidden">
|
||||
<a
|
||||
href="#"
|
||||
class="relative inline-flex items-center px-4 py-2 border border-gray-300 text-sm font-medium rounded-md text-gray-700 bg-white hover:text-gray-500"
|
||||
>
|
||||
Previous
|
||||
</a>
|
||||
<a
|
||||
href="#"
|
||||
class="ml-3 relative inline-flex items-center px-4 py-2 border border-gray-300 text-sm font-medium rounded-md text-gray-700 bg-white hover:text-gray-500"
|
||||
>
|
||||
Next
|
||||
</a>
|
||||
</div>
|
||||
<div
|
||||
class="hidden sm:flex-1 sm:flex sm:items-center sm:justify-between"
|
||||
>
|
||||
<div>
|
||||
<p class="text-sm text-gray-700">
|
||||
Showing
|
||||
<span class="font-medium">{{ showingFrom }}</span>
|
||||
to
|
||||
<span class="font-medium">{{ showingTo }}</span>
|
||||
of
|
||||
<span class="font-medium">{{ recordsTotal }}</span>
|
||||
results
|
||||
</p>
|
||||
</div>
|
||||
<div>
|
||||
<nav
|
||||
class="relative z-0 inline-flex shadow-sm -space-x-px"
|
||||
aria-label="Pagination"
|
||||
>
|
||||
<a
|
||||
href="javascript:void(0)"
|
||||
class="relative inline-flex items-center px-2 py-2 rounded-l-md border border-gray-300 bg-white text-sm font-medium text-gray-500 hover:bg-gray-50"
|
||||
@click="gotoPage('first', 0)"
|
||||
>
|
||||
<span class="sr-only">First</span>
|
||||
<!-- Heroicon name: chevron-double-left -->
|
||||
<svg
|
||||
class="w-6 h-6"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
viewBox="0 0 24 24"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
stroke-width="2"
|
||||
d="M11 19l-7-7 7-7m8 14l-7-7 7-7"
|
||||
></path>
|
||||
</svg>
|
||||
</a>
|
||||
|
||||
<a
|
||||
href="javascript:void(0)"
|
||||
class="relative inline-flex items-center px-2 py-2 rounded-l-md border border-gray-300 bg-white text-sm font-medium text-gray-500 hover:bg-gray-50"
|
||||
@click="gotoPage('prev', 0)"
|
||||
>
|
||||
<span class="sr-only">Previous</span>
|
||||
<!-- Heroicon name: chevron-left -->
|
||||
<svg
|
||||
class="h-5 w-5"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
viewBox="0 0 20 20"
|
||||
fill="currentColor"
|
||||
aria-hidden="true"
|
||||
>
|
||||
<path
|
||||
fill-rule="evenodd"
|
||||
d="M12.707 5.293a1 1 0 010 1.414L9.414 10l3.293 3.293a1 1 0 01-1.414 1.414l-4-4a1 1 0 010-1.414l4-4a1 1 0 011.414 0z"
|
||||
clip-rule="evenodd"
|
||||
/>
|
||||
</svg>
|
||||
</a>
|
||||
|
||||
<a
|
||||
v-for="(item, index) in currentSlots"
|
||||
:key="index"
|
||||
href="javascript:void(0)"
|
||||
:class="
|
||||
currentSlots[index] == currentPageNumber
|
||||
? 'bg-gray-200'
|
||||
: 'bg-white'
|
||||
"
|
||||
class="relative inline-flex items-center px-4 py-2 border border-gray-300 bg-white text-sm font-medium text-gray-700 hover:bg-gray-50"
|
||||
@click="gotoPage(currentSlots[index], index)"
|
||||
>
|
||||
{{ currentSlots[index] }}
|
||||
</a>
|
||||
|
||||
<a
|
||||
href="javascript:void(0)"
|
||||
class="relative inline-flex items-center px-2 py-2 rounded-r-md border border-gray-300 bg-white text-sm font-medium text-gray-500 hover:bg-gray-50"
|
||||
@click="gotoPage('next', 0)"
|
||||
>
|
||||
<span class="sr-only">Next</span>
|
||||
<!-- Heroicon name: chevron-right -->
|
||||
<svg
|
||||
class="h-5 w-5"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
viewBox="0 0 20 20"
|
||||
fill="currentColor"
|
||||
aria-hidden="true"
|
||||
>
|
||||
<path
|
||||
fill-rule="evenodd"
|
||||
d="M7.293 14.707a1 1 0 010-1.414L10.586 10 7.293 6.707a1 1 0 011.414-1.414l4 4a1 1 0 010 1.414l-4 4a1 1 0 01-1.414 0z"
|
||||
clip-rule="evenodd"
|
||||
/>
|
||||
</svg>
|
||||
</a>
|
||||
|
||||
<a
|
||||
href="javascript:void(0)"
|
||||
class="relative inline-flex items-center px-2 py-2 rounded-r-md border border-gray-300 bg-white text-sm font-medium text-gray-500 hover:bg-gray-50"
|
||||
@click="gotoPage('last', 0)"
|
||||
>
|
||||
<span class="sr-only">Last</span>
|
||||
<!-- Heroicon name: chevron-double-right -->
|
||||
<svg
|
||||
class="w-6 h-6"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
viewBox="0 0 24 24"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
stroke-width="2"
|
||||
d="M13 5l7 7-7 7M5 5l7 7-7 7"
|
||||
></path>
|
||||
</svg>
|
||||
</a>
|
||||
</nav>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import {
|
||||
ChevronLeftIcon,
|
||||
ChevronRightIcon,
|
||||
} from '@heroicons/vue/solid/index.js';
|
||||
|
||||
import { computed } from 'vue';
|
||||
|
||||
const props = defineProps({
|
||||
totalPageCount: { type: Number, default: 1 },
|
||||
currentPageNumber: { type: Number, default: 1 },
|
||||
pageSize: { type: Number, default: 10 },
|
||||
recordsTotal: { type: Number, default: 0 },
|
||||
pageMove: { type: Function, required: true },
|
||||
});
|
||||
|
||||
const pagenationSize = 5;
|
||||
|
||||
//console.log(props);
|
||||
|
||||
const showingFrom = computed(() => {
|
||||
const result = 1 + (props.currentPageNumber - 1) * props.pageSize;
|
||||
return result;
|
||||
});
|
||||
|
||||
//console.log(showingFrom.value);
|
||||
|
||||
const showingTo = computed(() => {
|
||||
let result = props.currentPageNumber * props.pageSize;
|
||||
if (result > props.recordsTotal) {
|
||||
result = props.recordsTotal;
|
||||
}
|
||||
return result;
|
||||
});
|
||||
|
||||
const currentSlots = computed(() => {
|
||||
const result: string[] = [];
|
||||
if (props.totalPageCount > pagenationSize) {
|
||||
if (props.currentPageNumber < Math.ceil(pagenationSize / 2)) {
|
||||
for (let i = 1; i < pagenationSize; i++) {
|
||||
// good for 3, 4, ....
|
||||
result.push(i.toString());
|
||||
}
|
||||
result.push('...');
|
||||
} else if (
|
||||
props.currentPageNumber >
|
||||
props.totalPageCount - pagenationSize / 2
|
||||
) {
|
||||
result.push('...');
|
||||
for (
|
||||
let i = props.totalPageCount - pagenationSize + 1;
|
||||
i <= props.totalPageCount;
|
||||
i++
|
||||
) {
|
||||
// good for 3, 4, ....
|
||||
result.push(i.toString());
|
||||
}
|
||||
} else {
|
||||
result.push('...');
|
||||
for (
|
||||
let i =
|
||||
props.currentPageNumber - Math.floor(pagenationSize / 2);
|
||||
i < props.currentPageNumber + Math.ceil(pagenationSize / 2);
|
||||
i++
|
||||
) {
|
||||
// good for 3, 4, ....
|
||||
result.push(i.toString());
|
||||
}
|
||||
result.push('...');
|
||||
}
|
||||
} else {
|
||||
for (let i = 1; i <= props.totalPageCount; i++) {
|
||||
result.push(i.toString());
|
||||
}
|
||||
}
|
||||
|
||||
//console.log('result = ', result);
|
||||
|
||||
return result;
|
||||
});
|
||||
|
||||
function gotoPage(target, opt) {
|
||||
//console.log('gotoPage, target=', target);
|
||||
let targetPageIndex = props.currentPageNumber;
|
||||
switch (target) {
|
||||
case 'first':
|
||||
targetPageIndex = 1;
|
||||
break;
|
||||
case 'prev':
|
||||
if (props.currentPageNumber > 1) {
|
||||
targetPageIndex = props.currentPageNumber - 1;
|
||||
} else {
|
||||
targetPageIndex = props.currentPageNumber;
|
||||
}
|
||||
break;
|
||||
case 'next':
|
||||
if (props.totalPageCount > props.currentPageNumber) {
|
||||
targetPageIndex = props.currentPageNumber + 1;
|
||||
} else {
|
||||
targetPageIndex = props.currentPageNumber;
|
||||
}
|
||||
|
||||
break;
|
||||
case 'last':
|
||||
targetPageIndex = props.totalPageCount;
|
||||
break;
|
||||
|
||||
case '...':
|
||||
if (opt == 0) {
|
||||
if (props.currentPageNumber - pagenationSize > 0) {
|
||||
targetPageIndex = props.currentPageNumber - pagenationSize;
|
||||
} else {
|
||||
targetPageIndex = 1;
|
||||
}
|
||||
} else {
|
||||
if (
|
||||
props.currentPageNumber + pagenationSize <
|
||||
props.totalPageCount
|
||||
) {
|
||||
targetPageIndex = props.currentPageNumber + pagenationSize;
|
||||
} else {
|
||||
targetPageIndex = props.totalPageCount;
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
const tmpPageIdx = parseInt(target);
|
||||
if (tmpPageIdx >= 1 && tmpPageIdx <= props.totalPageCount) {
|
||||
targetPageIndex = tmpPageIdx;
|
||||
} else {
|
||||
targetPageIndex = props.currentPageNumber;
|
||||
}
|
||||
}
|
||||
//console.log('final targetPageIdex = ', targetPageIndex);
|
||||
|
||||
// console.log('huk', this);
|
||||
// this.$parent.pageMove(targetPageIndex);
|
||||
// $emit('pageMove', targetPageIndex);
|
||||
props.pageMove(targetPageIndex);
|
||||
}
|
||||
</script>
|
||||
85
inspond-nuxt-safekiso/base/components/BaseTable1.vue
Normal file
85
inspond-nuxt-safekiso/base/components/BaseTable1.vue
Normal file
@@ -0,0 +1,85 @@
|
||||
<template>
|
||||
<div class="">
|
||||
<div class="card border overflow-x-auto">
|
||||
<table class="table w-full">
|
||||
<!-- head -->
|
||||
<thead>
|
||||
<tr>
|
||||
<th
|
||||
v-for="(heading, index) in headings"
|
||||
:key="index"
|
||||
:width="
|
||||
heading['widthRatio'] != ''
|
||||
? heading['widthRatio'] + '%'
|
||||
: '100%'
|
||||
"
|
||||
scope="col"
|
||||
>
|
||||
{{ heading['title'] }}
|
||||
</th>
|
||||
|
||||
<th
|
||||
v-for="(action, index) in actions"
|
||||
:key="index + headings.length"
|
||||
:width="actions.length * 1"
|
||||
scope="col"
|
||||
>
|
||||
<span class="sr-only">{{ action }}</span>
|
||||
</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr v-for="(item, itemIndex) in data" :key="itemIndex">
|
||||
<td v-for="(heading, index) in headings" :key="index">
|
||||
{{
|
||||
columnFilter
|
||||
? columnFilter(
|
||||
heading['key'],
|
||||
item[heading['key']]
|
||||
)
|
||||
: item[heading['key']]
|
||||
}}
|
||||
</td>
|
||||
|
||||
<td>
|
||||
<button
|
||||
v-for="(action, index) in actions"
|
||||
:key="index"
|
||||
class="btn btn-sm btn-primary"
|
||||
:class="index != 0 ? 'ml-1' : ''"
|
||||
@click="doAction(action, item[actionKey])"
|
||||
>
|
||||
{{ action
|
||||
}}<span class="sr-only"
|
||||
>, {{ item['serial'] }}</span
|
||||
>
|
||||
</button>
|
||||
</td>
|
||||
</tr>
|
||||
<tr v-if="data.length == 0">
|
||||
<td>{{ 'No Data to display...' }}</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
const props = defineProps({
|
||||
headings: { type: Array<object>, required: true },
|
||||
actions: { type: Array<string>, default: [] },
|
||||
data: { type: Array<object>, required: true },
|
||||
actionKey: { type: String, default: 'serial' },
|
||||
columnFilter: {
|
||||
type: Function,
|
||||
default: (key: string, val: string) => val,
|
||||
},
|
||||
doAction: {
|
||||
type: Function,
|
||||
default: (key: string) => {
|
||||
return key;
|
||||
},
|
||||
},
|
||||
});
|
||||
</script>
|
||||
110
inspond-nuxt-safekiso/base/components/BaseTable2.vue
Normal file
110
inspond-nuxt-safekiso/base/components/BaseTable2.vue
Normal file
@@ -0,0 +1,110 @@
|
||||
<template>
|
||||
<div class="flex flex-col">
|
||||
<div class="-my-2 -mx-4 overflow-x-auto sm:-mx-6 lg:-mx-8">
|
||||
<div
|
||||
class="inline-block min-w-full py-2 align-middle md:px-6 lg:px-8"
|
||||
>
|
||||
<table class="min-w-full divide-y divide-gray-300">
|
||||
<thead>
|
||||
<tr>
|
||||
<th
|
||||
v-for="(heading, index) in headings"
|
||||
:key="index"
|
||||
:width="
|
||||
heading['widthRatio'] != ''
|
||||
? heading['widthRatio'] + '%'
|
||||
: '100%'
|
||||
"
|
||||
:class="
|
||||
index == 0
|
||||
? 'py-3.5 pl-4 pr-3 text-left text-sm font-semibold text-gray-900 sm:pl-6 md:pl-0'
|
||||
: 'py-3.5 px-3 text-left text-sm font-semibold text-gray-900'
|
||||
"
|
||||
scope="col"
|
||||
>
|
||||
{{ heading['title'] }}
|
||||
</th>
|
||||
|
||||
<th
|
||||
v-for="(action, index) in actions"
|
||||
:key="index + headings.length"
|
||||
:width="actions.length * 1"
|
||||
class="relative py-3.5 pl-3 pr-4 sm:pr-6 md:pr-0"
|
||||
scope="col"
|
||||
>
|
||||
<span class="sr-only">{{ action }}</span>
|
||||
</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody class="divide-y divide-gray-200">
|
||||
<tr v-for="(item, itemIndex) in data" :key="itemIndex">
|
||||
<td
|
||||
v-for="(heading, index) in headings"
|
||||
:key="index"
|
||||
:class="
|
||||
index == 0
|
||||
? 'whitespace-nowrap py-4 pl-4 pr-3 text-sm font-medium text-gray-900 sm:pl-6 md:pl-0'
|
||||
: 'whitespace-nowrap py-4 px-3 text-sm text-gray-500'
|
||||
"
|
||||
>
|
||||
{{
|
||||
columnFilter
|
||||
? columnFilter(
|
||||
heading['key'],
|
||||
item[heading['key']]
|
||||
)
|
||||
: item[heading['key']]
|
||||
}}
|
||||
</td>
|
||||
|
||||
<td
|
||||
class="relative whitespace-nowrap py-4 pl-3 pr-4 text-right text-sm font-medium sm:pr-6 md:pr-0"
|
||||
>
|
||||
<a
|
||||
v-for="(action, index) in actions"
|
||||
:key="index"
|
||||
href="javascript:void(0)"
|
||||
:class="index != 0 ? 'ml-3' : ''"
|
||||
class="text-indigo-600 hover:text-indigo-900"
|
||||
@click="doAction(action, item[actionKey])"
|
||||
>{{ action
|
||||
}}<span class="sr-only"
|
||||
>, {{ item['serial'] }}</span
|
||||
></a
|
||||
>
|
||||
</td>
|
||||
</tr>
|
||||
<tr v-if="data.length == 0">
|
||||
<td
|
||||
:colspan="headings.length"
|
||||
class="whitespace-nowrap py-4 px-3 text-sm text-gray-500"
|
||||
>
|
||||
{{ noDataMessage }}
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
const props = defineProps({
|
||||
headings: { type: Array<object>, required: true },
|
||||
actions: { type: Array<string>, default: [] },
|
||||
data: { type: Array<object>, required: true },
|
||||
actionKey: { type: String, default: 'serial' },
|
||||
columnFilter: {
|
||||
type: Function,
|
||||
default: (key: string, val: string) => val,
|
||||
},
|
||||
doAction: {
|
||||
type: Function,
|
||||
default: (key: string) => {
|
||||
return key;
|
||||
},
|
||||
},
|
||||
noDataMessage: { type: String, default: '조회된 데이터가 없습니다.' },
|
||||
});
|
||||
</script>
|
||||
@@ -0,0 +1,25 @@
|
||||
<template>
|
||||
<div>
|
||||
<BaseAvater1
|
||||
:image-size="photoSize"
|
||||
:image-url="photoUrl"
|
||||
:image-alt="photoAlt"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
const props = defineProps({
|
||||
imageSize: { type: Number, default: 12 },
|
||||
});
|
||||
|
||||
const photoSize = ref(props.imageSize);
|
||||
const photoUrl = _crossCtl.profileUrlRef;
|
||||
const photoAlt = ref(
|
||||
_crossCtl.isAuthenticated
|
||||
? _crossCtl.userInfo['userInfo']['userName'] + '의 프로필 사진'
|
||||
: ''
|
||||
);
|
||||
</script>
|
||||
|
||||
<style lang="css" scoped></style>
|
||||
@@ -0,0 +1,22 @@
|
||||
<template>
|
||||
<div v-show="false"></div>
|
||||
</template>
|
||||
<script>
|
||||
export default {
|
||||
props: {
|
||||
show: {
|
||||
type: Boolean,
|
||||
},
|
||||
},
|
||||
emits: ['show', 'hide'],
|
||||
watch: {
|
||||
show(show) {
|
||||
if (show) {
|
||||
this.$emit('show');
|
||||
} else {
|
||||
this.$emit('hide');
|
||||
}
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
106
inspond-nuxt-safekiso/base/components/Footer1.vue
Normal file
106
inspond-nuxt-safekiso/base/components/Footer1.vue
Normal file
@@ -0,0 +1,106 @@
|
||||
<!-- This example requires Tailwind CSS v2.0+ -->
|
||||
<template>
|
||||
<footer class="bg-white">
|
||||
<div
|
||||
class="mx-auto max-w-7xl py-12 px-4 sm:px-6 md:flex md:items-center md:justify-between lg:px-8"
|
||||
>
|
||||
<div class="flex justify-center space-x-6 md:order-2">
|
||||
<a
|
||||
v-for="item in snsLinks"
|
||||
:key="item.tag"
|
||||
:href="item.url"
|
||||
class="text-gray-400 hover:text-gray-500"
|
||||
>
|
||||
<span class="sr-only">{{ item.tag }}</span>
|
||||
<component
|
||||
:is="socialLogos[item.tag].icon"
|
||||
v-if="socialLogos[item.tag] != undefined"
|
||||
class="h-6 w-6"
|
||||
aria-hidden="true"
|
||||
/>
|
||||
</a>
|
||||
</div>
|
||||
<div class="mt-8 md:order-1 md:mt-0">
|
||||
<p class="text-center text-base text-gray-400">
|
||||
{{ copyrightName }}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</footer>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
const copyrightName = ref(_siteConfig.copyrightName);
|
||||
const snsLinks = ref(_siteConfig.snsLinks);
|
||||
|
||||
const socialLogos = {
|
||||
facebook: {
|
||||
name: 'Facebook',
|
||||
href: '#',
|
||||
icon: defineComponent({
|
||||
render: () =>
|
||||
h('svg', { fill: 'currentColor', viewBox: '0 0 24 24' }, [
|
||||
h('path', {
|
||||
'fill-rule': 'evenodd',
|
||||
d: 'M22 12c0-5.523-4.477-10-10-10S2 6.477 2 12c0 4.991 3.657 9.128 8.438 9.878v-6.987h-2.54V12h2.54V9.797c0-2.506 1.492-3.89 3.777-3.89 1.094 0 2.238.195 2.238.195v2.46h-1.26c-1.243 0-1.63.771-1.63 1.562V12h2.773l-.443 2.89h-2.33v6.988C18.343 21.128 22 16.991 22 12z',
|
||||
'clip-rule': 'evenodd',
|
||||
}),
|
||||
]),
|
||||
}),
|
||||
},
|
||||
instagram: {
|
||||
name: 'Instagram',
|
||||
href: '#',
|
||||
icon: defineComponent({
|
||||
render: () =>
|
||||
h('svg', { fill: 'currentColor', viewBox: '0 0 24 24' }, [
|
||||
h('path', {
|
||||
'fill-rule': 'evenodd',
|
||||
d: 'M12.315 2c2.43 0 2.784.013 3.808.06 1.064.049 1.791.218 2.427.465a4.902 4.902 0 011.772 1.153 4.902 4.902 0 011.153 1.772c.247.636.416 1.363.465 2.427.048 1.067.06 1.407.06 4.123v.08c0 2.643-.012 2.987-.06 4.043-.049 1.064-.218 1.791-.465 2.427a4.902 4.902 0 01-1.153 1.772 4.902 4.902 0 01-1.772 1.153c-.636.247-1.363.416-2.427.465-1.067.048-1.407.06-4.123.06h-.08c-2.643 0-2.987-.012-4.043-.06-1.064-.049-1.791-.218-2.427-.465a4.902 4.902 0 01-1.772-1.153 4.902 4.902 0 01-1.153-1.772c-.247-.636-.416-1.363-.465-2.427-.047-1.024-.06-1.379-.06-3.808v-.63c0-2.43.013-2.784.06-3.808.049-1.064.218-1.791.465-2.427a4.902 4.902 0 011.153-1.772A4.902 4.902 0 015.45 2.525c.636-.247 1.363-.416 2.427-.465C8.901 2.013 9.256 2 11.685 2h.63zm-.081 1.802h-.468c-2.456 0-2.784.011-3.807.058-.975.045-1.504.207-1.857.344-.467.182-.8.398-1.15.748-.35.35-.566.683-.748 1.15-.137.353-.3.882-.344 1.857-.047 1.023-.058 1.351-.058 3.807v.468c0 2.456.011 2.784.058 3.807.045.975.207 1.504.344 1.857.182.466.399.8.748 1.15.35.35.683.566 1.15.748.353.137.882.3 1.857.344 1.054.048 1.37.058 4.041.058h.08c2.597 0 2.917-.01 3.96-.058.976-.045 1.505-.207 1.858-.344.466-.182.8-.398 1.15-.748.35-.35.566-.683.748-1.15.137-.353.3-.882.344-1.857.048-1.055.058-1.37.058-4.041v-.08c0-2.597-.01-2.917-.058-3.96-.045-.976-.207-1.505-.344-1.858a3.097 3.097 0 00-.748-1.15 3.098 3.098 0 00-1.15-.748c-.353-.137-.882-.3-1.857-.344-1.023-.047-1.351-.058-3.807-.058zM12 6.865a5.135 5.135 0 110 10.27 5.135 5.135 0 010-10.27zm0 1.802a3.333 3.333 0 100 6.666 3.333 3.333 0 000-6.666zm5.338-3.205a1.2 1.2 0 110 2.4 1.2 1.2 0 010-2.4z',
|
||||
'clip-rule': 'evenodd',
|
||||
}),
|
||||
]),
|
||||
}),
|
||||
},
|
||||
twitter: {
|
||||
name: 'Twitter',
|
||||
href: '#',
|
||||
icon: defineComponent({
|
||||
render: () =>
|
||||
h('svg', { fill: 'currentColor', viewBox: '0 0 24 24' }, [
|
||||
h('path', {
|
||||
d: 'M8.29 20.251c7.547 0 11.675-6.253 11.675-11.675 0-.178 0-.355-.012-.53A8.348 8.348 0 0022 5.92a8.19 8.19 0 01-2.357.646 4.118 4.118 0 001.804-2.27 8.224 8.224 0 01-2.605.996 4.107 4.107 0 00-6.993 3.743 11.65 11.65 0 01-8.457-4.287 4.106 4.106 0 001.27 5.477A4.072 4.072 0 012.8 9.713v.052a4.105 4.105 0 003.292 4.022 4.095 4.095 0 01-1.853.07 4.108 4.108 0 003.834 2.85A8.233 8.233 0 012 18.407a11.616 11.616 0 006.29 1.84',
|
||||
}),
|
||||
]),
|
||||
}),
|
||||
},
|
||||
github: {
|
||||
name: 'GitHub',
|
||||
href: '#',
|
||||
icon: defineComponent({
|
||||
render: () =>
|
||||
h('svg', { fill: 'currentColor', viewBox: '0 0 24 24' }, [
|
||||
h('path', {
|
||||
'fill-rule': 'evenodd',
|
||||
d: 'M12 2C6.477 2 2 6.484 2 12.017c0 4.425 2.865 8.18 6.839 9.504.5.092.682-.217.682-.483 0-.237-.008-.868-.013-1.703-2.782.605-3.369-1.343-3.369-1.343-.454-1.158-1.11-1.466-1.11-1.466-.908-.62.069-.608.069-.608 1.003.07 1.531 1.032 1.531 1.032.892 1.53 2.341 1.088 2.91.832.092-.647.35-1.088.636-1.338-2.22-.253-4.555-1.113-4.555-4.951 0-1.093.39-1.988 1.029-2.688-.103-.253-.446-1.272.098-2.65 0 0 .84-.27 2.75 1.026A9.564 9.564 0 0112 6.844c.85.004 1.705.115 2.504.337 1.909-1.296 2.747-1.027 2.747-1.027.546 1.379.202 2.398.1 2.651.64.7 1.028 1.595 1.028 2.688 0 3.848-2.339 4.695-4.566 4.943.359.309.678.92.678 1.855 0 1.338-.012 2.419-.012 2.747 0 .268.18.58.688.482A10.019 10.019 0 0022 12.017C22 6.484 17.522 2 12 2z',
|
||||
'clip-rule': 'evenodd',
|
||||
}),
|
||||
]),
|
||||
}),
|
||||
},
|
||||
dribbble: {
|
||||
name: 'Dribbble',
|
||||
href: '#',
|
||||
icon: defineComponent({
|
||||
render: () =>
|
||||
h('svg', { fill: 'currentColor', viewBox: '0 0 24 24' }, [
|
||||
h('path', {
|
||||
'fill-rule': 'evenodd',
|
||||
d: 'M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10c5.51 0 10-4.48 10-10S17.51 2 12 2zm6.605 4.61a8.502 8.502 0 011.93 5.314c-.281-.054-3.101-.629-5.943-.271-.065-.141-.12-.293-.184-.445a25.416 25.416 0 00-.564-1.236c3.145-1.28 4.577-3.124 4.761-3.362zM12 3.475c2.17 0 4.154.813 5.662 2.148-.152.216-1.443 1.941-4.48 3.08-1.399-2.57-2.95-4.675-3.189-5A8.687 8.687 0 0112 3.475zm-3.633.803a53.896 53.896 0 013.167 4.935c-3.992 1.063-7.517 1.04-7.896 1.04a8.581 8.581 0 014.729-5.975zM3.453 12.01v-.26c.37.01 4.512.065 8.775-1.215.25.477.477.965.694 1.453-.109.033-.228.065-.336.098-4.404 1.42-6.747 5.303-6.942 5.629a8.522 8.522 0 01-2.19-5.705zM12 20.547a8.482 8.482 0 01-5.239-1.8c.152-.315 1.888-3.656 6.703-5.337.022-.01.033-.01.054-.022a35.318 35.318 0 011.823 6.475 8.4 8.4 0 01-3.341.684zm4.761-1.465c-.086-.52-.542-3.015-1.659-6.084 2.679-.423 5.022.271 5.314.369a8.468 8.468 0 01-3.655 5.715z',
|
||||
'clip-rule': 'evenodd',
|
||||
}),
|
||||
]),
|
||||
}),
|
||||
},
|
||||
};
|
||||
</script>
|
||||
476
inspond-nuxt-safekiso/base/components/TopNavBar1.vue
Normal file
476
inspond-nuxt-safekiso/base/components/TopNavBar1.vue
Normal file
@@ -0,0 +1,476 @@
|
||||
<!-- This example requires Tailwind CSS v2.0+ -->
|
||||
<template>
|
||||
<Disclosure v-slot="{ open }" as="nav" class="bg-white shadow">
|
||||
<div class="mx-auto max-w-7xl px-4 sm:px-6 lg:px-8">
|
||||
<div class="flex h-16 justify-between">
|
||||
<div class="flex">
|
||||
<div class="flex flex-shrink-0 items-center">
|
||||
<a
|
||||
href="javascript:void(0)"
|
||||
@click="handleItemClick($event, null)"
|
||||
>
|
||||
<img
|
||||
class="block h-8 w-auto lg:hidden"
|
||||
:src="siteLogoUrl"
|
||||
alt="site logo image"
|
||||
/>
|
||||
<img
|
||||
class="hidden h-8 w-auto lg:block"
|
||||
:src="siteLogoUrl"
|
||||
alt="site logo image"
|
||||
/>
|
||||
</a>
|
||||
</div>
|
||||
<div class="hidden sm:ml-6 sm:flex sm:space-x-8">
|
||||
<!-- Current: "border-indigo-500 text-gray-900", Default: "border-transparent text-gray-500 hover:border-gray-300 hover:text-gray-700" -->
|
||||
<template
|
||||
v-for="item in currentMenu['main']"
|
||||
:key="item.idx"
|
||||
>
|
||||
<a
|
||||
v-if="item.subs == undefined"
|
||||
href="javascript:void(0)"
|
||||
:class="
|
||||
isCurrentMenu(item)
|
||||
? 'inline-flex items-center border-b-2 border-indigo-500 px-1 pt-1 text-sm font-medium text-gray-900'
|
||||
: 'inline-flex items-center border-b-2 border-transparent px-1 pt-1 text-sm font-medium text-gray-500 hover:border-gray-300 hover:text-gray-700'
|
||||
"
|
||||
@click="handleItemClick($event, item)"
|
||||
>{{ item['title'] }}</a
|
||||
>
|
||||
<div
|
||||
v-else
|
||||
:class="
|
||||
isCurrentMenu(item)
|
||||
? 'inline-flex items-center border-b-2 border-indigo-500 px-1 pt-1 text-sm font-medium text-gray-900'
|
||||
: 'inline-flex items-center border-b-2 border-transparent px-1 pt-1 text-sm font-medium text-gray-500 hover:border-gray-300 hover:text-gray-700'
|
||||
"
|
||||
>
|
||||
<!-- dropdown -->
|
||||
<Menu as="div" class="relative ml-3">
|
||||
<div>
|
||||
<MenuButton class="flex">
|
||||
{{ item['title'] }}
|
||||
<svg
|
||||
x-state:on="Item active"
|
||||
x-state:off="Item inactive"
|
||||
class="h-5 w-5 group-hover:text-gray-500 text-gray-400"
|
||||
x-bind:class="{ 'text-gray-600': flyoutMenuOpen, 'text-gray-400': !flyoutMenuOpen }"
|
||||
x-description="Heroicon name: chevron-down"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
viewBox="0 0 20 20"
|
||||
fill="currentColor"
|
||||
aria-hidden="true"
|
||||
>
|
||||
<path
|
||||
fill-rule="evenodd"
|
||||
d="M5.293 7.293a1 1 0 011.414 0L10 10.586l3.293-3.293a1 1 0 111.414 1.414l-4 4a1 1 0 01-1.414 0l-4-4a1 1 0 010-1.414z"
|
||||
clip-rule="evenodd"
|
||||
></path>
|
||||
</svg>
|
||||
</MenuButton>
|
||||
</div>
|
||||
<transition
|
||||
enter-active-class="transition ease-out duration-200"
|
||||
enter-from-class="transform opacity-0 scale-95"
|
||||
enter-to-class="transform opacity-100 scale-100"
|
||||
leave-active-class="transition ease-in duration-75"
|
||||
leave-from-class="transform opacity-100 scale-100"
|
||||
leave-to-class="transform opacity-0 scale-95"
|
||||
>
|
||||
<MenuItems
|
||||
class="absolute right-0 z-10 mt-2 w-48 origin-top-right rounded-md bg-white py-1 shadow-lg ring-1 ring-black ring-opacity-5 focus:outline-none"
|
||||
>
|
||||
<MenuItem
|
||||
v-for="subItem in item['subs']"
|
||||
v-slot="{ active }"
|
||||
:key="subItem.idx"
|
||||
>
|
||||
<a
|
||||
href="javascript:void(0)"
|
||||
:class="[
|
||||
active
|
||||
? 'bg-gray-100'
|
||||
: '',
|
||||
'block px-4 py-2 text-sm text-gray-700',
|
||||
]"
|
||||
@click="
|
||||
handleItemClick(
|
||||
$event,
|
||||
subItem
|
||||
)
|
||||
"
|
||||
>{{ subItem.title }}</a
|
||||
>
|
||||
</MenuItem>
|
||||
</MenuItems>
|
||||
</transition>
|
||||
</Menu>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<template
|
||||
v-for="item in currentMenu['sub']"
|
||||
:key="item.idx"
|
||||
>
|
||||
<a
|
||||
v-if="item.subs == undefined"
|
||||
href="javascript:void(0)"
|
||||
:class="
|
||||
isCurrentMenu(item)
|
||||
? 'inline-flex items-center border-b-2 border-indigo-500 px-1 pt-1 text-sm font-medium text-gray-900'
|
||||
: 'inline-flex items-center border-b-2 border-transparent px-1 pt-1 text-sm font-medium text-gray-500 hover:border-gray-300 hover:text-gray-700'
|
||||
"
|
||||
@click="handleItemClick($event, item)"
|
||||
>{{ item['title'] }}</a
|
||||
>
|
||||
<div
|
||||
v-else
|
||||
:class="
|
||||
isCurrentMenu(item)
|
||||
? 'inline-flex items-center border-b-2 border-indigo-500 px-1 pt-1 text-sm font-medium text-gray-900'
|
||||
: 'inline-flex items-center border-b-2 border-transparent px-1 pt-1 text-sm font-medium text-gray-500 hover:border-gray-300 hover:text-gray-700'
|
||||
"
|
||||
>
|
||||
<!-- dropdown -->
|
||||
<Menu as="div" class="relative ml-3">
|
||||
<div>
|
||||
<MenuButton class="flex">
|
||||
{{ item['title'] }}
|
||||
<svg
|
||||
x-state:on="Item active"
|
||||
x-state:off="Item inactive"
|
||||
class="h-5 w-5 group-hover:text-gray-500 text-gray-400"
|
||||
x-bind:class="{ 'text-gray-600': flyoutMenuOpen, 'text-gray-400': !flyoutMenuOpen }"
|
||||
x-description="Heroicon name: chevron-down"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
viewBox="0 0 20 20"
|
||||
fill="currentColor"
|
||||
aria-hidden="true"
|
||||
>
|
||||
<path
|
||||
fill-rule="evenodd"
|
||||
d="M5.293 7.293a1 1 0 011.414 0L10 10.586l3.293-3.293a1 1 0 111.414 1.414l-4 4a1 1 0 01-1.414 0l-4-4a1 1 0 010-1.414z"
|
||||
clip-rule="evenodd"
|
||||
></path>
|
||||
</svg>
|
||||
</MenuButton>
|
||||
</div>
|
||||
<transition
|
||||
enter-active-class="transition ease-out duration-200"
|
||||
enter-from-class="transform opacity-0 scale-95"
|
||||
enter-to-class="transform opacity-100 scale-100"
|
||||
leave-active-class="transition ease-in duration-75"
|
||||
leave-from-class="transform opacity-100 scale-100"
|
||||
leave-to-class="transform opacity-0 scale-95"
|
||||
>
|
||||
<MenuItems
|
||||
class="absolute right-0 z-10 mt-2 w-48 origin-top-right rounded-md bg-white py-1 shadow-lg ring-1 ring-black ring-opacity-5 focus:outline-none"
|
||||
>
|
||||
<MenuItem
|
||||
v-for="subItem in item['subs']"
|
||||
v-slot="{ active }"
|
||||
:key="subItem.idx"
|
||||
>
|
||||
<a
|
||||
href="javascript:void(0)"
|
||||
:class="[
|
||||
active
|
||||
? 'bg-gray-100'
|
||||
: '',
|
||||
'block px-4 py-2 text-sm text-gray-700',
|
||||
]"
|
||||
@click="
|
||||
handleItemClick(
|
||||
$event,
|
||||
subItem
|
||||
)
|
||||
"
|
||||
>{{ subItem.title }}</a
|
||||
>
|
||||
</MenuItem>
|
||||
</MenuItems>
|
||||
</transition>
|
||||
</Menu>
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
</div>
|
||||
<div class="hidden sm:ml-6 sm:flex sm:items-center">
|
||||
<button
|
||||
v-if="false"
|
||||
type="button"
|
||||
class="rounded-full bg-white p-1 text-gray-400 hover:text-gray-500 focus:outline-none focus:ring-2 focus:ring-indigo-500 focus:ring-offset-2"
|
||||
>
|
||||
<span class="sr-only">View notifications</span>
|
||||
<BellIcon class="h-6 w-6" aria-hidden="true" />
|
||||
</button>
|
||||
|
||||
<!-- Profile dropdown -->
|
||||
<Menu as="div" class="relative ml-3">
|
||||
<div>
|
||||
<MenuButton
|
||||
class="flex rounded-full bg-white text-sm focus:outline-none focus:ring-2 focus:ring-indigo-500 focus:ring-offset-2"
|
||||
>
|
||||
<span class="sr-only">Open user menu</span>
|
||||
<BaseUserProfileImage :image-size="2" />
|
||||
</MenuButton>
|
||||
</div>
|
||||
<transition
|
||||
enter-active-class="transition ease-out duration-200"
|
||||
enter-from-class="transform opacity-0 scale-95"
|
||||
enter-to-class="transform opacity-100 scale-100"
|
||||
leave-active-class="transition ease-in duration-75"
|
||||
leave-from-class="transform opacity-100 scale-100"
|
||||
leave-to-class="transform opacity-0 scale-95"
|
||||
>
|
||||
<MenuItems
|
||||
class="absolute right-0 z-10 mt-2 w-48 origin-top-right rounded-md bg-white py-1 shadow-lg ring-1 ring-black ring-opacity-5 focus:outline-none"
|
||||
>
|
||||
<MenuItem
|
||||
v-if="isAuthenticated"
|
||||
v-slot="{ active }"
|
||||
>
|
||||
<a
|
||||
href="javascript:void(0)"
|
||||
:class="[
|
||||
active ? 'bg-gray-100' : '',
|
||||
'block px-4 py-2 text-sm text-gray-700',
|
||||
]"
|
||||
@click="moveToPath('/user/info')"
|
||||
>설정</a
|
||||
>
|
||||
</MenuItem>
|
||||
<MenuItem v-slot="{ active }">
|
||||
<a
|
||||
href="javascript:void(0)"
|
||||
:class="[
|
||||
active ? 'bg-gray-100' : '',
|
||||
'block px-4 py-2 text-sm text-gray-700',
|
||||
]"
|
||||
@click="doSignInAndOut($event)"
|
||||
>{{
|
||||
isAuthenticated == true
|
||||
? '로그아웃'
|
||||
: '로그인'
|
||||
}}</a
|
||||
>
|
||||
</MenuItem>
|
||||
</MenuItems>
|
||||
</transition>
|
||||
</Menu>
|
||||
</div>
|
||||
<div class="-mr-2 flex items-center sm:hidden">
|
||||
<!-- Mobile menu button -->
|
||||
<DisclosureButton
|
||||
class="inline-flex items-center justify-center rounded-md p-2 text-gray-400 hover:bg-gray-100 hover:text-gray-500 focus:outline-none focus:ring-2 focus:ring-inset focus:ring-indigo-500"
|
||||
>
|
||||
<span class="sr-only">Open main menu</span>
|
||||
<Bars3Icon
|
||||
v-if="!open"
|
||||
class="block h-6 w-6"
|
||||
aria-hidden="true"
|
||||
/>
|
||||
<XMarkIcon
|
||||
v-else
|
||||
class="block h-6 w-6"
|
||||
aria-hidden="true"
|
||||
/>
|
||||
</DisclosureButton>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<DisclosurePanel class="sm:hidden">
|
||||
<div class="space-y-1 pt-2 pb-3">
|
||||
<!-- Current: "bg-indigo-50 border-indigo-500 text-indigo-700", Default: "border-transparent text-gray-500 hover:bg-gray-50 hover:border-gray-300 hover:text-gray-700" -->
|
||||
|
||||
<template v-for="item in currentMenu['main']" :key="item.idx">
|
||||
<DisclosureButton
|
||||
v-if="item.path != undefined"
|
||||
as="a"
|
||||
href="javascript:void(0)"
|
||||
:class="
|
||||
currentRoutePath == item.path
|
||||
? 'block border-l-4 border-indigo-500 bg-indigo-50 py-2 pl-3 pr-4 text-base font-medium text-indigo-700'
|
||||
: 'block border-l-4 border-transparent py-2 pl-3 pr-4 text-base font-medium text-gray-500 hover:border-gray-300 hover:bg-gray-50 hover:text-gray-700'
|
||||
"
|
||||
@click="handleItemClick($event, item)"
|
||||
>{{ item['title'] }}</DisclosureButton
|
||||
>
|
||||
<a
|
||||
v-else
|
||||
href="javascript:void(0)"
|
||||
:class="
|
||||
currentRoutePath == item.path
|
||||
? 'block border-l-4 border-indigo-500 bg-indigo-50 py-2 pl-3 pr-4 text-base font-medium text-indigo-700'
|
||||
: 'block border-l-4 border-transparent py-2 pl-3 pr-4 text-base font-medium text-gray-500 hover:border-gray-300 hover:bg-gray-50 hover:text-gray-700'
|
||||
"
|
||||
>{{ item['title'] }}</a
|
||||
>
|
||||
<template v-for="subItem in item.subs" :key="subItem.idx">
|
||||
<DisclosureButton
|
||||
v-if="subItem.path != undefined"
|
||||
as="a"
|
||||
href="javascript:void(0)"
|
||||
:class="
|
||||
currentRoutePath == subItem.path
|
||||
? 'block border-l-4 border-indigo-500 bg-indigo-50 py-2 pl-3 pr-4 text-base font-medium text-indigo-700'
|
||||
: 'block border-l-4 border-transparent py-2 pl-3 pr-4 text-base font-medium text-gray-500 hover:border-gray-300 hover:bg-gray-50 hover:text-gray-700'
|
||||
"
|
||||
class="ml-4"
|
||||
@click="handleItemClick($event, subItem)"
|
||||
>{{ subItem['title'] }}</DisclosureButton
|
||||
>
|
||||
<a
|
||||
v-else
|
||||
href="javascript:void(0)"
|
||||
:class="
|
||||
currentRoutePath == subItem.path
|
||||
? 'block border-l-4 border-indigo-500 bg-indigo-50 py-2 pl-3 pr-4 text-base font-medium text-indigo-700'
|
||||
: 'block border-l-4 border-transparent py-2 pl-3 pr-4 text-base font-medium text-gray-500 hover:border-gray-300 hover:bg-gray-50 hover:text-gray-700'
|
||||
"
|
||||
>{{ subItem['title'] }}</a
|
||||
>
|
||||
</template>
|
||||
</template>
|
||||
|
||||
<template v-for="item in currentMenu['sub']" :key="item.idx">
|
||||
<DisclosureButton
|
||||
as="a"
|
||||
href="javascript:void(0)"
|
||||
:class="
|
||||
currentRoutePath == item.path
|
||||
? 'block border-l-4 border-indigo-500 bg-indigo-50 py-2 pl-3 pr-4 text-base font-medium text-indigo-700'
|
||||
: 'block border-l-4 border-transparent py-2 pl-3 pr-4 text-base font-medium text-gray-500 hover:border-gray-300 hover:bg-gray-50 hover:text-gray-700'
|
||||
"
|
||||
@click="handleItemClick($event, item)"
|
||||
>{{ item['title'] }}</DisclosureButton
|
||||
>
|
||||
</template>
|
||||
</div>
|
||||
<div class="border-t border-gray-200 pt-4 pb-3">
|
||||
<div class="flex items-center px-4">
|
||||
<div class="flex-shrink-0">
|
||||
<BaseUserProfileImage :image-size="3" />
|
||||
</div>
|
||||
<div v-if="false" class="ml-3">
|
||||
<div class="text-base font-medium text-gray-800">
|
||||
Tom Cook
|
||||
</div>
|
||||
<div class="text-sm font-medium text-gray-500">
|
||||
tom@example.com
|
||||
</div>
|
||||
</div>
|
||||
<button
|
||||
v-if="false"
|
||||
type="button"
|
||||
class="ml-auto flex-shrink-0 rounded-full bg-white p-1 text-gray-400 hover:text-gray-500 focus:outline-none focus:ring-2 focus:ring-indigo-500 focus:ring-offset-2"
|
||||
>
|
||||
<span class="sr-only">View notifications</span>
|
||||
<BellIcon class="h-6 w-6" aria-hidden="true" />
|
||||
</button>
|
||||
</div>
|
||||
<div class="mt-3 space-y-1">
|
||||
<DisclosureButton
|
||||
v-if="isAuthenticated"
|
||||
as="a"
|
||||
href="javascript:void(0)"
|
||||
class="block px-4 py-2 text-base font-medium text-gray-500 hover:bg-gray-100 hover:text-gray-800"
|
||||
@click="moveToPath('/user/info')"
|
||||
>설정</DisclosureButton
|
||||
>
|
||||
<DisclosureButton
|
||||
as="a"
|
||||
href="javascript:void(0)"
|
||||
class="block px-4 py-2 text-base font-medium text-gray-500 hover:bg-gray-100 hover:text-gray-800"
|
||||
@click="doSignInAndOut($event)"
|
||||
>{{
|
||||
isAuthenticated == true ? '로그아웃' : '로그인'
|
||||
}}</DisclosureButton
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
</DisclosurePanel>
|
||||
</Disclosure>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import {
|
||||
Disclosure,
|
||||
DisclosureButton,
|
||||
DisclosurePanel,
|
||||
Menu,
|
||||
MenuButton,
|
||||
MenuItem,
|
||||
MenuItems,
|
||||
} from '@headlessui/vue';
|
||||
import { Bars3Icon, BellIcon, XMarkIcon } from '@heroicons/vue/24/outline';
|
||||
const route = useRoute();
|
||||
const currentRoutePath = ref(route.path);
|
||||
const siteLogoUrl = ref(_siteConfig.siteLogoUrl);
|
||||
const currentMenu = ref(_crossCtl.menu);
|
||||
|
||||
const isAuthenticated = ref(_crossCtl.isAuthenticated);
|
||||
|
||||
function isCurrentMenu(menuItem) {
|
||||
let finalResult = false;
|
||||
|
||||
if (menuItem.path == currentRoutePath.value) {
|
||||
finalResult = true;
|
||||
} else {
|
||||
if (menuItem['subs'] != undefined) {
|
||||
for (let i = 0; i < menuItem['subs'].length; i++) {
|
||||
if (menuItem['subs'][i].path == currentRoutePath.value) {
|
||||
finalResult = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return finalResult;
|
||||
}
|
||||
|
||||
function handleItemClick(e, item) {
|
||||
if (item == null) {
|
||||
navigateTo('/');
|
||||
currentRoutePath.value = '/';
|
||||
} else {
|
||||
_crossCtl.moveToMenuItem(item);
|
||||
currentRoutePath.value = item.path;
|
||||
}
|
||||
}
|
||||
|
||||
function moveToPath(path) {
|
||||
navigateTo(path);
|
||||
currentRoutePath.value = path;
|
||||
}
|
||||
|
||||
async function doSignInAndOut(e) {
|
||||
e.target.blur();
|
||||
if (_crossCtl.isAuthenticated.value) {
|
||||
const response = await _crossCtl.doComm('signout', '', {});
|
||||
console.log('response=', response);
|
||||
if (response['responseCode'] == 200) {
|
||||
_crossCtl.setUserInfo({
|
||||
isAdmin: false,
|
||||
isApproved: false,
|
||||
isAuthenticated: false,
|
||||
isHighLeveled: false,
|
||||
isOp: false,
|
||||
isSuperOp: false,
|
||||
});
|
||||
_crossCtl.setUserProfile({});
|
||||
isAuthenticated.value = false;
|
||||
window.location.href = '/';
|
||||
} else {
|
||||
alert(response['responseMessage']);
|
||||
}
|
||||
} else {
|
||||
navigateTo({
|
||||
path: '/user/signin',
|
||||
});
|
||||
}
|
||||
}
|
||||
</script>
|
||||
27
inspond-nuxt-safekiso/base/components/VueJsonPretty.ts
Normal file
27
inspond-nuxt-safekiso/base/components/VueJsonPretty.ts
Normal file
@@ -0,0 +1,27 @@
|
||||
import { defineComponent, h, PropType } from 'vue';
|
||||
|
||||
import VueJsonPretty from 'vue-json-pretty';
|
||||
|
||||
export default defineComponent({
|
||||
name: 'VueJsonPretty',
|
||||
components: {
|
||||
VueJsonPretty,
|
||||
},
|
||||
props: {
|
||||
path: {
|
||||
type: String,
|
||||
default: 'res',
|
||||
},
|
||||
data: {
|
||||
type: Object,
|
||||
default: () => {},
|
||||
},
|
||||
},
|
||||
setup(props) {
|
||||
return () =>
|
||||
h(VueJsonPretty, {
|
||||
path: props.path,
|
||||
data: props.data,
|
||||
});
|
||||
},
|
||||
});
|
||||
Reference in New Issue
Block a user