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:
qiudl
2026-01-04 20:38:54 +10:30
parent 76388f885c
commit 9e4ed2622d
3 changed files with 322 additions and 7 deletions

View File

@@ -11,9 +11,11 @@ const encrypted = CryptoJS.AES.encrypt(originalHtml, password).toString();
// 读取模板 // 读取模板
let template = fs.readFileSync('nav-home-encrypted.html', 'utf8'); let template = fs.readFileSync('nav-home-encrypted.html', 'utf8');
// 替换占位符 // 使用正则替换已有的加密内容(匹配 const ENCRYPTED_CONTENT = "..." 格式)
template = template.replace('const ENCRYPTED_CONTENT = "ENCRYPTED_PLACEHOLDER";', template = template.replace(
`const ENCRYPTED_CONTENT = "${encrypted}";`); /const ENCRYPTED_CONTENT = "[^"]*";/,
`const ENCRYPTED_CONTENT = "${encrypted}";`
);
// 写入最终文件 // 写入最终文件
fs.writeFileSync('nav-home-encrypted.html', template); fs.writeFileSync('nav-home-encrypted.html', template);

File diff suppressed because one or more lines are too long

View File

@@ -29,6 +29,10 @@
--accent-enterprise-dim: #52c41a33; --accent-enterprise-dim: #52c41a33;
--accent-enterprise-glow: #52c41a55; --accent-enterprise-glow: #52c41a55;
--accent-projects: #9d4edd;
--accent-projects-dim: #9d4edd33;
--accent-projects-glow: #9d4edd55;
--status-online: #00ff9d; --status-online: #00ff9d;
--status-offline: #ff4466; --status-offline: #ff4466;
--status-unknown: #ffaa00; --status-unknown: #ffaa00;
@@ -778,6 +782,174 @@
.hidden { .hidden {
display: none !important; 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> </style>
</head> </head>
<body> <body>
@@ -801,6 +973,9 @@
<button class="tab-btn" data-tab="enterprise"> <button class="tab-btn" data-tab="enterprise">
<span class="tab-icon">🏢</span>企业 <span class="tab-icon">🏢</span>企业
</button> </button>
<button class="tab-btn" data-tab="projects">
<span class="tab-icon">📦</span>项目
</button>
</div> </div>
<div class="search-container"> <div class="search-container">
@@ -824,6 +999,13 @@
</div> </div>
</section> </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"> <footer class="stats-footer">
<div class="stat-item"> <div class="stat-item">
<div class="stat-value" id="total-services">0</div> <div class="stat-value" id="total-services">0</div>
@@ -990,6 +1172,53 @@
url: 'https://dbeaver.pipexerp.com', url: 'https://dbeaver.pipexerp.com',
icon: '🗄️', icon: '🗄️',
isCustom: false 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 sections = document.querySelectorAll('.section');
const homeCards = document.getElementById('home-cards'); const homeCards = document.getElementById('home-cards');
const enterpriseCards = document.getElementById('enterprise-cards'); const enterpriseCards = document.getElementById('enterprise-cards');
const projectsCards = document.getElementById('projects-cards');
const searchInput = document.getElementById('searchInput'); const searchInput = document.getElementById('searchInput');
const addModal = document.getElementById('addModal'); const addModal = document.getElementById('addModal');
const addForm = document.getElementById('addForm'); const addForm = document.getElementById('addForm');
@@ -1131,10 +1361,84 @@
updateStats(); 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 // Update Stats
function updateStats() { function updateStats() {
const total = (services.home?.length || 0) + (services.enterprise?.length || 0); // Count services from home and enterprise
const onlineCount = document.querySelectorAll('.status-dot.online').length; 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('total-services').textContent = total;
document.getElementById('online-services').textContent = onlineCount; document.getElementById('online-services').textContent = onlineCount;
@@ -1152,7 +1456,9 @@
sections.forEach(s => s.classList.remove('active')); sections.forEach(s => s.classList.remove('active'));
document.getElementById(`${tab}-section`).classList.add('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; currentTab = tab;
searchInput.value = ''; searchInput.value = '';
@@ -1164,6 +1470,7 @@
const filter = e.target.value; const filter = e.target.value;
renderCards('home', filter); renderCards('home', filter);
renderCards('enterprise', filter); renderCards('enterprise', filter);
renderProjects(filter);
}); });
// Modal Functions // Modal Functions
@@ -1264,16 +1571,22 @@
if (e.key === '2' && e.altKey) { if (e.key === '2' && e.altKey) {
tabBtns[1].click(); tabBtns[1].click();
} }
if (e.key === '3' && e.altKey) {
tabBtns[2].click();
}
}); });
// Initial Render // Initial Render
renderCards('home'); renderCards('home');
renderCards('enterprise'); renderCards('enterprise');
renderProjects();
// Periodic status check // Periodic status check
setInterval(() => { setInterval(() => {
renderCards('home', searchInput.value); renderCards('home', searchInput.value);
renderCards('enterprise', searchInput.value); renderCards('enterprise', searchInput.value);
renderProjects(searchInput.value);
}, 60000); }, 60000);
</script> </script>
</body> </body>