This commit is contained in:
小蚂蚁云
2026-10-26 09:57:37 +08:00
parent 69f5c1767d
commit 2beb1ef15a
7 changed files with 726 additions and 148 deletions
+2 -3
View File
@@ -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: '',
});
+116 -33
View File
@@ -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(
'div',
{
style: { marginBottom: '4px' },
},
[
h(
Tag,
{
color: 'processing',
style: { fontSize: '12px', lineHeight: '20px' },
},
{
default: () => record.location,
},
{ 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 || '-'),
),
]);
},
},
+210 -33
View File
@@ -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',
},
],
+1 -1
View File
@@ -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';
+132 -34
View File
@@ -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: 'processing',
},
{
default: () => record.location,
color: 'default',
style: { fontSize: '12px', lineHeight: '20px' },
},
{ default: () => record.ip || '-' },
),
],
),
h(
'div',
{
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 || '-'),
),
]);
},
},
+255 -34
View File
@@ -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>