feat: 添加项目区域 - Coolbuy PaaS 作为项目展示
- 新增"项目"标签页,使用紫色主题 - Coolbuy PaaS 包含4个模块:租户端、平台端、Auth API、Foundation API - 支持模块状态检测和搜索过滤 - 统计信息包含项目模块数量 - 修复加密脚本正则匹配问题 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -11,9 +11,11 @@ const encrypted = CryptoJS.AES.encrypt(originalHtml, password).toString();
|
||||
// 读取模板
|
||||
let template = fs.readFileSync('nav-home-encrypted.html', 'utf8');
|
||||
|
||||
// 替换占位符
|
||||
template = template.replace('const ENCRYPTED_CONTENT = "ENCRYPTED_PLACEHOLDER";',
|
||||
`const ENCRYPTED_CONTENT = "${encrypted}";`);
|
||||
// 使用正则替换已有的加密内容(匹配 const ENCRYPTED_CONTENT = "..." 格式)
|
||||
template = template.replace(
|
||||
/const ENCRYPTED_CONTENT = "[^"]*";/,
|
||||
`const ENCRYPTED_CONTENT = "${encrypted}";`
|
||||
);
|
||||
|
||||
// 写入最终文件
|
||||
fs.writeFileSync('nav-home-encrypted.html', template);
|
||||
|
||||
File diff suppressed because one or more lines are too long
319
nav-home.html
319
nav-home.html
@@ -29,6 +29,10 @@
|
||||
--accent-enterprise-dim: #52c41a33;
|
||||
--accent-enterprise-glow: #52c41a55;
|
||||
|
||||
--accent-projects: #9d4edd;
|
||||
--accent-projects-dim: #9d4edd33;
|
||||
--accent-projects-glow: #9d4edd55;
|
||||
|
||||
--status-online: #00ff9d;
|
||||
--status-offline: #ff4466;
|
||||
--status-unknown: #ffaa00;
|
||||
@@ -778,6 +782,174 @@
|
||||
.hidden {
|
||||
display: none !important;
|
||||
}
|
||||
|
||||
/* Projects Section Styles */
|
||||
.projects-container {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 2rem;
|
||||
}
|
||||
|
||||
.project-card {
|
||||
background: var(--bg-card);
|
||||
border: 1px solid var(--border-dim);
|
||||
border-radius: var(--radius-lg);
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.project-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 1rem;
|
||||
padding: 1.25rem 1.5rem;
|
||||
background: var(--bg-elevated);
|
||||
border-bottom: 1px solid var(--border-dim);
|
||||
}
|
||||
|
||||
.project-icon {
|
||||
width: 48px;
|
||||
height: 48px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
background: var(--bg-deep);
|
||||
border: 1px solid var(--border-dim);
|
||||
border-radius: var(--radius-md);
|
||||
font-size: 1.5rem;
|
||||
}
|
||||
|
||||
.project-info {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.project-name {
|
||||
font-family: var(--font-display);
|
||||
font-size: 1.125rem;
|
||||
font-weight: 600;
|
||||
letter-spacing: 0.05em;
|
||||
margin-bottom: 0.25rem;
|
||||
}
|
||||
|
||||
.project-description {
|
||||
font-size: 0.75rem;
|
||||
color: var(--text-secondary);
|
||||
}
|
||||
|
||||
.project-modules {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fill, minmax(240px, 1fr));
|
||||
gap: 1rem;
|
||||
padding: 1.25rem;
|
||||
}
|
||||
|
||||
.module-card {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.875rem;
|
||||
padding: 1rem;
|
||||
background: var(--bg-surface);
|
||||
border: 1px solid var(--border-dim);
|
||||
border-radius: var(--radius-md);
|
||||
text-decoration: none;
|
||||
color: inherit;
|
||||
transition: all var(--transition-smooth);
|
||||
}
|
||||
|
||||
.module-card:hover {
|
||||
border-color: var(--accent-home);
|
||||
background: var(--accent-home-dim);
|
||||
transform: translateY(-2px);
|
||||
box-shadow: 0 4px 20px var(--accent-home-glow);
|
||||
}
|
||||
|
||||
body.enterprise .module-card:hover,
|
||||
body.projects .module-card:hover {
|
||||
border-color: var(--accent-enterprise);
|
||||
background: var(--accent-enterprise-dim);
|
||||
box-shadow: 0 4px 20px var(--accent-enterprise-glow);
|
||||
}
|
||||
|
||||
.module-icon {
|
||||
width: 36px;
|
||||
height: 36px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
background: var(--bg-deep);
|
||||
border-radius: var(--radius-sm);
|
||||
font-size: 1.125rem;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.module-info {
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.module-name {
|
||||
font-size: 0.875rem;
|
||||
font-weight: 500;
|
||||
margin-bottom: 0.125rem;
|
||||
}
|
||||
|
||||
.module-description {
|
||||
font-size: 0.6875rem;
|
||||
color: var(--text-dim);
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
.module-status {
|
||||
width: 8px;
|
||||
height: 8px;
|
||||
border-radius: 50%;
|
||||
background: var(--status-unknown);
|
||||
box-shadow: 0 0 8px currentColor;
|
||||
animation: pulse 2s ease-in-out infinite;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.module-status.online {
|
||||
background: var(--status-online);
|
||||
box-shadow: 0 0 8px var(--status-online);
|
||||
}
|
||||
|
||||
/* Tab button middle styling */
|
||||
.tab-btn:not(:first-child):not(:last-child) {
|
||||
border-radius: 0;
|
||||
border-right: none;
|
||||
}
|
||||
|
||||
body.projects .grid-bg::before {
|
||||
background: radial-gradient(ellipse at 50% 0%, var(--accent-projects-dim) 0%, transparent 60%);
|
||||
}
|
||||
|
||||
body.projects .time-display .seconds {
|
||||
color: var(--accent-projects);
|
||||
}
|
||||
|
||||
.tab-btn.active[data-tab="projects"] {
|
||||
color: var(--accent-projects);
|
||||
border-color: var(--accent-projects);
|
||||
background: var(--accent-projects-dim);
|
||||
box-shadow: 0 0 20px var(--accent-projects-glow), inset 0 0 20px var(--accent-projects-dim);
|
||||
}
|
||||
|
||||
body.projects .stat-value {
|
||||
color: var(--accent-projects);
|
||||
}
|
||||
|
||||
body.projects .search-input:focus {
|
||||
border-color: var(--accent-projects);
|
||||
box-shadow: 0 0 0 3px var(--accent-projects-dim);
|
||||
}
|
||||
|
||||
body.projects .module-card:hover {
|
||||
border-color: var(--accent-projects);
|
||||
background: var(--accent-projects-dim);
|
||||
box-shadow: 0 4px 20px var(--accent-projects-glow);
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
@@ -801,6 +973,9 @@
|
||||
<button class="tab-btn" data-tab="enterprise">
|
||||
<span class="tab-icon">🏢</span>企业
|
||||
</button>
|
||||
<button class="tab-btn" data-tab="projects">
|
||||
<span class="tab-icon">📦</span>项目
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div class="search-container">
|
||||
@@ -824,6 +999,13 @@
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- Projects Section -->
|
||||
<section class="section" id="projects-section">
|
||||
<div class="projects-container" id="projects-cards">
|
||||
<!-- Projects will be rendered by JS -->
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<footer class="stats-footer">
|
||||
<div class="stat-item">
|
||||
<div class="stat-value" id="total-services">0</div>
|
||||
@@ -990,6 +1172,53 @@
|
||||
url: 'https://dbeaver.pipexerp.com',
|
||||
icon: '🗄️',
|
||||
isCustom: false
|
||||
},
|
||||
{
|
||||
id: 'cool-lining',
|
||||
name: 'Cool Lining',
|
||||
description: '李宁酷采 · 电商采购系统',
|
||||
url: 'http://100.114.124.68:8080/cool/',
|
||||
icon: '👟',
|
||||
isCustom: false
|
||||
}
|
||||
],
|
||||
projects: [
|
||||
{
|
||||
id: 'coolbuy-paas',
|
||||
name: 'Coolbuy PaaS',
|
||||
description: '多租户电商 SaaS 平台',
|
||||
icon: '🛒',
|
||||
isCustom: false,
|
||||
modules: [
|
||||
{
|
||||
id: 'tenant-web',
|
||||
name: '租户端',
|
||||
description: '租户前端 · B端商城',
|
||||
url: 'http://100.118.62.18:28888',
|
||||
icon: '🏪'
|
||||
},
|
||||
{
|
||||
id: 'platform-web',
|
||||
name: '平台端',
|
||||
description: '平台管理 · 运营后台',
|
||||
url: 'http://100.118.62.18:18888',
|
||||
icon: '🏢'
|
||||
},
|
||||
{
|
||||
id: 'auth-api',
|
||||
name: 'Auth API',
|
||||
description: '认证服务 · JWT鉴权',
|
||||
url: 'http://100.118.62.18:27089',
|
||||
icon: '🔐'
|
||||
},
|
||||
{
|
||||
id: 'foundation-api',
|
||||
name: 'Foundation API',
|
||||
description: '基础服务 · 用户权限',
|
||||
url: 'http://100.118.62.18:27092',
|
||||
icon: '🏛️'
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
};
|
||||
@@ -1004,6 +1233,7 @@
|
||||
const sections = document.querySelectorAll('.section');
|
||||
const homeCards = document.getElementById('home-cards');
|
||||
const enterpriseCards = document.getElementById('enterprise-cards');
|
||||
const projectsCards = document.getElementById('projects-cards');
|
||||
const searchInput = document.getElementById('searchInput');
|
||||
const addModal = document.getElementById('addModal');
|
||||
const addForm = document.getElementById('addForm');
|
||||
@@ -1131,10 +1361,84 @@
|
||||
updateStats();
|
||||
}
|
||||
|
||||
// Render Projects
|
||||
async function renderProjects(filter = '') {
|
||||
const projects = services.projects || [];
|
||||
|
||||
const filteredProjects = projects.filter(p =>
|
||||
p.name.toLowerCase().includes(filter.toLowerCase()) ||
|
||||
p.description.toLowerCase().includes(filter.toLowerCase()) ||
|
||||
p.modules.some(m =>
|
||||
m.name.toLowerCase().includes(filter.toLowerCase()) ||
|
||||
m.description.toLowerCase().includes(filter.toLowerCase())
|
||||
)
|
||||
);
|
||||
|
||||
let html = '';
|
||||
|
||||
for (const project of filteredProjects) {
|
||||
let modulesHtml = '';
|
||||
for (const module of project.modules) {
|
||||
modulesHtml += `
|
||||
<a href="${module.url}" target="_blank" class="module-card" data-url="${module.url}">
|
||||
<div class="module-icon">${module.icon}</div>
|
||||
<div class="module-info">
|
||||
<div class="module-name">${module.name}</div>
|
||||
<div class="module-description">${module.description}</div>
|
||||
</div>
|
||||
<div class="module-status" data-url="${module.url}"></div>
|
||||
</a>
|
||||
`;
|
||||
}
|
||||
|
||||
html += `
|
||||
<div class="project-card" data-id="${project.id}">
|
||||
<div class="project-header">
|
||||
<div class="project-icon">${project.icon}</div>
|
||||
<div class="project-info">
|
||||
<h3 class="project-name">${project.name}</h3>
|
||||
<p class="project-description">${project.description}</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="project-modules">
|
||||
${modulesHtml}
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
projectsCards.innerHTML = html;
|
||||
|
||||
// Check status for each module
|
||||
for (const project of filteredProjects) {
|
||||
for (const module of project.modules) {
|
||||
const status = await checkStatus(module.url);
|
||||
const dots = projectsCards.querySelectorAll(`[data-url="${module.url}"].module-status`);
|
||||
dots.forEach(dot => {
|
||||
dot.classList.remove('online');
|
||||
if (status === 'online') {
|
||||
dot.classList.add('online');
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
updateStats();
|
||||
}
|
||||
|
||||
// Update Stats
|
||||
function updateStats() {
|
||||
const total = (services.home?.length || 0) + (services.enterprise?.length || 0);
|
||||
const onlineCount = document.querySelectorAll('.status-dot.online').length;
|
||||
// Count services from home and enterprise
|
||||
let total = (services.home?.length || 0) + (services.enterprise?.length || 0);
|
||||
|
||||
// Count modules from projects
|
||||
const projects = services.projects || [];
|
||||
for (const project of projects) {
|
||||
total += project.modules?.length || 0;
|
||||
}
|
||||
|
||||
// Count online indicators (both .status-dot and .module-status)
|
||||
const onlineCount = document.querySelectorAll('.status-dot.online, .module-status.online').length;
|
||||
|
||||
document.getElementById('total-services').textContent = total;
|
||||
document.getElementById('online-services').textContent = onlineCount;
|
||||
@@ -1152,7 +1456,9 @@
|
||||
sections.forEach(s => s.classList.remove('active'));
|
||||
document.getElementById(`${tab}-section`).classList.add('active');
|
||||
|
||||
document.body.classList.toggle('enterprise', tab === 'enterprise');
|
||||
document.body.classList.remove('enterprise', 'projects');
|
||||
if (tab === 'enterprise') document.body.classList.add('enterprise');
|
||||
if (tab === 'projects') document.body.classList.add('projects');
|
||||
currentTab = tab;
|
||||
|
||||
searchInput.value = '';
|
||||
@@ -1164,6 +1470,7 @@
|
||||
const filter = e.target.value;
|
||||
renderCards('home', filter);
|
||||
renderCards('enterprise', filter);
|
||||
renderProjects(filter);
|
||||
});
|
||||
|
||||
// Modal Functions
|
||||
@@ -1264,16 +1571,22 @@
|
||||
if (e.key === '2' && e.altKey) {
|
||||
tabBtns[1].click();
|
||||
}
|
||||
|
||||
if (e.key === '3' && e.altKey) {
|
||||
tabBtns[2].click();
|
||||
}
|
||||
});
|
||||
|
||||
// Initial Render
|
||||
renderCards('home');
|
||||
renderCards('enterprise');
|
||||
renderProjects();
|
||||
|
||||
// Periodic status check
|
||||
setInterval(() => {
|
||||
renderCards('home', searchInput.value);
|
||||
renderCards('enterprise', searchInput.value);
|
||||
renderProjects(searchInput.value);
|
||||
}, 60000);
|
||||
</script>
|
||||
</body>
|
||||
|
||||
Reference in New Issue
Block a user