@ -0,0 +1,20 @@ |
|||||
|
# Build and Release Folders |
||||
|
bin-debug/ |
||||
|
bin-release/ |
||||
|
[Oo]bj/ |
||||
|
[Bb]in/ |
||||
|
|
||||
|
# Other files and folders |
||||
|
.settings/ |
||||
|
/unpackage/ |
||||
|
node_modules |
||||
|
# Executables |
||||
|
*.swf |
||||
|
*.air |
||||
|
*.ipa |
||||
|
*.apk |
||||
|
*.zip |
||||
|
|
||||
|
# Project files, i.e. `.project`, `.actionScriptProperties` and `.flexProperties` |
||||
|
# should NOT be excluded as they contain compiler settings and other important |
||||
|
# information for Eclipse / Flash Builder. |
||||
@ -0,0 +1,33 @@ |
|||||
|
{ |
||||
|
// launch.json 配置了启动调试时相关设置,configurations下节点名称可为 app-plus/h5/mp-weixin/mp-baidu/mp-alipay/mp-qq/mp-toutiao/mp-360/ |
||||
|
// launchtype项可配置值为local或remote, local代表前端连本地云函数,remote代表前端连云端云函数 |
||||
|
"version" : "0.0", |
||||
|
"configurations" : [ |
||||
|
{ |
||||
|
"app-plus" : { |
||||
|
"launchtype" : "remote" |
||||
|
}, |
||||
|
"default" : { |
||||
|
"launchtype" : "local" |
||||
|
}, |
||||
|
"mp-weixin" : { |
||||
|
"launchtype" : "local" |
||||
|
}, |
||||
|
"type" : "uniCloud" |
||||
|
}, |
||||
|
{ |
||||
|
"openVueDevtools" : false, |
||||
|
"playground" : "custom", |
||||
|
"type" : "uni-app:app-ios" |
||||
|
}, |
||||
|
{ |
||||
|
"openVueDevtools" : false, |
||||
|
"playground" : "standard", |
||||
|
"type" : "uni-app:app-android" |
||||
|
}, |
||||
|
{ |
||||
|
"openVueDevtools" : false, |
||||
|
"type" : "uni-app:h5" |
||||
|
} |
||||
|
] |
||||
|
} |
||||
@ -0,0 +1,51 @@ |
|||||
|
-----BEGIN RSA PRIVATE KEY----- |
||||
|
MIIEowIBAAKCAQEA17mXxivjza/+LCpiNPKPTiiit0dcFiBAkOGN297tSiJjavsv |
||||
|
Pji04nGeBbi+z/2qb1+8/5r92QDy/Krp3xdHIuEGIwDll6UEa3Wcj3StqBnycAWc |
||||
|
JDlZ9dur7bpoVLF6O1/IeexfSIkqVkNmaKDS2kXKpllrgyx19ql3vXekFuFCwLIh |
||||
|
Ku6yLlcfEaKnWmd3h4vNV0X+q3ckK56w5IyPkxe6gOKpPbQ5TEB8J+w9X7u3MCKl |
||||
|
PfZPmLyJ53UzacPnkfH6SO+eHO+WSsauYtfnxfduuG7BzYndH6JO3A9m9GwIsM/o |
||||
|
N3WuMAk3DoFXK5dqnFhocXlsPizQOt+MZLYvLQIDAQABAoIBABo6VlmhvHCllSGJ |
||||
|
hacqVoIKLr5Zzrhh83ep9LVmxTLeO3gmUfgerN9bMPtBGvHuxWoFGdV54qMfmmtd |
||||
|
3FFjYyK6eSSIV2G4jnECO6a1aOenP/Keu/0re+SIcL7WixjA+zt8ycMJGgyXoK4c |
||||
|
Q7c01m3zTlArTwcDwNPchtJiWXDueQw0tjO2zdD2DA/hSohVUcgynJ6kDez1lQ35 |
||||
|
8p2LI0M4LGjn2NL5XN4PyarTvnslvG5Ik6Oo6wSTySqcddWHJm9+ozuVVqDrVqOY |
||||
|
R0bdm8g9PWDoPDiCLbx58c3MtFRoejBFhm3cQp16DEB1fbL20nnTQspXG5hvsYpP |
||||
|
uZEXbAECgYEA+pYztQ0+13BYoTxSMQMOUscxLigJXUcm3JnA8w95qG5jwkwosh52 |
||||
|
dqDNGAUmpeliokR0rI3xozXDsldX+onDGyExHHi8u9f03B7NMseoTK6edGjJEprl |
||||
|
syoAoGwg/GY6yle3yqEkRhchWxNYDewe+W1b6AK8JG7XMQEfU08r3rkCgYEA3GKZ |
||||
|
E212OyKgADaOVBqxwBN9bECxbne8KqsWRMnWN+cps0mqfPFrhDr62oqxwioh1ZAE |
||||
|
RWciBF7l5PrbaJ5NAjPuh6MzU6zBbJVkDqddySsbHExh69Uia81J1UG21GpPnjII |
||||
|
/h74dan6DYHJuxjFYDLqGrn81TCkd1JiikdfOhUCgYB4fTBvpebJgGOdY3vBxU5l |
||||
|
zxF+uBGIoGW3PNbiCFbe/fVJv4Tx4GPltnvnSNLEg+vBSlkvfzDo4Tkvz3+mIAeI |
||||
|
S/VpU0SsrbI3BTh1ajsqY+wc3SWRpJk+BLw4ZsWVlzI9iN/+tmzSptyLBkoYp6hd |
||||
|
FpBShr4gZotiLL/7Nt5JQQKBgQCZlKCGeGbHSRblbx96nuu2Jh7mnKLJj+lydq3b |
||||
|
HCkL5i0aQ0DrNzas/IkqWTMNU10mveksEHYVQ6jEDMlwO7kAyv30ShgPvLlCmU0U |
||||
|
JTBna4HGE7i9p1cIdxR36AaoOrnnTYkUxrJxFRYr6YGSv+10X6bjHy+BxhcnDCOd |
||||
|
p6VGDQKBgH+BVVOQ2I5ddO6ZICapfm6O3n32RPuSsZavvEE7T73kY0jY+gAf2q1j |
||||
|
KIZ9kH851D0q2ofmgly2lo9ctuV+4uq55wT07Bgz5UwTCoOQ8VE7sBC1aB6TP0ot |
||||
|
whW3ZXHQomC9aM+c11IcTsQAJ6suZfT7uZcazWhD8PN+BiluxTlm |
||||
|
-----END RSA PRIVATE KEY----- |
||||
|
-----BEGIN CERTIFICATE----- |
||||
|
MIID9zCCAt+gAwIBAgIJA2BOZCd6yrSRMA0GCSqGSIb3DQEBCwUAMGkxFDASBgNV |
||||
|
BAMTC2V4YW1wbGUub3JnMQswCQYDVQQGEwJVUzERMA8GA1UECBMIVmlyZ2luaWEx |
||||
|
EzARBgNVBAcTCkJsYWNrc2J1cmcxDTALBgNVBAoTBFRlc3QxDTALBgNVBAsTBFRl |
||||
|
c3QwHhcNMjIxMDIwMTI0MDUxWhcNMjIxMTE5MTI0MDUxWjBpMRQwEgYDVQQDEwtl |
||||
|
eGFtcGxlLm9yZzELMAkGA1UEBhMCVVMxETAPBgNVBAgTCFZpcmdpbmlhMRMwEQYD |
||||
|
VQQHEwpCbGFja3NidXJnMQ0wCwYDVQQKEwRUZXN0MQ0wCwYDVQQLEwRUZXN0MIIB |
||||
|
IjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA17mXxivjza/+LCpiNPKPTiii |
||||
|
t0dcFiBAkOGN297tSiJjavsvPji04nGeBbi+z/2qb1+8/5r92QDy/Krp3xdHIuEG |
||||
|
IwDll6UEa3Wcj3StqBnycAWcJDlZ9dur7bpoVLF6O1/IeexfSIkqVkNmaKDS2kXK |
||||
|
pllrgyx19ql3vXekFuFCwLIhKu6yLlcfEaKnWmd3h4vNV0X+q3ckK56w5IyPkxe6 |
||||
|
gOKpPbQ5TEB8J+w9X7u3MCKlPfZPmLyJ53UzacPnkfH6SO+eHO+WSsauYtfnxfdu |
||||
|
uG7BzYndH6JO3A9m9GwIsM/oN3WuMAk3DoFXK5dqnFhocXlsPizQOt+MZLYvLQID |
||||
|
AQABo4GhMIGeMAsGA1UdDwQEAwIC9DAxBgNVHSUEKjAoBggrBgEFBQcDAQYIKwYB |
||||
|
BQUHAwIGCCsGAQUFBwMDBggrBgEFBQcDCDBcBgNVHREEVTBTgglsb2NhbGhvc3SC |
||||
|
FWxvY2FsaG9zdC5sb2NhbGRvbWFpboIGbHZoLm1lgggqLmx2aC5tZYIFWzo6MV2H |
||||
|
BH8AAAGHEP6AAAAAAAAAAAAAAAAAAAEwDQYJKoZIhvcNAQELBQADggEBAEEPfOUj |
||||
|
4+CbTnJwjZIqz+4Qo9X7RnMTkuuckTWC/f4h8IXSuqF3ximcnFWrDu5uEw3x1wLM |
||||
|
VHedWIhTUw908dHW4flTTJfI/hQg1samK7XefVfGx7/0uRJi2tiPf5ztI45/z6cC |
||||
|
0dQxGZEu4t7mfu0sbfjvKtngDCdF5ZWKy4jjEWdW7CsJ7BSWuyp4dP/FVW9ntPz8 |
||||
|
m+qYYTIUI3QHQe1RoMmOKK+1Nqs7YkfB/avebt+xdv2VOekefqmYudpwDMbaQ+Zk |
||||
|
boqW/+u1WBpsWeXob+Fhmp3nM+j3G6KyNkPRbGBZcgfkdGrPdM8AvOrdGEcrK3rT |
||||
|
QKRyGCwDZqQjLgg= |
||||
|
-----END CERTIFICATE----- |
||||
@ -0,0 +1 @@ |
|||||
|
{"type":"module"} |
||||
@ -0,0 +1,881 @@ |
|||||
|
<script> |
||||
|
// 引入vue3 getCurrentInstance |
||||
|
import { getCurrentInstance } from 'vue' |
||||
|
import pinia from '@/store/index' |
||||
|
import { useMsgStore } from '@/store/message'; |
||||
|
import { useloginStore } from '@/store/login'; |
||||
|
import permision from "@/utils/permission.js" |
||||
|
import config from "@/common/config"; |
||||
|
import emoji from '@/utils/emoji.js'; |
||||
|
// #ifdef APP-PLUS |
||||
|
import db from './utils/db'; |
||||
|
import homeData from './service/homeData'; |
||||
|
import getSystemInfo from './service/getSystemInfo'; |
||||
|
import getMessageList from './service/getMessageList'; |
||||
|
import groupInfo from './service/groupInfo'; |
||||
|
import groupUserList from './service/groupUserList'; |
||||
|
import getUserInfo from './service/getUserInfo'; |
||||
|
// #endif |
||||
|
// #ifdef H5 |
||||
|
import VConsole from 'vconsole'; |
||||
|
// #endif |
||||
|
let vConsole = null; //移动H5调试器 |
||||
|
const msgStore = useMsgStore(pinia) |
||||
|
const userStore = useloginStore(pinia); |
||||
|
let keepAlive = null |
||||
|
let network_log = null |
||||
|
let networkCheckInterval = null |
||||
|
let emojiMap = [] |
||||
|
// #ifdef APP-PLUS |
||||
|
import appUpdate from '@/common/appUpdate.js'; |
||||
|
// 安卓设备引入保活插件,不使用保活或者编译ios的时候注释掉,只需要注释这一行即可。 |
||||
|
// keepAlive = uni.requireNativePlugin('Ba-KeepAlive'); |
||||
|
// #endif |
||||
|
export default { |
||||
|
async created() { |
||||
|
// #ifdef APP-PLUS |
||||
|
await db.createDatabase() |
||||
|
|
||||
|
await homeData.createTalbe(); |
||||
|
await getSystemInfo.createTalbe(); |
||||
|
await getMessageList.createTalbe(); |
||||
|
await groupInfo.createTalbe(); |
||||
|
await groupUserList.createTalbe(); |
||||
|
await getUserInfo.createTalbe(); |
||||
|
// #endif |
||||
|
// console.info('1111111'); |
||||
|
}, |
||||
|
onLaunch: function() { |
||||
|
this.startNetworkMonitor(); |
||||
|
this.network_log = uni.getStorageSync('network_log') |
||||
|
// this.network_log = msgStore.network_log |
||||
|
|
||||
|
const _this = this |
||||
|
if(this.network_log=='none') { |
||||
|
_this.getGroupData() |
||||
|
}else{ |
||||
|
// 获取全局配置 |
||||
|
userStore.getGlobalConfig(); |
||||
|
// console.log(config); |
||||
|
// _this.getGroupData() |
||||
|
} |
||||
|
|
||||
|
// 初始化APP设置 |
||||
|
let setting=uni.getStorageSync('appSetting') ?? ''; |
||||
|
if(!setting){ |
||||
|
userStore.setAppSetting({ |
||||
|
voiceStatus:true, |
||||
|
vibrateStatus:false, |
||||
|
circleAvatar:false |
||||
|
}); |
||||
|
}else{ |
||||
|
userStore.setAppSetting(setting); |
||||
|
} |
||||
|
|
||||
|
uni.getSystemInfo({ |
||||
|
success: function(e) { |
||||
|
let paddingB=0; |
||||
|
// 获取 appContext 上下文 |
||||
|
const {appContext} = getCurrentInstance(); |
||||
|
appContext.config.globalProperties.StatusBar = e.statusBarHeight; |
||||
|
// #ifndef MP |
||||
|
if (e.platform == 'android') { |
||||
|
appContext.config.globalProperties.CustomBar = e.statusBarHeight + 50; |
||||
|
} else { |
||||
|
appContext.config.globalProperties.CustomBar = e.statusBarHeight + 45; |
||||
|
}; |
||||
|
// #endif |
||||
|
// #ifdef MP-WEIXIN |
||||
|
|
||||
|
let custom = wx.getMenuButtonBoundingClientRect(); |
||||
|
appContext.config.globalProperties.Custom = custom; |
||||
|
appContext.config.globalProperties.CustomBar = custom.bottom + custom.top - e.statusBarHeight; |
||||
|
// #endif |
||||
|
|
||||
|
// #ifdef MP-ALIPAY |
||||
|
appContext.config.globalProperties.CustomBar = e.statusBarHeight + e.titleBarHeight; |
||||
|
// #endif |
||||
|
|
||||
|
// #ifdef MP |
||||
|
try { |
||||
|
|
||||
|
var res = uni.getSystemInfoSync(); |
||||
|
res.model = res.model.replace(' ', ''); |
||||
|
res.model = res.model.toLowerCase(); |
||||
|
var res1 = res.model.indexOf('iphonex'); |
||||
|
if(res1 > 5){res1 = -1;} |
||||
|
var res2 = res.model.indexOf('iphone1'); |
||||
|
if(res2 > 5){res2 = -1;} |
||||
|
if(res1 != -1 || res2 != -1){ |
||||
|
paddingB = uni.upx2px(50); |
||||
|
} |
||||
|
|
||||
|
} catch (e){return null;} |
||||
|
// #endif |
||||
|
// #ifdef H5 |
||||
|
paddingB = uni.upx2px(100); |
||||
|
// #endif |
||||
|
// 获取设备底部高度 |
||||
|
appContext.config.globalProperties.inlineTools=paddingB; |
||||
|
// 设置全局底部导航栏高度 |
||||
|
appContext.config.globalProperties.navBarHeight=uni.upx2px(100); |
||||
|
} |
||||
|
}) |
||||
|
|
||||
|
//启用H5调试模式 |
||||
|
// #ifdef H5 |
||||
|
this.loadVConsole(); |
||||
|
// #endif |
||||
|
|
||||
|
// #ifdef APP-PLUS |
||||
|
// 检查应用更新 |
||||
|
appUpdate(); |
||||
|
if (uni.getSystemInfoSync().platform === 'android' && keepAlive) { |
||||
|
// this.register(); |
||||
|
} |
||||
|
// 只有app才有推送权限 |
||||
|
uni.onPushMessage((res) => { |
||||
|
let data=res.data; |
||||
|
if(res.type=='click'){ |
||||
|
let playload=data.payload; |
||||
|
let toUser=playload.toContactId ? playload.toContactId : ''; |
||||
|
// 如果是音视频通话则不 |
||||
|
if(playload.type=='webrtc'){ |
||||
|
return; |
||||
|
} |
||||
|
uni.navigateTo({ |
||||
|
url:"/pages/message/chat?id=" + toUser |
||||
|
}) |
||||
|
} |
||||
|
}) |
||||
|
// #endif |
||||
|
}, |
||||
|
onUnload() { |
||||
|
this.scoketClose() |
||||
|
this.socketIo.traderDetailIndex = 100 // 初始化 tabIndex |
||||
|
}, |
||||
|
onShow: function() { |
||||
|
// #ifdef APP-PLUS |
||||
|
// 判断平台,如果是安卓就检测保活插件 |
||||
|
if (uni.getSystemInfoSync().platform === 'android' && keepAlive) { |
||||
|
// this.isRunning(); |
||||
|
} |
||||
|
// #endif |
||||
|
|
||||
|
if(!this.socketIo.checkStatus()){ |
||||
|
// console.log('ws断线了,重新链接!'); |
||||
|
uni.$emit('socketStatus',false); |
||||
|
this.getWebsocketData(); |
||||
|
} |
||||
|
this.appStatus=true; |
||||
|
var userInfo = uni.getStorageSync('userInfo'); |
||||
|
if(userInfo){ |
||||
|
this.getNoticeCount() |
||||
|
} |
||||
|
// console.log('App Show') |
||||
|
let emojiMap=[]; |
||||
|
// 解析所有表情 |
||||
|
emoji.forEach(function (item) { |
||||
|
let child=item.children; |
||||
|
if(child.length>0){ |
||||
|
child.forEach(function (val) { |
||||
|
let name=val.name; |
||||
|
let src=val.src; |
||||
|
emojiMap[name]=src; |
||||
|
}) |
||||
|
} |
||||
|
}); |
||||
|
this.emojiMap=emojiMap; |
||||
|
}, |
||||
|
|
||||
|
methods:{ |
||||
|
startNetworkMonitor() { |
||||
|
// 立即执行第一次检测 |
||||
|
this.checkAndSaveNetworkStatus(); |
||||
|
|
||||
|
// 设置定时器(每5秒检测一次) |
||||
|
this.networkCheckInterval = setInterval(() => { |
||||
|
this.checkAndSaveNetworkStatus(); |
||||
|
// if(this.network_log!=='none'&&this.$route.path!="/pages/login/404"){ |
||||
|
// userStore.getGlobalConfig(); |
||||
|
// }else if(this.$route.path=="/pages/login/404"){ |
||||
|
// clearInterval(this.networkCheckInterval) |
||||
|
// } |
||||
|
}, 5000); |
||||
|
}, |
||||
|
checkAndSaveNetworkStatus(){ |
||||
|
uni.getNetworkType({ |
||||
|
success (res) { |
||||
|
msgStore.network_log = res.networkType; |
||||
|
// userStore.getGlobalConfig(); |
||||
|
uni.setStorageSync('network_log', res.networkType); |
||||
|
// uni.setStorageSync('network_log', 'none'); |
||||
|
} |
||||
|
}); |
||||
|
}, |
||||
|
async getGroupData(){ |
||||
|
let groups = await getSystemInfo.getList(); |
||||
|
groups.forEach(item => { |
||||
|
item.sysInfo = JSON.parse(item.sysInfo); |
||||
|
item.chatInfo = JSON.parse(item.chatInfo); |
||||
|
item.fileUpload = JSON.parse(item.fileUpload); |
||||
|
item.compass = JSON.parse(item.compass); |
||||
|
item.demon_mode = JSON.parse(item.demon_mode); |
||||
|
}) |
||||
|
let [result] = groups |
||||
|
console.info('处理后的数据:', result) |
||||
|
uni.setStorageSync('globalConfig',result) |
||||
|
}, |
||||
|
// 开启调试模式 |
||||
|
loadVConsole() { //初始化vConsole,用于H5调试用 |
||||
|
if (config.isVConsole) { //开启调试时 |
||||
|
let systemInfo = uni.getSystemInfoSync(); |
||||
|
if (!(systemInfo.uniPlatform == 'app' || systemInfo.uniPlatform == 'web')) { // 当为app或者H5时 |
||||
|
return; |
||||
|
} |
||||
|
vConsole = new VConsole({ |
||||
|
defaultPlugins: ['system', 'network', 'element', 'storage'], |
||||
|
// 可以在此设定要默认加载的面板 |
||||
|
maxLogNumber: 1000, |
||||
|
// disableLogScrolling: true, |
||||
|
onReady: () => { |
||||
|
console.log('vConsole: onReady'); |
||||
|
// 置顶最高层级 |
||||
|
var vcSwitch = document.getElementsByClassName('vc-switch')[0]; |
||||
|
vcSwitch.style.zIndex = '9999999999'; |
||||
|
var vcMask = document.getElementsByClassName('vc-mask')[0]; |
||||
|
vcMask.style.zIndex = '9999999999'; |
||||
|
var vcPanel = document.getElementsByClassName('vc-panel')[0]; |
||||
|
vcPanel.style.zIndex = '9999999999'; |
||||
|
}, |
||||
|
onClearLog: () => { |
||||
|
console.log('vConsole: onClearLog'); |
||||
|
} |
||||
|
}); |
||||
|
} |
||||
|
}, |
||||
|
destroyVConsole() { |
||||
|
// 结束调试后,可移除掉 |
||||
|
vConsole.destroy(); |
||||
|
}, |
||||
|
isRunning() { //是否正在运行 |
||||
|
keepAlive.isRunning((res) => { |
||||
|
if(!res.isRunning){ |
||||
|
this.restart(); |
||||
|
}else{ |
||||
|
console.info('保活组件运行中'); |
||||
|
} |
||||
|
}); |
||||
|
}, |
||||
|
restart() { //重启 |
||||
|
keepAlive.restart((res) => { |
||||
|
console.info('重启成功!'); |
||||
|
}); |
||||
|
}, |
||||
|
register() { //注册 |
||||
|
keepAlive.register({ |
||||
|
channelId: 'Ba-KeepAlive', |
||||
|
channelName: "Ba-KeepAlive", |
||||
|
title: "消息服务正在运行中", //可以修改为自己想展示的文字 |
||||
|
content: '用于接收消息的常驻通知,请保留!', //可以修改为自己想展示的文字 |
||||
|
}, |
||||
|
(res) => { |
||||
|
console.log('保活注册成功'); |
||||
|
}); |
||||
|
}, |
||||
|
scoketClose() { |
||||
|
this.socketIo.connectNum = 1 |
||||
|
const data = { |
||||
|
type: "close" |
||||
|
}; |
||||
|
this.socketIo.send(data); // 这是给后端发送特定数据 关闭推送 |
||||
|
this.socketIo.Close(); // 主动 关闭连接 , 不会重连 |
||||
|
}, |
||||
|
getNoticeCount(){ |
||||
|
this.$api.compaApi.getNoticeCount().then(res => { |
||||
|
msgStore.getCount(res.data.count); |
||||
|
}) |
||||
|
}, |
||||
|
getWebsocketData() { |
||||
|
// 获取用户信息 |
||||
|
var userInfo = uni.getStorageSync('userInfo'); |
||||
|
var _this = this; |
||||
|
// 要发送的数据包 |
||||
|
const data = { |
||||
|
type: "ping" |
||||
|
}; |
||||
|
// 打开连接 |
||||
|
this.socketIo.connectSocketInit(data); |
||||
|
uni.$off("getPositonsOrder"); |
||||
|
// 接收数据,全局监听 |
||||
|
uni.$on("getPositonsOrder", (res) => { |
||||
|
var userInfo = uni.getStorageSync('userInfo'); |
||||
|
let data=res.data; |
||||
|
this.connect = true; |
||||
|
switch (res['type']) { |
||||
|
// 服务端ping客户端 |
||||
|
case 'ping': |
||||
|
this.socketIo.send({ |
||||
|
type: "pong" |
||||
|
}); |
||||
|
break; |
||||
|
case 'pong': |
||||
|
userStore.$patch({ |
||||
|
multiport: res.multiport |
||||
|
}) |
||||
|
break; |
||||
|
// 登录 更新用户列表 |
||||
|
case 'init': |
||||
|
// 设置全局clientId |
||||
|
uni.setStorageSync('client_id', res.client_id); |
||||
|
if(userInfo){ |
||||
|
this.$api.LoginApi.bindUid({ |
||||
|
client_id: res['client_id'], |
||||
|
user_id: userInfo.user_id, |
||||
|
cid:uni.getStorageSync('cid') |
||||
|
}).then(e => { |
||||
|
this.socketIo.send({ |
||||
|
type: "bindUid", |
||||
|
user_id: userInfo.user_id, |
||||
|
token:uni.getStorageSync("authToken") |
||||
|
}); |
||||
|
}).catch(error => {}) |
||||
|
} |
||||
|
|
||||
|
break; |
||||
|
//上线、下线通知 |
||||
|
case "isOnline": |
||||
|
msgStore.updateContacts({ |
||||
|
id: data.id, |
||||
|
is_online:data.is_online |
||||
|
}); |
||||
|
// 如果是下线,并且和通话的是用一个人,就将通话锁定关闭 |
||||
|
if(!data.is_online && msgStore.webrtcLock==data.id){ |
||||
|
msgStore.webrtcLock=false; |
||||
|
} |
||||
|
break; |
||||
|
case "offline": |
||||
|
let clientId=uni.getStorageSync('client_id'); |
||||
|
let globalConfig=uni.getStorageSync('globalConfig'); |
||||
|
// 如果开启了多设备同时登录,则不走后面的逻辑 |
||||
|
if(globalConfig.sysInfo.multipleLogin==1){ |
||||
|
break; |
||||
|
}else if(data.id==userInfo.user_id && data.client_id!=clientId && data.isMobile){ |
||||
|
uni.showToast({ |
||||
|
title: "您的账号在其他设备登录,已被迫下线!", |
||||
|
icon: "none", |
||||
|
duration: 2500 |
||||
|
}) |
||||
|
userStore.logout(); |
||||
|
} |
||||
|
break; |
||||
|
case "updateConfig": |
||||
|
uni.setStorageSync('globalConfig',data); |
||||
|
userStore.globalConfig=data; |
||||
|
if(data.sysInfo.state==0){ |
||||
|
uni.navigateTo({ |
||||
|
url:'/pages/login/404' |
||||
|
}) |
||||
|
} |
||||
|
break; |
||||
|
case 'simple': |
||||
|
case 'group': |
||||
|
// 只要不是自己发的,才可以播放声音 |
||||
|
if (data.fromUser.id != userInfo.user_id) { |
||||
|
const contact = msgStore.getContact(data.toContactId,data); |
||||
|
// 如果开启了声音才播放 |
||||
|
if (data.toContactId=='system' || contact.is_notice == 1) { |
||||
|
this.playSound(); |
||||
|
} |
||||
|
// #ifdef APP-PLUS |
||||
|
var self=this; |
||||
|
const list = [] |
||||
|
let imgSrcs = [] |
||||
|
let emojiSrcs = [] |
||||
|
list.push(data) |
||||
|
list.forEach((res)=>{ |
||||
|
res.content = this.emojiToHtml(res.content) |
||||
|
const parts = res.fromUser.avatar.split('/') |
||||
|
let lastPart = parts.pop() || parts.pop() || '' |
||||
|
const isNumber = !isNaN(lastPart)&&!isNaN(parseFloat(lastPart)); |
||||
|
res.fromUser.imgname = isNumber ? lastPart+'.png' : lastPart; |
||||
|
|
||||
|
|
||||
|
if(res.type=='image'||res.type=='video'||res.type=='emoji'){ |
||||
|
const parts1 = res.content.split('/') |
||||
|
let lastPart1 = parts1.pop() || parts1.pop() || '' |
||||
|
res.imgname = lastPart1 |
||||
|
uni.downloadFile({ url: res.content,success: (downloadResult) => { |
||||
|
self.saveToPermanentStorage(downloadResult.tempFilePath); |
||||
|
}}) |
||||
|
}else if(res.type=='text'){ |
||||
|
const regex = /<img\s+[^>]*src="([^"]+)"/g; |
||||
|
let match; |
||||
|
while ((match = regex.exec(res.content)) !== null) { |
||||
|
imgSrcs.push(match[1]); |
||||
|
} |
||||
|
if(imgSrcs.length!==0){ |
||||
|
imgSrcs.forEach((img)=>{ |
||||
|
const parts = img.split('/') |
||||
|
let lastPart = parts.pop() || parts.pop() || '' |
||||
|
emojiSrcs.push(lastPart) |
||||
|
uni.downloadFile({ url: img,success: (downloadResult) => { |
||||
|
self.saveToPermanentStorage(downloadResult.tempFilePath); |
||||
|
}}) |
||||
|
}) |
||||
|
res.imgname = emojiSrcs |
||||
|
} |
||||
|
} |
||||
|
}) |
||||
|
|
||||
|
// console.log(list); |
||||
|
this.insertdata(list) |
||||
|
// #endif |
||||
|
} |
||||
|
this.appendMessage(res); |
||||
|
break; |
||||
|
case "setChatTop": |
||||
|
msgStore.updateContacts({ |
||||
|
id: data.id, |
||||
|
is_top: data.is_top |
||||
|
}); |
||||
|
break; |
||||
|
case "setIsNotice": |
||||
|
msgStore.updateContacts({ |
||||
|
id: data.id, |
||||
|
is_notice: data.is_notice |
||||
|
}); |
||||
|
break; |
||||
|
// 新增加了群聊 |
||||
|
case "addGroup": |
||||
|
msgStore.appendContacts(data); |
||||
|
this.$api.LoginApi.bindGroup({ client_id: uni.getStorageSync('client_id'), group_id: data.id }); |
||||
|
// uni.$emit('initContacts', true) |
||||
|
break; |
||||
|
// 设置群管理员 |
||||
|
case "setManager": |
||||
|
case "addGroupUser": |
||||
|
case "removeUser": |
||||
|
if(res['type']=='removeUser' && data.user_id == userInfo.user_id){ |
||||
|
msgStore.deleteContacts({ |
||||
|
id: data.group_id |
||||
|
}) |
||||
|
}else{ |
||||
|
msgStore.updateContacts({ |
||||
|
id: data.group_id, |
||||
|
avatar: data.avatar |
||||
|
}); |
||||
|
uni.$emit('updateGroup',res); |
||||
|
} |
||||
|
|
||||
|
break; |
||||
|
// 撤回消息 |
||||
|
case "undoMessage": |
||||
|
const tabid = uni.getStorageSync('tabid') |
||||
|
if(data.is_last){ |
||||
|
msgStore.updateContacts({ |
||||
|
id: data.toContactId, |
||||
|
lastContent: data.content |
||||
|
}); |
||||
|
}else if(data.chat_identify&&data.from_user==tabid){ |
||||
|
msgStore.checkMsg(data); |
||||
|
msgStore.appendMsg(data); |
||||
|
} |
||||
|
break; |
||||
|
// 删除用户消息 |
||||
|
case "delUserAllMsg": |
||||
|
// #ifdef APP-PLUS |
||||
|
this.deleteList1('user') |
||||
|
// #endif |
||||
|
break; |
||||
|
// 删除群主消息 |
||||
|
case "delGroupAllMsg": |
||||
|
// #ifdef APP-PLUS |
||||
|
this.deleteList1('group') |
||||
|
// #endif |
||||
|
break; |
||||
|
case "delMessageAll": |
||||
|
// #ifdef APP-PLUS |
||||
|
if(data.form_user){ |
||||
|
this.deleteList(data) |
||||
|
// console.log('group_id',data) |
||||
|
}else if(data.group_id){ |
||||
|
data.group_id = "'"+'group-' + data.group_id+"'" |
||||
|
this.deleteList(data) |
||||
|
} |
||||
|
// #endif |
||||
|
break; |
||||
|
case "delSystemAllMsg": |
||||
|
this.delAllMsg() |
||||
|
break; |
||||
|
// 修改群组名称 |
||||
|
case "editGroupName": |
||||
|
msgStore.updateContacts({ |
||||
|
id: data.id, |
||||
|
displayName: data.displayName |
||||
|
}); |
||||
|
break; |
||||
|
case "removeGroup": |
||||
|
msgStore.deleteContacts({ |
||||
|
id: data.group_id |
||||
|
}) |
||||
|
break; |
||||
|
// 发布公告 |
||||
|
case "setNotice": |
||||
|
msgStore.updateContacts({ |
||||
|
id: data.group_id, |
||||
|
notice: data.notice |
||||
|
}); |
||||
|
break; |
||||
|
// 群聊设置 |
||||
|
case "groupSetting": |
||||
|
msgStore.updateContacts({ |
||||
|
id: data.group_id, |
||||
|
setting: data.setting |
||||
|
}); |
||||
|
break; |
||||
|
case "appendContact": |
||||
|
msgStore.appendContacts(data); |
||||
|
break; |
||||
|
case "postsNotice": |
||||
|
_this.getNoticeCount() |
||||
|
break; |
||||
|
case 'webrtc': |
||||
|
let platform='h5'; |
||||
|
//#ifdef H5 |
||||
|
platform='h5'; |
||||
|
//#endif |
||||
|
//#ifdef APP-PLUS |
||||
|
platform= 'app'; |
||||
|
//#endif |
||||
|
if(data.fromUser.id==userInfo.user_id){ |
||||
|
// 挂断的情况下解锁webrtc |
||||
|
if([902,903,905,906,907].includes(parseInt(data.extends.code))){ |
||||
|
msgStore.webrtcLock=false; |
||||
|
} |
||||
|
// 如果是当前设备发出的消息则不处理 |
||||
|
if(data.extends.isMobile==1 || data.extends.event=='calling'){ |
||||
|
if(data.extends.event=='calling'){ |
||||
|
this.appendMessage(res); |
||||
|
} |
||||
|
return; |
||||
|
} |
||||
|
|
||||
|
} |
||||
|
// 如果是多端在线,要将在通话中的用户锁定 |
||||
|
if(data.extends.event == 'offer' || data.extends.event == 'answer' ){ |
||||
|
msgStore.webrtcLock=true; |
||||
|
}else if(data.extends.event == 'hangup'){ |
||||
|
msgStore.webrtcLock=false; |
||||
|
} |
||||
|
if(data.extends.event == 'calling'){ |
||||
|
this.appendMessage(res); |
||||
|
const allroutes = getCurrentPages(); |
||||
|
const cureentRoute = allroutes[allroutes.length - 1].route; |
||||
|
// 如果当前已经在通话中,通知对方忙线中 |
||||
|
if (cureentRoute == 'pages/message/call') { |
||||
|
this.$api.msgApi.sendToMsg({ |
||||
|
toContactId:data.fromUser.user_id, |
||||
|
type:data.extends.type, |
||||
|
event:'busy', |
||||
|
status:data.extends.status, |
||||
|
code:907, |
||||
|
id:data.id, |
||||
|
msg_id:data.msg_id, |
||||
|
}) |
||||
|
}else{ |
||||
|
|
||||
|
// 小程序不支持音视频聊天 |
||||
|
//#ifdef APP-PLUS || H5 |
||||
|
msgStore.webrtcLock=data.fromUser.user_id; |
||||
|
uni.navigateTo({ |
||||
|
url: '/pages/message/call?msg_id='+data.id+'&type='+data.extends.type+'&status='+data.extends.status+'&id='+data.fromUser.user_id+'&name='+data.fromUser.realname+'&avatar='+encodeURI(data.fromUser.avatar) |
||||
|
}) |
||||
|
//#endif |
||||
|
} |
||||
|
|
||||
|
|
||||
|
}else{ |
||||
|
uni.$emit('webrtcConn',data); |
||||
|
} |
||||
|
break; |
||||
|
default: |
||||
|
break; |
||||
|
} |
||||
|
}) |
||||
|
// 错误时做些什么 |
||||
|
uni.$on("connectError", () => { |
||||
|
this.connect = false |
||||
|
this.scoketError = true |
||||
|
uni.$emit('socketStatus',false); |
||||
|
}) |
||||
|
}, |
||||
|
async insertdata(data){ |
||||
|
// console.log(data,'231'); |
||||
|
await getMessageList.batchInsertOrUpdate(data) |
||||
|
}, |
||||
|
async deleteList(data){ |
||||
|
await getMessageList.deleteList(data) |
||||
|
}, |
||||
|
async deleteList1(val){ |
||||
|
const list = {group_id:val=='user'?0:1} |
||||
|
await getMessageList.deleteallList(list) |
||||
|
}, |
||||
|
async delAllMsg(){ |
||||
|
await getMessageList.delAllMsg() |
||||
|
this.deleteImageFolder() |
||||
|
}, |
||||
|
deleteImageFolder() { |
||||
|
plus.io.resolveLocalFileSystemURL('_doc', function(docDir) { |
||||
|
docDir.getDirectory('image', { create: false }, function(imageDir) { |
||||
|
// 目录存在,删除它(包括所有内容) |
||||
|
imageDir.removeRecursively(function() { |
||||
|
console.log('删除成功'); |
||||
|
}, function(error) { |
||||
|
console.error('删除失败:', error); |
||||
|
}); |
||||
|
}, function(error) { |
||||
|
// 目录不存在(错误码 1),也算成功 |
||||
|
if (error.code === 1) { |
||||
|
console.log('目录不存在,无需删除'); |
||||
|
} else { |
||||
|
console.error('获取目录失败:', error); |
||||
|
} |
||||
|
}); |
||||
|
}, function(error) { |
||||
|
console.error('无法访问 _doc 目录:', error); |
||||
|
}); |
||||
|
}, |
||||
|
emojiToHtml(str){ |
||||
|
if(!str){ |
||||
|
return; |
||||
|
} |
||||
|
let emojiMap=this.emojiMap; |
||||
|
return str.replace(/\[!(\w+)\]/gi, function (str, match) { |
||||
|
var file = match; |
||||
|
return emojiMap[file] ? "<img style=\"width:18px;height:18px;margin-right:5px\" emoji-name=\"".concat(match, "\" src=\"").concat(emojiMap[file], "\" />") : "[!".concat(match, "]"); |
||||
|
}); |
||||
|
}, |
||||
|
appendMessage(res){ |
||||
|
let data=res.data; |
||||
|
let userInfo = uni.getStorageSync('userInfo'); |
||||
|
// 确定接受人是谁 |
||||
|
let toUser = data.toContactId; |
||||
|
if(data.toContactId == userInfo.user_id){ |
||||
|
toUser = data.toUser |
||||
|
} |
||||
|
let contact=msgStore.getContact(toUser,data); |
||||
|
if(data.toContactId=='system'){ |
||||
|
// 系统消息只需要把未读数增加一 |
||||
|
msgStore.$patch({ |
||||
|
sysUnread: msgStore.sysUnread += 1, |
||||
|
}) |
||||
|
}else{ |
||||
|
// 判断是否是自己,自己发的看接收人是谁,去更新接受人的信息 |
||||
|
let addUnread = 1; |
||||
|
// 自己发送的不需要加 |
||||
|
if(data.fromUser.id==userInfo.user_id){ |
||||
|
addUnread = 0; |
||||
|
} |
||||
|
let at=0; |
||||
|
// 如果at参数包含了我自己,就要增加@的数量 |
||||
|
if(data.at.includes(userInfo.user_id)){ |
||||
|
at=1; |
||||
|
} |
||||
|
if (contact) { |
||||
|
contact.lastContent = data.content; |
||||
|
contact.lastSendTime = data.sendTime*1000; |
||||
|
contact.type = data.type; |
||||
|
contact.unread += addUnread; |
||||
|
contact.is_at += at; |
||||
|
// 更新联系人信息 |
||||
|
msgStore.updateContacts(contact); |
||||
|
} else { |
||||
|
let newContact = { |
||||
|
id: data.toContactId, |
||||
|
displayName: data.fromUser.displayName, |
||||
|
avatar: data.fromUser.avatar, |
||||
|
lastContent: data.content, |
||||
|
lastSendTime: data.sendTime*1000, |
||||
|
is_group: data.is_group, |
||||
|
unread: addUnread, |
||||
|
is_top: 0, |
||||
|
dep_id: 0, |
||||
|
is_at:at |
||||
|
} |
||||
|
msgStore.appendContacts(newContact) |
||||
|
} |
||||
|
msgStore.catchSocketAction(res); |
||||
|
} |
||||
|
// #ifdef APP-PLUS |
||||
|
let appStatus=this.appStatus; |
||||
|
// 如果app在后台运行,不是自己发的,并且开启了通知,就创建通知栏 |
||||
|
if(!appStatus && data.fromUser.id!=userInfo.user_id && contact.is_notice==1){ |
||||
|
this.createPushMsg(data,contact); |
||||
|
} |
||||
|
// #endif |
||||
|
}, |
||||
|
createPushMsg(data,contact){ |
||||
|
console.info("创建通知栏"); |
||||
|
var regex = /<[^>]+>/g; // 定义正则表达式,匹配所有的HTML标签 |
||||
|
let content=data.content.replace(regex, ''); // 将匹配到的HTML标签替换为空字符串 |
||||
|
if(!['text','event','location','contact','create'].includes(data.type)){ |
||||
|
let callVideo=data.extends.type ?? 0; |
||||
|
content=this.$util.getMsgType(data.type,callVideo); |
||||
|
} |
||||
|
if(data.is_group==1){ |
||||
|
content=data.fromUser.displayName+':'+content; |
||||
|
} |
||||
|
let message={ |
||||
|
title:contact.displayName, |
||||
|
content:content, |
||||
|
payload:data, |
||||
|
} |
||||
|
|
||||
|
let systemInfo = uni.getSystemInfoSync(); |
||||
|
// 判断平台,如果是安卓就去下载头像并展示头像 |
||||
|
if (systemInfo.platform === 'android') { |
||||
|
uni.downloadFile({ |
||||
|
url: contact.avatar, |
||||
|
success: (res) => { |
||||
|
if (res.statusCode === 200) { |
||||
|
message.icon=res.tempFilePath |
||||
|
uni.createPushMessage(message) |
||||
|
} |
||||
|
} |
||||
|
}); |
||||
|
} else if (systemInfo.platform === 'ios') { |
||||
|
uni.createPushMessage(message) |
||||
|
} |
||||
|
}, |
||||
|
playSound() { |
||||
|
let setting=uni.getStorageSync('appSetting') ?? ''; |
||||
|
if(setting.voiceStatus){ |
||||
|
let _this = this |
||||
|
// _this.playing = true |
||||
|
const innerAudioContext = uni.createInnerAudioContext(); |
||||
|
innerAudioContext.autoplay = true; |
||||
|
innerAudioContext.src = config.apiUrl+'/static/voice/notify.mp3'; |
||||
|
innerAudioContext.onError((res) => { |
||||
|
//如果音频没有正常播放 |
||||
|
}) |
||||
|
innerAudioContext.onStop((res) => { |
||||
|
// _this.playing=false |
||||
|
_this.$forceUpdate() |
||||
|
}) |
||||
|
} |
||||
|
if(setting.vibrateStatus){ |
||||
|
uni.vibrateLong({ |
||||
|
success: function () { |
||||
|
console.log('手机震动'); |
||||
|
} |
||||
|
}); |
||||
|
} |
||||
|
|
||||
|
}, |
||||
|
// App端持久化存储实现 |
||||
|
saveToPermanentStorage(tempPath) { |
||||
|
return new Promise((resolve, reject) => { |
||||
|
// 获取应用文档目录(持久化存储) |
||||
|
plus.io.resolveLocalFileSystemURL( |
||||
|
'_doc', |
||||
|
(docDir) => { |
||||
|
// 创建目标路径 |
||||
|
docDir.getDirectory( |
||||
|
'image', |
||||
|
{ create: true, exclusive: false }, |
||||
|
(entry) => { |
||||
|
// 从临时路径获取文件名 |
||||
|
const fileName = this.getFileName(tempPath); |
||||
|
const fileName1 = this.getFileName(docDir.fullPath + 'image/' +fileName); |
||||
|
// console.log(fileName); |
||||
|
// console.log(fileName1); |
||||
|
// 新增:检查文件是否存在 |
||||
|
entry.getFile(fileName1,{ create: false }, // 不创建新文件 |
||||
|
(fileEntry) => { |
||||
|
// console.log('文件已存在,拒绝操作'); |
||||
|
// 文件已存在,拒绝操作 |
||||
|
reject(new Error('File already exists: ' + fileName)); |
||||
|
}, |
||||
|
(error) => { |
||||
|
// console.log(error); |
||||
|
// 文件不存在(或发生其他错误),继续复制操作 |
||||
|
if (error.code === 14) { // 1表示文件不存在(不同平台错误码可能不同) |
||||
|
this.copyFile(tempPath, entry, fileName, resolve, reject); |
||||
|
} else { |
||||
|
reject(error); |
||||
|
} |
||||
|
} |
||||
|
); |
||||
|
}, |
||||
|
(error) => { |
||||
|
reject(error); |
||||
|
} |
||||
|
); |
||||
|
}, |
||||
|
(error) => { |
||||
|
reject(error); |
||||
|
} |
||||
|
); |
||||
|
}); |
||||
|
}, |
||||
|
|
||||
|
// 提取复制逻辑为独立方法 |
||||
|
copyFile(tempPath, targetDir, fileName, resolve, reject) { |
||||
|
plus.io.resolveLocalFileSystemURL( |
||||
|
tempPath, |
||||
|
(tempEntry) => { |
||||
|
tempEntry.copyTo( |
||||
|
targetDir, |
||||
|
fileName, |
||||
|
(newEntry) => { |
||||
|
resolve(newEntry.toLocalURL()); |
||||
|
}, |
||||
|
(error) => { |
||||
|
reject(error); |
||||
|
} |
||||
|
); |
||||
|
}, |
||||
|
(error) => { |
||||
|
reject(error); |
||||
|
} |
||||
|
); |
||||
|
}, |
||||
|
|
||||
|
// 获取文件名工具方法 |
||||
|
getFileName(path) { |
||||
|
const index = path.lastIndexOf('/'); |
||||
|
let fileName = path.substr(index + 1); |
||||
|
fileName = fileName.replace(/\(\d+\)(?=\.[^./]+$)/, ''); |
||||
|
return fileName; |
||||
|
}, |
||||
|
}, |
||||
|
onHide: function() { |
||||
|
this.appStatus=false; |
||||
|
console.log('App Hide') |
||||
|
} |
||||
|
} |
||||
|
</script> |
||||
|
|
||||
|
|
||||
|
<style lang="scss"> |
||||
|
@import url("static/css/iconfont.css"); |
||||
|
@import url("static/css/main.css"); |
||||
|
@import url("static/css/icon.css"); |
||||
|
@import url("static/css/reset.css"); |
||||
|
/*每个页面公共css */ |
||||
|
@import '@/uni_modules/uni-scss/index.scss'; |
||||
|
/* #ifndef APP-NVUE */ |
||||
|
@import '@/static/customicons.css'; |
||||
|
// 设置整个项目的背景色 |
||||
|
page { |
||||
|
background-color: #f5f5f5; |
||||
|
} |
||||
|
|
||||
|
/* #endif */ |
||||
|
.example-info { |
||||
|
font-size: 14px; |
||||
|
color: #333; |
||||
|
padding: 10px; |
||||
|
} |
||||
|
</style> |
||||
@ -0,0 +1,201 @@ |
|||||
|
Apache License |
||||
|
Version 2.0, January 2004 |
||||
|
http://www.apache.org/licenses/ |
||||
|
|
||||
|
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION |
||||
|
|
||||
|
1. Definitions. |
||||
|
|
||||
|
"License" shall mean the terms and conditions for use, reproduction, |
||||
|
and distribution as defined by Sections 1 through 9 of this document. |
||||
|
|
||||
|
"Licensor" shall mean the copyright owner or entity authorized by |
||||
|
the copyright owner that is granting the License. |
||||
|
|
||||
|
"Legal Entity" shall mean the union of the acting entity and all |
||||
|
other entities that control, are controlled by, or are under common |
||||
|
control with that entity. For the purposes of this definition, |
||||
|
"control" means (i) the power, direct or indirect, to cause the |
||||
|
direction or management of such entity, whether by contract or |
||||
|
otherwise, or (ii) ownership of fifty percent (50%) or more of the |
||||
|
outstanding shares, or (iii) beneficial ownership of such entity. |
||||
|
|
||||
|
"You" (or "Your") shall mean an individual or Legal Entity |
||||
|
exercising permissions granted by this License. |
||||
|
|
||||
|
"Source" form shall mean the preferred form for making modifications, |
||||
|
including but not limited to software source code, documentation |
||||
|
source, and configuration files. |
||||
|
|
||||
|
"Object" form shall mean any form resulting from mechanical |
||||
|
transformation or translation of a Source form, including but |
||||
|
not limited to compiled object code, generated documentation, |
||||
|
and conversions to other media types. |
||||
|
|
||||
|
"Work" shall mean the work of authorship, whether in Source or |
||||
|
Object form, made available under the License, as indicated by a |
||||
|
copyright notice that is included in or attached to the work |
||||
|
(an example is provided in the Appendix below). |
||||
|
|
||||
|
"Derivative Works" shall mean any work, whether in Source or Object |
||||
|
form, that is based on (or derived from) the Work and for which the |
||||
|
editorial revisions, annotations, elaborations, or other modifications |
||||
|
represent, as a whole, an original work of authorship. For the purposes |
||||
|
of this License, Derivative Works shall not include works that remain |
||||
|
separable from, or merely link (or bind by name) to the interfaces of, |
||||
|
the Work and Derivative Works thereof. |
||||
|
|
||||
|
"Contribution" shall mean any work of authorship, including |
||||
|
the original version of the Work and any modifications or additions |
||||
|
to that Work or Derivative Works thereof, that is intentionally |
||||
|
submitted to Licensor for inclusion in the Work by the copyright owner |
||||
|
or by an individual or Legal Entity authorized to submit on behalf of |
||||
|
the copyright owner. For the purposes of this definition, "submitted" |
||||
|
means any form of electronic, verbal, or written communication sent |
||||
|
to the Licensor or its representatives, including but not limited to |
||||
|
communication on electronic mailing lists, source code control systems, |
||||
|
and issue tracking systems that are managed by, or on behalf of, the |
||||
|
Licensor for the purpose of discussing and improving the Work, but |
||||
|
excluding communication that is conspicuously marked or otherwise |
||||
|
designated in writing by the copyright owner as "Not a Contribution." |
||||
|
|
||||
|
"Contributor" shall mean Licensor and any individual or Legal Entity |
||||
|
on behalf of whom a Contribution has been received by Licensor and |
||||
|
subsequently incorporated within the Work. |
||||
|
|
||||
|
2. Grant of Copyright License. Subject to the terms and conditions of |
||||
|
this License, each Contributor hereby grants to You a perpetual, |
||||
|
worldwide, non-exclusive, no-charge, royalty-free, irrevocable |
||||
|
copyright license to reproduce, prepare Derivative Works of, |
||||
|
publicly display, publicly perform, sublicense, and distribute the |
||||
|
Work and such Derivative Works in Source or Object form. |
||||
|
|
||||
|
3. Grant of Patent License. Subject to the terms and conditions of |
||||
|
this License, each Contributor hereby grants to You a perpetual, |
||||
|
worldwide, non-exclusive, no-charge, royalty-free, irrevocable |
||||
|
(except as stated in this section) patent license to make, have made, |
||||
|
use, offer to sell, sell, import, and otherwise transfer the Work, |
||||
|
where such license applies only to those patent claims licensable |
||||
|
by such Contributor that are necessarily infringed by their |
||||
|
Contribution(s) alone or by combination of their Contribution(s) |
||||
|
with the Work to which such Contribution(s) was submitted. If You |
||||
|
institute patent litigation against any entity (including a |
||||
|
cross-claim or counterclaim in a lawsuit) alleging that the Work |
||||
|
or a Contribution incorporated within the Work constitutes direct |
||||
|
or contributory patent infringement, then any patent licenses |
||||
|
granted to You under this License for that Work shall terminate |
||||
|
as of the date such litigation is filed. |
||||
|
|
||||
|
4. Redistribution. You may reproduce and distribute copies of the |
||||
|
Work or Derivative Works thereof in any medium, with or without |
||||
|
modifications, and in Source or Object form, provided that You |
||||
|
meet the following conditions: |
||||
|
|
||||
|
(a) You must give any other recipients of the Work or |
||||
|
Derivative Works a copy of this License; and |
||||
|
|
||||
|
(b) You must cause any modified files to carry prominent notices |
||||
|
stating that You changed the files; and |
||||
|
|
||||
|
(c) You must retain, in the Source form of any Derivative Works |
||||
|
that You distribute, all copyright, patent, trademark, and |
||||
|
attribution notices from the Source form of the Work, |
||||
|
excluding those notices that do not pertain to any part of |
||||
|
the Derivative Works; and |
||||
|
|
||||
|
(d) If the Work includes a "NOTICE" text file as part of its |
||||
|
distribution, then any Derivative Works that You distribute must |
||||
|
include a readable copy of the attribution notices contained |
||||
|
within such NOTICE file, excluding those notices that do not |
||||
|
pertain to any part of the Derivative Works, in at least one |
||||
|
of the following places: within a NOTICE text file distributed |
||||
|
as part of the Derivative Works; within the Source form or |
||||
|
documentation, if provided along with the Derivative Works; or, |
||||
|
within a display generated by the Derivative Works, if and |
||||
|
wherever such third-party notices normally appear. The contents |
||||
|
of the NOTICE file are for informational purposes only and |
||||
|
do not modify the License. You may add Your own attribution |
||||
|
notices within Derivative Works that You distribute, alongside |
||||
|
or as an addendum to the NOTICE text from the Work, provided |
||||
|
that such additional attribution notices cannot be construed |
||||
|
as modifying the License. |
||||
|
|
||||
|
You may add Your own copyright statement to Your modifications and |
||||
|
may provide additional or different license terms and conditions |
||||
|
for use, reproduction, or distribution of Your modifications, or |
||||
|
for any such Derivative Works as a whole, provided Your use, |
||||
|
reproduction, and distribution of the Work otherwise complies with |
||||
|
the conditions stated in this License. |
||||
|
|
||||
|
5. Submission of Contributions. Unless You explicitly state otherwise, |
||||
|
any Contribution intentionally submitted for inclusion in the Work |
||||
|
by You to the Licensor shall be under the terms and conditions of |
||||
|
this License, without any additional terms or conditions. |
||||
|
Notwithstanding the above, nothing herein shall supersede or modify |
||||
|
the terms of any separate license agreement you may have executed |
||||
|
with Licensor regarding such Contributions. |
||||
|
|
||||
|
6. Trademarks. This License does not grant permission to use the trade |
||||
|
names, trademarks, service marks, or product names of the Licensor, |
||||
|
except as required for reasonable and customary use in describing the |
||||
|
origin of the Work and reproducing the content of the NOTICE file. |
||||
|
|
||||
|
7. Disclaimer of Warranty. Unless required by applicable law or |
||||
|
agreed to in writing, Licensor provides the Work (and each |
||||
|
Contributor provides its Contributions) on an "AS IS" BASIS, |
||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or |
||||
|
implied, including, without limitation, any warranties or conditions |
||||
|
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A |
||||
|
PARTICULAR PURPOSE. You are solely responsible for determining the |
||||
|
appropriateness of using or redistributing the Work and assume any |
||||
|
risks associated with Your exercise of permissions under this License. |
||||
|
|
||||
|
8. Limitation of Liability. In no event and under no legal theory, |
||||
|
whether in tort (including negligence), contract, or otherwise, |
||||
|
unless required by applicable law (such as deliberate and grossly |
||||
|
negligent acts) or agreed to in writing, shall any Contributor be |
||||
|
liable to You for damages, including any direct, indirect, special, |
||||
|
incidental, or consequential damages of any character arising as a |
||||
|
result of this License or out of the use or inability to use the |
||||
|
Work (including but not limited to damages for loss of goodwill, |
||||
|
work stoppage, computer failure or malfunction, or any and all |
||||
|
other commercial damages or losses), even if such Contributor |
||||
|
has been advised of the possibility of such damages. |
||||
|
|
||||
|
9. Accepting Warranty or Additional Liability. While redistributing |
||||
|
the Work or Derivative Works thereof, You may choose to offer, |
||||
|
and charge a fee for, acceptance of support, warranty, indemnity, |
||||
|
or other liability obligations and/or rights consistent with this |
||||
|
License. However, in accepting such obligations, You may act only |
||||
|
on Your own behalf and on Your sole responsibility, not on behalf |
||||
|
of any other Contributor, and only if You agree to indemnify, |
||||
|
defend, and hold each Contributor harmless for any liability |
||||
|
incurred by, or claims asserted against, such Contributor by reason |
||||
|
of your accepting any such warranty or additional liability. |
||||
|
|
||||
|
END OF TERMS AND CONDITIONS |
||||
|
|
||||
|
APPENDIX: How to apply the Apache License to your work. |
||||
|
|
||||
|
To apply the Apache License to your work, attach the following |
||||
|
boilerplate notice, with the fields enclosed by brackets "[]" |
||||
|
replaced with your own identifying information. (Don't include |
||||
|
the brackets!) The text should be enclosed in the appropriate |
||||
|
comment syntax for the file format. We also recommend that a |
||||
|
file or class name and description of purpose be included on the |
||||
|
same "printed page" as the copyright notice for easier |
||||
|
identification within third-party archives. |
||||
|
|
||||
|
Copyright [yyyy] [name of copyright owner] |
||||
|
|
||||
|
Licensed under the Apache License, Version 2.0 (the "License"); |
||||
|
you may not use this file except in compliance with the License. |
||||
|
You may obtain a copy of the License at |
||||
|
|
||||
|
http://www.apache.org/licenses/LICENSE-2.0 |
||||
|
|
||||
|
Unless required by applicable law or agreed to in writing, software |
||||
|
distributed under the License is distributed on an "AS IS" BASIS, |
||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||
|
See the License for the specific language governing permissions and |
||||
|
limitations under the License. |
||||
@ -0,0 +1,62 @@ |
|||||
|
# Raingad-IM for uniapp |
||||
|
|
||||
|
技术栈:`vue3` + `pinia` + `color-UI` |
||||
|
|
||||
|
#### 目录结构 |
||||
|
``` |
||||
|
Raingad |
||||
|
├─api 接口目录 |
||||
|
│ ├─index.js 总的接口文件 |
||||
|
│ └─message.js 消息接口 |
||||
|
│ |
||||
|
├─common 公共目录 |
||||
|
│ ├─socket.js websocket配置 |
||||
|
│ └─config.js 服务器地址配置 |
||||
|
│ |
||||
|
│─components 符合vue组件规范的uni-app组件目录 |
||||
|
│ └─comp-a.vue 可复用的a组件 |
||||
|
│ |
||||
|
├─pages 业务页面文件存放的目录 |
||||
|
│ ├─index |
||||
|
│ │ └─index.vue index页面 |
||||
|
│ ├─message |
||||
|
│ │ └─index.vue 消息列表页面 |
||||
|
│ ├─ ... 更多目录 |
||||
|
│ └─contact |
||||
|
│ └─index.vue 联系人列表页面 |
||||
|
│ |
||||
|
├─static 存放应用引用的本地静态资源(如图片、视频等)的目录,注意:静态资源只能存放于此 |
||||
|
│ |
||||
|
├─uni_modules 存放[uni_module](/uni_modules)。 |
||||
|
│ |
||||
|
├─hybrid App端存放本地html文件的目录,详见 |
||||
|
│ |
||||
|
├─nativeplugins 远程插件目录 |
||||
|
│ |
||||
|
├─uniCloud 云函数目录,里面有unipush的推送功能,如果不需要可以删除(已删除) |
||||
|
│ |
||||
|
├─unpackage 非工程代码,一般存放运行或发行的编译结果 |
||||
|
│ |
||||
|
├─main.js Vue初始化入口文件 |
||||
|
├─App.vue 应用配置,用来配置App全局样式以及监听 应用生命周期 |
||||
|
├─manifest.json 配置应用名称、appid、logo、版本等打包信息,详见 |
||||
|
├─pages.json 配置页面路由、导航条、选项卡等页面类信息,详见 |
||||
|
└─uni.scss 这里是uni-app内置的常用样式变量 |
||||
|
``` |
||||
|
|
||||
|
#### 5.3+ 重要更新 |
||||
|
修改接口的时候,为了避免自己打包的H5被盗用,我们加入了域名加密认证。 |
||||
|
- 在 `/common/config.js` 中需要添加一个加密信息:hostToken,可以搜索定位到该字符串 |
||||
|
- hostToken信息需要将自己的域名md5加密 **两次**(域名md5加密一次后得到的值再md5加密一次)在拼接两个字符串,得到我们的Token。不会加密的去百度搜索md5在线加密,将域名输入后加密得到第一次加密后的Token,把加密后的值再重复加密一次即可。 |
||||
|
|
||||
|
加密格式如下: |
||||
|
|
||||
|
> "sha"+域名加密两次后的md5 + "bi" |
||||
|
|
||||
|
假如我们的域名是 `im.xxxxx.com` ,那么加密后就应该是:**sha** f9cb4d7d77719f068e7233e81690f39a **bi** |
||||
|
|
||||
|
加密好了把该token填写到hostToken中,这样就可以避免别人下载我们的h5页面,修改域名来使用我们的源码,如果不影响可以将相应的代码屏蔽掉或者使用其他更先进的算法。 |
||||
|
|
||||
|
#### 安装打包请直接参考文档 |
||||
|
文档全部迁移至以下地址: |
||||
|
所有文档地址: [接口文档地址](https://apifox.com/apidoc/shared-e563aed5-7578-4620-913f-f6746ece6067) 访问密码: raingad-im-doc |
||||
@ -0,0 +1,38 @@ |
|||||
|
{ |
||||
|
"version" : "1", |
||||
|
"prompt" : "template", |
||||
|
"title" : "服务协议和隐私政策", |
||||
|
"message" : " 请你务必审慎阅读、充分理解“服务协议”和“隐私政策”各条款,包括但不限于:为了更好的向你提供服务,我们需要收集你的设备标识、操作日志等信息用于分析、优化应用性能。<br/> 你可阅读<a href=\"\">《服务协议》</a>和<a href=\"\">《隐私政策》</a>了解详细信息。如果你同意,请点击下面按钮开始接受我们的服务。", |
||||
|
"buttonAccept" : "同意并接受", |
||||
|
"buttonRefuse" : "暂不同意", |
||||
|
"hrefLoader" : "system", |
||||
|
"backToExit" : "false", |
||||
|
"second" : { |
||||
|
"title" : "确认提示", |
||||
|
"message" : " 进入应用前,你需先同意<a href=\"\">《服务协议》</a>和<a href=\"\">《隐私政策》</a>,否则将退出应用。", |
||||
|
"buttonAccept" : "同意并继续", |
||||
|
"buttonRefuse" : "退出应用" |
||||
|
}, |
||||
|
"disagreeMode" : { |
||||
|
"support" : false, |
||||
|
"loadNativePlugins" : false, |
||||
|
"visitorEntry" : false, |
||||
|
"showAlways" : false |
||||
|
}, |
||||
|
"styles" : { |
||||
|
"backgroundColor" : "#ffffff", |
||||
|
"borderRadius" : "5px", |
||||
|
"title" : { |
||||
|
"color" : "#000000" |
||||
|
}, |
||||
|
"buttonAccept" : { |
||||
|
"color" : "#00ff00" |
||||
|
}, |
||||
|
"buttonRefuse" : { |
||||
|
"color" : "#999999" |
||||
|
}, |
||||
|
"buttonVisitor" : { |
||||
|
"color" : "#999999" |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,54 @@ |
|||||
|
import {postRequest,postJsonRequest,apiUrl} from '@/utils/request.js'; |
||||
|
let compaApi = {} |
||||
|
|
||||
|
// 朋友圈帖子列表
|
||||
|
compaApi.wechatMomentsList = (params) =>{ |
||||
|
return postJsonRequest('enterprise/posts/index', params) |
||||
|
} |
||||
|
|
||||
|
// 发布朋友圈
|
||||
|
compaApi.wechatMomentsadd = (params) =>{ |
||||
|
return postJsonRequest('enterprise/posts/add', params) |
||||
|
} |
||||
|
|
||||
|
// 朋友圈点赞/取消点赞
|
||||
|
compaApi.onlikes = (params) =>{ |
||||
|
return postJsonRequest('enterprise/posts/like', params) |
||||
|
} |
||||
|
|
||||
|
// 评论/回复
|
||||
|
compaApi.oncomments = (params) =>{ |
||||
|
return postJsonRequest('enterprise/posts/comment', params) |
||||
|
} |
||||
|
|
||||
|
// 获取上次保存草稿数据
|
||||
|
compaApi.getLastPosts = (params) =>{ |
||||
|
return postJsonRequest('enterprise/posts/getLastPosts', params) |
||||
|
} |
||||
|
|
||||
|
// 删除帖子
|
||||
|
compaApi.Deleteapost = (params) =>{ |
||||
|
return postJsonRequest('enterprise/posts/del', params) |
||||
|
} |
||||
|
|
||||
|
// 我的朋友圈
|
||||
|
compaApi.myPosts = (params) =>{ |
||||
|
return postJsonRequest('enterprise/posts/myPosts', params) |
||||
|
} |
||||
|
|
||||
|
// 我的朋友圈
|
||||
|
compaApi.detailsList = (params) =>{ |
||||
|
return postJsonRequest('enterprise/posts/details', params) |
||||
|
} |
||||
|
|
||||
|
// 获取未读消息数量
|
||||
|
compaApi.getNoticeCount = (params) =>{ |
||||
|
return postJsonRequest('enterprise/posts/getNoticeCount', params) |
||||
|
} |
||||
|
|
||||
|
// 消息通知列表接口
|
||||
|
compaApi.getNoticeList = (params) =>{ |
||||
|
return postJsonRequest('enterprise/posts/getNoticeList', params) |
||||
|
} |
||||
|
|
||||
|
export default compaApi; |
||||
@ -0,0 +1,45 @@ |
|||||
|
// 统一请求路径前缀在libs/axios.js中修改
|
||||
|
import { |
||||
|
postRequest, |
||||
|
postJsonRequest, |
||||
|
apiUrl |
||||
|
} from '@/utils/request.js'; |
||||
|
let emojiApi = {} |
||||
|
|
||||
|
emojiApi.uploadEmoji=apiUrl+'/common/upload/uploadEmoji'; |
||||
|
|
||||
|
/** |
||||
|
* @desc 表情列表 |
||||
|
* @param {*} 参数 |
||||
|
*/ |
||||
|
emojiApi.emojiList = (params) => { |
||||
|
return postJsonRequest('/enterprise/emoji/index', params) |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* @desc 添加表情 |
||||
|
* @param {*} 参数 |
||||
|
*/ |
||||
|
emojiApi.addEmoji = (params) => { |
||||
|
return postJsonRequest('/enterprise/emoji/add', params) |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* @desc 删除表情 |
||||
|
* @param {*} 参数 |
||||
|
*/ |
||||
|
emojiApi.delEmoji = (params) => { |
||||
|
return postJsonRequest('/enterprise/emoji/del', params) |
||||
|
} |
||||
|
|
||||
|
|
||||
|
/** |
||||
|
* @desc 移动表情 |
||||
|
* @param {*} 参数 |
||||
|
*/ |
||||
|
emojiApi.moveEmoji = (params) => { |
||||
|
return postJsonRequest('/enterprise/emoji/move', params) |
||||
|
} |
||||
|
|
||||
|
|
||||
|
export default emojiApi; |
||||
@ -0,0 +1,48 @@ |
|||||
|
// 统一请求路径前缀在libs/axios.js中修改
|
||||
|
import { |
||||
|
postJsonRequest, |
||||
|
apiUrl |
||||
|
} from '@/utils/request.js'; |
||||
|
let friendApi = {} |
||||
|
|
||||
|
/** |
||||
|
* @desc 删除好友 |
||||
|
* @param {*} 参数 |
||||
|
*/ |
||||
|
friendApi.delFriend = (params) => { |
||||
|
return postJsonRequest('/enterprise/friend/del', params) |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* @desc 添加好友 |
||||
|
* @param {*} 参数 |
||||
|
*/ |
||||
|
friendApi.addFriend = (params) => { |
||||
|
return postJsonRequest('/enterprise/friend/add', params) |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* @desc 新朋友列表 |
||||
|
* @param {*} 参数 |
||||
|
*/ |
||||
|
friendApi.applyList = (params) => { |
||||
|
return postJsonRequest('/enterprise/friend/index', params) |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* @desc 同意或者拒绝请求 |
||||
|
* @param {*} 参数 |
||||
|
*/ |
||||
|
friendApi.acceptApply = (params) => { |
||||
|
return postJsonRequest('/enterprise/friend/update', params) |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* @desc 设置好有备注 |
||||
|
* @param {*} 参数 |
||||
|
*/ |
||||
|
friendApi.setNickname = (params) => { |
||||
|
return postJsonRequest('/enterprise/friend/setNickname', params) |
||||
|
} |
||||
|
|
||||
|
export default friendApi; |
||||
@ -0,0 +1,15 @@ |
|||||
|
// 引入其他模块的接口
|
||||
|
|
||||
|
import msgApi from '@/api/message.js'; // 消息
|
||||
|
import LoginApi from '@/api/login.js'; //登录相关
|
||||
|
import friendApi from '@/api/friend.js'; //登录相关
|
||||
|
import emojiApi from '@/api/emoji.js'; //登录相关
|
||||
|
import compaApi from '@/api/compass.js'; //朋友圈
|
||||
|
// 导出接口
|
||||
|
export default { |
||||
|
msgApi, |
||||
|
LoginApi, |
||||
|
friendApi, |
||||
|
emojiApi, |
||||
|
compaApi |
||||
|
} |
||||
@ -0,0 +1,65 @@ |
|||||
|
// 统一请求路径前缀在libs/axios.js中修改
|
||||
|
import { |
||||
|
postJsonRequest, |
||||
|
apiUrl |
||||
|
} from '@/utils/request.js'; |
||||
|
let LoginApi = {} |
||||
|
|
||||
|
/** |
||||
|
* @desc 登录接口 |
||||
|
* @param {*} 参数 |
||||
|
*/ |
||||
|
LoginApi.login = (params) => { |
||||
|
return postJsonRequest('/common/Pub/login', params) |
||||
|
} |
||||
|
|
||||
|
|
||||
|
/** |
||||
|
* @desc 退出接口 |
||||
|
* @param {*} 参数 |
||||
|
*/ |
||||
|
LoginApi.logout = (params) => { |
||||
|
return postJsonRequest('/common/Pub/logout', params) |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* 注册用户 |
||||
|
* @param {*} data |
||||
|
*/ |
||||
|
LoginApi.register = (params) => { |
||||
|
return postJsonRequest('/common/pub/register', params) |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* @desc 绑定client_id |
||||
|
* @param {*} 参数 |
||||
|
*/ |
||||
|
LoginApi.bindUid = (params) => { |
||||
|
return postJsonRequest('/common/Pub/bindUid', params) |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* @desc 绑定群聊client_id |
||||
|
* @param {*} 参数 |
||||
|
*/ |
||||
|
LoginApi.bindGroup = (params) => { |
||||
|
return postJsonRequest('common/pub/bindGroup', params) |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* 获取全局配置 |
||||
|
* @param {*} data |
||||
|
*/ |
||||
|
LoginApi.getSystemInfo = (params) => { |
||||
|
return postJsonRequest('/common/pub/getSystemInfo', params) |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* 发送验证码 |
||||
|
* @param {*} data |
||||
|
*/ |
||||
|
LoginApi.sendCode = (params) => { |
||||
|
return postJsonRequest('/common/pub/sendCode', params) |
||||
|
} |
||||
|
|
||||
|
export default LoginApi; |
||||
@ -0,0 +1,366 @@ |
|||||
|
// 统一请求路径前缀在libs/axios.js中修改
|
||||
|
import { |
||||
|
postRequest, |
||||
|
postJsonRequest, |
||||
|
apiUrl |
||||
|
} from '@/utils/request.js'; |
||||
|
let msgApi = {} |
||||
|
|
||||
|
msgApi.uploadUrl=apiUrl+'/common/upload/uploadFile'; |
||||
|
// 上传头像
|
||||
|
msgApi.uploadAvatar=apiUrl+'/common/upload/uploadAvatar'; |
||||
|
|
||||
|
/** |
||||
|
* @desc 网络图片上传 |
||||
|
* @param {*} 参数 |
||||
|
*/ |
||||
|
msgApi.uploadFileImage = (params) => { |
||||
|
return postJsonRequest('/common/upload/uploadFileImage', params) |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* @desc 个人聊天定时删除消息 |
||||
|
* @param {*} 参数 |
||||
|
*/ |
||||
|
msgApi.friendClearMsgDay = (params) => { |
||||
|
return postJsonRequest('/enterprise/friend/setClearMsgDay', params) |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* @desc 群聊天定时删除消息 |
||||
|
* @param {*} 参数 |
||||
|
*/ |
||||
|
msgApi.groupClearMsgDay = (params) => { |
||||
|
return postJsonRequest('/enterprise/group/setClearMsgDay', params) |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* @desc 单聊一键清除所有消息 |
||||
|
* @param {*} 参数 |
||||
|
*/ |
||||
|
msgApi.friendremoveAllMessage = (params) => { |
||||
|
return postJsonRequest('/enterprise/friend/removeAllMessage', params) |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* @desc 群聊一键清除所有消息 |
||||
|
* @param {*} 参数 |
||||
|
*/ |
||||
|
msgApi.groupremoveAllMessage = (params) => { |
||||
|
return postJsonRequest('/enterprise/group/removeAllMessage', params) |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* @desc 普通消息列表(小程序) |
||||
|
* @param {*} 参数 |
||||
|
*/ |
||||
|
msgApi.initContacts = (params) => { |
||||
|
return postJsonRequest('/enterprise/im/getContacts', params) |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* @desc AI对话 |
||||
|
* @param {*} 参数 |
||||
|
*/ |
||||
|
msgApi.sendChat = (params) => { |
||||
|
return postJsonRequest('/enterprise/chat/sendChat', params) |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* @desc 普通消息列表(小程序) |
||||
|
* @param {*} 参数 |
||||
|
*/ |
||||
|
msgApi.getMessageList = (params) => { |
||||
|
return postJsonRequest('/enterprise/im/getMessageList', params) |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* @desc 设置聊天置顶 |
||||
|
* @param {*} 参数 |
||||
|
*/ |
||||
|
msgApi.setChatTopAPI = (params) => { |
||||
|
return postJsonRequest('/enterprise/im/setChatTop', params) |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* @desc 获取好友信息 |
||||
|
* @param {*} 参数 |
||||
|
*/ |
||||
|
msgApi.getFriendInfo = (params) => { |
||||
|
return postJsonRequest('enterprise/friend/getFriendInfo', params) |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* @desc 删除聊天 |
||||
|
* @param {*} 参数 |
||||
|
*/ |
||||
|
msgApi.delChatAPI = (params) => { |
||||
|
return postJsonRequest('/enterprise/im/delChat', params) |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* @desc 发送文本聊天消息 |
||||
|
* @param {*} 参数 |
||||
|
*/ |
||||
|
msgApi.sendMessage = (params) => { |
||||
|
return postJsonRequest('/enterprise/im/sendMessage', params) |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* @desc 转发聊天消息 |
||||
|
* @param {*} 参数 |
||||
|
*/ |
||||
|
msgApi.forwardMessage = (params) => { |
||||
|
return postJsonRequest('/enterprise/im/forwardMessage', params) |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* @desc 设置消息已读 |
||||
|
* @param {*} 参数 |
||||
|
*/ |
||||
|
msgApi.setMsgIsRead = (params) => { |
||||
|
return postJsonRequest('/enterprise/im/setMsgIsRead', params) |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* @desc 设置艾特消息已读 |
||||
|
* @param {*} 参数 |
||||
|
*/ |
||||
|
msgApi.readAtMsg = (params) => { |
||||
|
return postJsonRequest('/enterprise/im/readAtMsg', params) |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* 撤回消息 |
||||
|
* @param {*} data |
||||
|
*/ |
||||
|
msgApi.undoMessage = (params) => { |
||||
|
return postJsonRequest('/enterprise/im/undoMessage', params) |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* 删除消息 |
||||
|
* @param {*} data |
||||
|
*/ |
||||
|
msgApi.delMessage = (params) => { |
||||
|
return postJsonRequest('/enterprise/im/delMessage', params) |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* 删除消息个人 |
||||
|
* @param {*} data |
||||
|
*/ |
||||
|
msgApi.removeMessage = (params) => { |
||||
|
return postJsonRequest('/enterprise/im/removeMessage', params) |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* 加入黑名单 |
||||
|
* @param {*} data |
||||
|
*/ |
||||
|
msgApi.isBlacklist = (params) => { |
||||
|
return postJsonRequest('/enterprise/friend/isBlacklist', params) |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* 发送ws消息 |
||||
|
* @param {*} data |
||||
|
*/ |
||||
|
msgApi.sendToMsg = (params) => { |
||||
|
return postJsonRequest('/enterprise/im/sendToMsg', params) |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* 消息免打扰 |
||||
|
* @param {*} data |
||||
|
*/ |
||||
|
msgApi.isNoticeAPI = (params) => { |
||||
|
return postJsonRequest('/enterprise/im/isNotice', params) |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* 更新业务卡片 |
||||
|
* @param {*} data |
||||
|
*/ |
||||
|
msgApi.updateCard = (params) => { |
||||
|
return postJsonRequest('/enterprise/im/updateCard', params) |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* 同意或者忽略团队 |
||||
|
* @param {*} data |
||||
|
*/ |
||||
|
msgApi.joinGroup = (params) => { |
||||
|
return postJsonRequest('/enterprise/group/joinGroup', params) |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* 获取群成员 |
||||
|
* @param {*} data |
||||
|
*/ |
||||
|
msgApi.groupUserList = (params) => { |
||||
|
return postJsonRequest('/enterprise/group/groupUserList', params) |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* 修改群公告 |
||||
|
* @param {*} data |
||||
|
*/ |
||||
|
msgApi.setNotice = (params) => { |
||||
|
return postJsonRequest('/enterprise/group/setNotice', params) |
||||
|
} |
||||
|
/** |
||||
|
* 群管理信息 |
||||
|
* @param {*} data |
||||
|
*/ |
||||
|
msgApi.groupInfo = (params) => { |
||||
|
return postJsonRequest('/enterprise/group/groupInfo', params) |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* 加入群聊 |
||||
|
* @param {*} data |
||||
|
*/ |
||||
|
msgApi.joinGroup = (params) => { |
||||
|
return postJsonRequest('/enterprise/group/joinGroup', params) |
||||
|
} |
||||
|
|
||||
|
|
||||
|
/** |
||||
|
* 修改群管理信息 |
||||
|
* @param {*} data |
||||
|
*/ |
||||
|
msgApi.groupSetting = (params) => { |
||||
|
return postJsonRequest('/enterprise/group/groupSetting', params) |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* 转让管理权限 |
||||
|
* @param {*} data |
||||
|
*/ |
||||
|
msgApi.changeOwner = (params) => { |
||||
|
return postJsonRequest('/enterprise/group/changeOwner', params) |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* 获取全部人员 |
||||
|
* @param {*} data |
||||
|
*/ |
||||
|
msgApi.getAllUser = (params) => { |
||||
|
return postJsonRequest('/enterprise/group/getAllUser', params) |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* 创建群聊 |
||||
|
* @param {*} data |
||||
|
*/ |
||||
|
msgApi.addGroup = (params) => { |
||||
|
return postJsonRequest('/enterprise/group/add', params) |
||||
|
} |
||||
|
/** |
||||
|
* 绑定群聊 |
||||
|
* @param {*} data |
||||
|
*/ |
||||
|
msgApi.bindGroup = (params) => { |
||||
|
return postJsonRequest('/common/index/bindGroup', params) |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* 修改群聊名字 |
||||
|
* @param {*} data |
||||
|
*/ |
||||
|
msgApi.editGroupName = (params) => { |
||||
|
return postJsonRequest('/enterprise/group/editGroupName', params) |
||||
|
} |
||||
|
/** |
||||
|
* 添加群成员 |
||||
|
* @param {*} data |
||||
|
*/ |
||||
|
msgApi.addGroupUser = (params) => { |
||||
|
return postJsonRequest('/enterprise/group/addGroupUser', params) |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* 删除群成员 |
||||
|
* @param {*} data |
||||
|
*/ |
||||
|
msgApi.removeUser = (params) => { |
||||
|
return postJsonRequest('/enterprise/group/removeUser', params) |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* 删除群聊 |
||||
|
* @param {*} data |
||||
|
*/ |
||||
|
msgApi.removeGroup = (params) => { |
||||
|
return postJsonRequest('/enterprise/group/removeGroup', params) |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* 删除群聊聊天记录 |
||||
|
* @param {*} data |
||||
|
*/ |
||||
|
msgApi.clearMessage = (params) => { |
||||
|
return postJsonRequest('/enterprise/group/clearMessage', params) |
||||
|
} |
||||
|
|
||||
|
|
||||
|
/** |
||||
|
* 设置/取消管理员 |
||||
|
* @param {*} data |
||||
|
*/ |
||||
|
msgApi.setManager = (params) => { |
||||
|
return postJsonRequest('/enterprise/group/setManager', params) |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* 设置禁言 |
||||
|
* @param {*} data |
||||
|
*/ |
||||
|
msgApi.setNoSpeak = (params) => { |
||||
|
return postJsonRequest('/enterprise/group/setNoSpeak', params) |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* 获取成员信息 |
||||
|
* @param {*} data |
||||
|
*/ |
||||
|
msgApi.getUserInfo = (params) => { |
||||
|
return postJsonRequest('/enterprise/im/getUserInfo', params) |
||||
|
} |
||||
|
|
||||
|
// 搜索用户
|
||||
|
msgApi.searchUser= (params) =>{ |
||||
|
return postJsonRequest('enterprise/im/searchUser', params) |
||||
|
} |
||||
|
|
||||
|
// 修改用户信息
|
||||
|
msgApi.updateUserInfo= (params) =>{ |
||||
|
return postJsonRequest('enterprise/im/updateUserInfo', params) |
||||
|
} |
||||
|
|
||||
|
// 修改账号
|
||||
|
msgApi.editAccount= (params) =>{ |
||||
|
return postJsonRequest('enterprise/im/editAccount', params) |
||||
|
} |
||||
|
|
||||
|
// 修改密码
|
||||
|
msgApi.editPassword= (params) =>{ |
||||
|
return postJsonRequest('enterprise/im/editPassword', params) |
||||
|
} |
||||
|
|
||||
|
// 获取联系人信息
|
||||
|
msgApi.contactInfo= (params) =>{ |
||||
|
return postJsonRequest('enterprise/im/getContactInfo', params) |
||||
|
} |
||||
|
|
||||
|
// 获取公告信息
|
||||
|
msgApi.getAdminNotice= (params) =>{ |
||||
|
return postJsonRequest('enterprise/im/getAdminNotice', params) |
||||
|
} |
||||
|
|
||||
|
// 获取聊天原图
|
||||
|
msgApi.viewOriginalImage= (params) =>{ |
||||
|
return postJsonRequest('enterprise/files/viewOriginalImage', params) |
||||
|
} |
||||
|
export default msgApi; |
||||
@ -0,0 +1,865 @@ |
|||||
|
|
||||
|
import config from "@/common/config.js" |
||||
|
import { postJsonRequest } from '@/utils/request.js'; |
||||
|
|
||||
|
const updateConfig=config.updateConfig; |
||||
|
const platform = uni.getSystemInfoSync().platform; |
||||
|
// 主颜色
|
||||
|
const $mainColor = updateConfig.bgColor ? updateConfig.bgColor : "FF5B78"; |
||||
|
// 弹窗图标url
|
||||
|
const $iconUrl = updateConfig.iconUrl ? updateConfig.iconUrl : "/static/image/rocket.png"; |
||||
|
|
||||
|
const $checkUpdateUrl=updateConfig.url ? updateConfig.url : ""; |
||||
|
|
||||
|
|
||||
|
// 获取当前应用的版本号
|
||||
|
export const getCurrentNo = (callback) => { |
||||
|
// 获取本地应用资源版本号
|
||||
|
plus.runtime.getProperty(plus.runtime.appid,(inf) => { |
||||
|
callback && callback({ |
||||
|
versionCode: inf.versionCode, |
||||
|
versionName: inf.version |
||||
|
}); |
||||
|
}); |
||||
|
} |
||||
|
// 从服务器下载应用资源包(wgt文件)
|
||||
|
const getDownload = (data)=>{ |
||||
|
let dtask; |
||||
|
if(data.updateType == 'forcibly' || data.updateType == 'solicit'){ |
||||
|
let popupData = { |
||||
|
progress: true, |
||||
|
buttonNum: 2 |
||||
|
}; |
||||
|
if(data.updateType == 'forcibly'){ |
||||
|
popupData.buttonNum = 0; |
||||
|
} |
||||
|
let lastProgressValue = 0; |
||||
|
let popupObj = downloadPopup(popupData); |
||||
|
dtask = plus.downloader.createDownload(data.downloadUrl, { |
||||
|
filename: "_doc/update/" |
||||
|
}, function(download, status) { |
||||
|
if (status == 200) { |
||||
|
popupObj.change({ |
||||
|
progressValue: 100, |
||||
|
progressTip:"正在安装文件...", |
||||
|
progress: true, |
||||
|
buttonNum: 0 |
||||
|
}); |
||||
|
plus.runtime.install(download.filename, {}, function() { |
||||
|
popupObj.change({ |
||||
|
contentText: "应用资源更新完成!", |
||||
|
buttonNum: 1, |
||||
|
progress: false |
||||
|
}); |
||||
|
}, function(e) { |
||||
|
popupObj.cancel(); |
||||
|
plus.nativeUI.alert("安装文件失败[" + e.code + "]:" + e.message); |
||||
|
}); |
||||
|
} else { |
||||
|
popupObj.change({ |
||||
|
contentText: "文件下载失败...", |
||||
|
buttonNum: 1, |
||||
|
progress: false |
||||
|
}); |
||||
|
} |
||||
|
}); |
||||
|
dtask.start(); |
||||
|
dtask.addEventListener("statechanged", function(task, status) { |
||||
|
switch (task.state) { |
||||
|
case 1: // 开始
|
||||
|
popupObj.change({ |
||||
|
progressValue:0, |
||||
|
progressTip:"准备下载...", |
||||
|
progress: true |
||||
|
}); |
||||
|
break; |
||||
|
case 2: // 已连接到服务器
|
||||
|
popupObj.change({ |
||||
|
progressValue:0, |
||||
|
progressTip:"开始下载...", |
||||
|
progress: true |
||||
|
}); |
||||
|
break; |
||||
|
case 3: |
||||
|
const progress = parseInt(task.downloadedSize / task.totalSize * 100); |
||||
|
if(progress - lastProgressValue >= 2){ |
||||
|
lastProgressValue = progress; |
||||
|
popupObj.change({ |
||||
|
progressValue:progress, |
||||
|
progressTip: "已下载" + progress + "%", |
||||
|
progress: true |
||||
|
}); |
||||
|
} |
||||
|
break; |
||||
|
} |
||||
|
}); |
||||
|
// 取消下载
|
||||
|
popupObj.cancelDownload = function(){ |
||||
|
dtask && dtask.abort(); |
||||
|
uni.showToast({ |
||||
|
title: "已取消下载", |
||||
|
icon:"none" |
||||
|
}); |
||||
|
} |
||||
|
// 重启APP
|
||||
|
popupObj.reboot = function(){ |
||||
|
plus.runtime.restart(); |
||||
|
} |
||||
|
} else if(data.updateType == "silent"){ |
||||
|
dtask = plus.downloader.createDownload(data.downloadUrl, { |
||||
|
filename: "_doc/update/" |
||||
|
}, function(download, status) { |
||||
|
if (status == 200) { |
||||
|
plus.runtime.install(download.filename, {}, function() { |
||||
|
console.log("应用资源更新完成"); |
||||
|
}, function(e) { |
||||
|
plus.nativeUI.alert("安装文件失败[" + e.code + "]:" + e.message); |
||||
|
}); |
||||
|
} else { |
||||
|
plus.nativeUI.alert("文件下载失败..."); |
||||
|
} |
||||
|
}); |
||||
|
dtask.start(); |
||||
|
} |
||||
|
} |
||||
|
// 文字换行
|
||||
|
const drawtext = (text, maxWidth)=>{ |
||||
|
let textArr = text.split(""); |
||||
|
let len = textArr.length; |
||||
|
// 上个节点
|
||||
|
let previousNode = 0; |
||||
|
// 记录节点宽度
|
||||
|
let nodeWidth = 0; |
||||
|
// 文本换行数组
|
||||
|
let rowText = []; |
||||
|
// 如果是字母,侧保存长度
|
||||
|
let letterWidth = 0; |
||||
|
// 汉字宽度
|
||||
|
let chineseWidth = 14; |
||||
|
// otherFont宽度
|
||||
|
let otherWidth = 7; |
||||
|
for (let i = 0; i < len; i++) { |
||||
|
if (/[\u4e00-\u9fa5]|[\uFE30-\uFFA0]/g.test(textArr[i])) { |
||||
|
if(letterWidth > 0){ |
||||
|
if(nodeWidth + chineseWidth + letterWidth * otherWidth > maxWidth){ |
||||
|
rowText.push({ |
||||
|
type: "text", |
||||
|
content: text.substring(previousNode, i) |
||||
|
}); |
||||
|
previousNode = i; |
||||
|
nodeWidth = chineseWidth; |
||||
|
letterWidth = 0; |
||||
|
} else { |
||||
|
nodeWidth += chineseWidth + letterWidth * otherWidth; |
||||
|
letterWidth = 0; |
||||
|
} |
||||
|
} else { |
||||
|
if(nodeWidth + chineseWidth > maxWidth){ |
||||
|
rowText.push({ |
||||
|
type: "text", |
||||
|
content: text.substring(previousNode, i) |
||||
|
}); |
||||
|
previousNode = i; |
||||
|
nodeWidth = chineseWidth; |
||||
|
}else{ |
||||
|
nodeWidth += chineseWidth; |
||||
|
} |
||||
|
} |
||||
|
} else { |
||||
|
if(/\n/g.test(textArr[i])){ |
||||
|
rowText.push({ |
||||
|
type: "break", |
||||
|
content: text.substring(previousNode, i) |
||||
|
}); |
||||
|
previousNode = i + 1; |
||||
|
nodeWidth = 0; |
||||
|
letterWidth = 0; |
||||
|
}else if(textArr[i] == "\\" && textArr[i + 1] == "n"){ |
||||
|
rowText.push({ |
||||
|
type: "break", |
||||
|
content: text.substring(previousNode, i) |
||||
|
}); |
||||
|
previousNode = i + 2; |
||||
|
nodeWidth = 0; |
||||
|
letterWidth = 0; |
||||
|
}else if(/[a-zA-Z0-9]/g.test(textArr[i])){ |
||||
|
letterWidth += 1; |
||||
|
if(nodeWidth + letterWidth * otherWidth > maxWidth){ |
||||
|
rowText.push({ |
||||
|
type: "text", |
||||
|
content: text.substring(previousNode, i + 1 - letterWidth) |
||||
|
}); |
||||
|
previousNode = i + 1 - letterWidth; |
||||
|
nodeWidth = letterWidth * otherWidth; |
||||
|
letterWidth = 0; |
||||
|
} |
||||
|
} else{ |
||||
|
if(nodeWidth + otherWidth > maxWidth){ |
||||
|
rowText.push({ |
||||
|
type: "text", |
||||
|
content: text.substring(previousNode, i) |
||||
|
}); |
||||
|
previousNode = i; |
||||
|
nodeWidth = otherWidth; |
||||
|
}else{ |
||||
|
nodeWidth += otherWidth; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
if (previousNode < len) { |
||||
|
rowText.push({ |
||||
|
type: "text", |
||||
|
content: text.substring(previousNode, len) |
||||
|
}); |
||||
|
} |
||||
|
return rowText; |
||||
|
} |
||||
|
// 是否更新弹窗
|
||||
|
const updatePopup = (data, callback) => { |
||||
|
// 弹窗遮罩层
|
||||
|
let maskLayer = new plus.nativeObj.View("maskLayer", { //先创建遮罩层
|
||||
|
top: '0px', |
||||
|
left: '0px', |
||||
|
height: '100%', |
||||
|
width: '100%', |
||||
|
backgroundColor: 'rgba(0,0,0,0.5)' |
||||
|
}); |
||||
|
|
||||
|
// 以下为计算菜单的nview绘制布局,为固定算法,使用者无关关心
|
||||
|
const screenWidth = plus.screen.resolutionWidth; |
||||
|
const screenHeight = plus.screen.resolutionHeight; |
||||
|
//弹窗容器宽度
|
||||
|
const popupViewWidth = screenWidth * 0.7; |
||||
|
// 弹窗容器的Padding
|
||||
|
const viewContentPadding = 20; |
||||
|
// 弹窗容器的宽度
|
||||
|
const viewContentWidth = parseInt(popupViewWidth - (viewContentPadding * 2)); |
||||
|
// 描述的列表
|
||||
|
const descriptionList = drawtext(data.versionInfo, viewContentWidth); |
||||
|
// 弹窗容器高度
|
||||
|
let popupViewHeight = 80 + 20 + 20 + 90 + 10; |
||||
|
|
||||
|
let popupViewContentList = [{ |
||||
|
src: $iconUrl, |
||||
|
id: "logo", |
||||
|
tag: "img", |
||||
|
position: { |
||||
|
top: "0px", |
||||
|
left: (popupViewWidth - 124) / 2 + "px", |
||||
|
width: "124px", |
||||
|
height: "80px", |
||||
|
} |
||||
|
}, |
||||
|
{ |
||||
|
tag: 'font', |
||||
|
id: 'title', |
||||
|
text: "发现新版本:" + data.versionName, |
||||
|
textStyles: { |
||||
|
size: '18px', |
||||
|
color: "#333", |
||||
|
weight: "bold", |
||||
|
whiteSpace: "normal" |
||||
|
}, |
||||
|
position: { |
||||
|
top: '90px', |
||||
|
left: viewContentPadding + "px", |
||||
|
width: viewContentWidth + "px", |
||||
|
height: "30px", |
||||
|
} |
||||
|
}]; |
||||
|
const textHeight = 18; |
||||
|
let contentTop = 130; |
||||
|
descriptionList.forEach((item,index) => { |
||||
|
if(index > 0){ |
||||
|
popupViewHeight += textHeight; |
||||
|
contentTop += textHeight; |
||||
|
} |
||||
|
popupViewContentList.push({ |
||||
|
tag: 'font', |
||||
|
id: 'content' + index + 1, |
||||
|
text: item.content, |
||||
|
textStyles: { |
||||
|
size: '14px', |
||||
|
color: "#666", |
||||
|
lineSpacing: "50%", |
||||
|
align: "left" |
||||
|
}, |
||||
|
position: { |
||||
|
top: contentTop + "px", |
||||
|
left: viewContentPadding + "px", |
||||
|
width: viewContentWidth + "px", |
||||
|
height: textHeight + "px", |
||||
|
} |
||||
|
}); |
||||
|
if(item.type == "break"){ |
||||
|
contentTop += 10; |
||||
|
popupViewHeight += 10; |
||||
|
} |
||||
|
}); |
||||
|
|
||||
|
if(data.updateType == "forcibly"){ |
||||
|
popupViewContentList.push({ |
||||
|
tag: 'rect', //绘制底边按钮
|
||||
|
rectStyles:{ |
||||
|
radius: "6px", |
||||
|
color: $mainColor |
||||
|
}, |
||||
|
position:{ |
||||
|
bottom: viewContentPadding + 'px', |
||||
|
left: viewContentPadding + "px", |
||||
|
width: viewContentWidth + "px", |
||||
|
height: "30px" |
||||
|
} |
||||
|
}); |
||||
|
popupViewContentList.push({ |
||||
|
tag: 'font', |
||||
|
id: 'confirmText', |
||||
|
text: "立即升级", |
||||
|
textStyles: { |
||||
|
size: '14px', |
||||
|
color: "#FFF", |
||||
|
lineSpacing: "0%", |
||||
|
}, |
||||
|
position: { |
||||
|
bottom: viewContentPadding + 'px', |
||||
|
left: viewContentPadding + "px", |
||||
|
width: viewContentWidth + "px", |
||||
|
height: "30px" |
||||
|
} |
||||
|
}); |
||||
|
} else { |
||||
|
// 绘制底边按钮
|
||||
|
popupViewContentList.push({ |
||||
|
tag: 'rect', |
||||
|
id: 'cancelBox', |
||||
|
rectStyles: { |
||||
|
radius: "3px", |
||||
|
borderColor: "#f1f1f1", |
||||
|
borderWidth: "1px", |
||||
|
}, |
||||
|
position: { |
||||
|
bottom: viewContentPadding + 'px', |
||||
|
left: viewContentPadding + "px", |
||||
|
width: (viewContentWidth - viewContentPadding) / 2 + "px", |
||||
|
height: "30px", |
||||
|
} |
||||
|
}); |
||||
|
popupViewContentList.push({ |
||||
|
tag: 'rect', |
||||
|
id: 'confirmBox', |
||||
|
rectStyles: { |
||||
|
radius: "3px", |
||||
|
color: $mainColor, |
||||
|
}, |
||||
|
position: { |
||||
|
bottom: viewContentPadding + 'px', |
||||
|
left: ((viewContentWidth - viewContentPadding) / 2 + viewContentPadding * 2) + "px", |
||||
|
width: (viewContentWidth - viewContentPadding) / 2 + "px", |
||||
|
height: "30px", |
||||
|
} |
||||
|
}); |
||||
|
popupViewContentList.push({ |
||||
|
tag: 'font', |
||||
|
id: 'cancelText', |
||||
|
text: "暂不升级", |
||||
|
textStyles: { |
||||
|
size: '14px', |
||||
|
color: "#666", |
||||
|
lineSpacing: "0%", |
||||
|
whiteSpace: "normal" |
||||
|
}, |
||||
|
position: { |
||||
|
bottom: viewContentPadding + 'px', |
||||
|
left: viewContentPadding + "px", |
||||
|
width: (viewContentWidth - viewContentPadding) / 2 + "px", |
||||
|
height: "30px", |
||||
|
} |
||||
|
}); |
||||
|
popupViewContentList.push({ |
||||
|
tag: 'font', |
||||
|
id: 'confirmText', |
||||
|
text: "立即升级", |
||||
|
textStyles: { |
||||
|
size: '14px', |
||||
|
color: "#FFF", |
||||
|
lineSpacing: "0%", |
||||
|
whiteSpace: "normal" |
||||
|
}, |
||||
|
position: { |
||||
|
bottom: viewContentPadding + 'px', |
||||
|
left: ((viewContentWidth - viewContentPadding) / 2 + viewContentPadding * 2) + "px", |
||||
|
width: (viewContentWidth - viewContentPadding) / 2 + "px", |
||||
|
height: "30px", |
||||
|
} |
||||
|
}); |
||||
|
} |
||||
|
// 弹窗内容
|
||||
|
let popupView = new plus.nativeObj.View("popupView", { //创建底部图标菜单
|
||||
|
tag: "rect", |
||||
|
top: (screenHeight - popupViewHeight) / 2 + "px", |
||||
|
left: '15%', |
||||
|
height: popupViewHeight + "px", |
||||
|
width: "70%" |
||||
|
}); |
||||
|
// 绘制白色背景
|
||||
|
popupView.drawRect({ |
||||
|
color: "#FFFFFF", |
||||
|
radius: "8px" |
||||
|
}, { |
||||
|
top: "40px", |
||||
|
height: popupViewHeight - 40 + "px", |
||||
|
}); |
||||
|
|
||||
|
popupView.draw(popupViewContentList); |
||||
|
popupView.addEventListener("click", function(e) { |
||||
|
let maxTop = popupViewHeight - viewContentPadding; |
||||
|
let maxLeft = popupViewWidth - viewContentPadding; |
||||
|
let buttonWidth = (viewContentWidth - viewContentPadding) / 2; |
||||
|
if (e.clientY > maxTop - 30 && e.clientY < maxTop) { |
||||
|
if(data.updateType == "forcibly"){ |
||||
|
if(e.clientX > viewContentPadding && e.clientX < maxLeft){ |
||||
|
// 立即升级
|
||||
|
maskLayer.hide(); |
||||
|
popupView.hide(); |
||||
|
callback && callback(); |
||||
|
} |
||||
|
} else { |
||||
|
// 暂不升级
|
||||
|
if (e.clientX > viewContentPadding && e.clientX < maxLeft - buttonWidth - viewContentPadding) { |
||||
|
maskLayer.hide(); |
||||
|
popupView.hide(); |
||||
|
} else if (e.clientX > maxLeft - buttonWidth && e.clientX < maxLeft) { |
||||
|
// 立即升级
|
||||
|
maskLayer.hide(); |
||||
|
popupView.hide(); |
||||
|
callback && callback(); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
} |
||||
|
}); |
||||
|
if(data.updateType == "solicit"){ |
||||
|
// 点击遮罩层
|
||||
|
maskLayer.addEventListener("click", function() { //处理遮罩层点击
|
||||
|
maskLayer.hide(); |
||||
|
popupView.hide(); |
||||
|
}); |
||||
|
} |
||||
|
// 显示弹窗
|
||||
|
maskLayer.show(); |
||||
|
popupView.show(); |
||||
|
} |
||||
|
// 文件下载的弹窗绘图
|
||||
|
const downloadPopupDrawing =(data) => { |
||||
|
// 以下为计算菜单的nview绘制布局,为固定算法,使用者无关关心
|
||||
|
const screenWidth = plus.screen.resolutionWidth; |
||||
|
const screenHeight = plus.screen.resolutionHeight; |
||||
|
//弹窗容器宽度
|
||||
|
const popupViewWidth = screenWidth * 0.7; |
||||
|
// 弹窗容器的Padding
|
||||
|
const viewContentPadding = 20; |
||||
|
// 弹窗容器的宽度
|
||||
|
const viewContentWidth = popupViewWidth - (viewContentPadding * 2); |
||||
|
// 弹窗容器高度
|
||||
|
let popupViewHeight = viewContentPadding * 3 + 60; |
||||
|
let progressTip = data.progressTip || "准备下载..."; |
||||
|
let contentText = data.contentText || "正在为您更新,请耐心等待"; |
||||
|
let elementList = [ |
||||
|
{ |
||||
|
tag: 'rect', //背景色
|
||||
|
color: '#FFFFFF', |
||||
|
rectStyles:{ |
||||
|
radius: "8px" |
||||
|
} |
||||
|
}, |
||||
|
{ |
||||
|
tag: 'font', |
||||
|
id: 'title', |
||||
|
text: "升级APP", |
||||
|
textStyles: { |
||||
|
size: '16px', |
||||
|
color: "#333", |
||||
|
weight: "bold", |
||||
|
verticalAlign: "middle", |
||||
|
whiteSpace: "normal" |
||||
|
}, |
||||
|
position: { |
||||
|
top: viewContentPadding + 'px', |
||||
|
height: "30px", |
||||
|
} |
||||
|
}, |
||||
|
{ |
||||
|
tag: 'font', |
||||
|
id: 'content', |
||||
|
text: contentText, |
||||
|
textStyles: { |
||||
|
size: '14px', |
||||
|
color: "#333", |
||||
|
verticalAlign: "middle", |
||||
|
whiteSpace: "normal" |
||||
|
}, |
||||
|
position: { |
||||
|
top: viewContentPadding * 2 + 30 + 'px', |
||||
|
height: "20px", |
||||
|
} |
||||
|
} |
||||
|
]; |
||||
|
// 是否有进度条
|
||||
|
if(data.progress){ |
||||
|
popupViewHeight += viewContentPadding + 40; |
||||
|
elementList = elementList.concat([ |
||||
|
{ |
||||
|
tag: 'font', |
||||
|
id: 'progressValue', |
||||
|
text: progressTip, |
||||
|
textStyles: { |
||||
|
size: '14px', |
||||
|
color: $mainColor, |
||||
|
whiteSpace: "normal" |
||||
|
}, |
||||
|
position: { |
||||
|
top: viewContentPadding * 4 + 20 + 'px', |
||||
|
height: "30px" |
||||
|
} |
||||
|
}, |
||||
|
{ |
||||
|
tag: 'rect', //绘制进度条背景
|
||||
|
id: 'progressBg', |
||||
|
rectStyles:{ |
||||
|
radius: "4px", |
||||
|
borderColor: "#f1f1f1", |
||||
|
borderWidth: "1px", |
||||
|
}, |
||||
|
position:{ |
||||
|
top: viewContentPadding * 4 + 60 + 'px', |
||||
|
left: viewContentPadding + "px", |
||||
|
width: viewContentWidth + "px", |
||||
|
height: "8px" |
||||
|
} |
||||
|
}, |
||||
|
]); |
||||
|
} |
||||
|
if (data.buttonNum == 2) { |
||||
|
popupViewHeight += viewContentPadding + 30; |
||||
|
elementList = elementList.concat([ |
||||
|
{ |
||||
|
tag: 'rect', //绘制底边按钮
|
||||
|
rectStyles:{ |
||||
|
radius: "3px", |
||||
|
borderColor: "#f1f1f1", |
||||
|
borderWidth: "1px", |
||||
|
}, |
||||
|
position:{ |
||||
|
bottom: viewContentPadding + 'px', |
||||
|
left: viewContentPadding + "px", |
||||
|
width: (viewContentWidth - viewContentPadding) / 2 + "px", |
||||
|
height: "30px" |
||||
|
} |
||||
|
}, |
||||
|
{ |
||||
|
tag: 'rect', //绘制底边按钮
|
||||
|
rectStyles:{ |
||||
|
radius: "3px", |
||||
|
color: $mainColor |
||||
|
}, |
||||
|
position:{ |
||||
|
bottom: viewContentPadding + 'px', |
||||
|
left: ((viewContentWidth - viewContentPadding) / 2 + viewContentPadding * 2) + "px", |
||||
|
width: (viewContentWidth - viewContentPadding) / 2 + "px", |
||||
|
height: "30px" |
||||
|
} |
||||
|
}, |
||||
|
{ |
||||
|
tag: 'font', |
||||
|
id: 'cancelText', |
||||
|
text: "取消下载", |
||||
|
textStyles: { |
||||
|
size: '14px', |
||||
|
color: "#666", |
||||
|
lineSpacing: "0%", |
||||
|
whiteSpace: "normal" |
||||
|
}, |
||||
|
position: { |
||||
|
bottom: viewContentPadding + 'px', |
||||
|
left: viewContentPadding + "px", |
||||
|
width: (viewContentWidth - viewContentPadding) / 2 + "px", |
||||
|
height: "30px", |
||||
|
} |
||||
|
}, |
||||
|
{ |
||||
|
tag: 'font', |
||||
|
id: 'confirmText', |
||||
|
text: "后台下载", |
||||
|
textStyles: { |
||||
|
size: '14px', |
||||
|
color: "#FFF", |
||||
|
lineSpacing: "0%", |
||||
|
whiteSpace: "normal" |
||||
|
}, |
||||
|
position: { |
||||
|
bottom: viewContentPadding + 'px', |
||||
|
left: ((viewContentWidth - viewContentPadding) / 2 + viewContentPadding * 2) + "px", |
||||
|
width: (viewContentWidth - viewContentPadding) / 2 + "px", |
||||
|
height: "30px", |
||||
|
} |
||||
|
} |
||||
|
]); |
||||
|
} |
||||
|
if (data.buttonNum == 1) { |
||||
|
popupViewHeight += viewContentPadding + 40; |
||||
|
elementList = elementList.concat([ |
||||
|
{ |
||||
|
tag: 'rect', //绘制底边按钮
|
||||
|
rectStyles:{ |
||||
|
radius: "6px", |
||||
|
color: $mainColor |
||||
|
}, |
||||
|
position:{ |
||||
|
bottom: viewContentPadding + 'px', |
||||
|
left: viewContentPadding + "px", |
||||
|
width: viewContentWidth + "px", |
||||
|
height: "40px" |
||||
|
} |
||||
|
}, |
||||
|
{ |
||||
|
tag: 'font', |
||||
|
id: 'confirmText', |
||||
|
text: "关闭", |
||||
|
textStyles: { |
||||
|
size: '14px', |
||||
|
color: "#FFF", |
||||
|
lineSpacing: "0%", |
||||
|
}, |
||||
|
position: { |
||||
|
bottom: viewContentPadding + 'px', |
||||
|
left: viewContentPadding + "px", |
||||
|
width: viewContentWidth + "px", |
||||
|
height: "40px" |
||||
|
} |
||||
|
} |
||||
|
]); |
||||
|
} |
||||
|
return { |
||||
|
popupViewHeight:popupViewHeight, |
||||
|
popupViewWidth:popupViewWidth, |
||||
|
screenHeight:screenHeight, |
||||
|
viewContentWidth:viewContentWidth, |
||||
|
viewContentPadding:viewContentPadding, |
||||
|
elementList: elementList |
||||
|
}; |
||||
|
} |
||||
|
// 文件下载的弹窗
|
||||
|
const downloadPopup=(data)=>{ |
||||
|
// 弹窗遮罩层
|
||||
|
let maskLayer = new plus.nativeObj.View("maskLayer", { //先创建遮罩层
|
||||
|
top: '0px', |
||||
|
left: '0px', |
||||
|
height: '100%', |
||||
|
width: '100%', |
||||
|
backgroundColor: 'rgba(0,0,0,0.5)' |
||||
|
}); |
||||
|
let popupViewData = downloadPopupDrawing(data); |
||||
|
// 弹窗内容
|
||||
|
let popupView = new plus.nativeObj.View("popupView", { //创建底部图标菜单
|
||||
|
tag: "rect", |
||||
|
top: (popupViewData.screenHeight - popupViewData.popupViewHeight) / 2 + "px", |
||||
|
left: '15%', |
||||
|
height: popupViewData.popupViewHeight + "px", |
||||
|
width: "70%", |
||||
|
}); |
||||
|
let progressValue = 0; |
||||
|
let progressTip = 0; |
||||
|
let contentText = 0; |
||||
|
let buttonNum = 2; |
||||
|
if(data.buttonNum >= 0){ |
||||
|
buttonNum = data.buttonNum; |
||||
|
} |
||||
|
popupView.draw(popupViewData.elementList); |
||||
|
let callbackData = { |
||||
|
change: function(res) { |
||||
|
let progressElement = []; |
||||
|
if(res.progressValue){ |
||||
|
progressValue = res.progressValue; |
||||
|
// 绘制进度条
|
||||
|
progressElement.push({ |
||||
|
tag: 'rect', //绘制进度条背景
|
||||
|
id: 'progressValueBg', |
||||
|
rectStyles:{ |
||||
|
radius: "4px", |
||||
|
color: $mainColor |
||||
|
}, |
||||
|
position:{ |
||||
|
top: popupViewData.viewContentPadding * 4 + 60 + 'px', |
||||
|
left: popupViewData.viewContentPadding + "px", |
||||
|
width: popupViewData.viewContentWidth * (res.progressValue / 100) + "px", |
||||
|
height: "8px" |
||||
|
} |
||||
|
}); |
||||
|
} |
||||
|
if(res.progressTip){ |
||||
|
progressTip = res.progressTip; |
||||
|
progressElement.push({ |
||||
|
tag: 'font', |
||||
|
id: 'progressValue', |
||||
|
text: res.progressTip, |
||||
|
textStyles: { |
||||
|
size: '14px', |
||||
|
color: $mainColor, |
||||
|
whiteSpace: "normal" |
||||
|
}, |
||||
|
position: { |
||||
|
top: popupViewData.viewContentPadding * 4 + 20 + 'px', |
||||
|
height: "30px" |
||||
|
} |
||||
|
}); |
||||
|
} |
||||
|
if(res.contentText){ |
||||
|
contentText = res.contentText; |
||||
|
progressElement.push({ |
||||
|
tag: 'font', |
||||
|
id: 'content', |
||||
|
text: res.contentText, |
||||
|
textStyles: { |
||||
|
size: '16px', |
||||
|
color: "#333", |
||||
|
whiteSpace: "normal" |
||||
|
}, |
||||
|
position: { |
||||
|
top: popupViewData.viewContentPadding * 2 + 30 + 'px', |
||||
|
height: "30px", |
||||
|
} |
||||
|
}); |
||||
|
} |
||||
|
if(res.buttonNum >= 0 && buttonNum != res.buttonNum){ |
||||
|
buttonNum = res.buttonNum; |
||||
|
popupView.reset(); |
||||
|
popupViewData = downloadPopupDrawing(Object.assign({ |
||||
|
progressValue:progressValue, |
||||
|
progressTip:progressTip, |
||||
|
contentText:contentText, |
||||
|
},res)); |
||||
|
let newElement = []; |
||||
|
popupViewData.elementList.map((item,index) => { |
||||
|
let have = false; |
||||
|
progressElement.forEach((childItem,childIndex) => { |
||||
|
if(item.id == childItem.id){ |
||||
|
have = true; |
||||
|
} |
||||
|
}); |
||||
|
if(!have){ |
||||
|
newElement.push(item); |
||||
|
} |
||||
|
}); |
||||
|
progressElement = newElement.concat(progressElement); |
||||
|
popupView.setStyle({ |
||||
|
tag: "rect", |
||||
|
top: (popupViewData.screenHeight - popupViewData.popupViewHeight) / 2 + "px", |
||||
|
left: '15%', |
||||
|
height: popupViewData.popupViewHeight + "px", |
||||
|
width: "70%", |
||||
|
}); |
||||
|
popupView.draw(progressElement); |
||||
|
}else{ |
||||
|
popupView.draw(progressElement); |
||||
|
} |
||||
|
}, |
||||
|
cancel: function() { |
||||
|
maskLayer.hide(); |
||||
|
popupView.hide(); |
||||
|
} |
||||
|
} |
||||
|
popupView.addEventListener("click", function(e) { |
||||
|
let maxTop = popupViewData.popupViewHeight - popupViewData.viewContentPadding; |
||||
|
let maxLeft = popupViewData.popupViewWidth - popupViewData.viewContentPadding; |
||||
|
if (e.clientY > maxTop - 40 && e.clientY < maxTop) { |
||||
|
if(buttonNum == 1){ |
||||
|
// 单按钮
|
||||
|
if (e.clientX > popupViewData.viewContentPadding && e.clientX < maxLeft) { |
||||
|
maskLayer.hide(); |
||||
|
popupView.hide(); |
||||
|
callbackData.reboot(); |
||||
|
} |
||||
|
}else if(buttonNum == 2){ |
||||
|
// 双按钮
|
||||
|
let buttonWidth = (popupViewData.viewContentWidth - popupViewData.viewContentPadding) / 2; |
||||
|
if (e.clientX > popupViewData.viewContentPadding && e.clientX < maxLeft - buttonWidth - popupViewData.viewContentPadding) { |
||||
|
maskLayer.hide(); |
||||
|
popupView.hide(); |
||||
|
callbackData.cancelDownload(); |
||||
|
} else if (e.clientX > maxLeft - buttonWidth && e.clientX < maxLeft) { |
||||
|
maskLayer.hide(); |
||||
|
popupView.hide(); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
}); |
||||
|
// 显示弹窗
|
||||
|
maskLayer.show(); |
||||
|
popupView.show(); |
||||
|
// 改变进度条
|
||||
|
return callbackData; |
||||
|
} |
||||
|
|
||||
|
export default (isPrompt = false)=>{ |
||||
|
|
||||
|
getCurrentNo(versionInfo => { |
||||
|
let httpData = { |
||||
|
release: versionInfo.versionCode, |
||||
|
// 版本名称
|
||||
|
version: versionInfo.versionName, |
||||
|
// setupPage参数说明(判断用户是不是从设置页面点击的更新,如果是设置页面点击的更新,有不要用静默更新了,不然用户点击没反应很奇怪的)
|
||||
|
setupPage: isPrompt |
||||
|
}; |
||||
|
if (platform == "android") { |
||||
|
httpData.type = 1101; |
||||
|
} else { |
||||
|
httpData.type = 1102; |
||||
|
} |
||||
|
/* 接口入参说明 |
||||
|
* version: 应用当前版本号(已自动获取) |
||||
|
* versionName: 应用当前版本名称(已自动获取) |
||||
|
* type:平台(1101是安卓,1102是IOS) |
||||
|
*/ |
||||
|
/****************以下是示例*******************/ |
||||
|
// 可以用自己项目的请求方法(接口自己找后台要,插件不提供)
|
||||
|
postJsonRequest($checkUpdateUrl,httpData).then((e)=>{ |
||||
|
let res=e.data; |
||||
|
if (res && res.downloadUrl) { |
||||
|
if (res.updateType == "forcibly" || res.updateType == "silent") { |
||||
|
if (/\.wgt$/i.test(res.downloadUrl)) { |
||||
|
getDownload(res); |
||||
|
} else if(/\.html$/i.test(res.downloadUrl)){ |
||||
|
plus.runtime.openURL(res.downloadUrl); |
||||
|
} else { |
||||
|
if (platform == "android") { |
||||
|
getDownload(res); |
||||
|
} else { |
||||
|
plus.runtime.openURL(res.downloadUrl); |
||||
|
} |
||||
|
} |
||||
|
} else if(res.updateType == "solicit"){ |
||||
|
updatePopup(res, function() { |
||||
|
if (/\.wgt$/i.test(res.downloadUrl)) { |
||||
|
getDownload(res); |
||||
|
} else if(/\.html$/i.test(res.downloadUrl)){ |
||||
|
plus.runtime.openURL(res.downloadUrl); |
||||
|
} else { |
||||
|
if (platform == "android") { |
||||
|
getDownload(res); |
||||
|
} else { |
||||
|
plus.runtime.openURL(res.downloadUrl); |
||||
|
} |
||||
|
} |
||||
|
}); |
||||
|
} |
||||
|
} else if (isPrompt) { |
||||
|
uni.showToast({ |
||||
|
title: "暂无新版本", |
||||
|
icon: "none" |
||||
|
}); |
||||
|
} |
||||
|
}) |
||||
|
}); |
||||
|
} |
||||
|
|
||||
@ -0,0 +1,65 @@ |
|||||
|
import CryptoJS from "crypto-js"; |
||||
|
|
||||
|
// 如果为开启ssl证书,请修改为http协议
|
||||
|
// let scheme ="https"; //协议头
|
||||
|
let scheme ="http"; //协议头
|
||||
|
// 请将下面的域名替换成自己的服务器域名,如果有端口要把端口加上
|
||||
|
let host = '192.168.66.16:8007'; |
||||
|
|
||||
|
// 为了避免h5页面被其他人盗用,可以自由选择加密方式,详细教程查看readme.md文件
|
||||
|
let hostToken="sha5602584d3bc770dd47e9680a99d0efe6bi"; |
||||
|
|
||||
|
// 是否开启H5的调试模式
|
||||
|
let isVConsole = false |
||||
|
|
||||
|
// 以下内容请勿修改
|
||||
|
// 以下内容请勿修改
|
||||
|
// 以下内容请勿修改
|
||||
|
// 以下内容请勿修改
|
||||
|
|
||||
|
// #ifdef MP-WEIXIN
|
||||
|
let env = wx.getAccountInfoSync() |
||||
|
|
||||
|
if (env.miniProgram.envVersion == 'develop') { |
||||
|
isVConsole = true |
||||
|
} |
||||
|
// #endif
|
||||
|
// #ifdef APP-PLUS || H5
|
||||
|
if (process.env.NODE_ENV === 'development') { |
||||
|
isVConsole = true |
||||
|
} |
||||
|
// #endif
|
||||
|
|
||||
|
let apiUrl = scheme + '://' + host; |
||||
|
let wssUrl = (scheme == 'https' ? 'wss' :'ws') + '://' + host + '/wss'; |
||||
|
|
||||
|
let hostMd5=CryptoJS.MD5(CryptoJS.MD5(host).toString()).toString(); |
||||
|
if('sha'+hostMd5+'bi'!=hostToken){ |
||||
|
apiUrl = 'false'; |
||||
|
wssUrl = 'false'; |
||||
|
} |
||||
|
|
||||
|
// app更新的配置
|
||||
|
const updateConfig = { |
||||
|
url:apiUrl+'/common/pub/checkVersion', //检查版本的接口
|
||||
|
bgColor:'', //升级主色,按钮背景颜色
|
||||
|
iconUrl:'' //升级小图标
|
||||
|
} |
||||
|
|
||||
|
/* 检查版本需要返回的数据说明 |
||||
|
* | 参数名称 | 一定返回 | 类型 | 描述 |
||||
|
* | -------------|--------- | --------- | ------------- | |
||||
|
* | versionCode | y | int | 版本号 :20240331 | |
||||
|
* | versionName | y | String | 版本名称 :4.0.1 | |
||||
|
* | versionInfo | y | String | 版本信息 : 修复了bug | |
||||
|
* | updateType | y | String | forcibly = 强制更新, solicit = 弹窗确认更新, silent = 静默更新 | |
||||
|
* | downloadUrl | y | String | 版本下载链接(IOS安装包更新请放跳转store应用商店链接,安卓apk和wgt文件放文件下载链接) | |
||||
|
*/ |
||||
|
|
||||
|
|
||||
|
export default { |
||||
|
apiUrl, |
||||
|
wssUrl, |
||||
|
isVConsole, |
||||
|
updateConfig |
||||
|
} |
||||
@ -0,0 +1,72 @@ |
|||||
|
import { |
||||
|
apiUrl, |
||||
|
postJsonRequest, |
||||
|
} from '@/utils/request.js'; |
||||
|
|
||||
|
const verifyQr=(url)=>{ |
||||
|
let pathinfo=url.replace(apiUrl,''); |
||||
|
let pathParts = pathinfo.split('/'); // 使用斜杠字符分割字符串
|
||||
|
let lastPart = pathParts[pathParts.length - 1]; // 获取最后一组数据
|
||||
|
postJsonRequest(pathinfo,{realToken:lastPart}).then((res)=>{ |
||||
|
if(res.code==0){ |
||||
|
switch(res.data.action){ |
||||
|
case 'groupInfo': |
||||
|
uni.navigateTo({ |
||||
|
url: '/pages/message/group/info?group_id='+ res.data.id |
||||
|
}) |
||||
|
break; |
||||
|
case 'userInfo': |
||||
|
uni.navigateTo({ |
||||
|
url:"/pages/contacts/detail?id="+res.data.id |
||||
|
}) |
||||
|
break; |
||||
|
} |
||||
|
} |
||||
|
}) |
||||
|
} |
||||
|
|
||||
|
const scanQr=()=>{ |
||||
|
// #ifndef H5
|
||||
|
uni.scanCode({ |
||||
|
success: function (res) { |
||||
|
checkQr(res.result); |
||||
|
} |
||||
|
}); |
||||
|
// #endif
|
||||
|
// #ifdef H5
|
||||
|
uni.navigateTo({ |
||||
|
url:'/pages/index/scan' |
||||
|
}) |
||||
|
// #endif
|
||||
|
} |
||||
|
|
||||
|
const checkQr=(data)=>{ |
||||
|
// 如果识别出二维码是跟服务器的地址一样,就请求该接口
|
||||
|
if(data.includes(apiUrl)){ |
||||
|
verifyQr(data); |
||||
|
}else{ |
||||
|
uni.showModal({ |
||||
|
title: '已识别内容', |
||||
|
content: data, |
||||
|
confirmText:'复制内容', |
||||
|
success: function (e) { |
||||
|
if (e.confirm) { |
||||
|
uni.setClipboardData({ |
||||
|
data: data, |
||||
|
success: function () { |
||||
|
uni.showToast({ |
||||
|
title:'复制成功', |
||||
|
icon:'none' |
||||
|
}) |
||||
|
} |
||||
|
}); |
||||
|
} |
||||
|
} |
||||
|
}); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
export default { |
||||
|
scanQr, |
||||
|
checkQr |
||||
|
} |
||||
@ -0,0 +1,193 @@ |
|||||
|
import api from '@/common/config.js' // 接口Api,图片地址等等配置,可根据自身情况引入,也可以直接在下面url填入你的 webSocket连接地址
|
||||
|
class socketIO { |
||||
|
constructor(data, time, url) { |
||||
|
this.socketTask = null |
||||
|
this.is_open_socket = false //避免重复连接
|
||||
|
this.url = url ? url : api.wssUrl //连接地址
|
||||
|
this.data = data ? data : null |
||||
|
this.connectNum = 1 // 重连次数
|
||||
|
this.traderDetailIndex = 100 // traderDetailIndex ==2 重连
|
||||
|
this.accountStateIndex = 100 // accountStateIndex ==1 重连
|
||||
|
this.followFlake = false // followFlake == true 重连
|
||||
|
this.init=false; |
||||
|
//心跳检测
|
||||
|
this.timeout = time ? time : 25000 //多少秒执行检测
|
||||
|
this.heartbeatInterval = null //检测服务器端是否还活着
|
||||
|
this.reconnectTimeOut = null //重连之后多久再次重连
|
||||
|
this.networkStatus=true |
||||
|
} |
||||
|
|
||||
|
CALLBACK = (res) => { |
||||
|
if(res.isConnected){ |
||||
|
this.traderDetailIndex=2; |
||||
|
this.connectSocketInit({type:'ping'}) |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
// 进入这个页面的时候创建websocket连接【整个页面随时使用】
|
||||
|
connectSocketInit(data) { |
||||
|
this.data = data |
||||
|
this.socketTask = uni.connectSocket({ |
||||
|
url: this.url, |
||||
|
success: () => { |
||||
|
// console.info("正准备建立websocket中...");
|
||||
|
// 返回实例
|
||||
|
return this.socketTask |
||||
|
}, |
||||
|
}); |
||||
|
this.socketTask.onOpen((res) => { |
||||
|
uni.$emit('socketStatus',true); |
||||
|
if(!this.networkStatus){ |
||||
|
// 连接成功后取消网络监听
|
||||
|
uni.offNetworkStatusChange(this.CALLBACK); |
||||
|
} |
||||
|
this.networkStatus=true; |
||||
|
this.connectNum = 1 |
||||
|
// console.info("WebSocket连接正常!");
|
||||
|
this.send(data) |
||||
|
clearInterval(this.reconnectTimeOut) |
||||
|
clearInterval(this.heartbeatInterval) |
||||
|
this.is_open_socket = true; |
||||
|
this.start(); |
||||
|
// 注:只有连接正常打开中 ,才能正常收到消息
|
||||
|
this.socketTask.onMessage((e) => { |
||||
|
// 字符串转json
|
||||
|
let res = JSON.parse(e.data); |
||||
|
if (res) { |
||||
|
uni.$emit('getPositonsOrder', res); |
||||
|
} |
||||
|
}); |
||||
|
}) |
||||
|
if(!this.init){ |
||||
|
// 监听连接失败,这里代码我注释掉的原因是因为如果服务器关闭后,和下面的onclose方法一起发起重连操作,这样会导致重复连接
|
||||
|
uni.onSocketError((res) => { |
||||
|
console.info(res,'WebSocket连接打开失败,请检查!'); |
||||
|
this.socketTask = null |
||||
|
this.is_open_socket = false; |
||||
|
clearInterval(this.heartbeatInterval) |
||||
|
clearInterval(this.reconnectTimeOut) |
||||
|
if (this.connectNum < 10) { |
||||
|
this.traderDetailIndex = 2 |
||||
|
this.reconnect(); |
||||
|
this.connectNum += 1 |
||||
|
} else { |
||||
|
uni.$emit('connectError'); |
||||
|
this.networkStatus=false |
||||
|
uni.onNetworkStatusChange(this.CALLBACK); |
||||
|
this.connectNum = 1 |
||||
|
} |
||||
|
}); |
||||
|
this.init=true; |
||||
|
} |
||||
|
// 这里仅是事件监听【如果socket关闭了会执行】
|
||||
|
this.socketTask.onClose(() => { |
||||
|
console.info("已经被关闭了-------") |
||||
|
clearInterval(this.heartbeatInterval) |
||||
|
clearInterval(this.reconnectTimeOut) |
||||
|
this.is_open_socket = false; |
||||
|
this.socketTask = null |
||||
|
if (this.connectNum < 5) { |
||||
|
this.reconnect(); |
||||
|
} else { |
||||
|
uni.$emit('connectError'); |
||||
|
this.networkStatus=false; |
||||
|
uni.onNetworkStatusChange(this.CALLBACK); |
||||
|
this.connectNum = 1 |
||||
|
} |
||||
|
|
||||
|
}) |
||||
|
} |
||||
|
// 主动关闭socket连接
|
||||
|
Close() { |
||||
|
if (!this.is_open_socket) { |
||||
|
return |
||||
|
} |
||||
|
this.socketTask.close({ |
||||
|
success() { |
||||
|
uni.showToast({ |
||||
|
title: 'SocketTask 关闭成功', |
||||
|
icon: "none" |
||||
|
}); |
||||
|
} |
||||
|
}); |
||||
|
} |
||||
|
//发送消息
|
||||
|
send(data) { |
||||
|
// 注:只有连接正常打开中 ,才能正常成功发送消息
|
||||
|
if (this.socketTask) { |
||||
|
this.socketTask.send({ |
||||
|
data: JSON.stringify(data), |
||||
|
async success() { |
||||
|
}, |
||||
|
}); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
// 检测状态
|
||||
|
checkStatus(){ |
||||
|
// console.info("检查状态")
|
||||
|
clearInterval(this.reconnectTimeOut) |
||||
|
if(!this.socketTask || [2,3].includes(this.socketTask.readyState)){ |
||||
|
// console.info("未链接!")
|
||||
|
return false; |
||||
|
} |
||||
|
return true; |
||||
|
} |
||||
|
|
||||
|
//开启心跳检测
|
||||
|
start() { |
||||
|
this.heartbeatInterval = setInterval(() => { |
||||
|
this.send({ |
||||
|
"type": "ping" |
||||
|
}); |
||||
|
}, this.timeout) |
||||
|
} |
||||
|
//重新连接
|
||||
|
reconnect() { |
||||
|
//停止发送心跳
|
||||
|
console.info('检查是否手动断开,并重新连接') |
||||
|
clearInterval(this.heartbeatInterval) |
||||
|
//如果不是人为关闭的话,进行重连
|
||||
|
if (!this.is_open_socket && (this.traderDetailIndex == 2 || this.accountStateIndex == 0 || this |
||||
|
.followFlake)) { |
||||
|
console.info("5秒后重新连接...") |
||||
|
this.reconnectTimeOut = setInterval(() => { |
||||
|
this.connectSocketInit(this.data); |
||||
|
}, 15000) |
||||
|
} |
||||
|
} |
||||
|
/** |
||||
|
* @description 将 scoket 数据进行过滤 |
||||
|
* @param {array} array |
||||
|
* @param {string} type 区分 弹窗 openposition 分为跟随和我的 |
||||
|
*/ |
||||
|
arrayFilter(array, type = 'normal', signalId = 0) { |
||||
|
let arr1 = [] |
||||
|
let arr2 = [] |
||||
|
let obj = { |
||||
|
arr1: [], |
||||
|
arr2: [] |
||||
|
} |
||||
|
arr1 = array.filter(v => v.flwsig == true) |
||||
|
arr2 = array.filter(v => v.flwsig == false) |
||||
|
if (type == 'normal') { |
||||
|
if (signalId) { |
||||
|
arr1 = array.filter(v => v.flwsig == true && v.sigtraderid == signalId) |
||||
|
return arr1 |
||||
|
} else { |
||||
|
return arr1.concat(arr2) |
||||
|
} |
||||
|
} else { |
||||
|
if (signalId > 0) { |
||||
|
arr1 = array.filter(v => v.flwsig == true && v.sigtraderid == signalId) |
||||
|
obj.arr1 = arr1 |
||||
|
} else { |
||||
|
obj.arr1 = arr1 |
||||
|
} |
||||
|
obj.arr2 = arr2 |
||||
|
|
||||
|
return obj |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
export default socketIO |
||||
@ -0,0 +1,53 @@ |
|||||
|
<template> |
||||
|
<view class="lz-empty-box"> |
||||
|
<view v-if="showImage" class="im-flex im-align-items-center im-justify-content-center"> |
||||
|
<!-- 请根据您的项目要求制作并更换为空图片 --> |
||||
|
<image class="empty-img" :src="src"></image> |
||||
|
</view> |
||||
|
<view class="text-center"> |
||||
|
<text :class="showImage ? ' mt-20' : '' " :style='{color:textcolor}'>{{noDatatext}}</text> |
||||
|
</view> |
||||
|
|
||||
|
</view> |
||||
|
</template> |
||||
|
|
||||
|
<script> |
||||
|
export default { |
||||
|
name: 'Empty', |
||||
|
props: { |
||||
|
src: { |
||||
|
type: String, |
||||
|
default: '/static/image/empty.png' |
||||
|
}, // 图片路径 |
||||
|
showImage: { |
||||
|
type: Boolean, |
||||
|
default: true |
||||
|
}, |
||||
|
noDatatext: { |
||||
|
type: String, |
||||
|
default: () => { |
||||
|
return '暂无数据' |
||||
|
} |
||||
|
}, |
||||
|
textcolor: { |
||||
|
type: String, |
||||
|
default: "#333333" |
||||
|
} |
||||
|
}, |
||||
|
data() { |
||||
|
return {}; |
||||
|
} |
||||
|
|
||||
|
}; |
||||
|
</script> |
||||
|
|
||||
|
<style> |
||||
|
.lz-empty-box { |
||||
|
margin: 40rpx auto; |
||||
|
align:center |
||||
|
} |
||||
|
|
||||
|
.empty-img { |
||||
|
margin-top: 40rpx; |
||||
|
} |
||||
|
</style> |
||||
@ -0,0 +1,75 @@ |
|||||
|
<template> |
||||
|
<view class="mr-5 radius-4 im-tags lh-15x" :class="[ |
||||
|
size=='mini' ? 'im-tags-mini' : '', |
||||
|
size=='small' ? 'im-tags-samll' : '', |
||||
|
size=='medium' ? 'im-tags-medium' : '', |
||||
|
type == 'info' ? 'im-tags-info' : '' , |
||||
|
type == 'success' ? 'im-tags-success' : '' , |
||||
|
type == 'warning' ? 'im-tags-warning' : '', |
||||
|
type == 'danger' ? 'im-tags-danger' : '',]" |
||||
|
>{{text}}</view> |
||||
|
</template> |
||||
|
|
||||
|
<script> |
||||
|
export default { |
||||
|
name: "tags", |
||||
|
props: { |
||||
|
size:{type: String,default:'mini'}, // 图片路径 |
||||
|
type:{type: String,default:'primary'}, |
||||
|
text:{type: String, default:()=>{return '暂无'}} |
||||
|
}, |
||||
|
data() { |
||||
|
return {} |
||||
|
} |
||||
|
} |
||||
|
</script> |
||||
|
|
||||
|
<style> |
||||
|
.im-tags{ |
||||
|
color: #175CFF; |
||||
|
background-color: #D4E1FF; |
||||
|
padding:10rpx 20rpx; |
||||
|
font-size: 24rpx; |
||||
|
border: 1px solid #b5cbff; |
||||
|
} |
||||
|
|
||||
|
.im-tags-success{ |
||||
|
background-color: #f0f9eb; |
||||
|
border-color: #e1f3d8; |
||||
|
color: #67c23a; |
||||
|
} |
||||
|
|
||||
|
.im-tags-info { |
||||
|
background-color: #f4f4f5; |
||||
|
border-color: #e9e9eb; |
||||
|
color: #909399; |
||||
|
} |
||||
|
|
||||
|
.im-tags-warning { |
||||
|
background-color: #fdf6ec; |
||||
|
border-color: #faecd8; |
||||
|
color: #e6a23c; |
||||
|
} |
||||
|
|
||||
|
.im-tags-danger { |
||||
|
background-color: #fef0f0; |
||||
|
border-color: #fde2e2; |
||||
|
color: #f56c6c; |
||||
|
} |
||||
|
|
||||
|
|
||||
|
.im-tags-mini{ |
||||
|
padding:0 8rpx; |
||||
|
font-size: 22rpx; |
||||
|
} |
||||
|
|
||||
|
.im-tags-small{ |
||||
|
padding:6rpx 12rpx; |
||||
|
font-size: 22rpx; |
||||
|
} |
||||
|
.im-tags-medium{ |
||||
|
padding:8rpx 16rpx; |
||||
|
font-size: 24rpx; |
||||
|
} |
||||
|
|
||||
|
</style> |
||||
@ -0,0 +1,63 @@ |
|||||
|
<template> |
||||
|
<view> |
||||
|
<scroll-view class="folder-wap" :scroll-x="true" :scroll-left="99999999"> |
||||
|
<view class="im-flex im-justify-content-start im-align-items-center"> |
||||
|
<view class="tab-item im-flex im-justify-content-start im-align-items-center" v-for="(item, index) in tree" @tap="open(item)" :key='index'> |
||||
|
<view class="mar10 lz-tree-name font-color-999" :class="{ 'font-color-333': index == tree.length - 1 }">{{ item.name }}</view> |
||||
|
<slot name="icon"> |
||||
|
<text class="font-color-999" :class="[icon ? icon :'cuIcon-right']" v-if="index < tree.length - 1"></text> |
||||
|
</slot> |
||||
|
</view> |
||||
|
</view> |
||||
|
</scroll-view> |
||||
|
</view> |
||||
|
</template> |
||||
|
|
||||
|
<script> |
||||
|
export default { |
||||
|
name: "breadcrum", |
||||
|
props: { |
||||
|
tree:{type: Array,default:()=>{return {}}}, |
||||
|
icon:{type: String,default:()=>{return 'cuIcon-right'}} |
||||
|
}, |
||||
|
data() { |
||||
|
return {} |
||||
|
}, |
||||
|
methods:{ |
||||
|
open(item){ |
||||
|
this.$emit('openBread',item); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
</script> |
||||
|
|
||||
|
<style lang="scss"> |
||||
|
.folder-wap { |
||||
|
box-sizing: border-box; |
||||
|
width: 100%; |
||||
|
white-space: nowrap; |
||||
|
padding:20rpx; |
||||
|
.tab-item { |
||||
|
padding-bottom: 10rpx; |
||||
|
box-sizing: border-box; |
||||
|
.lz-tree-name{ |
||||
|
margin:0 20rpx; |
||||
|
} |
||||
|
.iconfont{ |
||||
|
font-size:24rpx !important; |
||||
|
} |
||||
|
&:last-child { |
||||
|
margin-right: 32rpx; |
||||
|
.lz-tree-name{ |
||||
|
margin-right:80rpx; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
.font-color-333{ |
||||
|
color:#303133 !important; |
||||
|
} |
||||
|
.font-color-999{ |
||||
|
color:#909399 |
||||
|
} |
||||
|
</style> |
||||
@ -0,0 +1,79 @@ |
|||||
|
<template> |
||||
|
<view> |
||||
|
<view class="cu-custom" :style="[{height:CustomBar + 'px'}]"> |
||||
|
<view class="cu-bar fixed" :style="style" :class="[bgImage!=''?'none-bg text-white bg-img':'',bgColor]"> |
||||
|
<view class="action" @tap="BackPage" v-if="isBack"> |
||||
|
<text class="cuIcon-back"></text> |
||||
|
<slot name="backText"></slot> |
||||
|
</view> |
||||
|
<view class="action" v-else> |
||||
|
<slot name="backText"></slot> |
||||
|
</view> |
||||
|
<view class="content" :style="[{top:StatusBar + 'px'}]"> |
||||
|
<slot name="content"></slot> |
||||
|
</view> |
||||
|
<view class="right"> |
||||
|
<slot name="right"></slot> |
||||
|
</view> |
||||
|
|
||||
|
</view> |
||||
|
</view> |
||||
|
</view> |
||||
|
</template> |
||||
|
|
||||
|
<script> |
||||
|
export default { |
||||
|
data() { |
||||
|
return { |
||||
|
StatusBar: this.StatusBar, |
||||
|
CustomBar: this.CustomBar |
||||
|
}; |
||||
|
}, |
||||
|
name: 'custom', |
||||
|
computed: { |
||||
|
style() { |
||||
|
var StatusBar= this.StatusBar; |
||||
|
var CustomBar= this.CustomBar; |
||||
|
var bgImage = this.bgImage; |
||||
|
var style = `height:${CustomBar}px;padding-top:${StatusBar}px;`; |
||||
|
if (this.bgImage) { |
||||
|
style = `${style}background-image:url(${bgImage});`; |
||||
|
} |
||||
|
return style |
||||
|
} |
||||
|
}, |
||||
|
props: { |
||||
|
bgColor: { |
||||
|
type: String, |
||||
|
default: '' |
||||
|
}, |
||||
|
isBack: { |
||||
|
type: [Boolean, String], |
||||
|
default: false |
||||
|
}, |
||||
|
bgImage: { |
||||
|
type: String, |
||||
|
default: '' |
||||
|
}, |
||||
|
}, |
||||
|
methods: { |
||||
|
BackPage() { |
||||
|
const allroutes = getCurrentPages(); |
||||
|
const cureentRoute = allroutes[allroutes.length - 1].route; |
||||
|
// 如果当前在聊天页面,自动返回到首页,避免层级过深 |
||||
|
if (cureentRoute == 'pages/message/chat') { |
||||
|
uni.switchTab({ |
||||
|
url: '/pages/index/index' |
||||
|
}) |
||||
|
return; |
||||
|
}else if (allroutes.length < 2 && 'undefined' !== typeof __wxConfig) { |
||||
|
let url = '/' + __wxConfig.pages[0] |
||||
|
return uni.redirectTo({url}) |
||||
|
} |
||||
|
uni.navigateBack({ |
||||
|
delta: 1 |
||||
|
}); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
</script> |
||||
@ -0,0 +1,433 @@ |
|||||
|
<template> |
||||
|
<view class="canvasBox"> |
||||
|
<template v-if="isUse"> |
||||
|
<view class="box"> |
||||
|
<view class="line"></view> |
||||
|
<view class="angle"></view> |
||||
|
</view> |
||||
|
<view class="box2" v-if="isUseTorch"> |
||||
|
<view class="track" @click="openTrack"> |
||||
|
<svg |
||||
|
t="1653920715959" |
||||
|
class="icon" |
||||
|
viewBox="0 0 1024 1024" |
||||
|
version="1.1" |
||||
|
xmlns="http://www.w3.org/2000/svg" |
||||
|
p-id="1351" |
||||
|
width="32" |
||||
|
height="32" |
||||
|
> |
||||
|
<path |
||||
|
d="M651.353043 550.479503H378.752795L240.862609 364.315031c-3.688944-4.897391-5.660621-10.876025-5.660621-17.045466v-60.040745c0-15.773416 12.847702-28.621118 28.621118-28.621118h502.459627c15.773416 0 28.621118 12.847702 28.621118 28.621118v59.977143c0 6.105839-1.971677 12.084472-5.660621 17.045466l-137.890187 186.228074zM378.752795 598.308571v398.024348c0 15.328199 12.402484 27.667081 27.667081 27.667081h217.266087c15.328199 0 27.667081-12.402484 27.66708-27.667081V598.308571H378.752795z m136.300124 176.942112c-14.564969 0-26.331429-11.76646-26.331428-26.331428v-81.283975c0-14.564969 11.76646-26.331429 26.331428-26.331429 14.564969 0 26.331429 11.76646 26.331429 26.331429v81.283975c0 14.564969-11.76646 26.331429-26.331429 26.331428zM512 222.608696c-17.554286 0-31.801242-14.246957-31.801242-31.801243V31.801242c0-17.554286 14.246957-31.801242 31.801242-31.801242s31.801242 14.246957 31.801242 31.801242v159.006211c0 17.554286-14.246957 31.801242-31.801242 31.801243zM280.932174 205.881242c-9.47677 0-18.889938-4.197764-25.122981-12.275279L158.242981 67.991056a31.864845 31.864845 0 0 1 5.597019-44.648944 31.864845 31.864845 0 0 1 44.648944 5.597018l97.502609 125.551305a31.864845 31.864845 0 0 1-5.597019 44.648944c-5.787826 4.579379-12.656894 6.741863-19.46236 6.741863zM723.987081 205.881242c-6.805466 0-13.674534-2.162484-19.462361-6.678261a31.794882 31.794882 0 0 1-5.597018-44.648944l97.566211-125.551304a31.794882 31.794882 0 0 1 44.648944-5.597019 31.794882 31.794882 0 0 1 5.597019 44.648944l-97.566211 125.551305c-6.360248 8.077516-15.709814 12.27528-25.186584 12.275279z" |
||||
|
fill="#ffffff" |
||||
|
p-id="1352" |
||||
|
></path> |
||||
|
</svg> |
||||
|
{{ trackStatus ? '关闭闪光灯' : '打开闪光灯' }} |
||||
|
</view> |
||||
|
</view> |
||||
|
|
||||
|
<view class="mask1 mask" :style="'height:' + maskHeight + 'px;'"></view> |
||||
|
<view |
||||
|
class="mask2 mask" |
||||
|
:style="'width:' + maskWidth + 'px;top:' + maskHeight + 'px;height:' + canvasHeight + 'px'" |
||||
|
></view> |
||||
|
<view class="mask3 mask" :style="'height:' + maskHeight + 'px;'"></view> |
||||
|
<view |
||||
|
class="mask4 mask" |
||||
|
:style="'width:' + maskWidth + 'px;top:' + maskHeight + 'px;height:' + canvasHeight + 'px'" |
||||
|
></view> |
||||
|
</template> |
||||
|
<template v-else> |
||||
|
<slot name="error"> |
||||
|
<view class="error"> |
||||
|
<view class="on1">相机权限被拒绝,请尝试如下操作:</view> |
||||
|
<view>· 刷新页面后重试;</view> |
||||
|
<view>· 在系统中检测当前App或浏览器的相机权限是否被禁用;</view> |
||||
|
<view>· 如果依然不能体验,建议在微信中打开链接;</view> |
||||
|
</view> |
||||
|
</slot> |
||||
|
</template> |
||||
|
</view> |
||||
|
</template> |
||||
|
|
||||
|
<script> |
||||
|
import jsQR from "jsqr" |
||||
|
export default { |
||||
|
props: { |
||||
|
continue: { |
||||
|
type: Boolean, |
||||
|
default: false // false 监听一次 true 持续监听 |
||||
|
}, |
||||
|
exact: { |
||||
|
type: String, |
||||
|
default: 'environment' // environment 后摄像头 user 前摄像头 |
||||
|
}, |
||||
|
size: { |
||||
|
type: String, |
||||
|
default: 'whole' // whole 全屏 balf 半屏 |
||||
|
}, |
||||
|
definition: { |
||||
|
type: Boolean, |
||||
|
default: false // fasle 正常 true 高清 |
||||
|
} |
||||
|
}, |
||||
|
data() { |
||||
|
return { |
||||
|
windowWidth: 0, |
||||
|
windowHeight: 0, |
||||
|
video: null, |
||||
|
canvas2d: null, |
||||
|
canvas2d2: null, |
||||
|
canvasWidth: 200, |
||||
|
canvasHeight: 200, |
||||
|
maskWidth: 0, |
||||
|
maskHeight: 0, |
||||
|
inter: 0, |
||||
|
track: null, |
||||
|
isUseTorch: false, |
||||
|
trackStatus: false, |
||||
|
isParse: false, |
||||
|
isUse: true |
||||
|
} |
||||
|
}, |
||||
|
mounted() { |
||||
|
if (origin.indexOf('https') === -1) throw '请在 https 环境中使用摄像头组件。' |
||||
|
|
||||
|
this.windowWidth = document.documentElement.clientWidth || document.body.clientWidth |
||||
|
this.windowHeight = document.documentElement.clientHeight || document.body.clientHeight |
||||
|
this.windowHeight = this.size === 'whole' ? this.windowHeight : this.windowHeight / 2 |
||||
|
this.isParse = true |
||||
|
|
||||
|
this.$nextTick(() => { |
||||
|
this.createMsk() |
||||
|
this.openScan() |
||||
|
}) |
||||
|
}, |
||||
|
destroyed() { |
||||
|
this.closeCamera() |
||||
|
}, |
||||
|
methods: { |
||||
|
openScan() { |
||||
|
const width = this.transtion(this.windowHeight) |
||||
|
const height = this.transtion(this.windowWidth) |
||||
|
const videoParam = { |
||||
|
audio: false, |
||||
|
video: { |
||||
|
facingMode: { exact: this.exact }, |
||||
|
width, |
||||
|
height |
||||
|
} |
||||
|
} |
||||
|
navigator.mediaDevices |
||||
|
.getUserMedia(videoParam) |
||||
|
.then(stream => { |
||||
|
this.video = document.createElement('video') |
||||
|
this.video.width = this.windowWidth |
||||
|
this.video.height = this.windowHeight |
||||
|
|
||||
|
const canvas = document.createElement('canvas') |
||||
|
canvas.id = 'canvas' |
||||
|
canvas.width = this.transtion(this.canvasWidth) |
||||
|
canvas.height = this.transtion(this.canvasHeight) |
||||
|
canvas.style = 'display:none;' |
||||
|
//canvas.style = 'position: fixed;top: 0;z-index: 999;left:0' |
||||
|
this.canvas2d = canvas.getContext('2d') |
||||
|
|
||||
|
// 设置当前宽高 满屏 |
||||
|
const canvasBox = document.querySelector('.canvasBox') |
||||
|
canvasBox.append(this.video) |
||||
|
canvasBox.append(canvas) |
||||
|
canvasBox.style = `width:${this.windowWidth}px;height:${this.windowHeight}px;` |
||||
|
|
||||
|
// 创建第二个canvas |
||||
|
const canvas2 = document.createElement('canvas') |
||||
|
canvas2.id = 'canvas2' |
||||
|
canvas2.width = this.canvasWidth |
||||
|
canvas2.height = this.canvasHeight |
||||
|
canvas2.style = 'position: absolute;top: 50%;left: 50%;z-index: 20;transform: translate(-50%, -50%);' |
||||
|
this.canvas2d2 = canvas2.getContext('2d') |
||||
|
canvasBox.append(canvas2) |
||||
|
|
||||
|
this.video.srcObject = stream |
||||
|
this.video.setAttribute('playsinline', true) |
||||
|
this.video.play() |
||||
|
this.tick() |
||||
|
|
||||
|
this.track = stream.getVideoTracks()[0] |
||||
|
setTimeout(() => { |
||||
|
this.isUseTorch = this.track.getCapabilities().torch || null |
||||
|
}, 500) |
||||
|
}) |
||||
|
.catch(err => { |
||||
|
this.isUse = false |
||||
|
this.$emit('error', err) |
||||
|
}) |
||||
|
}, |
||||
|
|
||||
|
closeCamera() { |
||||
|
this.isParse = false |
||||
|
if (this.video && this.video.srcObject) { |
||||
|
this.video.srcObject.getTracks().forEach(track => { |
||||
|
track.stop() |
||||
|
}) |
||||
|
} |
||||
|
}, |
||||
|
|
||||
|
tick() { |
||||
|
if (!this.isParse) return |
||||
|
if (this.video.readyState === this.video.HAVE_ENOUGH_DATA) { |
||||
|
this.canvas2d.drawImage( |
||||
|
this.video, |
||||
|
this.transtion(this.maskWidth), |
||||
|
this.transtion(this.maskHeight), |
||||
|
this.transtion(200), |
||||
|
this.transtion(200), |
||||
|
0, |
||||
|
0, |
||||
|
this.transtion(this.canvasWidth), |
||||
|
this.transtion(this.canvasHeight) |
||||
|
) |
||||
|
|
||||
|
const imageData = this.canvas2d.getImageData( |
||||
|
0, |
||||
|
0, |
||||
|
this.transtion(this.canvasWidth), |
||||
|
this.transtion(this.canvasHeight) |
||||
|
) |
||||
|
|
||||
|
const code = jsQR(imageData.data, imageData.width, imageData.height, { |
||||
|
inversionAttempts: 'dontInvert' |
||||
|
}) |
||||
|
|
||||
|
this.canvas2d2.clearRect(0, 0, this.canvasWidth, this.canvasHeight) |
||||
|
if (code) { |
||||
|
this.drawLine(code.location.topLeftCorner, code.location.topRightCorner) |
||||
|
this.drawLine(code.location.topRightCorner, code.location.bottomRightCorner) |
||||
|
this.drawLine(code.location.bottomRightCorner, code.location.bottomLeftCorner) |
||||
|
this.drawLine(code.location.bottomLeftCorner, code.location.topLeftCorner) |
||||
|
if (code.data) { |
||||
|
this.getData(code.data) |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
requestAnimationFrame(this.tick) |
||||
|
}, |
||||
|
drawLine(begin, end, color = '#FF3B58') { |
||||
|
this.canvas2d2.beginPath() |
||||
|
this.canvas2d2.moveTo(this.nutranstion(begin.x), this.nutranstion(begin.y)) |
||||
|
this.canvas2d2.lineTo(this.nutranstion(end.x), this.nutranstion(end.y)) |
||||
|
this.canvas2d2.lineWidth = 4 |
||||
|
this.canvas2d2.strokeStyle = color |
||||
|
this.canvas2d2.stroke() |
||||
|
}, |
||||
|
|
||||
|
getData(data) { |
||||
|
this.$emit('success', data) |
||||
|
if (!this.continue) { |
||||
|
this.closeCamera() |
||||
|
} |
||||
|
}, |
||||
|
|
||||
|
openTrack() { |
||||
|
this.trackStatus = !this.trackStatus |
||||
|
this.track.applyConstraints({ |
||||
|
advanced: [{ torch: this.trackStatus }] |
||||
|
}) |
||||
|
}, |
||||
|
|
||||
|
createMsk() { |
||||
|
this.maskWidth = this.windowWidth / 2 - this.canvasWidth / 2 |
||||
|
this.maskHeight = this.windowHeight / 2 - this.canvasHeight / 2 |
||||
|
}, |
||||
|
|
||||
|
transtion(number) { |
||||
|
return this.definition ? number * 2.8 : number * 1.8 |
||||
|
}, |
||||
|
nutranstion(number) { |
||||
|
return this.definition ? number / 2.8 : number / 1.8 |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
</script> |
||||
|
|
||||
|
<style scoped> |
||||
|
page { |
||||
|
background-color: #333333; |
||||
|
} |
||||
|
|
||||
|
.canvasBox { |
||||
|
width: 100vw; |
||||
|
height: 100vh; |
||||
|
position: relative; |
||||
|
|
||||
|
background-image: linear-gradient( |
||||
|
0deg, |
||||
|
transparent 24%, |
||||
|
rgba(32, 255, 77, 0.1) 25%, |
||||
|
rgba(32, 255, 77, 0.1) 26%, |
||||
|
transparent 27%, |
||||
|
transparent 74%, |
||||
|
rgba(32, 255, 77, 0.1) 75%, |
||||
|
rgba(32, 255, 77, 0.1) 76%, |
||||
|
transparent 77%, |
||||
|
transparent |
||||
|
), |
||||
|
linear-gradient( |
||||
|
90deg, |
||||
|
transparent 24%, |
||||
|
rgba(32, 255, 77, 0.1) 25%, |
||||
|
rgba(32, 255, 77, 0.1) 26%, |
||||
|
transparent 27%, |
||||
|
transparent 74%, |
||||
|
rgba(32, 255, 77, 0.1) 75%, |
||||
|
rgba(32, 255, 77, 0.1) 76%, |
||||
|
transparent 77%, |
||||
|
transparent |
||||
|
); |
||||
|
background-size: 3rem 3rem; |
||||
|
background-position: -1rem -1rem; |
||||
|
z-index: 10; |
||||
|
background-color: #1110; |
||||
|
} |
||||
|
|
||||
|
.box { |
||||
|
width: 200px; |
||||
|
height: 200px; |
||||
|
position: absolute; |
||||
|
left: 50%; |
||||
|
top: 50%; |
||||
|
transform: translate(-50%, -50%); |
||||
|
overflow: hidden; |
||||
|
border: 0.1rem solid rgba(0, 255, 51, 0.2); |
||||
|
z-index: 11; |
||||
|
} |
||||
|
|
||||
|
.line { |
||||
|
height: calc(100% - 2px); |
||||
|
width: 100%; |
||||
|
background: linear-gradient(180deg, rgba(0, 255, 51, 0) 43%, #00ff33 211%); |
||||
|
border-bottom: 3px solid #00ff33; |
||||
|
transform: translateY(-100%); |
||||
|
animation: radar-beam 2s infinite alternate; |
||||
|
animation-timing-function: cubic-bezier(0.53, 0, 0.43, 0.99); |
||||
|
animation-delay: 1.4s; |
||||
|
} |
||||
|
|
||||
|
.box:after, |
||||
|
.box:before, |
||||
|
.angle:after, |
||||
|
.angle:before { |
||||
|
content: ''; |
||||
|
display: block; |
||||
|
position: absolute; |
||||
|
width: 3vw; |
||||
|
height: 3vw; |
||||
|
z-index: 12; |
||||
|
border: 0.2rem solid transparent; |
||||
|
} |
||||
|
|
||||
|
.box:after, |
||||
|
.box:before { |
||||
|
top: 0; |
||||
|
border-top-color: #00ff33; |
||||
|
} |
||||
|
|
||||
|
.angle:after, |
||||
|
.angle:before { |
||||
|
bottom: 0; |
||||
|
border-bottom-color: #00ff33; |
||||
|
} |
||||
|
|
||||
|
.box:before, |
||||
|
.angle:before { |
||||
|
left: 0; |
||||
|
border-left-color: #00ff33; |
||||
|
} |
||||
|
|
||||
|
.box:after, |
||||
|
.angle:after { |
||||
|
right: 0; |
||||
|
border-right-color: #00ff33; |
||||
|
} |
||||
|
|
||||
|
@keyframes radar-beam { |
||||
|
0% { |
||||
|
transform: translateY(-100%); |
||||
|
} |
||||
|
|
||||
|
100% { |
||||
|
transform: translateY(0); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
.msg { |
||||
|
text-align: center; |
||||
|
padding: 20rpx 0; |
||||
|
} |
||||
|
|
||||
|
.box2 { |
||||
|
width: 300px; |
||||
|
height: 200px; |
||||
|
position: absolute; |
||||
|
left: 50%; |
||||
|
top: 50%; |
||||
|
transform: translate(-50%, -50%); |
||||
|
z-index: 20; |
||||
|
} |
||||
|
|
||||
|
.track { |
||||
|
position: absolute; |
||||
|
bottom: -100px; |
||||
|
left: 50%; |
||||
|
transform: translateX(-50%); |
||||
|
z-index: 20; |
||||
|
color: #fff; |
||||
|
display: flex; |
||||
|
flex-direction: column; |
||||
|
align-items: center; |
||||
|
} |
||||
|
|
||||
|
.mask { |
||||
|
position: absolute; |
||||
|
z-index: 10; |
||||
|
background-color: rgba(0, 0, 0, 0.55); |
||||
|
} |
||||
|
|
||||
|
.mask1 { |
||||
|
top: 0; |
||||
|
left: 0; |
||||
|
right: 0; |
||||
|
} |
||||
|
|
||||
|
.mask2 { |
||||
|
right: 0; |
||||
|
} |
||||
|
|
||||
|
.mask3 { |
||||
|
right: 0; |
||||
|
left: 0; |
||||
|
bottom: 0; |
||||
|
} |
||||
|
|
||||
|
.mask4 { |
||||
|
left: 0; |
||||
|
} |
||||
|
|
||||
|
.error { |
||||
|
color: #fff; |
||||
|
padding: 40rpx; |
||||
|
font-size: 24rpx; |
||||
|
background-color: #333333; |
||||
|
position: fixed; |
||||
|
top: 50%; |
||||
|
left: 50%; |
||||
|
transform: translate(-50%, -50%); |
||||
|
width: 550rpx; |
||||
|
border-radius: 20rpx; |
||||
|
} |
||||
|
|
||||
|
.error .on1 { |
||||
|
font-size: 30rpx; |
||||
|
} |
||||
|
</style> |
||||
@ -0,0 +1,130 @@ |
|||||
|
<template> |
||||
|
<view |
||||
|
@touchstart="touchstart" |
||||
|
@touchmove="touchmove" |
||||
|
@touchend="touchend"> |
||||
|
<slot></slot> |
||||
|
</view> |
||||
|
</template> |
||||
|
<script> |
||||
|
export default{ |
||||
|
name : "im-touch", |
||||
|
props : { |
||||
|
datas:{type:Array, default:function(){return [];}} |
||||
|
}, |
||||
|
data() { |
||||
|
return { |
||||
|
toucheTimer : 0, |
||||
|
fingerRes : [], |
||||
|
distance : 0, |
||||
|
taptimer : 100 |
||||
|
} |
||||
|
}, |
||||
|
methods:{ |
||||
|
toInt : function(arr){ |
||||
|
var res = []; |
||||
|
arr.forEach((item)=>{ |
||||
|
item.pageX = parseInt(item.pageX); |
||||
|
item.pageY = parseInt(item.pageY); |
||||
|
res.push(item); |
||||
|
}); |
||||
|
return res; |
||||
|
}, |
||||
|
touchstart : function(e){ |
||||
|
this.toucheTimer = new Date().getTime(); |
||||
|
this.fingerRes = this.toInt(e.changedTouches); |
||||
|
if(this.fingerRes.length > 2){return ;} |
||||
|
var moves = [], i = 0; |
||||
|
this.fingerRes.forEach((finger)=>{ |
||||
|
var xTouch = finger.pageX; |
||||
|
var yTouch = finger.pageY; |
||||
|
moves.push([xTouch, yTouch]); |
||||
|
i++; |
||||
|
}); |
||||
|
this.$emit('thStart', moves, this.datas); |
||||
|
}, |
||||
|
touchmove : function(e){ |
||||
|
if(this.toucheTimer < 50){return ;} |
||||
|
var timer = new Date().getTime() - this.toucheTimer; |
||||
|
if(timer < this.taptimer){return ;} |
||||
|
var touches = this.toInt(e.changedTouches); |
||||
|
if(touches.length > 2){return ;} |
||||
|
if(touches.length == 1){ |
||||
|
var i = 0, moves = []; |
||||
|
touches.forEach((finger)=>{ |
||||
|
var xTouch = finger.pageX - this.fingerRes[i].pageX; |
||||
|
var yTouch = finger.pageY - this.fingerRes[i].pageY; |
||||
|
moves.push([xTouch, yTouch]); |
||||
|
i++; |
||||
|
}); |
||||
|
this.$emit('thMove', moves, this.datas); |
||||
|
} |
||||
|
else if(touches.length == 2){ |
||||
|
if(this.distance == 0){ |
||||
|
this.distance = parseInt(this.getDistance(touches[0].pageX,touches[0].pageY, touches[1].pageX, touches[1].pageY)); |
||||
|
}else{ |
||||
|
var distance1 = parseInt(this.getDistance(touches[0].pageX,touches[0].pageY, touches[1].pageX, touches[1].pageY)); |
||||
|
var scale = distance1 / this.distance; |
||||
|
scale = Math.floor(scale * 100) / 100; |
||||
|
this.$emit('scale', scale, this.datas); |
||||
|
} |
||||
|
} |
||||
|
}, |
||||
|
touchend : function (e){ |
||||
|
var timer = new Date().getTime() - this.toucheTimer; |
||||
|
if(timer < this.taptimer){ |
||||
|
this.$emit('tapme'); |
||||
|
return ; |
||||
|
} |
||||
|
var touches = this.toInt(e.changedTouches); |
||||
|
this.distance = 0; |
||||
|
if(touches.length == 1){ |
||||
|
var i = 0, moves = []; |
||||
|
touches.forEach((finger)=>{ |
||||
|
var xTouch = finger.pageX - this.fingerRes[i].pageX; |
||||
|
var yTouch = finger.pageY - this.fingerRes[i].pageY; |
||||
|
moves.push([xTouch, yTouch]); |
||||
|
i++; |
||||
|
}); |
||||
|
moves.push(timer); |
||||
|
this.$emit('thEnd', moves, this.datas); |
||||
|
// 根据时间及距离决定滑动时间 |
||||
|
if(timer < 300){ |
||||
|
var mx = Math.abs(moves[0][0]); |
||||
|
var my = Math.abs(moves[0][1]); |
||||
|
if(mx > my){ |
||||
|
if(mx >= 50){ |
||||
|
if(moves[0][0] > 0){ |
||||
|
this.$emit('swipe', 'right', this.datas); |
||||
|
}else{ |
||||
|
this.$emit('swipe', 'left', this.datas); |
||||
|
} |
||||
|
} |
||||
|
}else{ |
||||
|
if(my >= 50){ |
||||
|
if(moves[0][1] > 0){ |
||||
|
this.$emit('swipe', 'down', this.datas); |
||||
|
}else{ |
||||
|
this.$emit('swipe', 'up', this.datas); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
}, |
||||
|
getDistance : function (lat1, lng1, lat2, lng2){ |
||||
|
var radLat1 = lat1*Math.PI / 180.0; |
||||
|
var radLat2 = lat2*Math.PI / 180.0; |
||||
|
var a = radLat1 - radLat2; |
||||
|
var b = lng1*Math.PI / 180.0 - lng2*Math.PI / 180.0; |
||||
|
var s = 2 * Math.asin(Math.sqrt(Math.pow(Math.sin(a/2),2) + Math.cos(radLat1)*Math.cos(radLat2)*Math.pow(Math.sin(b/2),2))); |
||||
|
s = s * 6378.137; |
||||
|
return Math.round(s * 10000) / 10000; |
||||
|
}, |
||||
|
tapme : function(){ |
||||
|
this.isTap = true; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
</script> |
||||
|
<style scoped></style> |
||||
@ -0,0 +1,81 @@ |
|||||
|
<template> |
||||
|
<view> |
||||
|
<!-- #ifdef APP-PLUS --> |
||||
|
<view v-for="(items,indexs) in imglist" :key="indexs" v-if="network_log=='none'"> |
||||
|
<view class="cu-avatar lg" v-if="info.imgname === items.name" :class="appSetting.circleAvatar?'round':'radius'" @tap="openUserInfo(info)" :style="[{backgroundImage:'url('+ items.path +')'}]"></view> |
||||
|
</view> |
||||
|
<view v-else class="cu-avatar lg" :class="appSetting.circleAvatar?'round':'radius'" @tap="openUserInfo(info)" :style="[{backgroundImage:'url('+ info.avatar +')'}]"></view> |
||||
|
<!-- #endif --> |
||||
|
|
||||
|
<!-- #ifdef H5 --> |
||||
|
<view class="cu-avatar lg" :class="appSetting.circleAvatar?'round':'radius'" @tap="openUserInfo(info)" :style="[{backgroundImage:'url('+ info.avatar +')'}]"></view> |
||||
|
<!-- #endif --> |
||||
|
</view> |
||||
|
</template> |
||||
|
<script> |
||||
|
const userInfo=uni.getStorageSync('userInfo'); |
||||
|
const appSetting=uni.getStorageSync('appSetting'); |
||||
|
import { useMsgStore } from '@/store/message'; |
||||
|
// #ifdef APP-PLUS |
||||
|
import {getSavedImages} from '@/utils/LocalFileSystemURL.js' |
||||
|
// #endif |
||||
|
import { storeToRefs } from 'pinia'; |
||||
|
import pinia from '@/store/index' |
||||
|
const msgStore = useMsgStore(pinia) |
||||
|
const {network_log} = storeToRefs(msgStore); |
||||
|
export default{ |
||||
|
name : "im-touch", |
||||
|
props : { |
||||
|
info:{type:Object, default:function(){return {};}}, |
||||
|
circleAvatar:{type:Boolean, default:false}, |
||||
|
profile:{type:Boolean, default:false}, |
||||
|
}, |
||||
|
data() { |
||||
|
return { |
||||
|
toucheTimer : 0, |
||||
|
fingerRes : [], |
||||
|
distance : 0, |
||||
|
taptimer : 100, |
||||
|
appSetting:appSetting, |
||||
|
imglist:[], |
||||
|
network_log:network_log |
||||
|
} |
||||
|
}, |
||||
|
created() { |
||||
|
// #ifdef APP-PLUS |
||||
|
this.getImagePath(); |
||||
|
// #endif |
||||
|
// console.info(this.info,'读取地址'); |
||||
|
}, |
||||
|
methods:{ |
||||
|
async getImagePath(){ |
||||
|
this.imglist = await getSavedImages() |
||||
|
this.imglist.map(item => { |
||||
|
item.path = plus.io.convertLocalFileSystemURL(item.path) |
||||
|
}); |
||||
|
// console.info(this.imglist,'读取地址'); |
||||
|
}, |
||||
|
// 打开用户详情 |
||||
|
openUserInfo(item){ |
||||
|
let friend=msgStore.getContact(item.user_id); |
||||
|
// if(!this.profile && !friend){ |
||||
|
// uni.showToast({ |
||||
|
// title:'已开启用户隐私!', |
||||
|
// icon:'none' |
||||
|
// }) |
||||
|
// return false; |
||||
|
// } |
||||
|
// if(item.id==userInfo.user_id) return; |
||||
|
|
||||
|
// uni.redirectTo({ |
||||
|
// url:"/pages/contacts/detail?id="+this.info.id |
||||
|
// }) |
||||
|
|
||||
|
uni.navigateTo({ |
||||
|
url:"/pages/contacts/detail?id="+this.info.id |
||||
|
}) |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
</script> |
||||
|
<style scoped></style> |
||||
@ -0,0 +1,43 @@ |
|||||
|
<template name="im-image"> |
||||
|
<template v-if="src && info && info.fixMode"> |
||||
|
<image v-if="info.fixMode<=2" :src="src" class="radius" :mode="info.fixMode==1 ? 'widthFix' : 'heightFix' " :style="info.fixMode==1 ? 'width:200px' : 'height:240px'" @tap="showImgs" :data-img="src" ></image> |
||||
|
<image v-if="info.fixMode==3" :src="src" class="radius" mode="scaleToFill" :style="[{width:info.width+'px',height:info.height+'px'}]" @tap="showImgs" :data-img="src" ></image> |
||||
|
</template> |
||||
|
<template v-else-if="!src && info && info.width"> |
||||
|
<image :src="src" class="radius" mode="scaleToFill" :style="$util.imageCoverStyle(info.width,info.height)" @tap="showImgs" :data-img="src" ></image> |
||||
|
</template> |
||||
|
<template v-else> |
||||
|
<image :src="src" style="width:200px" class="radius" mode="widthFix" @tap="showImgs" :data-img="src" ></image> |
||||
|
</template> |
||||
|
</template> |
||||
|
<script> |
||||
|
export default { |
||||
|
name : "im-image", |
||||
|
props : { |
||||
|
info:{type:Object, default:function(){return {};}}, |
||||
|
src:{type:String, default:''}, |
||||
|
isview:{type:Object, default:function(){return {};}}, |
||||
|
}, |
||||
|
emits:['showImgs','viewImgs'], |
||||
|
data() { |
||||
|
return { |
||||
|
} |
||||
|
}, |
||||
|
created : function(){ |
||||
|
}, |
||||
|
methods:{ |
||||
|
showImgs(e){ |
||||
|
if(this.isview.is_view==0){ |
||||
|
this.$api.msgApi.viewOriginalImage({file_id:this.isview.file_id,msg_id:this.isview.msg_id}).then(res => { |
||||
|
// console.log(res); |
||||
|
const valobj = {img:res.data.src,is_view:this.isview.is_view} |
||||
|
this.$emit('showImgs',valobj); |
||||
|
this.$emit('viewImgs',{src:res.data.src,msg_id:this.isview.msg_id}); |
||||
|
}) |
||||
|
}else{ |
||||
|
this.$emit('showImgs',e); |
||||
|
} |
||||
|
}, |
||||
|
} |
||||
|
} |
||||
|
</script> |
||||
@ -0,0 +1,35 @@ |
|||||
|
<template name="im-item"> |
||||
|
<view v-if="item.type=='diy'" :item="item" :index="index" :isSelf="isSelf"> |
||||
|
自定义 |
||||
|
</view> |
||||
|
<view v-else> |
||||
|
暂不支持该消息类型 |
||||
|
</view> |
||||
|
</template> |
||||
|
<script> |
||||
|
// import imVoice from '@/components/message/im-card-voice.vue'; |
||||
|
export default { |
||||
|
|
||||
|
name : "im-item", |
||||
|
components: { |
||||
|
// imVoice |
||||
|
}, |
||||
|
props : { |
||||
|
item:{type:Object, default:function(){return {};}}, |
||||
|
index:{type:Number, default:0}, |
||||
|
isSelf:{type:Boolean, default:false}, |
||||
|
}, |
||||
|
data() { |
||||
|
return { |
||||
|
} |
||||
|
}, |
||||
|
created : function(){ |
||||
|
}, |
||||
|
methods:{ |
||||
|
|
||||
|
} |
||||
|
} |
||||
|
</script> |
||||
|
<style scoped> |
||||
|
|
||||
|
</style> |
||||
@ -0,0 +1,50 @@ |
|||||
|
<template name="im-tab"> |
||||
|
<view class="tab-main im-flex im-justify-content-start im-align-items-center bg-gray" :style="{height:height+'rpx',borderRadius:height/2+'rpx'}"> |
||||
|
<view class="tab-item" :class="active==index ? 'active' : ''" v-for="(item,index) in values" @click="changeItem(item,index)" :style="{height:itemHeight+'rpx',borderRadius:itemHeight/2+'rpx',lineHeight:itemHeight-8+'rpx'}" :key="index"> |
||||
|
{{item.name}} <text v-if="item.count>0">{{item.count>99 ? '99+' : item.count}}</text> |
||||
|
</view> |
||||
|
</view> |
||||
|
</template> |
||||
|
<script> |
||||
|
export default { |
||||
|
|
||||
|
name : "im-tab", |
||||
|
components: { |
||||
|
}, |
||||
|
props : { |
||||
|
values:{type:Array, default:function(){return [];}}, |
||||
|
height:{type:Number,default:72} |
||||
|
}, |
||||
|
data() { |
||||
|
return { |
||||
|
active:0, |
||||
|
itemHeight:48 |
||||
|
} |
||||
|
}, |
||||
|
created : function(){ |
||||
|
this.itemHeight=this.height-16; |
||||
|
}, |
||||
|
methods:{ |
||||
|
changeItem(item,index){ |
||||
|
this.active=index; |
||||
|
this.$emit('change',item,index) |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
</script> |
||||
|
<style lang="scss" scoped> |
||||
|
.tab-main{ |
||||
|
padding:10rpx; |
||||
|
.tab-item{ |
||||
|
padding: 6rpx 12rpx; |
||||
|
min-width:150rpx; |
||||
|
text-align: center; |
||||
|
vertical-align: middle; |
||||
|
&.active{ |
||||
|
box-shadow: 0 2px 4px rgba(0, 0, 0, .12), 0 0 6px rgba(0, 0, 0, .04); |
||||
|
background-color: #fff; |
||||
|
color:#18bc37; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
</style> |
||||
@ -0,0 +1,301 @@ |
|||||
|
<template> |
||||
|
<view> |
||||
|
<view class="cu-bar bg-white search"> |
||||
|
<view class="search-form round"> |
||||
|
<text class="cuIcon-search"></text> |
||||
|
<input type="text" v-model="keywords" placeholder="搜索联系人" confirm-type="search"/> |
||||
|
</view> |
||||
|
</view> |
||||
|
|
||||
|
<view class="gui-padding" style="padding-bottom: 120rpx;" v-if="!keywords"> |
||||
|
<view class="cu-list menu"> |
||||
|
<view class="cu-item arrow" v-if="isAuth" @tap="atAll"> |
||||
|
<view class='cu-avatar mr-15 group-bg sm'> |
||||
|
</view> |
||||
|
<view class="content"> |
||||
|
<text class="c-333">所有人</text> |
||||
|
</view> |
||||
|
</view> |
||||
|
</view> |
||||
|
<uni-data-checkbox :multiple="multiple" class="user-list-select" @change="chooseUser" v-model="changeUser" :localdata="lists"></uni-data-checkbox> |
||||
|
<Empty v-if="!lists.length" noDatatext="无联系人" textcolor="#999" ></Empty> |
||||
|
</view> |
||||
|
<view class="gui-padding" style="padding-bottom: 120rpx;" v-if="keywords"> |
||||
|
<uni-data-checkbox :multiple="multiple" class="user-list-select" @change="chooseSearchUser" v-model="searcheUser" :localdata="searchList"></uni-data-checkbox> |
||||
|
<Empty v-if="!searchList.length" noDatatext="未搜索到数据" textcolor="#999" ></Empty> |
||||
|
</view> |
||||
|
<view class="cu-bar bg-white tabbar border shop footer-opt"> |
||||
|
<scroll-view class="scroll-view_H" scroll-x="true" :scroll-anchoring="true" :scroll-left="scrollLeft"> |
||||
|
<view class="user-list-avatar"> |
||||
|
<template v-for="(item,index) in selectUser" :key="index"> |
||||
|
<image class="user-avatar" :src="item.avatar" @tap="removeUser(item.id,item.user_id)"></image> |
||||
|
</template> |
||||
|
</view> |
||||
|
<view class="select-num pd-10">已选{{selectUser.length}}人</view> |
||||
|
</scroll-view> |
||||
|
</view> |
||||
|
</view> |
||||
|
</template> |
||||
|
|
||||
|
<script> |
||||
|
import { storeToRefs } from 'pinia'; |
||||
|
import pinia from '@/store/index' |
||||
|
import { useloginStore } from '@/store/login'; |
||||
|
const userStore = useloginStore(pinia) |
||||
|
export default { |
||||
|
name : "user-select", |
||||
|
props : { |
||||
|
type:{type:Number, default:0},//1创建群聊,2邀请群成员,3:转发,4:提及某人,5:选择联系人 |
||||
|
contact_id:{type:String, default:''}, |
||||
|
multiple:{type:Boolean, default:true}, |
||||
|
user_ids:{type:Object, default:{}} |
||||
|
}, |
||||
|
data() { |
||||
|
return { |
||||
|
lists: [], |
||||
|
keywords:'', |
||||
|
searchList:[], |
||||
|
selectUser:[], |
||||
|
userList: [], |
||||
|
changeUser: [], //选中的数据 |
||||
|
scrollLeft:300, |
||||
|
contacList:[], |
||||
|
searcheUser:[], //搜索选中人员 |
||||
|
isAuth:false, |
||||
|
userInfo:userStore.userInfo, |
||||
|
} |
||||
|
}, |
||||
|
watch: { |
||||
|
keywords(val){ |
||||
|
this.search(); |
||||
|
// 如果关键词为空,搜索选中的人员为已经选中的人员 |
||||
|
if(val!=''){ |
||||
|
this.searcheUser=this.changeUser; |
||||
|
} |
||||
|
}, |
||||
|
searcheUser(val){ |
||||
|
const arr=this.changeUser.concat(val); |
||||
|
this.changeUser=[...new Set(arr)]; |
||||
|
} |
||||
|
}, |
||||
|
mounted() { |
||||
|
if (this.type == 4) { |
||||
|
this.getGroupUser() |
||||
|
} else if(this.type == 5){ |
||||
|
this.getAllUser() |
||||
|
this.changeUser = this.user_ids |
||||
|
this.selectUser = uni.getStorageSync('selectUser') |
||||
|
} else if(this.type == 6){ |
||||
|
this.getAllUser() |
||||
|
this.changeUser = this.user_ids |
||||
|
this.selectUser = uni.getStorageSync('selectUser1') |
||||
|
}else { |
||||
|
this.getAllUser() |
||||
|
} |
||||
|
let lists=JSON.parse(JSON.stringify(this.userList)); |
||||
|
this.lists=lists; |
||||
|
|
||||
|
}, |
||||
|
methods: { |
||||
|
getAllUser() { |
||||
|
const allContact=uni.getStorageSync('allContacts'); |
||||
|
let contact=[]; |
||||
|
if(this.type==3){ |
||||
|
contact = allContact.filter((item)=>{ |
||||
|
return item.id!=this.contact_id; |
||||
|
}) |
||||
|
contact.forEach(item => { |
||||
|
item.disable = false; |
||||
|
let name=item.displayName; |
||||
|
if(item.is_group==1){ |
||||
|
name+="(群聊)"; |
||||
|
} |
||||
|
item.text=name; |
||||
|
item.value=item.id; |
||||
|
}) |
||||
|
}else{ |
||||
|
contact = allContact.filter((item)=>{ |
||||
|
return item.is_group==0; |
||||
|
}) |
||||
|
this.contacList=JSON.parse(JSON.stringify(contact)); |
||||
|
contact.forEach(item => { |
||||
|
item.disable = false; |
||||
|
item.text=item.displayName; |
||||
|
item.value=item.id; |
||||
|
if (this.user_ids.length && this.type==2) { |
||||
|
if (this.user_ids.includes(item.id)) { |
||||
|
item.disable = true |
||||
|
} |
||||
|
} |
||||
|
}) |
||||
|
if(this.type==1 && this.contact_id){ |
||||
|
this.changeUser.push(parseInt(this.contact_id)); |
||||
|
this.selectUser=contact.filter((item)=>{ |
||||
|
return item.id==this.contact_id; |
||||
|
}) |
||||
|
} |
||||
|
} |
||||
|
this.userList = contact |
||||
|
this.lists = contact |
||||
|
}, |
||||
|
getGroupUser(){ |
||||
|
this.userList = [] |
||||
|
this.$api.msgApi.groupUserList({ |
||||
|
group_id: this.contact_id |
||||
|
}).then(res => { |
||||
|
const isAuth=res.data.filter(item => (item.role == 1 || item.role == 2) && item.userInfo.id== this.userInfo.user_id) |
||||
|
if(isAuth.length) this.isAuth=true; |
||||
|
const allUser=res.data; |
||||
|
allUser.forEach((item,index)=>{ |
||||
|
item.realname=item.userInfo.displayName; |
||||
|
item.displayName=item.userInfo.displayName; |
||||
|
item.avatar=item.userInfo.avatar; |
||||
|
item.name_py=item.userInfo.name_py; |
||||
|
item.disable = false; |
||||
|
item.text=item.userInfo.displayName; |
||||
|
item.value=item.userInfo.id; |
||||
|
}) |
||||
|
const userList=res.data.filter(item => item.userInfo.id != this.userInfo.user_id) |
||||
|
this.lists=userList; |
||||
|
console.log(this.lists); |
||||
|
this.userList = userList; |
||||
|
}) |
||||
|
}, |
||||
|
// 选择人员 |
||||
|
chooseUser(e){ |
||||
|
if(this.multiple){ |
||||
|
this.selectUser=e.detail.data; |
||||
|
}else{ |
||||
|
this.selectUser=[e.detail.data]; |
||||
|
} |
||||
|
setTimeout(()=>{ |
||||
|
this.scrollLeft+=300; |
||||
|
},50) |
||||
|
}, |
||||
|
// 提醒所有人 |
||||
|
atAll(){ |
||||
|
this.$emit('setData',{realname:'所有人',user_id:0}); |
||||
|
}, |
||||
|
// 选择搜索列表的成员 |
||||
|
chooseSearchUser(e){ |
||||
|
if(this.multiple){ |
||||
|
this.selectUser=this.$util.mergeArrays(e.detail.data,this.selectUser); |
||||
|
}else{ |
||||
|
this.selectUser=[e.detail.data]; |
||||
|
} |
||||
|
setTimeout(()=>{ |
||||
|
this.scrollLeft+=300; |
||||
|
},50) |
||||
|
}, |
||||
|
// 移除成员 |
||||
|
removeUser(id,user_id){ |
||||
|
this.selectUser=this.selectUser.filter((e)=>{ |
||||
|
return e.id!=id; |
||||
|
}) |
||||
|
this.changeUser=this.changeUser.filter((e)=>{ |
||||
|
return e!=user_id; |
||||
|
}) |
||||
|
}, |
||||
|
// 搜索成员 |
||||
|
search: function(e) { |
||||
|
let contact=JSON.parse(JSON.stringify(this.lists)); |
||||
|
this.searchList=this.$util.searchObject(contact,['displayName','name_py'],this.keywords); |
||||
|
}, |
||||
|
// 监听提交 |
||||
|
confirm: function(e) { |
||||
|
let arr = [] |
||||
|
if (e) { //这个值为输入框输入的值 |
||||
|
var brr = this.userList.filter(value => { |
||||
|
//遍历数组,返回值为true保留并复制到新数组,false则过滤掉 |
||||
|
let data = value.realname ? value.realname : value.userInfo.displayName |
||||
|
if (data.includes(e.trim())) { |
||||
|
arr.push(value) |
||||
|
} |
||||
|
return data.includes(e.trim()); |
||||
|
}); |
||||
|
this.lists = arr |
||||
|
} |
||||
|
|
||||
|
}, |
||||
|
} |
||||
|
} |
||||
|
</script> |
||||
|
|
||||
|
<style lang="scss"> |
||||
|
.group-bg{ |
||||
|
background-image: url(@/static/image/group.png); |
||||
|
} |
||||
|
.search-warp { |
||||
|
width: 750rpx; |
||||
|
padding: 15rpx 50rpx; |
||||
|
} |
||||
|
::v-deep .checklist-group{ |
||||
|
display: grid !important; |
||||
|
.checklist-box{ |
||||
|
padding:20rpx; |
||||
|
.checkbox__inner{ |
||||
|
width:40rpx !important; |
||||
|
height:40rpx !important; |
||||
|
overflow:hidden; |
||||
|
.checkbox__inner-icon{ |
||||
|
position: absolute; |
||||
|
top: -8px !important; |
||||
|
left: -4px !important; |
||||
|
height: 20px !important; |
||||
|
width: 20px !important; |
||||
|
border-right-width: 2px !important; |
||||
|
border-bottom-width: 2px !important; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
|
||||
|
.checklist-content{ |
||||
|
margin-left:20rpx; |
||||
|
.checklist-text{ |
||||
|
font-size:36rpx !important; |
||||
|
} |
||||
|
|
||||
|
} |
||||
|
} |
||||
|
.is-checked{ |
||||
|
.radio__inner{ |
||||
|
border-color:#18bc37 !important; |
||||
|
.radio__inner-icon{ |
||||
|
background-color: #18bc37 !important; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
} |
||||
|
} |
||||
|
.footer-opt{ |
||||
|
position: fixed; |
||||
|
bottom:0; |
||||
|
left:0; |
||||
|
width:100%; |
||||
|
} |
||||
|
|
||||
|
.scroll-view_H { |
||||
|
white-space: nowrap; |
||||
|
width: 100%; |
||||
|
} |
||||
|
|
||||
|
.user-list-avatar{ |
||||
|
float: left; |
||||
|
margin-top:10rpx; |
||||
|
.user-avatar{ |
||||
|
width:70rpx; |
||||
|
height:70rpx; |
||||
|
flex: 0 0 auto; |
||||
|
border-radius: 8rpx; |
||||
|
margin-left: 15rpx; |
||||
|
display: inline-block; |
||||
|
&:last-child{ |
||||
|
margin-right: 15rpx; |
||||
|
} |
||||
|
} |
||||
|
.select-num{ |
||||
|
padding:10rpx; |
||||
|
} |
||||
|
|
||||
|
} |
||||
|
</style> |
||||
@ -0,0 +1,461 @@ |
|||||
|
<!-- mosowe-canvas-image --> |
||||
|
<template> |
||||
|
<view class='mosowe-canvas-image'> |
||||
|
<view class="slot-view" @click="createCanvas"> |
||||
|
<slot></slot> |
||||
|
</view> |
||||
|
<view class="canvas-wrap-box"> |
||||
|
|
||||
|
<!-- 主面板绘制 --> |
||||
|
<canvas class="canvas-wrap" canvas-id="canvas" :style="'width: '+ width +'px; height: '+ height +'px;'"></canvas> |
||||
|
<!-- 这个是用来绘制圆形图片的 --> |
||||
|
<canvas class="canvas-wrap" canvas-id="canvas-arc" :style="'width: '+ canvasArcWidth +'px; height: '+ canvasArcHeight +'px;'"></canvas> |
||||
|
</view> |
||||
|
</view> |
||||
|
</template> |
||||
|
|
||||
|
<script> |
||||
|
import QR from './wxqrcode.js'; |
||||
|
export default { |
||||
|
name: 'mosowe-canvas-image', |
||||
|
components: {}, |
||||
|
props: { |
||||
|
imgType: { // 图片类型 |
||||
|
type: String, |
||||
|
default: 'jpg', |
||||
|
validator: () => { |
||||
|
return ['jpg', 'png']; |
||||
|
} |
||||
|
}, |
||||
|
compress: { // 是否开启压缩 |
||||
|
type: Boolean, |
||||
|
default: false |
||||
|
}, |
||||
|
compressSize: { // 压缩界限,超过界限压缩,默认2M |
||||
|
type: [Number, String], |
||||
|
default: 1024*1024*2 |
||||
|
}, |
||||
|
showPreview: { // 生成图像后是否预览 |
||||
|
type: Boolean, |
||||
|
default: false |
||||
|
}, |
||||
|
height: { // canvas高度 |
||||
|
type: [String, Number], |
||||
|
default: 200 |
||||
|
}, |
||||
|
width: { // canvas宽度 |
||||
|
type: [String, Number], |
||||
|
default: 200 |
||||
|
}, |
||||
|
lists: { |
||||
|
type: Array, |
||||
|
default: () => { |
||||
|
return []; |
||||
|
} |
||||
|
} |
||||
|
}, |
||||
|
data () { |
||||
|
return { |
||||
|
canvas: null, |
||||
|
listsIndex: 0, |
||||
|
listsLength: 0, |
||||
|
canvasArc: null, |
||||
|
canvasArcWidth: 100, |
||||
|
canvasArcHeight: 100, |
||||
|
compressQuality: 20, |
||||
|
compressQualityH5: 5, |
||||
|
}; |
||||
|
}, |
||||
|
watch: {}, |
||||
|
// 组件实例化之前 |
||||
|
beforeCreate () {}, |
||||
|
// 组件创建完成 |
||||
|
created () { |
||||
|
this.canvas = uni.createCanvasContext('canvas', this); |
||||
|
this.canvasArc = uni.createCanvasContext('canvas-arc', this); |
||||
|
}, |
||||
|
// 组件挂载之前 |
||||
|
beforeMount () {}, |
||||
|
// 组件挂载之后 |
||||
|
mounted () {}, |
||||
|
// 组件数据更新时 |
||||
|
beforeUpdate () {}, |
||||
|
// 组价更新 |
||||
|
updated () {}, |
||||
|
// 组件销毁前 |
||||
|
beforeDestroy () {}, |
||||
|
// 组件销毁后 |
||||
|
destroyed () {}, |
||||
|
// 页面方法 |
||||
|
methods: { |
||||
|
// 开始绘制 |
||||
|
createCanvas () { |
||||
|
this.clearCanvas(); |
||||
|
if (this.lists.length === 0) { |
||||
|
uni.showToast({ |
||||
|
title: 'lists不能为空', |
||||
|
icon: 'none' |
||||
|
}); |
||||
|
return; |
||||
|
} |
||||
|
this.listsIndex = 0; |
||||
|
this.listsLength = this.lists.length - 1; |
||||
|
uni.showLoading({ |
||||
|
title: '正在生成图片...', |
||||
|
mask: true |
||||
|
}); |
||||
|
setTimeout(()=>{ |
||||
|
uni.hideLoading(); |
||||
|
},10000); |
||||
|
this.dataDrawCanvas(); |
||||
|
}, |
||||
|
// 数据绘制 |
||||
|
dataDrawCanvas () { |
||||
|
let item = this.lists[this.listsIndex]; |
||||
|
if (item.type === 'image') { // 图片 |
||||
|
if (item.content.indexOf('https://') > -1||item.content.indexOf('http://') > -1) { // https://网络图片 |
||||
|
// #ifndef H5 |
||||
|
// 非H5 |
||||
|
this.downloadImageNotH5(item); |
||||
|
// #endif |
||||
|
// #ifdef H5 |
||||
|
// H5 |
||||
|
this.downloadImageH5(item); |
||||
|
// #endif |
||||
|
} else { // 本地选择图片 |
||||
|
if (this.compress && item.hasOwnProperty('file') && item.file.size > this.compressSize) { // 大于限制2M压缩 |
||||
|
this.compressImage(item); |
||||
|
} else { |
||||
|
if (item.arc) { |
||||
|
this.drawImageArc(item); |
||||
|
} else { |
||||
|
this.drawImage(item); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
} else if (item.type === 'text') { // 文本 |
||||
|
this.drawText(item); |
||||
|
} else if (item.type === 'rect') { // 矩形(线条) |
||||
|
this.drawRect(item); |
||||
|
} else if (item.type === 'arc') { // 圆形 |
||||
|
this.drawArc(item); |
||||
|
} else if (item.type === 'qr') { // 二维码 |
||||
|
this.drawQR(item); |
||||
|
} |
||||
|
}, |
||||
|
// #ifndef H5 |
||||
|
// https图片下载本地并绘制,非H5 |
||||
|
downloadImageNotH5 (item) { |
||||
|
uni.downloadFile({ |
||||
|
url: item.content, |
||||
|
header: { |
||||
|
'Access-Control-Allow-Origin': '*', |
||||
|
}, |
||||
|
success: (res) => { |
||||
|
item.content = res.tempFilePath; |
||||
|
if (item.arc) { |
||||
|
this.drawImageArc(item); |
||||
|
} else { |
||||
|
this.drawImage(item); |
||||
|
} |
||||
|
}, |
||||
|
fail: (res) => { |
||||
|
console.log(res); |
||||
|
} |
||||
|
}); |
||||
|
}, |
||||
|
// #endif |
||||
|
// #ifdef H5 |
||||
|
// https图片下载本地并绘制,H5 |
||||
|
downloadImageH5 (item) { |
||||
|
console.log(item.content); |
||||
|
if (/\.(jpg|png)$/i.test(item.content)) { |
||||
|
// 符合条件时跳过处理 |
||||
|
this.checkDrawOver(); |
||||
|
}else{ |
||||
|
let image = null; |
||||
|
image = new Image(); |
||||
|
image.setAttribute('crossOrigin', 'anonymous'); |
||||
|
image.crossOrigin = 'Anonymous'; |
||||
|
image.src = item.content; |
||||
|
image.onload = () => { |
||||
|
let canvas = document.createElement('canvas'); |
||||
|
canvas.width = item.width; |
||||
|
canvas.height = item.height; |
||||
|
let ctx = canvas.getContext('2d'); |
||||
|
ctx.drawImage( |
||||
|
image, |
||||
|
0, |
||||
|
0, |
||||
|
item.width, |
||||
|
item.height |
||||
|
); |
||||
|
let dataURL = canvas.toDataURL('image/png'); |
||||
|
if (item.arc) { // 绘制圆形 |
||||
|
item.content = dataURL; |
||||
|
this.drawImageArc(item); |
||||
|
} else { |
||||
|
this.canvas.globalAlpha = item.hasOwnProperty('globalAlpha') ? item.globalAlpha : 1; |
||||
|
this.canvas.drawImage( |
||||
|
dataURL, |
||||
|
item.x, |
||||
|
item.y, |
||||
|
item.hasOwnProperty('width') ? item.width : this.width, |
||||
|
item.hasOwnProperty('height') ? item.height : this.height |
||||
|
); |
||||
|
this.checkDrawOver(); |
||||
|
} |
||||
|
|
||||
|
}; |
||||
|
} |
||||
|
}, |
||||
|
// #endif |
||||
|
// 图片压缩 |
||||
|
compressImage (item) { |
||||
|
uni.showLoading({ |
||||
|
title: '压缩中...', |
||||
|
mask: true |
||||
|
}); |
||||
|
// 非H5压缩 |
||||
|
// #ifndef H5 |
||||
|
uni.compressImage({ |
||||
|
src: item.content, |
||||
|
quality: this.compressQuality, |
||||
|
success: (res) => { |
||||
|
uni.showLoading({ |
||||
|
title: '正在生成图片...', |
||||
|
mask: true |
||||
|
}); |
||||
|
item.content = res.tempFilePath; |
||||
|
if (item.arc) { |
||||
|
this.drawImageArc(item); |
||||
|
} else { |
||||
|
this.drawImage(item); |
||||
|
} |
||||
|
}, |
||||
|
fail: (res) => { |
||||
|
console.log(res); |
||||
|
uni.showToast({ |
||||
|
title: '压缩失败', |
||||
|
icon: 'none' |
||||
|
}); |
||||
|
} |
||||
|
}); |
||||
|
// #endif |
||||
|
// H5压缩 |
||||
|
// #ifdef H5 |
||||
|
let image = new Image(); |
||||
|
image.setAttribute('crossOrigin', 'anonymous'); |
||||
|
image.crossOrigin = 'Anonymous'; |
||||
|
image.src = item.content; |
||||
|
image.onload = () => { |
||||
|
let canvas = document.createElement('canvas'); |
||||
|
canvas.width = item.width; |
||||
|
canvas.height = item.height; |
||||
|
let ctx = canvas.getContext('2d'); |
||||
|
ctx.drawImage( |
||||
|
image, |
||||
|
0, |
||||
|
0, |
||||
|
item.width, |
||||
|
item.height |
||||
|
); |
||||
|
let dataURL = canvas.toDataURL('image/png'); |
||||
|
item.content = dataURL; |
||||
|
if (item.arc) { |
||||
|
this.drawImageArc(item); |
||||
|
} else { |
||||
|
this.drawImage(item); |
||||
|
} |
||||
|
}; |
||||
|
// #endif |
||||
|
}, |
||||
|
// 圆形图片另外绘制canvas,png格式 |
||||
|
drawImageArc (item) { |
||||
|
this.canvasArc.clearRect(0, 0, this.canvasArcWidth, this.canvasArcHeight); |
||||
|
this.canvasArcWidth = item.arcR * 2; |
||||
|
this.canvasArcHeight = item.arcR * 2; |
||||
|
this.canvasArc.save(); |
||||
|
let arcT = setTimeout(() => { |
||||
|
clearTimeout(arcT); |
||||
|
this.canvasArc.arc(item.arcR, item.arcR, item.arcR, 0, 2 * Math.PI); |
||||
|
this.canvasArc.clip(); |
||||
|
// this.canvasArc.closePath(); |
||||
|
|
||||
|
this.canvasArc.drawImage( |
||||
|
item.content, |
||||
|
item.arcX, |
||||
|
item.arcY, |
||||
|
item.width, |
||||
|
item.height |
||||
|
); |
||||
|
this.canvasArc.draw(false, setTimeout(() => { |
||||
|
let t = setTimeout(() => { |
||||
|
clearTimeout(t); |
||||
|
uni.canvasToTempFilePath({ |
||||
|
x: 0, |
||||
|
y: 0, |
||||
|
width: item.arcR * 2, |
||||
|
height: item.arcR * 2, |
||||
|
fileType: 'png', |
||||
|
canvasId: 'canvas-arc', |
||||
|
success: (res) => { |
||||
|
item.width = item.arcR * 2; |
||||
|
item.height = item.arcR * 2; |
||||
|
item.content = res.tempFilePath; |
||||
|
this.drawImage(item); |
||||
|
}, |
||||
|
fail: (res) => { |
||||
|
console.log(res); |
||||
|
}, |
||||
|
complete: () => { |
||||
|
this.canvasArc.restore(); |
||||
|
this.canvasArc.fillRect(0, 0, 0, 0); |
||||
|
this.canvasArc.clearRect(0, 0, this.canvasArcWidth, this.canvasArcHeight); |
||||
|
} |
||||
|
}, this); |
||||
|
}, 100); |
||||
|
})); |
||||
|
}, 100); |
||||
|
}, |
||||
|
// 图片绘制 |
||||
|
drawImage (item) { |
||||
|
this.canvas.globalAlpha = item.hasOwnProperty('globalAlpha') ? item.globalAlpha : 1; |
||||
|
this.canvas.drawImage( |
||||
|
item.content, |
||||
|
item.x, |
||||
|
item.y, |
||||
|
item.hasOwnProperty('width') ? item.width : this.width, |
||||
|
item.hasOwnProperty('height') ? item.height : this.height |
||||
|
); |
||||
|
this.checkDrawOver(); |
||||
|
}, |
||||
|
// 文本绘制 |
||||
|
drawText (item) { |
||||
|
this.canvas.setFillStyle(item.hasOwnProperty('color') ? item.color : '#000000'); |
||||
|
this.canvas.setFontSize(item.hasOwnProperty('size')? item.size : 20); |
||||
|
this.canvas.setTextAlign(item.hasOwnProperty('align') ? item.align: 'left'); |
||||
|
this.canvas.globalAlpha = item.hasOwnProperty('globalAlpha') ? item.globalAlpha : 1; |
||||
|
|
||||
|
if (item.maxWidth) { |
||||
|
this.canvas.fillText(item.content, item.x, item.y, item.maxWidth); |
||||
|
} else { |
||||
|
this.canvas.fillText(item.content, item.x, item.y); |
||||
|
} |
||||
|
this.checkDrawOver(); |
||||
|
}, |
||||
|
|
||||
|
// 矩形(线条)绘制 |
||||
|
drawRect (item) { |
||||
|
this.canvas.setFillStyle(item.hasOwnProperty('color') ? item.color : '#000000'); |
||||
|
this.canvas.globalAlpha = item.hasOwnProperty('globalAlpha') ? item.globalAlpha : 1; |
||||
|
this.canvas.fillRect(item.x, item.y, item.width, item.height); |
||||
|
this.checkDrawOver(); |
||||
|
}, |
||||
|
|
||||
|
// 圆形绘制 |
||||
|
drawArc (item) { |
||||
|
this.canvas.arc(item.arcX, item.arcY, item.arcR, 0, 2 * Math.PI); |
||||
|
this.canvas.setFillStyle(item.hasOwnProperty('color') ? item.color : '#000000'); |
||||
|
this.canvas.globalAlpha = item.hasOwnProperty('globalAlpha') ? item.globalAlpha : 1; |
||||
|
this.canvas.fill(); |
||||
|
this.canvas.closePath(); |
||||
|
this.checkDrawOver(); |
||||
|
}, |
||||
|
|
||||
|
// 二维码绘制 |
||||
|
drawQR (item) { |
||||
|
let len=item.content.length; |
||||
|
let typeNumber=Math.ceil(len/16); |
||||
|
if(typeNumber<4){ |
||||
|
typeNumber=4; |
||||
|
} |
||||
|
item['qr'] = QR.createQrCodeImg(item.content, { |
||||
|
size: parseInt(300), |
||||
|
typeNumber:typeNumber |
||||
|
}); |
||||
|
this.canvas.globalAlpha = item.hasOwnProperty('globalAlpha') ? item.globalAlpha : 1; |
||||
|
this.canvas.drawImage( |
||||
|
item.qr, |
||||
|
item.x, |
||||
|
item.y, |
||||
|
item.hasOwnProperty('width') ? item.width : this.width, |
||||
|
item.hasOwnProperty('height') ? item.height : this.height |
||||
|
); |
||||
|
this.checkDrawOver(); |
||||
|
}, |
||||
|
|
||||
|
|
||||
|
// 判断是否绘制完 |
||||
|
checkDrawOver () { |
||||
|
if (this.listsIndex < this.listsLength) { // lists未画完 |
||||
|
this.listsIndex++; |
||||
|
this.dataDrawCanvas(); |
||||
|
} else { |
||||
|
this.canvasImage(); |
||||
|
} |
||||
|
}, |
||||
|
|
||||
|
// 绘制到画布并生成图片 |
||||
|
canvasImage () { |
||||
|
this.listsIndex = 0; |
||||
|
this.canvas.draw(false, () => { |
||||
|
setTimeout(() => { |
||||
|
uni.canvasToTempFilePath({ |
||||
|
x: 0, |
||||
|
y: 0, |
||||
|
width: Number(this.width), |
||||
|
height: Number(this.height), |
||||
|
fileType: this.imgType, |
||||
|
canvasId: 'canvas', |
||||
|
success: (res) => { |
||||
|
this.$emit('canvasImage', res.tempFilePath); |
||||
|
if (this.showPreview) { |
||||
|
this.showPreviewFn(res.tempFilePath); |
||||
|
} |
||||
|
}, |
||||
|
fail: (res) => { |
||||
|
console.log(res); |
||||
|
}, |
||||
|
complete: () => { |
||||
|
uni.hideLoading(); |
||||
|
} |
||||
|
}, this); |
||||
|
}, 500); |
||||
|
}); |
||||
|
}, |
||||
|
// 预览图 |
||||
|
showPreviewFn (img) { |
||||
|
uni.previewImage({ |
||||
|
current: 0, |
||||
|
urls: [img] |
||||
|
}); |
||||
|
}, |
||||
|
// 清空画布 |
||||
|
clearCanvas () { |
||||
|
this.canvas.clearRect(0, 0, this.width, this.height); |
||||
|
}, |
||||
|
|
||||
|
} |
||||
|
}; |
||||
|
</script> |
||||
|
|
||||
|
<style lang='scss' scoped> |
||||
|
.mosowe-canvas-image{ |
||||
|
overflow: hidden; |
||||
|
.canvas-wrap-box{ |
||||
|
overflow: hidden; |
||||
|
height: 0; |
||||
|
width: 0; |
||||
|
position: fixed; |
||||
|
left:200%; |
||||
|
top: 0; |
||||
|
} |
||||
|
.canvas-wrap { |
||||
|
overflow: hidden; |
||||
|
height: 0; |
||||
|
width: 0; |
||||
|
} |
||||
|
} |
||||
|
</style> |
||||
@ -0,0 +1,195 @@ |
|||||
|
## mosowe-canvas-image:一个可以制作多用途图片的插件(海报,二维码,分享图) |
||||
|
|
||||
|
### v1.2.0: |
||||
|
1. 添加透明度`globalAlpha`,值0-1; |
||||
|
2. 修改只能画一个圆形图片问题,画圆形图片时耗时较多,因为额外增加了一个canvas处理圆形图片,请注意`arcX`与`arcY`的说明; |
||||
|
|
||||
|
### v1.1.0: |
||||
|
|
||||
|
1. 添加本地图片渲染,已支持本地相册/相机 + https的网络图片,建议画布宽高:750*1330 |
||||
|
2. 添加导出图片的类型:`imgType`,可选值,jpg、png |
||||
|
3. 添加本地图片压缩功能:`compress`是否开启压缩,`compressSize`:压缩程度,默认2M,lists列表图片项需增加传参`file`, |
||||
|
4. 解决H5网络图片“canvasToTempFilePath:fail SecurityError: Failed to execute 'toDataURL' on 'HTMLCanvasElement': Tainted canvases may not be exported”错误问题,图片服务器需支持“Access-Control-Allow-Origin: *” |
||||
|
5. 使用非solt插槽触发事件时,请确认`$ref`是否正确 |
||||
|
6. 示例增加参数编辑,使用前体验插件 |
||||
|
|
||||
|
#### 平台支持: |
||||
|
|
||||
|
| APP | H5 | 微信小程序 | 支付宝小程序 | 百度小程序 | 字节跳动小程序 | QQ小程序 | |
||||
|
| :--: | :--: | :--------: | :----------: | :--------: | :------------: | :------: | |
||||
|
| √ | √ | √ | × | 未测 | 未测 | 未测 | |
||||
|
|
||||
|
#### 插件功能 |
||||
|
|
||||
|
1. 支持多图片绘制,多文本绘制,圆形图片绘制; |
||||
|
2. 支持矩形(线条)绘制; |
||||
|
3. 支持圆形绘制; |
||||
|
4. 支持二维码生成,项目用不上可以去插件内去除,毕竟这个插件携带的比较大,单纯用来生成二维码图片也是阔以的; |
||||
|
5. 支持绘图后预览。 |
||||
|
|
||||
|
多用于海报图,分享图; |
||||
|
|
||||
|
注意H5跨域问题及小程序白名单配置; |
||||
|
|
||||
|
图片是网络图片:https://....(require及import引入不了3Kb以上的绝对路径图片,若有大神知道处理方法,望不吝赐教,谢谢!) |
||||
|
|
||||
|
#### 属性 |
||||
|
|
||||
|
| 名称 | 类型 | 默认值 | 说明 | 版本 | |
||||
|
| ------------ | ---------------- | ------------ | -------------------------------------------------------- | ----- | |
||||
|
| width | Number \String | 200 | canvas画布宽度,也是导出图片宽度,单位px,值中不要带单位 | 1.0.0 | |
||||
|
| height | Number \String | 200 | canvas画布高度,也是导出图片高度,单位px,值中不要带单位 | 1.0.0 | |
||||
|
| showPreview | Boolean | false | 绘制完成后是否打开预览 | 1.0.0 | |
||||
|
| lists | Array | [] | 绘制的元素列表:图片,文字,矩形(线条),圆形,二维码 | 1.0.0 | |
||||
|
| imgType | String | jpg | 生成图片格式,可选:png | 1.1.0 | |
||||
|
| compress | Boolean | false | 是否开启图片压缩 | 1.1.0 | |
||||
|
| compressSize | Number\String | 2097152 (2M) | 超过多少M压缩 | 1.1.0 | |
||||
|
|
||||
|
|
||||
|
|
||||
|
|
||||
|
#### lists\<item>属性 |
||||
|
|
||||
|
注意:图文先后顺序,底层的图片靠前,最上层的在最后,圆形图片放在最后,因为一旦绘制圆形,后续的元素都只在该圆形内显示,而超过圆形范围的将看不见。 |
||||
|
|
||||
|
| 名称 | 类型 | 必填 | 说明 |版本| |
||||
|
| :------- | :----- | :--- | :----------------------------------------------------------- |:----| |
||||
|
| type | String | 是 | 元素类型:`image`图片,`text`文本,`rect`矩形(线条),`arc`圆形,`qr`二维码 |1.0.0| |
||||
|
| content | String | 否 | image:图片路径(必填),text:文字(必填),qr:转二维码的数据(必填),rect及arc:非必填 |1.0.0| |
||||
|
| x | Number | 是 | X轴坐标,绘制圆形图片时:x = arcX - arcR |1.0.0| |
||||
|
| y | Number | 是 | Y轴坐标,绘制圆形图片时:y = arcY - arcR |1.0.0| |
||||
|
| width | Number | 否 | 图片、矩形(线条)、二维码宽度 |1.0.0| |
||||
|
| height | Number | 否 | 图片、矩形(线条)、二维码高度 |1.0.0| |
||||
|
| arc | Boolen | 否 | type=image时:是否绘制圆形图片 |1.0.0| |
||||
|
| arcX | Number | 否 | type=arc时:绘制圆形时中心点X轴坐标,type=image时,图片在圆形canvas的X坐标,多为负数,版本`1.2.0` |1.2.0| |
||||
|
| arcY | Number | 否 | type=arc时:绘制圆形时中心点Y轴坐标,type=image时,图片在圆形canvas的Y坐标,多为负数,版本`1.2.0` |1.2.0| |
||||
|
| arcR | Number | 否 | type=image、arc时:绘制圆形的半径 |1.0.0| |
||||
|
| color | String | 否 | 绘制文本、矩形(线条)的颜色,默认:#000000 |1.0.0| |
||||
|
| size | Number | 否 | 绘制文本的字号大小,默认:20 |1.0.0| |
||||
|
| align | String | 否 | 绘制文本的对齐方式,默认:left |1.0.0| |
||||
|
| maxWidth | Number | 否 | 绘制文本的最大宽度,文字长度超过该值会被压缩 |1.0.0| |
||||
|
| file | file | 否 | 选择本地图片的file文件,版本`1.1.0` |1.1.0| |
||||
|
| globalAlpha | Number | 否 | 透明度:0~1,默认1,版本`1.2.0` |1.2.0| |
||||
|
|
||||
|
#### slots |
||||
|
|
||||
|
| 名称 | 说明 | |
||||
|
| :------ | :--------------------------------- | |
||||
|
| default | 自定义插槽,点击此区会触发绘图事件 | |
||||
|
|
||||
|
#### 事件 |
||||
|
|
||||
|
| 名称 | 回调参数 | 说明 | |
||||
|
| ----------- | -------- | ------------------------------------ | |
||||
|
| canvasImage | url | 绘制成功后返回的本地地址,H5为base64 | |
||||
|
|
||||
|
|
||||
|
|
||||
|
#### 使用方式 |
||||
|
|
||||
|
若`page.json`中配置了`"easycom": true`,则无需`script`引入就可以使用,没有则需要引入。 |
||||
|
|
||||
|
1. 无slot:组件标签添加`ref`属性,采用父级调用子组件`createCanvas()`方法使用,见后文示例; |
||||
|
2. 有slot:slot区点击就会执行 |
||||
|
|
||||
|
#### 示例 |
||||
|
|
||||
|
```javascript |
||||
|
// js |
||||
|
data () { |
||||
|
return { |
||||
|
canvasUrl: '', |
||||
|
lists: [ |
||||
|
{ |
||||
|
type: 'image', |
||||
|
content: 'https://www.zhonglixunqing.cn/images/uniapp/1.jpg', |
||||
|
width: 200, |
||||
|
height: 100, |
||||
|
x: 50, |
||||
|
y: 20, |
||||
|
}, |
||||
|
{ |
||||
|
type: 'image', |
||||
|
content: 'https://www.zhonglixunqing.cn/images/uniapp/2.jpg', |
||||
|
width: 80, |
||||
|
height: 80, |
||||
|
x: 20, |
||||
|
y: 200, |
||||
|
arc: false, |
||||
|
arcX: 0, |
||||
|
arcY: 0, |
||||
|
arcR: 0 |
||||
|
}, |
||||
|
{ |
||||
|
type: 'text', |
||||
|
content: '扫一扫,获取更多信息', |
||||
|
x: 120, |
||||
|
y: 250, |
||||
|
color: '#ff0000', |
||||
|
size: 10, |
||||
|
// maxWidth: 100, |
||||
|
// align: 'left', |
||||
|
}, |
||||
|
{ |
||||
|
type: 'rect', |
||||
|
width: 1, |
||||
|
height: 100, |
||||
|
x: 0, |
||||
|
y: 10, |
||||
|
color: '#ff0000', |
||||
|
}, |
||||
|
{ |
||||
|
type: 'image', |
||||
|
content: 'https://www.zhonglixunqing.cn/images/uniapp/3.jpg', |
||||
|
width: 100, |
||||
|
height: 100, |
||||
|
x: 200, |
||||
|
y: 200, |
||||
|
arc: true, |
||||
|
arcX: 250, |
||||
|
arcY: 250, |
||||
|
arcR: 50 |
||||
|
}, |
||||
|
] |
||||
|
}; |
||||
|
}, |
||||
|
methods: { |
||||
|
beginCanvas () { |
||||
|
this.$refs.mosoweCanvasComponents.createCanvas(); |
||||
|
}, |
||||
|
_canvasImage (e) { |
||||
|
this.canvasUrl = e; |
||||
|
} |
||||
|
} |
||||
|
``` |
||||
|
|
||||
|
插件外独立按钮触发: |
||||
|
|
||||
|
```html |
||||
|
<button type="default" @click="beginCanvas">开始绘图</button> |
||||
|
<image :src="canvasUrl" mode="widthFix"></image> |
||||
|
<mosowe-canvas-image |
||||
|
ref="mosoweCanvasComponents" |
||||
|
@canvasImage="_canvasImage" |
||||
|
:lists="lists" |
||||
|
height="300" |
||||
|
width="300" |
||||
|
showPreview /> |
||||
|
``` |
||||
|
|
||||
|
slot插槽触发: |
||||
|
|
||||
|
```html |
||||
|
<mosowe-canvas-image |
||||
|
:lists="lists" |
||||
|
height="300" |
||||
|
width="300" |
||||
|
showPreview > |
||||
|
<view class="in_btn"> |
||||
|
slot按钮的 |
||||
|
</view> |
||||
|
</mosowe-canvas-image> |
||||
|
``` |
||||
|
|
||||
|
#### 预览地址 |
||||
|
|
||||
@ -0,0 +1,416 @@ |
|||||
|
<template> |
||||
|
<view class="burst-wrap"> |
||||
|
<view class="burst-wrap-bg"> |
||||
|
<view> |
||||
|
<!-- 信息提交 --> |
||||
|
<view class="burst-info"> |
||||
|
<view class="uni-uploader-body"> |
||||
|
<view class="uni-uploader__files"> |
||||
|
<!-- 图片 --> |
||||
|
<block v-for="(image,index) in imageList" :key="index"> |
||||
|
<view class="uni-uploader__file"> |
||||
|
<view class="icon iconfont icon-cuo"> |
||||
|
<!-- @tap="delect(index)" --> |
||||
|
<uni-icons type="closeempty" color="#000" size="15" class="closeempty_icons" @tap="delect(index)"></uni-icons> |
||||
|
</view> |
||||
|
<image class="uni-uploader__img" :src="image" :data-src="image" @tap="previewImage"></image> |
||||
|
</view> |
||||
|
</block> |
||||
|
<!-- 视频 --> |
||||
|
<view v-if="src1.url" style="width: 100%;height: 100%;"> |
||||
|
<view> |
||||
|
<!-- <video :src="src" id="myVideo" class="video" @play="playVideoFullscreen"></video> |
||||
|
<cover-view style="position: absolute;width: 160rpx;height: 160rpx;z-index: 999;color: #fff;display: flex;"> |
||||
|
<cover-view style="width: 80%;" @click="playVideoFullscreen"></cover-view> |
||||
|
<cover-view style="width: 20%;height: 40rpx;font-size: 45rpx;" @click="delectVideo">×</cover-view> |
||||
|
</cover-view> --> |
||||
|
<view class='course-video' :style="src1.url ? $util.imageCoverStyle(270,480) : ''"> |
||||
|
<view class="relative-shadow flex justify-center align-center" @tap="handlePlay(src1)"> |
||||
|
<view class="cuIcon-video icon-center f-28 c-white"></view> |
||||
|
</view> |
||||
|
<image :src="src1.poster" mode="aspectFit" style="position: absolute;top: 0px;width: 100%;height: 100%;"></image> |
||||
|
<uni-icons @click="delectVideo" type="closeempty" size="30" style="position: absolute;top: 0px;right: 0px;z-index: 1;"></uni-icons> |
||||
|
</view> |
||||
|
</view> |
||||
|
</view> |
||||
|
<view class="uni-uploader__input-box" v-if="VideoOfImagesShow"> |
||||
|
<view class="uni-uploader__input" @tap="chooseVideoImage"> |
||||
|
<image src="/static/image/jiahao.png" mode="widthFix" style="width: 60rpx;"></image> |
||||
|
</view> |
||||
|
</view> |
||||
|
</view> |
||||
|
</view> |
||||
|
</view> |
||||
|
</view> |
||||
|
</view> |
||||
|
</view> |
||||
|
</template> |
||||
|
|
||||
|
<script> |
||||
|
import config from '@/common/config.js' |
||||
|
import { chat } from '@/mixins/chat.js' |
||||
|
import imImage from '@/components/message/im-image.vue'; |
||||
|
var sourceType = [ |
||||
|
['camera'], |
||||
|
['album'], |
||||
|
['camera', 'album'] |
||||
|
] |
||||
|
export default { |
||||
|
components:{ |
||||
|
imImage |
||||
|
}, |
||||
|
mixins:[chat], |
||||
|
props: { |
||||
|
img_arr1: { type: Array, default: ()=>[] } |
||||
|
}, |
||||
|
data() { |
||||
|
return { |
||||
|
imageList:[],//图片 |
||||
|
src1:{},//视频存放 |
||||
|
sourceTypeIndex: 2, |
||||
|
checkedValue:true, |
||||
|
checkedIndex:0, |
||||
|
sourceType: ['拍摄', '相册', '拍摄或相册'], |
||||
|
cameraList: [{ |
||||
|
value: 'back', |
||||
|
name: '后置摄像头', |
||||
|
checked: 'true' |
||||
|
}, |
||||
|
{ |
||||
|
value: 'front', |
||||
|
name: '前置摄像头' |
||||
|
}, |
||||
|
], |
||||
|
cameraIndex: 0, |
||||
|
VideoOfImagesShow:true, |
||||
|
} |
||||
|
}, |
||||
|
watch: { |
||||
|
img_arr1: { |
||||
|
immediate: true, // 立即触发初始值 |
||||
|
handler(newVal) { |
||||
|
if(newVal.length > 0) { |
||||
|
// this.handleImageLoad(newVal); // 你的业务逻辑 |
||||
|
newVal.forEach((res)=>{ |
||||
|
if(res.type==1){ |
||||
|
this.imageList.push(config.apiUrl+res.src) |
||||
|
}else if(res.type==2){ |
||||
|
this.src1 = {url:config.apiUrl+res.src,poster:config.apiUrl+res.privacy}; |
||||
|
this.VideoOfImagesShow = false; |
||||
|
} |
||||
|
}) |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
}, |
||||
|
onUnload() { |
||||
|
this.src1 = {}, |
||||
|
this.sourceTypeIndex = 2, |
||||
|
this.sourceType = ['拍摄', '相册', '拍摄或相册']; |
||||
|
}, |
||||
|
methods: { |
||||
|
chooseVideoImage(){ |
||||
|
if(this.imageList.length>0){ |
||||
|
uni.showActionSheet({ |
||||
|
title:"选择上传类型", |
||||
|
itemList: ['图片'], |
||||
|
success: (res) => { |
||||
|
console.log(res) |
||||
|
if(res.tapIndex == 0){ |
||||
|
this.chooseImages() |
||||
|
} else { |
||||
|
this.chooseVideo() |
||||
|
} |
||||
|
} |
||||
|
}) |
||||
|
}else{ |
||||
|
uni.showActionSheet({ |
||||
|
title:"选择上传类型", |
||||
|
itemList: ['图片','视频'], |
||||
|
success: (res) => { |
||||
|
console.log(res) |
||||
|
if(res.tapIndex == 0){ |
||||
|
this.chooseImages() |
||||
|
} else { |
||||
|
this.chooseVideo() |
||||
|
} |
||||
|
} |
||||
|
}) |
||||
|
} |
||||
|
}, |
||||
|
playVideoFullscreen(){ |
||||
|
// 创建视频上下文 |
||||
|
const videoContext = uni.createVideoContext('myVideo') |
||||
|
|
||||
|
// 进入全屏 |
||||
|
videoContext.requestFullScreen({direction: 0}); |
||||
|
}, |
||||
|
chooseImages(){ |
||||
|
// 上传图片 |
||||
|
uni.chooseImage({ |
||||
|
count: 9, //默认9 |
||||
|
// sizeType: ['original', 'compressed'], //可以指定是原图还是压缩图,默认二者都有 |
||||
|
sourceType: ['album','camera'], //从相册选择 |
||||
|
success:(res)=> { |
||||
|
let igmFile = res.tempFilePaths; |
||||
|
console.log(igmFile,'123'); |
||||
|
// 计算还能添加多少张(最多9张) |
||||
|
const remainingSlots = 9 - this.imageList.length; |
||||
|
if (remainingSlots <= 0) { |
||||
|
uni.showToast({ title: '最多上传9张图片', icon: 'none' }); |
||||
|
return; |
||||
|
} |
||||
|
|
||||
|
// 只处理剩余可添加的数量 |
||||
|
const filesToUpload = igmFile.slice(0, remainingSlots); |
||||
|
|
||||
|
filesToUpload.map(fileurl => { |
||||
|
uni.uploadFile({ |
||||
|
url:config.apiUrl+'/common/upload/uploadPosts', |
||||
|
method:"POST", |
||||
|
header:{ |
||||
|
'Authorization':uni.getStorageSync('authToken') |
||||
|
}, |
||||
|
filePath:fileurl, |
||||
|
name:'file', |
||||
|
success: (res) =>{ |
||||
|
let imgUrls = JSON.parse(res.data); //微信和头条支持 |
||||
|
imgUrls.data.forEach((item)=>{ |
||||
|
if (this.imageList.length < 9) { |
||||
|
this.imageList = this.imageList.concat(item.url); |
||||
|
} |
||||
|
}) |
||||
|
this.$emit('send',this.imageList) |
||||
|
if(this.imageList.length>=9) { |
||||
|
this.VideoOfImagesShow = false; |
||||
|
} else { |
||||
|
this.VideoOfImagesShow = true; |
||||
|
} |
||||
|
} |
||||
|
}) |
||||
|
}) |
||||
|
}, |
||||
|
}); |
||||
|
}, |
||||
|
chooseVideo(){ |
||||
|
// 上传视频 |
||||
|
uni.chooseVideo({ |
||||
|
maxDuration:60, |
||||
|
count: 9, |
||||
|
camera: this.cameraList[this.cameraIndex].value, |
||||
|
sourceType: ['album', 'camera'], |
||||
|
success: (responent) => { |
||||
|
let videoFile = responent.tempFilePath; |
||||
|
uni.uploadFile({ |
||||
|
url:config.apiUrl+'/common/upload/uploadPosts', |
||||
|
method:"POST", |
||||
|
header:{ |
||||
|
'Authorization':uni.getStorageSync('authToken') |
||||
|
}, |
||||
|
filePath:videoFile, |
||||
|
name:'file', |
||||
|
success: (res) => { |
||||
|
let videoUrls = JSON.parse(res.data) //微信和头条支持 |
||||
|
// console.info(videoUrls,'12345'); |
||||
|
// let videoUrls = res.data //百度支持 |
||||
|
// this.imagesUrlPath = this.imagesUrlPath.concat(videoUrls); |
||||
|
this.src1 = videoUrls.data[0]; //微信 |
||||
|
this.$emit('videourl',this.src1) |
||||
|
if(this.src1.url) { |
||||
|
this.itemList = ['图片'] |
||||
|
this.VideoOfImagesShow = false; |
||||
|
} else { |
||||
|
this.itemList = ['图片','视频'] |
||||
|
} |
||||
|
|
||||
|
} |
||||
|
}) |
||||
|
// this.src = responent.tempFilePath; //头条 |
||||
|
} |
||||
|
}) |
||||
|
}, |
||||
|
previewImage(e) { |
||||
|
//预览图片 |
||||
|
var current = e.target.dataset.src |
||||
|
uni.previewImage({ |
||||
|
current: current, |
||||
|
urls: [current] |
||||
|
}) |
||||
|
}, |
||||
|
delect(index){ |
||||
|
uni.showModal({ |
||||
|
title: "提示", |
||||
|
content: "是否要删除该图片", |
||||
|
success: (res) => { |
||||
|
if (res.confirm) { |
||||
|
this.imageList.splice(index, 1) |
||||
|
if(this.imageList.length>=9) { |
||||
|
this.VideoOfImagesShow = false; |
||||
|
} else { |
||||
|
this.VideoOfImagesShow = true; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
}) |
||||
|
}, |
||||
|
delectVideo(){ |
||||
|
uni.showModal({ |
||||
|
title:"提示", |
||||
|
content:"是否要删除此视频", |
||||
|
success:(res) =>{ |
||||
|
if(res.confirm){ |
||||
|
this.src1 = {} |
||||
|
this.VideoOfImagesShow = true; |
||||
|
} |
||||
|
} |
||||
|
}) |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
</script> |
||||
|
|
||||
|
<style scoped> |
||||
|
.burst-wrap{ |
||||
|
width: 100%; |
||||
|
height: 100px; |
||||
|
margin-top: 20px; |
||||
|
} |
||||
|
/* .burst-wrap .burst-wrap-bg{ |
||||
|
position: relative; |
||||
|
width: 100%; |
||||
|
height: 320rpx; |
||||
|
background:linear-gradient(90deg,rgba(251,91,80,1) 0%,rgba(240,45,51,1) 100%); |
||||
|
border-bottom-right-radius: 80rpx; |
||||
|
border-bottom-left-radius: 80rpx; |
||||
|
} */ |
||||
|
.burst-wrap .burst-wrap-bg>view{ |
||||
|
width: 100%; |
||||
|
height: 100%; |
||||
|
/* margin: 0 auto; |
||||
|
position: absolute; |
||||
|
top: 65rpx; |
||||
|
left: 0; |
||||
|
right: 0; */ |
||||
|
} |
||||
|
|
||||
|
.form-item{ |
||||
|
width: 100%; |
||||
|
} |
||||
|
.form-item textarea{ |
||||
|
display: block; |
||||
|
height: 220rpx; |
||||
|
width: 100%; |
||||
|
color: #AAAAAA; |
||||
|
font-size: 28rpx; |
||||
|
} |
||||
|
.uni-uploader__files{ |
||||
|
display: flex; |
||||
|
flex-wrap: wrap; |
||||
|
} |
||||
|
.uni-uploader__file{ |
||||
|
z-index: 999; |
||||
|
width: 160rpx; |
||||
|
height: 160rpx; |
||||
|
margin-right: 6px; |
||||
|
margin-top: 5px; |
||||
|
position: relative; |
||||
|
} |
||||
|
.uploader_video{ |
||||
|
width: 160rpx; |
||||
|
height: 160rpx; |
||||
|
margin-right: 6px; |
||||
|
} |
||||
|
.uni-uploader__img { |
||||
|
width: 160rpx; |
||||
|
height: 160rpx; |
||||
|
} |
||||
|
.icon-cuo { |
||||
|
top: 5rpx; |
||||
|
right: 5px; |
||||
|
width: 20px; |
||||
|
height: 20px; |
||||
|
z-index: 999; |
||||
|
/* color: #FFFFFF; */ |
||||
|
position: absolute; |
||||
|
border-radius: 20px; |
||||
|
/* background: linear-gradient(90deg,rgba(209, 209, 209, 1.0) 0%,rgba(209, 209, 209, 1.0) 100%); */ |
||||
|
} |
||||
|
.video{ |
||||
|
width: 100%; |
||||
|
height: 100%; |
||||
|
position: absolute; |
||||
|
z-index: -1; |
||||
|
} |
||||
|
|
||||
|
.login-input-box{ |
||||
|
position: relative; |
||||
|
border-bottom: 1rpx solid #EEEEEE; |
||||
|
} |
||||
|
.login-input-box .forget,.login-input-box .phone{ |
||||
|
position: absolute; |
||||
|
top: 0; |
||||
|
height: 100%; |
||||
|
z-index: 100; |
||||
|
} |
||||
|
.login-input-box .phone{ |
||||
|
width: 100rpx; |
||||
|
left: 0; |
||||
|
color: #666666; |
||||
|
font-weight: bold; |
||||
|
} |
||||
|
.login-input-box .phone-input{ |
||||
|
padding-left: 100rpx; |
||||
|
} |
||||
|
.address-wrap,.open-info{ |
||||
|
margin-top: 20rpx; |
||||
|
} |
||||
|
.open-info>view:last-child{ |
||||
|
font-size: 28rpx; |
||||
|
color: #999999; |
||||
|
} |
||||
|
.address-wrap .address { |
||||
|
background: #F2F2F2; |
||||
|
border-radius: 40rpx; |
||||
|
padding: 0 20rpx; |
||||
|
} |
||||
|
.user-set-btn{ |
||||
|
margin: 40rpx; |
||||
|
background: linear-gradient(90deg,rgba(251,91,80,1) 0%,rgba(240,45,51,1) 100%); |
||||
|
color: #FFFFFF; |
||||
|
text-align: center; |
||||
|
height: 88rpx; |
||||
|
line-height: 88rpx; |
||||
|
} |
||||
|
.uni-uploader__input-box{ |
||||
|
width: 160rpx; |
||||
|
height: 160rpx; |
||||
|
margin-top: 5px; |
||||
|
} |
||||
|
.uni-uploader__input{ |
||||
|
width: 100%; |
||||
|
height: 100%; |
||||
|
display: flex; |
||||
|
align-items: center; |
||||
|
justify-content: center; |
||||
|
background-color: #ebebeb; |
||||
|
} |
||||
|
.closeempty_icons{ |
||||
|
display: flex; |
||||
|
line-height: 20px; |
||||
|
justify-content: center; |
||||
|
} |
||||
|
.course-video{ |
||||
|
overflow: hidden; |
||||
|
position: relative; |
||||
|
} |
||||
|
.relative-shadow{ |
||||
|
z-index:1; |
||||
|
width:100%; |
||||
|
height:100%; |
||||
|
height: 240px; |
||||
|
display: flex; |
||||
|
position: absolute; |
||||
|
align-items: center; |
||||
|
justify-content: center; |
||||
|
} |
||||
|
</style> |
||||
@ -0,0 +1,92 @@ |
|||||
|
<!-- 原子脉冲状态显示器 ,引用于scui--> |
||||
|
<template> |
||||
|
<view class="sc-state" :class="[{ 'sc-status-processing': pulse }, 'sc-state-bg--' + type]"></view> |
||||
|
</template> |
||||
|
|
||||
|
<script> |
||||
|
export default { |
||||
|
props: { |
||||
|
type: { type: String, default: "primary" }, |
||||
|
pulse: { type: [Boolean,Number], default: true } |
||||
|
} |
||||
|
} |
||||
|
</script> |
||||
|
|
||||
|
<style lang="scss" scoped> |
||||
|
|
||||
|
.sc-state { |
||||
|
display: inline-block; |
||||
|
background: '#409EFF'; |
||||
|
width: 6px; |
||||
|
height: 6px; |
||||
|
margin:0 5px 3px; |
||||
|
border-radius: 50%; |
||||
|
vertical-align: middle; |
||||
|
} |
||||
|
|
||||
|
.sc-status-processing { |
||||
|
position: relative; |
||||
|
|
||||
|
&:after { |
||||
|
position: absolute; |
||||
|
top: 0px; |
||||
|
left: 0px; |
||||
|
width: 100%; |
||||
|
height: 100%; |
||||
|
border-radius: 50%; |
||||
|
background: inherit; |
||||
|
content: ''; |
||||
|
animation: warns 1.2s ease-in-out infinite; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
.sc-state-bg--primary { |
||||
|
background: #409EFF; |
||||
|
} |
||||
|
|
||||
|
.sc-state-bg--success { |
||||
|
background: #67C23A; |
||||
|
} |
||||
|
|
||||
|
.sc-state-bg--warning { |
||||
|
background: #E6A23C; |
||||
|
} |
||||
|
|
||||
|
.sc-state-bg--danger { |
||||
|
background: #F56C6C; |
||||
|
} |
||||
|
|
||||
|
.sc-state-bg--info { |
||||
|
background: #909399; |
||||
|
} |
||||
|
@-webkit-keyframes warns { |
||||
|
0% { |
||||
|
transform: scale(0.5); |
||||
|
opacity: 1; |
||||
|
} |
||||
|
|
||||
|
30% { |
||||
|
opacity: 0.7; |
||||
|
} |
||||
|
|
||||
|
100% { |
||||
|
transform: scale(2.5); |
||||
|
opacity: 0; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
@keyframes warns { |
||||
|
0% { |
||||
|
transform: scale(0.5); |
||||
|
opacity: 1; |
||||
|
} |
||||
|
|
||||
|
30% { |
||||
|
opacity: 0.7; |
||||
|
} |
||||
|
|
||||
|
100% { |
||||
|
transform: scale(2.5); |
||||
|
opacity: 0; |
||||
|
} |
||||
|
}</style> |
||||
|
After Width: | Height: | Size: 9.4 KiB |
|
After Width: | Height: | Size: 2.0 KiB |
|
After Width: | Height: | Size: 2.2 KiB |
|
After Width: | Height: | Size: 3.5 KiB |
|
After Width: | Height: | Size: 3.6 KiB |
|
After Width: | Height: | Size: 3.3 KiB |
|
After Width: | Height: | Size: 4.4 KiB |
|
After Width: | Height: | Size: 3.6 KiB |
|
After Width: | Height: | Size: 24 KiB |
@ -0,0 +1,613 @@ |
|||||
|
<html lang="zh"> |
||||
|
<head> |
||||
|
<meta charset="utf-8" /> |
||||
|
<meta name="viewport" content="width=device-width,initial-scale=1,minimum-scale=1,maximum-scale=1,user-scalable=no" /> |
||||
|
<script type="text/javascript" src="./rtc/adapter-latest.js"></script> |
||||
|
<script type="text/javascript" src='./js/uni.webview.js'></script> |
||||
|
<script type="text/javascript" src='./js/utils.js'></script> |
||||
|
<script type="text/javascript" src='./js/jsonly.js'></script> |
||||
|
<style> |
||||
|
body{ |
||||
|
padding:0; |
||||
|
margin:0; |
||||
|
background-image: url('image/wallpaper.png'); |
||||
|
background-size: contain; |
||||
|
} |
||||
|
|
||||
|
.webrtc-box{ |
||||
|
background: #666; |
||||
|
border-radius: 6px; |
||||
|
width:100%; |
||||
|
box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.1); |
||||
|
} |
||||
|
.localvideo{ |
||||
|
width:100vw; |
||||
|
height:100vh; |
||||
|
object-fit: cover; |
||||
|
} |
||||
|
.remotevideo{ |
||||
|
min-height: 160px; |
||||
|
width: 100px; |
||||
|
position: fixed; |
||||
|
top: 40px; |
||||
|
right: 15px; |
||||
|
z-index:10; |
||||
|
object-fit: cover; |
||||
|
} |
||||
|
|
||||
|
.call-user-box{ |
||||
|
position:fixed; |
||||
|
bottom: 20px; |
||||
|
width:100%; |
||||
|
} |
||||
|
|
||||
|
.call-user{ |
||||
|
display: flex; |
||||
|
justify-content: center; |
||||
|
align-items: center; |
||||
|
flex-direction: column; |
||||
|
margin-bottom:50px; |
||||
|
} |
||||
|
.call-user .avatar{ |
||||
|
width:60px; |
||||
|
height:60px; |
||||
|
object-fit: contain; |
||||
|
border-radius: 50%; |
||||
|
overflow: hidden; |
||||
|
} |
||||
|
.call-user .text{ |
||||
|
font-size:16px; |
||||
|
margin-top:15px; |
||||
|
color:#f6f6f6 |
||||
|
} |
||||
|
|
||||
|
.call-time{ |
||||
|
color:#f6f6f6; |
||||
|
font-size: 24px; |
||||
|
text-align: center; |
||||
|
} |
||||
|
|
||||
|
.calling-button{ |
||||
|
display: flex; |
||||
|
justify-content: space-around; |
||||
|
padding: 20px; |
||||
|
} |
||||
|
.calling-button .button{ |
||||
|
display: flex; |
||||
|
flex-direction: column; |
||||
|
align-items: center; |
||||
|
justify-content: center; |
||||
|
} |
||||
|
.calling-button .button .image{ |
||||
|
width:60px; |
||||
|
height:60px; |
||||
|
margin-bottom: 10px; |
||||
|
} |
||||
|
.calling-button .button .text{ |
||||
|
color:#f6f6f6; |
||||
|
} |
||||
|
.calling-button .switch-btn .text{ |
||||
|
font-size:12px !important; |
||||
|
} |
||||
|
.calling-button .button .image-icon{ |
||||
|
width:40px; |
||||
|
height:40px; |
||||
|
margin-bottom: 10px; |
||||
|
} |
||||
|
|
||||
|
</style> |
||||
|
</head> |
||||
|
<body> |
||||
|
<div id="app"> |
||||
|
<div class="webrtc-box"> |
||||
|
<audio id="music1"> |
||||
|
<source src="./voice/calling.mp3"> |
||||
|
</audio> |
||||
|
<video v-show="localStream && is_video" class="localvideo" ref="localvideo" x5-video-player-fullscreen="true" autoplay x5-playsinline playsinline webkit-playsinline @click="displayBtn = !displayBtn" poster="./image/wallpaper.png"></video> |
||||
|
<video v-show="remoteStream && is_video" class="remotevideo" ref="remotevideo" x5-video-player-fullscreen="true" autoplay x5-playsinline playsinline webkit-playsinline @click="changeVideo()" poster="./image/wallpaper.png"></video> |
||||
|
<div class="call-user-box" v-if="displayBtn"> |
||||
|
<div class="call-user" v-if="contact"> |
||||
|
<img class="avatar" v-if="status!=2 || !is_video" :src="contact.avatar" alt=""> |
||||
|
<div class="text"> |
||||
|
<b v-if="!is_video && status==2">{{contact.displayName}}</b> |
||||
|
<span v-if="status!=2"> |
||||
|
<span v-if="status==3"> {{contact.displayName}} 正在请求与您{{is_video ? '视频' : '语音'}}通话</span> |
||||
|
<span v-else>您正对 <b>{{contact.displayName}}</b> 发起{{is_video ? '视频' : '语音'}}通话</span> |
||||
|
</span> |
||||
|
|
||||
|
</div> |
||||
|
</div> |
||||
|
<div class="call-time" v-if="callTime && status==2"> |
||||
|
{{setCallTime()}} |
||||
|
</div> |
||||
|
<div class="calling-button"> |
||||
|
<div class="button switch-btn" v-if="status<3" > |
||||
|
<img class="image-icon" :src="'./image/voice'+(voiceStatus ? '' : '-off')+'.png'" @click="switchVoice()"/> |
||||
|
<div class="text">{{ voiceStatus ? '关闭' : '开启'}}麦克风</div> |
||||
|
</div> |
||||
|
<div class="button" v-if="status!=0" > |
||||
|
<img class="image" src="./image/guaduan.png" @click="hangup(true)"/> |
||||
|
<div class="text">挂断</div> |
||||
|
</div> |
||||
|
<div class="button" v-if="status==3" > |
||||
|
<img class="image" src="./image/jieting.png" @click="answer()"/> |
||||
|
<div class="text">接听</div> |
||||
|
</div> |
||||
|
<div class="button switch-btn" v-if="status<3" > |
||||
|
<template v-if="is_video"> |
||||
|
<img class="image-icon" :src="'./image/video.png'" @click="exchangeVideo()"/> |
||||
|
<div class="text">切换摄像头</div> |
||||
|
</template> |
||||
|
<template v-else> |
||||
|
<img class="image-icon" :src="'./image/speaker'+(speaker ? '' : '-off')+'.png'" @click="speakBtn()"/> |
||||
|
<div class="text">{{ speaker ? '关闭' : '开启'}}扬声器</div> |
||||
|
</template> |
||||
|
</div> |
||||
|
</div> |
||||
|
</div> |
||||
|
</div> |
||||
|
</div> |
||||
|
</body> |
||||
|
<script type="text/javascript" src='./js/vue.js'></script> |
||||
|
<script> |
||||
|
const params=parseUrl(window.location.href); |
||||
|
const opt=JSON.parse(decodeURIComponent(params.stun)); |
||||
|
const config = { |
||||
|
'iceServers': [{ |
||||
|
'urls': ['stun:stun.xten.com', 'stun:stun.l.google.com:19302', 'stun:stun1.l.google.com:19302', |
||||
|
'stun:stun2.l.google.com:19302', 'stun:stun3.l.google.com:19302', 'stun:stun4.l.google.com:19302' |
||||
|
] |
||||
|
},{ |
||||
|
'urls': opt.stun ? opt.stun : ['stun:stun.callwithus.com'], // 自己搭建服务器地址 |
||||
|
"username":opt.stunUser ? opt.stunUser : '', |
||||
|
"credential":opt.stunPass ? opt.stunPass : '' |
||||
|
} |
||||
|
], |
||||
|
}; |
||||
|
const Counter = { |
||||
|
data() { |
||||
|
return { |
||||
|
displayBtn:true, |
||||
|
platform:params.platform, |
||||
|
status: 0, //状态0,默认,1:拨号中,2通话中,3来电中,4忙线 |
||||
|
pc: null, //pc实力化 |
||||
|
localVideo: "", //本地视频的DOM |
||||
|
remoteVideo: "", //远程视频的DOM |
||||
|
remoteStream: null, // 远端视频流 |
||||
|
localStream: null, // 本地视频流 |
||||
|
is_video: 0, //是否为视频通话 |
||||
|
videoStatus: true, //视频开启状态 |
||||
|
voiceStatus: true, //语音开启状态 |
||||
|
cutdown: 40, //拨号超时 |
||||
|
timer: null, //计时器 |
||||
|
offerParams:{}, |
||||
|
plus:null, |
||||
|
streamType:1, //视频通话展示方式 |
||||
|
facingMode:'user',//前置摄像头还是后置摄像头 user-前置 environment-后置 |
||||
|
headset : true, //麦克风 打开true 关闭false, |
||||
|
senders: null, // 数据流 |
||||
|
speaker:true, // 听筒 false 扬声器true |
||||
|
callTime:0, //通话时间 |
||||
|
callTimeDis:'', //通话时间展示 |
||||
|
timerIntervalId:null, //通话计时器 |
||||
|
contact:{ |
||||
|
id:params.target_id, |
||||
|
displayName:params.name, |
||||
|
avatar:params.avatar |
||||
|
} |
||||
|
}; |
||||
|
}, |
||||
|
mounted() { |
||||
|
this.pc = new RTCPeerConnection(config); |
||||
|
this.pc.ontrack = (event) => { |
||||
|
console.log(event,'接收视频流'); |
||||
|
if(this.localVideo){ |
||||
|
this.remoteStream = event.streams[0]; |
||||
|
setTimeout(()=>{ |
||||
|
this.streamType=2; |
||||
|
},50) |
||||
|
} |
||||
|
}; |
||||
|
|
||||
|
if (this.platform === 'app') { |
||||
|
document.addEventListener('plusready', () => { |
||||
|
console.log('设置扬声器') |
||||
|
this.plus = plus.audio.createPlayer(); |
||||
|
this.plus.setRoute(plus.audio.ROUTE_SPEAKER); |
||||
|
}); |
||||
|
} |
||||
|
|
||||
|
this.localVideo = this.$refs.localvideo; |
||||
|
this.remoteVideo = this.$refs.remotevideo; |
||||
|
|
||||
|
window.addEventListener('message', (e) => { |
||||
|
this.callMessagecallback(e) |
||||
|
}, false); |
||||
|
|
||||
|
window.getUniAppMessage = (arg) => { |
||||
|
const data = { |
||||
|
data: jsonly(arg) |
||||
|
} |
||||
|
this.callMessagecallback(data) |
||||
|
} |
||||
|
this.is_video = params.type==1 ? true : false; |
||||
|
|
||||
|
this.offerParams = this.is_video ? { |
||||
|
offerToRecieveAudio: 1, |
||||
|
offerToRecieveVideo: 1 |
||||
|
} : { |
||||
|
offerToRecieveAudio: 1, |
||||
|
offerToRecieveVideo: 0 |
||||
|
} |
||||
|
this.status=params.status |
||||
|
// 如果状态为1,表示拨打电话,并且calling状态为1的时候才是直接拨打; |
||||
|
if(this.status==1){ |
||||
|
if(params.calling==1){ |
||||
|
this.called(this.is_video) |
||||
|
} |
||||
|
}else{ |
||||
|
this.playMusicCall('state'); |
||||
|
} |
||||
|
}, |
||||
|
watch:{ |
||||
|
streamType(val){ |
||||
|
// 切换镜头位置 |
||||
|
if(val==1){ |
||||
|
this.localVideo.srcObject = this.localStream; |
||||
|
this.remoteVideo.srcObject = this.remoteStream; |
||||
|
this.localVideo.muted=true; |
||||
|
this.remoteVideo.muted=false; |
||||
|
}else{ |
||||
|
this.localVideo.srcObject = this.remoteStream; |
||||
|
this.remoteVideo.srcObject = this.localStream; |
||||
|
this.localVideo.muted=false; |
||||
|
this.remoteVideo.muted=true; |
||||
|
} |
||||
|
} |
||||
|
}, |
||||
|
methods: { |
||||
|
// 开始通话计时 |
||||
|
startTime() { |
||||
|
this.timerIntervalId=setInterval(()=>{ |
||||
|
this.callTime++ |
||||
|
},1000) |
||||
|
}, |
||||
|
// 设置通话时间 |
||||
|
setCallTime(){ |
||||
|
let time=this.callTime; |
||||
|
const hours = Math.floor(time / 3600); |
||||
|
const minutes = Math.floor((time - (hours * 3600)) / 60); |
||||
|
const seconds = time - (hours * 3600) - (minutes * 60); |
||||
|
return `${hours.toString().padStart(2, '0')}:${minutes.toString().padStart(2, '0')}:${seconds.toString().padStart(2, '0')}`; |
||||
|
}, |
||||
|
// 视频电话初始化本地视频 |
||||
|
initLocalStream(call_id, is_video) { |
||||
|
let video=false; |
||||
|
navigator.mediaDevices.enumerateDevices().then(devices => { |
||||
|
const videoDevices = devices.filter(device => device.kind === 'videoinput'); |
||||
|
if(videoDevices.length && is_video){ |
||||
|
video = { |
||||
|
width: window.screen.height, |
||||
|
height: window.screen.width |
||||
|
} |
||||
|
} |
||||
|
navigator.mediaDevices.getUserMedia({ |
||||
|
video: video, |
||||
|
audio: {echoCancellation: true} |
||||
|
}).then((stream) => { |
||||
|
this.localStream = stream; |
||||
|
// 同步音频 |
||||
|
stream.getTracks().forEach((track) => { |
||||
|
this.pc.addTrack(track, stream); |
||||
|
}); |
||||
|
this.localVideo.srcObject = this.localStream; |
||||
|
// 把自己的视频静音 |
||||
|
this.localVideo.muted = true; |
||||
|
if(call_id){ |
||||
|
this.postMsg({ |
||||
|
event:'calling', |
||||
|
status:3, |
||||
|
code:901 |
||||
|
}); |
||||
|
// 计时器,如果一段时间没有接听则自动挂断 |
||||
|
this.timer = setInterval(() => { |
||||
|
this.cutdown--; |
||||
|
if (this.cutdown == 0) { |
||||
|
this.hangup(true); |
||||
|
} |
||||
|
}, 1000) |
||||
|
}else{ |
||||
|
// 告诉对方已经接听电话 |
||||
|
this.postMsg({ event: 'acceptRtc',code:904}); |
||||
|
} |
||||
|
// 监听远程媒体流 |
||||
|
}).catch((e) => { |
||||
|
this.postMsg({ |
||||
|
event: 'mediaDevices', |
||||
|
}) |
||||
|
}); |
||||
|
}); |
||||
|
}, |
||||
|
// 拨打电话 |
||||
|
called(is_video) { |
||||
|
this.is_video = is_video; |
||||
|
this.initLocalStream(true, is_video); |
||||
|
this.playMusicCall('state'); |
||||
|
}, |
||||
|
// 接听电话 |
||||
|
answer() { |
||||
|
this.status = 2; |
||||
|
this.initLocalStream(false, this.is_video); |
||||
|
this.playMusicCall('close'); |
||||
|
this.startTime(); |
||||
|
}, |
||||
|
// 挂断电话 |
||||
|
hangup(btn) { |
||||
|
clearInterval(this.timer); |
||||
|
clearInterval(this.timerIntervalId); |
||||
|
if(this.status!=2){ |
||||
|
this.playMusicCall('close'); |
||||
|
} |
||||
|
if (this.status) { |
||||
|
this.closeLocalMedia(); //关闭本地媒体 |
||||
|
this.remoteStream=null; //关闭远程媒体 |
||||
|
} |
||||
|
// 通话取消 |
||||
|
let code=902; |
||||
|
// 通话中挂断 |
||||
|
if(this.status==2 ){ |
||||
|
code=906 |
||||
|
// 拒绝挂断 |
||||
|
}else if(this.status==3 ){ |
||||
|
code=903 |
||||
|
//对方忙线中 |
||||
|
}else if(this.status==4 ){ |
||||
|
code=907 |
||||
|
} |
||||
|
this.postMsg({ |
||||
|
event:'hangup', |
||||
|
isbtn:btn, |
||||
|
callTime:this.callTime, |
||||
|
code:code |
||||
|
}) |
||||
|
}, |
||||
|
// 关闭本地媒体 |
||||
|
closeLocalMedia() { |
||||
|
if (this.localStream && this.localStream.getTracks()) { |
||||
|
this.localStream.getTracks().forEach((track) => { |
||||
|
track.stop(); |
||||
|
}); |
||||
|
} |
||||
|
this.localStream = null; |
||||
|
}, |
||||
|
// 打开或关闭声音 |
||||
|
switchVoice() { |
||||
|
if (this.localStream == null) { |
||||
|
alert('请打开音视频'); |
||||
|
return false; |
||||
|
} |
||||
|
const tracks = this.localStream.getTracks(); |
||||
|
if (this.voiceStatus) { |
||||
|
tracks.forEach(track => { |
||||
|
if (track.kind === 'audio') { |
||||
|
track.enabled = false |
||||
|
} |
||||
|
}); |
||||
|
this.voiceStatus = false; |
||||
|
} else { |
||||
|
tracks.forEach(track => { |
||||
|
if (track.kind === 'audio') { |
||||
|
track.enabled = true |
||||
|
} |
||||
|
}); |
||||
|
this.voiceStatus = true; |
||||
|
} |
||||
|
}, |
||||
|
// 临时开、关视频 |
||||
|
switchVideo() { |
||||
|
if (this.localStream == null) { |
||||
|
alert('请打开音视频'); |
||||
|
return false; |
||||
|
} |
||||
|
const tracks = this.localStream.getTracks(); |
||||
|
if (this.videoStatus) { |
||||
|
tracks.forEach(track => { |
||||
|
if (track.kind === 'video') { |
||||
|
track.enabled = false |
||||
|
} |
||||
|
}); |
||||
|
this.videoStatus = false; |
||||
|
} else { |
||||
|
tracks.forEach(track => { |
||||
|
if (track.kind === 'video') { |
||||
|
track.enabled = true |
||||
|
} |
||||
|
}); |
||||
|
this.videoStatus = true; |
||||
|
} |
||||
|
}, |
||||
|
// 切换前后摄像头 |
||||
|
exchangeVideo() { |
||||
|
this.localStream.getTracks().forEach(track => track.stop()); |
||||
|
if (this.facingMode == 'user') this.facingMode = 'environment' |
||||
|
else this.facingMode = 'user' |
||||
|
navigator.mediaDevices.getUserMedia({ |
||||
|
video: { |
||||
|
width: window.screen.height, |
||||
|
height: window.screen.width, |
||||
|
facingMode: { |
||||
|
exact: this.facingMode |
||||
|
} |
||||
|
}, |
||||
|
audio: { |
||||
|
echoCancellation: true, |
||||
|
} |
||||
|
}).then((mediastream) => { |
||||
|
this.senders = this.pc.getSenders() |
||||
|
let videoTrack = mediastream.getVideoTracks()[0]; |
||||
|
let audioTrack = mediastream.getAudioTracks()[0]; |
||||
|
var sender = this.senders.find((s) => { |
||||
|
return s.track.kind == 'video'; |
||||
|
}); |
||||
|
var sender2 = this.senders.find((s) => { |
||||
|
return s.track.kind == 'audio'; |
||||
|
}); |
||||
|
sender.replaceTrack(videoTrack); |
||||
|
sender2.replaceTrack(audioTrack); |
||||
|
if (this.streamType === 2) this.remoteVideo.srcObject = mediastream; |
||||
|
else this.localVideo.srcObject = mediastream |
||||
|
this.localStream = mediastream |
||||
|
if(this.voiceStatus==false){ |
||||
|
this.voiceStatus=true; |
||||
|
this.switchVoice(); |
||||
|
} |
||||
|
if(this.speaker){ |
||||
|
this.speaker = !this.speaker |
||||
|
}else{ |
||||
|
this.speaker = !this.speaker |
||||
|
} |
||||
|
}) |
||||
|
}, |
||||
|
// 播放响铃 |
||||
|
playMusicCall(type) { |
||||
|
var audio = document.getElementById("music1"); |
||||
|
if(type=='close' && !audio.paused){ |
||||
|
audio.pause(); // 暂停 |
||||
|
return; |
||||
|
} |
||||
|
if (type === "state") { |
||||
|
audio.loop = true; |
||||
|
} else { |
||||
|
audio.loop = false; |
||||
|
} |
||||
|
|
||||
|
if (audio.paused) { |
||||
|
audio.play(); // 播放 |
||||
|
} else { |
||||
|
audio.pause(); // 暂停 |
||||
|
} |
||||
|
}, |
||||
|
// 向uniapp发送消息,页面通讯 |
||||
|
postMsg(data) { |
||||
|
if (this.platform === 'app') { |
||||
|
uni.postMessage({ |
||||
|
data: data |
||||
|
}) |
||||
|
} else { |
||||
|
window.parent.postMessage(data) |
||||
|
} |
||||
|
}, |
||||
|
// 接收websocket发送过来的消息,由uniapp接收后传输到当前页面 |
||||
|
callMessagecallback(msg){ |
||||
|
let e=msg.data; |
||||
|
switch (e.event) { |
||||
|
case "calling": |
||||
|
console.log('发起通话...'); |
||||
|
this.called(this.is_video); |
||||
|
break; |
||||
|
case "hangup": |
||||
|
this.hangup(false); |
||||
|
break; |
||||
|
case "busy": |
||||
|
this.status=4; |
||||
|
this.hangup(false); |
||||
|
break; |
||||
|
case "acceptRtc": //已经接听,创建offer并发送 |
||||
|
this.status = 2; |
||||
|
clearInterval(this.timer); |
||||
|
this.startTime(); |
||||
|
this.playMusicCall(); |
||||
|
this.createOffer() |
||||
|
break; |
||||
|
case "turndown": |
||||
|
break; |
||||
|
case "answer": |
||||
|
//同步answer信息... |
||||
|
this.pc.setRemoteDescription(new RTCSessionDescription({ |
||||
|
type: 'answer', |
||||
|
sdp: e.sdp |
||||
|
})); |
||||
|
break; |
||||
|
case "iceCandidate": |
||||
|
setTimeout(()=>{ |
||||
|
// 添加ice完成通话连接 |
||||
|
if (typeof(e.iceCandidate) === 'object') { |
||||
|
this.pc.addIceCandidate(new RTCIceCandidate(e.iceCandidate)); |
||||
|
} else { |
||||
|
this.pc.addIceCandidate(new RTCIceCandidate(JSON.parse(e.iceCandidate))); |
||||
|
} |
||||
|
},100) |
||||
|
break; |
||||
|
case "offer": |
||||
|
this.pc.setRemoteDescription(new RTCSessionDescription({ |
||||
|
type: 'offer', |
||||
|
sdp: e.sdp |
||||
|
})); |
||||
|
this.createAnswer(); |
||||
|
break; |
||||
|
|
||||
|
} |
||||
|
}, |
||||
|
// 创建offer-sdp |
||||
|
createOffer() { |
||||
|
this.pc.createOffer(this.offerParams).then((offer) => { |
||||
|
this.pc.setLocalDescription(offer); |
||||
|
this.postMsg({ |
||||
|
event: 'offer', |
||||
|
sdp: offer.sdp |
||||
|
}, '*'); |
||||
|
}); |
||||
|
// 创建offer需要监听ice流 |
||||
|
this.onicecandidate(); |
||||
|
}, |
||||
|
// 创建应答sdp |
||||
|
createAnswer() { |
||||
|
this.pc.createAnswer(this.offerParams).then((answer) => { |
||||
|
this.pc.setLocalDescription(answer); |
||||
|
this.postMsg({ |
||||
|
event: 'answer', |
||||
|
sdp: answer.sdp |
||||
|
}, '*'); |
||||
|
this.onicecandidate(); |
||||
|
}); |
||||
|
}, |
||||
|
onicecandidate(){ |
||||
|
this.pc.onicecandidate = (event) => { |
||||
|
var iceCandidate = event.candidate; |
||||
|
if (iceCandidate) { |
||||
|
this.postMsg({ |
||||
|
event: 'iceCandidate', |
||||
|
iceCandidate: JSON.parse(JSON.stringify(iceCandidate)) |
||||
|
}, '*'); |
||||
|
} |
||||
|
}; |
||||
|
}, |
||||
|
//切换视频显示位置 |
||||
|
changeVideo(){ |
||||
|
this.streamType==1 ? this.streamType=2 : this.streamType=1; |
||||
|
}, |
||||
|
//打开关闭扬声器 h5端就是静音 ROUTE_EARPIECE 听筒 ROUTE_SPEAKER 扬声器 |
||||
|
speakBtn() { |
||||
|
let isMuted=false; |
||||
|
let speaker=plus.audio.ROUTE_SPEAKER; |
||||
|
this.localVideo.muted = true; |
||||
|
if (this.speaker) { //扬声器 => 听筒 |
||||
|
isMuted=true; |
||||
|
speaker=plus.audio.ROUTE_EARPIECE |
||||
|
} |
||||
|
if (this.platform === 'h5') { |
||||
|
this.localVideo.muted=isMuted; |
||||
|
} |
||||
|
if (this.platform === 'app') { |
||||
|
this.plus.setRoute(speaker); |
||||
|
this.localVideo.muted = false; |
||||
|
} |
||||
|
this.speaker = !this.speaker |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
const app = Vue.createApp(Counter); |
||||
|
app.mount('#app'); |
||||
|
</script> |
||||
|
</html> |
||||
@ -0,0 +1,275 @@ |
|||||
|
var at, // The index of the current character
|
||||
|
ch, // The current character
|
||||
|
escapee = { |
||||
|
'"': '"', |
||||
|
'\\': '\\', |
||||
|
'/': '/', |
||||
|
b: '\b', |
||||
|
f: '\f', |
||||
|
n: '\n', |
||||
|
r: '\r', |
||||
|
t: '\t' |
||||
|
}, |
||||
|
text, |
||||
|
|
||||
|
error = function(m) { |
||||
|
// Call error when something is wrong.
|
||||
|
throw { |
||||
|
name: 'SyntaxError', |
||||
|
message: m, |
||||
|
at: at, |
||||
|
text: text |
||||
|
}; |
||||
|
}, |
||||
|
|
||||
|
next = function(c) { |
||||
|
// If a c parameter is provided, verify that it matches the current character.
|
||||
|
if (c && c !== ch) { |
||||
|
error("Expected '" + c + "' instead of '" + ch + "'"); |
||||
|
} |
||||
|
|
||||
|
// Get the next character. When there are no more characters,
|
||||
|
// return the empty string.
|
||||
|
|
||||
|
ch = text.charAt(at); |
||||
|
at += 1; |
||||
|
return ch; |
||||
|
}, |
||||
|
|
||||
|
number = function() { |
||||
|
// Parse a number value.
|
||||
|
var number, |
||||
|
string = ''; |
||||
|
|
||||
|
if (ch === '-') { |
||||
|
string = '-'; |
||||
|
next('-'); |
||||
|
} |
||||
|
while (ch >= '0' && ch <= '9') { |
||||
|
string += ch; |
||||
|
next(); |
||||
|
} |
||||
|
if (ch === '.') { |
||||
|
string += '.'; |
||||
|
while (next() && ch >= '0' && ch <= '9') { |
||||
|
string += ch; |
||||
|
} |
||||
|
} |
||||
|
if (ch === 'e' || ch === 'E') { |
||||
|
string += ch; |
||||
|
next(); |
||||
|
if (ch === '-' || ch === '+') { |
||||
|
string += ch; |
||||
|
next(); |
||||
|
} |
||||
|
while (ch >= '0' && ch <= '9') { |
||||
|
string += ch; |
||||
|
next(); |
||||
|
} |
||||
|
} |
||||
|
number = +string; |
||||
|
if (!isFinite(number)) { |
||||
|
error("Bad number"); |
||||
|
} else { |
||||
|
return number; |
||||
|
} |
||||
|
}, |
||||
|
|
||||
|
string = function() { |
||||
|
// Parse a string value.
|
||||
|
var hex, |
||||
|
i, |
||||
|
string = '', |
||||
|
uffff; |
||||
|
|
||||
|
// When parsing for string values, we must look for " and \ characters.
|
||||
|
if (ch === '"') { |
||||
|
while (next()) { |
||||
|
if (ch === '"') { |
||||
|
next(); |
||||
|
return string; |
||||
|
} else if (ch === '\\') { |
||||
|
next(); |
||||
|
if (ch === 'u') { |
||||
|
uffff = 0; |
||||
|
for (i = 0; i < 4; i += 1) { |
||||
|
hex = parseInt(next(), 16); |
||||
|
if (!isFinite(hex)) { |
||||
|
break; |
||||
|
} |
||||
|
uffff = uffff * 16 + hex; |
||||
|
} |
||||
|
string += String.fromCharCode(uffff); |
||||
|
} else if (typeof escapee[ch] === 'string') { |
||||
|
string += escapee[ch]; |
||||
|
} else { |
||||
|
break; |
||||
|
} |
||||
|
} else { |
||||
|
string += ch; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
error("Bad string"); |
||||
|
}, |
||||
|
|
||||
|
white = function() { |
||||
|
|
||||
|
// Skip whitespace.
|
||||
|
|
||||
|
while (ch && ch <= ' ') { |
||||
|
next(); |
||||
|
} |
||||
|
}, |
||||
|
|
||||
|
word = function() { |
||||
|
|
||||
|
// true, false, or null.
|
||||
|
|
||||
|
switch (ch) { |
||||
|
case 't': |
||||
|
next('t'); |
||||
|
next('r'); |
||||
|
next('u'); |
||||
|
next('e'); |
||||
|
return true; |
||||
|
case 'f': |
||||
|
next('f'); |
||||
|
next('a'); |
||||
|
next('l'); |
||||
|
next('s'); |
||||
|
next('e'); |
||||
|
return false; |
||||
|
case 'n': |
||||
|
next('n'); |
||||
|
next('u'); |
||||
|
next('l'); |
||||
|
next('l'); |
||||
|
return null; |
||||
|
} |
||||
|
error("Unexpected '" + ch + "'"); |
||||
|
}, |
||||
|
|
||||
|
value, // Place holder for the value function.
|
||||
|
|
||||
|
array = function() { |
||||
|
|
||||
|
// Parse an array value.
|
||||
|
|
||||
|
var array = []; |
||||
|
|
||||
|
if (ch === '[') { |
||||
|
next('['); |
||||
|
white(); |
||||
|
if (ch === ']') { |
||||
|
next(']'); |
||||
|
return array; // empty array
|
||||
|
} |
||||
|
while (ch) { |
||||
|
array.push(value()); |
||||
|
white(); |
||||
|
if (ch === ']') { |
||||
|
next(']'); |
||||
|
return array; |
||||
|
} |
||||
|
next(','); |
||||
|
white(); |
||||
|
} |
||||
|
} |
||||
|
error("Bad array"); |
||||
|
}, |
||||
|
|
||||
|
object = function() { |
||||
|
|
||||
|
// Parse an object value.
|
||||
|
|
||||
|
var key, |
||||
|
object = {}; |
||||
|
|
||||
|
if (ch === '{') { |
||||
|
next('{'); |
||||
|
white(); |
||||
|
if (ch === '}') { |
||||
|
next('}'); |
||||
|
return object; // empty object
|
||||
|
} |
||||
|
while (ch) { |
||||
|
key = string(); |
||||
|
white(); |
||||
|
next(':'); |
||||
|
if (Object.hasOwnProperty.call(object, key)) { |
||||
|
error('Duplicate key "' + key + '"'); |
||||
|
} |
||||
|
object[key] = value(); |
||||
|
white(); |
||||
|
if (ch === '}') { |
||||
|
next('}'); |
||||
|
return object; |
||||
|
} |
||||
|
next(','); |
||||
|
white(); |
||||
|
} |
||||
|
} |
||||
|
error("Bad object"); |
||||
|
}; |
||||
|
|
||||
|
value = function() { |
||||
|
|
||||
|
// Parse a JSON value. It could be an object, an array, a string, a number,
|
||||
|
// or a word.
|
||||
|
|
||||
|
white(); |
||||
|
switch (ch) { |
||||
|
case '{': |
||||
|
return object(); |
||||
|
case '[': |
||||
|
return array(); |
||||
|
case '"': |
||||
|
return string(); |
||||
|
case '-': |
||||
|
return number(); |
||||
|
default: |
||||
|
return ch >= '0' && ch <= '9' ? number() : word(); |
||||
|
} |
||||
|
}; |
||||
|
|
||||
|
// Return the json_parse function. It will have access to all of the above
|
||||
|
// functions and variables.
|
||||
|
|
||||
|
const jsonly = function(source, reviver) { |
||||
|
var result; |
||||
|
|
||||
|
text = source; |
||||
|
at = 0; |
||||
|
ch = ' '; |
||||
|
result = value(); |
||||
|
white(); |
||||
|
if (ch) { |
||||
|
error("Syntax error"); |
||||
|
} |
||||
|
|
||||
|
// If there is a reviver function, we recursively walk the new structure,
|
||||
|
// passing each name/value pair to the reviver function for possible
|
||||
|
// transformation, starting with a temporary root object that holds the result
|
||||
|
// in an empty key. If there is not a reviver function, we simply return the
|
||||
|
// result.
|
||||
|
|
||||
|
return typeof reviver === 'function' ? (function walk(holder, key) { |
||||
|
var k, v, value = holder[key]; |
||||
|
if (value && typeof value === 'object') { |
||||
|
for (k in value) { |
||||
|
if (Object.prototype.hasOwnProperty.call(value, k)) { |
||||
|
v = walk(value, k); |
||||
|
if (v !== undefined) { |
||||
|
value[k] = v; |
||||
|
} else { |
||||
|
delete value[k]; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
return reviver.call(holder, key, value); |
||||
|
}({ |
||||
|
'': result |
||||
|
}, '')) : result; |
||||
|
}; |
||||
@ -0,0 +1,11 @@ |
|||||
|
//序列化url,将url链接后面的get参数序列化成json对象
|
||||
|
function parseUrl(url){ |
||||
|
var param=url.substring(url.indexOf("?")+1); |
||||
|
var paramArr=param.split("&"); |
||||
|
var urlArr={}; |
||||
|
for (let i = 0; i < paramArr.length; i++) { |
||||
|
urlArr[paramArr[i].split("=")[0]] = decodeURI(paramArr[i].split("=")[1]); |
||||
|
// 将数组元素中'='左边的内容作为对象的属性名,'='右边的内容作为对象对应属性的属性值
|
||||
|
} |
||||
|
return urlArr; |
||||
|
} |
||||
@ -0,0 +1,702 @@ |
|||||
|
/* |
||||
|
* Copyright (c) 2016 The WebRTC project authors. All Rights Reserved. |
||||
|
* |
||||
|
* Use of this source code is governed by a BSD-style license |
||||
|
* that can be found in the LICENSE file in the root of the source |
||||
|
* tree. |
||||
|
*/ |
||||
|
/* eslint-env node */ |
||||
|
'use strict'; |
||||
|
import * as utils from '../utils.js'; |
||||
|
|
||||
|
export {shimGetUserMedia} from './getusermedia'; |
||||
|
export {shimGetDisplayMedia} from './getdisplaymedia'; |
||||
|
|
||||
|
export function shimMediaStream(window) { |
||||
|
window.MediaStream = window.MediaStream || window.webkitMediaStream; |
||||
|
} |
||||
|
|
||||
|
export function shimOnTrack(window) { |
||||
|
if (typeof window === 'object' && window.RTCPeerConnection && !('ontrack' in |
||||
|
window.RTCPeerConnection.prototype)) { |
||||
|
Object.defineProperty(window.RTCPeerConnection.prototype, 'ontrack', { |
||||
|
get() { |
||||
|
return this._ontrack; |
||||
|
}, |
||||
|
set(f) { |
||||
|
if (this._ontrack) { |
||||
|
this.removeEventListener('track', this._ontrack); |
||||
|
} |
||||
|
this.addEventListener('track', this._ontrack = f); |
||||
|
}, |
||||
|
enumerable: true, |
||||
|
configurable: true |
||||
|
}); |
||||
|
const origSetRemoteDescription = |
||||
|
window.RTCPeerConnection.prototype.setRemoteDescription; |
||||
|
window.RTCPeerConnection.prototype.setRemoteDescription = |
||||
|
function setRemoteDescription() { |
||||
|
if (!this._ontrackpoly) { |
||||
|
this._ontrackpoly = (e) => { |
||||
|
// onaddstream does not fire when a track is added to an existing
|
||||
|
// stream. But stream.onaddtrack is implemented so we use that.
|
||||
|
e.stream.addEventListener('addtrack', te => { |
||||
|
let receiver; |
||||
|
if (window.RTCPeerConnection.prototype.getReceivers) { |
||||
|
receiver = this.getReceivers() |
||||
|
.find(r => r.track && r.track.id === te.track.id); |
||||
|
} else { |
||||
|
receiver = {track: te.track}; |
||||
|
} |
||||
|
|
||||
|
const event = new Event('track'); |
||||
|
event.track = te.track; |
||||
|
event.receiver = receiver; |
||||
|
event.transceiver = {receiver}; |
||||
|
event.streams = [e.stream]; |
||||
|
this.dispatchEvent(event); |
||||
|
}); |
||||
|
e.stream.getTracks().forEach(track => { |
||||
|
let receiver; |
||||
|
if (window.RTCPeerConnection.prototype.getReceivers) { |
||||
|
receiver = this.getReceivers() |
||||
|
.find(r => r.track && r.track.id === track.id); |
||||
|
} else { |
||||
|
receiver = {track}; |
||||
|
} |
||||
|
const event = new Event('track'); |
||||
|
event.track = track; |
||||
|
event.receiver = receiver; |
||||
|
event.transceiver = {receiver}; |
||||
|
event.streams = [e.stream]; |
||||
|
this.dispatchEvent(event); |
||||
|
}); |
||||
|
}; |
||||
|
this.addEventListener('addstream', this._ontrackpoly); |
||||
|
} |
||||
|
return origSetRemoteDescription.apply(this, arguments); |
||||
|
}; |
||||
|
} else { |
||||
|
// even if RTCRtpTransceiver is in window, it is only used and
|
||||
|
// emitted in unified-plan. Unfortunately this means we need
|
||||
|
// to unconditionally wrap the event.
|
||||
|
utils.wrapPeerConnectionEvent(window, 'track', e => { |
||||
|
if (!e.transceiver) { |
||||
|
Object.defineProperty(e, 'transceiver', |
||||
|
{value: {receiver: e.receiver}}); |
||||
|
} |
||||
|
return e; |
||||
|
}); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
export function shimGetSendersWithDtmf(window) { |
||||
|
// Overrides addTrack/removeTrack, depends on shimAddTrackRemoveTrack.
|
||||
|
if (typeof window === 'object' && window.RTCPeerConnection && |
||||
|
!('getSenders' in window.RTCPeerConnection.prototype) && |
||||
|
'createDTMFSender' in window.RTCPeerConnection.prototype) { |
||||
|
const shimSenderWithDtmf = function(pc, track) { |
||||
|
return { |
||||
|
track, |
||||
|
get dtmf() { |
||||
|
if (this._dtmf === undefined) { |
||||
|
if (track.kind === 'audio') { |
||||
|
this._dtmf = pc.createDTMFSender(track); |
||||
|
} else { |
||||
|
this._dtmf = null; |
||||
|
} |
||||
|
} |
||||
|
return this._dtmf; |
||||
|
}, |
||||
|
_pc: pc |
||||
|
}; |
||||
|
}; |
||||
|
|
||||
|
// augment addTrack when getSenders is not available.
|
||||
|
if (!window.RTCPeerConnection.prototype.getSenders) { |
||||
|
window.RTCPeerConnection.prototype.getSenders = function getSenders() { |
||||
|
this._senders = this._senders || []; |
||||
|
return this._senders.slice(); // return a copy of the internal state.
|
||||
|
}; |
||||
|
const origAddTrack = window.RTCPeerConnection.prototype.addTrack; |
||||
|
window.RTCPeerConnection.prototype.addTrack = |
||||
|
function addTrack(track, stream) { |
||||
|
let sender = origAddTrack.apply(this, arguments); |
||||
|
if (!sender) { |
||||
|
sender = shimSenderWithDtmf(this, track); |
||||
|
this._senders.push(sender); |
||||
|
} |
||||
|
return sender; |
||||
|
}; |
||||
|
|
||||
|
const origRemoveTrack = window.RTCPeerConnection.prototype.removeTrack; |
||||
|
window.RTCPeerConnection.prototype.removeTrack = |
||||
|
function removeTrack(sender) { |
||||
|
origRemoveTrack.apply(this, arguments); |
||||
|
const idx = this._senders.indexOf(sender); |
||||
|
if (idx !== -1) { |
||||
|
this._senders.splice(idx, 1); |
||||
|
} |
||||
|
}; |
||||
|
} |
||||
|
const origAddStream = window.RTCPeerConnection.prototype.addStream; |
||||
|
window.RTCPeerConnection.prototype.addStream = function addStream(stream) { |
||||
|
this._senders = this._senders || []; |
||||
|
origAddStream.apply(this, [stream]); |
||||
|
stream.getTracks().forEach(track => { |
||||
|
this._senders.push(shimSenderWithDtmf(this, track)); |
||||
|
}); |
||||
|
}; |
||||
|
|
||||
|
const origRemoveStream = window.RTCPeerConnection.prototype.removeStream; |
||||
|
window.RTCPeerConnection.prototype.removeStream = |
||||
|
function removeStream(stream) { |
||||
|
this._senders = this._senders || []; |
||||
|
origRemoveStream.apply(this, [stream]); |
||||
|
|
||||
|
stream.getTracks().forEach(track => { |
||||
|
const sender = this._senders.find(s => s.track === track); |
||||
|
if (sender) { // remove sender
|
||||
|
this._senders.splice(this._senders.indexOf(sender), 1); |
||||
|
} |
||||
|
}); |
||||
|
}; |
||||
|
} else if (typeof window === 'object' && window.RTCPeerConnection && |
||||
|
'getSenders' in window.RTCPeerConnection.prototype && |
||||
|
'createDTMFSender' in window.RTCPeerConnection.prototype && |
||||
|
window.RTCRtpSender && |
||||
|
!('dtmf' in window.RTCRtpSender.prototype)) { |
||||
|
const origGetSenders = window.RTCPeerConnection.prototype.getSenders; |
||||
|
window.RTCPeerConnection.prototype.getSenders = function getSenders() { |
||||
|
const senders = origGetSenders.apply(this, []); |
||||
|
senders.forEach(sender => sender._pc = this); |
||||
|
return senders; |
||||
|
}; |
||||
|
|
||||
|
Object.defineProperty(window.RTCRtpSender.prototype, 'dtmf', { |
||||
|
get() { |
||||
|
if (this._dtmf === undefined) { |
||||
|
if (this.track.kind === 'audio') { |
||||
|
this._dtmf = this._pc.createDTMFSender(this.track); |
||||
|
} else { |
||||
|
this._dtmf = null; |
||||
|
} |
||||
|
} |
||||
|
return this._dtmf; |
||||
|
} |
||||
|
}); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
export function shimGetStats(window) { |
||||
|
if (!window.RTCPeerConnection) { |
||||
|
return; |
||||
|
} |
||||
|
|
||||
|
const origGetStats = window.RTCPeerConnection.prototype.getStats; |
||||
|
window.RTCPeerConnection.prototype.getStats = function getStats() { |
||||
|
const [selector, onSucc, onErr] = arguments; |
||||
|
|
||||
|
// If selector is a function then we are in the old style stats so just
|
||||
|
// pass back the original getStats format to avoid breaking old users.
|
||||
|
if (arguments.length > 0 && typeof selector === 'function') { |
||||
|
return origGetStats.apply(this, arguments); |
||||
|
} |
||||
|
|
||||
|
// When spec-style getStats is supported, return those when called with
|
||||
|
// either no arguments or the selector argument is null.
|
||||
|
if (origGetStats.length === 0 && (arguments.length === 0 || |
||||
|
typeof selector !== 'function')) { |
||||
|
return origGetStats.apply(this, []); |
||||
|
} |
||||
|
|
||||
|
const fixChromeStats_ = function(response) { |
||||
|
const standardReport = {}; |
||||
|
const reports = response.result(); |
||||
|
reports.forEach(report => { |
||||
|
const standardStats = { |
||||
|
id: report.id, |
||||
|
timestamp: report.timestamp, |
||||
|
type: { |
||||
|
localcandidate: 'local-candidate', |
||||
|
remotecandidate: 'remote-candidate' |
||||
|
}[report.type] || report.type |
||||
|
}; |
||||
|
report.names().forEach(name => { |
||||
|
standardStats[name] = report.stat(name); |
||||
|
}); |
||||
|
standardReport[standardStats.id] = standardStats; |
||||
|
}); |
||||
|
|
||||
|
return standardReport; |
||||
|
}; |
||||
|
|
||||
|
// shim getStats with maplike support
|
||||
|
const makeMapStats = function(stats) { |
||||
|
return new Map(Object.keys(stats).map(key => [key, stats[key]])); |
||||
|
}; |
||||
|
|
||||
|
if (arguments.length >= 2) { |
||||
|
const successCallbackWrapper_ = function(response) { |
||||
|
onSucc(makeMapStats(fixChromeStats_(response))); |
||||
|
}; |
||||
|
|
||||
|
return origGetStats.apply(this, [successCallbackWrapper_, |
||||
|
selector]); |
||||
|
} |
||||
|
|
||||
|
// promise-support
|
||||
|
return new Promise((resolve, reject) => { |
||||
|
origGetStats.apply(this, [ |
||||
|
function(response) { |
||||
|
resolve(makeMapStats(fixChromeStats_(response))); |
||||
|
}, reject]); |
||||
|
}).then(onSucc, onErr); |
||||
|
}; |
||||
|
} |
||||
|
|
||||
|
export function shimSenderReceiverGetStats(window) { |
||||
|
if (!(typeof window === 'object' && window.RTCPeerConnection && |
||||
|
window.RTCRtpSender && window.RTCRtpReceiver)) { |
||||
|
return; |
||||
|
} |
||||
|
|
||||
|
// shim sender stats.
|
||||
|
if (!('getStats' in window.RTCRtpSender.prototype)) { |
||||
|
const origGetSenders = window.RTCPeerConnection.prototype.getSenders; |
||||
|
if (origGetSenders) { |
||||
|
window.RTCPeerConnection.prototype.getSenders = function getSenders() { |
||||
|
const senders = origGetSenders.apply(this, []); |
||||
|
senders.forEach(sender => sender._pc = this); |
||||
|
return senders; |
||||
|
}; |
||||
|
} |
||||
|
|
||||
|
const origAddTrack = window.RTCPeerConnection.prototype.addTrack; |
||||
|
if (origAddTrack) { |
||||
|
window.RTCPeerConnection.prototype.addTrack = function addTrack() { |
||||
|
const sender = origAddTrack.apply(this, arguments); |
||||
|
sender._pc = this; |
||||
|
return sender; |
||||
|
}; |
||||
|
} |
||||
|
window.RTCRtpSender.prototype.getStats = function getStats() { |
||||
|
const sender = this; |
||||
|
return this._pc.getStats().then(result => |
||||
|
/* Note: this will include stats of all senders that |
||||
|
* send a track with the same id as sender.track as |
||||
|
* it is not possible to identify the RTCRtpSender. |
||||
|
*/ |
||||
|
utils.filterStats(result, sender.track, true)); |
||||
|
}; |
||||
|
} |
||||
|
|
||||
|
// shim receiver stats.
|
||||
|
if (!('getStats' in window.RTCRtpReceiver.prototype)) { |
||||
|
const origGetReceivers = window.RTCPeerConnection.prototype.getReceivers; |
||||
|
if (origGetReceivers) { |
||||
|
window.RTCPeerConnection.prototype.getReceivers = |
||||
|
function getReceivers() { |
||||
|
const receivers = origGetReceivers.apply(this, []); |
||||
|
receivers.forEach(receiver => receiver._pc = this); |
||||
|
return receivers; |
||||
|
}; |
||||
|
} |
||||
|
utils.wrapPeerConnectionEvent(window, 'track', e => { |
||||
|
e.receiver._pc = e.srcElement; |
||||
|
return e; |
||||
|
}); |
||||
|
window.RTCRtpReceiver.prototype.getStats = function getStats() { |
||||
|
const receiver = this; |
||||
|
return this._pc.getStats().then(result => |
||||
|
utils.filterStats(result, receiver.track, false)); |
||||
|
}; |
||||
|
} |
||||
|
|
||||
|
if (!('getStats' in window.RTCRtpSender.prototype && |
||||
|
'getStats' in window.RTCRtpReceiver.prototype)) { |
||||
|
return; |
||||
|
} |
||||
|
|
||||
|
// shim RTCPeerConnection.getStats(track).
|
||||
|
const origGetStats = window.RTCPeerConnection.prototype.getStats; |
||||
|
window.RTCPeerConnection.prototype.getStats = function getStats() { |
||||
|
if (arguments.length > 0 && |
||||
|
arguments[0] instanceof window.MediaStreamTrack) { |
||||
|
const track = arguments[0]; |
||||
|
let sender; |
||||
|
let receiver; |
||||
|
let err; |
||||
|
this.getSenders().forEach(s => { |
||||
|
if (s.track === track) { |
||||
|
if (sender) { |
||||
|
err = true; |
||||
|
} else { |
||||
|
sender = s; |
||||
|
} |
||||
|
} |
||||
|
}); |
||||
|
this.getReceivers().forEach(r => { |
||||
|
if (r.track === track) { |
||||
|
if (receiver) { |
||||
|
err = true; |
||||
|
} else { |
||||
|
receiver = r; |
||||
|
} |
||||
|
} |
||||
|
return r.track === track; |
||||
|
}); |
||||
|
if (err || (sender && receiver)) { |
||||
|
return Promise.reject(new DOMException( |
||||
|
'There are more than one sender or receiver for the track.', |
||||
|
'InvalidAccessError')); |
||||
|
} else if (sender) { |
||||
|
return sender.getStats(); |
||||
|
} else if (receiver) { |
||||
|
return receiver.getStats(); |
||||
|
} |
||||
|
return Promise.reject(new DOMException( |
||||
|
'There is no sender or receiver for the track.', |
||||
|
'InvalidAccessError')); |
||||
|
} |
||||
|
return origGetStats.apply(this, arguments); |
||||
|
}; |
||||
|
} |
||||
|
|
||||
|
export function shimAddTrackRemoveTrackWithNative(window) { |
||||
|
// shim addTrack/removeTrack with native variants in order to make
|
||||
|
// the interactions with legacy getLocalStreams behave as in other browsers.
|
||||
|
// Keeps a mapping stream.id => [stream, rtpsenders...]
|
||||
|
window.RTCPeerConnection.prototype.getLocalStreams = |
||||
|
function getLocalStreams() { |
||||
|
this._shimmedLocalStreams = this._shimmedLocalStreams || {}; |
||||
|
return Object.keys(this._shimmedLocalStreams) |
||||
|
.map(streamId => this._shimmedLocalStreams[streamId][0]); |
||||
|
}; |
||||
|
|
||||
|
const origAddTrack = window.RTCPeerConnection.prototype.addTrack; |
||||
|
window.RTCPeerConnection.prototype.addTrack = |
||||
|
function addTrack(track, stream) { |
||||
|
if (!stream) { |
||||
|
return origAddTrack.apply(this, arguments); |
||||
|
} |
||||
|
this._shimmedLocalStreams = this._shimmedLocalStreams || {}; |
||||
|
|
||||
|
const sender = origAddTrack.apply(this, arguments); |
||||
|
if (!this._shimmedLocalStreams[stream.id]) { |
||||
|
this._shimmedLocalStreams[stream.id] = [stream, sender]; |
||||
|
} else if (this._shimmedLocalStreams[stream.id].indexOf(sender) === -1) { |
||||
|
this._shimmedLocalStreams[stream.id].push(sender); |
||||
|
} |
||||
|
return sender; |
||||
|
}; |
||||
|
|
||||
|
const origAddStream = window.RTCPeerConnection.prototype.addStream; |
||||
|
window.RTCPeerConnection.prototype.addStream = function addStream(stream) { |
||||
|
this._shimmedLocalStreams = this._shimmedLocalStreams || {}; |
||||
|
|
||||
|
stream.getTracks().forEach(track => { |
||||
|
const alreadyExists = this.getSenders().find(s => s.track === track); |
||||
|
if (alreadyExists) { |
||||
|
throw new DOMException('Track already exists.', |
||||
|
'InvalidAccessError'); |
||||
|
} |
||||
|
}); |
||||
|
const existingSenders = this.getSenders(); |
||||
|
origAddStream.apply(this, arguments); |
||||
|
const newSenders = this.getSenders() |
||||
|
.filter(newSender => existingSenders.indexOf(newSender) === -1); |
||||
|
this._shimmedLocalStreams[stream.id] = [stream].concat(newSenders); |
||||
|
}; |
||||
|
|
||||
|
const origRemoveStream = window.RTCPeerConnection.prototype.removeStream; |
||||
|
window.RTCPeerConnection.prototype.removeStream = |
||||
|
function removeStream(stream) { |
||||
|
this._shimmedLocalStreams = this._shimmedLocalStreams || {}; |
||||
|
delete this._shimmedLocalStreams[stream.id]; |
||||
|
return origRemoveStream.apply(this, arguments); |
||||
|
}; |
||||
|
|
||||
|
const origRemoveTrack = window.RTCPeerConnection.prototype.removeTrack; |
||||
|
window.RTCPeerConnection.prototype.removeTrack = |
||||
|
function removeTrack(sender) { |
||||
|
this._shimmedLocalStreams = this._shimmedLocalStreams || {}; |
||||
|
if (sender) { |
||||
|
Object.keys(this._shimmedLocalStreams).forEach(streamId => { |
||||
|
const idx = this._shimmedLocalStreams[streamId].indexOf(sender); |
||||
|
if (idx !== -1) { |
||||
|
this._shimmedLocalStreams[streamId].splice(idx, 1); |
||||
|
} |
||||
|
if (this._shimmedLocalStreams[streamId].length === 1) { |
||||
|
delete this._shimmedLocalStreams[streamId]; |
||||
|
} |
||||
|
}); |
||||
|
} |
||||
|
return origRemoveTrack.apply(this, arguments); |
||||
|
}; |
||||
|
} |
||||
|
|
||||
|
export function shimAddTrackRemoveTrack(window, browserDetails) { |
||||
|
if (!window.RTCPeerConnection) { |
||||
|
return; |
||||
|
} |
||||
|
// shim addTrack and removeTrack.
|
||||
|
if (window.RTCPeerConnection.prototype.addTrack && |
||||
|
browserDetails.version >= 65) { |
||||
|
return shimAddTrackRemoveTrackWithNative(window); |
||||
|
} |
||||
|
|
||||
|
// also shim pc.getLocalStreams when addTrack is shimmed
|
||||
|
// to return the original streams.
|
||||
|
const origGetLocalStreams = window.RTCPeerConnection.prototype |
||||
|
.getLocalStreams; |
||||
|
window.RTCPeerConnection.prototype.getLocalStreams = |
||||
|
function getLocalStreams() { |
||||
|
const nativeStreams = origGetLocalStreams.apply(this); |
||||
|
this._reverseStreams = this._reverseStreams || {}; |
||||
|
return nativeStreams.map(stream => this._reverseStreams[stream.id]); |
||||
|
}; |
||||
|
|
||||
|
const origAddStream = window.RTCPeerConnection.prototype.addStream; |
||||
|
window.RTCPeerConnection.prototype.addStream = function addStream(stream) { |
||||
|
this._streams = this._streams || {}; |
||||
|
this._reverseStreams = this._reverseStreams || {}; |
||||
|
|
||||
|
stream.getTracks().forEach(track => { |
||||
|
const alreadyExists = this.getSenders().find(s => s.track === track); |
||||
|
if (alreadyExists) { |
||||
|
throw new DOMException('Track already exists.', |
||||
|
'InvalidAccessError'); |
||||
|
} |
||||
|
}); |
||||
|
// Add identity mapping for consistency with addTrack.
|
||||
|
// Unless this is being used with a stream from addTrack.
|
||||
|
if (!this._reverseStreams[stream.id]) { |
||||
|
const newStream = new window.MediaStream(stream.getTracks()); |
||||
|
this._streams[stream.id] = newStream; |
||||
|
this._reverseStreams[newStream.id] = stream; |
||||
|
stream = newStream; |
||||
|
} |
||||
|
origAddStream.apply(this, [stream]); |
||||
|
}; |
||||
|
|
||||
|
const origRemoveStream = window.RTCPeerConnection.prototype.removeStream; |
||||
|
window.RTCPeerConnection.prototype.removeStream = |
||||
|
function removeStream(stream) { |
||||
|
this._streams = this._streams || {}; |
||||
|
this._reverseStreams = this._reverseStreams || {}; |
||||
|
|
||||
|
origRemoveStream.apply(this, [(this._streams[stream.id] || stream)]); |
||||
|
delete this._reverseStreams[(this._streams[stream.id] ? |
||||
|
this._streams[stream.id].id : stream.id)]; |
||||
|
delete this._streams[stream.id]; |
||||
|
}; |
||||
|
|
||||
|
window.RTCPeerConnection.prototype.addTrack = |
||||
|
function addTrack(track, stream) { |
||||
|
if (this.signalingState === 'closed') { |
||||
|
throw new DOMException( |
||||
|
'The RTCPeerConnection\'s signalingState is \'closed\'.', |
||||
|
'InvalidStateError'); |
||||
|
} |
||||
|
const streams = [].slice.call(arguments, 1); |
||||
|
if (streams.length !== 1 || |
||||
|
!streams[0].getTracks().find(t => t === track)) { |
||||
|
// this is not fully correct but all we can manage without
|
||||
|
// [[associated MediaStreams]] internal slot.
|
||||
|
throw new DOMException( |
||||
|
'The adapter.js addTrack polyfill only supports a single ' + |
||||
|
' stream which is associated with the specified track.', |
||||
|
'NotSupportedError'); |
||||
|
} |
||||
|
|
||||
|
const alreadyExists = this.getSenders().find(s => s.track === track); |
||||
|
if (alreadyExists) { |
||||
|
throw new DOMException('Track already exists.', |
||||
|
'InvalidAccessError'); |
||||
|
} |
||||
|
|
||||
|
this._streams = this._streams || {}; |
||||
|
this._reverseStreams = this._reverseStreams || {}; |
||||
|
const oldStream = this._streams[stream.id]; |
||||
|
if (oldStream) { |
||||
|
// this is using odd Chrome behaviour, use with caution:
|
||||
|
// https://bugs.chromium.org/p/webrtc/issues/detail?id=7815
|
||||
|
// Note: we rely on the high-level addTrack/dtmf shim to
|
||||
|
// create the sender with a dtmf sender.
|
||||
|
oldStream.addTrack(track); |
||||
|
|
||||
|
// Trigger ONN async.
|
||||
|
Promise.resolve().then(() => { |
||||
|
this.dispatchEvent(new Event('negotiationneeded')); |
||||
|
}); |
||||
|
} else { |
||||
|
const newStream = new window.MediaStream([track]); |
||||
|
this._streams[stream.id] = newStream; |
||||
|
this._reverseStreams[newStream.id] = stream; |
||||
|
this.addStream(newStream); |
||||
|
} |
||||
|
return this.getSenders().find(s => s.track === track); |
||||
|
}; |
||||
|
|
||||
|
// replace the internal stream id with the external one and
|
||||
|
// vice versa.
|
||||
|
function replaceInternalStreamId(pc, description) { |
||||
|
let sdp = description.sdp; |
||||
|
Object.keys(pc._reverseStreams || []).forEach(internalId => { |
||||
|
const externalStream = pc._reverseStreams[internalId]; |
||||
|
const internalStream = pc._streams[externalStream.id]; |
||||
|
sdp = sdp.replace(new RegExp(internalStream.id, 'g'), |
||||
|
externalStream.id); |
||||
|
}); |
||||
|
return new RTCSessionDescription({ |
||||
|
type: description.type, |
||||
|
sdp |
||||
|
}); |
||||
|
} |
||||
|
function replaceExternalStreamId(pc, description) { |
||||
|
let sdp = description.sdp; |
||||
|
Object.keys(pc._reverseStreams || []).forEach(internalId => { |
||||
|
const externalStream = pc._reverseStreams[internalId]; |
||||
|
const internalStream = pc._streams[externalStream.id]; |
||||
|
sdp = sdp.replace(new RegExp(externalStream.id, 'g'), |
||||
|
internalStream.id); |
||||
|
}); |
||||
|
return new RTCSessionDescription({ |
||||
|
type: description.type, |
||||
|
sdp |
||||
|
}); |
||||
|
} |
||||
|
['createOffer', 'createAnswer'].forEach(function(method) { |
||||
|
const nativeMethod = window.RTCPeerConnection.prototype[method]; |
||||
|
const methodObj = {[method]() { |
||||
|
const args = arguments; |
||||
|
const isLegacyCall = arguments.length && |
||||
|
typeof arguments[0] === 'function'; |
||||
|
if (isLegacyCall) { |
||||
|
return nativeMethod.apply(this, [ |
||||
|
(description) => { |
||||
|
const desc = replaceInternalStreamId(this, description); |
||||
|
args[0].apply(null, [desc]); |
||||
|
}, |
||||
|
(err) => { |
||||
|
if (args[1]) { |
||||
|
args[1].apply(null, err); |
||||
|
} |
||||
|
}, arguments[2] |
||||
|
]); |
||||
|
} |
||||
|
return nativeMethod.apply(this, arguments) |
||||
|
.then(description => replaceInternalStreamId(this, description)); |
||||
|
}}; |
||||
|
window.RTCPeerConnection.prototype[method] = methodObj[method]; |
||||
|
}); |
||||
|
|
||||
|
const origSetLocalDescription = |
||||
|
window.RTCPeerConnection.prototype.setLocalDescription; |
||||
|
window.RTCPeerConnection.prototype.setLocalDescription = |
||||
|
function setLocalDescription() { |
||||
|
if (!arguments.length || !arguments[0].type) { |
||||
|
return origSetLocalDescription.apply(this, arguments); |
||||
|
} |
||||
|
arguments[0] = replaceExternalStreamId(this, arguments[0]); |
||||
|
return origSetLocalDescription.apply(this, arguments); |
||||
|
}; |
||||
|
|
||||
|
// TODO: mangle getStats: https://w3c.github.io/webrtc-stats/#dom-rtcmediastreamstats-streamidentifier
|
||||
|
|
||||
|
const origLocalDescription = Object.getOwnPropertyDescriptor( |
||||
|
window.RTCPeerConnection.prototype, 'localDescription'); |
||||
|
Object.defineProperty(window.RTCPeerConnection.prototype, |
||||
|
'localDescription', { |
||||
|
get() { |
||||
|
const description = origLocalDescription.get.apply(this); |
||||
|
if (description.type === '') { |
||||
|
return description; |
||||
|
} |
||||
|
return replaceInternalStreamId(this, description); |
||||
|
} |
||||
|
}); |
||||
|
|
||||
|
window.RTCPeerConnection.prototype.removeTrack = |
||||
|
function removeTrack(sender) { |
||||
|
if (this.signalingState === 'closed') { |
||||
|
throw new DOMException( |
||||
|
'The RTCPeerConnection\'s signalingState is \'closed\'.', |
||||
|
'InvalidStateError'); |
||||
|
} |
||||
|
// We can not yet check for sender instanceof RTCRtpSender
|
||||
|
// since we shim RTPSender. So we check if sender._pc is set.
|
||||
|
if (!sender._pc) { |
||||
|
throw new DOMException('Argument 1 of RTCPeerConnection.removeTrack ' + |
||||
|
'does not implement interface RTCRtpSender.', 'TypeError'); |
||||
|
} |
||||
|
const isLocal = sender._pc === this; |
||||
|
if (!isLocal) { |
||||
|
throw new DOMException('Sender was not created by this connection.', |
||||
|
'InvalidAccessError'); |
||||
|
} |
||||
|
|
||||
|
// Search for the native stream the senders track belongs to.
|
||||
|
this._streams = this._streams || {}; |
||||
|
let stream; |
||||
|
Object.keys(this._streams).forEach(streamid => { |
||||
|
const hasTrack = this._streams[streamid].getTracks() |
||||
|
.find(track => sender.track === track); |
||||
|
if (hasTrack) { |
||||
|
stream = this._streams[streamid]; |
||||
|
} |
||||
|
}); |
||||
|
|
||||
|
if (stream) { |
||||
|
if (stream.getTracks().length === 1) { |
||||
|
// if this is the last track of the stream, remove the stream. This
|
||||
|
// takes care of any shimmed _senders.
|
||||
|
this.removeStream(this._reverseStreams[stream.id]); |
||||
|
} else { |
||||
|
// relying on the same odd chrome behaviour as above.
|
||||
|
stream.removeTrack(sender.track); |
||||
|
} |
||||
|
this.dispatchEvent(new Event('negotiationneeded')); |
||||
|
} |
||||
|
}; |
||||
|
} |
||||
|
|
||||
|
export function shimPeerConnection(window, browserDetails) { |
||||
|
if (!window.RTCPeerConnection && window.webkitRTCPeerConnection) { |
||||
|
// very basic support for old versions.
|
||||
|
window.RTCPeerConnection = window.webkitRTCPeerConnection; |
||||
|
} |
||||
|
if (!window.RTCPeerConnection) { |
||||
|
return; |
||||
|
} |
||||
|
|
||||
|
// shim implicit creation of RTCSessionDescription/RTCIceCandidate
|
||||
|
if (browserDetails.version < 53) { |
||||
|
['setLocalDescription', 'setRemoteDescription', 'addIceCandidate'] |
||||
|
.forEach(function(method) { |
||||
|
const nativeMethod = window.RTCPeerConnection.prototype[method]; |
||||
|
const methodObj = {[method]() { |
||||
|
arguments[0] = new ((method === 'addIceCandidate') ? |
||||
|
window.RTCIceCandidate : |
||||
|
window.RTCSessionDescription)(arguments[0]); |
||||
|
return nativeMethod.apply(this, arguments); |
||||
|
}}; |
||||
|
window.RTCPeerConnection.prototype[method] = methodObj[method]; |
||||
|
}); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
// Attempt to fix ONN in plan-b mode.
|
||||
|
export function fixNegotiationNeeded(window, browserDetails) { |
||||
|
utils.wrapPeerConnectionEvent(window, 'negotiationneeded', e => { |
||||
|
const pc = e.target; |
||||
|
if (browserDetails.version < 72 || (pc.getConfiguration && |
||||
|
pc.getConfiguration().sdpSemantics === 'plan-b')) { |
||||
|
if (pc.signalingState !== 'stable') { |
||||
|
return; |
||||
|
} |
||||
|
} |
||||
|
return e; |
||||
|
}); |
||||
|
} |
||||
@ -0,0 +1,50 @@ |
|||||
|
/* |
||||
|
* Copyright (c) 2018 The adapter.js project authors. All Rights Reserved. |
||||
|
* |
||||
|
* Use of this source code is governed by a BSD-style license |
||||
|
* that can be found in the LICENSE file in the root of the source |
||||
|
* tree. |
||||
|
*/ |
||||
|
/* eslint-env node */ |
||||
|
'use strict'; |
||||
|
export function shimGetDisplayMedia(window, getSourceId) { |
||||
|
if (window.navigator.mediaDevices && |
||||
|
'getDisplayMedia' in window.navigator.mediaDevices) { |
||||
|
return; |
||||
|
} |
||||
|
if (!(window.navigator.mediaDevices)) { |
||||
|
return; |
||||
|
} |
||||
|
// getSourceId is a function that returns a promise resolving with
|
||||
|
// the sourceId of the screen/window/tab to be shared.
|
||||
|
if (typeof getSourceId !== 'function') { |
||||
|
console.error('shimGetDisplayMedia: getSourceId argument is not ' + |
||||
|
'a function'); |
||||
|
return; |
||||
|
} |
||||
|
window.navigator.mediaDevices.getDisplayMedia = |
||||
|
function getDisplayMedia(constraints) { |
||||
|
return getSourceId(constraints) |
||||
|
.then(sourceId => { |
||||
|
const widthSpecified = constraints.video && constraints.video.width; |
||||
|
const heightSpecified = constraints.video && |
||||
|
constraints.video.height; |
||||
|
const frameRateSpecified = constraints.video && |
||||
|
constraints.video.frameRate; |
||||
|
constraints.video = { |
||||
|
mandatory: { |
||||
|
chromeMediaSource: 'desktop', |
||||
|
chromeMediaSourceId: sourceId, |
||||
|
maxFrameRate: frameRateSpecified || 3 |
||||
|
} |
||||
|
}; |
||||
|
if (widthSpecified) { |
||||
|
constraints.video.mandatory.maxWidth = widthSpecified; |
||||
|
} |
||||
|
if (heightSpecified) { |
||||
|
constraints.video.mandatory.maxHeight = heightSpecified; |
||||
|
} |
||||
|
return window.navigator.mediaDevices.getUserMedia(constraints); |
||||
|
}); |
||||
|
}; |
||||
|
} |
||||
@ -0,0 +1,188 @@ |
|||||
|
/* |
||||
|
* Copyright (c) 2016 The WebRTC project authors. All Rights Reserved. |
||||
|
* |
||||
|
* Use of this source code is governed by a BSD-style license |
||||
|
* that can be found in the LICENSE file in the root of the source |
||||
|
* tree. |
||||
|
*/ |
||||
|
/* eslint-env node */ |
||||
|
'use strict'; |
||||
|
import * as utils from '../utils.js'; |
||||
|
const logging = utils.log; |
||||
|
|
||||
|
export function shimGetUserMedia(window, browserDetails) { |
||||
|
const navigator = window && window.navigator; |
||||
|
|
||||
|
if (!navigator.mediaDevices) { |
||||
|
return; |
||||
|
} |
||||
|
|
||||
|
const constraintsToChrome_ = function(c) { |
||||
|
if (typeof c !== 'object' || c.mandatory || c.optional) { |
||||
|
return c; |
||||
|
} |
||||
|
const cc = {}; |
||||
|
Object.keys(c).forEach(key => { |
||||
|
if (key === 'require' || key === 'advanced' || key === 'mediaSource') { |
||||
|
return; |
||||
|
} |
||||
|
const r = (typeof c[key] === 'object') ? c[key] : {ideal: c[key]}; |
||||
|
if (r.exact !== undefined && typeof r.exact === 'number') { |
||||
|
r.min = r.max = r.exact; |
||||
|
} |
||||
|
const oldname_ = function(prefix, name) { |
||||
|
if (prefix) { |
||||
|
return prefix + name.charAt(0).toUpperCase() + name.slice(1); |
||||
|
} |
||||
|
return (name === 'deviceId') ? 'sourceId' : name; |
||||
|
}; |
||||
|
if (r.ideal !== undefined) { |
||||
|
cc.optional = cc.optional || []; |
||||
|
let oc = {}; |
||||
|
if (typeof r.ideal === 'number') { |
||||
|
oc[oldname_('min', key)] = r.ideal; |
||||
|
cc.optional.push(oc); |
||||
|
oc = {}; |
||||
|
oc[oldname_('max', key)] = r.ideal; |
||||
|
cc.optional.push(oc); |
||||
|
} else { |
||||
|
oc[oldname_('', key)] = r.ideal; |
||||
|
cc.optional.push(oc); |
||||
|
} |
||||
|
} |
||||
|
if (r.exact !== undefined && typeof r.exact !== 'number') { |
||||
|
cc.mandatory = cc.mandatory || {}; |
||||
|
cc.mandatory[oldname_('', key)] = r.exact; |
||||
|
} else { |
||||
|
['min', 'max'].forEach(mix => { |
||||
|
if (r[mix] !== undefined) { |
||||
|
cc.mandatory = cc.mandatory || {}; |
||||
|
cc.mandatory[oldname_(mix, key)] = r[mix]; |
||||
|
} |
||||
|
}); |
||||
|
} |
||||
|
}); |
||||
|
if (c.advanced) { |
||||
|
cc.optional = (cc.optional || []).concat(c.advanced); |
||||
|
} |
||||
|
return cc; |
||||
|
}; |
||||
|
|
||||
|
const shimConstraints_ = function(constraints, func) { |
||||
|
if (browserDetails.version >= 61) { |
||||
|
return func(constraints); |
||||
|
} |
||||
|
constraints = JSON.parse(JSON.stringify(constraints)); |
||||
|
if (constraints && typeof constraints.audio === 'object') { |
||||
|
const remap = function(obj, a, b) { |
||||
|
if (a in obj && !(b in obj)) { |
||||
|
obj[b] = obj[a]; |
||||
|
delete obj[a]; |
||||
|
} |
||||
|
}; |
||||
|
constraints = JSON.parse(JSON.stringify(constraints)); |
||||
|
remap(constraints.audio, 'autoGainControl', 'googAutoGainControl'); |
||||
|
remap(constraints.audio, 'noiseSuppression', 'googNoiseSuppression'); |
||||
|
constraints.audio = constraintsToChrome_(constraints.audio); |
||||
|
} |
||||
|
if (constraints && typeof constraints.video === 'object') { |
||||
|
// Shim facingMode for mobile & surface pro.
|
||||
|
let face = constraints.video.facingMode; |
||||
|
face = face && ((typeof face === 'object') ? face : {ideal: face}); |
||||
|
const getSupportedFacingModeLies = browserDetails.version < 66; |
||||
|
|
||||
|
if ((face && (face.exact === 'user' || face.exact === 'environment' || |
||||
|
face.ideal === 'user' || face.ideal === 'environment')) && |
||||
|
!(navigator.mediaDevices.getSupportedConstraints && |
||||
|
navigator.mediaDevices.getSupportedConstraints().facingMode && |
||||
|
!getSupportedFacingModeLies)) { |
||||
|
delete constraints.video.facingMode; |
||||
|
let matches; |
||||
|
if (face.exact === 'environment' || face.ideal === 'environment') { |
||||
|
matches = ['back', 'rear']; |
||||
|
} else if (face.exact === 'user' || face.ideal === 'user') { |
||||
|
matches = ['front']; |
||||
|
} |
||||
|
if (matches) { |
||||
|
// Look for matches in label, or use last cam for back (typical).
|
||||
|
return navigator.mediaDevices.enumerateDevices() |
||||
|
.then(devices => { |
||||
|
devices = devices.filter(d => d.kind === 'videoinput'); |
||||
|
let dev = devices.find(d => matches.some(match => |
||||
|
d.label.toLowerCase().includes(match))); |
||||
|
if (!dev && devices.length && matches.includes('back')) { |
||||
|
dev = devices[devices.length - 1]; // more likely the back cam
|
||||
|
} |
||||
|
if (dev) { |
||||
|
constraints.video.deviceId = face.exact ? {exact: dev.deviceId} : |
||||
|
{ideal: dev.deviceId}; |
||||
|
} |
||||
|
constraints.video = constraintsToChrome_(constraints.video); |
||||
|
logging('chrome: ' + JSON.stringify(constraints)); |
||||
|
return func(constraints); |
||||
|
}); |
||||
|
} |
||||
|
} |
||||
|
constraints.video = constraintsToChrome_(constraints.video); |
||||
|
} |
||||
|
logging('chrome: ' + JSON.stringify(constraints)); |
||||
|
return func(constraints); |
||||
|
}; |
||||
|
|
||||
|
const shimError_ = function(e) { |
||||
|
if (browserDetails.version >= 64) { |
||||
|
return e; |
||||
|
} |
||||
|
return { |
||||
|
name: { |
||||
|
PermissionDeniedError: 'NotAllowedError', |
||||
|
PermissionDismissedError: 'NotAllowedError', |
||||
|
InvalidStateError: 'NotAllowedError', |
||||
|
DevicesNotFoundError: 'NotFoundError', |
||||
|
ConstraintNotSatisfiedError: 'OverconstrainedError', |
||||
|
TrackStartError: 'NotReadableError', |
||||
|
MediaDeviceFailedDueToShutdown: 'NotAllowedError', |
||||
|
MediaDeviceKillSwitchOn: 'NotAllowedError', |
||||
|
TabCaptureError: 'AbortError', |
||||
|
ScreenCaptureError: 'AbortError', |
||||
|
DeviceCaptureError: 'AbortError' |
||||
|
}[e.name] || e.name, |
||||
|
message: e.message, |
||||
|
constraint: e.constraint || e.constraintName, |
||||
|
toString() { |
||||
|
return this.name + (this.message && ': ') + this.message; |
||||
|
} |
||||
|
}; |
||||
|
}; |
||||
|
|
||||
|
const getUserMedia_ = function(constraints, onSuccess, onError) { |
||||
|
shimConstraints_(constraints, c => { |
||||
|
navigator.webkitGetUserMedia(c, onSuccess, e => { |
||||
|
if (onError) { |
||||
|
onError(shimError_(e)); |
||||
|
} |
||||
|
}); |
||||
|
}); |
||||
|
}; |
||||
|
navigator.getUserMedia = getUserMedia_.bind(navigator); |
||||
|
|
||||
|
// Even though Chrome 45 has navigator.mediaDevices and a getUserMedia
|
||||
|
// function which returns a Promise, it does not accept spec-style
|
||||
|
// constraints.
|
||||
|
if (navigator.mediaDevices.getUserMedia) { |
||||
|
const origGetUserMedia = navigator.mediaDevices.getUserMedia. |
||||
|
bind(navigator.mediaDevices); |
||||
|
navigator.mediaDevices.getUserMedia = function(cs) { |
||||
|
return shimConstraints_(cs, c => origGetUserMedia(c).then(stream => { |
||||
|
if (c.audio && !stream.getAudioTracks().length || |
||||
|
c.video && !stream.getVideoTracks().length) { |
||||
|
stream.getTracks().forEach(track => { |
||||
|
track.stop(); |
||||
|
}); |
||||
|
throw new DOMException('', 'NotFoundError'); |
||||
|
} |
||||
|
return stream; |
||||
|
}, e => Promise.reject(shimError_(e)))); |
||||
|
}; |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,433 @@ |
|||||
|
/* |
||||
|
* Copyright (c) 2017 The WebRTC project authors. All Rights Reserved. |
||||
|
* |
||||
|
* Use of this source code is governed by a BSD-style license |
||||
|
* that can be found in the LICENSE file in the root of the source |
||||
|
* tree. |
||||
|
*/ |
||||
|
/* eslint-env node */ |
||||
|
'use strict'; |
||||
|
|
||||
|
import SDPUtils from 'sdp'; |
||||
|
import * as utils from './utils'; |
||||
|
|
||||
|
export function shimRTCIceCandidate(window) { |
||||
|
// foundation is arbitrarily chosen as an indicator for full support for
|
||||
|
// https://w3c.github.io/webrtc-pc/#rtcicecandidate-interface
|
||||
|
if (!window.RTCIceCandidate || (window.RTCIceCandidate && 'foundation' in |
||||
|
window.RTCIceCandidate.prototype)) { |
||||
|
return; |
||||
|
} |
||||
|
|
||||
|
const NativeRTCIceCandidate = window.RTCIceCandidate; |
||||
|
window.RTCIceCandidate = function RTCIceCandidate(args) { |
||||
|
// Remove the a= which shouldn't be part of the candidate string.
|
||||
|
if (typeof args === 'object' && args.candidate && |
||||
|
args.candidate.indexOf('a=') === 0) { |
||||
|
args = JSON.parse(JSON.stringify(args)); |
||||
|
args.candidate = args.candidate.substr(2); |
||||
|
} |
||||
|
|
||||
|
if (args.candidate && args.candidate.length) { |
||||
|
// Augment the native candidate with the parsed fields.
|
||||
|
const nativeCandidate = new NativeRTCIceCandidate(args); |
||||
|
const parsedCandidate = SDPUtils.parseCandidate(args.candidate); |
||||
|
const augmentedCandidate = Object.assign(nativeCandidate, |
||||
|
parsedCandidate); |
||||
|
|
||||
|
// Add a serializer that does not serialize the extra attributes.
|
||||
|
augmentedCandidate.toJSON = function toJSON() { |
||||
|
return { |
||||
|
candidate: augmentedCandidate.candidate, |
||||
|
sdpMid: augmentedCandidate.sdpMid, |
||||
|
sdpMLineIndex: augmentedCandidate.sdpMLineIndex, |
||||
|
usernameFragment: augmentedCandidate.usernameFragment, |
||||
|
}; |
||||
|
}; |
||||
|
return augmentedCandidate; |
||||
|
} |
||||
|
return new NativeRTCIceCandidate(args); |
||||
|
}; |
||||
|
window.RTCIceCandidate.prototype = NativeRTCIceCandidate.prototype; |
||||
|
|
||||
|
// Hook up the augmented candidate in onicecandidate and
|
||||
|
// addEventListener('icecandidate', ...)
|
||||
|
utils.wrapPeerConnectionEvent(window, 'icecandidate', e => { |
||||
|
if (e.candidate) { |
||||
|
Object.defineProperty(e, 'candidate', { |
||||
|
value: new window.RTCIceCandidate(e.candidate), |
||||
|
writable: 'false' |
||||
|
}); |
||||
|
} |
||||
|
return e; |
||||
|
}); |
||||
|
} |
||||
|
|
||||
|
export function shimMaxMessageSize(window, browserDetails) { |
||||
|
if (!window.RTCPeerConnection) { |
||||
|
return; |
||||
|
} |
||||
|
|
||||
|
if (!('sctp' in window.RTCPeerConnection.prototype)) { |
||||
|
Object.defineProperty(window.RTCPeerConnection.prototype, 'sctp', { |
||||
|
get() { |
||||
|
return typeof this._sctp === 'undefined' ? null : this._sctp; |
||||
|
} |
||||
|
}); |
||||
|
} |
||||
|
|
||||
|
const sctpInDescription = function(description) { |
||||
|
if (!description || !description.sdp) { |
||||
|
return false; |
||||
|
} |
||||
|
const sections = SDPUtils.splitSections(description.sdp); |
||||
|
sections.shift(); |
||||
|
return sections.some(mediaSection => { |
||||
|
const mLine = SDPUtils.parseMLine(mediaSection); |
||||
|
return mLine && mLine.kind === 'application' |
||||
|
&& mLine.protocol.indexOf('SCTP') !== -1; |
||||
|
}); |
||||
|
}; |
||||
|
|
||||
|
const getRemoteFirefoxVersion = function(description) { |
||||
|
// TODO: Is there a better solution for detecting Firefox?
|
||||
|
const match = description.sdp.match(/mozilla...THIS_IS_SDPARTA-(\d+)/); |
||||
|
if (match === null || match.length < 2) { |
||||
|
return -1; |
||||
|
} |
||||
|
const version = parseInt(match[1], 10); |
||||
|
// Test for NaN (yes, this is ugly)
|
||||
|
return version !== version ? -1 : version; |
||||
|
}; |
||||
|
|
||||
|
const getCanSendMaxMessageSize = function(remoteIsFirefox) { |
||||
|
// Every implementation we know can send at least 64 KiB.
|
||||
|
// Note: Although Chrome is technically able to send up to 256 KiB, the
|
||||
|
// data does not reach the other peer reliably.
|
||||
|
// See: https://bugs.chromium.org/p/webrtc/issues/detail?id=8419
|
||||
|
let canSendMaxMessageSize = 65536; |
||||
|
if (browserDetails.browser === 'firefox') { |
||||
|
if (browserDetails.version < 57) { |
||||
|
if (remoteIsFirefox === -1) { |
||||
|
// FF < 57 will send in 16 KiB chunks using the deprecated PPID
|
||||
|
// fragmentation.
|
||||
|
canSendMaxMessageSize = 16384; |
||||
|
} else { |
||||
|
// However, other FF (and RAWRTC) can reassemble PPID-fragmented
|
||||
|
// messages. Thus, supporting ~2 GiB when sending.
|
||||
|
canSendMaxMessageSize = 2147483637; |
||||
|
} |
||||
|
} else if (browserDetails.version < 60) { |
||||
|
// Currently, all FF >= 57 will reset the remote maximum message size
|
||||
|
// to the default value when a data channel is created at a later
|
||||
|
// stage. :(
|
||||
|
// See: https://bugzilla.mozilla.org/show_bug.cgi?id=1426831
|
||||
|
canSendMaxMessageSize = |
||||
|
browserDetails.version === 57 ? 65535 : 65536; |
||||
|
} else { |
||||
|
// FF >= 60 supports sending ~2 GiB
|
||||
|
canSendMaxMessageSize = 2147483637; |
||||
|
} |
||||
|
} |
||||
|
return canSendMaxMessageSize; |
||||
|
}; |
||||
|
|
||||
|
const getMaxMessageSize = function(description, remoteIsFirefox) { |
||||
|
// Note: 65536 bytes is the default value from the SDP spec. Also,
|
||||
|
// every implementation we know supports receiving 65536 bytes.
|
||||
|
let maxMessageSize = 65536; |
||||
|
|
||||
|
// FF 57 has a slightly incorrect default remote max message size, so
|
||||
|
// we need to adjust it here to avoid a failure when sending.
|
||||
|
// See: https://bugzilla.mozilla.org/show_bug.cgi?id=1425697
|
||||
|
if (browserDetails.browser === 'firefox' |
||||
|
&& browserDetails.version === 57) { |
||||
|
maxMessageSize = 65535; |
||||
|
} |
||||
|
|
||||
|
const match = SDPUtils.matchPrefix(description.sdp, |
||||
|
'a=max-message-size:'); |
||||
|
if (match.length > 0) { |
||||
|
maxMessageSize = parseInt(match[0].substr(19), 10); |
||||
|
} else if (browserDetails.browser === 'firefox' && |
||||
|
remoteIsFirefox !== -1) { |
||||
|
// If the maximum message size is not present in the remote SDP and
|
||||
|
// both local and remote are Firefox, the remote peer can receive
|
||||
|
// ~2 GiB.
|
||||
|
maxMessageSize = 2147483637; |
||||
|
} |
||||
|
return maxMessageSize; |
||||
|
}; |
||||
|
|
||||
|
const origSetRemoteDescription = |
||||
|
window.RTCPeerConnection.prototype.setRemoteDescription; |
||||
|
window.RTCPeerConnection.prototype.setRemoteDescription = |
||||
|
function setRemoteDescription() { |
||||
|
this._sctp = null; |
||||
|
// Chrome decided to not expose .sctp in plan-b mode.
|
||||
|
// As usual, adapter.js has to do an 'ugly worakaround'
|
||||
|
// to cover up the mess.
|
||||
|
if (browserDetails.browser === 'chrome' && browserDetails.version >= 76) { |
||||
|
const {sdpSemantics} = this.getConfiguration(); |
||||
|
if (sdpSemantics === 'plan-b') { |
||||
|
Object.defineProperty(this, 'sctp', { |
||||
|
get() { |
||||
|
return typeof this._sctp === 'undefined' ? null : this._sctp; |
||||
|
}, |
||||
|
enumerable: true, |
||||
|
configurable: true, |
||||
|
}); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
if (sctpInDescription(arguments[0])) { |
||||
|
// Check if the remote is FF.
|
||||
|
const isFirefox = getRemoteFirefoxVersion(arguments[0]); |
||||
|
|
||||
|
// Get the maximum message size the local peer is capable of sending
|
||||
|
const canSendMMS = getCanSendMaxMessageSize(isFirefox); |
||||
|
|
||||
|
// Get the maximum message size of the remote peer.
|
||||
|
const remoteMMS = getMaxMessageSize(arguments[0], isFirefox); |
||||
|
|
||||
|
// Determine final maximum message size
|
||||
|
let maxMessageSize; |
||||
|
if (canSendMMS === 0 && remoteMMS === 0) { |
||||
|
maxMessageSize = Number.POSITIVE_INFINITY; |
||||
|
} else if (canSendMMS === 0 || remoteMMS === 0) { |
||||
|
maxMessageSize = Math.max(canSendMMS, remoteMMS); |
||||
|
} else { |
||||
|
maxMessageSize = Math.min(canSendMMS, remoteMMS); |
||||
|
} |
||||
|
|
||||
|
// Create a dummy RTCSctpTransport object and the 'maxMessageSize'
|
||||
|
// attribute.
|
||||
|
const sctp = {}; |
||||
|
Object.defineProperty(sctp, 'maxMessageSize', { |
||||
|
get() { |
||||
|
return maxMessageSize; |
||||
|
} |
||||
|
}); |
||||
|
this._sctp = sctp; |
||||
|
} |
||||
|
|
||||
|
return origSetRemoteDescription.apply(this, arguments); |
||||
|
}; |
||||
|
} |
||||
|
|
||||
|
export function shimSendThrowTypeError(window) { |
||||
|
if (!(window.RTCPeerConnection && |
||||
|
'createDataChannel' in window.RTCPeerConnection.prototype)) { |
||||
|
return; |
||||
|
} |
||||
|
|
||||
|
// Note: Although Firefox >= 57 has a native implementation, the maximum
|
||||
|
// message size can be reset for all data channels at a later stage.
|
||||
|
// See: https://bugzilla.mozilla.org/show_bug.cgi?id=1426831
|
||||
|
|
||||
|
function wrapDcSend(dc, pc) { |
||||
|
const origDataChannelSend = dc.send; |
||||
|
dc.send = function send() { |
||||
|
const data = arguments[0]; |
||||
|
const length = data.length || data.size || data.byteLength; |
||||
|
if (dc.readyState === 'open' && |
||||
|
pc.sctp && length > pc.sctp.maxMessageSize) { |
||||
|
throw new TypeError('Message too large (can send a maximum of ' + |
||||
|
pc.sctp.maxMessageSize + ' bytes)'); |
||||
|
} |
||||
|
return origDataChannelSend.apply(dc, arguments); |
||||
|
}; |
||||
|
} |
||||
|
const origCreateDataChannel = |
||||
|
window.RTCPeerConnection.prototype.createDataChannel; |
||||
|
window.RTCPeerConnection.prototype.createDataChannel = |
||||
|
function createDataChannel() { |
||||
|
const dataChannel = origCreateDataChannel.apply(this, arguments); |
||||
|
wrapDcSend(dataChannel, this); |
||||
|
return dataChannel; |
||||
|
}; |
||||
|
utils.wrapPeerConnectionEvent(window, 'datachannel', e => { |
||||
|
wrapDcSend(e.channel, e.target); |
||||
|
return e; |
||||
|
}); |
||||
|
} |
||||
|
|
||||
|
|
||||
|
/* shims RTCConnectionState by pretending it is the same as iceConnectionState. |
||||
|
* See https://bugs.chromium.org/p/webrtc/issues/detail?id=6145#c12
|
||||
|
* for why this is a valid hack in Chrome. In Firefox it is slightly incorrect |
||||
|
* since DTLS failures would be hidden. See |
||||
|
* https://bugzilla.mozilla.org/show_bug.cgi?id=1265827
|
||||
|
* for the Firefox tracking bug. |
||||
|
*/ |
||||
|
export function shimConnectionState(window) { |
||||
|
if (!window.RTCPeerConnection || |
||||
|
'connectionState' in window.RTCPeerConnection.prototype) { |
||||
|
return; |
||||
|
} |
||||
|
const proto = window.RTCPeerConnection.prototype; |
||||
|
Object.defineProperty(proto, 'connectionState', { |
||||
|
get() { |
||||
|
return { |
||||
|
completed: 'connected', |
||||
|
checking: 'connecting' |
||||
|
}[this.iceConnectionState] || this.iceConnectionState; |
||||
|
}, |
||||
|
enumerable: true, |
||||
|
configurable: true |
||||
|
}); |
||||
|
Object.defineProperty(proto, 'onconnectionstatechange', { |
||||
|
get() { |
||||
|
return this._onconnectionstatechange || null; |
||||
|
}, |
||||
|
set(cb) { |
||||
|
if (this._onconnectionstatechange) { |
||||
|
this.removeEventListener('connectionstatechange', |
||||
|
this._onconnectionstatechange); |
||||
|
delete this._onconnectionstatechange; |
||||
|
} |
||||
|
if (cb) { |
||||
|
this.addEventListener('connectionstatechange', |
||||
|
this._onconnectionstatechange = cb); |
||||
|
} |
||||
|
}, |
||||
|
enumerable: true, |
||||
|
configurable: true |
||||
|
}); |
||||
|
|
||||
|
['setLocalDescription', 'setRemoteDescription'].forEach((method) => { |
||||
|
const origMethod = proto[method]; |
||||
|
proto[method] = function() { |
||||
|
if (!this._connectionstatechangepoly) { |
||||
|
this._connectionstatechangepoly = e => { |
||||
|
const pc = e.target; |
||||
|
if (pc._lastConnectionState !== pc.connectionState) { |
||||
|
pc._lastConnectionState = pc.connectionState; |
||||
|
const newEvent = new Event('connectionstatechange', e); |
||||
|
pc.dispatchEvent(newEvent); |
||||
|
} |
||||
|
return e; |
||||
|
}; |
||||
|
this.addEventListener('iceconnectionstatechange', |
||||
|
this._connectionstatechangepoly); |
||||
|
} |
||||
|
return origMethod.apply(this, arguments); |
||||
|
}; |
||||
|
}); |
||||
|
} |
||||
|
|
||||
|
export function removeExtmapAllowMixed(window, browserDetails) { |
||||
|
/* remove a=extmap-allow-mixed for webrtc.org < M71 */ |
||||
|
if (!window.RTCPeerConnection) { |
||||
|
return; |
||||
|
} |
||||
|
if (browserDetails.browser === 'chrome' && browserDetails.version >= 71) { |
||||
|
return; |
||||
|
} |
||||
|
if (browserDetails.browser === 'safari' && browserDetails.version >= 605) { |
||||
|
return; |
||||
|
} |
||||
|
const nativeSRD = window.RTCPeerConnection.prototype.setRemoteDescription; |
||||
|
window.RTCPeerConnection.prototype.setRemoteDescription = |
||||
|
function setRemoteDescription(desc) { |
||||
|
if (desc && desc.sdp && desc.sdp.indexOf('\na=extmap-allow-mixed') !== -1) { |
||||
|
const sdp = desc.sdp.split('\n').filter((line) => { |
||||
|
return line.trim() !== 'a=extmap-allow-mixed'; |
||||
|
}).join('\n'); |
||||
|
// Safari enforces read-only-ness of RTCSessionDescription fields.
|
||||
|
if (window.RTCSessionDescription && |
||||
|
desc instanceof window.RTCSessionDescription) { |
||||
|
arguments[0] = new window.RTCSessionDescription({ |
||||
|
type: desc.type, |
||||
|
sdp, |
||||
|
}); |
||||
|
} else { |
||||
|
desc.sdp = sdp; |
||||
|
} |
||||
|
} |
||||
|
return nativeSRD.apply(this, arguments); |
||||
|
}; |
||||
|
} |
||||
|
|
||||
|
export function shimAddIceCandidateNullOrEmpty(window, browserDetails) { |
||||
|
// Support for addIceCandidate(null or undefined)
|
||||
|
// as well as addIceCandidate({candidate: "", ...})
|
||||
|
// https://bugs.chromium.org/p/chromium/issues/detail?id=978582
|
||||
|
// Note: must be called before other polyfills which change the signature.
|
||||
|
if (!(window.RTCPeerConnection && window.RTCPeerConnection.prototype)) { |
||||
|
return; |
||||
|
} |
||||
|
const nativeAddIceCandidate = |
||||
|
window.RTCPeerConnection.prototype.addIceCandidate; |
||||
|
if (!nativeAddIceCandidate || nativeAddIceCandidate.length === 0) { |
||||
|
return; |
||||
|
} |
||||
|
window.RTCPeerConnection.prototype.addIceCandidate = |
||||
|
function addIceCandidate() { |
||||
|
if (!arguments[0]) { |
||||
|
if (arguments[1]) { |
||||
|
arguments[1].apply(null); |
||||
|
} |
||||
|
return Promise.resolve(); |
||||
|
} |
||||
|
// Firefox 68+ emits and processes {candidate: "", ...}, ignore
|
||||
|
// in older versions.
|
||||
|
// Native support for ignoring exists for Chrome M77+.
|
||||
|
// Safari ignores as well, exact version unknown but works in the same
|
||||
|
// version that also ignores addIceCandidate(null).
|
||||
|
if (((browserDetails.browser === 'chrome' && browserDetails.version < 78) |
||||
|
|| (browserDetails.browser === 'firefox' |
||||
|
&& browserDetails.version < 68) |
||||
|
|| (browserDetails.browser === 'safari')) |
||||
|
&& arguments[0] && arguments[0].candidate === '') { |
||||
|
return Promise.resolve(); |
||||
|
} |
||||
|
return nativeAddIceCandidate.apply(this, arguments); |
||||
|
}; |
||||
|
} |
||||
|
|
||||
|
// Note: Make sure to call this ahead of APIs that modify
|
||||
|
// setLocalDescription.length
|
||||
|
export function shimParameterlessSetLocalDescription(window, browserDetails) { |
||||
|
if (!(window.RTCPeerConnection && window.RTCPeerConnection.prototype)) { |
||||
|
return; |
||||
|
} |
||||
|
const nativeSetLocalDescription = |
||||
|
window.RTCPeerConnection.prototype.setLocalDescription; |
||||
|
if (!nativeSetLocalDescription || nativeSetLocalDescription.length === 0) { |
||||
|
return; |
||||
|
} |
||||
|
window.RTCPeerConnection.prototype.setLocalDescription = |
||||
|
function setLocalDescription() { |
||||
|
let desc = arguments[0] || {}; |
||||
|
if (typeof desc !== 'object' || (desc.type && desc.sdp)) { |
||||
|
return nativeSetLocalDescription.apply(this, arguments); |
||||
|
} |
||||
|
// The remaining steps should technically happen when SLD comes off the
|
||||
|
// RTCPeerConnection's operations chain (not ahead of going on it), but
|
||||
|
// this is too difficult to shim. Instead, this shim only covers the
|
||||
|
// common case where the operations chain is empty. This is imperfect, but
|
||||
|
// should cover many cases. Rationale: Even if we can't reduce the glare
|
||||
|
// window to zero on imperfect implementations, there's value in tapping
|
||||
|
// into the perfect negotiation pattern that several browsers support.
|
||||
|
desc = {type: desc.type, sdp: desc.sdp}; |
||||
|
if (!desc.type) { |
||||
|
switch (this.signalingState) { |
||||
|
case 'stable': |
||||
|
case 'have-local-offer': |
||||
|
case 'have-remote-pranswer': |
||||
|
desc.type = 'offer'; |
||||
|
break; |
||||
|
default: |
||||
|
desc.type = 'answer'; |
||||
|
break; |
||||
|
} |
||||
|
} |
||||
|
if (desc.sdp || (desc.type !== 'offer' && desc.type !== 'answer')) { |
||||
|
return nativeSetLocalDescription.apply(this, [desc]); |
||||
|
} |
||||
|
const func = desc.type === 'offer' ? this.createOffer : this.createAnswer; |
||||
|
return func.apply(this) |
||||
|
.then(d => nativeSetLocalDescription.apply(this, [d])); |
||||
|
}; |
||||
|
} |
||||
@ -0,0 +1,296 @@ |
|||||
|
/* |
||||
|
* Copyright (c) 2016 The WebRTC project authors. All Rights Reserved. |
||||
|
* |
||||
|
* Use of this source code is governed by a BSD-style license |
||||
|
* that can be found in the LICENSE file in the root of the source |
||||
|
* tree. |
||||
|
*/ |
||||
|
/* eslint-env node */ |
||||
|
'use strict'; |
||||
|
|
||||
|
import * as utils from '../utils'; |
||||
|
export {shimGetUserMedia} from './getusermedia'; |
||||
|
export {shimGetDisplayMedia} from './getdisplaymedia'; |
||||
|
|
||||
|
export function shimOnTrack(window) { |
||||
|
if (typeof window === 'object' && window.RTCTrackEvent && |
||||
|
('receiver' in window.RTCTrackEvent.prototype) && |
||||
|
!('transceiver' in window.RTCTrackEvent.prototype)) { |
||||
|
Object.defineProperty(window.RTCTrackEvent.prototype, 'transceiver', { |
||||
|
get() { |
||||
|
return {receiver: this.receiver}; |
||||
|
} |
||||
|
}); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
export function shimPeerConnection(window, browserDetails) { |
||||
|
if (typeof window !== 'object' || |
||||
|
!(window.RTCPeerConnection || window.mozRTCPeerConnection)) { |
||||
|
return; // probably media.peerconnection.enabled=false in about:config
|
||||
|
} |
||||
|
if (!window.RTCPeerConnection && window.mozRTCPeerConnection) { |
||||
|
// very basic support for old versions.
|
||||
|
window.RTCPeerConnection = window.mozRTCPeerConnection; |
||||
|
} |
||||
|
|
||||
|
if (browserDetails.version < 53) { |
||||
|
// shim away need for obsolete RTCIceCandidate/RTCSessionDescription.
|
||||
|
['setLocalDescription', 'setRemoteDescription', 'addIceCandidate'] |
||||
|
.forEach(function(method) { |
||||
|
const nativeMethod = window.RTCPeerConnection.prototype[method]; |
||||
|
const methodObj = {[method]() { |
||||
|
arguments[0] = new ((method === 'addIceCandidate') ? |
||||
|
window.RTCIceCandidate : |
||||
|
window.RTCSessionDescription)(arguments[0]); |
||||
|
return nativeMethod.apply(this, arguments); |
||||
|
}}; |
||||
|
window.RTCPeerConnection.prototype[method] = methodObj[method]; |
||||
|
}); |
||||
|
} |
||||
|
|
||||
|
const modernStatsTypes = { |
||||
|
inboundrtp: 'inbound-rtp', |
||||
|
outboundrtp: 'outbound-rtp', |
||||
|
candidatepair: 'candidate-pair', |
||||
|
localcandidate: 'local-candidate', |
||||
|
remotecandidate: 'remote-candidate' |
||||
|
}; |
||||
|
|
||||
|
const nativeGetStats = window.RTCPeerConnection.prototype.getStats; |
||||
|
window.RTCPeerConnection.prototype.getStats = function getStats() { |
||||
|
const [selector, onSucc, onErr] = arguments; |
||||
|
return nativeGetStats.apply(this, [selector || null]) |
||||
|
.then(stats => { |
||||
|
if (browserDetails.version < 53 && !onSucc) { |
||||
|
// Shim only promise getStats with spec-hyphens in type names
|
||||
|
// Leave callback version alone; misc old uses of forEach before Map
|
||||
|
try { |
||||
|
stats.forEach(stat => { |
||||
|
stat.type = modernStatsTypes[stat.type] || stat.type; |
||||
|
}); |
||||
|
} catch (e) { |
||||
|
if (e.name !== 'TypeError') { |
||||
|
throw e; |
||||
|
} |
||||
|
// Avoid TypeError: "type" is read-only, in old versions. 34-43ish
|
||||
|
stats.forEach((stat, i) => { |
||||
|
stats.set(i, Object.assign({}, stat, { |
||||
|
type: modernStatsTypes[stat.type] || stat.type |
||||
|
})); |
||||
|
}); |
||||
|
} |
||||
|
} |
||||
|
return stats; |
||||
|
}) |
||||
|
.then(onSucc, onErr); |
||||
|
}; |
||||
|
} |
||||
|
|
||||
|
export function shimSenderGetStats(window) { |
||||
|
if (!(typeof window === 'object' && window.RTCPeerConnection && |
||||
|
window.RTCRtpSender)) { |
||||
|
return; |
||||
|
} |
||||
|
if (window.RTCRtpSender && 'getStats' in window.RTCRtpSender.prototype) { |
||||
|
return; |
||||
|
} |
||||
|
const origGetSenders = window.RTCPeerConnection.prototype.getSenders; |
||||
|
if (origGetSenders) { |
||||
|
window.RTCPeerConnection.prototype.getSenders = function getSenders() { |
||||
|
const senders = origGetSenders.apply(this, []); |
||||
|
senders.forEach(sender => sender._pc = this); |
||||
|
return senders; |
||||
|
}; |
||||
|
} |
||||
|
|
||||
|
const origAddTrack = window.RTCPeerConnection.prototype.addTrack; |
||||
|
if (origAddTrack) { |
||||
|
window.RTCPeerConnection.prototype.addTrack = function addTrack() { |
||||
|
const sender = origAddTrack.apply(this, arguments); |
||||
|
sender._pc = this; |
||||
|
return sender; |
||||
|
}; |
||||
|
} |
||||
|
window.RTCRtpSender.prototype.getStats = function getStats() { |
||||
|
return this.track ? this._pc.getStats(this.track) : |
||||
|
Promise.resolve(new Map()); |
||||
|
}; |
||||
|
} |
||||
|
|
||||
|
export function shimReceiverGetStats(window) { |
||||
|
if (!(typeof window === 'object' && window.RTCPeerConnection && |
||||
|
window.RTCRtpSender)) { |
||||
|
return; |
||||
|
} |
||||
|
if (window.RTCRtpSender && 'getStats' in window.RTCRtpReceiver.prototype) { |
||||
|
return; |
||||
|
} |
||||
|
const origGetReceivers = window.RTCPeerConnection.prototype.getReceivers; |
||||
|
if (origGetReceivers) { |
||||
|
window.RTCPeerConnection.prototype.getReceivers = function getReceivers() { |
||||
|
const receivers = origGetReceivers.apply(this, []); |
||||
|
receivers.forEach(receiver => receiver._pc = this); |
||||
|
return receivers; |
||||
|
}; |
||||
|
} |
||||
|
utils.wrapPeerConnectionEvent(window, 'track', e => { |
||||
|
e.receiver._pc = e.srcElement; |
||||
|
return e; |
||||
|
}); |
||||
|
window.RTCRtpReceiver.prototype.getStats = function getStats() { |
||||
|
return this._pc.getStats(this.track); |
||||
|
}; |
||||
|
} |
||||
|
|
||||
|
export function shimRemoveStream(window) { |
||||
|
if (!window.RTCPeerConnection || |
||||
|
'removeStream' in window.RTCPeerConnection.prototype) { |
||||
|
return; |
||||
|
} |
||||
|
window.RTCPeerConnection.prototype.removeStream = |
||||
|
function removeStream(stream) { |
||||
|
utils.deprecated('removeStream', 'removeTrack'); |
||||
|
this.getSenders().forEach(sender => { |
||||
|
if (sender.track && stream.getTracks().includes(sender.track)) { |
||||
|
this.removeTrack(sender); |
||||
|
} |
||||
|
}); |
||||
|
}; |
||||
|
} |
||||
|
|
||||
|
export function shimRTCDataChannel(window) { |
||||
|
// rename DataChannel to RTCDataChannel (native fix in FF60):
|
||||
|
// https://bugzilla.mozilla.org/show_bug.cgi?id=1173851
|
||||
|
if (window.DataChannel && !window.RTCDataChannel) { |
||||
|
window.RTCDataChannel = window.DataChannel; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
export function shimAddTransceiver(window) { |
||||
|
// https://github.com/webrtcHacks/adapter/issues/998#issuecomment-516921647
|
||||
|
// Firefox ignores the init sendEncodings options passed to addTransceiver
|
||||
|
// https://bugzilla.mozilla.org/show_bug.cgi?id=1396918
|
||||
|
if (!(typeof window === 'object' && window.RTCPeerConnection)) { |
||||
|
return; |
||||
|
} |
||||
|
const origAddTransceiver = window.RTCPeerConnection.prototype.addTransceiver; |
||||
|
if (origAddTransceiver) { |
||||
|
window.RTCPeerConnection.prototype.addTransceiver = |
||||
|
function addTransceiver() { |
||||
|
this.setParametersPromises = []; |
||||
|
const initParameters = arguments[1]; |
||||
|
const shouldPerformCheck = initParameters && |
||||
|
'sendEncodings' in initParameters; |
||||
|
if (shouldPerformCheck) { |
||||
|
// If sendEncodings params are provided, validate grammar
|
||||
|
initParameters.sendEncodings.forEach((encodingParam) => { |
||||
|
if ('rid' in encodingParam) { |
||||
|
const ridRegex = /^[a-z0-9]{0,16}$/i; |
||||
|
if (!ridRegex.test(encodingParam.rid)) { |
||||
|
throw new TypeError('Invalid RID value provided.'); |
||||
|
} |
||||
|
} |
||||
|
if ('scaleResolutionDownBy' in encodingParam) { |
||||
|
if (!(parseFloat(encodingParam.scaleResolutionDownBy) >= 1.0)) { |
||||
|
throw new RangeError('scale_resolution_down_by must be >= 1.0'); |
||||
|
} |
||||
|
} |
||||
|
if ('maxFramerate' in encodingParam) { |
||||
|
if (!(parseFloat(encodingParam.maxFramerate) >= 0)) { |
||||
|
throw new RangeError('max_framerate must be >= 0.0'); |
||||
|
} |
||||
|
} |
||||
|
}); |
||||
|
} |
||||
|
const transceiver = origAddTransceiver.apply(this, arguments); |
||||
|
if (shouldPerformCheck) { |
||||
|
// Check if the init options were applied. If not we do this in an
|
||||
|
// asynchronous way and save the promise reference in a global object.
|
||||
|
// This is an ugly hack, but at the same time is way more robust than
|
||||
|
// checking the sender parameters before and after the createOffer
|
||||
|
// Also note that after the createoffer we are not 100% sure that
|
||||
|
// the params were asynchronously applied so we might miss the
|
||||
|
// opportunity to recreate offer.
|
||||
|
const {sender} = transceiver; |
||||
|
const params = sender.getParameters(); |
||||
|
if (!('encodings' in params) || |
||||
|
// Avoid being fooled by patched getParameters() below.
|
||||
|
(params.encodings.length === 1 && |
||||
|
Object.keys(params.encodings[0]).length === 0)) { |
||||
|
params.encodings = initParameters.sendEncodings; |
||||
|
sender.sendEncodings = initParameters.sendEncodings; |
||||
|
this.setParametersPromises.push(sender.setParameters(params) |
||||
|
.then(() => { |
||||
|
delete sender.sendEncodings; |
||||
|
}).catch(() => { |
||||
|
delete sender.sendEncodings; |
||||
|
}) |
||||
|
); |
||||
|
} |
||||
|
} |
||||
|
return transceiver; |
||||
|
}; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
export function shimGetParameters(window) { |
||||
|
if (!(typeof window === 'object' && window.RTCRtpSender)) { |
||||
|
return; |
||||
|
} |
||||
|
const origGetParameters = window.RTCRtpSender.prototype.getParameters; |
||||
|
if (origGetParameters) { |
||||
|
window.RTCRtpSender.prototype.getParameters = |
||||
|
function getParameters() { |
||||
|
const params = origGetParameters.apply(this, arguments); |
||||
|
if (!('encodings' in params)) { |
||||
|
params.encodings = [].concat(this.sendEncodings || [{}]); |
||||
|
} |
||||
|
return params; |
||||
|
}; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
export function shimCreateOffer(window) { |
||||
|
// https://github.com/webrtcHacks/adapter/issues/998#issuecomment-516921647
|
||||
|
// Firefox ignores the init sendEncodings options passed to addTransceiver
|
||||
|
// https://bugzilla.mozilla.org/show_bug.cgi?id=1396918
|
||||
|
if (!(typeof window === 'object' && window.RTCPeerConnection)) { |
||||
|
return; |
||||
|
} |
||||
|
const origCreateOffer = window.RTCPeerConnection.prototype.createOffer; |
||||
|
window.RTCPeerConnection.prototype.createOffer = function createOffer() { |
||||
|
if (this.setParametersPromises && this.setParametersPromises.length) { |
||||
|
return Promise.all(this.setParametersPromises) |
||||
|
.then(() => { |
||||
|
return origCreateOffer.apply(this, arguments); |
||||
|
}) |
||||
|
.finally(() => { |
||||
|
this.setParametersPromises = []; |
||||
|
}); |
||||
|
} |
||||
|
return origCreateOffer.apply(this, arguments); |
||||
|
}; |
||||
|
} |
||||
|
|
||||
|
export function shimCreateAnswer(window) { |
||||
|
// https://github.com/webrtcHacks/adapter/issues/998#issuecomment-516921647
|
||||
|
// Firefox ignores the init sendEncodings options passed to addTransceiver
|
||||
|
// https://bugzilla.mozilla.org/show_bug.cgi?id=1396918
|
||||
|
if (!(typeof window === 'object' && window.RTCPeerConnection)) { |
||||
|
return; |
||||
|
} |
||||
|
const origCreateAnswer = window.RTCPeerConnection.prototype.createAnswer; |
||||
|
window.RTCPeerConnection.prototype.createAnswer = function createAnswer() { |
||||
|
if (this.setParametersPromises && this.setParametersPromises.length) { |
||||
|
return Promise.all(this.setParametersPromises) |
||||
|
.then(() => { |
||||
|
return origCreateAnswer.apply(this, arguments); |
||||
|
}) |
||||
|
.finally(() => { |
||||
|
this.setParametersPromises = []; |
||||
|
}); |
||||
|
} |
||||
|
return origCreateAnswer.apply(this, arguments); |
||||
|
}; |
||||
|
} |
||||
@ -0,0 +1,36 @@ |
|||||
|
/* |
||||
|
* Copyright (c) 2018 The adapter.js project authors. All Rights Reserved. |
||||
|
* |
||||
|
* Use of this source code is governed by a BSD-style license |
||||
|
* that can be found in the LICENSE file in the root of the source |
||||
|
* tree. |
||||
|
*/ |
||||
|
/* eslint-env node */ |
||||
|
'use strict'; |
||||
|
|
||||
|
export function shimGetDisplayMedia(window, preferredMediaSource) { |
||||
|
if (window.navigator.mediaDevices && |
||||
|
'getDisplayMedia' in window.navigator.mediaDevices) { |
||||
|
return; |
||||
|
} |
||||
|
if (!(window.navigator.mediaDevices)) { |
||||
|
return; |
||||
|
} |
||||
|
window.navigator.mediaDevices.getDisplayMedia = |
||||
|
function getDisplayMedia(constraints) { |
||||
|
if (!(constraints && constraints.video)) { |
||||
|
const err = new DOMException('getDisplayMedia without video ' + |
||||
|
'constraints is undefined'); |
||||
|
err.name = 'NotFoundError'; |
||||
|
// from https://heycam.github.io/webidl/#idl-DOMException-error-names
|
||||
|
err.code = 8; |
||||
|
return Promise.reject(err); |
||||
|
} |
||||
|
if (constraints.video === true) { |
||||
|
constraints.video = {mediaSource: preferredMediaSource}; |
||||
|
} else { |
||||
|
constraints.video.mediaSource = preferredMediaSource; |
||||
|
} |
||||
|
return window.navigator.mediaDevices.getUserMedia(constraints); |
||||
|
}; |
||||
|
} |
||||
@ -0,0 +1,67 @@ |
|||||
|
/* |
||||
|
* Copyright (c) 2016 The WebRTC project authors. All Rights Reserved. |
||||
|
* |
||||
|
* Use of this source code is governed by a BSD-style license |
||||
|
* that can be found in the LICENSE file in the root of the source |
||||
|
* tree. |
||||
|
*/ |
||||
|
/* eslint-env node */ |
||||
|
'use strict'; |
||||
|
|
||||
|
import * as utils from '../utils'; |
||||
|
|
||||
|
export function shimGetUserMedia(window, browserDetails) { |
||||
|
const navigator = window && window.navigator; |
||||
|
const MediaStreamTrack = window && window.MediaStreamTrack; |
||||
|
|
||||
|
navigator.getUserMedia = function(constraints, onSuccess, onError) { |
||||
|
// Replace Firefox 44+'s deprecation warning with unprefixed version.
|
||||
|
utils.deprecated('navigator.getUserMedia', |
||||
|
'navigator.mediaDevices.getUserMedia'); |
||||
|
navigator.mediaDevices.getUserMedia(constraints).then(onSuccess, onError); |
||||
|
}; |
||||
|
|
||||
|
if (!(browserDetails.version > 55 && |
||||
|
'autoGainControl' in navigator.mediaDevices.getSupportedConstraints())) { |
||||
|
const remap = function(obj, a, b) { |
||||
|
if (a in obj && !(b in obj)) { |
||||
|
obj[b] = obj[a]; |
||||
|
delete obj[a]; |
||||
|
} |
||||
|
}; |
||||
|
|
||||
|
const nativeGetUserMedia = navigator.mediaDevices.getUserMedia. |
||||
|
bind(navigator.mediaDevices); |
||||
|
navigator.mediaDevices.getUserMedia = function(c) { |
||||
|
if (typeof c === 'object' && typeof c.audio === 'object') { |
||||
|
c = JSON.parse(JSON.stringify(c)); |
||||
|
remap(c.audio, 'autoGainControl', 'mozAutoGainControl'); |
||||
|
remap(c.audio, 'noiseSuppression', 'mozNoiseSuppression'); |
||||
|
} |
||||
|
return nativeGetUserMedia(c); |
||||
|
}; |
||||
|
|
||||
|
if (MediaStreamTrack && MediaStreamTrack.prototype.getSettings) { |
||||
|
const nativeGetSettings = MediaStreamTrack.prototype.getSettings; |
||||
|
MediaStreamTrack.prototype.getSettings = function() { |
||||
|
const obj = nativeGetSettings.apply(this, arguments); |
||||
|
remap(obj, 'mozAutoGainControl', 'autoGainControl'); |
||||
|
remap(obj, 'mozNoiseSuppression', 'noiseSuppression'); |
||||
|
return obj; |
||||
|
}; |
||||
|
} |
||||
|
|
||||
|
if (MediaStreamTrack && MediaStreamTrack.prototype.applyConstraints) { |
||||
|
const nativeApplyConstraints = |
||||
|
MediaStreamTrack.prototype.applyConstraints; |
||||
|
MediaStreamTrack.prototype.applyConstraints = function(c) { |
||||
|
if (this.kind === 'audio' && typeof c === 'object') { |
||||
|
c = JSON.parse(JSON.stringify(c)); |
||||
|
remap(c, 'autoGainControl', 'mozAutoGainControl'); |
||||
|
remap(c, 'noiseSuppression', 'mozNoiseSuppression'); |
||||
|
} |
||||
|
return nativeApplyConstraints.apply(this, [c]); |
||||
|
}; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,353 @@ |
|||||
|
/* |
||||
|
* Copyright (c) 2016 The WebRTC project authors. All Rights Reserved. |
||||
|
* |
||||
|
* Use of this source code is governed by a BSD-style license |
||||
|
* that can be found in the LICENSE file in the root of the source |
||||
|
* tree. |
||||
|
*/ |
||||
|
'use strict'; |
||||
|
import * as utils from '../utils'; |
||||
|
|
||||
|
export function shimLocalStreamsAPI(window) { |
||||
|
if (typeof window !== 'object' || !window.RTCPeerConnection) { |
||||
|
return; |
||||
|
} |
||||
|
if (!('getLocalStreams' in window.RTCPeerConnection.prototype)) { |
||||
|
window.RTCPeerConnection.prototype.getLocalStreams = |
||||
|
function getLocalStreams() { |
||||
|
if (!this._localStreams) { |
||||
|
this._localStreams = []; |
||||
|
} |
||||
|
return this._localStreams; |
||||
|
}; |
||||
|
} |
||||
|
if (!('addStream' in window.RTCPeerConnection.prototype)) { |
||||
|
const _addTrack = window.RTCPeerConnection.prototype.addTrack; |
||||
|
window.RTCPeerConnection.prototype.addStream = function addStream(stream) { |
||||
|
if (!this._localStreams) { |
||||
|
this._localStreams = []; |
||||
|
} |
||||
|
if (!this._localStreams.includes(stream)) { |
||||
|
this._localStreams.push(stream); |
||||
|
} |
||||
|
// Try to emulate Chrome's behaviour of adding in audio-video order.
|
||||
|
// Safari orders by track id.
|
||||
|
stream.getAudioTracks().forEach(track => _addTrack.call(this, track, |
||||
|
stream)); |
||||
|
stream.getVideoTracks().forEach(track => _addTrack.call(this, track, |
||||
|
stream)); |
||||
|
}; |
||||
|
|
||||
|
window.RTCPeerConnection.prototype.addTrack = |
||||
|
function addTrack(track, ...streams) { |
||||
|
if (streams) { |
||||
|
streams.forEach((stream) => { |
||||
|
if (!this._localStreams) { |
||||
|
this._localStreams = [stream]; |
||||
|
} else if (!this._localStreams.includes(stream)) { |
||||
|
this._localStreams.push(stream); |
||||
|
} |
||||
|
}); |
||||
|
} |
||||
|
return _addTrack.apply(this, arguments); |
||||
|
}; |
||||
|
} |
||||
|
if (!('removeStream' in window.RTCPeerConnection.prototype)) { |
||||
|
window.RTCPeerConnection.prototype.removeStream = |
||||
|
function removeStream(stream) { |
||||
|
if (!this._localStreams) { |
||||
|
this._localStreams = []; |
||||
|
} |
||||
|
const index = this._localStreams.indexOf(stream); |
||||
|
if (index === -1) { |
||||
|
return; |
||||
|
} |
||||
|
this._localStreams.splice(index, 1); |
||||
|
const tracks = stream.getTracks(); |
||||
|
this.getSenders().forEach(sender => { |
||||
|
if (tracks.includes(sender.track)) { |
||||
|
this.removeTrack(sender); |
||||
|
} |
||||
|
}); |
||||
|
}; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
export function shimRemoteStreamsAPI(window) { |
||||
|
if (typeof window !== 'object' || !window.RTCPeerConnection) { |
||||
|
return; |
||||
|
} |
||||
|
if (!('getRemoteStreams' in window.RTCPeerConnection.prototype)) { |
||||
|
window.RTCPeerConnection.prototype.getRemoteStreams = |
||||
|
function getRemoteStreams() { |
||||
|
return this._remoteStreams ? this._remoteStreams : []; |
||||
|
}; |
||||
|
} |
||||
|
if (!('onaddstream' in window.RTCPeerConnection.prototype)) { |
||||
|
Object.defineProperty(window.RTCPeerConnection.prototype, 'onaddstream', { |
||||
|
get() { |
||||
|
return this._onaddstream; |
||||
|
}, |
||||
|
set(f) { |
||||
|
if (this._onaddstream) { |
||||
|
this.removeEventListener('addstream', this._onaddstream); |
||||
|
this.removeEventListener('track', this._onaddstreampoly); |
||||
|
} |
||||
|
this.addEventListener('addstream', this._onaddstream = f); |
||||
|
this.addEventListener('track', this._onaddstreampoly = (e) => { |
||||
|
e.streams.forEach(stream => { |
||||
|
if (!this._remoteStreams) { |
||||
|
this._remoteStreams = []; |
||||
|
} |
||||
|
if (this._remoteStreams.includes(stream)) { |
||||
|
return; |
||||
|
} |
||||
|
this._remoteStreams.push(stream); |
||||
|
const event = new Event('addstream'); |
||||
|
event.stream = stream; |
||||
|
this.dispatchEvent(event); |
||||
|
}); |
||||
|
}); |
||||
|
} |
||||
|
}); |
||||
|
const origSetRemoteDescription = |
||||
|
window.RTCPeerConnection.prototype.setRemoteDescription; |
||||
|
window.RTCPeerConnection.prototype.setRemoteDescription = |
||||
|
function setRemoteDescription() { |
||||
|
const pc = this; |
||||
|
if (!this._onaddstreampoly) { |
||||
|
this.addEventListener('track', this._onaddstreampoly = function(e) { |
||||
|
e.streams.forEach(stream => { |
||||
|
if (!pc._remoteStreams) { |
||||
|
pc._remoteStreams = []; |
||||
|
} |
||||
|
if (pc._remoteStreams.indexOf(stream) >= 0) { |
||||
|
return; |
||||
|
} |
||||
|
pc._remoteStreams.push(stream); |
||||
|
const event = new Event('addstream'); |
||||
|
event.stream = stream; |
||||
|
pc.dispatchEvent(event); |
||||
|
}); |
||||
|
}); |
||||
|
} |
||||
|
return origSetRemoteDescription.apply(pc, arguments); |
||||
|
}; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
export function shimCallbacksAPI(window) { |
||||
|
if (typeof window !== 'object' || !window.RTCPeerConnection) { |
||||
|
return; |
||||
|
} |
||||
|
const prototype = window.RTCPeerConnection.prototype; |
||||
|
const origCreateOffer = prototype.createOffer; |
||||
|
const origCreateAnswer = prototype.createAnswer; |
||||
|
const setLocalDescription = prototype.setLocalDescription; |
||||
|
const setRemoteDescription = prototype.setRemoteDescription; |
||||
|
const addIceCandidate = prototype.addIceCandidate; |
||||
|
|
||||
|
prototype.createOffer = |
||||
|
function createOffer(successCallback, failureCallback) { |
||||
|
const options = (arguments.length >= 2) ? arguments[2] : arguments[0]; |
||||
|
const promise = origCreateOffer.apply(this, [options]); |
||||
|
if (!failureCallback) { |
||||
|
return promise; |
||||
|
} |
||||
|
promise.then(successCallback, failureCallback); |
||||
|
return Promise.resolve(); |
||||
|
}; |
||||
|
|
||||
|
prototype.createAnswer = |
||||
|
function createAnswer(successCallback, failureCallback) { |
||||
|
const options = (arguments.length >= 2) ? arguments[2] : arguments[0]; |
||||
|
const promise = origCreateAnswer.apply(this, [options]); |
||||
|
if (!failureCallback) { |
||||
|
return promise; |
||||
|
} |
||||
|
promise.then(successCallback, failureCallback); |
||||
|
return Promise.resolve(); |
||||
|
}; |
||||
|
|
||||
|
let withCallback = function(description, successCallback, failureCallback) { |
||||
|
const promise = setLocalDescription.apply(this, [description]); |
||||
|
if (!failureCallback) { |
||||
|
return promise; |
||||
|
} |
||||
|
promise.then(successCallback, failureCallback); |
||||
|
return Promise.resolve(); |
||||
|
}; |
||||
|
prototype.setLocalDescription = withCallback; |
||||
|
|
||||
|
withCallback = function(description, successCallback, failureCallback) { |
||||
|
const promise = setRemoteDescription.apply(this, [description]); |
||||
|
if (!failureCallback) { |
||||
|
return promise; |
||||
|
} |
||||
|
promise.then(successCallback, failureCallback); |
||||
|
return Promise.resolve(); |
||||
|
}; |
||||
|
prototype.setRemoteDescription = withCallback; |
||||
|
|
||||
|
withCallback = function(candidate, successCallback, failureCallback) { |
||||
|
const promise = addIceCandidate.apply(this, [candidate]); |
||||
|
if (!failureCallback) { |
||||
|
return promise; |
||||
|
} |
||||
|
promise.then(successCallback, failureCallback); |
||||
|
return Promise.resolve(); |
||||
|
}; |
||||
|
prototype.addIceCandidate = withCallback; |
||||
|
} |
||||
|
|
||||
|
export function shimGetUserMedia(window) { |
||||
|
const navigator = window && window.navigator; |
||||
|
|
||||
|
if (navigator.mediaDevices && navigator.mediaDevices.getUserMedia) { |
||||
|
// shim not needed in Safari 12.1
|
||||
|
const mediaDevices = navigator.mediaDevices; |
||||
|
const _getUserMedia = mediaDevices.getUserMedia.bind(mediaDevices); |
||||
|
navigator.mediaDevices.getUserMedia = (constraints) => { |
||||
|
return _getUserMedia(shimConstraints(constraints)); |
||||
|
}; |
||||
|
} |
||||
|
|
||||
|
if (!navigator.getUserMedia && navigator.mediaDevices && |
||||
|
navigator.mediaDevices.getUserMedia) { |
||||
|
navigator.getUserMedia = function getUserMedia(constraints, cb, errcb) { |
||||
|
navigator.mediaDevices.getUserMedia(constraints) |
||||
|
.then(cb, errcb); |
||||
|
}.bind(navigator); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
export function shimConstraints(constraints) { |
||||
|
if (constraints && constraints.video !== undefined) { |
||||
|
return Object.assign({}, |
||||
|
constraints, |
||||
|
{video: utils.compactObject(constraints.video)} |
||||
|
); |
||||
|
} |
||||
|
|
||||
|
return constraints; |
||||
|
} |
||||
|
|
||||
|
export function shimRTCIceServerUrls(window) { |
||||
|
if (!window.RTCPeerConnection) { |
||||
|
return; |
||||
|
} |
||||
|
// migrate from non-spec RTCIceServer.url to RTCIceServer.urls
|
||||
|
const OrigPeerConnection = window.RTCPeerConnection; |
||||
|
window.RTCPeerConnection = |
||||
|
function RTCPeerConnection(pcConfig, pcConstraints) { |
||||
|
if (pcConfig && pcConfig.iceServers) { |
||||
|
const newIceServers = []; |
||||
|
for (let i = 0; i < pcConfig.iceServers.length; i++) { |
||||
|
let server = pcConfig.iceServers[i]; |
||||
|
if (!server.hasOwnProperty('urls') && |
||||
|
server.hasOwnProperty('url')) { |
||||
|
utils.deprecated('RTCIceServer.url', 'RTCIceServer.urls'); |
||||
|
server = JSON.parse(JSON.stringify(server)); |
||||
|
server.urls = server.url; |
||||
|
delete server.url; |
||||
|
newIceServers.push(server); |
||||
|
} else { |
||||
|
newIceServers.push(pcConfig.iceServers[i]); |
||||
|
} |
||||
|
} |
||||
|
pcConfig.iceServers = newIceServers; |
||||
|
} |
||||
|
return new OrigPeerConnection(pcConfig, pcConstraints); |
||||
|
}; |
||||
|
window.RTCPeerConnection.prototype = OrigPeerConnection.prototype; |
||||
|
// wrap static methods. Currently just generateCertificate.
|
||||
|
if ('generateCertificate' in OrigPeerConnection) { |
||||
|
Object.defineProperty(window.RTCPeerConnection, 'generateCertificate', { |
||||
|
get() { |
||||
|
return OrigPeerConnection.generateCertificate; |
||||
|
} |
||||
|
}); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
export function shimTrackEventTransceiver(window) { |
||||
|
// Add event.transceiver member over deprecated event.receiver
|
||||
|
if (typeof window === 'object' && window.RTCTrackEvent && |
||||
|
'receiver' in window.RTCTrackEvent.prototype && |
||||
|
!('transceiver' in window.RTCTrackEvent.prototype)) { |
||||
|
Object.defineProperty(window.RTCTrackEvent.prototype, 'transceiver', { |
||||
|
get() { |
||||
|
return {receiver: this.receiver}; |
||||
|
} |
||||
|
}); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
export function shimCreateOfferLegacy(window) { |
||||
|
const origCreateOffer = window.RTCPeerConnection.prototype.createOffer; |
||||
|
window.RTCPeerConnection.prototype.createOffer = |
||||
|
function createOffer(offerOptions) { |
||||
|
if (offerOptions) { |
||||
|
if (typeof offerOptions.offerToReceiveAudio !== 'undefined') { |
||||
|
// support bit values
|
||||
|
offerOptions.offerToReceiveAudio = |
||||
|
!!offerOptions.offerToReceiveAudio; |
||||
|
} |
||||
|
const audioTransceiver = this.getTransceivers().find(transceiver => |
||||
|
transceiver.receiver.track.kind === 'audio'); |
||||
|
if (offerOptions.offerToReceiveAudio === false && audioTransceiver) { |
||||
|
if (audioTransceiver.direction === 'sendrecv') { |
||||
|
if (audioTransceiver.setDirection) { |
||||
|
audioTransceiver.setDirection('sendonly'); |
||||
|
} else { |
||||
|
audioTransceiver.direction = 'sendonly'; |
||||
|
} |
||||
|
} else if (audioTransceiver.direction === 'recvonly') { |
||||
|
if (audioTransceiver.setDirection) { |
||||
|
audioTransceiver.setDirection('inactive'); |
||||
|
} else { |
||||
|
audioTransceiver.direction = 'inactive'; |
||||
|
} |
||||
|
} |
||||
|
} else if (offerOptions.offerToReceiveAudio === true && |
||||
|
!audioTransceiver) { |
||||
|
this.addTransceiver('audio', {direction: 'recvonly'}); |
||||
|
} |
||||
|
|
||||
|
if (typeof offerOptions.offerToReceiveVideo !== 'undefined') { |
||||
|
// support bit values
|
||||
|
offerOptions.offerToReceiveVideo = |
||||
|
!!offerOptions.offerToReceiveVideo; |
||||
|
} |
||||
|
const videoTransceiver = this.getTransceivers().find(transceiver => |
||||
|
transceiver.receiver.track.kind === 'video'); |
||||
|
if (offerOptions.offerToReceiveVideo === false && videoTransceiver) { |
||||
|
if (videoTransceiver.direction === 'sendrecv') { |
||||
|
if (videoTransceiver.setDirection) { |
||||
|
videoTransceiver.setDirection('sendonly'); |
||||
|
} else { |
||||
|
videoTransceiver.direction = 'sendonly'; |
||||
|
} |
||||
|
} else if (videoTransceiver.direction === 'recvonly') { |
||||
|
if (videoTransceiver.setDirection) { |
||||
|
videoTransceiver.setDirection('inactive'); |
||||
|
} else { |
||||
|
videoTransceiver.direction = 'inactive'; |
||||
|
} |
||||
|
} |
||||
|
} else if (offerOptions.offerToReceiveVideo === true && |
||||
|
!videoTransceiver) { |
||||
|
this.addTransceiver('video', {direction: 'recvonly'}); |
||||
|
} |
||||
|
} |
||||
|
return origCreateOffer.apply(this, arguments); |
||||
|
}; |
||||
|
} |
||||
|
|
||||
|
export function shimAudioContext(window) { |
||||
|
if (typeof window !== 'object' || window.AudioContext) { |
||||
|
return; |
||||
|
} |
||||
|
window.AudioContext = window.webkitAudioContext; |
||||
|
} |
||||
|
|
||||
@ -0,0 +1,263 @@ |
|||||
|
/* |
||||
|
* Copyright (c) 2016 The WebRTC project authors. All Rights Reserved. |
||||
|
* |
||||
|
* Use of this source code is governed by a BSD-style license |
||||
|
* that can be found in the LICENSE file in the root of the source |
||||
|
* tree. |
||||
|
*/ |
||||
|
/* eslint-env node */ |
||||
|
'use strict'; |
||||
|
|
||||
|
let logDisabled_ = true; |
||||
|
let deprecationWarnings_ = true; |
||||
|
|
||||
|
/** |
||||
|
* Extract browser version out of the provided user agent string. |
||||
|
* |
||||
|
* @param {!string} uastring userAgent string. |
||||
|
* @param {!string} expr Regular expression used as match criteria. |
||||
|
* @param {!number} pos position in the version string to be returned. |
||||
|
* @return {!number} browser version. |
||||
|
*/ |
||||
|
export function extractVersion(uastring, expr, pos) { |
||||
|
const match = uastring.match(expr); |
||||
|
return match && match.length >= pos && parseInt(match[pos], 10); |
||||
|
} |
||||
|
|
||||
|
// Wraps the peerconnection event eventNameToWrap in a function
|
||||
|
// which returns the modified event object (or false to prevent
|
||||
|
// the event).
|
||||
|
export function wrapPeerConnectionEvent(window, eventNameToWrap, wrapper) { |
||||
|
if (!window.RTCPeerConnection) { |
||||
|
return; |
||||
|
} |
||||
|
const proto = window.RTCPeerConnection.prototype; |
||||
|
const nativeAddEventListener = proto.addEventListener; |
||||
|
proto.addEventListener = function(nativeEventName, cb) { |
||||
|
if (nativeEventName !== eventNameToWrap) { |
||||
|
return nativeAddEventListener.apply(this, arguments); |
||||
|
} |
||||
|
const wrappedCallback = (e) => { |
||||
|
const modifiedEvent = wrapper(e); |
||||
|
if (modifiedEvent) { |
||||
|
if (cb.handleEvent) { |
||||
|
cb.handleEvent(modifiedEvent); |
||||
|
} else { |
||||
|
cb(modifiedEvent); |
||||
|
} |
||||
|
} |
||||
|
}; |
||||
|
this._eventMap = this._eventMap || {}; |
||||
|
if (!this._eventMap[eventNameToWrap]) { |
||||
|
this._eventMap[eventNameToWrap] = new Map(); |
||||
|
} |
||||
|
this._eventMap[eventNameToWrap].set(cb, wrappedCallback); |
||||
|
return nativeAddEventListener.apply(this, [nativeEventName, |
||||
|
wrappedCallback]); |
||||
|
}; |
||||
|
|
||||
|
const nativeRemoveEventListener = proto.removeEventListener; |
||||
|
proto.removeEventListener = function(nativeEventName, cb) { |
||||
|
if (nativeEventName !== eventNameToWrap || !this._eventMap |
||||
|
|| !this._eventMap[eventNameToWrap]) { |
||||
|
return nativeRemoveEventListener.apply(this, arguments); |
||||
|
} |
||||
|
if (!this._eventMap[eventNameToWrap].has(cb)) { |
||||
|
return nativeRemoveEventListener.apply(this, arguments); |
||||
|
} |
||||
|
const unwrappedCb = this._eventMap[eventNameToWrap].get(cb); |
||||
|
this._eventMap[eventNameToWrap].delete(cb); |
||||
|
if (this._eventMap[eventNameToWrap].size === 0) { |
||||
|
delete this._eventMap[eventNameToWrap]; |
||||
|
} |
||||
|
if (Object.keys(this._eventMap).length === 0) { |
||||
|
delete this._eventMap; |
||||
|
} |
||||
|
return nativeRemoveEventListener.apply(this, [nativeEventName, |
||||
|
unwrappedCb]); |
||||
|
}; |
||||
|
|
||||
|
Object.defineProperty(proto, 'on' + eventNameToWrap, { |
||||
|
get() { |
||||
|
return this['_on' + eventNameToWrap]; |
||||
|
}, |
||||
|
set(cb) { |
||||
|
if (this['_on' + eventNameToWrap]) { |
||||
|
this.removeEventListener(eventNameToWrap, |
||||
|
this['_on' + eventNameToWrap]); |
||||
|
delete this['_on' + eventNameToWrap]; |
||||
|
} |
||||
|
if (cb) { |
||||
|
this.addEventListener(eventNameToWrap, |
||||
|
this['_on' + eventNameToWrap] = cb); |
||||
|
} |
||||
|
}, |
||||
|
enumerable: true, |
||||
|
configurable: true |
||||
|
}); |
||||
|
} |
||||
|
|
||||
|
export function disableLog(bool) { |
||||
|
if (typeof bool !== 'boolean') { |
||||
|
return new Error('Argument type: ' + typeof bool + |
||||
|
'. Please use a boolean.'); |
||||
|
} |
||||
|
logDisabled_ = bool; |
||||
|
return (bool) ? 'adapter.js logging disabled' : |
||||
|
'adapter.js logging enabled'; |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* Disable or enable deprecation warnings |
||||
|
* @param {!boolean} bool set to true to disable warnings. |
||||
|
*/ |
||||
|
export function disableWarnings(bool) { |
||||
|
if (typeof bool !== 'boolean') { |
||||
|
return new Error('Argument type: ' + typeof bool + |
||||
|
'. Please use a boolean.'); |
||||
|
} |
||||
|
deprecationWarnings_ = !bool; |
||||
|
return 'adapter.js deprecation warnings ' + (bool ? 'disabled' : 'enabled'); |
||||
|
} |
||||
|
|
||||
|
export function log() { |
||||
|
if (typeof window === 'object') { |
||||
|
if (logDisabled_) { |
||||
|
return; |
||||
|
} |
||||
|
if (typeof console !== 'undefined' && typeof console.log === 'function') { |
||||
|
console.log.apply(console, arguments); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* Shows a deprecation warning suggesting the modern and spec-compatible API. |
||||
|
*/ |
||||
|
export function deprecated(oldMethod, newMethod) { |
||||
|
if (!deprecationWarnings_) { |
||||
|
return; |
||||
|
} |
||||
|
console.warn(oldMethod + ' is deprecated, please use ' + newMethod + |
||||
|
' instead.'); |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* Browser detector. |
||||
|
* |
||||
|
* @return {object} result containing browser and version |
||||
|
* properties. |
||||
|
*/ |
||||
|
export function detectBrowser(window) { |
||||
|
// Returned result object.
|
||||
|
const result = {browser: null, version: null}; |
||||
|
|
||||
|
// Fail early if it's not a browser
|
||||
|
if (typeof window === 'undefined' || !window.navigator) { |
||||
|
result.browser = 'Not a browser.'; |
||||
|
return result; |
||||
|
} |
||||
|
|
||||
|
const {navigator} = window; |
||||
|
|
||||
|
if (navigator.mozGetUserMedia) { // Firefox.
|
||||
|
result.browser = 'firefox'; |
||||
|
result.version = extractVersion(navigator.userAgent, |
||||
|
/Firefox\/(\d+)\./, 1); |
||||
|
} else if (navigator.webkitGetUserMedia || |
||||
|
(window.isSecureContext === false && window.webkitRTCPeerConnection && |
||||
|
!window.RTCIceGatherer)) { |
||||
|
// Chrome, Chromium, Webview, Opera.
|
||||
|
// Version matches Chrome/WebRTC version.
|
||||
|
// Chrome 74 removed webkitGetUserMedia on http as well so we need the
|
||||
|
// more complicated fallback to webkitRTCPeerConnection.
|
||||
|
result.browser = 'chrome'; |
||||
|
result.version = extractVersion(navigator.userAgent, |
||||
|
/Chrom(e|ium)\/(\d+)\./, 2); |
||||
|
} else if (window.RTCPeerConnection && |
||||
|
navigator.userAgent.match(/AppleWebKit\/(\d+)\./)) { // Safari.
|
||||
|
result.browser = 'safari'; |
||||
|
result.version = extractVersion(navigator.userAgent, |
||||
|
/AppleWebKit\/(\d+)\./, 1); |
||||
|
result.supportsUnifiedPlan = window.RTCRtpTransceiver && |
||||
|
'currentDirection' in window.RTCRtpTransceiver.prototype; |
||||
|
} else { // Default fallthrough: not supported.
|
||||
|
result.browser = 'Not a supported browser.'; |
||||
|
return result; |
||||
|
} |
||||
|
|
||||
|
return result; |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* Checks if something is an object. |
||||
|
* |
||||
|
* @param {*} val The something you want to check. |
||||
|
* @return true if val is an object, false otherwise. |
||||
|
*/ |
||||
|
function isObject(val) { |
||||
|
return Object.prototype.toString.call(val) === '[object Object]'; |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* Remove all empty objects and undefined values |
||||
|
* from a nested object -- an enhanced and vanilla version |
||||
|
* of Lodash's `compact`. |
||||
|
*/ |
||||
|
export function compactObject(data) { |
||||
|
if (!isObject(data)) { |
||||
|
return data; |
||||
|
} |
||||
|
|
||||
|
return Object.keys(data).reduce(function(accumulator, key) { |
||||
|
const isObj = isObject(data[key]); |
||||
|
const value = isObj ? compactObject(data[key]) : data[key]; |
||||
|
const isEmptyObject = isObj && !Object.keys(value).length; |
||||
|
if (value === undefined || isEmptyObject) { |
||||
|
return accumulator; |
||||
|
} |
||||
|
return Object.assign(accumulator, {[key]: value}); |
||||
|
}, {}); |
||||
|
} |
||||
|
|
||||
|
/* iterates the stats graph recursively. */ |
||||
|
export function walkStats(stats, base, resultSet) { |
||||
|
if (!base || resultSet.has(base.id)) { |
||||
|
return; |
||||
|
} |
||||
|
resultSet.set(base.id, base); |
||||
|
Object.keys(base).forEach(name => { |
||||
|
if (name.endsWith('Id')) { |
||||
|
walkStats(stats, stats.get(base[name]), resultSet); |
||||
|
} else if (name.endsWith('Ids')) { |
||||
|
base[name].forEach(id => { |
||||
|
walkStats(stats, stats.get(id), resultSet); |
||||
|
}); |
||||
|
} |
||||
|
}); |
||||
|
} |
||||
|
|
||||
|
/* filter getStats for a sender/receiver track. */ |
||||
|
export function filterStats(result, track, outbound) { |
||||
|
const streamStatsType = outbound ? 'outbound-rtp' : 'inbound-rtp'; |
||||
|
const filteredResult = new Map(); |
||||
|
if (track === null) { |
||||
|
return filteredResult; |
||||
|
} |
||||
|
const trackStats = []; |
||||
|
result.forEach(value => { |
||||
|
if (value.type === 'track' && |
||||
|
value.trackIdentifier === track.id) { |
||||
|
trackStats.push(value); |
||||
|
} |
||||
|
}); |
||||
|
trackStats.forEach(trackStat => { |
||||
|
result.forEach(stats => { |
||||
|
if (stats.type === streamStatsType && stats.trackId === trackStat.id) { |
||||
|
walkStats(result, stats, filteredResult); |
||||
|
} |
||||
|
}); |
||||
|
}); |
||||
|
return filteredResult; |
||||
|
} |
||||
|
|
||||
@ -0,0 +1,20 @@ |
|||||
|
<!DOCTYPE html> |
||||
|
<html lang="en"> |
||||
|
<head> |
||||
|
<meta charset="UTF-8" /> |
||||
|
<script> |
||||
|
var coverSupport = 'CSS' in window && typeof CSS.supports === 'function' && (CSS.supports('top: env(a)') || |
||||
|
CSS.supports('top: constant(a)')) |
||||
|
document.write( |
||||
|
'<meta name="viewport" content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0' + |
||||
|
(coverSupport ? ', viewport-fit=cover' : '') + '" />') |
||||
|
</script> |
||||
|
<title></title> |
||||
|
<!--preload-links--> |
||||
|
<!--app-context--> |
||||
|
</head> |
||||
|
<body> |
||||
|
<div id="app"><!--app-html--></div> |
||||
|
<script type="module" src="/main.js"></script> |
||||
|
</body> |
||||
|
</html> |
||||
@ -0,0 +1,54 @@ |
|||||
|
|
||||
|
// #ifdef H5
|
||||
|
import quill from "quill"; |
||||
|
window.Quill = quill; |
||||
|
// #endif
|
||||
|
|
||||
|
// #ifndef VUE3
|
||||
|
import Vue from 'vue' |
||||
|
import App from './App' |
||||
|
|
||||
|
Vue.config.productionTip = false |
||||
|
|
||||
|
import cuCustom from './components/cu-custom' |
||||
|
Vue.component('cu-custom',cuCustom) |
||||
|
|
||||
|
|
||||
|
App.mpType = 'app' |
||||
|
|
||||
|
const app = new Vue({ |
||||
|
...App |
||||
|
}) |
||||
|
app.$mount() |
||||
|
// #endif
|
||||
|
|
||||
|
|
||||
|
|
||||
|
// #ifdef VUE3
|
||||
|
import { createSSRApp } from 'vue' |
||||
|
import App from './App.vue' |
||||
|
import cuCustom from './components/cu-custom' |
||||
|
import util from '@/utils/utils.js' |
||||
|
import store from './store' |
||||
|
import '@/utils/request' |
||||
|
import api from '@/api/index.js'; |
||||
|
import socketIO from '@/common/socket.js'; |
||||
|
import Empty from "@/components/Empty.vue" //通用空状态
|
||||
|
import Tags from "@/components/Tags.vue" //通用标签
|
||||
|
|
||||
|
export function createApp() { |
||||
|
const app = createSSRApp(App) |
||||
|
app.config.globalProperties.appStatus=true; |
||||
|
app.config.globalProperties.$util = util; |
||||
|
app.config.globalProperties.$api = api; |
||||
|
app.config.globalProperties.$store = store; |
||||
|
app.config.globalProperties.socketIo = new socketIO() |
||||
|
app.component('cu-custom',cuCustom) |
||||
|
app.component('Empty',Empty) |
||||
|
app.component('Tags',Tags) |
||||
|
app.use(store) |
||||
|
return { |
||||
|
app |
||||
|
} |
||||
|
} |
||||
|
// #endif
|
||||
@ -0,0 +1,210 @@ |
|||||
|
{ |
||||
|
"name" : "Raingad-IM", |
||||
|
"appid" : "__UNI__A902A60", |
||||
|
"description" : "聊天小应用", |
||||
|
"versionName" : "5.5.2", |
||||
|
"versionCode" : 20250107, |
||||
|
"transformPx" : false, |
||||
|
"app-plus" : { |
||||
|
/* 5+App特有相关 */ |
||||
|
"usingComponents" : true, |
||||
|
"nvueCompiler" : "uni-app", |
||||
|
"compilerVersion" : 3, |
||||
|
"nvueStyleCompiler" : "uni-app", |
||||
|
"splashscreen" : { |
||||
|
"alwaysShowBeforeRender" : true, |
||||
|
"waiting" : true, |
||||
|
"autoclose" : true, |
||||
|
"delay" : 0 |
||||
|
}, |
||||
|
"modules" : { |
||||
|
"VideoPlayer" : {}, |
||||
|
"Record" : {}, |
||||
|
"UIWebview" : {}, |
||||
|
"Camera" : {}, |
||||
|
"Barcode" : {}, |
||||
|
"Push" : {}, |
||||
|
"SQLite" : {} |
||||
|
}, |
||||
|
/* 模块配置 */ |
||||
|
"distribute" : { |
||||
|
/* 应用发布信息 */ |
||||
|
"android" : { |
||||
|
/* android打包配置 */ |
||||
|
"permissions" : [ |
||||
|
"<uses-feature android:name=\"android.hardware.camera\"/>", |
||||
|
"<uses-feature android:name=\"android.hardware.camera.autofocus\"/>", |
||||
|
"<uses-permission android:name=\"android.permission.ACCESS_NETWORK_STATE\"/>", |
||||
|
"<uses-permission android:name=\"android.permission.ACCESS_WIFI_STATE\"/>", |
||||
|
"<uses-permission android:name=\"android.permission.CALL_PHONE\"/>", |
||||
|
"<uses-permission android:name=\"android.permission.CAMERA\"/>", |
||||
|
"<uses-permission android:name=\"android.permission.CHANGE_NETWORK_STATE\"/>", |
||||
|
"<uses-permission android:name=\"android.permission.CHANGE_WIFI_STATE\"/>", |
||||
|
"<uses-permission android:name=\"android.permission.FLASHLIGHT\"/>", |
||||
|
"<uses-permission android:name=\"android.permission.GET_ACCOUNTS\"/>", |
||||
|
"<uses-permission android:name=\"android.permission.MANAGE_APP_TOKENS\"/>", |
||||
|
"<uses-permission android:name=\"android.permission.MANAGE_DOCUMENTS\"/>", |
||||
|
"<uses-permission android:name=\"android.permission.MOUNT_UNMOUNT_FILESYSTEMS\"/>", |
||||
|
"<uses-permission android:name=\"android.permission.READ_EXTERNAL_STORAGE\"/>", |
||||
|
"<uses-permission android:name=\"android.permission.READ_LOGS\"/>", |
||||
|
"<uses-permission android:name=\"android.permission.READ_PHONE_STATE\"/>", |
||||
|
"<uses-permission android:name=\"android.permission.RECORD_AUDIO\"/>", |
||||
|
"<uses-permission android:name=\"android.permission.VIBRATE\"/>", |
||||
|
"<uses-permission android:name=\"android.permission.WAKE_LOCK\"/>", |
||||
|
"<uses-permission android:name=\"android.permission.WRITE_EXTERNAL_STORAGE\"/>", |
||||
|
"<uses-permission android:name=\"android.permission.WRITE_SETTINGS\"/>", |
||||
|
"<uses-permission android:name=\"android.permission.INSTALL_PACKAGES\"/>", |
||||
|
"<uses-permission android:name=\"android.permission.REQUEST_INSTALL_PACKAGES\"/>" |
||||
|
], |
||||
|
"abiFilters" : [ "armeabi-v7a", "arm64-v8a", "x86" ], |
||||
|
"minSdkVersion" : 21, |
||||
|
"targetSdkVersion" : "" |
||||
|
}, |
||||
|
"ios" : { |
||||
|
"dSYMs" : false, |
||||
|
"privacyDescription" : { |
||||
|
"NSPhotoLibraryUsageDescription" : "需要发送图片消息", |
||||
|
"NSPhotoLibraryAddUsageDescription" : "需要下载图片或者视频", |
||||
|
"NSCameraUsageDescription" : "需要拍照照片或者视频发送给好友", |
||||
|
"NSMicrophoneUsageDescription" : "需要和好友语音通话或者发送语音消息", |
||||
|
"NSLocationWhenInUseUsageDescription" : "APP将要获取你的位置信息,用于保持APP的活跃,并不会记录您的位置。是否允许?", |
||||
|
"NSLocationAlwaysUsageDescription" : "App将要在后台持续获取您的位置,用于保持APP的活跃,并不会记录您的位置。是否允许?", |
||||
|
"NSLocationAlwaysAndWhenInUseUsageDescription" : "APP将要获取你的位置信息,用于保持APP的活跃,并不会记录您的位置。是否允许?" |
||||
|
}, |
||||
|
"UIBackgroundModes" : "audio,location" |
||||
|
}, |
||||
|
"huawei" : { |
||||
|
"permissions" : [ |
||||
|
"ohos.permission.GET_NETWORK_INFO" // HarmonyOS 网络状态权限 |
||||
|
] |
||||
|
}, |
||||
|
/* ios打包配置 */ |
||||
|
"sdkConfigs" : { |
||||
|
"ad" : {}, |
||||
|
"push" : {}, |
||||
|
"maps" : { |
||||
|
"amap" : { |
||||
|
"name" : "", |
||||
|
"appkey_ios" : "", |
||||
|
"appkey_android" : "" |
||||
|
} |
||||
|
}, |
||||
|
"geolocation" : { |
||||
|
"system" : { |
||||
|
"__platform__" : [ "ios", "android" ] |
||||
|
} |
||||
|
} |
||||
|
}, |
||||
|
"icons" : { |
||||
|
"android" : { |
||||
|
"hdpi" : "unpackage/res/icons/72x72.png", |
||||
|
"xhdpi" : "unpackage/res/icons/96x96.png", |
||||
|
"xxhdpi" : "unpackage/res/icons/144x144.png", |
||||
|
"xxxhdpi" : "unpackage/res/icons/192x192.png" |
||||
|
}, |
||||
|
"ios" : { |
||||
|
"appstore" : "unpackage/res/icons/1024x1024.png", |
||||
|
"ipad" : { |
||||
|
"app" : "unpackage/res/icons/76x76.png", |
||||
|
"app@2x" : "unpackage/res/icons/152x152.png", |
||||
|
"notification" : "unpackage/res/icons/20x20.png", |
||||
|
"notification@2x" : "unpackage/res/icons/40x40.png", |
||||
|
"proapp@2x" : "unpackage/res/icons/167x167.png", |
||||
|
"settings" : "unpackage/res/icons/29x29.png", |
||||
|
"settings@2x" : "unpackage/res/icons/58x58.png", |
||||
|
"spotlight" : "unpackage/res/icons/40x40.png", |
||||
|
"spotlight@2x" : "unpackage/res/icons/80x80.png" |
||||
|
}, |
||||
|
"iphone" : { |
||||
|
"app@2x" : "unpackage/res/icons/120x120.png", |
||||
|
"app@3x" : "unpackage/res/icons/180x180.png", |
||||
|
"notification@2x" : "unpackage/res/icons/40x40.png", |
||||
|
"notification@3x" : "unpackage/res/icons/60x60.png", |
||||
|
"settings@2x" : "unpackage/res/icons/58x58.png", |
||||
|
"settings@3x" : "unpackage/res/icons/87x87.png", |
||||
|
"spotlight@2x" : "unpackage/res/icons/80x80.png", |
||||
|
"spotlight@3x" : "unpackage/res/icons/120x120.png" |
||||
|
} |
||||
|
} |
||||
|
}, |
||||
|
"splashscreen" : { |
||||
|
"useOriginalMsgbox" : true |
||||
|
} |
||||
|
}, |
||||
|
"nativePlugins" : { |
||||
|
"lemonjk-FileSelect" : { |
||||
|
"__plugin_info__" : { |
||||
|
"name" : "FileSelect", |
||||
|
"description" : "文件选取插件", |
||||
|
"platforms" : "Android,iOS", |
||||
|
"url" : "", |
||||
|
"android_package_name" : "", |
||||
|
"ios_bundle_id" : "", |
||||
|
"isCloud" : false, |
||||
|
"bought" : -1, |
||||
|
"pid" : "", |
||||
|
"parameters" : {} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
}, |
||||
|
// "Ba-KeepAlive" : { |
||||
|
// "__plugin_info__" : { |
||||
|
// "name" : "安卓保活(采用多种主流技术) Ba-KeepAlive", |
||||
|
// "description" : "原生保活插件,支持市面上大部分机型,Android4.4到13.0 。为定位、推送、websocket、定时任务、蓝牙、聊天等保驾护航(**注意:**不保证支持所有机型和场景,建议先试用再购买)", |
||||
|
// "platforms" : "Android", |
||||
|
// "url" : "https://ext.dcloud.net.cn/plugin?id=9423", |
||||
|
// "android_package_name" : "uni.UNID1E78EC", |
||||
|
// "ios_bundle_id" : "", |
||||
|
// "isCloud" : true, |
||||
|
// "bought" : 1, |
||||
|
// "pid" : "9423", |
||||
|
// "parameters" : {} |
||||
|
// } |
||||
|
// } |
||||
|
/* SDK配置 */ |
||||
|
"quickapp" : {}, |
||||
|
/* 快应用特有相关 */ |
||||
|
"mp-weixin" : { |
||||
|
/* 小程序特有相关 */ |
||||
|
"appid" : "wxd36ac7a23fbfcfea", |
||||
|
"setting" : { |
||||
|
"urlCheck" : false, |
||||
|
"minified" : true, |
||||
|
"ignoreDevUnusedFiles" : false, |
||||
|
"ignoreUploadUnusedFiles" : false |
||||
|
}, |
||||
|
"lazyCodeLoading" : "requiredComponents", |
||||
|
"usingComponents" : true, |
||||
|
"permission" : {}, |
||||
|
"unipush" : { |
||||
|
"enable" : false |
||||
|
}, |
||||
|
"libVersion" : "latest" |
||||
|
}, |
||||
|
"vueVersion" : "3", |
||||
|
"h5" : { |
||||
|
"router" : { |
||||
|
"mode" : "hash", |
||||
|
"base" : "./" |
||||
|
}, |
||||
|
"devServer" : { |
||||
|
"https" : false, |
||||
|
"port" : "" |
||||
|
}, |
||||
|
"title" : "raingad-IM", |
||||
|
"unipush" : { |
||||
|
"enable" : false |
||||
|
}, |
||||
|
"sdkConfigs" : { |
||||
|
"maps" : { |
||||
|
"amap" : { |
||||
|
"key" : "", |
||||
|
"securityJsCode" : "", |
||||
|
"serviceHost" : "" |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
}, |
||||
|
"fallbackLocale" : "zh-Hans" |
||||
|
} |
||||
@ -0,0 +1,144 @@ |
|||||
|
import config from '@/common/config.js' |
||||
|
export const chat = { |
||||
|
data() { |
||||
|
return { |
||||
|
network_log:'', |
||||
|
apiUrl: config.apiUrl, |
||||
|
emojiMap:'' |
||||
|
} |
||||
|
}, |
||||
|
created: function() { |
||||
|
this.network_log = uni.getStorageSync('network_log') |
||||
|
}, |
||||
|
methods: { |
||||
|
// 播放视频,禁止多个同时播放
|
||||
|
handlePlay (item,iteme) { |
||||
|
// console.log(item);
|
||||
|
if(this.network_log == 'none'){ |
||||
|
uni.navigateTo({ |
||||
|
url: '/pages/message/video?name='+item.fileName+'&src='+encodeURI(iteme.path), |
||||
|
animationType:"slide-in-bottom" |
||||
|
}); |
||||
|
}else if(item.src||item.url){ |
||||
|
const parts = item.src?item.src.split('/'):item.url.split('/'); |
||||
|
let lastPart = parts.pop() || parts.pop() || ''; |
||||
|
const url = item.src?this.apiUrl+item.src:item.url |
||||
|
uni.navigateTo({ |
||||
|
url: '/pages/message/video?name='+lastPart+'&src='+encodeURI(url), |
||||
|
animationType:"slide-in-bottom", |
||||
|
}); |
||||
|
}else{ |
||||
|
uni.navigateTo({ |
||||
|
url: '/pages/message/video?name='+item.fileName+'&src='+encodeURI(item.content), |
||||
|
animationType:"slide-in-bottom" |
||||
|
}); |
||||
|
} |
||||
|
}, |
||||
|
// 文件预览
|
||||
|
previewFile(item){ |
||||
|
if(this.islongPress){ |
||||
|
return; |
||||
|
} |
||||
|
this.curMsg=item; |
||||
|
this.modelName='preview'; |
||||
|
}, |
||||
|
preview(val){ |
||||
|
let item=this.curMsg; |
||||
|
let audioExt=['mp3','wav','acc']; |
||||
|
let extension = item.content.split('.').pop().toLowerCase(); |
||||
|
if(audioExt.includes(extension) || val==2){ |
||||
|
uni.navigateTo({ |
||||
|
url: '/pages/mine/webview?title=文件预览&src='+encodeURIComponent(item.preview), |
||||
|
animationType:"slide-in-bottom" |
||||
|
}); |
||||
|
return; |
||||
|
} |
||||
|
// #ifdef APP-PLUS || MP-WEIXIN
|
||||
|
let exts=['doc', 'xls', 'ppt', 'pdf', 'docx', 'xlsx', 'pptx']; |
||||
|
if(exts.includes(extension)){ |
||||
|
uni.showLoading({title: '文件加载中'}); |
||||
|
uni.downloadFile({ |
||||
|
url: item.content, |
||||
|
success: function (res) { |
||||
|
uni.hideLoading(); |
||||
|
var filePath = res.tempFilePath; |
||||
|
uni.openDocument({ |
||||
|
filePath: filePath, |
||||
|
showMenu: true, |
||||
|
success: function (res) { |
||||
|
console.info('打开文档成功'); |
||||
|
} |
||||
|
}); |
||||
|
}, |
||||
|
fail() { |
||||
|
uni.hideLoading(); |
||||
|
} |
||||
|
}); |
||||
|
}else{ |
||||
|
uni.showToast({ |
||||
|
title:'该文件不支持预览!', |
||||
|
icon:'none' |
||||
|
}) |
||||
|
} |
||||
|
// #endif
|
||||
|
|
||||
|
// #ifdef H5
|
||||
|
const tempLink = document.createElement("a"); |
||||
|
tempLink.style.display = "none"; |
||||
|
tempLink.href = item.download; |
||||
|
tempLink.setAttribute("download", item.fileName); |
||||
|
tempLink.setAttribute("target", "_blank"); |
||||
|
document.body.appendChild(tempLink); |
||||
|
tempLink.click(); |
||||
|
document.body.removeChild(tempLink); |
||||
|
// #endif
|
||||
|
}, |
||||
|
// 图片预览
|
||||
|
showImgs : function(e){ |
||||
|
var imgs = []; |
||||
|
var imgs1 = []; |
||||
|
var imgsCurrent = e.is_view==0?e.img:e.currentTarget.dataset.img; |
||||
|
for (var i = 0; i < this.messageList.length; i++) { |
||||
|
if (this.messageList[i].type == 'image' || this.messageList[i].type == 'emoji') { |
||||
|
imgs.push(this.messageList[i].content); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
if(e.is_view==0){ |
||||
|
imgs1.push(imgsCurrent); |
||||
|
uni.previewImage({urls : imgs1}); |
||||
|
}else{ |
||||
|
uni.previewImage({urls : imgs, current : imgsCurrent}); |
||||
|
} |
||||
|
}, |
||||
|
openLocation(item){ |
||||
|
uni.openLocation({ |
||||
|
latitude: item.latitude, |
||||
|
longitude: item.longitude, |
||||
|
success: function () { |
||||
|
console.log('success'); |
||||
|
} |
||||
|
}); |
||||
|
}, |
||||
|
// 打开用户详情
|
||||
|
openContact(item){ |
||||
|
uni.navigateTo({ |
||||
|
url:"/pages/contacts/detail?id="+item.id |
||||
|
}) |
||||
|
}, |
||||
|
// 自动解析消息中的表情
|
||||
|
emojiToHtml(str){ |
||||
|
let emojiMap=this.emojiMap; |
||||
|
return str.replace(/\[!(\w+)\]/gi, function (str, match) { |
||||
|
var file = match; |
||||
|
return emojiMap[file] ? "<img class='mr-5' style=\"width:18px;height:18px\" emoji-name=\"".concat(match, "\" src=\"").concat(emojiMap[file], "\" />") : "[!".concat(match, "]"); |
||||
|
}); |
||||
|
}, |
||||
|
fileSize(size){ |
||||
|
return this.$util.getFileSize(size); |
||||
|
}, |
||||
|
sendTime:function(mstime){ |
||||
|
return this.$util.timeFormat(mstime); |
||||
|
}, |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,43 @@ |
|||||
|
{ |
||||
|
"name": "FileSelect", |
||||
|
"id": "lemonjk-FileSelect", |
||||
|
"version": "3.1.0", |
||||
|
"description": "文件选取插件", |
||||
|
"_dp_type": "nativeplugin", |
||||
|
"_dp_nativeplugin": { |
||||
|
"android": { |
||||
|
"plugins": [{ |
||||
|
"type": "module", |
||||
|
"name": "lemonjk-FileSelect", |
||||
|
"class": "com.lemonjk.fileselect.FileSelectModule" |
||||
|
}], |
||||
|
"compileOptions": { |
||||
|
"sourceCompatibility": "1.8", |
||||
|
"targetCompatibility": "1.8" |
||||
|
}, |
||||
|
"dependencies": [ |
||||
|
"androidx.documentfile:documentfile:1.0.1" |
||||
|
], |
||||
|
"integrateType": "aar", |
||||
|
"permissions": [ |
||||
|
"<uses-permission android:name=\"android.permission.READ_EXTERNAL_STORAGE\"/>" |
||||
|
], |
||||
|
"abis": [ |
||||
|
"armeabi-v7a", |
||||
|
"arm64-v8a" |
||||
|
], |
||||
|
"minSdkVersion": 21 |
||||
|
}, |
||||
|
"ios": { |
||||
|
"plugins": [ |
||||
|
{ |
||||
|
"type": "module", |
||||
|
"name": "lemonjk-FileSelect", |
||||
|
"class": "TestModule" |
||||
|
} |
||||
|
], |
||||
|
"integrateType": "framework", |
||||
|
"deploymentTarget": "11.0" |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,412 @@ |
|||||
|
{ |
||||
|
"name": "Raingad-IM", |
||||
|
"version": "5.5.2", |
||||
|
"lockfileVersion": 1, |
||||
|
"requires": true, |
||||
|
"dependencies": { |
||||
|
"@babel/runtime": { |
||||
|
"version": "7.27.1", |
||||
|
"resolved": "https://registry.npmmirror.com/@babel/runtime/-/runtime-7.27.1.tgz", |
||||
|
"integrity": "sha512-1x3D2xEk2fRo3PAhwQwu5UubzgiVWSXTBfWpVd2Mx2AzRqJuDJCsgaDVZ7HB5iGzDW1Hl1sWN2mFyKjmR9uAog==" |
||||
|
}, |
||||
|
"@vue/devtools-api": { |
||||
|
"version": "6.6.4", |
||||
|
"resolved": "https://registry.npmmirror.com/@vue/devtools-api/-/devtools-api-6.6.4.tgz", |
||||
|
"integrity": "sha512-sGhTPMuXqZ1rVOk32RylztWkfXTRhuS7vgAKv0zjqk8gbsHkJ7xfFf+jbySxt7tWObEJwyKaHMikV/WGDiQm8g==" |
||||
|
}, |
||||
|
"argparse": { |
||||
|
"version": "2.0.1", |
||||
|
"resolved": "https://registry.npmmirror.com/argparse/-/argparse-2.0.1.tgz", |
||||
|
"integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==" |
||||
|
}, |
||||
|
"call-bind": { |
||||
|
"version": "1.0.8", |
||||
|
"resolved": "https://registry.npmmirror.com/call-bind/-/call-bind-1.0.8.tgz", |
||||
|
"integrity": "sha512-oKlSFMcMwpUg2ednkhQ454wfWiU/ul3CkJe/PEHcTKuiX6RpbehUiFMXu13HalGZxfUwCQzZG747YXBn1im9ww==", |
||||
|
"requires": { |
||||
|
"call-bind-apply-helpers": "^1.0.0", |
||||
|
"es-define-property": "^1.0.0", |
||||
|
"get-intrinsic": "^1.2.4", |
||||
|
"set-function-length": "^1.2.2" |
||||
|
} |
||||
|
}, |
||||
|
"call-bind-apply-helpers": { |
||||
|
"version": "1.0.2", |
||||
|
"resolved": "https://registry.npmmirror.com/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz", |
||||
|
"integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==", |
||||
|
"requires": { |
||||
|
"es-errors": "^1.3.0", |
||||
|
"function-bind": "^1.1.2" |
||||
|
} |
||||
|
}, |
||||
|
"call-bound": { |
||||
|
"version": "1.0.4", |
||||
|
"resolved": "https://registry.npmmirror.com/call-bound/-/call-bound-1.0.4.tgz", |
||||
|
"integrity": "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==", |
||||
|
"requires": { |
||||
|
"call-bind-apply-helpers": "^1.0.2", |
||||
|
"get-intrinsic": "^1.3.0" |
||||
|
} |
||||
|
}, |
||||
|
"clone": { |
||||
|
"version": "2.1.2", |
||||
|
"resolved": "https://registry.npmmirror.com/clone/-/clone-2.1.2.tgz", |
||||
|
"integrity": "sha512-3Pe/CF1Nn94hyhIYpjtiLhdCoEoz0DqQ+988E9gmeEdQZlojxnOb74wctFyuwWQHzqyf9X7C7MG8juUpqBJT8w==" |
||||
|
}, |
||||
|
"copy-text-to-clipboard": { |
||||
|
"version": "3.2.0", |
||||
|
"resolved": "https://registry.npmmirror.com/copy-text-to-clipboard/-/copy-text-to-clipboard-3.2.0.tgz", |
||||
|
"integrity": "sha512-RnJFp1XR/LOBDckxTib5Qjr/PMfkatD0MUCQgdpqS8MdKiNUzBjAQBEN6oUy+jW7LI93BBG3DtMB2KOOKpGs2Q==" |
||||
|
}, |
||||
|
"core-js": { |
||||
|
"version": "3.42.0", |
||||
|
"resolved": "https://registry.npmmirror.com/core-js/-/core-js-3.42.0.tgz", |
||||
|
"integrity": "sha512-Sz4PP4ZA+Rq4II21qkNqOEDTDrCvcANId3xpIgB34NDkWc3UduWj2dqEtN9yZIq8Dk3HyPI33x9sqqU5C8sr0g==" |
||||
|
}, |
||||
|
"crypto-js": { |
||||
|
"version": "4.2.0", |
||||
|
"resolved": "https://registry.npmmirror.com/crypto-js/-/crypto-js-4.2.0.tgz", |
||||
|
"integrity": "sha512-KALDyEYgpY+Rlob/iriUtjV6d5Eq+Y191A5g4UqLAi8CyGP9N1+FdVbkc1SxKc2r4YAYqG8JzO2KGL+AizD70Q==" |
||||
|
}, |
||||
|
"deep-equal": { |
||||
|
"version": "1.1.2", |
||||
|
"resolved": "https://registry.npmmirror.com/deep-equal/-/deep-equal-1.1.2.tgz", |
||||
|
"integrity": "sha512-5tdhKF6DbU7iIzrIOa1AOUt39ZRm13cmL1cGEh//aqR8x9+tNfbywRf0n5FD/18OKMdo7DNEtrX2t22ZAkI+eg==", |
||||
|
"requires": { |
||||
|
"is-arguments": "^1.1.1", |
||||
|
"is-date-object": "^1.0.5", |
||||
|
"is-regex": "^1.1.4", |
||||
|
"object-is": "^1.1.5", |
||||
|
"object-keys": "^1.1.1", |
||||
|
"regexp.prototype.flags": "^1.5.1" |
||||
|
} |
||||
|
}, |
||||
|
"define-data-property": { |
||||
|
"version": "1.1.4", |
||||
|
"resolved": "https://registry.npmmirror.com/define-data-property/-/define-data-property-1.1.4.tgz", |
||||
|
"integrity": "sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==", |
||||
|
"requires": { |
||||
|
"es-define-property": "^1.0.0", |
||||
|
"es-errors": "^1.3.0", |
||||
|
"gopd": "^1.0.1" |
||||
|
} |
||||
|
}, |
||||
|
"define-properties": { |
||||
|
"version": "1.2.1", |
||||
|
"resolved": "https://registry.npmmirror.com/define-properties/-/define-properties-1.2.1.tgz", |
||||
|
"integrity": "sha512-8QmQKqEASLd5nx0U1B1okLElbUuuttJ/AnYmRXbbbGDWh6uS208EjD4Xqq/I9wK7u0v6O08XhTWnt5XtEbR6Dg==", |
||||
|
"requires": { |
||||
|
"define-data-property": "^1.0.1", |
||||
|
"has-property-descriptors": "^1.0.0", |
||||
|
"object-keys": "^1.1.1" |
||||
|
} |
||||
|
}, |
||||
|
"dunder-proto": { |
||||
|
"version": "1.0.1", |
||||
|
"resolved": "https://registry.npmmirror.com/dunder-proto/-/dunder-proto-1.0.1.tgz", |
||||
|
"integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==", |
||||
|
"requires": { |
||||
|
"call-bind-apply-helpers": "^1.0.1", |
||||
|
"es-errors": "^1.3.0", |
||||
|
"gopd": "^1.2.0" |
||||
|
} |
||||
|
}, |
||||
|
"entities": { |
||||
|
"version": "4.5.0", |
||||
|
"resolved": "https://registry.npmmirror.com/entities/-/entities-4.5.0.tgz", |
||||
|
"integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==" |
||||
|
}, |
||||
|
"es-define-property": { |
||||
|
"version": "1.0.1", |
||||
|
"resolved": "https://registry.npmmirror.com/es-define-property/-/es-define-property-1.0.1.tgz", |
||||
|
"integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==" |
||||
|
}, |
||||
|
"es-errors": { |
||||
|
"version": "1.3.0", |
||||
|
"resolved": "https://registry.npmmirror.com/es-errors/-/es-errors-1.3.0.tgz", |
||||
|
"integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==" |
||||
|
}, |
||||
|
"es-object-atoms": { |
||||
|
"version": "1.1.1", |
||||
|
"resolved": "https://registry.npmmirror.com/es-object-atoms/-/es-object-atoms-1.1.1.tgz", |
||||
|
"integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==", |
||||
|
"requires": { |
||||
|
"es-errors": "^1.3.0" |
||||
|
} |
||||
|
}, |
||||
|
"eventemitter3": { |
||||
|
"version": "2.0.3", |
||||
|
"resolved": "https://registry.npmmirror.com/eventemitter3/-/eventemitter3-2.0.3.tgz", |
||||
|
"integrity": "sha512-jLN68Dx5kyFHaePoXWPsCGW5qdyZQtLYHkxkg02/Mz6g0kYpDx4FyP6XfArhQdlOC4b8Mv+EMxPo/8La7Tzghg==" |
||||
|
}, |
||||
|
"extend": { |
||||
|
"version": "3.0.2", |
||||
|
"resolved": "https://registry.npmmirror.com/extend/-/extend-3.0.2.tgz", |
||||
|
"integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==" |
||||
|
}, |
||||
|
"fast-diff": { |
||||
|
"version": "1.1.2", |
||||
|
"resolved": "https://registry.npmmirror.com/fast-diff/-/fast-diff-1.1.2.tgz", |
||||
|
"integrity": "sha512-KaJUt+M9t1qaIteSvjc6P3RbMdXsNhK61GRftR6SNxqmhthcd9MGIi4T+o0jD8LUSpSnSKXE20nLtJ3fOHxQig==" |
||||
|
}, |
||||
|
"function-bind": { |
||||
|
"version": "1.1.2", |
||||
|
"resolved": "https://registry.npmmirror.com/function-bind/-/function-bind-1.1.2.tgz", |
||||
|
"integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==" |
||||
|
}, |
||||
|
"functions-have-names": { |
||||
|
"version": "1.2.3", |
||||
|
"resolved": "https://registry.npmmirror.com/functions-have-names/-/functions-have-names-1.2.3.tgz", |
||||
|
"integrity": "sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ==" |
||||
|
}, |
||||
|
"get-intrinsic": { |
||||
|
"version": "1.3.0", |
||||
|
"resolved": "https://registry.npmmirror.com/get-intrinsic/-/get-intrinsic-1.3.0.tgz", |
||||
|
"integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==", |
||||
|
"requires": { |
||||
|
"call-bind-apply-helpers": "^1.0.2", |
||||
|
"es-define-property": "^1.0.1", |
||||
|
"es-errors": "^1.3.0", |
||||
|
"es-object-atoms": "^1.1.1", |
||||
|
"function-bind": "^1.1.2", |
||||
|
"get-proto": "^1.0.1", |
||||
|
"gopd": "^1.2.0", |
||||
|
"has-symbols": "^1.1.0", |
||||
|
"hasown": "^2.0.2", |
||||
|
"math-intrinsics": "^1.1.0" |
||||
|
} |
||||
|
}, |
||||
|
"get-proto": { |
||||
|
"version": "1.0.1", |
||||
|
"resolved": "https://registry.npmmirror.com/get-proto/-/get-proto-1.0.1.tgz", |
||||
|
"integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==", |
||||
|
"requires": { |
||||
|
"dunder-proto": "^1.0.1", |
||||
|
"es-object-atoms": "^1.0.0" |
||||
|
} |
||||
|
}, |
||||
|
"gopd": { |
||||
|
"version": "1.2.0", |
||||
|
"resolved": "https://registry.npmmirror.com/gopd/-/gopd-1.2.0.tgz", |
||||
|
"integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==" |
||||
|
}, |
||||
|
"has-property-descriptors": { |
||||
|
"version": "1.0.2", |
||||
|
"resolved": "https://registry.npmmirror.com/has-property-descriptors/-/has-property-descriptors-1.0.2.tgz", |
||||
|
"integrity": "sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==", |
||||
|
"requires": { |
||||
|
"es-define-property": "^1.0.0" |
||||
|
} |
||||
|
}, |
||||
|
"has-symbols": { |
||||
|
"version": "1.1.0", |
||||
|
"resolved": "https://registry.npmmirror.com/has-symbols/-/has-symbols-1.1.0.tgz", |
||||
|
"integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==" |
||||
|
}, |
||||
|
"has-tostringtag": { |
||||
|
"version": "1.0.2", |
||||
|
"resolved": "https://registry.npmmirror.com/has-tostringtag/-/has-tostringtag-1.0.2.tgz", |
||||
|
"integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==", |
||||
|
"requires": { |
||||
|
"has-symbols": "^1.0.3" |
||||
|
} |
||||
|
}, |
||||
|
"hasown": { |
||||
|
"version": "2.0.2", |
||||
|
"resolved": "https://registry.npmmirror.com/hasown/-/hasown-2.0.2.tgz", |
||||
|
"integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", |
||||
|
"requires": { |
||||
|
"function-bind": "^1.1.2" |
||||
|
} |
||||
|
}, |
||||
|
"highlight.js": { |
||||
|
"version": "11.11.1", |
||||
|
"resolved": "https://registry.npmmirror.com/highlight.js/-/highlight.js-11.11.1.tgz", |
||||
|
"integrity": "sha512-Xwwo44whKBVCYoliBQwaPvtd/2tYFkRQtXDWj1nackaV2JPXx3L0+Jvd8/qCJ2p+ML0/XVkJ2q+Mr+UVdpJK5w==" |
||||
|
}, |
||||
|
"is-arguments": { |
||||
|
"version": "1.2.0", |
||||
|
"resolved": "https://registry.npmmirror.com/is-arguments/-/is-arguments-1.2.0.tgz", |
||||
|
"integrity": "sha512-7bVbi0huj/wrIAOzb8U1aszg9kdi3KN/CyU19CTI7tAoZYEZoL9yCDXpbXN+uPsuWnP02cyug1gleqq+TU+YCA==", |
||||
|
"requires": { |
||||
|
"call-bound": "^1.0.2", |
||||
|
"has-tostringtag": "^1.0.2" |
||||
|
} |
||||
|
}, |
||||
|
"is-date-object": { |
||||
|
"version": "1.1.0", |
||||
|
"resolved": "https://registry.npmmirror.com/is-date-object/-/is-date-object-1.1.0.tgz", |
||||
|
"integrity": "sha512-PwwhEakHVKTdRNVOw+/Gyh0+MzlCl4R6qKvkhuvLtPMggI1WAHt9sOwZxQLSGpUaDnrdyDsomoRgNnCfKNSXXg==", |
||||
|
"requires": { |
||||
|
"call-bound": "^1.0.2", |
||||
|
"has-tostringtag": "^1.0.2" |
||||
|
} |
||||
|
}, |
||||
|
"is-regex": { |
||||
|
"version": "1.2.1", |
||||
|
"resolved": "https://registry.npmmirror.com/is-regex/-/is-regex-1.2.1.tgz", |
||||
|
"integrity": "sha512-MjYsKHO5O7mCsmRGxWcLWheFqN9DJ/2TmngvjKXihe6efViPqc274+Fx/4fYj/r03+ESvBdTXK0V6tA3rgez1g==", |
||||
|
"requires": { |
||||
|
"call-bound": "^1.0.2", |
||||
|
"gopd": "^1.2.0", |
||||
|
"has-tostringtag": "^1.0.2", |
||||
|
"hasown": "^2.0.2" |
||||
|
} |
||||
|
}, |
||||
|
"jsqr": { |
||||
|
"version": "1.4.0", |
||||
|
"resolved": "https://registry.npmmirror.com/jsqr/-/jsqr-1.4.0.tgz", |
||||
|
"integrity": "sha512-dxLob7q65Xg2DvstYkRpkYtmKm2sPJ9oFhrhmudT1dZvNFFTlroai3AWSpLey/w5vMcLBXRgOJsbXpdN9HzU/A==" |
||||
|
}, |
||||
|
"linkify-it": { |
||||
|
"version": "5.0.0", |
||||
|
"resolved": "https://registry.npmmirror.com/linkify-it/-/linkify-it-5.0.0.tgz", |
||||
|
"integrity": "sha512-5aHCbzQRADcdP+ATqnDuhhJ/MRIqDkZX5pyjFHRRysS8vZ5AbqGEoFIb6pYHPZ+L/OC2Lc+xT8uHVVR5CAK/wQ==", |
||||
|
"requires": { |
||||
|
"uc.micro": "^2.0.0" |
||||
|
} |
||||
|
}, |
||||
|
"markdown-it": { |
||||
|
"version": "14.1.0", |
||||
|
"resolved": "https://registry.npmmirror.com/markdown-it/-/markdown-it-14.1.0.tgz", |
||||
|
"integrity": "sha512-a54IwgWPaeBCAAsv13YgmALOF1elABB08FxO9i+r4VFk5Vl4pKokRPeX8u5TCgSsPi6ec1otfLjdOpVcgbpshg==", |
||||
|
"requires": { |
||||
|
"argparse": "^2.0.1", |
||||
|
"entities": "^4.4.0", |
||||
|
"linkify-it": "^5.0.0", |
||||
|
"mdurl": "^2.0.0", |
||||
|
"punycode.js": "^2.3.1", |
||||
|
"uc.micro": "^2.1.0" |
||||
|
} |
||||
|
}, |
||||
|
"math-intrinsics": { |
||||
|
"version": "1.1.0", |
||||
|
"resolved": "https://registry.npmmirror.com/math-intrinsics/-/math-intrinsics-1.1.0.tgz", |
||||
|
"integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==" |
||||
|
}, |
||||
|
"mdurl": { |
||||
|
"version": "2.0.0", |
||||
|
"resolved": "https://registry.npmmirror.com/mdurl/-/mdurl-2.0.0.tgz", |
||||
|
"integrity": "sha512-Lf+9+2r+Tdp5wXDXC4PcIBjTDtq4UKjCPMQhKIuzpJNW0b96kVqSwW0bT7FhRSfmAiFYgP+SCRvdrDozfh0U5w==" |
||||
|
}, |
||||
|
"mutation-observer": { |
||||
|
"version": "1.0.3", |
||||
|
"resolved": "https://registry.npmmirror.com/mutation-observer/-/mutation-observer-1.0.3.tgz", |
||||
|
"integrity": "sha512-M/O/4rF2h776hV7qGMZUH3utZLO/jK7p8rnNgGkjKUw8zCGjRQPxB8z6+5l8+VjRUQ3dNYu4vjqXYLr+U8ZVNA==" |
||||
|
}, |
||||
|
"object-is": { |
||||
|
"version": "1.1.6", |
||||
|
"resolved": "https://registry.npmmirror.com/object-is/-/object-is-1.1.6.tgz", |
||||
|
"integrity": "sha512-F8cZ+KfGlSGi09lJT7/Nd6KJZ9ygtvYC0/UYYLI9nmQKLMnydpB9yvbv9K1uSkEu7FU9vYPmVwLg328tX+ot3Q==", |
||||
|
"requires": { |
||||
|
"call-bind": "^1.0.7", |
||||
|
"define-properties": "^1.2.1" |
||||
|
} |
||||
|
}, |
||||
|
"object-keys": { |
||||
|
"version": "1.1.1", |
||||
|
"resolved": "https://registry.npmmirror.com/object-keys/-/object-keys-1.1.1.tgz", |
||||
|
"integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==" |
||||
|
}, |
||||
|
"parchment": { |
||||
|
"version": "1.1.4", |
||||
|
"resolved": "https://registry.npmmirror.com/parchment/-/parchment-1.1.4.tgz", |
||||
|
"integrity": "sha512-J5FBQt/pM2inLzg4hEWmzQx/8h8D0CiDxaG3vyp9rKrQRSDgBlhjdP5jQGgosEajXPSQouXGHOmVdgo7QmJuOg==" |
||||
|
}, |
||||
|
"pinia": { |
||||
|
"version": "2.3.1", |
||||
|
"resolved": "https://registry.npmmirror.com/pinia/-/pinia-2.3.1.tgz", |
||||
|
"integrity": "sha512-khUlZSwt9xXCaTbbxFYBKDc/bWAGWJjOgvxETwkTN7KRm66EeT1ZdZj6i2ceh9sP2Pzqsbc704r2yngBrxBVug==", |
||||
|
"requires": { |
||||
|
"@vue/devtools-api": "^6.6.3", |
||||
|
"vue-demi": "^0.14.10" |
||||
|
} |
||||
|
}, |
||||
|
"punycode.js": { |
||||
|
"version": "2.3.1", |
||||
|
"resolved": "https://registry.npmmirror.com/punycode.js/-/punycode.js-2.3.1.tgz", |
||||
|
"integrity": "sha512-uxFIHU0YlHYhDQtV4R9J6a52SLx28BCjT+4ieh7IGbgwVJWO+km431c4yRlREUAsAmt/uMjQUyQHNEPf0M39CA==" |
||||
|
}, |
||||
|
"quill": { |
||||
|
"version": "1.3.7", |
||||
|
"resolved": "https://registry.npmmirror.com/quill/-/quill-1.3.7.tgz", |
||||
|
"integrity": "sha512-hG/DVzh/TiknWtE6QmWAF/pxoZKYxfe3J/d/+ShUWkDvvkZQVTPeVmUJVu1uE6DDooC4fWTiCLh84ul89oNz5g==", |
||||
|
"requires": { |
||||
|
"clone": "^2.1.1", |
||||
|
"deep-equal": "^1.0.1", |
||||
|
"eventemitter3": "^2.0.3", |
||||
|
"extend": "^3.0.2", |
||||
|
"parchment": "^1.1.4", |
||||
|
"quill-delta": "^3.6.2" |
||||
|
} |
||||
|
}, |
||||
|
"quill-delta": { |
||||
|
"version": "3.6.3", |
||||
|
"resolved": "https://registry.npmmirror.com/quill-delta/-/quill-delta-3.6.3.tgz", |
||||
|
"integrity": "sha512-wdIGBlcX13tCHOXGMVnnTVFtGRLoP0imqxM696fIPwIf5ODIYUHIvHbZcyvGlZFiFhK5XzDC2lpjbxRhnM05Tg==", |
||||
|
"requires": { |
||||
|
"deep-equal": "^1.0.1", |
||||
|
"extend": "^3.0.2", |
||||
|
"fast-diff": "1.1.2" |
||||
|
} |
||||
|
}, |
||||
|
"regexp.prototype.flags": { |
||||
|
"version": "1.5.4", |
||||
|
"resolved": "https://registry.npmmirror.com/regexp.prototype.flags/-/regexp.prototype.flags-1.5.4.tgz", |
||||
|
"integrity": "sha512-dYqgNSZbDwkaJ2ceRd9ojCGjBq+mOm9LmtXnAnEGyHhN/5R7iDW2TRw3h+o/jCFxus3P2LfWIIiwowAjANm7IA==", |
||||
|
"requires": { |
||||
|
"call-bind": "^1.0.8", |
||||
|
"define-properties": "^1.2.1", |
||||
|
"es-errors": "^1.3.0", |
||||
|
"get-proto": "^1.0.1", |
||||
|
"gopd": "^1.2.0", |
||||
|
"set-function-name": "^2.0.2" |
||||
|
} |
||||
|
}, |
||||
|
"set-function-length": { |
||||
|
"version": "1.2.2", |
||||
|
"resolved": "https://registry.npmmirror.com/set-function-length/-/set-function-length-1.2.2.tgz", |
||||
|
"integrity": "sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg==", |
||||
|
"requires": { |
||||
|
"define-data-property": "^1.1.4", |
||||
|
"es-errors": "^1.3.0", |
||||
|
"function-bind": "^1.1.2", |
||||
|
"get-intrinsic": "^1.2.4", |
||||
|
"gopd": "^1.0.1", |
||||
|
"has-property-descriptors": "^1.0.2" |
||||
|
} |
||||
|
}, |
||||
|
"set-function-name": { |
||||
|
"version": "2.0.2", |
||||
|
"resolved": "https://registry.npmmirror.com/set-function-name/-/set-function-name-2.0.2.tgz", |
||||
|
"integrity": "sha512-7PGFlmtwsEADb0WYyvCMa1t+yke6daIG4Wirafur5kcf+MhUnPms1UeR0CKQdTZD81yESwMHbtn+TR+dMviakQ==", |
||||
|
"requires": { |
||||
|
"define-data-property": "^1.1.4", |
||||
|
"es-errors": "^1.3.0", |
||||
|
"functions-have-names": "^1.2.3", |
||||
|
"has-property-descriptors": "^1.0.2" |
||||
|
} |
||||
|
}, |
||||
|
"uc.micro": { |
||||
|
"version": "2.1.0", |
||||
|
"resolved": "https://registry.npmmirror.com/uc.micro/-/uc.micro-2.1.0.tgz", |
||||
|
"integrity": "sha512-ARDJmphmdvUk6Glw7y9DQ2bFkKBHwQHLi2lsaH6PPmz/Ka9sFOBsBluozhDltWmnv9u/cF6Rt87znRTPV+yp/A==" |
||||
|
}, |
||||
|
"vconsole": { |
||||
|
"version": "3.15.1", |
||||
|
"resolved": "https://registry.npmmirror.com/vconsole/-/vconsole-3.15.1.tgz", |
||||
|
"integrity": "sha512-KH8XLdrq9T5YHJO/ixrjivHfmF2PC2CdVoK6RWZB4yftMykYIaXY1mxZYAic70vADM54kpMQF+dYmvl5NRNy1g==", |
||||
|
"requires": { |
||||
|
"@babel/runtime": "^7.17.2", |
||||
|
"copy-text-to-clipboard": "^3.0.1", |
||||
|
"core-js": "^3.11.0", |
||||
|
"mutation-observer": "^1.0.3" |
||||
|
} |
||||
|
}, |
||||
|
"vue-demi": { |
||||
|
"version": "0.14.10", |
||||
|
"resolved": "https://registry.npmmirror.com/vue-demi/-/vue-demi-0.14.10.tgz", |
||||
|
"integrity": "sha512-nMZBOwuzabUO0nLgIcc6rycZEebF6eeUfaiQx9+WSk8e29IbLvPU9feI6tqW4kTo3hvoYAJkMh8n8D0fuISphg==" |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,26 @@ |
|||||
|
{ |
||||
|
"name": "Raingad-IM", |
||||
|
"version": "5.5.2", |
||||
|
"description": "一款基于vue3.0的uniapp即时聊天工具", |
||||
|
"logo": "/static/image/logo.png", |
||||
|
"author": "Raingad", |
||||
|
"main": "main.js", |
||||
|
"scripts": { |
||||
|
"test": "echo \"Error: no test specified\" && exit 1" |
||||
|
}, |
||||
|
"repository": { |
||||
|
"type": "git", |
||||
|
"url": "https://gitee.com/raingad/instant-chat-uniapp.git" |
||||
|
}, |
||||
|
"keywords": [], |
||||
|
"license": "ISC", |
||||
|
"dependencies": { |
||||
|
"crypto-js": "^4.2.0", |
||||
|
"highlight.js": "^11.11.1", |
||||
|
"jsqr": "^1.4.0", |
||||
|
"markdown-it": "^14.1.0", |
||||
|
"pinia": "^2.0.23", |
||||
|
"quill": "^1.3.7", |
||||
|
"vconsole": "^3.15.1" |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,247 @@ |
|||||
|
{ |
||||
|
"pages": [ |
||||
|
{ |
||||
|
"path": "pages/index/index", |
||||
|
"style": { |
||||
|
"navigationBarTitleText": "首页" |
||||
|
} |
||||
|
}, |
||||
|
{ |
||||
|
"path": "pages/message/chat", |
||||
|
"style": { |
||||
|
"navigationBarTitleText": "聊天" |
||||
|
// "enablePullDownRefresh": true |
||||
|
} |
||||
|
}, |
||||
|
{ |
||||
|
"path": "pages/message/emoji", |
||||
|
"style": { |
||||
|
"navigationBarTitleText": "表情" |
||||
|
} |
||||
|
}, |
||||
|
{ |
||||
|
"path": "pages/contacts/index", |
||||
|
"style": { |
||||
|
"navigationBarTitleText": "联系人" |
||||
|
} |
||||
|
}, |
||||
|
{ |
||||
|
"path": "pages/message/detail", |
||||
|
"style": { |
||||
|
"navigationBarTitleText": "聊天信息" |
||||
|
} |
||||
|
}, |
||||
|
{ |
||||
|
"path": "pages/message/record", |
||||
|
"style": { |
||||
|
"navigationBarTitleText": "聊天记录" |
||||
|
} |
||||
|
}, |
||||
|
{ |
||||
|
"path": "pages/contacts/detail", |
||||
|
"style": { |
||||
|
"navigationBarTitleText": "用户信息" |
||||
|
} |
||||
|
}, |
||||
|
{ |
||||
|
"path": "pages/contacts/friend", |
||||
|
"style": { |
||||
|
"navigationBarTitleText": "新朋友" |
||||
|
} |
||||
|
}, |
||||
|
{ |
||||
|
"path": "pages/contacts/search", |
||||
|
"style": { |
||||
|
"navigationBarTitleText": "搜索朋友" |
||||
|
} |
||||
|
}, |
||||
|
{ |
||||
|
"path": "pages/login/index", |
||||
|
"style": { |
||||
|
"navigationBarTitleText": "登录" |
||||
|
} |
||||
|
}, |
||||
|
{ |
||||
|
"path": "pages/login/register", |
||||
|
"style": { |
||||
|
"navigationBarTitleText": "注册" |
||||
|
} |
||||
|
}, |
||||
|
{ |
||||
|
"path": "pages/message/call", |
||||
|
"style": { |
||||
|
"navigationBarTitleText": "通话" |
||||
|
} |
||||
|
}, |
||||
|
{ |
||||
|
"path": "pages/message/video", |
||||
|
"style": { |
||||
|
"navigationBarTitleText": "视频播放" |
||||
|
} |
||||
|
}, |
||||
|
{ |
||||
|
"path": "pages/index/userSelection", |
||||
|
"style": { |
||||
|
"navigationBarTitleText": "用户选择" |
||||
|
} |
||||
|
}, |
||||
|
{ |
||||
|
"path": "pages/message/group/groupUser", |
||||
|
"style": { |
||||
|
"navigationBarTitleText": "群聊成员管理" |
||||
|
} |
||||
|
}, |
||||
|
{ |
||||
|
"path": "pages/index/qrcode", |
||||
|
"style": { |
||||
|
"navigationBarTitleText": "群二维码" |
||||
|
} |
||||
|
}, |
||||
|
{ |
||||
|
"path": "pages/message/group/info", |
||||
|
"style": { |
||||
|
"navigationBarTitleText": "群信息" |
||||
|
} |
||||
|
}, |
||||
|
{ |
||||
|
"path": "pages/contacts/group", |
||||
|
"style": { |
||||
|
"navigationBarTitleText": "群聊列表" |
||||
|
} |
||||
|
}, |
||||
|
{ |
||||
|
"path": "pages/index/search", |
||||
|
"style": { |
||||
|
"navigationBarTitleText": "搜索" |
||||
|
} |
||||
|
}, |
||||
|
{ |
||||
|
"path": "pages/index/scan", |
||||
|
"style": { |
||||
|
"navigationBarTitleText": "扫描" |
||||
|
} |
||||
|
}, |
||||
|
{ |
||||
|
"path": "pages/mine/webview", |
||||
|
"style": { |
||||
|
"navigationBarTitleText": "浏览器", |
||||
|
"navigationStyle":"default" |
||||
|
} |
||||
|
}, |
||||
|
{ |
||||
|
"path": "pages/compass/moments", |
||||
|
"style": { |
||||
|
"navigationBarTitleText": "朋友圈" |
||||
|
} |
||||
|
}, |
||||
|
{ |
||||
|
"path": "pages/mine/profile", |
||||
|
"style": { |
||||
|
"navigationBarTitleText": "个人信息" |
||||
|
} |
||||
|
}, |
||||
|
{ |
||||
|
"path": "pages/mine/secure", |
||||
|
"style": { |
||||
|
"navigationBarTitleText": "安全" |
||||
|
} |
||||
|
}, |
||||
|
{ |
||||
|
"path": "pages/mine/about", |
||||
|
"style": { |
||||
|
"navigationBarTitleText": "关于" |
||||
|
} |
||||
|
}, |
||||
|
{ |
||||
|
"path": "pages/mine/setting", |
||||
|
"style": { |
||||
|
"navigationBarTitleText": "设置" |
||||
|
} |
||||
|
}, |
||||
|
{ |
||||
|
"path": "pages/mine/doc", |
||||
|
"style": { |
||||
|
"navigationBarTitleText": "帮助文档" |
||||
|
} |
||||
|
}, |
||||
|
{ |
||||
|
"path" : "pages/compass/sendtoMoments", |
||||
|
"style" : { |
||||
|
"navigationBarTitleText" : "" |
||||
|
} |
||||
|
}, |
||||
|
{ |
||||
|
"path" : "pages/compass/personalcircleoffriends", |
||||
|
"style" : |
||||
|
{ |
||||
|
"navigationBarTitleText" : "" |
||||
|
} |
||||
|
}, |
||||
|
{ |
||||
|
"path" : "pages/compass/friendscircledetails", |
||||
|
"style" : |
||||
|
{ |
||||
|
"navigationBarTitleText" : "" |
||||
|
} |
||||
|
}, |
||||
|
{ |
||||
|
"path" : "pages/mapselect/mapselect", |
||||
|
"style" : |
||||
|
{ |
||||
|
"navigationBarTitleText" : "" |
||||
|
} |
||||
|
}, |
||||
|
{ |
||||
|
"path" : "pages/login/404", |
||||
|
"style" : |
||||
|
{ |
||||
|
"navigationBarTitleText" : "" |
||||
|
} |
||||
|
}, |
||||
|
{ |
||||
|
"path" : "pages/compass/Informationdetails", |
||||
|
"style" : |
||||
|
{ |
||||
|
"navigationBarTitleText" : "" |
||||
|
} |
||||
|
}, |
||||
|
{ |
||||
|
"path" : "pages/contacts/blacklist", |
||||
|
"style" : |
||||
|
{ |
||||
|
"navigationBarTitleText" : "" |
||||
|
} |
||||
|
} |
||||
|
], |
||||
|
"globalStyle": { |
||||
|
"navigationStyle": "custom", |
||||
|
"navigationBarTextStyle": "black", |
||||
|
"navigationBarTitleText": "uni-app", |
||||
|
"navigationBarBackgroundColor": "#F8F8F8", |
||||
|
"backgroundColor": "#F8F8F8", |
||||
|
"app-plus": { |
||||
|
"background": "#efeff4" |
||||
|
} |
||||
|
}, |
||||
|
"tabBar": { |
||||
|
"custom": true, |
||||
|
"color": "#333", |
||||
|
"selectedColor": "#1AAD19", |
||||
|
"borderStyle": "black", |
||||
|
"backgroundColor": "#FFFFFF", |
||||
|
"list": [{ |
||||
|
"pagePath": "pages/index/index", |
||||
|
"iconPath": "static/image/tabbar/demo.png", |
||||
|
"selectedIconPath": "static/image/tabbar/demo.png", |
||||
|
"text": "首页" |
||||
|
}, |
||||
|
|
||||
|
{ |
||||
|
"pagePath": "pages/contacts/index", |
||||
|
"iconPath": "static/image/tabbar/demo.png", |
||||
|
"selectedIconPath": "static/image/tabbar/demo.png", |
||||
|
"text": "通讯录" |
||||
|
} |
||||
|
] |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,156 @@ |
|||||
|
<template> |
||||
|
<cu-custom bgColor="bg-white" :isBack="true"> |
||||
|
<template #backText></template> |
||||
|
<template #content>消息</template> |
||||
|
</cu-custom> |
||||
|
<!-- #ifdef APP-PLUS --> |
||||
|
<scroll-view scroll-y style="height: 88vh;" @scrolltolower="scrollbot"> |
||||
|
<!-- #endif --> |
||||
|
<!-- #ifdef H5 --> |
||||
|
<scroll-view scroll-y style="height: 93vh;" @scrolltolower="scrollbot"> |
||||
|
<!-- #endif --> |
||||
|
<view v-for="(item,index) in filteredList" :key="index" class="flex align-center" @click="JumpDetails(item)"> |
||||
|
<view style="padding: 25rpx 25rpx 25rpx 20rpx;"> |
||||
|
<image :src="item.option_user.avatar" mode="widthFix" style="width: 80rpx;border-radius: 10rpx;"></image> |
||||
|
</view> |
||||
|
<view class="flex align-center" style="border-bottom: 1px solid #e7e7e7;padding: 25rpx 0px;width: 100%;margin-right: 30rpx;justify-content: space-between;"> |
||||
|
<view> |
||||
|
<view style="color: #576b95;font-size: 16px;margin-bottom: 10rpx;">{{item.option_user.nickname}}</view> |
||||
|
<view v-if="item.content"> |
||||
|
<mp-html container-style="overflow: hidden;display:inline;white-space: pre-wrap;font-size: 13px;" :content="emojiToHtml(item.content)"/> |
||||
|
</view> |
||||
|
<view v-if="item.type==1"> |
||||
|
<uni-icons type="heart" size="15" color="#576b95"></uni-icons> |
||||
|
</view> |
||||
|
<view style="font-size: 10px;margin-top: 10rpx;">{{item.create_time}}</view> |
||||
|
</view> |
||||
|
<view v-if="item.file.src"> |
||||
|
<image :src="apiUrl+item.file.src" mode="widthFix" style="width: 80rpx;border-radius: 10rpx;"></image> |
||||
|
</view> |
||||
|
</view> |
||||
|
</view> |
||||
|
<view v-if="!showEarlier && hasUnread" @click="showEarlier = true" style="text-align: center; padding: 20rpx; color: #576b95;"> |
||||
|
查看更早的消息... |
||||
|
</view> |
||||
|
<uni-load-more v-if="showEarlier" :status="status" :content-text="contentText"></uni-load-more> |
||||
|
</scroll-view> |
||||
|
</template> |
||||
|
|
||||
|
<script> |
||||
|
import pinia from '@/store/index'; |
||||
|
import config from "@/common/config"; |
||||
|
import emoji from '@/utils/emoji.js'; |
||||
|
import { useMsgStore } from '@/store/message'; |
||||
|
|
||||
|
const msgStore = useMsgStore(pinia) |
||||
|
export default { |
||||
|
data() { |
||||
|
return { |
||||
|
list:[], |
||||
|
apiUrl:config.apiUrl, |
||||
|
emojiMap:[], |
||||
|
page:1, |
||||
|
limit:20, |
||||
|
status: 'more',//状态 |
||||
|
//显示的状态 |
||||
|
contentText: { |
||||
|
contentdown: '查看更多', |
||||
|
contentrefresh: '加载中....', |
||||
|
contentnomore: '没有更多咯' |
||||
|
}, |
||||
|
showEarlier: false, // 控制是否显示更早消息 |
||||
|
hasUnread: false // 标记是否存在未读消息 |
||||
|
} |
||||
|
}, |
||||
|
computed: { |
||||
|
filteredList() { |
||||
|
if (this.showEarlier) { |
||||
|
// 显示所有已读消息(is_read=1) |
||||
|
return this.list.sort((a, b) => new Date(b.create_time) - new Date(a.create_time)) |
||||
|
} else { |
||||
|
// 优先显示未读消息(is_read=0) |
||||
|
const unread = this.list.filter(item => item.is_read === 0); |
||||
|
return unread.length ? unread : this.list; |
||||
|
} |
||||
|
} |
||||
|
}, |
||||
|
created() { |
||||
|
let emojiMap=[]; |
||||
|
// 解析所有表情 |
||||
|
emoji.forEach(function (item) { |
||||
|
let child=item.children; |
||||
|
if(child.length>0){ |
||||
|
child.forEach(function (val) { |
||||
|
let name=val.name; |
||||
|
let src=val.src; |
||||
|
emojiMap[name]=src; |
||||
|
}) |
||||
|
} |
||||
|
}); |
||||
|
this.emojiMap=emojiMap; |
||||
|
}, |
||||
|
onShow() { |
||||
|
this.getNoticeList() |
||||
|
}, |
||||
|
methods: { |
||||
|
getNoticeList(){ |
||||
|
let params = {page:this.page,limit:this.limit} |
||||
|
this.status = 'loading'; // 加载中 |
||||
|
this.$api.compaApi.getNoticeList(params).then(res => { |
||||
|
// 判断是否还有更多数据 |
||||
|
if (res.data.length <= res.count) { |
||||
|
this.status = 'noMore'; // 没有更多数据 |
||||
|
} else { |
||||
|
this.status = 'more'; // 还有更多数据 |
||||
|
} |
||||
|
|
||||
|
this.list = res.data |
||||
|
msgStore.getCount(0); |
||||
|
this.hasUnread = this.list.some(item => item.is_read === 0) |
||||
|
// 默认按时间倒序排列(确保最新消息在顶部) |
||||
|
this.list.sort((a, b) => new Date(b.create_time) - new Date(a.create_time)) |
||||
|
|
||||
|
// 如果没有未读消息,直接显示所有已读 |
||||
|
if (!this.hasUnread) { |
||||
|
this.showEarlier = true; |
||||
|
} |
||||
|
|
||||
|
|
||||
|
}) |
||||
|
}, |
||||
|
JumpDetails(item){ |
||||
|
uni.navigateTo({ |
||||
|
url:`/pages/compass/friendscircledetails?posts_id=${item.posts_id}&userid=${item.posts_user_id}` |
||||
|
}) |
||||
|
}, |
||||
|
scrollbot(){ |
||||
|
if (this.status == 'noMore') { |
||||
|
return; |
||||
|
} |
||||
|
this.page++ |
||||
|
this.getNoticeList() |
||||
|
// console.log('已触发底部事件'); |
||||
|
}, |
||||
|
// 自动解析消息中的表情 |
||||
|
emojiToHtml(str){ |
||||
|
if(!str){ |
||||
|
return; |
||||
|
} |
||||
|
let emojiMap=this.emojiMap; |
||||
|
return str.replace(/\[!(\w+)\]/gi, function (str, match) { |
||||
|
var file = match; |
||||
|
return emojiMap[file] ? "<img class='mr-5' style=\"width:18px;height:18px\" emoji-name=\"".concat(match, "\" src=\"").concat(emojiMap[file], "\" />") : "[!".concat(match, "]"); |
||||
|
}); |
||||
|
}, |
||||
|
} |
||||
|
} |
||||
|
</script> |
||||
|
|
||||
|
<style scoped lang="scss"> |
||||
|
.load-more-button { |
||||
|
font-size: 28rpx; |
||||
|
color: #576b95; |
||||
|
padding: 30rpx 0; |
||||
|
border-top: 1px solid #e7e7e7; |
||||
|
} |
||||
|
</style> |
||||
@ -0,0 +1,410 @@ |
|||||
|
<template> |
||||
|
<cu-custom bgColor="bg-white" :isBack="true"> |
||||
|
<template #backText></template> |
||||
|
<template #content>详情</template> |
||||
|
</cu-custom> |
||||
|
<scroll-view scroll-y style="height: 92vh;"> |
||||
|
<view class="cu-card dynamic no-card"> |
||||
|
<view class="cu-item shadow"> |
||||
|
<view class="cu-list menu-avatar"> |
||||
|
<view class="cu-item" style="margin-top: 20px;" v-if="list.user"> |
||||
|
<view class="cu-avatar round lg" :style="[{backgroundImage:'url('+list.user.avatar+')'}]"></view> |
||||
|
<view class="content flex-sub"> |
||||
|
<view>{{list.user.nickname}}</view> |
||||
|
<view class="text-gray text-sm flex justify-between"> |
||||
|
{{list.create_time}} |
||||
|
</view> |
||||
|
</view> |
||||
|
</view> |
||||
|
</view> |
||||
|
<view class="text-content"> |
||||
|
<!-- <mp-html container-style="overflow: hidden;display:inline;white-space: pre-wrap" :content="emojiToHtml(list.content)"/> --> |
||||
|
<view class="text-content" v-if="contenthtml(list.content)" @click="handleLink(list.content)" v-html="list.content"></view> |
||||
|
<view class="text-content" v-html="list.content" v-else></view> |
||||
|
</view> |
||||
|
<view class="grid" style="margin: 0px 24rpx;"> |
||||
|
<view class="grid" style="margin: 2rpx 0px;padding: 0px 8rpx;" v-for="(itemss,indexs) in list.files" :key="indexs"> |
||||
|
<!-- flex-sub --> |
||||
|
<view class="bg-img" v-if="itemss.type==1"> |
||||
|
<image :src="apiUrl+itemss.src" mode="aspectFill" style="width: 200rpx;height: 200rpx;" :data-src="apiUrl+itemss.src" @tap="previewImage"></image> |
||||
|
</view> |
||||
|
<view class='course-video' :style="list.files ? $util.imageCoverStyle(270,480) : ''" v-if="itemss.type==2"> |
||||
|
<view class="relative-shadow" @tap="handlePlay(itemss)" style=""> |
||||
|
<view class="cuIcon-video icon-center f-28 c-white"></view> |
||||
|
</view> |
||||
|
<im-image style="position: absolute;top: 0px;" :src="apiUrl+itemss.privacy" ></im-image> |
||||
|
</view> |
||||
|
</view> |
||||
|
</view> |
||||
|
<view v-if="list.address" @tap="openLocation(list.location)" class="im-location-msg im-flex im-rows im-nowrap im-align-items-center radius-8 pd-10"> |
||||
|
<view class="f-20 cuIcon-location pr-5"></view> |
||||
|
<view> |
||||
|
<view class="f-10 mb-5">{{list.address}}</view> |
||||
|
</view> |
||||
|
|
||||
|
</view> |
||||
|
<view class="text-gray text-sm text-right padding"> |
||||
|
<text @click="onLike(list.id)" :class="['cuIcon-appreciatefill', list.is_like === 1 ? 'text-red' : 'margin-lr-xs']"></text> {{list.likes?list.likes.length:0}} |
||||
|
<text @click="onComment(null)" class="cuIcon-messagefill margin-lr-xs"></text> {{list.comment?list.comment.length:0}} |
||||
|
<text v-if="list.user_id==userInfo.user_id" class="cuIcon-delete" style="margin-left: 5px;" @click="Delete(list.id)">删除</text> |
||||
|
</view> |
||||
|
|
||||
|
<view v-show="boll" style="margin: 5px 15px;position: relative;box-shadow:0px 0px 5px rgba(0, 0, 0, 0.2);"> |
||||
|
<view style="padding-bottom: 45px;"> |
||||
|
<editor id="editor" class="bg-white c-333" style="min-height: 50px;height: 50px;padding:0px 10px ;" :adjust-position="false" maxlength="300" cursor-spacing="10" |
||||
|
@input="changeMsgText" @ready="onEditorReady" :read-only="readOnly"> </editor> |
||||
|
</view> |
||||
|
<view style="position: absolute;bottom: 5px;right: 10px;"> |
||||
|
<view style="display: flex;justify-content: space-between;"> |
||||
|
<view> |
||||
|
<view class="im-menus cuIcon-emoji f-28 ml-5 mr-10" hover-class="tap" @click.stop="showAppBox"></view> |
||||
|
<scroll-view scroll-y class="icon" v-if="isFocus"> |
||||
|
<view class="im-flex im-wrap im-justify-content-start im-align-items-center pd-10"> |
||||
|
<view v-for="(item,index) in currentEmojiList" class="im-emoji-item" :key="index"> |
||||
|
<image :src="item.src" style="width:44rpx;;height:44rpx" mode="aspectFit" lazy-load @tap="chooseEmoji(item)"></image> |
||||
|
</view> |
||||
|
</view> |
||||
|
</scroll-view> |
||||
|
</view> |
||||
|
<button class="cu-btn bg-green shadow " @click="sendTextMsg(list.id)">发送</button> |
||||
|
</view> |
||||
|
</view> |
||||
|
</view> |
||||
|
|
||||
|
<view class="flex Likeview" style="flex-wrap: wrap;" v-if="list.likes?.length!==0"> |
||||
|
<uni-icons type="heart" size="15" color="#576b95"></uni-icons> |
||||
|
<view v-for="(itemws,indexws) in list.likes" :key="indexws" class="flex align-center" style="margin: 5px;"> |
||||
|
<!-- <view style="margin-left: 2px;">{{itemws.nickname}}</view> --> |
||||
|
<image :src="itemws.avatar" mode="widthFix" style="width: 60rpx;margin-left: 2px;"></image> |
||||
|
</view> |
||||
|
</view> |
||||
|
|
||||
|
<view style="margin-bottom: 10px;" v-if="list.comment?.length!==0"> |
||||
|
<view class="cu-list menu-avatar comment" style="margin-top: 0px;" v-for="(iteme,indexw) in list.comment" :key="indexw"> |
||||
|
<view class="comment_view"> |
||||
|
<view class="content"> |
||||
|
<view class="flex align-center" style="gap: 12rpx;" @click.stop="onComment(iteme.id)"> |
||||
|
<view> |
||||
|
<view class="flex" v-if="iteme.reply_user_name!==''"> |
||||
|
<view>{{iteme.nickname}} <text style="color: #000;margin-right: 5px;">回复</text></view> |
||||
|
<view class="flex-sub"> {{iteme.reply_user_name}}:</view> |
||||
|
</view> |
||||
|
<view v-else>{{iteme.nickname}}:</view> |
||||
|
</view> |
||||
|
<view class="text-gray text-content text-df" style="flex: 1; min-width: 0; word-break: break-word;"> |
||||
|
<!-- <mp-html container-style="overflow: hidden;display:inline;white-space: pre-wrap;color: #000;" :content="emojiToHtml(iteme.content)"/> --> |
||||
|
<view class="text-content content_img" style="color: #000;" v-if="contenthtml(iteme.content)" @click="handleLink(iteme.content)" v-html="iteme.content"></view> |
||||
|
<view class="text-content" style="color: #000;" v-html="iteme.content" v-else></view> |
||||
|
</view> |
||||
|
</view> |
||||
|
</view> |
||||
|
</view> |
||||
|
</view> |
||||
|
</view> |
||||
|
</view> |
||||
|
</view> |
||||
|
</scroll-view> |
||||
|
</template> |
||||
|
|
||||
|
<script> |
||||
|
import { useloginStore } from '@/store/login'; |
||||
|
import config from '@/common/config.js'; |
||||
|
import emoji from '@/utils/emoji.js'; |
||||
|
import Edit from '@/utils/edit.js'; |
||||
|
import { chat } from '@/mixins/chat.js'; |
||||
|
import imImage from '@/components/message/im-image.vue'; |
||||
|
|
||||
|
const loginStore = useloginStore() |
||||
|
export default { |
||||
|
components:{ |
||||
|
imImage |
||||
|
}, |
||||
|
mixins:[chat], |
||||
|
data() { |
||||
|
return { |
||||
|
list: {}, |
||||
|
boll:false, |
||||
|
edit: null, |
||||
|
userid:'', |
||||
|
inputMsg:'', |
||||
|
posts_id:0, |
||||
|
emojiMap:[], |
||||
|
isFocus:false, |
||||
|
readOnly:false, |
||||
|
currentEmojiList:[], |
||||
|
apiUrl: config.apiUrl, |
||||
|
userInfo:loginStore.userInfo, |
||||
|
} |
||||
|
}, |
||||
|
created(){ |
||||
|
this.currentEmojiList=emoji[0]['children']; |
||||
|
|
||||
|
let emojiMap=[]; |
||||
|
// 解析所有表情 |
||||
|
emoji.forEach(function (item) { |
||||
|
let child=item.children; |
||||
|
if(child.length>0){ |
||||
|
child.forEach(function (val) { |
||||
|
let name=val.name; |
||||
|
let src=val.src; |
||||
|
emojiMap[name]=src; |
||||
|
}) |
||||
|
} |
||||
|
}); |
||||
|
this.emojiMap=emojiMap; |
||||
|
}, |
||||
|
onLoad(option) { |
||||
|
this.posts_id = option.posts_id |
||||
|
if(this.userInfo.user_id==option.userid){ |
||||
|
this.userid = "" |
||||
|
}else{ |
||||
|
this.userid = option.userid |
||||
|
} |
||||
|
}, |
||||
|
onShow() { |
||||
|
if(this.userid){ |
||||
|
this.init(this.userid) |
||||
|
}else{ |
||||
|
this.init() |
||||
|
} |
||||
|
}, |
||||
|
methods: { |
||||
|
showAppBox(){ |
||||
|
this.isFocus = !this.isFocus; |
||||
|
}, |
||||
|
// 选择表情 |
||||
|
chooseEmoji(item){ |
||||
|
// #ifdef H5 |
||||
|
this.editorCtx.insertImage({ |
||||
|
src: item.src, |
||||
|
alt: item.title, |
||||
|
width: 18, |
||||
|
height: 18, |
||||
|
nowrap:true, |
||||
|
extClass:'emoji-image', |
||||
|
success: ()=>{ |
||||
|
}, |
||||
|
complete: ()=> { |
||||
|
this.editorCtx.blur(); |
||||
|
}, |
||||
|
}); |
||||
|
// #endif |
||||
|
// #ifndef H5 |
||||
|
this.readOnly= true |
||||
|
setTimeout(()=>{ |
||||
|
this.editorCtx.insertImage({ |
||||
|
src: item.src, |
||||
|
alt: item.title, |
||||
|
width: 18, |
||||
|
height: 18, |
||||
|
nowrap:true, |
||||
|
extClass:'emoji-image', |
||||
|
success: function() { |
||||
|
}, |
||||
|
complete: ()=> { |
||||
|
this.readOnly= false |
||||
|
}, |
||||
|
}); |
||||
|
},10); |
||||
|
// #endif |
||||
|
}, |
||||
|
onEditorReady() { |
||||
|
// #ifdef MP-BAIDU |
||||
|
this.editorCtx = requireDynamicLib('editorLib').createEditorContext('editor'); |
||||
|
// #endif |
||||
|
|
||||
|
// #ifdef APP-PLUS || MP-WEIXIN || H5 |
||||
|
const query = uni.createSelectorQuery().in(this); |
||||
|
query.select('#editor').context((res) => { |
||||
|
this.edit = new Edit({context: res.context,maxCount: 300}); |
||||
|
this.editorCtx = res.context |
||||
|
}).exec() |
||||
|
// #endif |
||||
|
}, |
||||
|
previewImage(e) { |
||||
|
//预览图片 |
||||
|
var current = e.target.dataset.src |
||||
|
uni.previewImage({ |
||||
|
current: current, |
||||
|
urls: [current] |
||||
|
}) |
||||
|
}, |
||||
|
changeMsgText(e){ |
||||
|
const txt=e.detail.text.replace(/\n/g, ''); |
||||
|
if(txt=='' && e.detail.html=='<p><br></p>'){ |
||||
|
this.inputMsg=''; |
||||
|
}else{ |
||||
|
this.inputMsg=e.detail.html; |
||||
|
} |
||||
|
}, |
||||
|
onLike(id){ |
||||
|
this.$api.compaApi.onlikes({posts_id:id}).then(res => { |
||||
|
if(this.userid){ |
||||
|
this.init(this.userid) |
||||
|
}else{ |
||||
|
this.init() |
||||
|
} |
||||
|
// this.list = res.data |
||||
|
// console.log(res); |
||||
|
}) |
||||
|
}, |
||||
|
onComment(id){ |
||||
|
this.boll = !this.boll |
||||
|
if(id){ |
||||
|
uni.setStorageSync('pid',id) |
||||
|
} |
||||
|
}, |
||||
|
Delete(id){ |
||||
|
const _this = this |
||||
|
uni.showModal({ |
||||
|
title: "提示", |
||||
|
content: "是否要删除该图片", |
||||
|
success: (res) => { |
||||
|
if (res.confirm) { |
||||
|
_this.$api.compaApi.Deleteapost({posts_id:id}).then(res => { |
||||
|
if(_this.userid){ |
||||
|
_this.init(_this.userid) |
||||
|
}else{ |
||||
|
_this.init() |
||||
|
} |
||||
|
}) |
||||
|
}else if (res.cancel) { |
||||
|
console.log('用户点击取消'); |
||||
|
} |
||||
|
} |
||||
|
}) |
||||
|
}, |
||||
|
contenthtml(val){ |
||||
|
return val && (val.includes('http://') || val.includes('https://')); |
||||
|
}, |
||||
|
handleLink(url){ |
||||
|
const textOnly = url.replace(/<[^>]+>/g, ''); |
||||
|
const urlRegex = /https?:\/\/[^\s"']+/g; |
||||
|
const urls = textOnly.match(urlRegex) || []; |
||||
|
if(urls.length > 0){ |
||||
|
urls.forEach((res,index)=>{ |
||||
|
// #ifdef H5 |
||||
|
window.open(urls[index], '_blank'); |
||||
|
// #endif |
||||
|
// #ifdef APP-PLUS |
||||
|
plus.runtime.openURL(urls[index]); |
||||
|
// #endif |
||||
|
}) |
||||
|
}else{ |
||||
|
return |
||||
|
} |
||||
|
|
||||
|
}, |
||||
|
init(id){ |
||||
|
this.$api.compaApi.detailsList({posts_id:+this.posts_id,friend_user_id:id?+id:''}).then(res => { |
||||
|
const [latitudeStr, longitudeStr] = res.data.location?.split('+'); |
||||
|
const location = { |
||||
|
latitude: +latitudeStr?+latitudeStr:0, |
||||
|
longitude: +longitudeStr?+longitudeStr:0 |
||||
|
}; |
||||
|
res.data.location = location |
||||
|
this.list = res.data |
||||
|
}) |
||||
|
}, |
||||
|
sendTextMsg(id1){ |
||||
|
const _this = this |
||||
|
const pid = uni.getStorageSync('pid') |
||||
|
this.editorCtx.getContents({ |
||||
|
success:(e)=>{ |
||||
|
let msg=e.html; |
||||
|
const text = /^\s*$/.test(msg.replace(/<[^>]+>/g, "")); |
||||
|
const hasImg = /<img\b/i.test(msg); |
||||
|
if(msg==''||(text && !hasImg)){ |
||||
|
uni.showToast({ title: "内容不能为空", icon: "none" }); |
||||
|
msg='' |
||||
|
_this.editorCtx.clear(); |
||||
|
return |
||||
|
} |
||||
|
let params = {posts_id:id1,content:msg,pid:pid?pid:""} |
||||
|
// console.log(params); |
||||
|
_this.$api.compaApi.oncomments(params).then(res => { |
||||
|
uni.removeStorageSync('pid') |
||||
|
if(_this.userid){ |
||||
|
_this.init(_this.userid) |
||||
|
}else{ |
||||
|
_this.init() |
||||
|
} |
||||
|
_this.editorCtx.clear(); |
||||
|
this.boll = false; |
||||
|
// _this.list = res.data |
||||
|
// console.log(res); |
||||
|
}) |
||||
|
} |
||||
|
}) |
||||
|
}, |
||||
|
} |
||||
|
} |
||||
|
</script> |
||||
|
|
||||
|
<style scoped lang="scss"> |
||||
|
.im-input{ |
||||
|
height:100%; |
||||
|
font-size: 28rpx; |
||||
|
min-height:150rpx; |
||||
|
max-height: 300rpx; |
||||
|
padding:14rpx 14rpx; |
||||
|
border-radius:10rpx; |
||||
|
word-break: break-all; |
||||
|
// margin:0 8rpx !important; |
||||
|
} |
||||
|
.im-location-msg{ |
||||
|
color:#2B2E3D; |
||||
|
margin: 5px 10px; |
||||
|
text-align: left !important; |
||||
|
} |
||||
|
.relative-shadow{ |
||||
|
z-index:1; |
||||
|
width:100%; |
||||
|
height:100%; |
||||
|
height: 240px; |
||||
|
display: flex; |
||||
|
position: absolute; |
||||
|
align-items: center; |
||||
|
justify-content: center; |
||||
|
} |
||||
|
.Likeview{ |
||||
|
padding: 5px; |
||||
|
display: flex; |
||||
|
color: #576b95; |
||||
|
align-items: center; |
||||
|
background-color: #f7f7f7; |
||||
|
margin: 0rem 0.9375rem 0rem 2rem; |
||||
|
} |
||||
|
.course-video{ |
||||
|
overflow: hidden; |
||||
|
position: relative; |
||||
|
} |
||||
|
.comment_view{ |
||||
|
height: auto; |
||||
|
color: #576b95; |
||||
|
padding: 20rpx 30rpx; |
||||
|
background-color: #f7f7f7; |
||||
|
margin: 0rem 0.9375rem 0rem 2rem; |
||||
|
} |
||||
|
.icon{ |
||||
|
top: -440rpx; |
||||
|
left: -170rpx; |
||||
|
width: 400rpx; |
||||
|
height: 400rpx; |
||||
|
position: absolute; |
||||
|
border-radius: 10rpx; |
||||
|
background-color: #fff; |
||||
|
box-shadow:0px 0px 5px rgba(0, 0, 0, 0.2); |
||||
|
|
||||
|
.im-emoji-item{ |
||||
|
padding:22rpx; |
||||
|
} |
||||
|
} |
||||
|
::v-deep .ql-editor{ |
||||
|
padding-top: 10px; |
||||
|
} |
||||
|
::v-deep .content_img img{ |
||||
|
width: 18px; |
||||
|
margin-right: 5px; |
||||
|
} |
||||
|
</style> |
||||
@ -0,0 +1,103 @@ |
|||||
|
<template> |
||||
|
<view :style="{paddingBottom: paddingB+'px'}"> |
||||
|
<view class="cu-list menu" :class="['sm-border','card-menu margin-top']" v-if="compass().mode == 1"> |
||||
|
<block v-for="(item,index) in sortedList" :key="item.order"> |
||||
|
<view class="cu-item" :class="'arrow'" @tap="openApp(item)"> |
||||
|
<view class="content"> |
||||
|
<image :src="item.icon" class="png" mode="aspectFit"></image> |
||||
|
<text class="text-grey">{{item.name}}</text> |
||||
|
</view> |
||||
|
<view class="content_num" v-if="item.name=='朋友圈'&&count>0">{{count>99?'99+':count}}</view> |
||||
|
</view> |
||||
|
</block> |
||||
|
</view> |
||||
|
<view class="cu-list grid" :class="['col-3',' margin-top']" v-if="compass().mode == 2"> |
||||
|
<block v-for="(item,index) in sortedList" :key="item.order"> |
||||
|
<view class="cu-item" @tap="openApp(item)"> |
||||
|
<view> |
||||
|
<image :src="item.icon" style="height:100rpx;width: 100rpx;"></image> |
||||
|
<view class="cu-tag badge" v-if="item.badge!=0"> |
||||
|
<block v-if="item.badge!=1">{{item.badge>99?'99+':item.badge}}</block> |
||||
|
</view> |
||||
|
</view> |
||||
|
<text>{{item.name}}</text> |
||||
|
</view> |
||||
|
</block> |
||||
|
</view> |
||||
|
|
||||
|
</view> |
||||
|
</template> |
||||
|
|
||||
|
<script> |
||||
|
import { useloginStore } from '@/store/login' |
||||
|
import pinia from '@/store/index' |
||||
|
import { storeToRefs } from 'pinia'; |
||||
|
import { useMsgStore } from '@/store/message'; |
||||
|
const loginStore = useloginStore(pinia) |
||||
|
|
||||
|
const msgStore = useMsgStore(pinia) |
||||
|
const {NoticeCount} = storeToRefs(msgStore); |
||||
|
|
||||
|
export default { |
||||
|
data() { |
||||
|
return { |
||||
|
isCard: true, |
||||
|
userInfo:loginStore.userInfo, |
||||
|
paddingB:0, |
||||
|
count:NoticeCount |
||||
|
}; |
||||
|
}, |
||||
|
computed:{ |
||||
|
sortedList() { |
||||
|
return this.compass()?.list |
||||
|
.filter(item => item.status == 1) |
||||
|
.sort((a, b) => a.order - b.order) |
||||
|
} |
||||
|
}, |
||||
|
created:function(){ |
||||
|
// #ifdef H5 |
||||
|
this.paddingB=this.inlineTools; |
||||
|
// #endif |
||||
|
|
||||
|
// #ifndef H5 |
||||
|
this.paddingB=this.navBarHeight+this.inlineTools; |
||||
|
// #endif |
||||
|
}, |
||||
|
methods: { |
||||
|
compass(){ |
||||
|
return loginStore.globalConfig.compass |
||||
|
}, |
||||
|
openApp(item) { |
||||
|
let url=item.url; |
||||
|
if(item.type==2){ |
||||
|
url='/pages/mine/webview?title='+item.name+'&src='+encodeURIComponent(item.url) |
||||
|
} |
||||
|
uni.navigateTo({ |
||||
|
url:url |
||||
|
}) |
||||
|
}, |
||||
|
} |
||||
|
} |
||||
|
</script> |
||||
|
|
||||
|
<style lang="scss"> |
||||
|
.im-friend-header{ |
||||
|
width:100%;height:400rpx;position: relative; |
||||
|
.im-friend-bg{ |
||||
|
width:100%;height:300rpx;overflow: hidden; |
||||
|
.im-friend-image{ |
||||
|
width:100%;height:300rpx; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
.im-user{ |
||||
|
position: absolute;right:60rpx;top:210rpx;overflow: hidden; |
||||
|
} |
||||
|
.content_num{ |
||||
|
color: #fff; |
||||
|
padding: 1px 5px; |
||||
|
border-radius: 50px; |
||||
|
text-align: center; |
||||
|
background-color: red; |
||||
|
} |
||||
|
</style> |
||||
@ -0,0 +1,547 @@ |
|||||
|
<template> |
||||
|
<cu-custom bgColor="bg-white" :isBack="true"> |
||||
|
<template #backText></template> |
||||
|
<template #content>朋友圈</template> |
||||
|
</cu-custom> |
||||
|
<!-- #ifdef APP-PLUS --> |
||||
|
<scroll-view scroll-y @scrolltolower="scrollbot" :lower-threshold="0" refresher-enabled="true" :refresher-triggered="refreshing" |
||||
|
:refresher-threshold="50" @refresherrefresh="onRefresh" style="height: 88vh;"> |
||||
|
<!-- #endif --> |
||||
|
<!-- #ifdef H5 --> |
||||
|
<scroll-view scroll-y @scrolltolower="scrollbot" :lower-threshold="0" refresher-enabled="true" :refresher-triggered="refreshing" |
||||
|
:refresher-threshold="50" @refresherrefresh="onRefresh" style="height: 93vh;"> |
||||
|
<!-- #endif --> |
||||
|
<!-- --> |
||||
|
<view @click="onicons" :style="{paddingBottom: paddingB+'px'}"> |
||||
|
<view class="im-friend-header"> |
||||
|
<view class="im-friend-bg"> |
||||
|
<image class="im-friend-image" src="/static/image/user-card-bg.jpg" mode="widthFix"></image> |
||||
|
</view> |
||||
|
<view class="im-user im-flex im-justify-content-start align-center"> |
||||
|
<text class="text-white mr-5">{{userInfo.realname}}</text> |
||||
|
<image class="radius-10" style="width:120rpx;height:120rpx" :src="userInfo.avatar" mode="widthFix"></image> |
||||
|
</view> |
||||
|
<view style="position: absolute;top: 15px;right: 18px;" @click="tomoments"> |
||||
|
<image class="radius-10" style="width:50rpx;height:50rpx" src="/static/image/moments.png" mode="widthFix"></image> |
||||
|
</view> |
||||
|
</view> |
||||
|
|
||||
|
<view class="text-center" style="background-color: #fff;padding: 10px;" @click="tonotice" v-if="notice>0"> |
||||
|
<view style="width: 100px;height: 30px;background-color: #f0f0f0;line-height: 30px;margin: auto;border-radius: 5px;color: #6b72ff;">{{notice}}条新信息</view> |
||||
|
</view> |
||||
|
<view v-for="(item,index) in list" :key="index"> |
||||
|
<view class="cu-card dynamic no-card"> |
||||
|
<view class="cu-item shadow"> |
||||
|
<view class="cu-list menu-avatar"> |
||||
|
<view class="cu-item" style="padding-top: 20px;"> |
||||
|
<view class="cu-avatar round lg" :style="[{backgroundImage:'url('+item.user.avatar+')'}]"></view> |
||||
|
<view class="content flex-sub"> |
||||
|
<view>{{item.user.nickname}}</view> |
||||
|
<view class="text-gray text-sm flex justify-between"> |
||||
|
{{item.create_time}} |
||||
|
</view> |
||||
|
</view> |
||||
|
</view> |
||||
|
</view> |
||||
|
<view class="text-content" v-if="contenthtml(item.content)" @click="handleLink(item.content)" v-html="item.content"></view> |
||||
|
<view class="text-content" v-html="item.content" v-else></view> |
||||
|
|
||||
|
<view class="grid" style="margin: 0px 24rpx;"> |
||||
|
<view class="grid" style="margin: 2rpx 0px;padding: 0px 8rpx;" v-for="(itemss,indexs) in item.files" :key="indexs"> |
||||
|
<!-- flex-sub --> |
||||
|
<view class="bg-img" v-if="itemss.type==1"> |
||||
|
<image :src="apiUrl+itemss.src" mode="aspectFill" style="width: 200rpx;height: 200rpx;" :data-src="apiUrl+itemss.src" @tap="previewImage"></image> |
||||
|
</view> |
||||
|
<view class='course-video' :style="item.files ? $util.imageCoverStyle(270,480) : ''" v-if="itemss.type==2"> |
||||
|
<view class="relative-shadow" @tap="handlePlay(itemss)" style=""> |
||||
|
<view class="cuIcon-video icon-center f-28 c-white"></view> |
||||
|
</view> |
||||
|
<im-image style="position: absolute;top: 0px;" :src="apiUrl+itemss.privacy" ></im-image> |
||||
|
<!-- :info="item.files" --> |
||||
|
</view> |
||||
|
</view> |
||||
|
</view> |
||||
|
<view v-if="item.location_address" @tap="openLocation(item.location)" class="im-location-msg im-flex im-rows im-nowrap im-align-items-center radius-8 pd-10"> |
||||
|
<view class="f-20 cuIcon-location pr-5"></view> |
||||
|
<view> |
||||
|
<view class="f-10 mb-5">{{item.location_address}}</view> |
||||
|
<!-- <view class="c-999 f-12">{{item.extends && item.extends.address}}</view> --> |
||||
|
</view> |
||||
|
|
||||
|
</view> |
||||
|
<view class="text-gray text-sm text-right padding"> |
||||
|
<!-- <text class="cuIcon-attentionfill margin-lr-xs"></text> 10 --> |
||||
|
<!-- +item.is_like --> |
||||
|
<text @click="onLike(item.id)" :class="['cuIcon-appreciatefill', item.is_like === 1 ? 'text-red' : 'margin-lr-xs']"></text> {{item.likes.length}} |
||||
|
<text @click="onComment(index)" class="cuIcon-messagefill margin-lr-xs"></text> {{item.comment.length}} |
||||
|
<text v-if="item.user_id==userInfo.user_id" class="cuIcon-delete" style="margin-left: 5px;" @click="Delete(item.id)">删除</text> |
||||
|
</view> |
||||
|
|
||||
|
<view v-show="numindex === index&&boll" style="margin: 5px 15px 5px 15px;position: relative;box-shadow:0px 0px 5px rgba(0, 0, 0, 0.2);"> |
||||
|
<view style="padding-bottom: 45px;"> |
||||
|
<!-- <uni-easyinput id="input" type="textarea" autoHeight v-model="content" placeholder="评论"></uni-easyinput> --> |
||||
|
<!-- id="editor1" --> |
||||
|
<editor :id="'editor' + index" class=" bg-white c-333" style="min-height: 50px;height: 50px;padding:0px 10px;" :adjust-position="false" |
||||
|
maxlength="200" cursor-spacing="10" @input="changeMsgText" @ready="onEditorReady(index)" :read-only="readOnly"> </editor> |
||||
|
<!-- @focus="InputFocus" @blur="InputBlur" --> |
||||
|
</view> |
||||
|
<view style="position: absolute;bottom: 5px;right: 10px;"> |
||||
|
<view style="display: flex;justify-content: space-between;"> |
||||
|
<view> |
||||
|
<view class="im-menus cuIcon-emoji f-28 ml-5 mr-10" hover-class="tap" @click.stop="showAppBox"></view> |
||||
|
<scroll-view scroll-y class="icon" v-if="isFocus"> |
||||
|
<view class="im-flex im-wrap im-justify-content-start im-align-items-center pd-10"> |
||||
|
<view v-for="(item,index) in currentEmojiList" class="im-emoji-item" :key="index"> |
||||
|
<image :src="item.src" style="width:44rpx;;height:44rpx" mode="aspectFit" lazy-load @tap="chooseEmoji(item)"></image> |
||||
|
</view> |
||||
|
</view> |
||||
|
</scroll-view> |
||||
|
</view> |
||||
|
<button class="cu-btn bg-green shadow " @touchend.prevent="sendTextMsg(item.id)">发送</button> |
||||
|
</view> |
||||
|
</view> |
||||
|
</view> |
||||
|
|
||||
|
<view class="flex Likeview" v-if="item.likes.length!=0"> |
||||
|
<view v-for="(itemws,indexws) in item.likes" :key="indexws" class="flex align-center" style="margin-right: 5px;"> |
||||
|
<uni-icons type="heart" size="15" color="#576b95"></uni-icons> |
||||
|
<view style="margin-left: 2px;">{{itemws.nickname}}</view> |
||||
|
</view> |
||||
|
</view> |
||||
|
|
||||
|
<view style="margin-bottom: 10px;"> |
||||
|
<view class="cu-list menu-avatar comment" style="margin-top: 0px;" v-for="(iteme,indexw) in item.comment" :key="indexw"> |
||||
|
<view class="comment_view"> |
||||
|
<view class="content"> |
||||
|
<view class="flex align-center" style="gap: 12rpx;" @click.stop="onComment(index,iteme.id)"> |
||||
|
<view> |
||||
|
<view class="flex" v-if="iteme.reply_user_name!==''"> |
||||
|
<view>{{iteme.nickname}} <text style="color: #000;margin-right: 5px;">回复</text></view> |
||||
|
<view class="flex-sub"> {{iteme.reply_user_name}}:</view> |
||||
|
</view> |
||||
|
<view v-else>{{iteme.nickname}}:</view> |
||||
|
</view> |
||||
|
<view class="text-gray text-content text-df" style="flex: 1; min-width: 0; word-break: break-word;"> |
||||
|
<view class="text-content content_img" style="color: #000;" v-if="contenthtml(iteme.content)" @click.stop="handleLink(iteme.content,index,iteme.id)" v-html="iteme.content"></view> |
||||
|
<view class="text-content" style="color: #000;" v-html="iteme.content" v-else></view> |
||||
|
<!-- <mp-html container-style="overflow: hidden;display:inline;white-space: pre-wrap;color: #000;" :content="emojiToHtml(iteme.content)"/> --> |
||||
|
</view> |
||||
|
</view> |
||||
|
</view> |
||||
|
</view> |
||||
|
</view> |
||||
|
</view> |
||||
|
</view> |
||||
|
</view> |
||||
|
</view> |
||||
|
<view v-if="list.length==0" style="display: flex;justify-content: center;align-items: center;margin-top: 40px;"> |
||||
|
<view> |
||||
|
<image src="@/static/image/empty.png" mode="widthFix" style="width: 560rpx;"></image> |
||||
|
<view style="text-align: center;color: #ccc;font-size: 40rpx;margin-right: 10px;">暂无数据</view> |
||||
|
</view> |
||||
|
</view> |
||||
|
|
||||
|
</view> |
||||
|
<uni-load-more :status="status" :content-text="contentText"></uni-load-more> |
||||
|
</scroll-view> |
||||
|
</template> |
||||
|
|
||||
|
<script> |
||||
|
import { useloginStore } from '@/store/login' |
||||
|
import config from '@/common/config.js' |
||||
|
import emoji from '@/utils/emoji.js' |
||||
|
import Edit from '@/utils/edit.js' |
||||
|
import { chat } from '@/mixins/chat.js' |
||||
|
import imImage from '@/components/message/im-image.vue'; |
||||
|
|
||||
|
import pinia from '@/store/index' |
||||
|
import { storeToRefs } from 'pinia'; |
||||
|
import { useMsgStore } from '@/store/message'; |
||||
|
const msgStore = useMsgStore(pinia) |
||||
|
const {NoticeCount} = storeToRefs(msgStore); |
||||
|
|
||||
|
const loginStore = useloginStore() |
||||
|
export default { |
||||
|
components:{ |
||||
|
imImage |
||||
|
}, |
||||
|
mixins:[chat], |
||||
|
data() { |
||||
|
return { |
||||
|
notice:NoticeCount, |
||||
|
isCard: true, |
||||
|
apiUrl: config.apiUrl, |
||||
|
userInfo:loginStore.userInfo, |
||||
|
paddingB:0, |
||||
|
page:1, |
||||
|
limit:20, |
||||
|
content:'', |
||||
|
numindex:-1, |
||||
|
boll:false, |
||||
|
list:[], |
||||
|
// editorCtx:null, |
||||
|
editorCtx:{}, |
||||
|
currentEmojiList:[], |
||||
|
emojiMap:[], |
||||
|
isFocus:false, |
||||
|
edit: null, |
||||
|
readOnly:false, |
||||
|
refreshing: false, |
||||
|
status: 'more',//状态 |
||||
|
inputMsg:'', |
||||
|
//显示的状态 |
||||
|
contentText: { |
||||
|
contentdown: '查看更多', |
||||
|
contentrefresh: '加载中....', |
||||
|
contentnomore: '没有更多咯' |
||||
|
}, |
||||
|
}; |
||||
|
}, |
||||
|
created(){ |
||||
|
// #ifdef H5 |
||||
|
this.paddingB=this.inlineTools; |
||||
|
// #endif |
||||
|
|
||||
|
// #ifndef H5 |
||||
|
this.paddingB=this.navBarHeight+this.inlineTools; |
||||
|
// #endif |
||||
|
this.currentEmojiList=emoji[0]['children']; |
||||
|
|
||||
|
let emojiMap=[]; |
||||
|
// 解析所有表情 |
||||
|
emoji.forEach(function (item) { |
||||
|
let child=item.children; |
||||
|
if(child.length>0){ |
||||
|
child.forEach(function (val) { |
||||
|
let name=val.name; |
||||
|
let src=val.src; |
||||
|
emojiMap[name]=src; |
||||
|
}) |
||||
|
} |
||||
|
}); |
||||
|
this.emojiMap=emojiMap; |
||||
|
}, |
||||
|
onShow() { |
||||
|
this.page = 1 |
||||
|
this.wechatMomentsList() |
||||
|
}, |
||||
|
methods: { |
||||
|
onRefresh() { |
||||
|
this.page = 1 |
||||
|
this.wechatMomentsList() |
||||
|
if (this.refreshing) return; |
||||
|
this.refreshing = true; |
||||
|
setTimeout(() => { |
||||
|
this.refreshing = false; |
||||
|
}, 1000) |
||||
|
}, |
||||
|
contenthtml(val){ |
||||
|
return val && (val.includes('http://') || val.includes('https://')); |
||||
|
}, |
||||
|
handleLink(url,index,id){ |
||||
|
const textOnly = url.replace(/<[^>]+>/g, ''); |
||||
|
const urlRegex = /https?:\/\/[^\s"']+/g; |
||||
|
const urls = textOnly.match(urlRegex) || []; |
||||
|
// console.log(urls); |
||||
|
if(urls.length > 0){ |
||||
|
urls.forEach((res,index)=>{ |
||||
|
// #ifdef H5 |
||||
|
window.open(urls[index], '_blank'); |
||||
|
// #endif |
||||
|
// #ifdef APP-PLUS |
||||
|
plus.runtime.openURL(urls[index]); |
||||
|
// #endif |
||||
|
}) |
||||
|
}else{ |
||||
|
this.onComment(index,id) |
||||
|
return |
||||
|
} |
||||
|
|
||||
|
}, |
||||
|
IsCard(e) { |
||||
|
this.isCard = e.detail.value |
||||
|
}, |
||||
|
wechatMomentsList(){ |
||||
|
let params = {page:this.page,limit:this.limit} |
||||
|
this.status = 'loading'; // 加载中 |
||||
|
this.$api.compaApi.wechatMomentsList(params).then(res => { |
||||
|
res.data.forEach((res)=>{ |
||||
|
const [latitudeStr, longitudeStr] = res.location.split('+'); |
||||
|
const location = { |
||||
|
latitude: +latitudeStr, |
||||
|
longitude: +longitudeStr |
||||
|
}; |
||||
|
res.location = location |
||||
|
res.content = res.content.replace(/\n/g, '<br>') |
||||
|
}) |
||||
|
if (this.page === 1) { |
||||
|
this.list = res.data |
||||
|
} else { |
||||
|
this.list = this.list.concat(res.data); |
||||
|
} |
||||
|
// 判断是否还有更多数据 |
||||
|
if (this.list.length < res.count) { |
||||
|
this.status = 'more'; // 还有更多数据 |
||||
|
} else { |
||||
|
this.status = 'noMore'; // 没有更多数据 |
||||
|
} |
||||
|
|
||||
|
// console.log(res); |
||||
|
}) |
||||
|
}, |
||||
|
scrollbot(){ |
||||
|
if (this.status == 'noMore') { |
||||
|
return; |
||||
|
} |
||||
|
this.page++ |
||||
|
this.wechatMomentsList() |
||||
|
// console.log('已触发底部事件'); |
||||
|
}, |
||||
|
tomoments(){ |
||||
|
uni.navigateTo({ |
||||
|
url:'/pages/compass/sendtoMoments' |
||||
|
}) |
||||
|
}, |
||||
|
tonotice(){ |
||||
|
uni.navigateTo({ |
||||
|
url:'/pages/compass/Informationdetails' |
||||
|
}) |
||||
|
}, |
||||
|
onLike(id){ |
||||
|
this.$api.compaApi.onlikes({posts_id:id}).then(res => { |
||||
|
this.wechatMomentsList() |
||||
|
// this.list = res.data |
||||
|
// console.log(res); |
||||
|
}) |
||||
|
}, |
||||
|
onComment(index,id){ |
||||
|
// 如果点击的是同一个项,关闭编辑 |
||||
|
if (this.numindex === index) { |
||||
|
this.boll = false |
||||
|
uni.removeStorageSync('pid') |
||||
|
this.numindex = -1 |
||||
|
return |
||||
|
} |
||||
|
|
||||
|
this.numindex = index |
||||
|
this.boll = !this.boll |
||||
|
if(id&&this.boll == true){ |
||||
|
uni.setStorageSync('pid',id) |
||||
|
} |
||||
|
}, |
||||
|
showAppBox(){ |
||||
|
this.isFocus = !this.isFocus; |
||||
|
}, |
||||
|
onicons(){ |
||||
|
this.isFocus = false; |
||||
|
}, |
||||
|
// 选择表情 |
||||
|
chooseEmoji(item){ |
||||
|
// #ifdef H5 |
||||
|
this.editorCtx[this.numindex].insertImage({ |
||||
|
src: item.src, |
||||
|
alt: item.title, |
||||
|
width: 18, |
||||
|
height: 18, |
||||
|
nowrap:true, |
||||
|
extClass:'emoji-image', |
||||
|
success: ()=>{ |
||||
|
}, |
||||
|
complete: ()=> { |
||||
|
this.editorCtx[this.numindex].blur(); |
||||
|
}, |
||||
|
}); |
||||
|
// console.log(this.editorCtx[this.numindex]); |
||||
|
// #endif |
||||
|
// #ifndef H5 |
||||
|
this.readOnly= true |
||||
|
setTimeout(()=>{ |
||||
|
this.editorCtx[this.numindex].insertImage({ |
||||
|
src: item.src, |
||||
|
alt: item.title, |
||||
|
width: 18, |
||||
|
height: 18, |
||||
|
nowrap:true, |
||||
|
extClass:'emoji-image', |
||||
|
success: function() { |
||||
|
}, |
||||
|
complete: ()=> { |
||||
|
this.readOnly= false |
||||
|
}, |
||||
|
}); |
||||
|
},10); |
||||
|
// #endif |
||||
|
}, |
||||
|
onEditorReady(index) { |
||||
|
const editorId = 'editor' + index; |
||||
|
// #ifdef MP-BAIDU |
||||
|
// this.editorCtx = requireDynamicLib('editorLib').createEditorContext('editor1'); |
||||
|
this.editorCtx[index] = requireDynamicLib('editorLib').createEditorContext(editorId); |
||||
|
// #endif |
||||
|
|
||||
|
// #ifdef APP-PLUS || MP-WEIXIN || H5 |
||||
|
const query = uni.createSelectorQuery().in(this); |
||||
|
// query.select('#editor1').context((res) => { |
||||
|
query.select('#editor' + index).context((res) => { |
||||
|
this.edit = new Edit({context: res.context,maxCount: 300}); |
||||
|
this.editorCtx[index] = res.context |
||||
|
}).exec() |
||||
|
// #endif |
||||
|
}, |
||||
|
changeMsgText(e){ |
||||
|
const txt=e.detail.text.replace(/\n/g, ''); |
||||
|
if(txt=='' && e.detail.html=='<p><br></p>'){ |
||||
|
this.inputMsg=''; |
||||
|
}else{ |
||||
|
this.inputMsg=e.detail.html; |
||||
|
} |
||||
|
}, |
||||
|
// 自动解析消息中的表情 |
||||
|
emojiToHtml(str){ |
||||
|
if(!str){ |
||||
|
return; |
||||
|
} |
||||
|
let emojiMap=this.emojiMap; |
||||
|
return str.replace(/\[!(\w+)\]/gi, function (str, match) { |
||||
|
var file = match; |
||||
|
return emojiMap[file] ? "<img class='mr-5' style=\"width:18px;height:18px\" emoji-name=\"".concat(match, "\" src=\"").concat(emojiMap[file], "\" />") : "[!".concat(match, "]"); |
||||
|
}); |
||||
|
}, |
||||
|
previewImage(e) { |
||||
|
//预览图片 |
||||
|
var current = e.target.dataset.src |
||||
|
uni.previewImage({ |
||||
|
current: current, |
||||
|
urls: [current] |
||||
|
}) |
||||
|
}, |
||||
|
Delete(id){ |
||||
|
const _this = this |
||||
|
uni.showModal({ |
||||
|
title: "提示", |
||||
|
content: "是否要删除该内容", |
||||
|
success: (res) => { |
||||
|
if (res.confirm) { |
||||
|
_this.$api.compaApi.Deleteapost({posts_id:id}).then(res => { |
||||
|
_this.wechatMomentsList() |
||||
|
}) |
||||
|
}else if (res.cancel) { |
||||
|
console.log('用户点击取消'); |
||||
|
} |
||||
|
} |
||||
|
}) |
||||
|
}, |
||||
|
sendTextMsg(id1){ |
||||
|
const _this = this |
||||
|
const pid = uni.getStorageSync('pid') |
||||
|
const currentIndex = this.numindex |
||||
|
this.editorCtx[currentIndex].getContents({ |
||||
|
success:(e)=>{ |
||||
|
let msg=e.html; |
||||
|
// console.log('消息发送',e); |
||||
|
let params = {posts_id:id1,content:msg,pid:pid?pid:""} |
||||
|
const text = /^\s*$/.test(params.content.replace(/<[^>]+>/g, "")); |
||||
|
const hasImg = /<img\b/i.test(params.content); |
||||
|
if(params.content==''|| (text && !hasImg)){ |
||||
|
uni.showToast({title: '内容不能为空!',icon:'error'}); |
||||
|
return |
||||
|
} |
||||
|
_this.$api.compaApi.oncomments(params).then(res => { |
||||
|
uni.removeStorageSync('pid') |
||||
|
_this.wechatMomentsList() |
||||
|
_this.inputMsg = "" |
||||
|
_this.boll = false; |
||||
|
_this.editorCtx[currentIndex].clear(); |
||||
|
// _this.list = res.data |
||||
|
// console.log(_this.editorCtx.clear()); |
||||
|
}) |
||||
|
} |
||||
|
}) |
||||
|
}, |
||||
|
} |
||||
|
} |
||||
|
</script> |
||||
|
|
||||
|
<style scoped lang="scss"> |
||||
|
.im-friend-header{ |
||||
|
width:100%;height:400rpx;position: relative; |
||||
|
.im-friend-bg{ |
||||
|
width:100%;height:400rpx;overflow: hidden; |
||||
|
.im-friend-image{ |
||||
|
width:100%;height:400rpx; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
.im-user{ |
||||
|
position: absolute; |
||||
|
right:60rpx; |
||||
|
top:210rpx; |
||||
|
overflow: hidden; |
||||
|
} |
||||
|
.icon{ |
||||
|
top: -440rpx; |
||||
|
left: -170rpx; |
||||
|
width: 400rpx; |
||||
|
height: 400rpx; |
||||
|
position: absolute; |
||||
|
border-radius: 10rpx; |
||||
|
background-color: #fff; |
||||
|
box-shadow:0px 0px 5px rgba(0, 0, 0, 0.2); |
||||
|
|
||||
|
.im-emoji-item{ |
||||
|
padding:22rpx; |
||||
|
} |
||||
|
} |
||||
|
.im-input{ |
||||
|
height:100%; |
||||
|
font-size: 28rpx; |
||||
|
min-height:150rpx; |
||||
|
max-height: 300rpx; |
||||
|
padding:14rpx 14rpx; |
||||
|
border-radius:10rpx; |
||||
|
word-break: break-all; |
||||
|
margin:0 8rpx !important; |
||||
|
} |
||||
|
.im-location-msg{ |
||||
|
color:#2B2E3D; |
||||
|
margin: 5px 10px; |
||||
|
text-align: left !important; |
||||
|
} |
||||
|
.relative-shadow{ |
||||
|
z-index:1; |
||||
|
width:100%; |
||||
|
height:100%; |
||||
|
height: 240px; |
||||
|
display: flex; |
||||
|
position: absolute; |
||||
|
align-items: center; |
||||
|
justify-content: center; |
||||
|
} |
||||
|
.Likeview{ |
||||
|
padding: 5px; |
||||
|
display: flex; |
||||
|
color: #576b95; |
||||
|
align-items: center; |
||||
|
background-color: #f7f7f7; |
||||
|
margin: 0rem 0.9375rem 0rem 2rem; |
||||
|
} |
||||
|
.course-video{ |
||||
|
overflow: hidden; |
||||
|
position: relative; |
||||
|
} |
||||
|
.comment_view{ |
||||
|
height: auto; |
||||
|
color: #576b95; |
||||
|
padding: 20rpx 30rpx; |
||||
|
background-color: #f7f7f7; |
||||
|
margin: 0rem 0.9375rem 0rem 2rem; |
||||
|
} |
||||
|
::v-deep .ql-editor{ |
||||
|
padding-top: 10px; |
||||
|
} |
||||
|
|
||||
|
::v-deep .content_img img{ |
||||
|
width: 18px; |
||||
|
margin-right: 5px; |
||||
|
} |
||||
|
</style> |
||||
@ -0,0 +1,313 @@ |
|||||
|
<template> |
||||
|
<cu-custom bgColor="bg-white" :isBack="true"> |
||||
|
<template #backText></template> |
||||
|
<template #content>朋友圈</template> |
||||
|
</cu-custom> |
||||
|
<!-- #ifdef APP-PLUS --> |
||||
|
<scroll-view scroll-y @scrolltolower="scrollbot" :lower-threshold="0" refresher-enabled="true" :refresher-triggered="refreshing" |
||||
|
:refresher-threshold="50" @refresherrefresh="onRefresh" style="height: 88vh;"> |
||||
|
<!-- #endif --> |
||||
|
<!-- #ifdef H5 --> |
||||
|
<scroll-view scroll-y @scrolltolower="scrollbot" :lower-threshold="0" refresher-enabled="true" :refresher-triggered="refreshing" |
||||
|
:refresher-threshold="50" @refresherrefresh="onRefresh" style="height: 93vh;"> |
||||
|
<!-- #endif --> |
||||
|
<view> |
||||
|
<view class="im-friend-header"> |
||||
|
<view class="im-friend-bg"> |
||||
|
<image class="im-friend-image" src="/static/image/user-card-bg.jpg" style="width: 750rpx;height: 460rpx;"></image> |
||||
|
</view> |
||||
|
<view class="im-user im-flex im-justify-content-start align-center"> |
||||
|
<text class="text-white mr-5">{{name}}</text> |
||||
|
<image class="radius-10" style="width:120rpx;height:120rpx" :src="urlimg" mode="widthFix"></image> |
||||
|
</view> |
||||
|
</view> |
||||
|
</view> |
||||
|
|
||||
|
<view class="viewtop" v-if="userInfo.user_id==userid"> |
||||
|
<view class="flex view_mar"> |
||||
|
<view class="view_text">今天</view> |
||||
|
<view class="view_icons"> |
||||
|
<uni-icons @tap="tomoments" type="camera-filled" size="30" color="#8a8a8a" style="padding: 30px;"></uni-icons> |
||||
|
</view> |
||||
|
</view> |
||||
|
</view> |
||||
|
|
||||
|
<view v-for="(yearGroup, year) in groupedList" :key="year" class="year-group"> |
||||
|
<!-- 年份标题 --> |
||||
|
<view class="year-title" @click="toggleYear(year)"> |
||||
|
<text class="year-text">{{ year }}年</text> |
||||
|
</view> |
||||
|
|
||||
|
<!-- 动态内容(展开时显示) --> |
||||
|
<view v-if="!collapsedYears.includes(year)" class="moments-list"> |
||||
|
<view v-for="(item, index) in yearGroup" :key="item.id" class="moment-item" @click="JumpDetails(item.id)"> |
||||
|
<view class="meta-info"> |
||||
|
<text class="time">{{ formatTime(item.create_time) }}</text> |
||||
|
</view> |
||||
|
<view class="content1"> |
||||
|
<view v-if="item.files" v-for="(items, indexs) in item.files" :key="indexs"> |
||||
|
<view v-if="items.type==1"><image :src="configurl+items.src" :data-src="configurl+items.src" class="post-image" mode="aspectFill" @tap.stop="previewImage"/></view> |
||||
|
<view v-if="items.type==2"> |
||||
|
<view class='course-video' style="width: 80px;height: 90px;"> |
||||
|
<view class="relative-shadow" @tap.stop="handlePlay(items)"> |
||||
|
<view class="cuIcon-video icon-center f-28 c-white"></view> |
||||
|
<!-- <view class="video-duration f-10 c-white" v-if="item.extends && item.extends.duration">{{$util.videoFormatTime(item.extends.duration)}}</view> --> |
||||
|
</view> |
||||
|
<im-image v-if="configurl+items.privacy" :src="configurl+items.privacy"></im-image> |
||||
|
</view> |
||||
|
|
||||
|
<!-- <image :src="configurl+items.privacy" :data-src="configurl+items.privacy" class="post-image1" mode="aspectFill" @tap.stop="previewImage"/> --> |
||||
|
</view> |
||||
|
</view> |
||||
|
<view style="margin-left: 20rpx;"> |
||||
|
<mp-html container-style="overflow: hidden;display:inline;word-break: break-word;" :content="emojiToHtml(item.content)"/> |
||||
|
</view> |
||||
|
</view> |
||||
|
</view> |
||||
|
</view> |
||||
|
</view> |
||||
|
</scroll-view> |
||||
|
</template> |
||||
|
|
||||
|
<script> |
||||
|
import { useloginStore } from '@/store/login' |
||||
|
import config from '@/common/config.js' |
||||
|
import {chat} from '@/mixins/chat.js' |
||||
|
import imImage from '@/components/message/im-image.vue'; |
||||
|
|
||||
|
const loginStore = useloginStore() |
||||
|
export default { |
||||
|
components: { |
||||
|
imImage |
||||
|
}, |
||||
|
mixins: [chat], |
||||
|
data() { |
||||
|
return { |
||||
|
page: 1, |
||||
|
limit: 20, |
||||
|
list: [], |
||||
|
// groupedList: {}, |
||||
|
collapsedYears: [], |
||||
|
status: 'more', //状态 |
||||
|
refreshing: false, |
||||
|
configurl:config.apiUrl, |
||||
|
urlimg:'', |
||||
|
name:'', |
||||
|
userid:'', |
||||
|
//显示的状态 |
||||
|
contentText: { |
||||
|
contentdown: '查看更多', |
||||
|
contentrefresh: '加载中....', |
||||
|
contentnomore: '没有更多咯' |
||||
|
}, |
||||
|
userInfo:loginStore.userInfo, |
||||
|
} |
||||
|
}, |
||||
|
computed: { |
||||
|
// 计算属性保持不变... |
||||
|
groupedList() { |
||||
|
return this.groupDataByYear(this.list) |
||||
|
} |
||||
|
}, |
||||
|
// watch: { |
||||
|
// list: { |
||||
|
// handler(newVal) { |
||||
|
// this.groupedList = this.groupDataByYear(newVal) |
||||
|
// }, |
||||
|
// immediate: true, |
||||
|
// deep: true |
||||
|
// } |
||||
|
// }, |
||||
|
onLoad(val) { |
||||
|
this.urlimg = val.avatar |
||||
|
this.name = val.realname |
||||
|
this.userid = val.user_id |
||||
|
}, |
||||
|
onShow() { |
||||
|
if(this.userInfo.user_id==this.userid){ |
||||
|
this.init() |
||||
|
}else{ |
||||
|
this.init(this.userid) |
||||
|
} |
||||
|
}, |
||||
|
methods: { |
||||
|
JumpDetails(id){ |
||||
|
uni.navigateTo({ |
||||
|
url:`/pages/compass/friendscircledetails?posts_id=${id}&userid=${this.userid}` |
||||
|
}) |
||||
|
}, |
||||
|
tomoments(){ |
||||
|
uni.navigateTo({ |
||||
|
url:'/pages/compass/sendtoMoments' |
||||
|
}) |
||||
|
}, |
||||
|
formatTime(timeStr) { |
||||
|
const date = new Date(timeStr) |
||||
|
return `${(date.getMonth() + 1).toString().padStart(2, '0')}-${date.getDate().toString().padStart(2, '0')}` |
||||
|
}, |
||||
|
groupDataByYear(data) { |
||||
|
return data.reduce((acc, item) => { |
||||
|
const year = new Date(item.create_time).getFullYear() |
||||
|
if (!acc[year]) acc[year] = [] |
||||
|
acc[year].push(item) |
||||
|
return acc |
||||
|
}, {}) |
||||
|
}, |
||||
|
previewImage(e) { |
||||
|
//预览图片 |
||||
|
var current = e.target.dataset.src |
||||
|
uni.previewImage({ |
||||
|
current: current, |
||||
|
urls: [current] |
||||
|
}) |
||||
|
}, |
||||
|
init(id){ |
||||
|
if (this.page > 1) { |
||||
|
this.page = 1 |
||||
|
} |
||||
|
let params = {page: this.page,limit: this.limit,friend_user_id: id?+id:''} |
||||
|
this.status = 'loading'; // 加载中 |
||||
|
this.$api.compaApi.myPosts(params).then(res => { |
||||
|
res.data.forEach((res) => { |
||||
|
const [latitudeStr, longitudeStr] = res.location.split('+'); |
||||
|
const location = { |
||||
|
latitude: +latitudeStr, |
||||
|
longitude: +longitudeStr |
||||
|
}; |
||||
|
res.location = location |
||||
|
}) |
||||
|
if (this.page == 1) { |
||||
|
this.list.length = 0 |
||||
|
this.list = res.data |
||||
|
} else { |
||||
|
this.list = this.list.concat(res.data); |
||||
|
} |
||||
|
// 判断是否还有更多数据 |
||||
|
if (res.data.length < res.count) { |
||||
|
this.status = 'noMore'; // 没有更多数据 |
||||
|
} else { |
||||
|
this.status = 'more'; // 还有更多数据 |
||||
|
} |
||||
|
}) |
||||
|
}, |
||||
|
onRefresh() { |
||||
|
this.page = 1 |
||||
|
// this.wechatMomentsList() |
||||
|
if (this.refreshing) return; |
||||
|
this.refreshing = true; |
||||
|
setTimeout(() => { |
||||
|
this.refreshing = false; |
||||
|
}, 1000) |
||||
|
}, |
||||
|
scrollbot(){ |
||||
|
if (this.status == 'noMore') { |
||||
|
return; |
||||
|
} |
||||
|
this.page++ |
||||
|
// this.wechatMomentsList() |
||||
|
// console.log('已触发底部事件'); |
||||
|
}, |
||||
|
} |
||||
|
} |
||||
|
</script> |
||||
|
|
||||
|
<style scoped lang="scss"> |
||||
|
.im-friend-header{ |
||||
|
width:100%;height:400rpx;position: relative; |
||||
|
.im-friend-bg{ |
||||
|
width:100%;height:400rpx;overflow: hidden; |
||||
|
.im-friend-image{ |
||||
|
width:100%;height:400rpx; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
.im-user{ |
||||
|
position: absolute; |
||||
|
right:60rpx; |
||||
|
top:210rpx; |
||||
|
overflow: hidden; |
||||
|
} |
||||
|
.icon{ |
||||
|
top: -440rpx; |
||||
|
left: -170rpx; |
||||
|
width: 400rpx; |
||||
|
height: 400rpx; |
||||
|
position: absolute; |
||||
|
border-radius: 10rpx; |
||||
|
background-color: #fff; |
||||
|
box-shadow:0px 0px 5px rgba(0, 0, 0, 0.2); |
||||
|
|
||||
|
.im-emoji-item{ |
||||
|
padding:22rpx; |
||||
|
} |
||||
|
} |
||||
|
.viewtop{ |
||||
|
padding: 0px 30rpx; |
||||
|
|
||||
|
.view_mar{ |
||||
|
margin-top: 100rpx; |
||||
|
|
||||
|
.view_text{ |
||||
|
font-size: 34rpx; |
||||
|
font-weight: bold; |
||||
|
} |
||||
|
.view_icons{ |
||||
|
display: flex; |
||||
|
margin-left: 40rpx; |
||||
|
align-items: center; |
||||
|
justify-content: center; |
||||
|
background-color: #c3c3c3; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
.year-group { |
||||
|
padding: 0px 30rpx; |
||||
|
margin-top: 40rpx; |
||||
|
|
||||
|
.year-title{ |
||||
|
font-size: 38rpx; |
||||
|
} |
||||
|
|
||||
|
.moment-item{ |
||||
|
display: flex; |
||||
|
margin-top: 40rpx; |
||||
|
|
||||
|
.meta-info{ |
||||
|
font-size: 36rpx; |
||||
|
margin-right: 40rpx; |
||||
|
margin-left: 20rpx; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
.content1{ |
||||
|
display: flex; |
||||
|
|
||||
|
.post-image{ |
||||
|
width: 160rpx; |
||||
|
height: 160rpx; |
||||
|
} |
||||
|
.post-image1{ |
||||
|
width: 160rpx; |
||||
|
height: 160rpx; |
||||
|
} |
||||
|
} |
||||
|
.course-video{ |
||||
|
overflow: hidden; |
||||
|
position: relative; |
||||
|
} |
||||
|
.relative-shadow{ |
||||
|
z-index:1; |
||||
|
width:100%; |
||||
|
height:100%; |
||||
|
position: absolute; |
||||
|
background: #8383833d; |
||||
|
} |
||||
|
.icon-center{ |
||||
|
position: absolute; |
||||
|
top: 50%; |
||||
|
z-index: 4; |
||||
|
transform: translate(-50%, -50%); |
||||
|
left: 50%; |
||||
|
padding: 0 4rpx 0 6rpx; |
||||
|
} |
||||
|
</style> |
||||
@ -0,0 +1,445 @@ |
|||||
|
<template> |
||||
|
<cu-custom bgColor="bg-white"> |
||||
|
<template #backText> |
||||
|
<uni-icons type="left" size="22" @click="BackPage"></uni-icons> |
||||
|
</template> |
||||
|
<template #right> |
||||
|
<!-- disabled="true" --> |
||||
|
<view class="right_view"><button class="mini-btn" type="primary" size="mini" style="padding: 0px 10px;" @click="publish">发表</button></view> |
||||
|
</template> |
||||
|
</cu-custom> |
||||
|
<view style="padding: 10px 15px;"> |
||||
|
<view> |
||||
|
<uni-easyinput type="textarea" :inputBorder="false" v-model="params.content" placeholder="这一刻的想法...."></uni-easyinput> |
||||
|
</view> |
||||
|
<view style="height: 600rpx;"> |
||||
|
<sendtoImg @send="params.img_arr = $event" @videourl="video_file1 = $event" :img_arr1="img_arr1" :video_file1="video_file1"></sendtoImg> |
||||
|
</view> |
||||
|
<view> |
||||
|
<view class="icons_view" @click="chooseLocation"> |
||||
|
<view style="display: flex;align-items: center;"> |
||||
|
<uni-icons type="location" size="30"></uni-icons> |
||||
|
<view style="font-size: 18px;margin-left: 10px;">{{positioningList.content?positioningList.content:'所在位置'}}</view> |
||||
|
</view> |
||||
|
<view><uni-icons type="right" size="20"></uni-icons></view> |
||||
|
</view> |
||||
|
<!-- <view style="display: flex;align-items: center;justify-content: space-between;border-top: 1px solid #e5e5e5;padding: 10px 0px;"> |
||||
|
<view style="display: flex;align-items: center;"> |
||||
|
<uni-icons type="person" size="30"></uni-icons> <view style="font-size: 18px;">提醒谁看</view> |
||||
|
</view> |
||||
|
<view><uni-icons type="right" size="18"></uni-icons></view> |
||||
|
</view> --> |
||||
|
<view class="icons_view" style="border-bottom: 2px solid #e5e5e5;" @click="toggle"> |
||||
|
<view style="display: flex;align-items: center;"> |
||||
|
<uni-icons type="person" size="30"></uni-icons> <view style="font-size: 18px;margin-left: 10px;">谁可以看</view> |
||||
|
</view> |
||||
|
<view style="display: flex;align-items: center;"> |
||||
|
<view style="font-size: 18px;color: #a3a3a3;">{{params.privacy==1?'公开':params.privacy==2?'部分可见':params.privacy==3?'私密':'不给谁看'}}</view> |
||||
|
<uni-icons type="right" size="20"></uni-icons> |
||||
|
</view> |
||||
|
</view> |
||||
|
</view> |
||||
|
</view> |
||||
|
|
||||
|
<uni-popup ref="popup" background-color="#fff" style="z-index: 999999;"> |
||||
|
<view class="popup-content"> |
||||
|
<cu-custom bgColor="bg-white"> |
||||
|
<template #backText> |
||||
|
<uni-icons type="left" size="22" @click="BackPage1"></uni-icons> |
||||
|
</template> |
||||
|
<template #content> |
||||
|
<view>谁可以看</view> |
||||
|
</template> |
||||
|
<template #right> |
||||
|
<!-- disabled="true" --> |
||||
|
<view class="right_view"><button class="mini-btn" type="primary" size="mini" style="padding: 0px 10px;" @click="complete">完成</button></view> |
||||
|
</template> |
||||
|
</cu-custom> |
||||
|
<view> |
||||
|
<view style="padding: 30rpx 60rpx;"> |
||||
|
<radio-group @change="radioChange"> |
||||
|
<label> |
||||
|
<view style="display: flex;align-items: center;margin-bottom: 30px;"> |
||||
|
<radio value="1" :checked="params.privacy==1" style="transform:scale(0.7)"/> |
||||
|
<view> |
||||
|
<view style="font-size: 30rpx;">公开</view> |
||||
|
<view style="font-size: 22rpx;color: #a3a3a3;">所有朋友可见</view> |
||||
|
</view> |
||||
|
</view> |
||||
|
</label> |
||||
|
<label> |
||||
|
<view style="display: flex;align-items: center;margin-bottom: 30px;"> |
||||
|
<radio value="3" :checked="params.privacy==3" style="transform:scale(0.7)" /> |
||||
|
<view> |
||||
|
<view style="font-size: 30rpx;">私密</view> |
||||
|
<view style="font-size: 22rpx;color: #a3a3a3;">仅自己可见</view> |
||||
|
</view> |
||||
|
</view> |
||||
|
</label> |
||||
|
<label> |
||||
|
<view style="margin-bottom: 30px;"> |
||||
|
<view style="display: flex;align-items: center;"> |
||||
|
<radio value="2" :checked="params.privacy==2" style="transform:scale(0.7)" /> |
||||
|
<view> |
||||
|
<view style="font-size: 30rpx;">部分可见</view> |
||||
|
</view> |
||||
|
</view> |
||||
|
<view style="padding: 40rpx 60rpx;" v-if="params.privacy==2"> |
||||
|
<view @click="editUser"> |
||||
|
<view style="color: #004f9f;font-size: 30rpx;">选择朋友</view> |
||||
|
<block v-for="(item,index) in user1" :key="index" v-if="user1.length!==0" style="display: flex;"> |
||||
|
<text style="font-size: 22rpx;margin-right: 5px;">{{item.displayName?item.displayName:item.nickname}}</text> |
||||
|
</block> |
||||
|
<view v-else style="font-size: 22rpx;">选中的朋友可见</view> |
||||
|
</view> |
||||
|
</view> |
||||
|
</view> |
||||
|
</label> |
||||
|
<label> |
||||
|
<view> |
||||
|
<view style="display: flex;align-items: center;"> |
||||
|
<radio value="4" :checked="params.privacy==4" style="transform:scale(0.7)" /> |
||||
|
<view> |
||||
|
<view style="font-size: 30rpx;">不给谁看</view> |
||||
|
</view> |
||||
|
</view> |
||||
|
<view style="padding: 40rpx 60rpx;" v-if="params.privacy==4"> |
||||
|
<view @click="editUser1"> |
||||
|
<view style="color: #004f9f;font-size: 30rpx;">选择朋友</view> |
||||
|
<block v-for="(item,index) in user2" :key="index" v-if="user2.length!==0" style="display: flex;"> |
||||
|
<text style="font-size: 22rpx;margin-right: 5px;">{{item.displayName?item.displayName:item.nickname}}</text> |
||||
|
</block> |
||||
|
<view v-else style="font-size: 22rpx;">选中的朋友不可见</view> |
||||
|
</view> |
||||
|
</view> |
||||
|
</view> |
||||
|
</label> |
||||
|
</radio-group> |
||||
|
</view> |
||||
|
</view> |
||||
|
</view> |
||||
|
</uni-popup> |
||||
|
</template> |
||||
|
|
||||
|
<script> |
||||
|
import sendtoImg from '@/components/sendtoImg.vue' |
||||
|
export default { |
||||
|
components: { |
||||
|
sendtoImg |
||||
|
}, |
||||
|
data() { |
||||
|
return { |
||||
|
params:{ |
||||
|
content:"", |
||||
|
privacy:1, |
||||
|
location:'', |
||||
|
status:1, |
||||
|
img_arr:[], |
||||
|
file_id:"", |
||||
|
user_ids:[], |
||||
|
posts_id:0, |
||||
|
video_file:"" |
||||
|
}, |
||||
|
video_file1:{}, |
||||
|
video_file2:{}, |
||||
|
positioningList:{}, |
||||
|
user1:[], |
||||
|
user2:[], |
||||
|
img_arr1:[], |
||||
|
bols:false |
||||
|
} |
||||
|
}, |
||||
|
created() { |
||||
|
// console.log(this.params.privacy); |
||||
|
this.getLastPosts() |
||||
|
}, |
||||
|
onShow() { |
||||
|
if(uni.getStorageSync('selectUser')){ |
||||
|
this.user1 = uni.getStorageSync('selectUser') |
||||
|
} |
||||
|
if(uni.getStorageSync('selectUser1')){ |
||||
|
this.user2 = uni.getStorageSync('selectUser1') |
||||
|
} |
||||
|
}, |
||||
|
methods: { |
||||
|
radioChange(e){ |
||||
|
this.params.privacy = +e.detail.value |
||||
|
// console.log(this.params.privacy); |
||||
|
}, |
||||
|
toggle() { |
||||
|
this.$refs.popup.open('bottom') |
||||
|
}, |
||||
|
complete(){ |
||||
|
this.$refs.popup.close() |
||||
|
if(this.params.privacy==2){ |
||||
|
this.user2 = []; |
||||
|
uni.removeStorageSync('selectUser'); |
||||
|
}else if(this.params.privacy==4){ |
||||
|
this.user1 = []; |
||||
|
uni.removeStorageSync('selectUser1'); |
||||
|
}else{ |
||||
|
this.user1 = []; |
||||
|
this.user2 = []; |
||||
|
uni.removeStorageSync('selectUser'); |
||||
|
uni.removeStorageSync('selectUser1'); |
||||
|
} |
||||
|
}, |
||||
|
BackPage1(){ |
||||
|
if(this.bols==false){ |
||||
|
this.params.privacy = 1; |
||||
|
this.user1 = []; |
||||
|
this.user2 = []; |
||||
|
uni.removeStorageSync('selectUser'); |
||||
|
uni.removeStorageSync('selectUser1'); |
||||
|
} |
||||
|
this.$refs.popup.close(); |
||||
|
}, |
||||
|
editUser(){ |
||||
|
if(uni.getStorageSync('selectUser')){ |
||||
|
this.user1 = uni.getStorageSync('selectUser') |
||||
|
const list = [] |
||||
|
this.user1.forEach((res)=>{ |
||||
|
list.push(res.user_id) |
||||
|
}) |
||||
|
uni.navigateTo({ |
||||
|
url: `/pages/index/userSelection?type=${5}&user_ids=${JSON.stringify(list)}` |
||||
|
}) |
||||
|
}else{ |
||||
|
uni.navigateTo({ |
||||
|
url: `/pages/index/userSelection?type=${5}` |
||||
|
// url: '/pages/index/userSelection?type='+type+'&contact_id=' + this.contact.id |
||||
|
}) |
||||
|
} |
||||
|
}, |
||||
|
editUser1(){ |
||||
|
if(uni.getStorageSync('selectUser1')){ |
||||
|
this.user2 = uni.getStorageSync('selectUser1') |
||||
|
const list1 = [] |
||||
|
this.user2.forEach((res)=>{ |
||||
|
list1.push(res.user_id) |
||||
|
}) |
||||
|
uni.navigateTo({ |
||||
|
url: `/pages/index/userSelection?type=${6}&user_ids=${JSON.stringify(list1)}` |
||||
|
}) |
||||
|
}else{ |
||||
|
uni.navigateTo({ |
||||
|
url: `/pages/index/userSelection?type=${6}` |
||||
|
// url: '/pages/index/userSelection?type='+type+'&contact_id=' + this.contact.id |
||||
|
}) |
||||
|
} |
||||
|
}, |
||||
|
transformToArrayObject(arr) { |
||||
|
// 创建空对象 |
||||
|
const resultObj = {}; |
||||
|
|
||||
|
// 遍历数组,用索引作为键 |
||||
|
arr.forEach((value, index) => { |
||||
|
resultObj[index] = value; |
||||
|
}); |
||||
|
|
||||
|
// 将对象包裹在数组中 |
||||
|
return [resultObj]; |
||||
|
}, |
||||
|
BackPage(){ |
||||
|
const _this = this |
||||
|
console.log(this.video_file1.url); |
||||
|
if(this.params.content!==''||this.params.img_arr.length!=0||this.video_file1.url){ |
||||
|
uni.showModal({ |
||||
|
content: '保留此次编辑', |
||||
|
cancelText: '不保留', |
||||
|
confirmText: '保留', |
||||
|
success: function (res) { |
||||
|
if (res.confirm) { |
||||
|
console.log('用户点击确定'); |
||||
|
_this.params.status = 2; |
||||
|
_this.publish() |
||||
|
} else if (res.cancel) { |
||||
|
_this.user1 = []; |
||||
|
_this.user2 = []; |
||||
|
uni.removeStorageSync('selectUser'); |
||||
|
uni.removeStorageSync('selectUser1'); |
||||
|
if(_this.params.posts_id){ |
||||
|
_this.Delete(_this.params.posts_id) |
||||
|
}else{ |
||||
|
uni.navigateBack({ |
||||
|
delta: 1 |
||||
|
}); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
}); |
||||
|
}else{ |
||||
|
uni.showModal({ |
||||
|
content: '退出此次编辑?', |
||||
|
cancelText: '取消', |
||||
|
confirmText: '退出', |
||||
|
confirmColor: "red", |
||||
|
success: function (res) { |
||||
|
if (res.confirm) { |
||||
|
_this.user1 = []; |
||||
|
_this.user2 = []; |
||||
|
uni.removeStorageSync('selectUser'); |
||||
|
uni.removeStorageSync('selectUser1'); |
||||
|
uni.navigateBack({ |
||||
|
delta: 1 |
||||
|
}); |
||||
|
} else if (res.cancel) { |
||||
|
console.log('用户点击取消'); |
||||
|
} |
||||
|
} |
||||
|
}); |
||||
|
} |
||||
|
}, |
||||
|
Delete(id){ |
||||
|
console.log(id); |
||||
|
this.$api.compaApi.Deleteapost({posts_id:id}).then(res => { |
||||
|
uni.navigateBack({ |
||||
|
delta: 1 |
||||
|
}); |
||||
|
}) |
||||
|
}, |
||||
|
chooseLocation(){ |
||||
|
uni.chooseLocation({ |
||||
|
success: (res) => { |
||||
|
// console.log('位置名称:' + res.name); |
||||
|
// console.log('详细地址:' + res.address); |
||||
|
// console.log('纬度:' + res.latitude); |
||||
|
// console.log('经度:' + res.longitude); |
||||
|
let message={ |
||||
|
type:'location', |
||||
|
status:'going', |
||||
|
content:'[位置] '+res.name, |
||||
|
name:res.name, |
||||
|
latitud: res.latitude+ '+' +res.longitude, |
||||
|
extends:res |
||||
|
}; |
||||
|
this.positioningList = message |
||||
|
// this.$emit('send',Object.assign(this.msgItem(), message),false); |
||||
|
} |
||||
|
}) |
||||
|
}, |
||||
|
getLastPosts(){ |
||||
|
this.$api.compaApi.getLastPosts().then(res => { |
||||
|
if(res.code==0&&res.data!==null){ |
||||
|
console.log(res); |
||||
|
this.params.content = res.data.content |
||||
|
// this.params.img_arr = res.data.files |
||||
|
this.positioningList.latitud = res.data.location |
||||
|
this.positioningList.content = res.data.location_address |
||||
|
this.params.posts_id = res.data.posts_id |
||||
|
this.params.privacy = res.data.privacy |
||||
|
if(res.data.files[0].type==1){ |
||||
|
this.img_arr1 = res.data.files |
||||
|
}else{ |
||||
|
this.img_arr1 = res.data.files |
||||
|
res.data.files.forEach((res1)=>{ |
||||
|
this.video_file1 = {url:res1.src,poster:res1.privacy,type:res1.type} |
||||
|
}) |
||||
|
} |
||||
|
this.bols = true |
||||
|
if(res.data.privacy==2){ |
||||
|
this.user1 = res.data.privacy_user |
||||
|
uni.setStorageSync('selectUser', this.user1); |
||||
|
}else if(res.data.privacy==4){ |
||||
|
this.user2 = res.data.privacy_user |
||||
|
uni.setStorageSync('selectUser1', this.user2); |
||||
|
} |
||||
|
// console.log(res); |
||||
|
// this.list = res.data |
||||
|
} |
||||
|
}) |
||||
|
}, |
||||
|
publish(){ |
||||
|
let list1 = [] |
||||
|
let list2 = [] |
||||
|
let listUrl = [] |
||||
|
|
||||
|
if(this.params.privacy == 2){ |
||||
|
this.user1.forEach((res)=>{ |
||||
|
list1.push(res.user_id) |
||||
|
}) |
||||
|
} else if(this.params.privacy == 4){ |
||||
|
this.user2.forEach((res)=>{ |
||||
|
list2.push(res.user_id) |
||||
|
}) |
||||
|
} |
||||
|
|
||||
|
if(this.img_arr1&&this.img_arr1[0]?.type==1){ |
||||
|
this.img_arr1.forEach(res=>{ |
||||
|
listUrl.push(res.src) |
||||
|
}) |
||||
|
} |
||||
|
if(this.params.content==''&&this.params.img_arr.length==0&&this.video_file1.url==undefined){ |
||||
|
uni.showToast({title: '文字或图片、视频必须上传一个,并且文字不能是空格',icon:'none'}); |
||||
|
return |
||||
|
} |
||||
|
|
||||
|
const data = { |
||||
|
privacy: this.params.privacy, |
||||
|
content: this.params.content||'', |
||||
|
status: this.params.status, |
||||
|
location: this.positioningList.latitud || '', |
||||
|
address: this.positioningList.content || '', |
||||
|
img_arr: this.params.img_arr.length==0?listUrl:this.params.img_arr||'', |
||||
|
video_file: this.video_file1.url || '', |
||||
|
poster_file: this.video_file1.poster || '', |
||||
|
user_ids: this.params.privacy == 2 ? list1 : this.params.privacy == 4 ? list2 : [], |
||||
|
posts_id: this.params.posts_id||'' |
||||
|
} |
||||
|
// console.log('传值',data); |
||||
|
this.$api.compaApi.wechatMomentsadd(data).then(res => { |
||||
|
// this.list = res.data |
||||
|
// console.log(res); |
||||
|
if(res.code==0){ |
||||
|
list1 = [] |
||||
|
list2 = [] |
||||
|
if(this.params.status==2){ |
||||
|
uni.showToast({title: '保存成功'}); |
||||
|
}else{ |
||||
|
uni.showToast({title: '添加成功'}); |
||||
|
} |
||||
|
uni.navigateBack({ |
||||
|
delta: 1 |
||||
|
}); |
||||
|
} |
||||
|
}) |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
</script> |
||||
|
|
||||
|
<style scoped lang="scss"> |
||||
|
.right_view{ |
||||
|
height: 30px; |
||||
|
margin-right: 10px; |
||||
|
} |
||||
|
uni-button[disabled][type='primary']{ |
||||
|
background-color:#e7e7e7; |
||||
|
color: #c6c6c6; |
||||
|
} |
||||
|
uni-button:after{ |
||||
|
border: 0px; |
||||
|
} |
||||
|
::v-deep .bg-white{ |
||||
|
background-color: #f1f1f1 !important; |
||||
|
} |
||||
|
::v-deep .uni-easyinput__content{ |
||||
|
background-color: #f1f1f1 !important; |
||||
|
} |
||||
|
.icons_view{ |
||||
|
display: flex; |
||||
|
padding: 10px 10px; |
||||
|
align-items: center; |
||||
|
border-top: 2px solid #e5e5e5; |
||||
|
justify-content: space-between; |
||||
|
} |
||||
|
::v-deep .uni-modal__bd{ |
||||
|
color: #000; |
||||
|
} |
||||
|
.popup-content { |
||||
|
height: 100vh; |
||||
|
align-items: center; |
||||
|
background-color: #fff; |
||||
|
justify-content: center; |
||||
|
} |
||||
|
</style> |
||||
@ -0,0 +1,101 @@ |
|||||
|
<template> |
||||
|
<view> |
||||
|
<cu-custom bgColor="bg-main-bar" :isBack="true"> |
||||
|
<template #backText></template> |
||||
|
<template #content>黑名单列表</template> |
||||
|
<!-- <template #right> |
||||
|
<view class="f-20 ml-10 mr-10" @tap="search()"> |
||||
|
<text class="cuIcon-search"></text> |
||||
|
</view> |
||||
|
</template> --> |
||||
|
</cu-custom> |
||||
|
<view class="cu-list menu-avatar no-padding"> |
||||
|
<view class="flex List-item" v-for="(items,sub) in groupList" :key="sub"> |
||||
|
<view class="flex align-center" style="padding: 10px;"> |
||||
|
<view class='cu-avatar lg radius mr-10' :style="[{backgroundImage:'url('+items.avatar+')'}]"> |
||||
|
</view> |
||||
|
<view> |
||||
|
<view class="c-333">{{items.displayName}}</view> |
||||
|
</view> |
||||
|
</view> |
||||
|
|
||||
|
<view class="flex"> |
||||
|
<view class="fc-danger mr-10" @tap="setis_blacklist(items)">移除黑名单</view> |
||||
|
<view class="action fc-primary mr-10" @tap='openDetails(items)'> |
||||
|
<view>发送信息</view> |
||||
|
</view> |
||||
|
</view> |
||||
|
</view> |
||||
|
<Empty v-if="!groupList.length" noDatatext="暂无数据" textcolor="#999" ></Empty> |
||||
|
</view> |
||||
|
|
||||
|
</view> |
||||
|
</template> |
||||
|
|
||||
|
<script> |
||||
|
import { storeToRefs } from 'pinia'; |
||||
|
import { useMsgStore } from '@/store/message'; |
||||
|
import { useloginStore } from '@/store/login' |
||||
|
import pinia from '@/store/index' |
||||
|
const userStore = useloginStore(pinia); |
||||
|
const msgStore = useMsgStore(pinia) |
||||
|
const {contacts} = storeToRefs(msgStore); |
||||
|
/** |
||||
|
* 初始的引导页 |
||||
|
*/ |
||||
|
export default { |
||||
|
name : "group", |
||||
|
data() { |
||||
|
return { |
||||
|
groupList:[], |
||||
|
userInfo:userStore.userInfo |
||||
|
}; |
||||
|
}, |
||||
|
created() { |
||||
|
}, |
||||
|
mounted(){ |
||||
|
this.initContacts(); |
||||
|
}, |
||||
|
methods: { |
||||
|
initContacts(arr){ |
||||
|
this.$api.msgApi.initContacts().then(res => { |
||||
|
this.groupList = res.data.filter(item=>item.is_blacklist==1) |
||||
|
}) |
||||
|
}, |
||||
|
setis_blacklist(items){ |
||||
|
this.$api.msgApi.isBlacklist({friend_user_id:items.user_id}).then(res => { |
||||
|
if (res.code == 0) { |
||||
|
uni.showToast({ |
||||
|
title: '移除成功!' |
||||
|
}) |
||||
|
this.initContacts(); |
||||
|
} |
||||
|
}) |
||||
|
}, |
||||
|
// 打开聊天 |
||||
|
openDetails(items){ |
||||
|
uni.navigateTo({ |
||||
|
url:"/pages/message/chat?id="+items.id |
||||
|
}) |
||||
|
}, |
||||
|
search(){ |
||||
|
uni.navigateTo({ |
||||
|
url:"/pages/index/search?type=4" |
||||
|
}) |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
</script> |
||||
|
|
||||
|
<style scoped> |
||||
|
.fc-primary{ |
||||
|
color: #409eff; |
||||
|
} |
||||
|
.fc-danger{ |
||||
|
color: #f56c6c; |
||||
|
} |
||||
|
.List-item{ |
||||
|
justify-content: space-between; |
||||
|
align-items: center; |
||||
|
} |
||||
|
</style> |
||||
@ -0,0 +1,327 @@ |
|||||
|
<template> |
||||
|
<view> |
||||
|
<cu-custom bgColor="bg-white" :isBack="true"> |
||||
|
<template #backText></template> |
||||
|
<template #content>个人信息</template> |
||||
|
</cu-custom> |
||||
|
<view class="padding flex justify-start align-center"> |
||||
|
<block v-for="(items,indexs) in imglist" :key="indexs" v-if="detail.imgname&&network_log == 'none'"> |
||||
|
<view class='cu-avatar lg radius mr-15' v-if="detail.imgname === items.name" @tap="showAvatar(detail)" :style="'background-image:url('+items.path+')'"></view> |
||||
|
</block> |
||||
|
<view v-else class='cu-avatar lg radius mr-15' @tap="showAvatar(detail)" :style="'background-image:url('+detail.avatar+')'"></view> |
||||
|
|
||||
|
<view class='im-flex im-justify-content-start im-columns'> |
||||
|
<view class="mb-5">{{detail.friend?.nickname?detail.friend.nickname:detail.realname}}</view> |
||||
|
<view class="text-gray">{{detail.account}}</view> |
||||
|
</view> |
||||
|
</view> |
||||
|
<view class="cu-list menu"> |
||||
|
<view class="cu-item" v-if="globalConfig.sysInfo.runMode==2 && detail.friend && userInfo.user_id!=detail.user_id" @tap="setNickname"> |
||||
|
<view class="content"> |
||||
|
<text class="cuIcon-edit text-green"></text> |
||||
|
<text>备注</text> |
||||
|
</view> |
||||
|
<view class="action"> |
||||
|
<text class="text-grey text-sm">{{detail.friend.nickname || '未设置'}}</text> |
||||
|
<text class="text-grey text-sm ml-5 cuIcon-write"></text> |
||||
|
</view> |
||||
|
</view> |
||||
|
<view class="cu-item"> |
||||
|
<view class="content"> |
||||
|
<text class="cuIcon-mail text-green"></text> |
||||
|
<text>昵称</text> |
||||
|
</view> |
||||
|
<view class="action"> |
||||
|
<text class="text-grey text-sm">{{detail.realname}}</text> |
||||
|
</view> |
||||
|
</view> |
||||
|
<view class="cu-item"> |
||||
|
<view class="content"> |
||||
|
<text class="cuIcon-mail text-green"></text> |
||||
|
<text>邮箱</text> |
||||
|
</view> |
||||
|
<view class="action"> |
||||
|
<text class="text-grey text-sm">{{detail.email ?? ''}}</text> |
||||
|
</view> |
||||
|
</view> |
||||
|
<view class="cu-item"> |
||||
|
<view class="content"> |
||||
|
<text class="cuIcon-safe text-green"></text> |
||||
|
<text>性别</text> |
||||
|
</view> |
||||
|
<view class="action"> |
||||
|
<text class="text-grey text-sm">{{ sex(detail.sex)}}</text> |
||||
|
</view> |
||||
|
</view> |
||||
|
<view class="cu-item" v-if="parseInt(globalConfig.sysInfo.ipregion)"> |
||||
|
<view class="content"> |
||||
|
<text class="cuIcon-location text-green"></text> |
||||
|
<text>IP</text> |
||||
|
</view> |
||||
|
<view class="action"> |
||||
|
<text class="text-grey text-sm" v-if="detail.last_login_ip">{{ detail.last_login_ip || "未知"}} ({{detail.location || "未知"}})</text> |
||||
|
<text class="text-grey text-sm" v-else>未知</text> |
||||
|
</view> |
||||
|
</view> |
||||
|
<view class="cu-item" @click="toFriendsCircle(detail)"> |
||||
|
<view class="content flex"> |
||||
|
<view class="flex justify-center" style="width: 1.6em;margin-right: 0.3125rem;"><uni-icons type="pyq" size="15" color="#45ba5b"></uni-icons></view> |
||||
|
<view>朋友圈</view> |
||||
|
</view> |
||||
|
<view class="action"> |
||||
|
<text class="text-grey cuIcon-right"></text> |
||||
|
</view> |
||||
|
</view> |
||||
|
</view> |
||||
|
|
||||
|
<template class="" v-if="userInfo.user_id!=detail.user_id"> |
||||
|
<view class="padding flex flex-direction" v-if="globalConfig.sysInfo.runMode==1 || detail.friend"> |
||||
|
<button class="cu-btn bg-green mt-10 lg" @tap="sendMsg(detail)">发消息</button> |
||||
|
<button class="cu-btn bg-blue mt-10 lg" v-if="validatePhone" @tap="callPhone()">打电话</button> |
||||
|
<!-- #ifdef APP | H5 --> |
||||
|
<button class="cu-btn bg-grey mt-10 lg" @tap="modelName='callRtc'" v-if="parseInt(globalConfig.chatInfo.webrtc) && parseInt(globalConfig.chatInfo.simpleChat)">音视频通话</button> |
||||
|
<!-- #endif --> |
||||
|
|
||||
|
<button class="cu-btn bg-red mt-10 lg" @tap="delFriend()" v-if="globalConfig.sysInfo.runMode==2">删除好友</button> |
||||
|
</view> |
||||
|
<view class="padding flex flex-direction" v-if="globalConfig.sysInfo.runMode==2 && !detail.friend"> |
||||
|
<button class="cu-btn bg-green lg" @tap="addFriend()">加好友</button> |
||||
|
</view> |
||||
|
</template> |
||||
|
<view class="cu-modal bottom-modal" :class="modelName=='callRtc'?'show':''" @tap="modelName=''"> |
||||
|
<view class="cu-dialog"> |
||||
|
<view class="manage-content"> |
||||
|
<view class="cu-list menu bg-white"> |
||||
|
<view class="cu-item" @tap="calling(0)"> |
||||
|
<view class="content padding-tb-sm"> |
||||
|
<text class="cuIcon-dianhua"></text> |
||||
|
<text>语音通话</text> |
||||
|
</view> |
||||
|
</view> |
||||
|
<view class="cu-item" @tap="calling(1)"> |
||||
|
<view class="content padding-tb-sm"> |
||||
|
<text class=" cuIcon-record"></text> |
||||
|
<text>视频通话</text> |
||||
|
</view> |
||||
|
</view> |
||||
|
<view class="parting-line-5"></view> |
||||
|
<view class="cu-item" @tap="modelName=''"> |
||||
|
<view class="content padding-tb-sm"> |
||||
|
<text class="c-red">取消</text> |
||||
|
</view> |
||||
|
</view> |
||||
|
|
||||
|
</view> |
||||
|
</view> |
||||
|
</view> |
||||
|
</view> |
||||
|
</view> |
||||
|
</template> |
||||
|
|
||||
|
<script> |
||||
|
import { useMsgStore } from '@/store/message'; |
||||
|
import { useloginStore } from '@/store/login'; |
||||
|
// #ifdef APP-PLUS |
||||
|
import {getSavedImages} from '@/utils/LocalFileSystemURL.js' |
||||
|
// #endif |
||||
|
import getUserInfo from '@/service/getUserInfo'; |
||||
|
import pinia from '@/store/index' |
||||
|
import { storeToRefs } from 'pinia'; |
||||
|
const msgStore = useMsgStore(pinia) |
||||
|
const userStore = useloginStore(pinia) |
||||
|
const {network_log} = storeToRefs(msgStore); |
||||
|
export default { |
||||
|
data() { |
||||
|
return { |
||||
|
modelName:'', |
||||
|
detail:{}, |
||||
|
userInfo:userStore.userInfo, |
||||
|
globalConfig:userStore.globalConfig, |
||||
|
network_log:'', |
||||
|
imglist:[], |
||||
|
ids:0 |
||||
|
} |
||||
|
}, |
||||
|
computed: { |
||||
|
validatePhone(){ |
||||
|
let reg = /^1[3456789]\d{9}$/; |
||||
|
return reg.test(this.detail.account); |
||||
|
} |
||||
|
}, |
||||
|
onLoad(options) { |
||||
|
const _this = this |
||||
|
this.network_log = uni.getStorageSync('network_log') |
||||
|
if(_this.network_log == 'none'){ |
||||
|
this.getImagePath() |
||||
|
this.getUserInfoList(options.id) |
||||
|
}else{ |
||||
|
this.ids = options.id |
||||
|
this.getInfo() |
||||
|
// this.getImagePath() |
||||
|
// this.getUserInfoList(options.id) |
||||
|
} |
||||
|
}, |
||||
|
methods: { |
||||
|
getInfo(){ |
||||
|
this.$api.msgApi.getUserInfo({user_id:this.ids}).then((res)=>{ |
||||
|
if(res.code==0){ |
||||
|
this.detail=res.data; |
||||
|
const list = [] |
||||
|
list.push(res.data) |
||||
|
this.insertUserInfo(list) |
||||
|
} |
||||
|
}) |
||||
|
}, |
||||
|
async insertUserInfo(val){ |
||||
|
await getUserInfo.batchInsertOrUpdate(val) |
||||
|
}, |
||||
|
async getUserInfoList(user_id){ |
||||
|
try { |
||||
|
const list = await getUserInfo.getList({user_id:user_id}); |
||||
|
list.forEach((item)=>{ |
||||
|
const parts = item.avatar.split('/'); |
||||
|
let lastPart = parts.pop() || parts.pop() || ''; |
||||
|
const isNumber = !isNaN(lastPart)&&!isNaN(parseFloat(lastPart)); |
||||
|
item.imgname = isNumber ? lastPart+'.png' : lastPart; |
||||
|
item.friend = JSON.parse(item.friend); |
||||
|
item.setting = JSON.parse(item.setting); |
||||
|
}) |
||||
|
this.detail = list[0]; |
||||
|
console.log('获取个人信息数据',list.length,list); |
||||
|
} catch (error) { |
||||
|
console.error(error); |
||||
|
} |
||||
|
}, |
||||
|
async getImagePath(){ |
||||
|
this.imglist = await getSavedImages() |
||||
|
this.imglist.map(item => { |
||||
|
item.path = plus.io.convertLocalFileSystemURL(item.path) |
||||
|
}); |
||||
|
// console.info(this.imglist,'读取地址'); |
||||
|
}, |
||||
|
showAvatar(detail){ |
||||
|
let imgs=[]; |
||||
|
imgs.push(detail.avatar); |
||||
|
uni.previewImage({urls : imgs}) |
||||
|
}, |
||||
|
sendMsg(info){ |
||||
|
uni.reLaunch({ |
||||
|
url:"/pages/message/chat?id="+info.user_id |
||||
|
}) |
||||
|
}, |
||||
|
toFriendsCircle(info){ |
||||
|
uni.navigateTo({ |
||||
|
url:"/pages/compass/personalcircleoffriends?user_id="+info.user_id+"&avatar="+info.avatar+"&realname="+info.realname |
||||
|
}) |
||||
|
}, |
||||
|
sex(value) { |
||||
|
let arr = ['女', '男','未知'] |
||||
|
return arr[value] || '未知'; |
||||
|
}, |
||||
|
callPhone(){ |
||||
|
uni.makePhoneCall({ |
||||
|
phoneNumber: this.detail.account |
||||
|
}); |
||||
|
}, |
||||
|
calling(is_video){ |
||||
|
if(msgStore.webrtcLock){ |
||||
|
return uni.showToast({ |
||||
|
title:'其他终端正在通话中', |
||||
|
icon:'none' |
||||
|
}) |
||||
|
} |
||||
|
this.modelName=''; |
||||
|
let msg_id=this.$util.getUuid(); |
||||
|
uni.navigateTo({ |
||||
|
url: '/pages/message/call?msg_id='+msg_id+'&type='+is_video+'&status=1&id='+this.detail.user_id+'&name='+this.detail.realname+'&avatar='+encodeURI(this.detail.avatar) |
||||
|
}) |
||||
|
}, |
||||
|
delFriend(){ |
||||
|
uni.showModal({ |
||||
|
title: '确定要删除该好友吗?', |
||||
|
success: (res)=>{ |
||||
|
if (res.confirm) { |
||||
|
let data={ id: this.detail.user_id}; |
||||
|
this.$api.friendApi.delFriend(data).then((res)=>{ |
||||
|
if(res.code==0){ |
||||
|
msgStore.deleteContacts(data); |
||||
|
// console.log(data,'123456'); |
||||
|
uni.reLaunch({ |
||||
|
url: '/pages/index/index' |
||||
|
}) |
||||
|
} |
||||
|
}) |
||||
|
} |
||||
|
}, |
||||
|
}) |
||||
|
|
||||
|
}, |
||||
|
addFriend(){ |
||||
|
uni.showModal({ |
||||
|
title: '请输入验证信息', |
||||
|
editable:true, |
||||
|
success: (res)=>{ |
||||
|
if (res.confirm) { |
||||
|
if(res.content==''){ |
||||
|
return uni.showToast({ |
||||
|
title:'请输入备注!', |
||||
|
icon:'error' |
||||
|
}) |
||||
|
} |
||||
|
this.$api.friendApi.addFriend({user_id:this.detail.user_id,remark:res.content}).then((e)=>{ |
||||
|
if(e.code==0){ |
||||
|
uni.showToast({ |
||||
|
title:e.msg, |
||||
|
icon:'none' |
||||
|
}) |
||||
|
this.getInfo() |
||||
|
} |
||||
|
}) |
||||
|
} |
||||
|
} |
||||
|
}); |
||||
|
}, |
||||
|
setNickname(){ |
||||
|
let friend_id=this.detail.friend.friend_id ?? ''; |
||||
|
if(!this.detail.friend){ |
||||
|
return uni.showToast({ |
||||
|
title:'无法设置', |
||||
|
icon:'error' |
||||
|
}) |
||||
|
} |
||||
|
uni.showModal({ |
||||
|
title: '请输入备注信息', |
||||
|
editable:true, |
||||
|
success: (res)=>{ |
||||
|
if (res.confirm) { |
||||
|
// if(res.content==''){ |
||||
|
// return uni.showToast({ |
||||
|
// title:'请输入好友备注!', |
||||
|
// icon:'error' |
||||
|
// }) |
||||
|
// } |
||||
|
this.$api.friendApi.setNickname({friend_id:friend_id,nickname:res.content}).then((e)=>{ |
||||
|
if(e.code==0){ |
||||
|
this.detail.friend.nickname=res.content; |
||||
|
// 修改备注后修改联系人 |
||||
|
msgStore.updateContacts({ |
||||
|
id:this.detaild.user_id, |
||||
|
displayName:res.content |
||||
|
}) |
||||
|
uni.showToast({ |
||||
|
title:e.msg, |
||||
|
icon:'none' |
||||
|
}) |
||||
|
} |
||||
|
}) |
||||
|
} |
||||
|
} |
||||
|
}); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
</script> |
||||
|
|
||||
|
<style lang="scss" scoped> |
||||
|
|
||||
|
</style> |
||||
@ -0,0 +1,150 @@ |
|||||
|
<template> |
||||
|
<view> |
||||
|
<cu-custom bgColor="bg-gradual-blue" :isBack="true"> |
||||
|
<template #backText></template> |
||||
|
<template #content>新邀请</template> |
||||
|
<template #right> |
||||
|
<view class="f-20 ml-10 mr-10" @tap="searchFriend()"> |
||||
|
<text class="cuIcon-add"></text> |
||||
|
</view> |
||||
|
</template> |
||||
|
</cu-custom> |
||||
|
<uni-segmented-control :current="current" :values="items" @clickItem="onClickItem" styleType="text"></uni-segmented-control> |
||||
|
<view class="cu-list menu"> |
||||
|
<view class="cu-item" v-if="!params.is_mine" v-for="(x,index) in list" :key="index"> |
||||
|
<view class='cu-avatar md radius mr-15' :style="[{backgroundImage:'url('+x.create_user_info.avatar+')'}]"> |
||||
|
</view> |
||||
|
<view class="content padding-tb-sm" @tap='openDetails(x.create_user_info.user_id)'> |
||||
|
<view class="text-grey" v-if="!params.is_mine"> |
||||
|
<text class="text-blue">{{x.create_user_info.realname}} </text> 申请添加您为好友 |
||||
|
</view> |
||||
|
<view class="text-gray text-sm lh-15x">{{x.remark}}</view> |
||||
|
</view> |
||||
|
<view class="action ml-10"> |
||||
|
<text class="text-red" v-if="x.status==0">已拒绝</text> |
||||
|
<text class="text-blue" v-if="x.status==1" @tap="sendMsg(x.create_user_info.user_id)">发消息</text> |
||||
|
<button class="cu-btn round sm bg-green" v-if="x.status==2" @tap="optApply(x)"> |
||||
|
操作 |
||||
|
</button> |
||||
|
</view> |
||||
|
</view> |
||||
|
<view class="cu-item" v-if="params.is_mine" v-for="(x,index) in list" :key="index"> |
||||
|
<view class='cu-avatar md radius mr-15' :style="[{backgroundImage:'url('+x.user_id_info.avatar+')'}]"> |
||||
|
</view> |
||||
|
<view class="content" @tap='openDetails(x.user_id_info.user_id)'> |
||||
|
<view class="text-grey"> |
||||
|
请求添加<text class="text-blue"> {{x.user_id_info.realname}} </text> 为好友 |
||||
|
</view> |
||||
|
</view> |
||||
|
<view class="action ml-10"> |
||||
|
<text class="text-red" v-if="x.status==0">已拒绝</text> |
||||
|
<text class="text-blue" v-if="x.status==1" @tap="sendMsg(x.user_id_info.user_id)">发消息</text> |
||||
|
<text class="text-orange" v-if="x.status==2">待同意</text> |
||||
|
</view> |
||||
|
</view> |
||||
|
<view class="m-10" v-if="list.length"> |
||||
|
<uni-pagination :current="params.page" :total="total" :pageSize="params.limit" @change="changePage"/> |
||||
|
</view> |
||||
|
|
||||
|
<Empty v-if="!list.length" noDatatext="暂无群聊" textcolor="#999" ></Empty> |
||||
|
</view> |
||||
|
|
||||
|
</view> |
||||
|
</template> |
||||
|
|
||||
|
<script> |
||||
|
import { storeToRefs } from 'pinia'; |
||||
|
import { useMsgStore } from '@/store/message'; |
||||
|
import pinia from '@/store/index' |
||||
|
const msgStore = useMsgStore(pinia) |
||||
|
const {contacts} = storeToRefs(msgStore); |
||||
|
/** |
||||
|
* 初始的引导页 |
||||
|
*/ |
||||
|
export default { |
||||
|
name : "group", |
||||
|
data() { |
||||
|
return { |
||||
|
items: ['我收到的', '我发起的'], |
||||
|
current: 0, |
||||
|
list: [], |
||||
|
total:0, |
||||
|
params: { |
||||
|
page: 1, |
||||
|
limit: 10, |
||||
|
is_mine:0 |
||||
|
} |
||||
|
}; |
||||
|
}, |
||||
|
created() { |
||||
|
}, |
||||
|
mounted(){ |
||||
|
this.getList(); |
||||
|
}, |
||||
|
methods: { |
||||
|
getList(){ |
||||
|
this.$api.friendApi.applyList(this.params).then((res)=>{ |
||||
|
if(res.code==0){ |
||||
|
this.list = res.data; |
||||
|
this.total = res.count; |
||||
|
} |
||||
|
}) |
||||
|
}, |
||||
|
changePage(e){ |
||||
|
this.params.page=e.current; |
||||
|
this.getList(); |
||||
|
}, |
||||
|
onClickItem(e) { |
||||
|
this.params.is_mine = e.currentIndex; |
||||
|
this.current = e.currentIndex; |
||||
|
this.params.page = 1; |
||||
|
this.getList(); |
||||
|
}, |
||||
|
sendMsg(id){ |
||||
|
uni.navigateTo({ |
||||
|
url:"/pages/message/chat?id="+id |
||||
|
}) |
||||
|
}, |
||||
|
openDetails(id){ |
||||
|
uni.navigateTo({ |
||||
|
url:"/pages/contacts/detail?id="+id |
||||
|
}) |
||||
|
}, |
||||
|
searchFriend(){ |
||||
|
uni.navigateTo({ |
||||
|
url:"/pages/contacts/search" |
||||
|
}) |
||||
|
}, |
||||
|
optApply(x){ |
||||
|
uni.showModal({ |
||||
|
title: '提示', |
||||
|
content:"你确定同意该好友的请求吗", |
||||
|
cancelText:"拒绝", |
||||
|
cancelColor:'#e54d42', |
||||
|
confirmText:"接受", |
||||
|
success: (res)=>{ |
||||
|
let status=0; |
||||
|
if (res.confirm) { |
||||
|
status=1 |
||||
|
} |
||||
|
this.$api.friendApi.acceptApply({friend_id:x.friend_id,status:status}).then((e)=>{ |
||||
|
if(e.code==0){ |
||||
|
uni.showToast({ |
||||
|
title:'添加好友成功', |
||||
|
icon:'none' |
||||
|
}) |
||||
|
msgStore.sysUnread--; |
||||
|
this.getList(); |
||||
|
} |
||||
|
}) |
||||
|
} |
||||
|
}) |
||||
|
} |
||||
|
|
||||
|
} |
||||
|
} |
||||
|
</script> |
||||
|
|
||||
|
<style scoped> |
||||
|
|
||||
|
</style> |
||||
@ -0,0 +1,106 @@ |
|||||
|
<template> |
||||
|
<view> |
||||
|
<cu-custom bgColor="bg-main-bar" :isBack="true"> |
||||
|
<template #backText></template> |
||||
|
<template #content>群聊列表</template> |
||||
|
<template #right> |
||||
|
<view class="f-20 ml-10 mr-10" @tap="search()"> |
||||
|
<text class="cuIcon-search"></text> |
||||
|
</view> |
||||
|
</template> |
||||
|
</cu-custom> |
||||
|
<view class="cu-list menu-avatar no-padding"> |
||||
|
<view class="cu-item" v-for="(items,sub) in groupList" :key="sub" @tap='openDetails(items)'> |
||||
|
<block v-for="(itemse,indexs) in imglist" :key="indexs" v-if="network_log == 'none'"> |
||||
|
<view v-if="items.imgname == itemse.name" class='cu-avatar lg radius mr-15' :style="[{backgroundImage:'url('+ itemse.path +')'}]"></view> |
||||
|
</block> |
||||
|
<view v-else class='cu-avatar lg radius mr-15' :style="[{backgroundImage:'url('+items.avatar+')'}]"> |
||||
|
</view> |
||||
|
<view class="content"> |
||||
|
<view class="c-333">{{items.displayName}}</view> |
||||
|
</view> |
||||
|
<view class="action"> |
||||
|
<text class="c-999 cuIcon-peoplefill" v-if="items.owner_id==userInfo.user_id"></text> |
||||
|
</view> |
||||
|
|
||||
|
</view> |
||||
|
<Empty v-if="!groupList.length" noDatatext="暂无群聊" textcolor="#999" ></Empty> |
||||
|
</view> |
||||
|
|
||||
|
</view> |
||||
|
</template> |
||||
|
|
||||
|
<script> |
||||
|
import { storeToRefs } from 'pinia'; |
||||
|
import { useMsgStore } from '@/store/message'; |
||||
|
import { useloginStore } from '@/store/login' |
||||
|
import pinia from '@/store/index' |
||||
|
// #ifdef APP-PLUS |
||||
|
import {getSavedImages} from '@/utils/LocalFileSystemURL.js' |
||||
|
// #endif |
||||
|
const userStore = useloginStore(pinia); |
||||
|
const msgStore = useMsgStore(pinia) |
||||
|
const {contacts,network_log} = storeToRefs(msgStore); |
||||
|
/** |
||||
|
* 初始的引导页 |
||||
|
*/ |
||||
|
export default { |
||||
|
name : "group", |
||||
|
data() { |
||||
|
return { |
||||
|
groupList:[], |
||||
|
userInfo:userStore.userInfo, |
||||
|
imglist:[], |
||||
|
network_log:network_log, |
||||
|
}; |
||||
|
}, |
||||
|
created() {}, |
||||
|
mounted(){ |
||||
|
this.initContacts(this.msgs); |
||||
|
// #ifdef APP-PLUS |
||||
|
this.getImagePath() |
||||
|
// #endif |
||||
|
}, |
||||
|
methods: { |
||||
|
initContacts(arr){ |
||||
|
const allContacts=uni.getStorageSync('allContacts'); |
||||
|
const contacts=allContacts.filter((item)=>{ |
||||
|
return item.is_group==1; |
||||
|
}) |
||||
|
// 将联系人进行排序 |
||||
|
const sorted = contacts.sort((a, b) => { |
||||
|
if (a.index === '#') { |
||||
|
return 1; |
||||
|
} |
||||
|
if (b.index === '#') { |
||||
|
return -1; |
||||
|
} |
||||
|
return a.index.localeCompare(b.index, 'zh'); |
||||
|
}); |
||||
|
this.groupList=sorted; |
||||
|
}, |
||||
|
async getImagePath(){ |
||||
|
this.imglist = await getSavedImages() |
||||
|
this.imglist.map(item => { |
||||
|
item.path = plus.io.convertLocalFileSystemURL(item.path) |
||||
|
}); |
||||
|
// console.info('读取地址',this.imglist); |
||||
|
}, |
||||
|
// 打开聊天 |
||||
|
openDetails(items){ |
||||
|
uni.navigateTo({ |
||||
|
url:"/pages/message/chat?id="+items.id |
||||
|
}) |
||||
|
}, |
||||
|
search(){ |
||||
|
uni.navigateTo({ |
||||
|
url:"/pages/index/search?type=3" |
||||
|
}) |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
</script> |
||||
|
|
||||
|
<style scoped> |
||||
|
|
||||
|
</style> |
||||
@ -0,0 +1,447 @@ |
|||||
|
<template> |
||||
|
<view> |
||||
|
<scroll-view scroll-y class="indexes" :scroll-into-view="'indexes-'+ listCurID" :style="[{height:'calc(100vh - '+ (CustomBar+inlineTools+StatusBar) + 'px)'}]" |
||||
|
:scroll-with-animation="true" :enable-back-to-top="true" v-if="TabCur==0"> |
||||
|
<view style="padding-bottom:30rpx"> |
||||
|
<view class="cu-list menu mt-10"> |
||||
|
<view class="cu-item arrow" @tap="openFriend" v-if="globalConfig.sysInfo.runMode==2"> |
||||
|
<view class='cu-avatar mr-15 invite-bg' :class="appSetting.circleAvatar?'round':'radius'"> |
||||
|
</view> |
||||
|
<view class="content"> |
||||
|
<text class="c-333">新邀请</text> |
||||
|
</view> |
||||
|
<view class="action"> |
||||
|
<view class="cu-tag round bg-red sm" v-if="unread>0">{{unread}}</view> |
||||
|
</view> |
||||
|
</view> |
||||
|
<view class="cu-item arrow" @tap="openGroup"> |
||||
|
<view class='cu-avatar mr-15 group-bg' :class="appSetting.circleAvatar?'round':'radius'"> |
||||
|
</view> |
||||
|
<view class="content"> |
||||
|
<text class="c-333">群聊</text> |
||||
|
</view> |
||||
|
</view> |
||||
|
<view class="cu-item arrow" @tap="openBlacklist"> |
||||
|
<view class='cu-avatar mr-15 blacklist' :class="appSetting.circleAvatar?'round':'radius'"></view> |
||||
|
<view class="content"> |
||||
|
<text class="c-333">黑名单</text> |
||||
|
</view> |
||||
|
</view> |
||||
|
<view class="cu-item arrow" @tap="openChat(item.id)" v-for="(item,index) in systemContact" :key="index"> |
||||
|
<block v-for="(items,indexs) in imglist" :key="indexs" v-if="network_log == 'none'"> |
||||
|
<view class='cu-avatar mr-15' v-if="item.imgname === items.name" :style="{background:'url('+items.path+')',backgroundSize: 'contain'}" :class="appSetting.circleAvatar?'round':'radius'"></view> |
||||
|
</block> |
||||
|
<view v-else class='cu-avatar mr-15' :style="{background:'url('+item.avatar+')',backgroundSize: 'contain'}" :class="appSetting.circleAvatar?'round':'radius'"></view> |
||||
|
<view class="content"> |
||||
|
<text class="c-333">{{item.displayName}}</text> |
||||
|
</view> |
||||
|
</view> |
||||
|
</view> |
||||
|
<block v-for="(item,index) in contacts" :key="index"> |
||||
|
<view :class="'indexItem-' + item.name" :id="'indexes-' + item.name" :data-index="item.name"> |
||||
|
<view class="padding">{{item.name}}</view> |
||||
|
<view class="cu-list menu no-padding"> |
||||
|
<view class="cu-item" v-for="(items,sub) in item.children" :key="sub" @tap='openDetails(items)'> |
||||
|
<block v-for="(iteme,indexs) in imglist" :key="indexs" v-if="network_log == 'none'"> |
||||
|
<view class="cu-avatar mr-15" v-if="items.imgname === iteme.name" :class="appSetting.circleAvatar?'round':'radius'" :style="[{backgroundImage:'url('+ iteme.path +')'}]"> |
||||
|
<view class="online-status-small" v-if="items.is_online && items.is_group==0 && globalConfig.chatInfo.online==1" ></view> |
||||
|
</view> |
||||
|
</block> |
||||
|
<view v-else class='cu-avatar mr-15' :class="appSetting.circleAvatar?'round':'radius'" :style="[{backgroundImage:'url('+items.avatar+')'}]"> |
||||
|
<view class="online-status-small" v-if="items.is_online && items.is_group==0 && globalConfig.chatInfo.online==1" ></view> |
||||
|
</view> |
||||
|
|
||||
|
<view class="content"> |
||||
|
<view class="c-333">{{items.displayName}}</view> |
||||
|
</view> |
||||
|
</view> |
||||
|
</view> |
||||
|
</view> |
||||
|
</block> |
||||
|
<view class="text-center m-20 text-grey">{{total}} 个朋友</view> |
||||
|
<Empty v-if="!contacts.length" noDatatext="暂无联系人" textcolor="#999" ></Empty> |
||||
|
</view> |
||||
|
</scroll-view> |
||||
|
|
||||
|
<view class="indexBar" :style="[{height:'calc(100vh - ' + CustomBar + 'px - 50px)'}]" v-if="TabCur==0"> |
||||
|
<view class="indexBar-box" @touchstart="tStart" @touchend="tEnd" @touchmove.stop="tMove"> |
||||
|
<view class="indexBar-item" v-for="(item,index) in contacts" :key="index" :id="index" @touchstart="getCur" @touchend="setCur"> {{item.name}}</view> |
||||
|
</view> |
||||
|
</view> |
||||
|
|
||||
|
<view class="cu-bar bg-white search fixed" :style="[{top:CustomBar + 'px'}]" v-if="TabCur==1"> |
||||
|
<view class="search-form round"> |
||||
|
<text class="cuIcon-search"></text> |
||||
|
<input type="text" v-model="keywords" placeholder="输入搜索的关键词" confirm-type="search"/> |
||||
|
</view> |
||||
|
</view> |
||||
|
<scroll-view scroll-y class="indexes" :style="[{top:'50px',height:'calc(100vh - '+ (CustomBar+inlineTools+StatusBar) + 'px)'}]" |
||||
|
:scroll-with-animation="true" :enable-back-to-top="true" v-if="TabCur==1"> |
||||
|
<view class=""> |
||||
|
<breadcurm :tree='tree' @openBread="openDep"></breadcurm> |
||||
|
</view> |
||||
|
<view class="im-department-list"> |
||||
|
<navigator class="im-flex im-justify-content-start im-align-items-center mt-10" @tap="openDep(item)" |
||||
|
v-for="(item, depindex) in depList" :key="depindex"> |
||||
|
<view class="im-folder-bar mr-10"> |
||||
|
<text class="cuIcon-file color-blue"></text> |
||||
|
</view> |
||||
|
<view class="im-list-body im-border-b"> |
||||
|
<view class="im-list-title word"> |
||||
|
{{item.name}} |
||||
|
</view> |
||||
|
</view> |
||||
|
</navigator> |
||||
|
<navigator class="im-flex im-justify-content-start im-align-items-center mt-10" v-for="(item, index) in userList" |
||||
|
:key="item.id" :url="'/pages/contacts/detail?user_id=' + item.id"> |
||||
|
<view class="im-folder-bar im-image mr-10"> |
||||
|
<image :src="item.avatar" mode="widthFix"></image> |
||||
|
</view> |
||||
|
<view class="im-list-body im-border-b"> |
||||
|
<view class="im-list-title word"> |
||||
|
{{item.realname}} |
||||
|
</view> |
||||
|
</view> |
||||
|
</navigator> |
||||
|
<Empty v-if="depList.length==0 && userList.length==0"></Empty> |
||||
|
</view> |
||||
|
</scroll-view> |
||||
|
|
||||
|
<!--选择显示--> |
||||
|
<view v-show="!hidden" class="indexToast"> |
||||
|
{{listCur}} |
||||
|
</view> |
||||
|
</view> |
||||
|
</template> |
||||
|
|
||||
|
<script> |
||||
|
import breadcurm from "@/components/breadcrum.vue" |
||||
|
import statusPoint from '@/components/status.vue' |
||||
|
import { storeToRefs } from 'pinia'; |
||||
|
import { useMsgStore } from '@/store/message'; |
||||
|
import { useloginStore } from '@/store/login'; |
||||
|
// #ifdef APP-PLUS |
||||
|
import {getSavedImages} from '@/utils/LocalFileSystemURL.js' |
||||
|
// #endif |
||||
|
import pinia from '@/store/index' |
||||
|
const msgStore = useMsgStore(pinia) |
||||
|
const userStore = useloginStore(pinia) |
||||
|
const {contacts,sysUnread,network_log} = storeToRefs(msgStore); |
||||
|
/** |
||||
|
* 初始的引导页 |
||||
|
*/ |
||||
|
export default { |
||||
|
components: { |
||||
|
breadcurm, |
||||
|
statusPoint |
||||
|
}, |
||||
|
name : "contacts", |
||||
|
props:{ |
||||
|
TabCur:{type:Number, default:0} |
||||
|
}, |
||||
|
data() { |
||||
|
return { |
||||
|
//#ifdef H5 |
||||
|
tabbarH:50, |
||||
|
//#endif |
||||
|
//#ifndef H5 |
||||
|
tabbarH: 100, |
||||
|
//#endif |
||||
|
hidden: true, |
||||
|
listCurID: '', |
||||
|
list: [], |
||||
|
systemContact:[], |
||||
|
network_log:network_log, |
||||
|
listCur: '', |
||||
|
total:0, |
||||
|
scrollLeft: 0, |
||||
|
msgs: contacts, |
||||
|
unread:sysUnread, |
||||
|
globalConfig:userStore.globalConfig, |
||||
|
appSetting:userStore.appSetting, |
||||
|
tree:[ |
||||
|
{ |
||||
|
id:1, |
||||
|
name:'技术部' |
||||
|
}, |
||||
|
{ |
||||
|
id:2, |
||||
|
name:'产品部' |
||||
|
}, |
||||
|
{ |
||||
|
id:3, |
||||
|
name:'信息部' |
||||
|
}, |
||||
|
], |
||||
|
depList:[ |
||||
|
{ |
||||
|
id:1, |
||||
|
name:"技术部" |
||||
|
}, |
||||
|
{ |
||||
|
id:2, |
||||
|
name:"财务部" |
||||
|
} |
||||
|
], |
||||
|
userList:[ |
||||
|
{ |
||||
|
id:1, |
||||
|
realname:"张三", |
||||
|
avatar:'https://api.multiavatar.com/raingad1.png?apikey=zdvXV3W4MjwhP9' |
||||
|
}, |
||||
|
{ |
||||
|
id:2, |
||||
|
realname:"李四", |
||||
|
avatar:'https://api.multiavatar.com/raingad2.png?apikey=zdvXV3W4MjwhP9' |
||||
|
} |
||||
|
], |
||||
|
tabList:[ |
||||
|
"普通通讯录", |
||||
|
"企业通讯录" |
||||
|
], |
||||
|
contacts:[], |
||||
|
imglist:[] |
||||
|
}; |
||||
|
}, |
||||
|
watch:{ |
||||
|
msgs(val){ |
||||
|
this.initContacts(val); |
||||
|
} |
||||
|
}, |
||||
|
created() { |
||||
|
this.listCur = this.contacts[0]; |
||||
|
// #ifdef APP-PLUS |
||||
|
this.getImagePath() |
||||
|
// #endif |
||||
|
}, |
||||
|
mounted(){ |
||||
|
this.initContacts(this.msgs); |
||||
|
}, |
||||
|
methods: { |
||||
|
async getImagePath(){ |
||||
|
this.imglist = await getSavedImages() |
||||
|
this.imglist.map(item => { |
||||
|
item.path = plus.io.convertLocalFileSystemURL(item.path) |
||||
|
}); |
||||
|
// console.info(this.imglist,'读取地址'); |
||||
|
}, |
||||
|
initContacts(arr){ |
||||
|
const allContacts=JSON.parse(JSON.stringify(arr)); |
||||
|
const contacts=allContacts.filter((item)=>{ |
||||
|
return item.is_group==0; |
||||
|
}) |
||||
|
this.systemContact=allContacts.filter((item)=>{ |
||||
|
return item.is_group==2||item.is_group==4; |
||||
|
}) |
||||
|
this.total=contacts.length; |
||||
|
// 将联系人进行排序 |
||||
|
const sorted = contacts.sort((a, b) => { |
||||
|
if (a.index === '#') { |
||||
|
return 1; |
||||
|
} |
||||
|
if (b.index === '#') { |
||||
|
return -1; |
||||
|
} |
||||
|
return a.index.localeCompare(b.index, 'zh'); |
||||
|
}); |
||||
|
// 重组联系人 |
||||
|
const result = sorted.reduce((acc, cur) => { |
||||
|
const index = cur.index; |
||||
|
const existingIndex = acc.findIndex(item => item.name === index); |
||||
|
if (existingIndex === -1) { |
||||
|
acc.push({ name: index, children: [cur] }); |
||||
|
} else { |
||||
|
acc[existingIndex].children.push(cur); |
||||
|
} |
||||
|
return acc; |
||||
|
}, []); |
||||
|
this.contacts=result; |
||||
|
}, |
||||
|
openDep(item){ |
||||
|
|
||||
|
}, |
||||
|
openChat(id){ |
||||
|
uni.navigateTo({ |
||||
|
url:"/pages/message/chat?id=" + id, |
||||
|
animationType:"slide-in-right" |
||||
|
}) |
||||
|
}, |
||||
|
// 打开聊天详情 |
||||
|
openDetails(items){ |
||||
|
uni.navigateTo({ |
||||
|
url:"/pages/contacts/detail?id="+items.id |
||||
|
}) |
||||
|
}, |
||||
|
openGroup(){ |
||||
|
uni.navigateTo({ |
||||
|
url:"/pages/contacts/group" |
||||
|
}) |
||||
|
}, |
||||
|
openBlacklist(){ |
||||
|
uni.navigateTo({ |
||||
|
url:"/pages/contacts/blacklist" |
||||
|
}) |
||||
|
}, |
||||
|
openFriend(){ |
||||
|
uni.navigateTo({ |
||||
|
url:"/pages/contacts/friend" |
||||
|
}) |
||||
|
}, |
||||
|
tabSelect(e) { |
||||
|
this.TabCur = e.currentTarget.dataset.id; |
||||
|
this.scrollLeft = (e.currentTarget.dataset.id - 1) * 60 |
||||
|
}, |
||||
|
//获取文字信息 |
||||
|
getCur(e) { |
||||
|
this.hidden = false; |
||||
|
this.listCur = this.contacts[e.target.id].name; |
||||
|
}, |
||||
|
setCur(e) { |
||||
|
this.hidden = true; |
||||
|
this.listCur = this.listCur |
||||
|
}, |
||||
|
//滑动选择Item |
||||
|
tMove(e) { |
||||
|
|
||||
|
let y = e.touches[0].clientY, |
||||
|
offsettop = this.boxTop, |
||||
|
that = this; |
||||
|
//判断选择区域,只有在选择区才会生效 |
||||
|
if (y > offsettop) { |
||||
|
let num = parseInt((y - offsettop) / 20); |
||||
|
this.listCur = that.contacts[num].name |
||||
|
}; |
||||
|
|
||||
|
}, |
||||
|
//触发全部开始选择 |
||||
|
tStart() { |
||||
|
this.hidden = false |
||||
|
}, |
||||
|
//触发结束选择 |
||||
|
tEnd() { |
||||
|
this.hidden = true; |
||||
|
this.listCurID = this.listCur |
||||
|
}, |
||||
|
indexSelect(e) { |
||||
|
let that = this; |
||||
|
let barHeight = this.barHeight; |
||||
|
let list = this.contacts; |
||||
|
let scrollY = Math.ceil(list.length * e.detail.y / barHeight); |
||||
|
for (let i = 0; i < list.length; i++) { |
||||
|
if (scrollY < i + 1) { |
||||
|
that.listCur = list[i].name; |
||||
|
that.movableY = i * 20 |
||||
|
return false |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
</script> |
||||
|
|
||||
|
<style scoped> |
||||
|
page { |
||||
|
padding-top: 100upx; |
||||
|
} |
||||
|
.indexes { |
||||
|
position: relative; |
||||
|
margin-bottom:20rpx |
||||
|
} |
||||
|
.indexBar { |
||||
|
position: fixed; |
||||
|
right: 0px; |
||||
|
bottom: 0px; |
||||
|
padding: 20upx 20upx 20upx 60upx; |
||||
|
display: flex; |
||||
|
align-items: center; |
||||
|
} |
||||
|
.indexBar .indexBar-box { |
||||
|
width: 40upx; |
||||
|
height: auto; |
||||
|
background: #fff; |
||||
|
display: flex; |
||||
|
flex-direction: column; |
||||
|
box-shadow: 0 0 20upx rgba(0, 0, 0, 0.1); |
||||
|
border-radius: 10upx; |
||||
|
} |
||||
|
.indexBar-item { |
||||
|
flex: 1; |
||||
|
width: 40upx; |
||||
|
height: 40upx; |
||||
|
display: flex; |
||||
|
align-items: center; |
||||
|
justify-content: center; |
||||
|
font-size: 24upx; |
||||
|
color: #888; |
||||
|
} |
||||
|
movable-view.indexBar-item { |
||||
|
width: 40upx; |
||||
|
height: 40upx; |
||||
|
z-index: 9; |
||||
|
position: relative; |
||||
|
} |
||||
|
movable-view.indexBar-item::before { |
||||
|
content: ""; |
||||
|
display: block; |
||||
|
position: absolute; |
||||
|
left: 0; |
||||
|
top: 10upx; |
||||
|
height: 20upx; |
||||
|
width: 4upx; |
||||
|
background-color: #f37b1d; |
||||
|
} |
||||
|
.indexToast { |
||||
|
position: fixed; |
||||
|
top: 0; |
||||
|
right: 80upx; |
||||
|
bottom: 0; |
||||
|
background: rgba(0, 0, 0, 0.5); |
||||
|
width: 100upx; |
||||
|
height: 100upx; |
||||
|
border-radius: 10upx; |
||||
|
margin: auto; |
||||
|
color: #fff; |
||||
|
line-height: 100upx; |
||||
|
text-align: center; |
||||
|
font-size: 48upx; |
||||
|
} |
||||
|
|
||||
|
.gui-list-image { |
||||
|
width: 70rpx !important; |
||||
|
height: 70rpx !important; |
||||
|
border-radius: 8rpx; |
||||
|
object-fit: cover |
||||
|
} |
||||
|
|
||||
|
.im-department-list { |
||||
|
padding: 0 20rpx 40rpx |
||||
|
} |
||||
|
|
||||
|
.im-folder-bar { |
||||
|
background-color: #aaccff52; |
||||
|
border-radius: 8rpx; |
||||
|
width: 70rpx; |
||||
|
height: 70rpx; |
||||
|
text-align: center; |
||||
|
line-height: 70rpx; |
||||
|
overflow: hidden; |
||||
|
} |
||||
|
|
||||
|
.im-image { |
||||
|
|
||||
|
} |
||||
|
|
||||
|
.im-folder-bar .iconfont { |
||||
|
font-size: 44rpx !important; |
||||
|
} |
||||
|
|
||||
|
.group-bg{ |
||||
|
background-image: url(@/static/image/group.png); |
||||
|
} |
||||
|
.invite-bg{ |
||||
|
background-image: url(@/static/image/invite.png); |
||||
|
} |
||||
|
.blacklist{ |
||||
|
background-image: url(@/static/image/blacklist-red.png); |
||||
|
} |
||||
|
</style> |
||||
@ -0,0 +1,88 @@ |
|||||
|
<template> |
||||
|
<view> |
||||
|
<cu-custom bgColor="bg-main-bar" :isBack="true"> |
||||
|
<template #backText></template> |
||||
|
<template #content>{{title}}</template> |
||||
|
</cu-custom> |
||||
|
<view class="cu-bar bg-white search fixed" :style="[{top:CustomBar + 'px'}]"> |
||||
|
<view class="search-form round"> |
||||
|
<text class="cuIcon-search"></text> |
||||
|
<input type="text" v-model="keywords" placeholder="请输入用户账号进行搜索" confirm-type="search"/> |
||||
|
|
||||
|
</view> |
||||
|
<view class="action"> |
||||
|
<button class="cu-btn round bg-green" @tap="search()">搜索</button> |
||||
|
</view> |
||||
|
</view> |
||||
|
|
||||
|
<view style="margin-top:120rpx"> |
||||
|
<view> |
||||
|
<view class="cu-list menu no-padding"> |
||||
|
<view class="cu-item" v-for="(items,sub) in contacts" :key="sub" @tap='openDetails(items)'> |
||||
|
<view class='cu-avatar radius mr-15' :style="[{backgroundImage:'url('+items.avatar+')'}]"> |
||||
|
</view> |
||||
|
<view class="content"> |
||||
|
<view class="c-333">{{items.realname}}</view> |
||||
|
</view> |
||||
|
<view class="action ml-10"> |
||||
|
<view class="text-blue" v-if="items.friend" @tap.stop="sendMsg(items.user_id)">发消息</view> |
||||
|
<view class="text-blue" v-if="!items.friend">查看</view> |
||||
|
</view> |
||||
|
</view> |
||||
|
</view> |
||||
|
</view> |
||||
|
<view v-if="!contacts.length"> |
||||
|
<Empty :noDatatext="noText" textcolor="#999" ></Empty> |
||||
|
</view> |
||||
|
</view> |
||||
|
</view> |
||||
|
</template> |
||||
|
|
||||
|
<script> |
||||
|
/** |
||||
|
* 初始的引导页 |
||||
|
*/ |
||||
|
export default { |
||||
|
name : "search", |
||||
|
data() { |
||||
|
return { |
||||
|
title:'搜索朋友', |
||||
|
keywords:'', |
||||
|
contacts:[], |
||||
|
type:1, |
||||
|
noText:'暂无数据' |
||||
|
}; |
||||
|
}, |
||||
|
methods: { |
||||
|
search(){ |
||||
|
if(this.keywords==''){ |
||||
|
return uni.showToast({ |
||||
|
title:"请输入用户账号进行搜索", |
||||
|
icon:'none' |
||||
|
}) |
||||
|
} |
||||
|
this.noText="未搜索到数据"; |
||||
|
this.$api.msgApi.searchUser({keywords:this.keywords}).then((res)=>{ |
||||
|
if(res.code==0){ |
||||
|
this.contacts=res.data; |
||||
|
} |
||||
|
}) |
||||
|
}, |
||||
|
// 打开用户详情 |
||||
|
openDetails(items){ |
||||
|
uni.navigateTo({ |
||||
|
url:"/pages/contacts/detail?id="+items.user_id |
||||
|
}) |
||||
|
}, |
||||
|
sendMsg(id){ |
||||
|
uni.navigateTo({ |
||||
|
url:"/pages/message/chat?id="+id |
||||
|
}) |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
</script> |
||||
|
|
||||
|
<style scoped> |
||||
|
|
||||
|
</style> |
||||
@ -0,0 +1,435 @@ |
|||||
|
<template> |
||||
|
<view> |
||||
|
<cu-custom :bgColor="PageCur=='mine'?'bg-mine':'bg-white'"> |
||||
|
<template #backText> |
||||
|
<view v-if="PageCur=='message' || PageCur=='contacts'" class="f-20 ml-10 mr-10" @tap="search()"> |
||||
|
<text class="cuIcon-search" style="margin-left: -10px;"></text> |
||||
|
</view> |
||||
|
</template> |
||||
|
<template #content>{{PageName}}</template> |
||||
|
<template #right> |
||||
|
<view v-if="PageCur=='message'" class="f-20 ml-10 mr-10" @tap="modelName='add'"> |
||||
|
<text class="cuIcon-add f-28" id="add"></text> |
||||
|
</view> |
||||
|
|
||||
|
</template> |
||||
|
</cu-custom> |
||||
|
<view> |
||||
|
<message v-show="PageCur=='message'"></message> |
||||
|
<contacts v-show="PageCur=='contacts'" :TabCur="TabCur"></contacts> |
||||
|
<compass v-show="PageCur=='compass'"></compass> |
||||
|
<mine v-show="PageCur=='mine'"></mine> |
||||
|
</view> |
||||
|
<view class="cu-bar tabbar bg-white shadow foot"> |
||||
|
<view class="action" @click="NavChange(item)" v-for="(item,index) in navList" :key="index" data-cur="message"> |
||||
|
<view class='cuIcon-cu-image' style="position: relative;"> |
||||
|
<image :src="'/static/image/tabbar/' + [item.name] + [PageCur==item.name?'-active':''] + '.svg'"></image> |
||||
|
<view class="cu-tag badge" v-if="item.notice>0">{{item.notice}}</view> |
||||
|
<view style="position: absolute;top: 0px;right: 2px;" v-if="item.bol>0"> |
||||
|
<view class="notice-back"></view> |
||||
|
</view> |
||||
|
</view> |
||||
|
<view :class="PageCur==item.name?'text-green':'text-black'">{{item.title}}</view> |
||||
|
</view> |
||||
|
</view> |
||||
|
<view class="add-modal" :class="modelName=='add' ? 'show' : 'none'" @tap="modelName=''" > |
||||
|
<view class="add-dialog" :style="{top:top+20+'px'}"> |
||||
|
<view class="add-item" @tap="initContacts();"> |
||||
|
<view class="content padding-tb-sm"> |
||||
|
<text class="cuIcon-refresh"></text> |
||||
|
<text class="text">更新消息</text> |
||||
|
</view> |
||||
|
</view> |
||||
|
<view class="add-item" @tap="addFriend()" v-if='globalConfig.sysInfo.runMode==2'> |
||||
|
<view class="content padding-tb-sm"> |
||||
|
<text class="cuIcon-friendadd"></text> |
||||
|
<text class="text">添加朋友</text> |
||||
|
</view> |
||||
|
</view> |
||||
|
<view class="add-item" @tap="addGroup()"> |
||||
|
<view class="content padding-tb-sm"> |
||||
|
<text class=" cuIcon-friend"></text> |
||||
|
<text class="text">创建群聊</text> |
||||
|
</view> |
||||
|
</view> |
||||
|
<view class="add-item" @tap="scan()"> |
||||
|
<view class="content padding-tb-sm" style="text-align: left;"> |
||||
|
<text class=" cuIcon-scan mr-5"></text> |
||||
|
<text class="text">扫 一 扫</text> |
||||
|
</view> |
||||
|
</view> |
||||
|
</view> |
||||
|
</view> |
||||
|
</view> |
||||
|
</template> |
||||
|
|
||||
|
<script> |
||||
|
import message from '@/pages/message'; |
||||
|
import contacts from '@/pages/contacts'; |
||||
|
import compass from '@/pages/compass'; |
||||
|
import mine from '@/pages/mine'; |
||||
|
import { storeToRefs } from 'pinia'; |
||||
|
import { useMsgStore } from '@/store/message'; |
||||
|
import { useloginStore } from '@/store/login'; |
||||
|
import pinia from '@/store/index' |
||||
|
import scan from '@/common/scan.js' |
||||
|
import homeData from '../../service/homeData'; |
||||
|
|
||||
|
const msgStore = useMsgStore(pinia) |
||||
|
const loginStore = useloginStore(pinia) |
||||
|
const { unread,sysUnread,NoticeCount } = storeToRefs(msgStore); |
||||
|
export default { |
||||
|
components: { |
||||
|
message, |
||||
|
contacts, |
||||
|
compass, |
||||
|
mine |
||||
|
}, |
||||
|
data() { |
||||
|
let navList=[ |
||||
|
{ |
||||
|
name:'message', |
||||
|
title:'消息', |
||||
|
notice:unread |
||||
|
}, |
||||
|
{ |
||||
|
name:'contacts', |
||||
|
title:'通讯录', |
||||
|
notice:sysUnread |
||||
|
} |
||||
|
] |
||||
|
let compass={ |
||||
|
name:'compass', |
||||
|
title:'探索', |
||||
|
notice:0, |
||||
|
bol:NoticeCount |
||||
|
}; |
||||
|
if(loginStore.globalConfig && loginStore.globalConfig.compass){ |
||||
|
if(loginStore.globalConfig.compass.status==1){ |
||||
|
navList.push(compass); |
||||
|
} |
||||
|
} |
||||
|
let mine={ |
||||
|
name:'mine', |
||||
|
title:'我的', |
||||
|
notice:0 |
||||
|
} |
||||
|
navList.push(mine); |
||||
|
return { |
||||
|
globalConfig:loginStore.globalConfig, |
||||
|
PageCur: 'message', |
||||
|
PageName: '消息', |
||||
|
TabCur:0, |
||||
|
modelName:false, |
||||
|
navList:navList, |
||||
|
top:10, |
||||
|
indexs:0, |
||||
|
IntervalChat:null |
||||
|
} |
||||
|
}, |
||||
|
onBackPress(options) { |
||||
|
if (getCurrentPages().length > 1) { |
||||
|
return false; |
||||
|
} |
||||
|
try { |
||||
|
const main = plus.android.runtimeMainActivity(); |
||||
|
main.moveTaskToBack(false); // 将任务移动到后台 |
||||
|
return true; // 拦截返回按键,防止退出应用 |
||||
|
} catch (e) { |
||||
|
return false; // 出现错误时允许默认返回行为 |
||||
|
} |
||||
|
}, |
||||
|
mounted(){ |
||||
|
// #ifndef MP |
||||
|
uni.hideTabBar(); |
||||
|
// #endif |
||||
|
|
||||
|
// 监听ws状态,如果重新连接了,要更新联系人 |
||||
|
uni.$on('socketStatus',(e)=>{ |
||||
|
if(e){ |
||||
|
console.log('触发了一次'); |
||||
|
this.initContacts(); |
||||
|
} |
||||
|
}) |
||||
|
uni.$off('initContacts'); |
||||
|
uni.$on('initContacts',(e)=>{ |
||||
|
this.initContacts(); |
||||
|
}) |
||||
|
let query = this.$util.getQuery(this).select("#add").boundingClientRect(); |
||||
|
query.exec((res)=> { |
||||
|
let top = res[0].top; |
||||
|
let height = res[0].height; |
||||
|
this.top = top+height |
||||
|
}); |
||||
|
}, |
||||
|
onShow(){ |
||||
|
const _this = this |
||||
|
const tabid = uni.getStorageSync('tabid') |
||||
|
if(tabid){ |
||||
|
uni.removeStorageSync('tabid') |
||||
|
} |
||||
|
uni.getNetworkType({ |
||||
|
success (res) { |
||||
|
if(res.networkType == 'none'){ |
||||
|
_this.getGroupData() |
||||
|
}else{ |
||||
|
_this.initContacts(0); |
||||
|
// _this.startPolling() |
||||
|
// _this.getGroupData() |
||||
|
} |
||||
|
// console.log(res.networkType,'1111'); |
||||
|
} |
||||
|
}); |
||||
|
}, |
||||
|
onHide() { |
||||
|
// 组件销毁时清除定时器 |
||||
|
this.stopPolling() |
||||
|
}, |
||||
|
methods: { |
||||
|
startPolling(){ |
||||
|
this.IntervalChat = setInterval(()=>{ |
||||
|
this.initContacts(); |
||||
|
},5000) |
||||
|
}, |
||||
|
stopPolling(){ |
||||
|
clearInterval(this.IntervalChat) |
||||
|
this.IntervalChat = null |
||||
|
}, |
||||
|
closeModel(){ |
||||
|
this.modelName=false; |
||||
|
}, |
||||
|
scan(){ |
||||
|
scan.scanQr(); |
||||
|
}, |
||||
|
NavChange: function(item) { |
||||
|
this.PageCur = item.name |
||||
|
this.PageName = item.title |
||||
|
}, |
||||
|
showContacts(){ |
||||
|
this.TabCur==1 ? this.TabCur=0 :this.TabCur=1 |
||||
|
}, |
||||
|
initContacts(num){ |
||||
|
if(num){ |
||||
|
this.indexs = num |
||||
|
} |
||||
|
this.modelName=''; |
||||
|
this.$api.msgApi.initContacts().then(res => { |
||||
|
this.indexs++ |
||||
|
// 设置消息未读数和系统消息未读数 |
||||
|
msgStore.sysUnread=res.count; |
||||
|
msgStore.initContacts(res.data); |
||||
|
// if(this.indexs === 1&&num===0){ |
||||
|
// 新增持久化存储检查 |
||||
|
res.data.forEach((val)=>{ |
||||
|
if(val.id==-2){ |
||||
|
val.lastContent = val.lastContent.replace(/'/g, "''"); |
||||
|
} |
||||
|
}) |
||||
|
this.syncGroupData(res.data) |
||||
|
const userInfo = uni.getStorageSync('userInfo') |
||||
|
const list = [...res.data] |
||||
|
list.push({avatar:userInfo.avatar}) |
||||
|
list.forEach(res => { |
||||
|
// #ifdef APP-PLUS |
||||
|
uni.downloadFile({ url: res.avatar,success: (downloadResult) => { |
||||
|
this.saveToPermanentStorage(downloadResult.tempFilePath); |
||||
|
}}) |
||||
|
// #endif |
||||
|
}) |
||||
|
// } |
||||
|
// this.getGroupData() |
||||
|
}) |
||||
|
}, |
||||
|
|
||||
|
// App端持久化存储实现 |
||||
|
saveToPermanentStorage(tempPath) { |
||||
|
return new Promise((resolve, reject) => { |
||||
|
// 获取应用文档目录(持久化存储) |
||||
|
plus.io.resolveLocalFileSystemURL( |
||||
|
'_doc', |
||||
|
(docDir) => { |
||||
|
// 创建目标路径 |
||||
|
docDir.getDirectory( |
||||
|
'img', |
||||
|
{ create: true, exclusive: false }, |
||||
|
(entry) => { |
||||
|
// 从临时路径获取文件名 |
||||
|
const fileName = this.getFileName(tempPath); |
||||
|
const fileName1 = this.getFileName(docDir.fullPath + 'img/' +fileName); |
||||
|
// 新增:检查文件是否存在 |
||||
|
entry.getFile(fileName1,{ create: false }, // 不创建新文件 |
||||
|
(fileEntry) => { |
||||
|
// console.log('文件已存在,拒绝操作'); |
||||
|
// 文件已存在,拒绝操作 |
||||
|
reject(new Error('File already exists: ' + fileName)); |
||||
|
}, |
||||
|
(error) => { |
||||
|
// console.log(error); |
||||
|
// 文件不存在(或发生其他错误),继续复制操作 |
||||
|
if (error.code === 14) { // 1表示文件不存在(不同平台错误码可能不同) |
||||
|
this.copyFile(tempPath, entry, fileName, resolve, reject); |
||||
|
} else { |
||||
|
reject(error); |
||||
|
} |
||||
|
} |
||||
|
); |
||||
|
}, |
||||
|
(error) => { |
||||
|
reject(error); |
||||
|
} |
||||
|
); |
||||
|
}, |
||||
|
(error) => { |
||||
|
reject(error); |
||||
|
} |
||||
|
); |
||||
|
}); |
||||
|
}, |
||||
|
|
||||
|
// 提取复制逻辑为独立方法 |
||||
|
copyFile(tempPath, targetDir, fileName, resolve, reject) { |
||||
|
plus.io.resolveLocalFileSystemURL( |
||||
|
tempPath, |
||||
|
(tempEntry) => { |
||||
|
tempEntry.copyTo( |
||||
|
targetDir, |
||||
|
fileName, |
||||
|
(newEntry) => { |
||||
|
resolve(newEntry.toLocalURL()); |
||||
|
}, |
||||
|
(error) => { |
||||
|
reject(error); |
||||
|
} |
||||
|
); |
||||
|
}, |
||||
|
(error) => { |
||||
|
reject(error); |
||||
|
} |
||||
|
); |
||||
|
}, |
||||
|
|
||||
|
// 获取文件名工具方法 |
||||
|
getFileName(path) { |
||||
|
const index = path.lastIndexOf('/'); |
||||
|
let fileName = path.substr(index + 1); |
||||
|
fileName = fileName.replace(/\(\d+\)(?=\.[^./]+$)/, ''); |
||||
|
return fileName; |
||||
|
}, |
||||
|
|
||||
|
async syncGroupData(data) { |
||||
|
// #ifdef APP-PLUS |
||||
|
// 1. 获取接口数据(示例) |
||||
|
let apiResponse = data; |
||||
|
// 3. 执行批量插入 |
||||
|
await homeData.deleteallList() |
||||
|
await homeData.batchInsertOrUpdate(apiResponse); |
||||
|
// #endif |
||||
|
}, |
||||
|
async getGroupData(){ |
||||
|
// #ifdef APP-PLUS |
||||
|
const groups = await homeData.getList(); |
||||
|
msgStore.initContacts(groups) |
||||
|
// console.log('处理后的数据:', groups) |
||||
|
// #endif |
||||
|
}, |
||||
|
addGroup(){ |
||||
|
uni.navigateTo({ |
||||
|
url: '/pages/index/userSelection?type=1' |
||||
|
}) |
||||
|
}, |
||||
|
addFriend(){ |
||||
|
uni.navigateTo({ |
||||
|
url: '/pages/contacts/search' |
||||
|
}) |
||||
|
}, |
||||
|
search(){ |
||||
|
const type = this.PageCur=="message" ? 1 : 2; |
||||
|
uni.navigateTo({ |
||||
|
url: '/pages/index/search?type='+type |
||||
|
}) |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
</script> |
||||
|
|
||||
|
<style scoped lang="scss"> |
||||
|
.add-modal{ |
||||
|
position: fixed; |
||||
|
top: 0; |
||||
|
right: 0; |
||||
|
z-index: -99999; |
||||
|
|
||||
|
.add-dialog{ |
||||
|
display: flex; |
||||
|
flex-direction: column; |
||||
|
background-color: #4f4f4f; |
||||
|
color: #fff; |
||||
|
border-radius: 10rpx; |
||||
|
justify-content: space-around; |
||||
|
align-items: center; |
||||
|
position: fixed; |
||||
|
right: 10rpx; |
||||
|
width: 240rpx; |
||||
|
padding:20rpx; |
||||
|
.add-item{ |
||||
|
.text{ |
||||
|
margin-left: 10rpx; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
.add-dialog::after { |
||||
|
content: ""; |
||||
|
top: -10rpx; |
||||
|
transform: rotate(45deg); |
||||
|
position: absolute; |
||||
|
display: inline-block; |
||||
|
overflow: hidden; |
||||
|
width: 30rpx; |
||||
|
height: 30rpx; |
||||
|
right: 20rpx; |
||||
|
left: initial; |
||||
|
background: #4f4f4f; |
||||
|
} |
||||
|
} |
||||
|
.show{ |
||||
|
position: fixed; |
||||
|
top: 0; |
||||
|
z-index: 9999; |
||||
|
height: 100vh; |
||||
|
width: 100vw; |
||||
|
opacity:1 |
||||
|
} |
||||
|
.none{ |
||||
|
position: fixed; |
||||
|
top: 0; |
||||
|
right: 0; |
||||
|
z-index: -99999; |
||||
|
opacity: 0; |
||||
|
} |
||||
|
|
||||
|
.notice-back{ |
||||
|
width: 6px; |
||||
|
height: 6px; |
||||
|
position: relative; |
||||
|
border-radius: 50%; |
||||
|
background-color: red; |
||||
|
vertical-align: middle; |
||||
|
} |
||||
|
.notice-back::after{ |
||||
|
content: ""; |
||||
|
width: 100%; |
||||
|
height: 100%; |
||||
|
top: 0px; |
||||
|
right: 0px; |
||||
|
position: absolute; |
||||
|
border-radius: 50%; |
||||
|
background: inherit; |
||||
|
animation: notiback 1.2s ease-in-out infinite; |
||||
|
} |
||||
|
@keyframes notiback{ |
||||
|
0% {transform: scale(0.5);opacity: 1;} |
||||
|
30%{opacity: 0.7;} |
||||
|
100%{ transform: scale(2.5);opacity: 0;} |
||||
|
} |
||||
|
</style> |
||||
@ -0,0 +1,225 @@ |
|||||
|
<template> |
||||
|
<view> |
||||
|
<cu-custom bgColor="bg-main-bar" :isBack="true"> |
||||
|
<template #backText></template> |
||||
|
<template #content>{{title}}</template> |
||||
|
</cu-custom> |
||||
|
<view class="mt-20 im-flex im-align-items-center"> |
||||
|
<image :src="canvasUrl" mode="widthFix" style="margin:0 auto"></image> |
||||
|
</view> |
||||
|
<view class="padding flex flex-direction mt-10" v-if="canvasUrl" style="position: relative;"> |
||||
|
<button class="cu-btn bg-green lg" @tap="saveHeadImgFile"> |
||||
|
<!-- #ifdef H5 --> |
||||
|
下载图片 |
||||
|
<!-- #endif --> |
||||
|
<!-- #ifndef H5 --> |
||||
|
保存到相册 |
||||
|
<!-- #endif --> |
||||
|
</button> |
||||
|
<!-- #ifdef H5 --> |
||||
|
<image :src="contact.avatar" mode="widthFix" style="width: 120rpx;position: absolute;top: -850rpx;left: 320rpx;" /> |
||||
|
<!-- #endif --> |
||||
|
</view> |
||||
|
<mosowe-canvas-image ref="mosoweCanvasComponents" :lists="lists" :height="height" :width="width" @canvasImage="canvasImage" ></mosowe-canvas-image> |
||||
|
</view> |
||||
|
</template> |
||||
|
<script> |
||||
|
import mosoweCanvasImage from '@/components/mosowe-canvas-image/mosowe-canvas-image.vue'; |
||||
|
import pinia from '@/store/index' |
||||
|
import { useloginStore } from '@/store/login'; |
||||
|
const userStore = useloginStore(pinia); |
||||
|
|
||||
|
export default { |
||||
|
data() { |
||||
|
return { |
||||
|
canvasUrl: '', |
||||
|
group_id:0, |
||||
|
lists:[], |
||||
|
width:500, |
||||
|
height:700, |
||||
|
title:'我的二维码', |
||||
|
contact:{} |
||||
|
} |
||||
|
}, |
||||
|
components:{ |
||||
|
mosoweCanvasImage |
||||
|
}, |
||||
|
onLoad(options) { |
||||
|
this.group_id = options.group_id?options.group_id:'' |
||||
|
// 如果有团队ID就生成群聊二维码,没有就生成个人二维码 |
||||
|
if(this.group_id){ |
||||
|
this.title="群二维码"; |
||||
|
this.getGroupInfo() |
||||
|
}else{ |
||||
|
this.createUserQr(); |
||||
|
} |
||||
|
|
||||
|
}, |
||||
|
methods: { |
||||
|
createUserQr(){ |
||||
|
let userInfo=userStore.userInfo; |
||||
|
let qrUrl=userInfo.qrUrl ?? ''; |
||||
|
if(!qrUrl){ |
||||
|
uni.showToast({ |
||||
|
title:'请重新登陆后再生成二维码', |
||||
|
icon:'none' |
||||
|
}) |
||||
|
uni.navigateBack(); |
||||
|
return; |
||||
|
} |
||||
|
this.lists=[ |
||||
|
{ |
||||
|
type: 'rect', |
||||
|
color: '#ffffff', |
||||
|
width: this.width, |
||||
|
height: this.height, |
||||
|
x: 0, |
||||
|
y: 0 |
||||
|
}, |
||||
|
{ |
||||
|
type: 'image', |
||||
|
content: userInfo.avatar, |
||||
|
width: 100, |
||||
|
height: 100, |
||||
|
x: 70, |
||||
|
y: 60 |
||||
|
}, |
||||
|
{ |
||||
|
type: 'text', |
||||
|
content: userInfo.displayName, |
||||
|
x: 190, |
||||
|
y: 96, |
||||
|
maxWidth:400, |
||||
|
color: '#000', |
||||
|
size: 28, |
||||
|
}, |
||||
|
{ |
||||
|
type: 'text', |
||||
|
content: userInfo.account, |
||||
|
x: 190, |
||||
|
y: 135, |
||||
|
maxWidth:400, |
||||
|
color: '#999', |
||||
|
size: 20, |
||||
|
}, |
||||
|
{ |
||||
|
type: 'qr', |
||||
|
content: qrUrl, |
||||
|
width: 360, |
||||
|
height: 360, |
||||
|
x: 70, |
||||
|
y: 200, |
||||
|
}, |
||||
|
{ |
||||
|
type: 'text', |
||||
|
content: '扫一扫上面的二维码图案,加我为好友', |
||||
|
x: 105, |
||||
|
y: 620, |
||||
|
color: '#999', |
||||
|
maxWidth:400, |
||||
|
size: 18, |
||||
|
}, |
||||
|
]; |
||||
|
setTimeout(()=>{ |
||||
|
this.$refs.mosoweCanvasComponents.createCanvas(); |
||||
|
},100) |
||||
|
}, |
||||
|
getGroupInfo() { |
||||
|
this.userList = [] |
||||
|
this.$api.msgApi.groupInfo({ |
||||
|
group_id: this.group_id |
||||
|
}).then((res) => { |
||||
|
let data=res.data; |
||||
|
this.contact=data; |
||||
|
let groupName=data.name; |
||||
|
let length=groupName.length; |
||||
|
console.log(this.contact); |
||||
|
if(length>12){ |
||||
|
groupName=groupName.substring(0,12)+"..."; |
||||
|
} |
||||
|
this.lists=[ |
||||
|
{ |
||||
|
type: 'rect', |
||||
|
color: '#ffffff', |
||||
|
width: this.width, |
||||
|
height: this.height, |
||||
|
x: 0, |
||||
|
y: 0 |
||||
|
}, |
||||
|
{ |
||||
|
type: 'image', |
||||
|
content: data.avatar, |
||||
|
// content: "http://192.168.66.16:8007/avatar/吕布/120/20", |
||||
|
width: 100, |
||||
|
height: 100, |
||||
|
x: 200, |
||||
|
y: 40 |
||||
|
}, |
||||
|
{ |
||||
|
type: 'text', |
||||
|
content: '群聊:'+groupName, |
||||
|
x: this.width/2, |
||||
|
align:'center', |
||||
|
y: 180, |
||||
|
maxWidth:400, |
||||
|
color: '#000', |
||||
|
size: 24, |
||||
|
}, |
||||
|
{ |
||||
|
type: 'qr', |
||||
|
content: data.qrUrl, |
||||
|
width: 360, |
||||
|
height: 360, |
||||
|
x: 70, |
||||
|
y: 220, |
||||
|
}, |
||||
|
{ |
||||
|
type: 'text', |
||||
|
content: '该二维码7天内('+data.qrExpire+'前)有效', |
||||
|
x: 105, |
||||
|
y: 640, |
||||
|
color: '#999', |
||||
|
maxWidth:400, |
||||
|
size: 18, |
||||
|
}, |
||||
|
]; |
||||
|
setTimeout(()=>{ |
||||
|
this.$refs.mosoweCanvasComponents.createCanvas(); |
||||
|
},100) |
||||
|
}) |
||||
|
}, |
||||
|
canvasImage (e) { |
||||
|
this.canvasUrl = e; |
||||
|
}, |
||||
|
// 将base64 图片保存到本地系统相册 |
||||
|
saveHeadImgFile() { |
||||
|
// #ifdef H5 |
||||
|
const tempLink = document.createElement("a"); |
||||
|
tempLink.style.display = "none"; |
||||
|
tempLink.href = this.canvasUrl; |
||||
|
tempLink.setAttribute("download", this.contact.name+".jpg"); |
||||
|
tempLink.setAttribute("target", "_blank"); |
||||
|
document.body.appendChild(tempLink); |
||||
|
tempLink.click(); |
||||
|
document.body.removeChild(tempLink); |
||||
|
// #endif |
||||
|
// #ifndef H5 |
||||
|
uni.saveImageToPhotosAlbum({ |
||||
|
filePath: this.canvasUrl, |
||||
|
success: () => { |
||||
|
uni.showToast({title:'图片保存成功',icon:'none'}) |
||||
|
} |
||||
|
}) |
||||
|
// #endif |
||||
|
|
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
</script> |
||||
|
<style scoped> |
||||
|
.list-image { |
||||
|
width: 80rpx; |
||||
|
height: 80rpx; |
||||
|
font-size: 0; |
||||
|
} |
||||
|
</style> |
||||
@ -0,0 +1,34 @@ |
|||||
|
<template> |
||||
|
<!-- HTML --> |
||||
|
<get-qrcode @success='qrcodeSucess' @error="qrcodeError" ></get-qrcode> |
||||
|
</template> |
||||
|
|
||||
|
<script> |
||||
|
import scan from '@/common/scan.js' |
||||
|
import getQrcode from '@/components/get-qrcode.vue' |
||||
|
// 嫌路径长的话可以单独复制出来 |
||||
|
export default { |
||||
|
components: { |
||||
|
getQrcode |
||||
|
}, |
||||
|
methods: { |
||||
|
qrcodeSucess(data) { |
||||
|
scan.checkQr(data); |
||||
|
uni.navigateBack({}) |
||||
|
}, |
||||
|
qrcodeError(err) { |
||||
|
console.log(err) |
||||
|
uni.showModal({ |
||||
|
title: '摄像头授权失败', |
||||
|
content: '摄像头授权失败,请检测当前浏览器是否有摄像头权限。', |
||||
|
success: () => { |
||||
|
uni.navigateBack() |
||||
|
} |
||||
|
}) |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
</script> |
||||
|
|
||||
|
<style> |
||||
|
</style> |
||||
@ -0,0 +1,112 @@ |
|||||
|
<template> |
||||
|
<view> |
||||
|
<cu-custom bgColor="bg-main-bar" :isBack="true"> |
||||
|
<template #backText></template> |
||||
|
<template #content>{{title}}</template> |
||||
|
</cu-custom> |
||||
|
<view class="cu-bar bg-white search fixed" :style="[{top:CustomBar + 'px'}]"> |
||||
|
<view class="search-form round"> |
||||
|
<text class="cuIcon-search"></text> |
||||
|
<input type="text" v-model="keywords" placeholder="搜索联系人" confirm-type="search"/> |
||||
|
</view> |
||||
|
</view> |
||||
|
|
||||
|
<view style="margin-top:120rpx"> |
||||
|
<view v-if="type<3 && contacts.length>0"> |
||||
|
<view class="padding">联系人</view> |
||||
|
<view class="cu-list menu-avatar no-padding"> |
||||
|
<view class="cu-item" v-for="(items,sub) in contacts" :key="sub" @tap='openDetails(items)'> |
||||
|
<view class='cu-avatar lg radius mr-15' :style="[{backgroundImage:'url('+items.avatar+')'}]"> |
||||
|
</view> |
||||
|
<view class="content"> |
||||
|
<view class="c-333">{{items.displayName}}</view> |
||||
|
</view> |
||||
|
</view> |
||||
|
</view> |
||||
|
</view> |
||||
|
<view v-if="groupList.length>0"> |
||||
|
<view class="padding">群聊</view> |
||||
|
<view class="cu-list menu-avatar no-padding"> |
||||
|
<view class="cu-item" v-for="(items,sub) in groupList" :key="sub" @tap='openDetails(items)'> |
||||
|
<view class='cu-avatar lg radius mr-15' :style="[{backgroundImage:'url('+items.avatar+')'}]"> |
||||
|
</view> |
||||
|
<view class="content"> |
||||
|
<view class="c-333">{{items.displayName}}</view> |
||||
|
</view> |
||||
|
</view> |
||||
|
</view> |
||||
|
</view> |
||||
|
<view v-if="!groupList.length && !contacts.length"> |
||||
|
<Empty noDatatext="暂无数据" textcolor="#999" ></Empty> |
||||
|
</view> |
||||
|
</view> |
||||
|
</view> |
||||
|
</template> |
||||
|
|
||||
|
<script> |
||||
|
/** |
||||
|
* 初始的引导页 |
||||
|
*/ |
||||
|
export default { |
||||
|
name : "search", |
||||
|
data() { |
||||
|
return { |
||||
|
title:'搜索聊天', |
||||
|
keywords:'', |
||||
|
allContacts:[], |
||||
|
contacts:[], |
||||
|
groupList:[], |
||||
|
type:1 |
||||
|
}; |
||||
|
}, |
||||
|
watch:{ |
||||
|
keywords(val){ |
||||
|
this.search(); |
||||
|
} |
||||
|
}, |
||||
|
onLoad(options){ |
||||
|
this.type=options.type; |
||||
|
if(this.type==2){ |
||||
|
this.title="搜索联系人"; |
||||
|
}else if(this.type==3){ |
||||
|
this.title="搜索群聊"; |
||||
|
}else{ |
||||
|
this.title="搜索聊天"; |
||||
|
} |
||||
|
}, |
||||
|
mounted(){ |
||||
|
this.allContacts=uni.getStorageSync('allContacts'); |
||||
|
}, |
||||
|
methods: { |
||||
|
search(){ |
||||
|
const arr=this.$util.searchObject(this.allContacts,['displayName','name_py','account'],this.keywords); |
||||
|
const contacts=[]; |
||||
|
const groupList=[]; |
||||
|
arr.forEach((item)=>{ |
||||
|
if(item.is_group==1){ |
||||
|
groupList.push(item); |
||||
|
}else{ |
||||
|
contacts.push(item); |
||||
|
} |
||||
|
}) |
||||
|
this.groupList=groupList; |
||||
|
this.contacts=contacts; |
||||
|
}, |
||||
|
// 打开聊天 |
||||
|
openDetails(items){ |
||||
|
if(this.type==2 && items.is_gourp==0){ |
||||
|
uni.navigateTo({ |
||||
|
url:"/pages/contacts/detail?id="+items.id |
||||
|
}) |
||||
|
} |
||||
|
uni.navigateTo({ |
||||
|
url:"/pages/message/chat?id="+items.id |
||||
|
}) |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
</script> |
||||
|
|
||||
|
<style scoped> |
||||
|
|
||||
|
</style> |
||||
@ -0,0 +1,354 @@ |
|||||
|
<template> |
||||
|
<view> |
||||
|
<cu-custom bgColor="bg-main-bar" :isBack="true"> |
||||
|
<template #backText></template> |
||||
|
<template #content>{{title}}</template> |
||||
|
<template #right> |
||||
|
<view class="mr-10 f-16" @tap="save">{{type==3 ? '转发' : '完成'}}</view> |
||||
|
</template> |
||||
|
</cu-custom> |
||||
|
<user-select :type="type" :contact_id="contact_id" :user_ids="user_ids" ref="userSelect"></user-select> |
||||
|
</view> |
||||
|
</template> |
||||
|
|
||||
|
<script> |
||||
|
import userSelect from '@/components/message/user-select.vue'; |
||||
|
import { useMsgStore } from '@/store/message'; |
||||
|
import { storeToRefs } from 'pinia'; |
||||
|
import pinia from '@/store/index' |
||||
|
const msgStore = useMsgStore(pinia) |
||||
|
const {appendContacts} = storeToRefs(msgStore); |
||||
|
export default { |
||||
|
components: { |
||||
|
userSelect |
||||
|
}, |
||||
|
data() { |
||||
|
return { |
||||
|
title:'发起群聊', |
||||
|
contact_id:'', |
||||
|
selectUser:[], |
||||
|
userList: [], |
||||
|
changeUser: [], //选中的数据 |
||||
|
user_ids: [], // |
||||
|
type: 1, |
||||
|
relayState: false, |
||||
|
scrollLeft:300, |
||||
|
content:'', |
||||
|
curMsg:{} |
||||
|
} |
||||
|
}, |
||||
|
watch: { |
||||
|
relayState(val) { |
||||
|
if (val == true) { |
||||
|
uni.showToast({ |
||||
|
icon: 'success', |
||||
|
title: '转发成功' |
||||
|
}) |
||||
|
setTimeout(() => { |
||||
|
uni.navigateBack() |
||||
|
}, 2000) |
||||
|
} |
||||
|
|
||||
|
} |
||||
|
}, |
||||
|
onLoad(options) { |
||||
|
var pages = getCurrentPages(); //当前页 |
||||
|
var beforePage = pages[pages.length - 2]; //上个页面 |
||||
|
this.type = options.type ? options.type : 1; |
||||
|
this.contact_id = options.contact_id ? options.contact_id : ''; |
||||
|
this.content = options.content ? options.content : ''; |
||||
|
if (options.type == 2) { |
||||
|
this.title="添加成员"; |
||||
|
// 调用上一页的方法刷新 |
||||
|
// #ifdef H5 |
||||
|
this.user_ids = beforePage.user_ids |
||||
|
// #endif |
||||
|
// #ifndef H5 |
||||
|
this.user_ids = beforePage.$vm.user_ids |
||||
|
// #endif |
||||
|
console.log(this.user_ids); |
||||
|
}else if (options.type == 3) { |
||||
|
this.title="转发聊天"; |
||||
|
// 调用上一页的方法刷新 |
||||
|
// #ifdef H5 |
||||
|
this.curMsg = beforePage.curMsg |
||||
|
// #endif |
||||
|
// #ifndef H5 |
||||
|
this.curMsg = beforePage.$vm.curMsg |
||||
|
// #endif |
||||
|
} else if (options.type == 4) { |
||||
|
this.title="选择提醒的人"; |
||||
|
} else if(options.type == 5){ |
||||
|
this.user_ids = JSON.parse(options.user_ids) |
||||
|
// console.log(this.user_ids); |
||||
|
}else if(options.type == 6){ |
||||
|
if(options.user_ids){ |
||||
|
this.user_ids = JSON.parse(options.user_ids) |
||||
|
} |
||||
|
// console.log(this.user_ids); |
||||
|
}else{ |
||||
|
this.title="发起群聊"; |
||||
|
} |
||||
|
|
||||
|
|
||||
|
}, |
||||
|
methods: { |
||||
|
// 转发 |
||||
|
relay() { |
||||
|
let user_ids = this.changeUser.map(it => { |
||||
|
return it.id |
||||
|
}) |
||||
|
if (!user_ids.length) { |
||||
|
uni.showToast({ |
||||
|
title: "请选择至少一名人员", |
||||
|
icon: "none" |
||||
|
}) |
||||
|
} else if (user_ids.length > 5) { |
||||
|
uni.showToast({ |
||||
|
title: "转发的人数不能超过5人!", |
||||
|
icon: "none" |
||||
|
}) |
||||
|
} else { |
||||
|
var pages = getCurrentPages(); //当前页 |
||||
|
var beforePage = pages[pages.length - 2]; //上个页面 |
||||
|
let toContactId = '' |
||||
|
let fromUser = '' |
||||
|
// 调用上一页的方法刷新 |
||||
|
// #ifdef H5 |
||||
|
fromUser = beforePage.fromUser |
||||
|
toContactId = beforePage.contact.id |
||||
|
// #endif |
||||
|
// #ifndef H5 |
||||
|
fromUser = beforePage.$vm.fromUser |
||||
|
// #endif |
||||
|
let selectedItem = '' |
||||
|
if (this.type == 'relayCrm' || this.type == 'relayProject') { |
||||
|
// #ifdef H5 |
||||
|
selectedItem = beforePage.message |
||||
|
// #endif |
||||
|
// #ifndef H5 |
||||
|
selectedItem = beforePage.$vm.message |
||||
|
// #endif |
||||
|
} else { |
||||
|
selectedItem = uni.getStorageSync('selectedItem') |
||||
|
} |
||||
|
user_ids.forEach(it => { |
||||
|
let msg = { |
||||
|
id: this.$util.getUuid(), |
||||
|
is_group: 0, |
||||
|
fromUser, |
||||
|
extends: selectedItem.extends ? selectedItem.extends : '', |
||||
|
type: selectedItem.type, |
||||
|
toContactId: it, |
||||
|
content: selectedItem.content, |
||||
|
sendTime: new Date().getTime() |
||||
|
} |
||||
|
|
||||
|
this.$api.msgApi.sendMessage(msg) |
||||
|
.then((res) => { |
||||
|
if (res.code !== 200) return |
||||
|
this.relayState = true |
||||
|
}) |
||||
|
}) |
||||
|
} |
||||
|
}, |
||||
|
// 添加群成员 |
||||
|
addGroupUser(user_ids) { |
||||
|
var pages = getCurrentPages(); //当前页 |
||||
|
var beforePage = pages[pages.length - 2]; //上个页面 |
||||
|
// 调用上一页的方法刷新 |
||||
|
// #ifdef H5 |
||||
|
beforePage.getsimpleMessage = false |
||||
|
let group_id = beforePage.group_id |
||||
|
// #endif |
||||
|
// #ifndef H5 |
||||
|
beforePage.$vm.getsimpleMessage = false |
||||
|
let group_id = beforePage.$vm.group_id |
||||
|
// #endif |
||||
|
this.$api.msgApi.addGroupUser({ |
||||
|
user_ids, |
||||
|
id: group_id |
||||
|
}).then(res => { |
||||
|
if (res.code == 200) { |
||||
|
uni.navigateBack() |
||||
|
} |
||||
|
}) |
||||
|
}, |
||||
|
// 添加群聊 |
||||
|
addGroup(user_ids) { |
||||
|
this.$api.msgApi.addGroup({ |
||||
|
user_ids |
||||
|
}).then(res => { |
||||
|
if (res.code == 200) { |
||||
|
setTimeout(() => { |
||||
|
uni.navigateBack() |
||||
|
}, 2000) |
||||
|
|
||||
|
|
||||
|
|
||||
|
} |
||||
|
}) |
||||
|
}, |
||||
|
save(){ |
||||
|
uni.showLoading({ |
||||
|
title: '保存中...', |
||||
|
mask:true |
||||
|
}); |
||||
|
this.changeUser=this.$refs.userSelect.changeUser; |
||||
|
this.selectUser=this.$refs.userSelect.selectUser; |
||||
|
// console.log(this.changeUser); |
||||
|
if(!this.changeUser.length){ |
||||
|
return uni.showToast({ |
||||
|
title:'请选择人员', |
||||
|
icon:'none' |
||||
|
}) |
||||
|
} |
||||
|
try{ |
||||
|
if(this.type==1){ |
||||
|
this.$api.msgApi.addGroup({user_ids:this.changeUser}).then(res =>{ |
||||
|
uni.hideLoading(); |
||||
|
const data = res.data; |
||||
|
msgStore.appendContacts(data); |
||||
|
uni.navigateTo({ |
||||
|
url:'/pages/message/chat?id='+data.id |
||||
|
}) |
||||
|
}) |
||||
|
}else if(this.type==2){ |
||||
|
this.$api.msgApi.addGroupUser({user_ids:this.changeUser,id:this.contact_id}).then(res =>{ |
||||
|
this.closePage(); |
||||
|
}) |
||||
|
}else if(this.type==3){ |
||||
|
if(this.changeUser.length>5){ |
||||
|
return uni.showToast({ |
||||
|
title:'单次转发不能超过5人', |
||||
|
icon:'none' |
||||
|
}) |
||||
|
} |
||||
|
this.$api.msgApi.forwardMessage({user_ids:this.changeUser,msg_id:this.curMsg.msg_id,content:this.content}).then(res =>{ |
||||
|
this.closePage(); |
||||
|
}) |
||||
|
}else if(this.type==4){ |
||||
|
const eventChannel = this.getOpenerEventChannel(); |
||||
|
eventChannel.emit('getAtList',this.selectUser); |
||||
|
this.closePage(); |
||||
|
}else if(this.type==5){ |
||||
|
uni.setStorageSync('selectUser',this.selectUser) |
||||
|
this.closePage(); |
||||
|
}else if(this.type==6){ |
||||
|
uni.setStorageSync('selectUser1',this.selectUser) |
||||
|
this.closePage(); |
||||
|
}else{ |
||||
|
this.$api.msgApi.removeUser({user_ids:this.changeUser,id:this.contact_id}).then(res =>{ |
||||
|
this.closePage(); |
||||
|
}) |
||||
|
} |
||||
|
}catch(e){ |
||||
|
console.info(e); |
||||
|
this.closePage(); |
||||
|
} |
||||
|
|
||||
|
}, |
||||
|
// 关闭加载动画返回上一个页面 |
||||
|
closePage(){ |
||||
|
uni.hideLoading(); |
||||
|
uni.navigateBack(); |
||||
|
}, |
||||
|
// 监听提交 |
||||
|
confirm: function(e) { |
||||
|
let arr = [] |
||||
|
if (e) { //这个值为输入框输入的值 |
||||
|
var brr = this.userList.filter(value => { |
||||
|
//遍历数组,返回值为true保留并复制到新数组,false则过滤掉 |
||||
|
let data = value.realname ? value.realname : value.userInfo.displayName |
||||
|
if (data.includes(e.trim())) { |
||||
|
arr.push(value) |
||||
|
} |
||||
|
return data.includes(e.trim()); |
||||
|
}); |
||||
|
this.lists = arr |
||||
|
} |
||||
|
|
||||
|
}, |
||||
|
} |
||||
|
} |
||||
|
</script> |
||||
|
|
||||
|
<style lang="scss" scoped> |
||||
|
.group-bg{ |
||||
|
background-image: url(@/static/image/group.png); |
||||
|
} |
||||
|
.search-warp { |
||||
|
width: 750rpx; |
||||
|
padding: 15rpx 50rpx; |
||||
|
} |
||||
|
::v-deep .checklist-group{ |
||||
|
display: grid !important; |
||||
|
.checklist-box{ |
||||
|
padding:20rpx; |
||||
|
.checkbox__inner{ |
||||
|
width:40rpx !important; |
||||
|
height:40rpx !important; |
||||
|
overflow:hidden; |
||||
|
.checkbox__inner-icon{ |
||||
|
position: absolute; |
||||
|
top: -8px !important; |
||||
|
left: -4px !important; |
||||
|
height: 20px !important; |
||||
|
width: 20px !important; |
||||
|
border-right-width: 2px !important; |
||||
|
border-bottom-width: 2px !important; |
||||
|
} |
||||
|
} |
||||
|
.checklist-content{ |
||||
|
margin-left:20rpx; |
||||
|
.checklist-text{ |
||||
|
font-size:36rpx !important; |
||||
|
} |
||||
|
|
||||
|
} |
||||
|
} |
||||
|
.is-checked{ |
||||
|
.checkbox__inner{ |
||||
|
background-color: #18bc37 !important; |
||||
|
border-color: #18bc37 !important; |
||||
|
} |
||||
|
.checklist-content{ |
||||
|
.checklist-text{ |
||||
|
color: #18bc37 !important; |
||||
|
} |
||||
|
|
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
.footer-opt{ |
||||
|
position: fixed; |
||||
|
bottom:0; |
||||
|
left:0; |
||||
|
width:100%; |
||||
|
} |
||||
|
|
||||
|
.scroll-view_H { |
||||
|
white-space: nowrap; |
||||
|
width: 100%; |
||||
|
} |
||||
|
|
||||
|
.user-list-avatar{ |
||||
|
float: left; |
||||
|
margin-top:10rpx; |
||||
|
.user-avatar{ |
||||
|
width:70rpx; |
||||
|
height:70rpx; |
||||
|
flex: 0 0 auto; |
||||
|
border-radius: 8rpx; |
||||
|
margin-left: 15rpx; |
||||
|
display: inline-block; |
||||
|
&:last-child{ |
||||
|
margin-right: 15rpx; |
||||
|
} |
||||
|
} |
||||
|
.select-num{ |
||||
|
padding:10rpx; |
||||
|
} |
||||
|
|
||||
|
} |
||||
|
</style> |
||||
@ -0,0 +1,57 @@ |
|||||
|
<template> |
||||
|
<view class="flex justify-center align-center" style="height: 100vh;background-color: #fff;"> |
||||
|
<view> |
||||
|
<image src="../../static/image/123.gif" mode="widthFix"></image> |
||||
|
<view style="text-align: center;"> |
||||
|
<view class="text1">抱歉!</view> |
||||
|
<view style="margin-bottom: 40rpx;">{{globalConfig.sysInfo.closeTips}}</view> |
||||
|
<view style="display: flex;justify-content: center;"> |
||||
|
<a class="bullshit-return-home" href="/">返回首页</a> |
||||
|
</view> |
||||
|
</view> |
||||
|
</view> |
||||
|
</view> |
||||
|
</template> |
||||
|
|
||||
|
<script> |
||||
|
import pinia from '@/store/index' |
||||
|
import { useloginStore } from '@/store/login'; |
||||
|
const userStore = useloginStore(pinia); |
||||
|
export default { |
||||
|
data() { |
||||
|
return { |
||||
|
globalConfig:userStore.globalConfig, |
||||
|
} |
||||
|
}, |
||||
|
created() { |
||||
|
|
||||
|
}, |
||||
|
methods: { |
||||
|
|
||||
|
} |
||||
|
} |
||||
|
</script> |
||||
|
|
||||
|
<style scoped> |
||||
|
.text1{ |
||||
|
margin-bottom: 40rpx; |
||||
|
font-size: 64rpx; |
||||
|
font-weight: bold; |
||||
|
line-height: 80rpx; |
||||
|
color: #175CFF; |
||||
|
} |
||||
|
.bullshit-return-home{ |
||||
|
display:block; |
||||
|
float:left; |
||||
|
width:110px; |
||||
|
height:36px; |
||||
|
font-size:14px; |
||||
|
line-height:36px; |
||||
|
color:#fff; |
||||
|
text-align:center; |
||||
|
cursor:pointer; |
||||
|
background:#175cff; |
||||
|
border-radius:100px; |
||||
|
text-decoration: none; |
||||
|
} |
||||
|
</style> |
||||
@ -0,0 +1,214 @@ |
|||||
|
<template> |
||||
|
<view> |
||||
|
<view style="height:150rpx;"></view> |
||||
|
<view class="padding im-flex im-rows im-justify-content-center mb-10"> |
||||
|
<view class="im-flex im-rows im-justify-content-center"> |
||||
|
<image class="login-logo " :src="globalConfig.sysInfo.logo ?? packData.logo" mode="fixWidth"></image> |
||||
|
|
||||
|
</view> |
||||
|
</view> |
||||
|
<view class="im-flex im-rows im-justify-content-center">{{globalConfig.sysInfo.name ?? packData.name}}</view> |
||||
|
<form> |
||||
|
<view class="cu-form-group margin-top"> |
||||
|
<view class="title">账号</view> |
||||
|
<input placeholder="账号" maxlength="32" name="input" v-model="loginForm.account"/> |
||||
|
</view> |
||||
|
<view class="cu-form-group" v-if="!forget"> |
||||
|
<view class="title">密码</view> |
||||
|
<input placeholder="请输入密码" maxlength="32" type="password" name="input" v-model="loginForm.password"/> |
||||
|
</view> |
||||
|
<view class="cu-form-group" v-else> |
||||
|
<view class="title">验证码</view> |
||||
|
<input placeholder="请输入验证码" maxlength="6" name="input" v-model="loginForm.code"/> |
||||
|
<button class='cu-btn bg-blue shadow' @tap="sendCode">发送验证码</button> |
||||
|
</view> |
||||
|
</form> |
||||
|
<view class='forget'> |
||||
|
<view><switch class="switch" :checked="loginForm.rememberMe" :class="loginForm.rememberMe?'checked':''" @change="switchChange" style="transform:scale(0.7)" />记住我</view> |
||||
|
<view class="text-blue" @tap="forget=!forget;loginForm.code=''">{{forget ? '密码登陆' : '忘记密码'}}</view> |
||||
|
</view> |
||||
|
<view class="flex flex-direction im-login-btn"> |
||||
|
<button class="cu-btn lg bg-blue" @tap="login()">登录</button> |
||||
|
</view> |
||||
|
<view class="flex flex-direction im-reg-btn" v-if="globalConfig && globalConfig.sysInfo.regtype==1"> |
||||
|
<button class=" cu-btn lg bg-white" @tap="register()">注册</button> |
||||
|
</view> |
||||
|
<view class="m-20 c-666" v-if="globalConfig && globalConfig.demon_mode "> |
||||
|
<view class="f-16 remark-title mb-10">站点仅用于演示,演示账号</view> |
||||
|
<view class="c-999">账号:13800000002~13800000020</view> |
||||
|
<view class="c-999">密码:123456</view> |
||||
|
</view> |
||||
|
<view class="footer-version c-999"> |
||||
|
{{globalConfig.sysInfo.name ?? packData.name}} for {{packData.version}} |
||||
|
</view> |
||||
|
</view> |
||||
|
</template> |
||||
|
|
||||
|
<script> |
||||
|
import { useloginStore } from '@/store/login' |
||||
|
import pinia from '@/store/index' |
||||
|
import packageData from "../../package.json" |
||||
|
import getSystemInfo from '../../service/getSystemInfo'; |
||||
|
import { useMsgStore } from '@/store/message'; |
||||
|
|
||||
|
const loginStore = useloginStore(pinia) |
||||
|
const msgStore = useMsgStore(pinia) |
||||
|
export default { |
||||
|
data() { |
||||
|
return { |
||||
|
loginForm:{ |
||||
|
account:'', |
||||
|
password:'', |
||||
|
code:'', |
||||
|
client_id:'', |
||||
|
rememberMe:false |
||||
|
}, |
||||
|
forget:false, |
||||
|
packData:packageData, |
||||
|
globalConfig:loginStore.globalConfig |
||||
|
} |
||||
|
}, |
||||
|
watch:{ |
||||
|
forget(val){ |
||||
|
if(val){ |
||||
|
this.loginForm.password='123456'; |
||||
|
} |
||||
|
} |
||||
|
}, |
||||
|
onLoad(options){ |
||||
|
// 检查是否有token,如果有就自动登录 |
||||
|
const token=options.token; |
||||
|
if(token){ |
||||
|
this.doLogin({token:token}); |
||||
|
} |
||||
|
}, |
||||
|
mounted() { |
||||
|
if(this.globalConfig && this.globalConfig.demon_mode){ |
||||
|
const random = Math.floor(Math.random() * 19 + 2) |
||||
|
this.loginForm.account=13800000000+random; |
||||
|
this.loginForm.password='123456'; |
||||
|
} |
||||
|
let LoginAccount=uni.getStorageSync('LoginAccount'); |
||||
|
if(LoginAccount){ |
||||
|
this.loginForm=LoginAccount; |
||||
|
} |
||||
|
}, |
||||
|
methods: { |
||||
|
switchChange(e) { |
||||
|
this.loginForm.rememberMe=e.detail.value; |
||||
|
}, |
||||
|
sendCode(){ |
||||
|
if(!this.loginForm.account){ |
||||
|
uni.showToast({ |
||||
|
title: '请输入账号!', |
||||
|
icon: "none" |
||||
|
}); |
||||
|
return false; |
||||
|
} |
||||
|
let data={ |
||||
|
account:this.loginForm.account, |
||||
|
type:1 |
||||
|
} |
||||
|
this.$api.LoginApi.sendCode(data).then((res)=>{ |
||||
|
uni.showToast({ |
||||
|
title: res.msg, |
||||
|
icon: "none" |
||||
|
}); |
||||
|
}) |
||||
|
}, |
||||
|
register(){ |
||||
|
uni.navigateTo({ |
||||
|
url:"/pages/login/register" |
||||
|
}) |
||||
|
}, |
||||
|
login(){ |
||||
|
if(this.loginForm.rememberMe){ |
||||
|
uni.setStorageSync('LoginAccount',this.loginForm); |
||||
|
}else{ |
||||
|
uni.removeStorageSync('LoginAccount'); |
||||
|
} |
||||
|
if(this.loginForm.account==""){ |
||||
|
uni.showToast({ |
||||
|
title: '请输入账号!', |
||||
|
icon: "none" |
||||
|
}); |
||||
|
return false; |
||||
|
} |
||||
|
if(this.loginForm.password==""){ |
||||
|
uni.showToast({ |
||||
|
title: '请输入密码!', |
||||
|
icon: "none" |
||||
|
}); |
||||
|
return false; |
||||
|
} |
||||
|
let client_id=uni.getStorageSync('client_id'); |
||||
|
if(client_id){ |
||||
|
this.loginForm.client_id=client_id; |
||||
|
} |
||||
|
this.doLogin(this.loginForm); |
||||
|
|
||||
|
const config = uni.getStorageSync('globalConfig') |
||||
|
getSystemInfo.batchInsertOrUpdate(config); |
||||
|
}, |
||||
|
doLogin(data){ |
||||
|
this.$api.LoginApi.login(data).then(res => { |
||||
|
if (res.code == 0) { |
||||
|
uni.setStorageSync('authToken', res.data.authToken) |
||||
|
const parts = res.data.userInfo.avatar.split('/') |
||||
|
let lastPart = parts.pop() || parts.pop() || '' |
||||
|
const isNumber = !isNaN(lastPart)&&!isNaN(parseFloat(lastPart)); |
||||
|
res.data.userInfo.imgname = isNumber ? lastPart+'.png' : lastPart |
||||
|
let userInfo=res.data.userInfo; |
||||
|
// 登录成功后绑定wss |
||||
|
this.socketIo.send({ |
||||
|
type: "bindUid", |
||||
|
user_id: userInfo.user_id, |
||||
|
token:res.data.authToken |
||||
|
}); |
||||
|
loginStore.login(userInfo); |
||||
|
this.getNoticeCount() |
||||
|
uni.reLaunch({ |
||||
|
url: '/pages/index/index' |
||||
|
}) |
||||
|
} |
||||
|
}) |
||||
|
}, |
||||
|
getNoticeCount(){ |
||||
|
this.$api.compaApi.getNoticeCount().then(res => { |
||||
|
msgStore.getCount(res.data.count); |
||||
|
}) |
||||
|
}, |
||||
|
} |
||||
|
} |
||||
|
</script> |
||||
|
|
||||
|
<style scoped> |
||||
|
.login-logo { |
||||
|
width: 180rpx; |
||||
|
height: 180rpx; |
||||
|
font-size: 80rpx; |
||||
|
text-align: center; |
||||
|
line-height: 120rpx; |
||||
|
border-radius: 18rpx; |
||||
|
} |
||||
|
.footer-version{ |
||||
|
width:100%; |
||||
|
text-align: center; |
||||
|
position: fixed; |
||||
|
bottom: 40rpx; |
||||
|
} |
||||
|
.remark-title{ |
||||
|
font-weight: 600; |
||||
|
} |
||||
|
.im-reg-btn{ |
||||
|
padding:30rpx; |
||||
|
} |
||||
|
.im-login-btn{ |
||||
|
padding:0 30rpx; |
||||
|
} |
||||
|
.forget{ |
||||
|
display: flex; |
||||
|
justify-content: space-between; |
||||
|
padding:30rpx; |
||||
|
} |
||||
|
</style> |
||||
@ -0,0 +1,183 @@ |
|||||
|
<template> |
||||
|
<view> |
||||
|
<cu-custom bgColor="bg-gradual-blue" :isBack="true"> |
||||
|
<template #backText></template> |
||||
|
<template #content>账号注册</template> |
||||
|
</cu-custom> |
||||
|
<view style="height:100rpx;"></view> |
||||
|
<view class="padding im-flex im-rows im-justify-content-center mb-10"> |
||||
|
<view class="im-flex im-rows im-justify-content-center"> |
||||
|
<image class="login-logo " :src="globalConfig.sysInfo.logo ?? packData.logo" mode="fixWidth"></image> |
||||
|
|
||||
|
</view> |
||||
|
</view> |
||||
|
<view class="im-flex im-rows im-justify-content-center">{{globalConfig.sysInfo.name ?? packData.name}}</view> |
||||
|
<form> |
||||
|
<view class="cu-form-group margin-top"> |
||||
|
<view class="title">账号</view> |
||||
|
<input :placeholder="placeholder" class="uni-input" maxlength="32" name="input" v-model="regForm.account" @input="handleInput"/> |
||||
|
</view> |
||||
|
<view class="cu-form-group margin-top"> |
||||
|
<view class="title">用户名/昵称</view> |
||||
|
<input placeholder="请输入用户名或昵称" maxlength="32" name="input" v-model="regForm.realname"/> |
||||
|
</view> |
||||
|
<view class="cu-form-group" v-if="parseInt(globalConfig.sysInfo.regauth)"> |
||||
|
<view class="title">验证码</view> |
||||
|
<input placeholder="请输入验证码" maxlength="6" name="input" v-model="regForm.code"/> |
||||
|
<button class='cu-btn bg-blue shadow' @tap="sendCode">发送验证码</button> |
||||
|
</view> |
||||
|
<view class="cu-form-group"> |
||||
|
<view class="title">密码</view> |
||||
|
<input placeholder="请输入密码" maxlength="32" type="password" name="input" v-model="regForm.password"/> |
||||
|
</view> |
||||
|
<view class="cu-form-group"> |
||||
|
<view class="title">重复密码</view> |
||||
|
<input placeholder="请重复输入密码" maxlength="32" type="password" name="input" v-model="regForm.repass"/> |
||||
|
</view> |
||||
|
</form> |
||||
|
<view class="flex flex-direction im-login-btn"> |
||||
|
<button class="cu-btn lg bg-blue" @tap="login()">注册</button> |
||||
|
</view> |
||||
|
<view class="footer-version c-999"> |
||||
|
{{globalConfig.sysInfo.name ?? packData.name}} for {{packData.version}} |
||||
|
</view> |
||||
|
</view> |
||||
|
</template> |
||||
|
|
||||
|
<script> |
||||
|
import { useloginStore } from '@/store/login' |
||||
|
import pinia from '@/store/index' |
||||
|
import packageData from "../../package.json" |
||||
|
const loginStore = useloginStore(pinia) |
||||
|
export default { |
||||
|
data() { |
||||
|
return { |
||||
|
regForm:{ |
||||
|
account:'', |
||||
|
realname:'', |
||||
|
password:'', |
||||
|
repass:'', |
||||
|
code:'' |
||||
|
}, |
||||
|
placeholder:'请输入账号:4-32个字符', |
||||
|
forget:false, |
||||
|
packData:packageData, |
||||
|
globalConfig:loginStore.globalConfig |
||||
|
} |
||||
|
}, |
||||
|
watch:{ |
||||
|
// forget(val){ |
||||
|
// if(val){ |
||||
|
// this.regForm.password='123456'; |
||||
|
// } |
||||
|
// } |
||||
|
}, |
||||
|
mounted() { |
||||
|
let regauth=this.globalConfig.sysInfo.regauth ?? 0; |
||||
|
if(regauth==1){ |
||||
|
this.placeholder='请输入手机号'; |
||||
|
}else if(regauth==2){ |
||||
|
this.placeholder='请输入邮箱账号'; |
||||
|
}else if(regauth==3){ |
||||
|
this.placeholder='请输入手机号/邮箱'; |
||||
|
} |
||||
|
}, |
||||
|
methods: { |
||||
|
handleInput(event) { |
||||
|
let value = event.detail.value; |
||||
|
let filteredValue = value.replace(/[\u4e00-\u9fa5]/g, ''); |
||||
|
this.regForm.account = filteredValue; |
||||
|
}, |
||||
|
sendCode(){ |
||||
|
if(!this.regForm.account){ |
||||
|
uni.showToast({ |
||||
|
title: '请输入账号!', |
||||
|
icon: "none" |
||||
|
}); |
||||
|
return false; |
||||
|
} |
||||
|
let data={ |
||||
|
account:this.regForm.account, |
||||
|
type:2 |
||||
|
} |
||||
|
this.$api.LoginApi.sendCode(data).then((res)=>{ |
||||
|
uni.showToast({ |
||||
|
title: res.msg, |
||||
|
icon: "none" |
||||
|
}); |
||||
|
}) |
||||
|
}, |
||||
|
login(){ |
||||
|
if(this.regForm.account==""){ |
||||
|
uni.showToast({ |
||||
|
title: '请输入账号!', |
||||
|
icon: "none" |
||||
|
}); |
||||
|
return false; |
||||
|
} |
||||
|
if(this.regForm.realname==""){ |
||||
|
uni.showToast({ |
||||
|
title: '请输入用户名或者昵称!', |
||||
|
icon: "none" |
||||
|
}); |
||||
|
return false; |
||||
|
} |
||||
|
if(this.regForm.password=="" || this.regForm.password.length<6 || this.regForm.password.length>16){ |
||||
|
uni.showToast({ |
||||
|
title: '请输入6-16位密码!', |
||||
|
icon: "none" |
||||
|
}); |
||||
|
return false; |
||||
|
} |
||||
|
|
||||
|
if(this.regForm.password!=this.regForm.repass){ |
||||
|
uni.showToast({ |
||||
|
title: '两次密码输入不相同!', |
||||
|
icon: "none" |
||||
|
}); |
||||
|
return false; |
||||
|
} |
||||
|
this.$api.LoginApi.register(this.regForm).then(res => { |
||||
|
if (res.code == 0) { |
||||
|
setTimeout(()=>{ |
||||
|
uni.reLaunch({ |
||||
|
url: '/pages/login/index' |
||||
|
}) |
||||
|
},2000) |
||||
|
} |
||||
|
}) |
||||
|
|
||||
|
}, |
||||
|
} |
||||
|
} |
||||
|
</script> |
||||
|
|
||||
|
<style scoped> |
||||
|
.login-logo { |
||||
|
width: 180rpx; |
||||
|
height: 180rpx; |
||||
|
font-size: 80rpx; |
||||
|
text-align: center; |
||||
|
line-height: 120rpx; |
||||
|
border-radius: 18rpx; |
||||
|
} |
||||
|
.footer-version{ |
||||
|
width:100%; |
||||
|
text-align: center; |
||||
|
position: fixed; |
||||
|
bottom: 40rpx; |
||||
|
} |
||||
|
.remark-title{ |
||||
|
font-weight: 600; |
||||
|
} |
||||
|
.im-reg-btn{ |
||||
|
padding:30rpx; |
||||
|
} |
||||
|
.im-login-btn{ |
||||
|
padding:30rpx; |
||||
|
} |
||||
|
.forget{ |
||||
|
padding:30rpx; |
||||
|
text-align: right; |
||||
|
} |
||||
|
</style> |
||||
@ -0,0 +1,41 @@ |
|||||
|
<template> |
||||
|
<map-Point @commitCheck="commitCheck" :mapKey='mapKey' :Radius='Radius' :showResetting='showResetting' :listIco='listIco' :orientationIco='orientationIco' :resettingIco='resettingIco' :configData='configData'></map-Point> |
||||
|
</template> |
||||
|
|
||||
|
<script> |
||||
|
export default { |
||||
|
data() { |
||||
|
return { |
||||
|
mapKey:'99e0c683598315f86d872c5ea3cf0a7b', |
||||
|
address: '', |
||||
|
latitude: '', |
||||
|
longitude: '', |
||||
|
listIco:'/uni_modules/map-Point/static/item-inx.png', |
||||
|
orientationIco:'/uni_modules/map-Point/static/map-inx.png', |
||||
|
resettingIco:"/uni_modules/map-Point/static/position.png", |
||||
|
showResetting:true, |
||||
|
Radius:'', |
||||
|
// 微信公众号jsSdk配置 |
||||
|
configData:{ |
||||
|
debug:false,// 开启调试模式,调用的所有api的返回值会在客户端alert出来,若要查看传入的参数,可以在pc端打开,参数信息会通过log打出,仅在pc端时才会打印。 |
||||
|
appId:'',// 必填,公众号的唯一标识 |
||||
|
timestamp: '', // 必填,生成签名的时间戳 |
||||
|
nonceStr: '', // 必填,生成签名的随机串 |
||||
|
signature: '', // 必填,签名 |
||||
|
} |
||||
|
}; |
||||
|
}, |
||||
|
methods: { |
||||
|
commitCheck(e) { |
||||
|
console.log(e, 565); |
||||
|
uni.$emit('commitCheck', e); |
||||
|
uni.navigateBack({ |
||||
|
delta: 1 |
||||
|
}); |
||||
|
} |
||||
|
} |
||||
|
}; |
||||
|
</script> |
||||
|
|
||||
|
<style> |
||||
|
</style> |
||||
@ -0,0 +1,262 @@ |
|||||
|
<template> |
||||
|
<view class=""> |
||||
|
<web-view @message="message" :src="html"></web-view> |
||||
|
</view> |
||||
|
</template> |
||||
|
|
||||
|
<script> |
||||
|
import permision from "@/utils/permission.js" |
||||
|
import { storeToRefs } from 'pinia'; |
||||
|
import { useloginStore } from '@/store/login'; |
||||
|
import pinia from '@/store/index' |
||||
|
import config from "@/common/config"; |
||||
|
const userStore = useloginStore(pinia) |
||||
|
const {userInfo} = storeToRefs(userStore); |
||||
|
|
||||
|
export default { |
||||
|
data() { |
||||
|
return { |
||||
|
html: '', |
||||
|
postMsg:null, |
||||
|
webview:null, |
||||
|
Frames: null, |
||||
|
mainFrame: null, |
||||
|
contact:null, |
||||
|
type:false, |
||||
|
wsData:null, |
||||
|
main_id:null, |
||||
|
globalConfig:userStore.globalConfig |
||||
|
} |
||||
|
}, |
||||
|
onReady() { |
||||
|
// #ifdef APP-PLUS |
||||
|
var currentWebview = this.$scope.$getAppWebview() //获取当前页面的webview对象 |
||||
|
var that=this; |
||||
|
setTimeout(function() { |
||||
|
that.webview = currentWebview.children()[0] |
||||
|
}, 600); //如果是页面初始化调用时,需要延时一下 |
||||
|
this.postMsg = this.appsendH5 |
||||
|
// #endif |
||||
|
}, |
||||
|
onLoad: function (option) { |
||||
|
let platform='h5'; |
||||
|
let calling=0; |
||||
|
//#ifdef H5 |
||||
|
platform='h5'; |
||||
|
calling=1; |
||||
|
//#endif |
||||
|
//#ifdef APP-PLUS |
||||
|
platform= 'app'; |
||||
|
// 如果是app需要用户授权 |
||||
|
this.checkAuth(option).then((e)=>{ |
||||
|
// 如果是拨打方拿到权限之后发起通话 |
||||
|
if(option.status==1){ |
||||
|
setTimeout(() => { |
||||
|
this.postMsg({event:'calling'}); |
||||
|
}, 800) |
||||
|
} |
||||
|
}); |
||||
|
//#endif |
||||
|
let stun=encodeURIComponent(JSON.stringify({ |
||||
|
stun:this.globalConfig.chatInfo.stun ?? '', |
||||
|
stunUser:this.globalConfig.chatInfo.stunUser ?? '', |
||||
|
stunPass:this.globalConfig.chatInfo.stunPass ?? '', |
||||
|
})); |
||||
|
this.html='/hybrid/html/index.html?id='+userInfo.value.user_id+'&status='+option.status+'&calling='+calling+'&name='+option.name+'&target_id='+option.id+'&type='+option.type+'&platform='+platform+'&avatar='+option.avatar+'&stun='+stun; |
||||
|
this.main_id=option.msg_id; |
||||
|
this.type= option.type; |
||||
|
this.status = option.status |
||||
|
this.contact={ |
||||
|
id:option.id, |
||||
|
displayName:option.name, |
||||
|
avatar:option.avatar |
||||
|
} |
||||
|
// #ifdef H5 |
||||
|
setTimeout(() => { |
||||
|
this.Frames = document.getElementsByTagName('iframe'); |
||||
|
this.mainFrame = this.Frames[0].contentWindow; |
||||
|
this.postMsg = this.h5sendH5 |
||||
|
}, 500) |
||||
|
|
||||
|
window.onmessage = (e) => { |
||||
|
this.msgCallback(e) |
||||
|
} |
||||
|
//#endif |
||||
|
|
||||
|
uni.$off('webrtcConn'); |
||||
|
// 接收websocket传输过来的消息,并发送给webview |
||||
|
uni.$on('webrtcConn',(e)=>{ |
||||
|
if(e.fromUser.id==userInfo.value.user_id){ |
||||
|
if(e.extends.event=="otherOpt"){ |
||||
|
this.wsData=null; |
||||
|
this.main_id=null; |
||||
|
this.postMsg({event:'hangup'},'*'); |
||||
|
} |
||||
|
return; |
||||
|
} |
||||
|
// 如果msgID不相同,表示在忙线中 |
||||
|
if(this.main_id && this.main_id!=e.id){ |
||||
|
this.$api.msgApi.sendToMsg({ |
||||
|
toContactId:e.fromUser.user_id, |
||||
|
type:e.extends.type, |
||||
|
event:'busy', |
||||
|
status:e.extends.status, |
||||
|
code:907, |
||||
|
id:e.id, |
||||
|
msg_id:e.msg_id, |
||||
|
}) |
||||
|
return; |
||||
|
}else{ |
||||
|
this.wsData=e; |
||||
|
if(this.main_id && this.main_id==e.id){ |
||||
|
this.postMsg(JSON.parse(JSON.stringify(e.extends)),'*'); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
}) |
||||
|
}, |
||||
|
methods: { |
||||
|
async checkAuth(option){ |
||||
|
// #ifdef APP-PLUS |
||||
|
let record=await this.requestPermission('record'); |
||||
|
if(!record){ |
||||
|
return false; |
||||
|
} |
||||
|
if(option.type==1){ |
||||
|
let camera=await this.requestPermission('camera'); |
||||
|
if(!camera){ |
||||
|
return false; |
||||
|
} |
||||
|
} |
||||
|
// #endif |
||||
|
}, |
||||
|
//音视频权限获取 |
||||
|
async requestPermission(auth) { |
||||
|
let isIos=false; |
||||
|
// #ifdef APP-PLUS |
||||
|
isIos = (plus.os.name == "iOS") |
||||
|
// #endif |
||||
|
let andriodAuth=''; |
||||
|
if(auth=='record'){ |
||||
|
andriodAuth='android.permission.RECORD_AUDIO'; |
||||
|
}else if(auth=='camera'){ |
||||
|
andriodAuth='android.permission.CAMERA'; |
||||
|
} |
||||
|
if(isIos){ |
||||
|
// let iosRes = await permision.judgeIosPermission(auth); |
||||
|
return true; |
||||
|
}else{ |
||||
|
let andRes = await permision.requestAndroidPermission(andriodAuth); |
||||
|
return andRes==1 ? true : false; |
||||
|
} |
||||
|
}, |
||||
|
// 接收webview传输过来的消息 |
||||
|
message(event) { |
||||
|
const msg = { |
||||
|
data: event.detail.data[0] |
||||
|
} |
||||
|
this.msgCallback(msg) |
||||
|
}, |
||||
|
// app端向webview传输消息 |
||||
|
appsendH5(params) { |
||||
|
if (params.iceCandidate && params.iceCandidate.length>0){ |
||||
|
params.iceCandidate = JSON.parse(params.iceCandidate) |
||||
|
} |
||||
|
this.webview.evalJS("getUniAppMessage('" + JSON.stringify(params) + "')") |
||||
|
}, |
||||
|
// h5端向webview传输消息 |
||||
|
h5sendH5(params) { |
||||
|
this.mainFrame.postMessage(params, '*') |
||||
|
}, |
||||
|
msgCallback(e){ |
||||
|
let iceCandidate = ''; |
||||
|
let msg_id=''; |
||||
|
if(this.wsData){ |
||||
|
msg_id=this.wsData.msg_id ?? ''; |
||||
|
} |
||||
|
let api=true; |
||||
|
switch(e.data['event']){ |
||||
|
case 'hangup': |
||||
|
this.closeCall(); |
||||
|
if(e.data['code']==907){ |
||||
|
uni.showToast({ |
||||
|
title: '对方忙~~', |
||||
|
icon:'none' |
||||
|
}) |
||||
|
} |
||||
|
if(!e.data.isbtn){ |
||||
|
api=false; |
||||
|
} |
||||
|
break; |
||||
|
case 'iceCandidate': |
||||
|
console.log('监听同步ice') |
||||
|
let niceCandidate = {} |
||||
|
niceCandidate['candidate'] = e.data['iceCandidate']['candidate'] |
||||
|
niceCandidate['sdpMLineIndex'] = e.data['iceCandidate']['sdpMLineIndex'] |
||||
|
niceCandidate['sdpMid'] = e.data['iceCandidate']['sdpMid'] |
||||
|
iceCandidate=JSON.stringify(niceCandidate) |
||||
|
break; |
||||
|
case "mediaDevices": |
||||
|
api=false; |
||||
|
uni.showToast({ |
||||
|
title: '请检查音视频', |
||||
|
icon:'none' |
||||
|
}) |
||||
|
this.closeCall(); |
||||
|
} |
||||
|
if(api){ |
||||
|
this.$api.msgApi.sendToMsg({ |
||||
|
id:this.main_id, |
||||
|
msg_id:msg_id, |
||||
|
toContactId:this.contact.id, |
||||
|
type:this.type, |
||||
|
event:e.data['event'], |
||||
|
status:e.data['status'] ?? '', |
||||
|
code:e.data['code'] ?? '', |
||||
|
callTime:e.data['callTime'] ?? '', |
||||
|
sdp:e.data['sdp'] ?? '', |
||||
|
iceCandidate:iceCandidate, |
||||
|
}).then((res)=>{ |
||||
|
if(e.data['event']=='calling'){ |
||||
|
this.wsData=res.data; |
||||
|
} |
||||
|
if(res.data.extends.code==907){ |
||||
|
uni.showToast({ |
||||
|
title:'对方不在线', |
||||
|
icon:'none' |
||||
|
}) |
||||
|
this.closeCall(); |
||||
|
} |
||||
|
}); |
||||
|
} |
||||
|
|
||||
|
}, |
||||
|
//关闭通话页面 |
||||
|
closeCall(){ |
||||
|
const innerAudioContext = uni.createInnerAudioContext(); |
||||
|
innerAudioContext.autoplay = true; |
||||
|
innerAudioContext.src = config.apiUrl+'/static/voice/guaduan.mp3'; |
||||
|
innerAudioContext.onStop((res) => { |
||||
|
this.$forceUpdate() |
||||
|
}) |
||||
|
let pages = getCurrentPages(); |
||||
|
if (pages.length > 1) { |
||||
|
uni.navigateBack(); |
||||
|
} else { |
||||
|
uni.reLaunch({ |
||||
|
url: '/pages/index/index' |
||||
|
}) |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
} |
||||
|
} |
||||
|
</script> |
||||
|
|
||||
|
<style> |
||||
|
.container { |
||||
|
padding: 20px; |
||||
|
font-size: 14px; |
||||
|
line-height: 24px; |
||||
|
} |
||||
|
</style> |
||||
@ -0,0 +1,965 @@ |
|||||
|
<template> |
||||
|
<view> |
||||
|
<cu-custom bgColor="bg-main-bar" :isBack="true"> |
||||
|
<template #backText></template> |
||||
|
<template #content>聊天信息</template> |
||||
|
</cu-custom> |
||||
|
<view> |
||||
|
<view class="bg-white"> |
||||
|
<view class="user-list im-flex im-justify-content-start im-align-items-center im-a im-wrap" v-if="is_group<2"> |
||||
|
<view class="user-info mt-20" v-for="(item,index) in userList" :key="index" align="center"> |
||||
|
<block v-for="(iteme,indexs) in imglist" :key="indexs" v-if="network_log=='none'"> |
||||
|
<image class="user-avatar" v-if="item.userInfo.imgname === iteme.name" :src="iteme.path" @tap="openChatDetail(item.userInfo)"></image> |
||||
|
</block> |
||||
|
<image v-else class="user-avatar" :src="item.userInfo.avatar" @tap="openChatDetail(item.userInfo)"></image> |
||||
|
|
||||
|
<view class="text-center user-name text-overflow">{{item.userInfo.displayName}}</view> |
||||
|
</view> |
||||
|
<view class="user-info mt-20" v-if="isAuth || is_group==0 || contact.setting.invite=='1'"> |
||||
|
<view class="user-opt radius-8" style='margin:auto' @tap='editUser(2)'> |
||||
|
<view class="icon cuIcon-add f-24"></view> |
||||
|
</view> |
||||
|
<view class="f-11 mt-5">添加成员</view> |
||||
|
</view> |
||||
|
<view class="user-info mt-20" v-if="isAuth"> |
||||
|
<view class="user-opt radius-8" style='margin:auto' @tap='manageUser()'> |
||||
|
<view class="icon cuIcon-move f-24"></view> |
||||
|
</view> |
||||
|
<view class="f-11 mt-5">移除成员</view> |
||||
|
</view> |
||||
|
</view> |
||||
|
<navigator v-if="is_group==1 " class="mt-10" :url="`/pages/message/group/groupUser?group_id=${contact_id}&group_id1=${contact.group_id}`"> |
||||
|
<view class="text-center pb-15 pt-15 im-flex im-justify-content-center im-align-items-center"> |
||||
|
<text class="gui-list-title-text gui-list-one-line gui-primary-color">查看全部群成员</text> |
||||
|
<text class="gui-list-title-desc gui-color-gray">{{groupUserCount}}人</text> |
||||
|
<text class="cuIcon-right"></text> |
||||
|
</view> |
||||
|
</navigator> |
||||
|
|
||||
|
|
||||
|
</view> |
||||
|
<view class="cu-list menu mt-15 bg-white" v-if="is_group==1"> |
||||
|
<view class="cu-item" @click="open"> |
||||
|
<view class="content padding-tb-sm"> |
||||
|
<view> 群聊名称 </view> |
||||
|
</view> |
||||
|
<view class="action"> |
||||
|
<view class="text-grey">{{contact.displayName}} <text class="cuIcon-right"></text></view> |
||||
|
</view> |
||||
|
</view> |
||||
|
<view class="cu-item" @click="openQr" v-if="contact.setting.invite"> |
||||
|
<view class="content padding-tb-sm"> |
||||
|
<view> 群二维码 </view> |
||||
|
</view> |
||||
|
<view class="action"> |
||||
|
<view class="text-grey"><text class="cuIcon-qr_code f-18"></text> <text class="cuIcon-right"></text></view> |
||||
|
</view> |
||||
|
</view> |
||||
|
<view class="cu-item" @tap="openModel('notice')"> |
||||
|
<view class="content padding-tb-sm"> |
||||
|
<view> 群公告 </view> |
||||
|
</view> |
||||
|
<view class="action" style="width:80%"> |
||||
|
<view class="text-grey im-flex im-justify-content-end"> |
||||
|
<view class="text-overflow notice-line"> |
||||
|
<!-- {{contact.notice ?? '暂无公告'}} --> |
||||
|
{{contact.notice==null?'暂无公告':contact.notice}} |
||||
|
</view> |
||||
|
<text class="cuIcon-right"></text> |
||||
|
</view> |
||||
|
</view> |
||||
|
</view> |
||||
|
<view class="cu-item" v-if="isAuth" @tap="openModel('manage')"> |
||||
|
<view class="content padding-tb-sm"> |
||||
|
<view> 群管理 </view> |
||||
|
</view> |
||||
|
<view class="action"> |
||||
|
<view class="text-grey"><text class="cuIcon-right"></text></view> |
||||
|
</view> |
||||
|
</view> |
||||
|
<uni-popup ref="popup" type="dialog"> |
||||
|
<uni-popup-dialog mode="input" :value="contact.displayName" title="修改群名称" :duration="2000" :before-close="true" @close="closePop" @confirm="editGroupName"> |
||||
|
|
||||
|
</uni-popup-dialog> |
||||
|
</uni-popup> |
||||
|
</view> |
||||
|
|
||||
|
<view class="cu-list menu mt-15 bg-white"> |
||||
|
<view class="cu-item"> |
||||
|
<view class="content padding-tb-sm"> |
||||
|
<view> 消息免打扰 </view> |
||||
|
</view> |
||||
|
<view class="action"> |
||||
|
<switch class="switch" @change="setIsNotice" :class="contactUser.is_notice==0?'checked':''" :checked="contactUser.is_notice==0?true:false"></switch> |
||||
|
</view> |
||||
|
<!-- <view class="action" v-else> |
||||
|
<switch class="switch" @change="setIsNotice" :class="!contact.is_notice?'checked':''" :checked="!contact.is_notice?true:false"></switch> |
||||
|
</view> --> |
||||
|
</view> |
||||
|
<view class="cu-item"> |
||||
|
<view class="content padding-tb-sm"> |
||||
|
<view> 置顶聊天 </view> |
||||
|
</view> |
||||
|
<view class="action"> |
||||
|
<switch class="switch" @change="setIsTop" :class="contactUser.is_top?'checked':''" :checked="contactUser.is_top?true:false"></switch> |
||||
|
</view> |
||||
|
<!-- <view class="action" v-else> |
||||
|
<switch class="switch" @change="setIsTop" :class="contact.is_top?'checked':''" :checked="contact.is_top?true:false"></switch> |
||||
|
</view> --> |
||||
|
</view> |
||||
|
<view class="cu-item" v-if="contact.user_id"> |
||||
|
<view class="content padding-tb-sm"> |
||||
|
<view> 加入黑名单 </view> |
||||
|
</view> |
||||
|
<view class="action"> |
||||
|
<switch class="switch" @change="setis_blacklist($event,contact.user_id)" :class="contactUser.is_blacklist?'checked':''" :checked="contactUser.is_blacklist?true:false"></switch> |
||||
|
</view> |
||||
|
</view> |
||||
|
<view class="cu-item menu bg-white" @click="onDeleterecord" v-if="userConfig.chatInfo.messageOneClickDel=='1'"> |
||||
|
<view class="content padding-tb-sm"> |
||||
|
<view> 一键清除聊天记录 </view> |
||||
|
</view> |
||||
|
<view class="action"> |
||||
|
<view class="text-grey"><text class="cuIcon-right"></text></view> |
||||
|
</view> |
||||
|
</view> |
||||
|
<view class="cu-list menu bg-white" @click="showRecord" v-if="userConfig.chatInfo.userMsgClear=='1'"> |
||||
|
<view class="cu-item"> |
||||
|
<view class="content padding-tb-sm"> |
||||
|
<view> 自动清除聊天记录 </view> |
||||
|
</view> |
||||
|
<view class="action"> |
||||
|
<view class="text-grey"><text class="cuIcon-right"></text></view> |
||||
|
</view> |
||||
|
</view> |
||||
|
</view> |
||||
|
</view> |
||||
|
<!-- #ifndef H5 --> |
||||
|
<view class="cu-list menu mt-15 bg-white" @tap="modelName='setBg'"> |
||||
|
<view class="cu-item"> |
||||
|
<view class="content padding-tb-sm"> |
||||
|
<view> 设置当前聊天背景 </view> |
||||
|
</view> |
||||
|
<view class="action"> |
||||
|
<view class="text-grey"><text class="cuIcon-right"></text></view> |
||||
|
</view> |
||||
|
</view> |
||||
|
</view> |
||||
|
<!-- #endif --> |
||||
|
|
||||
|
<navigator class="mt-10" :url="`/pages/message/record?id=${contact_id}`"> |
||||
|
<view class="cu-list menu mt-15 bg-white"> |
||||
|
<view class="cu-item"> |
||||
|
<view class="content padding-tb-sm"> |
||||
|
<view> 查看聊天记录 </view> |
||||
|
</view> |
||||
|
<view class="action"> |
||||
|
<view class="text-grey"><text class="cuIcon-right"></text></view> |
||||
|
</view> |
||||
|
</view> |
||||
|
</view> |
||||
|
</navigator> |
||||
|
<view class="cu-list menu mt-15 bg-white" v-if="is_group==1 && isAdmin" @tap="clearMessage"> |
||||
|
<view class="cu-item text-center delete-btn"> |
||||
|
<text class="c-orange">清空聊天记录</text> |
||||
|
</view> |
||||
|
</view> |
||||
|
|
||||
|
<view class="cu-list menu mt-15 bg-white" v-if="is_group==1" @tap="removeGroup"> |
||||
|
<view class="cu-item text-center delete-btn"> |
||||
|
<text class="c-red">{{isAdmin ? '解散群聊' : '退出群聊' }}</text> |
||||
|
</view> |
||||
|
</view> |
||||
|
<view class="parting-line-20"></view> |
||||
|
<view class="cu-modal bottom-modal" :class="modelName=='notice'&&contact.setting.manage=='0'||modelName=='notice'&&contact.role<3?'show':''"> |
||||
|
<view class="cu-dialog"> |
||||
|
<view class="cu-bar bg-white"> |
||||
|
<view class="action text-gray" @tap="closeModel">取消</view> |
||||
|
<view class="action text-green" @tap="editNotice">保存</view> |
||||
|
</view> |
||||
|
<view class="notice-content"> |
||||
|
<textarea class="im-textarea" maxlength="-1" v-model="contact.notice" placeholder="请输入公告内容..."></textarea> |
||||
|
<!-- :disabled="!isAuth" --> |
||||
|
</view> |
||||
|
</view> |
||||
|
</view> |
||||
|
<view class="cu-modal bottom-modal" :class="modelName=='setBg'?'show':''" @tap="modelName=''"> |
||||
|
<view class="cu-dialog" @tap.stop=''> |
||||
|
<view class="cu-bar"> |
||||
|
<view class="action" >设置当前聊天背景</view> |
||||
|
<view class="action cuIcon-close f-18" @tap="modelName=''"></view> |
||||
|
</view> |
||||
|
<view class="cu-list menu mb-15 bg-white"> |
||||
|
<view class="cu-item" @click="chooseImg()"> |
||||
|
<view class="content padding-tb-sm"> |
||||
|
<view>选取背景图片</view> |
||||
|
</view> |
||||
|
<view class="action"> |
||||
|
<view class="text-grey"><text class="cuIcon-right"></text></view> |
||||
|
</view> |
||||
|
</view> |
||||
|
</view> |
||||
|
<view v-if="bgInfo.image"> |
||||
|
<view><image :src="bgInfo.image" style="width:200px" mode="widthFix"></image></view> |
||||
|
<button class="cu-btn bg-red mt-10" @tap="removeBg">移除背景图片</button> |
||||
|
</view> |
||||
|
<view class="cu-list menu mt-15 mb-15 bg-white"> |
||||
|
<view class="cu-item"> |
||||
|
<view class="content padding-tb-sm"> |
||||
|
<view>背景虚化</view> |
||||
|
</view> |
||||
|
<view class="action"> |
||||
|
<switch class="switch" @change="setFilter" :class="bgInfo.filter?'checked':''" :checked="bgInfo.filter == true ? true :false"></switch> |
||||
|
</view> |
||||
|
</view> |
||||
|
</view> |
||||
|
<uni-notice-bar text="修改后重新进入聊天才能生效" class="mb-15"/> |
||||
|
</view> |
||||
|
</view> |
||||
|
<view class="cu-modal bottom-modal" :class="modelName=='manage'?'show':''"> |
||||
|
<view class="cu-dialog"> |
||||
|
<view class="cu-bar bg-white"> |
||||
|
<view class="action text-gray" @tap="closeModel">取消</view> |
||||
|
<view class="action text-green" @tap="saveManage">保存</view> |
||||
|
</view> |
||||
|
<view class="manage-content"> |
||||
|
<view class="cu-list menu mt-15 bg-white"> |
||||
|
<view class="cu-item"> |
||||
|
<view class="content padding-tb-sm"> |
||||
|
<view>仅群主和群管理员可以管理</view> |
||||
|
<view class="text-gray text-sm"> 启用后,其他成员不能修改群名称,编辑公告等</view> |
||||
|
</view> |
||||
|
<view class="action"> |
||||
|
<switch class="switch" @change="setManage" :class="contact.setting.manage=='1'?'checked':''" :checked="contact.setting.manage=='1'?true:false"></switch> |
||||
|
</view> |
||||
|
</view> |
||||
|
<view class="cu-item"> |
||||
|
<view class="content padding-tb-sm"> |
||||
|
<view>允许群成员邀请</view> |
||||
|
<view class="text-gray text-sm">启用后,其他成员可以邀请其他人加入群聊</view> |
||||
|
</view> |
||||
|
<view class="action"> |
||||
|
<switch class="switch" @change="setInvite" :class="contact.setting.invite=='1'?'checked':''" :checked="contact.setting.invite=='1'?true:false"></switch> |
||||
|
</view> |
||||
|
</view> |
||||
|
<view class="cu-item"> |
||||
|
<view class="content padding-tb-sm"> |
||||
|
<view>允许成员查看历史消息</view> |
||||
|
<view class="text-gray text-sm">启用后,新入群的成员可以查看所有的历史记录</view> |
||||
|
</view> |
||||
|
<view class="action"> |
||||
|
<switch class="switch" @change="setHistory" :class="contact.setting.history=='1'?'checked':''" :checked="contact.setting.history=='1'?true:false"></switch> |
||||
|
</view> |
||||
|
</view> |
||||
|
<view class="cu-item"> |
||||
|
<view class="content padding-tb-sm"> |
||||
|
<view>允许添加群成员为好友</view> |
||||
|
<view class="text-gray text-sm">启用后,成员可以互相查看资料并添加为好友或发消息</view> |
||||
|
</view> |
||||
|
<view class="action"> |
||||
|
<switch class="switch" @change="setProfile" :class="contact.setting.profile=='1'?'checked':''" :checked="contact.setting.profile=='1'?true:false"></switch> |
||||
|
</view> |
||||
|
</view> |
||||
|
<uni-section title="群聊禁言" type="line"> |
||||
|
<radio-group class="block" @change="setSpeak"> |
||||
|
<view class="cu-form-group" v-for="(item,indexs) in radioList" :key="indexs"> |
||||
|
<view class="title">{{item.label}}</view> |
||||
|
<radio :class="contact.setting.nospeak==item.value?'checked':''" :checked="contact.setting.nospeak==item.value?true:false" :value="item.value.toString()"></radio> |
||||
|
</view> |
||||
|
</radio-group> |
||||
|
</uni-section> |
||||
|
</view> |
||||
|
</view> |
||||
|
</view> |
||||
|
</view> |
||||
|
|
||||
|
<view class="cu-modal bottom-modal" :class="bolRecord=='manage'?'show':''"> |
||||
|
<view class="cu-dialog"> |
||||
|
<view class="cu-bar bg-white"> |
||||
|
<view class="action text-gray" @tap="RecordModel">取消</view> |
||||
|
<view class="action text-green" @tap="RecordManage">保存</view> |
||||
|
</view> |
||||
|
<view class="manage-content"> |
||||
|
<radio-group class="block" @change="setuserMsgClear"> |
||||
|
<label class="cu-form-group" v-for="(tag,tagindexs) in userConfig.chatInfo.userMsgClearDay" :key="tagindexs"> |
||||
|
<view class="title">{{tag.title}}</view> |
||||
|
<radio :checked="MsgClear==tag.value?true:false" :value="tag.value"></radio> |
||||
|
<!-- :class="MsgClear==tag.title?'checked':''" --> |
||||
|
</label> |
||||
|
</radio-group> |
||||
|
</view> |
||||
|
</view> |
||||
|
</view> |
||||
|
</view> |
||||
|
</view> |
||||
|
</template> |
||||
|
|
||||
|
<script> |
||||
|
import { useMsgStore } from '@/store/message'; |
||||
|
// #ifdef APP-PLUS |
||||
|
import groupUserList from '@/service/groupUserList'; |
||||
|
import {getSavedImages1} from '@/utils/LocalFileSystemURL.js' |
||||
|
import getMessageList from '@/service/getMessageList'; |
||||
|
// #endif |
||||
|
import pinia from '@/store/index'; |
||||
|
import { storeToRefs } from 'pinia'; |
||||
|
import { useloginStore } from '@/store/login'; |
||||
|
const userStore = useloginStore(pinia) |
||||
|
const msgStore = useMsgStore(pinia) |
||||
|
const {network_log} = storeToRefs(msgStore); |
||||
|
export default { |
||||
|
components: { |
||||
|
}, |
||||
|
data() { |
||||
|
return { |
||||
|
pageLoading: true, |
||||
|
contact_id: null, //聊天id, |
||||
|
is_group:0, |
||||
|
groupUserCount:0, |
||||
|
modelName:false, |
||||
|
userList: [], //群成员 |
||||
|
allUser:[], |
||||
|
userInfo:userStore.userInfo, |
||||
|
userConfig:userStore.globalConfig, |
||||
|
groupInfo:uni.getStorageSync('setgroupInfo'), |
||||
|
groupInfo1:uni.getStorageSync('setgroupInfo'), |
||||
|
chatRecordlist: [{ |
||||
|
text: '文本', |
||||
|
icon: "icon-wenben", |
||||
|
type: 'text' |
||||
|
|
||||
|
}, |
||||
|
{ |
||||
|
text: '图片', |
||||
|
icon: "icon-zhaopian", |
||||
|
type: 'image' |
||||
|
|
||||
|
}, { |
||||
|
text: '文件', |
||||
|
icon: "icon-wenjian", |
||||
|
type: 'file' |
||||
|
|
||||
|
}, { |
||||
|
text: '视频', |
||||
|
icon: "icon-shipin", |
||||
|
type: 'video' |
||||
|
|
||||
|
}, { |
||||
|
text: '项目', |
||||
|
icon: "icon-xiangmu_2", |
||||
|
type: 'project' |
||||
|
|
||||
|
}, { |
||||
|
text: '客户', |
||||
|
icon: "icon-kehu", |
||||
|
type: 'leads' |
||||
|
|
||||
|
}, |
||||
|
], |
||||
|
radioList: [{ |
||||
|
label: "关闭", |
||||
|
value: 0 |
||||
|
}, |
||||
|
{ |
||||
|
label: "仅管理员可发言", |
||||
|
value: 1 |
||||
|
}, |
||||
|
{ |
||||
|
label: "仅群主可发言", |
||||
|
value: 2 |
||||
|
}, |
||||
|
], |
||||
|
isAuth: false, //判断自己是否时群管理或者群主 |
||||
|
contact: null, //联系人相关信息 |
||||
|
isAdmin:false, //如果为真,自己就是群主 |
||||
|
isManage: false, // 如果为真,自己就是管理 |
||||
|
user_ids: [], |
||||
|
user:[],//全部群成员 |
||||
|
bgInfo:{ |
||||
|
image:'', |
||||
|
filter:false |
||||
|
}, |
||||
|
imglist:[], |
||||
|
network_log:network_log, |
||||
|
contactUser:{}, |
||||
|
bolRecord:false, |
||||
|
MsgClear:0, |
||||
|
MsgClearDay:0 |
||||
|
} |
||||
|
}, |
||||
|
onShow() { |
||||
|
const _this = this |
||||
|
if(this.network_log == 'none'){ |
||||
|
_this.Getchatinformation() |
||||
|
_this.getImagePath() |
||||
|
}else{ |
||||
|
_this.getUserlist() |
||||
|
// _this.Getchatinformation() |
||||
|
// _this.getImagePath() |
||||
|
} |
||||
|
}, |
||||
|
onLoad: function(options) { |
||||
|
let bgInfo=uni.getStorageSync('chat-bg-info'+options.id); |
||||
|
if(bgInfo){ |
||||
|
this.bgInfo=bgInfo; |
||||
|
} |
||||
|
this.is_group = options.is_group; |
||||
|
this.contact_id = options.id; |
||||
|
let contact=msgStore.getContact(this.contact_id); |
||||
|
if(!contact){ |
||||
|
uni.showToast({ |
||||
|
title:'联系人不存在', |
||||
|
icon:'none', |
||||
|
duration:1500, |
||||
|
complete:(res)=>{ |
||||
|
uni.reLaunch({ |
||||
|
url: '/pages/index/index' |
||||
|
}) |
||||
|
} |
||||
|
}) |
||||
|
return; |
||||
|
} |
||||
|
|
||||
|
if(options.notice!=='undefined'){ |
||||
|
this.contact=contact; |
||||
|
this.contact.notice = uni.getStorageSync('notice'); |
||||
|
}else{ |
||||
|
this.contact=contact; |
||||
|
} |
||||
|
|
||||
|
if(this.is_group==0){ |
||||
|
contact.userInfo={ |
||||
|
id:contact.user_id, |
||||
|
account:contact.account, |
||||
|
displayName:contact.displayName, |
||||
|
avatar:contact.avatar |
||||
|
} |
||||
|
this.allUser.push(contact); |
||||
|
this.userList.push(contact); |
||||
|
} |
||||
|
if(this.is_group==1){ |
||||
|
this.MsgClear = this.groupInfo.clear_msg_day; |
||||
|
}else{ |
||||
|
this.getFriendInfo() |
||||
|
} |
||||
|
}, |
||||
|
methods: { |
||||
|
getFriendInfo(){ |
||||
|
this.$api.msgApi.getFriendInfo({friend_user_id:this.contact_id}).then(res => { |
||||
|
this.contactUser = res.data; |
||||
|
this.MsgClearDay = this.contactUser.clear_msg_day; |
||||
|
this.MsgClear = this.contactUser.clear_msg_day; |
||||
|
}) |
||||
|
}, |
||||
|
showRecord(){ |
||||
|
this.bolRecord = 'manage'; |
||||
|
}, |
||||
|
RecordModel(){ |
||||
|
if(this.is_group==1){ |
||||
|
this.MsgClear = this.groupInfo.clear_msg_day; |
||||
|
}else{ |
||||
|
this.MsgClear = this.contactUser.clear_msg_day; |
||||
|
} |
||||
|
this.bolRecord = false; |
||||
|
}, |
||||
|
RecordManage(){ |
||||
|
if(this.is_group==1){ |
||||
|
this.$api.msgApi.groupClearMsgDay({id: this.contact.id,day: this.MsgClearDay}).then(res=>{ |
||||
|
this.groupInfo.clear_msg_day = this.MsgClearDay |
||||
|
msgStore.checkMsg(res.data); |
||||
|
msgStore.appendMsg(res.data); |
||||
|
uni.setStorageSync('setgroupInfo',this.groupInfo); |
||||
|
this.deleteList(res.data) |
||||
|
}) |
||||
|
}else{ |
||||
|
this.$api.msgApi.friendClearMsgDay({to_user: this.contact.id,day: this.MsgClearDay}).then(res=>{ |
||||
|
this.getFriendInfo() |
||||
|
this.deleteList(res.data) |
||||
|
msgStore.checkMsg(res.data); |
||||
|
msgStore.appendMsg(res.data); |
||||
|
}) |
||||
|
} |
||||
|
this.bolRecord = false; |
||||
|
}, |
||||
|
async deleteList(data){ |
||||
|
await getMessageList.deleteList(data) |
||||
|
}, |
||||
|
async deleteallList1(data){ |
||||
|
await getMessageList.deleteallList1(data) |
||||
|
}, |
||||
|
openModel(model){ |
||||
|
this.modelName=model; |
||||
|
}, |
||||
|
closeModel(){ |
||||
|
this.modelName=false; |
||||
|
}, |
||||
|
saveManage(){ |
||||
|
if(!this.isAuth) return; |
||||
|
this.$api.msgApi.groupSetting({ |
||||
|
id: this.contact.id, |
||||
|
setting: this.contact.setting |
||||
|
}) |
||||
|
this.modelName=false; |
||||
|
}, |
||||
|
setManage(e){ |
||||
|
this.contact.setting.manage=e.detail.value ? '1' : '0'; |
||||
|
}, |
||||
|
setInvite(e){ |
||||
|
this.contact.setting.invite=e.detail.value ? '1' : '0'; |
||||
|
}, |
||||
|
setHistory(e){ |
||||
|
this.contact.setting.history=e.detail.value ? '1' : '0'; |
||||
|
}, |
||||
|
setProfile(e){ |
||||
|
this.contact.setting.profile=e.detail.value ? '1' : '0'; |
||||
|
}, |
||||
|
setSpeak(e){ |
||||
|
this.contact.setting.nospeak=e.detail.value; |
||||
|
}, |
||||
|
setuserMsgClear(e){ |
||||
|
this.MsgClear = e.detail.value; |
||||
|
// const val = this.userConfig.chatInfo.userMsgClearDay.filter(item=>item.value==e.detail.value) |
||||
|
this.MsgClearDay = e.detail.value; |
||||
|
}, |
||||
|
setIsNotice(e){ |
||||
|
// if(this.contact.user_id){ |
||||
|
console.log(e.detail.value); |
||||
|
this.contactUser.is_notice=e.detail.value ? 0 : 1; |
||||
|
this.contact.is_notice=e.detail.value ? 0 : 1; |
||||
|
// }else{ |
||||
|
// this.contact.is_notice=e.detail.value ? 0 : 1; |
||||
|
// } |
||||
|
|
||||
|
this.$api.msgApi.isNoticeAPI({ |
||||
|
id: this.contact.id, |
||||
|
is_group:this.contact.is_group, |
||||
|
is_notice:this.contact.is_notice |
||||
|
}) |
||||
|
}, |
||||
|
setIsTop(e){ |
||||
|
// if(this.contact.user_id){ |
||||
|
this.contactUser.is_top=e.detail.value ? 1 : 0; |
||||
|
this.contact.is_top=e.detail.value ? 1 : 0; |
||||
|
// }else{ |
||||
|
// this.contact.is_top=e.detail.value ? 1 : 0; |
||||
|
// } |
||||
|
|
||||
|
this.$api.msgApi.setChatTopAPI({ |
||||
|
id: this.contact.id, |
||||
|
is_group:this.contact.is_group, |
||||
|
is_top:this.contact.is_top |
||||
|
}) |
||||
|
}, |
||||
|
setis_blacklist(e,id){ |
||||
|
this.contactUser.is_blacklist=e.detail.value ? 1 : 0; |
||||
|
this.$api.msgApi.isBlacklist({friend_user_id:id}).then( res =>{ |
||||
|
if(res.code==400){ |
||||
|
this.contactUser.is_blacklist = 0 |
||||
|
} |
||||
|
}) |
||||
|
}, |
||||
|
editNotice(){ |
||||
|
// if(!this.isAuth) return; |
||||
|
this.$api.msgApi.setNotice({ |
||||
|
id: this.contact.id, |
||||
|
notice: this.contact.notice |
||||
|
}) |
||||
|
this.modelName=false; |
||||
|
}, |
||||
|
open() { |
||||
|
this.$refs.popup.open() |
||||
|
}, |
||||
|
openQr() { |
||||
|
uni.navigateTo({ |
||||
|
url: '/pages/index/qrcode?group_id='+ this.contact.id |
||||
|
}) |
||||
|
}, |
||||
|
editGroupName(e){ |
||||
|
this.$api.msgApi.editGroupName({id:this.contact.id,displayName:e}).then( res =>{ |
||||
|
if(res.code!==400){ |
||||
|
this.contact.displayName=e; |
||||
|
} |
||||
|
this.$refs.popup.close() |
||||
|
}) |
||||
|
}, |
||||
|
closePop(){ |
||||
|
this.$refs.popup.close() |
||||
|
}, |
||||
|
//移除群聊 |
||||
|
removeGroup() { |
||||
|
// 如果是群主就解散群聊,否则就退出群聊 |
||||
|
let txt="退出群聊"; |
||||
|
if(this.isAdmin) txt="解散群聊"; |
||||
|
uni.showModal({ |
||||
|
title: '确定要'+txt+'吗?', |
||||
|
success: e => { |
||||
|
if (e.confirm) { |
||||
|
if(this.isAdmin){ |
||||
|
this.$api.msgApi.removeGroup({id:this.contact.id}).then((res)=>{ |
||||
|
// 删除之后返回首页 |
||||
|
uni.reLaunch({ |
||||
|
url: '/pages/index/index' |
||||
|
}) |
||||
|
}) |
||||
|
}else{ |
||||
|
this.$api.msgApi.removeUser({id:this.contact.id,user_id:this.userInfo.user_id}).then((res)=>{ |
||||
|
// 删除之后返回首页 |
||||
|
uni.reLaunch({ |
||||
|
url: '/pages/index/index' |
||||
|
}) |
||||
|
}) |
||||
|
} |
||||
|
|
||||
|
} |
||||
|
} |
||||
|
}); |
||||
|
|
||||
|
}, |
||||
|
clearMessage() { |
||||
|
// 如果是群主就解散群聊,否则就退出群聊 |
||||
|
if(!this.isAdmin) { |
||||
|
uni.showToast({ |
||||
|
title:'无权操作', |
||||
|
icon:'none' |
||||
|
}) |
||||
|
}; |
||||
|
uni.showModal({ |
||||
|
title: '删除消息会从当前聊天记录中被删除,确定继续吗?', |
||||
|
success: e => { |
||||
|
if (e.confirm) { |
||||
|
this.$api.msgApi.clearMessage({id:this.contact.id}).then((res)=>{ |
||||
|
uni.showToast({ |
||||
|
title:'清除成功', |
||||
|
icon:'none' |
||||
|
}) |
||||
|
}) |
||||
|
} |
||||
|
} |
||||
|
}); |
||||
|
|
||||
|
}, |
||||
|
// 添加群成员 |
||||
|
editUser(type) { |
||||
|
this.user_ids = this.allUser.map(item => item.user_id) |
||||
|
if(this.contact.is_group==0){ |
||||
|
type=1 |
||||
|
} |
||||
|
uni.navigateTo({ |
||||
|
url: '/pages/index/userSelection?type='+type+'&contact_id=' + this.contact.id |
||||
|
}) |
||||
|
}, |
||||
|
// 管理群成员 |
||||
|
manageUser() { |
||||
|
uni.navigateTo({ |
||||
|
url: '/pages/message/group/groupUser?group_id=' + this.contact.id |
||||
|
}) |
||||
|
}, |
||||
|
// 跳转到聊天记录 |
||||
|
goChatRecord(type) { |
||||
|
uni.navigateTo({ |
||||
|
url: '/package/message/pages/chatRecord/chatRecord?type=' + type + '&toContactId=' + this.contact_id + '&is_group=1' |
||||
|
}) |
||||
|
}, |
||||
|
// 获取群成员列表 |
||||
|
getUserlist() { |
||||
|
if(this.is_group==0) return; |
||||
|
this.userList = [] |
||||
|
this.$api.msgApi.groupUserList({ |
||||
|
group_id: this.contact_id, |
||||
|
limit:20000, |
||||
|
}).then(res => { |
||||
|
this.user = res.data |
||||
|
this.Insertchatmessage(res.data) |
||||
|
res.data.forEach(res => { |
||||
|
// #ifdef APP-PLUS |
||||
|
uni.downloadFile({ url: res.userInfo.avatar,success: (downloadResult) => { |
||||
|
this.saveToPermanentStorage(downloadResult.tempFilePath); |
||||
|
}}) |
||||
|
// #endif |
||||
|
}) |
||||
|
if (res.code !== 0) return |
||||
|
// 判断自己是否为群主 |
||||
|
const admin=res.data.filter(item => item.role == 1 && item.userInfo.id== this.userInfo.user_id) |
||||
|
if(admin.length) this.isAdmin=true; |
||||
|
// 判断自己是否是群管理 |
||||
|
const manage=res.data.filter(item => item.role == 2 && item.userInfo.id== this.userInfo.user_id) |
||||
|
if(manage.length) this.manage=true; |
||||
|
// 判断是否有管理权限 |
||||
|
if(admin.length || manage.length) this.isAuth=true; |
||||
|
this.allUser=JSON.parse(JSON.stringify(res.data)); |
||||
|
if (res.data.length > 18) { |
||||
|
if (this.isAuth) { |
||||
|
// this.userList = res.data.splice(0, 18) |
||||
|
this.userList = res.data.splice(0, 20000) |
||||
|
}else if(this.contact.setting.invite){ |
||||
|
// this.userList = res.data.splice(0, 19) |
||||
|
this.userList = res.data.splice(0, 20000) |
||||
|
} else { |
||||
|
// this.userList = res.data.splice(0, 20) |
||||
|
this.userList = res.data.splice(0, 20000) |
||||
|
} |
||||
|
} else { |
||||
|
this.userList = res.data |
||||
|
} |
||||
|
this.groupUserCount=res.count; |
||||
|
this.pageLoading = false; |
||||
|
}) |
||||
|
}, |
||||
|
async Getchatinformation(){ |
||||
|
// #ifdef APP-PLUS |
||||
|
const groups = await groupUserList.getList({group_id: this.contact.group_id}); |
||||
|
groups.map((res)=>{ |
||||
|
res.userInfo = JSON.parse(res.userInfo) |
||||
|
}) |
||||
|
this.user = groups |
||||
|
|
||||
|
if (groups.length == 0) return |
||||
|
// 判断自己是否为群主 |
||||
|
const admin=groups.filter(item => item.role == 1 && item.userInfo.id== this.userInfo.user_id) |
||||
|
if(admin.length) this.isAdmin=true; |
||||
|
// 判断自己是否是群管理 |
||||
|
const manage=groups.filter(item => item.role == 2 && item.userInfo.id== this.userInfo.user_id) |
||||
|
if(manage.length) this.manage=true; |
||||
|
// 判断是否有管理权限 |
||||
|
if(admin.length || manage.length) this.isAuth=true; |
||||
|
this.allUser=JSON.parse(JSON.stringify(groups)); |
||||
|
|
||||
|
this.groupUserCount=groups.length; |
||||
|
this.userList = groups |
||||
|
this.pageLoading = false; |
||||
|
|
||||
|
console.info('获取聊天信息数据',groups.length,groups); |
||||
|
// #endif |
||||
|
}, |
||||
|
async Insertchatmessage(val){ |
||||
|
// #ifdef APP-PLUS |
||||
|
val.forEach((item)=>{ |
||||
|
const parts = item.userInfo.avatar.split('/') |
||||
|
let lastPart = parts.pop() || parts.pop() || '' |
||||
|
const isNumber = !isNaN(lastPart)&&!isNaN(parseFloat(lastPart)); |
||||
|
item.userInfo.imgname = isNumber ? lastPart+'.png' : lastPart; |
||||
|
}) |
||||
|
// console.log('插入聊天信息数据',val); |
||||
|
await groupUserList.batchInsertOrUpdate(val); |
||||
|
// #endif |
||||
|
}, |
||||
|
|
||||
|
// App端持久化存储实现 |
||||
|
saveToPermanentStorage(tempPath) { |
||||
|
return new Promise((resolve, reject) => { |
||||
|
// 获取应用文档目录(持久化存储) |
||||
|
plus.io.resolveLocalFileSystemURL( |
||||
|
'_doc', |
||||
|
(docDir) => { |
||||
|
// 创建目标路径 |
||||
|
docDir.getDirectory( |
||||
|
'img1', |
||||
|
{ create: true, exclusive: false }, |
||||
|
(entry) => { |
||||
|
// 从临时路径获取文件名 |
||||
|
const fileName = this.getFileName(tempPath); |
||||
|
const fileName1 = this.getFileName(docDir.fullPath + 'img1/' +fileName); |
||||
|
// 新增:检查文件是否存在 |
||||
|
entry.getFile(fileName1,{ create: false }, // 不创建新文件 |
||||
|
(fileEntry) => { |
||||
|
// console.log('文件已存在,拒绝操作'); |
||||
|
// 文件已存在,拒绝操作 |
||||
|
reject(new Error('File already exists: ' + fileName)); |
||||
|
}, |
||||
|
(error) => { |
||||
|
// console.log(error); |
||||
|
// 文件不存在(或发生其他错误),继续复制操作 |
||||
|
if (error.code === 14) { // 1表示文件不存在(不同平台错误码可能不同) |
||||
|
this.copyFile(tempPath, entry, fileName, resolve, reject); |
||||
|
} else { |
||||
|
reject(error); |
||||
|
} |
||||
|
} |
||||
|
); |
||||
|
}, |
||||
|
(error) => { |
||||
|
reject(error); |
||||
|
} |
||||
|
); |
||||
|
}, |
||||
|
(error) => { |
||||
|
reject(error); |
||||
|
} |
||||
|
); |
||||
|
}); |
||||
|
}, |
||||
|
|
||||
|
// 提取复制逻辑为独立方法 |
||||
|
copyFile(tempPath, targetDir, fileName, resolve, reject) { |
||||
|
plus.io.resolveLocalFileSystemURL( |
||||
|
tempPath, |
||||
|
(tempEntry) => { |
||||
|
tempEntry.copyTo( |
||||
|
targetDir, |
||||
|
fileName, |
||||
|
(newEntry) => { |
||||
|
resolve(newEntry.toLocalURL()); |
||||
|
}, |
||||
|
(error) => { |
||||
|
reject(error); |
||||
|
} |
||||
|
); |
||||
|
}, |
||||
|
(error) => { |
||||
|
reject(error); |
||||
|
} |
||||
|
); |
||||
|
}, |
||||
|
|
||||
|
// 获取文件名工具方法 |
||||
|
getFileName(path) { |
||||
|
const index = path.lastIndexOf('/'); |
||||
|
let fileName = path.substr(index + 1); |
||||
|
fileName = fileName.replace(/\(\d+\)(?=\.[^./]+$)/, ''); |
||||
|
return fileName; |
||||
|
}, |
||||
|
// 获取图片地址 |
||||
|
async getImagePath(){ |
||||
|
this.imglist = await getSavedImages1() |
||||
|
this.imglist.map(item => { |
||||
|
item.path = plus.io.convertLocalFileSystemURL(item.path) |
||||
|
}); |
||||
|
console.info('读取地址',this.imglist); |
||||
|
}, |
||||
|
|
||||
|
// 打开联系人详情 |
||||
|
openChatDetail(item){ |
||||
|
if(this.userInfo.user_id==item.id) return; |
||||
|
let friend=msgStore.getContact(item.id); |
||||
|
// this.contact_id |
||||
|
if(this.contact.role<3 || this.contact.setting.profile=='1' || friend){ |
||||
|
uni.navigateTo({ |
||||
|
url:"/pages/contacts/detail?id="+item.id |
||||
|
}) |
||||
|
}else{ |
||||
|
uni.showToast({ |
||||
|
title:'已开启用户隐私!', |
||||
|
icon:'none' |
||||
|
}) |
||||
|
return false; |
||||
|
} |
||||
|
}, |
||||
|
setFilter(e){ |
||||
|
this.bgInfo.filter=e.detail.value ? true : false; |
||||
|
uni.setStorageSync('chat-bg-info'+this.contact.id,this.bgInfo) |
||||
|
}, |
||||
|
chooseImg(){ |
||||
|
uni.chooseImage({ |
||||
|
count : 1, |
||||
|
sizeType : ['compressed'], |
||||
|
sourceType : ['album', 'camera'], |
||||
|
success : (res)=>{ |
||||
|
const tempFiles = res.tempFiles; |
||||
|
tempFiles.forEach((item) => { |
||||
|
uni.saveFile({ |
||||
|
tempFilePath:item.path, |
||||
|
success:(res)=>{ |
||||
|
this.bgInfo.image=res.savedFilePath; |
||||
|
uni.setStorageSync('chat-bg-info'+this.contact.id,this.bgInfo) |
||||
|
uni.showToast({ |
||||
|
title:'设置成功,重新进入聊天后生效', |
||||
|
icon:'none' |
||||
|
}) |
||||
|
} |
||||
|
}) |
||||
|
}) |
||||
|
} |
||||
|
}); |
||||
|
}, |
||||
|
removeBg(){ |
||||
|
this.bgInfo.image=''; |
||||
|
uni.setStorageSync('chat-bg-info'+this.contact.id,'') |
||||
|
}, |
||||
|
onDeleterecord(){ |
||||
|
const removeval = {group_id:this.contact.is_group==1?this.contact.id:'',form_user:this.userInfo.user_id,to_user:this.contact.id} |
||||
|
uni.showModal({ |
||||
|
title: '提示', |
||||
|
content: `是否要删除${this.contact.is_group==1?'群聊':'个人'}消息,需注意点击后所有人的聊天记录会消失`, |
||||
|
success:(res)=>{ |
||||
|
if (res.confirm) { |
||||
|
if(this.contact.is_group==1){ |
||||
|
this.$api.msgApi.groupremoveAllMessage({id: this.contact.id}).then(res => { |
||||
|
if(res.code==0){ |
||||
|
uni.showToast({ |
||||
|
title:'删除成功', |
||||
|
icon:'none' |
||||
|
}) |
||||
|
this.deleteallList1(removeval) |
||||
|
} |
||||
|
}) |
||||
|
}else{ |
||||
|
this.$api.msgApi.friendremoveAllMessage({to_user: this.contact.id}).then(res => { |
||||
|
if(res.code==0){ |
||||
|
uni.showToast({ |
||||
|
title:'删除成功', |
||||
|
icon:'none' |
||||
|
}) |
||||
|
this.deleteallList1(removeval) |
||||
|
} |
||||
|
}) |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
}); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
</script> |
||||
|
|
||||
|
<style lang="scss" scoped> |
||||
|
.user-list{ |
||||
|
padding:0 20rpx 20rpx; |
||||
|
.user-info{ |
||||
|
width:142rpx; |
||||
|
height:130rpx; |
||||
|
text-align:center; |
||||
|
.user-avatar{ |
||||
|
width:100rpx; |
||||
|
height:100rpx; |
||||
|
border-radius: 16rpx; |
||||
|
} |
||||
|
.user-name{ |
||||
|
width:100rpx; |
||||
|
margin:0 auto; |
||||
|
font-size: 22rpx; |
||||
|
} |
||||
|
.user-opt{ |
||||
|
border:1px dashed #999; |
||||
|
height:98rpx; |
||||
|
width:98rpx; |
||||
|
display: flex; |
||||
|
align-items: center; |
||||
|
justify-content: center; |
||||
|
.icon{ |
||||
|
color:#999 !important; |
||||
|
} |
||||
|
|
||||
|
} |
||||
|
} |
||||
|
|
||||
|
} |
||||
|
|
||||
|
.delete-btn{ |
||||
|
justify-content:center !important ; |
||||
|
} |
||||
|
|
||||
|
.notice-content{ |
||||
|
width:100%; |
||||
|
min-height:480rpx; |
||||
|
.im-textarea{ |
||||
|
width:100%; |
||||
|
min-height:480rpx; |
||||
|
padding:20rpx; |
||||
|
text-align:left; |
||||
|
} |
||||
|
} |
||||
|
.notice-line{ |
||||
|
width:70%; |
||||
|
text-align: right; |
||||
|
} |
||||
|
</style> |
||||
@ -0,0 +1,225 @@ |
|||||
|
<template> |
||||
|
<cu-custom :isBack="true" bgColor="bg-white"> |
||||
|
<template #content>表情管理({{emojiList.length}})</template> |
||||
|
<template #right> |
||||
|
<view class="ml-10 mr-10" @tap="manage()"> |
||||
|
{{manageName}} |
||||
|
</view> |
||||
|
</template> |
||||
|
</cu-custom> |
||||
|
<view> |
||||
|
<scroll-view scroll-y class="bg-white" style="padding-bottom:100rpx"> |
||||
|
<uni-grid :column="5" :highlight="true" @change="change" style="padding:10rpx"> |
||||
|
<uni-grid-item> |
||||
|
<view class="grid-item-box" :index="0"> |
||||
|
<view class="upload-emoji" @tap="uploadEmoji"><text class="cuIcon-add c-999" style="vertical-align: sub;"></text></view> |
||||
|
</view> |
||||
|
</uni-grid-item> |
||||
|
<uni-grid-item v-for="(item, index) in emojiList" :index="index+1" :key="index"> |
||||
|
<view class="grid-item-box"> |
||||
|
<image :src="item.src" style="width:100rpx;height:100rpx" :fade-show="false" mode="aspectFit" lazy-load></image> |
||||
|
<view class="emoji-check-box" v-if="isManage" :class="item.isCheck ? 'text-green cuIcon-roundcheckfill' : 'cuIcon-round'"></view> |
||||
|
</view> |
||||
|
</uni-grid-item> |
||||
|
</uni-grid> |
||||
|
</scroll-view> |
||||
|
<view class="btn-opt bg-white im-flex im-align-items-center im-space-between pd-10" v-if="checkList.length>0"> |
||||
|
<button class="cu-btn bg-green" @tap="moveEmoji()">移动到最前面</button> |
||||
|
<button class="cu-btn bg-red" @tap="delEmoji()">删除({{checkList.length}})</button> |
||||
|
</view> |
||||
|
</view> |
||||
|
</template> |
||||
|
|
||||
|
<script> |
||||
|
export default { |
||||
|
data() { |
||||
|
return { |
||||
|
emojiList:[], |
||||
|
isManage:false, |
||||
|
manageName:'整理', |
||||
|
checkList:[] |
||||
|
} |
||||
|
}, |
||||
|
onLoad(option){ |
||||
|
this.url=decodeURI(option.src); |
||||
|
this.name=option.name; |
||||
|
}, |
||||
|
mounted(){ |
||||
|
this.getEmojiList(); |
||||
|
}, |
||||
|
methods: { |
||||
|
getEmojiList(){ |
||||
|
this.$api.emojiApi.emojiList({}).then((res)=>{ |
||||
|
if(res.code==0){ |
||||
|
this.emojiList=res.data; |
||||
|
} |
||||
|
}) |
||||
|
}, |
||||
|
// 上传表情 |
||||
|
uploadEmoji(){ |
||||
|
uni.chooseImage({ |
||||
|
count : 9, |
||||
|
sizeType : ['compressed'], |
||||
|
sourceType : ['album', 'camera'], |
||||
|
success : (res)=>{ |
||||
|
const tempFiles = res.tempFiles; |
||||
|
tempFiles.forEach((item) => { |
||||
|
if(item.size>2*1024*1024){ |
||||
|
return uni.showToast({ |
||||
|
title: '表情大小不能超过2MB', |
||||
|
icon:'error' |
||||
|
}) |
||||
|
} |
||||
|
uni.uploadFile({ |
||||
|
url: this.$api.emojiApi.uploadEmoji, |
||||
|
filePath: item.path, |
||||
|
name: 'file', |
||||
|
header: { |
||||
|
'Authorization': uni.getStorageSync('authToken'), |
||||
|
}, |
||||
|
success: (e) => { |
||||
|
let res=JSON.parse(e.data); |
||||
|
if(res.code==0){ |
||||
|
this.updateEmoji(); |
||||
|
} |
||||
|
}, |
||||
|
fail: (res) => { |
||||
|
} |
||||
|
}) |
||||
|
}) |
||||
|
} |
||||
|
}); |
||||
|
}, |
||||
|
manage(){ |
||||
|
this.isManage =!this.isManage; |
||||
|
this.manageName = this.isManage ? '取消' : '整理'; |
||||
|
if(!this.isManage){ |
||||
|
this.checkList=[]; |
||||
|
this.emojiList.forEach((item,index)=>{ |
||||
|
this.emojiList[index].isCheck=false; |
||||
|
}) |
||||
|
} |
||||
|
}, |
||||
|
change(e) { |
||||
|
let { index } = e.detail; |
||||
|
if(!this.isManage || index==0) return; |
||||
|
let isCheck=this.emojiList[index-1].isCheck; |
||||
|
let id=this.emojiList[index-1].id; |
||||
|
if(!isCheck){ |
||||
|
this.checkList.push(id); |
||||
|
}else{ |
||||
|
this.checkList = this.checkList.filter(item => item !== id); |
||||
|
} |
||||
|
this.emojiList[index-1].isCheck=!isCheck; |
||||
|
}, |
||||
|
// 移动表情 |
||||
|
moveEmoji(){ |
||||
|
if(this.checkList.length<=0){ |
||||
|
return; |
||||
|
} |
||||
|
this.$api.emojiApi.moveEmoji({ids:this.checkList}).then((res)=>{ |
||||
|
this.updateEmoji() |
||||
|
}) |
||||
|
}, |
||||
|
// 删除表情 |
||||
|
delEmoji(){ |
||||
|
if(this.checkList.length<=0){ |
||||
|
return; |
||||
|
} |
||||
|
uni.showModal({ |
||||
|
title: '确定要删除选中的表情吗?', |
||||
|
success: (res)=>{ |
||||
|
if (res.confirm) { |
||||
|
this.$api.emojiApi.delEmoji({ids:this.checkList}).then((res)=>{ |
||||
|
this.updateEmoji() |
||||
|
}) |
||||
|
} |
||||
|
}, |
||||
|
}) |
||||
|
|
||||
|
}, |
||||
|
updateEmoji(){ |
||||
|
this.getEmojiList(); |
||||
|
this.checkList=[]; |
||||
|
uni.$emit('updateEmoji',true) |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
</script> |
||||
|
|
||||
|
<style lang="scss"> |
||||
|
page{ |
||||
|
background-color: #fff; |
||||
|
} |
||||
|
.upload-emoji{ |
||||
|
height:70rpx; |
||||
|
font-size:50rpx; |
||||
|
text-align: center; |
||||
|
} |
||||
|
.grid-dynamic-box { |
||||
|
margin-bottom: 15px; |
||||
|
} |
||||
|
|
||||
|
.btn-opt{ |
||||
|
position: fixed; |
||||
|
bottom: 0; |
||||
|
width:100%; |
||||
|
height:100rpx; |
||||
|
border-top: solid 1px #D2D2D2; |
||||
|
z-index:1000; |
||||
|
} |
||||
|
|
||||
|
.grid-item-box { |
||||
|
flex: 1; |
||||
|
// position: relative; |
||||
|
/* #ifndef APP-NVUE */ |
||||
|
display: flex; |
||||
|
/* #endif */ |
||||
|
flex-direction: column; |
||||
|
align-items: center; |
||||
|
justify-content: center; |
||||
|
} |
||||
|
|
||||
|
.grid-item-box-row { |
||||
|
flex: 1; |
||||
|
// position: relative; |
||||
|
/* #ifndef APP-NVUE */ |
||||
|
display: flex; |
||||
|
/* #endif */ |
||||
|
flex-direction: row; |
||||
|
align-items: center; |
||||
|
justify-content: center; |
||||
|
padding: 15px 0; |
||||
|
} |
||||
|
|
||||
|
.grid-dot { |
||||
|
position: absolute; |
||||
|
top: 5px; |
||||
|
right: 15px; |
||||
|
} |
||||
|
|
||||
|
.swiper { |
||||
|
height: 420px; |
||||
|
} |
||||
|
|
||||
|
.emoji-check-box{ |
||||
|
position:absolute; |
||||
|
right:5rpx; |
||||
|
top:5rpx; |
||||
|
} |
||||
|
|
||||
|
/* #ifdef H5 */ |
||||
|
@media screen and (min-width: 768px) and (max-width: 1425px) { |
||||
|
.swiper { |
||||
|
height: 630px; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
@media screen and (min-width: 1425px) { |
||||
|
.swiper { |
||||
|
height: 830px; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
/* #endif */ |
||||
|
</style> |
||||
@ -0,0 +1,349 @@ |
|||||
|
<template> |
||||
|
<view> |
||||
|
<cu-custom bgColor="bg-main-bar" :isBack="true"> |
||||
|
<template #backText></template> |
||||
|
<template #content>群成员</template> |
||||
|
</cu-custom> |
||||
|
<view class="cu-bar bg-white search fixed" :style="[{top:CustomBar + 'px'}]"> |
||||
|
<view class="search-form round"> |
||||
|
<text class="cuIcon-search"></text> |
||||
|
<input type="text" v-model="keywords" placeholder="输入搜索的关键词" confirm-type="search"/> |
||||
|
</view> |
||||
|
</view> |
||||
|
<view style="margin-top: 104rpx;"> |
||||
|
<view class="cu-list menu-avatar"> |
||||
|
<view class="cu-item" v-for="(item,index) in userList" :key="index"> |
||||
|
<block v-for="(iteme,indexs) in imglist" :key="indexs" v-if="network_log == 'none'"> |
||||
|
<view class="cu-avatar round lg" v-if="item.userInfo.imgname === iteme.name" :style="'background-image:url('+iteme.path+');'" @tap="openChatDetail(item)"></view> |
||||
|
</block> |
||||
|
<view v-else class="cu-avatar round lg" :style="'background-image:url('+item.userInfo.avatar+');'" @tap="openChatDetail(item)"></view> |
||||
|
|
||||
|
<view class="content"> |
||||
|
<view class="text-grey"> |
||||
|
<view class="text-cut">{{item.userInfo.displayName}}</view> |
||||
|
<view v-if="item.role<3" class="cu-tag round sm" :class="item.role==1 ? 'bg-red' : 'bg-orange'">{{item.role ==1?"群主":item.role==2?'管理员':''}}</view> |
||||
|
<view v-if="item.user_id==userInfo.user_id" class="cu-tag round sm">我</view> |
||||
|
|
||||
|
</view> |
||||
|
<view v-if="noSpeakExp(item.no_speak_time)" class="text-red text-xs">禁言至:{{noSpeakExp(item.no_speak_time)}}</view> |
||||
|
</view> |
||||
|
<view class="action" @tap="openModel(item)"> |
||||
|
<view class="text-grey text-sm"> <text class="cuIcon-more f-24" v-if="item.role>1 && isAuth" ></text></view> |
||||
|
</view> |
||||
|
</view> |
||||
|
<Empty v-if="!userList.length" noDatatext="未搜索到数据" textcolor="#999" ></Empty> |
||||
|
</view> |
||||
|
</view> |
||||
|
<view class="cu-modal bottom-modal" :class="modelName=='userOpt'?'show':''"> |
||||
|
<view class="cu-dialog"> |
||||
|
<view class="manage-content"> |
||||
|
<view class="cu-list menu bg-white"> |
||||
|
<view class="cu-item" v-if="curUser"> |
||||
|
<view class="content im-flex im-justify-content-center im-align-items-center"> |
||||
|
<view class="cu-avatar round sm" :style="'background-image:url('+(curUser.avatar)+');'"></view> |
||||
|
<view class="text-cut ml-5">{{curUser.realname}}</view> |
||||
|
<view v-if="curUser.role==2" class="cu-tag round sm bg-orange">管理员</view> |
||||
|
</view> |
||||
|
</view> |
||||
|
<view class="cu-item" @tap="changeOwner()" v-if="isAdmin"> |
||||
|
<view class="content padding-tb-sm"> |
||||
|
<!-- <text class="c-orange">转让管理权限</text> --> |
||||
|
<text class="c-orange">转让群主</text> |
||||
|
</view> |
||||
|
</view> |
||||
|
<view class="cu-item" @tap="setManage()" v-if="isAdmin"> |
||||
|
<view class="content padding-tb-sm"> |
||||
|
<text>{{curUser.role==2 ? '取消管理员' : '设为管理员'}}</text> |
||||
|
</view> |
||||
|
</view> |
||||
|
<view class="cu-item" @tap="showNoSpeak()" v-if="isAdmin"> |
||||
|
<view class="content padding-tb-sm"> |
||||
|
<text>设置禁言</text> |
||||
|
</view> |
||||
|
</view> |
||||
|
<view class="cu-item" @tap="removeUser()"> |
||||
|
<view class="content padding-tb-sm"> |
||||
|
<text>移出群聊</text> |
||||
|
</view> |
||||
|
</view> |
||||
|
<view class="parting-line-5"></view> |
||||
|
<view class="cu-item" @tap="modelName=''"> |
||||
|
<view class="content padding-tb-sm"> |
||||
|
<text class="c-red">取消</text> |
||||
|
</view> |
||||
|
</view> |
||||
|
|
||||
|
</view> |
||||
|
</view> |
||||
|
</view> |
||||
|
</view> |
||||
|
<uni-popup ref="nospeak" type="share" safeArea backgroundColor="#fff"> |
||||
|
<uni-list> |
||||
|
<uni-list-item clickable v-for="(item,index) in noSpeakList" :key="index" :title="item.name" @click="setTime(item.id)" > |
||||
|
<template v-slot:footer> |
||||
|
<text class="cuIcon-check c-green" v-if="noSpeakTimer==item.id"></text> |
||||
|
</template> |
||||
|
</uni-list-item> |
||||
|
<uni-list-item title="自定义" clickable @click="setTime(0)" > |
||||
|
<template v-slot:footer> |
||||
|
<uni-number-box :min="1" :max="365" :value="noSpeakDay" /> 天 |
||||
|
</template> |
||||
|
</uni-list-item> |
||||
|
</uni-list> |
||||
|
<view class="padding flex flex-direction mt-10"> |
||||
|
<button class="cu-btn bg-green lg" @tap="setNoSpeak()">确定</button> |
||||
|
</view> |
||||
|
</uni-popup> |
||||
|
</view> |
||||
|
</template> |
||||
|
<script> |
||||
|
import { useMsgStore } from '@/store/message'; |
||||
|
import pinia from '@/store/index' |
||||
|
const msgStore = useMsgStore(pinia) |
||||
|
import { useloginStore } from '@/store/login'; |
||||
|
const userStore = useloginStore(pinia) |
||||
|
// #ifdef APP-PLUS |
||||
|
import {getSavedImages1} from '@/utils/LocalFileSystemURL.js' |
||||
|
import groupUserList from '@/service/groupUserList'; |
||||
|
// #endif |
||||
|
import { storeToRefs } from 'pinia'; |
||||
|
const {network_log} = storeToRefs(msgStore); |
||||
|
export default { |
||||
|
data() { |
||||
|
return { |
||||
|
keywords:'', |
||||
|
group_id: '', |
||||
|
group_id1: null, |
||||
|
modelName:'', |
||||
|
userList: [], |
||||
|
allUser:[], |
||||
|
isAdmin:false, |
||||
|
isManage:false, |
||||
|
isAuth:false, |
||||
|
noSpeakTimer:0, |
||||
|
noSpeakDay:1, |
||||
|
userInfo:userStore.userInfo, |
||||
|
curUser:{}, //选中的用户 |
||||
|
noSpeakList:[{ |
||||
|
name:'10分钟', |
||||
|
id:1 |
||||
|
},{ |
||||
|
name:'1小时', |
||||
|
id:2 |
||||
|
},{ |
||||
|
name:'3小时', |
||||
|
id:3 |
||||
|
},{ |
||||
|
name:'1天', |
||||
|
id:4 |
||||
|
}], |
||||
|
imglist:[], |
||||
|
network_log:network_log |
||||
|
} |
||||
|
}, |
||||
|
watch:{ |
||||
|
keywords(val){ |
||||
|
if(val==''){ |
||||
|
this.userList=this.allUser; |
||||
|
}else{ |
||||
|
this.search(); |
||||
|
} |
||||
|
}, |
||||
|
}, |
||||
|
onLoad(options) { |
||||
|
this.group_id = options.group_id?options.group_id:'' |
||||
|
this.group_id1 = options.group_id1?options.group_id1:null |
||||
|
const _this = this |
||||
|
if(this.network_log == 'none'){ |
||||
|
_this.Getchatinformation() |
||||
|
_this.getImagePath() |
||||
|
}else{ |
||||
|
_this.getGroupuserlist() |
||||
|
// _this.Getchatinformation() |
||||
|
// _this.getImagePath() |
||||
|
} |
||||
|
}, |
||||
|
methods: { |
||||
|
getGroupuserlist() { |
||||
|
this.userList = [] |
||||
|
this.$api.msgApi.groupUserList({ |
||||
|
group_id: this.group_id |
||||
|
}).then(res => { |
||||
|
const admin=res.data.filter(item => item.role == 1 && item.userInfo.id== this.userInfo.user_id) |
||||
|
if(admin.length) this.isAdmin=true; |
||||
|
const manage=res.data.filter(item => item.role == 2 && item.userInfo.id== this.userInfo.user_id) |
||||
|
if(manage.length) this.manage=true; |
||||
|
if(admin.length || manage.length) this.isAuth=true; |
||||
|
const allUser=res.data; |
||||
|
allUser.forEach((item)=>{ |
||||
|
item.realname=item.userInfo.displayName; |
||||
|
item.name_py=item.userInfo.name_py; |
||||
|
}) |
||||
|
this.allUser=allUser; |
||||
|
this.userList = res.data; |
||||
|
}) |
||||
|
}, |
||||
|
|
||||
|
async Getchatinformation(){ |
||||
|
// #ifdef APP-PLUS |
||||
|
const groups = await groupUserList.getList({group_id: this.group_id1}); |
||||
|
groups.map((res)=>{ |
||||
|
res.userInfo = JSON.parse(res.userInfo) |
||||
|
}) |
||||
|
const admin=groups.filter(item => item.role == 1 && item.userInfo.id== this.userInfo.user_id) |
||||
|
if(admin.length) this.isAdmin=true; |
||||
|
const manage=groups.filter(item => item.role == 2 && item.userInfo.id== this.userInfo.user_id) |
||||
|
if(manage.length) this.manage=true; |
||||
|
if(admin.length || manage.length) this.isAuth=true; |
||||
|
const allUser=groups; |
||||
|
allUser.forEach((item)=>{ |
||||
|
item.realname=item.userInfo.displayName; |
||||
|
item.name_py=item.userInfo.name_py; |
||||
|
}) |
||||
|
this.allUser=allUser; |
||||
|
this.userList = groups; |
||||
|
|
||||
|
console.info('获取群成员数据',groups.length,groups); |
||||
|
// #endif |
||||
|
}, |
||||
|
// 获取图片地址 |
||||
|
async getImagePath(){ |
||||
|
this.imglist = await getSavedImages1() |
||||
|
this.imglist.map(item => { |
||||
|
item.path = plus.io.convertLocalFileSystemURL(item.path) |
||||
|
}); |
||||
|
console.info('读取地址',this.imglist); |
||||
|
}, |
||||
|
|
||||
|
openModel(item){ |
||||
|
item.realname=item.userInfo.displayName; |
||||
|
item.avatar=item.userInfo.avatar; |
||||
|
this.curUser=item; |
||||
|
this.modelName='userOpt'; |
||||
|
}, |
||||
|
// 设置取消管理员 |
||||
|
setManage(){ |
||||
|
const role=this.curUser.role==2 ? 3 : 2; |
||||
|
this.$api.msgApi.setManager({ |
||||
|
id:this.group_id, |
||||
|
user_id:this.curUser.user_id, |
||||
|
role:role |
||||
|
}).then((res)=>{ |
||||
|
if(res.code==0){ |
||||
|
this.getGroupuserlist(); |
||||
|
} |
||||
|
this.modelName=''; |
||||
|
}) |
||||
|
}, |
||||
|
// 移出成员 |
||||
|
removeUser(){ |
||||
|
this.modelName=''; |
||||
|
uni.showModal({ |
||||
|
title: '确定要删除该成员吗?', |
||||
|
success: e => { |
||||
|
if (e.confirm) { |
||||
|
this.$api.msgApi.removeUser({ |
||||
|
id:this.group_id, |
||||
|
user_id:this.curUser.user_id, |
||||
|
}).then((res)=>{ |
||||
|
if(res.code==0){ |
||||
|
this.getGroupuserlist(); |
||||
|
} |
||||
|
}) |
||||
|
} |
||||
|
} |
||||
|
}); |
||||
|
}, |
||||
|
changeOwner(){ |
||||
|
this.modelName=''; |
||||
|
uni.showModal({ |
||||
|
// title: '确定将管理权限转移给该成员吗?', |
||||
|
title: '确定将群主管理权限转移给该成员吗?', |
||||
|
success: e => { |
||||
|
if (e.confirm) { |
||||
|
this.$api.msgApi.changeOwner({ |
||||
|
id:this.group_id, |
||||
|
user_id:this.curUser.user_id, |
||||
|
}).then((res)=>{ |
||||
|
if(res.code==0){ |
||||
|
uni.reLaunch({ |
||||
|
url:'/pages/index/index' |
||||
|
}) |
||||
|
} |
||||
|
}) |
||||
|
} |
||||
|
} |
||||
|
}); |
||||
|
}, |
||||
|
search(){ |
||||
|
const allUser=JSON.parse(JSON.stringify(this.allUser)); |
||||
|
this.userList=this.$util.searchObject(allUser,['realname','name_py'],this.keywords); |
||||
|
}, |
||||
|
openChatDetail(item){ |
||||
|
if(this.userInfo.user_id==item.user_id) return; |
||||
|
let friend=msgStore.getContact(item.user_id); |
||||
|
let curContact=msgStore.getContact(this.group_id); |
||||
|
if(curContact.role<3 || curContact.setting.profile=='1' || friend){ |
||||
|
uni.navigateTo({ |
||||
|
url:"/pages/contacts/detail?id="+item.user_id |
||||
|
}) |
||||
|
}else{ |
||||
|
uni.showToast({ |
||||
|
title:'已开启用户隐私!', |
||||
|
icon:'none' |
||||
|
}) |
||||
|
return false; |
||||
|
} |
||||
|
}, |
||||
|
showNoSpeak(){ |
||||
|
this.modelName='' |
||||
|
this.$refs.nospeak.open() |
||||
|
}, |
||||
|
setTime(val){ |
||||
|
console.log(val); |
||||
|
this.noSpeakTimer=val; |
||||
|
}, |
||||
|
setNoSpeak(){ |
||||
|
this.$refs.nospeak.close(); |
||||
|
this.$api.msgApi.setNoSpeak({ |
||||
|
id:this.group_id, |
||||
|
user_id:this.curUser.user_id, |
||||
|
noSpeakDay:this.noSpeakDay, |
||||
|
noSpeakTimer:this.noSpeakTimer |
||||
|
}).then((res)=>{ |
||||
|
this.noSpeakTimer=0; |
||||
|
this.noSpeakDay=1; |
||||
|
if(res.code==0){ |
||||
|
this.getGroupuserlist(); |
||||
|
} |
||||
|
}) |
||||
|
}, |
||||
|
noSpeakExp(time){ |
||||
|
if(time * 1000>new Date().getTime()){ |
||||
|
return this.$util.date('m-d H:i',time); |
||||
|
}else{ |
||||
|
return false; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
</script> |
||||
|
<style scoped lang="scss"> |
||||
|
.list-image { |
||||
|
width: 80rpx; |
||||
|
height: 80rpx; |
||||
|
font-size: 0; |
||||
|
} |
||||
|
.share { |
||||
|
/* #ifndef APP-NVUE */ |
||||
|
display: flex; |
||||
|
/* #endif */ |
||||
|
flex-direction: column; |
||||
|
} |
||||
|
.uni-popup{ |
||||
|
z-index:9999 !important; |
||||
|
} |
||||
|
::v-deep uni-modal{ |
||||
|
z-index: 1025; |
||||
|
} |
||||
|
</style> |
||||