145 Commits
zj ... zhy

Author SHA1 Message Date
52058d5ebc 是否有直播资格 2020-11-19 15:26:16 +08:00
6f5f407565 Merge pull request '是否有直播资格' (#137) from zhy into master
Reviewed-on: http://git.luyuan.tk/luyuan/beelink/pulls/137
2020-11-19 14:24:30 +08:00
564a7fab9b 是否有直播资格 2020-11-19 14:22:59 +08:00
6913f79e3b Merge pull request '货币提示' (#136) from xbx into master
Reviewed-on: http://git.luyuan.tk/luyuan/beelink/pulls/136
2020-11-18 15:27:38 +08:00
bd0d1912d2 货币提示 2020-11-18 15:26:55 +08:00
b58e92381c Merge pull request 'xbx' (#135) from xbx into master
Reviewed-on: http://git.luyuan.tk/luyuan/beelink/pulls/135
2020-11-18 15:22:31 +08:00
8a33d29b54 转码中 2020-11-18 15:21:51 +08:00
cfc2b60f71 钱包修改 2020-11-18 15:18:40 +08:00
1e4efc75e3 Merge pull request 'xbx' (#134) from xbx into master
Reviewed-on: http://git.luyuan.tk/luyuan/beelink/pulls/134
2020-11-18 14:56:14 +08:00
b339604347 修改货币之后禁止修改 2020-11-18 14:54:52 +08:00
d88448060f 货币弹窗 2020-11-18 14:48:58 +08:00
cc18504959 直播禁止点击 2020-11-18 14:41:52 +08:00
5c7ff10cc8 确认退出 删除top货币 2020-11-18 14:34:28 +08:00
5e2db3ad91 头像和保存按钮 2020-11-18 14:28:21 +08:00
224bacbb2a 提现金额 2020-11-18 14:24:20 +08:00
da5ad1202c 添加了? 2020-11-18 14:20:33 +08:00
60d7a9905e 修改了登录文案 2020-11-18 14:05:25 +08:00
90ce114a7f 吸顶 2020-11-18 11:02:27 +08:00
da8c001b44 时区渲染 2020-11-18 10:51:03 +08:00
849b04a1ee 当天高亮 2020-11-18 10:23:34 +08:00
f2b63a2fcc 当天高亮 2020-11-18 10:23:22 +08:00
49edb01a3f 获取时区 2020-11-18 10:10:48 +08:00
97a5090e0f 根据默认获取区号 2020-11-18 09:55:25 +08:00
c73f45d27e 区号自动获取 2020-11-18 09:40:56 +08:00
0d6a2329d4 修改了固定 2020-11-17 17:38:47 +08:00
9f75d4a2f8 添加了title 2020-11-17 16:50:28 +08:00
bff5eef8fa 修改成计算属性 2020-11-17 16:41:52 +08:00
64ee8b8a4f 修改了字体颜色 2020-11-17 16:39:24 +08:00
5b4a1efb39 修改了验证 2020-11-17 16:27:40 +08:00
5b1f165528 进度初始就出现 2020-11-17 16:04:36 +08:00
9dad777061 添加图片大小 2020-11-17 15:09:10 +08:00
6e19aa583d 添加文件大小限制 2020-11-17 15:07:54 +08:00
8371a7188f 端视频介绍 2020-11-17 14:55:36 +08:00
a340c38e79 开启视频验证 2020-11-17 10:42:17 +08:00
168daa14f9 帐号已存在 2020-11-17 09:59:30 +08:00
fc9a05636c 修复了语言的问题 2020-11-17 09:49:50 +08:00
cb814e4fe2 修正了直播的样式 2020-11-17 09:36:24 +08:00
1242882785 使用后台的进行验证 2020-11-16 11:44:46 +08:00
edcee2f592 未上传完成文件不让提交 2020-11-16 11:20:17 +08:00
1bf49e37b4 修正了弹出层的数据问题 2020-11-16 11:09:29 +08:00
c493582885 调整了--的位置 2020-11-16 11:02:33 +08:00
15903d525e 引出了外部可以使用的加载中 2020-11-16 10:59:07 +08:00
bb6012b01d 改成s 2020-11-13 17:36:33 +08:00
5fdd0c3fc6 Merge pull request 'xbx' (#133) from xbx into master
Reviewed-on: http://git.luyuan.tk/luyuan/beelink/pulls/133
2020-11-13 17:22:37 +08:00
d1a3115ed4 上传videoid 2020-11-13 17:21:16 +08:00
25c2f49576 更换了艰巨 2020-11-13 17:09:04 +08:00
a104d4bcd7 更换了提示 2020-11-13 17:07:11 +08:00
0359178f17 修正了部分直播样式 2020-11-13 14:45:50 +08:00
aae6ac3937 删除了登录卡片 2020-11-13 10:22:06 +08:00
6bafbdef55 Merge pull request 'xbx' (#132) from xbx into master
Reviewed-on: http://git.luyuan.tk/luyuan/beelink/pulls/132
2020-11-12 18:07:49 +08:00
8b6342eed2 删除恶劣验证 2020-11-12 18:04:07 +08:00
5d893d26b5 优化了登录 2020-11-12 18:03:30 +08:00
3b2c47e0ec 完成调用 2020-11-12 17:43:45 +08:00
bc46dde11c init live 2020-11-12 17:27:59 +08:00
fe80512d46 Merge pull request '修改密码' (#131) from xbx into master
Reviewed-on: http://git.luyuan.tk/luyuan/beelink/pulls/131
2020-11-12 17:14:08 +08:00
0d29e223e6 修改密码 2020-11-12 17:13:38 +08:00
612f5c81f1 Merge pull request 'xbx' (#130) from xbx into master
Reviewed-on: http://git.luyuan.tk/luyuan/beelink/pulls/130
2020-11-12 16:18:04 +08:00
d51732f348 取消 2020-11-12 16:17:10 +08:00
9702477c23 删除完成 2020-11-12 16:16:56 +08:00
b694dc1a1c Merge pull request '修复了某些错误' (#129) from xbx into master
Reviewed-on: http://git.luyuan.tk/luyuan/beelink/pulls/129
2020-11-12 15:56:07 +08:00
06ecc8e678 修复了某些错误 2020-11-12 15:55:41 +08:00
146b59fcbc Merge pull request '修复' (#128) from xbx into master
Reviewed-on: http://git.luyuan.tk/luyuan/beelink/pulls/128
2020-11-12 15:32:53 +08:00
ad9707ddbe 修复 2020-11-12 15:32:33 +08:00
975c4f2ab6 Merge pull request '回复ok' (#127) from xbx into master
Reviewed-on: http://git.luyuan.tk/luyuan/beelink/pulls/127
2020-11-12 15:17:55 +08:00
5a7f0527b0 回复ok 2020-11-12 15:17:20 +08:00
c43c74e967 Merge pull request 'xbx' (#126) from xbx into master
Reviewed-on: http://git.luyuan.tk/luyuan/beelink/pulls/126
2020-11-12 10:40:08 +08:00
dcf70059fa 修正了绑定手机的code 2020-11-12 10:38:56 +08:00
aaf8655db8 修改字段 2020-11-12 10:33:35 +08:00
fb173c548d 添加了删除提示 2020-11-12 10:30:27 +08:00
77664c98e3 回复完成 2020-11-12 10:26:21 +08:00
47e05ec6ff 修改了min 2020-11-12 09:54:43 +08:00
4b83d61df7 上传文件 2020-11-12 09:53:30 +08:00
84d3e45292 替换了格式 2020-11-12 09:26:51 +08:00
1f2cae25fd 修改了换行 2020-11-12 09:22:18 +08:00
d9e0899264 头像自动更新 2020-11-12 09:19:44 +08:00
1971fb8370 Merge pull request '修改密码' (#125) from xbx into master
Reviewed-on: http://git.luyuan.tk/luyuan/beelink/pulls/125
2020-11-11 17:37:55 +08:00
26b815e046 修改密码 2020-11-11 17:37:03 +08:00
d96bce28b1 Merge pull request 'xbx' (#124) from xbx into master
Reviewed-on: http://git.luyuan.tk/luyuan/beelink/pulls/124
2020-11-11 10:52:42 +08:00
da50f3a8dc 编译删除console 2020-11-11 10:50:04 +08:00
00a4dee4ec 注册手机号 2020-11-11 10:28:47 +08:00
6d410c2a03 更换了个人中心修改手机号的验证码获取 2020-11-11 10:17:51 +08:00
1cca2b754c 直播的评论和视频的原因 2020-11-11 09:58:10 +08:00
9f03f3b3cc 修复了原因 2020-11-11 09:43:03 +08:00
162e566a32 Merge pull request '视频要求' (#123) from xbx into master
Reviewed-on: http://git.luyuan.tk/luyuan/beelink/pulls/123
2020-11-10 18:11:23 +08:00
5590866254 视频要求 2020-11-10 18:10:42 +08:00
11d6ff4be4 Merge pull request 'xbx' (#122) from xbx into master
Reviewed-on: http://git.luyuan.tk/luyuan/beelink/pulls/122
2020-11-10 18:01:58 +08:00
d536156625 更换了logo 2020-11-10 17:59:36 +08:00
d6ceb62b3d 添加了选择开播 2020-11-10 17:57:49 +08:00
ff3fe09026 直播的人数 2020-11-10 17:28:48 +08:00
929d60b478 交易详情跳转 2020-11-10 15:23:03 +08:00
9e128db2c6 添加了区号和修复多语言 2020-11-10 15:15:31 +08:00
b0a6592373 修复了个人中心和直播视频item高度 2020-11-10 14:56:42 +08:00
e515b8266d 修复了周历显示010 的小时 2020-11-10 11:00:54 +08:00
e7b3f694b0 添加了注册的搜索和档案的我还会说禁止选择 2020-11-10 10:58:42 +08:00
eb799090b2 Merge pull request '修复了视频评分' (#121) from xbx into master
Reviewed-on: http://git.luyuan.tk/luyuan/beelink/pulls/121
2020-11-10 10:14:34 +08:00
82d7a69123 修复了视频评分 2020-11-10 10:13:44 +08:00
4e06d7011c Merge pull request '修复了语言' (#120) from xbx into master
Reviewed-on: http://git.luyuan.tk/luyuan/beelink/pulls/120
2020-11-10 09:55:52 +08:00
6f7322628e 修复了语言 2020-11-10 09:55:21 +08:00
10561b7a7d Merge pull request 'xbx' (#119) from xbx into master
Reviewed-on: http://git.luyuan.tk/luyuan/beelink/pulls/119
2020-11-10 09:36:49 +08:00
73eceefbf0 中英文 2020-11-10 09:33:40 +08:00
3874f6ab2e 日历跳转完成 2020-11-09 17:55:51 +08:00
fe3e929898 修复了订阅学生一直显示为空 2020-11-09 17:29:09 +08:00
c9fb33cd13 修复了video的数据状态 2020-11-09 17:25:54 +08:00
151ff96a25 删除手机号格式验证 2020-11-09 17:01:25 +08:00
9c24b82cf2 修复了无限刷新和未登录设置语言 2020-11-09 16:59:29 +08:00
3ece8985ca 添加了监听 2020-11-09 15:12:04 +08:00
b683a58354 滑动到最底部 2020-11-09 14:46:55 +08:00
7e576ce029 修改之后上传 2020-11-09 11:31:13 +08:00
244011e3e4 添加了登录后刷新页面 2020-11-09 09:21:54 +08:00
f9c7d799aa Merge pull request 'xbx' (#118) from xbx into master
Reviewed-on: http://git.luyuan.tk/luyuan/beelink/pulls/118
2020-11-06 23:45:04 +08:00
945a99d4ab 直播发送命令 2020-11-06 23:41:23 +08:00
6b8ca9b797 直播status 为当前 2020-11-06 23:10:44 +08:00
86121ed4f0 z-index 2020-11-06 23:08:08 +08:00
739c31116e 跳转个人页面 2020-11-06 23:02:32 +08:00
cf1e2ed768 直播 2020-11-06 23:00:25 +08:00
15e2acbbe0 视频回复 2020-11-06 22:55:30 +08:00
013cae84af 修复验证码 2020-11-06 22:51:42 +08:00
45180ae026 menu 2020-11-06 22:46:40 +08:00
133048364f 参与人数 2020-11-06 22:42:07 +08:00
a75e5a3c02 一部分内容 2020-11-06 22:30:17 +08:00
2a2476cb5e Merge pull request '修复了一些bug' (#117) from xbx into master
Reviewed-on: http://git.luyuan.tk/luyuan/beelink/pulls/117
2020-11-04 18:12:51 +08:00
8e146606da 修复了一些bug 2020-11-04 17:54:26 +08:00
d46e11a51a Merge pull request '修改银行卡跳转' (#116) from xbx into master
Reviewed-on: http://git.luyuan.tk/luyuan/beelink/pulls/116
2020-11-03 17:05:30 +08:00
f85fc8a926 修改银行卡跳转 2020-11-03 17:04:59 +08:00
206db77c8c Merge pull request 'xbx' (#115) from xbx into master
Reviewed-on: http://git.luyuan.tk/luyuan/beelink/pulls/115
2020-11-03 16:57:45 +08:00
2d13a4500a 注册修复下拉框 2020-11-03 16:56:30 +08:00
07d5ee35fc 修改了登录的语言 2020-11-03 16:04:19 +08:00
289938ec46 修复了退出登录不灵敏 2020-11-03 16:00:07 +08:00
52f58a846a 增加了宽度 2020-11-03 15:39:14 +08:00
dd08807e8e 默认语言 2020-11-03 15:37:06 +08:00
63d3598a2c Merge pull request 'https' (#114) from xbx into master
Reviewed-on: http://git.luyuan.tk/luyuan/beelink/pulls/114
2020-11-03 14:57:02 +08:00
817a67b1ca https 2020-11-03 14:56:34 +08:00
69d349e82c Merge pull request '删除代理' (#113) from xbx into master
Reviewed-on: http://git.luyuan.tk/luyuan/beelink/pulls/113
2020-11-03 14:37:28 +08:00
f567cac0b6 删除代理 2020-11-03 14:37:04 +08:00
5e7886fd50 Merge pull request 'xbx' (#112) from xbx into master
Reviewed-on: http://git.luyuan.tk/luyuan/beelink/pulls/112
2020-11-03 14:36:05 +08:00
56e8571d6d Merge branch 'master' of http://git.luyuan.tk/luyuan/beelink into xbx 2020-11-03 14:32:29 +08:00
asd
121be56e98 Merge pull request '提现记录 申诉' (#111) from zj into master
Reviewed-on: http://git.luyuan.tk/luyuan/beelink/pulls/111
2020-11-03 14:30:57 +08:00
f12be561c3 周日历跳转 2020-11-03 14:30:18 +08:00
6f202a0ae5 修改视频跳转 2020-11-03 14:16:50 +08:00
asd
a56ba496d6 Merge pull request '钱包 新增账户验证' (#109) from zj into master
Reviewed-on: http://git.luyuan.tk/luyuan/beelink/pulls/109
2020-11-03 11:53:44 +08:00
e08b778260 显示账户号 2020-11-03 11:35:34 +08:00
81c33e9c3c 删除了! 2020-11-03 11:22:53 +08:00
131f7e36cd 添加了kong 2020-11-03 11:21:08 +08:00
f1667c865e 添加了分页为空 2020-11-03 11:20:10 +08:00
81895edf3d 删除了上课学生显示id 2020-11-03 11:10:01 +08:00
99 changed files with 14731 additions and 830 deletions

View File

@@ -1,5 +1,10 @@
let build = []
if (process.env.NODE_ENV === 'production') {
build = ['transform-remove-console']
}
module.exports = {
presets: [
'@vue/cli-plugin-babel/preset'
]
],
plugins: [...build]
}

View File

@@ -19,6 +19,7 @@
"trtc-js-sdk": "^4.6.5",
"vod-js-sdk-v6": "^1.4.10",
"vue": "^3.0.0-0",
"vue-cropper": "^0.5.5",
"vue-router": "^4.0.0-0",
"vuex": "^4.0.0-0"
},
@@ -33,6 +34,7 @@
"@vue/cli-service": "~4.5.0",
"@vue/compiler-sfc": "^3.0.0-0",
"@vue/eslint-config-typescript": "^5.0.2",
"babel-plugin-transform-remove-console": "^6.9.4",
"eslint": "^6.7.2",
"eslint-plugin-vue": "^7.0.0-0",
"node-sass": "^4.14.1",

287
public/CHANGELOG.md Normal file
View File

@@ -0,0 +1,287 @@
# TRTC Web SDK 版本发布日志
- 版本号规则:[major.minor.patch]
- major主版本号如有重大版本重构则该字段递增通常各主版本间接口不兼容。
- minor次版本号各次版本号间接口保持兼容如有接口新增或优化则该字段递增。
- patch补丁号如有功能改善或缺陷修复则该字段递增。
- 我们建议您及时更新到最新版本以便获得更好的产品稳定性及在线支持!
## 4.6.6 (2020-10-23)
**Improvement**
- 优化上行 peerConnection 重连逻辑
- 优化下行 peerConnection 重连逻辑
- 优化 TRTC.checkSystemRequirements 检测逻辑
- 支持 Safari 屏幕分享,参考:[屏幕分享使用教程](https://trtc-1252463788.file.myqcloud.com/web/docs/tutorial-06-advanced-screencast.html)
**Bug Fixed**
- 修复因自动播放策略限制手动恢复音频播放后getAudioLevel 值为0的问题
## 4.6.52020-10-14
**Improvement**
- 优化 WebSocket 信令通道重连逻辑,提升连接稳定性
- 优化日志输出逻辑
**Bug Fixed**
- Chrome 重新订阅后getAudioLevel 接口返回值为0的问题
- Safari 重新订阅后,播放无声的问题
- 使用 replaceTrack 替换上行音频轨道后getLocalVideoStats 接口返回 undefined 的问题
- 移动设备通话过程中,切换网络类型,偶现 WebSocket 连接断开的问题
## 4.6.42020-9-24
**Improvement**
- 退房后停止网络质量统计
**Bug Fixed**
- 修复 Chrome 56 进房报错的问题
- 修复移动端推旁路出现画面旋转的问题
- 修复纯音频推流时云端录制异常的问题
- 修复因分辨率不一致导致摄像头拔出后,自动恢复推流失败的问题
## 4.6.3 (2020-8-28)
**Improvement**
- 优化兼容性检测逻辑
- 优化日志上报逻辑
- 优化上行码率控制逻辑
## 4.6.2 (2020-8-14)
**Improvement**
- 优化上行码率调控逻辑
- 优化 switchRole 参数校验逻辑
- 优化上行网络质量计算逻辑
- 优化错误提示信息
- 检测当前推流采集设备变更时,自动恢复推流状态
**Bug Fixes**
- 修复 unpublish 成功后,立即重新 publish 失败报错的问题
## 4.6.1 (2020-7-28)
**Improvement**
- [TRTC.isScreenShareSupported](https://trtc-1252463788.file.myqcloud.com/web/docs/TRTC.html#.isScreenShareSupported) Safari 不支持屏幕分享
- 完善 subscribe & unsubscribe 接口的参数校验逻辑
- 增加网络质量日志
**Bug Fixes**
- 修复当未授权媒体设备,且 TRTC.createStream 接口传入的设备 ID 为空串时SDK 报 OverconstrainedError 的问题
- 修复上行 peerConnection 断开时没有打印日志的问题
## 4.6.0 (2020-7-16)
**Feature**
- 增加 NETWORK_QUALITY 事件
## 4.5.0 (2020-7-2)
**Feature**
- createStream 接口增加 screenAudio 参数
**Bug Fixes**
- 修复 Android 浏览器中回声消除不起作用的问题
- 修复 getTransportStats 接口返回的 rtt 值为 NAN 的问题
## 4.4.0 (2020-5-28)
**Feature**
- 支持 Chrome >= 74 屏幕分享采集系统windows或者当前 Tab 页面Mac的声音
## 4.3.14 (2020-4-29)
**Bug Fixes**
- 修复小程序音频 muted unmute 事件。
## 4.3.13 (2020-4-13)
**Improvement**
- 优化浏览器可用性检测
## 4.3.12 (2020-4-13)
**Bug Fixes**
- 修复一个潜在的RTCPeerConnection状态变化异常
## 4.3.11 (2020-3-28)
**Improvement**
- 增加手机 QQ 浏览器检测,手机 QQ 浏览器暂时无法支持 WebRTC
**Bug Fixes**
- 修复 Boolean 返回值类型
## 4.3.10 (2020-3-17)
**Improvement**
- 优化环境检测逻辑
- RtcError 增加 name code
## 4.3.9 (2020-3-13)
**Improvement**
- 增加部署环境自动检测
- 优化日志
## 4.3.8 (2020-2-24)
**Improvement**
- createClient 增加 streamId userdefinerecordid 字段
## 4.3.7 (2020-2-21)
**Improvement**
- 屏幕分享时切换设备抛出异常。
**Bug Fixes**
- 切换设备时释放 MediaStream解决设备占用问题。
- 订阅接口增加处理潜在错误。
## 4.3.6 (2020-2-5)
**Bug Fixes**
- 调整 Stream.resume() 音视频播放顺序,修复 iOS 上微信浏览器自动播放异常问题。
## 4.3.5 (2020-2-5)
**Improvement**
- 增加 publish 超时检查,提高信令发送成功率。
## 4.3.4 (2020-1-6)
**Improvement**
- 升级 core-js 至 v3.6.1。
**Bug Fixes**
- unpublish 超时后向外部抛出异常事件。
- 修复第三方库引起 V8 负优化问题。
## 4.3.3 (2019-12-25)
**Improvement**
- 增加主动检测环境是否支持 webrtc 能力。
- 优化 sdp 响应机制。
- 优化上报逻辑。
**Bug Fixes**
- 修复 turn url 协议格式。
## 4.3.2 (2019-12-09)
**Improvement**
- 增加下行连接 ICE 断开自动重连机制。
- 去除 STUN 打洞环节,增加内网用户连接成功率及提高连接速度。
- 日志上报时间戳统一使用服务器校正后的 UTC 时间。
- 优化 ICE 错误上报。
- 增加更多关键事件上报到 avmonitor 监控。
**Bug Fixes**
- 修复 WebSocket 信令通道 1005 异常重连及重连错误处理。
- 修复下行丢包率上报问题。
## 4.3.1 (2019-11-23)
**Improvement**
- 增加通话过程中上行链路 ICE 断开自动重连机制。
**Bug Fixes**
- 修复 STUN 打洞失败后 host 公网 IP 类型 ICE Candidate 不生效问题。
## 4.3.0(2019-11-15)
**Feature**
增加 Client.getTransportStats() API。
**Improvement**
- 增加更详细的上报日志。
- 事件解除绑定支持通配符。
- 增加连接超时时间至 5s。
- 增加发布超时时间至 5s。
**Bug Fixes**
修复因 zone.js 修改原型链导致 SDK 判断异常的问题。
## 4.2.0(2019-11-04)
**Feature**
- 增加 Client.off() 接口取消客户端事件绑定。
**Improvement**
- 通话状态统计优化。
- Client.publish() 增加权限检查。
- Stream.play()/resume() 增加自动播放错误提示。
**Bug Fixes**
- LocalStream.switchDevice() 切换摄像头黑屏问题修复。
## 4.1.1(2019-10-24)
**Bug Fixes**
- 修复日志丢失问题。
- 修复断网重连远端用户丢失问题。
## 4.1.0(2019-10-17)
**Feature**
- Stream.play() 接口支持传入 HTMLDivElement 对象。
- 增加音频码率调控设置,开发者可通过 LocalStream.setAudioProfile() 设置音频属性,目前支持两种 Profilestandard 和 high。
**Bug Fixes**
- 修复旧版本 Chrome 上的 WebAudio Context 数量受限问题。
- 修复 replaceTrack() 未重启本地音视频播放器问题。
- 修复 LocalStream.setScreenProfile() 自定义属性设置未生效问题。
- 修复 audio/video player 重启及状态上报问题。
## 4.0.0(2019-10-11)
TRTC Web SDK (WebRTC) 重构版本,提供 Client/Stream 模式的接口,各对象职责更明确,语义更简洁明了。
重构版本与旧版本不兼容,除接口改动之外,还提供如下功能:
- 视频属性 (分辨率、帧率及码率)控制完全由 App 通过 SDK 的 LocalStream.setVideoProfile() 接口设置,不再支持老版本通过腾讯云控制台的“画面设定 Spear Role”。
- SDK 在 Stream 对象中封装了音视频播放器,音视频播放完全由 SDK 控制。
- 提供远端流的订阅与取消订阅功能,开发者可以通过 Client.subscribe()/unsubscribe() 接口灵活控制远端流的音频、视频或音视频数据流的接收。

98
public/README.md Normal file
View File

@@ -0,0 +1,98 @@
本文主要介绍如何快速运行腾讯云 TRTC Web SDK Demo。
## 支持的平台
WebRTC 技术由 Google 最先提出,目前主要在桌面版 Chrome 浏览器、桌面版 Safari 浏览器以及移动版的 Safari 浏览器上有较为完整的支持,其他平台(例如 Android 平台的浏览器)支持情况均比较差。
- 在移动端推荐使用 [小程序](https://cloud.tencent.com/document/product/647/32399) 解决方案,微信和手机 QQ 小程序均已支持,都是由各平台的 Native 技术实现,音视频性能更好,且针对主流手机品牌进行了定向适配。
- 如果您的应用场景主要为教育场景,那么教师端推荐使用稳定性更好的 [Electron](https://cloud.tencent.com/document/product/647/38549) 解决方案,支持大小双路画面,更灵活的屏幕分享方案以及更强大而弱网络恢复能力。
| 操作系统 | 浏览器类型 | 浏览器最低版本要求 | 接收(播放) | 发送(上麦) | 屏幕分享 |
| :------: | :------------------: | :----------------: | :----------: | :----------: | :-----------------------: |
| Mac OS | 桌面版 Safari 浏览器 | 11+ | 支持 | 支持 | 不支持 |
| Mac OS | 桌面版 Chrome 浏览器 | 56+ | 支持 | 支持 | 支持需要chrome72+版本) |
| Windows | 桌面版 Chrome 浏览器 | 56+ | 支持 | 支持 | 支持需要chrome72+版本) |
| Windows | 桌面版 QQ 浏览器 | 10.4 | 支持 | 支持 | 不支持 |
| iOS | 移动版 Safari 浏览器 | 11.1.2 | 支持 | 支持 | 不支持 |
| iOS | 微信内嵌网页 | 12.1.4 | 支持 | 不支持 | 不支持 |
| Android | 移动版 QQ 浏览器 | - | 不支持 | 不支持 | 不支持 |
| Android | 移动版 UC 浏览器 | - | 不支持 | 不支持 | 不支持 |
| Android | 微信内嵌网页 | - | 不支持 | 不支持 | 不支持 |
>!
>- 您可以在浏览器中打开 [WebRTC 能力测试](https://www.qcloudtrtc.com/webrtc-samples/abilitytest/index.html) 页面进行检测是否完整支持 WebRTC。例如公众号等浏览器环境。
>- 由于 H.264 版权限制,华为系统的 Chrome 浏览器和以 Chrome WebView 为内核的浏览器均不支持 TRTC 的 Web 版 SDK 的正常运行。
<span id="requirements"></span>
## 环境要求
- 请使用最新版本的 Chrome 浏览器。
- TRTC Web SDK 依赖以下端口进行数据传输,请将其加入防火墙白名单,配置完成后,您可以通过访问并体验 [官网 Demo](https://trtc-1252463788.file.myqcloud.com/web/demo/official-demo/index.html) 检查配置是否生效。
- TCP 端口8687
- UDP 端口80008080880084344316285
- 域名qcloud.rtc.qq.com
## 前提条件
您已 [注册腾讯云](https://cloud.tencent.com/document/product/378/17985) 账号,并完成 [实名认证](https://cloud.tencent.com/document/product/378/3629)。
## 操作步骤
<span id="step1"></span>
### 步骤1创建新的应用
1. 登录实时音视频控制台,选择【开发辅助】>【[快速跑通Demo](https://console.cloud.tencent.com/trtc/quickstart)】。
2. 单击【立即开始】,输入应用名称,例如`TestTRTC`,单击【创建应用】。
<span id="step2"></span>
### 步骤2下载 SDK 和 Demo 源码
1. 鼠标移动至对应卡片,单击【[Github](https://github.com/tencentyun/TRTCSDK/tree/master/Web/TRTCSimpleDemo)】跳转至 Github或单击【[ZIP](https://liteavsdk-1252463788.cos.ap-guangzhou.myqcloud.com/H5_latest.zip?_ga=1.195966252.185644906.1567570704)】),下载相关 SDK 及配套的 Demo 源码。
![](https://main.qcloudimg.com/raw/0f35fe3bafe9fcdbd7cc73f991984d1a.png)
2. 下载完成后,返回实时音视频控制台,单击【我已下载,下一步】,可以查看 SDKAppID 和密钥信息。
<span id="step3"></span>
### 步骤3配置 Demo 工程文件
1. 解压 [步骤2](#step2) 中下载的源码包。
2. 找到并打开`Web/TRTCSimpleDemo/js/debug/GenerateTestUserSig.js`文件。
3. 设置`GenerateTestUserSig.js`文件中的相关参数:
<ul><li>SDKAPPID默认为0请设置为实际的 SDKAppID。</li>
<li>SECRETKEY默认为空字符串请设置为实际的密钥信息。</li></ul>
<img src="https://main.qcloudimg.com/raw/1732ea2401af6111b41259a78b5330a4.png">
4. 返回实时音视频控制台,单击【粘贴完成,下一步】。
5. 单击【关闭指引,进入控制台管理应用】。
>!本文提到的生成 UserSig 的方案是在客户端代码中配置 SECRETKEY该方法中 SECRETKEY 很容易被反编译逆向破解,一旦您的密钥泄露,攻击者就可以盗用您的腾讯云流量,因此**该方法仅适合本地跑通 Demo 和功能调试**。
>正确的 UserSig 签发方式是将 UserSig 的计算代码集成到您的服务端,并提供面向 App 的接口,在需要 UserSig 时由您的 App 向业务服务器发起请求获取动态 UserSig。更多详情请参见 [服务端生成 UserSig](https://cloud.tencent.com/document/product/647/17275#Server)。
### 步骤4运行 Demo
使用 Chrome 浏览器打开 Demo 根目录下的`index.html`文件即可运行 Demo。
>!
> - 一般情况下体验 Demo 需要部署至服务器,通过`https://域名/xxx`访问,或者直接在本地搭建服务器,通过`localhost:端口`访问。
> - 目前桌面端 Chrome 浏览器支持 TRTC Web SDK 的相关特性比较完整,因此建议使用 Chrome 浏览器进行体验。
Demo 运行界面如图所示:
![](https://main.qcloudimg.com/raw/e989c968446e6e3bdcc19c58e40e2b86.png)
- 单击【加入房间】加入音视频通话房间并且发布本地音视频流。
您可以打开多个页面,每个页面都单击 【加入房间】,正常情况下可以看到多个画面并模拟实时音视频通话。
- 单击摄像头图标可以选择摄像头设备。
- 单击麦克风图表可以选择麦克风设备。
>?WebRTC 需要使用摄像头和麦克风采集音视频,在体验过程中您可能会收到来自 Chrome 浏览器的相关提示,单击【允许】。
> ![](https://main.qcloudimg.com/raw/1a2c1e7036720b11f921f8ee1829762a.png)
## 常见问题
### 1. 查看密钥时只能获取公钥和私钥信息,要如何获取密钥?
TRTC SDK 6.6 版本2019年08月开始启用新的签名算法 HMAC-SHA256。在此之前已创建的应用需要先升级签名算法才能获取新的加密密钥。如不升级您也可以继续使用 [老版本算法 ECDSA-SHA256](https://cloud.tencent.com/document/product/647/17275#.E8.80.81.E7.89.88.E6.9C.AC.E7.AE.97.E6.B3.95)。
升级操作:
1. 登录 [实时音视频控制台](https://console.cloud.tencent.com/trtc)。
2. 在左侧导航栏选择【应用管理】,单击目标应用所在行的【应用信息】。
3. 选择【快速上手】页签,单击【第二步 获取签发UserSig的密钥】区域的【点此升级】。
### 2. 出现客户端错误“RtcError: no valid ice candidate found”该如何处理
出现该错误说明 TRTC Web SDK 在 STUN 打洞失败,请根据 [环境要求](#requirements) 检查防火墙配置。
### 3. 出现客户端错误:"RtcError: ICE/DTLS Transport connection failed" 或 “RtcError: DTLS Transport connection timeout”该如何处理
出现该错误说明 TRTC Web SDK 在建立媒体传输通道时失败,请根据 [环境要求](#requirements) 检查防火墙配置。
### 4. 出现10006 error 该如何处理?
如果出现"Join room failed result: 10006 error: service is suspended,if charge is overdue,renew it",请确认您的实时音视频应用的服务状态是否为可用状态。
登录 [实时音视频控制台](https://console.cloud.tencent.com/rav),单击您创建的应用,单击【帐号信息】,在帐号信息面板即可确认服务状态。
![](https://main.qcloudimg.com/raw/13c9b520ea333804cffb4e2c4273fced.png)

File diff suppressed because one or more lines are too long

530
public/css/index.css Normal file
View File

@@ -0,0 +1,530 @@
*{
margin: 0;
padding: 0;
}
body{
font-family: PingFangSC-Regular !important;
}
html{
width: 100%;
height: 100%;
}
.icon {
width: 1em; height: 1em;
vertical-align: -0.15em;
fill: currentColor;
overflow: hidden;
}
button{
margin: 0 !important;
color: #fff !important;
background-color: #006EFF !important
}
div{
display: flex;
align-items: center;
}
.row-div{
flex-direction: row;
}
.col-div{
flex-direction: column;
}
#root{
width: 100%;
height: 100%;
background-color: rgb(250, 250, 250);
position: absolute;
display: block;
}
#login-root{
width: 100%;
height: 100%;
display: flex;
justify-content: center;
align-items: center;
}
#login-card{
width: 450px;
height: 500px;
display: flex;
/* justify-content: center; */
align-items: center;
background-color: white;
}
.login-card{
width: 360px;
height: 450px;
background-color: white;
margin: 0 auto;
display: flex;
/* justify-content: center; */
align-items: center;
}
.popover{
min-width: 300px;
max-width: 1000px;
border: 0;
white-space: nowrap;
/* overflow: hidden; */
}
.popover-body{
flex-direction: column;
padding: 0;
}
.popover-body>div{
width: 100%;
height: 35px;
justify-content: center;
cursor: default;
}
.popover-body>div:hover{
background-color: #F7F7F7;
}
.icon-gray{
color: #bfbfbf;
}
.icon-normal{
color: #515151;
}
.icon-blue{
color: #006EFF;
}
.device-testing-btn{
color: #515151;
cursor: pointer;
margin-top: -14px;
opacity: 0.8;
}
.device-connect-list{
width: 310px;
height: 70px;
position: absolute;
bottom: 50px;
background-color: #fff;
border-radius: 5px;
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.2);
justify-content: space-around;
padding: 0 10px;
}
.device-connect-list::before{
content: '';
width: 0;
height: 0;
border: 8px transparent solid;
border-top-color: rgba(0, 0, 0, 0.2);
opacity: 0.6;
position: absolute;
bottom: -16px;
left: 50%;
transform: translateX(-50%);
}
.device-connect-list::after{
content: '';
width: 0;
height: 0;
border: 7px transparent solid;
border-top-color: #fff;
position: absolute;
bottom: -14px;
left: 50%;
transform: translateX(-50%);
}
.connect{
width: 28px;
height: 64px;
font-size: 28px;
text-align: center;
position: relative;
opacity: 0.8;
}
.device-icon{
width: 20px;
height: 20px;
margin-right: 3px;
display: block;
text-align: center;
font-size: 20px;
line-height: 20px;
}
#device-testing-root {
width: 100%;
height: 100%;
position: absolute;
top: 0;
left: 0;
display: flex;
justify-content: center;
align-self: center;
background: rgba(0, 0, 0, 0.1);
}
.device-testing-card{
width: 640px;
height: 480px;
background: #F7F7F7;
border-radius: 10px;
position: relative;
display: block;
}
.device-testing-prepare,.device-testing,.device-testing-report{
display: block;
}
.testing-title{
font-size: 34px;
justify-content: center;
margin-top: 55px;
color: #201e1ee5;
}
.testing-prepare-info{
font-size: 16px;
justify-content: center;
margin-top: 25px;
color: #585656e5;
}
.device-testing-close-btn{
width: 25px;
height: 25px;
position: absolute;
top: 8px;
right: 8px;
cursor: pointer;
font-size: 20px;
}
.device-display{
margin-top: 40px;
justify-content: center;
}
.device{
width: 46px;
height: 46px;
position: relative;
justify-content: center;
font-size: 38px;
}
.device:not(:first-child){
margin-left: 60px;
}
.device:not(:first-child).safari{
margin-left: 100px;
}
.device::before{
content: '';
width: 28px;
height: 28px;
position: absolute;
bottom: -34px;
left: 50%;
transform: translateX(-50%);
}
.connect-success::before{
background: url('../img/success.png') no-repeat;
background-size: 100% 100%;
}
.connect-fail::before{
background: url('../img/fail.png') no-repeat;
background-size: 100% 100%;
}
@keyframes device-loading {
0%{
width: 0%;
border-radius: 6px 0 0 6px;
}
50%{
width: 50%;
border-radius: 6px 0 0 6px;
}
100%{
width: 100%;
border-radius: 6px;
}
}
.loading-background{
width: 350px;
height: 3px;
border-radius: 6px;
margin: 20px auto 0;
background: #bfbfbf;
position: absolute;
left: 50%;
transform: translateX(-50%);
}
.device-loading{
height: 3px;
background-color: #808080;
animation: device-loading 2s;
animation-fill-mode: both;
}
.connect-info{
margin-top: 60px;
display: flex;
height: 48px;
justify-content: center;
}
.connect-attention-container{
position: relative;
margin-left: 3px;
}
.connect-attention-icon{
font-size: 20px;
color: red;
}
.connect-attention-info{
padding: 8px 12px;
min-width: 120px;
min-height: 50px;
background: rgba(0, 0, 0, 0.6);
border-radius: 10px;
color: #fff;
position: absolute;
right: 0;
bottom: 100%;
transform: translate(20px, -10px);
display: block;
white-space: nowrap;
font-size: 12px;
}
.connect-attention-info::after{
content: '';
width: 0;
height: 0;
border: 10px transparent solid;
border-top-color: rgba(0, 0, 0, 0.6);
position: absolute;
left: 100%;
top: 100%;
transform: translateX(-40px);
}
.testing-btn-display{
justify-content: center;
margin-top: 30px;
}
.test-btn{
width: 200px;
height: 44px;
background: #006EFF;
border-radius: 5px;
text-align: center;
color: #fff;
justify-content: center;
cursor: pointer;
}
.start-gray{
background: #dddddd;
color: #fff;
cursor: not-allowed;
}
.device-testing-title{
justify-content: center;
width: 100%;
margin-top: 40px;
}
.testing{
width: 26px;
height: 26px;
position: relative;
justify-content: center;
text-align: center;
font-size: 24px;
line-height: 24px;
}
.testing:not(:first-child){
margin-left: 90px;
}
.testing:not(:first-child)::before {
content: '';
width: 70px;
height: 2px;
position: absolute;
top: 50%;
left: -80px;
background: #bfbfbf;
}
.testing:not(:first-child).safari{
margin-left: 150px;
}
.testing:not(:first-child).safari::before{
width: 130px;
left: -140px;
}
.testing.complete {
cursor: pointer;
}
.testing.complete:not(:first-child)::before {
background: #006EFF;
}
.testing-body{
width: 100%;
display: block;
}
.device-list{
margin-left: 140px;
margin-top: 30px;
}
.device-select{
width: 260px;
height: 30px;
margin-left: 20px;
padding: 0 10px;
border-radius: 4px;
}
.camera-video{
width: 300px;
height: 180px;
display: block;
margin: 30px auto 0;
}
.testing-info-container{
display: block;
position: absolute;
bottom: 50px;
margin-top: 24px;
left: 50%;
transform: translateX(-50%);
}
.testing-info{
width: 100%;
text-align: center;
display: block;
}
.button-list{
margin-top: 20px;
width: 300px;
justify-content: space-around;
}
.fail-button{
border: 1px solid #006EFF;
border-radius: 8px;
color: #006EFF;
padding: 6px 14px;
cursor: pointer;
}
.success-button{
border: 1px solid #006EFF;
border-radius: 8px;
background: #006EFF;
color: #fff;
padding: 6px 14px;
cursor: pointer;
}
.audio-control{
width: 320px;
display: block;
margin: 40px auto 0;
}
.audio-control-info{
margin: 0px auto 20px 6px;
color: #5f5f5f;
}
.mic-testing-container{
display: block;
margin-top: 30px;
}
.mic-testing-info{
margin-left: 140px;
color: #bbbbbb;
font-size: 14px;
}
.mic-bar-container{
justify-content: center;
margin-top: 10px;
}
.mic-bar{
width: 10px;
height: 30px;
border: 1px solid #cccccc;
border-radius: 1px;
}
.mic-bar:not(:first-child){
margin-left: 3px;
}
.mic-bar.active{
background: #006EFF;
}
.testing-index-list{
margin-top: 40px;
display: block;
}
.testing-index-group{
width: 55%;
display: flex;
justify-content: space-between;
margin-top: 14px;
margin: 10px auto 0;
}
@keyframes loading-circle{
0% {
transform: rotate(0deg);
}
25% {
transform: rotate(90deg);
}
50% {
transform: rotate(180deg);
}
75% {
transform: rotate(270deg);
}
100% {
transform: rotate(360deg);
}
}
.network-loading::before{
content: '';
width: 16px;
height: 16px;
background: url('../img/loading.png');
background-size: 100% 100%;
animation: loading-circle 2s linear infinite;
}
.testing-footer{
margin-top: 70px;
justify-content: center;
}
.device-report-list{
display: block;
margin-top: 40px;
}
.device-report{
width: 60%;
margin: 20px auto 0;
justify-content: space-between;
}
.report-icon{
width: 24px;
height: 24px;
margin-right: 20px;
justify-content: center;
font-size: 22px;
line-height: 22px;
color: #515151;
}
.device-name{
width: 280px;
height: 24px;
display: block;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.device-report-footer{
margin-top: 50px;
justify-content: center;
}
.device-report-btn{
width: 160px;
height: 40px;
border: 1px solid;
border-radius: 6px;
justify-content: center;
cursor: pointer;
}
.testing-agin{
border-color: #006EFF;
color: #006EFF;
}
.testing-finish{
background: #006EFF;
color: #fff;
margin-left: 60px;
}

92
public/css/room.css Normal file
View File

@@ -0,0 +1,92 @@
*{
margin: 0;
padding: 0;
}
html{
width: 100%;
height: 100%;
}
body{
width: 100%;
height: 100%;
background-color: #F5F5F5;
font-family: Futura,sans-serif;
}
video{
background-color: #d8d8d8;
}
/* @media screen and (min-width:960px) {
video{
width: auto !important;
height: 100% !important;
position: inherit !important;
}
.video-div{
height: 100%;
display: flex;
justify-content: center;
align-items: center;
}
}
@media screen and (max-width:960px) {
video{
width: 100% !important;
height: auto !important;
position: inherit !important;
}
.video-div{
width: 100%;
display: flex;
justify-content: center;
align-items: center;
}
} */
#room-root{
width: 100%;
height: 100%;
min-width: 1500px;
min-height: 700px;
display: none;
align-items: flex-start;
background-color: #f0f0f0
}
.member{
cursor: default;
border-bottom-style: solid;
border-width: 1px;
border-bottom-color: #f0f0f0;
}
#video-grid{
display: grid;
grid-template-columns: repeat(5, 1fr);
grid-template-rows: repeat(3, 1fr);
grid-template-areas: 'm m m a b'
'm m m c d'
'e f g h i';
}
#video-grid>div{
position: relative;
padding: 10px;
}
#main-video{
width: 100%;
height: 100%;
padding: 10px;
grid-area: 1/1/3/4;
}
.video-box{
width: 100%;
height: 100%;
}
.mask{
width: 100%;
height: 100%;
position: absolute;
font-size: 14px;
color: #888888;
z-index: 9;
justify-content: center
}
div[id^=player] {
background-color: #d8d8d8 !important;
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.6 KiB

BIN
public/img/big-mic-off.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.5 KiB

BIN
public/img/big-mic-on.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.6 KiB

BIN
public/img/camera-max.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.6 KiB

BIN
public/img/camera-off.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 794 B

BIN
public/img/camera-on.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 774 B

BIN
public/img/camera.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 796 B

BIN
public/img/code.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 67 KiB

BIN
public/img/fail.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 533 B

BIN
public/img/loading.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 701 B

BIN
public/img/logo.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.4 KiB

BIN
public/img/logout.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.4 KiB

BIN
public/img/mic-off.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 800 B

BIN
public/img/mic-on.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 761 B

BIN
public/img/mic.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 809 B

BIN
public/img/screen-off.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.2 KiB

BIN
public/img/screen-on.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.2 KiB

BIN
public/img/shot.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

BIN
public/img/success.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 569 B

View File

@@ -7,6 +7,15 @@
<meta name="viewport" content="width=device-width,initial-scale=1.0">
<link rel="icon" href="<%= BASE_URL %>favicon.ico">
<title><%= htmlWebpackPlugin.options.title %></title>
<link href="https://imgcache.qq.com/open/qcloud/video/tcplayer/tcplayer.css" rel="stylesheet">
<!-- 如需在IE8、9浏览器中初始化播放器浏览器需支持Flash并在页面中引入 -->
<!--[if lt IE 9]>
<script src="//imgcache.qq.com/open/qcloud/video/tcplayer/ie8/videojs-ie8.js"></script>
<![endif]-->
<!-- 如果需要在 Chrome Firefox 等现代浏览器中通过H5播放hls需要引入 hls.js -->
<script src="https://imgcache.qq.com/open/qcloud/video/tcplayer/lib/hls.min.0.8.8.js"></script>
<!-- 引入播放器 js 文件 -->
<script src="https://imgcache.qq.com/open/qcloud/video/tcplayer/tcplayer.min.js"></script>
<script>
//designWidth:设计稿的实际宽度值,需要根据实际设置
//maxWidth:制作稿的最大宽度值,需要根据实际设置
@@ -70,7 +79,7 @@
position: fixed;
top: 0;
left: 0;
z-index: 999;
z-index: 1001;
display: none;
}
</style>

6939
public/js/bootstrap-material-design.js vendored Normal file

File diff suppressed because it is too large Load Diff

656
public/js/common.js Normal file
View File

@@ -0,0 +1,656 @@
/* eslint-disable no-cond-assign */
/* global $ TRTC presetting RtcClient ShareClient */
/* eslint-disable require-jsdoc */
let isCamOn = true;
let isMicOn = true;
let isScreenOn = false;
let isJoined = true;
let rtc = null;
let share = null;
let shareUserId = '';
let cameraId = '';
let micId = '';
function login() {
presetting.login(false, options => {
rtc = new RtcClient(options);
join();
});
presetting.login(true, options => {
shareUserId = options.userId;
share = new ShareClient(options);
});
}
$(function (){
login();
})
function join() {
rtc.join();
$('#login-root').hide();
$('#room-root').show();
$('#header-roomId').html('房间号: ' + $('#roomId').val());
$('#member-me')
.find('.member-id')
.html($('#userId').val() + '(我)');
}
function leave() {
$('#mask_main').appendTo($('#main-video'));
rtc.leave();
share.leave();
}
function publish() {
rtc.publish();
}
function unpublish() {
rtc.unpublish();
}
function muteAudio() {
rtc.muteLocalAudio();
}
function unmuteAudio() {
rtc.unmuteLocalAudio();
}
function muteVideo() {
$('#mask_main').show();
rtc.muteLocalVideo();
}
function unmuteVideo() {
rtc.unmuteLocalVideo();
$('#mask_main').hide();
}
function startSharing() {
share.join();
}
function stopSharing() {
share.leave();
}
function setBtnClickFuc() {
//userid roomid规格
//$('#userId').on('input', function(e) {
// e.preventDefault();
// console.log('userId input ' + e.target.value);
// let val = $('#userId').val().slice(5);
// $('#userId').val('user_'+val.replace(/[^\d]/g,''));
//});
$('#roomId').on('input', function(e) {
e.preventDefault();
console.log('roomId input ' + e.target.value);
let val = $('#roomId').val();
$('#roomId').val(val.replace(/[^\d]/g, ''));
});
//login
$('#login-btn').click(() => {
login();
});
//open or close camera
$('#video-btn').on('click', () => {
if (isCamOn) {
$('#video-btn').attr('src', './img/big-camera-off.png');
$('#video-btn').attr('title', '打开摄像头');
$('#member-me')
.find('.member-video-btn')
.attr('src', 'img/camera-off.png');
isCamOn = false;
muteVideo();
} else {
$('#video-btn').attr('src', './img/big-camera-on.png');
$('#video-btn').attr('title', '关闭摄像头');
$('#member-me')
.find('.member-video-btn')
.attr('src', 'img/camera-on.png');
isCamOn = true;
unmuteVideo();
}
});
//open or close microphone
$('#mic-btn').on('click', () => {
if (isMicOn) {
$('#mic-btn').attr('src', './img/big-mic-off.png');
$('#mic-btn').attr('title', '打开麦克风');
$('#member-me')
.find('.member-audio-btn')
.attr('src', 'img/mic-off.png');
isMicOn = false;
muteAudio();
} else {
$('#mic-btn').attr('src', './img/big-mic-on.png');
$('#mic-btn').attr('title', '关闭麦克风');
$('#member-me')
.find('.member-audio-btn')
.attr('src', 'img/mic-on.png');
isMicOn = true;
unmuteAudio();
}
});
//share screen or not
$('#screen-btn').on(
'click',
throttle(() => {
if (!TRTC.isScreenShareSupported()) {
alert('当前浏览器不支持屏幕分享!');
return;
}
if ($('#screen-btn').attr('src') == './img/screen-on.png') {
$('#screen-btn').attr('src', './img/screen-off.png');
stopSharing();
isScreenOn = false;
} else {
$('#screen-btn').attr('src', './img/screen-on.png');
startSharing();
isScreenOn = true;
}
}, 2000)
);
//logout
$('#logout-btn').on('click', () => {
leave();
$('#room-root').hide();
$('#login-root').show();
});
//switch main video
$('#main-video').on('click', () => {
let mainVideo = $('.video-box').first();
if ($('#main-video').is(mainVideo)) {
return;
}
//释放main-video grid-area
mainVideo.css('grid-area', 'auto/auto/auto/auto');
exchangeView($('#main-video'), mainVideo);
//将video-grid中第一个div设为main-video
$('.video-box')
.first()
.css('grid-area', '1/1/3/4');
//chromeM71以下会自动暂停手动唤醒
if (getBroswer().broswer == 'Chrome' && getBroswer().version < '72') {
rtc.resumeStreams();
}
});
//chrome60以下不支持popover防止error
if (getBroswer().broswer == 'Chrome' && getBroswer().version < '60') return;
//开启popover
$(function() {
$('[data-toggle="popover"]').popover();
});
$('#camera').popover({
html: true,
content: () => {
return $('#camera-option').html();
}
});
$('#microphone').popover({
html: true,
content: () => {
return $('#mic-option').html();
}
});
$('#camera').on('click', () => {
$('#microphone').popover('hide');
$('.popover-body')
.find('div')
.attr('onclick', 'setCameraId(this)');
});
$('#microphone').on('click', () => {
$('#camera').popover('hide');
$('.popover-body')
.find('div')
.attr('onclick', 'setMicId(this)');
});
//点击body关闭popover
$('body').click(() => {
$('#camera').popover('hide');
$('#microphone').popover('hide');
});
//popover事件
$('#camera').on('show.bs.popover', () => {
$('#camera').attr('src', './img/camera-on.png');
});
$('#camera').on('hide.bs.popover', () => {
$('#camera').attr('src', './img/camera.png');
});
$('#microphone').on('show.bs.popover', () => {
$('#microphone').attr('src', './img/mic-on.png');
});
$('#microphone').on('hide.bs.popover', () => {
$('#microphone').attr('src', './img/mic.png');
});
}
function setCameraId(thisDiv) {
cameraId = $(thisDiv).attr('id');
console.log('setCameraId: ' + cameraId);
}
function setMicId(thisDiv) {
micId = $(thisDiv).attr('id');
console.log('setMicId: ' + micId);
}
function addVideoView(id, isLocal = false) {
let div = $('<div/>', {
id: id,
class: 'video-box',
style: 'justify-content: center'
});
div.appendTo('#video-grid');
//设置监听
div.click(() => {
let mainVideo = $('.video-box').first();
if (div.is(mainVideo)) {
return;
}
//释放main-video grid-area
mainVideo.css('grid-area', 'auto/auto/auto/auto');
exchangeView(div, mainVideo);
//将video-grid中第一个div设为main-video
$('.video-box')
.first()
.css('grid-area', '1/1/3/4');
//chromeM71以下会自动暂停手动唤醒
if (getBroswer().broswer == 'Chrome' && getBroswer().version < '72') {
rtc.resumeStreams();
}
});
}
function addMemberView(id) {
let memberElm = $('#member-me').clone();
memberElm.attr('id', id);
memberElm.find('div.member-id').html(id);
memberElm.css('display', 'flex');
memberElm.appendTo($('#member-list'));
}
function removeView(id) {
if ($('#' + id)[0]) {
$('#' + id).remove();
//将video-grid中第一个div设为main-video
$('.video-box')
.first()
.css('grid-area', '1/1/3/4');
}
}
function exchangeView(a, b) {
var $div1 = $(a);
var $div3 = $(b);
var $temobj1 = $('<div></div>');
var $temobj2 = $('<div></div>');
$temobj1.insertBefore($div1);
$temobj2.insertBefore($div3);
$div1.insertAfter($temobj2);
$div3.insertAfter($temobj1);
$temobj1.remove();
$temobj2.remove();
}
function isPC() {
var userAgentInfo = navigator.userAgent;
var Agents = new Array('Android', 'iPhone', 'SymbianOS', 'Windows Phone', 'iPad', 'iPod');
var flag = true;
for (var v = 0; v < Agents.length; v++) {
if (userAgentInfo.indexOf(Agents[v]) > 0) {
flag = false;
break;
}
}
return flag;
}
function getCameraId() {
console.log('selected cameraId: ' + cameraId);
return cameraId;
}
function getMicrophoneId() {
console.log('selected microphoneId: ' + micId);
return micId;
}
function throttle(func, delay) {
var timer = null;
var startTime = Date.now();
return function() {
var curTime = Date.now();
var remaining = delay - (curTime - startTime);
var context = this;
var args = arguments;
clearTimeout(timer);
if (remaining <= 0) {
func.apply(context, args);
startTime = Date.now();
} else {
timer = setTimeout(() => {
console.log('duplicate click');
}, remaining);
}
};
}
function resetView() {
isCamOn = true;
isMicOn = true;
isScreenOn = false;
isJoined = true;
$('#main-video-btns').hide();
$('#video-btn').attr('src', './img/big-camera-on.png');
$('#mic-btn').attr('src', './img/big-mic-on.png');
$('#screen-btn').attr('src', './img/screen-off.png');
$('#member-me')
.find('.member-video-btn')
.attr('src', 'img/camera-on.png');
$('#member-me')
.find('.member-audio-btn')
.attr('src', 'img/mic-on.png');
$('.mask').hide();
//清空member-list
if ($('#member-list')) {
$('#member-list')
.find('.member')
.each((index, element) => {
if (
$(element)
.parent()
.attr('id') != 'member-me'
) {
$(element)
.parent()
.remove();
}
});
}
}
function getBroswer() {
var sys = {};
var ua = navigator.userAgent.toLowerCase();
var s;
(s = ua.match(/edge\/([\d.]+)/))
? (sys.edge = s[1])
: (s = ua.match(/rv:([\d.]+)\) like gecko/))
? (sys.ie = s[1])
: (s = ua.match(/msie ([\d.]+)/))
? (sys.ie = s[1])
: (s = ua.match(/firefox\/([\d.]+)/))
? (sys.firefox = s[1])
: (s = ua.match(/chrome\/([\d.]+)/))
? (sys.chrome = s[1])
: (s = ua.match(/opera.([\d.]+)/))
? (sys.opera = s[1])
: (s = ua.match(/version\/([\d.]+).*safari/))
? (sys.safari = s[1])
: 0;
if (sys.edge) return { broswer: 'Edge', version: sys.edge };
if (sys.ie) return { broswer: 'IE', version: sys.ie };
if (sys.firefox) return { broswer: 'Firefox', version: sys.firefox };
if (sys.chrome) return { broswer: 'Chrome', version: sys.chrome };
if (sys.opera) return { broswer: 'Opera', version: sys.opera };
if (sys.safari) return { broswer: 'Safari', version: sys.safari };
return { broswer: '', version: '0' };
}
function isHidden() {
var hidden, visibilityChange;
if (typeof document.hidden !== 'undefined') {
hidden = 'hidden';
visibilityChange = 'visibilitychange';
} else if (typeof document.msHidden !== 'undefined') {
hidden = 'msHidden';
visibilityChange = 'msvisibilitychange';
} else if (typeof document.webkitHidden !== 'undefined') {
hidden = 'webkitHidden';
visibilityChange = 'webkitvisibilitychange';
}
return document[hidden];
}
function getIPAddress() {
return new Promise(resolve => {
window.RTCPeerConnection =
window.RTCPeerConnection || window.mozRTCPeerConnection || window.webkitRTCPeerConnection; //compatibility for firefox and chrome
let pc = new RTCPeerConnection({ iceServers: [] });
let noop = function() {};
let IPAddress = '';
let ipRegex = /([0-9]{1,3}(\.[0-9]{1,3}){3}|[a-f0-9]{1,4}(:[a-f0-9]{1,4}){7})/;
pc.createDataChannel(''); //create a bogus data channel
pc.createOffer(pc.setLocalDescription.bind(pc), noop); // create offer and set local description
//listen for candidate events
pc.onicecandidate = function(ice) {
if (
!ice ||
!ice.candidate ||
!ice.candidate.candidate ||
!ipRegex.exec(ice.candidate.candidate)
) {
return;
}
IPAddress = ipRegex.exec(ice.candidate.candidate)[1];
pc.onicecandidate = noop;
resolve(IPAddress);
};
});
}
let isMobile = {
Android: function() {
return navigator.userAgent.match(/Android/i);
},
BlackBerry: function() {
return navigator.userAgent.match(/BlackBerry|BB10/i);
},
iOS: function() {
return navigator.userAgent.match(/iPhone|iPad|iPod/i);
},
Opera: function() {
return navigator.userAgent.match(/Opera Mini/i);
},
Windows: function() {
return navigator.userAgent.match(/IEMobile/i);
},
any: function() {
return (
isMobile.Android() ||
isMobile.BlackBerry() ||
isMobile.iOS() ||
isMobile.Opera() ||
isMobile.Windows()
);
},
getOsName: function() {
var osName = 'Unknown OS';
if (isMobile.Android()) {
osName = 'Android';
}
if (isMobile.BlackBerry()) {
osName = 'BlackBerry';
}
if (isMobile.iOS()) {
osName = 'iOS';
}
if (isMobile.Opera()) {
osName = 'Opera Mini';
}
if (isMobile.Windows()) {
osName = 'Windows';
}
return osName;
}
};
function detectDesktopOS() {
var unknown = '-';
var nVer = navigator.appVersion;
var nAgt = navigator.userAgent;
var os = unknown;
var clientStrings = [
{
s: 'Chrome OS',
r: /CrOS/
},
{
s: 'Windows 10',
r: /(Windows 10.0|Windows NT 10.0)/
},
{
s: 'Windows 8.1',
r: /(Windows 8.1|Windows NT 6.3)/
},
{
s: 'Windows 8',
r: /(Windows 8|Windows NT 6.2)/
},
{
s: 'Windows 7',
r: /(Windows 7|Windows NT 6.1)/
},
{
s: 'Windows Vista',
r: /Windows NT 6.0/
},
{
s: 'Windows Server 2003',
r: /Windows NT 5.2/
},
{
s: 'Windows XP',
r: /(Windows NT 5.1|Windows XP)/
},
{
s: 'Windows 2000',
r: /(Windows NT 5.0|Windows 2000)/
},
{
s: 'Windows ME',
r: /(Win 9x 4.90|Windows ME)/
},
{
s: 'Windows 98',
r: /(Windows 98|Win98)/
},
{
s: 'Windows 95',
r: /(Windows 95|Win95|Windows_95)/
},
{
s: 'Windows NT 4.0',
r: /(Windows NT 4.0|WinNT4.0|WinNT|Windows NT)/
},
{
s: 'Windows CE',
r: /Windows CE/
},
{
s: 'Windows 3.11',
r: /Win16/
},
{
s: 'Android',
r: /Android/
},
{
s: 'Open BSD',
r: /OpenBSD/
},
{
s: 'Sun OS',
r: /SunOS/
},
{
s: 'Linux',
r: /(Linux|X11)/
},
{
s: 'iOS',
r: /(iPhone|iPad|iPod)/
},
{
s: 'Mac OS X',
r: /Mac OS X/
},
{
s: 'Mac OS',
r: /(MacPPC|MacIntel|Mac_PowerPC|Macintosh)/
},
{
s: 'QNX',
r: /QNX/
},
{
s: 'UNIX',
r: /UNIX/
},
{
s: 'BeOS',
r: /BeOS/
},
{
s: 'OS/2',
r: /OS\/2/
},
{
s: 'Search Bot',
r: /(nuhk|Googlebot|Yammybot|Openbot|Slurp|MSNBot|Ask Jeeves\/Teoma|ia_archiver)/
}
];
for (var i = 0, cs; (cs = clientStrings[i]); i++) {
if (cs.r.test(nAgt)) {
os = cs.s;
break;
}
}
var osVersion = unknown;
if (/Windows/.test(os)) {
if (/Windows (.*)/.test(os)) {
osVersion = /Windows (.*)/.exec(os)[1];
}
os = 'Windows';
}
switch (os) {
case 'Mac OS X':
if (/Mac OS X (10[/._\d]+)/.test(nAgt)) {
// eslint-disable-next-line no-useless-escape
osVersion = /Mac OS X (10[\.\_\d]+)/.exec(nAgt)[1];
}
break;
case 'Android':
// eslint-disable-next-line no-useless-escape
if (/Android ([\.\_\d]+)/.test(nAgt)) {
// eslint-disable-next-line no-useless-escape
osVersion = /Android ([\.\_\d]+)/.exec(nAgt)[1];
}
break;
case 'iOS':
if (/OS (\d+)_(\d+)_?(\d+)?/.test(nAgt)) {
osVersion = /OS (\d+)_(\d+)_?(\d+)?/.exec(nVer);
osVersion = osVersion[1] + '.' + osVersion[2] + '.' + (osVersion[3] | 0);
}
break;
}
return {
osName: os + osVersion
};
}
function getOS() {
if (isMobile.any()) {
return isMobile.getOsName();
} else {
return detectDesktopOS();
}
}

View File

@@ -0,0 +1,73 @@
/* eslint-disable require-jsdoc */
/*
* Module: GenerateTestUserSig
*
* Function: 用于生成测试用的 UserSigUserSig 是腾讯云为其云服务设计的一种安全保护签名。
* 其计算方法是对 SDKAppID、UserID 和 EXPIRETIME 进行加密,加密算法为 HMAC-SHA256。
*
* Attention: 请不要将如下代码发布到您的线上正式版本的 App 中,原因如下:
*
* 本文件中的代码虽然能够正确计算出 UserSig但仅适合快速调通 SDK 的基本功能,不适合线上产品,
* 这是因为客户端代码中的 SECRETKEY 很容易被反编译逆向破解,尤其是 Web 端的代码被破解的难度几乎为零。
* 一旦您的密钥泄露,攻击者就可以计算出正确的 UserSig 来盗用您的腾讯云流量。
*
* 正确的做法是将 UserSig 的计算代码和加密密钥放在您的业务服务器上,然后由 App 按需向您的服务器获取实时算出的 UserSig。
* 由于破解服务器的成本要高于破解客户端 App所以服务器计算的方案能够更好地保护您的加密密钥。
*
* Referencehttps://cloud.tencent.com/document/product/647/17275#Server
*/
function genTestUserSig(userID) {
/**
* 腾讯云 SDKAppId需要替换为您自己账号下的 SDKAppId。
*
* 进入腾讯云实时音视频[控制台](https://console.cloud.tencent.com/rav ) 创建应用,即可看到 SDKAppId
* 它是腾讯云用于区分客户的唯一标识。
*/
// const SDKAPPID = 1400435767;
// /**
// * 签名过期时间,建议不要设置的过短
// * <p>
// * 时间单位:秒
// * 默认时间7 x 24 x 60 x 60 = 604800 = 7 天
// */
// const EXPIRETIME = 604800;
// /**
// * 计算签名用的加密密钥,获取步骤如下:
// *
// * step1. 进入腾讯云实时音视频[控制台](https://console.cloud.tencent.com/rav ),如果还没有应用就创建一个,
// * step2. 单击“应用配置”进入基础配置页面,并进一步找到“帐号体系集成”部分。
// * step3. 点击“查看密钥”按钮,就可以看到计算 UserSig 使用的加密的密钥了,请将其拷贝并复制到如下的变量中
// *
// * 注意该方案仅适用于调试Demo正式上线前请将 UserSig 计算代码和密钥迁移到您的后台服务器上,以避免加密密钥泄露导致的流量盗用。
// * 文档https://cloud.tencent.com/document/product/647/17275#Server
// */
// const SECRETKEY = 'dzrUpsgeMo0ygiSmqeDVqxnLbdT3Lbbh';
// // a soft reminder to guide developer to configure sdkAppId/secretKey
// if (SDKAPPID === '' || SECRETKEY === '') {
// alert(
// '请先配置好您的账号信息: SDKAPPID 及 SECRETKEY ' +
// '\r\n\r\nPlease configure your SDKAPPID/SECRETKEY in js/debug/GenerateTestUserSig.js'
// );
// }
// const generator = new LibGenerateTestUserSig(SDKAPPID, SECRETKEY, EXPIRETIME);
// const userSig = window.istow ? "eJwtzdEKgjAUBuB32XXYdJubQhcV2IWBkGYEgohOO1SypoYQvXtLvTzff85-Pig5xtZbauQjx8JoNc1QybaHGibuboWW*dBJnRNMMcYeX9a66l4oBRXybeOUMO7yOZGjAi2NM8YcczJrD8*-uZhhYRPBlhZozJfW4*f9y7EPafmgTXjdnYQagjIRY7Ye4qim20sNaRsGEWk26PsDMPg1Xg__" : "eJwtzE8LgkAQBfDvstdCJt3RTeigl6gMw-7oLUK3GCNZ1Foj*u5t6vH93sz7sEO0t16yZj6zLWDTPlMhq5au1POzkfXZAQ4Ac288aIr7RSkqmD8zzh30XG9oZKeolsYR0TYvg7b0*JsLCAIcjuMK3cx*LmyV5GFXBWGZ6m4VVaejzjax3K3fehsTpk2QJKVeTsSCfX94djN6";
// console.log(userSig)
// window.istow = true
// return {
// sdkAppId: SDKAPPID,
// userSig:userSig
// };
return new Promise((res)=>{
$.ajax({
url: window.url + 'userSig?userid=' + userID,
headers: "Bearer " + localStorage.getItem("token"),
success(data){
res({data: data.data, id: 1400435767})
}
})
})
}

838
public/js/device-testing.js Normal file
View File

@@ -0,0 +1,838 @@
/**
* 设备检测demo
*/
/* global $ TRTC presetting getOS getBroswer cameraId micId */
// 用于记录检测结果,生成检测报告
let hasCameraDevice = false,
hasMicAndVoiceDevice = false,
hasCameraConnect,
hasVoiceConnect,
hasMicConnect,
hasNetworkConnect;
let cameraTestingResult = {};
let voiceTestingResult = {};
let micTestingResult = {};
let networkTestingResult = {};
// 记录检测步骤,用于关闭时清空弹窗
let completedTestingPageIdList = [];
let curTestingPageId = '';
let localStream = null;
let client = null;
let timeout = null;
// 监听到network-quality事件的次数
let networkQualityNum = 0;
const deviceFailAttention =
'1. 若浏览器弹出提示,请选择“允许”<br>' +
'2. 若杀毒软件弹出提示,请选择“允许”<br>' +
'3. 检查浏览器设置,允许网页访问摄像头及麦克风<br>' +
'4. 检查摄像头/麦克风是否正确连接并开启<br>' +
'5. 尝试重新连接摄像头/麦克风<br>' +
'6. 尝试重启电脑后重新检测';
const networkFailAttention =
'1. 请检查设备是否联网<br>' + '2. 请刷新网页后再次检测<br>' + '3. 请尝试更换网络后再次检测';
// 网络参数对照表
const NETWORK_QUALITY = {
'0': '未知',
'1': '极佳',
'2': '较好',
'3': '一般',
'4': '差',
'5': '极差',
'6': '断开'
};
// 设备检测tab页签对应的执行方法
const pageCallbackConfig = {
'camera-testing-body': 'startCameraTesting',
'voice-testing-body': 'startVoiceTesting',
'mic-testing-body': 'startMicTesting',
'network-testing-body': 'startNetworkTesting'
};
// 判断是否为safari浏览器
let isSafari = /Safari/.test(navigator.userAgent) && !/Chrome/.test(navigator.userAgent);
hideVoiceForSafari();
/**
* safari浏览器中隐藏扬声器相关检测
*/
function hideVoiceForSafari() {
if (!isSafari) return;
$('#connect-voice').hide();
$('#device-voice').hide();
$('#voice-testing').hide();
$('#voice-report').hide();
$('#device-mic').addClass('safari');
$('#device-network').addClass('safari');
$('#mic-testing').addClass('safari');
$('#network-testing').addClass('safari');
}
// 是否是本地路径打开
let isFilePath = location.href.indexOf('file://') > -1;
/**
* 设备检测初始化
*/
async function deviceTestingInit() {
// 点击【设备检测】文字, 点击 【重新连接】按钮
$('#device-testing-btn, #connect-again-btn').on('click', () => {
startDeviceConnect();
});
// 连接设备错误icon
$('#connect-attention-icon').on('mouseover', () => {
$('#connect-attention-info').show();
});
// 连接设备错误icon
$('#connect-attention-icon').on('mouseout', () => {
$('#connect-attention-info').hide();
});
// 【开始检测】开始设备检测按钮
$('#start-test-btn').on('click', function() {
if ($(this).hasClass('start-gray')) return;
$('#device-testing-prepare').hide();
$('#device-testing').show();
startCameraTesting();
});
// 摄像头检测失败/成功
$('#camera-fail, #camera-success').on('click', function() {
cameraTestingResult.statusResult = $(this).attr('id') === 'camera-success';
$('#camera-testing-body').hide();
localStream.close();
// safari浏览器跳过扬声器检测
isSafari ? startMicTesting() : startVoiceTesting();
});
// 播放器检测失败/成功
$('#voice-fail, #voice-success').on('click', function() {
voiceTestingResult.statusResult = $(this).attr('id') === 'voice-success';
$('#voice-testing-body').hide();
let audioPlayer = document.querySelector('#audio-player');
if (!audioPlayer.paused) {
audioPlayer.pause();
}
startMicTesting();
});
// 麦克风测试失败/成功
$('#mic-fail, #mic-success').on('click', function() {
micTestingResult.statusResult = $(this).attr('id') === 'mic-success';
$('#mic-testing-body').hide();
localStream.close();
startNetworkTesting();
});
// 点击【查看检测报告】按钮
$('#testing-report-btn').on('click', () => {
showTestingReport();
localStream.close();
client && client.leave();
client && client.off('network-quality');
});
// 点击【重新测试】按钮
$('#testing-again').on('click', () => {
$('#device-testing-report').hide();
startDeviceConnect();
completedTestingPageIdList = [];
});
// 点击【测试完成】按钮 / 点击关闭图标
$('#testing-finish, #device-testing-close-btn').on('click', () => {
finishDeviceTesting();
});
// 测试tab页切换
$('#camera-testing, #voice-testing, #mic-testing, #network-testing').on('click', function() {
let targetPageId = $(this).attr('id') + '-body';
if (
targetPageId !== curTestingPageId &&
completedTestingPageIdList.indexOf(targetPageId) > -1
) {
$(`#${curTestingPageId}`).hide();
localStream && localStream.close();
client && client.leave();
client && client.off('network-quality');
// 停止播放器的音乐
let audioPlayer = document.querySelector('#audio-player');
if (!audioPlayer.paused) {
audioPlayer.pause();
}
// 展示要切换的设备检测tab页面
$(`#${targetPageId}`).show();
window[pageCallbackConfig[targetPageId]] && window[pageCallbackConfig[targetPageId]]();
}
});
// 摄像头设备切换
$('#camera-select').change(async function() {
let newCameraId = $(this)
.children('option:selected')
.val();
localStorage.setItem('txy_webRTC_cameraId', newCameraId);
cameraTestingResult.device = {
label: $(this)
.children('option:selected')
.text(),
deviceId: $(this)
.children('option:selected')
.val(),
kind: 'videoinput'
};
await localStream.switchDevice('video', newCameraId);
});
// 扬声器设备切换
$('#voice-select').change(async function() {
let newVoiceId = $(this)
.children('option:selected')
.val();
localStorage.setItem('txy_webRTC_voiceId', newVoiceId);
voiceTestingResult.device = {
label: $(this)
.children('option:selected')
.text(),
deviceId: $(this)
.children('option:selected')
.val(),
kind: 'audiooutput'
};
let audioPlayer = document.querySelector('#audio-player');
await audioPlayer.setSinkId(newVoiceId);
});
// 麦克风设备切换
$('#mic-select').change(async function() {
let newMicID = $(this)
.children('option:selected')
.val();
localStorage.setItem('txy_webRTC_micId', newMicID);
micTestingResult.device = {
label: $(this)
.children('option:selected')
.text(),
deviceId: $(this)
.children('option:selected')
.val(),
kind: 'audioinput'
};
await localStream.switchDevice('audio', newMicID);
});
$('body').on('click', function() {
$('#device-connect-list').hide();
});
// 获取设备信息
await getDevicesInfo();
// 初始化设备弹窗信息
deviceDialogInit();
}
/**
* 获取设备信息及网络连接信息
*/
async function getDevicesInfo() {
let micList = await TRTC.getMicrophones();
let voiceList = await TRTC.getSpeakers();
let cameraList = await TRTC.getCameras();
let index = isFilePath ? 'label' : 'deviceId';
if (cameraList.length > 0) {
hasCameraDevice = true;
}
if (micList.length > 0) {
hasMicAndVoiceDevice = true;
}
cameraList.forEach(camera => {
if (camera[index].length > 0) {
hasCameraConnect = true;
}
});
micList.forEach(mic => {
if (mic[index].length > 0) {
hasMicConnect = true;
}
});
if (isSafari) {
hasVoiceConnect = true;
} else {
voiceList.forEach(voice => {
if (voice[index].length > 0) {
hasVoiceConnect = true;
}
});
}
hasNetworkConnect = !!navigator.onLine;
}
/**
* 判断是否展示弹窗
*/
function deviceDialogInit() {
if (!localStorage.getItem('txy_device_testing')) {
localStorage.setItem('txy_device_testing', Date.now());
startDeviceConnect();
} else {
// 在首页展示设备连接结果
let showDeviceStatus = function() {
$('#device-connect-list').show();
timeout = setTimeout(() => {
$('#device-connect-list').hide();
}, 3000);
$('#connect-camera').css('color', `${hasCameraConnect ? 'green' : 'red'}`);
$('#connect-voice').css('color', `${hasVoiceConnect ? 'green' : 'red'}`);
$('#connect-mic').css('color', `${hasMicConnect ? 'green' : 'red'}`);
$('#connect-network').css('color', `${hasNetworkConnect ? 'green' : 'red'}`);
if (!(hasCameraConnect && hasVoiceConnect && hasMicConnect && hasNetworkConnect)) {
$('#device-testing-btn').css('color', 'red');
} else {
$('#device-testing-btn').css('color', 'green');
}
};
showDeviceStatus();
if (!(hasCameraConnect && hasVoiceConnect && hasMicConnect)) {
navigator.mediaDevices
.getUserMedia({ video: hasCameraDevice, audio: hasMicAndVoiceDevice })
.then(async () => {
// 重新获取设备信息
await getDevicesInfo();
// 更新首页popover的option list
getDevicesList();
// 展示连接结果
showDeviceStatus();
})
.catch(err => {});
}
}
}
/**
* 弹窗-设备连接检查
*/
function startDeviceConnect() {
// 显示设备检测弹窗
$('#device-testing-root').show();
// 设备检测弹窗-设备连接页
$('#device-testing-prepare').show();
curTestingPageId = 'device-testing-prepare';
initTestingTabTitle();
// 在设备检测弹窗显示设备连接信息
let showDeviceConnectInfo = function() {
if (!(hasCameraConnect && hasVoiceConnect && hasMicConnect && hasNetworkConnect)) {
$('#device-testing-btn').css('color', 'red');
} else {
$('#device-testing-btn').css('color', 'green');
}
// 隐藏设备连接失败提示
$('#connect-attention-container').hide();
// 设备连接中
$('#device-loading').show();
$('#connect-info')
.text('设备正在连接中,请稍等')
.css('color', '#cccccc');
$('#device-camera, #device-voice, #device-mic, #device-network').removeClass(
'connect-success connect-fail'
);
$('#connect-again-btn').hide();
$('#start-test-btn')
.addClass('start-gray')
.show();
// 设备连接结束,展示连接结果
setTimeout(() => {
$('#device-loading').hide();
$('#device-camera')
.removeClass('connect-success connect-fail')
.addClass(`${hasCameraConnect ? 'connect-success' : 'connect-fail'}`);
$('#device-voice')
.removeClass('connect-success connect-fail')
.addClass(`${hasVoiceConnect ? 'connect-success' : 'connect-fail'}`);
$('#device-mic')
.removeClass('connect-success connect-fail')
.addClass(`${hasMicConnect ? 'connect-success' : 'connect-fail'}`);
$('#device-network')
.removeClass('connect-success connect-fail')
.addClass(`${hasNetworkConnect ? 'connect-success' : 'connect-fail'}`);
if (!(hasCameraConnect && hasVoiceConnect && hasMicConnect)) {
let connectInfo = hasNetworkConnect
? '设备连接失败,请允许网页访问摄像头及麦克风'
: '设备及网络连接失败,请允许网页访问摄像头及麦克风并检查网络连接';
$('#connect-info')
.text(connectInfo)
.css('color', 'red');
// 显示设备连接失败引导
$('#connect-attention-container').show();
$('#connect-attention-info').html(deviceFailAttention);
// 切换按钮状态
$('#start-test-btn').hide();
$('#connect-again-btn').show();
}
if (hasCameraConnect && hasVoiceConnect && hasMicConnect && !hasNetworkConnect) {
$('#connect-info')
.text('网络连接失败,请检查网络连接')
.css('color', 'red');
// 显示网络连接失败引导
$('#connect-attention-container').show();
$('#connect-attention-info').html(networkFailAttention);
// 切换按钮状态
$('#start-test-btn').hide();
$('#connect-again-btn').show();
}
if (hasCameraConnect && hasVoiceConnect && hasMicConnect && hasNetworkConnect) {
$('#connect-info')
.text('设备及网络连接成功,请开始设备检测')
.css('color', '#32CD32');
$('#connect-again-btn').hide();
$('#start-test-btn')
.removeClass('start-gray')
.show();
}
}, 2000);
};
showDeviceConnectInfo();
// 如果有设备未连接,唤起请求弹窗
if (!(hasCameraConnect && hasVoiceConnect && hasMicConnect)) {
navigator.mediaDevices
.getUserMedia({ video: hasCameraDevice, audio: hasMicAndVoiceDevice })
.then(async () => {
// 重新获取设备信息
await getDevicesInfo();
// 更新首页popover的option list
getDevicesList();
// 显示设备连接信息
showDeviceConnectInfo();
})
.catch(err => {
console.log('err', err.name);
});
}
}
/**
* 更新首页popover的option list
*/
function getDevicesList() {
// populate camera options
TRTC.getCameras().then(devices => {
$('#camera-option').empty();
devices.forEach(device => {
if (!cameraId) {
// eslint-disable-next-line no-global-assign
cameraId = device.deviceId;
}
let div = $('<div></div>');
div.attr('id', device.deviceId);
div.html(device.label);
div.appendTo('#camera-option');
});
});
// populate microphone options
TRTC.getMicrophones().then(devices => {
$('#mic-option').empty();
devices.forEach(device => {
if (!micId) {
// eslint-disable-next-line no-global-assign
micId = device.deviceId;
}
let div = $('<div></div>');
div.attr('id', device.deviceId);
div.html(device.label);
div.appendTo('#mic-option');
});
});
}
/**
* 摄像头检测页-检测展示摄像头设备选择列表
*/
async function updateCameraDeviceList() {
let cameraDevices = await TRTC.getCameras();
cameraDevices.filter(camera => camera.deviceId !== 'default');
$('#camera-select').empty();
cameraDevices.forEach(camera => {
let option = $('<option></option>');
option.attr('value', camera.deviceId);
option.html(camera.label);
option.appendTo('#camera-select');
});
// 如果有用户设备选择缓存优先使用缓存的deviceId
let cacheCameraDevice = cameraDevices.filter(
camera => camera.deviceId === localStorage.getItem('txy_webRTC_cameraId')
);
if (cacheCameraDevice.length > 0) {
$('#camera-select').val(localStorage.getItem('txy_webRTC_cameraId'));
cameraTestingResult.device = cacheCameraDevice[0];
} else {
$('#camera-select').val(cameraDevices[0].deviceId);
cameraTestingResult.device = cameraDevices[0];
}
}
/**
* 摄像头设备测试
*/
async function startCameraTesting() {
$('#camera-testing-body').show();
curTestingPageId = 'camera-testing-body';
$('#camera-testing')
.removeClass('icon-normal')
.addClass('icon-blue complete');
completedTestingPageIdList.push('camera-testing-body');
completedTestingPageIdList = [...new Set(completedTestingPageIdList)];
await updateCameraDeviceList();
// 创建本地视频流
await createLocalStream(
{
audio: false,
video: true,
cameraId: cameraTestingResult.device.deviceId
},
'camera-video'
);
}
/**
* 初始化/更新扬声器设备数组
*/
async function updateVoiceDeviceList() {
// 获取扬声器设备并展示在界面中
let voiceDevices = await TRTC.getSpeakers();
voiceDevices = voiceDevices.filter(voice => voice.deviceId !== 'default');
$('#voice-select').empty();
voiceDevices.forEach(voice => {
let option = $('<option></option>');
option.attr('value', voice.deviceId);
option.html(voice.label);
option.appendTo('#voice-select');
});
// 如果有用户设备选择缓存优先使用缓存的deviceId
let cacheVoiceDevice = voiceDevices.filter(
mic => mic.deviceId === localStorage.getItem('txy_webRTC_voiceId')
);
if (cacheVoiceDevice.length > 0) {
$('#voice-select').val(localStorage.getItem('txy_webRTC_voiceId'));
voiceTestingResult.device = cacheVoiceDevice[0];
} else {
$('#voice-select').val(voiceDevices[0].deviceId);
voiceTestingResult.device = voiceDevices[0];
}
}
/**
* 播放器设备测试
*/
async function startVoiceTesting() {
$('#voice-testing-body').show();
curTestingPageId = 'voice-testing-body';
$('#voice-testing')
.removeClass('icon-gray')
.addClass('icon-blue complete');
completedTestingPageIdList.push('voice-testing-body');
completedTestingPageIdList = [...new Set(completedTestingPageIdList)];
await updateVoiceDeviceList();
}
/**
* 更新/初始化麦克风设备
*/
async function updateMicDeviceList() {
// 展示麦克风设备选择
let micDevices = await TRTC.getMicrophones();
micDevices = micDevices.filter(mic => mic.deviceId !== 'default');
$('#mic-select').empty();
micDevices.forEach(mic => {
let option = $('<option></option>');
option.attr('value', mic.deviceId);
option.html(mic.label);
option.appendTo('#mic-select');
});
// 如果有用户设备选择缓存优先使用缓存的deviceId
let cacheMicDevice = micDevices.filter(
mic => mic.deviceId === localStorage.getItem('txy_webRTC_micId')
);
if (cacheMicDevice.length > 0) {
$('#mic-select').val(localStorage.getItem('txy_webRTC_micId'));
micTestingResult.device = cacheMicDevice[0];
} else {
$('#mic-select').val(micDevices[0].deviceId);
micTestingResult.device = micDevices[0];
}
}
/**
* 麦克风设备测试
*/
async function startMicTesting() {
$('#mic-testing-body').show();
curTestingPageId = 'mic-testing-body';
$('#mic-testing')
.removeClass('icon-gray')
.addClass('icon-blue complete');
completedTestingPageIdList.push('mic-testing-body');
completedTestingPageIdList = [...new Set(completedTestingPageIdList)];
await updateMicDeviceList();
// 展示麦克风的声音大小显示
if ($('#mic-bar-container').children().length === 0) {
for (let index = 0; index < 28; index++) {
$('<div></div>')
.addClass('mic-bar')
.appendTo('#mic-bar-container');
}
}
// 创建本地音频流
await createLocalStream(
{
audio: true,
microphoneId: micTestingResult.device.deviceId,
video: false
},
'audio-container'
);
// 监听音量,并量化显示出来
setInterval(() => {
let volume = localStream.getAudioLevel();
let num = Math.ceil(28 * volume);
$('#mic-bar-container')
.children('.active')
.removeClass('active');
for (let i = 0; i < num; i++) {
$('#mic-bar-container')
.children()
.slice(0, i)
.addClass('active');
}
}, 100);
}
/**
* 系统信息展示
*/
async function startNetworkTesting() {
$('#network-testing-body').show();
$('#testing-report-btn').hide();
curTestingPageId = 'network-testing-body';
$('#network-testing')
.removeClass('icon-gray')
.addClass('icon-blue complete');
completedTestingPageIdList.push('network-testing-body');
completedTestingPageIdList = [...new Set(completedTestingPageIdList)];
networkQualityNum = 0;
$('#uplink-network')
.addClass('network-loading')
.text('');
// 获取系统信息
$('#system').empty();
let OSInfo = getOS();
$('<div></div>')
.text(OSInfo.osName)
.appendTo('#system');
// 获取浏览器及版本信息
$('#browser').empty();
let browser = getBroswer();
$('<div></div>')
.text(`${browser.broswer} ${browser.version}`)
.appendTo('#browser');
// 获取ip地址信息
// $('#ip').empty();
// let IPAddress = await getIPAddress();
// $('<div></div>').text(IPAddress).appendTo('#ip');
// networkTestingResult.IPAddress = IPAddress;
// 是否支持屏幕分享能力
$('#screen-share').empty();
let isScreenShareSupported = TRTC.isScreenShareSupported();
$('<div></div>')
.text(isScreenShareSupported ? '支持' : '不支持')
.appendTo('#screen-share');
// 上下行网络质量
presetting.login(false, async options => {
client = TRTC.createClient({ mode: 'rtc', ...options });
client.on('network-quality', event => {
networkQualityNum++;
// 收到3次'network-quality'事件的时候认为拿到了网络实际质量
if (networkQualityNum === 3) {
networkTestingResult.upLinkNetwork = event.uplinkNetworkQuality;
networkTestingResult.downLinkNetwork = event.downlinkNetworkQuality;
$('#uplink-network')
.removeClass('network-loading')
.text(NETWORK_QUALITY[String(networkTestingResult.upLinkNetwork)]);
$('#testing-report-btn').show();
client && client.leave();
client && client.off('network-quality');
}
});
await client.join({
roomId: options.roomId
});
await createLocalStream(
{
audio: true,
video: false
},
'audio-container'
);
await client.publish(localStream);
// 音频轨道静音
localStream.muteAudio();
});
}
/**
* 展示检测报告
*/
function showTestingReport() {
$('#device-testing').hide();
$('#network-testing-body').hide();
$('#device-testing-report').show();
curTestingPageId = 'device-testing-report';
// 摄像头检测结果
$('#camera-name').text(cameraTestingResult.device.label);
if (cameraTestingResult.statusResult) {
$('#camera-testing-result')
.text('正常')
.css('color', 'green');
} else {
$('#camera-testing-result')
.text('异常')
.css('color', 'red');
}
// 扬声器检测结果(safari浏览器不显示扬声器检测结果)
if (!isSafari) {
$('#voice-name').text(voiceTestingResult.device.label);
if (voiceTestingResult.statusResult) {
$('#voice-testing-result')
.text('正常')
.css('color', 'green');
} else {
$('#voice-testing-result')
.text('异常')
.css('color', 'red');
}
}
// 麦克风检测结果
$('#mic-name').text(micTestingResult.device.label);
if (micTestingResult.statusResult) {
$('#mic-testing-result')
.text('正常')
.css('color', 'green');
} else {
$('#mic-testing-result')
.text('异常')
.css('color', 'red');
}
// 网络检测结果
// $('#network-name').text(networkTestingResult.IPAddress);
$('#network-name').text('网络质量');
$('#network-testing-result')
.html(`${NETWORK_QUALITY[String(networkTestingResult.upLinkNetwork)]}`)
.css('color', `${Number(networkTestingResult.upLinkNetwork) > 3 ? 'red' : 'green'}`);
}
/**
* 结束设备检测,隐藏设备检测弹窗
*/
function finishDeviceTesting() {
$('#device-testing-root').hide();
$('#device-testing').hide();
$(`#${curTestingPageId}`).hide();
curTestingPageId = '';
completedTestingPageIdList = [];
// 停止摄像头/麦克风的流采集并释放摄像头/麦克风设备
localStream && localStream.close();
client && client.leave();
client && client.off('network-quality');
// 停止播放器的音乐
let audioPlayer = document.querySelector('#audio-player');
if (!audioPlayer.paused) {
audioPlayer.pause();
}
audioPlayer.currentTime = 0;
}
/**
* 恢复检测页面头部图标的状态
*/
function initTestingTabTitle() {
['camera', 'voice', 'mic', 'network'].forEach(item => {
$(`#${item}-testing`)
.removeClass('icon-blue complete')
.addClass('icon-gray');
});
}
/**
* 监听设备变化
*/
navigator.mediaDevices.ondevicechange = async function(event) {
// 当前在摄像头检测页
if (curTestingPageId === 'camera-testing-body') {
await updateCameraDeviceList();
return;
}
// 当前在扬声器检测页
if (curTestingPageId === 'voice-testing-body') {
await updateVoiceDeviceList();
return;
}
// 当前在麦克风检测页
if (curTestingPageId === 'mic-testing-body') {
await updateMicDeviceList();
return;
}
};
/**
* 抽离createStream的公共处理函数
*/
async function createLocalStream(constraints, container) {
localStream = TRTC.createStream(constraints);
try {
await localStream.initialize();
} catch (error) {
switch (error.name) {
case 'NotReadableError':
// 当系统或浏览器异常的时候,可能会出现此错误,您可能需要引导用户重启电脑/浏览器来尝试恢复。
alert('暂时无法访问摄像头/麦克风,请确保当前没有其他应用请求访问摄像头/麦克风,并重试');
return;
case 'NotAllowedError':
// 用户拒绝授权访问摄像头或麦克风 | 屏幕分享,您需要引导客户来授权访问
alert('用户已拒绝授权访问摄像头或麦克风');
return;
case 'NotFoundError':
// 找不到摄像头或麦克风设备
alert('找不到摄像头或麦克风设备');
return;
case 'OverConstrainedError':
alert(
'采集属性设置错误,如果您指定了 cameraId/microphoneId请确保它们是一个有效的非空字符串'
);
return;
default:
alert('未知错误');
return;
}
}
container && localStream.play(container);
}

1
public/js/iconfont.js Normal file

File diff suppressed because one or more lines are too long

51
public/js/index.js Normal file
View File

@@ -0,0 +1,51 @@
/* eslint-disable no-global-assign */
/* global $ TRTC Presetting deviceTestingInit cameraId micId */
const presetting = new Presetting();
presetting.init();
deviceTestingInit();
// check if browser is compatible with TRTC
TRTC.checkSystemRequirements().then(result => {
if (!result) {
alert('您的浏览器不兼容此应用!\n建议下载最新版Chrome浏览器');
window.location.href = 'http://www.google.cn/chrome/';
}
});
// setup logging stuffs
TRTC.Logger.setLogLevel(TRTC.Logger.LogLevel.DEBUG);
TRTC.Logger.enableUploadLog();
TRTC.getDevices()
.then(devices => {
devices.forEach(item => {
console.log('device: ' + item.kind + ' ' + item.label + ' ' + item.deviceId);
});
})
.catch(error => console.error('getDevices error observed ' + error));
// populate camera options
TRTC.getCameras().then(devices => {
devices.forEach(device => {
if (!cameraId) {
cameraId = device.deviceId;
}
let div = $('<div></div>');
div.attr('id', device.deviceId);
div.html(device.label);
div.appendTo('#camera-option');
});
});
// populate microphone options
TRTC.getMicrophones().then(devices => {
devices.forEach(device => {
if (!micId) {
micId = device.deviceId;
}
let div = $('<div></div>');
div.attr('id', device.deviceId);
div.html(device.label);
div.appendTo('#mic-option');
});
});

4
public/js/jquery-3.2.1.min.js vendored Normal file

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

2442
public/js/popper.js Normal file

File diff suppressed because it is too large Load Diff

51
public/js/presetting.js Normal file
View File

@@ -0,0 +1,51 @@
/* global $ setBtnClickFuc genTestUserSig */
// preset before starting RTC
class Presetting {
init() {
// populate userId/roomId
$('#userId').val('user_30400097');
$('#roomId').val(parseInt(Math.random() * 100000));
const roomId = this.query('roomId');
const userId = this.query('userId');
if (roomId) {
$('#roomId').val(roomId);
}
if (userId) {
$('#userId').val(userId);
}
$('#main-video-btns').hide();
$('.mask').hide();
setBtnClickFuc();
}
query(name) {
const match = window.location.search.match(new RegExp('(\\?|&)' + name + '=([^&]*)(&|$)'));
return !match ? '' : decodeURIComponent(match[2]);
}
login(share, callback) {
let userId = window.mid;
if (share) {
userId = 'share_' + userId;
}
console.log(userId)
let sdkAppId;
let userSig;
let roomId = window.roomid;
genTestUserSig(userId).then((res)=>{
sdkAppId = res.id;
userSig = res.data
callback({
sdkAppId,
userId,
userSig,
roomId
});
})
}
}

341
public/js/rtc-client.js Normal file
View File

@@ -0,0 +1,341 @@
/* global $ TRTC getCameraId getMicrophoneId resetView isHidden shareUserId addMemberView removeView addVideoView */
class RtcClient {
constructor(options) {
this.sdkAppId_ = options.sdkAppId;
this.userId_ = options.userId;
this.userSig_ = options.userSig;
this.roomId_ = options.roomId;
this.isJoined_ = false;
this.isPublished_ = false;
this.isAudioMuted = false;
this.isVideoMuted = false;
this.localStream_ = null;
this.remoteStreams_ = [];
this.members_ = new Map();
// create a client for RtcClient
this.client_ = TRTC.createClient({
mode: 'rtc',
sdkAppId: this.sdkAppId_,
userId: this.userId_,
userSig: this.userSig_
});
this.handleEvents();
}
async join() {
if (this.isJoined_) {
console.warn('duplicate RtcClient.join() observed');
return;
}
try {
// join the room
await this.client_.join({
roomId: this.roomId_
});
console.log('join room success');
this.isJoined_ = true;
// create a local stream with audio/video from microphone/camera
if (getCameraId() && getMicrophoneId()) {
this.localStream_ = TRTC.createStream({
audio: true,
video: true,
userId: this.userId_,
cameraId: getCameraId(),
microphoneId: getMicrophoneId(),
mirror: true
});
} else {
// not to specify cameraId/microphoneId to avoid OverConstrainedError
this.localStream_ = TRTC.createStream({
audio: true,
video: true,
userId: this.userId_,
mirror: true
});
}
try {
// initialize the local stream and the stream will be populated with audio/video
await this.localStream_.initialize();
console.log('initialize local stream success');
this.localStream_.on('player-state-changed', event => {
console.log(`local stream ${event.type} player is ${event.state}`);
});
// publish the local stream
await this.publish();
this.localStream_.play('main-video');
$('#main-video-btns').show();
$('#mask_main').appendTo($('#player_' + this.localStream_.getId()));
} catch (e) {
console.error('failed to initialize local stream - ' + e);
}
} catch (e) {
console.error('join room failed! ' + e);
}
//更新成员状态
let states = this.client_.getRemoteMutedState();
for (let state of states) {
if (state.audioMuted) {
$('#' + state.userId)
.find('.member-audio-btn')
.attr('src', './img/mic-off.png');
}
if (state.videoMuted) {
$('#' + state.userId)
.find('.member-video-btn')
.attr('src', './img/camera-off.png');
$('#mask_' + this.members_.get(state.userId).getId()).show();
}
}
}
async leave() {
if (!this.isJoined_) {
console.warn('leave() - please join() firstly');
return;
}
// ensure the local stream is unpublished before leaving.
await this.unpublish();
// leave the room
await this.client_.leave();
this.localStream_.stop();
this.localStream_.close();
this.localStream_ = null;
this.isJoined_ = false;
resetView();
}
async publish() {
if (!this.isJoined_) {
console.warn('publish() - please join() firstly');
return;
}
if (this.isPublished_) {
console.warn('duplicate RtcClient.publish() observed');
return;
}
try {
await this.client_.publish(this.localStream_);
} catch (e) {
console.error('failed to publish local stream ' + e);
this.isPublished_ = false;
}
this.isPublished_ = true;
}
async unpublish() {
if (!this.isJoined_) {
console.warn('unpublish() - please join() firstly');
return;
}
if (!this.isPublished_) {
console.warn('RtcClient.unpublish() called but not published yet');
return;
}
await this.client_.unpublish(this.localStream_);
this.isPublished_ = false;
}
muteLocalAudio() {
this.localStream_.muteAudio();
}
unmuteLocalAudio() {
this.localStream_.unmuteAudio();
}
muteLocalVideo() {
this.localStream_.muteVideo();
}
unmuteLocalVideo() {
this.localStream_.unmuteVideo();
}
resumeStreams() {
this.localStream_.resume();
for (let stream of this.remoteStreams_) {
stream.resume();
}
}
handleEvents() {
this.client_.on('error', err => {
console.error(err);
alert(err);
location.reload();
});
this.client_.on('client-banned', err => {
console.error('client has been banned for ' + err);
if (!isHidden()) {
alert('您已被踢出房间');
location.reload();
} else {
document.addEventListener(
'visibilitychange',
() => {
if (!isHidden()) {
alert('您已被踢出房间');
location.reload();
}
},
false
);
}
});
// fired when a remote peer is joining the room
this.client_.on('peer-join', evt => {
const userId = evt.userId;
console.log('peer-join ' + userId);
if (userId !== shareUserId) {
addMemberView(userId);
}
});
// fired when a remote peer is leaving the room
this.client_.on('peer-leave', evt => {
const userId = evt.userId;
removeView(userId);
console.log('peer-leave ' + userId);
});
// fired when a remote stream is added
this.client_.on('stream-added', evt => {
const remoteStream = evt.stream;
const id = remoteStream.getId();
const userId = remoteStream.getUserId();
this.members_.set(userId, remoteStream);
console.log(`remote stream added: [${userId}] ID: ${id} type: ${remoteStream.getType()}`);
if (remoteStream.getUserId() === shareUserId) {
// don't need screen shared by us
this.client_.unsubscribe(remoteStream);
} else {
console.log('subscribe to this remote stream');
this.client_.subscribe(remoteStream);
}
});
// fired when a remote stream has been subscribed
this.client_.on('stream-subscribed', evt => {
const uid = evt.userId;
const remoteStream = evt.stream;
const id = remoteStream.getId();
this.remoteStreams_.push(remoteStream);
remoteStream.on('player-state-changed', event => {
console.log(`${event.type} player is ${event.state}`);
if (event.type == 'video' && event.state == 'STOPPED') {
$('#mask_' + remoteStream.getId()).show();
$('#' + remoteStream.getUserId())
.find('.member-video-btn')
.attr('src', 'img/camera-off.png');
}
if (event.type == 'video' && event.state == 'PLAYING') {
$('#mask_' + remoteStream.getId()).hide();
$('#' + remoteStream.getUserId())
.find('.member-video-btn')
.attr('src', 'img/camera-on.png');
}
});
addVideoView(id);
// objectFit 为播放的填充模式详细参考https://trtc-1252463788.file.myqcloud.com/web/docs/Stream.html#play
remoteStream.play(id, { objectFit: 'contain' });
//添加“摄像头未打开”遮罩
let mask = $('#mask_main').clone();
mask.attr('id', 'mask_' + id);
mask.appendTo($('#player_' + id));
mask.hide();
if (!remoteStream.hasVideo()) {
mask.show();
$('#' + remoteStream.getUserId())
.find('.member-video-btn')
.attr('src', 'img/camera-off.png');
}
console.log('stream-subscribed ID: ', id);
});
// fired when the remote stream is removed, e.g. the remote user called Client.unpublish()
this.client_.on('stream-removed', evt => {
const remoteStream = evt.stream;
const id = remoteStream.getId();
remoteStream.stop();
this.remoteStreams_ = this.remoteStreams_.filter(stream => {
return stream.getId() !== id;
});
removeView(id);
console.log(`stream-removed ID: ${id} type: ${remoteStream.getType()}`);
});
this.client_.on('stream-updated', evt => {
const remoteStream = evt.stream;
let uid = this.getUidByStreamId(remoteStream.getId());
if (!remoteStream.hasVideo()) {
$('#' + uid)
.find('.member-video-btn')
.attr('src', 'img/camera-off.png');
}
console.log(
'type: ' +
remoteStream.getType() +
' stream-updated hasAudio: ' +
remoteStream.hasAudio() +
' hasVideo: ' +
remoteStream.hasVideo() +
' uid: ' +
uid
);
});
this.client_.on('mute-audio', evt => {
console.log(evt.userId + ' mute audio');
$('#' + evt.userId)
.find('.member-audio-btn')
.attr('src', 'img/mic-off.png');
});
this.client_.on('unmute-audio', evt => {
console.log(evt.userId + ' unmute audio');
$('#' + evt.userId)
.find('.member-audio-btn')
.attr('src', 'img/mic-on.png');
});
this.client_.on('mute-video', evt => {
console.log(evt.userId + ' mute video');
$('#' + evt.userId)
.find('.member-video-btn')
.attr('src', 'img/camera-off.png');
let streamId = this.members_.get(evt.userId).getId();
if (streamId) {
$('#mask_' + streamId).show();
}
});
this.client_.on('unmute-video', evt => {
console.log(evt.userId + ' unmute video');
$('#' + evt.userId)
.find('.member-video-btn')
.attr('src', 'img/camera-on.png');
const stream = this.members_.get(evt.userId);
if (stream) {
let streamId = stream.getId();
if (streamId) {
$('#mask_' + streamId).hide();
}
}
});
}
showStreamState(stream) {
console.log('has audio: ' + stream.hasAudio() + ' has video: ' + stream.hasVideo());
}
getUidByStreamId(streamId) {
for (let [uid, stream] of this.members_) {
if (stream.getId() == streamId) {
return uid;
}
}
}
}

163
public/js/share-client.js Normal file
View File

@@ -0,0 +1,163 @@
/* global $ TRTC */
class ShareClient {
constructor(options) {
this.sdkAppId_ = options.sdkAppId;
this.userId_ = options.userId;
this.userSig_ = options.userSig;
this.roomId_ = options.roomId;
this.isJoined_ = false;
this.isPublished_ = false;
this.localStream_ = null;
this.client_ = TRTC.createClient({
mode: 'rtc',
sdkAppId: this.sdkAppId_,
userId: this.userId_,
userSig: this.userSig_,
/**
* disable receivers to avoid receiving remote streams as we only want to
* publish the screen stream
*/
disableReceiver: true
});
this.client_.setDefaultMuteRemoteStreams(true);
this.handleEvents();
}
async join() {
if (this.isJoined_) {
console.warn('duplicate RtcClient.join() observed');
return;
}
try {
await this.client_.join({
roomId: this.roomId_
});
console.log('ShareClient join room success');
this.isJoined_ = true;
// create a local stream for screen share
this.localStream_ = TRTC.createStream({
// disable audio as RtcClient already enable audio
audio: false,
// enable screen share
screen: true,
userId: this.userId_
});
try {
// initialize the local stream to populate the screen stream
await this.localStream_.initialize();
console.log('ShareClient initialize local stream for screen share success');
this.localStream_.on('player-state-changed', event => {
console.log(`local stream ${event.type} player is ${event.state}`);
});
this.localStream_.on('screen-sharing-stopped', event => {
console.log('share stream video track enned');
this.leave();
$('#screen-btn').attr('src', './img/screen-off.png');
});
// publish the screen share stream
await this.client_.publish(this.localStream_);
} catch (e) {
console.error('ShareClient failed to initialize local stream - ' + e);
//用户取消分享屏幕导致推流失败
await this.client_.leave();
this.isJoined_ = false;
$('#screen-btn').attr('src', 'img/screen-off.png');
}
} catch (e) {
console.error('ShareClient join room failed! ' + e);
}
}
async leave() {
if (!this.isJoined_) {
console.warn('leave() - please join() firstly');
return;
}
if (this.isPublished_) {
await this.client_.unpublish(this.localStream_);
this.isPublished_ = false;
}
await this.client_.leave();
if (this.localStream_) {
this.localStream_.close();
this.localStream_ = null;
}
this.isJoined_ = false;
}
handleEvents() {
this.client_.on('error', err => {
console.error(err);
alert(err);
});
this.client_.on('client-banned', err => {
console.error('client has been banned for ' + err);
});
// fired when a remote peer is joining the room
this.client_.on('peer-join', evt => {
const userId = evt.userId;
console.log('peer-join ' + userId);
});
// fired when a remote peer is leaving the room
this.client_.on('peer-leave', evt => {
const userId = evt.userId;
console.log('peer-leave ' + userId);
});
// fired when a remote stream is added
this.client_.on('stream-added', evt => {
const remoteStream = evt.stream;
const id = remoteStream.getId();
const userId = remoteStream.getUserId();
console.log(`remote stream added: [${userId}] ID: ${id} type: ${remoteStream.getType()}`);
console.log('subscribe to this remote stream');
});
// fired when a remote stream has been subscribed
this.client_.on('stream-subscribed', evt => {
const remoteStream = evt.stream;
const id = remoteStream.getId();
remoteStream.on('player-state-changed', event => {
console.log(`${event.type} player is ${event.state}`);
});
console.log('stream-subscribed ID: ', id);
});
// fired when the remote stream is removed, e.g. the remote user called Client.unpublish()
this.client_.on('stream-removed', evt => {
const remoteStream = evt.stream;
const id = remoteStream.getId();
console.log(`stream-removed ID: ${id} type: ${remoteStream.getType()}`);
});
this.client_.on('stream-updated', evt => {
const remoteStream = evt.stream;
console.log(
'type: ' +
remoteStream.getType() +
' stream-updated hasAudio: ' +
remoteStream.hasAudio() +
' hasVideo: ' +
remoteStream.hasVideo() +
' uid: ' +
remoteStream.getUserId()
);
});
this.client_.on('mute-audio', evt => {
console.log(evt.userId + ' mute audio');
});
this.client_.on('unmute-audio', evt => {
console.log(evt.userId + ' unmute audio');
});
this.client_.on('mute-video', evt => {
console.log(evt.userId + ' mute video');
});
this.client_.on('unmute-video', evt => {
console.log(evt.userId + ' unmute video');
});
}
}

1
public/js/trtc.js Normal file

File diff suppressed because one or more lines are too long

357
public/zhibo.html Normal file
View File

@@ -0,0 +1,357 @@
<!DOCTYPE html>
<html>
<head>
<title>TRTC实时音视频通话</title>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=0.7, user-scalable=no, shrink-to-fit=no">
<link rel="stylesheet" href="./css/bootstrap-material-design.min.css">
<link rel="stylesheet" href="./css/index.css">
<link rel="stylesheet" href="./css/room.css">
</head>
<body>
<div id="root">
<!-- 设备检测界面弹窗 -->
<div id="device-testing-root" style="display: none;">
<!-- 设备检测卡片 -->
<div class="device-testing-card">
<!-- 设备检测准备界面 -->
<div id="device-testing-prepare" class="device-testing-prepare">
<div class="testing-title">设备连接</div>
<div class="testing-prepare-info">设备检测前请务必给当前页面开放摄像头,麦克风权限哦~</div>
<div class="device-display">
<div id="device-camera" class="device icon-normal">
<svg class="icon" aria-hidden="true">
<use xlink:href="#icon-shiping-xue"></use>
</svg>
</div>
<div id="device-voice" class="device icon-normal">
<svg class="icon" aria-hidden="true">
<use xlink:href="#icon-shengyin"></use>
</svg>
</div>
<div id="device-mic" class="device icon-normal">
<svg class="icon" aria-hidden="true">
<use xlink:href="#icon-maikefeng-xue"></use>
</svg>
</div>
<div id="device-network" class="device icon-normal">
<svg class="icon" aria-hidden="true">
<use xlink:href="#icon-wangluo"></use>
</svg>
</div>
</div>
<div id="device-loading" class="loading-background">
<div class="device-loading"></div>
</div>
<!-- 连接结果提示 -->
<div class="connect-info">
<!-- 连接结果 -->
<div id="connect-info"></div>
<!-- 错误icon及错误解决指引 -->
<div id="connect-attention-container" class="connect-attention-container" style="display: none;">
<div id="connect-attention-icon" class="connect-attention-icon">
<svg class="icon" aria-hidden="true">
<use xlink:href="#icon-warn"></use>
</svg>
</div>
<div id="connect-attention-info" class="connect-attention-info" style="display: none;">
</div>
</div>
</div>
<!-- 设备连接页面button -->
<div class="testing-btn-display">
<div id="start-test-btn" class="test-btn start-test start-gray">开始检测</div>
<div id="connect-again-btn" class="test-btn connect-again" style="display: none;">重新连接</div>
</div>
</div>
<!-- 设备检测tab页 -->
<div id="device-testing" class="device-testing" style="display: none;">
<div class="device-testing-title">
<div id="camera-testing" class="testing icon-gray">
<svg class="icon" aria-hidden="true">
<use xlink:href="#icon-shiping-xue"></use>
</svg>
</div>
<div id="voice-testing" class="testing icon-gray">
<svg class="icon" aria-hidden="true">
<use xlink:href="#icon-shengyin"></use>
</svg>
</div>
<div id="mic-testing" class="testing icon-gray">
<svg class="icon" aria-hidden="true">
<use xlink:href="#icon-maikefeng-xue"></use>
</svg>
</div>
<div id="network-testing" class="testing icon-gray">
<svg class="icon" aria-hidden="true">
<use xlink:href="#icon-wangluo"></use>
</svg>
</div>
</div>
<!-- 设备检测-摄像头检测 -->
<div id="camera-testing-body" class="testing-body" style="display: none;">
<div class="device-list camera-device-list">
<div class="select-title" style="display: block;">摄像头选择</div>
<div class="select-list" style="display: block;">
<select name="select" id="camera-select" class="device-select"></select>
</div>
</div>
<div id="camera-video" class="camera-video"></div>
<div class="testing-info-container">
<div class="testing-info">是否可以清楚的看到自己?</div>
<div class="button-list">
<div id="camera-fail" class="fail-button">看不到</div>
<div id="camera-success" class="success-button">可以看到</div>
</div>
</div>
</div>
<!-- 设备检测-播放器检测 -->
<div id="voice-testing-body" class="testing-body" style="display: none;">
<div class="device-list camera-device-list">
<div class="select-title" style="display: block;">扬声器选择</div>
<div class="select-list" style="display: block;">
<select name="select" id="voice-select" class="device-select"></select>
</div>
</div>
<div class="audio-control">
<div class="audio-control-info">请调高设备音量, 点击播放下面的音频试试~</div>
<audio id="audio-player" src="https://trtc-1252463788.cos.ap-guangzhou.myqcloud.com/web/assets/bgm-test.mp3" controls></audio>
</div>
<div class="testing-info-container">
<div class="testing-info">是否可以听到声音?</div>
<div class="button-list">
<div id="voice-fail" class="fail-button">听不到</div>
<div id="voice-success" class="success-button">可以听到</div>
</div>
</div>
</div>
<!-- 设备检测-麦克风检测 -->
<div id="mic-testing-body" class="testing-body" style="display: none;">
<div class="device-list camera-device-list">
<div class="select-title" style="display: block;">麦克风选择</div>
<div class="select-list" style="display: block;">
<select name="select" id="mic-select" class="device-select"></select>
</div>
</div>
<div class="mic-testing-container">
<div class="mic-testing-info">对着麦克风说'哈喽'试试~</div>
<div id="mic-bar-container" class="mic-bar-container"></div>
<div id="audio-container"></div>
</div>
<div class="testing-info-container">
<div class="testing-info">是否可以看到音量图标跳动?</div>
<div class="button-list">
<div id="mic-fail" class="fail-button">看不到</div>
<div id="mic-success" class="success-button">可以看到</div>
</div>
</div>
</div>
<!-- 设备检测-硬件及网速检测 -->
<div id="network-testing-body" class="testing-body" style="display: none;">
<div class="testing-index-list">
<div class="testing-index-group">
<div class="testing-index">操作系统</div>
<div id="system"></div>
</div>
<div class="testing-index-group">
<div class="testing-index">浏览器版本</div>
<div id="browser"></div>
</div>
<!-- <div class="testing-index-group">
<div class="testing-index">IP地址</div>
<div id="ip"></div>
</div> -->
<div class="testing-index-group">
<div class="testing-index">屏幕共享能力</div>
<div id="screen-share"></div>
</div>
<div class="testing-index-group">
<div class="testing-index">网络质量</div>
<div id="uplink-network" class="network-loading"></div>
</div>
</div>
<div class="testing-footer">
<div id="testing-report-btn" class="test-btn">查看检测报告</div>
</div>
</div>
</div>
<!-- 设备检测报告 -->
<div id="device-testing-report" class="device-testing-report" style="display: none;">
<div class="testing-title">检测报告</div>
<!-- 检测报告内容 -->
<div class="device-report-list">
<!-- 摄像头报告信息 -->
<div class="device-report camera-report">
<div class="device-info">
<div class="report-icon">
<svg class="icon" aria-hidden="true">
<use xlink:href="#icon-shiping-xue"></use>
</svg>
</div>
<div id="camera-name" class="device-name"></div>
</div>
<div id="camera-testing-result" class="camera-testing-result"></div>
</div>
<!-- 扬声器报告信息 -->
<div id="voice-report" class="device-report voice-report">
<div class="device-info">
<div class="report-icon">
<svg class="icon" aria-hidden="true">
<use xlink:href="#icon-shengyin"></use>
</svg>
</div>
<div id="voice-name" class="device-name"></div>
</div>
<div id="voice-testing-result" class="voice-testing-result"></div>
</div>
<!-- 麦克风报告信息 -->
<div class="device-report mic-report">
<div class="device-info">
<div class="report-icon">
<svg class="icon" aria-hidden="true">
<use xlink:href="#icon-maikefeng-xue"></use>
</svg>
</div>
<div id="mic-name" class="device-name"></div>
</div>
<div id="mic-testing-result" class="mic-testing-result"></div>
</div>
<!-- 网络报告信息 -->
<div class="device-report network-report">
<div class="device-info">
<div class="report-icon">
<svg class="icon" aria-hidden="true">
<use xlink:href="#icon-wangluo"></use>
</svg>
</div>
<div id="network-name" class="device-name"></div>
</div>
<div id="network-testing-result" class="network-testing-result"></div>
</div>
</div>
<div class="device-report-footer">
<div id="testing-again" class="device-report-btn testing-agin">重新检测</div>
<div id="testing-finish" class="device-report-btn testing-finish">完成检测</div>
</div>
</div>
<!-- 设备检测关闭按钮 -->
<div id="device-testing-close-btn" class="device-testing-close-btn">
<svg class="icon" aria-hidden="true">
<use xlink:href="#icon-baseline-close-px"></use>
</svg>
</div>
</div>
</div>
<!-- 聊天室页面 -->
<div id="room-root" class="col-div">
<!-- header -->
<div class="row-div card" style="width: 100%; height: 65px; justify-content: space-between;border-radius: 17px;">
<!-- 腾讯云logo -->
<div class="row-div" style="height: 100%; width: 230px; justify-content: center">
<div style="width: 86px; height: 23px; font-size: 18px; color: #333333">直播页面</div>
</div>
<!-- 分享屏幕 退出 按钮 -->
<div class="row-div" style="height: 100%; width: auto;margin-right: 28px;">
<img id="screen-btn" style="width: 45px; height: 45px" src="./img/screen-off.png" alt="">
<div style="width: 20px"></div>
<img id="logout-btn" style="width: 45px; height: 45px" src="./img/logout.png" alt="">
</div>
</div>
<!-- content -->
<div class="row-div" style="height: 100%; width: 100%; padding: 10px">
<div class="col-div" style="width: 340px; height: 100%; padding: 10px">
<div class="col-div card" style="width: 100%; height: 100%; padding: 23px">
<!-- 成员列表 -->
<div style="width: 100%;">上课人员</div>
<div id="member-list" class="col-div" style="width: 100%; justify-content: flex-start; flex: 1">
<!-- member -->
<!-- <div id="member-me" style="width: 100%; padding-left: 20px">
<div class="row-div member"
style="width: 100%; height: 50px; justify-content: space-between">
<div class="member-id">(我)</div>
<div class="row-div" style="width:100px; height: 27px; justify-content: center">
<img class="member-video-btn" style="height: 100%" src="./img/camera-on.png"
alt="">
<div style="width: 18px"></div>
<img class="member-audio-btn" style="height: 100%" src="./img/mic-on.png"
alt="">
</div>
</div>
</div> -->
</div>
</div>
</div>
<!-- 视频网格 -->
<div id="video-grid" style="height: 100%; flex: 1">
<!-- 主视频 -->
<div id="main-video" class="video-box col-div" style="justify-content: flex-end">
<!-- 主视频控制按钮 -->
<div id="main-video-btns" class="row-div"
style="width: 156px; position: absolute; z-index: 10; justify-content: center; align-self: flex-end">
<img id="video-btn" style="width: 68px; height: 68px" onClick="event.cancelBubble = true"
src="./img/big-camera-on.png" alt="" title="关闭摄像头">
<img id="mic-btn" style="width: 68px; height: 68px" onClick="event.cancelBubble = true"
src="./img/big-mic-on.png" alt="" title="关闭麦克风">
</div>
<div id="mask_main" class="mask col-div">
<!-- “摄像头未开启”遮罩 -->
<div style="height: 100%; width: 100%; position: absolute; background-color: #D8D8D8"></div>
<img style="width: 63px; height: 69px; z-index: 10;" src="./img/camera-max.png" alt="">
<div style="height: 10px"></div>
<div style="z-index: 10">摄像头未打开</div>
</div>
</div>
<!-- 小视频 -->
</div>
</div>
</div>
</div>
<script>
function GetRequest() {
var url = location.search; //获取url中"?"符后的字串
var theRequest = new Object();
if(url.indexOf("?") != -1) {
var str = url.substr(1);
strs = str.split("&");
for(var i = 0; i < strs.length; i++) {
theRequest[strs[i].split("=")[0]] = unescape(strs[i].split("=")[1]);
}
}
return theRequest;
};
window.url = 'https://case.sy-my.net/beelink/public/home/'
const urldata = GetRequest('roomid')
window.roomid = urldata.roomid
window.mid = urldata.memberid
</script>
<script src="./js/jquery-3.2.1.min.js"></script>
<script src="./js/popper.js"></script>
<script src="./js/bootstrap-material-design.js"></script>
<script>
$(document).ready(function () {
$('body').bootstrapMaterialDesign();
});
</script>
<script src="./js/lib-generate-test-usersig.min.js"></script>
<script src="./js/debug/GenerateTestUserSig.js"></script>
<script src="./js/iconfont.js"></script>
<script src="./js/trtc.js"></script>
<script src="./js/common.js"></script>
<script src="./js/rtc-client.js"></script>
<script src="./js/share-client.js"></script>
<script src="./js/presetting.js"></script>
<script src="./js/device-testing.js"></script>
<script src="./js/index.js"></script>
</body>
</html>

View File

@@ -16,21 +16,33 @@ import { provideI18n } from "@/utils/i18n"
import i18ninit from "@/i18n/init"
import enUS from 'ant-design-vue/es/locale/en_US';
import zhCN from 'ant-design-vue/es/locale/zh_CN';
import dayjs from 'dayjs';
export default defineComponent({
setup(){
console.log(i18ninit)
const len = provideI18n(i18ninit);
// len.locale.value = !getValue("Lanvuage") ? 'zh' : getValue("Lanvuage");
if(getValue('token')){
store.commit("login", true)
store.dispatch("setUserInfo");
}else{
console.log('ip')
store.dispatch("getip");
store.commit('setWlan')
router.push("/")
}
const zh = zhCN
const en = enUS
/* eslint-disable */
const utc = require('dayjs/plugin/utc') // dependent on utc plugin
/* eslint-disable */
const timezone = require('dayjs/plugin/timezone')
const days: any = dayjs;
dayjs.extend(utc)
dayjs.extend(timezone)
console.log(days.tz.guess())
return{
zh,
en,

View File

@@ -14,6 +14,25 @@ export interface Get {
let login: MessageType;
let count = 0;
const div: any = document.getElementById("make");
export function countadd(){
if(count == 0){
login = message.loading('加载中..', 0)
div.style.display = "block"
}
count++;
}
export function countdel(){
if(count != 0){
setTimeout(()=>{
count--;
if(count == 0){
login();
div.style.display = "none"
}
console.log(count)
}, 1000)
}
}
axios.interceptors.request.use((config)=>{
if(count == 0){
login = message.loading('加载中..', 0)
@@ -80,11 +99,15 @@ const put: Get = async function (url: string, data?: unknown){
function setToken(){
axios.defaults.headers.common['Authorization'] = "Bearer " + getValue("token");
}
function setLanvuage(yuyan: string){
axios.defaults.headers.common['Language'] = yuyan;
}
export {
get,
post,
del,
put,
setToken
setToken,
setLanvuage
}

View File

@@ -1,8 +1,9 @@
import router from '@/router';
import store from '@/store';
import { LiveList, LivelistInfo, LoginData, UserInfo } from '@/types';
import { saveValue } from '@/utils/common';
import { getValue, saveValue } from '@/utils/common';
import { message } from 'ant-design-vue';
import dayjs from 'dayjs';
import { del, get, post, put, setToken } from './base'
@@ -31,8 +32,9 @@ export async function loginpass(phone: string, password: string,type?: number,sm
}else{
setToken();
store.commit("login", true);
store.dispatch("setUserInfo");
router.push("/mine/archives")
await store.dispatch("setUserInfo");
await router.push("/mine/archives")
location.reload();
}
}
}
@@ -44,7 +46,9 @@ export async function userinfo(){
const user = await get<UserInfo>('personalInfo');
// console.log(user.data.img)
if(user.code == 1001){
saveValue("token","")
router.push("/")
return '未登录';
}
return user.data;
@@ -204,6 +208,7 @@ export async function setvideo(data?: any) {
const res=await put<Liveaddrule>('video/'+data.id,data)
if(res.code==0){
message.success(res.msg)
router.push("/regime/video")
}else{
message.error(res.msg)
}
@@ -228,6 +233,8 @@ interface VideoDetail{
updated_at: string;
share: number;
watch: number;
score: string;
statusdesc: string;
}
export async function videodetail(data?: any,ifupdate?: number) {
@@ -258,7 +265,9 @@ export async function videodetail(data?: any,ifupdate?: number) {
createdAt: res.data.created_at,
updatedAt: res.data.updated_at,
watch: res.data.watch,
share: res.data.share
share: res.data.share,
score: res.data.score,
statusdesc: res.data.statusdesc
}
}
@@ -286,6 +295,7 @@ export async function accountadd(data?: any) {
const res = await post<Liveaddrule>('wallect',data);
if(res.code==0){
message.success("新增成功")
router.push("/mine/wallet")
}
console.log(res)
@@ -337,10 +347,11 @@ export async function cashout(data?: any,accountinfo?: any){
console.log(data,'tixian')
console.log(accountinfo)
data.account=accountinfo.account
data.bankcode=accountinfo.bankcode
data.bankname=accountinfo.bankname
data.mname=accountinfo.mname
// data.account=accountinfo.account
// data.bankcode=accountinfo.bankcode
// data.bankname=accountinfo.bankname
// data.mname=accountinfo.mname
data.wallectid=accountinfo.wallectid
console.log(data)
const res = await post<Liveaddrule>('withdrawal',data);
if(res.code==0){
@@ -379,8 +390,8 @@ export async function getaccountinfo(data?: any){
/**
* 账户编辑
*/
export async function editaccount(data?: any){
const res=await put<Liveaddrule>('wallect/'+data);
export async function editaccount( id: any,data?: any){
const res=await put<Liveaddrule>('wallect/' +id ,data);
if(res.code==0){
message.success("修改成功")
}
@@ -393,6 +404,7 @@ export async function deleteaccount(data: any) {
const res = await del<Liveaddrule>('wallect/' + data);
if(res.code==0){
message.success("删除成功")
router.push("/mine/wallet")
}
console.log(res)
@@ -427,18 +439,20 @@ export async function transactioninfo(data?: any){
export async function editpassword(data?: any): Promise<any> {
console.log(data,111)
const newdata={
memberid:0,
password: "",
password:"",
newpassword: "",
topassword: ""
}
newdata.memberid=data.memberid
newdata.password=data.password
newdata.topassword=data.repassword
newdata.password = data.original
newdata.newpassword=data.password
newdata.topassword=data.topassword
console.log(newdata)
const res = await post<Liveaddrule>('resetPassword',newdata)
const res = await put<Liveaddrule>('/member/' + data.memberid,newdata)
if(res.code==0){
message.success("修改成功")
}else{
message.error(res.msg)
}
return res
}
@@ -632,9 +646,9 @@ export async function editsystemsetting(e?: any): Promise<boolean> {
const res = await put(`member/${store.state.userinfo.memberid}`,newdata);
console.log(res)
if(res.code == 0){
location.reload();
message.success("修改成功")
store.dispatch("setUserInfo");
location.reload();
return true;
}else{
message.error(res.msg);
@@ -669,8 +683,9 @@ interface SendSms{
msg: string;
}
export async function sendsms(phone: string, type: number): Promise<boolean>{
const res = await post<SendSms>("SendSms", {phone, type});
export async function sendsms(code: string ,phone: string): Promise<boolean>{
const type = code == '86' ? 0 : 1;
const res = await post<SendSms>("SendSms", {phone: code + phone, type});
console.log(res);
if(res.code == 0){
message.success(res.msg);
@@ -755,7 +770,8 @@ export async function putmember(data: any): Promise<any>{
language: data.languageValue,
tlanguage: data.tlanguageValue,
video: data.video,
desc: data.desc
desc: data.desc,
videoid: data.videoid
}
console.log(newdata)
const res = await put<Liveaddrule>(`member/${store.state.userinfo.memberid}`, newdata)
@@ -787,6 +803,7 @@ interface LiveInfo {
deleted_at: null;
created_at: string;
updated_at: string;
livestatus: number;
}
interface StudentList {
@@ -839,8 +856,8 @@ export async function cancellive(id: number, status: number){
/**
* 修改手机号
*/
export async function changetel(e: string) {
const res = await put(`member/${store.state.userinfo.memberid}`,{mobile:e});
export async function changetel(code: string,e: string) {
const res = await put(`member/${store.state.userinfo.memberid}`,{code: code, mobile:e});
console.log(res)
if(res.code == 0){
message.success("修改成功")
@@ -869,6 +886,14 @@ export async function checksmscode(phone: string, smscode: string){
export async function register(data: any){
/* eslint-disable */
const utc = require('dayjs/plugin/utc') // dependent on utc plugin
/* eslint-disable */
const timezone = require('dayjs/plugin/timezone')
const days: any = dayjs;
dayjs.extend(utc)
dayjs.extend(timezone)
const res = await post<any>("register",{
mobile: data.phone,
code: data.quhao,
@@ -877,7 +902,10 @@ export async function register(data: any){
name: data.name,
email: data.emil,
mtongue: data.muyu,
tlanguage: data.jiaoshou
tlanguage: data.jiaoshou,
language: getValue("Lanvuage") || 'zh',
zoneid: days.tz.guess()
})
if(res.code == 0){
message.success(res.msg)
@@ -938,13 +966,22 @@ export async function liveinfo(id: number): Promise<any>{
dateline: liveinfo.dateline,
livetime: liveinfo.livetime,
livenumber: liveinfo.livenumber,
desc: liveinfo.desc
desc: liveinfo.desc,
status: liveinfo.status
}
}
export async function setlive(data: any){
// data.status = data.livestatus;
console.log(data)
const info = await put("live/" + data.id, data)
console.log(info.data)
if(info.code==0){
message.success(info.msg)
router.push("/regime/live")
}else{
message.error(info.msg)
}
}
@@ -996,11 +1033,26 @@ export async function luzhi(roomid: string){
export async function getaddr() {
const res = await get<any>('ip');
const gj = res.data.address.split("|")[0];
if(gj == "CN"){
return ["zh", "中文", '人民币¥'];
}else {
return ['en', 'English', '美元$']
const lan = getValue("Lanvuage");
const qh = await get<any>('countryCode', {
longitude: res.data.content.point.x,
latitude: res.data.content.point.y
});
console.log(qh)
if(lan != null && lan){
if(gj == "CN"){
return {hb: '人民币¥', qh};
}else {
return {hb: '美元$'}
}
}else{
if(gj == "CN"){
return {yy: "zh", yyx: "中文", hb: '人民币¥', qh};
}else {
return {yy: 'en', yyx: 'English', hb: '美元$', qh}
}
}
}
// export async function StopMCUMixTranscode(roomid: number) {
@@ -1013,3 +1065,17 @@ export async function interests() {
return res.data;
}
export async function getset() {
const res = await get('getset');
return res.data;
}
export async function setheadimg(src: string){
const res = await put(`member/${store.state.userinfo.memberid}`,{img: src});
console.log(res)
}
export async function setusername(src: string){
const res = await put(`member/${store.state.userinfo.memberid}`,{name: src});
console.log(res)
}

View File

@@ -9,7 +9,7 @@
<div style="display: flex">
<img :src="i.img" alt="" />
<div class="stuinfo">
<div>{{ i.name }} {{zid}}</div>
<div>{{ i.name }}</div>
<div class="lessonname">{{ i.interest }}</div>
</div>
</div>

View File

@@ -35,8 +35,8 @@
</template>
<style lang="scss" scoped>
.videoitem{
width: 226px;
height: 198px;
min-width: 226px;
// height: 198px;
background-color: #fff;
border-radius: 17px;
overflow: hidden;
@@ -46,7 +46,8 @@
position: relative;
box-shadow: 0px 5px 6px 0px rgba(158, 158, 158, 0.11);
.cover{
width: 100%;
width: 226px;
min-width: 100%;
height: 127px;
}
.play{
@@ -75,7 +76,8 @@
.info{
display: flex;
align-items: center;
margin-top: 18px;
margin-top: 9px;
margin-bottom: 9px;
margin-left: 18px;
.datetime{
@@ -174,15 +176,12 @@ export default defineComponent({
const lan: any = useI18n();
function navto(){
let url = '';
switch (props.type) {
switch (props.status) {
case 1:
url = '/regime/livedetail';
url = '/regime/liveing';
break;
case 2:
default:
url = '/regime/livedetail';
break;
case 3:
url = '/regeime/liveing';
}
console.log(props.zid);
if(props.zid != undefined){

View File

@@ -1,6 +1,6 @@
<template>
<div class="video">
<video :controls="true" :src="info.livestatus == 0 ? info.fileurl : info.vodurl"></video>
<video :id=" 'a' + time" style="width:100%;height:100%"></video>
<div class="liveinfo">
<div class="left">
<div>
@@ -9,7 +9,7 @@
</div>
<div>
<img src="@/static/images/livewatch.png" alt="" class="icon">
<span>{{info.dateline}}</span>
<span>{{info.tlanguage}}</span>
</div>
<div>
<img src="@/static/images/livetimetake.png" alt="" class="icon">
@@ -17,14 +17,14 @@
</div>
<div>
<img src="@/static/images/shoucang.png" alt="" class="icon">
<span class="score">5.0{{lan.$t('fen')}}</span>
<span class="score">{{info.score}}{{lan.$t('fen')}}</span>
</div>
</div>
<div style="display: flex">
<div class="right" @click="bianji">
<div class="right" @click="bianji" v-if="info.livestatus == 0">
{{lan.$t('bianjixinxi')}}
</div>
<div class="right" @click="kaishi">
<div class="right" @click="kaishi" v-if="info.livestatus == 0">
{{lan.$t('kaishizhibo')}}
</div>
</div>
@@ -38,11 +38,14 @@
height: 563px;
border-radius: 18px;
overflow: hidden;
display: flex;
flex-direction: column;
> video {
width: 100%;
height: 505px;
}
.liveinfo{
flex-shrink: 0;
display: flex;
justify-content: space-between;
height: 58px;
@@ -87,7 +90,8 @@
import { livestart } from '@/api';
import router from '@/router';
import { useI18n } from '@/utils/i18n';
import { defineComponent, ref } from "vue";
import { defineComponent, onUpdated, ref } from "vue";
import { onBeforeRouteLeave } from 'vue-router';
export default defineComponent({
props:{
@@ -110,12 +114,30 @@ export default defineComponent({
})
}
}
const time = ref(new Date().getTime())
let play: any;
onUpdated(()=>{
// console.log(props.url)
if(props.info){
play = window.TCPlayer('a' + time.value, {
fileID: (props.info.livestatus == 0 ? props.info.fileid : props.info.vodid),
appID: '1303872925'
});
}
})
onBeforeRouteLeave((to, from, next) => {
console.log(121)
play.dispose()
next()
})
// const liveinfo = ref(props.liveinfo)
return {
bianji,
kaishi,
lan
lan,
time
}
},
}
});
</script>

View File

@@ -157,7 +157,7 @@
width: 57px;
height: 57px;
border-radius: 50%;
background-color: #0f0;
// background-color: #0f0;
}
}
}

View File

@@ -5,56 +5,20 @@
</div>
<div class="info">
<div class="item">
<div class="item" v-for="(item,index) in list" :key="index">
<div style="display:flex">
<div class="stuinfo">
<div>andy</div>
<div>{{item.name}}</div>
</div>
</div>
<div class="icons">
<img src="@/static/images/camera.png" alt="" class="icon">
<img src="@/static/images/vol.png" alt="" class="icon">
<img src="@/static/images/camera.png" @click="cameta(item.memberid)" alt="" class="icon">
<img src="@/static/images/vol.png" alt="" class="icon" @click="vol(item.memberid)">
</div>
</div>
<div class="item">
<div style="display:flex">
<div class="stuinfo">
<div>andy</div>
</div>
</div>
<div class="icons">
<img src="@/static/images/camera.png" alt="" class="icon">
<img src="@/static/images/vol.png" alt="" class="icon">
</div>
</div>
<div class="item">
<div style="display:flex">
<div class="stuinfo">
<div>andy</div>
</div>
</div>
<div class="icons">
<img src="@/static/images/camera.png" alt="" class="icon">
<img src="@/static/images/vol.png" alt="" class="icon">
</div>
</div>
<div class="item">
<div style="display:flex">
<div class="stuinfo">
<div>andy</div>
</div>
</div>
<div class="icons">
<img src="@/static/images/camera.png" alt="" class="icon">
<img src="@/static/images/vol.png" alt="" class="icon">
</div>
</div>
</div>
</div>
</template>
@@ -147,5 +111,23 @@
<script lang="ts">
import { defineComponent } from "vue";
export default defineComponent({});
export default defineComponent({
props:{
list:{
type: Array
}
},
setup(prop,context){
function cameta(id: number){
context.emit("cameta", id)
}
function vol(id: number){
context.emit("vol", id)
}
return {
cameta,
vol
}
}
});
</script>

View File

@@ -2,7 +2,10 @@
<div class="menu">
<div class="user" style="overflow: hidden;">
<div class="user" :class="{'seltop': selnum == 0}">
<img :src="userinfo.img" alt="" class="head">
<!-- <img :src="userinfo.img" alt="" class="head"> -->
<a-avatar :size="85" shape="circle" class="head" :src="userinfo.img">
<template v-slot:icon><UserOutlined /></template>
</a-avatar>
<div class="name">{{userinfo.name}}</div>
</div>
</div>
@@ -20,8 +23,8 @@
<div style="overflow: hidden;">
<div class="item" :class="{'selbottom': selnum == list.length - 1}"></div>
</div>
<div class="item loginout">
<div class="route" @click="logout">
<div class="item loginout" @click="visible = true">
<div class="route">
<img src="../static/images/tuichu.png" alt="" class="icon">
<div class="title">
{{lan.$t('tuichu')}}
@@ -29,6 +32,9 @@
</div>
</div>
</div>
<a-modal v-model:visible="visible" :title="lan.$t('tishi')" @ok="logout">
<p>{{lan.$t('querentuichu')}}</p>
</a-modal>
</div>
</template>
<style lang="scss" scoped>
@@ -185,11 +191,22 @@ export default defineComponent({
const userinfo = computed(() => store.state.userinfo)
// 设置当前路由
for(const i in list){
console.log(list[i].route==useRoute().path)
if(list[i].route == useRoute().path){
selnum.value = parseInt(i);
const routelist = [
["/mine/archives"],
['/mine/webcast'],
['/mine/video'],
['/mine/wallet', '/mine/cashout', '/mine/addaccount', '/mine/transaction', '/mine/transactionxq'],
['/mine/liststatistic'],
['/mine/aboutus']
]
for(const i in routelist){
for(const j in routelist[i]){
console.log(routelist[i][j]==useRoute().path)
if(routelist[i][j] == useRoute().path){
selnum.value = parseInt(i);
}
}
}
/**
* 跳转路由与赋值对应的下标
@@ -209,7 +226,10 @@ export default defineComponent({
selnum.value = index;
}
const visible = ref(false);
function logout(): void{
console.log("退出")
store.commit("login", false)
saveValue("token", "");
setToken();
@@ -225,6 +245,7 @@ export default defineComponent({
logout,
mouse,
lan,
visible,
jiantou: require('../static/images/jiantou.png'),
jiantous: require('../static/images/kuozhan1.png')
}

View File

@@ -41,34 +41,7 @@
</template>
</a-dropdown>
<a-dropdown :trigger="['click']" :getPopupContainer="triggerNode => triggerNode.parentNode" v-if="islogin">
<div class="item" @click="e => e.preventDefault()">
<img src="@/static/images/qianbi.png" alt="" class="icon">
<div class="name">{{userinfo.currency}}</div>
<img src="@/static/images/jiantou2.png" alt="" class="down">
</div>
<template v-slot:overlay>
<a-menu style="max-height:70vh;overflow: auto;">
<!-- 货币 -->
<a-menu-item v-for="(i,j) in currencylist" :key="j" style="position: relative;">
<div class="selitem" @click="currencychange(i.value)">
<span :style="{'color': i.name == userinfo.currency ? '#06C7AE' : ''}">{{i.name}} </span>
<img src="@/static/images/duihao.png" alt="" v-if="i.name == userinfo.currency" class="duihao">
</div>
</a-menu-item>
<!-- <a-menu-item key="1">
<div class="selitem">
<span>时区2b</span>
</div>
</a-menu-item>
<a-menu-item key="3">
<div class="selitem">
<span>时区3b</span>
</div>
</a-menu-item> -->
</a-menu>
</template>
</a-dropdown>
<a-dropdown :trigger="['click']" :getPopupContainer="triggerNode => triggerNode.parentNode">
<div class="item" @click="e => e.preventDefault()">
<img src="@/static/images/yuyan.png" alt="" class="icon">
@@ -208,6 +181,8 @@ import { computed, defineComponent, onMounted, ref } from 'vue';
import { useRoute } from 'vue-router';
import axios from 'axios'
import { useI18n } from '@/utils/i18n';
import { setLanvuage } from '@/api/base';
import { saveValue } from '@/utils/common';
export default defineComponent({
props:{
@@ -305,7 +280,7 @@ export default defineComponent({
languagelist.value=await getlanguages()
console.log(routes.path)
nowroute.value=routes.path
lan.locale.value = userinfo.value.languageValue
// lan.locale.value = userinfo.value.languageValue
})
function zonechange(e?: any){
@@ -318,11 +293,21 @@ export default defineComponent({
}
function setlanguage(e?: any){
console.log(e)
editsystemsetting({language:e})
lan.locale.value = e
saveValue("Lanvuage", e)
if(store.state.islogin){
editsystemsetting({language:e})
}else {
location.reload();
// setLanvuage(e)
}
// lan.locale.value = e
}
function toindex(){
router.push("/")
if(!store.state.islogin){
return ;
}
router.push("/mine/archives")
}
return {

View File

@@ -14,8 +14,9 @@
<div class="num">{{(score+'').split('.')[1]?score:score+'.0'}} {{lan.$t('fen')}}</div>
</div>
<div class="all" @click="findall(replyid)" >
<span>={{lan.$t('suoyouhuifu')}}</span>
<img src="@/static/images/arrowdownblue.png" alt="">
<span>{{lan.$t('suoyouhuifu')}}</span>
<img v-show="!iszk" src="@/static/images/arrowdownblue.png" alt="">
<img v-show="iszk" src="@/static/images/arrowdownblueup.png" alt="">
</div>
</div>
<div class="cont">
@@ -23,7 +24,13 @@
</div>
<div class="bottom">
<div class="date">{{date}}</div>
<div class="reply" @click="reply(username)">{{lan.$t('huifu')}}</div>
<div style="display: flex">
<div class="del" @click="del(replyid)">
{{lan.$t('shanchu')}}
</div>
<div class="reply" @click="reply(username)">{{lan.$t('huifu')}}</div>
</div>
</div>
<div class="huifu" v-if="ifshow">
@@ -35,7 +42,8 @@
:date="i.created_at"
:memberid="i.memberid"
:replyid="i.commentid"
@replying="reply"
:info="i"
@replying="replytow"
@reload="refresh"
></ReviewItemtwo>
</div>
@@ -63,7 +71,7 @@
width: 57px;
height: 57px;
border-radius: 50%;
background-color: #0f0;
// background-color: #0f0;
}
.name{
@@ -113,16 +121,24 @@
font-size: 10px;
color: #08AE98;
}
.del{
font-size: 10px;
color:#D12C2D!important;
flex-shrink: 0;
margin-right: 28px;
}
}
.huifu{
border-top: solid 1px #eee;
margin-left: 56px;
margin-top: 30px;
}
}
</style>
<script lang="ts">
import { getcommentlist } from '@/api';
import { delreply, getcommentlist } from '@/api';
import { useI18n } from '@/utils/i18n';
import { message } from 'ant-design-vue';
import { defineComponent, onMounted, ref } from 'vue';
import { useRoute } from 'vue-router';
import ReviewItemtwo from "./ReviewItemtwo.vue"
@@ -160,14 +176,22 @@ export default defineComponent({
const reviewlist=ref({})
const videoid=ref(useRoute().query.id)
const ifshow=ref(false)
onMounted(async () => {
reviewlist.value=await getcommentlist({type: 2,id: videoid.value})
})
const iszk = ref(false)
const url = useRoute().path
let type = 1;
if(url == '/regime/livedetail'){
type = 1;
}else{
type = 2;
}
// onMounted(async () => {
// reviewlist.value=await getcommentlist({type: type,id: videoid.value})
// })
async function refresh(e?: any){
console.log("rekload")
reviewlist.value=await getcommentlist({type: 2,id: videoid.value})
replylist.value=await getcommentlist({type: 3,id: e})
// reviewlist.value=await getcommentlist({type: type,id: videoid.value})
replylist.value=await getcommentlist({type: 3,id: prop.replyid})
}
const stars=ref<Array<number>>([])
console.log(prop.score)
@@ -191,13 +215,47 @@ export default defineComponent({
function reply(e?: string){
console.log(155)
context.emit("replying",{name: e,replyid: prop.replyid,score: prop.score})
context.emit("replying",{name: e,cid: prop.replyid,score: prop.score})
}
function replytow(e: any){
console.log(1556)
e.cid = prop.replyid
context.emit("replying", e)
}
async function findall(e: number){
console.log("all")
if(iszk.value){
iszk.value = false;
ifshow.value= false;
return ;
}
iszk.value = true
replylist.value =await getcommentlist({type: 3,id: e})
ifshow.value=ifshow.value==false?true:false
ifshow.value = true;
}
async function reload() {
if(iszk.value == true){
console.log(iszk.value,121212)
iszk.value = false;
if(prop.replyid){
findall(prop.replyid)
}
}
}
async function del(e?: number){
console.log(e,1212)
const res=await delreply(e)
if(res.code != 0){
message.error(res.msg)
}
context.emit("reload")
// reload()
}
return {
stars,
reply,
@@ -206,7 +264,11 @@ export default defineComponent({
reviewlist,
refresh,
ifshow,
lan
lan,
del,
iszk,
reload,
replytow
}
}
})

View File

@@ -5,18 +5,20 @@
<div class="name myname" v-if="parseInt(memberid)==myid">{{username}}</div>
<div class="name" v-else>{{username}}</div>
</div>
<div class="cont">
{{content}}
<div class="cont" v-html="inhtml()">
</div>
<div class="bottom">
<div class="date">{{date}}</div>
<div class="operate">
<!-- <div class="reply" @click="reply(username)">
回复
</div> -->
<div class="del" @click="del(replyid)" v-if="parseInt(memberid)==myid">
<div class="del" @click="del(replyid)">
{{lan.$t('shanchu')}}
</div>
<div class="reply" @click="reply(username)">
{{lan.$t('huifu')}}
</div>
</div>
</div>
@@ -33,7 +35,7 @@
width: 45px;
height: 45px;
border-radius: 50%;
background-color: #0f0;
// background-color: #0f0;
}
.name{
@@ -69,7 +71,9 @@
margin-left: 67px;
font-size: 11px;
line-height: 1.2;
::v-deep(span){
color: #2581D0;
}
}
.bottom{
display: flex;
@@ -91,7 +95,8 @@
}
.del{
color:#D12C2D!important;
margin-left: 28px;
font-size: 10px;
margin-right: 28px;
}
}
}
@@ -100,10 +105,14 @@
import { delreply } from '@/api';
import store from '@/store';
import { useI18n } from '@/utils/i18n';
import { message } from 'ant-design-vue';
import { defineComponent, ref } from 'vue';
export default defineComponent({
props: {
info:{
type: Object
},
photo: {
type: String
},
@@ -139,15 +148,25 @@ export default defineComponent({
const res=await delreply(e)
if(res.code==0){
context.emit("reload",prop.replyid)
}else {
message.error(res.msg)
}
console.log(res)
}
function inhtml(){
if(prop.info){
return prop.content + (prop.info.replyid != 0 ? '//<span>' + prop.info.with + '</span>' + prop.info.replyContent : '')
}
// {{content}}{{info.replyid != 0 ? '//' + info.with + info.replyContent : ''}}
}
return {
reply,
myid,
del,
lan
lan,
inhtml
}
}
})

View File

@@ -23,14 +23,20 @@
<span>{{lan.$t('zhuangtai')}}</span>
<span class="status">{{lan.$t('shenheing')}}</span>
</div>
<!-- <div class="item item1" v-if="status==1">
<span>{{lan.$t('zhuangtai')}}</span>
<span class="status1">{{lan.$t('shenheing')}}</span>
</div> -->
<div class="item item1" v-if="status==2">
<span>{{lan.$t('zhuangtai')}}</span>
<span class="status1">{{lan.$t('shenheweitongguo')}}</span>
<span class="status1" style="color: #D12C2E">{{lan.$t('shenheweitongguo')}}</span>
</div>
<div class="item item1" v-if="status==1">
<span style="flex-shrink:0">{{lan.$t('yuanyin')}}</span>
<span class="status"> {{lan.$t('yuanyintext')}} </span>
</div>
<div class="item item1" v-if="status==2">
<span style="flex-shrink:0">{{lan.$t('yuanyin')}}</span>
<span> {{yuanyin}} </span>
</div>
</div>
<div class="button">
<div class="modify" @click="update(videoid)">{{lan.$t('xiugaishipin')}}</div>
@@ -123,20 +129,23 @@ import { useRoute } from 'vue-router';
export default defineComponent({
props:{
date:{
date: {
type:String
},
watch:{
watch: {
type:Number
},
share:{
share: {
type:Number
},
status:{
status: {
type:Number
},
videoid:{
videoid: {
type:Number
},
yuanyin: {
type: String
}
},
setup(){

View File

@@ -24,7 +24,7 @@
</div>
</div>
<div class="state audit" v-if="status==0">
{{lan.$t('shenhezhong')}}
{{transcoding == 0 ? lan.$t('zhuanmazhong') : lan.$t('shenhezhong')}}
</div>
<div class="state audit fail" v-if="status==2">
{{lan.$t('weitongguo')}}
@@ -36,8 +36,8 @@
</template>
<style lang="scss" scoped>
.videoitem{
width: 226px;
height: 198px;
min-width: 226px;
// height: 198px;
background-color: #fff;
border-radius: 17px;
overflow: hidden;
@@ -47,7 +47,8 @@
box-shadow: 0px 5px 6px 0px rgba(158, 158, 158, 0.11);
cursor: pointer;
.cover{
width: 100%;
width: 226px;
min-width: 100%;
height: 127px;
// background-color: #0f0;
}
@@ -75,7 +76,8 @@
.info{
display: flex;
align-items: center;
margin-top: 18px;
margin-top: 9px;
margin-bottom: 9px;
margin-left: 18px;
.datetime{
@@ -166,6 +168,9 @@ export default defineComponent({
},
status:{
type:Number
},
transcoding: {
type: Number
}
},
setup() {

View File

@@ -1,7 +1,7 @@
<template>
<div class="video">
<video :src="url" :controls="true"></video>
<div class="title">{{title}}</div>
<video style="width:100%; height:100%;" :id="'a' + url" ></video>
</div>
</template>
<style lang="scss" scoped>
@@ -10,25 +10,53 @@
height: 563px;
border-radius: 17px;
background: white;
position: relative;
// background-color: #0f0;
overflow: hidden;
>video{
width: 100%;
height: 100%;
}
.title{
position: absolute;
top: 23px;
left: 51px;
font-size: 13px;
color: #fff;
z-index: 999;
}
}
</style>
<script lang="ts">
import { defineComponent } from 'vue';
import router from '@/router';
import { defineComponent, onMounted, onUpdated } from 'vue';
import { onBeforeRouteLeave, useRouter } from 'vue-router';
export default defineComponent({
props:{
url:{
type:String
url: {
type: String
},
title: {
type: String
}
},
setup(){
setup(props, ctx){
console.log(1)
let play: any;
onUpdated(()=>{
console.log(props.url)
play = window.TCPlayer('a' + props.url, {
fileID: props.url,
appID: '1303872925'
});
})
onBeforeRouteLeave((to, from, next) => {
console.log(121)
play.dispose()
next()
})
}
})
</script>

View File

@@ -1,11 +1,11 @@
<template>
<div class="review">
<div class="review" v-if="reviewlist.data.length != 0">
<div class="top">
<div class="title">
{{lan.$t('shipinpingjia')}}
<span>8.0{{lan.$t('fen')}}</span>
<span>{{videoinfo}}{{lan.$t('fen')}}</span>
</div>
<div class="score">8.0{{lan.$t('fen')}}</div>
<div class="score">{{videoinfo}}{{lan.$t('fen')}}</div>
</div>
<div class="list" v-for="(i,j) in reviewlist.data" :key="j" >
<ReviewItem
@@ -18,6 +18,8 @@
:replyid="i.commentid"
@replying="reply"
@findall="findreply"
@reload="getlist"
:ref="el => {list[j] = el}"
></ReviewItem>
@@ -111,18 +113,34 @@ export default defineComponent({
props: {
videoid: {
type: Number
},
videoinfo: {
type: String
}
},
setup(prop,context){
const lan: any = useI18n();
const reviewlist=ref({})
const reviewlist=ref<any>({data: []})
const commentval=ref<string>('')
const uinfo=ref<any>({})
const replylist =ref({})
const videoid=ref(useRoute().query.id)
const url = useRoute().path
const list = ref<any>([])
let type = 1;
if(url == '/regime/livedetail'){
type = 1;
}else{
type = 2;
}
async function getlist(){
reviewlist.value=await getcommentlist({type: type,id: videoid.value})
}
onMounted(async () => {
reviewlist.value=await getcommentlist({type: 2,id: videoid.value})
getlist()
})
console.log(useRoute().query)
console.log(store.state.userinfo.memberid,"userifno")
interface SendData{
@@ -131,17 +149,24 @@ export default defineComponent({
teacherid?: number;
score?: number;
content?: string;
replyid?: number;
}
function send(){
const data = ref<SendData>({})
data.value.type=3;
data.value.cid=uinfo.value.replyid
data.value.cid=uinfo.value.cid
data.value.replyid = uinfo.value.replyid
// data.value.teacherid=uinfo.value.memberid
// data.value.score=uinfo.value.score
data.value.content=commentval.value
console.log(data.value,2221)
if(uinfo.value.name){
addcomment(toRaw(data.value))
addcomment(toRaw(data.value)).then(()=>{
for(const i in list.value){
list.value[i].reload()
}
})
}else{
message.error(lan.$t('xuanzehuifuxuesheng'))
}
@@ -173,7 +198,9 @@ export default defineComponent({
haslist,
reviewlist,
refresh,
lan
lan,
list,
getlist
}

View File

@@ -1,7 +1,8 @@
import { getValue } from '@/utils/common';
import axios from 'axios'
axios.defaults.baseURL = '/beelink/public/home/';
axios.defaults.baseURL = 'https://case.sy-my.net/beelink/public/home/';
axios.defaults.headers.common['Authorization'] = "Bearer " + getValue("token");
axios.defaults.headers.common['Language'] = !getValue("Lanvuage") ? 'zh' : getValue("Lanvuage");
axios.defaults.headers.post['Content-Type'] = 'application/x-www-form-urlencoded';
export default axios

View File

@@ -42,6 +42,7 @@ export default {
banquan: "Copyright Beelink Inc. All rights reserved 2019-2022",
zhanghao: "accounts",
shuruzhanghao: "Please enter your email or mobile phone number",
shuruzhanghaol: "Please enter your email address or country number + mobile phone number34690xxx",
mima: "password",
shurumima: "Please enter your password",
wangjimima: "Forget the password?",
@@ -83,18 +84,18 @@ export default {
xiugai: "modify",
shuruxinnicheng: "Please enter a new nickname",
jibenxinxi: "Basic information",
laiziguojia: "From the country",
laiziguojia: "From a country or region",
juzhudi: "Place of residence",
shurujuzhudi: "Enter residence",
wohaihuishuo: "I would also say",
shuliandu: "Proficiency",
jixutianjia: "Continue adding",
xindemuyu: "Please enter your new mother tongue",
duanshipin: "Short video",
duanshipin: "Short video introduction",
shipinyaoqiu: "Video requirements:",
shipinyaoqiu1: "The time required for uploading video is within 30s",
shipinyaoqiu2: "Support file size 100m",
shipinyaoqiu3: "File extension: FIV, MP4",
shipinyaoqiu3: "File extension: FLV, MP4, MOV, AVI, WAV",
ziwojieshao: "self-introduction",
lianxifangshi: "contact information",
genghuanshoujihao: "Change mobile phone number",
@@ -189,9 +190,8 @@ export default {
xuanzezhanghu: "Select account",
tixianjine: "Withdrawal amount",
quanbujine: "Total amount",
tixianzhu: "Note: 0.1% service fee will be charged for each withdrawal, with a minimum of ¥ 0.1",
yueshu: "Your balance is only",
zuiditixian: "Minimum withdrawal amount ¥ 100",
zuiditixian: "Minimum withdrawal amount",
mingxichaxun: "Details inquiry",
kaishiriqi: "Please select the start date",
jieshuriqi: "Please select the end date",
@@ -295,5 +295,24 @@ export default {
fengmianyaoqiu2:"File size limit: 2m",
kahaoweikong:"Card number cannot be empty",
kaihuhangweikong:"Swiftcode cannot be empty",
shensu:"Appeal"
shensu:"Appeal",
querenquxiao: "Are you sure to cancel the live broadcast?",
tianjiatixian: "Add a withdrawal account",
shijirenshu: "Actual number of live broadcast",
shijishichang: "Actual live broadcast duration",
cshipinyaoqiu1: "The video should be no longer than 10 minutes",
cshipinyaoqiu2: "Video size cannot exceed 500M",
shangchuanwancheng: "Please wait for the file to be uploaded",
zhanghaocunzai: "account already exists",
shouruguize: "Description of revenue rules",
shouru1: "Live Revenue=Actual attendess x Actual duration x unit price",
shouru2: "1 on 1, 20€/hour; 1 on N, 10€/hour/person, (1<N<=4)",
shouru3: "Attention! You can only change the currency once!",
tishi: "tip",
querentuichu: "You confirm to exit?",
huobitishi: "Attention! You can only change the currency once!",
zhuanmazhong: "Transcoding in",
shichangtishi:"",
renshutishi: '',
tixianzhu: "",
}

View File

@@ -1,9 +1,31 @@
import zh from "./zh"
import en from "./en"
import { getset } from '@/api';
import { geti18n } from '@/utils/i18n';
import store from '@/store';
import { getValue } from '@/utils/common';
getset().then((res: any)=>{
zh.shichangtishi = `最短${res.timeLowerLimit}min, 最长${res.timeCeiling}min`
en.shichangtishi = `The shortest is ${res.timeLowerLimit}min and the longest is ${res.timeCeiling}min`
zh.renshutishi = `最少${res.lowerLimit}人, 最多${res.numberCeiling}`
en.renshutishi = `At least ${res.lowerLimit}, at most ${res.numberCeiling}`
// en.shichangtishi = `Minimum ${res.lowerLimit} person, maximum ${res.numberCeiling} people`
zh.tixianzhu = `注:每笔提现收取${res.sxf}服务费,最低${ res.symbol + res.minmoney }`
en.tixianzhu = `Note: ${res.sxf} service fee will be charged for each withdrawal, with a minimum of ${ res.symbol + res.minmoney }`
zh.zuiditixian = "最低提现金额" + res.symbol + res.minwithdraw
en.zuiditixian = "Minimum withdrawal amount " + res.symbol + res.minwithdraw
const i18n = geti18n();
const loc = i18n.locale.value;
i18n.locale.value = '';
i18n.locale.value = loc;
console.log('i18n')
store.commit("setseting", res)
})
export default {
locale: "zh", //默认语言
locale: getValue("Lanvuage") || 'zh', //默认语言
messages: {
zh,
en
zh: zh,
en: en
}
}

View File

@@ -42,6 +42,7 @@ export default {
banquan:"Beelink公司版权所有 2019—2022",
zhanghao:"帐号",
shuruzhanghao:"请输入您的邮箱或者手机号",
shuruzhanghaol:"请输入您的邮箱或者国家号+手机号86186xxx",
mima:"密码",
shurumima:"请输入您的密码",
wangjimima:"忘记密码?",
@@ -83,18 +84,18 @@ export default {
xiugai:"修改",
shuruxinnicheng:"请输入新的昵称",
jibenxinxi:"基本信息",
laiziguojia:"来自国家",
laiziguojia:"来自国家或地区",
juzhudi:"居住地",
shurujuzhudi:"输入居住地",
wohaihuishuo:"我还会说",
shuliandu:"熟练度",
jixutianjia:"继续添加",
xindemuyu:"请输入新的母语",
duanshipin:"短视频",
duanshipin:"短视频介绍",
shipinyaoqiu:"视频要求:",
shipinyaoqiu1:"上传视频时间要求为30s之内",
shipinyaoqiu2:"支持文件大小100M",
shipinyaoqiu3:"文件扩展名fiv、mp4",
shipinyaoqiu3:"文件扩展名flv、mp4、mov、avi、wav",
fengmianyaoqiu:"封面要求:",
fengmianyaoqiu1:"文件扩展名jpgpng",
fengmianyaoqiu2:"文件大小限制2M",
@@ -192,9 +193,8 @@ export default {
xuanzezhanghu:"选择账户",
tixianjine:"提现金额",
quanbujine:"全部金额",
tixianzhu:"注每笔提现收取0.1%服务费最低¥0.1",
yueshu:"您的余额只有",
zuiditixian:"最低提现金额¥100",
zuiditixian:"最低提现金额",
mingxichaxun:"明细查询",
kaishiriqi:"请选择开始日期",
jieshuriqi:"请选择结束日期",
@@ -295,5 +295,24 @@ export default {
guanbishibai:"关闭失败",
kahaoweikong:"卡号不能为空",
kaihuhangweikong:"开户行不能为空",
shensu:"申诉"
shensu:"申诉",
querenquxiao: "您确认取消直播吗?",
tianjiatixian: "添加提现账户",
shijirenshu: "实际直播人数",
shijishichang: "实际直播时长",
cshipinyaoqiu1: "视频长度不能超过10分钟",
cshipinyaoqiu2: "视频大小不能超过500m",
shangchuanwancheng: "请等待文件上传完成",
zhanghaocunzai: "帐号已存在",
shuoruguize: "收入规则说明",
shouru1: "直播收入=实际参加学生数x参加时长x单价",
shouru2: "单价1对120欧/小时1对N10欧/小时/人1<N<=4",
shouru3: "请注意,货币一旦修改,不能变动",
tishi: "提示",
querentuichu: "您确认退出?",
huobitishi: "请注意,货币一旦修改,不能变动。",
zhuanmazhong: "转码中",
shichangtishi:"",
renshutishi: '',
tixianzhu:"",
}

17
src/import-png.d.ts vendored
View File

@@ -13,4 +13,19 @@ declare module "ant-design-vue/es/locale/zh_CN" {
export default value;
}
declare var FB: any;
declare module 'tim-js-sdk'{
const value: any;
export default value;
}
declare module 'vue-cropper'{
const VueCropper: any
export {
VueCropper
}
}
declare var FB: any;
declare var TCPlayer: any;

View File

@@ -1,7 +1,7 @@
<template>
<div class="mine" :style="{height:height + 'px'}">
<NavTop :type="1" style="flex-shrink:0"></NavTop>
<div class="body">
<div class="body" id="rbody">
<router-view/>
</div>
</div>

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 19 KiB

After

Width:  |  Height:  |  Size: 116 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.4 KiB

View File

@@ -1,4 +1,7 @@
import { getaddr, userinfo } from '@/api';
import { setLanvuage } from '@/api/base';
import { getValue, saveValue } from '@/utils/common';
import { geti18n } from '@/utils/i18n';
import { isProxy } from 'vue';
import { createStore } from 'vuex'
@@ -43,14 +46,37 @@ export default createStore({
uid: "",
updatedAt: "",
video: "",
videoid: 0,
willsay: [{name: "请选择", level: 0}],
willsayValue: [{name: "0", level: 0}],
zoneStr: "中途岛GMT-11:00",
zoneid: 1,
symbol: "$",
zoneValue:"",
currencytag: 0
},
islogin: false
islogin: false,
seting:{
lowerLimit: "",
minmoney: 0,
minwithdraw: "",
numberCeiling: "",
sxf: "",
symbol: "",
timeCeiling: "",
timeLowerLimit: "",
},
qh: {
code: "86",
ename: "China",
name: "中国",
}
},
mutations: {
setseting(state, info){
console.log(info, 112)
state.seting = info
},
setUserInfo(state, userinfo){
userinfo.money = userinfo.money.toString()
state.userinfo = userinfo
@@ -62,16 +88,40 @@ export default createStore({
setLanguage(state, data){
const split = new Date().toString().split(" ");
const timeZoneFormatted = split[split.length - 2] + " " + split[split.length - 1];
state.userinfo.language = data[1] // English 中文
state.userinfo.languageValue = data[0] // 'en' 'zh'
state.userinfo.zoneStr = timeZoneFormatted;
state.userinfo.currency = data[2];
const lan = getValue("Lanvuage");
state.qh = data.qh.data;
if(lan != null && lan){
// state.userinfo.language = data[1] // English 中文
// state.userinfo.languageValue = data[0] // 'en' 'zh'
state.userinfo.zoneStr = timeZoneFormatted;
state.userinfo.currency = data.hb;
}else{
console.log(data, 111)
state.userinfo.language = data.yyx // English 中文
state.userinfo.languageValue = data.yy // 'en' 'zh'
state.userinfo.zoneStr = timeZoneFormatted;
state.userinfo.currency = data.hb;
saveValue("Lanvuage", data.yy);
if( data.yy != geti18n().$s()){
location.reload()
}
// setLanvuage(data[0]);
}
},
setWlan(state){
const lan = getValue("Lanvuage");
if(lan != null && lan){
state.userinfo.languageValue = !getValue("Lanvuage") ? 'zh' : getValue("Lanvuage");
state.userinfo.language = (!getValue("Lanvuage") ? 'zh' : getValue("Lanvuage")) == 'zh' ? '中文' : 'English';
}
}
},
actions: {
async setUserInfo({ commit }){
const user = await userinfo();
if(user != '未登录'){
saveValue("Lanvuage", user.languageValue);
commit('setUserInfo', user);
} else {
const info = await getaddr();

View File

@@ -58,7 +58,7 @@ export interface UserInfo {
video: string;
desc: string;
money: string;
languageValue: number;
}

View File

@@ -1,3 +1,5 @@
import { message } from 'ant-design-vue';
/**
* 图片转Base64
*/
@@ -52,4 +54,59 @@ export function getValue(key: string): any{
}
}
return value;
}
/**
* 验证图片是否为对应类型
* @param name 图片名字
*/
export function provenimg(file: any): boolean | void{
const type = ['png', 'jpg'];
const ntypearr = file.name.split('.');
const ntype = ntypearr[ntypearr.length - 1];
console.log(ntype)
const size = 2 * 1024 * 1024;
if(file.size > size){
message.error("最大2MB")
return false;
}
let istype = false
for(const i in type){
if(type[i] == ntype){
istype = true
}
}
console.log(istype)
return istype;
}
/**
* 验证视频是否为对应类型
* @param name 图片名字
*/
export function provenvideo(file: any, isvideo?: boolean): boolean{
const type = ['flv', 'mp4', 'wmv', 'mov', 'avi'];
const ntypearr = file.name.split('.');
const ntype = ntypearr[ntypearr.length - 1];
if(isvideo){
const size = 500 * 1024 * 1024;
if(file.size > size){
message.error("最大500MB")
return false;
}
}else{
const size = 100 * 1024 * 1024;
if(file.size > size){
message.error("最大100MB")
return false;
}
}
for(const i in type){
if(type[i] == ntype){
return true;
}
}
return false;
}

View File

@@ -64,8 +64,18 @@ export function getdate(yue?: number): GetDate{
}
export function getweek(time: string,zhou?: number){
let now = dayjs((!time ? undefined : time))
export function getweek(time: string, id: string,zhou?: number){
/* eslint-disable */
const utc = require('dayjs/plugin/utc') // dependent on utc plugin
/* eslint-disable */
const timezone = require('dayjs/plugin/timezone')
dayjs.extend(utc)
dayjs.extend(timezone)
const days: any = dayjs;
console.log(id, 11111)
let now = days((!time ? undefined : time)).tz(id)
console.log(now, 11111)
if(zhou != undefined){
now = now.day(now.day() + (zhou * 6));
}

View File

@@ -22,9 +22,9 @@ const createI18n = (config: Config) => ({
});
const i18nSymbol = Symbol();
let i18n: any;
export function provideI18n(i18nConfig: Config) {
const i18n = createI18n(i18nConfig);
i18n = createI18n(i18nConfig);
provide(i18nSymbol, i18n);
return i18n;
}
@@ -33,5 +33,9 @@ export function useI18n() {
const i18n = inject(i18nSymbol);
if (!i18n) throw new Error("No i18n provided!!!");
return i18n;
}
export function geti18n(){
return i18n;
}

View File

@@ -6,7 +6,7 @@ interface OnFunctio {
}
interface UploaderDone {
fileId: string;
fileId: number;
video: {
url: string;
verify_content: string;

View File

@@ -124,6 +124,7 @@ import store from '@/store';
import router from '@/router';
import { useI18n } from '@/utils/i18n';
import { editsystemsetting, getlanguages } from '@/api';
import { saveValue } from '@/utils/common';
export default defineComponent({
components: {
NavBottom
@@ -153,8 +154,13 @@ export default defineComponent({
}
function setlanguage(e?: any){
console.log(e)
editsystemsetting({language:e})
lan.locale.value = e
saveValue("Lanvuage", e)
if(store.state.islogin){
editsystemsetting({language:e})
}else {
location.reload();
// setLanvuage(e)
}
}
return {
languagelist,
@@ -195,7 +201,7 @@ export default defineComponent({
align-items: center;
position: fixed;
top: 0;
z-index: 9999;
z-index: 99;
.navcontent{
width: 910px;
margin: 0 auto;

View File

@@ -23,7 +23,7 @@
</a-select-option>
<a-select-option value="Jiangsu"> Jiangsu </a-select-option>
</a-select> -->
<a-select :default-value="quhaolist[0].name + '+' + quhaolist[0].code" size="small" option-label-prop="label" @change="getquhao" class="getcode" show-search >
<a-select :default-value="mrqh.name + '+' + mrqh.code" size="small" option-label-prop="label" @change="getquhao" class="getcode" show-search >
<a-select-option v-for="(i,j) in quhaolist" :key="j" :value="i.name + '+' + i.code" :label="'+' + i.code">
{{i.name}}+{{i.code}}
</a-select-option>
@@ -53,7 +53,7 @@
<a-input-group compact>
<a-input
style="width: 80%"
:placeholder="lan.$t('shuruzhanghao')"
:placeholder="lan.$t('shuruzhanghaol')"
v-model:value="userinfo.phone"
/>
</a-input-group>
@@ -117,13 +117,14 @@
</template>
<script lang="ts">
import { defineComponent, onMounted, reactive, ref } from "vue";
import { computed, defineComponent, onMounted, reactive, ref } from "vue";
import LoginTab from "@/components/login/LoginTab.vue";
import NavTop from "@/components/NavTop.vue"
import { checksmscode, getquhaolist, getwebvideolist, loginpass, sendsms } from '@/api';
import { message } from 'ant-design-vue';
import router from '@/router';
import { useI18n } from '@/utils/i18n';
import store from '@/store';
export default defineComponent({
name: "Login",
@@ -146,6 +147,7 @@ export default defineComponent({
phone: '13152639856',
password: '123456'
})
const mrqh = computed(() => store.state.qh)
const quhaolist = ref<any>([
{
code: "86",
@@ -188,7 +190,7 @@ export default defineComponent({
}
lock = true;
console.log(myquhao.value,"quhao")
sendsms(myquhao.value + phone.value, 0);
sendsms(myquhao.value, phone.value);
const timestep = setInterval(() => {
console.log(11112);
@@ -224,12 +226,13 @@ export default defineComponent({
function sublogin(){
console.log(11)
// else if(!(/^1[3-9]\d{9}$/.test(phone.value))){
// message.error(lan.$t('shoujihaoyouwu'));
// return
// }
if(phone.value==''){
message.error(lan.$t('shoujihaoweikong'))
return
}else if(!(/^1[3-9]\d{9}$/.test(phone.value))){
message.error(lan.$t('shoujihaoyouwu'));
return
} else if(code.value==""){
// console.log(phone.value)
console.log((/^1[3-9]\d{9}$/.test(phone.value)))
@@ -280,7 +283,8 @@ export default defineComponent({
tovideoxq,
navto,
lan,
slogin
slogin,
mrqh
};
},
});

View File

@@ -138,7 +138,7 @@ export default defineComponent({
return;
}
lock = true;
sendsms(uinfo.value.quhao + uinfo.value.phone, 0);
sendsms(uinfo.value.quhao, uinfo.value.phone);
const timestep = setInterval(() => {
console.log(11112);
time.value = time.value - 1;

View File

@@ -23,16 +23,19 @@
>
<a-input-group compact>
<a-select
:default-value="quhaolist[0].code"
:default-value="mrqh.name + '+' + mrqh.code"
size="small"
@change="getquhao"
class="getcode"
style="width: 50%"
option-label-prop="label"
show-search
>
<a-select-option
v-for="(i, j) in quhaolist"
:key="j"
:value="i.code"
:value="i.name + '+' + i.code"
:label="'+' + i.code"
>
{{ i.name }}+{{ i.code }}
</a-select-option>
@@ -146,7 +149,7 @@
<a-select-option
v-for="(item, index) in willsay"
:key="index"
:value="item.languageid"
:value="item.value"
>
{{ item.name }}
</a-select-option>
@@ -218,18 +221,21 @@
</template>
<script lang="ts">
import { defineComponent, onMounted, reactive, ref, toRaw } from "vue";
import { computed, defineComponent, onMounted, reactive, ref, toRaw } from "vue";
import NavTop from "@/components/NavTop.vue";
import {
checksmscode,
checkuser,
getquhaolist,
getwillsay,
interests,
register,
sendsms,
} from "@/api";
import { message } from "ant-design-vue";
import router from "@/router";
import { useI18n } from "@/utils/i18n";
import store from '@/store';
export default defineComponent({
name: "Sign",
@@ -255,6 +261,7 @@ export default defineComponent({
jiaoshou: "",
});
const willsay = ref<any>();
const mrqh = computed(() => store.state.qh)
const quhaolist = ref<any>([
{
code: "86",
@@ -262,7 +269,7 @@ export default defineComponent({
name: "中国",
},
]);
getwillsay().then((res) => {
interests().then((res) => {
willsay.value = res;
});
onMounted(async () => {
@@ -274,8 +281,9 @@ export default defineComponent({
* 点击获取验证码 触发60S倒计时
*/
let lock = false;
const getcode: () => void = () => {
const getcode = async () => {
console.log(11111);
if (lock) {
return;
}
@@ -283,8 +291,13 @@ export default defineComponent({
message.error(lan.$t('shoujihaoweikong'));
return;
}
const iszc = await checkuser({phone: phone.value.phone})
if(iszc.code == 0){
message.error(lan.$t('zhanghaocunzai'))
return ;
}
lock = true;
sendsms(phone.value.quhao + phone.value.phone, 0);
sendsms(phone.value.quhao, phone.value.phone);
const timestep = setInterval(() => {
console.log(phone);
time.value = time.value - 1;
@@ -367,7 +380,9 @@ export default defineComponent({
phone.value.quhao + phone.value.phone,
phone.value.code
);
if (res) {
stepnow.value = e;
}
@@ -392,6 +407,13 @@ export default defineComponent({
function navto(url: string) {
router.push(url);
}
function getquhao(e?: any){
console.log(e)
phone.value.quhao = e.toString()
phone.value.quhao = phone.value.quhao.split("+")[1];
}
return {
formLayout,
getcode,
@@ -406,6 +428,8 @@ export default defineComponent({
navto,
lan,
quhaolist,
getquhao,
mrqh
};
},
});
@@ -482,7 +506,7 @@ export default defineComponent({
position: relative;
z-index: 999;
}
.getcode {
::v-deep(.getcode) {
font-size: 15px;
color: #08ae98;
width: 313px;
@@ -490,6 +514,9 @@ export default defineComponent({
line-height: 30px;
cursor: pointer;
}
::v-deep(.ant-select-selection){
background-color: unset;
}
.orginfo {
margin-top: 178px;
}

View File

@@ -274,7 +274,7 @@ export default defineComponent({
} else {
if (toRaw(accountinfo.value).accountid) {
console.log(300);
editaccount(toRaw(accountinfo.value).accountid);
editaccount(toRaw(accountinfo.value).accountid, accountinfo.value);
} else {
console.log(111);
accountadd(toRaw(accountinfo.value));
@@ -286,7 +286,7 @@ export default defineComponent({
} else {
if (toRaw(accountinfo.value).accountid) {
console.log(300);
editaccount(toRaw(accountinfo.value).accountid);
editaccount(toRaw(accountinfo.value).accountid, accountinfo.value);
} else {
console.log(111);
accountadd(toRaw(accountinfo.value));
@@ -295,7 +295,7 @@ export default defineComponent({
} else if (accountinfofin.type == 1) {
if (toRaw(accountinfo.value).accountid) {
console.log(300);
editaccount(toRaw(accountinfo.value).accountid);
editaccount(toRaw(accountinfo.value).accountid, accountinfo.value);
} else {
console.log(111);
accountadd(toRaw(accountinfo.value));
@@ -313,7 +313,8 @@ export default defineComponent({
message.error(lan.$t("kaihuhangweikong"));
} else {
if (toRaw(accountinfo.value).accountid) {
editaccount(toRaw(accountinfo.value).accountid);
editaccount(toRaw(accountinfo.value).accountid, accountinfo.value);
} else {
accountadd(toRaw(accountinfo.value));
}
@@ -325,7 +326,7 @@ export default defineComponent({
} else {
if (toRaw(accountinfo.value).accountid) {
console.log(300);
editaccount(toRaw(accountinfo.value).accountid);
editaccount(toRaw(accountinfo.value).accountid, accountinfo.value);
} else {
console.log(111);
accountadd(toRaw(accountinfo.value));

View File

@@ -9,6 +9,7 @@
class="avatar-uploader"
:show-upload-list="false"
:customRequest="uploadspic"
:beforeUpload="imgs"
@change="handleChange"
>
<a-avatar :size="85" shape="circle" :src="userinfo.img">
@@ -37,6 +38,7 @@
size="small"
v-model:value="userinfo.name"
:placeholder="lan.$t('shuruxinnicheng')"
@pressEnter="setname"
/>
</div>
</div>
@@ -47,7 +49,7 @@
<div class="label">{{ lan.$t("laiziguojia") }}</div>
<a-select
v-model:value="userinfo.country"
style="width: 171px"
style="width: 1.71rem"
size="small"
ref="select"
show-search
@@ -76,7 +78,7 @@
<div class="label">{{ lan.$t("jiaoshou") }}</div>
<a-select
v-model:value="userinfo.tlanguageValue"
style="width: 171px"
style="width: 1.71rem"
size="small"
show-search
ref="select"
@@ -104,7 +106,7 @@
>
<a-select
v-model:value="lang.name"
style="width: 171px"
style="width: 1.71rem"
size="small"
ref="select"
show-search
@@ -119,6 +121,7 @@
index) in chiveslist[1]"
:key="index"
:value="item.name"
:disabled="isdisabled(item.name)"
>
{{ item.name }}
</a-select-option>
@@ -155,7 +158,7 @@
<div class="label">{{ lan.$t("muyu") }}</div>
<!-- <a-select
v-model:value="userinfo.mtongue"
style="width: 171px"
style="width: 1.71rem"
size="small"
ref="select"
:getPopupContainer="triggerNode => triggerNode.parentNode"
@@ -175,7 +178,7 @@
<a-upload
list-type="picture"
:customRequest="uploads"
:beforeUpload="beforeVideoUpload"
:beforeUpload="video"
>
<div class="upload-image">
<PlaySquareOutlined
@@ -228,7 +231,7 @@
</div>
<div class="input-box phone-box">
<div class="label">{{ lan.$t("shoujihao") }}</div>
<div class="phone">{{ userinfo.mobile }}</div>
<div class="phone">{{ userinfo.code }}{{ userinfo.mobile }}</div>
<div class="update-btn" @click="togglePhoneModal(true)">
{{ lan.$t("genghuanshoujihao") }}
</div>
@@ -252,7 +255,7 @@
<div class="label">{{ lan.$t("shiqu") }}</div>
<a-select
v-model:value="userinfo.zoneStr"
style="width: 171px"
style="width: 1.71rem"
size="small"
ref="select"
show-search
@@ -269,10 +272,11 @@
</a-select-option>
</a-select>
</div>
<div class="input-box currency-box">
<div class="label">{{ lan.$t("huobi") }}</div>
<a-select
style="width: 171px"
<div><a-select
style="width: 1.71rem"
size="small"
ref="select"
:getPopupContainer="
@@ -280,6 +284,7 @@
"
@change="currencychange"
v-model:value="currencyindex"
:disabled="userinfo.currencytag == 1"
>
<a-select-option
v-for="(item, index) in currencylist"
@@ -289,12 +294,14 @@
{{ item.name }}
</a-select-option>
</a-select>
<div style="font-size:18px;color: red;margin-top:0.05rem">{{lan.$t('huobitishi')}}</div></div>
</div>
<div class="input-box time-zone">
<div class="label">{{ lan.$t("yuyan") }}</div>
<a-select
v-model:value="userinfo.languageValue"
style="width: 171px"
style="width: 1.71rem"
size="small"
ref="select"
:getPopupContainer="
@@ -333,7 +340,7 @@
{{ lan.$t("wanchengrenzheng") }}
</div>
<div class="title sub-title">
{{ lan.$t("shuru") }} {{ userinfo.mobile }}
{{ lan.$t("shuru") }}{{ userinfo.code }} {{ userinfo.mobile }}
{{ lan.$t("shoudaodeyzm") }}
</div>
<div class="form-box">
@@ -348,7 +355,7 @@
<div
@click="
sendVerificationCode(
userinfo.code + userinfo.mobile
userinfo.code , userinfo.mobile
)
"
class="confirm-btn"
@@ -380,14 +387,19 @@
<!-- <a-input size="small" v-model:value="bindPhone.number" /> -->
<a-input-group compact class="telbox">
<a-select
:default-value="quhaolist[0].code"
:default-value="mrqh.name + '+' + mrqh.code"
size="small"
@change="getquhao"
option-label-prop="label"
style="width: 1rem;"
show-search
>
<a-select-option
v-for="(i, j) in quhaolist"
:key="j"
:value="i.code"
:value="i.name + '+' + i.code"
:label="'+' + i.code"
>
{{ i.name }}+{{ i.code }}
</a-select-option>
@@ -411,7 +423,7 @@
/>
<div
@click="
sendVerificationCode(myquhao + mynewtel)
sendVerificationCode(myquhao , mynewtel)
"
class="confirm-btn"
>
@@ -485,6 +497,9 @@
{{ lan.$t("baocun") }}
</div>
</div>
<a-modal v-model:visible="huobi" :footer="null" :title="lan.$t('tishi')" >
<p>{{lan.$t('huobitishi')}}</p>
</a-modal>
<nav-bottom></nav-bottom>
</div>
</template>
@@ -519,9 +534,12 @@ import {
interests,
putmember,
sendsms,
setheadimg,
setusername,
} from "@/api/index";
import { message } from "ant-design-vue";
import { useI18n } from "@/utils/i18n";
import { provenimg, provenvideo } from '@/utils/common';
export default defineComponent({
name: "Archives",
@@ -531,6 +549,7 @@ export default defineComponent({
NavBottom,
},
setup() {
let issum = true;
const lan: any = useI18n();
interface SpeakItem {
lang: string;
@@ -545,15 +564,14 @@ export default defineComponent({
const userinfo = computed(() => {
return store.state.userinfo;
});
const huobi = ref(false);
// 表单数据
const currencyindex = ref<string>(userinfo.value.currency);
const formData = ref(toRaw(userinfo.value));
watch(userinfo, () => {
formData.value = toRaw(userinfo.value);
// console.log(lan.$s(), formData.value.languageValue)
if(lan.$s() != formData.value.languageValue){
location.reload();
}
console.log(userinfo.value.currencyValue, "listsssss");
currencyindex.value = userinfo.value.currency;
console.log(currencyindex.value, "listsssss");
@@ -564,6 +582,8 @@ export default defineComponent({
const chiveslist = ref<any>([[], []]);
const languages = ref<unknown>([]);
const quhaolist = ref<any>([]);
const mrqh = computed(() => store.state.qh)
const myquhao = ref<string>("");
const mynewtel = ref<string>("");
@@ -678,11 +698,11 @@ export default defineComponent({
/**
* 发送验证码
*/
function sendVerificationCode(tel: string): void {
function sendVerificationCode(code: string ,tel: string): void {
if (remainTime.value === 0) {
computedVerificationCode();
console.log(tel, "send");
sendsms(tel, 0);
sendsms(code, tel);
}
}
// 绑定手机号是否是第二步
@@ -730,7 +750,7 @@ export default defineComponent({
);
console.log(res, "xiugai");
if (res) {
const res1 = await changetel(mynewtel.value);
const res1 = await changetel(myquhao.value, mynewtel.value);
if (res1) {
store.dispatch("setUserInfo");
updatePhoneVisible.value = false;
@@ -740,6 +760,7 @@ export default defineComponent({
function getquhao(e?: any) {
console.log(e);
myquhao.value = e.toString();
myquhao.value = myquhao.value.split("+")[1];
}
// 是否显示修改密码框
const updatePasswordVisible: Ref<boolean> = ref(false);
@@ -747,11 +768,14 @@ export default defineComponent({
original?: string;
password?: string;
topassword?: string;
memberid?: number;
}
const passwordForm: PassWord = reactive({
original: "",
password: "",
topassword: "",
memberid:0
});
/**
* 密码对话框清空数据
@@ -775,6 +799,7 @@ export default defineComponent({
* @return { void }
*/
function updateUserPassword(): void {
passwordForm.memberid = userinfo.value.memberid
console.log(toRaw(passwordForm));
if (
toRaw(passwordForm).password === toRaw(passwordForm).topassword
@@ -798,6 +823,10 @@ export default defineComponent({
// for(let i in toRaw(formData.value).willsay){
// console.log(toRaw(formData.value).willsay[i])
// }
if(!issum){
message.error(lan.$t("shangchuanwancheng"))
return;
}
const uesrinfo = toRaw(formData.value);
// for (let m = 0; m < toRaw(chiveslist.value).length; m++) {
// for (const i in uesrinfo.willsayValue) {
@@ -885,7 +914,10 @@ export default defineComponent({
// console.log(zonelist.value[i].city + zonelist.value[i].gmt, uesrinfo.zoneStr)
}
if (reg.test(userinfo.value.email)) {
putmember(uesrinfo);
await putmember(uesrinfo);
if(lan.$s() != uesrinfo.languageValue){
location.reload();
}
} else {
message.error(lan.$t('youxiangcuowu'));
}
@@ -907,13 +939,17 @@ export default defineComponent({
}
const uploadprogress: Ref<number> = ref(0);
async function uploads(file: AntUpload) {
uploadprogress.value = 0;
uploadprogress.value = 1;
issum = false;
const res = await uploadflie(file.file, (info: any) => {
console.log(info);
uploadprogress.value = info.percent.toFixed(2) * 100;
const jindu = info.percent.toFixed(2) * 100
uploadprogress.value = jindu > 0 ? jindu : 1;
});
userinfo.value.video = res.video.url;
userinfo.value.videoid = res.fileId;
issum = true;
}
if (formData.value.video != "") {
@@ -928,11 +964,23 @@ export default defineComponent({
function choosewillsay(e?: any) {
console.log(formData.value.willsayValue);
console.log(e);
}
function isdisabled(name: string){
for(const i in formData.value.willsay){
if(formData.value.willsay[i].name == name){
return true;
}else{
return false;
}
}
}
function currencychange(e?: any) {
console.log(e);
huobi.value = true;
userinfo.value.currencyValue = e;
// editsystemsetting({currency:e})
}
@@ -946,7 +994,29 @@ export default defineComponent({
// picinfo.fileId=res.fileId
// picinfo.url=res.video.url
formData.value.img = res.video.url;
setheadimg(res.video.url);
}
// function beforeVideoUpload(file: any){
// console.log(file)
// const type = provenvideo(file.name);
// // if(!type){
// // message.error("文件类型错误,请重新选择")
// // }
// return type;
// }
function video(file: any){
return provenvideo(file)
}
function imgs(file: any){
return provenimg(file)
}
function setname(){
setusername(formData.value.name).then(()=>{
showname.value = true;
})
}
// function selguojia(e: any){
// userinfo.value.countryValue = e;
// console.log(e)
@@ -993,7 +1063,13 @@ export default defineComponent({
showname,
uploadspic,
lan,
interestslist
interestslist,
isdisabled,
setname,
video,
imgs,
mrqh,
huobi
};
},
});
@@ -1010,6 +1086,7 @@ export default defineComponent({
.telbox {
margin-left: 15px;
font-size: 12px;
// width: 200px;
}
.update-btn {
font-size: 11px;
@@ -1054,7 +1131,7 @@ export default defineComponent({
align-items: center;
margin-bottom: 28px;
.label {
width: 60px;
width: 90px;
font-size: 11px;
font-weight: 500;
color: #808080;
@@ -1063,7 +1140,7 @@ export default defineComponent({
align-self: flex-start;
}
.ant-input {
width: 171px;
width: 1.71rem;
padding: 6px 11px;
border-radius: 3px;
border: 1px solid #dcdfe0;
@@ -1119,7 +1196,7 @@ export default defineComponent({
}
.video-lang {
.upload-image {
width: 171px;
width: 1.71rem;
height: 96px;
border: 1px solid #dcdfe0;
border-radius: 3px;
@@ -1283,6 +1360,9 @@ export default defineComponent({
line-height: 23px;
cursor: pointer;
user-select: none;
position: fixed;
bottom: 100px;
right: 300px;
}
// .submit-btn:hover {
// background: #08ae98;

View File

@@ -3,8 +3,8 @@
<div class="mingxilist">
<div class="tabs">
<div class="beforetab">{{lan.$t('tixianzhanghu')}}</div>
<span class="residue">{{lan.$t('yue')}}{{ yue }}</span>
<div class="topbtn topbtn2" @click="navto('/mine/addaccount')">{{lan.$t('tixianzhanghu')}}</div>
<span class="residue">{{lan.$t('yue')}}{{ danwei + yue }}</span>
<div class="topbtn topbtn2" @click="navto('/mine/addaccount')">{{lan.$t('tianjiatixian')}}</div>
<div class="topbtn topbtn1" @click="navto('/mine/transaction')">{{lan.$t('tixianjilu')}}</div>
</div>
<div class="line"></div>
@@ -61,7 +61,7 @@
</div>
</div>
<div class="zhanghao">
{{lan.$t('zhanghao')}}6217 **** **** **** 175
{{lan.$t('zhanghao')}}{{i.account}}
</div>
</div>
</a-radio>
@@ -103,7 +103,7 @@
<div class="label">{{lan.$t('tixianjine')}}</div>
<div class="moneynum">
<a-input v-model:value="payinfo.money" class="shuru" />
<div></div>
<div>{{danwei}}</div>
<div class="cashoutall" @click="all">{{lan.$t('quanbujine')}}</div>
<div class="desc">
{{lan.$t('tixianzhu')}}
@@ -116,7 +116,7 @@
payinfo.money < 100 || payinfo.money > parseFloat(yue)
"
>
*{{lan.$t('yueshu')}} {{ parseFloat(yue) }}{{lan.$t('zuiditixian')}}
*{{lan.$t('yueshu')}} {{danwei + parseFloat(yue) }}{{lan.$t('zuiditixian')}}
</div>
</div>
<div class="cashoutall submit" @click="sub">{{lan.$t('lijitixian')}}</div>
@@ -126,7 +126,7 @@
</template>
<script lang="ts">
import { defineComponent, onMounted, ref, toRaw } from "vue";
import { computed, defineComponent, onMounted, ref, toRaw } from "vue";
import NavBottom from "@/components/NavBottom.vue";
import { cashout, getwallect } from "@/api";
import store from "@/store";
@@ -153,7 +153,8 @@ export default defineComponent({
const moneychange: (e: number) => void = (e: number) => {
console.log(e);
};
const yue=ref<number>(store.state.userinfo.moneyValue)
const yue=computed( () => store.state.userinfo.moneyValue)
const danwei = computed(() => store.state.userinfo.symbol)
// const yue = ref<number>(10000);
// yue.value=store.state.userinfo.money
const accountlist = ref<Array<any>>([]);
@@ -179,7 +180,7 @@ export default defineComponent({
if (accountlist.value.length == 0) {
message.error(lan.$t('kongzhanghaoliebiao'));
return;
} else if (payinfo.value.money < 100) {
} else if (payinfo.value.money < store.state.seting.minwithdraw) {
message.error(lan.$t('zuiditixian'));
return;
} else if (payinfo.value.money > yue.value) {
@@ -212,7 +213,8 @@ export default defineComponent({
accountlist,
store,
lan,
navto
navto,
danwei
};
},
});

View File

@@ -37,11 +37,11 @@
</div>
<div class="data">
<div class="label">{{lan.$t('zongguankanshu')}}</div>
<div class="right">{{statistics.videoInfo?statistics.videoInfo.sum:0}}</div>
<div class="right">{{statistics.videoInfo?statistics.videoInfo.watch:0}}</div>
</div>
<div class="data">
<div class="label">{{lan.$t('pingjundefen')}}</div>
<div class="right">{{statistics.videoInfo?statistics.videoInfo.sum:0}}</div>
<div class="right">{{statistics.videoInfo?statistics.videoInfo.avg:0}}</div>
</div>
<img src="@/static/images/shipintj.png" alt="" class="zhuzi" />
</div>

View File

@@ -6,27 +6,27 @@
<div class="hits">视频点击量</div>
</div>
<div class="list-body">
<div class="rank-item" v-for="item in rankData" :key="item.uid" :class="{'mine-item': item.uid === 5 }">
<div class="other-rank" :class="{'mine-rank': item.uid === 5 }">
<div class="rank-item" v-for="(item,index) in newList" :key="index" :class="{'mine-item': item.isme }">
<div class="other-rank" :class="{'mine-rank': item.isme }">
<div class="ranking-number">
<span v-if="item.uid === 5" class="mine">我的成绩</span>
<span v-if="item.isme" class="mine">我的成绩</span>
<div v-else>
<img src="@/static/images/rank_first.png" class="rank-img" v-if="item.id === 1" />
<img src="@/static/images/rank_second.png" class="rank-img" v-else-if="item.id === 2" />
<img src="@/static/images/rank_third.png" class="rank-img" v-else-if="item.id === 3" />
<span class="other" v-else>{{ item.id }}</span>
<img src="@/static/images/rank_first.png" class="rank-img" v-if="index === 0" />
<img src="@/static/images/rank_second.png" class="rank-img" v-else-if="index === 1" />
<img src="@/static/images/rank_third.png" class="rank-img" v-else-if="index === 2" />
<span class="other" v-else>{{ item.rank }}</span>
</div>
</div>
<div class="user-info">
<a-avatar :size="34">
<a-avatar :size="34" :src="item.img">
<template v-slot:icon><UserOutlined /></template>
</a-avatar>
<span class="name">{{ item.name }}</span>
</div>
<div class="hits">{{ item.value }}</div>
<div class="hits">{{ item.clicks }}</div>
</div>
<div class="third-ellipsis" v-if="item.id === 3">...</div>
<div class="twenty-ellipsis" v-if="item.id === 20">
<div class="third-ellipsis" v-if="item.rank == 3">...</div>
<div class="twenty-ellipsis" v-if="item.rank === deadLine">
<div class="third-ellipsis">
<div>...</div>
</div>
@@ -37,7 +37,7 @@
</div>
</template>
<script lang="ts">
import { defineComponent, reactive } from 'vue';
import { defineComponent, ref } from 'vue';
import { UserOutlined } from '@ant-design/icons-vue';
export default defineComponent({
@@ -45,59 +45,28 @@ export default defineComponent({
components: {
UserOutlined
},
setup() {
interface RankItem {
uid: number;
id: number;
avatar: string;
name: string;
value: number | string;
props: {
list: {
type: Array
}
const rankData: Array<RankItem> = reactive([{
uid: 1,
id: 1,
avatar: '',
name: '1',
value: '123456',
}, {
uid: 2,
id: 2,
avatar: '',
name: '12',
value: '12345',
}, {
uid: 3,
id: 3,
avatar: '',
name: '123',
value: '1234',
}, {
uid: 4,
id: 20,
avatar: '',
name: '1234',
value: '123',
}, {
uid: 15,
id: 21,
avatar: '',
name: '15',
value: '12',
}, {
uid: 5,
id: 22,
avatar: '',
name: '61',
value: '1',
}, {
uid: 51,
id: 23,
avatar: '',
name: '61',
value: '1',
}]);
},
setup(props) {
const deadLine = ref(4); // 写死的合格线
const list = ref(props.list);
let mineRank = 0; // 自己的排名
list.value!.forEach((element: any) => {
if(element.isme) {
mineRank = element.rank;
}
});
// 过滤数据
const newList = list.value!.filter((item: any) => {
return item.rank <= 3 || item.rank == deadLine.value || item.rank === mineRank || item.rank === mineRank - 1 || item.rank === mineRank + 1;
})
return {
rankData
newList,
deadLine,
}
}
})

View File

@@ -14,7 +14,7 @@
<a-upload
list-type="picture"
:customRequest="uploadspic"
:before-upload="beforeUploadpic"
:beforeUpload="imgs"
>
<div
class="upload-image"
@@ -59,7 +59,7 @@
<a-upload
list-type="picture"
:customRequest="uploads"
:before-upload="beforeUpload"
:beforeUpload="video"
>
<div class="upload-image" v-if="form.fileurl.length == 0">
<PlaySquareOutlined
@@ -95,8 +95,8 @@
<p>
{{ lan.$t("shipinyaoqiu") }}
</p>
<p>1.{{ lan.$t("shipinyaoqiu1") }}</p>
<p>2.{{ lan.$t("shipinyaoqiu2") }}</p>
<p>1.{{ lan.$t("cshipinyaoqiu1") }}</p>
<p>2.{{ lan.$t("cshipinyaoqiu2") }}</p>
<p>3.{{ lan.$t("shipinyaoqiu3") }}</p>
</div>
<!-- <div class="demand">
@@ -131,7 +131,7 @@
import { defineComponent, onMounted, reactive, Ref, ref, toRaw } from "vue";
import { PlaySquareOutlined, PlusOutlined } from "@ant-design/icons-vue";
import NavBottom from "@/components/NavBottom.vue";
import { previewCover } from "@/utils/common";
import { previewCover, provenimg, provenvideo } from "@/utils/common";
import { FromSend, ImgInfo, VideoInfo } from "@/types";
import { uploadflie } from "@/utils/vod";
import { setvideo, videoadd, videodetail } from "@/api";
@@ -147,6 +147,7 @@ export default defineComponent({
NavBottom,
},
setup() {
let issum= true;
const lan: any = useI18n();
interface FileItem {
video: Array<string>;
@@ -191,6 +192,10 @@ export default defineComponent({
*/
const beforeCoverUpload = (file: File): boolean => {
console.log(file);
const type = provenimg(file.name);
if(!type){
message.error("请重新选择")
}
return false;
};
@@ -223,16 +228,14 @@ export default defineComponent({
file: File;
}
async function uploadspic(file: AntUpload) {
if (ifalowupload.value) {
const res = await uploadflie(file.file, (info: any) => {
console.log(info);
uploadpicprogress.value = info.percent.toFixed(2) * 100;
});
console.log(res);
form.value.img = res.video.url;
} else {
return;
}
}
function beforeUploadpic(info?: any) {
@@ -252,22 +255,24 @@ export default defineComponent({
const uploadprogress: Ref<number> = ref(0);
const ifallowvideo = ref<boolean>(false);
async function uploads(file: AntUpload) {
if (ifallowvideo.value) {
issum = false;
console.log(file);
videofile.value = file.file;
uploadprogress.value = 1;
videos.value[0].addEventListener("durationchange", () => {
console.log(videos.value[0].duration);
form.value.fileduration = videos.value[0].duration;
});
const res = await uploadflie(file.file, (info: any) => {
console.log(info);
uploadprogress.value = info.percent.toFixed(2) * 100;
const jindu = info.percent.toFixed(2) * 100
uploadprogress.value = jindu > 0 ? jindu : 1;
});
console.log(res);
form.value.fileid = res.fileId;
form.value.fileurl = res.video.url;
}
issum = true;
}
function beforeVideoUpload(info?: any) {
@@ -285,6 +290,10 @@ export default defineComponent({
*/
const routes = useRoute();
const onSubmit = async (e: FromSend) => {
if(!issum){
message.error(lan.$t("shangchuanwancheng"))
return;
}
e.preventDefault();
console.log(toRaw(form.value), 111);
console.log(toRaw(form.value).video[0].length);
@@ -322,6 +331,13 @@ export default defineComponent({
ifallowvideo.value = true;
}
}
function video(file: any){
return provenvideo(file, true)
}
function imgs(file: any){
return provenimg(file)
}
return {
labelCol: { span: 4 },
@@ -345,7 +361,9 @@ export default defineComponent({
ifalowupload,
beforeVideoUpload,
lan,
beforeUploadpic
beforeUploadpic,
video,
imgs
};
},
});

View File

@@ -7,6 +7,7 @@
size="small"
v-model:value="form.title"
:placeholder="lan.$t('shuruzhibobiaoti')"
@click="isEntitled = jinzhi"
/>
</a-form-item>
<a-form-item :label="lan.$t('zhibofengmian')" class="item-cover" :rules="{ required: true, message: 'Please input Activity name', trigger: 'blur'}">
@@ -14,7 +15,7 @@
<a-upload
list-type="picture"
:customRequest="uploadspic"
:before-upload="beforeUploadpic"
:before-upload="imgs"
>
<div
class="upload-image"
@@ -61,7 +62,7 @@
<a-upload
list-type="picture"
:customRequest="uploads"
:before-upload="beforeUpload"
:before-upload="video"
>
<div class="upload-image" v-if="form.fileurl.length == 0">
<PlaySquareOutlined
@@ -94,7 +95,7 @@
</div>
</a-upload>
<div class="demand">
<p class="one-line-hide">
<p>
{{ lan.$t("shipinyaoqiu") }}
</p>
<p>1.{{ lan.$t("shipinyaoqiu1") }}</p>
@@ -110,7 +111,7 @@
</p>
</div> -->
</a-form-item>
<a-form-item :label="lan.$t('kaishishijian')" :rules="{ required: true, message: 'Please input Activity name', trigger: 'blur'}">
<a-form-item :label="lan.$t('kaishishijian')" @click="isEntitled = jinzhi" :rules="{ required: true, message: 'Please input Activity name', trigger: 'blur'}">
<!-- <a-input
size="small"
@@ -123,6 +124,7 @@
format="YYYY-MM-DD HH:mm"
:value="form.dateline"
:disabled-date="disabledDate"
@click="isEntitled = jinzhi"
@change="startchange"
:placeholder="lan.$t('shezhikaishishijian')"
:getCalendarContainer="
@@ -139,11 +141,12 @@
<a-input
size="small"
v-model:value="form.livetime"
@click="isEntitled = jinzhi"
:placeholder="lan.$t('shuruzhiboshijian')"
type="number"
/>
<span class="unit">{{ lan.$t("fenzhong") }}</span>
<div style="color: red;font-size: 0.12rem;line-height: 1.3;">*最短30min, 最长120min</div>
<div style="color: red;font-size: 0.12rem;line-height: 1.3;">*{{ lan.$t('shichangtishi') }}</div>
</a-form-item>
<a-form-item
:label="lan.$t('zhiborenshu')"
@@ -153,15 +156,17 @@
<a-input
size="small"
v-model:value="form.livenumber"
@click="isEntitled = jinzhi"
:placeholder="lan.$t('shuruzhiborenshu')"
type="number"
/>
<div style="color: red;font-size: 0.12rem;line-height: 1.3;">*最少1人, 最多4人</div>
<div style="color: red;font-size: 0.12rem;line-height: 1.3;">*{{ lan.$t('renshutishi') }}</div>
</a-form-item>
<a-form-item :label="lan.$t('zhibojianjie')" class="brief">
<a-textarea
v-model:value="form.desc"
@click="isEntitled = jinzhi"
:autoSize="true"
class="brief-textarea"
:maxlength="200"
@@ -171,7 +176,9 @@
</a-form-item>
<a-form-item :wrapper-col="{ span: 4, offset: 0 }">
<a-button @click="onSubmit">{{ lan.$t("fabuzhibo") }}</a-button>
<a-button style="margin-left:0.15rem;background-color: red;" @click="isquxiao = true" v-if="isbianji">{{ lan.$t("quxiaozhibo") }}</a-button>
</a-form-item>
</a-form>
<div class="modal-container">
<a-modal
@@ -206,11 +213,20 @@
</div>
</a-modal>
</div>
<a-modal
:title="lan.$t('quxiaozhibo')"
v-model:visible="isquxiao"
:confirm-loading="confirmLoading"
@ok="onquxiao()"
>
<p>{{ lan.$t("querenquxiao") }}</p>
</a-modal>
<nav-bottom></nav-bottom>
</div>
</template>
<script lang="ts">
import {
computed,
defineComponent,
onBeforeUpdate,
onMounted,
@@ -223,15 +239,16 @@ import { PlaySquareOutlined, PlusOutlined } from "@ant-design/icons-vue";
import { useForm } from "@ant-design-vue/use";
import NavBottom from "@/components/NavBottom.vue";
import RankList from "./RankList.vue";
import { previewCover } from "@/utils/common";
import { previewCover, provenimg, provenvideo } from "@/utils/common";
import { FromSend, ImgInfo } from "@/types/index";
import { uploadflie } from "@/utils/vod";
import { getlivest, liveadd, liveinfo, setlive } from "@/api";
import { cancellive, getlivest, liveadd, liveinfo, setlive } from "@/api";
import { useRoute } from "vue-router";
import dayjs from "dayjs";
import { message } from "ant-design-vue";
import router from "@/router";
import { useI18n } from "@/utils/i18n";
import store from '@/store';
export default defineComponent({
name: "ReleaseWebcast",
@@ -242,12 +259,13 @@ export default defineComponent({
RankList,
},
setup() {
let issum = true;
const lan: any = useI18n();
// 表单数据
const form = ref({
title: "",
img: "",
fileid: "",
fileid: 0,
fileurl: "",
fileduration: 0,
dateline: "",
@@ -255,12 +273,15 @@ export default defineComponent({
livenumber: "",
desc: "",
});
const isquxiao = ref(false)
const uploadprogress: Ref<number> = ref(0);
const uploadpicprogress: Ref<number> = ref(0);
const videofile = ref<File>();
const videos = ref<Array<any>>([]);
const lives = ref<any>({});
const jinzhi = ref(false)
const isEntitled: Ref<boolean> = ref(false);
/**
* 验证直播时间
*/
@@ -344,15 +365,27 @@ export default defineComponent({
* todo 需要后台返回年份
*/
const id = useRoute().query.id;
const isbianji = ref(id);
if (id != null && typeof id == "string") {
liveinfo(parseInt(id)).then((res) => {
form.value = res;
});
}
const seting = computed(() => store.state.seting)
const onSubmit = (e: FromSend) => {
if(jinzhi.value){
isEntitled.value = true;
}
if(!issum){
message.error(lan.$t("shangchuanwancheng"))
return;
}
e.preventDefault();
validate()
.then(() => {
console.log(seting.value)
console.log(toRaw(form), 111);
const subdata: any = toRaw(form.value);
if (subdata.title == "") {
@@ -374,12 +407,13 @@ export default defineComponent({
message.error(lan.$t('zhiborenshuweikong'));
return;
} else {
if(subdata.livetime < 30 || subdata.livetime > 120){
message.error("直播时长最短30min, 最长120min");
if(subdata.livetime < parseInt(seting.value.timeLowerLimit) || subdata.livetime > parseInt(seting.value.timeCeiling)){
// console.log(subdata.livetime, subdata.livetime < seting.value.timeLowerLimit || subdata.livetime > seting.value.timeCeiling)
message.error(lan.$t('shichangtishi'));
return ;
}
if(subdata.livenumber > 4 || subdata.livenumber < 1){
message.error("直播人数最少1人, 最多4人");
if(subdata.livenumber > parseInt(seting.value.numberCeiling) || subdata.livenumber < parseInt(seting.value.lowerLimit)){
message.error(lan.$t('renshutishi'));
return ;
}
if (!lives.value.status) {
@@ -404,7 +438,6 @@ export default defineComponent({
console.log("error", err);
});
};
const isEntitled: Ref<boolean> = ref(false);
/**
* 隐藏无资格提示
*/
@@ -464,26 +497,27 @@ export default defineComponent({
}
}
async function uploads(file: AntUpload) {
if (ifallowupload.value) {
issum = false;
console.log(file);
videofile.value = file.file;
videos.value[0].addEventListener("durationchange", () => {
console.log(videos.value[0].duration);
form.value.fileduration = videos.value[0].duration;
});
uploadprogress.value = 1;
const res = await uploadflie(file.file, (info: any) => {
console.log(info);
uploadprogress.value = info.percent.toFixed(2) * 100;
const jindu = info.percent.toFixed(2) * 100
uploadprogress.value = jindu > 0 ? jindu : 1;
});
console.log(res);
form.value.fileid = res.fileId;
form.value.fileurl = res.video.url;
}
issum = true;
}
const ifallowpic = ref<boolean>(false);
async function uploadspic(file: AntUpload) {
if (ifallowpic.value) {
const res = await uploadflie(file.file, (info: any) => {
console.log(info);
uploadpicprogress.value = info.percent.toFixed(2) * 100;
@@ -493,13 +527,12 @@ export default defineComponent({
// picinfo.fileId=res.fileId
// picinfo.url=res.video.url
form.value.img = res.video.url;
}
}
getlivest().then((res) => {
if (res) {
isEntitled.value = true;
lives.value = res;
jinzhi.value = true;
}
});
function beforeUploadpic(info?: any) {
@@ -519,6 +552,31 @@ export default defineComponent({
return current && current < now;
}
function onquxiao(){
isquxiao.value = false;
if (id != null && typeof id == "string") {
cancellive(parseInt(id), 3).then((res)=>{
if(res){
router.push("/regime/live")
}
})
}
}
function video(file: any){
if(jinzhi.value){
isEntitled.value = true;
return false;
}
return provenvideo(file)
}
function imgs(file: any){
if(jinzhi.value){
isEntitled.value = true;
return false;
}
return provenimg(file)
}
return {
labelCol: { span: 4 },
wrapperCol: { span: 14 },
@@ -549,7 +607,13 @@ export default defineComponent({
ifallowupload,
fankui,
lan,
disabledDate
disabledDate,
isbianji,
onquxiao,
isquxiao,
video,
imgs,
jinzhi
};
},
});
@@ -647,7 +711,7 @@ export default defineComponent({
font-size: 10px;
font-weight: 500;
color: #808080;
width: 134px;
// width: 134px;
> p {
margin: 0;
}

View File

@@ -20,12 +20,11 @@
</thead>
<tbody>
<tr v-for="(i,j) in withdrawallist.data" :key="j">
<td>{{i.typename}} {{i.account}}</td>
<td>{{i.typename}}&nbsp;&nbsp;&nbsp;&nbsp;{{i.account}}</td>
<td>{{i.created_at}}</td>
<td class="moneyadd">{{i.statusname}}</td>
<td>
<span v-if="international==1">$</span>
<span v-else></span>
{{i.money}}
</td>
<td @click="navto(3,i.withdrawalid)">{{lan.$t('chakanxiangqing')}}</td>
@@ -34,8 +33,11 @@
</tbody>
</table>
<template v-if="!withdrawallist.total">
<a-empty />
</template>
<div class="pages">
<a-pagination v-model:current="page" :total="withdrawallist.total" :showLessItems="true" @change="pagechange"/>
<a-pagination v-if="withdrawallist.total" v-model:current="page" :total="withdrawallist.total" :showLessItems="true" @change="pagechange"/>
</div>
</div>
<NavBottom class="navbottom"></NavBottom>

View File

@@ -29,9 +29,17 @@
<div class="right">{{accountinfo.created_at}}</div>
</div>
<div class="infoitem">
<div class="infoitem" v-if="accountinfo.type != 2">
<div class="left">{{lan.$t('laiyuan')}}</div>
<div class="right">{{accountinfo.typename}}</div>
<div class="right" style="cursor:pointer" @click="toinfo(accountinfo.source)">{{accountinfo.sourcetitle}}</div>
</div>
<div class="infoitem" v-if="accountinfo.type != 2">
<div class="left">{{lan.$t('shijishichang')}}</div>
<div class="right">{{accountinfo.livetime}}</div>
</div>
<div class="infoitem" v-if="accountinfo.type != 2">
<div class="left">{{lan.$t('shijirenshu')}}</div>
<div class="right">{{accountinfo.count}}</div>
</div>
<div class="back" @click="navto(1,2)">{{lan.$t('fanhui')}}</div>
@@ -58,7 +66,7 @@
</div>
<div class="infoitem">
<div class="left">{{lan.$t('tixianzhuangtai')}}</div>
<div class="right" >{{accountinfo.statusname}}</div>
<div class="right" style="color: red;" >{{accountinfo.statusname}}</div>
<!-- <div class="right" v-if="accountinfo.status==1">直播收入</div>
<div class="right" v-if="accountinfo.status==2">提现</div>
<div class="right" v-if="accountinfo.status==3">后台充值</div> -->
@@ -83,7 +91,7 @@
<div class="infoitem">
<div class="left">{{lan.$t('shoukuanzhanghu')}}</div>
<div class="right">{{accountinfo.typename}}</div>
<div class="right">{{accountinfo.typename}}&nbsp;&nbsp;&nbsp;&nbsp;{{accountinfo.account}}</div>
</div>
<div style="display:flex">
@@ -145,14 +153,20 @@ export default defineComponent({
router.push({
path: url
});
}
}
}
}
function toinfo(id: string){
router.push({path: '/regime/livedetail', query: {id: id}})
}
return {
accountinfo,
query,
navto,
lan
lan,
toinfo
};
},
});

View File

@@ -157,7 +157,7 @@
<div class="mingxilist" v-if="ifchina && listindex==2">
<div class="mingxitop">
<div class="tabs">
<span class="tabtitle">{{lan.$t('mingxichaxun')}}</span>
<span class="tabtitle">{{lan.$t('mingxichaxun')}}<img src="@/static/images/wenhao.png" @click="visible = true" /></span>
<div :class="tabindex == 0 ? 'on' : ''" @click="tabchange(0)">
{{lan.$t('quanbu')}}
@@ -186,18 +186,27 @@
<tr v-for="(i,j) in salelist.data" :key="j">
<td>{{i.created_at}}</td>
<td>{{i.typename}}</td>
<td class="moneyadd moneyreverse" v-if="i.type==2">-{{i.money}}</td>
<td class="moneyadd " v-else>+{{i.money}}</td>
<td class="moneyadd moneyreverse" v-if="i.type==2">-{{i.money}}</td>
<td class="moneyadd " v-else>+{{i.money}}</td>
<td style="cursor: pointer;" @click="navto(4,i.accountid)">查看详情</td>
</tr>
</tbody>
</table>
<template v-if="!salelist.total">
<a-empty />
</template>
</div>
<div class="pages">
<a-pagination v-model:current="page" :total="salelist.total" :showLessItems="true" @change="pagechange"/>
<a-pagination v-if="salelist.total" v-model:current="page" :total="salelist.total" :showLessItems="true" @change="pagechange"/>
</div>
</div>
<a-modal v-model:visible="visible" :footer="null" :title="lan.$t('shuoruguize')" @cancel="visible = false">
<p>{{lan.$t('shouru1')}}</p>
<p>{{lan.$t('shouru2')}}</p>
<p style="color:#D12C2E">{{lan.$t('shouru3')}}</p>
</a-modal>
<NavBottom class="navbottom"></NavBottom>
</div>
</template>
@@ -225,6 +234,7 @@ export default defineComponent({
const state=ref<number>(0)
const dates=ref<Array<string>>(["",""])
const page = ref(1);
const visible = ref(false);
onMounted(async () => {
console.log(useRoute().query)
listindex.value=1
@@ -308,7 +318,8 @@ export default defineComponent({
del,
store,
pagechange,
lan
lan,
visible
};
},
});
@@ -338,7 +349,7 @@ export default defineComponent({
color: #111;
padding: 11px 0;
.tabtitle {
width: 60px;
// width: 60px;
margin-right: 30px;
}
> div {
@@ -351,6 +362,15 @@ export default defineComponent({
color: #08ae98;
}
}
>span{
display: flex;
align-items: center;
>img{
margin-left: 8px;
width: 13px;
height: 13px;
}
}
.on {
color: #08ae98;
position: relative;

View File

@@ -25,9 +25,9 @@
:img="i.img"
:title="i.title"
:score="i.score"
:date="i.starttime"
:takehour="i.vodduration"
:livenum="i.livenumber"
:date="i.dateline"
:takehour="i.livetime"
:livenum="i.count"
:status="i.livestatus"
:zid="i.liveid"
></LiveItem>
@@ -41,9 +41,9 @@
:img="i.img"
:title="i.title"
:score="i.score"
:date="i.starttime"
:takehour="i.vodduration"
:livenum="i.livenumber"
:date="i.dateline"
:takehour="i.livetime"
:livenum="i.count"
:status="i.livestatus"
:zid="i.liveid"
></LiveItem>
@@ -56,15 +56,18 @@
:img="i.img"
:title="i.title"
:score="i.score"
:date="i.starttime"
:takehour="i.vodduration"
:livenum="i.livenumber"
:date="i.dateline"
:takehour="i.livetime"
:livenum="i.count"
:status="i.livestatus"
:zid="i.liveid"
></LiveItem>
</div>
<template v-if="!livelist.total">
<a-empty />
</template>
<div class="pages">
<a-pagination v-model:current="page" :total="livelist.total" :showLessItems="true" />
<a-pagination v-if="livelist.total" v-model:current="page" :total="livelist.total" :showLessItems="true" />
</div>
</div>
</template>
@@ -184,7 +187,7 @@ export default defineComponent({
setup() {
const lan: any = useI18n();
const page = ref(1);
const tabindex = ref(1);
const tabindex = ref<number | string>(1);
const livelist = ref<LivelistInfo>({
code: 0,
total: 0,
@@ -197,7 +200,11 @@ export default defineComponent({
});
async function tab(){
input.value = '';
livelist.value = await getlivelist({ status: tabindex.value});
let index: string | number = '';
if(tabindex.value != 1){
index = tabindex.value
}
livelist.value = await getlivelist({ status: index});
}
function tabchange(e: number): void {

View File

@@ -7,7 +7,7 @@
<LiveCount :info="liveinfo.studentlist" :livestatus="liveinfo.livestatus" :zid="liveinfo.liveid"></LiveCount>
</div>
<VideoReview class="review" v-if="liveinfo.livestatus == 2"></VideoReview>
<VideoReview class="review" v-if="liveinfo.livestatus == 2" :videoinfo="liveinfo.score"></VideoReview>
</div>
</template>
<style lang="scss" scoped>

View File

@@ -1,412 +1,30 @@
<template>
<div class="liveing">
<div class="top">
<div class="left">{{lan.$t('zhiboyemian')}}</div>
<div class="right">
<img src="@/static/images/liveshare.png" alt="" @click="fenxiang()" />
<img src="@/static/images/liveend.png" alt="" @click="visible = true" />
</div>
</div>
<div class="info">
<LiveingWatcher></LiveingWatcher>
<div class="LivePlaying">
<LivePlaying></LivePlaying>
<div class="comment">
<div class="commentitem">
<span>13:32:30</span>
<span class="name"> Andy </span>
<span> 老师这个部分可以讲的慢一些吗 </span>
</div>
<div class="commentitem">
<span>13:32:30</span>
<span class="name"> Andy </span>
<span> 老师这个部分可以讲的慢一些吗 </span>
</div>
<div class="commentitem">
<span>13:32:30</span>
<span class="name"> Andy </span>
<span> 老师这个部分可以讲的慢一些吗 </span>
</div>
</div>
</div>
<div class="others">
<div :id="'s-' + item.memberid" class="othersitem" v-for="(item, index) in roominfo.studentlist" :key="index">
<div class="watcher"></div>
<div class="name">{{item.name}}</div>
<!-- <img src="" alt="" /> -->
</div>
<!-- <div class="othersitem">
<div class="watcher"></div>
<div class="name">asdsada</div>
<img src="" alt="" />
</div>
<div class="othersitem">
<div class="watcher"></div>
<div class="name">asdsada</div>
<img src="" alt="" />
</div>
<div class="othersitem">
<div class="watcher"></div>
<div class="name">asdsada</div>
<img src="" alt="" />
</div> -->
</div>
</div>
<a-modal v-model:visible="visible" title="Basic Modal" @ok="guanbi">
<p>{{lan.$t('querenguanbi')}}</p>
</a-modal>
</div>
<iframe style="width:100%;height: 100%" :src="url" frameborder="0"></iframe>
</template>
<style lang="scss" scoped>
.liveing ::v-deep(.ant-breadcrumb) > span:last-child {
color: #08ae98;
}
.liveing {
width: 1320px;
height: 563px;
.top {
width: 1321px;
height: 57px;
background: white;
border-radius: 18px;
margin-top: 23px;
margin-bottom: 29px;
display: flex;
justify-content: space-between;
color: #121212;
font-size: 13px;
align-items: center;
.left {
margin-left: 40px;
font-weight: bold;
}
.right {
margin-right: 27px;
> img {
margin-right: 10px;
}
}
}
.info {
display: flex;
justify-content: space-between;
.LivePlaying {
border-radius: 18px;
margin: 0 35px;
.comment {
width: 797px;
height: 153px;
background: white;
border-radius: 18px;
margin-top: 29px;
padding: 30px;
font-size: 12px;
color: #121212;
.commentitem {
margin-bottom: 17px;
.name {
margin-left: 28px;
color: #08ae98;
font-weight: bold;
}
}
}
.liveinfo {
display: flex;
justify-content: space-between;
height: 58px;
align-items: center;
.left {
display: flex;
color: #121212;
font-size: 13px;
margin-left: 29px;
> div {
margin-right: 57px;
}
.icon {
width: 25px;
height: 24px;
margin-right: 6px;
}
}
.right {
width: 74px;
height: 29px;
border: 1px solid #08ae98;
border-radius: 3px;
margin-right: 29px;
color: #08ae98;
font-size: 13px;
line-height: 29px;
font-weight: bold;
text-align: center;
}
}
}
}
.others {
width: 252px;
height: 630px;
display: flex;
flex-direction: column;
.othersitem {
position: relative;
width: 100%;
height: 132px;
margin-bottom: 35px;
background-color: #eee;
border-radius: 18px 18px 0px 0px;
overflow: hidden;
.watcher {
width: 100%;
height: 29px;
background: #000000;
color: white;
position: absolute;
opacity: 0.1;
border-radius: 18px 18px 0px 0px;
top: 0;
}
> img {
width: 228px;
height: 132px;
border-radius: 18px;
}
.name {
position: absolute;
top: 9px;
left: 28px;
font-size: 12px;
color: #fff;
}
}
}
}
</style>
<script lang="ts">
import { defineComponent, onMounted, ref } from "vue";
import LivePlaying from "@/components/LivePlaying.vue";
import LiveingWatcher from "@/components/LiveingWatcher.vue";
import TRTC from "trtc-js-sdk"
import { getliveinfo, livestop, luzhi, usersig } from '@/api';
import { getliveinfo } from '@/api';
import { defineComponent, ref } from 'vue';
import { useRoute } from 'vue-router';
import store from '@/store';
import { message } from 'ant-design-vue';
import { useI18n } from '@/utils/i18n';
export default defineComponent({
components: {
LivePlaying,
LiveingWatcher,
},
setup() {
const lan: any = useI18n();
console.log(1);
let client: any;
let localStream: any;
let statie = true;
let userSing = '';
let type = false;
const visible = ref(false);
const roominfo = ref<any>([]);
console.log(useRoute())
setup(){
const id = useRoute().query.id;
TRTC.checkSystemRequirements().then((result: any) => {
if(!result) {
message.error(lan.$t('buzhichitonghua'))
}
})
async function qiehuan(){
// 1 屏幕分享 2 摄像头
client.unpublish(localStream)
localStream = type ? TRTC.createStream({ userid: store.state.userinfo.memberid, audio: true, screen: true }) : TRTC.createStream({ userId: 10, audio: false, video: true });
type = !type;
localStream.initialize().then(()=>{
client
.publish(localStream)
.catch((error: string) => {
console.error('本地流发布失败 ' + error);
})
.then(() => {
localStream.play('local_stream');
console.log('本地流发布成功');
});
});
}
async function shexiang(){
localStream = TRTC.createStream({ userId: store.state.userinfo.memberid, audio: true, video: true });
const id = localStream.getId();
await localStream
.initialize()
.catch((error: string) => {
console.error('初始化本地流失败 ' + error);
const url = ref<string>()
if(id && typeof id == 'string'){
getliveinfo(parseInt(id)).then((res: any)=>{
url.value = `/zhibo.html?roomid=${res.roomid}&memberid=${res.memberid}`
})
.then(() => {
console.log('初始化本地流成功');
client
.publish(localStream)
.catch((error: string) => {
console.error('本地流发布失败 ' + error);
})
.then(() => {
const el = document.querySelector("#local_stream");
if(el){
el.innerHTML = ""
}
localStream.play('local_stream');
console.log('本地流发布成功');
console.log(id, 123)
});
});
}
async function pingmu(){
const result = await TRTC.checkSystemRequirements()
console.log(result,11111)
if(!result) {
message.error(lan.$t('buzhichifenxiang'));
shexiang()
return ;
}
localStream = TRTC.createStream({ userid: store.state.userinfo.memberid, audio: true, screen: true });
const id = localStream.getId();
await localStream
.initialize()
.catch((error: string) => {
console.error('初始化本地流失败 ' + error);
message.error(lan.$t('xuanzefenxiangneirong'))
setTimeout(()=>{
pingmu()
}, 1000)
})
.then(() => {
console.log('初始化本地流成功');
client
.publish(localStream)
.catch((error: string) => {
console.log('本地流发布失败 ' + error);
})
.then(() => {
const el = document.querySelector("#local_stream");
if(el){
el.innerHTML = ""
}
localStream.play('local_stream');
console.log('本地流发布成功');
console.log(id , 123)
});
});
return {
url
}
async function init(fun: any, userSig: string): Promise<void>{
console.log(userSig)
const el = document.querySelector("#local_stream");
if(el){
el.innerHTML = ""
}
if (typeof id == "string") {
roominfo.value = await getliveinfo(parseInt(id))
console.log(roominfo.value)
}
}
})
client = TRTC.createClient({
mode: 'rtc',
sdkAppId: '1400400340',
userId: store.state.userinfo.memberid,
userSig: userSig
});
client.on('stream-added', (event: { stream: any }) => {
const remoteStream = event.stream;
console.log('远端流增加: ' + remoteStream.getId());
//订阅远端流
client.subscribe(remoteStream);
});
client.on('stream-subscribed', (event: { stream: any }) => {
const remoteStream = event.stream;
console.log(remoteStream);
// 播放远端流
remoteStream.play('s-' + remoteStream.userId_);
});
client
.join({ roomId: roominfo.value.roomid})
.catch((error: string) => {
console.error('进房失败 ' + error);
})
.then(() => {
console.log('进房成功');
// if(typeof id == "string"){
luzhi(roominfo.value.roomid)
// }
fun()
});
}
async function fenxiang(){
console.log(localStream)
await client.unpublish(localStream).then(() => {
// 关闭屏幕分享流
console.log("关闭")
// client.leave().then(() => {
// // leaving room success
// console.log("关闭成功")
// }).catch((error: string) => {
// console.error('leaving room failed: ' + error);
// });
const el = document.querySelector("#local_stream");
if(el){
el.innerHTML = ""
}
});
statie ? await shexiang() : await pingmu();
statie = !statie;
console.log(localStream.getId())
}
function guanbi(){
client.leave().then(() => {
// leaving room success
visible.value = false;
if(typeof id == "string"){
livestop(id, roominfo.value.roomid)
}
}).catch((error: string) => {
message.error(lan.$t('guanbishibai')+':' + error);
});
}
onMounted(async ()=>{
const si = setInterval(async ()=>{
if(store.state.userinfo.memberid != 0 && store.state.userinfo.memberid){
clearInterval(si);
userSing = await usersig(store.state.userinfo.memberid);
init(pingmu, userSing);
}
})
})
return{
fenxiang,
qiehuan,
roominfo,
guanbi,
visible,
lan
}
},
});
</script>

View File

@@ -0,0 +1,587 @@
<template>
<div class="liveing">
<div class="top">
<div class="left">{{lan.$t('zhiboyemian')}}</div>
<div class="right">
<img src="@/static/images/liveshare.png" alt="" @click="fenxiang()" />
<img src="@/static/images/liveend.png" alt="" @click="visible = true" />
</div>
</div>
<div class="info">
<LiveingWatcher :list="roominfo.studentlist" @cameta="sendtext" @vol="senvol"></LiveingWatcher>
<div class="LivePlaying">
<LivePlaying></LivePlaying>
<div class="comment">
<div class="commentitem" v-for="(item,index) in imlist" :key="index">
<span>{{item.time}}</span>
<span class="name"> {{item.name}} </span>
<span> {{item.text}} </span>
</div>
</div>
</div>
<div class="others">
<div class="othersitem" v-for="(item, index) in roominfo.studentlist" :key="index">
<div class="watcher"></div>
<div class="name">{{item.name}}</div>
<div class="sbox" :id="'s-' + item.memberid"></div>
<!-- <img src="" alt="" /> -->
</div>
<!-- <div class="othersitem">
<div class="watcher"></div>
<div class="name">asdsada</div>
<img src="" alt="" />
</div>
<div class="othersitem">
<div class="watcher"></div>
<div class="name">asdsada</div>
<img src="" alt="" />
</div>
<div class="othersitem">
<div class="watcher"></div>
<div class="name">asdsada</div>
<img src="" alt="" />
</div> -->
</div>
</div>
<a-modal v-model:visible="visible" title="Basic Modal" @ok="guanbi">
<p>{{lan.$t('querenguanbi')}}</p>
</a-modal>
<a-modal v-model:visible="xuanze" title="提示" okText="摄像头" cancelText="屏幕分享" @ok="xianze(1)" @cancel="xianze(0)" :closable="false" :maskClosable="false">
<p>请选择开播方式</p>
</a-modal>
</div>
</template>
<style lang="scss" scoped>
.liveing ::v-deep(.ant-breadcrumb) > span:last-child {
color: #08ae98;
}
.liveing {
width: 1320px;
height: 563px;
.top {
width: 1321px;
height: 57px;
background: white;
border-radius: 18px;
margin-top: 23px;
margin-bottom: 29px;
display: flex;
justify-content: space-between;
color: #121212;
font-size: 13px;
align-items: center;
.left {
margin-left: 40px;
font-weight: bold;
}
.right {
margin-right: 27px;
> img {
margin-right: 10px;
}
}
}
.info {
display: flex;
justify-content: space-between;
.LivePlaying {
border-radius: 18px;
margin: 0 35px;
.comment {
width: 797px;
height: 153px;
background: white;
border-radius: 18px;
margin-top: 29px;
padding: 30px;
font-size: 12px;
overflow-y: auto;
color: #121212;
.commentitem {
margin-bottom: 17px;
.name {
margin-left: 28px;
color: #08ae98;
font-weight: bold;
}
}
}
.liveinfo {
display: flex;
justify-content: space-between;
height: 58px;
align-items: center;
.left {
display: flex;
color: #121212;
font-size: 13px;
margin-left: 29px;
> div {
margin-right: 57px;
}
.icon {
width: 25px;
height: 24px;
margin-right: 6px;
}
}
.right {
width: 74px;
height: 29px;
border: 1px solid #08ae98;
border-radius: 3px;
margin-right: 29px;
color: #08ae98;
font-size: 13px;
line-height: 29px;
font-weight: bold;
text-align: center;
}
}
}
}
.others {
width: 252px;
height: 630px;
display: flex;
flex-direction: column;
.othersitem {
position: relative;
width: 100%;
height: 132px;
margin-bottom: 35px;
background-color: #eee;
border-radius: 18px 18px 0px 0px;
overflow: hidden;
.watcher {
width: 100%;
height: 29px;
background: #000000;
color: white;
position: absolute;
opacity: 0.1;
border-radius: 18px 18px 0px 0px;
top: 0;
}
> img {
width: 228px;
height: 132px;
border-radius: 18px;
}
.name {
position: absolute;
top: 9px;
left: 28px;
font-size: 12px;
color: #fff;
}
.sbox{
width: 100%;
height: 100%;
}
}
}
}
</style>
<script lang="ts">
import { defineComponent, onMounted, ref, resolveComponent } from "vue";
import LivePlaying from "@/components/LivePlaying.vue";
import LiveingWatcher from "@/components/LiveingWatcher.vue";
import TRTC from "trtc-js-sdk"
import { getliveinfo, livestop, luzhi, usersig } from '@/api';
import { useRoute } from 'vue-router';
import store from '@/store';
import { message } from 'ant-design-vue';
import { useI18n } from '@/utils/i18n';
import TIM from 'tim-js-sdk';
import dayjs from 'dayjs';
export default defineComponent({
components: {
LivePlaying,
LiveingWatcher,
},
setup() {
const lan: any = useI18n();
console.log(1);
let client: any;
let localStream: any;
let statie = true;
let userSing = '';
let type = false;
const visible = ref(false);
const roominfo = ref<any>([]);
console.log(useRoute())
const id = useRoute().query.id;
let tim: any;
TRTC.checkSystemRequirements().then((result: any) => {
if(!result) {
message.error(lan.$t('buzhichitonghua'))
}
})
async function qiehuan(){
// 1 屏幕分享 2 摄像头
client.unpublish(localStream)
localStream = type ? TRTC.createStream({ userid: store.state.userinfo.memberid, audio: true, screen: true }) : TRTC.createStream({ userId: 10, audio: false, video: true });
type = !type;
localStream.initialize().then(()=>{
client
.publish(localStream)
.catch((error: string) => {
console.error('本地流发布失败 ' + error);
})
.then(() => {
localStream.play('local_stream');
console.log('本地流发布成功');
});
});
}
async function shexiang(){
localStream = TRTC.createStream({ userId: store.state.userinfo.memberid, audio: true, video: true });
const id = localStream.getId();
await localStream
.initialize()
.catch((error: string) => {
console.error('初始化本地流失败 ' + error);
})
.then(() => {
console.log('初始化本地流成功');
client
.publish(localStream)
.catch((error: string) => {
console.error('本地流发布失败 ' + error);
})
.then(() => {
const el = document.querySelector("#local_stream");
if(el){
el.innerHTML = ""
}
localStream.play('local_stream');
console.log('本地流发布成功');
console.log(id, 123)
});
});
}
async function pingmu(){
const result = await TRTC.checkSystemRequirements()
console.log(result,11111)
if(!result) {
message.error(lan.$t('buzhichifenxiang'));
shexiang()
return ;
}
localStream = TRTC.createStream({ userid: store.state.userinfo.memberid, audio: true, screen: true });
const id = localStream.getId();
await localStream
.initialize()
.catch((error: string) => {
console.error('初始化本地流失败 ' + error);
message.error(lan.$t('xuanzefenxiangneirong'))
setTimeout(()=>{
pingmu()
}, 1000)
})
.then(() => {
console.log('初始化本地流成功');
client
.publish(localStream)
.catch((error: string) => {
console.log('本地流发布失败 ' + error);
})
.then(() => {
const el = document.querySelector("#local_stream");
if(el){
el.innerHTML = ""
}
localStream.play('local_stream');
console.log('本地流发布成功');
console.log(id , 123)
});
});
}
const imlist = ref<any>([])
async function init(fun: any, userSig: string): Promise<void>{
console.log(userSig)
const el = document.querySelector("#local_stream");
if(el){
el.innerHTML = ""
}
if (typeof id == "string") {
roominfo.value = await getliveinfo(parseInt(id))
console.log(roominfo.value)
}
client = TRTC.createClient({
mode: 'rtc',
sdkAppId: '1400435767',
userId: store.state.userinfo.memberid,
userSig: userSig
});
// 监听远端开启推流
client.on('stream-added', (event: { stream: any }) => {
const remoteStream = event.stream;
console.log('远端流增加: ' + remoteStream.getId());
//订阅远端流
client.subscribe(remoteStream);
});
// 远端流初始化成功 本地播放
client.on('stream-subscribed', (event: { stream: any }) => {
const remoteStream = event.stream;
console.log(remoteStream);
// 播放远端流
const el = document.querySelector('#s-' + remoteStream.userId_);
if(el){
el.innerHTML = ""
}
remoteStream.play('s-' + remoteStream.userId_);
});
// 远端关闭麦克风
client.on('mute-audio', (event: any) => {
const userId = event.userId;
console.log(userId, '远端关闭麦克风')
});
// 远端关闭摄像头
client.on('mute-video', (event: any) => {
const userId = event.userId;
console.log(userId, '远端关闭摄像头')
});
// 远端打开麦克风
client.on('unmute-audio', (event: any) => {
const userId = event.userId;
console.log(userId, '远端打开麦克风')
});
// 远端打开摄像头
client.on('unmute-video', (event: any) => {
const userId = event.userId;
console.log(userId, '远端打开摄像头')
});
client
.join({ roomId: roominfo.value.roomid})
.catch((error: string) => {
console.error('进房失败 ' + error);
})
.then(() => {
console.log('进房成功');
// if(typeof id == "string"){
luzhi(roominfo.value.roomid)
// }
fun()
});
// im 初始化
tim = TIM.create({
SDKAppID: 1400435767
}); // SDK 实例通常用 tim 表示
tim.setLogLevel(0);
tim.on(TIM.EVENT.MESSAGE_RECEIVED, function(event: any) {
// 收到推送的单聊、群聊、群提示、群系统通知的新消息,可通过遍历 event.data 获取消息列表数据并渲染到页面
// event.name - TIM.EVENT.MESSAGE_RECEIVED
// event.data - 存储 Message 对象的数组 - [Message]
for(const i in event.data){
console.log(event.data[i])
const now = dayjs(event.data[i].time)
if(!event.data[i].payload.text){
break;
}
imlist.value.push({
name: event.data[i].nick,
text: event.data[i].payload.text,
time: `${now.hour()}:${now.minute()}:${now.second()}`
})
const div = document.querySelector(".comment")
if(div){
div.scrollTop = div.scrollHeight
}
}
});
tim.on(TIM.EVENT.GROUP_LIST_UPDATED, function(event: any) {
// 收到群组列表更新通知,可通过遍历 event.data 获取群组列表数据并渲染到页面
// event.name - TIM.EVENT.GROUP_LIST_UPDATED
// event.data - 存储 Group 对象的数组 - [Group]
console.log(event.data)
});
tim.login({userID: store.state.userinfo.memberid.toString(), userSig: userSig}).then((res: any)=>{
console.log(res.data); // 登录成功
if (res.data.repeatLogin === true) {
// 标识账号已登录本次登录操作为重复登录。v2.5.1 起支持
console.log(res.data.errorInfo);
}
}).catch(function(imError: any) {
console.warn('login error:', imError); // 登录失败的相关信息
});
tim.on(TIM.EVENT.SDK_READY, function (){
const promise = tim.createGroup({
type: TIM.TYPES.GRP_AVCHATROOM,
name: 'live',
groupID: roominfo.value.roomid
});
promise.then(function(imResponse: any) { // 创建成功
console.log(imResponse.data.group); // 创建的群的资料
tim.joinGroup({
groupID: roominfo.value.roomid,
type: TIM.TYPES.GRP_AVCHATROOM
}).then((res: any)=>{
switch (res.data.status) {
case TIM.TYPES.JOIN_STATUS_WAIT_APPROVAL: // 等待管理员同意
break;
case TIM.TYPES.JOIN_STATUS_SUCCESS: // 加群成功
console.log(res.data.group); // 加入的群组资料
break;
case TIM.TYPES.JOIN_STATUS_ALREADY_IN_GROUP: // 已经在群中
break;
default:
break;
}
}).catch((err: any)=>{
console.log(err)
})
}).catch(function(imError: any) {
console.warn('createGroup error:', imError); // 创建群组失败的相关信息
tim.joinGroup({
groupID: roominfo.value.roomid,
type: TIM.TYPES.GRP_AVCHATROOM
}).then((res: any)=>{
switch (res.data.status) {
case TIM.TYPES.JOIN_STATUS_WAIT_APPROVAL: // 等待管理员同意
break;
case TIM.TYPES.JOIN_STATUS_SUCCESS: // 加群成功
console.log(res.data.group); // 加入的群组资料
break;
case TIM.TYPES.JOIN_STATUS_ALREADY_IN_GROUP: // 已经在群中
break;
default:
break;
}
}).catch((err: any)=>{
console.log(err)
})
});
});
}
async function fenxiang(){
console.log(localStream)
await client.unpublish(localStream).then(() => {
// 关闭屏幕分享流
console.log("关闭")
// client.leave().then(() => {
// // leaving room success
// console.log("关闭成功")
// }).catch((error: string) => {
// console.error('leaving room failed: ' + error);
// });
const el = document.querySelector("#local_stream");
if(el){
el.innerHTML = ""
}
});
statie ? await shexiang() : await pingmu();
statie = !statie;
console.log(localStream.getId())
}
function guanbi(){
client.leave().then(() => {
// leaving room success
visible.value = false;
if(typeof id == "string"){
livestop(id, roominfo.value.roomid)
}
}).catch((error: string) => {
message.error(lan.$t('guanbishibai')+':' + error);
});
}
function sendtext(id: number){
const m = tim.createTextMessage({
to: roominfo.value.roomid,
conversationType: TIM.TYPES.CONV_GROUP,
payload: {
text: `beelinkMuteUserId:${id},isClose:0`
}
});
const promise = tim.sendMessage(m);
promise.then(function(imResponse: any) {
// 发送成功
console.log(imResponse);
message.success("发送命令成功")
}).catch(function(imError: any) {
// 发送失败
message.error("发送命令失败")
console.warn('sendMessage error:', imError);
});
}
function sendvol(id: number){
const m = tim.createTextMessage({
to: roominfo.value.roomid,
conversationType: TIM.TYPES.CONV_GROUP,
payload: {
text: `beelinkTurnOffTheCameraUserId:${id},isClose:1
0`
}
});
const promise = tim.sendMessage(m);
promise.then(function(imResponse: any) {
// 发送成功
console.log(imResponse);
message.success("发送命令成功")
}).catch(function(imError: any) {
// 发送失败
message.error("发送命令失败")
console.warn('sendMessage error:', imError);
});
}
const xuanze = ref(true)
async function xianze(index: number){
if(store.state.userinfo.memberid != 0 && store.state.userinfo.memberid){
// clearInterval(si);
userSing = await usersig(store.state.userinfo.memberid);
init(index == 0 ? pingmu : shexiang, userSing);
xuanze.value = false;
}
}
// onMounted(async ()=>{
// const si = setInterval(async ()=>{
// })
// })
return{
fenxiang,
qiehuan,
roominfo,
guanbi,
visible,
lan,
sendtext,
imlist,
xianze,
xuanze
}
},
});
</script>

View File

@@ -4,7 +4,7 @@
<div class="title">直播报名学生</div>
<div class="sel">
<img src="@/static/images/sousuo.png" alt="" class="icon" />
<input type="text" placeholder="请输入想要搜索的学生姓名" v-model="condition.title" @keyup.enter="search(condition)"/>
<input type="text" placeholder="请输入想要搜索的学生姓名" v-model="condition.keyword" @keyup.enter="search(condition)"/>
</div>
</div>
<div class="mid">
@@ -20,7 +20,7 @@
<div class="infoitem">
<span class="label">所在国家:</span>
<span class="one-line-hide">{{i.live}}</span>
<span class="one-line-hide">{{i.country}}</span>
</div>
<div class="infoitem">
@@ -49,7 +49,9 @@
</div>
</div>
</div>
<template v-if="!teacherlikedlist.length">
<a-empty />
</template>
</div>
</div>
<!-- <div class="pages">
@@ -177,7 +179,7 @@ export default defineComponent({
});
const title=ref('')
const condition = ref<any>({
title:"",
keyword:"",
id:Number(useRoute().query.liveid)
})
onMounted(async () => {

View File

@@ -50,11 +50,13 @@
</div>
</div>
</div>
<template v-if="!teacherlikedlist.total">
<a-empty />
</template>
</div>
</div>
<div class="pages">
<a-pagination v-model:current="page" :total="teacherlikedlist.total" :showLessItems="true" @change="pagechange"/>
<a-pagination v-if="teacherlikedlist.total" v-model:current="page" :total="teacherlikedlist.total" :showLessItems="true" @change="pagechange"/>
</div>
</div>
</template>

View File

@@ -20,7 +20,11 @@
<input type="text" :placeholder="lan.$t('shipinsousuo')" @keyup.enter="sel()" v-model="input"/>
</div>
</div>
<template v-if="!videolist.total">
<a-empty />
</template>
<div class="list" v-if="tabindex == 4">
<VideoItem
v-for="(i, j) in videolist.data"
:key="j"
@@ -34,6 +38,7 @@
:status="i.status"
:watch="i.watch"
:share="i.share"
:transcoding="i.transcoding"
></VideoItem>
</div>
@@ -50,6 +55,7 @@
:livenum="i.statusname"
:status="i.status"
:watch="i.watch"
:transcoding="i.transcoding"
:share="i.share"
></VideoItem>
</div>
@@ -66,6 +72,7 @@
:livenum="i.statusname"
:status="i.status"
:watch="i.watch"
:transcoding="i.transcoding"
:share="i.share"
></VideoItem>
</div>
@@ -82,11 +89,12 @@
:livenum="i.statusname"
:status="i.status"
:watch="i.watch"
:transcoding="i.transcoding"
:share="i.share"
></VideoItem>
</div>
<div class="pages">
<a-pagination v-model:current="page" :total="videolist.total" :showLessItems="true" @change="pagechange" />
<a-pagination v-if="videolist.total" v-model:current="page" :total="videolist.total" :showLessItems="true" @change="pagechange" />
</div>
</div>
</template>

View File

@@ -1,10 +1,10 @@
<template>
<div class="videoinfo">
<div class="info">
<VideoPlay :url="result.fileurl"></VideoPlay>
<VideoCont :videoid="result.videoid" :date="result.createdAt" :watch="result.watch" :share="result.share" :status="result.status"></VideoCont>
<VideoPlay :title="result.title" :url="result.fileid"></VideoPlay>
<VideoCont :videoid="result.videoid" :yuanyin="result.statusdesc" :date="result.createdAt" :watch="result.watch" :share="result.share" :status="result.status"></VideoCont>
</div>
<VideoReview class="review"></VideoReview>
<VideoReview :videoinfo="result.score" class="review" v-if="result.status == 1"></VideoReview>
</div>
</template>
<style lang="scss" scoped>

View File

@@ -28,10 +28,10 @@
<div class="row" v-for="(item,index) in month.date" :key="index">
<div v-for="(i,j) in item" :key="j">
<div class="day">
<div :class="{ing: yue == 0 && month.day == i.day,old: (yue < 0 || (yue == 0 && month.day > i.day)) && i.list.length != 0 ,next: (yue > 0 || (yue == 0 && month.day < i.day)) && i.list.length != 0 }" @click="navto(i.time)">
<div :class="{ing:i.s == 1, old: i.s == 2 ,next: i.s == 0 }" @click="navto(i.time)">
{{i.day}}
<div class="item" v-for="(i,j) in i.list" :key="j">
<div></div><p>{{i}}</p>
<div class="item" @click.stop="gotolive(i.id, i.statit)" v-for="(i,j) in i.list" :key="j">
<div></div><p>{{i.title}}</p>
</div>
<!-- <span class="ing"></span> -->
<!-- <span class="next"></span> -->
@@ -99,6 +99,9 @@
width: 1320px;
height: 63px;
display: flex;
position: sticky;
top: -23px;
z-index: 1000;
>div{
width: 100%;
height: 100%;
@@ -214,11 +217,20 @@ export default defineComponent({
const day = getDay(res[i].dateline)
for(const j in month.value.date){
for(const k in month.value.date[j]){
if(yue.value == 0 && month.value.date[j][k].day == month.value.day){
month.value.date[j][k].s = 1
}
if(month.value.date[j][k].day == day){
if(month.value.date[j][k].list == undefined){
month.value.date[j][k].list = [];
}
month.value.date[j][k].list.push(res[i].title);
if(!month.value.date[j][k].s){
month.value.date[j][k].s = res[i].livestatus
}else if(month.value.date[j][k].s != 1){
res[i].livestatus == 0 && month.value.date[j][k].s == 2 ? month.value.date[j][k].s = 0 : month.value.date[j][k].s = 2
}
month.value.date[j][k].list.push({title: res[i].title, id: res[i].liveid, statit: res[i].livestatus});
console.log(day)
}
}
@@ -245,13 +257,25 @@ export default defineComponent({
function navto(date: string){
router.push("/regime/week?time=" + (!date? '' : date) )
}
function gotolive(id: string, s: number){
let url = '';
switch (s) {
case 1:
url = '/regime/liveing';
break;
default:
url = '/regime/livedetail';
}
router.push({path: url,query: { id: id }})
}
getdates(userid.value);
return {
month,
xia,
shang,
yue,
navto
navto,
gotolive
}
}
})

View File

@@ -6,7 +6,7 @@
上一周
</div>
{{week.year}}{{week.yue}}
<a-button type="primary" class="button" @click="navto()"> 月日历 </a-button>
<a-button type="primary" class="button" @click="navto('/regime/date')"> 月日历 </a-button>
<div @click="zhou++">
下一周
<img src="../../static/images/right.png" alt="" />
@@ -40,13 +40,13 @@
<div class="body">
<div class="row" v-for="item in 24" :key="item">
<div class="day date" :style="{'background-color': item - 1 == xs ? '#0DBBA4' : '', 'color': item - 1 == xs ? '#fff' : ''}">
{{ item > 10 ? item - 1 : "0" + (item - 1) }}:00-{{
{{ item > 9 ? item - 1 : "0" + (item - 1) }}:00-{{
item > 9 ? item : "0" + item
}}:00
</div>
<div v-for="i in 7" :key="i" >
<div class="day">
<div :class="zhuangtai(week.date[i -1].list[item - 1].zhuangtai)" v-if="week.date[i -1].list[item - 1].title != ''" :style="{'top': (week.date[i -1].list[item - 1].start / 60 * 0.63) + 'rem', 'min-height': (week.date[i -1].list[item - 1].num / 60 * 0.63) + 'rem' }">
<div :class="zhuangtai(week.date[i -1].list[item - 1].zhuangtai)" v-if="week.date[i -1].list[item - 1].title != ''" :style="{'top': (week.date[i -1].list[item - 1].start / 60 * 0.63) + 'rem', 'min-height': (week.date[i -1].list[item - 1].num / 60 * 0.63) + 'rem' }" @click="tolive(week.date[i -1].list[item - 1])">
<div class="one-line-hide" style="max-width: 1.5rem">
{{week.date[i -1].list[item - 1].title}}
</div>
@@ -122,6 +122,9 @@
width: 1320px;
height: 63px;
display: flex;
position: sticky;
top: -23px;
z-index: 1000;
> div {
width: 100%;
height: 100%;
@@ -189,6 +192,7 @@
justify-content: center;
font-size: 11px;
border-radius: 6px;
z-index: 999;
overflow: hidden;
> div:last-child {
font-size: 11px;
@@ -238,7 +242,7 @@
}
.times{
font-size: 11px;
color: #FFFA18;
color: #D12C2E;
}
}
}
@@ -258,7 +262,7 @@ export default defineComponent({
setup() {
const zhou = ref(0);
const time: any = useRoute().query.time;
const week = ref<any>(getweek(time));
const week = ref<any>(getweek(time, store.state.userinfo.zoneValue));
const userid = store.state.userinfo.memberid;
console.log(week.value);
function getdates(userid: number){
@@ -276,6 +280,7 @@ export default defineComponent({
week.value.date[j].list[gethour(res[i].dateline)].title = res[i].title
week.value.date[j].list[gethour(res[i].dateline)].time = gettime(res[i].dateline, res[i].livetime)
week.value.date[j].list[gethour(res[i].dateline)].zhuangtai = res[i].livestatus
week.value.date[j].list[gethour(res[i].dateline)].zid = res[i].liveid
}
}
}
@@ -289,7 +294,14 @@ export default defineComponent({
const times = ref('');
const xs = ref(0);
setInterval(()=>{
const now = dayjs();
/* eslint-disable */
const utc = require('dayjs/plugin/utc') // dependent on utc plugin
/* eslint-disable */
const timezone = require('dayjs/plugin/timezone')
dayjs.extend(utc)
dayjs.extend(timezone)
const days: any = dayjs;
const now = days().tz(store.state.userinfo.zoneValue)
const xiaoshi = now.hour()
const fenzhong = now.minute()
top.value = (xiaoshi + (fenzhong / 60)) * 0.63;
@@ -298,7 +310,7 @@ export default defineComponent({
}, 2000)
watch(zhou, (value) => {
week.value = getweek(time, value);
week.value = getweek(time, store.state.userinfo.zoneValue, value);
console.log(week.value);
getdates(userid)
});
@@ -315,8 +327,19 @@ export default defineComponent({
return 'old'
}
}
function navto(){
router.push("/regime/date")
function navto(url: string){
router.push(url)
}
function tolive(data: any){
let url = '';
console.log(data)
if(data.zhuangtai != 1){
url = '/regime/livedetail?id=' + data.zid
}else{
url = '/regime/liveing?id=' + data.zid
}
console.log(url)
router.push(url)
}
return {
zhou,
@@ -325,7 +348,8 @@ export default defineComponent({
zhuangtai,
top,
times,
xs
xs,
tolive
};
},
});

View File

@@ -1,4 +1,5 @@
{
"defaultSeverity": "error",
"compilerOptions": {
"target": "esnext",
"module": "esnext",
@@ -35,6 +36,10 @@
"tests/**/*.tsx"
],
"exclude": [
"node_modules"
]
"node_modules",
"tslint:recommended"
],
"rules": {
"no-var-requires": false
}
}

View File

@@ -2151,6 +2151,11 @@ babel-plugin-dynamic-import-node@^2.3.3:
dependencies:
object.assign "^4.1.0"
babel-plugin-transform-remove-console@^6.9.4:
version "6.9.4"
resolved "https://registry.npmjs.org/babel-plugin-transform-remove-console/-/babel-plugin-transform-remove-console-6.9.4.tgz#b980360c067384e24b357a588d807d3c83527780"
integrity sha1-uYA2DAZzhOJLNXpYjYB9PINSd4A=
balanced-match@^1.0.0:
version "1.0.0"
resolved "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz#89b4d199ab2bee49de164ea02b89ce462d71b767"
@@ -9240,6 +9245,11 @@ vod-js-sdk-v6@^1.4.10:
js-sha1 "^0.6.0"
uuid "^3.3.2"
vue-cropper@^0.5.5:
version "0.5.5"
resolved "https://registry.npmjs.org/vue-cropper/-/vue-cropper-0.5.5.tgz#9bd1ba563c7faa268abd52fb2af4c6c28d33c962"
integrity sha512-5mGaBlS1EwLxUFwHHX2Q8zOZSiVfBUjOfolR+ZNKwu7Rh3u+GhwHYOyFkgZHhhoQBBNdyVB28O6W+MpMimhCbA==
vue-eslint-parser@^7.0.0, vue-eslint-parser@^7.1.1:
version "7.1.1"
resolved "https://registry.npmjs.org/vue-eslint-parser/-/vue-eslint-parser-7.1.1.tgz#c43c1c715ff50778b9a7e9a4e16921185f3425d3"