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,266 @@
<!-- This example requires Tailwind CSS v2.0+ -->
<template>
<div class="pb-8 px-4 sm:px-6 lg:px-8">
<br />
<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">
<label for="mobile-search-candidate" class="sr-only"
>Search</label
>
<label for="desktop-search-candidate" class="sr-only"
>Search</label
>
<div class="flex rounded-md shadow-sm">
<div class="relative flex-grow focus-within:z-10">
<div
class="absolute inset-y-0 left-0 pl-3 flex items-center pointer-events-none"
>
<MagnifyingGlassCircleIcon
class="h-5 w-5 text-gray-400"
aria-hidden="true"
/>
</div>
<input
id="mobile-search-candidate"
v-model="searchKeyword"
type="text"
name="mobile-search-candidate"
class="focus:ring-indigo-500 focus:border-indigo-500 block w-full rounded-none rounded-l-md pl-10 sm:hidden border-gray-300"
placeholder=""
/>
<input
id="desktop-search-candidate"
v-model="searchKeyword"
type="text"
name="desktop-search-candidate"
class="hidden focus:ring-indigo-500 focus:border-indigo-500 w-full rounded-none rounded-l-md pl-10 sm:block sm:text-sm border-gray-300"
placeholder=""
/>
</div>
<button
type="button"
class="-ml-px relative inline-flex items-center px-4 py-2 border border-gray-300 text-sm font-medium rounded-r-md text-gray-700 bg-gray-50 hover:bg-gray-100 focus:outline-none focus:ring-1 focus:ring-indigo-500 focus:border-indigo-500"
@click="doAction('search', searchKeyword)"
>
<span class="ml-2">검색</span>
</button>
</div>
</div>
-->
</div>
<BaseList1
:headings="listHeadings"
:actions="listActions"
:keys="listKeys"
:data="listData"
:action-key="actionKey"
:column-filter="columnFilter"
:do-action="doAction"
/>
<BasePagination1
:total-page-count="totalPageCount"
:current-page-number="currentPageNumber"
:page-size="pageSize"
:records-total="recordsTotal"
:page-move="pageMove"
/>
<div class="p-0 rounded-bl-2xl rounded-br-2xl md:px-0">
<a
href="javascript:void(0)"
class="text-base font-medium text-indigo-700 hover:text-indigo-600"
@click="$router.back()"
>&larr;이전 화면으로<span aria-hidden="true"> </span
></a>
</div>
</div>
</template>
<script setup lang="ts">
import {
ChevronDownIcon,
MagnifyingGlassCircleIcon,
} from '@heroicons/vue/24/solid';
import consolaGlobalInstance from 'consola';
const { $dayjs } = useNuxtApp();
const router = useRouter();
definePageMeta({
middleware: 'check-auth-admin',
});
const route = useRoute();
console.log('route.params=', route.params);
const targetName = route.params.hero;
const hero = route.params.uid;
console.log('targetName=', targetName);
console.log('hero=', hero);
let listTarget = 'log:user:active';
const listHeadings = [
{
title: '누가',
class: 'py-3.5 pl-4 pr-3 text-left text-sm font-semibold text-gray-900 sm:pl-6',
key: 'uid',
hiddenInfo: {
headClass: '',
dts: [],
dds: [],
},
subClass:
'w-full max-w-0 py-4 pl-4 pr-3 text-sm font-medium text-gray-900 sm:w-auto sm:max-w-none sm:pl-6',
},
{
title: '언제',
class: 'hidden px-3 py-3.5 text-left text-sm font-semibold text-gray-900 lg:table-cell',
key: 'created',
hiddenInfo: {
headClass: '',
dts: [],
dds: [],
},
subClass: 'hidden px-3 py-4 text-sm text-gray-500 lg:table-cell',
},
{
title: '무엇을',
class: 'hidden px-3 py-3.5 text-left text-sm font-semibold text-gray-900 sm:table-cell',
key: 'tag',
hiddenInfo: {
headClass: '',
dts: [],
dds: [],
},
subClass: 'hidden px-3 py-4 text-sm text-gray-500 sm:table-cell',
},
];
const listActions = ['상세보기'];
const actionKey = 'serial';
const listKeys = [
'serial',
'raw',
'level',
'comment',
'status',
'updated',
'created',
];
const listData = ref([]);
const totalPageCount = ref(0);
const currentPageNumber = ref(1);
const pageSize = ref(10);
const recordsTotal = ref(0);
// const order = [{ column: 'serial', dir: 'desc' }];
// const columns = { serial: { data: 'serial' } };
function columnFilter(key, val) {
// console.log("columnFilter(), key = ", key, ", val = ", val);
if (key == 'updated' || key == 'created') {
return $dayjs(val).format('YY/MM/DD A h:mm:ss');
// return $dayjs(val).format('YY/MM/DD');
} else if (key == 'uid') {
return '' + targetName + '';
} else if (key == 'status') {
let statusTag = '정상';
switch (val) {
case 0:
statusTag = '정상등록';
break;
case 4:
statusTag = '삭제됨';
break;
default:
statusTag = val;
}
return statusTag;
} else if (key == 'level') {
let levelTag = 'mid';
switch (val) {
case 10:
levelTag = 'high';
break;
case 50:
levelTag = 'mid';
break;
case 100:
levelTag = 'low';
break;
default:
levelTag = val;
}
return levelTag;
} else {
return val;
}
}
function doAction(tag, target) {
console.log('on doAction(), tag=', tag, ', target=', target);
if (tag == '상세보기') {
navigateTo('/admin/user/' + hero + '/history/detail/' + target);
} else if (tag == 'search') {
console.log('search for ', target);
if (target == '') {
listTarget = 'log:user:active';
} else {
listTarget = 'log:user';
// hero = target;
}
refresh();
}
}
function pageMove(targetPageIdex) {
console.log('on pageMove(), targetPageIdex=', targetPageIdex);
currentPageNumber.value = targetPageIdex;
refresh();
}
async function refresh() {
const responseJson = await _crossCtl.doComm('list', listTarget, {
start: (currentPageNumber.value - 1) * pageSize.value,
length: pageSize.value,
hero: hero,
});
console.log('listTarget=', listTarget);
console.log('hero=', hero);
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 (hero != undefined) {
doAction('search', hero);
} else {
refresh();
}
</script>

View File

@@ -0,0 +1,232 @@
<!-- This example requires Tailwind CSS v2.0+ -->
<template>
<div class="divide-y divide-gray-200">
<form class="lg:col-span-9">
<!-- Profile section -->
<div class="py-6 px-4 sm:p-6 lg:pb-8">
<div class="sm:flex sm:items-center">
<div class="sm:flex-auto">
<h2 class="text-lg leading-6 font-medium text-gray-900">
로그 상세 보기
</h2>
<p class="mt-1 text-sm text-gray-500"></p>
</div>
<div class="mt-4 sm:mt-0 sm:ml-16 sm:flex-none"></div>
</div>
<div class="mt-6 grid grid-cols-12 gap-6">
<div class="col-span-12 sm:col-span-6">
<label
for="email"
class="block text-sm font-medium text-gray-700"
>이메일</label
>
<input
id="email"
v-model="email"
disabled
type="text"
name="email"
autocomplete="email"
class="mt-1 block w-full border border-gray-300 rounded-md shadow-sm py-2 px-3 focus:outline-none focus:ring-sky-500 focus:border-sky-500 sm:text-sm"
/>
</div>
<div class="col-span-12 sm:col-span-6"></div>
</div>
<div class="mt-6 flex flex-col lg:flex-row">
<div class="flex-grow space-y-6">
<div>
<label
for="username"
class="block text-sm font-medium text-gray-700"
>
사용자 이름
</label>
<div class="mt-1 rounded-md shadow-sm flex">
<input
id="username"
v-model="displayName"
disabled
type="text"
name="username"
autocomplete="username"
class="focus:ring-sky-500 focus:border-sky-500 flex-grow block w-full min-w-0 rounded-none rounded-r-md sm:text-sm border-gray-300"
/>
</div>
</div>
<div>
<label
for="phone"
class="block text-sm font-medium text-gray-700"
>
전화번호
</label>
<div class="mt-1 rounded-md shadow-sm flex">
<input
id="phone"
v-model="phone"
disabled
type="text"
name="phone"
autocomplete="phone"
class="focus:ring-sky-500 focus:border-sky-500 flex-grow block w-full min-w-0 rounded-none rounded-r-md sm:text-sm border-gray-300"
/>
</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="memo"
disabled
name="about"
rows="3"
class="shadow-sm focus:ring-sky-500 focus:border-sky-500 mt-1 block w-full sm:text-sm border border-gray-300 rounded-md"
/>
</div>
<p class="mt-2 text-sm text-gray-500">
로그 참고 사항으로 수정할 없습니다.
</p>
</div>
</div>
</div>
<div class="mt-6 grid grid-cols-12 gap-6">
<div class="col-span-12 sm:col-span-6">
<label
for="email"
class="block text-sm font-medium text-gray-700"
>로그 태그</label
>
<input
id="logTag"
v-model="logTag"
disabled
type="text"
name="logTag"
autocomplete="logTag"
class="mt-1 block w-full border border-gray-300 rounded-md shadow-sm py-2 px-3 focus:outline-none focus:ring-sky-500 focus:border-sky-500 sm:text-sm"
/>
</div>
<div class="col-span-12 sm:col-span-6"></div>
</div>
<div class="mt-6">
<div>
<label
for="about"
class="block text-sm font-medium text-gray-700"
>
부가 정보
</label>
<div class="mt-1">
<vue-json-pretty
class="bg-white shadow-sm focus:ring-sky-500 focus:border-sky-500 mt-1 block w-full sm:text-sm border border-gray-300 rounded-md"
:path="'root'"
:data="logMemo"
>
</vue-json-pretty>
</div>
<p class="mt-2 text-sm text-gray-500"></p>
</div>
</div>
</div>
<!-- Privacy section -->
<div class="pt-0">
<div class="mt-4 py-4 px-4 flex justify-end sm:px-6">
<button
type="button"
class="bg-white border border-gray-300 rounded-md shadow-sm py-2 px-4 inline-flex justify-center text-sm font-medium text-gray-700 hover:bg-gray-50 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-sky-500"
@click="doCancel"
>
이전 화면으로
</button>
</div>
</div>
</form>
</div>
</template>
<script setup lang="ts">
definePageMeta({
middleware: 'check-auth-admin',
});
const router = useRouter();
const route = useRoute();
const email = ref('');
const displayName = ref('');
const photoUrl = ref('');
const phone = ref('');
const memo = ref('');
const logTag = ref('');
const logMemo = ref('');
let logInfo = {};
let userInfo = {};
async function doCancel() {
router.back();
}
console.log('route.params=', route.params);
const hero = route.params.hero;
const uid = route.params.uid;
console.log('hero=', hero);
let responseJson = await _crossCtl.doComm('select', 'admin:user:byid', {
hero: uid,
});
console.log('responseJson=', responseJson);
if (responseJson['responseMessage'] == 'ok') {
userInfo = responseJson['data'][0];
const tmpUserInfo = _utils.safeJSON(userInfo['infos']);
if (tmpUserInfo != null) {
email.value = tmpUserInfo['email'];
displayName.value = userInfo['display_name'];
photoUrl.value = userInfo['photo_url'];
phone.value = tmpUserInfo['phone'];
memo.value = tmpUserInfo['memo'];
} else {
email.value = 'NaN';
displayName.value = userInfo['display_name'];
photoUrl.value = userInfo['photo_url'];
phone.value = 'NaN';
memo.value = 'NaN';
}
responseJson = await _crossCtl.doComm('select', 'log:user', {
hero: hero,
});
console.log('responseJson=', responseJson);
if (responseJson['responseMessage'] == 'ok') {
logInfo = responseJson['data'][0];
const tmpLogMemo = _utils.safeJSON(logInfo['memo']);
console.log('logInfo = ', logInfo);
console.log('tmpLogMemo = ', tmpLogMemo);
logTag.value = logInfo['tag'];
logMemo.value = tmpLogMemo;
} else {
alert(responseJson['responseMessage']);
}
} else {
alert(responseJson['responseMessage']);
}
</script>