```
refactor(meeting): 优化悬浮窗交互和会议控制功能 - 移除废弃的环境配置模块文件 - 简化悬浮球组件的折叠逻辑,移除复杂的窗口位置计算 - 将静态图片图标替换为动态图标组件,提升视觉效果 - 重构悬浮列表窗口的大小调整逻辑,实现折叠展开平滑过渡 - 优化教师端全员静音功能,使用displayUsers替代studentUsers - 调整会议控制事件处理函数的调用方式,提升响应性能 - 添加窗口边界检测,确保折叠展开时窗口始终可见 ```
This commit is contained in:
11
src-tauri/src/env/mod.rs
vendored
11
src-tauri/src/env/mod.rs
vendored
@ -1,11 +0,0 @@
|
||||
//! 环境配置模块
|
||||
// const TEACHERCONTROL_DEV_URL: &str = "https://jk.qyzhjy.com/TeacherControl_bak.shtml";
|
||||
const TEACHERCONTROL_DEV_URL: &str = "https://socket.qyzhjy.com/JKList/TeacherControl_bak.shtml";
|
||||
const TEACHERCONTROL_PROD_URL: &str = "https://jk.qyzhjy.com/TeacherControl_bak.shtml";
|
||||
|
||||
/// 实时字幕地址
|
||||
pub const TEACHERCONTROL_JK_URL: &str = if cfg!(feature = "development") || cfg!(feature = "prod_150_8080") {
|
||||
TEACHERCONTROL_DEV_URL
|
||||
} else {
|
||||
TEACHERCONTROL_PROD_URL
|
||||
};
|
||||
@ -51,29 +51,16 @@
|
||||
|
||||
<script setup lang="ts">
|
||||
import { playPromptTone } from '@/utils/prompt-tone';
|
||||
import { handleMouseDown, handleTouchStart, startWindowX, startWindowY } from '../move-window';
|
||||
import { LogicalSize, getCurrentWindow } from '@tauri-apps/api/window';
|
||||
import { handleMouseDown, handleTouchStart } from '../move-window';
|
||||
|
||||
const isFold = defineModel<boolean>();
|
||||
const currentWindow = getCurrentWindow();
|
||||
// 使用Tauri API直接移动窗口,保持与outerPosition()返回类型一致
|
||||
currentWindow.setSize(new LogicalSize(60, 50));
|
||||
|
||||
/**
|
||||
* 切换折叠状态
|
||||
*/
|
||||
async function toggleFold() {
|
||||
// 获取缩放因子和窗口物理位置
|
||||
const [factor, windowPhysicalPosition] = await Promise.all([currentWindow.scaleFactor(), currentWindow.outerPosition()]);
|
||||
// 转换为逻辑像素位置
|
||||
const windowPosition = windowPhysicalPosition.toLogical(factor);
|
||||
|
||||
const newX = windowPosition.x - startWindowX.value;
|
||||
const newY = windowPosition.y - startWindowY.value;
|
||||
if (Math.abs(newX) < 4 && Math.abs(newY) < 4) {
|
||||
playPromptTone();
|
||||
isFold.value = !isFold.value;
|
||||
}
|
||||
function toggleFold() {
|
||||
playPromptTone();
|
||||
isFold.value = !isFold.value;
|
||||
}
|
||||
</script>
|
||||
|
||||
|
||||
@ -11,19 +11,19 @@
|
||||
<div style="width: calc(100% - 12px); height: 1px; margin: 9px 10px; background: rgba(180, 180, 180, 0.6)"></div>
|
||||
<!-- 会议控制按钮 -->
|
||||
<div class="menu-item" @click="muteAllStudents()">
|
||||
<img class="menu-icon" src="@/assets/images/floating-list-window/icon_02_04.png" />
|
||||
<icon-mdi-volume-high class="menu-icon"></icon-mdi-volume-high>
|
||||
<span class="menu-text">全员静音</span>
|
||||
</div>
|
||||
<div class="menu-item" @click="muteTeacher()">
|
||||
<img class="menu-icon" src="@/assets/images/floating-list-window/icon_02_05.png" />
|
||||
<icon-mdi-microphone-off class="menu-icon"></icon-mdi-microphone-off>
|
||||
<span class="menu-text">老师静音</span>
|
||||
</div>
|
||||
<div class="menu-item" @click="endClass()">
|
||||
<img class="menu-icon" src="@/assets/images/floating-list-window/icon_02_06.png" />
|
||||
<icon-mdi-school class="menu-icon"></icon-mdi-school>
|
||||
<span class="menu-text">下课</span>
|
||||
</div>
|
||||
<div class="menu-item" @click="openConsole()">
|
||||
<img class="menu-icon" src="@/assets/images/floating-list-window/icon_02_07.png" />
|
||||
<icon-mdi-console class="menu-icon"></icon-mdi-console>
|
||||
<span class="menu-text">控制台</span>
|
||||
</div>
|
||||
</div>
|
||||
@ -32,31 +32,23 @@
|
||||
<script setup lang="ts">
|
||||
import { emitTo } from '@tauri-apps/api/event';
|
||||
import { windowLabel } from '@/utils/tauri/windows-util';
|
||||
import { tauri_floating_list_page_action } from '@/utils/constant';
|
||||
import { handleMouseDown, handleTouchStart, startWindowX, startWindowY } from '../move-window';
|
||||
import { handleMouseDown, handleTouchStart } from '../move-window';
|
||||
|
||||
import { getCurrentWindow } from '@tauri-apps/api/window';
|
||||
import { playPromptTone } from '@/utils/prompt-tone';
|
||||
import type { FloatingListPageAction } from '@/utils/tauri/types';
|
||||
const isFold = defineModel<boolean>();
|
||||
import { createMainWindow } from '@/utils/tauri/windows-action';
|
||||
|
||||
const currentWindow = getCurrentWindow();
|
||||
// 导入图标组件
|
||||
import IconMdiVolumeHigh from '~icons/mdi/volume-high';
|
||||
import IconMdiMicrophoneOff from '~icons/mdi/microphone-off';
|
||||
import IconMdiSchool from '~icons/mdi/school';
|
||||
import IconMdiConsole from '~icons/mdi/console';
|
||||
const isFold = defineModel<boolean>();
|
||||
|
||||
/**
|
||||
* 切换折叠状态
|
||||
*/
|
||||
async function toggleFold() {
|
||||
// 获取缩放因子和窗口物理位置
|
||||
const [factor, windowPhysicalPosition] = await Promise.all([currentWindow.scaleFactor(), currentWindow.outerPosition()]);
|
||||
// 转换为逻辑像素位置
|
||||
const windowPosition = windowPhysicalPosition.toLogical(factor);
|
||||
|
||||
const newX = windowPosition.x - startWindowX.value;
|
||||
const newY = windowPosition.y - startWindowY.value;
|
||||
if (Math.abs(newX) < 4 && Math.abs(newY) < 4) {
|
||||
playPromptTone();
|
||||
isFold.value = !isFold.value;
|
||||
}
|
||||
function toggleFold() {
|
||||
isFold.value = !isFold.value;
|
||||
}
|
||||
/**
|
||||
* 全员静音 (不包括老师)
|
||||
@ -98,13 +90,6 @@
|
||||
playPromptTone();
|
||||
await createMainWindow();
|
||||
}
|
||||
|
||||
/**
|
||||
* 向某个窗口发送消息
|
||||
*/
|
||||
function sendtPageActionEmit(action: FloatingListPageAction) {
|
||||
emitTo<FloatingListPageAction>(windowLabel.main, tauri_floating_list_page_action, action);
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
@ -183,11 +168,12 @@
|
||||
.menu-container .menu-item .menu-icon {
|
||||
width: 36px;
|
||||
height: 36px;
|
||||
color: #ffffff; // 白色图标,与深色背景形成对比
|
||||
}
|
||||
|
||||
.menu-container .menu-item .menu-text {
|
||||
font-size: 12px;
|
||||
color: #ffffff;
|
||||
color: #ffffff; // 白色文字
|
||||
text-shadow: 2px 2px 4px #000000;
|
||||
}
|
||||
</style>
|
||||
|
||||
@ -6,11 +6,115 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { onMounted, ref } from 'vue';
|
||||
import { onMounted, ref, watch } from 'vue';
|
||||
import { LogicalSize, PhysicalPosition, currentMonitor, getCurrentWindow } from '@tauri-apps/api/window';
|
||||
import FloatingBall from './aa-components/floating-ball.vue';
|
||||
import FloatingList from './aa-components/floating-list.vue';
|
||||
|
||||
const isFold = ref(false);
|
||||
const currentWindow = getCurrentWindow();
|
||||
|
||||
/**
|
||||
* 设置折叠状态下的窗口大小
|
||||
*/
|
||||
async function setFoldedWindowSize() {
|
||||
const factor = await currentWindow.scaleFactor();
|
||||
const windowPhysicalPos = await currentWindow.outerPosition();
|
||||
const windowLogicalPos = windowPhysicalPos.toLogical(factor);
|
||||
|
||||
// 折叠时设置为较小的固定大小
|
||||
const foldedWidth = 60;
|
||||
const foldedHeight = 50;
|
||||
|
||||
// 设置窗口大小
|
||||
currentWindow.setSize(new LogicalSize(foldedWidth, foldedHeight));
|
||||
|
||||
// 获取显示器信息以确保窗口可见
|
||||
const monitor = await currentMonitor();
|
||||
if (monitor) {
|
||||
const screenLogicalSize = monitor.size.toLogical(factor);
|
||||
|
||||
// 确保窗口在屏幕范围内
|
||||
const newY = Math.min(windowLogicalPos.y, screenLogicalSize.height - foldedHeight);
|
||||
const finalY = Math.max(0, newY);
|
||||
|
||||
if (finalY !== windowLogicalPos.y) {
|
||||
await currentWindow.setPosition(new PhysicalPosition(windowLogicalPos.x * factor, finalY * factor));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新窗口大小并确保完全可见 (从折叠状态展开时使用)
|
||||
*/
|
||||
async function expandWindowSizeFromFolded() {
|
||||
// 获取当前显示器信息
|
||||
const monitor = await currentMonitor();
|
||||
if (!monitor) {
|
||||
return;
|
||||
}
|
||||
|
||||
const factor = await currentWindow.scaleFactor();
|
||||
|
||||
// 关键:先获取折叠状态下的窗口位置 (此时还未改变大小)
|
||||
const windowPhysicalPos = await currentWindow.outerPosition();
|
||||
const windowLogicalPos = windowPhysicalPos.toLogical(factor);
|
||||
|
||||
// 计算展开后的窗口大小:4 个按钮 (全员静音、老师静音、下课、控制台)
|
||||
const item_num = 4;
|
||||
const popup_height = 64 * item_num + 60; // 316px
|
||||
const popup_width = 60;
|
||||
|
||||
// 关键修复:增加额外余量,避免因为 padding/border 导致内容显示不全
|
||||
const extraPadding = 8; // 上下各留 4px 余量
|
||||
const actualHeight = popup_height + extraPadding; // 324px
|
||||
|
||||
// 获取屏幕尺寸 (逻辑像素)
|
||||
const screenLogicalSize = monitor.size.toLogical(factor);
|
||||
|
||||
// 关键:使用展开后的高度重新计算位置,确保窗口完全可见
|
||||
// 如果当前 Y 坐标 + 展开后的高度 > 屏幕高度,则需要向上移动
|
||||
const maxY = screenLogicalSize.height - actualHeight;
|
||||
let finalY = windowLogicalPos.y;
|
||||
|
||||
if (finalY > maxY) {
|
||||
// 窗口底部超出屏幕,向上移动到刚好显示的位置
|
||||
finalY = maxY;
|
||||
}
|
||||
|
||||
// 确保不小于 0
|
||||
finalY = Math.max(0, finalY);
|
||||
|
||||
// 关键修复:先移动到安全位置,再改变窗口大小
|
||||
// 转换为物理像素
|
||||
const physicalX = Math.round(windowLogicalPos.x * factor);
|
||||
const physicalY = Math.round(finalY * factor);
|
||||
console.log(' 最终 physicalY:', physicalY, factor);
|
||||
|
||||
// 第二步:再设置展开后的窗口大小 (使用实际高度)
|
||||
await currentWindow.setSize(new LogicalSize(popup_width, actualHeight));
|
||||
|
||||
setTimeout(() => {
|
||||
// 第一步:先移动到目标位置 (此时窗口还是折叠状态的小尺寸)
|
||||
currentWindow.setPosition(new PhysicalPosition(physicalX, physicalY));
|
||||
}, 1000);
|
||||
}
|
||||
|
||||
// 监听折叠状态变化,调整窗口大小并确保完全可见
|
||||
watch(
|
||||
() => isFold.value,
|
||||
async (newVal) => {
|
||||
if (newVal) {
|
||||
// 折叠成悬浮球:设置小窗口并确保可见
|
||||
await setFoldedWindowSize();
|
||||
} else {
|
||||
// 展开成列表:设置完整窗口大小并确保完全可见
|
||||
// 从折叠状态展开时,使用专门的方法处理位置调整
|
||||
await expandWindowSizeFromFolded();
|
||||
}
|
||||
},
|
||||
{ immediate: true }
|
||||
);
|
||||
|
||||
onMounted(() => {
|
||||
document.addEventListener('contextmenu', (e) => {
|
||||
|
||||
@ -896,46 +896,18 @@
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理来自悬浮窗的会议控制事件
|
||||
* @param action - 控制动作
|
||||
*/
|
||||
async function handleMeetingControl(action: string): Promise<void> {
|
||||
console.log('[教师端] 处理会议控制动作:', action);
|
||||
|
||||
switch (action) {
|
||||
case 'mute_all_students': {
|
||||
// 全员静音 (不包括老师)
|
||||
await muteAllStudents();
|
||||
break;
|
||||
}
|
||||
case 'mute_teacher': {
|
||||
// 老师静音
|
||||
await muteTeacher();
|
||||
break;
|
||||
}
|
||||
case 'end_class': {
|
||||
// 下课
|
||||
await endClassAndLeave();
|
||||
break;
|
||||
}
|
||||
default:
|
||||
console.warn('[教师端] 未知的控制动作:', action);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 全员静音 (不包括老师)
|
||||
*/
|
||||
async function muteAllStudents(): Promise<void> {
|
||||
function muteAllStudents(): void {
|
||||
if (!roomId.value) {
|
||||
Toast.warning('房间未初始化');
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
try {
|
||||
// 遍历所有学生用户,发送禁麦消息
|
||||
const studentUids = studentUsers.value.map((u) => u.uid);
|
||||
const studentUids = displayUsers.value.map((u: DisplayUser) => u.uid);
|
||||
for (const uid of studentUids) {
|
||||
socket.emit('client_mute_audio', {
|
||||
roomId: roomId.value,
|
||||
@ -952,12 +924,12 @@
|
||||
/**
|
||||
* 老师静音
|
||||
*/
|
||||
async function muteTeacher(): Promise<void> {
|
||||
function muteTeacher(): void {
|
||||
if (!roomId.value || !shortUid.value) {
|
||||
Toast.warning('房间未初始化');
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
try {
|
||||
// 给自己发送禁麦消息
|
||||
socket.emit('client_mute_audio', {
|
||||
@ -971,6 +943,34 @@
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理来自悬浮窗的会议控制事件
|
||||
* @param action - 控制动作
|
||||
*/
|
||||
async function handleMeetingControl(action: string): Promise<void> {
|
||||
console.log('[教师端] 处理会议控制动作:', action);
|
||||
|
||||
switch (action) {
|
||||
case 'mute_all_students': {
|
||||
// 全员静音 (不包括老师)
|
||||
muteAllStudents();
|
||||
break;
|
||||
}
|
||||
case 'mute_teacher': {
|
||||
// 老师静音
|
||||
muteTeacher();
|
||||
break;
|
||||
}
|
||||
case 'end_class': {
|
||||
// 下课
|
||||
await endClassAndLeave();
|
||||
break;
|
||||
}
|
||||
default:
|
||||
console.warn('[教师端] 未知的控制动作:', action);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 组件挂载生命周期钩子
|
||||
*/
|
||||
|
||||
Reference in New Issue
Block a user