mirror of
https://github.com/Wei-Shaw/sub2api.git
synced 2026-10-07 17:27:58 +08:00
fix(mobile): 修复全站排查发现的移动端布局问题
全站排查各页面移动端表现,修复以下确认会溢出/挤压的布局: - 分组用户倍率/RPM 上限弹窗:7 列宽表滚动容器补横向滚动(min-w-max) - 设置页联盟自定义用户表:容器 overflow-hidden 改为 overflow-x-auto - 模型/分组/端点分布、消费排行、用户仪表盘 5 处甜甜圈图例表: 移动端纵向堆叠(flex-col sm:flex-row),图例表全宽并支持横向滚动 - 安装向导:步骤指示器移动端只显示序号圆点(hidden sm:inline)避免 硬溢出,表单两列栅格移动端折叠为单列 - 设置页:支付商品名三列、Beta 策略与帮助图片两列输入行移动端折叠 - 分组创建/编辑弹窗:三个 time 输入的三列行移动端折叠(时间控件有 固有宽度,窄屏弹窗内会硬溢出) - 密钥页分组选择浮层:min-w-[380px] 改为视口限宽 + JS 夹取 left, 对齐 AccountGroupsCell 的定位收敛做法 桌面端布局均保持不变。 Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
@@ -102,8 +102,8 @@
|
||||
|
||||
<div v-else>
|
||||
<div class="overflow-hidden rounded-lg border border-gray-200 dark:border-dark-600">
|
||||
<div class="max-h-[420px] overflow-y-auto">
|
||||
<table class="w-full text-sm">
|
||||
<div class="max-h-[420px] overflow-auto">
|
||||
<table class="w-full min-w-max text-sm">
|
||||
<thead class="sticky top-0 z-[1]">
|
||||
<tr class="border-b border-gray-200 bg-gray-50 dark:border-dark-600 dark:bg-dark-700">
|
||||
<th class="px-3 py-2 text-left text-xs font-medium text-gray-500 dark:text-gray-400">{{ t('admin.groups.columns.userEmail') }}</th>
|
||||
|
||||
@@ -126,8 +126,8 @@
|
||||
<div v-else>
|
||||
<!-- 表格 -->
|
||||
<div class="overflow-hidden rounded-lg border border-gray-200 dark:border-dark-600">
|
||||
<div class="max-h-[420px] overflow-y-auto">
|
||||
<table class="w-full text-sm">
|
||||
<div class="max-h-[420px] overflow-auto">
|
||||
<table class="w-full min-w-max text-sm">
|
||||
<thead class="sticky top-0 z-[1]">
|
||||
<tr class="border-b border-gray-200 bg-gray-50 dark:border-dark-600 dark:bg-dark-700">
|
||||
<th class="px-3 py-2 text-left text-xs font-medium text-gray-500 dark:text-gray-400">{{ t('admin.groups.columns.userEmail') }}</th>
|
||||
|
||||
@@ -71,11 +71,11 @@
|
||||
<div v-if="loading" class="flex h-48 items-center justify-center">
|
||||
<LoadingSpinner />
|
||||
</div>
|
||||
<div v-else-if="displayEndpointStats.length > 0 && chartData" class="flex items-center gap-6">
|
||||
<div class="h-48 w-48">
|
||||
<div v-else-if="displayEndpointStats.length > 0 && chartData" class="flex flex-col items-center gap-4 sm:flex-row sm:gap-6">
|
||||
<div class="h-48 w-48 shrink-0">
|
||||
<Doughnut :data="chartData" :options="doughnutOptions" />
|
||||
</div>
|
||||
<div class="max-h-48 flex-1 overflow-y-auto">
|
||||
<div class="max-h-48 w-full min-w-0 flex-1 overflow-auto">
|
||||
<table class="w-full text-xs">
|
||||
<thead>
|
||||
<tr class="text-gray-500 dark:text-gray-400">
|
||||
|
||||
@@ -33,11 +33,11 @@
|
||||
<div v-if="loading" class="flex h-48 items-center justify-center">
|
||||
<LoadingSpinner />
|
||||
</div>
|
||||
<div v-else-if="displayGroupStats.length > 0 && chartData" class="flex items-center gap-6">
|
||||
<div class="h-48 w-48">
|
||||
<div v-else-if="displayGroupStats.length > 0 && chartData" class="flex flex-col items-center gap-4 sm:flex-row sm:gap-6">
|
||||
<div class="h-48 w-48 shrink-0">
|
||||
<Doughnut :data="chartData" :options="doughnutOptions" />
|
||||
</div>
|
||||
<div class="max-h-48 flex-1 overflow-y-auto">
|
||||
<div class="max-h-48 w-full min-w-0 flex-1 overflow-auto">
|
||||
<table class="w-full text-xs">
|
||||
<thead>
|
||||
<tr class="text-gray-500 dark:text-gray-400">
|
||||
|
||||
@@ -101,12 +101,12 @@
|
||||
</div>
|
||||
<div
|
||||
v-else-if="activeView === 'model_distribution' && displayModelStats.length > 0 && chartData"
|
||||
class="flex items-center gap-6"
|
||||
class="flex flex-col items-center gap-4 sm:flex-row sm:gap-6"
|
||||
>
|
||||
<div class="h-48 w-48">
|
||||
<div class="h-48 w-48 shrink-0">
|
||||
<Doughnut :data="chartData" :options="doughnutOptions" />
|
||||
</div>
|
||||
<div class="max-h-48 flex-1 overflow-y-auto">
|
||||
<div class="max-h-48 w-full min-w-0 flex-1 overflow-auto">
|
||||
<table class="w-full text-xs">
|
||||
<thead>
|
||||
<tr class="text-gray-500 dark:text-gray-400">
|
||||
@@ -182,11 +182,11 @@
|
||||
>
|
||||
{{ t('admin.dashboard.failedToLoad') }}
|
||||
</div>
|
||||
<div v-else-if="rankingDisplayItems.length > 0 && rankingChartData" class="flex items-center gap-6">
|
||||
<div class="h-48 w-48">
|
||||
<div v-else-if="rankingDisplayItems.length > 0 && rankingChartData" class="flex flex-col items-center gap-4 sm:flex-row sm:gap-6">
|
||||
<div class="h-48 w-48 shrink-0">
|
||||
<Doughnut :data="rankingChartData" :options="rankingDoughnutOptions" />
|
||||
</div>
|
||||
<div class="max-h-48 flex-1 overflow-y-auto">
|
||||
<div class="max-h-48 w-full min-w-0 flex-1 overflow-auto">
|
||||
<table class="w-full text-xs">
|
||||
<thead>
|
||||
<tr class="text-gray-500 dark:text-gray-400">
|
||||
|
||||
@@ -27,12 +27,12 @@
|
||||
<LoadingSpinner size="md" />
|
||||
</div>
|
||||
<h3 class="mb-4 text-sm font-semibold text-gray-900 dark:text-white">{{ t('dashboard.modelDistribution') }}</h3>
|
||||
<div class="flex items-center gap-6">
|
||||
<div class="h-48 w-48">
|
||||
<div class="flex flex-col items-center gap-4 sm:flex-row sm:gap-6">
|
||||
<div class="h-48 w-48 shrink-0">
|
||||
<Doughnut v-if="modelData" :data="modelData" :options="doughnutOptions" />
|
||||
<div v-else class="flex h-full items-center justify-center text-sm text-gray-500 dark:text-gray-400">{{ t('dashboard.noDataAvailable') }}</div>
|
||||
</div>
|
||||
<div class="max-h-48 flex-1 overflow-y-auto">
|
||||
<div class="max-h-48 w-full min-w-0 flex-1 overflow-auto">
|
||||
<table class="w-full text-xs">
|
||||
<thead>
|
||||
<tr class="text-gray-500 dark:text-gray-400">
|
||||
|
||||
@@ -1101,7 +1101,7 @@
|
||||
</div>
|
||||
<div
|
||||
v-if="createForm.peak_rate_enabled"
|
||||
class="mb-4 grid grid-cols-3 gap-3"
|
||||
class="mb-4 grid grid-cols-1 gap-3 sm:grid-cols-3"
|
||||
>
|
||||
<div>
|
||||
<label class="input-label">{{ t("admin.groups.peakRate.peakStart") }}</label>
|
||||
@@ -2615,7 +2615,7 @@
|
||||
</div>
|
||||
<div
|
||||
v-if="editForm.peak_rate_enabled"
|
||||
class="mb-4 grid grid-cols-3 gap-3"
|
||||
class="mb-4 grid grid-cols-1 gap-3 sm:grid-cols-3"
|
||||
>
|
||||
<div>
|
||||
<label class="input-label">{{ t("admin.groups.peakRate.peakStart") }}</label>
|
||||
|
||||
@@ -834,7 +834,7 @@
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<div class="grid grid-cols-2 gap-4">
|
||||
<div class="grid grid-cols-1 gap-4 sm:grid-cols-2">
|
||||
<!-- Action -->
|
||||
<div>
|
||||
<label
|
||||
@@ -6344,7 +6344,7 @@
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div class="overflow-hidden rounded-lg border border-gray-200 dark:border-dark-700">
|
||||
<div class="overflow-x-auto rounded-lg border border-gray-200 dark:border-dark-700">
|
||||
<table class="min-w-full divide-y divide-gray-200 dark:divide-dark-700">
|
||||
<thead class="bg-gray-50 dark:bg-dark-800">
|
||||
<tr>
|
||||
@@ -6664,7 +6664,7 @@
|
||||
</div>
|
||||
<template v-if="form.payment_enabled">
|
||||
<!-- Row 1: Product name -->
|
||||
<div class="grid grid-cols-3 gap-3">
|
||||
<div class="grid grid-cols-1 gap-3 sm:grid-cols-3">
|
||||
<div>
|
||||
<label class="input-label">{{
|
||||
t("admin.settings.payment.productNamePrefix")
|
||||
@@ -7083,7 +7083,7 @@
|
||||
</p>
|
||||
</div>
|
||||
<!-- Row 5: Help image + text -->
|
||||
<div class="grid grid-cols-2 gap-3">
|
||||
<div class="grid grid-cols-1 gap-3 sm:grid-cols-2">
|
||||
<div>
|
||||
<label class="input-label">{{
|
||||
t("admin.settings.payment.helpImage")
|
||||
|
||||
@@ -38,7 +38,7 @@
|
||||
<span v-else>{{ index + 1 }}</span>
|
||||
</div>
|
||||
<span
|
||||
class="ml-2 text-sm font-medium"
|
||||
class="ml-2 hidden text-sm font-medium sm:inline"
|
||||
:class="
|
||||
currentStep >= index
|
||||
? 'text-gray-900 dark:text-white'
|
||||
@@ -50,7 +50,7 @@
|
||||
</div>
|
||||
<div
|
||||
v-if="index < steps.length - 1"
|
||||
class="mx-3 h-0.5 w-12"
|
||||
class="mx-2 h-0.5 w-6 sm:mx-3 sm:w-12"
|
||||
:class="currentStep > index ? 'bg-primary-500' : 'bg-gray-200 dark:bg-dark-700'"
|
||||
></div>
|
||||
</template>
|
||||
@@ -70,7 +70,7 @@
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div class="grid grid-cols-2 gap-4">
|
||||
<div class="grid grid-cols-1 gap-4 sm:grid-cols-2">
|
||||
<div>
|
||||
<label class="input-label">{{ t('setup.database.host') }}</label>
|
||||
<input
|
||||
@@ -103,7 +103,7 @@
|
||||
<Toggle v-model="formData.redis.enable_tls" />
|
||||
</div>
|
||||
|
||||
<div class="grid grid-cols-2 gap-4">
|
||||
<div class="grid grid-cols-1 gap-4 sm:grid-cols-2">
|
||||
<div>
|
||||
<label class="input-label">{{ t('setup.database.username') }}</label>
|
||||
<input
|
||||
@@ -124,7 +124,7 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="grid grid-cols-2 gap-4">
|
||||
<div class="grid grid-cols-1 gap-4 sm:grid-cols-2">
|
||||
<div>
|
||||
<label class="input-label">{{ t('setup.database.databaseName') }}</label>
|
||||
<input
|
||||
@@ -195,7 +195,7 @@
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div class="grid grid-cols-2 gap-4">
|
||||
<div class="grid grid-cols-1 gap-4 sm:grid-cols-2">
|
||||
<div>
|
||||
<label class="input-label">{{ t('setup.redis.host') }}</label>
|
||||
<input
|
||||
@@ -216,7 +216,7 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="grid grid-cols-2 gap-4">
|
||||
<div class="grid grid-cols-1 gap-4 sm:grid-cols-2">
|
||||
<div>
|
||||
<label class="input-label">{{ t('setup.redis.password') }}</label>
|
||||
<input
|
||||
|
||||
@@ -1050,7 +1050,7 @@
|
||||
<div
|
||||
v-if="groupSelectorKeyId !== null && dropdownPosition"
|
||||
ref="dropdownRef"
|
||||
class="animate-in fade-in slide-in-from-top-2 fixed z-[100000020] w-max min-w-[380px] overflow-hidden rounded-xl bg-white shadow-lg ring-1 ring-black/5 duration-200 dark:bg-dark-800 dark:ring-white/10"
|
||||
class="animate-in fade-in slide-in-from-top-2 fixed z-[100000020] w-max max-w-[calc(100vw-16px)] overflow-hidden rounded-xl bg-white shadow-lg ring-1 ring-black/5 duration-200 sm:min-w-[380px] dark:bg-dark-800 dark:ring-white/10"
|
||||
style="pointer-events: auto !important;"
|
||||
:style="{
|
||||
top: dropdownPosition.top !== undefined ? dropdownPosition.top + 'px' : undefined,
|
||||
@@ -1605,20 +1605,23 @@ const openGroupSelector = (key: ApiKey) => {
|
||||
if (buttonEl) {
|
||||
const rect = buttonEl.getBoundingClientRect()
|
||||
const dropdownEstHeight = 400 // estimated max dropdown height
|
||||
const dropdownEstWidth = Math.min(380, window.innerWidth - 16)
|
||||
const spaceBelow = window.innerHeight - rect.bottom
|
||||
const spaceAbove = rect.top
|
||||
// 夹取 left,避免窄屏下浮层超出视口右缘
|
||||
const left = Math.max(8, Math.min(rect.left, window.innerWidth - dropdownEstWidth - 8))
|
||||
|
||||
if (spaceBelow < dropdownEstHeight && spaceAbove > spaceBelow) {
|
||||
// Not enough space below, pop upward
|
||||
dropdownPosition.value = {
|
||||
bottom: window.innerHeight - rect.top + 4,
|
||||
left: rect.left
|
||||
left
|
||||
}
|
||||
} else {
|
||||
// Default: pop downward
|
||||
dropdownPosition.value = {
|
||||
top: rect.bottom + 4,
|
||||
left: rect.left
|
||||
left
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user