From d5eace6a05b2a68a7c9c8e51fe5cb0d490972012 Mon Sep 17 00:00:00 2001 From: Ryan Wang <i@ryanc.cc> Date: Sat, 22 Jan 2022 22:33:25 +0800 Subject: [PATCH] refactor: action logs ui (#419) Signed-off-by: Ryan Wang <i@ryanc.cc> --- src/config/router.config.js | 7 + src/core/constant.js | 58 ++++++ src/styles/tailwind.css | 4 +- src/views/dashboard/Dashboard.vue | 102 ++-------- .../dashboard/components/LogListDrawer.vue | 187 ------------------ src/views/system/ActionLogs.vue | 171 ++++++++++++++++ tailwind.config.js | 3 + 7 files changed, 255 insertions(+), 277 deletions(-) create mode 100644 src/core/constant.js delete mode 100644 src/views/dashboard/components/LogListDrawer.vue create mode 100644 src/views/system/ActionLogs.vue diff --git a/src/config/router.config.js b/src/config/router.config.js index 5d7a8b14..f94d0c14 100644 --- a/src/config/router.config.js +++ b/src/config/router.config.js @@ -205,6 +205,13 @@ export const asyncRouterMap = [ component: () => import('@/views/system/ToolList'), meta: { title: '小工具', hiddenHeaderContent: false } }, + { + path: '/system/actionlogs', + name: 'SystemActionLogs', + hidden: true, + component: () => import('@/views/system/ActionLogs'), + meta: { title: '操作日志', hiddenHeaderContent: false } + }, { path: '/system/about', name: 'About', diff --git a/src/core/constant.js b/src/core/constant.js new file mode 100644 index 00000000..aa3273a9 --- /dev/null +++ b/src/core/constant.js @@ -0,0 +1,58 @@ +export const actionLogTypes = { + BLOG_INITIALIZED: { + value: 0, + text: '博客初始化' + }, + POST_PUBLISHED: { + value: 5, + text: '文章发布' + }, + POST_EDITED: { + value: 15, + text: '文章修改' + }, + POST_DELETED: { + value: 20, + text: '文章删除' + }, + LOGGED_IN: { + value: 25, + text: '用户登录' + }, + LOGGED_OUT: { + value: 30, + text: '注销登录' + }, + LOGIN_FAILED: { + value: 35, + text: '登录失败' + }, + PASSWORD_UPDATED: { + value: 40, + text: '修改密码' + }, + PROFILE_UPDATED: { + value: 45, + text: '资料修改' + }, + SHEET_PUBLISHED: { + value: 50, + text: '页面发布' + }, + SHEET_EDITED: { + value: 55, + text: '页面修改' + }, + SHEET_DELETED: { + value: 60, + text: '页面删除' + }, + MFA_UPDATED: { + value: 65, + text: '两步验证' + }, + LOGGED_PRE_CHECK: { + value: 70, + text: '登录验证' + } +} diff --git a/src/styles/tailwind.css b/src/styles/tailwind.css index 3db5b698..b5c61c95 100644 --- a/src/styles/tailwind.css +++ b/src/styles/tailwind.css @@ -1 +1,3 @@ -@tailwind utilities; \ No newline at end of file +@tailwind base; +@tailwind components; +@tailwind utilities; diff --git a/src/views/dashboard/Dashboard.vue b/src/views/dashboard/Dashboard.vue index 9789a3ce..cf222031 100644 --- a/src/views/dashboard/Dashboard.vue +++ b/src/views/dashboard/Dashboard.vue @@ -95,17 +95,17 @@ <a-col :lg="8" :md="12" :sm="24" :xl="8" :xs="24" class="mb-3"> <a-card :bodyStyle="{ padding: '16px' }" :bordered="false"> <template slot="title"> - 操作记录 + 操作日志 <a-tooltip slot="action" title="更多"> - <a href="javascript:void(0);" @click="logListDrawerVisible = true"> + <router-link :to="{ name: 'SystemActionLogs' }"> <a-icon type="ellipsis" /> - </a> + </router-link> </a-tooltip> </template> - <a-list :dataSource="formattedLogDatas" :loading="logLoading"> + <a-list :dataSource="latestLogs" :loading="logLoading"> <a-list-item :key="index" slot="renderItem" slot-scope="item, index"> <a-list-item-meta :description="item.createTime | timeAgo"> - <span slot="title">{{ item.type }}</span> + <span slot="title">{{ item.type | typeConvert }}</span> </a-list-item-meta> <ellipsis :length="35" tooltip>{{ item.content }}</ellipsis> </a-list-item> @@ -113,8 +113,6 @@ </a-card> </a-col> </a-row> - - <LogListDrawer :visible="logListDrawerVisible" @close="handleLogListClose" /> </page-view> </template> @@ -123,9 +121,9 @@ import { PageView } from '@/layouts' import AnalysisCard from './components/AnalysisCard' import JournalPublishCard from './components/JournalPublishCard' import RecentCommentTab from './components/RecentCommentTab' -import LogListDrawer from './components/LogListDrawer' import apiClient from '@/utils/api-client' +import { actionLogTypes } from '@/core/constant' export default { name: 'Dashboard', @@ -133,73 +131,13 @@ export default { PageView, AnalysisCard, JournalPublishCard, - RecentCommentTab, - LogListDrawer + RecentCommentTab }, data() { return { - logTypes: { - BLOG_INITIALIZED: { - value: 0, - text: '博客初始化' - }, - POST_PUBLISHED: { - value: 5, - text: '文章发布' - }, - POST_EDITED: { - value: 15, - text: '文章修改' - }, - POST_DELETED: { - value: 20, - text: '文章删除' - }, - LOGGED_IN: { - value: 25, - text: '用户登录' - }, - LOGGED_OUT: { - value: 30, - text: '注销登录' - }, - LOGIN_FAILED: { - value: 35, - text: '登录失败' - }, - PASSWORD_UPDATED: { - value: 40, - text: '修改密码' - }, - PROFILE_UPDATED: { - value: 45, - text: '资料修改' - }, - SHEET_PUBLISHED: { - value: 50, - text: '页面发布' - }, - SHEET_EDITED: { - value: 55, - text: '页面修改' - }, - SHEET_DELETED: { - value: 60, - text: '页面删除' - }, - MFA_UPDATED: { - value: 65, - text: '两步验证' - }, - LOGGED_PRE_CHECK: { - value: 70, - text: '登录验证' - } - }, activityLoading: false, logLoading: false, statisticsLoading: true, - logListDrawerVisible: false, latestPosts: [], latestLogs: [], statisticsData: {}, @@ -214,19 +152,6 @@ export default { this.handleListLatestPosts() this.handleListLatestLogs() }, - computed: { - formattedLogDatas() { - return this.latestLogs.map(log => { - log.type = this.logTypes[log.type].text - return log - }) - } - }, - destroyed: function () { - if (this.logListDrawerVisible) { - this.logListDrawerVisible = false - } - }, beforeRouteEnter(to, from, next) { next(vm => { vm.interval = setInterval(() => { @@ -240,9 +165,6 @@ export default { this.interval = null this.$log.debug('Cleared interval') } - if (this.logListDrawerVisible) { - this.logListDrawerVisible = false - } next() }, methods: { @@ -285,10 +207,12 @@ export default { apiClient.post.getPreviewLinkById(postId).then(response => { window.open(response.data, '_blank') }) - }, - handleLogListClose() { - this.logListDrawerVisible = false - this.handleListLatestLogs() + } + }, + filters: { + typeConvert(key) { + const type = actionLogTypes[key] + return type ? type.text : key } } } diff --git a/src/views/dashboard/components/LogListDrawer.vue b/src/views/dashboard/components/LogListDrawer.vue deleted file mode 100644 index b01c421a..00000000 --- a/src/views/dashboard/components/LogListDrawer.vue +++ /dev/null @@ -1,187 +0,0 @@ -<template> - <div> - <a-drawer - :afterVisibleChange="handleAfterVisibleChanged" - :visible="visible" - :width="isMobile() ? '100%' : '480'" - closable - destroyOnClose - title="操作日志" - @close="onClose" - > - <a-row align="middle" type="flex"> - <a-col :span="24"> - <a-list :dataSource="formattedLogsDatas" :loading="loading"> - <a-list-item :key="index" slot="renderItem" slot-scope="item, index"> - <a-list-item-meta :description="item.createTime | timeAgo"> - <span slot="title">{{ item.type }}</span> - </a-list-item-meta> - <ellipsis :length="35" tooltip>{{ item.content }}</ellipsis> - </a-list-item> - </a-list> - - <div class="page-wrapper"> - <a-pagination - :current="pagination.page" - :defaultPageSize="pagination.size" - :pageSizeOptions="['50', '100', '150', '200']" - :total="pagination.total" - class="pagination" - showLessItems - showSizeChanger - @change="handlePaginationChange" - @showSizeChange="handlePaginationChange" - /> - </div> - </a-col> - </a-row> - <a-divider class="divider-transparent" /> - <div class="bottom-control"> - <a-popconfirm cancelText="取消" okText="确定" title="你确定要清空所有操作日志?" @confirm="handleClearLogs"> - <a-button type="danger">清空操作日志</a-button> - </a-popconfirm> - </div> - </a-drawer> - </div> -</template> - -<script> -import { mixin, mixinDevice } from '@/mixins/mixin.js' -import apiClient from '@/utils/api-client' - -export default { - name: 'LogListDrawer', - mixins: [mixin, mixinDevice], - data() { - return { - logTypes: { - BLOG_INITIALIZED: { - value: 0, - text: '博客初始化' - }, - POST_PUBLISHED: { - value: 5, - text: '文章发布' - }, - POST_EDITED: { - value: 15, - text: '文章修改' - }, - POST_DELETED: { - value: 20, - text: '文章删除' - }, - LOGGED_IN: { - value: 25, - text: '用户登录' - }, - LOGGED_OUT: { - value: 30, - text: '注销登录' - }, - LOGIN_FAILED: { - value: 35, - text: '登录失败' - }, - PASSWORD_UPDATED: { - value: 40, - text: '修改密码' - }, - PROFILE_UPDATED: { - value: 45, - text: '资料修改' - }, - SHEET_PUBLISHED: { - value: 50, - text: '页面发布' - }, - SHEET_EDITED: { - value: 55, - text: '页面修改' - }, - SHEET_DELETED: { - value: 60, - text: '页面删除' - }, - MFA_UPDATED: { - value: 65, - text: '两步验证' - }, - LOGGED_PRE_CHECK: { - value: 70, - text: '登录验证' - } - }, - loading: true, - logs: [], - pagination: { - page: 1, - size: 50, - sort: null, - total: 1 - }, - logQueryParam: { - page: 0, - size: 50, - sort: null - } - } - }, - props: { - visible: { - type: Boolean, - required: false, - default: false - } - }, - computed: { - formattedLogsDatas() { - return this.logs.map(log => { - log.type = this.logTypes[log.type].text - return log - }) - } - }, - methods: { - handleListLogs() { - this.loading = true - this.logQueryParam.page = this.pagination.page - 1 - this.logQueryParam.size = this.pagination.size - this.logQueryParam.sort = this.pagination.sort - apiClient.log - .list(this.logQueryParam) - .then(response => { - this.logs = response.data.content - this.pagination.total = response.data.total - }) - .finally(() => { - this.loading = false - }) - }, - handleClearLogs() { - apiClient.log - .clear() - .then(() => { - this.$message.success('清除成功!') - }) - .finally(() => { - this.handleListLogs() - }) - }, - handlePaginationChange(page, pageSize) { - this.$log.debug(`Current: ${page}, PageSize: ${pageSize}`) - this.pagination.page = page - this.pagination.size = pageSize - this.handleListLogs() - }, - onClose() { - this.$emit('close', false) - }, - handleAfterVisibleChanged(visible) { - if (visible) { - this.handleListLogs() - } - } - } -} -</script> diff --git a/src/views/system/ActionLogs.vue b/src/views/system/ActionLogs.vue new file mode 100644 index 00000000..05b72cbd --- /dev/null +++ b/src/views/system/ActionLogs.vue @@ -0,0 +1,171 @@ +<template> + <page-view> + <a-card :bodyStyle="{ padding: '16px' }" :bordered="false"> + <div class="table-operator"> + <a-button type="danger" @click="handleClearActionLogs">清空操作日志</a-button> + </div> + <div class="mt-4"> + <a-table + :columns="list.columns" + :dataSource="list.data" + :loading="list.loading" + :pagination="false" + :rowKey="log => log.id" + :scrollToFirstRowOnChange="true" + > + <template #type="type"> + {{ type | typeConvert }} + </template> + <template #ipAddress="ipAddress"> + <div class="blur hover:blur-none transition-all">{{ ipAddress }}</div> + </template> + <template #createTime="createTime"> + <a-tooltip placement="top"> + <template slot="title"> + {{ createTime | moment }} + </template> + {{ createTime | timeAgo }} + </a-tooltip> + </template> + </a-table> + <div class="page-wrapper"> + <a-pagination + :current="pagination.page" + :defaultPageSize="pagination.size" + :pageSizeOptions="['10', '20', '50', '100']" + :total="pagination.total" + class="pagination" + showLessItems + showSizeChanger + @change="handlePageChange" + @showSizeChange="handlePageSizeChange" + /> + </div> + </div> + </a-card> + </page-view> +</template> + +<script> +// components +import { PageView } from '@/layouts' + +// libs +import apiClient from '@/utils/api-client' +import { actionLogTypes } from '@/core/constant' + +const columns = [ + { + title: 'ID', + dataIndex: 'id' + }, + { + title: '类型', + dataIndex: 'type', + scopedSlots: { customRender: 'type' } + }, + { + title: '关键值', + dataIndex: 'logKey' + }, + { + title: '内容', + dataIndex: 'content' + }, + { + title: 'IP', + dataIndex: 'ipAddress', + scopedSlots: { customRender: 'ipAddress' } + }, + { + title: '操作时间', + dataIndex: 'createTime', + scopedSlots: { customRender: 'createTime' } + } +] + +export default { + name: 'ActionLog', + components: { + PageView + }, + data() { + return { + list: { + columns, + data: [], + total: 0, + loading: false, + params: { + page: 0, + size: 50 + } + } + } + }, + computed: { + pagination() { + return { + page: this.list.params.page + 1, + size: this.list.params.size, + total: this.list.total + } + } + }, + created() { + this.handleListActionLogs() + }, + methods: { + async handleListActionLogs() { + try { + this.list.loading = true + + const response = await apiClient.log.list(this.list.params) + + this.list.data = response.data.content + this.list.total = response.data.total + } catch (error) { + this.$log.error(error) + } finally { + this.list.loading = false + } + }, + + handlePageChange(page = 1) { + this.list.params.page = page - 1 + this.handleListActionLogs() + }, + + handlePageSizeChange(current, size) { + this.$log.debug(`Current: ${current}, PageSize: ${size}`) + this.list.params.page = 0 + this.list.params.size = size + this.handleListActionLogs() + }, + + handleClearActionLogs() { + const _this = this + _this.$confirm({ + title: '提示', + maskClosable: true, + content: '是否确定要清空所有操作日志?', + async onOk() { + try { + await apiClient.log.clear() + } catch (e) { + _this.$log.error('Failed to clear action logs.', e) + } finally { + await _this.handleListActionLogs() + } + } + }) + } + }, + filters: { + typeConvert(key) { + const type = actionLogTypes[key] + return type ? type.text : key + } + } +} +</script> diff --git a/tailwind.config.js b/tailwind.config.js index b3dc7184..0875e1e4 100644 --- a/tailwind.config.js +++ b/tailwind.config.js @@ -3,5 +3,8 @@ module.exports = { theme: { extend: {} }, + corePlugins: { + preflight: false + }, plugins: [] }