시스템관리 > 권한관리 > 사이트관리 화면과 API 추가

This commit is contained in:
이진기 2024-11-19 16:01:42 +09:00
parent aed1321d88
commit 843db73d12
10 changed files with 538 additions and 1 deletions

View File

@ -0,0 +1,28 @@
<script setup lang="ts">
import { useAuthStore } from '~/stores/login';
import { some } from 'lodash-es';
const props = defineProps({
api: {
type: String,
required: true
}
});
const authStore = useAuthStore();
const { permitApiList } = storeToRefs(authStore);
const permit = computed(() => {
return some(permitApiList.value, (value) => {
return props.api === value.menuUrl;
});
});
</script>
<template>
<template v-if="permit">
<slot />
</template>
</template>
<style scoped></style>

View File

@ -0,0 +1,41 @@
<script setup lang="ts">
import 'tui-pagination/dist/tui-pagination.css';
import Pagination from 'tui-pagination/dist/tui-pagination';
import type { PaginationType } from '~/types/data/pagination';
const instance = ref();
const paginationRef = ref();
const props = defineProps<PaginationType>();
const emit = defineEmits(['change']);
onMounted(() => {
instance.value = new Pagination(paginationRef.value, {
totalItems: props.totalItems,
itemPerPage: props.itemsPerPage,
visiblePages: props.visiblePages ?? 10,
centerAlign: props.centerAlign ?? false,
usageStatistics: false
});
instance.value.on('beforeMove', (data: any) => {
emit('change', data.page);
});
});
watch(
() => props,
(newValue) => {
instance.value.setItemsPerPage(newValue.itemsPerPage);
instance.value.setTotalItems(newValue.itemsPerPage);
},
{
deep: true
}
);
</script>
<template>
<div ref="paginationRef" class="tui-pagination"></div>
</template>
<style scoped></style>

View File

@ -0,0 +1,32 @@
<script setup lang="ts">
import { Editor } from '@toast-ui/editor';
import '@toast-ui/editor/dist/toastui-editor.css';
const editorRef = ref();
const instance = ref();
const emit = defineEmits(['change']);
const props = defineProps<{ initialValue: string }>();
onMounted(() => {
instance.value = new Editor({
el: editorRef.value,
initialValue: props.initialValue ?? '',
initialEditType: 'wysiwyg',
hideModeSwitch: true,
usageStatistics: false,
events: {
keyup: () => {
if (instance.value) {
emit('change', instance.value.getHTML());
}
}
}
});
});
</script>
<template>
<div ref="editorRef"></div>
</template>
<style scoped></style>

View File

@ -0,0 +1,136 @@
<script setup lang="ts">
import 'tui-grid/dist/tui-grid.css';
import 'tui-date-picker/dist/tui-date-picker.min.css';
import Grid, { type RowKey } from 'tui-grid';
import { cloneDeep } from 'lodash-es';
import type { DataGridType } from '~/types/data/grid';
import type { GridEventName, OptRow } from 'tui-grid/types/options';
import { TUI_GRID_THEME } from '~/constants/theme/grid';
Grid.setLanguage('ko', {
display: {
noData: '조회된 데이터가 존재하지 않습니다.'
}
});
Grid.applyTheme('default', TUI_GRID_THEME);
const gridRef = ref();
const instance = ref<Grid>();
const props = defineProps<DataGridType>();
onMounted(() => {
const gridOptions = {
el: gridRef.value,
columns: props.columns ?? [],
data: cloneDeep(props.data) ?? [],
bodyHeight: props.bodyHeight,
columnOptions: props.columnOptions,
keyColumnName: props.keyColumnName,
width: props.width,
heightResizable: props.heightResizable,
minBodyHeight: props.minBodyHeight,
rowHeight: props.rowHeight ?? 40,
minRowHeight: props.minRowHeight,
scrollX: props.scrollX,
scrollY: props.scrollY,
editingEvent: props.editingEvent,
tabMode: props.tabMode,
rowHeaders: props.rowHeaders,
summary: props.summary,
useClientSort: props.useClientSort,
selectionUnit: props.selectionUnit,
showDummyRows: props.showDummyRows,
copyOptions: props.copyOptions,
pageOptions: props.pageOptions,
treeColumnOptions: props.treeColumnOptions,
header: props.header,
usageStatistics: false,
disabled: props.disabled,
draggable: props.draggable,
contextMenu: props.contextMenu
};
if (props.onGridMounted) {
gridOptions['onGridMounted'] = props.onGridMounted;
}
if (props.onGridUpdated) {
gridOptions['onGridUpdated'] = props.onGridUpdated;
}
if (props.onGridBeforeDestroy) {
gridOptions['onGridBeforeDestroy'] = props.onGridBeforeDestroy;
}
instance.value = new Grid(gridOptions);
});
const appendRow = (value: OptRow) => {
instance.value?.appendRow(value);
};
const appendRows = (value: OptRow[]) => {
instance.value?.appendRows(value);
};
const prependRow = (value: OptRow) => {
instance.value?.prependRow(value);
};
const getCheckedRows = () => {
return instance.value?.getCheckedRows();
};
const getData = () => {
return instance.value?.getData();
};
const validate = () => {
return instance.value?.validate();
};
const resetData = (data: OptRow[]) => {
instance.value?.resetData(data);
};
const removeRow = (data: RowKey) => {
instance.value?.removeRow(data);
};
const removeRows = (data: RowKey[]) => {
instance.value?.removeRows(data);
};
const on = (eventName: GridEventName, eventFunction: (event) => void) => {
instance.value?.on(eventName, eventFunction);
};
const off = (eventName: GridEventName) => {
instance.value?.off(eventName);
};
defineExpose({
appendRow,
appendRows,
prependRow,
getCheckedRows,
getData,
validate,
resetData,
on,
off,
removeRow,
removeRows
});
watch(
() => props.data,
(newValue) => {
resetData(newValue);
}
);
</script>
<template>
<div ref="gridRef" />
</template>

View File

@ -0,0 +1,192 @@
<script setup lang="ts">
import type { OptColumn, OptRowHeader } from 'tui-grid/types/options';
import { useSiteStore } from '~/stores/sys/site';
import type { SiteType } from '~/types/sys/site';
import { MaxLengthTextEditor } from '~/constants/theme/grid/MaxLengthTextEditor';
const siteStore = useSiteStore();
const { siteList } = storeToRefs(siteStore);
const gridRef = ref();
const gridRowHeaders: OptRowHeader[] = ['checkbox', 'rowNum'];
const columns: OptColumn[] = [
{
name: 'siteId',
header: '사이트 ID',
width: 80,
editor: {
type: 'text'
},
validation: {
required: true
}
},
{
name: 'siteName',
header: '사이트명',
width: 120,
editor: {
type: 'text'
},
validation: {
required: true
}
},
{
name: 'siteDescription',
header: '사이트 설명',
width: 200,
editor: {
type: 'text'
}
},
{
name: 'siteDomain',
header: '사이트 도메인',
width: 150,
editor: {
type: 'text'
},
validation: {
required: true
}
},
{
name: 'siteType',
header: '사이트 형식',
width: 80,
formatter: 'listItemText',
editor: {
type: 'select',
options: {
listItems: [
{ text: '사용자', value: 'USER' },
{ text: '관리자', value: 'ADMIN' }
]
}
},
validation: {
required: true
}
},
{
name: 'sitePrefix',
header: '사이트 구분',
width: 120,
editor: {
type: 'select',
options: {
listItems: [
{ text: '대국민포털', value: 'portal' },
{ text: '참여기관포털', value: 'admin' }
]
}
}
},
{
name: 'lgnUrl',
header: '로그인 URL',
width: 120,
editor: {
type: MaxLengthTextEditor,
options: {
maxlength: 200,
placeholder: '로그인 URL을 입력해주세요.'
}
}
},
{
name: 'bscUrl',
header: '기본 URL',
width: 120,
editor: {
type: MaxLengthTextEditor,
options: {
maxlength: 200,
placeholder: '로그인 후 이동될 URL을 입력해주세요.'
}
}
},
{ name: 'siteRegdate', header: '등록일시', width: 140 }
];
onBeforeMount(() => {
siteStore.searchSiteList();
});
const save = () => {
if (gridRef.value.validate().length) {
alert('len:::' + gridRef.value.validate().length);
message.warn('입력된 사이트 정보를 확인해주세요.');
return;
}
Modal.confirm({
type: 'info',
title: '사이트 정보 저장',
content: '선택한 사이트 정보를 저장하시겠습니까?',
okText: '예',
cancelText: '아니오',
onOk: async () => {
try {
const data = gridRef.value.getData();
siteStore.updateSiteList(data);
} catch (e) {
message.error('사이트 정보를 저장하는 도중 에러가 발생되었습니다.');
}
}
});
};
const deleteSiteList = () => {
const checkedRows = gridRef.value.getCheckedRows() as Array<SiteType>;
if (!checkedRows.length) {
message.warn('삭제할 사이트가 없습니다.');
return;
}
const params = new URLSearchParams();
checkedRows.forEach((value) => {
params.append('siteId', value.siteId);
});
};
const addRow = () => {
gridRef.value.appendRow({});
};
</script>
<template>
<client-only>
<a-space direction="vertical" style="width: 100%">
<a-card>
<a-space direction="vertical" style="width: 100%">
<a-flex justify="space-between">
<common-permit-button api="/api/admin/sys/site/deleteSiteList">
<a-button type="primary" danger @click="deleteSiteList"
>삭제</a-button
>
</common-permit-button>
<common-permit-button api="/api/admin/sys/site/updateSiteList">
<a-space>
<a-button type="primary" @click="addRow">추가</a-button>
<a-button type="primary" @click="save">저장</a-button>
</a-space>
</common-permit-button>
</a-flex>
<data-grid
:key="`site-grid-${Math.random()}`"
:row-headers="gridRowHeaders"
:data="siteList"
:columns="columns"
ref="gridRef"
/>
</a-space>
</a-card>
</a-space>
</client-only>
</template>
<style scoped></style>

View File

@ -19,9 +19,14 @@ export const useAuthStore = defineStore('authStore', () => {
return await useAxios().post(`/api/admin/login`, loginRequest.value); return await useAxios().post(`/api/admin/login`, loginRequest.value);
}; };
const permitApiList = computed(() => {
return loginResponse.value.permitApiList;
});
return { return {
loginRequest, loginRequest,
loginResponse, loginResponse,
LoginAPI LoginAPI,
permitApiList
}; };
}); });

View File

@ -0,0 +1,34 @@
import type { SiteType } from '~/types/sys/site';
export const useSiteStore = defineStore('useSiteStore', () => {
const siteList = ref<SiteType[]>([]);
const resetSiteList = () => {
siteList.value = [];
};
const searchSiteList = async () => {
try {
const { data } = await useAxios().get('/api/admin/sys/site/siteList');
siteList.value = data;
} catch (e) {
message.error('사이트 리스트를 불러오는데 실패하였습니다.');
}
};
const updateSiteList = async (data: SiteType[]) => {
try {
await useAxios().post('/api/admin/sys/site/updateSiteList', data);
message.success('사이트 정보가 저장이 되었습니다.');
} catch (e) {
message.error('사이트 저장에 실패하였습니다.');
}
};
return {
siteList,
resetSiteList,
searchSiteList,
updateSiteList
};
});

50
nuxt/types/data/grid.ts Normal file
View File

@ -0,0 +1,50 @@
import type {
GridEventListener,
OptColumn,
OptHeader,
OptRow,
OptRowHeader,
OptSummaryData,
OptTree
} from 'tui-grid/types/options';
import type { EditingEvent, TabMode } from 'tui-grid/types/store/focus';
import type { SelectionUnit } from 'tui-grid/types/store/selection';
import type {
ClipboardCopyOptions,
ColumnOptions
} from 'tui-grid/types/store/column';
import type { PageOptions } from 'tui-grid/types/store/data';
import type { CreateMenuGroups } from 'tui-grid/types/store/contextMenu';
export type DataGridType = {
data: OptRow[];
columns: OptColumn[];
bodyHeight?: number | 'fitToParent' | 'auto';
columnOptions?: ColumnOptions;
keyColumnName?: String;
width?: number | 'auto';
heightResizable?: Boolean;
minBodyHeight?: Number;
rowHeight?: number | 'auto';
minRowHeight?: Number;
scrollX?: Boolean;
scrollY?: Boolean;
editingEvent?: EditingEvent;
tabMode?: TabMode;
rowHeaders?: OptRowHeader[];
summary?: OptSummaryData;
useClientSort?: Boolean;
selectionUnit?: SelectionUnit;
showDummyRows?: Boolean;
copyOptions?: ClipboardCopyOptions;
pageOptions?: PageOptions;
treeColumnOptions?: OptTree;
header?: OptHeader;
usageStatistics?: Boolean;
disabled?: Boolean;
onGridMounted?: GridEventListener;
onGridUpdated?: GridEventListener;
onGridBeforeDestroy?: GridEventListener;
draggable?: Boolean;
contextMenu?: CreateMenuGroups;
};

View File

@ -0,0 +1,6 @@
export type PaginationType = {
totalItems: number;
itemsPerPage: number;
visiblePages: number;
centerAlign?: boolean;
};

View File

@ -0,0 +1,13 @@
export type SiteType = {
siteId: string;
siteName: string;
siteDescription: string;
siteDomain: string;
siteType: '' | 'ADMIN' | 'USER';
sitePrefix: string;
siteLocale: string;
siteLogo: string;
bscUrl: string;
lgnUrl: string;
siteRegdate: string;
};