3.18医生

This commit is contained in:
zoujiandong 2024-03-18 18:50:29 +08:00
parent d37fb6b1de
commit a9edbc3489
112 changed files with 4490 additions and 340 deletions

View File

@ -3,6 +3,7 @@
"usingComponents": {
"t-image": "tdesign-miniprogram/image/image",
"van-overlay": "@vant/weapp/overlay/index",
"van-icon": "@vant/weapp/icon/index",
"van-dialog": "@vant/weapp/dialog/index"
}
}

View File

@ -22,6 +22,7 @@ Page({
avg_response_time: 0,
number_of_fans: 0,
avatar: api.getStaticHost()+"/applet/doctor/static/images/default_photo.png",
introduction_status:'',
reject_prescription_number: false,//处方管理小红点
},
static_host: api.getStaticHost(),
@ -241,7 +242,7 @@ Page({
this.getMenuButtonBound();
api.yiShiIndex().then(response => {
// console.log(response);
console.log(response);
let avatar = response.data.info.avatar;
this.setData({
"info.user_name": response.data.info.user_name,
@ -249,6 +250,7 @@ Page({
"info.accepting_inquiry_num": response.data.info.accepting_inquiry_num,
"info.praise_rate": response.data.info.praise_rate,
"info.avg_response_time": response.data.info.avg_response_time,
"info.introduction_status":response.data.info.introduction_status,
"info.number_of_fans": response.data.info.number_of_fans,
doctor_inquiry_config:response.data.doctor_inquiry_config
})

View File

@ -5,6 +5,7 @@
"van-button": "@vant/weapp/button/index",
"t-badge": "tdesign-miniprogram/badge/badge",
"t-dialog": "tdesign-miniprogram/dialog/dialog",
"van-icon": "@vant/weapp/icon/index",
"van-image": "@vant/weapp/image/index",
"t-skeleton": "tdesign-miniprogram/skeleton/skeleton",
"van-overlay": "@vant/weapp/overlay/index"

View File

@ -113,6 +113,7 @@
src="{{static_host}}/applet/doctor/static/images/yishi/jianjie.png"
/>
<text class="fun_box_item_txt">个人简介管理</text>
<view class="introicon" wx:if="{{info.introduction_status==3}}">!</view>
</view>
<view bindtap="go" data-moudle="4" data-url="/user/pages/yishi/onlinesetup/index" class="fun_box_item" style="height: {{funbox_height}}rpx;">
<van-image class="fun_box_item_img"

View File

@ -189,6 +189,7 @@ page{
background-color: #fff;
}
.fun_box_item{
position: relative;
width: 40vw;
height: 110rpx;
background-color: #FAFAFA;
@ -252,6 +253,18 @@ page{
border-left: 2px solid var(--td-border-color, #E9E9E9);
border-top-color: #E9E9E9 !important;
}
.overlay_show{
.introicon{
top:-7rpx;
left:50rpx;
width:32rpx;
height:32rpx;
display: flex;
align-items: center;
color:#fff;
font-size: 24rpx;
justify-content: center;
border-radius: 50%;
background-color: red;
position: absolute;
}

View File

@ -329,6 +329,15 @@ Page({
}
lastMessage.messageForShow = text;
}
if(lastMessage.type=="TIMCustomElem"){
let payload = lastMessage.payload;
if(payload && payload.data) {
// console.log("payload: ",payload.data);
let payloadJson = JSON.parse(payload.data);
session_item.custommessagetype =payloadJson.message_type?payloadJson.message_type:'';
}
}
if(lastMessage.isRevoked){
if(lastMessage.fromAccount == app.globalData.config.userID){
lastMessage.messageForShow = "你撤回了一条消息";

View File

@ -49,7 +49,8 @@
</view>
<view class="date"> {{item.message_send_time}}</view>
</view>
<view class="content_2">{{item.last_message_content.Text}}</view>
<!-- {{item.last_message_content.message_type}} -->
<view class="content_2">{{item.custommessagetype==13?'[附赠回复]':item.last_message_content.Text}}</view>
<view class="content_3">
<view class="status">{{ item.inquiry_status==1?'待支付':item.inquiry_status==2?'待分配':item.inquiry_status==3?'待接诊':item.inquiry_status==4?'接诊中':item.inquiry_status==5?'已完成':item.inquiry_status==6?'已结束':item.inquiry_status==7?'已取消':'其他' }}</view>
<view class="btn" wx:if="{{ item.inquiry_status == 3 }}">

View File

@ -46,9 +46,9 @@ Component({
if(value=='idle'){
this.sendCustom();
}
console.log("通话状态该笔:"+value);
console.log(this.data.callerUserInfo);
console.log(this.data.localUserInfo);
// console.log("通话状态该笔:"+value);
// console.log(this.data.callerUserInfo);
// console.log(this.data.localUserInfo);
},
//纠正音视频电话不能携带自定义消息,结束后,主动发条消息
sendCustom(){
@ -56,11 +56,12 @@ Component({
let {timer}=this.data;
if(timer){
clearTimeout(timer);
}
return false;
};
wx.getStorage({
key: 'patientInfo',
success (res) {
let patientInfo=JSON.parse(res.data);
let patientInfo=res.data;
let chat=TUICallKitServer.getTim();
let message = chat.createCustomMessage({
to:patientInfo.patient_user_id,
@ -73,12 +74,12 @@ Component({
cloudCustomData:JSON.stringify(patientInfo)
});
let time=setTimeout(()=>{
let promise = chat.sendMessage(message,{
messageControlInfo: {
excludedFromUnreadCount: true, // 消息不更新会话 unreadCount消息存漫游
}
});
promise.then(function(imResponse) {
let promise = chat.sendMessage(message, {
messageControlInfo: {
excludedFromUnreadCount: true, // 消息不更新会话 unreadCount消息存漫游
}
});
promise.then((imResponse)=>{
console.log('发送成功');
console.log(imResponse);
}).catch(function(imError) {
@ -245,6 +246,7 @@ Component({
[PUSHER]: this.handlePusherChange.bind(that),
[PLAYER]: this.handlePlayerListChange.bind(that),
});
await TUICallKitServer.reject();
},
},
});

View File

@ -10,6 +10,9 @@ Component({
type: String,
observer(callStatus) {
console.log("改编状态:"+callStatus);
if(callStatus=='CONNECTED'){
TUICallKitServer.enableMuteMode(false);
}
}
},
callMediaType: {
@ -37,18 +40,23 @@ Component({
type: Boolean
}
},
pageLifetimes: {
show: function() {
// 页面被展示
lifetimes: {
moved(){
//console.log('moved')
},
hide: function() {
// 页面被隐藏
this.hangup()
},
resize: function(size) {
// 页面尺寸变化
detached(){
//console.log('detached')
this.hangup()
}
},
pageLifetimes: {
show() {
},
hide() {
this.hangup()
},
},
data:{
IMG_DEFAULT_AVATAR:`${PATH}/default_avatar.png`,
IMG_HANGUP:`${PATH}/hangup.png`,
@ -73,7 +81,7 @@ Component({
},
async reject() {
await TUICallKitServer.reject();
console.log(this.data.remoteUserInfoList)
//console.log(this.data.remoteUserInfoList)
},
async switchCamera() {
await TUICallKitServer.switchCamera();

View File

@ -0,0 +1,86 @@
import { CallStatus, NAME, CallRole } from '../const/index';
import { IBellParams } from '../interface/index';
import { isUndefined } from '../utils/common-utils';
const DEFAULT_CALLER_BELL_FILEPATH = '/TUICallKit/static/phone_dialing.mp3';
const DEFAULT_CALLEE_BELL_FILEPATH = '/TUICallKit/static/phone_ringing.mp3';
export class BellContext {
private _bellContext: any = null;
private _isMuteBell: boolean = false;
private _calleeBellFilePath: string = DEFAULT_CALLEE_BELL_FILEPATH;
private _callRole: string = CallRole.UNKNOWN;
private _callStatus: string = CallStatus.IDLE;
constructor() {
// @ts-ignore
this._bellContext = wx.createInnerAudioContext();
this._bellContext.loop = true;
}
setBellSrc() {
// @ts-ignore
const fs = wx.getFileSystemManager();
try {
let playBellFilePath = DEFAULT_CALLER_BELL_FILEPATH;
if (this._callRole === CallRole.CALLEE) {
playBellFilePath = this._calleeBellFilePath || DEFAULT_CALLEE_BELL_FILEPATH;
}
fs.readFileSync(playBellFilePath, 'utf8', 0);
this._bellContext.src = playBellFilePath;
} catch (error) {
console.warn(`${NAME.PREFIX}Failed to setBellSrc, ${error}`);
}
}
setBellProperties(bellParams: IBellParams) {
this._callRole = bellParams.callRole || this._callRole;
this._callStatus = bellParams.callStatus || this._callStatus;
this._calleeBellFilePath = bellParams.calleeBellFilePath || this._calleeBellFilePath;
// undefined/false || isMuteBell => isMuteBell (不符合预期)
this._isMuteBell = isUndefined(bellParams.isMuteBell) ? this._isMuteBell : bellParams.isMuteBell;
}
async play() {
try {
if (this._callStatus !== CallStatus.CALLING) {
return ;
}
this.setBellSrc();
if (this._callRole === CallRole.CALLEE && !this._isMuteBell) {
await this._bellContext.play();
}
if (this._callRole === CallRole.CALLER) {
await this._bellContext.play();
}
} catch (error) {
console.warn(`${NAME.PREFIX}Failed to play audio file, ${error}`);
}
}
async stop() {
try {
this._bellContext.stop();
} catch (error) {
console.warn(`${NAME.PREFIX}Failed to stop audio file, ${error}`);
}
}
async setBellMute(enable: boolean) {
if (this._callStatus !== CallStatus.CALLING && this._callRole !== CallRole.CALLEE) {
return;
}
if (enable) {
await this.stop();
} else {
await this.play();
}
}
destroy() {
try {
this._isMuteBell = false;
this._calleeBellFilePath = '';
this._callRole = CallRole.UNKNOWN;
this._callStatus = CallStatus.IDLE;
this._bellContext.destroy();
this._bellContext = null;
} catch (error) {
console.warn(`${NAME.PREFIX}Failed to destroy, ${error}`);
}
}
}

View File

@ -11,7 +11,9 @@ export default class TUICallService implements ITUICallService {
private _tim;
private _TUICore;
private _timerId;
private _startTimeStamp;
private _bellContext;
private _defaultOfflinePushInfo;
constructor();
static getInstance(): TUICallService;
init(params: IInitParams): Promise<void>;

View File

@ -34,9 +34,11 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
Object.defineProperty(exports, "__esModule", { value: true });
exports.TUIStore = exports.TUIGlobal = void 0;
const tui_core_1 = require("../../min-npm/@tencentcloud/tui-core/index");
//const tui_core_1 = require("@tencentcloud/tui-core");
const index_1 = require("../const/index");
// @ts-ignore
const tuicall_engine_wx_1 = require("../../min-npm/tuicall-engine-wx/index");
//const tuicall_engine_wx_1 = require("tuicall-engine-wx");
const index_2 = require("../locales/index");
const miniProgram_1 = require("./miniProgram");
const bellContext_1 = require("./bellContext");
@ -56,7 +58,12 @@ class TUICallService {
this._tim = null;
this._TUICore = null;
this._timerId = -1;
this._startTimeStamp = (0, common_utils_1.performanceNow)();
this._bellContext = null;
this._defaultOfflinePushInfo = {
title: '',
description: (0, index_2.t)('you have a new call'),
};
// =========================【监听 TUIStore 中的状态】=========================
this._handleCallStatusChange = (value) => __awaiter(this, void 0, void 0, function* () {
var _a, _b;
@ -76,6 +83,7 @@ class TUICallService {
const callMediaType = TUIStore.getData(index_1.StoreName.CALL, index_1.NAME.CALL_MEDIA_TYPE);
const remoteUserInfoList = TUIStore.getData(index_1.StoreName.CALL, index_1.NAME.REMOTE_USER_INFO_LIST);
const oldStatus = isGroup ? index_1.StatusChange.DIALING_GROUP : index_1.StatusChange.DIALING_C2C;
TUIStore.update(index_1.StoreName.CALL, index_1.NAME.CALL_TIPS, '');
this.statusChanged && this.statusChanged({ oldStatus, newStatus: (0, utils_1.generateStatusChangeText)(TUIStore) });
if (!isGroup && callMediaType === index_1.CallMediaType.VIDEO) {
this.switchScreen(remoteUserInfoList[0].domId);
@ -124,6 +132,7 @@ class TUICallService {
});
this._addListenTuiCallEngineEvent();
TUIStore.update(index_1.StoreName.CALL, index_1.NAME.LOCAL_USER_INFO, { userId: userID });
TUIStore.update(index_1.StoreName.CALL, index_1.NAME.LOCAL_USER_INFO_EXCLUDE_VOLUMN, { userId: userID });
yield this._tuiCallEngine.login({ userID, userSig, assetsPath: '' }); // web && mini
}
catch (error) {
@ -156,11 +165,12 @@ class TUICallService {
call(callParams) {
return __awaiter(this, void 0, void 0, function* () {
try {
const { type, userID } = callParams;
const { type, userID, offlinePushInfo } = callParams;
if (TUIStore.getData(index_1.StoreName.CALL, index_1.NAME.CALL_STATUS) !== index_1.CallStatus.IDLE)
return;
yield this._updateCallStoreBeforeCall(type, [{ userId: userID }]);
this._executeExternalBeforeCalling(); // 执行外部传入的 beforeCall 方法
callParams.offlinePushInfo = Object.assign(Object.assign({}, this._defaultOfflinePushInfo), offlinePushInfo);
const response = yield this._tuiCallEngine.call(callParams);
yield this._updateCallStoreAfterCall([userID], response);
}
@ -173,12 +183,13 @@ class TUICallService {
groupCall(groupCallParams) {
return __awaiter(this, void 0, void 0, function* () {
try {
const { userIDList, type, groupID } = groupCallParams;
const { userIDList, type, groupID, offlinePushInfo } = groupCallParams;
if (TUIStore.getData(index_1.StoreName.CALL, index_1.NAME.CALL_STATUS) !== index_1.CallStatus.IDLE)
return;
const remoteUserInfoList = userIDList.map(userId => ({ userId }));
yield this._updateCallStoreBeforeCall(type, remoteUserInfoList, groupID);
this._executeExternalBeforeCalling();
groupCallParams.offlinePushInfo = Object.assign(Object.assign({}, this._defaultOfflinePushInfo), offlinePushInfo);
const response = yield this._tuiCallEngine.groupCall(groupCallParams);
yield this._updateCallStoreAfterCall(userIDList, response);
}
@ -194,6 +205,7 @@ class TUICallService {
let inviteUserInfoList = yield (0, utils_1.getRemoteUserProfile)(userIDList, this.getTim(), TUIStore);
const remoteUserInfoList = TUIStore.getData(index_1.StoreName.CALL, index_1.NAME.REMOTE_USER_INFO_LIST);
TUIStore.update(index_1.StoreName.CALL, index_1.NAME.REMOTE_USER_INFO_LIST, [...remoteUserInfoList, ...inviteUserInfoList]);
TUIStore.update(index_1.StoreName.CALL, index_1.NAME.REMOTE_USER_INFO_EXCLUDE_VOLUMN_LIST, [...remoteUserInfoList, ...inviteUserInfoList]);
this._tuiCallEngine && (yield this._tuiCallEngine.inviteUser(params));
}
catch (error) {
@ -223,6 +235,7 @@ class TUICallService {
this.setSoundMode(params.type === index_1.CallMediaType.AUDIO ? index_1.AudioPlayBackDevice.EAR : index_1.AudioPlayBackDevice.SPEAKER);
const localUserInfo = TUIStore.getData(index_1.StoreName.CALL, index_1.NAME.LOCAL_USER_INFO);
TUIStore.update(index_1.StoreName.CALL, index_1.NAME.LOCAL_USER_INFO, Object.assign(Object.assign({}, localUserInfo), { isEnter: true }));
TUIStore.update(index_1.StoreName.CALL, index_1.NAME.LOCAL_USER_INFO_EXCLUDE_VOLUMN, Object.assign(Object.assign({}, localUserInfo), { isEnter: true }));
this._setLocalUserInfoAudioVideoAvailable(true, index_1.NAME.AUDIO);
}
catch (error) {
@ -301,6 +314,7 @@ class TUICallService {
this.setSoundMode(callMediaType === index_1.CallMediaType.AUDIO ? index_1.AudioPlayBackDevice.EAR : index_1.AudioPlayBackDevice.SPEAKER);
const localUserInfo = TUIStore.getData(index_1.StoreName.CALL, index_1.NAME.LOCAL_USER_INFO);
TUIStore.update(index_1.StoreName.CALL, index_1.NAME.LOCAL_USER_INFO, Object.assign(Object.assign({}, localUserInfo), { isEnter: true }));
TUIStore.update(index_1.StoreName.CALL, index_1.NAME.LOCAL_USER_INFO_EXCLUDE_VOLUMN, Object.assign(Object.assign({}, localUserInfo), { isEnter: true }));
this._setLocalUserInfoAudioVideoAvailable(true, index_1.NAME.AUDIO); // web && mini default open audio
}
}
@ -422,8 +436,11 @@ class TUICallService {
}
switchCamera() {
return __awaiter(this, void 0, void 0, function* () {
const currentPosition = TUIStore.getData(index_1.StoreName.CALL, index_1.NAME.CAMERA_POSITION);
const targetPosition = currentPosition === index_1.CameraPosition.BACK ? index_1.CameraPosition.FRONT : index_1.CameraPosition.BACK;
try {
yield this._tuiCallEngine.switchCamera();
yield this._tuiCallEngine.switchCamera(targetPosition);
TUIStore.update(index_1.StoreName.CALL, index_1.NAME.CAMERA_POSITION, targetPosition);
}
catch (error) {
console.error(`${index_1.NAME.PREFIX}_switchCamera failed, error: ${error}.`);
@ -541,16 +558,19 @@ class TUICallService {
const [userInfo] = remoteUserInfoList.filter((userInfo) => userInfo.userId === sponsor);
remoteUserInfoList.length > 0 && TUIStore.updateStore({
[index_1.NAME.REMOTE_USER_INFO_LIST]: remoteUserInfoList,
[index_1.NAME.REMOTE_USER_INFO_EXCLUDE_VOLUMN_LIST]: remoteUserInfoList,
[index_1.NAME.CALLER_USER_INFO]: {
userId: sponsor,
nick: (userInfo === null || userInfo === void 0 ? void 0 : userInfo.nick) || '',
avatar: (userInfo === null || userInfo === void 0 ? void 0 : userInfo.avatar) || '',
displayUserInfo: (userInfo === null || userInfo === void 0 ? void 0 : userInfo.remark) || (userInfo === null || userInfo === void 0 ? void 0 : userInfo.nick) || sponsor,
},
}, index_1.StoreName.CALL);
});
}
_handleUserAccept(event) {
this._callerChangeToConnected();
TUIStore.update(index_1.StoreName.CALL, index_1.NAME.TOAST_INFO, (0, index_2.t)('answered'));
console.log(`${index_1.NAME.PREFIX}accept event data: ${JSON.stringify(event)}.`);
}
_handleUserEnter(event) {
@ -562,7 +582,10 @@ class TUICallService {
const isInRemoteUserList = remoteUserInfoList.find(item => (item === null || item === void 0 ? void 0 : item.userId) === userId);
if (!isInRemoteUserList) {
remoteUserInfoList.push({ userId });
remoteUserInfoList.length > 0 && TUIStore.update(index_1.StoreName.CALL, index_1.NAME.REMOTE_USER_INFO_LIST, remoteUserInfoList);
if (remoteUserInfoList.length > 0) {
TUIStore.update(index_1.StoreName.CALL, index_1.NAME.REMOTE_USER_INFO_LIST, remoteUserInfoList);
TUIStore.update(index_1.StoreName.CALL, index_1.NAME.REMOTE_USER_INFO_EXCLUDE_VOLUMN_LIST, remoteUserInfoList);
}
const [userInfo] = yield (0, utils_1.getRemoteUserProfile)([userId], this.getTim(), TUIStore);
remoteUserInfoList = TUIStore.getData(index_1.StoreName.CALL, index_1.NAME.REMOTE_USER_INFO_LIST);
remoteUserInfoList.forEach((obj) => {
@ -576,7 +599,10 @@ class TUICallService {
obj.isEnter = true;
return obj;
});
remoteUserInfoList.length > 0 && TUIStore.update(index_1.StoreName.CALL, index_1.NAME.REMOTE_USER_INFO_LIST, remoteUserInfoList);
if (remoteUserInfoList.length > 0) {
TUIStore.update(index_1.StoreName.CALL, index_1.NAME.REMOTE_USER_INFO_LIST, remoteUserInfoList);
TUIStore.update(index_1.StoreName.CALL, index_1.NAME.REMOTE_USER_INFO_EXCLUDE_VOLUMN_LIST, remoteUserInfoList);
}
console.log(`${index_1.NAME.PREFIX}userEnter event data: ${JSON.stringify(event)}.`);
});
}
@ -660,8 +686,10 @@ class TUICallService {
_handleSDKReady(event) {
return __awaiter(this, void 0, void 0, function* () {
let localUserInfo = TUIStore.getData(index_1.StoreName.CALL, index_1.NAME.LOCAL_USER_INFO);
localUserInfo = yield (0, utils_1.getMyProfile)(localUserInfo.userId, this.getTim(), TUIStore); // 方法里已经 try...catch 了
localUserInfo = yield (0, utils_1.getMyProfile)(localUserInfo.userId, this.getTim(), TUIStore);
this._defaultOfflinePushInfo.title = localUserInfo === null || localUserInfo === void 0 ? void 0 : localUserInfo.displayUserInfo;
TUIStore.update(index_1.StoreName.CALL, index_1.NAME.LOCAL_USER_INFO, localUserInfo);
TUIStore.update(index_1.StoreName.CALL, index_1.NAME.LOCAL_USER_INFO_EXCLUDE_VOLUMN, localUserInfo);
});
}
_handleKickedOut(event) {
@ -757,15 +785,18 @@ class TUICallService {
localUserInfo = Object.assign(Object.assign({}, localUserInfo), { isVideoAvailable: isAvailable });
}
TUIStore.update(index_1.StoreName.CALL, index_1.NAME.LOCAL_USER_INFO, localUserInfo);
TUIStore.update(index_1.StoreName.CALL, index_1.NAME.LOCAL_USER_INFO_EXCLUDE_VOLUMN, localUserInfo);
}
_updateCallStoreBeforeCall(type, remoteUserInfoList, groupID) {
return __awaiter(this, void 0, void 0, function* () {
const callTips = groupID || TUIStore.getData(index_1.StoreName.CALL, index_1.NAME.IS_MINIMIZED) ? index_2.CallTips.CALLER_GROUP_CALLING_MSG : index_2.CallTips.CALLER_CALLING_MSG;
let updateStoreParams = {
[index_1.NAME.CALL_MEDIA_TYPE]: type,
[index_1.NAME.CALL_ROLE]: index_1.CallRole.CALLER,
[index_1.NAME.REMOTE_USER_INFO_LIST]: remoteUserInfoList,
[index_1.NAME.REMOTE_USER_INFO_EXCLUDE_VOLUMN_LIST]: remoteUserInfoList,
[index_1.NAME.IS_GROUP]: !!groupID,
[index_1.NAME.CALL_TIPS]: (0, index_2.t)(index_2.CallTips.CALLER_CALLING_MSG),
[index_1.NAME.CALL_TIPS]: (0, index_2.t)(callTips),
[index_1.NAME.GROUP_ID]: groupID
};
const pusher = { enableCamera: type === index_1.CallMediaType.VIDEO, enableMic: true }; // mini 默认打开麦克风
@ -775,7 +806,10 @@ class TUICallService {
console.log(`${index_1.NAME.PREFIX}mini beforeCall return callStatus: ${callStatus}.`);
TUIStore.update(index_1.StoreName.CALL, index_1.NAME.CALL_STATUS, callStatus);
const remoteUserInfoLists = yield (0, utils_1.getRemoteUserProfile)(remoteUserInfoList.map(obj => obj.userId), this.getTim(), TUIStore);
remoteUserInfoLists.length > 0 && TUIStore.update(index_1.StoreName.CALL, index_1.NAME.REMOTE_USER_INFO_LIST, remoteUserInfoLists);
if (remoteUserInfoLists.length > 0) {
TUIStore.update(index_1.StoreName.CALL, index_1.NAME.REMOTE_USER_INFO_LIST, remoteUserInfoLists);
TUIStore.update(index_1.StoreName.CALL, index_1.NAME.REMOTE_USER_INFO_EXCLUDE_VOLUMN_LIST, remoteUserInfoLists);
}
});
}
_updateCallStoreAfterCall(userIdList, response) {
@ -792,6 +826,7 @@ class TUICallService {
TUIStore.update(index_1.StoreName.CALL, index_1.NAME.CALL_STATUS, index_1.CallStatus.CALLING); // 小程序未授权时, 此时状态为 idle; web 直接设置为 calling
const localUserInfo = TUIStore.getData(index_1.StoreName.CALL, index_1.NAME.LOCAL_USER_INFO);
TUIStore.update(index_1.StoreName.CALL, index_1.NAME.LOCAL_USER_INFO, Object.assign(Object.assign({}, localUserInfo), { isEnter: true }));
TUIStore.update(index_1.StoreName.CALL, index_1.NAME.LOCAL_USER_INFO_EXCLUDE_VOLUMN, Object.assign(Object.assign({}, localUserInfo), { isEnter: true }));
this._setLocalUserInfoAudioVideoAvailable(true, index_1.NAME.AUDIO); // web && mini, default open audio
}
else {
@ -800,9 +835,13 @@ class TUICallService {
});
}
_resetCurrentDevice() {
// 挂断后,重置当前摄像头和麦克风为默认设备
const { cameraList, microphoneList } = TUIStore.getData(index_1.StoreName.CALL, index_1.NAME.DEVICE_LIST);
TUIStore.update(index_1.StoreName.CALL, index_1.NAME.DEVICE_LIST, { microphoneList, cameraList, currentCamera: (cameraList === null || cameraList === void 0 ? void 0 : cameraList[0]) || {}, currentMicrophone: (microphoneList === null || microphoneList === void 0 ? void 0 : microphoneList[0]) || {} });
// 挂断后,重置当前摄像头,麦克风和扬声器为默认设备
const { cameraList, microphoneList, speakerList } = TUIStore.getData(index_1.StoreName.CALL, index_1.NAME.DEVICE_LIST);
TUIStore.update(index_1.StoreName.CALL, index_1.NAME.DEVICE_LIST, { microphoneList, cameraList, speakerList,
currentCamera: (cameraList === null || cameraList === void 0 ? void 0 : cameraList[0]) || {},
currentMicrophone: (microphoneList === null || microphoneList === void 0 ? void 0 : microphoneList[0]) || {},
currentSpeaker: (speakerList === null || speakerList === void 0 ? void 0 : speakerList[0]) || {}
});
}
_resetCallStore() {
const oldStatusStr = (0, utils_1.generateStatusChangeText)(TUIStore);
@ -820,7 +859,8 @@ class TUICallService {
case index_1.NAME.DISPLAY_MODE:
case index_1.NAME.VIDEO_RESOLUTION:
case index_1.NAME.ENABLE_FLOAT_WINDOW:
case index_1.NAME.LOCAL_USER_INFO: {
case index_1.NAME.LOCAL_USER_INFO:
case index_1.NAME.LOCAL_USER_INFO_EXCLUDE_VOLUMN: {
return false;
}
default: {
@ -835,6 +875,9 @@ class TUICallService {
TUIStore.reset(index_1.StoreName.CALL, [index_1.NAME.IS_MINIMIZED], true); // isMinimized reset need notify
TUIStore.reset(index_1.StoreName.CALL, [index_1.NAME.IS_EAR_PHONE], true); // isEarPhone reset need notify
TUIStore.update(index_1.StoreName.CALL, index_1.NAME.LOCAL_USER_INFO, Object.assign(Object.assign({}, TUIStore.getData(index_1.StoreName.CALL, index_1.NAME.LOCAL_USER_INFO)), { isVideoAvailable: false, isAudioAvailable: false }));
TUIStore.update(index_1.StoreName.CALL, index_1.NAME.LOCAL_USER_INFO_EXCLUDE_VOLUMN, Object.assign(Object.assign({}, TUIStore.getData(index_1.StoreName.CALL, index_1.NAME.LOCAL_USER_INFO_EXCLUDE_VOLUMN)), { isVideoAvailable: false, isAudioAvailable: false }));
TUIStore.update(index_1.StoreName.CALL, index_1.NAME.REMOTE_USER_INFO_LIST, []);
TUIStore.update(index_1.StoreName.CALL, index_1.NAME.REMOTE_USER_INFO_EXCLUDE_VOLUMN_LIST, []);
this._resetCurrentDevice();
const newStatusStr = (0, utils_1.generateStatusChangeText)(TUIStore);
if (oldStatusStr !== newStatusStr) {
@ -857,13 +900,13 @@ class TUICallService {
// 通话时长更新
_startTimer() {
if (this._timerId === -1) {
this._startTimeStamp = (0, common_utils_1.performanceNow)();
this._timerId = timer_1.default.run(index_1.NAME.TIMEOUT, this._updateCallDuration.bind(this), { delay: 1000 });
}
}
_updateCallDuration() {
let callDurationStr = TUIStore.getData(index_1.StoreName.CALL, index_1.NAME.CALL_DURATION);
const callDurationNum = (0, common_utils_1.formatTimeInverse)(callDurationStr);
callDurationStr = (0, common_utils_1.formatTime)(callDurationNum + 1);
const callDurationNum = Math.round(((0, common_utils_1.performanceNow)() - this._startTimeStamp) / 1000); // miniProgram stop timer when background
const callDurationStr = (0, common_utils_1.formatTime)(callDurationNum);
TUIStore.update(index_1.StoreName.CALL, index_1.NAME.CALL_DURATION, callDurationStr);
}
_stopTimer() {
@ -880,6 +923,7 @@ class TUICallService {
remoteUserInfoList = remoteUserInfoList.filter((obj) => obj.userId !== userId);
});
TUIStore.update(index_1.StoreName.CALL, index_1.NAME.REMOTE_USER_INFO_LIST, remoteUserInfoList);
TUIStore.update(index_1.StoreName.CALL, index_1.NAME.REMOTE_USER_INFO_EXCLUDE_VOLUMN_LIST, remoteUserInfoList);
}
_analyzeEventData(event) {
return (event === null || event === void 0 ? void 0 : event.data) || {}; // mini INVITED

View File

@ -0,0 +1,991 @@
import { TUICore, TUILogin, TUIConstants, ExtensionInfo } from '@tencentcloud/tui-core';
import {
ITUICallService,
ICallParams,
IGroupCallParams,
IUserInfo,
ICallbackParam,
ISelfInfoParams,
IBellParams,
IInviteUserParams,
IJoinInGroupCallParams,
IInitParams,
} from '../interface/ICallService';
import {
StoreName,
CallStatus,
CallMediaType,
NAME,
CALL_DATA_KEY,
LanguageType,
CallRole,
LOG_LEVEL,
VideoDisplayMode,
VideoResolution,
StatusChange,
AudioCallIcon,
VideoCallIcon,
ErrorCode,
ErrorMessage,
AudioPlayBackDevice,
CameraPosition,
} from '../const/index';
// @ts-ignore
import { TUICallEngine, EVENT as TUICallEvent } from 'tuicall-engine-wx';
import { CallTips, t } from '../locales/index';
import { initAndCheckRunEnv, beforeCall, handlePackageError } from './miniProgram';
import { BellContext } from './bellContext';
import { VALIDATE_PARAMS, avoidRepeatedCall, paramValidate } from '../utils/validate/index';
import { handleRepeatedCallError, handleNoDevicePermissionError, formatTime, performanceNow } from '../utils/common-utils';
import { getMyProfile, getRemoteUserProfile, generateText, generateStatusChangeText, getGroupMemberList, getGroupProfile } from './utils';
import timer from '../utils/timer';
import { ITUIGlobal } from '../interface/ITUIGlobal';
import { ITUIStore } from '../interface/ITUIStore';
import TuiGlobal from '../TUIGlobal/tuiGlobal';
import TuiStore from '../TUIStore/tuiStore';
const TUIGlobal: ITUIGlobal = TuiGlobal.getInstance();
const TUIStore: ITUIStore = TuiStore.getInstance();
const version = '2.1.1';
export { TUIGlobal, TUIStore };
export default class TUICallService implements ITUICallService {
static instance: TUICallService;
public _tuiCallEngine: any;
private _tim: any = null;
private _TUICore: any = null;
private _timerId: number = -1;
private _startTimeStamp: number = performanceNow();
private _bellContext: any = null;
private _defaultOfflinePushInfo = {
title: '',
description: t('you have a new call'),
};
constructor() {
console.log(`${NAME.PREFIX}version: ${version}`);
this._watchTUIStore();
this._bellContext = new BellContext();
// 下面TUICore注册事件注册组件服务注册界面拓展
TUICore.registerEvent(TUIConstants.TUILogin.EVENT.LOGIN_STATE_CHANGED, TUIConstants.TUILogin.EVENT_SUB_KEY.USER_LOGIN_SUCCESS, this);
TUICore.registerService(TUIConstants.TUICalling.SERVICE.NAME, this);
TUICore.registerExtension(TUIConstants.TUIChat.EXTENSION.INPUT_MORE.EXT_ID, this);
}
static getInstance() {
if (!TUICallService.instance) {
TUICallService.instance = new TUICallService();
}
return TUICallService.instance;
}
@avoidRepeatedCall()
@paramValidate(VALIDATE_PARAMS.init)
public async init(params: IInitParams) {
try {
if (this._tuiCallEngine) return;
// @ts-ignore
let { userID, tim, userSig, sdkAppID, SDKAppID, isFromChat } = params;
if (this._TUICore) {
sdkAppID = this._TUICore.SDKAppID;
tim = this._TUICore.tim;
}
this._tim = tim;
console.log(`${NAME.PREFIX}init sdkAppId: ${sdkAppID || SDKAppID}, userId: ${userID}`);
this._tuiCallEngine = TUICallEngine.createInstance({
tim,
// @ts-ignore
sdkAppID: sdkAppID || SDKAppID, // 兼容传入 SDKAppID 的问题
callkitVersion: version,
chat: isFromChat || false,
});
this._addListenTuiCallEngineEvent();
TUIStore.update(StoreName.CALL, NAME.LOCAL_USER_INFO, { userId: userID });
TUIStore.update(StoreName.CALL, NAME.LOCAL_USER_INFO_EXCLUDE_VOLUMN, { userId: userID });
await this._tuiCallEngine.login({ userID, userSig, assetsPath: '' }); // web && mini
} catch (error) {
console.error(`${NAME.PREFIX}init failed, error: ${error}.`);
throw error;
}
}
// component destroy
public async destroyed() {
try {
const currentCallStatus = TUIStore.getData(StoreName.CALL, NAME.CALL_STATUS);
if (currentCallStatus !== CallStatus.IDLE) {
throw new Error(`please destroyed when status is idle, current status: ${currentCallStatus}`);
}
if (this._tuiCallEngine) {
this._removeListenTuiCallEngineEvent();
await this._tuiCallEngine.destroyInstance();
this._tuiCallEngine = null;
}
} catch (error) {
console.error(`${NAME.PREFIX}destroyed failed, error: ${error}.`);
throw error;
}
}
// ===============================【通话操作】===============================
@avoidRepeatedCall()
@paramValidate(VALIDATE_PARAMS.call)
public async call(callParams: ICallParams) {
try {
const { type, userID, offlinePushInfo } = callParams;
if (TUIStore.getData(StoreName.CALL, NAME.CALL_STATUS) !== CallStatus.IDLE) return;
await this._updateCallStoreBeforeCall(type, [{ userId: userID }]);
this._executeExternalBeforeCalling(); // 执行外部传入的 beforeCall 方法
callParams.offlinePushInfo = { ...this._defaultOfflinePushInfo, ...offlinePushInfo };
const response = await this._tuiCallEngine.call(callParams);
await this._updateCallStoreAfterCall([userID], response);
} catch (error: any) {
this._handleCallError(error, 'call');
}
};
@avoidRepeatedCall()
@paramValidate(VALIDATE_PARAMS.groupCall)
public async groupCall(groupCallParams: IGroupCallParams) {
try {
const { userIDList, type, groupID, offlinePushInfo } = groupCallParams;
if (TUIStore.getData(StoreName.CALL, NAME.CALL_STATUS) !== CallStatus.IDLE) return;
const remoteUserInfoList = userIDList.map(userId => ({ userId }));
await this._updateCallStoreBeforeCall(type, remoteUserInfoList, groupID);
this._executeExternalBeforeCalling();
groupCallParams.offlinePushInfo = { ...this._defaultOfflinePushInfo, ...offlinePushInfo };
const response = await this._tuiCallEngine.groupCall(groupCallParams);
await this._updateCallStoreAfterCall(userIDList, response);
} catch (error: any) {
this._handleCallError(error, 'groupCall');
}
}
@avoidRepeatedCall()
@paramValidate(VALIDATE_PARAMS.inviteUser)
public async inviteUser(params: IInviteUserParams) {
try {
const { userIDList } = params;
let inviteUserInfoList = await getRemoteUserProfile(userIDList, this.getTim(), TUIStore);
const remoteUserInfoList = TUIStore.getData(StoreName.CALL, NAME.REMOTE_USER_INFO_LIST);
TUIStore.update(StoreName.CALL, NAME.REMOTE_USER_INFO_LIST, [...remoteUserInfoList, ...inviteUserInfoList]);
TUIStore.update(StoreName.CALL, NAME.REMOTE_USER_INFO_EXCLUDE_VOLUMN_LIST, [...remoteUserInfoList, ...inviteUserInfoList]);
this._tuiCallEngine && await this._tuiCallEngine.inviteUser(params);
} catch (error: any) {
console.error(`${NAME.PREFIX}inviteUser failed, error: ${error}.`);
this._resetCallStore();
throw error;
}
}
@avoidRepeatedCall()
@paramValidate(VALIDATE_PARAMS.joinInGroupCall)
public async joinInGroupCall(params: IJoinInGroupCallParams) {
try {
const updateStoreParams = {
[NAME.CALL_ROLE]: CallRole.CALLEE,
[NAME.IS_GROUP]: true,
[NAME.CALL_STATUS]: CallStatus.CONNECTED,
[NAME.CALL_MEDIA_TYPE]: params.type,
[NAME.GROUP_ID]: params.groupID,
[NAME.ROOM_ID]: params.roomID,
};
TUIStore.updateStore(updateStoreParams, StoreName.CALL);
const response = await this._tuiCallEngine.joinInGroupCall(params);
(params.type === CallMediaType.VIDEO) && await this.openCamera(NAME.LOCAL_VIDEO);
TUIStore.update(StoreName.CALL, NAME.IS_CLICKABLE, true);
this._startTimer();
TUIStore.update(StoreName.CALL, NAME.PUSHER, response);
this.setSoundMode(params.type === CallMediaType.AUDIO ? AudioPlayBackDevice.EAR : AudioPlayBackDevice.SPEAKER);
const localUserInfo = TUIStore.getData(StoreName.CALL, NAME.LOCAL_USER_INFO);
TUIStore.update(StoreName.CALL, NAME.LOCAL_USER_INFO, { ...localUserInfo, isEnter: true });
TUIStore.update(StoreName.CALL, NAME.LOCAL_USER_INFO_EXCLUDE_VOLUMN, { ...localUserInfo, isEnter: true });
this._setLocalUserInfoAudioVideoAvailable(true, NAME.AUDIO);
} catch (error) {
console.error(`${NAME.PREFIX}joinInGroupCall failed, error: ${error}.`);
this._resetCallStore();
throw error;
}
}
// ===============================【其它对外接口】===============================
public getTUICallEngineInstance(): any {
return this?._tuiCallEngine || null;
}
public setLogLevel(level: LOG_LEVEL) {
this?._tuiCallEngine?.setLogLevel(level);
}
@paramValidate(VALIDATE_PARAMS.setLanguage)
public setLanguage(language: LanguageType) {
if (language && Object.values(LanguageType).includes(language)) {
TUIStore.update(StoreName.CALL, NAME.LANGUAGE, language);
}
}
@paramValidate(VALIDATE_PARAMS.enableFloatWindow)
public enableFloatWindow(enable: boolean) {
TUIStore.update(StoreName.CALL, NAME.ENABLE_FLOAT_WINDOW, enable);
}
@paramValidate(VALIDATE_PARAMS.setSelfInfo)
public async setSelfInfo(params: ISelfInfoParams) {
const { nickName, avatar } = params;
try {
await this._tuiCallEngine.setSelfInfo(nickName, avatar);
} catch (error) {
console.error(`${NAME.PREFIX}setSelfInfo failed, error: ${error}.`);
}
}
// 修改默认铃声:只支持本地铃声文件,不支持在线铃声文件;修改铃声修改的是被叫的铃声
@paramValidate(VALIDATE_PARAMS.setCallingBell)
public async setCallingBell(filePath?: string) {
let isCheckFileExist: boolean = true;
if (!isCheckFileExist) {
console.warn(`${NAME.PREFIX}setCallingBell failed, filePath: ${filePath}.`);
return ;
}
const bellParams: IBellParams = { calleeBellFilePath: filePath };
this._bellContext.setBellProperties(bellParams);
}
@paramValidate(VALIDATE_PARAMS.enableMuteMode)
public async enableMuteMode(enable: boolean) {
try {
const bellParams: IBellParams = { isMuteBell: enable };
this._bellContext.setBellProperties(bellParams);
await this._bellContext.setBellMute(enable);
} catch (error) {
console.warn(`${NAME.PREFIX}enableMuteMode failed, error: ${error}.`);
}
}
// =============================【内部按钮操作方法】=============================
@avoidRepeatedCall()
public async accept() {
try {
const response = await this._tuiCallEngine.accept();
if (response) {
// 小程序接通时会进行授权弹框, 状态需要放在 accept 后, 否则先接通后再拉起权限设置
TUIStore.update(StoreName.CALL, NAME.CALL_STATUS, CallStatus.CONNECTED);
this._callTUIService({ message: response?.data?.message });
TUIStore.update(StoreName.CALL, NAME.IS_CLICKABLE, true);
this._startTimer();
const callMediaType = TUIStore.getData(StoreName.CALL, NAME.CALL_MEDIA_TYPE);
(callMediaType === CallMediaType.VIDEO) && await this.openCamera(NAME.LOCAL_VIDEO);
response.pusher && TUIStore.update(StoreName.CALL, NAME.PUSHER, response.pusher);
this.setSoundMode(callMediaType === CallMediaType.AUDIO ? AudioPlayBackDevice.EAR : AudioPlayBackDevice.SPEAKER);
const localUserInfo = TUIStore.getData(StoreName.CALL, NAME.LOCAL_USER_INFO);
TUIStore.update(StoreName.CALL, NAME.LOCAL_USER_INFO, { ...localUserInfo, isEnter: true });
TUIStore.update(StoreName.CALL, NAME.LOCAL_USER_INFO_EXCLUDE_VOLUMN, { ...localUserInfo, isEnter: true });
this._setLocalUserInfoAudioVideoAvailable(true, NAME.AUDIO); // web && mini default open audio
}
} catch (error) {
if (handleRepeatedCallError(error)) return;
this._noDevicePermissionToast(error, CallMediaType.AUDIO);
this._resetCallStore();
}
}
@avoidRepeatedCall()
public async hangup() {
try {
const response = await this._tuiCallEngine.hangup();
response?.forEach((item) => {
if (item?.code === 0) {
this._callTUIService({ message: item?.data?.message });
}
});
} catch (error) {
console.debug(error);
}
this._resetCallStore();
}
@avoidRepeatedCall()
public async reject() {
try {
const response = await this._tuiCallEngine.reject();
if (response?.code === 0) {
this._callTUIService({ message: response?.data?.message });
}
} catch (error) {
console.debug(error);
}
this._resetCallStore();
}
@avoidRepeatedCall()
public async openCamera(videoViewDomID: string) {
try {
await this._tuiCallEngine.openCamera();
this._setLocalUserInfoAudioVideoAvailable(true, NAME.VIDEO);
} catch (error: any) {
this._noDevicePermissionToast(error, CallMediaType.VIDEO);
console.error(`${NAME.PREFIX}openCamera error: ${error}.`);
}
}
@avoidRepeatedCall()
public async closeCamera() {
try {
await this._tuiCallEngine.closeCamera();
this._setLocalUserInfoAudioVideoAvailable(false, NAME.VIDEO);
} catch (error: any) {
console.error(`${NAME.PREFIX}closeCamera error: ${error}.`);
}
}
@avoidRepeatedCall()
public async openMicrophone() {
try {
await this._tuiCallEngine.openMicrophone();
this._setLocalUserInfoAudioVideoAvailable(true, NAME.AUDIO);
} catch (error: any) {
console.error(`${NAME.PREFIX}openMicrophone failed, error: ${error}.`);
}
}
@avoidRepeatedCall()
public async closeMicrophone() {
try {
await this._tuiCallEngine.closeMicrophone();
this._setLocalUserInfoAudioVideoAvailable(false, NAME.AUDIO);
} catch (error: any) {
console.error(`${NAME.PREFIX}closeMicrophone failed, error: ${error}.`);
}
}
@avoidRepeatedCall()
public switchScreen(userId: string) {
if(!userId) return;
TUIStore.update(StoreName.CALL, NAME.BIG_SCREEN_USER_ID, userId);
}
// support video to audio; not support audio to video
@avoidRepeatedCall()
public async switchCallMediaType() {
try {
const callMediaType = TUIStore.getData(StoreName.CALL, NAME.CALL_MEDIA_TYPE);
if (callMediaType === CallMediaType.AUDIO) {
console.warn(`${NAME.PREFIX}switchCallMediaType failed, ${callMediaType} not support.`);
return;
}
const response = await this._tuiCallEngine.switchCallMediaType(CallMediaType.AUDIO);
if (response?.code === 0) {
this._callTUIService({ message: response?.data?.message });
}
TUIStore.update(StoreName.CALL, NAME.CALL_MEDIA_TYPE, CallMediaType.AUDIO);
const isGroup = TUIStore.getData(StoreName.CALL, NAME.IS_GROUP);
const oldStatus = isGroup ? StatusChange.CALLING_GROUP_VIDEO : StatusChange.CALLING_C2C_VIDEO;
const newStatus = generateStatusChangeText(TUIStore);
this.statusChanged && this.statusChanged({ oldStatus, newStatus });
this.setSoundMode(AudioPlayBackDevice.EAR);
} catch (error: any) {
console.error(`${NAME.PREFIX}switchCallMediaType failed, error: ${error}.`);
}
}
@avoidRepeatedCall()
public async switchCamera() {
const currentPosition = TUIStore.getData(StoreName.CALL, NAME.CAMERA_POSITION);
const targetPosition = currentPosition === CameraPosition.BACK ? CameraPosition.FRONT : CameraPosition.BACK;
try {
await this._tuiCallEngine.switchCamera(targetPosition);
TUIStore.update(StoreName.CALL, NAME.CAMERA_POSITION, targetPosition);
} catch (error) {
console.error(`${NAME.PREFIX}_switchCamera failed, error: ${error}.`);
}
}
@avoidRepeatedCall()
public setSoundMode(type?: string): void {
try {
let isEarPhone = TUIStore.getData(StoreName.CALL, NAME.IS_EAR_PHONE);
const soundMode = type || (isEarPhone ? AudioPlayBackDevice.SPEAKER : AudioPlayBackDevice.EAR); // UI 层切换时传参数
this._tuiCallEngine?.selectAudioPlaybackDevice(soundMode);
if (type) {
isEarPhone = type === AudioPlayBackDevice.EAR;
} else {
isEarPhone = !isEarPhone;
}
TUIStore.update(StoreName.CALL, NAME.IS_EAR_PHONE, isEarPhone);
} catch (error) {
console.error(`${NAME.PREFIX}setSoundMode failed, error: ${error}.`);
}
}
// 切前后置 miniProgram, 切扬声器
public getTim() {
if (this._tim) return this._tim;
if (!this._tuiCallEngine) {
console.warn(`${NAME.PREFIX}getTim warning: _tuiCallEngine Instance is not available.`);
return null;
}
return this._tuiCallEngine?.tim || this._tuiCallEngine?.getTim(); // mini support getTim interface
}
// ==========================【TUICallEngine 事件处理】==========================
private _addListenTuiCallEngineEvent() {
if (!this._tuiCallEngine) {
console.warn(`${NAME.PREFIX}add engine event listener failed, engine is empty.`);
return;
}
this._tuiCallEngine.on(TUICallEvent.ERROR, this._handleError, this);
this._tuiCallEngine.on(TUICallEvent.INVITED, this._handleNewInvitationReceived, this); // 收到邀请事件
this._tuiCallEngine.on(TUICallEvent.USER_ACCEPT, this._handleUserAccept, this); // 主叫收到被叫接通事件
this._tuiCallEngine.on(TUICallEvent.USER_ENTER, this._handleUserEnter, this); // 有用户进房事件
this._tuiCallEngine.on(TUICallEvent.USER_LEAVE, this._handleUserLeave, this); // 有用户离开通话事件
this._tuiCallEngine.on(TUICallEvent.REJECT, this._handleInviteeReject, this); // 主叫收到被叫的拒绝通话事件
this._tuiCallEngine.on(TUICallEvent.NO_RESP, this._handleNoResponse, this); // 主叫收到被叫的无应答事件
this._tuiCallEngine.on(TUICallEvent.LINE_BUSY, this._handleLineBusy, this); // 主叫收到被叫的忙线事件
this._tuiCallEngine.on(TUICallEvent.CALLING_CANCEL, this._handleCallingCancel, this); // 主被叫在通话未建立时, 收到的取消事件
this._tuiCallEngine.on(TUICallEvent.SDK_READY, this._handleSDKReady, this); // SDK Ready 回调
this._tuiCallEngine.on(TUICallEvent.KICKED_OUT, this._handleKickedOut, this); // 未开启多端登录时, 多端登录收到的被踢事件
this._tuiCallEngine.on(TUICallEvent.MESSAGE_SENT_BY_ME, this._messageSentByMe, this);
this._tuiCallEngine.on(TUICallEvent.CALL_END, this._handleCallingEnd, this); // 主被叫在通话结束时, 收到的通话结束事件
// @ts-ignore
this._tuiCallEngine.on(TUICallEvent.CALL_MODE, this._handleCallTypeChange, this);
// @ts-ignore
this._tuiCallEngine.on(TUICallEvent.USER_UPDATE, this._handleUserUpdate, this); // mini: user data update
}
private _removeListenTuiCallEngineEvent() {
this._tuiCallEngine.off(TUICallEvent.ERROR, this._handleError);
this._tuiCallEngine.off(TUICallEvent.INVITED, this._handleNewInvitationReceived);
this._tuiCallEngine.off(TUICallEvent.USER_ACCEPT, this._handleUserAccept);
this._tuiCallEngine.off(TUICallEvent.USER_ENTER, this._handleUserEnter);
this._tuiCallEngine.off(TUICallEvent.USER_LEAVE, this._handleUserLeave);
this._tuiCallEngine.off(TUICallEvent.REJECT, this._handleInviteeReject);
this._tuiCallEngine.off(TUICallEvent.NO_RESP, this._handleNoResponse);
this._tuiCallEngine.off(TUICallEvent.LINE_BUSY, this._handleLineBusy);
this._tuiCallEngine.off(TUICallEvent.CALLING_CANCEL, this._handleCallingCancel);
this._tuiCallEngine.off(TUICallEvent.SDK_READY, this._handleSDKReady);
this._tuiCallEngine.off(TUICallEvent.KICKED_OUT, this._handleKickedOut);
this._tuiCallEngine.off(TUICallEvent.MESSAGE_SENT_BY_ME, this._messageSentByMe);
this._tuiCallEngine.off(TUICallEvent.CALL_END, this._handleCallingEnd);
// @ts-ignore
this._tuiCallEngine.off(TUICallEvent.CALL_MODE, this._handleCallTypeChange); // 切换通话事件 miniProgram CALL_MODE
// @ts-ignore
this._tuiCallEngine.off(TUICallEvent.USER_UPDATE, this._handleUserUpdate); // mini: user data update
}
private _handleError(event: any): void {
const { code, message } = event || {};
const index = Object.values(ErrorCode).indexOf(code);
let callTips = '';
if (index !== -1) {
const key = Object.keys(ErrorCode)[index];
callTips = t(ErrorMessage[key]);
callTips && TUIStore.update(StoreName.CALL, NAME.TOAST_INFO, { text: callTips, type: NAME.ERROR });
}
this._executeExternalAfterCalling();
console.error(`${NAME.PREFIX}_handleError, errorCode: ${code}; errorMessage: ${callTips || message}.`);
}
private async _handleNewInvitationReceived(event: any) {
console.log(`${NAME.PREFIX}onCallReceived event data: ${JSON.stringify(event)}.`);
const { sponsor = '', isFromGroup, callMediaType, inviteData = {}, calleeIdList = [], groupID = '' } = this._analyzeEventData(event);
const currentUserInfo: IUserInfo = TUIStore.getData(StoreName.CALL, NAME.LOCAL_USER_INFO);
const remoteUserIdList: string[] = [sponsor, ...calleeIdList.filter((userId: string) => userId !== currentUserInfo.userId)];
const type = callMediaType || inviteData.callType;
const callTipsKey = type === CallMediaType.AUDIO ? CallTips.CALLEE_CALLING_AUDIO_MSG : CallTips.CALLEE_CALLING_VIDEO_MSG;
let updateStoreParams = {
[NAME.CALL_ROLE]: CallRole.CALLEE,
[NAME.IS_GROUP]: isFromGroup,
[NAME.CALL_STATUS]: CallStatus.CALLING,
[NAME.CALL_MEDIA_TYPE]: type,
[NAME.CALL_TIPS]: t(callTipsKey),
[NAME.CALLER_USER_INFO]: { userId: sponsor },
[NAME.GROUP_ID]: groupID,
};
initAndCheckRunEnv();
const pusher = { enableCamera: type === CallMediaType.VIDEO, enableMic: true }; // mini 默认打开麦克风
updateStoreParams = { ...updateStoreParams, [NAME.PUSHER]: pusher };
TUIStore.updateStore(updateStoreParams, StoreName.CALL);
this._executeExternalBeforeCalling();
this.statusChanged && this.statusChanged({ oldStatus: StatusChange.IDLE, newStatus: StatusChange.BE_INVITED });
const remoteUserInfoList = await getRemoteUserProfile(remoteUserIdList, this.getTim(), TUIStore);
const [userInfo] = remoteUserInfoList.filter((userInfo: IUserInfo) => userInfo.userId === sponsor);
remoteUserInfoList.length > 0 && TUIStore.updateStore({
[NAME.REMOTE_USER_INFO_LIST]: remoteUserInfoList,
[NAME.REMOTE_USER_INFO_EXCLUDE_VOLUMN_LIST]: remoteUserInfoList,
[NAME.CALLER_USER_INFO]: {
userId: sponsor,
nick: userInfo?.nick || '',
avatar: userInfo?.avatar || '',
displayUserInfo: userInfo?.remark || userInfo?.nick || sponsor,
},
}, StoreName.CALL);
}
private _handleUserAccept(event: any): void {
this._callerChangeToConnected();
TUIStore.update(StoreName.CALL, NAME.TOAST_INFO, t('answered'));
console.log(`${NAME.PREFIX}accept event data: ${JSON.stringify(event)}.`);
}
private async _handleUserEnter(event: any): Promise<void> {
this._callerChangeToConnected();
const { userID: userId, data } = this._analyzeEventData(event);
data?.playerList && TUIStore.update(StoreName.CALL, NAME.PLAYER, data.playerList);
let remoteUserInfoList = TUIStore.getData(StoreName.CALL, NAME.REMOTE_USER_INFO_LIST);
const isInRemoteUserList = remoteUserInfoList.find(item => item?.userId === userId);
if (!isInRemoteUserList) {
remoteUserInfoList.push({ userId });
if (remoteUserInfoList.length > 0) {
TUIStore.update(StoreName.CALL, NAME.REMOTE_USER_INFO_LIST, remoteUserInfoList);
TUIStore.update(StoreName.CALL, NAME.REMOTE_USER_INFO_EXCLUDE_VOLUMN_LIST, remoteUserInfoList);
}
const [userInfo] = await getRemoteUserProfile([userId], this.getTim(), TUIStore);
remoteUserInfoList = TUIStore.getData(StoreName.CALL, NAME.REMOTE_USER_INFO_LIST);
remoteUserInfoList.forEach((obj) => {
if (obj?.userId === userId) {
obj = Object.assign(obj, userInfo);
}
});
}
remoteUserInfoList = remoteUserInfoList.map((obj: IUserInfo) => {
if (obj.userId === userId) obj.isEnter = true;
return obj;
});
if (remoteUserInfoList.length > 0) {
TUIStore.update(StoreName.CALL, NAME.REMOTE_USER_INFO_LIST, remoteUserInfoList);
TUIStore.update(StoreName.CALL, NAME.REMOTE_USER_INFO_EXCLUDE_VOLUMN_LIST, remoteUserInfoList);
}
console.log(`${NAME.PREFIX}userEnter event data: ${JSON.stringify(event)}.`);
}
private _callerChangeToConnected() {
const callRole = TUIStore.getData(StoreName.CALL, NAME.CALL_ROLE);
const callStatus = TUIStore.getData(StoreName.CALL, NAME.CALL_STATUS);
if (callStatus === CallStatus.CALLING && callRole === CallRole.CALLER) {
TUIStore.update(StoreName.CALL, NAME.CALL_STATUS, CallStatus.CONNECTED);
this._startTimer();
}
}
private _handleUserLeave(event: any): void {
console.log(`${NAME.PREFIX}userLeave event data: ${JSON.stringify(event)}.`);
const { data, userID: userId } = this._analyzeEventData(event);
data?.playerList && TUIStore.update(StoreName.CALL, NAME.PLAYER, data.playerList);
if (TUIStore.getData(StoreName.CALL, NAME.IS_GROUP)) {
const remoteUserInfoList = TUIStore.getData(StoreName.CALL, NAME.REMOTE_USER_INFO_LIST);
const prefix: string = (remoteUserInfoList.find(obj => obj.userId === userId) || {}).displayUserInfo || userId;
const text = generateText(TUIStore, CallTips.END_CALL, prefix);
TUIStore.update(StoreName.CALL, NAME.TOAST_INFO, { text });
}
userId && this._deleteRemoteUser([userId]);
}
private _unNormalEventsManager(event: any, eventName: TUICallEvent): void {
console.log(`${NAME.PREFIX}${eventName} event data: ${JSON.stringify(event)}.`);
const isGroup = TUIStore.getData(StoreName.CALL, NAME.IS_GROUP);
const remoteUserInfoList = TUIStore.getData(StoreName.CALL, NAME.REMOTE_USER_INFO_LIST);
switch (eventName) {
case TUICallEvent.REJECT:
case TUICallEvent.LINE_BUSY: {
const { userID: userId } = this._analyzeEventData(event);
let callTipsKey = eventName === TUICallEvent.REJECT ? CallTips.OTHER_SIDE_REJECT_CALL : CallTips.OTHER_SIDE_LINE_BUSY;
let text = generateText(TUIStore, callTipsKey);
if (isGroup) {
const prefix: string = (remoteUserInfoList.find(obj => obj.userId === userId) || {}).displayUserInfo || userId;
callTipsKey = eventName === TUICallEvent.REJECT ? CallTips.REJECT_CALL : CallTips.IN_BUSY;
text = generateText(TUIStore, callTipsKey, prefix);
}
TUIStore.update(StoreName.CALL, NAME.TOAST_INFO, { text });
userId && this._deleteRemoteUser([userId]);
break;
}
case TUICallEvent.NO_RESP: {
const { userIDList = [] } = this._analyzeEventData(event);
const callTipsKey = isGroup ? CallTips.TIMEOUT : CallTips.CALL_TIMEOUT;
const userInfoList: string[] = userIDList.map(userId => {
const userInfo: IUserInfo = remoteUserInfoList.find(obj => obj.userId === userId) || {};
return userInfo.displayUserInfo || userId;
});
const text = isGroup ? generateText(TUIStore, callTipsKey, userInfoList.join()) : generateText(TUIStore, callTipsKey);
TUIStore.update(StoreName.CALL, NAME.TOAST_INFO, { text });
userIDList.length > 0 && this._deleteRemoteUser(userIDList);
break;
}
case TUICallEvent.CALLING_CANCEL: {
// TUIStore.update(StoreName.CALL, NAME.TOAST_INFO, { text: generateText(TUIStore, CallTips.CANCEL) });
this._resetCallStore();
break;
}
}
}
private _handleInviteeReject(event: any): void {
this._unNormalEventsManager(event, TUICallEvent.REJECT);
}
private _handleNoResponse(event: any): void {
this._unNormalEventsManager(event, TUICallEvent.NO_RESP);
}
private _handleLineBusy(event: any): void {
this._unNormalEventsManager(event, TUICallEvent.LINE_BUSY);
}
private _handleCallingCancel(event: any): void {
this._executeExternalAfterCalling();
this._unNormalEventsManager(event, TUICallEvent.CALLING_CANCEL);
}
private _handleCallingEnd(event: any): void {
console.log(`${NAME.PREFIX}callEnd event data: ${JSON.stringify(event)}.`);
this._executeExternalAfterCalling();
this._resetCallStore();
}
// SDK_READY 后才能调用 tim 接口, 否则登录后立刻获取导致调用接口失败. v2.27.4+、v3 接口 login 后会抛出 SDK_READY
private async _handleSDKReady(event: any): Promise<void> {
let localUserInfo: IUserInfo = TUIStore.getData(StoreName.CALL, NAME.LOCAL_USER_INFO);
localUserInfo = await getMyProfile(localUserInfo.userId, this.getTim(), TUIStore);
this._defaultOfflinePushInfo.title = localUserInfo?.displayUserInfo;
TUIStore.update(StoreName.CALL, NAME.LOCAL_USER_INFO, localUserInfo);
TUIStore.update(StoreName.CALL, NAME.LOCAL_USER_INFO_EXCLUDE_VOLUMN, localUserInfo);
}
private _handleKickedOut(event: any): void {
console.log(`${NAME.PREFIX}kickOut event data: ${JSON.stringify(event)}.`);
this.kickedOut && this.kickedOut(event);
TUIStore.update(StoreName.CALL, NAME.CALL_TIPS, generateText(TUIStore, CallTips.KICK_OUT));
this._resetCallStore();
}
private _handleCallTypeChange(event: any): void {
const { newCallType, type } = this._analyzeEventData(event);
TUIStore.update(StoreName.CALL, NAME.CALL_MEDIA_TYPE, newCallType || type);
this.setSoundMode(AudioPlayBackDevice.EAR);
}
private _messageSentByMe(event: any): void {
const message = event?.data;
this.onMessageSentByMe && this.onMessageSentByMe(message);
}
// ==========================【 miniProgram 私有事件】==========================
private _handleUserUpdate(event: any): void {
const data = this._analyzeEventData(event);
data?.pusher && TUIStore.update(StoreName.CALL, NAME.PUSHER, data.pusher);
data?.playerList && TUIStore.update(StoreName.CALL, NAME.PLAYER, data.playerList);
}
// 处理 “呼叫” 抛出的异常
private _handleCallError(error: any, methodName?: string) {
if (handleRepeatedCallError(error)) return;
handlePackageError(error); // 无套餐提示, 小程序 engine 不抛出 onError
this._noDevicePermissionToast(error, CallMediaType.AUDIO);
console.error(`${NAME.PREFIX}${methodName} failed, error: ${error}.`);
this._resetCallStore();
throw error;
}
// ========================【原 Web CallKit 提供的方法】========================
public beforeCalling: ((...args: any[]) => void) | undefined; // 原来
public afterCalling: ((...args: any[]) => void) | undefined;
public onMinimized: ((...args: any[]) => void) | undefined;
public onMessageSentByMe: ((...args: any[]) => void) | undefined;
public kickedOut: ((...args: any[]) => void) | undefined;
public statusChanged: ((...args: any[]) => void) | undefined;
public setCallback(params: ICallbackParam) {
const { beforeCalling, afterCalling, onMinimized, onMessageSentByMe, kickedOut, statusChanged } = params;
beforeCalling && (this.beforeCalling = beforeCalling);
afterCalling && (this.afterCalling = afterCalling);
onMinimized && (this.onMinimized = onMinimized);
onMessageSentByMe && (this.onMessageSentByMe = onMessageSentByMe);
kickedOut && (this.kickedOut = kickedOut);
statusChanged && (this.statusChanged = statusChanged);
}
public toggleMinimize() {
const isMinimized = TUIStore.getData(StoreName.CALL, NAME.IS_MINIMIZED);
TUIStore.update(StoreName.CALL, NAME.IS_MINIMIZED, !isMinimized);
console.log(`${NAME.PREFIX}toggleMinimize: ${isMinimized} -> ${!isMinimized}.`);
this.onMinimized && this.onMinimized(isMinimized, !isMinimized);
}
private _executeExternalBeforeCalling(): void {
this.beforeCalling && this.beforeCalling();
}
private _executeExternalAfterCalling(): void {
this.afterCalling && this.afterCalling();
}
// ========================【TUICallKit 组件属性设置方法】========================
@paramValidate(VALIDATE_PARAMS.setVideoDisplayMode)
public setVideoDisplayMode(displayMode: VideoDisplayMode) {
TUIStore.update(StoreName.CALL, NAME.DISPLAY_MODE, displayMode);
}
@paramValidate(VALIDATE_PARAMS.setVideoResolution)
public async setVideoResolution(resolution: VideoResolution) {
try {
if (!resolution) return;
TUIStore.update(StoreName.CALL, NAME.VIDEO_RESOLUTION, resolution);
await this._tuiCallEngine.setVideoQuality(resolution);
} catch (error) {
console.warn(`${NAME.PREFIX}setVideoResolution failed, error: ${error}.`);
}
}
// =========================【 miniProgram 私有公共方法】=========================
// 处理用户异常退出的情况,处理了右滑退出,以及返回退出的情况。
private async _handleExceptionExit() {
const callStatus = TUIStore.getData(StoreName.CALL, NAME.CALL_STATUS);
const callRole = TUIStore.getData(StoreName.CALL, NAME.CALL_ROLE);
// 在呼叫状态下, 被叫调用 reject主叫调用 hangup
if (callStatus === CallStatus.CALLING) {
callRole === CallRole.CALLER && await this.hangup();
callRole === CallRole.CALLEE && await this.reject();
}
// 在通话状态下, 统一调用 hangup 接口
callStatus === CallStatus.CONNECTED && await this.hangup();
}
private _setLocalUserInfoAudioVideoAvailable(isAvailable: boolean, type: string) {
let localUserInfo = TUIStore.getData(StoreName.CALL, NAME.LOCAL_USER_INFO);
if (type === NAME.AUDIO) {
localUserInfo = { ...localUserInfo, isAudioAvailable: isAvailable };
}
if (type === NAME.VIDEO) {
localUserInfo = { ...localUserInfo, isVideoAvailable: isAvailable };
}
TUIStore.update(StoreName.CALL, NAME.LOCAL_USER_INFO, localUserInfo);
TUIStore.update(StoreName.CALL, NAME.LOCAL_USER_INFO_EXCLUDE_VOLUMN, localUserInfo);
}
private async _updateCallStoreBeforeCall(type: number, remoteUserInfoList: IUserInfo[], groupID?: string): Promise<void> {
const callTips = groupID || TUIStore.getData(StoreName.CALL, NAME.IS_MINIMIZED) ? CallTips.CALLER_GROUP_CALLING_MSG : CallTips.CALLER_CALLING_MSG;
let updateStoreParams: any = {
[NAME.CALL_MEDIA_TYPE]: type,
[NAME.CALL_ROLE]: CallRole.CALLER,
[NAME.REMOTE_USER_INFO_LIST]: remoteUserInfoList,
[NAME.REMOTE_USER_INFO_EXCLUDE_VOLUMN_LIST]: remoteUserInfoList,
[NAME.IS_GROUP]: !!groupID,
[NAME.CALL_TIPS]: t(callTips),
[NAME.GROUP_ID]: groupID
};
const pusher = { enableCamera: type === CallMediaType.VIDEO, enableMic: true }; // mini 默认打开麦克风
updateStoreParams = { ...updateStoreParams, [NAME.PUSHER]: pusher };
TUIStore.updateStore(updateStoreParams, StoreName.CALL);
const callStatus = await beforeCall(type, this); // 如果没有权限, 此时为 false. 因此需要在 call 后设置为 calling. 和 web 存在差异
console.log(`${NAME.PREFIX}mini beforeCall return callStatus: ${callStatus}.`);
TUIStore.update(StoreName.CALL, NAME.CALL_STATUS, callStatus);
const remoteUserInfoLists = await getRemoteUserProfile(remoteUserInfoList.map(obj => obj.userId), this.getTim(), TUIStore);
if (remoteUserInfoLists.length > 0) {
TUIStore.update(StoreName.CALL, NAME.REMOTE_USER_INFO_LIST, remoteUserInfoLists);
TUIStore.update(StoreName.CALL, NAME.REMOTE_USER_INFO_EXCLUDE_VOLUMN_LIST, remoteUserInfoLists);
}
}
private async _updateCallStoreAfterCall(userIdList: string[], response: any) {
if (response) {
TUIStore.update(StoreName.CALL, NAME.IS_CLICKABLE, true);
TUIStore.update(StoreName.CALL, NAME.ROOM_ID, response.roomID);
this._callTUIService({ message: response?.data?.message });
response.pusher && TUIStore.update(StoreName.CALL, NAME.PUSHER, response.pusher);
const callMediaType = TUIStore.getData(StoreName.CALL, NAME.CALL_MEDIA_TYPE);
(callMediaType === CallMediaType.VIDEO) && await this.openCamera(NAME.LOCAL_VIDEO);
this.setSoundMode(callMediaType === CallMediaType.AUDIO ? AudioPlayBackDevice.EAR : AudioPlayBackDevice.SPEAKER);
TUIStore.update(StoreName.CALL, NAME.CALL_STATUS, CallStatus.CALLING); // 小程序未授权时, 此时状态为 idle; web 直接设置为 calling
const localUserInfo = TUIStore.getData(StoreName.CALL, NAME.LOCAL_USER_INFO);
TUIStore.update(StoreName.CALL, NAME.LOCAL_USER_INFO, { ...localUserInfo, isEnter: true });
TUIStore.update(StoreName.CALL, NAME.LOCAL_USER_INFO_EXCLUDE_VOLUMN, { ...localUserInfo, isEnter: true });
this._setLocalUserInfoAudioVideoAvailable(true, NAME.AUDIO); // web && mini, default open audio
} else {
this._resetCallStore();
}
}
private _resetCurrentDevice() {
// 挂断后,重置当前摄像头,麦克风和扬声器为默认设备
const { cameraList, microphoneList, speakerList } = TUIStore.getData(StoreName.CALL, NAME.DEVICE_LIST);
TUIStore.update(
StoreName.CALL,
NAME.DEVICE_LIST,
{ microphoneList, cameraList, speakerList,
currentCamera: cameraList?.[0] || {},
currentMicrophone: microphoneList?.[0] || {},
currentSpeaker: speakerList?.[0] || {}
},
);
}
private _resetCallStore() {
const oldStatusStr = generateStatusChangeText(TUIStore);
this._stopTimer();
// localUserInfo, language 在通话结束后不需要清除
// callStatus 清除需要通知; isMinimized 也需要通知basic-vue3 中切小窗关闭后, 再呼叫还是小窗, 因此需要通知到组件侧)
// isGroup 也不清除(engine 先抛 cancel 事件, 再抛 reject 事件)
// displayMode、videoResolution 也不能清除, 组件不卸载, 这些属性也需保留, 否则采用默认值.
// enableFloatWindow 不清除:开启/关闭悬浮窗功能。
let notResetOrNotifyKeys = Object.keys(CALL_DATA_KEY).filter((key) => {
switch (CALL_DATA_KEY[key]) {
case NAME.CALL_STATUS:
case NAME.LANGUAGE:
case NAME.IS_GROUP:
case NAME.DISPLAY_MODE:
case NAME.VIDEO_RESOLUTION:
case NAME.ENABLE_FLOAT_WINDOW:
case NAME.LOCAL_USER_INFO:
case NAME.LOCAL_USER_INFO_EXCLUDE_VOLUMN: {
return false;
}
default: {
return true;
}
}
});
notResetOrNotifyKeys = notResetOrNotifyKeys.map(key => CALL_DATA_KEY[key]);
TUIStore.reset(StoreName.CALL, notResetOrNotifyKeys);
const callStatus = TUIStore.getData(StoreName.CALL, NAME.CALL_STATUS);
callStatus !== CallStatus.IDLE && TUIStore.reset(StoreName.CALL, [NAME.CALL_STATUS], true); // callStatus reset need notify
TUIStore.reset(StoreName.CALL, [NAME.IS_MINIMIZED], true); // isMinimized reset need notify
TUIStore.reset(StoreName.CALL, [NAME.IS_EAR_PHONE], true); // isEarPhone reset need notify
TUIStore.update(StoreName.CALL, NAME.LOCAL_USER_INFO, {
...TUIStore.getData(StoreName.CALL, NAME.LOCAL_USER_INFO),
isVideoAvailable: false,
isAudioAvailable: false,
});
TUIStore.update(StoreName.CALL, NAME.LOCAL_USER_INFO_EXCLUDE_VOLUMN, {
...TUIStore.getData(StoreName.CALL, NAME.LOCAL_USER_INFO_EXCLUDE_VOLUMN),
isVideoAvailable: false,
isAudioAvailable: false,
});
TUIStore.update(StoreName.CALL, NAME.REMOTE_USER_INFO_LIST, []);
TUIStore.update(StoreName.CALL, NAME.REMOTE_USER_INFO_EXCLUDE_VOLUMN_LIST, []);
this._resetCurrentDevice();
const newStatusStr = generateStatusChangeText(TUIStore);
if (oldStatusStr !== newStatusStr) {
this.statusChanged && this.statusChanged({ oldStatus: oldStatusStr, newStatus: newStatusStr });
}
}
private _noDevicePermissionToast(error: any, type: CallMediaType): void {
if (handleNoDevicePermissionError(error)) {
let text = '';
if (type === CallMediaType.AUDIO) {
text = generateText(TUIStore, CallTips.NO_MICROPHONE_DEVICE_PERMISSION);
}
if (type === CallMediaType.VIDEO) {
text = generateText(TUIStore, CallTips.NO_CAMERA_DEVICE_PERMISSION);
}
text && TUIStore.update(StoreName.CALL, NAME.TOAST_INFO, { text, type: NAME.ERROR });
console.error(`${NAME.PREFIX}call failed, error: ${error.message}.`);
}
}
// 通话时长更新
private _startTimer(): void {
if (this._timerId === -1) {
this._startTimeStamp = performanceNow();
this._timerId = timer.run(NAME.TIMEOUT, this._updateCallDuration.bind(this), { delay: 1000 });
}
}
private _updateCallDuration(): void {
const callDurationNum = Math.round((performanceNow() - this._startTimeStamp) / 1000); // miniProgram stop timer when background
const callDurationStr = formatTime(callDurationNum);
TUIStore.update(StoreName.CALL, NAME.CALL_DURATION, callDurationStr);
}
private _stopTimer(): void {
if (this._timerId !== -1) {
timer.clearTask(this._timerId);
this._timerId = -1;
}
}
private _deleteRemoteUser(userIdList: string[]): void {
if (userIdList.length === 0) return;
let remoteUserInfoList = TUIStore.getData(StoreName.CALL, NAME.REMOTE_USER_INFO_LIST);
userIdList.forEach((userId) => {
remoteUserInfoList = remoteUserInfoList.filter((obj: IUserInfo) => obj.userId !== userId);
});
TUIStore.update(StoreName.CALL, NAME.REMOTE_USER_INFO_LIST, remoteUserInfoList);
TUIStore.update(StoreName.CALL, NAME.REMOTE_USER_INFO_EXCLUDE_VOLUMN_LIST, remoteUserInfoList);
}
private _analyzeEventData(event: any): any {
return event?.data || {}; // mini INVITED
}
// =========================【调用 chat api】=========================
// 获取群成员
public async getGroupMemberList(count: number, offset: number) {
const groupID = TUIStore.getData(StoreName.CALL, NAME.GROUP_ID);
let groupMemberList = await getGroupMemberList(groupID, this.getTim(), count, offset);
return groupMemberList;
}
// 获取群信息
public async getGroupProfile() {
const groupID: string = TUIStore.getData(StoreName.CALL, NAME.GROUP_ID);
return await getGroupProfile(groupID, this.getTim());
}
// =========================【监听 TUIStore 中的状态】=========================
private _handleCallStatusChange = async (value: CallStatus) => {
try {
const bellParams: IBellParams = {
callRole: TUIStore.getData(StoreName.CALL, NAME.CALL_ROLE),
callStatus: TUIStore.getData(StoreName.CALL, NAME.CALL_STATUS),
};
this._bellContext.setBellProperties(bellParams);
if (value === CallStatus.CALLING) {
await this?._bellContext?.play();
} else {
// 状态变更通知
if (value === CallStatus.CONNECTED) {
const isGroup = TUIStore.getData(StoreName.CALL, NAME.IS_GROUP);
const callMediaType = TUIStore.getData(StoreName.CALL, NAME.CALL_MEDIA_TYPE);
const remoteUserInfoList = TUIStore.getData(StoreName.CALL, NAME.REMOTE_USER_INFO_LIST);
const oldStatus = isGroup ? StatusChange.DIALING_GROUP : StatusChange.DIALING_C2C;
TUIStore.update(StoreName.CALL, NAME.CALL_TIPS, '');
this.statusChanged && this.statusChanged({ oldStatus, newStatus: generateStatusChangeText(TUIStore) });
if (!isGroup && callMediaType === CallMediaType.VIDEO) {
this.switchScreen(remoteUserInfoList[0].domId);
}
}
await this?._bellContext?.stop();
}
} catch (error) {
console.warn(`${NAME.PREFIX}handleCallStatusChange, ${error}.`);
}
};
private _watchTUIStore() {
TUIStore?.watch(StoreName.CALL, {
[NAME.CALL_STATUS]: this._handleCallStatusChange,
});
}
private _unwatchTUIStore() {
TUIStore?.unwatch(StoreName.CALL, {
[NAME.CALL_STATUS]: this._handleCallStatusChange,
});
}
// =========================【web 融合 chat 提供的方法】=========================
public bindTUICore(TUICore: any) {
this._TUICore = TUICore;
}
// 下面方法用于接入 TUICore
private _callTUIService(params) {
const { message } = params || {};
TUICore.callService({
serviceName: TUIConstants.TUIChat.SERVICE.NAME,
method: TUIConstants.TUIChat.SERVICE.METHOD.UPDATE_MESSAGE_LIST,
params: { message },
});
}
public async onNotifyEvent(eventName: string, subKey: string) {
try {
if (eventName === TUIConstants.TUILogin.EVENT.LOGIN_STATE_CHANGED) {
if (subKey === TUIConstants.TUILogin.EVENT_SUB_KEY.USER_LOGIN_SUCCESS) {
// TUICallkit 收到登录成功时执行自己的业务逻辑处理
// @ts-ignore
const { chat, userID, userSig, SDKAppID } = TUILogin.getContext();
await this.init({ tim: chat, userID, userSig, sdkAppID: SDKAppID, isFromChat: true });
} else if (subKey === TUIConstants.TUILogin.EVENT_SUB_KEY.USER_LOGOUT_SUCCESS) {
await this.destroyed();
}
}
} catch (error) {
console.error(`${NAME.PREFIX}TUICore onNotifyEvent failed, error: ${error}.`);
}
}
public async onCall(method: String, params: any) {
if (method === TUIConstants.TUICalling.SERVICE.METHOD.START_CALL) {
await this._handleTUICoreOnClick(params, params.type);
}
}
private async _handleTUICoreOnClick(options, type: CallMediaType) {
try {
const { groupID, userIDList = [], ...rest } = options;
if (groupID) {
await this.groupCall({ groupID, userIDList, type, ...rest });
} else if (userIDList.length === 1) {
await this.call({ userID: userIDList[0], type, ...rest });
}
} catch (error: any) {
console.debug(error);
}
}
public onGetExtension(extensionID: string, params: any) {
if (extensionID === TUIConstants.TUIChat.EXTENSION.INPUT_MORE.EXT_ID) {
const list = [];
const audioCallExtension: ExtensionInfo = {
weight: 1000,
text: '语音通话',
icon: AudioCallIcon,
data: {
name: 'voiceCall',
},
listener: {
onClicked: async options => await this._handleTUICoreOnClick(options, options.type || CallMediaType.AUDIO), // 点击时发起通话
},
};
const videoCallExtension: ExtensionInfo = {
weight: 900,
text: '视频通话',
icon: VideoCallIcon,
data: {
name: 'videoCall',
},
listener: {
onClicked: async options => await this._handleTUICoreOnClick(options, options.type || CallMediaType.VIDEO), // 点击时发起通话
},
};
if (!params?.filterVoice) {
list.push(audioCallExtension);
}
if (!params?.filterVideo) {
list.push(videoCallExtension);
}
return list;
}
}
}

View File

@ -0,0 +1,55 @@
import { CallMediaType, CallStatus } from '../const/index';
export function initialUI() {
// 收起键盘
// @ts-ignore
wx.hideKeyboard({
complete: () => {},
});
};
// 检测运行时环境, 当是微信开发者工具时, 提示用户需要手机调试
export function checkRunPlatform() {
// @ts-ignore
const systemInfo = wx.getSystemInfoSync();
if (systemInfo.platform === 'devtools') {
// 当前运行在微信开发者工具里
// @ts-ignore
wx.showModal({
icon: 'none',
title: '运行环境提醒',
content: '微信开发者工具不支持原生推拉流组件(即 <live-pusher> 和 <live-player> 标签),请使用真机调试或者扫码预览。',
showCancel: false,
});
}
};
export function initAndCheckRunEnv() {
initialUI(); // miniProgram 收起键盘, 隐藏 tabBar
checkRunPlatform(); // miniProgram 检测运行时环境
}
export async function beforeCall(type: CallMediaType, that: any) {
try {
initAndCheckRunEnv();
// 检查设备权限
const deviceMap = {
microphone: true,
camera: type === CallMediaType.VIDEO,
};
const hasDevicePermission = await that._tuiCallEngine.deviceCheck(deviceMap); // miniProgram 检查设备权限
return hasDevicePermission ? CallStatus.CALLING : CallStatus.IDLE;
} catch (error) {
console.debug(error);
return CallStatus.IDLE;
}
}
// 套餐问题提示, 小程序最低需要群组通话版, 1v1 通话版本使用 TRTC 就会报错
export function handlePackageError(error) {
if (error?.code === -1002) {
// @ts-ignore
wx.showModal({
icon: 'none',
title: 'error',
content: error?.message || '',
showCancel: false,
});
}
}

View File

@ -30,7 +30,7 @@ function setDefaultUserInfo(userId, domId) {
exports.setDefaultUserInfo = setDefaultUserInfo;
// 获取个人用户信息
function getMyProfile(myselfUserId, tim, TUIStore) {
var _a, _b, _c;
var _a, _b, _c, _d, _e;
return __awaiter(this, void 0, void 0, function* () {
let localUserInfo = setDefaultUserInfo(myselfUserId, index_1.NAME.LOCAL_VIDEO);
try {
@ -39,7 +39,7 @@ function getMyProfile(myselfUserId, tim, TUIStore) {
const res = yield tim.getMyProfile();
const currentLocalUserInfo = TUIStore === null || TUIStore === void 0 ? void 0 : TUIStore.getData(index_1.StoreName.CALL, index_1.NAME.LOCAL_USER_INFO); // localUserInfo may have been updated
if ((res === null || res === void 0 ? void 0 : res.code) === 0) {
localUserInfo = Object.assign(Object.assign(Object.assign({}, localUserInfo), currentLocalUserInfo), { userId: (_a = res === null || res === void 0 ? void 0 : res.data) === null || _a === void 0 ? void 0 : _a.userID, nick: (_b = res === null || res === void 0 ? void 0 : res.data) === null || _b === void 0 ? void 0 : _b.nick, avatar: (_c = res === null || res === void 0 ? void 0 : res.data) === null || _c === void 0 ? void 0 : _c.avatar });
localUserInfo = Object.assign(Object.assign(Object.assign({}, localUserInfo), currentLocalUserInfo), { userId: (_a = res === null || res === void 0 ? void 0 : res.data) === null || _a === void 0 ? void 0 : _a.userID, nick: (_b = res === null || res === void 0 ? void 0 : res.data) === null || _b === void 0 ? void 0 : _b.nick, avatar: (_c = res === null || res === void 0 ? void 0 : res.data) === null || _c === void 0 ? void 0 : _c.avatar, displayUserInfo: ((_d = res === null || res === void 0 ? void 0 : res.data) === null || _d === void 0 ? void 0 : _d.nick) || ((_e = res === null || res === void 0 ? void 0 : res.data) === null || _e === void 0 ? void 0 : _e.userID) });
}
return localUserInfo;
}

View File

@ -0,0 +1,142 @@
import { NAME, StoreName, CallStatus, StatusChange, CallMediaType } from '../const/index';
import { IUserInfo } from '../interface/ICallService';
import { ITUIStore } from '../interface/ITUIStore';
import { t } from '../locales/index';
// 设置默认的 UserInfo 信息
export function setDefaultUserInfo(userId: string, domId?: string): IUserInfo {
const userInfo: IUserInfo = {
userId,
nick: '',
avatar: '',
remark: '',
displayUserInfo: '',
isAudioAvailable: false,
isVideoAvailable: false,
isEnter: false,
domId: domId || userId,
};
return domId ? userInfo : { ...userInfo, isEnter: false }; // localUserInfo 没有 isEnter, remoteUserInfoList 有 isEnter
}
// 获取个人用户信息
export async function getMyProfile(myselfUserId: string, tim: any, TUIStore: any): Promise<IUserInfo> {
let localUserInfo: IUserInfo = setDefaultUserInfo(myselfUserId, NAME.LOCAL_VIDEO);
try {
if (!tim) return localUserInfo;
const res = await tim.getMyProfile();
const currentLocalUserInfo = TUIStore?.getData(StoreName.CALL, NAME.LOCAL_USER_INFO); // localUserInfo may have been updated
if (res?.code === 0) {
localUserInfo = {
...localUserInfo,
...currentLocalUserInfo,
userId: res?.data?.userID,
nick: res?.data?.nick,
avatar: res?.data?.avatar,
displayUserInfo: res?.data?.nick || res?.data?.userID,
};
}
return localUserInfo;
} catch (error) {
console.error(`${NAME.PREFIX}getMyProfile failed, error: ${error}.`);
return localUserInfo;
}
}
// 获取远端用户列表信息
export async function getRemoteUserProfile(userIdList: Array<string>, tim: any, TUIStore: any): Promise<any> {
let remoteUserInfoList: IUserInfo[] = userIdList.map((userId: string) => setDefaultUserInfo(userId));
try {
if (!tim) return remoteUserInfoList;
const res = await tim.getFriendProfile({ userIDList: userIdList });
if (res.code === 0) {
const { friendList = [], failureUserIDList = [] } = res.data;
let unFriendList: IUserInfo[] = failureUserIDList.map((obj: any) => obj.userID);
if (failureUserIDList.length > 0) {
const res = await tim.getUserProfile({ userIDList: failureUserIDList.map((obj: any) => obj.userID) });
if (res?.code === 0) {
unFriendList = res?.data || [];
}
}
const currentRemoteUserInfoList = TUIStore?.getData(StoreName.CALL, NAME.REMOTE_USER_INFO_LIST); // remoteUserInfoList may have been updated
const tempFriendIdList: string[] = friendList.map((obj: any) => obj.userID);
const tempUnFriendIdList: string[] = unFriendList.map((obj: any) => obj.userID);
remoteUserInfoList = userIdList.map((userId: string) => {
const defaultUserInfo: IUserInfo = setDefaultUserInfo(userId);
const friendListIndex: number = tempFriendIdList.indexOf(userId);
const unFriendListIndex: number = tempUnFriendIdList.indexOf(userId);
let remark = '';
let nick = '';
let displayUserInfo = '' ;
let avatar = '';
if (friendListIndex !== -1) {
remark = friendList[friendListIndex]?.remark || '';
nick = friendList[friendListIndex]?.profile?.nick || '';
displayUserInfo = remark || nick || defaultUserInfo.userId || '';
avatar = friendList[friendListIndex]?.profile?.avatar || '';
}
if (unFriendListIndex !== -1) {
nick = unFriendList[unFriendListIndex]?.nick || '';
displayUserInfo = nick || defaultUserInfo.userId || '';
avatar = unFriendList[unFriendListIndex]?.avatar || '';
}
const userInfo = currentRemoteUserInfoList.find(subObj => subObj.userId === userId) || {};
return { ...defaultUserInfo, ...userInfo, remark, nick, displayUserInfo, avatar };
});
}
return remoteUserInfoList;
} catch (error) {
console.error(`${NAME.PREFIX}getRemoteUserProfile failed, error: ${error}.`);
return remoteUserInfoList;
}
}
// 生成弹框提示文案
export function generateText(TUIStore: ITUIStore, key: string, prefix?: string, suffix?: string): string {
const isGroup = TUIStore.getData(StoreName.CALL, NAME.IS_GROUP);
let callTips = `${t(key)}`;
if (isGroup) {
callTips = prefix ? `${prefix} ${callTips}` : callTips;
callTips = suffix ? `${callTips} ${suffix}` : callTips;
}
return callTips;
}
// 生成 statusChange 抛出的字符串
export function generateStatusChangeText(TUIStore: ITUIStore): string {
const callStatus = TUIStore.getData(StoreName.CALL, NAME.CALL_STATUS);
if (callStatus === CallStatus.IDLE) {
return StatusChange.IDLE;
}
const isGroup = TUIStore.getData(StoreName.CALL, NAME.IS_GROUP);
if (callStatus === CallStatus.CALLING) {
return isGroup ? StatusChange.DIALING_GROUP : StatusChange.DIALING_C2C;
}
const callMediaType = TUIStore.getData(StoreName.CALL, NAME.CALL_MEDIA_TYPE);
if (isGroup) {
return callMediaType === CallMediaType.AUDIO ? StatusChange.CALLING_GROUP_AUDIO : StatusChange.CALLING_GROUP_VIDEO;
}
return callMediaType === CallMediaType.AUDIO ? StatusChange.CALLING_C2C_AUDIO : StatusChange.CALLING_C2C_VIDEO;
}
// 获取群组[offset, count + offset]区间成员
export async function getGroupMemberList(groupID: string, tim: any, count, offset) {
let groupMemberList = [];
try {
const res = await tim.getGroupMemberList({ groupID, count, offset });
if (res.code === 0) {
return res.data.memberList || groupMemberList;
}
} catch(error) {
console.error(`${NAME.PREFIX}getGroupMember failed, error: ${error}.`);
return groupMemberList;
}
}
// 获取 IM 群信息
export async function getGroupProfile(groupID: string, tim: any): Promise<any> {
let groupProfile = {};
try {
const res = await tim.getGroupProfile({ groupID });
if (res.code === 0) {
return res.data.group || groupProfile;
}
} catch(error) {
console.error(`${NAME.PREFIX}getGroupProfile failed, error: ${error}.`);
return groupProfile;
}
}

View File

@ -0,0 +1,43 @@
import { APP_NAMESPACE, IS_PC, IS_H5, IN_WX_MINI_APP, IN_UNI_NATIVE_APP, IN_UNI_APP, IS_MAC, IS_WIN } from '../utils/env';
import { ITUIGlobal } from '../interface/ITUIGlobal';
export default class TUIGlobal implements ITUIGlobal {
static instance: TUIGlobal;
public global: any = APP_NAMESPACE;
public isPC: boolean = false;
public isH5: boolean = false;
public isWeChat: boolean = false;
public isApp: boolean = false;
public isUniPlatform: boolean = false;
public isOfficial: boolean = false;
public isWIN: boolean = false;
public isMAC: boolean = false;
constructor() {
this.initEnv();
}
/**
* TUIGlobal
* @returns {TUIGlobal}
*/
static getInstance() {
if (!TUIGlobal.instance) {
TUIGlobal.instance = new TUIGlobal();
}
return TUIGlobal.instance;
}
initEnv() {
this.isPC = IS_PC;
this.isH5 = IS_H5;
this.isWeChat = IN_WX_MINI_APP;
this.isApp = IN_UNI_NATIVE_APP && !IN_WX_MINI_APP; // uniApp 打包小程序时 IN_UNI_NATIVE_APP 为 true所以此处需要增加条件
this.isUniPlatform = IN_UNI_APP;
this.isWIN = IS_WIN;
this.isMAC = IS_MAC;
}
initOfficial(SDKAppID: number) {
this.isOfficial = (SDKAppID === 1400187352 || SDKAppID === 1400188366);
}
}

View File

@ -9,7 +9,9 @@ class CallStore {
callRole: index_1.CallRole.UNKNOWN,
callMediaType: index_1.CallMediaType.UNKNOWN,
localUserInfo: { userId: '' },
localUserInfoExcludeVolume: { userId: '' },
remoteUserInfoList: [],
remoteUserInfoExcludeVolumeList: [],
callerUserInfo: { userId: '' },
isGroup: false,
callDuration: '00:00:00',
@ -24,6 +26,7 @@ class CallStore {
showPermissionTip: false,
groupID: '',
roomID: 0,
cameraPosition: index_1.CameraPosition.FRONT,
// TUICallKit 组件上的属性
displayMode: index_1.VideoDisplayMode.COVER,
videoResolution: index_1.VideoResolution.RESOLUTION_480P,

View File

@ -0,0 +1,66 @@
import { CallStatus, CallRole, CallMediaType, VideoDisplayMode, VideoResolution, CameraPosition } from '../const/index';
import { ICallStore } from '../interface/ICallStore';
import { getLanguage } from '../utils/common-utils';
export default class CallStore {
public defaultStore: ICallStore = {
callStatus: CallStatus.IDLE,
callRole: CallRole.UNKNOWN,
callMediaType: CallMediaType.UNKNOWN,
localUserInfo: { userId: '' },
localUserInfoExcludeVolume: { userId: '' },
remoteUserInfoList: [],
remoteUserInfoExcludeVolumeList: [],
callerUserInfo: { userId: '' },
isGroup: false,
callDuration: '00:00:00', // 通话时长
callTips: '', // 通话提示的信息. 例如: '等待谁接听', 'xxx 拒绝通话', 'xxx 挂断通话'
toastInfo: { text: '' }, // 远端用户挂断、拒绝、超时、忙线等的 toast 提示信息
isMinimized: false, // 用来记录当前是否悬浮窗模式
enableFloatWindow: false, // 开启/关闭悬浮窗功能设置为false通话界面左上角的悬浮窗按钮会隐藏
bigScreenUserId: '', // 当前大屏幕显示的 userID 用户
language: getLanguage(), // en, zh-cn
isClickable: false, // 是否可点击, 用于按钮增加 loading 效果,不可点击
deviceList: { cameraList: [], microphoneList: [], currentCamera: {}, currentMicrophone: {} },
showPermissionTip: false,
groupID: '',
roomID: 0,
cameraPosition: CameraPosition.FRONT, // 前置或后置值为front, back
// TUICallKit 组件上的属性
displayMode: VideoDisplayMode.COVER, // 设置预览远端的画面显示模式
videoResolution: VideoResolution.RESOLUTION_480P,
showSelectUser: false,
// 小程序相关属性
pusher: {},
player: [],
isEarPhone: false, // 是否是听筒, 默认: false
};
public store: ICallStore = Object.assign({}, this.defaultStore);;
public update(key: keyof ICallStore, data: any): void {
switch (key) {
// case 'callTips':
// break;
default:
// resolve "Type 'any' is not assignable to type 'never'.ts", ref: https://github.com/microsoft/TypeScript/issues/31663
(this.store[key] as any) = data as any;
}
}
public getData(key: string | undefined): any {
if (!key) return this.store;
return this.store[key as keyof ICallStore];
}
// reset call store
public reset(keyList: Array<string> = []) {
if (keyList.length === 0) {
keyList = Object.keys(this.store);
}
const resetToDefault = keyList.reduce((acc, key) => ({ ...acc, [key]: this.defaultStore[key as keyof ICallStore] }), {});
this.store = {
...this.defaultStore,
...this.store,
...resetToDefault,
};
}
}

View File

@ -0,0 +1,157 @@
import { ITUIStore, IOptions, Task } from '../interface/ITUIStore';
import { StoreName, NAME } from '../const/index';
import CallStore from './callStore';
import { isString, isNumber, isBoolean } from '../utils/common-utils';
export default class TUIStore implements ITUIStore {
static instance: TUIStore;
public task: Task;
private storeMap: Partial<Record<StoreName, any>>;
private timerId: number = -1;
constructor() {
this.storeMap = {
[StoreName.CALL]: new CallStore(),
};
// todo 此处后续优化结构后调整
this.task = {} as Task; // 保存监听回调列表
}
/**
* TUIStore
* @returns {TUIStore}
*/
static getInstance() {
if (!TUIStore.instance) {
TUIStore.instance = new TUIStore();
}
return TUIStore.instance;
}
/**
* UI
* @param {StoreName} storeName store
* @param {IOptions} options
* @param {Object} params
* @param {String} params.notifyRangeWhenWatch , 'all' - key ; 'myself' - key ;
*/
watch(storeName: StoreName, options: IOptions, params?: any) {
if (!this.task[storeName]) {
this.task[storeName] = {};
}
const watcher = this.task[storeName];
Object.keys(options).forEach((key) => {
const callback = options[key];
if (!watcher[key]) {
watcher[key] = new Map();
}
watcher[key].set(callback, 1);
const { notifyRangeWhenWatch } = params || {};
// 注册监听后, 通知所有注册该 key 的监听,使用 'all' 时要特别注意是否对其他地方的监听产生影响
if (notifyRangeWhenWatch === NAME.ALL) {
this.notify(storeName, key);
}
// 注册监听后, 仅通知自己本次监听该 key 的数据
if (notifyRangeWhenWatch === NAME.MYSELF) {
const data = this.getData(storeName, key);
callback.call(this, data);
}
});
}
/**
* UI
* @param {StoreName} storeName store
* @param {IOptions} options ,
*/
unwatch(storeName: StoreName, options: IOptions) {
// todo 该接口暂未支持unwatch掉同一类如仅传入store注销掉该store下的所有callback同样options仅传入key注销掉该key下的所有callback
// options的callback function为必填参数后续修改
if (!this.task[storeName]) {
return;
};
// if (isString(options)) {
// // 移除所有的监听
// if (options === '*') {
// const watcher = this.task[storeName];
// Object.keys(watcher).forEach(key => {
// watcher[key].clear();
// });
// } else {
// console.warn(`${NAME.PREFIX}unwatch warning: options is ${options}`);
// }
// return;
// }
const watcher = this.task[storeName];
Object.keys(options).forEach((key: string) => {
watcher[key].delete(options[key]);
});
}
/**
* store messageList
* @param {StoreName} storeName store
* @param {string} key key
* @param {unknown} data
*/
update(storeName: StoreName, key: string, data: unknown) {
// 基本数据类型时, 如果相等, 就不进行更新, 减少不必要的 notify
if (isString(data) || isNumber(data) || isBoolean(data)) {
const currentData = this.storeMap[storeName]['store'][key]; // eslint-disable-line
if (currentData === data) return;
}
this.storeMap[storeName]?.update(key, data);
this.notify(storeName, key);
}
/**
* Store
* @param {StoreName} storeName store
* @param {string} key key
* @returns {Any}
*/
getData(storeName: StoreName, key: string) {
return this.storeMap[storeName]?.getData(key);
}
/**
* UI
* @param {StoreName} storeName store
* @param {string} key key
*/
private notify(storeName: StoreName, key: string) {
if (!this.task[storeName]) {
return;
}
const watcher = this.task[storeName];
if (watcher[key]) {
const callbackMap = watcher[key];
const data = this.getData(storeName, key);
for (const [callback] of callbackMap.entries()) {
callback.call(this, data);
}
}
}
public reset(storeName: StoreName, keyList: Array<string> = [], isNotificationNeeded = false) {
if (storeName in this.storeMap) {
const store = this.storeMap[storeName];
// reset all
if (keyList.length === 0) {
keyList = Object.keys(store?.store);
}
store.reset(keyList);
if (isNotificationNeeded) {
keyList.forEach((key) => {
this.notify(storeName, key);
});
}
}
}
// 批量修改多个 key-value
public updateStore(params: any, name?: StoreName): void {
const storeName = name ? name : StoreName.CALL;
Object.keys(params).forEach((key) => {
this.update(storeName, key, params[key]);
});
}
}

Binary file not shown.

Binary file not shown.

View File

@ -89,5 +89,10 @@ export declare enum AudioPlayBackDevice {
}
export declare enum DeviceType {
MICROPHONE = "microphone",
CAMERA = "camera"
CAMERA = "camera",
SPEAKER = "speaker"
}
export declare enum CameraPosition {
FRONT = 0,
BACK = 1
}

View File

@ -1,6 +1,6 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.DeviceType = exports.AudioPlayBackDevice = exports.StatusChange = exports.LanguageType = exports.VideoResolution = exports.VideoDisplayMode = exports.CallStatus = exports.CallRole = exports.CallMediaType = exports.StoreName = void 0;
exports.CameraPosition = exports.DeviceType = exports.AudioPlayBackDevice = exports.StatusChange = exports.LanguageType = exports.VideoResolution = exports.VideoDisplayMode = exports.CallStatus = exports.CallRole = exports.CallMediaType = exports.StoreName = void 0;
/**
* @property {String} call 1v1 通话 + 群组通话
* @property {String} CUSTOM 自定义 Store
@ -101,4 +101,10 @@ var DeviceType;
(function (DeviceType) {
DeviceType["MICROPHONE"] = "microphone";
DeviceType["CAMERA"] = "camera";
DeviceType["SPEAKER"] = "speaker";
})(DeviceType = exports.DeviceType || (exports.DeviceType = {}));
var CameraPosition;
(function (CameraPosition) {
CameraPosition[CameraPosition["FRONT"] = 0] = "FRONT";
CameraPosition[CameraPosition["BACK"] = 1] = "BACK";
})(CameraPosition = exports.CameraPosition || (exports.CameraPosition = {}));

View File

@ -0,0 +1,107 @@
/**
* @property {String} call 1v1 +
* @property {String} CUSTOM Store
*/
export enum StoreName {
CALL = 'call',
CUSTOM = 'custom'
}
/**
* @property {String} idle
* @property {String} connecting
* @property {String} connected
*/
export enum CallMediaType {
UNKNOWN,
AUDIO,
VIDEO,
}
/**
* @property {String} caller
* @property {String} callee
*/
export enum CallRole {
UNKNOWN = 'unknown',
CALLEE = 'callee',
CALLER = 'caller',
}
/**
* @property {String} idle
* @property {String} calling
* @property {String} connected
*/
export enum CallStatus {
IDLE = 'idle',
CALLING = 'calling',
CONNECTED = 'connected',
}
/**
*
* 使 cover ; 使 contain
* @property {String} contain
* @property {String} cover
* @property {String} fill ( v4.12.1 )
*/
export enum VideoDisplayMode {
CONTAIN = 'contain',
COVER = 'cover',
FILL = 'fill',
}
/**
*
* @property {String} 480p
* @property {String} 720p
* @property {String} 1080p
*/
export enum VideoResolution {
RESOLUTION_480P = '480p',
RESOLUTION_720P = '720p',
RESOLUTION_1080P = '1080p',
}
// 支持的语言
export enum LanguageType {
EN = 'en',
'ZH-CN' = 'zh-cn',
JA_JP = 'ja_JP',
}
export type TDeviceList = {
cameraList: any[],
microphoneList: any[],
currentCamera: any,
currentMicrophone: any,
};
/* === 【原来 TUICallKit 对外暴露】=== */
// 原来 web callKit 定义通知外部状态变更的变量, 对外暴露
export const StatusChange = {
IDLE: "idle",
BE_INVITED: "be-invited",
DIALING_C2C: "dialing-c2c",
DIALING_GROUP: "dialing-group",
CALLING_C2C_AUDIO: "calling-c2c-audio",
CALLING_C2C_VIDEO: "calling-c2c-video",
CALLING_GROUP_AUDIO: "calling-group-audio",
CALLING_GROUP_VIDEO: "calling-group-video",
} as const;
/* === 【小程序使用】=== */
/**
* @property {String} ear
* @property {String} speaker
*/
export enum AudioPlayBackDevice {
EAR = 'ear',
SPEAKER = 'speaker',
};
export enum DeviceType {
MICROPHONE = 'microphone',
CAMERA = 'camera',
SPEAKER = 'speaker',
}
export enum CameraPosition {
FRONT = 0,
BACK = 1,
}

View File

@ -0,0 +1,20 @@
// 错误码
export const ErrorCode: any = {
SWITCH_TO_AUDIO_CALL_FAILED: 60001,
SWITCH_TO_VIDEO_CALL_FAILED: 60002,
MICROPHONE_UNAVAILABLE: 60003,
CAMERA_UNAVAILABLE: 60004,
BAN_DEVICE: 60005,
NOT_SUPPORTED_WEBRTC: 60006,
ERROR_BLACKLIST: 20007,
} as const;
export const ErrorMessage: any = {
SWITCH_TO_AUDIO_CALL_FAILED: 'switchToAudioCall-call-failed',
SWITCH_TO_VIDEO_CALL_FAILED: 'switchToVideoCall-call-failed',
MICROPHONE_UNAVAILABLE: 'microphone-unavailable',
CAMERA_UNAVAILABLE: 'camera-unavailable',
BAN_DEVICE: 'ban-device',
NOT_SUPPORTED_WEBRTC: 'not-supported-webrtc',
ERROR_BLACKLIST: 'blacklist-user-tips'
} as const;

View File

@ -26,7 +26,9 @@ exports.CALL_DATA_KEY = {
CALL_ROLE: 'callRole',
CALL_MEDIA_TYPE: 'callMediaType',
LOCAL_USER_INFO: 'localUserInfo',
LOCAL_USER_INFO_EXCLUDE_VOLUMN: 'localUserInfoExcludeVolume',
REMOTE_USER_INFO_LIST: 'remoteUserInfoList',
REMOTE_USER_INFO_EXCLUDE_VOLUMN_LIST: 'remoteUserInfoExcludeVolumeList',
CALLER_USER_INFO: 'callerUserInfo',
IS_GROUP: 'isGroup',
CALL_DURATION: 'callDuration',
@ -47,7 +49,7 @@ exports.CALL_DATA_KEY = {
ROOM_ID: 'roomID',
SHOW_SELECT_USER: 'showSelectUser',
};
exports.NAME = Object.assign({ PREFIX: '【CallService】', AUDIO: 'audio', VIDEO: 'video', LOCAL_VIDEO: 'localVideo', ERROR: 'error', TIMEOUT: 'timeout', RAF: 'raf', INTERVAL: 'interval', DEFAULT: 'default', BOOLEAN: 'boolean', STRING: 'string', NUMBER: 'number', OBJECT: 'object', ARRAY: 'array', FUNCTION: 'function', UNDEFINED: "undefined", ALL: 'all', MYSELF: 'myself', DEVICE_LIST: 'deviceList' }, exports.CALL_DATA_KEY);
exports.NAME = Object.assign({ PREFIX: '【CallService】', AUDIO: 'audio', VIDEO: 'video', LOCAL_VIDEO: 'localVideo', ERROR: 'error', TIMEOUT: 'timeout', RAF: 'raf', INTERVAL: 'interval', DEFAULT: 'default', BOOLEAN: 'boolean', STRING: 'string', NUMBER: 'number', OBJECT: 'object', ARRAY: 'array', FUNCTION: 'function', UNDEFINED: "undefined", ALL: 'all', MYSELF: 'myself', DEVICE_LIST: 'deviceList', CAMERA_POSITION: 'cameraPosition' }, exports.CALL_DATA_KEY);
exports.AudioCallIcon = 'https://web.sdk.qcloud.com/component/TUIKit/assets/call.png';
exports.VideoCallIcon = 'https://web.sdk.qcloud.com/component/TUIKit/assets/call-video-reverse.svg';
exports.MAX_NUMBER_ROOM_ID = 2147483647;

View File

@ -0,0 +1,70 @@
export * from './call';
export * from './error';
export * from './log';
// import { keys } from 'ts-transformer-keys';
// import { ICallStore } from '../interface/store';
// console.warn('--> ', keys<ICallStore>())
export const CALL_DATA_KEY: any = {
CALL_STATUS: 'callStatus',
CALL_ROLE: 'callRole',
CALL_MEDIA_TYPE: 'callMediaType',
LOCAL_USER_INFO: 'localUserInfo',
LOCAL_USER_INFO_EXCLUDE_VOLUMN: 'localUserInfoExcludeVolume',
REMOTE_USER_INFO_LIST: 'remoteUserInfoList',
REMOTE_USER_INFO_EXCLUDE_VOLUMN_LIST: 'remoteUserInfoExcludeVolumeList',
CALLER_USER_INFO: 'callerUserInfo',
IS_GROUP: 'isGroup',
CALL_DURATION: 'callDuration',
CALL_TIPS: 'callTips',
TOAST_INFO: 'toastInfo',
IS_MINIMIZED: 'isMinimized',
ENABLE_FLOAT_WINDOW: 'enableFloatWindow',
BIG_SCREEN_USER_ID: 'bigScreenUserId',
LANGUAGE: 'language',
IS_CLICKABLE: 'isClickable',
DISPLAY_MODE: 'displayMode',
VIDEO_RESOLUTION: 'videoResolution',
PUSHER: 'pusher',
PLAYER: 'player',
IS_EAR_PHONE: 'isEarPhone',
SHOW_PERMISSION_TIP: 'SHOW_PERMISSION_TIP',
GROUP_ID: 'groupID',
ROOM_ID: 'roomID',
SHOW_SELECT_USER: 'showSelectUser',
};
export const NAME = {
PREFIX: '【CallService】',
AUDIO: 'audio',
VIDEO: 'video',
LOCAL_VIDEO: 'localVideo',
ERROR: 'error',
TIMEOUT: 'timeout',
RAF: 'raf',
INTERVAL: 'interval',
DEFAULT: 'default',
BOOLEAN: 'boolean',
STRING: 'string',
NUMBER: 'number',
OBJECT: 'object',
ARRAY: 'array',
FUNCTION: 'function',
UNDEFINED: "undefined",
ALL: 'all',
MYSELF: 'myself',
DEVICE_LIST: 'deviceList',
CAMERA_POSITION: 'cameraPosition',
...CALL_DATA_KEY,
};
export const AudioCallIcon = 'https://web.sdk.qcloud.com/component/TUIKit/assets/call.png';
export const VideoCallIcon = 'https://web.sdk.qcloud.com/component/TUIKit/assets/call-video-reverse.svg';
export const MAX_NUMBER_ROOM_ID = 2147483647;
export enum PLATFORM {
// eslint-disable-next-line no-unused-vars
MAC = 'mac',
// eslint-disable-next-line no-unused-vars
WIN = 'win',
}

View File

@ -0,0 +1,9 @@
/* eslint-disable */
// 唯一一个变量格式有问题的, 但是为了和原来 TUICallKit 对外暴露的保持一致
export enum LOG_LEVEL {
NORMAL = 0, // 普通级别,日志量较多,接入时建议使用
RELEASE = 1, // release级别SDK 输出关键信息,生产环境时建议使用
WARNING = 2, // 告警级别SDK 只输出告警和错误级别的日志
ERROR = 3, // 错误级别SDK 只输出错误级别的日志
NONE = 4, // 无日志级别SDK 将不打印任何日志
}

View File

@ -0,0 +1,23 @@
import TUICallService, { TUIGlobal, TUIStore } from './CallService/index';
import { StoreName, NAME, CallRole, CallMediaType, CallStatus, StatusChange, VideoResolution, VideoDisplayMode, AudioPlayBackDevice } from './const/index';
import { t } from './locales/index';
// 实例化
const TUICallKitServer = TUICallService.getInstance();
// 输出产物
export {
TUIGlobal,
TUIStore,
StoreName,
TUICallKitServer,
NAME,
CallStatus,
CallRole,
CallMediaType,
StatusChange,
VideoResolution,
VideoDisplayMode,
AudioPlayBackDevice,
t,
};

View File

@ -0,0 +1,97 @@
import { CallStatus, CallRole } from '../const/index';
/**
* @interface ITUICallService
*/
export interface ITUICallService {
/**
* Service
* @function
* @private
*/
init(params: any): void;
/**
* 1v1
* @function
* @param {SwitchUserStatusParams} options
*/
call(callParams: ICallParams): void;
}
type SDKAppID = { SDKAppID: number; } | { sdkAppID: number; };
export interface IInitParamsBase {
userID: string;
userSig: string;
tim?: any;
isFromChat?: boolean;
}
export type IInitParams = IInitParamsBase & SDKAppID;
// call params interface
export interface ICallParams {
userID: string;
type: number;
roomID?: number;
userData?: string;
timeout?: number;
offlinePushInfo?: IOfflinePushInfo;
}
// groupCall params interface
export interface IGroupCallParams {
userIDList: Array<string>;
type: number;
groupID: string;
roomID?: number;
userData?: string;
timeout?: number;
offlinePushInfo?: IOfflinePushInfo;
}
// userInfo interface
export interface IUserInfo {
userId: string;
nick?: string;
avatar?: string;
remark?: string;
displayUserInfo?: string; // 远端用户信息展示: remark -> nick -> userId, 简化 UI 组件; 本地用户信息展示: nick -> userId
isAudioAvailable?: boolean; // 用来设置: 麦克风是否打开
isVideoAvailable?: boolean; // 用来设置: 摄像头是否打开
volume?: number;
isEnter?: boolean; // 远端用户, 用来控制预览远端是否显示 loading 效果; 本地用户, 用来控制 "呼叫"、"接通" 接通后显示的 loading 效果
domId?: string; // 播放流 dom 节点, localUserInfo 的 domId = 'localVideo'; remoteUserInfo 的 domId 就是 userId
}
export interface IOfflinePushInfo {
title?: string,
description?: string,
androidOPPOChannelID?: string,
extension: String
}
export interface ICallbackParam {
beforeCalling?: (...args: any[]) => void;
afterCalling?: (...args: any[]) => void;
onMinimized?: (...args: any[]) => void;
onMessageSentByMe?: (...args: any[]) => void;
kickedOut?: (...args: any[]) => void;
statusChanged?: (...args: any[]) => void;
}
export interface ISelfInfoParams {
nickName: string;
avatar: string;
}
export interface IBellParams {
callRole?: CallRole;
callStatus?: CallStatus;
isMuteBell?: boolean;
calleeBellFilePath?: string;
}
export interface IInviteUserParams {
userIDList: string[];
offlinePushInfo?: IOfflinePushInfo;
}
export interface IJoinInGroupCallParams {
type: number;
groupID: string;
roomID: number;
}

View File

@ -1,4 +1,4 @@
import { CallStatus, CallRole, CallMediaType, VideoDisplayMode, VideoResolution, TDeviceList } from '../const/index';
import { CallStatus, CallRole, CallMediaType, VideoDisplayMode, VideoResolution, TDeviceList, CameraPosition } from '../const/index';
import { IUserInfo } from './index';
export interface IToastInfo {
text: string;
@ -9,7 +9,9 @@ export interface ICallStore {
callRole: CallRole;
callMediaType: CallMediaType;
localUserInfo: IUserInfo;
localUserInfoExcludeVolume: IUserInfo;
remoteUserInfoList: Array<IUserInfo>;
remoteUserInfoExcludeVolumeList: Array<IUserInfo>;
callerUserInfo: IUserInfo;
isGroup: boolean;
callDuration: string;
@ -24,6 +26,7 @@ export interface ICallStore {
deviceList: TDeviceList;
groupID: string;
roomID: number;
cameraPosition: CameraPosition;
displayMode: VideoDisplayMode;
videoResolution: VideoResolution;
pusher: any;

View File

@ -0,0 +1,41 @@
import { CallStatus, CallRole, CallMediaType, VideoDisplayMode, VideoResolution, TDeviceList, CameraPosition } from '../const/index';
import { IUserInfo } from './index';
export interface IToastInfo {
text: string;
type?: string; // 默认 info 通知, 取值: 'info' | 'warn' | 'success' | 'error'
}
export interface ICallStore {
callStatus: CallStatus; // 当前的通话状态, 默认: 'idle'
callRole: CallRole; // 通话角色, 默认: 'callee'
callMediaType: CallMediaType; // 通话类型
localUserInfo: IUserInfo; // 自己的信息, 默认: { userId: '' }
localUserInfoExcludeVolume: IUserInfo; // 不包含音量的当前用户信息
remoteUserInfoList: Array<IUserInfo>; // 远端用户信息列表, 默认: []
remoteUserInfoExcludeVolumeList: Array<IUserInfo>; // 不包含音量的远端用户信息列表
// 被叫在未接通时,展示主叫的 userId、头像。但是如果主叫进入通话后再挂断此时被叫无法知道主叫的信息了。
// 因为目前 store 中仅提供了 remoteUserInfoList 数据,主叫离开后,被叫就没有主叫的信息了。因此考虑在 store 中增加 callerUserInfo 字段。
callerUserInfo: IUserInfo;
isGroup: boolean; // 是否是群组通话, 默认: false
callDuration: string; // 通话时长, 默认: '00:00:00'
callTips: string; // 通话提示的信息. 例如: '等待谁接听', 'xxx 拒绝通话', 'xxx 挂断通话'
toastInfo: IToastInfo; // 远端用户挂断、拒绝、超时、忙线等的 toast 提示信息
isMinimized: boolean; // 当前是否悬浮窗模式, 默认: false
enableFloatWindow: boolean, // 开启/关闭悬浮窗功能,默认: false
bigScreenUserId: string, // 当前大屏幕显示的 userID 用户
language: string; // 当前语言
isClickable: boolean; // 按钮是否可点击(呼叫后, '挂断' 按钮不可点击, 发送信令后才可以点击)
showPermissionTip: boolean; // 设备权限弹窗是否展示(如果有麦克风权限为 false如果没有麦克风也没有摄像头权限为 true
deviceList: TDeviceList;
groupID: string;
roomID: number;
cameraPosition: CameraPosition; // 前置或后置值为front, back
// TUICallKit 组件上的属性
displayMode: VideoDisplayMode; // 设置预览远端的画面显示模式, 默认: VideoDisplayMode.COVER
videoResolution: VideoResolution; // 设置视频分辨率, 默认: VideoResolution.RESOLUTION_480P
// 小程序相关属性
pusher: any;
player: any[];
isEarPhone: boolean; // 是否是听筒, 默认: false
showSelectUser: boolean;
}

View File

@ -0,0 +1,38 @@
/**
* @interface TUIGlobal
* @property {Object} global wxuniwindow
* @property {Boolean} isPC true pc
* @property {Boolean} isH5 true H5
* @property {Boolean} isWeChat true
* @property {Boolean} isApp true uniapp native app
* @property {Boolean} isUniPlatform true uniapp
* @property {Boolean} isOfficial true Demo
* @property {Boolean} isWIN true window系统pc
* @property {Boolean} isMAC true mac os系统pc
*/
export interface ITUIGlobal {
global: any; // 挂在 wx、uni、window 对象
isPC: boolean;
isH5: boolean;
isWeChat: boolean;
isApp: boolean;
isUniPlatform: boolean;
isOfficial: boolean;
isWIN: boolean;
isMAC: boolean;
/**
* TUIGlobal
* @function
* @private
*/
initEnv(): void;
/**
* isOfficial
* @function
* @param {number} SDKAppID SDKAppID
* @private
*/
initOfficial(SDKAppID: number): void;
}

View File

@ -0,0 +1,85 @@
import { StoreName } from '../const/index';
// 此处 Map 的 value = 1 为占位作用
export type Task = Record<StoreName, Record<string, Map<(data?: unknown) => void, 1>>>
export interface IOptions {
[key: string]: (newData?: any) => void;
}
/**
* @class TUIStore
* @property {ICustomStore} customStore store API
*/
export interface ITUIStore {
task: Task;
/**
* UI
* @function
* @param {StoreName} storeName store
* @param {IOptions} options UI
* @param {Object} params
* @param {String} params.notifyRangeWhenWatch
* @example
* // UI 层监听会话列表更新通知
* let onConversationListUpdated = function(conversationList) {
* console.warn(conversationList);
* }
* TUIStore.watch(StoreName.CONV, {
* conversationList: onConversationListUpdated,
* })
*/
watch(storeName: StoreName, options: IOptions, params?: any): void;
/**
* UI
* @function
* @param {StoreName} storeName store
* @param {IOptions} options
* @example
* // UI 层取消监听会话列表更新通知
* TUIStore.unwatch(StoreName.CONV, {
* conversationList: onConversationListUpdated,
* })
*/
unwatch(storeName: StoreName, options: IOptions | string): void;
/**
* store
* @function
* @param {StoreName} storeName store
* @param {String} key key
* @private
*/
getData(storeName: StoreName, key: string): any;
/**
* store
* - 使 store API
* @function
* @param {StoreName} storeName store
* @param {String} key key
* @example
* // UI 层更新自定义 Store 数据
* TUIStore.update(StoreName.CUSTOM, 'customKey', 'customData')
*/
update(storeName: StoreName, key: string, data: unknown): void;
/**
* store
* @function
* @param {StoreName} storeName store
* @param {Array<string>} keyList reset keyList
* @param {boolean} isNotificationNeeded
* @private
*/
reset: (storeName: StoreName, keyList?: Array<string>, isNotificationNeeded?: boolean) => void;
/**
* key-value
* @param {Object} params key-value object
* @param {StoreName} storeName store
*/
updateStore: (params: any, name?: StoreName) => void;
}

View File

@ -0,0 +1,4 @@
export * from './ICallService';
export * from './ICallStore';
export * from './ITUIGlobal';
export * from './ITUIStore';

View File

@ -1,25 +1,54 @@
export declare const en: {
hangup: string;
reject: string;
accept: string;
camera: string;
microphone: string;
speaker: string;
'open camera': string;
'close camera': string;
'open microphone': string;
'close microphone': string;
'video-to-audio': string;
'other side reject call': string;
'reject call': string;
accept: string;
cancel: string;
'other side line busy': string;
'in busy': string;
'call timeout': string;
'no response from the other side': string;
'end call': string;
timeout: string;
'kick out': string;
'caller calling message': string;
'callee calling video message': string;
'callee calling audio message': string;
'no microphone access': string;
'no camera access': string;
'invite member': string;
speaker: string;
'Invited group call': string;
waiting: string;
me: string;
'browser-authorization': string;
'mac-privacy': string;
'win-privacy': string;
'mac-preferences': string;
'win-preferences': string;
'Please enter userID': string;
'View more': string;
'people selected': string;
'Select all': string;
Cancel: string;
Done: string;
'camera enabled': string;
'camera disabled': string;
'microphone enabled': string;
'microphone disabled': string;
'speaker phone': string;
'ear piece': string;
'wait to be called': string;
answered: string;
'people in the call': string;
'failed to obtain speakers': string;
'you have a new call': string;
'Those involved': string;
call: string;
'video-call': string;
@ -35,18 +64,15 @@ export declare const en: {
'not-support-multi-call': string;
userID: string;
'already-enter': string;
waiting: string;
'camera-opened': string;
'camera-closed': string;
'microphone-opened': string;
'microphone-closed': string;
camera: string;
microphone: string;
timeout: string;
'kick out': string;
'image-resolution': string;
'default-image-resolution': string;
'invited-person': string;
'video-to-audio': string;
me: string;
'be-rejected': string;
'be-no-response': string;
'be-line-busy': string;
@ -72,19 +98,4 @@ export declare const en: {
'accept-error': string;
'accept-device-error': string;
'call-error': string;
'browser-authorization': string;
'mac-privacy': string;
'win-privacy': string;
'mac-preferences': string;
'win-preferences': string;
'open camera': string;
'close camera': string;
'open microphone': string;
'close microphone': string;
'Please enter userID': string;
'View more': string;
'people selected': string;
'Select all': string;
Cancel: string;
Done: string;
};

View File

@ -2,27 +2,62 @@
Object.defineProperty(exports, "__esModule", { value: true });
exports.en = void 0;
exports.en = {
// 按钮文案
'hangup': 'Hang up',
'reject': 'Decline',
'accept': 'Accept',
'camera': 'Camera',
'microphone': 'Microphone',
'speaker': 'speaker',
'open camera': 'Open Camera',
'close camera': 'Close Camera',
'open microphone': 'Open Microphone',
'close microphone': 'Close Microphone',
'video-to-audio': 'Switch to audio',
// 通话结果
'other side reject call': 'other side reject call',
'reject call': 'Reject Call',
'accept': 'Accept',
'cancel': 'Cancel Call',
'other side line busy': 'other side line busy',
'in busy': 'in busy',
'call timeout': 'call timeout',
'no response from the other side': 'no response from the other side',
'end call': 'end call',
'timeout': 'timeout',
'kick out': 'kick out',
'caller calling message': 'Waiting for the callee to accept the invitation...',
'callee calling video message': 'You are invited to a video call...',
'callee calling audio message': 'You are invited to a audio call...',
// 通话提示语
'caller calling message': 'Awaiting response',
'callee calling video message': 'invites you to a video call',
'callee calling audio message': 'invites you to a voice call',
'no microphone access': 'no microphone access',
'no camera access': 'no camera access',
'invite member': 'Invite Member',
'speaker': 'speaker',
'Invited group call': 'Invited you to a group call',
'Invited group call': 'invites you to a group call',
'waiting': 'Calling...',
'me': '(me)',
// 弹出层文案
'browser-authorization': 'Browser authorization',
'mac-privacy': 'System Preferences -> Security and Privacy -> Privacy',
'win-privacy': 'Setting -> Privacy and Security -> App permissions',
'mac-preferences': 'Open System Preferences',
'win-preferences': 'Open Setting',
'Please enter userID': 'Please enter userID',
'View more': 'View more',
'people selected': 'people selected',
'Select all': 'Select all',
'Cancel': 'Cancel',
'Done': 'Done',
// UI3.0 新增
'camera enabled': 'Camera On',
'camera disabled': 'Camera Off',
'microphone enabled': 'Unmuted',
'microphone disabled': 'Muted',
'speaker phone': 'Speaker',
'ear piece': 'Earpiece',
'wait to be called': 'Waiting',
'answered': 'Connected',
'people in the call': ' other(s) in the call',
'failed to obtain speakers': 'failed to obtain speakers',
'you have a new call': 'You have a new call',
// 待废弃文案
'Those involved': 'Those involved in the call are',
'call': 'call',
'video-call': 'video call',
@ -38,18 +73,15 @@ exports.en = {
'not-support-multi-call': 'multi-person call interface is not open',
'userID': 'userID',
'already-enter': 'entered the call',
'waiting': 'Calling...',
'camera-opened': 'Camera on',
'camera-closed': 'Camera off',
'microphone-opened': 'Mic on',
'microphone-closed': 'Mic off',
'camera': 'Camera',
'microphone': 'Microphone',
'timeout': 'timeout',
'kick out': 'kick out',
'image-resolution': 'Resolution',
'default-image-resolution': 'Default',
'invited-person': 'Invite',
'video-to-audio': 'Switch to audio',
'me': '(me)',
'be-rejected': 'Call declined, ',
'be-no-response': 'No response, ',
'be-line-busy': 'Line busy, ',
@ -75,19 +107,4 @@ exports.en = {
'accept-error': 'Accept failed',
'accept-device-error': 'Accept failed, unable to auth calling device',
'call-error': 'Start call failed',
'browser-authorization': 'Browser authorization',
'mac-privacy': 'System Preferences -> Security and Privacy -> Privacy',
'win-privacy': 'Setting -> Privacy and Security -> App permissions',
'mac-preferences': 'Open System Preferences',
'win-preferences': 'Open Setting',
'open camera': 'Open Camera',
'close camera': 'Close Camera',
'open microphone': 'Open Microphone',
'close microphone': 'Close Microphone',
'Please enter userID': 'Please enter userID',
'View more': 'View more',
'people selected': 'people selected',
'Select all': 'Select all',
'Cancel': 'Cancel',
'Done': 'Done',
};

View File

@ -0,0 +1,107 @@
export const en = {
// 按钮文案
'hangup': 'Hang up',
'reject': 'Decline',
'accept': 'Accept',
'camera': 'Camera',
'microphone': 'Microphone',
'speaker': 'speaker',
'open camera': 'Open Camera',
'close camera': 'Close Camera',
'open microphone': 'Open Microphone',
'close microphone': 'Close Microphone',
'video-to-audio': 'Switch to audio',
// 通话结果
'other side reject call': 'other side reject call',
'reject call': 'Reject Call',
'cancel': 'Cancel Call',
'other side line busy': 'other side line busy',
'in busy': 'in busy',
'call timeout': 'call timeout',
'no response from the other side': 'no response from the other side',
'end call': 'end call',
// 通话提示语
'caller calling message': 'Awaiting response',
'callee calling video message': 'invites you to a video call',
'callee calling audio message': 'invites you to a voice call',
'no microphone access': 'no microphone access',
'no camera access': 'no camera access',
'invite member': 'Invite Member',
'Invited group call': 'invites you to a group call',
'waiting': 'Calling...',
'me': '(me)',
// 弹出层文案
'browser-authorization': 'Browser authorization',
'mac-privacy': 'System Preferences -> Security and Privacy -> Privacy',
'win-privacy': 'Setting -> Privacy and Security -> App permissions',
'mac-preferences': 'Open System Preferences',
'win-preferences': 'Open Setting',
'Please enter userID': 'Please enter userID',
'View more': 'View more',
'people selected': 'people selected',
'Select all': 'Select all',
'Cancel': 'Cancel',
'Done': 'Done',
// UI3.0 新增
'camera enabled': 'Camera On',
'camera disabled': 'Camera Off',
'microphone enabled': 'Unmuted',
'microphone disabled': 'Muted',
'speaker phone': 'Speaker',
'ear piece': 'Earpiece',
'wait to be called': 'Waiting',
'answered': 'Connected',
'people in the call': ' other(s) in the call',
'failed to obtain speakers': 'failed to obtain speakers',
'you have a new call': 'You have a new call',
// 待废弃文案
'Those involved': 'Those involved in the call are',
'call': 'call',
'video-call': 'video call',
'audio-call': 'audio call',
'search': 'search',
'search-result': 'search result',
'no-user': 'user not found',
'member-not-added': 'member not added',
'input-phone-userID': 'phone number or userID',
'not-login': 'not logged in',
'login-status-expire': 'login status is invalid, please refresh the page and try again',
'experience-multi-call': 'experience multi-person calls, please download the full-featured demo: ',
'not-support-multi-call': 'multi-person call interface is not open',
'userID': 'userID',
'already-enter': 'entered the call',
'camera-opened': 'Camera on',
'camera-closed': 'Camera off',
'microphone-opened': 'Mic on',
'microphone-closed': 'Mic off',
'timeout': 'timeout',
'kick out': 'kick out',
'image-resolution': 'Resolution',
'default-image-resolution': 'Default',
'invited-person': 'Invite',
'be-rejected': 'Call declined, ',
'be-no-response': 'No response, ',
'be-line-busy': 'Line busy, ',
'be-canceled': 'The call is canceled, ',
'voice-call-end': 'Voice call ended',
'video-call-end': 'Video call ended',
'method-call-failed': 'Failed to sync the operation',
'failed-to-obtain-permission': 'Failed to obtain permissions',
'environment-detection-failed': 'Failed to check the environment',
'switchToAudioCall-call-failed': 'switch to audio call method failed',
'switchToVideoCall-call-failed': 'switch to video call method failed',
'microphone-unavailable': 'No mic found',
'camera-unavailable': 'No camera found',
'ban-device': 'Device access denied',
'not-supported-webrtc': 'Your current environment does not support WebRTC',
'blacklist-user-tips': 'The identifier is in blacklist. Failed to send this message!',
'is-already-calling': 'TUICallKit is already on a call',
'need-init': 'Before initiating a call with TUICallKit, ensure that the TUICallKitServer.init() method has executed successfully. ',
"can't call yourself": "Can't call yourself", // eslint-disable-line
'Use-phone-and-computer': 'Use your mobile phone and computer to experience video calls',
'Wechat scan right QR code': 'Wechat scan right QR code',
'Scan the QR code above': 'Scan the QR code above',
'accept-error': 'Accept failed',
'accept-device-error': 'Accept failed, unable to auth calling device',
'call-error': 'Start call failed',
};

View File

@ -18,6 +18,7 @@ exports.CallTips = {
TIMEOUT: 'timeout',
KICK_OUT: 'kick out',
CALLER_CALLING_MSG: 'caller calling message',
CALLER_GROUP_CALLING_MSG: 'wait to be called',
CALLEE_CALLING_VIDEO_MSG: 'callee calling video message',
CALLEE_CALLING_AUDIO_MSG: 'callee calling audio message',
NO_MICROPHONE_DEVICE_PERMISSION: 'no microphone access',

View File

@ -0,0 +1,57 @@
import { TUIStore } from '../CallService/index';
import { NAME, StoreName } from '../const/index';
import { en } from './en';
import { zh } from './zh-cn';
import { ja_JP } from './ja_JP';
export const CallTips: any = {
OTHER_SIDE: 'other side',
CANCEL: 'cancel',
OTHER_SIDE_REJECT_CALL: 'other side reject call',
REJECT_CALL: 'reject call',
OTHER_SIDE_LINE_BUSY: 'other side line busy',
IN_BUSY: 'in busy',
CALL_TIMEOUT: 'call timeout',
END_CALL: 'end call',
TIMEOUT: 'timeout',
KICK_OUT: 'kick out',
CALLER_CALLING_MSG: 'caller calling message',
CALLER_GROUP_CALLING_MSG: 'wait to be called',
CALLEE_CALLING_VIDEO_MSG: 'callee calling video message',
CALLEE_CALLING_AUDIO_MSG: 'callee calling audio message',
NO_MICROPHONE_DEVICE_PERMISSION: 'no microphone access',
NO_CAMERA_DEVICE_PERMISSION: 'no camera access',
};
export const languageData: languageDataType = {
en,
'zh-cn': zh,
ja_JP,
};
// language translate
export function t(key: any): string {
const language = TUIStore.getData(StoreName.CALL, NAME.LANGUAGE);
// eslint-disable-next-line
for (const langKey in languageData) {
if (langKey === language) {
const currentLanguage = languageData[langKey];
// eslint-disable-next-line
for (const sentenceKey in currentLanguage) {
if (sentenceKey === key) {
return currentLanguage[sentenceKey];
}
}
}
}
const enString = key['en']?.key; // eslint-disable-line
console.error(`${NAME.PREFIX}translation is not found: ${key}.`);
return enString;
}
interface languageItemType {
[key: string]: string;
}
interface languageDataType {
[key: string]: languageItemType;
}

View File

@ -1,23 +1,50 @@
export declare const ja_JP: {
hangup: string;
reject: string;
accept: string;
camera: string;
microphone: string;
speaker: string;
'other side reject call': string;
'reject call': string;
accept: string;
cancel: string;
'other side line busy': string;
'in busy': string;
'call timeout': string;
'end call': string;
timeout: string;
'kick out': string;
'caller calling message': string;
'callee calling video message': string;
'callee calling audio message': string;
'no microphone access': string;
'no camera access': string;
'invite member': string;
speaker: string;
'browser-authorization': string;
'mac-privacy': string;
'win-privacy': string;
'mac-preferences': string;
'win-preferences': string;
'Please enter userID': string;
'View more': string;
'people selected': string;
'Select all': string;
Cancel: string;
Done: string;
'open camera': string;
'close camera': string;
'open microphone': string;
'close microphone': string;
'camera enabled': string;
'camera disabled': string;
'microphone enabled': string;
'microphone disabled': string;
'speaker phone': string;
'wait to be called': string;
answered: string;
'people in the call': string;
'failed to obtain speakers': string;
'you have a new call': string;
timeout: string;
'kick out': string;
'Invited group call': string;
'Those involved': string;
call: string;
@ -42,8 +69,6 @@ export declare const ja_JP: {
'camera-closed': string;
'microphone-opened': string;
'microphone-closed': string;
camera: string;
microphone: string;
'image-resolution': string;
'default-image-resolution': string;
'invited-person': string;
@ -71,15 +96,4 @@ export declare const ja_JP: {
'accept-error': string;
'accept-device-error': string;
'call-error': string;
'browser-authorization': string;
'mac-privacy': string;
'win-privacy': string;
'mac-preferences': string;
'win-preferences': string;
'Please enter userID': string;
'View more': string;
'people selected': string;
'Select all': string;
Cancel: string;
Done: string;
};

View File

@ -2,25 +2,58 @@
Object.defineProperty(exports, "__esModule", { value: true });
exports.ja_JP = void 0;
exports.ja_JP = {
// 按钮文案
'hangup': '通話終了',
'reject': '拒否',
'accept': '応答',
'camera': 'カメラ',
'microphone': 'マイク',
'speaker': 'スピーカー',
// 通话结果
'other side reject call': '通話が拒否されました',
'reject call': '通話拒否',
'accept': '応答',
'cancel': '通話をキャンセル',
'other side line busy': '相手が通話中です',
'in busy': '通話中',
'call timeout': '呼び出しタイムアウト',
'end call': '通話終了',
'timeout': 'タイムアウト',
'kick out': 'キックアウトされました',
'caller calling message': '相手が招待を承諾するのを待っています。',
'callee calling video message': 'ビデオ通話に招待されました。',
'callee calling audio message': '音声通話に招待されました。',
// 通话提示语
'caller calling message': '応答を待っています',
'callee calling video message': 'ビデオ通話に招待されました',
'callee calling audio message': '音声通話に招待されました',
'no microphone access': 'マイクにアクセスできません',
'no camera access': 'カメラにアクセスできません',
'invite member': 'メンバーを招待する',
'speaker': 'スピーカー',
// 弹出层文案
'browser-authorization': 'ブラウザ認証',
'mac-privacy': 'システム環境設定 -> セキュリティとプライバシー ->プライバシー',
'win-privacy': '設定 -> セキュリティとプライバシー ->アプリのアクセス許可',
'mac-preferences': 'システム環境設定を開く',
'win-preferences': 'システム設定を開く',
'Please enter userID': 'ユーザーIDを入力してください',
'View more': 'もっと見る',
'people selected': '人が選択されました',
'Select all': 'すべて選択',
'Cancel': 'キャンセル',
'Done': '完了',
// UI3.0文案
'open camera': 'オープンカメラ',
'close camera': 'カメラを閉じる',
'open microphone': 'オープンマイク',
'close microphone': 'マイクを閉じる',
'camera enabled': 'カメラオン',
'camera disabled': 'カメラオフ',
'microphone enabled': 'マイクオン',
'microphone disabled': 'マイクオフ',
'speaker phone': 'スピーカーオン',
'wait to be called': '待機中',
'answered': '接続済み',
'people in the call': '通話に参加している人たち',
'failed to obtain speakers': 'スピーカーが見つかりません',
'you have a new call': '新しい通話があります',
// 待废弃文案
'timeout': 'タイムアウト',
'kick out': 'キックアウトされました',
'Invited group call': 'グループ通話に招待されました。',
'Those involved': '参加者:',
'call': '通話',
@ -45,13 +78,11 @@ exports.ja_JP = {
'camera-closed': 'カメラがオフになっています',
'microphone-opened': 'マイクがオンになっています',
'microphone-closed': 'マイクがオフになっています',
'camera': 'カメラ',
'microphone': 'マイク',
'image-resolution': '解像度',
'default-image-resolution': 'デフォルト解像度',
'invited-person': 'メンバーを招待',
'video-to-audio': '音声通話に切り替えます',
'me': '(自分)',
'me': '(自分)',
'be-rejected': '通話が拒否されました, ',
'be-no-response': '応答なし, ',
'be-line-busy': '相手が通話中です, ',
@ -74,15 +105,4 @@ exports.ja_JP = {
'accept-error': '接続できませんでした',
'accept-device-error': '接続できませんでした。発信側デバイスを認証できません',
'call-error': '通話が開始できませんでした',
'browser-authorization': 'ブラウザ認証',
'mac-privacy': 'システム環境設定 -> セキュリティとプライバシー ->プライバシー',
'win-privacy': '設定 -> セキュリティとプライバシー ->アプリのアクセス許可',
'mac-preferences': 'システム環境設定を開く',
'win-preferences': 'システム設定を開く',
'Please enter userID': 'ユーザーIDを入力してください',
'View more': 'もっと見る',
'people selected': '人が選択されました',
'Select all': 'すべて選択',
'Cancel': 'キャンセル',
'Done': '完了',
};

View File

@ -0,0 +1,105 @@
export const ja_JP = {
// 按钮文案
'hangup': '通話終了',
'reject': '拒否',
'accept': '応答',
'camera': 'カメラ',
'microphone': 'マイク',
'speaker': 'スピーカー',
// 通话结果
'other side reject call': '通話が拒否されました',
'reject call': '通話拒否',
'cancel': '通話をキャンセル',
'other side line busy': '相手が通話中です',
'in busy': '通話中',
'call timeout': '呼び出しタイムアウト',
'end call': '通話終了',
// 通话提示语
'caller calling message': '応答を待っています',
'callee calling video message': 'ビデオ通話に招待されました',
'callee calling audio message': '音声通話に招待されました',
'no microphone access': 'マイクにアクセスできません',
'no camera access': 'カメラにアクセスできません',
'invite member': 'メンバーを招待する',
// 弹出层文案
'browser-authorization': 'ブラウザ認証',
'mac-privacy': 'システム環境設定 -> セキュリティとプライバシー ->プライバシー',
'win-privacy': '設定 -> セキュリティとプライバシー ->アプリのアクセス許可',
'mac-preferences': 'システム環境設定を開く',
'win-preferences': 'システム設定を開く',
'Please enter userID': 'ユーザーIDを入力してください',
'View more': 'もっと見る',
'people selected': '人が選択されました',
'Select all': 'すべて選択',
'Cancel': 'キャンセル',
'Done': '完了',
// UI3.0文案
'open camera': 'オープンカメラ',
'close camera': 'カメラを閉じる',
'open microphone': 'オープンマイク',
'close microphone': 'マイクを閉じる',
'camera enabled': 'カメラオン',
'camera disabled': 'カメラオフ',
'microphone enabled': 'マイクオン',
'microphone disabled': 'マイクオフ',
'speaker phone': 'スピーカーオン',
'wait to be called': '待機中',
'answered': '接続済み',
'people in the call': '通話に参加している人たち',
'failed to obtain speakers': 'スピーカーが見つかりません',
'you have a new call': '新しい通話があります',
// 待废弃文案
'timeout': 'タイムアウト',
'kick out': 'キックアウトされました',
'Invited group call': 'グループ通話に招待されました。',
'Those involved': '参加者:',
'call': '通話',
'video-call': 'ビデオ通話',
'audio-call': '音声通話',
'search': '検索',
'search-result': '検索結果',
'Wechat scan right QR code': 'WeChatで右側にあるQRコードを読み取ります。',
'Use-phone-and-computer': '携帯電話とコンピュータを使用してビデオ通話を体験してください',
'Scan the QR code above': '上のQRコードを読み取ります。',
'no-user': 'ユーザーが見つかりませんでした',
'member-not-added': 'メンバーが追加されていません',
'not-login': 'ログインしていません',
'login-status-expire': 'ログインの有効期限が過ぎています。ページを更新してもう一度お試しください',
'experience-multi-call': '複数人で同時に音声通話できるグループ通話機能を体験するには、全機能のデモをダウンロードしてください',
'not-support-multi-call': 'グループ通話インターフェイスが開いていません',
'input-phone-userID': '携帯電話番号/ユーザーIDを入力してください',
'userID': 'ユーザーID',
'already-enter': 'すでに通話に参加しています',
'waiting': '応答を待っています...',
'camera-opened': 'カメラがオンになっています',
'camera-closed': 'カメラがオフになっています',
'microphone-opened': 'マイクがオンになっています',
'microphone-closed': 'マイクがオフになっています',
'image-resolution': '解像度',
'default-image-resolution': 'デフォルト解像度',
'invited-person': 'メンバーを招待',
'video-to-audio': '音声通話に切り替えます',
'me': '(自分)',
'be-rejected': '通話が拒否されました, ',
'be-no-response': '応答なし, ',
'be-line-busy': '相手が通話中です, ',
'be-canceled': '相手が通話をキャンセルしました',
'voice-call-end': '音声通話が終了しました',
'video-call-end': 'ビデオ通話が終了しました',
'method-call-failed': '操作の同期に失敗しました',
'failed-to-obtain-permission': '権限の取得に失敗しました',
'environment-detection-failed': '環境の検出に失敗しました',
'switchToAudioCall-call-failed': '音声通話に切り替えることはできません',
'switchToVideoCall-call-failed': 'ビデオ通話に切り替えることはできません',
'microphone-unavailable': '使用できるマイクがありません',
'camera-unavailable': '使用できるカメラがありません',
'ban-device': 'デバイスへのアクセスが拒否されました',
'not-supported-webrtc': '現在の環境はWebRTCをサポートしていません',
'blacklist-user-tips': 'ユーザーはブラックリストに登録され、通話が開始できませんでした',
'is-already-calling': 'TUICallKit はすでに通話中です',
'need-init': 'TUICallKitで通話を開始する前に、TUICallKitServer.init() メソッドが正常に実行されたことを確認してください。',
"can't call yourself": '自分に電話をかけることができません',
'accept-error': '接続できませんでした',
'accept-device-error': '接続できませんでした。発信側デバイスを認証できません',
'call-error': '通話が開始できませんでした',
};

View File

@ -1,25 +1,56 @@
export declare const zh: {
hangup: string;
reject: string;
accept: string;
camera: string;
microphone: string;
speaker: string;
'open camera': string;
'close camera': string;
'open microphone': string;
'close microphone': string;
'video-to-audio': string;
'other side reject call': string;
'reject call': string;
accept: string;
cancel: string;
'other side line busy': string;
'in busy': string;
'call timeout': string;
'end call': string;
timeout: string;
'kick out': string;
'caller calling message': string;
'callee calling video message': string;
'callee calling audio message': string;
'no microphone access': string;
'no camera access': string;
'invite member': string;
speaker: string;
'Invited group call': string;
'Those involved': string;
waiting: string;
me: string;
'browser-authorization': string;
'mac-privacy': string;
'win-privacy': string;
'mac-preferences': string;
'win-preferences': string;
'Please enter userID': string;
'View more': string;
'people selected': string;
'Select all': string;
Cancel: string;
Done: string;
'camera enabled': string;
'camera disabled': string;
'microphone enabled': string;
'microphone disabled': string;
'speaker phone': string;
'ear piece': string;
'wait to be called': string;
answered: string;
'people in the call': string;
'failed to obtain speakers': string;
'you have a new call': string;
timeout: string;
'kick out': string;
call: string;
'video-call': string;
'audio-call': string;
@ -37,18 +68,9 @@ export declare const zh: {
'input-phone-userID': string;
userID: string;
'already-enter': string;
waiting: string;
'camera-opened': string;
'camera-closed': string;
'microphone-opened': string;
'microphone-closed': string;
camera: string;
microphone: string;
'image-resolution': string;
'default-image-resolution': string;
'invited-person': string;
'video-to-audio': string;
me: string;
'be-rejected': string;
'be-no-response': string;
'be-line-busy': string;
@ -71,19 +93,4 @@ export declare const zh: {
'accept-error': string;
'accept-device-error': string;
'call-error': string;
'browser-authorization': string;
'mac-privacy': string;
'win-privacy': string;
'mac-preferences': string;
'win-preferences': string;
'open camera': string;
'close camera': string;
'open microphone': string;
'close microphone': string;
'Please enter userID': string;
'View more': string;
'people selected': string;
'Select all': string;
Cancel: string;
Done: string;
};

View File

@ -2,27 +2,64 @@
Object.defineProperty(exports, "__esModule", { value: true });
exports.zh = void 0;
exports.zh = {
// 按钮文案
'hangup': '挂断',
'reject': '拒绝',
'accept': '接受',
'camera': '摄像头',
'microphone': '麦克风',
'speaker': '扬声器',
'open camera': '打开摄像头',
'close camera': '关闭摄像头',
'open microphone': '打开麦克风',
'close microphone': '关闭麦克风',
'video-to-audio': '转语音通话',
// 通话结果
'other side reject call': '对方已拒绝',
'reject call': '拒绝通话',
'accept': '接受',
'cancel': '取消通话',
'other side line busy': '对方忙线',
'in busy': '正在忙',
'call timeout': '呼叫超时',
'end call': '结束通话',
'timeout': '超时',
'kick out': '被踢',
'caller calling message': '正在等待对方接受邀请…',
'callee calling video message': '邀请您进行视频通话…',
'callee calling audio message': '邀请您进行语音通话…',
// 通话提示语
'caller calling message': '等待对方接受邀请',
'callee calling video message': '邀请你视频通话',
'callee calling audio message': '邀请你语音通话',
'no microphone access': '没有麦克风权限',
'no camera access': '没有摄像头权限',
'invite member': '邀请成员',
'speaker': '扬声器',
'Invited group call': '邀请你参加多人通话',
'Invited group call': '邀请你加入多人通话',
'Those involved': '参与通话的有:',
'waiting': '等待接听...',
'me': '(我)',
// 弹出层文案
'browser-authorization': '浏览器授权',
'mac-privacy': '系统偏好设置 -> 安全与隐私 -> 隐私',
'win-privacy': '设置 -> 隐私和安全性 -> 应用权限',
'mac-preferences': '打开系统偏好设置',
'win-preferences': '打开系统设置',
'Please enter userID': '请输入 userID',
'View more': '查看更多',
'people selected': '人已选中',
'Select all': '全选',
'Cancel': '取消',
'Done': '完成',
// UI3.0 新增
'camera enabled': '摄像头已开',
'camera disabled': '摄像头已关',
'microphone enabled': '麦克风已开',
'microphone disabled': '麦克风已关',
'speaker phone': '扬声器已开',
'ear piece': '扬声器已关',
'wait to be called': '等待接听',
'answered': '已接通',
'people in the call': '人参与通话',
'failed to obtain speakers': '无法获取扬声器',
'you have a new call': '您有一个新的通话',
// 待废弃文案
'timeout': '超时',
'kick out': '被踢',
'call': '通话',
'video-call': '视频通话',
'audio-call': '音频通话',
@ -40,18 +77,9 @@ exports.zh = {
'input-phone-userID': '请输入手机号/用户ID',
'userID': '用户ID',
'already-enter': '已经进入当前通话',
'waiting': '等待接听...',
'camera-opened': '摄像头已开',
'camera-closed': '摄像头已关',
'microphone-opened': '麦克风已开',
'microphone-closed': '麦克风已关',
'camera': '摄像头',
'microphone': '麦克风',
'image-resolution': '分辨率',
'default-image-resolution': '默认分辨率',
'invited-person': '添加成员',
'video-to-audio': '切到语音通话',
'me': '(我)',
'be-rejected': '对方已拒绝,',
'be-no-response': '对方无应答,',
'be-line-busy': '对方忙线中,',
@ -74,19 +102,4 @@ exports.zh = {
'accept-error': '接通失败',
'accept-device-error': '接通失败,通话设备获取失败',
'call-error': '发起通话失败',
'browser-authorization': '浏览器授权',
'mac-privacy': '系统偏好设置 -> 安全与隐私 -> 隐私',
'win-privacy': '设置 -> 隐私和安全性 -> 应用权限',
'mac-preferences': '打开系统偏好设置',
'win-preferences': '打开系统设置',
'open camera': '打开摄像头',
'close camera': '关闭摄像头',
'open microphone': '打开麦克风',
'close microphone': '关闭麦克风',
'Please enter userID': '请输入 userID',
'View more': '查看更多',
'people selected': '人已选中',
'Select all': '全选',
'Cancel': '取消',
'Done': '完成',
};

View File

@ -0,0 +1,102 @@
export const zh = {
// 按钮文案
'hangup': '挂断',
'reject': '拒绝',
'accept': '接受',
'camera': '摄像头',
'microphone': '麦克风',
'speaker': '扬声器',
'open camera': '打开摄像头',
'close camera': '关闭摄像头',
'open microphone': '打开麦克风',
'close microphone': '关闭麦克风',
'video-to-audio': '转语音通话',
// 通话结果
'other side reject call': '对方已拒绝',
'reject call': '拒绝通话',
'cancel': '取消通话',
'other side line busy': '对方忙线',
'in busy': '正在忙',
'call timeout': '呼叫超时',
'end call': '结束通话',
// 通话提示语
'caller calling message': '等待对方接受邀请',
'callee calling video message': '邀请你视频通话',
'callee calling audio message': '邀请你语音通话',
'no microphone access': '没有麦克风权限',
'no camera access': '没有摄像头权限',
'invite member': '邀请成员',
'Invited group call': '邀请你加入多人通话',
'Those involved': '参与通话的有:',
'waiting': '等待接听...',
'me': '(我)',
// 弹出层文案
'browser-authorization': '浏览器授权',
'mac-privacy': '系统偏好设置 -> 安全与隐私 -> 隐私',
'win-privacy': '设置 -> 隐私和安全性 -> 应用权限',
'mac-preferences': '打开系统偏好设置',
'win-preferences': '打开系统设置',
'Please enter userID': '请输入 userID',
'View more': '查看更多',
'people selected': '人已选中',
'Select all': '全选',
'Cancel': '取消',
'Done': '完成',
// UI3.0 新增
'camera enabled': '摄像头已开',
'camera disabled': '摄像头已关',
'microphone enabled': '麦克风已开',
'microphone disabled': '麦克风已关',
'speaker phone': '扬声器已开',
'ear piece': '扬声器已关',
'wait to be called': '等待接听',
'answered': '已接通',
'people in the call': '人参与通话',
'failed to obtain speakers': '无法获取扬声器',
'you have a new call': '您有一个新的通话',
// 待废弃文案
'timeout': '超时',
'kick out': '被踢',
'call': '通话',
'video-call': '视频通话',
'audio-call': '音频通话',
'search': '搜索',
'search-result': '搜索结果',
'Wechat scan right QR code': '微信扫右二维码',
'Use-phone-and-computer': '用手机与电脑互打体验视频通话',
'Scan the QR code above': '扫描上方二维码',
'no-user': '未搜索到用户',
'member-not-added': '未添加成员',
'not-login': '未登录',
'login-status-expire': '登录状态已失效,请刷新网页重试',
'experience-multi-call': '体验多人通话请下载全功能demo:',
'not-support-multi-call': '多人通话接口未开放',
'input-phone-userID': '请输入手机号/用户ID',
'userID': '用户ID',
'already-enter': '已经进入当前通话',
'image-resolution': '分辨率',
'default-image-resolution': '默认分辨率',
'invited-person': '添加成员',
'be-rejected': '对方已拒绝,',
'be-no-response': '对方无应答,',
'be-line-busy': '对方忙线中,',
'be-canceled': '对方已取消',
'voice-call-end': '语音通话结束',
'video-call-end': '视频通话结束',
'method-call-failed': '同步操作失败',
'failed-to-obtain-permission': '权限获取失败',
'environment-detection-failed': '环境检测失败',
'switchToAudioCall-call-failed': '切语音调用失败',
'switchToVideoCall-call-failed': '切视频调用失败',
'microphone-unavailable': '没有可用的麦克风设备',
'camera-unavailable': '没有可用的摄像头设备',
'ban-device': '用户禁止使用设备',
'not-supported-webrtc': '当前环境不支持 WebRTC',
'blacklist-user-tips': '发起通话失败,被对方拉入黑名单,禁止发起!',
'is-already-calling': 'TUICallKit 已在通话状态',
'need-init': 'TUICallKit 发起通话前需保证 TUICallKitServer.init() 方法执行成功',
"can't call yourself": '不能呼叫自己', // eslint-disable-line
'accept-error': '接通失败',
'accept-device-error': '接通失败,通话设备获取失败',
'call-error': '发起通话失败',
};

View File

@ -1,11 +1,13 @@
export declare class CallManager {
private _globalCallPagePath;
private _isPageRedirected;
private _targetPagePath;
init(params: any): Promise<void>;
private _watchTUIStore;
private _unwatchTUIStore;
private _handleCallStatusChange;
private _handleCallStatusToCalling;
private _handleCallStatusToIdle;
getRoute(): any;
destroyed(): Promise<void>;
}

View File

@ -34,6 +34,7 @@ class CallManager {
constructor() {
this._globalCallPagePath = '';
this._isPageRedirected = false;
this._targetPagePath = '';
this._handleCallStatusChange = (value) => __awaiter(this, void 0, void 0, function* () {
switch (value) {
case index_2.CallStatus.CALLING:
@ -88,6 +89,7 @@ class CallManager {
_handleCallStatusToCalling() {
if (this._isPageRedirected)
return;
this._targetPagePath = this.getRoute().route;
// @ts-ignore
wx.navigateTo({
url: `/${this._globalCallPagePath}`,
@ -103,6 +105,10 @@ class CallManager {
_handleCallStatusToIdle() {
if (!this._isPageRedirected)
return;
if (this._targetPagePath === this.getRoute().route) {
this._isPageRedirected = false;
return;
}
// @ts-ignore
wx.navigateBack({
success: () => {
@ -114,6 +120,13 @@ class CallManager {
complete: () => { },
});
}
// 获取当前的页面地址
getRoute() {
// @ts-ignore
const pages = getCurrentPages();
const currentPage = pages[pages.length - 1];
return currentPage;
}
// 卸载 callManger
destroyed() {
return __awaiter(this, void 0, void 0, function* () {

View File

@ -0,0 +1,117 @@
import { TUICallKitServer, NAME, TUIStore, StoreName } from '../../index';
import { CallStatus } from '../const/index';
import { avoidRepeatedCall } from '../utils/validate/index';
/**
* @param {Number} sdkAppID sdkAppID
* @param {String} userID userID
* @param {String} userSig userSig
* @param {String} globalCallPagePath
* @param {ChatSDK} tim tim实例
*/
const PREFIX = 'callManager';
export class CallManager {
private _globalCallPagePath:string = '';
private _isPageRedirected:boolean = false;
private _targetPagePath:string = '';
@avoidRepeatedCall()
public async init(params) {
const { sdkAppID, userID, userSig, globalCallPagePath, tim } = params;
if (!globalCallPagePath) {
console.error(`${PREFIX} globalCallPagePath Can not be empty!`);
return;
};
this._globalCallPagePath = globalCallPagePath;
try {
await TUICallKitServer.init({
sdkAppID,
userID,
userSig,
tim,
});
this._watchTUIStore();
// 全局监听下,关闭悬浮窗
TUICallKitServer.enableFloatWindow(false);
console.log(`${PREFIX} init Ready!`);
} catch (error) {
console.error(`${PREFIX} init fail!`);
}
}
// =========================【监听 TUIStore 中的状态】=========================
private _watchTUIStore() {
TUIStore?.watch(StoreName.CALL, {
[NAME.CALL_STATUS]: this._handleCallStatusChange,
}, {
notifyRangeWhenWatch: NAME.MYSELF,
});
}
private _unwatchTUIStore() {
TUIStore?.unwatch(StoreName.CALL, {
[NAME.CALL_STATUS]: this._handleCallStatusChange,
});
}
private _handleCallStatusChange = async (value: CallStatus) => {
switch (value) {
case CallStatus.CALLING:
case CallStatus.CONNECTED:
this._handleCallStatusToCalling();
break;
case CallStatus.IDLE:
this._handleCallStatusToIdle();
break;
}
};
private _handleCallStatusToCalling() {
if (this._isPageRedirected) return;
this._targetPagePath = this.getRoute().route;
// @ts-ignore
wx.navigateTo({
url: `/${this._globalCallPagePath}`,
success: () => {
this._isPageRedirected = true;
},
fail: () => {
console.error(`${PREFIX} navigateTo fail!`);
},
complete: () => {},
});
}
private _handleCallStatusToIdle() {
if (!this._isPageRedirected) return;
if(this._targetPagePath === this.getRoute().route) {
this._isPageRedirected = false;
return;
}
// @ts-ignore
wx.navigateBack({
success: () => {
this._isPageRedirected = false;
},
fail: () => {
console.error(`${PREFIX} navigateBack fail!`);
},
complete: () => {},
});
}
// 获取当前的页面地址
getRoute() {
// @ts-ignore
const pages = getCurrentPages();
const currentPage = pages[pages.length - 1];
return currentPage;
}
// 卸载 callManger
public async destroyed() {
this._globalCallPagePath = '';
this._isPageRedirected = false;
this._unwatchTUIStore();
await TUICallKitServer.destroyed();
}
}

View File

@ -170,16 +170,16 @@ function handleNoDevicePermissionError(error) {
exports.handleNoDevicePermissionError = handleNoDevicePermissionError;
/*
* 获取向下取整的 performance.now()
* 在不支持 performance.now 的浏览器中使用 Date.now(). 例如 ie 9ie 10避免加载 sdk 时报错
* @export
* @return {Number}
*/
function performanceNow() {
// 在不支持 performance.now 的浏览器中,使用 Date.now()
// 例如 ie 9ie 10避免加载 sdk 时报错
if (!performance || !performance.now) {
return Date.now();
}
return Math.floor(performance.now());
// if (!performance || !performance.now) {
// return Date.now();
// }
// return Math.floor(performance.now()); // uni-app 打包小程序没有 performance, 报错
return Date.now();
}
exports.performanceNow = performanceNow;
/**

View File

@ -0,0 +1,239 @@
import { NAME } from '../const/index';
import TUIGlobal from '../TUIGlobal/tuiGlobal';
export const isUndefined = function (input: any) {
return typeof input === NAME.UNDEFINED;
};
export const isPlainObject = function (input: any) {
// 注意不能使用以下方式判断因为IE9/IE10下对象的__proto__是 undefined
// return isObject(input) && input.__proto__ === Object.prototype;
if (typeof input !== NAME.OBJECT || input === null) {
return false;
}
const proto = Object.getPrototypeOf(input);
if (proto === null) { // edge case Object.create(null)
return true;
}
let baseProto = proto;
while (Object.getPrototypeOf(baseProto) !== null) {
baseProto = Object.getPrototypeOf(baseProto);
}
// 原型链第一个和最后一个比较
return proto === baseProto;
};
export const isArray = function (input: any) {
if (typeof Array.isArray === NAME.FUNCTION) {
return Array.isArray(input);
}
return (Object as any).prototype.toString.call(input).match(/^\[object (.*)\]$/)[1].toLowerCase() === NAME.ARRAY;
};
export const isPrivateKey = function (key: string) {
return key.startsWith('_');
};
export const isUrl = function (url: string) {
return /^(https?:\/\/(([a-zA-Z0-9]+-?)+[a-zA-Z0-9]+\.)+[a-zA-Z]+)(:\d+)?(\/.*)?(\?.*)?(#.*)?$/.test(url);
};
/**
* input类型是否为string
* @param {*} input
* @returns {Boolean} true->string / false->not a string
*/
export const isString = function (input: any) {
return typeof input === NAME.STRING;
};
export const isBoolean = function (input: any) {
return typeof input === NAME.BOOLEAN;
};
export const isNumber = function (input: any) {
return (
// eslint-disable-next-line
input !== null &&
((typeof input === NAME.NUMBER && !isNaN(input - 0)) || (typeof input === NAME.OBJECT && input.constructor === Number))
);
};
export function formatTime(secondTime: number): string {
const hours: number = Math.floor(secondTime / 3600);
const minutes: number = Math.floor((secondTime % 3600) / 60);
const seconds: number = Math.floor(secondTime % 60);
let callDurationStr: string = hours > 9 ? `${hours}` : `0${hours}`;
callDurationStr += minutes > 9 ? `:${minutes}` : `:0${minutes}`;
callDurationStr += seconds > 9 ? `:${seconds}` : `:0${seconds}`;
return callDurationStr;
}
export function formatTimeInverse(stringTime: string): number {
const list = stringTime.split(':');
return parseInt(list[0]) * 3600 + parseInt(list[1]) * 60 + parseInt(list[2]); // eslint-disable-line
}
// Determine if it is a JSON string
export function isJSON(str: string) {
if (typeof str === NAME.STRING) {
try {
const data = JSON.parse(str);
if (data) {
return true;
}
return false;
} catch (error) {
console.debug(error);
return false;
}
}
return false;
}
// Determine if it is a JSON string
export const JSONToObject = function (str: string) {
if (!str || !isJSON(str)) {
return str;
}
return JSON.parse(str);
};
/**
* , catch
* @param {Promise} promise
* @param {number} num
* @param {number} time s
* @returns {Promise<any>} im response
*/
export const retryPromise = (promise: Promise<any>, num: number = 6, time: number = 0.5) => {
let n = num;
const func = () => promise;
return func()
.catch((error: any) => {
if (n === 0) {
throw error;
}
const timer = setTimeout(() => {
func();
clearTimeout(timer);
n = n - 1;
}, time * 1000);
});
};
// /**
// * 节流函数(目前 TUICallKit 增加防重调用装饰器,该方法可删除)
// * @param {Function} func 传入的函数
// * @param {wait} time 间隔时间ms
// */
// export const throttle = (func: Function, wait: number) => {
// let previousTime = 0;
// return function () {
// const now = Date.now();
// const args = [...arguments];
// if (now - previousTime > wait) {
// func.apply(this, args);
// previousTime = now;
// }
// };
// }
/**
* web call engine , TUICallKit
* @param {any} error
* @returns {Boolean}
*/
export function handleRepeatedCallError(error: any) {
if (error?.message.indexOf('is ongoing, please avoid repeated calls') !== -1) {
return true;
}
return false;
}
/**
*
* @param {any} error
* @returns {Boolean}
*/
export function handleNoDevicePermissionError(error: any) {
const { message } = error;
if (message.indexOf('NotAllowedError: Permission denied') !== -1) {
return true;
}
return false;
}
/*
* performance.now()
* performance.now 使 Date.now(). ie 9ie 10 sdk
* @export
* @return {Number}
*/
export function performanceNow() {
// if (!performance || !performance.now) {
// return Date.now();
// }
// return Math.floor(performance.now()); // uni-app 打包小程序没有 performance, 报错
return Date.now();
}
/**
* input类型是否为function
* @param {*} input
* @returns {Boolean} true->input is a function
*/
export const isFunction = function (input: any) {
return typeof input === NAME.FUNCTION;
};
/*
*
* @export
* @return {zh-cn | en}
*/
export const getLanguage = () => {
if (TUIGlobal.getInstance().isWeChat) {
return 'zh-cn';
}
// @ts-ignore
const lang = (navigator?.language || navigator?.userLanguage || '').substr(0, 2);
let language = 'en';
switch (lang) {
case 'zh':
language = 'zh-cn';
break;
case 'ja':
language = 'ja_JP';
break;
default:
language = 'en';
}
return language;
};
export function noop(e: any) {}
/**
* Get the object type string
* @param {*} input
* @returns {String} the object type string
*/
export const getType = function(input) {
return Object.prototype.toString
.call(input)
.match(/^\[object (.*)\]$/)[1]
.toLowerCase();
};
// 修改对象键名
export function modifyObjectKey(obj, oldKey, newKey) {
if (!obj.hasOwnProperty(oldKey)) {
return obj;
}
const newObj = {};
Object.keys(obj).forEach(key => {
if (key === oldKey) {
newObj[newKey] = obj[key];
} else {
newObj[key] = obj[key];
}
});
return newObj;
}

View File

@ -0,0 +1,32 @@
/**
* Interface
* @param {number} [retries = 5]
* @param {number} [timeout = 2000]
* @param {Function=} onError
* @param {Function=} onRetrying
* @param {Function=} onRetryFailed
*/
interface IPromiseRetryDecoratorSettings {
retries?: number;
timeout?: number;
onError?: Function;
onRetrying?: Function;
onRetryFailed?: Function;
}
/**
*
* @param {Object} settings
* @returns {Function}
* @example
* class LocalStream {
* @promiseRetryDecorator({
* retries: 10,
* timeout: 3000,
* onRetryFailed: function(error) {
* }
* })
* async recoverCapture(options) {}
* }
*/
export default function promiseRetryDecorator(settings: IPromiseRetryDecoratorSettings): (target: any, name: any, descriptor: any) => any;
export {};

View File

@ -0,0 +1,40 @@
"use strict";
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
const retry_1 = __importDefault(require("../retry"));
;
/**
* 装饰器函数给异步函数增加重试
* @param {Object} settings 入参
* @returns {Function}
* @example
* class LocalStream {
* @promiseRetryDecorator({
* retries: 10,
* timeout: 3000,
* onRetryFailed: function(error) {
* }
* })
* async recoverCapture(options) {}
* }
*/
function promiseRetryDecorator(settings) {
return function (target, name, descriptor) {
const { retries = 5, timeout = 2000, onError, onRetrying, onRetryFailed } = settings;
const oldFn = (0, retry_1.default)({
retryFunction: descriptor.value,
settings: { retries, timeout },
onError,
onRetrying,
onRetryFailed,
context: null,
});
descriptor.value = function (...args) {
return oldFn.apply(this, args);
};
return descriptor;
};
}
exports.default = promiseRetryDecorator;

View File

@ -0,0 +1,51 @@
import promiseRetry from '../retry';
/**
* Interface
* @param {number} [retries = 5]
* @param {number} [timeout = 2000]
* @param {Function=} onError
* @param {Function=} onRetrying
* @param {Function=} onRetryFailed
*/
interface IPromiseRetryDecoratorSettings {
retries?: number;
timeout?: number;
onError?: Function;
onRetrying?: Function;
onRetryFailed?: Function;
};
/**
*
* @param {Object} settings
* @returns {Function}
* @example
* class LocalStream {
* @promiseRetryDecorator({
* retries: 10,
* timeout: 3000,
* onRetryFailed: function(error) {
* }
* })
* async recoverCapture(options) {}
* }
*/
export default function promiseRetryDecorator(settings: IPromiseRetryDecoratorSettings) {
return function(target, name, descriptor) {
const { retries = 5, timeout = 2000, onError, onRetrying, onRetryFailed } = settings;
const oldFn = promiseRetry({
retryFunction: descriptor.value,
settings: { retries, timeout },
onError,
onRetrying,
onRetryFailed,
context: null,
});
descriptor.value = function(...args) {
return oldFn.apply(this, args);
};
return descriptor;
};
}

View File

@ -0,0 +1,51 @@
// eslint-disable-next-line
declare var wx: any;
// eslint-disable-next-line
declare var uni: any;
// eslint-disable-next-line
declare var window: any;
// 在 uniApp 框架下,打包 H5、ios app、android app 时存在 wx/qq/tt/swan/my 等变量会导致引入 web sdk 环境判断失效
// 小程序 getSystemInfoSync 返回的 fontSizeSetting 在 H5 和 app 中为 undefined所以通过 fontSizeSetting 增强小程序环境判断
// wx 小程序
export const IN_WX_MINI_APP = (typeof wx !== 'undefined' && typeof wx.getSystemInfoSync === 'function' && Boolean(wx.getSystemInfoSync().fontSizeSetting));
// 用 uni-app 打包 native app此时运行于 js core无 window 等对象,此时调用 api 都得 uni.xxx由于风格跟小程序类似就归为 IN_MINI_APP 的一种
export const IN_UNI_NATIVE_APP = (typeof uni !== 'undefined' && typeof uni === 'undefined');
export const IN_MINI_APP = IN_WX_MINI_APP || IN_UNI_NATIVE_APP;
export const IN_UNI_APP = (typeof uni !== 'undefined');
// 在 uniApp 框架下,由于客户打包 ios app、android app 时 window 不一定存在,所以通过 !IN_MINI_APP 进行判断
// 非 uniApp 框架下,仍然通过 window 结合 IN_MINI_APP 进行判断,可兼容 Taro3.0+ 暴露 window 对象引起的 IN_BROWSER 判断失效问题
export const IN_BROWSER = (function () {
if (typeof uni !== 'undefined') {
return !IN_MINI_APP;
}
return (typeof window !== 'undefined') && !IN_MINI_APP;
}());
// 命名空间
export const APP_NAMESPACE = (function () {
if (IN_WX_MINI_APP) {
return wx;
}
if (IN_UNI_APP) {
return uni;
}
return window;
}());
// eslint-disable-next-line no-mixed-operators
const USER_AGENT = IN_BROWSER && window && window.navigator && window.navigator.userAgent || '';
const IS_ANDROID = /Android/i.test(USER_AGENT);
const IS_WIN_PHONE = /(?:Windows Phone)/.test(USER_AGENT);
const IS_SYMBIAN = /(?:SymbianOS)/.test(USER_AGENT);
const IS_IOS = /iPad/i.test(USER_AGENT) || /iPhone/i.test(USER_AGENT) || /iPod/i.test(USER_AGENT);
export const IS_H5 = IS_ANDROID || IS_WIN_PHONE || IS_SYMBIAN || IS_IOS;
export const IS_PC = IN_BROWSER && !IS_H5;
export const IS_WIN = IS_PC && USER_AGENT.includes('Windows NT');
export const IS_MAC = IS_PC && USER_AGENT.includes('Mac');

View File

@ -0,0 +1,16 @@
export async function checkLocalMP3FileExists(src: string) {
if (!src) return false;
try {
const response = await new Promise<XMLHttpRequest>((resolve, reject) => {
const xhr = new XMLHttpRequest();
xhr.open('HEAD', src, true);
xhr.onload = () => resolve(xhr);
xhr.onerror = () => reject(xhr);
xhr.send();
});
return response.status === 200 && response.getResponseHeader('Content-Type') === 'audio/mpeg';
} catch (error) {
console.warn(error);
return false;
}
}

View File

@ -0,0 +1,31 @@
import { isPlainObject } from './common-utils';
const isEmpty = function (input: any) {
// Null and Undefined...
if (input === null || typeof (input) === 'undefined') return true;
// Booleans...
if (typeof input === 'boolean') return false;
// Numbers...
if (typeof input === 'number') return input === 0;
// Strings...
if (typeof input === 'string') return input.length === 0;
// Functions...
if (typeof input === 'function') return input.length === 0;
// Arrays...
if (Array.isArray(input)) return input.length === 0;
// Errors...
if (input instanceof Error) return input.message === '';
// plain object
if (isPlainObject(input)) {
// eslint-disable-next-line
for (const key in input) {
if (Object.prototype.hasOwnProperty.call(input, key)) {
return false;
}
}
return true;
}
return false;
};
export default isEmpty;

View File

@ -0,0 +1,36 @@
/**
*
* @param {Object} options
* @param {Object} options.retryFunction
* @param {Object} options.settings
* @param {Number} [options.settings.retries = 5]
* @param {Number} [options.settings.timeout = 1000]
* @param {onErrorCallback} options.onError
* @param {onRetryingCallback} [options.onRetrying]
* @param {Object} options.context
* @returns {Function}
* @example
* const getUserMedia = promiseRetry({
* retryFunction: getUserMedia_,
* settings: { retries: 5, timeout: 2000 },
* onError: (error, retry, reject) => {
* if (error.name === 'NotReadableError') {
* retry();
* } else {
* reject(error);
* }
* },
* onRetrying: retryCount => {
* console.warn(`getUserMedia NotReadableError observed, retrying [${retryCount}/5]`);
* }
* });
*/
declare function promiseRetry({ retryFunction, settings, onError, onRetrying, onRetryFailed, context }: {
retryFunction: any;
settings: any;
onError: any;
onRetrying: any;
onRetryFailed: any;
context: any;
}): (...args: any[]) => Promise<unknown>;
export default promiseRetry;

View File

@ -0,0 +1,95 @@
"use strict";
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
return new (P || (P = Promise))(function (resolve, reject) {
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
step((generator = generator.apply(thisArg, _arguments || [])).next());
});
};
Object.defineProperty(exports, "__esModule", { value: true });
const common_utils_1 = require("./common-utils");
const RETRY_STATE_NOT_START = 0;
const RETRY_STATE_STARTED = 1;
const RETRY_STATE_STOPPED = 2;
/**
* 给异步函数封装重试逻辑
* @param {Object} options 重试逻辑入参
* @param {Object} options.retryFunction 需要封装的异步函数
* @param {Object} options.settings 重试属性
* @param {Number} [options.settings.retries = 5] 重试次数
* @param {Number} [options.settings.timeout = 1000] 重试间隔
* @param {onErrorCallback} options.onError 重试错误回调
* @param {onRetryingCallback} [options.onRetrying] 重试后的回调
* @param {Object} options.context 上下文可选
* @returns {Function} 封装后的函数
* @example
* const getUserMedia = promiseRetry({
* retryFunction: getUserMedia_,
* settings: { retries: 5, timeout: 2000 },
* onError: (error, retry, reject) => {
* if (error.name === 'NotReadableError') {
* retry();
* } else {
* reject(error);
* }
* },
* onRetrying: retryCount => {
* console.warn(`getUserMedia NotReadableError observed, retrying [${retryCount}/5]`);
* }
* });
*/
function promiseRetry({ retryFunction, settings, onError, onRetrying, onRetryFailed, context }) {
return function (...args) {
const retries = settings.retries || 5;
let retryCount = 0;
let timer = -1;
let retryState = RETRY_STATE_NOT_START;
const run = (resolve, reject) => __awaiter(this, void 0, void 0, function* () {
const ctx = context || this;
try {
const result = yield retryFunction.apply(ctx, args);
// 执行成功,正常返回
retryCount = 0;
resolve(result);
}
catch (error) {
// 用于停止重试
const stopRetry = () => {
clearTimeout(timer);
retryCount = 0;
retryState = RETRY_STATE_STOPPED;
reject(error);
};
const retry = () => {
if (retryState !== RETRY_STATE_STOPPED && retryCount < retries) {
retryCount++;
retryState = RETRY_STATE_STARTED;
if ((0, common_utils_1.isFunction)(onRetrying)) {
onRetrying.call(ctx, retryCount, stopRetry);
}
timer = setTimeout(() => {
timer = -1;
run(resolve, reject);
}, (0, common_utils_1.isUndefined)(settings.timeout) ? 1000 : settings.timeout);
}
else {
stopRetry();
if ((0, common_utils_1.isFunction)(onRetryFailed)) {
onRetryFailed.call(ctx, error);
}
}
};
if ((0, common_utils_1.isFunction)(onError)) {
onError.call(ctx, error, retry, reject, args);
}
else {
retry();
}
}
});
return new Promise(run);
};
}
exports.default = promiseRetry;

View File

@ -0,0 +1,88 @@
import { isFunction, isUndefined } from './common-utils';
const RETRY_STATE_NOT_START = 0;
const RETRY_STATE_STARTED = 1;
const RETRY_STATE_STOPPED = 2;
/**
*
* @param {Object} options
* @param {Object} options.retryFunction
* @param {Object} options.settings
* @param {Number} [options.settings.retries = 5]
* @param {Number} [options.settings.timeout = 1000]
* @param {onErrorCallback} options.onError
* @param {onRetryingCallback} [options.onRetrying]
* @param {Object} options.context
* @returns {Function}
* @example
* const getUserMedia = promiseRetry({
* retryFunction: getUserMedia_,
* settings: { retries: 5, timeout: 2000 },
* onError: (error, retry, reject) => {
* if (error.name === 'NotReadableError') {
* retry();
* } else {
* reject(error);
* }
* },
* onRetrying: retryCount => {
* console.warn(`getUserMedia NotReadableError observed, retrying [${retryCount}/5]`);
* }
* });
*/
function promiseRetry({ retryFunction, settings, onError, onRetrying, onRetryFailed, context }) {
return function(...args) {
const retries = settings.retries || 5;
let retryCount = 0;
let timer: any = -1;
let retryState = RETRY_STATE_NOT_START;
const run = async (resolve, reject) => {
const ctx = context || this;
try {
const result = await retryFunction.apply(ctx, args);
// 执行成功,正常返回
retryCount = 0;
resolve(result);
} catch (error) {
// 用于停止重试
const stopRetry = () => {
clearTimeout(timer);
retryCount = 0;
retryState = RETRY_STATE_STOPPED;
reject(error);
};
const retry = () => {
if (retryState !== RETRY_STATE_STOPPED && retryCount < retries) {
retryCount++;
retryState = RETRY_STATE_STARTED;
if (isFunction(onRetrying)) {
onRetrying.call(ctx, retryCount, stopRetry);
}
timer = setTimeout(
() => {
timer = -1;
run(resolve, reject);
},
isUndefined(settings.timeout) ? 1000 : settings.timeout
);
} else {
stopRetry();
if (isFunction(onRetryFailed)) {
onRetryFailed.call(ctx, error);
}
}
};
if (isFunction(onError)) {
onError.call(ctx, error, retry, reject, args);
} else {
retry();
}
}
};
return new Promise(run);
};
}
export default promiseRetry;

View File

@ -42,7 +42,7 @@ declare class Timer {
* @param {*} count
* @returns ID
*/
static interval(taskItem: any): NodeJS.Timer;
static interval(taskItem: any): NodeJS.Timeout;
/**
*
* count = 0,

View File

@ -0,0 +1,162 @@
/* eslint-disable */
import { isPlainObject, performanceNow, isFunction } from './common-utils';
import { NAME } from '../const/index';
/**
*
* 1. [1,n]
* @example
* // 默认嵌套执行count=0 无限次
* timer.run(callback, {delay: 2000});
* // count=1 等同于 原始setTimeout
* timer.run(callback, {delay: 2000, count:0});
* 2. RAF audio音量获取等任务退 setTimeout 1s
* @example
* // 默认60fps可以根据单位时长换算默认开启后台执行
* timer.run('raf', callback, {fps: 60});
* // 设置执行次数
* timer.run('raf', callback, {fps: 60, count: 300, backgroundTask: false});
* 3. , requestIdleCallback storage
* @example
* // 支持原生setInterval 但不推荐使用,定时任务推荐用 timeout
* timer.run('interval', callback, {delay:2000, count:10})
*/
class Timer {
static taskMap = new Map();
static currentTaskID = 1;
static generateTaskID() {
return this.currentTaskID++;
}
/**
*
* @param {string} taskName 'interval' 'timeout'
* @param {function} callback
* @param {object} options include:
* @param {number} options.delay millisecond
* @param {number} options.count 0 or n次
* @param {boolean} options.backgroundTask
*/
static run(taskName = NAME.TIMEOUT, callback: any, options: any) {
// default options
if (taskName === NAME.INTERVAL) {
options = { ...{ delay: 2000, count: 0, backgroundTask: true }, ...options };
} else {
options = { ...{ delay: 2000, count: 0, backgroundTask: true }, ...options };
}
// call run(function, {...})
if (isPlainObject(callback)) {
options = { ...options, ...callback };
}
if (isFunction(taskName)) {
callback = taskName;
taskName = NAME.TIMEOUT;
}
// 1. 创建 taskID作为 timer task 的唯一标识,在本函数执行完后返回,用于在调用的地方实现互斥逻辑
// 2. 根据 taskName 执行相应的函数
const taskItem = {
taskID: this.generateTaskID(),
loopCount: 0,
intervalID: null,
timeoutID: null,
taskName,
callback,
...options
};
this.taskMap.set(taskItem.taskID, taskItem);
// console.log(`timer run task:${taskItem.taskName}, task queue size: ${this.taskMap.size}`);
if (taskName === NAME.INTERNAL) {
this.interval(taskItem);
} else {
this.timeout(taskItem);
}
return taskItem.taskID;
}
/**
*
*
*
* @param {object} taskItem
* @param {function} callback
* @param {*} delay
* @param {*} count
* @returns ID
*/
static interval(taskItem: any) {
// setInterval 缺点,浏览器退后台会降频,循环执行间隔时间不可靠
// 创建进入定时器循环的任务函数,函数内1. 判断是否满足执行条件2.执行 callback
// 通过 setInterval 执行任务函数
// 将 intervalID 记录到 taskMap 对应的 item
const task = () => {
taskItem.callback();
taskItem.loopCount += 1;
if (this.isBreakLoop(taskItem)) {
return;
}
};
return taskItem.intervalID = setInterval(task, taskItem.delay);
}
/**
*
* count = 0,
* count = n, n次
* @param {object} taskItem
*
*/
static timeout(taskItem: any) {
// setTimeout 浏览器退后台延迟变为至少1s
const task: any = () => {
// 执行回调
taskItem.callback();
taskItem.loopCount += 1;
if (this.isBreakLoop(taskItem)) {
return;
}
// 不修正延迟每次callback间隔平均
return taskItem.timeoutID = setTimeout(task, taskItem.delay);
};
return taskItem.timeoutID = setTimeout(task, taskItem.delay);
}
static hasTask(taskID: any) {
return this.taskMap.has(taskID);
}
static clearTask(taskID: any) {
// console.log('timer clearTask start', `| taskID:${taskID} | size:${this.taskMap.size}`);
if (!this.taskMap.has(taskID)) {
return true;
}
const { intervalID, timeoutID, onVisibilitychange } = this.taskMap.get(taskID);
if (intervalID) {
clearInterval(intervalID);
}
if (timeoutID) {
clearTimeout(timeoutID);
}
if (onVisibilitychange) {
document.removeEventListener('visibilitychange', onVisibilitychange);
}
this.taskMap.delete(taskID);
// console.log('timer clearTask end ', `| size:${this.taskMap.size}`);
return true;
}
/**
* 1. 退
* 2. 退
* @param {object} taskItem
* @returns
*/
static isBreakLoop(taskItem: any) {
if (!this.taskMap.has(taskItem.taskID)) {
return true;
}
if (taskItem.count !== 0 && taskItem.loopCount >= taskItem.count) {
this.clearTask(taskItem.taskID);
return true;
}
return false;
}
}
export default Timer;

View File

@ -0,0 +1,34 @@
import { NAME } from '../../const/index';
/**
*
* @export
* @param {Object} options
* @param {Function} options.fn
* @param {Object} options.context
* @param {String} options.name
* @returns {Function}
*/
export function avoidRepeatedCall() {
return function (target: any, name: string, descriptor: any) {
const oldFn = descriptor.value;
const isCallingSet = new Set();
descriptor.value = async function (...args: any[]) {
if (isCallingSet.has(this)) {
console.warn((`${NAME.PREFIX}previous ${name}() is ongoing, please avoid repeated calls`));
// throw new Error(`previous ${name}() is ongoing, please avoid repeated calls`);
return;
}
try {
isCallingSet.add(this);
const result = await oldFn.apply(this, args);
isCallingSet.delete(this);
return result;
} catch (error) {
isCallingSet.delete(this);
throw error;
}
};
return descriptor;
};
}

View File

@ -0,0 +1,11 @@
import { avoidRepeatedCall } from './avoidRepeatedCall';
import { paramValidate } from './validateParams';
import { VALIDATE_PARAMS } from './validateConfig';
// import { apiCallQueue } from "./apiCallQueue";
export {
VALIDATE_PARAMS,
paramValidate,
avoidRepeatedCall,
// apiCallQueue,
};

View File

@ -0,0 +1,188 @@
import { NAME, MAX_NUMBER_ROOM_ID, VideoResolution, VideoDisplayMode } from "../../const/index";
export const VALIDATE_PARAMS = {
init: {
SDKAppID: {
required: true,
rules: [NAME.NUMBER],
allowEmpty: false,
},
userID: {
required: true,
rules: [NAME.STRING],
allowEmpty: false,
},
userSig: {
required: true,
rules: [NAME.STRING],
allowEmpty: false,
},
tim: {
required: false,
rules: [NAME.OBJECT],
},
},
call: {
userID: {
required: true,
rules: [NAME.STRING],
allowEmpty: false
},
type: {
required: true,
rules: [NAME.NUMBER],
range: [1, 2],
allowEmpty: false
},
roomID: {
required: false,
rules: [NAME.NUMBER], // 仅支持数字房间号, 后续会支持字符串房间号
range: `1~${MAX_NUMBER_ROOM_ID}`,
allowEmpty: false,
},
userData: {
required: false,
rules: [NAME.STRING],
allowEmpty: false,
},
timeout: {
required: false,
rules: [NAME.NUMBER],
allowEmpty: false
}
},
groupCall: {
userIDList: {
required: true,
rules: [NAME.ARRAY],
allowEmpty: false
},
type: {
required: true,
rules: [NAME.NUMBER],
range: [1, 2],
allowEmpty: false
},
groupID: {
required: true,
rules: [NAME.STRING],
allowEmpty: false
},
roomID: {
required: false,
rules: [NAME.NUMBER], // 仅支持数字房间号, 后续会支持字符串房间号
range: `1~${MAX_NUMBER_ROOM_ID}`,
allowEmpty: false
},
timeout: {
required: false,
rules: [NAME.NUMBER],
allowEmpty: false
},
userData: {
required: false,
rules: [NAME.STRING],
allowEmpty: false,
},
offlinePushInfo: {
required: false,
rules: [NAME.OBJECT],
allowEmpty: false,
},
},
joinInGroupCall: {
type: {
required: true,
rules: [NAME.NUMBER],
range: [1, 2],
allowEmpty: false
},
groupID: {
required: true,
rules: [NAME.STRING],
allowEmpty: false
},
roomID: {
required: true,
rules: [NAME.NUMBER],
allowEmpty: false,
},
},
inviteUser: {
userIDList: {
required: true,
rules: [NAME.ARRAY],
allowEmpty: false
},
},
setSelfInfo: {
nickName: {
required: false,
rules: [NAME.STRING],
allowEmpty: false,
},
avatar: {
required: false,
rules: [NAME.STRING],
allowEmpty: false,
}
},
enableFloatWindow: [
{
key: "enable",
required: false,
rules: [NAME.BOOLEAN],
allowEmpty: false,
}
],
enableAIVoice: [
{
key: "enable",
required: true,
rules: [NAME.BOOLEAN],
allowEmpty: false,
}
],
enableMuteMode: [
{
key: "enable",
required: true,
rules: [NAME.BOOLEAN],
allowEmpty: false,
}
],
setCallingBell: [
{
key: "filePath",
required: false,
rules: [NAME.STRING],
allowEmpty: true,
}
],
setLanguage: [
{
key: "language",
required: true,
rules: [NAME.STRING],
allowEmpty: false
}
],
setVideoDisplayMode: [
{
key: "displayMode",
required: true,
rules: [NAME.STRING],
range: [VideoDisplayMode.CONTAIN, VideoDisplayMode.COVER, VideoDisplayMode.FILL],
allowEmpty: false
}
],
setVideoResolution: [
{
key: "resolution",
required: true,
rules: [NAME.STRING],
range: [VideoResolution.RESOLUTION_1080P, VideoResolution.RESOLUTION_480P, VideoResolution.RESOLUTION_720P],
allowEmpty: false
}
]
};

View File

@ -0,0 +1,88 @@
import { getType, isArray, isString, isUndefined, isNumber, modifyObjectKey } from "../common-utils";
import { NAME } from "../../const/index";
const PREFIX = NAME.PREFIX + "API";
export function paramValidate (config: any) {
return function (target, propertyName: string, descriptor: PropertyDescriptor) {
let method = descriptor.value;
descriptor.value = function (...args) {
doValidate.call(this, config, args, propertyName);
return method.apply(this, args);
};
return descriptor;
};
}
function doValidate(config, args, name) {
try {
// 兼容 init 方法中: SDKAppID sdkAppID 两种写法的参数校验判断
if (!args[0].SDKAppID) {
config = modifyObjectKey(config, "SDKAppID", "sdkAppID");
}
if (isArray(config)) {
for (let i = 0; i < config.length; i++) {
check.call(this, {
...config[i],
value: args[i],
name,
});
}
} else {
for (const key in config) {
if (config.hasOwnProperty(key)) {
check.call(this, {
...config[key],
value: args[0][key],
name,
key,
});
}
}
}
} catch (error) {
console.error(error);
throw error;
}
}
function check({ required, rules, range, value, allowEmpty, name, key }) {
// 用户没传指定参数
if (isUndefined(value)) {
// 检查必填参数, 若配置是必填则报错
if (required) {
throw new Error(`${PREFIX}<${name}>: ${key} is required.`);
} else {
return;
}
}
// 判断参数类型是否正确
const result = rules.some((item)=>item === getType(value));
let type = '';
if (!result) {
for (let i = 0; i < rules.length; i++) {
let str = rules[i];
str = str.replace(str[0], str[0].toUpperCase());
type += `${str}/`;
}
type = type.substring(0, type.length - 1);
throw new Error(`${PREFIX}<${name}>: ${key} must be ${type}, current ${key} is ${typeof value}.`);
}
// 不允许传空值, 例如: '', ' '
if (allowEmpty === false) {
const isEmptyString = isString(value) && value.trim() === '';
if (isEmptyString) {
throw new Error(`${PREFIX}<${name}>: ${key} is blank.`);
}
}
// 判断是否符合限制条件
if (isArray(range)) {
if (range && range.indexOf(value) === -1) {
throw new Error(`${PREFIX}<${name}>: ${key} error, only be ${range}, current ${key} is ${value}.`);
}
}
// 取值范围, 前闭后闭
if (isString(range) && range.indexOf('~') !== -1) {
const valueList = range.split('~');
if (value < +valueList[0] || value > +valueList[1] || (isNumber(value) && Number.isNaN(value))) {
throw new Error(`${PREFIX}<${name}>: ${key} error, only be ${range}, current ${key} is ${value}.`);
}
}
}

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -1,4 +1,9 @@
Page({
data: {},
onShow() {},
onShow() {
},
onHide(){
console.log('接听页面隐藏');
}
});

View File

@ -228,14 +228,13 @@ Component({
}];
return renderDom;
}
//13 SEND_TALK
if (customMessage.message_type === GDXZ_CUSTOM_MSEEAGE.SEND_TALK){
//13 SEND_TALK 14
if (customMessage.message_type === GDXZ_CUSTOM_MSEEAGE.SEND_TALK || customMessage.message_type === GDXZ_CUSTOM_MSEEAGE.RECOMMEND_TALK){
const renderDom = [{
type: 'send_talk',
title: customMessage.title,
desc: customMessage.desc,
}];
return renderDom;
}
@ -246,8 +245,7 @@ Component({
const renderDom = [{
type: 'video_time',
title: customMessage.title,
desc: customMessage.desc,
desc: customMessage.desc.replace(/text/g,'span')
}];
return renderDom;
}

View File

@ -35,7 +35,9 @@
<view class="custom-content-text">{{renderDom[0].text}}</view>
</view>
<view wx:if="{{renderDom[0].type ==='c2cCalling' || renderDom[0].type ==='groupCalling'}}" class="call-message {{isMine?'my-text':'your-text'}}" >
<image src="{{isMine?'../../../../../static/images/video-right.png':'../../../../../static/images/video-left.png'}}" mode="" class="{{isMine?'classImgright':'classImgleft'}}"/>
<view class="custom-content-text">{{renderDom[0].text}}</view>
</view>
<!-- <view wx:if="{{renderDom[0].type ==='notSupport'}}" class="message-body-span text-message" >
<view class="message-body-span-text">{{renderDom[0].text}}</view>
@ -144,10 +146,10 @@
</view>
</view>
<view class="videotime" wx:if="{{renderDom[0].type==='video_time'}}" >
{{renderDom[0].desc}}
<rich-text nodes="{{renderDom[0].desc}}"></rich-text>
</view>
<view class="videotime" wx:if="{{renderDom[0].type==='send_talk'}}" >
{{renderDom[0].desc}}
<rich-text nodes="{{renderDom[0].desc}}"></rich-text>
</view>
</view>

View File

@ -422,6 +422,7 @@ color: #666666;
}
.call-message.my-text {
max-width: 60vw;
flex-direction: row-reverse;
min-height: 50rpx;
padding: 10rpx 24rpx;
line-height: 50rpx;
@ -470,3 +471,24 @@ color: #666666;
line-height: 44rpx;
color: rgba(0,0,0,0.65);
}
.classImgleft{
width:30rpx;
height:30rpx;
margin-right:10rpx;
}
.classImgright{
margin-top: 4rpx;
width:30rpx;
height:30rpx;
margin-left:10rpx;
}
.call-message{
display: flex;
align-items: center;
}
.tipColor{
color:#3BB5FE;
}
.tipRoundsColor{
color: #FF4D4F;
}

View File

@ -148,9 +148,10 @@ Component({
recordtime:0,
dialog_message: "在线开处方需先进行多点执业认证",
message_rounds: 0,
times_number:'',
duration:'',
times_number:2,
duration:7,
networkstatus: "wifi",
defaultRoundIndex:0,
static_host: api.getStaticHost(),
formatter(type, value) {
if (type === 'year') {
@ -333,6 +334,12 @@ Component({
* 组件的方法列表
*/
methods: {
onClickHide(){
this.setData({
showTalk:false
})
},
noop() {},
sendTalk(){
let {order_inquiry_id,duration,times_number}=this.data;
if(times_number==''){
@ -382,6 +389,17 @@ Component({
})
},
openRound(){
let {duration,columnsRound}=this.data;
for (let i = 0; i < columnsRound.length; i++) {
if(columnsRound[i]==duration){
this.setData({
defaultRoundIndex:i
});
break;
};
}
this.setData({
showRound:true
})
@ -410,7 +428,7 @@ Component({
title: '预约成功',
icon:"none"
})
this.triggerEvent("freshVideoInfo")
this.triggerEvent("freshVideoInfo");
}
}).catch(errors => {
@ -425,7 +443,9 @@ Component({
call() {
let {baseInfo}=this.data;
console.log(baseInfo)
wx.setStorage('patientInfo',{
wx.setStorage({
key:'patientInfo',
data:{
order_inquiry_id:baseInfo.order_inquiry_id,
inquiry_type:baseInfo.inquiry_type,
inquiry_mode:baseInfo.inquiry_mode,
@ -436,18 +456,22 @@ Component({
patient_sex:baseInfo.patient_family_sex,
patient_age:baseInfo.patient_family_age
},
is_system:0
is_system:1
}
}
)
api.geRoomId({
order_inquiry_id:baseInfo.order_inquiry_id
}).then(async(res)=>{
let result=res.data;
console.log("userID:"+this.data.patient_user_id)
TUICallKitServer.setLogLevel(0);
await TUICallKitServer.call({
userID: this.data.patient_user_id,
type: 2,
roomID:Number(result)
});
this.triggerEvent("freshVideoInfo");
})
},
@ -1470,9 +1494,19 @@ Component({
app.go("/user/pages/yishi/write_sickform/index?order_inquiry_id="+this.data.order_inquiry_id)
},
showSendTalk(){
this.setData({
showTalk:true
})
let {baseInfo,message_rounds}=this.data;
console.log(message_rounds)
if(baseInfo.inquiry_status==4 || baseInfo.inquiry_status==5){
this.setData({
showTalk:true
})
}else{
wx.showToast({
title: '结束问诊或患者回合沟通数为0后才可赠送患者沟通回合数',
icon:'none',
})
}
}
},

View File

@ -3,7 +3,7 @@
<view class="TUI-commom-function-item" data-key="10" bindtap="handleCommonFunctions">查看完整病历</view>
<view class="TUI-commom-function-item" data-key="11" bindtap="handleCommonFunctions" wx:if="{{baseInfo.multi_point_status == 1 && baseInfo.inquiry_status==4 && baseInfo.multi_point_enable==1}}">在线开处方</view>
<view class="TUI-commom-function-item" data-key="12" bindtap="handleCommonFunctions" wx:if="{{baseInfo.multi_point_status == 1 && baseInfo.inquiry_status==4 && baseInfo.multi_point_enable==1 && !videoInfo.is_reservation_time && baseInfo.inquiry_mode==2 }}">预约视频时间</view>
<view class="TUI-commom-function-item" data-key="13" bindtap="handleCommonFunctions" wx:if="{{baseInfo.multi_point_status == 1 && baseInfo.inquiry_status==4 && baseInfo.multi_point_enable==1 && videoInfo.is_reservation_time && videoInfo.is_video==0 && baseInfo.inquiry_mode==2 }}">发起视频</view>
<view class="TUI-commom-function-item" data-key="13" bindtap="handleCommonFunctions" wx:if="{{baseInfo.multi_point_status == 1 && baseInfo.inquiry_status==4 && baseInfo.multi_point_enable==1 && videoInfo.is_reservation_time && baseInfo.inquiry_mode==2 }}">发起视频</view>
<!-- wx:if="{{baseInfo.multi_point_status == 1 && baseInfo.inquiry_status==4 && baseInfo.multi_point_enable==1 && videoInfo.is_reservation_time && videoInfo.is_video==0 }}" -->
</view>
<view class="TUI-message-input">
@ -63,7 +63,7 @@
<image mode="widthFix" class="TUI-Extension-icon MY-TUI-Extension-icon" src="{{static_host}}/applet/doctor/static/images/yishi/wenzhenicon.png" />
<view class="TUI-Extension-slot-name MY-TUI-Extension-slot-name">问诊表</view>
</view>
<view class="TUI-Extension-slot MY-TUI-Extension-slot" bindtap="showSendTalk" wx:if="{{baseInfo.inquiry_status==5}}">
<view class="TUI-Extension-slot MY-TUI-Extension-slot" bindtap="showSendTalk" >
<image mode="widthFix" class="TUI-Extension-icon MY-TUI-Extension-icon" src="../../../../static/images/talk.png" style="width:70rpx;height:70rpx;padding:18rpx"/>
<view class="TUI-Extension-slot-name MY-TUI-Extension-slot-name">赠送沟通</view>
</view>
@ -163,9 +163,9 @@
立即发起
</view>
</t-dialog>
<van-overlay show="{{ showTalk }}" z-index="9900">
<van-overlay show="{{ showTalk }}" z-index="9900" bind:click="onClickHide">
<view class="wrapper">
<view class="custombox">
<view class="custombox" catch:tap="noop">
<view class="title">赠送患者免费图文回合沟通数</view>
<view class="roundBox">
<view class="lname">回合数</view>
@ -208,7 +208,7 @@ z-index="9901"
position="bottom"
custom-style="height: 50%"
>
<van-picker columns="{{ columnsRound }}" show-toolbar
<van-picker columns="{{ columnsRound }}" default-index="{{defaultRoundIndex}}" show-toolbar
title="选择回合天数" bind:cancel="onCancelRound"
bind:confirm="onConfirmRound" />
</van-popup>

View File

@ -59,6 +59,7 @@ Component({
unreadCount: '',
conversation: {}, // 当前会话
baseInfo:{},
sendTalkEnable:true,
messageList: [],
// 自己的 ID 用于区分历史消息中,哪部分是自己发出的
scrollView: '',
@ -344,11 +345,27 @@ Component({
let order_inquiry_id = cloudCustomDataJson.order_inquiry_id;
let now_order_inquiry_id = this.data.order_inquiry_id
let payload = message.payload.data;
if(order_inquiry_id != now_order_inquiry_id){
return
return;
// if(payload){
// const payload_Data = JSON.parse(message.payload.data);
// if( payload_Data && payload_Data.message_type!=13){
// return
// }else{
// this.setData({
// message_rounds: 0
// })
// console.log('刷新refreshMessageRounds')
// var myEventOption = {}
// myEventOption.message_rounds = 0
// this.triggerEvent('refreshMessageRounds', myEventOption)
// }
// }
}
let payload = message.payload.data;
if(payload){
const payloadData = JSON.parse(message.payload.data);
if(payloadData){
@ -390,20 +407,36 @@ Component({
let type = message.type;
// console.log("message type: ", type);
let show_avatar = true;
console.log("执行1——————————————————————")
if(type === "TIMCustomElem"){
const customMessage = JSON.parse(message.payload.data);
if(Number(customMessage.message_type) != GDXZ_CUSTOM_MSEEAGE.PRESCRIBE && Number(customMessage.message_type) != GDXZ_CUSTOM_MSEEAGE.PRESCRIBE_VERIFY && Number(customMessage.message_type) != GDXZ_CUSTOM_MSEEAGE.SUGAR_CHECK && Number(customMessage.message_type) != GDXZ_CUSTOM_MSEEAGE.WENZHEN_FORM && message.cloudCustomData){
show_avatar = false;
}
if(Number(customMessage.message_type) == GDXZ_CUSTOM_MSEEAGE.TRABECULA && refreshBaseInfo){
const title = customMessage.title
const title = customMessage.title;
if(title.indexOf("问诊已结束") > -1){
setTimeout(() => {
//收到横条消息去触发父组件getbase方法,演示1秒
this.triggerEvent('getInquiryMessageBasic');
}, 1000);
}
}
};
// if(Number(customMessage.message_type) == GDXZ_CUSTOM_MSEEAGE.SEND_TALK){
// console.log("执行2——————————————————————")
// const title = customMessage.desc;
// console.log(title)
// if(title.indexOf("医生已赠送") > -1){
// setTimeout(() => {
// console.log('已赠送')
// //收到横条消息去触发父组件getbase方法,演示1秒
// // this.triggerEvent('getInquiryMessageBasic', customMessage.data.order_inquiry_id);
// }, 1000);
// }
// }
}
@ -493,6 +526,24 @@ Component({
}
if(!this.checkShowAvatar(message, true)){
message.no_avatar = true;
};
if(message.type=='TIMCustomElem'){
const customMessage = JSON.parse(message.payload.data);
const info=JSON.parse(message.cloudCustomData);
console.log(customMessage);
console.log(info);
if(Number(customMessage.message_type) == GDXZ_CUSTOM_MSEEAGE.SEND_TALK){
if(info){
wx.showToast({
title: info.inquiry_type,
})
wx.redirectTo({
url: '/TUIChatService/pages/index?order_inquiry_id='+customMessage.data.order_inquiry_id+"&from_account=end&inquiry_type="+info.inquiry_type,
})
}
}
}
wx.$TUIKit.setMessageRead({ conversationID: this.data.conversation.conversationID }).then(() => {
logger.log('| MessageList | setMessageRead | ok');
@ -1006,7 +1057,7 @@ Component({
},
goMedinceList(){
wx.navigateTo({
url: '/Pages/yishi/medince_list/index',
url: '/user/pages/yishi/medince_list/index',
})
},
},

View File

@ -5,7 +5,7 @@
<view style="width: 100%;text-align: center;position: absolute;top: 20rpx;">
<van-loading size="24px" wx:if="{{list_first_loading}}" vertical color="#1989fa">加载中...</van-loading>
</view>
<view class="t-message" wx:if="{{conversation.type !== '@TIM#SYSTEM'}}" wx:for="{{messageList}}" wx:key="index" hidden="{{!(item.payload.data &&concat.formateText(item.payload.data).message_type!=20 || item.type!='TIMCustomElem')}}" data-index ='{{index}}'>
<view class="t-message" wx:if="{{conversation.type !== '@TIM#SYSTEM'}}" wx:for="{{messageList}}" wx:key="index" hidden="{{!(item.payload.data && concat.formateText(item.payload.data).message_type!=20 || item.type!='TIMCustomElem') || (item.payload.data && concat.formateText(item.payload.data).message_type==2) }}" data-index ='{{index}}'>
<view class="time-pop-mask" data-value="{{item.time}}" wx:if="{{item.showMessageTime}}">
<view class="showmessagetime 1" wx:if="{{item.isShowTime}}">
<text class="time" wx:if="{{!item.isDeleted && !item.isRevoked}}">{{item.messageTime}} </text>

View File

@ -110,7 +110,8 @@ Component({
},
ready() {
console.log('222222222222222o');
console.log(this.data.baseInfo);
let hasTip=wx.getStorageSync('showNewerTip');
if(hasTip){
this.setData({
@ -155,6 +156,17 @@ Component({
}
},
pageLifetimes: {
show: function() {
// console.log(11111);
// if(this.data.order_inquiry_id){
// this.getAudioMsg();
// }
// 页面被展示
},
},
/**
* 组件的初始数据
*/
@ -183,6 +195,7 @@ Component({
viewData: {
style: inputStyle,
},
order_inquiry_id:'',
input_area_style:"",
KeyboardHeight: 0,
showTips: false,
@ -215,6 +228,7 @@ Component({
})
},
getAudioMsg(){
console.log(this.data.order_inquiry_id)
api.getVideoMsg(this.data.order_inquiry_id).then(response => {
let result=response.data
console.log(result);
@ -406,10 +420,12 @@ Component({
},
//获取问诊订单消息内页基础数据
getInquiryMessageBasic() {
getInquiryMessageBasic(e) {
let id=e.detail?e.detail:this.data.order_inquiry_id;
console.log(id);
// console.log("order_inquiry_id: ", this.data.order_inquiry_id);
api.getInquiryMessageBasic({order_inquiry_id: this.data.order_inquiry_id}).then(response => {
console.log("11111111111111111111111111111111111111")
api.getInquiryMessageBasic({order_inquiry_id:id }).then(response => {
//console.log("11111111111111111111111111111111111111")
// console.log(response);
this.setData({
baseInfo: response.data,

View File

@ -32,7 +32,7 @@
<view class="status {{baseInfo.inquiry_status==5?'status_complete':''}}" wx:else="{{baseInfo.inquiry_status!=4}}">{{baseInfo.inquiry_status==1?'待支付':baseInfo.inquiry_status==2?'待分配':baseInfo.inquiry_status==3?'待接诊':baseInfo.inquiry_status==4?'接诊中':baseInfo.inquiry_status==5?'问诊完成':baseInfo.inquiry_status==6?'已结束':baseInfo.inquiry_status==7?'已取消':'未知'}}</view>
</view>
</view>
<view class="yuyuetip" bind:tap="openTime" wx:if="{{isEditTime && videoInfo.is_reservation_time && (baseInfo.inquiry_status==4)}}">
<view class="yuyuetip" bind:tap="openTime" wx:if="{{isEditTime && videoInfo.is_reservation_time && (baseInfo.inquiry_status==4) && videoInfo.is_video==0}}">
<view >如需要修改视频时间,请点击这里</view>
<van-icon name="arrow" size="14px" style="margin-top: 3rpx;" color="#FF9C00" />
</view>
@ -43,7 +43,7 @@
<image class="tui-navigatorbar-back" bindtap="goBack" src="../../static/assets/ic_back_black.svg" />
<view class="conversation-title">{{conversationName}}</view>
</view> -->
<view class="list-box {{ showTips && 'list-box-notips'}} || {{ showGroupTips && 'list-box-group'}} || {{ showAll && 'list-box-group-notips'}}" style="height: calc(100vh {{baseInfo.inquiry_status==4?'- 197rpx':'- 196rpx'}} - 100rpx - {{navbar_height}}px {{(isEditTime && videoInfo.is_reservation_time && (baseInfo.inquiry_status==4)) ?'- 72rpx':'- 0rpx'}});"><!-- 100vh -input-area高度 - info高度 -navbar高度 -->
<view class="list-box {{ showTips && 'list-box-notips'}} || {{ showGroupTips && 'list-box-group'}} || {{ showAll && 'list-box-group-notips'}}" style="height: calc(100vh {{baseInfo.inquiry_status==4?'- 197rpx':'- 196rpx'}} - 100rpx - {{navbar_height}}px {{(isEditTime && videoInfo.is_reservation_time && (baseInfo.inquiry_status==4) && videoInfo.is_video==0) ?'- 72rpx':'- 0rpx'}});"><!-- 100vh -input-area高度 - info高度 -navbar高度 -->
<!-- <view wx:if="{{showTips}}" class="safetytips-box"> -->
<!-- <view class="safetytips">
<text>【安全提示】本 APP 仅用于体验腾讯云即时通信 IM 产品功能,不可用于业务洽谈与拓展。请勿轻信汇款、中奖等涉及钱款等信息,勿轻易拨打陌生电话,谨防上当受骗。</text>
@ -93,7 +93,7 @@
确定
</view>
</t-dialog>
<van-overlay show="{{ showTip }}" z-index="999">
<van-overlay show="{{ showTip && baseInfo.inquiry_mode==2 }}" z-index="999">
<view class="wrapper">
<view class="block">
<image src="{{static_host}}/applet/doctor/static/images/yishi/yuyue_star.png" mode="" class="star" />

View File

@ -1,5 +1,5 @@
<!--TUIKitWChat/Chat/index.wxml-->
<!-- <TUIConversation id="TUIConversation" wx:if="{{isShowConversationList}}" bind:currentConversationID="currentConversationID" bind:currentGroupID="currentGroupID"></TUIConversation> -->
<TUIChat id="TUIChat" currentConversationID="{{currentConversationID}}" inquiry_type="{{baseInfo.inquiry_type}}" order_inquiry_id="{{order_inquiry_id}}" baseInfo="{{baseInfo}}"
from="{{from}}" ></TUIChat>
from="{{from}}"></TUIChat>
<!-- <TUICallKit class="calling" id="TUICallKit" config="{{config}}" bind:sendMessage="sendMessage"></TUICallKit> -->

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 855 B

View File

@ -53,6 +53,7 @@ const constant = {
PATIENT_INFO:11,//患者信息
WENZHEN_FORM:12,//问诊表;
SEND_TALK:13,//赠送回合;
RECOMMEND_TALK:14,//订单结束,提醒赠送回合数
VIDEO_TIME:16,//新增/修改视频预约时间
},

View File

@ -1 +1,20 @@
/* TUIKit/pages/index.wxss */
.van-picker-column__item--selected,.active-class,.van-picker__confirm{
color: #3CC7C0!important;
}
.van-picker__title{
font-weight: 500!important;
font-size: 36rpx!important;
color: rgba(0,0,0,0.85)!important;
}
.van-picker__confirm{
font-weight: 600;
font-size: 32rpx;
color: #3CC7C0;
}
.van-picker__cancel{
font-weight: 600;
font-size: 32rpx;
color: rgba(0,0,0,0.65);
}

30
app.js
View File

@ -19,11 +19,7 @@ App({
} else {
this.globalData.share = false
};
this.globalData.scene = options.scene
//获取设备顶部窗口的高度(不同设备窗口高度不一样,根据这个来设置自定义导航栏的高度)
//这个最初我是在组件中获取,但是出现了一个问题,当第一次进入小程序时导航栏会把
//页面内容盖住一部分,当打开调试重新进入时就没有问题,这个问题弄得我是莫名其妙
//虽然最后解决了,但是花费了不少时间
this.globalData.scene = options.scene;
wx.getSystemInfo({
success: (res) => {
this.globalData.height = res.statusBarHeight
@ -100,6 +96,7 @@ App({
})
callback();
THIS.globalData.isLogin=true;
}).catch(function(imError) {
console.log('login error:', imError); // 登录失败的相关信息
if(type==1 && imError.indexOf("重复登录")!=-1){
@ -148,25 +145,6 @@ App({
console.log("onSDKReady from app.js");
this.globalData.chat_sdk_ready = true;
// let promise = wx.$TUIKit.deleteConversation('C2C500318318078251008');
// promise.then(function(imResponse) {
// // 删除会话成功
// const { conversationID } = imResponse.data; // 被删除的会话 ID
// }).catch(function(imError) {
// console.warn('deleteConversation error:', imError); // 删除会话失败的相关信息
// });
// console.log("TIM.version: ", wx.$TUIKitTIM)
// console.log("wx.$TUIKit: ",wx.$TUIKit.deleteConversation)
// // 删除单一会话, 不清空会话历史消息
// let promise = wx.$TUIKit.deleteConversation({conversationIDList:["C2C492404831991414785"], clearHistoryMessage: false});
// promise.then(function(imResponse) {
// // 删除会话成功
// console.log("删除成功!!")
// const { conversationIDList } = imResponse.data; // 被删除的会话 ID 列表
// }).catch(function(imError) {
// console.warn('deleteConversation error:', imError); // 删除会话失败的相关信息
// });
let usertype = wx.getStorageSync('usertype');
let userID = wx.getStorageSync('user_id_'+usertype);
@ -245,6 +223,7 @@ App({
this.formatUnReadNum(unread_name);
},
formatUnReadNum(name, increment=1){
try {
let val = wx.getStorageSync(name);
if(val == "") val = 0;
if(increment > 0){
@ -256,6 +235,9 @@ App({
if(val > 99) val = '+99';
if(val == 0) val = '';
wx.setStorageSync(name, val);
} catch (error) {
}
},
aegisInit() {
wx.aegis = new Aegis({

View File

@ -1,7 +1,7 @@
/// <reference types="miniprogram-api-typings" />
/// <reference types="miniprogram-api-typings" />
export declare type Action = 'confirm' | 'cancel' | 'overlay';
declare type DialogContext = WechatMiniprogram.Page.TrivialInstance | WechatMiniprogram.Component.TrivialInstance;
export type Action = 'confirm' | 'cancel' | 'overlay';
type DialogContext = WechatMiniprogram.Page.TrivialInstance | WechatMiniprogram.Component.TrivialInstance;
interface DialogOptions {
lang?: string;
show?: boolean;

View File

@ -6,6 +6,7 @@ var color_1 = require("../common/color");
var utils_1 = require("../common/utils");
(0, component_1.VantComponent)({
mixins: [button_1.button],
classes: ['cancle-button-class', 'confirm-button-class'],
props: {
show: {
type: Boolean,

View File

@ -33,7 +33,7 @@
size="large"
loading="{{ loading.cancel }}"
class="van-dialog__button van-hairline--right"
custom-class="van-dialog__cancel"
custom-class="van-dialog__cancel cancle-button-class"
custom-style="color: {{ cancelButtonColor }}"
bind:click="onCancel"
>
@ -44,7 +44,7 @@
size="large"
class="van-dialog__button"
loading="{{ loading.confirm }}"
custom-class="van-dialog__confirm"
custom-class="van-dialog__confirm confirm-button-class"
custom-style="color: {{ confirmButtonColor }}"
open-type="{{ confirmButtonOpenType }}"
@ -75,7 +75,7 @@
size="large"
loading="{{ loading.cancel }}"
class="van-dialog__button van-hairline--right"
custom-class="van-dialog__cancel"
custom-class="van-dialog__cancel cancle-button-class"
custom-style="color: {{ cancelButtonColor }}"
bind:click="onCancel"
>
@ -86,7 +86,7 @@
size="large"
class="van-dialog__button"
loading="{{ loading.confirm }}"
custom-class="van-dialog__confirm"
custom-class="van-dialog__confirm confirm-button-class"
custom-style="color: {{ confirmButtonColor }}"
open-type="{{ confirmButtonOpenType }}"

View File

@ -81,6 +81,7 @@ Component({
license_cert_reason: "",
qualification_cert_reason: "",
work_cert_reason: "",
email_reason:'',
department_custom_name_reason: '',
zhuanchang_note: '请选择专长',
jianjie_show: false,
@ -923,6 +924,10 @@ Component({
params.department_custom_name = this.data.custom_keshi;//自定义科室名称
if(!this.data.office_phone){wx.showToast({title: '请输入科室电话',icon: "error"});return}
params.department_custom_mobile = this.data.office_phone;//科室电话
if(!(/^\w+([-+.]\w+)*@\w+([-.]\w+)*\.\w+([-.]\w+)*$/.test(this.data.email))){
wx.showToast({title: '请输入正确格式的邮箱',icon: "error"});return
}
params.email=this.data.email;
params.doctor_expertise = this.data.zhuanchang_columns.filter(e => e.is_selected == 1 ).map(e => e.expertise_id);//专长
if(params.doctor_expertise.length == 0){wx.showToast({title: '请选择专长',icon: "error"});return}
if(!this.data.select_zhicheng_id){wx.showToast({title: '请选择职称',icon: "error"});return}
@ -942,6 +947,7 @@ Component({
if(this.data.zhicheng_file_list.length == 0){wx.showToast({title: '请上传职称证',icon: "error"});return}
params.work_cert = this.data.zhicheng_file_list.map(e => e.url);//工作证,职称证
}
params.source=1;
console.log(params);
let _this = this;
@ -963,6 +969,12 @@ Component({
department_custom_mobile_reason: ""
})
},
onEmailChange(){
this.setData({
email_reason: ""
})
},
doUploadFile(event) {
console.log("index douploadFIle: ", event);
const scene = event.currentTarget.dataset.scene;

View File

@ -1,11 +1,11 @@
<view class="{{avatar_reason!=''?'photo':'photo_reason'}}">
<view class="title">头像 <text class="required"> *</text></view>
<van-uploader disabled="{{ indentity && iden_auth_disabled }}" bind:after-read="doUploadPhoto" data-scene="1" data-field_name="avatar" data-file_multiple="false">
<van-uploader disabled="{{ (indentity && iden_auth_disabled ) || (!indentity && introduction_status==2)}}" bind:after-read="doUploadPhoto" data-scene="1" data-field_name="avatar" data-file_multiple="false">
<view class="right">
<view class="note">真人正脸</view>
<t-avatar image="{{ avatar }}" />
<view class="icon" wx:if="{{ !(indentity && iden_auth_disabled) }}">
<view class="icon" wx:if="{{ !(indentity && iden_auth_disabled) || !(!indentity && introduction_status==2)}}">
<t-icon name="chevron-right" size="48rpx" data-name="chevron-right" />
</view>
</view>
@ -21,7 +21,7 @@
<!-- <t-cell title="头像" hover required arrow note="真人正脸" /> -->
<!-- {{!indentity && introduction_status==3}} -->
<t-cell title="城市" hover required arrow="{{ !((indentity && iden_auth_disabled) || (!indentity && introduction_status==3)) }}" note="{{ city_note }}" bindtap="{{((indentity && iden_auth_disabled) || (!indentity && introduction_status==3))?'':'onCityShow'}}"/>
<t-cell title="城市" hover required arrow="{{ !((indentity && iden_auth_disabled) || (!indentity && introduction_status!=2)) }}" note="{{ city_note }}" bindtap="{{((indentity && iden_auth_disabled) || (!indentity && introduction_status!=2))?'':'onCityShow'}}"/>
<t-cascader
visible="{{ city_show }}"
@ -34,7 +34,7 @@
bind:close="onCloseCity"
></t-cascader>
<t-cell title="医院" hover required arrow="{{ !((indentity && iden_auth_disabled) || (!indentity && introduction_status==3)) }}" note="{{ yiyuan_note }}" bindtap="{{((indentity && iden_auth_disabled) || (!indentity && introduction_status==3))?'':'onYiYuanShow'}}"/>
<t-cell title="医院" hover required arrow="{{ !((indentity && iden_auth_disabled) || (!indentity && introduction_status!=2)) }}" note="{{ yiyuan_note }}" bindtap="{{((indentity && iden_auth_disabled) || (!indentity && introduction_status!=2))?'':'onYiYuanShow'}}"/>
<van-popup
show="{{ yiyuan_show }}"
position="bottom"
@ -64,8 +64,8 @@
<van-empty description="暂无医院" wx:if="{{yiyuan_columns.length == 0}}"/>
</van-popup>
<t-cell title="科室" hover required arrow="{{ !((indentity && iden_auth_disabled) || (!indentity && introduction_status==3)) }}" note="{{keshi_note}}"
bindtap="{{((indentity && iden_auth_disabled) || (!indentity && introduction_status==3))?'':'onKeshiShow'}}" bordered="{{ select_keshi_id == '' }}"/>
<t-cell title="科室" hover required arrow="{{ !((indentity && iden_auth_disabled) || (!indentity && introduction_status!=2)) }}" note="{{keshi_note}}"
bindtap="{{((indentity && iden_auth_disabled) || (!indentity && introduction_status!=2))?'':'onKeshiShow'}}" bordered="{{ select_keshi_id == '' }}"/>
<view class="custom_keshi" wx:if="{{ select_keshi_id != ''}}">
<van-field bind:change="onCustomKeshiChange"
@ -220,8 +220,8 @@
</view>
</t-popup>
<t-cell title="职称" hover required arrow="{{ !((indentity && iden_auth_disabled) || (!indentity && introduction_status==3)) }}" note="{{zhicheng_note}}"
bindtap="{{((indentity && iden_auth_disabled) || (!indentity && introduction_status==3))?'':'onZhiChengShow'}}" />
<t-cell title="职称" hover required arrow="{{ !((indentity && iden_auth_disabled) || (!indentity && introduction_status!=2)) }}" note="{{zhicheng_note}}"
bindtap="{{((indentity && iden_auth_disabled) || (!indentity && introduction_status!=2))?'':'onZhiChengShow'}}" />
<!-- <t-input
label="科室电话"
placeholder="请输入科室电话"
@ -239,7 +239,7 @@
input-align="right"
type="number"
clearable
disabled="{{(indentity && iden_auth_disabled) || (!indentity && introduction_status==3) }}"
disabled="{{(indentity && iden_auth_disabled) || (!indentity && introduction_status!=2) }}"
border="{{ department_custom_mobile_reason == '' }}"
custom-style="font-size:30rpx;"
>
@ -252,7 +252,27 @@
<text class="error_msg">{{ department_custom_mobile_reason }}</text>
</view>
</view>
<van-field
model:value="{{ email }}"
bind:change="onEmailChange"
placeholder="请输入个人邮箱"
placeholder-style="font-size:30rpx;letter-spacing: 2rpx;color:rgba(0, 0, 0, 0.4);"
input-align="right"
type="text"
clearable
wx:if="{{indentity}}"
disabled="{{(indentity && iden_auth_disabled)}}"
border="{{ email_reason == '' }}"
custom-style="font-size:30rpx;"
>
<text slot="label">个人邮箱 <text style="color: #e34d59;">*</text> </text>
</van-field>
<view class="has_error" wx:if="{{email_reason != '' }}" >
<view class="error_box">
<t-icon color="red" name="close-circle-filled" size="48rpx" data-name="close-circle-filled" />
<text class="error_msg">{{ email_reason }}</text>
</view>
</view>
<view class="zhuanchang" bindtap="{{(indentity && iden_auth_disabled)?'':'onZhuanChangShow'}}">
<view class="title">专长 <text class="required"> *</text></view>
<view class="content">
@ -362,7 +382,7 @@
<van-button data-from="doctorauthiden" disabled="{{iden_auth_disabled}}" color="#3CC7C0" custom-style="border-radius: 20rpx;" type="primary" block bind:click="addDoctorAuthIden">{{iden_auth_status_txt}}</van-button>
</view>
<view class="sub_button" wx:else>
<van-button color="#3CC7C0" data-from="myinfo" disabled="{{!(introduction_status==0 || introduction_status==3)}}" custom-style="border-radius: 20rpx;" type="primary" block bind:click="{{introduction_status==0?'addDoctorAuthIden':'handleUpdateIntroduction'}}">{{iden_auth_status_txt}}</van-button>
<van-button color="#3CC7C0" data-from="myinfo" disabled="{{introduction_status==2}}" custom-style="border-radius: 20rpx;" type="primary" block bind:click="{{introduction_status==0?'addDoctorAuthIden':'handleUpdateIntroduction'}}">{{iden_auth_status_txt}}</van-button>
</view>

View File

@ -1,7 +1,7 @@
/// <reference types="miniprogram-api-typings" />
/// <reference types="miniprogram-api-typings" />
export declare type Action = 'confirm' | 'cancel' | 'overlay';
declare type DialogContext = WechatMiniprogram.Page.TrivialInstance | WechatMiniprogram.Component.TrivialInstance;
export type Action = 'confirm' | 'cancel' | 'overlay';
type DialogContext = WechatMiniprogram.Page.TrivialInstance | WechatMiniprogram.Component.TrivialInstance;
interface DialogOptions {
lang?: string;
show?: boolean;

View File

@ -6,6 +6,7 @@ var color_1 = require("../common/color");
var utils_1 = require("../common/utils");
(0, component_1.VantComponent)({
mixins: [button_1.button],
classes: ['cancle-button-class', 'confirm-button-class'],
props: {
show: {
type: Boolean,

View File

@ -33,7 +33,7 @@
size="large"
loading="{{ loading.cancel }}"
class="van-dialog__button van-hairline--right"
custom-class="van-dialog__cancel"
custom-class="van-dialog__cancel cancle-button-class"
custom-style="color: {{ cancelButtonColor }}"
bind:click="onCancel"
>
@ -44,7 +44,7 @@
size="large"
class="van-dialog__button"
loading="{{ loading.confirm }}"
custom-class="van-dialog__confirm"
custom-class="van-dialog__confirm confirm-button-class"
custom-style="color: {{ confirmButtonColor }}"
open-type="{{ confirmButtonOpenType }}"
@ -75,7 +75,7 @@
size="large"
loading="{{ loading.cancel }}"
class="van-dialog__button van-hairline--right"
custom-class="van-dialog__cancel"
custom-class="van-dialog__cancel cancle-button-class"
custom-style="color: {{ cancelButtonColor }}"
bind:click="onCancel"
>
@ -86,7 +86,7 @@
size="large"
class="van-dialog__button"
loading="{{ loading.confirm }}"
custom-class="van-dialog__confirm"
custom-class="van-dialog__confirm confirm-button-class"
custom-style="color: {{ confirmButtonColor }}"
open-type="{{ confirmButtonOpenType }}"

View File

@ -154,7 +154,7 @@ Page({
url: next_url
})
}
app.imInit({path:'Pages/login/index'},1,handleGo);
app.imInit({path:'/user/pages/login/index'},1,handleGo);
//跳转页面
}).catch(errors => {

View File

@ -12,13 +12,27 @@
<view class="item_list">
<view class="item" wx:for="{{appraise_list_1}}">
<view class="top">
<view class="name">{{item.name_mask}}</view>
<view class="namebox">
<view class="name">{{item.name_mask}}</view>
<view class="comemntType" wx:if="item.inquiry_mode==1">
图文
</view>
<view class="comemntType video" wx:elif="item.inquiry_mode==2 && item.inquiry_type==1">
视频
</view>
<view class="comemntType yinan" wx:elif="item.inquiry_mode==6 && item.inquiry_type==1">
疑难会诊
</view>
</view>
<view class="start">
<t-rate value="{{item.avg_score}}" count="5" color="#ED9C00"/>
</view>
</view>
<view class="content">{{item.content}}</view>
<view class="date">{{dateSubstr.substring(item.created_at,0,16)}}</view>
<view class="datebox">
<view class="sick_name">{{item.disease_class_name}}</view>
<view class="date">{{dateSubstr.substring(item.created_at,0,16)}}</view>
</view>
</view>
</view>
<van-empty description="暂无数据" wx:if="{{appraise_list_1.length == 0}}" />
@ -28,13 +42,27 @@
<view class="item_list">
<view class="item" wx:for="{{appraise_list_2}}">
<view class="top">
<view class="name">{{item.name_mask}}</view>
<view class="namebox">
<view class="name">{{item.name_mask}}</view>
<view class="comemntType" wx:if="item.inquiry_mode==1">
图文
</view>
<view class="comemntType video" wx:elif="item.inquiry_mode==2 && item.inquiry_type==1">
视频
</view>
<view class="comemntType yinan" wx:elif="item.inquiry_mode==6 && item.inquiry_type==1">
疑难会诊
</view>
</view>
<view class="start">
<t-rate value="{{item.avg_score}}" count="5" color="#ED9C00"/>
</view>
</view>
<view class="content">{{item.content}}</view>
<view class="date">{{dateSubstr.substring(item.created_at,0,16)}}</view>
<view class="datebox">
<view class="sick_name">{{item.disease_class_name}}</view>
<view class="date">{{dateSubstr.substring(item.created_at,0,16)}}</view>
</view>
</view>
</view>
<van-empty description="暂无数据" wx:if="{{appraise_list_2.length == 0}}" />
@ -44,13 +72,27 @@
<view class="item_list">
<view class="item" wx:for="{{appraise_list_3}}">
<view class="top">
<view class="name">{{item.name_mask}}</view>
<view class="namebox">
<view class="name">{{item.name_mask}}</view>
<view class="comemntType" wx:if="item.inquiry_mode==1">
图文
</view>
<view class="comemntType video" wx:elif="item.inquiry_mode==2 && item.inquiry_type==1">
视频
</view>
<view class="comemntType yinan" wx:elif="item.inquiry_mode==6 && item.inquiry_type==1">
疑难会诊
</view>
</view>
<view class="start">
<t-rate value="{{item.avg_score}}" count="5" color="#ED9C00"/>
</view>
</view>
<view class="content">{{item.content}}</view>
<view class="date">{{dateSubstr.substring(item.created_at,0,16)}}</view>
<view class="datebox">
<view class="sick_name">{{item.disease_class_name}}</view>
<view class="date">{{dateSubstr.substring(item.created_at,0,16)}}</view>
</view>
</view>
</view>
<van-empty description="暂无数据" wx:if="{{appraise_list_3.length == 0}}" />

Some files were not shown because too many files have changed in this diff Show More