first commit
This commit is contained in:
+272
@@ -0,0 +1,272 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
博客构建脚本:blog/posts/*.md -> website/static/blog/(列表 + 文章页)+ website/static/home.html(首页)
|
||||
|
||||
front matter 格式:
|
||||
---
|
||||
title: 文章标题
|
||||
date: 2026-08-13
|
||||
tags: 工业软件, ResLib
|
||||
---
|
||||
"""
|
||||
import re
|
||||
import sys
|
||||
from datetime import datetime
|
||||
from pathlib import Path
|
||||
|
||||
import markdown
|
||||
|
||||
BASE = Path(__file__).resolve().parent
|
||||
POSTS_DIR = BASE / "posts"
|
||||
OUT_BLOG = BASE.parent / "website" / "static" / "blog"
|
||||
OUT_HOME = BASE.parent / "website" / "static" / "home.html"
|
||||
OUT_ABOUT = BASE.parent / "website" / "static" / "about.html"
|
||||
|
||||
SITE_NAME = "Easy Data Collector"
|
||||
SEO_NAME = "江北的渔夫的空间"
|
||||
|
||||
NAV = [
|
||||
("/", "博客"),
|
||||
("/reslib/", "ResLib"),
|
||||
("/reslib/apply", "申请 License"),
|
||||
("/reslib/status", "查询 / 下载"),
|
||||
("/reslib/package", "资源包获取"),
|
||||
("https://ec.sincos.cc", "Engineer Craft"),
|
||||
("https://dt.sincos.cc", "Digital Twin"),
|
||||
("https://neural.sincos.cc", "Neural Research"),
|
||||
("https://git.sincos.cc", "Gitea Project"),
|
||||
]
|
||||
|
||||
|
||||
def page_header(active: str, title: str = "") -> str:
|
||||
seo_title = f"{title} - {SEO_NAME}" if title else SEO_NAME
|
||||
links = "\n".join(
|
||||
f' <a href="{href}"{" class=active" if href == active else ""}>{label}</a>'
|
||||
for href, label in NAV
|
||||
)
|
||||
return f"""<!DOCTYPE html>
|
||||
<html lang="zh-CN">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>{seo_title}</title>
|
||||
<meta name="description" content="{SEO_NAME} —— 工业软件、标准件库与数字化实践分享">
|
||||
<meta name="keywords" content="{SEO_NAME}, Easy Data Collector, ResLib, 资源中心管理器, Creo, 标准零部件库, 数字孪生">
|
||||
<link rel="stylesheet" href="/static/css/style.css">
|
||||
<link rel="icon" href="/static/img/logo.svg" type="image/svg+xml">
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<header class="site-header">
|
||||
<div class="container header-inner">
|
||||
<a class="brand" href="/">
|
||||
<img src="/static/img/logo.svg" alt="{SITE_NAME}">
|
||||
<span class="brand-name">{SITE_NAME}</span>
|
||||
</a>
|
||||
<nav class="main-nav" id="mainNav">
|
||||
{links}
|
||||
</nav>
|
||||
<button class="nav-toggle" id="navToggle" aria-label="菜单">☰</button>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<main>"""
|
||||
|
||||
|
||||
def page_footer() -> str:
|
||||
return f"""</main>
|
||||
|
||||
<footer class="site-footer">
|
||||
<div class="container footer-inner">
|
||||
<span>Copyright © 2026 {SITE_NAME}. All rights reserved.</span>
|
||||
<span><a href="https://beian.miit.gov.cn/" target="_blank" rel="noopener">苏ICP备2026056863号</a></span>
|
||||
</div>
|
||||
</footer>
|
||||
|
||||
<script src="/static/js/nav.js"></script>
|
||||
</body>
|
||||
</html>"""
|
||||
|
||||
|
||||
def parse_post(path: Path) -> dict:
|
||||
text = path.read_text(encoding="utf-8")
|
||||
m = re.match(r"^---\s*\n(.*?)\n---\s*\n?(.*)$", text, re.S)
|
||||
front, body = ("", text)
|
||||
if m:
|
||||
front, body = m.group(1), m.group(2)
|
||||
meta = {}
|
||||
for line in front.splitlines():
|
||||
if ":" in line:
|
||||
k, _, v = line.partition(":")
|
||||
meta[k.strip()] = v.strip()
|
||||
html = markdown.markdown(
|
||||
body,
|
||||
extensions=["extra", "toc", "sane_lists", "attr_list", "admonition"],
|
||||
)
|
||||
return {
|
||||
"slug": path.stem,
|
||||
"title": meta.get("title", path.stem),
|
||||
"date": meta.get("date", ""),
|
||||
"tags": [t.strip() for t in meta.get("tags", "").split(",") if t.strip()],
|
||||
"html": html,
|
||||
}
|
||||
|
||||
|
||||
def summary(html: str, length: int = 120) -> str:
|
||||
text = re.sub(r"<[^>]+>", "", html)
|
||||
text = re.sub(r"\s+", " ", text).strip()
|
||||
if len(text) > length:
|
||||
text = text[:length] + "…"
|
||||
return text
|
||||
|
||||
|
||||
def fmt_date(s: str) -> str:
|
||||
try:
|
||||
return datetime.strptime(s, "%Y-%m-%d").strftime("%Y年%m月%d日")
|
||||
except ValueError:
|
||||
return s
|
||||
|
||||
|
||||
def build():
|
||||
posts = [parse_post(p) for p in sorted(POSTS_DIR.glob("*.md"), reverse=True)]
|
||||
posts.sort(key=lambda p: p["date"], reverse=True)
|
||||
|
||||
OUT_BLOG.mkdir(parents=True, exist_ok=True)
|
||||
|
||||
# ---------- 博客列表页 ----------
|
||||
cards = []
|
||||
for p in posts:
|
||||
tags = "".join(f'<span class="tag">{t}</span>' for t in p["tags"])
|
||||
cards.append(f"""
|
||||
<a class="post-card" href="/blog/{p['slug']}/">
|
||||
<div class="post-meta"><span class="post-date">{fmt_date(p['date'])}</span>{tags}</div>
|
||||
<h3 class="post-title">{p['title']}</h3>
|
||||
<p class="post-excerpt">{summary(p['html'])}</p>
|
||||
</a>""")
|
||||
if not cards:
|
||||
cards.append('<p class="section-desc">暂无文章,敬请期待。</p>')
|
||||
|
||||
blog_list = f"""{page_header("/blog/")}
|
||||
<section class="section">
|
||||
<div class="container">
|
||||
<h2 class="section-title">博客</h2>
|
||||
<p class="section-desc">工业软件、标准件库与数字化实践记录。</p>
|
||||
<div class="post-list">
|
||||
{''.join(cards)}
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
{page_footer()}"""
|
||||
(OUT_BLOG / "index.html").write_text(blog_list, encoding="utf-8")
|
||||
|
||||
# ---------- 文章详情页 ----------
|
||||
for p in posts:
|
||||
tags = "".join(f'<span class="tag">{t}</span>' for t in p["tags"])
|
||||
html = f"""{page_header(p['slug'], p['title'])}
|
||||
<section class="section">
|
||||
<div class="container narrow">
|
||||
<nav class="breadcrumb"><a href="/">博客</a> / <a href="/blog/">文章列表</a></nav>
|
||||
<h2 class="section-title">{p['title']}</h2>
|
||||
<div class="post-meta"><span class="post-date">{fmt_date(p['date'])}</span>{tags}</div>
|
||||
<article class="article">
|
||||
{p['html']}
|
||||
</article>
|
||||
</div>
|
||||
</section>
|
||||
{page_footer()}"""
|
||||
(OUT_BLOG / p["slug"]).mkdir(parents=True, exist_ok=True)
|
||||
(OUT_BLOG / p["slug"] / "index.html").write_text(html, encoding="utf-8")
|
||||
|
||||
# ---------- 首页(博客首页) ----------
|
||||
recent = "".join(f"""
|
||||
<a class="post-card" href="/blog/{p['slug']}/">
|
||||
<div class="post-meta"><span class="post-date">{fmt_date(p['date'])}</span></div>
|
||||
<h3 class="post-title">{p['title']}</h3>
|
||||
<p class="post-excerpt">{summary(p['html'], 90)}</p>
|
||||
</a>""" for p in posts[:3])
|
||||
if not recent:
|
||||
recent = '<p class="section-desc">暂无文章,敬请期待。</p>'
|
||||
|
||||
home = f"""{page_header("/")}
|
||||
<section class="hero">
|
||||
<div class="hero-euler" aria-hidden="true">e<sup>iπ</sup> + 1 = 0</div>
|
||||
<div class="container">
|
||||
<p class="hero-eyebrow">INDUSTRIAL SOFTWARE · STANDARD PARTS · DIGITAL PRACTICE</p>
|
||||
<h1>从数学出发</h1>
|
||||
<p class="hero-sub">记录工程与代码 —— 工业软件 · 标准件库 · 数字化实践,Easy Data Collector 旗下各平台的技术博客。</p>
|
||||
<div class="hero-actions">
|
||||
<a href="/blog/" class="btn btn-light">全部文章</a>
|
||||
<a href="/reslib/" class="btn btn-outline-light">ResLib 产品</a>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="section">
|
||||
<div class="container">
|
||||
<h2 class="section-title">平台</h2>
|
||||
<div class="grid">
|
||||
<a class="card card-link" href="/reslib/">
|
||||
<div class="icon">▤</div><h3>ResLib</h3><p>资源中心管理器 —— Creo 7.0+ 企业级标准零部件管理库,含 LIC 授权体系与在线申请。</p>
|
||||
</a>
|
||||
<a class="card card-link" href="https://ec.sincos.cc">
|
||||
<div class="icon">⚙</div><h3>Engineer Craft</h3><p>工程实践与工艺设计平台。</p>
|
||||
</a>
|
||||
<a class="card card-link" href="https://dt.sincos.cc">
|
||||
<div class="icon">◉</div><h3>Digital Twin</h3><p>three.js 三维可视化 · MQTT 实时数据 · 数字孪生平台。</p>
|
||||
</a>
|
||||
<a class="card card-link" href="https://neural.sincos.cc">
|
||||
<div class="icon">◈</div><h3>Neural Research</h3><p>神经网络与算法研究。</p>
|
||||
</a>
|
||||
<a class="card card-link" href="https://git.sincos.cc">
|
||||
<div class="icon">⌥</div><h3>Gitea Project</h3><p>代码托管与版本管理。</p>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="section section-alt">
|
||||
<div class="container">
|
||||
<h2 class="section-title">最新文章</h2>
|
||||
<div class="post-list">
|
||||
{recent}
|
||||
</div>
|
||||
<div style="text-align:center; margin-top: 30px;">
|
||||
<a href="/blog/" class="btn btn-primary">查看全部文章</a>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
{page_footer()}"""
|
||||
OUT_HOME.write_text(home, encoding="utf-8")
|
||||
|
||||
# ---------- 关于页 ----------
|
||||
about = f"""{page_header("/about/")}
|
||||
<section class="section">
|
||||
<div class="container narrow">
|
||||
<h2 class="section-title">关于</h2>
|
||||
<div class="article">
|
||||
<h3>SinCos</h3>
|
||||
<p>工业软件方向开发者。</p>
|
||||
<h3>正在做</h3>
|
||||
<ul>
|
||||
<li><b>ResLib</b>:资源中心管理器 —— Creo 7.0+ 标准零部件管理库(Pro/TOOLKIT DLL 插件),支持 License 在线申请与审批</li>
|
||||
<li><b>Engineer Craft</b>:工程实践与工艺设计平台</li>
|
||||
<li><b>Digital Twin</b>:数字孪生平台(three.js + Python + MQTT/EMQX)</li>
|
||||
<li><b>Neural Research</b>:神经网络与算法研究</li>
|
||||
<li><b>Gitea Project</b>:代码托管与版本管理</li>
|
||||
</ul>
|
||||
<h3>联系方式</h3>
|
||||
<ul>
|
||||
<li>博客:<a href="/">sincos.cc</a></li>
|
||||
<li>License 申请:<a href="/reslib/apply">/reslib/apply</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
{page_footer()}"""
|
||||
OUT_ABOUT.write_text(about, encoding="utf-8")
|
||||
|
||||
print(f"构建完成:{len(posts)} 篇文章 -> {OUT_BLOG} / home.html / about.html")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
build()
|
||||
@@ -0,0 +1,17 @@
|
||||
---
|
||||
title: 博客开张
|
||||
date: 2026-08-13
|
||||
tags: 公告, ResLib, 数字孪生
|
||||
---
|
||||
|
||||
# 博客开张
|
||||
|
||||
用统一风格搭建的聚合博客正式上线,部署在 sincos.cc。
|
||||
|
||||
本站主要记录:
|
||||
|
||||
- **工业软件**:Creo 8.0 二次开发、企业级标准零部件库 ResLib 的设计与 License 体系
|
||||
- **数字化孪生**:three.js 可视化、MQTT 数据链路(EMQX)、数字孪生平台落地
|
||||
- **工程与运维**:Python、1Panel、OpenResty 反代与多站点部署实践
|
||||
|
||||
后续内容陆续更新。
|
||||
Reference in New Issue
Block a user