fix: 列表页卡片与表格的切换点抬到 lg,补上用户管理的移动端视图

手机上"溢出屏幕"的两个真因(375 / 769 / 1100 三档实测):

- 卡片↔表格切换卡在 md(768px),而侧栏也在 768 出现,吃掉 224px 加 48px 内边距后
  内容区只剩 495px,渠道表自然宽 1097px —— 横屏手机、小平板、分屏(768~1023)
  整张表被塞进 495px 的盒子里横向滚。改为 lg(1024px) 起才用表格;769px 实测
  可见表格数为 0、docSW 等于视口宽。
- 用户管理页只有 720px 的表格、没有卡片回退,手机上只能横向滑整张表。
  补移动端卡片(用户名/邮箱、角色与状态、ID/余额/注册时间、编辑与调余额),
  表格改 hidden lg:block;1100px 实测 client=826 / scroll=826,铺满不滚。
- 顺带修掉上次改动留下的挤列问题:渠道表"近N小时"列被表格自动布局压到 95px
  (min-w 只约束整表、不保证单列),四个指标折成 4 行。该列加 min-w-[190px]、
  指标拆两行,并把 Key 并入名称行、启用并入健康行,8 列减到 6 列
  (colspan 同步 7→6),自然宽 1125 → 1053。

编辑渠道弹窗 375px 下无溢出元素。遗留:渠道表在 1024~1325 视口仍需容器内横向滚动
(6 列数据表的固有行为,不动整页),卡片列表在 640~1023 仍是单列。
This commit is contained in:
Sakurasan committed 2026-10-19 09:25:52 +08:00
1 parent c39c3e7da4
commit 75c8ecd519
5 files changed
+73 -28

No files matched your search

+21 -21
View File
@@ -219,7 +219,7 @@ onMounted(load)
</div>
<!-- 移动端:卡片列表 -->
<div class="space-y-3 md:hidden">
<div class="space-y-3 lg:hidden">
<div v-for="{ ch, st } in rows" :key="ch.id" class="card p-4" :class="ch.enabled ? 'bg-accent-soft' : ''">
<div class="flex flex-wrap items-start justify-between gap-2">
<div class="min-w-0">
@@ -291,30 +291,29 @@ onMounted(load)
</p>
</div>
<!-- 桌面端:表格 -->
<div class="card hidden md:block">
<!-- 桌面端:表格(lg 起才显示:md 断点下侧栏一出现内容区只剩 ~495px,表格会被挤成横向滚动) -->
<div class="card hidden lg:block">
<div class="overflow-x-auto">
<table class="w-full text-sm min-w-[920px]">
<table class="w-full text-sm min-w-[860px]">
<thead>
<tr class="border-b border-edge text-left text-xs text-muted">
<th scope="col" class="px-4 py-2.5 font-medium">名称</th>
<th scope="col" class="px-4 py-2.5 font-medium">名称 / Key</th>
<th scope="col" class="px-4 py-2.5 font-medium">API 格式</th>
<th scope="col" class="px-4 py-2.5 font-medium">Base URL</th>
<th scope="col" class="px-4 py-2.5 font-medium">Key</th>
<th scope="col" class="px-4 py-2.5 font-medium">健康</th>
<th scope="col" class="px-4 py-2.5 font-medium">启用</th>
<th scope="col" class="px-4 py-2.5 font-medium">近{{ statWindows.find((w) => w.v === statWindow)?.label }}</th>
<th scope="col" class="px-4 py-2.5 font-medium">健康 / 状态</th>
<th scope="col" class="min-w-[190px] px-4 py-2.5 font-medium">近{{ statWindows.find((w) => w.v === statWindow)?.label }}</th>
<th scope="col" class="px-4 py-2.5" />
</tr>
</thead>
<tbody>
<template v-for="{ ch, st } in rows" :key="ch.id">
<tr class="table-row" :style="ch.enabled ? { borderLeft: '2px solid var(--color-accent)' } : {}">
<td class="px-4 py-2.5">
<button class="inline-flex items-center gap-1.5 text-ink transition hover:text-accent" @click="toggleDrawer(ch)">
<td class="max-w-[240px] px-4 py-2.5">
<button class="flex items-center gap-1.5 text-left text-ink transition hover:text-accent" @click="toggleDrawer(ch)">
<span class="truncate">{{ ch.name }}</span>
<PhCaretDown :size="12" class="shrink-0 text-muted transition-transform" :class="expandedId === ch.id ? 'rotate-180' : ''" />
</button>
<p class="mt-0.5 truncate font-mono text-[11px] text-muted">{{ ch.api_key_masked || '****' }}</p>
</td>
<td class="px-4 py-2.5">
<div class="flex flex-col gap-0.5">
@@ -325,26 +324,27 @@ onMounted(load)
>{{ protocolShort(f) }}</code>
</div>
</td>
<td class="max-w-[220px] truncate px-4 py-2.5 font-mono text-xs text-muted">{{ ch.base_url }}</td>
<td class="px-4 py-2.5 font-mono text-xs text-muted">{{ ch.api_key_masked || '****' }}</td>
<td class="max-w-[200px] truncate px-4 py-2.5 font-mono text-xs text-muted">{{ ch.base_url }}</td>
<td class="px-4 py-2.5">
<Badge :variant="ch.health_status === 'healthy' ? 'ok' : ch.health_status === 'cooldown' ? 'err' : 'warn'">
{{ ch.health_status }}
</Badge>
<div class="flex flex-wrap items-center gap-1">
<Badge :variant="ch.health_status === 'healthy' ? 'ok' : ch.health_status === 'cooldown' ? 'err' : 'warn'">
{{ ch.health_status }}
</Badge>
<Badge :variant="ch.enabled ? 'neutral' : 'err'">{{ ch.enabled ? '启用' : '停用' }}</Badge>
</div>
<div
v-if="runtimeHint(ch)"
class="mt-1 font-mono text-[11px]"
:class="ch.cooldown_seconds > 0 ? 'text-err' : 'text-warn'"
>{{ runtimeHint(ch) }}</div>
</td>
<td class="px-4 py-2.5 text-xs text-muted">{{ ch.enabled ? '是' : '否' }}</td>
<td class="px-4 py-2.5">
<div v-if="st" class="flex flex-col gap-0.5 text-[11px] leading-4 text-muted">
<span class="mono-num">
{{ st.requests }} 次 ·
<span :class="st.err_rate > 0.05 ? 'text-err' : ''">成功 {{ successRate(st) }}</span> ·
P95 {{ fmtMs(st.p95_ms, st.success > 0) }} · {{ fmtCost(st.cost) }}
<span :class="st.err_rate > 0.05 ? 'text-err' : ''">成功 {{ successRate(st) }}</span>
</span>
<span class="mono-num">P95 {{ fmtMs(st.p95_ms, st.success > 0) }} · {{ fmtCost(st.cost) }}</span>
<span v-if="st.top_errors.length" class="font-mono text-[10px] text-err">
{{ st.top_errors.map((e) => `${e.code}×${e.count}`).join(' ') }}
</span>
@@ -378,13 +378,13 @@ onMounted(load)
</td>
</tr>
<tr v-if="expandedId === ch.id" class="bg-surface/40">
<td colspan="7" class="px-4 py-3">
<td colspan="6" class="px-4 py-3">
<ChannelModelsDrawer :channel="ch" />
</td>
</tr>
</template>
<tr v-if="channels.length === 0">
<td colspan="7" class="px-4 py-10 text-center text-sm text-muted">还没有渠道,点击「添加渠道」</td>
<td colspan="6" class="px-4 py-10 text-center text-sm text-muted">还没有渠道,点击「添加渠道」</td>
</tr>
</tbody>
</table>