mirror of
https://github.com/hi2shark/nazhua.git
synced 2026-01-18 10:10:43 +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-param-reassign': 'off',
|
||||||
'no-underscore-dangle': 'off',
|
'no-underscore-dangle': 'off',
|
||||||
'no-unsafe-optional-chaining': 'off',
|
'no-unsafe-optional-chaining': 'off',
|
||||||
|
'max-classes-per-file': 'off',
|
||||||
'max-len': ['warn', 120],
|
'max-len': ['warn', 120],
|
||||||
'vue/max-len': ['warn', 120],
|
'vue/max-len': ['warn', 120],
|
||||||
'object-property-newline': ['error', {
|
'object-property-newline': ['error', {
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "nazhua",
|
"name": "nazhua",
|
||||||
"version": "0.4.25",
|
"version": "0.5.1",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "vite",
|
"dev": "vite",
|
||||||
|
|||||||
@ -5,12 +5,15 @@ window.$$nazhuaConfig = {
|
|||||||
// buyBtnText: '购买', // 购买按钮文案
|
// buyBtnText: '购买', // 购买按钮文案
|
||||||
// customBackgroundImage: '', // 自定义的背景图片地址
|
// customBackgroundImage: '', // 自定义的背景图片地址
|
||||||
// lightBackground: true, // 启用了浅色系背景图,会强制关闭点点背景
|
// lightBackground: true, // 启用了浅色系背景图,会强制关闭点点背景
|
||||||
|
// showFireworks: true, // 是否显示烟花,建议开启浅色系背景
|
||||||
|
// showLantern: true, // 是否显示灯笼
|
||||||
|
// listServerItemTypeToggle: true, // 服务器列表项类型切换
|
||||||
// listServerItemType: 'row', // 服务器列表项类型 card/row row列表模式移动端自动切换至card
|
// listServerItemType: 'row', // 服务器列表项类型 card/row row列表模式移动端自动切换至card
|
||||||
// listServerStatusType: 'progress', // 服务器状态类型--列表
|
// listServerStatusType: 'progress', // 服务器状态类型--列表
|
||||||
// listServerRealTimeShowLoad: false, // 列表显示服务器实时负载
|
// listServerRealTimeShowLoad: true, // 列表显示服务器实时负载
|
||||||
// detailServerStatusType: 'progress', // 服务器状态类型--详情页
|
// detailServerStatusType: 'progress', // 服务器状态类型--详情页
|
||||||
// serverStatusLinear: true, // 服务器状态渐变线性显示
|
// serverStatusLinear: true, // 服务器状态渐变线性显示
|
||||||
// disableSarasaTermSC: false, // 禁用Sarasa Term SC字体
|
// disableSarasaTermSC: true, // 禁用Sarasa Term SC字体
|
||||||
// hideWorldMap: false, // 隐藏地图
|
// hideWorldMap: false, // 隐藏地图
|
||||||
// hideHomeWorldMap: false, // 隐藏首页地图
|
// hideHomeWorldMap: false, // 隐藏首页地图
|
||||||
// hideDetailWorldMap: false, // 隐藏详情地图
|
// hideDetailWorldMap: false, // 隐藏详情地图
|
||||||
|
|||||||
@ -71,12 +71,17 @@ Nazhua对这个支持大概在90%左右,参与数据处理了的字段如下
|
|||||||
"bandwidth": "30Mbps",
|
"bandwidth": "30Mbps",
|
||||||
"trafficVol": "1TB/月",
|
"trafficVol": "1TB/月",
|
||||||
"trafficType": "1",
|
"trafficType": "1",
|
||||||
|
"IPv4": "1",
|
||||||
|
"IPv6": "1",
|
||||||
"networkRoute": "CN2,GIA",
|
"networkRoute": "CN2,GIA",
|
||||||
"extra": "传家宝,AS9929"
|
"extra": "传家宝,AS9929"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
其中IPv4、IPv6暂未参与到处理中,后续可能会支持。
|
~~其中IPv4、IPv6暂未参与到处理中,后续可能会支持。~~
|
||||||
|
- 都有显示标签:双栈IP;
|
||||||
|
- 单IPv4显示标签:仅IPv4;
|
||||||
|
- 单IPv6显示标签:仅IPv6;
|
||||||
|
|
||||||
## 数据来源
|
## 数据来源
|
||||||
1-0. 公开的全量配置,其中包括“公开备注”(PublicNote),来自探针主页上暴露的服务器节点列表配置信息。此处是根据正则匹配的方式,获取到的节点列表。在主题项目中,默认将访问`/nezha/`的指向此处。
|
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 dynamicContentRef = ref();
|
||||||
|
|
||||||
const dynamicContent = computed(() => {
|
const dynamicContent = computed(() => {
|
||||||
|
if (store.state.setting?.config?.custom_code) {
|
||||||
|
return store.state.setting.config.custom_code;
|
||||||
|
}
|
||||||
if (store.state.setting?.custom_code) {
|
if (store.state.setting?.custom_code) {
|
||||||
return store.state.setting.custom_code;
|
return store.state.setting.custom_code;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -335,6 +335,10 @@ const dashboardUrl = computed(() => config.nazhua.v1DashboardUrl || '/dashboard'
|
|||||||
color: #ddd;
|
color: #ddd;
|
||||||
line-height: 30px;
|
line-height: 30px;
|
||||||
|
|
||||||
|
.value {
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
|
||||||
&.server-count--total {
|
&.server-count--total {
|
||||||
.value {
|
.value {
|
||||||
color: #70f3ff;
|
color: #70f3ff;
|
||||||
|
|||||||
@ -12,6 +12,12 @@
|
|||||||
<slot />
|
<slot />
|
||||||
<layout-footer />
|
<layout-footer />
|
||||||
</div>
|
</div>
|
||||||
|
<template v-if="config.nazhua.showFireworks">
|
||||||
|
<fireworks />
|
||||||
|
</template>
|
||||||
|
<template v-if="config.nazhua.showLantern">
|
||||||
|
<lantern />
|
||||||
|
</template>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
@ -21,6 +27,8 @@
|
|||||||
*/
|
*/
|
||||||
import { computed } from 'vue';
|
import { computed } from 'vue';
|
||||||
import config from '@/config';
|
import config from '@/config';
|
||||||
|
import Fireworks from '@/components/fireworks.vue';
|
||||||
|
import Lantern from '@/components/lantern.vue';
|
||||||
import LayoutHeader from './components/header.vue';
|
import LayoutHeader from './components/header.vue';
|
||||||
import LayoutFooter from './components/footer.vue';
|
import LayoutFooter from './components/footer.vue';
|
||||||
|
|
||||||
|
|||||||
@ -118,7 +118,7 @@ const store = createStore({
|
|||||||
commit('SET_SETTING', res);
|
commit('SET_SETTING', res);
|
||||||
// 如果自定义配置没有设置title,使用站点名称
|
// 如果自定义配置没有设置title,使用站点名称
|
||||||
if (!window.$$nazhuaConfig.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) {
|
if (route?.name === 'Home' || !route) {
|
||||||
document.title = config.nazhua.title;
|
document.title = config.nazhua.title;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -212,8 +212,13 @@ export function getPlatformLogoIconClassName(platform) {
|
|||||||
/**
|
/**
|
||||||
* 获取系统发行版本
|
* 获取系统发行版本
|
||||||
*/
|
*/
|
||||||
export function getSystemOSLabel(platform) {
|
export function getSystemOSLabel(platform, short = false) {
|
||||||
switch (platform) {
|
const platformStr = (platform || '').toLowerCase();
|
||||||
|
// 匹配一些超长系统发行版本
|
||||||
|
if (short && platformStr.includes('windows')) {
|
||||||
|
return 'Windows';
|
||||||
|
}
|
||||||
|
switch (platformStr) {
|
||||||
case 'windows':
|
case 'windows':
|
||||||
return 'Windows';
|
return 'Windows';
|
||||||
case 'linux':
|
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({
|
export const loadSetting = async () => request({
|
||||||
url: config.nazhua.v1ApiSettingPath,
|
url: config.nazhua.v1ApiSettingPath,
|
||||||
|
|||||||
@ -212,6 +212,9 @@
|
|||||||
v-for="(tag, index) in tagList"
|
v-for="(tag, index) in tagList"
|
||||||
:key="`${tag}_${index}`"
|
:key="`${tag}_${index}`"
|
||||||
class="server-info-tag-item"
|
class="server-info-tag-item"
|
||||||
|
:class="{
|
||||||
|
'has-sarasa-term': $hasSarasaTerm && config.nazhua.disableSarasaTermSC !== true,
|
||||||
|
}"
|
||||||
>
|
>
|
||||||
{{ tag }}
|
{{ tag }}
|
||||||
</span>
|
</span>
|
||||||
@ -415,11 +418,24 @@ const billPlanData = computed(() => ['billing', 'remainingTime', 'bandwidth', 't
|
|||||||
|
|
||||||
const tagList = computed(() => {
|
const tagList = computed(() => {
|
||||||
const list = [];
|
const list = [];
|
||||||
if (props?.info?.PublicNote?.planDataMod?.networkRoute) {
|
const {
|
||||||
list.push(...props.info.PublicNote.planDataMod.networkRoute.split(','));
|
networkRoute,
|
||||||
|
extra,
|
||||||
|
IPv4,
|
||||||
|
IPv6,
|
||||||
|
} = props?.info?.PublicNote?.planDataMod || {};
|
||||||
|
if (networkRoute) {
|
||||||
|
list.push(...networkRoute?.split?.(','));
|
||||||
}
|
}
|
||||||
if (props?.info?.PublicNote?.planDataMod?.extra) {
|
if (extra) {
|
||||||
list.push(...props.info.PublicNote.planDataMod.extra.split(','));
|
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;
|
return list;
|
||||||
});
|
});
|
||||||
@ -592,12 +608,16 @@ const processCount = computed(() => props.info?.State?.ProcessCount);
|
|||||||
.server-info-tag-item {
|
.server-info-tag-item {
|
||||||
height: 18px;
|
height: 18px;
|
||||||
padding: 0 5px 0 6px;
|
padding: 0 5px 0 6px;
|
||||||
line-height: 20px;
|
line-height: 18px;
|
||||||
font-size: 12px;
|
font-size: 12px;
|
||||||
color: var(--public-note-tag-color);
|
color: var(--public-note-tag-color);
|
||||||
background: var(--public-note-tag-bg);
|
background: var(--public-note-tag-bg);
|
||||||
text-shadow: 1px 1px 2px rgba(#000, 0.2);
|
text-shadow: 1px 1px 2px rgba(#000, 0.2);
|
||||||
border-radius: 4px;
|
border-radius: 4px;
|
||||||
|
|
||||||
|
&.has-sarasa-term {
|
||||||
|
line-height: 20px;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -37,7 +37,7 @@
|
|||||||
:key="`${tagItem}_${index}`"
|
:key="`${tagItem}_${index}`"
|
||||||
class="tag-item"
|
class="tag-item"
|
||||||
:class="{
|
:class="{
|
||||||
'has-sarasa-term': $hasSarasaTerm,
|
'has-sarasa-term': $hasSarasaTerm && config.nazhua.disableSarasaTermSC !== true,
|
||||||
}"
|
}"
|
||||||
>
|
>
|
||||||
{{ tagItem }}
|
{{ tagItem }}
|
||||||
@ -106,11 +106,24 @@ function toBuy() {
|
|||||||
|
|
||||||
const tagList = computed(() => {
|
const tagList = computed(() => {
|
||||||
const list = [];
|
const list = [];
|
||||||
if (props?.info?.PublicNote?.planDataMod?.networkRoute) {
|
const {
|
||||||
list.push(...props.info.PublicNote.planDataMod.networkRoute.split(','));
|
networkRoute,
|
||||||
|
extra,
|
||||||
|
IPv4,
|
||||||
|
IPv6,
|
||||||
|
} = props?.info?.PublicNote?.planDataMod || {};
|
||||||
|
if (networkRoute) {
|
||||||
|
list.push(...networkRoute.split(','));
|
||||||
}
|
}
|
||||||
if (props?.info?.PublicNote?.planDataMod?.extra) {
|
if (extra) {
|
||||||
list.push(...props.info.PublicNote.planDataMod.extra.split(','));
|
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个标签
|
// 列表最多显示5个标签
|
||||||
return list.slice(0, 5);
|
return list.slice(0, 5);
|
||||||
|
|||||||
@ -92,7 +92,7 @@ const { cpuAndMemAndDisk } = handleServerInfo({
|
|||||||
props,
|
props,
|
||||||
});
|
});
|
||||||
|
|
||||||
const platformSystemLabel = computed(() => hostUtils.getSystemOSLabel(props.info?.Host?.Platform));
|
const platformSystemLabel = computed(() => hostUtils.getSystemOSLabel(props.info?.Host?.Platform, true));
|
||||||
|
|
||||||
function openDetail() {
|
function openDetail() {
|
||||||
router.push({
|
router.push({
|
||||||
@ -156,6 +156,7 @@ function openDetail() {
|
|||||||
height: 32px;
|
height: 32px;
|
||||||
line-height: 34px;
|
line-height: 34px;
|
||||||
font-size: 16px;
|
font-size: 16px;
|
||||||
|
font-weight: bold;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
text-overflow: ellipsis;
|
text-overflow: ellipsis;
|
||||||
white-space: nowrap;
|
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"
|
:key="item.key"
|
||||||
class="server-option-item"
|
class="server-option-item"
|
||||||
:class="{
|
:class="{
|
||||||
|
'has-icon': item.icon,
|
||||||
active: activeValue === item.value,
|
active: activeValue === item.value,
|
||||||
}"
|
}"
|
||||||
:title="item?.title || false"
|
:title="item?.title || false"
|
||||||
@click="toggleModelValue(item)"
|
@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>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
@ -85,13 +95,27 @@ function toggleModelValue(item) {
|
|||||||
background: rgba(#000, 0.3);
|
background: rgba(#000, 0.3);
|
||||||
transition: all 0.3s linear;
|
transition: all 0.3s linear;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
|
|
||||||
|
&.has-icon {
|
||||||
|
padding: 0 10px;
|
||||||
|
}
|
||||||
|
|
||||||
@media screen and (max-width: 768px) {
|
@media screen and (max-width: 768px) {
|
||||||
|
height: 30px;
|
||||||
|
padding: 0 10px;
|
||||||
|
border-radius: 3px;
|
||||||
background-color: rgba(#000, 0.8);
|
background-color: rgba(#000, 0.8);
|
||||||
cursor: default;
|
cursor: default;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.option-icon {
|
||||||
|
line-height: 1;
|
||||||
|
font-size: 18px;
|
||||||
|
}
|
||||||
|
|
||||||
.option-label {
|
.option-label {
|
||||||
color: #fff;
|
color: #fff;
|
||||||
|
font-weight: bold;
|
||||||
transition: all 0.3s linear;
|
transition: all 0.3s linear;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -120,6 +144,10 @@ function toggleModelValue(item) {
|
|||||||
&:hover {
|
&:hover {
|
||||||
background: rgba(#000, 0.8);
|
background: rgba(#000, 0.8);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
&.active {
|
||||||
|
background: rgba(#ff7500, 0.75);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -31,34 +31,36 @@
|
|||||||
v-model="filterFormData.online"
|
v-model="filterFormData.online"
|
||||||
:options="onlineOptions"
|
:options="onlineOptions"
|
||||||
/>
|
/>
|
||||||
|
<server-option-box
|
||||||
|
v-if="config.nazhua.listServerItemTypeToggle"
|
||||||
|
v-model="listType"
|
||||||
|
:options="listTypeOptions"
|
||||||
|
:accpet-empty="false"
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<transition-group
|
<server-list-warp
|
||||||
v-if="showListRow"
|
v-if="showListRow"
|
||||||
name="list"
|
:show-transition="showTransition"
|
||||||
tag="div"
|
:show-list-row="showListRow"
|
||||||
class="server-list-container"
|
|
||||||
:class="`server-list--row`"
|
|
||||||
>
|
>
|
||||||
<server-row-item
|
<server-row-item
|
||||||
v-for="item in filterServerList.list"
|
v-for="item in filterServerList.list"
|
||||||
:key="item.ID"
|
:key="item.ID"
|
||||||
:info="item"
|
:info="item"
|
||||||
/>
|
/>
|
||||||
</transition-group>
|
</server-list-warp>
|
||||||
<transition-group
|
<server-list-warp
|
||||||
v-if="showListCard"
|
v-if="showListCard"
|
||||||
name="list"
|
:show-transition="showTransition"
|
||||||
tag="div"
|
:show-list-card="showListCard"
|
||||||
class="server-list-container"
|
|
||||||
:class="`server-list--card`"
|
|
||||||
>
|
>
|
||||||
<server-card-item
|
<server-card-item
|
||||||
v-for="item in filterServerList.list"
|
v-for="item in filterServerList.list"
|
||||||
:key="item.ID"
|
:key="item.ID"
|
||||||
:info="item"
|
:info="item"
|
||||||
/>
|
/>
|
||||||
</transition-group>
|
</server-list-warp>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
@ -93,6 +95,7 @@ import validate from '@/utils/validate';
|
|||||||
|
|
||||||
import WorldMap from '@/components/world-map/world-map.vue';
|
import WorldMap from '@/components/world-map/world-map.vue';
|
||||||
import ServerOptionBox from './components/server-list/server-option-box.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 ServerCardItem from './components/server-list/card/server-list-item.vue';
|
||||||
import ServerRowItem from './components/server-list/row/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 worldMapWidth = ref();
|
||||||
const windowWidth = ref(window.innerWidth);
|
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(() => {
|
const showListRow = computed(() => {
|
||||||
if (windowWidth.value > 1024) {
|
if (windowWidth.value > 1024) {
|
||||||
|
if (config.nazhua.listServerItemTypeToggle) {
|
||||||
|
return listType.value === 'row';
|
||||||
|
}
|
||||||
return config.nazhua.listServerItemType === 'row';
|
return config.nazhua.listServerItemType === 'row';
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
});
|
});
|
||||||
const showListCard = computed(() => {
|
const showListCard = computed(() => {
|
||||||
if (windowWidth.value > 1024) {
|
if (windowWidth.value > 1024) {
|
||||||
|
if (config.nazhua.listServerItemTypeToggle) {
|
||||||
|
return listType.value !== 'row';
|
||||||
|
}
|
||||||
return config.nazhua.listServerItemType !== 'row';
|
return config.nazhua.listServerItemType !== 'row';
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
@ -159,6 +178,18 @@ const onlineOptions = computed(() => {
|
|||||||
return [];
|
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 filterServerList = computed(() => {
|
||||||
const fields = {};
|
const fields = {};
|
||||||
const locationMap = {};
|
const locationMap = {};
|
||||||
@ -329,75 +360,19 @@ onActivated(() => {
|
|||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
gap: 10px 20px;
|
gap: 10px 20px;
|
||||||
width: var(--list-container-width);
|
width: var(--list-container-width);
|
||||||
|
padding: 0 20px;
|
||||||
margin: auto;
|
margin: auto;
|
||||||
|
|
||||||
&.list-is-card {
|
.left-box {
|
||||||
padding: 0 20px;
|
display: flex;
|
||||||
}
|
flex-wrap: wrap;
|
||||||
}
|
gap: 12px;
|
||||||
|
|
||||||
.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) {
|
.right-box {
|
||||||
--list-item-num: 2;
|
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>
|
</style>
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user