更新
This commit is contained in:
@@ -60,11 +60,10 @@
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { reactive, ref, h, nextTick, defineAsyncComponent } from 'vue';
|
||||
import { reactive, ref, nextTick, defineAsyncComponent } from 'vue';
|
||||
import { EyeOutlined, DeleteOutlined } from '@ant-design/icons-vue';
|
||||
import { schemas } from './loginLog/querySchemas';
|
||||
import { useForm } from '@/components/Form/index';
|
||||
import { TableAction } from '@/components/Table';
|
||||
import { getLoginLogList, loginLogDelete, loginLogBatchDelete } from '@/api/system/loginLog';
|
||||
import { columns } from './loginLog/columns';
|
||||
import { Modal, message } from 'ant-design-vue';
|
||||
@@ -87,7 +86,7 @@
|
||||
*/
|
||||
const formParams = reactive({
|
||||
username: '',
|
||||
type: '',
|
||||
logType: '',
|
||||
status: '',
|
||||
});
|
||||
|
||||
|
||||
@@ -1,5 +1,21 @@
|
||||
import { h } from 'vue';
|
||||
import { Tag } from 'ant-design-vue';
|
||||
import { Tag, Avatar } from 'ant-design-vue';
|
||||
|
||||
// 灰色副文本统一风格
|
||||
const subText: Record<string, string> = {
|
||||
fontSize: '14px',
|
||||
color: '#666',
|
||||
};
|
||||
|
||||
// 请求方式标签颜色
|
||||
function methodTagColor(method?: string) {
|
||||
const m = (method || '').toUpperCase();
|
||||
if (m === 'GET') return 'success';
|
||||
if (m === 'POST') return 'processing';
|
||||
if (m === 'PUT' || m === 'PATCH') return 'warning';
|
||||
if (m === 'DELETE') return 'error';
|
||||
return 'default';
|
||||
}
|
||||
|
||||
export const columns = [
|
||||
{
|
||||
@@ -11,99 +27,166 @@ export const columns = [
|
||||
{
|
||||
title: '访客',
|
||||
align: 'left',
|
||||
width: 200,
|
||||
minWidth: 220,
|
||||
customRender({ record }) {
|
||||
return h('div', [
|
||||
const textBlock = h('div', { style: { minWidth: '0', flex: '1' } }, [
|
||||
h(
|
||||
'div',
|
||||
{
|
||||
style: { marginBottom: '5px' },
|
||||
style: { marginBottom: '4px', fontWeight: 'bold' },
|
||||
},
|
||||
record.username,
|
||||
record.username || '-',
|
||||
),
|
||||
h(
|
||||
'div',
|
||||
{
|
||||
style: { marginBottom: '5px' },
|
||||
style: { marginBottom: '4px' },
|
||||
},
|
||||
record.ip,
|
||||
[
|
||||
h(
|
||||
Tag,
|
||||
{
|
||||
color: 'default',
|
||||
style: { fontSize: '12px', lineHeight: '20px' },
|
||||
},
|
||||
{ default: () => record.ip || '-' },
|
||||
),
|
||||
],
|
||||
),
|
||||
h(
|
||||
Tag,
|
||||
'div',
|
||||
{
|
||||
color: 'processing',
|
||||
},
|
||||
{
|
||||
default: () => record.location,
|
||||
style: { marginBottom: '4px' },
|
||||
},
|
||||
[
|
||||
h(
|
||||
Tag,
|
||||
{
|
||||
color: 'processing',
|
||||
style: { fontSize: '12px', lineHeight: '20px' },
|
||||
},
|
||||
{ default: () => record.location || '-' },
|
||||
),
|
||||
],
|
||||
),
|
||||
]);
|
||||
return h('div', { style: { display: 'flex', alignItems: 'center' } }, [
|
||||
h(
|
||||
Avatar,
|
||||
{
|
||||
src: record.avatar,
|
||||
size: 48,
|
||||
shape: 'square',
|
||||
style: {
|
||||
marginRight: '12px',
|
||||
flexShrink: 0,
|
||||
background: '#1890ff',
|
||||
fontWeight: 'bold',
|
||||
},
|
||||
},
|
||||
{ default: () => (record.username || '?').slice(0, 1) },
|
||||
),
|
||||
textBlock,
|
||||
]);
|
||||
},
|
||||
},
|
||||
{
|
||||
title: '请求接口',
|
||||
align: 'left',
|
||||
width: 200,
|
||||
minWidth: 220,
|
||||
customRender({ record }) {
|
||||
return h('div', [
|
||||
h(
|
||||
Tag,
|
||||
{
|
||||
color: 'default',
|
||||
style: { marginBottom: '5px' },
|
||||
},
|
||||
{
|
||||
default: () => record.requestMethod,
|
||||
color: methodTagColor(record.requestMethod),
|
||||
style: { marginBottom: '5px', fontSize: '12px', lineHeight: '20px' },
|
||||
},
|
||||
{ default: () => record.requestMethod || '-' },
|
||||
),
|
||||
h(
|
||||
'div',
|
||||
{
|
||||
style: { marginBottom: '5px' },
|
||||
style: { ...subText, wordBreak: 'break-all', marginBottom: '4px' },
|
||||
title: record.url,
|
||||
},
|
||||
'接口:' + record.url,
|
||||
'接口:' + (record.url || '-'),
|
||||
),
|
||||
h(
|
||||
'div',
|
||||
{
|
||||
style: { fontWeight: 'bold' },
|
||||
},
|
||||
'名称:' + (record.title || '-'),
|
||||
),
|
||||
h('div', '名称:' + record.title),
|
||||
]);
|
||||
},
|
||||
},
|
||||
{
|
||||
title: '接口响应',
|
||||
align: 'left',
|
||||
width: 250,
|
||||
minWidth: 250,
|
||||
customRender({ record }) {
|
||||
const abnormal = record.status != 0;
|
||||
return h('div', [
|
||||
h('span', '状态:'),
|
||||
h('span', { style: subText }, '状态:'),
|
||||
h(
|
||||
Tag,
|
||||
{
|
||||
color: record.status == 0 ? 'success' : 'error',
|
||||
style: { marginBottom: '5px' },
|
||||
},
|
||||
{
|
||||
default: () => (record.status == 0 ? '正常' : '异常'),
|
||||
color: abnormal ? 'error' : 'success',
|
||||
style: { marginBottom: '5px', fontSize: '12px', lineHeight: '20px' },
|
||||
},
|
||||
{ default: () => (abnormal ? '异常' : '正常') },
|
||||
),
|
||||
h(
|
||||
'div',
|
||||
{
|
||||
style: { marginBottom: '5px' },
|
||||
style: {
|
||||
...subText,
|
||||
marginBottom: '4px',
|
||||
color: abnormal ? '#f5222d' : '#666',
|
||||
fontWeight: abnormal ? 'bold' : 'normal',
|
||||
},
|
||||
},
|
||||
'请求耗时:' + record.consumeTime + 'ms',
|
||||
'请求耗时:' + (record.consumeTime ?? '-') + 'ms',
|
||||
),
|
||||
h(
|
||||
'div',
|
||||
{
|
||||
style: subText,
|
||||
},
|
||||
'响应时间:' + (record.updateTime || '-'),
|
||||
),
|
||||
h('div', '响应时间:' + record.updateTime),
|
||||
]);
|
||||
},
|
||||
},
|
||||
{
|
||||
title: '操作来源',
|
||||
align: 'left',
|
||||
width: 200,
|
||||
minWidth: 200,
|
||||
customRender({ record }) {
|
||||
return h('div', [
|
||||
h('div', '系统:' + record.os),
|
||||
h('div', '类型:' + record.logTypeText),
|
||||
h('div', '来源:' + record.sourceText),
|
||||
h(
|
||||
'div',
|
||||
{
|
||||
style: { marginBottom: '4px' },
|
||||
},
|
||||
'系统:' + (record.os || '-'),
|
||||
),
|
||||
h(
|
||||
'div',
|
||||
{
|
||||
style: { marginBottom: '4px' },
|
||||
},
|
||||
'类型:' + (record.logTypeText || '-'),
|
||||
),
|
||||
h(
|
||||
'div',
|
||||
{
|
||||
style: subText,
|
||||
},
|
||||
'来源:' + (record.sourceText || '-'),
|
||||
),
|
||||
]);
|
||||
},
|
||||
},
|
||||
|
||||
@@ -1,40 +1,87 @@
|
||||
<template>
|
||||
<a-modal v-model:visible="props.visible" title="登录日志详情" width="800px" @cancel="dialogClose">
|
||||
<a-descriptions class="margin-top" :column="2" bordered :labelStyle="{ width: '120px' }">
|
||||
<a-descriptions-item label="操作用户">
|
||||
{{ formData.createUser }}
|
||||
</a-descriptions-item>
|
||||
<a-descriptions-item label="IP地址">
|
||||
{{ formData.ip }}
|
||||
</a-descriptions-item>
|
||||
<a-descriptions-item label="操作系统">
|
||||
{{ formData.os }}
|
||||
</a-descriptions-item>
|
||||
<a-descriptions-item label="操作浏览器">
|
||||
{{ formData.browser }}
|
||||
</a-descriptions-item>
|
||||
<a-descriptions-item label="请求耗时"> {{ formData.consumeTime }}s </a-descriptions-item>
|
||||
<a-descriptions-item label="请求地区">
|
||||
{{ formData.location }}
|
||||
</a-descriptions-item>
|
||||
<a-modal
|
||||
v-model:visible="props.visible"
|
||||
title="登录日志详情"
|
||||
width="860px"
|
||||
:mask-closable="false"
|
||||
@cancel="dialogClose"
|
||||
>
|
||||
<!-- 概览条:头像 + 操作用户 + 状态标签 + 摘要 -->
|
||||
<div class="overview">
|
||||
<a-avatar :src="formData.avatar" :size="56" shape="square" class="overview-avatar">
|
||||
{{ (formData.createUser || '?').slice(0, 1) }}
|
||||
</a-avatar>
|
||||
<div class="overview-main">
|
||||
<div class="overview-name-row">
|
||||
<span class="overview-name">{{ formData.createUser || '-' }}</span>
|
||||
<a-tag :color="formData.status ? 'error' : 'success'" class="ml-3">
|
||||
{{ formData.status ? '异常' : '正常' }}
|
||||
</a-tag>
|
||||
</div>
|
||||
<div class="overview-sub" :title="formData.ip">
|
||||
{{ formData.ip || '-' }} · {{ formData.location || '-' }}
|
||||
</div>
|
||||
<div class="overview-sub-light" :title="formData.os">
|
||||
{{ formData.os || '-' }} · {{ formData.browser || '-' }}
|
||||
</div>
|
||||
</div>
|
||||
<div class="overview-time">
|
||||
<div>请求耗时</div>
|
||||
<div class="overview-time-value">{{ formData.consumeTime ?? '-' }}ms</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 请求信息 -->
|
||||
<a-descriptions class="section-gap" title="请求信息" :column="2" bordered>
|
||||
<a-descriptions-item label="请求方式">
|
||||
{{ formData.requestMethod }}
|
||||
<a-tag>{{ formData.requestMethod || '-' }}</a-tag>
|
||||
</a-descriptions-item>
|
||||
<a-descriptions-item label="请求状态">
|
||||
<a-tag :color="formData.status ? 'danger' : 'success'">{{
|
||||
formData.status ? '异常' : '正常'
|
||||
}}</a-tag>
|
||||
<a-tag :color="formData.status ? 'error' : 'success'">
|
||||
{{ formData.status ? '异常' : '正常' }}
|
||||
</a-tag>
|
||||
</a-descriptions-item>
|
||||
<a-descriptions-item label="调用方法" :span="3">
|
||||
{{ formData.method }}
|
||||
</a-descriptions-item>
|
||||
<a-descriptions-item label="请求参数" :span="3">
|
||||
{{ formData.param }}
|
||||
</a-descriptions-item>
|
||||
<a-descriptions-item label="返回结果" :span="3">
|
||||
{{ formData.result }}
|
||||
<a-descriptions-item label="请求地区">{{ formData.location || '-' }}</a-descriptions-item>
|
||||
<a-descriptions-item label="请求耗时"
|
||||
>{{ formData.consumeTime ?? '-' }}ms</a-descriptions-item
|
||||
>
|
||||
<a-descriptions-item label="调用方法" :span="2">
|
||||
<span class="field-break">{{ formData.method || '-' }}</span>
|
||||
</a-descriptions-item>
|
||||
</a-descriptions>
|
||||
|
||||
<!-- 登录环境 -->
|
||||
<a-descriptions class="section-gap" title="登录环境" :column="2" bordered>
|
||||
<a-descriptions-item label="操作用户">{{ formData.createUser || '-' }}</a-descriptions-item>
|
||||
<a-descriptions-item label="IP地址">
|
||||
<a-tag color="default">{{ formData.ip || '-' }}</a-tag>
|
||||
</a-descriptions-item>
|
||||
<a-descriptions-item label="操作系统">{{ formData.os || '-' }}</a-descriptions-item>
|
||||
<a-descriptions-item label="浏览器">{{ formData.browser || '-' }}</a-descriptions-item>
|
||||
</a-descriptions>
|
||||
|
||||
<!-- 请求参数 -->
|
||||
<div class="code-block section-gap">
|
||||
<div class="code-head">
|
||||
<span>请求参数</span>
|
||||
<a-button type="link" size="small" class="code-copy" @click="copyText(formData.param)"
|
||||
>复制</a-button
|
||||
>
|
||||
</div>
|
||||
<pre class="code-body">{{ formData.param || '-' }}</pre>
|
||||
</div>
|
||||
|
||||
<!-- 返回结果 -->
|
||||
<div class="code-block mt-3">
|
||||
<div class="code-head">
|
||||
<span>返回结果</span>
|
||||
<a-button type="link" size="small" class="code-copy" @click="copyText(formData.result)"
|
||||
>复制</a-button
|
||||
>
|
||||
</div>
|
||||
<pre class="code-body">{{ formData.result || '-' }}</pre>
|
||||
</div>
|
||||
|
||||
<template #footer>
|
||||
<span class="dialog-footer">
|
||||
<a-button @click="dialogClose">取消</a-button>
|
||||
@@ -45,10 +92,8 @@
|
||||
<script lang="ts" setup>
|
||||
import { getLoginLogDetail } from '@/api/system/loginLog';
|
||||
import { onMounted, ref } from 'vue';
|
||||
import { message } from 'ant-design-vue';
|
||||
|
||||
/**
|
||||
* 定义参数变量
|
||||
*/
|
||||
const emit = defineEmits(['success', 'update:visible']);
|
||||
|
||||
/**
|
||||
@@ -56,6 +101,7 @@
|
||||
*/
|
||||
const formData = ref({
|
||||
createUser: '',
|
||||
avatar: '',
|
||||
ip: '',
|
||||
os: '',
|
||||
browser: '',
|
||||
@@ -91,6 +137,22 @@
|
||||
emit('update:visible', false);
|
||||
};
|
||||
|
||||
/**
|
||||
* 复制文本
|
||||
*/
|
||||
const copyText = async (text?: string) => {
|
||||
if (!text) {
|
||||
message.warn('无内容可复制');
|
||||
return;
|
||||
}
|
||||
try {
|
||||
await navigator.clipboard.writeText(text);
|
||||
message.success('复制成功');
|
||||
} catch {
|
||||
message.error('复制失败,请手动选择复制');
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* 设置表单数据
|
||||
*/
|
||||
@@ -103,12 +165,127 @@
|
||||
* 钩子函数
|
||||
*/
|
||||
onMounted(() => {
|
||||
// 组件挂载后执行的代码
|
||||
if (props.loginlogId) {
|
||||
setFormData();
|
||||
}
|
||||
});
|
||||
</script>
|
||||
<style lang="less" scoped>
|
||||
.overview {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 16px 20px;
|
||||
background: linear-gradient(120deg, #f5f7ff 0%, #f8fafc 100%);
|
||||
border: 1px solid #ebeef5;
|
||||
border-radius: 8px;
|
||||
|
||||
.overview-avatar {
|
||||
flex-shrink: 0;
|
||||
background: #1890ff;
|
||||
font-weight: bold;
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.overview-main {
|
||||
margin-left: 16px;
|
||||
min-width: 0;
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.overview-name-row {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.overview-name {
|
||||
font-size: 16px;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.overview-sub {
|
||||
margin-top: 4px;
|
||||
font-size: 14px;
|
||||
color: #666;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.overview-sub-light {
|
||||
margin-top: 4px;
|
||||
font-size: 12px;
|
||||
color: #999;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.overview-time {
|
||||
flex-shrink: 0;
|
||||
margin-left: 16px;
|
||||
text-align: right;
|
||||
font-size: 12px;
|
||||
color: #999;
|
||||
|
||||
.overview-time-value {
|
||||
margin-top: 4px;
|
||||
font-size: 14px;
|
||||
color: #666;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.ml-3 {
|
||||
margin-left: 12px;
|
||||
}
|
||||
|
||||
.section-gap {
|
||||
margin-top: 24px;
|
||||
}
|
||||
|
||||
.mt-3 {
|
||||
margin-top: 12px;
|
||||
}
|
||||
|
||||
.field-break {
|
||||
word-break: break-all;
|
||||
}
|
||||
|
||||
.code-block {
|
||||
border: 1px solid #ebeef5;
|
||||
border-radius: 8px;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.code-head {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
padding: 8px 14px;
|
||||
background: #f8fafc;
|
||||
border-bottom: 1px solid #ebeef5;
|
||||
font-size: 13px;
|
||||
color: #666;
|
||||
|
||||
.code-copy {
|
||||
font-size: 13px;
|
||||
}
|
||||
}
|
||||
|
||||
.code-body {
|
||||
margin: 0;
|
||||
padding: 12px 14px;
|
||||
font-size: 12px;
|
||||
color: #303133;
|
||||
white-space: pre-wrap;
|
||||
word-break: break-all;
|
||||
max-height: 160px;
|
||||
overflow-y: auto;
|
||||
background: #fff;
|
||||
}
|
||||
|
||||
:deep(.ant-descriptions__body .ant-descriptions__table .ant-descriptions__cell) {
|
||||
word-break: break-all;
|
||||
}
|
||||
|
||||
@@ -9,20 +9,20 @@ export const schemas: FormSchema[] = [
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'type',
|
||||
name: 'logType',
|
||||
component: 'Select',
|
||||
label: '请求类型',
|
||||
label: '登录类型',
|
||||
componentProps: {
|
||||
placeholder: '请选择请求类型',
|
||||
placeholder: '请选择登录类型',
|
||||
clearable: true,
|
||||
options: [
|
||||
{
|
||||
label: '登录',
|
||||
value: '0',
|
||||
value: '1',
|
||||
},
|
||||
{
|
||||
label: '退出',
|
||||
value: '1',
|
||||
value: '2',
|
||||
},
|
||||
],
|
||||
},
|
||||
@@ -30,17 +30,17 @@ export const schemas: FormSchema[] = [
|
||||
{
|
||||
name: 'status',
|
||||
component: 'Select',
|
||||
label: '请求状态',
|
||||
label: '登录状态',
|
||||
componentProps: {
|
||||
placeholder: '请选择状态',
|
||||
clearable: true,
|
||||
options: [
|
||||
{
|
||||
label: '正常',
|
||||
label: '成功',
|
||||
value: '0',
|
||||
},
|
||||
{
|
||||
label: '异常',
|
||||
label: '失败',
|
||||
value: '1',
|
||||
},
|
||||
],
|
||||
|
||||
@@ -60,7 +60,7 @@
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { reactive, ref, h, nextTick, defineAsyncComponent } from 'vue';
|
||||
import { reactive, ref, nextTick, defineAsyncComponent } from 'vue';
|
||||
import { EyeOutlined, DeleteOutlined } from '@ant-design/icons-vue';
|
||||
import { schemas } from './operLog/querySchemas';
|
||||
import { useForm } from '@/components/Form/index';
|
||||
|
||||
@@ -1,5 +1,28 @@
|
||||
import { h } from 'vue';
|
||||
import { Tag } from 'ant-design-vue';
|
||||
import { Tag, Avatar } from 'ant-design-vue';
|
||||
|
||||
// 灰色副文本统一风格
|
||||
const subText: Record<string, string> = {
|
||||
fontSize: '14px',
|
||||
color: '#666',
|
||||
};
|
||||
|
||||
// 请求方式标签颜色
|
||||
function methodTagColor(method?: string) {
|
||||
const m = (method || '').toUpperCase();
|
||||
if (m === 'GET') return 'success';
|
||||
if (m === 'POST') return 'processing';
|
||||
if (m === 'PUT' || m === 'PATCH') return 'warning';
|
||||
if (m === 'DELETE') return 'error';
|
||||
return 'default';
|
||||
}
|
||||
|
||||
// 操作类型标签颜色:删除/异常类用 error,修改用 warning,其余 processing
|
||||
function typeTagColor(type?: number) {
|
||||
if (type === 3) return 'error'; // 删除
|
||||
if (type === 2) return 'warning'; // 修改
|
||||
return 'processing';
|
||||
}
|
||||
|
||||
export const columns = [
|
||||
{
|
||||
@@ -11,93 +34,168 @@ export const columns = [
|
||||
{
|
||||
title: '访客',
|
||||
align: 'left',
|
||||
width: 200,
|
||||
minWidth: 220,
|
||||
customRender({ record }) {
|
||||
return h('div', [
|
||||
const textBlock = h('div', { style: { minWidth: '0', flex: '1' } }, [
|
||||
h(
|
||||
'div',
|
||||
{
|
||||
style: { marginBottom: '5px' },
|
||||
style: { marginBottom: '4px', fontWeight: 'bold' },
|
||||
},
|
||||
record.createUser,
|
||||
[
|
||||
record.createUser || '-',
|
||||
record.typeText
|
||||
? h(
|
||||
Tag,
|
||||
{
|
||||
color: typeTagColor(record.type),
|
||||
style: { marginLeft: '8px', fontSize: '12px', lineHeight: '20px' },
|
||||
},
|
||||
{ default: () => record.typeText },
|
||||
)
|
||||
: null,
|
||||
],
|
||||
),
|
||||
h(
|
||||
'div',
|
||||
{
|
||||
style: { marginBottom: '5px' },
|
||||
style: { marginBottom: '4px' },
|
||||
},
|
||||
record.ip,
|
||||
[
|
||||
h(
|
||||
Tag,
|
||||
{
|
||||
color: 'default',
|
||||
style: { fontSize: '12px', lineHeight: '20px' },
|
||||
},
|
||||
{ default: () => record.ip || '-' },
|
||||
),
|
||||
],
|
||||
),
|
||||
h(
|
||||
Tag,
|
||||
'div',
|
||||
{
|
||||
color: 'processing',
|
||||
},
|
||||
{
|
||||
default: () => record.location,
|
||||
style: subText,
|
||||
},
|
||||
'地点:' + (record.location || '-'),
|
||||
),
|
||||
]);
|
||||
return h('div', { style: { display: 'flex', alignItems: 'center' } }, [
|
||||
h(
|
||||
Avatar,
|
||||
{
|
||||
src: record.avatar,
|
||||
size: 48,
|
||||
shape: 'square',
|
||||
style: {
|
||||
marginRight: '12px',
|
||||
flexShrink: 0,
|
||||
background: '#1890ff',
|
||||
fontWeight: 'bold',
|
||||
},
|
||||
},
|
||||
{ default: () => (record.createUser || '?').slice(0, 1) },
|
||||
),
|
||||
textBlock,
|
||||
]);
|
||||
},
|
||||
},
|
||||
{
|
||||
title: '请求接口',
|
||||
align: 'left',
|
||||
width: 300,
|
||||
minWidth: 220,
|
||||
customRender({ record }) {
|
||||
return h('div', [
|
||||
h(
|
||||
Tag,
|
||||
{
|
||||
color: 'default',
|
||||
style: { marginBottom: '5px' },
|
||||
},
|
||||
{
|
||||
default: () => record.requestMethod,
|
||||
color: methodTagColor(record.requestMethod),
|
||||
style: { marginBottom: '5px', fontSize: '12px', lineHeight: '20px' },
|
||||
},
|
||||
{ default: () => record.requestMethod || '-' },
|
||||
),
|
||||
h(
|
||||
'div',
|
||||
{
|
||||
style: { ...subText, wordBreak: 'break-all', marginBottom: '4px' },
|
||||
title: record.url,
|
||||
},
|
||||
'接口:' + (record.url || '-'),
|
||||
),
|
||||
h(
|
||||
'div',
|
||||
{
|
||||
style: { fontWeight: 'bold' },
|
||||
},
|
||||
'名称:' + (record.title || '-'),
|
||||
),
|
||||
h('div', '接口:' + record.url),
|
||||
h('div', '名称:' + record.title),
|
||||
]);
|
||||
},
|
||||
},
|
||||
{
|
||||
title: '接口响应',
|
||||
align: 'left',
|
||||
width: 250,
|
||||
minWidth: 200,
|
||||
customRender({ record }) {
|
||||
const abnormal = record.status != 0;
|
||||
return h('div', [
|
||||
h('span', '状态:'),
|
||||
h(
|
||||
Tag,
|
||||
{
|
||||
color: record.status == 0 ? 'success' : 'error',
|
||||
style: { marginBottom: '5px' },
|
||||
},
|
||||
{
|
||||
default: () => (record.status == 0 ? '正常' : '异常'),
|
||||
color: abnormal ? 'error' : 'success',
|
||||
style: { marginBottom: '5px', fontSize: '12px', lineHeight: '20px' },
|
||||
},
|
||||
{ default: () => (abnormal ? '异常' : '正常') },
|
||||
),
|
||||
h(
|
||||
'div',
|
||||
{
|
||||
style: { marginBottom: '5px' },
|
||||
style: {
|
||||
...subText,
|
||||
marginBottom: '4px',
|
||||
color: abnormal ? '#f5222d' : '#666',
|
||||
fontWeight: abnormal ? 'bold' : 'normal',
|
||||
},
|
||||
},
|
||||
'请求耗时:' + record.consumeTime + 'ms',
|
||||
'请求耗时:' + (record.consumeTime ?? '-') + 'ms',
|
||||
),
|
||||
h(
|
||||
'div',
|
||||
{
|
||||
style: subText,
|
||||
},
|
||||
'响应时间:' + (record.updateTime || '-'),
|
||||
),
|
||||
h('div', '响应时间:' + record.updateTime),
|
||||
]);
|
||||
},
|
||||
},
|
||||
{
|
||||
title: '操作来源',
|
||||
align: 'left',
|
||||
width: 200,
|
||||
minWidth: 180,
|
||||
customRender({ record }) {
|
||||
return h('div', [
|
||||
h('div', '系统:' + record.os),
|
||||
h('div', '类型:' + record.typeText),
|
||||
h('div', '来源:' + record.sourceText),
|
||||
h(
|
||||
'div',
|
||||
{
|
||||
style: { marginBottom: '4px' },
|
||||
},
|
||||
[h('span', { style: subText }, '系统:'), h('span', record.os || '-')],
|
||||
),
|
||||
h(
|
||||
'div',
|
||||
{
|
||||
style: { marginBottom: '4px' },
|
||||
},
|
||||
[h('span', { style: subText }, '来源:'), h('span', record.sourceText || '-')],
|
||||
),
|
||||
h(
|
||||
'div',
|
||||
{
|
||||
style: subText,
|
||||
},
|
||||
'浏览器:' + (record.browser || '-'),
|
||||
),
|
||||
]);
|
||||
},
|
||||
},
|
||||
|
||||
@@ -1,43 +1,109 @@
|
||||
<template>
|
||||
<a-modal v-model:visible="props.visible" title="操作日志详情" width="800px" @cancel="dialogClose">
|
||||
<a-descriptions class="margin-top" :column="2" bordered :labelStyle="{ width: '120px' }">
|
||||
<a-descriptions-item label="日志标题">
|
||||
{{ formData.title }}
|
||||
</a-descriptions-item>
|
||||
<a-descriptions-item label="操作用户">
|
||||
{{ formData.createUser }}
|
||||
</a-descriptions-item>
|
||||
<a-descriptions-item label="IP地址">
|
||||
{{ formData.ip }}
|
||||
</a-descriptions-item>
|
||||
<a-descriptions-item label="操作系统">
|
||||
{{ formData.os }}
|
||||
</a-descriptions-item>
|
||||
<a-descriptions-item label="操作浏览器">
|
||||
{{ formData.browser }}
|
||||
</a-descriptions-item>
|
||||
<a-descriptions-item label="请求耗时"> {{ formData.consumeTime }}s </a-descriptions-item>
|
||||
<a-modal
|
||||
v-model:visible="props.visible"
|
||||
title="操作日志详情"
|
||||
width="860px"
|
||||
:mask-closable="false"
|
||||
@cancel="dialogClose"
|
||||
>
|
||||
<!-- 概览条:头像 + 操作人 + 状态/类型标签 + 摘要 -->
|
||||
<div class="overview">
|
||||
<a-avatar :src="formData.avatar" :size="56" shape="square" class="overview-avatar">
|
||||
{{ (formData.createUser || '?').slice(0, 1) }}
|
||||
</a-avatar>
|
||||
<div class="overview-main">
|
||||
<div class="overview-name-row">
|
||||
<span class="overview-name">{{ formData.createUser || '-' }}</span>
|
||||
<a-tag :color="formData.status ? 'error' : 'success'" class="ml-3">
|
||||
{{ formData.status ? '异常' : '正常' }}
|
||||
</a-tag>
|
||||
<a-tag v-if="formData.typeText" color="processing" class="ml-2">
|
||||
{{ formData.typeText }}
|
||||
</a-tag>
|
||||
<a-tag v-if="formData.sourceText" color="default" class="ml-2">
|
||||
{{ formData.sourceText }}
|
||||
</a-tag>
|
||||
</div>
|
||||
<div class="overview-sub" :title="formData.title">
|
||||
{{ formData.title || '-' }}
|
||||
</div>
|
||||
<div class="overview-sub-light" :title="formData.url">
|
||||
{{ formData.url || '-' }}
|
||||
</div>
|
||||
</div>
|
||||
<div class="overview-time">
|
||||
<div>响应时间</div>
|
||||
<div class="overview-time-value">{{ formData.updateTime || '-' }}</div>
|
||||
<div class="overview-time-label">创建时间</div>
|
||||
<div class="overview-time-value">{{ formData.createTime || '-' }}</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 请求信息 -->
|
||||
<a-descriptions class="section-gap" title="请求信息" :column="2" bordered>
|
||||
<a-descriptions-item label="请求方式">
|
||||
{{ formData.requestMethod }}
|
||||
<a-tag>{{ formData.requestMethod || '-' }}</a-tag>
|
||||
</a-descriptions-item>
|
||||
<a-descriptions-item label="请求耗时">
|
||||
<span :class="{ 'consume-abnormal': formData.status }">
|
||||
{{ formData.consumeTime ?? '-' }}ms
|
||||
</span>
|
||||
</a-descriptions-item>
|
||||
<a-descriptions-item label="请求URL" :span="2">
|
||||
<span class="field-break">{{ formData.url || '-' }}</span>
|
||||
</a-descriptions-item>
|
||||
<a-descriptions-item label="调用方法" :span="2">
|
||||
<span class="field-break">{{ formData.method || '-' }}</span>
|
||||
</a-descriptions-item>
|
||||
<a-descriptions-item label="请求状态">
|
||||
<a-tag :color="formData.status ? 'danger' : 'success'">{{
|
||||
formData.status ? '异常' : '正常'
|
||||
}}</a-tag>
|
||||
<a-tag :color="formData.status ? 'error' : 'success'">
|
||||
{{ formData.status ? '异常' : '正常' }}
|
||||
</a-tag>
|
||||
</a-descriptions-item>
|
||||
<a-descriptions-item label="调用方法" :span="3">
|
||||
{{ formData.method }}
|
||||
</a-descriptions-item>
|
||||
<a-descriptions-item label="请求参数" :span="3">
|
||||
{{ formData.param }}
|
||||
</a-descriptions-item>
|
||||
<a-descriptions-item label="返回结果" :span="3">
|
||||
{{ formData.result }}
|
||||
<a-descriptions-item label="错误信息">
|
||||
<span class="field-break">{{ formData.error || '-' }}</span>
|
||||
</a-descriptions-item>
|
||||
</a-descriptions>
|
||||
|
||||
<!-- 操作环境 -->
|
||||
<a-descriptions class="section-gap" title="操作环境" :column="2" bordered>
|
||||
<a-descriptions-item label="操作人">{{ formData.createUser || '-' }}</a-descriptions-item>
|
||||
<a-descriptions-item label="操作类型">{{ formData.typeText || '-' }}</a-descriptions-item>
|
||||
<a-descriptions-item label="操作来源">{{ formData.sourceText || '-' }}</a-descriptions-item>
|
||||
<a-descriptions-item label="请求IP">
|
||||
<a-tag color="default">{{ formData.ip || '-' }}</a-tag>
|
||||
</a-descriptions-item>
|
||||
<a-descriptions-item label="请求地区">{{ formData.location || '-' }}</a-descriptions-item>
|
||||
<a-descriptions-item label="操作系统">{{ formData.os || '-' }}</a-descriptions-item>
|
||||
<a-descriptions-item label="浏览器">{{ formData.browser || '-' }}</a-descriptions-item>
|
||||
<a-descriptions-item label="日志标题">{{ formData.title || '-' }}</a-descriptions-item>
|
||||
</a-descriptions>
|
||||
|
||||
<!-- 请求参数 -->
|
||||
<div class="code-block section-gap">
|
||||
<div class="code-head">
|
||||
<span>请求参数</span>
|
||||
<a-button type="link" size="small" class="code-copy" @click="copyText(formData.param)"
|
||||
>复制</a-button
|
||||
>
|
||||
</div>
|
||||
<pre class="code-body">{{ formData.param || '-' }}</pre>
|
||||
</div>
|
||||
|
||||
<!-- 返回结果 -->
|
||||
<div class="code-block mt-3">
|
||||
<div class="code-head">
|
||||
<span>返回结果</span>
|
||||
<a-button type="link" size="small" class="code-copy" @click="copyText(formData.result)"
|
||||
>复制</a-button
|
||||
>
|
||||
</div>
|
||||
<pre class="code-body">{{ formData.result || '-' }}</pre>
|
||||
</div>
|
||||
|
||||
<template #footer>
|
||||
<span class="dialog-footer">
|
||||
<a-button @click="dialogClose">取消</a-button>
|
||||
<a-button @click="dialogClose">关 闭</a-button>
|
||||
</span>
|
||||
</template>
|
||||
</a-modal>
|
||||
@@ -45,10 +111,8 @@
|
||||
<script lang="ts" setup>
|
||||
import { getOperLogDetail } from '@/api/system/operLog';
|
||||
import { onMounted, ref } from 'vue';
|
||||
import { message } from 'ant-design-vue';
|
||||
|
||||
/**
|
||||
* 定义参数变量
|
||||
*/
|
||||
const emit = defineEmits(['success', 'update:visible']);
|
||||
|
||||
/**
|
||||
@@ -57,7 +121,9 @@
|
||||
const formData = ref({
|
||||
title: '',
|
||||
createUser: '',
|
||||
avatar: '',
|
||||
ip: '',
|
||||
location: '',
|
||||
os: '',
|
||||
browser: '',
|
||||
consumeTime: '',
|
||||
@@ -66,6 +132,12 @@
|
||||
method: '',
|
||||
param: '',
|
||||
result: '',
|
||||
error: '',
|
||||
url: '',
|
||||
typeText: '',
|
||||
sourceText: '',
|
||||
updateTime: '',
|
||||
createTime: '',
|
||||
});
|
||||
|
||||
/**
|
||||
@@ -91,6 +163,22 @@
|
||||
emit('update:visible', false);
|
||||
};
|
||||
|
||||
/**
|
||||
* 复制文本
|
||||
*/
|
||||
const copyText = async (text?: string) => {
|
||||
if (!text) {
|
||||
message.warn('无内容可复制');
|
||||
return;
|
||||
}
|
||||
try {
|
||||
await navigator.clipboard.writeText(text);
|
||||
message.success('复制成功');
|
||||
} catch {
|
||||
message.error('复制失败,请手动选择复制');
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* 设置表单数据
|
||||
*/
|
||||
@@ -103,8 +191,141 @@
|
||||
* 钩子函数
|
||||
*/
|
||||
onMounted(() => {
|
||||
// 组件挂载后执行的代码
|
||||
if (props.operlogId) {
|
||||
setFormData();
|
||||
}
|
||||
});
|
||||
</script>
|
||||
<style lang="less" scoped>
|
||||
.overview {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 16px 20px;
|
||||
background: linear-gradient(120deg, #f5f7ff 0%, #f8fafc 100%);
|
||||
border: 1px solid #ebeef5;
|
||||
border-radius: 8px;
|
||||
|
||||
.overview-avatar {
|
||||
flex-shrink: 0;
|
||||
background: #1890ff;
|
||||
font-weight: bold;
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.overview-main {
|
||||
margin-left: 16px;
|
||||
min-width: 0;
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.overview-name-row {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.overview-name {
|
||||
font-size: 16px;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.overview-sub {
|
||||
margin-top: 4px;
|
||||
font-size: 14px;
|
||||
color: #666;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.overview-sub-light {
|
||||
margin-top: 4px;
|
||||
font-size: 12px;
|
||||
color: #999;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.overview-time {
|
||||
flex-shrink: 0;
|
||||
margin-left: 16px;
|
||||
text-align: right;
|
||||
font-size: 12px;
|
||||
color: #999;
|
||||
|
||||
.overview-time-label {
|
||||
margin-top: 8px;
|
||||
}
|
||||
|
||||
.overview-time-value {
|
||||
margin-top: 4px;
|
||||
font-size: 14px;
|
||||
color: #666;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.ml-2 {
|
||||
margin-left: 8px;
|
||||
}
|
||||
|
||||
.ml-3 {
|
||||
margin-left: 12px;
|
||||
}
|
||||
|
||||
.section-gap {
|
||||
margin-top: 24px;
|
||||
}
|
||||
|
||||
.mt-3 {
|
||||
margin-top: 12px;
|
||||
}
|
||||
|
||||
.field-break {
|
||||
word-break: break-all;
|
||||
}
|
||||
|
||||
.consume-abnormal {
|
||||
color: #f5222d;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.code-block {
|
||||
border: 1px solid #ebeef5;
|
||||
border-radius: 8px;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.code-head {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
padding: 8px 14px;
|
||||
background: #f8fafc;
|
||||
border-bottom: 1px solid #ebeef5;
|
||||
font-size: 13px;
|
||||
color: #666;
|
||||
|
||||
.code-copy {
|
||||
font-size: 13px;
|
||||
}
|
||||
}
|
||||
|
||||
.code-body {
|
||||
margin: 0;
|
||||
padding: 12px 14px;
|
||||
font-size: 12px;
|
||||
color: #303133;
|
||||
white-space: pre-wrap;
|
||||
word-break: break-all;
|
||||
max-height: 160px;
|
||||
overflow-y: auto;
|
||||
background: #fff;
|
||||
}
|
||||
|
||||
:deep(.ant-descriptions__body .ant-descriptions__table .ant-descriptions__cell) {
|
||||
word-break: break-all;
|
||||
}
|
||||
</style>
|
||||
|
||||
Reference in New Issue
Block a user