mirror of
https://github.com/nagisa77/OpenIsle.git
synced 2026-03-01 17:41:03 +08:00
Compare commits
7 Commits
codex-mv1x
...
feature/da
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
856d3dd513 | ||
|
|
d96aae59d2 | ||
|
|
ec0fd63e30 | ||
|
|
dfd4c70b6e | ||
|
|
d79dc8877d | ||
|
|
99bf80a47a | ||
|
|
02287c05be |
@@ -36,6 +36,7 @@ public class ReactionController {
|
|||||||
Authentication auth) {
|
Authentication auth) {
|
||||||
Reaction reaction = reactionService.reactToPost(auth.getName(), postId, req.getType());
|
Reaction reaction = reactionService.reactToPost(auth.getName(), postId, req.getType());
|
||||||
if (reaction == null) {
|
if (reaction == null) {
|
||||||
|
pointService.deductForReactionOfPost(auth.getName(), postId);
|
||||||
return ResponseEntity.noContent().build();
|
return ResponseEntity.noContent().build();
|
||||||
}
|
}
|
||||||
ReactionDto dto = reactionMapper.toDto(reaction);
|
ReactionDto dto = reactionMapper.toDto(reaction);
|
||||||
@@ -50,6 +51,7 @@ public class ReactionController {
|
|||||||
Authentication auth) {
|
Authentication auth) {
|
||||||
Reaction reaction = reactionService.reactToComment(auth.getName(), commentId, req.getType());
|
Reaction reaction = reactionService.reactToComment(auth.getName(), commentId, req.getType());
|
||||||
if (reaction == null) {
|
if (reaction == null) {
|
||||||
|
pointService.deductForReactionOfComment(auth.getName(), commentId);
|
||||||
return ResponseEntity.noContent().build();
|
return ResponseEntity.noContent().build();
|
||||||
}
|
}
|
||||||
ReactionDto dto = reactionMapper.toDto(reaction);
|
ReactionDto dto = reactionMapper.toDto(reaction);
|
||||||
|
|||||||
@@ -5,6 +5,8 @@ public enum PointHistoryType {
|
|||||||
COMMENT,
|
COMMENT,
|
||||||
POST_LIKED,
|
POST_LIKED,
|
||||||
COMMENT_LIKED,
|
COMMENT_LIKED,
|
||||||
|
POST_LIKE_CANCELLED,
|
||||||
|
COMMENT_LIKE_CANCELLED,
|
||||||
INVITE,
|
INVITE,
|
||||||
FEATURE,
|
FEATURE,
|
||||||
SYSTEM_ONLINE,
|
SYSTEM_ONLINE,
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import com.openisle.model.User;
|
|||||||
import com.openisle.model.Post;
|
import com.openisle.model.Post;
|
||||||
import com.openisle.model.Comment;
|
import com.openisle.model.Comment;
|
||||||
import com.openisle.model.NotificationType;
|
import com.openisle.model.NotificationType;
|
||||||
|
import com.openisle.model.ReactionType;
|
||||||
import org.springframework.data.jpa.repository.JpaRepository;
|
import org.springframework.data.jpa.repository.JpaRepository;
|
||||||
import org.springframework.data.domain.Page;
|
import org.springframework.data.domain.Page;
|
||||||
import org.springframework.data.domain.Pageable;
|
import org.springframework.data.domain.Pageable;
|
||||||
@@ -29,4 +30,8 @@ public interface NotificationRepository extends JpaRepository<Notification, Long
|
|||||||
List<Notification> findByTypeAndFromUser(NotificationType type, User fromUser);
|
List<Notification> findByTypeAndFromUser(NotificationType type, User fromUser);
|
||||||
|
|
||||||
void deleteByTypeAndFromUserAndPost(NotificationType type, User fromUser, Post post);
|
void deleteByTypeAndFromUserAndPost(NotificationType type, User fromUser, Post post);
|
||||||
|
|
||||||
|
void deleteByTypeAndFromUserAndPostAndReactionType(NotificationType type, User fromUser, Post post, ReactionType reactionType);
|
||||||
|
|
||||||
|
void deleteByTypeAndFromUserAndCommentAndReactionType(NotificationType type, User fromUser, Comment comment, ReactionType reactionType);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -114,6 +114,14 @@ public class NotificationService {
|
|||||||
return n;
|
return n;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void deleteReactionNotification(User fromUser, Post post, Comment comment, ReactionType reactionType) {
|
||||||
|
if (post != null) {
|
||||||
|
notificationRepository.deleteByTypeAndFromUserAndPostAndReactionType(NotificationType.REACTION, fromUser, post, reactionType);
|
||||||
|
} else if (comment != null) {
|
||||||
|
notificationRepository.deleteByTypeAndFromUserAndCommentAndReactionType(NotificationType.REACTION, fromUser, comment, reactionType);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Create notifications for all admins when a user submits a register request.
|
* Create notifications for all admins when a user submits a register request.
|
||||||
* Old register request notifications from the same applicant are removed first.
|
* Old register request notifications from the same applicant are removed first.
|
||||||
|
|||||||
@@ -150,6 +150,16 @@ public class PointService {
|
|||||||
return addPoint(poster, 10, PointHistoryType.POST_LIKED, post, null, reactioner);
|
return addPoint(poster, 10, PointHistoryType.POST_LIKED, post, null, reactioner);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public int deductForReactionOfPost(String reactionerName, Long postId) {
|
||||||
|
User poster = postRepository.findById(postId).orElseThrow().getAuthor();
|
||||||
|
User reactioner = userRepository.findByUsername(reactionerName).orElseThrow();
|
||||||
|
if (poster.getId().equals(reactioner.getId())) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
Post post = postRepository.findById(postId).orElseThrow();
|
||||||
|
return addPoint(poster, -10, PointHistoryType.POST_LIKE_CANCELLED, post, null, reactioner);
|
||||||
|
}
|
||||||
|
|
||||||
// 考虑点赞者和评论者是同一个的情况
|
// 考虑点赞者和评论者是同一个的情况
|
||||||
public int awardForReactionOfComment(String reactionerName, Long commentId) {
|
public int awardForReactionOfComment(String reactionerName, Long commentId) {
|
||||||
// 根据帖子id找到评论者
|
// 根据帖子id找到评论者
|
||||||
@@ -169,6 +179,17 @@ public class PointService {
|
|||||||
return addPoint(commenter, 10, PointHistoryType.COMMENT_LIKED, post, comment, reactioner);
|
return addPoint(commenter, 10, PointHistoryType.COMMENT_LIKED, post, comment, reactioner);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public int deductForReactionOfComment(String reactionerName, Long commentId) {
|
||||||
|
User commenter = commentRepository.findById(commentId).orElseThrow().getAuthor();
|
||||||
|
User reactioner = userRepository.findByUsername(reactionerName).orElseThrow();
|
||||||
|
if (commenter.getId().equals(reactioner.getId())) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
Comment comment = commentRepository.findById(commentId).orElseThrow();
|
||||||
|
Post post = comment.getPost();
|
||||||
|
return addPoint(commenter, -10, PointHistoryType.COMMENT_LIKE_CANCELLED, post, comment, reactioner);
|
||||||
|
}
|
||||||
|
|
||||||
public java.util.List<PointHistory> listHistory(String userName) {
|
public java.util.List<PointHistory> listHistory(String userName) {
|
||||||
User user = userRepository.findByUsername(userName).orElseThrow();
|
User user = userRepository.findByUsername(userName).orElseThrow();
|
||||||
if (pointHistoryRepository.countByUser(user) == 0) {
|
if (pointHistoryRepository.countByUser(user) == 0) {
|
||||||
|
|||||||
@@ -42,6 +42,7 @@ public class ReactionService {
|
|||||||
java.util.Optional<Reaction> existing =
|
java.util.Optional<Reaction> existing =
|
||||||
reactionRepository.findByUserAndPostAndType(user, post, type);
|
reactionRepository.findByUserAndPostAndType(user, post, type);
|
||||||
if (existing.isPresent()) {
|
if (existing.isPresent()) {
|
||||||
|
notificationService.deleteReactionNotification(user, post, null, type);
|
||||||
reactionRepository.delete(existing.get());
|
reactionRepository.delete(existing.get());
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
@@ -65,6 +66,7 @@ public class ReactionService {
|
|||||||
java.util.Optional<Reaction> existing =
|
java.util.Optional<Reaction> existing =
|
||||||
reactionRepository.findByUserAndCommentAndType(user, comment, type);
|
reactionRepository.findByUserAndCommentAndType(user, comment, type);
|
||||||
if (existing.isPresent()) {
|
if (existing.isPresent()) {
|
||||||
|
notificationService.deleteReactionNotification(user, null, comment, type);
|
||||||
reactionRepository.delete(existing.get());
|
reactionRepository.delete(existing.get());
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -113,17 +113,23 @@ const error = ref(null)
|
|||||||
const conversationId = route.params.id
|
const conversationId = route.params.id
|
||||||
const currentUser = ref(null)
|
const currentUser = ref(null)
|
||||||
const messagesListEl = ref(null)
|
const messagesListEl = ref(null)
|
||||||
let lastMessageEl = null
|
|
||||||
const currentPage = ref(0)
|
const currentPage = ref(0)
|
||||||
const totalPages = ref(0)
|
const totalPages = ref(0)
|
||||||
const loadingMore = ref(false)
|
const loadingMore = ref(false)
|
||||||
let scrollInterval = null
|
|
||||||
const conversationName = ref('')
|
const conversationName = ref('')
|
||||||
const isChannel = ref(false)
|
const isChannel = ref(false)
|
||||||
const isFloatMode = computed(() => route.query.float !== undefined)
|
const isFloatMode = computed(() => route.query.float !== undefined)
|
||||||
const floatRoute = useState('messageFloatRoute')
|
const floatRoute = useState('messageFloatRoute')
|
||||||
const replyTo = ref(null)
|
const replyTo = ref(null)
|
||||||
|
|
||||||
|
const isUserNearBottom = ref(true)
|
||||||
|
function updateNearBottom() {
|
||||||
|
const el = messagesListEl.value
|
||||||
|
if (!el) return
|
||||||
|
const threshold = 40 // px
|
||||||
|
isUserNearBottom.value = el.scrollHeight - el.scrollTop - el.clientHeight <= threshold
|
||||||
|
}
|
||||||
|
|
||||||
const hasMoreMessages = computed(() => currentPage.value < totalPages.value - 1)
|
const hasMoreMessages = computed(() => currentPage.value < totalPages.value - 1)
|
||||||
|
|
||||||
const otherParticipant = computed(() => {
|
const otherParticipant = computed(() => {
|
||||||
@@ -133,20 +139,37 @@ const otherParticipant = computed(() => {
|
|||||||
return participants.value.find((p) => p.id !== currentUser.value.id)
|
return participants.value.find((p) => p.id !== currentUser.value.id)
|
||||||
})
|
})
|
||||||
|
|
||||||
function isSentByCurrentUser(message) {
|
|
||||||
return message.sender.id === currentUser.value?.id
|
|
||||||
}
|
|
||||||
|
|
||||||
function handleAvatarError(event) {
|
|
||||||
event.target.src = '/default-avatar.svg'
|
|
||||||
}
|
|
||||||
|
|
||||||
function setReply(message) {
|
function setReply(message) {
|
||||||
replyTo.value = message
|
replyTo.value = message
|
||||||
}
|
}
|
||||||
|
|
||||||
// No changes needed here, as renderMarkdown is now imported.
|
/** 改造:滚动函数 —— smooth & instant */
|
||||||
// The old function is removed.
|
function scrollToBottomSmooth() {
|
||||||
|
const el = messagesListEl.value
|
||||||
|
if (!el) return
|
||||||
|
// 优先使用原生 smooth,失败则降级
|
||||||
|
try {
|
||||||
|
el.scrollTo({ top: el.scrollHeight, behavior: 'smooth' })
|
||||||
|
} catch {
|
||||||
|
// 降级:简易动画
|
||||||
|
const start = el.scrollTop
|
||||||
|
const end = el.scrollHeight
|
||||||
|
const duration = 200
|
||||||
|
const startTs = performance.now()
|
||||||
|
function step(now) {
|
||||||
|
const p = Math.min(1, (now - startTs) / duration)
|
||||||
|
el.scrollTop = start + (end - start) * p
|
||||||
|
if (p < 1) requestAnimationFrame(step)
|
||||||
|
}
|
||||||
|
requestAnimationFrame(step)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function scrollToBottomInstant() {
|
||||||
|
const el = messagesListEl.value
|
||||||
|
if (!el) return
|
||||||
|
el.scrollTop = el.scrollHeight
|
||||||
|
}
|
||||||
|
|
||||||
async function fetchMessages(page = 0) {
|
async function fetchMessages(page = 0) {
|
||||||
if (page === 0) {
|
if (page === 0) {
|
||||||
@@ -181,7 +204,6 @@ async function fetchMessages(page = 0) {
|
|||||||
isChannel.value = conversationData.channel
|
isChannel.value = conversationData.channel
|
||||||
}
|
}
|
||||||
|
|
||||||
// Since the backend sorts by descending, we need to reverse for correct chat order
|
|
||||||
const newMessages = pageData.content.reverse().map((item) => ({
|
const newMessages = pageData.content.reverse().map((item) => ({
|
||||||
...item,
|
...item,
|
||||||
src: item.sender.avatar,
|
src: item.sender.avatar,
|
||||||
@@ -202,12 +224,16 @@ async function fetchMessages(page = 0) {
|
|||||||
currentPage.value = pageData.number
|
currentPage.value = pageData.number
|
||||||
totalPages.value = pageData.totalPages
|
totalPages.value = pageData.totalPages
|
||||||
|
|
||||||
// Scrolling is now fully handled by the watcher
|
|
||||||
await nextTick()
|
await nextTick()
|
||||||
if (page > 0 && list) {
|
if (page > 0 && list) {
|
||||||
|
// 加载更多:保持原视口位置
|
||||||
const newScrollHeight = list.scrollHeight
|
const newScrollHeight = list.scrollHeight
|
||||||
list.scrollTop = newScrollHeight - oldScrollHeight
|
list.scrollTop = newScrollHeight - oldScrollHeight
|
||||||
|
} else if (page === 0) {
|
||||||
|
// 首次加载:定位到底部(不用动画,避免“闪动感”)
|
||||||
|
scrollToBottomInstant()
|
||||||
}
|
}
|
||||||
|
updateNearBottom()
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
error.value = e.message
|
error.value = e.message
|
||||||
toast.error(e.message)
|
toast.error(e.message)
|
||||||
@@ -272,9 +298,10 @@ async function sendMessage(content, clearInput) {
|
|||||||
})
|
})
|
||||||
clearInput()
|
clearInput()
|
||||||
replyTo.value = null
|
replyTo.value = null
|
||||||
setTimeout(() => {
|
|
||||||
scrollToBottom()
|
await nextTick()
|
||||||
}, 100)
|
// 仅“发送消息成功后”才平滑滚动到底部
|
||||||
|
scrollToBottomSmooth()
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
toast.error(e.message)
|
toast.error(e.message)
|
||||||
} finally {
|
} finally {
|
||||||
@@ -290,7 +317,6 @@ async function markConversationAsRead() {
|
|||||||
method: 'POST',
|
method: 'POST',
|
||||||
headers: { Authorization: `Bearer ${token}` },
|
headers: { Authorization: `Bearer ${token}` },
|
||||||
})
|
})
|
||||||
// After marking as read, refresh the global unread count
|
|
||||||
refreshGlobalUnreadCount()
|
refreshGlobalUnreadCount()
|
||||||
refreshChannelUnread()
|
refreshChannelUnread()
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
@@ -298,37 +324,12 @@ async function markConversationAsRead() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function scrollToBottom() {
|
|
||||||
if (messagesListEl.value) {
|
|
||||||
const element = messagesListEl.value
|
|
||||||
// 強制滾動到底部,使用 smooth 行為確保視覺效果
|
|
||||||
element.scrollTop = element.scrollHeight
|
|
||||||
|
|
||||||
// 再次確認滾動位置
|
|
||||||
setTimeout(() => {
|
|
||||||
if (element.scrollTop < element.scrollHeight - element.clientHeight) {
|
|
||||||
element.scrollTop = element.scrollHeight
|
|
||||||
}
|
|
||||||
}, 50)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
watch(
|
|
||||||
messages,
|
|
||||||
async (newMessages) => {
|
|
||||||
if (newMessages.length === 0) return
|
|
||||||
|
|
||||||
await nextTick()
|
|
||||||
|
|
||||||
// Simple, reliable scroll to bottom
|
|
||||||
setTimeout(() => {
|
|
||||||
scrollToBottom()
|
|
||||||
}, 100)
|
|
||||||
},
|
|
||||||
{ deep: true },
|
|
||||||
)
|
|
||||||
|
|
||||||
onMounted(async () => {
|
onMounted(async () => {
|
||||||
|
// 监听列表滚动,实时感知是否接近底部
|
||||||
|
if (messagesListEl.value) {
|
||||||
|
messagesListEl.value.addEventListener('scroll', updateNearBottom, { passive: true })
|
||||||
|
}
|
||||||
|
|
||||||
currentUser.value = await fetchCurrentUser()
|
currentUser.value = await fetchCurrentUser()
|
||||||
if (currentUser.value) {
|
if (currentUser.value) {
|
||||||
await fetchMessages(0)
|
await fetchMessages(0)
|
||||||
@@ -345,9 +346,8 @@ onMounted(async () => {
|
|||||||
|
|
||||||
watch(isConnected, (newValue) => {
|
watch(isConnected, (newValue) => {
|
||||||
if (newValue) {
|
if (newValue) {
|
||||||
// 等待一小段时间确保连接稳定
|
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
subscription = subscribe(`/topic/conversation/${conversationId}`, (message) => {
|
subscription = subscribe(`/topic/conversation/${conversationId}`, async (message) => {
|
||||||
// 避免重复显示当前用户发送的消息
|
// 避免重复显示当前用户发送的消息
|
||||||
if (message.sender.id !== currentUser.value.id) {
|
if (message.sender.id !== currentUser.value.id) {
|
||||||
messages.value.push({
|
messages.value.push({
|
||||||
@@ -357,11 +357,10 @@ watch(isConnected, (newValue) => {
|
|||||||
openUser(message.sender.id)
|
openUser(message.sender.id)
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
// 实时收到消息时自动标记为已读
|
// 收到消息后只标记已读,不强制滚动(符合“非发送不拉底”)
|
||||||
markConversationAsRead()
|
markConversationAsRead()
|
||||||
setTimeout(() => {
|
await nextTick()
|
||||||
scrollToBottom()
|
updateNearBottom()
|
||||||
}, 100)
|
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}, 500)
|
}, 500)
|
||||||
@@ -369,23 +368,12 @@ watch(isConnected, (newValue) => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
onActivated(async () => {
|
onActivated(async () => {
|
||||||
// This will be called every time the component is activated (navigated to)
|
// 返回页面时:刷新数据与已读,不做强制滚动,保持用户当前位置
|
||||||
if (currentUser.value) {
|
if (currentUser.value) {
|
||||||
await fetchMessages(0)
|
await fetchMessages(0)
|
||||||
await markConversationAsRead()
|
await markConversationAsRead()
|
||||||
|
|
||||||
// 確保滾動到底部 - 使用多重延遲策略
|
|
||||||
await nextTick()
|
await nextTick()
|
||||||
setTimeout(() => {
|
updateNearBottom()
|
||||||
scrollToBottom()
|
|
||||||
}, 100)
|
|
||||||
setTimeout(() => {
|
|
||||||
scrollToBottom()
|
|
||||||
}, 300)
|
|
||||||
setTimeout(() => {
|
|
||||||
scrollToBottom()
|
|
||||||
}, 500)
|
|
||||||
|
|
||||||
if (!isConnected.value) {
|
if (!isConnected.value) {
|
||||||
const token = getToken()
|
const token = getToken()
|
||||||
if (token) connect(token)
|
if (token) connect(token)
|
||||||
@@ -406,6 +394,9 @@ onUnmounted(() => {
|
|||||||
subscription.unsubscribe()
|
subscription.unsubscribe()
|
||||||
subscription = null
|
subscription = null
|
||||||
}
|
}
|
||||||
|
if (messagesListEl.value) {
|
||||||
|
messagesListEl.value.removeEventListener('scroll', updateNearBottom)
|
||||||
|
}
|
||||||
disconnect()
|
disconnect()
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|||||||
@@ -104,6 +104,31 @@
|
|||||||
,获得{{ item.amount }}积分
|
,获得{{ item.amount }}积分
|
||||||
</template>
|
</template>
|
||||||
</template>
|
</template>
|
||||||
|
<template v-else-if="item.type === 'POST_LIKE_CANCELLED' && item.fromUserId">
|
||||||
|
你的帖子
|
||||||
|
<NuxtLink :to="`/posts/${item.postId}`" class="timeline-link">
|
||||||
|
{{ item.postTitle }}
|
||||||
|
</NuxtLink>
|
||||||
|
被
|
||||||
|
<NuxtLink :to="`/users/${item.fromUserId}`" class="timeline-link">
|
||||||
|
{{ item.fromUserName }}
|
||||||
|
</NuxtLink>
|
||||||
|
取消点赞,扣除{{ -item.amount }}积分
|
||||||
|
</template>
|
||||||
|
<template v-else-if="item.type === 'COMMENT_LIKE_CANCELLED' && item.fromUserId">
|
||||||
|
你的评论
|
||||||
|
<NuxtLink
|
||||||
|
:to="`/posts/${item.postId}#comment-${item.commentId}`"
|
||||||
|
class="timeline-link"
|
||||||
|
>
|
||||||
|
{{ stripMarkdownLength(item.commentContent, 100) }}
|
||||||
|
</NuxtLink>
|
||||||
|
被
|
||||||
|
<NuxtLink :to="`/users/${item.fromUserId}`" class="timeline-link">
|
||||||
|
{{ item.fromUserName }}
|
||||||
|
</NuxtLink>
|
||||||
|
取消点赞,扣除{{ -item.amount }}积分
|
||||||
|
</template>
|
||||||
<template v-else-if="item.type === 'POST_LIKED' && item.fromUserId">
|
<template v-else-if="item.type === 'POST_LIKED' && item.fromUserId">
|
||||||
帖子
|
帖子
|
||||||
<NuxtLink :to="`/posts/${item.postId}`" class="timeline-link">{{
|
<NuxtLink :to="`/posts/${item.postId}`" class="timeline-link">{{
|
||||||
@@ -227,6 +252,8 @@ const iconMap = {
|
|||||||
FEATURE: 'fas fa-star',
|
FEATURE: 'fas fa-star',
|
||||||
LOTTERY_JOIN: 'fas fa-ticket-alt',
|
LOTTERY_JOIN: 'fas fa-ticket-alt',
|
||||||
LOTTERY_REWARD: 'fas fa-ticket-alt',
|
LOTTERY_REWARD: 'fas fa-ticket-alt',
|
||||||
|
POST_LIKE_CANCELLED: 'fas fa-thumbs-down',
|
||||||
|
COMMENT_LIKE_CANCELLED: 'fas fa-thumbs-down',
|
||||||
}
|
}
|
||||||
|
|
||||||
const loadTrend = async () => {
|
const loadTrend = async () => {
|
||||||
|
|||||||
@@ -4,6 +4,17 @@ export default class TimeManager {
|
|||||||
if (Number.isNaN(date.getTime())) return ''
|
if (Number.isNaN(date.getTime())) return ''
|
||||||
|
|
||||||
const now = new Date()
|
const now = new Date()
|
||||||
|
const diffMs = now.getTime() - date.getTime()
|
||||||
|
|
||||||
|
if (diffMs >= 0 && diffMs < 60 * 1000) {
|
||||||
|
return '刚刚'
|
||||||
|
}
|
||||||
|
|
||||||
|
if (diffMs >= 0 && diffMs < 60 * 60 * 1000) {
|
||||||
|
const mins = Math.floor(diffMs / 60_000)
|
||||||
|
return `${mins || 1}分钟前`
|
||||||
|
}
|
||||||
|
|
||||||
const startOfToday = new Date(now.getFullYear(), now.getMonth(), now.getDate())
|
const startOfToday = new Date(now.getFullYear(), now.getMonth(), now.getDate())
|
||||||
const startOfDate = new Date(date.getFullYear(), date.getMonth(), date.getDate())
|
const startOfDate = new Date(date.getFullYear(), date.getMonth(), date.getDate())
|
||||||
const diffDays = Math.floor((startOfToday - startOfDate) / 86400000)
|
const diffDays = Math.floor((startOfToday - startOfDate) / 86400000)
|
||||||
|
|||||||
Reference in New Issue
Block a user