first commit

This commit is contained in:
2026-08-13 21:24:16 +08:00
commit a306eee7d5
24 changed files with 2457 additions and 0 deletions
+17
View File
@@ -0,0 +1,17 @@
# 环境与密钥
website/.env
.env
# 运行时数据(申请记录/授权文件/资源包)
website/data/
# Python
__pycache__/
*.pyc
*.pyo
.venv/
venv/
# 系统
.DS_Store
Thumbs.db
+28
View File
@@ -0,0 +1,28 @@
# Easy Data Collector 聚合站
`sincos.cc` 单站架构:博客(首页)+ ResLib(License 申请系统)+ 子页板块。
## 目录
```
website/ FastAPI 后端(博客/ResLib/申请/管理后台,端口 8003)
blog/ 博客构建脚本 + 文章(markdown)
deploy/ 部署文档
```
## 快速开始
```bash
pip install -r website/requirements.txt
cd website
uvicorn main:app --host 0.0.0.0 --port 8003
```
管理后台密码:环境变量 `RESLIB_ADMIN_PASSWORD` 或 `/opt/website/.env` 文件。
## 写博客
`blog/posts/*.md`(front matter: title/date/tags)→ `python -X utf8 blog/build.py`
生成结果输出到 `website/static/`(home.html / about.html / blog/)。
部署详见 `deploy/DEPLOY.md`。
+272
View File
@@ -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 &copy; 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()
+17
View File
@@ -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 反代与多站点部署实践
后续内容陆续更新。
+195
View File
@@ -0,0 +1,195 @@
# SinCos 聚合站部署手册(详细版)
- 服务器:`124.220.63.9`(1Panel + OpenResty + Python)
- 域名:`sincos.cc`(腾讯云备案,已解析 A 记录 → 124.220.63.9)
- 站点形态:单站 FastAPI(:8003),根路径 = 博客首页,其余为子页面
## 目录结构
```
website/ ← 整个目录上传到服务器 /opt/website
├─ main.py FastAPI 主程序
├─ license.py MAC→LIC 算法
├─ requirements.txt 依赖清单
├─ run.sh 启动脚本
├─ static/ 前端(首页/博客/ResLib/申请/查询/资源包/管理后台)
└─ data/ 运行时数据(自动创建)
├─ requests/*.txt 申请记录(DokuWiki 式)
├─ licenses/*.lic 生成的授权文件
└─ packages/ ★ 资源包(把真实资源包 zip 放这里)
```
---
## 第一步:准备本地文件
1. 打包本地 `website/` 目录为 `website.zip`(只打包目录本身,不含外层)
---
## 第二步:上传到服务器
**方式 A:1Panel 网页上传**
1Panel → 文件 → 进入 `/opt` → 上传 `website.zip` → 右键解压 → 得到 `/opt/website/`
**方式 B:scp 命令行**
```bash
scp -r D:\黑奴编程\网站\website root@124.220.63.9:/opt/website
```
---
## 第三步:服务器安装依赖
SSH 登录服务器后执行:
```bash
# 确认 python 版本(1Panel 环境一般自带 python3)
python3 --version
# 安装依赖
pip3 install -r /opt/website/requirements.txt -i https://pypi.tuna.tsinghua.edu.cn/simple
```
若提示 pip3 不存在,用 `python3 -m pip install -r /opt/website/requirements.txt -i https://pypi.tuna.tsinghua.edu.cn/simple`
---
## 第四步:设置管理密码并启动
**方式 A(推荐):1Panel 进程守护(图形化)**
1. 1Panel → 应用商店 → 安装 **进程守护**(Process Supervisor)
2. 1Panel → 进程守护 → 创建:
- 名称:`reslib`
- 运行用户:`root`
- 运行目录:`/opt/website`
- 启动命令:`uvicorn main:app --host 0.0.0.0 --port 8003`
- 环境变量:`RESLIB_ADMIN_PASSWORD=你的强密码`(管理后台密码,务必修改)
- 开机自启:开
3. 创建后进程状态为"运行中"即成功
**方式 B:1Panel Python 项目(图形化)**
1Panel → 网站 → 创建网站 → 选择 **Python 项目**:
- 运行环境:选择已装的 Python
- 项目路径:`/opt/website`
- 启动命令:`uvicorn main:app --host 0.0.0.0 --port 8003`
- 环境变量:`RESLIB_ADMIN_PASSWORD=你的强密码`
**方式 C:临时测试**
```bash
cd /opt/website && RESLIB_ADMIN_PASSWORD="你的强密码" python3 -m uvicorn main:app --host 0.0.0.0 --port 8003
```
验证服务已监听:
```bash
curl -s -o /dev/null -w "%{http_code}" http://127.0.0.1:8003/
# 应返回 200
```
---
## 第五步:1Panel 建站(反代)
1Panel → 网站 → 创建网站 → **反向代理**:
- 主域名:`sincos.cc`
- 其他域名:留空(不需要 www)
- 代理地址:`http://127.0.0.1:8003`
- 创建
然后 网站 → 选中 sincos.cc → **网站** → 确认 OpenResty 已启用(反向代理自动走 OpenResty)。
---
## 第六步:HTTPS 证书
1Panel → 网站 → sincos.cc → **HTTPS** → 申请证书:
- 类型:Let's Encrypt(推荐,自动续期)
- 勾选:强制 HTTPS、自动续期
- 申请成功后站点即带 https://
> 备案域名证书申请无特殊要求。若 Let's Encrypt 连不上,可改用腾讯云免费证书(DNSPod 申请 → 上传证书)。
---
## 第七步:防火墙放行
1Panel → 安全 → 防火墙:
- 放行端口:`80`、`443`(8003 不需要对外开放,只在服务器内网访问)
- 若之前有其他站点端口(30000/10000 等)保持原状
---
## 第八步:上线验证
| 地址 | 预期 |
|---|---|
| `https://sincos.cc/` | 博客首页(从数学出发 + 平台卡片 + 最新文章) |
| `https://sincos.cc/blog/` | 文章列表 |
| `https://sincos.cc/reslib/` | ResLib 产品介绍 |
| `https://sincos.cc/reslib/apply` | License 申请(MAC 输入) |
| `https://sincos.cc/reslib/status` | 查询/下载 |
| `https://sincos.cc/reslib/package` | 资源包获取(仅 approved) |
| `https://sincos.cc/admin` | 管理后台(密码 = 第四步设置的) |
---
## 第九步:数据目录初始化
```bash
mkdir -p /opt/website/data/{requests,licenses,packages}
```
- `packages/` 放入真实资源包文件(zip 等),文件名即展示给用户的名字
- `requests/`、`licenses/` 由系统自动写入
---
## 日常维护
**写博客**
```powershell
# 本地
# 1. 新建 blog\posts\2026-xx-xx-标题.md(front matter: title/date/tags)
# 2. 构建
python -X utf8 blog\build.py
# 3. 将 website\static\home.html、about.html、blog\ 目录 上传覆盖到服务器 /opt/website/static/
```
**备份**(每周):备份 `/opt/website/data/` 目录即可(申请记录 + 授权文件)
```bash
tar czf /root/reslib-backup-$(date +%F).tar.gz /opt/website/data
```
**查看日志**
```bash
# 1Panel 进程守护 → reslib → 日志
```
**重启服务**
```bash
# 1Panel 进程守护 → reslib → 重启(或先停后启)
```
---
## 常见问题
| 问题 | 处理 |
|---|---|
| 80/443 访问超时 | 检查防火墙放行、1Panel 网站状态 |
| 反代 502 | `curl http://127.0.0.1:8003/` 检查后端是否在跑;1Panel 进程守护查看 reslib 状态 |
| 证书申请失败 | 确认 DNS A 记录解析完成且 80 端口可访问 |
| 申请提交后 txt 乱码 | 确认服务器 `python3` 默认编码 UTF-8(正常不会) |
| 忘记管理密码 | SSH 执行 `export RESLIB_ADMIN_PASSWORD=新密码` 后重启服务 |
## 待办清单
- [ ] 上传 website/ 到服务器
- [ ] 安装依赖
- [ ] 设置 RESLIB_ADMIN_PASSWORD
- [ ] 启动服务并 curl 验证 200
- [ ] 1Panel 建反代站 + SSL
- [ ] 防火墙放行 80/443
- [ ] 放真实资源包到 data/packages/
- [ ] 全站访问验证
+37
View File
@@ -0,0 +1,37 @@
# -*- coding: utf-8 -*-
"""
ResLib LIC 授权算法
规则(见开发技术文档 V1.2 附录B):
MAC "AA-BB-CC-DD-EE-FF" -> 去横线/冒号/空格 -> "AABBCCDDEEFF"
-> 每字符映射 A=01 B=02 ... Z=26(数字 0-9 映射为 00-09)
-> 拼接后倒序 -> LIC 文件内容
"""
import re
def normalize_mac(mac: str) -> str:
mac = mac.strip().upper()
mac = re.sub(r"[-:.\s]", "", mac)
if not mac or len(mac) != 12:
raise ValueError("MAC 地址无效,应为 12 位十六进制(如 AA-BB-CC-DD-EE-FF)")
if not re.fullmatch(r"[0-9A-F]{12}", mac):
raise ValueError("MAC 地址包含非法字符,仅允许 0-9、A-F")
return mac
def mac_to_code(mac: str) -> str:
"""逐字符映射:A=01 B=02 ... Z=26,数字 0-9 -> 00-09"""
out = []
for ch in mac:
if ch.isdigit():
out.append(f"{int(ch):02d}")
else:
out.append(f"{ord(ch) - ord('A') + 1:02d}")
return "".join(out)
def generate_lic(mac: str) -> str:
"""生成 LIC 文件内容(倒序)"""
norm = normalize_mac(mac)
code = mac_to_code(norm)
return code[::-1]
+320
View File
@@ -0,0 +1,320 @@
# -*- coding: utf-8 -*-
"""
ResLib License 申请系统后端 (FastAPI)
存储:data/requests/<id>.txt(DokuWiki 式纯文本)
公开:申请 / 状态查询 / 下载 license
管理:密码登录 -> 审批 -> 生成 res_center.lic
"""
import os
import re
import secrets
import time
from datetime import datetime
from pathlib import Path
from fastapi import FastAPI, Form, HTTPException, Request, Response
from fastapi.responses import FileResponse, PlainTextResponse, RedirectResponse
from fastapi.staticfiles import StaticFiles
from pydantic import BaseModel, Field
from license import generate_lic, normalize_mac
BASE_DIR = Path(__file__).resolve().parent
DATA_DIR = BASE_DIR / "data"
REQ_DIR = DATA_DIR / "requests"
LIC_DIR = DATA_DIR / "licenses"
PKG_DIR = DATA_DIR / "packages"
STATIC_DIR = BASE_DIR / "static"
def load_admin_password() -> str:
env = os.environ.get("RESLIB_ADMIN_PASSWORD", "").strip()
if env:
return env
env_file = BASE_DIR / ".env"
if env_file.is_file():
for line in env_file.read_text(encoding="utf-8").splitlines():
line = line.strip()
if line and not line.startswith("#") and "=" in line:
k, _, v = line.partition("=")
if k.strip() == "RESLIB_ADMIN_PASSWORD":
return v.strip()
return "reslib-admin"
ADMIN_PASSWORD = load_admin_password()
SESSION_FILE = DATA_DIR / "session.txt"
STATUS_PENDING = "pending"
STATUS_APPROVED = "approved"
STATUS_DENIED = "denied"
for d in (REQ_DIR, LIC_DIR, PKG_DIR):
d.mkdir(parents=True, exist_ok=True)
app = FastAPI(title="ResLib License System", docs_url=None, redoc_url=None)
# ---------------------------------------------------------------------------
# 工具
# ---------------------------------------------------------------------------
def new_request_id() -> str:
stamp = datetime.now().strftime("%Y%m%d-%H%M%S")
return f"{stamp}-{secrets.token_hex(3).upper()}"
def request_path(req_id: str) -> Path:
if not re.fullmatch(r"\d{8}-\d{6}-[0-9A-F]{6}", req_id):
raise HTTPException(400, "无效的申请编号")
return REQ_DIR / f"{req_id}.txt"
def read_request(req_id: str) -> dict:
path = request_path(req_id)
if not path.is_file():
raise HTTPException(404, "申请不存在")
data = {}
for line in path.read_text(encoding="utf-8").splitlines():
if ":" in line:
k, _, v = line.partition(":")
data[k.strip()] = v.strip()
return data
def write_request(data: dict):
path = request_path(data["id"])
content = "\n".join(f"{k}: {data.get(k, '')}" for k in (
"id", "created", "status", "mac", "company", "contact",
"email", "creo", "seats", "notes", "reason",
))
path.write_text(content + "\n", encoding="utf-8")
def save_lic(req_id: str, mac: str) -> Path:
lic_path = LIC_DIR / f"{req_id}.lic"
lic_path.write_text(generate_lic(mac) + "\n", encoding="ascii")
return lic_path
def new_session_token() -> str:
token = secrets.token_hex(24)
SESSION_FILE.write_text(token, encoding="utf-8")
return token
def get_session_token() -> str:
if SESSION_FILE.is_file():
return SESSION_FILE.read_text(encoding="utf-8").strip()
return ""
def is_admin(request: Request) -> bool:
cookie = request.cookies.get("reslib_admin")
return bool(cookie) and secrets.compare_digest(cookie, get_session_token())
def require_admin(request: Request):
if not is_admin(request):
raise HTTPException(401, "未登录或会话已失效")
# ---------------------------------------------------------------------------
# 公开 API
# ---------------------------------------------------------------------------
class ApplyForm(BaseModel):
mac: str = Field(..., min_length=1)
company: str = Field(..., min_length=1)
contact: str = Field(..., min_length=1)
email: str = Field(..., min_length=1)
creo: str = ""
seats: str = "1"
notes: str = ""
@app.post("/api/apply")
def api_apply(form: ApplyForm):
try:
mac = normalize_mac(form.mac)
except ValueError as e:
raise HTTPException(400, str(e))
if "@" not in form.email or "." not in form.email.split("@")[-1]:
raise HTTPException(400, "邮箱格式不正确")
req_id = new_request_id()
data = {
"id": req_id,
"created": datetime.now().strftime("%Y-%m-%d %H:%M:%S"),
"status": STATUS_PENDING,
"mac": mac,
"company": form.company.strip(),
"contact": form.contact.strip(),
"email": form.email.strip(),
"creo": form.creo.strip(),
"seats": form.seats.strip(),
"notes": form.notes.strip(),
"reason": "",
}
write_request(data)
return {"id": req_id, "status": STATUS_PENDING}
@app.get("/api/status/{req_id}")
def api_status(req_id: str):
data = read_request(req_id)
return {
"id": data.get("id"),
"status": data.get("status"),
"created": data.get("created"),
"reason": data.get("reason", ""),
}
@app.get("/api/license/{req_id}")
def api_license(req_id: str):
data = read_request(req_id)
if data.get("status") != STATUS_APPROVED:
raise HTTPException(403, "申请尚未通过审批")
lic_path = LIC_DIR / f"{req_id}.lic"
if not lic_path.is_file():
save_lic(req_id, data["mac"])
lic_path = LIC_DIR / f"{req_id}.lic"
return FileResponse(
lic_path, filename="res_center.lic",
media_type="application/octet-stream",
)
# ---------------------------------------------------------------------------
# 资源包(仅审核通过的用户可获取)
# ---------------------------------------------------------------------------
@app.get("/api/package/{req_id}")
def api_package_list(req_id: str):
data = read_request(req_id)
if data.get("status") != STATUS_APPROVED:
raise HTTPException(403, "资源包仅对审核通过的用户提供,请先完成 License 审批")
files = sorted(p.name for p in PKG_DIR.glob("*") if p.is_file())
if not files:
return {"files": []}
return {"files": files}
@app.get("/api/package/download/{req_id}/{filename}")
def api_package_download(req_id: str, filename: str):
data = read_request(req_id)
if data.get("status") != STATUS_APPROVED:
raise HTTPException(403, "资源包仅对审核通过的用户提供")
name = os.path.basename(filename)
path = PKG_DIR / name
if not path.is_file():
raise HTTPException(404, "资源包不存在")
return FileResponse(path, filename=name, media_type="application/octet-stream")
# ---------------------------------------------------------------------------
# 管理 API
# ---------------------------------------------------------------------------
@app.post("/api/admin/login")
def admin_login(response: Response, password: str = Form(...)):
if not secrets.compare_digest(password, ADMIN_PASSWORD):
raise HTTPException(401, "密码错误")
token = new_session_token()
response.set_cookie("reslib_admin", token, httponly=True, samesite="lax", max_age=86400 * 7)
return {"ok": True}
@app.post("/api/admin/logout")
def admin_logout(request: Request, response: Response):
response.delete_cookie("reslib_admin")
return {"ok": True}
@app.get("/api/admin/list")
def admin_list(request: Request):
require_admin(request)
items = []
for path in sorted(REQ_DIR.glob("*.txt"), reverse=True):
items.append(read_request(path.stem))
return items
@app.post("/api/admin/approve/{req_id}")
def admin_approve(req_id: str, request: Request):
require_admin(request)
data = read_request(req_id)
if data.get("status") == STATUS_APPROVED:
return {"ok": True, "id": req_id}
save_lic(req_id, data["mac"])
data["status"] = STATUS_APPROVED
data["reason"] = ""
write_request(data)
return {"ok": True, "id": req_id}
@app.post("/api/admin/deny/{req_id}")
def admin_deny(req_id: str, request: Request, reason: str = Form("")):
require_admin(request)
data = read_request(req_id)
data["status"] = STATUS_DENIED
data["reason"] = reason.strip()[:200]
write_request(data)
return {"ok": True, "id": req_id}
# ---------------------------------------------------------------------------
# 页面与静态资源
# ---------------------------------------------------------------------------
@app.get("/")
def index():
return FileResponse(STATIC_DIR / "home.html")
@app.get("/about/")
def about_page():
return FileResponse(STATIC_DIR / "about.html")
@app.get("/reslib/")
def reslib_home():
return FileResponse(STATIC_DIR / "index.html")
@app.get("/reslib/apply")
def reslib_apply():
return FileResponse(STATIC_DIR / "apply.html")
@app.get("/reslib/status")
def reslib_status():
return FileResponse(STATIC_DIR / "status.html")
@app.get("/reslib/package")
def reslib_package():
return FileResponse(STATIC_DIR / "package.html")
@app.get("/admin")
def admin_page(request: Request):
if not is_admin(request):
return RedirectResponse("/admin/login", status_code=302)
return FileResponse(STATIC_DIR / "admin.html")
@app.get("/admin/login")
def admin_login_page():
return FileResponse(STATIC_DIR / "admin_login.html")
@app.get("/admin/logout")
def admin_logout_page(request: Request):
return RedirectResponse("/admin/login", status_code=302)
app.mount("/static", StaticFiles(directory=STATIC_DIR), name="static")
app.mount("/blog", StaticFiles(directory=STATIC_DIR / "blog", html=True, check_dir=False), name="blog")
+3
View File
@@ -0,0 +1,3 @@
fastapi>=0.110
uvicorn>=0.29
python-multipart>=0.0.9
+8
View File
@@ -0,0 +1,8 @@
#!/bin/bash
# ResLib License 系统启动脚本(服务器 / 1Panel Python 应用)
# 用法:
# 1. 上传 website/ 目录到服务器,如 /opt/website
# 2. 设置管理密码:export RESLIB_ADMIN_PASSWORD='你的强密码'
# 3. 执行本脚本(或配置成 systemd / 1Panel Python 应用)
cd "$(dirname "$0")"
exec uvicorn main:app --host 0.0.0.0 --port 8003
+69
View File
@@ -0,0 +1,69 @@
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>江北的渔夫的空间</title>
<meta name="description" content="江北的渔夫的空间 —— 工业软件、标准件库与数字化实践分享">
<meta name="keywords" content="江北的渔夫的空间, 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="Easy Data Collector">
<span class="brand-name">Easy Data Collector</span>
</a>
<nav class="main-nav" id="mainNav">
<a href="/">博客</a>
<a href="/reslib/">ResLib</a>
<a href="/reslib/apply">申请 License</a>
<a href="/reslib/status">查询 / 下载</a>
<a href="/reslib/package">资源包获取</a>
<a href="https://ec.sincos.cc">Engineer Craft</a>
<a href="https://dt.sincos.cc">Digital Twin</a>
<a href="https://neural.sincos.cc">Neural Research</a>
<a href="https://git.sincos.cc">Gitea Project</a>
</nav>
<button class="nav-toggle" id="navToggle" aria-label="菜单">☰</button>
</div>
</header>
<main>
<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>
</main>
<footer class="site-footer">
<div class="container footer-inner">
<span>Copyright &copy; 2026 Easy Data Collector. 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>
+233
View File
@@ -0,0 +1,233 @@
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>管理后台 - ResLib License</title>
<link rel="stylesheet" href="/static/css/style.css">
<link rel="icon" href="/static/img/logo.svg" type="image/svg+xml">
<style>
.filters { display: flex; gap: 8px; flex-wrap: wrap; margin: 18px 0; }
.filter-btn {
padding: 6px 16px; border-radius: 20px; border: 1px solid var(--line);
background: #fff; color: var(--ink-light); font-size: 0.85rem; cursor: pointer; font-family: inherit;
}
.filter-btn.active { background: var(--blue); color: #fff; border-color: var(--blue); }
.empty { text-align: center; color: var(--ink-light); padding: 40px 0; }
.modal-mask {
position: fixed; inset: 0; background: rgba(15, 30, 50, 0.5);
display: none; align-items: center; justify-content: center; z-index: 200; padding: 20px;
}
.modal-mask.show { display: flex; }
.modal {
background: #fff; border-radius: 10px; width: 100%; max-width: 560px;
max-height: 90vh; overflow: auto; padding: 26px;
}
.modal h3 { color: var(--blue-dark); margin-bottom: 14px; }
.kv { display: grid; grid-template-columns: 110px 1fr; gap: 8px 12px; font-size: 0.92rem; margin-bottom: 16px; }
.kv dt { color: var(--ink-light); }
.kv dd { word-break: break-all; }
.lic-preview {
background: #0d2b4a; color: #7cc0ff; font-family: Consolas, monospace;
border-radius: 6px; padding: 12px 14px; font-size: 0.85rem; margin: 12px 0; letter-spacing: 0.05em;
}
.modal-actions { display: flex; gap: 10px; justify-content: flex-end; margin-top: 18px; flex-wrap: wrap; }
.deny-reason { width: 100%; margin-top: 10px; display: none; }
.deny-reason.show { display: block; }
</style>
</head>
<body>
<header class="site-header">
<div class="container header-inner">
<a class="brand" href="/admin">
<img src="/static/img/logo.svg" alt="ResLib">
<span class="brand-name">ResLib License 管理后台</span>
</a>
<div>
<a href="/reslib/" class="btn btn-ghost btn-sm" style="margin-right:8px;">前台</a>
<a href="/admin/logout" class="btn btn-danger btn-sm">退出</a>
</div>
</div>
</header>
<main class="section" style="padding-top: 30px;">
<div class="container">
<div class="filters" id="filters">
<button class="filter-btn active" data-f="all">全部</button>
<button class="filter-btn" data-f="pending">待审核</button>
<button class="filter-btn" data-f="approved">已批准</button>
<button class="filter-btn" data-f="denied">已拒绝</button>
<button id="refresh" class="filter-btn" style="margin-left:auto; border-color: var(--blue); color: var(--blue);">刷新</button>
</div>
<div class="table-wrap">
<table class="admin" id="table">
<thead>
<tr>
<th>申请编号</th><th>提交时间</th><th>公司</th><th>联系人</th><th>邮箱</th>
<th>MAC</th><th>状态</th><th>操作</th>
</tr>
</thead>
<tbody id="tbody"></tbody>
</table>
</div>
<div class="empty" id="empty" style="display:none;">暂无申请记录</div>
</div>
</main>
<div class="modal-mask" id="mask">
<div class="modal">
<h3 id="m-title">申请详情</h3>
<dl class="kv" id="m-body"></dl>
<div id="m-lic"></div>
<div class="deny-reason" id="deny-box">
<label style="font-size:0.85rem; font-weight:600;">拒绝原因(可选):</label>
<input type="text" id="deny-reason-input" style="width:100%; margin-top:6px; padding:9px 12px; border:1px solid #cfdcea; border-radius:6px;">
</div>
<div class="modal-actions">
<button class="btn btn-ghost btn-sm" id="m-close">关闭</button>
<button class="btn btn-danger btn-sm" id="m-deny" style="display:none;">拒绝</button>
<button class="btn btn-primary btn-sm" id="m-approve" style="display:none;">批准并生成 License</button>
</div>
</div>
</div>
<script>
(function () {
var tbody = document.getElementById("tbody");
var empty = document.getElementById("empty");
var filter = "all";
var items = [];
var current = null;
function badge(s) {
if (s === "approved") return '<span class="badge badge-approved">已批准</span>';
if (s === "denied") return '<span class="badge badge-denied">已拒绝</span>';
return '<span class="badge badge-pending">待审核</span>';
}
async function load() {
try {
var resp = await fetch("/api/admin/list");
if (resp.status === 401) { window.location.href = "/admin/login"; return; }
items = await resp.json();
render();
} catch (e) {
alert("加载失败:" + e.message);
}
}
function render() {
var list = filter === "all" ? items : items.filter(function (i) { return i.status === filter; });
tbody.innerHTML = "";
empty.style.display = list.length ? "none" : "block";
list.forEach(function (i) {
var tr = document.createElement("tr");
tr.innerHTML =
'<td class="mono">' + i.id + "</td>" +
"<td>" + i.created + "</td>" +
"<td>" + esc(i.company) + "</td>" +
"<td>" + esc(i.contact) + "</td>" +
"<td>" + esc(i.email) + "</td>" +
'<td class="mono">' + esc(i.mac) + "</td>" +
"<td>" + badge(i.status) + "</td>" +
'<td class="actions"><button class="btn btn-ghost btn-sm" data-view="' + i.id + '">详情</button></td>';
tbody.appendChild(tr);
});
}
function esc(s) {
return String(s == null ? "" : s).replace(/[&<>"']/g, function (c) {
return { "&": "&amp;", "<": "&lt;", ">": "&gt;", '"': "&quot;", "'": "&#39;" }[c];
});
}
function showDetail(id) {
current = items.find(function (i) { return i.id === id; });
if (!current) return;
document.getElementById("m-body").innerHTML =
"<dt>申请编号</dt><dd class='mono'>" + current.id + "</dd>" +
"<dt>提交时间</dt><dd>" + current.created + "</dd>" +
"<dt>状态</dt><dd>" + badge(current.status) + "</dd>" +
"<dt>MAC 地址</dt><dd class='mono'>" + esc(current.mac) + "</dd>" +
"<dt>公司</dt><dd>" + esc(current.company) + "</dd>" +
"<dt>联系人</dt><dd>" + esc(current.contact) + "</dd>" +
"<dt>邮箱</dt><dd>" + esc(current.email) + "</dd>" +
"<dt>Creo 版本</dt><dd>" + esc(current.creo) + "</dd>" +
"<dt>授权套数</dt><dd>" + esc(current.seats) + "</dd>" +
"<dt>备注</dt><dd>" + esc(current.notes) || "(无)" + "</dd>" +
(current.reason ? "<dt>拒绝原因</dt><dd>" + esc(current.reason) + "</dd>" : "");
var licBox = document.getElementById("m-lic");
licBox.innerHTML = "";
if (current.status === "approved") {
licBox.innerHTML =
"<b style='font-size:0.88rem;'>已生成授权文件:</b>" +
"<div class='lic-preview' id='lic-content'></div>" +
"<a class='btn btn-primary btn-sm' href='/api/license/" + encodeURIComponent(current.id) + "' download>下载 res_center.lic</a>";
fetch("/api/license/" + encodeURIComponent(current.id)).then(function (r) { return r.text(); }).then(function (t) {
var el = document.getElementById("lic-content");
if (el) el.textContent = t.trim();
}).catch(function () {});
}
document.getElementById("m-approve").style.display =
current.status === "pending" ? "" : "none";
document.getElementById("m-deny").style.display =
current.status === "pending" ? "" : "none";
document.getElementById("deny-box").classList.remove("show");
document.getElementById("deny-reason-input").value = "";
document.getElementById("mask").classList.add("show");
}
document.getElementById("filters").addEventListener("click", function (e) {
var b = e.target.closest(".filter-btn");
if (!b || b.id === "refresh") return;
document.querySelectorAll(".filter-btn").forEach(function (x) { x.classList.remove("active"); });
b.classList.add("active");
filter = b.dataset.f;
render();
});
document.getElementById("refresh").addEventListener("click", load);
tbody.addEventListener("click", function (e) {
var b = e.target.closest("[data-view]");
if (b) showDetail(b.dataset.view);
});
document.getElementById("m-close").addEventListener("click", function () {
document.getElementById("mask").classList.remove("show");
});
document.getElementById("mask").addEventListener("click", function (e) {
if (e.target === this) this.classList.remove("show");
});
document.getElementById("m-approve").addEventListener("click", async function () {
if (!confirm("确认批准该申请并生成 License?")) return;
var resp = await fetch("/api/admin/approve/" + encodeURIComponent(current.id), { method: "POST" });
if (resp.ok) { closeAndReload(); } else { alert("操作失败"); }
});
document.getElementById("m-deny").addEventListener("click", async function () {
if (!confirm("确认拒绝该申请?")) return;
var reason = document.getElementById("deny-reason-input").value;
var resp = await fetch("/api/admin/deny/" + encodeURIComponent(current.id), {
method: "POST",
headers: { "Content-Type": "application/x-www-form-urlencoded" },
body: "reason=" + encodeURIComponent(reason)
});
if (resp.ok) { closeAndReload(); } else { alert("操作失败"); }
});
function closeAndReload() {
document.getElementById("mask").classList.remove("show");
load();
}
load();
})();
</script>
</body>
</html>
+65
View File
@@ -0,0 +1,65 @@
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>管理登录 - ResLib License</title>
<link rel="stylesheet" href="/static/css/style.css">
<link rel="icon" href="/static/img/logo.svg" type="image/svg+xml">
</head>
<body style="background: #f4f8fd;">
<main style="display:flex; align-items:center; justify-content:center; min-height:100vh;">
<div style="width:100%; max-width:360px; padding:0 20px;">
<div style="text-align:center; margin-bottom:22px;">
<img src="/static/img/logo.svg" alt="ResLib" style="width:48px; height:48px;">
<h1 style="font-size:1.25rem; color:var(--blue-dark); margin-top:10px;">ResLib License 管理后台</h1>
</div>
<div class="form-card" style="margin-top:0;">
<div class="form-group">
<label for="password">管理密码</label>
<input type="password" id="password" autocomplete="current-password">
</div>
<button type="button" id="login" class="btn btn-primary btn-block">登 录</button>
<div id="result"></div>
</div>
</div>
</main>
<script>
(function () {
var btn = document.getElementById("login");
var result = document.getElementById("result");
btn.addEventListener("click", async function () {
var password = document.getElementById("password").value;
btn.disabled = true;
btn.textContent = "登录中…";
try {
var resp = await fetch("/api/admin/login", {
method: "POST",
headers: { "Content-Type": "application/x-www-form-urlencoded" },
body: "password=" + encodeURIComponent(password)
});
var data = await resp.json();
if (resp.ok) {
window.location.href = "/admin";
} else {
result.className = "alert show alert-err";
result.textContent = data.detail || "登录失败";
}
} catch (e) {
result.className = "alert show alert-err";
result.textContent = "网络错误:" + e.message;
}
btn.disabled = false;
btn.textContent = "登 录";
});
document.getElementById("password").addEventListener("keydown", function (e) {
if (e.key === "Enter") btn.click();
});
})();
</script>
</body>
</html>
+140
View File
@@ -0,0 +1,140 @@
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>申请 License - 江北的渔夫的空间</title>
<meta name="description" content="ResLib 资源中心管理器 License 申请:填写 MAC 地址、公司与联系方式,审核通过后下载 res_center.lic 授权文件。">
<meta name="keywords" content="江北的渔夫的空间, ResLib, License, MAC 地址, 授权">
<link rel="stylesheet" href="/static/css/style.css">
<link rel="icon" href="/static/img/logo.svg" type="image/svg+xml">
<script src="/static/js/nav.js"></script>
</head>
<body>
<header class="site-header">
<div class="container header-inner">
<a class="brand" href="/reslib/">
<img src="/static/img/logo.svg" alt="Easy Data Collector">
<span class="brand-name">Easy Data Collector</span>
</a>
<nav class="main-nav">
<a href="/blog/">博客</a>
<a href="/reslib/">ResLib</a>
<a href="/reslib/apply" class="active">申请 License</a>
<a href="/reslib/status">查询 / 下载</a>
<a href="/reslib/package">资源包获取</a>
<a href="https://ec.sincos.cc">Engineer Craft</a>
<a href="https://dt.sincos.cc">Digital Twin</a>
<a href="https://neural.sincos.cc">Neural Research</a>
<a href="https://git.sincos.cc">Gitea Project</a>
</nav>
<button class="nav-toggle" id="navToggle" aria-label="菜单">☰</button>
<button class="nav-toggle" id="navToggle" aria-label="菜单">☰</button>
</div>
</header>
<main class="section">
<div class="container narrow">
<h2 class="section-title">申请 License</h2>
<p class="section-desc">提交后我们会进行人工审核,审核通过后凭申请编号下载授权文件。</p>
<div class="form-card">
<div class="form-group">
<label for="mac">本机 MAC 地址 *</label>
<input type="text" id="mac" class="mono" placeholder="如:AA-BB-CC-DD-EE-FF" maxlength="17" autocomplete="off">
<p class="form-hint">如何获取:cmd 运行 <span class="mono">getmac /v</span>,取"物理地址"列(12 位十六进制)。</p>
</div>
<div class="form-row">
<div class="form-group">
<label for="company">公司 / 单位名称 *</label>
<input type="text" id="company" placeholder="如:某某机械制造有限公司">
</div>
<div class="form-group">
<label for="contact">联系人 *</label>
<input type="text" id="contact" placeholder="您的姓名">
</div>
</div>
<div class="form-row">
<div class="form-group">
<label for="email">邮箱 *</label>
<input type="email" id="email" placeholder="用于接收 License 相关信息">
</div>
<div class="form-group">
<label for="creo">Creo 版本</label>
<select id="creo">
<option value="Creo 7.0">Creo 7.0</option>
<option value="Creo 8.0" selected>Creo 8.0</option>
<option value="Creo 9.0">Creo 9.0</option>
<option value="Creo 10.0">Creo 10.0</option>
<option value="其他">其他</option>
</select>
</div>
<div class="form-group">
<label for="seats">授权数量(套)</label>
<input type="number" id="seats" min="1" value="1">
</div>
</div>
<div class="form-group">
<label for="notes">备注</label>
<textarea id="notes" rows="3" placeholder="选填:使用场景、部门规模等"></textarea>
</div>
<button type="button" id="submit" class="btn btn-primary btn-block">提交申请</button>
<div id="result"></div>
</div>
</div>
</main>
<footer class="site-footer">
<div class="container footer-inner">
<span>Copyright &copy; 2026 SinCos. All rights reserved.</span>
<span><a href="https://beian.miit.gov.cn/" target="_blank" rel="noopener">苏ICP备2026056863号</a></span>
</div>
</footer>
<script>
(function () {
var btn = document.getElementById("submit");
var result = document.getElementById("result");
function show(ok, html) {
result.className = "alert show " + (ok ? "alert-ok" : "alert-err");
result.innerHTML = html;
}
btn.addEventListener("click", async function () {
var payload = {
mac: document.getElementById("mac").value,
company: document.getElementById("company").value,
contact: document.getElementById("contact").value,
email: document.getElementById("email").value,
creo: document.getElementById("creo").value,
seats: document.getElementById("seats").value,
notes: document.getElementById("notes").value
};
btn.disabled = true;
btn.textContent = "提交中…";
try {
var resp = await fetch("/api/apply", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify(payload)
});
var data = await resp.json();
if (resp.ok) {
show(true,
"申请已提交成功!<br><b>申请编号:<span class='mono'>" + data.id + "</span></b><br>" +
"请妥善保存编号,审核通过后凭编号到 <a href='/reslib/status'>查询 / 下载</a> 页面获取授权文件。");
} else {
show(false, "提交失败:" + (data.detail || "未知错误"));
}
} catch (e) {
show(false, "网络错误:" + e.message);
}
btn.disabled = false;
btn.textContent = "提交申请";
});
})();
</script>
</body>
</html>
@@ -0,0 +1,65 @@
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>博客开张 - 江北的渔夫的空间</title>
<meta name="description" content="江北的渔夫的空间 —— 工业软件、标准件库与数字化实践分享">
<meta name="keywords" content="江北的渔夫的空间, 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="Easy Data Collector">
<span class="brand-name">Easy Data Collector</span>
</a>
<nav class="main-nav" id="mainNav">
<a href="/">博客</a>
<a href="/reslib/">ResLib</a>
<a href="/reslib/apply">申请 License</a>
<a href="/reslib/status">查询 / 下载</a>
<a href="/reslib/package">资源包获取</a>
<a href="https://ec.sincos.cc">Engineer Craft</a>
<a href="https://dt.sincos.cc">Digital Twin</a>
<a href="https://neural.sincos.cc">Neural Research</a>
<a href="https://git.sincos.cc">Gitea Project</a>
</nav>
<button class="nav-toggle" id="navToggle" aria-label="菜单">☰</button>
</div>
</header>
<main>
<section class="section">
<div class="container narrow">
<nav class="breadcrumb"><a href="/">博客</a> / <a href="/blog/">文章列表</a></nav>
<h2 class="section-title">博客开张</h2>
<div class="post-meta"><span class="post-date">2026年08月13日</span><span class="tag">公告</span><span class="tag">ResLib</span><span class="tag">数字孪生</span></div>
<article class="article">
<h1 id="_1">博客开张</h1>
<p>用统一风格搭建的聚合博客正式上线,部署在 sincos.cc。</p>
<p>本站主要记录:</p>
<ul>
<li><strong>工业软件</strong>:Creo 8.0 二次开发、企业级标准零部件库 ResLib 的设计与 License 体系</li>
<li><strong>数字化孪生</strong>:three.js 可视化、MQTT 数据链路(EMQX)、数字孪生平台落地</li>
<li><strong>工程与运维</strong>:Python、1Panel、OpenResty 反代与多站点部署实践</li>
</ul>
<p>后续内容陆续更新。</p>
</article>
</div>
</section>
</main>
<footer class="site-footer">
<div class="container footer-inner">
<span>Copyright &copy; 2026 Easy Data Collector. 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>
+61
View File
@@ -0,0 +1,61 @@
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>江北的渔夫的空间</title>
<meta name="description" content="江北的渔夫的空间 —— 工业软件、标准件库与数字化实践分享">
<meta name="keywords" content="江北的渔夫的空间, 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="Easy Data Collector">
<span class="brand-name">Easy Data Collector</span>
</a>
<nav class="main-nav" id="mainNav">
<a href="/">博客</a>
<a href="/reslib/">ResLib</a>
<a href="/reslib/apply">申请 License</a>
<a href="/reslib/status">查询 / 下载</a>
<a href="/reslib/package">资源包获取</a>
<a href="https://ec.sincos.cc">Engineer Craft</a>
<a href="https://dt.sincos.cc">Digital Twin</a>
<a href="https://neural.sincos.cc">Neural Research</a>
<a href="https://git.sincos.cc">Gitea Project</a>
</nav>
<button class="nav-toggle" id="navToggle" aria-label="菜单">☰</button>
</div>
</header>
<main>
<section class="section">
<div class="container">
<h2 class="section-title">博客</h2>
<p class="section-desc">工业软件、标准件库与数字化实践记录。</p>
<div class="post-list">
<a class="post-card" href="/blog/2026-08-13-welcome/">
<div class="post-meta"><span class="post-date">2026年08月13日</span><span class="tag">公告</span><span class="tag">ResLib</span><span class="tag">数字孪生</span></div>
<h3 class="post-title">博客开张</h3>
<p class="post-excerpt">博客开张 用统一风格搭建的聚合博客正式上线,部署在 sincos.cc。 本站主要记录: 工业软件:Creo 8.0 二次开发、企业级标准零部件库 ResLib 的设计与 License 体系 数字化孪生:three.js 可视化、MQTT…</p>
</a>
</div>
</div>
</section>
</main>
<footer class="site-footer">
<div class="container footer-inner">
<span>Copyright &copy; 2026 Easy Data Collector. 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>
+266
View File
@@ -0,0 +1,266 @@
:root {
--blue: #005eb8;
--blue-dark: #00427f;
--blue-light: #0a7bd8;
--ink: #1c2b3a;
--ink-light: #3d5a75;
--bg-soft: #f4f8fd;
--line: #e3ecf5;
--danger: #c0392b;
--ok: #1e8449;
--radius: 10px;
}
* { margin: 0; padding: 0; box-sizing: border-box; }
body {
font-family: "Segoe UI", "Microsoft YaHei", "PingFang SC", sans-serif;
color: var(--ink);
background: #fff;
line-height: 1.6;
}
.container { width: 100%; max-width: 1320px; margin: 0 auto; padding: 0 32px; }
.container.narrow { max-width: 720px; margin: 0 auto; }
a { color: var(--blue); text-decoration: none; }
a:hover { color: var(--blue-dark); }
/* Header */
.site-header {
position: sticky;
top: 0;
background: rgba(255, 255, 255, 0.97);
border-bottom: 1px solid var(--line);
z-index: 100;
}
.header-inner {
display: flex; align-items: center; justify-content: space-between;
min-height: 60px; padding: 10px 0; gap: 12px;
position: relative;
}
.brand { display: flex; align-items: center; gap: 10px; font-weight: 700; color: var(--ink); flex-shrink: 0; }
.brand img { width: 26px; height: 26px; }
.brand-name { color: var(--blue); font-size: 1.1rem; white-space: nowrap; }
.nav-toggle {
display: none; margin-left: auto;
background: none; border: 1px solid var(--line); border-radius: 6px;
font-size: 1.3rem; line-height: 1; color: var(--blue);
padding: 6px 12px; cursor: pointer;
}
.main-nav { display: flex; gap: 20px; align-items: center; flex-wrap: nowrap; }
.main-nav a { color: var(--ink-light); font-size: 0.93rem; font-weight: 500; white-space: nowrap; }
.main-nav a:hover, .main-nav a.active { color: var(--blue); }
@media (max-width: 1280px) {
.main-nav { gap: 14px; }
.main-nav a { font-size: 0.88rem; }
}
@media (max-width: 1080px) {
.nav-toggle { display: block; }
.main-nav {
display: none; position: absolute; top: 100%; left: 0; right: 0;
background: #fff; border-bottom: 1px solid var(--line);
flex-direction: column; align-items: stretch; gap: 0;
padding: 8px 0; box-shadow: 0 12px 24px rgba(0, 30, 60, 0.10);
}
.main-nav.open { display: flex; }
.main-nav a { padding: 12px 32px; font-size: 0.95rem; }
.main-nav a:hover { background: var(--bg-soft); }
}
/* Buttons */
.btn {
display: inline-block; padding: 11px 24px; border-radius: 6px;
font-weight: 600; font-size: 0.95rem; border: 1px solid transparent;
cursor: pointer; font-family: inherit; text-align: center;
transition: background 0.15s ease, color 0.15s ease;
}
.btn-primary { background: var(--blue); color: #fff; }
.btn-primary:hover { background: var(--blue-dark); color: #fff; }
.btn-ghost { background: transparent; color: var(--blue); border-color: var(--blue); }
.btn-ghost:hover { background: #e8f2ff; }
.btn-danger { background: #fff; color: var(--danger); border-color: var(--danger); }
.btn-danger:hover { background: #fdf0ee; }
.btn-block { width: 100%; }
.btn-sm { padding: 7px 14px; font-size: 0.85rem; }
.btn[disabled] { opacity: 0.5; cursor: not-allowed; }
/* Hero */
.hero {
background: linear-gradient(135deg, #003766 0%, #005eb8 55%, #0a7bd8 100%);
color: #fff; padding: 76px 0 84px; position: relative; overflow: hidden;
}
.hero { max-width: none; }
.hero::before, .hero::after {
content: ""; position: absolute; border-radius: 50%;
background: radial-gradient(circle, rgba(255,255,255,0.12) 0%, transparent 70%);
}
.hero::before { width: 460px; height: 460px; right: -130px; top: -150px; }
.hero::after { width: 320px; height: 320px; left: -100px; bottom: -140px; }
.hero .container { position: relative; }
.hero h1 { font-size: 2.4rem; font-weight: 700; line-height: 1.2; }
.hero-eyebrow {
font-size: 0.9rem; letter-spacing: 0.18em;
color: #bcd9f5; margin-bottom: 18px;
}
.hero-sub { max-width: 640px; margin: 16px 0 28px; color: #e8f2ff; font-size: 1.05rem; }
.hero-actions { display: flex; gap: 12px; flex-wrap: wrap; }
/* 欧拉恒等式装饰 */
.hero-euler {
position: absolute; right: 18%; top: 50%;
transform: translateY(-50%);
font-family: Georgia, "Times New Roman", serif;
font-style: italic;
font-size: clamp(1.6rem, 4.5vw, 4rem);
color: rgba(255, 255, 255, 0.14);
pointer-events: none; user-select: none;
white-space: nowrap; letter-spacing: 0.02em;
}
.hero-euler sup { font-size: 0.6em; }
.btn-light { background: #fff; color: var(--blue); }
.btn-light:hover { background: #e8f2ff; color: var(--blue-dark); }
.btn-outline-light { background: transparent; color: #fff; border-color: rgba(255,255,255,0.7); }
.btn-outline-light:hover { background: rgba(255,255,255,0.12); }
/* Sections & cards */
.section { padding: 64px 0; }
.section-alt { background: var(--bg-soft); }
.section-title {
font-size: 1.55rem; color: var(--blue-dark);
border-left: 4px solid var(--blue); padding-left: 14px; margin-bottom: 10px;
}
.section-desc { color: var(--ink-light); margin-bottom: 26px; }
.grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(280px, 1fr)); gap: 20px; margin-top: 26px; }
.card {
background: #fff; border: 1px solid var(--line); border-radius: var(--radius);
padding: 26px 24px; transition: transform 0.15s ease, box-shadow 0.15s ease;
}
.card:hover { transform: translateY(-3px); box-shadow: 0 10px 28px rgba(0,94,184,0.10); }
.card .icon { font-size: 1.7rem; color: var(--blue); margin-bottom: 12px; }
.card h3 { font-size: 1.05rem; color: var(--blue-dark); margin-bottom: 8px; }
.card p { font-size: 0.93rem; color: var(--ink-light); }
/* Forms */
.form-card {
background: #fff; border: 1px solid var(--line); border-radius: var(--radius);
padding: 30px 28px; margin-top: 26px;
}
.form-row { display: grid; grid-template-columns: 1fr 1fr; gap: 16px; }
.form-group { margin-bottom: 16px; }
.form-group label { display: block; font-size: 0.88rem; font-weight: 600; margin-bottom: 6px; }
.form-group input, .form-group select, .form-group textarea {
width: 100%; padding: 10px 13px; border: 1px solid #cfdcea; border-radius: 6px;
font-size: 0.93rem; font-family: inherit; background: #fbfdff; color: var(--ink);
}
.form-group input:focus, .form-group select:focus, .form-group textarea:focus {
outline: none; border-color: var(--blue); box-shadow: 0 0 0 3px rgba(0,94,184,0.12);
}
.form-hint { font-size: 0.82rem; color: var(--ink-light); margin-top: 4px; }
.form-tip { margin-top: 14px; font-size: 0.88rem; text-align: center; }
/* Result / alerts */
.alert { padding: 12px 16px; border-radius: 6px; font-size: 0.92rem; margin-top: 14px; display: none; }
.alert.show { display: block; }
.alert-ok { background: #e9f7ef; color: var(--ok); border: 1px solid #b8e6c8; }
.alert-err { background: #fdf0ee; color: var(--danger); border: 1px solid #f2c4be; }
.badge { display: inline-block; padding: 2px 12px; border-radius: 20px; font-size: 0.8rem; font-weight: 600; }
.badge-pending { background: #fef3dc; color: #9a6700; }
.badge-approved { background: #e9f7ef; color: var(--ok); }
.badge-denied { background: #fdf0ee; color: var(--danger); }
/* Admin table */
.toolbar { display: flex; justify-content: space-between; align-items: center; margin: 24px 0 14px; flex-wrap: wrap; gap: 10px; }
.table-wrap { overflow-x: auto; }
table.admin { width: 100%; border-collapse: collapse; background: #fff; border: 1px solid var(--line); border-radius: var(--radius); overflow: hidden; min-width: 720px; }
table.admin th, table.admin td { padding: 11px 14px; text-align: left; font-size: 0.9rem; border-bottom: 1px solid var(--line); }
table.admin th { background: var(--bg-soft); color: var(--blue-dark); font-weight: 600; white-space: nowrap; }
table.admin tr:hover td { background: #f7fbff; }
table.admin .actions { white-space: nowrap; }
/* Footer */
.site-footer { background: var(--blue-dark); color: #d6e6f7; padding: 36px 0 24px; }
.footer-inner { display: flex; justify-content: space-between; align-items: center; flex-wrap: wrap; gap: 12px; font-size: 0.86rem; }
.site-footer a { color: #a9c6e3; }
.site-footer a:hover { color: #fff; }
.mono { font-family: Consolas, monospace; letter-spacing: 0.04em; }
/* Blog */
.breadcrumb { font-size: 0.85rem; color: var(--ink-light); margin-bottom: 14px; }
.breadcrumb a { color: var(--ink-light); }
.breadcrumb a:hover { color: var(--blue); }
.post-list { display: grid; grid-template-columns: repeat(auto-fill, minmax(320px, 1fr)); gap: 18px; margin-top: 26px; }
.post-card {
display: block; background: #fff; border: 1px solid var(--line); border-radius: var(--radius);
padding: 22px 24px; transition: transform 0.15s ease, box-shadow 0.15s ease; color: var(--ink);
}
.post-card:hover { transform: translateY(-3px); box-shadow: 0 10px 28px rgba(0, 94, 184, 0.10); color: var(--ink); }
.post-meta { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; margin-bottom: 8px; }
.post-date { font-size: 0.8rem; color: var(--ink-light); }
.post-title { font-size: 1.08rem; color: var(--blue-dark); margin-bottom: 6px; }
.post-excerpt { font-size: 0.9rem; color: var(--ink-light); line-height: 1.55; }
.tag {
display: inline-block; font-size: 0.74rem; color: var(--blue);
background: #e8f2ff; border-radius: 20px; padding: 1px 10px;
}
/* Article */
.article { line-height: 1.8; font-size: 0.98rem; color: var(--ink); margin-top: 20px; }
.article h1, .article h2, .article h3, .article h4 {
color: var(--blue-dark); margin: 1.6em 0 0.6em; line-height: 1.4;
}
.article h1 { font-size: 1.6rem; border-bottom: 1px solid var(--line); padding-bottom: 0.3em; }
.article h2 { font-size: 1.3rem; }
.article h3 { font-size: 1.1rem; }
.article p { margin: 0.8em 0; }
.article ul, .article ol { padding-left: 1.5em; margin: 0.8em 0; }
.article li { margin: 0.3em 0; }
.article code {
background: #eef4fb; color: #00518f; border-radius: 4px;
padding: 1px 6px; font-family: Consolas, monospace; font-size: 0.9em;
}
.article pre {
background: #0d2b4a; color: #d8ecff; border-radius: 8px;
padding: 16px 18px; overflow-x: auto; margin: 1em 0;
}
.article pre code { background: none; color: inherit; padding: 0; }
.article blockquote {
border-left: 4px solid var(--blue); background: var(--bg-soft);
padding: 10px 16px; margin: 1em 0; color: var(--ink-light); border-radius: 0 6px 6px 0;
}
.article table { width: 100%; border-collapse: collapse; margin: 1em 0; font-size: 0.92rem; }
.article th, .article td { border: 1px solid var(--line); padding: 8px 12px; text-align: left; }
.article th { background: var(--bg-soft); color: var(--blue-dark); }
.article img { max-width: 100%; border-radius: 8px; }
.article a { text-decoration: underline; }
/* Card as link */
.card-link { display: block; color: var(--ink); }
.card-link:hover { color: var(--ink); }
@media (max-width: 720px) {
.post-list { grid-template-columns: 1fr; }
}
@media (max-width: 1024px) {
.main-nav { gap: 12px 16px; }
.main-nav a { font-size: 0.88rem; }
}
@media (max-width: 720px) {
.form-row { grid-template-columns: 1fr; }
.hero h1 { font-size: 1.8rem; }
.hero { padding: 56px 0 64px; }
.section { padding: 48px 0; }
.container { padding: 0 18px; }
.hero-actions .btn { width: 100%; }
}
+99
View File
@@ -0,0 +1,99 @@
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>江北的渔夫的空间</title>
<meta name="description" content="江北的渔夫的空间 —— 工业软件、标准件库与数字化实践分享">
<meta name="keywords" content="江北的渔夫的空间, 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="Easy Data Collector">
<span class="brand-name">Easy Data Collector</span>
</a>
<nav class="main-nav" id="mainNav">
<a href="/" class=active>博客</a>
<a href="/reslib/">ResLib</a>
<a href="/reslib/apply">申请 License</a>
<a href="/reslib/status">查询 / 下载</a>
<a href="/reslib/package">资源包获取</a>
<a href="https://ec.sincos.cc">Engineer Craft</a>
<a href="https://dt.sincos.cc">Digital Twin</a>
<a href="https://neural.sincos.cc">Neural Research</a>
<a href="https://git.sincos.cc">Gitea Project</a>
</nav>
<button class="nav-toggle" id="navToggle" aria-label="菜单">☰</button>
</div>
</header>
<main>
<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">
<a class="post-card" href="/blog/2026-08-13-welcome/">
<div class="post-meta"><span class="post-date">2026年08月13日</span></div>
<h3 class="post-title">博客开张</h3>
<p class="post-excerpt">博客开张 用统一风格搭建的聚合博客正式上线,部署在 sincos.cc。 本站主要记录: 工业软件:Creo 8.0 二次开发、企业级标准零部件库 ResLib 的设计与 Lice…</p>
</a>
</div>
<div style="text-align:center; margin-top: 30px;">
<a href="/blog/" class="btn btn-primary">查看全部文章</a>
</div>
</div>
</section>
</main>
<footer class="site-footer">
<div class="container footer-inner">
<span>Copyright &copy; 2026 Easy Data Collector. 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>
+7
View File
@@ -0,0 +1,7 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 32 32">
<rect width="32" height="32" rx="7" fill="#005EB8"/>
<rect x="6" y="7" width="8" height="8" rx="2" fill="#fff"/>
<rect x="18" y="7" width="8" height="8" rx="2" fill="#7cc0ff"/>
<rect x="6" y="17" width="8" height="8" rx="2" fill="#7cc0ff"/>
<rect x="18" y="17" width="8" height="8" rx="2" fill="#fff"/>
</svg>

After

Width:  |  Height:  |  Size: 381 B

+119
View File
@@ -0,0 +1,119 @@
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>江北的渔夫的空间 | Easy Data Collector</title>
<meta name="description" content="江北的渔夫的空间 —— 工业软件、标准件库与数字化实践分享。Easy Data Collector 旗下 ResLib 资源中心管理器:Creo 7.0+ 企业级标准零部件管理库。">
<meta name="keywords" content="江北的渔夫的空间, Easy Data Collector, ResLib, 资源中心管理器, Creo, 标准零部件库, License">
<link rel="stylesheet" href="/static/css/style.css">
<link rel="icon" href="/static/img/logo.svg" type="image/svg+xml">
<script src="/static/js/nav.js"></script>
</head>
<body>
<header class="site-header">
<div class="container header-inner">
<a class="brand" href="/reslib/">
<img src="/static/img/logo.svg" alt="Easy Data Collector">
<span class="brand-name">Easy Data Collector</span>
</a>
<nav class="main-nav">
<a href="/blog/">博客</a>
<a href="/reslib/" class="active">ResLib</a>
<a href="/reslib/apply">申请 License</a>
<a href="/reslib/status">查询 / 下载</a>
<a href="/reslib/package">资源包获取</a>
<a href="https://ec.sincos.cc">Engineer Craft</a>
<a href="https://dt.sincos.cc">Digital Twin</a>
<a href="https://neural.sincos.cc">Neural Research</a>
<a href="https://git.sincos.cc">Gitea Project</a>
</nav>
<button class="nav-toggle" id="navToggle" aria-label="菜单">☰</button>
<button class="nav-toggle" id="navToggle" aria-label="菜单">☰</button>
</div>
</header>
<main>
<section class="hero">
<div class="container">
<h1>资源中心管理器 ResLib</h1>
<p class="hero-sub">面向 Creo 7.0+ 的企业级标准零部件管理库 —— 标准件浏览、3D 预览、搜索过滤、快速装配,配套自研 LIC 授权体系。</p>
<div class="hero-actions">
<a href="/reslib/apply" class="btn btn-light">申请 License</a>
<a href="/reslib/status" class="btn btn-outline-light">查询申请状态</a>
<a href="/reslib/package" class="btn btn-outline-light">资源包获取</a>
</div>
</div>
</section>
<section class="section">
<div class="container">
<h2 class="section-title">核心功能</h2>
<div class="grid">
<div class="card"><div class="icon">▤</div><h3>标准件库浏览</h3><p>TreeView 目录树 + 零件列表,直接扫描目录中的 .prt / .asm 文件,去重保留最高版本。</p></div>
<div class="card"><div class="icon">◉</div><h3>3D 实时预览</h3><p>OpenGL 三角面片渲染,三点光源,鼠标旋转 / 缩放,装配体递归解析。</p></div>
<div class="card"><div class="icon">⌕</div><h3>全局搜索过滤</h3><p>递归全库匹配文件名,支持按名称快速定位标准件。</p></div>
<div class="card"><div class="icon">⇱</div><h3>快速装配</h3><p>选中零件一键插入装配,或进入参数定位模式(X/Y/Z/RX/RY/RZ)精确装配。</p></div>
<div class="card"><div class="icon">☆</div><h3>收藏与最近使用</h3><p>favorites / recent 记录,MRU 去重,常用件一键直达。</p></div>
<div class="card"><div class="icon">⛨</div><h3>LIC 授权体系</h3><p>MAC 地址绑定授权,未授权时装配功能隐藏,保障企业版权。</p></div>
<div class="card"><div class="icon">✎</div><h3>版本与功能定制</h3><p>支持按企业需求定制产品版本与扩展功能,贴合实际业务流程开发。</p></div>
<div class="card"><div class="icon">☁</div><h3>私有云部署</h3><p>企业私有云部署与标准化同步服务,标准件库统一管理、多端实时同步。</p></div>
<div class="card"><div class="icon">◍</div><h3>标准化降本</h3><p>减少 SKU 与同类物料,提升零部件借用率与标准化统一程度,有效降低成本。</p></div>
<div class="card"><div class="icon">◈</div><h3>Inventor 服务</h3><p>提供 Autodesk Inventor 版本类似服务功能,多 CAD 平台标准件管理方案。</p></div>
</div>
</div>
</section>
<section class="section">
<div class="container">
<h2 class="section-title">资源包内容</h2>
<p class="section-desc">资源包随 License 发放,内置常用标准件库,覆盖以下品类:</p>
<div class="grid">
<div class="card"><div class="icon">▣</div><h3>标准化配件</h3><p>通用标准化零部件库,覆盖各行业通用件,开箱即用。</p></div>
<div class="card"><div class="icon">❖</div><h3>BPE 卫生级配件</h3><p>符合 BPE 规范的卫生级管件、阀门与连接件,适用于生物制药与洁净工艺。</p></div>
<div class="card"><div class="icon">✚</div><h3>食品 · 医药 · 包装配件</h3><p>食品、医药与包装工程常用配件库,贴合产线设计选型需求。</p></div>
<div class="card"><div class="icon">⏏</div><h3>气动系统配件</h3><p>气缸、阀岛、接头等气动系统常用件,助力自动化产线快速搭建。</p></div>
</div>
<div style="text-align:center; margin-top: 28px;">
<a href="/reslib/package" class="btn btn-primary">获取资源包</a>
</div>
</div>
</section>
<section class="section section-alt">
<div class="container">
<h2 class="section-title">版本与环境</h2>
<div class="grid">
<div class="card"><h3>适配版本</h3><p>Creo Parametric 7.0 ~ 10.0(64 位),Pro/TOOLKIT DLL 插件。</p></div>
<div class="card"><h3>系统要求</h3><p>Windows 10 / 11 64 位,Visual C++ 2022 运行库。</p></div>
<div class="card"><h3>部署形态</h3><p>本地部署:DLL + protk.dat + config\lic\res_center.lic,configen.exe 一键配置。</p></div>
</div>
</div>
</section>
<section class="section">
<div class="container narrow">
<h2 class="section-title">License 申请流程</h2>
<p class="section-desc">三步完成授权:</p>
<div class="grid">
<div class="card"><h3>1. 提交申请</h3><p>填写公司、联系人与机器 MAC 地址,提交后获得申请编号。</p></div>
<div class="card"><h3>2. 人工审批</h3><p>我们核对信息后确认授权,通过后自动生成授权文件。</p></div>
<div class="card"><h3>3. 下载安装</h3><p>凭申请编号查询状态并下载 <span class="mono">res_center.lic</span>,放入 config\lic\ 目录。</p></div>
</div>
<div style="text-align:center; margin-top: 28px;">
<a href="/reslib/apply" class="btn btn-primary">开始申请</a>
</div>
</div>
</section>
</main>
<footer class="site-footer">
<div class="container footer-inner">
<span>Copyright &copy; 2026 Easy Data Collector. All rights reserved.</span>
<span><a href="https://beian.miit.gov.cn/" target="_blank" rel="noopener">苏ICP备2026056863号</a></span>
</div>
</footer>
</body>
</html>
+16
View File
@@ -0,0 +1,16 @@
(function () {
"use strict";
document.addEventListener("DOMContentLoaded", function () {
var toggle = document.getElementById("navToggle");
var nav = document.getElementById("mainNav");
if (!toggle || !nav) return;
toggle.addEventListener("click", function () {
nav.classList.toggle("open");
});
document.addEventListener("click", function (e) {
if (!nav.contains(e.target) && !toggle.contains(e.target)) {
nav.classList.remove("open");
}
});
});
})();
+97
View File
@@ -0,0 +1,97 @@
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>资源包获取 - 江北的渔夫的空间</title>
<meta name="description" content="ResLib 资源包获取:仅对 License 审核通过的用户提供,凭申请编号验证后下载资源包。">
<meta name="keywords" content="江北的渔夫的空间, ResLib, 资源包, License, 下载">
<link rel="stylesheet" href="/static/css/style.css">
<link rel="icon" href="/static/img/logo.svg" type="image/svg+xml">
<script src="/static/js/nav.js"></script>
</head>
<body>
<header class="site-header">
<div class="container header-inner">
<a class="brand" href="/reslib/">
<img src="/static/img/logo.svg" alt="Easy Data Collector">
<span class="brand-name">Easy Data Collector</span>
</a>
<nav class="main-nav">
<a href="/blog/">博客</a>
<a href="/reslib/">ResLib</a>
<a href="/reslib/apply">申请 License</a>
<a href="/reslib/status">查询 / 下载</a>
<a href="/reslib/package" class="active">资源包获取</a>
<a href="https://ec.sincos.cc">Engineer Craft</a>
<a href="https://dt.sincos.cc">Digital Twin</a>
<a href="https://neural.sincos.cc">Neural Research</a>
<a href="https://git.sincos.cc">Gitea Project</a>
</nav>
<button class="nav-toggle" id="navToggle" aria-label="菜单">☰</button>
<button class="nav-toggle" id="navToggle" aria-label="菜单">☰</button>
</div>
</header>
<main class="section">
<div class="container narrow">
<h2 class="section-title">资源包获取</h2>
<p class="section-desc">资源包(部署文件、标准件库样例、说明文档等)<b>仅对 License 审核通过的用户提供</b>,请凭申请编号验证身份。内含标准化配件、BPE 卫生级配件、食品 / 医药 / 包装工程常用配件与气动系统配件等标准件库。</p>
<div class="form-card">
<div class="form-group">
<label for="rid">申请编号</label>
<input type="text" id="rid" class="mono" placeholder="如:20260813-201500-A1B2C3" autocomplete="off">
</div>
<button type="button" id="verify" class="btn btn-primary btn-block">验证并获取</button>
<div id="result"></div>
</div>
</div>
</main>
<footer class="site-footer">
<div class="container footer-inner">
<span>Copyright &copy; 2026 Easy Data Collector. All rights reserved.</span>
<span><a href="https://beian.miit.gov.cn/" target="_blank" rel="noopener">苏ICP备2026056863号</a></span>
</div>
</footer>
<script>
(function () {
var btn = document.getElementById("verify");
var result = document.getElementById("result");
btn.addEventListener("click", async function () {
var rid = document.getElementById("rid").value.trim();
if (!rid) { result.innerHTML = '<div class="alert show alert-err">请输入申请编号</div>'; return; }
btn.disabled = true;
btn.textContent = "验证中…";
try {
var resp = await fetch("/api/package/" + encodeURIComponent(rid));
var data = await resp.json();
if (resp.ok) {
var html =
'<div class="alert show alert-ok">' +
"<b>身份验证通过</b>(申请编号 <span class='mono'>" + rid + "</span>)<br><br>" +
"<b>可用资源包:</b><br>";
data.files.forEach(function (f) {
html += "<div style='margin:10px 0;'>📦 <span class='mono'>" + f + "</span><br>" +
"<a class='btn btn-primary btn-sm' style='margin-top:6px;' href='/api/package/download/" +
encodeURIComponent(rid) + "/" + encodeURIComponent(f) + "' download>下载</a></div>";
});
html += "</div>";
result.innerHTML = html;
} else {
result.innerHTML = '<div class="alert show alert-err">' + (data.detail || "验证失败") + "</div>";
}
} catch (e) {
result.innerHTML = '<div class="alert show alert-err">网络错误:' + e.message + "</div>";
}
btn.disabled = false;
btn.textContent = "验证并获取";
});
})();
</script>
</body>
</html>
+109
View File
@@ -0,0 +1,109 @@
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>查询 / 下载 License - ResLib</title>
<link rel="stylesheet" href="/static/css/style.css">
<link rel="icon" href="/static/img/logo.svg" type="image/svg+xml">
<script src="/static/js/nav.js"></script>
</head>
<body>
<header class="site-header">
<div class="container header-inner">
<a class="brand" href="/reslib/">
<img src="/static/img/logo.svg" alt="ResLib">
<span class="brand-name">ResLib</span>
</a>
<nav class="main-nav">
<a href="/blog/">博客</a>
<a href="/reslib/">ResLib</a>
<a href="/reslib/apply">申请 License</a>
<a href="/reslib/status" class="active">查询 / 下载</a>
<a href="/reslib/package">资源包获取</a>
<a href="https://ec.sincos.cc">Engineer Craft</a>
<a href="https://dt.sincos.cc">Digital Twin</a>
<a href="https://neural.sincos.cc">Neural Research</a>
<a href="https://git.sincos.cc">Gitea Project</a>
</nav>
<button class="nav-toggle" id="navToggle" aria-label="菜单">☰</button>
<button class="nav-toggle" id="navToggle" aria-label="菜单">☰</button>
</div>
</header>
<main class="section">
<div class="container narrow">
<h2 class="section-title">查询申请状态</h2>
<p class="section-desc">输入申请时获得的申请编号。</p>
<div class="form-card">
<div class="form-group">
<label for="rid">申请编号</label>
<input type="text" id="rid" class="mono" placeholder="如:20260813-201500-A1B2C3" autocomplete="off">
</div>
<button type="button" id="query" class="btn btn-primary btn-block">查询</button>
<div id="result"></div>
</div>
</div>
</main>
<footer class="site-footer">
<div class="container footer-inner">
<span>Copyright &copy; 2026 SinCos. All rights reserved.</span>
<span><a href="https://beian.miit.gov.cn/" target="_blank" rel="noopener">苏ICP备2026056863号</a></span>
</div>
</footer>
<script>
(function () {
var btn = document.getElementById("query");
var result = document.getElementById("result");
function statusBadge(s) {
if (s === "approved") return '<span class="badge badge-approved">已批准</span>';
if (s === "denied") return '<span class="badge badge-denied">已拒绝</span>';
return '<span class="badge badge-pending">审核中</span>';
}
function show(html) {
result.innerHTML = html;
}
btn.addEventListener("click", async function () {
var rid = document.getElementById("rid").value.trim();
if (!rid) { show('<div class="alert show alert-err">请输入申请编号</div>'); return; }
btn.disabled = true;
btn.textContent = "查询中…";
try {
var resp = await fetch("/api/status/" + encodeURIComponent(rid));
var data = await resp.json();
if (resp.ok) {
var html =
'<div class="alert show alert-ok">' +
"<b>申请编号:</b><span class='mono'>" + data.id + "</span><br>" +
"<b>提交时间:</b>" + data.created + "<br>" +
"<b>当前状态:</b>" + statusBadge(data.status);
if (data.status === "approved") {
html += "<br><br><a class='btn btn-primary' href='/api/license/" + encodeURIComponent(data.id) +
"' download>下载 res_center.lic</a>";
} else if (data.status === "denied" && data.reason) {
html += "<br><b>原因:</b>" + data.reason;
} else {
html += "<br>请耐心等待审核,稍后再来查询。";
}
html += "</div>";
show(html);
} else {
show('<div class="alert show alert-err">' + (data.detail || "查询失败") + "</div>");
}
} catch (e) {
show('<div class="alert show alert-err">网络错误:' + e.message + "</div>");
}
btn.disabled = false;
btn.textContent = "查询";
});
})();
</script>
</body>
</html>
+115
View File
@@ -0,0 +1,115 @@
======================================================================
资源中心管理器 V1.2 — 开发技术文档
2026-07-29
======================================================================
一、项目概述
--------------------------------------------------
项目:资源中心管理器 (Res_center)
类型:Creo Pro/TOOLKIT DLL 插件
功能:标准件库浏览、3D 预览、搜索过滤、快速装配、LIC 授权
语言:C (核心) + C++ (preview.cpp)
编译器:MSVC 2022 | SDK:Creo 8.0 PTK | 平台:Win10/11 x64 | Creo 7.0+
二、源文件
--------------------------------------------------
src/main.c (50行) DLL入口,路径初始化
src/dialog.c (1080行) 主对话框(TreeView+Parts+Preview+FT+Search+LIC)
src/menu.c (73行) Creo命令注册(2个命令)
src/assembly.c (624行) 装配逻辑 + 参数化对话框
src/preview.cpp (223行) OpenGL 3D 预览
src/license.c (250行) MAC激活(源码保留,未编译)
include/res_center.h (84行) 公共头文件
tools/keygen_lic.c LIC授权文件生成器(Metro UI + BGM)
tools/configen.c protk.dat自动配置器(Metro UI)
tools/build_lic.bat keygen编译脚本
tools/build_config.bat configen编译脚本
三、Creo命令(2个)
--------------------------------------------------
RL_Ico2 资源中心 → 对话框 + 插入装配
RL_Ico3 资源中心(参数定位) → 对话框 + 参数窗口(X/Y/Z/RX/RY/RZ)
四、核心技术
--------------------------------------------------
【4.1】零件发现 — LoadParts()
直接扫目录 *.prt / *.prt.* / *.asm / *.asm.* → 提取基名 → 去重保留最高版本
【4.2】全局搜索 — SearchDirRecursive()
搜索框 → 递归全库匹配文件名 → 显示结果
【4.3】3D 预览 — OpenGL
ProPartTessellate → 三角面片 → glDrawElements
装配体:ProSolidFeatVisit → ProAsmcompMdlGet → ProAsmcomppathInit → ProAsmcomppathTrfGet
3点光 + 双面光,灰蓝材质,中灰背景,鼠标旋转/缩放
【4.4】对话框布局
TreeView(300固定) | 按钮+搜索+零件列表(35%) | GL预览+族表+按钮(65%)
【4.5】收藏/最近使用
config\favorites.txt, config\recent.txt,MRU去重
【4.6】LIC授权
文件:config\lic\res_center.lic
算法:MAC → 去横线 → A=01 B=02 ... Z=26 → 倒序
验证:选中零件时检查LIC → 不匹配则隐藏"插入装配",显示"LIC文件不正确"
工具:tools\keygen_lic.exe (Metro暗色UI + poemstear.mp3背景音乐)
五、已知限制
--------------------------------------------------
超大模型(>500万顶点) → 提示"模型过大,已取消预览"
旧版 .prt → 部分文件 ProPartTessellate 失败
装配体阵列实例 → Pattern 成员待完善
组件替换/删除 → PTK DLL 不支持(参见附录A)
六、已修复 Bug
--------------------------------------------------
最近使用/收藏模式崩溃 → ApplyFilter 递归断环
文件路径双 config → FAV_FILE/RECENT_FILE 修正
参数对话框无确定按钮 → 英文→中文,对话框加高
搜索框上边框缺失 → WS_EX_CLIENTEDGE 替换 WS_BORDER
Insert 按钮中文被截 → 200×50 加宽居中
七、工具说明
--------------------------------------------------
tools\keygen_lic.exe LIC授权文件生成器(Metro暗色UI + 背景音乐)
tools\configen.exe protk.dat自动配置器(一键生成)
部署时放在DLL同目录运行
======================================================================
附录A:组件替换开发记录(已暂停)
======================================================================
需求:装配环境选中组件 → 从资源中心选新零件 → 删旧换新
结论:PTK DLL 不支持装配树修改
尝试方案:
ProFeatureDelete → 链接失败(库未导出)
ProFeatureSuppress → 运行时崩溃
ProFeatureWithoptionsDelete → 运行时崩溃
ProMacroExecute(ProCmdMdlTreeDelete) → 运行时崩溃
ProSelect+先装后删 → 状态冲突
后续:研究 Creo 原生 Replace 命令名或 OTK/同步模式
======================================================================
附录B:LIC 授权规则
======================================================================
算法:
MAC "AA-BB-CC-DD-EE-FF" → 去横线 "AABBCCDDEEFF"
→ A=01 B=02 ... Z=26 → "010102020303040405050606"
→ 倒序 → "606050504040303020201010"
= LIC 文件内容
生成:keygen_lic.exe AA-BB-CC-DD-EE-FF res_center.lic
部署:res_center.lic 放入 config\lic\
+99
View File
@@ -0,0 +1,99 @@
======================================================================
资源中心管理器 V1.2 — 部署指南
Creo 7.0 / 8.0 / 9.0 / 10.0 (64位) | 2026-07-29
======================================================================
一、环境要求
--------------------------------------------------
1. Windows 10/11 64位
2. Creo Parametric 7.0+ (64位)
3. Visual C++ 2022 运行库
二、部署文件清单
--------------------------------------------------
D:\ResLib\
├── Res_center_80.dll ← 核心 DLL(必须)
├── protk.dat ← Creo 插件注册文件(必须)
├── ucore68.dll ← Creo 运行时依赖(必须)
├── udata68.dll ← Creo 运行时依赖(必须)
├── folder.ico ← 图标
├── configen.exe ← protk.dat 自动配置器
├── config\ ← 配置文件(自动生成)
│ └── lic\
│ └── res_center.lic ← 授权文件(必须,见第五节)
├── text\ ← 本地化资源(必须)
│ └── usascii\
│ └── folder.gif
└── Resource_Library\ ← ★ 标准件库(核心数据)
三、部署步骤
--------------------------------------------------
【步骤 1】复制文件
将部署文件夹复制到目标电脑(如 D:\ResLib)
路径不要有中文或空格
【步骤 2】生成 protk.dat
双击 configen.exe → 点击 "GENERATE protk.dat"
自动检测当前路径并生成正确配置
【步骤 3】注册到 Creo
在 config.pro 中添加一行:
protkdat D:\ResLib\protk.dat
config.pro 位置:
C:\Users\<用户名>\AppData\Roaming\PTC\ProENGINEER\Wildfire\.wf\.Creo\
【步骤 4】配置授权
将获得的 res_center.lic 放入 config\lic\ 目录
不可则"插入装配"按钮不显示,其他功能不受影响
【步骤 5】验证
启动 Creo → 出现"资源中心"按钮 → 打开对话框 → 选零件 → 3D 预览 → 插入装配
四、可用命令
--------------------------------------------------
资源中心 → 选零件 → "插入装配"
资源中心(参数定位) → 选零件 → 参数窗口(X/Y/Z/RX/RY/RZ) → 精确装配
五、LIC 授权文件
--------------------------------------------------
位置:config\lic\res_center.lic
生成方式:运行 tools\keygen_lic.exe,输入 MAC 地址,点击 Generate
将生成的 res_center.lic 复制到 config\lic\ 目录
授权规则:
MAC 地址 → 去除横线/空格 → A=01 B=02 ... Z=26 → 数字串倒序 → lic 内容
六、环境变量(可选)
--------------------------------------------------
RES_CENTER_DIR = D:\ResLib
未设置时自动使用 DLL 所在目录
七、常见问题
--------------------------------------------------
Q1: 启动提示"无法加载辅助应用程序"
A1: 检查 protk.dat 路径和 REVISION=36,或用 configen.exe 重新生成
Q2: 点击按钮无反应
A2: 确保 ucore68.dll、udata68.dll 与 DLL 同目录
Q3: 3D 预览空白
A3: 显卡需支持 OpenGL(Windows 自带即可)
Q4: "插入装配"按钮不显示
A4: 检查 config\lic\res_center.lic 是否存在且内容正确
Q5: 如何更新标准件库
A5: 直接在 Resource_Library 下增删 .prt/.asm 或目录,无需维护 .mnu
八、版本信息
--------------------------------------------------
版本:V1.2 | DLL:Res_center_80.dll | 兼容:Creo 7.0~10.0