feature-nxdev #49
@ -49,7 +49,7 @@ namespace IM_API.Controllers
|
||||
var userIdStr = User.FindFirstValue(ClaimTypes.NameIdentifier);
|
||||
int userId = int.Parse(userIdStr);
|
||||
var list = await _friendService.GetFriendRequestListAsync(userId,page,limit,desc);
|
||||
var res = new BaseResponse<List<FriendRequest>>(list);
|
||||
var res = new BaseResponse<List<FriendRequestResDto>>(list);
|
||||
return Ok(res);
|
||||
}
|
||||
/// <summary>
|
||||
|
||||
36
backend/IM_API/Dtos/FriendRequestResDto.cs
Normal file
36
backend/IM_API/Dtos/FriendRequestResDto.cs
Normal file
@ -0,0 +1,36 @@
|
||||
using IM_API.Models;
|
||||
|
||||
namespace IM_API.Dtos
|
||||
{
|
||||
public class FriendRequestResDto
|
||||
{
|
||||
public int Id { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 申请人
|
||||
/// </summary>
|
||||
public int RequestUser { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 被申请人
|
||||
/// </summary>
|
||||
public int ResponseUser { get; set; }
|
||||
public string Avatar { get; set; }
|
||||
public string NickName { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 申请时间
|
||||
/// </summary>
|
||||
public DateTime Created { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 申请附言
|
||||
/// </summary>
|
||||
public string? Description { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 申请状态(0:待通过,1:拒绝,2:同意,3:拉黑)
|
||||
/// </summary>
|
||||
public FriendRequestState State { get; set; }
|
||||
}
|
||||
}
|
||||
@ -27,7 +27,7 @@ namespace IM_API.Interface.Services
|
||||
/// <param name="page"></param>
|
||||
/// <param name="limit"></param>
|
||||
/// <returns></returns>
|
||||
Task<List<FriendRequest>> GetFriendRequestListAsync(int userId,int page,int limit, bool desc);
|
||||
Task<List<FriendRequestResDto>> GetFriendRequestListAsync(int userId,int page,int limit, bool desc);
|
||||
/// <summary>
|
||||
/// 处理好友请求
|
||||
/// </summary>
|
||||
|
||||
@ -73,15 +73,29 @@ namespace IM_API.Services
|
||||
}
|
||||
#endregion
|
||||
#region 获取好友请求列表
|
||||
public async Task<List<FriendRequest>> GetFriendRequestListAsync(int userId, int page, int limit, bool desc)
|
||||
public async Task<List<FriendRequestResDto>> GetFriendRequestListAsync(int userId, int page, int limit, bool desc)
|
||||
{
|
||||
var query = _context.FriendRequests
|
||||
.Include(x => x.ResponseUserNavigation)
|
||||
.Include(x => x.RequestUserNavigation)
|
||||
.Where(
|
||||
x => (x.ResponseUser == userId) ||
|
||||
x.RequestUser == userId
|
||||
);
|
||||
)
|
||||
.Select(s => new FriendRequestResDto
|
||||
{
|
||||
Id = s.Id,
|
||||
RequestUser = s.RequestUser,
|
||||
ResponseUser = s.ResponseUser,
|
||||
Avatar = s.RequestUser == userId ? s.ResponseUserNavigation.Avatar : s.RequestUserNavigation.Avatar,
|
||||
Created = s.Created,
|
||||
NickName = s.RequestUser == userId ? s.ResponseUserNavigation.NickName : s.RequestUserNavigation.NickName,
|
||||
Description = s.Description,
|
||||
State = (FriendRequestState)s.State
|
||||
})
|
||||
;
|
||||
query = query.OrderByDescending(x => x.Id);
|
||||
var friendRequestList = await query.Skip((page - 1 * limit)).Take(limit).ToListAsync();
|
||||
var friendRequestList = await query.Skip(((page - 1) * limit)).Take(limit).ToListAsync();
|
||||
return friendRequestList;
|
||||
}
|
||||
#endregion
|
||||
|
||||
@ -9,7 +9,6 @@ export const messageHandler = (msg) => {
|
||||
} else {
|
||||
conversation.unreadCount += 1;
|
||||
}
|
||||
console.log(conversation)
|
||||
conversation.dateTime = new Date().toISOString();
|
||||
|
||||
}
|
||||
@ -28,5 +28,5 @@ export const friendService = {
|
||||
* @param {*} limit
|
||||
* @returns
|
||||
*/
|
||||
getFriendRequests: (page = 1, limit = 100) => request.get(`/friend/request?page=${page}&limit=${limit}`)
|
||||
getFriendRequests: (page = 1, limit = 100) => request.get(`/friend/requests?page=${page}&limit=${limit}`)
|
||||
}
|
||||
21
frontend/web/src/services/useBrowserNotification.js
Normal file
21
frontend/web/src/services/useBrowserNotification.js
Normal file
@ -0,0 +1,21 @@
|
||||
export function useBrowserNotification() {
|
||||
const requestPermission = async () => {
|
||||
if ("Notification" in window && Notification.permission === "default") {
|
||||
await Notification.requestPermission();
|
||||
}
|
||||
};
|
||||
|
||||
const send = (title, options = {}) => {
|
||||
if ("Notification" in window && Notification.permission === "granted") {
|
||||
// 如果页面正处于激活状态,通常不需要弹窗提醒,以免干扰用户
|
||||
/*
|
||||
if (document.visibilityState === 'visible' && document.hasFocus()) {
|
||||
return;
|
||||
}
|
||||
*/
|
||||
return new Notification(title, options);
|
||||
}
|
||||
};
|
||||
|
||||
return { requestPermission, send };
|
||||
}
|
||||
@ -6,6 +6,8 @@ import { useChatStore } from "./chat";
|
||||
import { authService } from "@/services/auth";
|
||||
import { generateSessionId } from "@/utils/sessionIdTools";
|
||||
import { messageHandler } from "@/handler/messageHandler";
|
||||
import { useBrowserNotification } from "@/services/useBrowserNotification";
|
||||
import { useConversationStore } from "./conversation";
|
||||
|
||||
export const useSignalRStore = defineStore('signalr', {
|
||||
state: () => ({
|
||||
@ -40,11 +42,17 @@ export const useSignalRStore = defineStore('signalr', {
|
||||
},
|
||||
registerHandlers() {
|
||||
const chatStore = useChatStore()
|
||||
const browserNotification = useBrowserNotification();
|
||||
|
||||
this.connection.on('ReceiveMessage', (msg) => {
|
||||
const sessionId = generateSessionId(msg.senderId, msg.receiverId);
|
||||
messageHandler(msg);
|
||||
chatStore.addMessage(msg, sessionId);
|
||||
const conversation = useConversationStore().conversations.find(x => x.targetId == msg.senderId);
|
||||
browserNotification.send(`${conversation.targetName}发来一条消息`, {
|
||||
body: msg.content,
|
||||
icon: conversation.targetAvatar
|
||||
});
|
||||
});
|
||||
|
||||
this.connection.onclose(() => { this.isConnected = false });
|
||||
|
||||
@ -10,19 +10,31 @@
|
||||
<div class="info">
|
||||
<div class="title-row">
|
||||
<span class="name">{{ item.nickName }}</span>
|
||||
<span class="date">{{ item.time }}</span>
|
||||
<span class="date">{{ formatDate(item.created) }}</span>
|
||||
</div>
|
||||
<p class="sub-text">{{ item.desc }}</p>
|
||||
<p class="sub-text">{{ item.description }}</p>
|
||||
<p v-if="item.remark" class="remark-text">{{ item.remark }}</p>
|
||||
</div>
|
||||
|
||||
<div class="actions">
|
||||
<template v-if="item.status === 0">
|
||||
<template v-if="item.state === 0 && item.requestUser != authStore.userInfo.id">
|
||||
<button class="btn-text btn-reject" @click="item.status = 2">拒绝</button>
|
||||
<button class="btn-text btn-accept" @click="item.status = 1">接受</button>
|
||||
</template>
|
||||
<span v-else-if="item.state === 0" class="status-label">
|
||||
待对方同意
|
||||
</span>
|
||||
<span v-else-if="item.state === 1" class="status-label">
|
||||
{{item.requestUser != authStore.userInfo.id ? '已拒绝' : '对方拒绝'}}
|
||||
</span>
|
||||
<span v-else-if="item.state === 2" class="status-label">
|
||||
已添加
|
||||
</span>
|
||||
<span v-else-if="item.state === 3 && item.requestUser != authStore.userInfo.id" class="status-label">
|
||||
已拉黑
|
||||
</span>
|
||||
<span v-else class="status-label">
|
||||
{{ item.status === 1 ? '已添加' : '已忽略' }}
|
||||
已忽略
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
@ -32,7 +44,29 @@
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref } from 'vue';
|
||||
import { onMounted, ref } from 'vue';
|
||||
import { friendService } from '@/services/friend';
|
||||
import { useMessage } from '@/components/messages/useAlert';
|
||||
import { formatDate } from '@/utils/formatDate';
|
||||
import { useAuthStore } from '@/stores/auth';
|
||||
|
||||
const message = useMessage();
|
||||
const authStore = useAuthStore();
|
||||
|
||||
const requests = ref([]);
|
||||
|
||||
const loadFriendRequests = async () => {
|
||||
const res = await friendService.getFriendRequests();
|
||||
if(res.code != 0){
|
||||
message.error(res.message);
|
||||
return;
|
||||
}
|
||||
requests.value = res.data;
|
||||
}
|
||||
|
||||
onMounted(async () => {
|
||||
await loadFriendRequests();
|
||||
})
|
||||
|
||||
const requests = ref([
|
||||
{ id: 1, nickName: '猫之使者', avatar: 'https://api.dicebear.com/7.x/avataaars/svg?seed=Felix', desc: '你好,我是你在桌游局认识的朋友。', remark: '来自桌面游戏组', time: '14:20', status: 0 },
|
||||
|
||||
@ -10,6 +10,10 @@
|
||||
<AddMenu :menu-list="addMenuList" @action-active="actionHandler"/>
|
||||
</div>
|
||||
</div>
|
||||
<div v-if="msgTitleShow" class="showMsg" @click="requestNotificationPermission">
|
||||
<i style="color: red;line-height:0;" v-html="feather.icons['alert-circle'].toSvg({width:14})"></i>
|
||||
<span>新消息无法通知,点我授予通知权限</span>
|
||||
</div>
|
||||
|
||||
<div class="scroll-area">
|
||||
<div v-for="s in filteredSessions" :key="s.id"
|
||||
@ -44,14 +48,17 @@ import { useConversationStore } from '@/stores/conversation'
|
||||
import AddMenu from '@/components/addMenu.vue'
|
||||
import feather from 'feather-icons'
|
||||
import SearchUser from '@/components/user/SearchUser.vue'
|
||||
import { useBrowserNotification } from '@/services/useBrowserNotification'
|
||||
|
||||
const conversationStore = useConversationStore();
|
||||
const router = useRouter();
|
||||
const browserNotification = useBrowserNotification();
|
||||
|
||||
const searchQuery = ref('')
|
||||
const activeId = ref(1)
|
||||
const conversations = ref([]);
|
||||
const searchUserModal = ref(false);
|
||||
const msgTitleShow = ref(false);
|
||||
const addMenuList = [
|
||||
{
|
||||
text: '发起群聊',
|
||||
@ -90,6 +97,11 @@ function actionHandler(type){
|
||||
}
|
||||
}
|
||||
|
||||
async function requestNotificationPermission(){
|
||||
await browserNotification.requestPermission();
|
||||
if(Notification.permission === "granted") msgTitleShow.value = false;
|
||||
}
|
||||
|
||||
async function loadConversation() {
|
||||
const res = await messageService.getConversations();
|
||||
conversations.value = res.data;
|
||||
@ -97,6 +109,7 @@ async function loadConversation() {
|
||||
|
||||
onMounted(async () => {
|
||||
await conversationStore.loadUserConversations();
|
||||
if(Notification.permission != "granted") msgTitleShow.value = true;
|
||||
})
|
||||
|
||||
|
||||
@ -118,6 +131,20 @@ onMounted(async () => {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
.showMsg {
|
||||
/* width: 10px; */
|
||||
height: 20px;
|
||||
background: #e3f98d;
|
||||
font-size: 12px;
|
||||
display: flex;
|
||||
/* text-align: center; */
|
||||
flex-wrap: nowrap;
|
||||
align-content: center;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
color: red;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
|
||||
/* 修复:搜索框美化 */
|
||||
|
||||
Loading…
Reference in New Issue
Block a user