mirror of
https://github.com/hi2shark/nazhua.git
synced 2026-01-15 16:50:42 +08:00
Compare commits
12 Commits
718b0138b0
...
86b45b5f2a
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
86b45b5f2a | ||
|
|
3ede341f3d | ||
|
|
d3e549cad0 | ||
|
|
1f7e87c28d | ||
|
|
26de335304 | ||
|
|
efbf38738f | ||
|
|
9d301b9681 | ||
|
|
786d6c0a87 | ||
|
|
bdbd083d45 | ||
|
|
2910c2bf41 | ||
|
|
bcfc53b784 | ||
|
|
963c06dfce |
@ -66,6 +66,7 @@ module.exports = {
|
||||
'no-param-reassign': 'off',
|
||||
'no-underscore-dangle': 'off',
|
||||
'no-unsafe-optional-chaining': 'off',
|
||||
'max-classes-per-file': 'off',
|
||||
'max-len': ['warn', 120],
|
||||
'vue/max-len': ['warn', 120],
|
||||
'object-property-newline': ['error', {
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "nazhua",
|
||||
"version": "0.4.25",
|
||||
"version": "0.5.1",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"dev": "vite",
|
||||
|
||||
@ -5,12 +5,15 @@ window.$$nazhuaConfig = {
|
||||
// buyBtnText: '购买', // 购买按钮文案
|
||||
// customBackgroundImage: '', // 自定义的背景图片地址
|
||||
// lightBackground: true, // 启用了浅色系背景图,会强制关闭点点背景
|
||||
// showFireworks: true, // 是否显示烟花,建议开启浅色系背景
|
||||
// showLantern: true, // 是否显示灯笼
|
||||
// listServerItemTypeToggle: true, // 服务器列表项类型切换
|
||||
// listServerItemType: 'row', // 服务器列表项类型 card/row row列表模式移动端自动切换至card
|
||||
// listServerStatusType: 'progress', // 服务器状态类型--列表
|
||||
// listServerRealTimeShowLoad: false, // 列表显示服务器实时负载
|
||||
// listServerRealTimeShowLoad: true, // 列表显示服务器实时负载
|
||||
// detailServerStatusType: 'progress', // 服务器状态类型--详情页
|
||||
// serverStatusLinear: true, // 服务器状态渐变线性显示
|
||||
// disableSarasaTermSC: false, // 禁用Sarasa Term SC字体
|
||||
// disableSarasaTermSC: true, // 禁用Sarasa Term SC字体
|
||||
// hideWorldMap: false, // 隐藏地图
|
||||
// hideHomeWorldMap: false, // 隐藏首页地图
|
||||
// hideDetailWorldMap: false, // 隐藏详情地图
|
||||
|
||||
@ -71,12 +71,17 @@ Nazhua对这个支持大概在90%左右,参与数据处理了的字段如下
|
||||
"bandwidth": "30Mbps",
|
||||
"trafficVol": "1TB/月",
|
||||
"trafficType": "1",
|
||||
"IPv4": "1",
|
||||
"IPv6": "1",
|
||||
"networkRoute": "CN2,GIA",
|
||||
"extra": "传家宝,AS9929"
|
||||
}
|
||||
}
|
||||
```
|
||||
其中IPv4、IPv6暂未参与到处理中,后续可能会支持。
|
||||
~~其中IPv4、IPv6暂未参与到处理中,后续可能会支持。~~
|
||||
- 都有显示标签:双栈IP;
|
||||
- 单IPv4显示标签:仅IPv4;
|
||||
- 单IPv6显示标签:仅IPv6;
|
||||
|
||||
## 数据来源
|
||||
1-0. 公开的全量配置,其中包括“公开备注”(PublicNote),来自探针主页上暴露的服务器节点列表配置信息。此处是根据正则匹配的方式,获取到的节点列表。在主题项目中,默认将访问`/nezha/`的指向此处。
|
||||
|
||||
158
src/components/fireworks.vue
Normal file
158
src/components/fireworks.vue
Normal file
@ -0,0 +1,158 @@
|
||||
<template>
|
||||
<canvas
|
||||
ref="canvas"
|
||||
class="fireworks-canvas"
|
||||
/>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import {
|
||||
ref,
|
||||
onMounted,
|
||||
onUnmounted,
|
||||
} from 'vue';
|
||||
|
||||
const canvas = ref(null);
|
||||
let ctx = null;
|
||||
let particles = [];
|
||||
let rockets = [];
|
||||
let animationFrameId = null;
|
||||
|
||||
class Particle {
|
||||
constructor(x, y, color) {
|
||||
this.x = x;
|
||||
this.y = y;
|
||||
this.color = color;
|
||||
this.velocity = {
|
||||
x: (Math.random() - 0.5) * 8,
|
||||
y: (Math.random() - 0.5) * 12 - 8,
|
||||
};
|
||||
this.alpha = 1;
|
||||
this.decay = 0.02;
|
||||
}
|
||||
|
||||
draw() {
|
||||
ctx.beginPath();
|
||||
ctx.arc(this.x, this.y, 2, 0, Math.PI * 2);
|
||||
ctx.fillStyle = `rgba(${this.color}, ${this.alpha})`;
|
||||
ctx.fill();
|
||||
}
|
||||
|
||||
update() {
|
||||
this.velocity.y += 0.1;
|
||||
this.x += this.velocity.x;
|
||||
this.y += this.velocity.y;
|
||||
this.alpha -= this.decay;
|
||||
}
|
||||
}
|
||||
|
||||
function createFirework(x, y) {
|
||||
const colors = [
|
||||
'255, 0, 0',
|
||||
'0, 255, 0',
|
||||
'0, 0, 255',
|
||||
'255, 255, 0',
|
||||
'255, 0, 255',
|
||||
'0, 255, 255',
|
||||
];
|
||||
const color = colors[Math.floor(Math.random() * colors.length)];
|
||||
for (let i = 0; i < 80; i += 1) {
|
||||
particles.push(new Particle(x, y, color));
|
||||
}
|
||||
}
|
||||
|
||||
class Rocket {
|
||||
constructor() {
|
||||
this.x = Math.random() * canvas.value.width;
|
||||
this.y = canvas.value.height;
|
||||
this.targetY = canvas.value.height * 0.5;
|
||||
this.speed = 15;
|
||||
this.trail = [];
|
||||
this.maxTrailLength = 5;
|
||||
}
|
||||
|
||||
draw() {
|
||||
// 绘制火箭尾迹
|
||||
ctx.beginPath();
|
||||
this.trail.forEach((pos, index) => {
|
||||
ctx.fillStyle = `rgba(255, 200, 0, ${index / this.trail.length})`;
|
||||
ctx.fillRect(pos.x, pos.y, 2, 2);
|
||||
});
|
||||
|
||||
// 绘制火箭本体
|
||||
ctx.fillStyle = 'rgba(255, 220, 0, 1)';
|
||||
ctx.fillRect(this.x, this.y, 3, 3);
|
||||
}
|
||||
|
||||
update() {
|
||||
this.trail.push({
|
||||
x: this.x,
|
||||
y: this.y,
|
||||
});
|
||||
if (this.trail.length > this.maxTrailLength) {
|
||||
this.trail.shift();
|
||||
}
|
||||
this.y -= this.speed;
|
||||
if (this.y <= this.targetY) {
|
||||
createFirework(this.x, this.y);
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
function animate() {
|
||||
ctx.clearRect(0, 0, canvas.value.width, canvas.value.height);
|
||||
|
||||
// 更新和绘制火箭
|
||||
rockets = rockets.filter((rocket) => {
|
||||
rocket.draw();
|
||||
return rocket.update();
|
||||
});
|
||||
|
||||
// 更新和绘制粒子
|
||||
particles = particles.filter((particle) => particle.alpha > 0);
|
||||
particles.forEach((particle) => {
|
||||
particle.draw();
|
||||
particle.update();
|
||||
});
|
||||
|
||||
// 发射新的火箭
|
||||
if (Math.random() < 0.03 && rockets.length < 3) {
|
||||
rockets.push(new Rocket());
|
||||
}
|
||||
|
||||
animationFrameId = requestAnimationFrame(animate);
|
||||
}
|
||||
|
||||
function resizeCanvas() {
|
||||
if (canvas.value) {
|
||||
canvas.value.width = window.innerWidth;
|
||||
canvas.value.height = window.innerHeight;
|
||||
}
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
ctx = canvas.value.getContext('2d');
|
||||
resizeCanvas();
|
||||
window.addEventListener('resize', resizeCanvas);
|
||||
animate();
|
||||
});
|
||||
|
||||
onUnmounted(() => {
|
||||
window.removeEventListener('resize', resizeCanvas);
|
||||
if (animationFrameId) {
|
||||
cancelAnimationFrame(animationFrameId);
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.fireworks-canvas {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
z-index: 8;
|
||||
pointer-events: none;
|
||||
}
|
||||
</style>
|
||||
257
src/components/lantern.vue
Normal file
257
src/components/lantern.vue
Normal file
@ -0,0 +1,257 @@
|
||||
<template>
|
||||
<div class="lantern-container">
|
||||
<div class="lantern-group right-group">
|
||||
<div class="deng-box">
|
||||
<div class="deng">
|
||||
<div class="xian" />
|
||||
<div class="deng-a">
|
||||
<div class="deng-b">
|
||||
<div class="deng-t">快</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="shui shui-a">
|
||||
<div class="shui-c" />
|
||||
<div class="shui-b" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="deng-box deng-box--2">
|
||||
<div class="deng">
|
||||
<div class="xian" />
|
||||
<div class="deng-a">
|
||||
<div class="deng-b">
|
||||
<div class="deng-t">乐</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="shui shui-a">
|
||||
<div class="shui-c" />
|
||||
<div class="shui-b" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="lantern-group left-group">
|
||||
<div class="deng-box">
|
||||
<div class="deng">
|
||||
<div class="xian" />
|
||||
<div class="deng-a">
|
||||
<div class="deng-b">
|
||||
<div class="deng-t">新</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="shui shui-a">
|
||||
<div class="shui-c" />
|
||||
<div class="shui-b" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="deng-box deng-box--2">
|
||||
<div class="deng">
|
||||
<div class="xian" />
|
||||
<div class="deng-a">
|
||||
<div class="deng-b">
|
||||
<div class="deng-t">年</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="shui shui-a">
|
||||
<div class="shui-c" />
|
||||
<div class="shui-b" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
// 灯笼组件
|
||||
// 由AI生成
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.lantern-container {
|
||||
position: fixed;
|
||||
top: calc(var(--layout-header-height) + 5px);
|
||||
width: 100%;
|
||||
z-index: 50;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.lantern-group {
|
||||
position: fixed;
|
||||
top: 70px;
|
||||
animation: swing 3s infinite ease-in-out;
|
||||
transform-origin: 50% -10px;
|
||||
|
||||
&.left-group {
|
||||
left: 40px;
|
||||
animation-delay: -1.5s;
|
||||
|
||||
.deng-box:nth-child(2) {
|
||||
margin-top: -12px;
|
||||
|
||||
.deng {
|
||||
animation: swing-extra 2s infinite ease-in-out;
|
||||
animation-delay: -0.5s;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&.right-group {
|
||||
right: 30px;
|
||||
animation-delay: -0.5s;
|
||||
|
||||
.deng-box:nth-child(2) {
|
||||
|
||||
.deng {
|
||||
animation: swing-extra 2s infinite ease-in-out;
|
||||
animation-delay: -1s;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.deng {
|
||||
animation: none;
|
||||
}
|
||||
|
||||
.deng-box {
|
||||
position: relative;
|
||||
top: -40px;
|
||||
|
||||
&:first-child {
|
||||
z-index: 2;
|
||||
.deng {
|
||||
margin-bottom: 23px;
|
||||
}
|
||||
}
|
||||
|
||||
&:nth-child(2) {
|
||||
z-index: 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.deng {
|
||||
position: relative;
|
||||
width: 120px;
|
||||
height: 90px;
|
||||
margin: 50px;
|
||||
background: rgba(216, 0, 15, 0.8);
|
||||
border-radius: 50% 50%;
|
||||
transform-origin: 50% -100px;
|
||||
animation: swing 3s infinite ease-in-out;
|
||||
box-shadow: -5px 5px 50px 4px rgba(250, 108, 0, 1);
|
||||
}
|
||||
|
||||
.deng-a {
|
||||
width: 100px;
|
||||
height: 90px;
|
||||
background: rgba(216, 0, 15, 0.1);
|
||||
margin: 12px 8px 8px 10px;
|
||||
border-radius: 50% 50%;
|
||||
border: 2px solid #dc8f03;
|
||||
}
|
||||
|
||||
.deng-b {
|
||||
width: 45px;
|
||||
height: 90px;
|
||||
background: rgba(216, 0, 15, 0.1);
|
||||
margin: -4px 8px 8px 26px;
|
||||
border-radius: 50% 50%;
|
||||
border: 2px solid #dc8f03;
|
||||
}
|
||||
|
||||
.xian {
|
||||
position: absolute;
|
||||
top: -20px;
|
||||
left: 60px;
|
||||
width: 2px;
|
||||
height: 20px;
|
||||
background: #dc8f03;
|
||||
}
|
||||
|
||||
.shui-a {
|
||||
position: relative;
|
||||
width: 5px;
|
||||
height: 20px;
|
||||
margin: -5px 0 0 59px;
|
||||
transform-origin: 50% -45px;
|
||||
background: #ffa500;
|
||||
border-radius: 0 0 5px 5px;
|
||||
}
|
||||
|
||||
.shui-b {
|
||||
position: absolute;
|
||||
top: 14px;
|
||||
left: -2px;
|
||||
width: 10px;
|
||||
height: 10px;
|
||||
background: #dc8f03;
|
||||
border-radius: 50%;
|
||||
}
|
||||
|
||||
.shui-c {
|
||||
position: absolute;
|
||||
top: 18px;
|
||||
left: -2px;
|
||||
width: 10px;
|
||||
height: 35px;
|
||||
background: #ffa500;
|
||||
border-radius: 0 0 0 5px;
|
||||
}
|
||||
|
||||
.deng:before {
|
||||
position: absolute;
|
||||
top: -7px;
|
||||
left: 29px;
|
||||
height: 12px;
|
||||
width: 60px;
|
||||
content: " ";
|
||||
display: block;
|
||||
z-index: 999;
|
||||
border-radius: 5px 5px 0 0;
|
||||
border: solid 1px #dc8f03;
|
||||
background: linear-gradient(to right, #dc8f03, #ffa500, #dc8f03, #ffa500, #dc8f03);
|
||||
}
|
||||
|
||||
.deng:after {
|
||||
position: absolute;
|
||||
bottom: -7px;
|
||||
left: 10px;
|
||||
height: 12px;
|
||||
width: 60px;
|
||||
content: " ";
|
||||
display: block;
|
||||
margin-left: 20px;
|
||||
border-radius: 0 0 5px 5px;
|
||||
border: solid 1px #dc8f03;
|
||||
background: linear-gradient(to right, #dc8f03, #ffa500, #dc8f03, #ffa500, #dc8f03);
|
||||
}
|
||||
|
||||
.deng-t {
|
||||
font-family: 华文行楷, Arial, Lucida Grande, Tahoma, sans-serif;
|
||||
font-size: 3.2rem;
|
||||
color: #ffd000;
|
||||
line-height: 85px;
|
||||
text-align: center;
|
||||
margin-left: -5px;
|
||||
}
|
||||
|
||||
@keyframes swing {
|
||||
0% { transform: rotate(-6deg) }
|
||||
50% { transform: rotate(6deg) }
|
||||
100% { transform: rotate(-6deg) }
|
||||
}
|
||||
|
||||
@keyframes swing-extra {
|
||||
0% { transform: rotate(-3deg) }
|
||||
50% { transform: rotate(3deg) }
|
||||
100% { transform: rotate(-3deg) }
|
||||
}
|
||||
|
||||
@media screen and (max-width: 1024px) {
|
||||
.lantern-container {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@ -46,6 +46,9 @@ const store = useStore();
|
||||
const dynamicContentRef = ref();
|
||||
|
||||
const dynamicContent = computed(() => {
|
||||
if (store.state.setting?.config?.custom_code) {
|
||||
return store.state.setting.config.custom_code;
|
||||
}
|
||||
if (store.state.setting?.custom_code) {
|
||||
return store.state.setting.custom_code;
|
||||
}
|
||||
|
||||
@ -335,6 +335,10 @@ const dashboardUrl = computed(() => config.nazhua.v1DashboardUrl || '/dashboard'
|
||||
color: #ddd;
|
||||
line-height: 30px;
|
||||
|
||||
.value {
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
&.server-count--total {
|
||||
.value {
|
||||
color: #70f3ff;
|
||||
|
||||
@ -12,6 +12,12 @@
|
||||
<slot />
|
||||
<layout-footer />
|
||||
</div>
|
||||
<template v-if="config.nazhua.showFireworks">
|
||||
<fireworks />
|
||||
</template>
|
||||
<template v-if="config.nazhua.showLantern">
|
||||
<lantern />
|
||||
</template>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@ -21,6 +27,8 @@
|
||||
*/
|
||||
import { computed } from 'vue';
|
||||
import config from '@/config';
|
||||
import Fireworks from '@/components/fireworks.vue';
|
||||
import Lantern from '@/components/lantern.vue';
|
||||
import LayoutHeader from './components/header.vue';
|
||||
import LayoutFooter from './components/footer.vue';
|
||||
|
||||
|
||||
@ -118,7 +118,7 @@ const store = createStore({
|
||||
commit('SET_SETTING', res);
|
||||
// 如果自定义配置没有设置title,使用站点名称
|
||||
if (!window.$$nazhuaConfig.title) {
|
||||
config.nazhua.title = res.site_name;
|
||||
config.nazhua.title = res.config?.site_name || res.site_name;
|
||||
if (route?.name === 'Home' || !route) {
|
||||
document.title = config.nazhua.title;
|
||||
}
|
||||
|
||||
@ -212,8 +212,13 @@ export function getPlatformLogoIconClassName(platform) {
|
||||
/**
|
||||
* 获取系统发行版本
|
||||
*/
|
||||
export function getSystemOSLabel(platform) {
|
||||
switch (platform) {
|
||||
export function getSystemOSLabel(platform, short = false) {
|
||||
const platformStr = (platform || '').toLowerCase();
|
||||
// 匹配一些超长系统发行版本
|
||||
if (short && platformStr.includes('windows')) {
|
||||
return 'Windows';
|
||||
}
|
||||
switch (platformStr) {
|
||||
case 'windows':
|
||||
return 'Windows';
|
||||
case 'linux':
|
||||
|
||||
@ -26,7 +26,8 @@ export const loadServerGroup = async () => request({
|
||||
/**
|
||||
* 加载网站配置
|
||||
*
|
||||
* 暂时只使用site_name
|
||||
* 暂时只使用site_name\custom_code
|
||||
* 哪吒v1.4.9之后,上面的参数调整至data.config
|
||||
*/
|
||||
export const loadSetting = async () => request({
|
||||
url: config.nazhua.v1ApiSettingPath,
|
||||
|
||||
@ -212,6 +212,9 @@
|
||||
v-for="(tag, index) in tagList"
|
||||
:key="`${tag}_${index}`"
|
||||
class="server-info-tag-item"
|
||||
:class="{
|
||||
'has-sarasa-term': $hasSarasaTerm && config.nazhua.disableSarasaTermSC !== true,
|
||||
}"
|
||||
>
|
||||
{{ tag }}
|
||||
</span>
|
||||
@ -415,11 +418,24 @@ const billPlanData = computed(() => ['billing', 'remainingTime', 'bandwidth', 't
|
||||
|
||||
const tagList = computed(() => {
|
||||
const list = [];
|
||||
if (props?.info?.PublicNote?.planDataMod?.networkRoute) {
|
||||
list.push(...props.info.PublicNote.planDataMod.networkRoute.split(','));
|
||||
const {
|
||||
networkRoute,
|
||||
extra,
|
||||
IPv4,
|
||||
IPv6,
|
||||
} = props?.info?.PublicNote?.planDataMod || {};
|
||||
if (networkRoute) {
|
||||
list.push(...networkRoute?.split?.(','));
|
||||
}
|
||||
if (props?.info?.PublicNote?.planDataMod?.extra) {
|
||||
list.push(...props.info.PublicNote.planDataMod.extra.split(','));
|
||||
if (extra) {
|
||||
list.push(...extra?.split?.(','));
|
||||
}
|
||||
if (IPv4 === '1' && IPv6 === '1') {
|
||||
list.push('双栈IP');
|
||||
} else if (IPv4 === '1') {
|
||||
list.push('仅IPv4');
|
||||
} else if (IPv6 === '1') {
|
||||
list.push('仅IPv6');
|
||||
}
|
||||
return list;
|
||||
});
|
||||
@ -592,12 +608,16 @@ const processCount = computed(() => props.info?.State?.ProcessCount);
|
||||
.server-info-tag-item {
|
||||
height: 18px;
|
||||
padding: 0 5px 0 6px;
|
||||
line-height: 20px;
|
||||
line-height: 18px;
|
||||
font-size: 12px;
|
||||
color: var(--public-note-tag-color);
|
||||
background: var(--public-note-tag-bg);
|
||||
text-shadow: 1px 1px 2px rgba(#000, 0.2);
|
||||
border-radius: 4px;
|
||||
|
||||
&.has-sarasa-term {
|
||||
line-height: 20px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -37,7 +37,7 @@
|
||||
:key="`${tagItem}_${index}`"
|
||||
class="tag-item"
|
||||
:class="{
|
||||
'has-sarasa-term': $hasSarasaTerm,
|
||||
'has-sarasa-term': $hasSarasaTerm && config.nazhua.disableSarasaTermSC !== true,
|
||||
}"
|
||||
>
|
||||
{{ tagItem }}
|
||||
@ -106,11 +106,24 @@ function toBuy() {
|
||||
|
||||
const tagList = computed(() => {
|
||||
const list = [];
|
||||
if (props?.info?.PublicNote?.planDataMod?.networkRoute) {
|
||||
list.push(...props.info.PublicNote.planDataMod.networkRoute.split(','));
|
||||
const {
|
||||
networkRoute,
|
||||
extra,
|
||||
IPv4,
|
||||
IPv6,
|
||||
} = props?.info?.PublicNote?.planDataMod || {};
|
||||
if (networkRoute) {
|
||||
list.push(...networkRoute.split(','));
|
||||
}
|
||||
if (props?.info?.PublicNote?.planDataMod?.extra) {
|
||||
list.push(...props.info.PublicNote.planDataMod.extra.split(','));
|
||||
if (extra) {
|
||||
list.push(...extra.split(','));
|
||||
}
|
||||
if (IPv4 === '1' && IPv6 === '1') {
|
||||
list.push('双栈IP');
|
||||
} else if (IPv4 === '1') {
|
||||
list.push('仅IPv4');
|
||||
} else if (IPv6 === '1') {
|
||||
list.push('仅IPv6');
|
||||
}
|
||||
// 列表最多显示5个标签
|
||||
return list.slice(0, 5);
|
||||
|
||||
@ -92,7 +92,7 @@ const { cpuAndMemAndDisk } = handleServerInfo({
|
||||
props,
|
||||
});
|
||||
|
||||
const platformSystemLabel = computed(() => hostUtils.getSystemOSLabel(props.info?.Host?.Platform));
|
||||
const platformSystemLabel = computed(() => hostUtils.getSystemOSLabel(props.info?.Host?.Platform, true));
|
||||
|
||||
function openDetail() {
|
||||
router.push({
|
||||
@ -156,6 +156,7 @@ function openDetail() {
|
||||
height: 32px;
|
||||
line-height: 34px;
|
||||
font-size: 16px;
|
||||
font-weight: bold;
|
||||
width: 100%;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
|
||||
113
src/views/components/server-list/server-list-warp.vue
Normal file
113
src/views/components/server-list/server-list-warp.vue
Normal file
@ -0,0 +1,113 @@
|
||||
<template>
|
||||
<transition-group
|
||||
v-if="showTransition"
|
||||
name="list"
|
||||
tag="div"
|
||||
class="server-list-container"
|
||||
:class="{
|
||||
'server-list--row': showListRow,
|
||||
'server-list--card': showListCard,
|
||||
}"
|
||||
>
|
||||
<slot />
|
||||
</transition-group>
|
||||
<div
|
||||
v-else
|
||||
class="server-list-container"
|
||||
:class="{
|
||||
'server-list--row': showListRow,
|
||||
'server-list--card': showListCard,
|
||||
}"
|
||||
>
|
||||
<slot />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
/**
|
||||
* 服务器列表
|
||||
*/
|
||||
|
||||
defineProps({
|
||||
showTransition: {
|
||||
type: Boolean,
|
||||
default: true,
|
||||
},
|
||||
showListRow: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
showListCard: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.server-list-container.server-list--card {
|
||||
--list-padding: 20px;
|
||||
--list-gap-size: 20px;
|
||||
--list-item-num: 3;
|
||||
--list-item-width: calc(
|
||||
(
|
||||
var(--list-container-width)
|
||||
- (var(--list-padding) * 2)
|
||||
- (
|
||||
var(--list-gap-size)
|
||||
* (var(--list-item-num) - 1)
|
||||
)
|
||||
)
|
||||
/ var(--list-item-num)
|
||||
);
|
||||
position: relative;
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: var(--list-gap-size);
|
||||
padding: 0 var(--list-padding);
|
||||
width: var(--list-container-width);
|
||||
margin: auto;
|
||||
|
||||
// 针对1440px以下的屏幕
|
||||
@media screen and (max-width: 1440px) {
|
||||
--list-gap-size: 10px;
|
||||
}
|
||||
|
||||
@media screen and (max-width: 1024px) {
|
||||
--list-item-num: 2;
|
||||
}
|
||||
|
||||
@media screen and (max-width: 680px) {
|
||||
--list-item-num: 1;
|
||||
}
|
||||
}
|
||||
|
||||
.server-list-container.server-list--row {
|
||||
--list-padding: 20px;
|
||||
--list-gap-size: 12px;
|
||||
position: relative;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: var(--list-gap-size);
|
||||
width: var(--list-container-width);
|
||||
padding: 0 var(--list-padding);
|
||||
margin: auto;
|
||||
}
|
||||
|
||||
.list-move,
|
||||
.list-enter-active,
|
||||
.list-leave-active {
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
.list-enter-from {
|
||||
opacity: 0;
|
||||
transform: translateY(-30px);
|
||||
}
|
||||
.list-leave-to {
|
||||
opacity: 0;
|
||||
transform: translateY(30px);
|
||||
}
|
||||
.list-leave-active {
|
||||
position: absolute;
|
||||
}
|
||||
</style>
|
||||
@ -10,12 +10,22 @@
|
||||
:key="item.key"
|
||||
class="server-option-item"
|
||||
:class="{
|
||||
'has-icon': item.icon,
|
||||
active: activeValue === item.value,
|
||||
}"
|
||||
:title="item?.title || false"
|
||||
@click="toggleModelValue(item)"
|
||||
>
|
||||
<span class="option-label">{{ item.label }}</span>
|
||||
<i
|
||||
v-if="item.icon"
|
||||
class="option-icon"
|
||||
:class="item.icon"
|
||||
:title="item.label"
|
||||
/>
|
||||
<span
|
||||
v-else
|
||||
class="option-label"
|
||||
>{{ item.label }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
@ -85,13 +95,27 @@ function toggleModelValue(item) {
|
||||
background: rgba(#000, 0.3);
|
||||
transition: all 0.3s linear;
|
||||
cursor: pointer;
|
||||
|
||||
&.has-icon {
|
||||
padding: 0 10px;
|
||||
}
|
||||
|
||||
@media screen and (max-width: 768px) {
|
||||
height: 30px;
|
||||
padding: 0 10px;
|
||||
border-radius: 3px;
|
||||
background-color: rgba(#000, 0.8);
|
||||
cursor: default;
|
||||
}
|
||||
|
||||
.option-icon {
|
||||
line-height: 1;
|
||||
font-size: 18px;
|
||||
}
|
||||
|
||||
.option-label {
|
||||
color: #fff;
|
||||
font-weight: bold;
|
||||
transition: all 0.3s linear;
|
||||
}
|
||||
|
||||
@ -120,6 +144,10 @@ function toggleModelValue(item) {
|
||||
&:hover {
|
||||
background: rgba(#000, 0.8);
|
||||
}
|
||||
|
||||
&.active {
|
||||
background: rgba(#ff7500, 0.75);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -31,34 +31,36 @@
|
||||
v-model="filterFormData.online"
|
||||
:options="onlineOptions"
|
||||
/>
|
||||
<server-option-box
|
||||
v-if="config.nazhua.listServerItemTypeToggle"
|
||||
v-model="listType"
|
||||
:options="listTypeOptions"
|
||||
:accpet-empty="false"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<transition-group
|
||||
<server-list-warp
|
||||
v-if="showListRow"
|
||||
name="list"
|
||||
tag="div"
|
||||
class="server-list-container"
|
||||
:class="`server-list--row`"
|
||||
:show-transition="showTransition"
|
||||
:show-list-row="showListRow"
|
||||
>
|
||||
<server-row-item
|
||||
v-for="item in filterServerList.list"
|
||||
:key="item.ID"
|
||||
:info="item"
|
||||
/>
|
||||
</transition-group>
|
||||
<transition-group
|
||||
</server-list-warp>
|
||||
<server-list-warp
|
||||
v-if="showListCard"
|
||||
name="list"
|
||||
tag="div"
|
||||
class="server-list-container"
|
||||
:class="`server-list--card`"
|
||||
:show-transition="showTransition"
|
||||
:show-list-card="showListCard"
|
||||
>
|
||||
<server-card-item
|
||||
v-for="item in filterServerList.list"
|
||||
:key="item.ID"
|
||||
:info="item"
|
||||
/>
|
||||
</transition-group>
|
||||
</server-list-warp>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
@ -93,6 +95,7 @@ import validate from '@/utils/validate';
|
||||
|
||||
import WorldMap from '@/components/world-map/world-map.vue';
|
||||
import ServerOptionBox from './components/server-list/server-option-box.vue';
|
||||
import ServerListWarp from './components/server-list/server-list-warp.vue';
|
||||
import ServerCardItem from './components/server-list/card/server-list-item.vue';
|
||||
import ServerRowItem from './components/server-list/row/server-list-item.vue';
|
||||
|
||||
@ -100,14 +103,30 @@ const store = useStore();
|
||||
const worldMapWidth = ref();
|
||||
const windowWidth = ref(window.innerWidth);
|
||||
|
||||
const listType = ref(config.nazhua.listServerItemType || 'card');
|
||||
|
||||
const showTransition = computed(() => {
|
||||
// 强制开启
|
||||
if (config.nazhua.forceTransition) {
|
||||
return true;
|
||||
}
|
||||
// 服务器数量小于7时,不开启
|
||||
return store.state.serverList.length < 7;
|
||||
});
|
||||
const showListRow = computed(() => {
|
||||
if (windowWidth.value > 1024) {
|
||||
if (config.nazhua.listServerItemTypeToggle) {
|
||||
return listType.value === 'row';
|
||||
}
|
||||
return config.nazhua.listServerItemType === 'row';
|
||||
}
|
||||
return false;
|
||||
});
|
||||
const showListCard = computed(() => {
|
||||
if (windowWidth.value > 1024) {
|
||||
if (config.nazhua.listServerItemTypeToggle) {
|
||||
return listType.value !== 'row';
|
||||
}
|
||||
return config.nazhua.listServerItemType !== 'row';
|
||||
}
|
||||
return true;
|
||||
@ -159,6 +178,18 @@ const onlineOptions = computed(() => {
|
||||
return [];
|
||||
});
|
||||
|
||||
const listTypeOptions = computed(() => [{
|
||||
key: 'card',
|
||||
label: '卡片',
|
||||
value: 'card',
|
||||
icon: 'ri-gallery-view-2',
|
||||
}, {
|
||||
key: 'row',
|
||||
label: '列表',
|
||||
value: 'row',
|
||||
icon: 'ri-list-view',
|
||||
}]);
|
||||
|
||||
const filterServerList = computed(() => {
|
||||
const fields = {};
|
||||
const locationMap = {};
|
||||
@ -329,75 +360,19 @@ onActivated(() => {
|
||||
justify-content: space-between;
|
||||
gap: 10px 20px;
|
||||
width: var(--list-container-width);
|
||||
padding: 0 20px;
|
||||
margin: auto;
|
||||
|
||||
&.list-is-card {
|
||||
padding: 0 20px;
|
||||
}
|
||||
}
|
||||
|
||||
.server-list-container.server-list--card {
|
||||
--list-padding: 20px;
|
||||
--list-gap-size: 20px;
|
||||
--list-item-num: 3;
|
||||
--list-item-width: calc(
|
||||
(
|
||||
var(--list-container-width)
|
||||
- (var(--list-padding) * 2)
|
||||
- (
|
||||
var(--list-gap-size)
|
||||
* (var(--list-item-num) - 1)
|
||||
)
|
||||
)
|
||||
/ var(--list-item-num)
|
||||
);
|
||||
position: relative;
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: var(--list-gap-size);
|
||||
padding: 0 var(--list-padding);
|
||||
width: var(--list-container-width);
|
||||
margin: auto;
|
||||
|
||||
// 针对1440px以下的屏幕
|
||||
@media screen and (max-width: 1440px) {
|
||||
--list-gap-size: 10px;
|
||||
.left-box {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
@media screen and (max-width: 1024px) {
|
||||
--list-item-num: 2;
|
||||
.right-box {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
@media screen and (max-width: 680px) {
|
||||
--list-item-num: 1;
|
||||
}
|
||||
}
|
||||
|
||||
.server-list-container.server-list--row {
|
||||
--list-padding: 20px;
|
||||
--list-gap-size: 12px;
|
||||
position: relative;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: var(--list-gap-size);
|
||||
width: var(--list-container-width);
|
||||
margin: auto;
|
||||
}
|
||||
|
||||
.list-move,
|
||||
.list-enter-active,
|
||||
.list-leave-active {
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
.list-enter-from {
|
||||
opacity: 0;
|
||||
transform: translateY(-30px);
|
||||
}
|
||||
.list-leave-to {
|
||||
opacity: 0;
|
||||
transform: translateY(30px);
|
||||
}
|
||||
.list-leave-active {
|
||||
position: absolute;
|
||||
}
|
||||
</style>
|
||||
|
||||
Loading…
Reference in New Issue
Block a user