feat:日志记录
This commit is contained in:
@@ -7,6 +7,8 @@ import cn.code.nl.framework.common.pojo.PageResult;
|
|||||||
import cn.code.nl.framework.common.util.object.BeanUtils;
|
import cn.code.nl.framework.common.util.object.BeanUtils;
|
||||||
import cn.code.nl.framework.excel.core.util.ExcelUtils;
|
import cn.code.nl.framework.excel.core.util.ExcelUtils;
|
||||||
import cn.code.nl.framework.translate.core.TranslateUtils;
|
import cn.code.nl.framework.translate.core.TranslateUtils;
|
||||||
|
import cn.code.nl.module.system.api.logger.dto.OperateLogPageReqDTO;
|
||||||
|
import cn.code.nl.module.system.api.logger.dto.OperateLogRespDTO;
|
||||||
import cn.code.nl.module.system.controller.admin.logger.vo.operatelog.OperateLogPageReqVO;
|
import cn.code.nl.module.system.controller.admin.logger.vo.operatelog.OperateLogPageReqVO;
|
||||||
import cn.code.nl.module.system.controller.admin.logger.vo.operatelog.OperateLogRespVO;
|
import cn.code.nl.module.system.controller.admin.logger.vo.operatelog.OperateLogRespVO;
|
||||||
import cn.code.nl.module.system.dal.dataobject.logger.OperateLogDO;
|
import cn.code.nl.module.system.dal.dataobject.logger.OperateLogDO;
|
||||||
@@ -58,6 +60,15 @@ public class OperateLogController {
|
|||||||
return success(BeanUtils.toBean(pageResult, OperateLogRespVO.class));
|
return success(BeanUtils.toBean(pageResult, OperateLogRespVO.class));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@GetMapping("/page2")
|
||||||
|
@Operation(summary = "查看操作日志分页列表2")
|
||||||
|
@PreAuthorize("@ss.hasPermission('system:operate-log:query')")
|
||||||
|
@TransMethodResult
|
||||||
|
public CommonResult<PageResult<OperateLogRespDTO>> getOperateLogPage(OperateLogPageReqDTO pageReqDTO) {
|
||||||
|
PageResult<OperateLogDO> operateLogPage = operateLogService.getOperateLogPage(pageReqDTO);
|
||||||
|
return success(BeanUtils.toBean(operateLogPage, OperateLogRespDTO.class));
|
||||||
|
}
|
||||||
|
|
||||||
@Operation(summary = "导出操作日志")
|
@Operation(summary = "导出操作日志")
|
||||||
@GetMapping("/export-excel")
|
@GetMapping("/export-excel")
|
||||||
@PreAuthorize("@ss.hasPermission('system:operate-log:export')")
|
@PreAuthorize("@ss.hasPermission('system:operate-log:export')")
|
||||||
|
|||||||
@@ -33,6 +33,14 @@ export function getOperateLogPage(params: PageParam) {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** 查询操作日志列表2 */
|
||||||
|
export function getOperateLogPage2(params: PageParam) {
|
||||||
|
return requestClient.get<PageResult<SystemOperateLogApi.OperateLog>>(
|
||||||
|
'/system/operate-log/page2',
|
||||||
|
{ params },
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
/** 导出操作日志 */
|
/** 导出操作日志 */
|
||||||
export function exportOperateLog(params: any) {
|
export function exportOperateLog(params: any) {
|
||||||
return requestClient.download('/system/operate-log/export-excel', { params });
|
return requestClient.download('/system/operate-log/export-excel', { params });
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import { requestClient } from '#/api/request';
|
|||||||
export namespace TaskTransportTaskApi {
|
export namespace TaskTransportTaskApi {
|
||||||
/** 搬运任务信息 */
|
/** 搬运任务信息 */
|
||||||
export interface TransportTask {
|
export interface TransportTask {
|
||||||
|
id?: number | string;
|
||||||
taskId?: string;
|
taskId?: string;
|
||||||
taskCode?: string; // 任务编码
|
taskCode?: string; // 任务编码
|
||||||
taskName: string; // 任务名称
|
taskName: string; // 任务名称
|
||||||
@@ -52,7 +53,7 @@ export function getTransportTaskPage(params: PageParam) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/** 查询搬运任务详情 */
|
/** 查询搬运任务详情 */
|
||||||
export function getTransportTask(id: number) {
|
export function getTransportTask(id: number | string) {
|
||||||
return requestClient.get<TaskTransportTaskApi.TransportTask>(
|
return requestClient.get<TaskTransportTaskApi.TransportTask>(
|
||||||
`/task/transport-task/get?id=${id}`,
|
`/task/transport-task/get?id=${id}`,
|
||||||
);
|
);
|
||||||
@@ -69,12 +70,12 @@ export function updateTransportTask(data: TaskTransportTaskApi.TransportTask) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/** 删除搬运任务 */
|
/** 删除搬运任务 */
|
||||||
export function deleteTransportTask(id: number) {
|
export function deleteTransportTask(id: number | string) {
|
||||||
return requestClient.delete(`/task/transport-task/delete?id=${id}`);
|
return requestClient.delete(`/task/transport-task/delete?id=${id}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 批量删除搬运任务 */
|
/** 批量删除搬运任务 */
|
||||||
export function deleteTransportTaskList(ids: number[]) {
|
export function deleteTransportTaskList(ids: Array<number | string>) {
|
||||||
return requestClient.delete(
|
return requestClient.delete(
|
||||||
`/task/transport-task/delete-list?ids=${ids.join(',')}`,
|
`/task/transport-task/delete-list?ids=${ids.join(',')}`,
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -1,15 +1,28 @@
|
|||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import type { VxeTableGridOptions } from '#/adapter/vxe-table';
|
import type { VxeTableGridOptions } from '#/adapter/vxe-table';
|
||||||
|
import type { SystemOperateLogApi } from '#/api/system/operate-log';
|
||||||
import type { TaskTransportTaskApi } from '#/api/task/transporttask';
|
import type { TaskTransportTaskApi } from '#/api/task/transporttask';
|
||||||
|
|
||||||
import { onMounted, ref } from 'vue';
|
import { computed, onMounted, ref } from 'vue';
|
||||||
|
|
||||||
import { confirm, Page, useVbenModal } from '@vben/common-ui';
|
import { confirm, Page, useVbenModal } from '@vben/common-ui';
|
||||||
import { downloadFileFromBlobPart, isEmpty } from '@vben/utils';
|
import { downloadFileFromBlobPart, formatDateTime, isEmpty } from '@vben/utils';
|
||||||
|
|
||||||
import { message } from 'antdv-next';
|
import {
|
||||||
|
Button,
|
||||||
|
Descriptions,
|
||||||
|
DescriptionsItem,
|
||||||
|
Drawer,
|
||||||
|
Empty,
|
||||||
|
message,
|
||||||
|
Modal,
|
||||||
|
Spin,
|
||||||
|
Timeline,
|
||||||
|
TimelineItem,
|
||||||
|
} from 'antdv-next';
|
||||||
|
|
||||||
import { ACTION_ICON, TableAction, useVbenVxeGrid } from '#/adapter/vxe-table';
|
import { ACTION_ICON, TableAction, useVbenVxeGrid } from '#/adapter/vxe-table';
|
||||||
|
import { getOperateLogPage2 } from '#/api/system/operate-log';
|
||||||
import {
|
import {
|
||||||
deleteTransportTask,
|
deleteTransportTask,
|
||||||
deleteTransportTaskList,
|
deleteTransportTaskList,
|
||||||
@@ -22,13 +35,15 @@ import { $t } from '#/locales';
|
|||||||
import { loadAllLookupData, useGridColumns, useGridFormSchema } from './data';
|
import { loadAllLookupData, useGridColumns, useGridFormSchema } from './data';
|
||||||
import Form from './modules/form.vue';
|
import Form from './modules/form.vue';
|
||||||
|
|
||||||
|
const OPERATE_LOG_PAGE_SIZE = 20;
|
||||||
|
|
||||||
const ready = ref(false);
|
const ready = ref(false);
|
||||||
onMounted(async () => {
|
onMounted(async () => {
|
||||||
await loadAllLookupData();
|
await loadAllLookupData();
|
||||||
ready.value = true;
|
ready.value = true;
|
||||||
});
|
});
|
||||||
|
|
||||||
const [FormModal, formModalApi] = useVbenModal({
|
const [FormModal] = useVbenModal({
|
||||||
connectedComponent: Form,
|
connectedComponent: Form,
|
||||||
destroyOnClose: true,
|
destroyOnClose: true,
|
||||||
});
|
});
|
||||||
@@ -38,11 +53,6 @@ function handleRefresh() {
|
|||||||
gridApi.query();
|
gridApi.query();
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 创建搬运任务 */
|
|
||||||
function handleCreate() {
|
|
||||||
formModalApi.setData(null).open();
|
|
||||||
}
|
|
||||||
|
|
||||||
/** PC 端操作搬运任务(完成/取消/强制完成) */
|
/** PC 端操作搬运任务(完成/取消/强制完成) */
|
||||||
async function handleOperate(
|
async function handleOperate(
|
||||||
row: TaskTransportTaskApi.TransportTask,
|
row: TaskTransportTaskApi.TransportTask,
|
||||||
@@ -95,7 +105,7 @@ async function handleDeleteBatch() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const checkedIds = ref<number[]>([]);
|
const checkedIds = ref<Array<number | string>>([]);
|
||||||
function handleRowCheckboxChange({
|
function handleRowCheckboxChange({
|
||||||
records,
|
records,
|
||||||
}: {
|
}: {
|
||||||
@@ -110,6 +120,66 @@ async function handleExport() {
|
|||||||
downloadFileFromBlobPart({ fileName: '搬运任务.xls', source: data });
|
downloadFileFromBlobPart({ fileName: '搬运任务.xls', source: data });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const operateLogDrawerOpen = ref(false);
|
||||||
|
const operateLogLoading = ref(false);
|
||||||
|
const operateLogPageNo = ref(1);
|
||||||
|
const operateLogTotal = ref(0);
|
||||||
|
const currentOperateLogTask = ref<TaskTransportTaskApi.TransportTask>();
|
||||||
|
const operateLogList = ref<SystemOperateLogApi.OperateLog[]>([]);
|
||||||
|
const selectedOperateLog = ref<SystemOperateLogApi.OperateLog>();
|
||||||
|
|
||||||
|
const operateLogDrawerTitle = computed(() => {
|
||||||
|
const taskCode = currentOperateLogTask.value?.taskCode;
|
||||||
|
return taskCode ? `操作记录 - ${taskCode}` : '操作记录';
|
||||||
|
});
|
||||||
|
const operateLogHasMore = computed(
|
||||||
|
() => operateLogList.value.length < operateLogTotal.value,
|
||||||
|
);
|
||||||
|
|
||||||
|
async function loadOperateLogs(reset = false) {
|
||||||
|
if (operateLogLoading.value) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const taskId = currentOperateLogTask.value?.taskId;
|
||||||
|
if (!taskId) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (reset) {
|
||||||
|
operateLogPageNo.value = 1;
|
||||||
|
operateLogTotal.value = 0;
|
||||||
|
operateLogList.value = [];
|
||||||
|
}
|
||||||
|
operateLogLoading.value = true;
|
||||||
|
try {
|
||||||
|
const data = await getOperateLogPage2({
|
||||||
|
pageNo: operateLogPageNo.value,
|
||||||
|
pageSize: OPERATE_LOG_PAGE_SIZE,
|
||||||
|
type: '任务信息',
|
||||||
|
bizId: taskId,
|
||||||
|
});
|
||||||
|
operateLogList.value = [...operateLogList.value, ...(data.list || [])];
|
||||||
|
operateLogTotal.value = data.total || 0;
|
||||||
|
operateLogPageNo.value += 1;
|
||||||
|
} finally {
|
||||||
|
operateLogLoading.value = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleOperateLog(row: TaskTransportTaskApi.TransportTask) {
|
||||||
|
currentOperateLogTask.value = row;
|
||||||
|
selectedOperateLog.value = undefined;
|
||||||
|
operateLogDrawerOpen.value = true;
|
||||||
|
loadOperateLogs(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleOperateLogDrawerClose() {
|
||||||
|
selectedOperateLog.value = undefined;
|
||||||
|
currentOperateLogTask.value = undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleOperateLogDetail(log: SystemOperateLogApi.OperateLog) {
|
||||||
|
selectedOperateLog.value = log;
|
||||||
|
}
|
||||||
|
|
||||||
const [Grid, gridApi] = useVbenVxeGrid({
|
const [Grid, gridApi] = useVbenVxeGrid({
|
||||||
formOptions: {
|
formOptions: {
|
||||||
@@ -188,7 +258,7 @@ const [Grid, gridApi] = useVbenVxeGrid({
|
|||||||
type: 'link',
|
type: 'link',
|
||||||
icon: ACTION_ICON.CIRCLE_CHECK,
|
icon: ACTION_ICON.CIRCLE_CHECK,
|
||||||
auth: ['task:transport-task:finish'],
|
auth: ['task:transport-task:finish'],
|
||||||
disabled: ['79', '85', '89', '99'].includes(row.taskStatus),
|
disabled: ['79', '85', '89', '99'].includes(row.taskStatus || ''),
|
||||||
onClick: handleOperate.bind(null, row, 'FINISHED', '完成任务'),
|
onClick: handleOperate.bind(null, row, 'FINISHED', '完成任务'),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -196,7 +266,7 @@ const [Grid, gridApi] = useVbenVxeGrid({
|
|||||||
type: 'link',
|
type: 'link',
|
||||||
icon: ACTION_ICON.CIRCLE_X,
|
icon: ACTION_ICON.CIRCLE_X,
|
||||||
auth: ['task:transport-task:cancel'],
|
auth: ['task:transport-task:cancel'],
|
||||||
disabled: ['75', '79', '89', '99'].includes(row.taskStatus),
|
disabled: ['75', '79', '89', '99'].includes(row.taskStatus || ''),
|
||||||
onClick: handleOperate.bind(null, row, 'CANCELLED', '取消任务'),
|
onClick: handleOperate.bind(null, row, 'CANCELLED', '取消任务'),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -204,9 +274,16 @@ const [Grid, gridApi] = useVbenVxeGrid({
|
|||||||
type: 'link',
|
type: 'link',
|
||||||
icon: ACTION_ICON.CANCEL,
|
icon: ACTION_ICON.CANCEL,
|
||||||
auth: ['task:transport-task:cancel'],
|
auth: ['task:transport-task:cancel'],
|
||||||
disabled: ['79', '89', '99'].includes(row.taskStatus),
|
disabled: ['79', '89', '99'].includes(row.taskStatus || ''),
|
||||||
onClick: handleOperate.bind(null, row, 'FORCE-FINISH', '强制完成任务'),
|
onClick: handleOperate.bind(null, row, 'FORCE-FINISH', '强制完成任务'),
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
label: '操作记录',
|
||||||
|
type: 'link',
|
||||||
|
icon: ACTION_ICON.VIEW,
|
||||||
|
auth: ['system:operate-log:query'],
|
||||||
|
onClick: handleOperateLog.bind(null, row),
|
||||||
|
},
|
||||||
{
|
{
|
||||||
label: $t('common.delete'),
|
label: $t('common.delete'),
|
||||||
type: 'link',
|
type: 'link',
|
||||||
@@ -222,5 +299,141 @@ const [Grid, gridApi] = useVbenVxeGrid({
|
|||||||
/>
|
/>
|
||||||
</template>
|
</template>
|
||||||
</Grid>
|
</Grid>
|
||||||
|
<Drawer
|
||||||
|
v-model:open="operateLogDrawerOpen"
|
||||||
|
:title="operateLogDrawerTitle"
|
||||||
|
width="560"
|
||||||
|
placement="right"
|
||||||
|
destroy-on-close
|
||||||
|
@close="handleOperateLogDrawerClose"
|
||||||
|
>
|
||||||
|
<Spin :spinning="operateLogLoading && isEmpty(operateLogList)">
|
||||||
|
<Empty v-if="!operateLogLoading && isEmpty(operateLogList)" description="暂无操作记录" />
|
||||||
|
<Timeline v-else class="operate-log-timeline">
|
||||||
|
<TimelineItem v-for="log in operateLogList" :key="log.id">
|
||||||
|
<div class="operate-log-timeline-row" @click="handleOperateLogDetail(log)">
|
||||||
|
<span class="operate-log-time">
|
||||||
|
{{ formatDateTime(log.createTime) }}
|
||||||
|
</span>
|
||||||
|
<span class="operate-log-action">{{ log.action || '-' }}</span>
|
||||||
|
</div>
|
||||||
|
</TimelineItem>
|
||||||
|
</Timeline>
|
||||||
|
</Spin>
|
||||||
|
<div v-if="!isEmpty(operateLogList)" class="operate-log-load-more">
|
||||||
|
<Button
|
||||||
|
v-if="operateLogHasMore"
|
||||||
|
:loading="operateLogLoading"
|
||||||
|
block
|
||||||
|
@click="loadOperateLogs()"
|
||||||
|
>
|
||||||
|
加载更多
|
||||||
|
</Button>
|
||||||
|
<span v-else class="operate-log-finished">已加载全部记录</span>
|
||||||
|
</div>
|
||||||
|
</Drawer>
|
||||||
|
<Modal
|
||||||
|
:open="!!selectedOperateLog"
|
||||||
|
title="操作日志详情"
|
||||||
|
width="760px"
|
||||||
|
:footer="null"
|
||||||
|
@cancel="selectedOperateLog = undefined"
|
||||||
|
>
|
||||||
|
<Descriptions
|
||||||
|
v-if="selectedOperateLog"
|
||||||
|
bordered
|
||||||
|
size="small"
|
||||||
|
:column="1"
|
||||||
|
class="operate-log-descriptions"
|
||||||
|
>
|
||||||
|
<DescriptionsItem label="链路追踪编号">
|
||||||
|
{{ selectedOperateLog.traceId || '-' }}
|
||||||
|
</DescriptionsItem>
|
||||||
|
<DescriptionsItem label="用户名称">
|
||||||
|
{{ selectedOperateLog.userName || '-' }}
|
||||||
|
</DescriptionsItem>
|
||||||
|
<DescriptionsItem label="操作模块类型">
|
||||||
|
{{ selectedOperateLog.type || '-' }}
|
||||||
|
</DescriptionsItem>
|
||||||
|
<DescriptionsItem label="操作模块业务编号">
|
||||||
|
{{ selectedOperateLog.bizId || '-' }}
|
||||||
|
</DescriptionsItem>
|
||||||
|
<DescriptionsItem label="操作内容">
|
||||||
|
{{ selectedOperateLog.action || '-' }}
|
||||||
|
</DescriptionsItem>
|
||||||
|
<DescriptionsItem label="拓展字段">
|
||||||
|
<pre>{{ selectedOperateLog.extra || '-' }}</pre>
|
||||||
|
</DescriptionsItem>
|
||||||
|
<DescriptionsItem label="请求方法名">
|
||||||
|
{{ selectedOperateLog.requestMethod || '-' }}
|
||||||
|
</DescriptionsItem>
|
||||||
|
<DescriptionsItem label="请求地址">
|
||||||
|
{{ selectedOperateLog.requestUrl || '-' }}
|
||||||
|
</DescriptionsItem>
|
||||||
|
<DescriptionsItem label="用户 IP">
|
||||||
|
{{ selectedOperateLog.userIp || '-' }}
|
||||||
|
</DescriptionsItem>
|
||||||
|
<DescriptionsItem label="浏览器 UserAgent">
|
||||||
|
{{ selectedOperateLog.userAgent || '-' }}
|
||||||
|
</DescriptionsItem>
|
||||||
|
<DescriptionsItem label="创建时间">
|
||||||
|
{{ formatDateTime(selectedOperateLog.createTime) }}
|
||||||
|
</DescriptionsItem>
|
||||||
|
</Descriptions>
|
||||||
|
</Modal>
|
||||||
</Page>
|
</Page>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.operate-log-timeline {
|
||||||
|
padding: 8px 4px 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.operate-log-timeline-row {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: 156px minmax(0, 1fr);
|
||||||
|
gap: 12px;
|
||||||
|
padding: 8px 10px;
|
||||||
|
margin: -8px 0 8px;
|
||||||
|
cursor: pointer;
|
||||||
|
border-radius: 6px;
|
||||||
|
transition: background-color 0.2s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.operate-log-timeline-row:hover {
|
||||||
|
background-color: hsl(var(--accent));
|
||||||
|
}
|
||||||
|
|
||||||
|
.operate-log-time {
|
||||||
|
font-size: 13px;
|
||||||
|
line-height: 22px;
|
||||||
|
color: hsl(var(--muted-foreground));
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.operate-log-action {
|
||||||
|
min-width: 0;
|
||||||
|
line-height: 22px;
|
||||||
|
word-break: break-word;
|
||||||
|
}
|
||||||
|
|
||||||
|
.operate-log-load-more {
|
||||||
|
padding: 8px 4px 0;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.operate-log-finished {
|
||||||
|
font-size: 13px;
|
||||||
|
color: hsl(var(--muted-foreground));
|
||||||
|
}
|
||||||
|
|
||||||
|
.operate-log-descriptions :deep(pre) {
|
||||||
|
max-height: 180px;
|
||||||
|
padding: 0;
|
||||||
|
margin: 0;
|
||||||
|
overflow: auto;
|
||||||
|
font-family: inherit;
|
||||||
|
white-space: pre-wrap;
|
||||||
|
word-break: break-word;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|||||||
Reference in New Issue
Block a user