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: []
 }