feat: 积分/等级系统
- V10 migration: add score column, back-fill from activity counts, recompute level - IScoreService + ScoreServiceImpl: addScore() atomically updates score and level in DB - UsersRepository.addScore(): JPQL UPDATE with inline CASE level computation - Level tiers: 0新手(<50) 1学徒(50) 2熟手(200) 3达人(500) 4专家(1000) 5大神(2000+) - Score events: - Post discussion: +10 (UserPostController) - Post reply: +5 (UserPostController) - Discussion/post liked: +2 to author; unliked/undone: -2 (LikeCollectServiceImpl) - Score self-like guard: no points awarded when user votes on own content - user-card API: expose level + score fields - user.ftl: replace raw level number with color-coded Lv.N badge + 积分 stat - discussions.ftl + index.ftl hover cards: show level badge and score Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
This commit is contained in:
@@ -74,6 +74,9 @@ public class UserPostController extends BaseController {
|
|||||||
@Autowired
|
@Autowired
|
||||||
private com.yaoyuan.jiscuss.service.INotificationService notificationService;
|
private com.yaoyuan.jiscuss.service.INotificationService notificationService;
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private com.yaoyuan.jiscuss.service.IScoreService scoreService;
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 首页查看主题列表(各种条件筛选,最热最新标签等)
|
* 首页查看主题列表(各种条件筛选,最热最新标签等)
|
||||||
@@ -183,6 +186,10 @@ public class UserPostController extends BaseController {
|
|||||||
logger.info(">>>{}", saveDiscussion);
|
logger.info(">>>{}", saveDiscussion);
|
||||||
resultobj.put("msg", "添加主题成功");
|
resultobj.put("msg", "添加主题成功");
|
||||||
resultobj.put("flag", true);
|
resultobj.put("flag", true);
|
||||||
|
// Award points for creating a discussion
|
||||||
|
if (user != null) {
|
||||||
|
scoreService.addScore(user.getId(), 10);
|
||||||
|
}
|
||||||
return resultobj;
|
return resultobj;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -242,6 +249,10 @@ public class UserPostController extends BaseController {
|
|||||||
logger.info(">>>{}", savePost);
|
logger.info(">>>{}", savePost);
|
||||||
resultobj.put("msg", "添加评论成功");
|
resultobj.put("msg", "添加评论成功");
|
||||||
resultobj.put("flag", true);
|
resultobj.put("flag", true);
|
||||||
|
// Award points for posting a reply
|
||||||
|
if (user != null) {
|
||||||
|
scoreService.addScore(user.getId(), 5);
|
||||||
|
}
|
||||||
return resultobj;
|
return resultobj;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -49,7 +49,9 @@ public class RestMsgController extends BaseController {
|
|||||||
"id", user.getId(),
|
"id", user.getId(),
|
||||||
"username", user.getUsername() != null ? user.getUsername() : "",
|
"username", user.getUsername() != null ? user.getUsername() : "",
|
||||||
"discussionsCount", user.getDiscussionsCount() != null ? user.getDiscussionsCount() : 0,
|
"discussionsCount", user.getDiscussionsCount() != null ? user.getDiscussionsCount() : 0,
|
||||||
"commentsCount", user.getCommentsCount() != null ? user.getCommentsCount() : 0
|
"commentsCount", user.getCommentsCount() != null ? user.getCommentsCount() : 0,
|
||||||
|
"level", user.getLevel() != null ? user.getLevel() : 0,
|
||||||
|
"score", user.getScore() != null ? user.getScore() : 0
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -76,4 +76,7 @@ public class User implements Serializable {
|
|||||||
|
|
||||||
@Column(name = "level")
|
@Column(name = "level")
|
||||||
private Integer level;
|
private Integer level;
|
||||||
|
|
||||||
|
@Column(name = "score")
|
||||||
|
private Integer score;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -26,6 +26,7 @@ public class UserInfo implements UserDetails {
|
|||||||
private String email;
|
private String email;
|
||||||
private String gender;
|
private String gender;
|
||||||
private Integer level;
|
private Integer level;
|
||||||
|
private Integer score;
|
||||||
private Integer flag;
|
private Integer flag;
|
||||||
|
|
||||||
public UserInfo() {
|
public UserInfo() {
|
||||||
|
|||||||
@@ -48,4 +48,17 @@ public interface UsersRepository extends JpaRepository<User, Integer> {
|
|||||||
/** Top active users by comment count (for the ranking sidebar). Shows all users if counts are 0. */
|
/** Top active users by comment count (for the ranking sidebar). Shows all users if counts are 0. */
|
||||||
@Query("FROM User u ORDER BY COALESCE(u.commentsCount, 0) DESC")
|
@Query("FROM User u ORDER BY COALESCE(u.commentsCount, 0) DESC")
|
||||||
List<User> findTop10ActiveUsers(org.springframework.data.domain.Pageable pageable);
|
List<User> findTop10ActiveUsers(org.springframework.data.domain.Pageable pageable);
|
||||||
|
|
||||||
|
@Modifying
|
||||||
|
@Transactional
|
||||||
|
@Query("UPDATE User u SET u.score = GREATEST(0, COALESCE(u.score, 0) + :delta), " +
|
||||||
|
"u.level = CASE " +
|
||||||
|
" WHEN (GREATEST(0, COALESCE(u.score, 0) + :delta)) >= 2000 THEN 5 " +
|
||||||
|
" WHEN (GREATEST(0, COALESCE(u.score, 0) + :delta)) >= 1000 THEN 4 " +
|
||||||
|
" WHEN (GREATEST(0, COALESCE(u.score, 0) + :delta)) >= 500 THEN 3 " +
|
||||||
|
" WHEN (GREATEST(0, COALESCE(u.score, 0) + :delta)) >= 200 THEN 2 " +
|
||||||
|
" WHEN (GREATEST(0, COALESCE(u.score, 0) + :delta)) >= 50 THEN 1 " +
|
||||||
|
" ELSE 0 END " +
|
||||||
|
"WHERE u.id = :id")
|
||||||
|
void addScore(@Param("id") Integer id, @Param("delta") int delta);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,42 @@
|
|||||||
|
package com.yaoyuan.jiscuss.service;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Service for awarding activity points and recomputing user levels.
|
||||||
|
*
|
||||||
|
* <p>Level thresholds (score → level):
|
||||||
|
* <ul>
|
||||||
|
* <li>0 – 49 → 0 新手</li>
|
||||||
|
* <li>50 – 199 → 1 学徒</li>
|
||||||
|
* <li>200 – 499 → 2 熟手</li>
|
||||||
|
* <li>500 – 999 → 3 达人</li>
|
||||||
|
* <li>1000 – 1999 → 4 专家</li>
|
||||||
|
* <li>2000+ → 5 大神</li>
|
||||||
|
* </ul>
|
||||||
|
*/
|
||||||
|
public interface IScoreService {
|
||||||
|
|
||||||
|
/** Award (or deduct) {@code delta} points to the user and refresh their level. */
|
||||||
|
void addScore(Integer userId, int delta);
|
||||||
|
|
||||||
|
/** Compute the level (0-5) corresponding to a given score. */
|
||||||
|
static int computeLevel(int score) {
|
||||||
|
if (score >= 2000) return 5;
|
||||||
|
if (score >= 1000) return 4;
|
||||||
|
if (score >= 500) return 3;
|
||||||
|
if (score >= 200) return 2;
|
||||||
|
if (score >= 50) return 1;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Human-readable name for a level (0-5). */
|
||||||
|
static String levelName(int level) {
|
||||||
|
return switch (level) {
|
||||||
|
case 1 -> "学徒";
|
||||||
|
case 2 -> "熟手";
|
||||||
|
case 3 -> "达人";
|
||||||
|
case 4 -> "专家";
|
||||||
|
case 5 -> "大神";
|
||||||
|
default -> "新手";
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -7,6 +7,7 @@ import com.yaoyuan.jiscuss.repository.LikeCollectRepository;
|
|||||||
import com.yaoyuan.jiscuss.repository.PostsRepository;
|
import com.yaoyuan.jiscuss.repository.PostsRepository;
|
||||||
import com.yaoyuan.jiscuss.service.ILikeCollectService;
|
import com.yaoyuan.jiscuss.service.ILikeCollectService;
|
||||||
import com.yaoyuan.jiscuss.service.INotificationService;
|
import com.yaoyuan.jiscuss.service.INotificationService;
|
||||||
|
import com.yaoyuan.jiscuss.entity.Discussion;
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
import org.springframework.stereotype.Service;
|
import org.springframework.stereotype.Service;
|
||||||
import org.springframework.transaction.annotation.Transactional;
|
import org.springframework.transaction.annotation.Transactional;
|
||||||
@@ -29,9 +30,19 @@ public class LikeCollectServiceImpl implements ILikeCollectService {
|
|||||||
@Autowired
|
@Autowired
|
||||||
private INotificationService notificationService;
|
private INotificationService notificationService;
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private com.yaoyuan.jiscuss.service.IScoreService scoreService;
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private com.yaoyuan.jiscuss.repository.UsersRepository usersRepository;
|
||||||
|
|
||||||
@Transactional
|
@Transactional
|
||||||
@Override
|
@Override
|
||||||
public String voteDiscussion(Integer userId, Integer discussionId, String action) {
|
public String voteDiscussion(Integer userId, Integer discussionId, String action) {
|
||||||
|
// Look up discussion author once for score updates
|
||||||
|
com.yaoyuan.jiscuss.entity.Discussion disc = discussionsRepository.findById(discussionId).orElse(null);
|
||||||
|
Integer authorId = (disc != null) ? disc.getStartUserId() : null;
|
||||||
|
|
||||||
Optional<LikeCollect> existing = likeCollectRepository.findDiscussionVote(userId, discussionId);
|
Optional<LikeCollect> existing = likeCollectRepository.findDiscussionVote(userId, discussionId);
|
||||||
if (existing.isPresent()) {
|
if (existing.isPresent()) {
|
||||||
LikeCollect vote = existing.get();
|
LikeCollect vote = existing.get();
|
||||||
@@ -39,6 +50,10 @@ public class LikeCollectServiceImpl implements ILikeCollectService {
|
|||||||
// Toggle off: cancel the same action
|
// Toggle off: cancel the same action
|
||||||
likeCollectRepository.delete(vote);
|
likeCollectRepository.delete(vote);
|
||||||
adjustDiscussion(discussionId, action, -1);
|
adjustDiscussion(discussionId, action, -1);
|
||||||
|
// Reverse score for the author (only likes affect score)
|
||||||
|
if ("like".equals(action) && authorId != null && !authorId.equals(userId)) {
|
||||||
|
scoreService.addScore(authorId, -2);
|
||||||
|
}
|
||||||
return "none";
|
return "none";
|
||||||
} else {
|
} else {
|
||||||
// Switch action: undo old, apply new
|
// Switch action: undo old, apply new
|
||||||
@@ -46,6 +61,14 @@ public class LikeCollectServiceImpl implements ILikeCollectService {
|
|||||||
vote.setAction(action);
|
vote.setAction(action);
|
||||||
likeCollectRepository.save(vote);
|
likeCollectRepository.save(vote);
|
||||||
adjustDiscussion(discussionId, action, +1);
|
adjustDiscussion(discussionId, action, +1);
|
||||||
|
// Switched from like→dislike: deduct; dislike→like: award
|
||||||
|
if (authorId != null && !authorId.equals(userId)) {
|
||||||
|
if ("like".equals(action)) {
|
||||||
|
scoreService.addScore(authorId, +2);
|
||||||
|
} else {
|
||||||
|
scoreService.addScore(authorId, -2);
|
||||||
|
}
|
||||||
|
}
|
||||||
return action;
|
return action;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
@@ -59,6 +82,10 @@ public class LikeCollectServiceImpl implements ILikeCollectService {
|
|||||||
vote.setCreateTime(new Date());
|
vote.setCreateTime(new Date());
|
||||||
likeCollectRepository.save(vote);
|
likeCollectRepository.save(vote);
|
||||||
adjustDiscussion(discussionId, action, +1);
|
adjustDiscussion(discussionId, action, +1);
|
||||||
|
// Award score to discussion author on new like
|
||||||
|
if ("like".equals(action) && authorId != null && !authorId.equals(userId)) {
|
||||||
|
scoreService.addScore(authorId, +2);
|
||||||
|
}
|
||||||
return action;
|
return action;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -72,12 +99,28 @@ public class LikeCollectServiceImpl implements ILikeCollectService {
|
|||||||
if (vote.getAction().equals(action)) {
|
if (vote.getAction().equals(action)) {
|
||||||
likeCollectRepository.delete(vote);
|
likeCollectRepository.delete(vote);
|
||||||
adjustPost(postId, action, -1);
|
adjustPost(postId, action, -1);
|
||||||
|
// Reverse score on unlike
|
||||||
|
if ("like".equals(action)) {
|
||||||
|
Post post = postsRepository.findById(postId).orElse(null);
|
||||||
|
if (post != null && post.getCreateId() != null && !post.getCreateId().equals(userId)) {
|
||||||
|
scoreService.addScore(post.getCreateId(), -2);
|
||||||
|
}
|
||||||
|
}
|
||||||
return "none";
|
return "none";
|
||||||
} else {
|
} else {
|
||||||
adjustPost(postId, vote.getAction(), -1);
|
adjustPost(postId, vote.getAction(), -1);
|
||||||
vote.setAction(action);
|
vote.setAction(action);
|
||||||
likeCollectRepository.save(vote);
|
likeCollectRepository.save(vote);
|
||||||
adjustPost(postId, action, +1);
|
adjustPost(postId, action, +1);
|
||||||
|
// Switched vote direction: adjust score accordingly
|
||||||
|
Post post = postsRepository.findById(postId).orElse(null);
|
||||||
|
if (post != null && post.getCreateId() != null && !post.getCreateId().equals(userId)) {
|
||||||
|
if ("like".equals(action)) {
|
||||||
|
scoreService.addScore(post.getCreateId(), +2);
|
||||||
|
} else {
|
||||||
|
scoreService.addScore(post.getCreateId(), -2);
|
||||||
|
}
|
||||||
|
}
|
||||||
return action;
|
return action;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
@@ -90,11 +133,14 @@ public class LikeCollectServiceImpl implements ILikeCollectService {
|
|||||||
vote.setCreateTime(new Date());
|
vote.setCreateTime(new Date());
|
||||||
likeCollectRepository.save(vote);
|
likeCollectRepository.save(vote);
|
||||||
adjustPost(postId, action, +1);
|
adjustPost(postId, action, +1);
|
||||||
// Notify post author when liked (not disliked)
|
// Award score to post author on new like; also send notification
|
||||||
if ("like".equals(action)) {
|
if ("like".equals(action)) {
|
||||||
Post post = postsRepository.findById(postId).orElse(null);
|
Post post = postsRepository.findById(postId).orElse(null);
|
||||||
if (post != null && post.getCreateId() != null) {
|
if (post != null && post.getCreateId() != null) {
|
||||||
notificationService.notifyLike(post.getCreateId(), userId, postId, post.getDiscussionId());
|
notificationService.notifyLike(post.getCreateId(), userId, postId, post.getDiscussionId());
|
||||||
|
if (!post.getCreateId().equals(userId)) {
|
||||||
|
scoreService.addScore(post.getCreateId(), +2);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return action;
|
return action;
|
||||||
|
|||||||
@@ -0,0 +1,23 @@
|
|||||||
|
package com.yaoyuan.jiscuss.service.impl;
|
||||||
|
|
||||||
|
import com.yaoyuan.jiscuss.repository.UsersRepository;
|
||||||
|
import com.yaoyuan.jiscuss.service.IScoreService;
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
import org.springframework.cache.annotation.CacheEvict;
|
||||||
|
import org.springframework.stereotype.Service;
|
||||||
|
import org.springframework.transaction.annotation.Transactional;
|
||||||
|
|
||||||
|
@Service
|
||||||
|
public class ScoreServiceImpl implements IScoreService {
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private UsersRepository usersRepository;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@Transactional
|
||||||
|
@CacheEvict(value = "user", key = "#userId")
|
||||||
|
public void addScore(Integer userId, int delta) {
|
||||||
|
if (userId == null || delta == 0) return;
|
||||||
|
usersRepository.addScore(userId, delta);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,23 @@
|
|||||||
|
-- V10: Add score column for the points/level system.
|
||||||
|
-- score: cumulative activity points (default 0).
|
||||||
|
-- level is already present; it will be recomputed by ScoreServiceImpl whenever score changes.
|
||||||
|
ALTER TABLE user ADD COLUMN score INTEGER NOT NULL DEFAULT 0;
|
||||||
|
|
||||||
|
-- Back-fill an initial score for existing users based on their activity counts.
|
||||||
|
-- Formula: discussions_count*10 + comments_count*5 (mirrors live award rates)
|
||||||
|
UPDATE user
|
||||||
|
SET score = COALESCE(discussions_count, 0) * 10 + COALESCE(comments_count, 0) * 5;
|
||||||
|
|
||||||
|
-- Recompute level for all users based on back-filled score.
|
||||||
|
-- Thresholds: 0=新手(<50), 1=学徒(50-199), 2=熟手(200-499),
|
||||||
|
-- 3=达人(500-999), 4=专家(1000-1999), 5=大神(>=2000)
|
||||||
|
UPDATE user SET level =
|
||||||
|
CASE
|
||||||
|
WHEN score >= 2000 THEN 5
|
||||||
|
WHEN score >= 1000 THEN 4
|
||||||
|
WHEN score >= 500 THEN 3
|
||||||
|
WHEN score >= 200 THEN 2
|
||||||
|
WHEN score >= 50 THEN 1
|
||||||
|
ELSE 0
|
||||||
|
END
|
||||||
|
WHERE level = 0 OR level IS NULL;
|
||||||
@@ -287,10 +287,15 @@
|
|||||||
var $card = $('<div id="jUserCard" style="position:fixed;background:#fff;border:1px solid #ddd;border-radius:6px;padding:12px 16px;box-shadow:0 4px 16px rgba(0,0,0,.15);z-index:9999;min-width:180px;display:none;"></div>').appendTo('body');
|
var $card = $('<div id="jUserCard" style="position:fixed;background:#fff;border:1px solid #ddd;border-radius:6px;padding:12px 16px;box-shadow:0 4px 16px rgba(0,0,0,.15);z-index:9999;min-width:180px;display:none;"></div>').appendTo('body');
|
||||||
|
|
||||||
function showCard(u, x, y) {
|
function showCard(u, x, y) {
|
||||||
|
var lvlNames = ['新手','学徒','熟手','达人','专家','大神'];
|
||||||
|
var lvlColors = ['#95a5a6','#27ae60','#2980b9','#8e44ad','#e67e22','#e74c3c'];
|
||||||
|
var lvl = Math.min(5, Math.max(0, u.level || 0));
|
||||||
|
var badge = '<span style="display:inline-block;padding:1px 8px;border-radius:10px;background:' + lvlColors[lvl] + ';color:#fff;font-size:.78em;font-weight:bold;">Lv.' + lvl + ' ' + lvlNames[lvl] + '</span>';
|
||||||
$card.html(
|
$card.html(
|
||||||
'<div style="font-weight:700;font-size:1em;margin-bottom:4px;">' + (u.username || '') + '</div>' +
|
'<div style="font-weight:700;font-size:1em;margin-bottom:4px;">' + (u.username || '') + ' ' + badge + '</div>' +
|
||||||
'<div style="color:#888;font-size:.88em;">发帖: ' + (u.discussionsCount || 0) +
|
'<div style="color:#888;font-size:.88em;">发帖: ' + (u.discussionsCount || 0) +
|
||||||
' 回复: ' + (u.commentsCount || 0) + '</div>' +
|
' 回复: ' + (u.commentsCount || 0) +
|
||||||
|
' 积分: ' + (u.score || 0) + '</div>' +
|
||||||
'<div style="margin-top:8px;display:flex;gap:12px;">' +
|
'<div style="margin-top:8px;display:flex;gap:12px;">' +
|
||||||
'<a href="/profile?uid=' + u.id + '" style="font-size:.85em;color:#2185d0;"><i class="user icon"></i>主页</a>' +
|
'<a href="/profile?uid=' + u.id + '" style="font-size:.85em;color:#2185d0;"><i class="user icon"></i>主页</a>' +
|
||||||
'<a class="open-msg-modal" data-uid="' + u.id + '" data-uname="' + (u.username||'') + '" style="font-size:.85em;color:#2185d0;cursor:pointer;"><i class="envelope icon"></i>发私信</a>' +
|
'<a class="open-msg-modal" data-uid="' + u.id + '" data-uname="' + (u.username||'') + '" style="font-size:.85em;color:#2185d0;cursor:pointer;"><i class="envelope icon"></i>发私信</a>' +
|
||||||
|
|||||||
@@ -564,10 +564,15 @@
|
|||||||
var cardCache = {};
|
var cardCache = {};
|
||||||
var $card = $('<div id="jUserCard" style="position:fixed;background:#fff;border:1px solid #ddd;border-radius:6px;padding:12px 16px;box-shadow:0 4px 16px rgba(0,0,0,.15);z-index:9999;min-width:180px;display:none;"></div>').appendTo('body');
|
var $card = $('<div id="jUserCard" style="position:fixed;background:#fff;border:1px solid #ddd;border-radius:6px;padding:12px 16px;box-shadow:0 4px 16px rgba(0,0,0,.15);z-index:9999;min-width:180px;display:none;"></div>').appendTo('body');
|
||||||
function showCard(u, x, y) {
|
function showCard(u, x, y) {
|
||||||
|
var lvlNames = ['新手','学徒','熟手','达人','专家','大神'];
|
||||||
|
var lvlColors = ['#95a5a6','#27ae60','#2980b9','#8e44ad','#e67e22','#e74c3c'];
|
||||||
|
var lvl = Math.min(5, Math.max(0, u.level || 0));
|
||||||
|
var badge = '<span style="display:inline-block;padding:1px 8px;border-radius:10px;background:' + lvlColors[lvl] + ';color:#fff;font-size:.78em;font-weight:bold;">Lv.' + lvl + ' ' + lvlNames[lvl] + '</span>';
|
||||||
$card.html(
|
$card.html(
|
||||||
'<div style="font-weight:700;font-size:1em;margin-bottom:4px;">' + (u.username || '') + '</div>' +
|
'<div style="font-weight:700;font-size:1em;margin-bottom:4px;">' + (u.username || '') + ' ' + badge + '</div>' +
|
||||||
'<div style="color:#888;font-size:.88em;">发帖: ' + (u.discussionsCount || 0) +
|
'<div style="color:#888;font-size:.88em;">发帖: ' + (u.discussionsCount || 0) +
|
||||||
' 回复: ' + (u.commentsCount || 0) + '</div>' +
|
' 回复: ' + (u.commentsCount || 0) +
|
||||||
|
' 积分: ' + (u.score || 0) + '</div>' +
|
||||||
'<div style="margin-top:8px;display:flex;gap:12px;">' +
|
'<div style="margin-top:8px;display:flex;gap:12px;">' +
|
||||||
'<a href="/profile?uid=' + u.id + '" style="font-size:.85em;color:#2185d0;"><i class="user icon"></i>主页</a>' +
|
'<a href="/profile?uid=' + u.id + '" style="font-size:.85em;color:#2185d0;"><i class="user icon"></i>主页</a>' +
|
||||||
'<a class="open-msg-modal" data-uid="' + u.id + '" data-uname="' + (u.username||'') + '" style="font-size:.85em;color:#2185d0;cursor:pointer;"><i class="envelope icon"></i>发私信</a>' +
|
'<a class="open-msg-modal" data-uid="' + u.id + '" data-uname="' + (u.username||'') + '" style="font-size:.85em;color:#2185d0;cursor:pointer;"><i class="envelope icon"></i>发私信</a>' +
|
||||||
|
|||||||
@@ -104,10 +104,19 @@
|
|||||||
<div class="label2">回复</div>
|
<div class="label2">回复</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="column profile-stat">
|
<div class="column profile-stat">
|
||||||
<div class="number">${(userEntity.level)!1}</div>
|
<div class="number">${(userEntity.score)!0}</div>
|
||||||
<div class="label2">等级</div>
|
<div class="label2">积分</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<div style="text-align:center; margin-top:10px;">
|
||||||
|
<#assign lvl = (userEntity.level)!0>
|
||||||
|
<#assign lvlName = ["新手","学徒","熟手","达人","专家","大神"][lvl?int]>
|
||||||
|
<#assign lvlColors = ["#95a5a6","#27ae60","#2980b9","#8e44ad","#e67e22","#e74c3c"]>
|
||||||
|
<span style="display:inline-block; padding:3px 12px; border-radius:20px;
|
||||||
|
background:${lvlColors[lvl?int]}; color:#fff; font-size:.85em; font-weight:bold;">
|
||||||
|
Lv.${lvl} ${lvlName}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user