From 838592253a98d3fef281e84b0d3ab7ddff6b8e2c Mon Sep 17 00:00:00 2001
From: dap1 <15891557205@163.com>
Date: Sat, 10 Jun 2023 13:04:32 +0800
Subject: [PATCH 1/5] =?UTF-8?q?feat:=20=E6=93=8D=E4=BD=9C=E6=97=A5?=
 =?UTF-8?q?=E6=9C=9F=E8=AF=A6=E6=83=85=E6=9F=A5=E7=9C=8B?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

---
 src/components/Table/src/hooks/useRender.ts   |  20 +++
 src/views/system/operatelog/LogInfoModal.vue  |  27 ++++
 src/views/system/operatelog/index.vue         |  31 ++++-
 .../system/operatelog/operateLog.data.ts      | 116 ++++++++++++++++--
 4 files changed, 181 insertions(+), 13 deletions(-)
 create mode 100644 src/views/system/operatelog/LogInfoModal.vue

diff --git a/src/components/Table/src/hooks/useRender.ts b/src/components/Table/src/hooks/useRender.ts
index a7e4eb1a..af1bc4a4 100644
--- a/src/components/Table/src/hooks/useRender.ts
+++ b/src/components/Table/src/hooks/useRender.ts
@@ -5,6 +5,7 @@ import { isArray, isString } from '@/utils/is'
 import { DictTag } from '@/components/DictTag'
 import { Icon } from '@/components/Icon'
 import TableImg from '../components/TableImg.vue'
+import { JsonPreview } from '@/components/CodeEditor'
 
 export const useRender = {
   /**
@@ -112,5 +113,24 @@ export const useRender = {
     if (text) {
       return h(Icon, { icon: text })
     }
+  },
+  /**
+   * 使用JsonPreview组件  方便预览JSON
+   * @param json json字符串/obj
+   * @returns 能转为json返回JsonPreview 否则返回自身
+   */
+  renderJsonPreview: (json: any) => {
+    if (!json) return ''
+    if (typeof json === 'object') {
+      return h(JsonPreview, { data: json })
+    }
+    if (typeof json === 'string') {
+      try {
+        const data = JSON.parse(json)
+        return h(JsonPreview, { data })
+      } catch (e) {
+        return json
+      }
+    }
   }
 }
diff --git a/src/views/system/operatelog/LogInfoModal.vue b/src/views/system/operatelog/LogInfoModal.vue
new file mode 100644
index 00000000..63d1971b
--- /dev/null
+++ b/src/views/system/operatelog/LogInfoModal.vue
@@ -0,0 +1,27 @@
+<template>
+  <BasicModal v-bind="$attrs" title="操作日志详情" @register="registerModalInner">
+    <Description @register="registerDescription" />
+  </BasicModal>
+</template>
+
+<script setup lang="ts">
+import { BasicModal, useModalInner } from '@/components/Modal'
+import { Description, useDescription } from '@/components/Description/index'
+import { ref } from 'vue'
+import { infoSchema } from './operateLog.data'
+
+defineOptions({ name: 'OperLogInfoModal' })
+
+const logData = ref()
+const [registerModalInner] = useModalInner((record: Recordable) => {
+  logData.value = record
+})
+
+const [registerDescription] = useDescription({
+  column: 1,
+  schema: infoSchema,
+  data: logData
+})
+</script>
+
+<style scoped></style>
diff --git a/src/views/system/operatelog/index.vue b/src/views/system/operatelog/index.vue
index c5f28a83..93fbd163 100644
--- a/src/views/system/operatelog/index.vue
+++ b/src/views/system/operatelog/index.vue
@@ -4,16 +4,32 @@
       <template #toolbar>
         <a-button type="warning" :preIcon="IconEnum.EXPORT" @click="handleExport"> {{ t('action.export') }} </a-button>
       </template>
+      <template #bodyCell="{ column, record }">
+        <template v-if="column.key === 'action'">
+          <TableAction
+            :actions="[
+              {
+                icon: IconEnum.VIEW,
+                label: t('action.detail'),
+                onClick: handleShowInfo.bind(null, record)
+              }
+            ]"
+          />
+        </template>
+      </template>
     </BasicTable>
+    <OperLogInfoModal @register="registerModal" />
   </div>
 </template>
 <script lang="ts" setup>
 import { useI18n } from '@/hooks/web/useI18n'
 import { useMessage } from '@/hooks/web/useMessage'
 import { IconEnum } from '@/enums/appEnum'
-import { BasicTable, useTable } from '@/components/Table'
+import { BasicTable, useTable, TableAction } from '@/components/Table'
 import { OperateLogPageReqVO, exportOperateLog, getOperateLogPage } from '@/api/system/operatelog'
 import { columns, searchFormSchema } from './operateLog.data'
+import { useModal } from '@/components/Modal'
+import OperLogInfoModal from './LogInfoModal.vue'
 
 defineOptions({ name: 'SystemOperateLog' })
 
@@ -26,7 +42,13 @@ const [registerTable, { getForm }] = useTable({
   formConfig: { labelWidth: 120, schemas: searchFormSchema },
   useSearchForm: true,
   showTableSetting: true,
-  showIndexColumn: false
+  showIndexColumn: false,
+  actionColumn: {
+    width: 140,
+    title: t('common.action'),
+    dataIndex: 'action',
+    fixed: 'right'
+  }
 })
 
 async function handleExport() {
@@ -40,4 +62,9 @@ async function handleExport() {
     }
   })
 }
+
+const [registerModal, { openModal }] = useModal()
+function handleShowInfo(record: Recordable) {
+  openModal(true, record)
+}
 </script>
diff --git a/src/views/system/operatelog/operateLog.data.ts b/src/views/system/operatelog/operateLog.data.ts
index 8e0473c4..d509b9e3 100644
--- a/src/views/system/operatelog/operateLog.data.ts
+++ b/src/views/system/operatelog/operateLog.data.ts
@@ -1,5 +1,6 @@
 import { BasicColumn, FormSchema, useRender } from '@/components/Table'
 import { DICT_TYPE, getDictOptions } from '@/utils/dict'
+import { DescItem } from '@/components/Description/index'
 
 export const columns: BasicColumn[] = [
   {
@@ -10,7 +11,7 @@ export const columns: BasicColumn[] = [
   {
     title: '操作模块',
     dataIndex: 'module',
-    width: 120
+    width: 200
   },
   {
     title: '操作名',
@@ -20,7 +21,7 @@ export const columns: BasicColumn[] = [
   {
     title: '操作类型',
     dataIndex: 'type',
-    width: 180,
+    width: 120,
     customRender: ({ text }) => {
       return useRender.renderDict(text, DICT_TYPE.SYSTEM_OPERATE_TYPE)
     }
@@ -30,10 +31,14 @@ export const columns: BasicColumn[] = [
     dataIndex: 'userNickname',
     width: 120
   },
+  // {
+  //   title: 'userAgent',
+  //   dataIndex: 'userAgent',
+  //   width: 400
+  // },
   {
-    title: 'userAgent',
-    dataIndex: 'userAgent',
-    width: 400
+    title: '请求路径',
+    dataIndex: 'requestUrl'
   },
   {
     title: '操作结果',
@@ -44,19 +49,19 @@ export const columns: BasicColumn[] = [
     }
   },
   {
-    title: '操作日期',
-    dataIndex: 'startTime',
+    title: '执行时长',
+    dataIndex: 'duration',
     width: 180,
     customRender: ({ text }) => {
-      return useRender.renderDate(text)
+      return useRender.renderText(text, 'ms')
     }
   },
   {
-    title: '执行时长',
-    dataIndex: 'duration',
+    title: '操作日期',
+    dataIndex: 'startTime',
     width: 180,
     customRender: ({ text }) => {
-      return useRender.renderText(text, 'ms')
+      return useRender.renderDate(text)
     }
   }
 ]
@@ -102,3 +107,92 @@ export const searchFormSchema: FormSchema[] = [
     colProps: { span: 8 }
   }
 ]
+
+const httpMethods = [
+  { value: 'GET', color: '#108ee9' },
+  { value: 'POST', color: '#2db7f5' },
+  { value: 'PUT', color: 'warning' },
+  { value: 'DELETE', color: '#f50' }
+]
+
+export const infoSchema: DescItem[] = [
+  {
+    field: 'module',
+    label: '操作模块'
+  },
+  {
+    field: 'name',
+    label: '操作名'
+  },
+  {
+    field: 'userNickname',
+    label: '操作人',
+    render(_, data) {
+      const { userNickname, userId } = data
+      return useRender.renderText(userNickname, 'uid: ' + userId)
+    }
+  },
+  {
+    field: 'resultCode',
+    label: '请求结果',
+    render(value) {
+      return useRender.renderTag(value === 0 ? '成功' : '失败', value === 0 ? '#87d068' : '#f50')
+    }
+  },
+  {
+    field: 'resultMsg',
+    label: '响应信息',
+    show(data) {
+      return data && data.resultMsg && data.resultMsg !== ''
+    }
+  },
+  {
+    field: 'userIp',
+    label: '请求ip'
+  },
+  {
+    field: 'startTime',
+    label: '请求时间',
+    render(value) {
+      return useRender.renderDate(value)
+    }
+  },
+  {
+    field: 'requestUrl',
+    label: '请求路径'
+  },
+  {
+    field: 'requestMethod',
+    label: '请求方法',
+    render(value) {
+      const current = httpMethods.find((item) => item.value === value.toUpperCase())
+      if (current) {
+        return useRender.renderTag(value, current.color)
+      }
+      return value
+    }
+  },
+  {
+    field: 'javaMethod',
+    label: '操作方法',
+    labelMinWidth: 80
+  },
+  {
+    field: 'javaMethodArgs',
+    label: '请求参数',
+    render(value) {
+      return useRender.renderJsonPreview(value)
+    }
+  },
+  {
+    field: 'userAgent',
+    label: 'userAgent'
+  },
+  {
+    field: 'duration',
+    label: '请求耗时',
+    render(value) {
+      return useRender.renderText(value, 'ms')
+    }
+  }
+]

From 02d0c92e8993f2f0c2bee36fb2f1914b6410f7f8 Mon Sep 17 00:00:00 2001
From: dap1 <15891557205@163.com>
Date: Sat, 10 Jun 2023 13:47:57 +0800
Subject: [PATCH 2/5] =?UTF-8?q?feat:=20=E5=AD=97=E5=85=B8=E6=A0=87?=
 =?UTF-8?q?=E7=AD=BE=E4=BF=AE=E6=94=B9=E6=94=AF=E6=8C=81=E9=A2=84=E8=A7=88?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

---
 src/views/system/dict/dict.data.ts | 126 ++++++++++++++++-------------
 1 file changed, 71 insertions(+), 55 deletions(-)

diff --git a/src/views/system/dict/dict.data.ts b/src/views/system/dict/dict.data.ts
index b1173b99..1d21ed9e 100644
--- a/src/views/system/dict/dict.data.ts
+++ b/src/views/system/dict/dict.data.ts
@@ -1,6 +1,74 @@
 import { BasicColumn, FormSchema, useRender } from '@/components/Table'
 import { DICT_TYPE, getDictOptions } from '@/utils/dict'
 
+const options = [
+  {
+    value: '',
+    label: '无'
+  },
+  {
+    value: 'processing',
+    label: '主要'
+  },
+  {
+    value: 'success',
+    label: '成功'
+  },
+  {
+    value: 'default',
+    label: '默认'
+  },
+  {
+    value: 'warning',
+    label: '警告'
+  },
+  {
+    value: 'error',
+    label: '危险'
+  },
+  {
+    value: 'pink',
+    label: 'pink'
+  },
+  {
+    value: 'red',
+    label: 'red'
+  },
+  {
+    value: 'orange',
+    label: 'orange'
+  },
+  {
+    value: 'green',
+    label: 'green'
+  },
+  {
+    value: 'cyan',
+    label: 'cyan'
+  },
+  {
+    value: 'blue',
+    label: 'blue'
+  },
+  {
+    value: 'purple',
+    label: 'purple'
+  }
+]
+
+function previewOptions() {
+  return options.map((option) => {
+    const { value, label } = option
+    if (value === '') {
+      return option
+    }
+    return {
+      label: useRender.renderTag(label, value),
+      value
+    }
+  })
+}
+
 export const dataColumns: BasicColumn[] = [
   {
     title: '字典编码',
@@ -119,67 +187,15 @@ export const dataFormSchema: FormSchema[] = [
     field: 'colorType',
     component: 'Select',
     componentProps: {
-      options: [
-        {
-          value: '',
-          label: '空'
-        },
-        {
-          value: 'processing',
-          label: '主要'
-        },
-        {
-          value: 'success',
-          label: '成功'
-        },
-        {
-          value: 'default',
-          label: '默认'
-        },
-        {
-          value: 'warning',
-          label: '警告'
-        },
-        {
-          value: 'error',
-          label: '危险'
-        },
-        {
-          value: 'pink',
-          label: 'pink'
-        },
-        {
-          value: 'red',
-          label: 'red'
-        },
-        {
-          value: 'orange',
-          label: 'orange'
-        },
-        {
-          value: 'green',
-          label: 'green'
-        },
-        {
-          value: 'cyan',
-          label: 'cyan'
-        },
-        {
-          value: 'blue',
-          label: 'blue'
-        },
-        {
-          value: 'purple',
-          label: 'purple'
-        }
-      ]
+      options: previewOptions()
     }
   },
   {
     label: 'CSS Class',
     field: 'cssClass',
     component: 'Input',
-    helpMessage: '输入hex模式的颜色,例如#108ee9'
+    helpMessage: '输入hex模式的颜色, 例如#108ee9',
+    rules: [{ required: false, message: '输入正确的16进制颜色', pattern: /^#([0-9a-fA-F]{3}){1,2}$/, trigger: 'blur' }]
   },
   {
     label: '备注',

From dae144ccf9ad27f7599a97463924562d8cbf5cf6 Mon Sep 17 00:00:00 2001
From: dap1 <15891557205@163.com>
Date: Sat, 10 Jun 2023 20:03:00 +0800
Subject: [PATCH 3/5] =?UTF-8?q?feat:=20=E4=B8=80=E4=BA=9B=E6=97=A5?=
 =?UTF-8?q?=E5=BF=97=E7=9A=84=E8=AF=A6=E6=83=85=E6=9F=A5=E7=9C=8B?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

---
 .../infra/apiAccessLog/AccessLogModal.vue     |  27 ++++
 .../infra/apiAccessLog/apiAccessLog.data.ts   | 104 ++++++++++++-
 src/views/infra/apiAccessLog/index.vue        |  31 +++-
 src/views/infra/apiErrorLog/ErrorLogModal.vue |  27 ++++
 .../infra/apiErrorLog/apiErrorLog.data.ts     | 139 ++++++++++++++++++
 src/views/infra/apiErrorLog/index.vue         |  15 +-
 .../system/operatelog/operateLog.data.ts      |  25 ++--
 7 files changed, 353 insertions(+), 15 deletions(-)
 create mode 100644 src/views/infra/apiAccessLog/AccessLogModal.vue
 create mode 100644 src/views/infra/apiErrorLog/ErrorLogModal.vue

diff --git a/src/views/infra/apiAccessLog/AccessLogModal.vue b/src/views/infra/apiAccessLog/AccessLogModal.vue
new file mode 100644
index 00000000..45a65715
--- /dev/null
+++ b/src/views/infra/apiAccessLog/AccessLogModal.vue
@@ -0,0 +1,27 @@
+<template>
+  <BasicModal v-bind="$attrs" title="访问日志详情" @register="registerModalInner" @ok="closeModal" width="800px">
+    <Description @register="registerDescription" />
+  </BasicModal>
+</template>
+
+<script setup lang="ts">
+import { BasicModal, useModalInner } from '@/components/Modal'
+import { Description, useDescription } from '@/components/Description/index'
+import { ref } from 'vue'
+import { infoSchema } from './apiAccessLog.data'
+
+defineOptions({ name: 'AcessLogModal' })
+
+const logData = ref()
+const [registerModalInner, { closeModal }] = useModalInner((record: Recordable) => {
+  logData.value = record
+})
+
+const [registerDescription] = useDescription({
+  column: 1,
+  schema: infoSchema,
+  data: logData
+})
+</script>
+
+<style scoped></style>
diff --git a/src/views/infra/apiAccessLog/apiAccessLog.data.ts b/src/views/infra/apiAccessLog/apiAccessLog.data.ts
index 12bb65a2..eae52ff0 100644
--- a/src/views/infra/apiAccessLog/apiAccessLog.data.ts
+++ b/src/views/infra/apiAccessLog/apiAccessLog.data.ts
@@ -1,5 +1,7 @@
 import { BasicColumn, FormSchema, useRender } from '@/components/Table'
 import { DICT_TYPE, getDictOptions } from '@/utils/dict'
+import { DescItem } from '@/components/Description/index'
+import { h } from 'vue'
 
 export const columns: BasicColumn[] = [
   {
@@ -58,7 +60,7 @@ export const columns: BasicColumn[] = [
     ellipsis: true,
     customRender: ({ record }) => {
       const success = record.resultCode === 0
-      return useRender.renderTag(success ? '成功' : '失败(' + record.resultMsg + ')', success ? '#87d068' : '#f50')
+      return useRender.renderTag(success ? '成功' : '失败', success ? '#87d068' : '#f50')
     }
   }
 ]
@@ -110,3 +112,103 @@ export const searchFormSchema: FormSchema[] = [
     colProps: { span: 8 }
   }
 ]
+
+const httpMethods = [
+  { value: 'GET', color: '#108ee9' },
+  { value: 'POST', color: '#2db7f5' },
+  { value: 'PUT', color: 'warning' },
+  { value: 'DELETE', color: '#f50' }
+]
+
+export const infoSchema: DescItem[] = [
+  {
+    label: '日志id',
+    field: 'id'
+  },
+  {
+    label: '链路id',
+    field: 'traceId',
+    show: (data) => data && data.traceId && data.traceId !== ''
+  },
+  {
+    label: '应用名称',
+    field: 'applicationName',
+    labelMinWidth: 100
+  },
+  {
+    field: 'userId',
+    label: '用户id',
+    render(value, data) {
+      const tag = useRender.renderDict(data.userType, DICT_TYPE.USER_TYPE)
+      const uidTag = useRender.renderTag('uid: ' + value)
+      return h('span', {}, [tag, uidTag])
+    }
+  },
+  {
+    field: 'resultCode',
+    label: '请求结果',
+    render(value) {
+      return useRender.renderTag(value === 0 ? '成功' : '失败', value === 0 ? '#87d068' : '#f50')
+    }
+  },
+  {
+    field: 'resultMsg',
+    label: '响应信息',
+    show(data) {
+      return data && data.resultMsg && data.resultMsg !== ''
+    },
+    render(value) {
+      return h('span', { style: { color: 'red', fontWeight: 'bold' } }, value)
+    }
+  },
+  {
+    field: 'userIp',
+    label: '请求ip'
+  },
+  {
+    field: 'userAgent',
+    label: 'userAgent'
+  },
+  {
+    field: 'beginTime',
+    label: '请求时间',
+    render(value) {
+      return useRender.renderDate(value)
+    }
+  },
+  {
+    field: 'requestUrl',
+    label: '请求路径',
+    render(_, data) {
+      if (!data) {
+        return ''
+      }
+      const { requestMethod, requestUrl } = data
+      const current = httpMethods.find((item) => item.value === requestMethod.toUpperCase())
+      const methodTag = current ? useRender.renderTag(requestMethod, current.color) : requestMethod
+      return h('span', {}, [methodTag, requestUrl])
+    }
+  },
+  {
+    field: 'beginTime',
+    label: '请求开始时间',
+    render(value) {
+      return useRender.renderDate(value)
+    }
+  },
+  {
+    field: 'endTime',
+    label: '请求结束时间',
+    render(value) {
+      return useRender.renderDate(value)
+    }
+  },
+  {
+    field: 'duration',
+    label: '请求耗时',
+    render(value) {
+      // 为0的话需要转为string  否则不会显示
+      return useRender.renderText(String(value), 'ms')
+    }
+  }
+]
diff --git a/src/views/infra/apiAccessLog/index.vue b/src/views/infra/apiAccessLog/index.vue
index ce222317..dd9ee2ec 100644
--- a/src/views/infra/apiAccessLog/index.vue
+++ b/src/views/infra/apiAccessLog/index.vue
@@ -6,16 +6,32 @@
           {{ t('action.export') }}
         </a-button>
       </template>
+      <template #bodyCell="{ column, record }">
+        <template v-if="column.key === 'action'">
+          <TableAction
+            :actions="[
+              {
+                icon: IconEnum.VIEW,
+                label: t('action.detail'),
+                onClick: handleShowInfo.bind(null, record)
+              }
+            ]"
+          />
+        </template>
+      </template>
     </BasicTable>
+    <AccessLogModal @register="registerModal" />
   </div>
 </template>
 <script lang="ts" setup>
 import { useI18n } from '@/hooks/web/useI18n'
 import { useMessage } from '@/hooks/web/useMessage'
-import { BasicTable, useTable } from '@/components/Table'
+import { BasicTable, useTable, TableAction } from '@/components/Table'
 import { IconEnum } from '@/enums/appEnum'
 import { getApiAccessLogPage, exportApiAccessLog, ApiAccessLogExportReqVO } from '@/api/infra/apiAccessLog'
 import { columns, searchFormSchema } from './apiAccessLog.data'
+import { useModal } from '@/components/Modal'
+import AccessLogModal from './AccessLogModal.vue'
 
 defineOptions({ name: 'InfraApiErrorLog' })
 
@@ -28,9 +44,20 @@ const [registerTable, { getForm }] = useTable({
   formConfig: { labelWidth: 120, schemas: searchFormSchema },
   useSearchForm: true,
   showTableSetting: true,
-  showIndexColumn: false
+  showIndexColumn: false,
+  actionColumn: {
+    width: 120,
+    title: t('common.action'),
+    dataIndex: 'action',
+    fixed: 'right'
+  }
 })
 
+const [registerModal, { openModal }] = useModal()
+function handleShowInfo(record: Recordable) {
+  openModal(true, record)
+}
+
 async function handleExport() {
   createConfirm({
     title: t('common.exportTitle'),
diff --git a/src/views/infra/apiErrorLog/ErrorLogModal.vue b/src/views/infra/apiErrorLog/ErrorLogModal.vue
new file mode 100644
index 00000000..c3be60c2
--- /dev/null
+++ b/src/views/infra/apiErrorLog/ErrorLogModal.vue
@@ -0,0 +1,27 @@
+<template>
+  <BasicModal v-bind="$attrs" title="错误日志详情" @register="registerModalInner" @ok="closeModal" width="800px">
+    <Description @register="registerDescription" />
+  </BasicModal>
+</template>
+
+<script setup lang="ts">
+import { BasicModal, useModalInner } from '@/components/Modal'
+import { Description, useDescription } from '@/components/Description/index'
+import { ref } from 'vue'
+import { infoSchema } from './apiErrorLog.data'
+
+defineOptions({ name: 'ErrorLogModal' })
+
+const logData = ref()
+const [registerModalInner, { closeModal }] = useModalInner((record: Recordable) => {
+  logData.value = record
+})
+
+const [registerDescription] = useDescription({
+  column: 1,
+  schema: infoSchema,
+  data: logData
+})
+</script>
+
+<style scoped></style>
diff --git a/src/views/infra/apiErrorLog/apiErrorLog.data.ts b/src/views/infra/apiErrorLog/apiErrorLog.data.ts
index f3191681..bdc55f71 100644
--- a/src/views/infra/apiErrorLog/apiErrorLog.data.ts
+++ b/src/views/infra/apiErrorLog/apiErrorLog.data.ts
@@ -1,5 +1,8 @@
 import { BasicColumn, FormSchema, useRender } from '@/components/Table'
 import { DICT_TYPE, getDictOptions } from '@/utils/dict'
+import { Textarea } from 'ant-design-vue'
+import { h } from 'vue'
+import { DescItem } from '@/components/Description/index'
 
 export const columns: BasicColumn[] = [
   {
@@ -108,3 +111,139 @@ export const searchFormSchema: FormSchema[] = [
     colProps: { span: 8 }
   }
 ]
+
+function renderText(value: string, color: string, bold = true) {
+  return h('span', { style: { color, fontWeight: bold ? 'bold' : 'normal' } }, value)
+}
+
+const httpMethods = [
+  { value: 'GET', color: '#108ee9' },
+  { value: 'POST', color: '#2db7f5' },
+  { value: 'PUT', color: 'warning' },
+  { value: 'DELETE', color: '#f50' }
+]
+
+export const infoSchema: DescItem[] = [
+  {
+    field: 'id',
+    label: '异常id'
+  },
+  {
+    field: 'traceId',
+    label: '链路ID',
+    show(data) {
+      return data && data.traceId && data.traceId !== ''
+    }
+  },
+  {
+    field: 'applicationName',
+    label: '应用名称',
+    labelMinWidth: 100
+  },
+  {
+    field: 'processStatus',
+    label: '处理状态',
+    render(_, data) {
+      const { processStatus, processUserId } = data
+      const tag = useRender.renderDict(processStatus, DICT_TYPE.INFRA_API_ERROR_LOG_PROCESS_STATUS)
+      if (!processUserId) {
+        return tag
+      }
+      const uidTag = useRender.renderTag('uid: ' + processUserId)
+      return h('span', {}, [tag, uidTag])
+    }
+  },
+  {
+    field: 'processTime',
+    label: '处理时间',
+    show(data) {
+      return data && data.processTime && data.processTime !== ''
+    },
+    render(value) {
+      return useRender.renderDate(value)
+    }
+  },
+  {
+    field: 'userId',
+    label: '用户id',
+    render(value, data) {
+      const tag = useRender.renderDict(data.userType, DICT_TYPE.USER_TYPE)
+      const uidTag = useRender.renderTag('uid: ' + value)
+      return h('span', {}, [tag, uidTag])
+    }
+  },
+  {
+    field: 'userIp',
+    label: 'ip地址'
+  },
+  {
+    field: 'requestUrl',
+    label: '请求地址',
+    render(_, data) {
+      if (data) {
+        const { requestMethod } = data
+        const current = httpMethods.find((item) => item.value === requestMethod)
+        const tag = current ? useRender.renderTag(requestMethod, current.color) : requestMethod
+        return h('span', {}, [tag, data.requestUrl])
+      }
+    }
+  },
+  {
+    field: 'requestParams',
+    label: '请求参数',
+    render(value) {
+      return useRender.renderJsonPreview(value)
+    }
+  },
+  {
+    field: 'userAgent',
+    label: 'userAgent'
+  },
+  {
+    field: 'exceptionTime',
+    label: '异常时间',
+    render(value) {
+      return useRender.renderDate(value)
+    }
+  },
+  {
+    field: 'exceptionClassName',
+    label: '异常类名/方法',
+    render(_, data) {
+      if (data) {
+        return renderText(data.exceptionClassName + ' / ' + data.exceptionMethodName, 'red')
+      }
+    }
+  },
+  {
+    field: 'exceptionMessage',
+    label: '异常信息',
+    render(value) {
+      return renderText(value, 'red')
+    }
+  },
+  {
+    field: 'exceptionFileName',
+    label: '异常文件名',
+    render(_, data) {
+      if (data) {
+        return useRender.renderText(data.exceptionFileName, 'Line: ' + data.exceptionLineNumber)
+      }
+    }
+  },
+  {
+    field: 'exceptionName',
+    label: '异常名称'
+  },
+  {
+    field: 'exceptionRootCauseMessage',
+    label: '异常信息'
+  },
+  {
+    field: 'exceptionStackTrace',
+    label: '异常堆栈',
+    render(value) {
+      return h(Textarea, { value, readonly: true, style: { minHeight: '300px' } })
+    }
+  }
+]
diff --git a/src/views/infra/apiErrorLog/index.vue b/src/views/infra/apiErrorLog/index.vue
index b018151c..ddd53998 100644
--- a/src/views/infra/apiErrorLog/index.vue
+++ b/src/views/infra/apiErrorLog/index.vue
@@ -10,6 +10,11 @@
         <template v-if="column.key === 'action'">
           <TableAction
             :actions="[
+              {
+                icon: IconEnum.VIEW,
+                label: t('action.detail'),
+                onClick: handleShowInfo.bind(null, record)
+              },
               {
                 icon: IconEnum.EDIT,
                 label: '已处理',
@@ -29,6 +34,7 @@
         </template>
       </template>
     </BasicTable>
+    <ErrorLogModal @register="registerModal" />
   </div>
 </template>
 <script lang="ts" setup>
@@ -39,6 +45,8 @@ import { useMessage } from '@/hooks/web/useMessage'
 import { BasicTable, useTable, TableAction } from '@/components/Table'
 import { updateApiErrorLogProcess, getApiErrorLogPage, exportApiErrorLog, ApiErrorLogExportReqVO } from '@/api/infra/apiErrorLog'
 import { columns, searchFormSchema } from './apiErrorLog.data'
+import { useModal } from '@/components/Modal'
+import ErrorLogModal from './ErrorLogModal.vue'
 
 defineOptions({ name: 'InfraApiErrorLog' })
 
@@ -53,13 +61,18 @@ const [registerTable, { getForm, reload }] = useTable({
   showTableSetting: true,
   showIndexColumn: false,
   actionColumn: {
-    width: 180,
+    width: 220,
     title: t('common.action'),
     dataIndex: 'action',
     fixed: 'right'
   }
 })
 
+const [registerModal, { openModal }] = useModal()
+function handleShowInfo(record: Recordable) {
+  openModal(true, record)
+}
+
 function handleProcessClick(record, processStatus: number, type: string) {
   createConfirm({
     iconType: 'warning',
diff --git a/src/views/system/operatelog/operateLog.data.ts b/src/views/system/operatelog/operateLog.data.ts
index d509b9e3..f6bffe7e 100644
--- a/src/views/system/operatelog/operateLog.data.ts
+++ b/src/views/system/operatelog/operateLog.data.ts
@@ -1,6 +1,7 @@
 import { BasicColumn, FormSchema, useRender } from '@/components/Table'
 import { DICT_TYPE, getDictOptions } from '@/utils/dict'
 import { DescItem } from '@/components/Description/index'
+import { h } from 'vue'
 
 export const columns: BasicColumn[] = [
   {
@@ -129,7 +130,8 @@ export const infoSchema: DescItem[] = [
     label: '操作人',
     render(_, data) {
       const { userNickname, userId } = data
-      return useRender.renderText(userNickname, 'uid: ' + userId)
+      // return useRender.renderText(userNickname, 'uid: ' + userId)
+      return useRender.renderTags([userNickname, 'uid: ' + userId])
     }
   },
   {
@@ -144,6 +146,9 @@ export const infoSchema: DescItem[] = [
     label: '响应信息',
     show(data) {
       return data && data.resultMsg && data.resultMsg !== ''
+    },
+    render(value) {
+      return h('span', { style: { color: 'red', fontWeight: 'bold' } }, value)
     }
   },
   {
@@ -159,17 +164,15 @@ export const infoSchema: DescItem[] = [
   },
   {
     field: 'requestUrl',
-    label: '请求路径'
-  },
-  {
-    field: 'requestMethod',
-    label: '请求方法',
-    render(value) {
-      const current = httpMethods.find((item) => item.value === value.toUpperCase())
-      if (current) {
-        return useRender.renderTag(value, current.color)
+    label: '请求路径',
+    render(_, data) {
+      if (!data) {
+        return ''
       }
-      return value
+      const { requestMethod, requestUrl } = data
+      const current = httpMethods.find((item) => item.value === requestMethod.toUpperCase())
+      const methodTag = current ? useRender.renderTag(requestMethod, current.color) : requestMethod
+      return h('span', {}, [methodTag, requestUrl])
     }
   },
   {

From ebadd364ca4dc318cdd1d484f86a3f2118bd00a4 Mon Sep 17 00:00:00 2001
From: dap1 <15891557205@163.com>
Date: Sat, 10 Jun 2023 20:07:57 +0800
Subject: [PATCH 4/5] =?UTF-8?q?fix:=20=E8=AF=B7=E6=B1=82=E5=8F=82=E6=95=B0?=
 =?UTF-8?q?=E6=BC=8F=E6=8E=89=E4=BA=86?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

---
 src/views/infra/apiAccessLog/apiAccessLog.data.ts | 7 +++++++
 1 file changed, 7 insertions(+)

diff --git a/src/views/infra/apiAccessLog/apiAccessLog.data.ts b/src/views/infra/apiAccessLog/apiAccessLog.data.ts
index eae52ff0..21926cc1 100644
--- a/src/views/infra/apiAccessLog/apiAccessLog.data.ts
+++ b/src/views/infra/apiAccessLog/apiAccessLog.data.ts
@@ -189,6 +189,13 @@ export const infoSchema: DescItem[] = [
       return h('span', {}, [methodTag, requestUrl])
     }
   },
+  {
+    field: 'requestParams',
+    label: '请求参数',
+    render(value) {
+      return useRender.renderJsonPreview(value)
+    }
+  },
   {
     field: 'beginTime',
     label: '请求开始时间',

From f5dc15f2bbbb8f4a75a326e9c1929140c93e4121 Mon Sep 17 00:00:00 2001
From: dap1 <15891557205@163.com>
Date: Sat, 10 Jun 2023 20:11:58 +0800
Subject: [PATCH 5/5] =?UTF-8?q?fix:=20=E6=93=8D=E4=BD=9C=E6=97=A5=E5=BF=97?=
 =?UTF-8?q?=E5=85=B3=E9=97=AD?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

---
 src/views/system/operatelog/LogInfoModal.vue | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/src/views/system/operatelog/LogInfoModal.vue b/src/views/system/operatelog/LogInfoModal.vue
index 63d1971b..263ceea3 100644
--- a/src/views/system/operatelog/LogInfoModal.vue
+++ b/src/views/system/operatelog/LogInfoModal.vue
@@ -1,5 +1,5 @@
 <template>
-  <BasicModal v-bind="$attrs" title="操作日志详情" @register="registerModalInner">
+  <BasicModal v-bind="$attrs" title="操作日志详情" @register="registerModalInner" @ok="closeModal" width="800px">
     <Description @register="registerDescription" />
   </BasicModal>
 </template>
@@ -13,7 +13,7 @@ import { infoSchema } from './operateLog.data'
 defineOptions({ name: 'OperLogInfoModal' })
 
 const logData = ref()
-const [registerModalInner] = useModalInner((record: Recordable) => {
+const [registerModalInner, { closeModal }] = useModalInner((record: Recordable) => {
   logData.value = record
 })