mirror of
https://github.com/1Panel-dev/1Panel.git
synced 2024-11-24 02:59:16 +08:00
refactor: refactor theme and logo switching functionality (#5015)
This commit is contained in:
parent
f30b05a495
commit
63c330054e
@ -87,6 +87,7 @@
|
||||
"vite-plugin-eslint": "^1.6.0",
|
||||
"vite-plugin-html": "^3.2.0",
|
||||
"vite-plugin-vue-setup-extend": "^0.4.0",
|
||||
"vite-svg-loader": "^5.1.0",
|
||||
"vue-tsc": "^0.29.8"
|
||||
},
|
||||
"overrides": {
|
||||
|
Binary file not shown.
Before Width: | Height: | Size: 8.9 KiB |
Binary file not shown.
Before Width: | Height: | Size: 9.1 KiB |
1
frontend/src/assets/images/1panel-logo.svg
Normal file
1
frontend/src/assets/images/1panel-logo.svg
Normal file
@ -0,0 +1 @@
|
||||
<svg fill="currentColor" height="33" viewBox="0 0 120 33" width="120" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"><clipPath id="a"><path d="m0 0h120v32.7791h-120z"/></clipPath><g clip-path="url(#a)"><path d="m12.9834 26.1691-7.8556-4.5363v-10.4865l-3.04418-1.75723v14.00093l7.85558 4.5364z"/><path d="m23.2918 21.6342-9.0827 5.2418-3.0442 1.7572 3.0442 1.7573 12.1268-6.9991z"/><path d="m14.209 5.90309 3.0442-1.75724-3.0442-1.75724-12.12538 7.00048 3.04418 1.75721z"/><path d="m18.4803 4.85416-3.0441 1.75725 7.8555 4.53489v10.4865l3.0428 1.7572v-14.00092z"/><path d="m14.2091.577186 13.6931 7.905464v15.81945l-13.6931 7.9055-13.693125-7.9126v-15.81947l13.693125-7.905459m0-.427554-14.066522 8.123513v16.24707l14.066522 8.1235 14.0679-8.1235v-16.24707z"/><path d="m14.6494 8.82043.9292.55438v14.24039l-.9292.5359z"/><path d="m14.6494 8.82043-4.5834 2.64087v1.7872h1.6133v9.1909l2.9687 1.7131z"/><path d="m65.078 17.447c-.971 2.247-3.0613 3.371-6.2708 3.372-.409.0271-3.1354-.0841-3.7525-.1425v6.5829h-5.2047v-20.86321s5.1306-.32209 8.9601-.14252c3.2437.1582 4.8983.91924 5.9145 2.76057 1.0289 1.90406 1.3225 6.18386.3534 8.43276zm-4.7587-6.6983c-.3306-.3492-.8551-.5573-1.5677-.6385-.7895-.095-2.0223-.0679-3.6983.0812v6.6741c1.281.1611 2.5759.1788 3.8608.0528.5886-.0556 1.0105-.2309 1.2641-.5316.7297-.5915 1.2699-4.4494.1411-5.6395z"/><path d="m44.8703 27.1069h-4.8071v-16.2314h-3.4062l-.2851-4.27549h8.4998z"/><path d="m116.041 21.4447c0 .9805.321 1.5828.965 1.8071.329.0883 1.341.1425 1.948.1739.107.0085.214.0128.322.0128h.634v3.7881c-.276.0181-.642.0271-1.097.0271-3.187 0-5.923-.285-6.905-2.5297-.26-.6599-.305-2.1378-.358-2.7306-.021-.237-.03-.4748-.027-.7126v-15.35776l4.524-.55582z"/><path d="m109.783 18.5173v2.6252h-8.285v.4475c0 1.0015.307 1.6105.921 1.8271.53.1419 1.076.21 1.625.2024h.365c1.624 0 2.609-.0442 4.314-.3962h.027l.681 3.5786c0 .0437-.776.1924-2.329.4461-1.115.1153-2.235.1696-3.356.1625-3.25 0-5.3478-1.0965-6.2951-3.2893-.3518-.8638-.5314-1.7881-.5288-2.7207v-3.5473c0-2.5995 1.0927-4.3278 3.2779-5.1848 1.002-.3209 2.048-.4806 3.1-.4731 3.059 0 5.044.9976 5.956 2.9928.351.7848.527 1.8946.527 3.3292zm-4.576-.6912c0-1.0289-.221-1.638-.663-1.827-.362-.234-.787-.3523-1.219-.3392-1.218 0-1.827.5734-1.827 1.7202v.6954h3.706z"/><path d="m79.7886 18.1112v9.1211h-3.9905l-.0185-.9235c-1.3948.7259-2.7577 1.0884-4.0888 1.0874h-.3934c-1.3682 0-2.4551-.5254-3.2608-1.5763-.533-.8196-.8116-1.7786-.801-2.7562v-.1098c0-1.7929.6295-2.9833 1.8884-3.5715.5159-.3449 1.8242-.5168 3.925-.5159h2.1463v-.5031c0-1.0318-.1497-1.6247-.449-1.7786-.323-.2413-.8978-.362-1.7244-.362h-4.2756l.0727-3.6556 4.9254-.1339c2.9245 0 4.7354.7244 5.4328 2.1734.4076.8427.6114 2.0109.6114 3.5045zm-7.6803 3.7909c-.1354.1611-.285.687-.285 1.0575 0 .8152.4166 1.2228 1.2499 1.2228.4978 0 1.2043-.2038 2.1192-.6114v-1.9952s-2.5881-.2637-3.0841.3263z"/><path d="m94.867 18.5658v8.6936h-4.6005v-9.5173c0-.925-.3093-1.3625-1.3055-1.6732s-2.5154.067-2.8418.2665v10.924h-4.5634v-14.8846h4.2214s-.0556.8551-.02.9791c.0606-.0233.1196-.0505.1767-.0812l.2722-.1425c.2743-.1338.556-.2518.8437-.3535.3937-.1525.7981-.2754 1.21-.3677.5055-.1086 1.0196-.172 1.5364-.1895 1.0451-.0456 1.8788.0446 2.5012.2708.8171.3097 1.4878.8993 2.0123 1.7686.3725.6869.5582 2.1226.5573 4.3069z"/></g></svg>
|
After Width: | Height: | Size: 3.2 KiB |
Binary file not shown.
Before Width: | Height: | Size: 23 KiB |
Binary file not shown.
Before Width: | Height: | Size: 26 KiB |
8
frontend/src/assets/images/1panel-menu-logo.svg
Normal file
8
frontend/src/assets/images/1panel-menu-logo.svg
Normal file
@ -0,0 +1,8 @@
|
||||
<svg width="33" height="33" viewBox="0 0 33 33" fill="currentColor" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M19.4207 4.84191L7.78421 11.5601V21.6237L15.323 25.9772L12.4016 27.6635L4.86277 23.3101V9.87373L16.4993 3.15553L19.4207 4.84191Z"/>
|
||||
<path d="M25.2158 21.6251L13.5779 28.3419L16.4993 30.0283L28.1372 23.3115L25.2158 21.6251Z"/>
|
||||
<path d="M20.5983 5.52166L17.6769 7.20806L25.2157 11.5601V21.6237L28.1358 23.3101V9.87372L20.5983 5.52166Z"/>
|
||||
<path d="M16.9219 9.32801L17.8136 9.86003V23.5262L16.9219 24.0405V9.32801Z"/>
|
||||
<path d="M12.5233 11.8624L16.9219 9.32801L16.9205 24.0418L14.0716 22.3979V13.5775H12.5233V11.8624Z"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M3 8.79596L16.4993 1L30 8.79596V24.3879L16.4993 32.1838L3 24.3879V8.79596ZM29.6403 9.00384L16.4993 1.41714V1.41031L3.35834 8.99701V24.1786L16.4993 31.7722L29.6403 24.1854V9.00384Z" />
|
||||
</svg>
|
After Width: | Height: | Size: 869 B |
@ -1,8 +1,8 @@
|
||||
<template>
|
||||
<div>
|
||||
<el-dialog v-model="open" :close-on-click-modal="false" @close="handleClose">
|
||||
<el-dialog class="level-up-pro" v-model="open" :close-on-click-modal="false" @close="handleClose">
|
||||
<div style="text-align: center" v-loading="loading">
|
||||
<span class="text-3xl font-medium">{{ $t('license.levelUpPro') }}</span>
|
||||
<span class="text-3xl font-medium title">{{ $t('license.levelUpPro') }}</span>
|
||||
<el-row type="flex" justify="center" class="mt-6">
|
||||
<el-col :span="22">
|
||||
<el-upload
|
||||
|
@ -64,11 +64,7 @@
|
||||
{{ upgradeInfo.testVersion }}
|
||||
</el-radio>
|
||||
</el-radio-group>
|
||||
<MdEditor
|
||||
v-model="upgradeInfo.releaseNote"
|
||||
previewOnly
|
||||
:theme="globalStore.$state.themeConfig.theme === 'dark' ? 'dark' : 'light'"
|
||||
/>
|
||||
<MdEditor v-model="upgradeInfo.releaseNote" previewOnly :theme="isDarkTheme ? 'dark' : 'light'" />
|
||||
</div>
|
||||
<template #footer>
|
||||
<span class="dialog-footer">
|
||||
@ -88,7 +84,10 @@ import { MsgSuccess } from '@/utils/message';
|
||||
import { onMounted, ref } from 'vue';
|
||||
import { GlobalStore } from '@/store';
|
||||
import { ElMessageBox } from 'element-plus';
|
||||
import { storeToRefs } from 'pinia';
|
||||
|
||||
const globalStore = GlobalStore();
|
||||
const { isDarkTheme } = storeToRefs(globalStore);
|
||||
|
||||
const version = ref<string>('');
|
||||
const isProductPro = ref();
|
||||
|
@ -6,7 +6,10 @@ import { onMounted, nextTick, watch, onBeforeUnmount } from 'vue';
|
||||
import * as echarts from 'echarts';
|
||||
import { GlobalStore } from '@/store';
|
||||
import { computeSizeFromKBs, computeSizeFromKB, computeSizeFromMB } from '@/utils/util';
|
||||
import { storeToRefs } from 'pinia';
|
||||
const globalStore = GlobalStore();
|
||||
const { isDarkTheme } = storeToRefs(globalStore);
|
||||
|
||||
const props = defineProps({
|
||||
id: {
|
||||
type: String,
|
||||
@ -89,8 +92,6 @@ function initChart() {
|
||||
itemChart = echarts.init(document.getElementById(props.id) as HTMLElement);
|
||||
}
|
||||
|
||||
const theme = globalStore.$state.themeConfig.theme || 'light';
|
||||
|
||||
const series = [];
|
||||
if (props.option?.yData?.length) {
|
||||
props.option?.yData.forEach((item: any, index: number) => {
|
||||
@ -112,7 +113,7 @@ function initChart() {
|
||||
show: true,
|
||||
lineStyle: {
|
||||
type: 'dashed',
|
||||
opacity: theme === 'dark' ? 0.1 : 1,
|
||||
opacity: isDarkTheme.value ? 0.1 : 1,
|
||||
},
|
||||
},
|
||||
...item,
|
||||
@ -184,7 +185,7 @@ function initChart() {
|
||||
//分隔辅助线
|
||||
lineStyle: {
|
||||
type: 'dashed', //线的类型 虚线0
|
||||
opacity: theme === 'dark' ? 0.1 : 1, //透明度
|
||||
opacity: isDarkTheme.value ? 0.1 : 1, //透明度
|
||||
},
|
||||
},
|
||||
},
|
||||
|
@ -5,7 +5,10 @@
|
||||
import { onMounted, nextTick, watch, onBeforeUnmount } from 'vue';
|
||||
import * as echarts from 'echarts';
|
||||
import { GlobalStore } from '@/store';
|
||||
import { storeToRefs } from 'pinia';
|
||||
const globalStore = GlobalStore();
|
||||
const { isDarkGoldTheme, isDarkTheme } = storeToRefs(globalStore);
|
||||
|
||||
const props = defineProps({
|
||||
id: {
|
||||
type: String,
|
||||
@ -30,7 +33,6 @@ function initChart() {
|
||||
if (myChart === null || myChart === undefined) {
|
||||
myChart = echarts.init(document.getElementById(props.id) as HTMLElement);
|
||||
}
|
||||
const theme = globalStore.$state.themeConfig.theme || 'light';
|
||||
let percentText = String(props.option.data).split('.');
|
||||
const option = {
|
||||
title: [
|
||||
@ -47,7 +49,7 @@ function initChart() {
|
||||
},
|
||||
},
|
||||
|
||||
color: theme === 'dark' ? '#ffffff' : '#0f0f0f',
|
||||
color: isDarkTheme.value ? '#ffffff' : '#0f0f0f',
|
||||
lineHeight: 25,
|
||||
// fontSize: 20,
|
||||
fontWeight: 500,
|
||||
@ -56,7 +58,7 @@ function initChart() {
|
||||
top: '32%',
|
||||
subtext: props.option.title,
|
||||
subtextStyle: {
|
||||
color: theme === 'dark' ? '#BBBFC4' : '#646A73',
|
||||
color: isDarkTheme.value ? '#BBBFC4' : '#646A73',
|
||||
fontSize: 13,
|
||||
},
|
||||
textAlign: 'center',
|
||||
@ -91,17 +93,17 @@ function initChart() {
|
||||
showBackground: true,
|
||||
coordinateSystem: 'polar',
|
||||
backgroundStyle: {
|
||||
color: theme === 'dark' ? 'rgba(255, 255, 255, 0.05)' : 'rgba(0, 94, 235, 0.05)',
|
||||
color: isDarkTheme.value ? 'rgba(255, 255, 255, 0.05)' : 'rgba(0, 94, 235, 0.05)',
|
||||
},
|
||||
color: [
|
||||
new echarts.graphic.LinearGradient(0, 1, 0, 0, [
|
||||
{
|
||||
offset: 0,
|
||||
color: 'rgba(81, 192, 255, .1)',
|
||||
color: isDarkGoldTheme.value ? '#836c4c' : 'rgba(81, 192, 255, .1)',
|
||||
},
|
||||
{
|
||||
offset: 1,
|
||||
color: '#4261F6',
|
||||
color: isDarkGoldTheme.value ? '#eaba63' : '#4261F6',
|
||||
},
|
||||
]),
|
||||
],
|
||||
@ -117,12 +119,12 @@ function initChart() {
|
||||
label: {
|
||||
show: false,
|
||||
},
|
||||
color: theme === 'dark' ? '#16191D' : '#fff',
|
||||
color: isDarkTheme.value ? '#16191D' : '#fff',
|
||||
data: [
|
||||
{
|
||||
value: 0,
|
||||
itemStyle: {
|
||||
shadowColor: theme === 'dark' ? '#16191D' : 'rgba(0, 94, 235, 0.1)',
|
||||
shadowColor: isDarkTheme.value ? '#16191D' : 'rgba(0, 94, 235, 0.1)',
|
||||
shadowBlur: 5,
|
||||
},
|
||||
},
|
||||
|
@ -1,14 +1,16 @@
|
||||
import { GlobalStore } from '@/store';
|
||||
import { searchXSetting } from '@/xpack/api/modules/setting';
|
||||
import { getXpackSetting } from '@/utils/xpack';
|
||||
|
||||
export const useLogo = async () => {
|
||||
const globalStore = GlobalStore();
|
||||
const res = await searchXSetting();
|
||||
const res = await getXpackSetting();
|
||||
if (res) {
|
||||
localStorage.setItem('1p-favicon', res.data.logo);
|
||||
globalStore.themeConfig.title = res.data.title;
|
||||
globalStore.themeConfig.logo = res.data.logo;
|
||||
globalStore.themeConfig.logoWithText = res.data.logoWithText;
|
||||
globalStore.themeConfig.favicon = res.data.favicon;
|
||||
}
|
||||
|
||||
const link = (document.querySelector("link[rel*='icon']") || document.createElement('link')) as HTMLLinkElement;
|
||||
link.type = 'image/x-icon';
|
||||
|
@ -1,48 +1,62 @@
|
||||
import { computed, onBeforeMount } from 'vue';
|
||||
import { getLightColor, getDarkColor } from '@/utils/theme/tool';
|
||||
import { onBeforeMount, watch } from 'vue';
|
||||
import { GlobalStore } from '@/store';
|
||||
import { MsgSuccess } from '@/utils/message';
|
||||
import { storeToRefs } from 'pinia';
|
||||
|
||||
export const useTheme = () => {
|
||||
const globalStore = GlobalStore();
|
||||
const themeConfig = computed(() => globalStore.themeConfig);
|
||||
|
||||
const switchDark = () => {
|
||||
if (themeConfig.value.theme === 'auto') {
|
||||
const { themeConfig } = storeToRefs(GlobalStore());
|
||||
const prefersDark = window.matchMedia('(prefers-color-scheme: dark)');
|
||||
|
||||
/**
|
||||
* This method is only executed when loading or manually switching for the first time.
|
||||
*/
|
||||
const switchTheme = () => {
|
||||
if (themeConfig.value.theme === 'auto') {
|
||||
themeConfig.value.theme = prefersDark.matches ? 'dark' : 'light';
|
||||
if (prefersDark.addEventListener) {
|
||||
prefersDark.addEventListener('change', switchAccordingUserProxyTheme);
|
||||
} else if (prefersDark.addListener) {
|
||||
prefersDark.addListener(switchAccordingUserProxyTheme);
|
||||
}
|
||||
const body = document.documentElement as HTMLElement;
|
||||
if (themeConfig.value.theme === 'dark') body.setAttribute('class', 'dark');
|
||||
else body.setAttribute('class', '');
|
||||
} else {
|
||||
prefersDark.removeEventListener('change', switchAccordingUserProxyTheme);
|
||||
prefersDark.removeListener(switchAccordingUserProxyTheme);
|
||||
}
|
||||
updateTheme(themeConfig.value.theme);
|
||||
};
|
||||
|
||||
const changePrimary = (val: string) => {
|
||||
if (!val) {
|
||||
val = '#409EFF';
|
||||
MsgSuccess('主题颜色已重置为 #409EFF');
|
||||
}
|
||||
globalStore.setThemeConfig({ ...themeConfig.value, primary: val });
|
||||
document.documentElement.style.setProperty(
|
||||
'--el-color-primary-dark-2',
|
||||
`${getDarkColor(themeConfig.value.primary, 0.1)}`,
|
||||
);
|
||||
document.documentElement.style.setProperty('--el-color-primary', themeConfig.value.primary);
|
||||
for (let i = 1; i <= 9; i++) {
|
||||
document.documentElement.style.setProperty(
|
||||
`--el-color-primary-light-${i}`,
|
||||
`${getLightColor(themeConfig.value.primary, i / 10)}`,
|
||||
);
|
||||
}
|
||||
const switchAccordingUserProxyTheme = (event: MediaQueryListEvent) => {
|
||||
const preferTheme = event.matches ? 'dark' : 'light';
|
||||
|
||||
themeConfig.value.theme = preferTheme;
|
||||
updateTheme(preferTheme);
|
||||
};
|
||||
|
||||
const updateTheme = (theme: string) => {
|
||||
const body = document.documentElement as HTMLElement;
|
||||
body.setAttribute('class', theme);
|
||||
};
|
||||
|
||||
onBeforeMount(() => {
|
||||
switchDark();
|
||||
changePrimary(themeConfig.value.primary);
|
||||
updateTheme(themeConfig.value.theme);
|
||||
});
|
||||
|
||||
/**
|
||||
* Called internally by the system for automatically switching themes
|
||||
*/
|
||||
const autoSwitchTheme = () => {
|
||||
let preferTheme = themeConfig.value.theme;
|
||||
if (themeConfig.value.theme === 'auto') {
|
||||
preferTheme = prefersDark.matches ? 'dark' : 'light';
|
||||
}
|
||||
updateTheme(preferTheme);
|
||||
};
|
||||
|
||||
watch(themeConfig, () => {
|
||||
autoSwitchTheme();
|
||||
});
|
||||
|
||||
return {
|
||||
switchDark,
|
||||
changePrimary,
|
||||
autoSwitchTheme,
|
||||
switchTheme,
|
||||
};
|
||||
};
|
||||
|
@ -1,35 +1,34 @@
|
||||
<template>
|
||||
<div class="logo">
|
||||
<img :src="getLogoUrl(isCollapse)" style="cursor: pointer" alt="logo" @click="goHome" />
|
||||
<div class="logo" style="cursor: pointer" @click="goHome">
|
||||
<template v-if="isCollapse">
|
||||
<img v-if="globalStore.themeConfig.logo" :src="'/api/v1/images/logo'" style="cursor: pointer" alt="logo" />
|
||||
<MenuLogo v-else />
|
||||
</template>
|
||||
<template v-else>
|
||||
<img
|
||||
v-if="globalStore.themeConfig.logoWithText"
|
||||
:src="'/api/v1/images/logoWithText'"
|
||||
style="cursor: pointer"
|
||||
alt="logo"
|
||||
/>
|
||||
<PrimaryLogo v-else />
|
||||
</template>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import router from '@/routers';
|
||||
import { GlobalStore } from '@/store';
|
||||
import PrimaryLogo from '@/assets/images/1panel-logo.svg?component';
|
||||
import MenuLogo from '@/assets/images/1panel-menu-logo.svg?component';
|
||||
|
||||
defineProps<{ isCollapse: boolean }>();
|
||||
|
||||
const globalStore = GlobalStore();
|
||||
|
||||
const goHome = () => {
|
||||
router.push({ name: 'home' });
|
||||
};
|
||||
|
||||
const getLogoUrl = (isCollapse: boolean) => {
|
||||
if (isCollapse) {
|
||||
if (globalStore.themeConfig.logo) {
|
||||
return '/api/v1/images/logo';
|
||||
} else {
|
||||
return new URL(`../../../../assets/images/1panel-logo-light.png`, import.meta.url).href;
|
||||
}
|
||||
} else {
|
||||
if (globalStore.themeConfig.logoWithText) {
|
||||
return '/api/v1/images/logoWithText';
|
||||
} else {
|
||||
return new URL(`../../../../assets/images/1panel-menu-light.png`, import.meta.url).href;
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
|
@ -24,7 +24,7 @@ import { useI18n } from 'vue-i18n';
|
||||
import { useTheme } from '@/hooks/use-theme';
|
||||
import { getLicenseStatus, getSettingInfo, getSystemAvailable } from '@/api/modules/setting';
|
||||
import { useRoute, useRouter } from 'vue-router';
|
||||
import { initFavicon, resetXSetting } from '@/utils/xpack';
|
||||
import { getXpackSetting, initFavicon, resetXSetting } from '@/utils/xpack';
|
||||
useResize();
|
||||
|
||||
const router = useRouter();
|
||||
@ -37,7 +37,7 @@ const i18n = useI18n();
|
||||
const loading = ref(false);
|
||||
const loadingText = ref();
|
||||
const themeConfig = computed(() => globalStore.themeConfig);
|
||||
const { switchDark } = useTheme();
|
||||
const { switchTheme } = useTheme();
|
||||
|
||||
let timer: NodeJS.Timer | null = null;
|
||||
|
||||
@ -79,26 +79,20 @@ const loadDataFromDB = async () => {
|
||||
globalStore.entrance = res.data.securityEntrance;
|
||||
globalStore.setOpenMenuTabs(res.data.menuTabs === 'enable');
|
||||
globalStore.updateLanguage(res.data.language);
|
||||
globalStore.setThemeConfig({ ...themeConfig.value, theme: res.data.theme });
|
||||
globalStore.setThemeConfig({ ...themeConfig.value, panelName: res.data.panelName });
|
||||
switchDark();
|
||||
globalStore.setThemeConfig({ ...themeConfig.value, theme: res.data.theme, panelName: res.data.panelName });
|
||||
};
|
||||
|
||||
const loadDataFromXDB = async () => {
|
||||
const xpackModules = import.meta.globEager('../xpack/api/modules/*.ts');
|
||||
if (xpackModules['../xpack/api/modules/setting.ts']) {
|
||||
const searchXSetting = xpackModules['../xpack/api/modules/setting.ts'].searchXSetting;
|
||||
if (searchXSetting) {
|
||||
const res = await searchXSetting();
|
||||
globalStore.themeConfig.title = res.data.title;
|
||||
globalStore.themeConfig.logo = res.data.logo;
|
||||
globalStore.themeConfig.logoWithText = res.data.logoWithText;
|
||||
globalStore.themeConfig.favicon = res.data.favicon;
|
||||
} else {
|
||||
resetXSetting();
|
||||
}
|
||||
} else {
|
||||
resetXSetting();
|
||||
const res = await getXpackSetting();
|
||||
if (res) {
|
||||
globalStore.setThemeConfig({
|
||||
...themeConfig.value,
|
||||
title: res.data.title,
|
||||
logo: res.data.logo,
|
||||
logoWithText: res.data.logoWithText,
|
||||
favicon: res.data.favicon,
|
||||
theme: res.data.theme || 'dark-gold',
|
||||
});
|
||||
}
|
||||
initFavicon();
|
||||
};
|
||||
@ -116,22 +110,10 @@ const loadProductProFromDB = async () => {
|
||||
loadDataFromXDB();
|
||||
globalStore.productProExpires = Number(res.data.productPro);
|
||||
} else {
|
||||
globalStore.themeConfig.title = '';
|
||||
globalStore.themeConfig.logo = '';
|
||||
globalStore.themeConfig.logoWithText = '';
|
||||
globalStore.themeConfig.favicon = '';
|
||||
resetXSetting();
|
||||
}
|
||||
};
|
||||
|
||||
const updateDarkMode = async (event: MediaQueryListEvent) => {
|
||||
const res = await getSettingInfo();
|
||||
if (res.data.theme !== 'auto') {
|
||||
return;
|
||||
}
|
||||
globalStore.setThemeConfig({ ...themeConfig.value, theme: event.matches ? 'dark' : 'light' });
|
||||
switchDark();
|
||||
};
|
||||
|
||||
const loadStatus = async () => {
|
||||
loading.value = globalStore.isLoading;
|
||||
loadingText.value = globalStore.loadingText;
|
||||
@ -166,17 +148,7 @@ onMounted(() => {
|
||||
initFavicon();
|
||||
loadDataFromDB();
|
||||
loadProductProFromDB();
|
||||
|
||||
const mqList = window.matchMedia('(prefers-color-scheme: dark)');
|
||||
if (mqList.addEventListener) {
|
||||
mqList.addEventListener('change', (e) => {
|
||||
updateDarkMode(e);
|
||||
});
|
||||
} else if (mqList.addListener) {
|
||||
mqList.addListener((e) => {
|
||||
updateDarkMode(e);
|
||||
});
|
||||
}
|
||||
switchTheme();
|
||||
});
|
||||
</script>
|
||||
|
||||
|
@ -6,6 +6,10 @@ import '@/styles/common.scss';
|
||||
import '@/assets/iconfont/iconfont.css';
|
||||
import '@/assets/iconfont/iconfont.js';
|
||||
import '@/styles/style.css';
|
||||
const styleModule = import.meta.glob('xpack/styles/index.scss');
|
||||
for (const path in styleModule) {
|
||||
styleModule[path]?.();
|
||||
}
|
||||
|
||||
import directives from '@/directives/index';
|
||||
import router from '@/routers/index';
|
||||
|
@ -39,7 +39,10 @@ const GlobalStore = defineStore({
|
||||
isProductPro: false,
|
||||
productProExpires: 0,
|
||||
}),
|
||||
getters: {},
|
||||
getters: {
|
||||
isDarkTheme: (state) => state.themeConfig.theme === 'dark' || state.themeConfig.theme === 'dark-gold',
|
||||
isDarkGoldTheme: (state) => state.themeConfig.theme === 'dark-gold' && state.isProductPro,
|
||||
},
|
||||
actions: {
|
||||
setOpenMenuTabs(openMenuTabs: boolean) {
|
||||
this.openMenuTabs = openMenuTabs;
|
||||
|
@ -335,4 +335,29 @@ html.dark {
|
||||
.file-item:hover {
|
||||
background-color: #4f4f4f;
|
||||
}
|
||||
|
||||
.level-up-pro {
|
||||
--dark-gold-base-color: #eaba63;
|
||||
--dark-gold-text-color: #1f2329;
|
||||
--el-color-primary: var(--dark-gold-base-color);
|
||||
|
||||
.title {
|
||||
color: var(--dark-gold-base-color);
|
||||
}
|
||||
.el-button--primary {
|
||||
&.is-plain {
|
||||
background: var(--dark-gold-base-color);
|
||||
--el-button-text-color: var(--dark-gold-text-color);
|
||||
|
||||
&.is-disabled {
|
||||
background: #1d2023;
|
||||
border-color: #303438;
|
||||
}
|
||||
}
|
||||
|
||||
&.is-text {
|
||||
--el-button-text-color: var(--dark-gold-base-color);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,3 +1,17 @@
|
||||
:root {
|
||||
--el-color-primary: #005eeb;
|
||||
--el-color-primary-dark-2: #0054d3;
|
||||
--el-color-primary-light-1: #196eed;
|
||||
--el-color-primary-light-2: #337eef;
|
||||
--el-color-primary-light-3: #4c8ef1;
|
||||
--el-color-primary-light-4: #669ef3;
|
||||
--el-color-primary-light-5: #7faef5;
|
||||
--el-color-primary-light-6: #99bef7;
|
||||
--el-color-primary-light-7: #b2cef9;
|
||||
--el-color-primary-light-8: #ccdefb;
|
||||
--el-color-primary-light-9: #e5eefd;
|
||||
}
|
||||
|
||||
html {
|
||||
--el-box-shadow-light: 0px 0px 4px rgba(0, 94, 235, 0.1) !important;
|
||||
|
||||
@ -206,3 +220,7 @@ html {
|
||||
vertical-align: text-top !important;
|
||||
}
|
||||
}
|
||||
|
||||
.logo {
|
||||
color: var(--el-color-primary);
|
||||
}
|
||||
|
@ -1 +1 @@
|
||||
$primary-color: #005eeb;
|
||||
$primary-color: var(--el-color-primary);
|
||||
|
@ -16,3 +16,20 @@ export function initFavicon() {
|
||||
link.href = favicon ? '/api/v1/images/favicon' : '/public/favicon.png';
|
||||
document.getElementsByTagName('head')[0].appendChild(link);
|
||||
}
|
||||
|
||||
export async function getXpackSetting() {
|
||||
const searchXSettingGlob = import.meta.glob('xpack/api/modules/setting.ts');
|
||||
const module = await searchXSettingGlob?.['../xpack/api/modules/setting.ts']?.();
|
||||
const res = module?.searchXSetting();
|
||||
if (!res) {
|
||||
resetXSetting();
|
||||
return;
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
export async function updateXpackSetting(fromData: FormData) {
|
||||
const searchXSettingGlob = import.meta.glob('xpack/api/modules/setting.ts');
|
||||
const module = await searchXSettingGlob?.['../xpack/api/modules/setting.ts']?.();
|
||||
return module?.updateXSetting(fromData);
|
||||
}
|
||||
|
@ -38,6 +38,7 @@
|
||||
v-if="appDetail.enable && operate === 'install'"
|
||||
@click="openInstall"
|
||||
type="primary"
|
||||
class="brief-button"
|
||||
>
|
||||
{{ $t('app.install') }}
|
||||
</el-button>
|
||||
@ -71,11 +72,7 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<MdEditor
|
||||
previewOnly
|
||||
v-model="app.readMe"
|
||||
:theme="globalStore.$state.themeConfig.theme === 'dark' ? 'dark' : 'light'"
|
||||
/>
|
||||
<MdEditor previewOnly v-model="app.readMe" :theme="isDarkTheme ? 'dark' : 'light'" />
|
||||
</el-drawer>
|
||||
<Install ref="installRef"></Install>
|
||||
</template>
|
||||
@ -88,7 +85,9 @@ import Install from './install/index.vue';
|
||||
import router from '@/routers';
|
||||
import { GlobalStore } from '@/store';
|
||||
import { getLanguage } from '@/utils/util';
|
||||
import { storeToRefs } from 'pinia';
|
||||
const globalStore = GlobalStore();
|
||||
const { isDarkTheme } = storeToRefs(globalStore);
|
||||
|
||||
const language = getLanguage();
|
||||
|
||||
|
@ -50,7 +50,7 @@ import ErrDomain from '@/components/error-message/err_domain.vue';
|
||||
import ErrFound from '@/components/error-message/404.vue';
|
||||
import { ref, onMounted } from 'vue';
|
||||
import { GlobalStore } from '@/store';
|
||||
import { initFavicon, resetXSetting } from '@/utils/xpack';
|
||||
import { getXpackSetting, initFavicon } from '@/utils/xpack';
|
||||
const globalStore = GlobalStore();
|
||||
|
||||
const screenWidth = ref(null);
|
||||
@ -116,28 +116,12 @@ const getStatus = async () => {
|
||||
};
|
||||
|
||||
const loadDataFromXDB = async () => {
|
||||
const xpackModules = import.meta.globEager('../../../xpack/api/modules/*.ts');
|
||||
if (xpackModules['../../../xpack/api/modules/setting.ts']) {
|
||||
const searchXSetting = xpackModules['../../../xpack/api/modules/setting.ts'].searchXSetting;
|
||||
if (searchXSetting) {
|
||||
await searchXSetting()
|
||||
.then((res) => {
|
||||
const res = await getXpackSetting();
|
||||
if (res) {
|
||||
globalStore.themeConfig.title = res.data.title;
|
||||
globalStore.themeConfig.logo = res.data.logo;
|
||||
globalStore.themeConfig.logoWithText = res.data.logoWithText;
|
||||
globalStore.themeConfig.favicon = res.data.favicon;
|
||||
})
|
||||
.catch(() => {
|
||||
loading.value = false;
|
||||
resetXSetting();
|
||||
});
|
||||
} else {
|
||||
loading.value = false;
|
||||
resetXSetting();
|
||||
}
|
||||
} else {
|
||||
loading.value = false;
|
||||
resetXSetting();
|
||||
}
|
||||
loading.value = false;
|
||||
initFavicon();
|
||||
|
@ -24,7 +24,7 @@ import LoginForm from './components/login-form.vue';
|
||||
import { ref, onMounted } from 'vue';
|
||||
import router from '@/routers';
|
||||
import { GlobalStore } from '@/store';
|
||||
import { initFavicon, resetXSetting } from '@/utils/xpack';
|
||||
import { getXpackSetting, initFavicon } from '@/utils/xpack';
|
||||
|
||||
const gStore = GlobalStore();
|
||||
const loading = ref();
|
||||
@ -48,28 +48,12 @@ const getStatus = async () => {
|
||||
};
|
||||
|
||||
const loadDataFromXDB = async () => {
|
||||
const xpackModules = import.meta.globEager('../../xpack/api/modules/*.ts');
|
||||
if (xpackModules['../../xpack/api/modules/setting.ts']) {
|
||||
const searchXSetting = xpackModules['../../xpack/api/modules/setting.ts'].searchXSetting;
|
||||
if (searchXSetting) {
|
||||
await searchXSetting()
|
||||
.then((resItem) => {
|
||||
gStore.themeConfig.title = resItem.data.title;
|
||||
gStore.themeConfig.logo = resItem.data.logo;
|
||||
gStore.themeConfig.logoWithText = resItem.data.logoWithText;
|
||||
gStore.themeConfig.favicon = resItem.data.favicon;
|
||||
})
|
||||
.catch(() => {
|
||||
loading.value = false;
|
||||
resetXSetting();
|
||||
});
|
||||
} else {
|
||||
loading.value = false;
|
||||
resetXSetting();
|
||||
}
|
||||
} else {
|
||||
loading.value = false;
|
||||
resetXSetting();
|
||||
const res = await getXpackSetting();
|
||||
if (res) {
|
||||
gStore.themeConfig.title = res.data.title;
|
||||
gStore.themeConfig.logo = res.data.logo;
|
||||
gStore.themeConfig.logoWithText = res.data.logoWithText;
|
||||
gStore.themeConfig.favicon = res.data.favicon;
|
||||
}
|
||||
loading.value = false;
|
||||
initFavicon();
|
||||
|
@ -4,7 +4,8 @@
|
||||
<template #main>
|
||||
<div style="text-align: center; margin-top: 20px">
|
||||
<div style="justify-self: center">
|
||||
<img style="width: 80px" :src="getLogoUrl()" />
|
||||
<img v-if="globalStore.themeConfig.logo" style="width: 80px" :src="'/api/v1/images/logo'" />
|
||||
<PrimaryLogo v-else />
|
||||
</div>
|
||||
<h3>{{ globalStore.themeConfig.title || $t('setting.description') }}</h3>
|
||||
<h3>
|
||||
@ -39,6 +40,7 @@ import { getSettingInfo, getSystemAvailable } from '@/api/modules/setting';
|
||||
import { onMounted, ref } from 'vue';
|
||||
import SystemUpgrade from '@/components/system-upgrade/index.vue';
|
||||
import { GlobalStore } from '@/store';
|
||||
import PrimaryLogo from '@/assets/images/1panel-logo.svg?component';
|
||||
const globalStore = GlobalStore();
|
||||
|
||||
const version = ref();
|
||||
@ -61,14 +63,6 @@ const toGithubStar = () => {
|
||||
window.open('https://github.com/1Panel-dev/1Panel', '_blank', 'noopener,noreferrer');
|
||||
};
|
||||
|
||||
const getLogoUrl = () => {
|
||||
if (globalStore.themeConfig.logo) {
|
||||
return '/api/v1/images/logo';
|
||||
} else {
|
||||
return new URL(`../../../assets/images/1panel-logo-light.png`, import.meta.url).href;
|
||||
}
|
||||
};
|
||||
|
||||
onMounted(() => {
|
||||
search();
|
||||
getSystemAvailable();
|
||||
|
@ -28,6 +28,9 @@
|
||||
|
||||
<el-form-item :label="$t('setting.theme')" prop="theme">
|
||||
<el-radio-group @change="onSave('Theme', form.theme)" v-model="form.theme">
|
||||
<el-radio-button v-if="isProductPro" value="dark-gold">
|
||||
<span>{{ $t('xpack.setting.darkGold') }}</span>
|
||||
</el-radio-button>
|
||||
<el-radio-button value="light">
|
||||
<span>{{ $t('setting.light') }}</span>
|
||||
</el-radio-button>
|
||||
@ -154,7 +157,7 @@
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { ref, reactive, onMounted, computed } from 'vue';
|
||||
import { ref, reactive, onMounted } from 'vue';
|
||||
import { ElForm } from 'element-plus';
|
||||
import { getSettingInfo, updateSetting, getSystemAvailable } from '@/api/modules/setting';
|
||||
import { GlobalStore } from '@/store';
|
||||
@ -168,12 +171,16 @@ import PanelName from '@/views/setting/panel/name/index.vue';
|
||||
import SystemIP from '@/views/setting/panel/systemip/index.vue';
|
||||
import Network from '@/views/setting/panel/default-network/index.vue';
|
||||
import HideMenu from '@/views/setting/panel/hidemenu/index.vue';
|
||||
import { storeToRefs } from 'pinia';
|
||||
import { getXpackSetting, updateXpackSetting } from '@/utils/xpack';
|
||||
|
||||
const loading = ref(false);
|
||||
const i18n = useI18n();
|
||||
const globalStore = GlobalStore();
|
||||
const themeConfig = computed(() => globalStore.themeConfig);
|
||||
const { switchDark } = useTheme();
|
||||
|
||||
const { themeConfig, isProductPro } = storeToRefs(globalStore);
|
||||
|
||||
const { switchTheme } = useTheme();
|
||||
|
||||
const form = reactive({
|
||||
userName: '',
|
||||
@ -227,7 +234,6 @@ const search = async () => {
|
||||
form.ntpSite = res.data.ntpSite;
|
||||
form.panelName = res.data.panelName;
|
||||
form.systemIP = res.data.systemIP;
|
||||
form.theme = res.data.theme;
|
||||
form.menuTabs = res.data.menuTabs;
|
||||
form.language = res.data.language;
|
||||
form.complexityVerification = res.data.complexityVerification;
|
||||
@ -241,6 +247,15 @@ const search = async () => {
|
||||
const json: Node = JSON.parse(res.data.xpackHideMenu);
|
||||
const checkedTitles = getCheckedTitles(json);
|
||||
form.proHideMenus = checkedTitles.toString();
|
||||
|
||||
if (isProductPro.value) {
|
||||
const xpackRes = await getXpackSetting();
|
||||
if (xpackRes) {
|
||||
form.theme = xpackRes.data.theme || 'dark-gold';
|
||||
return;
|
||||
}
|
||||
}
|
||||
form.theme = res.data.theme;
|
||||
};
|
||||
|
||||
function extractTitles(node: Node, result: string[]): void {
|
||||
@ -298,7 +313,21 @@ const onSave = async (key: string, val: any) => {
|
||||
}
|
||||
if (key === 'Theme') {
|
||||
globalStore.setThemeConfig({ ...themeConfig.value, theme: val });
|
||||
switchDark();
|
||||
switchTheme();
|
||||
if (isProductPro.value) {
|
||||
let formData = new FormData();
|
||||
formData.append('theme', val);
|
||||
await updateXpackSetting(formData)
|
||||
.then(async () => {
|
||||
loading.value = false;
|
||||
MsgSuccess(i18n.t('commons.msg.operationSuccess'));
|
||||
await search();
|
||||
})
|
||||
.catch(() => {
|
||||
loading.value = false;
|
||||
});
|
||||
return;
|
||||
}
|
||||
}
|
||||
if (key === 'MenuTabs') {
|
||||
globalStore.setOpenMenuTabs(val === 'enable');
|
||||
|
2
frontend/vite-env.d.ts
vendored
Normal file
2
frontend/vite-env.d.ts
vendored
Normal file
@ -0,0 +1,2 @@
|
||||
/// <reference types="vite/client" />
|
||||
/// <reference types="vite-svg-loader" />
|
@ -12,6 +12,7 @@ import DefineOptions from 'unplugin-vue-define-options/vite';
|
||||
import AutoImport from 'unplugin-auto-import/vite';
|
||||
import Components from 'unplugin-vue-components/vite';
|
||||
import { ElementPlusResolver } from 'unplugin-vue-components/resolvers';
|
||||
import svgLoader from 'vite-svg-loader';
|
||||
|
||||
const prefix = `monaco-editor/esm/vs`;
|
||||
|
||||
@ -24,6 +25,7 @@ export default defineConfig(({ mode }: ConfigEnv): UserConfig => {
|
||||
alias: {
|
||||
'@': resolve(__dirname, './src'),
|
||||
'vue-i18n': 'vue-i18n/dist/vue-i18n.cjs.js',
|
||||
xpack: resolve(__dirname, './src/xpack'),
|
||||
},
|
||||
},
|
||||
css: {
|
||||
@ -76,6 +78,9 @@ export default defineConfig(({ mode }: ConfigEnv): UserConfig => {
|
||||
}),
|
||||
],
|
||||
}),
|
||||
svgLoader({
|
||||
defaultImport: 'url',
|
||||
}),
|
||||
],
|
||||
esbuild: {
|
||||
pure: viteEnv.VITE_DROP_CONSOLE ? ['console.log', 'debugger'] : [],
|
||||
|
Loading…
Reference in New Issue
Block a user