-
{{ t('admin.accounts.bulkActions.selected', { count: selectedIds.length }) }}
+
+
+
+ {{ t('admin.accounts.bulkActions.selected', { count: selectedIds.length }) }}
+
+
+ •
+
+
+
+
@@ -10,5 +29,5 @@
\ No newline at end of file
diff --git a/frontend/src/composables/useTableLoader.ts b/frontend/src/composables/useTableLoader.ts
index 01703ee1..5fb6c5e0 100644
--- a/frontend/src/composables/useTableLoader.ts
+++ b/frontend/src/composables/useTableLoader.ts
@@ -43,7 +43,8 @@ export function useTableLoader
>(options: TableL
if (abortController) {
abortController.abort()
}
- abortController = new AbortController()
+ const currentController = new AbortController()
+ abortController = currentController
loading.value = true
try {
@@ -51,9 +52,9 @@ export function useTableLoader>(options: TableL
pagination.page,
pagination.page_size,
toRaw(params) as P,
- { signal: abortController.signal }
+ { signal: currentController.signal }
)
-
+
items.value = response.items || []
pagination.total = response.total || 0
pagination.pages = response.pages || 0
@@ -63,7 +64,7 @@ export function useTableLoader>(options: TableL
throw error
}
} finally {
- if (abortController && !abortController.signal.aborted) {
+ if (abortController === currentController) {
loading.value = false
}
}
@@ -77,7 +78,9 @@ export function useTableLoader>(options: TableL
const debouncedReload = useDebounceFn(reload, debounceMs)
const handlePageChange = (page: number) => {
- pagination.page = page
+ // 确保页码在有效范围内
+ const validPage = Math.max(1, Math.min(page, pagination.pages || 1))
+ pagination.page = validPage
load()
}
diff --git a/frontend/src/i18n/locales/en.ts b/frontend/src/i18n/locales/en.ts
index d04a48aa..ca220281 100644
--- a/frontend/src/i18n/locales/en.ts
+++ b/frontend/src/i18n/locales/en.ts
@@ -1085,12 +1085,16 @@ export default {
tokenRefreshed: 'Token refreshed successfully',
accountDeleted: 'Account deleted successfully',
rateLimitCleared: 'Rate limit cleared successfully',
+ bulkSchedulableEnabled: 'Successfully enabled scheduling for {count} account(s)',
+ bulkSchedulableDisabled: 'Successfully disabled scheduling for {count} account(s)',
bulkActions: {
selected: '{count} account(s) selected',
selectCurrentPage: 'Select this page',
clear: 'Clear selection',
edit: 'Bulk Edit',
- delete: 'Bulk Delete'
+ delete: 'Bulk Delete',
+ enableScheduling: 'Enable Scheduling',
+ disableScheduling: 'Disable Scheduling'
},
bulkEdit: {
title: 'Bulk Edit Accounts',
diff --git a/frontend/src/i18n/locales/zh.ts b/frontend/src/i18n/locales/zh.ts
index d9f885da..6749c02e 100644
--- a/frontend/src/i18n/locales/zh.ts
+++ b/frontend/src/i18n/locales/zh.ts
@@ -1221,12 +1221,16 @@ export default {
accountCreatedSuccess: '账号添加成功',
accountUpdatedSuccess: '账号更新成功',
accountDeletedSuccess: '账号删除成功',
+ bulkSchedulableEnabled: '成功启用 {count} 个账号的调度',
+ bulkSchedulableDisabled: '成功停止 {count} 个账号的调度',
bulkActions: {
selected: '已选择 {count} 个账号',
selectCurrentPage: '本页全选',
clear: '清除选择',
edit: '批量编辑账号',
- delete: '批量删除'
+ delete: '批量删除',
+ enableScheduling: '批量启用调度',
+ disableScheduling: '批量停止调度'
},
bulkEdit: {
title: '批量编辑账号',
diff --git a/frontend/src/views/admin/AccountsView.vue b/frontend/src/views/admin/AccountsView.vue
index 0ca22a76..79c6072c 100644
--- a/frontend/src/views/admin/AccountsView.vue
+++ b/frontend/src/views/admin/AccountsView.vue
@@ -7,7 +7,7 @@
v-model:searchQuery="params.search"
:filters="params"
@update:filters="(newFilters) => Object.assign(params, newFilters)"
- @change="reload"
+ @change="debouncedReload"
@update:searchQuery="debouncedReload"
/>
-
+
@@ -107,7 +107,7 @@
-
+
@@ -175,7 +175,7 @@ const statsAcc = ref(null)
const togglingSchedulable = ref(null)
const menu = reactive<{show:boolean, acc:Account|null, pos:{top:number, left:number}|null}>({ show: false, acc: null, pos: null })
-const { items: accounts, loading, params, pagination, load, reload, debouncedReload, handlePageChange } = useTableLoader({
+const { items: accounts, loading, params, pagination, load, reload, debouncedReload, handlePageChange, handlePageSizeChange } = useTableLoader({
fetchFn: adminAPI.accounts.list,
initialParams: { platform: '', type: '', status: '', search: '' }
})
@@ -209,6 +209,21 @@ const openMenu = (a: Account, e: MouseEvent) => { menu.acc = a; menu.pos = { top
const toggleSel = (id: number) => { const i = selIds.value.indexOf(id); if(i === -1) selIds.value.push(id); else selIds.value.splice(i, 1) }
const selectPage = () => { selIds.value = [...new Set([...selIds.value, ...accounts.value.map(a => a.id)])] }
const handleBulkDelete = async () => { if(!confirm(t('common.confirm'))) return; try { await Promise.all(selIds.value.map(id => adminAPI.accounts.delete(id))); selIds.value = []; reload() } catch (error) { console.error('Failed to bulk delete accounts:', error) } }
+const handleBulkToggleSchedulable = async (schedulable: boolean) => {
+ const count = selIds.value.length
+ try {
+ const result = await adminAPI.accounts.bulkUpdate(selIds.value, { schedulable });
+ const message = schedulable
+ ? t('admin.accounts.bulkSchedulableEnabled', { count: result.success || count })
+ : t('admin.accounts.bulkSchedulableDisabled', { count: result.success || count });
+ appStore.showSuccess(message);
+ selIds.value = [];
+ reload()
+ } catch (error) {
+ console.error('Failed to bulk toggle schedulable:', error);
+ appStore.showError(t('common.error'))
+ }
+}
const handleBulkUpdated = () => { showBulkEdit.value = false; selIds.value = []; reload() }
const closeTestModal = () => { showTest.value = false; testingAcc.value = null }
const closeStatsModal = () => { showStats.value = false; statsAcc.value = null }
diff --git a/frontend/src/views/admin/GroupsView.vue b/frontend/src/views/admin/GroupsView.vue
index f7ef2339..d8322154 100644
--- a/frontend/src/views/admin/GroupsView.vue
+++ b/frontend/src/views/admin/GroupsView.vue
@@ -16,6 +16,7 @@
type="text"
:placeholder="t('admin.groups.searchGroups')"
class="input pl-10"
+ @input="handleSearch"
/>