feat: 完善聊天模块

This commit is contained in:
橙子
2024-04-06 18:28:32 +08:00
parent 43b4032bbb
commit 6aedff75f1
17 changed files with 507 additions and 110 deletions

View File

@@ -0,0 +1,16 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Yi.Framework.ChatHub.Application.Contracts.Dtos
{
public class GroupMessageInputDto
{
/// <summary>
/// 消息内容
/// </summary>
public string Content { get; set; }
}
}

View File

@@ -0,0 +1,21 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Yi.Framework.ChatHub.Application.Contracts.Dtos
{
public class PersonalMessageInputDto
{
/// <summary>
/// 用户id
/// </summary>
public Guid UserId { get; set; }
/// <summary>
/// 消息内容
/// </summary>
public string Content { get; set; }
}
}

View File

@@ -1,4 +1,4 @@
<Project Sdk="Microsoft.NET.Sdk"> <Project Sdk="Microsoft.NET.Sdk">
<Import Project="..\..\..\common.props" /> <Import Project="..\..\..\common.props" />

View File

@@ -0,0 +1,43 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using Volo.Abp.Application.Services;
using Yi.Framework.ChatHub.Application.Contracts.Dtos;
using Yi.Framework.ChatHub.Domain.Managers;
using Yi.Framework.ChatHub.Domain.Shared.Model;
namespace Yi.Framework.ChatHub.Application.Services
{
public class ChatMessageService : ApplicationService
{
private UserMessageManager _userMessageManager;
public ChatMessageService(UserMessageManager userMessageManager) { _userMessageManager = userMessageManager; }
/// <summary>
/// 发送个人消息
/// </summary>
/// <returns></returns>
[HttpPost("chat-message/personal")]
[Authorize]
public async Task SendPersonalMessageAsync(PersonalMessageInputDto input)
{
await _userMessageManager.SendMessageAsync(MessageContext.CreatePersonal(input.Content, input.UserId,CurrentUser.Id!.Value)); ;
}
/// <summary>
/// 发送群组消息
/// </summary>
/// <returns></returns>
[HttpPost("chat-message/group")]
[Authorize]
public async Task SendGroupMessageAsync(GroupMessageInputDto input)
{
await _userMessageManager.SendMessageAsync(MessageContext.CreateAll(input.Content, CurrentUser.Id!.Value)); ;
}
}
}

View File

@@ -1,31 +1,19 @@
using System; using Mapster;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using FreeRedis;
using Mapster;
using Microsoft.Extensions.Options;
using Volo.Abp.Application.Services; using Volo.Abp.Application.Services;
using Volo.Abp.Caching; using Yi.Framework.ChatHub.Domain.Managers;
using Volo.Abp.DependencyInjection; using Yi.Framework.ChatHub.Domain.Shared.Model;
using Yi.Framework.ChatHub.Application.Contracts.Dtos;
using Yi.Framework.ChatHub.Domain.Shared.Caches;
namespace Yi.Framework.ChatHub.Application.Services namespace Yi.Framework.ChatHub.Application.Services
{ {
public class ChatUserService : ApplicationService public class ChatUserService : ApplicationService
{ {
/// <summary> private UserMessageManager _messageManager;
/// 使用懒加载防止报错 public ChatUserService(UserMessageManager messageManager) { _messageManager = messageManager; }
/// </summary>
private IRedisClient RedisClient => LazyServiceProvider.LazyGetRequiredService<IRedisClient>(); public async Task<List<ChatUserModel>> GetListAsync()
private string CacheKeyPrefix => LazyServiceProvider.LazyGetRequiredService<IOptions<AbpDistributedCacheOptions>>().Value.KeyPrefix;
public async Task<List<ChatUserGetListOutputDto>> GetListAsync()
{ {
var key = new ChatOnlineUserCacheKey(CacheKeyPrefix); var userList = await _messageManager.GetAllUserAsync();
var cacheUsers = (await RedisClient.HGetAllAsync(key.GetKey())).Select(x => System.Text.Json.JsonSerializer.Deserialize < ChatOnlineUserCacheItem >( x.Value)).ToList(); var output = userList.Adapt<List<ChatUserModel>>();
var output = cacheUsers.Adapt<List<ChatUserGetListOutputDto>>();
return output; return output;
} }
} }

View File

@@ -9,5 +9,8 @@ namespace Yi.Framework.ChatHub.Domain.Shared.Consts
public class ChatConst public class ChatConst
{ {
public const string AllGroupName = "all"; public const string AllGroupName = "all";
public const string ClientActionReceiveMsg = "receiveMsg";
public const string ClientActionUserStatus = "userStatus";
} }
} }

View File

@@ -4,9 +4,9 @@ using System.Linq;
using System.Text; using System.Text;
using System.Threading.Tasks; using System.Threading.Tasks;
namespace Yi.Framework.ChatHub.Application.Contracts.Dtos namespace Yi.Framework.ChatHub.Domain.Shared.Model
{ {
public class ChatUserGetListOutputDto public class ChatUserModel
{ {
/// <summary> /// <summary>
/// 用户id /// 用户id
@@ -22,5 +22,6 @@ namespace Yi.Framework.ChatHub.Application.Contracts.Dtos
/// 用户头像 /// 用户头像
/// </summary> /// </summary>
public string UserIcon { get; set; } public string UserIcon { get; set; }
} }
} }

View File

@@ -0,0 +1,51 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Text.Json.Serialization;
using System.Threading.Tasks;
namespace Yi.Framework.ChatHub.Domain.Shared.Model
{
public class MessageContext
{
public static MessageContext CreatePersonal(string content, Guid userId,Guid sendUserId)
{
return new MessageContext() { MessageType = MessageType.Personal, Content = content, ReceiveId = userId ,SendUserId= sendUserId };
}
public static MessageContext CreateAll(string content, Guid sendUserId)
{
return new MessageContext() { MessageType = MessageType.All, Content = content, SendUserId = sendUserId };
}
/// <summary>
/// 消息类型
/// </summary>
[JsonConverter(typeof(JsonStringEnumConverter))]
public MessageType MessageType { get; set; }
/// <summary>
/// 接收者(用户id、群组id)
/// </summary>
public Guid? ReceiveId { get; set; }
/// <summary>
/// 发送者的用户id
/// </summary>
public Guid SendUserId { get; set; }
/// <summary>
/// 消息内容
/// </summary>
public string Content { get; set; }
}
public enum MessageType
{
Personal,
Group,
All
}
}

View File

@@ -3,7 +3,6 @@
<ItemGroup> <ItemGroup>
<Folder Include="Enums\" /> <Folder Include="Enums\" />
<Folder Include="Etos\" /> <Folder Include="Etos\" />
<Folder Include="Model\" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>

View File

@@ -0,0 +1,66 @@
using FreeRedis;
using Mapster;
using Microsoft.AspNetCore.SignalR;
using Microsoft.Extensions.Options;
using Volo.Abp.Caching;
using Volo.Abp.Domain.Services;
using Yi.Framework.ChatHub.Domain.Shared.Caches;
using Yi.Framework.ChatHub.Domain.Shared.Consts;
using Yi.Framework.ChatHub.Domain.Shared.Model;
using Yi.Framework.ChatHub.Domain.SignalRHubs;
namespace Yi.Framework.ChatHub.Domain.Managers
{
public class UserMessageManager : DomainService
{
private IHubContext<ChatCenterHub> _hubContext;
public UserMessageManager(IHubContext<ChatCenterHub> hubContext)
{
_hubContext = hubContext;
}
/// <summary>
/// 使用懒加载防止报错
/// </summary>
private IRedisClient RedisClient => LazyServiceProvider.LazyGetRequiredService<IRedisClient>();
private string CacheKeyPrefix => LazyServiceProvider.LazyGetRequiredService<IOptions<AbpDistributedCacheOptions>>().Value.KeyPrefix;
public async Task SendMessageAsync(MessageContext context)
{
switch (context.MessageType)
{
case MessageType.Personal:
var userModel =await GetUserAsync(context.ReceiveId.Value);
if (userModel is not null)
{
await _hubContext.Clients.Client(userModel.ClientId).SendAsync(ChatConst.ClientActionReceiveMsg, context.MessageType, context);
}
break;
case MessageType.Group:
throw new NotImplementedException();
break;
case MessageType.All:
await _hubContext.Clients.All.SendAsync(ChatConst.ClientActionReceiveMsg, context.MessageType, context);
break;
default:
break;
}
}
public async Task<List<ChatOnlineUserCacheItem>> GetAllUserAsync()
{
var key = new ChatOnlineUserCacheKey(CacheKeyPrefix);
var cacheUsers = (await RedisClient.HGetAllAsync(key.GetKey())).Select(x => System.Text.Json.JsonSerializer.Deserialize<ChatOnlineUserCacheItem>(x.Value)).ToList();
return cacheUsers;
}
public async Task<ChatOnlineUserCacheItem?> GetUserAsync(Guid userId)
{
var key = new ChatOnlineUserCacheKey(CacheKeyPrefix);
var cacheUser = System.Text.Json.JsonSerializer.Deserialize < ChatOnlineUserCacheItem >( await RedisClient.HGetAsync(key.GetKey(), key.GetField(userId)));
return cacheUser;
}
}
}

View File

@@ -5,16 +5,15 @@ using Microsoft.AspNetCore.SignalR;
using Microsoft.Extensions.Options; using Microsoft.Extensions.Options;
using Volo.Abp.AspNetCore.SignalR; using Volo.Abp.AspNetCore.SignalR;
using Volo.Abp.Caching; using Volo.Abp.Caching;
using Volo.Abp.DependencyInjection;
using Yi.Framework.ChatHub.Application.Contracts.Dtos;
using Yi.Framework.ChatHub.Domain.Shared.Caches; using Yi.Framework.ChatHub.Domain.Shared.Caches;
using Yi.Framework.ChatHub.Domain.Shared.Consts; using Yi.Framework.ChatHub.Domain.Shared.Consts;
using Yi.Framework.ChatHub.Domain.Shared.Model;
namespace Yi.Framework.ChatHub.Application.SignalRHubs namespace Yi.Framework.ChatHub.Domain.SignalRHubs
{ {
[HubRoute("/hub/chat")] [HubRoute("/hub/chat")]
[Authorize] [Authorize]
public class ChatHub : AbpHub public class ChatCenterHub : AbpHub
{ {
/// <summary> /// <summary>
/// 使用懒加载防止报错 /// 使用懒加载防止报错
@@ -24,7 +23,7 @@ namespace Yi.Framework.ChatHub.Application.SignalRHubs
/// 缓存前缀 /// 缓存前缀
/// </summary> /// </summary>
private string CacheKeyPrefix => LazyServiceProvider.LazyGetRequiredService<IOptions<AbpDistributedCacheOptions>>().Value.KeyPrefix; private string CacheKeyPrefix => LazyServiceProvider.LazyGetRequiredService<IOptions<AbpDistributedCacheOptions>>().Value.KeyPrefix;
public ChatHub() public ChatCenterHub()
{ {
} }
@@ -48,7 +47,7 @@ namespace Yi.Framework.ChatHub.Application.SignalRHubs
//连接时,还需要去查询用户包含在的群组,将群主全部加入.Todo //连接时,还需要去查询用户包含在的群组,将群主全部加入.Todo
await Groups.AddToGroupAsync(Context.ConnectionId, ChatConst.AllGroupName); await Groups.AddToGroupAsync(Context.ConnectionId, ChatConst.AllGroupName);
await Clients.All.SendAsync("liveUser", item.Adapt<ChatUserGetListOutputDto>()); await Clients.All.SendAsync("liveUser", item.Adapt<ChatUserModel>());
} }

View File

@@ -7,7 +7,6 @@
<ItemGroup> <ItemGroup>
<PackageReference Include="Volo.Abp.AspNetCore.SignalR" Version="$(AbpVersion)" /> <PackageReference Include="Volo.Abp.AspNetCore.SignalR" Version="$(AbpVersion)" />
<PackageReference Include="Volo.Abp.Ddd.Domain" Version="$(AbpVersion)" /> <PackageReference Include="Volo.Abp.Ddd.Domain" Version="$(AbpVersion)" />
@@ -19,7 +18,6 @@
<ItemGroup> <ItemGroup>
<Folder Include="Entities\" /> <Folder Include="Entities\" />
<Folder Include="EventHandlers\" /> <Folder Include="EventHandlers\" />
<Folder Include="Managers\" />
<Folder Include="Repositories\" /> <Folder Include="Repositories\" />
</ItemGroup> </ItemGroup>

View File

@@ -0,0 +1,18 @@
import request from "@/config/axios/service";
export function sendPersonalMessage(data) {
return request({
url: "/chat-message/personal",
method: "post",
data
});
}
export function sendGroupMessage(data) {
return request({
url: "/chat-message/group",
method: "post",
data
});
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.9 KiB

View File

@@ -13,7 +13,7 @@ const receiveMsg = (connection) => {
}); });
//接受其他用户消息 //接受其他用户消息
connection.on("receiveMsg", (type, content) => { connection.on("receiveMsg", (type, content) => {
chatStore.addMsg(content);
}); });
//用户状态-正在输入中,无 //用户状态-正在输入中,无
connection.on("userStatus", (type) => { connection.on("userStatus", (type) => {

View File

@@ -1,25 +1,28 @@
import { defineStore } from "pinia"; import { defineStore } from "pinia";
const chatStore = defineStore("chat", { const chatStore = defineStore("chat", {
state: () => ({ state: () => ({
userList: [], userList: [],
msgList: []
}), }),
// getters: { getters: {
// userListData: (state) => state.userList, allMsgContext: (state) => state.msgList.filter(x=>x.messageType=="All"),
// }, personalMsgContext: (state) => state.msgList.filter(x=>x.messageType=="Personal"),
actions: { },
// 设置在线总数 actions:
{
setMsgList(value) {
this.msgList = value;
},
addMsg(msg) {
this.msgList.push(msg);
},
setUserList(value) { setUserList(value) {
this.userList = value; this.userList = value;
}, },
addUser(user) addUser(user) {
{
this.userList.push(user); this.userList.push(user);
}, },
delUser(userId) delUser(userId) {
{
this.userList = this.userList.filter(obj => obj.userId != userId); this.userList = this.userList.filter(obj => obj.userId != userId);
} }
}, },

View File

@@ -1,18 +1,162 @@
<script setup> <script setup>
import { onMounted, ref,computed } from 'vue'; import { onMounted, ref, computed } from 'vue';
import {storeToRefs} from 'pinia' import { storeToRefs } from 'pinia'
import {getList as getChatUserList} from '@/apis/chatUserApi' import { getList as getChatUserList } from '@/apis/chatUserApi'
import { sendPersonalMessage,sendGroupMessage } from '@/apis/chatMessageApi'
import useChatStore from "@/stores/chat"; import useChatStore from "@/stores/chat";
const chatStore=useChatStore(); import useUserStore from "@/stores/user";
const {userList} =storeToRefs(chatStore);
onMounted(async()=>{ //聊天存储
const chatStore = useChatStore();
const { userList } = storeToRefs(chatStore);
//用户信息
const userStore = useUserStore();
//发送消息是否为空
const msgIsNullShow=ref(false)
//当前选择用户
const currentSelectUser = ref(null);
//当前输入框的值
const currentInputValue = ref("");
//临时存储的输入框根据用户id及组name、all组为keydata为value
const inputListDataStore = ref([{ key: "all", value: "" }]);
//当前聊天框显示的消息
const currentMsgContext = computed(() => {
if (selectIsAll()) {
return chatStore.allMsgContext;
}
else {
return chatStore.personalMsgContext.filter(x => {
//两个条件
//接收用户者id为对面id我发给他
//或者发送用户id为对面他发给我
return (x.receiveId == currentSelectUser.value.userId && x.sendUserId == userStore.id)||
(x.sendUserId == currentSelectUser.value.userId && x.receiveId == userStore.id);
});
}
});
//当前聊天框显示的名称
const currentHeaderName = computed(() => {
return currentSelectUser.value == null ? "官方学习交流群" : currentSelectUser.value.userName;
});
const currentUserItem = computed(() => {
return userList.value.filter(x=>x.userId!=useUserStore().id)
});
//初始化
onMounted(async () => {
chatStore.setUserList((await getChatUserList()).data); chatStore.setUserList((await getChatUserList()).data);
}) })
/*-----方法-----*/
//当前选择的是否为全部
const selectIsAll = () => {
return currentSelectUser.value == null;
};
//输入框的值被更改
const changeInputValue = (inputValue) => {
currentInputValue.value = inputValue;
let index = -1;
let findKey = currentSelectUser.value?.userId
if (selectIsAll()) {
findKey = 'all'
}
index = inputListDataStore.value.findIndex(obj => obj.key == findKey);
inputListDataStore.value[index].value = currentInputValue.value;
}
//绑定的input改变事件
const updateInputValue = (event) => {
changeInputValue(event.target.value);
}
//获取输入框的值
const getCurrentInputValue = () => {
if (selectIsAll()) {
return inputListDataStore.value.filter(x => x.key == "all")[0].value;
} else {
//如果不存在初始存储值
if (!inputListDataStore.value.some(x => x.key == currentSelectUser.value.userId)) {
inputListDataStore.value.push({ key: currentSelectUser.value.userId, value: "" });
return "";
}
return inputListDataStore.value.filter(x => x.key == currentSelectUser.value.userId)[0].value;
}
};
//点击用户列表,
const onclickUserItem = (userInfo, isAllItem) => {
if (isAllItem) {
currentSelectUser.value = null;
}
else {
currentSelectUser.value = userInfo;
}
//填充临时存储的输入框
var value = getCurrentInputValue();
//更新当前的输入框
changeInputValue(value);
}
//点击发送按钮
const onclickSendMsg = () => {
console.log(currentInputValue.value ,"currentInputValue.value");
if(currentInputValue.value=="")
{
msgIsNullShow.value=true;
setTimeout(() => {
// 这里写上你想要3秒后执行的代码
msgIsNullShow.value=false;
}, 3000);
return;
}
if (selectIsAll()) {
onclickSendGroupMsg("all", currentInputValue.value);
}
else {
onclickSendPersonalMsg(currentSelectUser.value.userId, currentInputValue.value);
}
changeInputValue("");
}
//点击发送个人消息
const onclickSendPersonalMsg = (receiveId, msg) => {
//添加到本地存储
chatStore.addMsg({
messageType: "Personal",
sendUserId: userStore.id,
content: msg,
receiveId: receiveId
});
sendPersonalMessage({userId:receiveId,content:msg});
//调用接口发送消息
}
//点击发送群组消息按钮
const onclickSendGroupMsg = (groupName, msg) => {
//组还需区分是否给全部成员组
if (selectIsAll) {
//添加到本地存储,不需要,因为广播自己能够接收
// chatStore.addMsg({
// messageType: "All",
// sendUserId: userStore.id,
// content: msg
// });
//调用接口发送消息
sendGroupMessage({content:msg});
}
else {
alert("暂未实现");
}
}
</script> </script>
<template> <template>
<div class="body"> <div class="body">
<div class="left"> <div class="left">
<div class="icon"> <div class="icon">
@@ -47,9 +191,10 @@ onMounted(async()=>{
</div> </div>
</div> </div>
<div class="user-list"> <div class="user-list">
<div class="user-div" style="background-color: #C8C8CA;"> <div class="user-div" @click="onclickUserItem(null, true)"
:class="{ 'select-user-item': currentSelectUser == null }">
<div class="user-div-left"> <div class="user-div-left">
<img src="@/assets/chat_images/friendicon.jpg" /> <img src="@/assets/chat_images/yilogo.png" />
<div class="user-name-msg"> <div class="user-name-msg">
<p class="font-name">官方学习交流群</p> <p class="font-name">官方学习交流群</p>
<p class="font-msg">冲冲冲</p> <p class="font-msg">冲冲冲</p>
@@ -62,11 +207,12 @@ onMounted(async()=>{
</div> </div>
<div v-for="(item,i) in userList" :key="i" class="user-div"> <div v-for="(item, i) in currentUserItem" :key="i" @click="onclickUserItem(item, false)" class="user-div"
:class="{ 'select-user-item': currentSelectUser?.userId == item.userId }">
<div class="user-div-left"> <div class="user-div-left">
<img src="@/assets/chat_images/friendicon.jpg" /> <img src="@/assets/chat_images/friendicon.jpg" />
<div class="user-name-msg"> <div class="user-name-msg">
<p class="font-name">{{item.userName}}</p> <p class="font-name">{{ item.userName }}</p>
<p class="font-msg">现在感觉怎么样</p> <p class="font-msg">现在感觉怎么样</p>
</div> </div>
</div> </div>
@@ -79,7 +225,7 @@ onMounted(async()=>{
<div class="right"> <div class="right">
<div class="header"> <div class="header">
<div class="header-left">橙子</div> <div class="header-left">{{ currentHeaderName }}</div>
<div class="header-right"> <div class="header-right">
<div> <div>
<ul> <ul>
@@ -96,48 +242,53 @@ onMounted(async()=>{
</div> </div>
<div class="content"> <div class="content">
<div v-for="i in 100" :key="i"> <div v-for="(item, i) in currentMsgContext" :key="i">
<div class="content-others content-common"> <div class="content-myself content-common" v-if="item.sendUserId == userStore.id">
<img src="@/assets/chat_images/friendicon.jpg" /> <div class="content-myself-msg content-msg-common ">{{ item.content }}</div>
<div class="content-others-msg content-msg-common ">感觉还可以</div>
</div>
<div class="content-myself content-common">
<div class="content-myself-msg content-msg-common ">现在感觉怎么样</div>
<img src="@/assets/chat_images/icon.jpg" /> <img src="@/assets/chat_images/icon.jpg" />
</div> </div>
<div class="content-others content-common" v-else>
<img src="@/assets/chat_images/friendicon.jpg" />
<div class="content-others-msg content-msg-common ">{{ item.content }}</div>
</div>
</div> </div>
</div> </div>
<div class="bottom"> <div class="bottom">
<div class="bottom-tool"> <div class="bottom-tool">
<ul class="ul-left"> <ul class="ul-left">
<li><img src="@/assets/chat_images/emoji.png" /></li> <li><img src="@/assets/chat_images/emoji.png" /></li>
<li><img src="@/assets/chat_images/sendFile.png" /></li> <li><img src="@/assets/chat_images/sendFile.png" /></li>
<li><img src="@/assets/chat_images/screenshot.png" /></li> <li><img src="@/assets/chat_images/screenshot.png" /></li>
<li><img src="@/assets/chat_images/chatHistory.png" /></li> <li><img src="@/assets/chat_images/chatHistory.png" /></li>
</ul> </ul>
<ul class="ul-right"> <ul class="ul-right">
<li><img src="@/assets/chat_images/landline.png" /></li> <li><img src="@/assets/chat_images/landline.png" /></li>
<li><img src="@/assets/chat_images/videoChat.png" /></li> <li><img src="@/assets/chat_images/videoChat.png" /></li>
</ul> </ul>
</div>
<div class="bottom-input" contenteditable="true">
</div> </div>
<!-- <div class="bottom-input" contenteditable="true" @input="updateInputValue"> -->
<!-- <div class="bottom-input" contenteditable="true" @input="updateInputValue">
<div class="bottom-send"> </div> -->
<button> <textarea class="bottom-input" v-model="currentInputValue" @input="updateInputValue" @keyup.enter="onclickSendMsg()">
发送(S)
</button> </textarea>
<div class="bottom-send">
<div class="msg-null" v-show="msgIsNullShow">不能发送空白信息</div>
<button @click="onclickSendMsg()">
发送(S)
</button>
</div>
</div>
</div> </div>
</div> </div>
</div>
</div>
</template> </template>
<style scoped lang="scss"> <style scoped lang="scss">
.body { .body {
@@ -148,6 +299,10 @@ onMounted(async()=>{
box-shadow: 0px 0px 5px rgba(0, 0, 0, 0.2); box-shadow: 0px 0px 5px rgba(0, 0, 0, 0.2);
} }
.select-user-item {
background-color: #C8C8CA !important;
}
.left { .left {
background-color: #2a2a2a; background-color: #2a2a2a;
width: 70px; width: 70px;
@@ -344,46 +499,52 @@ onMounted(async()=>{
&-tool { &-tool {
display: flex; display: flex;
justify-content: space-between; justify-content: space-between;
height: 22px; height: 22px;
.ul-left { .ul-left {
display: flex; display: flex;
li { li {
width: 22px; width: 22px;
height: 22px; height: 22px;
margin-right: 20px; margin-right: 20px;
}
img {
width: 100%;
height: 100%;
}
} }
.ul-right {
display: flex;
li { img {
width: 22px; width: 100%;
height: 22px; height: 100%;
margin-right: 20px;
}
img {
width: 100%;
height: 100%;
}
} }
} }
.ul-right {
display: flex;
li {
width: 22px;
height: 22px;
margin-right: 20px;
}
img {
width: 100%;
height: 100%;
}
}
}
&-input { &-input {
font-family: "Microsoft YaHei", sans-serif;
height: 70px; height: 70px;
width: 100%; width: 100%;
overflow-y: auto; overflow-y: auto;
padding: 10px 0; padding: 10px 0;
font-size: 18px; font-size: 18px;
background: #F7F7F7;
border: none;
resize: none;
outline: none;
} }
&-send { &-send {
@@ -445,6 +606,7 @@ onMounted(async()=>{
.content-common { .content-common {
display: flex; display: flex;
margin-bottom: 18px;
img { img {
height: 45px; height: 45px;
@@ -526,5 +688,34 @@ onMounted(async()=>{
border-bottom: 10px solid transparent; border-bottom: 10px solid transparent;
border-right: 10px solid #FFFFFF; border-right: 10px solid #FFFFFF;
}
.msg-null{
width: 140px;
height: 41px;
background-color: #FFFFFF;
position: relative;
left: 132px;
bottom: 60px;
border-radius: 5px;
// border: 2px solid #E5E5E5;
display: flex;
align-content: center;
justify-content: center;
flex-wrap: wrap;
font-size: 14px;
}
.msg-null:after {
/* 箭头靠下边 */
content: "";
position: absolute;
width: 0;
height: 0;
top: 40px;
left: 80px;
border-top: 10px solid #FFFFFF;
border-bottom: 10px solid transparent;
border-right: 10px solid transparent;
border-left: 10px solid transparent;
} }
</style> </style>