@@ -38,10 +38,16 @@ const currentSelectUser = ref('all');
const currentInputValue = ref ( "" ) ;
//临时存储的输入框, 根据用户id及组name、all组为key, data为value
const inputListDataStore = ref ( [
{ key : "all" , name : "官方学习交流群" , titleName : "官方学习交流群" , logo : "yilogo.png" , value : "" } ,
{ key : "ai@deepseek-chat" , name : "DeepSeek聊天" , titleName : "DeepSeek-聊天模式" , logo : "deepSeekAi.png" , value : "" } ,
{ key : "ai@deepseek-reasoner" , name : "DeepSeek思索" , titleName : "DeepSeek-思索模式" , logo : "deepSeekAi.png" , value : "" } ,
{ key : "ai@gpt-4o-mini" , name : "ChatGpt聊天" , titleName : "ChatGpt-聊天模式" , logo : "openAi.png" , value : "" } ,
{ key : "all" , name : "官方学习交流群" , titleName : "官方学习交流群" , logo : "yilogo.png" , value : "" } ,
{ key : "ai@deepseek-chat" , name : "DeepSeek聊天" , titleName : "DeepSeek-聊天模式" , logo : "deepSeekAi.png" , value : "" } ,
{
key : "ai@DeepSeek-R1" ,
name : "DeepSeek思索" ,
titleName : "DeepSeek-思索模式" ,
logo : "deepSeekAi.png" ,
value : ""
} ,
{ key : "ai@gpt-4o-mini" , name : "ChatGpt聊天" , titleName : "ChatGpt-聊天模式" , logo : "openAi.png" , value : "" } ,
] ) ;
//AI聊天临时存储
const sendAiChatContext = ref ( [ ] ) ;
@@ -60,7 +66,9 @@ onMounted(async () => {
onclickClose ( ) ;
} , 3000 ) ;
}
//all的聊天消息
chatStore . setMsgList ( ( await getChatAccountMessageList ( ) ) . data ) ;
//在线用户列表
chatStore . setUserList ( ( await getChatUserList ( ) ) . data ) ;
startCountTip ( ) ;
} )
@@ -83,11 +91,9 @@ const currentMsgContext = computed(() => {
//选择ai
else if ( selectIsAi ( ) ) {
//如果是ai的值, 还行经过markdown处理
// console.log(chatStore.aiMsgContext, "chatStore.aiMsgContext");
// return chatStore.aiMsgContext;
let tempHtml = [ ] ;
codeCopyDic = [ ] ;
chatStore . ai MsgContext. forEach ( element => {
chatStore . get MsgContextFunc ( currentSelectUser . value ) . forEach ( element => {
tempHtml . push ( {
content : toMarkDownHtml ( element . content ) ,
messageType : 'Ai' ,
@@ -109,19 +115,17 @@ const currentMsgContext = computed(() => {
} ) ;
}
} ) ;
//图片
//获取聊天内容的头像
const getChatUrl = ( url , position ) => {
if ( position === "left" && selectIsAi ( ) ) {
return "/openAi.png"
if ( position === "left" && ( selectIsAi ( ) || selectIsAll ( ) ) ) {
return imageSrc ( inputListDataStore . value . find ( x => x . key === currentSelectUser . value ) . logo )
}
return getUrl ( url ) ;
}
//当前聊天框显示的名称
const currentHeaderName = computed ( ( ) => {
if ( selectIsAll ( ) ) {
return "官方学习交流群" ;
} else if ( selectIsAi ( ) ) {
return "Ai-ChatGpt4.0(你的私人ai小助手)"
if ( selectIsAll ( ) || selectIsAi ( ) ) {
return inputListDataStore . value . find ( x => x . key === currentSelectUser . value ) . name ;
} else {
return currentSelectUser . value . userName ;
}
@@ -138,8 +142,13 @@ const selectIsAll = () => {
} ;
//当前选择的是否为Ai
const selectIsAi = ( ) => {
return currentSelectUser . value === 'ai' ;
//以ai@开头
return /^ai@/ . test ( currentSelectUser . value ) ;
} ;
//是否为公共的类型
const isPublicType = ( itemType ) => {
return itemType === 'all' || /^ai@/ . test ( itemType ) ;
}
//输入框的值被更改( 同时保存到store中)
const changeInputValue = ( inputValue ) => {
@@ -149,7 +158,7 @@ const changeInputValue = (inputValue) => {
if ( selectIsAll ( ) ) {
findKey = 'all' ;
} else if ( selectIsAi ( ) ) {
findKey = 'ai' ;
findKey = currentSelectUser . value ;
}
index = inputListDataStore . value . findIndex ( obj => obj . key === findKey ) ;
inputListDataStore . value [ index ] . value = currentInputValue . value ;
@@ -160,11 +169,11 @@ const getCurrentInputValue = () => {
if ( selectIsAll ( ) ) {
return inputListDataStore . value . filter ( x => x . key === "all" ) [ 0 ] . value ;
} else if ( selectIsAi ( ) ) {
return inputListDataStore . value . filter ( x => x . key === "ai" ) [ 0 ] . value ;
return inputListDataStore . value . filter ( x => x . key === currentSelectUser . value ) [ 0 ] . value ;
} else {
//如果不存在初始存储值
if ( ! inputListDataStore . value . some ( x => x . key === currentSelectUser . value . userId ) ) {
inputListDataStore . value . push ( { key : currentSelectUser . value . userId , value : "" } ) ;
inputListDataStore . value . push ( { key : currentSelectUser . value . userId , value : "" , type : 'user' }) ;
return "" ;
}
return inputListDataStore . value . filter ( x => x . key === currentSelectUser . value . userId ) [ 0 ] . value ;
@@ -173,11 +182,12 @@ const getCurrentInputValue = () => {
//点击用户列表,
const onclickUserItem = ( userInfo , itemType ) => {
if ( itemType === "all" ) {
currentSelectUser . value = 'all' ;
} else if ( itemType === "ai" ) {
currentSelectUser . value = 'ai' ;
} else {
//公共
if ( isPublicType ( itemType ) ) {
currentSelectUser . value = itemType ;
}
//个人
else {
currentSelectUser . value = userInfo ;
}
//填充临时存储的输入框
@@ -188,6 +198,7 @@ const onclickUserItem = (userInfo, itemType) => {
//点击发送按钮
const onclickSendMsg = ( ) => {
//发送空消息
if ( currentInputValue . value === "" ) {
msgIsNullShow . value = true ;
setTimeout ( ( ) => {
@@ -204,18 +215,24 @@ const onclickSendMsg = () => {
sendAiChatContext . value . push ( {
answererType : 'User' ,
message : currentInputValue . value ,
number : sendAiChatContext . value . length
number : sendAiChatContext . value . length ,
messageType : currentSelectUser . value
} )
//离线前端存储
chatStore . addMsg ( {
messageType : "Ai" ,
messageType : currentSelectUser . value ,
content : currentInputValue . value ,
sendUserId : userStore . id ,
sendUserInfo : { user : { icon : userStore . icon } }
} )
//ai模型, 去掉key的ai@开头的字符串
const model = currentSelectUser . value . replace ( /^ai@/ , '' ) ;
//上下文内容, 当前ai进行隔离
const content = sendAiChatContext . value . filter ( x => x . messageType === currentSelectUser . value )
//发送ai消息
sendAiChat ( sendAiChatC ontex t. value ) ;
sendAiChat ( c onten t, model ) ;
} else {
onclickSendPersonalMsg ( currentSelectUser . value . userId , currentInputValue . value ) ;
}
@@ -247,11 +264,11 @@ const onclickSendGroupMsg = (groupName, msg) => {
//获取当前最后一条信息(显示在左侧菜单)
const getLastMessage = ( ( receiveId , itemType ) => {
if ( itemType === "all" ) {
return chatStore . allMsgContext [ chatStore . all MsgContext. length - 1 ] ? . content . substring ( 0 , 15 ) ;
} else if ( itemType === "ai" ) {
return chatStore . aiMsgContext [ chatStore . aiMsgContext . length - 1 ] ? . content . substring ( 0 , 15 ) ;
} else {
if ( isPublicType ( itemType ) ) {
const message = chatStore . get MsgContextFunc ( itemType ) ;
return message [ message . length - 1 ] ? . content . substring ( 0 , 15 ) ;
}
else {
const messageContext = chatStore . personalMsgContext . filter ( x => {
//两个条件
//接收用户者id为对面id( 我发给他)
@@ -266,151 +283,151 @@ const getLastMessage = ((receiveId, itemType) => {
/*----------以下方法是对内容通用处理,没有业务逻辑,无需变化----------*/
const imageSrc = ( imageName ) => {
// 动态拼接路径
return new URL ( ` ../../assets/chat_images/ ${ imageName } ` , import . meta . url ) . href ;
}
//绑定的input改变事件
const updateInputValue = ( event ) => {
changeInputValue ( event . target . value ) ;
}
//输入框按键事件
const handleKeydownInput = ( ) => {
// 检查是否按下 Shift + Enter
if ( event . key === 'Enter' && event . shiftKey ) {
// 允许输入换行
return ; // 让默认行为继续
}
// 如果只按下 Enter, 则阻止默认的提交行为, 比如在表单中
if ( event . key === 'Enter' ) {
// 阻止默认行为
event . preventDefault ( ) ;
onclickSendMsg ( ) ;
return ;
}
}
//关闭聊天框
const onclickClose = ( ) => {
router . push ( { path : "/index" } )
. then ( ( ) => {
// 重新刷新页面
location . reload ( )
} )
}
//清除ai对话
const clearAiMsg = ( ) => {
sendAiChatContext . value = [ ] ;
chatStore . clearAiMsg ( ) ;
ElMessage ( {
message : "当前会话清除成功" ,
type : "success" ,
duration : 2000 ,
} ) ;
const imageSrc = ( imageName ) => {
// 动态拼接路径
return new URL ( ` ../../assets/chat_images/ ${ imageName } ` , import . meta . url ) . href ;
}
//绑定的input改变事件
const updateInputValue = ( event ) => {
changeInputValue ( event . target . value ) ;
}
//输入框按键事件
const handleKeydownInput = ( ) => {
// 检查是否按下 Shift + Enter
if ( event . key === 'Enter' && event . shiftKey ) {
// 允许输入换行
return ; // 让默认行为继续
}
// 如果只按下 Enter, 则阻止默认的提交行为, 比如在表单中
if ( event . key === 'Enter' ) {
// 阻止默认行为
event . preventDefault ( ) ;
onclickSendMsg ( ) ;
return ;
}
}
//关闭聊天框
const onclickClose = ( ) => {
router . push ( { path : "/index" } )
. then ( ( ) => {
// 重新刷新页面
location . reload ( )
} )
}
//清除ai对话
const clearAiMsg = ( ) => {
sendAiChatContext . value = [ ] ;
chatStore . clearTypeMsg ( currentSelectUser . value ) ;
ElMessage ( {
message : "当前Ai会话清除成功" ,
type : "success" ,
duration : 2000 ,
} ) ;
}
//转换markdown
const toMarkDownHtml = ( text ) => {
marked . setOptions ( {
renderer : new marked . Renderer ( ) ,
highlight : function ( code , language ) {
return codeHandler ( code , language ) ;
} ,
pedantic : false ,
gfm : true , //允许 Git Hub标准的markdown
tables : true , //支持表格
breaks : true ,
sanitize : false ,
smartypants : false ,
xhtml : false ,
smartLists : true ,
}
) ;
//需要注意代码块样式
const soureHtml = marked ( text ) ;
nextTick ( ( ) => {
addCopyEvent ( ) ;
} )
return soureHtml ;
}
const toMarkDownHtml = ( text ) => {
marked . setOptions ( {
renderer : new marked . Renderer ( ) ,
highlight : function ( code , language ) {
return codeHandler ( code , language ) ;
} ,
pedantic : false ,
gfm : true , //允许 Git Hub标准的markdown
tables : true , //支持表格
breaks : true ,
sanitize : false ,
smartypants : false ,
xhtml : false ,
smartLists : true ,
}
) ;
//需要注意代码块样式
const soureHtml = marked ( text ) ;
nextTick ( ( ) => {
addCopyEvent ( ) ;
} )
return soureHtml ;
}
//code部分处理、高亮
const codeHandler = ( code , language ) => {
const codeIndex = parseInt ( Date . now ( ) + "" ) + Math . floor ( Math . random ( ) * 10000000 ) ;
//console.log(codeIndex,"codeIndex");
// 格式化第一行是右侧language和 “复制” 按钮;
if ( code ) {
const navCode = navHandler ( code )
try {
// 使用 highlight.js 对代码进行高亮显示
const preCode = hljs . highlightAuto ( code ) . value ;
// 将代码包裹在 textarea 中, 由于防止textarea渲染出现问题, 这里将 "<" 用 "<" 代替,不影响复制功能
let html = ` <pre class='hljs pre'><div class="header"><span class="language"> ${ language } </span><span class="copy" id=" ${ codeIndex } ">复制代码</span></div><div class="code-con"><div class="nav"> ${ navCode } </div><code class="code"> ${ preCode } </code></div></pre> ` ;
codeCopyDic . push ( { id : codeIndex , code : code } ) ;
// console.log(codeCopyDic.length);
return html ;
//<textarea style="position: absolute;top: -9999px;left: -9999px;z-index: -9999;" id="copy${codeIndex}">${code.replace(/<\/textarea>/g, "</textarea>")}</textarea>
} catch ( error ) {
console . log ( error ) ;
}
const codeHandler = ( code , language ) => {
const codeIndex = parseInt ( Date . now ( ) + "" ) + Math . floor ( Math . random ( ) * 10000000 ) ;
//console.log(codeIndex,"codeIndex");
// 格式化第一行是右侧language和 “复制” 按钮;
if ( code ) {
const navCode = navHandler ( code )
try {
// 使用 highlight.js 对代码进行高亮显示
const preCode = hljs . highlightAuto ( code ) . value ;
// 将代码包裹在 textarea 中, 由于防止textarea渲染出现问题, 这里将 "<" 用 "<" 代替,不影响复制功能
let html = ` <pre class='hljs pre'><div class="header"><span class="language"> ${ language } </span><span class="copy" id=" ${ codeIndex } ">复制代码</span></div><div class="code-con"><div class="nav"> ${ navCode } </div><code class="code"> ${ preCode } </code></div></pre> ` ;
codeCopyDic . push ( { id : codeIndex , code : code } ) ;
// console.log(codeCopyDic.length);
return html ;
//<textarea style="position: absolute;top: -9999px;left: -9999px;z-index: -9999;" id="copy${codeIndex}">${code.replace(/<\/textarea>/g, "</textarea>")}</textarea>
} catch ( error ) {
console . log ( error ) ;
}
}
}
//左侧导航栏处理
const navHandler = ( code ) => {
//获取行数
var linesCount = getLinesCount ( code ) ;
var currentLine = 1 ;
var liHtml = ` ` ;
while ( linesCount + 1 >= currentLine ) {
liHtml += ` <li class="nav-li"> ${ currentLine } </li> `
currentLine ++
}
let html = ` <ul class="nav-ul"> ${ liHtml } </ul> `
return html ;
}
const BREAK _LINE _REGEXP = /\r\n|\r|\n/g ;
const getLinesCount = ( text ) => {
return ( text . trim ( ) . match ( BREAK _LINE _REGEXP ) || [ ] ) . length ;
const navHandler = ( code ) => {
//获取行数
var linesCount = getLinesCount ( code ) ;
var currentLine = 1 ;
var liHtml = ` ` ;
while ( linesCount + 1 >= currentLine ) {
liHtml += ` <li class="nav-li"> ${ currentLine } </li> `
currentLine ++
}
let timerTip = null ;
let html = ` <ul class="nav-ul"> ${ liHtml } </ul> `
return html ;
}
const BREAK _LINE _REGEXP = /\r\n|\r|\n/g ;
const getLinesCount = ( text ) => {
return ( text . trim ( ) . match ( BREAK _LINE _REGEXP ) || [ ] ) . length ;
}
let timerTip = null ;
//倒计时显示tip
const startCountTip = ( ) => {
timerTip = setInterval ( ( ) => {
if ( isShowTipNumber . value > 0 ) {
isShowTipNumber . value -- ;
} else {
clearInterval ( timerTip ) ; // 倒计时结束
}
} , 1000 ) ;
} ;
const startCountTip = ( ) => {
timerTip = setInterval ( ( ) => {
if ( isShowTipNumber . value > 0 ) {
isShowTipNumber . value -- ;
} else {
clearInterval ( timerTip ) ; // 倒计时结束
}
} , 1000 ) ;
} ;
//代码copy事件
const addCopyEvent = ( ) => {
const copySpans = document . querySelectorAll ( '.copy' ) ;
const addCopyEvent = ( ) => {
const copySpans = document . querySelectorAll ( '.copy' ) ;
// 为每个 copy span 元素添加点击事件
copySpans . forEach ( span => {
//先移除,再新增
span . removeEventListener ( 'click' , clickCopyEvent ) ;
span . addEventListener ( 'click' , clickCopyEvent ) ;
} ) ;
}
const clickCopyEvent = async function ( event ) {
const spanId = event . target . id ;
console . log ( codeCopyDic , "codeCopyDic" )
console . log ( spanId , "spanId" )
await navigator . clipboard . writeText ( codeCopyDic . filter ( x => x . id === spanId ) [ 0 ] . code ) ;
ElMessage ( {
message : "代码块复制成功" ,
type : "success" ,
duration : 2000 ,
} ) ;
}
copySpans . forEach ( span => {
//先移除,再新增
span . removeEventListener ( 'click' , clickCopyEvent ) ;
span . addEventListener ( 'click' , clickCopyEvent ) ;
} ) ;
}
const clickCopyEvent = async function ( event ) {
const spanId = event . target . id ;
console . log ( codeCopyDic , "codeCopyDic" )
console . log ( spanId , "spanId" )
await navigator . clipboard . writeText ( codeCopyDic . filter ( x => x . id === spanId ) [ 0 ] . code ) ;
ElMessage ( {
message : "代码块复制成功" ,
type : "success" ,
duration : 2000 ,
} ) ;
}
< / script >
< template >
@@ -447,7 +464,6 @@ const getLastMessage = ((receiveId, itemType) => {
< li > < img src = "@/assets/chat_images/phone.png" / > < / li >
< li > < img src = "@/assets/chat_images/other.png" / > < / li >
< / ul >
< / div >
< div class = "middle" >
@@ -461,16 +477,13 @@ const getLastMessage = ((receiveId, itemType) => {
< / div >
< / div >
< div class = "user-list" >
< div v-for = "item in inputListDataStore" :key="item.key" class="user-div"
@click ="onclickUserItem(null, item.key)"
< div v-for = "item in inputListDataStore.filter(x=>x.type!=='user')" :key="item.key" class="user-div"
@click ="onclickUserItem(null, item.key)"
: class = "{ 'select-user-item': currentSelectUser === item.key }" >
< div class = "user-div-left" >
< img style = "height: 48px;width: 48px; " :src = "imageSrc(item.logo)" / >
< img style = "height: 48px;width: 48px; " :src = "imageSrc(item.logo)" / >
< div class = "user-name-msg" >
< p class = "font-name" > { { item . name } } < / p >
< p class = "font-name" > { { item . name } } < / p >
< p class = "font-msg" > { { getLastMessage ( null , item . key ) } } < / p >
< / div >
< / div >
@@ -478,12 +491,10 @@ const getLastMessage = ((receiveId, itemType) => {
10 : 28
< / div >
< / div >
< div v-for = "(item, i) in currentUserItem" :key="i" @click="onclickUserItem(item, 'user')" class="user-div"
: class = "{ 'select-user-item': currentSelectUser?.userId === item.userId }" >
< div class = "user-div-left" >
< img :src = "getChatUrl(item.userIcon)" / >
< div class = "user-name-msg" >
< p class = "font-name" > { { item . userName } } < / p >