feat:添加logo配置

main
黄伟杰 2 days ago
parent 5ace5f5a99
commit 4d74903cad

@ -0,0 +1,19 @@
import request from '@/config/axios'
export interface BrandingLogoConfig {
webLoginLogo?: string
webHeaderLogo?: string
appLoginLogo?: string
}
export const getBrandingLogoConfig = () => {
return request.get<BrandingLogoConfig>({ url: '/infra/branding/logo' })
}
export const updateBrandingLogoConfig = (data: BrandingLogoConfig) => {
return request.put({ url: '/infra/branding/logo', data })
}
export const uploadBrandingLogo = (data: FormData) => {
return request.upload({ url: '/infra/file/upload', data })
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 40 KiB

@ -0,0 +1,20 @@
import { reactive } from 'vue'
import * as BrandingLogoApi from '@/api/infra/branding/logo'
export const brandingLogoConfig = reactive<BrandingLogoApi.BrandingLogoConfig>({})
export const loadBrandingLogoConfig = async () => {
const config = await BrandingLogoApi.getBrandingLogoConfig()
const refreshVersion = Date.now()
const withRefreshVersion = (url?: string) => {
if (!url) return url
return `${url}${url.includes('?') ? '&' : '?'}_=${refreshVersion}`
}
Object.assign(brandingLogoConfig, {
...config,
webLoginLogo: withRefreshVersion(config?.webLoginLogo),
webHeaderLogo: withRefreshVersion(config?.webHeaderLogo),
appLoginLogo: withRefreshVersion(config?.appLoginLogo)
})
}

@ -2,6 +2,7 @@
import { computed, onMounted, ref, unref, watch } from 'vue'
import { useAppStore } from '@/store/modules/app'
import { useDesign } from '@/hooks/web/useDesign'
import { brandingLogoConfig } from '@/config/brandingLogo'
defineOptions({ name: 'Logo' })
@ -70,6 +71,11 @@ watch(
class="h-[calc(var(--logo-height)-10px)] w-[calc(var(--logo-height)-10px)]"
src="@/assets/imgs/logo.png"
/>
<img
v-if="brandingLogoConfig.webHeaderLogo"
:src="brandingLogoConfig.webHeaderLogo"
class="ml-6px h-[calc(var(--logo-height)-10px)] w-[calc(var(--logo-height)-10px)] object-contain"
/>
<div
v-if="show"
:class="[

@ -9,6 +9,9 @@ import { setCssVar, trim } from '@/utils'
import { colorIsDark, hexToRGB, lighten } from '@/utils/color'
import { useAppStore } from '@/store/modules/app'
import { ThemeSwitch } from '@/layout/components/ThemeSwitch'
import { brandingLogoConfig, loadBrandingLogoConfig } from '@/config/brandingLogo'
import * as BrandingLogoApi from '@/api/infra/branding/logo'
import type { UploadRequestOptions } from 'element-plus'
import ColorRadioPicker from './components/ColorRadioPicker.vue'
import InterfaceDisplay from './components/InterfaceDisplay.vue'
import LayoutRadioPicker from './components/LayoutRadioPicker.vue'
@ -23,6 +26,33 @@ const prefixCls = getPrefixCls('setting')
const layout = computed(() => appStore.getLayout)
const drawer = ref(false)
const brandingLogoItems = [
{ field: 'webLoginLogo', label: 'setting.webLoginLogo', path: 'branding/logo/web-login-logo.png' },
{ field: 'webHeaderLogo', label: 'setting.webHeaderLogo', path: 'branding/logo/web-header-logo.png' },
{ field: 'appLoginLogo', label: 'setting.appLoginLogo', path: 'branding/logo/app-login-logo.png' }
] as const
const uploadBrandingLogo = async (
field: (typeof brandingLogoItems)[number]['field'],
path: string,
options: UploadRequestOptions
) => {
const formData = new FormData()
formData.append('file', options.file)
formData.append('path', path)
try {
const response: any = await BrandingLogoApi.uploadBrandingLogo(formData)
const fileUrl = String(response?.data?.fileUrl || '').trim()
if (!fileUrl) throw new Error('Missing file URL')
await BrandingLogoApi.updateBrandingLogoConfig({ [field]: fileUrl })
await loadBrandingLogoConfig()
options.onSuccess(response)
ElMessage.success(t('setting.logoSaveSuccess'))
} catch (error) {
options.onError(error as any)
}
}
//
const systemTheme = ref(appStore.getTheme.elColorPrimary)
@ -214,6 +244,30 @@ const clear = () => {
</template>
<div class="text-center">
<ElDivider>{{ t('setting.brandingLogo') }}</ElDivider>
<div class="grid grid-cols-3 gap-10px">
<div v-for="item in brandingLogoItems" :key="item.field" class="text-center">
<div class="mb-6px text-12px">{{ t(item.label) }}</div>
<ElUpload
accept="image/*"
:http-request="(options) => uploadBrandingLogo(item.field, item.path, options)"
:show-file-list="false"
>
<img
v-if="brandingLogoConfig[item.field]"
:src="brandingLogoConfig[item.field]"
class="h-72px w-72px rounded-4px border border-solid border-[var(--el-border-color)] object-contain"
/>
<div
v-else
class="h-72px w-72px flex items-center justify-center rounded-4px border border-dashed border-[var(--el-border-color)]"
>
<Icon icon="ep:plus" />
</div>
</ElUpload>
</div>
</div>
<!-- 主题 -->
<ElDivider>{{ t('setting.theme') }}</ElDivider>
<ThemeSwitch />

@ -1204,6 +1204,11 @@ export default {
},
setting: {
projectSetting: 'Project setting',
brandingLogo: 'Logo settings',
webLoginLogo: 'Web login logo',
webHeaderLogo: 'Web menu logo',
appLoginLogo: 'App login logo',
logoSaveSuccess: 'Logo settings saved',
theme: 'Theme',
layout: 'Layout',
systemTheme: 'System theme',

@ -1230,6 +1230,11 @@ export default {
},
setting: {
projectSetting: '项目配置',
brandingLogo: 'Logo 配置',
webLoginLogo: 'Web 首页 Logo',
webHeaderLogo: 'Web 菜单 Logo',
appLoginLogo: 'App Logo',
logoSaveSuccess: 'Logo 配置已保存',
theme: '主题',
layout: '布局',
systemTheme: '系统主题',

@ -39,6 +39,7 @@ import './permission'
import '@/plugins/tongji' // 百度统计
import Logger from '@/utils/Logger'
import { loadBrandingLogoConfig } from '@/config/brandingLogo'
import VueDOMPurifyHTML from 'vue-dompurify-html' // 解决v-html 的安全隐患
@ -50,6 +51,10 @@ const setupAll = async () => {
setupStore(app)
loadBrandingLogoConfig().catch((error) => {
console.warn('Failed to load branding logos', error)
})
setupGlobCom(app)
setupElementPlus(app)

@ -5,15 +5,23 @@
>
<div class="relative mx-auto h-full flex">
<div
:class="`${prefixCls}__left flex-1 bg-gray-500 bg-opacity-20 relative p-30px lt-xl:hidden overflow-x-hidden overflow-y-auto`"
:class="`${prefixCls}__left relative flex flex-1 flex-col bg-gray-500 bg-opacity-20 p-30px lt-xl:hidden overflow-x-hidden overflow-y-auto`"
>
<!-- 左上角的 logo + 系统标题 -->
<div class="relative flex items-center text-white">
<img alt="" class="mr-10px h-48px w-48px" src="@/assets/imgs/logo.png" width="32"/>
<span class="text-20px font-bold">{{ underlineToHump(appStore.getTitle) }}</span>
<div class="mr-10px flex flex-col gap-8px">
<ElImage :src="leftLogo" class="h-80px w-auto" fit="contain" />
<ElImage
v-if="brandingLogoConfig.webLoginLogo"
:src="brandingLogoConfig.webLoginLogo"
class="h-80px w-auto"
fit="contain"
/>
</div>
<!-- <span class="text-20px font-bold">{{ underlineToHump(appStore.getTitle) }}</span> -->
</div>
<!-- 左边的背景图 + 欢迎语 -->
<div class="h-[calc(100%-60px)] flex items-center justify-center">
<div class="min-h-0 flex flex-1 items-center justify-center">
<TransitionGroup
appear
enter-active-class="animate__animated animate__bounceInLeft"
@ -36,7 +44,15 @@
style="color: var(--el-text-color-primary);"
>
<div class="flex items-center at-2xl:hidden at-xl:hidden">
<img alt="" class="mr-10px h-48px w-48px" src="@/assets/imgs/logo.png" />
<div class="mr-10px flex flex-col gap-8px">
<ElImage :src="compactLogo" class="h-56px w-auto" fit="contain" />
<ElImage
v-if="brandingLogoConfig.webLoginLogo"
:src="brandingLogoConfig.webLoginLogo"
class="h-56px w-auto"
fit="contain"
/>
</div>
<span class="text-20px font-bold" >{{ underlineToHump(appStore.getTitle) }}</span>
</div>
<div class="flex items-center justify-end space-x-10px h-48px">
@ -67,11 +83,14 @@
</template>
<script lang="ts" setup>
import { underlineToHump } from '@/utils'
import leftLogo from '@/assets/imgs/logo1.png'
import compactLogo from '@/assets/imgs/logo.png'
import { useDesign } from '@/hooks/web/useDesign'
import { useAppStore } from '@/store/modules/app'
import { ThemeSwitch } from '@/layout/components/ThemeSwitch'
import { LocaleDropdown } from '@/layout/components/LocaleDropdown'
import { brandingLogoConfig } from '@/config/brandingLogo'
import { LoginForm, MobileForm, QrCodeForm, RegisterForm, SSOLoginVue } from './components'
@ -87,7 +106,7 @@ const prefixCls = getPrefixCls('login')
$prefix-cls: #{$namespace}-login;
.#{$prefix-cls} {
overflow: auto;
overflow: hidden;
&__left {
&::before {

Loading…
Cancel
Save