mirror of
https://github.com/nagisa77/OpenIsle.git
synced 2026-05-11 05:07:28 +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.NotificationDto;
|
||||||
import com.openisle.dto.NotificationMarkReadRequest;
|
import com.openisle.dto.NotificationMarkReadRequest;
|
||||||
import com.openisle.dto.NotificationUnreadCountDto;
|
import com.openisle.dto.NotificationUnreadCountDto;
|
||||||
|
import com.openisle.dto.NotificationPreferenceDto;
|
||||||
|
import com.openisle.dto.NotificationPreferenceUpdateRequest;
|
||||||
import com.openisle.mapper.NotificationMapper;
|
import com.openisle.mapper.NotificationMapper;
|
||||||
import com.openisle.service.NotificationService;
|
import com.openisle.service.NotificationService;
|
||||||
import lombok.RequiredArgsConstructor;
|
import lombok.RequiredArgsConstructor;
|
||||||
@@ -40,4 +42,14 @@ public class NotificationController {
|
|||||||
public void markRead(@RequestBody NotificationMarkReadRequest req, Authentication auth) {
|
public void markRead(@RequestBody NotificationMarkReadRequest req, Authentication auth) {
|
||||||
notificationService.markRead(auth.getName(), req.getIds());
|
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 org.hibernate.annotations.CreationTimestamp;
|
||||||
|
|
||||||
import java.time.LocalDateTime;
|
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.
|
* Simple user entity with basic fields and a role.
|
||||||
@@ -62,6 +65,15 @@ public class User {
|
|||||||
@Enumerated(EnumType.STRING)
|
@Enumerated(EnumType.STRING)
|
||||||
private MedalType displayMedal;
|
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
|
@CreationTimestamp
|
||||||
@Column(nullable = false, updatable = false,
|
@Column(nullable = false, updatable = false,
|
||||||
columnDefinition = "DATETIME(6) DEFAULT CURRENT_TIMESTAMP(6)")
|
columnDefinition = "DATETIME(6) DEFAULT CURRENT_TIMESTAMP(6)")
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
package com.openisle.service;
|
package com.openisle.service;
|
||||||
|
|
||||||
import com.openisle.model.*;
|
import com.openisle.model.*;
|
||||||
|
import com.openisle.dto.NotificationPreferenceDto;
|
||||||
import com.openisle.repository.NotificationRepository;
|
import com.openisle.repository.NotificationRepository;
|
||||||
import com.openisle.repository.ReactionRepository;
|
import com.openisle.repository.ReactionRepository;
|
||||||
import com.openisle.repository.UserRepository;
|
import com.openisle.repository.UserRepository;
|
||||||
@@ -20,7 +21,9 @@ import java.util.Set;
|
|||||||
import java.util.HashSet;
|
import java.util.HashSet;
|
||||||
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.ArrayList;
|
||||||
import java.util.concurrent.Executor;
|
import java.util.concurrent.Executor;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
/** Service for creating and retrieving notifications. */
|
/** Service for creating and retrieving notifications. */
|
||||||
@Service
|
@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) {
|
public List<Notification> listNotifications(String username, Boolean read) {
|
||||||
User user = userRepository.findByUsername(username)
|
User user = userRepository.findByUsername(username)
|
||||||
.orElseThrow(() -> new com.openisle.exception.NotFoundException("User not found"));
|
.orElseThrow(() -> new com.openisle.exception.NotFoundException("User not found"));
|
||||||
|
Set<NotificationType> disabled = user.getDisabledNotificationTypes();
|
||||||
|
List<Notification> list;
|
||||||
if (read == null) {
|
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) {
|
public void markRead(String username, List<Long> ids) {
|
||||||
@@ -162,7 +195,9 @@ public class NotificationService {
|
|||||||
public long countUnread(String username) {
|
public long countUnread(String username) {
|
||||||
User user = userRepository.findByUsername(username)
|
User user = userRepository.findByUsername(username)
|
||||||
.orElseThrow(() -> new com.openisle.exception.NotFoundException("User not found"));
|
.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) {
|
public void notifyMentions(String content, User fromUser, Post post, Comment comment) {
|
||||||
|
|||||||
@@ -6,6 +6,7 @@
|
|||||||
text="建站送奶茶活动火热进行中,快来参与吧!"
|
text="建站送奶茶活动火热进行中,快来参与吧!"
|
||||||
@close="closeMilkTeaPopup"
|
@close="closeMilkTeaPopup"
|
||||||
/>
|
/>
|
||||||
|
<NotificationSettingPopup :visible="showNotificationPopup" @close="closeNotificationPopup" />
|
||||||
<MedalPopup :visible="showMedalPopup" :medals="newMedals" @close="closeMedalPopup" />
|
<MedalPopup :visible="showMedalPopup" :medals="newMedals" @close="closeMedalPopup" />
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
@@ -13,25 +14,30 @@
|
|||||||
<script>
|
<script>
|
||||||
import ActivityPopup from '~/components/ActivityPopup.vue'
|
import ActivityPopup from '~/components/ActivityPopup.vue'
|
||||||
import MedalPopup from '~/components/MedalPopup.vue'
|
import MedalPopup from '~/components/MedalPopup.vue'
|
||||||
|
import NotificationSettingPopup from '~/components/NotificationSettingPopup.vue'
|
||||||
import { API_BASE_URL } from '~/main'
|
import { API_BASE_URL } from '~/main'
|
||||||
import { authState } from '~/utils/auth'
|
import { authState } from '~/utils/auth'
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'GlobalPopups',
|
name: 'GlobalPopups',
|
||||||
components: { ActivityPopup, MedalPopup },
|
components: { ActivityPopup, MedalPopup, NotificationSettingPopup },
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
showMilkTeaPopup: false,
|
showMilkTeaPopup: false,
|
||||||
milkTeaIcon: '',
|
milkTeaIcon: '',
|
||||||
|
showNotificationPopup: false,
|
||||||
showMedalPopup: false,
|
showMedalPopup: false,
|
||||||
newMedals: [],
|
newMedals: [],
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
async mounted() {
|
async mounted() {
|
||||||
await this.checkMilkTeaActivity()
|
await this.checkMilkTeaActivity()
|
||||||
if (!this.showMilkTeaPopup) {
|
if (this.showMilkTeaPopup) return
|
||||||
await this.checkNewMedals()
|
|
||||||
}
|
await this.checkNotificationSetting()
|
||||||
|
if (this.showNotificationPopup) return
|
||||||
|
|
||||||
|
await this.checkNewMedals()
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
async checkMilkTeaActivity() {
|
async checkMilkTeaActivity() {
|
||||||
@@ -55,6 +61,18 @@ export default {
|
|||||||
if (!process.client) return
|
if (!process.client) return
|
||||||
localStorage.setItem('milkTeaActivityPopupShown', 'true')
|
localStorage.setItem('milkTeaActivityPopupShown', 'true')
|
||||||
this.showMilkTeaPopup = false
|
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()
|
this.checkNewMedals()
|
||||||
},
|
},
|
||||||
async 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>
|
||||||
|
<div
|
||||||
|
:class="['message-tab-item', { selected: selectedTab === 'control' }]"
|
||||||
|
@click="selectedTab = 'control'"
|
||||||
|
>
|
||||||
|
消息设置
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="message-page-header-right">
|
<div class="message-page-header-right">
|
||||||
@@ -24,32 +30,192 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div v-if="isLoadingMessage" class="loading-message">
|
<div v-if="selectedTab === 'control'">
|
||||||
<l-hatch size="28" stroke="4" speed="3.5" color="var(--primary-color)"></l-hatch>
|
<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>
|
</div>
|
||||||
|
|
||||||
<BasePlaceholder
|
<template v-else>
|
||||||
v-else-if="filteredNotifications.length === 0"
|
<div v-if="isLoadingMessage" class="loading-message">
|
||||||
text="暂时没有消息 :)"
|
<l-hatch size="28" stroke="4" speed="3.5" color="var(--primary-color)"></l-hatch>
|
||||||
icon="fas fa-inbox"
|
</div>
|
||||||
/>
|
|
||||||
|
|
||||||
<div class="timeline-container" v-if="filteredNotifications.length > 0">
|
<BasePlaceholder
|
||||||
<BaseTimeline :items="filteredNotifications">
|
v-else-if="filteredNotifications.length === 0"
|
||||||
<template #item="{ item }">
|
text="暂时没有消息 :)"
|
||||||
<div class="notif-content" :class="{ read: item.read }">
|
icon="fas fa-inbox"
|
||||||
<span v-if="!item.read" class="unread-dot"></span>
|
/>
|
||||||
<span class="notif-type">
|
|
||||||
<template v-if="item.type === 'COMMENT_REPLY' && item.parentComment">
|
<div class="timeline-container" v-if="filteredNotifications.length > 0">
|
||||||
<NotificationContainer :item="item" :markRead="markRead">
|
<BaseTimeline :items="filteredNotifications">
|
||||||
<router-link
|
<template #item="{ item }">
|
||||||
class="notif-content-text"
|
<div class="notif-content" :class="{ read: item.read }">
|
||||||
@click="markRead(item.id)"
|
<span v-if="!item.read" class="unread-dot"></span>
|
||||||
:to="`/users/${item.comment.author.id}`"
|
<span class="notif-type">
|
||||||
>{{ item.comment.author.username }}
|
<template v-if="item.type === 'COMMENT_REPLY' && item.parentComment">
|
||||||
</router-link>
|
<NotificationContainer :item="item" :markRead="markRead">
|
||||||
对我的评论
|
<router-link
|
||||||
<span>
|
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
|
<router-link
|
||||||
class="notif-content-text"
|
class="notif-content-text"
|
||||||
@click="markRead(item.id)"
|
@click="markRead(item.id)"
|
||||||
@@ -57,9 +223,7 @@
|
|||||||
>
|
>
|
||||||
{{ stripMarkdownLength(item.parentComment.content, 100) }}
|
{{ stripMarkdownLength(item.parentComment.content, 100) }}
|
||||||
</router-link>
|
</router-link>
|
||||||
</span>
|
回复了
|
||||||
回复了
|
|
||||||
<span>
|
|
||||||
<router-link
|
<router-link
|
||||||
class="notif-content-text"
|
class="notif-content-text"
|
||||||
@click="markRead(item.id)"
|
@click="markRead(item.id)"
|
||||||
@@ -67,19 +231,19 @@
|
|||||||
>
|
>
|
||||||
{{ stripMarkdownLength(item.comment.content, 100) }}
|
{{ stripMarkdownLength(item.comment.content, 100) }}
|
||||||
</router-link>
|
</router-link>
|
||||||
</span>
|
</NotificationContainer>
|
||||||
</NotificationContainer>
|
</template>
|
||||||
</template>
|
<template v-else-if="item.type === 'USER_ACTIVITY'">
|
||||||
<template v-else-if="item.type === 'COMMENT_REPLY' && !item.parentComment">
|
<NotificationContainer :item="item" :markRead="markRead">
|
||||||
<NotificationContainer :item="item" :markRead="markRead">
|
你关注的
|
||||||
<router-link
|
<router-link
|
||||||
class="notif-content-text"
|
class="notif-content-text"
|
||||||
@click="markRead(item.id)"
|
@click="markRead(item.id)"
|
||||||
:to="`/users/${item.comment.author.id}`"
|
:to="`/users/${item.comment.author.id}`"
|
||||||
>{{ item.comment.author.username }}
|
>
|
||||||
</router-link>
|
{{ item.comment.author.username }}
|
||||||
对我的文章
|
</router-link>
|
||||||
<span>
|
在文章
|
||||||
<router-link
|
<router-link
|
||||||
class="notif-content-text"
|
class="notif-content-text"
|
||||||
@click="markRead(item.id)"
|
@click="markRead(item.id)"
|
||||||
@@ -87,9 +251,7 @@
|
|||||||
>
|
>
|
||||||
{{ stripMarkdownLength(item.post.title, 100) }}
|
{{ stripMarkdownLength(item.post.title, 100) }}
|
||||||
</router-link>
|
</router-link>
|
||||||
</span>
|
下面评论了
|
||||||
回复了
|
|
||||||
<span>
|
|
||||||
<router-link
|
<router-link
|
||||||
class="notif-content-text"
|
class="notif-content-text"
|
||||||
@click="markRead(item.id)"
|
@click="markRead(item.id)"
|
||||||
@@ -97,19 +259,37 @@
|
|||||||
>
|
>
|
||||||
{{ stripMarkdownLength(item.comment.content, 100) }}
|
{{ stripMarkdownLength(item.comment.content, 100) }}
|
||||||
</router-link>
|
</router-link>
|
||||||
</span>
|
</NotificationContainer>
|
||||||
</NotificationContainer>
|
</template>
|
||||||
</template>
|
<template v-else-if="item.type === 'MENTION' && item.comment">
|
||||||
<template v-else-if="item.type === 'ACTIVITY_REDEEM' && !item.parentComment">
|
<NotificationContainer :item="item" :markRead="markRead">
|
||||||
<NotificationContainer :item="item" :markRead="markRead">
|
<router-link
|
||||||
<span class="notif-user">{{ item.fromUser.username }} </span>
|
class="notif-content-text"
|
||||||
申请进行奶茶兑换,联系方式是:{{ item.content }}
|
@click="markRead(item.id)"
|
||||||
</NotificationContainer>
|
:to="`/users/${item.fromUser.id}`"
|
||||||
</template>
|
>
|
||||||
<template v-else-if="item.type === 'REACTION' && item.post && !item.comment">
|
{{ item.fromUser.username }}
|
||||||
<NotificationContainer :item="item" :markRead="markRead">
|
</router-link>
|
||||||
<span class="notif-user">{{ item.fromUser.username }} </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>
|
||||||
|
</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
|
<router-link
|
||||||
class="notif-content-text"
|
class="notif-content-text"
|
||||||
@click="markRead(item.id)"
|
@click="markRead(item.id)"
|
||||||
@@ -117,339 +297,184 @@
|
|||||||
>
|
>
|
||||||
{{ stripMarkdownLength(item.post.title, 100) }}
|
{{ stripMarkdownLength(item.post.title, 100) }}
|
||||||
</router-link>
|
</router-link>
|
||||||
</span>
|
中提到了你
|
||||||
进行了表态
|
</NotificationContainer>
|
||||||
</NotificationContainer>
|
</template>
|
||||||
</template>
|
<template v-else-if="item.type === 'USER_FOLLOWED'">
|
||||||
<template v-else-if="item.type === 'REACTION' && item.comment">
|
<NotificationContainer :item="item" :markRead="markRead">
|
||||||
<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
|
<router-link
|
||||||
class="notif-content-text"
|
class="notif-content-text"
|
||||||
@click="markRead(item.id)"
|
@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>
|
</router-link>
|
||||||
</span>
|
开始关注你了
|
||||||
进行了表态
|
</NotificationContainer>
|
||||||
</NotificationContainer>
|
</template>
|
||||||
</template>
|
<template v-else-if="item.type === 'USER_UNFOLLOWED'">
|
||||||
<template v-else-if="item.type === 'POST_VIEWED'">
|
<NotificationContainer :item="item" :markRead="markRead">
|
||||||
<NotificationContainer :item="item" :markRead="markRead">
|
<router-link
|
||||||
<router-link
|
class="notif-content-text"
|
||||||
class="notif-content-text"
|
@click="markRead(item.id)"
|
||||||
@click="markRead(item.id)"
|
:to="`/users/${item.fromUser.id}`"
|
||||||
:to="`/users/${item.fromUser.id}`"
|
>
|
||||||
>
|
{{ item.fromUser.username }}
|
||||||
{{ item.fromUser.username }}
|
</router-link>
|
||||||
</router-link>
|
取消关注你了
|
||||||
查看了您的帖子
|
</NotificationContainer>
|
||||||
<router-link
|
</template>
|
||||||
class="notif-content-text"
|
<template v-else-if="item.type === 'FOLLOWED_POST'">
|
||||||
@click="markRead(item.id)"
|
<NotificationContainer :item="item" :markRead="markRead">
|
||||||
:to="`/posts/${item.post.id}`"
|
你关注的
|
||||||
>
|
<router-link
|
||||||
{{ stripMarkdownLength(item.post.title, 100) }}
|
class="notif-content-text"
|
||||||
</router-link>
|
@click="markRead(item.id)"
|
||||||
</NotificationContainer>
|
:to="`/users/${item.fromUser.id}`"
|
||||||
</template>
|
>
|
||||||
<template v-else-if="item.type === 'POST_UPDATED'">
|
{{ item.fromUser.username }}
|
||||||
<NotificationContainer :item="item" :markRead="markRead">
|
</router-link>
|
||||||
您关注的帖子
|
发布了文章
|
||||||
<router-link
|
<router-link
|
||||||
class="notif-content-text"
|
class="notif-content-text"
|
||||||
@click="markRead(item.id)"
|
@click="markRead(item.id)"
|
||||||
:to="`/posts/${item.post.id}`"
|
:to="`/posts/${item.post.id}`"
|
||||||
>
|
>
|
||||||
{{ stripMarkdownLength(item.post.title, 100) }}
|
{{ stripMarkdownLength(item.post.title, 100) }}
|
||||||
</router-link>
|
</router-link>
|
||||||
下面有新评论
|
</NotificationContainer>
|
||||||
<router-link
|
</template>
|
||||||
class="notif-content-text"
|
<template v-else-if="item.type === 'POST_SUBSCRIBED'">
|
||||||
@click="markRead(item.id)"
|
<NotificationContainer :item="item" :markRead="markRead">
|
||||||
:to="`/posts/${item.post.id}#comment-${item.comment.id}`"
|
<router-link
|
||||||
>
|
class="notif-content-text"
|
||||||
{{ stripMarkdownLength(item.comment.content, 100) }}
|
@click="markRead(item.id)"
|
||||||
</router-link>
|
:to="`/users/${item.fromUser.id}`"
|
||||||
</NotificationContainer>
|
>
|
||||||
</template>
|
{{ item.fromUser.username }}
|
||||||
<template v-else-if="item.type === 'USER_ACTIVITY' && item.parentComment">
|
</router-link>
|
||||||
<NotificationContainer :item="item" :markRead="markRead">
|
订阅了你的文章
|
||||||
你关注的
|
<router-link
|
||||||
<router-link
|
class="notif-content-text"
|
||||||
class="notif-content-text"
|
@click="markRead(item.id)"
|
||||||
@click="markRead(item.id)"
|
:to="`/posts/${item.post.id}`"
|
||||||
:to="`/users/${item.comment.author.id}`"
|
>
|
||||||
>
|
{{ stripMarkdownLength(item.post.title, 100) }}
|
||||||
{{ item.comment.author.username }}
|
</router-link>
|
||||||
</router-link>
|
</NotificationContainer>
|
||||||
在 对评论
|
</template>
|
||||||
<router-link
|
<template v-else-if="item.type === 'POST_UNSUBSCRIBED'">
|
||||||
class="notif-content-text"
|
<NotificationContainer :item="item" :markRead="markRead">
|
||||||
@click="markRead(item.id)"
|
<router-link
|
||||||
:to="`/posts/${item.post.id}#comment-${item.parentComment.id}`"
|
class="notif-content-text"
|
||||||
>
|
@click="markRead(item.id)"
|
||||||
{{ stripMarkdownLength(item.parentComment.content, 100) }}
|
:to="`/users/${item.fromUser.id}`"
|
||||||
</router-link>
|
>
|
||||||
回复了
|
{{ item.fromUser.username }}
|
||||||
<router-link
|
</router-link>
|
||||||
class="notif-content-text"
|
取消订阅了你的文章
|
||||||
@click="markRead(item.id)"
|
<router-link
|
||||||
:to="`/posts/${item.post.id}#comment-${item.comment.id}`"
|
class="notif-content-text"
|
||||||
>
|
@click="markRead(item.id)"
|
||||||
{{ stripMarkdownLength(item.comment.content, 100) }}
|
:to="`/posts/${item.post.id}`"
|
||||||
</router-link>
|
>
|
||||||
</NotificationContainer>
|
{{ stripMarkdownLength(item.post.title, 100) }}
|
||||||
</template>
|
</router-link>
|
||||||
<template v-else-if="item.type === 'USER_ACTIVITY'">
|
</NotificationContainer>
|
||||||
<NotificationContainer :item="item" :markRead="markRead">
|
</template>
|
||||||
你关注的
|
<template v-else-if="item.type === 'POST_REVIEW_REQUEST' && item.fromUser">
|
||||||
<router-link
|
<NotificationContainer :item="item" :markRead="markRead">
|
||||||
class="notif-content-text"
|
<router-link
|
||||||
@click="markRead(item.id)"
|
class="notif-content-text"
|
||||||
:to="`/users/${item.comment.author.id}`"
|
@click="markRead(item.id)"
|
||||||
>
|
:to="`/users/${item.fromUser.id}`"
|
||||||
{{ item.comment.author.username }}
|
>
|
||||||
</router-link>
|
{{ item.fromUser.username }}
|
||||||
在文章
|
</router-link>
|
||||||
<router-link
|
发布了帖子
|
||||||
class="notif-content-text"
|
<router-link
|
||||||
@click="markRead(item.id)"
|
class="notif-content-text"
|
||||||
:to="`/posts/${item.post.id}`"
|
@click="markRead(item.id)"
|
||||||
>
|
:to="`/posts/${item.post.id}`"
|
||||||
{{ stripMarkdownLength(item.post.title, 100) }}
|
>
|
||||||
</router-link>
|
{{ stripMarkdownLength(item.post.title, 100) }}
|
||||||
下面评论了
|
</router-link>
|
||||||
<router-link
|
,请审核
|
||||||
class="notif-content-text"
|
</NotificationContainer>
|
||||||
@click="markRead(item.id)"
|
</template>
|
||||||
:to="`/posts/${item.post.id}#comment-${item.comment.id}`"
|
<template v-else-if="item.type === 'POST_REVIEW_REQUEST'">
|
||||||
>
|
<NotificationContainer :item="item" :markRead="markRead">
|
||||||
{{ stripMarkdownLength(item.comment.content, 100) }}
|
您发布的帖子
|
||||||
</router-link>
|
<router-link
|
||||||
</NotificationContainer>
|
class="notif-content-text"
|
||||||
</template>
|
@click="markRead(item.id)"
|
||||||
<template v-else-if="item.type === 'MENTION' && item.comment">
|
:to="`/posts/${item.post.id}`"
|
||||||
<NotificationContainer :item="item" :markRead="markRead">
|
>
|
||||||
<router-link
|
{{ stripMarkdownLength(item.post.title, 100) }}
|
||||||
class="notif-content-text"
|
</router-link>
|
||||||
@click="markRead(item.id)"
|
已提交审核
|
||||||
:to="`/users/${item.fromUser.id}`"
|
</NotificationContainer>
|
||||||
>
|
</template>
|
||||||
{{ item.fromUser.username }}
|
<template v-else-if="item.type === 'REGISTER_REQUEST'">
|
||||||
</router-link>
|
<NotificationContainer :item="item" :markRead="markRead">
|
||||||
在评论中提到了你:
|
{{ item.fromUser.username }} 希望注册为会员,理由是:{{ item.content }}
|
||||||
<router-link
|
<template #actions v-if="authState.role === 'ADMIN'">
|
||||||
class="notif-content-text"
|
<div v-if="!item.read" class="optional-buttons">
|
||||||
@click="markRead(item.id)"
|
<div
|
||||||
:to="`/posts/${item.post.id}#comment-${item.comment.id}`"
|
class="mark-approve-button-item"
|
||||||
>
|
@click="approve(item.fromUser.id, item.id)"
|
||||||
{{ stripMarkdownLength(item.comment.content, 100) }}
|
>
|
||||||
</router-link>
|
同意
|
||||||
</NotificationContainer>
|
</div>
|
||||||
</template>
|
<div
|
||||||
<template v-else-if="item.type === 'MENTION'">
|
class="mark-reject-button-item"
|
||||||
<NotificationContainer :item="item" :markRead="markRead">
|
@click="reject(item.fromUser.id, item.id)"
|
||||||
<router-link
|
>
|
||||||
class="notif-content-text"
|
拒绝
|
||||||
@click="markRead(item.id)"
|
</div>
|
||||||
: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)"
|
|
||||||
>
|
|
||||||
同意
|
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div v-else class="has_read_button" @click="markRead(item.id)">已读</div>
|
||||||
class="mark-reject-button-item"
|
</template>
|
||||||
@click="reject(item.fromUser.id, item.id)"
|
</NotificationContainer>
|
||||||
>
|
</template>
|
||||||
拒绝
|
<template v-else-if="item.type === 'POST_REVIEWED' && item.approved">
|
||||||
</div>
|
<NotificationContainer :item="item" :markRead="markRead">
|
||||||
</div>
|
您发布的帖子
|
||||||
<div v-else class="has_read_button" @click="markRead(item.id)">已读</div>
|
<router-link
|
||||||
</template>
|
class="notif-content-text"
|
||||||
</NotificationContainer>
|
@click="markRead(item.id)"
|
||||||
</template>
|
:to="`/posts/${item.post.id}`"
|
||||||
<template v-else-if="item.type === 'POST_REVIEWED' && item.approved">
|
>
|
||||||
<NotificationContainer :item="item" :markRead="markRead">
|
{{ stripMarkdownLength(item.post.title, 100) }}
|
||||||
您发布的帖子
|
</router-link>
|
||||||
<router-link
|
已审核通过
|
||||||
class="notif-content-text"
|
</NotificationContainer>
|
||||||
@click="markRead(item.id)"
|
</template>
|
||||||
:to="`/posts/${item.post.id}`"
|
<template v-else-if="item.type === 'POST_REVIEWED' && item.approved === false">
|
||||||
>
|
<NotificationContainer :item="item" :markRead="markRead">
|
||||||
{{ stripMarkdownLength(item.post.title, 100) }}
|
您发布的帖子
|
||||||
</router-link>
|
<router-link
|
||||||
已审核通过
|
class="notif-content-text"
|
||||||
</NotificationContainer>
|
@click="markRead(item.id)"
|
||||||
</template>
|
:to="`/posts/${item.post.id}`"
|
||||||
<template v-else-if="item.type === 'POST_REVIEWED' && item.approved === false">
|
>
|
||||||
<NotificationContainer :item="item" :markRead="markRead">
|
{{ stripMarkdownLength(item.post.title, 100) }}
|
||||||
您发布的帖子
|
</router-link>
|
||||||
<router-link
|
已被管理员拒绝
|
||||||
class="notif-content-text"
|
</NotificationContainer>
|
||||||
@click="markRead(item.id)"
|
</template>
|
||||||
:to="`/posts/${item.post.id}`"
|
<template v-else>
|
||||||
>
|
<NotificationContainer :item="item" :markRead="markRead">
|
||||||
{{ stripMarkdownLength(item.post.title, 100) }}
|
{{ formatType(item.type) }}
|
||||||
</router-link>
|
</NotificationContainer>
|
||||||
已被管理员拒绝
|
</template>
|
||||||
</NotificationContainer>
|
</span>
|
||||||
</template>
|
<span class="notif-time">{{ TimeManager.format(item.createdAt) }}</span>
|
||||||
<template v-else>
|
</div>
|
||||||
<NotificationContainer :item="item" :markRead="markRead">
|
</template>
|
||||||
{{ formatType(item.type) }}
|
</BaseTimeline>
|
||||||
</NotificationContainer>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
</span>
|
|
||||||
<span class="notif-time">{{ TimeManager.format(item.createdAt) }}</span>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
</BaseTimeline>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
@@ -472,9 +497,13 @@ export default {
|
|||||||
components: { BaseTimeline, BasePlaceholder, NotificationContainer },
|
components: { BaseTimeline, BasePlaceholder, NotificationContainer },
|
||||||
setup() {
|
setup() {
|
||||||
const router = useRouter()
|
const router = useRouter()
|
||||||
|
const route = useRoute()
|
||||||
const notifications = ref([])
|
const notifications = ref([])
|
||||||
const isLoadingMessage = ref(false)
|
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(() =>
|
const filteredNotifications = computed(() =>
|
||||||
selectedTab.value === 'all'
|
selectedTab.value === 'all'
|
||||||
? notifications.value
|
? notifications.value
|
||||||
@@ -547,6 +576,7 @@ export default {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
isLoadingMessage.value = true
|
isLoadingMessage.value = true
|
||||||
|
notifications.value = []
|
||||||
const res = await fetch(`${API_BASE_URL}/api/notifications`, {
|
const res = await fetch(`${API_BASE_URL}/api/notifications`, {
|
||||||
headers: {
|
headers: {
|
||||||
Authorization: `Bearer ${token}`,
|
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 approve = async (id, nid) => {
|
||||||
const token = getToken()
|
const token = getToken()
|
||||||
if (!token) return
|
if (!token) return
|
||||||
@@ -742,12 +787,19 @@ export default {
|
|||||||
return '关注的用户有新动态'
|
return '关注的用户有新动态'
|
||||||
case 'MENTION':
|
case 'MENTION':
|
||||||
return '有人提到了你'
|
return '有人提到了你'
|
||||||
|
case 'REGISTER_REQUEST':
|
||||||
|
return '有人申请注册'
|
||||||
|
case 'ACTIVITY_REDEEM':
|
||||||
|
return '有人申请兑换奶茶'
|
||||||
default:
|
default:
|
||||||
return t
|
return t
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
onMounted(fetchNotifications)
|
onMounted(() => {
|
||||||
|
fetchNotifications()
|
||||||
|
fetchPrefs()
|
||||||
|
})
|
||||||
|
|
||||||
return {
|
return {
|
||||||
notifications,
|
notifications,
|
||||||
@@ -762,6 +814,8 @@ export default {
|
|||||||
filteredNotifications,
|
filteredNotifications,
|
||||||
markAllRead,
|
markAllRead,
|
||||||
authState,
|
authState,
|
||||||
|
notificationPrefs,
|
||||||
|
togglePref,
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
@@ -915,6 +969,38 @@ export default {
|
|||||||
border-bottom: 2px solid var(--primary-color);
|
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) {
|
@media (max-width: 768px) {
|
||||||
.has_read_button {
|
.has_read_button {
|
||||||
display: none;
|
display: none;
|
||||||
|
|||||||
@@ -46,3 +46,35 @@ export async function markNotificationsRead(ids) {
|
|||||||
return false
|
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