Update Search footer header index user

This commit is contained in:
2026-05-13 17:40:26 +08:00
parent cda8a3e0ee
commit aeb0866ec1
12 changed files with 477 additions and 107 deletions
@@ -88,7 +88,7 @@ public class WebSecurityConfig {
auth.requestMatchers(
"/css/**", "/js/**", "/images/**", "/static/**", "/favicon.ico",
"/login/**", "/register", "/registerDo", "/initUserData",
"/", "/main", "/index", "/getdiscussionsbyid",
"/", "/main", "/index", "/getdiscussionsbyid", "/search", "/profile",
// SpringDoc OpenAPI UI and spec endpoint (developer tools, no sensitive data)
"/swagger-ui/**", "/swagger-ui.html", "/v3/api-docs/**"
).permitAll();
@@ -0,0 +1,30 @@
package com.yaoyuan.jiscuss.controller;
import com.yaoyuan.jiscuss.entity.UserInfo;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpSession;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.context.SecurityContext;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ModelAttribute;
/**
* Injects common model attributes (e.g. isAdmin) into every rendered view.
*/
@ControllerAdvice
public class GlobalModelAdvice {
@ModelAttribute("isAdmin")
public boolean isAdmin(HttpServletRequest request) {
HttpSession session = request.getSession(false);
if (session == null) return false;
Object ctx = session.getAttribute("SPRING_SECURITY_CONTEXT");
if (!(ctx instanceof SecurityContext sc)) return false;
Authentication auth = sc.getAuthentication();
if (auth == null || !(auth.getPrincipal() instanceof UserInfo)) return false;
return auth.getAuthorities().stream()
.map(GrantedAuthority::getAuthority)
.anyMatch(a -> a.equals("ROLE_ADMIN"));
}
}
@@ -18,6 +18,7 @@ import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;
import org.springframework.stereotype.Controller;
import org.springframework.ui.ModelMap;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
@@ -100,6 +101,54 @@ public class UserPageController extends BaseController {
map.put("likedDiscussions", liked);
}
map.put("isOwn", true);
return "user";
}
/**
* 公开用户主页 /profile?uid=xxx (无需登录)
*/
@GetMapping("/profile")
public String profile(@RequestParam Integer uid,
@RequestParam(defaultValue = "discussion") String type,
HttpServletRequest request, ModelMap map) {
User userEntity = usersService.findOne(uid);
if (userEntity == null) return "redirect:/";
long liveDiscCount = discussionsRepository.countByStartUserId(uid);
long liveReplyCount = postsRepository.countByCreateId(uid);
map.put("username", userEntity.getUsername());
map.put("userEntity", userEntity);
map.put("liveDiscCount", liveDiscCount);
map.put("liveReplyCount", liveReplyCount);
map.put("type", type);
map.put("isOwn", false);
if ("discussion".equals(type)) {
Page<Discussion> discussions = discussionsRepository.findByStartUserIdOrderByStartTimeDesc(
uid, PageRequest.of(0, 20));
map.put("discussions", discussions.getContent());
} else if ("reply".equals(type)) {
Page<Post> posts = postsRepository.findByCreateIdOrderByCreateTimeDesc(
uid, PageRequest.of(0, 20));
map.put("posts", posts.getContent());
List<String> discTitles = new ArrayList<>();
for (Post p : posts.getContent()) {
Discussion d = p.getDiscussionId() != null
? discussionsRepository.findById(p.getDiscussionId()).orElse(null) : null;
discTitles.add(d != null ? d.getTitle() : "");
}
map.put("discTitles", discTitles);
} else if ("like".equals(type)) {
List<Integer> likedIds = likeCollectRepository.findLikedDiscussionIdsByUser(uid);
List<Discussion> liked = new ArrayList<>();
for (Integer id : likedIds) {
Discussion d = discussionsRepository.findById(id).orElse(null);
if (d != null) liked.add(d);
}
map.put("likedDiscussions", liked);
}
return "user";
}
}
@@ -7,6 +7,8 @@ import com.yaoyuan.jiscuss.entity.Tag;
import com.yaoyuan.jiscuss.entity.User;
import com.yaoyuan.jiscuss.entity.UserInfo;
import com.yaoyuan.jiscuss.entity.custom.DiscussionCustom;
import com.yaoyuan.jiscuss.repository.DiscussionsRepository;
import com.yaoyuan.jiscuss.repository.PostsRepository;
import com.yaoyuan.jiscuss.service.IDiscussionsService;
import com.yaoyuan.jiscuss.service.IDiscussionsTagsService;
import com.yaoyuan.jiscuss.service.IPostsService;
@@ -19,6 +21,7 @@ import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.PageRequest;
import org.springframework.stereotype.Controller;
import org.springframework.ui.ModelMap;
import org.springframework.web.bind.annotation.PostMapping;
@@ -59,6 +62,12 @@ public class UserPostController extends BaseController {
@Autowired
private IUsersService usersService;
@Autowired
private DiscussionsRepository discussionsRepository;
@Autowired
private PostsRepository postsRepository;
@Autowired
private IpRegionService ipRegionService;
@@ -112,6 +121,19 @@ public class UserPostController extends BaseController {
map.put("discussions", newdd);
map.put("posts", postsObj);
map.put("sort", sort);
// Sidebar: author stats and top discussions
if (newdd.getStartUserId() != null) {
Integer authorId = newdd.getStartUserId();
long authorDiscCount = discussionsRepository.countByStartUserId(authorId);
long authorReplyCount = postsRepository.countByCreateId(authorId);
java.util.List<Discussion> authorTopDiscs = discussionsRepository
.findTopByStartUserIdOrderByLikes(authorId, PageRequest.of(0, 5));
map.put("authorDiscCount", authorDiscCount);
map.put("authorReplyCount", authorReplyCount);
map.put("authorTopDiscs", authorTopDiscs);
}
UserInfo user = getUserInfo(request);
if (user != null) {
map.put("username", user.getUsername());
@@ -6,6 +6,7 @@ import com.yaoyuan.jiscuss.entity.User;
import com.yaoyuan.jiscuss.entity.UserInfo;
import com.yaoyuan.jiscuss.entity.custom.DiscussionCustom;
import com.yaoyuan.jiscuss.entity.custom.TagCustom;
import com.yaoyuan.jiscuss.repository.DiscussionsRepository;
import com.yaoyuan.jiscuss.service.IDiscussionsService;
import com.yaoyuan.jiscuss.service.ITagsService;
import com.yaoyuan.jiscuss.service.IUsersService;
@@ -54,6 +55,9 @@ public class UserSystemController extends BaseController {
@Autowired
private BCryptPasswordEncoder passwordEncoder;
@Autowired
private DiscussionsRepository discussionsRepository;
/**
* 首页index
* @param tag
@@ -276,6 +280,36 @@ public class UserSystemController extends BaseController {
return "register";
}
/**
* 搜索页
*/
@GetMapping("/search")
public String search(@RequestParam(defaultValue = "") String q,
@RequestParam(defaultValue = "1") Integer pageNum,
HttpServletRequest request, ModelMap map) {
String keyword = q.trim();
int pageSize = 10;
int page = Math.max(pageNum - 1, 0);
Page<Discussion> resultPage = keyword.isEmpty()
? Page.empty()
: discussionsRepository.searchByKeyword(keyword,
org.springframework.data.domain.PageRequest.of(page, pageSize));
List<DiscussionCustom> results = new ArrayList<>();
if (!keyword.isEmpty()) {
setTagAndUserList(results, resultPage.getContent());
}
map.put("q", keyword);
map.put("results", results);
map.put("totalPages", resultPage.getTotalPages());
map.put("totalElements", resultPage.getTotalElements());
map.put("pageNum", pageNum);
UserInfo user = getUserInfo(request);
if (user != null) map.put("username", user.getUsername());
return "search";
}
//获取设置信息
//获取首页统计
@@ -10,6 +10,7 @@ import org.springframework.stereotype.Repository;
import org.springframework.transaction.annotation.Transactional;
import java.util.Date;
import java.util.List;
@Repository
public interface DiscussionsRepository extends JpaRepository<Discussion, Integer> {
@@ -49,4 +50,14 @@ public interface DiscussionsRepository extends JpaRepository<Discussion, Integer
@org.springframework.data.repository.query.Param("userId") Integer userId,
org.springframework.data.domain.Pageable pageable);
long countByStartUserId(Integer startUserId);
/** Author's top discussions ordered by likeCount (for sidebar). */
@Query("SELECT d FROM Discussion d WHERE d.startUserId = :userId ORDER BY COALESCE(d.likeCount, 0) DESC, d.startTime DESC")
List<Discussion> findTopByStartUserIdOrderByLikes(
@org.springframework.data.repository.query.Param("userId") Integer userId,
Pageable pageable);
/** Full-text search by title or content. */
@Query("SELECT d FROM Discussion d WHERE d.title LIKE %:q% OR d.content LIKE %:q% ORDER BY d.startTime DESC")
Page<Discussion> searchByKeyword(@org.springframework.data.repository.query.Param("q") String q, Pageable pageable);
}
+8 -6
View File
@@ -29,20 +29,22 @@
</div>
</div>
<div class="seven wide column">
<h4 class="ui header">这里是页脚预留信息</h4>
<p>This is about Jiscuus Page
.</p>
<h4 class="ui header">关于 Jiscuss</h4>
<p>一个简单、易用的 Java 开源论坛,基于 Spring Boot + Semantic UI 构建。</p>
</div>
</div>
<div class="ui section divider"></div>
<img src="static/assets/images/logo.png" class="ui centered mini image">
@ 2019-2020 &nbsp;
<div class="ui horizontal small divided link list">
<img src="/static/assets/images/logo.png" class="ui centered mini image">
&copy; 2019-2026 &nbsp;
<div class="ui horizontal small divided link list">
<a class="item" href="fixed.php#">站点地图</a>
<a class="item" href="fixed.php#">关于这里</a>
<a class="item" href="fixed.php#">团队介绍</a>
<a class="item" href="fixed.php#">隐私政策</a>
</div>
<div style="margin-top:8px; color:#aaa; font-size:.82em;">
Powered by <strong>Jiscuss V2.0</strong>
</div>
</div>
</div>
<script>if(typeof NProgress !== 'undefined') NProgress.done();</script>
+26 -23
View File
@@ -6,22 +6,11 @@
</div>
<a class=" item borderless" href="/">首页</a>
<a class="active borderless item">论坛</a>
<div class="ui dropdown item borderless" tabindex="0">
更多
<i class="dropdown icon"></i>
<div class="menu transition hidden" tabindex="-1">
<a class=" item " href="/user">用户页</a>
<div class="divider"></div>
<div class="item">分割链接</div>
<div class="divider"></div>
<a class="item" href="/admin/home">后台管理</a>
</div>
</div>
<div class="right menu">
<div class="item">
<div class="ui transparent icon input">
<div class="ui transparent icon input" id="headerSearchBox">
<i class="search icon"></i>
<input type="text" placeholder="搜索">
<input type="text" id="headerSearchInput" placeholder="搜索" onkeydown="if(event.key==='Enter'&&this.value.trim()){window.location='/search?q='+encodeURIComponent(this.value.trim());}">
</div>
</div>
<div class="item">
@@ -44,17 +33,29 @@
style="display:none; position:absolute; top:-4px; right:-8px; font-size:10px; padding:2px 4px;"></span>
</a>
</div>
</#if>
<div class="item" id="userlogin">
<#if username??>
<form class="ui large form" action="/logout" id="logoutForm" method="post" style="display: none;">
<input type="hidden" name="${_csrf.parameterName}" value="${_csrf.token}"/>
</form>
${username} &nbsp;&nbsp;<a href="javascript:document.getElementById('logoutForm').submit();">注销</a>
<#else>
<a href="/login" id="loginmodel">登录&nbsp;&nbsp;&&nbsp;&nbsp;注册</a>
</#if>
<div class="ui dropdown item borderless" tabindex="0">
<i class="user circle icon"></i>${username}
<i class="dropdown icon"></i>
<div class="menu transition hidden" tabindex="-1">
<a class="item" href="/user"><i class="id card outline icon"></i>&nbsp;个人主页</a>
<#if isAdmin?? && isAdmin>
<div class="divider"></div>
<a class="item" href="/admin/home"><i class="settings icon"></i>&nbsp;后台管理</a>
</#if>
<div class="divider"></div>
<a class="item" onclick="document.getElementById('logoutForm').submit();" style="cursor:pointer;">
<i class="sign out alternate icon"></i>&nbsp;注销
</a>
</div>
</div>
<form action="/logout" id="logoutForm" method="post" style="display:none;">
<input type="hidden" name="${_csrf.parameterName}" value="${_csrf.token}"/>
</form>
<#else>
<div class="item" id="userlogin">
<a href="/login" id="loginmodel">登录&nbsp;&nbsp;&amp;&nbsp;&nbsp;注册</a>
</div>
</#if>
</div>
</div>
</div>
@@ -66,6 +67,8 @@
var saved = localStorage.getItem('jiscuss-theme') || 'light';
if (saved === 'dark') { document.documentElement.setAttribute('data-theme', 'dark'); }
document.addEventListener('DOMContentLoaded', function() {
// Initialize user dropdown
$('.ui.dropdown').dropdown();
var btn = document.getElementById('themeToggle');
var icon = document.getElementById('themeIcon');
function applyTheme(t) {
+34 -73
View File
@@ -187,10 +187,10 @@
</div>
<div class="widescreen large screen computer tablet only four wide column">
<div class="ui fluid action input">
<input type="text" placeholder="搜索...">
<div class="ui button">搜索</div>
</div>
<form action="/search" method="get" class="ui fluid action input">
<input type="text" name="q" placeholder="搜索主题..." id="sidebarSearch">
<button type="submit" class="ui button">搜索</button>
</form>
<div class="ui section divider"></div>
@@ -199,90 +199,48 @@
<div class="header">作者</div>
</div>
<div class="content">
<h4 class="ui sub header">测试人员1</h4>
<div class="ui small feed">
<div class="event">
<div class="content">
<div class="summary">
<a>Jiscuss</a> 一直, <a>Jiscuss</a> 一感谢支持!
</div>
</div>
<h4 class="ui sub header">
<a class="user-card-trigger" data-user-id="${discussions.startUserId!''}">${discussions.realname!''}</a>
</h4>
<div class="ui small statistics" style="margin-top:8px;">
<div class="statistic">
<div class="value">${authorDiscCount!0}</div>
<div class="label">发帖</div>
</div>
<div class="event">
<div class="content">
<div class="summary">
<a>2019</a> 测试内容
</div>
</div>
<div class="statistic">
<div class="value">${authorReplyCount!0}</div>
<div class="label">回复</div>
</div>
</div>
</div>
<div class="extra content">
<button class="ui button">预留按钮</button>
</div>
</div>
<#if authorTopDiscs?? && authorTopDiscs?size gt 0>
<div class="ui card">
<div class="content">
<div class="header">作者热门主题</div>
<div class="header">作者其他主题</div>
</div>
<div class="content">
<div class="content" style="padding:0;">
<div class="ui middle aligned divided list">
<div class="item">
<img class="ui avatar image" src="/static/assets/images/logo.png">
<#list authorTopDiscs as d>
<#if d.id != discussions.id>
<div class="item" style="padding:8px 12px;">
<div class="content">
<a class="header">its here</a>
<a class="header" href="/getdiscussionsbyid?id=${d.id}" style="font-size:.9em;font-weight:normal;">
${(d.title?length > 30)?then(d.title?substring(0,30) + '…', d.title)}
</a>
<div class="description" style="font-size:.8em;color:#999;">
<i class="heart icon"></i>${d.likeCount!0}
&nbsp;<i class="comment icon"></i>${d.commentsCount!0}
</div>
</div>
</div>
<div class="item">
<img class="ui avatar image" src="/static/assets/images/logo.png">
<div class="content">
<a class="header">他的测试主题2</a>
</div>
</div>
<div class="item">
<img class="ui avatar image" src="/static/assets/images/logo.png">
<div class="content">
<a class="header">他的测试主题3</a>
</div>
</div>
</div>
</div>
<div class="extra content">
<p>预留信息</p>
</div>
</div>
<div class="ui card">
<div class="content">
<img class="right floated mini ui image"
src="static/assets/images/logo.png">
<div class="header">Jiscuss Demo</div>
<div class="meta">简单、易用、开源</div>
<div class="description">一款基于JAVA的开源论坛 框架采用:Spring Boot + H2
+ Semantic UI
</div>
</div>
<div class="extra content">
<div class="ui two buttons">
<div class="ui basic red button">
<div class="ui mini statistic">
<div class="value">1</div>
<div class="label">用户数</div>
</div>
</div>
<div class="ui basic green button">
<div class="ui mini statistic">
<div class="value">10</div>
<div class="label">发帖数</div>
</div>
</div>
</#if>
</#list>
</div>
</div>
</div>
</#if>
</div>
@@ -333,7 +291,10 @@
'<div style="font-weight:700;font-size:1em;margin-bottom:4px;">' + (u.username || '') + '</div>' +
'<div style="color:#888;font-size:.88em;">发帖: ' + (u.discussionsCount || 0) +
'&nbsp;&nbsp;回复: ' + (u.commentsCount || 0) + '</div>' +
'<div style="margin-top:8px;"><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></div>'
'<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 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>' +
'</div>'
).css({ left: x + 14, top: y + 14 }).show();
}
+86 -4
View File
@@ -333,7 +333,8 @@
</div>
<div class="meta">
<div class=" description "><i class="edit icon"></i>
<a data-tooltip="${discussions.username}"
<a class="user-card-trigger" data-user-id="${discussions.startUserId!''}"
data-tooltip="${discussions.username}"
data-position="top center"><b>${discussions.username}</b></a>&nbsp;&nbsp;•&nbsp;&nbsp;
<i class="reply icon"></i>最后由 <a><b>${discussions.usernameLast}</b></a>
回复于${discussions.lastTime}.
@@ -387,7 +388,7 @@
</div>
<div class="meta">
<div class=" description "><i class="edit icon"></i>
<a><b>${discussions.username}</b></a>&nbsp;&nbsp;•&nbsp;&nbsp;
<a class="user-card-trigger" data-user-id="${discussions.startUserId!''}"><b>${discussions.username}</b></a>&nbsp;&nbsp;•&nbsp;&nbsp;
<i class="reply icon"></i>最后由 <a><b>${discussions.usernameLast}</b></a>
回复于${discussions.lastTime}.
</div>
@@ -439,7 +440,7 @@
</div>
<div class="meta">
<div class=" description "><i class="edit icon"></i>
<a><b>${discussions.username}</b></a>&nbsp;&nbsp;•&nbsp;&nbsp;
<a class="user-card-trigger" data-user-id="${discussions.startUserId!''}"><b>${discussions.username}</b></a>&nbsp;&nbsp;•&nbsp;&nbsp;
<i class="reply icon"></i>最后由 <a><b>${discussions.usernameLast}</b></a>
回复于${discussions.lastTime}.
</div>
@@ -505,7 +506,7 @@
<div class="date">${discussions.startTime}</div>
</div>
<div class="meta">
<a><b>${discussions.username}</b></a>&nbsp;•&nbsp;
<a class="user-card-trigger" data-user-id="${discussions.startUserId!''}"><b>${discussions.username}</b></a>&nbsp;•&nbsp;
<i class="comment icon"></i>${discussions.commentsCount!0} 回复&nbsp;
<i class="eye icon"></i>${discussions.viewCount!0} 浏览
</div>
@@ -556,6 +557,87 @@
</script>
<script type="text/javascript" charset="UTF-8" src="/static/js/user/system.js"></script>
<#-- 用户悬浮卡片 -->
<script>
(function() {
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');
function showCard(u, x, y) {
$card.html(
'<div style="font-weight:700;font-size:1em;margin-bottom:4px;">' + (u.username || '') + '</div>' +
'<div style="color:#888;font-size:.88em;">发帖: ' + (u.discussionsCount || 0) +
'&nbsp;&nbsp;回复: ' + (u.commentsCount || 0) + '</div>' +
'<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 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>' +
'</div>'
).css({ left: x + 14, top: y + 14 }).show();
}
$(document).on('mouseenter', '.user-card-trigger', function(e) {
var userId = $(this).data('user-id');
if (!userId) return;
if (cardCache[userId]) { showCard(cardCache[userId], e.clientX, e.clientY); return; }
$.getJSON('/msg_api/user-card/' + userId, function(res) {
if (res && res.code === 10000 && res.data) {
cardCache[userId] = res.data;
showCard(res.data, e.clientX, e.clientY);
}
});
}).on('mousemove', '.user-card-trigger', function(e) {
if ($card.is(':visible')) $card.css({ left: e.clientX + 14, top: e.clientY + 14 });
}).on('mouseleave', '.user-card-trigger', function() {
setTimeout(function() { if (!$card.is(':hover')) $card.hide(); }, 300);
});
$card.on('mouseleave', function() { $card.hide(); });
})();
</script>
<#-- 私信弹窗 Modal -->
<div class="ui small modal" id="sendMsgModal">
<div class="header"><i class="envelope icon"></i> 发送私信给 <span id="msgModalUsername"></span></div>
<div class="content">
<div class="ui form">
<div class="field">
<textarea id="msgModalContent" rows="4" placeholder="输入消息内容..." style="resize:none;"></textarea>
</div>
<div id="msgModalError" class="ui red message" style="display:none;"></div>
</div>
</div>
<div class="actions">
<div class="ui cancel button">取消</div>
<div class="ui primary button" id="msgModalSendBtn"><i class="send icon"></i>发送</div>
</div>
</div>
<script>
var _msgReceiverId = null;
var _msgCsrfToken = $('meta[name="_csrf"]').attr('content');
var _msgCsrfHeader = $('meta[name="_csrf_header"]').attr('content');
$(document).on('click', '.open-msg-modal', function() {
_msgReceiverId = $(this).data('uid');
$('#msgModalUsername').text($(this).data('uname'));
$('#msgModalContent').val('');
$('#msgModalError').hide();
$('#jUserCard').hide();
$('#sendMsgModal').modal('show');
});
$('#msgModalSendBtn').on('click', function() {
var content = $('#msgModalContent').val().trim();
if (!content) { $('#msgModalError').text('消息内容不能为空').show(); return; }
var $btn = $(this).addClass('loading disabled');
var headers = {}; headers[_msgCsrfHeader] = _msgCsrfToken;
$.ajax({
url: '/msg_api/messages/send', type: 'POST', headers: headers,
data: { receiverId: _msgReceiverId, content: content },
success: function(resp) {
$btn.removeClass('loading disabled');
if (resp && resp.code === 10000) { $('#sendMsgModal').modal('hide'); }
else { $('#msgModalError').text((resp && resp.msg) || '发送失败').show(); }
},
error: function() { $btn.removeClass('loading disabled'); $('#msgModalError').text('请先登录').show(); }
});
});
</script>
</body>
</html>
+94
View File
@@ -0,0 +1,94 @@
<!DOCTYPE html>
<html>
<head>
<title>搜索 - Jiscuss</title>
<meta name="_csrf" content="${_csrf.token}"/>
<meta name="_csrf_header" content="${_csrf.headerName}"/>
<#include "comm/commjs.ftl"/>
</head>
<body style="background: #f7f8fa;">
<#include "comm/header.ftl"/>
<div class="ui container" id="container" style="margin-top: 20px;">
<div class="ui segment">
<form action="/search" method="get" class="ui form">
<div class="ui large fluid action input">
<input type="text" name="q" value="${q!''}" placeholder="搜索主题..." autofocus>
<button type="submit" class="ui primary button"><i class="search icon"></i>搜索</button>
</div>
</form>
</div>
<#if q?? && q != "">
<div class="ui secondary segment" style="margin-top:0; border-top:none;">
<#if totalElements?? && totalElements gt 0>
<span style="color:#555;">找到 <strong>${totalElements}</strong> 个关于 "<strong>${q}</strong>" 的结果</span>
<#else>
<span style="color:#888;">没有找到关于 "<strong>${q}</strong>" 的相关结果</span>
</#if>
</div>
</#if>
<#if results?? && results?size gt 0>
<div class="ui relaxed divided items" style="margin-top:10px;">
<#list results as disc>
<div class="item" style="padding: 14px 0; border-bottom: 1px solid #eee;">
<div class="content">
<a class="header" href="/getdiscussionsbyid?id=${disc.id}" style="font-size:1.1em; color:#2185d0;">
${disc.title}
</a>
<div class="description" style="margin-top:6px; color:#555; font-size:.9em; overflow:hidden; white-space:nowrap; text-overflow:ellipsis; max-width:700px;">
${disc.content!''}
</div>
<div class="meta" style="margin-top:6px; color:#999; font-size:.85em;">
<i class="user icon"></i> <b>${disc.username!''}</b>
&nbsp;&nbsp;•&nbsp;&nbsp;
<i class="clock outline icon"></i> ${disc.startTime!''}
&nbsp;&nbsp;
<i class="like icon"></i> ${disc.likeCount!0}
&nbsp;
<i class="comment icon"></i> ${disc.commentsCount!0} 回复
&nbsp;
<i class="eye icon"></i> ${disc.viewCount!0} 浏览
<#if disc.tagList?? && disc.tagList?size gt 0>
&nbsp;&nbsp;
<#list disc.tagList as tag>
<a class="ui mini ${tag.color!''} label">${tag.name}</a>
</#list>
</#if>
</div>
</div>
</div>
</#list>
</div>
<#if totalPages?? && totalPages gt 1>
<div class="ui pagination menu" style="margin-top:20px;">
<#if pageNum gt 1>
<a class="item" href="/search?q=${q?url}&pageNum=${pageNum - 1}"><i class="left chevron icon"></i>上一页</a>
</#if>
<#list 1..totalPages as p>
<a class="item <#if p == pageNum>active</#if>" href="/search?q=${q?url}&pageNum=${p}">${p}</a>
</#list>
<#if pageNum lt totalPages>
<a class="item" href="/search?q=${q?url}&pageNum=${pageNum + 1}">下一页<i class="right chevron icon"></i></a>
</#if>
</div>
</#if>
<#elseif q?? && q != "">
<div class="ui placeholder segment" style="margin-top:10px;">
<div class="ui icon header">
<i class="search icon"></i>
暂无搜索结果
</div>
<a class="ui primary button" href="/">返回首页</a>
</div>
</#if>
</div>
<#include "comm/footer.ftl"/>
</body>
</html>
+82
View File
@@ -11,6 +11,52 @@
.profile-stat .number { font-size:1.6em; font-weight:bold; color:var(--color-primary,#2185d0); }
.profile-stat .label2 { color:#888; font-size:0.85em; }
.tab-content { background:#fff; border-radius:8px; padding:1.2em; min-height:200px; }
/* 彩蛋按钮特效 */
@keyframes rainbowBg {
0% { background-position: 0% 50%; }
50% { background-position: 100% 50%; }
100% { background-position: 0% 50%; }
}
@keyframes btnGlow {
0%, 100% { box-shadow: 0 0 8px 2px rgba(41,128,185,.6); }
33% { box-shadow: 0 0 10px 3px rgba(192,57,43,.6); }
66% { box-shadow: 0 0 10px 3px rgba(39,174,96,.6); }
}
@keyframes btnWiggle {
0%,100% { transform: rotate(0deg) scale(1); }
20% { transform: rotate(-4deg) scale(1.08); }
40% { transform: rotate(4deg) scale(1.08); }
60% { transform: rotate(-3deg) scale(1.05); }
80% { transform: rotate(3deg) scale(1.05); }
}
@keyframes ripple {
to { transform: scale(3); opacity: 0; }
}
#userButton {
position: relative; overflow: hidden;
border: 2px solid transparent !important;
background: linear-gradient(#fff,#fff) padding-box,
linear-gradient(270deg,#e74c3c,#f39c12,#27ae60,#2980b9,#8e44ad,#e74c3c) border-box;
background-size: auto, 400% 400%;
animation: rainbowBg 4s ease infinite, btnGlow 3s ease-in-out infinite;
color: #333 !important;
font-weight: bold;
letter-spacing: .5px;
transition: transform .15s;
}
#userButton:hover {
animation: btnWiggle .5s ease, rainbowBg 4s ease infinite, btnGlow 3s ease-in-out infinite;
cursor: pointer;
}
#userButton .ripple-circle {
position: absolute; border-radius: 50%;
background: rgba(100,100,255,.25);
width: 60px; height: 60px;
margin-top: -30px; margin-left: -30px;
animation: ripple .6s linear;
pointer-events: none;
}
</style>
</head>
<body style="background:#f7f8fa;">
@@ -39,6 +85,13 @@
加入于 ${userEntity.joinTime?string("yyyy-MM-dd")}
</div>
</#if>
<div style="margin-top:14px;">
<#if isOwn!true>
<button class="ui basic button" id="userButton">
<i class="hand peace outline icon"></i>小按钮·点下试试
</button>
</#if>
</div>
</div>
<div class="profile-card">
<div class="ui three column grid" style="text-align:center;">
@@ -148,5 +201,34 @@
</div>
<#include "comm/footer.ftl"/>
<#if isOwn!true>
<div class="ui bottom three item labeled icon sidebar menu scale down" id="userButtonsidebar">
<a class="item" href="/user?type=discussion">
<i class="list icon"></i>主题
</a>
<a class="item" href="/user?type=reply">
<i class="comment icon"></i>回复
</a>
<a class="item" href="/user?type=like">
<i class="heart icon"></i>点赞
</a>
</div>
<script type="text/javascript" charset="UTF-8" src="/static/js/user/user.js"></script>
<script>
// Ripple effect on button click
document.getElementById('userButton') && document.getElementById('userButton').addEventListener('click', function(e) {
var btn = this;
var circle = document.createElement('span');
circle.classList.add('ripple-circle');
var rect = btn.getBoundingClientRect();
circle.style.left = (e.clientX - rect.left) + 'px';
circle.style.top = (e.clientY - rect.top) + 'px';
btn.appendChild(circle);
setTimeout(function(){ circle.remove(); }, 650);
});
</script>
</#if>
</body>
</html>