⚡ 初始提交 - FutureOSS v1.0 插件化运行时框架
一切皆为插件的开发者工具运行时框架
🧩 核心特性:
- 插件热插拔 (importlib 动态加载)
- 依赖自动解析 (拓扑排序 + 循环检测)
- 企业级稳定 (熔断/降级/重试/隔离)
- 事件驱动 (发布/订阅事件总线)
- 完整配置 (YAML 配置 + 热重载)
This commit is contained in:
35
website/js/animations.js
Normal file
35
website/js/animations.js
Normal file
@@ -0,0 +1,35 @@
|
||||
// 动画入口
|
||||
gsap.registerPlugin(ScrollTrigger);
|
||||
|
||||
// 页面元素入场
|
||||
gsap.utils.toArray('.feature-card, .step-card, .arch-layer, .arch-plugin').forEach((el, i) => {
|
||||
gsap.fromTo(el,
|
||||
{ y: 40, opacity: 0 },
|
||||
{
|
||||
y: 0, opacity: 1, duration: 0.6,
|
||||
ease: 'power3.out',
|
||||
scrollTrigger: { trigger: el, start: 'top 85%' },
|
||||
delay: (i % 4) * 0.1
|
||||
}
|
||||
);
|
||||
});
|
||||
|
||||
// Hero 动画
|
||||
if (document.querySelector('.hero-content')) {
|
||||
gsap.fromTo('.hero-content > *',
|
||||
{ y: 50, opacity: 0 },
|
||||
{ y: 0, opacity: 1, duration: 0.8, stagger: 0.1, ease: 'power3.out', delay: 0.2 }
|
||||
);
|
||||
gsap.fromTo('.hero-visual',
|
||||
{ scale: 0.85, opacity: 0 },
|
||||
{ scale: 1, opacity: 1, duration: 1, ease: 'back.out(1.4)', delay: 0.4 }
|
||||
);
|
||||
}
|
||||
|
||||
// 平滑滚动
|
||||
document.querySelectorAll('a[href^="#"]').forEach(a => {
|
||||
a.addEventListener('click', e => {
|
||||
const target = document.querySelector(a.getAttribute('href'));
|
||||
if (target) { e.preventDefault(); target.scrollIntoView({ behavior: 'smooth' }); }
|
||||
});
|
||||
});
|
||||
16
website/js/app.js
Normal file
16
website/js/app.js
Normal file
@@ -0,0 +1,16 @@
|
||||
// 主应用入口
|
||||
document.addEventListener('DOMContentLoaded', () => {
|
||||
// 平滑滚动到锚点
|
||||
document.querySelectorAll('a[href^="#"]').forEach(anchor => {
|
||||
anchor.addEventListener('click', function(e) {
|
||||
e.preventDefault();
|
||||
const target = document.querySelector(this.getAttribute('href'));
|
||||
if (target) {
|
||||
target.scrollIntoView({ behavior: 'smooth', block: 'start' });
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
console.log('%c Future OSS ', 'background: linear-gradient(135deg, #06b6d4, #3b82f6); color: #fff; padding: 8px 16px; border-radius: 4px; font-weight: bold;');
|
||||
console.log('%c一切皆为插件的开发者工具运行时框架', 'color: #9ca3af; font-size: 14px;');
|
||||
});
|
||||
75
website/js/dock.js
Normal file
75
website/js/dock.js
Normal file
@@ -0,0 +1,75 @@
|
||||
/**
|
||||
* Dock 侧边栏组件
|
||||
* 在所有 HTML 页面中统一引用
|
||||
*/
|
||||
|
||||
(function () {
|
||||
'use strict';
|
||||
|
||||
// Dock HTML 模板
|
||||
// current: 当前页面文件名,用于设置 active 状态
|
||||
function renderDock(current) {
|
||||
const items = [
|
||||
{ href: 'index.html', tooltip: '首页', svg: 'M3 12l2-2m0 0l7-7 7 7M5 10v10a1 1 0 001 1h3m10-11l2 2m-2-2v10a1 1 0 01-1 1h-3m-6 0a1 1 0 001-1v-4a1 1 0 011-1h2a1 1 0 011 1v4a1 1 0 001 1m-6 0h6' },
|
||||
{ href: 'features.html', tooltip: '特性', svg: 'M5 3v4M3 5h4M6 17v4m-2-2h4m5-16l2.286 6.857L21 12l-5.714 2.143L13 21l-2.286-6.857L5 12l5.714-2.143L13 3z' },
|
||||
{ href: 'plugins.html', tooltip: '插件', svg: 'M11 4a2 2 0 114 0v1a1 1 0 001 1h3a1 1 0 011 1v3a1 1 0 01-1 1h-1a2 2 0 100 4h1a1 1 0 011 1v3a1 1 0 01-1 1h-3a1 1 0 01-1-1v-1a2 2 0 10-4 0v1a1 1 0 01-1 1H7a1 1 0 01-1-1v-3a1 1 0 00-1-1H4a2 2 0 110-4h1a1 1 0 001-1V7a1 1 0 011-1h3a1 1 0 001-1V4z' },
|
||||
{ href: 'architecture.html', tooltip: '架构', svg: 'M4 5a1 1 0 011-1h14a1 1 0 011 1v2a1 1 0 01-1 1H5a1 1 0 01-1-1V5zM4 13a1 1 0 011-1h6a1 1 0 011 1v6a1 1 0 01-1 1H5a1 1 0 01-1-1v-6zM16 13a1 1 0 011-1h2a1 1 0 011 1v6a1 1 0 01-1 1h-2a1 1 0 01-1-1v-6z' },
|
||||
{ separator: true },
|
||||
{ href: 'quickstart.html', tooltip: '快速开始', svg: 'M13 10V3L4 14h7v7l9-11h-7z' },
|
||||
{ separator: true },
|
||||
{ href: 'community/', tooltip: '社区', svg: 'M17 20h5v-2a3 3 0 00-5.356-1.857M17 20H7m10 0v-2c0-.656-.126-1.283-.356-1.857M7 20H2v-2a3 3 0 015.356-1.857M7 20v-2c0-.656.126-1.283.356-1.857m0 0a5.002 5.002 0 019.288 0M15 7a3 3 0 11-6 0 3 3 0 016 0zm6 3a2 2 0 11-4 0 2 2 0 014 0zM7 10a2 2 0 11-4 0 2 2 0 014 0z' },
|
||||
{ href: 'https://gitee.com/starlight-apk/feature-oss', tooltip: '源码', svg: 'M10 20l4-16m4 4l4 4-4 4M6 16l-4-4 4-4', target: '_blank' }
|
||||
];
|
||||
|
||||
let html = '<aside id="dock">\n';
|
||||
|
||||
items.forEach(item => {
|
||||
if (item.separator) {
|
||||
html += ' <div class="dock-separator"></div>\n';
|
||||
} else {
|
||||
const isActive = item.href === current ? ' active' : '';
|
||||
const targetAttr = item.target ? ` target="${item.target}"` : '';
|
||||
html += ` <a href="${item.href}" class="dock-item${isActive}" data-tooltip="${item.tooltip}"${targetAttr}>\n`;
|
||||
html += ` <svg viewBox="0 0 24 24" fill="none" stroke="currentColor"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="${item.svg}"/></svg>\n`;
|
||||
html += ` </a>\n`;
|
||||
}
|
||||
});
|
||||
|
||||
html += ' </aside>';
|
||||
return html;
|
||||
}
|
||||
|
||||
// 获取当前页面文件名
|
||||
function getCurrentPage() {
|
||||
const path = window.location.pathname;
|
||||
const filename = path.substring(path.lastIndexOf('/') + 1);
|
||||
return filename || 'index.html';
|
||||
}
|
||||
|
||||
// 初始化:将 dock 插入到 body 的第一个子元素之前
|
||||
function initDock() {
|
||||
const current = getCurrentPage();
|
||||
const dockHTML = renderDock(current);
|
||||
const tempDiv = document.createElement('div');
|
||||
tempDiv.innerHTML = dockHTML;
|
||||
const dockElement = tempDiv.firstElementChild;
|
||||
|
||||
const body = document.body;
|
||||
const firstChild = body.firstChild;
|
||||
if (firstChild) {
|
||||
body.insertBefore(dockElement, firstChild);
|
||||
} else {
|
||||
body.appendChild(dockElement);
|
||||
}
|
||||
}
|
||||
|
||||
// DOM 加载完成后初始化
|
||||
if (document.readyState === 'loading') {
|
||||
document.addEventListener('DOMContentLoaded', initDock);
|
||||
} else {
|
||||
initDock();
|
||||
}
|
||||
|
||||
// 对外暴露(如果需要手动调用)
|
||||
window.OSSDock = { init: initDock, render: renderDock };
|
||||
})();
|
||||
83
website/js/particles.js
Normal file
83
website/js/particles.js
Normal file
@@ -0,0 +1,83 @@
|
||||
// 粒子背景动画
|
||||
const canvas = document.getElementById('particles');
|
||||
const ctx = canvas.getContext('2d');
|
||||
|
||||
let width, height, particles = [];
|
||||
|
||||
function resize() {
|
||||
width = canvas.width = window.innerWidth;
|
||||
height = canvas.height = window.innerHeight;
|
||||
}
|
||||
|
||||
class Particle {
|
||||
constructor() {
|
||||
this.reset();
|
||||
}
|
||||
|
||||
reset() {
|
||||
this.x = Math.random() * width;
|
||||
this.y = Math.random() * height;
|
||||
this.size = 1 + Math.random() * 2;
|
||||
this.speedX = -0.2 + Math.random() * 0.4;
|
||||
this.speedY = -0.2 + Math.random() * 0.4;
|
||||
this.opacity = 0.1 + Math.random() * 0.3;
|
||||
}
|
||||
|
||||
update() {
|
||||
this.x += this.speedX;
|
||||
this.y += this.speedY;
|
||||
|
||||
if (this.x < 0 || this.x > width || this.y < 0 || this.y > height) {
|
||||
this.reset();
|
||||
}
|
||||
}
|
||||
|
||||
draw() {
|
||||
ctx.beginPath();
|
||||
ctx.arc(this.x, this.y, this.size, 0, Math.PI * 2);
|
||||
ctx.fillStyle = `rgba(6, 182, 212, ${this.opacity})`;
|
||||
ctx.fill();
|
||||
}
|
||||
}
|
||||
|
||||
function init() {
|
||||
resize();
|
||||
const count = Math.min(80, Math.floor((width * height) / 15000));
|
||||
particles = Array.from({ length: count }, () => new Particle());
|
||||
}
|
||||
|
||||
function animate() {
|
||||
ctx.clearRect(0, 0, width, height);
|
||||
|
||||
// 绘制连线
|
||||
for (let i = 0; i < particles.length; i++) {
|
||||
for (let j = i + 1; j < particles.length; j++) {
|
||||
const dx = particles[i].x - particles[j].x;
|
||||
const dy = particles[i].y - particles[j].y;
|
||||
const dist = Math.sqrt(dx * dx + dy * dy);
|
||||
|
||||
if (dist < 120) {
|
||||
ctx.beginPath();
|
||||
ctx.moveTo(particles[i].x, particles[i].y);
|
||||
ctx.lineTo(particles[j].x, particles[j].y);
|
||||
ctx.strokeStyle = `rgba(6, 182, 212, ${0.05 * (1 - dist / 120)})`;
|
||||
ctx.lineWidth = 0.5;
|
||||
ctx.stroke();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
particles.forEach(p => {
|
||||
p.update();
|
||||
p.draw();
|
||||
});
|
||||
|
||||
requestAnimationFrame(animate);
|
||||
}
|
||||
|
||||
window.addEventListener('resize', () => {
|
||||
resize();
|
||||
});
|
||||
|
||||
init();
|
||||
animate();
|
||||
100
website/js/spa.js
Normal file
100
website/js/spa.js
Normal file
@@ -0,0 +1,100 @@
|
||||
/**
|
||||
* 极简软重定向 (SPA Router)
|
||||
* 拦截所有站内链接,使用 fetch + DOM 替换实现无刷新跳转
|
||||
*/
|
||||
(function() {
|
||||
document.addEventListener('click', function(e) {
|
||||
const link = e.target.closest('a');
|
||||
if (!link || link.target === '_blank' || link.href.startsWith('javascript:')) return;
|
||||
|
||||
const url = new URL(link.href);
|
||||
if (url.origin !== window.location.origin) return; // 仅拦截站内
|
||||
|
||||
e.preventDefault();
|
||||
navigate(url.pathname + url.search, true);
|
||||
});
|
||||
|
||||
window.addEventListener('popstate', function(e) {
|
||||
if (e.state && e.state.html) {
|
||||
document.title = e.state.title;
|
||||
document.querySelector('main').innerHTML = e.state.html;
|
||||
initPage();
|
||||
} else {
|
||||
navigate(window.location.pathname + window.location.search, false);
|
||||
}
|
||||
});
|
||||
|
||||
async function navigate(url, push) {
|
||||
try {
|
||||
const res = await fetch(url);
|
||||
if (!res.ok) { window.location.href = url; return; }
|
||||
|
||||
const html = await res.text();
|
||||
const doc = new DOMParser().parseFromString(html, 'text/html');
|
||||
|
||||
// 1. 更新标题
|
||||
document.title = doc.title;
|
||||
|
||||
// 2. 替换 Main 内容
|
||||
const newMain = doc.querySelector('main');
|
||||
const oldMain = document.querySelector('main');
|
||||
if (newMain && oldMain) {
|
||||
oldMain.innerHTML = newMain.innerHTML;
|
||||
}
|
||||
|
||||
// 3. 更新 URL
|
||||
if (push) {
|
||||
history.pushState({
|
||||
url: url,
|
||||
html: newMain ? newMain.innerHTML : '',
|
||||
title: doc.title
|
||||
}, '', url);
|
||||
window.scrollTo(0, 0);
|
||||
}
|
||||
|
||||
// 4. 更新 Dock 高亮
|
||||
document.querySelectorAll('#dock .dock-item').forEach(el => {
|
||||
const href = el.getAttribute('href');
|
||||
el.classList.toggle('active', href && url.startsWith(href));
|
||||
});
|
||||
|
||||
// 5. 重新注入脚本/逻辑
|
||||
injectScripts(doc);
|
||||
initPage();
|
||||
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
window.location.href = url;
|
||||
}
|
||||
}
|
||||
|
||||
function injectScripts(doc) {
|
||||
doc.scripts.forEach(oldScript => {
|
||||
const newScript = document.createElement('script');
|
||||
Array.from(oldScript.attributes).forEach(attr => newScript.setAttribute(attr.name, attr.value));
|
||||
if (oldScript.src) {
|
||||
newScript.src = oldScript.src;
|
||||
document.body.appendChild(newScript);
|
||||
} else {
|
||||
newScript.textContent = oldScript.textContent;
|
||||
document.body.appendChild(newScript);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// 通用页面初始化入口
|
||||
function initPage() {
|
||||
// 尝试调用社区模块初始化
|
||||
if (typeof initCommunity === 'function') {
|
||||
// 如果脚本已加载,直接调用
|
||||
initCommunity();
|
||||
} else {
|
||||
// 否则动态加载
|
||||
if (location.pathname.includes('/community/') || location.pathname.endsWith('/community/')) {
|
||||
const s = document.createElement('script');
|
||||
s.src = '/community/assets/js/community.js';
|
||||
document.body.appendChild(s);
|
||||
}
|
||||
}
|
||||
}
|
||||
})();
|
||||
Reference in New Issue
Block a user