feat(admin): 所有后台页统一页头(标题+说明+刷新+主操作),新默认 logo 与站名字体
- 13 个后台页统一 page-head:左标题与一行说明,右侧刷新与主操作,刷新位置一致 - 渠道/并发/违禁词/订单/邀请日志/作品/首页内容/系统配置补齐或改写标题 - 违禁词与订单页改用统一 toolbar,订单筛选胶囊改下拉 - 站名字体改站酷快乐体(英文数字 5KB 子集 + 汉字按需加载)并加粗描边 - 默认 logo 重画:墨黑圆角方块 + 白色星芒 + 暖橙点,favicon 同步 Generated with [Devin](https://devin.ai) Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
This commit is contained in:
+12
-12
@@ -1,17 +1,17 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 48 48" width="48" height="48" fill="none">
|
||||
<svg width="48" height="48" viewBox="0 0 48 48" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<defs>
|
||||
<linearGradient id="ring" x1="6" y1="6" x2="42" y2="42" gradientUnits="userSpaceOnUse">
|
||||
<stop stop-color="#e879f9"/><stop offset=".5" stop-color="#8b5cf6"/><stop offset="1" stop-color="#38bdf8"/>
|
||||
<linearGradient id="bg" x1="8" y1="4" x2="40" y2="44" gradientUnits="userSpaceOnUse">
|
||||
<stop stop-color="#3f3f46"/>
|
||||
<stop offset="1" stop-color="#18181b"/>
|
||||
</linearGradient>
|
||||
<linearGradient id="v" x1="14" y1="14" x2="34" y2="34" gradientUnits="userSpaceOnUse">
|
||||
<stop stop-color="#f0abfc"/><stop offset="1" stop-color="#8b5cf6"/>
|
||||
<linearGradient id="shine" x1="24" y1="3" x2="24" y2="24" gradientUnits="userSpaceOnUse">
|
||||
<stop stop-color="#fff" stop-opacity="0.14"/>
|
||||
<stop offset="0.9" stop-color="#fff" stop-opacity="0"/>
|
||||
</linearGradient>
|
||||
</defs>
|
||||
<g transform="translate(24 24)">
|
||||
<circle r="18.5" fill="none" stroke="url(#ring)" stroke-width="3.6" stroke-linecap="round"
|
||||
stroke-dasharray="86 30" transform="rotate(-58)"/>
|
||||
<path d="M-8 -7.5 L0 9 L8 -7.5" fill="none" stroke="url(#v)" stroke-width="5.2"
|
||||
stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<circle cx="13" cy="-13" r="3.6" fill="#38bdf8"/>
|
||||
</g>
|
||||
<rect x="2" y="2" width="44" height="44" rx="15" fill="url(#bg)"/>
|
||||
<rect x="2" y="2" width="44" height="22" rx="15" fill="url(#shine)"/>
|
||||
<path d="M22 11 Q23.4 22.6 35 24 Q23.4 25.4 22 37 Q20.6 25.4 9 24 Q20.6 22.6 22 11 Z" fill="#fff" stroke="#fff" stroke-width="2.2" stroke-linejoin="round"/>
|
||||
<path d="M35.5 9 Q36.2 13.8 41 14.5 Q36.2 15.2 35.5 20 Q34.8 15.2 30 14.5 Q34.8 13.8 35.5 9 Z" fill="#fff" stroke="#fff" stroke-width="1.4" stroke-linejoin="round" opacity="0.92"/>
|
||||
<circle cx="36" cy="36" r="2.6" fill="#fb923c"/>
|
||||
</svg>
|
||||
|
||||
|
Before Width: | Height: | Size: 934 B After Width: | Height: | Size: 1.1 KiB |
Binary file not shown.
Binary file not shown.
@@ -1,8 +1,7 @@
|
||||
<script setup>
|
||||
// Vivid mark: an open spectrum ring (lens/aperture) around a play triangle,
|
||||
// with a shutter dot closing the gap — image + video in one glyph.
|
||||
// Transparent background so it reads on both the dark and light shells.
|
||||
// Colors follow the aurora palette (violet / pink / cyan).
|
||||
// Default mark: an ink squircle holding a soft white sparkle (the "idea"),
|
||||
// a small companion star and a warm dot — cute, and on-theme with the
|
||||
// ink-black / warm-paper palette used across the site.
|
||||
// Each instance gets unique gradient ids (useId) so multiple logos on one
|
||||
// page don't share/clobber defs.
|
||||
import { useId } from 'vue'
|
||||
@@ -10,29 +9,35 @@ import { useId } from 'vue'
|
||||
defineProps({ size: { type: [Number, String], default: 40 } })
|
||||
|
||||
const uid = useId()
|
||||
const gRing = `vring-${uid}`
|
||||
const gBg = `vbg-${uid}`
|
||||
const gShine = `vshine-${uid}`
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<svg :width="size" :height="size" viewBox="0 0 48 48" fill="none"
|
||||
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="#7c5cfc" />
|
||||
<stop offset="0.5" stop-color="#e879f9" />
|
||||
<stop offset="1" stop-color="#22d3ee" />
|
||||
<linearGradient :id="gBg" x1="8" y1="4" x2="40" y2="44" gradientUnits="userSpaceOnUse">
|
||||
<stop stop-color="#3f3f46" />
|
||||
<stop offset="1" stop-color="#18181b" />
|
||||
</linearGradient>
|
||||
<linearGradient :id="gShine" x1="24" y1="3" x2="24" y2="24" gradientUnits="userSpaceOnUse">
|
||||
<stop stop-color="#fff" stop-opacity="0.14" />
|
||||
<stop offset="0.9" stop-color="#fff" stop-opacity="0" />
|
||||
</linearGradient>
|
||||
</defs>
|
||||
|
||||
<g transform="translate(24 24)">
|
||||
<!-- open ring: dash gap leaves room for the shutter dot -->
|
||||
<circle r="17.5" fill="none" :stroke="`url(#${gRing})`" stroke-width="4.4"
|
||||
stroke-linecap="round" stroke-dasharray="84 32" transform="rotate(-58)" />
|
||||
<!-- play triangle = image + video -->
|
||||
<path d="M-3 -6.5 L8.5 0 L-3 6.5 Z" fill="#7c5cfc" stroke="#111" stroke-width="1.4"
|
||||
stroke-linejoin="round" />
|
||||
<!-- shutter dot -->
|
||||
<circle cx="13" cy="-13" r="4.2" fill="#22d3ee" stroke="#111" stroke-width="1.6" />
|
||||
</g>
|
||||
<!-- squircle tile -->
|
||||
<rect x="2" y="2" width="44" height="44" rx="15" :fill="`url(#${gBg})`" />
|
||||
<rect x="2" y="2" width="44" height="22" rx="15" :fill="`url(#${gShine})`" />
|
||||
|
||||
<!-- big sparkle -->
|
||||
<path d="M22 11 Q23.4 22.6 35 24 Q23.4 25.4 22 37 Q20.6 25.4 9 24 Q20.6 22.6 22 11 Z"
|
||||
fill="#fff" stroke="#fff" stroke-width="2.2" stroke-linejoin="round" />
|
||||
<!-- small companion star -->
|
||||
<path d="M35.5 9 Q36.2 13.8 41 14.5 Q36.2 15.2 35.5 20 Q34.8 15.2 30 14.5 Q34.8 13.8 35.5 9 Z"
|
||||
fill="#fff" stroke="#fff" stroke-width="1.4" stroke-linejoin="round" opacity="0.92" />
|
||||
<!-- warm dot -->
|
||||
<circle cx="36" cy="36" r="2.6" fill="#fb923c" />
|
||||
</svg>
|
||||
</template>
|
||||
|
||||
+14
-8
@@ -347,13 +347,14 @@ button:not(:disabled), [role="button"]:not([aria-disabled="true"]) { cursor: poi
|
||||
.public-dark .hover\:bg-slate-50:hover { background: rgb(255 255 255 / 0.04); }
|
||||
.public-dark .hover\:bg-slate-100:hover { background: rgb(255 255 255 / 0.06); }
|
||||
|
||||
/* ===== 站名 logo 字:圆润可爱的字体(自托管子集)=====
|
||||
英文用 Fredoka;汉字用站酷快乐体,unicode-range 限定后只有站名含汉字时才会下载。 */
|
||||
/* ===== 站名 logo 字:站酷快乐体(自托管子集)=====
|
||||
同一字体名拆成两份:英文数字小文件 + 汉字大文件,unicode-range 限定后
|
||||
只有站名里真的含汉字时才会下载汉字那份。 */
|
||||
@font-face {
|
||||
font-family: 'Fredoka';
|
||||
src: url('/fonts/fredoka.woff2') format('woff2');
|
||||
font-weight: 300 700;
|
||||
font-family: 'ZCOOL KuaiLe';
|
||||
src: url('/fonts/zcool-kuaile-latin.woff2') format('woff2');
|
||||
font-display: swap;
|
||||
unicode-range: U+0020-007E;
|
||||
}
|
||||
@font-face {
|
||||
font-family: 'ZCOOL KuaiLe';
|
||||
@@ -362,15 +363,20 @@ button:not(:disabled), [role="button"]:not([aria-disabled="true"]) { cursor: poi
|
||||
unicode-range: U+4E00-9FFF, U+3000-303F, U+FF00-FFEF, U+2014, U+2018-201D, U+2026;
|
||||
}
|
||||
.brand-word {
|
||||
font-family: 'Fredoka', 'ZCOOL KuaiLe', ui-rounded, 'Hiragino Maru Gothic ProN', 'Arial Rounded MT Bold', sans-serif;
|
||||
font-weight: 600;
|
||||
letter-spacing: 0.01em;
|
||||
font-family: 'ZCOOL KuaiLe', ui-rounded, 'Hiragino Maru Gothic ProN', 'Arial Rounded MT Bold', sans-serif;
|
||||
font-weight: 400;
|
||||
letter-spacing: 0.03em;
|
||||
color: var(--fg);
|
||||
-webkit-text-stroke: 0.55px currentColor;
|
||||
paint-order: stroke fill;
|
||||
}
|
||||
|
||||
/* ===== 后台页头 + 统一工具栏 ===== */
|
||||
.page-head { display: flex; align-items: center; justify-content: space-between; gap: 12px; }
|
||||
.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 strong { color: var(--fg-2); font-weight: 700; }
|
||||
.page-actions { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; }
|
||||
.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 input.field:not([type]), .toolbar input.field[type="text"], .toolbar input.field[type="search"] {
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
import { ref, computed, onMounted } from 'vue'
|
||||
import { api } from '../api'
|
||||
import Icon from '../components/Icon.vue'
|
||||
import SelectMenu from '../components/SelectMenu.vue'
|
||||
|
||||
const items = ref([])
|
||||
const total = ref(0)
|
||||
@@ -27,6 +28,8 @@ function fmt(unix) {
|
||||
return `${d.getFullYear()}-${p(d.getMonth() + 1)}-${p(d.getDate())} ${p(d.getHours())}:${p(d.getMinutes())}`
|
||||
}
|
||||
|
||||
const STATUS_OPTS = [{ value: '', label: '全部状态' }, { value: 'pending', label: '待支付' }, { value: 'paid', label: '已支付' }, { value: 'cancelled', label: '已取消' }]
|
||||
const SOURCE_OPTS = [{ value: '', label: '全部来源' }, { value: 'epay', label: '在线充值' }, { value: 'admin', label: '后台充值' }, { value: 'cdk', label: '兑换码' }]
|
||||
async function load() {
|
||||
loading.value = true
|
||||
const qs = new URLSearchParams({ limit: String(pageSize), offset: String((page.value - 1) * pageSize) })
|
||||
@@ -73,26 +76,25 @@ function goPage(n) {
|
||||
|
||||
<template>
|
||||
<section class="theme-text space-y-4">
|
||||
<div class="card p-4 flex items-center justify-between gap-3 flex-wrap">
|
||||
<div class="page-head">
|
||||
<div>
|
||||
<h2 class="text-sm font-semibold">订单管理</h2>
|
||||
<p class="text-xs text-white/45 mt-0.5">{{ total }} 笔充值订单</p>
|
||||
<h2 class="page-title">订单管理</h2>
|
||||
<p class="page-sub">{{ total }} 笔充值订单</p>
|
||||
</div>
|
||||
<div class="flex items-center gap-3 flex-wrap">
|
||||
<div class="flex items-center gap-1.5">
|
||||
<button v-for="s in [['','全部'],['pending','待支付'],['paid','已支付'],['cancelled','已取消']]" :key="s[0]"
|
||||
@click="setStatus(s[0])" class="fp" :class="status === s[0] && 'fp-on'">{{ s[1] }}</button>
|
||||
</div>
|
||||
<div class="flex items-center gap-1.5">
|
||||
<button v-for="s in [['','全部来源'],['epay','在线充值'],['admin','后台充值'],['cdk','兑换码']]" :key="s[0]"
|
||||
@click="setSource(s[0])" class="fp" :class="source === s[0] && 'fp-on'">{{ s[1] }}</button>
|
||||
</div>
|
||||
<input v-model="search" @keyup.enter="doSearch" @change="doSearch"
|
||||
class="field !py-1.5 text-xs !w-52" placeholder="搜索 订单号 / 用户名 / 金额…" />
|
||||
<div class="page-actions">
|
||||
<button @click="load" class="btn-soft"><Icon name="refresh" class="w-3.5 h-3.5" /> 刷新</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="card toolbar">
|
||||
<SelectMenu :model-value="status" compact :options="STATUS_OPTS" @update:model-value="setStatus" />
|
||||
<SelectMenu :model-value="source" compact :options="SOURCE_OPTS" @update:model-value="setSource" />
|
||||
<div class="flex-1 min-w-[200px]">
|
||||
<input v-model="search" @keyup.enter="doSearch" @change="doSearch"
|
||||
class="field" placeholder="搜索 订单号 / 用户名 / 金额…" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div v-if="loading && !items.length" class="card text-center text-sm text-white/40 py-20">加载中…</div>
|
||||
<div v-else-if="!total" class="card text-center text-sm text-white/40 py-20">暂无订单</div>
|
||||
|
||||
|
||||
@@ -51,18 +51,23 @@ function goPage(n) {
|
||||
|
||||
<template>
|
||||
<section class="theme-text space-y-4">
|
||||
<div class="card p-4 flex items-center justify-between gap-3 flex-wrap">
|
||||
<div class="page-head">
|
||||
<div>
|
||||
<h2 class="text-sm font-semibold">违禁词触发列表</h2>
|
||||
<p class="text-xs text-white/45 mt-0.5">{{ total }} 条触发记录 · 每次拦截记一条(违禁词 / 用户 / 时间 / 提示词)</p>
|
||||
<h2 class="page-title">违禁词触发列表</h2>
|
||||
<p class="page-sub">{{ total }} 条触发记录 · 每次拦截记一条(违禁词 / 用户 / 时间 / 提示词)</p>
|
||||
</div>
|
||||
<div class="flex items-center gap-2">
|
||||
<input v-model="search" @keyup.enter="doSearch" @change="doSearch"
|
||||
class="field !py-1.5 text-xs !w-56" placeholder="搜索 违禁词 / 用户名 / 提示词…" />
|
||||
<div class="page-actions">
|
||||
<button @click="load" class="btn-soft"><Icon name="refresh" class="w-3.5 h-3.5" /> 刷新</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="card toolbar">
|
||||
<div class="flex-1 min-w-[200px]">
|
||||
<input v-model="search" @keyup.enter="doSearch" @change="doSearch"
|
||||
class="field" placeholder="搜索 违禁词 / 用户名 / 提示词…" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="card overflow-hidden">
|
||||
<table class="w-full text-sm table-fixed">
|
||||
<colgroup>
|
||||
|
||||
@@ -117,21 +117,25 @@ onMounted(load)
|
||||
|
||||
<template>
|
||||
<section class="theme-text space-y-4">
|
||||
<div class="card p-4 flex items-center justify-between gap-3 flex-wrap">
|
||||
<div class="page-head">
|
||||
<div>
|
||||
<h2 class="text-sm font-semibold">违禁词管理</h2>
|
||||
<p class="text-xs text-white/45 mt-0.5">提示词包含违禁词的生成请求(画图台 + API)会被<strong class="text-white/70">直接拦截</strong>,并累计触发次数(见用户管理)。匹配不区分大小写。</p>
|
||||
<h2 class="page-title">违禁词管理</h2>
|
||||
<p class="page-sub">提示词包含违禁词的生成请求(画图台 + API)会被<strong>直接拦截</strong>,并累计触发次数(见用户管理)。匹配不区分大小写。</p>
|
||||
</div>
|
||||
<div class="flex items-center gap-2">
|
||||
<button v-if="selected.size" @click="delSelected" class="btn-soft danger shrink-0" title="删除选中的违禁词">
|
||||
<Icon name="trash" class="w-3.5 h-3.5" /> 删除选中 ({{ selected.size }})
|
||||
</button>
|
||||
<input v-model="newWord" @keyup.enter="add" class="field !py-1.5 text-xs w-52" placeholder="输入违禁词后回车" />
|
||||
<button @click="add" class="btn-primary shrink-0">+ 添加</button>
|
||||
<button @click="importOpen = true" class="btn-soft shrink-0">批量导入</button>
|
||||
<div class="page-actions">
|
||||
<button @click="load" class="btn-soft"><Icon name="refresh" class="w-3.5 h-3.5" /> 刷新</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="card toolbar">
|
||||
<input v-model="newWord" @keyup.enter="add" class="field w-72" placeholder="输入违禁词后回车添加" />
|
||||
<button @click="add" class="btn-primary shrink-0">+ 添加</button>
|
||||
<button @click="importOpen = true" class="btn-soft shrink-0">批量导入</button>
|
||||
<button v-if="selected.size" @click="delSelected" class="btn-soft danger shrink-0 ml-auto" title="删除选中的违禁词">
|
||||
<Icon name="trash" class="w-3.5 h-3.5" /> 删除选中 ({{ selected.size }})
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div class="card overflow-hidden">
|
||||
<table class="w-full text-sm">
|
||||
<thead>
|
||||
|
||||
@@ -143,14 +143,15 @@ onMounted(load)
|
||||
|
||||
<template>
|
||||
<section class="space-y-4">
|
||||
<div class="card p-4 flex items-center justify-between gap-3 flex-wrap">
|
||||
<div class="page-head">
|
||||
<div>
|
||||
<div class="text-sm font-medium">渠道</div>
|
||||
<p class="text-xs text-white/45 mt-0.5">同构 /v1 协议上游实例 · 保存时自动同步上游模型 · 余额实时探测</p>
|
||||
<h2 class="page-title">渠道管理</h2>
|
||||
<p class="page-sub">同构 /v1 协议上游实例 · 保存时自动同步上游模型 · 余额实时探测</p>
|
||||
</div>
|
||||
<div class="page-actions">
|
||||
<button @click="load" class="btn-soft"><Icon name="refresh" class="w-3.5 h-3.5" /> 刷新</button>
|
||||
<button @click="openAdd" class="btn-primary"><Icon name="plus" class="w-4 h-4" /> 添加渠道</button>
|
||||
</div>
|
||||
<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>
|
||||
|
||||
@@ -51,12 +51,15 @@ onMounted(load)
|
||||
|
||||
<template>
|
||||
<section class="theme-text space-y-4">
|
||||
<div class="card p-4 flex items-center justify-between gap-3 flex-wrap">
|
||||
<div class="page-head">
|
||||
<div>
|
||||
<h2 class="text-sm font-semibold">并发分组</h2>
|
||||
<p class="text-xs text-white/45 mt-0.5">每个分组限制成员用户的<strong class="text-white/70">同时生成数</strong>(画图台 + API key 合计)。<strong class="text-white/70">0 = 不限制</strong>。新用户自动进入「默认注册分组」。</p>
|
||||
<h2 class="page-title">并发分组</h2>
|
||||
<p class="page-sub">每个分组限制成员用户的<strong>同时生成数</strong>(画图台 + API key 合计)。<strong>0 = 不限制</strong>。新用户自动进入「默认注册分组」。</p>
|
||||
</div>
|
||||
<div class="page-actions">
|
||||
<button @click="load" class="btn-soft"><Icon name="refresh" class="w-3.5 h-3.5" /> 刷新</button>
|
||||
<button @click="openNew" class="btn-primary shrink-0">+ 新增分组</button>
|
||||
</div>
|
||||
<button @click="openNew" class="btn-primary shrink-0">+ 新增分组</button>
|
||||
</div>
|
||||
|
||||
<div class="card overflow-hidden">
|
||||
|
||||
@@ -293,6 +293,15 @@ onMounted(() => { loadSite(); loadReg(); loadSmtp(); loadCredits(); loadAnnounce
|
||||
|
||||
<template>
|
||||
<section class="space-y-5">
|
||||
<div class="page-head">
|
||||
<div>
|
||||
<h2 class="page-title">系统配置</h2>
|
||||
<p class="page-sub">网站、支付、注册、邮件、代理等全站设置</p>
|
||||
</div>
|
||||
<div class="page-actions">
|
||||
|
||||
</div>
|
||||
</div>
|
||||
<!-- site -->
|
||||
<div class="card p-5">
|
||||
<div class="flex items-center justify-between mb-4">
|
||||
|
||||
@@ -207,6 +207,14 @@ onUnmounted(() => window.removeEventListener('keydown', onKey))
|
||||
|
||||
<template>
|
||||
<section class="space-y-4">
|
||||
<div class="page-head">
|
||||
<div>
|
||||
<h2 class="page-title">作品管理</h2>
|
||||
</div>
|
||||
<div class="page-actions">
|
||||
<button @click="load" class="btn-soft"><Icon name="refresh" class="w-3.5 h-3.5" /> 刷新</button>
|
||||
</div>
|
||||
</div>
|
||||
<!-- KPI strip — same shape as the LogsView so the admin shell stays
|
||||
consistent. /images returns all four numbers in one payload. -->
|
||||
<div class="grid grid-cols-2 md:grid-cols-4 gap-3">
|
||||
@@ -247,9 +255,6 @@ onUnmounted(() => window.removeEventListener('keydown', onKey))
|
||||
<Icon name="trash" class="w-3.5 h-3.5" /> 删除选中 ({{ picked.size }})
|
||||
</button>
|
||||
</template>
|
||||
<button @click="load" class="btn-soft">
|
||||
<Icon name="refresh" class="w-3.5 h-3.5" /> 刷新
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -20,6 +20,15 @@ onMounted(load)
|
||||
|
||||
<template>
|
||||
<section class="space-y-4">
|
||||
<div class="page-head">
|
||||
<div>
|
||||
<h2 class="page-title">邀请日志</h2>
|
||||
<p class="page-sub">全站邀请记录:谁邀请了谁、注册与完成时间、奖励状态</p>
|
||||
</div>
|
||||
<div class="page-actions">
|
||||
<button @click="load" class="btn-soft"><Icon name="refresh" class="w-3.5 h-3.5" /> 刷新</button>
|
||||
</div>
|
||||
</div>
|
||||
<!-- KPI strip — same shape as the LogsView so the admin shell stays
|
||||
consistent. /invites already returns the stats payload. -->
|
||||
<div class="grid grid-cols-2 md:grid-cols-4 gap-3">
|
||||
@@ -42,10 +51,6 @@ onMounted(load)
|
||||
</div>
|
||||
|
||||
<div class="card overflow-hidden">
|
||||
<div class="px-5 py-3 border-b border-white/[0.06] flex items-center justify-between">
|
||||
<div class="text-xs text-white/55">全站邀请记录:谁邀请了谁、注册与完成时间、奖励状态。</div>
|
||||
<button @click="load" class="btn-soft"><Icon name="refresh" class="w-3.5 h-3.5" /> 刷新</button>
|
||||
</div>
|
||||
|
||||
<div v-if="loading && !items.length" class="text-center text-sm text-white/40 py-16">加载中…</div>
|
||||
<div v-else-if="!items.length" class="text-center text-sm text-white/40 py-16">还没有邀请记录</div>
|
||||
|
||||
@@ -375,7 +375,7 @@ onUnmounted(() => clearInterval(timer))
|
||||
.ov-bar { display: flex; align-items: flex-end; justify-content: space-between; gap: 12px; }
|
||||
.ov-bar--sub { margin-top: 8px; align-items: center; }
|
||||
.ov-bar__right { display: flex; align-items: center; gap: 10px; }
|
||||
.ov-title { font-size: 22px; font-weight: 800; letter-spacing: -0.03em; color: var(--fg); }
|
||||
.ov-title { font-size: 20px; font-weight: 800; letter-spacing: -0.03em; color: var(--fg); }
|
||||
.ov-title--sm { font-size: 16px; }
|
||||
.ov-sub { margin-top: 2px; font-size: 12px; color: var(--fg-3); }
|
||||
.ov-btn { display: inline-flex; align-items: center; gap: 6px; height: 34px; padding: 0 14px; border-radius: 999px; border: 1px solid rgb(24 24 27 / 0.12); background: #fff; font-size: 13px; font-weight: 600; color: var(--fg); cursor: pointer; transition: border-color .15s, box-shadow .15s; }
|
||||
|
||||
@@ -179,7 +179,16 @@ onMounted(refresh)
|
||||
|
||||
<template>
|
||||
<section class="space-y-4">
|
||||
<!-- header / filter / new -->
|
||||
<div class="page-head">
|
||||
<div>
|
||||
<h2 class="page-title">首页内容</h2>
|
||||
<p class="page-sub">首页首屏拼贴(Hero)与作品墙(Work)的展示图</p>
|
||||
</div>
|
||||
<div class="page-actions">
|
||||
<button @click="refresh" class="btn-soft"><Icon name="refresh" class="w-3.5 h-3.5" /> 刷新</button>
|
||||
</div>
|
||||
</div>
|
||||
<!-- filter / new -->
|
||||
<div class="card p-4 flex items-center justify-between gap-3 flex-wrap">
|
||||
<div class="flex items-center gap-1.5">
|
||||
<button @click="setFilter('all')" class="filter-pill" :class="filter === 'all' && 'on'">全部</button>
|
||||
|
||||
Reference in New Issue
Block a user