fix: 修复子路径部署静态资源引用
- 修复 admin 与 usercenter 登录验证码资源在 /flow 子路径下的加载路径 - 统一 logo、空状态图、兜底头像与模型服务商图标的 BASE_URL 处理 - 补齐 usercenter 公共布局与 loading 注入的子路径兼容
This commit is contained in:
@@ -19,6 +19,7 @@ const props = defineProps({
|
||||
},
|
||||
});
|
||||
const emits = defineEmits(['delete']);
|
||||
const fallbackAvatarUrl = `${import.meta.env.BASE_URL || '/'}favicon.svg`;
|
||||
const handleDelete = (item: any) => {
|
||||
ElMessageBox.confirm($t('message.deleteAlert'), $t('message.noticeTitle'), {
|
||||
confirmButtonText: $t('button.confirm'),
|
||||
@@ -40,7 +41,7 @@ const handleDelete = (item: any) => {
|
||||
<div class="el-list-item-container">
|
||||
<div class="flex-center">
|
||||
<ElAvatar :src="item.icon" v-if="item.icon" />
|
||||
<ElAvatar v-else src="/favicon.svg" shape="circle" />
|
||||
<ElAvatar v-else :src="fallbackAvatarUrl" shape="circle" />
|
||||
</div>
|
||||
<div class="el-list-item-content">
|
||||
<div class="title">{{ item[titleKey] }}</div>
|
||||
|
||||
@@ -57,6 +57,7 @@ const emit = defineEmits(['getData', 'buttonClick']);
|
||||
const dialogVisible = ref(false);
|
||||
const pageDataRef = ref();
|
||||
const loading = ref(false);
|
||||
const fallbackAvatarUrl = `${import.meta.env.BASE_URL || '/'}favicon.svg`;
|
||||
const selectedIds = ref<(number | string)[]>([]);
|
||||
// 存储上一级id与选中tool.name的关联关系
|
||||
const selectedToolMap = ref<Record<number | string, SelectedMcpTool[]>>({});
|
||||
@@ -231,7 +232,11 @@ const handleSearch = (query: string) => {
|
||||
>
|
||||
<div>
|
||||
<ElAvatar :src="item.icon" v-if="item.icon" />
|
||||
<ElAvatar v-else src="/favicon.svg" shape="circle" />
|
||||
<ElAvatar
|
||||
v-else
|
||||
:src="fallbackAvatarUrl"
|
||||
shape="circle"
|
||||
/>
|
||||
</div>
|
||||
<div class="title-right-container">
|
||||
<ElText truncated class="title">
|
||||
@@ -326,7 +331,11 @@ const handleSearch = (query: string) => {
|
||||
<div class="content-sec-left-container">
|
||||
<div>
|
||||
<ElAvatar :src="item.icon" v-if="item.icon" />
|
||||
<ElAvatar v-else src="/favicon.svg" shape="circle" />
|
||||
<ElAvatar
|
||||
v-else
|
||||
:src="fallbackAvatarUrl"
|
||||
shape="circle"
|
||||
/>
|
||||
</div>
|
||||
<div class="title-sec-right-container">
|
||||
<ElText truncated class="title">
|
||||
|
||||
@@ -13,6 +13,9 @@ onMounted(() => {});
|
||||
const authStore = useAuthStore();
|
||||
|
||||
const { apiURL } = useAppConfig(import.meta.env, import.meta.env.PROD);
|
||||
const assetBase = import.meta.env.BASE_URL || '/';
|
||||
const captchaAssetBase = `${assetBase}tac`;
|
||||
const captchaButtonUrl = `${assetBase}tac-btn.png`;
|
||||
|
||||
const formSchema = computed((): EasyFlowFormSchema[] => {
|
||||
return [
|
||||
@@ -71,11 +74,11 @@ function onSubmit(values: any) {
|
||||
const style = {
|
||||
logoUrl: null, // 去除logo
|
||||
// logoUrl: "/xx/xx/xxx.png" // 替换成自定义的logo
|
||||
btnUrl: '/tac-btn.png',
|
||||
btnUrl: captchaButtonUrl,
|
||||
};
|
||||
window
|
||||
// @ts-ignore
|
||||
.initTAC('/tac', config, style)
|
||||
.initTAC(captchaAssetBase, config, style)
|
||||
.then((tac: any) => {
|
||||
tac.init(); // 调用init则显示验证码
|
||||
})
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
import providerList from './providerList.json';
|
||||
|
||||
const assetBase = import.meta.env.BASE_URL || '/';
|
||||
|
||||
export interface ProviderModelPreset {
|
||||
description: string;
|
||||
label: string;
|
||||
@@ -28,8 +30,25 @@ export interface ProviderPreset {
|
||||
|
||||
const providerOptions = providerList as ProviderPreset[];
|
||||
|
||||
export const getProviderPresetByValue = (targetValue?: string) =>
|
||||
providerOptions.find((item) => item.value === targetValue);
|
||||
const normalizeAssetUrl = (url?: string) => {
|
||||
if (!url || !url.startsWith('/')) {
|
||||
return url || '';
|
||||
}
|
||||
return `${assetBase}${url.slice(1)}`;
|
||||
};
|
||||
|
||||
export const getProviderPresetByValue = (targetValue?: string) => {
|
||||
const preset = providerOptions.find((item) => item.value === targetValue);
|
||||
|
||||
if (!preset) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
return {
|
||||
...preset,
|
||||
icon: normalizeAssetUrl(preset.icon),
|
||||
};
|
||||
};
|
||||
|
||||
/**
|
||||
* 根据传入的value,返回对应的icon属性
|
||||
@@ -63,4 +82,7 @@ export const getProviderBadgeText = (
|
||||
return source.replaceAll(/\s+/g, '').slice(0, 2).toUpperCase();
|
||||
};
|
||||
|
||||
export const providerPresets = providerOptions;
|
||||
export const providerPresets = providerOptions.map((item) => ({
|
||||
...item,
|
||||
icon: normalizeAssetUrl(item.icon),
|
||||
}));
|
||||
|
||||
@@ -142,7 +142,7 @@ const defaultPreferences: Preferences = {
|
||||
timezone: false,
|
||||
},
|
||||
auth: {
|
||||
sloganImage: '/slogan.svg',
|
||||
sloganImage: `${assetBase}slogan.svg`,
|
||||
pageTitle: '',
|
||||
pageDescription: '',
|
||||
welcomeBack: '',
|
||||
|
||||
@@ -14,6 +14,7 @@ defineProps({
|
||||
},
|
||||
});
|
||||
|
||||
const emptyImageUrl = `${import.meta.env.BASE_URL || '/'}empty.png`;
|
||||
const themeMode = ref(preferences.theme.mode);
|
||||
watch(
|
||||
() => preferences.theme.mode,
|
||||
@@ -26,7 +27,7 @@ watch(
|
||||
<template>
|
||||
<div class="res-container">
|
||||
<JsonViewer v-if="value" :value="value" copyable :theme="themeMode" />
|
||||
<ElEmpty image="/empty.png" v-else />
|
||||
<ElEmpty :image="emptyImageUrl" v-else />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
|
||||
@@ -17,6 +17,7 @@ const props = withDefaults(defineProps<PageDataProps>(), {
|
||||
pageSizes: () => [10, 20, 50, 100],
|
||||
extraQueryParams: () => ({}),
|
||||
});
|
||||
const emptyImageUrl = `${import.meta.env.BASE_URL || '/'}empty.png`;
|
||||
|
||||
// 响应式数据
|
||||
const pageList = ref([]);
|
||||
@@ -119,6 +120,6 @@ onMounted(() => {
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
<ElEmpty image="/empty.png" v-else />
|
||||
<ElEmpty :image="emptyImageUrl" v-else />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -12,6 +12,9 @@ import { useAuthStore } from '#/store';
|
||||
defineOptions({ name: 'Login' });
|
||||
const { apiURL } = useAppConfig(import.meta.env, import.meta.env.PROD);
|
||||
const authStore = useAuthStore();
|
||||
const assetBase = import.meta.env.BASE_URL || '/';
|
||||
const captchaAssetBase = `${assetBase}tac`;
|
||||
const captchaButtonUrl = `${assetBase}tac-btn.png`;
|
||||
|
||||
const formSchema = computed((): EasyFlowFormSchema[] => {
|
||||
return [
|
||||
@@ -69,11 +72,11 @@ function onSubmit(values: any) {
|
||||
const style = {
|
||||
logoUrl: null, // 去除logo
|
||||
// logoUrl: "/xx/xx/xxx.png" // 替换成自定义的logo
|
||||
btnUrl: '/tac-btn.png',
|
||||
btnUrl: captchaButtonUrl,
|
||||
};
|
||||
window
|
||||
// @ts-ignore
|
||||
.initTAC('/tac', config, style)
|
||||
.initTAC(captchaAssetBase, config, style)
|
||||
.then((tac: any) => {
|
||||
tac.init(); // 调用init则显示验证码
|
||||
})
|
||||
|
||||
@@ -19,6 +19,7 @@ const props = withDefaults(defineProps<ExecResultProps>(), {
|
||||
showMessage: true,
|
||||
pollingData: {},
|
||||
});
|
||||
const emptyImageUrl = `${import.meta.env.BASE_URL || '/'}empty.png`;
|
||||
|
||||
const finalNode = computed(() => {
|
||||
const nodes = props.nodeJson;
|
||||
@@ -93,7 +94,7 @@ function getResult(res: any) {
|
||||
<ShowJson :value="result" />
|
||||
</div>
|
||||
<div>
|
||||
<ElEmpty image="/empty.png" v-if="!result" />
|
||||
<ElEmpty :image="emptyImageUrl" v-if="!result" />
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -16,6 +16,7 @@ const ids = reactive({
|
||||
const conversationInfo = ref<any>();
|
||||
const messageList = ref<any[]>([]);
|
||||
const loading = ref(true);
|
||||
const logoUrl = `${import.meta.env.BASE_URL || '/'}logo.svg`;
|
||||
|
||||
onMounted(() => {
|
||||
if (route.params.id) {
|
||||
@@ -80,7 +81,7 @@ function getMessageList() {
|
||||
</div>
|
||||
|
||||
<!-- Right -->
|
||||
<img src="/logo.svg" class="w-40 max-sm:w-28" />
|
||||
<img :src="logoUrl" class="w-40 max-sm:w-28" />
|
||||
</div>
|
||||
</ElHeader>
|
||||
<ElMain class="relative max-sm:mt-2 max-sm:!p-0" v-loading="loading">
|
||||
|
||||
@@ -20,6 +20,7 @@ async function viteInjectAppLoadingPlugin(
|
||||
const { version } = await readPackageJSON(process.cwd());
|
||||
const envRaw = isBuild ? 'prod' : 'dev';
|
||||
const cacheName = `'${env.VITE_APP_NAMESPACE}-${version}-${envRaw}-preferences-theme'`;
|
||||
const appBase = JSON.stringify(ensureTrailingSlash(env.VITE_BASE || '/'));
|
||||
|
||||
// 获取缓存的主题
|
||||
// 保证黑暗主题下,刷新页面时,loading也是黑暗主题
|
||||
@@ -29,7 +30,7 @@ async function viteInjectAppLoadingPlugin(
|
||||
document.documentElement.classList.toggle('dark', /dark/.test(theme));
|
||||
setTimeout(() => {
|
||||
if (/dark/.test(theme)) {
|
||||
document.querySelector('#__app-loading__ img').src = '/logoDark.svg';
|
||||
document.querySelector('#__app-loading__ img').src = ${appBase} + 'logoDark.svg';
|
||||
}
|
||||
})
|
||||
</script>
|
||||
@@ -68,4 +69,8 @@ async function getLoadingRawByHtmlTemplate(loadingTemplate: string) {
|
||||
return await fsp.readFile(appLoadingPath, 'utf8');
|
||||
}
|
||||
|
||||
function ensureTrailingSlash(path: string) {
|
||||
return path.endsWith('/') ? path : `${path}/`;
|
||||
}
|
||||
|
||||
export { viteInjectAppLoadingPlugin };
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
import type { Preferences } from './types';
|
||||
|
||||
const assetBase = import.meta.env.BASE_URL || '/';
|
||||
|
||||
const defaultPreferences: Preferences = {
|
||||
app: {
|
||||
accessMode: 'frontend',
|
||||
@@ -65,8 +67,9 @@ const defaultPreferences: Preferences = {
|
||||
logo: {
|
||||
enable: true,
|
||||
fit: 'contain',
|
||||
source: '/logo.svg',
|
||||
sourceDark: '/logoDark.svg',
|
||||
source: `${assetBase}logo.svg`,
|
||||
sourceDark: `${assetBase}logoDark.svg`,
|
||||
sourceMini: `${assetBase}logoMini.svg`,
|
||||
},
|
||||
navigation: {
|
||||
accordion: true,
|
||||
|
||||
@@ -148,6 +148,10 @@ interface LogoPreferences {
|
||||
source: string;
|
||||
/** 暗色主题logo地址 (可选,若不设置则使用 source) */
|
||||
sourceDark?: string;
|
||||
/** 侧边栏收起时 logo 地址 (可选,若不设置则使用 source) */
|
||||
sourceMini?: string;
|
||||
/** 暗色主题下侧边栏收起 logo 地址 (可选,若不设置则按 sourceMini/sourceDark/source 回退) */
|
||||
sourceMiniDark?: string;
|
||||
}
|
||||
|
||||
interface NavigationPreferences {
|
||||
|
||||
@@ -30,6 +30,14 @@ interface Props {
|
||||
* @zh_CN 暗色主题 Logo 图标 (可选,若不设置则使用 src)
|
||||
*/
|
||||
srcDark?: string;
|
||||
/**
|
||||
* @zh_CN 侧边栏收起时 Logo 图标 (可选,若不设置则使用 src)
|
||||
*/
|
||||
srcMini?: string;
|
||||
/**
|
||||
* @zh_CN 暗色主题下侧边栏收起时 Logo 图标 (可选,若不设置则按 srcMini/srcDark/src 回退)
|
||||
*/
|
||||
srcMiniDark?: string;
|
||||
/**
|
||||
* @zh_CN Logo 文本
|
||||
*/
|
||||
@@ -50,6 +58,8 @@ const props = withDefaults(defineProps<Props>(), {
|
||||
logoSize: 120,
|
||||
src: '',
|
||||
srcDark: '',
|
||||
srcMini: '',
|
||||
srcMiniDark: '',
|
||||
theme: 'light',
|
||||
fit: 'cover',
|
||||
});
|
||||
@@ -59,7 +69,10 @@ const props = withDefaults(defineProps<Props>(), {
|
||||
*/
|
||||
const logoSrc = computed(() => {
|
||||
if (props.collapsed) {
|
||||
return '/logoMini.svg';
|
||||
if (props.theme === 'dark' && props.srcMiniDark) {
|
||||
return props.srcMiniDark;
|
||||
}
|
||||
return props.srcMini || props.src;
|
||||
}
|
||||
// 如果是暗色主题且提供了 srcDark,则使用暗色主题的 logo
|
||||
if (props.theme === 'dark' && props.srcDark) {
|
||||
|
||||
@@ -260,6 +260,14 @@ const headerSlots = computed(() => {
|
||||
:collapsed="logoCollapsed"
|
||||
:src="preferences.logo.source"
|
||||
:src-dark="preferences.logo.sourceDark"
|
||||
:src-mini="preferences.logo.sourceMini ?? preferences.logo.source ?? ''"
|
||||
:src-mini-dark="
|
||||
preferences.logo.sourceMiniDark ??
|
||||
preferences.logo.sourceMini ??
|
||||
preferences.logo.sourceDark ??
|
||||
preferences.logo.source ??
|
||||
''
|
||||
"
|
||||
:text="preferences.app.name"
|
||||
:theme="showHeaderNav ? headerTheme : theme"
|
||||
@click="clickLogo"
|
||||
@@ -353,6 +361,14 @@ const headerSlots = computed(() => {
|
||||
:fit="preferences.logo.fit"
|
||||
:src="preferences.logo.source"
|
||||
:src-dark="preferences.logo.sourceDark"
|
||||
:src-mini="preferences.logo.sourceMini ?? preferences.logo.source ?? ''"
|
||||
:src-mini-dark="
|
||||
preferences.logo.sourceMiniDark ??
|
||||
preferences.logo.sourceMini ??
|
||||
preferences.logo.sourceDark ??
|
||||
preferences.logo.source ??
|
||||
''
|
||||
"
|
||||
:text="preferences.app.name"
|
||||
:theme="theme"
|
||||
>
|
||||
|
||||
Reference in New Issue
Block a user