first commit
This commit is contained in:
+17
@@ -0,0 +1,17 @@
|
|||||||
|
# 环境与密钥
|
||||||
|
website/.env
|
||||||
|
.env
|
||||||
|
|
||||||
|
# 运行时数据(申请记录/授权文件/资源包)
|
||||||
|
website/data/
|
||||||
|
|
||||||
|
# Python
|
||||||
|
__pycache__/
|
||||||
|
*.pyc
|
||||||
|
*.pyo
|
||||||
|
.venv/
|
||||||
|
venv/
|
||||||
|
|
||||||
|
# 系统
|
||||||
|
.DS_Store
|
||||||
|
Thumbs.db
|
||||||
@@ -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
@@ -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 反代与多站点部署实践
|
||||||
|
|
||||||
|
后续内容陆续更新。
|
||||||
@@ -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/
|
||||||
|
- [ ] 全站访问验证
|
||||||
@@ -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
@@ -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")
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
fastapi>=0.110
|
||||||
|
uvicorn>=0.29
|
||||||
|
python-multipart>=0.0.9
|
||||||
@@ -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
|
||||||
@@ -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 © 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>
|
||||||
@@ -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 { "&": "&", "<": "<", ">": ">", '"': """, "'": "'" }[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>
|
||||||
@@ -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>
|
||||||
@@ -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 © 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 © 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>
|
||||||
@@ -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 © 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>
|
||||||
@@ -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%; }
|
||||||
|
}
|
||||||
@@ -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 © 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>
|
||||||
@@ -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 |
@@ -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 © 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>
|
||||||
@@ -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");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
})();
|
||||||
@@ -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 © 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>
|
||||||
@@ -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 © 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
@@ -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\
|
||||||
@@ -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
|
||||||
Reference in New Issue
Block a user