vue ^2.6.14
@vue/cli-plugin-babel": "~5.0.0"
@vue/cli-plugin-eslint": "~5.0.0"
@vue/cli-plugin-router": "~5.0.0"
@vue/cli-plugin-vuex": "~5.0.0"
@vue/cli-service": "~5.0.0"
npm install i3v-chat-system@latest
npm install -D @babel/plugin-proposal-private-methods
# 在babel.config.cjs文件添加以下配置
plugins: ["@babel/plugin-proposal-private-methods"],
import { $tinode } from "i3v-chat-system";
// 把$tinode放在data中,并且以后一直使用它:
tinode: $tinode;
// 初始化tinode,填写配置(只应该执行一次)
this.tinode.init({
appName: "应用名字如I3VIM", // 必填,找管理员获取
host: "101.35.189.186:6060", // 必填 使用wss必须填https域名如home.i3vsoft.com:6060
apiKey: "应用的key", // 必填,找管理员获取
transport: "ws", // 或者wss
secure: false, // 如果用wss则填true
persist: false, // 是否使用indexedDB缓存
});
// 禁用 console.log
this.$tinode.instance.enableLogging(false);
// 检查是否连接到聊天服务器
const isConnected = await this.tinode.connect();
if (!isConnected) {
console.log("无法连接到聊天服务器");
return;
}
// 通过用户名和密码登录
const { code } = await this.tinode.loginBasic(
username, // 登录名一般是手机号
password // 密码一般是sass系统或者业务系统的token
);
if (code === 200) {
console.log("登录成功,可以使用聊天界面组件了");
}
this.tinode.instance.disconnect();
// 最完整的聊天界面
// 除了抬头,分为左中右三列模式:左侧是 消息、通讯录、会议;中列是会话列表;右列是消息界面和输入界面
import { I3vChat } from "i3v-chat-system"
components:{
I3vChat
}
// 判断是否已经授权
const isAuthenticated = this.tinode.isAuthenticated()
// 输出聊天会话列表
<I3vChat
v-if="isAuthenticated"
:tinode="tinode"
:getCompanyAndUserDataTree="getCompanyAndUserDataTree"
:getProjectDataList="getProjectDataList"
:getProjectUserDataTree="getProjectUserDataTree"
:getRobotDataListByIds="getRobotDataListByIds"
:getUserByIds="getUserByIds"
:uploader="uploader"
:downloader="downloader"
:globalSearcher="globalSearcher"
:topicFilter="topicFilter"
:typeAlias="typeAlias"
:msgAlias="msgAlias"
:meetingUrl='meetingUrl'
@select-topic="onSelectTopic"
ref='chat'
/>
// 返回企业组织和用户混合树形数组;用户必须有个属性isUser,必须包含id,name,children(为保证id唯一性,插入uniqId用于树形组件)
async function getCompanyAndUserDataTree () {
return [
{
uniqId:'x1',
id: 'id1',
name: 'xxx公司',
isUser: false,
children: [
{
uniqId:'x2',
id: 'id2',
name: '张三',
isUser: true
}
// ... 重复上级结构
]
}
]
}
// 返回项目内组织和用户混合树形数组;用户必须有个属性isUser,必须包含id,name,children(为保证id唯一性,插入uniqId作为树形结构的id)
async function getProjectUserDataTree (projectId) {
return [
{
uniqId:'x1',
id: 'id',
name: 'xxx公司',
isUser: false,
avatar: '头像网址',
children: [
{
// ... 重复上级结构
}
]
}
]
}
// 返回我参与的项目列表,必须包含id,name
async function getProjectDataList () {
return [
{
id: 'id1',
name: '项目1',
avatar: '头像网址'
}
// ...
]
}
// 根据消息通告Id数组从业务系统获取业务数据的方法,接受两个参数:sendIds(消息的id数组),topic(主题,一般是聊天机器人对象)
async function getRobotDataListByIds (sendIds = ['id1','id2'], topicObj={}) {
// 一般使用annountCement/getInfoByIds根据sendIds获取数据
// 返回数组,结构如下
return [
{
id: 'id1',
name: 'xxx',
content: '消息数据的json字符串对象',
typeId: '类型id',
belongId: '所属id',
belongData: {
//...'数据对象'
},
isRead: true // 是否已读
// ...
}
]
}
// 根据用户id数组返回用户信息.入参是用户id数组,返回值如下
async function getUserByIds (ids = ['id1','id2']) {
return [
{
id: 'id1',
name: 'xxx',
avatar: 'http://www.xxx.com/avatar.jpg',
department: '广州君和信息技术有限公司-广州总部-产品部-产品设计2部',
title: '产品经理'
}
]
}
// 上传附件的接口, 接收参数 file
// 必须返回文件链接;失败返回false
async function uploader (file) {
// ...上传逻辑
return {
id: "fileId",
src: "https://xxx.xxx.xxx/xxx/file.jpg" // 分片上传的文件不会有这个值
} // 如果失败,应该抛出错误 throw new Error('errmessage')
}
// 下载、打开文件的接口
// 入参是一个文件id列表或者文件链接列表,业务系统负责将它们下载
async function downloader (ids=[], forceZip = false) {
// ...根据文件id数组下载文件
// 返回数组,数组的格式是:[ src, src, src ]
// 如果forceZip为true,则压缩为zip文件直接下载
}
// 全局搜索接口, 接收参数对象 { searchType: "", keyword: "", tags: [] }
// searchType的值:
// "user": 从企业查找用户
// "file": 从聊天系统上传网盘查找文件
// "": 分别查找上述内容
// keyword的值:
// 当searchType === 'user',模糊查找手机和姓名
// 当searchType === 'file',模糊查找聊天系统上传网盘的文件
// tags的值:
// 当searchType === 'file',限定从网盘查找文件的分类
async function globalSearcher ({ searchType, keyword, tags }) {
return {
user: { total:1, rows: [ { id, name, avatar, department, title } ] } ,
file: { total:1, rows: [{ id, name, user: {id, avatar, name}, size }] }
}
}
// 过滤会话列表的函数,它被用于作为会话列表的filter参数。
// 参数是会话对象(参考下面topicObj说明)
function topicFilter (topicObj)=> {
return true
// return (topicObj && topic.isGroupType === "function") && topic.isGroupType() // 示例: 只显示群组
// return (topicObj && topic.isP2PType === "function") && topic.isP2PType() // 示例: 只显示个人
// return topicObj && robotListIds.includes(topic.topic) // 示例: 只显示机器人,robotListIds是机器人id数组
// return topicObj && topicObj.tags().includes('group:${groupId}') // 实例: 只显示包含指定项目标签的群组
}
typeAlias: String, // dataList元素中"busType"字段的别名;如果dataList元素里业务类型字段名是businessType,可以通过设置 typeAlias: "businessType"做映射
msgAlias: String, // dataList元素中"msgContent"字段的别名;如果dataList元素里消息内容字段名是msgContent,可以通过设置 typeAlias: "msgContent"做映射
// 会议系统的链接,需要能够接受token一键登录, 格式为:
// http://xxx.xxx.xxx?token={{sassToken}}
// 其中的{{sassToken}}是要被替换掉的内容,由业务系统通过$tinode.setState({{sassToken}})设置
meetingUrl:String,
function setTopicId(topicId){
// 主动设置当前会话
// 个人会话topicId格式是`usr${userId}`,如:usr123456
// 群会话的topicId格式是`grp${groupId}`,如:grp123456
}
这个 slot 会替换 MessageAndInput 的消息显示组件
<I3vChat
// ....
>
<div v-if="needMyOwnSlot">我的显示组件</div>
</I3vChat>
{
topic: 'usr123334', // 'grp55555' 会话的id,个人以usr开头,群组以grp开头
public: { fn: "名称" ,note: "备注"},
unread: 0, // 未读消息数量
isGroupType () { return true }, // 是否是群组
isP2PType () { return true }, // 是否是个人
isMeType () { return true }, // 是否是我自己
// ...
}
import { BizDataDisplay } from "i3v-chat-system"
// 显示系统通知界面,包括任务通知等
<BizDataDisplay
:tinode="tinode"
:topicId="topicId"
:dataList="dataList"
:uid="myUserBizId"
:typeAlias="typeAlias"
:msgAlias="msgAlias"
@on-emit="handleEmit"
/>
// 参数说明
topicId: 聊天机器人id
uid: 业务系统的用户id
dataList是从业务系统传来的数据,必须包含以下字段:
{
id: String, // 消息的id,必须是唯一值
isRead: Boolean, // 是否已读
busType: String, // 业务类型,如'2','approval'等
msgContent: String // 包含消息内容的JSON字符串
creatTime: String // 创建时间
... // 其它数据
}
// @handleEmit传回的数据格式:
{
operation: "操作字符串",
busType: "业务类型",
data: {} // 从dataList传入的的元素
}
除了可以通过 on-emit 获取交互,还可以通过 pubsubjs 监听消息("tinode-ask-action"),参考后面说明
// operation的可能值列表
"ask-comment-task"; // 要求对任务评论
"ask-detail-task"; // 要求查看任务详情
"ask-confirm-task"; // 要求确认任务
"ask-reject-task"; // 要求拒绝任务
"ask-detail-tenent"; // 要求查看企业详情
"ask-confirm-tenent"; // 要求要求确认加入企业的申请
"ask-reject-tenent"; // 要求拒绝加入企业的申请
"ask-detail-project"; // 要求查看项目详情
"ask-confirm-task-attention"; // 要求确认任务关注
"ask-reject-task-attention"; // 要求拒绝任务关注
"ask-comment-approval"; // 要求评论立项审批
"ask-detail-approval"; // 要求查看立项审批详情
"ask-detail-project-report"; // 要求要求查看项目报告详情
"ask-comment-result-approval"; // 要求评论业绩审批
"ask-detail-result-approval"; // 要求查看业绩审批详情
"ask-mark-read"; // 要求对一条记录标记为已读
"ask-detail-clue"; // 要求查看线索详情
"ask-assign-clue"; // 要求分配线索
"ask-appoint-other-assign-clue"; // 要求指定他人分配线索
"ask-focus-clue"; // 要求关注线索
"ask-back-clue"; // 要求退回线索
"ask-change-clue"; // 要求转交线索
"ask-sure-clue"; // 要求确认线索
"on-scroll-top"; // 当滚动到顶部
"on-scroll-bottom"; // 当滚动到底部
"ask-detail-business"; // 要求查看商机详
"ask-detail-follow-up"; // 要求查看跟进详情
"ask-change-task"; // 要求转交任务
"ask-continue-task"; // 要求继续任务
"ask-accecptance-task"; // 要求验收任务
"ask-convert-task"; // 要求把聊天转任务,data包含:{ messages: [ {} ]}
"ask-form-task"; // 要求向聊天者发起任务
"ask-submit-log"; // 要求提交日志(业务系统弹出日志表单)
"ask-remind-log"; // 提醒别人提交日志(业务系统调用提醒接口)
"ask-detail-log"; // 查看日志中心
"ask-select-users": // 要求客户端选择用户
"ask-form-group": // 要求客户端输入建群信息
业务系统对每一个 operation 做处理后更新数据,应该使用$set
- this.$set(data, 'isRead', true) // 标记已读
- this.$set(this.dataList, idx, newData) // 更新一条数据
- this.dataList.push(...nextPageDataList) // 添加一页数据
<BizDisplay
:tinode="tinode"
:bizData="bizData"
:uid="myUserBizId"
:typeAlias="typeAlias"
:msgAlias="msgAlias"
@on-emit="handleEmit"
/>
import { ChatMenu, MessageAndInput } from "i3v-chat-system"
// 会话列表
<ChatMenu
:tinode="tinode"
:chatMenuSize="240"
:topicFilter="topicFilter"
:projectId="projectId" // 当前项目的id
@select-topic="onSelectTopic"
/>
// ChatMenu组件显示会话和群组列表
// onSelectTopic 函数返回当前的一个聊天对象,它的topic属性是聊天对象的id
onSelectTopic(topicObj) {
this.topicId = topicObj.topic;
}
// 聊天窗口界面
<MessageAndInput
v-if="!!topicId"
:tinode="tinode"
:topicId="topicId"
:getRobotDataListByIds="getRobotDataListByIds"
:chatInputSize="240"
:projectId="projectId"
width = "100%"
/>
以下是 pubsub-helper.js
import PubSub from "pubsub-js";
/**
* @description: 发布消息
* @param {String} topic 订阅主题
* @param {any} payload 负载
* @return {token}
*/
function publish(topic, payload) {
PubSub.publish(topic, payload);
}
/**
* @description: 订阅消息
* @param {Array} tokens 订阅列表
* @param {String} topic 如on-click-row
* @param {Function} callback 回调函数
* @return {void}
*/
function subscribe(tokens, topic, callback) {
validateTopic(topic);
if (typeof callback === "function") {
tokens.push(PubSub.subscribe(topic, (_, payload) => callback(payload)));
}
}
/**
* @description: 销毁订阅
* @param {Array} tokens 订阅列表
* @return {void}
*/
function unsubscribe(tokens) {
tokens.forEach((token) => {
PubSub.unsubscribe(token);
});
tokens.length = 0;
}
export default {
publish,
subscribe,
unsubscribe,
};
import $pubsub from "./pubsub-helper.js";
// 获取到未读消息总数
this.pubsubTokens = [];
$pubsub.subscribe(
this.pubsubTokens,
"tinode-ask-action",
({ operation, busType, data }) => {
console.log("业务处理", operation, busType, data);
}
);
$pubsub.subscribe(this.pubsubTokens, "tinode-totalUnreadCount", (number) => {
console.log("未读消息总数:", number);
});
// 关闭聊天界面
$pubsub.subscribe(this.pubsubTokens, "tinode-askClose", () => {
/**业务系统关闭聊天界面*/
});
// 全屏聊天界面
$pubsub.subscribe(this.pubsubTokens, "tinode-askFullScreen", () => {
/**业务系统全屏聊天界面*/
});
// 销毁订阅
beforeDestroy() {
$pubsub.unsubscribe(this.pubsubTokens);
},
$pubsub.publish(topic, data);
// 客户端发送用户,
topic = "client-send-users", data格式是[{ id, name, avatar }]
// 客户端发送建群数据
topic = "client-send-group-form",
data格式是
{
name: '群名',
users: [
{ id: "用户id", name: "用户姓名" , avatar: "用户头像" }
],
project: { id: '项目id', name: '项目名' }
}
this.tinode.setState({ key1: "value1", key2: "value2" });
const value1 = this.tinode.getState("key1");
const value2 = this.tinode.getState("key2");
- saasToken // 业务系统登录 saas 后提供的 token
- topicList // Array 所有聊天会话的列表
- robotList // Array 系统聊天机器人列表,格式是 [ { id, name, robotName, tenantId }], 用于禁止向其发聊天消息
- myUserAvatar // String 登录用户的头像网址