fix(live): 管控区与预览横排断点、礼物条移至画面下方

Made-with: Cursor
This commit is contained in:
whm
2026-03-27 10:30:37 +08:00
parent 435fbfd47e
commit 2f78fd0d52
2 changed files with 155 additions and 70 deletions

View File

@@ -1,7 +1,6 @@
<template> <template>
<div class="live-broadcast live-broadcast--split"> <div class="live-broadcast">
<div class="live-broadcast-main"> <el-card class="live-broadcast-card">
<el-card>
<template #header> <template #header>
<span>官网视频直播WebRTC</span> <span>官网视频直播WebRTC</span>
</template> </template>
@@ -71,6 +70,7 @@
<el-button v-if="!session" type="primary" :disabled="!token" @click="start">开始直播</el-button> <el-button v-if="!session" type="primary" :disabled="!token" @click="start">开始直播</el-button>
<el-button v-else type="danger" @click="stop">结束直播</el-button> <el-button v-else type="danger" @click="stop">结束直播</el-button>
</div> </div>
<div class="live-preview-row">
<div ref="previewWrapRef" class="preview-wrap"> <div ref="previewWrapRef" class="preview-wrap">
<video <video
v-show="previewLayout !== 'screen_pip'" v-show="previewLayout !== 'screen_pip'"
@@ -100,10 +100,7 @@
@pointerdown.prevent="onPipPointerDown" @pointerdown.prevent="onPipPointerDown"
></video> ></video>
</div> </div>
</el-card> <aside class="live-moderation-aside" aria-label="观众与发言管控">
</div>
<aside class="live-broadcast-side" aria-label="观众与发言管控">
<el-card class="moderation-card" shadow="never"> <el-card class="moderation-card" shadow="never">
<template #header> <template #header>
<div class="moderation-head"> <div class="moderation-head">
@@ -191,6 +188,8 @@
</el-card> </el-card>
</aside> </aside>
</div> </div>
</el-card>
</div>
</template> </template>
<script setup> <script setup>
@@ -511,7 +510,43 @@ onBeforeRouteLeave(() => {
<style scoped> <style scoped>
.live-broadcast { .live-broadcast {
max-width: min(1200px, 100%); max-width: min(1680px, 100%);
}
/* 预览与观众管控同一行:画面在左、管控在右(勿在窄屏外误触发布局为上下) */
.live-preview-row {
display: flex;
flex-direction: row;
flex-wrap: nowrap;
align-items: flex-start;
gap: 16px;
margin-top: 4px;
width: 100%;
}
.live-moderation-aside {
flex: 0 0 auto;
width: clamp(280px, 34vw, 420px);
min-width: 260px;
max-height: calc(100vh - 180px);
overflow-y: auto;
overflow-x: hidden;
position: sticky;
top: 8px;
align-self: stretch;
}
/* 仅小屏/手机再上下堆叠,避免 1200px 以下管理后台侧栏占宽导致误变单列 */
@media (max-width: 768px) {
.live-preview-row {
flex-direction: column;
flex-wrap: wrap;
}
.live-moderation-aside {
flex: 1 1 auto;
width: 100%;
min-width: 0;
max-height: none;
position: static;
overflow-y: visible;
}
} }
.status { .status {
color: #409eff; color: #409eff;
@@ -548,8 +583,9 @@ onBeforeRouteLeave(() => {
} }
.preview-wrap { .preview-wrap {
position: relative; position: relative;
width: 100%; flex: 1 1 0;
max-width: 1100px; min-width: 0;
max-width: none;
} }
.preview-main { .preview-main {
display: block; display: block;

View File

@@ -57,7 +57,8 @@
<span class="live-gift-from">{{ g.from }} 送出</span> <span class="live-gift-from">{{ g.from }} 送出</span>
</div> </div>
</div> </div>
<div class="live-video-overlay" role="toolbar" aria-label="播放与音量"> <!-- 控制条放右上避免底栏占满与抖音类布局冲突 -->
<div class="live-video-overlay live-video-overlay--corner" role="toolbar" aria-label="播放与音量">
<div class="live-video-overlay-inner"> <div class="live-video-overlay-inner">
<button type="button" class="live-overlay-btn" @click="toggleMute"> <button type="button" class="live-overlay-btn" @click="toggleMute">
{{ displayMuted ? '开声音' : '静音' }} {{ displayMuted ? '开声音' : '静音' }}
@@ -95,7 +96,25 @@
<router-link class="live-dm-auth-link" to="/live/register">注册</router-link> <router-link class="live-dm-auth-link" to="/live/register">注册</router-link>
</template> </template>
</div> </div>
<div v-if="dmLoggedIn" class="live-gift-row live-gift-row--scroll" aria-label="礼物"> <div class="live-dm-bar">
<input
v-model="dmDraft"
class="live-dm-input"
type="text"
maxlength="120"
:placeholder="dmLoggedIn ? '发条弹幕…' : '登录后可发弹幕'"
autocomplete="off"
:disabled="!dmLoggedIn"
@keydown.enter.prevent="sendDm"
/>
<button type="button" class="live-dm-send" :disabled="!dmLoggedIn" @click="sendDm">发送</button>
</div>
<p v-if="dmHint" class="live-dm-hint">{{ dmHint }}</p>
</aside>
</div>
<!-- 礼物条全宽横条贴在画面区域下方类抖音底栏 -->
<div class="live-gift-dock" aria-label="礼物">
<div v-if="dmLoggedIn" class="live-gift-row live-gift-row--scroll">
<span class="live-gift-hint">送礼</span> <span class="live-gift-hint">送礼</span>
<div class="live-gift-strip"> <div class="live-gift-strip">
<button <button
@@ -112,24 +131,9 @@
</button> </button>
</div> </div>
</div> </div>
<div v-else class="live-gift-row"> <div v-else class="live-gift-row live-gift-row--guest">
<span class="live-gift-hint-muted">登录后可送礼物含火箭·跑车·飞机·嘉年华等</span> <span class="live-gift-hint-muted">登录后可送礼物含火箭·跑车·飞机·嘉年华等</span>
</div> </div>
<div class="live-dm-bar">
<input
v-model="dmDraft"
class="live-dm-input"
type="text"
maxlength="120"
:placeholder="dmLoggedIn ? '发条弹幕…' : '登录后可发弹幕'"
autocomplete="off"
:disabled="!dmLoggedIn"
@keydown.enter.prevent="sendDm"
/>
<button type="button" class="live-dm-send" :disabled="!dmLoggedIn" @click="sendDm">发送</button>
</div>
<p v-if="dmHint" class="live-dm-hint">{{ dmHint }}</p>
</aside>
</div> </div>
</div> </div>
<div v-if="dmToast" class="live-dm-toast" role="status">{{ dmToast }}</div> <div v-if="dmToast" class="live-dm-toast" role="status">{{ dmToast }}</div>
@@ -628,6 +632,31 @@ onUnmounted(() => {
width: 100%; width: 100%;
max-width: 100%; max-width: 100%;
margin: 0 auto; margin: 0 auto;
display: flex;
flex-direction: column;
align-items: stretch;
gap: 12px;
}
.live-gift-dock {
width: 100%;
box-sizing: border-box;
padding: 10px 12px 12px;
border-radius: 0 0 14px 14px;
background: rgba(0, 0, 0, 0.42);
border: 1px solid rgba(255, 255, 255, 0.08);
border-top: 1px solid rgba(0, 212, 255, 0.12);
}
.live-gift-dock .live-gift-row {
max-width: none;
margin: 0;
justify-content: center;
}
.live-gift-dock .live-gift-row--guest {
justify-content: center;
padding: 6px 0;
}
.live-gift-dock .live-gift-hint-muted {
text-align: center;
} }
.live-stage-inner { .live-stage-inner {
display: flex; display: flex;
@@ -651,7 +680,6 @@ onUnmounted(() => {
border: 1px solid rgba(255, 255, 255, 0.08); border: 1px solid rgba(255, 255, 255, 0.08);
} }
.live-room-side .live-dm-auth-row, .live-room-side .live-dm-auth-row,
.live-room-side .live-gift-row,
.live-room-side .live-dm-bar, .live-room-side .live-dm-bar,
.live-room-side .live-dm-hint { .live-room-side .live-dm-hint {
max-width: none; max-width: none;
@@ -659,9 +687,6 @@ onUnmounted(() => {
margin-right: 0; margin-right: 0;
justify-content: flex-start; justify-content: flex-start;
} }
.live-room-side .live-gift-hint-muted {
text-align: left;
}
@media (max-width: 900px) { @media (max-width: 900px) {
.live-stage-inner { .live-stage-inner {
flex-direction: column; flex-direction: column;
@@ -709,6 +734,13 @@ onUnmounted(() => {
display: flex; display: flex;
flex-direction: column; flex-direction: column;
overflow: hidden; overflow: hidden;
gap: 10px;
}
.live-stage:fullscreen .live-gift-dock,
.live-stage:-webkit-full-screen .live-gift-dock {
flex-shrink: 0;
border-radius: 12px;
margin-top: 0;
} }
.live-stage:fullscreen .live-stage-inner, .live-stage:fullscreen .live-stage-inner,
.live-stage:-webkit-full-screen .live-stage-inner { .live-stage:-webkit-full-screen .live-stage-inner {
@@ -761,13 +793,13 @@ onUnmounted(() => {
object-fit: cover; object-fit: cover;
} }
.live-stage:fullscreen .live-dm-auth-row, .live-stage:fullscreen .live-dm-auth-row,
.live-stage:fullscreen .live-gift-row,
.live-stage:fullscreen .live-dm-bar, .live-stage:fullscreen .live-dm-bar,
.live-stage:fullscreen .live-dm-hint, .live-stage:fullscreen .live-dm-hint,
.live-stage:fullscreen .live-gift-dock .live-gift-row,
.live-stage:-webkit-full-screen .live-dm-auth-row, .live-stage:-webkit-full-screen .live-dm-auth-row,
.live-stage:-webkit-full-screen .live-gift-row,
.live-stage:-webkit-full-screen .live-dm-bar, .live-stage:-webkit-full-screen .live-dm-bar,
.live-stage:-webkit-full-screen .live-dm-hint { .live-stage:-webkit-full-screen .live-dm-hint,
.live-stage:-webkit-full-screen .live-gift-dock .live-gift-row {
max-width: none; max-width: none;
} }
.live-video-overlay { .live-video-overlay {
@@ -781,6 +813,23 @@ onUnmounted(() => {
background: linear-gradient(to top, rgba(0, 0, 0, 0.82) 0%, rgba(0, 0, 0, 0.45) 55%, transparent 100%); background: linear-gradient(to top, rgba(0, 0, 0, 0.82) 0%, rgba(0, 0, 0, 0.45) 55%, transparent 100%);
pointer-events: auto; pointer-events: auto;
} }
/* 右上条:不占满底栏,避免与右侧互动区「头重脚轻」 */
.live-video-overlay--corner {
left: auto;
bottom: auto;
top: 0;
right: 0;
width: auto;
max-width: min(100%, 400px);
padding: 10px 10px 12px 16px;
border-radius: 0 14px 0 16px;
background: linear-gradient(
to left,
rgba(0, 0, 0, 0.78) 0%,
rgba(0, 0, 0, 0.35) 65%,
transparent 100%
);
}
.live-video-overlay-inner { .live-video-overlay-inner {
display: flex; display: flex;
flex-wrap: wrap; flex-wrap: wrap;