优化目录结构

This commit is contained in:
2026-05-25 20:37:36 +08:00
parent bbe7e71274
commit 40fd7f7e08
101 changed files with 108 additions and 110 deletions

View File

@@ -0,0 +1,77 @@
export const DEFAULT_PARTICIPANT_NAME = '参与者';
export const DEFAULT_PARTICIPANT_AVATAR = '/images/p2.png';
const DEFAULT_MEDIA_STATE = Object.freeze({
audio: false,
video: true,
isSpeaking: false
});
function createParticipantRecord(current = {}, patch = {}) {
return {
id: '',
name: DEFAULT_PARTICIPANT_NAME,
avatar: DEFAULT_PARTICIPANT_AVATAR,
mediaState: { ...DEFAULT_MEDIA_STATE },
status: 'online',
...current,
...patch,
mediaState: {
...DEFAULT_MEDIA_STATE,
...(current.mediaState || {}),
...(patch.mediaState || {})
}
};
}
export function upsertParticipant(participants, participantId, patch = {}) {
if (!participantId) {
return null;
}
participants[participantId] = createParticipantRecord(participants[participantId], patch);
return participants[participantId];
}
export function removeParticipant(participants, participantId) {
if (!participantId || !participants[participantId]) {
return false;
}
delete participants[participantId];
return true;
}
export function omitParticipant(participants, excludedParticipantId) {
const filtered = {};
for (const [participantId, participant] of Object.entries(participants || {})) {
if (participantId !== excludedParticipantId) {
filtered[participantId] = participant;
}
}
return filtered;
}
export function buildParticipantsSyncData(localUser, participants) {
const memberList = {
host: {
id: localUser.id,
name: localUser.name,
avatar: localUser.avatar,
mediaState: { ...localUser.mediaState },
status: 'online',
role: 'host'
}
};
for (const [participantId, participant] of Object.entries(participants || {})) {
memberList[participantId] = {
...createParticipantRecord(participant),
role: 'participant'
};
}
return memberList;
}

View File

@@ -0,0 +1,64 @@
function createParticipantPlaceholder() {
const placeholder = document.createElement('div');
placeholder.className = 'participant-video-placeholder absolute inset-0 flex items-center justify-center bg-gradient-to-br from-indigo-900/80 to-purple-900/80 hidden';
placeholder.innerHTML = `
<div class="text-center">
<div class="w-20 h-20 rounded-full bg-indigo-700/50 flex items-center justify-center mx-auto mb-3">
<i class="fas fa-video-slash text-2xl text-white/70"></i>
</div>
<p class="text-white text-sm font-medium">\u6444\u50cf\u5934\u5df2\u5173\u95ed</p>
</div>
`;
return placeholder;
}
export function createParticipantTile(connectionId, displayName) {
const tile = document.createElement('div');
tile.className = 'relative bg-black/60 rounded-xl overflow-hidden flex items-center justify-center';
tile.dataset.participantId = connectionId;
const video = document.createElement('video');
video.className = 'w-full h-full object-contain';
video.autoplay = true;
video.playsinline = true;
video.muted = false;
video.id = `participantVideo_${connectionId}`;
tile.appendChild(video);
tile.appendChild(createParticipantPlaceholder());
const label = document.createElement('div');
label.className = 'absolute bottom-3 left-3 glass px-3 py-1 rounded-full text-xs flex items-center gap-2';
label.innerHTML = `<i class="fas fa-user text-purple-400"></i><span>${displayName || '\u53c2\u4e0e\u8005'}</span>`;
tile.appendChild(label);
const liveTag = document.createElement('div');
liveTag.className = 'absolute top-3 right-3 bg-green-500/80 px-2 py-0.5 rounded-full text-xs flex items-center gap-1';
liveTag.innerHTML = `<span class="w-1.5 h-1.5 bg-white rounded-full animate-pulse"></span><span>\u5728\u7ebf</span>`;
tile.appendChild(liveTag);
return tile;
}
export function getParticipantTile(grid, participantId) {
return grid?.querySelector(`[data-participant-id="${participantId}"]`) || null;
}
export function updateParticipantTilePlaceholder(grid, participantId, showPlaceholder) {
const tile = getParticipantTile(grid, participantId);
if (!tile) return;
const placeholder = tile.querySelector('.participant-video-placeholder');
if (placeholder) {
placeholder.classList.toggle('hidden', !showPlaceholder);
}
}
export function updateParticipantTileName(grid, participantId, name) {
const tile = getParticipantTile(grid, participantId);
if (!tile) return;
const label = tile.querySelector('.absolute.bottom-3 span');
if (label && name) {
label.textContent = name;
}
}