mirror of
https://github.com/ZeroCatDev/Classworks.git
synced 2025-12-08 22:03:09 +00:00
- Implemented ChatWidget component for real-time chat functionality with socket integration. - Added InitServiceChooser component for selecting services with manual token input and auto-authorization. - Updated settings and data provider to support local development with localhost. - Enhanced settings page with Classworks KV card and improved styles. - Introduced debug socket page for monitoring connection status and device interactions. - Refactored socket client utility for better connection management and event handling. - Added glow highlight effect in styles for UI enhancements.
196 lines
5.9 KiB
Vue
196 lines
5.9 KiB
Vue
<template>
|
||
<div>
|
||
<v-dialog
|
||
v-model="visible"
|
||
persistent
|
||
transition="dialog-bottom-transition"
|
||
>
|
||
<v-card
|
||
class="kvinit-card"
|
||
elevation="8"
|
||
title="初始化云端存储授权"
|
||
subtitle="请完成授权以启用云端存储功能"
|
||
prepend-icon="mdi-cloud-lock"
|
||
>
|
||
<v-card-actions class="justify-end">
|
||
<v-btn
|
||
text
|
||
class="me-3"
|
||
@click="useLocalMode"
|
||
>
|
||
使用本地模式
|
||
</v-btn>
|
||
<v-btn
|
||
color="primary"
|
||
variant="flat"
|
||
:loading="loading"
|
||
@click="goToAuthorize"
|
||
>
|
||
前往授权
|
||
</v-btn>
|
||
</v-card-actions>
|
||
<div class="d-flex align-center justify-space-between">
|
||
<div>
|
||
<div
|
||
v-if="loading"
|
||
class="d-flex align-center"
|
||
>
|
||
<v-progress-circular
|
||
indeterminate
|
||
size="20"
|
||
width="2"
|
||
class="me-2"
|
||
/>
|
||
<span class="body-2"> 正在检查授权状态… </span>
|
||
</div>
|
||
<div
|
||
v-else-if="error"
|
||
class="body-2 text-error"
|
||
>
|
||
检查出错:{{ error }}
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</v-card>
|
||
</v-dialog>
|
||
</div>
|
||
</template>
|
||
|
||
<script setup>
|
||
import { ref, onMounted, onBeforeUnmount } from "vue";
|
||
import { useRoute } from "vue-router";
|
||
import { getSetting,setSetting } from "@/utils/settings";
|
||
import { kvServerProvider } from "@/utils/providers/kvServerProvider";
|
||
|
||
const visible = ref(false);
|
||
const loading = ref(false);
|
||
const error = ref("");
|
||
const route = useRoute();
|
||
|
||
// allow external components to reopen the dialog via an event
|
||
const onExternalOpen = () => {
|
||
visible.value = true;
|
||
};
|
||
|
||
// Guard key to avoid infinite redirect loops across reloads
|
||
const REDIRECT_GUARD_KEY = "kvinit.redirecting";
|
||
|
||
const isKvProvider = (provider) =>
|
||
provider === "kv-server" || provider === "classworkscloud";
|
||
|
||
const shouldInitialize = () => {
|
||
const provider = getSetting("server.provider");
|
||
if (!isKvProvider(provider)) return false;
|
||
if (route.path === "/authorize") return false; // don't run during callback
|
||
const kvToken = getSetting("server.kvToken");
|
||
return kvToken === "" || kvToken == null;
|
||
};
|
||
|
||
const goToAuthorize = () => {
|
||
const authDomain = getSetting("server.authDomain");
|
||
const appId = "d158067f53627d2b98babe8bffd2fd7d";
|
||
const currentDomain = window.location.origin;
|
||
const callbackUrl = encodeURIComponent(`${currentDomain}/authorize`);
|
||
|
||
const uuid =
|
||
getSetting("device.uuid") || "00000000-0000-4000-8000-000000000000";
|
||
let authorizeUrl = `${authDomain}/authorize?app_id=${appId}&mode=callback&callback_url=${callbackUrl}&remark=Classworks 自动授权 来自${window.location.hostname} ${new Date().toLocaleString()}`;
|
||
|
||
// 如果UUID不是默认值,附加编码后的 uuid 参数用于迁移
|
||
if (uuid !== "00000000-0000-4000-8000-000000000000") {
|
||
authorizeUrl += `&uuid=${encodeURIComponent(uuid)}`;
|
||
}
|
||
|
||
// set a short-lived guard to prevent immediate re-redirect
|
||
try {
|
||
const guardObj = { ts: Date.now() };
|
||
sessionStorage.setItem(REDIRECT_GUARD_KEY, JSON.stringify(guardObj));
|
||
} catch (err) {
|
||
// sessionStorage may be unavailable in some environments
|
||
console.debug("sessionStorage set failed", err);
|
||
}
|
||
|
||
window.location.href = authorizeUrl;
|
||
};
|
||
|
||
const tryLoadNamespace = async () => {
|
||
try {
|
||
await kvServerProvider.loadNamespaceInfo();
|
||
} catch (err) {
|
||
console.error("加载命名空间信息失败:", err);
|
||
// not fatal, show non-blocking error
|
||
error.value = err && err.message ? err.message : String(err);
|
||
}
|
||
};
|
||
const useLocalMode = () => {
|
||
// Switch to local provider and hide dialog
|
||
setSetting("server.provider", "kv-local");
|
||
visible.value = false;
|
||
// Reload to let app re-evaluate
|
||
location.reload();
|
||
};
|
||
onMounted(async () => {
|
||
const provider = getSetting("server.provider");
|
||
|
||
// If not using kv provider, hide component immediately
|
||
if (!isKvProvider(provider)) {
|
||
visible.value = false;
|
||
return;
|
||
}
|
||
|
||
// First try loading namespace info (safe operation) so the app can continue if already authorized
|
||
loading.value = true;
|
||
await tryLoadNamespace();
|
||
loading.value = false;
|
||
|
||
// Decide whether we must show initialization UI / redirect
|
||
if (shouldInitialize()) {
|
||
// If there's a guard in sessionStorage and it's recent, don't auto-redirect to avoid loops
|
||
let guarded = false;
|
||
try {
|
||
const raw = sessionStorage.getItem(REDIRECT_GUARD_KEY);
|
||
if (raw) {
|
||
const obj = JSON.parse(raw);
|
||
// guard valid for 30 seconds
|
||
if (obj && obj.ts && Date.now() - obj.ts < 30000) guarded = true;
|
||
}
|
||
} catch (err) {
|
||
// ignore parse errors but log for debugging
|
||
console.debug("sessionStorage parse guard failed", err);
|
||
}
|
||
|
||
visible.value = true;
|
||
// Only auto-redirect if UUID is non-default (we have a device to migrate)
|
||
const uuid =
|
||
getSetting("device.uuid") || "00000000-0000-4000-8000-000000000000";
|
||
const isDefaultUuid = uuid === "00000000-0000-4000-8000-000000000000";
|
||
|
||
if (!guarded && !isDefaultUuid) {
|
||
// auto-redirect to authorize for better UX
|
||
goToAuthorize();
|
||
} else {
|
||
// if guarded or uuid is default, stay on the init UI and let user click button
|
||
// clear guard so subsequent attempts can redirect
|
||
try {
|
||
sessionStorage.removeItem(REDIRECT_GUARD_KEY);
|
||
} catch (err) {
|
||
console.debug("sessionStorage remove failed", err);
|
||
}
|
||
}
|
||
} else {
|
||
// not initializing: hide component
|
||
visible.value = false;
|
||
}
|
||
});
|
||
// add/remove listener in lifecycle hooks
|
||
if (typeof window !== "undefined") {
|
||
window.addEventListener('kvinit:open', onExternalOpen);
|
||
}
|
||
|
||
onBeforeUnmount(() => {
|
||
if (typeof window !== "undefined") {
|
||
window.removeEventListener('kvinit:open', onExternalOpen);
|
||
}
|
||
});
|
||
</script>
|