mirror of
https://github.com/EasyTierMC/ETMC.Web.git
synced 2025-12-07 21:15:48 +08:00
feat: homepage
This commit is contained in:
8
.editorconfig
Normal file
8
.editorconfig
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
[*.{js,jsx,mjs,cjs,ts,tsx,mts,cts,vue,css,scss,sass,less,styl}]
|
||||||
|
charset = utf-8
|
||||||
|
indent_size = 4
|
||||||
|
indent_style = space
|
||||||
|
insert_final_newline = true
|
||||||
|
trim_trailing_whitespace = true
|
||||||
|
end_of_line = lf
|
||||||
|
max_line_length = 100
|
||||||
1
.gitattributes
vendored
Normal file
1
.gitattributes
vendored
Normal file
@@ -0,0 +1 @@
|
|||||||
|
* text=auto eol=crlf
|
||||||
5
.gitignore
vendored
5
.gitignore
vendored
@@ -34,8 +34,3 @@ coverage
|
|||||||
|
|
||||||
# Vitest
|
# Vitest
|
||||||
__screenshots__/
|
__screenshots__/
|
||||||
|
|
||||||
# package
|
|
||||||
package-lock.json
|
|
||||||
pnpm-lock.yaml
|
|
||||||
yarn.lock
|
|
||||||
|
|||||||
6
.prettierrc
Normal file
6
.prettierrc
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
{
|
||||||
|
"tabWidth": 4,
|
||||||
|
"useTabs": false,
|
||||||
|
"vueIndentScriptAndStyle": true,
|
||||||
|
"bracketSameLine": true
|
||||||
|
}
|
||||||
6
.vscode/extensions.json
vendored
6
.vscode/extensions.json
vendored
@@ -1,3 +1,7 @@
|
|||||||
{
|
{
|
||||||
"recommendations": ["Vue.volar", "bradlc.vscode-tailwindcss"]
|
"recommendations": [
|
||||||
|
"Vue.volar",
|
||||||
|
"dbaeumer.vscode-eslint",
|
||||||
|
"EditorConfig.EditorConfig"
|
||||||
|
]
|
||||||
}
|
}
|
||||||
|
|||||||
12
README.md
12
README.md
@@ -1,4 +1,4 @@
|
|||||||
# EasyTierWeb
|
# NeoUptime Web
|
||||||
|
|
||||||
This template should help get you started developing with Vue 3 in Vite.
|
This template should help get you started developing with Vue 3 in Vite.
|
||||||
|
|
||||||
@@ -32,11 +32,17 @@ pnpm install
|
|||||||
### Compile and Hot-Reload for Development
|
### Compile and Hot-Reload for Development
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
pnpm run dev
|
pnpm dev
|
||||||
```
|
```
|
||||||
|
|
||||||
### Type-Check, Compile and Minify for Production
|
### Type-Check, Compile and Minify for Production
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
pnpm run build
|
pnpm build
|
||||||
|
```
|
||||||
|
|
||||||
|
### Lint with [ESLint](https://eslint.org/)
|
||||||
|
|
||||||
|
```sh
|
||||||
|
pnpm lint
|
||||||
```
|
```
|
||||||
|
|||||||
21
index.html
21
index.html
@@ -1,14 +1,13 @@
|
|||||||
<!DOCTYPE html>
|
<!DOCTYPE html>
|
||||||
<html lang="">
|
<html lang="">
|
||||||
<head>
|
<head>
|
||||||
<meta charset="UTF-8">
|
<meta charset="UTF-8" />
|
||||||
<link rel="icon" href="/favicon.ico">
|
<link rel="icon" href="/favicon.ico" />
|
||||||
<link rel="stylesheet" href="src/assets/style.css">
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
<title>EasyTierMC Uptime</title>
|
||||||
<title>Vite App</title>
|
</head>
|
||||||
</head>
|
<body class="bg-base-200 w-screen h-screen">
|
||||||
<body>
|
<div id="app"></div>
|
||||||
<div id="app"></div>
|
<script type="module" src="/src/main.ts"></script>
|
||||||
<script type="module" src="/src/main.ts"></script>
|
</body>
|
||||||
</body>
|
|
||||||
</html>
|
</html>
|
||||||
|
|||||||
74
package.json
74
package.json
@@ -1,38 +1,40 @@
|
|||||||
{
|
{
|
||||||
"name": "neoeasytierweb",
|
"name": "neouptime-web",
|
||||||
"version": "0.0.0",
|
"version": "0.0.0",
|
||||||
"private": true,
|
"private": true,
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"packageManager": "pnpm@10.18.0",
|
"engines": {
|
||||||
"engines": {
|
"node": "^20.19.0 || >=22.12.0"
|
||||||
"node": "^20.19.0 || >=22.12.0"
|
},
|
||||||
},
|
"scripts": {
|
||||||
"scripts": {
|
"dev": "vite",
|
||||||
"dev": "vite",
|
"build": "run-p type-check \"build-only {@}\" --",
|
||||||
"build": "run-p type-check \"build-only {@}\" --",
|
"preview": "vite preview",
|
||||||
"preview": "vite preview",
|
"build-only": "vite build",
|
||||||
"build-only": "vite build",
|
"type-check": "vue-tsc --build",
|
||||||
"type-check": "vue-tsc --build"
|
"lint": "eslint . --fix --cache"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"echarts": "^6.0.0",
|
"@iconify/tailwind4": "^1.1.0",
|
||||||
"neoeasytierweb": "link:",
|
"@tailwindcss/vite": "^4.1.17",
|
||||||
"tailwindcss": "^4.1.17",
|
"daisyui": "^5.5.4",
|
||||||
"vue": "^3.5.22",
|
"pinia": "^3.0.4",
|
||||||
"vue-echarts": "^8.0.1",
|
"tailwindcss": "^4.1.17",
|
||||||
"vue-router": "^4.6.3"
|
"vue": "^3.5.24",
|
||||||
},
|
"vue-router": "^4.6.3"
|
||||||
"devDependencies": {
|
},
|
||||||
"@tailwindcss/vite": "^4.1.17",
|
"devDependencies": {
|
||||||
"@tsconfig/node22": "^22.0.2",
|
"@iconify-json/octicon": "^1.2.19",
|
||||||
"@types/node": "^22.18.11",
|
"@tsconfig/node22": "^22.0.3",
|
||||||
"@vitejs/plugin-vue": "^6.0.1",
|
"@types/node": "^22.19.1",
|
||||||
"@vue/tsconfig": "^0.8.1",
|
"@vitejs/plugin-vue": "^6.0.1",
|
||||||
"daisyui": "^5.4.7",
|
"@vue/eslint-config-typescript": "^14.6.0",
|
||||||
"npm-run-all2": "^8.0.4",
|
"@vue/tsconfig": "^0.8.1",
|
||||||
"typescript": "~5.9.0",
|
"jiti": "^2.6.1",
|
||||||
"vite": "^7.1.11",
|
"npm-run-all2": "^8.0.4",
|
||||||
"vite-plugin-vue-devtools": "^8.0.3",
|
"typescript": "~5.9.3",
|
||||||
"vue-tsc": "^3.1.1"
|
"vite": "npm:rolldown-vite@^7.2.5",
|
||||||
}
|
"vite-plugin-vue-devtools": "^8.0.3",
|
||||||
|
"vue-tsc": "^3.1.3"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
2706
pnpm-lock.yaml
generated
2706
pnpm-lock.yaml
generated
File diff suppressed because it is too large
Load Diff
69
src/App.vue
69
src/App.vue
@@ -1,70 +1,11 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { RouterLink, RouterView } from 'vue-router'
|
import { Layout } from "./layout";
|
||||||
const year = new Date().getFullYear()
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div class="layout">
|
<Layout>
|
||||||
<header class="navbar bg-base-100 shadow-md">
|
<RouterView />
|
||||||
<div class="navbar-start">
|
</Layout>
|
||||||
<div class="dropdown">
|
|
||||||
<div tabindex="0" role="button" class="btn btn-ghost lg:hidden">
|
|
||||||
<svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
|
||||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 6h16M4 12h8m-8 6h16" />
|
|
||||||
</svg>
|
|
||||||
</div>
|
|
||||||
<ul tabindex="0" class="dropdown-content menu p-2 shadow bg-base-100 rounded-box w-52">
|
|
||||||
<li><RouterLink to="/">主页</RouterLink></li>
|
|
||||||
<li><RouterLink to="/monitor">节点监控</RouterLink></li>
|
|
||||||
<li><RouterLink to="/submit">提交节点</RouterLink></li>
|
|
||||||
</ul>
|
|
||||||
</div>
|
|
||||||
<div class="flex-1">
|
|
||||||
<h1 class="btn btn-ghost normal-case text-2xl font-bold">EasyTierMC Uptime</h1>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="navbar-end hidden lg:flex">
|
|
||||||
<ul class="menu menu-horizontal px-1 gap-2">
|
|
||||||
<li><RouterLink to="/" class="btn btn-ghost">主页</RouterLink></li>
|
|
||||||
<li><RouterLink to="/monitor" class="btn btn-ghost">节点监控</RouterLink></li>
|
|
||||||
<li><RouterLink to="/submit" class="btn btn-ghost">提交节点</RouterLink></li>
|
|
||||||
</ul>
|
|
||||||
</div>
|
|
||||||
</header>
|
|
||||||
<main class="flex-1">
|
|
||||||
<RouterView />
|
|
||||||
</main>
|
|
||||||
<footer class="footer footer-center bg-base-200 text-base-content p-4 border-t">
|
|
||||||
<div>
|
|
||||||
<small>© {{ year }} EasyTierMC Uptime</small>
|
|
||||||
</div>
|
|
||||||
</footer>
|
|
||||||
</div>
|
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<style scoped>
|
<style scoped></style>
|
||||||
.layout {
|
|
||||||
min-height: 100vh;
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
}
|
|
||||||
|
|
||||||
header {
|
|
||||||
flex-shrink: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
main {
|
|
||||||
flex: 1;
|
|
||||||
padding: 1.25rem 1.5rem 2rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
footer {
|
|
||||||
flex-shrink: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
@media (min-width: 1024px) {
|
|
||||||
main {
|
|
||||||
padding: 1.75rem 2rem 2.5rem;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
|
|||||||
@@ -1,89 +0,0 @@
|
|||||||
@import "tailwindcss";
|
|
||||||
@plugin "daisyui";
|
|
||||||
|
|
||||||
/* color palette from <https://github.com/vuejs/theme> */
|
|
||||||
:root {
|
|
||||||
--vt-c-white: #ffffff;
|
|
||||||
--vt-c-white-soft: #f8f8f8;
|
|
||||||
--vt-c-white-mute: #f2f2f2;
|
|
||||||
|
|
||||||
--vt-c-black: #181818;
|
|
||||||
--vt-c-black-soft: #222222;
|
|
||||||
--vt-c-black-mute: #282828;
|
|
||||||
|
|
||||||
--vt-c-indigo: #2c3e50;
|
|
||||||
|
|
||||||
--vt-c-divider-light-1: rgba(60, 60, 60, 0.29);
|
|
||||||
--vt-c-divider-light-2: rgba(60, 60, 60, 0.12);
|
|
||||||
--vt-c-divider-dark-1: rgba(84, 84, 84, 0.65);
|
|
||||||
--vt-c-divider-dark-2: rgba(84, 84, 84, 0.48);
|
|
||||||
|
|
||||||
--vt-c-text-light-1: var(--vt-c-indigo);
|
|
||||||
--vt-c-text-light-2: rgba(60, 60, 60, 0.66);
|
|
||||||
--vt-c-text-dark-1: var(--vt-c-white);
|
|
||||||
--vt-c-text-dark-2: rgba(235, 235, 235, 0.64);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* semantic color variables for this project */
|
|
||||||
:root {
|
|
||||||
--color-background: var(--vt-c-white);
|
|
||||||
--color-background-soft: var(--vt-c-white-soft);
|
|
||||||
--color-background-mute: var(--vt-c-white-mute);
|
|
||||||
|
|
||||||
--color-border: var(--vt-c-divider-light-2);
|
|
||||||
--color-border-hover: var(--vt-c-divider-light-1);
|
|
||||||
|
|
||||||
--color-heading: var(--vt-c-text-light-1);
|
|
||||||
--color-text: var(--vt-c-text-light-1);
|
|
||||||
|
|
||||||
--section-gap: 160px;
|
|
||||||
}
|
|
||||||
|
|
||||||
@media (prefers-color-scheme: dark) {
|
|
||||||
:root {
|
|
||||||
--color-background: var(--vt-c-black);
|
|
||||||
--color-background-soft: var(--vt-c-black-soft);
|
|
||||||
--color-background-mute: var(--vt-c-black-mute);
|
|
||||||
|
|
||||||
--color-border: var(--vt-c-divider-dark-2);
|
|
||||||
--color-border-hover: var(--vt-c-divider-dark-1);
|
|
||||||
|
|
||||||
--color-heading: var(--vt-c-text-dark-1);
|
|
||||||
--color-text: var(--vt-c-text-dark-2);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
*,
|
|
||||||
*::before,
|
|
||||||
*::after {
|
|
||||||
box-sizing: border-box;
|
|
||||||
margin: 0;
|
|
||||||
font-weight: normal;
|
|
||||||
}
|
|
||||||
|
|
||||||
body {
|
|
||||||
min-height: 100vh;
|
|
||||||
color: var(--color-text);
|
|
||||||
background: var(--color-background);
|
|
||||||
transition:
|
|
||||||
color 0.5s,
|
|
||||||
background-color 0.5s;
|
|
||||||
line-height: 1.6;
|
|
||||||
font-family:
|
|
||||||
Inter,
|
|
||||||
-apple-system,
|
|
||||||
BlinkMacSystemFont,
|
|
||||||
'Segoe UI',
|
|
||||||
Roboto,
|
|
||||||
Oxygen,
|
|
||||||
Ubuntu,
|
|
||||||
Cantarell,
|
|
||||||
'Fira Sans',
|
|
||||||
'Droid Sans',
|
|
||||||
'Helvetica Neue',
|
|
||||||
sans-serif;
|
|
||||||
font-size: 15px;
|
|
||||||
text-rendering: optimizeLegibility;
|
|
||||||
-webkit-font-smoothing: antialiased;
|
|
||||||
-moz-osx-font-smoothing: grayscale;
|
|
||||||
}
|
|
||||||
84
src/assets/index.css
Normal file
84
src/assets/index.css
Normal file
@@ -0,0 +1,84 @@
|
|||||||
|
@import "tailwindcss";
|
||||||
|
|
||||||
|
@layer base {
|
||||||
|
*:not(.selectable) {
|
||||||
|
@apply select-none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@plugin "@iconify/tailwind4";
|
||||||
|
|
||||||
|
@plugin "daisyui" {
|
||||||
|
themes: false;
|
||||||
|
excludes: "rootscrollgutter";
|
||||||
|
}
|
||||||
|
|
||||||
|
@plugin "daisyui/theme" {
|
||||||
|
name: "light";
|
||||||
|
default: true;
|
||||||
|
prefersdark: false;
|
||||||
|
color-scheme: "light";
|
||||||
|
--color-base-100: oklch(0.9782 0.0034 247.86);
|
||||||
|
--color-base-200: oklch(0.9782 0.0034 247.86);
|
||||||
|
--color-base-300: oklch(1 0 0);
|
||||||
|
--color-base-content: oklch(0.135 0.0111 254.04);
|
||||||
|
--color-primary: oklch(0.7967 0.1103 178.33);
|
||||||
|
--color-primary-content: oklch(0.0757 0.1103 178.33);
|
||||||
|
--color-secondary: oklch(0.7967 0.1103 62.49);
|
||||||
|
--color-secondary-content: oklch(0.0757 0.1103 62.49);
|
||||||
|
--color-accent: oklch(0.7967 0.1103 337.03);
|
||||||
|
--color-accent-content: oklch(0.0757 0.1103 337.03);
|
||||||
|
--color-neutral: oklch(14% 0.005 285.823);
|
||||||
|
--color-neutral-content: oklch(92% 0.004 286.32);
|
||||||
|
--color-info: oklch(0.701 0.201 255.48);
|
||||||
|
--color-info-content: oklch(25% 0.09 281.288);
|
||||||
|
--color-success: oklch(0.7701 0.1809 145.62);
|
||||||
|
--color-success-content: oklch(26% 0.051 172.552);
|
||||||
|
--color-warning: oklch(0.848 0.1394 72.63);
|
||||||
|
--color-warning-content: oklch(27% 0.077 45.635);
|
||||||
|
--color-error: oklch(0.7145 0.2234 26.79);
|
||||||
|
--color-error-content: oklch(25% 0.092 26.042);
|
||||||
|
--radius-selector: 0.5rem;
|
||||||
|
--radius-field: 0.25rem;
|
||||||
|
--radius-box: 0.5rem;
|
||||||
|
--size-selector: 0.25rem;
|
||||||
|
--size-field: 0.25rem;
|
||||||
|
--border: 1px;
|
||||||
|
--depth: 1;
|
||||||
|
--noise: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
@plugin "daisyui/theme" {
|
||||||
|
name: "dark";
|
||||||
|
default: false;
|
||||||
|
prefersdark: true;
|
||||||
|
color-scheme: "dark";
|
||||||
|
--color-base-100: oklch(0.2735 0.0179 251.92);
|
||||||
|
--color-base-200: oklch(0.1763 0.014 258.36);
|
||||||
|
--color-base-300: oklch(0.1039 0.0194 248.35);
|
||||||
|
--color-base-content: oklch(0.9852 0 116.07);
|
||||||
|
--color-primary: oklch(0.7967 0.1103 178.33);
|
||||||
|
--color-primary-content: oklch(0.0757 0.1103 178.33);
|
||||||
|
--color-secondary: oklch(0.7967 0.1103 62.49);
|
||||||
|
--color-secondary-content: oklch(0.0757 0.1103 62.49);
|
||||||
|
--color-accent: oklch(0.7967 0.1103 337.03);
|
||||||
|
--color-accent-content: oklch(0.0757 0.1103 337.03);
|
||||||
|
--color-neutral: oklch(14% 0.005 285.823);
|
||||||
|
--color-neutral-content: oklch(92% 0.004 286.32);
|
||||||
|
--color-info: oklch(0.626 0.201 255.48);
|
||||||
|
--color-info-content: oklch(25% 0.09 281.288);
|
||||||
|
--color-success: oklch(0.6951 0.1809 145.62);
|
||||||
|
--color-success-content: oklch(26% 0.051 172.552);
|
||||||
|
--color-warning: oklch(0.773 0.1394 72.63);
|
||||||
|
--color-warning-content: oklch(27% 0.077 45.635);
|
||||||
|
--color-error: oklch(0.6395 0.2234 26.79);
|
||||||
|
--color-error-content: oklch(25% 0.092 26.042);
|
||||||
|
--radius-selector: 0.5rem;
|
||||||
|
--radius-field: 0.25rem;
|
||||||
|
--radius-box: 0.5rem;
|
||||||
|
--size-selector: 0.25rem;
|
||||||
|
--size-field: 0.25rem;
|
||||||
|
--border: 1px;
|
||||||
|
--depth: 1;
|
||||||
|
--noise: 0;
|
||||||
|
}
|
||||||
@@ -1 +0,0 @@
|
|||||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 261.76 226.69"><path d="M161.096.001l-30.225 52.351L100.647.001H-.005l130.877 226.688L261.749.001z" fill="#41b883"/><path d="M161.096.001l-30.225 52.351L100.647.001H52.346l78.526 136.01L209.398.001z" fill="#34495e"/></svg>
|
|
||||||
|
Before Width: | Height: | Size: 276 B |
@@ -1,29 +0,0 @@
|
|||||||
@import './base.css';
|
|
||||||
|
|
||||||
#app {
|
|
||||||
padding: 2rem;
|
|
||||||
font-weight: normal;
|
|
||||||
}
|
|
||||||
|
|
||||||
a,
|
|
||||||
.green {
|
|
||||||
text-decoration: none;
|
|
||||||
color: hsla(160, 100%, 37%, 1);
|
|
||||||
transition: 0.4s;
|
|
||||||
padding: 3px;
|
|
||||||
}
|
|
||||||
|
|
||||||
@media (hover: hover) {
|
|
||||||
a:hover {
|
|
||||||
background-color: hsla(160, 100%, 37%, 0.2);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@media (min-width: 1024px) {
|
|
||||||
body {
|
|
||||||
}
|
|
||||||
|
|
||||||
#app {
|
|
||||||
padding: 0 2rem;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,92 +0,0 @@
|
|||||||
<template>
|
|
||||||
<div>
|
|
||||||
<v-chart class="chart" :option="option" />
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script setup lang="ts">
|
|
||||||
import { ref } from 'vue';
|
|
||||||
import VChart from 'vue-echarts';
|
|
||||||
import china from "../../../public/china.json"
|
|
||||||
import * as echarts from 'echarts';
|
|
||||||
echarts.registerMap('china',china as any);
|
|
||||||
|
|
||||||
const data = [
|
|
||||||
{ name: '北京', value: 85 },
|
|
||||||
{ name: '上海', value: 92 },
|
|
||||||
{ name: '广东', value: 9 },
|
|
||||||
{ name: '浙江', value: 65 },
|
|
||||||
{ name: '江苏', value: 58 },
|
|
||||||
// 更多数据...
|
|
||||||
]
|
|
||||||
|
|
||||||
const option = ref<echarts.EChartsOption>({
|
|
||||||
tooltip: {
|
|
||||||
show: true,
|
|
||||||
formatter: '{b}: {c}个节点'
|
|
||||||
},
|
|
||||||
visualMap: {
|
|
||||||
type: 'piecewise',
|
|
||||||
min: 0,
|
|
||||||
max: 50,
|
|
||||||
pieces:[
|
|
||||||
{
|
|
||||||
max:5,
|
|
||||||
label:'少',
|
|
||||||
color:'#ff4d4d'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
min:5,
|
|
||||||
max:15,
|
|
||||||
label:'中',
|
|
||||||
color:'#ffa64d'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
min:15,
|
|
||||||
max:20,
|
|
||||||
label:'多',
|
|
||||||
color:'#ffcc00'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
min:20,
|
|
||||||
max:30,
|
|
||||||
label:'非常多',
|
|
||||||
color:'#99cc33'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
min:30,
|
|
||||||
label:'数据中心',
|
|
||||||
color:'#33cc33'
|
|
||||||
}
|
|
||||||
],
|
|
||||||
textStyle:{
|
|
||||||
color:'#fff'
|
|
||||||
}
|
|
||||||
},
|
|
||||||
series: [{
|
|
||||||
name: '服务器节点',
|
|
||||||
type: 'map',
|
|
||||||
map: 'china',
|
|
||||||
roam: false,
|
|
||||||
select: {
|
|
||||||
disabled: true // 禁用选择状态
|
|
||||||
},
|
|
||||||
emphasis: {
|
|
||||||
label: {
|
|
||||||
show: true,
|
|
||||||
},
|
|
||||||
itemStyle:{
|
|
||||||
areaColor:'#FFDE59'
|
|
||||||
}
|
|
||||||
},
|
|
||||||
data
|
|
||||||
}]
|
|
||||||
});
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<style scoped>
|
|
||||||
.chart {
|
|
||||||
height: 600px;
|
|
||||||
width: 30%;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
1
src/layout/Admin.vue
Normal file
1
src/layout/Admin.vue
Normal file
@@ -0,0 +1 @@
|
|||||||
|
<template></template>
|
||||||
20
src/layout/Default.vue
Normal file
20
src/layout/Default.vue
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
<template>
|
||||||
|
<div class="layout">
|
||||||
|
<nav class="navbar bg-base-100 shadow-sm justify-between">
|
||||||
|
<a class="btn btn-ghost text-xl">EasyTierMC Uptime</a>
|
||||||
|
<section class="flex gap-2">
|
||||||
|
<button class="btn btn-link">Home</button>
|
||||||
|
<button class="btn btn-link">About</button>
|
||||||
|
<button class="btn btn-link">Docs</button>
|
||||||
|
</section>
|
||||||
|
<button class="btn btn-primary mr-4">Submit a Node</button>
|
||||||
|
</nav>
|
||||||
|
<div class="content p-4">
|
||||||
|
<slot />
|
||||||
|
</div>
|
||||||
|
<div class="divider"></div>
|
||||||
|
<footer class="p-4 -mt-2">
|
||||||
|
<p>© {{ new Date().getFullYear() }} EasyTierMC. All Right Reserved.</p>
|
||||||
|
</footer>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
1
src/layout/index.ts
Normal file
1
src/layout/index.ts
Normal file
@@ -0,0 +1 @@
|
|||||||
|
export { default as Layout } from "./index.vue";
|
||||||
26
src/layout/index.vue
Normal file
26
src/layout/index.vue
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import { ref, watch, type Component } from "vue";
|
||||||
|
import { useRoute } from "vue-router";
|
||||||
|
import Default from "./Default.vue";
|
||||||
|
import Admin from "./Admin.vue";
|
||||||
|
|
||||||
|
const route = useRoute();
|
||||||
|
const LayoutComponent = ref<Component>(Default);
|
||||||
|
|
||||||
|
watch(
|
||||||
|
() => route.fullPath,
|
||||||
|
(newPath) => {
|
||||||
|
if (newPath === "/admin") {
|
||||||
|
LayoutComponent.value = Admin;
|
||||||
|
} else {
|
||||||
|
LayoutComponent.value = Default;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<LayoutComponent>
|
||||||
|
<slot />
|
||||||
|
</LayoutComponent>
|
||||||
|
</template>
|
||||||
17
src/main.ts
17
src/main.ts
@@ -1,11 +1,14 @@
|
|||||||
import './assets/main.css'
|
import { createApp } from "vue";
|
||||||
|
import { createPinia } from "pinia";
|
||||||
|
|
||||||
import { createApp } from 'vue'
|
import App from "./App.vue";
|
||||||
import App from './App.vue'
|
import router from "./modules/router";
|
||||||
import router from './router'
|
|
||||||
|
|
||||||
const app = createApp(App)
|
const app = createApp(App);
|
||||||
|
|
||||||
app.use(router)
|
app.use(createPinia());
|
||||||
|
app.use(router);
|
||||||
|
|
||||||
app.mount('#app')
|
app.mount("#app");
|
||||||
|
|
||||||
|
import "@/assets/index.css";
|
||||||
|
|||||||
60
src/modules/router/index.ts
Normal file
60
src/modules/router/index.ts
Normal file
@@ -0,0 +1,60 @@
|
|||||||
|
import type { Component } from "vue";
|
||||||
|
import { createRouter, createWebHistory, type RouteRecordRaw } from "vue-router";
|
||||||
|
|
||||||
|
type ComponentImport = () => Promise<{ default: Component }>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Step 1
|
||||||
|
* Lazy improt all files from \@/pages
|
||||||
|
* @returns ComponentImport
|
||||||
|
*/
|
||||||
|
const modules = import.meta.glob("../../pages/**/*.vue");
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Step 2
|
||||||
|
* Convert file path to router path
|
||||||
|
* @param file - File path
|
||||||
|
* @returns Router path
|
||||||
|
*/
|
||||||
|
function pathFromFile(file: string): string {
|
||||||
|
let path = file.replace(/^\..\/..\/pages|\.vue$/g, "");
|
||||||
|
path = path.replace(/\[\.\.\.(.+?)\]/g, ":$1(.*)*"); // [...slug] → :slug(.*)*
|
||||||
|
path = path.replace(/\[(.+?)\]/g, ":$1"); // [id] → :id
|
||||||
|
path = path.replace(/\/index$/, "/"); // /user/index -> /user/
|
||||||
|
path = path.replace(/^\/index$/, "/"); // /index -> /
|
||||||
|
return path;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Step 3
|
||||||
|
* Create router routes from modules
|
||||||
|
* @returns RouteRecordRaw[]
|
||||||
|
*/
|
||||||
|
const routes: RouteRecordRaw[] = Object.keys(modules).map((file) => ({
|
||||||
|
path: pathFromFile(file),
|
||||||
|
component: modules[file] as ComponentImport,
|
||||||
|
}));
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Step 4
|
||||||
|
* Auto regist 404 route (if /404.vue exists)
|
||||||
|
*/
|
||||||
|
const notFound = Object.keys(modules).find((f) => f.includes("/404.vue"));
|
||||||
|
if (notFound) {
|
||||||
|
routes.push({
|
||||||
|
path: "/:pathMatch(.*)*",
|
||||||
|
component: modules[notFound] as ComponentImport,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Step 5
|
||||||
|
* Create router instance
|
||||||
|
* @returns Router instance
|
||||||
|
*/
|
||||||
|
const router = createRouter({
|
||||||
|
history: createWebHistory(),
|
||||||
|
routes,
|
||||||
|
});
|
||||||
|
|
||||||
|
export default router;
|
||||||
96
src/pages/index.vue
Normal file
96
src/pages/index.vue
Normal file
@@ -0,0 +1,96 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import { ref } from "vue";
|
||||||
|
|
||||||
|
const nodes = ref([
|
||||||
|
{
|
||||||
|
id: 1,
|
||||||
|
status: "down",
|
||||||
|
name: "Beta Node 7",
|
||||||
|
location: "US",
|
||||||
|
sponsor: "Blue",
|
||||||
|
uptime: "99.8%",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 2,
|
||||||
|
status: "up",
|
||||||
|
name: "Alpha Node 3",
|
||||||
|
location: "UK",
|
||||||
|
sponsor: "Jason",
|
||||||
|
uptime: "98.5%",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 3,
|
||||||
|
status: "up",
|
||||||
|
name: "Gamma Node 2",
|
||||||
|
location: "JP",
|
||||||
|
sponsor: "Whatever Foundation",
|
||||||
|
uptime: "97.2%",
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<section class="pt-4 px-4">
|
||||||
|
<div>
|
||||||
|
<h1 class="text-3xl font-bold">Public Node Directory</h1>
|
||||||
|
<p class="opacity-50 mt-2">
|
||||||
|
Explore and contribute to our community-maintained list of public nodes. Your
|
||||||
|
<br />
|
||||||
|
participation ensures transparency and a robust network.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<div class="grid grid-cols-3 grid-rows-1 mt-4 w-full gap-4">
|
||||||
|
<div class="card shadow-[0_0_2px_0_var(--color-neutral-content)] p-4 bg-base-100">
|
||||||
|
<div class="stat-title">Total Nodes</div>
|
||||||
|
<div class="stat-value text-neutral-content">138</div>
|
||||||
|
<div class="stat-desc text-success">+1.2% this week</div>
|
||||||
|
</div>
|
||||||
|
<div class="card shadow-[0_0_2px_0_var(--color-neutral-content)] p-4 bg-base-100">
|
||||||
|
<div class="stat-title">Active Nodes</div>
|
||||||
|
<div class="stat-value text-neutral-content">72</div>
|
||||||
|
<div class="stat-desc text-success">+0.5% this week</div>
|
||||||
|
</div>
|
||||||
|
<div class="card shadow-[0_0_2px_0_var(--color-neutral-content)] p-4 bg-base-100">
|
||||||
|
<div class="stat-title">Total Sponsors</div>
|
||||||
|
<div class="stat-value text-neutral-content">24</div>
|
||||||
|
<div class="stat-desc text-success">+3.1% this week</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="overflow-x-auto rounded-box border border-base-content/5 bg-base-100 mt-4">
|
||||||
|
<table class="table">
|
||||||
|
<!-- head -->
|
||||||
|
<thead class="bg-base-300">
|
||||||
|
<tr>
|
||||||
|
<th>STATUS</th>
|
||||||
|
<th>NODE NAME</th>
|
||||||
|
<th>LOCATION</th>
|
||||||
|
<th>SPONSOR</th>
|
||||||
|
<th>UPTIME</th>
|
||||||
|
<th>ACTIONS</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
<tr v-for="node in nodes" :key="node.id">
|
||||||
|
<td>
|
||||||
|
<div class="inline-grid *:[grid-area:1/1] -translate-y-0.25" v-if="node.status === 'up'">
|
||||||
|
<div class="status status-success animate-ping"></div>
|
||||||
|
<div class="status status-success"></div>
|
||||||
|
</div>
|
||||||
|
<div class="status status-error -translate-y-0.25" v-else></div>
|
||||||
|
<span class="ml-2">{{ node.status == "up" ? "Online" : "Offline" }}</span>
|
||||||
|
</td>
|
||||||
|
<td>{{ node.name }}</td>
|
||||||
|
<td>{{ node.location }}</td>
|
||||||
|
<td>{{ node.sponsor }}</td>
|
||||||
|
<td>{{ node.uptime }}</td>
|
||||||
|
<td>
|
||||||
|
<button class="btn aspect-square">
|
||||||
|
<i class="icon-[octicon--info-24] size-5 -mx-2.5" />
|
||||||
|
</button>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
</template>
|
||||||
@@ -1,25 +0,0 @@
|
|||||||
import { createRouter, createWebHistory } from 'vue-router'
|
|
||||||
import HomeView from '../views/HomeView.vue'
|
|
||||||
|
|
||||||
const router = createRouter({
|
|
||||||
history: createWebHistory(import.meta.env.BASE_URL),
|
|
||||||
routes: [
|
|
||||||
{
|
|
||||||
path: '/',
|
|
||||||
name: 'home',
|
|
||||||
component: HomeView,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
path: '/monitor',
|
|
||||||
name: 'monitor',
|
|
||||||
component: () => import('../views/MonitorView.vue'),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
path: '/submit',
|
|
||||||
name: 'submit',
|
|
||||||
component: () => import('../views/SubmitView.vue'),
|
|
||||||
},
|
|
||||||
],
|
|
||||||
})
|
|
||||||
|
|
||||||
export default router
|
|
||||||
@@ -1,15 +0,0 @@
|
|||||||
<template>
|
|
||||||
<div class="about">
|
|
||||||
<h1>This is an about page</h1>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<style>
|
|
||||||
@media (min-width: 1024px) {
|
|
||||||
.about {
|
|
||||||
min-height: 100vh;
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
@@ -1,11 +0,0 @@
|
|||||||
<template>
|
|
||||||
<section style="padding:2rem; text-align:center;">
|
|
||||||
<h2>欢迎来到 EasyTierMC Uptime</h2>
|
|
||||||
<p>使用上方导航查看节点监控或提交新的节点。</p>
|
|
||||||
<Map />
|
|
||||||
</section>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script setup lang="ts">
|
|
||||||
import Map from '@/components/dashboard/Map.vue';
|
|
||||||
</script>
|
|
||||||
@@ -1,10 +0,0 @@
|
|||||||
<template>
|
|
||||||
<div>
|
|
||||||
<h2>节点监控</h2>
|
|
||||||
<p>这里是节点监控页面。</p>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script setup lang="ts">
|
|
||||||
// 可根据需要添加逻辑
|
|
||||||
</script>
|
|
||||||
@@ -1,10 +0,0 @@
|
|||||||
<template>
|
|
||||||
<div>
|
|
||||||
<h2>提交节点</h2>
|
|
||||||
<p>这里是提交节点页面。</p>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script setup lang="ts">
|
|
||||||
// 可根据需要添加逻辑
|
|
||||||
</script>
|
|
||||||
@@ -1,12 +1,12 @@
|
|||||||
{
|
{
|
||||||
"extends": "@vue/tsconfig/tsconfig.dom.json",
|
"extends": "@vue/tsconfig/tsconfig.dom.json",
|
||||||
"include": ["env.d.ts", "src/**/*", "src/**/*.vue"],
|
"include": ["env.d.ts", "src/**/*", "src/**/*.vue"],
|
||||||
"exclude": ["src/**/__tests__/*"],
|
"exclude": ["src/**/__tests__/*"],
|
||||||
"compilerOptions": {
|
"compilerOptions": {
|
||||||
"tsBuildInfoFile": "./node_modules/.tmp/tsconfig.app.tsbuildinfo",
|
"tsBuildInfoFile": "./node_modules/.tmp/tsconfig.app.tsbuildinfo",
|
||||||
|
|
||||||
"paths": {
|
"paths": {
|
||||||
"@/*": ["./src/*"]
|
"@/*": ["./src/*"]
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,11 +1,11 @@
|
|||||||
{
|
{
|
||||||
"files": [],
|
"files": [],
|
||||||
"references": [
|
"references": [
|
||||||
{
|
{
|
||||||
"path": "./tsconfig.node.json"
|
"path": "./tsconfig.node.json"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"path": "./tsconfig.app.json"
|
"path": "./tsconfig.app.json"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,19 +1,19 @@
|
|||||||
{
|
{
|
||||||
"extends": "@tsconfig/node22/tsconfig.json",
|
"extends": "@tsconfig/node22/tsconfig.json",
|
||||||
"include": [
|
"include": [
|
||||||
"vite.config.*",
|
"vite.config.*",
|
||||||
"vitest.config.*",
|
"vitest.config.*",
|
||||||
"cypress.config.*",
|
"cypress.config.*",
|
||||||
"nightwatch.conf.*",
|
"nightwatch.conf.*",
|
||||||
"playwright.config.*",
|
"playwright.config.*",
|
||||||
"eslint.config.*"
|
"eslint.config.*"
|
||||||
],
|
],
|
||||||
"compilerOptions": {
|
"compilerOptions": {
|
||||||
"noEmit": true,
|
"noEmit": true,
|
||||||
"tsBuildInfoFile": "./node_modules/.tmp/tsconfig.node.tsbuildinfo",
|
"tsBuildInfoFile": "./node_modules/.tmp/tsconfig.node.tsbuildinfo",
|
||||||
|
|
||||||
"module": "ESNext",
|
"module": "ESNext",
|
||||||
"moduleResolution": "Bundler",
|
"moduleResolution": "Bundler",
|
||||||
"types": ["node"]
|
"types": ["node"]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,20 +1,16 @@
|
|||||||
import { fileURLToPath, URL } from 'node:url'
|
import { fileURLToPath, URL } from "node:url";
|
||||||
|
|
||||||
import { defineConfig } from 'vite'
|
import { defineConfig } from "vite";
|
||||||
import vue from '@vitejs/plugin-vue'
|
import vue from "@vitejs/plugin-vue";
|
||||||
import vueDevTools from 'vite-plugin-vue-devtools'
|
import vueDevTools from "vite-plugin-vue-devtools";
|
||||||
import tailwindcss from '@tailwindcss/vite'
|
import tailwindcss from "@tailwindcss/vite";
|
||||||
|
|
||||||
// https://vite.dev/config/
|
// https://vite.dev/config/
|
||||||
export default defineConfig({
|
export default defineConfig({
|
||||||
plugins: [
|
plugins: [vue(), vueDevTools(), tailwindcss()],
|
||||||
vue(),
|
resolve: {
|
||||||
vueDevTools(),
|
alias: {
|
||||||
tailwindcss()
|
"@": fileURLToPath(new URL("./src", import.meta.url)),
|
||||||
],
|
},
|
||||||
resolve: {
|
|
||||||
alias: {
|
|
||||||
'@': fileURLToPath(new URL('./src', import.meta.url))
|
|
||||||
},
|
},
|
||||||
},
|
});
|
||||||
})
|
|
||||||
|
|||||||
Reference in New Issue
Block a user