Merge pull request '前端:' (#43) from feature-nxdev into main

Reviewed-on: #43
This commit is contained in:
西街长安 2026-01-18 22:38:39 +08:00
commit ac54af3ff8
32 changed files with 294 additions and 837 deletions

View File

@ -14,7 +14,7 @@ using System.Reflection;
[assembly: System.Reflection.AssemblyCompanyAttribute("IMTest")]
[assembly: System.Reflection.AssemblyConfigurationAttribute("Debug")]
[assembly: System.Reflection.AssemblyFileVersionAttribute("1.0.0.0")]
[assembly: System.Reflection.AssemblyInformationalVersionAttribute("1.0.0+5274f0d22d4fe646d03a3bc0ea6621d299074816")]
[assembly: System.Reflection.AssemblyInformationalVersionAttribute("1.0.0+d855c8f8fb8edc48b6c55f08ee2ebf74415cc5ea")]
[assembly: System.Reflection.AssemblyProductAttribute("IMTest")]
[assembly: System.Reflection.AssemblyTitleAttribute("IMTest")]
[assembly: System.Reflection.AssemblyVersionAttribute("1.0.0.0")]

View File

@ -1 +1 @@
7390b702e3c578dad3a8fa4fa4cc93b25ccd34a9b353beca60372a7182717d73
0e2c3a5367325662a3e7db912bec1cd772d661d101def6cb95abbceb77be09ff

View File

@ -54,6 +54,7 @@ namespace IM_API.Configs
.ForMember(dest => dest.ReceiverId, opt => opt.MapFrom(src => src.Recipient))
.ForMember(dest => dest.Content, opt => opt.MapFrom(src => src.Content))
.ForMember(dest => dest.TimeStamp, opt => opt.MapFrom(src => src.Created))
.ForMember(dest => dest.GroupMemberId , opt => opt.MapFrom(src => src.GroupMemberId))
;
CreateMap<MessageBaseDto, Message>()
.ForMember(dest => dest.Sender, opt => opt.MapFrom(src => src.SenderId))
@ -64,6 +65,7 @@ namespace IM_API.Configs
.ForMember(dest => dest.Recipient, opt => opt.MapFrom(src => src.ReceiverId))
.ForMember(dest => dest.StreamKey, opt => opt.Ignore() )
.ForMember(dest => dest.StateEnum, opt => opt.MapFrom(src => MessageState.Sent))
.ForMember(dest => dest.GroupMemberId, opt => opt.MapFrom(src => src.GroupMemberId))
.ForMember(dest => dest.ChatType, opt => opt.Ignore())
.ForMember(dest => dest.MsgType, opt => opt.Ignore())
;
@ -102,6 +104,27 @@ namespace IM_API.Configs
.ForMember(dest => dest.LastMessageTime, opt => opt.MapFrom(src => DateTime.Now))
;
//创建会话对象
CreateMap<Conversation, ConversationDto>()
.ForMember(dest => dest.Id, opt => opt.MapFrom(src => src.Id))
.ForMember(dest => dest.LastMessage, opt => opt.MapFrom(src => src.LastMessage))
.ForMember(dest => dest.LastReadMessage, opt => opt.MapFrom(src => src.LastReadMessage))
.ForMember(dest => dest.LastReadMessageId, opt => opt.MapFrom(src => src.LastReadMessageId))
.ForMember(dest => dest.ChatType, opt => opt.MapFrom(src => src.ChatType))
.ForMember(dest => dest.DateTime, opt => opt.MapFrom(src => src.LastMessageTime))
.ForMember(dest => dest.TargetId, opt => opt.MapFrom(src => src.TargetId))
.ForMember(dest => dest.UnreadCount, opt => opt.MapFrom(src => src.UnreadCount))
.ForMember(dest => dest.UserId, opt => opt.MapFrom(src => src.UserId));
CreateMap<Friend, ConversationDto>()
.ForMember(dest => dest.TargetAvatar, opt => opt.MapFrom(src => src.Avatar))
.ForMember(dest => dest.TargetName, opt => opt.MapFrom(src => src.RemarkName));
CreateMap<Group, ConversationDto>()
.ForMember(dest => dest.TargetAvatar, opt => opt.MapFrom(src => src.Avatar))
.ForMember(dest => dest.TargetName, opt => opt.MapFrom(src => src.Name));
}
}
}

View File

@ -28,6 +28,14 @@ namespace IM_API.Controllers
var res = new BaseResponse<List<ConversationDto>>(list);
return Ok(res);
}
[HttpGet]
public async Task<IActionResult> Get([FromQuery]int conversationId)
{
var userIdStr = User.FindFirstValue(ClaimTypes.NameIdentifier);
var conversation = await _conversationSerivice.GetConversationByIdAsync(int.Parse(userIdStr), conversationId);
var res = new BaseResponse<ConversationDto>(conversation);
return Ok(res);
}
[HttpPost]
public async Task<IActionResult> Clear()
{

View File

@ -3,6 +3,7 @@ using IM_API.Interface.Services;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using System.ComponentModel.DataAnnotations;
using System.Security.Claims;
namespace IM_API.Controllers
@ -33,5 +34,13 @@ namespace IM_API.Controllers
await _messageService.SendGroupMessageAsync(int.Parse(userIdstr), dto.ReceiverId, dto);
return Ok(new BaseResponse<object?>());
}
[HttpGet]
public async Task<IActionResult> GetMessageList([Required]int conversationId, int? msgId, int? pageSize)
{
var userIdStr = User.FindFirstValue(ClaimTypes.NameIdentifier);
var msgList = await _messageService.GetMessagesAsync(int.Parse(userIdStr), conversationId, msgId, pageSize, false);
var res = new BaseResponse<List<MessageBaseDto>>(msgList);
return Ok(res);
}
}
}

View File

@ -46,6 +46,6 @@ namespace IM_API.Dtos
/// 对方头像
/// </summary>
public string? TargetAvatar { get; set; }
public virtual Message? LastReadMessage { get; set; }
public MessageBaseDto? LastReadMessage { get; set; }
}
}

View File

@ -1,5 +1,16 @@
namespace IM_API.Dtos
{
public record MessageBaseDto(
string Type,string ChatType, string? MsgId,int SenderId,int ReceiverId,string Content,DateTime TimeStamp);
public record MessageBaseDto
{
// 使用 { get; init; } 确保对象创建后不可修改,且支持无参构造
public string Type { get; init; } = default!;
public string ChatType { get; init; } = default!;
public string? MsgId { get; init; }
public int SenderId { get; init; }
public int ReceiverId { get; init; }
public int? GroupMemberId { get; init; }
public string Content { get; init; } = default!;
public DateTime TimeStamp { get; init; }
public MessageBaseDto() { }
}
}

View File

@ -29,5 +29,11 @@ namespace IM_API.Interface.Services
/// <param name="userId"></param>
/// <returns></returns>
Task<List<string>> GetUserAllStreamKeyAsync(int userId);
/// <summary>
/// 获取单个conversation信息
/// </summary>
/// <param name="conversationId"></param>
/// <returns></returns>
Task<ConversationDto> GetConversationByIdAsync(int userId, int conversationId);
}
}

View File

@ -21,24 +21,14 @@ namespace IM_API.Interface.Services
/// <returns></returns>
Task<bool> SendGroupMessageAsync(int senderId,int groupId,MessageBaseDto dto);
/// <summary>
/// 获取私聊消息列表
/// 获取消息列表
/// </summary>
/// <param name="userAId"></param>
/// <param name="userBId"></param>
/// <param name="page"></param>
/// <param name="pageSize"></param>
/// <param name="conversationId">会话id用于获取指定用户间聊天消息</param>
/// <param name="msgId">消息id</param>
/// <param name="pageSize">获取消息数量</param>
/// <param name="desc"></param>
/// <returns></returns>
Task<List<MessageBaseDto>> GetPrivateMessagesAsync(int userAId,int userBId,int page,int pageSize,bool desc);
/// <summary>
/// 获取群聊消息列表
/// </summary>
/// <param name="groupId"></param>
/// <param name="page"></param>
/// <param name="pageSize"></param>
/// <param name="desc"></param>
/// <returns></returns>
Task<List<MessageBaseDto>> GetGroupMessagesAsync(int groupId, int page, int pageSize, bool desc);
Task<List<MessageBaseDto>> GetMessagesAsync(int userId, int conversationId,int? msgId,int? pageSize,bool desc);
/// <summary>
/// 获取未读消息数
/// </summary>

View File

@ -1,16 +1,11 @@
using System;
using System.Collections.Generic;
using Microsoft.EntityFrameworkCore;
using Pomelo.EntityFrameworkCore.MySql.Scaffolding.Internal;
namespace IM_API.Models;
public partial class ImContext : DbContext
{
public ImContext()
{
}
public ImContext(DbContextOptions<ImContext> options)
: base(options)
{
@ -50,10 +45,6 @@ public partial class ImContext : DbContext
public virtual DbSet<User> Users { get; set; }
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
#warning To protect potentially sensitive information in your connection string, you should move it out of source code. You can avoid scaffolding the connection string by using the Name= syntax to read it from configuration - see https://go.microsoft.com/fwlink/?linkid=2131148. For more guidance on storing connection strings, see https://go.microsoft.com/fwlink/?LinkId=723263.
=> optionsBuilder.UseMySql("server=frp-era.com;port=26582;database=IM;user=product;password=12345678", Microsoft.EntityFrameworkCore.ServerVersion.Parse("5.7.44-mysql"));
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder
@ -547,6 +538,9 @@ public partial class ImContext : DbContext
entity.Property(e => e.Created)
.HasComment("发送时间 ")
.HasColumnType("datetime");
entity.Property(e => e.GroupMemberId)
.HasComment("若为群消息则表示具体的成员id")
.HasColumnType("int(11)");
entity.Property(e => e.MsgType)
.HasComment("消息类型\r\n(0:文本,1图片,2语音,3视频,4文件5语音聊天,6视频聊天)")
.HasColumnType("tinyint(4)");

View File

@ -1,732 +0,0 @@
using System;
using System.Collections.Generic;
using Microsoft.EntityFrameworkCore;
using Pomelo.EntityFrameworkCore.MySql.Scaffolding.Internal;
namespace IM_API.Models;
public partial class ImDbContext : DbContext
{
public ImDbContext()
{
}
public ImDbContext(DbContextOptions<ImDbContext> options)
: base(options)
{
}
public virtual DbSet<Admin> Admins { get; set; }
public virtual DbSet<Conversation> Conversations { get; set; }
public virtual DbSet<Device> Devices { get; set; }
public virtual DbSet<File> Files { get; set; }
public virtual DbSet<Friend> Friends { get; set; }
public virtual DbSet<FriendRequest> FriendRequests { get; set; }
public virtual DbSet<Group> Groups { get; set; }
public virtual DbSet<GroupInvite> GroupInvites { get; set; }
public virtual DbSet<GroupMember> GroupMembers { get; set; }
public virtual DbSet<GroupRequest> GroupRequests { get; set; }
public virtual DbSet<LoginLog> LoginLogs { get; set; }
public virtual DbSet<Message> Messages { get; set; }
public virtual DbSet<Notification> Notifications { get; set; }
public virtual DbSet<Permission> Permissions { get; set; }
public virtual DbSet<Permissionarole> Permissionaroles { get; set; }
public virtual DbSet<Role> Roles { get; set; }
public virtual DbSet<User> Users { get; set; }
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
#warning To protect potentially sensitive information in your connection string, you should move it out of source code. You can avoid scaffolding the connection string by using the Name= syntax to read it from configuration - see https://go.microsoft.com/fwlink/?linkid=2131148. For more guidance on storing connection strings, see https://go.microsoft.com/fwlink/?LinkId=723263.
=> optionsBuilder.UseMySql("server=frp-era.com;port=26582;database=IM;user=product;password=12345678", Microsoft.EntityFrameworkCore.ServerVersion.Parse("5.7.44-mysql"));
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder
.UseCollation("latin1_swedish_ci")
.HasCharSet("latin1");
modelBuilder.Entity<Admin>(entity =>
{
entity.HasKey(e => e.Id).HasName("PRIMARY");
entity
.ToTable("admins")
.HasCharSet("utf8mb4")
.UseCollation("utf8mb4_general_ci");
entity.HasIndex(e => e.RoleId, "RoleId");
entity.Property(e => e.Id)
.HasColumnType("int(11)")
.HasColumnName("ID");
entity.Property(e => e.Created)
.HasComment("创建时间 ")
.HasColumnType("datetime");
entity.Property(e => e.Password)
.HasMaxLength(50)
.HasComment("密码");
entity.Property(e => e.RoleId)
.HasComment("角色")
.HasColumnType("int(11)");
entity.Property(e => e.State)
.HasComment("状态0:正常2封禁 ")
.HasColumnType("tinyint(4)");
entity.Property(e => e.Updated)
.HasComment("更新时间 ")
.HasColumnType("datetime");
entity.Property(e => e.Username)
.HasMaxLength(50)
.HasComment("用户名");
entity.HasOne(d => d.Role).WithMany(p => p.Admins)
.HasForeignKey(d => d.RoleId)
.OnDelete(DeleteBehavior.ClientSetNull)
.HasConstraintName("admins_ibfk_1");
});
modelBuilder.Entity<Conversation>(entity =>
{
entity.HasKey(e => e.Id).HasName("PRIMARY");
entity
.ToTable("conversations")
.HasCharSet("utf8mb4")
.UseCollation("utf8mb4_general_ci");
entity.HasIndex(e => e.UserId, "Userid");
entity.HasIndex(e => e.LastReadMessageId, "lastReadMessageId");
entity.Property(e => e.Id)
.HasColumnType("int(11)")
.HasColumnName("ID");
entity.Property(e => e.ChatType).HasColumnType("int(11)");
entity.Property(e => e.LastReadMessageId)
.HasComment("最后一条已读消息ID ")
.HasColumnType("int(11)")
.HasColumnName("lastMessageId");
entity.Property(e => e.StreamKey)
.HasMaxLength(255)
.HasComment("消息推送唯一标识符");
entity.Property(e => e.TargetId)
.HasComment("对方ID群聊为群聊ID单聊为单聊ID ")
.HasColumnType("int(11)");
entity.Property(e => e.UnreadCount)
.HasComment("未读消息数 ")
.HasColumnType("int(11)");
entity.Property(e => e.UserId)
.HasComment("用户")
.HasColumnType("int(11)");
entity.HasOne(d => d.User).WithMany(p => p.Conversations)
.HasForeignKey(d => d.UserId)
.OnDelete(DeleteBehavior.ClientSetNull)
.HasConstraintName("conversations_ibfk_1");
});
modelBuilder.Entity<Device>(entity =>
{
entity.HasKey(e => e.Id).HasName("PRIMARY");
entity
.ToTable("devices")
.HasCharSet("utf8mb4")
.UseCollation("utf8mb4_general_ci");
entity.HasIndex(e => e.UserId, "Userid");
entity.Property(e => e.Id)
.HasColumnType("int(11)")
.HasColumnName("ID");
entity.Property(e => e.Dtype)
.HasComment("设备类型(\r\n0:Android,1:Ios,2:PC,3Pad,4:未知)")
.HasColumnType("tinyint(4)")
.HasColumnName("DType");
entity.Property(e => e.LastLogin)
.HasComment("最后一次登录 ")
.HasColumnType("datetime");
entity.Property(e => e.UserId)
.HasComment("设备所属用户 ")
.HasColumnType("int(11)");
entity.HasOne(d => d.User).WithMany(p => p.Devices)
.HasForeignKey(d => d.UserId)
.OnDelete(DeleteBehavior.ClientSetNull)
.HasConstraintName("devices_ibfk_1");
});
modelBuilder.Entity<File>(entity =>
{
entity.HasKey(e => e.Id).HasName("PRIMARY");
entity
.ToTable("files")
.HasCharSet("utf8mb4")
.UseCollation("utf8mb4_general_ci");
entity.HasIndex(e => e.MessageId, "Messageld");
entity.Property(e => e.Id)
.HasColumnType("int(11)")
.HasColumnName("ID");
entity.Property(e => e.Created)
.HasComment("创建时间 ")
.HasColumnType("datetime");
entity.Property(e => e.MessageId)
.HasComment("关联消息ID ")
.HasColumnType("int(11)");
entity.Property(e => e.Name)
.HasMaxLength(50)
.HasComment("文件名 ");
entity.Property(e => e.Size)
.HasComment("文件大小单位KB ")
.HasColumnType("int(11)");
entity.Property(e => e.Type)
.HasMaxLength(10)
.HasComment("文件类型 ");
entity.Property(e => e.Url)
.HasMaxLength(100)
.HasComment("文件储存URL ")
.HasColumnName("URL");
entity.HasOne(d => d.Message).WithMany(p => p.Files)
.HasForeignKey(d => d.MessageId)
.OnDelete(DeleteBehavior.ClientSetNull)
.HasConstraintName("files_ibfk_1");
});
modelBuilder.Entity<Friend>(entity =>
{
entity.HasKey(e => e.Id).HasName("PRIMARY");
entity
.ToTable("friends")
.HasCharSet("utf8mb4")
.UseCollation("utf8mb4_general_ci");
entity.HasIndex(e => e.Id, "ID");
entity.HasIndex(e => new { e.UserId, e.FriendId }, "Userld");
entity.HasIndex(e => e.FriendId, "用户2id");
entity.Property(e => e.Id)
.HasColumnType("int(11)")
.HasColumnName("ID");
entity.Property(e => e.Avatar)
.HasMaxLength(255)
.HasComment("好友头像");
entity.Property(e => e.Created)
.HasComment("好友关系创建时间")
.HasColumnType("datetime");
entity.Property(e => e.FriendId)
.HasComment("用户2ID")
.HasColumnType("int(11)");
entity.Property(e => e.RemarkName)
.HasMaxLength(20)
.HasComment("好友备注名");
entity.Property(e => e.Status)
.HasComment("当前好友关系状态\r\n0待通过,1已添加,2已拒绝,3已拉黑")
.HasColumnType("tinyint(4)");
entity.Property(e => e.UserId)
.HasComment("用户ID")
.HasColumnType("int(11)");
entity.HasOne(d => d.FriendNavigation).WithMany(p => p.FriendFriendNavigations)
.HasForeignKey(d => d.FriendId)
.OnDelete(DeleteBehavior.ClientSetNull)
.HasConstraintName("用户2id");
entity.HasOne(d => d.User).WithMany(p => p.FriendUsers)
.HasForeignKey(d => d.UserId)
.OnDelete(DeleteBehavior.ClientSetNull)
.HasConstraintName("用户id");
});
modelBuilder.Entity<FriendRequest>(entity =>
{
entity.HasKey(e => e.Id).HasName("PRIMARY");
entity
.ToTable("friend_request")
.HasCharSet("utf8mb4")
.UseCollation("utf8mb4_general_ci");
entity.HasIndex(e => e.RequestUser, "RequestUser");
entity.HasIndex(e => e.ResponseUser, "ResponseUser");
entity.Property(e => e.Id)
.HasColumnType("int(11)")
.HasColumnName("ID");
entity.Property(e => e.Created)
.HasComment("申请时间 ")
.HasColumnType("datetime");
entity.Property(e => e.Description)
.HasComment("申请附言 ")
.HasColumnType("text");
entity.Property(e => e.RequestUser)
.HasComment("申请人 ")
.HasColumnType("int(11)");
entity.Property(e => e.ResponseUser)
.HasComment("被申请人 ")
.HasColumnType("int(11)");
entity.Property(e => e.State)
.HasComment("申请状态0待通过,1:拒绝,2:同意,3拉黑 ")
.HasColumnType("tinyint(4)");
entity.HasOne(d => d.RequestUserNavigation).WithMany(p => p.FriendRequestRequestUserNavigations)
.HasForeignKey(d => d.RequestUser)
.OnDelete(DeleteBehavior.ClientSetNull)
.HasConstraintName("friend_request_ibfk_1");
entity.HasOne(d => d.ResponseUserNavigation).WithMany(p => p.FriendRequestResponseUserNavigations)
.HasForeignKey(d => d.ResponseUser)
.OnDelete(DeleteBehavior.ClientSetNull)
.HasConstraintName("friend_request_ibfk_2");
});
modelBuilder.Entity<Group>(entity =>
{
entity.HasKey(e => e.Id).HasName("PRIMARY");
entity
.ToTable("groups")
.HasCharSet("utf8mb4")
.UseCollation("utf8mb4_general_ci");
entity.HasIndex(e => e.GroupMaster, "GroupMaster");
entity.HasIndex(e => e.Id, "ID");
entity.Property(e => e.Id)
.HasColumnType("int(11)")
.HasColumnName("ID");
entity.Property(e => e.AllMembersBanned)
.HasComment("全员禁言0允许发言2全员禁言")
.HasColumnType("tinyint(4)");
entity.Property(e => e.Announcement)
.HasComment("群公告")
.HasColumnType("text");
entity.Property(e => e.Auhority)
.HasComment("群权限\r\n0需管理员同意,1任意人可加群,2不允许任何人加入")
.HasColumnType("tinyint(4)");
entity.Property(e => e.Created)
.HasComment("群聊创建时间")
.HasColumnType("datetime");
entity.Property(e => e.GroupMaster)
.HasComment("群主")
.HasColumnType("int(11)");
entity.Property(e => e.Name)
.HasMaxLength(20)
.HasComment("群聊名称");
entity.Property(e => e.Status)
.HasComment("群聊状态\r\n(1正常,2封禁)")
.HasColumnType("tinyint(4)");
entity.HasOne(d => d.GroupMasterNavigation).WithMany(p => p.Groups)
.HasForeignKey(d => d.GroupMaster)
.OnDelete(DeleteBehavior.ClientSetNull)
.HasConstraintName("groups_ibfk_1");
});
modelBuilder.Entity<GroupInvite>(entity =>
{
entity.HasKey(e => e.Id).HasName("PRIMARY");
entity
.ToTable("group_invite")
.HasCharSet("utf8mb4")
.UseCollation("utf8mb4_general_ci");
entity.HasIndex(e => e.GroupId, "GroupId");
entity.HasIndex(e => e.InviteUser, "InviteUser");
entity.HasIndex(e => e.InvitedUser, "InvitedUser");
entity.Property(e => e.Id)
.HasColumnType("int(11)")
.HasColumnName("ID");
entity.Property(e => e.Created)
.HasComment("创建时间")
.HasColumnType("datetime");
entity.Property(e => e.GroupId)
.HasComment("群聊编号")
.HasColumnType("int(11)");
entity.Property(e => e.InviteUser)
.HasComment("邀请用户")
.HasColumnType("int(11)");
entity.Property(e => e.InvitedUser)
.HasComment("被邀请用户")
.HasColumnType("int(11)");
entity.Property(e => e.State)
.HasComment("当前状态(0:待被邀请人同意\r\n1:被邀请人已同意)")
.HasColumnType("tinyint(4)");
entity.HasOne(d => d.Group).WithMany(p => p.GroupInvites)
.HasForeignKey(d => d.GroupId)
.OnDelete(DeleteBehavior.ClientSetNull)
.HasConstraintName("group_invite_ibfk_2");
entity.HasOne(d => d.InviteUserNavigation).WithMany(p => p.GroupInviteInviteUserNavigations)
.HasForeignKey(d => d.InviteUser)
.HasConstraintName("group_invite_ibfk_1");
entity.HasOne(d => d.InvitedUserNavigation).WithMany(p => p.GroupInviteInvitedUserNavigations)
.HasForeignKey(d => d.InvitedUser)
.HasConstraintName("group_invite_ibfk_3");
});
modelBuilder.Entity<GroupMember>(entity =>
{
entity.HasKey(e => e.Id).HasName("PRIMARY");
entity
.ToTable("group_member")
.HasCharSet("utf8mb4")
.UseCollation("utf8mb4_general_ci");
entity.HasIndex(e => e.GroupId, "Groupld");
entity.HasIndex(e => e.Id, "ID");
entity.HasIndex(e => e.UserId, "Userld");
entity.Property(e => e.Id)
.HasColumnType("int(11)")
.HasColumnName("ID");
entity.Property(e => e.Created)
.HasDefaultValueSql("'1970-01-01 00:00:00'")
.HasComment("加入群聊时间")
.HasColumnType("datetime");
entity.Property(e => e.GroupId)
.HasComment("群聊编号")
.HasColumnType("int(11)");
entity.Property(e => e.Role)
.HasComment("成员角色0:普通成员,1:管理员,2:群主)")
.HasColumnType("tinyint(4)");
entity.Property(e => e.UserId)
.HasComment("用户编号")
.HasColumnType("int(11)");
entity.HasOne(d => d.Group).WithMany(p => p.GroupMemberGroups)
.HasForeignKey(d => d.GroupId)
.OnDelete(DeleteBehavior.ClientSetNull)
.HasConstraintName("group_member_ibfk_2");
entity.HasOne(d => d.User).WithMany(p => p.GroupMemberUsers)
.HasForeignKey(d => d.UserId)
.OnDelete(DeleteBehavior.ClientSetNull)
.HasConstraintName("group_member_ibfk_1");
});
modelBuilder.Entity<GroupRequest>(entity =>
{
entity.HasKey(e => e.Id).HasName("PRIMARY");
entity
.ToTable("group_request")
.HasCharSet("utf8mb4")
.UseCollation("utf8mb4_general_ci");
entity.HasIndex(e => e.GroupId, "GroupId");
entity.Property(e => e.Id)
.HasColumnType("int(11)")
.HasColumnName("ID");
entity.Property(e => e.Created)
.HasComment("创建时间")
.HasColumnType("datetime");
entity.Property(e => e.Description)
.HasComment("入群附言")
.HasColumnType("text");
entity.Property(e => e.GroupId)
.HasComment("群聊编号\r\n")
.HasColumnType("int(11)");
entity.Property(e => e.State)
.HasComment("申请状态0:待管理员同意,1:已拒绝,2已同意")
.HasColumnType("tinyint(4)");
entity.Property(e => e.UserId)
.HasComment("申请人 ")
.HasColumnType("int(11)");
entity.HasOne(d => d.Group).WithMany(p => p.GroupRequests)
.HasForeignKey(d => d.GroupId)
.OnDelete(DeleteBehavior.ClientSetNull)
.HasConstraintName("group_request_ibfk_1");
entity.HasOne(d => d.GroupNavigation).WithMany(p => p.GroupRequests)
.HasForeignKey(d => d.GroupId)
.OnDelete(DeleteBehavior.ClientSetNull)
.HasConstraintName("group_request_ibfk_2");
});
modelBuilder.Entity<LoginLog>(entity =>
{
entity.HasKey(e => e.Id).HasName("PRIMARY");
entity
.ToTable("login_log")
.HasCharSet("utf8mb4")
.UseCollation("utf8mb4_general_ci");
entity.HasIndex(e => e.UserId, "Userld");
entity.Property(e => e.Id)
.HasColumnType("int(11)")
.HasColumnName("ID");
entity.Property(e => e.Dtype)
.HasComment("设备类型通Devices/DType ")
.HasColumnType("tinyint(4)")
.HasColumnName("DType");
entity.Property(e => e.Logined)
.HasComment("登录时间 ")
.HasColumnType("datetime");
entity.Property(e => e.State)
.HasComment("登录状态(0:登陆成功,1:未验证,2:已被拒绝) ")
.HasColumnType("tinyint(4)");
entity.Property(e => e.UserId)
.HasComment("登录用户 ")
.HasColumnType("int(11)");
entity.HasOne(d => d.User).WithMany(p => p.LoginLogs)
.HasForeignKey(d => d.UserId)
.OnDelete(DeleteBehavior.ClientSetNull)
.HasConstraintName("login_log_ibfk_1");
});
modelBuilder.Entity<Message>(entity =>
{
entity.HasKey(e => e.Id).HasName("PRIMARY");
entity
.ToTable("messages")
.HasCharSet("utf8mb4")
.UseCollation("utf8mb4_general_ci");
entity.HasIndex(e => e.Sender, "Sender");
entity.Property(e => e.Id)
.HasColumnType("int(11)")
.HasColumnName("ID");
entity.Property(e => e.ChatType)
.HasComment("聊天类型\r\n0私聊,1群聊")
.HasColumnType("tinyint(4)");
entity.Property(e => e.Content)
.HasComment("消息内容 ")
.HasColumnType("text");
entity.Property(e => e.Created)
.HasComment("发送时间 ")
.HasColumnType("datetime");
entity.Property(e => e.MsgType)
.HasComment("消息类型\r\n(0:文本,1图片,2语音,3视频,4文件5语音聊天,6视频聊天)")
.HasColumnType("tinyint(4)");
entity.Property(e => e.Recipient)
.HasComment("接收者私聊为用户ID群聊为群聊ID ")
.HasColumnType("int(11)");
entity.Property(e => e.Sender)
.HasComment("发送者 ")
.HasColumnType("int(11)");
entity.Property(e => e.State)
.HasComment("消息状态(0:已发送,1:已撤回) ")
.HasColumnType("tinyint(4)");
entity.Property(e => e.StreamKey)
.HasMaxLength(255)
.HasComment("消息推送唯一标识符");
entity.HasOne(d => d.SenderNavigation).WithMany(p => p.Messages)
.HasForeignKey(d => d.Sender)
.OnDelete(DeleteBehavior.ClientSetNull)
.HasConstraintName("messages_ibfk_1");
});
modelBuilder.Entity<Notification>(entity =>
{
entity.HasKey(e => e.Id).HasName("PRIMARY");
entity
.ToTable("notifications")
.HasCharSet("utf8mb4")
.UseCollation("utf8mb4_general_ci");
entity.HasIndex(e => e.UserId, "Userld");
entity.Property(e => e.Id)
.HasColumnType("int(11)")
.HasColumnName("ID");
entity.Property(e => e.Content)
.HasComment("通知内容")
.HasColumnType("text");
entity.Property(e => e.Created)
.HasComment("创建时间")
.HasColumnType("datetime");
entity.Property(e => e.Ntype)
.HasComment("通知类型(0文本)")
.HasColumnType("tinyint(4)")
.HasColumnName("NType");
entity.Property(e => e.Title)
.HasMaxLength(40)
.HasComment("通知标题");
entity.Property(e => e.UserId)
.HasComment("接收人(为空为全体通知)")
.HasColumnType("int(11)");
entity.HasOne(d => d.User).WithMany(p => p.Notifications)
.HasForeignKey(d => d.UserId)
.OnDelete(DeleteBehavior.ClientSetNull)
.HasConstraintName("notifications_ibfk_1");
});
modelBuilder.Entity<Permission>(entity =>
{
entity.HasKey(e => e.Id).HasName("PRIMARY");
entity
.ToTable("permissions")
.HasCharSet("utf8mb4")
.UseCollation("utf8mb4_general_ci");
entity.Property(e => e.Id)
.HasColumnType("int(11)")
.HasColumnName("ID");
entity.Property(e => e.Code)
.HasComment("权限编码 ")
.HasColumnType("int(11)");
entity.Property(e => e.Created)
.HasComment("创建时间 ")
.HasColumnType("datetime");
entity.Property(e => e.Name)
.HasMaxLength(50)
.HasComment("权限名称 ");
entity.Property(e => e.Ptype)
.HasComment("权限类型(0:增,1:删,2:改,3:查) ")
.HasColumnType("int(11)")
.HasColumnName("PType");
});
modelBuilder.Entity<Permissionarole>(entity =>
{
entity.HasKey(e => e.Id).HasName("PRIMARY");
entity
.ToTable("permissionarole")
.HasCharSet("utf8mb4")
.UseCollation("utf8mb4_general_ci");
entity.HasIndex(e => e.PermissionId, "Permissionld");
entity.HasIndex(e => e.RoleId, "Roleld");
entity.Property(e => e.Id)
.ValueGeneratedNever()
.HasColumnType("int(11)")
.HasColumnName("ID");
entity.Property(e => e.PermissionId)
.HasComment("权限 ")
.HasColumnType("int(11)");
entity.Property(e => e.RoleId)
.HasComment("角色 ")
.HasColumnType("int(11)");
entity.HasOne(d => d.Permission).WithMany(p => p.Permissionaroles)
.HasForeignKey(d => d.PermissionId)
.OnDelete(DeleteBehavior.ClientSetNull)
.HasConstraintName("permissionarole_ibfk_2");
entity.HasOne(d => d.Role).WithMany(p => p.Permissionaroles)
.HasForeignKey(d => d.RoleId)
.OnDelete(DeleteBehavior.ClientSetNull)
.HasConstraintName("permissionarole_ibfk_1");
});
modelBuilder.Entity<Role>(entity =>
{
entity.HasKey(e => e.Id).HasName("PRIMARY");
entity
.ToTable("roles")
.HasCharSet("utf8mb4")
.UseCollation("utf8mb4_general_ci");
entity.Property(e => e.Id)
.HasColumnType("int(11)")
.HasColumnName("ID");
entity.Property(e => e.Created)
.HasComment("创建时间 ")
.HasColumnType("datetime");
entity.Property(e => e.Description)
.HasComment("角色描述 ")
.HasColumnType("text");
entity.Property(e => e.Name)
.HasMaxLength(20)
.HasComment("角色名称 ");
});
modelBuilder.Entity<User>(entity =>
{
entity.HasKey(e => e.Id).HasName("PRIMARY");
entity
.ToTable("users")
.HasCharSet("utf8mb4")
.UseCollation("utf8mb4_general_ci");
entity.HasIndex(e => e.Id, "ID");
entity.HasIndex(e => e.Username, "Username").IsUnique();
entity.Property(e => e.Id)
.HasColumnType("int(11)")
.HasColumnName("ID");
entity.Property(e => e.Avatar)
.HasMaxLength(255)
.HasComment("用户头像链接");
entity.Property(e => e.Created)
.HasDefaultValueSql("'1970-01-01 00:00:00'")
.HasComment("创建时间")
.HasColumnType("datetime");
entity.Property(e => e.IsDeleted)
.HasComment("软删除标识\r\n0账号正常\r\n1账号已删除")
.HasColumnType("tinyint(4)");
entity.Property(e => e.NickName)
.HasMaxLength(50)
.HasComment("用户昵称");
entity.Property(e => e.OnlineStatus)
.HasComment("用户在线状态\r\n0默认不在线\r\n1在线")
.HasColumnType("tinyint(4)");
entity.Property(e => e.Password)
.HasMaxLength(50)
.HasComment("密码");
entity.Property(e => e.Status)
.HasDefaultValueSql("'1'")
.HasComment("账户状态\r\n(0未激活,1正常,2封禁)")
.HasColumnType("tinyint(4)");
entity.Property(e => e.Updated)
.HasComment("修改时间")
.HasColumnType("datetime");
entity.Property(e => e.Username)
.HasMaxLength(50)
.HasComment("唯一用户名");
});
OnModelCreatingPartial(modelBuilder);
}
partial void OnModelCreatingPartial(ModelBuilder modelBuilder);
}

View File

@ -49,6 +49,11 @@ public partial class Message
/// </summary>
public string StreamKey { get; set; } = null!;
/// <summary>
/// 若为群消息则表示具体的成员id
/// </summary>
public int? GroupMemberId { get; set; }
public virtual ICollection<Conversation> Conversations { get; set; } = new List<Conversation>();
public virtual ICollection<File> Files { get; set; } = new List<File>();

View File

@ -1,4 +1,5 @@
using IM_API.Dtos;
using AutoMapper;
using IM_API.Dtos;
using IM_API.Exceptions;
using IM_API.Interface.Services;
using IM_API.Models;
@ -10,9 +11,11 @@ namespace IM_API.Services
public class ConversationService : IConversationService
{
private readonly ImContext _context;
public ConversationService(ImContext context)
private readonly IMapper _mapper;
public ConversationService(ImContext context, IMapper mapper)
{
_context = context;
_mapper = mapper;
}
#region
public async Task<bool> ClearConversationsAsync(int userId)
@ -26,47 +29,41 @@ namespace IM_API.Services
#region
public async Task<List<ConversationDto>> GetConversationsAsync(int userId)
{
var privateQuery = from c in _context.Conversations
join f in _context.Friends on new { c.UserId, c.TargetId}
equals new { UserId = f.UserId, TargetId = f.FriendId}
where c.UserId == userId && c.ChatType == (int)ChatType.PRIVATE
select new ConversationDto
{
Id = c.Id,
UserId = c.UserId,
TargetId = c.TargetId,
LastReadMessageId = c.LastReadMessageId,
LastReadMessage = c.LastReadMessage,
UnreadCount = c.UnreadCount,
ChatType = c.ChatType,
LastMessage = c.LastMessage,
TargetAvatar = f.Avatar,
TargetName = f.RemarkName,
DateTime = c.LastMessageTime
// 1. 获取私聊会话
var privateList = await (from c in _context.Conversations
join f in _context.Friends on new { c.UserId, c.TargetId }
equals new { UserId = f.UserId, TargetId = f.FriendId }
where c.UserId == userId && c.ChatType == (int)ChatType.PRIVATE
select new { c, f.Avatar, f.RemarkName })
.ToListAsync();
};
// 2. 获取群聊会话
var groupList = await (from c in _context.Conversations
join g in _context.Groups on c.TargetId equals g.Id
where c.UserId == userId && c.ChatType == (int)ChatType.GROUP
select new { c, g.Avatar, g.Name })
.ToListAsync();
var groupQuery = from c in _context.Conversations
join g in _context.Groups on c.TargetId equals g.Id
where c.UserId == userId && c.ChatType == (int)ChatType.GROUP
select new ConversationDto
{
Id = c.Id,
UserId = c.UserId,
TargetId = c.TargetId,
LastReadMessageId = c.LastReadMessageId,
LastReadMessage = c.LastReadMessage,
UnreadCount = c.UnreadCount,
ChatType = c.ChatType,
LastMessage = c.LastMessage,
TargetAvatar = g.Avatar,
TargetName = g.Name,
DateTime = c.LastMessageTime
};
return await privateQuery
.Concat(groupQuery)
.OrderByDescending(x => x.DateTime)
.ToListAsync();
var privateDtos = privateList.Select(x =>
{
var dto = _mapper.Map<ConversationDto>(x.c);
dto.TargetAvatar = x.Avatar;
dto.TargetName = x.RemarkName;
return dto;
});
var groupDtos = groupList.Select(x =>
{
var dto = _mapper.Map<ConversationDto>(x.c);
dto.TargetAvatar = x.Avatar;
dto.TargetName = x.Name;
return dto;
});
// 4. 合并并排序
return privateDtos.Concat(groupDtos)
.OrderByDescending(x => x.DateTime)
.ToList();
}
#endregion
#region
@ -81,6 +78,7 @@ namespace IM_API.Services
#endregion
#region
public async Task<List<string>> GetUserAllStreamKeyAsync(int userId)
{
return await _context.Conversations.Where(x => x.UserId == userId)
@ -88,5 +86,37 @@ namespace IM_API.Services
.Distinct()
.ToListAsync();
}
#endregion
#region
public async Task<ConversationDto> GetConversationByIdAsync(int userId, int conversationId)
{
var conversation = await _context.Conversations
.Include(x => x.LastReadMessage)
.FirstOrDefaultAsync(
x => x.UserId == userId && x.Id == conversationId
);
if (conversation is null) throw new BaseException(CodeDefine.CONVERSATION_NOT_FOUND);
var dto = _mapper.Map<ConversationDto>(conversation);
//dto.LastReadMessage = _mapper.Map<MessageBaseDto>(conversation);
if(conversation.ChatType == (int)ChatType.PRIVATE)
{
var friendInfo = await _context.Friends.FirstOrDefaultAsync(
x => x.UserId == userId && x.FriendId == conversation.TargetId
);
if (friendInfo is null) throw new BaseException(CodeDefine.FRIEND_RELATION_NOT_FOUND);
_mapper.Map(friendInfo,dto);
}
if(conversation.ChatType == (int)ChatType.GROUP)
{
var groupInfo = await _context.Groups.FirstOrDefaultAsync(
x => x.Id == conversation.TargetId
);
if (groupInfo is null) throw new BaseException(CodeDefine.GROUP_NOT_FOUND);
_mapper.Map(groupInfo, dto);
}
return dto;
}
#endregion
}
}

View File

@ -20,14 +20,38 @@ namespace IM_API.Services
_mapper = mapper;
}
public Task<List<MessageBaseDto>> GetGroupMessagesAsync(int groupId, int page, int pageSize, bool desc)
public async Task<List<MessageBaseDto>> GetMessagesAsync(int userId, int conversationId, int? msgId, int? pageSize, bool desc)
{
throw new NotImplementedException();
}
public Task<List<MessageBaseDto>> GetPrivateMessagesAsync(int userAId, int userBId, int page, int pageSize, bool desc)
{
throw new NotImplementedException();
//获取会话信息用于获取双方聊天的唯一标识streamkey
Conversation? conversation = await _context.Conversations.FirstOrDefaultAsync(
x => x.Id == conversationId && x.UserId == userId
);
if (conversation is null) throw new BaseException(CodeDefine.CONVERSATION_NOT_FOUND);
var query = _context.Messages.AsQueryable();
if(msgId != null)
{
query = query.Where(
x => x.StreamKey == conversation.StreamKey && x.Id < msgId.Value
)
.OrderByDescending(x => x.Id);
}
else
{
query = query.Where(
x => x.StreamKey == conversation.StreamKey && x.Id > conversation.LastReadMessageId
);
}
if(pageSize != null)
{
query = query.Take(pageSize.Value);
}
var msgList = await query
.ToListAsync();
msgList = msgList
.OrderBy(x => x.Created)
.ThenBy(t => t.Id)
.ToList();
return _mapper.Map<List<MessageBaseDto>>(msgList);
}
public Task<int> GetUnreadCountAsync(int userId)

View File

@ -71,3 +71,12 @@ public WeatherForecastController(IDemo demo)
## 4. 模型类字段使用规范
### 4.1 类中状态相关字段例如Status返回值为sbyte。若类中有同名字段+后缀Enum则优先使用后者,StatusEnum。
## 5.数据库相关
### 5.1 若数据库表结构更新,请在软件包控制台执行如下命令:
```cmd
Scaffold-DbContext "Name=ConnectionStrings:DefaultConnection" Pomelo.EntityFrameworkCore.MySql -OutputDir Models -Context ImContext -Force -NoOnConfiguring
```

View File

@ -11,7 +11,10 @@ const routes = [
{
path: '/',
component: MainView,
redirect: '/messages',
meta: {
requiresAuth: true
},
children: [
{
path: '/messages',
@ -42,6 +45,7 @@ const routes = [
{
path: '/index',
component: MainView,
redirect: '/messages',
meta: {
requiresAuth: true
}
@ -56,10 +60,18 @@ const router = createRouter({
router.beforeEach((to, from, next) => {
const authStore = useAuthStore();
if (to.path == '/auth/login') {
if (authStore.isLoggedIn) {
message.info('已登录,即将跳转...');
next('/');
}
next();
}
if (to.meta.requiresAuth && !authStore.isLoggedIn) {
message.info('未登录,即将跳转...');
next('auth/login');
} else {
}
else {
next();
}
})

View File

@ -11,5 +11,25 @@ export const messageService = {
* 清空所有会话消息
* @returns
*/
clearConversation: () => request.post('')
clearConversation: () => request.post(''),
/**
* 获取单个会话信息
* @param {*} conversationId
* @returns
*/
getConversationById: (conversationId) => request.get(`/conversation/get?conversationId=${conversationId}`),
/**
* 获取历史消息列表
* @param {*} conversationId 指定会话
* @param {*} msgId
* @param {*} pageSize
* @returns
*/
getHistoryMessages: (conversationId, msgId, pageSize = 10) => request.get(`/message/getmessageList?conversationId=${conversationId}&msgId=${msgId}&pageSize=${pageSize}`),
/**
* 获取最新消息
* @param {*} conversationId
* @returns
*/
getMessages: (conversationId) => request.get(`/message/getmessageList?conversationId=${conversationId}`)
}

View File

@ -0,0 +1,10 @@
export function getChatCodeStr(code) {
switch (code) {
case 0:
return '私聊'
case 1:
return '群聊'
default:
return '未知类型'
}
}

View File

@ -0,0 +1,18 @@
export function formatDate(dateStr) {
const date = new Date(dateStr);
const year = date.getFullYear();
const month = String(date.getMonth() + 1).padStart(2, '0'); // 补零
const day = String(date.getDate()).padStart(2, '0');
const hours = String(date.getHours()).padStart(2, '0');
const minutes = String(date.getMinutes()).padStart(2, '0');
const seconds = String(date.getSeconds()).padStart(2, '0');
const nowDate = new Date();
if (year == nowDate.getFullYear() && month == String(nowDate.getMonth() + 1).padStart(2, '0') && day == String(nowDate.getDate()).padStart(2, '0')) {
return `${hours}:${minutes}:${seconds}`;
}
if (year == nowDate.getFullYear()) {
return `${month}/${day} ${hours}:${minutes}:${seconds}`;
}
return `${year}/${month}/${day} ${hours}:${minutes}:${seconds}`;
}

View File

@ -126,7 +126,6 @@ function handleGoToChat() {
const loadContactList = async (page = 1,limit = 100) => {
const res = await friendService.getFriendList(page,limit);
contacts.value = res.data;
console.log(contacts.value)
}

View File

@ -1,7 +1,7 @@
<template>
<section class="chat-panel">
<header class="chat-header">
<span class="title">{{ currentSession?.name || '未选择会话' }}</span>
<span class="title">{{ conversationInfo?.targetName || '未选择会话' }}</span>
<div class="actions">
<button @click="startCall('video')">📹</button>
<button @click="startCall('voice')">📞</button>
@ -9,15 +9,15 @@
</header>
<div class="chat-history" ref="historyRef">
<div v-for="m in activeMessages" :key="m.id" :class="['msg', m.mine ? 'mine' : 'other']">
<img :src="m.mine ? (myInfo?.avatar || defaultAvatar) : currentSession?.avatar" class="avatar-chat" />
<div v-for="m in messages" :key="m.id" :class="['msg', m.senderId == myInfo.id ? 'mine' : 'other']">
<img :src="m.senderId == myInfo.id ? (myInfo?.avatar || defaultAvatar) : defaultAvatar" class="avatar-chat" />
<div class="msg-content">
<div class="bubble">
<div v-if="m.type === 'text'">{{ m.content }}</div>
<div v-if="m.type === 'Text'">{{ m.content }}</div>
<div v-else-if="m.type === 'emoji'" class="emoji-msg">{{ m.content }}</div>
</div>
<span class="msg-time">{{ m.time }}</span>
<span class="msg-time">{{ formatDate(m.timeStamp) }}</span>
</div>
</div>
</div>
@ -45,6 +45,8 @@
import { ref, computed, nextTick, onMounted } from 'vue';
import { useAuthStore } from '@/stores/auth';
import defaultAvatar from '@/assets/default_avatar.png';
import { messageService } from '@/services/message';
import { formatDate } from '@/utils/formatDate';
const props = defineProps({
id:{
@ -56,6 +58,8 @@ const input = ref(''); // 输入框内容
const historyRef = ref(null); // DOM
const myInfo = useAuthStore().userInfo;
const conversationInfo = ref(null)
// --- ( store ) ---
const sessions = ref([
{ id: 1, name: '南浔', avatar: 'https://i.pravatar.cc/40?1', last: '' },
@ -71,12 +75,6 @@ const messages = ref({
2: []
});
// --- ---
const currentSession = computed(() => sessions.value.find(s => s.id == props.id));
const activeMessages = computed(() => messages.value[props.id] || []);
// --- ---
//
const scrollToBottom = async () => {
await nextTick(); // DOM
@ -128,8 +126,21 @@ function toggleEmoji() {
console.log('打开表情面板');
}
async function loadConversation(conversationId) {
const res = await messageService.getConversationById(conversationId);
conversationInfo.value = res.data;
console.log(res)
}
async function loadMessages(conversationId, msgId = null, pageSize = null) {
const res = await messageService.getMessages(conversationId);
messages.value = res.data;
}
//
onMounted(() => {
onMounted(async () => {
await loadConversation(props.id);
await loadMessages(props.id);
scrollToBottom();
});
</script>

View File

@ -12,15 +12,15 @@
<div v-for="s in filteredSessions" :key="s.id"
class="list-item" :class="{active: activeId === s.id}" @click="selectSession(s)">
<div class="avatar-container">
<img :src="s.avatar" class="avatar-std" />
<span v-if="s.unread > 0" class="unread-badge">{{ s.unread }}</span>
<img :src="s.targetAvatar ?? defaultAvatar" class="avatar-std" />
<span v-if="s.unread > 0" class="unread-badge">{{ s.unreadCount ?? 0 }}</span>
</div>
<div class="info">
<div class="name-row">
<span class="name">{{ s.name }}</span>
<span class="time">{{ s.lastTime }}</span>
<span class="name">{{ s.targetName ?? '未知用户' }}</span>
<span class="time">{{ formatDate(s.dateTime) ?? '1970/1/1 00:00:00' }}</span>
</div>
<div class="last-msg">{{ s.last }}</div>
<div class="last-msg">{{ s.lastMessage ?? '获取消息内容失败' }}</div>
</div>
</div>
</div>
@ -32,18 +32,18 @@
<script setup>
import router from '@/router'
import { ref, computed, nextTick } from 'vue'
import { ref, computed, nextTick, onMounted } from 'vue'
import { messageService } from '@/services/message'
import defaultAvatar from '@/assets/default_avatar.png'
import { formatDate } from '@/utils/formatDate'
const searchQuery = ref('')
const input = ref('')
const historyRef = ref(null)
const activeId = ref(1)
const conversations = ref([]);
const sessions = ref([
{ id: 1, name: '南浔', last: '在写代码呢', lastTime: '14:20', avatar: 'https://i.pravatar.cc/40?1', unread: 2 },
{ id: 2, name: '技术群', last: '部署好了', lastTime: '12:05', avatar: 'https://i.pravatar.cc/40?2', unread: 0 }
])
const filteredSessions = computed(() => sessions.value.filter(s => s.name.includes(searchQuery.value)))
const filteredSessions = computed(() => conversations.value.filter(s => s.targetName.includes(searchQuery.value)))
const currentSession = computed(() => sessions.value.find(s => s.id === activeId.value))
@ -59,6 +59,16 @@ const scrollToBottom = async () => {
if (historyRef.value) historyRef.value.scrollTop = historyRef.value.scrollHeight
}
async function loadConversation() {
const res = await messageService.getConversations();
conversations.value = res.data;
console.log(res)
}
onMounted(async () => {
await loadConversation();
})
</script>