|
|
@@ -0,0 +1,696 @@
|
|
|
+<template>
|
|
|
+ <div>
|
|
|
+ <div class="gva-search-box flex">
|
|
|
+ <el-form
|
|
|
+ ref="elSearchFormRef"
|
|
|
+ :inline="true"
|
|
|
+ :model="searchInfo"
|
|
|
+ class="demo-form-inline"
|
|
|
+ @keyup.enter="onSubmit"
|
|
|
+ >
|
|
|
+ <el-form-item label="日志类型">
|
|
|
+ <el-select
|
|
|
+ v-model="logType"
|
|
|
+ placeholder="请选择日志类型"
|
|
|
+ value-key="id"
|
|
|
+ style="width: 150px"
|
|
|
+ @change="onLogTypeChange"
|
|
|
+ >
|
|
|
+ <el-option
|
|
|
+ v-for="item in logTypeOptions"
|
|
|
+ :key="item.id"
|
|
|
+ :label="item.label"
|
|
|
+ :value="item.id"
|
|
|
+ />
|
|
|
+ </el-select>
|
|
|
+ </el-form-item>
|
|
|
+
|
|
|
+ <el-form-item label="用户名称">
|
|
|
+ <el-input
|
|
|
+ v-model="searchInfo.userName"
|
|
|
+ placeholder="搜索条件"
|
|
|
+ />
|
|
|
+ </el-form-item>
|
|
|
+ <el-form-item>
|
|
|
+ <el-button
|
|
|
+ v-preReClick
|
|
|
+ icon="search"
|
|
|
+ type="primary"
|
|
|
+ @click="onSubmit"
|
|
|
+ >查询
|
|
|
+ </el-button>
|
|
|
+ <el-button
|
|
|
+ icon="refresh"
|
|
|
+ @click="onReset"
|
|
|
+ >重置
|
|
|
+ </el-button>
|
|
|
+ </el-form-item>
|
|
|
+ </el-form>
|
|
|
+ </div>
|
|
|
+ <div class="gva-table-box">
|
|
|
+ <!-- table-header-button -->
|
|
|
+ <!-- 新增批量操作按钮 -->
|
|
|
+ <div class="gva-btn-list">
|
|
|
+ <el-button
|
|
|
+ type="primary"
|
|
|
+ @click="multipleAudit"
|
|
|
+ >
|
|
|
+ 批量审计
|
|
|
+ </el-button>
|
|
|
+ <el-button
|
|
|
+ type="primary"
|
|
|
+ @click="multipleArchive"
|
|
|
+ >
|
|
|
+ 归档
|
|
|
+ </el-button>
|
|
|
+ <el-button
|
|
|
+ type="primary"
|
|
|
+ @click="archiveByDate"
|
|
|
+ >
|
|
|
+ 按日期归档
|
|
|
+ </el-button>
|
|
|
+ </div>
|
|
|
+ <!-- 表格 -->
|
|
|
+ <el-table
|
|
|
+ v-show="logType === 1"
|
|
|
+ ref="elTableRef"
|
|
|
+ :data="tableData"
|
|
|
+ style="width: 100%"
|
|
|
+ @selection-change="handleSelectionChange"
|
|
|
+ >
|
|
|
+ <el-table-column
|
|
|
+ :selectable="handleSelectable"
|
|
|
+ type="selection"
|
|
|
+ width="55"
|
|
|
+ />
|
|
|
+ <el-table-column
|
|
|
+ align="center"
|
|
|
+ fixed
|
|
|
+ header-align="center"
|
|
|
+ label="接口请求路径"
|
|
|
+ prop="uriPath"
|
|
|
+ width="200"
|
|
|
+ />
|
|
|
+
|
|
|
+ <el-table-column
|
|
|
+ align="center"
|
|
|
+ header-align="center"
|
|
|
+ label="用户IP"
|
|
|
+ prop="clientIp"
|
|
|
+ width="200"
|
|
|
+ />
|
|
|
+ <el-table-column
|
|
|
+ align="center"
|
|
|
+ header-align="center"
|
|
|
+ label="请求耗时"
|
|
|
+ prop="durationMs"
|
|
|
+ width="200"
|
|
|
+ >
|
|
|
+ <template #default="scope">
|
|
|
+ {{ scope.row.durationMs }} ms
|
|
|
+ </template>
|
|
|
+ </el-table-column>
|
|
|
+ <el-table-column
|
|
|
+ align="center"
|
|
|
+ header-align="center"
|
|
|
+ label="操作时间"
|
|
|
+ prop="createdAt"
|
|
|
+ width="300"
|
|
|
+ />
|
|
|
+ <el-table-column
|
|
|
+ align="center"
|
|
|
+ header-align="center"
|
|
|
+ label="审计状态"
|
|
|
+ prop="auditStatus"
|
|
|
+ width="180"
|
|
|
+ >
|
|
|
+ <template #default="scope">
|
|
|
+ <span :style="getStatusClass(scope.row.auditStatus)">
|
|
|
+ {{ statusMap[scope.row.auditStatus] }}
|
|
|
+ </span>
|
|
|
+ </template>
|
|
|
+ </el-table-column>
|
|
|
+ <el-table-column
|
|
|
+ align="center"
|
|
|
+ fixed="right"
|
|
|
+ header-align="center"
|
|
|
+ label="操作"
|
|
|
+ >
|
|
|
+ <template #default="scope">
|
|
|
+ <el-button
|
|
|
+ link
|
|
|
+ type="primary"
|
|
|
+ @click="getDetail(scope.row)"
|
|
|
+ >查看详情
|
|
|
+ </el-button>
|
|
|
+ <el-button
|
|
|
+ link
|
|
|
+ type="primary"
|
|
|
+ @click="audit(scope.row)"
|
|
|
+ >审计
|
|
|
+ </el-button>
|
|
|
+
|
|
|
+ </template>
|
|
|
+ </el-table-column>
|
|
|
+ </el-table>
|
|
|
+ <el-table
|
|
|
+ v-show="logType === 2"
|
|
|
+ ref="elTableRef"
|
|
|
+ :data="tableData"
|
|
|
+ style="width: 100%"
|
|
|
+ @selection-change="handleSelectionChange"
|
|
|
+ >
|
|
|
+ <el-table-column
|
|
|
+ :selectable="handleSelectable"
|
|
|
+ type="selection"
|
|
|
+ width="55"
|
|
|
+ />
|
|
|
+ <el-table-column
|
|
|
+ align="center"
|
|
|
+ fixed
|
|
|
+ header-align="center"
|
|
|
+ label="应用名称"
|
|
|
+ prop="serviceName"
|
|
|
+ width="120"
|
|
|
+ />
|
|
|
+ <el-table-column
|
|
|
+ align="center"
|
|
|
+ header-align="center"
|
|
|
+ label="请求方式"
|
|
|
+ prop="method"
|
|
|
+ width="150"
|
|
|
+ />
|
|
|
+ <el-table-column
|
|
|
+ align="center"
|
|
|
+ header-align="center"
|
|
|
+ label="请求者IP"
|
|
|
+ prop="clientIp"
|
|
|
+ width="160"
|
|
|
+ />
|
|
|
+ <el-table-column
|
|
|
+ align="center"
|
|
|
+ header-align="center"
|
|
|
+ label="响应时间"
|
|
|
+ prop="durationMs"
|
|
|
+ width="80"
|
|
|
+ >
|
|
|
+ <template #default="scope">
|
|
|
+ {{ scope.row.durationMs }} ms
|
|
|
+ </template>
|
|
|
+ </el-table-column>
|
|
|
+ <el-table-column
|
|
|
+ align="center"
|
|
|
+ header-align="center"
|
|
|
+ label="错误信息"
|
|
|
+ prop="errorMsg"
|
|
|
+ width="300"
|
|
|
+ />
|
|
|
+ <el-table-column
|
|
|
+ align="center"
|
|
|
+ fixed
|
|
|
+ header-align="center"
|
|
|
+ label="操作时间"
|
|
|
+ prop="createdAt"
|
|
|
+ width="250"
|
|
|
+ />
|
|
|
+ <el-table-column
|
|
|
+ align="center"
|
|
|
+ header-align="center"
|
|
|
+ label="审计状态"
|
|
|
+ prop="auditStatus"
|
|
|
+ width="80"
|
|
|
+ >
|
|
|
+ <template #default="scope">
|
|
|
+ <span :style="getStatusClass(scope.row.auditStatus)">
|
|
|
+ {{ statusMap[scope.row.auditStatus] }}
|
|
|
+ </span>
|
|
|
+ </template>
|
|
|
+ </el-table-column>
|
|
|
+ <el-table-column
|
|
|
+ align="center"
|
|
|
+ fixed="right"
|
|
|
+ header-align="center"
|
|
|
+ label="操作"
|
|
|
+ >
|
|
|
+ <template #default="scope">
|
|
|
+ <el-button
|
|
|
+ link
|
|
|
+ type="primary"
|
|
|
+ @click="getDetail(scope.row)"
|
|
|
+ >查看详情
|
|
|
+ </el-button>
|
|
|
+ <el-button
|
|
|
+ link
|
|
|
+ type="primary"
|
|
|
+ @click="audit(scope.row)"
|
|
|
+ >审计
|
|
|
+ </el-button>
|
|
|
+ <el-button
|
|
|
+ link
|
|
|
+ type="primary"
|
|
|
+ @click="viewLink(scope.row)"
|
|
|
+ >查看完整链路
|
|
|
+ </el-button>
|
|
|
+ </template>
|
|
|
+ </el-table-column>
|
|
|
+ </el-table>
|
|
|
+ </div>
|
|
|
+ <!-- 分页栏 -->
|
|
|
+ <div class="gva-pagination">
|
|
|
+ <el-pagination
|
|
|
+ :current-page="page"
|
|
|
+ :page-size="pageSize"
|
|
|
+ :page-sizes="[10, 30, 50, 100]"
|
|
|
+ :total="total"
|
|
|
+ layout="total, sizes, prev, pager, next, jumper"
|
|
|
+ @current-change="handleCurrentChange"
|
|
|
+ @size-change="handleSizeChange"
|
|
|
+ />
|
|
|
+ </div>
|
|
|
+ <!-- 详情弹窗 -->
|
|
|
+ <el-dialog
|
|
|
+ v-model="detailDialogVisible"
|
|
|
+ width="70%"
|
|
|
+ >
|
|
|
+ <el-descriptions
|
|
|
+ title="操作日志详情"
|
|
|
+ border
|
|
|
+ :column="1"
|
|
|
+ >
|
|
|
+ <el-descriptions-item label="日志标识">{{ detailData.iden }}</el-descriptions-item>
|
|
|
+ <el-descriptions-item label="全链路TraceId">{{ detailData.traceId }}</el-descriptions-item>
|
|
|
+ <el-descriptions-item label="子链路SpanId">{{ detailData.spanId }}</el-descriptions-item>
|
|
|
+ <el-descriptions-item label="父SpanId">{{ detailData.parentSpanId }}</el-descriptions-item>
|
|
|
+ <el-descriptions-item label="日志类型">{{ detailData.type === 1 ? '操作日志':'接口日志' }}</el-descriptions-item>
|
|
|
+ <el-descriptions-item label="所属服务名称">{{ detailData.serviceName }}</el-descriptions-item>
|
|
|
+ <el-descriptions-item label="接口请求路径">{{ detailData.uriPath }}</el-descriptions-item>
|
|
|
+ <el-descriptions-item label="请求方法">{{ detailData.httpMethod }}</el-descriptions-item>
|
|
|
+ <el-descriptions-item label="业务方法名">{{ detailData.method }}</el-descriptions-item>
|
|
|
+ <el-descriptions-item label="日志级别">{{ detailData.level }}</el-descriptions-item>
|
|
|
+ <el-descriptions-item label="调用接口应用标识">{{ detailData.appKey }}</el-descriptions-item>
|
|
|
+ <el-descriptions-item label="用户ID">{{ detailData.userId }}</el-descriptions-item>
|
|
|
+ <el-descriptions-item label="用户名称">{{ detailData.userName }}</el-descriptions-item>
|
|
|
+ <el-descriptions-item label="用户IP">{{ detailData.clientIp }}</el-descriptions-item>
|
|
|
+ <el-descriptions-item label="请求头">{{ detailData.requestHeader }}</el-descriptions-item>
|
|
|
+ <el-descriptions-item label="请求体摘要">{{ detailData.requestBodyDigest }}</el-descriptions-item>
|
|
|
+ <el-descriptions-item label="请求体FileKey">{{ detailData.requestBodyFileKey }}</el-descriptions-item>
|
|
|
+ <el-descriptions-item label="响应码">{{ detailData.responseCode }}</el-descriptions-item>
|
|
|
+ <el-descriptions-item label="响应体摘要">{{ detailData.responseBodyDigest }}</el-descriptions-item>
|
|
|
+ <el-descriptions-item label="响应体FileKey">{{ detailData.responseBodyFileKey }}</el-descriptions-item>
|
|
|
+ <el-descriptions-item label="错误信息">{{ detailData.errorMsg }}</el-descriptions-item>
|
|
|
+ <el-descriptions-item label="请求耗时">{{ detailData.durationMs }}</el-descriptions-item>
|
|
|
+ <el-descriptions-item label="操作时间">{{ detailData.createdAt }}</el-descriptions-item>
|
|
|
+ <el-descriptions-item label="Hmac值">{{ detailData.hmac }}</el-descriptions-item>
|
|
|
+ <el-descriptions-item label="审计状态">{{ statusMap[detailData.auditStatus] }}</el-descriptions-item>
|
|
|
+ </el-descriptions>
|
|
|
+ </el-dialog>
|
|
|
+ <!-- 按日期归档弹窗 -->
|
|
|
+ <el-dialog
|
|
|
+ v-model="archiveDialogVisible"
|
|
|
+ width="30%"
|
|
|
+ >
|
|
|
+ <el-form
|
|
|
+ ref="elArchiveFormRef"
|
|
|
+ :model="archiveInfo"
|
|
|
+ label-width="100px"
|
|
|
+ :rules="archiveRules"
|
|
|
+ class="demo-form-inline"
|
|
|
+ >
|
|
|
+ <el-form-item label="归档任务名称">
|
|
|
+ <el-input
|
|
|
+ v-model="archiveInfo.name"
|
|
|
+ :placeholder="`${userStore.userInfo.userName}_manual_archive`"
|
|
|
+ />
|
|
|
+ </el-form-item>
|
|
|
+ <el-form-item label="归档起始日期" prop="startDate">
|
|
|
+ <el-date-picker
|
|
|
+ v-model="archiveInfo.startDate"
|
|
|
+ type="date"
|
|
|
+ placeholder="选择日期"
|
|
|
+ />
|
|
|
+ </el-form-item>
|
|
|
+ <el-form-item label="归档结束日期" prop="endDate">
|
|
|
+ <el-date-picker
|
|
|
+ v-model="archiveInfo.endDate"
|
|
|
+ type="date"
|
|
|
+ placeholder="选择日期"
|
|
|
+ />
|
|
|
+ </el-form-item>
|
|
|
+ <el-form-item>
|
|
|
+ <el-button
|
|
|
+ v-preReClick
|
|
|
+ type="primary"
|
|
|
+ @click="onArchiveSubmit"
|
|
|
+ >归档
|
|
|
+ </el-button>
|
|
|
+ </el-form-item>
|
|
|
+ </el-form>
|
|
|
+ </el-dialog>
|
|
|
+ <!-- 查看完整链路弹窗 -->
|
|
|
+ <el-dialog
|
|
|
+ v-model="linkDialogVisible"
|
|
|
+ title="日志完整链路"
|
|
|
+ width="70%"
|
|
|
+ >
|
|
|
+ <el-steps direction="horizontal"
|
|
|
+ align-center>
|
|
|
+ <el-step
|
|
|
+ v-for="(activity, index) in activities"
|
|
|
+ :key="activity.id || index"
|
|
|
+ :title="activity.method"
|
|
|
+ :status="activeStep === index + 1 ? 'finish' : 'wait'"
|
|
|
+ >
|
|
|
+ <template #description>
|
|
|
+ <div
|
|
|
+ class="step-description text-sm mb-1 space-y-2"
|
|
|
+ :class="activity.errorMsg ? 'text-red-500' : 'text-gray-500'"
|
|
|
+ >
|
|
|
+ <div class="step-description-wrapper">
|
|
|
+ <div>{{ activity.timestamp }}</div>
|
|
|
+ <div class="text-xs">{{ activity.type === 1 ? '操作日志':'接口日志' }}</div>
|
|
|
+ <div class="text-xs">服务: {{ activity.serviceName }}</div>
|
|
|
+ <div class="text-xs">路径: {{ activity.uriPath }}</div>
|
|
|
+ <div class="text-xs">耗时: {{ activity.durationMs }}ms</div>
|
|
|
+ <div v-if="activity.errorMsg" class="text-xs mt-1">
|
|
|
+ 错误信息: {{ activity.errorMsg }}
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </template>
|
|
|
+ </el-step>
|
|
|
+ </el-steps>
|
|
|
+
|
|
|
+
|
|
|
+ </el-dialog>
|
|
|
+ </div>
|
|
|
+</template>
|
|
|
+
|
|
|
+<script setup>
|
|
|
+import { ref } from 'vue'
|
|
|
+
|
|
|
+import { ElMessage, ElMessageBox } from 'element-plus'
|
|
|
+import { archiveLogList, auditLogList, getLogDetail, getLogList, getTraceLog } from '@/api/sys/sysLog'
|
|
|
+import { useUserStore } from '@/pinia/modules/user'
|
|
|
+
|
|
|
+defineOptions({
|
|
|
+ name: 'MaskLogList'
|
|
|
+})
|
|
|
+
|
|
|
+const userStore = useUserStore()
|
|
|
+
|
|
|
+const logType = ref(1)
|
|
|
+const statusMap = {
|
|
|
+ 1: '未审计',
|
|
|
+ 2: '审计通过',
|
|
|
+ 3: '审计异常'
|
|
|
+}
|
|
|
+const getStatusClass = (status) => {
|
|
|
+ switch (status) {
|
|
|
+ case 1:
|
|
|
+ return { color: '#f56c6c' }
|
|
|
+ case 2:
|
|
|
+ return { color: '#67C23A' }
|
|
|
+ case 3:
|
|
|
+ return { color: '#E6A23C' }
|
|
|
+ default:
|
|
|
+ return ''
|
|
|
+ }
|
|
|
+}
|
|
|
+const logTypeOptions = ref([
|
|
|
+ { id: 1,
|
|
|
+ label: '操作日志',
|
|
|
+ value: '1'
|
|
|
+ },
|
|
|
+ { id: 2,
|
|
|
+ label: '接口日志',
|
|
|
+ value: '2'
|
|
|
+ }])
|
|
|
+const page = ref(1)
|
|
|
+const pageSize = ref(10)
|
|
|
+const total = ref(0)
|
|
|
+const handleCurrentChange = val => {
|
|
|
+ page.value = val
|
|
|
+ getTableData()
|
|
|
+}
|
|
|
+const handleSizeChange = val => {
|
|
|
+ pageSize.value = val
|
|
|
+ getTableData()
|
|
|
+}
|
|
|
+
|
|
|
+const tableData = ref([])
|
|
|
+const multipleSelection = ref([])
|
|
|
+const elSearchFormRef = ref(null)
|
|
|
+const searchInfo = ref({
|
|
|
+ userName: ''
|
|
|
+})
|
|
|
+const onSubmit = () => {
|
|
|
+ getTableData()
|
|
|
+}
|
|
|
+const onReset = () => {
|
|
|
+ searchInfo.value = {
|
|
|
+ userName: ''
|
|
|
+ }
|
|
|
+}
|
|
|
+const getTableData = async() => {
|
|
|
+ const res = await getLogList({
|
|
|
+ type: logType.value,
|
|
|
+ page: page.value,
|
|
|
+ pageSize: pageSize.value,
|
|
|
+ ...searchInfo.value,
|
|
|
+ })
|
|
|
+ if (res.code === 100000) {
|
|
|
+ tableData.value = res.data.logs
|
|
|
+ total.value = res.data.total
|
|
|
+ }
|
|
|
+ // 发送请求到后端
|
|
|
+}
|
|
|
+getTableData()
|
|
|
+
|
|
|
+// 切换日志类型时清空上一列表的勾选,避免批量操作作用到不可见行
|
|
|
+const onLogTypeChange = () => {
|
|
|
+ multipleSelection.value = []
|
|
|
+ getTableData()
|
|
|
+}
|
|
|
+
|
|
|
+const detailDialogVisible = ref(false)
|
|
|
+const detailData = ref([])
|
|
|
+const getDetail = async(row) => {
|
|
|
+ detailDialogVisible.value = true
|
|
|
+ detailData.value = row
|
|
|
+}
|
|
|
+const getDetailIn = async(activity) => {
|
|
|
+ detailDialogVisible.value = true
|
|
|
+ await getLogDetail({
|
|
|
+ iden: activity.iden
|
|
|
+ }).then(res => {
|
|
|
+ if (res.code === 100000) {
|
|
|
+ detailData.value = res.data
|
|
|
+ }
|
|
|
+ })
|
|
|
+}
|
|
|
+const multipleAudit = () => {
|
|
|
+ // 检查 multipleSelection 是否为空
|
|
|
+ if (multipleSelection.value.length === 0) {
|
|
|
+ ElMessage.error('请选择要审计的日志!')
|
|
|
+ return
|
|
|
+ }
|
|
|
+ const count = multipleSelection.value.length
|
|
|
+ ElMessageBox.confirm(`确定要审计选中的 ${count} 条日志吗?`, '提示', {
|
|
|
+ confirmButtonText: '确定',
|
|
|
+ cancelButtonText: '取消',
|
|
|
+ type: 'warning',
|
|
|
+ }).then(async() => {
|
|
|
+ const idens = multipleSelection.value.map(item => item.iden)
|
|
|
+ const res = await auditLogList({
|
|
|
+ idens: idens
|
|
|
+ })
|
|
|
+ if (res.code === 100000) {
|
|
|
+ const totalCount = res.data.totalCount
|
|
|
+ const validCount = res.data.validCount
|
|
|
+ const inValidCount = res.data.inValidCount
|
|
|
+ await ElMessageBox.alert(`审计 ${totalCount} 条日志记录完成,其中审计通过: ${validCount} 条,审计异常: ${inValidCount} 条`, '提示', {
|
|
|
+ confirmButtonText: '确定',
|
|
|
+ type: 'success'
|
|
|
+ })
|
|
|
+ getTableData()
|
|
|
+ }
|
|
|
+ }).catch(() => {
|
|
|
+ ElMessage.info('已取消审计操作')
|
|
|
+ })
|
|
|
+}
|
|
|
+const multipleArchive = () => {
|
|
|
+ // 检查 multipleSelection 是否为空
|
|
|
+ if (multipleSelection.value.length === 0) {
|
|
|
+ ElMessage.error('请选择要归档的日志!')
|
|
|
+ return
|
|
|
+ }
|
|
|
+
|
|
|
+ // 获取选中记录的数量
|
|
|
+ const count = multipleSelection.value.length
|
|
|
+ const idens = multipleSelection.value.map(item => item.id)
|
|
|
+
|
|
|
+ // 显示确认对话框
|
|
|
+ ElMessageBox.prompt(`将归档 ${count} 条日志记录。`, '确认归档', {
|
|
|
+ confirmButtonText: '确定',
|
|
|
+ cancelButtonText: '取消',
|
|
|
+ inputPlaceholder: `${userStore.userInfo.userName}_manual_archive`,
|
|
|
+ }).then(({ value: name }) => {
|
|
|
+ // 用户点击确定且输入有效时执行
|
|
|
+ archiveLogList({
|
|
|
+ ids: idens,
|
|
|
+ name: name || `${userStore.userInfo.userName}_manual_archive`,
|
|
|
+ startDate: '',
|
|
|
+ endDate: ''
|
|
|
+ }).then(res => {
|
|
|
+ if (res.code === 100000) {
|
|
|
+ ElMessage.success(`成功归档${res.data.count}条日志记录`)
|
|
|
+ getTableData()
|
|
|
+ }
|
|
|
+ })
|
|
|
+ }).catch(() => {
|
|
|
+ // 用户取消操作
|
|
|
+ ElMessage.info('已取消归档操作')
|
|
|
+ })
|
|
|
+}
|
|
|
+const archiveInfo = ref({})
|
|
|
+const archiveDialogVisible = ref(false)
|
|
|
+const elArchiveFormRef = ref(null)
|
|
|
+const archiveRules = {
|
|
|
+ startDate: [
|
|
|
+ { required: true, message: '请选择起始日期', trigger: 'change' }
|
|
|
+ ],
|
|
|
+ endDate: [
|
|
|
+ { required: true, message: '请选择结束日期', trigger: 'change' },
|
|
|
+ {
|
|
|
+ validator: (rule, value, callback) => {
|
|
|
+ // 获取 startDate 的值
|
|
|
+ const startDate = archiveInfo.value.startDate
|
|
|
+
|
|
|
+ // 如果 startDate 未选择或 endDate 未选择,直接通过验证(由 required 规则处理)
|
|
|
+ if (!startDate || !value) {
|
|
|
+ return callback()
|
|
|
+ }
|
|
|
+
|
|
|
+ // 确保 endDate 大于 startDate
|
|
|
+ if (new Date(value) <= new Date(startDate)) {
|
|
|
+ callback(new Error('结束日期必须大于起始日期'))
|
|
|
+ } else {
|
|
|
+ callback()
|
|
|
+ }
|
|
|
+ },
|
|
|
+ trigger: 'change'
|
|
|
+ }
|
|
|
+ ]
|
|
|
+}
|
|
|
+const archiveByDate = () => {
|
|
|
+ archiveDialogVisible.value = true
|
|
|
+}
|
|
|
+
|
|
|
+const elTableRef = ref(null)
|
|
|
+const handleSelectionChange = val => {
|
|
|
+ multipleSelection.value = val
|
|
|
+}
|
|
|
+// 定义一个函数来控制行是否可选中
|
|
|
+const handleSelectable = (row, index) => {
|
|
|
+ // 返回false禁止选中该行
|
|
|
+ return true
|
|
|
+}
|
|
|
+const audit = async(row) => {
|
|
|
+ ElMessageBox.confirm('确定要审计该日志吗?', '提示', {
|
|
|
+ confirmButtonText: '确定',
|
|
|
+ cancelButtonText: '取消',
|
|
|
+ type: 'warning',
|
|
|
+ }).then(async() => {
|
|
|
+ const idens = [row.iden]
|
|
|
+ const res = await auditLogList({
|
|
|
+ idens: idens
|
|
|
+ })
|
|
|
+ if (res.code === 100000) {
|
|
|
+ const totalCount = res.data.totalCount
|
|
|
+ const validCount = res.data.validCount
|
|
|
+ const inValidCount = res.data.inValidCount
|
|
|
+ await ElMessageBox.alert(`审计 ${totalCount} 条日志记录完成,其中审计通过: ${validCount} 条,审计异常: ${inValidCount} 条`, '提示', {
|
|
|
+ confirmButtonText: '确定',
|
|
|
+ type: 'success'
|
|
|
+ })
|
|
|
+ getTableData()
|
|
|
+ }
|
|
|
+ }).catch(() => {
|
|
|
+ ElMessage({
|
|
|
+ type: 'info',
|
|
|
+ message: '取消审计',
|
|
|
+ })
|
|
|
+ })
|
|
|
+}
|
|
|
+const onArchiveSubmit = () => {
|
|
|
+ // 校验表单(起始/结束日期必选,且结束日期须大于起始日期)
|
|
|
+ elArchiveFormRef.value.validate(async valid => {
|
|
|
+ if (!valid) return
|
|
|
+ const res = await archiveLogList({
|
|
|
+ name: archiveInfo.value.name || `${userStore.userInfo.userName}_manual_archive`,
|
|
|
+ startDate: archiveInfo.value.startDate,
|
|
|
+ endDate: archiveInfo.value.endDate,
|
|
|
+ ids: []
|
|
|
+ })
|
|
|
+ if (res.code === 100000) {
|
|
|
+ ElMessage({
|
|
|
+ type: 'success',
|
|
|
+ message: `成功归档${res.data.count}条日志记录`
|
|
|
+ })
|
|
|
+ archiveDialogVisible.value = false
|
|
|
+ getTableData()
|
|
|
+ }
|
|
|
+ })
|
|
|
+}
|
|
|
+
|
|
|
+const activities = ref([])
|
|
|
+const activeStep = ref(1) // 默认第一个高亮
|
|
|
+const linkDialogVisible = ref(false)
|
|
|
+const viewLink = async(row) => {
|
|
|
+ linkDialogVisible.value = true
|
|
|
+ const res = await getTraceLog({
|
|
|
+ traceId: row.traceId
|
|
|
+ })
|
|
|
+ if (res.code === 100000) {
|
|
|
+ activities.value = res.data.logLinks.map(activity => ({
|
|
|
+ // 需要显示什么参数再改
|
|
|
+ type: activity.type,
|
|
|
+ color: getActivityColor(activity, row.iden),
|
|
|
+ timestamp: activity.createdAt,
|
|
|
+ method: activity.method,
|
|
|
+ serviceName: activity.serviceName,
|
|
|
+ uriPath: activity.uriPath,
|
|
|
+ responseCode: activity.responseCode,
|
|
|
+ durationMs: activity.durationMs,
|
|
|
+ errorMsg: activity.errorMsg,
|
|
|
+ iden: activity.iden,
|
|
|
+ }))
|
|
|
+ // 找到 traceId 对应的 index(注意 el-steps 索引从 1 开始)
|
|
|
+ const idx = activities.value.findIndex(a => a.iden === row.iden)
|
|
|
+ activeStep.value = idx >= 0 ? idx + 1 : 1
|
|
|
+ }
|
|
|
+}
|
|
|
+const getActivityColor = (activity, iden) => {
|
|
|
+ if (activity.iden === iden) return true
|
|
|
+ if (activity.errorMsg !== '') return '#FF0005'
|
|
|
+ return ''
|
|
|
+}
|
|
|
+</script>
|
|
|
+
|
|
|
+<style scoped lang="scss">
|
|
|
+.step-description {
|
|
|
+
|
|
|
+ width: 100%;
|
|
|
+ margin-left: auto;
|
|
|
+ display: flex;
|
|
|
+ flex-direction: column;
|
|
|
+ align-items: center; /* 水平居中 */
|
|
|
+ justify-content: center; /* 垂直居中(如果有固定高度) */
|
|
|
+}
|
|
|
+.step-description-wrapper{
|
|
|
+ text-align: left;
|
|
|
+}
|
|
|
+:deep(.el-step__description) {
|
|
|
+ padding: 0 !important;
|
|
|
+}
|
|
|
+</style>
|