feat: 用户徽章与等级体系,积分经济管理重构

- 新增徽章系统:badge 服务与 handler、管理端配置页、BadgeChip/LevelBadge 展示组件
- 新增等级模型:model/level 及前端 levels 元数据
- 管理端经济模块拆分为 points/badges/levels 独立页面,EconomySettings 迁入设置
- 积分、签到、评论等服务适配等级与徽章逻辑
This commit is contained in:
2026-09-25 16:38:29 +08:00
parent 7b327bc8cc
commit 4ae57d9392
57 changed files with 3939 additions and 147 deletions

View File

@@ -32,6 +32,14 @@
--ok: #137333;
--ok-soft: rgba(19, 115, 51, 0.1);
/* 等级徽章与自定义徽章扩展色板 */
--info: #1c6ea8;
--info-soft: rgba(28, 110, 168, 0.1);
--purple: #6d3fc0;
--purple-soft: rgba(109, 63, 192, 0.1);
--teal: #0f7a6e;
--teal-soft: rgba(15, 122, 110, 0.1);
/* 积分看板最近流水:金额与类型标签 */
--ledger-in: #16a34a;
--ledger-out: #dc2626;
@@ -103,6 +111,18 @@
--rail-top: calc(var(--header-h) + var(--main-pad-y));
--rail-max-h: calc(100vh - var(--rail-top) - 1rem);
/* 等级徽章默认色兜底(layout.tsx 会按后台配置注入 --lv-N-* 覆盖此处;
与 lib/levels.ts 色板保持同步:灰/蓝/青/绿/红牌/黑金/鎏金,
特效档为含高光段的流动渐变,流光由高光段随 position 流动呈现) */
--lv-1-fg: #9aa3b2; --lv-1-bg: rgba(148,158,176,0.16); --lv-1-ring: transparent;
--lv-2-fg: #4d8dff; --lv-2-bg: rgba(59,118,240,0.15); --lv-2-ring: transparent;
--lv-3-fg: #12a89b; --lv-3-bg: rgba(14,148,136,0.15); --lv-3-ring: transparent;
--lv-4-fg: #27a94f; --lv-4-bg: rgba(31,156,71,0.15); --lv-4-ring: transparent;
--lv-5-fg: #ffffff; --lv-5-bg: linear-gradient(90deg, #b91c1c, #ef4444 25%, #fecaca 38%, #dc2626 52%, #991b1b 78%, #b91c1c) 0% 0% / 200% 100%; --lv-5-ring: transparent;
--lv-6-fg: #e9c46a; --lv-6-bg: linear-gradient(90deg, #16171c, #23252c 22%, #4a3d1c 36%, #23252c 52%, #16171c 78%) 0% 0% / 200% 100%; --lv-6-ring: rgba(233, 196, 106, 0.5);
--lv-7-fg: transparent; --lv-7-bg: linear-gradient(180deg, #221b0f, #100d07); --lv-7-ring: rgba(240, 200, 120, 0.45);
--lv-7-text-image: linear-gradient(90deg, #c9973f, #ffe9a8 30%, #fff6d8 46%, #f0c964 66%, #c9973f); --lv-7-text-position: 0% 0%; --lv-7-text-size: 200% 100%;
color-scheme: light;
}
@@ -133,6 +153,14 @@
--ok: #58d18a;
--ok-soft: rgba(88, 209, 138, 0.15);
/* 等级徽章与自定义徽章扩展色板 */
--info: #5db8f0;
--info-soft: rgba(93, 184, 240, 0.16);
--purple: #c39bf5;
--purple-soft: rgba(195, 155, 245, 0.16);
--teal: #4fd8c4;
--teal-soft: rgba(79, 216, 196, 0.16);
--ledger-in: #4ade80;
--ledger-out: #f87171;
--ledger-checkin-bg: rgba(22, 163, 74, 0.22);
@@ -279,6 +307,51 @@ html.j13-has-admin-bg[data-j13-chrome="admin"] body::after {
display: none;
}
/* 等级徽章特效(LevelBadge / .lv-badge):底色/前景由 SSR 注入的 --lv-N-* 变量驱动。
特效档背景为水平多段渐变(首尾同色)+ background-size 200%,
lv-flow 位移 0→200% 恰好一个图块周期(百分比 position 非线性:偏移=(100%-size)×p,
size=200% 时 p=200% 的偏移=-200% 即一个图块宽),循环首尾严格重合、无接缝。
.lv-text 为文字渐变层(--lv-N-text 存在时经 background-clip:text 呈现鎏金字,
同样走 lv-flow 流动;无渐变档 background 为 none,文字按 --lv-N-fg 正常着色) */
@keyframes lv-flow {
to {
background-position: 200% 0%;
}
}
.lv-badge {
position: relative;
overflow: hidden;
animation: lv-flow 6s linear infinite;
}
.lv-text {
animation: lv-flow 6s linear infinite;
}
/* 站点特效总开关(levels_fx 设置,layout SSR 注入 html[data-lv-fx]):
关闭时停用流光动画,徽章退化为静态配色 */
html[data-lv-fx="off"] .lv-badge,
html[data-lv-fx="off"] .lv-text {
animation: none;
}
@media (prefers-reduced-motion: reduce) {
.lv-badge,
.lv-text {
animation: none;
}
}
/* 徽章类统一禁止鼠标选择:拖选/双击正文时避免把小胶囊文字带进选区 */
.lv-badge,
.cmt-badge,
.badge-pending,
.badge-type,
.badge-access,
.badge-status,
.badge-accepted,
.j13-board-tag {
user-select: none;
-webkit-user-select: none;
}
/* 全站悬停即显提示气泡(配合 TipHost + data-tip,替代原生 title 延迟) */
.j13-tip-bubble {
position: fixed;
@@ -3079,6 +3152,9 @@ body:has([data-admin-viewport]) {
height: 100%;
box-shadow: var(--shadow-lg);
animation: admin-drawer-in 0.18s ease-out;
/* 覆盖 .admin-rail 的上下内边距并叠加刘海/手势区避让 */
padding-top: calc(1rem + env(safe-area-inset-top, 0px));
padding-bottom: calc(1.25rem + env(safe-area-inset-bottom, 0px));
}
.admin-rail-brand {
@@ -3265,6 +3341,7 @@ body:has([data-admin-viewport]) {
inset: 0;
z-index: 60;
display: flex;
overflow: hidden;
}
.admin-drawer-backdrop {
@@ -3273,7 +3350,8 @@ body:has([data-admin-viewport]) {
border: 0;
padding: 0;
cursor: pointer;
background: color-mix(in srgb, var(--ink) 28%, transparent);
background: color-mix(in srgb, var(--ink) 32%, transparent);
animation: admin-fade-in 0.18s ease-out;
}
@keyframes admin-drawer-in {
@@ -3287,8 +3365,49 @@ body:has([data-admin-viewport]) {
}
}
@keyframes admin-drawer-out {
from {
transform: translateX(0);
opacity: 1;
}
to {
transform: translateX(-100%);
opacity: 0.85;
}
}
@keyframes admin-fade-in {
from {
opacity: 0;
}
to {
opacity: 1;
}
}
@keyframes admin-fade-out {
from {
opacity: 1;
}
to {
opacity: 0;
}
}
/* 退场:面板滑出 + 背板淡出,结束后由组件卸载 */
.admin-drawer.is-closing .admin-drawer-backdrop {
animation: admin-fade-out 0.18s ease-in forwards;
}
.admin-drawer.is-closing .admin-drawer-panel {
animation: admin-drawer-out 0.18s ease-in forwards;
}
@media (prefers-reduced-motion: reduce) {
.admin-drawer-panel {
.admin-drawer-panel,
.admin-drawer-backdrop,
.admin-drawer.is-closing .admin-drawer-panel,
.admin-drawer.is-closing .admin-drawer-backdrop {
animation: none;
}
}