mirror of
https://github.com/nagisa77/OpenIsle.git
synced 2026-02-21 06:20:59 +08:00
Merge pull request #512 from nagisa77/feature/message_control
feat: message control
This commit is contained in:
@@ -3,6 +3,8 @@ package com.openisle.controller;
|
||||
import com.openisle.dto.NotificationDto;
|
||||
import com.openisle.dto.NotificationMarkReadRequest;
|
||||
import com.openisle.dto.NotificationUnreadCountDto;
|
||||
import com.openisle.dto.NotificationPreferenceDto;
|
||||
import com.openisle.dto.NotificationPreferenceUpdateRequest;
|
||||
import com.openisle.mapper.NotificationMapper;
|
||||
import com.openisle.service.NotificationService;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
@@ -40,4 +42,14 @@ public class NotificationController {
|
||||
public void markRead(@RequestBody NotificationMarkReadRequest req, Authentication auth) {
|
||||
notificationService.markRead(auth.getName(), req.getIds());
|
||||
}
|
||||
|
||||
@GetMapping("/prefs")
|
||||
public List<NotificationPreferenceDto> prefs(Authentication auth) {
|
||||
return notificationService.listPreferences(auth.getName());
|
||||
}
|
||||
|
||||
@PostMapping("/prefs")
|
||||
public void updatePref(@RequestBody NotificationPreferenceUpdateRequest req, Authentication auth) {
|
||||
notificationService.updatePreference(auth.getName(), req.getType(), req.isEnabled());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,11 @@
|
||||
package com.openisle.dto;
|
||||
|
||||
import com.openisle.model.NotificationType;
|
||||
import lombok.Data;
|
||||
|
||||
/** User notification preference DTO. */
|
||||
@Data
|
||||
public class NotificationPreferenceDto {
|
||||
private NotificationType type;
|
||||
private boolean enabled;
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
package com.openisle.dto;
|
||||
|
||||
import com.openisle.model.NotificationType;
|
||||
import lombok.Data;
|
||||
|
||||
/** Request to update a single notification preference. */
|
||||
@Data
|
||||
public class NotificationPreferenceUpdateRequest {
|
||||
private NotificationType type;
|
||||
private boolean enabled;
|
||||
}
|
||||
@@ -7,6 +7,9 @@ import lombok.Setter;
|
||||
import org.hibernate.annotations.CreationTimestamp;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.EnumSet;
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* Simple user entity with basic fields and a role.
|
||||
@@ -62,6 +65,15 @@ public class User {
|
||||
@Enumerated(EnumType.STRING)
|
||||
private MedalType displayMedal;
|
||||
|
||||
@ElementCollection(targetClass = NotificationType.class)
|
||||
@CollectionTable(name = "user_disabled_notification_types", joinColumns = @JoinColumn(name = "user_id"))
|
||||
@Column(name = "notification_type")
|
||||
@Enumerated(EnumType.STRING)
|
||||
private Set<NotificationType> disabledNotificationTypes = EnumSet.of(
|
||||
NotificationType.POST_VIEWED,
|
||||
NotificationType.USER_ACTIVITY
|
||||
);
|
||||
|
||||
@CreationTimestamp
|
||||
@Column(nullable = false, updatable = false,
|
||||
columnDefinition = "DATETIME(6) DEFAULT CURRENT_TIMESTAMP(6)")
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package com.openisle.service;
|
||||
|
||||
import com.openisle.model.*;
|
||||
import com.openisle.dto.NotificationPreferenceDto;
|
||||
import com.openisle.repository.NotificationRepository;
|
||||
import com.openisle.repository.ReactionRepository;
|
||||
import com.openisle.repository.UserRepository;
|
||||
@@ -20,7 +21,9 @@ import java.util.Set;
|
||||
import java.util.HashSet;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.ArrayList;
|
||||
import java.util.concurrent.Executor;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/** Service for creating and retrieving notifications. */
|
||||
@Service
|
||||
@@ -138,13 +141,43 @@ public class NotificationService {
|
||||
}
|
||||
}
|
||||
|
||||
public List<NotificationPreferenceDto> listPreferences(String username) {
|
||||
User user = userRepository.findByUsername(username)
|
||||
.orElseThrow(() -> new com.openisle.exception.NotFoundException("User not found"));
|
||||
Set<NotificationType> disabled = user.getDisabledNotificationTypes();
|
||||
List<NotificationPreferenceDto> prefs = new ArrayList<>();
|
||||
for (NotificationType nt : NotificationType.values()) {
|
||||
NotificationPreferenceDto dto = new NotificationPreferenceDto();
|
||||
dto.setType(nt);
|
||||
dto.setEnabled(!disabled.contains(nt));
|
||||
prefs.add(dto);
|
||||
}
|
||||
return prefs;
|
||||
}
|
||||
|
||||
public void updatePreference(String username, NotificationType type, boolean enabled) {
|
||||
User user = userRepository.findByUsername(username)
|
||||
.orElseThrow(() -> new com.openisle.exception.NotFoundException("User not found"));
|
||||
Set<NotificationType> disabled = user.getDisabledNotificationTypes();
|
||||
if (enabled) {
|
||||
disabled.remove(type);
|
||||
} else {
|
||||
disabled.add(type);
|
||||
}
|
||||
userRepository.save(user);
|
||||
}
|
||||
|
||||
public List<Notification> listNotifications(String username, Boolean read) {
|
||||
User user = userRepository.findByUsername(username)
|
||||
.orElseThrow(() -> new com.openisle.exception.NotFoundException("User not found"));
|
||||
Set<NotificationType> disabled = user.getDisabledNotificationTypes();
|
||||
List<Notification> list;
|
||||
if (read == null) {
|
||||
return notificationRepository.findByUserOrderByCreatedAtDesc(user);
|
||||
list = notificationRepository.findByUserOrderByCreatedAtDesc(user);
|
||||
} else {
|
||||
list = notificationRepository.findByUserAndReadOrderByCreatedAtDesc(user, read);
|
||||
}
|
||||
return notificationRepository.findByUserAndReadOrderByCreatedAtDesc(user, read);
|
||||
return list.stream().filter(n -> !disabled.contains(n.getType())).collect(Collectors.toList());
|
||||
}
|
||||
|
||||
public void markRead(String username, List<Long> ids) {
|
||||
@@ -162,7 +195,9 @@ public class NotificationService {
|
||||
public long countUnread(String username) {
|
||||
User user = userRepository.findByUsername(username)
|
||||
.orElseThrow(() -> new com.openisle.exception.NotFoundException("User not found"));
|
||||
return notificationRepository.countByUserAndRead(user, false);
|
||||
Set<NotificationType> disabled = user.getDisabledNotificationTypes();
|
||||
return notificationRepository.findByUserAndReadOrderByCreatedAtDesc(user, false).stream()
|
||||
.filter(n -> !disabled.contains(n.getType())).count();
|
||||
}
|
||||
|
||||
public void notifyMentions(String content, User fromUser, Post post, Comment comment) {
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
text="建站送奶茶活动火热进行中,快来参与吧!"
|
||||
@close="closeMilkTeaPopup"
|
||||
/>
|
||||
<NotificationSettingPopup :visible="showNotificationPopup" @close="closeNotificationPopup" />
|
||||
<MedalPopup :visible="showMedalPopup" :medals="newMedals" @close="closeMedalPopup" />
|
||||
</div>
|
||||
</template>
|
||||
@@ -13,25 +14,30 @@
|
||||
<script>
|
||||
import ActivityPopup from '~/components/ActivityPopup.vue'
|
||||
import MedalPopup from '~/components/MedalPopup.vue'
|
||||
import NotificationSettingPopup from '~/components/NotificationSettingPopup.vue'
|
||||
import { API_BASE_URL } from '~/main'
|
||||
import { authState } from '~/utils/auth'
|
||||
|
||||
export default {
|
||||
name: 'GlobalPopups',
|
||||
components: { ActivityPopup, MedalPopup },
|
||||
components: { ActivityPopup, MedalPopup, NotificationSettingPopup },
|
||||
data() {
|
||||
return {
|
||||
showMilkTeaPopup: false,
|
||||
milkTeaIcon: '',
|
||||
showNotificationPopup: false,
|
||||
showMedalPopup: false,
|
||||
newMedals: [],
|
||||
}
|
||||
},
|
||||
async mounted() {
|
||||
await this.checkMilkTeaActivity()
|
||||
if (!this.showMilkTeaPopup) {
|
||||
await this.checkNewMedals()
|
||||
}
|
||||
if (this.showMilkTeaPopup) return
|
||||
|
||||
await this.checkNotificationSetting()
|
||||
if (this.showNotificationPopup) return
|
||||
|
||||
await this.checkNewMedals()
|
||||
},
|
||||
methods: {
|
||||
async checkMilkTeaActivity() {
|
||||
@@ -55,6 +61,18 @@ export default {
|
||||
if (!process.client) return
|
||||
localStorage.setItem('milkTeaActivityPopupShown', 'true')
|
||||
this.showMilkTeaPopup = false
|
||||
this.checkNotificationSetting()
|
||||
},
|
||||
async checkNotificationSetting() {
|
||||
if (!process.client) return
|
||||
if (!authState.loggedIn) return
|
||||
if (localStorage.getItem('notificationSettingPopupShown')) return
|
||||
this.showNotificationPopup = true
|
||||
},
|
||||
closeNotificationPopup() {
|
||||
if (!process.client) return
|
||||
localStorage.setItem('notificationSettingPopupShown', 'true')
|
||||
this.showNotificationPopup = false
|
||||
this.checkNewMedals()
|
||||
},
|
||||
async checkNewMedals() {
|
||||
|
||||
82
frontend_nuxt/components/NotificationSettingPopup.vue
Normal file
82
frontend_nuxt/components/NotificationSettingPopup.vue
Normal file
@@ -0,0 +1,82 @@
|
||||
<template>
|
||||
<BasePopup :visible="visible" @close="close">
|
||||
<div class="notification-popup">
|
||||
<div class="notification-popup-title">🎉 通知设置上线啦</div>
|
||||
<div class="notification-popup-text">现在可以在消息 -> 消息设置中调整通知类型</div>
|
||||
<div class="notification-popup-actions">
|
||||
<div class="notification-popup-close" @click="close">知道了</div>
|
||||
<div class="notification-popup-button" @click="gotoSetting">去看看</div>
|
||||
</div>
|
||||
</div>
|
||||
</BasePopup>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import BasePopup from '~/components/BasePopup.vue'
|
||||
import { useRouter } from 'vue-router'
|
||||
|
||||
export default {
|
||||
name: 'NotificationSettingPopup',
|
||||
components: { BasePopup },
|
||||
props: {
|
||||
visible: { type: Boolean, default: false },
|
||||
},
|
||||
emits: ['close'],
|
||||
setup(props, { emit }) {
|
||||
const router = useRouter()
|
||||
const gotoSetting = () => {
|
||||
emit('close')
|
||||
router.push('/message?tab=control')
|
||||
}
|
||||
const close = () => emit('close')
|
||||
return { gotoSetting, close }
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.notification-popup {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
text-align: center;
|
||||
gap: 10px;
|
||||
min-width: 200px;
|
||||
}
|
||||
|
||||
.notification-popup-title {
|
||||
font-size: 18px;
|
||||
font-weight: bold;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.notification-popup-actions {
|
||||
margin-top: 10px;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
gap: 20px;
|
||||
}
|
||||
|
||||
.notification-popup-button {
|
||||
background-color: var(--primary-color);
|
||||
color: #fff;
|
||||
padding: 8px 16px;
|
||||
border-radius: 8px;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.notification-popup-button:hover {
|
||||
background-color: var(--primary-color-hover);
|
||||
}
|
||||
|
||||
.notification-popup-close {
|
||||
cursor: pointer;
|
||||
color: var(--primary-color);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.notification-popup-close:hover {
|
||||
text-decoration: underline;
|
||||
}
|
||||
</style>
|
||||
@@ -14,6 +14,12 @@
|
||||
>
|
||||
未读
|
||||
</div>
|
||||
<div
|
||||
:class="['message-tab-item', { selected: selectedTab === 'control' }]"
|
||||
@click="selectedTab = 'control'"
|
||||
>
|
||||
消息设置
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="message-page-header-right">
|
||||
@@ -24,32 +30,192 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div v-if="isLoadingMessage" class="loading-message">
|
||||
<l-hatch size="28" stroke="4" speed="3.5" color="var(--primary-color)"></l-hatch>
|
||||
<div v-if="selectedTab === 'control'">
|
||||
<div class="message-control-container">
|
||||
<div class="message-control-title">通知设置</div>
|
||||
<div class="message-control-push-item-container">
|
||||
<div
|
||||
v-for="pref in notificationPrefs"
|
||||
:key="pref.type"
|
||||
class="message-control-push-item"
|
||||
:class="{ select: pref.enabled }"
|
||||
@click="togglePref(pref)"
|
||||
>
|
||||
{{ formatType(pref.type) }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<BasePlaceholder
|
||||
v-else-if="filteredNotifications.length === 0"
|
||||
text="暂时没有消息 :)"
|
||||
icon="fas fa-inbox"
|
||||
/>
|
||||
<template v-else>
|
||||
<div v-if="isLoadingMessage" class="loading-message">
|
||||
<l-hatch size="28" stroke="4" speed="3.5" color="var(--primary-color)"></l-hatch>
|
||||
</div>
|
||||
|
||||
<div class="timeline-container" v-if="filteredNotifications.length > 0">
|
||||
<BaseTimeline :items="filteredNotifications">
|
||||
<template #item="{ item }">
|
||||
<div class="notif-content" :class="{ read: item.read }">
|
||||
<span v-if="!item.read" class="unread-dot"></span>
|
||||
<span class="notif-type">
|
||||
<template v-if="item.type === 'COMMENT_REPLY' && item.parentComment">
|
||||
<NotificationContainer :item="item" :markRead="markRead">
|
||||
<router-link
|
||||
class="notif-content-text"
|
||||
@click="markRead(item.id)"
|
||||
:to="`/users/${item.comment.author.id}`"
|
||||
>{{ item.comment.author.username }}
|
||||
</router-link>
|
||||
对我的评论
|
||||
<span>
|
||||
<BasePlaceholder
|
||||
v-else-if="filteredNotifications.length === 0"
|
||||
text="暂时没有消息 :)"
|
||||
icon="fas fa-inbox"
|
||||
/>
|
||||
|
||||
<div class="timeline-container" v-if="filteredNotifications.length > 0">
|
||||
<BaseTimeline :items="filteredNotifications">
|
||||
<template #item="{ item }">
|
||||
<div class="notif-content" :class="{ read: item.read }">
|
||||
<span v-if="!item.read" class="unread-dot"></span>
|
||||
<span class="notif-type">
|
||||
<template v-if="item.type === 'COMMENT_REPLY' && item.parentComment">
|
||||
<NotificationContainer :item="item" :markRead="markRead">
|
||||
<router-link
|
||||
class="notif-content-text"
|
||||
@click="markRead(item.id)"
|
||||
:to="`/users/${item.comment.author.id}`"
|
||||
>{{ item.comment.author.username }}
|
||||
</router-link>
|
||||
对我的评论
|
||||
<span>
|
||||
<router-link
|
||||
class="notif-content-text"
|
||||
@click="markRead(item.id)"
|
||||
:to="`/posts/${item.post.id}#comment-${item.parentComment.id}`"
|
||||
>
|
||||
{{ stripMarkdownLength(item.parentComment.content, 100) }}
|
||||
</router-link>
|
||||
</span>
|
||||
回复了
|
||||
<span>
|
||||
<router-link
|
||||
class="notif-content-text"
|
||||
@click="markRead(item.id)"
|
||||
:to="`/posts/${item.post.id}#comment-${item.comment.id}`"
|
||||
>
|
||||
{{ stripMarkdownLength(item.comment.content, 100) }}
|
||||
</router-link>
|
||||
</span>
|
||||
</NotificationContainer>
|
||||
</template>
|
||||
<template v-else-if="item.type === 'COMMENT_REPLY' && !item.parentComment">
|
||||
<NotificationContainer :item="item" :markRead="markRead">
|
||||
<router-link
|
||||
class="notif-content-text"
|
||||
@click="markRead(item.id)"
|
||||
:to="`/users/${item.comment.author.id}`"
|
||||
>{{ item.comment.author.username }}
|
||||
</router-link>
|
||||
对我的文章
|
||||
<span>
|
||||
<router-link
|
||||
class="notif-content-text"
|
||||
@click="markRead(item.id)"
|
||||
:to="`/posts/${item.post.id}`"
|
||||
>
|
||||
{{ stripMarkdownLength(item.post.title, 100) }}
|
||||
</router-link>
|
||||
</span>
|
||||
回复了
|
||||
<span>
|
||||
<router-link
|
||||
class="notif-content-text"
|
||||
@click="markRead(item.id)"
|
||||
:to="`/posts/${item.post.id}#comment-${item.comment.id}`"
|
||||
>
|
||||
{{ stripMarkdownLength(item.comment.content, 100) }}
|
||||
</router-link>
|
||||
</span>
|
||||
</NotificationContainer>
|
||||
</template>
|
||||
<template v-else-if="item.type === 'ACTIVITY_REDEEM' && !item.parentComment">
|
||||
<NotificationContainer :item="item" :markRead="markRead">
|
||||
<span class="notif-user">{{ item.fromUser.username }} </span>
|
||||
申请进行奶茶兑换,联系方式是:{{ item.content }}
|
||||
</NotificationContainer>
|
||||
</template>
|
||||
<template v-else-if="item.type === 'REACTION' && item.post && !item.comment">
|
||||
<NotificationContainer :item="item" :markRead="markRead">
|
||||
<span class="notif-user">{{ item.fromUser.username }} </span> 对我的文章
|
||||
<span>
|
||||
<router-link
|
||||
class="notif-content-text"
|
||||
@click="markRead(item.id)"
|
||||
:to="`/posts/${item.post.id}`"
|
||||
>
|
||||
{{ stripMarkdownLength(item.post.title, 100) }}
|
||||
</router-link>
|
||||
</span>
|
||||
进行了表态
|
||||
</NotificationContainer>
|
||||
</template>
|
||||
<template v-else-if="item.type === 'REACTION' && item.comment">
|
||||
<NotificationContainer :item="item" :markRead="markRead">
|
||||
<router-link
|
||||
class="notif-content-text"
|
||||
@click="markRead(item.id)"
|
||||
:to="`/users/${item.fromUser.id}`"
|
||||
>{{ item.fromUser.username }}
|
||||
</router-link>
|
||||
对我的评论
|
||||
<span>
|
||||
<router-link
|
||||
class="notif-content-text"
|
||||
@click="markRead(item.id)"
|
||||
:to="`/posts/${item.post.id}#comment-${item.comment.id}`"
|
||||
>
|
||||
{{ stripMarkdownLength(item.comment.content, 100) }}
|
||||
</router-link>
|
||||
</span>
|
||||
进行了表态
|
||||
</NotificationContainer>
|
||||
</template>
|
||||
<template v-else-if="item.type === 'POST_VIEWED'">
|
||||
<NotificationContainer :item="item" :markRead="markRead">
|
||||
<router-link
|
||||
class="notif-content-text"
|
||||
@click="markRead(item.id)"
|
||||
:to="`/users/${item.fromUser.id}`"
|
||||
>
|
||||
{{ item.fromUser.username }}
|
||||
</router-link>
|
||||
查看了您的帖子
|
||||
<router-link
|
||||
class="notif-content-text"
|
||||
@click="markRead(item.id)"
|
||||
:to="`/posts/${item.post.id}`"
|
||||
>
|
||||
{{ stripMarkdownLength(item.post.title, 100) }}
|
||||
</router-link>
|
||||
</NotificationContainer>
|
||||
</template>
|
||||
<template v-else-if="item.type === 'POST_UPDATED'">
|
||||
<NotificationContainer :item="item" :markRead="markRead">
|
||||
您关注的帖子
|
||||
<router-link
|
||||
class="notif-content-text"
|
||||
@click="markRead(item.id)"
|
||||
:to="`/posts/${item.post.id}`"
|
||||
>
|
||||
{{ stripMarkdownLength(item.post.title, 100) }}
|
||||
</router-link>
|
||||
下面有新评论
|
||||
<router-link
|
||||
class="notif-content-text"
|
||||
@click="markRead(item.id)"
|
||||
:to="`/posts/${item.post.id}#comment-${item.comment.id}`"
|
||||
>
|
||||
{{ stripMarkdownLength(item.comment.content, 100) }}
|
||||
</router-link>
|
||||
</NotificationContainer>
|
||||
</template>
|
||||
<template v-else-if="item.type === 'USER_ACTIVITY' && item.parentComment">
|
||||
<NotificationContainer :item="item" :markRead="markRead">
|
||||
你关注的
|
||||
<router-link
|
||||
class="notif-content-text"
|
||||
@click="markRead(item.id)"
|
||||
:to="`/users/${item.comment.author.id}`"
|
||||
>
|
||||
{{ item.comment.author.username }}
|
||||
</router-link>
|
||||
在 对评论
|
||||
<router-link
|
||||
class="notif-content-text"
|
||||
@click="markRead(item.id)"
|
||||
@@ -57,9 +223,7 @@
|
||||
>
|
||||
{{ stripMarkdownLength(item.parentComment.content, 100) }}
|
||||
</router-link>
|
||||
</span>
|
||||
回复了
|
||||
<span>
|
||||
回复了
|
||||
<router-link
|
||||
class="notif-content-text"
|
||||
@click="markRead(item.id)"
|
||||
@@ -67,19 +231,19 @@
|
||||
>
|
||||
{{ stripMarkdownLength(item.comment.content, 100) }}
|
||||
</router-link>
|
||||
</span>
|
||||
</NotificationContainer>
|
||||
</template>
|
||||
<template v-else-if="item.type === 'COMMENT_REPLY' && !item.parentComment">
|
||||
<NotificationContainer :item="item" :markRead="markRead">
|
||||
<router-link
|
||||
class="notif-content-text"
|
||||
@click="markRead(item.id)"
|
||||
:to="`/users/${item.comment.author.id}`"
|
||||
>{{ item.comment.author.username }}
|
||||
</router-link>
|
||||
对我的文章
|
||||
<span>
|
||||
</NotificationContainer>
|
||||
</template>
|
||||
<template v-else-if="item.type === 'USER_ACTIVITY'">
|
||||
<NotificationContainer :item="item" :markRead="markRead">
|
||||
你关注的
|
||||
<router-link
|
||||
class="notif-content-text"
|
||||
@click="markRead(item.id)"
|
||||
:to="`/users/${item.comment.author.id}`"
|
||||
>
|
||||
{{ item.comment.author.username }}
|
||||
</router-link>
|
||||
在文章
|
||||
<router-link
|
||||
class="notif-content-text"
|
||||
@click="markRead(item.id)"
|
||||
@@ -87,9 +251,7 @@
|
||||
>
|
||||
{{ stripMarkdownLength(item.post.title, 100) }}
|
||||
</router-link>
|
||||
</span>
|
||||
回复了
|
||||
<span>
|
||||
下面评论了
|
||||
<router-link
|
||||
class="notif-content-text"
|
||||
@click="markRead(item.id)"
|
||||
@@ -97,19 +259,37 @@
|
||||
>
|
||||
{{ stripMarkdownLength(item.comment.content, 100) }}
|
||||
</router-link>
|
||||
</span>
|
||||
</NotificationContainer>
|
||||
</template>
|
||||
<template v-else-if="item.type === 'ACTIVITY_REDEEM' && !item.parentComment">
|
||||
<NotificationContainer :item="item" :markRead="markRead">
|
||||
<span class="notif-user">{{ item.fromUser.username }} </span>
|
||||
申请进行奶茶兑换,联系方式是:{{ item.content }}
|
||||
</NotificationContainer>
|
||||
</template>
|
||||
<template v-else-if="item.type === 'REACTION' && item.post && !item.comment">
|
||||
<NotificationContainer :item="item" :markRead="markRead">
|
||||
<span class="notif-user">{{ item.fromUser.username }} </span> 对我的文章
|
||||
<span>
|
||||
</NotificationContainer>
|
||||
</template>
|
||||
<template v-else-if="item.type === 'MENTION' && item.comment">
|
||||
<NotificationContainer :item="item" :markRead="markRead">
|
||||
<router-link
|
||||
class="notif-content-text"
|
||||
@click="markRead(item.id)"
|
||||
:to="`/users/${item.fromUser.id}`"
|
||||
>
|
||||
{{ item.fromUser.username }}
|
||||
</router-link>
|
||||
在评论中提到了你:
|
||||
<router-link
|
||||
class="notif-content-text"
|
||||
@click="markRead(item.id)"
|
||||
:to="`/posts/${item.post.id}#comment-${item.comment.id}`"
|
||||
>
|
||||
{{ stripMarkdownLength(item.comment.content, 100) }}
|
||||
</router-link>
|
||||
</NotificationContainer>
|
||||
</template>
|
||||
<template v-else-if="item.type === 'MENTION'">
|
||||
<NotificationContainer :item="item" :markRead="markRead">
|
||||
<router-link
|
||||
class="notif-content-text"
|
||||
@click="markRead(item.id)"
|
||||
:to="`/users/${item.fromUser.id}`"
|
||||
>
|
||||
{{ item.fromUser.username }}
|
||||
</router-link>
|
||||
在帖子
|
||||
<router-link
|
||||
class="notif-content-text"
|
||||
@click="markRead(item.id)"
|
||||
@@ -117,339 +297,184 @@
|
||||
>
|
||||
{{ stripMarkdownLength(item.post.title, 100) }}
|
||||
</router-link>
|
||||
</span>
|
||||
进行了表态
|
||||
</NotificationContainer>
|
||||
</template>
|
||||
<template v-else-if="item.type === 'REACTION' && item.comment">
|
||||
<NotificationContainer :item="item" :markRead="markRead">
|
||||
<router-link
|
||||
class="notif-content-text"
|
||||
@click="markRead(item.id)"
|
||||
:to="`/users/${item.fromUser.id}`"
|
||||
>{{ item.fromUser.username }}
|
||||
</router-link>
|
||||
对我的评论
|
||||
<span>
|
||||
中提到了你
|
||||
</NotificationContainer>
|
||||
</template>
|
||||
<template v-else-if="item.type === 'USER_FOLLOWED'">
|
||||
<NotificationContainer :item="item" :markRead="markRead">
|
||||
<router-link
|
||||
class="notif-content-text"
|
||||
@click="markRead(item.id)"
|
||||
:to="`/posts/${item.post.id}#comment-${item.comment.id}`"
|
||||
:to="`/users/${item.fromUser.id}`"
|
||||
>
|
||||
{{ stripMarkdownLength(item.comment.content, 100) }}
|
||||
{{ item.fromUser.username }}
|
||||
</router-link>
|
||||
</span>
|
||||
进行了表态
|
||||
</NotificationContainer>
|
||||
</template>
|
||||
<template v-else-if="item.type === 'POST_VIEWED'">
|
||||
<NotificationContainer :item="item" :markRead="markRead">
|
||||
<router-link
|
||||
class="notif-content-text"
|
||||
@click="markRead(item.id)"
|
||||
:to="`/users/${item.fromUser.id}`"
|
||||
>
|
||||
{{ item.fromUser.username }}
|
||||
</router-link>
|
||||
查看了您的帖子
|
||||
<router-link
|
||||
class="notif-content-text"
|
||||
@click="markRead(item.id)"
|
||||
:to="`/posts/${item.post.id}`"
|
||||
>
|
||||
{{ stripMarkdownLength(item.post.title, 100) }}
|
||||
</router-link>
|
||||
</NotificationContainer>
|
||||
</template>
|
||||
<template v-else-if="item.type === 'POST_UPDATED'">
|
||||
<NotificationContainer :item="item" :markRead="markRead">
|
||||
您关注的帖子
|
||||
<router-link
|
||||
class="notif-content-text"
|
||||
@click="markRead(item.id)"
|
||||
:to="`/posts/${item.post.id}`"
|
||||
>
|
||||
{{ stripMarkdownLength(item.post.title, 100) }}
|
||||
</router-link>
|
||||
下面有新评论
|
||||
<router-link
|
||||
class="notif-content-text"
|
||||
@click="markRead(item.id)"
|
||||
:to="`/posts/${item.post.id}#comment-${item.comment.id}`"
|
||||
>
|
||||
{{ stripMarkdownLength(item.comment.content, 100) }}
|
||||
</router-link>
|
||||
</NotificationContainer>
|
||||
</template>
|
||||
<template v-else-if="item.type === 'USER_ACTIVITY' && item.parentComment">
|
||||
<NotificationContainer :item="item" :markRead="markRead">
|
||||
你关注的
|
||||
<router-link
|
||||
class="notif-content-text"
|
||||
@click="markRead(item.id)"
|
||||
:to="`/users/${item.comment.author.id}`"
|
||||
>
|
||||
{{ item.comment.author.username }}
|
||||
</router-link>
|
||||
在 对评论
|
||||
<router-link
|
||||
class="notif-content-text"
|
||||
@click="markRead(item.id)"
|
||||
:to="`/posts/${item.post.id}#comment-${item.parentComment.id}`"
|
||||
>
|
||||
{{ stripMarkdownLength(item.parentComment.content, 100) }}
|
||||
</router-link>
|
||||
回复了
|
||||
<router-link
|
||||
class="notif-content-text"
|
||||
@click="markRead(item.id)"
|
||||
:to="`/posts/${item.post.id}#comment-${item.comment.id}`"
|
||||
>
|
||||
{{ stripMarkdownLength(item.comment.content, 100) }}
|
||||
</router-link>
|
||||
</NotificationContainer>
|
||||
</template>
|
||||
<template v-else-if="item.type === 'USER_ACTIVITY'">
|
||||
<NotificationContainer :item="item" :markRead="markRead">
|
||||
你关注的
|
||||
<router-link
|
||||
class="notif-content-text"
|
||||
@click="markRead(item.id)"
|
||||
:to="`/users/${item.comment.author.id}`"
|
||||
>
|
||||
{{ item.comment.author.username }}
|
||||
</router-link>
|
||||
在文章
|
||||
<router-link
|
||||
class="notif-content-text"
|
||||
@click="markRead(item.id)"
|
||||
:to="`/posts/${item.post.id}`"
|
||||
>
|
||||
{{ stripMarkdownLength(item.post.title, 100) }}
|
||||
</router-link>
|
||||
下面评论了
|
||||
<router-link
|
||||
class="notif-content-text"
|
||||
@click="markRead(item.id)"
|
||||
:to="`/posts/${item.post.id}#comment-${item.comment.id}`"
|
||||
>
|
||||
{{ stripMarkdownLength(item.comment.content, 100) }}
|
||||
</router-link>
|
||||
</NotificationContainer>
|
||||
</template>
|
||||
<template v-else-if="item.type === 'MENTION' && item.comment">
|
||||
<NotificationContainer :item="item" :markRead="markRead">
|
||||
<router-link
|
||||
class="notif-content-text"
|
||||
@click="markRead(item.id)"
|
||||
:to="`/users/${item.fromUser.id}`"
|
||||
>
|
||||
{{ item.fromUser.username }}
|
||||
</router-link>
|
||||
在评论中提到了你:
|
||||
<router-link
|
||||
class="notif-content-text"
|
||||
@click="markRead(item.id)"
|
||||
:to="`/posts/${item.post.id}#comment-${item.comment.id}`"
|
||||
>
|
||||
{{ stripMarkdownLength(item.comment.content, 100) }}
|
||||
</router-link>
|
||||
</NotificationContainer>
|
||||
</template>
|
||||
<template v-else-if="item.type === 'MENTION'">
|
||||
<NotificationContainer :item="item" :markRead="markRead">
|
||||
<router-link
|
||||
class="notif-content-text"
|
||||
@click="markRead(item.id)"
|
||||
:to="`/users/${item.fromUser.id}`"
|
||||
>
|
||||
{{ item.fromUser.username }}
|
||||
</router-link>
|
||||
在帖子
|
||||
<router-link
|
||||
class="notif-content-text"
|
||||
@click="markRead(item.id)"
|
||||
:to="`/posts/${item.post.id}`"
|
||||
>
|
||||
{{ stripMarkdownLength(item.post.title, 100) }}
|
||||
</router-link>
|
||||
中提到了你
|
||||
</NotificationContainer>
|
||||
</template>
|
||||
<template v-else-if="item.type === 'USER_FOLLOWED'">
|
||||
<NotificationContainer :item="item" :markRead="markRead">
|
||||
<router-link
|
||||
class="notif-content-text"
|
||||
@click="markRead(item.id)"
|
||||
:to="`/users/${item.fromUser.id}`"
|
||||
>
|
||||
{{ item.fromUser.username }}
|
||||
</router-link>
|
||||
开始关注你了
|
||||
</NotificationContainer>
|
||||
</template>
|
||||
<template v-else-if="item.type === 'USER_UNFOLLOWED'">
|
||||
<NotificationContainer :item="item" :markRead="markRead">
|
||||
<router-link
|
||||
class="notif-content-text"
|
||||
@click="markRead(item.id)"
|
||||
:to="`/users/${item.fromUser.id}`"
|
||||
>
|
||||
{{ item.fromUser.username }}
|
||||
</router-link>
|
||||
取消关注你了
|
||||
</NotificationContainer>
|
||||
</template>
|
||||
<template v-else-if="item.type === 'FOLLOWED_POST'">
|
||||
<NotificationContainer :item="item" :markRead="markRead">
|
||||
你关注的
|
||||
<router-link
|
||||
class="notif-content-text"
|
||||
@click="markRead(item.id)"
|
||||
:to="`/users/${item.fromUser.id}`"
|
||||
>
|
||||
{{ item.fromUser.username }}
|
||||
</router-link>
|
||||
发布了文章
|
||||
<router-link
|
||||
class="notif-content-text"
|
||||
@click="markRead(item.id)"
|
||||
:to="`/posts/${item.post.id}`"
|
||||
>
|
||||
{{ stripMarkdownLength(item.post.title, 100) }}
|
||||
</router-link>
|
||||
</NotificationContainer>
|
||||
</template>
|
||||
<template v-else-if="item.type === 'POST_SUBSCRIBED'">
|
||||
<NotificationContainer :item="item" :markRead="markRead">
|
||||
<router-link
|
||||
class="notif-content-text"
|
||||
@click="markRead(item.id)"
|
||||
:to="`/users/${item.fromUser.id}`"
|
||||
>
|
||||
{{ item.fromUser.username }}
|
||||
</router-link>
|
||||
订阅了你的文章
|
||||
<router-link
|
||||
class="notif-content-text"
|
||||
@click="markRead(item.id)"
|
||||
:to="`/posts/${item.post.id}`"
|
||||
>
|
||||
{{ stripMarkdownLength(item.post.title, 100) }}
|
||||
</router-link>
|
||||
</NotificationContainer>
|
||||
</template>
|
||||
<template v-else-if="item.type === 'POST_UNSUBSCRIBED'">
|
||||
<NotificationContainer :item="item" :markRead="markRead">
|
||||
<router-link
|
||||
class="notif-content-text"
|
||||
@click="markRead(item.id)"
|
||||
:to="`/users/${item.fromUser.id}`"
|
||||
>
|
||||
{{ item.fromUser.username }}
|
||||
</router-link>
|
||||
取消订阅了你的文章
|
||||
<router-link
|
||||
class="notif-content-text"
|
||||
@click="markRead(item.id)"
|
||||
:to="`/posts/${item.post.id}`"
|
||||
>
|
||||
{{ stripMarkdownLength(item.post.title, 100) }}
|
||||
</router-link>
|
||||
</NotificationContainer>
|
||||
</template>
|
||||
<template v-else-if="item.type === 'POST_REVIEW_REQUEST' && item.fromUser">
|
||||
<NotificationContainer :item="item" :markRead="markRead">
|
||||
<router-link
|
||||
class="notif-content-text"
|
||||
@click="markRead(item.id)"
|
||||
:to="`/users/${item.fromUser.id}`"
|
||||
>
|
||||
{{ item.fromUser.username }}
|
||||
</router-link>
|
||||
发布了帖子
|
||||
<router-link
|
||||
class="notif-content-text"
|
||||
@click="markRead(item.id)"
|
||||
:to="`/posts/${item.post.id}`"
|
||||
>
|
||||
{{ stripMarkdownLength(item.post.title, 100) }}
|
||||
</router-link>
|
||||
,请审核
|
||||
</NotificationContainer>
|
||||
</template>
|
||||
<template v-else-if="item.type === 'POST_REVIEW_REQUEST'">
|
||||
<NotificationContainer :item="item" :markRead="markRead">
|
||||
您发布的帖子
|
||||
<router-link
|
||||
class="notif-content-text"
|
||||
@click="markRead(item.id)"
|
||||
:to="`/posts/${item.post.id}`"
|
||||
>
|
||||
{{ stripMarkdownLength(item.post.title, 100) }}
|
||||
</router-link>
|
||||
已提交审核
|
||||
</NotificationContainer>
|
||||
</template>
|
||||
<template v-else-if="item.type === 'REGISTER_REQUEST'">
|
||||
<NotificationContainer :item="item" :markRead="markRead">
|
||||
{{ item.fromUser.username }} 希望注册为会员,理由是:{{ item.content }}
|
||||
<template #actions v-if="authState.role === 'ADMIN'">
|
||||
<div v-if="!item.read" class="optional-buttons">
|
||||
<div
|
||||
class="mark-approve-button-item"
|
||||
@click="approve(item.fromUser.id, item.id)"
|
||||
>
|
||||
同意
|
||||
开始关注你了
|
||||
</NotificationContainer>
|
||||
</template>
|
||||
<template v-else-if="item.type === 'USER_UNFOLLOWED'">
|
||||
<NotificationContainer :item="item" :markRead="markRead">
|
||||
<router-link
|
||||
class="notif-content-text"
|
||||
@click="markRead(item.id)"
|
||||
:to="`/users/${item.fromUser.id}`"
|
||||
>
|
||||
{{ item.fromUser.username }}
|
||||
</router-link>
|
||||
取消关注你了
|
||||
</NotificationContainer>
|
||||
</template>
|
||||
<template v-else-if="item.type === 'FOLLOWED_POST'">
|
||||
<NotificationContainer :item="item" :markRead="markRead">
|
||||
你关注的
|
||||
<router-link
|
||||
class="notif-content-text"
|
||||
@click="markRead(item.id)"
|
||||
:to="`/users/${item.fromUser.id}`"
|
||||
>
|
||||
{{ item.fromUser.username }}
|
||||
</router-link>
|
||||
发布了文章
|
||||
<router-link
|
||||
class="notif-content-text"
|
||||
@click="markRead(item.id)"
|
||||
:to="`/posts/${item.post.id}`"
|
||||
>
|
||||
{{ stripMarkdownLength(item.post.title, 100) }}
|
||||
</router-link>
|
||||
</NotificationContainer>
|
||||
</template>
|
||||
<template v-else-if="item.type === 'POST_SUBSCRIBED'">
|
||||
<NotificationContainer :item="item" :markRead="markRead">
|
||||
<router-link
|
||||
class="notif-content-text"
|
||||
@click="markRead(item.id)"
|
||||
:to="`/users/${item.fromUser.id}`"
|
||||
>
|
||||
{{ item.fromUser.username }}
|
||||
</router-link>
|
||||
订阅了你的文章
|
||||
<router-link
|
||||
class="notif-content-text"
|
||||
@click="markRead(item.id)"
|
||||
:to="`/posts/${item.post.id}`"
|
||||
>
|
||||
{{ stripMarkdownLength(item.post.title, 100) }}
|
||||
</router-link>
|
||||
</NotificationContainer>
|
||||
</template>
|
||||
<template v-else-if="item.type === 'POST_UNSUBSCRIBED'">
|
||||
<NotificationContainer :item="item" :markRead="markRead">
|
||||
<router-link
|
||||
class="notif-content-text"
|
||||
@click="markRead(item.id)"
|
||||
:to="`/users/${item.fromUser.id}`"
|
||||
>
|
||||
{{ item.fromUser.username }}
|
||||
</router-link>
|
||||
取消订阅了你的文章
|
||||
<router-link
|
||||
class="notif-content-text"
|
||||
@click="markRead(item.id)"
|
||||
:to="`/posts/${item.post.id}`"
|
||||
>
|
||||
{{ stripMarkdownLength(item.post.title, 100) }}
|
||||
</router-link>
|
||||
</NotificationContainer>
|
||||
</template>
|
||||
<template v-else-if="item.type === 'POST_REVIEW_REQUEST' && item.fromUser">
|
||||
<NotificationContainer :item="item" :markRead="markRead">
|
||||
<router-link
|
||||
class="notif-content-text"
|
||||
@click="markRead(item.id)"
|
||||
:to="`/users/${item.fromUser.id}`"
|
||||
>
|
||||
{{ item.fromUser.username }}
|
||||
</router-link>
|
||||
发布了帖子
|
||||
<router-link
|
||||
class="notif-content-text"
|
||||
@click="markRead(item.id)"
|
||||
:to="`/posts/${item.post.id}`"
|
||||
>
|
||||
{{ stripMarkdownLength(item.post.title, 100) }}
|
||||
</router-link>
|
||||
,请审核
|
||||
</NotificationContainer>
|
||||
</template>
|
||||
<template v-else-if="item.type === 'POST_REVIEW_REQUEST'">
|
||||
<NotificationContainer :item="item" :markRead="markRead">
|
||||
您发布的帖子
|
||||
<router-link
|
||||
class="notif-content-text"
|
||||
@click="markRead(item.id)"
|
||||
:to="`/posts/${item.post.id}`"
|
||||
>
|
||||
{{ stripMarkdownLength(item.post.title, 100) }}
|
||||
</router-link>
|
||||
已提交审核
|
||||
</NotificationContainer>
|
||||
</template>
|
||||
<template v-else-if="item.type === 'REGISTER_REQUEST'">
|
||||
<NotificationContainer :item="item" :markRead="markRead">
|
||||
{{ item.fromUser.username }} 希望注册为会员,理由是:{{ item.content }}
|
||||
<template #actions v-if="authState.role === 'ADMIN'">
|
||||
<div v-if="!item.read" class="optional-buttons">
|
||||
<div
|
||||
class="mark-approve-button-item"
|
||||
@click="approve(item.fromUser.id, item.id)"
|
||||
>
|
||||
同意
|
||||
</div>
|
||||
<div
|
||||
class="mark-reject-button-item"
|
||||
@click="reject(item.fromUser.id, item.id)"
|
||||
>
|
||||
拒绝
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="mark-reject-button-item"
|
||||
@click="reject(item.fromUser.id, item.id)"
|
||||
>
|
||||
拒绝
|
||||
</div>
|
||||
</div>
|
||||
<div v-else class="has_read_button" @click="markRead(item.id)">已读</div>
|
||||
</template>
|
||||
</NotificationContainer>
|
||||
</template>
|
||||
<template v-else-if="item.type === 'POST_REVIEWED' && item.approved">
|
||||
<NotificationContainer :item="item" :markRead="markRead">
|
||||
您发布的帖子
|
||||
<router-link
|
||||
class="notif-content-text"
|
||||
@click="markRead(item.id)"
|
||||
:to="`/posts/${item.post.id}`"
|
||||
>
|
||||
{{ stripMarkdownLength(item.post.title, 100) }}
|
||||
</router-link>
|
||||
已审核通过
|
||||
</NotificationContainer>
|
||||
</template>
|
||||
<template v-else-if="item.type === 'POST_REVIEWED' && item.approved === false">
|
||||
<NotificationContainer :item="item" :markRead="markRead">
|
||||
您发布的帖子
|
||||
<router-link
|
||||
class="notif-content-text"
|
||||
@click="markRead(item.id)"
|
||||
:to="`/posts/${item.post.id}`"
|
||||
>
|
||||
{{ stripMarkdownLength(item.post.title, 100) }}
|
||||
</router-link>
|
||||
已被管理员拒绝
|
||||
</NotificationContainer>
|
||||
</template>
|
||||
<template v-else>
|
||||
<NotificationContainer :item="item" :markRead="markRead">
|
||||
{{ formatType(item.type) }}
|
||||
</NotificationContainer>
|
||||
</template>
|
||||
</span>
|
||||
<span class="notif-time">{{ TimeManager.format(item.createdAt) }}</span>
|
||||
</div>
|
||||
</template>
|
||||
</BaseTimeline>
|
||||
</div>
|
||||
<div v-else class="has_read_button" @click="markRead(item.id)">已读</div>
|
||||
</template>
|
||||
</NotificationContainer>
|
||||
</template>
|
||||
<template v-else-if="item.type === 'POST_REVIEWED' && item.approved">
|
||||
<NotificationContainer :item="item" :markRead="markRead">
|
||||
您发布的帖子
|
||||
<router-link
|
||||
class="notif-content-text"
|
||||
@click="markRead(item.id)"
|
||||
:to="`/posts/${item.post.id}`"
|
||||
>
|
||||
{{ stripMarkdownLength(item.post.title, 100) }}
|
||||
</router-link>
|
||||
已审核通过
|
||||
</NotificationContainer>
|
||||
</template>
|
||||
<template v-else-if="item.type === 'POST_REVIEWED' && item.approved === false">
|
||||
<NotificationContainer :item="item" :markRead="markRead">
|
||||
您发布的帖子
|
||||
<router-link
|
||||
class="notif-content-text"
|
||||
@click="markRead(item.id)"
|
||||
:to="`/posts/${item.post.id}`"
|
||||
>
|
||||
{{ stripMarkdownLength(item.post.title, 100) }}
|
||||
</router-link>
|
||||
已被管理员拒绝
|
||||
</NotificationContainer>
|
||||
</template>
|
||||
<template v-else>
|
||||
<NotificationContainer :item="item" :markRead="markRead">
|
||||
{{ formatType(item.type) }}
|
||||
</NotificationContainer>
|
||||
</template>
|
||||
</span>
|
||||
<span class="notif-time">{{ TimeManager.format(item.createdAt) }}</span>
|
||||
</div>
|
||||
</template>
|
||||
</BaseTimeline>
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -472,9 +497,13 @@ export default {
|
||||
components: { BaseTimeline, BasePlaceholder, NotificationContainer },
|
||||
setup() {
|
||||
const router = useRouter()
|
||||
const route = useRoute()
|
||||
const notifications = ref([])
|
||||
const isLoadingMessage = ref(false)
|
||||
const selectedTab = ref('unread')
|
||||
const selectedTab = ref(
|
||||
['all', 'unread', 'control'].includes(route.query.tab) ? route.query.tab : 'unread',
|
||||
)
|
||||
const notificationPrefs = ref([])
|
||||
const filteredNotifications = computed(() =>
|
||||
selectedTab.value === 'all'
|
||||
? notifications.value
|
||||
@@ -547,6 +576,7 @@ export default {
|
||||
return
|
||||
}
|
||||
isLoadingMessage.value = true
|
||||
notifications.value = []
|
||||
const res = await fetch(`${API_BASE_URL}/api/notifications`, {
|
||||
headers: {
|
||||
Authorization: `Bearer ${token}`,
|
||||
@@ -684,6 +714,21 @@ export default {
|
||||
}
|
||||
}
|
||||
|
||||
const fetchPrefs = async () => {
|
||||
notificationPrefs.value = await fetchNotificationPreferences()
|
||||
}
|
||||
|
||||
const togglePref = async (pref) => {
|
||||
const ok = await updateNotificationPreference(pref.type, !pref.enabled)
|
||||
if (ok) {
|
||||
pref.enabled = !pref.enabled
|
||||
await fetchNotifications()
|
||||
await fetchUnreadCount()
|
||||
} else {
|
||||
toast.error('操作失败')
|
||||
}
|
||||
}
|
||||
|
||||
const approve = async (id, nid) => {
|
||||
const token = getToken()
|
||||
if (!token) return
|
||||
@@ -742,12 +787,19 @@ export default {
|
||||
return '关注的用户有新动态'
|
||||
case 'MENTION':
|
||||
return '有人提到了你'
|
||||
case 'REGISTER_REQUEST':
|
||||
return '有人申请注册'
|
||||
case 'ACTIVITY_REDEEM':
|
||||
return '有人申请兑换奶茶'
|
||||
default:
|
||||
return t
|
||||
}
|
||||
}
|
||||
|
||||
onMounted(fetchNotifications)
|
||||
onMounted(() => {
|
||||
fetchNotifications()
|
||||
fetchPrefs()
|
||||
})
|
||||
|
||||
return {
|
||||
notifications,
|
||||
@@ -762,6 +814,8 @@ export default {
|
||||
filteredNotifications,
|
||||
markAllRead,
|
||||
authState,
|
||||
notificationPrefs,
|
||||
togglePref,
|
||||
}
|
||||
},
|
||||
}
|
||||
@@ -915,6 +969,38 @@ export default {
|
||||
border-bottom: 2px solid var(--primary-color);
|
||||
}
|
||||
|
||||
.message-control-title {
|
||||
font-size: 16px;
|
||||
font-weight: bold;
|
||||
margin-bottom: 15px;
|
||||
}
|
||||
|
||||
.message-control-container {
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
.message-control-push-item-container {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
flex-wrap: wrap;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.message-control-push-item {
|
||||
font-size: 14px;
|
||||
margin-bottom: 5px;
|
||||
padding: 8px 16px;
|
||||
border: 1px solid var(--normal-border-color);
|
||||
border-radius: 10px;
|
||||
cursor: pointer;
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
|
||||
.message-control-push-item.select {
|
||||
background-color: var(--primary-color);
|
||||
color: white;
|
||||
}
|
||||
|
||||
@media (max-width: 768px) {
|
||||
.has_read_button {
|
||||
display: none;
|
||||
|
||||
@@ -46,3 +46,35 @@ export async function markNotificationsRead(ids) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
export async function fetchNotificationPreferences() {
|
||||
try {
|
||||
const token = getToken()
|
||||
if (!token) return []
|
||||
const res = await fetch(`${API_BASE_URL}/api/notifications/prefs`, {
|
||||
headers: { Authorization: `Bearer ${token}` },
|
||||
})
|
||||
if (!res.ok) return []
|
||||
return await res.json()
|
||||
} catch (e) {
|
||||
return []
|
||||
}
|
||||
}
|
||||
|
||||
export async function updateNotificationPreference(type, enabled) {
|
||||
try {
|
||||
const token = getToken()
|
||||
if (!token) return false
|
||||
const res = await fetch(`${API_BASE_URL}/api/notifications/prefs`, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
Authorization: `Bearer ${token}`,
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
body: JSON.stringify({ type, enabled }),
|
||||
})
|
||||
return res.ok
|
||||
} catch (e) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user