清空操作日志.

This commit is contained in:
lijiahang
2024-03-04 19:10:55 +08:00
parent b9127967d0
commit ba955571a3
18 changed files with 717 additions and 185 deletions

View File

@@ -25,8 +25,8 @@ public class HostConnectLogOperatorType extends InitializingOperatorTypes {
@Override @Override
public OperatorType[] types() { public OperatorType[] types() {
return new OperatorType[]{ return new OperatorType[]{
new OperatorType(H, DELETE, "删除主机连接记录 <sb>${count}</sb>条"), new OperatorType(H, DELETE, "删除主机连接记录 <sb>${count}</sb> "),
new OperatorType(H, CLEAR, "清空主机连接记录 <sb>${count}</sb>条"), new OperatorType(H, CLEAR, "清空主机连接记录 <sb>${count}</sb> "),
new OperatorType(M, FORCE_OFFLINE, "强制下线主机连接 <sb>${hostName}</sb>"), new OperatorType(M, FORCE_OFFLINE, "强制下线主机连接 <sb>${hostName}</sb>"),
}; };
} }

View File

@@ -1,16 +1,18 @@
package com.orion.ops.module.infra.controller; package com.orion.ops.module.infra.controller;
import com.orion.lang.define.wrapper.DataGrid; import com.orion.lang.define.wrapper.DataGrid;
import com.orion.ops.framework.biz.operator.log.core.annotation.OperatorLog;
import com.orion.ops.framework.common.validator.group.Page; import com.orion.ops.framework.common.validator.group.Page;
import com.orion.ops.framework.log.core.annotation.IgnoreLog; import com.orion.ops.framework.log.core.annotation.IgnoreLog;
import com.orion.ops.framework.log.core.enums.IgnoreLogMode; import com.orion.ops.framework.log.core.enums.IgnoreLogMode;
import com.orion.ops.framework.security.core.utils.SecurityUtils;
import com.orion.ops.framework.web.core.annotation.RestWrapper; import com.orion.ops.framework.web.core.annotation.RestWrapper;
import com.orion.ops.module.infra.define.operator.OperatorLogOperatorType;
import com.orion.ops.module.infra.entity.request.operator.OperatorLogQueryRequest; import com.orion.ops.module.infra.entity.request.operator.OperatorLogQueryRequest;
import com.orion.ops.module.infra.entity.vo.LoginHistoryVO; import com.orion.ops.module.infra.entity.vo.LoginHistoryVO;
import com.orion.ops.module.infra.entity.vo.OperatorLogVO; import com.orion.ops.module.infra.entity.vo.OperatorLogVO;
import com.orion.ops.module.infra.service.OperatorLogService; import com.orion.ops.module.infra.service.OperatorLogService;
import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.tags.Tag; import io.swagger.v3.oas.annotations.tags.Tag;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.springframework.security.access.prepost.PreAuthorize; import org.springframework.security.access.prepost.PreAuthorize;
@@ -47,6 +49,29 @@ public class OperatorLogController {
return operatorLogService.getOperatorLogPage(request); return operatorLogService.getOperatorLogPage(request);
} }
@OperatorLog(OperatorLogOperatorType.DELETE)
@DeleteMapping("/delete")
@Operation(summary = "删除操作日志")
@Parameter(name = "idList", description = "idList", required = true)
@PreAuthorize("@ss.hasPermission('infra:operator-log:delete')")
public Integer deleteOperatorLog(@RequestParam("idList") List<Long> idList) {
return operatorLogService.deleteOperatorLog(idList);
}
@PostMapping("/query-count")
@Operation(summary = "查询操作日志数量")
public Long getOperatorLogCount(@RequestBody OperatorLogQueryRequest request) {
return operatorLogService.getOperatorLogCount(request);
}
@OperatorLog(OperatorLogOperatorType.CLEAR)
@PostMapping("/clear")
@Operation(summary = "清空操作日志")
@PreAuthorize("@ss.hasPermission('infra:operator-log:clear')")
public Integer clearOperatorLog(@RequestBody OperatorLogQueryRequest request) {
return operatorLogService.clearOperatorLog(request);
}
@IgnoreLog(IgnoreLogMode.RET) @IgnoreLog(IgnoreLogMode.RET)
@GetMapping("/login-history") @GetMapping("/login-history")
@Operation(summary = "查询用户登录日志") @Operation(summary = "查询用户登录日志")

View File

@@ -6,6 +6,7 @@ import com.orion.ops.module.infra.entity.request.operator.OperatorLogQueryReques
import com.orion.ops.module.infra.entity.vo.LoginHistoryVO; import com.orion.ops.module.infra.entity.vo.LoginHistoryVO;
import com.orion.ops.module.infra.entity.vo.OperatorLogVO; import com.orion.ops.module.infra.entity.vo.OperatorLogVO;
import org.mapstruct.Mapper; import org.mapstruct.Mapper;
import org.mapstruct.Mapping;
import org.mapstruct.factory.Mappers; import org.mapstruct.factory.Mappers;
/** /**
@@ -24,6 +25,7 @@ public interface OperatorLogConvert {
OperatorLogDO to(OperatorLogQueryRequest request); OperatorLogDO to(OperatorLogQueryRequest request);
@Mapping(target = "extra", ignore = true)
OperatorLogVO to(OperatorLogDO domain); OperatorLogVO to(OperatorLogDO domain);
LoginHistoryVO toLoginHistory(OperatorLogDO domain); LoginHistoryVO toLoginHistory(OperatorLogDO domain);

View File

@@ -0,0 +1,31 @@
package com.orion.ops.module.infra.define.operator;
import com.orion.ops.framework.biz.operator.log.core.annotation.Module;
import com.orion.ops.framework.biz.operator.log.core.factory.InitializingOperatorTypes;
import com.orion.ops.framework.biz.operator.log.core.model.OperatorType;
import static com.orion.ops.framework.biz.operator.log.core.enums.OperatorRiskLevel.H;
/**
* 操作日志 操作日志类型
*
* @author Jiahang Li
* @version 1.0.0
* @since 2024-3-4 16:20
*/
@Module("infra:operator-log")
public class OperatorLogOperatorType extends InitializingOperatorTypes {
public static final String DELETE = "operator-log:delete";
public static final String CLEAR = "operator-log:clear";
@Override
public OperatorType[] types() {
return new OperatorType[]{
new OperatorType(H, DELETE, "删除操作日志 <sb>${count}</sb> 条"),
new OperatorType(H, CLEAR, "清空操作日志 <sb>${count}</sb> 条"),
};
}
}

View File

@@ -8,6 +8,7 @@ import lombok.NoArgsConstructor;
import java.io.Serializable; import java.io.Serializable;
import java.util.Date; import java.util.Date;
import java.util.Map;
/** /**
* 操作日志 视图响应对象 * 操作日志 视图响应对象
@@ -59,7 +60,7 @@ public class OperatorLogVO implements Serializable {
private String logInfo; private String logInfo;
@Schema(description = "参数") @Schema(description = "参数")
private String extra; private Map<String, Object> extra;
@Schema(description = "操作结果 0失败 1成功") @Schema(description = "操作结果 0失败 1成功")
private Integer result; private Integer result;

View File

@@ -32,6 +32,30 @@ public interface OperatorLogService {
*/ */
DataGrid<OperatorLogVO> getOperatorLogPage(OperatorLogQueryRequest request); DataGrid<OperatorLogVO> getOperatorLogPage(OperatorLogQueryRequest request);
/**
* 删除操作日志
*
* @param idList idList
* @return effect
*/
Integer deleteOperatorLog(List<Long> idList);
/**
* 查询操作日志数量
*
* @param request request
* @return count
*/
Long getOperatorLogCount(OperatorLogQueryRequest request);
/**
* 清空操作日志
*
* @param request request
* @return effect
*/
Integer clearOperatorLog(OperatorLogQueryRequest request);
/** /**
* 查询用户登录日志 * 查询用户登录日志
* *

View File

@@ -1,9 +1,11 @@
package com.orion.ops.module.infra.service.impl; package com.orion.ops.module.infra.service.impl;
import com.alibaba.fastjson.JSON;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.orion.lang.define.wrapper.DataGrid; import com.orion.lang.define.wrapper.DataGrid;
import com.orion.lang.utils.Arrays1; import com.orion.lang.utils.Arrays1;
import com.orion.ops.framework.biz.operator.log.core.model.OperatorLogModel; import com.orion.ops.framework.biz.operator.log.core.model.OperatorLogModel;
import com.orion.ops.framework.biz.operator.log.core.utils.OperatorLogs;
import com.orion.ops.framework.common.constant.Const; import com.orion.ops.framework.common.constant.Const;
import com.orion.ops.module.infra.convert.OperatorLogConvert; import com.orion.ops.module.infra.convert.OperatorLogConvert;
import com.orion.ops.module.infra.dao.OperatorLogDAO; import com.orion.ops.module.infra.dao.OperatorLogDAO;
@@ -48,7 +50,38 @@ public class OperatorLogServiceImpl implements OperatorLogService {
// 查询 // 查询
return operatorLogDAO.of(wrapper) return operatorLogDAO.of(wrapper)
.page(request) .page(request)
.dataGrid(OperatorLogConvert.MAPPER::to); .dataGrid(s -> {
OperatorLogVO vo = OperatorLogConvert.MAPPER.to(s);
vo.setExtra(JSON.parseObject(s.getExtra()));
return vo;
});
}
@Override
public Integer deleteOperatorLog(List<Long> idList) {
log.info("OperatorLogService.deleteOperatorLog start {}", JSON.toJSONString(idList));
int effect = operatorLogDAO.deleteBatchIds(idList);
log.info("OperatorLogService.deleteOperatorLog finish {}", effect);
// 设置日志参数
OperatorLogs.add(OperatorLogs.COUNT, effect);
return effect;
}
@Override
public Long getOperatorLogCount(OperatorLogQueryRequest request) {
return operatorLogDAO.selectCount(this.buildQueryWrapper(request));
}
@Override
public Integer clearOperatorLog(OperatorLogQueryRequest request) {
log.info("OperatorLogService.clearOperatorLog start {}", JSON.toJSONString(request));
// 删除
LambdaQueryWrapper<OperatorLogDO> wrapper = this.buildQueryWrapper(request);
int effect = operatorLogDAO.delete(wrapper);
log.info("OperatorLogService.clearOperatorLog finish {}", effect);
// 设置日志参数
OperatorLogs.add(OperatorLogs.COUNT, effect);
return effect;
} }
@Override @Override

View File

@@ -1,5 +1,6 @@
import type { DataGrid, Pagination } from '@/types/global'; import type { DataGrid, Pagination } from '@/types/global';
import axios from 'axios'; import axios from 'axios';
import qs from 'query-string';
/** /**
* 操作日志查询参数 * 操作日志查询参数
@@ -60,6 +61,32 @@ export function getOperatorLogPage(request: OperatorLogQueryRequest) {
return axios.post<DataGrid<OperatorLogQueryResponse>>('/infra/operator-log/query', request); return axios.post<DataGrid<OperatorLogQueryResponse>>('/infra/operator-log/query', request);
} }
/**
* 删除操作日志
*/
export function deleteOperatorLog(idList: Array<number>) {
return axios.delete('/infra/operator-log/delete', {
params: { idList },
paramsSerializer: params => {
return qs.stringify(params, { arrayFormat: 'comma' });
}
});
}
/**
* 查询操作日志数量
*/
export function getOperatorLogCount(request: OperatorLogQueryRequest) {
return axios.post<number>('/infra/operator-log/query-count', request);
}
/**
* 清空操作日志
*/
export function clearOperatorLog(request: OperatorLogQueryRequest) {
return axios.post<number>('/infra/operator-log/clear', request);
}
/** /**
* 查询登录日志 * 查询登录日志
*/ */

View File

@@ -14,9 +14,8 @@
title="操作日志" title="操作日志"
:header-style="{ paddingBottom: '0' }" :header-style="{ paddingBottom: '0' }"
:body-style="{ padding: '8px 20px 8px 20px' }"> :body-style="{ padding: '8px 20px 8px 20px' }">
<operator-log-table :visible-user="false" <operator-log-simple-table :current="true"
:visible-handle="false" :handle-column="false" />
:current="true" />
</a-card> </a-card>
</div> </div>
<a-grid class="right-side" <a-grid class="right-side"
@@ -39,7 +38,7 @@
import Banner from './components/banner.vue'; import Banner from './components/banner.vue';
import QuickOperation from './components/quick-operation.vue'; import QuickOperation from './components/quick-operation.vue';
import Docs from './components/docs.vue'; import Docs from './components/docs.vue';
import OperatorLogTable from '@/views/user/operator-log/components/operator-log-table.vue'; import OperatorLogSimpleTable from '@/views/user/operator-log/components/operator-log-simple-table.vue';
</script> </script>
<script lang="ts"> <script lang="ts">

View File

@@ -174,12 +174,12 @@
</div> </div>
</template> </template>
</a-table> </a-table>
<!-- 清空模态框 -->
<connect-log-clear-modal ref="clearModal"
@clear="fetchTableData" />
<!-- 详情模态框 -->
<connect-log-detail-drawer ref="detailModal" />
</a-card> </a-card>
<!-- 清空模态框 -->
<connect-log-clear-modal ref="clearModal"
@clear="fetchTableData" />
<!-- 详情模态框 -->
<connect-log-detail-drawer ref="detailModal" />
</template> </template>
<script lang="ts"> <script lang="ts">

View File

@@ -12,14 +12,12 @@
</script> </script>
<script lang="ts" setup> <script lang="ts" setup>
import ConnectLogTable from './components/connect-log-table.vue';
import { ref, onBeforeMount, onUnmounted } from 'vue'; import { ref, onBeforeMount, onUnmounted } from 'vue';
import { useCacheStore, useDictStore } from '@/store'; import { useCacheStore, useDictStore } from '@/store';
import { dictKeys } from './types/const'; import { dictKeys } from './types/const';
import ConnectLogTable from './components/connect-log-table.vue';
const render = ref(false); const render = ref(false);
const table = ref();
const modal = ref();
// 加载字典配置 // 加载字典配置
onBeforeMount(async () => { onBeforeMount(async () => {

View File

@@ -18,20 +18,16 @@
</div> </div>
</template> </template>
<!-- 表格组件 --> <!-- 表格组件 -->
<operator-log-table ref="table" <operator-log-simple-table ref="table"
:visible-user="false" :current="!user"
:current="!user" :base-params="{ userId: user?.id }" />
:base-params="{userId: user?.id}"
@viewDetail="(e) => view.open(e)" />
</a-card> </a-card>
<!-- json 查看器模态框 -->
<json-editor-modal ref="view" />
</div> </div>
</template> </template>
<script lang="ts"> <script lang="ts">
export default { export default {
name: 'operatorLogList' name: 'userOperatorLog'
}; };
</script> </script>
@@ -40,10 +36,9 @@
import type { PropType } from 'vue'; import type { PropType } from 'vue';
import { ref, onBeforeMount } from 'vue'; import { ref, onBeforeMount } from 'vue';
import { useCacheStore, useDictStore } from '@/store'; import { useCacheStore, useDictStore } from '@/store';
import { dictKeys } from '../../operator-log/types/const'; import { dictKeys } from '@/views/user/operator-log/types/const';
import OperatorLogQueryHeader from '../../operator-log/components/operator-log-query-header.vue'; import OperatorLogQueryHeader from '@/views/user/operator-log/components/operator-log-query-header.vue';
import OperatorLogTable from '../../operator-log/components/operator-log-table.vue'; import OperatorLogSimpleTable from '@/views/user/operator-log/components/operator-log-simple-table.vue';
import JsonEditorModal from '@/components/view/json-editor/json-editor-modal.vue';
const props = defineProps({ const props = defineProps({
user: Object as PropType<UserQueryResponse>, user: Object as PropType<UserQueryResponse>,
@@ -53,7 +48,6 @@
const render = ref(); const render = ref();
const table = ref(); const table = ref();
const view = ref();
onBeforeMount(async () => { onBeforeMount(async () => {
// //

View File

@@ -28,7 +28,7 @@
<a-tab-pane key="operatorLog" <a-tab-pane key="operatorLog"
v-if="!user || hasPermission('infra:operator-log:query')" v-if="!user || hasPermission('infra:operator-log:query')"
title="操作日志"> title="操作日志">
<operator-log-list :user="user" /> <user-operator-log :user="user" />
</a-tab-pane> </a-tab-pane>
<!-- 返回 --> <!-- 返回 -->
<a-tab-pane key="back" v-if="userId"> <a-tab-pane key="back" v-if="userId">
@@ -48,14 +48,15 @@
</script> </script>
<script lang="ts" setup> <script lang="ts" setup>
import UserSession from './components/user-session.vue'; import type { UserQueryResponse } from '@/api/user/user';
import OperatorLogList from './components/operator-log-list.vue';
import { useRoute, useRouter } from 'vue-router'; import { useRoute, useRouter } from 'vue-router';
import { onBeforeMount, ref } from 'vue';
import usePermission from '@/hooks/permission'; import usePermission from '@/hooks/permission';
import { onBeforeMount, ref } from 'vue';
import { useUserStore } from '@/store'; import { useUserStore } from '@/store';
import { getUser, UserQueryResponse } from '@/api/user/user'; import { getUser } from '@/api/user/user';
import UserBaseInfo from './components/user-base-info.vue'; import UserBaseInfo from './components/user-base-info.vue';
import UserSession from './components/user-session.vue';
import UserOperatorLog from './components/user-operator-log.vue';
import LoginHistory from './components/login-history.vue'; import LoginHistory from './components/login-history.vue';
const route = useRoute(); const route = useRoute();

View File

@@ -0,0 +1,199 @@
<template>
<a-modal v-model:visible="visible"
body-class="modal-form"
title-align="start"
title="清空操作日志"
:align-center="false"
:draggable="true"
:mask-closable="false"
:unmount-on-close="true"
ok-text="清空"
:ok-button-props="{ disabled: loading }"
:cancel-button-props="{ disabled: loading }"
:on-before-ok="handlerOk"
@close="handleClose">
<a-spin class="full" :loading="loading">
<a-form :model="formModel"
label-align="right"
:style="{ width: '460px' }"
:label-col-props="{ span: 5 }"
:wrapper-col-props="{ span: 19 }">
<!-- 操作用户 -->
<a-form-item field="userId"
label="操作用户"
>
<user-selector v-model="formModel.userId"
placeholder="请选择操作用户"
allow-clear />
</a-form-item>
<!-- 操作模块 -->
<a-form-item field="module" label="操作模块">
<a-select v-model="formModel.module"
:options="toOptions(operatorLogModuleKey)"
:allow-search="true"
:filter-option="labelFilter"
placeholder="请选择操作模块"
@change="m => selectedModule(m as string)"
allow-clear />
</a-form-item>
<!-- 操作类型 -->
<a-form-item field="type" label="操作类型">
<a-select v-model="formModel.type"
:options="typeOptions"
:allow-search="true"
:filter-option="labelFilter"
placeholder="请选择操作类型"
allow-clear />
</a-form-item>
<!-- 风险等级 -->
<a-form-item field="riskLevel" label="风险等级">
<a-select v-model="formModel.riskLevel"
:options="toOptions(operatorRiskLevelKey)"
placeholder="请选择风险等级"
allow-clear />
</a-form-item>
<!-- 执行结果 -->
<a-form-item field="result" label="执行结果">
<a-select v-model="formModel.result"
:options="toOptions(operatorLogResultKey)"
placeholder="请选择执行结果"
allow-clear />
</a-form-item>
<!-- 执行时间 -->
<a-form-item field="startTimeRange" label="执行时间">
<a-range-picker v-model="formModel.startTimeRange"
style="width: 100%"
:time-picker-props="{ defaultValue: ['00:00:00', '23:59:59'] }"
show-time
format="YYYY-MM-DD HH:mm:ss" />
</a-form-item>
</a-form>
</a-spin>
</a-modal>
</template>
<script lang="ts">
export default {
name: 'userOperatorLogClearModal'
};
</script>
<script lang="ts" setup>
import type { SelectOptionData } from '@arco-design/web-vue/es/select/interface';
import type { OperatorLogQueryRequest } from '@/api/user/operator-log';
import { ref } from 'vue';
import useLoading from '@/hooks/loading';
import useVisible from '@/hooks/visible';
import { getOperatorLogCount, clearOperatorLog } from '@/api/user/operator-log';
import { Message, Modal } from '@arco-design/web-vue';
import { useDictStore } from '@/store';
import { operatorLogModuleKey, operatorLogResultKey, operatorLogTypeKey, operatorRiskLevelKey } from '@/views/user/operator-log/types/const';
import { labelFilter } from '@/types/form';
import UserSelector from '@/components/user/user/user-selector.vue';
const { $state: dictState, toOptions } = useDictStore();
const { visible, setVisible } = useVisible();
const { loading, setLoading } = useLoading();
const defaultForm = (): OperatorLogQueryRequest => {
return {
module: undefined,
type: undefined,
riskLevel: undefined,
result: undefined,
startTimeRange: undefined,
};
};
const typeOptions = ref<SelectOptionData[]>(toOptions(operatorLogTypeKey));
const formModel = ref<OperatorLogQueryRequest>({});
const emits = defineEmits(['clear']);
// 打开
const open = () => {
renderForm({ ...defaultForm() });
setVisible(true);
};
// 渲染表单
const renderForm = (record: any) => {
formModel.value = Object.assign({}, record);
};
defineExpose({ open });
// 选择类型
const selectedModule = (module: string) => {
if (!module) {
// 不选择则重置 options
typeOptions.value = toOptions(operatorLogTypeKey);
return;
}
const moduleArr = module.split(':');
const modulePrefix = moduleArr[moduleArr.length - 1] + ':';
// 渲染 options
typeOptions.value = dictState[operatorLogTypeKey].filter(s => (s.value as string).startsWith(modulePrefix));
// 渲染输入框
if (formModel.value.type && !formModel.value.type.startsWith(modulePrefix)) {
formModel.value.type = undefined;
}
};
// 确定
const handlerOk = async () => {
setLoading(true);
try {
// 获取总数量
const { data } = await getOperatorLogCount(formModel.value);
if (data) {
// 清空
doClear(data);
} else {
// 无数据
Message.warning('当前条件未查询到数据');
}
} catch (e) {
} finally {
setLoading(false);
}
return false;
};
// 执行删除
const doClear = (count: number) => {
Modal.confirm({
title: '删除清空',
content: `确定要删除 ${count} 条数据吗? 确定后将立即删除且无法恢复!`,
onOk: async () => {
setLoading(true);
try {
// 调用删除
await clearOperatorLog(formModel.value);
emits('clear');
// 清空
setVisible(false);
handlerClear();
} catch (e) {
} finally {
setLoading(false);
}
}
});
};
// 关闭
const handleClose = () => {
handlerClear();
};
// 清空
const handlerClear = () => {
setLoading(false);
};
</script>
<style lang="less" scoped>
</style>

View File

@@ -0,0 +1,167 @@
<template>
<!-- 表格 -->
<a-table row-key="id"
class="table-wrapper-8"
ref="tableRef"
:loading="loading"
:columns="tableColumns"
:data="tableRenderData"
:pagination="pagination"
@page-change="(page) => fetchTableData(page, pagination.pageSize)"
@page-size-change="(size) => fetchTableData(1, size)"
:bordered="false">
<!-- 操作模块 -->
<template #module="{ record }">
{{ getDictValue(operatorLogModuleKey, record.module) }}
<icon-oblique-line />
{{ getDictValue(operatorLogTypeKey, record.type) }}
</template>
<!-- 风险等级 -->
<template #riskLevel="{ record }">
<a-tag :color="getDictValue(operatorRiskLevelKey, record.riskLevel, 'color')">
{{ getDictValue(operatorRiskLevelKey, record.riskLevel) }}
</a-tag>
</template>
<!-- 执行结果 -->
<template #result="{ record }">
<a-tag :color="getDictValue(operatorLogResultKey, record.result, 'color')">
{{ getDictValue(operatorLogResultKey, record.result) }}
</a-tag>
</template>
<!-- 操作日志 -->
<template #originLogInfo="{ record }">
<icon-copy class="copy-left" @click="copy(record.originLogInfo, '已复制')" />
<span v-html="replaceHtmlTag(record.logInfo)" />
</template>
<!-- 操作 -->
<template #handle="{ record }">
<div class="table-handle-wrapper">
<!-- 详情 -->
<a-button type="text"
size="mini"
@click="openLogDetail(record)">
详情
</a-button>
</div>
</template>
</a-table>
<!-- json 查看器模态框 -->
<json-editor-modal ref="jsonView" />
</template>
<script lang="ts">
export default {
name: 'userOperatorLogSimpleTable'
};
</script>
<script lang="ts" setup>
import type { OperatorLogQueryRequest, OperatorLogQueryResponse } from '@/api/user/operator-log';
import { ref, onMounted, onBeforeMount } from 'vue';
import { operatorLogModuleKey, operatorLogTypeKey, operatorRiskLevelKey, operatorLogResultKey, dictKeys } from '../types/const';
import columns from '../types/table.columns';
import { getLogDetail } from '../types/const';
import useCopy from '@/hooks/copy';
import useLoading from '@/hooks/loading';
import { usePagination } from '@/types/table';
import { useDictStore } from '@/store';
import { getOperatorLogPage } from '@/api/user/operator-log';
import { getCurrentUserOperatorLog } from '@/api/user/mine';
import { replaceHtmlTag, clearHtmlTag } from '@/utils';
import JsonEditorModal from '@/components/view/json-editor/json-editor-modal.vue';
const props = defineProps({
handleColumn: {
type: Boolean,
default: true
},
current: {
type: Boolean,
default: false
},
baseParams: {
type: Object,
default: () => {
return {};
}
}
});
const pagination = usePagination();
const { loading, setLoading } = useLoading();
const { getDictValue } = useDictStore();
const { copy } = useCopy();
const jsonView = ref();
const tableColumns = ref();
const tableRenderData = ref<OperatorLogQueryResponse[]>([]);
// 查看详情
const openLogDetail = (record: OperatorLogQueryResponse) => {
jsonView.value.open(getLogDetail(record));
};
// 加载数据
const doFetchTableData = async (request: OperatorLogQueryRequest) => {
try {
setLoading(true);
let rows;
if (props.current) {
// 查询当前用户
const { data } = await getCurrentUserOperatorLog(request);
rows = data;
} else {
// 查询所有
const { data } = await getOperatorLogPage({ ...request, ...props.baseParams });
rows = data;
}
tableRenderData.value = rows.rows.map(s => {
return { ...s, originLogInfo: clearHtmlTag(s.logInfo) };
});
pagination.total = rows.total;
pagination.current = request.page;
pagination.pageSize = request.limit;
} catch (e) {
} finally {
setLoading(false);
}
};
// 切换页码
const fetchTableData = (page = 1, limit = pagination.pageSize, form = {}) => {
doFetchTableData({ page, limit, ...form });
};
defineExpose({
fetchTableData
});
// 加载字典值
onBeforeMount(async () => {
const dictStore = useDictStore();
await dictStore.loadKeys(dictKeys);
});
// 初始化
onMounted(() => {
let cols = [...columns].map(s => {
return { ...s };
}).filter(s => s.dataIndex !== 'username');
if (props.handleColumn) {
const handleCol = cols.find(s => s.dataIndex === 'handle');
// 设置操作项宽度
if (handleCol) {
handleCol.width = 80;
}
} else {
// 不显示操作
cols = cols.filter(s => s.dataIndex !== 'handle');
}
tableColumns.value = cols;
fetchTableData();
});
</script>
<style lang="less" scoped>
</style>

View File

@@ -1,117 +1,191 @@
<template> <template>
<a-table row-key="id" <!-- 查询头 -->
class="table-wrapper-8" <a-card class="general-card table-search-card">
ref="tableRef" <!-- 查询头组件 -->
:loading="loading" <operator-log-query-header @submit="(e) => fetchTableData(undefined, undefined, e)" />
:columns="tableColumns" </a-card>
:data="tableRenderData" <!-- 表格 -->
:pagination="pagination" <a-card class="general-card table-card">
@page-change="(page) => fetchTableData(page, pagination.pageSize)" <template #title>
@page-size-change="(size) => fetchTableData(1, size)" <!-- 左侧操作 -->
:bordered="false"> <div class="table-left-bar-handle">
<!-- 操作模块 --> <!-- 标题 -->
<template #module="{ record }"> <div class="table-title">
{{ getDictValue(operatorLogModuleKey, record.module) }} 操作日志
</template> </div>
<!-- 操作类型 --> </div>
<template #type="{ record }"> <!-- 右侧操作 -->
{{ getDictValue(operatorLogTypeKey, record.type) }} <div class="table-right-bar-handle">
</template> <a-space>
<!-- 风险等级 --> <!-- 清空 -->
<template #riskLevel="{ record }"> <a-button v-permission="['infra:operator-log:clear']"
<a-tag :color="getDictValue(operatorRiskLevelKey, record.riskLevel, 'color')"> status="danger"
{{ getDictValue(operatorRiskLevelKey, record.riskLevel) }} @click="openClear">
</a-tag> 清空
</template> <template #icon>
<!-- 执行结果 --> <icon-close />
<template #result="{ record }"> </template>
<a-tag :color="getDictValue(operatorLogResultKey, record.result, 'color')"> </a-button>
{{ getDictValue(operatorLogResultKey, record.result) }} <!-- 删除 -->
</a-tag> <a-popconfirm :content="`确认删除选中的 ${selectedKeys.length} 条记录吗?`"
</template> position="br"
<!-- 操作日志 --> type="warning"
<template #originLogInfo="{ record }"> @ok="deleteSelectRows">
<icon-copy class="copy-left" @click="copy(record.originLogInfo, '已复制')" /> <a-button v-permission="['infra:operator-log:delete']"
<span v-html="replaceHtmlTag(record.logInfo)" /> type="secondary"
</template> status="danger"
<!-- 操作 --> :disabled="selectedKeys.length === 0">
<template #handle="{ record }"> 删除
<div class="table-handle-wrapper"> <template #icon>
<!-- 详情 --> <icon-delete />
<a-button type="text" </template>
size="mini" </a-button>
@click="viewDetail(record)"> </a-popconfirm>
详情 </a-space>
</a-button>
</div> </div>
</template> </template>
</a-table> <!-- 表格 -->
<a-table row-key="id"
class="table-wrapper-8"
ref="tableRef"
:loading="loading"
v-model:selected-keys="selectedKeys"
:row-selection="rowSelection"
:columns="columns"
:data="tableRenderData"
:pagination="pagination"
@page-change="(page) => fetchTableData(page, pagination.pageSize)"
@page-size-change="(size) => fetchTableData(1, size)"
:bordered="false">
<!-- 操作模块 -->
<template #module="{ record }">
{{ getDictValue(operatorLogModuleKey, record.module) }}
<icon-oblique-line />
{{ getDictValue(operatorLogTypeKey, record.type) }}
</template>
<!-- 风险等级 -->
<template #riskLevel="{ record }">
<a-tag :color="getDictValue(operatorRiskLevelKey, record.riskLevel, 'color')">
{{ getDictValue(operatorRiskLevelKey, record.riskLevel) }}
</a-tag>
</template>
<!-- 执行结果 -->
<template #result="{ record }">
<a-tag :color="getDictValue(operatorLogResultKey, record.result, 'color')">
{{ getDictValue(operatorLogResultKey, record.result) }}
</a-tag>
</template>
<!-- 操作日志 -->
<template #originLogInfo="{ record }">
<icon-copy class="copy-left" @click="copy(record.originLogInfo, '已复制')" />
<span v-html="replaceHtmlTag(record.logInfo)" />
</template>
<!-- 操作 -->
<template #handle="{ record }">
<div class="table-handle-wrapper">
<!-- 详情 -->
<a-button type="text"
size="mini"
@click="openLogDetail(record)">
详情
</a-button>
<!-- 删除 -->
<a-popconfirm content="确认删除这条记录吗?"
position="left"
type="warning"
@ok="deleteRow(record)">
<a-button v-permission="['infra:operator-log:delete']"
type="text"
size="mini"
status="danger">
删除
</a-button>
</a-popconfirm>
</div>
</template>
</a-table>
</a-card>
<!-- 清理模态框 -->
<operator-log-clear-modal ref="clearModal"
@clear="fetchTableData" />
<!-- json 查看器模态框 -->
<json-editor-modal ref="jsonView" />
</template> </template>
<script lang="ts"> <script lang="ts">
export default { export default {
name: 'operatorLogTable' name: 'userOperatorLogTable'
}; };
</script> </script>
<script lang="ts" setup> <script lang="ts" setup>
import type { OperatorLogQueryRequest, OperatorLogQueryResponse } from '@/api/user/operator-log'; import type { OperatorLogQueryRequest, OperatorLogQueryResponse } from '@/api/user/operator-log';
import { ref, onMounted, onBeforeMount } from 'vue'; import { ref, onMounted } from 'vue';
import { operatorLogModuleKey, operatorLogTypeKey, operatorRiskLevelKey, operatorLogResultKey, dictKeys } from '../types/const'; import { operatorLogModuleKey, operatorLogTypeKey, operatorRiskLevelKey, operatorLogResultKey, getLogDetail } from '../types/const';
import columns from '../types/table.columns'; import columns from '../types/table.columns';
import useLoading from '@/hooks/loading';
import { usePagination } from '@/types/table';
import { useDictStore } from '@/store';
import { getOperatorLogPage } from '@/api/user/operator-log';
import { replaceHtmlTag, clearHtmlTag, dateFormat } from '@/utils';
import { pick } from 'lodash';
import { getCurrentUserOperatorLog } from '@/api/user/mine';
import useCopy from '@/hooks/copy'; import useCopy from '@/hooks/copy';
import useLoading from '@/hooks/loading';
const emits = defineEmits(['viewDetail']); import { usePagination, useRowSelection } from '@/types/table';
const props = defineProps({ import { useDictStore } from '@/store';
visibleUser: { import { getOperatorLogPage, deleteOperatorLog } from '@/api/user/operator-log';
type: Boolean, import { replaceHtmlTag, clearHtmlTag } from '@/utils';
default: true import { Message } from '@arco-design/web-vue';
}, import OperatorLogQueryHeader from './operator-log-query-header.vue';
visibleHandle: { import OperatorLogClearModal from './operator-log-clear-modal.vue';
type: Boolean, import JsonEditorModal from '@/components/view/json-editor/json-editor-modal.vue';
default: true
},
current: {
type: Boolean,
default: false
},
baseParams: {
type: Object,
default: () => {
return {};
}
}
});
const pagination = usePagination(); const pagination = usePagination();
const rowSelection = useRowSelection();
const { loading, setLoading } = useLoading(); const { loading, setLoading } = useLoading();
const { getDictValue } = useDictStore(); const { getDictValue } = useDictStore();
const { copy } = useCopy(); const { copy } = useCopy();
const tableColumns = ref(); const clearModal = ref();
const jsonView = ref();
const tableRenderData = ref<OperatorLogQueryResponse[]>([]); const tableRenderData = ref<OperatorLogQueryResponse[]>([]);
const selectedKeys = ref<number[]>([]);
// 查看详情 // 查看详情
const viewDetail = (record: OperatorLogQueryResponse) => { const openLogDetail = (record: OperatorLogQueryResponse) => {
jsonView.value.open(getLogDetail(record));
};
// 打开清空
const openClear = () => {
clearModal.value?.open();
};
// 删除选中行
const deleteSelectRows = async () => {
try { try {
const detail = Object.assign({} as Record<string, any>, setLoading(true);
pick(record, 'traceId', 'address', 'location', // 调用删除接口
'userAgent', 'errorMessage')); await deleteOperatorLog(selectedKeys.value);
detail.duration = `${record.duration} ms`; Message.success(`成功删除 ${selectedKeys.value.length} 条数据`);
detail.startTime = dateFormat(new Date(record.startTime)); selectedKeys.value = [];
detail.endTime = dateFormat(new Date(record.endTime)); // 重新加载数据
detail.extra = JSON.parse(record?.extra); fetchTableData();
detail.returnValue = JSON.parse(record?.returnValue);
emits('viewDetail', detail);
} catch (e) { } catch (e) {
emits('viewDetail', record); } finally {
setLoading(false);
}
};
// 删除当前行
const deleteRow = async ({ id }: {
id: number
}) => {
try {
setLoading(true);
// 调用删除接口
await deleteOperatorLog([id]);
Message.success('删除成功');
selectedKeys.value = [];
// 重新加载数据
fetchTableData();
} catch (e) {
} finally {
setLoading(false);
} }
}; };
@@ -119,22 +193,14 @@
const doFetchTableData = async (request: OperatorLogQueryRequest) => { const doFetchTableData = async (request: OperatorLogQueryRequest) => {
try { try {
setLoading(true); setLoading(true);
let rows; const { data } = await getOperatorLogPage(request);
if (props.current) { tableRenderData.value = data.rows.map(s => {
// 查询当前用户
const { data } = await getCurrentUserOperatorLog(request);
rows = data;
} else {
// 查询所有
const { data } = await getOperatorLogPage({ ...request, ...props.baseParams });
rows = data;
}
tableRenderData.value = rows.rows.map(s => {
return { ...s, originLogInfo: clearHtmlTag(s.logInfo) }; return { ...s, originLogInfo: clearHtmlTag(s.logInfo) };
}); });
pagination.total = rows.total; pagination.total = data.total;
pagination.current = request.page; pagination.current = request.page;
pagination.pageSize = request.limit; pagination.pageSize = request.limit;
selectedKeys.value = [];
} catch (e) { } catch (e) {
} finally { } finally {
setLoading(false); setLoading(false);
@@ -146,31 +212,11 @@
doFetchTableData({ page, limit, ...form }); doFetchTableData({ page, limit, ...form });
}; };
// 加载字典值
onBeforeMount(async () => {
const dictStore = useDictStore();
await dictStore.loadKeys(dictKeys);
});
// 初始化 // 初始化
onMounted(() => { onMounted(() => {
let cols = columns;
// 不显示用户
if (!props.visibleUser) {
cols = cols.filter(s => s.dataIndex !== 'username');
}
// 不显示操作
if (!props.visibleHandle) {
cols = cols.filter(s => s.dataIndex !== 'handle');
}
tableColumns.value = cols;
fetchTableData(); fetchTableData();
}); });
defineExpose({
fetchTableData
});
</script> </script>
<style lang="less" scoped> <style lang="less" scoped>

View File

@@ -1,26 +1,6 @@
<template> <template>
<div class="layout-container"> <div class="layout-container" v-if="render">
<!-- 查询头 --> <operator-log-table />
<a-card class="general-card table-search-card">
<!-- 查询头组件 -->
<operator-log-query-header @submit="(e) => table.fetchTableData(undefined, undefined, e)" />
</a-card>
<!-- 表格 -->
<a-card class="general-card table-card">
<template #title>
<!-- 左侧操作 -->
<div class="table-left-bar-handle">
<!-- 标题 -->
<div class="table-title">
操作日志
</div>
</div>
</template>
<!-- 表格组件 -->
<operator-log-table ref="table" @viewDetail="(e) => view.open(e)" />
</a-card>
<!-- json 查看器模态框 -->
<json-editor-modal ref="view" />
</div> </div>
</template> </template>
@@ -31,16 +11,21 @@
</script> </script>
<script lang="ts" setup> <script lang="ts" setup>
import { ref, onUnmounted } from 'vue'; import { ref, onUnmounted, onBeforeMount } from 'vue';
import { useCacheStore } from '@/store'; import { useCacheStore, useDictStore } from '@/store';
import OperatorLogQueryHeader from './components/operator-log-query-header.vue'; import { dictKeys } from './types/const';
import OperatorLogTable from './components/operator-log-table.vue'; import OperatorLogTable from './components/operator-log-table.vue';
import JsonEditorModal from '@/components/view/json-editor/json-editor-modal.vue';
const cacheStore = useCacheStore(); const cacheStore = useCacheStore();
const table = ref(); const render = ref(false);
const view = ref();
// 加载字典值
onBeforeMount(async () => {
const dictStore = useDictStore();
await dictStore.loadKeys(dictKeys);
render.value = true;
});
// 卸载时清除 cache // 卸载时清除 cache
onUnmounted(() => { onUnmounted(() => {

View File

@@ -45,7 +45,7 @@ const columns = [
title: '操作', title: '操作',
dataIndex: 'handle', dataIndex: 'handle',
slotName: 'handle', slotName: 'handle',
width: 138, width: 128,
align: 'center', align: 'center',
fixed: 'right', fixed: 'right',
}, },