视频会议保存

This commit is contained in:
duoaohui
2026-05-18 19:42:55 +08:00
parent 315884685f
commit ac8ec4c903
41 changed files with 5069 additions and 39 deletions

View File

@@ -45,6 +45,54 @@
<text class="label">{{ videoEnabled ? '停止视频' : '开启视频' }}</text>
</button>
<button
class="btn"
:class="{ active: screenSharing, loading: screenLoading }"
:disabled="screenLoading || screenDisabled"
:title="screenDisabledReason || ''"
@click="emitIf('screen-toggle')"
>
<view class="icon">
<svg v-if="screenSharing" viewBox="0 0 24 24" width="22" height="22" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<!-- 共享中实心显示器 + 停止图示 -->
<rect x="2" y="3" width="20" height="14" rx="2" ry="2"></rect>
<line x1="8" y1="21" x2="16" y2="21"></line>
<line x1="12" y1="17" x2="12" y2="21"></line>
<line x1="6" y1="7" x2="18" y2="13"></line>
<line x1="18" y1="7" x2="6" y2="13"></line>
</svg>
<svg v-else viewBox="0 0 24 24" width="22" height="22" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<!-- 未共享空显示器 + 上箭头 -->
<rect x="2" y="3" width="20" height="14" rx="2" ry="2"></rect>
<line x1="8" y1="21" x2="16" y2="21"></line>
<line x1="12" y1="17" x2="12" y2="21"></line>
<polyline points="9 10 12 7 15 10"></polyline>
<line x1="12" y1="7" x2="12" y2="14"></line>
</svg>
</view>
<text class="label">{{ screenSharing ? '停止共享' : '共享屏幕' }}</text>
</button>
<!-- 录制按钮Phase B仅主持人可见recording=true 时红色脉动上传中禁用 -->
<button
v-if="showRecording"
class="btn btn-recording"
:class="{ active: recording, 'is-recording': recording, loading: recordingLoading }"
:disabled="recordingLoading"
@click="emitIf('recording-toggle')"
>
<view class="icon">
<svg v-if="recording" viewBox="0 0 24 24" width="22" height="22" fill="currentColor" stroke="none">
<circle cx="12" cy="12" r="6"></circle>
</svg>
<svg v-else viewBox="0 0 24 24" width="22" height="22" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<circle cx="12" cy="12" r="7"></circle>
<circle cx="12" cy="12" r="3"></circle>
</svg>
</view>
<text class="label">{{ recording ? '停止录制' : '开始录制' }}</text>
</button>
<button class="btn" @click="emitIf('invite')">
<view class="icon">
<svg viewBox="0 0 24 24" width="22" height="22" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
@@ -102,15 +150,31 @@ const props = defineProps({
unreadChatCount: { type: Number, default: 0 },
allowChat: { type: Boolean, default: true },
/** Task 15 原创特色 4全员静音氛围色 */
allMuted: { type: Boolean, default: false }
allMuted: { type: Boolean, default: false },
/** Phase 3 屏幕共享:当前用户是否在共享屏幕(决定按钮文案/高亮) */
screenSharing: { type: Boolean, default: false },
/** 屏幕共享操作中getDisplayMedia / produce / closeProducer 同步等待) */
screenLoading: { type: Boolean, default: false },
/** 是否禁用屏幕共享按钮(有他人在共享 / 平台不支持等场景) */
screenDisabled: { type: Boolean, default: false },
/** 屏幕共享按钮禁用原因hover 提示文案) */
screenDisabledReason: { type: String, default: '' },
/** Phase B 录制:是否显示录制按钮(仅 host 显示) */
showRecording: { type: Boolean, default: false },
/** Phase B 录制:当前是否在录制中 */
recording: { type: Boolean, default: false },
/** Phase B 录制REST 调用 / 上传中 loading 态,禁用按钮 */
recordingLoading: { type: Boolean, default: false }
})
const emit = defineEmits(['mic-toggle', 'cam-toggle', 'invite', 'members', 'chat', 'leave'])
const emit = defineEmits(['mic-toggle', 'cam-toggle', 'screen-toggle', 'recording-toggle', 'invite', 'members', 'chat', 'leave'])
/** 统一过滤 loading 态,避免重复点击 */
const emitIf = (name) => {
if (name === 'mic-toggle' && props.audioLoading) return
if (name === 'cam-toggle' && props.videoLoading) return
if (name === 'screen-toggle' && (props.screenLoading || props.screenDisabled)) return
if (name === 'recording-toggle' && props.recordingLoading) return
emit(name)
}
</script>
@@ -191,6 +255,16 @@ const emitIf = (name) => {
.btn-leave .icon { background: #EF4444; }
.btn-leave:hover .icon { background: #DC2626; }
/* Phase B 录制按钮:录制中红色脉动 */
.btn-recording.is-recording .icon {
background: #EF4444;
animation: recording-pulse 1.6s ease-in-out infinite;
}
@keyframes recording-pulse {
0%, 100% { box-shadow: 0 0 0 0 rgba(239, 68, 68, 0.55); }
50% { box-shadow: 0 0 0 12rpx rgba(239, 68, 68, 0); }
}
.badge {
position: absolute;
top: -6rpx;