vue_zhongyou 1.0.2 → 1.0.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/icon//346/225/254/350/257/267/346/234/237/345/276/205.png +0 -0
- package/package.json +1 -1
- package//345/212/237/350/203/275/344/273/243/347/240/201//345/255/230/345/220/216/347/253/257/347/211/210/347/233/221/346/216/247/pcErrorLogPage.vue +585 -0
- package//346/217/222/344/273/266/BCompare-zh-5.0.1.29877.exe +0 -0
|
Binary file
|
package/package.json
CHANGED
|
@@ -0,0 +1,585 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<div class="pc-error-log-page">
|
|
3
|
+
<!-- 顶部统计卡片 -->
|
|
4
|
+
<el-row :gutter="20" class="stats-row">
|
|
5
|
+
<el-col :span="6">
|
|
6
|
+
<el-card class="stat-card">
|
|
7
|
+
<div class="stat-content">
|
|
8
|
+
<div class="stat-label">总计错误</div>
|
|
9
|
+
<div class="stat-value total">{{ statistics.total }}</div>
|
|
10
|
+
</div>
|
|
11
|
+
</el-card>
|
|
12
|
+
</el-col>
|
|
13
|
+
<el-col :span="6">
|
|
14
|
+
<el-card class="stat-card">
|
|
15
|
+
<div class="stat-content">
|
|
16
|
+
<div class="stat-label">接口错误</div>
|
|
17
|
+
<div class="stat-value api">{{ statistics.api }}</div>
|
|
18
|
+
</div>
|
|
19
|
+
</el-card>
|
|
20
|
+
</el-col>
|
|
21
|
+
<el-col :span="6">
|
|
22
|
+
<el-card class="stat-card">
|
|
23
|
+
<div class="stat-content">
|
|
24
|
+
<div class="stat-label">页面错误</div>
|
|
25
|
+
<div class="stat-value page">{{ statistics.page }}</div>
|
|
26
|
+
</div>
|
|
27
|
+
</el-card>
|
|
28
|
+
</el-col>
|
|
29
|
+
<el-col :span="6">
|
|
30
|
+
<el-card class="stat-card">
|
|
31
|
+
<div class="stat-content">
|
|
32
|
+
<div class="stat-label">Promise错误</div>
|
|
33
|
+
<div class="stat-value promise">{{ statistics.promise }}</div>
|
|
34
|
+
</div>
|
|
35
|
+
</el-card>
|
|
36
|
+
</el-col>
|
|
37
|
+
</el-row>
|
|
38
|
+
|
|
39
|
+
<!-- 筛选和操作栏 -->
|
|
40
|
+
<el-card class="filter-card">
|
|
41
|
+
<el-form :inline="true" :model="filterForm" class="filter-form">
|
|
42
|
+
<el-form-item label="错误类型">
|
|
43
|
+
<el-select v-model="filterForm.type" placeholder="全部" clearable style="width: 150px">
|
|
44
|
+
<el-option label="全部" value="" />
|
|
45
|
+
<el-option label="接口错误" value="api" />
|
|
46
|
+
<el-option label="页面错误" value="page" />
|
|
47
|
+
<el-option label="Promise错误" value="promise" />
|
|
48
|
+
</el-select>
|
|
49
|
+
</el-form-item>
|
|
50
|
+
<el-form-item label="时间范围">
|
|
51
|
+
<el-date-picker
|
|
52
|
+
v-model="filterForm.dateRange"
|
|
53
|
+
type="datetimerange"
|
|
54
|
+
range-separator="至"
|
|
55
|
+
start-placeholder="开始时间"
|
|
56
|
+
end-placeholder="结束时间"
|
|
57
|
+
format="YYYY-MM-DD HH:mm:ss"
|
|
58
|
+
value-format="YYYY-MM-DD HH:mm:ss"
|
|
59
|
+
style="width: 380px"
|
|
60
|
+
/>
|
|
61
|
+
</el-form-item>
|
|
62
|
+
<el-form-item label="关键词">
|
|
63
|
+
<el-input
|
|
64
|
+
v-model="filterForm.keyword"
|
|
65
|
+
placeholder="搜索错误信息、URL、组件名等"
|
|
66
|
+
clearable
|
|
67
|
+
style="width: 300px"
|
|
68
|
+
@keyup.enter="handleSearch"
|
|
69
|
+
>
|
|
70
|
+
<template #prefix>
|
|
71
|
+
<el-icon><Search /></el-icon>
|
|
72
|
+
</template>
|
|
73
|
+
</el-input>
|
|
74
|
+
</el-form-item>
|
|
75
|
+
<el-form-item>
|
|
76
|
+
<el-button type="primary" :icon="Search" @click="handleSearch">搜索</el-button>
|
|
77
|
+
<el-button :icon="Refresh" @click="handleReset">重置</el-button>
|
|
78
|
+
<el-button type="danger" :icon="Delete" @click="handleClear">清空日志</el-button>
|
|
79
|
+
<el-button type="success" :icon="Download" @click="handleExport">导出</el-button>
|
|
80
|
+
</el-form-item>
|
|
81
|
+
</el-form>
|
|
82
|
+
</el-card>
|
|
83
|
+
|
|
84
|
+
<!-- 日志表格 -->
|
|
85
|
+
<el-card class="table-card">
|
|
86
|
+
<el-table
|
|
87
|
+
v-loading="loading"
|
|
88
|
+
:data="tableData"
|
|
89
|
+
stripe
|
|
90
|
+
border
|
|
91
|
+
style="width: 100%"
|
|
92
|
+
@row-click="handleRowClick"
|
|
93
|
+
>
|
|
94
|
+
<el-table-column type="expand">
|
|
95
|
+
<template #default="{ row }">
|
|
96
|
+
<div class="log-detail">
|
|
97
|
+
<el-descriptions :column="2" border>
|
|
98
|
+
<el-descriptions-item label="错误ID">{{ row.id }}</el-descriptions-item>
|
|
99
|
+
<el-descriptions-item label="错误类型">
|
|
100
|
+
<el-tag :type="getTypeTagType(row.type)">{{ getTypeLabel(row.type) }}</el-tag>
|
|
101
|
+
</el-descriptions-item>
|
|
102
|
+
<el-descriptions-item label="时间" :span="2">{{ row.time }}</el-descriptions-item>
|
|
103
|
+
<el-descriptions-item label="页面URL" :span="2">
|
|
104
|
+
<el-link :href="row.pageUrl" target="_blank" type="primary">{{ row.pageUrl }}</el-link>
|
|
105
|
+
</el-descriptions-item>
|
|
106
|
+
<el-descriptions-item v-if="row.type === 'api'" label="请求方法">
|
|
107
|
+
<el-tag>{{ row.method }}</el-tag>
|
|
108
|
+
</el-descriptions-item>
|
|
109
|
+
<el-descriptions-item v-if="row.type === 'api'" label="请求URL" :span="row.method ? 1 : 2">
|
|
110
|
+
{{ row.url }}
|
|
111
|
+
</el-descriptions-item>
|
|
112
|
+
<el-descriptions-item v-if="row.type === 'api' && row.status" label="HTTP状态">
|
|
113
|
+
<el-tag :type="getStatusTagType(row.status)">
|
|
114
|
+
{{ row.status }} {{ row.statusText }}
|
|
115
|
+
</el-tag>
|
|
116
|
+
</el-descriptions-item>
|
|
117
|
+
<el-descriptions-item v-if="row.type === 'api' && Object.keys(row.params || {}).length > 0" label="请求参数" :span="2">
|
|
118
|
+
<pre class="json-content">{{ JSON.stringify(row.params, null, 2) }}</pre>
|
|
119
|
+
</el-descriptions-item>
|
|
120
|
+
<el-descriptions-item v-if="row.fileName" label="文件位置" :span="2">
|
|
121
|
+
{{ row.fileName }}:{{ row.lineNumber }}:{{ row.columnNumber }}
|
|
122
|
+
</el-descriptions-item>
|
|
123
|
+
<el-descriptions-item v-if="row.componentName" label="组件名称" :span="2">
|
|
124
|
+
{{ row.componentName }}
|
|
125
|
+
</el-descriptions-item>
|
|
126
|
+
<el-descriptions-item label="错误信息" :span="2">
|
|
127
|
+
<div class="error-message">{{ row.message }}</div>
|
|
128
|
+
</el-descriptions-item>
|
|
129
|
+
<el-descriptions-item v-if="row.stack" label="堆栈信息" :span="2">
|
|
130
|
+
<pre class="stack-content">{{ row.stack }}</pre>
|
|
131
|
+
</el-descriptions-item>
|
|
132
|
+
<el-descriptions-item label="用户代理" :span="2">
|
|
133
|
+
<div class="user-agent">{{ row.userAgent }}</div>
|
|
134
|
+
</el-descriptions-item>
|
|
135
|
+
</el-descriptions>
|
|
136
|
+
</div>
|
|
137
|
+
</template>
|
|
138
|
+
</el-table-column>
|
|
139
|
+
<el-table-column prop="type" label="类型" width="100" align="center">
|
|
140
|
+
<template #default="{ row }">
|
|
141
|
+
<el-tag :type="getTypeTagType(row.type)" size="small">
|
|
142
|
+
{{ getTypeLabel(row.type) }}
|
|
143
|
+
</el-tag>
|
|
144
|
+
</template>
|
|
145
|
+
</el-table-column>
|
|
146
|
+
<el-table-column prop="time" label="时间" width="180" />
|
|
147
|
+
<el-table-column prop="message" label="错误信息" min-width="300" show-overflow-tooltip />
|
|
148
|
+
<el-table-column v-if="filterForm.type === 'api' || !filterForm.type" prop="url" label="URL" min-width="200" show-overflow-tooltip />
|
|
149
|
+
<el-table-column v-if="filterForm.type === 'api' || !filterForm.type" prop="method" label="方法" width="80" align="center">
|
|
150
|
+
<template #default="{ row }">
|
|
151
|
+
<el-tag v-if="row.method" size="small" :type="getMethodTagType(row.method)">
|
|
152
|
+
{{ row.method }}
|
|
153
|
+
</el-tag>
|
|
154
|
+
</template>
|
|
155
|
+
</el-table-column>
|
|
156
|
+
<el-table-column v-if="filterForm.type === 'api' || !filterForm.type" prop="status" label="状态码" width="100" align="center">
|
|
157
|
+
<template #default="{ row }">
|
|
158
|
+
<el-tag v-if="row.status" :type="getStatusTagType(row.status)" size="small">
|
|
159
|
+
{{ row.status }}
|
|
160
|
+
</el-tag>
|
|
161
|
+
</template>
|
|
162
|
+
</el-table-column>
|
|
163
|
+
<el-table-column prop="pageUrl" label="页面" min-width="200" show-overflow-tooltip />
|
|
164
|
+
<el-table-column label="操作" width="100" align="center" fixed="right">
|
|
165
|
+
<template #default="{ row }">
|
|
166
|
+
<el-button link type="primary" size="small" @click.stop="handleViewDetail(row)">
|
|
167
|
+
详情
|
|
168
|
+
</el-button>
|
|
169
|
+
</template>
|
|
170
|
+
</el-table-column>
|
|
171
|
+
</el-table>
|
|
172
|
+
|
|
173
|
+
<!-- 分页 -->
|
|
174
|
+
<div class="pagination-container">
|
|
175
|
+
<el-pagination
|
|
176
|
+
v-model:current-page="pagination.page"
|
|
177
|
+
v-model:page-size="pagination.pageSize"
|
|
178
|
+
:page-sizes="[10, 20, 50, 100]"
|
|
179
|
+
:total="pagination.total"
|
|
180
|
+
layout="total, sizes, prev, pager, next, jumper"
|
|
181
|
+
@size-change="handleSizeChange"
|
|
182
|
+
@current-change="handlePageChange"
|
|
183
|
+
/>
|
|
184
|
+
</div>
|
|
185
|
+
</el-card>
|
|
186
|
+
|
|
187
|
+
<!-- 详情对话框 -->
|
|
188
|
+
<el-dialog
|
|
189
|
+
v-model="detailVisible"
|
|
190
|
+
title="错误详情"
|
|
191
|
+
width="80%"
|
|
192
|
+
:close-on-click-modal="false"
|
|
193
|
+
>
|
|
194
|
+
<div v-if="currentDetail" class="detail-dialog">
|
|
195
|
+
<el-descriptions :column="2" border>
|
|
196
|
+
<el-descriptions-item label="错误ID">{{ currentDetail.id }}</el-descriptions-item>
|
|
197
|
+
<el-descriptions-item label="错误类型">
|
|
198
|
+
<el-tag :type="getTypeTagType(currentDetail.type)">
|
|
199
|
+
{{ getTypeLabel(currentDetail.type) }}
|
|
200
|
+
</el-tag>
|
|
201
|
+
</el-descriptions-item>
|
|
202
|
+
<el-descriptions-item label="发生时间" :span="2">
|
|
203
|
+
{{ currentDetail.time }}
|
|
204
|
+
</el-descriptions-item>
|
|
205
|
+
<el-descriptions-item label="页面URL" :span="2">
|
|
206
|
+
<el-link :href="currentDetail.pageUrl" target="_blank" type="primary">
|
|
207
|
+
{{ currentDetail.pageUrl }}
|
|
208
|
+
</el-link>
|
|
209
|
+
</el-descriptions-item>
|
|
210
|
+
<template v-if="currentDetail.type === 'api'">
|
|
211
|
+
<el-descriptions-item label="请求方法">
|
|
212
|
+
<el-tag>{{ currentDetail.method }}</el-tag>
|
|
213
|
+
</el-descriptions-item>
|
|
214
|
+
<el-descriptions-item label="请求URL">
|
|
215
|
+
{{ currentDetail.url }}
|
|
216
|
+
</el-descriptions-item>
|
|
217
|
+
<el-descriptions-item v-if="currentDetail.status" label="HTTP状态">
|
|
218
|
+
<el-tag :type="getStatusTagType(currentDetail.status)">
|
|
219
|
+
{{ currentDetail.status }} {{ currentDetail.statusText }}
|
|
220
|
+
</el-tag>
|
|
221
|
+
</el-descriptions-item>
|
|
222
|
+
<el-descriptions-item v-if="Object.keys(currentDetail.params || {}).length > 0" label="请求参数" :span="2">
|
|
223
|
+
<pre class="json-content">{{ JSON.stringify(currentDetail.params, null, 2) }}</pre>
|
|
224
|
+
</el-descriptions-item>
|
|
225
|
+
<el-descriptions-item v-if="Object.keys(currentDetail.data || {}).length > 0" label="请求体" :span="2">
|
|
226
|
+
<pre class="json-content">{{ JSON.stringify(currentDetail.data, null, 2) }}</pre>
|
|
227
|
+
</el-descriptions-item>
|
|
228
|
+
<el-descriptions-item v-if="currentDetail.responseData" label="响应数据" :span="2">
|
|
229
|
+
<pre class="json-content">{{ JSON.stringify(currentDetail.responseData, null, 2) }}</pre>
|
|
230
|
+
</el-descriptions-item>
|
|
231
|
+
</template>
|
|
232
|
+
<template v-else>
|
|
233
|
+
<el-descriptions-item v-if="currentDetail.fileName" label="文件位置" :span="2">
|
|
234
|
+
{{ currentDetail.fileName }}:{{ currentDetail.lineNumber }}:{{ currentDetail.columnNumber }}
|
|
235
|
+
</el-descriptions-item>
|
|
236
|
+
<el-descriptions-item v-if="currentDetail.componentName" label="组件名称" :span="2">
|
|
237
|
+
{{ currentDetail.componentName }}
|
|
238
|
+
</el-descriptions-item>
|
|
239
|
+
<el-descriptions-item v-if="currentDetail.props && Object.keys(currentDetail.props).length > 0" label="组件Props" :span="2">
|
|
240
|
+
<pre class="json-content">{{ JSON.stringify(currentDetail.props, null, 2) }}</pre>
|
|
241
|
+
</el-descriptions-item>
|
|
242
|
+
</template>
|
|
243
|
+
<el-descriptions-item label="错误信息" :span="2">
|
|
244
|
+
<div class="error-message">{{ currentDetail.message }}</div>
|
|
245
|
+
</el-descriptions-item>
|
|
246
|
+
<el-descriptions-item v-if="currentDetail.stack" label="堆栈信息" :span="2">
|
|
247
|
+
<pre class="stack-content">{{ currentDetail.stack }}</pre>
|
|
248
|
+
</el-descriptions-item>
|
|
249
|
+
<el-descriptions-item label="用户代理" :span="2">
|
|
250
|
+
<div class="user-agent">{{ currentDetail.userAgent }}</div>
|
|
251
|
+
</el-descriptions-item>
|
|
252
|
+
</el-descriptions>
|
|
253
|
+
</div>
|
|
254
|
+
</el-dialog>
|
|
255
|
+
</div>
|
|
256
|
+
</template>
|
|
257
|
+
|
|
258
|
+
<script setup>
|
|
259
|
+
import { ref, reactive, onMounted, computed } from 'vue'
|
|
260
|
+
import { ElMessage, ElMessageBox } from 'element-plus'
|
|
261
|
+
import { Search, Refresh, Delete, Download } from '@element-plus/icons-vue'
|
|
262
|
+
import errorMonitor from '@/utils/errorMonitor'
|
|
263
|
+
|
|
264
|
+
// 响应式数据
|
|
265
|
+
const loading = ref(false)
|
|
266
|
+
const tableData = ref([])
|
|
267
|
+
const detailVisible = ref(false)
|
|
268
|
+
const currentDetail = ref(null)
|
|
269
|
+
const statistics = ref({
|
|
270
|
+
total: 0,
|
|
271
|
+
api: 0,
|
|
272
|
+
page: 0,
|
|
273
|
+
promise: 0,
|
|
274
|
+
today: 0,
|
|
275
|
+
thisWeek: 0,
|
|
276
|
+
thisMonth: 0
|
|
277
|
+
})
|
|
278
|
+
|
|
279
|
+
// 筛选表单
|
|
280
|
+
const filterForm = reactive({
|
|
281
|
+
type: '',
|
|
282
|
+
dateRange: null,
|
|
283
|
+
keyword: ''
|
|
284
|
+
})
|
|
285
|
+
|
|
286
|
+
// 分页
|
|
287
|
+
const pagination = reactive({
|
|
288
|
+
page: 1,
|
|
289
|
+
pageSize: 20,
|
|
290
|
+
total: 0
|
|
291
|
+
})
|
|
292
|
+
|
|
293
|
+
// 获取类型标签
|
|
294
|
+
const getTypeLabel = (type) => {
|
|
295
|
+
const labels = {
|
|
296
|
+
api: '接口错误',
|
|
297
|
+
page: '页面错误',
|
|
298
|
+
promise: 'Promise错误'
|
|
299
|
+
}
|
|
300
|
+
return labels[type] || type
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
// 获取类型标签类型
|
|
304
|
+
const getTypeTagType = (type) => {
|
|
305
|
+
const types = {
|
|
306
|
+
api: 'danger',
|
|
307
|
+
page: 'warning',
|
|
308
|
+
promise: 'info'
|
|
309
|
+
}
|
|
310
|
+
return types[type] || ''
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
// 获取状态标签类型
|
|
314
|
+
const getStatusTagType = (status) => {
|
|
315
|
+
if (status >= 200 && status < 300) return 'success'
|
|
316
|
+
if (status >= 400 && status < 500) return 'warning'
|
|
317
|
+
if (status >= 500) return 'danger'
|
|
318
|
+
return 'info'
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
// 获取方法标签类型
|
|
322
|
+
const getMethodTagType = (method) => {
|
|
323
|
+
const types = {
|
|
324
|
+
GET: 'success',
|
|
325
|
+
POST: 'primary',
|
|
326
|
+
PUT: 'warning',
|
|
327
|
+
DELETE: 'danger'
|
|
328
|
+
}
|
|
329
|
+
return types[method] || ''
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
// 加载数据
|
|
333
|
+
const loadData = async () => {
|
|
334
|
+
loading.value = true
|
|
335
|
+
try {
|
|
336
|
+
// 构建查询参数
|
|
337
|
+
const params = {
|
|
338
|
+
page: pagination.page,
|
|
339
|
+
pageSize: pagination.pageSize,
|
|
340
|
+
type: filterForm.type || undefined,
|
|
341
|
+
keyword: filterForm.keyword || undefined
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
// 添加时间范围
|
|
345
|
+
if (filterForm.dateRange && filterForm.dateRange.length === 2) {
|
|
346
|
+
params.startTime = filterForm.dateRange[0]
|
|
347
|
+
params.endTime = filterForm.dateRange[1]
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
// 获取日志列表
|
|
351
|
+
const logs = await errorMonitor.getLogs(params.type, params)
|
|
352
|
+
|
|
353
|
+
// 客户端筛选和分页(如果后端不支持)
|
|
354
|
+
let filteredLogs = logs
|
|
355
|
+
if (filterForm.keyword) {
|
|
356
|
+
const keyword = filterForm.keyword.toLowerCase()
|
|
357
|
+
filteredLogs = logs.filter(log => {
|
|
358
|
+
return (
|
|
359
|
+
log.message?.toLowerCase().includes(keyword) ||
|
|
360
|
+
log.url?.toLowerCase().includes(keyword) ||
|
|
361
|
+
log.pageUrl?.toLowerCase().includes(keyword) ||
|
|
362
|
+
log.componentName?.toLowerCase().includes(keyword)
|
|
363
|
+
)
|
|
364
|
+
})
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
if (filterForm.dateRange && filterForm.dateRange.length === 2) {
|
|
368
|
+
const [start, end] = filterForm.dateRange
|
|
369
|
+
filteredLogs = filteredLogs.filter(log => {
|
|
370
|
+
const logTime = new Date(log.timestamp || log.time)
|
|
371
|
+
return logTime >= new Date(start) && logTime <= new Date(end)
|
|
372
|
+
})
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
// 分页
|
|
376
|
+
pagination.total = filteredLogs.length
|
|
377
|
+
const start = (pagination.page - 1) * pagination.pageSize
|
|
378
|
+
const end = start + pagination.pageSize
|
|
379
|
+
tableData.value = filteredLogs.slice(start, end)
|
|
380
|
+
|
|
381
|
+
// 获取统计信息
|
|
382
|
+
statistics.value = await errorMonitor.getStatistics()
|
|
383
|
+
} catch (error) {
|
|
384
|
+
console.error('加载数据失败:', error)
|
|
385
|
+
ElMessage.error('加载数据失败')
|
|
386
|
+
} finally {
|
|
387
|
+
loading.value = false
|
|
388
|
+
}
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
// 搜索
|
|
392
|
+
const handleSearch = () => {
|
|
393
|
+
pagination.page = 1
|
|
394
|
+
loadData()
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
// 重置
|
|
398
|
+
const handleReset = () => {
|
|
399
|
+
filterForm.type = ''
|
|
400
|
+
filterForm.dateRange = null
|
|
401
|
+
filterForm.keyword = ''
|
|
402
|
+
pagination.page = 1
|
|
403
|
+
loadData()
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
// 清空日志
|
|
407
|
+
const handleClear = async () => {
|
|
408
|
+
try {
|
|
409
|
+
await ElMessageBox.confirm(
|
|
410
|
+
'确定要清空所有错误日志吗?此操作不可恢复。',
|
|
411
|
+
'确认清空',
|
|
412
|
+
{
|
|
413
|
+
confirmButtonText: '确定',
|
|
414
|
+
cancelButtonText: '取消',
|
|
415
|
+
type: 'warning'
|
|
416
|
+
}
|
|
417
|
+
)
|
|
418
|
+
|
|
419
|
+
await errorMonitor.clearLogs()
|
|
420
|
+
ElMessage.success('日志已清空')
|
|
421
|
+
await loadData()
|
|
422
|
+
} catch (error) {
|
|
423
|
+
if (error !== 'cancel') {
|
|
424
|
+
console.error('清空日志失败:', error)
|
|
425
|
+
ElMessage.error('清空日志失败')
|
|
426
|
+
}
|
|
427
|
+
}
|
|
428
|
+
}
|
|
429
|
+
|
|
430
|
+
// 导出
|
|
431
|
+
const handleExport = async () => {
|
|
432
|
+
try {
|
|
433
|
+
await errorMonitor.exportLogs()
|
|
434
|
+
ElMessage.success('导出成功')
|
|
435
|
+
} catch (error) {
|
|
436
|
+
console.error('导出失败:', error)
|
|
437
|
+
ElMessage.error('导出失败')
|
|
438
|
+
}
|
|
439
|
+
}
|
|
440
|
+
|
|
441
|
+
// 查看详情
|
|
442
|
+
const handleViewDetail = (row) => {
|
|
443
|
+
currentDetail.value = row
|
|
444
|
+
detailVisible.value = true
|
|
445
|
+
}
|
|
446
|
+
|
|
447
|
+
// 行点击
|
|
448
|
+
const handleRowClick = (row) => {
|
|
449
|
+
// 点击行可以展开/收起详情
|
|
450
|
+
}
|
|
451
|
+
|
|
452
|
+
// 分页大小改变
|
|
453
|
+
const handleSizeChange = (size) => {
|
|
454
|
+
pagination.pageSize = size
|
|
455
|
+
pagination.page = 1
|
|
456
|
+
loadData()
|
|
457
|
+
}
|
|
458
|
+
|
|
459
|
+
// 页码改变
|
|
460
|
+
const handlePageChange = (page) => {
|
|
461
|
+
pagination.page = page
|
|
462
|
+
loadData()
|
|
463
|
+
}
|
|
464
|
+
|
|
465
|
+
// 初始化
|
|
466
|
+
onMounted(() => {
|
|
467
|
+
loadData()
|
|
468
|
+
})
|
|
469
|
+
</script>
|
|
470
|
+
|
|
471
|
+
<style lang="scss" scoped>
|
|
472
|
+
.pc-error-log-page {
|
|
473
|
+
padding: 20px;
|
|
474
|
+
background-color: #f5f7fa;
|
|
475
|
+
min-height: 100vh;
|
|
476
|
+
|
|
477
|
+
.stats-row {
|
|
478
|
+
margin-bottom: 20px;
|
|
479
|
+
|
|
480
|
+
.stat-card {
|
|
481
|
+
.stat-content {
|
|
482
|
+
display: flex;
|
|
483
|
+
flex-direction: column;
|
|
484
|
+
align-items: center;
|
|
485
|
+
|
|
486
|
+
.stat-label {
|
|
487
|
+
font-size: 12px;
|
|
488
|
+
color: #606266;
|
|
489
|
+
margin-bottom: 10px;
|
|
490
|
+
}
|
|
491
|
+
|
|
492
|
+
.stat-value {
|
|
493
|
+
font-size: 32px;
|
|
494
|
+
font-weight: bold;
|
|
495
|
+
|
|
496
|
+
&.total {
|
|
497
|
+
color: #409eff;
|
|
498
|
+
}
|
|
499
|
+
|
|
500
|
+
&.api {
|
|
501
|
+
color: #f56c6c;
|
|
502
|
+
}
|
|
503
|
+
|
|
504
|
+
&.page {
|
|
505
|
+
color: #e6a23c;
|
|
506
|
+
}
|
|
507
|
+
|
|
508
|
+
&.promise {
|
|
509
|
+
color: #909399;
|
|
510
|
+
}
|
|
511
|
+
}
|
|
512
|
+
}
|
|
513
|
+
}
|
|
514
|
+
}
|
|
515
|
+
|
|
516
|
+
.filter-card {
|
|
517
|
+
margin-bottom: 20px;
|
|
518
|
+
|
|
519
|
+
.filter-form {
|
|
520
|
+
margin: 0;
|
|
521
|
+
}
|
|
522
|
+
}
|
|
523
|
+
|
|
524
|
+
.table-card {
|
|
525
|
+
.log-detail {
|
|
526
|
+
padding: 10px;
|
|
527
|
+
|
|
528
|
+
.json-content,
|
|
529
|
+
.stack-content {
|
|
530
|
+
background-color: #f5f7fa;
|
|
531
|
+
padding: 10px;
|
|
532
|
+
border-radius: 4px;
|
|
533
|
+
font-size: 12px;
|
|
534
|
+
max-height: 300px;
|
|
535
|
+
overflow-y: auto;
|
|
536
|
+
margin: 0;
|
|
537
|
+
}
|
|
538
|
+
|
|
539
|
+
.error-message {
|
|
540
|
+
color: #f56c6c;
|
|
541
|
+
word-break: break-all;
|
|
542
|
+
}
|
|
543
|
+
|
|
544
|
+
.user-agent {
|
|
545
|
+
font-size: 12px;
|
|
546
|
+
color: #909399;
|
|
547
|
+
word-break: break-all;
|
|
548
|
+
}
|
|
549
|
+
}
|
|
550
|
+
|
|
551
|
+
.pagination-container {
|
|
552
|
+
margin-top: 20px;
|
|
553
|
+
display: flex;
|
|
554
|
+
justify-content: flex-end;
|
|
555
|
+
}
|
|
556
|
+
}
|
|
557
|
+
|
|
558
|
+
.detail-dialog {
|
|
559
|
+
.json-content,
|
|
560
|
+
.stack-content {
|
|
561
|
+
background-color: #f5f7fa;
|
|
562
|
+
padding: 15px;
|
|
563
|
+
border-radius: 4px;
|
|
564
|
+
font-size: 13px;
|
|
565
|
+
max-height: 400px;
|
|
566
|
+
overflow-y: auto;
|
|
567
|
+
margin: 0;
|
|
568
|
+
line-height: 1.5;
|
|
569
|
+
}
|
|
570
|
+
|
|
571
|
+
.error-message {
|
|
572
|
+
color: #f56c6c;
|
|
573
|
+
font-weight: 500;
|
|
574
|
+
word-break: break-all;
|
|
575
|
+
}
|
|
576
|
+
|
|
577
|
+
.user-agent {
|
|
578
|
+
font-size: 12px;
|
|
579
|
+
color: #909399;
|
|
580
|
+
word-break: break-all;
|
|
581
|
+
}
|
|
582
|
+
}
|
|
583
|
+
}
|
|
584
|
+
</style>
|
|
585
|
+
|
|
Binary file
|