Compare commits

...

14 Commits

Author SHA1 Message Date
tim
cab8cd06dc fix: 频道聊天,点击写个回复没反应,点击小箭头才行 #916 2025-09-07 22:46:55 +08:00
Tim
b77a96938a Merge pull request #915 from nagisa77/feature/article_ui_fix
Article UI fixes
2025-09-07 14:14:56 +08:00
tim
1c28201cb8 fix: 侧边栏收起打开 有引导 #848 2025-09-07 14:13:48 +08:00
tim
0e26758585 fix: 首页帖子padding 写为15px 2025-09-07 14:10:04 +08:00
tim
786e60e8e5 fix: 帖子元素 -- 上下间距一样 #846 2025-09-07 14:08:37 +08:00
Tim
df4a707e3a Merge pull request #914 from nagisa77/feature/article_ui_fix
Article UI Fixes
2025-09-07 13:58:42 +08:00
tim
d94302635a fix: 生产环境新增www 2025-09-07 13:58:22 +08:00
tim
9519f66474 fix: 首页帖子padding可以大一些 比如 20px #845 2025-09-07 13:43:40 +08:00
Tim
14ee5faa1f Merge pull request #913 from nagisa77/feature/sidebar-logic
fix: 更新分类选择
2025-09-07 13:38:56 +08:00
Tim
2eebc1c004 Merge pull request #911 from nagisa77/feature/sidebar-logic
feat: 侧边栏按钮样式逻辑修改
2025-09-07 13:23:05 +08:00
Tim
135a6b8c51 Merge pull request #910 from nagisa77/codex/analyze-and-refactor-case
Fix point history balance recalculation
2025-09-07 12:48:04 +08:00
Tim
c43e4b85bc Test recalculation updates balance 2025-09-07 12:47:44 +08:00
Tim
fb3a2839db Merge pull request #909 from Linindoo/main
修复纯数字用户名的用户个人首页 404 问题(注册和修改校验用户名不能为纯数字)
2025-09-07 11:14:40 +08:00
zhoujia
5534573a19 创建和更新用户名校验增加校验,不允许纯数字用户名 2025-09-05 15:08:22 +08:00
14 changed files with 123 additions and 25 deletions

View File

@@ -10,6 +10,7 @@ import java.util.List;
public interface PointHistoryRepository extends JpaRepository<PointHistory, Long> { public interface PointHistoryRepository extends JpaRepository<PointHistory, Long> {
List<PointHistory> findByUserOrderByIdDesc(User user); List<PointHistory> findByUserOrderByIdDesc(User user);
List<PointHistory> findByUserOrderByIdAsc(User user);
long countByUser(User user); long countByUser(User user);
List<PointHistory> findByUserAndCreatedAtAfterOrderByCreatedAtDesc(User user, LocalDateTime createdAt); List<PointHistory> findByUserAndCreatedAtAfterOrderByCreatedAtDesc(User user, LocalDateTime createdAt);

View File

@@ -225,17 +225,20 @@ public class PointService {
*/ */
public int recalculateUserPoints(User user) { public int recalculateUserPoints(User user) {
// 获取用户所有的积分历史记录(由于@Where注解已删除的记录会被自动过滤 // 获取用户所有的积分历史记录(由于@Where注解已删除的记录会被自动过滤
List<PointHistory> histories = pointHistoryRepository.findByUserOrderByIdDesc(user); List<PointHistory> histories = pointHistoryRepository.findByUserOrderByIdAsc(user);
int totalPoints = 0; int totalPoints = 0;
for (PointHistory history : histories) { for (PointHistory history : histories) {
totalPoints += history.getAmount(); totalPoints += history.getAmount();
// 重新计算每条历史记录的余额
history.setBalance(totalPoints);
} }
// 更新用户积分 // 批量更新历史记录及用户积分
pointHistoryRepository.saveAll(histories);
user.setPoint(totalPoints); user.setPoint(totalPoints);
userRepository.save(user); userRepository.save(user);
return totalPoints; return totalPoints;
} }

View File

@@ -1,6 +1,7 @@
package com.openisle.service; package com.openisle.service;
import com.openisle.exception.FieldException; import com.openisle.exception.FieldException;
import org.apache.commons.lang3.math.NumberUtils;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
/** /**
@@ -17,6 +18,11 @@ public class UsernameValidator {
if (username == null || username.isEmpty()) { if (username == null || username.isEmpty()) {
throw new FieldException("username", "Username cannot be empty"); throw new FieldException("username", "Username cannot be empty");
} }
if (NumberUtils.isDigits(username)) {
throw new FieldException("username", "Username cannot be pure number");
}
} }
} }

View File

@@ -0,0 +1,67 @@
package com.openisle.service;
import com.openisle.model.PointHistory;
import com.openisle.model.PointHistoryType;
import com.openisle.model.Role;
import com.openisle.model.User;
import com.openisle.repository.PointHistoryRepository;
import com.openisle.repository.UserRepository;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest;
import org.springframework.context.annotation.Import;
import java.time.LocalDateTime;
import static org.junit.jupiter.api.Assertions.assertEquals;
@DataJpaTest
@Import(PointService.class)
class PointServiceRecalculateUserPointsTest {
@Autowired
private PointService pointService;
@Autowired
private UserRepository userRepository;
@Autowired
private PointHistoryRepository pointHistoryRepository;
@Test
void recalculatesBalanceAfterDeletion() {
User user = new User();
user.setUsername("u");
user.setEmail("u@example.com");
user.setPassword("p");
user.setRole(Role.USER);
userRepository.save(user);
PointHistory h1 = new PointHistory();
h1.setUser(user);
h1.setType(PointHistoryType.POST);
h1.setAmount(30);
h1.setBalance(30);
h1.setCreatedAt(LocalDateTime.now().minusMinutes(2));
pointHistoryRepository.save(h1);
PointHistory h2 = new PointHistory();
h2.setUser(user);
h2.setType(PointHistoryType.COMMENT);
h2.setAmount(10);
h2.setBalance(40);
h2.setCreatedAt(LocalDateTime.now().minusMinutes(1));
pointHistoryRepository.save(h2);
user.setPoint(40);
userRepository.save(user);
pointHistoryRepository.delete(h1);
int total = pointService.recalculateUserPoints(user);
assertEquals(10, total);
assertEquals(10, userRepository.findById(user.getId()).orElseThrow().getPoint());
assertEquals(10, pointHistoryRepository.findById(h2.getId()).orElseThrow().getBalance());
}
}

View File

@@ -1,10 +1,10 @@
; 生产环境后端 ; 生产环境后端
NUXT_PUBLIC_API_BASE_URL=https://open-isle.com NUXT_PUBLIC_API_BASE_URL=https://www.open-isle.com
; 正式环境/生产环境 ; 正式环境/生产环境
NUXT_PUBLIC_WEBSITE_BASE_URL=https://open-isle.com NUXT_PUBLIC_WEBSITE_BASE_URL=https://www.open-isle.com
; 生产环境ws后端 ; 生产环境ws后端
NUXT_PUBLIC_WEBSOCKET_URL=https://open-isle.com/websocket NUXT_PUBLIC_WEBSOCKET_URL=https://www.open-isle.com/websocket
NUXT_PUBLIC_GOOGLE_CLIENT_ID=777830451304-nt8afkkap18gui4f9entcha99unal744.apps.googleusercontent.com NUXT_PUBLIC_GOOGLE_CLIENT_ID=777830451304-nt8afkkap18gui4f9entcha99unal744.apps.googleusercontent.com
NUXT_PUBLIC_GITHUB_CLIENT_ID=Ov23liVkO1NPAX5JyWxJ NUXT_PUBLIC_GITHUB_CLIENT_ID=Ov23liVkO1NPAX5JyWxJ

View File

@@ -18,7 +18,9 @@
--background-color-blur: rgba(255, 255, 255, 0.57); --background-color-blur: rgba(255, 255, 255, 0.57);
--menu-border-color: lightgray; --menu-border-color: lightgray;
--normal-border-color: lightgray; --normal-border-color: lightgray;
--menu-selected-background-color: rgba(242, 242, 242, 0.884); --menu-selected-background-color: rgba(88, 241, 255, 0.166);
--normal-light-background-color: rgba(242, 242, 242, 0.884);
--menu-selected-background-color-hover: rgba(242, 242, 242, 0.884);
--menu-text-color: rgb(99, 99, 99); --menu-text-color: rgb(99, 99, 99);
--scroller-background-color: rgba(130, 175, 180, 0.5); --scroller-background-color: rgba(130, 175, 180, 0.5);
/* --normal-background-color: rgb(241, 241, 241); */ /* --normal-background-color: rgb(241, 241, 241); */
@@ -58,6 +60,8 @@
--menu-border-color: #555; --menu-border-color: #555;
--normal-border-color: #555; --normal-border-color: #555;
--menu-selected-background-color: rgba(255, 255, 255, 0.1); --menu-selected-background-color: rgba(255, 255, 255, 0.1);
--normal-light-background-color: rgba(255, 255, 255, 0.1);
--menu-selected-background-color-hover: rgba(17, 182, 197, 0.082);
--menu-text-color: rgb(173, 173, 173); --menu-text-color: rgb(173, 173, 173);
/* --normal-background-color: #000000; */ /* --normal-background-color: #000000; */
--normal-background-color: #333; --normal-background-color: #333;
@@ -162,7 +166,7 @@ body {
padding-left: 1em; padding-left: 1em;
border-left: 4px solid #d0d7de; border-left: 4px solid #d0d7de;
color: var(--blockquote-text-color); color: var(--blockquote-text-color);
background-color: var(--menu-selected-background-color); background-color: var(--normal-light-background-color);
padding-top: 1px; padding-top: 1px;
padding-bottom: 1px; padding-bottom: 1px;
} }
@@ -295,7 +299,7 @@ body {
/* 鼠标悬停行高亮 */ /* 鼠标悬停行高亮 */
.info-content-text tbody tr:hover { .info-content-text tbody tr:hover {
background-color: var(--menu-selected-background-color); background-color: var(--normal-light-background-color);
transition: background-color 0.2s ease; transition: background-color 0.2s ease;
} }

View File

@@ -404,7 +404,6 @@ const handleContentClick = (e) => {
margin-left: 10px; margin-left: 10px;
margin-right: 10px; margin-right: 10px;
opacity: 0.5; opacity: 0.5;
transform: scaleX(-1);
} }
.reply-user-name { .reply-user-name {

View File

@@ -4,7 +4,9 @@
<div class="header-content-left"> <div class="header-content-left">
<div v-if="showMenuBtn" class="menu-btn-wrapper"> <div v-if="showMenuBtn" class="menu-btn-wrapper">
<button class="menu-btn" ref="menuBtn" @click="$emit('toggle-menu')"> <button class="menu-btn" ref="menuBtn" @click="$emit('toggle-menu')">
<application-menu class="micon"></application-menu> <ToolTip content="展开/收起菜单" placement="bottom">
<application-menu class="micon"></application-menu>
</ToolTip>
</button> </button>
<span <span
v-if="isMobile && (unreadMessageCount > 0 || hasChannelUnread)" v-if="isMobile && (unreadMessageCount > 0 || hasChannelUnread)"

View File

@@ -316,6 +316,10 @@ const gotoTag = (t) => {
align-items: center; align-items: center;
} }
.menu-item:hover {
background-color: var(--menu-selected-background-color-hover);
}
.menu-item.selected { .menu-item.selected {
font-weight: bold; font-weight: bold;
background-color: var(--menu-selected-background-color); background-color: var(--menu-selected-background-color);
@@ -407,7 +411,7 @@ const gotoTag = (t) => {
} }
.section-item:hover { .section-item:hover {
background-color: var(--menu-selected-background-color); background-color: var(--menu-selected-background-color-hover);
} }
.section-item-text-count { .section-item-text-count {

View File

@@ -136,7 +136,7 @@ export default {
left: 0; left: 0;
width: 100%; width: 100%;
height: 100%; height: 100%;
background: var(--menu-selected-background-color); background: var(--normal-light-background-color);
display: flex; display: flex;
align-items: center; align-items: center;
justify-content: center; justify-content: center;

View File

@@ -331,11 +331,11 @@ onMounted(async () => {
.reactions-viewer-item.placeholder, .reactions-viewer-item.placeholder,
.reactions-viewer-single-item.selected { .reactions-viewer-single-item.selected {
background-color: var(--menu-selected-background-color); background-color: var(--normal-light-background-color);
} }
.reaction-option.selected { .reaction-option.selected {
background-color: var(--menu-selected-background-color); background-color: var(--normal-light-background-color);
} }
@media (max-width: 768px) { @media (max-width: 768px) {

View File

@@ -65,16 +65,17 @@
class="article-item" class="article-item"
v-for="article in articles" v-for="article in articles"
:key="article.id" :key="article.id"
@click="navigateTo(`/posts/${article.id}`)"
> >
<div class="article-main-container"> <div class="article-main-container">
<NuxtLink class="article-item-title main-item" :to="`/posts/${article.id}`"> <NuxtLink class="article-item-title main-item">
<pin v-if="article.pinned" theme="outline" class="pinned-icon" /> <pin v-if="article.pinned" theme="outline" class="pinned-icon" />
<gift v-if="article.type === 'LOTTERY'" class="lottery-icon" /> <gift v-if="article.type === 'LOTTERY'" class="lottery-icon" />
<ranking-list v-else-if="article.type === 'POLL'" class="poll-icon" /> <ranking-list v-else-if="article.type === 'POLL'" class="poll-icon" />
<star v-if="!article.rssExcluded" class="featured-icon" /> <star v-if="!article.rssExcluded" class="featured-icon" />
{{ article.title }} {{ article.title }}
</NuxtLink> </NuxtLink>
<NuxtLink class="article-item-description main-item" :to="`/posts/${article.id}`"> <NuxtLink class="article-item-description main-item">
{{ sanitizeDescription(article.description) }} {{ sanitizeDescription(article.description) }}
</NuxtLink> </NuxtLink>
<div class="article-info-container main-item"> <div class="article-info-container main-item">
@@ -488,6 +489,11 @@ const sanitizeDescription = (text) => stripMarkdown(text)
border-bottom: 1px solid var(--normal-border-color); border-bottom: 1px solid var(--normal-border-color);
} }
.article-item:hover {
background-color: var(--menu-selected-background-color-hover);
cursor: pointer;
}
.article-main-container, .article-main-container,
.header-item.main-item { .header-item.main-item {
width: calc(60% - 20px); width: calc(60% - 20px);
@@ -529,7 +535,7 @@ const sanitizeDescription = (text) => stripMarkdown(text)
} }
.article-item-title { .article-item-title {
margin-top: 10px; margin-top: 15px;
font-size: 18px; font-size: 18px;
text-decoration: none; text-decoration: none;
color: var(--text-color); color: var(--text-color);
@@ -558,7 +564,7 @@ const sanitizeDescription = (text) => stripMarkdown(text)
.article-item-description { .article-item-description {
max-width: 100%; max-width: 100%;
margin-top: 10px; margin-top: 5px;
font-size: 13px; font-size: 13px;
color: rgba(140, 140, 140, 0.888); color: rgba(140, 140, 140, 0.888);
display: -webkit-box; display: -webkit-box;
@@ -605,6 +611,7 @@ const sanitizeDescription = (text) => stripMarkdown(text)
display: flex; display: flex;
flex-direction: column; flex-direction: column;
align-items: flex-start; align-items: flex-start;
padding-bottom: 5px;
} }
.article-member-avatars-container { .article-member-avatars-container {
@@ -719,10 +726,15 @@ const sanitizeDescription = (text) => stripMarkdown(text)
} }
.article-item-title { .article-item-title {
margin-top: 10px;
font-size: 16px; font-size: 16px;
font-weight: bold; font-weight: bold;
} }
.article-main-container {
padding-bottom: 0px;
}
.article-item-description { .article-item-description {
margin-top: 2px; margin-top: 2px;
font-size: 10px; font-size: 10px;

View File

@@ -48,7 +48,7 @@
:content-id="item.id" :content-id="item.id"
@update:modelValue="(v) => (item.reactions = v)" @update:modelValue="(v) => (item.reactions = v)"
> >
<div class="reply-btn"><next @click="setReply(item)" /> 写个回复...</div> <div @click="setReply(item)" class="reply-btn"><next /> 写个回复...</div>
</ReactionsGroup> </ReactionsGroup>
</template> </template>
</BaseTimeline> </BaseTimeline>
@@ -614,7 +614,7 @@ function goBack() {
border-left: 5px solid var(--primary-color); border-left: 5px solid var(--primary-color);
margin-bottom: 5px; margin-bottom: 5px;
font-size: 13px; font-size: 13px;
background-color: var(--menu-selected-background-color); background-color: var(--normal-light-background-color);
} }
.reply-author { .reply-author {
@@ -634,7 +634,7 @@ function goBack() {
} }
.active-reply { .active-reply {
background-color: var(--bg-color-soft); background-color: var(--normal-light-background-color);
padding: 5px 10px; padding: 5px 10px;
border-left: 5px solid var(--primary-color); border-left: 5px solid var(--primary-color);
margin-bottom: 5px; margin-bottom: 5px;

View File

@@ -419,7 +419,7 @@ function minimize() {
} }
.conversation-item:hover { .conversation-item:hover {
background-color: var(--menu-selected-background-color); background-color: var(--normal-light-background-color);
} }
.conversation-avatar { .conversation-avatar {