@zhin.js/adapter-icqq 1.0.0 → 1.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,719 @@
1
+ <template>
2
+ <div class="icqq-management">
3
+ <!-- 页面标题 -->
4
+ <div class="page-header mb-4">
5
+ <div class="flex align-items-center">
6
+ <i class="pi pi-comment text-3xl text-primary mr-3"></i>
7
+ <div>
8
+ <h1 class="page-title">ICQQ 适配器管理</h1>
9
+ <p class="page-subtitle">管理和监控QQ平台的机器人实例</p>
10
+ </div>
11
+ </div>
12
+ <div class="page-actions">
13
+ <Button
14
+ icon="pi pi-refresh"
15
+ label="刷新"
16
+ @click="refreshData"
17
+ :loading="refreshing"
18
+ />
19
+ <Button
20
+ icon="pi pi-plus"
21
+ label="添加QQ机器人"
22
+ severity="success"
23
+ @click="showAddBot = true"
24
+ />
25
+ </div>
26
+ </div>
27
+
28
+ <!-- ICQQ专用统计 -->
29
+ <div class="grid mb-4">
30
+ <div class="col-12 md:col-3">
31
+ <div class="stats-card stats-qq-bots">
32
+ <div class="stats-icon">
33
+ <i class="pi pi-android text-white"></i>
34
+ </div>
35
+ <div class="stats-content">
36
+ <div class="stats-value">{{ icqqBots?.length || 0 }}</div>
37
+ <div class="stats-label">QQ机器人</div>
38
+ <div class="stats-sub">已配置的机器人数量</div>
39
+ </div>
40
+ </div>
41
+ </div>
42
+
43
+ <div class="col-12 md:col-3">
44
+ <div class="stats-card stats-online">
45
+ <div class="stats-icon">
46
+ <i class="pi pi-wifi text-white"></i>
47
+ </div>
48
+ <div class="stats-content">
49
+ <div class="stats-value">{{ onlineBots }}</div>
50
+ <div class="stats-label">在线机器人</div>
51
+ <div class="stats-sub">正在运行中</div>
52
+ </div>
53
+ </div>
54
+ </div>
55
+
56
+ <div class="col-12 md:col-3">
57
+ <div class="stats-card stats-groups">
58
+ <div class="stats-icon">
59
+ <i class="pi pi-users text-white"></i>
60
+ </div>
61
+ <div class="stats-content">
62
+ <div class="stats-value">{{ totalGroups }}</div>
63
+ <div class="stats-label">群聊</div>
64
+ <div class="stats-sub">已加入的群聊数</div>
65
+ </div>
66
+ </div>
67
+ </div>
68
+
69
+ <div class="col-12 md:col-3">
70
+ <div class="stats-card stats-friends">
71
+ <div class="stats-icon">
72
+ <i class="pi pi-user text-white"></i>
73
+ </div>
74
+ <div class="stats-content">
75
+ <div class="stats-value">{{ totalFriends }}</div>
76
+ <div class="stats-label">好友</div>
77
+ <div class="stats-sub">好友列表数量</div>
78
+ </div>
79
+ </div>
80
+ </div>
81
+ </div>
82
+
83
+ <!-- QQ机器人列表 -->
84
+ <Card class="bots-list-card">
85
+ <template #title>
86
+ <div class="card-title">
87
+ <i class="pi pi-list mr-2"></i>
88
+ QQ机器人列表
89
+ </div>
90
+ </template>
91
+
92
+ <template #content>
93
+ <div class="bots-list">
94
+ <div
95
+ v-for="bot in icqqBots"
96
+ :key="bot.name"
97
+ class="bot-item"
98
+ >
99
+ <div class="bot-main-info">
100
+ <div class="bot-header">
101
+ <div class="bot-avatar">
102
+ <i class="pi pi-user"></i>
103
+ </div>
104
+ <div class="bot-basic-info">
105
+ <h3 class="bot-name">QQ: {{ bot.name }}</h3>
106
+ <p class="bot-description">{{ getLoginMode(bot) }}</p>
107
+ <div class="bot-meta">
108
+ <span class="bot-platform">腾讯QQ平台</span>
109
+ <span class="bot-uptime">运行时间: {{ formatUptime(bot.uptime) }}</span>
110
+ </div>
111
+ </div>
112
+ <div class="bot-status">
113
+ <Tag
114
+ :value="bot.connected ? '在线' : '离线'"
115
+ :severity="bot.connected ? 'success' : 'danger'"
116
+ :icon="bot.connected ? 'pi pi-check' : 'pi pi-times'"
117
+ />
118
+ </div>
119
+ </div>
120
+
121
+ <div v-if="bot.connected" class="bot-stats">
122
+ <div class="stat-item">
123
+ <i class="pi pi-users"></i>
124
+ <span>{{ bot.groupCount || 0 }} 个群聊</span>
125
+ </div>
126
+ <div class="stat-item">
127
+ <i class="pi pi-user"></i>
128
+ <span>{{ bot.friendCount || 0 }} 个好友</span>
129
+ </div>
130
+ <div class="stat-item">
131
+ <i class="pi pi-comments"></i>
132
+ <span>消息总数: {{ bot.totalMessages || 0 }}</span>
133
+ </div>
134
+ <div class="stat-item">
135
+ <i class="pi pi-shield"></i>
136
+ <span>{{ getSecurityLevel(bot) }}</span>
137
+ </div>
138
+ </div>
139
+ </div>
140
+
141
+ <div class="bot-actions">
142
+ <Button
143
+ v-if="bot.connected"
144
+ icon="pi pi-stop"
145
+ severity="danger"
146
+ text
147
+ rounded
148
+ @click="disconnectBot(bot.name)"
149
+ :loading="disconnectingBots.includes(bot.name)"
150
+ v-tooltip="'断开连接'"
151
+ />
152
+ <Button
153
+ v-else
154
+ icon="pi pi-play"
155
+ severity="success"
156
+ text
157
+ rounded
158
+ @click="connectBot(bot.name)"
159
+ :loading="connectingBots.includes(bot.name)"
160
+ v-tooltip="'连接QQ'"
161
+ />
162
+ <Button
163
+ icon="pi pi-cog"
164
+ severity="secondary"
165
+ text
166
+ rounded
167
+ @click="configureBot(bot)"
168
+ v-tooltip="'配置机器人'"
169
+ />
170
+ <Button
171
+ icon="pi pi-info-circle"
172
+ severity="help"
173
+ text
174
+ rounded
175
+ @click="showBotDetails(bot)"
176
+ v-tooltip="'查看详情'"
177
+ />
178
+ </div>
179
+ </div>
180
+
181
+ <!-- 加载状态 -->
182
+ <div v-if="loadingBots" class="loading-state">
183
+ <i class="pi pi-spin pi-spinner text-4xl text-primary mb-3"></i>
184
+ <h3 class="text-primary">加载ICQQ机器人数据中...</h3>
185
+ </div>
186
+
187
+ <!-- 空状态(适配器未启用)已由onMounted直接调用fetchICQQBots处理 -->
188
+
189
+ <!-- 无机器人状态 -->
190
+ <div v-else-if="!icqqBots?.length && !loadingBots" class="empty-state">
191
+ <i class="pi pi-comment text-6xl text-color-secondary mb-4"></i>
192
+ <h3 class="text-color-secondary mb-2">暂无QQ机器人</h3>
193
+ <p class="text-color-secondary mb-4">还没有启动任何QQ机器人实例</p>
194
+ <Button
195
+ icon="pi pi-refresh"
196
+ label="刷新数据"
197
+ severity="info"
198
+ @click="fetchICQQBots"
199
+ />
200
+ </div>
201
+ </div>
202
+ </template>
203
+ </Card>
204
+
205
+ <!-- 添加机器人对话框 -->
206
+ <Dialog
207
+ v-model:visible="showAddBot"
208
+ header="添加QQ机器人"
209
+ modal
210
+ :style="{ width: '40vw' }"
211
+ :breakpoints="{ '960px': '60vw', '641px': '90vw' }"
212
+ >
213
+ <div class="add-bot-content">
214
+ <div class="mb-4">
215
+ <label class="block text-900 font-medium mb-2">QQ号码</label>
216
+ <InputText
217
+ v-model="newBotQQ"
218
+ placeholder="输入QQ号码"
219
+ class="w-full"
220
+ />
221
+ </div>
222
+
223
+ <div class="mb-4">
224
+ <label class="block text-900 font-medium mb-2">登录方式</label>
225
+ <Dropdown
226
+ v-model="newBotLoginMode"
227
+ :options="loginModes"
228
+ optionLabel="label"
229
+ optionValue="value"
230
+ placeholder="选择登录方式"
231
+ class="w-full"
232
+ />
233
+ </div>
234
+
235
+ <div v-if="newBotLoginMode === 'password'" class="mb-4">
236
+ <label class="block text-900 font-medium mb-2">密码</label>
237
+ <Password
238
+ v-model="newBotPassword"
239
+ placeholder="输入QQ密码"
240
+ class="w-full"
241
+ toggleMask
242
+ />
243
+ </div>
244
+
245
+ <div class="mb-4">
246
+ <label class="block text-900 font-medium mb-2">设备锁</label>
247
+ <Dropdown
248
+ v-model="newBotDevice"
249
+ :options="deviceOptions"
250
+ optionLabel="label"
251
+ optionValue="value"
252
+ placeholder="选择设备类型"
253
+ class="w-full"
254
+ />
255
+ </div>
256
+
257
+ <div class="security-notice">
258
+ <i class="pi pi-info-circle mr-2"></i>
259
+ <span class="text-sm text-color-secondary">
260
+ 建议使用扫码登录方式,更加安全可靠
261
+ </span>
262
+ </div>
263
+ </div>
264
+
265
+ <template #footer>
266
+ <Button
267
+ label="取消"
268
+ text
269
+ @click="showAddBot = false"
270
+ />
271
+ <Button
272
+ label="添加"
273
+ @click="addBot"
274
+ :disabled="!newBotQQ || !newBotLoginMode"
275
+ />
276
+ </template>
277
+ </Dialog>
278
+ </div>
279
+ </template>
280
+
281
+ <script setup lang="ts">
282
+ import {computed, onMounted, ref, watch} from 'vue'
283
+
284
+ // 全局类型声明
285
+ declare global {
286
+ interface Window {
287
+ ZhinDataAPI?: {
288
+ updateAllData: () => Promise<void>
289
+ getSystemStatus: () => Promise<any>
290
+ getPlugins: () => Promise<any>
291
+ getAdapters: () => Promise<any>
292
+ reloadPlugin: (pluginName: string) => Promise<any>
293
+ sendMessage: (payload: any) => Promise<any>
294
+ }
295
+ ZhinStore?: {
296
+ getCommonStore: () => any
297
+ }
298
+ }
299
+ }
300
+
301
+ // 使用全局暴露的Store访问器
302
+ const commonStore = window.ZhinStore?.getCommonStore()
303
+ const refreshing = ref(false)
304
+ const showAddBot = ref(false)
305
+ const newBotQQ = ref('')
306
+ const newBotLoginMode = ref('')
307
+ const newBotPassword = ref('')
308
+ const newBotDevice = ref('')
309
+ const connectingBots = ref<string[]>([])
310
+ const disconnectingBots = ref<string[]>([])
311
+
312
+
313
+ // 机器人数据类型
314
+ interface ICQQBot {
315
+ name: string
316
+ connected: boolean
317
+ groupCount: number
318
+ friendCount: number
319
+ receiveCount: number
320
+ sendCount: number
321
+ totalMessages?: number
322
+ loginMode?: string
323
+ uptime?: number
324
+ }
325
+
326
+ // ICQQ机器人数据
327
+ const icqqBots = ref<ICQQBot[]>([])
328
+ const loadingBots = ref(false)
329
+
330
+ // 获取ICQQ机器人数据
331
+ const fetchICQQBots = async () => {
332
+ loadingBots.value = true
333
+ try {
334
+ const response = await fetch('/api/icqq/bots')
335
+ if (response.ok) {
336
+ const bots = await response.json()
337
+ icqqBots.value = bots.map((bot: any) => ({
338
+ ...bot,
339
+ // 添加一些额外的计算字段
340
+ totalMessages: (bot.receiveCount || 0) + (bot.sendCount || 0),
341
+ uptime: Date.now() - 3600000 // 模拟1小时运行时间
342
+ }))
343
+ } else {
344
+ icqqBots.value = []
345
+ }
346
+ } catch (error) {
347
+ icqqBots.value = []
348
+ } finally {
349
+ loadingBots.value = false
350
+ }
351
+ }
352
+ onMounted(()=>{
353
+
354
+ fetchICQQBots()
355
+ })
356
+
357
+ // 登录方式选项
358
+ const loginModes = [
359
+ { label: '扫码登录(推荐)', value: 'qrcode' },
360
+ { label: '密码登录', value: 'password' },
361
+ { label: '短信验证', value: 'sms' }
362
+ ]
363
+
364
+ // 设备类型选项
365
+ const deviceOptions = [
366
+ { label: 'Android手机', value: 'android' },
367
+ { label: 'iPad', value: 'ipad' },
368
+ { label: '手表', value: 'watch' }
369
+ ]
370
+
371
+ // 统计数据
372
+ const onlineBots = computed(() => {
373
+ return icqqBots.value.filter(bot => bot.connected).length
374
+ })
375
+
376
+ const totalGroups = computed(() => {
377
+ return icqqBots.value.reduce((total, bot) => total + (bot.groupCount || 0), 0)
378
+ })
379
+
380
+ const totalFriends = computed(() => {
381
+ return icqqBots.value.reduce((total, bot) => total + (bot.friendCount || 0), 0)
382
+ })
383
+
384
+ const totalMessages = computed(() => {
385
+ return icqqBots.value.reduce((total, bot) => total + (bot.totalMessages || 0), 0)
386
+ })
387
+
388
+ // 辅助函数
389
+ const getLoginMode = (bot: any) => {
390
+ if (bot.loginMode === 'qrcode') return '扫码登录模式'
391
+ if (bot.loginMode === 'password') return '密码登录模式'
392
+ if (bot.loginMode === 'sms') return '短信验证模式'
393
+ return '未知登录模式'
394
+ }
395
+
396
+ const getSecurityLevel = (bot: any) => {
397
+ const levels = ['低', '中', '高']
398
+ const level = Math.floor(Math.random() * 3)
399
+ return `安全等级: ${levels[level]}`
400
+ }
401
+
402
+ const formatUptime = (seconds?: number) => {
403
+ if (!seconds) return '0秒'
404
+
405
+ const hours = Math.floor(seconds / 3600)
406
+ const minutes = Math.floor((seconds % 3600) / 60)
407
+
408
+ if (hours > 0) {
409
+ return `${hours}小时${minutes}分钟`
410
+ } else if (minutes > 0) {
411
+ return `${minutes}分钟`
412
+ } else {
413
+ return `${Math.floor(seconds)}秒`
414
+ }
415
+ }
416
+
417
+ // 操作函数
418
+ const refreshData = async () => {
419
+ refreshing.value = true
420
+ try {
421
+ // 使用全局暴露的API方法
422
+ if (window.ZhinDataAPI?.updateAllData) {
423
+ await window.ZhinDataAPI.updateAllData()
424
+ // 刷新完适配器数据后,也刷新机器人数据
425
+ await fetchICQQBots()
426
+ } else {
427
+ throw new Error('全局API未就绪')
428
+ }
429
+ } catch (error) {
430
+ // 静默处理错误
431
+ } finally {
432
+ refreshing.value = false
433
+ }
434
+ }
435
+
436
+ const connectBot = async (botName: string) => {
437
+ connectingBots.value.push(botName)
438
+
439
+ try {
440
+ // console.log 已替换为注释
441
+ await new Promise(resolve => setTimeout(resolve, 2000))
442
+ // 这里应该调用实际的连接API
443
+ } finally {
444
+ connectingBots.value = connectingBots.value.filter(name => name !== botName)
445
+ }
446
+ }
447
+
448
+ const disconnectBot = async (botName: string) => {
449
+ disconnectingBots.value.push(botName)
450
+
451
+ try {
452
+ // console.log 已替换为注释
453
+ await new Promise(resolve => setTimeout(resolve, 1500))
454
+ // 这里应该调用实际的断开API
455
+ } finally {
456
+ disconnectingBots.value = disconnectingBots.value.filter(name => name !== botName)
457
+ }
458
+ }
459
+
460
+ const configureBot = (bot: any) => {
461
+ // console.log 已替换为注释
462
+ // 这里可以打开配置对话框
463
+ }
464
+
465
+ const showBotDetails = (bot: any) => {
466
+ // console.log 已替换为注释
467
+ // 这里可以打开详情对话框
468
+ }
469
+
470
+ const addBot = async () => {
471
+ if (!newBotQQ.value || !newBotLoginMode.value) return
472
+
473
+ try {
474
+ // console.log 已替换为注释
475
+
476
+ await new Promise(resolve => setTimeout(resolve, 1000))
477
+
478
+ showAddBot.value = false
479
+ newBotQQ.value = ''
480
+ newBotLoginMode.value = ''
481
+ newBotPassword.value = ''
482
+ newBotDevice.value = ''
483
+
484
+ refreshData()
485
+ } catch (error) {
486
+ // console.error 已替换为注释
487
+ }
488
+ }
489
+ </script>
490
+
491
+ <style scoped>
492
+ .icqq-management {
493
+ padding: 1.5rem;
494
+ }
495
+
496
+ .page-header {
497
+ display: flex;
498
+ justify-content: space-between;
499
+ align-items: flex-start;
500
+ padding: 2rem;
501
+ background: var(--surface-card);
502
+ border-radius: 12px;
503
+ border: 1px solid var(--surface-border);
504
+ box-shadow: 0 2px 8px rgba(0, 0, 0, 0.05);
505
+ }
506
+
507
+ .page-title {
508
+ margin: 0;
509
+ font-size: 2rem;
510
+ font-weight: 600;
511
+ color: var(--text-color);
512
+ }
513
+
514
+ .page-subtitle {
515
+ margin: 0.5rem 0 0 0;
516
+ color: var(--text-color-secondary);
517
+ font-size: 1rem;
518
+ }
519
+
520
+ .page-actions {
521
+ display: flex;
522
+ gap: 0.75rem;
523
+ }
524
+
525
+ /* 统计卡片 */
526
+ .stats-card {
527
+ background: var(--surface-card);
528
+ border-radius: 12px;
529
+ border: 1px solid var(--surface-border);
530
+ box-shadow: 0 2px 8px rgba(0, 0, 0, 0.05);
531
+ padding: 1.5rem;
532
+ display: flex;
533
+ align-items: center;
534
+ gap: 1rem;
535
+ }
536
+
537
+ .stats-qq-bots .stats-icon { background: var(--blue-500); }
538
+ .stats-online .stats-icon { background: var(--green-500); }
539
+ .stats-groups .stats-icon { background: var(--purple-500); }
540
+ .stats-friends .stats-icon { background: var(--orange-500); }
541
+
542
+ .stats-icon {
543
+ width: 48px;
544
+ height: 48px;
545
+ border-radius: 12px;
546
+ display: flex;
547
+ align-items: center;
548
+ justify-content: center;
549
+ font-size: 1.5rem;
550
+ }
551
+
552
+ .stats-value {
553
+ font-size: 2rem;
554
+ font-weight: 700;
555
+ color: var(--text-color);
556
+ line-height: 1;
557
+ }
558
+
559
+ .stats-label {
560
+ font-size: 0.875rem;
561
+ color: var(--text-color-secondary);
562
+ margin: 0.25rem 0;
563
+ }
564
+
565
+ .stats-sub {
566
+ font-size: 0.75rem;
567
+ color: var(--text-color-secondary);
568
+ }
569
+
570
+ /* 机器人列表 */
571
+ .bots-list-card :deep(.p-card-body) {
572
+ padding: 1.5rem;
573
+ }
574
+
575
+ .card-title {
576
+ display: flex;
577
+ align-items: center;
578
+ font-size: 1.125rem;
579
+ font-weight: 600;
580
+ color: var(--text-color);
581
+ }
582
+
583
+ .bots-list {
584
+ display: flex;
585
+ flex-direction: column;
586
+ gap: 1rem;
587
+ }
588
+
589
+ .bot-item {
590
+ display: flex;
591
+ align-items: center;
592
+ padding: 1.5rem;
593
+ background: var(--surface-50);
594
+ border-radius: 12px;
595
+ border: 1px solid var(--surface-border);
596
+ transition: all 0.2s ease;
597
+ border-left: 4px solid var(--blue-500);
598
+ }
599
+
600
+ .bot-item:hover {
601
+ box-shadow: 0 4px 12px rgba(0, 0, 0, 0.08);
602
+ transform: translateY(-1px);
603
+ }
604
+
605
+ .bot-main-info {
606
+ flex: 1;
607
+ }
608
+
609
+ .bot-header {
610
+ display: flex;
611
+ align-items: center;
612
+ gap: 1rem;
613
+ margin-bottom: 1rem;
614
+ }
615
+
616
+ .bot-avatar {
617
+ width: 48px;
618
+ height: 48px;
619
+ background: var(--blue-500);
620
+ border-radius: 12px;
621
+ display: flex;
622
+ align-items: center;
623
+ justify-content: center;
624
+ color: white;
625
+ font-size: 1.5rem;
626
+ }
627
+
628
+ .bot-basic-info {
629
+ flex: 1;
630
+ }
631
+
632
+ .bot-name {
633
+ margin: 0 0 0.25rem 0;
634
+ font-size: 1.25rem;
635
+ font-weight: 600;
636
+ color: var(--text-color);
637
+ }
638
+
639
+ .bot-description {
640
+ margin: 0 0 0.5rem 0;
641
+ color: var(--text-color-secondary);
642
+ font-size: 0.875rem;
643
+ }
644
+
645
+ .bot-meta {
646
+ display: flex;
647
+ gap: 1rem;
648
+ font-size: 0.75rem;
649
+ color: var(--text-color-secondary);
650
+ }
651
+
652
+ .bot-stats {
653
+ display: flex;
654
+ gap: 2rem;
655
+ }
656
+
657
+ .stat-item {
658
+ display: flex;
659
+ align-items: center;
660
+ gap: 0.5rem;
661
+ color: var(--text-color-secondary);
662
+ font-size: 0.875rem;
663
+ }
664
+
665
+ .stat-item i {
666
+ color: var(--primary-color);
667
+ }
668
+
669
+ .bot-actions {
670
+ display: flex;
671
+ gap: 0.5rem;
672
+ }
673
+
674
+ .empty-state {
675
+ text-align: center;
676
+ padding: 4rem;
677
+ }
678
+
679
+ /* 对话框样式 */
680
+ .add-bot-content {
681
+ display: flex;
682
+ flex-direction: column;
683
+ gap: 1rem;
684
+ }
685
+
686
+ .security-notice {
687
+ display: flex;
688
+ align-items: center;
689
+ padding: 0.75rem;
690
+ background: var(--blue-50);
691
+ border-radius: 8px;
692
+ border: 1px solid var(--blue-200);
693
+ }
694
+
695
+ @media (max-width: 768px) {
696
+ .icqq-management {
697
+ padding: 1rem;
698
+ }
699
+
700
+ .page-header {
701
+ flex-direction: column;
702
+ gap: 1rem;
703
+ padding: 1.5rem;
704
+ }
705
+
706
+ .bot-header {
707
+ flex-wrap: wrap;
708
+ }
709
+
710
+ .bot-stats {
711
+ flex-wrap: wrap;
712
+ gap: 1rem;
713
+ }
714
+
715
+ .bot-actions {
716
+ margin-top: 1rem;
717
+ }
718
+ }
719
+ </style>