mirror of
https://github.com/AstrBotDevs/AstrBot
synced 2026-07-16 01:40:15 +08:00
Feat(webui): dashboard and console qol improvements (#7215)
* feat: support native fullscreen for log console * feat: skip welcome page for configured users * feat: display plugin names under pinned icons * fix: refine container styles
This commit is contained in:
@@ -65,18 +65,26 @@ const authorDisplay = computed(() => {
|
||||
>
|
||||
<v-menu offset-y>
|
||||
<template #activator="{ props: menuProps }">
|
||||
<v-avatar
|
||||
v-bind="menuProps"
|
||||
size="72"
|
||||
class="pinned-avatar activator-avatar"
|
||||
:title="plugin.display_name || plugin.name"
|
||||
>
|
||||
<img
|
||||
:src="(typeof plugin.logo === 'string' && plugin.logo.trim()) ? plugin.logo : defaultPluginIcon"
|
||||
:alt="plugin.name"
|
||||
@error="handlePinnedImgError"
|
||||
/>
|
||||
</v-avatar>
|
||||
<div class="d-flex flex-column align-center" style="cursor: pointer; width: 80px;">
|
||||
<v-avatar
|
||||
v-bind="menuProps"
|
||||
size="72"
|
||||
class="pinned-avatar activator-avatar mb-1"
|
||||
:title="plugin.display_name || plugin.name"
|
||||
>
|
||||
<img
|
||||
:src="(typeof plugin.logo === 'string' && plugin.logo.trim()) ? plugin.logo : defaultPluginIcon"
|
||||
:alt="plugin.name"
|
||||
@error="handlePinnedImgError"
|
||||
/>
|
||||
</v-avatar>
|
||||
<span
|
||||
class="text-caption text-center text-truncate"
|
||||
style="width: 100%; font-size: 0.75rem; opacity: 0.9; line-height: 1.2;"
|
||||
>
|
||||
{{ plugin.display_name || plugin.name }}
|
||||
</span>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<v-card>
|
||||
@@ -182,8 +190,7 @@ const authorDisplay = computed(() => {
|
||||
.pinned-card-wrapper {
|
||||
position: relative;
|
||||
display: inline-block;
|
||||
width: 72px;
|
||||
height: 72px;
|
||||
width: 80px;
|
||||
}
|
||||
|
||||
.pinned-item {
|
||||
|
||||
@@ -5,7 +5,7 @@ import { EventSourcePolyfill } from 'event-source-polyfill';
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div>
|
||||
<div class="console-displayer-wrapper" id="console-wrapper">
|
||||
<div class="filter-controls mb-2" v-if="showLevelBtns">
|
||||
<v-chip-group v-model="selectedLevels" column multiple>
|
||||
<v-chip v-for="level in logLevels" :key="level" :color="getLevelColor(level)" filter variant="flat" size="small"
|
||||
@@ -13,6 +13,14 @@ import { EventSourcePolyfill } from 'event-source-polyfill';
|
||||
{{ level }}
|
||||
</v-chip>
|
||||
</v-chip-group>
|
||||
<v-spacer></v-spacer>
|
||||
<v-btn
|
||||
:icon="isFullscreen ? 'mdi-fullscreen-exit' : 'mdi-fullscreen'"
|
||||
variant="text"
|
||||
density="compact"
|
||||
class="me-4 fullscreen-btn"
|
||||
@click="toggleFullscreen"
|
||||
></v-btn>
|
||||
</div>
|
||||
|
||||
<div id="term" style="background-color: #1e1e1e; padding: 16px; border-radius: 8px; overflow-y:auto; height: 100%">
|
||||
@@ -26,6 +34,7 @@ export default {
|
||||
data() {
|
||||
return {
|
||||
autoScroll: true,
|
||||
isFullscreen: false,
|
||||
logColorAnsiMap: {
|
||||
'\u001b[1;34m': 'color: #39C5BB; font-weight: bold;',
|
||||
'\u001b[1;36m': 'color: #00FFFF; font-weight: bold;',
|
||||
@@ -80,8 +89,10 @@ export default {
|
||||
async mounted() {
|
||||
await this.fetchLogHistory();
|
||||
this.connectSSE();
|
||||
document.addEventListener('fullscreenchange', this.handleFullscreenChange);
|
||||
},
|
||||
beforeUnmount() {
|
||||
document.removeEventListener('fullscreenchange', this.handleFullscreenChange);
|
||||
if (this.eventSource) {
|
||||
this.eventSource.close();
|
||||
this.eventSource = null;
|
||||
@@ -253,6 +264,21 @@ export default {
|
||||
this.autoScroll = !this.autoScroll;
|
||||
},
|
||||
|
||||
toggleFullscreen() {
|
||||
const container = document.getElementById('console-wrapper');
|
||||
if (!document.fullscreenElement) {
|
||||
container.requestFullscreen().catch(err => {
|
||||
console.error(`Error attempting to enable full-screen mode: ${err.message}`);
|
||||
});
|
||||
} else {
|
||||
document.exitFullscreen();
|
||||
}
|
||||
},
|
||||
|
||||
handleFullscreenChange() {
|
||||
this.isFullscreen = !!document.fullscreenElement;
|
||||
},
|
||||
|
||||
printLog(log) {
|
||||
let ele = document.getElementById('term')
|
||||
if (!ele) {
|
||||
@@ -282,14 +308,30 @@ export default {
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.console-displayer-wrapper {
|
||||
height: 100%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
#console-wrapper:fullscreen {
|
||||
background-color: #1e1e1e;
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
.filter-controls {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
flex-wrap: wrap;
|
||||
gap: 8px;
|
||||
margin-bottom: 8px;
|
||||
margin-left: 20px;
|
||||
}
|
||||
|
||||
.fullscreen-btn {
|
||||
color: rgba(255, 255, 255, 0.7) !important; /* 提高在深色背景下的对比度 */
|
||||
}
|
||||
|
||||
:deep(.console-log-line) {
|
||||
display: block;
|
||||
margin-bottom: 2px;
|
||||
|
||||
@@ -25,12 +25,47 @@ export const useAuthStore = defineStore({
|
||||
localStorage.setItem('user', this.username);
|
||||
localStorage.setItem('token', res.data.data.token);
|
||||
localStorage.setItem('change_pwd_hint', res.data.data?.change_pwd_hint);
|
||||
|
||||
const onboardingCompleted = await this.checkOnboardingCompleted();
|
||||
this.returnUrl = null;
|
||||
router.push('/welcome');
|
||||
if (onboardingCompleted) {
|
||||
router.push('/dashboard/default');
|
||||
} else {
|
||||
router.push('/welcome');
|
||||
}
|
||||
} catch (error) {
|
||||
return Promise.reject(error);
|
||||
}
|
||||
},
|
||||
async checkOnboardingCompleted(): Promise<boolean> {
|
||||
try {
|
||||
// 1. 检查平台配置
|
||||
const platformRes = await axios.get('/api/config/get');
|
||||
const hasPlatform = (platformRes.data.data.config.platform || []).length > 0;
|
||||
if (!hasPlatform) return false;
|
||||
|
||||
// 2. 检查提供者配置
|
||||
const providerRes = await axios.get('/api/config/provider/template');
|
||||
const providers = providerRes.data.data?.providers || [];
|
||||
const sources = providerRes.data.data?.provider_sources || [];
|
||||
const sourceMap = new Map();
|
||||
sources.forEach((s: any) => sourceMap.set(s.id, s.provider_type));
|
||||
|
||||
const hasProvider = providers.some((provider: any) => {
|
||||
if (provider.provider_type) return provider.provider_type === 'chat_completion';
|
||||
if (provider.provider_source_id) {
|
||||
const type = sourceMap.get(provider.provider_source_id);
|
||||
if (type === 'chat_completion') return true;
|
||||
}
|
||||
return String(provider.type || '').includes('chat_completion');
|
||||
});
|
||||
|
||||
return hasProvider;
|
||||
} catch (e) {
|
||||
console.error('Failed to check onboarding status:', e);
|
||||
return false;
|
||||
}
|
||||
},
|
||||
logout() {
|
||||
this.username = '';
|
||||
localStorage.removeItem('user');
|
||||
|
||||
@@ -22,10 +22,15 @@ function toggleTheme() {
|
||||
theme.global.name.value = newTheme;
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
onMounted(async () => {
|
||||
// 检查用户是否已登录,如果已登录则重定向
|
||||
if (authStore.has_token()) {
|
||||
router.push('/welcome');
|
||||
const onboardingCompleted = await authStore.checkOnboardingCompleted();
|
||||
if (onboardingCompleted) {
|
||||
router.push('/dashboard/default');
|
||||
} else {
|
||||
router.push('/welcome');
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user