chengjie месяцев назад: 5
Родитель
Сommit
e6d4978cfb
6 измененных файлов с 1981 добавлено и 1 удалено
  1. 859 0
      public/mg/add_info.html
  2. 1070 0
      public/mg/kylx365_db_admin.html
  3. 12 0
      src/api/web/routes.js
  4. 35 0
      src/api/web/webController.js
  5. 3 0
      src/app.js
  6. 2 1
      src/test/build.test.js

+ 859 - 0
public/mg/add_info.html

@@ -0,0 +1,859 @@
1
+<!DOCTYPE html>
2
+<html>
3
+
4
+<head>
5
+    <meta charset="UTF-8">
6
+    <meta http-equiv="Content-Type" content="text/html;charset=utf-8">
7
+    <title>秒过加资料信息维护</title>
8
+    <script src="https://kylx365-1253256735.file.myqcloud.com/js/jquery-1.10.2.min.js"></script>
9
+    <script src="https://kylx365-1253256735.file.myqcloud.com/js/vue.min.js"></script>
10
+    <style>
11
+        .main00 {
12
+            width: 100%;
13
+            height: 100vh;
14
+            min-height: 600px;
15
+            background: white;
16
+            display: flex;
17
+            flex-direction: column;
18
+            overflow: hidden;
19
+        }
20
+
21
+        .ListTop {
22
+            width: 100%;
23
+            height: 60px;
24
+            background: white;
25
+            border-bottom: 1px solid #EEEEEE;
26
+            justify-content: flex-start;
27
+            flex-shrink: 0;
28
+        }
29
+
30
+        .ListTop3 {
31
+            margin-left: 40px;
32
+            height: 50px;
33
+            align-items: center;
34
+        }
35
+
36
+        .title {
37
+            font-size: 24px;
38
+            color: #333333;
39
+            font-weight: bold;
40
+        }
41
+
42
+        .main0 {
43
+            width: 100%;
44
+            background: white;
45
+            flex: 1;
46
+            min-height: 0;
47
+            overflow: hidden;
48
+            display: flex;
49
+            position: relative;
50
+            align-items: flex-start;
51
+        }
52
+
53
+        .search-panel {
54
+            width: 400px;
55
+            border-right: 1px solid #EEEEEE;
56
+            background: #F9F9F9;
57
+            display: flex;
58
+            flex-direction: column;
59
+            height: 100%;
60
+            position: relative;
61
+            flex-shrink: 0;
62
+        }
63
+
64
+        .search-box {
65
+            padding: 15px;
66
+            border-bottom: 1px solid #EEEEEE;
67
+            background: white;
68
+            width: 100%;
69
+            justify-content: center;
70
+        }
71
+
72
+        .search-input {
73
+            width: 200px;
74
+            height: 32px;
75
+            padding: 0 32px 0 12px;
76
+            border: 1px solid #DDDDDD;
77
+            border-radius: 4px;
78
+            font-size: 14px;
79
+            color: #333333;
80
+        }
81
+
82
+        .search-input:focus {
83
+            border-color: #4A90E2;
84
+            outline: none;
85
+        }
86
+
87
+        .btn33 {
88
+            width: 32px;
89
+            height: 32px;
90
+            margin-left: 8px;
91
+            border-radius: 4px;
92
+            background: #F5F5F5;
93
+            cursor: pointer;
94
+            justify-content: center;
95
+            align-items: center;
96
+        }
97
+
98
+        .btn33:hover {
99
+            background: #EEEEEE;
100
+        }
101
+
102
+        .json-editor {
103
+            width: calc(100% - 400px);
104
+            padding: 20px;
105
+            overflow-y: auto;
106
+            height: calc(100% - 60px);
107
+        }
108
+
109
+        .json-node {
110
+            margin: 5px 0;
111
+            padding: 5px;
112
+            font-family: monospace;
113
+        }
114
+
115
+        .json-key {
116
+            color: #881391;
117
+            margin-right: 5px;
118
+        }
119
+
120
+        .json-string {
121
+            color: #268BD2;
122
+        }
123
+
124
+        .json-number {
125
+            color: #859900;
126
+        }
127
+
128
+        .json-boolean {
129
+            color: #CB4B16;
130
+        }
131
+
132
+        .json-null {
133
+            color: #93A1A1;
134
+        }
135
+
136
+        .editable {
137
+            cursor: pointer;
138
+        }
139
+
140
+        .editable:hover {
141
+            background-color: #f0f0f0;
142
+        }
143
+
144
+        .json-editor-input {
145
+            width: 100%;
146
+            padding: 4px;
147
+            font-family: monospace;
148
+            border: 1px solid #4A90E2;
149
+            border-radius: 4px;
150
+        }
151
+
152
+        .btn-group {
153
+            position: fixed;
154
+            bottom: 20px;
155
+            right: 20px;
156
+            text-align: right;
157
+            padding: 20px;
158
+            background: white;
159
+            box-shadow: 0 -2px 10px rgba(0, 0, 0, 0.1);
160
+            border-radius: 4px;
161
+        }
162
+
163
+        .btn {
164
+            min-width: 30px;
165
+            height: 36px;
166
+            padding: 0 16px;
167
+            margin-left: 12px;
168
+            border: none;
169
+            border-radius: 4px;
170
+            cursor: pointer;
171
+            font-size: 14px;
172
+            transition: all 0.3s;
173
+        }
174
+
175
+        .btn-primary {
176
+            background: #4A90E2;
177
+            color: white;
178
+        }
179
+
180
+        .btn-primary:hover {
181
+            background: #357ABD;
182
+        }
183
+
184
+        .btn-default {
185
+            background: white;
186
+            border: 1px solid #DDDDDD;
187
+            color: #666666;
188
+        }
189
+
190
+        .btn-default:hover {
191
+            background: #F5F5F5;
192
+            border-color: #CCCCCC;
193
+        }
194
+
195
+        .toast {
196
+            position: fixed;
197
+            top: 20px;
198
+            left: 50%;
199
+            transform: translateX(-50%);
200
+            padding: 12px 24px;
201
+            background: rgba(0, 0, 0, 0.7);
202
+            color: white;
203
+            border-radius: 4px;
204
+            z-index: 9999;
205
+            opacity: 0;
206
+            transition: opacity 0.3s;
207
+        }
208
+
209
+        .toast.show {
210
+            opacity: 1;
211
+        }
212
+
213
+        .toast.success {
214
+            background: #4CAF50;
215
+        }
216
+
217
+        .toast.error {
218
+            background: #F44336;
219
+        }
220
+
221
+        .toast.info {
222
+            background: #2196F3;
223
+        }
224
+
225
+        /* Loading样式 */
226
+        .loading-overlay {
227
+            position: absolute;
228
+            top: 0;
229
+            left: 0;
230
+            width: 100%;
231
+            height: 100%;
232
+            background-color: rgba(255, 255, 255, 0.7);
233
+            display: flex;
234
+            justify-content: center;
235
+            align-items: center;
236
+            z-index: 100;
237
+        }
238
+
239
+        .loading-spinner {
240
+            width: 50px;
241
+            height: 50px;
242
+            border: 5px solid #f3f3f3;
243
+            border-top: 5px solid #4A90E2;
244
+            border-radius: 50%;
245
+            animation: spin 1s linear infinite;
246
+        }
247
+
248
+        @keyframes spin {
249
+            0% { transform: rotate(0deg); }
250
+            100% { transform: rotate(360deg); }
251
+        }
252
+
253
+        .clear-btn {
254
+            position: absolute;
255
+            right: 12px;
256
+            top: 50%;
257
+            transform: translateY(-50%);
258
+            width: 16px;
259
+            height: 16px;
260
+            background-color: #999;
261
+            border-radius: 50%;
262
+            display: flex;
263
+            align-items: center;
264
+            justify-content: center;
265
+            cursor: pointer;
266
+            transition: background-color 0.2s;
267
+        }
268
+
269
+        .clear-btn:hover {
270
+            background-color: #666;
271
+        }
272
+
273
+        .clear-x {
274
+            position: relative;
275
+            width: 8px;
276
+            height: 8px;
277
+        }
278
+
279
+        .clear-x:before,
280
+        .clear-x:after {
281
+            content: '';
282
+            position: absolute;
283
+            width: 8px;
284
+            height: 2px;
285
+            background-color: white;
286
+            top: 3px;
287
+            left: 0;
288
+        }
289
+
290
+        .clear-x:before {
291
+            transform: rotate(45deg);
292
+        }
293
+
294
+        .clear-x:after {
295
+            transform: rotate(-45deg);
296
+        }
297
+
298
+        .FlexRow {
299
+            display: flex;
300
+            flex-direction: row;
301
+        }
302
+
303
+        .FlexColumn {
304
+            display: flex;
305
+            flex-direction: column;
306
+        }
307
+    </style>
308
+</head>
309
+
310
+<body class="container FlexRow">
311
+    <div id="app" class="main00 FlexColumn">
312
+        <div class="ListTop FlexRow">
313
+            <div class="ListTop3 FlexRow">
314
+                <div class="title">秒过加资料信息维护</div>
315
+            </div>
316
+        </div>
317
+        <div class="main0 FlexRow">
318
+            <!-- 左侧搜索面板 -->
319
+            <div class="search-panel" style="position: relative;">
320
+                <div v-if="isSearchLoading" class="loading-overlay">
321
+                    <div class="loading-spinner"></div>
322
+                </div>
323
+                <div class="search-box FlexRow">
324
+                    <div style="position: relative;">
325
+                        <input type="text" class="search-input" v-model="searchText" @keyup.enter="searchKeyword"
326
+                            placeholder="输入关键词搜索...">
327
+                        <div class="clear-btn" v-show="searchText" @click="clearSearch">
328
+                            <span class="clear-x"></span>
329
+                        </div>
330
+                    </div>
331
+                    <div class="btn33 FlexRow" @click="searchKeyword">
332
+                        <img title="搜索" alt="搜索"
333
+                            src="https://kylx365-1253256735.file.myqcloud.com/web/universalpic_search_gray_30x30.png"
334
+                            style="width: 20px; height: 20px;" />
335
+                    </div>
336
+                </div>
337
+                <!-- 搜索结果列表 -->
338
+                <div class="search-results" style="flex: 1; overflow-y: auto; padding: 10px;">
339
+                    <div v-if="searchResults && searchResults.length > 0" 
340
+                         v-for="(item, index) in searchResults" 
341
+                         :key="index"
342
+                         @click="selectItem(item)"
343
+                         style="padding: 10px; margin: 5px 0; background: white; border-radius: 4px; cursor: pointer; transition: background-color 0.2s;"
344
+                         :style="{ backgroundColor: selectedItem === item ? '#e6f3ff' : 'white' }"
345
+                         class="search-result-item">
346
+                        <div v-if="typeof item === 'object'">
347
+                            <div style="font-weight: bold;">{{ item.Word || item }}</div>
348
+                            <div v-if="item.Author" style="font-size: 12px; color: #666; margin-top: 4px;">
349
+                                作者: {{ item.Author }}
350
+                            </div>
351
+                            <div v-if="item.SearchType" style="font-size: 12px; color: #666;">
352
+                                类型: {{ item.SearchType }}
353
+                            </div>
354
+                            <div v-if="item.ShiciUrl" style="font-size: 12px; color: #0066cc; text-decoration: underline;">
355
+                                {{ item.ShiciUrl }}
356
+                            </div>
357
+                        </div>
358
+                        <div v-else>{{ item }}</div>
359
+                    </div>
360
+                    <div v-else-if="searchResults && searchResults.length === 0" 
361
+                         style="text-align: center; color: #999; padding: 20px;">
362
+                        未找到相关结果
363
+                    </div>
364
+                </div>
365
+            </div>
366
+
367
+            <!-- 右侧JSON编辑器 -->
368
+            <div class="json-editor" v-if="jsonData" style="position: relative;">
369
+                <div v-if="isDetailLoading" class="loading-overlay">
370
+                    <div class="loading-spinner"></div>
371
+                </div>
372
+                <div class="json-node" v-for="(value, key) in jsonData" :key="key">
373
+                    <span class="json-key">"{{ key }}":</span>
374
+                    <div v-if="typeof value === 'object' && value !== null" style="margin-left: 20px;">
375
+                        <div v-for="(subValue, subKey) in value" :key="subKey">
376
+                            <span class="json-key">"{{ subKey }}":</span>
377
+                            <!-- 处理第二层对象 -->
378
+                            <div v-if="typeof subValue === 'object' && subValue !== null && !Array.isArray(subValue)" style="margin-left: 20px;">
379
+                                <div v-for="(subSubValue, subSubKey) in subValue" :key="subSubKey">
380
+                                    <span class="json-key">"{{ subSubKey }}":</span>
381
+                                    <span v-if="typeof subSubValue === 'string'" class="json-string editable"
382
+                                        @dblclick="editValue(subValue, subSubKey, key + '.' + subKey + '.' + subSubKey)">
383
+                                        <template v-if="editingKey === key + '.' + subKey + '.' + subSubKey">
384
+                                            <input type="text" class="json-editor-input" v-model="subValue[subSubKey]"
385
+                                                @blur="finishEdit" @keyup.enter="finishEdit" v-focus>
386
+                                        </template>
387
+                                        <template v-else>
388
+                                            "{{ subSubValue }}"
389
+                                        </template>
390
+                                    </span>
391
+                                    <span v-else-if="typeof subSubValue === 'number'" class="json-number editable"
392
+                                        @dblclick="editValue(subValue, subSubKey, key + '.' + subKey + '.' + subSubKey)">
393
+                                        <template v-if="editingKey === key + '.' + subKey + '.' + subSubKey">
394
+                                            <input type="number" class="json-editor-input" v-model.number="subValue[subSubKey]"
395
+                                                @blur="finishEdit" @keyup.enter="finishEdit" v-focus>
396
+                                        </template>
397
+                                        <template v-else>
398
+                                            {{ subSubValue }}
399
+                                        </template>
400
+                                    </span>
401
+                                    <span v-else-if="typeof subSubValue === 'boolean'" class="json-boolean editable"
402
+                                        @dblclick="editValue(subValue, subSubKey, key + '.' + subKey + '.' + subSubKey)">
403
+                                        <template v-if="editingKey === key + '.' + subKey + '.' + subSubKey">
404
+                                            <select class="json-editor-input" v-model="subValue[subSubKey]" @blur="finishEdit"
405
+                                                @change="finishEdit" v-focus>
406
+                                                <option :value="true">true</option>
407
+                                                <option :value="false">false</option>
408
+                                            </select>
409
+                                        </template>
410
+                                        <template v-else>
411
+                                            {{ subSubValue }}
412
+                                        </template>
413
+                                    </span>
414
+                                    <span v-else-if="subSubValue === null" class="json-null">null</span>
415
+                                    <span v-else-if="typeof subSubValue === 'object'" class="json-string">
416
+                                        [复杂对象]
417
+                                    </span>
418
+                                </div>
419
+                            </div>
420
+                            <!-- 处理数组 -->
421
+                            <div v-else-if="Array.isArray(subValue)" style="margin-left: 20px;">
422
+                                <div v-for="(item, index) in subValue" :key="index">
423
+                                    <span class="json-key">[{{ index }}]:</span>
424
+                                    <span v-if="typeof item === 'string'" class="json-string editable"
425
+                                        @dblclick="editValue(subValue, index, key + '.' + subKey + '.' + index)">
426
+                                        <template v-if="editingKey === key + '.' + subKey + '.' + index">
427
+                                            <input type="text" class="json-editor-input" v-model="subValue[index]"
428
+                                                @blur="finishEdit" @keyup.enter="finishEdit" v-focus>
429
+                                        </template>
430
+                                        <template v-else>
431
+                                            <template v-if="['BiShunArr', 'BiShunArr2', 'KaitiArr'].includes(subKey)">
432
+                                                <img :src="item" 
433
+                                                     style="max-width: 100px; max-height: 100px; margin: 5px 0;" 
434
+                                                     @error="handleImageError($event)"
435
+                                                     :alt="item" />
436
+                                                <div class="json-string" style="font-size: 12px; color: #666;">"{{ item }}"</div>
437
+                                            </template>
438
+                                            <template v-else>
439
+                                                "{{ item }}"
440
+                                            </template>
441
+                                        </template>
442
+                                    </span>
443
+                                    <span v-else-if="typeof item === 'number'" class="json-number editable"
444
+                                        @dblclick="editValue(subValue, index, key + '.' + subKey + '.' + index)">
445
+                                        <template v-if="editingKey === key + '.' + subKey + '.' + index">
446
+                                            <input type="number" class="json-editor-input" v-model.number="subValue[index]"
447
+                                                @blur="finishEdit" @keyup.enter="finishEdit" v-focus>
448
+                                        </template>
449
+                                        <template v-else>
450
+                                            {{ item }}
451
+                                        </template>
452
+                                    </span>
453
+                                    <span v-else-if="typeof item === 'boolean'" class="json-boolean editable"
454
+                                        @dblclick="editValue(subValue, index, key + '.' + subKey + '.' + index)">
455
+                                        <template v-if="editingKey === key + '.' + subKey + '.' + index">
456
+                                            <select class="json-editor-input" v-model="subValue[index]" @blur="finishEdit"
457
+                                                @change="finishEdit" v-focus>
458
+                                                <option :value="true">true</option>
459
+                                                <option :value="false">false</option>
460
+                                            </select>
461
+                                        </template>
462
+                                        <template v-else>
463
+                                            {{ item }}
464
+                                        </template>
465
+                                    </span>
466
+                                    <span v-else-if="item === null" class="json-null">null</span>
467
+                                    <div v-else-if="typeof item === 'object'" style="margin-left: 20px;">
468
+                                        <div v-for="(itemValue, itemKey) in item" :key="itemKey">
469
+                                            <span class="json-key">"{{ itemKey }}":</span>
470
+                                            <span v-if="typeof itemValue === 'string'" class="json-string editable"
471
+                                                @dblclick="editValue(item, itemKey, key + '.' + subKey + '.' + index + '.' + itemKey)">
472
+                                                <template v-if="editingKey === key + '.' + subKey + '.' + index + '.' + itemKey">
473
+                                                    <input type="text" class="json-editor-input" v-model="item[itemKey]"
474
+                                                        @blur="finishEdit" @keyup.enter="finishEdit" v-focus>
475
+                                                </template>
476
+                                                <template v-else>
477
+                                                    "{{ itemValue }}"
478
+                                                </template>
479
+                                            </span>
480
+                                            <span v-else-if="typeof itemValue === 'number'" class="json-number editable"
481
+                                                @dblclick="editValue(item, itemKey, key + '.' + subKey + '.' + index + '.' + itemKey)">
482
+                                                <template v-if="editingKey === key + '.' + subKey + '.' + index + '.' + itemKey">
483
+                                                    <input type="number" class="json-editor-input" v-model.number="item[itemKey]"
484
+                                                        @blur="finishEdit" @keyup.enter="finishEdit" v-focus>
485
+                                                </template>
486
+                                                <template v-else>
487
+                                                    {{ itemValue }}
488
+                                                </template>
489
+                                            </span>
490
+                                            <div v-else-if="Array.isArray(itemValue)" style="margin-left: 20px;">
491
+                                                <div v-for="(arrayItem, arrayIndex) in itemValue" :key="arrayIndex">
492
+                                                    <span class="json-key">[{{ arrayIndex }}]:</span>
493
+                                                    <span v-if="typeof arrayItem === 'string'" class="json-string editable"
494
+                                                        @dblclick="editValue(itemValue, arrayIndex, key + '.' + subKey + '.' + index + '.' + itemKey + '.' + arrayIndex)">
495
+                                                        <template v-if="editingKey === key + '.' + subKey + '.' + index + '.' + itemKey + '.' + arrayIndex">
496
+                                                            <input type="text" class="json-editor-input" v-model="itemValue[arrayIndex]"
497
+                                                                @blur="finishEdit" @keyup.enter="finishEdit" v-focus>
498
+                                                        </template>
499
+                                                        <template v-else>
500
+                                                            "{{ arrayItem }}"
501
+                                                        </template>
502
+                                                    </span>
503
+                                                    <span v-else-if="typeof arrayItem === 'number'" class="json-number editable"
504
+                                                        @dblclick="editValue(itemValue, arrayIndex, key + '.' + subKey + '.' + index + '.' + itemKey + '.' + arrayIndex)">
505
+                                                        <template v-if="editingKey === key + '.' + subKey + '.' + index + '.' + itemKey + '.' + arrayIndex">
506
+                                                            <input type="number" class="json-editor-input" v-model.number="itemValue[arrayIndex]"
507
+                                                                @blur="finishEdit" @keyup.enter="finishEdit" v-focus>
508
+                                                        </template>
509
+                                                        <template v-else>
510
+                                                            {{ arrayItem }}
511
+                                                        </template>
512
+                                                    </span>
513
+                                                    <span v-else-if="typeof arrayItem === 'boolean'" class="json-boolean editable"
514
+                                                        @dblclick="editValue(itemValue, arrayIndex, key + '.' + subKey + '.' + index + '.' + itemKey + '.' + arrayIndex)">
515
+                                                        <template v-if="editingKey === key + '.' + subKey + '.' + index + '.' + itemKey + '.' + arrayIndex">
516
+                                                            <select class="json-editor-input" v-model="itemValue[arrayIndex]" @blur="finishEdit"
517
+                                                                @change="finishEdit" v-focus>
518
+                                                                <option :value="true">true</option>
519
+                                                                <option :value="false">false</option>
520
+                                                            </select>
521
+                                                        </template>
522
+                                                        <template v-else>
523
+                                                            {{ arrayItem }}
524
+                                                        </template>
525
+                                                    </span>
526
+                                                    <span v-else-if="arrayItem === null" class="json-null">null</span>
527
+                                                    <span v-else-if="typeof arrayItem === 'object'" class="json-string">[复杂对象]</span>
528
+                                                </div>
529
+                                            </div>
530
+                                        </div>
531
+                                    </div>
532
+                                </div>
533
+                            </div>
534
+                            <!-- 处理基本类型 -->
535
+                            <span v-else-if="typeof subValue === 'string'" class="json-string editable"
536
+                                @dblclick="editValue(value, subKey, key + '.' + subKey)">
537
+                                <template v-if="editingKey === key + '.' + subKey">
538
+                                    <input type="text" class="json-editor-input" v-model="value[subKey]"
539
+                                        @blur="finishEdit" @keyup.enter="finishEdit" v-focus>
540
+                                </template>
541
+                                <template v-else>
542
+                                    "{{ subValue }}"
543
+                                </template>
544
+                            </span>
545
+                            <span v-else-if="typeof subValue === 'number'" class="json-number editable"
546
+                                @dblclick="editValue(value, subKey, key + '.' + subKey)">
547
+                                <template v-if="editingKey === key + '.' + subKey">
548
+                                    <input type="number" class="json-editor-input" v-model.number="value[subKey]"
549
+                                        @blur="finishEdit" @keyup.enter="finishEdit" v-focus>
550
+                                </template>
551
+                                <template v-else>
552
+                                    {{ subValue }}
553
+                                </template>
554
+                            </span>
555
+                            <span v-else-if="typeof subValue === 'boolean'" class="json-boolean editable"
556
+                                @dblclick="editValue(value, subKey, key + '.' + subKey)">
557
+                                <template v-if="editingKey === key + '.' + subKey">
558
+                                    <select class="json-editor-input" v-model="value[subKey]" @blur="finishEdit"
559
+                                        @change="finishEdit" v-focus>
560
+                                        <option :value="true">true</option>
561
+                                        <option :value="false">false</option>
562
+                                    </select>
563
+                                </template>
564
+                                <template v-else>
565
+                                    {{ subValue }}
566
+                                </template>
567
+                            </span>
568
+                            <span v-else-if="subValue === null" class="json-null">null</span>
569
+                        </div>
570
+                    </div>
571
+                    <span v-else-if="typeof value === 'string'" class="json-string editable"
572
+                        @dblclick="editValue(jsonData, key, key)">
573
+                        <template v-if="editingKey === key">
574
+                            <input type="text" class="json-editor-input" v-model="jsonData[key]" @blur="finishEdit"
575
+                                @keyup.enter="finishEdit" v-focus>
576
+                        </template>
577
+                        <template v-else>
578
+                            "{{ value }}"
579
+                        </template>
580
+                    </span>
581
+                    <span v-else-if="typeof value === 'number'" class="json-number editable"
582
+                        @dblclick="editValue(jsonData, key, key)">
583
+                        <template v-if="editingKey === key">
584
+                            <input type="number" class="json-editor-input" v-model.number="jsonData[key]"
585
+                                @blur="finishEdit" @keyup.enter="finishEdit" v-focus>
586
+                        </template>
587
+                        <template v-else>
588
+                            {{ value }}
589
+                        </template>
590
+                    </span>
591
+                    <span v-else-if="typeof value === 'boolean'" class="json-boolean editable"
592
+                        @dblclick="editValue(jsonData, key, key)">
593
+                        <template v-if="editingKey === key">
594
+                            <select class="json-editor-input" v-model="jsonData[key]" @blur="finishEdit"
595
+                                @change="finishEdit" v-focus>
596
+                                <option :value="true">true</option>
597
+                                <option :value="false">false</option>
598
+                            </select>
599
+                        </template>
600
+                        <template v-else>
601
+                            {{ value }}
602
+                        </template>
603
+                    </span>
604
+                    <span v-else-if="value === null" class="json-null">null</span>
605
+                </div>
606
+            </div>
607
+            <div v-else class="json-editor FlexColumn" style="justify-content: center; align-items: center;">
608
+                <div style="font-size:36px;color:#999;">请输入关键词搜索</div>
609
+            </div>
610
+        </div>
611
+
612
+        <!-- 保存按钮 -->
613
+        <div class="btn-group" v-if="jsonData">
614
+            <button type="button" class="btn btn-default" @click="resetData">重置</button>
615
+            <button type="button" class="btn btn-primary" @click="saveData">保存</button>
616
+        </div>
617
+
618
+        <!-- Toast提示 -->
619
+        <div class="toast" :class="{ show: showToast, [toastType]: showToast }">{{ toastMessage }}</div>
620
+    </div>
621
+
622
+    <script>
623
+        // 自定义指令:自动聚焦
624
+        Vue.directive('focus', {
625
+            inserted: function (el) {
626
+                el.focus()
627
+            }
628
+        })
629
+
630
+        new Vue({
631
+            el: '#app',
632
+            data: {
633
+                searchText: '',
634
+                jsonData: null,
635
+                originalData: null,
636
+                editingKey: null,
637
+                showToast: false,
638
+                toastMessage: '',
639
+                toastType: 'info',
640
+                searchResults: null,
641
+                selectedItem: null,
642
+                isSearchLoading: false,
643
+                isDetailLoading: false
644
+            },
645
+            methods: {
646
+                // 清空搜索
647
+                clearSearch() {
648
+                    this.searchText = '';
649
+                    this.jsonData = null;
650
+                    this.originalData = null;
651
+                    this.searchResults = null;
652
+                    this.selectedItem = null;
653
+                },
654
+
655
+                // 搜索关键词
656
+                searchKeyword() {
657
+                    if (!this.searchText.trim()) {
658
+                        this.showToastMessage('请输入搜索关键词', 'info');
659
+                        return;
660
+                    }
661
+
662
+                    this.isSearchLoading = true;
663
+                    fetch(`/api/GetMiaoguoLiteracyListByWord?Word=${encodeURIComponent(this.searchText)}`)
664
+                        .then(response => response.json())
665
+                        .then(data => {
666
+                            data = data.result;
667
+                            if (data && Array.isArray(data)) {
668
+                                // 对搜索结果进行三级排序
669
+                                data.sort((a, b) => {
670
+                                    // 检查是否是对象
671
+                                    const aIsObj = typeof a === 'object' && a !== null;
672
+                                    const bIsObj = typeof b === 'object' && b !== null;
673
+                                    
674
+                                    // 获取Word值
675
+                                    const aWord = aIsObj ? a.Word : a;
676
+                                    const bWord = bIsObj ? b.Word : b;
677
+                                    
678
+                                    // 检查是否是精确匹配(Word等于关键词且其他字段为空)
679
+                                    const aIsExactMatch = aIsObj && 
680
+                                        aWord === this.searchText && 
681
+                                        (!a.Author || a.Author === '') && 
682
+                                        (!a.SearchType || a.SearchType === '') && 
683
+                                        (!a.ShiciUrl || a.ShiciUrl === '');
684
+                                    
685
+                                    const bIsExactMatch = bIsObj && 
686
+                                        bWord === this.searchText && 
687
+                                        (!b.Author || b.Author === '') && 
688
+                                        (!b.SearchType || b.SearchType === '') && 
689
+                                        (!b.ShiciUrl || b.ShiciUrl === '');
690
+                                    
691
+                                    // 检查是否是Word匹配但有其他数据
692
+                                    const aIsPartialMatch = aIsObj && 
693
+                                        aWord === this.searchText && 
694
+                                        !aIsExactMatch;
695
+                                    
696
+                                    const bIsPartialMatch = bIsObj && 
697
+                                        bWord === this.searchText && 
698
+                                        !bIsExactMatch;
699
+                                    
700
+                                    // 三级排序逻辑
701
+                                    if (aIsExactMatch !== bIsExactMatch) {
702
+                                        // 第一优先级:精确匹配(Word等于关键词且其他字段为空)
703
+                                        return aIsExactMatch ? -1 : 1;
704
+                                    } else if (aIsPartialMatch !== bIsPartialMatch) {
705
+                                        // 第二优先级:Word等于关键词但其他字段有数据
706
+                                        return aIsPartialMatch ? -1 : 1;
707
+                                    } else if (aWord === this.searchText && bWord !== this.searchText) {
708
+                                        // 第三优先级:Word等于关键词 vs 不等于
709
+                                        return -1;
710
+                                    } else if (aWord !== this.searchText && bWord === this.searchText) {
711
+                                        return 1;
712
+                                    }
713
+                                    // 其他情况保持原有顺序
714
+                                    return 0;
715
+                                });
716
+                                
717
+                                this.searchResults = data;
718
+                                this.selectedItem = null;
719
+                                this.jsonData = null; // 清空右侧详情
720
+                                this.originalData = null;
721
+                            } else {
722
+                                this.searchResults = [];
723
+                                this.showToastMessage('未找到相关结果', 'info');
724
+                            }
725
+                            this.isSearchLoading = false;
726
+                        })
727
+                        .catch(error => {
728
+                            console.error('搜索出错:', error);
729
+                            this.showToastMessage('搜索失败,请稍后重试', 'error');
730
+                            this.isSearchLoading = false;
731
+                        });
732
+                },
733
+                
734
+                // 选择列表项
735
+                selectItem(item) {
736
+                    this.selectedItem = item;
737
+                    this.isDetailLoading = true;
738
+                    
739
+                    fetch(`/api/GetMiaoguoLiteracyListByID?ID=${this.selectedItem.ID}`)
740
+                        .then(response => response.text())
741
+                        .then(text => {
742
+                            // 修复JSON格式问题,添加缺失的逗号
743
+                            const fixedText = this.fixJsonFormat(text);
744
+                            const data = JSON.parse(fixedText);
745
+                            var json = data.result;
746
+                            this.jsonData = json;
747
+                            this.originalData = JSON.parse(JSON.stringify(json)); // 深拷贝
748
+                            this.isDetailLoading = false;
749
+                        })
750
+                        .catch(error => {
751
+                            console.error('获取详情出错:', error);
752
+                            this.showToastMessage('获取详情失败,请稍后重试', 'error');
753
+                            this.isDetailLoading = false;
754
+                        });
755
+                },
756
+
757
+                // 编辑值
758
+                editValue(obj, key, path) {
759
+                    this.editingKey = path || key;
760
+                },
761
+
762
+                // 完成编辑
763
+                finishEdit() {
764
+                    this.editingKey = null;
765
+                },
766
+
767
+                // 重置数据
768
+                resetData() {
769
+                    if (this.originalData) {
770
+                        this.jsonData = JSON.parse(JSON.stringify(this.originalData));
771
+                    }
772
+                },
773
+
774
+                // 保存数据
775
+                saveData() {
776
+                    fetch('/api/UpdateMiaoguoLiteracyByWord', {
777
+                        method: 'POST',
778
+                        headers: {
779
+                            'Content-Type': 'application/json'
780
+                        },
781
+                        body: JSON.stringify({
782
+                            ID: this.selectedItem.ID,
783
+                            JSONString: JSON.stringify(this.jsonData)
784
+                        })
785
+                    })
786
+                        .then(response => response.json())
787
+                        .then(data => {
788
+                            if (data) {
789
+                                this.showToastMessage('保存成功', 'success');
790
+                                this.originalData = JSON.parse(JSON.stringify(this.jsonData));
791
+                            } else {
792
+                                this.showToastMessage('保存失败', 'error');
793
+                            }
794
+                        })
795
+                        .catch(error => {
796
+                            console.error('保存出错:', error);
797
+                            this.showToastMessage('保存失败,请稍后重试', 'error');
798
+                        });
799
+                },
800
+
801
+                // 显示Toast消息
802
+                showToastMessage(message, type = 'info') {
803
+                    this.toastMessage = message;
804
+                    this.toastType = type;
805
+                    this.showToast = true;
806
+
807
+                    setTimeout(() => {
808
+                        this.showToast = false;
809
+                    }, 3000);
810
+                },
811
+                
812
+                // 修复JSON格式问题
813
+                fixJsonFormat(jsonStr) {
814
+                    // 修复缺少逗号的问题
815
+                    // 在引号后面跟着引号的地方添加逗号
816
+                    let fixed = jsonStr.replace(/(["\d])\s*"([^"]+)":/g, '$1,"$2":');
817
+                    // 修复数字后面跟着引号的情况
818
+                    fixed = fixed.replace(/(\d+)\s*"([^"]+)":/g, '$1,"$2":');
819
+                    // 修复大括号后面跟着引号的情况
820
+                    fixed = fixed.replace(/({)\s*"([^"]+)":/g, '$1"$2":');
821
+                    // 修复方括号后面跟着引号的情况
822
+                    fixed = fixed.replace(/(\[)\s*"([^"]+)":/g, '$1"$2":');
823
+                    
824
+                    //console.log("原始JSON:", jsonStr);
825
+                    //console.log("修复后JSON:", fixed);
826
+                    
827
+                    return fixed;
828
+                },
829
+                
830
+                // 处理图片加载错误
831
+                handleImageError(event) {
832
+                    // 添加错误样式,显示边框和错误提示
833
+                    event.target.style.border = '1px dashed #ff6b6b';
834
+                    event.target.style.padding = '10px';
835
+                    event.target.style.backgroundColor = '#fff0f0';
836
+                    
837
+                    // 如果是gif图片,尝试使用代理或CORS解决方案
838
+                    if (event.target.src.endsWith('.gif')) {
839
+                        // 可以尝试使用代理服务器来解决CORS问题
840
+                        // 这里我们只添加一个提示,实际项目中可以根据需要实现代理
841
+                        const originalSrc = event.target.src;
842
+                        
843
+                        // 创建一个错误提示元素
844
+                        const errorDiv = document.createElement('div');
845
+                        errorDiv.style.color = '#ff6b6b';
846
+                        errorDiv.style.fontSize = '12px';
847
+                        errorDiv.style.marginTop = '5px';
848
+                        errorDiv.textContent = 'GIF图片加载失败,可能存在跨域问题';
849
+                        
850
+                        // 将错误提示添加到图片后面
851
+                        event.target.parentNode.insertBefore(errorDiv, event.target.nextSibling);
852
+                    }
853
+                }
854
+            }
855
+        });
856
+    </script>
857
+</body>
858
+
859
+</html>

Разница между файлами не показана из-за своего большого размера
+ 1070 - 0
public/mg/kylx365_db_admin.html


+ 12 - 0
src/api/web/routes.js

@@ -0,0 +1,12 @@
1
+import Router from 'koa-router';
2
+import * as webController from './webController.js';
3
+
4
+
5
+const router = new Router();
6
+
7
+// 页面路由
8
+router.get('/kylx365_db_admin',webController.Kylx365DBAdmin);
9
+router.get('/api/GetKylx365Tables',webController.GetKylx365Tables);
10
+router.get('/api/GetKylx365TableColumnByTable',webController.GetKylx365TableColumnByTable);
11
+
12
+export default router;

+ 35 - 0
src/api/web/webController.js

@@ -0,0 +1,35 @@
1
+import moment from 'moment';
2
+import fs from 'fs';
3
+import { promises as fsPromises } from 'fs';
4
+import { globalCache } from '../../util/GlobalCache.js';
5
+import config from '../../config/index.js';
6
+import _ from 'lodash';
7
+import axios from 'axios';
8
+import commonModel from '../../model/commonModel.js';
9
+
10
+/**
11
+ * 分数线网页首页
12
+ * @generator
13
+ * @yields {Object} 返回渲染后的HTML页面内容
14
+ * @description 读取并返回public目录下的mpsDefault.html文件内容作为响应体
15
+ */
16
+export async function Kylx365DBAdmin(ctx) {
17
+    console.log("Kylx365DBAdmin");
18
+    const data = await fsPromises.readFile("./public/mg/kylx365_db_admin.html");
19
+    ctx.body = data.toString();
20
+};
21
+
22
+export async function GetKylx365Tables(ctx) {
23
+    let result = await commonModel.RunSql(null,"SELECT table_name,table_comment FROM information_schema.tables WHERE table_schema = 'kylx365_db';");
24
+    console.log("表格数:"+result.length);
25
+    ctx.body = {"errcode": 10000, result: result};
26
+};
27
+    
28
+export async function GetKylx365TableColumnByTable(ctx) {
29
+    let param = {
30
+        TableName: ctx.query.table || "",
31
+    };
32
+    let result = await commonModel.RunSql(null,"SHOW COLUMNS FROM `"+param.TableName+"`;");
33
+    ctx.body = {"errcode": 10000, result: result};
34
+};
35
+    

+ 3 - 0
src/app.js

@@ -12,6 +12,7 @@ import mpsRouter from './api/mps/routes.js';
12 12
 import phonicsRouter from './api/phonics/routes.js';
13 13
 import pinyinRouter from './api/pinyin/routes.js';
14 14
 import hanziRouter from './api/hanzi/routes.js';
15
+import webRouter from './api/web/routes.js';
15 16
 
16 17
 const __dirname = path.dirname(fileURLToPath(import.meta.url));
17 18
 
@@ -52,6 +53,8 @@ app.use(pinyinRouter.routes());
52 53
 app.use(pinyinRouter.allowedMethods());
53 54
 app.use(hanziRouter.routes());
54 55
 app.use(hanziRouter.allowedMethods());
56
+app.use(webRouter.routes());
57
+app.use(webRouter.allowedMethods());
55 58
 
56 59
 // 启动服务器
57 60
 app.listen(config.port, () => {

+ 2 - 1
src/test/build.test.js

@@ -70,4 +70,5 @@ async function runScript(){
70 70
 // 处理Promise并添加错误捕获
71 71
 runScript().catch(error => {
72 72
     console.error('Error in runScript:', error);
73
-});
73
+});
74
+