fix: 修复管理端静态资源基础路径

- 统一内置品牌资源与空状态图片的 BASE_URL 解析

- 应用启动时自动归一化历史偏好里的内置资源路径

- 多个空状态组件改为复用公共资源地址工具
This commit is contained in:
2026-03-24 18:39:16 +08:00
parent da536ea742
commit c78db961c5
10 changed files with 107 additions and 16 deletions

View File

@@ -16,6 +16,7 @@ import {
} from 'element-plus';
import { hasPermission } from '#/api/common/hasPermission.ts';
import { getEmptyStateImageUrl } from '#/utils/assets';
const props = defineProps({
titleKey: {
@@ -137,9 +138,7 @@ const filteredActions = computed(() => {
</div>
<div v-if="data.length === 0" class="empty-state">
<ElEmpty
:image="`/empty${preferences.theme.mode === 'dark' ? '-dark' : ''}.png`"
/>
<ElEmpty :image="getEmptyStateImageUrl(preferences.theme.mode)" />
</div>
</div>
</template>

View File

@@ -20,6 +20,8 @@ import {
ElMessageBox,
} from 'element-plus';
import { getEmptyStateImageUrl } from '#/utils/assets';
const props = defineProps({
title: {
type: String,
@@ -245,7 +247,7 @@ const handleDeleteClick = (event: any, item: any) => {
<!-- 无数据提示 -->
<div v-if="categoryData.length === 0 && !loading" class="no-data">
<ElEmpty
:image="`/empty${preferences.theme.mode === 'dark' ? '-dark' : ''}.png`"
:image="getEmptyStateImageUrl(preferences.theme.mode)"
:description="$t('common.noDataAvailable')"
/>
</div>

View File

@@ -7,6 +7,7 @@ import { ElEmpty } from 'element-plus';
import { JsonViewer } from 'vue3-json-viewer';
import 'vue3-json-viewer/dist/vue3-json-viewer.css';
import { getEmptyStateImageUrl } from '#/utils/assets';
defineProps({
value: {
@@ -26,10 +27,7 @@ watch(
<template>
<div class="res-container">
<JsonViewer v-if="value" :value="value" copyable :theme="themeMode" />
<ElEmpty
:image="`/empty${preferences.theme.mode === 'dark' ? '-dark' : ''}.png`"
v-else
/>
<ElEmpty :image="getEmptyStateImageUrl(preferences.theme.mode)" v-else />
</div>
</template>

View File

@@ -6,6 +6,7 @@ import { preferences } from '@easyflow/preferences';
import { ElEmpty, ElPagination } from 'element-plus';
import { api } from '#/api/request';
import { getEmptyStateImageUrl } from '#/utils/assets';
interface PageDataProps {
pageUrl: string;
@@ -126,9 +127,7 @@ onMounted(() => {
</div>
</template>
<slot v-else name="empty">
<ElEmpty
:image="`/empty${preferences.theme.mode === 'dark' ? '-dark' : ''}.png`"
/>
<ElEmpty :image="getEmptyStateImageUrl(preferences.theme.mode)" />
</slot>
</div>
</template>

View File

@@ -16,6 +16,8 @@ import {
ElIcon,
} from 'element-plus';
import { getEmptyStateImageUrl } from '#/utils/assets';
interface Props {
title?: string;
menus: T[];
@@ -182,7 +184,7 @@ const isSvgString = (icon: any) => {
</div>
<ElEmpty
v-if="menus.length <= 0"
:image="`/empty${preferences.theme.mode === 'dark' ? '-dark' : ''}.png`"
:image="getEmptyStateImageUrl(preferences.theme.mode)"
/>
</div>
</div>

View File

@@ -1,7 +1,10 @@
import { initPreferences } from '@easyflow/preferences';
import { initPreferences, preferences, updatePreferences } from '@easyflow/preferences';
import { unmountGlobalLoading } from '@easyflow/utils';
import { overridesPreferences } from './preferences';
import {
normalizeBrandAssetPreferences,
overridesPreferences,
} from './preferences';
/**
* 应用初始化完成之后再进行页面加载渲染
@@ -19,6 +22,11 @@ async function initApplication() {
overrides: overridesPreferences,
});
const { changed, normalized } = normalizeBrandAssetPreferences(preferences);
if (changed) {
updatePreferences(normalized);
}
// 启动应用并挂载
// vue应用主要逻辑及视图
const { bootstrap } = await import('./bootstrap');

View File

@@ -1,5 +1,7 @@
import { defineOverridesPreferences } from '@easyflow/preferences';
const assetBase = import.meta.env.BASE_URL || '/';
/**
* @description 项目配置文件
* 只需要覆盖项目中的一部分配置,不需要的配置不用覆盖,会自动使用默认配置
@@ -11,9 +13,75 @@ export const overridesPreferences = defineOverridesPreferences({
name: import.meta.env.VITE_APP_TITLE,
accessMode: 'mixed',
},
auth: {
sloganImage: `${assetBase}slogan.svg`,
},
logo: {
source: `${assetBase}logo.svg`,
sourceDark: `${assetBase}logoDark.svg`,
sourceMini: `${assetBase}logoMini.svg`,
sourceMiniDark: `${assetBase}logoMiniDark.svg`,
},
transition: {
enable: false,
loading: false,
progress: false,
},
});
const BUILTIN_BRAND_ASSETS = new Map([
['/logo.svg', 'logo.svg'],
['/logoDark.svg', 'logoDark.svg'],
['/logoMini.svg', 'logoMini.svg'],
['/logoMiniDark.svg', 'logoMiniDark.svg'],
['/slogan.svg', 'slogan.svg'],
]);
function normalizeBuiltinBrandAsset(path?: string) {
if (!path) {
return path;
}
const fileName = BUILTIN_BRAND_ASSETS.get(path);
if (!fileName) {
return path;
}
return `${assetBase}${fileName}`;
}
export function normalizeBrandAssetPreferences(preferences: {
auth?: { sloganImage?: string };
logo?: {
source?: string;
sourceDark?: string;
sourceMini?: string;
sourceMiniDark?: string;
};
}) {
const normalized = {
auth: {
sloganImage: normalizeBuiltinBrandAsset(preferences.auth?.sloganImage),
},
logo: {
source: normalizeBuiltinBrandAsset(preferences.logo?.source),
sourceDark: normalizeBuiltinBrandAsset(preferences.logo?.sourceDark),
sourceMini: normalizeBuiltinBrandAsset(preferences.logo?.sourceMini),
sourceMiniDark: normalizeBuiltinBrandAsset(
preferences.logo?.sourceMiniDark,
),
},
};
const changed =
normalized.auth.sloganImage !== preferences.auth?.sloganImage ||
normalized.logo.source !== preferences.logo?.source ||
normalized.logo.sourceDark !== preferences.logo?.sourceDark ||
normalized.logo.sourceMini !== preferences.logo?.sourceMini ||
normalized.logo.sourceMiniDark !== preferences.logo?.sourceMiniDark;
return {
changed,
normalized,
};
}

View File

@@ -0,0 +1,13 @@
const assetBase = import.meta.env.BASE_URL || '/';
export function getPublicAssetUrl(path: string) {
if (!path) {
return assetBase;
}
const normalizedPath = path.startsWith('/') ? path.slice(1) : path;
return `${assetBase}${normalizedPath}`;
}
export function getEmptyStateImageUrl(themeMode?: string) {
return getPublicAssetUrl(`empty${themeMode === 'dark' ? '-dark' : ''}.png`);
}

View File

@@ -23,6 +23,7 @@ import { getBotDetails, getSessionList } from '#/api';
import BotAvatar from '#/components/botAvatar/botAvatar.vue';
import Chat from '#/components/chat/chat.vue';
import { $t } from '#/locales';
import { getEmptyStateImageUrl } from '#/utils/assets';
const route = useRoute();
const router = useRouter();
@@ -132,7 +133,7 @@ const updateActive = (_sessionId?: number | string) => {
@update:active="updateActive"
/>
<ElEmpty
:image="`/empty${preferences.theme.mode === 'dark' ? '-dark' : ''}.png`"
:image="getEmptyStateImageUrl(preferences.theme.mode)"
v-show="sessionList.length === 0"
/>
</div>

View File

@@ -7,6 +7,7 @@ import { ElEmpty, ElMessage, ElRow } from 'element-plus';
import ShowJson from '#/components/json/ShowJson.vue';
import { $t } from '#/locales';
import { getEmptyStateImageUrl } from '#/utils/assets';
import ExecResultItem from '#/views/ai/workflow/components/ExecResultItem.vue';
export interface ExecResultProps {
@@ -87,7 +88,7 @@ function getResult(res: any) {
</div>
<div>
<ElEmpty
:image="`/empty${preferences.theme.mode === 'dark' ? '-dark' : ''}.png`"
:image="getEmptyStateImageUrl(preferences.theme.mode)"
v-if="!result"
/>
</div>