Files
frontend/apps/web-antd/src/views/mp/modules/wx-msg/wx-msg.vue
2025-11-09 17:30:37 +08:00

198 lines
5.5 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<!--
- Copyright (C) 2018-2019
- All rights reserved, Designed By www.joolun.com
芋道源码
移除暂时用不到的 websocket
代码优化补充注释提升阅读性
-->
<script lang="ts" setup>
import type { User } from './types';
import type { Reply } from '#/views/mp/modules/wx-reply';
import { nextTick, onMounted, reactive, ref, unref } from 'vue';
import { Button, message, Spin } from 'ant-design-vue';
import { getMessagePage, sendMessage } from '#/api/mp/message';
import { getUser } from '#/api/mp/user';
import profile from '#/assets/imgs/profile.jpg';
import { ReplyType, WxReplySelect } from '#/views/mp/modules/wx-reply';
import MsgList from './msg-list.vue';
defineOptions({ name: 'WxMsg' });
const props = defineProps({
userId: {
type: Number,
required: true,
},
});
const accountId = ref(-1); // 公众号ID需要通过userId初始化
const loading = ref(false); // 消息列表是否正在加载中
const hasMore = ref(true); // 是否可以加载更多
const list = ref<any[]>([]); // 消息列表
const queryParams = reactive({
pageNo: 1, // 当前页数
pageSize: 14, // 每页显示多少条
accountId,
});
const user: User = reactive({
nickname: '用户', // 由于微信不再提供昵称,直接使用"用户"展示
avatar: profile,
accountId, // 公众号账号编号
});
// ========= 消息发送 =========
const sendLoading = ref(false); // 发送消息是否加载中
const reply = ref<Reply>({
type: ReplyType.Text,
accountId: -1,
articles: [],
}); // 微信发送消息
const replySelectRef = ref<InstanceType<typeof WxReplySelect> | null>(null); // WxReplySelect组件ref用于消息发送成功后清除内容
const msgDivRef = ref<HTMLDivElement | null>(null); // 消息显示窗口ref用于滚动到底部
/** 完成加载 */
onMounted(async () => {
const data = await getUser(props.userId);
user.nickname = data.nickname?.length > 0 ? data.nickname : user.nickname;
// API 返回的数据可能包含 headImageUrl但类型定义中没有使用类型断言
const userData = data as typeof data & { headImageUrl?: string };
user.avatar =
userData.headImageUrl && userData.headImageUrl.length > 0
? userData.headImageUrl
: user.avatar;
accountId.value = data.accountId;
reply.value.accountId = data.accountId;
refreshChange();
});
/** 执行发送 */
async function sendMsg() {
if (!unref(reply)) {
return;
}
// 公众号限制:客服消息,公众号只允许发送一条
if (
reply.value.type === ReplyType.News &&
reply.value.articles &&
reply.value.articles.length > 1
) {
reply.value.articles = [reply.value.articles[0]];
message.success('图文消息条数限制在 1 条以内,已默认发送第一条');
}
// 注意sendMessage API 需要 openid但这里传入的是 userId
// 这可能是后端 API 的特殊处理,使用类型断言绕过类型检查
const data = await sendMessage({
userId: props.userId,
...reply.value,
} as any);
sendLoading.value = false;
list.value = [...list.value, data];
await scrollToBottom();
// 发送后清空数据
replySelectRef.value?.clear();
}
/** 加载更多 */
function loadMore() {
queryParams.pageNo++;
getPage(queryParams, null);
}
/** 获取分页数据 */
async function getPage(page: any, params: any = null) {
loading.value = true;
const dataTemp = await getMessagePage(
Object.assign(
{
pageNo: page.pageNo,
pageSize: page.pageSize,
userId: props.userId,
accountId: page.accountId,
},
params,
),
);
const scrollHeight = msgDivRef.value?.scrollHeight ?? 0;
// 处理数据
const data = dataTemp.list.reverse();
list.value = [...data, ...list.value];
loading.value = false;
if (data.length < queryParams.pageSize || data.length === 0) {
hasMore.value = false;
}
queryParams.pageNo = page.pageNo;
queryParams.pageSize = page.pageSize;
// 滚动到原来的位置
if (queryParams.pageNo === 1) {
// 定位到消息底部
await scrollToBottom();
} else if (data.length > 0) {
// 定位滚动条
await nextTick();
if (scrollHeight !== 0 && msgDivRef.value) {
msgDivRef.value.scrollTop =
msgDivRef.value.scrollHeight - scrollHeight - 100;
}
}
}
/** 刷新消息 */
function refreshChange() {
getPage(queryParams);
}
/** 定位到消息底部 */
async function scrollToBottom() {
await nextTick();
if (msgDivRef.value) {
msgDivRef.value.scrollTop = msgDivRef.value.scrollHeight;
}
}
</script>
<template>
<ContentWrap>
<Spin :spinning="loading">
<div class="bg-background ml-2 mr-2 h-12 overflow-auto" ref="msgDivRef">
<!-- 加载更多 -->
<div v-if="!loading">
<div
class="cursor-pointer py-5 text-center"
v-if="hasMore"
@click="loadMore"
>
<span class="text-foreground">点击加载更多</span>
</div>
<div class="py-5 text-center" v-if="!hasMore">
<span class="text-foreground">没有更多了</span>
</div>
</div>
<!-- 消息列表 -->
<MsgList :list="list" :account-id="accountId" :user="user" />
</div>
</Spin>
<Spin :spinning="sendLoading">
<div class="p-[10px]">
<WxReplySelect ref="replySelectRef" v-model="reply" />
<Button type="primary" class="float-right mb-2 mt-2" @click="sendMsg">
发送(S)
</Button>
</div>
</Spin>
</ContentWrap>
</template>