refactor(homepage): 官网/文档/定价源码迁出程序 + 新增独立授权与激活页 /license
- 官网源码归拢到 homepage/(index/pricing/terms/license + content/ + assets/ + docs-assets/),
删除程序内 go:embed 的 server/internal/{site,docs},后端仅静态托管 homepage/dist 做本地预览
- 新增独立静态页 /license(授权与激活):交付与激活四步(邮件交付授权码)、
在线授权码 LIC- 与离线许可证 LICOFF- 对比、授权服务器 licence.rlwj.cn 通信范围说明、
授权类型与价格、10 条授权 FAQ
- tools/homepage-build 支持可选单页,产出 <name>.html 与 <name>/index.html
- 导航/页脚接入 /license,跨页锚点补 / 前缀,页脚底栏文案同步为静态站口径
This commit is contained in:
1 parent
327a265572
commit
3cb9545121
49 files changed
+1308
-600
No files matched your search
@@ -1,4 +1,4 @@
|
||||
.PHONY: install web gui all server stop status build-web test desktop
|
||||
.PHONY: install web gui all server stop status build-web test desktop homepage
|
||||
|
||||
install:
|
||||
npm install
|
||||
@@ -24,6 +24,9 @@ status:
|
||||
desktop:
|
||||
./run.sh desktop
|
||||
|
||||
homepage:
|
||||
scripts/build-homepage.sh
|
||||
|
||||
build-web:
|
||||
npm run build
|
||||
|
||||
|
||||
@@ -62,24 +62,18 @@ make install
|
||||
|
||||
打开 http://127.0.0.1:8787/ 看落地页;操作请用 `./run.sh gui`。`:5173` 只是桌面 WebView 源。
|
||||
|
||||
## 用户文档站(/docs)
|
||||
## 官网 / 文档 / 定价(homepage/,纯静态)
|
||||
|
||||
Gin 内置产品文档,Markdown 嵌入二进制、SSR 渲染,随服务一键部署:
|
||||
官网(落地页 + `/docs` + `/pricing`)已拆分为独立静态站:源在 `homepage/`,与后端/GUI 解耦;后端仅在本地静态托管产物 `homepage/dist`(nginx 直接托管同一目录)。构建入口 `scripts/build-homepage.sh`(= `make homepage`),工具在独立 Go module `tools/homepage-build/`。
|
||||
|
||||
- 地址:`http://127.0.0.1:8787/docs`(开发态 `http://127.0.0.1:5173/docs`,Vite 已反代;工作台顶栏「📖 文档」直达)
|
||||
- 布局:顶栏搜索 + 左侧分组导航(16 篇:开始使用 / 请求调试 / 数据管理 / 参考)+ 右侧本页目录
|
||||
- 能力:全文搜索、代码块复制 + 高亮、提示块、上一页/下一页、反馈组件、打印导出 PDF、移动端响应式、404 页
|
||||
- 内容在 `server/internal/docs/content/*.md`(本地预览优先托管 `homepage/dist`)
|
||||
|
||||
## 产品官网(/home)
|
||||
|
||||
Gin 内置营销单页,与 /docs 同为 SSR 嵌入交付(`internal/site`):
|
||||
|
||||
- 地址:`/` 与 `/home` 双入口(开发态 `http://127.0.0.1:5173/home`);文档站顶栏「官网」互链
|
||||
- 区块:Hero(纯 CSS 产品界面 mock)/ 卖点 / 功能 / 六协议 / 导入导出生态 / 与 Apifox·Apipost 对比表 / 定价 / 让利计划 / 购买 FAQ / 联系 CTA / Footer
|
||||
- 定价三档(标价/成交价分离):个人版 ¥99/年、团队版 ¥99/人/年(入门锚点);企业私有化版标价 **¥8,000/年 · ¥30,000 买断**,经早鸟/教育/多年预付/案例合作落在成交区间 ¥5,000–8,000/年、¥15,000–30,000
|
||||
- 让利机制四项:早鸟创始价(前 20 席 5–7 折永久)、教育/开源 6–8 折、多年预付 8–9 折、案例换授权;销售原则:不直接降价,用「永久授权 + 1 年升级 + 5 人团队 1–2 年回本」回应预算顾虑
|
||||
- 交付前替换:联系邮箱 `hello@opsxapi.dev`(template.html 内两处)与对比表脚注中的价格核对
|
||||
- 源结构:
|
||||
- `homepage/index.html` 落地页;`homepage/pricing.html` 定价页;`homepage/terms.html` 服务条款
|
||||
- `homepage/docs.html` 文档模板;`homepage/content/*.md` + `manifest.json`(20 篇,6 分组)
|
||||
- `homepage/assets/`(官网资源,产物落 `dist/site/assets/`);`homepage/docs-assets/`(文档资源,产物落 `dist/docs/assets/`)
|
||||
- 产物 `homepage/dist/`(结构固定):`index.html`、`docs/index.html`、`docs/<slug>/index.html`、`docs/search-data.json`、`pricing.html` + `pricing/index.html`、`site/assets/*`
|
||||
- 本地预览:`./run.sh web` → 落地页 `http://127.0.0.1:8787/`、文档 `http://127.0.0.1:8787/docs`、定价 `http://127.0.0.1:8787/pricing`
|
||||
- `.gitignore` 含 `dist/`,产物默认不入库;如需入库 `git add -f homepage/dist`
|
||||
- 交付前替换:联系邮箱 `hello@opsxapi.dev`(`homepage/index.html`)
|
||||
|
||||
## 服务端端点(server/)
|
||||
|
||||
|
||||
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
+1
-1
@@ -139,7 +139,7 @@
|
||||
<div class="page-meta">更新于 2026-09-17</div>
|
||||
<article class="md">
|
||||
<h1>账号与团队</h1>
|
||||
<p>登录后可使用<strong>云端空间</strong>:个人空间「我的空间」与团队共享空间,数据保存在本机服务的 SQLite 数据库(<code>apps/server/data/opsxapi.db</code>),不上传任何第三方。</p>
|
||||
<p>登录后可使用<strong>云端空间</strong>:个人空间「我的空间」与团队共享空间,数据保存在本机服务的 SQLite 数据库(<code>server/data/opsxapi.db</code>),不上传任何第三方。</p>
|
||||
<h2 id="sec-1">注册 / 登录</h2>
|
||||
<p>最左<strong>活动栏</strong>底部 <strong>登录</strong> 按钮打开弹窗:</p>
|
||||
<ul>
|
||||
|
||||
Vendored
+5
-5
@@ -139,9 +139,9 @@
|
||||
<div class="page-meta">更新于 2026-09-17</div>
|
||||
<article class="md">
|
||||
<h1>AI 助手</h1>
|
||||
<p>接入 <strong>OpenAI 兼容协议</strong>的大模型,为接口调试与自动化测试提供 AI 能力。密钥只保存在本机(localStorage),请求直连供应商,不经过任何第三方中转。</p>
|
||||
<p>接入 <strong>OpenAI 兼容协议</strong>的大模型,为自动化测试提供 AI 能力。模型调用直连供应商;供应商配置(包括密钥)随当前工作区保存,团队工作区会同步工作区数据。</p>
|
||||
<h2 id="sec-1">配置模型供应商</h2>
|
||||
<p><strong>设置 → AI 助手 → 模型供应商</strong>,点「添加供应商」,内置常用预设:</p>
|
||||
<p><strong>设置 → 大模型</strong>,选择预设并点击「+」,在右侧填写模型 ID、密钥和 Base URL,测试连接后点击「保存配置」。内置常用预设:</p>
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
@@ -180,8 +180,8 @@
|
||||
</table>
|
||||
<ul>
|
||||
<li><strong>API Key</strong>:Ollama 等本地服务可留空;</li>
|
||||
<li><strong>测试连接</strong>:保存前先发一条 ping 验证地址 / 密钥 / 模型名;</li>
|
||||
<li>多个供应商时可切换「当前使用」的模型。</li>
|
||||
<li><strong>测试连接</strong>:发一条 ping 验证地址 / 密钥 / 模型名;离线时也可先保存,之后再测试;</li>
|
||||
<li>多个供应商时可在左侧选择,保存后点「设为默认」指定自动化测试使用的模型。</li>
|
||||
</ul>
|
||||
<h2 id="sec-2">AI 生成场景用例</h2>
|
||||
<p>自动化测试视图 →「<strong>AI 生成</strong>」:</p>
|
||||
@@ -194,7 +194,7 @@
|
||||
<p>生成结果只是起点 —— 步骤、断言、提取规则都可再手动调整。</p>
|
||||
<h2 id="sec-3">隐私与边界</h2>
|
||||
<ul>
|
||||
<li>API Key 仅存本机,随工作区数据一起持久化;导出全量备份时会包含,分享备份文件前请注意;</li>
|
||||
<li>API Key 随当前工作区数据保存;团队工作区会同步,导出全量备份时也会包含。分享工作区或备份文件前请检查密钥;</li>
|
||||
<li>供应商收到的内容为接口清单摘要(方法 / URL / Body 片段)与你的指令,<strong>不含</strong>环境变量值与 API Key;</li>
|
||||
<li>未配置供应商时,Mock 数据、环境变量等离线功能不受影响。</li>
|
||||
</ul>
|
||||
|
||||
Vendored
+1
-1
@@ -1 +1 @@
|
||||
[{"Slug":"intro","Title":"产品介绍","Group":"开始使用","Text":"OpsxAPI 是一款 本地优先 的专业接口工作台,面向运维与开发,把 HTTP、GraphQL、WebSocket、SSE、Socket.IO、MQTT 六种协议的调试放进同一个窗口。 无需注册、无需联网同步,数据全部保存在本地浏览器(localStorage),导出备份随时带走。 功能总览 能力 说明 六种协议 HTTP / GraphQL / WebSocket / SSE / Socket.IO / MQTT 集合管理 目录树、搜索、右键菜单、拖拽移动、多标签页 多环境变量 、 全局替换,一键切换环境 数据导入 OpenAPI/Swagger、Postman、Apifox、Apipost、cURL、HAR、Insomnia、Hoppscotch、Markdown 等 数据导出 OpenAPI 3.0、Postman v2.1、Markdown 文档、全量备份 代码生成 cURL / JavaScript fetch / Python requests / Go net/http 代理能力 本地 Gin 代理绕开 CORS;WebSocket 隧道支持自定义握手 Header 双端形态 浏览器(Web)与桌面端(Tauri)共用同一套界面 支持的协议与场景 协议 典型场景 特色 HTTP / REST 调试 REST 接口、运维平台 API 7 种方法、6 种请求体、3 种鉴权 GraphQL 查询网关、中台接口 Query + Variables 独立编辑 WebSocket 实时推送、行情、IM 自定义握手 Header(经本地隧道) SSE AI 流式输出、监控事件流 直连失败自动切代理、断点续传 Socket.IO Node 实时应用 事件监听、emit 参数、path/auth 配置 MQTT IoT 设备、消息总线 通配符订阅、QoS、自动重连 为什么选 OpsxAPI 数据主权 :所有集合、环境、历史保存在本机,不经过任何第三方服务器。 开箱即用 : 一条命令启动;首次打开自带覆盖全部协议的示例集合。 工程友好 :导入自动建目录、路径参数自动转变量;导出即标准 OpenAPI,可回流 Apifox / Swagger 生态。 轻量克制 :一个 Go 二进制 + 一份前端,资源占用小,内网可部署。 提示 :想快速感受?直接看下一章《快速开始》,30 秒发出第一个请求。"},{"Slug":"quickstart","Title":"快速开始","Group":"开始使用","Text":"环境要求 组件 版本 用途 Node.js ≥ 20 Web 前端(Vite) Go ≥ 1.23 本地代理服务(Gin) Rust + Cargo 可选 仅编译桌面端(Tauri)时需要 启动 看到下面的输出即可打开浏览器: 命令 说明 浏览器版(推荐) 桌面窗口(Tauri,共用同一 UI) Web + 桌面一起开 / 查看状态 / 停止 30 秒发出第一个请求 1. 打开 \u003chttp://127.0.0.1:5173 ,左侧是内置的「示例集合」。 2. 点击 HTTP · httpbin → 获取请求回显 。 3. 点击右上角 发送 。 4. 下方响应区显示 、耗时与 JSON 高亮响应体 —— 完成! 桌面端(可选) 桌面端复用同一份界面与数据通道,HTTP 请求走 Tauri 原生网络栈,不依赖浏览器 CORS。 提示 :首次打开会自动创建两个环境(正式 / 本地)和一个覆盖全部协议的示例集合,可随时在「设置」中重置。"},{"Slug":"interface","Title":"界面导览","Group":"开始使用","Text":"OpsxAPI 主界面分为五个区域: 顶栏 元素 作用 + 新建 快捷创建各协议请求 / 目录(默认建在第一个集合) 导入 / 导出 打开数据导入、导出对话框 环境选择器 切换当前环境(变量随之生效),「⚙ 环境」管理环境与变量 🌙 / ☀️ 明暗主题切换 ⋯ 设置(主题、超时、重置工作区) 左侧集合树 点击请求 → 在标签页中打开;点击目录 → 展开 / 折叠。 右键 任意节点:新建、重命名、复制、导出、删除。 拖拽 请求或目录到另一个目录,即可移动(支持跨集合)。 顶部搜索框按名称 / 方法 / URL 实时过滤。 底部为 历史记录 :显示最近请求的状态码与耗时,点击可回跳到对应接口。 标签页栏 同时打开多个接口,中键或 × 关闭;右键支持「关闭其他 / 关闭全部」。正在连接中的实时协议标签会显示呼吸圆点。 请求编辑区(上半部分) 按协议呈现不同编辑器: 协议 标签页 HTTP Params · Body · Headers · Cookies · Auth · 说明 GraphQL Query · Variables · Headers · Auth WebSocket 消息 · 握手配置 · 说明 SSE 事件流 · 连接配置 · 说明 Socket.IO 事件 · 连接配置 · 说明 MQTT 消息 · Broker / 订阅 · 发布 · 说明 响应区(下半部分) HTTP / GraphQL :响应体(Pretty JSON 高亮)、Raw、响应头,右上角状态 / 耗时 / 大小 / 复制。 实时协议 :分色消息日志(接收 · 绿、发送 · 蓝、系统 · 灰、错误 · 红),顶部实时统计收发条数与连接时长。"},{"Slug":"http","Title":"发送 HTTP 请求","Group":"请求调试","Text":"发送请求 1. 选择方法(GET / POST / PUT / PATCH / DELETE / HEAD / OPTIONS)。 2. 输入 URL,支持 形式的变量(见《环境与变量》)。 3. 点击 发送 ,Line truncated
|
||||
[{"Slug":"intro","Title":"产品介绍","Group":"开始使用","Text":"OpsxAPI 是一款 本地优先 的专业接口工作台,面向运维与开发,把 HTTP、GraphQL、WebSocket、SSE、Socket.IO、MQTT 六种协议的调试放进同一个窗口。 无需注册、无需联网同步,数据全部保存在本地浏览器(localStorage),导出备份随时带走。 功能总览 能力 说明 六种协议 HTTP / GraphQL / WebSocket / SSE / Socket.IO / MQTT 集合管理 目录树、搜索、右键菜单、拖拽移动、多标签页 多环境变量 、 全局替换,一键切换环境 数据导入 OpenAPI/Swagger、Postman、Apifox、Apipost、cURL、HAR、Insomnia、Hoppscotch、Markdown 等 数据导出 OpenAPI 3.0、Postman v2.1、Markdown 文档、全量备份 代码生成 cURL / JavaScript fetch / Python requests / Go net/http 代理能力 本地 Gin 代理绕开 CORS;WebSocket 隧道支持自定义握手 Header 双端形态 浏览器(Web)与桌面端(Tauri)共用同一套界面 支持的协议与场景 协议 典型场景 特色 HTTP / REST 调试 REST 接口、运维平台 API 7 种方法、6 种请求体、3 种鉴权 GraphQL 查询网关、中台接口 Query + Variables 独立编辑 WebSocket 实时推送、行情、IM 自定义握手 Header(经本地隧道) SSE AI 流式输出、监控事件流 直连失败自动切代理、断点续传 Socket.IO Node 实时应用 事件监听、emit 参数、path/auth 配置 MQTT IoT 设备、消息总线 通配符订阅、QoS、自动重连 为什么选 OpsxAPI 数据主权 :所有集合、环境、历史保存在本机,不经过任何第三方服务器。 开箱即用 : 一条命令启动;首次打开自带覆盖全部协议的示例集合。 工程友好 :导入自动建目录、路径参数自动转变量;导出即标准 OpenAPI,可回流 Apifox / Swagger 生态。 轻量克制 :一个 Go 二进制 + 一份前端,资源占用小,内网可部署。 提示 :想快速感受?直接看下一章《快速开始》,30 秒发出第一个请求。"},{"Slug":"quickstart","Title":"快速开始","Group":"开始使用","Text":"环境要求 组件 版本 用途 Node.js ≥ 20 Web 前端(Vite) Go ≥ 1.23 本地代理服务(Gin) Rust + Cargo 可选 仅编译桌面端(Tauri)时需要 启动 看到下面的输出即可打开浏览器: 命令 说明 浏览器版(推荐) 桌面窗口(Tauri,共用同一 UI) Web + 桌面一起开 / 查看状态 / 停止 30 秒发出第一个请求 1. 打开 \u003chttp://127.0.0.1:5173 ,左侧是内置的「示例集合」。 2. 点击 HTTP · httpbin → 获取请求回显 。 3. 点击右上角 发送 。 4. 下方响应区显示 、耗时与 JSON 高亮响应体 —— 完成! 桌面端(可选) 桌面端复用同一份界面与数据通道,HTTP 请求走 Tauri 原生网络栈,不依赖浏览器 CORS。 提示 :首次打开会自动创建两个环境(正式 / 本地)和一个覆盖全部协议的示例集合,可随时在「设置」中重置。"},{"Slug":"interface","Title":"界面导览","Group":"开始使用","Text":"OpsxAPI 主界面分为五个区域: 顶栏 元素 作用 + 新建 快捷创建各协议请求 / 目录(默认建在第一个集合) 导入 / 导出 打开数据导入、导出对话框 环境选择器 切换当前环境(变量随之生效),「⚙ 环境」管理环境与变量 🌙 / ☀️ 明暗主题切换 ⋯ 设置(主题、超时、重置工作区) 左侧集合树 点击请求 → 在标签页中打开;点击目录 → 展开 / 折叠。 右键 任意节点:新建、重命名、复制、导出、删除。 拖拽 请求或目录到另一个目录,即可移动(支持跨集合)。 顶部搜索框按名称 / 方法 / URL 实时过滤。 底部为 历史记录 :显示最近请求的状态码与耗时,点击可回跳到对应接口。 标签页栏 同时打开多个接口,中键或 × 关闭;右键支持「关闭其他 / 关闭全部」。正在连接中的实时协议标签会显示呼吸圆点。 请求编辑区(上半部分) 按协议呈现不同编辑器: 协议 标签页 HTTP Params · Body · Headers · Cookies · Auth · 说明 GraphQL Query · Variables · Headers · Auth WebSocket 消息 · 握手配置 · 说明 SSE 事件流 · 连接配置 · 说明 Socket.IO 事件 · 连接配置 · 说明 MQTT 消息 · Broker / 订阅 · 发布 · 说明 响应区(下半部分) HTTP / GraphQL :响应体(Pretty JSON 高亮)、Raw、响应头,右上角状态 / 耗时 / 大小 / 复制。 实时协议 :分色消息日志(接收 · 绿、发送 · 蓝、系统 · 灰、错误 · 红),顶部实时统计收发条数与连接时长。"},{"Slug":"http","Title":"发送 HTTP 请求","Group":"请求调试","Text":"发送请求 1. 选择方法(GET / POST / PUT / PATCH / DELETE / HEAD / OPTIONS)。 2. 输入 URL,支持 形式的变量(见《环境与变量》)。 3. 点击 发送 ,Line truncated
|
||||
Vendored
+1
@@ -45,6 +45,7 @@
|
||||
<a href="#protocols">协议</a>
|
||||
<a href="#exchange">导入导出</a>
|
||||
<a href="/pricing">定价</a>
|
||||
<a href="/license">授权</a>
|
||||
<a href="/docs" target="_blank" rel="noopener">文档</a>
|
||||
</nav>
|
||||
<div class="nav-cta">
|
||||
|
||||
Vendored
+211
@@ -0,0 +1,211 @@
|
||||
<!doctype html>
|
||||
<html lang="zh-CN">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<title>授权与激活 · OpsxAPI</title>
|
||||
<meta name="description" content="OpsxAPI 授权与激活说明:付款后邮件交付授权码(LIC-),在「设置 → 授权」粘贴即完成激活;生产内网可用 LICOFF- 离线许可证,一机一码、支持自助解绑换机。">
|
||||
<link rel="icon" type="image/svg+xml" href="data:image/svg+xml,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20viewBox%3D%220%200%2064%2064%22%3E%3Cdefs%3E%3ClinearGradient%20id%3D%22g%22%20x1%3D%220%22%20y1%3D%220%22%20x2%3D%2264%22%20y2%3D%2264%22%20gradientUnits%3D%22userSpaceOnUse%22%3E%3Cstop%20offset%3D%220%22%20stop-color%3D%22%232563EB%22%2F%3E%3Cstop%20offset%3D%221%22%20stop-color%3D%22%2306B6D4%22%2F%3E%3C%2FlinearGradient%3E%3C%2Fdefs%3E%3Crect%20width%3D%2264%22%20height%3D%2264%22%20rx%3D%2214%22%20fill%3D%22url%28%23g%29%22%2F%3E%3Cg%20stroke%3D%22%23fff%22%20stroke-width%3D%225%22%20stroke-linecap%3D%22round%22%20stroke-linejoin%3D%22round%22%20fill%3D%22none%22%3E%3Cpath%20d%3D%22M17%2025h26%22%2F%3E%3Cpath%20d%3D%22M37%2019l6%206-6%206%22%2F%3E%3Cpath%20d%3D%22M47%2039H21%22%2F%3E%3Cpath%20d%3D%22M27%2033l-6%206%206%206%22%2F%3E%3C%2Fg%3E%3C%2Fsvg%3E">
|
||||
<link rel="stylesheet" href="/site/assets/site.css">
|
||||
<style>
|
||||
/* 授权页专用样式(lic- 前缀,避免与站点既有类冲突) // Logan.Li */
|
||||
.lic-hero{padding:72px 0 30px;text-align:center;}
|
||||
.lic-hero h1{font-size:clamp(28px,4.5vw,42px);margin:12px 0 10px;}
|
||||
.lic-hero .lead{max-width:780px;margin:0 auto;font-size:15px;line-height:1.9;opacity:.75;}
|
||||
.lic-chip{display:inline-flex;align-items:center;gap:8px;padding:7px 16px;border-radius:999px;border:1px solid var(--blue,#2563eb);color:var(--blue,#2563eb);font-size:13px;font-weight:600;}
|
||||
.lic-steps{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:22px 16px;margin:22px 0 0;}
|
||||
@media (max-width:720px){.lic-steps{grid-template-columns:1fr;}}
|
||||
.lic-step{position:relative;border:1px solid var(--border);border-radius:calc(var(--radius,14px) + 2px);padding:22px 18px 18px;background:var(--bg);}
|
||||
.lic-step .n{position:absolute;top:-11px;left:18px;background:var(--blue,#2563eb);color:#fff;font-size:11.5px;font-weight:700;padding:2px 10px;border-radius:999px;letter-spacing:.03em;}
|
||||
.lic-step h3{margin:4px 0 8px;font-size:15.5px;}
|
||||
.lic-step p{margin:0;font-size:13.5px;line-height:1.9;opacity:.78;}
|
||||
.lic-cmp-wrap{overflow-x:auto;margin-top:20px;}
|
||||
.lic-cmp{width:100%;min-width:640px;border-collapse:collapse;font-size:14px;background:var(--bg);}
|
||||
.lic-cmp th,.lic-cmp td{border-bottom:1px solid var(--border);padding:12px;text-align:left;vertical-align:top;}
|
||||
.lic-cmp thead th{font-size:13.5px;letter-spacing:.02em;}
|
||||
.lic-cmp tbody th{width:132px;font-weight:600;color:var(--text-3,#98a2b3);}
|
||||
.lic-cmp code{font-family:var(--mono,monospace);font-size:12.5px;background:rgba(127,127,127,.14);padding:1px 5px;border-radius:4px;}
|
||||
.lic-faq{max-width:880px;margin:0 auto;}
|
||||
.lic-faq details{border-bottom:1px solid var(--border);padding:15px 4px;}
|
||||
.lic-faq summary{cursor:pointer;font-weight:600;font-size:15px;list-style:none;display:flex;justify-content:space-between;gap:12px;align-items:center;}
|
||||
.lic-faq summary::after{content:"+";opacity:.5;font-size:18px;line-height:1;}
|
||||
.lic-faq details[open] summary::after{content:"–";}
|
||||
.lic-faq details p{margin:10px 0 2px;font-size:14px;line-height:1.9;opacity:.78;}
|
||||
.lic-note{max-width:880px;margin:16px auto 0;font-size:13px;line-height:1.9;opacity:.68;}
|
||||
.lic-cta{text-align:center;max-width:680px;margin:0 auto;}
|
||||
.lic-cta .btns{display:flex;gap:12px;justify-content:center;flex-wrap:wrap;margin-top:20px;}
|
||||
.lic-scope{max-width:880px;margin:24px auto 0;font-size:13.5px;line-height:1.95;opacity:.78;}
|
||||
.lic-scope li{margin:6px 0;}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<header class="nav">
|
||||
<div class="nav-inner">
|
||||
<a class="brand" href="/home">
|
||||
<span class="brand-logo"><svg width="30" height="30" viewBox="0 0 64 64" aria-label="OpsxAPI"><defs><linearGradient id="lg-site" x1="0" y1="0" x2="64" y2="64" gradientUnits="userSpaceOnUse"><stop offset="0" stop-color="#2563EB"/><stop offset="1" stop-color="#06B6D4"/></linearGradient></defs><rect width="64" height="64" rx="14" fill="url(#lg-site)"/><g stroke="#fff" stroke-width="5" stroke-linecap="round" stroke-linejoin="round" fill="none"><path d="M17 25h26"/><path d="M37 19l6 6-6 6"/><path d="M47 39H21"/><path d="M27 33l-6 6 6 6"/></g></svg></span>
|
||||
<span class="brand-name">OpsxAPI</span>
|
||||
</a>
|
||||
<nav class="nav-links">
|
||||
<a href="/#features">功能</a>
|
||||
<a href="/#protocols">协议</a>
|
||||
<a href="/#exchange">导入导出</a>
|
||||
<a href="/pricing">定价</a>
|
||||
<a href="/license">授权</a>
|
||||
<a href="/docs" target="_blank" rel="noopener">文档</a>
|
||||
</nav>
|
||||
<div class="nav-cta">
|
||||
<a class="btn btn-primary" href="/docs/quickstart" target="_blank" rel="noopener">快速上手 ↗</a>
|
||||
</div>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<main>
|
||||
<section class="lic-hero">
|
||||
<span class="lic-chip">30 天免费试用 · 首次启动自动开始 · 无需注册</span>
|
||||
<h1>授权与激活</h1>
|
||||
<p class="lead">一次说清四件事:怎么买、授权码怎么交付、程序里怎么激活、换机与断网怎么办。授权绑定设备而非账号,全流程不需要注册。</p>
|
||||
</section>
|
||||
|
||||
<section class="section">
|
||||
<div class="wrap">
|
||||
<h2 class="sec-title">交付与激活:四步</h2>
|
||||
<p class="sec-sub">从下单到激活通常当天完成,最短几分钟。</p>
|
||||
<div class="lic-steps">
|
||||
<div class="lic-step">
|
||||
<span class="n">STEP 1</span>
|
||||
<h3>下单确认</h3>
|
||||
<p>邮件说明产品、授权类型(包年 / 永久 / 团队席位 / 私有化)与设备数量。支持对公转账与开具发票,工作日当天回复。</p>
|
||||
</div>
|
||||
<div class="lic-step">
|
||||
<span class="n">STEP 2</span>
|
||||
<h3>邮件交付授权码</h3>
|
||||
<p>付款确认后即刻签发,授权码以邮件送达你的邮箱:联网用 <code>LIC-</code> 前缀的在线授权码,隔离内网用 <code>LICOFF-</code> 前缀的离线许可证。</p>
|
||||
</div>
|
||||
<div class="lic-step">
|
||||
<span class="n">STEP 3</span>
|
||||
<h3>应用内激活</h3>
|
||||
<p>打开「设置 → 授权」,粘贴授权码点击激活。程序连接授权服务器 <code>licence.rlwj.cn</code> 登记本机设备并回写一次性激活令牌,此后每 7 天自动校验一次。</p>
|
||||
</div>
|
||||
<div class="lic-step">
|
||||
<span class="n">STEP 4</span>
|
||||
<h3>换机与续期</h3>
|
||||
<p>在本机自助解绑即可把席位迁到新机器,每年免费换绑 2 次;到期前 30 天邮件提醒,续期后签发新授权码替换即可。</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="section alt">
|
||||
<div class="wrap">
|
||||
<h2 class="sec-title">两种激活方式</h2>
|
||||
<p class="sec-sub">能联网就用在线授权码,完全不出网就用离线许可证——功能与有效期一致。</p>
|
||||
<div class="lic-cmp-wrap">
|
||||
<table class="lic-cmp">
|
||||
<thead>
|
||||
<tr><th>对比项</th><th>在线授权码 <code>LIC-</code></th><th>离线许可证 <code>LICOFF-</code></th></tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr><th>是否联网</th><td>激活时联网一次,之后每 7 天校验一次</td><td>全程无需联网,本地验签</td></tr>
|
||||
<tr><th>设备绑定</th><td>绑定设备 ID,一机一码</td><td>绑定设备 ID,一机一码</td></tr>
|
||||
<tr><th>断网可用</th><td>激活后可连续离线使用 30 天</td><td>始终有效,与网络无关</td></tr>
|
||||
<tr><th>有效期</th><td>包年 365 天;永久授权长期有效</td><td>单张最长 365 天,可续签</td></tr>
|
||||
<tr><th>换绑</th><td>应用内自助解绑,每年免费换绑 2 次</td><td>邮件换取新证并解绑旧设备</td></tr>
|
||||
<tr><th>适合场景</th><td>办公网、开发机、云主机</td><td>生产内网、专有云、隔离环境</td></tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<div class="lic-scope">
|
||||
<p><b>授权服务器会收到什么?</b>客户端只与 <code>licence.rlwj.cn</code>(443)通信,用于设备登记与周期校验,只发送授权码与设备标识(设备 ID、设备名称)——不发送接口内容、请求/响应报文、环境变量与集合数据等任何业务数据。接口数据本机存储,全流程不上云。服务端返回 Ed25519 签名,客户端本地验签,验签失败按未授权处理。</p>
|
||||
<ul>
|
||||
<li>内网只放行白名单出网:把 <code>licence.rlwj.cn</code> 加入白名单,或直接使用离线许可证。</li>
|
||||
<li>授权服务器不可达:按宽限期继续可用,业务不会当场中断;恢复网络后自动补校验。</li>
|
||||
<li>不想让客户端出网:一律选离线许可证,客户端永不发起网络请求。</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="section">
|
||||
<div class="wrap">
|
||||
<h2 class="sec-title">授权类型与价格</h2>
|
||||
<p class="sec-sub">个人非商业用途免费;工作中的商业用途需要授权。</p>
|
||||
<div class="lic-cmp-wrap">
|
||||
<table class="lic-cmp">
|
||||
<thead>
|
||||
<tr><th>授权</th><th>价格</th><th>设备数</th><th>包含</th></tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr><th>个人 · 非商业</th><td>¥0</td><td>不涉及授权</td><td>六种协议调试、集合与环境本地保存;商业用途需购买授权</td></tr>
|
||||
<tr><th>专业版 · 包年</th><td>¥99 / 年</td><td>3 台</td><td>全协议调试能力、集合/环境/历史无限量、全格式导入导出与代码生成、本机 Mock、设备自助解绑</td></tr>
|
||||
<tr><th>专业版 · 永久</th><td>¥199 一次性</td><td>3 台</td><td>包年全部能力,一次买断、无续费,含发布版本与一年内更新</td></tr>
|
||||
<tr><th>团队版</th><td>¥99 / 人 / 年</td><td>按席位,5 席起</td><td>个人版全部能力 + 集合批量导入分发、团队共享空间(自托管)、优先技术支持</td></tr>
|
||||
<tr><th>企业私有化</th><td>¥8,000 / 年(标价)</td><td>单法人不限席位</td><td>整机 / 内网部署、数据不出企业、远程部署协助、定制开发通道;可选 ¥30,000 一次性买断</td></tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<p class="lic-note">完整功能对比、让利与组合报价见 <a href="/pricing">定价页</a>。表中为标价,成交可按规模与场景议价。</p>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="section alt">
|
||||
<div class="wrap">
|
||||
<h2 class="sec-title">授权常见问题</h2>
|
||||
<div class="lic-faq">
|
||||
<details><summary>授权码多久能收到?</summary><p>付款确认后即刻签发并邮件发出,工作日通常当天完成(含开票需求)。邮件里就是完整授权码,无需注册账号或登录任何后台。</p></details>
|
||||
<details><summary>授权绑定设备还是账号?</summary><p>绑定设备。每台机器有唯一设备 ID,一个授权码按授权的设备数占座,与账号无关;团队版按席位签发多个授权码,组织内可转移。</p></details>
|
||||
<details><summary>换电脑 / 重装系统后设备 ID 变了怎么办?</summary><p>旧机还在:在「设置 → 授权」点解绑,腾出席位后在新机激活。旧机已不在手:邮件与我们说明(附授权码后四位与新设备 ID),由我们在授权后台释放旧设备。每年免费换绑 2 次。</p></details>
|
||||
<details><summary>断网了授权会失效吗?</summary><p>不会。离线许可证本地自验,永不联网也有效;在线授权码激活后可连续离线使用 30 天,超时后联网校验一次即可继续。授权服务器不可达时按宽限期放行,不会打断正在进行的调试。</p></details>
|
||||
<details><summary>生产内网完全不出网,怎么激活?</summary><p>改用离线许可证(<code>LICOFF-</code>):在应用内导出本机机器码,邮件发我们换取离线证,本地导入即完成激活,全程不需要客户端联网。</p></details>
|
||||
<details><summary>续期怎么操作?</summary><p>到期前 30 天我们会邮件提醒。付款后签发新的授权码,在「设置 → 授权」里替换旧码即可,同一台设备无需先解绑;永久授权无需续期。</p></details>
|
||||
<details><summary>试用期结束后我的接口集合和环境还在吗?</summary><p>都在。授权只控制专业功能是否可用,本机集合、环境、历史记录与配置不做任何改动;试用结束后个人非商业用途可继续使用,购买授权后专业功能立即恢复。</p></details>
|
||||
<details><summary>激活失败怎么排查?</summary><p>先在「设置 → 授权」看当前状态与到期时间。若提示无效或不匹配,请把授权码后四位、设备 ID 与报错截图一并发邮件,我们核对授权服务器上的记录后处理(常见原因是设备数已占满或授权码输漏字符)。</p></details>
|
||||
<details><summary>能开发票、走对公转账吗?</summary><p>可以。个人与企业均可开具发票,支持对公转账、多产品捆绑与批量授权;邮件说明抬头与税号即可。</p></details>
|
||||
<details><summary>三个产品(OpsxAPI / OpsxDB / OpsXShell)要分别买吗?</summary><p>三个产品独立授权、独立计价;它们共用同一套授权体系(同一个设备 ID 规则与同一个授权服务器)。多产品组合、团队或教育用途可邮件说明需求,给捆绑价。</p></details>
|
||||
</div>
|
||||
<p class="lic-note">本页更新于 2026-10-16,授权口径与授权服务器 <code>licence.rlwj.cn</code> 实际配置一致。</p>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="section">
|
||||
<div class="wrap lic-cta">
|
||||
<h2 class="sec-title">准备好购买授权</h2>
|
||||
<p class="sec-sub">邮件说明产品、授权类型与设备数,当天回复并开票。</p>
|
||||
<div class="btns">
|
||||
<a class="btn btn-primary" href="mailto:hello@opsxapi.dev?subject=OpsxAPI%20%E6%8E%88%E6%9D%83%E4%B8%8E%E6%BF%80%E6%B4%BB%E5%92%A8%E8%AF%A2">邮件咨询购买</a>
|
||||
<a class="btn btn-ghost" href="/#exchange">还没装?立即下载</a>
|
||||
</div>
|
||||
<p class="lic-note">付款后当场发放授权码(LIC-),粘贴到「设置 → 授权」即完成激活——整个流程无需注册账号。</p>
|
||||
</div>
|
||||
</section>
|
||||
</main>
|
||||
|
||||
<footer class="footer">
|
||||
<div class="wrap footer-grid">
|
||||
<div>
|
||||
<div class="brand"><span class="brand-logo"><svg width="30" height="30" viewBox="0 0 64 64" aria-label="OpsxAPI"><defs><linearGradient id="lg-site" x1="0" y1="0" x2="64" y2="64" gradientUnits="userSpaceOnUse"><stop offset="0" stop-color="#2563EB"/><stop offset="1" stop-color="#06B6D4"/></linearGradient></defs><rect width="64" height="64" rx="14" fill="url(#lg-site)"/><g stroke="#fff" stroke-width="5" stroke-linecap="round" stroke-linejoin="round" fill="none"><path d="M17 25h26"/><path d="M37 19l6 6-6 6"/><path d="M47 39H21"/><path d="M27 33l-6 6 6 6"/></g></svg></span><span class="brand-name">OpsxAPI</span></div>
|
||||
<p class="footer-desc">本地优先的全协议接口工作台。<br>数据在自己手里,才是真的效率工具。</p>
|
||||
</div>
|
||||
<div>
|
||||
<h4>产品</h4>
|
||||
<a href="/#features">功能特性</a>
|
||||
<a href="/#pricing">定价与让利</a>
|
||||
<a href="/#compare">工具对比</a>
|
||||
</div>
|
||||
<div>
|
||||
<h4>资源</h4>
|
||||
<a href="/pricing">定价</a>
|
||||
<a href="/license">授权与激活</a>
|
||||
<a href="/docs" target="_blank" rel="noopener">用户文档</a>
|
||||
<a href="/docs/faq" target="_blank" rel="noopener">常见问题</a>
|
||||
</div>
|
||||
<div>
|
||||
<h4>支持</h4>
|
||||
<a href="mailto:hello@opsxapi.dev">hello@opsxapi.dev</a>
|
||||
<a href="/docs/quickstart" target="_blank" rel="noopener">快速开始</a>
|
||||
<a href="/terms" target="_blank" rel="noopener">服务条款</a>
|
||||
</div>
|
||||
</div>
|
||||
<div class="footer-bar">© 2026 OpsxAPI · <a href="/terms" target="_blank" rel="noopener">服务条款</a> · <a href="/license">授权与激活</a></div>
|
||||
</footer>
|
||||
</body>
|
||||
</html>
|
||||
Vendored
+211
@@ -0,0 +1,211 @@
|
||||
<!doctype html>
|
||||
<html lang="zh-CN">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<title>授权与激活 · OpsxAPI</title>
|
||||
<meta name="description" content="OpsxAPI 授权与激活说明:付款后邮件交付授权码(LIC-),在「设置 → 授权」粘贴即完成激活;生产内网可用 LICOFF- 离线许可证,一机一码、支持自助解绑换机。">
|
||||
<link rel="icon" type="image/svg+xml" href="data:image/svg+xml,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20viewBox%3D%220%200%2064%2064%22%3E%3Cdefs%3E%3ClinearGradient%20id%3D%22g%22%20x1%3D%220%22%20y1%3D%220%22%20x2%3D%2264%22%20y2%3D%2264%22%20gradientUnits%3D%22userSpaceOnUse%22%3E%3Cstop%20offset%3D%220%22%20stop-color%3D%22%232563EB%22%2F%3E%3Cstop%20offset%3D%221%22%20stop-color%3D%22%2306B6D4%22%2F%3E%3C%2FlinearGradient%3E%3C%2Fdefs%3E%3Crect%20width%3D%2264%22%20height%3D%2264%22%20rx%3D%2214%22%20fill%3D%22url%28%23g%29%22%2F%3E%3Cg%20stroke%3D%22%23fff%22%20stroke-width%3D%225%22%20stroke-linecap%3D%22round%22%20stroke-linejoin%3D%22round%22%20fill%3D%22none%22%3E%3Cpath%20d%3D%22M17%2025h26%22%2F%3E%3Cpath%20d%3D%22M37%2019l6%206-6%206%22%2F%3E%3Cpath%20d%3D%22M47%2039H21%22%2F%3E%3Cpath%20d%3D%22M27%2033l-6%206%206%206%22%2F%3E%3C%2Fg%3E%3C%2Fsvg%3E">
|
||||
<link rel="stylesheet" href="/site/assets/site.css">
|
||||
<style>
|
||||
/* 授权页专用样式(lic- 前缀,避免与站点既有类冲突) // Logan.Li */
|
||||
.lic-hero{padding:72px 0 30px;text-align:center;}
|
||||
.lic-hero h1{font-size:clamp(28px,4.5vw,42px);margin:12px 0 10px;}
|
||||
.lic-hero .lead{max-width:780px;margin:0 auto;font-size:15px;line-height:1.9;opacity:.75;}
|
||||
.lic-chip{display:inline-flex;align-items:center;gap:8px;padding:7px 16px;border-radius:999px;border:1px solid var(--blue,#2563eb);color:var(--blue,#2563eb);font-size:13px;font-weight:600;}
|
||||
.lic-steps{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:22px 16px;margin:22px 0 0;}
|
||||
@media (max-width:720px){.lic-steps{grid-template-columns:1fr;}}
|
||||
.lic-step{position:relative;border:1px solid var(--border);border-radius:calc(var(--radius,14px) + 2px);padding:22px 18px 18px;background:var(--bg);}
|
||||
.lic-step .n{position:absolute;top:-11px;left:18px;background:var(--blue,#2563eb);color:#fff;font-size:11.5px;font-weight:700;padding:2px 10px;border-radius:999px;letter-spacing:.03em;}
|
||||
.lic-step h3{margin:4px 0 8px;font-size:15.5px;}
|
||||
.lic-step p{margin:0;font-size:13.5px;line-height:1.9;opacity:.78;}
|
||||
.lic-cmp-wrap{overflow-x:auto;margin-top:20px;}
|
||||
.lic-cmp{width:100%;min-width:640px;border-collapse:collapse;font-size:14px;background:var(--bg);}
|
||||
.lic-cmp th,.lic-cmp td{border-bottom:1px solid var(--border);padding:12px;text-align:left;vertical-align:top;}
|
||||
.lic-cmp thead th{font-size:13.5px;letter-spacing:.02em;}
|
||||
.lic-cmp tbody th{width:132px;font-weight:600;color:var(--text-3,#98a2b3);}
|
||||
.lic-cmp code{font-family:var(--mono,monospace);font-size:12.5px;background:rgba(127,127,127,.14);padding:1px 5px;border-radius:4px;}
|
||||
.lic-faq{max-width:880px;margin:0 auto;}
|
||||
.lic-faq details{border-bottom:1px solid var(--border);padding:15px 4px;}
|
||||
.lic-faq summary{cursor:pointer;font-weight:600;font-size:15px;list-style:none;display:flex;justify-content:space-between;gap:12px;align-items:center;}
|
||||
.lic-faq summary::after{content:"+";opacity:.5;font-size:18px;line-height:1;}
|
||||
.lic-faq details[open] summary::after{content:"–";}
|
||||
.lic-faq details p{margin:10px 0 2px;font-size:14px;line-height:1.9;opacity:.78;}
|
||||
.lic-note{max-width:880px;margin:16px auto 0;font-size:13px;line-height:1.9;opacity:.68;}
|
||||
.lic-cta{text-align:center;max-width:680px;margin:0 auto;}
|
||||
.lic-cta .btns{display:flex;gap:12px;justify-content:center;flex-wrap:wrap;margin-top:20px;}
|
||||
.lic-scope{max-width:880px;margin:24px auto 0;font-size:13.5px;line-height:1.95;opacity:.78;}
|
||||
.lic-scope li{margin:6px 0;}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<header class="nav">
|
||||
<div class="nav-inner">
|
||||
<a class="brand" href="/home">
|
||||
<span class="brand-logo"><svg width="30" height="30" viewBox="0 0 64 64" aria-label="OpsxAPI"><defs><linearGradient id="lg-site" x1="0" y1="0" x2="64" y2="64" gradientUnits="userSpaceOnUse"><stop offset="0" stop-color="#2563EB"/><stop offset="1" stop-color="#06B6D4"/></linearGradient></defs><rect width="64" height="64" rx="14" fill="url(#lg-site)"/><g stroke="#fff" stroke-width="5" stroke-linecap="round" stroke-linejoin="round" fill="none"><path d="M17 25h26"/><path d="M37 19l6 6-6 6"/><path d="M47 39H21"/><path d="M27 33l-6 6 6 6"/></g></svg></span>
|
||||
<span class="brand-name">OpsxAPI</span>
|
||||
</a>
|
||||
<nav class="nav-links">
|
||||
<a href="/#features">功能</a>
|
||||
<a href="/#protocols">协议</a>
|
||||
<a href="/#exchange">导入导出</a>
|
||||
<a href="/pricing">定价</a>
|
||||
<a href="/license">授权</a>
|
||||
<a href="/docs" target="_blank" rel="noopener">文档</a>
|
||||
</nav>
|
||||
<div class="nav-cta">
|
||||
<a class="btn btn-primary" href="/docs/quickstart" target="_blank" rel="noopener">快速上手 ↗</a>
|
||||
</div>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<main>
|
||||
<section class="lic-hero">
|
||||
<span class="lic-chip">30 天免费试用 · 首次启动自动开始 · 无需注册</span>
|
||||
<h1>授权与激活</h1>
|
||||
<p class="lead">一次说清四件事:怎么买、授权码怎么交付、程序里怎么激活、换机与断网怎么办。授权绑定设备而非账号,全流程不需要注册。</p>
|
||||
</section>
|
||||
|
||||
<section class="section">
|
||||
<div class="wrap">
|
||||
<h2 class="sec-title">交付与激活:四步</h2>
|
||||
<p class="sec-sub">从下单到激活通常当天完成,最短几分钟。</p>
|
||||
<div class="lic-steps">
|
||||
<div class="lic-step">
|
||||
<span class="n">STEP 1</span>
|
||||
<h3>下单确认</h3>
|
||||
<p>邮件说明产品、授权类型(包年 / 永久 / 团队席位 / 私有化)与设备数量。支持对公转账与开具发票,工作日当天回复。</p>
|
||||
</div>
|
||||
<div class="lic-step">
|
||||
<span class="n">STEP 2</span>
|
||||
<h3>邮件交付授权码</h3>
|
||||
<p>付款确认后即刻签发,授权码以邮件送达你的邮箱:联网用 <code>LIC-</code> 前缀的在线授权码,隔离内网用 <code>LICOFF-</code> 前缀的离线许可证。</p>
|
||||
</div>
|
||||
<div class="lic-step">
|
||||
<span class="n">STEP 3</span>
|
||||
<h3>应用内激活</h3>
|
||||
<p>打开「设置 → 授权」,粘贴授权码点击激活。程序连接授权服务器 <code>licence.rlwj.cn</code> 登记本机设备并回写一次性激活令牌,此后每 7 天自动校验一次。</p>
|
||||
</div>
|
||||
<div class="lic-step">
|
||||
<span class="n">STEP 4</span>
|
||||
<h3>换机与续期</h3>
|
||||
<p>在本机自助解绑即可把席位迁到新机器,每年免费换绑 2 次;到期前 30 天邮件提醒,续期后签发新授权码替换即可。</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="section alt">
|
||||
<div class="wrap">
|
||||
<h2 class="sec-title">两种激活方式</h2>
|
||||
<p class="sec-sub">能联网就用在线授权码,完全不出网就用离线许可证——功能与有效期一致。</p>
|
||||
<div class="lic-cmp-wrap">
|
||||
<table class="lic-cmp">
|
||||
<thead>
|
||||
<tr><th>对比项</th><th>在线授权码 <code>LIC-</code></th><th>离线许可证 <code>LICOFF-</code></th></tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr><th>是否联网</th><td>激活时联网一次,之后每 7 天校验一次</td><td>全程无需联网,本地验签</td></tr>
|
||||
<tr><th>设备绑定</th><td>绑定设备 ID,一机一码</td><td>绑定设备 ID,一机一码</td></tr>
|
||||
<tr><th>断网可用</th><td>激活后可连续离线使用 30 天</td><td>始终有效,与网络无关</td></tr>
|
||||
<tr><th>有效期</th><td>包年 365 天;永久授权长期有效</td><td>单张最长 365 天,可续签</td></tr>
|
||||
<tr><th>换绑</th><td>应用内自助解绑,每年免费换绑 2 次</td><td>邮件换取新证并解绑旧设备</td></tr>
|
||||
<tr><th>适合场景</th><td>办公网、开发机、云主机</td><td>生产内网、专有云、隔离环境</td></tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<div class="lic-scope">
|
||||
<p><b>授权服务器会收到什么?</b>客户端只与 <code>licence.rlwj.cn</code>(443)通信,用于设备登记与周期校验,只发送授权码与设备标识(设备 ID、设备名称)——不发送接口内容、请求/响应报文、环境变量与集合数据等任何业务数据。接口数据本机存储,全流程不上云。服务端返回 Ed25519 签名,客户端本地验签,验签失败按未授权处理。</p>
|
||||
<ul>
|
||||
<li>内网只放行白名单出网:把 <code>licence.rlwj.cn</code> 加入白名单,或直接使用离线许可证。</li>
|
||||
<li>授权服务器不可达:按宽限期继续可用,业务不会当场中断;恢复网络后自动补校验。</li>
|
||||
<li>不想让客户端出网:一律选离线许可证,客户端永不发起网络请求。</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="section">
|
||||
<div class="wrap">
|
||||
<h2 class="sec-title">授权类型与价格</h2>
|
||||
<p class="sec-sub">个人非商业用途免费;工作中的商业用途需要授权。</p>
|
||||
<div class="lic-cmp-wrap">
|
||||
<table class="lic-cmp">
|
||||
<thead>
|
||||
<tr><th>授权</th><th>价格</th><th>设备数</th><th>包含</th></tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr><th>个人 · 非商业</th><td>¥0</td><td>不涉及授权</td><td>六种协议调试、集合与环境本地保存;商业用途需购买授权</td></tr>
|
||||
<tr><th>专业版 · 包年</th><td>¥99 / 年</td><td>3 台</td><td>全协议调试能力、集合/环境/历史无限量、全格式导入导出与代码生成、本机 Mock、设备自助解绑</td></tr>
|
||||
<tr><th>专业版 · 永久</th><td>¥199 一次性</td><td>3 台</td><td>包年全部能力,一次买断、无续费,含发布版本与一年内更新</td></tr>
|
||||
<tr><th>团队版</th><td>¥99 / 人 / 年</td><td>按席位,5 席起</td><td>个人版全部能力 + 集合批量导入分发、团队共享空间(自托管)、优先技术支持</td></tr>
|
||||
<tr><th>企业私有化</th><td>¥8,000 / 年(标价)</td><td>单法人不限席位</td><td>整机 / 内网部署、数据不出企业、远程部署协助、定制开发通道;可选 ¥30,000 一次性买断</td></tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<p class="lic-note">完整功能对比、让利与组合报价见 <a href="/pricing">定价页</a>。表中为标价,成交可按规模与场景议价。</p>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="section alt">
|
||||
<div class="wrap">
|
||||
<h2 class="sec-title">授权常见问题</h2>
|
||||
<div class="lic-faq">
|
||||
<details><summary>授权码多久能收到?</summary><p>付款确认后即刻签发并邮件发出,工作日通常当天完成(含开票需求)。邮件里就是完整授权码,无需注册账号或登录任何后台。</p></details>
|
||||
<details><summary>授权绑定设备还是账号?</summary><p>绑定设备。每台机器有唯一设备 ID,一个授权码按授权的设备数占座,与账号无关;团队版按席位签发多个授权码,组织内可转移。</p></details>
|
||||
<details><summary>换电脑 / 重装系统后设备 ID 变了怎么办?</summary><p>旧机还在:在「设置 → 授权」点解绑,腾出席位后在新机激活。旧机已不在手:邮件与我们说明(附授权码后四位与新设备 ID),由我们在授权后台释放旧设备。每年免费换绑 2 次。</p></details>
|
||||
<details><summary>断网了授权会失效吗?</summary><p>不会。离线许可证本地自验,永不联网也有效;在线授权码激活后可连续离线使用 30 天,超时后联网校验一次即可继续。授权服务器不可达时按宽限期放行,不会打断正在进行的调试。</p></details>
|
||||
<details><summary>生产内网完全不出网,怎么激活?</summary><p>改用离线许可证(<code>LICOFF-</code>):在应用内导出本机机器码,邮件发我们换取离线证,本地导入即完成激活,全程不需要客户端联网。</p></details>
|
||||
<details><summary>续期怎么操作?</summary><p>到期前 30 天我们会邮件提醒。付款后签发新的授权码,在「设置 → 授权」里替换旧码即可,同一台设备无需先解绑;永久授权无需续期。</p></details>
|
||||
<details><summary>试用期结束后我的接口集合和环境还在吗?</summary><p>都在。授权只控制专业功能是否可用,本机集合、环境、历史记录与配置不做任何改动;试用结束后个人非商业用途可继续使用,购买授权后专业功能立即恢复。</p></details>
|
||||
<details><summary>激活失败怎么排查?</summary><p>先在「设置 → 授权」看当前状态与到期时间。若提示无效或不匹配,请把授权码后四位、设备 ID 与报错截图一并发邮件,我们核对授权服务器上的记录后处理(常见原因是设备数已占满或授权码输漏字符)。</p></details>
|
||||
<details><summary>能开发票、走对公转账吗?</summary><p>可以。个人与企业均可开具发票,支持对公转账、多产品捆绑与批量授权;邮件说明抬头与税号即可。</p></details>
|
||||
<details><summary>三个产品(OpsxAPI / OpsxDB / OpsXShell)要分别买吗?</summary><p>三个产品独立授权、独立计价;它们共用同一套授权体系(同一个设备 ID 规则与同一个授权服务器)。多产品组合、团队或教育用途可邮件说明需求,给捆绑价。</p></details>
|
||||
</div>
|
||||
<p class="lic-note">本页更新于 2026-10-16,授权口径与授权服务器 <code>licence.rlwj.cn</code> 实际配置一致。</p>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="section">
|
||||
<div class="wrap lic-cta">
|
||||
<h2 class="sec-title">准备好购买授权</h2>
|
||||
<p class="sec-sub">邮件说明产品、授权类型与设备数,当天回复并开票。</p>
|
||||
<div class="btns">
|
||||
<a class="btn btn-primary" href="mailto:hello@opsxapi.dev?subject=OpsxAPI%20%E6%8E%88%E6%9D%83%E4%B8%8E%E6%BF%80%E6%B4%BB%E5%92%A8%E8%AF%A2">邮件咨询购买</a>
|
||||
<a class="btn btn-ghost" href="/#exchange">还没装?立即下载</a>
|
||||
</div>
|
||||
<p class="lic-note">付款后当场发放授权码(LIC-),粘贴到「设置 → 授权」即完成激活——整个流程无需注册账号。</p>
|
||||
</div>
|
||||
</section>
|
||||
</main>
|
||||
|
||||
<footer class="footer">
|
||||
<div class="wrap footer-grid">
|
||||
<div>
|
||||
<div class="brand"><span class="brand-logo"><svg width="30" height="30" viewBox="0 0 64 64" aria-label="OpsxAPI"><defs><linearGradient id="lg-site" x1="0" y1="0" x2="64" y2="64" gradientUnits="userSpaceOnUse"><stop offset="0" stop-color="#2563EB"/><stop offset="1" stop-color="#06B6D4"/></linearGradient></defs><rect width="64" height="64" rx="14" fill="url(#lg-site)"/><g stroke="#fff" stroke-width="5" stroke-linecap="round" stroke-linejoin="round" fill="none"><path d="M17 25h26"/><path d="M37 19l6 6-6 6"/><path d="M47 39H21"/><path d="M27 33l-6 6 6 6"/></g></svg></span><span class="brand-name">OpsxAPI</span></div>
|
||||
<p class="footer-desc">本地优先的全协议接口工作台。<br>数据在自己手里,才是真的效率工具。</p>
|
||||
</div>
|
||||
<div>
|
||||
<h4>产品</h4>
|
||||
<a href="/#features">功能特性</a>
|
||||
<a href="/#pricing">定价与让利</a>
|
||||
<a href="/#compare">工具对比</a>
|
||||
</div>
|
||||
<div>
|
||||
<h4>资源</h4>
|
||||
<a href="/pricing">定价</a>
|
||||
<a href="/license">授权与激活</a>
|
||||
<a href="/docs" target="_blank" rel="noopener">用户文档</a>
|
||||
<a href="/docs/faq" target="_blank" rel="noopener">常见问题</a>
|
||||
</div>
|
||||
<div>
|
||||
<h4>支持</h4>
|
||||
<a href="mailto:hello@opsxapi.dev">hello@opsxapi.dev</a>
|
||||
<a href="/docs/quickstart" target="_blank" rel="noopener">快速开始</a>
|
||||
<a href="/terms" target="_blank" rel="noopener">服务条款</a>
|
||||
</div>
|
||||
</div>
|
||||
<div class="footer-bar">© 2026 OpsxAPI · <a href="/terms" target="_blank" rel="noopener">服务条款</a> · <a href="/license">授权与激活</a></div>
|
||||
</footer>
|
||||
</body>
|
||||
</html>
|
||||
Vendored
+11
-8
@@ -53,15 +53,16 @@
|
||||
</svg>
|
||||
<header class="nav">
|
||||
<div class="nav-inner">
|
||||
<a class="brand" href="/home">
|
||||
<a class="brand" href="/">
|
||||
<span class="brand-logo"><svg width="30" height="30" viewBox="0 0 64 64" aria-label="OpsxAPI"><defs><linearGradient id="lg-site" x1="0" y1="0" x2="64" y2="64" gradientUnits="userSpaceOnUse"><stop offset="0" stop-color="#2563EB"/><stop offset="1" stop-color="#06B6D4"/></linearGradient></defs><rect width="64" height="64" rx="14" fill="url(#lg-site)"/><g stroke="#fff" stroke-width="5" stroke-linecap="round" stroke-linejoin="round" fill="none"><path d="M17 25h26"/><path d="M37 19l6 6-6 6"/><path d="M47 39H21"/><path d="M27 33l-6 6 6 6"/></g></svg></span>
|
||||
<span class="brand-name">OpsxAPI</span>
|
||||
</a>
|
||||
<nav class="nav-links">
|
||||
<a href="#features">功能</a>
|
||||
<a href="#protocols">协议</a>
|
||||
<a href="#exchange">导入导出</a>
|
||||
<a href="/#features">功能</a>
|
||||
<a href="/#protocols">协议</a>
|
||||
<a href="/#exchange">导入导出</a>
|
||||
<a href="/pricing">定价</a>
|
||||
<a href="/license">授权</a>
|
||||
<a href="/docs" target="_blank" rel="noopener">文档</a>
|
||||
</nav>
|
||||
<div class="nav-cta">
|
||||
@@ -173,12 +174,14 @@
|
||||
</div>
|
||||
<div>
|
||||
<h4>产品</h4>
|
||||
<a href="#features">功能特性</a>
|
||||
<a href="#pricing">定价与让利</a>
|
||||
<a href="#compare">工具对比</a>
|
||||
<a href="/#features">功能特性</a>
|
||||
<a href="/#pricing">定价与让利</a>
|
||||
<a href="/#compare">工具对比</a>
|
||||
</div>
|
||||
<div>
|
||||
<h4>资源</h4>
|
||||
<a href="/pricing">定价</a>
|
||||
<a href="/license">授权与激活</a>
|
||||
<a href="/docs" target="_blank" rel="noopener">用户文档</a>
|
||||
<a href="/docs/server-api" target="_blank" rel="noopener">服务端 API</a>
|
||||
<a href="/docs/faq" target="_blank" rel="noopener">常见问题</a>
|
||||
@@ -189,7 +192,7 @@
|
||||
<a href="/docs/quickstart" target="_blank" rel="noopener">快速开始</a>
|
||||
</div>
|
||||
</div>
|
||||
<div class="footer-bar">© 2026 OpsxAPI · <a href="/terms" target="_blank">服务条款</a> · 本页由单二进制内置提供(/home)</div>
|
||||
<div class="footer-bar">© 2026 OpsxAPI · <a href="/terms" target="_blank">服务条款</a> · <a href="/license">授权与激活</a></div>
|
||||
</footer>
|
||||
<script>
|
||||
document.querySelectorAll('.toggle').forEach(function(t){
|
||||
|
||||
Vendored
+211
@@ -0,0 +1,211 @@
|
||||
<!doctype html>
|
||||
<html lang="zh-CN">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<title>授权与定价 · OpsxAPI</title>
|
||||
<meta name="description" content="OpsxAPI 定价:30 天全功能试用;专业版 ¥99/年或 ¥199 永久,团队版 ¥99/人/年,企业私有化。支持在线授权与完全离线激活。">
|
||||
<link rel="icon" type="image/svg+xml" href="data:image/svg+xml,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20viewBox%3D%220%200%2064%2064%22%3E%3Cdefs%3E%3ClinearGradient%20id%3D%22g%22%20x1%3D%220%22%20y1%3D%220%22%20x2%3D%2264%22%20y2%3D%2264%22%20gradientUnits%3D%22userSpaceOnUse%22%3E%3Cstop%20offset%3D%220%22%20stop-color%3D%22%232563EB%22%2F%3E%3Cstop%20offset%3D%221%22%20stop-color%3D%22%2306B6D4%22%2F%3E%3C%2FlinearGradient%3E%3C%2Fdefs%3E%3Crect%20width%3D%2264%22%20height%3D%2264%22%20rx%3D%2214%22%20fill%3D%22url%28%23g%29%22%2F%3E%3Cg%20stroke%3D%22%23fff%22%20stroke-width%3D%225%22%20stroke-linecap%3D%22round%22%20stroke-linejoin%3D%22round%22%20fill%3D%22none%22%3E%3Cpath%20d%3D%22M17%2025h26%22%2F%3E%3Cpath%20d%3D%22M37%2019l6%206-6%206%22%2F%3E%3Cpath%20d%3D%22M47%2039H21%22%2F%3E%3Cpath%20d%3D%22M27%2033l-6%206%206%206%22%2F%3E%3C%2Fg%3E%3C%2Fsvg%3E">">
|
||||
<link rel="stylesheet" href="/site/assets/site.css">
|
||||
<style>
|
||||
.trial-hero{padding:72px 0 28px;text-align:center;}
|
||||
.trial-hero h1{font-size:clamp(28px,4.5vw,44px);margin:14px 0 10px;}
|
||||
.trial-hero .lead{max-width:720px;margin:0 auto;opacity:.75;line-height:1.8;}
|
||||
.trial-strip{display:inline-flex;gap:8px;align-items:center;padding:7px 16px;border-radius:999px;border:1px solid var(--accent,#2563eb);color:var(--accent,#2563eb);font-size:13px;font-weight:600;}
|
||||
.toggle{display:inline-flex;border:1px solid var(--border,#d8dee9);border-radius:999px;padding:3px;gap:2px;margin:14px auto 0;}
|
||||
.toggle button{border:0;background:transparent;padding:6px 18px;border-radius:999px;font-size:13px;font-weight:600;color:inherit;opacity:.65;cursor:pointer;}
|
||||
.toggle button.on{background:var(--accent,#2563eb);color:#fff;opacity:1;}
|
||||
.price-num b{font-size:34px;}
|
||||
.faq{max-width:820px;margin:0 auto;}
|
||||
.faq details{border-bottom:1px solid var(--border,#e5e9f0);padding:16px 4px;}
|
||||
.faq summary{cursor:pointer;font-weight:600;font-size:15px;list-style:none;display:flex;justify-content:space-between;align-items:center;}
|
||||
.faq summary::after{content:"+";font-size:18px;opacity:.5;}
|
||||
.faq details[open] summary::after{content:"–";}
|
||||
.faq details p{margin:10px 0 2px;opacity:.72;line-height:1.85;font-size:14px;}
|
||||
.indie{text-align:center;opacity:.7;font-size:14px;line-height:1.9;max-width:560px;margin:0 auto;}
|
||||
.act-grid{display:grid;grid-template-columns:repeat(auto-fit,minmax(240px,1fr));gap:14px;margin-top:22px;text-align:left;}
|
||||
.act-grid .card h3{display:flex;align-items:center;gap:8px;margin:0 0 6px;font-size:15px;}
|
||||
.act-grid .card p{margin:0;opacity:.7;font-size:13px;line-height:1.8;}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" style="display:none" aria-hidden="true">
|
||||
<symbol id="i-rocket" viewBox="0 0 24 24"><path d="M12 15v5s3.03-.55 4-2c1.08-1.62 0-5 0-5" /> <path d="M4.5 16.5c-1.5 1.26-2 5-2 5s3.74-.5 5-2c.71-.84.7-2.13-.09-2.91a2.18 2.18 0 0 0-2.91-.09" /> <path d="M9 12a22 22 0 0 1 2-3.95A12.88 12.88 0 0 1 22 2c0 2.72-.78 7.5-6 11a22.4 22.4 0 0 1-4 2z" /> <path d="M9 12H4s.55-3.03 2-4c1.62-1.08 5 .05 5 .05" /></symbol>
|
||||
<symbol id="i-shield-check" viewBox="0 0 24 24"><path d="M20 13c0 5-3.5 7.5-7.66 8.95a1 1 0 0 1-.67-.01C7.5 20.5 4 18 4 13V6a1 1 0 0 1 1-1c2 0 4.5-1.2 6.24-2.72a1.17 1.17 0 0 1 1.52 0C14.51 3.81 17 5 19 5a1 1 0 0 1 1 1z" /> <path d="m9 12 2 2 4-4" /></symbol>
|
||||
<symbol id="i-arrow-left-right" viewBox="0 0 24 24"><path d="M8 3 4 7l4 4" /> <path d="M4 7h16" /> <path d="m16 21 4-4-4-4" /> <path d="M20 17H4" /></symbol>
|
||||
<symbol id="i-plug-zap" viewBox="0 0 24 24"><path d="M6.3 20.3a2.4 2.4 0 0 0 3.4 0L12 18l-6-6-2.3 2.3a2.4 2.4 0 0 0 0 3.4Z" /> <path d="m2 22 3-3" /> <path d="M7.5 13.5 10 11" /> <path d="M10.5 16.5 13 14" /> <path d="m18 3-4 4h6l-4 4" /></symbol>
|
||||
<symbol id="i-package" viewBox="0 0 24 24"><path d="M11 21.73a2 2 0 0 0 2 0l7-4A2 2 0 0 0 21 16V8a2 2 0 0 0-1-1.73l-7-4a2 2 0 0 0-2 0l-7 4A2 2 0 0 0 3 8v8a2 2 0 0 0 1 1.73z" /> <path d="M12 22V12" /> <polyline points="3.29 7 12 12 20.71 7" /> <path d="m7.5 4.27 9 5.15" /></symbol>
|
||||
<symbol id="i-folder-tree" viewBox="0 0 24 24"><path d="M20 10a1 1 0 0 0 1-1V6a1 1 0 0 0-1-1h-2.5a1 1 0 0 1-.8-.4l-.9-1.2A1 1 0 0 0 15 3h-2a1 1 0 0 0-1 1v5a1 1 0 0 0 1 1Z" /> <path d="M20 21a1 1 0 0 0 1-1v-3a1 1 0 0 0-1-1h-2.9a1 1 0 0 1-.88-.55l-.42-.85a1 1 0 0 0-.92-.6H13a1 1 0 0 0-1 1v5a1 1 0 0 0 1 1Z" /> <path d="M3 5a2 2 0 0 0 2 2h3" /> <path d="M3 3v13a2 2 0 0 0 2 2h3" /></symbol>
|
||||
<symbol id="i-globe" viewBox="0 0 24 24"><circle cx="12" cy="12" r="10" /> <path d="M12 2a14.5 14.5 0 0 0 0 20 14.5 14.5 0 0 0 0-20" /> <path d="M2 12h20" /></symbol>
|
||||
<symbol id="i-hexagon" viewBox="0 0 24 24"><path d="M21 16V8a2 2 0 0 0-1-1.73l-7-4a2 2 0 0 0-2 0l-7 4A2 2 0 0 0 3 8v8a2 2 0 0 0 1 1.73l7 4a2 2 0 0 0 2 0l7-4A2 2 0 0 0 21 16z" /></symbol>
|
||||
<symbol id="i-zap" viewBox="0 0 24 24"><path d="M15.914 4a1.5 1.5 0 00-2.474-1.561l-9 9A1.5 1.5 0 005.5 14h4.002a.5.5 0 01.471.666L8.086 20a1.5 1.5 0 002.475 1.56l9-9A1.5 1.5 0 0018.5 10h-3.997a.5.5 0 01-.472-.667z" /></symbol>
|
||||
<symbol id="i-radio" viewBox="0 0 24 24"><path d="M16.247 7.761a6 6 0 0 1 0 8.478" /> <path d="M19.075 4.933a10 10 0 0 1 0 14.134" /> <path d="M4.925 19.067a10 10 0 0 1 0-14.134" /> <path d="M7.753 16.239a6 6 0 0 1 0-8.478" /> <circle cx="12" cy="12" r="2" /></symbol>
|
||||
<symbol id="i-mailbox" viewBox="0 0 24 24"><path d="M22 17a2 2 0 0 1-2 2H4a2 2 0 0 1-2-2V9.5C2 7 4 5 6.5 5H18c2.2 0 4 1.8 4 4v8Z" /> <polyline points="15,9 18,9 18,11" /> <path d="M6.5 5C9 5 11 7 11 9.5V17a2 2 0 0 1-2 2" /> <line x1="6" x2="7" y1="10" y2="10" /></symbol>
|
||||
<symbol id="i-code" viewBox="0 0 24 24"><path d="m16 18 6-6-6-6" /> <path d="m8 6-6 6 6 6" /></symbol>
|
||||
<symbol id="i-book-open" viewBox="0 0 24 24"><path d="M12 5v16" /> <path d="M20.001 19A2 2 0 0022 17V5a2 2 0 00-1.999-2L16 3.002A5 5 0 0012 5a5 5 0 00-4-2H4a2 2 0 00-2 2v12a2 2 0 001.999 2H8a5 5 0 014 2 5 5 0 014-2z" /></symbol>
|
||||
<symbol id="i-sprout" viewBox="0 0 24 24"><path d="M14 9.536V7a4 4 0 0 1 4-4h1.5a.5.5 0 0 1 .5.5V5a4 4 0 0 1-4 4 4 4 0 0 0-4 4c0 2 1 3 1 5a5 5 0 0 1-1 3" /> <path d="M4 9a5 5 0 0 1 8 4 5 5 0 0 1-8-4" /> <path d="M5 21h14" /></symbol>
|
||||
<symbol id="i-graduation-cap" viewBox="0 0 24 24"><path d="M21.42 10.922a1 1 0 0 0-.019-1.838L12.83 5.18a2 2 0 0 0-1.66 0L2.6 9.08a1 1 0 0 0 0 1.832l8.57 3.908a2 2 0 0 0 1.66 0z" /> <path d="M22 10v6" /> <path d="M6 12.5V16a6 3 0 0 0 12 0v-3.5" /></symbol>
|
||||
<symbol id="i-credit-card" viewBox="0 0 24 24"><rect width="20" height="14" x="2" y="5" rx="2" /> <line x1="2" x2="22" y1="10" y2="10" /> <path d="M6 14h2" /></symbol>
|
||||
<symbol id="i-megaphone" viewBox="0 0 24 24"><path d="M11 6a13 13 0 0 0 8.4-2.8A1 1 0 0 1 21 4v12a1 1 0 0 1-1.6.8A13 13 0 0 0 11 14H5a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2z" /> <path d="M6 14a12 12 0 0 0 2.4 7.2 2 2 0 0 0 3.2-2.4A8 8 0 0 1 10 14" /> <path d="M8 6v8" /></symbol>
|
||||
<symbol id="i-download" viewBox="0 0 24 24"><path d="M12 15V3" /> <path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4" /> <path d="m7 10 5 5 5-5" /></symbol>
|
||||
<symbol id="i-upload" viewBox="0 0 24 24"><path d="M12 3v12" /> <path d="m17 8-5-5-5 5" /> <path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4" /></symbol>
|
||||
<symbol id="i-library" viewBox="0 0 24 24"><path d="m16 6 4 14" /> <path d="M12 6v14" /> <path d="M8 8v12" /> <path d="M4 4v16" /></symbol>
|
||||
</svg>
|
||||
<header class="nav">
|
||||
<div class="nav-inner">
|
||||
<a class="brand" href="/">
|
||||
<span class="brand-logo"><svg width="30" height="30" viewBox="0 0 64 64" aria-label="OpsxAPI"><defs><linearGradient id="lg-site" x1="0" y1="0" x2="64" y2="64" gradientUnits="userSpaceOnUse"><stop offset="0" stop-color="#2563EB"/><stop offset="1" stop-color="#06B6D4"/></linearGradient></defs><rect width="64" height="64" rx="14" fill="url(#lg-site)"/><g stroke="#fff" stroke-width="5" stroke-linecap="round" stroke-linejoin="round" fill="none"><path d="M17 25h26"/><path d="M37 19l6 6-6 6"/><path d="M47 39H21"/><path d="M27 33l-6 6 6 6"/></g></svg></span>
|
||||
<span class="brand-name">OpsxAPI</span>
|
||||
</a>
|
||||
<nav class="nav-links">
|
||||
<a href="/#features">功能</a>
|
||||
<a href="/#protocols">协议</a>
|
||||
<a href="/#exchange">导入导出</a>
|
||||
<a href="/pricing">定价</a>
|
||||
<a href="/license">授权</a>
|
||||
<a href="/docs" target="_blank" rel="noopener">文档</a>
|
||||
</nav>
|
||||
<div class="nav-cta">
|
||||
<a class="btn btn-primary" href="/docs/quickstart" target="_blank" rel="noopener">快速上手 ↗</a>
|
||||
</div>
|
||||
</div>
|
||||
</header>
|
||||
<main>
|
||||
<section class="trial-hero">
|
||||
<span class="trial-strip"><svg class="i"><use href="#i-rocket"/></svg> 30 天免费试用 · 首次启动自动开始 · 无需注册</span>
|
||||
<h1>授权与定价</h1>
|
||||
<p class="lead">OpsxAPI 对个人非商业用途永久免费;在工作中商用需要授权。授权绑定设备而非账号,支持在线激活与完全离线激活两种方式。</p>
|
||||
</section>
|
||||
|
||||
<section class="section" style="padding-top:26px">
|
||||
<div class="wrap">
|
||||
<h2 class="sec-title">先试用,再决定</h2>
|
||||
<p class="sec-sub">下载即含 30 天全功能试用;激活支持在线与离线两种方式。</p>
|
||||
<div class="act-grid">
|
||||
<div class="card"><h3><svg class="i"><use href="#i-rocket"/></svg>30 天全功能试用</h3><p>首次启动自动开始;无需注册、无需信用卡。试用结束后个人非商业用途仍可继续使用。</p></div>
|
||||
<div class="card"><h3><svg class="i"><use href="#i-globe"/></svg>在线授权 LIC-</h3><p>联网激活并绑定设备;支持自助解绑换机;激活后可连续离线使用 30 天。</p></div>
|
||||
<div class="card"><h3><svg class="i"><use href="#i-shield-check"/></svg>离线许可证 LICOFF-</h3><p>Ed25519 自包含签名,绑定设备;完全无网络也能激活,适合隔离内网环境。</p></div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="section alt">
|
||||
<div class="wrap">
|
||||
<h2 class="sec-title">选择适合的授权</h2>
|
||||
<p class="sec-sub">个人版可选年付或一次性买断;团队按席位,私有化不限席位。</p>
|
||||
<div class="pricing three">
|
||||
<div class="price-card">
|
||||
<div class="plan">专业版</div>
|
||||
<div class="toggle" data-mode="year"><button class="on" data-mode="year">¥99 / 年</button><button data-mode="life">永久 ¥199</button></div>
|
||||
<div class="price price-num"><b data-price="year">¥99</b><b data-price="life" style="display:none">¥199</b><span>/ 年</span></div>
|
||||
<div class="plan-sub">单人不分设备多少 · 最多 3 台设备同时激活</div>
|
||||
<ul>
|
||||
<li>六种协议全部调试能力</li>
|
||||
<li>集合 / 环境 / 历史无限量</li>
|
||||
<li>全格式导入导出 + 代码生成</li>
|
||||
<li>本机 Mock 服务</li>
|
||||
<li>设备可自助解绑迁移</li>
|
||||
<li>1 年免费升级<span data-price="life" style="display:none">(永久版含后续更新)</span></li>
|
||||
</ul>
|
||||
<a class="btn btn-primary" href="mailto:hello@opsxapi.dev?subject=OpsxAPI%20%E4%B8%AA%E4%BA%BA%E7%89%88%E8%B4%AD%E4%B9%B0">购买咨询</a>
|
||||
</div>
|
||||
<div class="price-card featured">
|
||||
<div class="plan-tag">高性价比之选</div>
|
||||
<div class="plan">团队版</div>
|
||||
<div class="price price-num"><b>¥99</b><span>/ 人 / 年</span></div>
|
||||
<div class="plan-sub">按席位授权 · 5 席起 · 组织内可转移</div>
|
||||
<ul>
|
||||
<li>个人版全部能力</li>
|
||||
<li>集合批量导入分发</li>
|
||||
<li>团队共享空间(自托管)</li>
|
||||
<li>内网自托管(单二进制)</li>
|
||||
<li>优先技术支持</li>
|
||||
<li>1 年免费升级</li>
|
||||
</ul>
|
||||
<a class="btn btn-primary" href="mailto:hello@opsxapi.dev?subject=OpsxAPI%20%E5%9B%A2%E9%98%9F%E7%89%88%E8%B4%AD%E4%B9%B0">联系商务</a>
|
||||
</div>
|
||||
<div class="price-card enterprise">
|
||||
<div class="plan-tag dark">私有化 · 不限席位</div>
|
||||
<div class="plan">企业私有化版</div>
|
||||
<div class="price price-num"><b>¥8,000</b><span>/ 年 · 标价</span></div>
|
||||
<div class="plan-sub">或 ¥30,000 一次性买断 · 单法人不限席位</div>
|
||||
<ul>
|
||||
<li>整机 / 内网部署,数据不出企业</li>
|
||||
<li>团队版全部能力</li>
|
||||
<li>远程部署协助 + 专属支持</li>
|
||||
<li>定制开发通道(另行报价)</li>
|
||||
<li>买断可选:永久使用 + 1 年升级</li>
|
||||
</ul>
|
||||
<a class="btn btn-dark" href="mailto:hello@opsxapi.dev?subject=OpsxAPI%20%E7%A7%81%E6%9C%89%E5%8C%96%E8%B4%AD%E4%B9%B0">获取报价</a>
|
||||
</div>
|
||||
</div>
|
||||
<p class="deal-note" style="text-align:center">支付后当场发放授权码(LIC-),粘贴到「设置 → 授权」即完成激活——整个流程无需注册账号;许可证一机一码绑定设备,每年可免费换绑 2 次。<br>标价透明,成交灵活:早鸟 / 教育 / 多年预付可议价,常见成交区间见 <a href="/#pricing">首页定价区</a>。</p>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="section">
|
||||
<div class="wrap">
|
||||
<h2 class="sec-title">常见问题</h2>
|
||||
<div class="faq">
|
||||
<details><summary>试用是怎么计算的?</summary><p>30 天全功能试用,从应用首次启动那一刻自动开始——无需注册账号,无需信用卡。试用期结束后,个人非商业用途可继续使用;在工作中的商业用途需要购买授权。</p></details>
|
||||
<details><summary>在线授权和离线激活有什么区别?</summary><p>在线授权码(LIC- 前缀):联网激活,绑定设备,支持在设置中自助解绑换机;激活后可连续离线使用最长 30 天。离线许可证(LICOFF- 前缀):同样绑定设备,但采用 Ed25519 自包含签名,在完全无网络的环境也能激活——适合隔离内网。两种方式在「设置 → 授权」中粘贴即用。</p></details>
|
||||
<details><summary>一个授权能用几台设备?</summary><p>授权绑定设备而非订阅账号。个人版最多 3 台设备;团队版按席位授权、组织内可转移。设备可以在「设置 → 授权」中解绑后迁移到新机器。</p></details>
|
||||
<details><summary>断网了授权会失效吗?</summary><p>离线许可证(LICOFF-)本地自验,永不联网也有效。在线授权(LIC-)容忍连续 30 天离线,超过后需联网重新验证一次。</p></details>
|
||||
<details><summary>退款政策是怎样的?</summary><p>自付款之日起 30 天内无理由全额退款——先用起来,不满意直接退。</p></details>
|
||||
<details><summary>怎么购买?</summary><p>邮件咨询购买,当天回复;支持对公转账与开票。团队与私有化支持按规模议价。</p></details>
|
||||
<details><summary>三个产品(OpsxAPI / OpsxDB / OpsXShell)要分别买吗?</summary><p>三个产品独立授权、独立计价。多产品组合或家庭/学生优惠可直接邮件说明需求,给捆绑价。</p></details>
|
||||
<details><summary>授权包含哪些升级?</summary><p>年付授权含 1 年免费升级;终身授权包含发布版本与后续一年内的全部更新。</p></details>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="section alt">
|
||||
<div class="wrap indie">
|
||||
<p><b>独立开发,认真长跑。</b><br>Opsx 系列工具由独立开发者打造,每一份授权的收入都直接投入产品迭代——没有投资人,也没有广告。</p>
|
||||
<p style="margin-top:14px"><a class="btn btn-primary" href="/#exchange">还没装?立即下载</a></p>
|
||||
</div>
|
||||
</section>
|
||||
</main>
|
||||
<footer class="footer">
|
||||
<div class="wrap footer-grid">
|
||||
<div>
|
||||
<div class="brand"><span class="brand-logo"><svg width="30" height="30" viewBox="0 0 64 64" aria-label="OpsxAPI"><defs><linearGradient id="lg-site" x1="0" y1="0" x2="64" y2="64" gradientUnits="userSpaceOnUse"><stop offset="0" stop-color="#2563EB"/><stop offset="1" stop-color="#06B6D4"/></linearGradient></defs><rect width="64" height="64" rx="14" fill="url(#lg-site)"/><g stroke="#fff" stroke-width="5" stroke-linecap="round" stroke-linejoin="round" fill="none"><path d="M17 25h26"/><path d="M37 19l6 6-6 6"/><path d="M47 39H21"/><path d="M27 33l-6 6 6 6"/></g></svg></span><span class="brand-name">OpsxAPI</span></div>
|
||||
<p class="footer-desc">本地优先的全协议接口工作台。<br>数据在自己手里,才是真的效率工具。</p>
|
||||
</div>
|
||||
<div>
|
||||
<h4>产品</h4>
|
||||
<a href="/#features">功能特性</a>
|
||||
<a href="/#pricing">定价与让利</a>
|
||||
<a href="/#compare">工具对比</a>
|
||||
</div>
|
||||
<div>
|
||||
<h4>资源</h4>
|
||||
<a href="/pricing">定价</a>
|
||||
<a href="/license">授权与激活</a>
|
||||
<a href="/docs" target="_blank" rel="noopener">用户文档</a>
|
||||
<a href="/docs/server-api" target="_blank" rel="noopener">服务端 API</a>
|
||||
<a href="/docs/faq" target="_blank" rel="noopener">常见问题</a>
|
||||
</div>
|
||||
<div>
|
||||
<h4>支持</h4>
|
||||
<a href="mailto:hello@opsxapi.dev">hello@opsxapi.dev</a>
|
||||
<a href="/docs/quickstart" target="_blank" rel="noopener">快速开始</a>
|
||||
</div>
|
||||
</div>
|
||||
<div class="footer-bar">© 2026 OpsxAPI · <a href="/terms" target="_blank">服务条款</a> · <a href="/license">授权与激活</a></div>
|
||||
</footer>
|
||||
<script>
|
||||
document.querySelectorAll('.toggle').forEach(function(t){
|
||||
t.addEventListener('click', function(e){
|
||||
if(e.target.tagName!=='BUTTON')return;
|
||||
t.querySelectorAll('button').forEach(function(b){b.classList.remove('on')});
|
||||
e.target.classList.add('on');
|
||||
t.dataset.mode = e.target.dataset.mode;
|
||||
t.closest('.price-card').querySelectorAll('[data-price]').forEach(function(el){
|
||||
el.style.display = el.dataset.price===t.dataset.mode ? '' : 'none';
|
||||
});
|
||||
});
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
File renamed without changes.
File renamed without changes.
File renamed without changes.
@@ -44,11 +44,12 @@
|
||||
<a href="#features">功能</a>
|
||||
<a href="#protocols">协议</a>
|
||||
<a href="#exchange">导入导出</a>
|
||||
<a href="#pricing">定价</a>
|
||||
<a href="/pricing">定价</a>
|
||||
<a href="/license">授权</a>
|
||||
<a href="/docs" target="_blank" rel="noopener">文档</a>
|
||||
</nav>
|
||||
<div class="nav-cta">
|
||||
<a class="btn btn-primary" href="/" target="_blank" rel="noopener">打开工作台 ↗</a>
|
||||
<a class="btn btn-primary" href="/docs/quickstart" target="_blank" rel="noopener">快速上手 ↗</a>
|
||||
</div>
|
||||
</div>
|
||||
</header>
|
||||
@@ -57,18 +58,18 @@
|
||||
<section class="hero">
|
||||
<div class="hero-inner">
|
||||
<div class="hero-copy">
|
||||
<div class="hero-badge"><svg class="i"><use href="#i-rocket"/></svg> v1.0 正式发布 · 数据 100% 本地</div>
|
||||
<div class="hero-badge"><svg class="i"><use href="#i-rocket"/></svg> v0.1 公测 · 数据 100% 本地</div>
|
||||
<h1>一个工作台,<br>调完<em>六种协议</em>的接口</h1>
|
||||
<p class="hero-sub">
|
||||
HTTP · GraphQL · WebSocket · SSE · Socket.IO · MQTT。<br>
|
||||
集合管理、多环境变量、全格式导入导出、代码生成、内置文档站 —— 运维与开发的自托管接口利器。
|
||||
</p>
|
||||
<div class="hero-actions">
|
||||
<a class="btn btn-primary btn-lg" href="/" target="_blank" rel="noopener">立即开始(免费体验)</a>
|
||||
<a class="btn btn-primary btn-lg" href="/docs/quickstart" target="_blank" rel="noopener">立即开始(免费体验)</a>
|
||||
<a class="btn btn-ghost btn-lg" href="/docs/quickstart" target="_blank" rel="noopener">快速开始 →</a>
|
||||
</div>
|
||||
<div class="hero-pills">
|
||||
<span>本地优先</span><span>自定义 WS 握手头</span><span>SSE 断点续传</span><span>OpenAPI 双向同步</span>
|
||||
<span>本地优先</span><span>自定义 WS 握手头</span><span>SSE 断点续传</span><span>本机 Mock 服务</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -227,14 +228,14 @@
|
||||
<p class="sec-sub">先在工作台免费用起来,需要授权与服务时再付费。</p>
|
||||
<div class="pricing three">
|
||||
<div class="price-card">
|
||||
<div class="plan">个人版</div>
|
||||
<div class="plan">专业版</div>
|
||||
<div class="price"><b>¥99</b><span>/ 年</span></div>
|
||||
<div class="plan-sub">单设备授权 · 全功能</div>
|
||||
<ul>
|
||||
<li>✓ 六种协议全部调试能力</li>
|
||||
<li>✓ 集合 / 环境 / 历史无限量</li>
|
||||
<li>✓ 全格式导入导出 + 代码生成</li>
|
||||
<li>✓ 桌面端 + Web 双形态</li>
|
||||
<li>✓ 桌面端单机全功能</li>
|
||||
<li>✓ 1 年免费升级</li>
|
||||
</ul>
|
||||
<a class="btn btn-primary" href="#contact">购买咨询</a>
|
||||
@@ -317,9 +318,9 @@
|
||||
<section class="cta" id="contact">
|
||||
<div class="wrap">
|
||||
<h2>把接口调试收回自己手里</h2>
|
||||
<p>现在打开工作台,30 秒发出第一个请求;采购事宜邮件联系,当天回复。</p>
|
||||
<p>按快速上手启动工作台,30 秒发出第一个请求;采购事宜邮件联系,当天回复。</p>
|
||||
<div class="hero-actions center">
|
||||
<a class="btn btn-inverse btn-lg" href="/" target="_blank" rel="noopener">打开工作台 ↗</a>
|
||||
<a class="btn btn-inverse btn-lg" href="/docs/quickstart" target="_blank" rel="noopener">快速上手 ↗</a>
|
||||
<a class="btn btn-outline btn-lg" href="mailto:hello@opsxapi.dev?subject=OpsxAPI%20购买咨询">hello@opsxapi.dev</a>
|
||||
</div>
|
||||
</div>
|
||||
@@ -0,0 +1,211 @@
|
||||
<!doctype html>
|
||||
<html lang="zh-CN">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<title>授权与激活 · OpsxAPI</title>
|
||||
<meta name="description" content="OpsxAPI 授权与激活说明:付款后邮件交付授权码(LIC-),在「设置 → 授权」粘贴即完成激活;生产内网可用 LICOFF- 离线许可证,一机一码、支持自助解绑换机。">
|
||||
<link rel="icon" type="image/svg+xml" href="data:image/svg+xml,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20viewBox%3D%220%200%2064%2064%22%3E%3Cdefs%3E%3ClinearGradient%20id%3D%22g%22%20x1%3D%220%22%20y1%3D%220%22%20x2%3D%2264%22%20y2%3D%2264%22%20gradientUnits%3D%22userSpaceOnUse%22%3E%3Cstop%20offset%3D%220%22%20stop-color%3D%22%232563EB%22%2F%3E%3Cstop%20offset%3D%221%22%20stop-color%3D%22%2306B6D4%22%2F%3E%3C%2FlinearGradient%3E%3C%2Fdefs%3E%3Crect%20width%3D%2264%22%20height%3D%2264%22%20rx%3D%2214%22%20fill%3D%22url%28%23g%29%22%2F%3E%3Cg%20stroke%3D%22%23fff%22%20stroke-width%3D%225%22%20stroke-linecap%3D%22round%22%20stroke-linejoin%3D%22round%22%20fill%3D%22none%22%3E%3Cpath%20d%3D%22M17%2025h26%22%2F%3E%3Cpath%20d%3D%22M37%2019l6%206-6%206%22%2F%3E%3Cpath%20d%3D%22M47%2039H21%22%2F%3E%3Cpath%20d%3D%22M27%2033l-6%206%206%206%22%2F%3E%3C%2Fg%3E%3C%2Fsvg%3E">
|
||||
<link rel="stylesheet" href="/site/assets/site.css">
|
||||
<style>
|
||||
/* 授权页专用样式(lic- 前缀,避免与站点既有类冲突) // Logan.Li */
|
||||
.lic-hero{padding:72px 0 30px;text-align:center;}
|
||||
.lic-hero h1{font-size:clamp(28px,4.5vw,42px);margin:12px 0 10px;}
|
||||
.lic-hero .lead{max-width:780px;margin:0 auto;font-size:15px;line-height:1.9;opacity:.75;}
|
||||
.lic-chip{display:inline-flex;align-items:center;gap:8px;padding:7px 16px;border-radius:999px;border:1px solid var(--blue,#2563eb);color:var(--blue,#2563eb);font-size:13px;font-weight:600;}
|
||||
.lic-steps{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:22px 16px;margin:22px 0 0;}
|
||||
@media (max-width:720px){.lic-steps{grid-template-columns:1fr;}}
|
||||
.lic-step{position:relative;border:1px solid var(--border);border-radius:calc(var(--radius,14px) + 2px);padding:22px 18px 18px;background:var(--bg);}
|
||||
.lic-step .n{position:absolute;top:-11px;left:18px;background:var(--blue,#2563eb);color:#fff;font-size:11.5px;font-weight:700;padding:2px 10px;border-radius:999px;letter-spacing:.03em;}
|
||||
.lic-step h3{margin:4px 0 8px;font-size:15.5px;}
|
||||
.lic-step p{margin:0;font-size:13.5px;line-height:1.9;opacity:.78;}
|
||||
.lic-cmp-wrap{overflow-x:auto;margin-top:20px;}
|
||||
.lic-cmp{width:100%;min-width:640px;border-collapse:collapse;font-size:14px;background:var(--bg);}
|
||||
.lic-cmp th,.lic-cmp td{border-bottom:1px solid var(--border);padding:12px;text-align:left;vertical-align:top;}
|
||||
.lic-cmp thead th{font-size:13.5px;letter-spacing:.02em;}
|
||||
.lic-cmp tbody th{width:132px;font-weight:600;color:var(--text-3,#98a2b3);}
|
||||
.lic-cmp code{font-family:var(--mono,monospace);font-size:12.5px;background:rgba(127,127,127,.14);padding:1px 5px;border-radius:4px;}
|
||||
.lic-faq{max-width:880px;margin:0 auto;}
|
||||
.lic-faq details{border-bottom:1px solid var(--border);padding:15px 4px;}
|
||||
.lic-faq summary{cursor:pointer;font-weight:600;font-size:15px;list-style:none;display:flex;justify-content:space-between;gap:12px;align-items:center;}
|
||||
.lic-faq summary::after{content:"+";opacity:.5;font-size:18px;line-height:1;}
|
||||
.lic-faq details[open] summary::after{content:"–";}
|
||||
.lic-faq details p{margin:10px 0 2px;font-size:14px;line-height:1.9;opacity:.78;}
|
||||
.lic-note{max-width:880px;margin:16px auto 0;font-size:13px;line-height:1.9;opacity:.68;}
|
||||
.lic-cta{text-align:center;max-width:680px;margin:0 auto;}
|
||||
.lic-cta .btns{display:flex;gap:12px;justify-content:center;flex-wrap:wrap;margin-top:20px;}
|
||||
.lic-scope{max-width:880px;margin:24px auto 0;font-size:13.5px;line-height:1.95;opacity:.78;}
|
||||
.lic-scope li{margin:6px 0;}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<header class="nav">
|
||||
<div class="nav-inner">
|
||||
<a class="brand" href="/home">
|
||||
<span class="brand-logo"><svg width="30" height="30" viewBox="0 0 64 64" aria-label="OpsxAPI"><defs><linearGradient id="lg-site" x1="0" y1="0" x2="64" y2="64" gradientUnits="userSpaceOnUse"><stop offset="0" stop-color="#2563EB"/><stop offset="1" stop-color="#06B6D4"/></linearGradient></defs><rect width="64" height="64" rx="14" fill="url(#lg-site)"/><g stroke="#fff" stroke-width="5" stroke-linecap="round" stroke-linejoin="round" fill="none"><path d="M17 25h26"/><path d="M37 19l6 6-6 6"/><path d="M47 39H21"/><path d="M27 33l-6 6 6 6"/></g></svg></span>
|
||||
<span class="brand-name">OpsxAPI</span>
|
||||
</a>
|
||||
<nav class="nav-links">
|
||||
<a href="/#features">功能</a>
|
||||
<a href="/#protocols">协议</a>
|
||||
<a href="/#exchange">导入导出</a>
|
||||
<a href="/pricing">定价</a>
|
||||
<a href="/license">授权</a>
|
||||
<a href="/docs" target="_blank" rel="noopener">文档</a>
|
||||
</nav>
|
||||
<div class="nav-cta">
|
||||
<a class="btn btn-primary" href="/docs/quickstart" target="_blank" rel="noopener">快速上手 ↗</a>
|
||||
</div>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<main>
|
||||
<section class="lic-hero">
|
||||
<span class="lic-chip">30 天免费试用 · 首次启动自动开始 · 无需注册</span>
|
||||
<h1>授权与激活</h1>
|
||||
<p class="lead">一次说清四件事:怎么买、授权码怎么交付、程序里怎么激活、换机与断网怎么办。授权绑定设备而非账号,全流程不需要注册。</p>
|
||||
</section>
|
||||
|
||||
<section class="section">
|
||||
<div class="wrap">
|
||||
<h2 class="sec-title">交付与激活:四步</h2>
|
||||
<p class="sec-sub">从下单到激活通常当天完成,最短几分钟。</p>
|
||||
<div class="lic-steps">
|
||||
<div class="lic-step">
|
||||
<span class="n">STEP 1</span>
|
||||
<h3>下单确认</h3>
|
||||
<p>邮件说明产品、授权类型(包年 / 永久 / 团队席位 / 私有化)与设备数量。支持对公转账与开具发票,工作日当天回复。</p>
|
||||
</div>
|
||||
<div class="lic-step">
|
||||
<span class="n">STEP 2</span>
|
||||
<h3>邮件交付授权码</h3>
|
||||
<p>付款确认后即刻签发,授权码以邮件送达你的邮箱:联网用 <code>LIC-</code> 前缀的在线授权码,隔离内网用 <code>LICOFF-</code> 前缀的离线许可证。</p>
|
||||
</div>
|
||||
<div class="lic-step">
|
||||
<span class="n">STEP 3</span>
|
||||
<h3>应用内激活</h3>
|
||||
<p>打开「设置 → 授权」,粘贴授权码点击激活。程序连接授权服务器 <code>licence.rlwj.cn</code> 登记本机设备并回写一次性激活令牌,此后每 7 天自动校验一次。</p>
|
||||
</div>
|
||||
<div class="lic-step">
|
||||
<span class="n">STEP 4</span>
|
||||
<h3>换机与续期</h3>
|
||||
<p>在本机自助解绑即可把席位迁到新机器,每年免费换绑 2 次;到期前 30 天邮件提醒,续期后签发新授权码替换即可。</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="section alt">
|
||||
<div class="wrap">
|
||||
<h2 class="sec-title">两种激活方式</h2>
|
||||
<p class="sec-sub">能联网就用在线授权码,完全不出网就用离线许可证——功能与有效期一致。</p>
|
||||
<div class="lic-cmp-wrap">
|
||||
<table class="lic-cmp">
|
||||
<thead>
|
||||
<tr><th>对比项</th><th>在线授权码 <code>LIC-</code></th><th>离线许可证 <code>LICOFF-</code></th></tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr><th>是否联网</th><td>激活时联网一次,之后每 7 天校验一次</td><td>全程无需联网,本地验签</td></tr>
|
||||
<tr><th>设备绑定</th><td>绑定设备 ID,一机一码</td><td>绑定设备 ID,一机一码</td></tr>
|
||||
<tr><th>断网可用</th><td>激活后可连续离线使用 30 天</td><td>始终有效,与网络无关</td></tr>
|
||||
<tr><th>有效期</th><td>包年 365 天;永久授权长期有效</td><td>单张最长 365 天,可续签</td></tr>
|
||||
<tr><th>换绑</th><td>应用内自助解绑,每年免费换绑 2 次</td><td>邮件换取新证并解绑旧设备</td></tr>
|
||||
<tr><th>适合场景</th><td>办公网、开发机、云主机</td><td>生产内网、专有云、隔离环境</td></tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<div class="lic-scope">
|
||||
<p><b>授权服务器会收到什么?</b>客户端只与 <code>licence.rlwj.cn</code>(443)通信,用于设备登记与周期校验,只发送授权码与设备标识(设备 ID、设备名称)——不发送接口内容、请求/响应报文、环境变量与集合数据等任何业务数据。接口数据本机存储,全流程不上云。服务端返回 Ed25519 签名,客户端本地验签,验签失败按未授权处理。</p>
|
||||
<ul>
|
||||
<li>内网只放行白名单出网:把 <code>licence.rlwj.cn</code> 加入白名单,或直接使用离线许可证。</li>
|
||||
<li>授权服务器不可达:按宽限期继续可用,业务不会当场中断;恢复网络后自动补校验。</li>
|
||||
<li>不想让客户端出网:一律选离线许可证,客户端永不发起网络请求。</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="section">
|
||||
<div class="wrap">
|
||||
<h2 class="sec-title">授权类型与价格</h2>
|
||||
<p class="sec-sub">个人非商业用途免费;工作中的商业用途需要授权。</p>
|
||||
<div class="lic-cmp-wrap">
|
||||
<table class="lic-cmp">
|
||||
<thead>
|
||||
<tr><th>授权</th><th>价格</th><th>设备数</th><th>包含</th></tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr><th>个人 · 非商业</th><td>¥0</td><td>不涉及授权</td><td>六种协议调试、集合与环境本地保存;商业用途需购买授权</td></tr>
|
||||
<tr><th>专业版 · 包年</th><td>¥99 / 年</td><td>3 台</td><td>全协议调试能力、集合/环境/历史无限量、全格式导入导出与代码生成、本机 Mock、设备自助解绑</td></tr>
|
||||
<tr><th>专业版 · 永久</th><td>¥199 一次性</td><td>3 台</td><td>包年全部能力,一次买断、无续费,含发布版本与一年内更新</td></tr>
|
||||
<tr><th>团队版</th><td>¥99 / 人 / 年</td><td>按席位,5 席起</td><td>个人版全部能力 + 集合批量导入分发、团队共享空间(自托管)、优先技术支持</td></tr>
|
||||
<tr><th>企业私有化</th><td>¥8,000 / 年(标价)</td><td>单法人不限席位</td><td>整机 / 内网部署、数据不出企业、远程部署协助、定制开发通道;可选 ¥30,000 一次性买断</td></tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<p class="lic-note">完整功能对比、让利与组合报价见 <a href="/pricing">定价页</a>。表中为标价,成交可按规模与场景议价。</p>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="section alt">
|
||||
<div class="wrap">
|
||||
<h2 class="sec-title">授权常见问题</h2>
|
||||
<div class="lic-faq">
|
||||
<details><summary>授权码多久能收到?</summary><p>付款确认后即刻签发并邮件发出,工作日通常当天完成(含开票需求)。邮件里就是完整授权码,无需注册账号或登录任何后台。</p></details>
|
||||
<details><summary>授权绑定设备还是账号?</summary><p>绑定设备。每台机器有唯一设备 ID,一个授权码按授权的设备数占座,与账号无关;团队版按席位签发多个授权码,组织内可转移。</p></details>
|
||||
<details><summary>换电脑 / 重装系统后设备 ID 变了怎么办?</summary><p>旧机还在:在「设置 → 授权」点解绑,腾出席位后在新机激活。旧机已不在手:邮件与我们说明(附授权码后四位与新设备 ID),由我们在授权后台释放旧设备。每年免费换绑 2 次。</p></details>
|
||||
<details><summary>断网了授权会失效吗?</summary><p>不会。离线许可证本地自验,永不联网也有效;在线授权码激活后可连续离线使用 30 天,超时后联网校验一次即可继续。授权服务器不可达时按宽限期放行,不会打断正在进行的调试。</p></details>
|
||||
<details><summary>生产内网完全不出网,怎么激活?</summary><p>改用离线许可证(<code>LICOFF-</code>):在应用内导出本机机器码,邮件发我们换取离线证,本地导入即完成激活,全程不需要客户端联网。</p></details>
|
||||
<details><summary>续期怎么操作?</summary><p>到期前 30 天我们会邮件提醒。付款后签发新的授权码,在「设置 → 授权」里替换旧码即可,同一台设备无需先解绑;永久授权无需续期。</p></details>
|
||||
<details><summary>试用期结束后我的接口集合和环境还在吗?</summary><p>都在。授权只控制专业功能是否可用,本机集合、环境、历史记录与配置不做任何改动;试用结束后个人非商业用途可继续使用,购买授权后专业功能立即恢复。</p></details>
|
||||
<details><summary>激活失败怎么排查?</summary><p>先在「设置 → 授权」看当前状态与到期时间。若提示无效或不匹配,请把授权码后四位、设备 ID 与报错截图一并发邮件,我们核对授权服务器上的记录后处理(常见原因是设备数已占满或授权码输漏字符)。</p></details>
|
||||
<details><summary>能开发票、走对公转账吗?</summary><p>可以。个人与企业均可开具发票,支持对公转账、多产品捆绑与批量授权;邮件说明抬头与税号即可。</p></details>
|
||||
<details><summary>三个产品(OpsxAPI / OpsxDB / OpsXShell)要分别买吗?</summary><p>三个产品独立授权、独立计价;它们共用同一套授权体系(同一个设备 ID 规则与同一个授权服务器)。多产品组合、团队或教育用途可邮件说明需求,给捆绑价。</p></details>
|
||||
</div>
|
||||
<p class="lic-note">本页更新于 2026-10-16,授权口径与授权服务器 <code>licence.rlwj.cn</code> 实际配置一致。</p>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="section">
|
||||
<div class="wrap lic-cta">
|
||||
<h2 class="sec-title">准备好购买授权</h2>
|
||||
<p class="sec-sub">邮件说明产品、授权类型与设备数,当天回复并开票。</p>
|
||||
<div class="btns">
|
||||
<a class="btn btn-primary" href="mailto:hello@opsxapi.dev?subject=OpsxAPI%20%E6%8E%88%E6%9D%83%E4%B8%8E%E6%BF%80%E6%B4%BB%E5%92%A8%E8%AF%A2">邮件咨询购买</a>
|
||||
<a class="btn btn-ghost" href="/#exchange">还没装?立即下载</a>
|
||||
</div>
|
||||
<p class="lic-note">付款后当场发放授权码(LIC-),粘贴到「设置 → 授权」即完成激活——整个流程无需注册账号。</p>
|
||||
</div>
|
||||
</section>
|
||||
</main>
|
||||
|
||||
<footer class="footer">
|
||||
<div class="wrap footer-grid">
|
||||
<div>
|
||||
<div class="brand"><span class="brand-logo"><svg width="30" height="30" viewBox="0 0 64 64" aria-label="OpsxAPI"><defs><linearGradient id="lg-site" x1="0" y1="0" x2="64" y2="64" gradientUnits="userSpaceOnUse"><stop offset="0" stop-color="#2563EB"/><stop offset="1" stop-color="#06B6D4"/></linearGradient></defs><rect width="64" height="64" rx="14" fill="url(#lg-site)"/><g stroke="#fff" stroke-width="5" stroke-linecap="round" stroke-linejoin="round" fill="none"><path d="M17 25h26"/><path d="M37 19l6 6-6 6"/><path d="M47 39H21"/><path d="M27 33l-6 6 6 6"/></g></svg></span><span class="brand-name">OpsxAPI</span></div>
|
||||
<p class="footer-desc">本地优先的全协议接口工作台。<br>数据在自己手里,才是真的效率工具。</p>
|
||||
</div>
|
||||
<div>
|
||||
<h4>产品</h4>
|
||||
<a href="/#features">功能特性</a>
|
||||
<a href="/#pricing">定价与让利</a>
|
||||
<a href="/#compare">工具对比</a>
|
||||
</div>
|
||||
<div>
|
||||
<h4>资源</h4>
|
||||
<a href="/pricing">定价</a>
|
||||
<a href="/license">授权与激活</a>
|
||||
<a href="/docs" target="_blank" rel="noopener">用户文档</a>
|
||||
<a href="/docs/faq" target="_blank" rel="noopener">常见问题</a>
|
||||
</div>
|
||||
<div>
|
||||
<h4>支持</h4>
|
||||
<a href="mailto:hello@opsxapi.dev">hello@opsxapi.dev</a>
|
||||
<a href="/docs/quickstart" target="_blank" rel="noopener">快速开始</a>
|
||||
<a href="/terms" target="_blank" rel="noopener">服务条款</a>
|
||||
</div>
|
||||
</div>
|
||||
<div class="footer-bar">© 2026 OpsxAPI · <a href="/terms" target="_blank" rel="noopener">服务条款</a> · <a href="/license">授权与激活</a></div>
|
||||
</footer>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,211 @@
|
||||
<!doctype html>
|
||||
<html lang="zh-CN">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<title>授权与定价 · OpsxAPI</title>
|
||||
<meta name="description" content="OpsxAPI 定价:30 天全功能试用;专业版 ¥99/年或 ¥199 永久,团队版 ¥99/人/年,企业私有化。支持在线授权与完全离线激活。">
|
||||
<link rel="icon" type="image/svg+xml" href="data:image/svg+xml,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20viewBox%3D%220%200%2064%2064%22%3E%3Cdefs%3E%3ClinearGradient%20id%3D%22g%22%20x1%3D%220%22%20y1%3D%220%22%20x2%3D%2264%22%20y2%3D%2264%22%20gradientUnits%3D%22userSpaceOnUse%22%3E%3Cstop%20offset%3D%220%22%20stop-color%3D%22%232563EB%22%2F%3E%3Cstop%20offset%3D%221%22%20stop-color%3D%22%2306B6D4%22%2F%3E%3C%2FlinearGradient%3E%3C%2Fdefs%3E%3Crect%20width%3D%2264%22%20height%3D%2264%22%20rx%3D%2214%22%20fill%3D%22url%28%23g%29%22%2F%3E%3Cg%20stroke%3D%22%23fff%22%20stroke-width%3D%225%22%20stroke-linecap%3D%22round%22%20stroke-linejoin%3D%22round%22%20fill%3D%22none%22%3E%3Cpath%20d%3D%22M17%2025h26%22%2F%3E%3Cpath%20d%3D%22M37%2019l6%206-6%206%22%2F%3E%3Cpath%20d%3D%22M47%2039H21%22%2F%3E%3Cpath%20d%3D%22M27%2033l-6%206%206%206%22%2F%3E%3C%2Fg%3E%3C%2Fsvg%3E">">
|
||||
<link rel="stylesheet" href="/site/assets/site.css">
|
||||
<style>
|
||||
.trial-hero{padding:72px 0 28px;text-align:center;}
|
||||
.trial-hero h1{font-size:clamp(28px,4.5vw,44px);margin:14px 0 10px;}
|
||||
.trial-hero .lead{max-width:720px;margin:0 auto;opacity:.75;line-height:1.8;}
|
||||
.trial-strip{display:inline-flex;gap:8px;align-items:center;padding:7px 16px;border-radius:999px;border:1px solid var(--accent,#2563eb);color:var(--accent,#2563eb);font-size:13px;font-weight:600;}
|
||||
.toggle{display:inline-flex;border:1px solid var(--border,#d8dee9);border-radius:999px;padding:3px;gap:2px;margin:14px auto 0;}
|
||||
.toggle button{border:0;background:transparent;padding:6px 18px;border-radius:999px;font-size:13px;font-weight:600;color:inherit;opacity:.65;cursor:pointer;}
|
||||
.toggle button.on{background:var(--accent,#2563eb);color:#fff;opacity:1;}
|
||||
.price-num b{font-size:34px;}
|
||||
.faq{max-width:820px;margin:0 auto;}
|
||||
.faq details{border-bottom:1px solid var(--border,#e5e9f0);padding:16px 4px;}
|
||||
.faq summary{cursor:pointer;font-weight:600;font-size:15px;list-style:none;display:flex;justify-content:space-between;align-items:center;}
|
||||
.faq summary::after{content:"+";font-size:18px;opacity:.5;}
|
||||
.faq details[open] summary::after{content:"–";}
|
||||
.faq details p{margin:10px 0 2px;opacity:.72;line-height:1.85;font-size:14px;}
|
||||
.indie{text-align:center;opacity:.7;font-size:14px;line-height:1.9;max-width:560px;margin:0 auto;}
|
||||
.act-grid{display:grid;grid-template-columns:repeat(auto-fit,minmax(240px,1fr));gap:14px;margin-top:22px;text-align:left;}
|
||||
.act-grid .card h3{display:flex;align-items:center;gap:8px;margin:0 0 6px;font-size:15px;}
|
||||
.act-grid .card p{margin:0;opacity:.7;font-size:13px;line-height:1.8;}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" style="display:none" aria-hidden="true">
|
||||
<symbol id="i-rocket" viewBox="0 0 24 24"><path d="M12 15v5s3.03-.55 4-2c1.08-1.62 0-5 0-5" /> <path d="M4.5 16.5c-1.5 1.26-2 5-2 5s3.74-.5 5-2c.71-.84.7-2.13-.09-2.91a2.18 2.18 0 0 0-2.91-.09" /> <path d="M9 12a22 22 0 0 1 2-3.95A12.88 12.88 0 0 1 22 2c0 2.72-.78 7.5-6 11a22.4 22.4 0 0 1-4 2z" /> <path d="M9 12H4s.55-3.03 2-4c1.62-1.08 5 .05 5 .05" /></symbol>
|
||||
<symbol id="i-shield-check" viewBox="0 0 24 24"><path d="M20 13c0 5-3.5 7.5-7.66 8.95a1 1 0 0 1-.67-.01C7.5 20.5 4 18 4 13V6a1 1 0 0 1 1-1c2 0 4.5-1.2 6.24-2.72a1.17 1.17 0 0 1 1.52 0C14.51 3.81 17 5 19 5a1 1 0 0 1 1 1z" /> <path d="m9 12 2 2 4-4" /></symbol>
|
||||
<symbol id="i-arrow-left-right" viewBox="0 0 24 24"><path d="M8 3 4 7l4 4" /> <path d="M4 7h16" /> <path d="m16 21 4-4-4-4" /> <path d="M20 17H4" /></symbol>
|
||||
<symbol id="i-plug-zap" viewBox="0 0 24 24"><path d="M6.3 20.3a2.4 2.4 0 0 0 3.4 0L12 18l-6-6-2.3 2.3a2.4 2.4 0 0 0 0 3.4Z" /> <path d="m2 22 3-3" /> <path d="M7.5 13.5 10 11" /> <path d="M10.5 16.5 13 14" /> <path d="m18 3-4 4h6l-4 4" /></symbol>
|
||||
<symbol id="i-package" viewBox="0 0 24 24"><path d="M11 21.73a2 2 0 0 0 2 0l7-4A2 2 0 0 0 21 16V8a2 2 0 0 0-1-1.73l-7-4a2 2 0 0 0-2 0l-7 4A2 2 0 0 0 3 8v8a2 2 0 0 0 1 1.73z" /> <path d="M12 22V12" /> <polyline points="3.29 7 12 12 20.71 7" /> <path d="m7.5 4.27 9 5.15" /></symbol>
|
||||
<symbol id="i-folder-tree" viewBox="0 0 24 24"><path d="M20 10a1 1 0 0 0 1-1V6a1 1 0 0 0-1-1h-2.5a1 1 0 0 1-.8-.4l-.9-1.2A1 1 0 0 0 15 3h-2a1 1 0 0 0-1 1v5a1 1 0 0 0 1 1Z" /> <path d="M20 21a1 1 0 0 0 1-1v-3a1 1 0 0 0-1-1h-2.9a1 1 0 0 1-.88-.55l-.42-.85a1 1 0 0 0-.92-.6H13a1 1 0 0 0-1 1v5a1 1 0 0 0 1 1Z" /> <path d="M3 5a2 2 0 0 0 2 2h3" /> <path d="M3 3v13a2 2 0 0 0 2 2h3" /></symbol>
|
||||
<symbol id="i-globe" viewBox="0 0 24 24"><circle cx="12" cy="12" r="10" /> <path d="M12 2a14.5 14.5 0 0 0 0 20 14.5 14.5 0 0 0 0-20" /> <path d="M2 12h20" /></symbol>
|
||||
<symbol id="i-hexagon" viewBox="0 0 24 24"><path d="M21 16V8a2 2 0 0 0-1-1.73l-7-4a2 2 0 0 0-2 0l-7 4A2 2 0 0 0 3 8v8a2 2 0 0 0 1 1.73l7 4a2 2 0 0 0 2 0l7-4A2 2 0 0 0 21 16z" /></symbol>
|
||||
<symbol id="i-zap" viewBox="0 0 24 24"><path d="M15.914 4a1.5 1.5 0 00-2.474-1.561l-9 9A1.5 1.5 0 005.5 14h4.002a.5.5 0 01.471.666L8.086 20a1.5 1.5 0 002.475 1.56l9-9A1.5 1.5 0 0018.5 10h-3.997a.5.5 0 01-.472-.667z" /></symbol>
|
||||
<symbol id="i-radio" viewBox="0 0 24 24"><path d="M16.247 7.761a6 6 0 0 1 0 8.478" /> <path d="M19.075 4.933a10 10 0 0 1 0 14.134" /> <path d="M4.925 19.067a10 10 0 0 1 0-14.134" /> <path d="M7.753 16.239a6 6 0 0 1 0-8.478" /> <circle cx="12" cy="12" r="2" /></symbol>
|
||||
<symbol id="i-mailbox" viewBox="0 0 24 24"><path d="M22 17a2 2 0 0 1-2 2H4a2 2 0 0 1-2-2V9.5C2 7 4 5 6.5 5H18c2.2 0 4 1.8 4 4v8Z" /> <polyline points="15,9 18,9 18,11" /> <path d="M6.5 5C9 5 11 7 11 9.5V17a2 2 0 0 1-2 2" /> <line x1="6" x2="7" y1="10" y2="10" /></symbol>
|
||||
<symbol id="i-code" viewBox="0 0 24 24"><path d="m16 18 6-6-6-6" /> <path d="m8 6-6 6 6 6" /></symbol>
|
||||
<symbol id="i-book-open" viewBox="0 0 24 24"><path d="M12 5v16" /> <path d="M20.001 19A2 2 0 0022 17V5a2 2 0 00-1.999-2L16 3.002A5 5 0 0012 5a5 5 0 00-4-2H4a2 2 0 00-2 2v12a2 2 0 001.999 2H8a5 5 0 014 2 5 5 0 014-2z" /></symbol>
|
||||
<symbol id="i-sprout" viewBox="0 0 24 24"><path d="M14 9.536V7a4 4 0 0 1 4-4h1.5a.5.5 0 0 1 .5.5V5a4 4 0 0 1-4 4 4 4 0 0 0-4 4c0 2 1 3 1 5a5 5 0 0 1-1 3" /> <path d="M4 9a5 5 0 0 1 8 4 5 5 0 0 1-8-4" /> <path d="M5 21h14" /></symbol>
|
||||
<symbol id="i-graduation-cap" viewBox="0 0 24 24"><path d="M21.42 10.922a1 1 0 0 0-.019-1.838L12.83 5.18a2 2 0 0 0-1.66 0L2.6 9.08a1 1 0 0 0 0 1.832l8.57 3.908a2 2 0 0 0 1.66 0z" /> <path d="M22 10v6" /> <path d="M6 12.5V16a6 3 0 0 0 12 0v-3.5" /></symbol>
|
||||
<symbol id="i-credit-card" viewBox="0 0 24 24"><rect width="20" height="14" x="2" y="5" rx="2" /> <line x1="2" x2="22" y1="10" y2="10" /> <path d="M6 14h2" /></symbol>
|
||||
<symbol id="i-megaphone" viewBox="0 0 24 24"><path d="M11 6a13 13 0 0 0 8.4-2.8A1 1 0 0 1 21 4v12a1 1 0 0 1-1.6.8A13 13 0 0 0 11 14H5a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2z" /> <path d="M6 14a12 12 0 0 0 2.4 7.2 2 2 0 0 0 3.2-2.4A8 8 0 0 1 10 14" /> <path d="M8 6v8" /></symbol>
|
||||
<symbol id="i-download" viewBox="0 0 24 24"><path d="M12 15V3" /> <path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4" /> <path d="m7 10 5 5 5-5" /></symbol>
|
||||
<symbol id="i-upload" viewBox="0 0 24 24"><path d="M12 3v12" /> <path d="m17 8-5-5-5 5" /> <path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4" /></symbol>
|
||||
<symbol id="i-library" viewBox="0 0 24 24"><path d="m16 6 4 14" /> <path d="M12 6v14" /> <path d="M8 8v12" /> <path d="M4 4v16" /></symbol>
|
||||
</svg>
|
||||
<header class="nav">
|
||||
<div class="nav-inner">
|
||||
<a class="brand" href="/">
|
||||
<span class="brand-logo"><svg width="30" height="30" viewBox="0 0 64 64" aria-label="OpsxAPI"><defs><linearGradient id="lg-site" x1="0" y1="0" x2="64" y2="64" gradientUnits="userSpaceOnUse"><stop offset="0" stop-color="#2563EB"/><stop offset="1" stop-color="#06B6D4"/></linearGradient></defs><rect width="64" height="64" rx="14" fill="url(#lg-site)"/><g stroke="#fff" stroke-width="5" stroke-linecap="round" stroke-linejoin="round" fill="none"><path d="M17 25h26"/><path d="M37 19l6 6-6 6"/><path d="M47 39H21"/><path d="M27 33l-6 6 6 6"/></g></svg></span>
|
||||
<span class="brand-name">OpsxAPI</span>
|
||||
</a>
|
||||
<nav class="nav-links">
|
||||
<a href="/#features">功能</a>
|
||||
<a href="/#protocols">协议</a>
|
||||
<a href="/#exchange">导入导出</a>
|
||||
<a href="/pricing">定价</a>
|
||||
<a href="/license">授权</a>
|
||||
<a href="/docs" target="_blank" rel="noopener">文档</a>
|
||||
</nav>
|
||||
<div class="nav-cta">
|
||||
<a class="btn btn-primary" href="/docs/quickstart" target="_blank" rel="noopener">快速上手 ↗</a>
|
||||
</div>
|
||||
</div>
|
||||
</header>
|
||||
<main>
|
||||
<section class="trial-hero">
|
||||
<span class="trial-strip"><svg class="i"><use href="#i-rocket"/></svg> 30 天免费试用 · 首次启动自动开始 · 无需注册</span>
|
||||
<h1>授权与定价</h1>
|
||||
<p class="lead">OpsxAPI 对个人非商业用途永久免费;在工作中商用需要授权。授权绑定设备而非账号,支持在线激活与完全离线激活两种方式。</p>
|
||||
</section>
|
||||
|
||||
<section class="section" style="padding-top:26px">
|
||||
<div class="wrap">
|
||||
<h2 class="sec-title">先试用,再决定</h2>
|
||||
<p class="sec-sub">下载即含 30 天全功能试用;激活支持在线与离线两种方式。</p>
|
||||
<div class="act-grid">
|
||||
<div class="card"><h3><svg class="i"><use href="#i-rocket"/></svg>30 天全功能试用</h3><p>首次启动自动开始;无需注册、无需信用卡。试用结束后个人非商业用途仍可继续使用。</p></div>
|
||||
<div class="card"><h3><svg class="i"><use href="#i-globe"/></svg>在线授权 LIC-</h3><p>联网激活并绑定设备;支持自助解绑换机;激活后可连续离线使用 30 天。</p></div>
|
||||
<div class="card"><h3><svg class="i"><use href="#i-shield-check"/></svg>离线许可证 LICOFF-</h3><p>Ed25519 自包含签名,绑定设备;完全无网络也能激活,适合隔离内网环境。</p></div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="section alt">
|
||||
<div class="wrap">
|
||||
<h2 class="sec-title">选择适合的授权</h2>
|
||||
<p class="sec-sub">个人版可选年付或一次性买断;团队按席位,私有化不限席位。</p>
|
||||
<div class="pricing three">
|
||||
<div class="price-card">
|
||||
<div class="plan">专业版</div>
|
||||
<div class="toggle" data-mode="year"><button class="on" data-mode="year">¥99 / 年</button><button data-mode="life">永久 ¥199</button></div>
|
||||
<div class="price price-num"><b data-price="year">¥99</b><b data-price="life" style="display:none">¥199</b><span>/ 年</span></div>
|
||||
<div class="plan-sub">单人不分设备多少 · 最多 3 台设备同时激活</div>
|
||||
<ul>
|
||||
<li>六种协议全部调试能力</li>
|
||||
<li>集合 / 环境 / 历史无限量</li>
|
||||
<li>全格式导入导出 + 代码生成</li>
|
||||
<li>本机 Mock 服务</li>
|
||||
<li>设备可自助解绑迁移</li>
|
||||
<li>1 年免费升级<span data-price="life" style="display:none">(永久版含后续更新)</span></li>
|
||||
</ul>
|
||||
<a class="btn btn-primary" href="mailto:hello@opsxapi.dev?subject=OpsxAPI%20%E4%B8%AA%E4%BA%BA%E7%89%88%E8%B4%AD%E4%B9%B0">购买咨询</a>
|
||||
</div>
|
||||
<div class="price-card featured">
|
||||
<div class="plan-tag">高性价比之选</div>
|
||||
<div class="plan">团队版</div>
|
||||
<div class="price price-num"><b>¥99</b><span>/ 人 / 年</span></div>
|
||||
<div class="plan-sub">按席位授权 · 5 席起 · 组织内可转移</div>
|
||||
<ul>
|
||||
<li>个人版全部能力</li>
|
||||
<li>集合批量导入分发</li>
|
||||
<li>团队共享空间(自托管)</li>
|
||||
<li>内网自托管(单二进制)</li>
|
||||
<li>优先技术支持</li>
|
||||
<li>1 年免费升级</li>
|
||||
</ul>
|
||||
<a class="btn btn-primary" href="mailto:hello@opsxapi.dev?subject=OpsxAPI%20%E5%9B%A2%E9%98%9F%E7%89%88%E8%B4%AD%E4%B9%B0">联系商务</a>
|
||||
</div>
|
||||
<div class="price-card enterprise">
|
||||
<div class="plan-tag dark">私有化 · 不限席位</div>
|
||||
<div class="plan">企业私有化版</div>
|
||||
<div class="price price-num"><b>¥8,000</b><span>/ 年 · 标价</span></div>
|
||||
<div class="plan-sub">或 ¥30,000 一次性买断 · 单法人不限席位</div>
|
||||
<ul>
|
||||
<li>整机 / 内网部署,数据不出企业</li>
|
||||
<li>团队版全部能力</li>
|
||||
<li>远程部署协助 + 专属支持</li>
|
||||
<li>定制开发通道(另行报价)</li>
|
||||
<li>买断可选:永久使用 + 1 年升级</li>
|
||||
</ul>
|
||||
<a class="btn btn-dark" href="mailto:hello@opsxapi.dev?subject=OpsxAPI%20%E7%A7%81%E6%9C%89%E5%8C%96%E8%B4%AD%E4%B9%B0">获取报价</a>
|
||||
</div>
|
||||
</div>
|
||||
<p class="deal-note" style="text-align:center">支付后当场发放授权码(LIC-),粘贴到「设置 → 授权」即完成激活——整个流程无需注册账号;许可证一机一码绑定设备,每年可免费换绑 2 次。<br>标价透明,成交灵活:早鸟 / 教育 / 多年预付可议价,常见成交区间见 <a href="/#pricing">首页定价区</a>。</p>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="section">
|
||||
<div class="wrap">
|
||||
<h2 class="sec-title">常见问题</h2>
|
||||
<div class="faq">
|
||||
<details><summary>试用是怎么计算的?</summary><p>30 天全功能试用,从应用首次启动那一刻自动开始——无需注册账号,无需信用卡。试用期结束后,个人非商业用途可继续使用;在工作中的商业用途需要购买授权。</p></details>
|
||||
<details><summary>在线授权和离线激活有什么区别?</summary><p>在线授权码(LIC- 前缀):联网激活,绑定设备,支持在设置中自助解绑换机;激活后可连续离线使用最长 30 天。离线许可证(LICOFF- 前缀):同样绑定设备,但采用 Ed25519 自包含签名,在完全无网络的环境也能激活——适合隔离内网。两种方式在「设置 → 授权」中粘贴即用。</p></details>
|
||||
<details><summary>一个授权能用几台设备?</summary><p>授权绑定设备而非订阅账号。个人版最多 3 台设备;团队版按席位授权、组织内可转移。设备可以在「设置 → 授权」中解绑后迁移到新机器。</p></details>
|
||||
<details><summary>断网了授权会失效吗?</summary><p>离线许可证(LICOFF-)本地自验,永不联网也有效。在线授权(LIC-)容忍连续 30 天离线,超过后需联网重新验证一次。</p></details>
|
||||
<details><summary>退款政策是怎样的?</summary><p>自付款之日起 30 天内无理由全额退款——先用起来,不满意直接退。</p></details>
|
||||
<details><summary>怎么购买?</summary><p>邮件咨询购买,当天回复;支持对公转账与开票。团队与私有化支持按规模议价。</p></details>
|
||||
<details><summary>三个产品(OpsxAPI / OpsxDB / OpsXShell)要分别买吗?</summary><p>三个产品独立授权、独立计价。多产品组合或家庭/学生优惠可直接邮件说明需求,给捆绑价。</p></details>
|
||||
<details><summary>授权包含哪些升级?</summary><p>年付授权含 1 年免费升级;终身授权包含发布版本与后续一年内的全部更新。</p></details>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="section alt">
|
||||
<div class="wrap indie">
|
||||
<p><b>独立开发,认真长跑。</b><br>Opsx 系列工具由独立开发者打造,每一份授权的收入都直接投入产品迭代——没有投资人,也没有广告。</p>
|
||||
<p style="margin-top:14px"><a class="btn btn-primary" href="/#exchange">还没装?立即下载</a></p>
|
||||
</div>
|
||||
</section>
|
||||
</main>
|
||||
<footer class="footer">
|
||||
<div class="wrap footer-grid">
|
||||
<div>
|
||||
<div class="brand"><span class="brand-logo"><svg width="30" height="30" viewBox="0 0 64 64" aria-label="OpsxAPI"><defs><linearGradient id="lg-site" x1="0" y1="0" x2="64" y2="64" gradientUnits="userSpaceOnUse"><stop offset="0" stop-color="#2563EB"/><stop offset="1" stop-color="#06B6D4"/></linearGradient></defs><rect width="64" height="64" rx="14" fill="url(#lg-site)"/><g stroke="#fff" stroke-width="5" stroke-linecap="round" stroke-linejoin="round" fill="none"><path d="M17 25h26"/><path d="M37 19l6 6-6 6"/><path d="M47 39H21"/><path d="M27 33l-6 6 6 6"/></g></svg></span><span class="brand-name">OpsxAPI</span></div>
|
||||
<p class="footer-desc">本地优先的全协议接口工作台。<br>数据在自己手里,才是真的效率工具。</p>
|
||||
</div>
|
||||
<div>
|
||||
<h4>产品</h4>
|
||||
<a href="/#features">功能特性</a>
|
||||
<a href="/#pricing">定价与让利</a>
|
||||
<a href="/#compare">工具对比</a>
|
||||
</div>
|
||||
<div>
|
||||
<h4>资源</h4>
|
||||
<a href="/pricing">定价</a>
|
||||
<a href="/license">授权与激活</a>
|
||||
<a href="/docs" target="_blank" rel="noopener">用户文档</a>
|
||||
<a href="/docs/server-api" target="_blank" rel="noopener">服务端 API</a>
|
||||
<a href="/docs/faq" target="_blank" rel="noopener">常见问题</a>
|
||||
</div>
|
||||
<div>
|
||||
<h4>支持</h4>
|
||||
<a href="mailto:hello@opsxapi.dev">hello@opsxapi.dev</a>
|
||||
<a href="/docs/quickstart" target="_blank" rel="noopener">快速开始</a>
|
||||
</div>
|
||||
</div>
|
||||
<div class="footer-bar">© 2026 OpsxAPI · <a href="/terms" target="_blank">服务条款</a> · <a href="/license">授权与激活</a></div>
|
||||
</footer>
|
||||
<script>
|
||||
document.querySelectorAll('.toggle').forEach(function(t){
|
||||
t.addEventListener('click', function(e){
|
||||
if(e.target.tagName!=='BUTTON')return;
|
||||
t.querySelectorAll('button').forEach(function(b){b.classList.remove('on')});
|
||||
e.target.classList.add('on');
|
||||
t.dataset.mode = e.target.dataset.mode;
|
||||
t.closest('.price-card').querySelectorAll('[data-price]').forEach(function(el){
|
||||
el.style.display = el.dataset.price===t.dataset.mode ? '' : 'none';
|
||||
});
|
||||
});
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
File renamed without changes.
Executable
+8
@@ -0,0 +1,8 @@
|
||||
#!/usr/bin/env bash
|
||||
# 官网构建入口:从 homepage/ 源生成 homepage/dist/(纯静态,供 nginx 直接托管)。
|
||||
# 用法: scripts/build-homepage.sh
|
||||
set -euo pipefail
|
||||
|
||||
ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
|
||||
cd "$ROOT/tools/homepage-build"
|
||||
exec go run . -src "$ROOT/homepage" -out "$ROOT/homepage/dist"
|
||||
@@ -1,264 +0,0 @@
|
||||
// Package docs 在 /docs 路径提供产品用户文档(Markdown 嵌入二进制,SSR 渲染)。
|
||||
package docs
|
||||
|
||||
import (
|
||||
"embed"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"path"
|
||||
"regexp"
|
||||
"strings"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/yuin/goldmark"
|
||||
"github.com/yuin/goldmark/extension"
|
||||
gmhtml "github.com/yuin/goldmark/renderer/html"
|
||||
)
|
||||
|
||||
//go:embed content assets template.html
|
||||
var files embed.FS
|
||||
|
||||
type PageDef struct {
|
||||
File string `json:"file"`
|
||||
Slug string `json:"slug"`
|
||||
Title string `json:"title"`
|
||||
Updated string `json:"updated,omitempty"`
|
||||
}
|
||||
|
||||
type GroupDef struct {
|
||||
Name string `json:"name"`
|
||||
Pages []PageDef `json:"pages"`
|
||||
}
|
||||
|
||||
type Manifest struct {
|
||||
ProductVersion string `json:"productVersion"`
|
||||
Groups []GroupDef `json:"groups"`
|
||||
}
|
||||
|
||||
// TocItem 本页目录项
|
||||
type TocItem struct {
|
||||
ID string `json:"id"`
|
||||
Level int `json:"level"`
|
||||
Text string `json:"text"`
|
||||
}
|
||||
|
||||
type SearchEntry struct {
|
||||
Slug string `json:"slug"`
|
||||
Title string `json:"title"`
|
||||
Group string `json:"group"`
|
||||
Text string `json:"text"`
|
||||
}
|
||||
|
||||
type NavLink struct {
|
||||
Slug string
|
||||
Title string
|
||||
}
|
||||
|
||||
type Page struct {
|
||||
Slug string
|
||||
Title string
|
||||
Group string
|
||||
Updated string
|
||||
HTML string
|
||||
TOC []TocItem
|
||||
Prev *NavLink
|
||||
Next *NavLink
|
||||
}
|
||||
|
||||
// Handler 文档服务
|
||||
type Handler struct {
|
||||
manifest Manifest
|
||||
pages map[string]*Page
|
||||
order []string
|
||||
searchData []byte
|
||||
tpl string
|
||||
}
|
||||
|
||||
func New() *Handler {
|
||||
h := &Handler{pages: map[string]*Page{}}
|
||||
raw, err := files.ReadFile("content/manifest.json")
|
||||
if err != nil {
|
||||
panic("docs: manifest.json 缺失: " + err.Error())
|
||||
}
|
||||
if err := json.Unmarshal(raw, &h.manifest); err != nil {
|
||||
panic("docs: manifest.json 解析失败: " + err.Error())
|
||||
}
|
||||
tplRaw, err := files.ReadFile("template.html")
|
||||
if err != nil {
|
||||
panic("docs: template.html 缺失: " + err.Error())
|
||||
}
|
||||
h.tpl = string(tplRaw)
|
||||
|
||||
md := goldmark.New(
|
||||
goldmark.WithExtensions(extension.Table, extension.Strikethrough, extension.TaskList),
|
||||
goldmark.WithRendererOptions(gmhtml.WithHardWraps()),
|
||||
)
|
||||
|
||||
search := []SearchEntry{}
|
||||
var prev *NavLink
|
||||
for _, group := range h.manifest.Groups {
|
||||
for _, def := range group.Pages {
|
||||
src, err := files.ReadFile("content/" + def.File)
|
||||
if err != nil {
|
||||
panic("docs: 缺少内容文件 " + def.File)
|
||||
}
|
||||
var buf strings.Builder
|
||||
if err := md.Convert(src, &buf); err != nil {
|
||||
panic("docs: 渲染失败 " + def.File + ": " + err.Error())
|
||||
}
|
||||
body, toc := injectHeadingIDs(buf.String())
|
||||
page := &Page{
|
||||
Slug: def.Slug,
|
||||
Title: def.Title,
|
||||
Group: group.Name,
|
||||
Updated: def.Updated,
|
||||
HTML: body,
|
||||
TOC: toc,
|
||||
Prev: prev,
|
||||
}
|
||||
h.pages[def.Slug] = page
|
||||
h.order = append(h.order, def.Slug)
|
||||
prev = &NavLink{Slug: def.Slug, Title: def.Title}
|
||||
search = append(search, SearchEntry{
|
||||
Slug: def.Slug,
|
||||
Title: def.Title,
|
||||
Group: group.Name,
|
||||
Text: plainText(string(src)),
|
||||
})
|
||||
}
|
||||
}
|
||||
// 下一页链接
|
||||
for i, slug := range h.order {
|
||||
if i+1 < len(h.order) {
|
||||
next := h.pages[h.order[i+1]]
|
||||
h.pages[slug].Next = &NavLink{Slug: next.Slug, Title: next.Title}
|
||||
}
|
||||
}
|
||||
searchJSON, _ := json.Marshal(search)
|
||||
h.searchData = searchJSON
|
||||
return h
|
||||
}
|
||||
|
||||
// Register 注册路由(单一 catch-all,避免静态/参数段冲突)
|
||||
func (h *Handler) Register(r *gin.Engine) {
|
||||
r.GET("/docs", func(c *gin.Context) {
|
||||
c.Redirect(http.StatusFound, "/docs/"+h.order[0])
|
||||
})
|
||||
r.GET("/docs/*path", h.dispatch)
|
||||
}
|
||||
|
||||
func (h *Handler) dispatch(c *gin.Context) {
|
||||
p := strings.Trim(c.Param("path"), "/")
|
||||
switch {
|
||||
case p == "":
|
||||
c.Redirect(http.StatusFound, "/docs/"+h.order[0])
|
||||
case p == "search-data.json":
|
||||
h.SearchData(c)
|
||||
case strings.HasPrefix(p, "assets/"):
|
||||
c.Params = []gin.Param{{Key: "file", Value: "/" + strings.TrimPrefix(p, "assets")}}
|
||||
h.Asset(c)
|
||||
default:
|
||||
// 取第一段作为 slug(忽略子路径)
|
||||
slug := p
|
||||
if i := strings.IndexByte(p, '/'); i >= 0 {
|
||||
slug = p[:i]
|
||||
}
|
||||
c.Params = []gin.Param{{Key: "slug", Value: slug}}
|
||||
h.Page(c)
|
||||
}
|
||||
}
|
||||
|
||||
// Page 渲染文档页
|
||||
func (h *Handler) Page(c *gin.Context) {
|
||||
slug := c.Param("slug")
|
||||
page, ok := h.pages[slug]
|
||||
if !ok {
|
||||
c.Status(http.StatusNotFound)
|
||||
_, _ = c.Writer.WriteString(notFoundHTML(h.order[0])) //nolint:errcheck
|
||||
return
|
||||
}
|
||||
c.Header("Content-Type", "text/html; charset=utf-8")
|
||||
c.Header("Cache-Control", "no-cache")
|
||||
_, _ = c.Writer.WriteString(renderPage(h, page))
|
||||
}
|
||||
|
||||
// SearchData 搜索索引
|
||||
func (h *Handler) SearchData(c *gin.Context) {
|
||||
c.Data(http.StatusOK, "application/json; charset=utf-8", h.searchData)
|
||||
}
|
||||
|
||||
// Asset 静态资源
|
||||
func (h *Handler) Asset(c *gin.Context) {
|
||||
name := path.Clean(c.Param("file"))
|
||||
if strings.Contains(name, "..") {
|
||||
c.Status(http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
data, err := files.ReadFile("assets" + name)
|
||||
if err != nil {
|
||||
c.Status(http.StatusNotFound)
|
||||
return
|
||||
}
|
||||
switch {
|
||||
case strings.HasSuffix(name, ".css"):
|
||||
c.Header("Content-Type", "text/css; charset=utf-8")
|
||||
case strings.HasSuffix(name, ".js"):
|
||||
c.Header("Content-Type", "application/javascript; charset=utf-8")
|
||||
case strings.HasSuffix(name, ".svg"):
|
||||
c.Header("Content-Type", "image/svg+xml")
|
||||
default:
|
||||
c.Header("Content-Type", "application/octet-stream")
|
||||
}
|
||||
c.Header("Cache-Control", "public, max-age=3600")
|
||||
c.Data(http.StatusOK, "", data)
|
||||
}
|
||||
|
||||
// ManifestExport 供测试使用
|
||||
func (h *Handler) ManifestExport() Manifest { return h.manifest }
|
||||
|
||||
func (h *Handler) FirstSlug() string { return h.order[0] }
|
||||
|
||||
var (
|
||||
hRe = regexp.MustCompile(`(?s)<(h[23])>(.*?)</h[23]>`)
|
||||
tagRe = regexp.MustCompile(`<[^>]+>`)
|
||||
)
|
||||
|
||||
// injectHeadingIDs 给 h2/h3 加锚点 id,并产出目录
|
||||
func injectHeadingIDs(html string) (string, []TocItem) {
|
||||
var toc []TocItem
|
||||
counter := 0
|
||||
out := hRe.ReplaceAllStringFunc(html, func(m string) string {
|
||||
sub := hRe.FindStringSubmatch(m)
|
||||
tag, inner := sub[1], sub[2]
|
||||
counter++
|
||||
id := fmt.Sprintf("sec-%d", counter)
|
||||
text := strings.TrimSpace(tagRe.ReplaceAllString(inner, ""))
|
||||
level := 2
|
||||
if tag == "h3" {
|
||||
level = 3
|
||||
}
|
||||
toc = append(toc, TocItem{ID: id, Level: level, Text: text})
|
||||
return fmt.Sprintf(`<%s id="%s">%s</%s>`, tag, id, inner, tag)
|
||||
})
|
||||
return out, toc
|
||||
}
|
||||
|
||||
var mdNoise = regexp.MustCompile("(?s)```.*?```|`[^`]*`|!\\[[^\\]]*\\]\\([^)]*\\)|\\[[^\\]]*\\]\\([^)]*\\)|[#>*_\\-|]+")
|
||||
|
||||
// plainText 供搜索索引的纯文本
|
||||
func plainText(src string) string {
|
||||
s := mdNoise.ReplaceAllString(src, " ")
|
||||
s = strings.Join(strings.Fields(s), " ")
|
||||
if len(s) > 6000 {
|
||||
s = s[:6000]
|
||||
}
|
||||
return s
|
||||
}
|
||||
|
||||
func notFoundHTML(home string) string {
|
||||
return fmt.Sprintf(`<!doctype html><html lang="zh-CN"><head><meta charset="utf-8"><title>404 · OpsxAPI 文档</title>
|
||||
<link rel="stylesheet" href="/docs/assets/docs.css"></head><body class="doc-404">
|
||||
<div class="wrap"><h1>404</h1><p>文档页面不存在或已被移动。</p><a class="btn" href="%s">返回文档首页</a></div>
|
||||
</body></html>`, home)
|
||||
}
|
||||
@@ -1,122 +0,0 @@
|
||||
package docs
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
func newTestRouter(t *testing.T) *gin.Engine {
|
||||
t.Helper()
|
||||
gin.SetMode(gin.TestMode)
|
||||
r := gin.New()
|
||||
New().Register(r)
|
||||
return r
|
||||
}
|
||||
|
||||
func TestDocsIndexRedirect(t *testing.T) {
|
||||
r := newTestRouter(t)
|
||||
w := httptest.NewRecorder()
|
||||
r.ServeHTTP(w, httptest.NewRequest(http.MethodGet, "/docs", nil))
|
||||
if w.Code != http.StatusFound {
|
||||
t.Fatalf("expect 302, got %d", w.Code)
|
||||
}
|
||||
if !strings.HasPrefix(w.Header().Get("Location"), "/docs/") {
|
||||
t.Fatalf("bad redirect: %s", w.Header().Get("Location"))
|
||||
}
|
||||
}
|
||||
|
||||
func TestDocsAllPagesRender(t *testing.T) {
|
||||
h := New()
|
||||
r := newTestRouter(t)
|
||||
for _, slug := range h.order {
|
||||
w := httptest.NewRecorder()
|
||||
req := httptest.NewRequest(http.MethodGet, "/docs/"+slug, nil)
|
||||
r.ServeHTTP(w, req)
|
||||
if w.Code != http.StatusOK {
|
||||
t.Fatalf("page %s: expect 200, got %d", slug, w.Code)
|
||||
}
|
||||
body := w.Body.String()
|
||||
if !strings.Contains(body, "<title>") || !strings.Contains(body, `class="md"`) {
|
||||
t.Fatalf("page %s: html structure incomplete", slug)
|
||||
}
|
||||
// 正文有渲染出的标题与段落
|
||||
if !strings.Contains(body, "<h1") {
|
||||
t.Fatalf("page %s: missing h1", slug)
|
||||
}
|
||||
// 侧栏包含全部分组
|
||||
for _, g := range h.manifest.Groups {
|
||||
if !strings.Contains(body, g.Name) {
|
||||
t.Fatalf("page %s: sidebar missing group %s", slug, g.Name)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestDocsPageContainsTOCAndNav(t *testing.T) {
|
||||
r := newTestRouter(t)
|
||||
w := httptest.NewRecorder()
|
||||
r.ServeHTTP(w, httptest.NewRequest(http.MethodGet, "/docs/http", nil))
|
||||
body := w.Body.String()
|
||||
if !strings.Contains(body, "sec-1") {
|
||||
t.Fatal("expect heading anchors")
|
||||
}
|
||||
if !strings.Contains(body, "上一页") {
|
||||
t.Fatal("expect prev link")
|
||||
}
|
||||
}
|
||||
|
||||
func TestDocsSearchData(t *testing.T) {
|
||||
r := newTestRouter(t)
|
||||
w := httptest.NewRecorder()
|
||||
r.ServeHTTP(w, httptest.NewRequest(http.MethodGet, "/docs/search-data.json", nil))
|
||||
if w.Code != http.StatusOK {
|
||||
t.Fatalf("expect 200, got %d", w.Code)
|
||||
}
|
||||
var entries []SearchEntry
|
||||
if err := json.Unmarshal(w.Body.Bytes(), &entries); err != nil {
|
||||
t.Fatalf("invalid json: %v", err)
|
||||
}
|
||||
if len(entries) < 10 {
|
||||
t.Fatalf("expect >= 10 search entries, got %d", len(entries))
|
||||
}
|
||||
// MQTT 页应可被检索(标题或正文命中)
|
||||
found := false
|
||||
for _, e := range entries {
|
||||
if e.Slug == "mqtt" && (e.Title == "MQTT" || strings.Contains(e.Text, "MQTT")) {
|
||||
found = true
|
||||
}
|
||||
}
|
||||
if !found {
|
||||
t.Fatal("mqtt entry missing searchable text")
|
||||
}
|
||||
}
|
||||
|
||||
func TestDocsAssetsAnd404(t *testing.T) {
|
||||
r := newTestRouter(t)
|
||||
|
||||
w := httptest.NewRecorder()
|
||||
r.ServeHTTP(w, httptest.NewRequest(http.MethodGet, "/docs/assets/docs.css", nil))
|
||||
if w.Code != http.StatusOK || !strings.Contains(w.Header().Get("Content-Type"), "text/css") {
|
||||
t.Fatalf("css asset: %d %s", w.Code, w.Header().Get("Content-Type"))
|
||||
}
|
||||
|
||||
w = httptest.NewRecorder()
|
||||
r.ServeHTTP(w, httptest.NewRequest(http.MethodGet, "/docs/assets/docs.js", nil))
|
||||
if w.Code != http.StatusOK {
|
||||
t.Fatalf("js asset: %d", w.Code)
|
||||
}
|
||||
|
||||
w = httptest.NewRecorder()
|
||||
r.ServeHTTP(w, httptest.NewRequest(http.MethodGet, "/docs/not-exist-page", nil))
|
||||
if w.Code != http.StatusNotFound {
|
||||
t.Fatalf("404 page: %d", w.Code)
|
||||
}
|
||||
if !strings.Contains(w.Body.String(), "返回文档首页") {
|
||||
t.Fatal("404 page missing home link")
|
||||
}
|
||||
}
|
||||
@@ -1,25 +0,0 @@
|
||||
package docs
|
||||
|
||||
import (
|
||||
"html/template"
|
||||
"strings"
|
||||
)
|
||||
|
||||
func renderPage(h *Handler, p *Page) string {
|
||||
tpl, err := template.New("doc").Parse(h.tpl)
|
||||
if err != nil {
|
||||
return "<!doctype html><meta charset=utf-8><title>error</title>template error: " + template.HTMLEscapeString(err.Error())
|
||||
}
|
||||
|
||||
var sb strings.Builder
|
||||
data := map[string]any{
|
||||
"Manifest": h.manifest,
|
||||
"Page": p,
|
||||
"TOC": p.TOC,
|
||||
"Content": template.HTML(p.HTML), //nolint:gosec —— 内容为内置受信 Markdown
|
||||
}
|
||||
if err := tpl.Execute(&sb, data); err != nil {
|
||||
return "<!doctype html><meta charset=utf-8><title>error</title>render error: " + template.HTMLEscapeString(err.Error())
|
||||
}
|
||||
return sb.String()
|
||||
}
|
||||
@@ -1,6 +1,7 @@
|
||||
package server
|
||||
|
||||
import (
|
||||
"log"
|
||||
"net"
|
||||
"net/http"
|
||||
"os"
|
||||
@@ -9,11 +10,9 @@ import (
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
"opsxapi/server/internal/api"
|
||||
"opsxapi/server/internal/docs"
|
||||
"opsxapi/server/internal/echo"
|
||||
"opsxapi/server/internal/mocksrv"
|
||||
"opsxapi/server/internal/proxy"
|
||||
"opsxapi/server/internal/site"
|
||||
)
|
||||
|
||||
func NewRouter() *gin.Engine {
|
||||
@@ -43,15 +42,16 @@ func NewRouter() *gin.Engine {
|
||||
mocksrv.New(accountAPI.Store()).Register(r)
|
||||
|
||||
echo.Mount(r)
|
||||
// 官网/文档/定价已拆分独立静态站(源在 homepage/,构建见 scripts/build-homepage.sh),
|
||||
// 本地仅静态托管 homepage/dist 预览;不再内置 embed 兜底,与后端彻底解耦。
|
||||
if !mountHomepage(r) {
|
||||
docs.New().Register(r)
|
||||
site.New().Register(r)
|
||||
log.Printf("[warn] homepage/dist 未找到,官网/文档/定价未挂载;请先运行 scripts/build-homepage.sh 生成产物")
|
||||
}
|
||||
|
||||
return r
|
||||
}
|
||||
|
||||
// mountHomepage 本地预览 homepage/dist(落地页 + 文档)。不改 homepage/ 源。
|
||||
// mountHomepage 本地预览 homepage/dist(落地页 + 文档 + 定价)。只读产物,不碰 homepage/ 源。
|
||||
func mountHomepage(r *gin.Engine) bool {
|
||||
for _, dir := range []string{
|
||||
os.Getenv("OPSXAPI_HOMEPAGE"),
|
||||
|
||||
@@ -1,80 +0,0 @@
|
||||
// Package site 在 /home(及 /)提供产品官网单页(SSR,资源嵌入二进制)。
|
||||
package site
|
||||
|
||||
import (
|
||||
"embed"
|
||||
"net/http"
|
||||
"strings"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
//go:embed template.html terms.html assets
|
||||
var files embed.FS
|
||||
|
||||
// Handler 官网服务
|
||||
type Handler struct {
|
||||
html string
|
||||
terms string
|
||||
}
|
||||
|
||||
func New() *Handler {
|
||||
raw, err := files.ReadFile("template.html")
|
||||
if err != nil {
|
||||
panic("site: template.html 缺失: " + err.Error())
|
||||
}
|
||||
terms, err := files.ReadFile("terms.html")
|
||||
if err != nil {
|
||||
panic("site: terms.html 缺失: " + err.Error())
|
||||
}
|
||||
return &Handler{html: string(raw), terms: string(terms)}
|
||||
}
|
||||
|
||||
// Register 注册路由:/ 与 /home 均渲染官网
|
||||
func (h *Handler) Register(r *gin.Engine) {
|
||||
r.GET("/", h.Page)
|
||||
r.GET("/home", h.Page)
|
||||
r.GET("/index.html", h.Page)
|
||||
r.GET("/site/assets/*file", h.Asset)
|
||||
r.GET("/terms", h.Terms)
|
||||
}
|
||||
|
||||
// Terms 渲染服务条款
|
||||
func (h *Handler) Terms(c *gin.Context) {
|
||||
c.Header("Content-Type", "text/html; charset=utf-8")
|
||||
c.Header("Cache-Control", "no-cache")
|
||||
_, _ = c.Writer.WriteString(h.terms)
|
||||
}
|
||||
|
||||
// Page 渲染官网
|
||||
func (h *Handler) Page(c *gin.Context) {
|
||||
c.Header("Content-Type", "text/html; charset=utf-8")
|
||||
c.Header("Cache-Control", "no-cache")
|
||||
_, _ = c.Writer.WriteString(h.html)
|
||||
}
|
||||
|
||||
// Asset 静态资源
|
||||
func (h *Handler) Asset(c *gin.Context) {
|
||||
name := strings.TrimPrefix(c.Param("file"), "/")
|
||||
if strings.Contains(name, "..") {
|
||||
c.Status(http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
data, err := files.ReadFile("assets/" + name)
|
||||
if err != nil {
|
||||
c.Status(http.StatusNotFound)
|
||||
return
|
||||
}
|
||||
switch {
|
||||
case strings.HasSuffix(name, ".css"):
|
||||
c.Header("Content-Type", "text/css; charset=utf-8")
|
||||
case strings.HasSuffix(name, ".js"):
|
||||
c.Header("Content-Type", "application/javascript; charset=utf-8")
|
||||
case strings.HasSuffix(name, ".svg"):
|
||||
c.Header("Content-Type", "image/svg+xml")
|
||||
default:
|
||||
c.Header("Content-Type", "application/octet-stream")
|
||||
}
|
||||
c.Header("Cache-Control", "public, max-age=3600")
|
||||
c.Data(http.StatusOK, "", data)
|
||||
}
|
||||
@@ -1,63 +0,0 @@
|
||||
package site
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
func newTestRouter(t *testing.T) *gin.Engine {
|
||||
t.Helper()
|
||||
gin.SetMode(gin.TestMode)
|
||||
r := gin.New()
|
||||
New().Register(r)
|
||||
return r
|
||||
}
|
||||
|
||||
func TestSitePageRender(t *testing.T) {
|
||||
r := newTestRouter(t)
|
||||
for _, path := range []string{"/", "/home", "/index.html"} {
|
||||
w := httptest.NewRecorder()
|
||||
r.ServeHTTP(w, httptest.NewRequest(http.MethodGet, path, nil))
|
||||
if w.Code != http.StatusOK {
|
||||
t.Fatalf("%s: expect 200, got %d", path, w.Code)
|
||||
}
|
||||
body := w.Body.String()
|
||||
for _, want := range []string{
|
||||
"<title>OpsxAPI", // 标题
|
||||
`id="features"`, // 功能区
|
||||
`id="pricing"`, // 定价区
|
||||
"¥99", // 入门价
|
||||
"¥8,000", // 企业档标价
|
||||
"¥30,000", // 买断标价
|
||||
"¥5,000–8,000", // 成交区间
|
||||
"永久买断", // 买断 FAQ
|
||||
"早鸟创始价", // 让利策略
|
||||
"Socket.IO", // 协议覆盖
|
||||
"hello@opsxapi.dev", // 联系方式
|
||||
`href="/docs"`, // 文档互链
|
||||
`href="/site/assets/site.css"`, // 样式资源
|
||||
} {
|
||||
if !strings.Contains(body, want) {
|
||||
t.Fatalf("%s: missing %q", path, want)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestSiteAsset(t *testing.T) {
|
||||
r := newTestRouter(t)
|
||||
w := httptest.NewRecorder()
|
||||
r.ServeHTTP(w, httptest.NewRequest(http.MethodGet, "/site/assets/site.css", nil))
|
||||
if w.Code != http.StatusOK || !strings.Contains(w.Header().Get("Content-Type"), "text/css") {
|
||||
t.Fatalf("css: %d %s", w.Code, w.Header().Get("Content-Type"))
|
||||
}
|
||||
w = httptest.NewRecorder()
|
||||
r.ServeHTTP(w, httptest.NewRequest(http.MethodGet, "/site/assets/../template.html", nil))
|
||||
if w.Code != http.StatusBadRequest && w.Code != http.StatusNotFound {
|
||||
t.Fatalf("path traversal should be rejected, got %d", w.Code)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
module opsxapi/tools/homepage-build
|
||||
|
||||
go 1.25.0
|
||||
|
||||
require github.com/yuin/goldmark v1.8.6
|
||||
@@ -0,0 +1,2 @@
|
||||
github.com/yuin/goldmark v1.8.6 h1:d0VcaP1sx9GkFVkoW+KtggpGi2KZ965i14b0+bDQST4=
|
||||
github.com/yuin/goldmark v1.8.6/go.mod h1:ip/1k0VRfGynBgxOz0yCqHrbZXhcjxyuS66Brc7iBKg=
|
||||
@@ -0,0 +1,190 @@
|
||||
// homepage-build:把 homepage/ 源(落地页 + 定价页 + 服务条款 + 文档 md)构建为纯静态站点 homepage/dist/。
|
||||
// 产物可直接部署到任意静态托管(nginx/Pages)——官网与后端二进制彻底解耦。
|
||||
// 用法: go run . [-src homepage] [-out homepage/dist]
|
||||
package main
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"flag"
|
||||
"fmt"
|
||||
"html/template"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"regexp"
|
||||
"strings"
|
||||
|
||||
"github.com/yuin/goldmark"
|
||||
"github.com/yuin/goldmark/extension"
|
||||
gmhtml "github.com/yuin/goldmark/renderer/html"
|
||||
)
|
||||
|
||||
type PageDef struct {
|
||||
File string `json:"file"`
|
||||
Slug string `json:"slug"`
|
||||
Title string `json:"title"`
|
||||
Updated string `json:"updated,omitempty"`
|
||||
}
|
||||
type GroupDef struct {
|
||||
Name string `json:"name"`
|
||||
Pages []PageDef `json:"pages"`
|
||||
}
|
||||
type Manifest struct {
|
||||
ProductVersion string `json:"productVersion"`
|
||||
Groups []GroupDef `json:"groups"`
|
||||
}
|
||||
type TocItem struct {
|
||||
ID string
|
||||
Level int
|
||||
Text string
|
||||
}
|
||||
type SearchEntry struct {
|
||||
Slug, Title, Group, Text string
|
||||
}
|
||||
type NavLink struct {
|
||||
Slug, Title string
|
||||
}
|
||||
type Page struct {
|
||||
Slug, Title, Group, Updated string
|
||||
HTML string
|
||||
TOC []TocItem
|
||||
Prev, Next *NavLink
|
||||
}
|
||||
|
||||
var (
|
||||
hRe = regexp.MustCompile(`(?s)<(h[23])>(.*?)</h[23]>`)
|
||||
tagRe = regexp.MustCompile(`<[^>]+>`)
|
||||
mdNoise = regexp.MustCompile("(?s)```.*?```|`[^`]*`|!\\[[^\\]]*\\]\\([^)]*\\)|\\[[^\\]]*\\]\\([^)]*\\)|[#>*_\\-|]+")
|
||||
)
|
||||
|
||||
func injectHeadingIDs(html string) (string, []TocItem) {
|
||||
var toc []TocItem
|
||||
counter := 0
|
||||
out := hRe.ReplaceAllStringFunc(html, func(m string) string {
|
||||
sub := hRe.FindStringSubmatch(m)
|
||||
tag, inner := sub[1], sub[2]
|
||||
counter++
|
||||
id := fmt.Sprintf("sec-%d", counter)
|
||||
text := strings.TrimSpace(tagRe.ReplaceAllString(inner, ""))
|
||||
level := 2
|
||||
if tag == "h3" {
|
||||
level = 3
|
||||
}
|
||||
toc = append(toc, TocItem{ID: id, Level: level, Text: text})
|
||||
return fmt.Sprintf(`<%s id="%s">%s</%s>`, tag, id, inner, tag)
|
||||
})
|
||||
return out, toc
|
||||
}
|
||||
|
||||
func plainText(src string) string {
|
||||
s := mdNoise.ReplaceAllString(src, " ")
|
||||
s = strings.Join(strings.Fields(s), " ")
|
||||
if len(s) > 6000 {
|
||||
s = s[:6000]
|
||||
}
|
||||
return s
|
||||
}
|
||||
|
||||
func must(err error, what string) {
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, "[homepage-build] %s: %v\n", what, err)
|
||||
os.Exit(1)
|
||||
}
|
||||
}
|
||||
|
||||
func writeFile(path, content string) {
|
||||
must(os.MkdirAll(filepath.Dir(path), 0o755), "mkdir "+path)
|
||||
must(os.WriteFile(path, []byte(content), 0o644), "write "+path)
|
||||
fmt.Println("[OK]", path)
|
||||
}
|
||||
|
||||
func main() {
|
||||
src := flag.String("src", "homepage", "源目录")
|
||||
out := flag.String("out", "homepage/dist", "产物目录")
|
||||
flag.Parse()
|
||||
|
||||
// 幂等重建:清理上一轮生成的单页/资源目录(os.CopyFS 不会覆盖已存在的文件)
|
||||
for _, d := range []string{"site", "docs", "pricing", "terms", "license"} {
|
||||
must(os.RemoveAll(filepath.Join(*out, d)), "清理 "+d)
|
||||
}
|
||||
|
||||
// 1) 落地页 + 附加单页:index.html 必需;pricing/terms/license 源存在即产出。
|
||||
// 附加页同时产出 <name>.html 与 <name>/index.html:兼容 nginx 的干净
|
||||
// URL(location = /license) 与纯静态服务器(python -m http.server)。 // Logan.Li
|
||||
for _, page := range []string{"index.html", "pricing.html", "terms.html", "license.html"} {
|
||||
raw, err := os.ReadFile(filepath.Join(*src, page))
|
||||
if err != nil {
|
||||
if page == "index.html" {
|
||||
must(err, "读 index.html")
|
||||
}
|
||||
continue
|
||||
}
|
||||
writeFile(filepath.Join(*out, page), string(raw))
|
||||
if page != "index.html" {
|
||||
writeFile(filepath.Join(*out, strings.TrimSuffix(page, ".html"), "index.html"), string(raw))
|
||||
}
|
||||
}
|
||||
siteAssets := filepath.Join(*src, "assets")
|
||||
if st, _ := os.Stat(siteAssets); st != nil {
|
||||
// 落地页模板引用 /site/assets/*,故产物固定落在 dist/site/assets/
|
||||
must(os.CopyFS(filepath.Join(*out, "site", "assets"), os.DirFS(siteAssets)), "拷 site assets")
|
||||
fmt.Println("[OK]", filepath.Join(*out, "site", "assets"))
|
||||
}
|
||||
|
||||
// 2) 文档: manifest → goldmark 渲染 → docs.html 模板
|
||||
mraw, err := os.ReadFile(filepath.Join(*src, "content", "manifest.json"))
|
||||
must(err, "读 manifest")
|
||||
var manifest Manifest
|
||||
must(json.Unmarshal(mraw, &manifest), "解析 manifest")
|
||||
tplRaw, err := os.ReadFile(filepath.Join(*src, "docs.html"))
|
||||
must(err, "读 docs.html")
|
||||
tpl, err := template.New("doc").Parse(string(tplRaw))
|
||||
must(err, "解析 docs 模板")
|
||||
md := goldmark.New(goldmark.WithExtensions(extension.Table, extension.Strikethrough, extension.TaskList), goldmark.WithRendererOptions(gmhtml.WithHardWraps()))
|
||||
|
||||
var order []string
|
||||
pages := map[string]*Page{}
|
||||
var search []SearchEntry
|
||||
var prev *NavLink
|
||||
for _, g := range manifest.Groups {
|
||||
for _, def := range g.Pages {
|
||||
srcb, err := os.ReadFile(filepath.Join(*src, "content", def.File))
|
||||
must(err, "读文档 "+def.File)
|
||||
var buf strings.Builder
|
||||
must(md.Convert(srcb, &buf), "渲染 "+def.File)
|
||||
body, toc := injectHeadingIDs(buf.String())
|
||||
p := &Page{Slug: def.Slug, Title: def.Title, Group: g.Name, Updated: def.Updated, HTML: body, TOC: toc, Prev: prev}
|
||||
pages[def.Slug] = p
|
||||
order = append(order, def.Slug)
|
||||
prev = &NavLink{Slug: def.Slug, Title: def.Title}
|
||||
search = append(search, SearchEntry{Slug: def.Slug, Title: def.Title, Group: g.Name, Text: plainText(string(srcb))})
|
||||
}
|
||||
}
|
||||
for i, slug := range order {
|
||||
if i+1 < len(order) {
|
||||
n := pages[order[i+1]]
|
||||
pages[slug].Next = &NavLink{Slug: n.Slug, Title: n.Title}
|
||||
}
|
||||
}
|
||||
|
||||
render := func(p *Page) string {
|
||||
var sb strings.Builder
|
||||
must(tpl.Execute(&sb, map[string]any{"Manifest": manifest, "Page": p, "TOC": p.TOC, "Content": template.HTML(p.HTML)}), "渲染模板 "+p.Slug)
|
||||
return sb.String()
|
||||
}
|
||||
// /docs/ 首页 = 第一篇(静态站无重定向,直接渲染)
|
||||
writeFile(filepath.Join(*out, "docs", "index.html"), render(pages[order[0]]))
|
||||
for _, slug := range order {
|
||||
writeFile(filepath.Join(*out, "docs", slug, "index.html"), render(pages[slug]))
|
||||
}
|
||||
sj, _ := json.Marshal(search)
|
||||
writeFile(filepath.Join(*out, "docs", "search-data.json"), string(sj))
|
||||
|
||||
// docs assets(模板引用 /docs/assets/*)
|
||||
da := filepath.Join(*src, "docs-assets")
|
||||
if st, _ := os.Stat(da); st != nil {
|
||||
must(os.CopyFS(filepath.Join(*out, "docs", "assets"), os.DirFS(da)), "拷 docs assets")
|
||||
fmt.Println("[OK]", filepath.Join(*out, "docs", "assets"))
|
||||
}
|
||||
|
||||
fmt.Printf("[DONE] 落地页 + %d 页文档 → %s(纯静态,可部署任意域名)\n", len(order), *out)
|
||||
}
|
||||
Reference in new issue
Block a user