feat:聊天im替换

This commit is contained in:
ctw committed 2025-11-25 12:11:55 +08:00
1 parent 18fdcafd39
commit 6d26839aee
395 files changed
+8914 -40109

No files matched your search

@@ -1,332 +0,0 @@
<template>
<view class="mask" :class="!show?'':'mask-show'" :style="{backgroundColor:show?maskBg:'rgba(0,0,0,0)'}" @tap="tapMask">
<view class="popups" :class="[theme]"
:style="{top: popupsTop ,left: popupsLeft,flexDirection:direction}">
<text :class="dynPlace" :style="{width:'0px',height:'0px'}" v-if="triangle"></text>
<view v-for="(item,index) in popData" :key="index" @tap.stop="tapItem(item)"
class="itemChild view" :class="[direction==='row'?'solid-right':'solid-bottom',item.disabled?'disabledColor':'']">
<image class="image" :src="item.icon" v-if="item.icon"></image>{{item.title}}
</view>
<slot></slot>
</view>
</view>
</template>
<script>
export default{
props:{
maskBg:{
type:String,
default:'rgba(0,0,0,0)'
},
placement:{
type:String,
default:'default' //default top-start top-end bottom-start bottom-end
},
direction:{
type:String,
default:'column' //column row
},
x:{
type:Number,
default:0
},
y:{
type:Number,
default:0
},
modelValue:{
type:Boolean,
default:false
},
popData:{
type:Array,
default:()=>[]
},
theme:{
type:String,
default:'light' //light dark
},
dynamic:{
type:Boolean,
default:false
},
gap:{
type:Number,
default:20
},
triangle:{
type:Boolean,
default:true
}
},
data(){
return{
popupsTop:'0px',
popupsLeft:'0px',
show:false,
dynPlace:''
}
},
mounted() {
this.popupsPosition()
},
methods:{
tapMask(){
this.$emit('update:modelValue', false) // previously was `this.$emit('input', title)`
},
tapItem(item){
if(item.disabled) return
this.$emit('tapPopup',item)
this.$emit('update:modelValue', false) // previously was `this.$emit('input', title)`
},
getStatusBar(){
let promise = new Promise((resolve,reject)=>{
uni.getSystemInfo({
success: function(e) {
let customBar
// #ifdef H5
customBar = e.statusBarHeight + e.windowTop;
// #endif
resolve(customBar)
}
})
})
return promise
},
async popupsPosition(){
let statusBar = await this.getStatusBar()
let promise = new Promise((resolve,reject)=>{
let popupsDom = uni.createSelectorQuery().in(this).select(".popups")
popupsDom.fields({
size: true,
}, (data) => {
let width = data.width
let height = data.height
let y = this.dynamic?this.dynamicGetY(this.y,this.gap):this.transformRpx(this.y)
let x = this.dynamic?this.dynamicGetX(this.x,this.gap):this.transformRpx(this.x)
// #ifdef H5
y = this.dynamic?(this.y+statusBar): this.transformRpx(this.y+statusBar)
// #endif
this.dynPlace = this.placement=='default'?this.getPlacement(x,y):this.placement
switch(this.dynPlace){
case 'top-start':
this.popupsTop = `${y+9}px`
this.popupsLeft = `${x-15}px`
break;
case 'top-end':
this.popupsTop = `${y+9}px`
this.popupsLeft = `${x+15-width}px`
break;
case 'bottom-start':
this.popupsTop = `${y-18-height}px`
this.popupsLeft = `${x-15}px`
break;
case 'bottom-end':
this.popupsTop = `${y-9-height}px`
this.popupsLeft = `${x+15-width}px`
break;
}
resolve()
}).exec();
})
return promise
},
getPlacement(x,y){
let width = uni.getSystemInfoSync().windowWidth
let height = uni.getSystemInfoSync().windowHeight
if(x>width/2&&y>height/2){
return 'bottom-end'
}else if(x<width/2&&y<height/2){
return 'top-start'
}else if(x>width/2&&y<height/2){
return 'top-end'
}else if(x<width/2&&y>height/2){
return 'bottom-start'
}else if(x>width/2){
return 'top-end'
}else{
return 'top-start'
}
},
dynamicGetY(y,gap){
let height = uni.getSystemInfoSync().windowHeight
y = y<gap?gap:y
y = height - y <gap? (height - gap) : y
return y
},
dynamicGetX(x,gap){
let width = uni.getSystemInfoSync().windowWidth
x = x< gap?gap:x
x = width - x <gap? (width - gap) : x
return x
},
transformRpx(params){
return params*uni.getSystemInfoSync().screenWidth/375
}
},
watch:{
modelValue:{
immediate:true,
handler:async function (newVal,oldVal){
if(newVal) await this.popupsPosition()
this.show = newVal
}
},
placement:{
immediate:true,
handler(newVal,oldVal){
this.dynPlace = newVal
}
}
}
}
</script>
<style lang="scss" scoped>
.mask{
position: fixed;
top: 0;
right: 0;
bottom: 0;
left: 0;
z-index: 9999;
transition: background 0.3s ease-in-out;
visibility: hidden;
&.mask-show{
visibility: visible;
}
}
.popups{
position: absolute;
padding: 20rpx;
border-radius: 5px;
display:flex;
.view{
padding: 10rpx;
}
.image{
display: inline-block;
vertical-align: middle;
width: 40rpx;
height: 40rpx;
margin-right: 20rpx;
}
}
.dark{
background-color: #4C4C4C;
color: #fff;
.top-start:after {
content: "";
position: absolute;
top: -18rpx;
left: 10rpx;
border-width: 0 20rpx 20rpx;
border-style: solid;
border-color: transparent transparent #4C4C4C;
}
.top-end:after {
content: "";
position: absolute;
top: -18rpx;
right: 10rpx;
border-width: 0 20rpx 20rpx;
border-style: solid;
border-color: transparent transparent #4C4C4C;
}
.bottom-start:after {
content: "";
position: absolute;
bottom: -18rpx;
left: 10rpx;
border-width: 20rpx 20rpx 0 ;
border-style: solid;
border-color: #4C4C4C transparent transparent ;
}
.bottom-end:after {
content: "";
position: absolute;
bottom: -18rpx;
right: 10rpx;
border-width: 20rpx 20rpx 0 ;
border-style: solid;
border-color: #4C4C4C transparent transparent ;
}
.disabledColor{
color: #c5c8ce;
}
}
.light{
color: #515a6e;
box-shadow: 0upx 0upx 30upx rgba(0,0,0,0.2);
background: #fff;
.top-start:after {
content: "";
position: absolute;
top: -18rpx;
left: 10rpx;
border-width: 0 20rpx 20rpx;
border-style: solid;
border-color: transparent transparent #fff;
}
.top-end:after {
content: "";
position: absolute;
top: -18rpx;
right: 10rpx;
border-width: 0 20rpx 20rpx;
border-style: solid;
border-color: transparent transparent #fff;
}
.bottom-start:after {
content: "";
position: absolute;
bottom: -18rpx;
left: 10rpx;
border-width: 20rpx 20rpx 0 ;
border-style: solid;
border-color: #fff transparent transparent ;
}
.bottom-end:after {
content: "";
position: absolute;
bottom: -18rpx;
right: 10rpx;
border-width: 20rpx 20rpx 0 ;
border-style: solid;
border-color: #fff transparent transparent ;
}
.disabledColor{
color: #c5c8ce;
}
}
.solid-bottom{
border-bottom: 1px solid #ccc;
}
.solid-right{
border-right: 1px solid #ccc;
}
.popups .itemChild:last-child{
border: none;
}
</style>
@@ -1,250 +0,0 @@
<template>
<view
@touchmove.stop.prevent="moveHandle('touchmove')"
@click="moveHandle('click')"
v-if="isActive"
>
<view class="main-action-menu" :animation="animationData" :style="{ top: height + 'px' }">
<view class="main-action-menu-top-arrow"></view>
<view class="menu-list">
<view class="menu-item" @click="go2CreateGroup">
<view class="menu-item-icon">
<view class="wxfont message2"></view>
</view>
<view class="text">发起群聊</view>
</view>
<view class="menu-item" @click="go2SearchFriend">
<view class="menu-item-icon">
<view class="wxfont add2"></view>
</view>
<view class="text">添加朋友</view>
</view>
<view class="menu-item" @click="go2ScanQrCode">
<view class="menu-item-icon">
<view class="wxfont qr_code"></view>
</view>
<view class="text">扫一扫</view>
</view>
</view>
</view>
<view class="main-action-menu-background-model"></view>
</view>
</template>
<script>
import Config from '../../config';
import store from '../../store';
import WfcScheme from '../../wfcScheme';
import wfc from '../../wfc/client/wfc';
export default {
data() {
return {
height: 0, //距离顶部高度oo
isActive: false,
animationData: {},
};
},
props: {
list: {
type: Array,
default() {
return [{}];
},
},
},
onShow() {},
mounted() {
this.getstatusBarHeight();
let animation = uni.createAnimation({
duration: 300,
timingFunction: 'linear',
});
this.animation = animation;
},
methods: {
go2CreateGroup() {
let sharedContactState = store.state.contact;
let users = sharedContactState.favContactList.concat(sharedContactState.friendList);
users = users.filter((u) => {
return u.uid !== Config.FILE_HELPER_ID;
});
this.$pickUsers({
users: users,
successCB: (users) => {
// TODO 创建群聊会比较慢,可以在这儿加一个 loading 页面
store.createConversation(
users,
(conversation) => {
// 不加延时的话,不能正常切换页面,会报莫名其妙的错误
setTimeout(() => {
this.$go2ConversationPage();
}, 50);
},
(err) => {},
);
},
});
},
go2SearchFriend() {
uni.navigateTo({
url: '/pages/contact/SearchUserPage',
fail: (err) => {},
});
},
go2ScanQrCode() {
uni.scanCode({
success: (res) => {
console.log('条码类型:' + res.scanType);
console.log('条码内容:' + res.result);
if (res.result) {
// TODO
// wildfirechat://pcsession/xxxx
this.onScanQrCode(res.result);
// uni.showToast({
// title: '扫码成功: ' + res.result,
// icon: 'none',
// });
}
},
});
},
showAnimation() {
this.animation.opacity(1).step();
this.animationData = this.animation.export();
},
hideAnimation() {
this.animation.opacity(0).step();
this.animationData = this.animation.export();
},
moveHandle(e) {
this.hide();
},
toggle() {
if (this.isActive) {
this.hide();
} else {
this.show();
}
},
show() {
this.isActive = true;
setTimeout(() => {
this.showAnimation();
}, 30);
},
hide() {
if (this.isActive) {
this.isActive = false;
this.hideAnimation();
}
},
getstatusBarHeight() {
let SystemInfo = uni.getSystemInfoSync();
// #ifdef H5
this.height = SystemInfo.safeArea.top + SystemInfo.windowTop;
// #endif
},
onScanQrCode(qrcode) {
let prefix = qrcode.substring(0, qrcode.lastIndexOf('/') + 1);
let value = qrcode.substring(qrcode.lastIndexOf('/') + 1);
switch (prefix) {
case WfcScheme.QR_CODE_PREFIX_USER:
wfc.getUserInfoEx(
value,
false,
(userInfo) => {
store.setCurrentFriend(userInfo);
uni.navigateTo({
url: '/pages/contact/UserDetailPage',
success: () => {
console.log('nav to UserDetailPage success');
},
fail: (err) => {
console.log('nav to UserDetailPage err', err);
},
});
},
(err) => {},
);
break;
default:
uni.showToast({
title: '不支持该二维码: ' + qrcode,
icon: 'none',
});
break;
}
},
},
};
</script>
<style scoped>
.main-action-menu {
width: 300rpx;
position: fixed;
z-index: 9999;
top: -10px;
right: 16rpx;
display: flex;
flex-direction: row;
flex-wrap: wrap;
opacity: 0;
}
.main-action-menu-background-model {
background-color: rgba(0, 0, 0, 0);
position: fixed;
top: 0;
bottom: 0;
right: 0;
left: 0;
z-index: 9998;
}
.main-action-menu-top-arrow {
width: 0px;
height: 0px;
border: 10px solid transparent;
border-bottom-color: #4c4c4c;
margin-left: auto;
margin-right: 20rpx;
}
.menu-list {
width: 100%;
background-color: #4c4c4c;
border-radius: 10rpx;
}
.menu-item {
display: flex;
flex-direction: row;
align-items: center;
padding: 0rpx 36rpx;
padding-right: 0;
}
.menu-item-icon {
width: 38rpx;
height: 38rpx;
margin-right: 28rpx;
color: #fff;
}
.menu-item-icon .wxfont {
font-size: 40rpx;
}
.menu-item .text {
color: #fff;
font-size: 32rpx;
border-bottom: 1px #535353 solid;
padding: 30rpx 0rpx;
flex: 1;
}
.menu-item:nth-last-child(1) .text {
border: none;
}
</style>