mirror of
https://github.com/AstrBotDevs/AstrBot
synced 2026-07-15 17:30:13 +08:00
- Change login page title from "AstrBot WebUI/Dashboard" to "AstrBot Starlight Panel" - Replace welcome subtitle with DailyQuote component showing programming quotes - Quotes are localized for en-US, zh-CN, and ru-RU - Quote of the day stays consistent throughout each day
319 lines
9.0 KiB
Vue
319 lines
9.0 KiB
Vue
<script setup lang="ts">
|
||
import AuthLogin from "../authForms/AuthLogin.vue";
|
||
import DailyQuote from "@/components/shared/DailyQuote.vue";
|
||
import LanguageSwitcher from "@/components/shared/LanguageSwitcher.vue";
|
||
import { onMounted, ref } from "vue";
|
||
import { useAuthStore } from "@/stores/auth";
|
||
import { useApiStore } from "@/stores/api";
|
||
import { useRouter } from "vue-router";
|
||
import { useCustomizerStore } from "@/stores/customizer";
|
||
import { useModuleI18n } from "@/i18n/composables";
|
||
import { useToast } from "@/utils/toast";
|
||
import { getApiBaseUrlValidationError } from "@/utils/request";
|
||
|
||
const cardVisible = ref(false);
|
||
const router = useRouter();
|
||
const authStore = useAuthStore();
|
||
const apiStore = useApiStore();
|
||
const customizer = useCustomizerStore();
|
||
const { tm: t } = useModuleI18n("features/auth");
|
||
const toast = useToast();
|
||
|
||
const serverConfigDialog = ref(false);
|
||
const apiUrl = ref(apiStore.apiBaseUrl);
|
||
|
||
// URL parameter handling for shareable config
|
||
function applyUrlParams() {
|
||
const params = new URLSearchParams(window.location.search);
|
||
const apiUrlParam = params.get("api_url");
|
||
const usernameParam = params.get("username");
|
||
if (apiUrlParam) {
|
||
apiUrl.value = apiUrlParam;
|
||
const validationError = getApiBaseUrlValidationError(apiUrlParam);
|
||
if (!validationError) {
|
||
apiStore.setApiBaseUrl(apiUrlParam);
|
||
}
|
||
}
|
||
if (usernameParam) {
|
||
window.dispatchEvent(
|
||
new CustomEvent("astrbot-url-param-username", {
|
||
detail: { username: usernameParam },
|
||
}),
|
||
);
|
||
}
|
||
}
|
||
|
||
function getShareableUrl() {
|
||
const url = new URL(window.location.href);
|
||
url.searchParams.set("api_url", apiUrl.value);
|
||
return url.toString();
|
||
}
|
||
|
||
async function copyShareableUrl() {
|
||
try {
|
||
await navigator.clipboard.writeText(getShareableUrl());
|
||
toast.success(t("linkCopied"));
|
||
} catch {
|
||
toast.error(t("linkCopyFailed"));
|
||
}
|
||
}
|
||
|
||
const showAddPreset = ref(false);
|
||
const newPresetName = ref("");
|
||
const newPresetUrl = ref("");
|
||
|
||
function saveApiUrl() {
|
||
const validationError = getApiBaseUrlValidationError(apiUrl.value);
|
||
if (validationError) {
|
||
toast.error(validationError);
|
||
return;
|
||
}
|
||
|
||
apiStore.setApiBaseUrl(apiUrl.value);
|
||
serverConfigDialog.value = false;
|
||
window.location.reload();
|
||
}
|
||
|
||
function savePreset() {
|
||
if (!newPresetName.value || !newPresetUrl.value) return;
|
||
apiStore.addPreset({
|
||
name: newPresetName.value,
|
||
url: newPresetUrl.value,
|
||
});
|
||
showAddPreset.value = false;
|
||
newPresetName.value = "";
|
||
newPresetUrl.value = "";
|
||
}
|
||
|
||
function isCustomPreset(name: string) {
|
||
return apiStore.customPresets.some((p) => p.name === name);
|
||
}
|
||
|
||
// 主题切换函数
|
||
function toggleTheme() {
|
||
customizer.TOGGLE_DARK_MODE();
|
||
}
|
||
|
||
onMounted(() => {
|
||
// 应用URL参数(用于分享预设配置)
|
||
applyUrlParams();
|
||
|
||
// 检查用户是否已登录,如果已登录则重定向
|
||
if (authStore.has_token()) {
|
||
router.push(authStore.returnUrl || "/");
|
||
return;
|
||
}
|
||
|
||
// 添加一个小延迟以获得更好的动画效果
|
||
setTimeout(() => {
|
||
cardVisible.value = true;
|
||
}, 100);
|
||
});
|
||
</script>
|
||
|
||
<template>
|
||
<div class="login-page-container">
|
||
<v-card class="login-card" elevation="1">
|
||
<v-card-title>
|
||
<div class="d-flex justify-space-between align-center w-100">
|
||
<img
|
||
width="80"
|
||
src="@/assets/images/icon-no-shadow.svg"
|
||
alt="AstrBot Logo"
|
||
/>
|
||
<div class="d-flex align-center gap-1">
|
||
<LanguageSwitcher />
|
||
<v-divider
|
||
vertical
|
||
class="mx-1"
|
||
style="
|
||
height: 24px !important;
|
||
opacity: 0.9 !important;
|
||
align-self: center !important;
|
||
border-color: rgba(var(--v-theme-primary), 0.45) !important;
|
||
"
|
||
/>
|
||
|
||
<v-btn
|
||
icon
|
||
variant="text"
|
||
size="small"
|
||
@click="serverConfigDialog = true"
|
||
>
|
||
<v-icon size="18" :color="'rgb(var(--v-theme-primary))'">
|
||
mdi-server
|
||
</v-icon>
|
||
<v-tooltip activator="parent" location="top">
|
||
{{ t("serverConfig.tooltip") }}
|
||
</v-tooltip>
|
||
</v-btn>
|
||
|
||
<v-btn
|
||
class="theme-toggle-btn"
|
||
icon
|
||
variant="text"
|
||
size="small"
|
||
@click="toggleTheme"
|
||
>
|
||
<v-icon size="18" :color="'rgb(var(--v-theme-primary))'">
|
||
{{
|
||
customizer.isDarkTheme
|
||
? "mdi-weather-night"
|
||
: "mdi-white-balance-sunny"
|
||
}}
|
||
</v-icon>
|
||
<v-tooltip activator="parent" location="top">
|
||
{{
|
||
customizer.isDarkTheme
|
||
? t("theme.switchToLight")
|
||
: t("theme.switchToDark")
|
||
}}
|
||
</v-tooltip>
|
||
</v-btn>
|
||
</div>
|
||
</div>
|
||
<div class="ml-2" style="font-size: 26px">
|
||
{{ t("logo.title") }}
|
||
</div>
|
||
<div class="mt-2 ml-2" style="font-size: 14px; color: grey">
|
||
<DailyQuote />
|
||
</div>
|
||
</v-card-title>
|
||
<v-card-text>
|
||
<AuthLogin />
|
||
</v-card-text>
|
||
</v-card>
|
||
|
||
<v-dialog v-model="serverConfigDialog" max-width="450">
|
||
<v-card>
|
||
<v-card-title>{{ t("serverConfig.title") }}</v-card-title>
|
||
<v-card-text>
|
||
<div class="text-body-2 text-medium-emphasis mb-4">
|
||
{{ t("serverConfig.description") }}
|
||
</div>
|
||
|
||
<div
|
||
v-if="
|
||
(apiStore.presets && apiStore.presets.length > 0) ||
|
||
apiStore.customPresets
|
||
"
|
||
class="mb-4"
|
||
>
|
||
<div class="d-flex justify-space-between align-center mb-2">
|
||
<div class="text-caption text-medium-emphasis">
|
||
{{ t("serverConfig.presetLabel") }}
|
||
</div>
|
||
<v-btn
|
||
size="x-small"
|
||
variant="text"
|
||
icon
|
||
@click="showAddPreset = !showAddPreset"
|
||
>
|
||
<v-icon>mdi-plus</v-icon>
|
||
</v-btn>
|
||
</div>
|
||
|
||
<v-expand-transition>
|
||
<div
|
||
v-if="showAddPreset"
|
||
class="mb-2 pa-2 bg-grey-lighten-4 rounded border"
|
||
>
|
||
<v-text-field
|
||
v-model="newPresetName"
|
||
label="Name"
|
||
density="compact"
|
||
hide-details
|
||
class="mb-2"
|
||
variant="outlined"
|
||
bg-color="white"
|
||
/>
|
||
<v-text-field
|
||
v-model="newPresetUrl"
|
||
label="URL"
|
||
density="compact"
|
||
hide-details
|
||
class="mb-2"
|
||
variant="outlined"
|
||
bg-color="white"
|
||
/>
|
||
<v-btn
|
||
size="small"
|
||
block
|
||
color="primary"
|
||
variant="flat"
|
||
@click="savePreset"
|
||
>
|
||
Add Preset
|
||
</v-btn>
|
||
</div>
|
||
</v-expand-transition>
|
||
|
||
<v-chip-group column>
|
||
<v-chip
|
||
v-for="preset in apiStore.presets"
|
||
:key="preset.name"
|
||
size="small"
|
||
:variant="apiUrl === preset.url ? 'flat' : 'tonal'"
|
||
:color="apiUrl === preset.url ? 'primary' : undefined"
|
||
:closable="isCustomPreset(preset.name)"
|
||
@click="apiUrl = preset.url"
|
||
@click:close="apiStore.removePreset(preset.name)"
|
||
>
|
||
{{ preset.name }}
|
||
</v-chip>
|
||
</v-chip-group>
|
||
</div>
|
||
|
||
<v-text-field
|
||
v-model="apiUrl"
|
||
:label="t('serverConfig.label')"
|
||
:placeholder="t('serverConfig.placeholder')"
|
||
:hint="t('serverConfig.hint')"
|
||
persistent-hint
|
||
variant="outlined"
|
||
density="compact"
|
||
/>
|
||
|
||
<v-btn
|
||
variant="tonal"
|
||
size="small"
|
||
block
|
||
class="mt-2"
|
||
prepend-icon="mdi-share-variant"
|
||
@click="copyShareableUrl"
|
||
>
|
||
{{ t("shareLink") }}
|
||
</v-btn>
|
||
</v-card-text>
|
||
<v-card-actions>
|
||
<v-spacer />
|
||
<v-btn variant="text" @click="serverConfigDialog = false">
|
||
{{ t("serverConfig.cancel") }}
|
||
</v-btn>
|
||
<v-btn color="primary" variant="flat" @click="saveApiUrl">
|
||
{{ t("serverConfig.save") }}
|
||
</v-btn>
|
||
</v-card-actions>
|
||
</v-card>
|
||
</v-dialog>
|
||
</div>
|
||
</template>
|
||
|
||
<style lang="scss">
|
||
.login-page-container {
|
||
background-color: rgb(var(--v-theme-containerBg));
|
||
position: relative;
|
||
width: 100vw;
|
||
height: 100vh;
|
||
overflow: hidden;
|
||
display: flex;
|
||
justify-content: center;
|
||
align-items: center;
|
||
}
|
||
|
||
.login-card {
|
||
width: 400px;
|
||
padding: 8px;
|
||
}
|
||
</style>
|