feat: 新增 ele infra ApiErrorLog api 错误日志模块
							parent
							
								
									50e57a1418
								
							
						
					
					
						commit
						d18ffd0d69
					
				|  | @ -0,0 +1,175 @@ | |||
| import type { VbenFormSchema } from '#/adapter/form'; | ||||
| import type { OnActionClickFn, VxeTableGridOptions } from '#/adapter/vxe-table'; | ||||
| import type { InfraApiErrorLogApi } from '#/api/infra/api-error-log'; | ||||
| 
 | ||||
| import { useAccess } from '@vben/access'; | ||||
| 
 | ||||
| import { | ||||
|   DICT_TYPE, | ||||
|   getDictOptions, | ||||
|   getRangePickerDefaultProps, | ||||
|   InfraApiErrorLogProcessStatusEnum, | ||||
| } from '#/utils'; | ||||
| 
 | ||||
| const { hasAccessByCodes } = useAccess(); | ||||
| 
 | ||||
| /** 列表的搜索表单 */ | ||||
| export function useGridFormSchema(): VbenFormSchema[] { | ||||
|   return [ | ||||
|     { | ||||
|       fieldName: 'userId', | ||||
|       label: '用户编号', | ||||
|       component: 'Input', | ||||
|       componentProps: { | ||||
|         allowClear: true, | ||||
|         placeholder: '请输入用户编号', | ||||
|       }, | ||||
|     }, | ||||
|     { | ||||
|       fieldName: 'userType', | ||||
|       label: '用户类型', | ||||
|       component: 'Select', | ||||
|       componentProps: { | ||||
|         options: getDictOptions(DICT_TYPE.USER_TYPE, 'number'), | ||||
|         allowClear: true, | ||||
|         placeholder: '请选择用户类型', | ||||
|       }, | ||||
|     }, | ||||
|     { | ||||
|       fieldName: 'applicationName', | ||||
|       label: '应用名', | ||||
|       component: 'Input', | ||||
|       componentProps: { | ||||
|         allowClear: true, | ||||
|         placeholder: '请输入应用名', | ||||
|       }, | ||||
|     }, | ||||
|     { | ||||
|       fieldName: 'exceptionTime', | ||||
|       label: '异常时间', | ||||
|       component: 'RangePicker', | ||||
|       componentProps: { | ||||
|         ...getRangePickerDefaultProps(), | ||||
|         allowClear: true, | ||||
|       }, | ||||
|     }, | ||||
|     { | ||||
|       fieldName: 'processStatus', | ||||
|       label: '处理状态', | ||||
|       component: 'Select', | ||||
|       componentProps: { | ||||
|         options: getDictOptions( | ||||
|           DICT_TYPE.INFRA_API_ERROR_LOG_PROCESS_STATUS, | ||||
|           'number', | ||||
|         ), | ||||
|         allowClear: true, | ||||
|         placeholder: '请选择处理状态', | ||||
|       }, | ||||
|       defaultValue: InfraApiErrorLogProcessStatusEnum.INIT, | ||||
|     }, | ||||
|   ]; | ||||
| } | ||||
| 
 | ||||
| /** 列表的字段 */ | ||||
| export function useGridColumns<T = InfraApiErrorLogApi.ApiErrorLog>( | ||||
|   onActionClick: OnActionClickFn<T>, | ||||
| ): VxeTableGridOptions['columns'] { | ||||
|   return [ | ||||
|     { | ||||
|       field: 'id', | ||||
|       title: '日志编号', | ||||
|       minWidth: 100, | ||||
|     }, | ||||
|     { | ||||
|       field: 'userId', | ||||
|       title: '用户编号', | ||||
|       minWidth: 100, | ||||
|     }, | ||||
|     { | ||||
|       field: 'userType', | ||||
|       title: '用户类型', | ||||
|       minWidth: 120, | ||||
|       cellRender: { | ||||
|         name: 'CellDict', | ||||
|         props: { type: DICT_TYPE.USER_TYPE }, | ||||
|       }, | ||||
|     }, | ||||
|     { | ||||
|       field: 'applicationName', | ||||
|       title: '应用名', | ||||
|       minWidth: 150, | ||||
|     }, | ||||
|     { | ||||
|       field: 'requestMethod', | ||||
|       title: '请求方法', | ||||
|       minWidth: 80, | ||||
|     }, | ||||
|     { | ||||
|       field: 'requestUrl', | ||||
|       title: '请求地址', | ||||
|       minWidth: 200, | ||||
|     }, | ||||
|     { | ||||
|       field: 'exceptionTime', | ||||
|       title: '异常发生时间', | ||||
|       minWidth: 180, | ||||
|       formatter: 'formatDateTime', | ||||
|     }, | ||||
|     { | ||||
|       field: 'exceptionName', | ||||
|       title: '异常名', | ||||
|       minWidth: 180, | ||||
|     }, | ||||
|     { | ||||
|       field: 'processStatus', | ||||
|       title: '处理状态', | ||||
|       minWidth: 120, | ||||
|       cellRender: { | ||||
|         name: 'CellDict', | ||||
|         props: { type: DICT_TYPE.INFRA_API_ERROR_LOG_PROCESS_STATUS }, | ||||
|       }, | ||||
|     }, | ||||
|     { | ||||
|       field: 'operation', | ||||
|       title: '操作', | ||||
|       minWidth: 200, | ||||
|       align: 'center', | ||||
|       fixed: 'right', | ||||
|       cellRender: { | ||||
|         attrs: { | ||||
|           nameField: 'id', | ||||
|           nameTitle: 'API错误日志', | ||||
|           onClick: onActionClick, | ||||
|         }, | ||||
|         name: 'CellOperation', | ||||
|         options: [ | ||||
|           { | ||||
|             code: 'detail', | ||||
|             text: '详情', | ||||
|             show: hasAccessByCodes(['infra:api-error-log:query']), | ||||
|           }, | ||||
|           { | ||||
|             code: 'done', | ||||
|             text: '已处理', | ||||
|             show: (row: InfraApiErrorLogApi.ApiErrorLog) => { | ||||
|               return ( | ||||
|                 row.processStatus === InfraApiErrorLogProcessStatusEnum.INIT && | ||||
|                 hasAccessByCodes(['infra:api-error-log:update-status']) | ||||
|               ); | ||||
|             }, | ||||
|           }, | ||||
|           { | ||||
|             code: 'ignore', | ||||
|             text: '已忽略', | ||||
|             show: (row: InfraApiErrorLogApi.ApiErrorLog) => { | ||||
|               return ( | ||||
|                 row.processStatus === InfraApiErrorLogProcessStatusEnum.INIT && | ||||
|                 hasAccessByCodes(['infra:api-error-log:update-status']) | ||||
|               ); | ||||
|             }, | ||||
|           }, | ||||
|         ], | ||||
|       }, | ||||
|     }, | ||||
|   ]; | ||||
| } | ||||
|  | @ -0,0 +1,132 @@ | |||
| <script lang="ts" setup> | ||||
| import type { | ||||
|   OnActionClickParams, | ||||
|   VxeTableGridOptions, | ||||
| } from '#/adapter/vxe-table'; | ||||
| import type { InfraApiErrorLogApi } from '#/api/infra/api-error-log'; | ||||
| 
 | ||||
| import { confirm, Page, useVbenModal } from '@vben/common-ui'; | ||||
| import { Download } from '@vben/icons'; | ||||
| import { downloadFileFromBlobPart } from '@vben/utils'; | ||||
| 
 | ||||
| import { ElButton, ElMessage } from 'element-plus'; | ||||
| 
 | ||||
| import { useVbenVxeGrid } from '#/adapter/vxe-table'; | ||||
| import { | ||||
|   exportApiErrorLog, | ||||
|   getApiErrorLogPage, | ||||
|   updateApiErrorLogStatus, | ||||
| } from '#/api/infra/api-error-log'; | ||||
| import { DocAlert } from '#/components/doc-alert'; | ||||
| import { $t } from '#/locales'; | ||||
| import { InfraApiErrorLogProcessStatusEnum } from '#/utils'; | ||||
| 
 | ||||
| import { useGridColumns, useGridFormSchema } from './data'; | ||||
| import Detail from './modules/detail.vue'; | ||||
| 
 | ||||
| const [DetailModal, detailModalApi] = useVbenModal({ | ||||
|   connectedComponent: Detail, | ||||
|   destroyOnClose: true, | ||||
| }); | ||||
| 
 | ||||
| /** 刷新表格 */ | ||||
| function onRefresh() { | ||||
|   gridApi.query(); | ||||
| } | ||||
| 
 | ||||
| /** 导出表格 */ | ||||
| async function onExport() { | ||||
|   const data = await exportApiErrorLog(await gridApi.formApi.getValues()); | ||||
|   downloadFileFromBlobPart({ fileName: 'API 错误日志.xls', source: data }); | ||||
| } | ||||
| 
 | ||||
| /** 查看 API 错误日志详情 */ | ||||
| function onDetail(row: InfraApiErrorLogApi.ApiErrorLog) { | ||||
|   detailModalApi.setData(row).open(); | ||||
| } | ||||
| 
 | ||||
| /** 处理已处理 / 已忽略的操作 */ | ||||
| async function onProcess(id: number, processStatus: number) { | ||||
|   confirm({ | ||||
|     content: `确认标记为${InfraApiErrorLogProcessStatusEnum.DONE ? '已处理' : '已忽略'}?`, | ||||
|   }).then(async () => { | ||||
|     await updateApiErrorLogStatus(id, processStatus); | ||||
|     // 关闭并提示 | ||||
|     ElMessage.success($t('ui.actionMessage.operationSuccess')); | ||||
|     onRefresh(); | ||||
|   }); | ||||
| } | ||||
| 
 | ||||
| /** 表格操作按钮的回调函数 */ | ||||
| function onActionClick({ | ||||
|   code, | ||||
|   row, | ||||
| }: OnActionClickParams<InfraApiErrorLogApi.ApiErrorLog>) { | ||||
|   switch (code) { | ||||
|     case 'detail': { | ||||
|       onDetail(row); | ||||
|       break; | ||||
|     } | ||||
|     case 'done': { | ||||
|       onProcess(row.id, InfraApiErrorLogProcessStatusEnum.DONE); | ||||
|       break; | ||||
|     } | ||||
|     case 'ignore': { | ||||
|       onProcess(row.id, InfraApiErrorLogProcessStatusEnum.IGNORE); | ||||
|       break; | ||||
|     } | ||||
|   } | ||||
| } | ||||
| 
 | ||||
| const [Grid, gridApi] = useVbenVxeGrid({ | ||||
|   formOptions: { | ||||
|     schema: useGridFormSchema(), | ||||
|   }, | ||||
|   gridOptions: { | ||||
|     columns: useGridColumns(onActionClick), | ||||
|     height: 'auto', | ||||
|     keepSource: true, | ||||
|     proxyConfig: { | ||||
|       ajax: { | ||||
|         query: async ({ page }, formValues) => { | ||||
|           return await getApiErrorLogPage({ | ||||
|             pageNo: page.currentPage, | ||||
|             pageSize: page.pageSize, | ||||
|             ...formValues, | ||||
|           }); | ||||
|         }, | ||||
|       }, | ||||
|     }, | ||||
|     rowConfig: { | ||||
|       keyField: 'id', | ||||
|     }, | ||||
|     toolbarConfig: { | ||||
|       refresh: { code: 'query' }, | ||||
|       search: true, | ||||
|     }, | ||||
|   } as VxeTableGridOptions<InfraApiErrorLogApi.ApiErrorLog>, | ||||
| }); | ||||
| </script> | ||||
| 
 | ||||
| <template> | ||||
|   <Page auto-content-height> | ||||
|     <template #doc> | ||||
|       <DocAlert title="系统日志" url="https://doc.iocoder.cn/system-log/" /> | ||||
|     </template> | ||||
| 
 | ||||
|     <DetailModal @success="onRefresh" /> | ||||
|     <Grid table-title="API 错误日志列表"> | ||||
|       <template #toolbar-tools> | ||||
|         <ElButton | ||||
|           type="primary" | ||||
|           class="ml-2" | ||||
|           @click="onExport" | ||||
|           v-access:code="['infra:api-error-log:export']" | ||||
|         > | ||||
|           <Download class="size-5" /> | ||||
|           {{ $t('ui.actionTitle.export') }} | ||||
|         </ElButton> | ||||
|       </template> | ||||
|     </Grid> | ||||
|   </Page> | ||||
| </template> | ||||
|  | @ -0,0 +1,104 @@ | |||
| <script lang="ts" setup> | ||||
| import type { InfraApiErrorLogApi } from '#/api/infra/api-error-log'; | ||||
| 
 | ||||
| import { ref } from 'vue'; | ||||
| 
 | ||||
| import { useVbenModal } from '@vben/common-ui'; | ||||
| import { formatDateTime } from '@vben/utils'; | ||||
| 
 | ||||
| import { ElDescriptions, ElDescriptionsItem, ElInput } from 'element-plus'; | ||||
| 
 | ||||
| import { DictTag } from '#/components/dict-tag'; | ||||
| import { DICT_TYPE } from '#/utils'; | ||||
| 
 | ||||
| const formData = ref<InfraApiErrorLogApi.ApiErrorLog>(); | ||||
| 
 | ||||
| const [Modal, modalApi] = useVbenModal({ | ||||
|   async onOpenChange(isOpen: boolean) { | ||||
|     if (!isOpen) { | ||||
|       formData.value = undefined; | ||||
|       return; | ||||
|     } | ||||
|     // 加载数据 | ||||
|     const data = modalApi.getData<InfraApiErrorLogApi.ApiErrorLog>(); | ||||
|     if (!data || !data.id) { | ||||
|       return; | ||||
|     } | ||||
|     modalApi.lock(); | ||||
|     try { | ||||
|       formData.value = data; | ||||
|     } finally { | ||||
|       modalApi.unlock(); | ||||
|     } | ||||
|   }, | ||||
| }); | ||||
| </script> | ||||
| 
 | ||||
| <template> | ||||
|   <Modal | ||||
|     title="API错误日志详情" | ||||
|     class="w-1/2" | ||||
|     :show-cancel-button="false" | ||||
|     :show-confirm-button="false" | ||||
|   > | ||||
|     <ElDescriptions | ||||
|       border | ||||
|       :column="1" | ||||
|       size="default" | ||||
|       class="mx-4" | ||||
|       label-width="110px" | ||||
|     > | ||||
|       <ElDescriptionsItem label="日志编号"> | ||||
|         {{ formData?.id }} | ||||
|       </ElDescriptionsItem> | ||||
|       <ElDescriptionsItem label="链路追踪"> | ||||
|         {{ formData?.traceId }} | ||||
|       </ElDescriptionsItem> | ||||
|       <ElDescriptionsItem label="应用名"> | ||||
|         {{ formData?.applicationName }} | ||||
|       </ElDescriptionsItem> | ||||
|       <ElDescriptionsItem label="用户编号"> | ||||
|         {{ formData?.userId }} | ||||
|         <DictTag :type="DICT_TYPE.USER_TYPE" :value="formData?.userType" /> | ||||
|       </ElDescriptionsItem> | ||||
|       <ElDescriptionsItem label="用户IP"> | ||||
|         {{ formData?.userIp }} | ||||
|       </ElDescriptionsItem> | ||||
|       <ElDescriptionsItem label="用户UA"> | ||||
|         {{ formData?.userAgent }} | ||||
|       </ElDescriptionsItem> | ||||
|       <ElDescriptionsItem label="请求信息"> | ||||
|         {{ formData?.requestMethod }} {{ formData?.requestUrl }} | ||||
|       </ElDescriptionsItem> | ||||
|       <ElDescriptionsItem label="请求参数"> | ||||
|         {{ formData?.requestParams }} | ||||
|       </ElDescriptionsItem> | ||||
|       <ElDescriptionsItem label="异常时间"> | ||||
|         {{ formatDateTime(formData?.exceptionTime || '') }} | ||||
|       </ElDescriptionsItem> | ||||
|       <ElDescriptionsItem label="异常名"> | ||||
|         {{ formData?.exceptionName }} | ||||
|       </ElDescriptionsItem> | ||||
|       <ElDescriptionsItem v-if="formData?.exceptionStackTrace" label="异常堆栈"> | ||||
|         <ElInput | ||||
|           type="textarea" | ||||
|           :model-value="formData?.exceptionStackTrace" | ||||
|           :autosize="{ maxRows: 20 }" | ||||
|           readonly | ||||
|         /> | ||||
|       </ElDescriptionsItem> | ||||
|       <ElDescriptionsItem label="处理状态"> | ||||
|         <DictTag | ||||
|           :type="DICT_TYPE.INFRA_API_ERROR_LOG_PROCESS_STATUS" | ||||
|           :value="formData?.processStatus" | ||||
|         /> | ||||
|       </ElDescriptionsItem> | ||||
|       <ElDescriptionsItem v-if="formData?.processUserId" label="处理人"> | ||||
|         {{ formData?.processUserId }} | ||||
|       </ElDescriptionsItem> | ||||
|       <ElDescriptionsItem v-if="formData?.processTime" label="处理时间"> | ||||
|         {{ formatDateTime(formData?.processTime || '') }} | ||||
|       </ElDescriptionsItem> | ||||
|     </ElDescriptions> | ||||
|   </Modal> | ||||
| </template> | ||||
		Loading…
	
		Reference in New Issue
	
	 puhui999
						puhui999