這里給大家分享我在網上總結出來的一些知識,希望對大家有所幫助
Vue3+TS(uniapp)手擼一個聊天頁面
前言
最近在自己的小程式中做了一個智能客服,API使用的是云廠商的API,然后聊天頁面...嗯,找了一下關于UniApp(vite/ts)版本的好像不多,有一個官方的但其中的其他代碼太多了,去看懂再洗掉那些對我無用的代碼不如自己手擼一個,先看效果:
好,下面開始介紹如何一步一步實作
重難點調研
1. 如何撰寫氣泡

可以發現一般的氣泡是有個“小箭頭”,一般是指向用戶的頭像,所以這里我們的初步思路就是通過before與after偽類來放置這個小三角形,這個小三角形通過隱藏border的其余三邊來實作,

然后其中一個細節就是聊天氣泡的最大寬度不超過對方的頭像,超過就換行,這個簡單,設定一個max-width: cacl(100vw - XX)就可以了
2. 如何撰寫輸入框
考慮到用戶可能輸入多行文字,這里使用的是<textarea>標簽,點開微信發個訊息試試,發現它是自適應的,這里去調研了解了一下,發現小程式自帶組件有這個實作,好,那直接用:

然后我們繼續注意到發送按鈕與輸入框的底線保持水平,這個flex里有對應屬性可以實作,跳過...
3.如何實作滾動條始終居于底部
當聊天訊息較多時,我們發現我們繼續輸入訊息,頁面并沒有更新(滾動),打開微信聊天框一看,當訊息過多時,你發一條訊息,頁面就自動滾動到了最新的訊息,這又是怎實作的呢?
繼續調研,發現小程式自帶的<scroll-view>標簽中有個屬性scroll-into-view可以自動跳轉:
<scroll-view scroll-y="true" :scroll-into-view="`msg${messages.length-1}`" :scroll-with-animation="true">
<view :id="`msg${index}`" v-for="(msg, index) in messages" :key="msg.time">
<view >
略
</view>
</view>
</scroll-view>
概述
簡單分析下來好像一點都不難,如下是我的檔案串列,話不多說,開始擼代碼!
chat ├─ chat.vue ├─ leftBubble.vue └─ rightBubble.vue
左氣泡模塊
左氣泡模塊就是剛剛分析的那一部分,然后增加一點點細節,如下:
<template>
<view >
<view >
<image :src="https://www.cnblogs.com/smileZAZ/archive/2023/05/11/props.avatarUrl"></image>
</view>
<view >
<view >
<text>{{ props.message }}</text>
</view>
</view>
</view>
</template>
<script setup lang="ts">
import { userDefaultData } from "@/const";
interface propsI {
message: string;
avatarUrl: string;
}
const props = withDefaults(defineProps<propsI>(), {
avatarUrl: userDefaultData.avatarUrl,
});
</script>
<style lang="scss" scoped>
.left-bubble-container {
margin: 10px 0;
display: flex;
.left {
image {
height: 50px;
width: 50px;
border-radius: 5px;
}
}
}
.bubble {
max-width: calc(100vw - 160px);
min-height: 25px;
border-radius: 10px;
background-color: #ffffff;
position: relative;
margin-left: 20px;
padding: 15px;
text {
height: 25px;
line-height: 25px;
}
}
.bubble::before {
position: absolute;
top: 15px;
left: -20px;
content: "";
width: 0;
height: 0;
border-right: 10px solid #ffffff;
border-bottom: 10px solid transparent;
border-left: 10px solid transparent;
border-top: 10px solid transparent;
}
</style>
右氣泡模塊
右氣泡模塊我們需要將三角形放在右邊,這個好實作,然后這整個氣泡我們需要讓它處于水平居右,所以這里我使用了:
display: flex; direction: rtl;
這個屬性,但使用的程序中發現氣泡中的內容(符號與文字)會出現翻轉,“遇事不決,再加一層”,所以我們在內容節點外再套一層:
<span style="direction: ltr; unicode-bidi: bidi-override">
<text>{{ props.message }}</text>
</span>
然后繼續增加一點點細節:
<template>
<view >
<view >
<image :src="https://www.cnblogs.com/smileZAZ/archive/2023/05/11/props.avatarUrl"></image>
</view>
<view >
<view >
<span style="direction: ltr; unicode-bidi: bidi-override">
<text>{{ props.message }}</text>
</span>
</view>
</view>
</view>
</template>
<script setup lang="ts">
import { userDefaultData } from "@/const";
interface propsI {
message: string;
avatarUrl: string;
}
const props = withDefaults(defineProps<propsI>(), {
avatarUrl: userDefaultData.avatarUrl,
});
</script>
<style lang="scss" scoped>
.left-bubble-container {
display: flex;
direction: rtl;
margin: 10px 0;
.right {
image {
height: 50px;
width: 50px;
border-radius: 5px;
}
}
}
.bubble {
max-width: calc(100vw - 160px);
min-height: 25px;
border-radius: 10px;
background-color: #ffffff;
position: relative;
margin-right: 20px;
padding: 15px;
text-align: left;
text {
height: 25px;
line-height: 25px;
}
}
.bubble::after {
position: absolute;
top: 15px;
right: -20px;
content: "";
width: 0;
height: 0;
border-right: 10px solid transparent;
border-bottom: 10px solid transparent;
border-left: 10px solid #ffffff;
border-top: 10px solid transparent;
}
</style>
輸入模塊
沒啥說的,需要注意的是:Button記得防抖
<view > <view > <textarea auto-height fixed="true" confirm-type="send" v-model="input" @confirm="submit" /> </view> <button style=" width: 70px; height: 40px; line-height: 34px; margin: 0 10px; background-color: #ffffff; border: 3px solid #0256ff; color: #0256ff; " @click="submit" > 發送 </button>

整體
1)考慮如何存盤訊息
這里僅考慮記憶體中如何存盤,不考慮本地存盤,后續思考中會聊到,
export interface messagesI {
left: boolean;
text: string;
time: number;
}
如上是訊息串列中的一項,為了區分是渲染到左氣泡還是右氣泡,這里用left來區分了一下;
const messages: Ref<messagesI[]> = ref([]);
2)如何推薦訊息
這邊我封裝的服務端介面是這樣的:
mutation chat{
customerChat(talk: "你好啊"){
knowledge
text
recommend
}
}
recommend是用戶可能輸入了錯誤的訊息,這里是預測用戶的輸入字串,所以我們需要在得到這個字串后直接顯示,然后用戶可以一鍵通過這條訊息回復:
function submit(){
// 略...
const finalMsg = receive?.knowledge || receive?.text || "你是否想問: " + receive?.recommend;
// 略...
if (receive?.recommend) {
input.value = https://www.cnblogs.com/smileZAZ/archive/2023/05/11/receive?.recommend;
} else {
input.value ="";
}
}
如上,得益于Vue框架,這里實作起來也非常簡單,當用戶提交之后,如果有推薦的訊息,就直接修改input.value從而修改輸入框的文字;如果沒有就直接清空方便下一次輸入,
接下來繼續增加一點點細節(chat.vue檔案)
<template>
<view >
<view >
<!-- https://github.com/wepyjs/wepy-wechat-demo/issues/7 -->
<scroll-view scroll-y="true" :scroll-into-view="`msg${messages.length-1}`" :scroll-with-animation="true">
<view :id="`msg${index}`" v-for="(msg, index) in messages" :key="msg.time">
<view >
<left-bubble v-if="msg.left" :message="msg.text" :avatar-url="meStore.user?.avatarUrl"></left-bubble>
<right-bubble v-else :message="msg.text" :avatar-url="logoUrl"></right-bubble>
</view>
</view>
</scroll-view>
</view>
<view >
<view >
<textarea
auto-height
fixed="true"
confirm-type="send"
v-model="input"
@confirm="submit"
/>
</view>
<button
style="
width: 70px;
height: 40px;
line-height: 34px;
margin: 0 10px;
background-color: #ffffff;
border: 3px solid #0256ff;
color: #0256ff;
"
@click="submit"
>
發送
</button>
</view>
</view>
</template>
<script setup lang="ts">
import { ref, type Ref } from "vue";
import leftBubble from "./leftBubble.vue";
import rightBubble from "./rightBubble.vue";
import type { messagesI } from "./chat.interface";
import { chatGQL } from "@/graphql/me.graphql";
import { useMutation } from "villus";
import { logoUrl } from "@/const";
import { useMeStore } from "@/stores/me.store";
const meStore = useMeStore();
const messages: Ref<messagesI[]> = ref([]);
const input = ref("");
async function submit() {
if (input.value =https://www.cnblogs.com/smileZAZ/archive/2023/05/11/=="") return;
messages.value.push({
left: true,
text: input.value,
time: new Date().getTime(),
});
const { execute } = useMutation(chatGQL);
const { error, data } = await execute({ talk: input.value })
if (error) {
uni.showToast({
title: `加載錯誤`,
icon: "error",
duration: 3000,
});
throw new Error(`加載錯誤: ${error}`);
}
const receive = data?.customerChat;
const finalMsg = receive?.knowledge || receive?.text || "你是否想問: " + receive?.recommend;
messages.value.push({
left: false,
text: finalMsg,
time: new Date().getTime(),
});
if (receive?.recommend) {
input.value = https://www.cnblogs.com/smileZAZ/archive/2023/05/11/receive?.recommend;
} else {
input.value ="";
}
}
</script>
<style lang="scss" scoped>
.chat-container {
.msg-container {
padding: 20px 5px 100px 5px;
height: calc(100vh - 120px);
scroll-view {
height: 100%;
}
}
.bottom-input {
display: flex;
align-items: flex-end;
position: fixed;
bottom: 0px;
background-color: #fbfbfb;
padding: 20px;
box-shadow: 0px -10px 30px #eeeeee;
.textarea-container {
background-color: #ffffff;
padding: 10px;
textarea {
width: calc(100vw - 146px);
background-color: #ffffff;
}
}
}
}
</style>
思考
如何保存到本地,然后每次加載最新訊息,然后向上滾動進行懶加載?
我這里沒有實作該功能,畢竟只是一個客服,前端沒必要保存訊息記錄到本地如Localstorage,
這里拋磚引玉,想到了一個最基礎的資料結構--鏈表,用Localstorage-key/value的形式來實作訊息佇列在本地的多段存盤:

當然,有效性有待驗證,這里僅僅屬于一些想法
最后
然后,我擼了小半天的頁面,準備給朋友看看來著,他告訴我微信小程式自帶一個客服系統,只需要讓button的open-type屬性等于contract;
本文轉載于:
https://juejin.cn/post/7224059698911641658
如果對您有所幫助,歡迎您點個關注,我會定時更新技術檔案,大家一起討論學習,一起進步,

轉載請註明出處,本文鏈接:https://www.uj5u.com/qiye/552260.html
標籤:其他
下一篇:返回列表

