mirror of
https://github.com/hi2shark/nazhua.git
synced 2026-01-14 00:00:48 +08:00
✨ 新增自定义背景图片
✨ 新增浅色系背景设定,适配自定义背景图片 🪄 修改图表的渲染方式为SVG 🔧 优化页面路由切换的状态记录
This commit is contained in:
parent
582b367088
commit
bf30e14c30
@ -3,6 +3,8 @@ window.$$nazhuaConfig = {
|
||||
// freeAmount: '白嫖', // 免费服务的费用名称
|
||||
// infinityCycle: '长期有效', // 无限周期名称
|
||||
// buyBtnText: '购买', // 购买按钮文案
|
||||
// customBackgroundImage: '', // 自定义的背景图片地址
|
||||
// lightBackground: true, // 启用了浅色系背景图,会强制关闭点点背景
|
||||
// listServerItemType: 'row', // 服务器列表项类型 card/row row列表模式移动端自动切换至card
|
||||
// listServerStatusType: 'progress', // 服务器状态类型--列表
|
||||
// listServerRealTimeShowLoad: false, // 列表显示服务器实时负载
|
||||
|
||||
@ -185,6 +185,8 @@ window.$$nazhuaConfig = {
|
||||
freeAmount: '白嫖', // 免费服务的费用名称
|
||||
infinityCycle: '长期有效', // 无限周期名称
|
||||
buyBtnText: '购买', // 购买按钮文案
|
||||
customBackgroundImage: '', // 自定义的背景图片地址
|
||||
lightBackground: true, // 启用了浅色系背景图,会强制关闭点点背景
|
||||
listServerItemType: 'row', // 服务器列表项类型 card/row row列表模式目前不兼容移动端
|
||||
listServerStatusType: 'progress', // 服务器状态类型--列表
|
||||
listServerRealTimeShowLoad: false, // 列表显示服务器实时负载
|
||||
|
||||
@ -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>
|
||||
|
||||
|
||||
@ -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',
|
||||
|
||||
@ -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,
|
||||
|
||||
@ -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) {
|
||||
|
||||
@ -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);
|
||||
|
||||
@ -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;
|
||||
|
||||
@ -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>
|
||||
|
||||
@ -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);
|
||||
|
||||
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -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: {
|
||||
|
||||
@ -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>
|
||||
|
||||
Loading…
Reference in New Issue
Block a user