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:
@@ -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,11 +51,7 @@ 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