新增自定义背景图片

 新增浅色系背景设定,适配自定义背景图片
🪄 修改图表的渲染方式为SVG
🔧 优化页面路由切换的状态记录
This commit is contained in:
hi2hi 2024-12-20 16:43:48 +00:00
parent 582b367088
commit bf30e14c30
13 changed files with 209 additions and 38 deletions

View File

@ -3,6 +3,8 @@ window.$$nazhuaConfig = {
// freeAmount: '白嫖', // 免费服务的费用名称
// infinityCycle: '长期有效', // 无限周期名称
// buyBtnText: '购买', // 购买按钮文案
// customBackgroundImage: '', // 自定义的背景图片地址
// lightBackground: true, // 启用了浅色系背景图,会强制关闭点点背景
// listServerItemType: 'row', // 服务器列表项类型 card/row row列表模式移动端自动切换至card
// listServerStatusType: 'progress', // 服务器状态类型--列表
// listServerRealTimeShowLoad: false, // 列表显示服务器实时负载

View File

@ -185,6 +185,8 @@ window.$$nazhuaConfig = {
freeAmount: '白嫖', // 免费服务的费用名称
infinityCycle: '长期有效', // 无限周期名称
buyBtnText: '购买', // 购买按钮文案
customBackgroundImage: '', // 自定义的背景图片地址
lightBackground: true, // 启用了浅色系背景图,会强制关闭点点背景
listServerItemType: 'row', // 服务器列表项类型 card/row row列表模式目前不兼容移动端
listServerStatusType: 'progress', // 服务器状态类型--列表
listServerRealTimeShowLoad: false, // 列表显示服务器实时负载

View File

@ -1,6 +1,10 @@
<template>
<layout-main>
<router-view />
<router-view v-slot="{ Component }">
<keep-alive>
<component :is="Component" />
</keep-alive>
</router-view>
</layout-main>
</template>

View File

@ -1,5 +1,5 @@
import { use } from 'echarts/core';
import { CanvasRenderer } from 'echarts/renderers';
import { SVGRenderer } from 'echarts/renderers';
import {
BarChart,
} from 'echarts/charts';
@ -10,7 +10,7 @@ import {
import config from '@/config';
use([
CanvasRenderer,
SVGRenderer,
BarChart,
PolarComponent,
]);
@ -80,8 +80,16 @@ export default (used, total, itemColors, size = 100) => ({
itemStyle: {
color: typeof itemColors === 'string' ? itemColors : handleColor(itemColors?.used),
borderRadius: 5,
shadowColor: config.nazhua.serverStatusLinear ? 'rgba(0, 0, 0, 0.5)' : undefined,
shadowBlur: config.nazhua.serverStatusLinear ? 10 : undefined,
shadowColor: (() => {
if (config.nazhua.serverStatusLinear) {
return 'rgba(0, 0, 0, 0.5)';
}
if (config.nazhua.lightBackground) {
return 'rgba(0, 0, 0, 0.2)';
}
return undefined;
})(),
shadowBlur: (config.nazhua.serverStatusLinear || config.nazhua.lightBackground) ? 10 : undefined,
},
coordinateSystem: 'polar',
cursor: 'default',

View File

@ -1,5 +1,5 @@
import { use } from 'echarts/core';
import { CanvasRenderer } from 'echarts/renderers';
import { SVGRenderer } from 'echarts/renderers';
import { LineChart } from 'echarts/charts';
import {
TooltipComponent,
@ -12,7 +12,7 @@ import dayjs from 'dayjs';
import config from '@/config';
use([
CanvasRenderer,
SVGRenderer,
LineChart,
TooltipComponent,
// LegendComponent,

View File

@ -2,7 +2,7 @@
<div
class="dot-dot-box"
:class="{
'dot-dot-box--hide': $config.nazhua?.hideDotBG === true,
'dot-dot-box--hide': hideDotBG,
}"
:style="boxStyle"
>
@ -16,6 +16,7 @@
*/
import { computed } from 'vue';
import config from '@/config';
const props = defineProps({
borderRadius: {
@ -32,6 +33,10 @@ const props = defineProps({
},
});
const lightBackground = computed(() => config.nazhua.lightBackground);
const hideDotBG = computed(() => lightBackground.value || config.nazhua?.hideDotBG === true);
const boxStyle = computed(() => {
const style = {};
if (props.borderRadius) {
@ -68,9 +73,14 @@ const boxStyle = computed(() => {
backdrop-filter: saturate(50%) blur(3px);
&--hide {
background-color: rgba(#000, 0.8);
background-color: rgba(#000, 0.5);
background-image: none;
backdrop-filter: none;
transition: all 0.3s linear;
&:hover {
background-color: rgba(#000, 0.8);
}
}
@media screen and (max-width: 768px) {

View File

@ -1,6 +1,9 @@
<template>
<div
class="world-map-group"
:class="{
'world-map-group--light-background': lightBackground,
}"
:style="mapStyle"
>
<div class="world-map-img" />
@ -39,6 +42,7 @@ import {
computed,
watch,
} from 'vue';
import config from '@/config';
import validate from '@/utils/validate';
import WorldMapPoint from './world-map-point.vue';
@ -63,37 +67,46 @@ const props = defineProps({
},
});
const lightBackground = computed(() => config.nazhua.lightBackground);
const boxPadding = computed(() => (lightBackground.value ? 20 : 0));
// 1280:621
const computedSize = computed(() => {
// padding
const adjustedWidth = Number(props.width) - (boxPadding.value * 2);
const adjustedHeight = Number(props.height) - (boxPadding.value * 2);
if (!validate.isEmpty(props.width) && !validate.isEmpty(props.height)) {
return {
width: 1280,
height: 621,
};
}
const width = Number(props.width);
const height = Number(props.height);
if (!validate.isEmpty(props.width) && validate.isEmpty(props.height)) {
return {
width,
height: Math.ceil((621 / 1280) * width),
width: adjustedWidth,
height: Math.ceil((621 / 1280) * adjustedWidth),
};
}
if (validate.isEmpty(props.width) && !validate.isEmpty(props.height)) {
return {
width: Math.ceil((1280 / 621) * height),
height,
width: Math.ceil((1280 / 621) * adjustedHeight),
height: adjustedHeight,
};
}
if (width / height > 1280 / 621) {
if (adjustedWidth / adjustedHeight > 1280 / 621) {
return {
width: Math.ceil(height * (1280 / 621)),
height,
width: Math.ceil(adjustedHeight * (1280 / 621)),
height: adjustedHeight,
};
}
return {
width,
height: Math.ceil(width * (621 / 1280)),
width: adjustedWidth,
height: Math.ceil(adjustedWidth * (621 / 1280)),
};
});
@ -118,8 +131,8 @@ function computeMapPoints() {
const points = props.locations.map((i) => {
const item = {
key: i.key,
left: (computedSize.value.width / 1280) * i.x,
top: (computedSize.value.height / 621) * i.y,
left: (computedSize.value.width / 1280) * i.x + boxPadding.value,
top: (computedSize.value.height / 621) * i.y + boxPadding.value,
size: i.size || 4,
label: i.label,
servers: i.servers,
@ -219,6 +232,30 @@ function handlePointTap(e) {
height: var(--world-map-height, 621px);
position: relative;
&--light-background {
padding: 20px;
background: rgba(#000, 0.6);
border-radius: 12px;
box-sizing: content-box;
transition: background-color 0.3s linear;
.world-map-img {
opacity: 1;
}
&:hover {
background: rgba(#000, 0.9);
}
@media screen and (max-width: 768px) {
background: rgba(#000, 0.8);
&:hover {
background: rgba(#000, 0.8);
}
}
}
.world-map-img {
width: var(--world-map-width, 1280px);
height: var(--world-map-height, 621px);

View File

@ -143,6 +143,8 @@ const store = useStore();
const route = useRoute();
const router = useRouter();
const lightBackground = computed(() => config.nazhua.lightBackground);
const headerStyle = computed(() => {
const style = {};
if (route.name === 'ServerDetail') {
@ -267,6 +269,9 @@ const headerClass = computed(() => {
if (showServerCount.value) {
classes.push('layout-header--show-server-count');
}
if (lightBackground.value) {
classes.push('layout-header--light-background');
}
return classes;
});
@ -304,6 +309,12 @@ const dashboardUrl = computed(() => config.nazhua.v1DashboardUrl || '/dashboard'
}
}
&--light-background {
background-color: rgba(#000, 0.7);
background-image: none;
backdrop-filter: none;
}
.site-name {
line-height: calc(var(--layout-header-height) - 20px);
font-size: 24px;

View File

@ -1,6 +1,12 @@
<template>
<div class="layout-group">
<div class="layout-bg" />
<div
class="layout-group"
:style="layoutGroupStyle"
>
<div
class="layout-bg"
:style="layoutBGStyle"
/>
<div class="layout-main">
<layout-header />
<slot />
@ -13,8 +19,27 @@
/**
* LayoutMain
*/
import { computed } from 'vue';
import config from '@/config';
import LayoutHeader from './components/header.vue';
import LayoutFooter from './components/footer.vue';
const layoutGroupStyle = computed(() => {
const style = {};
if (config.nazhua.lightBackground) {
style['--layout-main-bg-color'] = 'rgba(20, 30, 40, 0.2)';
}
return style;
});
const layoutBGStyle = computed(() => {
const style = {};
if (config.nazhua.customBackgroundImage) {
style.background = `url(${config.nazhua.customBackgroundImage}) 50% 50%`;
style.backgroundSize = 'cover';
}
return style;
});
</script>
<style lang="scss" scoped>

View File

@ -27,10 +27,15 @@ const constantRoutes = [{
const routerOptions = {
history: config.nazhua.routeMode === 'h5' ? createWebHistory() : createWebHashHistory(),
scrollBehavior: () => ({
top: 0,
behavior: 'smooth',
}),
scrollBehavior: (to, from, savedPosition) => {
if (savedPosition) {
return savedPosition;
}
return {
top: 0,
behavior: 'smooth',
};
},
routes: constantRoutes,
};
const router = createRouter(routerOptions);

View File

@ -1,5 +1,10 @@
<template>
<div class="server-option-box">
<div
class="server-option-box"
:class="{
'server-option-box--light-background': lightBackground,
}"
>
<div
v-for="item in options"
:key="item.key"
@ -22,6 +27,7 @@
import {
computed,
} from 'vue';
import config from '@/config';
const props = defineProps({
modelValue: {
@ -42,6 +48,8 @@ const emits = defineEmits([
'update:modelValue',
]);
const lightBackground = computed(() => config.nazhua.lightBackground);
const activeValue = computed({
get: () => props.modelValue,
set: (val) => {
@ -75,6 +83,7 @@ function toggleModelValue(item) {
line-height: 1.2;
border-radius: 6px;
background: rgba(#000, 0.3);
transition: all 0.3s linear;
cursor: pointer;
@media screen and (max-width: 768px) {
background-color: rgba(#000, 0.8);
@ -83,10 +92,35 @@ function toggleModelValue(item) {
.option-label {
color: #fff;
transition: all 0.3s linear;
}
@media screen and (min-width: 768px) {
&:hover {
.option-label {
color: #ff7500;
}
}
}
&.active {
background: rgba(#ff7500, 0.75);
.option-label {
color: #fff;
}
}
}
@media screen and (min-width: 768px) {
&--light-background {
.server-option-item {
background: rgba(#000, 0.5);
&:hover {
background: rgba(#000, 0.8);
}
}
}
}
}

View File

@ -13,6 +13,10 @@ export default (params) => {
if (!props?.info) {
return {};
}
const lightBackground = computed(() => config.nazhua.lightBackground);
const serverStatusLinear = computed(() => config.nazhua.serverStatusLinear || lightBackground.value);
const cpuInfo = computed(() => {
if (props.info?.Host?.CPU?.[0]) {
return hostUtils.getCPUInfo(props.info.Host.CPU[0]);
@ -60,11 +64,12 @@ export default (params) => {
* 状态列表
*/
const serverStatusList = computed(() => statusListTpl.split(',').map((i) => {
const totalColor = lightBackground.value ? 'rgba(125, 125, 125, 0.5)' : 'rgba(255, 255, 255, 0.25)';
switch (i) {
case 'cpu':
{
const CoresVal = cpuInfo.value?.cores ? `${cpuInfo.value?.cores}C` : '-';
const usedColor = config.nazhua.serverStatusLinear ? ['#0088FF', '#72B7FF'] : '#0088FF';
const usedColor = serverStatusLinear.value ? ['#0088FF', '#72B7FF'] : '#0088FF';
const valPercent = `${(props.info.State?.CPU || 0).toFixed(1) * 1}%`;
const valText = valPercent;
return {
@ -72,7 +77,7 @@ export default (params) => {
used: (props.info.State?.CPU || 0).toFixed(1) * 1,
colors: {
used: usedColor,
total: 'rgba(255, 255, 255, 0.25)',
total: totalColor,
},
valText,
valPercent,
@ -97,13 +102,13 @@ export default (params) => {
} else {
contentVal = `${Math.ceil(useMemAndTotalMem.value.total.m)}M`;
}
const usedColor = config.nazhua.serverStatusLinear ? ['#2B6939', '#0AA344'] : '#0AA344';
const usedColor = serverStatusLinear.value ? ['#2B6939', '#0AA344'] : '#0AA344';
return {
type: 'mem',
used: useMemAndTotalMem.value.usePercent,
colors: {
used: usedColor,
total: 'rgba(255, 255, 255, 0.25)',
total: totalColor,
},
valText,
valPercent: `${useMemAndTotalMem.value.usePercent.toFixed(1) * 1}%`,
@ -131,13 +136,13 @@ export default (params) => {
} else {
contentVal = `${Math.ceil(useSwapAndTotalSwap.value.total.m)}M`;
}
const usedColor = config.nazhua.serverStatusLinear ? ['#FF8C00', '#F38100'] : '#FF8C00';
const usedColor = serverStatusLinear.value ? ['#FF8C00', '#F38100'] : '#FF8C00';
return {
type: 'swap',
used: useSwapAndTotalSwap.value.usePercent,
colors: {
used: usedColor,
total: 'rgba(255, 255, 255, 0.25)',
total: totalColor,
},
valText,
valPercent: `${useSwapAndTotalSwap.value.usePercent.toFixed(1) * 1}%`,
@ -150,21 +155,27 @@ export default (params) => {
}
case 'disk':
{
let valText;
if (useDiskAndTotalDisk.value.used.t >= 1 && useDiskAndTotalDisk.value.total.t >= 1) {
valText = `${(useDiskAndTotalDisk.value.used.t).toFixed(1) * 1}T`;
} else {
valText = `${Math.ceil(useDiskAndTotalDisk.value.used.g)}G`;
}
let contentValue;
if (useDiskAndTotalDisk.value.total.t >= 1) {
contentValue = `${(useDiskAndTotalDisk.value.total.t).toFixed(1) * 1}T`;
} else {
contentValue = `${Math.ceil(useDiskAndTotalDisk.value.total.g)}G`;
}
const usedColor = config.nazhua.serverStatusLinear ? ['#00848F', '#70F3FF'] : '#70F3FF';
const usedColor = serverStatusLinear.value ? ['#00848F', '#70F3FF'] : '#70F3FF';
return {
type: 'disk',
used: useDiskAndTotalDisk.value.usePercent,
colors: {
used: usedColor,
total: 'rgba(255, 255, 255, 0.25)',
total: totalColor,
},
valText: `${(useDiskAndTotalDisk.value.used.g).toFixed(1) * 1}G`,
valText,
valPercent: `${useDiskAndTotalDisk.value.usePercent.toFixed(1) * 1}%`,
label: '磁盘',
content: {

View File

@ -74,6 +74,9 @@ import {
computed,
onMounted,
onUnmounted,
onActivated,
onDeactivated,
nextTick,
} from 'vue';
import {
useStore,
@ -279,6 +282,25 @@ onMounted(() => {
onUnmounted(() => {
window.removeEventListener('resize', handleResize);
});
const scrollPosition = ref(0);
onDeactivated(() => {
//
scrollPosition.value = document.documentElement.scrollTop || document.body.scrollTop;
});
onActivated(() => {
//
if (scrollPosition.value > 0) {
nextTick(() => {
window.scrollTo({
top: scrollPosition.value,
behavior: 'instant',
});
});
}
});
</script>
<style lang="scss" scoped>