Spaces:
BG5
/
Running

BG5 commited on
Commit
0fd60ee
·
verified ·
1 Parent(s): 178e98a

Delete index.html

Browse files
Files changed (1) hide show
  1. index.html +0 -1606
index.html DELETED
@@ -1,1606 +0,0 @@
1
- <!DOCTYPE html>
2
- <html lang="zh-CN">
3
- <head>
4
- <meta charset="UTF-8">
5
- <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
- <title>JetBrains激活链接管理系统</title>
7
- <!-- 使用CDN引入Vue 3和Element Plus,指定固定版本 -->
8
- <link rel="stylesheet" href="https://unpkg.com/[email protected]/dist/index.css" />
9
- <script src="https://unpkg.com/[email protected]/dist/vue.global.prod.js"></script>
10
- <script src="https://unpkg.com/[email protected]"></script>
11
- <script src="https://unpkg.com/@element-plus/[email protected]"></script>
12
- <script src="https://unpkg.com/[email protected]/dist/axios.min.js"></script>
13
- <style>
14
- body {
15
- margin: 0;
16
- padding: 0;
17
- font-family: "Helvetica Neue", Helvetica, "PingFang SC", "Hiragino Sans GB", "Microsoft YaHei", Arial, sans-serif;
18
- -webkit-font-smoothing: antialiased;
19
- -moz-osx-font-smoothing: grayscale;
20
- background-color: #f5f7fa;
21
- color: #303133;
22
- }
23
- .app-container {
24
- padding: 8px;
25
- }
26
- .dashboard-card {
27
- margin-bottom: 20px;
28
- border-radius: 4px;
29
- box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);
30
- background-color: #fff;
31
- }
32
- .dashboard-header {
33
- display: flex;
34
- align-items: center;
35
- justify-content: space-between;
36
- padding: 10px 20px;
37
- border-bottom: 1px solid #ebeef5;
38
- }
39
- .el-table .status-column {
40
- display: flex;
41
- align-items: center;
42
- justify-content: center;
43
- }
44
- .el-table .status-0 {
45
- color: #909399;
46
- }
47
- .el-table .status-1 {
48
- color: #409EFF;
49
- }
50
- .el-table .status-2 {
51
- color: #F56C6C;
52
- }
53
- .el-table .status-3 {
54
- color: #67C23A;
55
- }
56
- .operation-btn {
57
- margin-right: 5px;
58
- }
59
- .statistics-card {
60
- text-align: center;
61
- padding: 20px;
62
- }
63
- .statistics-value {
64
- font-size: 36px;
65
- font-weight: bold;
66
- }
67
- .statistics-label {
68
- font-size: 14px;
69
- color: #909399;
70
- }
71
- .task-card {
72
- margin-bottom: 15px;
73
- }
74
- .copy-button {
75
- margin-left: 5px;
76
- }
77
- .el-tag {
78
- margin: 2px;
79
- }
80
- .tab-container {
81
- padding: 15px;
82
- }
83
- .sr-only {
84
- position: absolute;
85
- width: 1px;
86
- height: 1px;
87
- padding: 0;
88
- margin: -1px;
89
- overflow: hidden;
90
- clip: rect(0, 0, 0, 0);
91
- white-space: nowrap;
92
- border-width: 0;
93
- }
94
- .required-field::after {
95
- content: ' *';
96
- color: #F56C6C;
97
- }
98
- .el-table .id-column,
99
- .el-table .register-time-column,
100
- .el-table .username-column,
101
- .el-table .email-column {
102
- white-space: nowrap;
103
- overflow: hidden;
104
- text-overflow: ellipsis;
105
- }
106
- .el-table .email-column {
107
- max-width: 250px;
108
- }
109
- /* 调试样式 - 显示表格边框和背景色 */
110
- .el-table {
111
- border: 1px solid #dcdfe6;
112
- }
113
- .el-table th {
114
- background-color: #f5f7fa !important;
115
- color: #606266;
116
- font-weight: bold;
117
- text-align: center !important;
118
- }
119
- .el-table td {
120
- text-align: center;
121
- }
122
- /* 确保ID列和时间列有足够的宽度 */
123
- .el-table .id-column {
124
- min-width: 80px !important;
125
- width: 80px !important;
126
- }
127
- .el-table .register-time-column {
128
- min-width: 180px !important;
129
- width: 180px !important;
130
- }
131
- .el-table .username-column {
132
- min-width: 250px !important;
133
- }
134
- .el-table .email-column {
135
- min-width: 180px !important;
136
- }
137
- /* 必填字段标记 */
138
- .required::after {
139
- content: '*';
140
- color: #F56C6C;
141
- margin-left: 4px;
142
- }
143
- @media (max-width: 900px) {
144
- .app-container {
145
- padding: 2px !important;
146
- }
147
- .dashboard-card {
148
- margin-bottom: 8px;
149
- padding: 0 2px;
150
- }
151
- .statistics-card {
152
- border-radius: 10px;
153
- box-shadow: 0 2px 8px #eee;
154
- padding: 10px 0;
155
- text-align: center !important;
156
- display: flex;
157
- flex-direction: column;
158
- align-items: center;
159
- justify-content: center;
160
- }
161
- .statistics-value {
162
- font-size: 28px;
163
- text-align: center !important;
164
- width: 100%;
165
- margin: 0 auto;
166
- }
167
- .statistics-label {
168
- font-size: 16px;
169
- text-align: center !important;
170
- width: 100%;
171
- margin: 0 auto;
172
- }
173
- .tab-container {
174
- padding: 4px;
175
- }
176
- .el-row.filter-row {
177
- margin: 0 -2px;
178
- }
179
- .el-col.filter-col {
180
- margin-bottom: 4px;
181
- padding: 0 2px;
182
- }
183
- .el-table {
184
- border-radius: 6px;
185
- box-shadow: 0 1px 4px #eee;
186
- font-size: 15px;
187
- }
188
- .el-table th, .el-table td {
189
- padding-left: 2px;
190
- padding-right: 2px;
191
- }
192
- .el-pagination {
193
- font-size: 15px;
194
- }
195
- }
196
- </style>
197
- </head>
198
- <body>
199
- <!--- /> 标签改为标准的成对闭合形式,否则显示将不正常-->
200
- <div id="app" class="app-container">
201
- <el-container>
202
- <el-header style="height: auto; padding: 0; margin-bottom: 20px;">
203
- <div class="dashboard-card">
204
- <div class="dashboard-header">
205
- <h2>JetBrains激活链接管理系统</h2>
206
- <div>
207
- <el-button type="primary" @click="showImportDialog">导入数据</el-button>
208
- <el-button type="success" @click="showExportDialog">导出数据</el-button>
209
- <el-button type="danger" @click="resetFailedAccounts">重置失败账号</el-button>
210
- <el-button type="warning" @click="logout" style="margin-left: 10px;">退出登录</el-button>
211
- </div>
212
- </div>
213
- </div>
214
- </el-header>
215
-
216
- <el-main>
217
- <!-- 统计信息卡片 /> 标签改为标准的成对闭合形式,否则显示将不正常 -->
218
- <el-row :gutter="20">
219
- <el-col :span="4">
220
- <el-card shadow="hover" class="statistics-card">
221
- <div class="statistics-value">{{ statistics.total }}</div>
222
- <div class="statistics-label">总数</div>
223
- </el-card>
224
- </el-col>
225
- <!-- <el-col :span="5">
226
- <el-card shadow="hover" class="statistics-card" style="background-color: #f0f9eb;">
227
- <div class="statistics-value" style="color: #67C23A;">{{ statistics.link_extracted }}</div>
228
- <div class="statistics-label">链接</div>
229
- </el-card>
230
- </el-col> -->
231
- <el-col :span="5">
232
- <el-card shadow="hover" class="statistics-card" style="background-color: #f9f9e3;">
233
- <div class="statistics-value" style="color: #e6a23c;">{{ statistics.unused }}</div>
234
- <div class="statistics-label">未用</div>
235
- </el-card>
236
- </el-col>
237
- <el-col :span="5">
238
- <el-card shadow="hover" class="statistics-card" style="background-color: #ecf5ff;">
239
- <div class="statistics-value" style="color: #409EFF;">{{ statistics.submitted }}</div>
240
- <div class="statistics-label">提交</div>
241
- </el-card>
242
- </el-col>
243
- <el-col :span="5">
244
- <el-card shadow="hover" class="statistics-card" style="background-color: #fef0f0;">
245
- <div class="statistics-value" style="color: #F56C6C;">{{ statistics.failed }}</div>
246
- <div class="statistics-label">失败</div>
247
- </el-card>
248
- </el-col>
249
- <el-col :span="5">
250
- <el-card shadow="hover" class="statistics-card" style="background-color: #f4f4f5;">
251
- <div class="statistics-value" style="color: #909399;">{{ statistics.pending }}</div>
252
- <div class="statistics-label">未取</div>
253
- </el-card>
254
- </el-col>
255
- </el-row>
256
-
257
- <!-- 主要标签页 -->
258
- <el-tabs type="border-card" v-model="activeTab" @tab-click="handleTabClick">
259
- <!-- 账号管理标签页 -->
260
- <el-tab-pane label="账号管理" name="accounts">
261
- <div class="tab-container">
262
- <div style="margin-bottom: 20px;">
263
- <el-row :gutter="10" class="filter-row" style="flex-wrap: nowrap; display: flex;">
264
- <el-col :span="3" class="filter-col" style="min-width: 120px;">
265
- <el-select v-model="accountsFilter.status" placeholder="选择状态" clearable style="width: 100%;" @change="handleStatusChange">
266
- <el-option label="未提交" :value="0"></el-option>
267
- <el-option label="已提交" :value="1"></el-option>
268
- <el-option label="提交失败" :value="2"></el-option>
269
- <el-option label="已提取链接" :value="3"></el-option>
270
- </el-select>
271
- </el-col>
272
- <el-col :span="3" class="filter-col" style="min-width: 140px;">
273
- <el-date-picker v-model="accountsFilter.registerTimeStart" type="date" placeholder="注册起始日期" style="width: 100%;" format="YYYY-MM-DD" value-format="YYYY-MM-DD" clearable></el-date-picker>
274
- </el-col>
275
- <el-col :span="3" class="filter-col" style="min-width: 140px;">
276
- <el-date-picker v-model="accountsFilter.registerTimeEnd" type="date" placeholder="注册结束日期" style="width: 100%;" format="YYYY-MM-DD" value-format="YYYY-MM-DD" clearable></el-date-picker>
277
- </el-col>
278
- <el-col :span="2" class="filter-col" style="min-width: 90px;">
279
- <el-input v-model="accountsFilter.idMin" placeholder="最小ID" clearable></el-input>
280
- </el-col>
281
- <el-col :span="2" class="filter-col" style="min-width: 90px;">
282
- <el-input v-model="accountsFilter.idMax" placeholder="最大ID" clearable></el-input>
283
- </el-col>
284
- <el-col :span="3" class="filter-col" style="min-width: 120px;">
285
- <el-select v-model="accountsFilter.hasActivationLink" placeholder="激活链接" clearable style="width: 100%;">
286
- <el-option label="全部" value=""></el-option>
287
- <el-option label="有激活链接" value="yes"></el-option>
288
- <el-option label="无激活链接" value="no"></el-option>
289
- </el-select>
290
- </el-col>
291
- <el-col :span="3" class="filter-col" style="min-width: 120px;">
292
- <el-select v-model="accountsFilter.used" placeholder="使用状态" clearable style="width: 100%;">
293
- <el-option label="全部" :value="''"></el-option>
294
- <el-option label="未被使用" :value="0"></el-option>
295
- <el-option label="已被使用" :value="1"></el-option>
296
- </el-select>
297
- </el-col>
298
- <el-col :span="4" class="filter-col" style="min-width: 160px;">
299
- <el-input v-model="accountsFilter.search" placeholder="邮箱/备注" clearable></el-input>
300
- </el-col>
301
- </el-row>
302
- </div>
303
-
304
- <el-row :gutter="20" style="margin-bottom: 10px;">
305
- <el-col :span="24" style="text-align: right;">
306
- <el-button type="primary" :disabled="selectedAccounts.length === 0" @click="batchToggleUsed(1)">
307
- 批量标记为已使用
308
- </el-button>
309
- <el-button type="info" :disabled="selectedAccounts.length === 0" @click="batchToggleUsed(0)">
310
- 批量标记为未使用
311
- </el-button>
312
- </el-col>
313
- </el-row>
314
-
315
- <el-table
316
- :data="accounts"
317
- style="width: 100%;overflow-x:auto;"
318
- v-loading="loading.accounts"
319
- @selection-change="handleSelectionChange"
320
- border
321
- stripe
322
- highlight-current-row
323
- >
324
- <el-table-column type="selection" width="50"> </el-table-column>
325
- <el-table-column label="ID" width="80" sortable>
326
- <template #default="scope">
327
- {{ scope.row.id || '-' }}
328
- </template>
329
- </el-table-column>
330
- <el-table-column label="注册时间" width="180" sortable>
331
- <template #default="scope">
332
- {{ scope.row.register_time || '-' }}
333
- </template>
334
- </el-table-column>
335
- <el-table-column label="用户名" min-width="250" show-overflow-tooltip>
336
- <template #default="scope">
337
- {{ scope.row.username || '-' }}
338
- </template>
339
- </el-table-column>
340
- <el-table-column label="状态" width="120">
341
- <template #default="scope">
342
- <el-tag :type="getStatusType(scope.row.status)" size="small">
343
- {{ getStatusText(scope.row.status) }}
344
- </el-tag>
345
- </template>
346
- </el-table-column>
347
- <el-table-column label="激活链接" min-width="300">
348
- <template #default="scope">
349
- <div v-if="scope.row.activation_link" style="display: flex; align-items: center;">
350
- <el-tooltip :content="scope.row.activation_link" placement="top" effect="light">
351
- <span style="white-space: nowrap; overflow: hidden; text-overflow: ellipsis; max-width: 200px;">
352
- {{ scope.row.activation_link }}
353
- </span>
354
- </el-tooltip>
355
- <el-button
356
- size="small"
357
- type="primary"
358
- class="copy-button"
359
- @click="copyToClipboard(scope.row.activation_link, scope.row)"
360
- >
361
- 复制
362
- </el-button>
363
- </div>
364
- <span v-else>-</span>
365
- </template>
366
- </el-table-column>
367
- <el-table-column label="已被使用" width="100">
368
- <template #default="scope">
369
- <el-tag :type="scope.row.used ? 'success' : 'info'" @click="toggleAccountUsed(scope.row)" style="cursor:pointer;">
370
- {{ scope.row.used ? '✔ 已使用' : '未使用' }}
371
- </el-tag>
372
- </template>
373
- </el-table-column>
374
- <el-table-column label="操作" width="250" fixed="right">
375
- <template #default="scope">
376
- <el-button
377
- size="small"
378
- type="primary"
379
- @click="submitSingleAccount(scope.row)"
380
- :disabled="scope.row.status === 1 || scope.row.status === 3"
381
- >
382
- 提交
383
- </el-button>
384
- <el-button
385
- size="small"
386
- type="success"
387
- @click="extractSingleAccount(scope.row)"
388
- :disabled="scope.row.status !== 1"
389
- >
390
- 提取
391
- </el-button>
392
- <el-button
393
- size="small"
394
- @click="viewAccountDetails(scope.row)"
395
- >
396
- 详情
397
- </el-button>
398
- <el-button
399
- size="small"
400
- type="danger"
401
- @click="deleteAccount(scope.row)"
402
- >
403
- 删除
404
- </el-button>
405
- </template>
406
- </el-table-column>
407
- </el-table>
408
-
409
- <div style="margin-top: 20px; display: flex; justify-content: center;">
410
- <el-pagination
411
- v-model:current-page="pagination.current"
412
- v-model:page-size="pagination.pageSize"
413
- :page-sizes="[10, 20, 50, 100]"
414
- layout="total, sizes, prev, pager, next, jumper"
415
- :total="pagination.total"
416
- @size-change="handleSizeChange"
417
- @current-change="handleCurrentChange"
418
- />
419
- </div>
420
- </div>
421
- </el-tab-pane>
422
-
423
- <!-- 任务管理标签页 -->
424
- <el-tab-pane label="任务管理" name="tasks">
425
- <div class="tab-container">
426
- <el-button type="primary" @click="loadTasks" style="margin-bottom: 20px;">刷新任务列表</el-button>
427
-
428
- <el-timeline>
429
- <el-timeline-item
430
- v-for="(task, id) in tasks"
431
- :key="id"
432
- :type="getTaskStatusType(task.status)"
433
- :icon="getTaskStatusIcon(task.status)"
434
- :timestamp="task.start_time"
435
- >
436
- <el-card class="task-card">
437
- <div style="display: flex; justify-content: space-between; align-items: center;">
438
- <div>
439
- <span style="font-weight: bold;">{{ getTaskTypeText(task.type) }} - {{ id }}</span>
440
- <el-tag size="small" style="margin-left: 10px;" :type="getTaskStatusType(task.status)">
441
- {{ getTaskStatusText(task.status) }}
442
- </el-tag>
443
- </div>
444
- <div v-if="task.status === 'completed'">
445
- <span style="margin-right: 10px;">成功: {{ task.success_count || 0 }}</span>
446
- <span>失败: {{ task.error_count || 0 }}</span>
447
- </div>
448
- </div>
449
- <div v-if="task.error" style="color: #F56C6C; margin-top: 10px;">
450
- 错误: {{ task.error }}
451
- </div>
452
- <div v-if="task.account_ids && task.account_ids.length > 0" style="margin-top: 10px;">
453
- <span>处理账号: {{ task.account_ids.length }}个</span>
454
- </div>
455
- </el-card>
456
- </el-timeline-item>
457
- </el-timeline>
458
-
459
- <div v-if="Object.keys(tasks).length === 0" style="text-align: center; padding: 30px;">
460
- <el-empty description="暂无任务记录" />
461
- </div>
462
- </div>
463
- </el-tab-pane>
464
-
465
- <!-- 操作日志标签页 -->
466
- <el-tab-pane label="操作日志" name="logs">
467
- <div class="tab-container">
468
- <div style="margin-bottom: 20px;">
469
- <el-row :gutter="20">
470
- <el-col :span="8">
471
- <el-input v-model="logsFilter.username" placeholder="用户名" clearable></el-input>
472
- </el-col>
473
- <el-col :span="8">
474
- <el-select v-model="logsFilter.operation" placeholder="操作类型" clearable style="width: 100%;">
475
- <el-option label="提交邮箱" value="submit_email"></el-option>
476
- <el-option label="提取链接" value="extract_link"></el-option>
477
- <el-option label="后台提交" value="submit_email_background"></el-option>
478
- <el-option label="后台提取" value="extract_link_background"></el-option>
479
- <el-option label="更新状态" value="update_status"></el-option>
480
- </el-select>
481
- </el-col>
482
- <el-col :span="8">
483
- <el-select v-model="logsFilter.status" placeholder="操作状态" clearable style="width: 100%;">
484
- <el-option label="成功" value="success"></el-option>
485
- <el-option label="失败" value="failed"></el-option>
486
- <el-option label="处理中" value="processing"></el-option>
487
- <el-option label="错误" value="error"></el-option>
488
- </el-select>
489
- </el-col>
490
- </el-row>
491
- </div>
492
-
493
- <el-table
494
- :data="logs"
495
- style="width: 100%"
496
- v-loading="loading.logs"
497
- border
498
- >
499
- <el-table-column label="ID" width="80">
500
- <template #default="scope">
501
- {{ scope.row.log_id || scope.row.id || '-' }}
502
- </template>
503
- </el-table-column>
504
- <el-table-column label="用户名" min-width="150">
505
- <template #default="scope">
506
- {{ scope.row.account_username || scope.row.username || '-' }}
507
- </template>
508
- </el-table-column>
509
- <el-table-column label="操作" width="180">
510
- <template #default="scope">
511
- <el-tag size="small">{{ getOperationText(scope.row.operation) }}</el-tag>
512
- </template>
513
- </el-table-column>
514
- <el-table-column label="状态" width="120">
515
- <template #default="scope">
516
- <el-tag :type="getLogStatusType(scope.row.status)" size="small">
517
- {{ scope.row.status }}
518
- </el-tag>
519
- </template>
520
- </el-table-column>
521
- <el-table-column label="消息" min-width="200">
522
- <template #default="scope">
523
- <el-tag size="small">{{ getMessageText(scope.row.message) }}</el-tag>
524
- </template>
525
- </el-table-column>
526
- <el-table-column label="时间" width="180">
527
- <template #default="scope">
528
- {{ scope.row.created_at || scope.row.timestamp || '-' }}
529
- </template>
530
- </el-table-column>
531
- </el-table>
532
-
533
- <div style="margin-top: 20px; display: flex; justify-content: center;">
534
- <el-pagination
535
- v-model:current-page="logsPagination.current"
536
- v-model:page-size="logsPagination.pageSize"
537
- :page-sizes="[20, 50, 100]"
538
- layout="total, sizes, prev, pager, next, jumper"
539
- :total="logsPagination.total"
540
- @size-change="handleLogsSizeChange"
541
- @current-change="handleLogsCurrentChange"
542
- />
543
- </div>
544
- </div>
545
- </el-tab-pane>
546
- </el-tabs>
547
- </el-main>
548
- </el-container>
549
-
550
- <!-- 导入数据对话框 -->
551
- <el-dialog title="导入数据" v-model="dialogs.import" width="500px">
552
- <el-form label-width="100px">
553
- <el-form-item label="选择文件" required>
554
- <input type="file" @change="handleImportFileChange" accept=".txt,.csv" />
555
- </el-form-item>
556
- </el-form>
557
- <template #footer>
558
- <span class="dialog-footer">
559
- <el-button @click="dialogs.import = false">取消</el-button>
560
- <el-button type="primary" @click="importAccounts" :loading="loading.import">导入</el-button>
561
- </span>
562
- </template>
563
- </el-dialog>
564
-
565
- <!-- 导出数据对话框 -->
566
- <el-dialog title="导出数据" v-model="dialogs.export" width="500px">
567
- <el-form :model="exportForm" label-width="100px">
568
- <el-form-item label="文件名">
569
- <el-input v-model="exportForm.filename" placeholder="导出文件名"></el-input>
570
- </el-form-item>
571
- </el-form>
572
- <template #footer>
573
- <span class="dialog-footer">
574
- <el-button @click="dialogs.export = false">取消</el-button>
575
- <el-button type="primary" @click="exportAccounts" :loading="loading.export">导出</el-button>
576
- </span>
577
- </template>
578
- </el-dialog>
579
-
580
- <!-- 账号详情对话框 -->
581
- <el-dialog title="账号详情" v-model="dialogs.accountDetails" width="700px">
582
- <el-descriptions v-if="currentAccount" :column="1" border>
583
- <el-descriptions-item label="ID">{{ currentAccount.id }}</el-descriptions-item>
584
- <el-descriptions-item label="注册时间">{{ currentAccount.register_time }}</el-descriptions-item>
585
- <el-descriptions-item label="用户名">{{ currentAccount.username }}</el-descriptions-item>
586
- <el-descriptions-item label="密码">{{ currentAccount.password }}</el-descriptions-item>
587
- <el-descriptions-item label="密保邮箱">{{ currentAccount.security_email || '-' }}</el-descriptions-item>
588
- <el-descriptions-item label="状态">
589
- <el-tag :type="getStatusType(currentAccount.status)">
590
- {{ getStatusText(currentAccount.status) }}
591
- </el-tag>
592
- </el-descriptions-item>
593
- <el-descriptions-item label="激活链接">
594
- <div v-if="currentAccount.activation_link" style="display: flex; align-items: center;">
595
- <span style="word-break: break-all;">{{ currentAccount.activation_link }}</span>
596
- <el-button
597
- size="small"
598
- type="primary"
599
- class="copy-button"
600
- @click="copyToClipboard(currentAccount.activation_link, currentAccount)"
601
- >
602
- 复制
603
- </el-button>
604
- </div>
605
- <span v-else>-</span>
606
- </el-descriptions-item>
607
- <el-descriptions-item label="更新时间">{{ currentAccount.updated_at || '-' }}</el-descriptions-item>
608
- <el-descriptions-item label="备注">{{ currentAccount.notes || '-' }}</el-descriptions-item>
609
- </el-descriptions>
610
-
611
- <div style="margin-top: 20px;">
612
- <h3>更新状态</h3>
613
- <el-form :model="statusUpdateForm" label-width="100px">
614
- <el-form-item label="状态">
615
- <el-select v-model="statusUpdateForm.status" placeholder="选择状态" style="width: 100%;">
616
- <el-option label="未提交" :value="0"></el-option>
617
- <el-option label="已提交" :value="1"></el-option>
618
- <el-option label="提交失败" :value="2"></el-option>
619
- <el-option label="已提取链接" :value="3"></el-option>
620
- </el-select>
621
- </el-form-item>
622
- <el-form-item label="激活链接">
623
- <el-input v-model="statusUpdateForm.activation_link" placeholder="可选"></el-input>
624
- </el-form-item>
625
- <el-form-item label="备注">
626
- <el-input v-model="statusUpdateForm.notes" type="textarea" placeholder="可选"></el-input>
627
- </el-form-item>
628
- </el-form>
629
- </div>
630
-
631
- <template #footer>
632
- <span class="dialog-footer">
633
- <el-button @click="dialogs.accountDetails = false">取消</el-button>
634
- <el-button type="primary" @click="updateAccountStatus" :loading="loading.updateStatus">更新</el-button>
635
- </span>
636
- </template>
637
- </el-dialog>
638
-
639
- <!-- 提交邮箱对话框 -->
640
- <el-dialog title="提交邮箱" v-model="dialogs.submit" width="500px">
641
- <el-form :model="submitForm" label-width="100px">
642
- <el-form-item label="最大线程数">
643
- <el-input-number v-model="submitForm.max_workers" :min="1" :max="10"></el-input-number>
644
- </el-form-item>
645
- <el-form-item label="代理服务器">
646
- <el-input v-model="submitForm.proxy" placeholder="可选,例如:http://127.0.0.1:60060"></el-input>
647
- </el-form-item>
648
- </el-form>
649
- <template #footer>
650
- <span class="dialog-footer">
651
- <el-button @click="dialogs.submit = false">取消</el-button>
652
- <el-button type="primary" @click="confirmSubmit" :loading="loading.submit">提交</el-button>
653
- </span>
654
- </template>
655
- </el-dialog>
656
-
657
- <!-- 提取链接对话框 -->
658
- <el-dialog title="提取链接" v-model="dialogs.extract" width="500px">
659
- <el-form :model="extractForm" label-width="100px">
660
- <el-form-item label="最大线程数">
661
- <el-input-number v-model="extractForm.max_workers" :min="1" :max="5"></el-input-number>
662
- </el-form-item>
663
- </el-form>
664
- <template #footer>
665
- <span class="dialog-footer">
666
- <el-button @click="dialogs.extract = false">取消</el-button>
667
- <el-button type="primary" @click="confirmExtract" :loading="loading.extract">提取</el-button>
668
- </span>
669
- </template>
670
- </el-dialog>
671
-
672
- <!-- 登录验证对话框 -->
673
- <el-dialog title="登录验证" v-model="dialogs.login" width="350px" :close-on-click-modal="false" :show-close="false">
674
- <el-form @submit.prevent="handleLogin">
675
- <el-form-item label="密码">
676
- <el-input v-model="loginForm.password" type="password" autocomplete="off" @keyup.enter="handleLogin"></el-input>
677
- </el-form-item>
678
- </el-form>
679
- <template #footer>
680
- <span class="dialog-footer">
681
- <el-button type="primary" @click="handleLogin" :loading="loading.login">登录</el-button>
682
- </span>
683
- </template>
684
- </el-dialog>
685
- </div>
686
-
687
- <script>
688
- const { createApp, ref, reactive, onMounted, computed, nextTick, watch, onUnmounted } = Vue;
689
-
690
- // 后端API基础URL
691
- const API_BASE_URL = 'https://168.fucku.tech';
692
-
693
- const app = createApp({
694
- setup() {
695
- // 状态管理
696
- const activeTab = ref('accounts');
697
- const accounts = ref([]);
698
- const selectedAccounts = ref([]);
699
- const currentAccount = ref(null);
700
- const tasks = ref({});
701
- const logs = ref([]);
702
- const statistics = reactive({
703
- total: 0,
704
- pending: 0,
705
- submitted: 0,
706
- failed: 0,
707
- link_extracted: 0,
708
- unused: 0
709
- });
710
-
711
- // 任务轮询相关状态
712
- const runningTaskPolls = reactive({});
713
-
714
- // 分页配置
715
- const pagination = reactive({
716
- current: 1,
717
- pageSize: 10,
718
- total: 0
719
- });
720
-
721
- const logsPagination = reactive({
722
- current: 1,
723
- pageSize: 20,
724
- total: 0
725
- });
726
-
727
- // 过滤器
728
- const accountsFilter = reactive({
729
- status: null,
730
- search: '',
731
- registerTimeStart: '',
732
- registerTimeEnd: '',
733
- idMin: '',
734
- idMax: '',
735
- hasActivationLink: '', // '', 'yes', 'no'
736
- used: '' // 新增
737
- });
738
-
739
- const logsFilter = reactive({
740
- username: '',
741
- operation: '',
742
- status: ''
743
- });
744
-
745
- // 表单数据
746
- const importForm = reactive({
747
- filepath: 'user-4-21-success.txt'
748
- });
749
-
750
- const exportForm = reactive({
751
- filename: 'export_results.txt',
752
- status: null
753
- });
754
-
755
- const statusUpdateForm = reactive({
756
- status: 0,
757
- activation_link: '',
758
- notes: ''
759
- });
760
-
761
- const submitForm = reactive({
762
- max_workers: 1,
763
- proxy: ''
764
- });
765
-
766
- const extractForm = reactive({
767
- max_workers: 1
768
- });
769
-
770
- const loginForm = reactive({ password: '' });
771
-
772
- // 对话框控制
773
- const dialogs = reactive({
774
- import: false,
775
- export: false,
776
- accountDetails: false,
777
- submit: false,
778
- extract: false,
779
- login: false
780
- });
781
-
782
- // 加载状态
783
- const loading = reactive({
784
- accounts: false,
785
- tasks: false,
786
- logs: false,
787
- statistics: false,
788
- import: false,
789
- export: false,
790
- updateStatus: false,
791
- submit: false,
792
- extract: false,
793
- login: false
794
- });
795
-
796
- // token管理
797
- const getToken = () => {
798
- return document.cookie.replace(/(?:(?:^|.*;\s*)token\s*\=\s*([^;]*).*$)|^.*$/, "$1");
799
- };
800
- const setToken = (token) => {
801
- document.cookie = `token=${token};path=/;max-age=2592000`;
802
- };
803
- const clearToken = () => {
804
- document.cookie = 'token=;path=/;expires=Thu, 01 Jan 1970 00:00:01 GMT;';
805
- };
806
-
807
- // axios请求拦截
808
- axios.interceptors.request.use(config => {
809
- const token = getToken();
810
- if (token) config.headers.token = token;
811
- return config;
812
- });
813
-
814
- // 初始化
815
- onMounted(async () => {
816
- if (!(await checkLogin())) return;
817
- console.log('应用已加载,开始获取数据');
818
- loadAccounts();
819
- loadStatistics();
820
- loadTasks();
821
-
822
- // 监听过滤器变化
823
- watch([
824
- () => accountsFilter.status,
825
- () => accountsFilter.search,
826
- () => accountsFilter.registerTimeStart,
827
- () => accountsFilter.registerTimeEnd,
828
- () => accountsFilter.idMin,
829
- () => accountsFilter.idMax,
830
- () => accountsFilter.hasActivationLink,
831
- () => accountsFilter.used
832
- ], () => {
833
- pagination.current = 1;
834
- loadAccounts();
835
- });
836
-
837
- watch([
838
- () => logsFilter.username,
839
- () => logsFilter.operation,
840
- () => logsFilter.status
841
- ], () => {
842
- logsPagination.current = 1;
843
- loadLogs();
844
- });
845
- });
846
-
847
- // 登录逻辑
848
- const handleLogin = async () => {
849
- if (!loginForm.password) {
850
- ElementPlus.ElMessage.warning('请输入密码');
851
- return;
852
- }
853
- loading.login = true;
854
- try {
855
- const res = await axios.post(`${API_BASE_URL}/login`, { password: loginForm.password });
856
- setToken(res.data.token);
857
- dialogs.login = false;
858
- ElementPlus.ElMessage.success('登录成功');
859
- // 登录后自动加载数据
860
- loadAccounts();
861
- loadStatistics();
862
- loadTasks();
863
- } catch (e) {
864
- ElementPlus.ElMessage.error(e.response?.data?.detail || '登录失败');
865
- } finally {
866
- loading.login = false;
867
- }
868
- };
869
-
870
- // 退出登录
871
- const logout = () => {
872
- clearToken();
873
- dialogs.login = true;
874
- };
875
-
876
- // 检查token
877
- const checkLogin = async () => {
878
- const token = getToken();
879
- if (!token) {
880
- dialogs.login = true;
881
- return false;
882
- }
883
- // 简单校验token有效性(可选:请求一个接口)
884
- try {
885
- await axios.get(`${API_BASE_URL}/statistics`);
886
- return true;
887
- } catch {
888
- dialogs.login = true;
889
- return false;
890
- }
891
- };
892
-
893
- // 轮询任务状态的功能
894
- const startTaskPolling = (taskId) => {
895
- console.log(`开始轮询任务 ${taskId} 的状态`);
896
-
897
- if (runningTaskPolls[taskId]) {
898
- // 如果已经在轮询这个任务,则不重复创建
899
- return;
900
- }
901
-
902
- // 创建一个轮询间隔,每10秒查询一次任务状态
903
- const intervalId = setInterval(async () => {
904
- try {
905
- const response = await axios.get(`${API_BASE_URL}/tasks/${taskId}`);
906
- const taskData = response.data;
907
-
908
- // 更新本地任务数据
909
- tasks.value = { ...tasks.value, [taskId]: taskData };
910
-
911
- // 如果任务已完成或失败,停止轮询
912
- if (taskData.status === 'completed' || taskData.status === 'failed') {
913
- console.log(`任务 ${taskId} 已${taskData.status === 'completed' ? '完成' : '失败'},停止轮询`);
914
- clearInterval(runningTaskPolls[taskId]);
915
- delete runningTaskPolls[taskId];
916
-
917
- // 提醒用户任务已完成
918
- ElementPlus.ElMessage({
919
- message: `任务 ${taskId} 已${taskData.status === 'completed' ? '完成' : '失败'},成功: ${taskData.success_count || 0},失败: ${taskData.error_count || 0}`,
920
- type: taskData.status === 'completed' ? 'success' : 'warning',
921
- duration: 5000
922
- });
923
-
924
- // 如果任务成功,刷新账号列表和统计信息
925
- if (taskData.status === 'completed') {
926
- loadAccounts();
927
- loadStatistics();
928
- }
929
- }
930
- } catch (error) {
931
- console.error(`轮询任务 ${taskId} 状态失败:`, error);
932
- // 如果出错,也停止轮询
933
- clearInterval(runningTaskPolls[taskId]);
934
- delete runningTaskPolls[taskId];
935
- }
936
- }, 10000); // 每10秒轮询一次
937
-
938
- // 保存轮询间隔ID,以便稍后可以停止它
939
- runningTaskPolls[taskId] = intervalId;
940
- };
941
-
942
- // 加载账号列表
943
- const loadAccounts = async () => {
944
- loading.accounts = true;
945
- try {
946
- const params = {
947
- page: pagination.current,
948
- per_page: pagination.pageSize
949
- };
950
-
951
- if (accountsFilter.status !== null && accountsFilter.status !== '') params.status = accountsFilter.status;
952
- if (accountsFilter.search) params.search = accountsFilter.search;
953
- if (accountsFilter.registerTimeStart) params.register_time_start = accountsFilter.registerTimeStart;
954
- if (accountsFilter.registerTimeEnd) params.register_time_end = accountsFilter.registerTimeEnd;
955
- if (accountsFilter.idMin) params.id_min = accountsFilter.idMin;
956
- if (accountsFilter.idMax) params.id_max = accountsFilter.idMax;
957
- if (accountsFilter.hasActivationLink) params.has_activation_link = accountsFilter.hasActivationLink;
958
- if (accountsFilter.used !== '' && accountsFilter.used !== null && accountsFilter.used !== undefined) params.used = accountsFilter.used;
959
-
960
- console.log('请求参数:', params);
961
- const response = await axios.get(`${API_BASE_URL}/accounts`, { params });
962
- console.log('API响应:', response.data);
963
-
964
- if (response.data && Array.isArray(response.data.accounts)) {
965
- accounts.value = response.data.accounts;
966
- pagination.total = response.data.total;
967
-
968
- // 调试信息 - 检查每个账号数据
969
- accounts.value.forEach((account, index) => {
970
- console.log(`账号[${index}]:`, account);
971
- if (!account.id) console.warn(`账号[${index}] 缺少ID字段`);
972
- if (!account.register_time) console.warn(`账号[${index}] 缺少注册时间字段`);
973
- if (!account.username) console.warn(`账号[${index}] 缺少用户名字段`);
974
- });
975
-
976
- // 如果应该有数据但显示不出来,尝试强制更新视图
977
- if (accounts.value.length > 0) {
978
- nextTick(() => {
979
- console.log('视图已更新');
980
- });
981
- }
982
- } else {
983
- console.error('API返回的数据格式不正确:', response.data);
984
- ElementPlus.ElMessage.warning('API返回的数据格式不正确');
985
- }
986
- } catch (error) {
987
- console.error('加载账号失败:', error);
988
- ElementPlus.ElMessage.error('加载账号失败: ' + (error.response?.data?.detail || error.message));
989
- } finally {
990
- loading.accounts = false;
991
- }
992
- };
993
-
994
- // 加载统计信息
995
- const loadStatistics = async () => {
996
- loading.statistics = true;
997
- try {
998
- const response = await axios.get(`${API_BASE_URL}/statistics`);
999
- Object.assign(statistics, response.data);
1000
- } catch (error) {
1001
- console.error('加载统计信息失败:', error);
1002
- ElementPlus.ElMessage.error('加载统计信息失败: ' + (error.response?.data?.detail || error.message));
1003
- } finally {
1004
- loading.statistics = false;
1005
- }
1006
- };
1007
-
1008
- // 加载任务列表
1009
- const loadTasks = async () => {
1010
- loading.tasks = true;
1011
- try {
1012
- const response = await axios.get(`${API_BASE_URL}/tasks`);
1013
- tasks.value = response.data;
1014
-
1015
- // 检查并为所有正在运行的任务启动轮询
1016
- Object.entries(response.data).forEach(([taskId, taskData]) => {
1017
- if (taskData.status === 'running' && !runningTaskPolls[taskId]) {
1018
- startTaskPolling(taskId);
1019
- }
1020
- });
1021
- } catch (error) {
1022
- console.error('加载任务列表失败:', error);
1023
- ElementPlus.ElMessage.error('加载任务列表失败: ' + (error.response?.data?.detail || error.message));
1024
- } finally {
1025
- loading.tasks = false;
1026
- }
1027
- };
1028
-
1029
- // 加载日志列表
1030
- const loadLogs = async () => {
1031
- loading.logs = true;
1032
- try {
1033
- const params = {
1034
- page: logsPagination.current,
1035
- per_page: logsPagination.pageSize
1036
- };
1037
-
1038
- if (logsFilter.username) {
1039
- params.username = logsFilter.username;
1040
- }
1041
-
1042
- if (logsFilter.operation) {
1043
- params.operation = logsFilter.operation;
1044
- }
1045
-
1046
- if (logsFilter.status) {
1047
- params.status = logsFilter.status;
1048
- }
1049
-
1050
- const response = await axios.get(`${API_BASE_URL}/logs`, { params });
1051
- logs.value = response.data.logs;
1052
- logsPagination.total = response.data.total;
1053
- } catch (error) {
1054
- console.error('加载日志失败:', error);
1055
- ElementPlus.ElMessage.error('加载日志失败: ' + (error.response?.data?.detail || error.message));
1056
- } finally {
1057
- loading.logs = false;
1058
- }
1059
- };
1060
-
1061
- // 分页事件处理
1062
- const handleSizeChange = (size) => {
1063
- pagination.pageSize = size;
1064
- loadAccounts();
1065
- };
1066
-
1067
- const handleCurrentChange = (page) => {
1068
- pagination.current = page;
1069
- loadAccounts();
1070
- };
1071
-
1072
- const handleLogsSizeChange = (size) => {
1073
- logsPagination.pageSize = size;
1074
- loadLogs();
1075
- };
1076
-
1077
- const handleLogsCurrentChange = (page) => {
1078
- logsPagination.current = page;
1079
- loadLogs();
1080
- };
1081
-
1082
- // 表格选择事件
1083
- const handleSelectionChange = (selection) => {
1084
- selectedAccounts.value = selection;
1085
- };
1086
-
1087
- // 标签页点击事件
1088
- const handleTabClick = (tab) => {
1089
- if (tab.props.name === 'logs') {
1090
- loadLogs();
1091
- }
1092
- };
1093
-
1094
- // 导入账号
1095
- const showImportDialog = () => {
1096
- dialogs.import = true;
1097
- };
1098
-
1099
- const handleImportFileChange = (event) => {
1100
- const file = event.target.files[0];
1101
- if (file) {
1102
- importForm.filepath = file;
1103
- }
1104
- };
1105
-
1106
- const importAccounts = async () => {
1107
- if (!importForm.filepath) {
1108
- ElementPlus.ElMessage.warning('请选择文件');
1109
- return;
1110
- }
1111
-
1112
- loading.import = true;
1113
- try {
1114
- const formData = new FormData();
1115
- formData.append('file', importForm.filepath);
1116
-
1117
- const response = await axios.post(`${API_BASE_URL}/import`, formData, {
1118
- headers: {
1119
- 'Content-Type': 'multipart/form-data'
1120
- }
1121
- });
1122
-
1123
- ElementPlus.ElMessage.success(`成功导入 ${response.data.imported_count} 个账号`);
1124
- dialogs.import = false;
1125
-
1126
- // 重新加载数据
1127
- loadAccounts();
1128
- loadStatistics();
1129
- } catch (error) {
1130
- console.error('导入账号失败:', error);
1131
- ElementPlus.ElMessage.error('导入账号失败: ' + (error.response?.data?.detail || error.message));
1132
- } finally {
1133
- loading.import = false;
1134
- }
1135
- };
1136
-
1137
- // 导出账号
1138
- const showExportDialog = () => {
1139
- dialogs.export = true;
1140
- };
1141
-
1142
- const exportAccounts = async () => {
1143
- if (!exportForm.filename) {
1144
- ElementPlus.ElMessage.warning('请输入文件名');
1145
- return;
1146
- }
1147
- loading.export = true;
1148
- try {
1149
- // 导出时使用当前筛选条件
1150
- const params = {
1151
- filename: exportForm.filename
1152
- };
1153
- if (accountsFilter.status !== null && accountsFilter.status !== '') params.status = accountsFilter.status;
1154
- if (accountsFilter.used !== '' && accountsFilter.used !== null && accountsFilter.used !== undefined) params.used = accountsFilter.used;
1155
- if (accountsFilter.hasActivationLink) params.has_activation_link = accountsFilter.hasActivationLink;
1156
- if (accountsFilter.search) params.search = accountsFilter.search;
1157
- if (accountsFilter.registerTimeStart) params.register_time_start = accountsFilter.registerTimeStart;
1158
- if (accountsFilter.registerTimeEnd) params.register_time_end = accountsFilter.registerTimeEnd;
1159
- if (accountsFilter.idMin) params.id_min = accountsFilter.idMin;
1160
- if (accountsFilter.idMax) params.id_max = accountsFilter.idMax;
1161
-
1162
- // 使用axios以blob方式请求导出数据
1163
- const response = await axios.post(`${API_BASE_URL}/export`, params, {
1164
- responseType: 'blob' // 设置响应类型为blob
1165
- });
1166
-
1167
- // 创建Blob链接并触发下载
1168
- const blob = new Blob([response.data], { type: 'text/plain' });
1169
- const url = window.URL.createObjectURL(blob);
1170
- const link = document.createElement('a');
1171
- link.href = url;
1172
- link.setAttribute('download', exportForm.filename); // 使用用户指定的文件名
1173
- document.body.appendChild(link);
1174
- link.click();
1175
-
1176
- // 清理资源
1177
- document.body.removeChild(link);
1178
- window.URL.revokeObjectURL(url);
1179
-
1180
- ElementPlus.ElMessage.success('文件已导出并下载到本地');
1181
- dialogs.export = false;
1182
- } catch (error) {
1183
- console.error('导出账号失败:', error);
1184
- ElementPlus.ElMessage.error('导出账号失败: ' + (error.response?.data?.detail || error.message));
1185
- } finally {
1186
- loading.export = false;
1187
- }
1188
- };
1189
-
1190
- // 重置失败账号
1191
- const resetFailedAccounts = async () => {
1192
- try {
1193
- const response = await axios.post(`${API_BASE_URL}/reset-failed`);
1194
- ElementPlus.ElMessage.success(response.data.message);
1195
-
1196
- // 重新加载数据
1197
- loadAccounts();
1198
- loadStatistics();
1199
- } catch (error) {
1200
- console.error('重置失败账号出错:', error);
1201
- ElementPlus.ElMessage.error('重置失败账号出错: ' + (error.response?.data?.detail || error.message));
1202
- }
1203
- };
1204
-
1205
- // 查看账号详情
1206
- const viewAccountDetails = (account) => {
1207
- currentAccount.value = {...account};
1208
- statusUpdateForm.status = account.status;
1209
- statusUpdateForm.activation_link = account.activation_link || '';
1210
- statusUpdateForm.notes = account.notes || '';
1211
- dialogs.accountDetails = true;
1212
- };
1213
-
1214
- // 更新账号状态
1215
- const updateAccountStatus = async () => {
1216
- if (currentAccount.value === null) {
1217
- return;
1218
- }
1219
-
1220
- loading.updateStatus = true;
1221
- try {
1222
- const response = await axios.post(
1223
- `${API_BASE_URL}/accounts/${currentAccount.value.id}/status`,
1224
- statusUpdateForm
1225
- );
1226
-
1227
- ElementPlus.ElMessage.success('账号状态更新成功');
1228
-
1229
- // 更新本地数据
1230
- const index = accounts.value.findIndex(a => a.id === currentAccount.value.id);
1231
- if (index !== -1) {
1232
- accounts.value[index] = response.data;
1233
- }
1234
-
1235
- dialogs.accountDetails = false;
1236
-
1237
- // 重新加载统计数据
1238
- loadStatistics();
1239
- } catch (error) {
1240
- console.error('更新账号状态失败:', error);
1241
- ElementPlus.ElMessage.error('更新账号状态失败: ' + (error.response?.data?.detail || error.message));
1242
- } finally {
1243
- loading.updateStatus = false;
1244
- }
1245
- };
1246
-
1247
- // 删除账号
1248
- const deleteAccount = (account) => {
1249
- ElementPlus.ElMessageBox.confirm(
1250
- `确定要删除账号【${account.username}】吗?`,
1251
- '删除确认',
1252
- {
1253
- confirmButtonText: '删除',
1254
- cancelButtonText: '取消',
1255
- type: 'warning',
1256
- }
1257
- ).then(async () => {
1258
- try {
1259
- await axios.delete(`${API_BASE_URL}/accounts/${account.id}`);
1260
- ElementPlus.ElMessage.success('账号已删除');
1261
- loadAccounts();
1262
- loadStatistics();
1263
- } catch (error) {
1264
- ElementPlus.ElMessage.error('删除失败: ' + (error.response?.data?.detail || error.message));
1265
- }
1266
- }).catch(() => {});
1267
- };
1268
-
1269
- // 提交邮箱相关
1270
- const submitSelectedAccounts = () => {
1271
- submitForm.account_ids = selectedAccounts.value.map(account => account.id);
1272
- dialogs.submit = true;
1273
- };
1274
-
1275
- const submitSingleAccount = (account) => {
1276
- submitForm.account_ids = [account.id];
1277
- dialogs.submit = true;
1278
- };
1279
-
1280
- const confirmSubmit = async () => {
1281
- loading.submit = true;
1282
- try {
1283
- const response = await axios.post(`${API_BASE_URL}/submit`, {
1284
- account_ids: submitForm.account_ids,
1285
- max_workers: submitForm.max_workers,
1286
- proxy: submitForm.proxy || null
1287
- });
1288
-
1289
- const taskId = response.data.task_id;
1290
- ElementPlus.ElMessage.success('提交任务已启动,任务ID: ' + taskId);
1291
- dialogs.submit = false;
1292
-
1293
- // 切换到任务标签页
1294
- activeTab.value = 'tasks';
1295
-
1296
- // 延迟刷新任务列表
1297
- setTimeout(() => {
1298
- loadTasks();
1299
-
1300
- // 开始轮询任务状态
1301
- startTaskPolling(taskId);
1302
- }, 1000);
1303
- } catch (error) {
1304
- console.error('提交邮箱失败:', error);
1305
- ElementPlus.ElMessage.error('提交邮箱失败: ' + (error.response?.data?.detail || error.message));
1306
- } finally {
1307
- loading.submit = false;
1308
- }
1309
- };
1310
-
1311
- // 提取链接相关
1312
- const extractSelectedAccounts = () => {
1313
- extractForm.account_ids = selectedAccounts.value
1314
- .filter(account => account.status === 1)
1315
- .map(account => account.id);
1316
-
1317
- if (extractForm.account_ids.length === 0) {
1318
- ElementPlus.ElMessage.warning('没有选择已提交的账号');
1319
- return;
1320
- }
1321
-
1322
- dialogs.extract = true;
1323
- };
1324
-
1325
- const extractSingleAccount = (account) => {
1326
- if (account.status !== 1) {
1327
- ElementPlus.ElMessage.warning('只能为已提交的账号提取链接');
1328
- return;
1329
- }
1330
-
1331
- extractForm.account_ids = [account.id];
1332
- dialogs.extract = true;
1333
- };
1334
-
1335
- const confirmExtract = async () => {
1336
- loading.extract = true;
1337
- try {
1338
- const response = await axios.post(`${API_BASE_URL}/extract`, {
1339
- account_ids: extractForm.account_ids,
1340
- max_workers: extractForm.max_workers
1341
- });
1342
-
1343
- const taskId = response.data.task_id;
1344
- ElementPlus.ElMessage.success('提取任务已启动,任务ID: ' + taskId);
1345
- dialogs.extract = false;
1346
-
1347
- // 切换到任务标签页
1348
- activeTab.value = 'tasks';
1349
-
1350
- // 延迟刷新任务列表
1351
- setTimeout(() => {
1352
- loadTasks();
1353
-
1354
- // 开始轮询任务状态
1355
- startTaskPolling(taskId);
1356
- }, 1000);
1357
- } catch (error) {
1358
- console.error('提取链接失败:', error);
1359
- ElementPlus.ElMessage.error('提取链接失败: ' + (error.response?.data?.detail || error.message));
1360
- } finally {
1361
- loading.extract = false;
1362
- }
1363
- };
1364
-
1365
- // 切换账号used状态
1366
- const toggleAccountUsed = async (account) => {
1367
- try {
1368
- const response = await axios.post(`${API_BASE_URL}/accounts/${account.id}/toggle-used`);
1369
- account.used = response.data.used;
1370
- ElementPlus.ElMessage.success('已切换使用状态');
1371
- } catch (error) {
1372
- ElementPlus.ElMessage.error('切换失败: ' + (error.response?.data?.detail || error.message));
1373
- }
1374
- };
1375
-
1376
- // 批量切换账号used状态
1377
- const batchToggleUsed = async (usedValue) => {
1378
- if (selectedAccounts.value.length === 0) return;
1379
- const ids = selectedAccounts.value.map(acc => acc.id);
1380
- const confirmMsg = usedValue ? `确定将选中账号批量标记为“已使用”?` : `确定将选中账号批量标记为“未使用”?`;
1381
- ElementPlus.ElMessageBox.confirm(confirmMsg, '批量操作确认', {
1382
- confirmButtonText: '确定',
1383
- cancelButtonText: '取消',
1384
- type: 'warning',
1385
- }).then(async () => {
1386
- try {
1387
- await axios.post(`${API_BASE_URL}/accounts/batch-toggle-used`, { ids, used: usedValue });
1388
- ElementPlus.ElMessage.success('批量操作成功');
1389
- loadAccounts();
1390
- } catch (error) {
1391
- ElementPlus.ElMessage.error('批量操作失败: ' + (error.response?.data?.detail || error.message));
1392
- }
1393
- }).catch(() => {});
1394
- };
1395
-
1396
- // 复制到剪贴板并自动标记为已使用
1397
- const copyToClipboard = (text, account) => {
1398
- navigator.clipboard.writeText(text).then(async () => {
1399
- ElementPlus.ElMessage({
1400
- message: '已复制到剪贴板',
1401
- type: 'success',
1402
- duration: 1500
1403
- });
1404
- // 自动标记为已使用
1405
- if (account && !account.used) {
1406
- try {
1407
- const response = await axios.post(`${API_BASE_URL}/accounts/${account.id}/toggle-used`);
1408
- account.used = response.data.used;
1409
- } catch (error) {
1410
- // 忽略错误
1411
- }
1412
- }
1413
- }).catch(err => {
1414
- console.error('复制失败:', err);
1415
- ElementPlus.ElMessage.error('复制失败');
1416
- });
1417
- };
1418
-
1419
- // 清理函数 - 组件卸载时调用
1420
- onUnmounted(() => {
1421
- // 清理所有正在运行的轮询
1422
- Object.values(runningTaskPolls).forEach(intervalId => {
1423
- clearInterval(intervalId);
1424
- });
1425
- });
1426
-
1427
- // 工具函数 - 获取状态文本
1428
- const getStatusText = (status) => {
1429
- const statusMap = {
1430
- 0: '未提交',
1431
- 1: '已提交',
1432
- 2: '提交失败',
1433
- 3: '已提取链接'
1434
- };
1435
- return statusMap[status] || '未知状态';
1436
- };
1437
-
1438
- // 工具函数 - 获取状态类型(Element UI的tag类型)
1439
- const getStatusType = (status) => {
1440
- const typeMap = {
1441
- 0: 'info', // 未提交
1442
- 1: 'primary', // 已提交
1443
- 2: 'danger', // 提交失败
1444
- 3: 'success' // 已提取链接
1445
- };
1446
- return typeMap[status] || 'info';
1447
- };
1448
-
1449
- // 工具函数 - 获取任务状态文本
1450
- const getTaskStatusText = (status) => {
1451
- const statusMap = {
1452
- 'running': '运行中',
1453
- 'completed': '已完成',
1454
- 'failed': '失败'
1455
- };
1456
- return statusMap[status] || status;
1457
- };
1458
-
1459
- // 工具函数 - 获取任务状态类型
1460
- const getTaskStatusType = (status) => {
1461
- const typeMap = {
1462
- 'running': 'primary',
1463
- 'completed': 'success',
1464
- 'failed': 'danger'
1465
- };
1466
- return typeMap[status] || 'info';
1467
- };
1468
-
1469
- // 工具函数 - 获取任务状态图标
1470
- const getTaskStatusIcon = (status) => {
1471
- const iconMap = {
1472
- 'running': 'Loading',
1473
- 'completed': 'Check',
1474
- 'failed': 'Close'
1475
- };
1476
- return iconMap[status] || '';
1477
- };
1478
-
1479
- // 工具函数 - 获取任务类型文本
1480
- const getTaskTypeText = (type) => {
1481
- const typeMap = {
1482
- 'submit': '提交邮箱',
1483
- 'extract': '提取链接'
1484
- };
1485
- return typeMap[type] || type;
1486
- };
1487
-
1488
- // 工具函数 - 获取操作文本
1489
- const getOperationText = (operation) => {
1490
- const operationMap = {
1491
- 'submit_email': '提交邮箱',
1492
- 'extract_link': '提取链接',
1493
- 'submit_email_background': '后台提交',
1494
- 'extract_link_background': '后台提取',
1495
- 'update_status': '更新状态'
1496
- };
1497
- return operationMap[operation] || operation;
1498
- };
1499
-
1500
- // 工具函数 - 获取日志记录文本
1501
- const getMessageText = (message) => { // 获取日志记录文本
1502
- //直接返回消息
1503
- return message || '-';
1504
- };
1505
- // 工具函数 - 获取日志状态类型
1506
- const getLogStatusType = (status) => {
1507
- const typeMap = {
1508
- 'success': 'success',
1509
- 'failed': 'danger',
1510
- 'error': 'danger',
1511
- 'processing': 'warning'
1512
- };
1513
- return typeMap[status] || 'info';
1514
- };
1515
-
1516
- // 工具函数 - 处理状态筛选变化
1517
- const handleStatusChange = (value) => {
1518
- console.log('状态筛选变化:', value);
1519
- };
1520
-
1521
- return {
1522
- activeTab,
1523
- accounts,
1524
- selectedAccounts,
1525
- currentAccount,
1526
- tasks,
1527
- logs,
1528
- statistics,
1529
- pagination,
1530
- logsPagination,
1531
- accountsFilter,
1532
- logsFilter,
1533
- importForm,
1534
- exportForm,
1535
- statusUpdateForm,
1536
- submitForm,
1537
- extractForm,
1538
- loginForm,
1539
- dialogs,
1540
- loading,
1541
-
1542
- // 方法
1543
- loadAccounts,
1544
- loadStatistics,
1545
- loadTasks,
1546
- loadLogs,
1547
- handleSizeChange,
1548
- handleCurrentChange,
1549
- handleLogsSizeChange,
1550
- handleLogsCurrentChange,
1551
- handleSelectionChange,
1552
- handleTabClick,
1553
- showImportDialog,
1554
- handleImportFileChange,
1555
- importAccounts,
1556
- showExportDialog,
1557
- exportAccounts,
1558
- resetFailedAccounts,
1559
- viewAccountDetails,
1560
- updateAccountStatus,
1561
- deleteAccount,
1562
- submitSelectedAccounts,
1563
- submitSingleAccount,
1564
- confirmSubmit,
1565
- extractSelectedAccounts,
1566
- extractSingleAccount,
1567
- confirmExtract,
1568
- toggleAccountUsed,
1569
- batchToggleUsed,
1570
- copyToClipboard,
1571
- handleLogin,
1572
- logout,
1573
- checkLogin,
1574
-
1575
- // 工具函数
1576
- getStatusText,
1577
- getStatusType,
1578
- getTaskStatusText,
1579
- getTaskStatusType,
1580
- getTaskStatusIcon,
1581
- getTaskTypeText,
1582
- getOperationText,
1583
- getLogStatusType,
1584
- getMessageText,
1585
- handleStatusChange
1586
- };
1587
- }
1588
- });
1589
-
1590
- // // 全局注册Element Plus
1591
- // for (const [key, component] of Object.entries(ElementPlus)) {
1592
- // app.component(key, component);
1593
- // }
1594
-
1595
- // 注册所有图标
1596
- for (const [key, component] of Object.entries(ElementPlusIconsVue)) {
1597
- app.component(key, component);
1598
- }
1599
-
1600
- // 使用Element Plus
1601
- app.use(ElementPlus);
1602
-
1603
- app.mount('#app');
1604
- </script>
1605
- </body>
1606
- </html>