|
@@ -1,16 +1,28 @@
|
|
|
<template>
|
|
<template>
|
|
|
<div class="floating-softphone">
|
|
<div class="floating-softphone">
|
|
|
- <!-- 来电提示气泡 -->
|
|
|
|
|
|
|
+ <!-- 来电醒目提示(右下角固定,高于面板层级) -->
|
|
|
<transition name="call-bubble-fade">
|
|
<transition name="call-bubble-fade">
|
|
|
- <div class="incoming-call-bubble"
|
|
|
|
|
- v-if="callStatus === 'ringing' && incomingCaller"
|
|
|
|
|
- :class="{ 'bubble-left': fabOnLeftEdge }"
|
|
|
|
|
- :style="bubbleStyle"
|
|
|
|
|
- @click="onFabClick">
|
|
|
|
|
- <div class="bubble-content">
|
|
|
|
|
- <i class="material-icons bubble-icon">phone_in_talk</i>
|
|
|
|
|
- <span class="bubble-number">{{ maskNumber(incomingCaller) }}</span>
|
|
|
|
|
- <span class="bubble-label">来电</span>
|
|
|
|
|
|
|
+ <div class="incoming-call-alert"
|
|
|
|
|
+ v-if="isIncomingRinging"
|
|
|
|
|
+ @click="onIncomingAlertClick">
|
|
|
|
|
+ <div class="incoming-call-alert__ripple"></div>
|
|
|
|
|
+ <div class="incoming-call-alert__content">
|
|
|
|
|
+ <div class="incoming-call-alert__icon-wrap">
|
|
|
|
|
+ <i class="material-icons">phone_in_talk</i>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ <div class="incoming-call-alert__info">
|
|
|
|
|
+ <span class="incoming-call-alert__title">来电响铃中</span>
|
|
|
|
|
+ <span class="incoming-call-alert__number">{{ incomingCallDisplayNumber }}</span>
|
|
|
|
|
+ <span class="incoming-call-alert__hint">点击卡片打开软电话</span>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ <div class="incoming-call-alert__actions">
|
|
|
|
|
+ <button class="incoming-call-alert__btn answer" title="接听" @click.stop="answerIncomingCall">
|
|
|
|
|
+ <i class="material-icons">call</i>
|
|
|
|
|
+ </button>
|
|
|
|
|
+ <button class="incoming-call-alert__btn reject" title="拒接" @click.stop="rejectIncomingCall">
|
|
|
|
|
+ <i class="material-icons">call_end</i>
|
|
|
|
|
+ </button>
|
|
|
|
|
+ </div>
|
|
|
</div>
|
|
</div>
|
|
|
</div>
|
|
</div>
|
|
|
</transition>
|
|
</transition>
|
|
@@ -27,7 +39,7 @@
|
|
|
:style="fabStyle"
|
|
:style="fabStyle"
|
|
|
@mousedown="onFabDragStart"
|
|
@mousedown="onFabDragStart"
|
|
|
@click="onFabClick">
|
|
@click="onFabClick">
|
|
|
- <i class="material-icons">{{ panelVisible ? 'close' : 'phone' }}</i>
|
|
|
|
|
|
|
+ <i class="material-icons">{{ fabIconName }}</i>
|
|
|
<span class="fab-badge" v-if="callStatus !== 'idle'"></span>
|
|
<span class="fab-badge" v-if="callStatus !== 'idle'"></span>
|
|
|
</div>
|
|
</div>
|
|
|
|
|
|
|
@@ -285,6 +297,11 @@ import {
|
|
|
getConnectedSharedCCPhoneBar,
|
|
getConnectedSharedCCPhoneBar,
|
|
|
incrementSharedCCPhoneBarRef
|
|
incrementSharedCCPhoneBarRef
|
|
|
} from '@/utils/ccPhoneBarShared';
|
|
} from '@/utils/ccPhoneBarShared';
|
|
|
|
|
+import {
|
|
|
|
|
+ startIncomingCallAttention,
|
|
|
|
|
+ stopIncomingCallAttention,
|
|
|
|
|
+ requestIncomingCallNotificationPermission
|
|
|
|
|
+} from '@/utils/incomingCallAttention';
|
|
|
|
|
|
|
|
// IPCC 和 JsSIP 默认配置已从 softPhone.js 统一导入,此处仅作别名引用
|
|
// IPCC 和 JsSIP 默认配置已从 softPhone.js 统一导入,此处仅作别名引用
|
|
|
const IPCC_CONFIG = IPCC_DEFAULTS;
|
|
const IPCC_CONFIG = IPCC_DEFAULTS;
|
|
@@ -302,8 +319,10 @@ const UI_STATE = {
|
|
|
};
|
|
};
|
|
|
|
|
|
|
|
const FAB_SIZE = 56;
|
|
const FAB_SIZE = 56;
|
|
|
|
|
+const FAB_RING_SIZE = 64;
|
|
|
const FAB_EDGE_OFFSET = 8;
|
|
const FAB_EDGE_OFFSET = 8;
|
|
|
const FAB_MIN_VISIBLE = 28;
|
|
const FAB_MIN_VISIBLE = 28;
|
|
|
|
|
+const FAB_SAFE_MARGIN = 16;
|
|
|
|
|
|
|
|
export default {
|
|
export default {
|
|
|
name: 'FloatingSoftPhone',
|
|
name: 'FloatingSoftPhone',
|
|
@@ -491,21 +510,26 @@ export default {
|
|
|
zIndex: 9998
|
|
zIndex: 9998
|
|
|
};
|
|
};
|
|
|
},
|
|
},
|
|
|
- bubbleStyle() {
|
|
|
|
|
- if (this.fabX === null) {
|
|
|
|
|
- return { position: 'fixed', bottom: '88px', right: '24px', zIndex: 9997 };
|
|
|
|
|
|
|
+ /** 来电提示显示的号码 */
|
|
|
|
|
+ incomingCallDisplayNumber() {
|
|
|
|
|
+ if (this.incomingCaller) {
|
|
|
|
|
+ return this.maskNumber(this.incomingCaller);
|
|
|
}
|
|
}
|
|
|
- // 气泡在FAB上方
|
|
|
|
|
- return {
|
|
|
|
|
- position: 'fixed',
|
|
|
|
|
- left: this.fabX + 'px',
|
|
|
|
|
- top: (this.fabY - 48) + 'px',
|
|
|
|
|
- zIndex: 9997
|
|
|
|
|
- };
|
|
|
|
|
|
|
+ if (this.displayText) {
|
|
|
|
|
+ return this.displayText;
|
|
|
|
|
+ }
|
|
|
|
|
+ return '未知号码';
|
|
|
},
|
|
},
|
|
|
hangupButtonIcon() {
|
|
hangupButtonIcon() {
|
|
|
return this.callStatus !== 'idle' ? 'call_end' : 'phone';
|
|
return this.callStatus !== 'idle' ? 'call_end' : 'phone';
|
|
|
},
|
|
},
|
|
|
|
|
+ /** 来电振铃时不用 close 图标,避免与面板关闭态混淆且吸边时易被裁切 */
|
|
|
|
|
+ fabIconName() {
|
|
|
|
|
+ if (this.isIncomingRinging) {
|
|
|
|
|
+ return 'phone_in_talk';
|
|
|
|
|
+ }
|
|
|
|
|
+ return this.panelVisible ? 'close' : 'phone';
|
|
|
|
|
+ },
|
|
|
fabOnLeftEdge() {
|
|
fabOnLeftEdge() {
|
|
|
if (this.fabEdge === 'left') return true;
|
|
if (this.fabEdge === 'left') return true;
|
|
|
if (this.fabEdge === 'right') return false;
|
|
if (this.fabEdge === 'right') return false;
|
|
@@ -556,6 +580,19 @@ export default {
|
|
|
if (val === 'ringing' || val === 'talking') {
|
|
if (val === 'ringing' || val === 'talking') {
|
|
|
this.panelVisible = true;
|
|
this.panelVisible = true;
|
|
|
this.fabIsCollapsed = false; // 来电/通话时自动展开FAB
|
|
this.fabIsCollapsed = false; // 来电/通话时自动展开FAB
|
|
|
|
|
+ this.$nextTick(() => this.ensureFabFullyVisible());
|
|
|
|
|
+ }
|
|
|
|
|
+ },
|
|
|
|
|
+ isIncomingRinging(val) {
|
|
|
|
|
+ if (val) {
|
|
|
|
|
+ this.fabIsCollapsed = false;
|
|
|
|
|
+ this.$nextTick(() => this.ensureFabFullyVisible());
|
|
|
|
|
+ startIncomingCallAttention({
|
|
|
|
|
+ caller: this.incomingCallDisplayNumber,
|
|
|
|
|
+ body: `来电号码:${this.incomingCallDisplayNumber},请尽快接听`
|
|
|
|
|
+ });
|
|
|
|
|
+ } else {
|
|
|
|
|
+ stopIncomingCallAttention();
|
|
|
}
|
|
}
|
|
|
},
|
|
},
|
|
|
'$route.path'(newPath, oldPath) {
|
|
'$route.path'(newPath, oldPath) {
|
|
@@ -617,6 +654,7 @@ export default {
|
|
|
this.$root.$on('cc-phonebar-reconnect-requested', this.onCCPhoneBarReconnectRequested);
|
|
this.$root.$on('cc-phonebar-reconnect-requested', this.onCCPhoneBarReconnectRequested);
|
|
|
},
|
|
},
|
|
|
beforeDestroy() {
|
|
beforeDestroy() {
|
|
|
|
|
+ stopIncomingCallAttention();
|
|
|
this.destroyAllConnections();
|
|
this.destroyAllConnections();
|
|
|
window.removeEventListener('beforeunload', this.handleBeforeUnload);
|
|
window.removeEventListener('beforeunload', this.handleBeforeUnload);
|
|
|
if (this._boundViewportResize) {
|
|
if (this._boundViewportResize) {
|
|
@@ -648,6 +686,24 @@ export default {
|
|
|
height: vv ? vv.height : window.innerHeight
|
|
height: vv ? vv.height : window.innerHeight
|
|
|
};
|
|
};
|
|
|
},
|
|
},
|
|
|
|
|
+ getFabSize() {
|
|
|
|
|
+ return this.callStatus === UI_STATE.RINGING ? FAB_RING_SIZE : FAB_SIZE;
|
|
|
|
|
+ },
|
|
|
|
|
+ ensureFabFullyVisible() {
|
|
|
|
|
+ const fabSize = this.getFabSize();
|
|
|
|
|
+ const { width, height } = this.getViewportSize();
|
|
|
|
|
+ if (this.fabX === null) {
|
|
|
|
|
+ this.fabX = width - fabSize - FAB_SAFE_MARGIN;
|
|
|
|
|
+ this.fabY = Math.max(0, Math.min(
|
|
|
|
|
+ this.fabYRatio != null ? this.fabYRatio * height : height * 0.85,
|
|
|
|
|
+ height - fabSize
|
|
|
|
|
+ ));
|
|
|
|
|
+ return;
|
|
|
|
|
+ }
|
|
|
|
|
+ this.fabX = Math.max(FAB_SAFE_MARGIN, Math.min(this.fabX, width - fabSize - FAB_SAFE_MARGIN));
|
|
|
|
|
+ this.fabY = Math.max(0, Math.min(this.fabY, height - fabSize));
|
|
|
|
|
+ this.syncFabRelativeFromAbsolute();
|
|
|
|
|
+ },
|
|
|
applyFabFromRelative(edge, yRatio) {
|
|
applyFabFromRelative(edge, yRatio) {
|
|
|
const { width, height } = this.getViewportSize();
|
|
const { width, height } = this.getViewportSize();
|
|
|
this.fabEdge = edge === 'left' ? 'left' : 'right';
|
|
this.fabEdge = edge === 'left' ? 'left' : 'right';
|
|
@@ -671,10 +727,11 @@ export default {
|
|
|
this.applyFabFromRelative(edge, yRatio);
|
|
this.applyFabFromRelative(edge, yRatio);
|
|
|
return;
|
|
return;
|
|
|
}
|
|
}
|
|
|
- const minX = -(FAB_SIZE - FAB_MIN_VISIBLE);
|
|
|
|
|
- const maxX = width - FAB_MIN_VISIBLE;
|
|
|
|
|
|
|
+ const fabSize = this.getFabSize();
|
|
|
|
|
+ const minX = FAB_SAFE_MARGIN;
|
|
|
|
|
+ const maxX = width - fabSize - FAB_SAFE_MARGIN;
|
|
|
this.fabX = Math.max(minX, Math.min(this.fabX, maxX));
|
|
this.fabX = Math.max(minX, Math.min(this.fabX, maxX));
|
|
|
- this.fabY = Math.max(0, Math.min(this.fabY, height - FAB_SIZE));
|
|
|
|
|
|
|
+ this.fabY = Math.max(0, Math.min(this.fabY, height - fabSize));
|
|
|
this.syncFabRelativeFromAbsolute();
|
|
this.syncFabRelativeFromAbsolute();
|
|
|
},
|
|
},
|
|
|
|
|
|
|
@@ -730,7 +787,23 @@ export default {
|
|
|
// 面板打开时不折叠,避免影响操作
|
|
// 面板打开时不折叠,避免影响操作
|
|
|
this.fabIsCollapsed = !this.panelVisible;
|
|
this.fabIsCollapsed = !this.panelVisible;
|
|
|
},
|
|
},
|
|
|
|
|
+ onIncomingAlertClick() {
|
|
|
|
|
+ this.panelVisible = true;
|
|
|
|
|
+ this.fabIsCollapsed = false;
|
|
|
|
|
+ },
|
|
|
|
|
+ answerIncomingCall() {
|
|
|
|
|
+ if (this.phone) {
|
|
|
|
|
+ this.phone.Answer();
|
|
|
|
|
+ }
|
|
|
|
|
+ this.panelVisible = true;
|
|
|
|
|
+ this.fabIsCollapsed = false;
|
|
|
|
|
+ },
|
|
|
|
|
+ rejectIncomingCall() {
|
|
|
|
|
+ this.endCall();
|
|
|
|
|
+ },
|
|
|
onFabClick(e) {
|
|
onFabClick(e) {
|
|
|
|
|
+ // 用户点击软电话时尝试申请桌面通知权限
|
|
|
|
|
+ requestIncomingCallNotificationPermission();
|
|
|
// 如果拖拽过程中产生了位移,不触发点击
|
|
// 如果拖拽过程中产生了位移,不触发点击
|
|
|
if (this.fabDragMoved) {
|
|
if (this.fabDragMoved) {
|
|
|
this.fabDragMoved = false;
|
|
this.fabDragMoved = false;
|
|
@@ -1514,9 +1587,10 @@ export default {
|
|
|
this.incomingCaller = '';
|
|
this.incomingCaller = '';
|
|
|
// 注意:不在_resetCallState中重置pendingManualNavigation,它由onSessionClosed消费
|
|
// 注意:不在_resetCallState中重置pendingManualNavigation,它由onSessionClosed消费
|
|
|
},
|
|
},
|
|
|
- onSessionClosed() {
|
|
|
|
|
|
|
+ onSessionClosed(event) {
|
|
|
// 转人工来电(incomingJsipCall)或主动设置的导航标记,挂断后都跳转
|
|
// 转人工来电(incomingJsipCall)或主动设置的导航标记,挂断后都跳转
|
|
|
- const shouldNavigate = this.incomingJsipCall || this.pendingManualNavigation;
|
|
|
|
|
|
|
+ const isTestRejected = event && event.reason === 'test_rejected';
|
|
|
|
|
+ const shouldNavigate = !isTestRejected && (this.incomingJsipCall || this.pendingManualNavigation);
|
|
|
this.showStatus('已挂机', 'info');
|
|
this.showStatus('已挂机', 'info');
|
|
|
this._resetCallState();
|
|
this._resetCallState();
|
|
|
// 转人工来电挂断后,跳转到转人工通话列表页面
|
|
// 转人工来电挂断后,跳转到转人工通话列表页面
|
|
@@ -1538,8 +1612,14 @@ export default {
|
|
|
this.rightButtonHangup = incoming;
|
|
this.rightButtonHangup = incoming;
|
|
|
this.delegatedCallActive = true;
|
|
this.delegatedCallActive = true;
|
|
|
this.showStatus(incoming ? '来电振铃中...' : '振铃中...', 'info');
|
|
this.showStatus(incoming ? '来电振铃中...' : '振铃中...', 'info');
|
|
|
|
|
+ if (incoming && this.phone) {
|
|
|
|
|
+ this.phone.playIncomingRing();
|
|
|
|
|
+ }
|
|
|
},
|
|
},
|
|
|
onDialogCallTalking() {
|
|
onDialogCallTalking() {
|
|
|
|
|
+ if (this.phone) {
|
|
|
|
|
+ this.phone.pauseIncomingRing();
|
|
|
|
|
+ }
|
|
|
this.isIncomingCall = false;
|
|
this.isIncomingCall = false;
|
|
|
this.callStatus = UI_STATE.TALKING;
|
|
this.callStatus = UI_STATE.TALKING;
|
|
|
this.showLeftButton = true;
|
|
this.showLeftButton = true;
|
|
@@ -1550,6 +1630,9 @@ export default {
|
|
|
this.showStatus('通话中', 'success');
|
|
this.showStatus('通话中', 'success');
|
|
|
},
|
|
},
|
|
|
onDialogCallEnded() {
|
|
onDialogCallEnded() {
|
|
|
|
|
+ if (this.phone) {
|
|
|
|
|
+ this.phone.pauseIncomingRing();
|
|
|
|
|
+ }
|
|
|
this._resetCallState();
|
|
this._resetCallState();
|
|
|
this.showStatus('已挂机', 'info');
|
|
this.showStatus('已挂机', 'info');
|
|
|
},
|
|
},
|
|
@@ -1991,58 +2074,146 @@ export default {
|
|
|
/* 振铃时FAB脉冲动画 */
|
|
/* 振铃时FAB脉冲动画 */
|
|
|
.softphone-fab.fab-ringing {
|
|
.softphone-fab.fab-ringing {
|
|
|
animation: fab-ringing-pulse 1s infinite;
|
|
animation: fab-ringing-pulse 1s infinite;
|
|
|
|
|
+ opacity: 1 !important;
|
|
|
|
|
+ width: 64px !important;
|
|
|
|
|
+ height: 64px !important;
|
|
|
|
|
+ background: #f44336 !important;
|
|
|
|
|
+ box-shadow: 0 6px 20px rgba(244, 67, 54, 0.55) !important;
|
|
|
}
|
|
}
|
|
|
@keyframes fab-ringing-pulse {
|
|
@keyframes fab-ringing-pulse {
|
|
|
0% { box-shadow: 0 0 0 0 rgba(244, 67, 54, 0.7); }
|
|
0% { box-shadow: 0 0 0 0 rgba(244, 67, 54, 0.7); }
|
|
|
- 70% { box-shadow: 0 0 0 16px rgba(244, 67, 54, 0); }
|
|
|
|
|
|
|
+ 70% { box-shadow: 0 0 0 20px rgba(244, 67, 54, 0); }
|
|
|
100% { box-shadow: 0 0 0 0 rgba(244, 67, 54, 0); }
|
|
100% { box-shadow: 0 0 0 0 rgba(244, 67, 54, 0); }
|
|
|
}
|
|
}
|
|
|
-.softphone-fab.fab-ringing:not(.fab-collapsed) {
|
|
|
|
|
- background: #f44336;
|
|
|
|
|
|
|
+.softphone-fab.fab-ringing .material-icons {
|
|
|
|
|
+ font-size: 32px;
|
|
|
|
|
+}
|
|
|
|
|
+.softphone-fab.fab-ringing .fab-badge {
|
|
|
|
|
+ width: 14px;
|
|
|
|
|
+ height: 14px;
|
|
|
|
|
+ top: 6px;
|
|
|
|
|
+ right: 6px;
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
-/* ===== 来电提示气泡 ===== */
|
|
|
|
|
-.incoming-call-bubble {
|
|
|
|
|
|
|
+/* ===== 来电醒目提示卡片 ===== */
|
|
|
|
|
+.incoming-call-alert {
|
|
|
position: fixed;
|
|
position: fixed;
|
|
|
- background: linear-gradient(135deg, #f44336, #e53935);
|
|
|
|
|
|
|
+ right: 24px;
|
|
|
|
|
+ bottom: 100px;
|
|
|
|
|
+ z-index: 10002;
|
|
|
|
|
+ min-width: 300px;
|
|
|
|
|
+ max-width: calc(100vw - 48px);
|
|
|
|
|
+ box-sizing: border-box;
|
|
|
|
|
+ background: linear-gradient(135deg, #ff5252 0%, #d32f2f 100%);
|
|
|
color: #fff;
|
|
color: #fff;
|
|
|
- border-radius: 24px;
|
|
|
|
|
- padding: 6px 16px;
|
|
|
|
|
- box-shadow: 0 4px 16px rgba(244, 67, 54, 0.4);
|
|
|
|
|
|
|
+ border-radius: 16px;
|
|
|
|
|
+ padding: 14px 20px 14px 16px;
|
|
|
|
|
+ box-shadow: 0 8px 32px rgba(211, 47, 47, 0.45), 0 0 0 2px rgba(255, 255, 255, 0.25);
|
|
|
cursor: pointer;
|
|
cursor: pointer;
|
|
|
- white-space: nowrap;
|
|
|
|
|
- animation: bubble-bounce 0.5s ease;
|
|
|
|
|
|
|
+ overflow: visible;
|
|
|
}
|
|
}
|
|
|
-.incoming-call-bubble.bubble-left {
|
|
|
|
|
- /* 左侧FAB时气泡在右边 */
|
|
|
|
|
|
|
+.incoming-call-alert__ripple {
|
|
|
|
|
+ position: absolute;
|
|
|
|
|
+ inset: 0;
|
|
|
|
|
+ border-radius: inherit;
|
|
|
|
|
+ box-shadow: 0 0 0 0 rgba(255, 255, 255, 0.35);
|
|
|
|
|
+ animation: incoming-alert-ripple 1.5s ease-out infinite;
|
|
|
|
|
+ pointer-events: none;
|
|
|
|
|
+ overflow: hidden;
|
|
|
}
|
|
}
|
|
|
-.bubble-content {
|
|
|
|
|
|
|
+.incoming-call-alert__content {
|
|
|
|
|
+ position: relative;
|
|
|
display: flex;
|
|
display: flex;
|
|
|
align-items: center;
|
|
align-items: center;
|
|
|
- gap: 8px;
|
|
|
|
|
|
|
+ gap: 12px;
|
|
|
|
|
+ animation: incoming-alert-shake 2s ease-in-out infinite;
|
|
|
|
|
+}
|
|
|
|
|
+.incoming-call-alert__icon-wrap {
|
|
|
|
|
+ width: 48px;
|
|
|
|
|
+ height: 48px;
|
|
|
|
|
+ border-radius: 50%;
|
|
|
|
|
+ background: rgba(255, 255, 255, 0.2);
|
|
|
|
|
+ display: flex;
|
|
|
|
|
+ align-items: center;
|
|
|
|
|
+ justify-content: center;
|
|
|
|
|
+ flex-shrink: 0;
|
|
|
|
|
+}
|
|
|
|
|
+.incoming-call-alert__icon-wrap .material-icons {
|
|
|
|
|
+ font-size: 28px;
|
|
|
|
|
+ animation: incoming-alert-icon-ring 0.6s ease-in-out infinite alternate;
|
|
|
}
|
|
}
|
|
|
-.bubble-icon {
|
|
|
|
|
- font-size: 18px;
|
|
|
|
|
- animation: bubble-icon-ring 0.8s infinite;
|
|
|
|
|
|
|
+@keyframes incoming-alert-icon-ring {
|
|
|
|
|
+ from { transform: rotate(-12deg); }
|
|
|
|
|
+ to { transform: rotate(12deg); }
|
|
|
}
|
|
}
|
|
|
-@keyframes bubble-icon-ring {
|
|
|
|
|
- 0%, 100% { transform: rotate(0deg); }
|
|
|
|
|
- 50% { transform: rotate(15deg); }
|
|
|
|
|
|
|
+.incoming-call-alert__info {
|
|
|
|
|
+ flex: 1;
|
|
|
|
|
+ min-width: 0;
|
|
|
|
|
+ display: flex;
|
|
|
|
|
+ flex-direction: column;
|
|
|
|
|
+ gap: 2px;
|
|
|
}
|
|
}
|
|
|
-.bubble-number {
|
|
|
|
|
- font-size: 15px;
|
|
|
|
|
- font-weight: 600;
|
|
|
|
|
|
|
+.incoming-call-alert__title {
|
|
|
|
|
+ font-size: 16px;
|
|
|
|
|
+ font-weight: 700;
|
|
|
letter-spacing: 0.5px;
|
|
letter-spacing: 0.5px;
|
|
|
}
|
|
}
|
|
|
-.bubble-label {
|
|
|
|
|
- font-size: 12px;
|
|
|
|
|
|
|
+.incoming-call-alert__number {
|
|
|
|
|
+ font-size: 20px;
|
|
|
|
|
+ font-weight: 700;
|
|
|
|
|
+ letter-spacing: 1px;
|
|
|
|
|
+ line-height: 1.2;
|
|
|
|
|
+}
|
|
|
|
|
+.incoming-call-alert__hint {
|
|
|
|
|
+ font-size: 11px;
|
|
|
opacity: 0.85;
|
|
opacity: 0.85;
|
|
|
- margin-left: 2px;
|
|
|
|
|
|
|
+ margin-top: 2px;
|
|
|
|
|
+}
|
|
|
|
|
+.incoming-call-alert__actions {
|
|
|
|
|
+ display: flex;
|
|
|
|
|
+ flex-direction: column;
|
|
|
|
|
+ gap: 8px;
|
|
|
|
|
+ flex-shrink: 0;
|
|
|
|
|
+ padding-right: 2px;
|
|
|
|
|
+}
|
|
|
|
|
+.incoming-call-alert__btn {
|
|
|
|
|
+ width: 44px;
|
|
|
|
|
+ height: 44px;
|
|
|
|
|
+ border: none;
|
|
|
|
|
+ border-radius: 50%;
|
|
|
|
|
+ display: flex;
|
|
|
|
|
+ align-items: center;
|
|
|
|
|
+ justify-content: center;
|
|
|
|
|
+ cursor: pointer;
|
|
|
|
|
+ flex-shrink: 0;
|
|
|
|
|
+ transition: transform 0.15s ease, box-shadow 0.15s ease;
|
|
|
|
|
+}
|
|
|
|
|
+.incoming-call-alert__btn .material-icons {
|
|
|
|
|
+ font-size: 22px;
|
|
|
|
|
+ color: #fff;
|
|
|
|
|
+}
|
|
|
|
|
+.incoming-call-alert__btn.answer {
|
|
|
|
|
+ background: #4caf50;
|
|
|
|
|
+ box-shadow: 0 4px 12px rgba(76, 175, 80, 0.5);
|
|
|
|
|
+}
|
|
|
|
|
+.incoming-call-alert__btn.reject {
|
|
|
|
|
+ background: rgba(0, 0, 0, 0.25);
|
|
|
|
|
+ box-shadow: 0 4px 12px rgba(0, 0, 0, 0.2);
|
|
|
|
|
+}
|
|
|
|
|
+.incoming-call-alert__btn:hover {
|
|
|
|
|
+ transform: scale(1.08);
|
|
|
|
|
+}
|
|
|
|
|
+.incoming-call-alert__btn:active {
|
|
|
|
|
+ transform: scale(0.95);
|
|
|
|
|
+}
|
|
|
|
|
+@keyframes incoming-alert-ripple {
|
|
|
|
|
+ 0% { box-shadow: 0 0 0 0 rgba(255, 255, 255, 0.45); }
|
|
|
|
|
+ 70% { box-shadow: 0 0 0 14px rgba(255, 255, 255, 0); }
|
|
|
|
|
+ 100% { box-shadow: 0 0 0 0 rgba(255, 255, 255, 0); }
|
|
|
}
|
|
}
|
|
|
-@keyframes bubble-bounce {
|
|
|
|
|
- 0% { opacity: 0; transform: translateY(10px) scale(0.9); }
|
|
|
|
|
- 60% { transform: translateY(-2px) scale(1.02); }
|
|
|
|
|
- 100% { opacity: 1; transform: translateY(0) scale(1); }
|
|
|
|
|
|
|
+@keyframes incoming-alert-shake {
|
|
|
|
|
+ 0%, 100% { transform: translateY(0); }
|
|
|
|
|
+ 50% { transform: translateY(-2px); }
|
|
|
}
|
|
}
|
|
|
/* 气泡过渡 */
|
|
/* 气泡过渡 */
|
|
|
.call-bubble-fade-enter-active { transition: opacity 0.3s ease, transform 0.3s ease; }
|
|
.call-bubble-fade-enter-active { transition: opacity 0.3s ease, transform 0.3s ease; }
|