refactor(im): 重构聊天面板UI组件并修复多处交互问题

- 将头像组件从 dicebear 外链改为本地首字+底色生成,新增 avatarColor 工具函数
- 重构订单卡片(footPrint.vue)布局,优化商品与订单信息展示结构
- 重构店铺详情卡片(storeDetail.vue),统一信息行与评分行样式
- 移除消息气泡中的昵称时间行,调整气泡背景色与头像内边距
- 修复 face 变量名与全局组件 <face> 冲突导致头像URL被当作标签渲染
- 将 unload 事件监听替换为 pagehide,兼容 Chrome 弃用策略
- 修复搜索关键词未转义正则元字符导致输入特殊字符时抛异常
- 修复 WebSocket onMessage 对无 text 字段的推送消息误调用 JSON.parse
- 将 setup 中同步调用全局 mixin 方法移至 onMounted,避免代理未就绪
- 商品/订单图片统一添加 onerror 默认图兜底
- 聊天列表消息预览支持 emoji 图片渲染(textReplaceEmoji)
- 搜索框增加 clearable 属性及"没有找到相关好友"空态提示
- 锁定 element-plus 版本为 2.14.1 防止自动升级引入兼容问题
- 日期格式化分隔符由斜杠改为横杠
- 提现设置页新增提现规则说明字段,满足微信审核公示要求
This commit is contained in:
pikachu1995@126.com
2026-10-29 10:56:28 +08:00
parent 68dfae9a62
commit 97e611c072
14 changed files with 473 additions and 246 deletions

View File

@@ -6,7 +6,7 @@
<el-header height="118px" class="logo-header">
<div class="user-login" v-popover:usercard>
<div class="user-box">
<face :text="face"></face>
<face :text="userFace"></face>
</div>
</div>
<p class="user-status">
@@ -66,7 +66,8 @@ const userStore = useUserStore();
const notifyStore = useNotifyStore();
const settingsStore = useSettingsStore();
const face = computed(() => userStore.face);
// 不能命名为 face:会遮蔽 <face> 组件解析,头像 URL 会被当作标签名渲染
const userFace = computed(() => userStore.face);
const name = computed(() => userStore.name);
const id = computed(() => userStore.id);
const defaultAvatar = computed(() => appStore.defaultAvatar);
@@ -107,8 +108,8 @@ function setApplyNum () {
<style lang="less" >
.main-layout {
position: fixed;
width: 80%;
height: 80%;
width: 88%;
height: 92%;
top: 0;
right: 0;
bottom: 0;
@@ -119,8 +120,8 @@ function setApplyNum () {
border-radius: 5px;
&.full-mode {
width: 1200px;
height: 85%;
width: 92%;
height: 96%;
border-radius: 0;
}

View File

@@ -10,7 +10,7 @@
<el-header height="60px" class="header">
<div class="user-login">
<div class="user-box">
<face :text="face" :name="name" class="user-face"></face>
<face :text="userFace" :name="name" class="user-face"></face>
</div>
</div>
<p class="user-status">
@@ -20,7 +20,7 @@
</el-header>
<el-header height="60px" class="header">
<div class="from-search">
<el-input v-model="input" :prefix-icon="$legacyIcon('el-icon-search')" placeholder="搜索好友" size="small" />
<el-input v-model="input" :prefix-icon="$legacyIcon('el-icon-search')" placeholder="搜索好友" size="small" clearable />
</div>
</el-header>
<!-- <el-header height="118px" class="logo-header">
@@ -58,6 +58,10 @@
暂无聊天消息
</p>
<p v-show="loadStatus === 1 && talkNum > 0 && userTalkItem.length === 0" class="empty-data">
没有找到相关好友
</p>
<p v-show="loadStatus === 1 && talkNum > 0" class="main-menu">
<span class="title">对话记录({{ talkNum }})</span>
@@ -108,7 +112,7 @@
</div>
</div>
<div class="talk-message">
<span v-if="item.lastMessageType === 'MESSAGE'">{{ item.lastTalkMessage }}</span>
<span v-if="item.lastMessageType === 'MESSAGE'" v-html="textReplaceEmoji(item.lastTalkMessage)"></span>
<span v-if="item.lastMessageType === 'GOODS'">[商品链接]</span>
<span v-if="item.lastMessageType === 'ORDER'">[订单链接]</span>
</div>
@@ -156,6 +160,7 @@ import {
getCurrentInstance,
nextTick,
onBeforeUnmount,
onMounted,
ref,
watch,
} from "vue";
@@ -174,6 +179,7 @@ import {
} from "@/api/chat";
import { ServeDeleteContact, ServeEditContactRemark } from "@/api/contacts";
import { beautifyTime } from "@/utils/functions";
import { textReplaceEmoji } from "@/utils/emojis";
import { formatTalkItem, findTalkIndex, getCacheIndexName } from "@/utils/talk";
import { setToken } from "@/utils/auth";
import { syncImTokenFromRoute } from "@/utils/im-mode";
@@ -243,7 +249,9 @@ const unreadNum = computed(() => talkStore.unreadNum);
const talkNum = computed(() => talkStore.talkNum);
// 原 vuex mapState
const face = computed(() => userStore.face);
// 注意:不能命名为 face——会与全局注册的 <face> 组件绑定冲突,
// 模板里的 <face> 标签会被解析成这个 computed 的值(头像 base64 字符串)当作标签名渲染
const userFace = computed(() => userStore.face);
const name = computed(() => userStore.name);
const talks = computed(() => talkStore.items);
const id = computed(() => userStore.id);
@@ -655,10 +663,11 @@ watch(talkItems, (val) => {
});
// 搜索用户的时候 根据当前用户表进行模糊搜索
watch(input, (val, oldVal) => {
console.log(val, oldVal);
watch(input, (val) => {
if (val) {
let str = ["", ...val, ""].join(".*");
// 转义正则元字符,避免输入 [ 等字符时构造 RegExp 抛异常
const keyword = val.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
let str = ["", ...keyword, ""].join(".*");
let reg = new RegExp(str);
userTalkItem.value = talkItems.value.filter((item) =>
reg.test(item.name)
@@ -703,8 +712,10 @@ onBeforeRouteUpdate((to, from, next) => {
// 原 beforeCreate
syncImTokenFromRoute(route, setToken);
// 原 async created(保持 await 顺序,但用 IIFE 避免 setup 变为异步)
(async () => {
// 原 async created。不能在 setup 同步执行:全局 mixin 的方法(initialize/
// loadUserSetting)在 finishComponentSetup 的 applyOptions 阶段才挂到公共代理上,
// 即 setup() 返回之后。故放到 onMounted 里执行。
onMounted(async () => {
await proxy.initialize();
await proxy.loadUserSetting();
// 初始化商品参数
@@ -714,7 +725,7 @@ syncImTokenFromRoute(route, setToken);
* 所以创建会话 并请求用户列表
* 如果没有id说明当前商家登录 直接请求用户列表
*/
})();
});
onBeforeUnmount(() => {
document.title = title;
@@ -747,8 +758,20 @@ onBeforeUnmount(() => {
flex-shrink: 0;
height: 40px;
:deep(.el-input .el-input__inner ) {
border-radius: 20px;
:deep(.el-input__wrapper) {
border-radius: 16px;
background-color: #f5f6f7;
box-shadow: none;
transition: box-shadow 0.2s;
&.is-focus {
background-color: #fff;
box-shadow: 0 0 0 1px #409eff inset;
}
}
:deep(.el-input__inner) {
font-size: 12px;
}
}
@@ -1058,6 +1081,12 @@ onBeforeUnmount(() => {
.talk-message {
font-size: 12px;
color: #8f959e;
:deep(img) {
width: 16px;
height: 16px;
vertical-align: middle;
}
}
.user-face {