优化代码

This commit is contained in:
pengxiaolong
2025-08-15 22:11:55 +08:00
parent 15335e8921
commit ce21a633ec
9 changed files with 540 additions and 72 deletions

View File

@@ -118,3 +118,7 @@ export function signIn(data) {
export function editEmail(data) {
return postAxios({ url: 'user/updateUserMail', data })
}
//获取OTP
export function getOtp() {
return getAxios({ url: 'otp/getotp' })
}

View File

@@ -0,0 +1,88 @@
<template>
<div class="picture-message" @click="dialogVisibleClick">
<img class="picture-message-img" :src="item.payload.url" alt="">
</div>
<el-dialog v-model="dialogVisible" fullscreen>
<div class="dialog-content">
<img class="dialog-img" :src="item.payload.url" alt="">
</div>
<div class="dialog-footer">
<el-button type="primary" @click="dialogVisible = false">关闭</el-button>
<el-button type="primary" @click="saveToDesktop">保存到本地</el-button>
</div>
</el-dialog>
</template>
<script setup>
import {
ref, // 响应式基础
watch, // 侦听器
onMounted, // 组件挂载完成后执行
onUpdated, // 组件更新后执行
onUnmounted, // 组件销毁前执行
} from "vue";
const props = defineProps({
item: {
type: Object,
required: true
}
});
// 保存到桌面
function saveToDesktop() {
const link = document.createElement('a');
link.href = props.item.payload.url;
link.download = props.item.payload.filename;
document.body.appendChild(link);
link.click();
document.body.removeChild(link);
}
// 弹窗相关
const dialogVisible = ref(false);
function dialogVisibleClick() {
dialogVisible.value = true;
}
const refname = ref('');
watch(refname, async (newQuestion, oldQuestion) => {
// 变化后执行
});
onMounted(() => {
// 组件挂载完成后执行
});
onUpdated(() => {
// 组件更新后执行
});
onUnmounted(() => {
// 组件销毁前执行
});
</script>
<style scoped>
.picture-message{
padding: 0;
margin: 0;
/* display: inline-block; */
max-width: 100%;
border-radius: 10px;
}
.picture-message-img{
max-width: 100%;
height: auto;
display: block;
border-radius: 10px;
}
.dialog-content{
width: 98vw;
height: 95vh;
display: flex;
justify-content: center;
align-items: center;
}
.dialog-img{
max-width: 100%;
height: auto;
display: block;
}
</style>

View File

@@ -11,8 +11,8 @@ import GoEasy from 'goeasy'
const goeasy = GoEasy.getInstance({
host:"hangzhou.goeasy.io", //若是新加坡区域singapore.goeasy.io
appkey:"您的common key",
modules:['pubsub']//根据需要传入pubsub或'im或数组方式同时传入
appkey:"PC-a88037e060ed4753bb316ac7239e62d9",
modules:['im']//根据需要传入pubsub或'im或数组方式同时传入
});
const app = createApp(App);

View File

@@ -32,4 +32,18 @@ export const tokenStore = defineStore('token', {
this.user = user
}
},
})
export const IMloginStore = defineStore('IMlogin', {
state: () => {
return { IMstate: false }
},
// 也可以这样定义
// state: () => ({ count: 0 })
actions: {
setIMstate(state){
this.IMstate = state
}
},
})

View File

@@ -1,6 +1,10 @@
import { goeasy } from '../main'
import { IMloginStore } from '@/stores/notice.js';
import GoEasy from 'goeasy'
//链接IM(登录IM)
export function goEasyLink(data) {
const counter = IMloginStore()
return new Promise((resolve, reject) => {
goeasy.connect({
id:data.id, //im必填最大长度60字符
@@ -8,14 +12,49 @@ export function goEasyLink(data) {
otp:data.key,
onSuccess: function () { //连接成功
console.log("连接成功");
counter.setIMstate(true); //登录IM成功
resolve(true);
},
onFailed: function (error) { //连接失败
console.log("连接失败,错误码:" + error.code + ",错误信息:" + error.content);
},
onProgress:function(attempts) { //连接或自动重连中
console.log("正在重连中..." + attempts);
console.log("正在重连中...");
}
});
})
}
//获取会话列表
export function goEasyGetConversations() {
var im = goeasy.im;
return new Promise((resolve, reject) => {
im.latestConversations({
onSuccess: function (result) {
resolve(result);
},
onFailed: function (error) {
console.log("获取会话列表失败,错误码:" + error.code + " content:" + error.content);
},
});
})
}
//获取指定会话的消息列表
export function goEasyGetMessages(data) {
var im = goeasy.im;
return new Promise((resolve, reject) => {
im.history({
id: data.id,//用户id或者群id
type: GoEasy.IM_SCENE.PRIVATE, //群聊GoEasy.IM_SCENE.GROUP, 客服GoEasy.IM_SCENE.CS,
lastTimestamp: data.timestamp, //上次查询结果里最后一条消息的时间戳首次查询传入null即可
limit: 30, //可选项返回的消息条数默认为10条最多30条
onSuccess: function (result) {
resolve(result.content);
},
onFailed: function (error) {
console.log("获取消息列表失败,错误码:" + error.code + " content:" + error.content);
}
});
})
}

View File

@@ -59,12 +59,13 @@
<script setup>
import { ref, watch, onMounted, onUnmounted } from "vue";
import { getVxQrcode, getScanResult, login } from "@/api/account"; // 导入登录接口
import { getVxQrcode, getScanResult, login, getOtp} from "@/api/account"; // 导入登录接口
import { useRouter } from "vue-router";
import { ElMessage } from "element-plus";
import { tokenStore, UserStore } from '@/stores/notice'
import { setStorage , getStorage } from '@/utils/storage.js';
import { goEasyLink } from '@/utils/goeasy.js';
import { ElLoading } from "element-plus";
const router = useRouter();
const refname = ref("");
const refEmail = ref(""); // 邮箱
@@ -73,6 +74,7 @@ const refSwitch = ref(false); // 登录切换/true:微信登录/false:邮箱登
const Qrcode = ref(""); // 二维码
const token = tokenStore()
const user = UserStore()
const info = ref({})
// 登录
function EmailLogin() {
// // 邮箱验证
@@ -98,19 +100,32 @@ function EmailLogin() {
// email: refEmail.value,
password: refpassword.value,
}).then((res) => {
console.log(res);
token.setToken(res.token);
setStorage("token", res.token);
setStorage("email", refEmail.value);
setStorage("password", refpassword.value);
user.setUser(res);
setStorage("user", res);
// otp/getopt 调用
goEasyLink().then(() => {
info.value = res
getOtp().then((res) => {
const data = {
id: String(info.value.id),
avatar: info.value.headerIcon,
nickname: info.value.nickName,
key: res,
}
goEasyLink(data).then(() => {
loading.close();
router.push("/nav");
}).catch((err) => {
ElMessage.error("登录失败:"+err.content);
loading.close();
});
}).catch((err) => {
loading.close();
});
}).catch((err) => {
loading.close();
});
}
// 登录切换
@@ -132,7 +147,6 @@ function register() {
function fetchQrcode() {
getVxQrcode().then((res) => {
Qrcode.value = res;
console.log(res);
});
}
//查询微信扫码是否成功

View File

@@ -6,20 +6,31 @@
<el-splitter-panel size="17%" min="17%" max="25%">
<!-- 会话列表 -->
<div class="demo-panel">
<div
v-infinite-scroll="load"
infinite-scroll-distance="100px"
class="chatList"
style="overflow: auto"
>
<div class="chatItem" v-for="(item, index) in chatList" :key="index">
<div class="Avatar"></div>
<div class="chatList" style="overflow: auto">
<div
class="chatItem"
v-for="(item, index) in chatList"
:key="index"
@click="handleClick(item)"
:style="{ background: handleClickdata == item ? '#f5f5f5' : '' }"
>
<el-badge
:value="item.unread > 0 ? item.unread : ''"
:max="999"
class="item"
>
<div class="Avatar">
<img class="Avatar-img" :src="item.data.avatar" alt="" />
</div>
</el-badge>
<div class="chatContent">
<div class="PersonalInfo">
<div class="name">名称</div>
<div class="time">07-29 14:00</div>
<div class="name">{{ item.data.nickname }}</div>
<div class="time">
{{ TimestamptolocalTime(item.lastMessage.timestamp) }}
</div>
</div>
<div class="chatText">最后一条消息</div>
<div class="chatText">{{ item.lastMessage.payload.text }}</div>
</div>
</div>
</div>
@@ -29,19 +40,87 @@
<el-splitter layout="vertical">
<el-splitter-panel>
<!-- 消息列表 -->
<div class="demo-panel"></div>
<div class="demo-panel">
<div class="chatlist" v-if="handleClickdata != null" style="overflow: auto" ref="chatlistContainer">
<div
class="chatmessages"
v-for="(item, index) in messageslist"
:key="index"
>
<!-- 对方消息 -->
<div
class="messageOtherSide"
v-if="item.senderId == handleClickdata.userId"
>
<!-- 头像 -->
<div class="messageAvatar">
<img
class="Avatar-img"
:src="handleClickdata.data.avatar"
alt=""
/>
</div>
<!-- 三角 -->
<div class="OtherTriangle" v-if="item.type == 'text'" />
<!-- 消息内容 -->
<div class="messageContent" :style="{padding:item.type == 'text'?' 10px 20px 10px 20px':'0'}">
<!-- 文本消息 -->
<div v-if="item.type == 'text'">
{{ item.payload.text }}
</div>
<!-- 图片消息 -->
<PictureMessage :item="item" v-if="item.type == 'image'"></PictureMessage>
</div>
</div>
<!-- 自己消息 -->
<div class="messageMySide" v-if="item.senderId == user.id">
<!-- 消息内容 -->
<div class="messageMyContent" :style="{padding:item.type == 'text'?' 10px 20px 10px 20px':''}">
<!-- 文本消息 -->
<div v-if="item.type == 'text'">
{{ item.payload.text }}
</div>
<!-- 图片消息 -->
<PictureMessage :item="item" v-if="item.type == 'image'"></PictureMessage>
</div>
<div class="MyTriangle" v-if="item.type == 'text'" />
<div class="messageAvatar">
<img class="Avatar-img" :src="headerIcon" alt="" />
</div>
</div>
</div>
</div>
</div>
</el-splitter-panel>
<el-splitter-panel size="20%" min="20%" max="40%">
<!-- 输入框 -->
<div class="demo-panel">
<div class="inputBox">
<div class="Console">
<div class="Console-content">
</div>
<!-- 控制面板 -->
<div class="Console-content">
<!-- 其他的消息 -->
<div class="chat-input-other">
<!-- 图片消息 -->
<div class="chat-input-img">
<img class="chat-input-img-img" src="https://vv-1317974657.cos.ap-shanghai.myqcloud.com/util/Album.png" alt="">
</div>
<!-- 邀请消息 -->
<div class="chat-input-img">
<img class="chat-input-img-img" src="https://vv-1317974657.cos.ap-shanghai.myqcloud.com/util/chat_invite.png" alt="">
</div>
</div>
<!-- 发送按钮 -->
<div class="chat-input-Send">发送</div>
</div>
</div>
<div class="input">
<textarea
<textarea
v-model="textarea"
class="textarea"
style="width: 100%; height: 100%"
@@ -64,53 +143,118 @@ import {
onMounted, // 组件挂载完成后执行
onUpdated, // 组件更新后执行
onUnmounted, // 组件销毁前执行
nextTick, // 确保DOM更新完成
} from "vue";
const refname = ref("");
import { goEasyGetConversations, goEasyGetMessages } from "@/utils/goeasy.js";
import { TimestamptolocalTime } from "@/utils/timeConversion.js";
import { IMloginStore } from "@/stores/notice.js";
import { goeasy } from "../../main";
import GoEasy from "goeasy";
import { getPromiseStorage } from "@/utils/storage.js";
import PictureMessage from "@/components/chatMessage/PictureMessage";
var im = goeasy.im;
const counter = IMloginStore();
const textarea = ref(""); // 输入框内容
const chatList = ref([
{},
{},
{},
{},
{},
{},
{},
{},
{},
{},
{},
{},
{},
{},
{},
{},
{},
{},
{},
{},
{},
{},
{},
{},
{},
]);
const chatList = ref([]); // 会话列表
const handleClickdata = ref(null); // 选中会话
const messageslist = ref([]); //消息列表
const user = ref(null); // 用户信息
const headerIcon = ref(null); // 用户头像
const chatlistContainer = ref(null); // 聊天列表容器引用
const isLoading = ref(false); // 防止重复加载
function load() {
console.log("加载更多",textarea.value);
} // 加载更多
// 加载更多消息
function loadMore() {
if (isLoading.value) return;
isLoading.value = true;
// 这里添加加载更多消息逻辑/加载完后才改为false
getMoreMessages();
}
watch(refname, async (newQuestion, oldQuestion) => {
// 变化后执行
});
// 滚动事件处理
function scrollHandler() {
const container = chatlistContainer.value;
if (container && container.scrollTop <= 0) {
loadMore();
}
}
//选中会话
function handleClick(item) {
handleClickdata.value = item;
console.log(item);
goEasyGetMessages({
id: item.userId,
timestamp: null,
}).then((res) => {
console.log(res);
messageslist.value = res;
nextTick(() => {
if (chatlistContainer.value) {
chatlistContainer.value.scrollTop = chatlistContainer.value.scrollHeight;
}
});
chatlistContainer.value.addEventListener('scroll', scrollHandler);
});
}
//获取更多消息
function getMoreMessages() {
goEasyGetMessages({
id: handleClickdata.value.userId,
timestamp: messageslist.value[0].timestamp,
}).then((res) => {
isLoading.value = false;
messageslist.value.unshift(...res);
});
}
//更新后的最新会话列表
function onConversationsUpdated(conversations) {
chatList.value = conversations;
}
//获取会话列表
function getChatList() {
goEasyGetConversations().then((res) => {
chatList.value = res.content.conversations;
console.log(chatList.value);
});
}
watch(counter, (newQuestion, oldQuestion) => {
setTimeout(() => {
getChatList();
}, 300);
}),
{ deep: true };
onMounted(() => {
// 组件挂载完成后执行
getPromiseStorage("user")
.then((res) => {
user.value = res;
headerIcon.value = user.value.headerIcon;
console.log(user.value);
})
.catch((err) => {
console.log(err);
});
if (counter.IMstate == true) {
getChatList();
}
setTimeout(() => {
im.on(GoEasy.IM_EVENT.CONVERSATIONS_UPDATED, onConversationsUpdated);
}, 1000);
});
onUpdated(() => {
// 组件更新后执行
});
onUnmounted(() => {
// 组件销毁前执行
// 移除滚动事件监听
if (chatlistContainer.value) {
chatlistContainer.value.removeEventListener('scroll', scrollHandler);
}
});
</script>
@@ -151,13 +295,18 @@ onUnmounted(() => {
.Avatar {
width: 50px;
height: 50px;
border-radius: 50%;
border-radius: 10px;
margin-left: 10px;
background-color: #f5f5f5;
}
.Avatar-img {
width: 100%;
height: 100%;
border-radius: 10px;
}
.chatContent {
margin-left: 10px;
width: calc(100% - 80px);
margin-left: 30px;
width: calc(100% - 100px);
height: 60%;
display: flex;
flex-direction: column;
@@ -189,6 +338,76 @@ onUnmounted(() => {
overflow: hidden;
text-overflow: ellipsis; /* 显示省略号 */
}
.chatlist {
width: 100%;
height: 100%;
display: flex;
flex-direction: column;
}
.chatmessages {
width: 100%;
height: auto;
margin-top: 20px;
}
.messageOtherSide {
width: 100%;
height: auto;
display: flex;
align-items: self-start;
}
.messageMySide {
width: 100%;
height: auto;
display: flex;
justify-content: end;
align-items: self-start;
}
.messageAvatar {
width: 50px;
height: 50px;
border-radius: 10px;
margin-right: 20px;
margin-left: 20px;
background-color: #f5f5f5;
}
.OtherTriangle {
width: 0;
height: 0;
border-top: 10px solid transparent;
border-right: 10px solid #f5f5f5;
border-bottom: 10px solid transparent;
margin-top: 12px;
}
.MyTriangle {
width: 0;
height: 0;
border-top: 10px solid transparent;
border-left: 10px solid #7bbd0093;
border-bottom: 10px solid transparent;
margin-top: 12px;
}
.messageContent {
max-width: 30%;
height: auto;
min-height: 20px;
background-color: #f5f5f5;
border-radius: 10px;
font-size: 20px;
word-break: break-all; /* 强制换行(适合中文) */
overflow-wrap: break-word; /* 长单词换行(适合英文) */
white-space: normal; /* 允许自动换行 */
}
.messageMyContent {
max-width: 30%;
height: auto;
min-height: 20px;
background-color: #7bbd0093;
border-radius: 10px;
font-size: 20px;
word-break: break-all; /* 强制换行(适合中文) */
overflow-wrap: break-word; /* 长单词换行(适合英文) */
white-space: normal; /* 允许自动换行 */
}
.inputBox {
width: 100%;
height: 100%;
@@ -203,21 +422,66 @@ onUnmounted(() => {
align-items: center;
justify-content: center;
}
.Console-content{
.Console-content {
width: 98%;
height: 100%;
display: flex;
align-items: center;
justify-content: space-between;
}
.chat-input-other{
display: flex;
align-items: center;
}
.chat-input-img{
width: 40px;
height: 40px;
border-radius: 10px;
display: flex;
margin-left: 10px;
justify-content: center;
align-items: center;
transition: all 0.4s ease;
}
.chat-input-img:hover{
background-color: #d4d4d4;
}
.chat-input-img-img{
width: 30px;
height: 30px;
}
.chat-input-Send {
width: 10%;
height: 80%;
text-align: center;
line-height: 40px;
font-size: 16px;
color: #03aba8;
border-radius: 10px;
margin-right: 6px;
transition: all 0.4s ease;
}
.chat-input-Send:hover {
box-shadow: 5px 5px 5px rgba(0, 0, 0, 0.3);
background-color: #03aba82d;
// color: #ffffff;
}
.chat-input-Send:active{
transition: all 0.1s ease;
transform: scale(0.95) !important;
}
.input {
width: 98%;
height: calc(100% - 70px);
}
.textarea{
.textarea {
width: 100%;
height: 100%;
border: none;
outline: none;
overflow: hidden;
background-color:#ffffff00;
resize:none;
background-color: #ffffff00;
resize: none;
}
</style>

View File

@@ -843,6 +843,10 @@ onUnmounted(() => {
background-color: #03aba82d;
// color: #ffffff;
}
.chat-input-Send:active{
transition: all 0.1s ease;
transform: scale(0.95) !important;
}
.chat-input-Textarea {
width: 100%;
height: 60px;

View File

@@ -1,7 +1,7 @@
<template>
<div class="common-layout">
<el-container>
<!-- 左侧导航栏 -->
<!-- 左侧导航栏 -->
<el-aside class="nav-aside">
<Appaside></Appaside>
</el-aside>
@@ -15,8 +15,49 @@
<script setup>
import Appaside from "/src/components/Appaside.vue";
import { ref, watch, onMounted, onUnmounted } from "vue";
import { getOtp } from "@/api/account";
const user = ref(null); // 用户信息
import { goEasyLink } from "@/utils/goeasy.js";
import { useRouter } from "vue-router";
import { ElMessage } from "element-plus";
import {getPromiseStorage } from "@/utils/storage.js";
const router = useRouter();
//自动链接IM
function autoLinkIM() {
getOtp()
.then((res) => {
const data = {
id: String(user.value.id),
avatar: user.value.headerIcon,
nickname: user.value.nickName,
key: res,
};
goEasyLink(data)
.then(() => {
console.log("IM链接成功");
})
.catch((err) => {
ElMessage.error("聊天系统链接失败");
router.push("/");
});
})
.catch((err) => {
ElMessage.error("聊天系统获取失败");
router.push("/");
});
}
onMounted(() => {
getPromiseStorage("user")
.then((res) => {
user.value = res;
autoLinkIM() //自动链接IM
})
.catch((err) => {
console.log(err);
});
});
</script>
<style lang="less">
@@ -43,4 +84,4 @@ html {
// background-image: linear-gradient(0deg, @bg-Sidebar-color-top, @bg-color);
// border: 2px solid @border-color;
}
</style>
</style>