Files
AstrBot/dashboard/src/App.vue
LIghtJUNction dcaaf6286a test: comprehensive test coverage and type fixes
- Add 100+ new test files covering provider sources, platform adapters,
  agent runners, star/plugin system, knowledge base, core utils,
  pipeline, computer tools, builtin commands, and dashboard routes
- Fix Python type errors in sqlite.py (col() wrappers for SQLModel),
  astr_agent_tool_exec, core_lifecycle, star context/manager
- Fix TypeScript strict mode errors across 30+ dashboard Vue files
- Add import smoke tests, auth roundtrip, startup tests
- Add compile-all check and CLI entry test for AUR compatibility
- Restore BotMessageAccumulator and helpers lost in merge
2026-04-29 06:29:56 +08:00

89 lines
2.2 KiB
Vue

<template>
<RouterView />
<WaitingForRestart ref="globalWaitingRef" />
<!-- 全局唯一 snackbar -->
<v-snackbar
v-if="toastStore.current"
v-model="snackbarShow"
:color="toastStore.current.color"
:timeout="toastStore.current.timeout"
:multi-line="toastStore.current.multiLine"
:location="snackbarLocation"
close-on-back
>
{{ toastStore.current.message }}
<template v-if="toastStore.current.closable" #actions>
<v-btn variant="text" @click="snackbarShow = false"> 关闭 </v-btn>
</template>
</v-snackbar>
</template>
<script setup lang="ts">
import { RouterView } from "vue-router";
import { computed, onBeforeUnmount, onMounted, ref, watch } from "vue";
import { useToastStore } from "@/stores/toast";
import { useCustomizerStore } from "@/stores/customizer";
import WaitingForRestart from "@/components/shared/WaitingForRestart.vue";
type SnackAnchor =
| "top"
| "bottom"
| "start"
| "end"
| "center"
| "center center"
| "top center"
| "top start"
| "top end"
| "bottom center"
| "bottom start"
| "bottom end";
const toastStore = useToastStore();
const customizer = useCustomizerStore();
const globalWaitingRef = ref<InstanceType<typeof WaitingForRestart> | null>(null);
let disposeTrayRestartListener: (() => void) | null = null;
const snackbarShow = computed({
get: () => !!toastStore.current,
set: (val) => {
if (!val) toastStore.shift();
},
});
const snackbarLocation = computed<SnackAnchor | undefined>(() => toastStore.current?.location as SnackAnchor | undefined);
// 统一监听 uiTheme 变化并同步到 Vuetify
watch(
() => customizer.uiTheme,
(newTheme) => {
if (newTheme) {
}
},
{ immediate: true },
);
onMounted(() => {
const desktopBridge = window.astrbotDesktop;
if (!desktopBridge?.onTrayRestartBackend) {
return;
}
disposeTrayRestartListener = desktopBridge.onTrayRestartBackend(async () => {
try {
await globalWaitingRef.value?.check?.();
} catch (error) {
globalWaitingRef.value?.stop?.();
console.error("Tray restart backend failed:", error);
}
});
});
onBeforeUnmount(() => {
if (disposeTrayRestartListener) {
disposeTrayRestartListener();
disposeTrayRestartListener = null;
}
});
</script>