完成v1.1.0
This commit is contained in:
1250
website/package-lock.json
generated
Normal file
1250
website/package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
33
website/package.json
Normal file
33
website/package.json
Normal file
@@ -0,0 +1,33 @@
|
|||||||
|
{
|
||||||
|
"name": "futureoss-website",
|
||||||
|
"version": "1.0.0",
|
||||||
|
"description": "FutureOSS官方网站 - 企业化简约风格",
|
||||||
|
"main": "src/server.js",
|
||||||
|
"scripts": {
|
||||||
|
"start": "node src/server.js",
|
||||||
|
"dev": "nodemon src/server.js",
|
||||||
|
"build": "echo 'Build step not required for this project'"
|
||||||
|
},
|
||||||
|
"keywords": [
|
||||||
|
"futureoss",
|
||||||
|
"plugin",
|
||||||
|
"framework",
|
||||||
|
"website"
|
||||||
|
],
|
||||||
|
"author": "FutureOSS Team",
|
||||||
|
"license": "Apache-2.0",
|
||||||
|
"dependencies": {
|
||||||
|
"compression": "^1.7.4",
|
||||||
|
"cors": "^2.8.5",
|
||||||
|
"ejs": "^5.0.2",
|
||||||
|
"express": "^4.18.2",
|
||||||
|
"express-ejs-layouts": "^2.5.1",
|
||||||
|
"morgan": "^1.10.0"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"nodemon": "^3.0.1"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=16.0.0"
|
||||||
|
}
|
||||||
|
}
|
||||||
555
website/public/css/animations.css
Normal file
555
website/public/css/animations.css
Normal file
@@ -0,0 +1,555 @@
|
|||||||
|
/* 动画样式文件 */
|
||||||
|
|
||||||
|
/* 淡入动画 */
|
||||||
|
@keyframes fadeIn {
|
||||||
|
from {
|
||||||
|
opacity: 0;
|
||||||
|
transform: translateY(20px);
|
||||||
|
}
|
||||||
|
to {
|
||||||
|
opacity: 1;
|
||||||
|
transform: translateY(0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes fadeInUp {
|
||||||
|
from {
|
||||||
|
opacity: 0;
|
||||||
|
transform: translateY(40px);
|
||||||
|
}
|
||||||
|
to {
|
||||||
|
opacity: 1;
|
||||||
|
transform: translateY(0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes fadeInDown {
|
||||||
|
from {
|
||||||
|
opacity: 0;
|
||||||
|
transform: translateY(-40px);
|
||||||
|
}
|
||||||
|
to {
|
||||||
|
opacity: 1;
|
||||||
|
transform: translateY(0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes fadeInLeft {
|
||||||
|
from {
|
||||||
|
opacity: 0;
|
||||||
|
transform: translateX(-40px);
|
||||||
|
}
|
||||||
|
to {
|
||||||
|
opacity: 1;
|
||||||
|
transform: translateX(0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes fadeInRight {
|
||||||
|
from {
|
||||||
|
opacity: 0;
|
||||||
|
transform: translateX(40px);
|
||||||
|
}
|
||||||
|
to {
|
||||||
|
opacity: 1;
|
||||||
|
transform: translateX(0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 缩放动画 */
|
||||||
|
@keyframes scaleIn {
|
||||||
|
from {
|
||||||
|
opacity: 0;
|
||||||
|
transform: scale(0.9);
|
||||||
|
}
|
||||||
|
to {
|
||||||
|
opacity: 1;
|
||||||
|
transform: scale(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes scaleOut {
|
||||||
|
from {
|
||||||
|
opacity: 1;
|
||||||
|
transform: scale(1);
|
||||||
|
}
|
||||||
|
to {
|
||||||
|
opacity: 0;
|
||||||
|
transform: scale(0.9);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 旋转动画 */
|
||||||
|
@keyframes spin {
|
||||||
|
from {
|
||||||
|
transform: rotate(0deg);
|
||||||
|
}
|
||||||
|
to {
|
||||||
|
transform: rotate(360deg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes spinReverse {
|
||||||
|
from {
|
||||||
|
transform: rotate(0deg);
|
||||||
|
}
|
||||||
|
to {
|
||||||
|
transform: rotate(-360deg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 脉动动画 */
|
||||||
|
@keyframes pulse {
|
||||||
|
0%, 100% {
|
||||||
|
opacity: 1;
|
||||||
|
transform: scale(1);
|
||||||
|
}
|
||||||
|
50% {
|
||||||
|
opacity: 0.8;
|
||||||
|
transform: scale(1.05);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes pulseSoft {
|
||||||
|
0%, 100% {
|
||||||
|
transform: scale(1);
|
||||||
|
}
|
||||||
|
50% {
|
||||||
|
transform: scale(1.02);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 滑动动画 */
|
||||||
|
@keyframes slideInUp {
|
||||||
|
from {
|
||||||
|
transform: translateY(100%);
|
||||||
|
opacity: 0;
|
||||||
|
}
|
||||||
|
to {
|
||||||
|
transform: translateY(0);
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes slideInDown {
|
||||||
|
from {
|
||||||
|
transform: translateY(-100%);
|
||||||
|
opacity: 0;
|
||||||
|
}
|
||||||
|
to {
|
||||||
|
transform: translateY(0);
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes slideInLeft {
|
||||||
|
from {
|
||||||
|
transform: translateX(-100%);
|
||||||
|
opacity: 0;
|
||||||
|
}
|
||||||
|
to {
|
||||||
|
transform: translateX(0);
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes slideInRight {
|
||||||
|
from {
|
||||||
|
transform: translateX(100%);
|
||||||
|
opacity: 0;
|
||||||
|
}
|
||||||
|
to {
|
||||||
|
transform: translateX(0);
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 弹跳动画 */
|
||||||
|
@keyframes bounce {
|
||||||
|
0%, 20%, 50%, 80%, 100% {
|
||||||
|
transform: translateY(0);
|
||||||
|
}
|
||||||
|
40% {
|
||||||
|
transform: translateY(-20px);
|
||||||
|
}
|
||||||
|
60% {
|
||||||
|
transform: translateY(-10px);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 闪烁动画 */
|
||||||
|
@keyframes blink {
|
||||||
|
0%, 100% {
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
50% {
|
||||||
|
opacity: 0.5;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 波浪动画 */
|
||||||
|
@keyframes wave {
|
||||||
|
0% {
|
||||||
|
transform: translateX(0);
|
||||||
|
}
|
||||||
|
50% {
|
||||||
|
transform: translateX(-10px);
|
||||||
|
}
|
||||||
|
100% {
|
||||||
|
transform: translateX(0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 动画工具类 */
|
||||||
|
.animate-fade-in {
|
||||||
|
animation: fadeIn var(--transition-normal);
|
||||||
|
}
|
||||||
|
|
||||||
|
.animate-fade-in-up {
|
||||||
|
animation: fadeInUp var(--transition-normal);
|
||||||
|
}
|
||||||
|
|
||||||
|
.animate-fade-in-down {
|
||||||
|
animation: fadeInDown var(--transition-normal);
|
||||||
|
}
|
||||||
|
|
||||||
|
.animate-fade-in-left {
|
||||||
|
animation: fadeInLeft var(--transition-normal);
|
||||||
|
}
|
||||||
|
|
||||||
|
.animate-fade-in-right {
|
||||||
|
animation: fadeInRight var(--transition-normal);
|
||||||
|
}
|
||||||
|
|
||||||
|
.animate-scale-in {
|
||||||
|
animation: scaleIn var(--transition-normal);
|
||||||
|
}
|
||||||
|
|
||||||
|
.animate-scale-out {
|
||||||
|
animation: scaleOut var(--transition-normal);
|
||||||
|
}
|
||||||
|
|
||||||
|
.animate-spin {
|
||||||
|
animation: spin 1s linear infinite;
|
||||||
|
}
|
||||||
|
|
||||||
|
.animate-spin-reverse {
|
||||||
|
animation: spinReverse 1s linear infinite;
|
||||||
|
}
|
||||||
|
|
||||||
|
.animate-pulse {
|
||||||
|
animation: pulse 2s ease-in-out infinite;
|
||||||
|
}
|
||||||
|
|
||||||
|
.animate-pulse-soft {
|
||||||
|
animation: pulseSoft 3s ease-in-out infinite;
|
||||||
|
}
|
||||||
|
|
||||||
|
.animate-slide-in-up {
|
||||||
|
animation: slideInUp var(--transition-normal);
|
||||||
|
}
|
||||||
|
|
||||||
|
.animate-slide-in-down {
|
||||||
|
animation: slideInDown var(--transition-normal);
|
||||||
|
}
|
||||||
|
|
||||||
|
.animate-slide-in-left {
|
||||||
|
animation: slideInLeft var(--transition-normal);
|
||||||
|
}
|
||||||
|
|
||||||
|
.animate-slide-in-right {
|
||||||
|
animation: slideInRight var(--transition-normal);
|
||||||
|
}
|
||||||
|
|
||||||
|
.animate-bounce {
|
||||||
|
animation: bounce 2s infinite;
|
||||||
|
}
|
||||||
|
|
||||||
|
.animate-blink {
|
||||||
|
animation: blink 1s infinite;
|
||||||
|
}
|
||||||
|
|
||||||
|
.animate-wave {
|
||||||
|
animation: wave 2s ease-in-out infinite;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 延迟动画 */
|
||||||
|
.delay-100 {
|
||||||
|
animation-delay: 100ms;
|
||||||
|
}
|
||||||
|
|
||||||
|
.delay-200 {
|
||||||
|
animation-delay: 200ms;
|
||||||
|
}
|
||||||
|
|
||||||
|
.delay-300 {
|
||||||
|
animation-delay: 300ms;
|
||||||
|
}
|
||||||
|
|
||||||
|
.delay-400 {
|
||||||
|
animation-delay: 400ms;
|
||||||
|
}
|
||||||
|
|
||||||
|
.delay-500 {
|
||||||
|
animation-delay: 500ms;
|
||||||
|
}
|
||||||
|
|
||||||
|
.delay-600 {
|
||||||
|
animation-delay: 600ms;
|
||||||
|
}
|
||||||
|
|
||||||
|
.delay-700 {
|
||||||
|
animation-delay: 700ms;
|
||||||
|
}
|
||||||
|
|
||||||
|
.delay-800 {
|
||||||
|
animation-delay: 800ms;
|
||||||
|
}
|
||||||
|
|
||||||
|
.delay-900 {
|
||||||
|
animation-delay: 900ms;
|
||||||
|
}
|
||||||
|
|
||||||
|
.delay-1000 {
|
||||||
|
animation-delay: 1000ms;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 动画持续时间 */
|
||||||
|
.duration-fast {
|
||||||
|
animation-duration: var(--transition-fast);
|
||||||
|
}
|
||||||
|
|
||||||
|
.duration-normal {
|
||||||
|
animation-duration: var(--transition-normal);
|
||||||
|
}
|
||||||
|
|
||||||
|
.duration-slow {
|
||||||
|
animation-duration: var(--transition-slow);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 页面切换动画 */
|
||||||
|
.page-transition {
|
||||||
|
animation: fadeIn var(--transition-normal);
|
||||||
|
}
|
||||||
|
|
||||||
|
.page-transition.active {
|
||||||
|
animation: fadeOut var(--transition-normal);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 悬停动画 */
|
||||||
|
.hover-lift {
|
||||||
|
transition: transform var(--transition-normal);
|
||||||
|
}
|
||||||
|
|
||||||
|
.hover-lift:hover {
|
||||||
|
transform: translateY(-4px);
|
||||||
|
}
|
||||||
|
|
||||||
|
.hover-scale {
|
||||||
|
transition: transform var(--transition-normal);
|
||||||
|
}
|
||||||
|
|
||||||
|
.hover-scale:hover {
|
||||||
|
transform: scale(1.05);
|
||||||
|
}
|
||||||
|
|
||||||
|
.hover-rotate {
|
||||||
|
transition: transform var(--transition-normal);
|
||||||
|
}
|
||||||
|
|
||||||
|
.hover-rotate:hover {
|
||||||
|
transform: rotate(5deg);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 加载动画 */
|
||||||
|
.loading-spinner {
|
||||||
|
width: 40px;
|
||||||
|
height: 40px;
|
||||||
|
border: 3px solid var(--border-color);
|
||||||
|
border-top-color: var(--primary-color);
|
||||||
|
border-radius: 50%;
|
||||||
|
animation: spin 1s linear infinite;
|
||||||
|
}
|
||||||
|
|
||||||
|
.loading-dots {
|
||||||
|
display: flex;
|
||||||
|
gap: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.loading-dots span {
|
||||||
|
width: 8px;
|
||||||
|
height: 8px;
|
||||||
|
background-color: var(--primary-color);
|
||||||
|
border-radius: 50%;
|
||||||
|
animation: pulse 1.4s ease-in-out infinite;
|
||||||
|
}
|
||||||
|
|
||||||
|
.loading-dots span:nth-child(2) {
|
||||||
|
animation-delay: 0.2s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.loading-dots span:nth-child(3) {
|
||||||
|
animation-delay: 0.4s;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 进度条动画 */
|
||||||
|
@keyframes progress {
|
||||||
|
from {
|
||||||
|
width: 0%;
|
||||||
|
}
|
||||||
|
to {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.progress-bar {
|
||||||
|
animation: progress 2s ease-in-out infinite;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 打字机效果 */
|
||||||
|
@keyframes typing {
|
||||||
|
from {
|
||||||
|
width: 0;
|
||||||
|
}
|
||||||
|
to {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes blink-caret {
|
||||||
|
from, to {
|
||||||
|
border-color: transparent;
|
||||||
|
}
|
||||||
|
50% {
|
||||||
|
border-color: var(--primary-color);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.typewriter {
|
||||||
|
overflow: hidden;
|
||||||
|
border-right: 2px solid var(--primary-color);
|
||||||
|
white-space: nowrap;
|
||||||
|
animation: typing 3.5s steps(40, end), blink-caret 0.75s step-end infinite;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 加载动画样式 */
|
||||||
|
.loading-overlay {
|
||||||
|
position: fixed;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
background-color: var(--bg-primary);
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
z-index: 9999;
|
||||||
|
transition: opacity 0.5s ease, visibility 0.5s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.loading-overlay.hidden {
|
||||||
|
opacity: 0;
|
||||||
|
visibility: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.loading-spinner {
|
||||||
|
text-align: center;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.spinner {
|
||||||
|
width: 60px;
|
||||||
|
height: 60px;
|
||||||
|
border: 4px solid rgba(30, 111, 187, 0.1);
|
||||||
|
border-top-color: var(--primary-color);
|
||||||
|
border-radius: 50%;
|
||||||
|
animation: spin 1s linear infinite;
|
||||||
|
margin-bottom: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.loading-text {
|
||||||
|
color: var(--text-secondary);
|
||||||
|
font-size: var(--font-size-lg);
|
||||||
|
font-weight: 500;
|
||||||
|
text-align: center;
|
||||||
|
animation: none !important; /* 确保文字不应用任何动画 */
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 骨架屏加载动画 */
|
||||||
|
.skeleton {
|
||||||
|
background: linear-gradient(90deg, #f0f0f0 25%, #e0e0e0 50%, #f0f0f0 75%);
|
||||||
|
background-size: 200% 100%;
|
||||||
|
animation: skeleton-loading 1.5s infinite;
|
||||||
|
border-radius: var(--radius-md);
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes skeleton-loading {
|
||||||
|
0% {
|
||||||
|
background-position: 200% 0;
|
||||||
|
}
|
||||||
|
100% {
|
||||||
|
background-position: -200% 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 渐进式加载动画 */
|
||||||
|
.fade-in-load {
|
||||||
|
opacity: 0;
|
||||||
|
transform: translateY(20px);
|
||||||
|
animation: fadeInUp 0.6s ease forwards;
|
||||||
|
}
|
||||||
|
|
||||||
|
.fade-in-load.delay-1 {
|
||||||
|
animation-delay: 0.1s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.fade-in-load.delay-2 {
|
||||||
|
animation-delay: 0.2s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.fade-in-load.delay-3 {
|
||||||
|
animation-delay: 0.3s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.fade-in-load.delay-4 {
|
||||||
|
animation-delay: 0.4s;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 性能优化:减少重绘 */
|
||||||
|
.will-change-transform {
|
||||||
|
will-change: transform;
|
||||||
|
}
|
||||||
|
|
||||||
|
.will-change-opacity {
|
||||||
|
will-change: opacity;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 图片懒加载占位符 */
|
||||||
|
.lazy-image {
|
||||||
|
background-color: var(--bg-secondary);
|
||||||
|
position: relative;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.lazy-image::after {
|
||||||
|
content: '';
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
background: linear-gradient(90deg, transparent, rgba(255,255,255,0.4), transparent);
|
||||||
|
animation: shimmer 2s infinite;
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes shimmer {
|
||||||
|
0% {
|
||||||
|
transform: translateX(-100%);
|
||||||
|
}
|
||||||
|
100% {
|
||||||
|
transform: translateX(100%);
|
||||||
|
}
|
||||||
|
}
|
||||||
283
website/public/css/components.css
Normal file
283
website/public/css/components.css
Normal file
@@ -0,0 +1,283 @@
|
|||||||
|
/* 组件样式文件 */
|
||||||
|
|
||||||
|
/* 导航栏样式 */
|
||||||
|
.navbar {
|
||||||
|
background-color: var(--bg-primary);
|
||||||
|
border-bottom: 1px solid var(--border-color);
|
||||||
|
padding: var(--spacing-md) 0;
|
||||||
|
position: sticky;
|
||||||
|
top: 0;
|
||||||
|
z-index: 1000;
|
||||||
|
box-shadow: var(--shadow-sm);
|
||||||
|
}
|
||||||
|
|
||||||
|
.navbar-container {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
}
|
||||||
|
|
||||||
|
.navbar-brand {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: var(--spacing-sm);
|
||||||
|
text-decoration: none;
|
||||||
|
color: var(--text-primary);
|
||||||
|
font-weight: 600;
|
||||||
|
font-size: var(--font-size-lg);
|
||||||
|
}
|
||||||
|
|
||||||
|
.navbar-logo {
|
||||||
|
height: 32px;
|
||||||
|
width: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.navbar-menu {
|
||||||
|
display: flex;
|
||||||
|
gap: var(--spacing-lg);
|
||||||
|
list-style: none;
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.navbar-item {
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
.navbar-link {
|
||||||
|
color: var(--text-secondary);
|
||||||
|
text-decoration: none;
|
||||||
|
padding: var(--spacing-sm) var(--spacing-md);
|
||||||
|
border-radius: var(--radius-sm);
|
||||||
|
transition: all var(--transition-fast);
|
||||||
|
font-weight: 500;
|
||||||
|
}
|
||||||
|
|
||||||
|
.navbar-link:hover {
|
||||||
|
color: var(--primary-color);
|
||||||
|
background-color: var(--bg-secondary);
|
||||||
|
}
|
||||||
|
|
||||||
|
.navbar-link.active {
|
||||||
|
color: var(--primary-color);
|
||||||
|
background-color: rgba(30, 111, 187, 0.1);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 页脚样式 */
|
||||||
|
.footer {
|
||||||
|
background-color: var(--bg-dark);
|
||||||
|
color: white;
|
||||||
|
padding: var(--spacing-xl) 0;
|
||||||
|
margin-top: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.footer-container {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
|
||||||
|
gap: var(--spacing-xl);
|
||||||
|
}
|
||||||
|
|
||||||
|
.footer-section h3 {
|
||||||
|
color: white;
|
||||||
|
margin-bottom: var(--spacing-lg);
|
||||||
|
font-size: var(--font-size-lg);
|
||||||
|
}
|
||||||
|
|
||||||
|
.footer-links {
|
||||||
|
list-style: none;
|
||||||
|
padding: 0;
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.footer-links li {
|
||||||
|
margin-bottom: var(--spacing-sm);
|
||||||
|
}
|
||||||
|
|
||||||
|
.footer-links a {
|
||||||
|
color: rgba(255, 255, 255, 0.8);
|
||||||
|
text-decoration: none;
|
||||||
|
transition: color var(--transition-fast);
|
||||||
|
}
|
||||||
|
|
||||||
|
.footer-links a:hover {
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
.footer-bottom {
|
||||||
|
text-align: center;
|
||||||
|
padding-top: var(--spacing-xl);
|
||||||
|
margin-top: var(--spacing-xl);
|
||||||
|
border-top: 1px solid rgba(255, 255, 255, 0.1);
|
||||||
|
color: rgba(255, 255, 255, 0.6);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 卡片组件 */
|
||||||
|
.card {
|
||||||
|
background-color: var(--bg-primary);
|
||||||
|
border: 1px solid var(--border-color);
|
||||||
|
border-radius: var(--radius-lg);
|
||||||
|
padding: var(--spacing-lg);
|
||||||
|
box-shadow: var(--shadow-sm);
|
||||||
|
transition: all var(--transition-normal);
|
||||||
|
}
|
||||||
|
|
||||||
|
.card:hover {
|
||||||
|
box-shadow: var(--shadow-md);
|
||||||
|
transform: translateY(-4px);
|
||||||
|
}
|
||||||
|
|
||||||
|
.card-header {
|
||||||
|
margin-bottom: var(--spacing-md);
|
||||||
|
}
|
||||||
|
|
||||||
|
.card-title {
|
||||||
|
font-size: var(--font-size-lg);
|
||||||
|
font-weight: 600;
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.card-content {
|
||||||
|
color: var(--text-secondary);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 页面头部 */
|
||||||
|
.page-header {
|
||||||
|
background: linear-gradient(135deg, var(--primary-color), var(--secondary-color));
|
||||||
|
color: white;
|
||||||
|
padding: var(--spacing-xxl) 0;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.page-title {
|
||||||
|
font-size: var(--font-size-xxxl);
|
||||||
|
font-weight: 700;
|
||||||
|
margin-bottom: var(--spacing-md);
|
||||||
|
}
|
||||||
|
|
||||||
|
.page-subtitle {
|
||||||
|
font-size: var(--font-size-lg);
|
||||||
|
opacity: 0.9;
|
||||||
|
margin-bottom: var(--spacing-xl);
|
||||||
|
max-width: 600px;
|
||||||
|
margin-left: auto;
|
||||||
|
margin-right: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.page-actions {
|
||||||
|
display: flex;
|
||||||
|
gap: var(--spacing-md);
|
||||||
|
justify-content: center;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 特性卡片 */
|
||||||
|
.feature-card {
|
||||||
|
height: 100%;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
|
|
||||||
|
.feature-header {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: var(--spacing-md);
|
||||||
|
margin-bottom: var(--spacing-md);
|
||||||
|
}
|
||||||
|
|
||||||
|
.feature-icon {
|
||||||
|
width: 48px;
|
||||||
|
height: 48px;
|
||||||
|
background-color: rgba(30, 111, 187, 0.1);
|
||||||
|
border-radius: var(--radius-round);
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
color: var(--primary-color);
|
||||||
|
font-size: var(--font-size-xl);
|
||||||
|
}
|
||||||
|
|
||||||
|
.feature-title {
|
||||||
|
font-size: var(--font-size-lg);
|
||||||
|
font-weight: 600;
|
||||||
|
margin: 0;
|
||||||
|
flex: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.feature-badge {
|
||||||
|
background-color: var(--primary-color);
|
||||||
|
color: white;
|
||||||
|
padding: var(--spacing-xs) var(--spacing-sm);
|
||||||
|
border-radius: var(--radius-sm);
|
||||||
|
font-size: var(--font-size-xs);
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
|
||||||
|
.feature-content {
|
||||||
|
flex: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.feature-desc {
|
||||||
|
color: var(--text-secondary);
|
||||||
|
margin-bottom: var(--spacing-md);
|
||||||
|
}
|
||||||
|
|
||||||
|
.feature-list {
|
||||||
|
list-style: none;
|
||||||
|
padding: 0;
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.feature-item {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: var(--spacing-sm);
|
||||||
|
margin-bottom: var(--spacing-sm);
|
||||||
|
color: var(--text-secondary);
|
||||||
|
}
|
||||||
|
|
||||||
|
.feature-item i {
|
||||||
|
color: var(--success-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
.feature-footer {
|
||||||
|
display: flex;
|
||||||
|
gap: var(--spacing-sm);
|
||||||
|
flex-wrap: wrap;
|
||||||
|
margin-top: var(--spacing-md);
|
||||||
|
padding-top: var(--spacing-md);
|
||||||
|
border-top: 1px solid var(--border-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
.feature-tag {
|
||||||
|
background-color: var(--bg-secondary);
|
||||||
|
color: var(--text-secondary);
|
||||||
|
padding: var(--spacing-xs) var(--spacing-sm);
|
||||||
|
border-radius: var(--radius-sm);
|
||||||
|
font-size: var(--font-size-xs);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 响应式设计 */
|
||||||
|
@media (max-width: 768px) {
|
||||||
|
.navbar-menu {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.navbar-container {
|
||||||
|
flex-direction: column;
|
||||||
|
gap: var(--spacing-md);
|
||||||
|
}
|
||||||
|
|
||||||
|
.footer-container {
|
||||||
|
grid-template-columns: 1fr;
|
||||||
|
}
|
||||||
|
|
||||||
|
.page-actions {
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.page-actions .btn {
|
||||||
|
width: 100%;
|
||||||
|
max-width: 300px;
|
||||||
|
}
|
||||||
|
}
|
||||||
239
website/public/css/main.css
Normal file
239
website/public/css/main.css
Normal file
@@ -0,0 +1,239 @@
|
|||||||
|
/* 主CSS文件 - 基础样式和重置 */
|
||||||
|
* {
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
|
||||||
|
:root {
|
||||||
|
/* 颜色变量 - 企业化简约风格 */
|
||||||
|
--primary-color: #1e6fbb; /* Gitee蓝 */
|
||||||
|
--primary-dark: #155a9e;
|
||||||
|
--primary-light: #2c8ae6;
|
||||||
|
--secondary-color: #667eea;
|
||||||
|
--secondary-dark: #764ba2;
|
||||||
|
--text-primary: #333333;
|
||||||
|
--text-secondary: #666666;
|
||||||
|
--text-light: #999999;
|
||||||
|
--bg-primary: #ffffff;
|
||||||
|
--bg-secondary: #f8f9fa;
|
||||||
|
--bg-dark: #1a1a1a;
|
||||||
|
--border-color: #e0e0e0;
|
||||||
|
--success-color: #28a745;
|
||||||
|
--warning-color: #ffc107;
|
||||||
|
--danger-color: #dc3545;
|
||||||
|
--info-color: #17a2b8;
|
||||||
|
|
||||||
|
/* 间距变量 */
|
||||||
|
--spacing-xs: 0.25rem;
|
||||||
|
--spacing-sm: 0.5rem;
|
||||||
|
--spacing-md: 1rem;
|
||||||
|
--spacing-lg: 1.5rem;
|
||||||
|
--spacing-xl: 2rem;
|
||||||
|
--spacing-xxl: 3rem;
|
||||||
|
|
||||||
|
/* 字体变量 */
|
||||||
|
--font-family: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
|
||||||
|
--font-size-xs: 0.75rem;
|
||||||
|
--font-size-sm: 0.875rem;
|
||||||
|
--font-size-md: 1rem;
|
||||||
|
--font-size-lg: 1.125rem;
|
||||||
|
--font-size-xl: 1.25rem;
|
||||||
|
--font-size-xxl: 1.5rem;
|
||||||
|
--font-size-xxxl: 2rem;
|
||||||
|
|
||||||
|
/* 阴影变量 */
|
||||||
|
--shadow-sm: 0 1px 3px rgba(0, 0, 0, 0.1);
|
||||||
|
--shadow-md: 0 4px 6px rgba(0, 0, 0, 0.1);
|
||||||
|
--shadow-lg: 0 10px 25px rgba(0, 0, 0, 0.1);
|
||||||
|
--shadow-xl: 0 20px 40px rgba(0, 0, 0, 0.15);
|
||||||
|
|
||||||
|
/* 边框半径 */
|
||||||
|
--radius-sm: 4px;
|
||||||
|
--radius-md: 8px;
|
||||||
|
--radius-lg: 12px;
|
||||||
|
--radius-xl: 16px;
|
||||||
|
--radius-round: 50%;
|
||||||
|
|
||||||
|
/* 过渡动画 */
|
||||||
|
--transition-fast: 150ms ease;
|
||||||
|
--transition-normal: 300ms ease;
|
||||||
|
--transition-slow: 500ms ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
html {
|
||||||
|
font-size: 16px;
|
||||||
|
scroll-behavior: smooth;
|
||||||
|
}
|
||||||
|
|
||||||
|
body {
|
||||||
|
font-family: var(--font-family);
|
||||||
|
font-size: var(--font-size-md);
|
||||||
|
line-height: 1.6;
|
||||||
|
color: var(--text-primary);
|
||||||
|
background-color: var(--bg-primary);
|
||||||
|
overflow-x: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 容器 */
|
||||||
|
.container {
|
||||||
|
width: 100%;
|
||||||
|
max-width: 1200px;
|
||||||
|
margin: 0 auto;
|
||||||
|
padding: 0 var(--spacing-md);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 排版 */
|
||||||
|
h1, h2, h3, h4, h5, h6 {
|
||||||
|
font-weight: 600;
|
||||||
|
line-height: 1.3;
|
||||||
|
margin-bottom: var(--spacing-md);
|
||||||
|
}
|
||||||
|
|
||||||
|
h1 { font-size: var(--font-size-xxxl); }
|
||||||
|
h2 { font-size: var(--font-size-xxl); }
|
||||||
|
h3 { font-size: var(--font-size-xl); }
|
||||||
|
h4 { font-size: var(--font-size-lg); }
|
||||||
|
h5 { font-size: var(--font-size-md); }
|
||||||
|
h6 { font-size: var(--font-size-sm); }
|
||||||
|
|
||||||
|
p {
|
||||||
|
margin-bottom: var(--spacing-md);
|
||||||
|
}
|
||||||
|
|
||||||
|
a {
|
||||||
|
color: var(--primary-color);
|
||||||
|
text-decoration: none;
|
||||||
|
transition: color var(--transition-fast);
|
||||||
|
}
|
||||||
|
|
||||||
|
a:hover {
|
||||||
|
color: var(--primary-dark);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 按钮 */
|
||||||
|
.btn {
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
gap: var(--spacing-sm);
|
||||||
|
padding: var(--spacing-sm) var(--spacing-lg);
|
||||||
|
font-size: var(--font-size-md);
|
||||||
|
font-weight: 500;
|
||||||
|
line-height: 1.5;
|
||||||
|
border: 2px solid transparent;
|
||||||
|
border-radius: var(--radius-md);
|
||||||
|
cursor: pointer;
|
||||||
|
transition: all var(--transition-normal);
|
||||||
|
text-align: center;
|
||||||
|
white-space: nowrap;
|
||||||
|
user-select: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-primary {
|
||||||
|
background-color: var(--primary-color);
|
||||||
|
color: white;
|
||||||
|
border-color: var(--primary-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-primary:hover {
|
||||||
|
background-color: var(--primary-dark);
|
||||||
|
border-color: var(--primary-dark);
|
||||||
|
color: white;
|
||||||
|
transform: translateY(-2px);
|
||||||
|
box-shadow: var(--shadow-md);
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-secondary {
|
||||||
|
background-color: var(--secondary-color);
|
||||||
|
color: white;
|
||||||
|
border-color: var(--secondary-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-secondary:hover {
|
||||||
|
background-color: var(--secondary-dark);
|
||||||
|
border-color: var(--secondary-dark);
|
||||||
|
color: white;
|
||||||
|
transform: translateY(-2px);
|
||||||
|
box-shadow: var(--shadow-md);
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-outline {
|
||||||
|
background-color: transparent;
|
||||||
|
color: var(--primary-color);
|
||||||
|
border-color: var(--primary-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-outline:hover {
|
||||||
|
background-color: var(--primary-color);
|
||||||
|
color: white;
|
||||||
|
transform: translateY(-2px);
|
||||||
|
box-shadow: var(--shadow-md);
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-large {
|
||||||
|
padding: var(--spacing-md) var(--spacing-xl);
|
||||||
|
font-size: var(--font-size-lg);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 工具类 */
|
||||||
|
.text-center { text-align: center; }
|
||||||
|
.text-left { text-align: left; }
|
||||||
|
.text-right { text-align: right; }
|
||||||
|
|
||||||
|
.mt-1 { margin-top: var(--spacing-xs); }
|
||||||
|
.mt-2 { margin-top: var(--spacing-sm); }
|
||||||
|
.mt-3 { margin-top: var(--spacing-md); }
|
||||||
|
.mt-4 { margin-top: var(--spacing-lg); }
|
||||||
|
.mt-5 { margin-top: var(--spacing-xl); }
|
||||||
|
|
||||||
|
.mb-1 { margin-bottom: var(--spacing-xs); }
|
||||||
|
.mb-2 { margin-bottom: var(--spacing-sm); }
|
||||||
|
.mb-3 { margin-bottom: var(--spacing-md); }
|
||||||
|
.mb-4 { margin-bottom: var(--spacing-lg); }
|
||||||
|
.mb-5 { margin-bottom: var(--spacing-xl); }
|
||||||
|
|
||||||
|
/* 页面过渡动画 */
|
||||||
|
.page-transition {
|
||||||
|
position: fixed;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
background-color: var(--primary-color);
|
||||||
|
z-index: 9999;
|
||||||
|
opacity: 0;
|
||||||
|
pointer-events: none;
|
||||||
|
transition: opacity var(--transition-normal);
|
||||||
|
}
|
||||||
|
|
||||||
|
.page-transition.active {
|
||||||
|
opacity: 1;
|
||||||
|
pointer-events: all;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 响应式设计 */
|
||||||
|
@media (max-width: 768px) {
|
||||||
|
:root {
|
||||||
|
--font-size-xxxl: 1.75rem;
|
||||||
|
--font-size-xxl: 1.25rem;
|
||||||
|
--font-size-xl: 1.125rem;
|
||||||
|
--font-size-lg: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.container {
|
||||||
|
padding: 0 var(--spacing-sm);
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-large {
|
||||||
|
padding: var(--spacing-sm) var(--spacing-lg);
|
||||||
|
font-size: var(--font-size-md);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 480px) {
|
||||||
|
:root {
|
||||||
|
--font-size-xxxl: 1.5rem;
|
||||||
|
--font-size-xxl: 1.125rem;
|
||||||
|
}
|
||||||
|
}
|
||||||
0
website/public/css/pages/architecture.css
Normal file
0
website/public/css/pages/architecture.css
Normal file
0
website/public/css/pages/features.css
Normal file
0
website/public/css/pages/features.css
Normal file
0
website/public/css/pages/home.css
Normal file
0
website/public/css/pages/home.css
Normal file
0
website/public/css/pages/plugins.css
Normal file
0
website/public/css/pages/plugins.css
Normal file
320
website/public/images/logo.svg
Normal file
320
website/public/images/logo.svg
Normal file
@@ -0,0 +1,320 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 1400 3300" width="1400" height="3300">
|
||||||
|
<defs>
|
||||||
|
<linearGradient id="bg" x1="0%" y1="0%" x2="0%" y2="100%">
|
||||||
|
<stop offset="0%" stop-color="#0a0a1a"/>
|
||||||
|
<stop offset="20%" stop-color="#0d1033"/>
|
||||||
|
<stop offset="50%" stop-color="#110824"/>
|
||||||
|
<stop offset="80%" stop-color="#0d1033"/>
|
||||||
|
<stop offset="100%" stop-color="#0a0a1a"/>
|
||||||
|
</linearGradient>
|
||||||
|
<radialGradient id="glow-purple" cx="50%" cy="50%" r="50%">
|
||||||
|
<stop offset="0%" stop-color="#6366f1" stop-opacity="0.2"/>
|
||||||
|
<stop offset="100%" stop-color="#6366f1" stop-opacity="0"/>
|
||||||
|
</radialGradient>
|
||||||
|
<radialGradient id="glow-cyan" cx="50%" cy="50%" r="50%">
|
||||||
|
<stop offset="0%" stop-color="#06b6d4" stop-opacity="0.15"/>
|
||||||
|
<stop offset="100%" stop-color="#06b6d4" stop-opacity="0"/>
|
||||||
|
</radialGradient>
|
||||||
|
<linearGradient id="cube-purple" x1="0%" y1="0%" x2="100%" y2="100%">
|
||||||
|
<stop offset="0%" stop-color="#6366f1" stop-opacity="0.6"/>
|
||||||
|
<stop offset="100%" stop-color="#8b5cf6" stop-opacity="0.2"/>
|
||||||
|
</linearGradient>
|
||||||
|
<linearGradient id="cube-cyan" x1="0%" y1="0%" x2="100%" y2="100%">
|
||||||
|
<stop offset="0%" stop-color="#06b6d4" stop-opacity="0.5"/>
|
||||||
|
<stop offset="100%" stop-color="#3b82f6" stop-opacity="0.2"/>
|
||||||
|
</linearGradient>
|
||||||
|
<linearGradient id="cube-pink" x1="0%" y1="0%" x2="100%" y2="100%">
|
||||||
|
<stop offset="0%" stop-color="#ec4899" stop-opacity="0.5"/>
|
||||||
|
<stop offset="100%" stop-color="#f43f5e" stop-opacity="0.2"/>
|
||||||
|
</linearGradient>
|
||||||
|
<pattern id="grid" width="60" height="60" patternUnits="userSpaceOnUse" patternTransform="rotate(12)">
|
||||||
|
<path d="M 60 0 L 0 0 0 60" fill="none" stroke="#6366f1" stroke-width="0.25" opacity="0.12"/>
|
||||||
|
</pattern>
|
||||||
|
<filter id="blur-sm"><feGaussianBlur stdDeviation="6"/></filter>
|
||||||
|
<filter id="blur-md"><feGaussianBlur stdDeviation="18"/></filter>
|
||||||
|
<filter id="blur-lg"><feGaussianBlur stdDeviation="40"/></filter>
|
||||||
|
<filter id="glow"><feGaussianBlur stdDeviation="5" result="blur"/><feMerge><feMergeNode in="blur"/><feMergeNode in="SourceGraphic"/></feMerge></filter>
|
||||||
|
<filter id="glow-strong"><feGaussianBlur stdDeviation="8" result="blur"/><feMerge><feMergeNode in="blur"/><feMergeNode in="blur"/><feMergeNode in="SourceGraphic"/></feMerge></filter>
|
||||||
|
<filter id="shadow"><feDropShadow dx="0" dy="4" stdDeviation="8" flood-color="#000" flood-opacity="0.5"/></filter>
|
||||||
|
|
||||||
|
<!-- 统一图标样式 (基于 Feather Icons) -->
|
||||||
|
<g id="icon-plugin"><rect x="2" y="2" width="20" height="20" rx="5" ry="5"/><path d="M16.88 3.549L7.12 20.451"/></g>
|
||||||
|
<g id="icon-deps"><circle cx="18" cy="18" r="3"/><circle cx="6" cy="6" r="3"/><path d="M6 21V9a9 9 0 0 0 9 9"/></g>
|
||||||
|
<g id="icon-shield"><path d="M12 22s8-4 8-10V5l-8-3-8 3v7c0 6 8 10 8 10z"/></g>
|
||||||
|
<g id="icon-shield-check"><path d="M12 22s8-4 8-10V5l-8-3-8 3v7c0 6 8 10 8 10z"/><path d="m9 12 2 2 4-4"/></g>
|
||||||
|
<g id="icon-bolt"><polygon points="13 2 3 14 12 14 11 22 21 10 12 10 13 2"/></g>
|
||||||
|
<g id="icon-rocket"><path d="M4.5 16.5c-1.5 1.26-2 5-2 5s3.74-.5 5-2c.48-.56.9-1.23 1.23-2l-4.23-1z"/><path d="M2 2l20 20"/></g>
|
||||||
|
<g id="icon-features"><line x1="8" y1="6" x2="21" y2="6"/><line x1="8" y1="12" x2="21" y2="12"/><line x1="8" y1="18" x2="21" y2="18"/><line x1="3" y1="6" x2="3.01" y2="6"/><line x1="3" y1="12" x2="3.01" y2="12"/><line x1="3" y1="18" x2="3.01" y2="18"/></g>
|
||||||
|
<g id="icon-arch"><polygon points="12 2 2 7 12 12 22 7 12 2"/><polyline points="2 17 12 22 22 17"/><polyline points="2 12 12 17 22 12"/></g>
|
||||||
|
<g id="icon-docs"><path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z"/><polyline points="14 2 14 8 20 8"/><line x1="16" y1="13" x2="8" y2="13"/><line x1="16" y1="17" x2="8" y2="17"/><polyline points="10 9 9 9 8 9"/></g>
|
||||||
|
<g id="icon-book"><path d="M4 19.5A2.5 2.5 0 0 1 6.5 17H20"/><path d="M6.5 2H20v20H6.5A2.5 2.5 0 0 1 4 19.5v-15A2.5 2.5 0 0 1 6.5 2z"/></g>
|
||||||
|
<g id="icon-config"><circle cx="12" cy="12" r="3"/><path d="M19.4 15a1.65 1.65 0 0 0 .33 1.82l.06.06a2 2 0 0 1 0 2.83 2 2 0 0 1-2.83 0l-.06-.06a1.65 1.65 0 0 0-1.82-.33 1.65 1.65 0 0 0-1 1.51V21a2 2 0 0 1-2 2 2 2 0 0 1-2-2v-.09A1.65 1.65 0 0 0 9 19.4a1.65 1.65 0 0 0-1.82.33l-.06.06a2 2 0 0 1-2.83 0 2 2 0 0 1 0-2.83l.06-.06a1.65 1.65 0 0 0 .33-1.82 1.65 1.65 0 0 0-1.51-1H3a2 2 0 0 1-2-2 2 2 0 0 1 2-2h.09A1.65 1.65 0 0 0 4.6 9a1.65 1.65 0 0 0-.33-1.82l-.06-.06a2 2 0 0 1 0-2.83 2 2 0 0 1 2.83 0l.06.06a1.65 1.65 0 0 0 1.82.33H9a1.65 1.65 0 0 0 1-1.51V3a2 2 0 0 1 2-2 2 2 0 0 1 2 2v.09a1.65 1.65 0 0 0 1 1.51 1.65 1.65 0 0 0 1.82-.33l.06-.06a2 2 0 0 1 2.83 0 2 2 0 0 1 0 2.83l-.06.06a1.65 1.65 0 0 0-.33 1.82V9a1.65 1.65 0 0 0 1.51 1H21a2 2 0 0 1 2 2 2 2 0 0 1-2 2h-.09a1.65 1.65 0 0 0-1.51 1z"/></g>
|
||||||
|
<g id="icon-deploy"><polyline points="4 17 10 11 4 5"/><line x1="12" y1="19" x2="20" y2="19"/></g>
|
||||||
|
<g id="icon-star"><polygon points="12 2 15.09 8.26 22 9.27 17 14.14 18.18 21.02 12 17.77 5.82 21.02 7 14.14 2 9.27 8.91 8.26 12 2"/></g>
|
||||||
|
<g id="icon-dev"><polyline points="16 18 22 12 16 6"/><polyline points="8 6 2 12 8 18"/></g>
|
||||||
|
<g id="icon-check"><path d="M22 11.08V12a10 10 0 1 1-5.93-9.14"/><polyline points="22 4 12 14.01 9 11.01"/></g>
|
||||||
|
<g id="icon-wip"><circle cx="12" cy="12" r="10"/><line x1="12" y1="8" x2="12" y2="12"/><line x1="12" y1="16" x2="12.01" y2="16"/></g>
|
||||||
|
<g id="icon-license"><path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z"/><path d="M14 2v6h6"/></g>
|
||||||
|
<g id="icon-heart"><path d="M20.84 4.61a5.5 5.5 0 0 0-7.78 0L12 5.67l-1.06-1.06a5.5 5.5 0 0 0-7.78 7.78l1.06 1.06L12 21.23l7.78-7.78 1.06-1.06a5.5 5.5 0 0 0 0-7.78z"/></g>
|
||||||
|
</defs>
|
||||||
|
|
||||||
|
<rect width="1400" height="3300" fill="url(#bg)"/>
|
||||||
|
<rect width="1400" height="3300" fill="url(#grid)"/>
|
||||||
|
|
||||||
|
<ellipse cx="700" cy="600" rx="800" ry="500" fill="url(#glow-purple)" filter="url(#blur-lg)" opacity="0.8"/>
|
||||||
|
<ellipse cx="300" cy="1200" rx="600" ry="400" fill="url(#glow-cyan)" filter="url(#blur-lg)" opacity="0.7"/>
|
||||||
|
<ellipse cx="1100" cy="1800" rx="700" ry="450" fill="url(#glow-purple)" filter="url(#blur-lg)" opacity="0.75"/>
|
||||||
|
<ellipse cx="500" cy="2500" rx="650" ry="400" fill="url(#glow-cyan)" filter="url(#blur-lg)" opacity="0.7"/>
|
||||||
|
|
||||||
|
<g transform="translate(200, 500) rotate(-25) scale(1.2)" filter="url(#glow-strong)" class="layer-cubes">
|
||||||
|
<polygon points="0,-40 60,-70 120,-40 60,-10" fill="url(#cube-purple)" opacity="0.5" stroke="#6366f1" stroke-width="0.8"/>
|
||||||
|
<polygon points="0,-40 60,-10 60,50 0,20" fill="url(#cube-purple)" opacity="0.4" stroke="#6366f1" stroke-width="0.8"/>
|
||||||
|
<polygon points="60,-10 120,-40 120,20 60,50" fill="url(#cube-purple)" opacity="0.35" stroke="#6366f1" stroke-width="0.8"/>
|
||||||
|
</g>
|
||||||
|
|
||||||
|
<g transform="translate(1100, 700) rotate(15) scale(1.1)" filter="url(#glow-strong)" class="layer-cubes">
|
||||||
|
<polygon points="0,-40 60,-70 120,-40 60,-10" fill="url(#cube-cyan)" opacity="0.5" stroke="#06b6d4" stroke-width="0.8"/>
|
||||||
|
<polygon points="0,-40 60,-10 60,50 0,20" fill="url(#cube-cyan)" opacity="0.4" stroke="#06b6d4" stroke-width="0.8"/>
|
||||||
|
<polygon points="60,-10 120,-40 120,20 60,50" fill="url(#cube-cyan)" opacity="0.35" stroke="#06b6d4" stroke-width="0.8"/>
|
||||||
|
</g>
|
||||||
|
|
||||||
|
<g transform="translate(700, 1000) rotate(35) scale(0.9)" filter="url(#glow)" class="layer-cubes">
|
||||||
|
<polygon points="0,-40 60,-70 120,-40 60,-10" fill="url(#cube-pink)" opacity="0.45" stroke="#ec4899" stroke-width="0.6"/>
|
||||||
|
<polygon points="0,-40 60,-10 60,50 0,20" fill="url(#cube-pink)" opacity="0.35" stroke="#ec4899" stroke-width="0.6"/>
|
||||||
|
<polygon points="60,-10 120,-40 120,20 60,50" fill="url(#cube-pink)" opacity="0.3" stroke="#ec4899" stroke-width="0.6"/>
|
||||||
|
</g>
|
||||||
|
|
||||||
|
<g transform="translate(300, 1500) rotate(-40) scale(0.85)" filter="url(#glow)">
|
||||||
|
<polygon points="0,-40 60,-70 120,-40 60,-10" fill="url(#cube-cyan)" opacity="0.4" stroke="#06b6d4" stroke-width="0.6"/>
|
||||||
|
<polygon points="0,-40 60,-10 60,50 0,20" fill="url(#cube-cyan)" opacity="0.3" stroke="#06b6d4" stroke-width="0.6"/>
|
||||||
|
<polygon points="60,-10 120,-40 120,20 60,50" fill="url(#cube-cyan)" opacity="0.25" stroke="#06b6d4" stroke-width="0.6"/>
|
||||||
|
</g>
|
||||||
|
|
||||||
|
<g transform="translate(1050, 1800) rotate(-20) scale(1.05)" filter="url(#glow-strong)">
|
||||||
|
<polygon points="0,-40 60,-70 120,-40 60,-10" fill="url(#cube-purple)" opacity="0.5" stroke="#8b5cf6" stroke-width="0.7"/>
|
||||||
|
<polygon points="0,-40 60,-10 60,50 0,20" fill="url(#cube-purple)" opacity="0.4" stroke="#8b5cf6" stroke-width="0.7"/>
|
||||||
|
<polygon points="60,-10 120,-40 120,20 60,50" fill="url(#cube-purple)" opacity="0.35" stroke="#8b5cf6" stroke-width="0.7"/>
|
||||||
|
</g>
|
||||||
|
|
||||||
|
<g transform="translate(500, 2200) rotate(28) scale(0.75)" filter="url(#glow)">
|
||||||
|
<polygon points="0,-40 60,-70 120,-40 60,-10" fill="url(#cube-pink)" opacity="0.4" stroke="#f43f5e" stroke-width="0.5"/>
|
||||||
|
<polygon points="0,-40 60,-10 60,50 0,20" fill="url(#cube-pink)" opacity="0.3" stroke="#f43f5e" stroke-width="0.5"/>
|
||||||
|
<polygon points="60,-10 120,-40 120,20 60,50" fill="url(#cube-pink)" opacity="0.25" stroke="#f43f5e" stroke-width="0.5"/>
|
||||||
|
</g>
|
||||||
|
|
||||||
|
<g transform="translate(900, 2600) rotate(-32) scale(0.95)" filter="url(#glow)">
|
||||||
|
<polygon points="0,-40 60,-70 120,-40 60,-10" fill="url(#cube-cyan)" opacity="0.45" stroke="#06b6d4" stroke-width="0.6"/>
|
||||||
|
<polygon points="0,-40 60,-10 60,50 0,20" fill="url(#cube-cyan)" opacity="0.35" stroke="#06b6d4" stroke-width="0.6"/>
|
||||||
|
<polygon points="60,-10 120,-40 120,20 60,50" fill="url(#cube-cyan)" opacity="0.3" stroke="#06b6d4" stroke-width="0.6"/>
|
||||||
|
</g>
|
||||||
|
|
||||||
|
<g transform="translate(250, 3000) rotate(18) scale(0.8)" filter="url(#glow)">
|
||||||
|
<polygon points="0,-40 60,-70 120,-40 60,-10" fill="url(#cube-purple)" opacity="0.4" stroke="#6366f1" stroke-width="0.5"/>
|
||||||
|
<polygon points="0,-40 60,-10 60,50 0,20" fill="url(#cube-purple)" opacity="0.3" stroke="#6366f1" stroke-width="0.5"/>
|
||||||
|
<polygon points="60,-10 120,-40 120,20 60,50" fill="url(#cube-purple)" opacity="0.25" stroke="#6366f1" stroke-width="0.5"/>
|
||||||
|
</g>
|
||||||
|
|
||||||
|
<g transform="translate(700, 150)" text-anchor="middle" class="layer-title">
|
||||||
|
<rect x="-230" y="-25" width="460" height="50" rx="25" fill="#6366f1" opacity="0.15" filter="url(#glow)"/>
|
||||||
|
<rect x="-228" y="-23" width="456" height="46" rx="23" fill="none" stroke="#6366f1" stroke-width="1" opacity="0.4"/>
|
||||||
|
<text font-family="system-ui, -apple-system, sans-serif" font-size="16" fill="#a5b4fc" letter-spacing="3" font-weight="400">Python 3.10+ Apache 2.0 Plugin Driven</text>
|
||||||
|
</g>
|
||||||
|
|
||||||
|
<g transform="translate(700, 380)" text-anchor="middle" filter="url(#shadow)" class="layer-title">
|
||||||
|
<text font-family="system-ui, -apple-system, 'Segoe UI', Roboto, sans-serif" font-size="96" fill="url(#cube-purple)" font-weight="800" letter-spacing="4">FutureOSS</text>
|
||||||
|
<text y="75" font-family="system-ui, -apple-system, 'Segoe UI', Roboto, sans-serif" font-size="32" fill="#e0e7ff" font-weight="300" letter-spacing="8">一切皆为插件的开发者工具运行时框架</text>
|
||||||
|
</g>
|
||||||
|
|
||||||
|
<g transform="translate(700, 520)" text-anchor="middle" class="layer-title">
|
||||||
|
<text font-family="system-ui, -apple-system, sans-serif" font-size="20" fill="#94a3b8" font-style="italic">一个空壳框架,通过插件获得无限能力</text>
|
||||||
|
</g>
|
||||||
|
|
||||||
|
<g transform="translate(0, 650)">
|
||||||
|
<g transform="translate(100, 0)">
|
||||||
|
<rect width="280" height="160" rx="12" fill="#1e1b4b" opacity="0.6" filter="url(#shadow)"/>
|
||||||
|
<rect width="280" height="160" rx="12" fill="none" stroke="#6366f1" stroke-width="1.5" opacity="0.4"/>
|
||||||
|
<use href="#icon-plugin" transform="translate(128, 38) scale(1)" color="#6366f1" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||||
|
<text x="140" y="95" text-anchor="middle" font-family="system-ui, sans-serif" font-size="18" fill="#e0e7ff" font-weight="600">一切皆插件</text>
|
||||||
|
<text x="140" y="125" text-anchor="middle" font-family="system-ui, sans-serif" font-size="13" fill="#94a3b8">协议、中间件、工具</text>
|
||||||
|
<text x="140" y="145" text-anchor="middle" font-family="system-ui, sans-serif" font-size="13" fill="#94a3b8">所有功能以插件加载</text>
|
||||||
|
</g>
|
||||||
|
<g transform="translate(420, 0)">
|
||||||
|
<rect width="280" height="160" rx="12" fill="#1e1b4b" opacity="0.6" filter="url(#shadow)"/>
|
||||||
|
<rect width="280" height="160" rx="12" fill="none" stroke="#06b6d4" stroke-width="1.5" opacity="0.4"/>
|
||||||
|
<use href="#icon-deps" transform="translate(128, 38) scale(1)" color="#06b6d4" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||||
|
<text x="140" y="95" text-anchor="middle" font-family="system-ui, sans-serif" font-size="18" fill="#e0e7ff" font-weight="600">依赖自动解析</text>
|
||||||
|
<text x="140" y="125" text-anchor="middle" font-family="system-ui, sans-serif" font-size="13" fill="#94a3b8">拓扑排序 + 循环检测</text>
|
||||||
|
<text x="140" y="145" text-anchor="middle" font-family="system-ui, sans-serif" font-size="13" fill="#94a3b8">自动识别多级依赖</text>
|
||||||
|
</g>
|
||||||
|
<g transform="translate(740, 0)">
|
||||||
|
<rect width="280" height="160" rx="12" fill="#1e1b4b" opacity="0.6" filter="url(#shadow)"/>
|
||||||
|
<rect width="280" height="160" rx="12" fill="none" stroke="#ec4899" stroke-width="1.5" opacity="0.4"/>
|
||||||
|
<use href="#icon-shield" transform="translate(128, 38) scale(1)" color="#ec4899" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||||
|
<text x="140" y="95" text-anchor="middle" font-family="system-ui, sans-serif" font-size="18" fill="#e0e7ff" font-weight="600">企业级稳定</text>
|
||||||
|
<text x="140" y="125" text-anchor="middle" font-family="system-ui, sans-serif" font-size="13" fill="#94a3b8">熔断、降级、重试</text>
|
||||||
|
<text x="140" y="145" text-anchor="middle" font-family="system-ui, sans-serif" font-size="13" fill="#94a3b8">隔离 + 资源限制</text>
|
||||||
|
</g>
|
||||||
|
<g transform="translate(1060, 0)">
|
||||||
|
<rect width="280" height="160" rx="12" fill="#1e1b4b" opacity="0.6" filter="url(#shadow)"/>
|
||||||
|
<rect width="280" height="160" rx="12" fill="none" stroke="#10b981" stroke-width="1.5" opacity="0.4"/>
|
||||||
|
<use href="#icon-bolt" transform="translate(128, 38) scale(1)" color="#10b981" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||||
|
<text x="140" y="95" text-anchor="middle" font-family="system-ui, sans-serif" font-size="18" fill="#e0e7ff" font-weight="600">事件驱动</text>
|
||||||
|
<text x="140" y="125" text-anchor="middle" font-family="system-ui, sans-serif" font-size="13" fill="#94a3b8">发布/订阅事件总线</text>
|
||||||
|
<text x="140" y="145" text-anchor="middle" font-family="system-ui, sans-serif" font-size="13" fill="#94a3b8">40+ 种系统事件</text>
|
||||||
|
</g>
|
||||||
|
</g>
|
||||||
|
|
||||||
|
<g transform="translate(700, 920)" text-anchor="middle">
|
||||||
|
<text font-family="system-ui, sans-serif" font-size="36" fill="#e0e7ff" font-weight="700" letter-spacing="3">快速开始</text>
|
||||||
|
</g>
|
||||||
|
|
||||||
|
<g transform="translate(200, 980)">
|
||||||
|
<rect width="1000" height="180" rx="10" fill="#0f172a" opacity="0.85" filter="url(#shadow)"/>
|
||||||
|
<rect width="1000" height="180" rx="10" fill="none" stroke="#334155" stroke-width="1"/>
|
||||||
|
<circle cx="30" cy="30" r="6" fill="#ef4444"/>
|
||||||
|
<circle cx="55" cy="30" r="6" fill="#f59e0b"/>
|
||||||
|
<circle cx="80" cy="30" r="6" fill="#10b981"/>
|
||||||
|
<text x="50" y="75" font-family="'Fira Code', 'Consolas', 'Monaco', monospace" font-size="18" fill="#22d3ee">$ git clone https://gitee.com/starlight-apk/feature-oss.git</text>
|
||||||
|
<text x="50" y="105" font-family="'Fira Code', 'Consolas', 'Monaco', monospace" font-size="18" fill="#22d3ee">$ cd feature-oss && bash start.sh</text>
|
||||||
|
<text x="50" y="145" font-family="'Fira Code', 'Consolas', 'Monaco', monospace" font-size="16" fill="#10b981">✓ 服务已启动 → http://localhost:8080/</text>
|
||||||
|
</g>
|
||||||
|
|
||||||
|
<g transform="translate(700, 1250)" text-anchor="middle">
|
||||||
|
<text font-family="system-ui, sans-serif" font-size="36" fill="#e0e7ff" font-weight="700" letter-spacing="3">核心特性</text>
|
||||||
|
</g>
|
||||||
|
|
||||||
|
<g transform="translate(150, 1320)">
|
||||||
|
<g transform="translate(0, 0)">
|
||||||
|
<rect width="540" height="60" rx="8" fill="#1e1b4b" opacity="0.5"/>
|
||||||
|
<use href="#icon-plugin" transform="translate(38, 22) scale(1)" color="#6366f1" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||||
|
<text x="85" y="38" font-family="system-ui, sans-serif" font-size="20" fill="#e0e7ff">插件热插拔</text>
|
||||||
|
<use href="#icon-check" transform="translate(490, 22) scale(1)" color="#10b981" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||||
|
</g>
|
||||||
|
<g transform="translate(580, 0)">
|
||||||
|
<rect width="540" height="60" rx="8" fill="#1e1b4b" opacity="0.5"/>
|
||||||
|
<use href="#icon-deps" transform="translate(38, 22) scale(1)" color="#06b6d4" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||||
|
<text x="85" y="38" font-family="system-ui, sans-serif" font-size="20" fill="#e0e7ff">依赖自动解析</text>
|
||||||
|
<use href="#icon-check" transform="translate(490, 22) scale(1)" color="#10b981" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||||
|
</g>
|
||||||
|
<g transform="translate(0, 80)">
|
||||||
|
<rect width="540" height="60" rx="8" fill="#1e1b4b" opacity="0.5"/>
|
||||||
|
<use href="#icon-bolt" transform="translate(38, 22) scale(1)" color="#10b981" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||||
|
<text x="85" y="38" font-family="system-ui, sans-serif" font-size="20" fill="#e0e7ff">事件总线</text>
|
||||||
|
<use href="#icon-check" transform="translate(490, 22) scale(1)" color="#10b981" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||||
|
</g>
|
||||||
|
<g transform="translate(580, 80)">
|
||||||
|
<rect width="540" height="60" rx="8" fill="#1e1b4b" opacity="0.5"/>
|
||||||
|
<use href="#icon-config" transform="translate(38, 22) scale(1)" color="#f59e0b" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||||
|
<text x="85" y="38" font-family="system-ui, sans-serif" font-size="20" fill="#e0e7ff">完整配置</text>
|
||||||
|
<use href="#icon-check" transform="translate(490, 22) scale(1)" color="#10b981" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||||
|
</g>
|
||||||
|
<g transform="translate(0, 160)">
|
||||||
|
<rect width="540" height="60" rx="8" fill="#1e1b4b" opacity="0.5"/>
|
||||||
|
<use href="#icon-deploy" transform="translate(38, 22) scale(1)" color="#6366f1" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||||
|
<text x="85" y="38" font-family="system-ui, sans-serif" font-size="20" fill="#e0e7ff">协议适配</text>
|
||||||
|
<use href="#icon-wip" transform="translate(490, 22) scale(1)" color="#f59e0b" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||||
|
</g>
|
||||||
|
<g transform="translate(580, 160)">
|
||||||
|
<rect width="540" height="60" rx="8" fill="#1e1b4b" opacity="0.5"/>
|
||||||
|
<use href="#icon-shield-check" transform="translate(38, 22) scale(1)" color="#ec4899" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||||
|
<text x="85" y="38" font-family="system-ui, sans-serif" font-size="20" fill="#e0e7ff">熔断降级</text>
|
||||||
|
<use href="#icon-wip" transform="translate(490, 22) scale(1)" color="#f59e0b" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||||
|
</g>
|
||||||
|
</g>
|
||||||
|
|
||||||
|
<g transform="translate(700, 1700)" text-anchor="middle">
|
||||||
|
<text font-family="system-ui, sans-serif" font-size="36" fill="#e0e7ff" font-weight="700" letter-spacing="3">🏗️ 架构设计</text>
|
||||||
|
</g>
|
||||||
|
|
||||||
|
<g transform="translate(250, 1760)">
|
||||||
|
<rect width="900" height="300" rx="12" fill="#0f172a" opacity="0.7" filter="url(#shadow)"/>
|
||||||
|
<rect width="900" height="300" rx="12" fill="none" stroke="#334155" stroke-width="1.5"/>
|
||||||
|
<text x="450" y="50" text-anchor="middle" font-family="system-ui, sans-serif" font-size="16" fill="#6366f1" font-weight="600">用户层</text>
|
||||||
|
<rect x="150" y="65" width="600" height="35" rx="6" fill="#1e1b4b" opacity="0.6"/>
|
||||||
|
<text x="450" y="88" text-anchor="middle" font-family="system-ui, sans-serif" font-size="14" fill="#94a3b8">CLI │ HTTP API │ WebSocket │ 社区论坛</text>
|
||||||
|
<line x1="450" y1="100" x2="450" y2="125" stroke="#6366f1" stroke-width="2"/>
|
||||||
|
<text x="450" y="155" text-anchor="middle" font-family="system-ui, sans-serif" font-size="16" fill="#06b6d4" font-weight="600">插件层</text>
|
||||||
|
<rect x="175" y="165" width="170" height="35" rx="6" fill="#1e1b4b" opacity="0.6"/>
|
||||||
|
<text x="260" y="188" text-anchor="middle" font-family="system-ui, sans-serif" font-size="13" fill="#94a3b8">http-api</text>
|
||||||
|
<rect x="365" y="165" width="170" height="35" rx="6" fill="#1e1b4b" opacity="0.6"/>
|
||||||
|
<text x="450" y="188" text-anchor="middle" font-family="system-ui, sans-serif" font-size="13" fill="#94a3b8">ws-api</text>
|
||||||
|
<rect x="555" y="165" width="170" height="35" rx="6" fill="#1e1b4b" opacity="0.6"/>
|
||||||
|
<text x="640" y="188" text-anchor="middle" font-family="system-ui, sans-serif" font-size="13" fill="#94a3b8">plugin-storage</text>
|
||||||
|
<line x1="450" y1="200" x2="450" y2="225" stroke="#06b6d4" stroke-width="2"/>
|
||||||
|
<text x="450" y="250" text-anchor="middle" font-family="system-ui, sans-serif" font-size="16" fill="#ec4899" font-weight="600">核心层</text>
|
||||||
|
<rect x="100" y="260" width="700" height="25" rx="6" fill="#1e1b4b" opacity="0.6"/>
|
||||||
|
<text x="450" y="278" text-anchor="middle" font-family="system-ui, sans-serif" font-size="13" fill="#94a3b8">PluginManager | EventBus | Config | Logger | Loader</text>
|
||||||
|
</g>
|
||||||
|
|
||||||
|
<g transform="translate(700, 2150)" text-anchor="middle">
|
||||||
|
<text font-family="system-ui, sans-serif" font-size="36" fill="#e0e7ff" font-weight="700" letter-spacing="3">文档</text>
|
||||||
|
</g>
|
||||||
|
|
||||||
|
<g transform="translate(450, 2220)">
|
||||||
|
<!-- 书影 -->
|
||||||
|
<rect x="8" y="8" width="500" height="310" rx="8" fill="#000" opacity="0.3" filter="url(#blur-md)"/>
|
||||||
|
|
||||||
|
<!-- 书封面 -->
|
||||||
|
<rect x="0" y="0" width="500" height="310" rx="8" fill="#1e1b4b" opacity="0.9"/>
|
||||||
|
<rect x="0" y="0" width="500" height="310" rx="8" fill="none" stroke="#6366f1" stroke-width="2" opacity="0.6"/>
|
||||||
|
|
||||||
|
<!-- 书脊 -->
|
||||||
|
<rect x="0" y="0" width="25" height="310" rx="8" fill="#0f172a" opacity="0.8"/>
|
||||||
|
<line x1="25" y1="0" x2="25" y2="310" stroke="#6366f1" stroke-width="1" opacity="0.4"/>
|
||||||
|
|
||||||
|
<!-- 书页 -->
|
||||||
|
<rect x="30" y="10" width="460" height="290" rx="4" fill="#0f172a" opacity="0.4"/>
|
||||||
|
|
||||||
|
<!-- 书签 -->
|
||||||
|
<polygon points="480,0 500,0 500,25 490,18 480,25" fill="#ec4899" opacity="0.8"/>
|
||||||
|
|
||||||
|
<!-- 标题 -->
|
||||||
|
<text x="250" y="40" text-anchor="middle" font-family="system-ui, sans-serif" font-size="16" fill="#6366f1" font-weight="600" letter-spacing="2">完整文档请访问项目 Wiki</text>
|
||||||
|
|
||||||
|
<!-- Wiki 链接列表 (单列 8 项) -->
|
||||||
|
<use href="#icon-book" transform="translate(100, 53) scale(0.75)" color="#a5b4fc" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||||
|
<text x="125" y="65" font-family="system-ui, sans-serif" font-size="15" fill="#a5b4fc">Wiki/项目介绍</text>
|
||||||
|
|
||||||
|
<use href="#icon-rocket" transform="translate(100, 83) scale(0.75)" color="#a5b4fc" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||||
|
<text x="125" y="95" font-family="system-ui, sans-serif" font-size="15" fill="#a5b4fc">Wiki/快速开始</text>
|
||||||
|
|
||||||
|
<use href="#icon-dev" transform="translate(100, 113) scale(0.75)" color="#a5b4fc" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||||
|
<text x="125" y="125" font-family="system-ui, sans-serif" font-size="15" fill="#a5b4fc">Wiki/插件开发</text>
|
||||||
|
|
||||||
|
<use href="#icon-docs" transform="translate(100, 143) scale(0.75)" color="#a5b4fc" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||||
|
<text x="125" y="155" font-family="system-ui, sans-serif" font-size="15" fill="#a5b4fc">Wiki/插件文档</text>
|
||||||
|
|
||||||
|
<use href="#icon-config" transform="translate(100, 173) scale(0.75)" color="#a5b4fc" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||||
|
<text x="125" y="185" font-family="system-ui, sans-serif" font-size="15" fill="#a5b4fc">Wiki/包管理</text>
|
||||||
|
|
||||||
|
<use href="#icon-features" transform="translate(100, 203) scale(0.75)" color="#a5b4fc" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||||
|
<text x="125" y="215" font-family="system-ui, sans-serif" font-size="15" fill="#a5b4fc">Wiki/配置参考</text>
|
||||||
|
|
||||||
|
<use href="#icon-deploy" transform="translate(100, 233) scale(0.75)" color="#a5b4fc" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||||
|
<text x="125" y="245" font-family="system-ui, sans-serif" font-size="15" fill="#a5b4fc">Wiki/部署运维</text>
|
||||||
|
|
||||||
|
<use href="#icon-star" transform="translate(100, 263) scale(0.75)" color="#a5b4fc" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||||
|
<text x="125" y="275" font-family="system-ui, sans-serif" font-size="15" fill="#a5b4fc">Wiki/社区与贡献</text>
|
||||||
|
</g>
|
||||||
|
|
||||||
|
<!-- 许可证区域 -->
|
||||||
|
<g transform="translate(700, 2680)" text-anchor="middle">
|
||||||
|
<text font-family="system-ui, sans-serif" font-size="32" fill="#e0e7ff" font-weight="700">许可证</text>
|
||||||
|
<text y="45" font-family="system-ui, sans-serif" font-size="18" fill="#94a3b8">Apache License 2.0</text>
|
||||||
|
<text y="80" font-family="system-ui, sans-serif" font-size="16" fill="#64748b">Copyright 2026 Falck</text>
|
||||||
|
</g>
|
||||||
|
|
||||||
|
<!-- Made with love 区域 -->
|
||||||
|
<g transform="translate(700, 2830)" text-anchor="middle">
|
||||||
|
<text font-family="system-ui, sans-serif" font-size="24" fill="#a5b4fc" font-weight="600">Made with love by Falck & yongwanxing</text>
|
||||||
|
<text y="40" font-family="system-ui, sans-serif" font-size="16" fill="#64748b">https://gitee.com/starlight-apk · https://gitcode.com/yongwanxing</text>
|
||||||
|
</g>
|
||||||
|
|
||||||
|
<text x="700" y="3000" font-family="system-ui, -apple-system, sans-serif" font-size="32" fill="#6366f1" opacity="0.08" text-anchor="middle" letter-spacing="15" font-weight="300">FUTURE OSS</text>
|
||||||
|
<text x="700" y="3050" font-family="system-ui, -apple-system, sans-serif" font-size="14" fill="#6366f1" opacity="0.06" text-anchor="middle" letter-spacing="10" font-weight="300">EVERYTHING IS A PLUGIN</text>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
@keyframes float-slow { 0%, 100% { transform: translateY(0); } 50% { transform: translateY(-8px); } }
|
||||||
|
@keyframes float-medium { 0%, 100% { transform: translateY(0); } 50% { transform: translateY(-15px); } }
|
||||||
|
</style>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 27 KiB |
465
website/public/js/animations.js
Normal file
465
website/public/js/animations.js
Normal file
@@ -0,0 +1,465 @@
|
|||||||
|
/**
|
||||||
|
* 动画和过渡效果管理
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 初始化所有动画
|
||||||
|
*/
|
||||||
|
function initAnimations() {
|
||||||
|
console.log('初始化动画系统...');
|
||||||
|
|
||||||
|
// 初始化滚动动画
|
||||||
|
initScrollAnimations();
|
||||||
|
|
||||||
|
// 初始化视差效果
|
||||||
|
initParallaxEffects();
|
||||||
|
|
||||||
|
// 初始化计数器动画
|
||||||
|
initCounterAnimations();
|
||||||
|
|
||||||
|
// 初始化进度条动画
|
||||||
|
initProgressBars();
|
||||||
|
|
||||||
|
// 初始化打字机效果
|
||||||
|
initTypewriterEffects();
|
||||||
|
|
||||||
|
// 初始化骨架屏
|
||||||
|
initSkeletonScreens();
|
||||||
|
|
||||||
|
console.log('动画系统初始化完成');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 初始化滚动动画
|
||||||
|
*/
|
||||||
|
function initScrollAnimations() {
|
||||||
|
const animatedElements = document.querySelectorAll('.animate-on-scroll');
|
||||||
|
|
||||||
|
if ('IntersectionObserver' in window) {
|
||||||
|
const observer = new IntersectionObserver((entries) => {
|
||||||
|
entries.forEach(entry => {
|
||||||
|
if (entry.isIntersecting) {
|
||||||
|
entry.target.classList.add('animated');
|
||||||
|
observer.unobserve(entry.target);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}, {
|
||||||
|
threshold: 0.1,
|
||||||
|
rootMargin: '0px 0px -50px 0px'
|
||||||
|
});
|
||||||
|
|
||||||
|
animatedElements.forEach(element => observer.observe(element));
|
||||||
|
} else {
|
||||||
|
// 回退方案:直接添加动画类
|
||||||
|
animatedElements.forEach(element => {
|
||||||
|
element.classList.add('animated');
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 初始化视差效果
|
||||||
|
*/
|
||||||
|
function initParallaxEffects() {
|
||||||
|
const parallaxElements = document.querySelectorAll('.parallax');
|
||||||
|
|
||||||
|
if (parallaxElements.length === 0) return;
|
||||||
|
|
||||||
|
window.addEventListener('scroll', () => {
|
||||||
|
const scrolled = window.pageYOffset;
|
||||||
|
|
||||||
|
parallaxElements.forEach(element => {
|
||||||
|
const speed = parseFloat(element.dataset.speed) || 0.5;
|
||||||
|
const yPos = -(scrolled * speed);
|
||||||
|
element.style.transform = `translateY(${yPos}px)`;
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 初始化计数器动画
|
||||||
|
*/
|
||||||
|
function initCounterAnimations() {
|
||||||
|
const counters = document.querySelectorAll('.counter');
|
||||||
|
|
||||||
|
if (counters.length === 0) return;
|
||||||
|
|
||||||
|
const observer = new IntersectionObserver((entries) => {
|
||||||
|
entries.forEach(entry => {
|
||||||
|
if (entry.isIntersecting) {
|
||||||
|
const counter = entry.target;
|
||||||
|
const target = parseInt(counter.dataset.target) || 0;
|
||||||
|
const duration = parseInt(counter.dataset.duration) || 2000;
|
||||||
|
const increment = target / (duration / 16); // 60fps
|
||||||
|
let current = 0;
|
||||||
|
|
||||||
|
const updateCounter = () => {
|
||||||
|
current += increment;
|
||||||
|
if (current < target) {
|
||||||
|
counter.textContent = Math.floor(current).toLocaleString();
|
||||||
|
requestAnimationFrame(updateCounter);
|
||||||
|
} else {
|
||||||
|
counter.textContent = target.toLocaleString();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
updateCounter();
|
||||||
|
observer.unobserve(counter);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
counters.forEach(counter => observer.observe(counter));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 初始化进度条动画
|
||||||
|
*/
|
||||||
|
function initProgressBars() {
|
||||||
|
const progressBars = document.querySelectorAll('.progress-bar');
|
||||||
|
|
||||||
|
if (progressBars.length === 0) return;
|
||||||
|
|
||||||
|
const observer = new IntersectionObserver((entries) => {
|
||||||
|
entries.forEach(entry => {
|
||||||
|
if (entry.isIntersecting) {
|
||||||
|
const progressBar = entry.target;
|
||||||
|
const width = progressBar.dataset.width || '100%';
|
||||||
|
const duration = parseInt(progressBar.dataset.duration) || 1000;
|
||||||
|
|
||||||
|
progressBar.style.transition = `width ${duration}ms ease`;
|
||||||
|
progressBar.style.width = width;
|
||||||
|
|
||||||
|
observer.unobserve(progressBar);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
progressBars.forEach(bar => observer.observe(bar));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 初始化打字机效果
|
||||||
|
*/
|
||||||
|
function initTypewriterEffects() {
|
||||||
|
const typewriters = document.querySelectorAll('.typewriter');
|
||||||
|
|
||||||
|
typewriters.forEach(element => {
|
||||||
|
const text = element.textContent;
|
||||||
|
element.textContent = '';
|
||||||
|
element.style.width = '0';
|
||||||
|
|
||||||
|
let i = 0;
|
||||||
|
const type = () => {
|
||||||
|
if (i < text.length) {
|
||||||
|
element.textContent += text.charAt(i);
|
||||||
|
element.style.width = `${(i + 1) / text.length * 100}%`;
|
||||||
|
i++;
|
||||||
|
setTimeout(type, 100);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// 延迟开始打字效果
|
||||||
|
setTimeout(type, 500);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 初始化骨架屏
|
||||||
|
*/
|
||||||
|
function initSkeletonScreens() {
|
||||||
|
const skeletonElements = document.querySelectorAll('.skeleton');
|
||||||
|
|
||||||
|
if (skeletonElements.length === 0) return;
|
||||||
|
|
||||||
|
// 模拟内容加载
|
||||||
|
setTimeout(() => {
|
||||||
|
skeletonElements.forEach(element => {
|
||||||
|
element.classList.remove('skeleton');
|
||||||
|
element.classList.add('fade-in-load');
|
||||||
|
});
|
||||||
|
}, 1000);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 创建骨架屏占位符
|
||||||
|
* @param {HTMLElement} container - 容器元素
|
||||||
|
* @param {number} count - 骨架屏数量
|
||||||
|
* @param {string} type - 骨架屏类型: 'text', 'card', 'image'
|
||||||
|
*/
|
||||||
|
function createSkeletonPlaceholders(container, count = 3, type = 'card') {
|
||||||
|
const skeletonClasses = {
|
||||||
|
'text': 'skeleton skeleton-text',
|
||||||
|
'card': 'skeleton skeleton-card',
|
||||||
|
'image': 'skeleton skeleton-image'
|
||||||
|
};
|
||||||
|
|
||||||
|
const skeletonClass = skeletonClasses[type] || skeletonClasses.card;
|
||||||
|
|
||||||
|
for (let i = 0; i < count; i++) {
|
||||||
|
const skeleton = document.createElement('div');
|
||||||
|
skeleton.className = skeletonClass;
|
||||||
|
container.appendChild(skeleton);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 显示页面切换动画
|
||||||
|
* @param {string} direction - 切换方向: 'left', 'right', 'up', 'down'
|
||||||
|
*/
|
||||||
|
function showPageTransition(direction = 'right') {
|
||||||
|
const transition = document.getElementById('page-transition');
|
||||||
|
if (!transition) return;
|
||||||
|
|
||||||
|
const directions = {
|
||||||
|
'left': 'slideInLeft',
|
||||||
|
'right': 'slideInRight',
|
||||||
|
'up': 'slideInUp',
|
||||||
|
'down': 'slideInDown'
|
||||||
|
};
|
||||||
|
|
||||||
|
const animation = directions[direction] || 'slideInRight';
|
||||||
|
transition.className = `page-transition animate-${animation}`;
|
||||||
|
transition.classList.add('active');
|
||||||
|
|
||||||
|
return new Promise(resolve => {
|
||||||
|
setTimeout(() => {
|
||||||
|
transition.classList.remove('active');
|
||||||
|
setTimeout(resolve, 300);
|
||||||
|
}, 300);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 显示元素动画
|
||||||
|
* @param {HTMLElement} element - 要动画的元素
|
||||||
|
* @param {string} animation - 动画名称
|
||||||
|
* @param {number} duration - 动画持续时间(毫秒)
|
||||||
|
*/
|
||||||
|
function animateElement(element, animation = 'fadeIn', duration = 300) {
|
||||||
|
if (!element) return;
|
||||||
|
|
||||||
|
element.style.animation = `${animation} ${duration}ms ease`;
|
||||||
|
element.classList.add('animated');
|
||||||
|
|
||||||
|
// 动画完成后移除动画类
|
||||||
|
setTimeout(() => {
|
||||||
|
element.style.animation = '';
|
||||||
|
}, duration);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 创建波纹效果
|
||||||
|
* @param {Event} event - 点击事件
|
||||||
|
* @param {string} color - 波纹颜色
|
||||||
|
*/
|
||||||
|
function createRippleEffect(event, color = 'rgba(255, 255, 255, 0.7)') {
|
||||||
|
const button = event.currentTarget;
|
||||||
|
const circle = document.createElement('span');
|
||||||
|
const diameter = Math.max(button.clientWidth, button.clientHeight);
|
||||||
|
const radius = diameter / 2;
|
||||||
|
|
||||||
|
circle.style.width = circle.style.height = `${diameter}px`;
|
||||||
|
circle.style.left = `${event.clientX - button.offsetLeft - radius}px`;
|
||||||
|
circle.style.top = `${event.clientY - button.offsetTop - radius}px`;
|
||||||
|
circle.style.backgroundColor = color;
|
||||||
|
circle.style.position = 'absolute';
|
||||||
|
circle.style.borderRadius = '50%';
|
||||||
|
circle.style.transform = 'scale(0)';
|
||||||
|
circle.style.animation = 'ripple 600ms linear';
|
||||||
|
|
||||||
|
const ripple = button.querySelector('.ripple');
|
||||||
|
if (ripple) {
|
||||||
|
ripple.remove();
|
||||||
|
}
|
||||||
|
|
||||||
|
button.appendChild(circle);
|
||||||
|
|
||||||
|
// 动画完成后移除波纹元素
|
||||||
|
setTimeout(() => {
|
||||||
|
if (circle.parentNode === button) {
|
||||||
|
button.removeChild(circle);
|
||||||
|
}
|
||||||
|
}, 600);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 添加波纹效果到按钮
|
||||||
|
* @param {string} selector - 按钮选择器
|
||||||
|
*/
|
||||||
|
function addRippleEffectToButtons(selector = '.btn') {
|
||||||
|
const buttons = document.querySelectorAll(selector);
|
||||||
|
|
||||||
|
buttons.forEach(button => {
|
||||||
|
button.style.position = 'relative';
|
||||||
|
button.style.overflow = 'hidden';
|
||||||
|
|
||||||
|
button.addEventListener('click', function(event) {
|
||||||
|
createRippleEffect(event);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
// 添加波纹动画CSS
|
||||||
|
if (!document.querySelector('#ripple-styles')) {
|
||||||
|
const style = document.createElement('style');
|
||||||
|
style.id = 'ripple-styles';
|
||||||
|
style.textContent = `
|
||||||
|
@keyframes ripple {
|
||||||
|
to {
|
||||||
|
transform: scale(4);
|
||||||
|
opacity: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
document.head.appendChild(style);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 初始化图片加载动画
|
||||||
|
*/
|
||||||
|
function initImageLoadingAnimations() {
|
||||||
|
const images = document.querySelectorAll('img:not([data-src])');
|
||||||
|
|
||||||
|
images.forEach(img => {
|
||||||
|
if (!img.complete) {
|
||||||
|
img.classList.add('loading');
|
||||||
|
|
||||||
|
img.addEventListener('load', function() {
|
||||||
|
this.classList.remove('loading');
|
||||||
|
this.classList.add('loaded');
|
||||||
|
animateElement(this, 'scaleIn', 500);
|
||||||
|
});
|
||||||
|
|
||||||
|
img.addEventListener('error', function() {
|
||||||
|
this.classList.remove('loading');
|
||||||
|
this.classList.add('error');
|
||||||
|
console.error('图片加载失败:', this.src);
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
img.classList.add('loaded');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 添加滚动到顶部按钮
|
||||||
|
*/
|
||||||
|
function initScrollToTopButton() {
|
||||||
|
// 创建按钮
|
||||||
|
const button = document.createElement('button');
|
||||||
|
button.id = 'scroll-to-top';
|
||||||
|
button.className = 'scroll-to-top-btn';
|
||||||
|
button.innerHTML = '<i class="fas fa-chevron-up"></i>';
|
||||||
|
button.title = '回到顶部';
|
||||||
|
button.style.cssText = `
|
||||||
|
position: fixed;
|
||||||
|
bottom: 30px;
|
||||||
|
right: 30px;
|
||||||
|
width: 50px;
|
||||||
|
height: 50px;
|
||||||
|
background-color: var(--primary-color);
|
||||||
|
color: white;
|
||||||
|
border: none;
|
||||||
|
border-radius: 50%;
|
||||||
|
cursor: pointer;
|
||||||
|
opacity: 0;
|
||||||
|
visibility: hidden;
|
||||||
|
transition: all 0.3s ease;
|
||||||
|
box-shadow: var(--shadow-lg);
|
||||||
|
z-index: 1000;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
font-size: 20px;
|
||||||
|
`;
|
||||||
|
|
||||||
|
document.body.appendChild(button);
|
||||||
|
|
||||||
|
// 滚动事件监听
|
||||||
|
window.addEventListener('scroll', () => {
|
||||||
|
if (window.pageYOffset > 300) {
|
||||||
|
button.style.opacity = '1';
|
||||||
|
button.style.visibility = 'visible';
|
||||||
|
button.style.transform = 'translateY(0)';
|
||||||
|
} else {
|
||||||
|
button.style.opacity = '0';
|
||||||
|
button.style.visibility = 'hidden';
|
||||||
|
button.style.transform = 'translateY(20px)';
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// 点击事件
|
||||||
|
button.addEventListener('click', () => {
|
||||||
|
window.scrollTo({
|
||||||
|
top: 0,
|
||||||
|
behavior: 'smooth'
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
// 添加悬停效果
|
||||||
|
button.addEventListener('mouseenter', () => {
|
||||||
|
button.style.backgroundColor = 'var(--primary-dark)';
|
||||||
|
button.style.transform = 'scale(1.1)';
|
||||||
|
});
|
||||||
|
|
||||||
|
button.addEventListener('mouseleave', () => {
|
||||||
|
button.style.backgroundColor = 'var(--primary-color)';
|
||||||
|
button.style.transform = 'scale(1)';
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 性能优化:减少重绘
|
||||||
|
*/
|
||||||
|
function optimizeForPerformance() {
|
||||||
|
// 为动画元素添加 will-change
|
||||||
|
const animatedElements = document.querySelectorAll('.animate-on-scroll, .parallax, .counter');
|
||||||
|
animatedElements.forEach(element => {
|
||||||
|
element.style.willChange = 'transform, opacity';
|
||||||
|
});
|
||||||
|
|
||||||
|
// 使用 requestAnimationFrame 优化动画
|
||||||
|
let ticking = false;
|
||||||
|
|
||||||
|
window.addEventListener('scroll', () => {
|
||||||
|
if (!ticking) {
|
||||||
|
window.requestAnimationFrame(() => {
|
||||||
|
// 执行滚动相关的动画
|
||||||
|
ticking = false;
|
||||||
|
});
|
||||||
|
ticking = true;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// 使用 transform 和 opacity 进行动画(GPU加速)
|
||||||
|
console.log('性能优化已应用:使用GPU加速动画');
|
||||||
|
}
|
||||||
|
|
||||||
|
// DOM加载完成后初始化动画
|
||||||
|
if (document.readyState === 'loading') {
|
||||||
|
document.addEventListener('DOMContentLoaded', () => {
|
||||||
|
initAnimations();
|
||||||
|
addRippleEffectToButtons();
|
||||||
|
initImageLoadingAnimations();
|
||||||
|
initScrollToTopButton();
|
||||||
|
optimizeForPerformance();
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
initAnimations();
|
||||||
|
addRippleEffectToButtons();
|
||||||
|
initImageLoadingAnimations();
|
||||||
|
initScrollToTopButton();
|
||||||
|
optimizeForPerformance();
|
||||||
|
}
|
||||||
|
|
||||||
|
// 导出函数
|
||||||
|
if (typeof module !== 'undefined' && module.exports) {
|
||||||
|
module.exports = {
|
||||||
|
initAnimations,
|
||||||
|
showPageTransition,
|
||||||
|
animateElement,
|
||||||
|
createSkeletonPlaceholders,
|
||||||
|
addRippleEffectToButtons
|
||||||
|
};
|
||||||
|
}
|
||||||
408
website/public/js/main.js
Normal file
408
website/public/js/main.js
Normal file
@@ -0,0 +1,408 @@
|
|||||||
|
// 主JavaScript文件
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 初始化导航栏
|
||||||
|
*/
|
||||||
|
function initNavigation() {
|
||||||
|
const navbarLinks = document.querySelectorAll('.navbar-link');
|
||||||
|
const currentPage = document.body.classList.contains('page-home') ? 'home' :
|
||||||
|
document.body.classList.contains('page-features') ? 'features' :
|
||||||
|
document.body.classList.contains('page-architecture') ? 'architecture' :
|
||||||
|
document.body.classList.contains('page-plugins') ? 'plugins' : 'home';
|
||||||
|
|
||||||
|
navbarLinks.forEach(link => {
|
||||||
|
if (link.getAttribute('data-page') === currentPage) {
|
||||||
|
link.classList.add('active');
|
||||||
|
}
|
||||||
|
|
||||||
|
// 添加点击事件
|
||||||
|
link.addEventListener('click', function(e) {
|
||||||
|
if (this.getAttribute('href') === '#') {
|
||||||
|
e.preventDefault();
|
||||||
|
const page = this.getAttribute('data-page');
|
||||||
|
if (page && page !== currentPage) {
|
||||||
|
navigateToPage(page);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 初始化页面切换
|
||||||
|
*/
|
||||||
|
function initPageTransitions() {
|
||||||
|
const pageTransition = document.getElementById('page-transition');
|
||||||
|
if (!pageTransition) return;
|
||||||
|
|
||||||
|
// 监听页面链接点击
|
||||||
|
document.addEventListener('click', function(e) {
|
||||||
|
const link = e.target.closest('a[data-page]');
|
||||||
|
if (link && link.getAttribute('href') === '#') {
|
||||||
|
e.preventDefault();
|
||||||
|
const page = link.getAttribute('data-page');
|
||||||
|
const currentPage = document.body.className.match(/page-(\w+)/)?.[1];
|
||||||
|
|
||||||
|
if (page && page !== currentPage) {
|
||||||
|
// 显示页面切换动画
|
||||||
|
pageTransition.classList.add('active');
|
||||||
|
|
||||||
|
// 延迟导航
|
||||||
|
setTimeout(() => {
|
||||||
|
navigateToPage(page);
|
||||||
|
}, 300);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 导航到指定页面
|
||||||
|
* @param {string} page - 页面名称
|
||||||
|
*/
|
||||||
|
function navigateToPage(page) {
|
||||||
|
console.log(`导航到页面: ${page}`);
|
||||||
|
|
||||||
|
// 这里应该使用前端路由或实际页面跳转
|
||||||
|
// 目前先更新URL和页面状态
|
||||||
|
const url = `/${page === 'home' ? '' : page}`;
|
||||||
|
window.history.pushState({ page }, '', url);
|
||||||
|
|
||||||
|
// 更新页面内容
|
||||||
|
updatePageContent(page);
|
||||||
|
|
||||||
|
// 更新导航栏激活状态
|
||||||
|
updateNavigation(page);
|
||||||
|
|
||||||
|
// 隐藏页面切换动画
|
||||||
|
const pageTransition = document.getElementById('page-transition');
|
||||||
|
if (pageTransition) {
|
||||||
|
setTimeout(() => {
|
||||||
|
pageTransition.classList.remove('active');
|
||||||
|
}, 300);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 更新页面内容
|
||||||
|
* @param {string} page - 页面名称
|
||||||
|
*/
|
||||||
|
function updatePageContent(page) {
|
||||||
|
// 更新body类名
|
||||||
|
document.body.className = document.body.className.replace(/page-\w+/, `page-${page}`);
|
||||||
|
|
||||||
|
// 更新页面标题
|
||||||
|
const pageTitles = {
|
||||||
|
'home': 'FutureOSS - 插件化运行时框架',
|
||||||
|
'features': '核心特性 - FutureOSS',
|
||||||
|
'architecture': '系统架构 - FutureOSS',
|
||||||
|
'plugins': '插件生态 - FutureOSS'
|
||||||
|
};
|
||||||
|
|
||||||
|
document.title = pageTitles[page] || pageTitles.home;
|
||||||
|
|
||||||
|
// 这里应该加载新的页面内容
|
||||||
|
// 目前先滚动到顶部
|
||||||
|
window.scrollTo({ top: 0, behavior: 'smooth' });
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 更新导航栏激活状态
|
||||||
|
* @param {string} page - 页面名称
|
||||||
|
*/
|
||||||
|
function updateNavigation(page) {
|
||||||
|
const navbarLinks = document.querySelectorAll('.navbar-link');
|
||||||
|
navbarLinks.forEach(link => {
|
||||||
|
link.classList.remove('active');
|
||||||
|
if (link.getAttribute('data-page') === page) {
|
||||||
|
link.classList.add('active');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 初始化按钮交互
|
||||||
|
*/
|
||||||
|
function initButtonInteractions() {
|
||||||
|
// 为所有按钮添加悬停效果
|
||||||
|
const buttons = document.querySelectorAll('.btn');
|
||||||
|
buttons.forEach(button => {
|
||||||
|
button.addEventListener('mouseenter', function() {
|
||||||
|
this.style.transform = 'translateY(-2px)';
|
||||||
|
});
|
||||||
|
|
||||||
|
button.addEventListener('mouseleave', function() {
|
||||||
|
this.style.transform = 'translateY(0)';
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
// 为卡片添加悬停效果
|
||||||
|
const cards = document.querySelectorAll('.card, .feature-card');
|
||||||
|
cards.forEach(card => {
|
||||||
|
card.addEventListener('mouseenter', function() {
|
||||||
|
this.style.transform = 'translateY(-4px)';
|
||||||
|
});
|
||||||
|
|
||||||
|
card.addEventListener('mouseleave', function() {
|
||||||
|
this.style.transform = 'translateY(0)';
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 初始化滚动效果
|
||||||
|
*/
|
||||||
|
function initScrollEffects() {
|
||||||
|
let lastScrollTop = 0;
|
||||||
|
const navbar = document.querySelector('.navbar');
|
||||||
|
|
||||||
|
if (!navbar) return;
|
||||||
|
|
||||||
|
window.addEventListener('scroll', function() {
|
||||||
|
const scrollTop = window.pageYOffset || document.documentElement.scrollTop;
|
||||||
|
|
||||||
|
// 隐藏/显示导航栏
|
||||||
|
if (scrollTop > lastScrollTop && scrollTop > 100) {
|
||||||
|
// 向下滚动,隐藏导航栏
|
||||||
|
navbar.style.transform = 'translateY(-100%)';
|
||||||
|
} else {
|
||||||
|
// 向上滚动,显示导航栏
|
||||||
|
navbar.style.transform = 'translateY(0)';
|
||||||
|
}
|
||||||
|
|
||||||
|
lastScrollTop = scrollTop;
|
||||||
|
|
||||||
|
// 添加滚动阴影
|
||||||
|
if (scrollTop > 10) {
|
||||||
|
navbar.style.boxShadow = 'var(--shadow-md)';
|
||||||
|
} else {
|
||||||
|
navbar.style.boxShadow = 'var(--shadow-sm)';
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 显示消息提示
|
||||||
|
* @param {string} message - 消息内容
|
||||||
|
* @param {string} type - 消息类型: 'success', 'error', 'info', 'warning'
|
||||||
|
*/
|
||||||
|
function showMessage(message, type = 'info') {
|
||||||
|
const messageDiv = document.createElement('div');
|
||||||
|
messageDiv.className = `message message-${type}`;
|
||||||
|
messageDiv.textContent = message;
|
||||||
|
messageDiv.style.cssText = `
|
||||||
|
position: fixed;
|
||||||
|
top: 20px;
|
||||||
|
right: 20px;
|
||||||
|
padding: 12px 20px;
|
||||||
|
background-color: ${getMessageColor(type)};
|
||||||
|
color: white;
|
||||||
|
border-radius: var(--radius-md);
|
||||||
|
box-shadow: var(--shadow-lg);
|
||||||
|
z-index: 9999;
|
||||||
|
animation: fadeIn 0.3s ease;
|
||||||
|
`;
|
||||||
|
|
||||||
|
document.body.appendChild(messageDiv);
|
||||||
|
|
||||||
|
// 3秒后自动消失
|
||||||
|
setTimeout(() => {
|
||||||
|
messageDiv.style.animation = 'fadeOut 0.3s ease';
|
||||||
|
setTimeout(() => {
|
||||||
|
if (messageDiv.parentNode) {
|
||||||
|
messageDiv.parentNode.removeChild(messageDiv);
|
||||||
|
}
|
||||||
|
}, 300);
|
||||||
|
}, 3000);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取消息颜色
|
||||||
|
* @param {string} type - 消息类型
|
||||||
|
* @returns {string} - 颜色值
|
||||||
|
*/
|
||||||
|
function getMessageColor(type) {
|
||||||
|
const colors = {
|
||||||
|
'success': 'var(--success-color)',
|
||||||
|
'error': 'var(--danger-color)',
|
||||||
|
'info': 'var(--info-color)',
|
||||||
|
'warning': 'var(--warning-color)'
|
||||||
|
};
|
||||||
|
return colors[type] || colors.info;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 防抖函数
|
||||||
|
* @param {Function} func - 要执行的函数
|
||||||
|
* @param {number} wait - 等待时间(毫秒)
|
||||||
|
* @returns {Function} - 防抖后的函数
|
||||||
|
*/
|
||||||
|
function debounce(func, wait) {
|
||||||
|
let timeout;
|
||||||
|
return function executedFunction(...args) {
|
||||||
|
const later = () => {
|
||||||
|
clearTimeout(timeout);
|
||||||
|
func(...args);
|
||||||
|
};
|
||||||
|
clearTimeout(timeout);
|
||||||
|
timeout = setTimeout(later, wait);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 节流函数
|
||||||
|
* @param {Function} func - 要执行的函数
|
||||||
|
* @param {number} limit - 限制时间(毫秒)
|
||||||
|
* @returns {Function} - 节流后的函数
|
||||||
|
*/
|
||||||
|
function throttle(func, limit) {
|
||||||
|
let inThrottle;
|
||||||
|
return function(...args) {
|
||||||
|
if (!inThrottle) {
|
||||||
|
func.apply(this, args);
|
||||||
|
inThrottle = true;
|
||||||
|
setTimeout(() => inThrottle = false, limit);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 隐藏加载动画
|
||||||
|
*/
|
||||||
|
function hideLoadingAnimation() {
|
||||||
|
const loadingOverlay = document.getElementById('loading-overlay');
|
||||||
|
if (loadingOverlay) {
|
||||||
|
// 简单隐藏
|
||||||
|
loadingOverlay.style.display = 'none';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 显示加载动画
|
||||||
|
*/
|
||||||
|
function showLoadingAnimation() {
|
||||||
|
const loadingOverlay = document.getElementById('loading-overlay');
|
||||||
|
if (loadingOverlay) {
|
||||||
|
loadingOverlay.style.display = 'flex';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 初始化页面
|
||||||
|
*/
|
||||||
|
function initPage() {
|
||||||
|
console.log('FutureOSS 网站初始化...');
|
||||||
|
|
||||||
|
// 确保加载动画被隐藏
|
||||||
|
setTimeout(() => {
|
||||||
|
hideLoadingAnimation();
|
||||||
|
}, 100);
|
||||||
|
|
||||||
|
// 初始化导航栏
|
||||||
|
initNavigation();
|
||||||
|
|
||||||
|
// 初始化页面切换
|
||||||
|
initPageTransitions();
|
||||||
|
|
||||||
|
// 初始化按钮交互
|
||||||
|
initButtonInteractions();
|
||||||
|
|
||||||
|
// 初始化滚动效果
|
||||||
|
initScrollEffects();
|
||||||
|
|
||||||
|
// 初始化图片懒加载
|
||||||
|
initLazyLoading();
|
||||||
|
|
||||||
|
console.log('页面初始化完成');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 初始化图片懒加载
|
||||||
|
*/
|
||||||
|
function initLazyLoading() {
|
||||||
|
const lazyImages = document.querySelectorAll('img[data-src]');
|
||||||
|
|
||||||
|
if ('IntersectionObserver' in window) {
|
||||||
|
const imageObserver = new IntersectionObserver((entries, observer) => {
|
||||||
|
entries.forEach(entry => {
|
||||||
|
if (entry.isIntersecting) {
|
||||||
|
const img = entry.target;
|
||||||
|
img.src = img.dataset.src;
|
||||||
|
img.classList.remove('lazy-image');
|
||||||
|
observer.unobserve(img);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
lazyImages.forEach(img => imageObserver.observe(img));
|
||||||
|
} else {
|
||||||
|
// 回退方案:直接加载所有图片
|
||||||
|
lazyImages.forEach(img => {
|
||||||
|
img.src = img.dataset.src;
|
||||||
|
img.classList.remove('lazy-image');
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 性能监控
|
||||||
|
*/
|
||||||
|
function initPerformanceMonitoring() {
|
||||||
|
// 监控页面加载性能
|
||||||
|
window.addEventListener('load', () => {
|
||||||
|
if (window.performance) {
|
||||||
|
const timing = performance.timing;
|
||||||
|
const loadTime = timing.loadEventEnd - timing.navigationStart;
|
||||||
|
console.log(`页面加载时间: ${loadTime}ms`);
|
||||||
|
|
||||||
|
if (loadTime > 3000) {
|
||||||
|
console.warn('页面加载时间过长,建议优化');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// 监控长任务
|
||||||
|
if ('PerformanceObserver' in window) {
|
||||||
|
const observer = new PerformanceObserver((list) => {
|
||||||
|
for (const entry of list.getEntries()) {
|
||||||
|
if (entry.duration > 50) {
|
||||||
|
console.warn('检测到长任务:', entry);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
observer.observe({ entryTypes: ['longtask'] });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 页面加载完成后初始化
|
||||||
|
document.addEventListener('DOMContentLoaded', () => {
|
||||||
|
console.log('DOMContentLoaded 事件触发');
|
||||||
|
initPerformanceMonitoring();
|
||||||
|
initPage();
|
||||||
|
});
|
||||||
|
|
||||||
|
// 窗口加载完成事件作为最后保障
|
||||||
|
window.addEventListener('load', () => {
|
||||||
|
console.log('window.load 事件触发');
|
||||||
|
// 确保加载动画被隐藏
|
||||||
|
hideLoadingAnimation();
|
||||||
|
});
|
||||||
|
|
||||||
|
// 立即隐藏加载动画(如果可能)
|
||||||
|
if (document.readyState !== 'loading') {
|
||||||
|
console.log('文档已加载完成,直接初始化');
|
||||||
|
initPerformanceMonitoring();
|
||||||
|
initPage();
|
||||||
|
}
|
||||||
|
|
||||||
|
// 导出函数供其他模块使用
|
||||||
|
if (typeof module !== 'undefined' && module.exports) {
|
||||||
|
module.exports = {
|
||||||
|
initPage,
|
||||||
|
navigateToPage,
|
||||||
|
showMessage,
|
||||||
|
debounce,
|
||||||
|
throttle
|
||||||
|
};
|
||||||
|
}
|
||||||
0
website/public/js/pages/architecture.js
Normal file
0
website/public/js/pages/architecture.js
Normal file
0
website/public/js/pages/features.js
Normal file
0
website/public/js/pages/features.js
Normal file
0
website/public/js/pages/home.js
Normal file
0
website/public/js/pages/home.js
Normal file
0
website/public/js/pages/plugins.js
Normal file
0
website/public/js/pages/plugins.js
Normal file
264
website/public/js/router.js
Normal file
264
website/public/js/router.js
Normal file
@@ -0,0 +1,264 @@
|
|||||||
|
// 前端路由系统
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 路由配置
|
||||||
|
*/
|
||||||
|
const routes = {
|
||||||
|
'/': {
|
||||||
|
name: 'home',
|
||||||
|
title: 'FutureOSS - 插件化运行时框架',
|
||||||
|
description: 'FutureOSS 是一个高度插件化的运行时框架,专为现代应用设计。'
|
||||||
|
},
|
||||||
|
'/features': {
|
||||||
|
name: 'features',
|
||||||
|
title: '核心特性 - FutureOSS',
|
||||||
|
description: 'FutureOSS 的六大核心特性,专为现代开发需求设计。'
|
||||||
|
},
|
||||||
|
'/architecture': {
|
||||||
|
name: 'architecture',
|
||||||
|
title: '系统架构 - FutureOSS',
|
||||||
|
description: '深入了解 FutureOSS 的微内核架构和插件化设计。'
|
||||||
|
},
|
||||||
|
'/plugins': {
|
||||||
|
name: 'plugins',
|
||||||
|
title: '插件生态 - FutureOSS',
|
||||||
|
description: '探索 FutureOSS 丰富的插件生态系统。'
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 初始化路由
|
||||||
|
*/
|
||||||
|
function initRouter() {
|
||||||
|
console.log('初始化前端路由...');
|
||||||
|
|
||||||
|
// 监听浏览器前进/后退
|
||||||
|
window.addEventListener('popstate', handlePopState);
|
||||||
|
|
||||||
|
// 拦截链接点击
|
||||||
|
document.addEventListener('click', handleLinkClick);
|
||||||
|
|
||||||
|
// 初始路由处理
|
||||||
|
handleRoute(window.location.pathname);
|
||||||
|
|
||||||
|
console.log('路由初始化完成');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 处理路由
|
||||||
|
* @param {string} path - 路径
|
||||||
|
*/
|
||||||
|
function handleRoute(path) {
|
||||||
|
const route = routes[path] || routes['/'];
|
||||||
|
|
||||||
|
// 更新页面状态
|
||||||
|
updatePageState(route.name);
|
||||||
|
|
||||||
|
// 更新文档标题和描述
|
||||||
|
document.title = route.title;
|
||||||
|
updateMetaDescription(route.description);
|
||||||
|
|
||||||
|
// 触发路由变化事件
|
||||||
|
window.dispatchEvent(new CustomEvent('routechange', {
|
||||||
|
detail: { route: route.name, path: path }
|
||||||
|
}));
|
||||||
|
|
||||||
|
console.log(`路由切换到: ${path} (${route.name})`);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 更新页面状态
|
||||||
|
* @param {string} pageName - 页面名称
|
||||||
|
*/
|
||||||
|
function updatePageState(pageName) {
|
||||||
|
// 更新body类名
|
||||||
|
const bodyClass = document.body.className;
|
||||||
|
const newClass = bodyClass.replace(/page-\w+/, `page-${pageName}`);
|
||||||
|
document.body.className = newClass.includes('page-') ? newClass : `${newClass} page-${pageName}`;
|
||||||
|
|
||||||
|
// 更新导航栏激活状态
|
||||||
|
updateNavActiveState(pageName);
|
||||||
|
|
||||||
|
// 滚动到顶部
|
||||||
|
window.scrollTo({ top: 0, behavior: 'smooth' });
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 更新导航栏激活状态
|
||||||
|
* @param {string} pageName - 页面名称
|
||||||
|
*/
|
||||||
|
function updateNavActiveState(pageName) {
|
||||||
|
const navLinks = document.querySelectorAll('.navbar-link');
|
||||||
|
navLinks.forEach(link => {
|
||||||
|
link.classList.remove('active');
|
||||||
|
if (link.getAttribute('data-page') === pageName) {
|
||||||
|
link.classList.add('active');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 更新meta描述
|
||||||
|
* @param {string} description - 描述内容
|
||||||
|
*/
|
||||||
|
function updateMetaDescription(description) {
|
||||||
|
let metaDescription = document.querySelector('meta[name="description"]');
|
||||||
|
if (!metaDescription) {
|
||||||
|
metaDescription = document.createElement('meta');
|
||||||
|
metaDescription.name = 'description';
|
||||||
|
document.head.appendChild(metaDescription);
|
||||||
|
}
|
||||||
|
metaDescription.content = description;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 处理链接点击
|
||||||
|
* @param {Event} event - 点击事件
|
||||||
|
*/
|
||||||
|
function handleLinkClick(event) {
|
||||||
|
const link = event.target.closest('a[data-page]');
|
||||||
|
if (!link) return;
|
||||||
|
|
||||||
|
const href = link.getAttribute('href');
|
||||||
|
const page = link.getAttribute('data-page');
|
||||||
|
|
||||||
|
// 如果是内部链接且不是当前页面
|
||||||
|
if (href === '#' && page) {
|
||||||
|
event.preventDefault();
|
||||||
|
|
||||||
|
const currentPage = document.body.className.match(/page-(\w+)/)?.[1];
|
||||||
|
if (page === currentPage) return;
|
||||||
|
|
||||||
|
// 显示页面切换动画
|
||||||
|
showPageTransition();
|
||||||
|
|
||||||
|
// 延迟导航
|
||||||
|
setTimeout(() => {
|
||||||
|
navigateTo(page);
|
||||||
|
}, 300);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 处理浏览器前进/后退
|
||||||
|
* @param {PopStateEvent} event - popstate事件
|
||||||
|
*/
|
||||||
|
function handlePopState(event) {
|
||||||
|
if (event.state && event.state.page) {
|
||||||
|
handleRoute(`/${event.state.page === 'home' ? '' : event.state.page}`);
|
||||||
|
} else {
|
||||||
|
handleRoute(window.location.pathname);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 导航到指定页面
|
||||||
|
* @param {string} pageName - 页面名称
|
||||||
|
*/
|
||||||
|
function navigateTo(pageName) {
|
||||||
|
const path = pageName === 'home' ? '/' : `/${pageName}`;
|
||||||
|
const route = routes[path];
|
||||||
|
|
||||||
|
if (!route) {
|
||||||
|
console.error(`路由未找到: ${pageName}`);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 更新浏览器历史
|
||||||
|
window.history.pushState({ page: pageName }, route.title, path);
|
||||||
|
|
||||||
|
// 处理路由
|
||||||
|
handleRoute(path);
|
||||||
|
|
||||||
|
// 隐藏页面切换动画
|
||||||
|
hidePageTransition();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 显示页面切换动画
|
||||||
|
*/
|
||||||
|
function showPageTransition() {
|
||||||
|
const transition = document.getElementById('page-transition');
|
||||||
|
if (transition) {
|
||||||
|
transition.classList.add('active');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 隐藏页面切换动画
|
||||||
|
*/
|
||||||
|
function hidePageTransition() {
|
||||||
|
const transition = document.getElementById('page-transition');
|
||||||
|
if (transition) {
|
||||||
|
setTimeout(() => {
|
||||||
|
transition.classList.remove('active');
|
||||||
|
}, 300);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取当前路由信息
|
||||||
|
* @returns {Object} 当前路由信息
|
||||||
|
*/
|
||||||
|
function getCurrentRoute() {
|
||||||
|
const path = window.location.pathname;
|
||||||
|
return routes[path] || routes['/'];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 检查路由是否存在
|
||||||
|
* @param {string} path - 路径
|
||||||
|
* @returns {boolean} 是否存在
|
||||||
|
*/
|
||||||
|
function routeExists(path) {
|
||||||
|
return routes.hasOwnProperty(path);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 添加路由
|
||||||
|
* @param {string} path - 路径
|
||||||
|
* @param {Object} config - 路由配置
|
||||||
|
*/
|
||||||
|
function addRoute(path, config) {
|
||||||
|
if (routes[path]) {
|
||||||
|
console.warn(`路由已存在: ${path}`);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
routes[path] = {
|
||||||
|
name: config.name || path.slice(1),
|
||||||
|
title: config.title || 'FutureOSS',
|
||||||
|
description: config.description || ''
|
||||||
|
};
|
||||||
|
|
||||||
|
console.log(`路由添加成功: ${path}`);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 移除路由
|
||||||
|
* @param {string} path - 路径
|
||||||
|
*/
|
||||||
|
function removeRoute(path) {
|
||||||
|
if (!routes[path]) {
|
||||||
|
console.warn(`路由不存在: ${path}`);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
delete routes[path];
|
||||||
|
console.log(`路由移除成功: ${path}`);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 导出路由函数
|
||||||
|
if (typeof module !== 'undefined' && module.exports) {
|
||||||
|
module.exports = {
|
||||||
|
initRouter,
|
||||||
|
navigateTo,
|
||||||
|
getCurrentRoute,
|
||||||
|
routeExists,
|
||||||
|
addRoute,
|
||||||
|
removeRoute,
|
||||||
|
routes
|
||||||
|
};
|
||||||
|
}
|
||||||
88
website/src/controllers/apiController.js
Normal file
88
website/src/controllers/apiController.js
Normal file
@@ -0,0 +1,88 @@
|
|||||||
|
/**
|
||||||
|
* API控制器
|
||||||
|
* 提供系统状态和健康检查接口
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 健康检查
|
||||||
|
*/
|
||||||
|
exports.healthCheck = (req, res) => {
|
||||||
|
const health = {
|
||||||
|
status: 'healthy',
|
||||||
|
timestamp: new Date().toISOString(),
|
||||||
|
uptime: process.uptime(),
|
||||||
|
memory: process.memoryUsage(),
|
||||||
|
nodeVersion: process.version,
|
||||||
|
platform: process.platform,
|
||||||
|
pid: process.pid
|
||||||
|
};
|
||||||
|
|
||||||
|
res.json(health);
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 性能指标
|
||||||
|
*/
|
||||||
|
exports.performanceMetrics = (req, res) => {
|
||||||
|
const metrics = {
|
||||||
|
timestamp: new Date().toISOString(),
|
||||||
|
memory: {
|
||||||
|
heapUsed: process.memoryUsage().heapUsed,
|
||||||
|
heapTotal: process.memoryUsage().heapTotal,
|
||||||
|
rss: process.memoryUsage().rss
|
||||||
|
},
|
||||||
|
cpu: process.cpuUsage(),
|
||||||
|
uptime: process.uptime(),
|
||||||
|
activeHandles: process._getActiveHandles().length,
|
||||||
|
activeRequests: process._getActiveRequests().length
|
||||||
|
};
|
||||||
|
|
||||||
|
res.json(metrics);
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 服务器信息
|
||||||
|
*/
|
||||||
|
exports.serverInfo = (req, res) => {
|
||||||
|
const info = {
|
||||||
|
name: 'FutureOSS 官方网站',
|
||||||
|
version: '1.0.0',
|
||||||
|
description: 'FutureOSS 插件化运行时框架官方网站',
|
||||||
|
repository: 'https://gitee.com/starlight-apk/future-oss',
|
||||||
|
endpoints: [
|
||||||
|
{ path: '/', method: 'GET', description: '首页' },
|
||||||
|
{ path: '/features', method: 'GET', description: '特性页面' },
|
||||||
|
{ path: '/architecture', method: 'GET', description: '架构页面' },
|
||||||
|
{ path: '/plugins', method: 'GET', description: '插件页面' },
|
||||||
|
{ path: '/api/health', method: 'GET', description: '健康检查' },
|
||||||
|
{ path: '/api/metrics', method: 'GET', description: '性能指标' },
|
||||||
|
{ path: '/api/info', method: 'GET', description: '服务器信息' }
|
||||||
|
],
|
||||||
|
environment: process.env.NODE_ENV || 'development'
|
||||||
|
};
|
||||||
|
|
||||||
|
res.json(info);
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 压力测试端点(仅开发环境)
|
||||||
|
*/
|
||||||
|
exports.stressTest = (req, res) => {
|
||||||
|
if (process.env.NODE_ENV !== 'development') {
|
||||||
|
return res.status(403).json({ error: '仅开发环境可用' });
|
||||||
|
}
|
||||||
|
|
||||||
|
const n = parseInt(req.query.n) || 1000000;
|
||||||
|
let result = 0;
|
||||||
|
|
||||||
|
// 模拟CPU密集型操作
|
||||||
|
for (let i = 0; i < n; i++) {
|
||||||
|
result += Math.sqrt(i) * Math.sin(i);
|
||||||
|
}
|
||||||
|
|
||||||
|
res.json({
|
||||||
|
result: result,
|
||||||
|
iterations: n,
|
||||||
|
message: '压力测试完成'
|
||||||
|
});
|
||||||
|
};
|
||||||
48
website/src/controllers/pagesController.js
Normal file
48
website/src/controllers/pagesController.js
Normal file
@@ -0,0 +1,48 @@
|
|||||||
|
/**
|
||||||
|
* 页面控制器
|
||||||
|
* 每个方法只负责渲染一个页面,业务逻辑分离
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 渲染首页
|
||||||
|
*/
|
||||||
|
exports.renderHome = (req, res) => {
|
||||||
|
res.render('pages/home', {
|
||||||
|
title: 'FutureOSS - 一切皆为插件的开发者运行时框架',
|
||||||
|
page: 'home',
|
||||||
|
description: 'FutureOSS 是一款面向开发者的插件化运行时框架,秉承「一切皆为插件」的设计理念,让功能扩展变得前所未有的简单。'
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 渲染特性页面
|
||||||
|
*/
|
||||||
|
exports.renderFeatures = (req, res) => {
|
||||||
|
res.render('pages/features', {
|
||||||
|
title: '核心特性 - FutureOSS',
|
||||||
|
page: 'features',
|
||||||
|
description: 'FutureOSS 的核心特性:插件化架构、安全沙箱、热重载支持、可视化控制台、双协议服务、依赖自动解析。'
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 渲染架构页面
|
||||||
|
*/
|
||||||
|
exports.renderArchitecture = (req, res) => {
|
||||||
|
res.render('pages/architecture', {
|
||||||
|
title: '技术架构 - FutureOSS',
|
||||||
|
page: 'architecture',
|
||||||
|
description: '深入了解 FutureOSS 的技术架构和设计思想,包括插件系统架构、安全机制、数据流等。'
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 渲染插件页面
|
||||||
|
*/
|
||||||
|
exports.renderPlugins = (req, res) => {
|
||||||
|
res.render('pages/plugins', {
|
||||||
|
title: '插件生态系统 - FutureOSS',
|
||||||
|
page: 'plugins',
|
||||||
|
description: 'FutureOSS 插件生态系统:核心插件、社区插件、插件开发指南、插件市场。'
|
||||||
|
});
|
||||||
|
};
|
||||||
218
website/src/middleware/performance.js
Normal file
218
website/src/middleware/performance.js
Normal file
@@ -0,0 +1,218 @@
|
|||||||
|
/**
|
||||||
|
* 性能优化中间件
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 响应时间头中间件
|
||||||
|
* 添加X-Response-Time头到响应中
|
||||||
|
*/
|
||||||
|
function responseTime(req, res, next) {
|
||||||
|
const start = Date.now();
|
||||||
|
|
||||||
|
// 保存原始的 end 方法
|
||||||
|
const originalEnd = res.end;
|
||||||
|
|
||||||
|
// 重写 end 方法以在响应发送前设置头部
|
||||||
|
res.end = function(...args) {
|
||||||
|
const duration = Date.now() - start;
|
||||||
|
|
||||||
|
// 只有在头部尚未发送时才能设置
|
||||||
|
if (!res.headersSent) {
|
||||||
|
res.setHeader('X-Response-Time', `${duration}ms`);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 记录慢响应
|
||||||
|
if (duration > 1000) {
|
||||||
|
console.warn(`慢响应: ${req.method} ${req.url} - ${duration}ms`);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 调用原始的 end 方法
|
||||||
|
return originalEnd.apply(this, args);
|
||||||
|
};
|
||||||
|
|
||||||
|
next();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 压缩中间件
|
||||||
|
* 已经使用compression,这里添加额外的压缩头
|
||||||
|
*/
|
||||||
|
function compressionHeaders(req, res, next) {
|
||||||
|
// 为文本资源启用压缩
|
||||||
|
if (req.url.match(/\.(html|css|js|json|xml)$/)) {
|
||||||
|
res.setHeader('Vary', 'Accept-Encoding');
|
||||||
|
}
|
||||||
|
|
||||||
|
next();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 缓存控制中间件
|
||||||
|
*/
|
||||||
|
function cacheControl(req, res, next) {
|
||||||
|
// 静态资源缓存策略
|
||||||
|
if (req.url.match(/\.(css|js|png|jpg|jpeg|gif|ico|svg|woff|woff2|ttf|eot)$/)) {
|
||||||
|
// 静态资源缓存1年
|
||||||
|
res.setHeader('Cache-Control', 'public, max-age=31536000, immutable');
|
||||||
|
} else if (req.url.match(/\.(html)$/)) {
|
||||||
|
// HTML文件不缓存
|
||||||
|
res.setHeader('Cache-Control', 'no-cache, no-store, must-revalidate');
|
||||||
|
} else {
|
||||||
|
// 默认缓存策略
|
||||||
|
res.setHeader('Cache-Control', 'no-cache');
|
||||||
|
}
|
||||||
|
|
||||||
|
next();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 安全头中间件
|
||||||
|
*/
|
||||||
|
function securityHeaders(req, res, next) {
|
||||||
|
// 防止MIME类型嗅探
|
||||||
|
res.setHeader('X-Content-Type-Options', 'nosniff');
|
||||||
|
|
||||||
|
// 防止点击劫持
|
||||||
|
res.setHeader('X-Frame-Options', 'DENY');
|
||||||
|
|
||||||
|
// XSS保护
|
||||||
|
res.setHeader('X-XSS-Protection', '1; mode=block');
|
||||||
|
|
||||||
|
// 推荐使用HTTPS
|
||||||
|
res.setHeader('Strict-Transport-Security', 'max-age=31536000; includeSubDomains');
|
||||||
|
|
||||||
|
// 内容安全策略
|
||||||
|
const csp = [
|
||||||
|
"default-src 'self'",
|
||||||
|
"script-src 'self' 'unsafe-inline' https://cdnjs.cloudflare.com https://fonts.googleapis.com",
|
||||||
|
"style-src 'self' 'unsafe-inline' https://cdnjs.cloudflare.com https://fonts.googleapis.com",
|
||||||
|
"font-src 'self' https://cdnjs.cloudflare.com https://fonts.gstatic.com",
|
||||||
|
"img-src 'self' data: https:",
|
||||||
|
"connect-src 'self'",
|
||||||
|
"frame-ancestors 'none'",
|
||||||
|
"base-uri 'self'",
|
||||||
|
"form-action 'self'"
|
||||||
|
].join('; ');
|
||||||
|
|
||||||
|
res.setHeader('Content-Security-Policy', csp);
|
||||||
|
|
||||||
|
next();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gzip预压缩中间件
|
||||||
|
* 检查是否存在预压缩的.gz文件
|
||||||
|
*/
|
||||||
|
function gzipStatic(req, res, next) {
|
||||||
|
const acceptEncoding = req.headers['accept-encoding'] || '';
|
||||||
|
|
||||||
|
if (acceptEncoding.includes('gzip') && req.url.match(/\.(js|css|html|json)$/)) {
|
||||||
|
req.url = req.url + '.gz';
|
||||||
|
res.setHeader('Content-Encoding', 'gzip');
|
||||||
|
|
||||||
|
// 设置正确的Content-Type
|
||||||
|
if (req.url.endsWith('.js.gz')) {
|
||||||
|
res.setHeader('Content-Type', 'application/javascript');
|
||||||
|
} else if (req.url.endsWith('.css.gz')) {
|
||||||
|
res.setHeader('Content-Type', 'text/css');
|
||||||
|
} else if (req.url.endsWith('.html.gz')) {
|
||||||
|
res.setHeader('Content-Type', 'text/html');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
next();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 请求限流中间件(简单版本)
|
||||||
|
*/
|
||||||
|
function rateLimiter(maxRequests = 100, windowMs = 15 * 60 * 1000) {
|
||||||
|
const requests = new Map();
|
||||||
|
|
||||||
|
return function(req, res, next) {
|
||||||
|
const ip = req.ip || req.connection.remoteAddress;
|
||||||
|
const now = Date.now();
|
||||||
|
|
||||||
|
if (!requests.has(ip)) {
|
||||||
|
requests.set(ip, []);
|
||||||
|
}
|
||||||
|
|
||||||
|
const timestamps = requests.get(ip);
|
||||||
|
|
||||||
|
// 清理过期的请求记录
|
||||||
|
const windowStart = now - windowMs;
|
||||||
|
while (timestamps.length && timestamps[0] < windowStart) {
|
||||||
|
timestamps.shift();
|
||||||
|
}
|
||||||
|
|
||||||
|
// 检查是否超过限制
|
||||||
|
if (timestamps.length >= maxRequests) {
|
||||||
|
res.status(429).json({
|
||||||
|
error: '请求过多',
|
||||||
|
message: '请稍后再试'
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 记录当前请求
|
||||||
|
timestamps.push(now);
|
||||||
|
|
||||||
|
// 设置限流头
|
||||||
|
res.setHeader('X-RateLimit-Limit', maxRequests);
|
||||||
|
res.setHeader('X-RateLimit-Remaining', maxRequests - timestamps.length);
|
||||||
|
res.setHeader('X-RateLimit-Reset', Math.ceil((timestamps[0] + windowMs) / 1000));
|
||||||
|
|
||||||
|
next();
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 数据库查询优化中间件(示例)
|
||||||
|
*/
|
||||||
|
function queryOptimizer(req, res, next) {
|
||||||
|
// 这里可以添加数据库查询优化逻辑
|
||||||
|
// 例如:限制查询结果数量、添加索引提示等
|
||||||
|
|
||||||
|
// 示例:为API请求添加默认分页
|
||||||
|
if (req.path.startsWith('/api/') && req.method === 'GET') {
|
||||||
|
req.query.limit = req.query.limit || '50';
|
||||||
|
req.query.offset = req.query.offset || '0';
|
||||||
|
}
|
||||||
|
|
||||||
|
next();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 内存使用监控
|
||||||
|
*/
|
||||||
|
function memoryMonitor(req, res, next) {
|
||||||
|
const memoryUsage = process.memoryUsage();
|
||||||
|
|
||||||
|
// 记录高内存使用
|
||||||
|
if (memoryUsage.heapUsed > 500 * 1024 * 1024) { // 500MB
|
||||||
|
console.warn('高内存使用:', {
|
||||||
|
heapUsed: Math.round(memoryUsage.heapUsed / 1024 / 1024) + 'MB',
|
||||||
|
heapTotal: Math.round(memoryUsage.heapTotal / 1024 / 1024) + 'MB',
|
||||||
|
rss: Math.round(memoryUsage.rss / 1024 / 1024) + 'MB',
|
||||||
|
url: req.url
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// 添加内存使用头(仅开发环境)
|
||||||
|
if (process.env.NODE_ENV === 'development') {
|
||||||
|
res.setHeader('X-Memory-Usage', Math.round(memoryUsage.heapUsed / 1024 / 1024) + 'MB');
|
||||||
|
}
|
||||||
|
|
||||||
|
next();
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
responseTime,
|
||||||
|
compressionHeaders,
|
||||||
|
cacheControl,
|
||||||
|
securityHeaders,
|
||||||
|
gzipStatic,
|
||||||
|
rateLimiter,
|
||||||
|
queryOptimizer,
|
||||||
|
memoryMonitor
|
||||||
|
};
|
||||||
40
website/src/router.js
Normal file
40
website/src/router.js
Normal file
@@ -0,0 +1,40 @@
|
|||||||
|
/**
|
||||||
|
* FutureOSS 网站路由配置
|
||||||
|
* 一个文件只管一个功能:路由只负责路由,不处理业务逻辑
|
||||||
|
*/
|
||||||
|
|
||||||
|
const express = require('express');
|
||||||
|
const router = express.Router();
|
||||||
|
const path = require('path');
|
||||||
|
|
||||||
|
// 导入页面控制器
|
||||||
|
const pagesController = require('./controllers/pagesController');
|
||||||
|
const apiController = require('./controllers/apiController');
|
||||||
|
|
||||||
|
// API路由
|
||||||
|
router.get('/api/health', apiController.healthCheck);
|
||||||
|
router.get('/api/metrics', apiController.performanceMetrics);
|
||||||
|
router.get('/api/info', apiController.serverInfo);
|
||||||
|
router.get('/api/stress-test', apiController.stressTest);
|
||||||
|
|
||||||
|
// 页面路由 - 每个路由对应一个独立的页面文件
|
||||||
|
router.get('/', pagesController.renderHome);
|
||||||
|
router.get('/features', pagesController.renderFeatures);
|
||||||
|
router.get('/architecture', pagesController.renderArchitecture);
|
||||||
|
router.get('/plugins', pagesController.renderPlugins);
|
||||||
|
|
||||||
|
// 静态文件路由(备用)
|
||||||
|
router.get('/static/*', (req, res) => {
|
||||||
|
const filePath = path.join(__dirname, '../public', req.params[0]);
|
||||||
|
res.sendFile(filePath, (err) => {
|
||||||
|
if (err) {
|
||||||
|
res.status(404).json({ error: '文件未找到' });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
// 重定向旧路由(如果需要)
|
||||||
|
router.get('/home', (req, res) => res.redirect('/'));
|
||||||
|
router.get('/index', (req, res) => res.redirect('/'));
|
||||||
|
|
||||||
|
module.exports = router;
|
||||||
175
website/src/server.js
Normal file
175
website/src/server.js
Normal file
@@ -0,0 +1,175 @@
|
|||||||
|
/**
|
||||||
|
* FutureOSS 官方网站服务器
|
||||||
|
* 支持端口自动切换:8080被占用则使用8081
|
||||||
|
*/
|
||||||
|
|
||||||
|
const express = require('express');
|
||||||
|
const morgan = require('morgan');
|
||||||
|
const cors = require('cors');
|
||||||
|
const compression = require('compression');
|
||||||
|
const path = require('path');
|
||||||
|
const fs = require('fs');
|
||||||
|
const expressLayouts = require('express-ejs-layouts');
|
||||||
|
const performanceMiddleware = require('./middleware/performance');
|
||||||
|
|
||||||
|
// 创建Express应用
|
||||||
|
const app = express();
|
||||||
|
|
||||||
|
// 中间件配置
|
||||||
|
app.use(morgan('dev')); // 日志
|
||||||
|
app.use(cors()); // CORS支持
|
||||||
|
app.use(compression()); // 压缩响应
|
||||||
|
app.use(performanceMiddleware.responseTime); // 响应时间监控
|
||||||
|
app.use(performanceMiddleware.compressionHeaders); // 压缩头
|
||||||
|
app.use(performanceMiddleware.cacheControl); // 缓存控制
|
||||||
|
app.use(performanceMiddleware.securityHeaders); // 安全头
|
||||||
|
app.use(performanceMiddleware.memoryMonitor); // 内存监控
|
||||||
|
app.use(express.json()); // JSON解析
|
||||||
|
app.use(express.urlencoded({ extended: true })); // URL编码解析
|
||||||
|
|
||||||
|
// 静态文件服务
|
||||||
|
app.use(express.static(path.join(__dirname, '../public')));
|
||||||
|
|
||||||
|
// 设置视图引擎和布局
|
||||||
|
app.use(expressLayouts);
|
||||||
|
app.set('layout', 'layouts/main');
|
||||||
|
app.set('view engine', 'ejs');
|
||||||
|
app.set('views', path.join(__dirname, '../views'));
|
||||||
|
|
||||||
|
// 导入路由
|
||||||
|
const router = require('./router');
|
||||||
|
app.use('/', router);
|
||||||
|
|
||||||
|
// 错误处理中间件
|
||||||
|
app.use((err, req, res, next) => {
|
||||||
|
console.error('服务器错误:', err);
|
||||||
|
res.status(500).json({
|
||||||
|
error: '服务器内部错误',
|
||||||
|
message: process.env.NODE_ENV === 'development' ? err.message : undefined
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
// 404处理
|
||||||
|
app.use((req, res) => {
|
||||||
|
res.status(404).json({ error: '页面未找到' });
|
||||||
|
});
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 检查端口是否可用
|
||||||
|
* @param {number} port - 要检查的端口
|
||||||
|
* @returns {Promise<boolean>} - 端口是否可用
|
||||||
|
*/
|
||||||
|
function checkPort(port) {
|
||||||
|
return new Promise((resolve) => {
|
||||||
|
const net = require('net');
|
||||||
|
const server = net.createServer();
|
||||||
|
|
||||||
|
server.once('error', (err) => {
|
||||||
|
if (err.code === 'EADDRINUSE') {
|
||||||
|
console.log(`端口 ${port} 已被占用`);
|
||||||
|
resolve(false);
|
||||||
|
} else {
|
||||||
|
resolve(false);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
server.once('listening', () => {
|
||||||
|
server.close();
|
||||||
|
console.log(`端口 ${port} 可用`);
|
||||||
|
resolve(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
server.listen(port);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取可用端口
|
||||||
|
* @param {number} startPort - 起始端口
|
||||||
|
* @param {number} maxAttempts - 最大尝试次数
|
||||||
|
* @returns {Promise<number>} - 可用的端口
|
||||||
|
*/
|
||||||
|
async function getAvailablePort(startPort = 8080, maxAttempts = 10) {
|
||||||
|
for (let i = 0; i < maxAttempts; i++) {
|
||||||
|
const port = startPort + i;
|
||||||
|
const isAvailable = await checkPort(port);
|
||||||
|
if (isAvailable) {
|
||||||
|
return port;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
throw new Error(`在端口 ${startPort} 到 ${startPort + maxAttempts - 1} 范围内找不到可用端口`);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 启动服务器
|
||||||
|
*/
|
||||||
|
async function startServer() {
|
||||||
|
try {
|
||||||
|
// 获取可用端口
|
||||||
|
const port = await getAvailablePort(8080, 5);
|
||||||
|
|
||||||
|
// 启动服务器
|
||||||
|
const server = app.listen(port, () => {
|
||||||
|
console.log(`
|
||||||
|
╔══════════════════════════════════════════════════════════╗
|
||||||
|
║ FutureOSS 官方网站 ║
|
||||||
|
╠══════════════════════════════════════════════════════════╣
|
||||||
|
║ 服务器已启动! ║
|
||||||
|
║ ║
|
||||||
|
║ 本地访问: http://localhost:${port} ║
|
||||||
|
║ 环境: ${process.env.NODE_ENV || 'development'} ║
|
||||||
|
║ 进程ID: ${process.pid} ║
|
||||||
|
║ ║
|
||||||
|
║ 可用路由: ║
|
||||||
|
║ • GET / - 首页 ║
|
||||||
|
║ • GET /features - 特性页面 ║
|
||||||
|
║ • GET /architecture - 架构页面 ║
|
||||||
|
║ • GET /plugins - 插件页面 ║
|
||||||
|
║ • GET /api/health - 健康检查 ║
|
||||||
|
║ ║
|
||||||
|
║ 按 Ctrl+C 停止服务器 ║
|
||||||
|
╚══════════════════════════════════════════════════════════╝
|
||||||
|
`);
|
||||||
|
});
|
||||||
|
|
||||||
|
// 优雅关闭
|
||||||
|
const signals = ['SIGINT', 'SIGTERM', 'SIGQUIT'];
|
||||||
|
signals.forEach(signal => {
|
||||||
|
process.on(signal, () => {
|
||||||
|
console.log(`\n接收到 ${signal} 信号,正在关闭服务器...`);
|
||||||
|
server.close(() => {
|
||||||
|
console.log('服务器已关闭');
|
||||||
|
process.exit(0);
|
||||||
|
});
|
||||||
|
|
||||||
|
// 5秒后强制退出
|
||||||
|
setTimeout(() => {
|
||||||
|
console.error('强制关闭服务器');
|
||||||
|
process.exit(1);
|
||||||
|
}, 5000);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
// 未捕获异常处理
|
||||||
|
process.on('uncaughtException', (err) => {
|
||||||
|
console.error('未捕获异常:', err);
|
||||||
|
server.close(() => process.exit(1));
|
||||||
|
});
|
||||||
|
|
||||||
|
process.on('unhandledRejection', (reason, promise) => {
|
||||||
|
console.error('未处理的Promise拒绝:', reason);
|
||||||
|
});
|
||||||
|
|
||||||
|
return server;
|
||||||
|
} catch (error) {
|
||||||
|
console.error('启动服务器失败:', error);
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 如果是直接运行此文件,则启动服务器
|
||||||
|
if (require.main === module) {
|
||||||
|
startServer();
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = { app, startServer };
|
||||||
87
website/views/layouts/main.ejs
Normal file
87
website/views/layouts/main.ejs
Normal file
@@ -0,0 +1,87 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="zh-CN">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<title><%= title %></title>
|
||||||
|
<meta name="description" content="<%= description %>">
|
||||||
|
<meta name="keywords" content="FutureOSS, 插件化, 运行时框架, 微服务, Python, 开发者工具">
|
||||||
|
|
||||||
|
<!-- 外部CSS引入 -->
|
||||||
|
<link rel="stylesheet" href="/css/main.css">
|
||||||
|
<link rel="stylesheet" href="/css/components.css">
|
||||||
|
<link rel="stylesheet" href="/css/animations.css">
|
||||||
|
|
||||||
|
<!-- 字体和图标 -->
|
||||||
|
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">
|
||||||
|
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700&display=swap" rel="stylesheet">
|
||||||
|
|
||||||
|
<!-- Favicon -->
|
||||||
|
<link rel="icon" type="image/svg+xml" href="/images/logo.svg">
|
||||||
|
|
||||||
|
<!-- 页面特定CSS -->
|
||||||
|
<% if (page === 'home') { %>
|
||||||
|
<link rel="stylesheet" href="/css/pages/home.css">
|
||||||
|
<% } else if (page === 'features') { %>
|
||||||
|
<link rel="stylesheet" href="/css/pages/features.css">
|
||||||
|
<% } else if (page === 'architecture') { %>
|
||||||
|
<link rel="stylesheet" href="/css/pages/architecture.css">
|
||||||
|
<% } else if (page === 'plugins') { %>
|
||||||
|
<link rel="stylesheet" href="/css/pages/plugins.css">
|
||||||
|
<% } %>
|
||||||
|
</head>
|
||||||
|
<body class="page-<%= page %>">
|
||||||
|
<!-- 加载动画 - 默认隐藏,由JavaScript控制 -->
|
||||||
|
<div id="loading-overlay" class="loading-overlay hidden">
|
||||||
|
<div class="loading-spinner">
|
||||||
|
<div class="spinner"></div>
|
||||||
|
<div class="loading-text">FutureOSS 加载中...</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 导航栏组件 -->
|
||||||
|
<%- include('../partials/navbar', { page: page }) %>
|
||||||
|
|
||||||
|
<!-- 页面内容 -->
|
||||||
|
<main id="main-content" class="page-content">
|
||||||
|
<%- body %>
|
||||||
|
</main>
|
||||||
|
|
||||||
|
<!-- 页脚组件 -->
|
||||||
|
<%- include('../partials/footer') %>
|
||||||
|
|
||||||
|
<!-- 页面切换动画遮罩 -->
|
||||||
|
<div id="page-transition" class="page-transition"></div>
|
||||||
|
|
||||||
|
<!-- 外部JS引入 -->
|
||||||
|
<script src="/js/main.js"></script>
|
||||||
|
<script src="/js/router.js"></script>
|
||||||
|
<script src="/js/animations.js"></script>
|
||||||
|
|
||||||
|
<!-- 页面特定JS -->
|
||||||
|
<% if (page === 'home') { %>
|
||||||
|
<script src="/js/pages/home.js"></script>
|
||||||
|
<% } else if (page === 'features') { %>
|
||||||
|
<script src="/js/pages/features.js"></script>
|
||||||
|
<% } else if (page === 'architecture') { %>
|
||||||
|
<script src="/js/pages/architecture.js"></script>
|
||||||
|
<% } else if (page === 'plugins') { %>
|
||||||
|
<script src="/js/pages/plugins.js"></script>
|
||||||
|
<% } %>
|
||||||
|
|
||||||
|
<!-- 初始化脚本 -->
|
||||||
|
<script>
|
||||||
|
document.addEventListener('DOMContentLoaded', function() {
|
||||||
|
// 初始化页面
|
||||||
|
if (typeof initPage === 'function') {
|
||||||
|
initPage();
|
||||||
|
}
|
||||||
|
|
||||||
|
// 初始化路由
|
||||||
|
if (typeof initRouter === 'function') {
|
||||||
|
initRouter();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
299
website/views/pages/features.ejs
Normal file
299
website/views/pages/features.ejs
Normal file
@@ -0,0 +1,299 @@
|
|||||||
|
<!-- 特性页面 -->
|
||||||
|
<section class="page-features">
|
||||||
|
<!-- 页面标题 -->
|
||||||
|
<div class="page-header">
|
||||||
|
<div class="container">
|
||||||
|
<h1 class="page-title">核心特性</h1>
|
||||||
|
<p class="page-subtitle">FutureOSS 的六大核心特性,专为现代开发需求设计</p>
|
||||||
|
<div class="page-actions">
|
||||||
|
<a href="/" class="btn btn-outline" data-page="home">
|
||||||
|
<i class="fas fa-arrow-left"></i>
|
||||||
|
<span>返回首页</span>
|
||||||
|
</a>
|
||||||
|
<a href="/architecture" class="btn btn-primary" data-page="architecture">
|
||||||
|
<i class="fas fa-arrow-right"></i>
|
||||||
|
<span>查看架构</span>
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 特性网格 -->
|
||||||
|
<div class="features-section">
|
||||||
|
<div class="container">
|
||||||
|
<div class="features-grid">
|
||||||
|
<!-- 特性1: 插件化架构 -->
|
||||||
|
<div class="feature-card feature-highlight">
|
||||||
|
<div class="feature-header">
|
||||||
|
<div class="feature-icon">
|
||||||
|
<i class="fas fa-puzzle-piece"></i>
|
||||||
|
</div>
|
||||||
|
<h3 class="feature-title">插件化架构</h3>
|
||||||
|
<span class="feature-badge">核心</span>
|
||||||
|
</div>
|
||||||
|
<div class="feature-content">
|
||||||
|
<p class="feature-desc">
|
||||||
|
核心功能全部插件化,按需加载,极致轻量。从 HTTP 服务到 Web 界面,一切皆为插件。
|
||||||
|
</p>
|
||||||
|
<ul class="feature-list">
|
||||||
|
<li class="feature-item">
|
||||||
|
<i class="fas fa-check"></i>
|
||||||
|
<span>按需加载,减少资源占用</span>
|
||||||
|
</li>
|
||||||
|
<li class="feature-item">
|
||||||
|
<i class="fas fa-check"></i>
|
||||||
|
<span>热插拔,无需重启服务</span>
|
||||||
|
</li>
|
||||||
|
<li class="feature-item">
|
||||||
|
<i class="fas fa-check"></i>
|
||||||
|
<span>模块化设计,易于维护</span>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
<div class="feature-footer">
|
||||||
|
<span class="feature-tag">架构</span>
|
||||||
|
<span class="feature-tag">扩展性</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 特性2: 安全沙箱 -->
|
||||||
|
<div class="feature-card">
|
||||||
|
<div class="feature-header">
|
||||||
|
<div class="feature-icon">
|
||||||
|
<i class="fas fa-shield-alt"></i>
|
||||||
|
</div>
|
||||||
|
<h3 class="feature-title">安全沙箱</h3>
|
||||||
|
<span class="feature-badge">安全</span>
|
||||||
|
</div>
|
||||||
|
<div class="feature-content">
|
||||||
|
<p class="feature-desc">
|
||||||
|
数字签名验证 + 权限分级控制,确保插件来源可信,运行安全。
|
||||||
|
</p>
|
||||||
|
<ul class="feature-list">
|
||||||
|
<li class="feature-item">
|
||||||
|
<i class="fas fa-check"></i>
|
||||||
|
<span>插件数字签名验证</span>
|
||||||
|
</li>
|
||||||
|
<li class="feature-item">
|
||||||
|
<i class="fas fa-check"></i>
|
||||||
|
<span>权限分级控制系统</span>
|
||||||
|
</li>
|
||||||
|
<li class="feature-item">
|
||||||
|
<i class="fas fa-check"></i>
|
||||||
|
<span>可选沙箱环境隔离</span>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
<div class="feature-footer">
|
||||||
|
<span class="feature-tag">安全</span>
|
||||||
|
<span class="feature-tag">验证</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 特性3: 热重载支持 -->
|
||||||
|
<div class="feature-card">
|
||||||
|
<div class="feature-header">
|
||||||
|
<div class="feature-icon">
|
||||||
|
<i class="fas fa-sync-alt"></i>
|
||||||
|
</div>
|
||||||
|
<h3 class="feature-title">热重载支持</h3>
|
||||||
|
<span class="feature-badge">开发</span>
|
||||||
|
</div>
|
||||||
|
<div class="feature-content">
|
||||||
|
<p class="feature-desc">
|
||||||
|
开发阶段插件实时更新,无需重启服务,提升开发效率。
|
||||||
|
</p>
|
||||||
|
<ul class="feature-list">
|
||||||
|
<li class="feature-item">
|
||||||
|
<i class="fas fa-check"></i>
|
||||||
|
<span>代码变更实时生效</span>
|
||||||
|
</li>
|
||||||
|
<li class="feature-item">
|
||||||
|
<i class="fas fa-check"></i>
|
||||||
|
<span>开发模式专属功能</span>
|
||||||
|
</li>
|
||||||
|
<li class="feature-item">
|
||||||
|
<i class="fas fa-check"></i>
|
||||||
|
<span>状态保持,无需重新登录</span>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
<div class="feature-footer">
|
||||||
|
<span class="feature-tag">开发</span>
|
||||||
|
<span class="feature-tag">效率</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 特性4: 可视化控制台 -->
|
||||||
|
<div class="feature-card">
|
||||||
|
<div class="feature-header">
|
||||||
|
<div class="feature-icon">
|
||||||
|
<i class="fas fa-tachometer-alt"></i>
|
||||||
|
</div>
|
||||||
|
<h3 class="feature-title">可视化控制台</h3>
|
||||||
|
<span class="feature-badge">监控</span>
|
||||||
|
</div>
|
||||||
|
<div class="feature-content">
|
||||||
|
<p class="feature-desc">
|
||||||
|
Web 仪表盘实时监控系统状态与插件运行情况,管理更直观。
|
||||||
|
</p>
|
||||||
|
<ul class="feature-list">
|
||||||
|
<li class="feature-item">
|
||||||
|
<i class="fas fa-check"></i>
|
||||||
|
<span>实时系统状态监控</span>
|
||||||
|
</li>
|
||||||
|
<li class="feature-item">
|
||||||
|
<i class="fas fa-check"></i>
|
||||||
|
<span>插件运行状态可视化</span>
|
||||||
|
</li>
|
||||||
|
<li class="feature-item">
|
||||||
|
<i class="fas fa-check"></i>
|
||||||
|
<span>一键启用/禁用插件</span>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
<div class="feature-footer">
|
||||||
|
<span class="feature-tag">UI</span>
|
||||||
|
<span class="feature-tag">监控</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 特性5: 双协议服务 -->
|
||||||
|
<div class="feature-card">
|
||||||
|
<div class="feature-header">
|
||||||
|
<div class="feature-icon">
|
||||||
|
<i class="fas fa-network-wired"></i>
|
||||||
|
</div>
|
||||||
|
<h3 class="feature-title">双协议服务</h3>
|
||||||
|
<span class="feature-badge">性能</span>
|
||||||
|
</div>
|
||||||
|
<div class="feature-content">
|
||||||
|
<p class="feature-desc">
|
||||||
|
同时支持 HTTP API 和 TCP 高性能模式,满足不同场景需求。
|
||||||
|
</p>
|
||||||
|
<ul class="feature-list">
|
||||||
|
<li class="feature-item">
|
||||||
|
<i class="fas fa-check"></i>
|
||||||
|
<span>HTTP RESTful API 服务</span>
|
||||||
|
</li>
|
||||||
|
<li class="feature-item">
|
||||||
|
<i class="fas fa-check"></i>
|
||||||
|
<span>TCP 高性能 HTTP 服务</span>
|
||||||
|
</li>
|
||||||
|
<li class="feature-item">
|
||||||
|
<i class="fas fa-check"></i>
|
||||||
|
<span>WebSocket 实时通信</span>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
<div class="feature-footer">
|
||||||
|
<span class="feature-tag">网络</span>
|
||||||
|
<span class="feature-tag">性能</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 特性6: 依赖自动解析 -->
|
||||||
|
<div class="feature-card">
|
||||||
|
<div class="feature-header">
|
||||||
|
<div class="feature-icon">
|
||||||
|
<i class="fas fa-cogs"></i>
|
||||||
|
</div>
|
||||||
|
<h3 class="feature-title">依赖自动解析</h3>
|
||||||
|
<span class="feature-badge">便捷</span>
|
||||||
|
</div>
|
||||||
|
<div class="feature-content">
|
||||||
|
<p class="feature-desc">
|
||||||
|
插件依赖自动安装,告别手动配置烦恼,开箱即用。
|
||||||
|
</p>
|
||||||
|
<ul class="feature-list">
|
||||||
|
<li class="feature-item">
|
||||||
|
<i class="fas fa-check"></i>
|
||||||
|
<span>依赖自动检测与安装</span>
|
||||||
|
</li>
|
||||||
|
<li class="feature-item">
|
||||||
|
<i class="fas fa-check"></i>
|
||||||
|
<span>版本冲突智能解决</span>
|
||||||
|
</li>
|
||||||
|
<li class="feature-item">
|
||||||
|
<i class="fas fa-check"></i>
|
||||||
|
<span>离线安装支持</span>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
<div class="feature-footer">
|
||||||
|
<span class="feature-tag">依赖</span>
|
||||||
|
<span class="feature-tag">自动化</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 特性对比 -->
|
||||||
|
<div class="comparison-section">
|
||||||
|
<div class="container">
|
||||||
|
<div class="section-header">
|
||||||
|
<h2 class="section-title">与传统框架对比</h2>
|
||||||
|
<p class="section-subtitle">FutureOSS 带来的变革性优势</p>
|
||||||
|
</div>
|
||||||
|
<div class="comparison-table">
|
||||||
|
<table class="table">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>特性</th>
|
||||||
|
<th>FutureOSS</th>
|
||||||
|
<th>传统框架</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
<tr>
|
||||||
|
<td>架构设计</td>
|
||||||
|
<td><span class="comparison-good">插件化微内核</span></td>
|
||||||
|
<td><span class="comparison-bad">单体或微服务</span></td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>资源占用</td>
|
||||||
|
<td><span class="comparison-good">按需加载,极致轻量</span></td>
|
||||||
|
<td><span class="comparison-bad">全量加载,资源浪费</span></td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>扩展性</td>
|
||||||
|
<td><span class="comparison-good">无限扩展,热插拔</span></td>
|
||||||
|
<td><span class="comparison-bad">有限扩展,需重启</span></td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>安全性</td>
|
||||||
|
<td><span class="comparison-good">数字签名 + 沙箱</span></td>
|
||||||
|
<td><span class="comparison-bad">依赖开发者自觉</span></td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>开发体验</td>
|
||||||
|
<td><span class="comparison-good">热重载,实时更新</span></td>
|
||||||
|
<td><span class="comparison-bad">修改后需重启</span></td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 下一步行动 -->
|
||||||
|
<div class="next-section">
|
||||||
|
<div class="container">
|
||||||
|
<div class="next-content">
|
||||||
|
<h2 class="next-title">深入了解技术实现?</h2>
|
||||||
|
<p class="next-subtitle">查看 FutureOSS 的技术架构和设计思想</p>
|
||||||
|
<div class="next-actions">
|
||||||
|
<a href="/architecture" class="btn btn-primary btn-large" data-page="architecture">
|
||||||
|
<i class="fas fa-sitemap"></i>
|
||||||
|
<span>查看架构</span>
|
||||||
|
</a>
|
||||||
|
<a href="/plugins" class="btn btn-secondary btn-large" data-page="plugins">
|
||||||
|
<i class="fas fa-puzzle-piece"></i>
|
||||||
|
<span>探索插件</span>
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
173
website/views/pages/home.ejs
Normal file
173
website/views/pages/home.ejs
Normal file
@@ -0,0 +1,173 @@
|
|||||||
|
<!-- 首页 -->
|
||||||
|
<section class="page-home">
|
||||||
|
<!-- 英雄区域 -->
|
||||||
|
<div class="hero-section">
|
||||||
|
<div class="container">
|
||||||
|
<div class="hero-content">
|
||||||
|
<h1 class="hero-title">
|
||||||
|
<span class="title-line">一切皆为</span>
|
||||||
|
<span class="title-highlight">插件</span>
|
||||||
|
</h1>
|
||||||
|
<p class="hero-subtitle">
|
||||||
|
FutureOSS 是一款面向开发者的插件化运行时框架,让功能扩展变得前所未有的简单。
|
||||||
|
</p>
|
||||||
|
<div class="hero-actions">
|
||||||
|
<a href="#start" class="btn btn-primary btn-large" data-action="scroll-to-start">
|
||||||
|
<i class="fas fa-rocket"></i>
|
||||||
|
<span>快速开始</span>
|
||||||
|
</a>
|
||||||
|
<a href="https://gitee.com/starlight-apk/future-oss" class="btn btn-secondary btn-large" target="_blank" rel="noopener noreferrer">
|
||||||
|
<i class="fab fa-gitee"></i>
|
||||||
|
<span>查看源码</span>
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
<div class="hero-stats">
|
||||||
|
<div class="stat-card">
|
||||||
|
<div class="stat-number">100%</div>
|
||||||
|
<div class="stat-label">插件化</div>
|
||||||
|
</div>
|
||||||
|
<div class="stat-card">
|
||||||
|
<div class="stat-number">15+</div>
|
||||||
|
<div class="stat-label">核心插件</div>
|
||||||
|
</div>
|
||||||
|
<div class="stat-card">
|
||||||
|
<div class="stat-number">3</div>
|
||||||
|
<div class="stat-label">协议支持</div>
|
||||||
|
</div>
|
||||||
|
<div class="stat-card">
|
||||||
|
<div class="stat-number">∞</div>
|
||||||
|
<div class="stat-label">扩展可能</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="hero-visual">
|
||||||
|
<div class="visual-container">
|
||||||
|
<div class="plugin-animation">
|
||||||
|
<div class="core-node">
|
||||||
|
<i class="fas fa-cube"></i>
|
||||||
|
<span class="node-label">核心</span>
|
||||||
|
</div>
|
||||||
|
<div class="plugin-node" style="--delay: 0s;">
|
||||||
|
<i class="fas fa-globe"></i>
|
||||||
|
<span class="node-label">HTTP</span>
|
||||||
|
</div>
|
||||||
|
<div class="plugin-node" style="--delay: 0.3s;">
|
||||||
|
<i class="fas fa-bolt"></i>
|
||||||
|
<span class="node-label">WebSocket</span>
|
||||||
|
</div>
|
||||||
|
<div class="plugin-node" style="--delay: 0.6s;">
|
||||||
|
<i class="fas fa-chart-line"></i>
|
||||||
|
<span class="node-label">Dashboard</span>
|
||||||
|
</div>
|
||||||
|
<div class="plugin-node" style="--delay: 0.9s;">
|
||||||
|
<i class="fas fa-shield-alt"></i>
|
||||||
|
<span class="node-label">Security</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 核心价值 -->
|
||||||
|
<div class="value-section">
|
||||||
|
<div class="container">
|
||||||
|
<div class="section-header">
|
||||||
|
<h2 class="section-title">为什么选择 FutureOSS?</h2>
|
||||||
|
<p class="section-subtitle">专为现代开发需求设计的强大框架</p>
|
||||||
|
</div>
|
||||||
|
<div class="value-grid">
|
||||||
|
<div class="value-card">
|
||||||
|
<div class="value-icon">
|
||||||
|
<i class="fas fa-weight"></i>
|
||||||
|
</div>
|
||||||
|
<h3 class="value-title">极致轻量</h3>
|
||||||
|
<p class="value-desc">核心框架最小化,所有功能通过插件实现,按需加载,资源占用极低。</p>
|
||||||
|
</div>
|
||||||
|
<div class="value-card">
|
||||||
|
<div class="value-icon">
|
||||||
|
<i class="fas fa-shield-alt"></i>
|
||||||
|
</div>
|
||||||
|
<h3 class="value-title">安全可靠</h3>
|
||||||
|
<p class="value-desc">数字签名验证 + 权限分级控制 + 沙箱环境,确保插件来源可信。</p>
|
||||||
|
</div>
|
||||||
|
<div class="value-card">
|
||||||
|
<div class="value-icon">
|
||||||
|
<i class="fas fa-bolt"></i>
|
||||||
|
</div>
|
||||||
|
<h3 class="value-title">高性能</h3>
|
||||||
|
<p class="value-desc">支持 HTTP、TCP、WebSocket 多协议,满足不同场景的性能需求。</p>
|
||||||
|
</div>
|
||||||
|
<div class="value-card">
|
||||||
|
<div class="value-icon">
|
||||||
|
<i class="fas fa-expand-arrows-alt"></i>
|
||||||
|
</div>
|
||||||
|
<h3 class="value-title">无限扩展</h3>
|
||||||
|
<p class="value-desc">插件化架构让功能扩展变得简单,社区生态持续壮大。</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 快速开始 -->
|
||||||
|
<div id="start" class="start-section">
|
||||||
|
<div class="container">
|
||||||
|
<div class="section-header">
|
||||||
|
<h2 class="section-title">快速开始</h2>
|
||||||
|
<p class="section-subtitle">几分钟内启动你的第一个 FutureOSS 项目</p>
|
||||||
|
</div>
|
||||||
|
<div class="start-steps">
|
||||||
|
<div class="step-card">
|
||||||
|
<div class="step-number">1</div>
|
||||||
|
<h3 class="step-title">安装依赖</h3>
|
||||||
|
<pre class="step-code"><code>pip install -e .</code></pre>
|
||||||
|
<p class="step-desc">安装 FutureOSS 及其依赖</p>
|
||||||
|
</div>
|
||||||
|
<div class="step-card">
|
||||||
|
<div class="step-number">2</div>
|
||||||
|
<h3 class="step-title">启动服务</h3>
|
||||||
|
<pre class="step-code"><code>oss serve</code></pre>
|
||||||
|
<p class="step-desc">启动 FutureOSS 服务器</p>
|
||||||
|
</div>
|
||||||
|
<div class="step-card">
|
||||||
|
<div class="step-number">3</div>
|
||||||
|
<h3 class="step-title">访问控制台</h3>
|
||||||
|
<pre class="step-code"><code>http://localhost:8080</code></pre>
|
||||||
|
<p class="step-desc">打开浏览器访问 Web 控制台</p>
|
||||||
|
</div>
|
||||||
|
<div class="step-card">
|
||||||
|
<div class="step-number">4</div>
|
||||||
|
<h3 class="step-title">安装插件</h3>
|
||||||
|
<pre class="step-code"><code># 通过控制台或API安装插件</code></pre>
|
||||||
|
<p class="step-desc">按需安装功能插件</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="start-actions">
|
||||||
|
<a href="https://gitee.com/starlight-apk/future-oss/wikis/快速开始" class="btn btn-outline" target="_blank" rel="noopener noreferrer">
|
||||||
|
<i class="fas fa-book"></i>
|
||||||
|
<span>查看完整文档</span>
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- CTA区域 -->
|
||||||
|
<div class="cta-section">
|
||||||
|
<div class="container">
|
||||||
|
<div class="cta-content">
|
||||||
|
<h2 class="cta-title">准备好开始了吗?</h2>
|
||||||
|
<p class="cta-subtitle">加入 FutureOSS 社区,一起构建更好的开发者工具生态</p>
|
||||||
|
<div class="cta-actions">
|
||||||
|
<a href="https://gitee.com/starlight-apk/future-oss" class="btn btn-primary btn-large" target="_blank" rel="noopener noreferrer">
|
||||||
|
<i class="fab fa-gitee"></i>
|
||||||
|
<span>Star 项目</span>
|
||||||
|
</a>
|
||||||
|
<a href="/features" class="btn btn-secondary btn-large" data-page="features">
|
||||||
|
<i class="fas fa-star"></i>
|
||||||
|
<span>了解更多</span>
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
122
website/views/partials/footer.ejs
Normal file
122
website/views/partials/footer.ejs
Normal file
@@ -0,0 +1,122 @@
|
|||||||
|
<!-- 页脚组件 -->
|
||||||
|
<footer class="footer" role="contentinfo">
|
||||||
|
<div class="container">
|
||||||
|
<!-- 主要页脚内容 -->
|
||||||
|
<div class="footer-main">
|
||||||
|
<!-- 品牌信息 -->
|
||||||
|
<div class="footer-brand">
|
||||||
|
<div class="footer-logo">
|
||||||
|
<img src="/images/logo.svg" alt="FutureOSS Logo" class="footer-logo-img">
|
||||||
|
</div>
|
||||||
|
<div class="footer-brand-info">
|
||||||
|
<h3 class="footer-brand-name">FutureOSS</h3>
|
||||||
|
<p class="footer-brand-tagline">一切皆为插件</p>
|
||||||
|
<p class="footer-brand-desc">面向开发者的插件化运行时框架</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 快速链接 -->
|
||||||
|
<div class="footer-links">
|
||||||
|
<h4 class="footer-heading">快速链接</h4>
|
||||||
|
<ul class="footer-link-list">
|
||||||
|
<li class="footer-link-item">
|
||||||
|
<a href="/" class="footer-link">首页</a>
|
||||||
|
</li>
|
||||||
|
<li class="footer-link-item">
|
||||||
|
<a href="/features" class="footer-link">核心特性</a>
|
||||||
|
</li>
|
||||||
|
<li class="footer-link-item">
|
||||||
|
<a href="/architecture" class="footer-link">技术架构</a>
|
||||||
|
</li>
|
||||||
|
<li class="footer-link-item">
|
||||||
|
<a href="/plugins" class="footer-link">插件生态</a>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 资源链接 -->
|
||||||
|
<div class="footer-resources">
|
||||||
|
<h4 class="footer-heading">资源</h4>
|
||||||
|
<ul class="footer-link-list">
|
||||||
|
<li class="footer-link-item">
|
||||||
|
<a href="https://gitee.com/starlight-apk/future-oss" class="footer-link" target="_blank" rel="noopener noreferrer">
|
||||||
|
<i class="fab fa-gitee"></i> 代码仓库
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
<li class="footer-link-item">
|
||||||
|
<a href="https://gitee.com/starlight-apk/future-oss/wikis" class="footer-link" target="_blank" rel="noopener noreferrer">
|
||||||
|
<i class="fas fa-book"></i> 文档
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
<li class="footer-link-item">
|
||||||
|
<a href="https://gitee.com/starlight-apk/future-oss/issues" class="footer-link" target="_blank" rel="noopener noreferrer">
|
||||||
|
<i class="fas fa-bug"></i> 问题反馈
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
<li class="footer-link-item">
|
||||||
|
<a href="https://gitee.com/starlight-apk/future-oss-pkg" class="footer-link" target="_blank" rel="noopener noreferrer">
|
||||||
|
<i class="fas fa-box"></i> 包仓库
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 联系信息 -->
|
||||||
|
<div class="footer-contact">
|
||||||
|
<h4 class="footer-heading">联系</h4>
|
||||||
|
<ul class="footer-link-list">
|
||||||
|
<li class="footer-link-item">
|
||||||
|
<a href="https://gitee.com/starlight-apk" class="footer-link" target="_blank" rel="noopener noreferrer">
|
||||||
|
<i class="fas fa-user"></i> 作者: Falck
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
<li class="footer-link-item">
|
||||||
|
<a href="https://gitcode.com/yongwanxing" class="footer-link" target="_blank" rel="noopener noreferrer">
|
||||||
|
<i class="fas fa-users"></i> 贡献者
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
<li class="footer-link-item">
|
||||||
|
<span class="footer-text">
|
||||||
|
<i class="fas fa-code-branch"></i> 版本: 1.0.0
|
||||||
|
</span>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 分隔线 -->
|
||||||
|
<div class="footer-divider"></div>
|
||||||
|
|
||||||
|
<!-- 底部信息 -->
|
||||||
|
<div class="footer-bottom">
|
||||||
|
<div class="footer-copyright">
|
||||||
|
<p class="copyright-text">
|
||||||
|
© 2026 FutureOSS. 保留所有权利。
|
||||||
|
<span class="license-info">
|
||||||
|
基于 <a href="/LICENSE" class="license-link">Apache License 2.0</a> 开源。
|
||||||
|
</span>
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="footer-social">
|
||||||
|
<a href="https://gitee.com/starlight-apk/future-oss" class="social-link gitee-link" target="_blank" rel="noopener noreferrer" aria-label="Gitee">
|
||||||
|
<i class="fab fa-gitee"></i>
|
||||||
|
</a>
|
||||||
|
<a href="https://gitee.com/starlight-apk/future-oss/wikis" class="social-link docs-link" target="_blank" rel="noopener noreferrer" aria-label="文档">
|
||||||
|
<i class="fas fa-book"></i>
|
||||||
|
</a>
|
||||||
|
<a href="https://gitee.com/starlight-apk/future-oss/issues" class="social-link issues-link" target="_blank" rel="noopener noreferrer" aria-label="问题反馈">
|
||||||
|
<i class="fas fa-bug"></i>
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 声明 -->
|
||||||
|
<div class="footer-disclaimer">
|
||||||
|
<p class="disclaimer-text">
|
||||||
|
FutureOSS 是一款面向开发者的插件化运行时框架,秉承「一切皆为插件」的设计理念。
|
||||||
|
本网站仅用于展示项目信息,实际功能请参考项目文档。
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</footer>
|
||||||
108
website/views/partials/navbar.ejs
Normal file
108
website/views/partials/navbar.ejs
Normal file
@@ -0,0 +1,108 @@
|
|||||||
|
<!-- 导航栏组件 -->
|
||||||
|
<nav class="navbar" role="navigation" aria-label="主导航">
|
||||||
|
<div class="container">
|
||||||
|
<!-- 品牌标识 -->
|
||||||
|
<div class="navbar-brand">
|
||||||
|
<a href="/" class="brand-link" data-page="home">
|
||||||
|
<div class="brand-logo">
|
||||||
|
<img src="/images/logo.svg" alt="FutureOSS Logo" class="logo-img">
|
||||||
|
</div>
|
||||||
|
<div class="brand-text">
|
||||||
|
<span class="brand-name">FutureOSS</span>
|
||||||
|
<span class="brand-tagline">一切皆为插件</span>
|
||||||
|
</div>
|
||||||
|
</a>
|
||||||
|
<button class="navbar-toggle" aria-label="切换导航菜单" aria-expanded="false">
|
||||||
|
<span class="toggle-icon"></span>
|
||||||
|
<span class="toggle-icon"></span>
|
||||||
|
<span class="toggle-icon"></span>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 导航菜单 -->
|
||||||
|
<div class="navbar-menu">
|
||||||
|
<ul class="nav-list">
|
||||||
|
<li class="nav-item">
|
||||||
|
<a href="/" class="nav-link <%= page === 'home' ? 'active' : '' %>" data-page="home">
|
||||||
|
<i class="fas fa-home nav-icon"></i>
|
||||||
|
<span class="nav-text">首页</span>
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
<li class="nav-item">
|
||||||
|
<a href="/features" class="nav-link <%= page === 'features' ? 'active' : '' %>" data-page="features">
|
||||||
|
<i class="fas fa-star nav-icon"></i>
|
||||||
|
<span class="nav-text">特性</span>
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
<li class="nav-item">
|
||||||
|
<a href="/architecture" class="nav-link <%= page === 'architecture' ? 'active' : '' %>" data-page="architecture">
|
||||||
|
<i class="fas fa-sitemap nav-icon"></i>
|
||||||
|
<span class="nav-text">架构</span>
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
<li class="nav-item">
|
||||||
|
<a href="/plugins" class="nav-link <%= page === 'plugins' ? 'active' : '' %>" data-page="plugins">
|
||||||
|
<i class="fas fa-puzzle-piece nav-icon"></i>
|
||||||
|
<span class="nav-text">插件</span>
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
<!-- 外部链接 -->
|
||||||
|
<div class="nav-external">
|
||||||
|
<a href="https://gitee.com/starlight-apk/future-oss" class="external-link gitee-link" target="_blank" rel="noopener noreferrer">
|
||||||
|
<i class="fab fa-gitee"></i>
|
||||||
|
<span>Gitee</span>
|
||||||
|
</a>
|
||||||
|
<a href="https://gitee.com/starlight-apk/future-oss/wikis" class="external-link docs-link" target="_blank" rel="noopener noreferrer">
|
||||||
|
<i class="fas fa-book"></i>
|
||||||
|
<span>文档</span>
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 移动端菜单 -->
|
||||||
|
<div class="navbar-mobile">
|
||||||
|
<div class="mobile-menu">
|
||||||
|
<ul class="mobile-nav-list">
|
||||||
|
<li class="mobile-nav-item">
|
||||||
|
<a href="/" class="mobile-nav-link <%= page === 'home' ? 'active' : '' %>" data-page="home">
|
||||||
|
<i class="fas fa-home"></i>
|
||||||
|
<span>首页</span>
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
<li class="mobile-nav-item">
|
||||||
|
<a href="/features" class="mobile-nav-link <%= page === 'features' ? 'active' : '' %>" data-page="features">
|
||||||
|
<i class="fas fa-star"></i>
|
||||||
|
<span>特性</span>
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
<li class="mobile-nav-item">
|
||||||
|
<a href="/architecture" class="mobile-nav-link <%= page === 'architecture' ? 'active' : '' %>" data-page="architecture">
|
||||||
|
<i class="fas fa-sitemap"></i>
|
||||||
|
<span>架构</span>
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
<li class="mobile-nav-item">
|
||||||
|
<a href="/plugins" class="mobile-nav-link <%= page === 'plugins' ? 'active' : '' %>" data-page="plugins">
|
||||||
|
<i class="fas fa-puzzle-piece"></i>
|
||||||
|
<span>插件</span>
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
<li class="mobile-nav-item">
|
||||||
|
<a href="https://gitee.com/starlight-apk/future-oss" class="mobile-nav-link" target="_blank" rel="noopener noreferrer">
|
||||||
|
<i class="fab fa-gitee"></i>
|
||||||
|
<span>Gitee</span>
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
<li class="mobile-nav-item">
|
||||||
|
<a href="https://gitee.com/starlight-apk/future-oss/wikis" class="mobile-nav-link" target="_blank" rel="noopener noreferrer">
|
||||||
|
<i class="fas fa-book"></i>
|
||||||
|
<span>文档</span>
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</nav>
|
||||||
Reference in New Issue
Block a user