fix: 修复管理端静态资源基础路径
- 统一内置品牌资源与空状态图片的 BASE_URL 解析 - 应用启动时自动归一化历史偏好里的内置资源路径 - 多个空状态组件改为复用公共资源地址工具
This commit is contained in:
@@ -16,6 +16,7 @@ import {
|
|||||||
} from 'element-plus';
|
} from 'element-plus';
|
||||||
|
|
||||||
import { hasPermission } from '#/api/common/hasPermission.ts';
|
import { hasPermission } from '#/api/common/hasPermission.ts';
|
||||||
|
import { getEmptyStateImageUrl } from '#/utils/assets';
|
||||||
|
|
||||||
const props = defineProps({
|
const props = defineProps({
|
||||||
titleKey: {
|
titleKey: {
|
||||||
@@ -137,9 +138,7 @@ const filteredActions = computed(() => {
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div v-if="data.length === 0" class="empty-state">
|
<div v-if="data.length === 0" class="empty-state">
|
||||||
<ElEmpty
|
<ElEmpty :image="getEmptyStateImageUrl(preferences.theme.mode)" />
|
||||||
:image="`/empty${preferences.theme.mode === 'dark' ? '-dark' : ''}.png`"
|
|
||||||
/>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|||||||
@@ -20,6 +20,8 @@ import {
|
|||||||
ElMessageBox,
|
ElMessageBox,
|
||||||
} from 'element-plus';
|
} from 'element-plus';
|
||||||
|
|
||||||
|
import { getEmptyStateImageUrl } from '#/utils/assets';
|
||||||
|
|
||||||
const props = defineProps({
|
const props = defineProps({
|
||||||
title: {
|
title: {
|
||||||
type: String,
|
type: String,
|
||||||
@@ -245,7 +247,7 @@ const handleDeleteClick = (event: any, item: any) => {
|
|||||||
<!-- 无数据提示 -->
|
<!-- 无数据提示 -->
|
||||||
<div v-if="categoryData.length === 0 && !loading" class="no-data">
|
<div v-if="categoryData.length === 0 && !loading" class="no-data">
|
||||||
<ElEmpty
|
<ElEmpty
|
||||||
:image="`/empty${preferences.theme.mode === 'dark' ? '-dark' : ''}.png`"
|
:image="getEmptyStateImageUrl(preferences.theme.mode)"
|
||||||
:description="$t('common.noDataAvailable')"
|
:description="$t('common.noDataAvailable')"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ import { ElEmpty } from 'element-plus';
|
|||||||
import { JsonViewer } from 'vue3-json-viewer';
|
import { JsonViewer } from 'vue3-json-viewer';
|
||||||
|
|
||||||
import 'vue3-json-viewer/dist/vue3-json-viewer.css';
|
import 'vue3-json-viewer/dist/vue3-json-viewer.css';
|
||||||
|
import { getEmptyStateImageUrl } from '#/utils/assets';
|
||||||
|
|
||||||
defineProps({
|
defineProps({
|
||||||
value: {
|
value: {
|
||||||
@@ -26,10 +27,7 @@ watch(
|
|||||||
<template>
|
<template>
|
||||||
<div class="res-container">
|
<div class="res-container">
|
||||||
<JsonViewer v-if="value" :value="value" copyable :theme="themeMode" />
|
<JsonViewer v-if="value" :value="value" copyable :theme="themeMode" />
|
||||||
<ElEmpty
|
<ElEmpty :image="getEmptyStateImageUrl(preferences.theme.mode)" v-else />
|
||||||
:image="`/empty${preferences.theme.mode === 'dark' ? '-dark' : ''}.png`"
|
|
||||||
v-else
|
|
||||||
/>
|
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import { preferences } from '@easyflow/preferences';
|
|||||||
import { ElEmpty, ElPagination } from 'element-plus';
|
import { ElEmpty, ElPagination } from 'element-plus';
|
||||||
|
|
||||||
import { api } from '#/api/request';
|
import { api } from '#/api/request';
|
||||||
|
import { getEmptyStateImageUrl } from '#/utils/assets';
|
||||||
|
|
||||||
interface PageDataProps {
|
interface PageDataProps {
|
||||||
pageUrl: string;
|
pageUrl: string;
|
||||||
@@ -126,9 +127,7 @@ onMounted(() => {
|
|||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
<slot v-else name="empty">
|
<slot v-else name="empty">
|
||||||
<ElEmpty
|
<ElEmpty :image="getEmptyStateImageUrl(preferences.theme.mode)" />
|
||||||
:image="`/empty${preferences.theme.mode === 'dark' ? '-dark' : ''}.png`"
|
|
||||||
/>
|
|
||||||
</slot>
|
</slot>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|||||||
@@ -16,6 +16,8 @@ import {
|
|||||||
ElIcon,
|
ElIcon,
|
||||||
} from 'element-plus';
|
} from 'element-plus';
|
||||||
|
|
||||||
|
import { getEmptyStateImageUrl } from '#/utils/assets';
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
title?: string;
|
title?: string;
|
||||||
menus: T[];
|
menus: T[];
|
||||||
@@ -182,7 +184,7 @@ const isSvgString = (icon: any) => {
|
|||||||
</div>
|
</div>
|
||||||
<ElEmpty
|
<ElEmpty
|
||||||
v-if="menus.length <= 0"
|
v-if="menus.length <= 0"
|
||||||
:image="`/empty${preferences.theme.mode === 'dark' ? '-dark' : ''}.png`"
|
:image="getEmptyStateImageUrl(preferences.theme.mode)"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,7 +1,10 @@
|
|||||||
import { initPreferences } from '@easyflow/preferences';
|
import { initPreferences, preferences, updatePreferences } from '@easyflow/preferences';
|
||||||
import { unmountGlobalLoading } from '@easyflow/utils';
|
import { unmountGlobalLoading } from '@easyflow/utils';
|
||||||
|
|
||||||
import { overridesPreferences } from './preferences';
|
import {
|
||||||
|
normalizeBrandAssetPreferences,
|
||||||
|
overridesPreferences,
|
||||||
|
} from './preferences';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 应用初始化完成之后再进行页面加载渲染
|
* 应用初始化完成之后再进行页面加载渲染
|
||||||
@@ -19,6 +22,11 @@ async function initApplication() {
|
|||||||
overrides: overridesPreferences,
|
overrides: overridesPreferences,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const { changed, normalized } = normalizeBrandAssetPreferences(preferences);
|
||||||
|
if (changed) {
|
||||||
|
updatePreferences(normalized);
|
||||||
|
}
|
||||||
|
|
||||||
// 启动应用并挂载
|
// 启动应用并挂载
|
||||||
// vue应用主要逻辑及视图
|
// vue应用主要逻辑及视图
|
||||||
const { bootstrap } = await import('./bootstrap');
|
const { bootstrap } = await import('./bootstrap');
|
||||||
|
|||||||
@@ -1,5 +1,7 @@
|
|||||||
import { defineOverridesPreferences } from '@easyflow/preferences';
|
import { defineOverridesPreferences } from '@easyflow/preferences';
|
||||||
|
|
||||||
|
const assetBase = import.meta.env.BASE_URL || '/';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @description 项目配置文件
|
* @description 项目配置文件
|
||||||
* 只需要覆盖项目中的一部分配置,不需要的配置不用覆盖,会自动使用默认配置
|
* 只需要覆盖项目中的一部分配置,不需要的配置不用覆盖,会自动使用默认配置
|
||||||
@@ -11,9 +13,75 @@ export const overridesPreferences = defineOverridesPreferences({
|
|||||||
name: import.meta.env.VITE_APP_TITLE,
|
name: import.meta.env.VITE_APP_TITLE,
|
||||||
accessMode: 'mixed',
|
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: {
|
transition: {
|
||||||
enable: false,
|
enable: false,
|
||||||
loading: false,
|
loading: false,
|
||||||
progress: 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,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|||||||
13
easyflow-ui-admin/app/src/utils/assets.ts
Normal file
13
easyflow-ui-admin/app/src/utils/assets.ts
Normal 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`);
|
||||||
|
}
|
||||||
@@ -23,6 +23,7 @@ import { getBotDetails, getSessionList } from '#/api';
|
|||||||
import BotAvatar from '#/components/botAvatar/botAvatar.vue';
|
import BotAvatar from '#/components/botAvatar/botAvatar.vue';
|
||||||
import Chat from '#/components/chat/chat.vue';
|
import Chat from '#/components/chat/chat.vue';
|
||||||
import { $t } from '#/locales';
|
import { $t } from '#/locales';
|
||||||
|
import { getEmptyStateImageUrl } from '#/utils/assets';
|
||||||
|
|
||||||
const route = useRoute();
|
const route = useRoute();
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
@@ -132,7 +133,7 @@ const updateActive = (_sessionId?: number | string) => {
|
|||||||
@update:active="updateActive"
|
@update:active="updateActive"
|
||||||
/>
|
/>
|
||||||
<ElEmpty
|
<ElEmpty
|
||||||
:image="`/empty${preferences.theme.mode === 'dark' ? '-dark' : ''}.png`"
|
:image="getEmptyStateImageUrl(preferences.theme.mode)"
|
||||||
v-show="sessionList.length === 0"
|
v-show="sessionList.length === 0"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ import { ElEmpty, ElMessage, ElRow } from 'element-plus';
|
|||||||
|
|
||||||
import ShowJson from '#/components/json/ShowJson.vue';
|
import ShowJson from '#/components/json/ShowJson.vue';
|
||||||
import { $t } from '#/locales';
|
import { $t } from '#/locales';
|
||||||
|
import { getEmptyStateImageUrl } from '#/utils/assets';
|
||||||
import ExecResultItem from '#/views/ai/workflow/components/ExecResultItem.vue';
|
import ExecResultItem from '#/views/ai/workflow/components/ExecResultItem.vue';
|
||||||
|
|
||||||
export interface ExecResultProps {
|
export interface ExecResultProps {
|
||||||
@@ -87,7 +88,7 @@ function getResult(res: any) {
|
|||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<ElEmpty
|
<ElEmpty
|
||||||
:image="`/empty${preferences.theme.mode === 'dark' ? '-dark' : ''}.png`"
|
:image="getEmptyStateImageUrl(preferences.theme.mode)"
|
||||||
v-if="!result"
|
v-if="!result"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
Reference in New Issue
Block a user