fix: merge general improvements from release branch

Backend:
- gateway_handler: pass subject.UserID instead of int64(0) for user-level routing
- setting_handler: add missing BalanceLowNotifyRechargeURL to UpdateSettings response
- openai_gateway_service: use applyAccountStatsCost for account stats pricing integration
- embed_on: add local file override (data/public/) for embedded frontend assets

Frontend:
- useTableSelection: add batchUpdate method for batch operations
- AccountsView: virtual scrolling params, Set-based isSelected, swipe virtualization
- ProxiesView: add batchUpdate to selection and swipe-select
- BulkEditAccountModal: fix submit handler to prevent event object as argument
- SettingsView: move payload construction outside try block
- i18n: add general translation keys (saved, deleted, view, validation, allowUserRefund)
- api/client: reorder error fields for consistency
- stores/payment: clarify pollOrderStatus JSDoc
This commit is contained in:
erio
2026-04-14 19:29:37 +08:00
parent c14d739360
commit 63f539b382
13 changed files with 114 additions and 36 deletions

View File

@@ -144,6 +144,7 @@
<AccountBulkActionsBar :selected-ids="selIds" @delete="handleBulkDelete" @reset-status="handleBulkResetStatus" @refresh-token="handleBulkRefreshToken" @edit="showBulkEdit = true" @clear="clearSelection" @select-page="selectPage" @toggle-schedulable="handleBulkToggleSchedulable" />
<div ref="accountTableRef" class="flex min-h-0 flex-1 flex-col overflow-hidden">
<DataTable
ref="dataTableRef"
:columns="cols"
:data="accounts"
:loading="loading"
@@ -153,6 +154,8 @@
default-sort-key="name"
default-sort-order="asc"
:sort-storage-key="ACCOUNT_SORT_STORAGE_KEY"
:estimate-row-height="72"
:overscan="5"
>
<template #header-select>
<input
@@ -164,7 +167,7 @@
/>
</template>
<template #cell-select="{ row }">
<input type="checkbox" :checked="selIds.includes(row.id)" @change="toggleSel(row.id)" class="rounded border-gray-300 text-primary-600 focus:ring-primary-500" />
<input type="checkbox" :checked="isSelected(row.id)" @change="toggleSel(row.id)" class="rounded border-gray-300 text-primary-600 focus:ring-primary-500" />
</template>
<template #cell-name="{ row, value }">
<div class="flex flex-col">
@@ -197,7 +200,9 @@
<AccountCapacityCell :account="row" />
</template>
<template #cell-status="{ row }">
<AccountStatusIndicator :account="row" @show-temp-unsched="handleShowTempUnsched" />
<div class="flex items-center gap-1.5">
<AccountStatusIndicator :account="row" @show-temp-unsched="handleShowTempUnsched" />
</div>
</template>
<template #cell-schedulable="{ row }">
<button @click="handleToggleSchedulable(row)" :disabled="togglingSchedulable === row.id" class="relative inline-flex h-5 w-9 flex-shrink-0 cursor-pointer rounded-full border-2 border-transparent transition-colors duration-200 ease-in-out focus:outline-none focus:ring-2 focus:ring-primary-500 focus:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50 dark:focus:ring-offset-dark-800" :class="[row.schedulable ? 'bg-primary-500 hover:bg-primary-600' : 'bg-gray-200 hover:bg-gray-300 dark:bg-dark-600 dark:hover:bg-dark-500']" :title="row.schedulable ? t('admin.accounts.schedulableEnabled') : t('admin.accounts.schedulableDisabled')">
@@ -313,7 +318,7 @@ import { useAppStore } from '@/stores/app'
import { useAuthStore } from '@/stores/auth'
import { adminAPI } from '@/api/admin'
import { useTableLoader } from '@/composables/useTableLoader'
import { useSwipeSelect } from '@/composables/useSwipeSelect'
import { useSwipeSelect, type SwipeSelectVirtualContext } from '@/composables/useSwipeSelect'
import { useTableSelection } from '@/composables/useTableSelection'
import AppLayout from '@/components/layout/AppLayout.vue'
import TablePageLayout from '@/components/layout/TablePageLayout.vue'
@@ -351,6 +356,7 @@ const authStore = useAuthStore()
const proxies = ref<AccountProxy[]>([])
const groups = ref<AdminGroup[]>([])
const accountTableRef = ref<HTMLElement | null>(null)
const dataTableRef = ref<InstanceType<typeof DataTable> | null>(null)
const selPlatforms = computed<AccountPlatform[]>(() => {
const platforms = new Set(
accounts.value
@@ -650,17 +656,25 @@ const {
clear: clearSelection,
removeMany: removeSelectedAccounts,
toggleVisible,
selectVisible: selectPage
selectVisible: selectPage,
batchUpdate
} = useTableSelection<Account>({
rows: accounts,
getId: (account) => account.id
})
const swipeVirtualContext: SwipeSelectVirtualContext = {
getVirtualizer: () => dataTableRef.value?.virtualizer ?? null,
getSortedData: () => dataTableRef.value?.sortedData ?? accounts.value,
getRowId: (row: any) => row.id,
}
useSwipeSelect(accountTableRef, {
isSelected,
select,
deselect
})
deselect,
batchUpdate
}, swipeVirtualContext)
const resetAutoRefreshCache = () => {
autoRefreshETag.value = null

View File

@@ -985,7 +985,8 @@ const {
deselect,
clear: clearSelectedProxies,
removeMany: removeSelectedProxies,
toggleVisible
toggleVisible,
batchUpdate
} = useTableSelection<Proxy>({
rows: proxies,
getId: (proxy) => proxy.id
@@ -993,7 +994,8 @@ const {
useSwipeSelect(proxyTableRef, {
isSelected,
select,
deselect
deselect,
batchUpdate
})
const accountsProxy = ref<Proxy | null>(null)
const proxyAccounts = ref<ProxyAccountSummary[]>([])

View File

@@ -4116,12 +4116,13 @@ async function handleToggleField(provider: ProviderInstance, field: 'enabled' |
if (field === 'enabled') newValue = !provider.enabled
else if (field === 'refund_enabled') newValue = !provider.refund_enabled
else newValue = !provider.allow_user_refund
const payload: Record<string, boolean> = { [field]: newValue }
// Cascade: turning off refund_enabled also turns off allow_user_refund
if (field === 'refund_enabled' && !newValue) {
payload.allow_user_refund = false
}
try {
const payload: Record<string, boolean> = { [field]: newValue }
// Cascade: turning off refund_enabled also disables allow_user_refund
if (field === 'refund_enabled' && !newValue) {
payload.allow_user_refund = false
}
await adminAPI.payment.updateProvider(provider.id, payload)
if (field === 'enabled') provider.enabled = newValue
else if (field === 'refund_enabled') {