适配微信小程序

This commit is contained in:
2025-12-15 11:06:42 +08:00
parent 8266de4902
commit 8e434a2b50
27 changed files with 746 additions and 190 deletions

View File

@@ -1,5 +1,5 @@
<template>
<!-- #ifdef H5 -->
<!-- #ifdef H5 || APP-PLUS -->
<view v-if="call.active" class="call-window" :class="{ minimized: call.minimized }">
<!-- 最小化悬浮窗 -->
<view v-if="call.minimized" class="minimized-bar" @click="$emit('toggleMinimize')">
@@ -79,13 +79,13 @@
<template v-if="call.status === 'incoming'">
<view class="action-item" @click="$emit('reject')">
<view class="control-btn decline">
<wd-icon name="close-bold" size="56rpx" color="#fff" />
<wd-icon name="close" size="56rpx" color="#fff" />
</view>
<text class="action-label">拒绝</text>
</view>
<view class="action-item" @click="$emit('accept')">
<view class="control-btn accept">
<wd-icon name="phone-filled" size="64rpx" color="#fff" />
<wd-icon name="phone" size="64rpx" color="#fff" />
</view>
<text class="action-label">接听</text>
</view>
@@ -97,7 +97,7 @@
<view class="action-item" @click="$emit('toggleMute')">
<view class="control-btn" :class="{ active: call.muted }">
<wd-icon
:name="call.muted ? 'mic-off' : 'mic-on'"
:name="call.muted ? 'mute' : 'sound'"
size="56rpx"
:color="call.muted ? '#000' : '#fff'"
/>
@@ -109,7 +109,7 @@
<view v-if="call.type === 'video'" class="action-item" @click="$emit('toggleCamera')">
<view class="control-btn" :class="{ active: call.camOff }">
<wd-icon
:name="call.camOff ? 'video-off' : 'video'"
name="video"
size="56rpx"
:color="call.camOff ? '#000' : '#fff'"
/>
@@ -120,7 +120,7 @@
<!-- 挂断 -->
<view class="action-item" @click="$emit('end')">
<view class="control-btn hangup">
<wd-icon name="phone-off-filled" size="64rpx" color="#fff" />
<wd-icon name="phone" size="64rpx" color="#fff" custom-style="transform: rotate(135deg);" />
</view>
<text class="action-label">挂断</text>
</view>
@@ -130,7 +130,7 @@
<template v-else-if="call.status === 'outgoing'">
<view class="action-item" @click="$emit('end')">
<view class="control-btn hangup">
<wd-icon name="phone-off-filled" size="64rpx" color="#fff" />
<wd-icon name="phone" size="64rpx" color="#fff" custom-style="transform: rotate(135deg);" />
</view>
<text class="action-label">挂断</text>
</view>
@@ -140,11 +140,11 @@
</view>
<!-- #endif -->
<!-- #ifndef H5 -->
<!-- #ifndef H5 || APP-PLUS -->
<view v-if="call.active" class="call-unsupported">
<view class="unsupported-content">
<view class="icon-box">
<wd-icon name="video-off" size="80rpx" color="#f97316" />
<wd-icon name="video" size="80rpx" color="#f97316" />
</view>
<text class="unsupported-title">暂不支持音视频通话</text>
<text class="unsupported-desc">当前平台暂不支持音视频通话功能</text>
@@ -207,7 +207,7 @@ function copyWebUrl() {
}
watch(() => props.localStream, (stream) => {
// #ifdef H5
// #ifdef H5 || APP-PLUS
nextTick(() => {
if (localVideoRef.value && stream) {
localVideoRef.value.srcObject = stream
@@ -217,7 +217,7 @@ watch(() => props.localStream, (stream) => {
}, { immediate: true })
watch(() => props.remoteStream, (stream) => {
// #ifdef H5
// #ifdef H5 || APP-PLUS
nextTick(() => {
if (remoteVideoRef.value && stream) {
remoteVideoRef.value.srcObject = stream
@@ -239,7 +239,6 @@ watch(() => props.remoteStream, (stream) => {
transition: all 0.3s cubic-bezier(0.34, 1.56, 0.64, 1);
&.minimized {
top: calc(var(--status-bar-height, 0) + var(--mp-safe-top, 0px) + 240rpx);
right: 32rpx;
left: auto;
bottom: auto;
@@ -249,6 +248,14 @@ watch(() => props.remoteStream, (stream) => {
border: 2rpx solid rgba(255, 255, 255, 0.15);
box-shadow: 0 16rpx 48rpx rgba(0, 0, 0, 0.4);
overflow: hidden;
/* #ifdef MP-WEIXIN */
top: calc(var(--status-bar-height, 44px) + 88rpx + 240rpx);
/* #endif */
/* #ifndef MP-WEIXIN */
top: calc(var(--status-bar-height, 0) + 240rpx);
/* #endif */
}
}
@@ -346,7 +353,6 @@ watch(() => props.remoteStream, (stream) => {
.minimize-btn {
position: absolute;
top: calc(28rpx + var(--status-bar-height, 0) + var(--mp-safe-top, 0px));
right: 40rpx;
z-index: 20;
width: 72rpx;
@@ -355,6 +361,14 @@ watch(() => props.remoteStream, (stream) => {
background: rgba(255, 255, 255, 0.1);
backdrop-filter: blur(8px);
display: flex;
/* #ifdef MP-WEIXIN */
top: calc(28rpx + var(--status-bar-height, 44px) + 88rpx);
/* #endif */
/* #ifndef MP-WEIXIN */
top: calc(28rpx + var(--status-bar-height, 0));
/* #endif */
align-items: center;
justify-content: center;
transition: all 0.15s;

View File

@@ -57,13 +57,13 @@
<!-- 顶部栏 -->
<view class="ui-header">
<view class="header-left">
<view class="status-dot"></view>
<text class="header-title">通话中</text>
<view class="status-dot" :class="statusDotClass"></view>
<text class="header-title">{{ headerTitle }}</text>
<text class="header-time">{{ formatDuration(call.duration) }}</text>
</view>
<view class="minimize-btn" @click="toggleMinimize">
<!-- 最小化图标 -->
<wd-icon name="arrow-down-circle" size="60rpx" color="rgba(255,255,255,0.8)" />
<wd-icon name="arrow-down" size="60rpx" color="rgba(255,255,255,0.8)" />
</view>
</view>
@@ -73,13 +73,13 @@
<view v-if="call.status === 'incoming'" class="incoming-actions">
<view class="action-btn reject" @click="rejectCall">
<view class="btn-circle reject-bg">
<wd-icon name="close-bold" size="50rpx" color="#fff" />
<wd-icon name="close" size="50rpx" color="#fff" />
</view>
<text class="action-text">拒绝</text>
</view>
<view class="action-btn accept" @click="acceptCall">
<view class="btn-circle accept-bg">
<wd-icon name="phone-filled" size="56rpx" color="#fff" />
<wd-icon name="phone" size="56rpx" color="#fff" />
</view>
<text class="action-text">接听</text>
</view>
@@ -90,7 +90,7 @@
<!-- 静音 -->
<view class="ctrl-btn-wrap" @click="toggleMute">
<view class="ctrl-btn" :class="{ active: call.muted }">
<wd-icon :name="call.muted ? 'mic-off' : 'mic-on'" size="50rpx" :color="call.muted ? '#000' : '#fff'" />
<wd-icon :name="call.muted ? 'mute' : 'sound'" size="50rpx" :color="call.muted ? '#000' : '#fff'" />
</view>
<text class="ctrl-text">静音</text>
</view>
@@ -98,7 +98,7 @@
<!-- 挂断 -->
<view class="ctrl-btn-wrap" @click="endCall">
<view class="ctrl-btn hangup">
<wd-icon name="phone-off-filled" size="50rpx" color="#fff" />
<wd-icon name="phone" size="50rpx" color="#fff" custom-style="transform: rotate(135deg);" />
</view>
<text class="ctrl-text">挂断</text>
</view>
@@ -106,7 +106,7 @@
<!-- 摄像头 -->
<view class="ctrl-btn-wrap" @click="toggleCamera">
<view class="ctrl-btn" :class="{ active: call.camOff }">
<wd-icon :name="call.camOff ? 'video-off' : 'video'" size="50rpx" :color="call.camOff ? '#000' : '#fff'" />
<wd-icon name="video" size="50rpx" :color="call.camOff ? '#000' : '#fff'" />
</view>
<text class="ctrl-text">摄像头</text>
</view>
@@ -148,6 +148,33 @@ const instance = getCurrentInstance()
const gridClass = computed(() => remoteStreams.value.length > 1 ? 'grid-multi' : 'grid-single')
// 顶部状态显示
const headerTitle = computed(() => {
switch (call.status) {
case 'outgoing':
return '正在呼叫...'
case 'incoming':
return '来电...'
case 'connected':
return '通话中'
default:
return call.statusText || '通话'
}
})
// 状态指示点样式
const statusDotClass = computed(() => {
switch (call.status) {
case 'connected':
return 'status-connected'
case 'outgoing':
case 'incoming':
return 'status-pending'
default:
return ''
}
})
// 监听通话激活,初始化 Context
watch(() => call.active, (newVal) => {
if (newVal) {
@@ -236,8 +263,8 @@ const onPlayerError = (e: any, userId: string) => {
top: 0;
left: 0;
width: 100%;
height: 160rpx;
padding-top: 60rpx;
height: calc(var(--status-bar-height, 44px) + var(--mp-navbar-height, 48px) + 20rpx);
padding-top: calc(var(--status-bar-height, 44px) + 20rpx);
display: flex;
justify-content: space-between;
padding-left: 30rpx;
@@ -247,7 +274,19 @@ const onPlayerError = (e: any, userId: string) => {
pointer-events: none; /* 让点击穿透到视频层(如果有交互) */
}
.header-left { display: flex; align-items: center; pointer-events: auto; }
.status-dot { width: 12rpx; height: 12rpx; background: #10b981; border-radius: 50%; margin-right: 16rpx; }
.status-dot {
width: 12rpx;
height: 12rpx;
background: #666;
border-radius: 50%;
margin-right: 16rpx;
}
.status-dot.status-connected { background: #10b981; } /* 绿色 = 已连接 */
.status-dot.status-pending { background: #f59e0b; animation: pulse 1.5s infinite; } /* 橙色 = 呼叫中 */
@keyframes pulse {
0%, 100% { opacity: 1; }
50% { opacity: 0.4; }
}
.header-title { color: #fff; font-size: 32rpx; font-weight: bold; }
.header-time { color: rgba(255,255,255,0.8); font-size: 26rpx; margin-left: 20rpx; }
.minimize-btn { pointer-events: auto; padding: 10rpx; }

View File

@@ -29,7 +29,7 @@
@touchend="stopRecording"
@touchcancel="cancelRecording"
>
<!-- #ifdef H5 -->
<!-- #ifdef H5 || APP-PLUS -->
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<path d="M12 1a3 3 0 0 0-3 3v8a3 3 0 0 0 6 0V4a3 3 0 0 0-3-3z"/>
<path d="M19 10v2a7 7 0 0 1-14 0v-2"/>
@@ -64,7 +64,7 @@
<view class="right-actions">
<!-- 表情按钮 -->
<view class="icon-btn" @click="toggleEmoji">
<!-- #ifdef H5 -->
<!-- #ifdef H5 || APP-PLUS -->
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<circle cx="12" cy="12" r="10"/>
<path d="M8 14s1.5 2 4 2 4-2 4-2"/>
@@ -79,7 +79,7 @@
<!-- 发送按钮 (有文字时显示) -->
<view v-if="inputValue.trim()" class="send-btn" @click="onSend">
<!-- #ifdef H5 -->
<!-- #ifdef H5 || APP-PLUS -->
<svg viewBox="0 0 24 24" fill="none" stroke="#fff" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round">
<line x1="22" y1="2" x2="11" y2="13"/>
<polygon points="22 2 15 22 11 13 2 9 22 2"/>
@@ -92,7 +92,7 @@
<!-- 更多按钮 (无文字时显示) -->
<view v-else class="icon-btn" @click="showMore = true">
<!-- #ifdef H5 -->
<!-- #ifdef H5 || APP-PLUS -->
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<circle cx="12" cy="12" r="10"/>
<line x1="12" y1="8" x2="12" y2="16"/>
@@ -112,7 +112,7 @@
<view class="tools-grid">
<view class="tool-item" @click="emitAction('send-image')">
<view class="tool-icon album">
<!-- #ifdef H5 -->
<!-- #ifdef H5 || APP-PLUS -->
<svg viewBox="0 0 24 24" fill="none" stroke="#fff" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect x="3" y="3" width="18" height="18" rx="2" ry="2"/><circle cx="8.5" cy="8.5" r="1.5"/><polyline points="21 15 16 10 5 21"/></svg>
<!-- #endif -->
<!-- #ifdef MP-WEIXIN -->
@@ -123,7 +123,7 @@
</view>
<view class="tool-item" @click="emitAction('send-camera')">
<view class="tool-icon camera">
<!-- #ifdef H5 -->
<!-- #ifdef H5 || APP-PLUS -->
<svg viewBox="0 0 24 24" fill="none" stroke="#fff" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M23 19a2 2 0 0 1-2 2H3a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h4l2-3h6l2 3h4a2 2 0 0 1 2 2z"/><circle cx="12" cy="13" r="4"/></svg>
<!-- #endif -->
<!-- #ifdef MP-WEIXIN -->
@@ -134,7 +134,7 @@
</view>
<view class="tool-item" @click="emitAction('send-file')">
<view class="tool-icon file">
<!-- #ifdef H5 -->
<!-- #ifdef H5 || APP-PLUS -->
<svg viewBox="0 0 24 24" fill="none" stroke="#fff" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M13 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V9z"/><polyline points="13 2 13 9 20 9"/></svg>
<!-- #endif -->
<!-- #ifdef MP-WEIXIN -->
@@ -150,7 +150,7 @@
<view v-if="isRecording" class="recording-overlay">
<view class="recording-content" :class="{ 'cancel-mode': isCancelRecording }">
<view class="recording-icon">
<!-- #ifdef H5 -->
<!-- #ifdef H5 || APP-PLUS -->
<svg v-if="!isCancelRecording" viewBox="0 0 24 24" fill="currentColor"><path d="M12 1a3 3 0 0 0-3 3v8a3 3 0 0 0 6 0V4a3 3 0 0 0-3-3z"/><path d="M19 10v2a7 7 0 0 1-14 0v-2"/><line x1="12" y1="19" x2="12" y2="23" stroke="currentColor" stroke-width="2"/><line x1="8" y1="23" x2="16" y2="23" stroke="currentColor" stroke-width="2"/></svg>
<svg v-else viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><circle cx="12" cy="12" r="10"/><line x1="15" y1="9" x2="9" y2="15"/><line x1="9" y1="9" x2="15" y2="15"/></svg>
<!-- #endif -->

View File

@@ -7,7 +7,7 @@
:left-arrow="leftArrow"
:fixed="fixed"
:placeholder="placeholder"
:safe-area-inset-top="safeAreaInsetTop"
:safe-area-inset-top="true"
:bordered="bordered"
custom-class="app-nav-bar"
@click-left="handleClickLeft"
@@ -81,6 +81,7 @@ function handleClickRight() {
<style lang="scss" scoped>
:deep(.app-nav-bar) {
position: relative;
// 适配暗黑模式的背景色和文字颜色
// 假设 src/uni.scss 或 variables.scss 中定义了这些 CSS 变量
background-color: rgba(255, 255, 255, 0.85) !important;
@@ -101,6 +102,18 @@ function handleClickRight() {
}
}
/* #ifdef MP-WEIXIN */
:deep(.app-nav-bar) {
top: 130rpx !important;
}
/* #endif */
/* #ifndef MP-WEIXIN */
:deep(.app-nav-bar) {
top: 0 !important;
}
/* #endif */
// 暗黑模式下的特殊覆盖(如果 CSS 变量不够用)
.dark {
:deep(.app-nav-bar) {
@@ -111,7 +124,7 @@ function handleClickRight() {
.wd-navbar__title {
color: #f5f5f4 !important;
}
.wd-icon {
color: #e7e5e4 !important;
}

View File

@@ -11,7 +11,7 @@
<view class="tabbar-icon">
<!-- 消息图标 (message-circle) -->
<template v-if="tab.name === 'messages'">
<!-- #ifdef H5 -->
<!-- #ifdef H5 || APP-PLUS -->
<svg v-if="current === tab.name" class="icon" viewBox="0 0 24 24" fill="currentColor">
<path d="M21 11.5a8.38 8.38 0 0 1-.9 3.8 8.5 8.5 0 0 1-7.6 4.7 8.38 8.38 0 0 1-3.8-.9L3 21l1.9-5.7a8.38 8.38 0 0 1-.9-3.8 8.5 8.5 0 0 1 4.7-7.6 8.38 8.38 0 0 1 3.8-.9h.5a8.48 8.48 0 0 1 8 8v.5z"/>
</svg>
@@ -30,7 +30,7 @@
<!-- 联系人图标 (users) -->
<template v-else-if="tab.name === 'contacts'">
<!-- #ifdef H5 -->
<!-- #ifdef H5 || APP-PLUS -->
<svg v-if="current === tab.name" class="icon" viewBox="0 0 24 24" fill="currentColor">
<path d="M17 21v-2a4 4 0 0 0-4-4H5a4 4 0 0 0-4 4v2"/>
<circle cx="9" cy="7" r="4" fill="currentColor"/>
@@ -51,7 +51,7 @@
<!-- 发现图标 (compass) -->
<template v-else-if="tab.name === 'moments'">
<!-- #ifdef H5 -->
<!-- #ifdef H5 || APP-PLUS -->
<svg v-if="current === tab.name" class="icon" viewBox="0 0 24 24" fill="currentColor">
<circle cx="12" cy="12" r="10" fill="currentColor"/>
<polygon points="16.24 7.76 14.12 14.12 7.76 16.24 9.88 9.88 16.24 7.76" fill="white"/>
@@ -68,7 +68,7 @@
<!-- 我的图标 (user) -->
<template v-else-if="tab.name === 'profile'">
<!-- #ifdef H5 -->
<!-- #ifdef H5 || APP-PLUS -->
<svg v-if="current === tab.name" class="icon" viewBox="0 0 24 24" fill="currentColor">
<path d="M20 21v-2a4 4 0 0 0-4-4H8a4 4 0 0 0-4 4v2"/>
<circle cx="12" cy="7" r="4"/>

View File

@@ -247,8 +247,6 @@ function handleClose() {
top: 0;
left: 0;
right: 0;
height: calc(88rpx + var(--status-bar-height, 0) + var(--mp-safe-top, 0px));
padding-top: calc(var(--status-bar-height, 0) + var(--mp-safe-top, 0px));
display: flex;
align-items: center;
justify-content: space-between;
@@ -256,6 +254,16 @@ function handleClose() {
padding-right: 24rpx;
background: linear-gradient(180deg, rgba(0, 0, 0, 0.6) 0%, transparent 100%);
z-index: 10;
/* #ifdef MP-WEIXIN */
height: calc(88rpx + var(--status-bar-height, 44px) + 88rpx);
padding-top: calc(var(--status-bar-height, 44px) + 88rpx);
/* #endif */
/* #ifndef MP-WEIXIN */
height: calc(88rpx + var(--status-bar-height, 0));
padding-top: var(--status-bar-height, 0);
/* #endif */
}
.close-btn,

View File

@@ -113,7 +113,6 @@ function handleClick(key: string) {
// ==========================================
.plus-menu {
position: fixed;
top: calc(var(--status-bar-height, 20px) + var(--mp-safe-top, 0px) + 180rpx);
right: 32rpx;
width: 288rpx;
background: var(--menu-bg);
@@ -122,6 +121,14 @@ function handleClick(key: string) {
z-index: 999;
box-shadow: var(--menu-shadow);
border: 2rpx solid var(--menu-border);
/* #ifdef MP-WEIXIN */
top: calc(var(--status-bar-height, 44px) + 88rpx + 180rpx);
/* #endif */
/* #ifndef MP-WEIXIN */
top: calc(var(--status-bar-height, 20px) + 180rpx);
/* #endif */
transform-origin: top right;
transition: all 0.2s cubic-bezier(0.175, 0.885, 0.32, 1.275);
overflow: hidden;

View File

@@ -142,7 +142,7 @@
<view class="action-grid">
<view class="action-item" @click="handleSendMessage">
<view class="action-icon blue">
<!-- #ifdef H5 -->
<!-- #ifdef H5 || APP-PLUS -->
<svg viewBox="0 0 24 24" fill="none" stroke="#fff" stroke-width="2">
<path d="M21 15a2 2 0 0 1-2 2H7l-4 4V5a2 2 0 0 1 2-2h14a2 2 0 0 1 2 2z"/>
</svg>
@@ -156,7 +156,7 @@
<view class="action-item" @click="handleAudioCall">
<view class="action-icon green">
<!-- #ifdef H5 -->
<!-- #ifdef H5 || APP-PLUS -->
<svg viewBox="0 0 24 24" fill="none" stroke="#fff" stroke-width="2">
<path d="M22 16.92v3a2 2 0 0 1-2.18 2 19.79 19.79 0 0 1-8.63-3.07 19.5 19.5 0 0 1-6-6 19.79 19.79 0 0 1-3.07-8.67A2 2 0 0 1 4.11 2h3a2 2 0 0 1 2 1.72 12.84 12.84 0 0 0 .7 2.81 2 2 0 0 1-.45 2.11L8.09 9.91a16 16 0 0 0 6 6l1.27-1.27a2 2 0 0 1 2.11-.45 12.84 12.84 0 0 0 2.81.7A2 2 0 0 1 22 16.92z"/>
</svg>
@@ -170,7 +170,7 @@
<view class="action-item" @click="handleVideoCall">
<view class="action-icon purple">
<!-- #ifdef H5 -->
<!-- #ifdef H5 || APP-PLUS -->
<svg viewBox="0 0 24 24" fill="none" stroke="#fff" stroke-width="2">
<polygon points="23 7 16 12 23 17 23 7"/>
<rect x="1" y="5" width="15" height="14" rx="2" ry="2"/>
@@ -185,7 +185,7 @@
<view class="action-item" @click="handleViewMoments">
<view class="action-icon orange">
<!-- #ifdef H5 -->
<!-- #ifdef H5 || APP-PLUS -->
<svg viewBox="0 0 24 24" fill="none" stroke="#fff" stroke-width="2">
<circle cx="12" cy="12" r="10"/>
<path d="M8 14s1.5 2 4 2 4-2 4-2"/>