Merge branch 'wfs-0829' into pro

This commit is contained in:
weifashi 2023-08-30 18:23:34 +08:00
commit d1e38910ef
9 changed files with 233 additions and 25 deletions

View File

@ -613,7 +613,10 @@ class UsersController extends AbstractController
*/
public function basic()
{
User::auth();
$sharekey = Request::header('Sharekey');
if(empty($sharekey) || !Meeting::getShareInfo($sharekey)){
User::auth();
}
//
$userid = Request::input('userid');
$array = Base::json2array($userid);
@ -1120,6 +1123,8 @@ class UsersController extends AbstractController
* - join: 加入会议有效参数meetingid (必填)
* @apiParam {String} [meetingid] 频道ID不是数字
* @apiParam {String} [name] 会话ID
* @apiParam {String} [sharekey] 分享的key
* @apiParam {String} [username] 用户名称
* @apiParam {Array} [userids] 邀请成员
*
* @apiSuccess {Number} ret 返回状态码1正确、0错误
@ -1128,12 +1133,20 @@ class UsersController extends AbstractController
*/
public function meeting__open()
{
$user = User::auth();
//
$type = trim(Request::input('type'));
$meetingid = trim(Request::input('meetingid'));
$name = trim(Request::input('name'));
$userids = Request::input('userids');
$sharekey = trim(Request::input('sharekey'));
$username = trim(Request::input('username'));
$user = null;
if(!empty($sharekey) && $type === 'join'){
if(!Meeting::getShareInfo($sharekey)){
return Base::retError('分享链接已过期');
}
}else{
$user = User::auth();
}
$isCreate = false;
// 创建、加入
if ($type === 'join') {
@ -1143,13 +1156,13 @@ class UsersController extends AbstractController
}
} elseif ($type === 'create') {
$meetingid = strtoupper(Base::generatePassword(11, 1));
$name = $name ?: "{$user->nickname} 发起的会议";
$name = $name ?: "{$user?->nickname} 发起的会议";
$channel = "DooTask:" . substr(md5($meetingid . env("APP_KEY")), 16);
$meeting = Meeting::createInstance([
'meetingid' => $meetingid,
'name' => $name,
'channel' => $channel,
'userid' => $user->userid
'userid' => $user?->userid
]);
$meeting->save();
$isCreate = true;
@ -1165,7 +1178,10 @@ class UsersController extends AbstractController
if (empty($meetingSetting['appid']) || empty($meetingSetting['app_certificate'])) {
return Base::retError('会议功能配置错误,请联系管理员');
}
$uid = intval(str_pad( Request::header('fd'), 6, 9, STR_PAD_LEFT) . $user->userid);
$uid = intval(str_pad(Base::generatePassword(4,1), 9, 8, STR_PAD_LEFT));
if($user){
$uid = intval(str_pad(Request::header('fd'), 5, 9, STR_PAD_LEFT).$user->userid);
}
try {
$service = new AgoraTokenGenerator($meetingSetting['appid'], $meetingSetting['app_certificate'], $meeting->channel, $uid);
} catch (\Exception $e) {
@ -1194,13 +1210,40 @@ class UsersController extends AbstractController
//
$data['appid'] = $meetingSetting['appid'];
$data['uid'] = $uid;
$data['userimg'] = $user->userimg;
$data['nickname'] = $user->nickname;
$data['userimg'] = $sharekey ? Base::fillUrl('avatar/'.$username.'.png') : $user?->userimg;
$data['nickname'] = $sharekey ? $username : $user?->nickname;
$data['token'] = $token;
$data['msgs'] = $msgs;
$data['sharelink'] = $meeting->getShareLink();
//
Meeting::setTouristInfo($data);
//
return Base::retSuccess('success', $data);
}
/**
* @api {get} api/users/meeting/tourist 16. 【会议】游客信息
*
* @apiDescription 需要token身份
* @apiVersion 1.0.0
* @apiGroup users
* @apiName meeting__tourist
*
* @apiParam {String} tourist_id 游客ID
*
* @apiSuccess {Number} ret 返回状态码1正确、0错误
* @apiSuccess {String} msg 返回信息(错误描述)
* @apiSuccess {Object} data 返回数据
*/
public function meeting__tourist()
{
$touristId = trim(Request::input('tourist_id'));
if ($touristInfo = Meeting::getTouristInfo($touristId)) {
return Base::retSuccess('success', $touristInfo);
}
return Base::retError('Id不存在');
}
/**
* @api {get} api/users/meeting/invitation 17. 【会议】发送邀请
*

View File

@ -2,6 +2,10 @@
namespace App\Models;
use Cache;
use App\Module\Base;
use Illuminate\Support\Carbon;
/**
* App\Models\Meeting
*
@ -30,5 +34,58 @@ namespace App\Models;
*/
class Meeting extends AbstractModel
{
const CACHE_KEY = 'meeting_share_link_code';
const CACHE_EXPIRED_TIME = 6; // 小时
/**
* 获取分享链接
* @return mixed
*/
public function getShareLink()
{
$code = base64_encode("{$this->meetingid}" . Base::generatePassword());
Cache::put(self::CACHE_KEY.'_'.$code, [
'id' => $this->id,
'meetingid' => $this->meetingid,
'channel' => $this->channel,
], Carbon::now()->addHours(self::CACHE_EXPIRED_TIME));
return Base::fillUrl("meeting/{$this->meetingid}/".$code);
}
/**
* 获取分享信息
* @return mixed
*/
public static function getShareInfo($code)
{
if(Cache::has(self::CACHE_KEY.'_'.$code)){
return Cache::get(self::CACHE_KEY.'_'.$code);
}
return null;
}
/**
* 保存访客信息
* @return mixed
*/
public static function setTouristInfo($data)
{
Cache::put(Meeting::CACHE_KEY.'_'.$data['uid'], [
'uid' => $data['uid'],
'userimg' => $data['userimg'],
'nickname' => $data['nickname'],
], Carbon::now()->addHours(self::CACHE_EXPIRED_TIME));
}
/**
* 获取访客信息
* @return mixed
*/
public static function getTouristInfo($touristId)
{
if(Cache::has(Meeting::CACHE_KEY.'_'.$touristId)){
return Base::retSuccess('success', Cache::get(Meeting::CACHE_KEY.'_'.$touristId) );
}
return null;
}
}

View File

@ -337,7 +337,7 @@ export default {
this.$store.dispatch("call", {
url: 'users/basic',
data: {
userid: [ (uuid+"").substring(6) ]
userid: [ (uuid+"").substring(5) ]
}
}).then(({data}) => {
$A.eeuiAppSendMessage({

View File

@ -4,13 +4,17 @@
<Modal
v-model="addShow"
:title="$L(addData.type === 'join' ? '加入会议' : '新会议')"
:mask-closable="false">
:mask-closable="false"
:closable="!addData.sharekey">
<Form ref="addForm" :model="addData" label-width="auto" @submit.native.prevent>
<template v-if="addData.type === 'join'">
<!-- 加入会议 -->
<FormItem v-if="addData.name" prop="userids" :label="$L('会议主题')">
<Input v-model="addData.name" disabled/>
</FormItem>
<FormItem v-if="addData.sharekey" prop="username" :label="$L('你的姓名')">
<Input v-model="addData.username" :placeholder="$L('请输入你的姓名')"/>
</FormItem>
<FormItem prop="meetingid" :label="$L('会议频道ID')">
<Input v-model="addData.meetingid" :disabled="addData.meetingdisabled === true" :placeholder="$L('请输入会议频道ID')"/>
</FormItem>
@ -36,7 +40,7 @@
</FormItem>
</Form>
<div slot="footer" class="adaption">
<Button type="default" @click="addShow=false">{{$L('取消')}}</Button>
<Button type="default" @click="addShow=false" v-if="!addData.sharekey">{{$L('取消')}}</Button>
<Button type="primary" :loading="loadIng > 0" @click="onSubmit">{{$L(addData.type === 'join' ? '加入会议' : '开始会议')}}</Button>
</div>
</Modal>
@ -56,7 +60,8 @@
<ul>
<li v-if="localUser.uid">
<MeetingPlayer :player="localUser" isLocal/>
</li><li v-for="user in remoteUsers">
</li>
<li v-for="user in remoteUsers">
<MeetingPlayer :player="user"/>
</li>
</ul>
@ -68,10 +73,10 @@
<i class="taskfont" v-html="localUser.videoTrack ? '&#xe7c1;' : '&#xe7c8;'"></i>
</Button>
<template v-if="windowPortrait">
<Button type="primary" @click="onInvitation('open')">
<Button type="primary" @click="onInvitation('open')">
<i class="taskfont">&#xe646;</i>
</Button>
<Button type="primary" @click="meetingMini = true">
<Button type="primary" v-if="!addData.sharekey" @click="meetingMini = true">
<i class="taskfont">&#xe656;</i>
</Button>
<Button type="warning" :loading="loadIng > 0" @click="onClose">
@ -80,7 +85,7 @@
</template>
<template v-else>
<Button type="primary" @click="onInvitation('open')">{{$L('邀请')}}</Button>
<Button type="primary" @click="meetingMini = true">{{$L('最小化')}}</Button>
<Button type="primary" v-if="!addData.sharekey" @click="meetingMini = true">{{$L('最小化')}}</Button>
<Button type="warning" :loading="loadIng > 0" @click="onClose">{{$L('离开会议')}}</Button>
</template>
</div>
@ -108,7 +113,7 @@
</FormItem>
</Form>
<div slot="footer" class="adaption">
<Button type="default" @click="invitationShow=false">{{$L('取消')}}</Button>
<Button type="default" @click="linkCopy">{{$L('复制链接')}}</Button>
<Button type="primary" :loading="invitationLoad" @click="onInvitation('submit')">{{$L('发送邀请')}}</Button>
</div>
</Modal>
@ -118,13 +123,21 @@
<script>
import {Store} from "le5le-store";
import {mapState} from 'vuex'
import MeetingPlayer from "./MeetingPlayer";
import MeetingPlayer from "./MeetingPlayer.vue";
import DragBallComponent from "../../../components/DragBallComponent";
import UserSelect from "../../../components/UserSelect.vue";
export default {
name: "MeetingManager",
components: {UserSelect, DragBallComponent, MeetingPlayer},
props: {
id: {
type: String,
default: () => {
return "meeting-player-" + Math.round(Math.random() * 10000);
}
}
},
data() {
return {
loadIng: 0,
@ -185,6 +198,16 @@ export default {
this.addShow = val.show;
this.loadIng = 0;
break;
case 'join':
this.addShow = val.show;
this.loadIng = 0;
this.addData.type = 'join';
if(val.meetingSharekey){
this.addData.sharekey = val.meetingSharekey;
this.addData.meetingid = val.meetingid || '';
this.addData.meetingdisabled = val.meetingSharekey ? true : false;
}
break;
case 'invitation':
this.invitationShow = val.show;
this.invitationLoad = false;
@ -251,6 +274,9 @@ export default {
}).then(({data}) => {
this.$set(this.addData, 'name', data.name);
this.$set(this.addData, 'meetingid', data.meetingid);
this.$set(this.addData, 'sharelink', data.sharelink);
this.$set(this.localUser, 'nickname', data.nickname);
this.$set(this.localUser, 'userimg', data.userimg);
this.$store.dispatch("saveDialogMsg", data.msgs);
this.$store.dispatch("updateDialogLastMsg", data.msgs);
delete data.name;
@ -270,6 +296,7 @@ export default {
video: this.addData.tracks.includes("video"),
audio: this.addData.tracks.includes("audio"),
meetingid: data.meetingid,
sharelink: data.sharelink,
alert: {
title: this.$L('温馨提示'),
message: this.$L('确定要离开会议吗?'),
@ -314,6 +341,10 @@ export default {
onInvitation(type) {
if (type === 'open') {
if(this.addData.sharekey){
this.linkCopy();
return;
}
this.invitationData = {
userids: [],
meetingid: this.addData.meetingid
@ -345,6 +376,10 @@ export default {
okText: '退出',
onOk: async _ => {
await this.leave()
if(this.addData.sharekey){
this.addShow = true;
this.loadIng = 0;
}
resolve()
}
});
@ -496,7 +531,16 @@ export default {
if (item) {
await this.agoraClient.unsubscribe(user, mediaType);
}
}
},
linkCopy() {
this.$copyText(this.addData.sharelink).then(_ => {
$A.messageSuccess('已复制会议邀请链接');
}).catch(_ => {
$A.messageError('复制失败');
});
this.invitationShow = false;
},
}
}
</script>

View File

@ -1,7 +1,13 @@
<template>
<div v-if="userid" class="meeting-player">
<div class="meeting-player">
<div :id="id" class="player" :style="playerStyle"></div>
<UserAvatar :userid="userid" :size="36" :borderWitdh="2"/>
<UserAvatar v-if="userid" :userid="userid" :size="36" :borderWitdh="2"/>
<div v-else-if="tourist.userimg" class="common-avatar avatar-wrapper">
<div class="avatar-box online">
<em style="transform: scale(1.0625);"></em>
<EAvatar :size="36" style="border: 2px solid #FFFFFF;" :src="tourist.userimg"></EAvatar>
</div>
</div>
<div class="player-state">
<i v-if="!audio" class="taskfont">&#xe7c7;</i>
<i v-if="!video" class="taskfont">&#xe7c8;</i>
@ -32,7 +38,12 @@ export default {
},
data() {
return {
timer: null
timer: null,
tourist: {
uid: '',
nickname: '',
userimg: '',
}
}
},
mounted() {
@ -52,7 +63,11 @@ export default {
...mapState(['cacheUserBasic']),
userid() {
if (this.player.uid) {
return parseInt( (this.player.uid+"").substring(6) )
if( (this.player.uid + '').indexOf('88888') !== -1 ){
this.getTouristInfo();
return 0;
}
return parseInt( (this.player.uid+"").substring(5) ) || 0
}
return 0
},
@ -62,6 +77,10 @@ export default {
return {
backgroundImage: `url("${user.userimg}")`
}
}else if(this.tourist.userimg){
return {
backgroundImage: `url("${this.tourist.userimg}")`
}
}
return null;
},
@ -99,6 +118,18 @@ export default {
console.log("Meeting Player Error", e);
}
})
},
getTouristInfo() {
this.$store.dispatch("call", {
url: 'users/meeting/tourist',
data: {
tourist_id: this.player.uid
}
}).then(({data}) => {
this.tourist = data.data;
}).catch(({msg}) => {
$A.modalError(msg);
});
}
}
}

View File

@ -0,0 +1,23 @@
<template>
<div>
<MeetingManager/>
</div>
</template>
<script>
import MeetingManager from "./manage/components/MeetingManager.vue";
export default {
components: {
MeetingManager,
},
mounted() {
this.$store.dispatch("showMeetingWindow",{
type: "join",
meetingid: this.$route.params.meetingId,
meetingSharekey: this.$route.params.sharekey,
meetingdisabled: true,
})
},
}
</script>

View File

@ -9,6 +9,11 @@ export default [
path: '/pro',
component: () => import('./pages/pro.vue'),
},
{
name: 'meeting',
path: '/meeting/:meetingId?/:sharekey?',
component: () => import('./pages/meeting.vue'),
},
{
name: 'manage',
path: '/manage',

View File

@ -112,6 +112,9 @@ export default {
'version': window.systemInfo.version || "0.0.1",
'platform': $A.Platform,
}
if(!state.userToken && state.meetingWindow?.meetingSharekey){
header.sharekey = state.meetingWindow.meetingSharekey;
}
if ($A.isJson(params.header)) {
params.header = Object.assign(header, params.header)
} else {
@ -3608,11 +3611,13 @@ export default {
* @param type
* @param meetingid
*/
showMeetingWindow({state}, {type, meetingid}) {
showMeetingWindow({state}, {type, meetingid, meetingdisabled, meetingSharekey}) {
state.meetingWindow = {
show: true,
type: type,
meetingid: meetingid
meetingid: meetingid,
meetingdisabled: meetingdisabled,
meetingSharekey: meetingSharekey
};
},

View File

@ -199,7 +199,7 @@ export default {
// 审批待办未读数量
approveUnreadNumber: 0,
// 会议窗口
// 会议
meetingWindow: {
show: false,
type: "",