diff --git a/frontend/src/components/meeting/InviteDialog.vue b/frontend/src/components/meeting/InviteDialog.vue
new file mode 100644
index 0000000..63cc514
--- /dev/null
+++ b/frontend/src/components/meeting/InviteDialog.vue
@@ -0,0 +1,319 @@
+
+
+
+
+
+ 邀请加入会议
+
+
+
+
+
+
+
+ 会议主题
+ {{ meetingTitle || '—' }}
+
+
+
+ 会议号
+
+ {{ formattedCode }}
+
+
+
+
+
+ 会议密码
+
+ {{ password }}
+
+
+
+
+
+ 加入链接
+
+ {{ joinLink }}
+
+
+
+
+
+ {{ inviteText }}
+
+
+
+ {{ toast }}
+
+
+
+
+
+
+
+
diff --git a/frontend/src/components/meeting/MeetingToolbar.vue b/frontend/src/components/meeting/MeetingToolbar.vue
new file mode 100644
index 0000000..9337b97
--- /dev/null
+++ b/frontend/src/components/meeting/MeetingToolbar.vue
@@ -0,0 +1,193 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/frontend/src/components/meeting/MemberPanel.vue b/frontend/src/components/meeting/MemberPanel.vue
new file mode 100644
index 0000000..12e371e
--- /dev/null
+++ b/frontend/src/components/meeting/MemberPanel.vue
@@ -0,0 +1,336 @@
+
+
+
+
+
+
+
+
+
+ {{ nameInitial(p) }}
+
+
+
+ {{ displayName(p) }}
+ 你
+ 主持人
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 当前会议室还没有其他成员
+
+
+
+
+
+
+
+
+
diff --git a/frontend/src/components/meeting/NetworkBadge.vue b/frontend/src/components/meeting/NetworkBadge.vue
new file mode 100644
index 0000000..ea1b542
--- /dev/null
+++ b/frontend/src/components/meeting/NetworkBadge.vue
@@ -0,0 +1,75 @@
+
+
+
+
+ {{ labelText }}
+
+
+
+
+
+
diff --git a/frontend/src/components/meeting/VideoGrid.vue b/frontend/src/components/meeting/VideoGrid.vue
new file mode 100644
index 0000000..0605f13
--- /dev/null
+++ b/frontend/src/components/meeting/VideoGrid.vue
@@ -0,0 +1,100 @@
+
+
+
+
+
+
+
+
+
+ 暂无成员,等待其他人加入…
+
+
+
+
+
+
+
diff --git a/frontend/src/components/meeting/VideoTile.vue b/frontend/src/components/meeting/VideoTile.vue
new file mode 100644
index 0000000..8754a93
--- /dev/null
+++ b/frontend/src/components/meeting/VideoTile.vue
@@ -0,0 +1,273 @@
+
+
+
+
+
+
+ {{ avatarInitial }}
+
+
+ 你
+ 主持人
+
+
+
+
+
+
+
+
diff --git a/frontend/src/pages.json b/frontend/src/pages.json
index 4c29d41..1464121 100644
--- a/frontend/src/pages.json
+++ b/frontend/src/pages.json
@@ -166,6 +166,13 @@
"navigationBarTitleText": "设备预览"
}
},
+ {
+ "path": "pages/meeting/room",
+ "style": {
+ "navigationBarTitleText": "会议进行中",
+ "navigationStyle": "custom"
+ }
+ },
{
"path": "pages/notify/index",
"style": {
diff --git a/frontend/src/pages/meeting/preview.vue b/frontend/src/pages/meeting/preview.vue
index 6c21dda..a0dfd0d 100644
--- a/frontend/src/pages/meeting/preview.vue
+++ b/frontend/src/pages/meeting/preview.vue
@@ -12,8 +12,7 @@
- 「入会默认开麦/摄像头」两个开关写入 mediaPrefs
- 兜底:权限被拒 / 浏览器不支持 WebRTC 时给明确错误提示
- 入会成功后(Task 11 room 页未就绪期间):uni.redirectTo 到会议调试页 debug.vue 作为会议室占位。
- TODO(Task 11 落地后):改为 uni.redirectTo '/pages/meeting/room?code=xxx'
+ 入会成功后:uni.redirectTo('/pages/meeting/room?code=xxx')
-->
@@ -362,8 +361,8 @@ const onJoin = async () => {
} else {
await meetingStore.joinAndEnter(joinCode.value, joinPassword.value, prefs)
}
- // Task 11 会议室页尚未落地,临时跳转 debug.vue 作为会议室占位
- uni.redirectTo({ url: '/pages/meeting/debug' })
+ const roomCode = meetingStore.currentRoom?.room_code
+ uni.redirectTo({ url: `/pages/meeting/room${roomCode ? `?code=${roomCode}` : ''}` })
} catch (err) {
const msg = err?.message || JSON.stringify(err)
uni.showToast({ title: `加入失败:${msg}`, icon: 'none', duration: 3500 })
diff --git a/frontend/src/pages/meeting/room.vue b/frontend/src/pages/meeting/room.vue
new file mode 100644
index 0000000..c62f412
--- /dev/null
+++ b/frontend/src/pages/meeting/room.vue
@@ -0,0 +1,647 @@
+
+
+
+
+
+
+ {{ meetingTitle }}
+
+ {{ formattedCode }}
+
+
+
+
+
+
+ {{ stateText }}
+
+
+ {{ durationText }}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 确认{{ isHost ? '结束或离开' : '离开' }}会议?
+
+ {{ isHost ? '作为主持人,你可以选择结束会议(所有成员都会离开),或仅自己离开(自动转让给其他成员)。' : '离开后可通过会议号重新加入。' }}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/frontend/src/store/meeting.js b/frontend/src/store/meeting.js
index 54ef83f..efe07de 100644
--- a/frontend/src/store/meeting.js
+++ b/frontend/src/store/meeting.js
@@ -306,11 +306,13 @@ export const useMeetingStore = defineStore('meeting', () => {
try {
const consumer = await _engine.consume({ producerId: data.producer_id })
if (!remoteConsumers[data.user_id]) {
- remoteConsumers[data.user_id] = markRaw({
+ // 外层 slot 保持 reactive 以便 slot.audio/slot.video 赋值能驱动 VideoTile 重渲染;
+ // 仅 Consumer 实例和 Set 本身用 markRaw 隔离 Vue 代理
+ remoteConsumers[data.user_id] = {
audio: null,
video: null,
- producerIds: new Set()
- })
+ producerIds: markRaw(new Set())
+ }
}
const slot = remoteConsumers[data.user_id]
slot[consumer.kind] = markRaw(consumer)