2025-06-10 00:51:49 +08:00

521 lines
17 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.

<template>
<view class="container">
<uni-nav-bar
:title="$t('MT Community')"
:right-text="$t('Add')"
right-icon="plus"
@clickRight="goto('/pages/im/index')"
:border="false"
backgroundColor="F5f5f5">
</uni-nav-bar>
<view class="box-1">
<scroll-view
scroll-y
refresher-background="transparent"
style="height: 100%;"
@refresherrefresh="refresherrefresh"
:refresher-enabled="ajax.last_page > ajax.page"
:scroll-with-animation="false"
:refresher-triggered="scrollView.refresherTriggered"
>
<view style="padding: 20rpx;">
<image src="/static/im/bannar.png" style="width: 100%;" mode="widthFix"></image>
</view>
<uni-list>
<!-- 显示圆形头像 -->
<uni-list-item
:title="$t('Union customer service')"
thumb="/static/im/kefu.png"
thumbSize="lg"
showArrow
clickable
@click="show_kefu_popup"
></uni-list-item>
<uni-list-item
:title="$t('My team')"
thumb="/static/im/member.png"
thumbSize="lg"
showArrow
clickable
to="/pages/im/member"
></uni-list-item>
<template v-for="item in friend_list" >
<uni-list-chat
v-if="item.id"
:key="`${item.id}`"
:avatar-circle="true"
:title="item.display_name || item.name || item.nickname || item.email"
:avatar="getAvatarUrl(item)"
:note="item.last_msg ? (item.last_msg_type === 'image' ? '[图片]' : item.last_msg) : '暂无消息'"
:time="item.last_post_at ? date(item.last_post_at) : ''"
:badge-text="item.unread_count > 0 ? (item.unread_count > 99 ? '99+' : item.unread_count) : ''"
clickable
@click="handleContactClick(item)"
></uni-list-chat>
</template>
<!--这里显示最近联系人-->
</uni-list>
</scroll-view>
</view>
<uni-popup ref="kefuPopup" type="bottom"
border-radius="10px 10px 0 0"
backgroundColor="#fff"
>
<view class="" style="padding: 20rpx 20rpx 200rpx;">
<uni-section class="mb-10" :title="$t('Union customer service')">
<template v-slot:right>
<button type="default" @click="$refs.kefuPopup.close">
<uni-icons type="closeempty"></uni-icons>
</button>
</template>
</uni-section>
<uni-list>
<uni-list-chat
v-for="item in kefu_list"
:key="`kefu_${item.id}`"
:avatar-circle="true"
:title="item.nickname"
:avatar="getAvatarUrl(item)"
:note="(item.status === 'online' ? $t('Online') : $t('Offline')) + ' 10: 00-20: 00'"
clickable
link
@click="handleContactClick(item)"
></uni-list-chat>
</uni-list>
</view>
</uni-popup>
<zNavigation></zNavigation>
</view>
</template>
<script>
import zNavigation from '@/components/module/navigation.vue';
import {
mapState,
mapMutations
} from 'vuex';
import mattermost from '@/static/im/mattermost.js';
import base from '@/config/baseUrl'; // 导入base变量
export default {
components: {
zNavigation
},
data() {
return {
// 初始化数据
config: {
cdnurl: '',
// 其他初始化数据
},
friend_list: {},
kefu_list: [],
// 聊天列表数据
ajax: {
limit: 20, // 每页数量
last_page: 1, // 总页数
page: 0, // 当前页码
flag: false, // 请求开关
loading: false // 加载状态
},
scrollView: {
refresherTriggered: false // 下拉刷新状态
},
// 固定的客服ID列表
kefu_ids: ['nps4r44g1fbwtn83cck18hn3iy', 'jj46siuqu3dzujk5uj3wekbqxe'],
// 固定频道ID
fixed_channel_id: 'd5j75u5r5id77jmysszdtrijcr'
}
},
computed: {
...mapState(['userInfo']),
// 添加计算属性来安全访问 ajax 数据
hasMoreData() {
return this.ajax && this.ajax.last_page > this.ajax.page;
}
},
onShow() {
this.init();
},
onLoad() {
this.init();
},
onUnload() {
// 移除消息事件监听
mattermost.off('posted', this.handleNewMessage);
mattermost.off('post_deleted', this.handleMessageDeleted);
mattermost.off('post_edited', this.handleMessageEdited);
},
onPullDownRefresh() {
this.loadContactList().finally(() => {
uni.stopPullDownRefresh();
});
},
methods: {
...mapMutations(['setUserInfo']),
async init(){
try {
if(!this.userInfo.token || !this.userInfo.im_token){
return ;
}
// 获取用户信息
//const userinfo = await this.$http.get('/api/user/profile');
//this.setUserInfo(userinfo.data);
//console.log('用户信息:', this.userInfo);
// 获取初始化数据
const initRes = await this.$http.get('/api/common/init?lang=' + this.$i18n.locale);
if (initRes.code === 0) {
this.config = initRes.data;
this.config.cdnurl = initRes.data.cdnurl || "http://www.dxmt.io";
}
// 检查 Mattermost 连接状态
await mattermost.init();
// 加载联系人列表
this.loadContactList();
// 注册消息事件监听
mattermost.on('posted', this.handleNewMessage);
mattermost.on('post_deleted', this.handleMessageDeleted);
mattermost.on('post_edited', this.handleMessageEdited);
} catch (error) {
console.error('页面加载失败:', error);
uni.showToast({
title: '加载失败',
icon: 'none'
});
}
},
// 加载联系人列表
async loadContactList() {
if (this.ajax.flag || this.ajax.loading) return;
this.ajax.flag = true;
this.ajax.loading = true;
try {
// 获取固定联系人列表
const fixedContactsRes = await this.$http.get('/api/chat/get_friend_list');
let fixedContacts = [];
if (fixedContactsRes.code === 0 && Array.isArray(fixedContactsRes.data)) {
fixedContacts = fixedContactsRes.data;
}
// 获取私聊列表
const recent_contacts = await mattermost.getRecentContacts();
const sortedContacts = {};
for (const key in fixedContacts) {
if (Object.prototype.hasOwnProperty.call(fixedContacts, key)) {
sortedContacts[fixedContacts[key].id] = fixedContacts[key];
}
}
for (const key in recent_contacts) {
if (Object.prototype.hasOwnProperty.call(recent_contacts, key)) {
sortedContacts[key] = recent_contacts[key];
}
}
for (const key in sortedContacts) {
if (Object.prototype.hasOwnProperty.call(sortedContacts, key)) {
sortedContacts[key].unread_count = sortedContacts[key].unread_count || 0;
this.friend_list[key] = sortedContacts[key];
}
}
console.log('联系人列表:',this.friend_list);
// 更新列表
if (this.ajax.page === 0) {
//this.friend_list = sortedContacts;
} else {
//this.friend_list = [...this.friend_list, ...sortedContacts];
}
// 更新分页信息
this.ajax.page++;
this.ajax.last_page = Math.ceil(sortedContacts.length / this.ajax.limit);
} catch (error) {
console.error('加载联系人列表失败:', error);
uni.showToast({
title: '加载失败',
icon: 'none'
});
} finally {
this.ajax.flag = false;
this.ajax.loading = false;
this.scrollView.refresherTriggered = false;
}
},
// 处理联系人点击
async handleContactClick(item) {
try {
// 如果是私聊类型需要先查询直接消息频道ID
var url = await mattermost.talk_to_user(item); // 跳转到聊天页面
uni.navigateTo({
url: url
});
} catch (error) {
console.error('处理联系人点击失败:', error);
uni.showToast({
title: '操作失败',
icon: 'none'
});
}
},
// 处理新消息
async handleNewMessage(message) {
// 更新联系人列表中的消息
const updateContact = async(list) => {
let is_exist = false;
let post = JSON.parse(message.post);
for(var k in list){
const contact = list[k];
if(message.channel_type == 'D'){
//私聊消息
if(contact.id === post.user_id) {
is_exist = true;
list[k]= contact;
list[k]['last_msg']=post.message;
list[k]['last_msg_type']= post.type || 'text';
list[k]['last_msg_time']= post.create_at;
list[k]['unread_count']= contact.unread_count+=1;
}
}
if(message.channel_type == 'O'){
if(contact.id === post.channel_id) {
is_exist = true;
list[k]= contact;
list[k]['last_msg']=post.message;
list[k]['last_msg_type']= post.type || 'text';
list[k]['last_msg_time']= post.create_at;
list[k]['unread_count']= contact.unread_count+=1;
}
//群组消息
}
}
if(message.channel_type == 'D' && !is_exist){
console.log('新消息:', message,'不存在');
let send_user = await mattermost.getUserInfo(post.user_id);
//console.log('send_user',send_user);
let contact = send_user.data;
contact['last_msg']=post.message;
contact['last_msg_type']= post.type || 'text';
contact['last_msg_time']= post.create_at;
contact['unread_count']= 1;
list[k]= contact;
}
let _keys = Object.keys(list).sort((a, b) => {
const timeA = a.last_msg_time ? new Date(a.last_msg_time).getTime() : 0;
const timeB = b.last_msg_time ? new Date(b.last_msg_time).getTime() : 0;
return timeB - timeA;
});
let _list = {};
for (let index = 0; index < _keys.length; index++) {
_list[_keys[index]] = list[_keys[index]];
}
return _list;
};
this.friend_list = await updateContact(this.friend_list);
},
// 处理消息删除
handleMessageDeleted(message) {
const updateContact = (list) => {
return list.map(contact => {
if(contact.id === message.channel_id) {
return {
...contact,
last_msg: '此消息已删除',
last_msg_type: 'text',
last_msg_time: message.create_at
};
}
return contact;
});
};
this.friend_list = updateContact(this.friend_list);
},
// 处理消息编辑
handleMessageEdited(message) {
const updateContact = (list) => {
return list.map(contact => {
if(contact.id === message.channel_id) {
return {
...contact,
last_msg: message.message,
last_msg_time: message.update_at
};
}
return contact;
});
};
this.friend_list = updateContact(this.friend_list);
},
refresherrefresh() {
this.scrollView.refresherTriggered = true;
this.ajax.page = 0;
this.loadContactList();
},
// 显示客服列表
async show_kefu_popup() {
try {
// 从服务端获取客服列表
const res = await this.$http.get('/api/chat/get_kefu_list');
console.log('客服列表数据:', res);
if (res.code === 0 && res.data.code === 0) {
// 获取实际的客服列表数据
const kefuData = res.data.data;
if (!Array.isArray(kefuData) || kefuData.length === 0) {
throw new Error('客服列表为空');
}
// 处理客服列表数据
this.kefu_list = kefuData;
// 获取每个客服的在线状态
const statusPromises = this.kefu_list.map(async (kefu) => {
try {
// 使用管理员 API 获取状态
const statusRes = await mattermost.request({
to_server:'admin',
url: `/api/v4/users/${kefu.id}/status`,
method: 'GET'
});
if (statusRes.statusCode === 200 && statusRes.data) {
return {
id: kefu.id,
status: statusRes.data.status || 'offline'
};
}
} catch (error) {
console.error(`获取客服 ${kefu.id} 状态失败:`, error);
}
return {
id: kefu.id,
status: 'offline'
};
});
// 等待所有状态请求完成
const statusResults = await Promise.all(statusPromises);
// 更新客服状态
this.kefu_list = this.kefu_list.map(kefu => {
const status = statusResults.find(s => s.id === kefu.id);
return {
...kefu,
status: status ? status.status : 'offline'
};
});
this.$refs.kefuPopup.open();
} else {
throw new Error(res.data.msg || res.msg || '获取客服列表失败');
}
} catch (error) {
console.error('获取客服列表失败:', error);
uni.showToast({
title: error.message || '获取客服列表失败',
icon: 'none'
});
}
},
goto(url,type){
if(type==2){
return uni.switchTab({url:url})
}
if(type==1){
return uni.navigateBack({delta:url});
}
uni.navigateTo({
url:url
})
},
/**
* JavaScript 仿 PHP date() 函数
* @param {string} format - 格式字符串
* @param {number|Date} [timestamp] - 可选的时间戳或Date对象默认为当前时间
* @return {string} 格式化后的日期时间字符串
*/
date(format, timestamp) {
// 处理时间参数
if(!timestamp){
return '';
}
let date = timestamp;
if(!(timestamp instanceof Date)){
date = Date(timestamp * 1000);
if(timestamp > 1749000000){
date = Date(timestamp);
}
}
// 定义替换规则
const replacements = {
// 日
'd': () => String(date.getDate()).padStart(2, '0'),
'j': () => date.getDate(),
// 月
'm': () => String(date.getMonth() + 1).padStart(2, '0'),
'n': () => date.getMonth() + 1,
// 年
'Y': () => date.getFullYear(),
'y': () => String(date.getFullYear()).slice(-2),
// 时间
'H': () => String(date.getHours()).padStart(2, '0'),
'i': () => String(date.getMinutes()).padStart(2, '0'),
's': () => String(date.getSeconds()).padStart(2, '0'),
// 其他
'w': () => date.getDay(), // 星期几 (0-6)
'A': () => date.getHours() >= 12 ? 'PM' : 'AM',
'a': () => date.getHours() >= 12 ? 'pm' : 'am',
};
// 执行替换
let result = '';
for (let i = 0; i < format.length; i++) {
const char = format[i];
if (char in replacements) {
result += replacements[char]();
} else {
result += char;
}
}
return result;
},
getAvatarUrl(item) {
if(!item.avatar){
return this.config.cdnurl+'/static/img/avatar.png';
}
return (item.avatar.startsWith('http') ? '' : this.config.cdnurl) + item.avatar;
}
}
}
</script>
<style lang="scss" scoped>
.container {
height: 100vh;
background-color: #F5F5F5;
font-family: "poppins";
.box-1 {
flex: 1;
height: calc(100vh - var(--window-top) - 44px);
}
}
</style>