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