/admin/content/list 프론트 기능 추가
This commit is contained in:
parent
505a3e5101
commit
dae8e6d3a7
|
|
@ -1,5 +1,5 @@
|
|||
<script setup lang="ts">
|
||||
import 'tui-pagination/dist/tui-pagination.css';
|
||||
import 'tui-pagination.css';
|
||||
import Pagination from 'tui-pagination/dist/tui-pagination';
|
||||
import type { PaginationType } from '~/types/data/pagination';
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,20 @@
|
|||
export const BOOLEANS = [
|
||||
{ text: '사용', value: 'true' },
|
||||
{ text: '미사용', value: 'false' }
|
||||
];
|
||||
|
||||
export const YES_OR_NO_CODE_LIST = [
|
||||
{ text: '예', value: 'true' },
|
||||
{ text: '아니오', value: 'false' }
|
||||
];
|
||||
|
||||
export const LOCK_CODE_LIST = [
|
||||
{ text: '정상', value: 'false' },
|
||||
{ text: '잠김', value: 'true' }
|
||||
];
|
||||
|
||||
export const ADMIN_STATUS_CODE_LIST = [
|
||||
{ text: '미승인', value: 'NONE' },
|
||||
{ text: '승인', value: 'APRV' },
|
||||
{ text: '반려', value: 'RJCT' }
|
||||
];
|
||||
|
|
@ -21,12 +21,6 @@ export default defineNuxtConfig({
|
|||
autoImport: true
|
||||
},
|
||||
devtools: { enabled: true },
|
||||
// plugins: [
|
||||
// '~/plugins/ant-design-vue.ts'
|
||||
// ],
|
||||
// css: [
|
||||
// 'ant-design-vue/dist/reset.css'
|
||||
// ],
|
||||
modules: [
|
||||
'@pinia/nuxt',
|
||||
'@unocss/nuxt',
|
||||
|
|
@ -35,11 +29,11 @@ export default defineNuxtConfig({
|
|||
],
|
||||
vite: {
|
||||
optimizeDeps: {
|
||||
include: ['tui-grid', '@ant-design', 'ant-design-vue']
|
||||
include: ['tui-grid', 'ant-design-vue']
|
||||
}
|
||||
},
|
||||
alias: {},
|
||||
experimental: {
|
||||
payloadExtraction: false
|
||||
payloadExtraction: true
|
||||
}
|
||||
});
|
||||
|
|
|
|||
|
|
@ -0,0 +1,126 @@
|
|||
<script setup lang="ts">
|
||||
import { useContentStore } from '~/stores/contents';
|
||||
|
||||
const route = useRoute();
|
||||
const router = useRouter();
|
||||
|
||||
const contentId = route.query.contentId;
|
||||
|
||||
const editorRef = ref();
|
||||
|
||||
const contentStore = useContentStore();
|
||||
const { contents, initialValue } = storeToRefs(contentStore);
|
||||
|
||||
onBeforeMount(() => {
|
||||
if (contentId) {
|
||||
contentStore.searchContents(Number(contentId));
|
||||
} else {
|
||||
contentStore.resetContents();
|
||||
}
|
||||
});
|
||||
|
||||
const save = () => {
|
||||
contents.value.contents = editorRef.value.getValue();
|
||||
|
||||
contentStore
|
||||
.updateContents()
|
||||
.then(() => {
|
||||
message.success('콘텐츠 정보가 저장이 되었습니다.');
|
||||
moveList();
|
||||
})
|
||||
.catch(() => {
|
||||
message.error('콘텐츠 저장에 실패하였습니다.');
|
||||
});
|
||||
};
|
||||
|
||||
const moveList = () => {
|
||||
router.push('/admin/content/list');
|
||||
};
|
||||
|
||||
const nonValid = computed(() => {
|
||||
return !contents.value.contentTitle;
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<client-only>
|
||||
<a-space direction="vertical" class="w-full">
|
||||
<a-card>
|
||||
<a-row>
|
||||
<a-col :span="24">
|
||||
<a-form-item
|
||||
label="제목"
|
||||
label-align="left"
|
||||
:colon="false"
|
||||
:label-col="{ span: 2 }"
|
||||
>
|
||||
<a-input
|
||||
title="제목"
|
||||
placeholder="콘텐츠 제목"
|
||||
v-model:value="contents.contentTitle"
|
||||
/>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
|
||||
<a-col :span="24">
|
||||
<a-form-item
|
||||
label="기관"
|
||||
label-align="left"
|
||||
:colon="false"
|
||||
:label-col="{ span: 2 }"
|
||||
>
|
||||
<!-- <common-inst-code-select v-model:value="contents.orgId" />-->
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
|
||||
<a-col :span="24">
|
||||
<a-form-item
|
||||
label="내용"
|
||||
label-align="left"
|
||||
:colon="false"
|
||||
:label-col="{ span: 2 }"
|
||||
:wrapper-col="{ span: 22 }"
|
||||
>
|
||||
<lazy-data-editor ref="editorRef" :initial-value="initialValue" />
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
|
||||
<a-col :span="24">
|
||||
<a-form-item
|
||||
label="사용여부"
|
||||
label-align="left"
|
||||
:colon="false"
|
||||
:label-col="{ span: 2 }"
|
||||
:wrapper-col="{ span: 22 }"
|
||||
>
|
||||
<a-switch v-model:checked="contents.useYn" />
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
|
||||
<a-col :span="24">
|
||||
<a-flex justify="space-between">
|
||||
<a-space>
|
||||
<common-permit-button api="/api/admin/contents/updateContents">
|
||||
<a-button
|
||||
type="primary"
|
||||
@click="save"
|
||||
:disabled="nonValid"
|
||||
v-if="!contentId"
|
||||
>저장</a-button
|
||||
>
|
||||
<a-button
|
||||
type="primary"
|
||||
@click="save"
|
||||
v-if="contentId != null"
|
||||
>수정</a-button
|
||||
>
|
||||
</common-permit-button>
|
||||
<a-button type="default" @click="moveList">목록</a-button>
|
||||
</a-space>
|
||||
</a-flex>
|
||||
</a-col>
|
||||
</a-row>
|
||||
</a-card>
|
||||
</a-space>
|
||||
</client-only>
|
||||
</template>
|
||||
|
|
@ -0,0 +1,194 @@
|
|||
<script setup lang="ts">
|
||||
import type { OptColumn, OptRowHeader } from 'tui-grid/types/options';
|
||||
import { useContentStore } from '~/stores/contents';
|
||||
import type { ContentType } from '~/types/contents';
|
||||
import { BOOLEANS } from '~/constants/grid';
|
||||
// import { useCommonCodeStore } from '~/stores';
|
||||
|
||||
const router = useRouter();
|
||||
|
||||
// const siteCodeList = await useCommonCodeStore().searchSiteCodeList();
|
||||
// const instCodeList = await useCommonCodeStore().searchInstCodeList();
|
||||
const contentStore = useContentStore();
|
||||
const { contentsList, contentsQuery } = storeToRefs(contentStore);
|
||||
const gridRef = ref();
|
||||
|
||||
const gridRowHeaders: OptRowHeader[] = ['checkbox', 'rowNum'];
|
||||
|
||||
const columns: OptColumn[] = [
|
||||
{
|
||||
name: 'contentId',
|
||||
hidden: true
|
||||
},
|
||||
{
|
||||
name: 'contentTitle',
|
||||
header: '콘텐츠제목'
|
||||
},
|
||||
{
|
||||
name: 'orgId',
|
||||
header: '관리기관',
|
||||
width: 100,
|
||||
},
|
||||
{
|
||||
name: 'useYn',
|
||||
header: '사용여부',
|
||||
width: 80,
|
||||
align: 'center',
|
||||
},
|
||||
{
|
||||
name: 'frstRgtrId',
|
||||
header: '작성자',
|
||||
width: 100
|
||||
},
|
||||
{
|
||||
name: 'frstRegDt',
|
||||
header: '작성일',
|
||||
width: 130
|
||||
},
|
||||
{
|
||||
name: 'lastMdfrId',
|
||||
header: '수정자',
|
||||
width: 100
|
||||
},
|
||||
{
|
||||
name: 'lastMdfcnDt',
|
||||
header: '수정일',
|
||||
width: 130
|
||||
}
|
||||
];
|
||||
|
||||
const contentType = [
|
||||
{ label: '전체', value: '' },
|
||||
{ label: '제목', value: 'TITLE' },
|
||||
{ label: '내용', value: 'CONTENT' }
|
||||
];
|
||||
|
||||
onBeforeMount(() => {
|
||||
contentStore.searchContentList();
|
||||
});
|
||||
|
||||
onBeforeUnmount(() => {
|
||||
// contentStore.resetContentListQuery();
|
||||
});
|
||||
|
||||
const search = () => {
|
||||
// contentStore.searchContentList();
|
||||
};
|
||||
|
||||
const list = computed(() => {
|
||||
return contentsList.value.content;
|
||||
});
|
||||
|
||||
watch(list, (newValue) => {
|
||||
if (gridRef.value) {
|
||||
gridRef.value.off('dblclick');
|
||||
if (newValue.length > 0) {
|
||||
setTimeout(() => {
|
||||
gridRef.value.on('dblclick', ({ instance, rowKey }) => {
|
||||
const row = instance.getRow(rowKey);
|
||||
editPage(row.contentId);
|
||||
});
|
||||
}, 100);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
const deleteContentList = async () => {
|
||||
const checkedRows = gridRef.value.getCheckedRows() as Array<ContentType>;
|
||||
|
||||
if (!checkedRows.length) {
|
||||
message.warn('삭제할 콘텐츠가 없습니다.');
|
||||
return;
|
||||
}
|
||||
Modal.confirm({
|
||||
type: 'warning',
|
||||
okText: '예',
|
||||
cancelText: '아니오',
|
||||
title: '컨텐츠 삭제',
|
||||
content: '선택한 컨텐츠를 삭제하시겠습니까?',
|
||||
onOk: async () => {
|
||||
const createdRows = checkedRows.filter((row) => row.contentId);
|
||||
await contentStore.deleteContents(createdRows);
|
||||
await contentStore.searchContentList();
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
const movePage = (page: number) => {
|
||||
contentsQuery.value.page = page;
|
||||
search();
|
||||
};
|
||||
|
||||
const editPage = (contentId: any) => {
|
||||
const query = typeof contentId === 'number' ? `?contentId=${contentId}` : '';
|
||||
router.push(`/admin/content${query}`);
|
||||
};
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<client-only>
|
||||
<a-space direction="vertical" class="w-full">
|
||||
<a-card>
|
||||
<a-row :gutter="[16, 8]">
|
||||
<a-col>
|
||||
<a-space>
|
||||
<a-typography-text>관리기관</a-typography-text>
|
||||
<!-- <lazy-common-inst-code-select-->
|
||||
<!-- key="inst-code-select"-->
|
||||
<!-- v-model="contentsQuery.orgId"-->
|
||||
<!-- class-name="w-40"-->
|
||||
<!-- select-type="ALL"-->
|
||||
<!-- />-->
|
||||
</a-space>
|
||||
</a-col>
|
||||
|
||||
<a-col>
|
||||
<a-space>
|
||||
<a-typography-text>검색어</a-typography-text>
|
||||
<a-select
|
||||
title="컨텐츠 구분"
|
||||
class="w-20"
|
||||
v-model:value="contentsQuery.type"
|
||||
:options="contentType"
|
||||
/>
|
||||
<a-input title="검색어" placeholder="Search" class="w-60" />
|
||||
</a-space>
|
||||
</a-col>
|
||||
<a-col>
|
||||
<a-button type="primary" @click="search">검색</a-button>
|
||||
</a-col>
|
||||
</a-row>
|
||||
</a-card>
|
||||
|
||||
<a-card>
|
||||
<a-space direction="vertical" class="w-full">
|
||||
<a-flex justify="space-between">
|
||||
<!-- <common-permit-button api="/api/admin/contents/deleteContents">-->
|
||||
<!-- <a-button type="primary" danger @click="deleteContentList"-->
|
||||
<!-- >삭제-->
|
||||
<!-- </a-button>-->
|
||||
<!-- </common-permit-button>-->
|
||||
<!-- <common-permit-button api="/api/admin/contents/updateContents">-->
|
||||
<!-- <a-button type="primary" @click="editPage">추가</a-button>-->
|
||||
<!-- </common-permit-button>-->
|
||||
</a-flex>
|
||||
<data-grid
|
||||
:key="`board-content-grid-${Math.random()}`"
|
||||
:row-headers="gridRowHeaders"
|
||||
:data="contentsList.content"
|
||||
:columns="columns"
|
||||
ref="gridRef"
|
||||
/>
|
||||
<data-pagination
|
||||
:key="`pagination-${Math.random()}`"
|
||||
:total-elements="contentsList.totalElements"
|
||||
:show-pagination-count="10"
|
||||
:size="contentsQuery.size"
|
||||
:page="contentsQuery.page"
|
||||
@change="movePage"
|
||||
/>
|
||||
</a-space>
|
||||
</a-card>
|
||||
</a-space>
|
||||
</client-only>
|
||||
</template>
|
||||
|
|
@ -0,0 +1,140 @@
|
|||
import type {
|
||||
ContentListQueryType,
|
||||
ContentListType,
|
||||
ContentType
|
||||
} from '~/types/contents';
|
||||
import { cloneDeep } from 'lodash-es';
|
||||
import type { Page } from '~/types/common';
|
||||
import { defineStore } from 'pinia';
|
||||
import { ref } from 'vue';
|
||||
import { useAxios } from '~/composables/useAxios';
|
||||
import { message } from 'ant-design-vue';
|
||||
|
||||
const DEFAULT_CONTENT_QUERY: ContentListQueryType = {
|
||||
keyword: '',
|
||||
orgId: '',
|
||||
page: 1,
|
||||
siteId: '',
|
||||
size: 10,
|
||||
type: ''
|
||||
};
|
||||
|
||||
const DEFAULT_CONTENTS_LIST: Page<ContentListType> = {
|
||||
content: [],
|
||||
totalElements: 0,
|
||||
totalPages: 0
|
||||
};
|
||||
|
||||
const DEFAULT_CONTENTS: ContentType = {
|
||||
contentTitle: '',
|
||||
contentPlain: '',
|
||||
contents: '',
|
||||
frstRegDt: '',
|
||||
frstRgtrId: '',
|
||||
lastMdfcnDt: '',
|
||||
lastMdfrId: '',
|
||||
orgId: '',
|
||||
siteId: '',
|
||||
useYn: true
|
||||
};
|
||||
|
||||
export const useContentStore = defineStore('useContentStore', () => {
|
||||
const contentsQuery = ref<ContentListQueryType>(
|
||||
cloneDeep(DEFAULT_CONTENT_QUERY)
|
||||
);
|
||||
|
||||
const contentsList = ref<Page<ContentListType>>(
|
||||
cloneDeep(DEFAULT_CONTENTS_LIST)
|
||||
);
|
||||
|
||||
const contents = ref<ContentType>(cloneDeep(DEFAULT_CONTENTS));
|
||||
const initialValue = ref<string>('');
|
||||
|
||||
const resetContentListQuery = () => {
|
||||
contentsQuery.value = cloneDeep(DEFAULT_CONTENT_QUERY);
|
||||
};
|
||||
|
||||
const resetContentList = () => {
|
||||
contentsList.value = cloneDeep(DEFAULT_CONTENTS_LIST);
|
||||
};
|
||||
|
||||
const resetContents = () => {
|
||||
contents.value = cloneDeep(DEFAULT_CONTENTS);
|
||||
initialValue.value = '';
|
||||
};
|
||||
|
||||
const initContentsQuery = () => {
|
||||
contentsQuery.value = cloneDeep(DEFAULT_CONTENT_QUERY);
|
||||
};
|
||||
|
||||
const searchContentList = async () => {
|
||||
try {
|
||||
const { data } = await useAxios().get(
|
||||
'/api/admin/contents/contentsList',
|
||||
{
|
||||
params: {
|
||||
...contentsQuery.value
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
contentsList.value = data;
|
||||
} catch (e) {
|
||||
message.error('사이트 리스트를 불러오는데 실패하였습니다.');
|
||||
}
|
||||
};
|
||||
|
||||
const searchContents = async (contentId: number) => {
|
||||
try {
|
||||
const { data } = await useAxios().get('/api/admin/contents/detail', {
|
||||
params: {
|
||||
contentId
|
||||
}
|
||||
});
|
||||
|
||||
contents.value = data;
|
||||
initialValue.value = data.contents;
|
||||
} catch (e) {
|
||||
message.error('컨텐츠 정보를 불러오는데 실패하였습니다.');
|
||||
}
|
||||
};
|
||||
|
||||
const updateContents = () => {
|
||||
console.log(contents.value);
|
||||
return useAxios().post(
|
||||
'/api/admin/contents/updateContents',
|
||||
contents.value
|
||||
);
|
||||
};
|
||||
|
||||
const deleteContents = async (data: ContentType[]) => {
|
||||
try {
|
||||
const params = new URLSearchParams();
|
||||
data.forEach((item) => {
|
||||
params.append('contentId', String(item.contentId));
|
||||
});
|
||||
|
||||
await useAxios().post('/api/admin/contents/deleteContents', null, {
|
||||
params
|
||||
});
|
||||
message.success('컨텐츠 정보가 삭제 되었습니다.');
|
||||
} catch (e) {
|
||||
message.error('컨텐츠 정보 삭제에 실패하였습니다.');
|
||||
}
|
||||
};
|
||||
|
||||
return {
|
||||
contentsQuery,
|
||||
contentsList,
|
||||
contents,
|
||||
initialValue,
|
||||
resetContentListQuery,
|
||||
searchContentList,
|
||||
searchContents,
|
||||
resetContentList,
|
||||
resetContents,
|
||||
initContentsQuery,
|
||||
deleteContents,
|
||||
updateContents
|
||||
};
|
||||
});
|
||||
|
|
@ -0,0 +1,99 @@
|
|||
import type { SiteType } from '~/types/sys/site';
|
||||
import type { GridCodeType } from '~/types';
|
||||
import { defineStore } from 'pinia';
|
||||
import { computed, ref } from 'vue';
|
||||
import { useAxios } from '~/composables/useAxios';
|
||||
|
||||
export const useLoadingStore = defineStore('useLoadingStore', () => {
|
||||
const loadCount = ref<number>(0);
|
||||
|
||||
const incrementLoadCount = () => {
|
||||
loadCount.value++;
|
||||
};
|
||||
|
||||
const decrementLoadCount = () => {
|
||||
loadCount.value--;
|
||||
};
|
||||
|
||||
const resetLoadCount = () => {
|
||||
loadCount.value = 0;
|
||||
};
|
||||
|
||||
const isLoading = computed(() => {
|
||||
return loadCount.value > 0;
|
||||
});
|
||||
|
||||
return {
|
||||
isLoading,
|
||||
resetLoadCount,
|
||||
incrementLoadCount,
|
||||
decrementLoadCount
|
||||
};
|
||||
});
|
||||
|
||||
export const useDefaultStore = defineStore('useDefaultStore', () => {
|
||||
const siteInfo = ref<SiteType>({
|
||||
siteId: '',
|
||||
siteName: '',
|
||||
siteDescription: '',
|
||||
siteDomain: '',
|
||||
siteType: '',
|
||||
sitePrefix: '',
|
||||
siteLocale: '',
|
||||
siteLogo: '',
|
||||
bscUrl: '',
|
||||
lgnUrl: '',
|
||||
delYn: false,
|
||||
useYn: true,
|
||||
frstRgtrId: '',
|
||||
frstRegDt: '',
|
||||
lastMdfrId: '',
|
||||
lastMdfcnDt: ''
|
||||
});
|
||||
|
||||
const fetchSiteInfo = async () => {
|
||||
const { data } = await useAxios().get<SiteType>('/api/admin/siteInfo');
|
||||
siteInfo.value = data;
|
||||
};
|
||||
|
||||
return { siteInfo, fetchSiteInfo };
|
||||
});
|
||||
|
||||
export const useCommonCodeStore = defineStore('useCommonCodeStore', () => {
|
||||
const searchCommonCodeList = async (
|
||||
codeGroupId: string
|
||||
): Promise<GridCodeType[]> => {
|
||||
const { data } = await useAxios().get('/api/admin/code/codeList', {
|
||||
params: {
|
||||
codeGroupId
|
||||
}
|
||||
});
|
||||
return data;
|
||||
};
|
||||
|
||||
const searchSiteCodeList = async (): Promise<GridCodeType[]> => {
|
||||
const { data } = await useAxios().get('/api/admin/code/siteList');
|
||||
return data;
|
||||
};
|
||||
|
||||
const searchInstCodeList = async (): Promise<GridCodeType[]> => {
|
||||
const { data } = await useAxios().get<GridCodeType[]>(
|
||||
'/api/admin/code/instList'
|
||||
);
|
||||
return data;
|
||||
};
|
||||
|
||||
const searchRoleCodeList = async (): Promise<GridCodeType[]> => {
|
||||
const { data } = await useAxios().get<GridCodeType[]>(
|
||||
'/api/admin/code/roleList'
|
||||
);
|
||||
return data;
|
||||
};
|
||||
|
||||
return {
|
||||
searchInstCodeList,
|
||||
searchSiteCodeList,
|
||||
searchCommonCodeList,
|
||||
searchRoleCodeList
|
||||
};
|
||||
});
|
||||
|
|
@ -0,0 +1,10 @@
|
|||
export type Page<T> = {
|
||||
content: T[];
|
||||
totalElements: number;
|
||||
totalPages: number;
|
||||
};
|
||||
|
||||
export type PagingQuery = {
|
||||
page: number;
|
||||
size: number;
|
||||
};
|
||||
|
|
@ -0,0 +1,32 @@
|
|||
export type ContentListQueryType = {
|
||||
page: number;
|
||||
size: number;
|
||||
siteId: string;
|
||||
orgId: string;
|
||||
keyword: string;
|
||||
type: '' | 'TITLE' | 'CONTENT';
|
||||
};
|
||||
|
||||
export type ContentListType = {
|
||||
contentId: number;
|
||||
contentTitle: string;
|
||||
useYn: boolean;
|
||||
frstRgtrId: string;
|
||||
frstRegDt: string;
|
||||
lastMdfrId: string;
|
||||
lastMdfcnDt: string;
|
||||
};
|
||||
|
||||
export type ContentType = {
|
||||
contentId?: number;
|
||||
siteId: string;
|
||||
orgId: string;
|
||||
contentTitle: string;
|
||||
contents: string;
|
||||
contentPlain: string;
|
||||
useYn: boolean;
|
||||
frstRgtrId: string;
|
||||
frstRegDt: string;
|
||||
lastMdfrId: string;
|
||||
lastMdfcnDt: string;
|
||||
};
|
||||
|
|
@ -0,0 +1,20 @@
|
|||
export type PagingQueryType = {
|
||||
page: number;
|
||||
size: number;
|
||||
};
|
||||
|
||||
export type GridCodeType = {
|
||||
label?: string;
|
||||
text?: string;
|
||||
value: string;
|
||||
};
|
||||
|
||||
export type FileInfoType = {
|
||||
fileId: number;
|
||||
fileOriginalName: string;
|
||||
filePath: string;
|
||||
fileMimeType: string;
|
||||
fileSize: number;
|
||||
fileAltText: string;
|
||||
fileDownloadCount: number;
|
||||
};
|
||||
Loading…
Reference in New Issue