1
This commit is contained in:
@@ -118,11 +118,34 @@ async function handleLoginSuccess(data: { token: string; permissions?: string })
|
||||
currentUsername.value = username
|
||||
userPermissions.value = data?.permissions || ''
|
||||
await deviceApi.register({ username })
|
||||
|
||||
// 建立SSE连接
|
||||
SSEManager.connect()
|
||||
} catch (e) {
|
||||
// 设备注册失败不影响登录流程,静默处理
|
||||
console.warn('设备注册失败:', e)
|
||||
}
|
||||
}
|
||||
async function logout() {
|
||||
try {
|
||||
// 删除后端token缓存 - 复刻HTML版本逻辑
|
||||
await fetch('/api/cache/delete?key=token', { method: 'POST' })
|
||||
} catch (e) {
|
||||
console.warn('删除后端token缓存失败:', e)
|
||||
}
|
||||
|
||||
// 清理前端状态
|
||||
try { localStorage.removeItem('token') } catch {}
|
||||
isAuthenticated.value = false
|
||||
currentUsername.value = ''
|
||||
userPermissions.value = ''
|
||||
showAuthDialog.value = true
|
||||
showDeviceDialog.value = false
|
||||
|
||||
// 关闭SSE连接
|
||||
SSEManager.disconnect()
|
||||
}
|
||||
|
||||
async function handleUserClick() {
|
||||
if (!isAuthenticated.value) {
|
||||
showAuthDialog.value = true
|
||||
@@ -130,14 +153,7 @@ async function handleUserClick() {
|
||||
}
|
||||
try {
|
||||
await ElMessageBox.confirm('确认退出登录?', '提示', { type: 'warning', confirmButtonText: '退出', cancelButtonText: '取消' })
|
||||
const token = localStorage.getItem('token') || ''
|
||||
try { await authApi.logout(token) } catch {}
|
||||
try { localStorage.removeItem('token') } catch {}
|
||||
isAuthenticated.value = false
|
||||
currentUsername.value = ''
|
||||
userPermissions.value = ''
|
||||
showAuthDialog.value = true
|
||||
showDeviceDialog.value = false
|
||||
await logout()
|
||||
ElMessage.success('已退出登录')
|
||||
} catch {}
|
||||
}
|
||||
@@ -177,6 +193,9 @@ async function checkAuth() {
|
||||
if (u) currentUsername.value = u
|
||||
}
|
||||
userPermissions.value = response.permissions || ''
|
||||
|
||||
// 认证成功后建立SSE连接
|
||||
SSEManager.connect()
|
||||
return
|
||||
}
|
||||
} catch {
|
||||
@@ -204,6 +223,67 @@ function getUsernameFromToken(token?: string) {
|
||||
return payload.username || ''
|
||||
}
|
||||
|
||||
// SSE管理器 - 简化封装
|
||||
const SSEManager = {
|
||||
connection: null as EventSource | null,
|
||||
|
||||
async connect() {
|
||||
if (this.connection) return
|
||||
|
||||
const token = localStorage.getItem('token')
|
||||
const clientId = getClientIdFromToken(token)
|
||||
if (!token || !clientId) return
|
||||
|
||||
try {
|
||||
// 简化配置获取,失败时使用默认配置
|
||||
let sseUrl = 'http://192.168.1.89:8080/monitor/account/events'
|
||||
try {
|
||||
const resp = await fetch('/api/config/server')
|
||||
if (resp.ok) {
|
||||
const config = await resp.json()
|
||||
sseUrl = config.sseUrl || sseUrl
|
||||
}
|
||||
} catch {}
|
||||
|
||||
const src = new EventSource(`${sseUrl}?clientId=${clientId}&token=${token}`)
|
||||
this.connection = src
|
||||
|
||||
src.onmessage = (e) => this.handleMessage(e)
|
||||
src.onerror = () => this.handleError()
|
||||
} catch (e) {
|
||||
console.warn('SSE连接失败:', e.message)
|
||||
}
|
||||
},
|
||||
|
||||
handleMessage(e: MessageEvent) {
|
||||
try {
|
||||
const payload = JSON.parse(e.data)
|
||||
switch (payload.type) {
|
||||
case 'DEVICE_REMOVED':
|
||||
case 'FORCE_LOGOUT':
|
||||
logout()
|
||||
ElMessage.warning('会话已失效,请重新登录')
|
||||
break
|
||||
case 'PERMISSIONS_UPDATED':
|
||||
checkAuth()
|
||||
break
|
||||
}
|
||||
} catch {}
|
||||
},
|
||||
|
||||
handleError() {
|
||||
this.disconnect()
|
||||
setTimeout(() => this.connect(), 3000)
|
||||
},
|
||||
|
||||
disconnect() {
|
||||
if (this.connection) {
|
||||
try { this.connection.close() } catch {}
|
||||
this.connection = null
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async function openDeviceManager() {
|
||||
if (!isAuthenticated.value) {
|
||||
showAuthDialog.value = true
|
||||
@@ -241,12 +321,13 @@ async function confirmRemoveDevice(row: DeviceItem & { isCurrent?: boolean }) {
|
||||
await deviceApi.remove({ deviceId: row.deviceId })
|
||||
devices.value = devices.value.filter(d => d.deviceId !== row.deviceId)
|
||||
deviceQuota.value.used = Math.max(0, (deviceQuota.value.used || 0) - 1)
|
||||
if (row.isCurrent) {
|
||||
// 当前设备被移除,清理登录状态
|
||||
isAuthenticated.value = false
|
||||
showAuthDialog.value = true
|
||||
try { localStorage.removeItem('token') } catch {}
|
||||
|
||||
// 如果是本机设备被移除,执行logout - 复刻HTML版本逻辑
|
||||
const clientId = getClientIdFromToken()
|
||||
if (row.deviceId === clientId) {
|
||||
await logout()
|
||||
}
|
||||
|
||||
ElMessage.success('已移除设备')
|
||||
} catch (e) {
|
||||
/* 用户取消或失败 */
|
||||
|
||||
@@ -329,7 +329,7 @@ onMounted(async () => {
|
||||
</div>
|
||||
|
||||
<!-- 表格加载遮罩 -->
|
||||
<div v-if="tableLoading" class="table-loading">
|
||||
<div v-if="tableLoading && paginatedData.length === 0" class="table-loading">
|
||||
<div class="spinner">⟳</div>
|
||||
<div>加载中...</div>
|
||||
</div>
|
||||
@@ -366,8 +366,8 @@ onMounted(async () => {
|
||||
.progress-section { margin: 15px 0 10px 0; }
|
||||
.progress-box { padding: 8px 0; }
|
||||
.progress-container { display: flex; align-items: center; position: relative; padding-right: 50px; margin-bottom: 8px; }
|
||||
.progress-bar { flex: 1; height: 6px; background: #ebeef5; border-radius: 3px; overflow: hidden; }
|
||||
.progress-fill { height: 100%; background: linear-gradient(90deg, #409EFF, #66b1ff); border-radius: 3px; transition: width 0.3s ease; }
|
||||
.progress-bar { flex: 1; height: 3px; background: #ebeef5; border-radius: 2px; overflow: hidden; }
|
||||
.progress-fill { height: 100%; background: linear-gradient(90deg, #409EFF, #66b1ff); border-radius: 2px; transition: width 0.3s ease; }
|
||||
.progress-text { position: absolute; right: 0; font-size: 13px; color: #409EFF; font-weight: 500; }
|
||||
.current-status { font-size: 12px; color: #606266; padding-left: 2px; }
|
||||
.table-container { display: flex; flex-direction: column; flex: 1; min-height: 400px; overflow: hidden; }
|
||||
|
||||
@@ -369,7 +369,7 @@ onMounted(loadLatest)
|
||||
</div>
|
||||
|
||||
<!-- 表格加载遮罩 -->
|
||||
<div v-if="tableLoading" class="table-loading">
|
||||
<div v-if="tableLoading && paginatedData.length === 0" class="table-loading">
|
||||
<div class="spinner">⟳</div>
|
||||
<div>加载中...</div>
|
||||
</div>
|
||||
@@ -454,20 +454,8 @@ onMounted(loadLatest)
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.progress-bar {
|
||||
flex: 1;
|
||||
height: 6px;
|
||||
background: #ebeef5;
|
||||
border-radius: 3px;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.progress-fill {
|
||||
height: 100%;
|
||||
background: linear-gradient(90deg, #409EFF, #66b1ff);
|
||||
border-radius: 3px;
|
||||
transition: width 0.3s ease;
|
||||
}
|
||||
.progress-bar { flex: 1; height: 3px; background: #ebeef5; border-radius: 2px; overflow: hidden; }
|
||||
.progress-fill { height: 100%; background: linear-gradient(90deg, #409EFF, #66b1ff); border-radius: 2px; transition: width 0.3s ease; }
|
||||
|
||||
.progress-text {
|
||||
position: absolute;
|
||||
|
||||
@@ -174,20 +174,7 @@ onMounted(async () => {
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 进度条显示 -->
|
||||
<div class="progress-section" v-if="showProgress">
|
||||
<div class="progress-box">
|
||||
<div class="progress-container">
|
||||
<div class="progress-bar">
|
||||
<div class="progress-fill" :style="{ width: progressPercentage + '%' }"></div>
|
||||
</div>
|
||||
<div class="progress-text">{{ progressPercentage }}%</div>
|
||||
</div>
|
||||
<div class="current-status" v-if="fetchTotalItems > 0">
|
||||
{{ progressPercentage >= 100 ? '完成' : `获取中... (${allOrderData.length}/${fetchTotalItems})` }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- 进度条显示:移动到底部,以免挤压表头 -->
|
||||
</div>
|
||||
|
||||
<!-- 数据显示区域 -->
|
||||
@@ -256,15 +243,19 @@ onMounted(async () => {
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 表格加载遮罩 -->
|
||||
<!-- 表格加载遮罩:仅在无数据时显示 -->
|
||||
<div v-if="loading && !allOrderData.length" class="table-loading">
|
||||
<div class="spinner">⟳</div>
|
||||
<div>加载中...</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 分页器 -->
|
||||
<!-- 底部区域:进度条 + 分页器 -->
|
||||
<div class="pagination-fixed">
|
||||
<div v-if="showProgress" class="progress-bottom">
|
||||
<div class="progress-bar"><div class="progress-fill" :style="{ width: progressPercentage + '%' }"></div></div>
|
||||
<div class="progress-text">{{ progressPercentage }}%</div>
|
||||
</div>
|
||||
<el-pagination
|
||||
background
|
||||
:current-page="currentPage"
|
||||
@@ -296,8 +287,8 @@ export default {
|
||||
.progress-section { margin: 15px 0 10px 0; }
|
||||
.progress-box { padding: 8px 0; }
|
||||
.progress-container { display: flex; align-items: center; position: relative; padding-right: 50px; margin-bottom: 8px; }
|
||||
.progress-bar { flex: 1; height: 6px; background: #ebeef5; border-radius: 3px; overflow: hidden; }
|
||||
.progress-fill { height: 100%; background: linear-gradient(90deg, #409EFF, #66b1ff); border-radius: 3px; transition: width 0.3s ease; }
|
||||
.progress-bar { flex: 1; height: 3px; background: #ebeef5; border-radius: 2px; overflow: hidden; }
|
||||
.progress-fill { height: 100%; background: linear-gradient(90deg, #409EFF, #66b1ff); border-radius: 2px; transition: width 0.3s ease; }
|
||||
.progress-text { position: absolute; right: 0; font-size: 13px; color: #409EFF; font-weight: 500; }
|
||||
.current-status { font-size: 12px; color: #606266; padding-left: 2px; }
|
||||
.table-container { display: flex; flex-direction: column; flex: 1; min-height: 400px; overflow: hidden; }
|
||||
@@ -326,6 +317,7 @@ export default {
|
||||
.empty-icon { font-size: 48px; margin-bottom: 12px; opacity: 0.6; }
|
||||
.empty-text { font-size: 14px; color: #909399; }
|
||||
.empty-abs { position: absolute; left: 0; right: 0; top: 48px; bottom: 0; display: flex; align-items: center; justify-content: center; }
|
||||
.progress-bottom { display: flex; align-items: center; gap: 8px; margin-right: auto; }
|
||||
</style>
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user