Skip to content

Commit e12312f

Browse files
committed
优化管理面板性能并发布 1.0.4.1
1 parent 47c67e2 commit e12312f

11 files changed

Lines changed: 267 additions & 143 deletions

File tree

nonebot_plugin_admin/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -146,4 +146,4 @@ async def _():
146146

147147
__help_plugin_name__ = "简易群管"
148148
__permission__ = 1
149-
__help__version__ = "1.0.4"
149+
__help__version__ = "1.0.4.1"

nonebot_plugin_admin/admin-web/src/App.vue

Lines changed: 18 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -45,48 +45,34 @@
4545
<div v-if="showMobileNav && !isDesktop" class="admin-sidebar-backdrop" @click="showMobileNav = false"></div>
4646

4747
<main class="admin-app-main" :class="{ 'is-sidebar-collapsed': desktopSidebarCollapsed }">
48-
<DashboardPage
49-
v-show="currentPage === 'dashboard'"
48+
<KeepAlive>
49+
<component
50+
:is="currentPageComponent"
51+
:key="currentPage"
5052
:api-base="settings.apiBase"
5153
:token="settings.token"
52-
:active="currentPage === 'dashboard'"
54+
:active="true"
5355
:refresh-key="refreshKey"
5456
@notify="notify"
5557
@connection="updateConnection"
56-
/>
57-
<LogsPage
58-
v-show="currentPage === 'logs'"
59-
:api-base="settings.apiBase"
60-
:token="settings.token"
61-
:active="currentPage === 'logs'"
62-
:refresh-key="refreshKey"
63-
@notify="notify"
64-
@connection="updateConnection"
65-
/>
66-
<WorkspacePage
67-
v-show="currentPage === 'workspace'"
68-
:api-base="settings.apiBase"
69-
:token="settings.token"
70-
:active="currentPage === 'workspace'"
71-
:refresh-key="refreshKey"
72-
@notify="notify"
73-
@connection="updateConnection"
74-
/>
58+
/>
59+
</KeepAlive>
7560
</main>
7661
</div>
7762
</div>
7863
</template>
7964

8065
<script setup>
81-
import { computed, onBeforeUnmount, onMounted, reactive, ref, watch } from 'vue'
66+
import { computed, defineAsyncComponent, onBeforeUnmount, onMounted, reactive, ref, watch } from 'vue'
8267
import AppHeader from './components/common/AppHeader.vue'
8368
import ConfigDialog from './components/common/ConfigDialog.vue'
84-
import DashboardPage from './pages/DashboardPage.vue'
85-
import LogsPage from './pages/LogsPage.vue'
86-
import WorkspacePage from './pages/WorkspacePage.vue'
8769
import LoginPage from './pages/LoginPage.vue'
8870
import { DEFAULT_API_BASE, DEFAULT_DASHBOARD_TITLE, DASHBOARD_BOOTSTRAP, apiRequest, extractErrorMessage, normalizeApiBase } from './lib/api'
8971
72+
const DashboardPage = defineAsyncComponent(() => import('./pages/DashboardPage.vue'))
73+
const LogsPage = defineAsyncComponent(() => import('./pages/LogsPage.vue'))
74+
const WorkspacePage = defineAsyncComponent(() => import('./pages/WorkspacePage.vue'))
75+
9076
const STORAGE_KEYS = {
9177
apiBase: 'nb-admin-web:api-base',
9278
token: 'nb-admin-web:session-token',
@@ -104,6 +90,12 @@ const pages = [
10490
{ key: 'workspace', label: '操作台', icon: 'forum' }
10591
]
10692
93+
const currentPageComponent = computed(() => ({
94+
dashboard: DashboardPage,
95+
logs: LogsPage,
96+
workspace: WorkspacePage
97+
}[currentPage.value] || DashboardPage))
98+
10799
function restoreSessionToken() {
108100
try {
109101
var ts = window.sessionStorage.getItem(STORAGE_KEYS.tokenTs)

nonebot_plugin_admin/admin-web/src/pages/DashboardPage.vue

Lines changed: 8 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@
3333
</div>
3434
<div class="admin-bot-stat">
3535
<span class="admin-bot-stat-value">{{ formatNumber(manageableGroupCount) }}</span>
36-
<span class="admin-bot-stat-label">可管理群</span>
36+
<span class="admin-bot-stat-label">已记录群</span>
3737
</div>
3838
<div class="admin-bot-stat">
3939
<span class="admin-bot-stat-value">{{ formatNumber(account.client_count) }}</span>
@@ -154,7 +154,6 @@ const overview = reactive({
154154
daily_trend: [],
155155
generated_at: null
156156
})
157-
const operations = reactive({ manageable_group_count: 0 })
158157
const account = reactive({ available: false, online: false, nickname: '', self_id: '', friend_count: 0, client_count: 0 })
159158
const contacts = reactive({ items: [] })
160159
const logsOverview = reactive({ runtime_log_enabled: false, runtime_log_file_path: null, plugin_error_total: 0, runtime_log_total: 0, total: 0 })
@@ -168,10 +167,7 @@ const todayMessageTotal = computed(() => {
168167
const sum = sumBy(realGroups.value, 'today_message_count')
169168
return sum || Number(overview.today_message_count || 0)
170169
})
171-
const manageableGroupCount = computed(() => {
172-
const raw = Number(operations.manageable_group_count || 0)
173-
return visibleGroupCount.value ? Math.min(raw, visibleGroupCount.value) : raw
174-
})
170+
const manageableGroupCount = computed(() => Number(overview.record_enabled_groups || 0))
175171
const recentContacts = computed(() => (contacts.items || []).filter((item) => !isPlaceholderGroupName(item.title || item.peer_name || item.peerName || '')).slice(0, 8))
176172
const trendItems = computed(() => (overview.daily_trend || []).slice(-7))
177173
@@ -246,8 +242,8 @@ const manageOption = computed(() => ({
246242
avoidLabelOverlap: false,
247243
label: { formatter: '{b}\n{c}' },
248244
data: [
249-
{ name: '可管理', value: Math.max(0, manageableGroupCount.value) },
250-
{ name: '其他', value: Math.max(0, visibleGroupCount.value - manageableGroupCount.value) }
245+
{ name: '已记录', value: Math.max(0, manageableGroupCount.value) },
246+
{ name: '未记录', value: Math.max(0, visibleGroupCount.value - manageableGroupCount.value) }
251247
]
252248
}
253249
]
@@ -256,21 +252,18 @@ const manageOption = computed(() => ({
256252
async function loadData() {
257253
loading.value = true
258254
try {
259-
const [overviewPayload, operationsPayload, accountPayload, contactsPayload, logsPayload, groupsPayload] = await Promise.all([
260-
apiRequest(props.apiBase, props.token, '/overview'),
261-
apiRequest(props.apiBase, props.token, '/operations/overview'),
255+
const [overviewPayload, accountPayload, contactsPayload, logsPayload] = await Promise.all([
256+
apiRequest(props.apiBase, props.token, '/overview', { params: { compact: true } }),
262257
apiRequest(props.apiBase, props.token, '/account/overview'),
263258
apiRequest(props.apiBase, props.token, '/contacts/recent', { params: { count: 8 } }),
264-
apiRequest(props.apiBase, props.token, '/logs/overview'),
265-
apiRequest(props.apiBase, props.token, '/groups')
259+
apiRequest(props.apiBase, props.token, '/logs/overview')
266260
])
267261
268262
Object.assign(overview, overviewPayload || {})
269-
Object.assign(operations, operationsPayload || {})
270263
Object.assign(account, accountPayload || {})
271264
Object.assign(contacts, contactsPayload || {})
272265
Object.assign(logsOverview, logsPayload || {})
273-
groups.value = Array.isArray(groupsPayload?.items) ? groupsPayload.items : []
266+
groups.value = Array.isArray(overviewPayload?.groups) ? overviewPayload.groups : []
274267
emit('connection', '已连接')
275268
} catch (error) {
276269
emit('connection', '连接失败')

nonebot_plugin_admin/admin-web/src/pages/WorkspacePage.vue

Lines changed: 66 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -222,7 +222,7 @@
222222
</template>
223223

224224
<script setup>
225-
import { computed, nextTick, onBeforeUnmount, onMounted, reactive, ref, watch } from 'vue'
225+
import { computed, nextTick, onActivated, onBeforeUnmount, onDeactivated, onMounted, reactive, ref, watch } from 'vue'
226226
import GroupListPanel from '../components/workspace/GroupListPanel.vue'
227227
import ChatPanel from '../components/workspace/ChatPanel.vue'
228228
import MemberPanel from '../components/workspace/MemberPanel.vue'
@@ -234,7 +234,11 @@ import { apiRequest, extractErrorMessage } from '../lib/api'
234234
import { isPlaceholderGroupRecord, initials, parseMessageContent } from '../lib/format'
235235
236236
const POLL_INTERVAL = 5000
237+
const MEMBER_SEARCH_DELAY = 300
237238
let pollTimer = null
239+
let memberSearchTimer = null
240+
let workspaceRequestKey = 0
241+
let polling = false
238242
239243
const props = defineProps({
240244
apiBase: { type: String, required: true },
@@ -352,6 +356,10 @@ function resetWorkspace() {
352356
workspace.files = { files: [], folders: [] }
353357
}
354358
359+
function isCurrentWorkspace(groupId, requestKey) {
360+
return String(selectedGroupId.value) === String(groupId) && workspaceRequestKey === requestKey
361+
}
362+
355363
function messageKey(item) {
356364
return String(item?.id ?? item?.message_id ?? item?.real_id ?? item?.real_seq ?? Math.random())
357365
}
@@ -367,7 +375,7 @@ function dedupeMessages(items) {
367375
async function loadGroups(autoSelect = false) {
368376
groupLoading.value = true
369377
try {
370-
const payload = await apiRequest(props.apiBase, props.token, '/groups')
378+
const payload = await apiRequest(props.apiBase, props.token, '/groups', { params: { compact: true } })
371379
allGroups.value = Array.isArray(payload?.items) ? payload.items : []
372380
emit('connection', '已连接')
373381
@@ -392,13 +400,23 @@ async function loadGroups(autoSelect = false) {
392400
393401
async function handleSelectGroup(groupId) {
394402
if (!groupId) return
403+
clearTimeout(memberSearchTimer)
395404
selectedGroupId.value = String(groupId)
396405
memberSearch.value = ''
406+
tabLoaded.announcements = false
407+
tabLoaded.files = false
408+
tabLoaded.essence = false
409+
tabLoaded.honors = false
410+
tabLoading.announcements = false
411+
tabLoading.files = false
412+
tabLoading.essence = false
413+
tabLoading.honors = false
397414
closeMemberMenu()
398415
await loadWorkspace(groupId)
399416
}
400417
401418
async function loadWorkspace(groupId) {
419+
const requestKey = ++workspaceRequestKey
402420
workspaceLoading.value = true
403421
try {
404422
const [profilePayload, messagesPayload, membersPayload, detailPayload] = await Promise.all([
@@ -407,15 +425,18 @@ async function loadWorkspace(groupId) {
407425
apiRequest(props.apiBase, props.token, `/groups/${groupId}/members`, { params: { page: 1, page_size: 30 }, timeout: 30000 }).catch(() => ({ items: [], pagination: {} })),
408426
apiRequest(props.apiBase, props.token, `/groups/${groupId}/feature-switches`, { timeout: 30000 }).catch(() => [])
409427
])
428+
if (!isCurrentWorkspace(groupId, requestKey)) return
410429
workspace.group_profile = profilePayload || {}
411430
workspace.messages = messagesPayload || { items: [], pagination: {} }
412431
workspace.members = membersPayload || { items: [], pagination: {} }
413432
workspace.detail.feature_switches = Array.isArray(detailPayload) ? detailPayload : (detailPayload?.switches || [])
414433
workspace.bot_profile = { capabilities: {} }
415434
try {
416435
const botProfile = await apiRequest(props.apiBase, props.token, `/groups/${groupId}/bot-profile`, { timeout: 15000 })
436+
if (!isCurrentWorkspace(groupId, requestKey)) return
417437
workspace.bot_profile = botProfile || { capabilities: {} }
418438
} catch {}
439+
if (!isCurrentWorkspace(groupId, requestKey)) return
419440
workspace.announcements = { items: [] }
420441
workspace.files = { files: [], folders: [] }
421442
workspace.essence = { items: [] }
@@ -433,31 +454,37 @@ async function loadWorkspace(groupId) {
433454
const errorMsg = error?.isTimeout ? '加载超时,该群数据量较大,请稍后重试' : extractErrorMessage(error)
434455
emit('notify', { message: `群工作台加载失败:${errorMsg}`, type: 'error' })
435456
} finally {
436-
workspaceLoading.value = false
457+
if (isCurrentWorkspace(groupId, requestKey)) workspaceLoading.value = false
437458
}
438459
}
439460
440461
async function loadTabData(tabKey) {
441462
if (!selectedGroupId.value || tabLoaded[tabKey] || tabLoading[tabKey]) return
463+
const groupId = selectedGroupId.value
464+
const requestKey = workspaceRequestKey
442465
tabLoading[tabKey] = true
443466
await nextTick()
444467
window.mdui?.mutation?.()
445468
const startTime = Date.now()
446469
try {
447470
if (tabKey === 'announcements') {
448-
const data = await apiRequest(props.apiBase, props.token, `/groups/${selectedGroupId.value}/announcements`, { timeout: 30000 })
471+
const data = await apiRequest(props.apiBase, props.token, `/groups/${groupId}/announcements`, { timeout: 30000 })
472+
if (!isCurrentWorkspace(groupId, requestKey)) return
449473
workspace.announcements = data || { items: [] }
450474
tabLoaded.announcements = true
451475
} else if (tabKey === 'files') {
452-
const data = await apiRequest(props.apiBase, props.token, `/groups/${selectedGroupId.value}/files`, { timeout: 30000 })
476+
const data = await apiRequest(props.apiBase, props.token, `/groups/${groupId}/files`, { timeout: 30000 })
477+
if (!isCurrentWorkspace(groupId, requestKey)) return
453478
workspace.files = data || { files: [], folders: [] }
454479
tabLoaded.files = true
455480
} else if (tabKey === 'essence') {
456-
const data = await apiRequest(props.apiBase, props.token, `/groups/${selectedGroupId.value}/essence`, { timeout: 30000 })
481+
const data = await apiRequest(props.apiBase, props.token, `/groups/${groupId}/essence`, { timeout: 30000 })
482+
if (!isCurrentWorkspace(groupId, requestKey)) return
457483
workspace.essence = data || { items: [] }
458484
tabLoaded.essence = true
459485
} else if (tabKey === 'honors') {
460-
const data = await apiRequest(props.apiBase, props.token, `/groups/${selectedGroupId.value}/honors`, { timeout: 30000 })
486+
const data = await apiRequest(props.apiBase, props.token, `/groups/${groupId}/honors`, { timeout: 30000 })
487+
if (!isCurrentWorkspace(groupId, requestKey)) return
461488
workspace.honors = data || { sections: [] }
462489
tabLoaded.honors = true
463490
}
@@ -478,51 +505,62 @@ function handleTabChange(tabKey) {
478505
startPolling()
479506
} else {
480507
stopPolling()
481-
console.log('Tab change to:', tabKey, 'tabLoaded:', tabLoaded[tabKey], 'tabLoading:', tabLoading[tabKey])
482508
loadTabData(tabKey)
483509
}
484510
}
485511
486512
async function loadMembers(page = 1) {
487513
if (!selectedGroupId.value) return
514+
const groupId = selectedGroupId.value
515+
const requestKey = workspaceRequestKey
488516
memberLoading.value = true
489517
try {
490-
workspace.members = await apiRequest(props.apiBase, props.token, `/groups/${selectedGroupId.value}/members`, {
518+
const members = await apiRequest(props.apiBase, props.token, `/groups/${groupId}/members`, {
491519
params: {
492520
page,
493521
page_size: 30,
494522
keyword: memberSearch.value
495523
}
496524
})
525+
if (!isCurrentWorkspace(groupId, requestKey)) return
526+
workspace.members = members
497527
} catch (error) {
498528
emit('notify', { message: `成员加载失败:${extractErrorMessage(error)}`, type: 'error' })
499529
} finally {
500-
memberLoading.value = false
530+
if (isCurrentWorkspace(groupId, requestKey)) memberLoading.value = false
501531
}
502532
}
503533
504534
async function loadLatestMessages() {
505535
if (!selectedGroupId.value) return
536+
const groupId = selectedGroupId.value
537+
const requestKey = workspaceRequestKey
506538
messagesLoading.value = true
507539
try {
508-
workspace.messages = await apiRequest(props.apiBase, props.token, `/groups/${selectedGroupId.value}/messages`, {
540+
const messages = await apiRequest(props.apiBase, props.token, `/groups/${groupId}/messages`, {
509541
params: { limit: 60 }
510542
})
543+
if (!isCurrentWorkspace(groupId, requestKey)) return
544+
workspace.messages = messages
511545
} catch (error) {
512546
emit('notify', { message: `消息刷新失败:${extractErrorMessage(error)}`, type: 'error' })
513547
} finally {
514-
messagesLoading.value = false
548+
if (isCurrentWorkspace(groupId, requestKey)) messagesLoading.value = false
515549
}
516550
}
517551
518552
async function pollNewMessages() {
519-
if (!selectedGroupId.value || centerActiveTab.value !== 'chat') return
553+
if (polling || !selectedGroupId.value || centerActiveTab.value !== 'chat') return
554+
const groupId = selectedGroupId.value
555+
const requestKey = workspaceRequestKey
520556
const latestId = workspace.messages.pagination?.latest_id
521557
if (!latestId) return
558+
polling = true
522559
try {
523-
const result = await apiRequest(props.apiBase, props.token, `/groups/${selectedGroupId.value}/messages`, {
560+
const result = await apiRequest(props.apiBase, props.token, `/groups/${groupId}/messages`, {
524561
params: { limit: 60, after_id: latestId }
525562
})
563+
if (!isCurrentWorkspace(groupId, requestKey)) return
526564
const newItems = result?.items || []
527565
if (newItems.length > 0) {
528566
workspace.messages = {
@@ -531,7 +569,9 @@ async function pollNewMessages() {
531569
pagination: result.pagination || workspace.messages.pagination
532570
}
533571
}
534-
} catch {}
572+
} catch {} finally {
573+
polling = false
574+
}
535575
}
536576
537577
function startPolling() {
@@ -757,7 +797,8 @@ function handleGroupSearch(value) {
757797
758798
function handleMemberSearch(value) {
759799
memberSearch.value = value
760-
loadMembers(1)
800+
clearTimeout(memberSearchTimer)
801+
memberSearchTimer = setTimeout(() => loadMembers(1), MEMBER_SEARCH_DELAY)
761802
}
762803
763804
function changeGroupPage(offset) {
@@ -783,8 +824,17 @@ onMounted(() => {
783824
window.addEventListener('scroll', onGlobalPointer, true)
784825
})
785826
827+
onActivated(() => {
828+
if (selectedGroupId.value && centerActiveTab.value === 'chat') startPolling()
829+
})
830+
831+
onDeactivated(() => {
832+
stopPolling()
833+
})
834+
786835
onBeforeUnmount(() => {
787836
stopPolling()
837+
clearTimeout(memberSearchTimer)
788838
window.removeEventListener('click', onGlobalPointer)
789839
window.removeEventListener('resize', onGlobalPointer)
790840
window.removeEventListener('scroll', onGlobalPointer, true)

0 commit comments

Comments
 (0)