设置了默认地址并修复退出房间异常问题,已部署成功

This commit is contained in:
Cloyir 2026-01-12 18:09:36 +08:00
parent b7f087536b
commit 985ff72906
9 changed files with 166 additions and 28 deletions

3
.gitignore vendored
View File

@ -36,9 +36,6 @@ src-tauri/target/
.env.local .env.local
.env.*.local .env.*.local
# Config
config.json
# OS # OS
.DS_Store .DS_Store
Thumbs.db Thumbs.db

View File

@ -75,6 +75,26 @@ io.on('connection', (socket) => {
socket.emit('room-users', roomUsers); socket.emit('room-users', roomUsers);
}); });
socket.on('leave-room', ({ roomId, userId, socketId }) => {
const user = users.get(socketId);
if (user && user.roomId === roomId) {
const room = rooms.get(roomId);
if (room) {
room.delete(socketId);
socket.to(roomId).emit('user-left', {
userId: user.userId,
socketId: socketId
});
if (room.size === 0) {
rooms.delete(roomId);
}
}
users.delete(socketId);
socket.leave(roomId);
}
});
socket.on('signal', ({ targetSocketId, signal }) => { socket.on('signal', ({ targetSocketId, signal }) => {
const targetUser = users.get(targetSocketId); const targetUser = users.get(targetSocketId);
if (targetUser) { if (targetUser) {

View File

@ -1,5 +1,5 @@
<script setup> <script setup>
import { ref, onMounted, onUnmounted } from 'vue'; import { ref, computed, onMounted, onUnmounted } from 'vue';
import { io } from 'socket.io-client'; import { io } from 'socket.io-client';
// //
@ -16,7 +16,7 @@ import { useWebRTC } from './composables/useWebRTC.js';
import { useFileTransfer } from './composables/useFileTransfer.js'; import { useFileTransfer } from './composables/useFileTransfer.js';
// 使 // 使
const { socket, userId, nickname, connected, roomUsers, roomId, joinRoom, serverUrl, connectToServer, disconnectFromServer } = useSocket(); const { socket, userId, nickname, connected, roomUsers, roomId, joinRoom, leaveRoom, onUserLeft, serverUrl, connectToServer, disconnectFromServer } = useSocket();
const localServerUrl = ref(serverUrl.value); const localServerUrl = ref(serverUrl.value);
// //
@ -35,7 +35,8 @@ const {
createPeerConnection, createPeerConnection,
handleSignal, handleSignal,
setupDataChannel, setupDataChannel,
closePeerConnection closePeerConnection,
closeAllPeerConnections
} = useWebRTC(); } = useWebRTC();
const { const {
transfers, transfers,
@ -45,7 +46,8 @@ const {
sendFile, sendFile,
acceptFile, acceptFile,
rejectFile, rejectFile,
selectFile selectFile,
clearTransfers
} = useFileTransfer(); } = useFileTransfer();
// //
@ -100,8 +102,29 @@ function handleDisconnectUser(socketId) {
closePeerConnection(socketId); closePeerConnection(socketId);
} }
// //
const hasActiveTransfer = computed(() => {
return transfers.value.some(t =>
t.status === 'sending' || t.status === 'receiving'
);
});
// 退
function handleLeaveRoom() {
if (hasActiveTransfer.value) {
return;
}
closeAllPeerConnections();
clearTransfers();
leaveRoom();
}
//
onMounted(() => { onMounted(() => {
onUserLeft.value = (socketId) => {
closePeerConnection(socketId);
};
if (socket.value) { if (socket.value) {
socket.value.on('signal', async ({ senderSocketId, signal }) => { socket.value.on('signal', async ({ senderSocketId, signal }) => {
await handleSignal( await handleSignal(
@ -182,8 +205,10 @@ onUnmounted(() => {
:peer-connections="peerConnections" :peer-connections="peerConnections"
:room-id="roomId" :room-id="roomId"
:current-socket-id="socket?.id" :current-socket-id="socket?.id"
:has-active-transfer="hasActiveTransfer"
@connect-user="connectToUser" @connect-user="connectToUser"
@disconnect-user="handleDisconnectUser" @disconnect-user="handleDisconnectUser"
@leave-room="handleLeaveRoom"
/> />
<FileSend <FileSend

View File

@ -1,6 +1,16 @@
<template> <template>
<div class="section" v-if="roomId"> <div class="section" v-if="roomId">
<div class="room-header">
<h2>{{ $t('room.users_in_room') }}</h2> <h2>{{ $t('room.users_in_room') }}</h2>
<button
@click="leaveRoom"
:disabled="hasActiveTransfer"
class="leave-room-btn"
:title="hasActiveTransfer ? $t('room.cannot_leave_room') : ''"
>
{{ $t('room.leave_room') }}
</button>
</div>
<div class="room-info"> <div class="room-info">
<span class="room-id">{{ $t('room.room_id') }}: {{ roomId }}</span> <span class="room-id">{{ $t('room.room_id') }}: {{ roomId }}</span>
<span class="user-count">{{ $t('room.users_in_room') }}: {{ roomUsers.length }}</span> <span class="user-count">{{ $t('room.users_in_room') }}: {{ roomUsers.length }}</span>
@ -57,11 +67,15 @@ const props = defineProps({
currentSocketId: { currentSocketId: {
type: String, type: String,
default: '' default: ''
},
hasActiveTransfer: {
type: Boolean,
default: false
} }
}); });
// emit // emit
const emit = defineEmits(['connectUser', 'disconnectUser']); const emit = defineEmits(['connectUser', 'disconnectUser', 'leaveRoom']);
// //
function isCurrentUser(socketId) { function isCurrentUser(socketId) {
@ -77,6 +91,11 @@ function connectToUser(socketId) {
function disconnectFromUser(socketId) { function disconnectFromUser(socketId) {
emit('disconnectUser', socketId); emit('disconnectUser', socketId);
} }
// 退
function leaveRoom() {
emit('leaveRoom');
}
</script> </script>
<style scoped> <style scoped>
@ -88,6 +107,42 @@ function disconnectFromUser(socketId) {
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1); box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
} }
.room-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 15px;
}
.room-header h2 {
margin: 0;
}
.leave-room-btn {
padding: 8px 16px;
background: #ff9800;
color: white;
border: none;
border-radius: 4px;
cursor: pointer;
transition: all 0.2s ease;
font-size: 0.9em;
}
.leave-room-btn:disabled {
background: #ccc;
cursor: not-allowed;
}
.leave-room-btn:hover:not(:disabled) {
transform: translateY(-1px);
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.2);
}
.leave-room-btn:active:not(:disabled) {
transform: translateY(0);
}
.room-info { .room-info {
display: flex; display: flex;
gap: 15px; gap: 15px;

View File

@ -319,6 +319,11 @@ export function useFileTransfer() {
} }
} }
function clearTransfers() {
transfers.value = [];
pendingTransfers.value = [];
}
return { return {
transfers, transfers,
pendingTransfers, pendingTransfers,
@ -327,6 +332,7 @@ export function useFileTransfer() {
sendFile, sendFile,
acceptFile, acceptFile,
rejectFile, rejectFile,
selectFile selectFile,
clearTransfers
}; };
} }

View File

@ -1,16 +1,12 @@
import { ref, onMounted, onUnmounted } from 'vue'; import { ref, onMounted, onUnmounted } from 'vue';
import { io } from 'socket.io-client'; import { io } from 'socket.io-client';
import { generateUserId } from '../utils'; import { generateUserId } from '../utils';
import config from '../../config.json';
function isTauri() { function isTauri() {
return window.__TAURI__ !== undefined; return window.__TAURI__ !== undefined;
} }
function getDefaultServerUrl() {
return config.defaultServerUrl || 'http://localhost:3000';
}
export function useSocket() { export function useSocket() {
const socket = ref(null); const socket = ref(null);
const userId = ref(''); const userId = ref('');
@ -18,7 +14,8 @@ export function useSocket() {
const connected = ref(false); const connected = ref(false);
const roomUsers = ref([]); const roomUsers = ref([]);
const roomId = ref(''); const roomId = ref('');
const serverUrl = ref(localStorage.getItem('p2p-server-url') || getDefaultServerUrl()); const serverUrl = ref(localStorage.getItem('p2p-server-url') || 'wss://p2p-file-transfer.cloyir.com');
const onUserLeft = ref(null);
onMounted(() => { onMounted(() => {
userId.value = generateUserId(); userId.value = generateUserId();
@ -83,6 +80,9 @@ export function useSocket() {
socket.value.on('user-left', ({ userId: leftUserId, socketId }) => { socket.value.on('user-left', ({ userId: leftUserId, socketId }) => {
roomUsers.value = roomUsers.value.filter(u => u.socketId !== socketId); roomUsers.value = roomUsers.value.filter(u => u.socketId !== socketId);
if (onUserLeft.value) {
onUserLeft.value(socketId);
}
}); });
} }
@ -107,6 +107,18 @@ export function useSocket() {
} }
} }
function leaveRoom() {
if (roomId.value && socket.value) {
socket.value.emit('leave-room', {
roomId: roomId.value,
userId: userId.value,
socketId: socket.value.id
});
roomId.value = '';
roomUsers.value = [];
}
}
return { return {
socket, socket,
userId, userId,
@ -115,8 +127,10 @@ export function useSocket() {
roomUsers, roomUsers,
roomId, roomId,
serverUrl, serverUrl,
joinRoom, onUserLeft,
connectToServer, connectToServer,
disconnectFromServer disconnectFromServer,
joinRoom,
leaveRoom
}; };
} }

View File

@ -137,6 +137,8 @@ export function useWebRTC() {
channel.onclose = () => { channel.onclose = () => {
console.log('Data channel closed with:', socketId); console.log('Data channel closed with:', socketId);
dataChannelReady.value.delete(socketId); dataChannelReady.value.delete(socketId);
dataChannels.value.delete(socketId);
peerConnections.value.delete(socketId);
}; };
} }
@ -153,6 +155,20 @@ export function useWebRTC() {
} }
} }
function closeAllPeerConnections() {
peerConnections.value.forEach((pc, socketId) => {
pc.close();
});
peerConnections.value.clear();
dataChannels.value.forEach((dc) => {
dc.close();
});
dataChannels.value.clear();
dataChannelReady.value.clear();
}
return { return {
peerConnections, peerConnections,
dataChannels, dataChannels,
@ -160,6 +176,7 @@ export function useWebRTC() {
createPeerConnection, createPeerConnection,
handleSignal, handleSignal,
setupDataChannel, setupDataChannel,
closePeerConnection closePeerConnection,
closeAllPeerConnections
}; };
} }

View File

@ -17,7 +17,9 @@ export default {
nickname: 'Nickname', nickname: 'Nickname',
join: 'Join', join: 'Join',
leave: 'Leave', leave: 'Leave',
users_in_room: 'Users in Room' users_in_room: 'Users in Room',
leave_room: 'Leave Room',
cannot_leave_room: 'Cannot leave room while transferring files'
}, },
file_transfer: { file_transfer: {
connect: 'Connect', connect: 'Connect',

View File

@ -17,7 +17,9 @@ export default {
nickname: '昵称', nickname: '昵称',
join: '加入', join: '加入',
leave: '离开', leave: '离开',
users_in_room: '房间内用户' users_in_room: '房间内用户',
leave_room: '退出房间',
cannot_leave_room: '正在传输文件,无法退出房间'
}, },
file_transfer: { file_transfer: {
connect: '连接', connect: '连接',