vue_zhongyou 1.0.1 → 1.0.3
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/errorLogPage.vue +422 -0
- 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/errorMonitor.js +375 -0
- 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//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/request.js +99 -0
- 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/testError.vue +500 -0
|
Binary file
|
package/package.json
CHANGED
|
@@ -0,0 +1,422 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<div class="error-log-page">
|
|
3
|
+
<!-- 顶部统计卡片 -->
|
|
4
|
+
<div class="stats-section">
|
|
5
|
+
<div class="stat-card" v-for="(value, key) in statistics" :key="key">
|
|
6
|
+
<div class="stat-label">{{ getStatLabel(key) }}</div>
|
|
7
|
+
<div class="stat-value">{{ value }}</div>
|
|
8
|
+
</div>
|
|
9
|
+
</div>
|
|
10
|
+
|
|
11
|
+
<!-- 筛选和操作栏 -->
|
|
12
|
+
<div class="toolbar">
|
|
13
|
+
<van-radio-group v-model="filterType" direction="horizontal" @change="handleFilterChange">
|
|
14
|
+
<van-radio name="">全部</van-radio>
|
|
15
|
+
<van-radio name="api">接口</van-radio>
|
|
16
|
+
<van-radio name="page">页面</van-radio>
|
|
17
|
+
<van-radio name="promise">Promise</van-radio>
|
|
18
|
+
</van-radio-group>
|
|
19
|
+
<div class="actions">
|
|
20
|
+
<van-button size="small" type="danger" plain @click="handleClear">清空</van-button>
|
|
21
|
+
<van-button size="small" type="primary" @click="handleExportJSON">导出JSON</van-button>
|
|
22
|
+
<van-button size="small" type="primary" @click="handleExportText">导出TXT</van-button>
|
|
23
|
+
</div>
|
|
24
|
+
</div>
|
|
25
|
+
|
|
26
|
+
<!-- 日志列表 -->
|
|
27
|
+
<div class="logs-container">
|
|
28
|
+
<van-empty v-if="filteredLogs.length === 0" description="暂无错误日志" />
|
|
29
|
+
<div v-else class="logs-list">
|
|
30
|
+
<div
|
|
31
|
+
v-for="log in filteredLogs"
|
|
32
|
+
:key="log.id"
|
|
33
|
+
class="log-item"
|
|
34
|
+
:class="`log-${log.type}`"
|
|
35
|
+
@click="toggleLogDetail(log)"
|
|
36
|
+
>
|
|
37
|
+
<div class="log-header">
|
|
38
|
+
<div class="log-type-badge" :class="`badge-${log.type}`">
|
|
39
|
+
{{ getTypeLabel(log.type) }}
|
|
40
|
+
</div>
|
|
41
|
+
<div class="log-time">{{ log.time }}</div>
|
|
42
|
+
</div>
|
|
43
|
+
<div class="log-message">{{ log.message || '无错误信息' }}</div>
|
|
44
|
+
<div v-if="log.type === 'api'" class="log-api-info">
|
|
45
|
+
<span class="method">{{ log.method }}</span>
|
|
46
|
+
<span class="url">{{ log.url }}</span>
|
|
47
|
+
<span v-if="log.status" class="status" :class="getStatusClass(log.status)">
|
|
48
|
+
{{ log.status }} {{ log.statusText }}
|
|
49
|
+
</span>
|
|
50
|
+
</div>
|
|
51
|
+
<div v-if="expandedLogs[log.id]" class="log-detail">
|
|
52
|
+
<div class="detail-row">
|
|
53
|
+
<span class="detail-label">页面URL:</span>
|
|
54
|
+
<span class="detail-value">{{ log.pageUrl }}</span>
|
|
55
|
+
</div>
|
|
56
|
+
<div v-if="log.type === 'api'" class="detail-row">
|
|
57
|
+
<span class="detail-label">请求方法:</span>
|
|
58
|
+
<span class="detail-value">{{ log.method }}</span>
|
|
59
|
+
</div>
|
|
60
|
+
<div v-if="log.type === 'api'" class="detail-row">
|
|
61
|
+
<span class="detail-label">请求URL:</span>
|
|
62
|
+
<span class="detail-value">{{ log.url }}</span>
|
|
63
|
+
</div>
|
|
64
|
+
<div v-if="log.type === 'api' && Object.keys(log.params || {}).length > 0" class="detail-row">
|
|
65
|
+
<span class="detail-label">请求参数:</span>
|
|
66
|
+
<pre class="detail-value">{{ JSON.stringify(log.params, null, 2) }}</pre>
|
|
67
|
+
</div>
|
|
68
|
+
<div v-if="log.fileName" class="detail-row">
|
|
69
|
+
<span class="detail-label">文件:</span>
|
|
70
|
+
<span class="detail-value">{{ log.fileName }}:{{ log.lineNumber }}:{{ log.columnNumber }}</span>
|
|
71
|
+
</div>
|
|
72
|
+
<div v-if="log.componentName" class="detail-row">
|
|
73
|
+
<span class="detail-label">组件:</span>
|
|
74
|
+
<span class="detail-value">{{ log.componentName }}</span>
|
|
75
|
+
</div>
|
|
76
|
+
<div v-if="log.stack" class="detail-row">
|
|
77
|
+
<span class="detail-label">堆栈信息:</span>
|
|
78
|
+
<pre class="detail-value stack">{{ log.stack }}</pre>
|
|
79
|
+
</div>
|
|
80
|
+
<div class="detail-row">
|
|
81
|
+
<span class="detail-label">用户代理:</span>
|
|
82
|
+
<span class="detail-value small">{{ log.userAgent }}</span>
|
|
83
|
+
</div>
|
|
84
|
+
</div>
|
|
85
|
+
</div>
|
|
86
|
+
</div>
|
|
87
|
+
</div>
|
|
88
|
+
</div>
|
|
89
|
+
</template>
|
|
90
|
+
|
|
91
|
+
<script setup>
|
|
92
|
+
import { ref, computed, onMounted } from 'vue'
|
|
93
|
+
import { showConfirmDialog, showToast, showSuccessToast, showFailToast } from 'vant'
|
|
94
|
+
import errorMonitor from '@/utils/errorMonitor'
|
|
95
|
+
|
|
96
|
+
const filterType = ref('')
|
|
97
|
+
const expandedLogs = ref({})
|
|
98
|
+
const statistics = ref({
|
|
99
|
+
total: 0,
|
|
100
|
+
api: 0,
|
|
101
|
+
page: 0,
|
|
102
|
+
promise: 0,
|
|
103
|
+
today: 0,
|
|
104
|
+
thisWeek: 0,
|
|
105
|
+
thisMonth: 0
|
|
106
|
+
})
|
|
107
|
+
|
|
108
|
+
const allLogs = ref([])
|
|
109
|
+
const filteredLogs = computed(() => {
|
|
110
|
+
if (!filterType.value) {
|
|
111
|
+
return allLogs.value
|
|
112
|
+
}
|
|
113
|
+
return allLogs.value.filter(log => log.type === filterType.value)
|
|
114
|
+
})
|
|
115
|
+
|
|
116
|
+
// 获取统计标签
|
|
117
|
+
const getStatLabel = (key) => {
|
|
118
|
+
const labels = {
|
|
119
|
+
total: '总计',
|
|
120
|
+
api: '接口错误',
|
|
121
|
+
page: '页面错误',
|
|
122
|
+
promise: 'Promise错误',
|
|
123
|
+
today: '今日',
|
|
124
|
+
thisWeek: '本周',
|
|
125
|
+
thisMonth: '本月'
|
|
126
|
+
}
|
|
127
|
+
return labels[key] || key
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
// 获取类型标签
|
|
131
|
+
const getTypeLabel = (type) => {
|
|
132
|
+
const labels = {
|
|
133
|
+
api: '接口',
|
|
134
|
+
page: '页面',
|
|
135
|
+
promise: 'Promise'
|
|
136
|
+
}
|
|
137
|
+
return labels[type] || type
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
// 获取状态样式类
|
|
141
|
+
const getStatusClass = (status) => {
|
|
142
|
+
if (status >= 200 && status < 300) return 'status-success'
|
|
143
|
+
if (status >= 400 && status < 500) return 'status-client-error'
|
|
144
|
+
if (status >= 500) return 'status-server-error'
|
|
145
|
+
return 'status-unknown'
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
// 切换日志详情
|
|
149
|
+
const toggleLogDetail = (log) => {
|
|
150
|
+
expandedLogs.value[log.id] = !expandedLogs.value[log.id]
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
// 筛选改变
|
|
154
|
+
const handleFilterChange = () => {
|
|
155
|
+
expandedLogs.value = {}
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
// 加载日志
|
|
159
|
+
const loadLogs = async () => {
|
|
160
|
+
try {
|
|
161
|
+
const [logs, stats] = await Promise.all([
|
|
162
|
+
errorMonitor.getLogs(),
|
|
163
|
+
errorMonitor.getStatistics()
|
|
164
|
+
])
|
|
165
|
+
allLogs.value = logs
|
|
166
|
+
statistics.value = stats
|
|
167
|
+
} catch (error) {
|
|
168
|
+
console.error('加载日志失败:', error)
|
|
169
|
+
showFailToast('加载日志失败')
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
// 清空日志
|
|
174
|
+
const handleClear = async () => {
|
|
175
|
+
try {
|
|
176
|
+
await showConfirmDialog({
|
|
177
|
+
title: '确认清空',
|
|
178
|
+
message: '确定要清空所有错误日志吗?此操作不可恢复。'
|
|
179
|
+
})
|
|
180
|
+
await errorMonitor.clearLogs()
|
|
181
|
+
await loadLogs()
|
|
182
|
+
showSuccessToast('日志已清空')
|
|
183
|
+
} catch (error) {
|
|
184
|
+
if (error !== 'cancel') {
|
|
185
|
+
console.error('清空日志失败:', error)
|
|
186
|
+
showFailToast('清空日志失败')
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
// 导出JSON
|
|
192
|
+
const handleExportJSON = async () => {
|
|
193
|
+
try {
|
|
194
|
+
await errorMonitor.exportLogs()
|
|
195
|
+
showSuccessToast('导出成功')
|
|
196
|
+
} catch (error) {
|
|
197
|
+
showFailToast('导出失败')
|
|
198
|
+
console.error('导出失败:', error)
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
// 导出TXT
|
|
203
|
+
const handleExportText = async () => {
|
|
204
|
+
try {
|
|
205
|
+
await errorMonitor.exportLogsAsText()
|
|
206
|
+
showSuccessToast('导出成功')
|
|
207
|
+
} catch (error) {
|
|
208
|
+
showFailToast('导出失败')
|
|
209
|
+
console.error('导出失败:', error)
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
onMounted(() => {
|
|
214
|
+
loadLogs()
|
|
215
|
+
})
|
|
216
|
+
</script>
|
|
217
|
+
|
|
218
|
+
<style lang="scss" scoped>
|
|
219
|
+
.error-log-page {
|
|
220
|
+
padding: 16px;
|
|
221
|
+
background-color: #f5f5f5;
|
|
222
|
+
min-height: 100vh;
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
.stats-section {
|
|
226
|
+
display: grid;
|
|
227
|
+
grid-template-columns: repeat(2, 1fr);
|
|
228
|
+
gap: 12px;
|
|
229
|
+
margin-bottom: 16px;
|
|
230
|
+
|
|
231
|
+
.stat-card {
|
|
232
|
+
background: #fff;
|
|
233
|
+
border-radius: 8px;
|
|
234
|
+
padding: 16px;
|
|
235
|
+
text-align: center;
|
|
236
|
+
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.05);
|
|
237
|
+
|
|
238
|
+
.stat-label {
|
|
239
|
+
font-size: 12px;
|
|
240
|
+
color: #666;
|
|
241
|
+
margin-bottom: 8px;
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
.stat-value {
|
|
245
|
+
font-size: 24px;
|
|
246
|
+
font-weight: bold;
|
|
247
|
+
color: #333;
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
.toolbar {
|
|
253
|
+
background: #fff;
|
|
254
|
+
border-radius: 8px;
|
|
255
|
+
padding: 12px;
|
|
256
|
+
margin-bottom: 16px;
|
|
257
|
+
display: flex;
|
|
258
|
+
flex-direction: column;
|
|
259
|
+
gap: 12px;
|
|
260
|
+
|
|
261
|
+
.actions {
|
|
262
|
+
display: flex;
|
|
263
|
+
gap: 8px;
|
|
264
|
+
justify-content: flex-end;
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
.logs-container {
|
|
269
|
+
.logs-list {
|
|
270
|
+
display: flex;
|
|
271
|
+
flex-direction: column;
|
|
272
|
+
gap: 12px;
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
.log-item {
|
|
276
|
+
background: #fff;
|
|
277
|
+
border-radius: 8px;
|
|
278
|
+
padding: 16px;
|
|
279
|
+
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.05);
|
|
280
|
+
cursor: pointer;
|
|
281
|
+
transition: all 0.2s;
|
|
282
|
+
|
|
283
|
+
&:active {
|
|
284
|
+
transform: scale(0.98);
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
.log-header {
|
|
288
|
+
display: flex;
|
|
289
|
+
justify-content: space-between;
|
|
290
|
+
align-items: center;
|
|
291
|
+
margin-bottom: 8px;
|
|
292
|
+
|
|
293
|
+
.log-type-badge {
|
|
294
|
+
display: inline-block;
|
|
295
|
+
padding: 4px 8px;
|
|
296
|
+
border-radius: 4px;
|
|
297
|
+
font-size: 12px;
|
|
298
|
+
font-weight: 500;
|
|
299
|
+
color: #fff;
|
|
300
|
+
|
|
301
|
+
&.badge-api {
|
|
302
|
+
background-color: #ff6b6b;
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
&.badge-page {
|
|
306
|
+
background-color: #4ecdc4;
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
&.badge-promise {
|
|
310
|
+
background-color: #95e1d3;
|
|
311
|
+
}
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
.log-time {
|
|
315
|
+
font-size: 12px;
|
|
316
|
+
color: #999;
|
|
317
|
+
}
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
.log-message {
|
|
321
|
+
font-size: 14px;
|
|
322
|
+
color: #333;
|
|
323
|
+
margin-bottom: 8px;
|
|
324
|
+
word-break: break-all;
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
.log-api-info {
|
|
328
|
+
display: flex;
|
|
329
|
+
align-items: center;
|
|
330
|
+
gap: 8px;
|
|
331
|
+
flex-wrap: wrap;
|
|
332
|
+
font-size: 12px;
|
|
333
|
+
|
|
334
|
+
.method {
|
|
335
|
+
padding: 2px 6px;
|
|
336
|
+
background-color: #f0f0f0;
|
|
337
|
+
border-radius: 3px;
|
|
338
|
+
font-weight: 500;
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
.url {
|
|
342
|
+
color: #666;
|
|
343
|
+
flex: 1;
|
|
344
|
+
word-break: break-all;
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
.status {
|
|
348
|
+
padding: 2px 6px;
|
|
349
|
+
border-radius: 3px;
|
|
350
|
+
font-weight: 500;
|
|
351
|
+
|
|
352
|
+
&.status-success {
|
|
353
|
+
background-color: #d4edda;
|
|
354
|
+
color: #155724;
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
&.status-client-error {
|
|
358
|
+
background-color: #f8d7da;
|
|
359
|
+
color: #721c24;
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
&.status-server-error {
|
|
363
|
+
background-color: #f5c6cb;
|
|
364
|
+
color: #721c24;
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
&.status-unknown {
|
|
368
|
+
background-color: #fff3cd;
|
|
369
|
+
color: #856404;
|
|
370
|
+
}
|
|
371
|
+
}
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
.log-detail {
|
|
375
|
+
margin-top: 12px;
|
|
376
|
+
padding-top: 12px;
|
|
377
|
+
border-top: 1px solid #f0f0f0;
|
|
378
|
+
|
|
379
|
+
.detail-row {
|
|
380
|
+
margin-bottom: 8px;
|
|
381
|
+
|
|
382
|
+
.detail-label {
|
|
383
|
+
font-size: 12px;
|
|
384
|
+
color: #999;
|
|
385
|
+
margin-right: 8px;
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
.detail-value {
|
|
389
|
+
font-size: 12px;
|
|
390
|
+
color: #666;
|
|
391
|
+
word-break: break-all;
|
|
392
|
+
|
|
393
|
+
&.small {
|
|
394
|
+
font-size: 11px;
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
&.stack {
|
|
398
|
+
background-color: #f5f5f5;
|
|
399
|
+
padding: 8px;
|
|
400
|
+
border-radius: 4px;
|
|
401
|
+
white-space: pre-wrap;
|
|
402
|
+
font-family: monospace;
|
|
403
|
+
max-height: 200px;
|
|
404
|
+
overflow-y: auto;
|
|
405
|
+
}
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
pre {
|
|
409
|
+
margin: 4px 0;
|
|
410
|
+
white-space: pre-wrap;
|
|
411
|
+
font-size: 12px;
|
|
412
|
+
background-color: #f5f5f5;
|
|
413
|
+
padding: 8px;
|
|
414
|
+
border-radius: 4px;
|
|
415
|
+
overflow-x: auto;
|
|
416
|
+
}
|
|
417
|
+
}
|
|
418
|
+
}
|
|
419
|
+
}
|
|
420
|
+
}
|
|
421
|
+
</style>
|
|
422
|
+
|