feat: merge dev
This commit is contained in:
@@ -61,6 +61,11 @@
|
||||
<template #cell-usage="{ row }">
|
||||
<AccountUsageCell :account="row" />
|
||||
</template>
|
||||
<template #cell-rate_multiplier="{ row }">
|
||||
<span class="text-sm font-mono text-gray-700 dark:text-gray-300">
|
||||
{{ (row.rate_multiplier ?? 1).toFixed(2) }}x
|
||||
</span>
|
||||
</template>
|
||||
<template #cell-priority="{ value }">
|
||||
<span class="text-sm text-gray-700 dark:text-gray-300">{{ value }}</span>
|
||||
</template>
|
||||
@@ -120,7 +125,7 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, reactive, computed, onMounted } from 'vue'
|
||||
import { ref, reactive, computed, onMounted, onUnmounted } from 'vue'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
import { useAppStore } from '@/stores/app'
|
||||
import { useAuthStore } from '@/stores/auth'
|
||||
@@ -190,10 +195,11 @@ const cols = computed(() => {
|
||||
if (!authStore.isSimpleMode) {
|
||||
c.push({ key: 'groups', label: t('admin.accounts.columns.groups'), sortable: false })
|
||||
}
|
||||
c.push(
|
||||
{ key: 'usage', label: t('admin.accounts.columns.usageWindows'), sortable: false },
|
||||
{ key: 'priority', label: t('admin.accounts.columns.priority'), sortable: true },
|
||||
{ key: 'last_used_at', label: t('admin.accounts.columns.lastUsed'), sortable: true },
|
||||
c.push(
|
||||
{ key: 'usage', label: t('admin.accounts.columns.usageWindows'), sortable: false },
|
||||
{ key: 'priority', label: t('admin.accounts.columns.priority'), sortable: true },
|
||||
{ key: 'rate_multiplier', label: t('admin.accounts.columns.billingRateMultiplier'), sortable: true },
|
||||
{ key: 'last_used_at', label: t('admin.accounts.columns.lastUsed'), sortable: true },
|
||||
{ key: 'expires_at', label: t('admin.accounts.columns.expiresAt'), sortable: true },
|
||||
{ key: 'notes', label: t('admin.accounts.columns.notes'), sortable: false },
|
||||
{ key: 'actions', label: t('admin.accounts.columns.actions'), sortable: false }
|
||||
@@ -202,7 +208,56 @@ const cols = computed(() => {
|
||||
})
|
||||
|
||||
const handleEdit = (a: Account) => { edAcc.value = a; showEdit.value = true }
|
||||
const openMenu = (a: Account, e: MouseEvent) => { menu.acc = a; menu.pos = { top: e.clientY, left: e.clientX - 200 }; menu.show = true }
|
||||
const openMenu = (a: Account, e: MouseEvent) => {
|
||||
menu.acc = a
|
||||
|
||||
const target = e.currentTarget as HTMLElement
|
||||
if (target) {
|
||||
const rect = target.getBoundingClientRect()
|
||||
const menuWidth = 200
|
||||
const menuHeight = 240
|
||||
const padding = 8
|
||||
const viewportWidth = window.innerWidth
|
||||
const viewportHeight = window.innerHeight
|
||||
|
||||
let left, top
|
||||
|
||||
if (viewportWidth < 768) {
|
||||
// 居中显示,水平位置
|
||||
left = Math.max(padding, Math.min(
|
||||
rect.left + rect.width / 2 - menuWidth / 2,
|
||||
viewportWidth - menuWidth - padding
|
||||
))
|
||||
|
||||
// 优先显示在按钮下方
|
||||
top = rect.bottom + 4
|
||||
|
||||
// 如果下方空间不够,显示在上方
|
||||
if (top + menuHeight > viewportHeight - padding) {
|
||||
top = rect.top - menuHeight - 4
|
||||
// 如果上方也不够,就贴在视口顶部
|
||||
if (top < padding) {
|
||||
top = padding
|
||||
}
|
||||
}
|
||||
} else {
|
||||
left = Math.max(padding, Math.min(
|
||||
e.clientX - menuWidth,
|
||||
viewportWidth - menuWidth - padding
|
||||
))
|
||||
top = e.clientY
|
||||
if (top + menuHeight > viewportHeight - padding) {
|
||||
top = viewportHeight - menuHeight - padding
|
||||
}
|
||||
}
|
||||
|
||||
menu.pos = { top, left }
|
||||
} else {
|
||||
menu.pos = { top: e.clientY, left: e.clientX - 200 }
|
||||
}
|
||||
|
||||
menu.show = true
|
||||
}
|
||||
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) } }
|
||||
@@ -360,5 +415,14 @@ const isExpired = (value: number | null) => {
|
||||
return value * 1000 <= Date.now()
|
||||
}
|
||||
|
||||
onMounted(async () => { load(); try { const [p, g] = await Promise.all([adminAPI.proxies.getAll(), adminAPI.groups.getAll()]); proxies.value = p; groups.value = g } catch (error) { console.error('Failed to load proxies/groups:', error) } })
|
||||
// 滚动时关闭菜单
|
||||
const handleScroll = () => {
|
||||
menu.show = false
|
||||
}
|
||||
|
||||
onMounted(async () => { load(); try { const [p, g] = await Promise.all([adminAPI.proxies.getAll(), adminAPI.groups.getAll()]); proxies.value = p; groups.value = g } catch (error) { console.error('Failed to load proxies/groups:', error) }; window.addEventListener('scroll', handleScroll, true) })
|
||||
|
||||
onUnmounted(() => {
|
||||
window.removeEventListener('scroll', handleScroll, true)
|
||||
})
|
||||
</script>
|
||||
|
||||
Reference in New Issue
Block a user