foggy-data-viewer 1.0.1-beta.0

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.
Files changed (34) hide show
  1. package/README.md +273 -0
  2. package/dist/favicon.svg +4 -0
  3. package/dist/index.js +1531 -0
  4. package/dist/index.umd +1 -0
  5. package/dist/style.css +1 -0
  6. package/package.json +51 -0
  7. package/src/App.vue +469 -0
  8. package/src/api/viewer.ts +163 -0
  9. package/src/components/DataTable.test.ts +533 -0
  10. package/src/components/DataTable.vue +810 -0
  11. package/src/components/DataTableWithSearch.test.ts +628 -0
  12. package/src/components/DataTableWithSearch.vue +277 -0
  13. package/src/components/DataViewer.vue +310 -0
  14. package/src/components/SearchToolbar.test.ts +521 -0
  15. package/src/components/SearchToolbar.vue +406 -0
  16. package/src/components/composables/index.ts +2 -0
  17. package/src/components/composables/useTableSelection.test.ts +248 -0
  18. package/src/components/composables/useTableSelection.ts +44 -0
  19. package/src/components/composables/useTableSummary.test.ts +341 -0
  20. package/src/components/composables/useTableSummary.ts +129 -0
  21. package/src/components/filters/BoolFilter.vue +103 -0
  22. package/src/components/filters/DateRangeFilter.vue +194 -0
  23. package/src/components/filters/NumberRangeFilter.vue +160 -0
  24. package/src/components/filters/SelectFilter.vue +464 -0
  25. package/src/components/filters/TextFilter.vue +230 -0
  26. package/src/components/filters/index.ts +5 -0
  27. package/src/examples/EnhancedTableExample.vue +136 -0
  28. package/src/index.ts +32 -0
  29. package/src/main.ts +14 -0
  30. package/src/types/index.ts +159 -0
  31. package/src/utils/README.md +140 -0
  32. package/src/utils/schemaHelper.test.ts +215 -0
  33. package/src/utils/schemaHelper.ts +44 -0
  34. package/src/vite-env.d.ts +7 -0
package/package.json ADDED
@@ -0,0 +1,51 @@
1
+ {
2
+ "name": "foggy-data-viewer",
3
+ "version": "1.0.1-beta.0",
4
+ "description": "A Vue 3 data table component with advanced features",
5
+ "author": "Foggy Framework",
6
+ "license": "Apache-2.0",
7
+ "private": false,
8
+ "type": "module",
9
+ "main": "./dist/index.js",
10
+ "module": "./dist/index.js",
11
+ "types": "./src/index.ts",
12
+ "exports": {
13
+ ".": {
14
+ "import": "./dist/index.js",
15
+ "types": "./src/index.ts"
16
+ },
17
+ "./style.css": "./dist/style.css"
18
+ },
19
+ "files": [
20
+ "dist",
21
+ "src",
22
+ "README.md"
23
+ ],
24
+ "scripts": {
25
+ "dev": "vite",
26
+ "build": "vite build && vue-tsc --declaration --emitDeclarationOnly --outDir dist",
27
+ "build:lib": "vite build --mode lib",
28
+ "preview": "vite preview",
29
+ "test": "vitest",
30
+ "test:ui": "vitest --ui",
31
+ "test:coverage": "vitest --coverage"
32
+ },
33
+ "dependencies": {
34
+ "axios": "^1.6.0",
35
+ "element-plus": "^2.13.0",
36
+ "vue": "^3.4.0",
37
+ "vxe-table": "^4.6.0",
38
+ "xe-utils": "^3.5.0"
39
+ },
40
+ "devDependencies": {
41
+ "@vitejs/plugin-vue": "^5.0.0",
42
+ "@vitest/coverage-v8": "^1.0.0",
43
+ "@vitest/ui": "^1.0.0",
44
+ "@vue/test-utils": "^2.4.0",
45
+ "happy-dom": "^12.10.0",
46
+ "typescript": "^5.3.0",
47
+ "vite": "^5.0.0",
48
+ "vitest": "^1.0.0",
49
+ "vue-tsc": "^1.8.0"
50
+ }
51
+ }
package/src/App.vue ADDED
@@ -0,0 +1,469 @@
1
+ <script setup lang="ts">
2
+ import { ref, computed } from 'vue'
3
+ import DataViewer from './components/DataViewer.vue'
4
+ import { createQuery, type CreateQueryRequest } from './api/viewer'
5
+
6
+ // 从 URL 中获取 queryId
7
+ const queryId = computed(() => {
8
+ const path = window.location.pathname
9
+ // 匹配 /data-viewer/view/{queryId} 格式
10
+ const match = path.match(/\/data-viewer\/view\/([^/]+)/)
11
+ return match ? match[1] : null
12
+ })
13
+
14
+ // DSL 输入相关状态
15
+ const dslInput = ref('')
16
+ const isSubmitting = ref(false)
17
+ const errorMessage = ref('')
18
+
19
+ // 示例 DSL 查询
20
+ const examples = [
21
+ {
22
+ name: '销售明细查询',
23
+ description: '查询2024年12月的销售订单明细',
24
+ dsl: {
25
+ model: 'FactSalesQueryModel',
26
+ title: '销售明细查询',
27
+ payload: {
28
+ columns: ['orderId', 'salesDate$caption', 'product$caption', 'customer$caption', 'quantity', 'salesAmount', 'profitAmount'],
29
+ slice: [
30
+ { field: 'salesDate$caption', op: '>=', value: '2024-12-01' },
31
+ { field: 'salesDate$caption', op: '<', value: '2024-12-31' }
32
+ ],
33
+ orderBy: [{ field: 'salesDate$caption', order: 'desc' }]
34
+ }
35
+ }
36
+ },
37
+ {
38
+ name: '订单查询',
39
+ description: '查询2024年12月的订单信息',
40
+ dsl: {
41
+ model: 'FactOrderQueryModel',
42
+ title: '订单查询',
43
+ payload: {
44
+ columns: ['orderId', 'orderStatus', 'paymentStatus', 'orderTime', 'customer$caption', 'amount', 'payAmount'],
45
+ slice: [
46
+ { field: 'orderDate$caption', op: '>=', value: '2024-12-01' },
47
+ { field: 'orderDate$caption', op: '<', value: '2024-12-31' }
48
+ ],
49
+ orderBy: [{ field: 'orderTime', order: 'desc' }]
50
+ }
51
+ }
52
+ },
53
+ {
54
+ name: '商品列表',
55
+ description: '查询所有商品基础信息',
56
+ dsl: {
57
+ model: 'DimProductQueryModel',
58
+ title: '商品列表',
59
+ payload: {
60
+ columns: ['productName', 'productId', 'brand', 'categoryName', 'subCategoryName', 'unitPrice', 'unitCost'],
61
+ slice: [
62
+ { field: 'status', op: '=', value: '正常' }
63
+ ],
64
+ orderBy: [{ field: 'productName', order: 'asc' }]
65
+ }
66
+ }
67
+ },
68
+ {
69
+ name: '客户列表',
70
+ description: '查询VIP会员客户',
71
+ dsl: {
72
+ model: 'DimCustomerQueryModel',
73
+ title: 'VIP客户列表',
74
+ payload: {
75
+ columns: ['customerName', 'customerId', 'customerType', 'memberLevel', 'gender', 'province', 'city'],
76
+ slice: [
77
+ { field: 'memberLevel', op: '=', value: 'VIP' }
78
+ ],
79
+ orderBy: [{ field: 'customerName', order: 'asc' }]
80
+ }
81
+ }
82
+ },
83
+ {
84
+ name: '门店业绩',
85
+ description: '2024年Q4按门店汇总销售业绩',
86
+ dsl: {
87
+ model: 'FactSalesQueryModel',
88
+ title: '门店业绩汇总',
89
+ payload: {
90
+ columns: ['store$caption', 'store$storeType', 'store$province', 'store$city', 'quantity', 'salesAmount', 'profitAmount'],
91
+ slice: [
92
+ { field: 'salesDate$caption', op: '>=', value: '2024-10-01' },
93
+ { field: 'salesDate$caption', op: '<', value: '2024-12-31' }
94
+ ],
95
+ groupBy: [{ field: 'store$id' }],
96
+ orderBy: [{ field: 'salesAmount', order: 'desc' }]
97
+ }
98
+ }
99
+ }
100
+ ]
101
+
102
+ // 获取相对日期(用于示例)
103
+ function getDateOffset(days: number): string {
104
+ const date = new Date()
105
+ date.setDate(date.getDate() + days)
106
+ return date.toISOString().split('T')[0]
107
+ }
108
+
109
+ // 选择示例
110
+ function selectExample(example: typeof examples[0]) {
111
+ dslInput.value = JSON.stringify(example.dsl, null, 2)
112
+ errorMessage.value = ''
113
+ }
114
+
115
+ // 提交查询
116
+ async function submitQuery() {
117
+ errorMessage.value = ''
118
+
119
+ if (!dslInput.value.trim()) {
120
+ errorMessage.value = '请输入查询 DSL'
121
+ return
122
+ }
123
+
124
+ let request: CreateQueryRequest
125
+ try {
126
+ request = JSON.parse(dslInput.value)
127
+ } catch (e) {
128
+ errorMessage.value = 'JSON 格式错误: ' + (e as Error).message
129
+ return
130
+ }
131
+
132
+ isSubmitting.value = true
133
+ try {
134
+ const response = await createQuery(request)
135
+ if (response.success && response.viewerUrl) {
136
+ // 跳转到查询页面
137
+ window.location.href = response.viewerUrl
138
+ } else {
139
+ errorMessage.value = response.error || '创建查询失败'
140
+ }
141
+ } catch (e) {
142
+ errorMessage.value = '请求失败: ' + (e as Error).message
143
+ } finally {
144
+ isSubmitting.value = false
145
+ }
146
+ }
147
+ </script>
148
+
149
+ <template>
150
+ <div id="app">
151
+ <DataViewer v-if="queryId" :query-id="queryId" />
152
+ <div v-else class="landing-page">
153
+ <div class="hero">
154
+ <h1>Foggy Data Viewer</h1>
155
+ <p class="subtitle">请通过有效的查询链接访问数据浏览器,或者输入 Foggy DSL 查询参数来浏览数据</p>
156
+ </div>
157
+
158
+ <div class="main-content">
159
+ <div class="examples-section">
160
+ <h2>示例查询</h2>
161
+ <p class="section-desc">点击示例将 DSL 填入输入框</p>
162
+ <div class="examples-grid">
163
+ <div
164
+ v-for="example in examples"
165
+ :key="example.name"
166
+ class="example-card"
167
+ @click="selectExample(example)"
168
+ >
169
+ <h3>{{ example.name }}</h3>
170
+ <p>{{ example.description }}</p>
171
+ </div>
172
+ </div>
173
+ </div>
174
+
175
+ <div class="input-section">
176
+ <h2>DSL 查询输入</h2>
177
+ <p class="section-desc">输入 JSON 格式的查询参数</p>
178
+ <textarea
179
+ v-model="dslInput"
180
+ class="dsl-textarea"
181
+ placeholder='{
182
+ "model": "FactSalesQueryModel",
183
+ "title": "我的查询",
184
+ "payload": {
185
+ "columns": ["orderId", "salesDate", "salesAmount"],
186
+ "slice": [
187
+ { "field": "salesDate", "op": ">=", "value": "2024-01-01" }
188
+ ]
189
+ }
190
+ }'
191
+ :disabled="isSubmitting"
192
+ ></textarea>
193
+
194
+ <div v-if="errorMessage" class="error-message">
195
+ {{ errorMessage }}
196
+ </div>
197
+
198
+ <button
199
+ class="submit-btn"
200
+ @click="submitQuery"
201
+ :disabled="isSubmitting"
202
+ >
203
+ {{ isSubmitting ? '提交中...' : '提交查询' }}
204
+ </button>
205
+ </div>
206
+ </div>
207
+
208
+ <div class="help-section">
209
+ <h3>DSL 参数说明</h3>
210
+ <table class="help-table">
211
+ <thead>
212
+ <tr>
213
+ <th>参数</th>
214
+ <th>类型</th>
215
+ <th>必填</th>
216
+ <th>说明</th>
217
+ </tr>
218
+ </thead>
219
+ <tbody>
220
+ <tr>
221
+ <td><code>model</code></td>
222
+ <td>string</td>
223
+ <td>是</td>
224
+ <td>查询模型名称,如 FactSalesQueryModel</td>
225
+ </tr>
226
+ <tr>
227
+ <td><code>payload</code></td>
228
+ <td>object</td>
229
+ <td>是</td>
230
+ <td>查询参数对象(与 dataset.query_model 格式一致)</td>
231
+ </tr>
232
+ <tr>
233
+ <td><code>payload.columns</code></td>
234
+ <td>string[]</td>
235
+ <td>是</td>
236
+ <td>要查询的列名列表</td>
237
+ </tr>
238
+ <tr>
239
+ <td><code>payload.slice</code></td>
240
+ <td>object[]</td>
241
+ <td>是</td>
242
+ <td>过滤条件,每项包含 field、op、value</td>
243
+ </tr>
244
+ <tr>
245
+ <td><code>payload.groupBy</code></td>
246
+ <td>object[]</td>
247
+ <td>否</td>
248
+ <td>分组字段,用于聚合查询</td>
249
+ </tr>
250
+ <tr>
251
+ <td><code>payload.orderBy</code></td>
252
+ <td>object[]</td>
253
+ <td>否</td>
254
+ <td>排序字段,包含 field 和 order</td>
255
+ </tr>
256
+ <tr>
257
+ <td><code>title</code></td>
258
+ <td>string</td>
259
+ <td>否</td>
260
+ <td>查询标题</td>
261
+ </tr>
262
+ </tbody>
263
+ </table>
264
+ </div>
265
+ </div>
266
+ </div>
267
+ </template>
268
+
269
+ <style>
270
+ * {
271
+ margin: 0;
272
+ padding: 0;
273
+ box-sizing: border-box;
274
+ }
275
+
276
+ html, body, #app {
277
+ width: 100%;
278
+ height: 100%;
279
+ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif;
280
+ }
281
+
282
+ .landing-page {
283
+ min-height: 100%;
284
+ padding: 40px 20px;
285
+ background: linear-gradient(135deg, #f5f7fa 0%, #c3cfe2 100%);
286
+ }
287
+
288
+ .hero {
289
+ text-align: center;
290
+ margin-bottom: 40px;
291
+ }
292
+
293
+ .hero h1 {
294
+ font-size: 2.5rem;
295
+ color: #303133;
296
+ margin-bottom: 12px;
297
+ }
298
+
299
+ .subtitle {
300
+ font-size: 1.1rem;
301
+ color: #606266;
302
+ max-width: 600px;
303
+ margin: 0 auto;
304
+ }
305
+
306
+ .main-content {
307
+ display: grid;
308
+ grid-template-columns: 1fr 1fr;
309
+ gap: 30px;
310
+ max-width: 1200px;
311
+ margin: 0 auto 40px;
312
+ }
313
+
314
+ @media (max-width: 900px) {
315
+ .main-content {
316
+ grid-template-columns: 1fr;
317
+ }
318
+ }
319
+
320
+ .examples-section,
321
+ .input-section {
322
+ background: white;
323
+ border-radius: 12px;
324
+ padding: 24px;
325
+ box-shadow: 0 4px 12px rgba(0, 0, 0, 0.08);
326
+ }
327
+
328
+ .examples-section h2,
329
+ .input-section h2 {
330
+ font-size: 1.3rem;
331
+ color: #303133;
332
+ margin-bottom: 8px;
333
+ }
334
+
335
+ .section-desc {
336
+ font-size: 0.9rem;
337
+ color: #909399;
338
+ margin-bottom: 16px;
339
+ }
340
+
341
+ .examples-grid {
342
+ display: grid;
343
+ gap: 12px;
344
+ }
345
+
346
+ .example-card {
347
+ padding: 16px;
348
+ border: 1px solid #e4e7ed;
349
+ border-radius: 8px;
350
+ cursor: pointer;
351
+ transition: all 0.2s;
352
+ }
353
+
354
+ .example-card:hover {
355
+ border-color: #409eff;
356
+ background: #f0f7ff;
357
+ transform: translateY(-2px);
358
+ }
359
+
360
+ .example-card h3 {
361
+ font-size: 1rem;
362
+ color: #303133;
363
+ margin-bottom: 4px;
364
+ }
365
+
366
+ .example-card p {
367
+ font-size: 0.85rem;
368
+ color: #909399;
369
+ }
370
+
371
+ .dsl-textarea {
372
+ width: 100%;
373
+ height: 300px;
374
+ padding: 12px;
375
+ border: 1px solid #dcdfe6;
376
+ border-radius: 8px;
377
+ font-family: 'Consolas', 'Monaco', monospace;
378
+ font-size: 13px;
379
+ line-height: 1.5;
380
+ resize: vertical;
381
+ transition: border-color 0.2s;
382
+ }
383
+
384
+ .dsl-textarea:focus {
385
+ outline: none;
386
+ border-color: #409eff;
387
+ }
388
+
389
+ .dsl-textarea:disabled {
390
+ background: #f5f7fa;
391
+ }
392
+
393
+ .error-message {
394
+ margin-top: 12px;
395
+ padding: 10px 12px;
396
+ background: #fef0f0;
397
+ border: 1px solid #fde2e2;
398
+ border-radius: 6px;
399
+ color: #f56c6c;
400
+ font-size: 0.9rem;
401
+ }
402
+
403
+ .submit-btn {
404
+ margin-top: 16px;
405
+ width: 100%;
406
+ padding: 12px 24px;
407
+ background: #409eff;
408
+ color: white;
409
+ border: none;
410
+ border-radius: 8px;
411
+ font-size: 1rem;
412
+ cursor: pointer;
413
+ transition: background 0.2s;
414
+ }
415
+
416
+ .submit-btn:hover:not(:disabled) {
417
+ background: #337ecc;
418
+ }
419
+
420
+ .submit-btn:disabled {
421
+ background: #a0cfff;
422
+ cursor: not-allowed;
423
+ }
424
+
425
+ .help-section {
426
+ max-width: 1200px;
427
+ margin: 0 auto;
428
+ background: white;
429
+ border-radius: 12px;
430
+ padding: 24px;
431
+ box-shadow: 0 4px 12px rgba(0, 0, 0, 0.08);
432
+ }
433
+
434
+ .help-section h3 {
435
+ font-size: 1.1rem;
436
+ color: #303133;
437
+ margin-bottom: 16px;
438
+ }
439
+
440
+ .help-table {
441
+ width: 100%;
442
+ border-collapse: collapse;
443
+ }
444
+
445
+ .help-table th,
446
+ .help-table td {
447
+ padding: 10px 12px;
448
+ text-align: left;
449
+ border-bottom: 1px solid #ebeef5;
450
+ }
451
+
452
+ .help-table th {
453
+ background: #f5f7fa;
454
+ font-weight: 600;
455
+ color: #606266;
456
+ }
457
+
458
+ .help-table td {
459
+ color: #606266;
460
+ }
461
+
462
+ .help-table code {
463
+ background: #f5f7fa;
464
+ padding: 2px 6px;
465
+ border-radius: 4px;
466
+ font-family: 'Consolas', 'Monaco', monospace;
467
+ color: #409eff;
468
+ }
469
+ </style>
@@ -0,0 +1,163 @@
1
+ import axios from 'axios'
2
+ import type { QueryMetaResponse, ViewerQueryRequest, ViewerDataResponse, FilterOptionsResponse, ColumnSchema } from '@/types'
3
+
4
+ const apiClient = axios.create({
5
+ baseURL: '/data-viewer/api',
6
+ timeout: 30000,
7
+ headers: {
8
+ 'Content-Type': 'application/json'
9
+ }
10
+ })
11
+
12
+ /**
13
+ * 查询 payload(与 dataset.query_model 格式一致)
14
+ */
15
+ export interface QueryPayload {
16
+ columns: string[]
17
+ slice: Array<{ field: string; op: string; value?: unknown }>
18
+ groupBy?: Array<{ field: string; agg?: string }>
19
+ orderBy?: Array<{ field: string; order: 'asc' | 'desc' }>
20
+ calculatedFields?: Array<{ name: string; expression: string; agg?: string }>
21
+ }
22
+
23
+ /**
24
+ * 创建查询请求类型
25
+ */
26
+ export interface CreateQueryRequest {
27
+ model: string
28
+ payload: QueryPayload
29
+ title?: string
30
+ }
31
+
32
+ /**
33
+ * 创建查询响应类型
34
+ */
35
+ export interface CreateQueryResponse {
36
+ success: boolean
37
+ queryId: string | null
38
+ viewerUrl: string | null
39
+ error: string | null
40
+ }
41
+
42
+ /**
43
+ * 创建查询(从 DSL 输入)
44
+ */
45
+ export async function createQuery(request: CreateQueryRequest): Promise<CreateQueryResponse> {
46
+ const response = await apiClient.post<any>('/query/create', request)
47
+
48
+ // Handle RX response format: { code: 200, msg: "", data: {} }
49
+ if (!response.data || response.data.code !== 200) {
50
+ // Return the error response if available in data
51
+ if (response.data?.data) {
52
+ return response.data.data
53
+ }
54
+ throw new Error(response.data?.msg || '创建查询失败')
55
+ }
56
+
57
+ return response.data.data
58
+ }
59
+
60
+ /**
61
+ * 获取查询元数据
62
+ */
63
+ export async function fetchQueryMeta(queryId: string): Promise<QueryMetaResponse> {
64
+ const response = await apiClient.get<any>(`/query/${queryId}/meta`)
65
+
66
+ // Handle RX response format: { code: 200, msg: "", data: {} }
67
+ if (!response.data || response.data.code !== 200) {
68
+ throw new Error(response.data?.msg || '获取查询元数据失败')
69
+ }
70
+
71
+ return response.data.data
72
+ }
73
+
74
+ /**
75
+ * 查询数据
76
+ */
77
+ export async function fetchQueryData(
78
+ queryId: string,
79
+ request: ViewerQueryRequest
80
+ ): Promise<ViewerDataResponse> {
81
+ const response = await apiClient.post<any>(`/query/${queryId}/data`, request)
82
+
83
+ // Handle RX response format: { code: 200, msg: "", data: {} }
84
+ if (!response.data || response.data.code !== 200) {
85
+ // If it's an expired query (status 410), return the expired response
86
+ if (response.data?.data?.expired) {
87
+ return response.data.data
88
+ }
89
+ throw new Error(response.data?.msg || '查询数据失败')
90
+ }
91
+
92
+ return response.data.data
93
+ }
94
+
95
+ /**
96
+ * 获取过滤选项(维度成员或字典项)
97
+ */
98
+ export async function fetchFilterOptions(
99
+ queryId: string,
100
+ columnName: string
101
+ ): Promise<FilterOptionsResponse> {
102
+ const response = await apiClient.get<FilterOptionsResponse>(
103
+ `/query/${queryId}/filter-options/${encodeURIComponent(columnName)}`
104
+ )
105
+ return response.data
106
+ }
107
+
108
+ /**
109
+ * 获取 QM Schema(查询模型的字段元数据)
110
+ */
111
+ export async function fetchQmSchema(qmModel: string): Promise<ColumnSchema[]> {
112
+ const response = await apiClient.get<any>(`/schema/${encodeURIComponent(qmModel)}`)
113
+
114
+ // Handle RX response format: { code: 200, msg: "", data: {} }
115
+ if (!response.data || response.data.code !== 200) {
116
+ throw new Error(response.data?.msg || '获取 QM Schema 失败')
117
+ }
118
+
119
+ const data = response.data.data
120
+
121
+ // 解析 SemanticMetadataResponse V3 格式
122
+ // 返回格式:{ "version": "v3", "fields": { "fieldName": { "name": "显示名", "type": "TEXT", ... } }, "models": {...} }
123
+ if (!data || !data.fields) {
124
+ return []
125
+ }
126
+
127
+ // 遍历 fields 对象,转换为 ColumnSchema 数组
128
+ const columns: ColumnSchema[] = []
129
+ for (const [fieldName, fieldInfo] of Object.entries(data.fields)) {
130
+ const field = fieldInfo as any
131
+
132
+ // 直接使用后端返回的字段(不再解析 meta)
133
+ columns.push({
134
+ name: fieldName,
135
+ title: field.name || fieldName,
136
+ type: field.type || 'TEXT',
137
+ filterable: field.filterable !== false,
138
+ aggregatable: field.aggregatable || false,
139
+ measure: field.measure || false,
140
+ filterType: field.filterType,
141
+ dictId: field.dictId,
142
+ format: field.format
143
+ })
144
+ }
145
+
146
+ return columns
147
+ }
148
+
149
+ /**
150
+ * 错误处理
151
+ */
152
+ apiClient.interceptors.response.use(
153
+ response => response,
154
+ error => {
155
+ if (error.response?.status === 410) {
156
+ return Promise.reject(new Error('查询链接已过期,请重新获取'))
157
+ }
158
+ if (error.response?.status === 404) {
159
+ return Promise.reject(new Error('查询不存在'))
160
+ }
161
+ return Promise.reject(error)
162
+ }
163
+ )