feat(admin): 刷新改为独立的圆形图标按钮(42px),主操作按钮单独一行

- 12 个后台页的刷新统一为 .refresh-btn,固定在页头最右,悬停旋转
- 渠道管理「添加渠道」、并发分组「新增分组」移到页头下方单独一行
- 渠道空状态文案同步

Generated with [Devin](https://devin.ai)

Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
This commit is contained in:
2026-10-27 16:59:57 +08:00
co-authored by Devin
parent 0fe1706ef9
commit af7e09cf21
14 changed files with 35 additions and 16 deletions
+17
View File
@@ -376,7 +376,24 @@ button:not(:disabled), [role="button"]:not([aria-disabled="true"]) { cursor: poi
.page-title { font-size: 20px; font-weight: 800; letter-spacing: -0.03em; color: var(--fg); } .page-title { font-size: 20px; font-weight: 800; letter-spacing: -0.03em; color: var(--fg); }
.page-sub { margin-top: 3px; font-size: 12.5px; color: var(--fg-3); line-height: 1.5; } .page-sub { margin-top: 3px; font-size: 12.5px; color: var(--fg-3); line-height: 1.5; }
.page-sub strong { color: var(--fg-2); font-weight: 700; } .page-sub strong { color: var(--fg-2); font-weight: 700; }
/* 刷新:独立的圆形图标按钮,固定在页头最右 */
.page-actions { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; } .page-actions { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; }
.refresh-btn {
flex-shrink: 0;
width: 42px; height: 42px;
display: inline-grid; place-items: center;
border-radius: 50%;
border: 1px solid rgb(24 24 27 / 0.12);
background: #fff;
color: var(--fg-2);
box-shadow: 0 1px 2px rgb(24 24 27 / 0.04), 0 6px 16px -8px rgb(24 24 27 / 0.15);
transition: color .15s ease, border-color .15s ease, box-shadow .15s ease;
}
.refresh-btn svg { transition: transform .5s cubic-bezier(.22,.61,.36,1); }
.refresh-btn:hover { color: var(--fg); border-color: rgb(24 24 27 / 0.3); box-shadow: 0 8px 20px -8px rgb(24 24 27 / 0.25); }
.refresh-btn:hover svg { transform: rotate(180deg); }
.refresh-btn:active svg { transform: rotate(360deg); transition-duration: .25s; }
.page-bar { display: flex; justify-content: flex-end; }
.toolbar { display: flex; align-items: center; flex-wrap: wrap; gap: 10px; padding: 10px 12px; } .toolbar { display: flex; align-items: center; flex-wrap: wrap; gap: 10px; padding: 10px 12px; }
.toolbar .field { height: 36px; padding-top: 0 !important; padding-bottom: 0 !important; font-size: 13px !important; } .toolbar .field { height: 36px; padding-top: 0 !important; padding-bottom: 0 !important; font-size: 13px !important; }
.toolbar input.field:not([type]), .toolbar input.field[type="text"], .toolbar input.field[type="search"] { .toolbar input.field:not([type]), .toolbar input.field[type="text"], .toolbar input.field[type="search"] {
+1 -1
View File
@@ -82,7 +82,7 @@ function goPage(n) {
<p class="page-sub">{{ total }} 笔充值订单</p> <p class="page-sub">{{ total }} 笔充值订单</p>
</div> </div>
<div class="page-actions"> <div class="page-actions">
<button @click="load" class="btn-soft"><Icon name="refresh" class="w-3.5 h-3.5" /> 刷新</button> <button @click="load" class="refresh-btn" title="刷新" aria-label="刷新"><Icon name="refresh" :size="18" /></button>
</div> </div>
</div> </div>
+1 -1
View File
@@ -57,7 +57,7 @@ function goPage(n) {
<p class="page-sub">{{ total }} 条触发记录 · 每次拦截记一条(违禁词 / 用户 / 时间 / 提示词)</p> <p class="page-sub">{{ total }} 条触发记录 · 每次拦截记一条(违禁词 / 用户 / 时间 / 提示词)</p>
</div> </div>
<div class="page-actions"> <div class="page-actions">
<button @click="load" class="btn-soft"><Icon name="refresh" class="w-3.5 h-3.5" /> 刷新</button> <button @click="load" class="refresh-btn" title="刷新" aria-label="刷新"><Icon name="refresh" :size="18" /></button>
</div> </div>
</div> </div>
+1 -1
View File
@@ -123,7 +123,7 @@ onMounted(load)
<p class="page-sub">提示词包含违禁词的生成请求(画图台 + API)会被<strong>直接拦截</strong>,并累计触发次数(见用户管理)。匹配不区分大小写。</p> <p class="page-sub">提示词包含违禁词的生成请求(画图台 + API)会被<strong>直接拦截</strong>,并累计触发次数(见用户管理)。匹配不区分大小写。</p>
</div> </div>
<div class="page-actions"> <div class="page-actions">
<button @click="load" class="btn-soft"><Icon name="refresh" class="w-3.5 h-3.5" /> 刷新</button> <button @click="load" class="refresh-btn" title="刷新" aria-label="刷新"><Icon name="refresh" :size="18" /></button>
</div> </div>
</div> </div>
+1 -1
View File
@@ -145,7 +145,7 @@ const pageNumbers = computed(() => {
<section class="space-y-4"> <section class="space-y-4">
<div class="page-head"> <div class="page-head">
<h2 class="page-title">兑换码管理</h2> <h2 class="page-title">兑换码管理</h2>
<button @click="load" class="btn-soft"><Icon name="refresh" class="w-3.5 h-3.5" /> 刷新</button> <button @click="load" class="refresh-btn" title="刷新" aria-label="刷新"><Icon name="refresh" :size="18" /></button>
</div> </div>
<!-- KPI strip — same shape as LogsView / InvitesAdminView --> <!-- KPI strip — same shape as LogsView / InvitesAdminView -->
<div class="grid grid-cols-2 md:grid-cols-4 gap-3"> <div class="grid grid-cols-2 md:grid-cols-4 gap-3">
+4 -3
View File
@@ -149,11 +149,12 @@ onMounted(load)
<p class="page-sub">同构 /v1 协议上游实例 · 保存时自动同步上游模型 · 余额实时探测</p> <p class="page-sub">同构 /v1 协议上游实例 · 保存时自动同步上游模型 · 余额实时探测</p>
</div> </div>
<div class="page-actions"> <div class="page-actions">
<button @click="load" class="btn-soft"><Icon name="refresh" class="w-3.5 h-3.5" /> 刷新</button> <button @click="load" class="refresh-btn" title="刷新" aria-label="刷新"><Icon name="refresh" :size="18" /></button>
<button @click="openAdd" class="btn-primary"><Icon name="plus" class="w-4 h-4" /> 添加渠道</button>
</div> </div>
</div> </div>
<div class="page-bar"><button @click="openAdd" class="btn-primary"><Icon name="plus" class="w-4 h-4" /> 添加渠道</button></div>
<p v-if="err" class="text-xs text-rose-300">{{ err }}</p> <p v-if="err" class="text-xs text-rose-300">{{ err }}</p>
<p v-if="warn" class="text-xs text-amber-300">{{ warn }}</p> <p v-if="warn" class="text-xs text-amber-300">{{ warn }}</p>
<p v-if="okMsg" class="text-xs text-emerald-300">{{ okMsg }}</p> <p v-if="okMsg" class="text-xs text-emerald-300">{{ okMsg }}</p>
@@ -204,7 +205,7 @@ onMounted(load)
</td> </td>
</tr> </tr>
<tr v-if="loading"><td colspan="11" class="py-10 text-center text-white/40 text-sm">正在加载渠道…</td></tr> <tr v-if="loading"><td colspan="11" class="py-10 text-center text-white/40 text-sm">正在加载渠道…</td></tr>
<tr v-else-if="!channels.length"><td colspan="11" class="py-10 text-center text-white/40 text-sm">没有渠道,点右上角「添加渠道」接入上游实例</td></tr> <tr v-else-if="!channels.length"><td colspan="11" class="py-10 text-center text-white/40 text-sm">没有渠道,点上方「添加渠道」接入上游实例</td></tr>
</tbody> </tbody>
</table> </table>
</div> </div>
+3 -2
View File
@@ -57,11 +57,12 @@ onMounted(load)
<p class="page-sub">每个分组限制成员用户的<strong>同时生成数</strong>(画图台 + API key 合计)。<strong>0 = 不限制</strong>。新用户自动进入「默认注册分组」。</p> <p class="page-sub">每个分组限制成员用户的<strong>同时生成数</strong>(画图台 + API key 合计)。<strong>0 = 不限制</strong>。新用户自动进入「默认注册分组」。</p>
</div> </div>
<div class="page-actions"> <div class="page-actions">
<button @click="load" class="btn-soft"><Icon name="refresh" class="w-3.5 h-3.5" /> 刷新</button> <button @click="load" class="refresh-btn" title="刷新" aria-label="刷新"><Icon name="refresh" :size="18" /></button>
<button @click="openNew" class="btn-primary shrink-0">+ 新增分组</button>
</div> </div>
</div> </div>
<div class="page-bar"><button @click="openNew" class="btn-primary shrink-0"><Icon name="plus" class="w-4 h-4" /> 新增分组</button></div>
<div class="card overflow-hidden"> <div class="card overflow-hidden">
<table class="w-full text-sm"> <table class="w-full text-sm">
<thead> <thead>
+1 -1
View File
@@ -212,7 +212,7 @@ onUnmounted(() => window.removeEventListener('keydown', onKey))
<h2 class="page-title">作品管理</h2> <h2 class="page-title">作品管理</h2>
</div> </div>
<div class="page-actions"> <div class="page-actions">
<button @click="load" class="btn-soft"><Icon name="refresh" class="w-3.5 h-3.5" /> 刷新</button> <button @click="load" class="refresh-btn" title="刷新" aria-label="刷新"><Icon name="refresh" :size="18" /></button>
</div> </div>
</div> </div>
<!-- KPI strip — same shape as the LogsView so the admin shell stays <!-- KPI strip — same shape as the LogsView so the admin shell stays
+1 -1
View File
@@ -26,7 +26,7 @@ onMounted(load)
<p class="page-sub">全站邀请记录:谁邀请了谁、注册与完成时间、奖励状态</p> <p class="page-sub">全站邀请记录:谁邀请了谁、注册与完成时间、奖励状态</p>
</div> </div>
<div class="page-actions"> <div class="page-actions">
<button @click="load" class="btn-soft"><Icon name="refresh" class="w-3.5 h-3.5" /> 刷新</button> <button @click="load" class="refresh-btn" title="刷新" aria-label="刷新"><Icon name="refresh" :size="18" /></button>
</div> </div>
</div> </div>
<!-- KPI strip — same shape as the LogsView so the admin shell stays <!-- KPI strip — same shape as the LogsView so the admin shell stays
+1 -1
View File
@@ -157,7 +157,7 @@ const sourcePill = (s) => ({
<section class="space-y-4"> <section class="space-y-4">
<div class="page-head"> <div class="page-head">
<h2 class="page-title">日志管理</h2> <h2 class="page-title">日志管理</h2>
<button @click="load" class="btn-soft"><Icon name="refresh" class="w-3.5 h-3.5" /> 刷新</button> <button @click="load" class="refresh-btn" title="刷新" aria-label="刷新"><Icon name="refresh" :size="18" /></button>
</div> </div>
<!-- KPI strip — dense pills aligned with the dashboard tints --> <!-- KPI strip — dense pills aligned with the dashboard tints -->
<div class="grid grid-cols-2 md:grid-cols-4 gap-3"> <div class="grid grid-cols-2 md:grid-cols-4 gap-3">
+1 -1
View File
@@ -77,7 +77,7 @@ onMounted(loadModels)
<section class="space-y-4"> <section class="space-y-4">
<div class="page-head"> <div class="page-head">
<h2 class="page-title">模型管理</h2> <h2 class="page-title">模型管理</h2>
<button @click="loadModels" class="btn-soft"><Icon name="refresh" class="w-3.5 h-3.5" /> 刷新</button> <button @click="loadModels" class="refresh-btn" title="刷新" aria-label="刷新"><Icon name="refresh" :size="18" /></button>
</div> </div>
<!-- KPI strip — same shape as LogsView / InvitesAdminView --> <!-- KPI strip — same shape as LogsView / InvitesAdminView -->
<div class="grid grid-cols-2 md:grid-cols-4 gap-3"> <div class="grid grid-cols-2 md:grid-cols-4 gap-3">
+1 -1
View File
@@ -155,7 +155,7 @@ onUnmounted(() => clearInterval(timer))
<span class="pill" :class="'pill--' + overallHealth.tone"> <span class="pill" :class="'pill--' + overallHealth.tone">
<span class="dot" :class="'dot--' + overallHealth.tone"></span>{{ overallHealth.label }} <span class="dot" :class="'dot--' + overallHealth.tone"></span>{{ overallHealth.label }}
</span> </span>
<button @click="refreshAll" class="ov-btn"><Icon name="refresh" :size="14" /> 刷新</button> <button @click="refreshAll" class="refresh-btn" title="刷新" aria-label="刷新"><Icon name="refresh" :size="18" /></button>
</div> </div>
</div> </div>
+1 -1
View File
@@ -185,7 +185,7 @@ onMounted(refresh)
<p class="page-sub">首页首屏拼贴(Hero)与作品墙(Work)的展示图</p> <p class="page-sub">首页首屏拼贴(Hero)与作品墙(Work)的展示图</p>
</div> </div>
<div class="page-actions"> <div class="page-actions">
<button @click="refresh" class="btn-soft"><Icon name="refresh" class="w-3.5 h-3.5" /> 刷新</button> <button @click="refresh" class="refresh-btn" title="刷新" aria-label="刷新"><Icon name="refresh" :size="18" /></button>
</div> </div>
</div> </div>
<!-- filter / new --> <!-- filter / new -->
+1 -1
View File
@@ -219,7 +219,7 @@ async function doRecharge() {
<section class="space-y-4"> <section class="space-y-4">
<div class="page-head"> <div class="page-head">
<h2 class="page-title">用户管理</h2> <h2 class="page-title">用户管理</h2>
<button @click="load" class="btn-soft"><Icon name="refresh" class="w-3.5 h-3.5" /> 刷新</button> <button @click="load" class="refresh-btn" title="刷新" aria-label="刷新"><Icon name="refresh" :size="18" /></button>
</div> </div>
<!-- KPI strip — same shape as LogsView / ModelsView --> <!-- KPI strip — same shape as LogsView / ModelsView -->
<div class="grid grid-cols-2 md:grid-cols-4 gap-3"> <div class="grid grid-cols-2 md:grid-cols-4 gap-3">