Skip to content

Commit b63f06b

Browse files
committed
feat: add api URL configurable support
1 parent 9d4472c commit b63f06b

2 files changed

Lines changed: 346 additions & 110 deletions

File tree

dashboard/src/main.ts

Lines changed: 164 additions & 110 deletions
Original file line numberDiff line numberDiff line change
@@ -1,139 +1,193 @@
1-
import { createApp } from 'vue';
2-
import { createPinia } from 'pinia';
3-
import App from './App.vue';
4-
import { router } from './router';
5-
import vuetify from './plugins/vuetify';
6-
import confirmPlugin from './plugins/confirmPlugin';
7-
import { setupI18n } from './i18n/composables';
8-
import '@/scss/style.scss';
9-
import VueApexCharts from 'vue3-apexcharts';
10-
11-
import print from 'vue3-print-nb';
12-
import { loader } from '@guolao/vue-monaco-editor'
13-
import * as monaco from 'monaco-editor';
14-
import editorWorker from 'monaco-editor/esm/vs/editor/editor.worker?worker';
15-
import jsonWorker from 'monaco-editor/esm/vs/language/json/json.worker?worker';
16-
import cssWorker from 'monaco-editor/esm/vs/language/css/css.worker?worker';
17-
import htmlWorker from 'monaco-editor/esm/vs/language/html/html.worker?worker';
18-
import tsWorker from 'monaco-editor/esm/vs/language/typescript/ts.worker?worker';
19-
import axios from 'axios';
20-
import { waitForRouterReadyInBackground } from './utils/routerReadiness.mjs';
1+
import { createApp } from "vue";
2+
import { createPinia } from "pinia";
3+
import App from "./App.vue";
4+
import { router } from "./router";
5+
import vuetify from "./plugins/vuetify";
6+
import confirmPlugin from "./plugins/confirmPlugin";
7+
import { setupI18n } from "./i18n/composables";
8+
import "@/scss/style.scss";
9+
import VueApexCharts from "vue3-apexcharts";
10+
11+
import print from "vue3-print-nb";
12+
import { loader } from "@guolao/vue-monaco-editor";
13+
import * as monaco from "monaco-editor";
14+
import editorWorker from "monaco-editor/esm/vs/editor/editor.worker?worker";
15+
import jsonWorker from "monaco-editor/esm/vs/language/json/json.worker?worker";
16+
import cssWorker from "monaco-editor/esm/vs/language/css/css.worker?worker";
17+
import htmlWorker from "monaco-editor/esm/vs/language/html/html.worker?worker";
18+
import tsWorker from "monaco-editor/esm/vs/language/typescript/ts.worker?worker";
19+
import axios from "axios";
20+
import { waitForRouterReadyInBackground } from "./utils/routerReadiness.mjs";
21+
import {
22+
getApiBaseUrl,
23+
resolveApiUrl,
24+
resolvePublicUrl,
25+
setApiBaseUrl,
26+
} from "@/utils/request";
2127

2228
(self as any).MonacoEnvironment = {
2329
getWorker(_: string, label: string) {
24-
if (label === 'json') {
30+
if (label === "json") {
2531
return new jsonWorker();
2632
}
27-
if (label === 'css' || label === 'scss' || label === 'less') {
33+
if (label === "css" || label === "scss" || label === "less") {
2834
return new cssWorker();
2935
}
30-
if (label === 'html' || label === 'handlebars' || label === 'razor') {
36+
if (label === "html" || label === "handlebars" || label === "razor") {
3137
return new htmlWorker();
3238
}
33-
if (label === 'typescript' || label === 'javascript') {
39+
if (label === "typescript" || label === "javascript") {
3440
return new tsWorker();
3541
}
3642
return new editorWorker();
3743
},
3844
};
3945

4046
// 初始化新的i18n系统,等待完成后再挂载应用
41-
setupI18n().then(async () => {
42-
console.log('🌍 新i18n系统初始化完成');
43-
44-
const app = createApp(App);
45-
const pinia = createPinia();
46-
app.use(pinia);
47-
app.use(router);
48-
app.use(print);
49-
app.use(VueApexCharts);
50-
app.use(vuetify);
51-
app.use(confirmPlugin);
52-
await router.isReady();
53-
app.mount('#app');
54-
55-
// 挂载后同步 Vuetify 主题
56-
import('./stores/customizer').then(({ useCustomizerStore }) => {
57-
const customizer = useCustomizerStore(pinia);
58-
vuetify.theme.global.name.value = customizer.uiTheme;
59-
const storedPrimary = localStorage.getItem('themePrimary');
60-
const storedSecondary = localStorage.getItem('themeSecondary');
61-
if (storedPrimary || storedSecondary) {
62-
const themes = vuetify.theme.themes.value;
63-
['PurpleTheme', 'PurpleThemeDark'].forEach((name) => {
64-
const theme = themes[name];
65-
if (!theme?.colors) return;
66-
if (storedPrimary) theme.colors.primary = storedPrimary;
67-
if (storedSecondary) theme.colors.secondary = storedSecondary;
68-
if (storedPrimary && theme.colors.darkprimary) theme.colors.darkprimary = storedPrimary;
69-
if (storedSecondary && theme.colors.darksecondary) theme.colors.darksecondary = storedSecondary;
70-
});
71-
}
72-
});
73-
}).catch(error => {
74-
console.error('❌ 新i18n系统初始化失败:', error);
75-
76-
// 即使i18n初始化失败,也要挂载应用(使用回退机制)
77-
const app = createApp(App);
78-
const pinia = createPinia();
79-
app.use(pinia);
80-
app.use(router);
81-
app.use(print);
82-
app.use(VueApexCharts);
83-
app.use(vuetify);
84-
app.use(confirmPlugin);
85-
app.mount('#app');
86-
waitForRouterReadyInBackground(router);
87-
88-
// 挂载后同步 Vuetify 主题
89-
import('./stores/customizer').then(({ useCustomizerStore }) => {
90-
const customizer = useCustomizerStore(pinia);
91-
vuetify.theme.global.name.value = customizer.uiTheme;
92-
const storedPrimary = localStorage.getItem('themePrimary');
93-
const storedSecondary = localStorage.getItem('themeSecondary');
94-
if (storedPrimary || storedSecondary) {
95-
const themes = vuetify.theme.themes.value;
96-
['PurpleTheme', 'PurpleThemeDark'].forEach((name) => {
97-
const theme = themes[name];
98-
if (!theme?.colors) return;
99-
if (storedPrimary) theme.colors.primary = storedPrimary;
100-
if (storedSecondary) theme.colors.secondary = storedSecondary;
101-
if (storedPrimary && theme.colors.darkprimary) theme.colors.darkprimary = storedPrimary;
102-
if (storedSecondary && theme.colors.darksecondary) theme.colors.darksecondary = storedSecondary;
103-
});
104-
}
105-
});
106-
});
47+
setupI18n()
48+
.then(async () => {
49+
console.log("🌍 新i18n系统初始化完成");
10750

51+
const app = createApp(App);
52+
const pinia = createPinia();
53+
app.use(pinia);
54+
app.use(router);
55+
app.use(print);
56+
app.use(VueApexCharts);
57+
app.use(vuetify);
58+
app.use(confirmPlugin);
59+
await router.isReady();
60+
app.mount("#app");
61+
62+
// 挂载后同步 Vuetify 主题
63+
import("./stores/customizer").then(({ useCustomizerStore }) => {
64+
const customizer = useCustomizerStore(pinia);
65+
vuetify.theme.global.name.value = customizer.uiTheme;
66+
const storedPrimary = localStorage.getItem("themePrimary");
67+
const storedSecondary = localStorage.getItem("themeSecondary");
68+
if (storedPrimary || storedSecondary) {
69+
const themes = vuetify.theme.themes.value;
70+
["PurpleTheme", "PurpleThemeDark"].forEach((name) => {
71+
const theme = themes[name];
72+
if (!theme?.colors) return;
73+
if (storedPrimary) theme.colors.primary = storedPrimary;
74+
if (storedSecondary) theme.colors.secondary = storedSecondary;
75+
if (storedPrimary && theme.colors.darkprimary)
76+
theme.colors.darkprimary = storedPrimary;
77+
if (storedSecondary && theme.colors.darksecondary)
78+
theme.colors.darksecondary = storedSecondary;
79+
});
80+
}
81+
});
82+
})
83+
.catch((error) => {
84+
console.error("❌ 新i18n系统初始化失败:", error);
85+
86+
// 即使i18n初始化失败,也要挂载应用(使用回退机制)
87+
const app = createApp(App);
88+
const pinia = createPinia();
89+
app.use(pinia);
90+
app.use(router);
91+
app.use(print);
92+
app.use(VueApexCharts);
93+
app.use(vuetify);
94+
app.use(confirmPlugin);
95+
app.mount("#app");
96+
waitForRouterReadyInBackground(router);
97+
98+
// 挂载后同步 Vuetify 主题
99+
import("./stores/customizer").then(({ useCustomizerStore }) => {
100+
const customizer = useCustomizerStore(pinia);
101+
vuetify.theme.global.name.value = customizer.uiTheme;
102+
const storedPrimary = localStorage.getItem("themePrimary");
103+
const storedSecondary = localStorage.getItem("themeSecondary");
104+
if (storedPrimary || storedSecondary) {
105+
const themes = vuetify.theme.themes.value;
106+
["PurpleTheme", "PurpleThemeDark"].forEach((name) => {
107+
const theme = themes[name];
108+
if (!theme?.colors) return;
109+
if (storedPrimary) theme.colors.primary = storedPrimary;
110+
if (storedSecondary) theme.colors.secondary = storedSecondary;
111+
if (storedPrimary && theme.colors.darkprimary)
112+
theme.colors.darkprimary = storedPrimary;
113+
if (storedSecondary && theme.colors.darksecondary)
114+
theme.colors.darksecondary = storedSecondary;
115+
});
116+
}
117+
});
118+
});
108119

109120
axios.interceptors.request.use((config) => {
110-
const token = localStorage.getItem('token');
121+
const token = localStorage.getItem("token");
111122
if (token) {
112-
config.headers['Authorization'] = `Bearer ${token}`;
123+
config.headers["Authorization"] = `Bearer ${token}`;
113124
}
114-
const locale = localStorage.getItem('astrbot-locale');
125+
const locale = localStorage.getItem("astrbot-locale");
115126
if (locale) {
116-
config.headers['Accept-Language'] = locale;
127+
config.headers["Accept-Language"] = locale;
117128
}
118129
return config;
119130
});
120131

121-
// Keep fetch() calls consistent with axios by automatically attaching the JWT.
122-
// Some parts of the UI use fetch directly; without this, those requests will 401.
123-
const _origFetch = window.fetch.bind(window);
124-
window.fetch = (input: RequestInfo | URL, init?: RequestInit) => {
125-
const token = localStorage.getItem('token');
126-
if (!token) return _origFetch(input, init);
127-
128-
const headers = new Headers(init?.headers || (typeof input !== 'string' && 'headers' in input ? (input as Request).headers : undefined));
129-
if (!headers.has('Authorization')) {
130-
headers.set('Authorization', `Bearer ${token}`);
132+
// 1. 定义加载配置的函数
133+
async function loadAppConfig() {
134+
try {
135+
const configUrl = new URL(resolvePublicUrl("config.json"));
136+
configUrl.searchParams.set("t", `${Date.now()}`);
137+
const response = await fetch(configUrl.toString());
138+
if (!response.ok) {
139+
throw new Error(`HTTP error! status: ${response.status}`);
140+
}
141+
return await response.json();
142+
} catch (error) {
143+
console.warn("Failed to load config.json, falling back to default.", error);
144+
return {};
131145
}
132-
const locale = localStorage.getItem('astrbot-locale');
133-
if (locale && !headers.has('Accept-Language')) {
134-
headers.set('Accept-Language', locale);
146+
}
147+
148+
async function initApp() {
149+
const config = await loadAppConfig();
150+
const configApiUrl = config.apiBaseUrl || "";
151+
const envApiUrl = import.meta.env.VITE_API_BASE || "";
152+
153+
const localApiUrl = localStorage.getItem("apiBaseUrl");
154+
const apiBaseUrl =
155+
localApiUrl !== null ? localApiUrl : configApiUrl || envApiUrl;
156+
157+
if (apiBaseUrl) {
158+
console.log(`API Base URL set to: ${apiBaseUrl}`);
135159
}
136-
return _origFetch(input, { ...init, headers });
137-
};
138160

139-
loader.config({ monaco })
161+
setApiBaseUrl(apiBaseUrl);
162+
163+
// Keep fetch() calls consistent with axios by automatically attaching the JWT.
164+
// Some parts of the UI use fetch directly; without this, those requests will 401.
165+
const _origFetch = window.fetch.bind(window);
166+
window.fetch = (input: RequestInfo | URL, init?: RequestInit) => {
167+
let url = input;
168+
if (typeof input === "string" && input.startsWith("/api")) {
169+
url = resolveApiUrl(input, getApiBaseUrl());
170+
}
171+
172+
const token = localStorage.getItem("token");
173+
const headers = new Headers(
174+
init?.headers ||
175+
(typeof input !== "string" && "headers" in input
176+
? (input as Request).headers
177+
: undefined),
178+
);
179+
180+
if (token && !headers.has("Authorization")) {
181+
headers.set("Authorization", `Bearer ${token}`);
182+
}
183+
const locale = localStorage.getItem("astrbot-locale");
184+
if (locale && !headers.has("Accept-Language")) {
185+
headers.set("Accept-Language", locale);
186+
}
187+
return _origFetch(url, { ...init, headers });
188+
};
189+
}
190+
191+
initApp();
192+
193+
loader.config({ monaco });

0 commit comments

Comments
 (0)