Merge pull request 'feature-nxdev' (#49) from feature-nxdev into main

Reviewed-on: #49
This commit is contained in:
西街长安 2026-01-22 15:29:17 +08:00
commit 9d942b38c7
10 changed files with 151 additions and 12 deletions

View File

@ -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>

View 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; }
}
}

View File

@ -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>

View File

@ -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

View File

@ -9,7 +9,6 @@ export const messageHandler = (msg) => {
} else {
conversation.unreadCount += 1;
}
console.log(conversation)
conversation.dateTime = new Date().toISOString();
}

View File

@ -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}`)
}

View 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 };
}

View File

@ -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 });

View File

@ -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 },

View File

@ -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;
}
/* 修复:搜索框美化 */