release: prepare v0.1.131

This commit is contained in:
kone
2026-05-14 05:18:31 +08:00
parent 066ceb823e
commit 41e60b20d6
20 changed files with 2818 additions and 337 deletions
+35 -1
View File
@@ -47,7 +47,26 @@
</div>
<!-- Nav Actions -->
<div class="flex items-center gap-3">
<div class="flex items-center gap-2 sm:gap-3">
<div
v-if="homeHeaderMenuItems.length > 0"
class="hidden items-center gap-1 lg:flex"
>
<router-link
v-for="item in homeHeaderMenuItems"
:key="item.id"
:to="customMenuRoute(item.id)"
class="inline-flex items-center gap-2 rounded-full border border-gray-200/70 bg-white/80 px-3 py-2 text-sm font-medium text-gray-700 transition-colors hover:border-primary-300 hover:text-primary-700 dark:border-dark-700/80 dark:bg-dark-900/70 dark:text-dark-100 dark:hover:border-primary-500/50 dark:hover:text-white"
>
<span
v-if="item.icon_svg"
class="flex h-4 w-4 items-center justify-center text-current"
v-html="sanitizeSvg(item.icon_svg)"
></span>
<span>{{ item.label }}</span>
</router-link>
</div>
<!-- Language Switcher -->
<LocaleSwitcher />
@@ -410,6 +429,12 @@ import { useI18n } from 'vue-i18n'
import { useAuthStore, useAppStore } from '@/stores'
import LocaleSwitcher from '@/components/common/LocaleSwitcher.vue'
import Icon from '@/components/icons/Icon.vue'
import { sanitizeSvg } from '@/utils/sanitize'
import {
getCustomMenuRoute,
isHomeHeaderMenuPlacement,
normalizeCustomMenuItems,
} from '@/utils/custom-menu'
const { t } = useI18n()
@@ -422,6 +447,11 @@ const siteLogo = computed(() => appStore.cachedPublicSettings?.site_logo || appS
const siteSubtitle = computed(() => appStore.cachedPublicSettings?.site_subtitle || 'AI API Gateway Platform')
const docUrl = computed(() => appStore.cachedPublicSettings?.doc_url || appStore.docUrl || '')
const homeContent = computed(() => appStore.cachedPublicSettings?.home_content || '')
const homeHeaderMenuItems = computed(() =>
normalizeCustomMenuItems(appStore.cachedPublicSettings?.custom_menu_items)
.filter((item) => item.visibility === 'user' && isHomeHeaderMenuPlacement(item))
.sort((a, b) => a.sort_order - b.sort_order)
)
// Check if homeContent is a URL (for iframe display)
const isHomeContentUrl = computed(() => {
@@ -448,6 +478,10 @@ const userInitial = computed(() => {
// Current year for footer
const currentYear = computed(() => new Date().getFullYear())
function customMenuRoute(id: string) {
return getCustomMenuRoute(id)
}
// Toggle theme
function toggleTheme() {
isDark.value = !isDark.value
+37 -1
View File
@@ -4408,6 +4408,26 @@
</select>
</div>
<!-- Placement -->
<div>
<label
class="mb-1 block text-xs font-medium text-gray-600 dark:text-gray-400"
>
{{ t("admin.settings.customMenu.placement") }}
</label>
<select v-model="item.placement" class="input text-sm">
<option value="sidebar">
{{ t("admin.settings.customMenu.placementSidebar") }}
</option>
<option value="home_header">
{{ t("admin.settings.customMenu.placementHomeHeader") }}
</option>
<option value="both">
{{ t("admin.settings.customMenu.placementBoth") }}
</option>
</select>
</div>
<!-- URL (full width) -->
<div class="sm:col-span-2">
<label
@@ -6145,6 +6165,7 @@ import type {
} from "@/api/admin/settings";
import type {
AdminGroup,
CustomMenuPlacement,
LoginAgreementDocument,
NotifyEmailEntry,
Proxy,
@@ -6163,6 +6184,10 @@ import ProxySelector from "@/components/common/ProxySelector.vue";
import ImageUpload from "@/components/common/ImageUpload.vue";
import BackupSettings from "@/views/admin/BackupView.vue";
import { useClipboard } from "@/composables/useClipboard";
import {
DEFAULT_CUSTOM_MENU_PLACEMENT,
normalizeCustomMenuItems,
} from "@/utils/custom-menu";
import { affiliatesAPI, type AffiliateAdminEntry, type SimpleUser as AffiliateSimpleUser } from "@/api/admin/affiliates";
import { extractApiErrorMessage, extractI18nErrorMessage } from "@/utils/apiError";
import { useAppStore } from "@/stores";
@@ -6491,6 +6516,7 @@ const form = reactive<SettingsForm>({
icon_svg: string;
url: string;
visibility: "user" | "admin";
placement: CustomMenuPlacement;
sort_order: number;
}>,
custom_endpoints: [] as Array<{
@@ -7094,6 +7120,7 @@ function addMenuItem() {
icon_svg: "",
url: "",
visibility: "user",
placement: DEFAULT_CUSTOM_MENU_PLACEMENT,
sort_order: form.custom_menu_items.length,
});
}
@@ -7209,6 +7236,12 @@ async function loadSettings() {
(form as Record<string, unknown>)[key] = value;
}
}
form.custom_menu_items = normalizeCustomMenuItems(
settings.custom_menu_items,
).map((item) => ({
...item,
placement: item.placement || DEFAULT_CUSTOM_MENU_PLACEMENT,
}));
form.login_agreement_mode =
settings.login_agreement_mode === "checkbox" ? "checkbox" : "modal";
form.login_agreement_updated_at =
@@ -7575,7 +7608,10 @@ async function saveSettings() {
hide_ccs_import_button: form.hide_ccs_import_button,
table_default_page_size: form.table_default_page_size,
table_page_size_options: form.table_page_size_options,
custom_menu_items: form.custom_menu_items,
custom_menu_items: form.custom_menu_items.map((item) => ({
...item,
placement: item.placement || DEFAULT_CUSTOM_MENU_PLACEMENT,
})),
custom_endpoints: form.custom_endpoints,
frontend_url: form.frontend_url,
smtp_host: form.smtp_host,