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
|
||||
__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.
|
||||
|
||||
@@ -32,11 +32,17 @@ pnpm install
|
||||
### Compile and Hot-Reload for Development
|
||||
|
||||
```sh
|
||||
pnpm run dev
|
||||
pnpm dev
|
||||
```
|
||||
|
||||
### Type-Check, Compile and Minify for Production
|
||||
|
||||
```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>
|
||||
<html lang="">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<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">
|
||||
<title>Vite App</title>
|
||||
</head>
|
||||
<body>
|
||||
<div id="app"></div>
|
||||
<script type="module" src="/src/main.ts"></script>
|
||||
</body>
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<link rel="icon" href="/favicon.ico" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>EasyTierMC Uptime</title>
|
||||
</head>
|
||||
<body class="bg-base-200 w-screen h-screen">
|
||||
<div id="app"></div>
|
||||
<script type="module" src="/src/main.ts"></script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
74
package.json
74
package.json
@@ -1,38 +1,40 @@
|
||||
{
|
||||
"name": "neoeasytierweb",
|
||||
"version": "0.0.0",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
"packageManager": "pnpm@10.18.0",
|
||||
"engines": {
|
||||
"node": "^20.19.0 || >=22.12.0"
|
||||
},
|
||||
"scripts": {
|
||||
"dev": "vite",
|
||||
"build": "run-p type-check \"build-only {@}\" --",
|
||||
"preview": "vite preview",
|
||||
"build-only": "vite build",
|
||||
"type-check": "vue-tsc --build"
|
||||
},
|
||||
"dependencies": {
|
||||
"echarts": "^6.0.0",
|
||||
"neoeasytierweb": "link:",
|
||||
"tailwindcss": "^4.1.17",
|
||||
"vue": "^3.5.22",
|
||||
"vue-echarts": "^8.0.1",
|
||||
"vue-router": "^4.6.3"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@tailwindcss/vite": "^4.1.17",
|
||||
"@tsconfig/node22": "^22.0.2",
|
||||
"@types/node": "^22.18.11",
|
||||
"@vitejs/plugin-vue": "^6.0.1",
|
||||
"@vue/tsconfig": "^0.8.1",
|
||||
"daisyui": "^5.4.7",
|
||||
"npm-run-all2": "^8.0.4",
|
||||
"typescript": "~5.9.0",
|
||||
"vite": "^7.1.11",
|
||||
"vite-plugin-vue-devtools": "^8.0.3",
|
||||
"vue-tsc": "^3.1.1"
|
||||
}
|
||||
"name": "neouptime-web",
|
||||
"version": "0.0.0",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
"engines": {
|
||||
"node": "^20.19.0 || >=22.12.0"
|
||||
},
|
||||
"scripts": {
|
||||
"dev": "vite",
|
||||
"build": "run-p type-check \"build-only {@}\" --",
|
||||
"preview": "vite preview",
|
||||
"build-only": "vite build",
|
||||
"type-check": "vue-tsc --build",
|
||||
"lint": "eslint . --fix --cache"
|
||||
},
|
||||
"dependencies": {
|
||||
"@iconify/tailwind4": "^1.1.0",
|
||||
"@tailwindcss/vite": "^4.1.17",
|
||||
"daisyui": "^5.5.4",
|
||||
"pinia": "^3.0.4",
|
||||
"tailwindcss": "^4.1.17",
|
||||
"vue": "^3.5.24",
|
||||
"vue-router": "^4.6.3"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@iconify-json/octicon": "^1.2.19",
|
||||
"@tsconfig/node22": "^22.0.3",
|
||||
"@types/node": "^22.19.1",
|
||||
"@vitejs/plugin-vue": "^6.0.1",
|
||||
"@vue/eslint-config-typescript": "^14.6.0",
|
||||
"@vue/tsconfig": "^0.8.1",
|
||||
"jiti": "^2.6.1",
|
||||
"npm-run-all2": "^8.0.4",
|
||||
"typescript": "~5.9.3",
|
||||
"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">
|
||||
import { RouterLink, RouterView } from 'vue-router'
|
||||
const year = new Date().getFullYear()
|
||||
import { Layout } from "./layout";
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="layout">
|
||||
<header class="navbar bg-base-100 shadow-md">
|
||||
<div class="navbar-start">
|
||||
<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>
|
||||
<Layout>
|
||||
<RouterView />
|
||||
</Layout>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.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>
|
||||
<style scoped></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 router from './router'
|
||||
import App from "./App.vue";
|
||||
import router from "./modules/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",
|
||||
"include": ["env.d.ts", "src/**/*", "src/**/*.vue"],
|
||||
"exclude": ["src/**/__tests__/*"],
|
||||
"compilerOptions": {
|
||||
"tsBuildInfoFile": "./node_modules/.tmp/tsconfig.app.tsbuildinfo",
|
||||
"extends": "@vue/tsconfig/tsconfig.dom.json",
|
||||
"include": ["env.d.ts", "src/**/*", "src/**/*.vue"],
|
||||
"exclude": ["src/**/__tests__/*"],
|
||||
"compilerOptions": {
|
||||
"tsBuildInfoFile": "./node_modules/.tmp/tsconfig.app.tsbuildinfo",
|
||||
|
||||
"paths": {
|
||||
"@/*": ["./src/*"]
|
||||
"paths": {
|
||||
"@/*": ["./src/*"]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
{
|
||||
"files": [],
|
||||
"references": [
|
||||
{
|
||||
"path": "./tsconfig.node.json"
|
||||
},
|
||||
{
|
||||
"path": "./tsconfig.app.json"
|
||||
}
|
||||
]
|
||||
"files": [],
|
||||
"references": [
|
||||
{
|
||||
"path": "./tsconfig.node.json"
|
||||
},
|
||||
{
|
||||
"path": "./tsconfig.app.json"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
@@ -1,19 +1,19 @@
|
||||
{
|
||||
"extends": "@tsconfig/node22/tsconfig.json",
|
||||
"include": [
|
||||
"vite.config.*",
|
||||
"vitest.config.*",
|
||||
"cypress.config.*",
|
||||
"nightwatch.conf.*",
|
||||
"playwright.config.*",
|
||||
"eslint.config.*"
|
||||
],
|
||||
"compilerOptions": {
|
||||
"noEmit": true,
|
||||
"tsBuildInfoFile": "./node_modules/.tmp/tsconfig.node.tsbuildinfo",
|
||||
"extends": "@tsconfig/node22/tsconfig.json",
|
||||
"include": [
|
||||
"vite.config.*",
|
||||
"vitest.config.*",
|
||||
"cypress.config.*",
|
||||
"nightwatch.conf.*",
|
||||
"playwright.config.*",
|
||||
"eslint.config.*"
|
||||
],
|
||||
"compilerOptions": {
|
||||
"noEmit": true,
|
||||
"tsBuildInfoFile": "./node_modules/.tmp/tsconfig.node.tsbuildinfo",
|
||||
|
||||
"module": "ESNext",
|
||||
"moduleResolution": "Bundler",
|
||||
"types": ["node"]
|
||||
}
|
||||
"module": "ESNext",
|
||||
"moduleResolution": "Bundler",
|
||||
"types": ["node"]
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,20 +1,16 @@
|
||||
import { fileURLToPath, URL } from 'node:url'
|
||||
import { fileURLToPath, URL } from "node:url";
|
||||
|
||||
import { defineConfig } from 'vite'
|
||||
import vue from '@vitejs/plugin-vue'
|
||||
import vueDevTools from 'vite-plugin-vue-devtools'
|
||||
import tailwindcss from '@tailwindcss/vite'
|
||||
import { defineConfig } from "vite";
|
||||
import vue from "@vitejs/plugin-vue";
|
||||
import vueDevTools from "vite-plugin-vue-devtools";
|
||||
import tailwindcss from "@tailwindcss/vite";
|
||||
|
||||
// https://vite.dev/config/
|
||||
export default defineConfig({
|
||||
plugins: [
|
||||
vue(),
|
||||
vueDevTools(),
|
||||
tailwindcss()
|
||||
],
|
||||
resolve: {
|
||||
alias: {
|
||||
'@': fileURLToPath(new URL('./src', import.meta.url))
|
||||
plugins: [vue(), vueDevTools(), tailwindcss()],
|
||||
resolve: {
|
||||
alias: {
|
||||
"@": fileURLToPath(new URL("./src", import.meta.url)),
|
||||
},
|
||||
},
|
||||
},
|
||||
})
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user