web会议聊天

This commit is contained in:
2025-12-27 11:29:52 +08:00
parent 545153fd01
commit 0f985ae8e8
5 changed files with 64 additions and 10 deletions

View File

@@ -111,7 +111,7 @@ services:
# 基础配置(局域网访问)
TZ: Asia/Shanghai
# 关键:使用 http:// 协议的完整 URL
PUBLIC_URL: http://192.168.0.253:8280
PUBLIC_URL: 192.168.0.253:8280
# 关键:禁用 HTTPS让容器生成 ws:// 而不是 wss://
ENABLE_HTTPS: 0
@@ -139,7 +139,7 @@ services:
ENABLE_GUESTS: 1
AUTH_TYPE: jwt
JWT_APP_ID: urbanLifeline
JWT_APP_SECRET: urbanLifelinejitsi
JWT_APP_SECRET: urbanLifeline-jitsi-secret-key-2025-production-safe-hs256
JWT_ACCEPTED_ISSUERS: urbanLifeline
JWT_ACCEPTED_AUDIENCES: jitsi
JWT_ASAP_KEYSERVER: https://192.168.0.253:8280/
@@ -206,7 +206,7 @@ services:
ENABLE_GUESTS: 1
AUTH_TYPE: jwt
JWT_APP_ID: urbanLifeline
JWT_APP_SECRET: urbanLifelinejitsi
JWT_APP_SECRET: urbanLifeline-jitsi-secret-key-2025-production-safe-hs256
JWT_ACCEPTED_ISSUERS: urbanLifeline
JWT_ACCEPTED_AUDIENCES: jitsi
JWT_ALLOW_EMPTY: 0

View File

@@ -62,8 +62,14 @@ public class JitsiTokenServiceImpl implements JitsiTokenService {
claims.put("exp", exp / 1000); // 秒级时间戳
claims.put("nbf", now / 1000);
// 构建JWT Header必须包含 typ: JWT
Map<String, Object> header = new HashMap<>();
header.put("alg", "HS256");
header.put("typ", "JWT");
// 生成JWT Token
String token = Jwts.builder()
.setHeader(header)
.setClaims(claims)
.setIssuedAt(new Date(now))
.setExpiration(new Date(exp))

View File

@@ -105,7 +105,7 @@ jitsi:
secret: urbanLifeline-jitsi-secret-key-2025-production-safe-hs256
server:
# Jitsi Meet服务器地址Docker部署在本地8280端口
url: http://192.168.0.253:8280
url: http://localhost:8280
token:
# JWT Token有效期毫秒- 默认2小时
expiration: 7200000

View File

@@ -16,10 +16,14 @@
v-if="finalUrl"
ref="iframeRef"
:src="finalUrl"
:key="iframeKey"
class="iframe-content"
:class="{ 'with-header': showHeader }"
frameborder="0"
allow="camera; microphone; fullscreen; display-capture; autoplay"
allowfullscreen
@load="handleLoad"
@error="handleError"
/>
<div v-else class="iframe-error">
<AlertTriangle :size="48" class="error-icon" />
@@ -33,7 +37,7 @@
</template>
<script setup lang="ts">
import { ref, computed, onMounted, watch } from 'vue'
import { ref, computed, onMounted, watch, onUnmounted } from 'vue'
import { useRoute } from 'vue-router'
import { Loader, AlertTriangle, RefreshCw } from 'lucide-vue-next'
@@ -41,21 +45,26 @@ interface Props {
url?: string // 直接传入的 URL优先级高于 route.meta
title?: string // 标题
showHeader?: boolean // 是否显示头部(带刷新按钮)
timeout?: number // 加载超时时间(毫秒),默认 10000
}
const props = withDefaults(defineProps<Props>(), {
url: '',
title: '',
showHeader: false
showHeader: false,
timeout: 10000
})
const emit = defineEmits<{
load: []
error: [error: string]
}>()
const route = useRoute()
const loading = ref(true)
const iframeRef = ref<HTMLIFrameElement>()
const iframeKey = ref(0)
let loadTimeout: number | null = null
// 最终的 iframe URLprops.url 优先,否则从 route.meta 获取)
const finalUrl = computed(() => {
@@ -63,22 +72,54 @@ const finalUrl = computed(() => {
})
function handleLoad() {
clearLoadTimeout()
loading.value = false
console.log('[IframeView] iframe 加载完成')
emit('load')
}
function handleError(e: Event) {
clearLoadTimeout()
loading.value = false
console.error('[IframeView] iframe 加载错误:', e)
emit('error', 'iframe 加载失败')
}
function clearLoadTimeout() {
if (loadTimeout) {
clearTimeout(loadTimeout)
loadTimeout = null
}
}
function startLoadTimeout() {
clearLoadTimeout()
// 设置超时,如果超时后仍在加载,则隐藏加载状态
// 因为某些情况下 iframe 的 load 事件可能不会触发
loadTimeout = window.setTimeout(() => {
if (loading.value) {
console.warn('[IframeView] iframe 加载超时,隐藏加载状态')
loading.value = false
}
}, props.timeout)
}
// 刷新 iframe
function refresh() {
if (iframeRef.value) {
loading.value = true
iframeRef.value.src = iframeRef.value.src
iframeKey.value++ // 通过改变 key 强制重新渲染 iframe
startLoadTimeout()
}
}
// 监听 URL 变化,重新加载
watch(finalUrl, () => {
if (finalUrl.value) {
watch(finalUrl, (newUrl) => {
if (newUrl) {
loading.value = true
iframeKey.value++ // 强制重新渲染
startLoadTimeout()
console.log('[IframeView] URL 变化,重新加载:', newUrl)
}
})
@@ -89,6 +130,13 @@ defineExpose({
onMounted(() => {
console.log('[IframeView] 加载 iframe:', finalUrl.value)
if (finalUrl.value) {
startLoadTimeout()
}
})
onUnmounted(() => {
clearLoadTimeout()
})
</script>

View File

@@ -157,7 +157,7 @@
</div>
</div>
<div class="meeting-modal-body">
<IframeView :src="meetingUrl" class="meeting-iframe" />
<IframeView :url="meetingUrl" class="meeting-iframe" />
</div>
</div>
</div>