style(ui): 全站清除紫色系,统一 Memphis 蜡笔配色

- Logo 渐变从粉紫改为橙/粉/蓝蜡笔色,快门圆点改橙色
- 批量替换 accent:violet→orange、fuchsia→rose、indigo→sky、purple→orange,
  十六进制与 rgb 字面量同步换算为 Tailwind 对应色
- 修复类名合并冲突:管理员筛选 pill 改 fp-orange、会员号改 fp-pink(新增),
  style.css 中去重 fp-rose/fp-orange 并补 fp-pink 的浅色模式变体
- 选区色、标签输入框、筛选 pill、图表、滑块等所有紫色残留全部归位

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-08 17:15:17 +08:00
co-authored by Devin
parent b39173643d
commit 602664c122
28 changed files with 147 additions and 147 deletions
+1 -1
View File
@@ -156,7 +156,7 @@ async function save() {
<div class="flex flex-wrap gap-1.5">
<button v-for="r in RATIO_OPTS" :key="r" type="button" @click="toggleRatio(r)"
class="px-2 py-1 rounded text-xs ring-1 transition-colors"
:class="ratios.includes(r) ? 'bg-indigo-500/15 text-indigo-600 ring-indigo-300' : 'bg-slate-50 text-slate-500 ring-slate-200 hover:ring-slate-300'">{{ r }}</button>
:class="ratios.includes(r) ? 'bg-sky-500/15 text-sky-600 ring-sky-300' : 'bg-slate-50 text-slate-500 ring-slate-200 hover:ring-slate-300'">{{ r }}</button>
</div>
</div>
+2 -2
View File
@@ -131,7 +131,7 @@ async function doSmartImport() {
<span v-if="detected.adobe" class="inline-flex items-center gap-1 px-1.5 py-0.5 rounded text-rose-700 bg-rose-50 ring-1 ring-rose-200">
Adobe · <span class="tabular-nums">{{ detected.adobe }}</span>
</span>
<span v-if="detected.runway" class="inline-flex items-center gap-1 px-1.5 py-0.5 rounded text-violet-700 bg-violet-50 ring-1 ring-violet-200">
<span v-if="detected.runway" class="inline-flex items-center gap-1 px-1.5 py-0.5 rounded text-orange-700 bg-orange-50 ring-1 ring-orange-200">
Runway · <span class="tabular-nums">{{ detected.runway }}</span>
</span>
<span v-if="detected.leonardo" class="inline-flex items-center gap-1 px-1.5 py-0.5 rounded text-amber-700 bg-amber-50 ring-1 ring-amber-200">
@@ -146,7 +146,7 @@ async function doSmartImport() {
<span v-if="detected.grok" class="inline-flex items-center gap-1 px-1.5 py-0.5 rounded text-slate-700 bg-slate-100 ring-1 ring-slate-300">
Grok · <span class="tabular-nums">{{ detected.grok }}</span>
</span>
<span v-if="detected.cf" class="inline-flex items-center gap-1 px-1.5 py-0.5 rounded text-fuchsia-700 bg-fuchsia-50 ring-1 ring-fuchsia-200">
<span v-if="detected.cf" class="inline-flex items-center gap-1 px-1.5 py-0.5 rounded text-rose-700 bg-rose-50 ring-1 ring-rose-200">
Creative Fabrica · <span class="tabular-nums">{{ detected.cf }}</span>
</span>
</template>
+7 -6
View File
@@ -1,7 +1,8 @@
<script setup>
// Vivid mark: an open spectrum ring (lens/aperture) around a folded "V", with a
// shutter dot closing the gap — image + video in one glyph. Transparent
// background so it reads on both the dark and light shells.
// background so it reads on both the dark and light shells. Colors follow the
// memphis crayon palette (orange / pink / blue).
// Each instance gets unique gradient ids (useId) so multiple logos on one
// page don't share/clobber defs.
import { useId } from 'vue'
@@ -18,13 +19,13 @@ const gV = `vv-${uid}`
xmlns="http://www.w3.org/2000/svg" class="shrink-0">
<defs>
<linearGradient :id="gRing" x1="6" y1="6" x2="42" y2="42" gradientUnits="userSpaceOnUse">
<stop stop-color="#e879f9" />
<stop offset="0.5" stop-color="#8b5cf6" />
<stop stop-color="#ec5b13" />
<stop offset="0.5" stop-color="#f472b6" />
<stop offset="1" stop-color="#38bdf8" />
</linearGradient>
<linearGradient :id="gV" x1="14" y1="14" x2="34" y2="34" gradientUnits="userSpaceOnUse">
<stop stop-color="#f0abfc" />
<stop offset="1" stop-color="#8b5cf6" />
<stop stop-color="#f97316" />
<stop offset="1" stop-color="#ec5b13" />
</linearGradient>
</defs>
@@ -35,7 +36,7 @@ const gV = `vv-${uid}`
<!-- folded V -->
<path d="M-8 -7.5 L0 9 L8 -7.5" fill="none" :stroke="`url(#${gV})`" stroke-width="4.8"
stroke-linecap="round" stroke-linejoin="round" />
<circle cx="13" cy="-13" r="3.4" fill="#38bdf8" />
<circle cx="13" cy="-13" r="3.4" fill="#ec5b13" />
</g>
</svg>
</template>
+2 -2
View File
@@ -219,8 +219,8 @@ async function save() {
<div v-if="entry" class="rounded-xl bg-white/[0.03] ring-1 ring-white/[0.06] p-3.5 space-y-2.5">
<div class="flex items-center gap-2">
<span class="inline-flex items-center gap-1 rounded-full px-2.5 py-1 text-[11px] font-medium ring-1"
:class="isVideo ? 'bg-fuchsia-500/10 text-fuchsia-300 ring-fuchsia-400/30'
: 'bg-indigo-500/10 text-indigo-300 ring-indigo-400/30'">
:class="isVideo ? 'bg-rose-500/10 text-rose-300 ring-rose-400/30'
: 'bg-sky-500/10 text-sky-300 ring-sky-400/30'">
{{ isVideo ? '生视频' : '生图' }}
</span>
<span class="text-[11px] text-white/45 capitalize">{{ entry.provider }}</span>
+4 -4
View File
@@ -58,12 +58,12 @@ function remove(i) {
font-size: 0.75rem;
font-weight: 500;
border-radius: 9999px;
color: rgb(238 224 255); /* near-white violet */
background: linear-gradient(135deg, rgb(167 139 250 / 0.18), rgb(236 72 153 / 0.14));
box-shadow: inset 0 0 0 1px rgb(167 139 250 / 0.3);
color: rgb(255 237 213); /* near-white orange */
background: linear-gradient(135deg, rgb(253 186 116 / 0.18), rgb(251 113 133 / 0.14));
box-shadow: inset 0 0 0 1px rgb(253 186 116 / 0.3);
transition: background 0.15s ease;
}
.chip:hover { background: linear-gradient(135deg, rgb(167 139 250 / 0.25), rgb(236 72 153 / 0.2)); }
.chip:hover { background: linear-gradient(135deg, rgb(253 186 116 / 0.25), rgb(251 113 133 / 0.2)); }
.chip-x {
display: grid;
+2 -2
View File
@@ -98,8 +98,8 @@ async function submit() {
<div v-else class="flex flex-wrap gap-1.5 max-h-44 overflow-y-auto rounded-lg ring-1 ring-slate-200 bg-slate-50/60 p-2">
<button v-for="m in allModels" :key="m.id" type="button" @click="toggle(m.id)"
class="inline-flex items-center gap-1.5 px-2.5 py-1 rounded-md text-xs ring-1 transition-colors"
:class="selected.includes(m.id) ? 'bg-indigo-500/15 text-indigo-700 ring-indigo-300 font-medium' : 'bg-white text-slate-600 ring-slate-200 hover:ring-slate-300'">
<span class="w-1.5 h-1.5 rounded-full" :class="m.type === 'video' ? 'bg-violet-400' : 'bg-emerald-400'"></span>{{ m.alias || m.id }}
:class="selected.includes(m.id) ? 'bg-sky-500/15 text-sky-700 ring-sky-300 font-medium' : 'bg-white text-slate-600 ring-slate-200 hover:ring-slate-300'">
<span class="w-1.5 h-1.5 rounded-full" :class="m.type === 'video' ? 'bg-orange-400' : 'bg-emerald-400'"></span>{{ m.alias || m.id }}
</button>
</div>
</div>
+1 -1
View File
@@ -47,7 +47,7 @@ watch(() => route.path, () => {
</script>
<template>
<div class="theme-x h-screen flex bg-[var(--app-bg)] text-[color:var(--fg-2)] selection:bg-violet-400/30 overflow-hidden">
<div class="theme-x h-screen flex bg-[var(--app-bg)] text-[color:var(--fg-2)] selection:bg-orange-400/30 overflow-hidden">
<!-- ===== Sidebar ===== -->
<aside class="w-60 shrink-0 border-r-[3px] border-r-[color:var(--ink)] bg-[var(--surface)] backdrop-blur-md flex flex-col">
<router-link to="/" class="h-16 flex items-center gap-2.5 px-5 border-b-[3px] border-b-[color:var(--ink)] group">
+1 -1
View File
@@ -67,7 +67,7 @@ async function onLogout() {
</script>
<template>
<div class="theme-x min-h-screen bg-[var(--app-bg)] text-[color:var(--fg-2)] selection:bg-violet-400/30">
<div class="theme-x min-h-screen bg-[var(--app-bg)] text-[color:var(--fg-2)] selection:bg-orange-400/30">
<!-- ===== Top bar ===== -->
<header class="fixed top-0 inset-x-0 z-40 backdrop-blur-xl top-bar">
<div class="mx-auto max-w-[1400px] px-4 md:px-8 h-[58px] flex items-center gap-3">
+13 -14
View File
@@ -101,9 +101,9 @@ html:not(.dark) .theme-text :is([class*="bg-slate-900"], [class*="bg-slate-800"]
shell and washes out on white. In LIGHT mode, darken to the -600 tone. The
matcher also catches the /80 opacity variants. Badges keep their pale tinted
bg (bg-*-500/10) which reads fine on white. */
html:not(.dark) .theme-text :is([class*="text-indigo-300"], [class*="text-indigo-200"]) { color: rgb(79 70 229); }
html:not(.dark) .theme-text :is([class*="text-violet-300"], [class*="text-violet-200"]) { color: rgb(124 58 237); }
html:not(.dark) .theme-text :is([class*="text-fuchsia-300"], [class*="text-fuchsia-200"]) { color: rgb(192 38 211); }
html:not(.dark) .theme-text :is([class*="text-sky-300"], [class*="text-sky-200"]) { color: rgb(79 70 229); }
html:not(.dark) .theme-text :is([class*="text-orange-300"], [class*="text-orange-200"]) { color: rgb(234 88 12); }
html:not(.dark) .theme-text :is([class*="text-rose-300"], [class*="text-rose-200"]) { color: rgb(225 29 72); }
html:not(.dark) .theme-text :is([class*="text-emerald-300"], [class*="text-emerald-200"]) { color: rgb(5 150 105); }
html:not(.dark) .theme-text :is([class*="text-sky-300"], [class*="text-sky-200"]) { color: rgb(2 132 199); }
html:not(.dark) .theme-text :is([class*="text-amber-300"], [class*="text-amber-200"]) { color: rgb(217 119 6); }
@@ -121,8 +121,8 @@ html:not(.dark) .theme-text :is(.fp, .pg, .act, .filter-pill, .kind-btn, .preset
color: var(--fg); background: var(--hover);
}
html:not(.dark) .theme-text :is(.fp-on, .pg-on, .seg-on, .opt-on) { background: var(--fg); color: #fff; box-shadow: 2px 2px 0 var(--m-orange); }
/* Tag chips (TagInput) — near-white violet text is unreadable on white; keep
the violet tint but switch the label to dark ink. */
/* Tag chips (TagInput) — near-white orange text is unreadable on white; keep
the orange tint but switch the label to dark ink. */
html:not(.dark) .theme-text .chip { color: rgb(15 23 42 / 0.9); }
html:not(.dark) .theme-text .chip .chip-x { color: rgb(15 23 42 / 0.55); background: transparent; box-shadow: none; }
html:not(.dark) .theme-text .chip .chip-x:hover { color: #fff; background: rgb(244 63 94 / 0.6); }
@@ -137,10 +137,9 @@ html:not(.dark) .theme-text :is(.filter-pill, .kind-btn).on { background: var(--
placed later, so the selection reads on a white page. */
html:not(.dark) .theme-text .fp-emerald { background: rgb(16 185 129 / 0.14); color: rgb(4 120 87); box-shadow: inset 0 0 0 1px rgb(16 185 129 / 0.45); }
html:not(.dark) .theme-text .fp-rose { background: rgb(244 63 94 / 0.12); color: rgb(190 18 60); box-shadow: inset 0 0 0 1px rgb(244 63 94 / 0.45); }
html:not(.dark) .theme-text .fp-fuchsia { background: rgb(217 70 239 / 0.12); color: rgb(162 28 175); box-shadow: inset 0 0 0 1px rgb(217 70 239 / 0.45); }
html:not(.dark) .theme-text .fp-amber { background: rgb(245 158 11 / 0.16); color: rgb(180 83 9); box-shadow: inset 0 0 0 1px rgb(245 158 11 / 0.5); }
html:not(.dark) .theme-text .fp-violet { background: rgb(139 92 246 / 0.14); color: rgb(109 40 217); box-shadow: inset 0 0 0 1px rgb(139 92 246 / 0.45); }
html:not(.dark) .theme-text .fp-purple { background: rgb(168 85 247 / 0.14); color: rgb(107 33 168); box-shadow: inset 0 0 0 1px rgb(168 85 247 / 0.45); }
html:not(.dark) .theme-text .fp-orange { background: rgb(249 115 22 / 0.14); color: rgb(194 65 12); box-shadow: inset 0 0 0 1px rgb(249 115 22 / 0.45); }
html:not(.dark) .theme-text .fp-pink { background: rgb(244 114 182 / 0.14); color: rgb(190 24 93); box-shadow: inset 0 0 0 1px rgb(244 114 182 / 0.45); }
html:not(.dark) .theme-text .fp-slate { background: rgb(100 116 139 / 0.14); color: rgb(51 65 85); box-shadow: inset 0 0 0 1px rgb(100 116 139 / 0.45); }
html:not(.dark) .theme-text .fp-sky { background: rgb(14 165 233 / 0.14); color: rgb(3 105 161); box-shadow: inset 0 0 0 1px rgb(14 165 233 / 0.45); }
html:not(.dark) .theme-text .fp-teal { background: rgb(20 184 166 / 0.14); color: rgb(15 118 110); box-shadow: inset 0 0 0 1px rgb(20 184 166 / 0.45); }
@@ -148,7 +147,7 @@ html:not(.dark) .theme-text .fp-teal { background: rgb(20 184 166 / 0.14); color
/* Custom checkbox for admin tables (<input type="checkbox" class="chk">).
Replaces the plain accent-color box with a rounded, branded one + white check.
Neutral border + transparent bg so it reads on both the dark admin shell and
light mode; fills fuchsia when checked. */
light mode; fills rose when checked. */
.chk {
appearance: none;
-webkit-appearance: none;
@@ -196,11 +195,11 @@ html:not(.dark) .theme-text .fp-teal { background: rgb(20 184 166 / 0.14); color
white-space: nowrap;
}
.acct-tag-sub { background: rgb(14 165 233 / 0.14); color: rgb(3 105 161); box-shadow: inset 0 0 0 1px rgb(14 165 233 / 0.4); }
.acct-tag-master { background: rgb(139 92 246 / 0.14); color: rgb(109 40 217); box-shadow: inset 0 0 0 1px rgb(139 92 246 / 0.4); }
.acct-tag-master { background: rgb(249 115 22 / 0.14); color: rgb(194 65 12); box-shadow: inset 0 0 0 1px rgb(249 115 22 / 0.4); }
.acct-tag-free { background: rgb(100 116 139 / 0.14); color: rgb(51 65 85); box-shadow: inset 0 0 0 1px rgb(100 116 139 / 0.4); }
.acct-tag-limit { background: rgb(245 158 11 / 0.16); color: rgb(180 83 9); box-shadow: inset 0 0 0 1px rgb(245 158 11 / 0.4); }
html.dark .acct-tag-sub { background: rgb(14 165 233 / 0.18); color: rgb(125 211 252); box-shadow: inset 0 0 0 1px rgb(56 189 248 / 0.35); }
html.dark .acct-tag-master { background: rgb(139 92 246 / 0.22); color: rgb(196 181 253); box-shadow: inset 0 0 0 1px rgb(167 139 250 / 0.35); }
html.dark .acct-tag-master { background: rgb(249 115 22 / 0.22); color: rgb(253 186 116); box-shadow: inset 0 0 0 1px rgb(253 186 116 / 0.35); }
html.dark .acct-tag-free { background: rgb(148 163 184 / 0.22); color: rgb(226 232 240); box-shadow: inset 0 0 0 1px rgb(148 163 184 / 0.35); }
html.dark .acct-tag-limit { background: rgb(245 158 11 / 0.18); color: rgb(252 211 77); box-shadow: inset 0 0 0 1px rgb(251 191 36 / 0.35); }
/* Capability chips (ModelsView): all variants need bg + ring + color reset in light mode.
@@ -255,9 +254,9 @@ html:not(.dark) .media-card :is([class*="border-white/"], [class*="ring-white/"]
/* Accent text inside a media-card sits on dark imagery too — keep it at its
bright (dark-mode) shade instead of the darkened light-theme rescue above. */
html:not(.dark) .media-card :is([class*="text-emerald-3"], [class*="text-emerald-2"]) { color: rgb(110 231 183) !important; }
html:not(.dark) .media-card :is([class*="text-fuchsia-3"], [class*="text-fuchsia-2"]) { color: rgb(240 171 252) !important; }
html:not(.dark) .media-card :is([class*="text-indigo-3"], [class*="text-indigo-2"]) { color: rgb(165 180 252) !important; }
html:not(.dark) .media-card :is([class*="text-violet-3"], [class*="text-violet-2"]) { color: rgb(196 181 253) !important; }
html:not(.dark) .media-card :is([class*="text-rose-3"], [class*="text-rose-2"]) { color: rgb(251 207 232) !important; }
html:not(.dark) .media-card :is([class*="text-sky-3"], [class*="text-sky-2"]) { color: rgb(147 197 253) !important; }
html:not(.dark) .media-card :is([class*="text-orange-3"], [class*="text-orange-2"]) { color: rgb(253 186 116) !important; }
html:not(.dark) .media-card :is([class*="text-sky-3"], [class*="text-sky-2"]) { color: rgb(125 211 252) !important; }
html:not(.dark) .media-card :is([class*="text-amber-3"], [class*="text-amber-2"]) { color: rgb(252 211 77) !important; }
+19 -19
View File
@@ -65,11 +65,11 @@ function typePill(t) {
return {
adobe: 'bg-rose-500/10 text-rose-300 ring-rose-400/30',
openai: 'bg-emerald-500/10 text-emerald-300 ring-emerald-400/30',
runway: 'bg-violet-500/10 text-violet-300 ring-violet-400/30',
runway: 'bg-orange-500/10 text-orange-300 ring-orange-400/30',
leonardo: 'bg-amber-500/10 text-amber-300 ring-amber-400/30',
krea: 'bg-sky-500/10 text-sky-300 ring-sky-400/30',
imagine: 'bg-teal-500/10 text-teal-300 ring-teal-400/30',
creativefabrica: 'bg-fuchsia-500/10 text-fuchsia-300 ring-fuchsia-400/30',
creativefabrica: 'bg-rose-500/10 text-rose-300 ring-rose-400/30',
}[t] || 'bg-white/[0.06] text-white/70 ring-white/15'
}
// 与后端 accountConcurrency 保持一致:adobe 普号固定 1 并发,会员号取配置值
@@ -339,7 +339,7 @@ onMounted(() => { loadAccounts(); loadModelList() })
<div class="text-2xl font-semibold mt-1 tabular-nums">{{ stats.total }}</div>
<div class="text-[10px] text-white/35 mt-0.5">成功/失败/限额</div>
</div>
<div v-for="t in [['openai','OpenAI','text-emerald-300/80'],['adobe','Adobe','text-rose-300/80'],['runway','Runway','text-violet-300/80'],['leonardo','Leonardo','text-amber-300/80'],['krea','Krea','text-sky-300/80'],['imagine','Imagine','text-teal-300/80'],['grok','Grok','text-slate-300/80'],['creativefabrica','Creative Fabrica','text-fuchsia-300/80']]"
<div v-for="t in [['openai','OpenAI','text-emerald-300/80'],['adobe','Adobe','text-rose-300/80'],['runway','Runway','text-orange-300/80'],['leonardo','Leonardo','text-amber-300/80'],['krea','Krea','text-sky-300/80'],['imagine','Imagine','text-teal-300/80'],['grok','Grok','text-slate-300/80'],['creativefabrica','Creative Fabrica','text-rose-300/80']]"
:key="t[0]" class="card p-4">
<div class="text-[11px] uppercase tracking-wider" :class="t[2]">{{ t[1] }}</div>
<div class="text-2xl font-semibold mt-1 tabular-nums">
@@ -359,8 +359,8 @@ onMounted(() => { loadAccounts(); loadModelList() })
<button @click="setFilter(() => typeFilter = 'adobe')" class="fp" :class="typeFilter === 'adobe' && 'fp-rose'">
<span class="w-1.5 h-1.5 rounded-full bg-rose-400"></span>Adobe
</button>
<button @click="setFilter(() => typeFilter = 'runway')" class="fp" :class="typeFilter === 'runway' && 'fp-violet'">
<span class="w-1.5 h-1.5 rounded-full bg-violet-400"></span>Runway
<button @click="setFilter(() => typeFilter = 'runway')" class="fp" :class="typeFilter === 'runway' && 'fp-orange'">
<span class="w-1.5 h-1.5 rounded-full bg-orange-400"></span>Runway
</button>
<button @click="setFilter(() => typeFilter = 'leonardo')" class="fp" :class="typeFilter === 'leonardo' && 'fp-amber'">
<span class="w-1.5 h-1.5 rounded-full bg-amber-400"></span>Leonardo
@@ -375,7 +375,7 @@ onMounted(() => { loadAccounts(); loadModelList() })
<span class="w-1.5 h-1.5 rounded-full bg-slate-400"></span>Grok
</button>
<button @click="setFilter(() => typeFilter = 'creativefabrica')" class="fp" :class="typeFilter === 'creativefabrica' && 'fp-on'">
<span class="w-1.5 h-1.5 rounded-full bg-fuchsia-400"></span>Creative Fabrica
<span class="w-1.5 h-1.5 rounded-full bg-rose-400"></span>Creative Fabrica
</button>
</div>
<div class="w-px h-5 bg-white/10"></div>
@@ -397,7 +397,7 @@ onMounted(() => { loadAccounts(); loadModelList() })
<button @click="setFilter(() => planFilter = '')" class="fp" :class="planFilter === '' && 'fp-on'">全部账号</button>
<button @click="setFilter(() => planFilter = 'free')" class="fp" :class="planFilter === 'free' && 'fp-slate'">普号</button>
<button @click="setFilter(() => planFilter = 'sub')" class="fp" :class="planFilter === 'sub' && 'fp-sky'">子号</button>
<button @click="setFilter(() => planFilter = 'master')" class="fp" :class="planFilter === 'master' && 'fp-purple'">会员号</button>
<button @click="setFilter(() => planFilter = 'master')" class="fp" :class="planFilter === 'master' && 'fp-pink'">会员号</button>
</div>
</template>
<div class="flex-1 min-w-[200px]">
@@ -552,7 +552,7 @@ onMounted(() => { loadAccounts(); loadModelList() })
<td class="px-3 py-3.5 align-middle">
<div class="flex items-center justify-center gap-1.5 text-xs tabular-nums">
<span class="px-1.5 py-0.5 rounded"
:class="a.in_flight ? 'bg-indigo-500/15 text-indigo-300 font-semibold' : 'text-white/25'"
:class="a.in_flight ? 'bg-sky-500/15 text-sky-300 font-semibold' : 'text-white/25'"
title="在途">{{ a.in_flight || 0 }}</span>
<span class="text-white/20">/</span>
<span class="px-1.5 py-0.5 rounded text-emerald-300 font-medium" title="成功">{{ a.success_total || 0 }}</span>
@@ -645,10 +645,10 @@ onMounted(() => { loadAccounts(); loadModelList() })
color: rgb(253 224 71);
box-shadow: inset 0 0 0 1px rgb(253 224 71 / 0.4);
}
.fp-violet {
background: rgb(139 92 246 / 0.22);
color: rgb(196 181 253);
box-shadow: inset 0 0 0 1px rgb(196 181 253 / 0.45);
.fp-orange {
background: rgb(249 115 22 / 0.22);
color: rgb(253 186 116);
box-shadow: inset 0 0 0 1px rgb(253 186 116 / 0.45);
}
.fp-sky {
background: rgb(56 189 248 / 0.22);
@@ -660,10 +660,10 @@ onMounted(() => { loadAccounts(); loadModelList() })
color: rgb(94 234 212);
box-shadow: inset 0 0 0 1px rgb(94 234 212 / 0.45);
}
.fp-purple {
background: rgb(168 85 247 / 0.22);
color: rgb(216 180 254);
box-shadow: inset 0 0 0 1px rgb(216 180 254 / 0.45);
.fp-pink {
background: rgb(244 114 182 / 0.22);
color: rgb(249 168 212);
box-shadow: inset 0 0 0 1px rgb(249 168 212 / 0.45);
}
.fp-slate {
background: rgb(148 163 184 / 0.22);
@@ -725,10 +725,10 @@ onMounted(() => { loadAccounts(); loadModelList() })
background: rgb(244 63 94 / 0.8);
box-shadow: inset 0 0 0 1px rgb(244 63 94 / 0.6);
}
/* pending (import quota probe in flight) — neutral indigo, thumb stays left */
/* pending (import quota probe in flight) — neutral sky, thumb stays left */
.sw-pending {
background: rgb(99 102 241 / 0.45);
box-shadow: inset 0 0 0 1px rgb(99 102 241 / 0.4);
background: rgb(14 165 233 / 0.45);
box-shadow: inset 0 0 0 1px rgb(14 165 233 / 0.4);
}
/* quota exhausted — amber (NOT red/dead), thumb stays left, locked until reset */
.sw-quota {
+2 -2
View File
@@ -116,7 +116,7 @@ function goPage(n) {
<td class="px-3 py-3.5 align-middle text-xs text-white/55 whitespace-nowrap">{{ fmt(o.created_at) }}</td>
<td class="px-3 py-3.5 align-middle text-xs text-white/55 whitespace-nowrap">{{ fmt(o.paid_at) }}</td>
<td class="px-3 py-3.5 align-middle text-right tabular-nums text-white/85">{{ o.source && o.source !== 'epay' ? '—' : '¥' + o.amount }}</td>
<td class="px-3 py-3.5 align-middle text-right tabular-nums" :class="o.points < 0 ? 'text-rose-300' : 'text-violet-300'">{{ o.points }}</td>
<td class="px-3 py-3.5 align-middle text-right tabular-nums" :class="o.points < 0 ? 'text-rose-300' : 'text-orange-300'">{{ o.points }}</td>
<td class="px-3 py-3.5 align-middle">
<span class="chip" :class="chipClass(o.status)">{{ STATUS[o.status] }}<span class="opacity-50 ml-1">· {{ METHOD[o.pay_type] || o.pay_type }}</span></span>
</td>
@@ -148,7 +148,7 @@ function goPage(n) {
.log-table { border-collapse: separate; border-spacing: 0; }
.log-row td { border-bottom: 1px solid rgb(255 255 255 / 0.04); transition: background-color 0.15s ease, box-shadow 0.15s ease; }
.log-row:hover td { background: rgb(255 255 255 / 0.025); }
.log-row:hover td:first-child { box-shadow: inset 2px 0 0 rgb(167 139 250 / 0.55); }
.log-row:hover td:first-child { box-shadow: inset 2px 0 0 rgb(253 186 116 / 0.55); }
.log-row:last-child td { border-bottom: none; }
.pg { min-width: 1.75rem; padding: 0.3rem 0.55rem; font-size: 0.72rem; font-weight: 500; text-align: center; border-radius: 0.45rem; color: rgb(255 255 255 / 0.7); background: rgb(255 255 255 / 0.04); box-shadow: inset 0 0 0 1px rgb(255 255 255 / 0.08); transition: background 0.15s, color 0.15s; }
.pg:hover:not(.pg-on) { background: rgb(255 255 255 / 0.1); color: white; }
+1 -1
View File
@@ -210,7 +210,7 @@ onMounted(load)
color: white;
background: rgb(244 63 94 / 0.25);
}
.chk { accent-color: rgb(217 70 239); width: 0.9rem; height: 0.9rem; cursor: pointer; }
.chk { accent-color: rgb(244 63 94); width: 0.9rem; height: 0.9rem; cursor: pointer; }
.pg { min-width: 1.75rem; padding: 0.3rem 0.55rem; font-size: 0.72rem; font-weight: 500; text-align: center; border-radius: 0.45rem; color: rgb(255 255 255 / 0.7); background: rgb(255 255 255 / 0.04); box-shadow: inset 0 0 0 1px rgb(255 255 255 / 0.08); transition: background 0.15s, color 0.15s; }
.pg:hover:not(.pg-on) { background: rgb(255 255 255 / 0.1); color: white; }
.pg-on { background: rgb(255 255 255 / 0.92); color: rgb(15 23 42); box-shadow: none; }
+11 -11
View File
@@ -155,8 +155,8 @@ const pageNumbers = computed(() => {
<div class="text-2xl font-semibold mt-1 tabular-nums text-white/60">{{ stats.redeemed }}</div>
</div>
<div class="card p-4">
<div class="text-[11px] uppercase tracking-wider text-fuchsia-300/80">未使用面额</div>
<div class="text-2xl font-semibold mt-1 tabular-nums text-fuchsia-300">{{ Number(stats.active_amount || 0).toLocaleString('en-US') }}</div>
<div class="text-[11px] uppercase tracking-wider text-rose-300/80">未使用面额</div>
<div class="text-2xl font-semibold mt-1 tabular-nums text-rose-300">{{ Number(stats.active_amount || 0).toLocaleString('en-US') }}</div>
</div>
</div>
@@ -178,12 +178,12 @@ const pageNumbers = computed(() => {
<label class="block text-xs text-white/55 mb-1.5">类型</label>
<div class="flex items-center gap-1">
<button type="button" @click="form.type = 'normal'" class="fp" :class="form.type === 'normal' && 'fp-on'">普通</button>
<button type="button" @click="form.type = 'marketing'" class="fp" :class="form.type === 'marketing' && 'fp-fuchsia'">营销</button>
<button type="button" @click="form.type = 'marketing'" class="fp" :class="form.type === 'marketing' && 'fp-rose'">营销</button>
</div>
</div>
<button @click="generate" class="btn-primary"><Icon name="plus" class="w-3.5 h-3.5" /> 生成</button>
</div>
<p v-if="form.type === 'marketing'" class="text-[11px] text-fuchsia-300/80 mt-2">营销兑换码:同一批次每个用户只能兑换一次。</p>
<p v-if="form.type === 'marketing'" class="text-[11px] text-rose-300/80 mt-2">营销兑换码:同一批次每个用户只能兑换一次。</p>
<!-- last batch -->
<div v-if="lastBatch.length" class="mt-4 rounded-xl bg-white/[0.04] ring-1 ring-white/10 p-4">
@@ -202,8 +202,8 @@ const pageNumbers = computed(() => {
<div class="flex items-center gap-1">
<button @click="setFilter(() => typeFilter = '')" class="fp" :class="typeFilter === '' && 'fp-on'">全部类型</button>
<button @click="setFilter(() => typeFilter = 'normal')" class="fp" :class="typeFilter === 'normal' && 'fp-on'">普通</button>
<button @click="setFilter(() => typeFilter = 'marketing')" class="fp" :class="typeFilter === 'marketing' && 'fp-fuchsia'">
<span class="w-1.5 h-1.5 rounded-full bg-fuchsia-400"></span>营销
<button @click="setFilter(() => typeFilter = 'marketing')" class="fp" :class="typeFilter === 'marketing' && 'fp-rose'">
<span class="w-1.5 h-1.5 rounded-full bg-rose-400"></span>营销
</button>
</div>
<div class="w-px h-5 bg-white/10"></div>
@@ -266,7 +266,7 @@ const pageNumbers = computed(() => {
<td class="px-5 py-3.5 align-middle font-mono text-xs text-white/90 truncate" :title="c.code">
<span class="inline-flex items-center gap-2">
<span class="truncate">{{ c.code }}</span>
<span v-if="c.type === 'marketing'" class="shrink-0 inline-flex items-center rounded px-1.5 py-0.5 text-[10px] font-sans font-medium bg-fuchsia-500/15 text-fuchsia-300 ring-1 ring-fuchsia-400/25">营销</span>
<span v-if="c.type === 'marketing'" class="shrink-0 inline-flex items-center rounded px-1.5 py-0.5 text-[10px] font-sans font-medium bg-rose-500/15 text-rose-300 ring-1 ring-rose-400/25">营销</span>
</span>
</td>
<td class="px-3 py-3.5 align-middle text-right tabular-nums text-white/85 whitespace-nowrap">
@@ -365,10 +365,10 @@ const pageNumbers = computed(() => {
color: rgb(110 231 183);
box-shadow: inset 0 0 0 1px rgb(110 231 183 / 0.45);
}
.fp-fuchsia {
background: rgb(217 70 239 / 0.22);
color: rgb(245 208 254);
box-shadow: inset 0 0 0 1px rgb(245 208 254 / 0.45);
.fp-rose {
background: rgb(244 63 94 / 0.22);
color: rgb(254 205 211);
box-shadow: inset 0 0 0 1px rgb(254 205 211 / 0.45);
}
.pg {
+2 -2
View File
@@ -81,8 +81,8 @@ onMounted(load)
</td>
<td class="px-3 py-3.5 align-middle text-right tabular-nums text-white/70">{{ g.user_count }}</td>
<td class="px-3 py-3.5 align-middle">
<span v-if="g.is_default" class="inline-flex items-center gap-1.5 rounded-full px-2.5 py-1 text-[11px] font-medium bg-fuchsia-500/10 text-fuchsia-300 ring-1 ring-fuchsia-400/30">
<span class="w-1.5 h-1.5 rounded-full bg-fuchsia-400"></span> 默认
<span v-if="g.is_default" class="inline-flex items-center gap-1.5 rounded-full px-2.5 py-1 text-[11px] font-medium bg-rose-500/10 text-rose-300 ring-1 ring-rose-400/30">
<span class="w-1.5 h-1.5 rounded-full bg-rose-400"></span> 默认
</span>
<button v-else @click="setDefault(g)" class="btn-soft text-xs">设为默认</button>
</td>
+2 -2
View File
@@ -310,7 +310,7 @@ onMounted(() => { loadSite(); loadReg(); loadSmtp(); loadCredits(); loadAnnounce
<div @click="pickLogo" @drop="onLogoDrop" @dragover.prevent="logoDragOver = true" @dragleave="logoDragOver = false"
title="点击或拖拽图片替换"
class="w-16 h-16 rounded-xl grid place-items-center overflow-hidden shrink-0 cursor-pointer transition-all"
:class="logoDragOver ? 'ring-2 ring-indigo-400 bg-indigo-50/40' : ''">
:class="logoDragOver ? 'ring-2 ring-sky-400 bg-sky-50/40' : ''">
<img v-if="logoPreview" :src="logoPreview" class="w-full h-full object-cover" />
<Logo v-else :size="64" class="w-full h-full" />
</div>
@@ -656,7 +656,7 @@ html.dark .hint b { color: rgb(253 164 175); } /* rose — visible warning, no
box-shadow: 0 1px 2px rgb(15 23 42 / 0.2);
transition: transform 0.18s ease;
}
.sw:checked { background: #4f46e5; } /* indigo-600 — matches btn-primary */
.sw:checked { background: #4f46e5; } /* sky-600 — matches btn-primary */
.sw:checked::after { transform: translateX(calc(2.25rem - 1.3rem)); }
.sw:focus-visible { box-shadow: 0 0 0 3px rgb(236 91 19 / 0.3); }
.sw:disabled { cursor: not-allowed; opacity: 0.55; }
+5 -5
View File
@@ -273,7 +273,7 @@ async function copy(text) {
<dt class="text-white/45">你的 Key</dt><dd class="font-mono text-white/70">{{ keyHint }}</dd>
</div>
</dl>
<p class="text-[11px] text-white/40 mt-4">还没有 Key?去 <router-link to="/settings" class="text-violet-300 underline">设置 → API Key</router-link> 生成。</p>
<p class="text-[11px] text-white/40 mt-4">还没有 Key?去 <router-link to="/settings" class="text-orange-300 underline">设置 → API Key</router-link> 生成。</p>
</div>
<div class="card p-6">
@@ -528,15 +528,15 @@ html.dark .badge-err { background: rgb(244 63 94 / 0.15); color: rgb(253 164 175
.cap { display: inline-flex; align-items: center; padding: 0.18rem 0.55rem; font-size: 0.7rem; border-radius: 9999px; white-space: nowrap; }
.cap-k { background: rgb(16 185 129 / 0.12); color: rgb(4 120 87); box-shadow: inset 0 0 0 1px rgb(16 185 129 / 0.3); }
.cap-dur { background: rgb(99 102 241 / 0.12); color: rgb(55 48 163); box-shadow: inset 0 0 0 1px rgb(99 102 241 / 0.3); }
.cap-frame { background: rgb(236 72 153 / 0.12); color: rgb(159 18 57); box-shadow: inset 0 0 0 1px rgb(236 72 153 / 0.3); }
.cap-dur { background: rgb(14 165 233 / 0.12); color: rgb(55 48 163); box-shadow: inset 0 0 0 1px rgb(14 165 233 / 0.3); }
.cap-frame { background: rgb(251 113 133 / 0.12); color: rgb(159 18 57); box-shadow: inset 0 0 0 1px rgb(251 113 133 / 0.3); }
.cap-ref { background: rgb(16 185 129 / 0.12); color: rgb(4 120 87); box-shadow: inset 0 0 0 1px rgb(16 185 129 / 0.3); }
.cap-media { background: rgb(245 158 11 / 0.12); color: rgb(146 64 14); box-shadow: inset 0 0 0 1px rgb(245 158 11 / 0.3); }
.cap-price { background: rgb(14 165 233 / 0.12); color: rgb(3 105 161); box-shadow: inset 0 0 0 1px rgb(14 165 233 / 0.3); font-variant-numeric: tabular-nums; }
.cap-ratio { background: rgb(100 116 139 / 0.1); color: rgb(51 65 85); font-family: ui-monospace, SFMono-Regular, Menlo, monospace; box-shadow: inset 0 0 0 1px rgb(100 116 139 / 0.25); }
html.dark .cap-k { background: rgb(16 185 129 / 0.18); color: rgb(110 231 183); box-shadow: inset 0 0 0 1px rgb(52 211 153 / 0.4); }
html.dark .cap-dur { background: rgb(99 102 241 / 0.18); color: rgb(165 180 252); box-shadow: inset 0 0 0 1px rgb(129 140 248 / 0.4); }
html.dark .cap-frame { background: rgb(236 72 153 / 0.18); color: rgb(244 114 182); box-shadow: inset 0 0 0 1px rgb(244 114 182 / 0.45); }
html.dark .cap-dur { background: rgb(14 165 233 / 0.18); color: rgb(147 197 253); box-shadow: inset 0 0 0 1px rgb(129 140 248 / 0.4); }
html.dark .cap-frame { background: rgb(251 113 133 / 0.18); color: rgb(244 114 182); box-shadow: inset 0 0 0 1px rgb(244 114 182 / 0.45); }
html.dark .cap-ref { background: rgb(16 185 129 / 0.18); color: rgb(110 231 183); box-shadow: inset 0 0 0 1px rgb(52 211 153 / 0.4); }
html.dark .cap-media { background: rgb(245 158 11 / 0.18); color: rgb(252 211 77); box-shadow: inset 0 0 0 1px rgb(252 211 77 / 0.45); }
html.dark .cap-price { background: rgb(14 165 233 / 0.18); color: rgb(125 211 252); box-shadow: inset 0 0 0 1px rgb(56 189 248 / 0.45); }
+8 -8
View File
@@ -215,12 +215,12 @@ onUnmounted(() => window.removeEventListener('keydown', onKey))
<div class="text-2xl font-semibold mt-1 tabular-nums">{{ stats.total }}</div>
</div>
<div class="card p-4">
<div class="text-[11px] uppercase tracking-wider text-indigo-300/80">图像</div>
<div class="text-2xl font-semibold mt-1 tabular-nums text-indigo-300">{{ stats.image }}</div>
<div class="text-[11px] uppercase tracking-wider text-sky-300/80">图像</div>
<div class="text-2xl font-semibold mt-1 tabular-nums text-sky-300">{{ stats.image }}</div>
</div>
<div class="card p-4">
<div class="text-[11px] uppercase tracking-wider text-fuchsia-300/80">视频</div>
<div class="text-2xl font-semibold mt-1 tabular-nums text-fuchsia-300">{{ stats.video }}</div>
<div class="text-[11px] uppercase tracking-wider text-rose-300/80">视频</div>
<div class="text-2xl font-semibold mt-1 tabular-nums text-rose-300">{{ stats.video }}</div>
</div>
<div class="card p-4">
<div class="text-[11px] uppercase tracking-wider text-amber-300/80">存储</div>
@@ -293,7 +293,7 @@ onUnmounted(() => window.removeEventListener('keydown', onKey))
<Icon name="check" class="w-3 h-3" />
</button>
<span class="inline-flex items-center gap-1 rounded-full px-2 py-0.5 text-[10px] font-medium uppercase tracking-wider ring-1"
:class="f.kind === 'video' ? 'bg-fuchsia-500/20 text-fuchsia-200 ring-fuchsia-400/30' : 'bg-indigo-500/20 text-indigo-200 ring-indigo-400/30'">
:class="f.kind === 'video' ? 'bg-rose-500/20 text-rose-200 ring-rose-400/30' : 'bg-sky-500/20 text-sky-200 ring-sky-400/30'">
{{ f.kind === 'video' ? '视频' : '图像' }}
</span>
</div>
@@ -327,8 +327,8 @@ onUnmounted(() => window.removeEventListener('keydown', onKey))
<span class="shrink-0 flex items-center gap-1">
<span v-if="f.resolution" class="text-emerald-300/90">{{ f.resolution }}</span>
<span v-if="f.ratio" class="text-white/40">{{ f.ratio }}</span>
<span v-if="f.kind === 'video' && f.duration" class="text-fuchsia-300/80">{{ f.duration }}</span>
<span v-if="f.deai" class="text-[#7c3aed]">去AI</span>
<span v-if="f.kind === 'video' && f.duration" class="text-rose-300/80">{{ f.duration }}</span>
<span v-if="f.deai" class="text-[#ec5b13]">去AI</span>
</span>
</div>
<div class="text-[10px] text-white/35 mt-0.5 tabular-nums">{{ fmtSize(f.size) }} · {{ fmtTs(f.mtime) }}</div>
@@ -438,6 +438,6 @@ html.dark .pg-on { background: rgb(255 255 255 / 0.92); color: rgb(15 23 42); }
.pick svg { opacity: 0; transition: opacity 0.15s; }
.pick:hover { background: rgb(0 0 0 / 0.65); }
.pick:hover svg { opacity: 0.6; }
.pick-on { background: rgb(217 70 239); box-shadow: inset 0 0 0 1.5px rgb(255 255 255 / 0.9); }
.pick-on { background: rgb(244 63 94); box-shadow: inset 0 0 0 1.5px rgb(255 255 255 / 0.9); }
.pick-on svg { opacity: 1; }
</style>
+2 -2
View File
@@ -36,8 +36,8 @@ onMounted(load)
<div class="text-2xl font-semibold mt-1 tabular-nums text-amber-300">{{ stats.pending }}</div>
</div>
<div class="card p-4">
<div class="text-[11px] uppercase tracking-wider text-fuchsia-300/80">已发奖励</div>
<div class="text-2xl font-semibold mt-1 tabular-nums text-fuchsia-300">{{ Number(stats.reward_paid || 0).toLocaleString('en-US') }}</div>
<div class="text-[11px] uppercase tracking-wider text-rose-300/80">已发奖励</div>
<div class="text-2xl font-semibold mt-1 tabular-nums text-rose-300">{{ Number(stats.reward_paid || 0).toLocaleString('en-US') }}</div>
</div>
</div>
+6 -6
View File
@@ -145,7 +145,7 @@ const statusDot = (s) => ({
// Source: backend stamps "v1" (API key), "user" (画图台), "admin" (后台测试模型).
const sourceLabel = (s) => ({ v1: 'API', user: '画图台', admin: '测试' }[s] || '画图台')
const sourcePill = (s) => ({
v1: 'bg-violet-500/15 text-violet-300 ring-violet-400/30',
v1: 'bg-orange-500/15 text-orange-300 ring-orange-400/30',
admin: 'bg-amber-500/15 text-amber-300 ring-amber-400/30',
user: 'bg-sky-500/15 text-sky-300 ring-sky-400/30',
}[s] || 'bg-sky-500/15 text-sky-300 ring-sky-400/30')
@@ -254,7 +254,7 @@ const sourcePill = (s) => ({
<td class="px-4 py-3.5 align-middle text-center">
<button v-if="e.status === 'success' && e.file && e.source !== 'v1'"
@click="openPreview(e)"
class="block w-12 h-12 mx-auto rounded-lg overflow-hidden ring-1 ring-white/10 hover:ring-fuchsia-400/60 transition-all">
class="block w-12 h-12 mx-auto rounded-lg overflow-hidden ring-1 ring-white/10 hover:ring-rose-400/60 transition-all">
<img v-if="e.kind !== 'video' || !thumbFail[e.id]" :src="thumbUrl(e.file)" loading="lazy"
class="w-full h-full object-cover"
@error="e.kind === 'video' && (thumbFail[e.id] = true)" />
@@ -291,7 +291,7 @@ const sourcePill = (s) => ({
<div class="font-mono text-xs text-white/90 break-all" :title="e.model">{{ e.model }}</div>
<div class="mt-1 flex items-center gap-1.5 min-w-0">
<span class="text-[10px] uppercase tracking-wider font-medium truncate min-w-0"
:class="e.kind === 'video' ? 'text-fuchsia-300/80' : 'text-indigo-300/80'">
:class="e.kind === 'video' ? 'text-rose-300/80' : 'text-sky-300/80'">
{{ e.kind === 'video' ? '视频' : '图像' }}
<span v-if="e.provider" class="text-white/30 ml-1">· {{ e.provider }}</span>
</span>
@@ -327,7 +327,7 @@ const sourcePill = (s) => ({
</template>
<template v-if="e.deai">
<span class="text-white/25 mx-1.5">·</span>
<span class="text-[#7c3aed]">去AI特征</span>
<span class="text-[#ec5b13]">去AI特征</span>
</template>
</td>
<td class="px-3 py-3.5 text-right text-xs tabular-nums align-middle whitespace-nowrap">
@@ -437,7 +437,7 @@ const sourcePill = (s) => ({
.fade-enter-from, .fade-leave-to { opacity: 0; }
/* --- log table: subtle row separators + a barely-there hover tint that
extends a soft violet accent on the left of the row (read as a
extends a soft orange accent on the left of the row (read as a
focus indicator without being noisy). --- */
.log-table { border-collapse: separate; border-spacing: 0; }
.log-row td {
@@ -446,7 +446,7 @@ const sourcePill = (s) => ({
}
.log-row:hover td { background: rgb(255 255 255 / 0.025); }
.log-row:hover td:first-child {
box-shadow: inset 2px 0 0 rgb(167 139 250 / 0.55);
box-shadow: inset 2px 0 0 rgb(253 186 116 / 0.55);
}
.log-row:last-child td { border-bottom: none; }
+9 -9
View File
@@ -96,12 +96,12 @@ onMounted(loadModels)
<div class="text-2xl font-semibold mt-1 tabular-nums">{{ stats.total }}</div>
</div>
<div class="card p-4">
<div class="text-[11px] uppercase tracking-wider text-indigo-300/80">图像</div>
<div class="text-2xl font-semibold mt-1 tabular-nums text-indigo-300">{{ stats.image }}</div>
<div class="text-[11px] uppercase tracking-wider text-sky-300/80">图像</div>
<div class="text-2xl font-semibold mt-1 tabular-nums text-sky-300">{{ stats.image }}</div>
</div>
<div class="card p-4">
<div class="text-[11px] uppercase tracking-wider text-fuchsia-300/80">视频</div>
<div class="text-2xl font-semibold mt-1 tabular-nums text-fuchsia-300">{{ stats.video }}</div>
<div class="text-[11px] uppercase tracking-wider text-rose-300/80">视频</div>
<div class="text-2xl font-semibold mt-1 tabular-nums text-rose-300">{{ stats.video }}</div>
</div>
<div class="card p-4">
<div class="text-[11px] uppercase tracking-wider text-emerald-300/80">启用</div>
@@ -187,8 +187,8 @@ onMounted(loadModels)
<td class="px-3 py-3.5 align-middle">
<span class="inline-flex items-center gap-1 rounded-full px-2.5 py-1 text-[11px] font-medium ring-1"
:class="m.type === 'video'
? 'bg-fuchsia-500/10 text-fuchsia-300 ring-fuchsia-400/30'
: 'bg-indigo-500/10 text-indigo-300 ring-indigo-400/30'">
? 'bg-rose-500/10 text-rose-300 ring-rose-400/30'
: 'bg-sky-500/10 text-sky-300 ring-sky-400/30'">
{{ TYPE_LABEL[m.type] || m.type }}
</span>
</td>
@@ -354,12 +354,12 @@ onMounted(loadModels)
white-space: nowrap;
}
.cap-dur {
background: rgb(99 102 241 / 0.12);
color: rgb(165 180 252);
background: rgb(14 165 233 / 0.12);
color: rgb(147 197 253);
box-shadow: inset 0 0 0 1px rgb(129 140 248 / 0.3);
}
.cap-frame {
background: rgb(236 72 153 / 0.12);
background: rgb(251 113 133 / 0.12);
color: rgb(244 114 182);
box-shadow: inset 0 0 0 1px rgb(244 114 182 / 0.35);
}
+3 -3
View File
@@ -140,7 +140,7 @@ async function cont(o) {
<td class="px-3 py-3 align-middle text-xs text-slate-500 whitespace-nowrap">{{ fmt(o.created_at) }}</td>
<td class="px-3 py-3 align-middle text-xs text-slate-500 whitespace-nowrap">{{ fmt(o.paid_at) }}</td>
<td class="px-3 py-3 align-middle text-right tabular-nums text-slate-800 font-medium">¥{{ o.amount }}</td>
<td class="px-3 py-3 align-middle text-right tabular-nums text-violet-600 font-medium">{{ o.points }}</td>
<td class="px-3 py-3 align-middle text-right tabular-nums text-orange-600 font-medium">{{ o.points }}</td>
<td class="px-3 py-3 align-middle">
<span class="inline-flex items-center gap-1.5 rounded-full px-2 py-0.5 text-[11px] font-medium ring-1 whitespace-nowrap" :class="statusPill(o.status)">
<span class="w-1.5 h-1.5 rounded-full" :class="statusDot(o.status)"></span>{{ STATUS[o.status] }}
@@ -149,7 +149,7 @@ async function cont(o) {
</td>
<td class="px-4 py-3 align-middle text-right">
<button v-if="o.status === 'pending'" @click="cont(o)" :disabled="continuingId === o.id"
class="rounded-lg bg-violet-600 text-white hover:bg-violet-500 disabled:opacity-60 disabled:cursor-not-allowed px-3 py-1.5 text-xs font-medium transition-colors inline-flex items-center gap-1.5">
class="rounded-lg bg-orange-600 text-white hover:bg-orange-500 disabled:opacity-60 disabled:cursor-not-allowed px-3 py-1.5 text-xs font-medium transition-colors inline-flex items-center gap-1.5">
<span v-if="continuingId === o.id" class="w-3 h-3 rounded-full border-2 border-white/40 border-t-white animate-spin"></span>
{{ continuingId === o.id ? '处理中…' : '继续支付' }}
</button>
@@ -177,7 +177,7 @@ async function cont(o) {
.log-table { border-collapse: separate; border-spacing: 0; }
.log-row td { border-bottom: 1px solid rgb(15 23 42 / 0.06); transition: background-color 0.15s ease, box-shadow 0.15s ease; }
.log-row:hover td { background: rgb(15 23 42 / 0.025); }
.log-row:hover td:first-child { box-shadow: inset 2px 0 0 rgb(124 58 237 / 0.6); }
.log-row:hover td:first-child { box-shadow: inset 2px 0 0 rgb(234 88 12 / 0.6); }
.log-row:last-child td { border-bottom: none; }
.pg {
min-width: 1.75rem; padding: 0.3rem 0.55rem; font-size: 0.72rem; font-weight: 500; text-align: center;
+10 -10
View File
@@ -162,7 +162,7 @@ onUnmounted(() => clearInterval(timer))
<div class="card p-4">
<div class="flex items-center justify-between">
<span class="text-xs text-white/55">用户</span>
<span class="w-7 h-7 rounded-lg bg-indigo-500/15 text-indigo-300 grid place-items-center ring-1 ring-indigo-400/20">
<span class="w-7 h-7 rounded-lg bg-sky-500/15 text-sky-300 grid place-items-center ring-1 ring-sky-400/20">
<Icon name="accounts" class="w-3.5 h-3.5" />
</span>
</div>
@@ -197,7 +197,7 @@ onUnmounted(() => clearInterval(timer))
<div class="card p-4">
<div class="flex items-center justify-between">
<span class="text-xs text-white/55">近 24 小时生成</span>
<span class="w-7 h-7 rounded-lg bg-violet-500/15 text-violet-300 grid place-items-center ring-1 ring-violet-400/20">
<span class="w-7 h-7 rounded-lg bg-orange-500/15 text-orange-300 grid place-items-center ring-1 ring-orange-400/20">
<Icon name="spark" class="w-3.5 h-3.5" />
</span>
</div>
@@ -223,7 +223,7 @@ onUnmounted(() => clearInterval(timer))
<div class="card p-4">
<div class="flex items-center justify-between">
<span class="text-xs text-white/55">累计生成</span>
<span class="w-7 h-7 rounded-lg bg-indigo-500/15 text-indigo-300 grid place-items-center ring-1 ring-indigo-400/20">
<span class="w-7 h-7 rounded-lg bg-sky-500/15 text-sky-300 grid place-items-center ring-1 ring-sky-400/20">
<Icon name="overview" class="w-3.5 h-3.5" />
</span>
</div>
@@ -317,7 +317,7 @@ onUnmounted(() => clearInterval(timer))
<div class="card p-4">
<div class="flex items-center justify-between">
<span class="text-xs text-white/55">邀请</span>
<span class="w-7 h-7 rounded-lg bg-fuchsia-500/15 text-fuchsia-300 grid place-items-center ring-1 ring-fuchsia-400/20">
<span class="w-7 h-7 rounded-lg bg-rose-500/15 text-rose-300 grid place-items-center ring-1 ring-rose-400/20">
<Icon name="accounts" class="w-3.5 h-3.5" />
</span>
</div>
@@ -387,8 +387,8 @@ onUnmounted(() => clearInterval(timer))
<div class="px-5 py-3 border-b border-white/[0.06] flex items-baseline justify-between">
<h2 class="text-sm font-semibold">24 小时生成趋势</h2>
<div class="text-[11px] text-white/45 flex items-center gap-3">
<span class="flex items-center gap-1"><span class="w-2 h-2 rounded-sm bg-indigo-400/80"></span>图像</span>
<span class="flex items-center gap-1"><span class="w-2 h-2 rounded-sm bg-fuchsia-400/80"></span>视频</span>
<span class="flex items-center gap-1"><span class="w-2 h-2 rounded-sm bg-sky-400/80"></span>图像</span>
<span class="flex items-center gap-1"><span class="w-2 h-2 rounded-sm bg-rose-400/80"></span>视频</span>
<span class="tabular-nums">峰值 {{ hourMax }}/h</span>
</div>
</div>
@@ -402,8 +402,8 @@ onUnmounted(() => clearInterval(timer))
whitespace-nowrap rounded-md bg-black/90 ring-1 ring-white/10 px-2 py-1 text-[10px] text-white/90 tabular-nums">
{{ 23 - i }}h 前 · 图 {{ b.image }} / 视 {{ b.video }}
</div>
<div v-if="b.video" class="bg-fuchsia-400/80 group-hover/bar:bg-fuchsia-400" :style="{ flex: b.video }"></div>
<div v-if="b.image" class="bg-indigo-400/80 group-hover/bar:bg-indigo-400" :style="{ flex: b.image }"></div>
<div v-if="b.video" class="bg-rose-400/80 group-hover/bar:bg-rose-400" :style="{ flex: b.video }"></div>
<div v-if="b.image" class="bg-sky-400/80 group-hover/bar:bg-sky-400" :style="{ flex: b.image }"></div>
<div v-if="!b.image && !b.video" class="bg-white/[0.06] group-hover/bar:bg-white/15 flex-1 rounded-t"></div>
</div>
</div>
@@ -448,7 +448,7 @@ onUnmounted(() => clearInterval(timer))
</span>
</div>
<div class="h-1.5 rounded-full bg-white/[0.06] overflow-hidden">
<div class="h-full rounded-full bg-gradient-to-r from-violet-400 to-fuchsia-500"
<div class="h-full rounded-full bg-gradient-to-r from-orange-400 to-rose-500"
:style="{ width: ((m.count / usageMax) * 100) + '%' }"></div>
</div>
</div>
@@ -544,7 +544,7 @@ onUnmounted(() => clearInterval(timer))
class="flex items-center gap-3 px-2 py-2 text-xs rounded-lg hover:bg-white/[0.03]">
<span class="w-1.5 h-1.5 rounded-full shrink-0" :class="logDot(e.status)"></span>
<span class="text-[10px] uppercase tracking-wider font-medium w-9"
:class="e.kind === 'video' ? 'text-fuchsia-300' : 'text-indigo-300'">
:class="e.kind === 'video' ? 'text-rose-300' : 'text-sky-300'">
{{ e.kind === 'video' ? '视频' : '图像' }}
</span>
<span class="font-mono text-white/85 truncate w-40 shrink-0">{{ e.model }}</span>
+3 -3
View File
@@ -889,7 +889,7 @@ onUnmounted(() => {
<div v-if="perSecondRate && secsRange">
<label class="block text-xs font-medium text-slate-500 mb-1.5">时长 <span class="text-slate-400 font-normal tabular-nums">{{ secs }}s</span></label>
<input type="range" v-model.number="secs" :min="secsRange.min" :max="secsRange.max" step="1"
class="w-full accent-indigo-500 h-2 rounded-lg appearance-none bg-slate-200 cursor-pointer" />
class="w-full accent-sky-500 h-2 rounded-lg appearance-none bg-slate-200 cursor-pointer" />
<div class="flex justify-between text-[10px] text-slate-400 mt-0.5"><span>{{ secsRange.min }}s</span><span>{{ secsRange.max }}s</span></div>
</div>
<div v-if="mode === 'video' && durations.length > 0 && !perSecondRate">
@@ -922,7 +922,7 @@ onUnmounted(() => {
</button>
</div>
<div class="flex gap-2 flex-wrap items-start rounded-lg transition-colors"
:class="dragOver ? 'ring-2 ring-indigo-400 ring-offset-2 bg-indigo-50/40' : ''"
:class="dragOver ? 'ring-2 ring-sky-400 ring-offset-2 bg-sky-50/40' : ''"
@drop="onDrop" @dragover="onDragOver" @dragleave="onDragLeave">
<div v-for="(img, i) in tileRefs" :key="img.mediaUrl || img.url || img.name + i"
class="relative w-20 h-20 rounded-lg overflow-hidden border border-slate-200 bg-slate-50 transition-all">
@@ -1058,7 +1058,7 @@ onUnmounted(() => {
<div class="pg-cap text-[11px] leading-tight font-medium line-clamp-2 transition-colors"
:class="item.prompt ? 'pointer-events-auto cursor-pointer' : ''"
:title="item.prompt ? '点击复制提示词' : ''" @click.stop="copyPrompt(item)">{{ item.prompt }}</div>
<div class="pg-cap-sub text-[9px] mt-0.5 font-mono truncate">{{ item.model }}<span v-if="item.deai"> · <span class="text-[#7c3aed]">去AI</span></span><span v-if="item.elapsed_ms"> · {{ (item.elapsed_ms / 1000).toFixed(1) }}s</span></div>
<div class="pg-cap-sub text-[9px] mt-0.5 font-mono truncate">{{ item.model }}<span v-if="item.deai"> · <span class="text-[#ec5b13]">去AI</span></span><span v-if="item.elapsed_ms"> · {{ (item.elapsed_ms / 1000).toFixed(1) }}s</span></div>
</div>
</template>
<!-- pending / running -->
+7 -7
View File
@@ -222,7 +222,7 @@ async function recharge() {
<!-- header -->
<header class="flex items-end justify-between flex-wrap gap-4">
<div>
<div class="text-[10px] uppercase tracking-[0.3em] text-violet-300/70 font-medium">账户</div>
<div class="text-[10px] uppercase tracking-[0.3em] text-orange-300/70 font-medium">账户</div>
<h1 class="mt-2 text-4xl md:text-5xl font-bold tracking-tight">设置</h1>
<p class="text-white/45 mt-2">API Key、密码、积分、登录状态 — 都在这里。</p>
</div>
@@ -235,7 +235,7 @@ async function recharge() {
<!-- ===== Account info ===== -->
<section class="card p-6">
<div class="flex items-center gap-2 mb-4">
<Icon name="accounts" class="w-4 h-4 text-violet-300" />
<Icon name="accounts" class="w-4 h-4 text-orange-300" />
<h2 class="text-sm font-semibold">账户信息</h2>
</div>
<div class="grid grid-cols-2 lg:grid-cols-5 gap-4 text-sm">
@@ -273,10 +273,10 @@ async function recharge() {
<section class="lg:col-span-2 card overflow-hidden">
<div class="p-7 md:p-8 relative">
<div class="absolute -top-16 -right-16 w-48 h-48 rounded-full opacity-30 blur-3xl"
style="background: radial-gradient(circle,#a855f7,transparent 60%)"></div>
style="background: radial-gradient(circle,#f97316,transparent 60%)"></div>
<div class="relative grid md:grid-cols-[260px_1fr] gap-8 items-start">
<div>
<div class="inline-grid w-10 h-10 rounded-xl bg-violet-500/15 ring-1 ring-violet-400/30 grid place-items-center text-violet-300">
<div class="inline-grid w-10 h-10 rounded-xl bg-orange-500/15 ring-1 ring-orange-400/30 grid place-items-center text-orange-300">
<Icon name="plug" class="w-4 h-4" />
</div>
<h2 class="text-xl font-bold mt-4">API Key</h2>
@@ -323,7 +323,7 @@ async function recharge() {
<p class="text-[11px] text-white/40 mt-3">
Key 由系统随机生成,不能手动填写。重新生成会让旧 Key 立刻失效。完整调用示例见
<router-link to="/docs" class="text-violet-300 underline">接口文档</router-link>。
<router-link to="/docs" class="text-orange-300 underline">接口文档</router-link>。
</p>
</div>
</div>
@@ -384,7 +384,7 @@ async function recharge() {
</div>
<div class="mt-5 flex items-center justify-between gap-3">
<span class="text-sm text-white/60">到账 <strong class="text-violet-300 text-base tabular-nums">{{ pointsPreview }}</strong> 积分</span>
<span class="text-sm text-white/60">到账 <strong class="text-orange-300 text-base tabular-nums">{{ pointsPreview }}</strong> 积分</span>
<button @click="recharge" :disabled="recharging"
class="rounded-xl bg-white text-black hover:bg-white/90 disabled:opacity-60 disabled:cursor-not-allowed px-6 py-2.5 text-sm font-semibold transition-colors inline-flex items-center gap-2">
<span v-if="recharging" class="w-3.5 h-3.5 rounded-full border-2 border-black/30 border-t-black animate-spin"></span>
@@ -476,7 +476,7 @@ async function recharge() {
.fade-enter-from, .fade-leave-to { opacity: 0; transform: translateY(8px); }
/* Recharge amount / method buttons — theme-aware (clean in light AND dark).
Selected uses the inverted solid-button color, not a harsh violet. */
Selected uses the inverted solid-button color, not a harsh orange. */
.amt {
border-radius: 0.6rem;
padding: 0.6rem 0;
+4 -4
View File
@@ -208,8 +208,8 @@ onMounted(refresh)
<div class="absolute inset-0 bg-gradient-to-t from-black/85 via-black/30 to-transparent"></div>
<div class="absolute top-3 left-3 flex items-center gap-1.5">
<span class="text-[10px] uppercase tracking-wider px-1.5 py-0.5 rounded ring-1"
:class="rec.kind === 'hero' ? 'bg-fuchsia-500/20 text-fuchsia-200 ring-fuchsia-400/30'
: rec.kind === 'bento' ? 'bg-violet-500/20 text-violet-200 ring-violet-400/30'
:class="rec.kind === 'hero' ? 'bg-rose-500/20 text-rose-200 ring-rose-400/30'
: rec.kind === 'bento' ? 'bg-orange-500/20 text-orange-200 ring-orange-400/30'
: 'bg-sky-500/20 text-sky-200 ring-sky-400/30'">
{{ rec.kind }}
</span>
@@ -265,7 +265,7 @@ onMounted(refresh)
<div class="p-5 space-y-4">
<!-- live preview — click or drag an image here to upload as 底图 -->
<div class="relative rounded-2xl overflow-hidden ring-1 ring-white/10 aspect-[5/2] bg-white/[0.04] cursor-pointer transition-all"
:class="scDragOver ? 'ring-2 ring-indigo-400' : ''"
:class="scDragOver ? 'ring-2 ring-sky-400' : ''"
:style="bgFor(form.image)"
@click="pickShowcaseImg" @drop="onScDrop" @dragover.prevent="scDragOver = true" @dragleave="scDragOver = false">
<div class="absolute inset-0 bg-gradient-to-t from-black/85 via-black/30 to-transparent"></div>
@@ -368,7 +368,7 @@ onMounted(refresh)
<div v-if="!recentFiles.length" class="text-center text-xs text-[color:var(--fg-faint)] py-10">尚未有生成过的图片</div>
<div v-else class="grid grid-cols-3 sm:grid-cols-4 md:grid-cols-6 gap-2">
<button v-for="f in recentFiles" :key="f.name" type="button" @click="pickImage(f)"
class="relative aspect-square rounded-lg overflow-hidden ring-1 ring-white/10 hover:ring-fuchsia-400/60 transition-all">
class="relative aspect-square rounded-lg overflow-hidden ring-1 ring-white/10 hover:ring-rose-400/60 transition-all">
<img :src="generatedUrl(f.name)" loading="lazy" class="w-full h-full object-cover" />
</button>
</div>
+6 -6
View File
@@ -70,7 +70,7 @@ onMounted(load)
const isApi = (e) => e.source === 'v1'
const sourceLabel = (e) => (isApi(e) ? 'API' : '画图台')
const sourcePill = (e) => (isApi(e)
? 'bg-violet-50 text-violet-700 ring-violet-200'
? 'bg-orange-50 text-orange-700 ring-orange-200'
: 'bg-sky-50 text-sky-700 ring-sky-200')
// 搜索走服务端(跨页),直接展示服务端返回的当页结果。
@@ -218,7 +218,7 @@ const params = (e) => {
<!-- API(v1) videos are no-store: file is an external provider URL
(not a RustFS path), so it can't be previewed in-browser — show —. -->
<button v-if="e.status === 'success' && e.file && !e.file.startsWith('http')" @click="lightbox = e"
class="block w-11 h-11 mx-auto rounded-lg overflow-hidden ring-1 ring-slate-200 hover:ring-fuchsia-300 transition-all">
class="block w-11 h-11 mx-auto rounded-lg overflow-hidden ring-1 ring-slate-200 hover:ring-rose-300 transition-all">
<img v-if="e.kind !== 'video' || !thumbFail[e.id]" :src="thumbUrl(e.file)" loading="lazy" class="w-full h-full object-cover"
@error="e.kind === 'video' && (thumbFail[e.id] = true)" />
<video v-else :src="generatedUrl(e.file)" muted preload="metadata" class="w-full h-full object-cover" />
@@ -241,7 +241,7 @@ const params = (e) => {
<div class="font-mono text-xs text-slate-800 break-all" :title="e.model">{{ e.model }}</div>
<div class="mt-0.5 flex items-center gap-1.5">
<span class="text-[10px] uppercase tracking-wider font-medium"
:class="e.kind === 'video' ? 'text-fuchsia-600' : 'text-indigo-600'">
:class="e.kind === 'video' ? 'text-rose-600' : 'text-sky-600'">
{{ e.kind === 'video' ? '视频' : '图像' }}
</span>
<span class="inline-flex items-center rounded px-1.5 py-px text-[10px] font-medium ring-1 whitespace-nowrap"
@@ -257,7 +257,7 @@ const params = (e) => {
:title="e.error + ' — 点击复制'"
@click.stop="copyError(e)">⚠ {{ e.error }}</div>
</td>
<td class="px-3 py-3 align-middle text-xs text-slate-500 tabular-nums">{{ params(e) || '—' }}<span v-if="e.deai"> · <span class="text-[#7c3aed]">去AI特征</span></span></td>
<td class="px-3 py-3 align-middle text-xs text-slate-500 tabular-nums">{{ params(e) || '—' }}<span v-if="e.deai"> · <span class="text-[#ec5b13]">去AI特征</span></span></td>
<td class="px-3 py-3 align-middle text-right text-xs text-slate-700 tabular-nums">{{ e.cost ? points(e.cost) : '—' }}</td>
<td class="px-3 py-3 align-middle text-right text-xs text-slate-500 tabular-nums">{{ e.elapsed_ms ? (e.elapsed_ms / 1000).toFixed(1) + 's' : '—' }}</td>
</tr>
@@ -299,14 +299,14 @@ const params = (e) => {
<style scoped>
/* Row hover — light-theme twin of the admin 日志 page's .log-row: a subtle
tint plus a violet accent bar on the left edge of the hovered row. */
tint plus a orange accent bar on the left edge of the hovered row. */
.log-table { border-collapse: separate; border-spacing: 0; }
.log-row td {
border-bottom: 1px solid rgb(15 23 42 / 0.06);
transition: background-color 0.15s ease, box-shadow 0.15s ease;
}
.log-row:hover td { background: rgb(15 23 42 / 0.025); }
.log-row:hover td:first-child { box-shadow: inset 2px 0 0 rgb(124 58 237 / 0.6); }
.log-row:hover td:first-child { box-shadow: inset 2px 0 0 rgb(234 88 12 / 0.6); }
.log-row:last-child td { border-bottom: none; }
/* Numbered pagination buttons — light-theme twin of the admin 日志 page's .pg */
+4 -4
View File
@@ -327,7 +327,7 @@ onUnmounted(() => {
<Icon name="check" class="w-3 h-3" />
</button>
<span class="inline-flex items-center gap-1 rounded-full px-2 py-0.5 text-[10px] font-medium uppercase tracking-wider ring-1"
:class="e.kind === 'video' ? 'bg-fuchsia-500/20 text-fuchsia-200 ring-fuchsia-400/30' : 'bg-indigo-500/20 text-indigo-200 ring-indigo-400/30'">
:class="e.kind === 'video' ? 'bg-rose-500/20 text-rose-200 ring-rose-400/30' : 'bg-sky-500/20 text-sky-200 ring-sky-400/30'">
{{ e.kind === 'video' ? '视频' : '图像' }}
</span>
</div>
@@ -360,8 +360,8 @@ onUnmounted(() => {
<span class="shrink-0 flex items-center gap-1">
<span v-if="e.resolution" class="text-emerald-300/90">{{ e.resolution }}</span>
<span v-if="e.ratio" class="text-white/40">{{ e.ratio }}</span>
<span v-if="e.kind === 'video' && e.duration" class="text-fuchsia-300/80">{{ e.duration }}</span>
<span v-if="e.deai" class="text-[#7c3aed]">去AI</span>
<span v-if="e.kind === 'video' && e.duration" class="text-rose-300/80">{{ e.duration }}</span>
<span v-if="e.deai" class="text-[#ec5b13]">去AI</span>
</span>
</div>
<div class="text-[10px] text-white/35 mt-0.5 tabular-nums">{{ fmtTs(e.ts) }}<span v-if="e.elapsed_ms"> · {{ fmtMs(e.elapsed_ms) }}</span></div>
@@ -436,6 +436,6 @@ onUnmounted(() => {
.pick svg { opacity: 0; transition: opacity 0.15s; }
.pick:hover { background: rgb(0 0 0 / 0.65); }
.pick:hover svg { opacity: 0.6; }
.pick-on { background: rgb(217 70 239); box-shadow: inset 0 0 0 1.5px rgb(255 255 255 / 0.9); }
.pick-on { background: rgb(244 63 94); box-shadow: inset 0 0 0 1.5px rgb(255 255 255 / 0.9); }
.pick-on svg { opacity: 1; }
</style>
+10 -10
View File
@@ -221,8 +221,8 @@ async function doRecharge() {
<div class="text-2xl font-semibold mt-1 tabular-nums text-emerald-300">{{ stats.active }}</div>
</div>
<div class="card p-4">
<div class="text-[11px] uppercase tracking-wider text-fuchsia-300/80">管理员</div>
<div class="text-2xl font-semibold mt-1 tabular-nums text-fuchsia-300">{{ stats.admins }}</div>
<div class="text-[11px] uppercase tracking-wider text-rose-300/80">管理员</div>
<div class="text-2xl font-semibold mt-1 tabular-nums text-rose-300">{{ stats.admins }}</div>
</div>
<div class="card p-4">
<div class="text-[11px] uppercase tracking-wider text-amber-300/80">总积分</div>
@@ -234,8 +234,8 @@ async function doRecharge() {
<div class="card p-3 flex items-center gap-3 flex-wrap">
<div class="flex items-center gap-1">
<button @click="setFilter(() => roleFilter = '')" class="fp" :class="roleFilter === '' && 'fp-on'">全部角色</button>
<button @click="setFilter(() => roleFilter = 'admin')" class="fp" :class="roleFilter === 'admin' && 'fp-fuchsia'">
<span class="w-1.5 h-1.5 rounded-full bg-fuchsia-400"></span>管理员
<button @click="setFilter(() => roleFilter = 'admin')" class="fp" :class="roleFilter === 'admin' && 'fp-orange'">
<span class="w-1.5 h-1.5 rounded-full bg-rose-400"></span>管理员
</button>
<button @click="setFilter(() => roleFilter = 'agent')" class="fp" :class="roleFilter === 'agent' && 'fp-amber'">
<span class="w-1.5 h-1.5 rounded-full bg-amber-400"></span>代理
@@ -340,12 +340,12 @@ async function doRecharge() {
<td class="px-3 py-3.5 align-middle">
<span class="inline-flex items-center gap-1.5 rounded-full px-2.5 py-1 text-[11px] font-medium ring-1 whitespace-nowrap"
:class="u.role === 'admin'
? 'bg-fuchsia-500/10 text-fuchsia-300 ring-fuchsia-400/30'
? 'bg-rose-500/10 text-rose-300 ring-rose-400/30'
: u.role === 'agent'
? 'bg-amber-500/10 text-amber-300 ring-amber-400/30'
: 'bg-white/[0.06] text-white/70 ring-white/15'">
<span class="w-1.5 h-1.5 rounded-full"
:class="u.role === 'admin' ? 'bg-fuchsia-400' : u.role === 'agent' ? 'bg-amber-400' : 'bg-slate-400'"></span>
:class="u.role === 'admin' ? 'bg-rose-400' : u.role === 'agent' ? 'bg-amber-400' : 'bg-slate-400'"></span>
{{ roleLabel(u.role) }}
</span>
</td>
@@ -596,10 +596,10 @@ async function doRecharge() {
color: rgb(253 164 175);
box-shadow: inset 0 0 0 1px rgb(253 164 175 / 0.45);
}
.fp-fuchsia {
background: rgb(217 70 239 / 0.22);
color: rgb(245 208 254);
box-shadow: inset 0 0 0 1px rgb(245 208 254 / 0.45);
.fp-orange {
background: rgb(249 115 22 / 0.22);
color: rgb(253 186 116);
box-shadow: inset 0 0 0 1px rgb(253 186 116 / 0.45);
}
.fp-amber {
background: rgb(245 158 11 / 0.22);