Update admin UI overhaul, scores , tooltip/version fixes
This commit is contained in:
@@ -55,6 +55,7 @@ public class AdminSystemController extends BaseController {
|
|||||||
private final UpgradeLogRepository upgradeLogRepository;
|
private final UpgradeLogRepository upgradeLogRepository;
|
||||||
private final AuditLogRepository auditLogRepository;
|
private final AuditLogRepository auditLogRepository;
|
||||||
private final AuditLogService auditLogService;
|
private final AuditLogService auditLogService;
|
||||||
|
private final com.yaoyuan.jiscuss.service.IScoreService scoreService;
|
||||||
|
|
||||||
public AdminSystemController(
|
public AdminSystemController(
|
||||||
UsersRepository usersRepository,
|
UsersRepository usersRepository,
|
||||||
@@ -65,7 +66,8 @@ public class AdminSystemController extends BaseController {
|
|||||||
UserRoleRepository userRoleRepository,
|
UserRoleRepository userRoleRepository,
|
||||||
UpgradeLogRepository upgradeLogRepository,
|
UpgradeLogRepository upgradeLogRepository,
|
||||||
AuditLogRepository auditLogRepository,
|
AuditLogRepository auditLogRepository,
|
||||||
AuditLogService auditLogService) {
|
AuditLogService auditLogService,
|
||||||
|
com.yaoyuan.jiscuss.service.IScoreService scoreService) {
|
||||||
this.usersRepository = usersRepository;
|
this.usersRepository = usersRepository;
|
||||||
this.discussionsRepository = discussionsRepository;
|
this.discussionsRepository = discussionsRepository;
|
||||||
this.postsRepository = postsRepository;
|
this.postsRepository = postsRepository;
|
||||||
@@ -75,6 +77,7 @@ public class AdminSystemController extends BaseController {
|
|||||||
this.upgradeLogRepository = upgradeLogRepository;
|
this.upgradeLogRepository = upgradeLogRepository;
|
||||||
this.auditLogRepository = auditLogRepository;
|
this.auditLogRepository = auditLogRepository;
|
||||||
this.auditLogService = auditLogService;
|
this.auditLogService = auditLogService;
|
||||||
|
this.scoreService = scoreService;
|
||||||
}
|
}
|
||||||
|
|
||||||
@GetMapping("/admin/home")
|
@GetMapping("/admin/home")
|
||||||
@@ -294,6 +297,27 @@ public class AdminSystemController extends BaseController {
|
|||||||
return "admin/themes";
|
return "admin/themes";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** 积分管理 — list all users sorted by score desc. */
|
||||||
|
@GetMapping("/admin/scores")
|
||||||
|
public String scores(HttpServletRequest request, ModelMap map) {
|
||||||
|
fillCommon(request, map, "scores");
|
||||||
|
List<User> users = usersRepository.findAll(Sort.by(Sort.Direction.DESC, "score"));
|
||||||
|
map.put("users", users);
|
||||||
|
return "admin/scores";
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Manually adjust a user's score (admin operation). */
|
||||||
|
@PostMapping("/admin/scores/{id}/adjust")
|
||||||
|
public String adjustScore(@PathVariable Integer id,
|
||||||
|
@RequestParam int delta,
|
||||||
|
HttpServletRequest request) {
|
||||||
|
UserInfo operator = getUserInfo(request);
|
||||||
|
scoreService.addScore(id, delta);
|
||||||
|
auditLogService.log(operator, "score_adjust", "user", id,
|
||||||
|
"score " + (delta >= 0 ? "+" : "") + delta);
|
||||||
|
return "redirect:/admin/scores";
|
||||||
|
}
|
||||||
|
|
||||||
private void fillCommon(HttpServletRequest request, ModelMap map, String active) {
|
private void fillCommon(HttpServletRequest request, ModelMap map, String active) {
|
||||||
UserInfo user = getUserInfo(request);
|
UserInfo user = getUserInfo(request);
|
||||||
map.put("adminName", user == null ? "admin" : user.getUsername());
|
map.put("adminName", user == null ? "admin" : user.getUsername());
|
||||||
@@ -327,13 +351,30 @@ public class AdminSystemController extends BaseController {
|
|||||||
result.put("java", System.getProperty("java.version"));
|
result.put("java", System.getProperty("java.version"));
|
||||||
result.put("spring", implVersion("org.springframework.core.SpringVersion"));
|
result.put("spring", implVersion("org.springframework.core.SpringVersion"));
|
||||||
result.put("hibernate", implVersion("org.hibernate.Version"));
|
result.put("hibernate", implVersion("org.hibernate.Version"));
|
||||||
result.put("flyway", implVersion("org.flywaydb.core.Flyway"));
|
result.put("flyway", mavenVersion("org.flywaydb", "flyway-core",
|
||||||
result.put("druid", implVersion("com.alibaba.druid.pool.DruidDataSource"));
|
implVersion("org.flywaydb.core.Flyway")));
|
||||||
|
result.put("druid", mavenVersion("com.alibaba", "druid-spring-boot-3-starter",
|
||||||
|
mavenVersion("com.alibaba", "druid",
|
||||||
|
implVersion("com.alibaba.druid.pool.DruidDataSource"))));
|
||||||
result.put("ehcache", implVersion("org.ehcache.CacheManager"));
|
result.put("ehcache", implVersion("org.ehcache.CacheManager"));
|
||||||
result.put("springdoc", implVersion("org.springdoc.core.configuration.SpringDocConfiguration"));
|
result.put("springdoc", implVersion("org.springdoc.core.configuration.SpringDocConfiguration"));
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** Read version from META-INF/maven pom.properties (works in fat JARs). Falls back to {@code fallback}. */
|
||||||
|
private String mavenVersion(String groupId, String artifactId, String fallback) {
|
||||||
|
String path = "/META-INF/maven/" + groupId + "/" + artifactId + "/pom.properties";
|
||||||
|
try (java.io.InputStream is = getClass().getResourceAsStream(path)) {
|
||||||
|
if (is != null) {
|
||||||
|
java.util.Properties props = new java.util.Properties();
|
||||||
|
props.load(is);
|
||||||
|
String v = props.getProperty("version");
|
||||||
|
if (v != null && !v.isBlank()) return v;
|
||||||
|
}
|
||||||
|
} catch (Exception ignored) {}
|
||||||
|
return fallback;
|
||||||
|
}
|
||||||
|
|
||||||
private String implVersion(String className) {
|
private String implVersion(String className) {
|
||||||
try {
|
try {
|
||||||
Class<?> clazz = Class.forName(className);
|
Class<?> clazz = Class.forName(className);
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
-- V10: Add score column for the points/level system.
|
-- V10: Add score column for the points/level system.
|
||||||
-- score: cumulative activity points (default 0).
|
-- score: cumulative activity points (default 0).
|
||||||
-- level is already present; it will be recomputed by ScoreServiceImpl whenever score changes.
|
-- level is recomputed by ScoreServiceImpl whenever score changes.
|
||||||
ALTER TABLE user ADD COLUMN score INTEGER NOT NULL DEFAULT 0;
|
ALTER TABLE user ADD COLUMN score INTEGER NOT NULL DEFAULT 0;
|
||||||
|
|
||||||
-- Back-fill an initial score for existing users based on their activity counts.
|
-- Back-fill an initial score for existing users based on their activity counts.
|
||||||
@@ -8,9 +8,10 @@ ALTER TABLE user ADD COLUMN score INTEGER NOT NULL DEFAULT 0;
|
|||||||
UPDATE user
|
UPDATE user
|
||||||
SET score = COALESCE(discussions_count, 0) * 10 + COALESCE(comments_count, 0) * 5;
|
SET score = COALESCE(discussions_count, 0) * 10 + COALESCE(comments_count, 0) * 5;
|
||||||
|
|
||||||
-- Recompute level for all users based on back-filled score.
|
-- Recompute level for ALL users based on back-filled score.
|
||||||
-- Thresholds: 0=新手(<50), 1=学徒(50-199), 2=熟手(200-499),
|
-- Thresholds: 0=新手(<50), 1=学徒(50-199), 2=熟手(200-499),
|
||||||
-- 3=达人(500-999), 4=专家(1000-1999), 5=大神(>=2000)
|
-- 3=达人(500-999), 4=专家(1000-1999), 5=大神(>=2000)
|
||||||
|
-- Note: admin access is now RBAC-based; level is purely cosmetic.
|
||||||
UPDATE user SET level =
|
UPDATE user SET level =
|
||||||
CASE
|
CASE
|
||||||
WHEN score >= 2000 THEN 5
|
WHEN score >= 2000 THEN 5
|
||||||
@@ -19,5 +20,4 @@ UPDATE user SET level =
|
|||||||
WHEN score >= 200 THEN 2
|
WHEN score >= 200 THEN 2
|
||||||
WHEN score >= 50 THEN 1
|
WHEN score >= 50 THEN 1
|
||||||
ELSE 0
|
ELSE 0
|
||||||
END
|
END;
|
||||||
WHERE level = 0 OR level IS NULL;
|
|
||||||
|
|||||||
@@ -9,46 +9,146 @@
|
|||||||
<title>${title}</title>
|
<title>${title}</title>
|
||||||
<#include "admin/admin-commjs.ftl"/>
|
<#include "admin/admin-commjs.ftl"/>
|
||||||
<style>
|
<style>
|
||||||
body { background: #f6f7fb; }
|
body { background: #f6f7fb; margin: 0; font-family: 'Lato', 'Helvetica Neue', Arial, sans-serif; }
|
||||||
.admin-header { background: #1f2937; color: #fff; padding: 12px 18px; }
|
|
||||||
.admin-grid { display: grid; grid-template-columns: 230px 1fr; gap: 16px; padding: 16px; }
|
/* ── Top navigation bar (matches main site style) ── */
|
||||||
.admin-sidebar { background: #fff; border-radius: 8px; padding: 8px; box-shadow: 0 1px 3px rgba(0,0,0,.08); }
|
.admin-topbar {
|
||||||
.admin-main { background: #fff; border-radius: 8px; padding: 16px; box-shadow: 0 1px 3px rgba(0,0,0,.08); }
|
background: #1b1c1d;
|
||||||
.admin-sidebar a.item.active { background: #1d4ed8 !important; color: #fff !important; border-radius: 6px; }
|
color: #fff;
|
||||||
.metric-card { border: 1px solid #e5e7eb; border-radius: 8px; padding: 12px; }
|
padding: 0 20px;
|
||||||
.small-muted { color: #6b7280; font-size: 12px; }
|
height: 50px;
|
||||||
@media (max-width: 900px) {
|
display: flex;
|
||||||
.admin-grid { grid-template-columns: 1fr; }
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
box-shadow: 0 2px 4px rgba(0,0,0,.2);
|
||||||
|
position: sticky;
|
||||||
|
top: 0;
|
||||||
|
z-index: 100;
|
||||||
}
|
}
|
||||||
|
.admin-topbar .brand { font-size: 1.05em; font-weight: 700; display: flex; align-items: center; gap: 8px; }
|
||||||
|
.admin-topbar .brand img { height: 24px; }
|
||||||
|
.admin-topbar .nav-right { display: flex; align-items: center; gap: 18px; font-size: .88em; }
|
||||||
|
.admin-topbar .nav-right a { color: #93c5fd; text-decoration: none; }
|
||||||
|
.admin-topbar .nav-right a:hover { color: #fff; }
|
||||||
|
.admin-topbar .nav-right .admin-name { color: #d1d5db; }
|
||||||
|
|
||||||
|
/* ── Layout ── */
|
||||||
|
.admin-layout { display: grid; grid-template-columns: 220px 1fr; min-height: calc(100vh - 50px - 60px); gap: 0; }
|
||||||
|
@media (max-width: 900px) { .admin-layout { grid-template-columns: 1fr; } }
|
||||||
|
|
||||||
|
/* ── Sidebar ── */
|
||||||
|
.admin-sidebar {
|
||||||
|
background: #fff;
|
||||||
|
border-right: 1px solid #e5e7eb;
|
||||||
|
padding: 12px 8px;
|
||||||
|
}
|
||||||
|
.admin-sidebar .section-label {
|
||||||
|
font-size: .72em; font-weight: 700; color: #9ca3af;
|
||||||
|
text-transform: uppercase; letter-spacing: .06em;
|
||||||
|
padding: 8px 12px 4px;
|
||||||
|
}
|
||||||
|
.admin-sidebar a.item {
|
||||||
|
display: flex; align-items: center; gap: 8px;
|
||||||
|
padding: 9px 14px; border-radius: 7px;
|
||||||
|
color: #374151; font-size: .91em;
|
||||||
|
text-decoration: none; margin: 1px 0;
|
||||||
|
transition: background .15s, color .15s;
|
||||||
|
}
|
||||||
|
.admin-sidebar a.item:hover { background: #f3f4f6; color: #111; }
|
||||||
|
.admin-sidebar a.item.active { background: #1d4ed8; color: #fff; font-weight: 600; }
|
||||||
|
.admin-sidebar a.item.active:hover { background: #1e40af; }
|
||||||
|
.admin-sidebar .divider { border-top: 1px solid #e5e7eb; margin: 8px 0; }
|
||||||
|
|
||||||
|
/* ── Main content ── */
|
||||||
|
.admin-main { padding: 24px; }
|
||||||
|
.admin-main .page-card {
|
||||||
|
background: #fff; border-radius: 10px;
|
||||||
|
box-shadow: 0 1px 4px rgba(0,0,0,.07);
|
||||||
|
padding: 24px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ── Footer ── */
|
||||||
|
.admin-footer {
|
||||||
|
background: #fff;
|
||||||
|
border-top: 1px solid #e5e7eb;
|
||||||
|
padding: 14px 24px;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
font-size: .82em;
|
||||||
|
color: #9ca3af;
|
||||||
|
}
|
||||||
|
.admin-footer a { color: #6b7280; text-decoration: none; }
|
||||||
|
.admin-footer a:hover { color: #2185d0; }
|
||||||
</style>
|
</style>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<div class="admin-header">
|
|
||||||
<div class="ui grid">
|
<div class="admin-topbar">
|
||||||
<div class="ten wide column">Jiscuss 后台管理</div>
|
<div class="brand">
|
||||||
<div class="six wide right aligned column">当前管理员: ${adminName} | <a style="color:#93c5fd" href="/swagger-ui.html" target="_blank">接口文档</a> | <a style="color:#93c5fd" href="/">前台首页</a></div>
|
<img src="/static/assets/images/logo.png" alt="logo">
|
||||||
|
Jiscuss 后台管理
|
||||||
|
</div>
|
||||||
|
<div class="nav-right">
|
||||||
|
<span class="admin-name"><i class="user circle icon"></i> ${adminName}</span>
|
||||||
|
<a href="/swagger-ui.html" target="_blank"><i class="book icon"></i> 接口文档</a>
|
||||||
|
<a href="/"><i class="home icon"></i> 前台首页</a>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="admin-grid">
|
<div class="admin-layout">
|
||||||
<div class="admin-sidebar">
|
<div class="admin-sidebar">
|
||||||
<div class="ui vertical fluid menu">
|
<div class="section-label">概览</div>
|
||||||
<a class="item <#if active=='home'>active</#if>" href="/admin/home">总览与运维</a>
|
<a class="item <#if active=='home'>active</#if>" href="/admin/home">
|
||||||
<a class="item <#if active=='users'>active</#if>" href="/admin/users">用户与角色</a>
|
<i class="tachometer alternate icon"></i>总览与运维
|
||||||
<a class="item <#if active=='discussions'>active</#if>" href="/admin/discussions">文章管理</a>
|
</a>
|
||||||
<a class="item <#if active=='posts'>active</#if>" href="/admin/posts">评论管理</a>
|
<div class="divider"></div>
|
||||||
<a class="item <#if active=='upgradeLogs'>active</#if>" href="/admin/upgrade-logs">版本升级日志</a>
|
<div class="section-label">内容管理</div>
|
||||||
<a class="item <#if active=='auditLogs'>active</#if>" href="/admin/audit-logs">操作审计日志</a>
|
<a class="item <#if active=='discussions'>active</#if>" href="/admin/discussions">
|
||||||
<div class="item small-muted">预留扩展</div>
|
<i class="list alternate outline icon"></i>文章管理
|
||||||
<a class="item <#if active=='plugins'>active</#if>" href="/admin/plugins">插件管理(预留)</a>
|
</a>
|
||||||
<a class="item <#if active=='themes'>active</#if>" href="/admin/themes">主题管理(预留)</a>
|
<a class="item <#if active=='posts'>active</#if>" href="/admin/posts">
|
||||||
</div>
|
<i class="comments outline icon"></i>评论管理
|
||||||
|
</a>
|
||||||
|
<div class="divider"></div>
|
||||||
|
<div class="section-label">用户管理</div>
|
||||||
|
<a class="item <#if active=='users'>active</#if>" href="/admin/users">
|
||||||
|
<i class="users icon"></i>用户与角色
|
||||||
|
</a>
|
||||||
|
<a class="item <#if active=='scores'>active</#if>" href="/admin/scores">
|
||||||
|
<i class="trophy icon"></i>积分管理
|
||||||
|
</a>
|
||||||
|
<div class="divider"></div>
|
||||||
|
<div class="section-label">系统</div>
|
||||||
|
<a class="item <#if active=='upgradeLogs'>active</#if>" href="/admin/upgrade-logs">
|
||||||
|
<i class="history icon"></i>升级日志
|
||||||
|
</a>
|
||||||
|
<a class="item <#if active=='auditLogs'>active</#if>" href="/admin/audit-logs">
|
||||||
|
<i class="shield alternate icon"></i>操作审计
|
||||||
|
</a>
|
||||||
|
<div class="divider"></div>
|
||||||
|
<div class="section-label">预留</div>
|
||||||
|
<a class="item <#if active=='plugins'>active</#if>" href="/admin/plugins">
|
||||||
|
<i class="plug icon"></i>插件管理
|
||||||
|
</a>
|
||||||
|
<a class="item <#if active=='themes'>active</#if>" href="/admin/themes">
|
||||||
|
<i class="paint brush icon"></i>主题管理
|
||||||
|
</a>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="admin-main">
|
<div class="admin-main">
|
||||||
|
<div class="page-card">
|
||||||
<#nested>
|
<#nested>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="admin-footer">
|
||||||
|
<span>Jiscuss 后台管理系统 © 2019-2026</span>
|
||||||
|
<span>Powered by <strong>Jiscuss V2.0</strong> | <a href="/">返回前台</a></span>
|
||||||
|
</div>
|
||||||
|
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
</#macro>
|
</#macro>
|
||||||
|
|
||||||
|
|||||||
@@ -1,50 +1,75 @@
|
|||||||
<#import "admin/admin-shell.ftl" as shell>
|
<#import "admin/admin-shell.ftl" as shell>
|
||||||
<@shell.page title="后台管理 - 总览" active=active adminName=adminName>
|
<@shell.page title="后台管理 - 总览" active=active adminName=adminName>
|
||||||
<h2 class="ui header">系统总览</h2>
|
<h2 class="ui header" style="margin-bottom:1.2em;"><i class="tachometer alternate icon"></i>系统总览</h2>
|
||||||
|
|
||||||
<div class="ui four stackable cards">
|
<div class="ui four stackable cards" style="margin-bottom:1.5em;">
|
||||||
<div class="card"><div class="content"><div class="header">用户总数</div><div class="description">${userCount}</div></div></div>
|
<div class="card" style="border-radius:8px; box-shadow:0 1px 4px rgba(0,0,0,.08);">
|
||||||
<div class="card"><div class="content"><div class="header">文章总数</div><div class="description">${discussionCount}</div></div></div>
|
|
||||||
<div class="card"><div class="content"><div class="header">评论总数</div><div class="description">${postCount}</div></div></div>
|
|
||||||
<div class="card"><div class="content"><div class="header">角色总数</div><div class="description">${roleCount}</div></div></div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<h3 class="ui dividing header">服务器与运行时</h3>
|
|
||||||
<div class="ui two stackable cards">
|
|
||||||
<div class="card">
|
|
||||||
<div class="content">
|
<div class="content">
|
||||||
<div class="header">CPU / 内存</div>
|
<div class="header" style="font-size:1em; color:#6b7280;">用户总数</div>
|
||||||
<div class="description">
|
<div class="description" style="font-size:2em; font-weight:700; color:#2185d0;">${userCount}</div>
|
||||||
<p>CPU 负载: ${sys.cpuLoad}</p>
|
|
||||||
<p>JVM 已用内存: ${sys.usedMb} MB / ${sys.totalMb} MB</p>
|
|
||||||
<p>JVM 最大内存: ${sys.maxMb} MB</p>
|
|
||||||
<p>Heap 已用: ${sys.heapUsedMb} MB</p>
|
|
||||||
<p>应用运行时长: ${sys.uptimeMs} ms</p>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
<div class="card" style="border-radius:8px; box-shadow:0 1px 4px rgba(0,0,0,.08);">
|
||||||
<div class="card">
|
|
||||||
<div class="content">
|
<div class="content">
|
||||||
<div class="header">关键依赖版本</div>
|
<div class="header" style="font-size:1em; color:#6b7280;">文章总数</div>
|
||||||
|
<div class="description" style="font-size:2em; font-weight:700; color:#21ba45;">${discussionCount}</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="card" style="border-radius:8px; box-shadow:0 1px 4px rgba(0,0,0,.08);">
|
||||||
|
<div class="content">
|
||||||
|
<div class="header" style="font-size:1em; color:#6b7280;">评论总数</div>
|
||||||
|
<div class="description" style="font-size:2em; font-weight:700; color:#f2711c;">${postCount}</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="card" style="border-radius:8px; box-shadow:0 1px 4px rgba(0,0,0,.08);">
|
||||||
|
<div class="content">
|
||||||
|
<div class="header" style="font-size:1em; color:#6b7280;">角色总数</div>
|
||||||
|
<div class="description" style="font-size:2em; font-weight:700; color:#a333c8;">${roleCount}</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<h3 class="ui dividing header"><i class="server icon"></i>服务器与运行时</h3>
|
||||||
|
<div class="ui two stackable cards" style="margin-bottom:1.5em;">
|
||||||
|
<div class="card" style="border-radius:8px; box-shadow:0 1px 4px rgba(0,0,0,.08);">
|
||||||
|
<div class="content">
|
||||||
|
<div class="header" style="font-size:1em; color:#374151; margin-bottom:.6em;"><i class="microchip icon"></i>CPU / 内存</div>
|
||||||
<div class="description">
|
<div class="description">
|
||||||
<p>Spring Boot: ${deps.springBoot}</p>
|
<p><span style="color:#888;">CPU 负载</span> <b>${sys.cpuLoad}</b></p>
|
||||||
<p>Java: ${deps.java}</p>
|
<p><span style="color:#888;">JVM 已用</span> <b>${sys.usedMb} MB</b> / ${sys.totalMb} MB</p>
|
||||||
<p>Spring: ${deps.spring}</p>
|
<p><span style="color:#888;">JVM 最大</span> <b>${sys.maxMb} MB</b></p>
|
||||||
<p>Hibernate: ${deps.hibernate}</p>
|
<p><span style="color:#888;">Heap 已用</span> <b>${sys.heapUsedMb} MB</b></p>
|
||||||
<p>Flyway: ${deps.flyway}</p>
|
<p><span style="color:#888;">运行时长</span> <b>${sys.uptimeMs} ms</b></p>
|
||||||
<p>Druid: ${deps.druid}</p>
|
</div>
|
||||||
<p>Ehcache: ${deps.ehcache}</p>
|
</div>
|
||||||
<p>SpringDoc: ${deps.springdoc}</p>
|
</div>
|
||||||
|
<div class="card" style="border-radius:8px; box-shadow:0 1px 4px rgba(0,0,0,.08);">
|
||||||
|
<div class="content">
|
||||||
|
<div class="header" style="font-size:1em; color:#374151; margin-bottom:.6em;"><i class="code branch icon"></i>关键依赖版本</div>
|
||||||
|
<div class="description">
|
||||||
|
<table style="width:100%; border-collapse:collapse; font-size:.9em;">
|
||||||
|
<tr><td style="color:#888; padding:2px 0;">Spring Boot</td><td><b>${deps.springBoot}</b></td></tr>
|
||||||
|
<tr><td style="color:#888; padding:2px 0;">Java</td><td><b>${deps.java}</b></td></tr>
|
||||||
|
<tr><td style="color:#888; padding:2px 0;">Spring</td><td><b>${deps.spring}</b></td></tr>
|
||||||
|
<tr><td style="color:#888; padding:2px 0;">Hibernate</td><td><b>${deps.hibernate}</b></td></tr>
|
||||||
|
<tr><td style="color:#888; padding:2px 0;">Flyway</td><td><b>${deps.flyway}</b></td></tr>
|
||||||
|
<tr><td style="color:#888; padding:2px 0;">Druid</td><td><b>${deps.druid}</b></td></tr>
|
||||||
|
<tr><td style="color:#888; padding:2px 0;">Ehcache</td><td><b>${deps.ehcache}</b></td></tr>
|
||||||
|
<tr><td style="color:#888; padding:2px 0;">SpringDoc</td><td><b>${deps.springdoc}</b></td></tr>
|
||||||
|
</table>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<h3 class="ui dividing header">快捷入口</h3>
|
<h3 class="ui dividing header"><i class="lightning icon"></i>快捷入口</h3>
|
||||||
<div class="ui buttons">
|
<div style="display:flex; flex-wrap:wrap; gap:10px;">
|
||||||
<a class="ui primary button" href="/admin/users">用户与角色</a>
|
<a class="ui primary button" href="/admin/users"><i class="users icon"></i>用户与角色</a>
|
||||||
<a class="ui button" href="/admin/discussions">文章管理</a>
|
<a class="ui teal button" href="/admin/scores"><i class="trophy icon"></i>积分管理</a>
|
||||||
<a class="ui button" href="/admin/posts">评论管理</a>
|
<a class="ui button" href="/admin/discussions"><i class="list alternate outline icon"></i>文章管理</a>
|
||||||
<a class="ui button" href="/swagger-ui.html" target="_blank">接口文档</a>
|
<a class="ui button" href="/admin/posts"><i class="comments outline icon"></i>评论管理</a>
|
||||||
|
<a class="ui button" href="/admin/audit-logs"><i class="shield alternate icon"></i>审计日志</a>
|
||||||
|
<a class="ui button" href="/swagger-ui.html" target="_blank"><i class="book icon"></i>接口文档</a>
|
||||||
</div>
|
</div>
|
||||||
</@shell.page>
|
</@shell.page>
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,75 @@
|
|||||||
|
<#import "admin/admin-shell.ftl" as shell>
|
||||||
|
<@shell.page title="后台管理 - 积分管理" active=active adminName=adminName>
|
||||||
|
<div style="display:flex; align-items:center; justify-content:space-between; margin-bottom:1.2em;">
|
||||||
|
<h2 class="ui header" style="margin:0;"><i class="trophy icon"></i>积分管理</h2>
|
||||||
|
<span style="color:#888; font-size:.9em;">共 ${users?size} 位用户</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<#assign lvlNames = ["新手","学徒","熟手","达人","专家","大神"]>
|
||||||
|
<#assign lvlColors = ["#95a5a6","#27ae60","#2980b9","#8e44ad","#e67e22","#e74c3c"]>
|
||||||
|
|
||||||
|
<div class="ui segments" style="margin-bottom:1.5em; background:#f8f9ff; border-radius:8px; padding:12px 16px;">
|
||||||
|
<div style="font-size:.9em; color:#555;">
|
||||||
|
<b>等级阈值:</b>
|
||||||
|
<#list 0..5 as i>
|
||||||
|
<span style="display:inline-block; margin-right:10px;">
|
||||||
|
<span style="display:inline-block; padding:1px 8px; border-radius:10px;
|
||||||
|
background:${lvlColors[i]}; color:#fff; font-size:.82em; font-weight:bold;">
|
||||||
|
Lv.${i} ${lvlNames[i]}
|
||||||
|
</span>
|
||||||
|
<span style="color:#888;">
|
||||||
|
<#if i==0><50<#elseif i==1>50–199<#elseif i==2>200–499
|
||||||
|
<#elseif i==3>500–999<#elseif i==4>1000–1999<#else>≥2000</#if>
|
||||||
|
</span>
|
||||||
|
</span>
|
||||||
|
</#list>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<table class="ui celled striped table" style="font-size:.93em;">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th style="width:50px;">ID</th>
|
||||||
|
<th>用户名</th>
|
||||||
|
<th>真实姓名</th>
|
||||||
|
<th style="width:90px; text-align:center;">等级</th>
|
||||||
|
<th style="width:80px; text-align:center;">积分</th>
|
||||||
|
<th style="width:70px; text-align:center;">发帖</th>
|
||||||
|
<th style="width:70px; text-align:center;">回复</th>
|
||||||
|
<th style="width:180px; text-align:center;">手动调整积分</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
<#list users as u>
|
||||||
|
<#assign lvl = (u.level)!0>
|
||||||
|
<#assign lvlIdx = lvl?int>
|
||||||
|
<#if lvlIdx < 0><#assign lvlIdx = 0></#if>
|
||||||
|
<#if lvlIdx > 5><#assign lvlIdx = 5></#if>
|
||||||
|
<tr>
|
||||||
|
<td>${u.id}</td>
|
||||||
|
<td><a href="/profile?uid=${u.id}" target="_blank">${u.username!}</a></td>
|
||||||
|
<td style="color:#666;">${u.realname!'-'}</td>
|
||||||
|
<td style="text-align:center;">
|
||||||
|
<span style="display:inline-block; padding:2px 10px; border-radius:12px;
|
||||||
|
background:${lvlColors[lvlIdx]}; color:#fff; font-size:.82em; font-weight:bold;">
|
||||||
|
Lv.${lvl} ${lvlNames[lvlIdx]}
|
||||||
|
</span>
|
||||||
|
</td>
|
||||||
|
<td style="text-align:center; font-weight:bold; color:#2185d0;">${(u.score)!0}</td>
|
||||||
|
<td style="text-align:center;">${(u.discussionsCount)!0}</td>
|
||||||
|
<td style="text-align:center;">${(u.commentsCount)!0}</td>
|
||||||
|
<td style="text-align:center;">
|
||||||
|
<form method="post" action="/admin/scores/${u.id}/adjust"
|
||||||
|
style="display:flex; align-items:center; gap:4px; justify-content:center;">
|
||||||
|
<input type="hidden" name="${_csrf.parameterName}" value="${_csrf.token}"/>
|
||||||
|
<input type="number" name="delta" value="0"
|
||||||
|
style="width:70px; text-align:center; border:1px solid #ddd; border-radius:4px; padding:3px 6px;"
|
||||||
|
min="-9999" max="9999">
|
||||||
|
<button type="submit" class="ui mini primary button" style="margin:0;">确定</button>
|
||||||
|
</form>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</#list>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</@shell.page>
|
||||||
@@ -333,9 +333,7 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="meta">
|
<div class="meta">
|
||||||
<div class=" description "><i class="edit icon"></i>
|
<div class=" description "><i class="edit icon"></i>
|
||||||
<a class="user-card-trigger" data-user-id="${discussions.startUserId!''}"
|
<a class="user-card-trigger" data-user-id="${discussions.startUserId!''}"><b>${discussions.username}</b></a> •
|
||||||
data-tooltip="${discussions.username}"
|
|
||||||
data-position="top center"><b>${discussions.username}</b></a> •
|
|
||||||
<i class="reply icon"></i>最后由 <a><b>${discussions.usernameLast}</b></a>
|
<i class="reply icon"></i>最后由 <a><b>${discussions.usernameLast}</b></a>
|
||||||
回复于${discussions.lastTime}.
|
回复于${discussions.lastTime}.
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
Reference in New Issue
Block a user