Archived
feat(api): 实装 /api/status 接口——无鉴权公开接口,换算比率权威来源
- endpoints:AuthMode 扩展 'none'(完全公开),追加 /api/status 定义(考证实证文案 + 实测示例响应),置于列表首位 - endpoint-info-line:鉴权徽标三态化(公开/宽松/严格,map 驱动 + none 中性样式) - endpoint-panel:requiresKey——无鉴权接口隐藏 sk-key 输入行、按钮不因空 key 禁用、空态文案区分 - spec:接口数 5→6、顺序、鉴权分布(none+strict×3+lenient×2)断言更新 - docs:接口文档同步 479 行新版(/api/status 详解 + QuotaPerUnit 考证);交互设计 §1 清单加第 0 行;README 与 side-nav 计数更新 - 验证:test 64/64 + type-check + lint + build 四绿
This commit is contained in:
@@ -35,7 +35,7 @@ bun run build # type-check + vite build(dist/)
|
|||||||
|
|
||||||
## 使用
|
## 使用
|
||||||
|
|
||||||
1. 左侧选择接口(5 个,平铺不分组)→ 右侧开 tab
|
1. 左侧选择接口(6 个,平铺不分组)→ 右侧开 tab
|
||||||
2. tab 内展开「注意事项」看该接口的实证调研坑文档
|
2. tab 内展开「注意事项」看该接口的实证调研坑文档
|
||||||
3. 填 sk-key(每 tab 独立、仅存内存、刷新即重置,不落盘;切换 tab 输入保留)
|
3. 填 sk-key(每 tab 独立、仅存内存、刷新即重置,不落盘;切换 tab 输入保留)
|
||||||
4. 点「发送请求」→ 响应区显示元信息(状态/耗时/字符数)+ 虚拟滚动高亮 JSON
|
4. 点「发送请求」→ 响应区显示元信息(状态/耗时/字符数)+ 虚拟滚动高亮 JSON
|
||||||
|
|||||||
@@ -37,6 +37,14 @@ Authorization: Bearer sk-xxxxxxxxxxxx
|
|||||||
|------|-----|------|
|
|------|-----|------|
|
||||||
| `QuotaPerUnit` | 500000 | 实测验证:接口① `total_used` ÷ 500000 = 接口④ `total_usage` ÷ 100 |
|
| `QuotaPerUnit` | 500000 | 实测验证:接口① `total_used` ÷ 500000 = 接口④ `total_usage` ÷ 100 |
|
||||||
|
|
||||||
|
**此值是 NewAPI 的默认值(源码 `common/constants.go:22`:`var QuotaPerUnit = 500 * 1000.0 // $0.002 / 1K tokens`),并非硬编码定律**:
|
||||||
|
|
||||||
|
- 它是 `var` 且挂入 OptionMap(`model/option.go:166`),站长可在后台修改(`model/option.go:605` 会用数据库值覆盖)
|
||||||
|
- 接口①④交叉实测吻合的原因:billing 接口内部就是拿 quota `÷ QuotaPerUnit` 换算(`controller/billing.go:50`),同一常量自证,**吻合验证不了管理员是否改过此值**
|
||||||
|
- **正确做法:换算比率不硬编码,从 `/api/status` 的 `quota_per_unit` 字段读取**(见接口⓪),启动时拉取并定期校验,站点改配置后工具自动跟随
|
||||||
|
|
||||||
|
> `500000` 的设计来由:NewAPI(继承自 One API)以 OpenAI 早期基准定价 **$0.002 / 1K tokens** 为锚点,内部用整数 quota 计费避免浮点误差,1 quota = $0.000002,反推 1 美元 = 500,000 quota。
|
||||||
|
|
||||||
### 关键开关:DisplayTokenStatEnabled
|
### 关键开关:DisplayTokenStatEnabled
|
||||||
|
|
||||||
| 接口 | 受开关影响? |
|
| 接口 | 受开关影响? |
|
||||||
@@ -54,6 +62,7 @@ Authorization: Bearer sk-xxxxxxxxxxxx
|
|||||||
|
|
||||||
| 序号 | 方法 | 路径 | 鉴权 | 用途 |
|
| 序号 | 方法 | 路径 | 鉴权 | 用途 |
|
||||||
|------|------|------|------|------|
|
|------|------|------|------|------|
|
||||||
|
| ⓪ | GET | `/api/status` | **无(完全公开)** | 站点配置与计费参数(含 `quota_per_unit`),工具校准换算比率的依据 |
|
||||||
| ① | GET | `/api/usage/token` | TokenAuthReadOnly | 查 token 的累计已用/剩余/总额(原始 quota) |
|
| ① | GET | `/api/usage/token` | TokenAuthReadOnly | 查 token 的累计已用/剩余/总额(原始 quota) |
|
||||||
| ② | GET | `/api/log/token` | TokenAuthReadOnly | 查该 key 最近 1000 条调用日志 |
|
| ② | GET | `/api/log/token` | TokenAuthReadOnly | 查该 key 最近 1000 条调用日志 |
|
||||||
| ③ | GET | `/v1/dashboard/billing/subscription` | TokenAuth | 查总额度上限(OpenAI 兼容,美元) |
|
| ③ | GET | `/v1/dashboard/billing/subscription` | TokenAuth | 查总额度上限(OpenAI 兼容,美元) |
|
||||||
@@ -73,6 +82,45 @@ Authorization: Bearer sk-xxxxxxxxxxxx
|
|||||||
|
|
||||||
## 2. 用量/额度查询接口详解
|
## 2. 用量/额度查询接口详解
|
||||||
|
|
||||||
|
### ⓪ GET `/api/status` - 站点配置与计费参数(完全公开)
|
||||||
|
|
||||||
|
**鉴权**:无。挂载于 `router/api-router.go:26`,在鉴权中间件组之外,任何人(不持有任何 key)可直接访问。本清单中唯一无需凭证的接口。
|
||||||
|
|
||||||
|
**请求参数**:无
|
||||||
|
|
||||||
|
**请求示例**:
|
||||||
|
|
||||||
|
```
|
||||||
|
GET /api/status
|
||||||
|
```
|
||||||
|
|
||||||
|
**返回结构**(源码:`controller/misc.go:44` -> `GetStatus`):外层为 `{ success, message, data }`,`data` 含 60+ 字段(版本、登录方式、前端模块开关、公告等)。**与本项目相关的计费字段**:
|
||||||
|
|
||||||
|
| 字段 | 类型 | 说明 | 实测值(2026-09-09) |
|
||||||
|
|------|------|------|------|
|
||||||
|
| `quota_per_unit` | float | **QuotaPerUnit 当前生效值**。quota ÷ 该值 = 美元。管理员可改,工具不得硬编码 | `500000` |
|
||||||
|
| `quota_display_type` | string | 站点额度展示模式:`USD` / `CNY` / `TOKENS`。影响 billing 接口(③④)的语义 | `"USD"` |
|
||||||
|
| `usd_exchange_rate` | float | USD→CNY 展示汇率(`quota_display_type=CNY` 时 billing 接口用它换算) | `6.8` |
|
||||||
|
| `display_in_currency` | bool | 是否以货币展示额度(旧字段,兼容保留) | `true` |
|
||||||
|
| `custom_currency_symbol` | string | 自定义货币符号(`¤` = 未自定义) | `"¤"` |
|
||||||
|
| `custom_currency_exchange_rate` | float | 自定义货币汇率 | `1` |
|
||||||
|
| `price` | float | 充值价格(人民币/单位),站点充值汇率相关 | `6.8` |
|
||||||
|
| `version` | string | NewAPI 版本号 | `"v1.0.0-rc.35"` |
|
||||||
|
|
||||||
|
**对本项目的意义**:
|
||||||
|
|
||||||
|
1. **校准换算比率**:`quota_per_unit` 是工具换算美元的唯一权威来源——管理员改配置后,工具启动时拉取 + 定期校验即可自动跟随,不会算错
|
||||||
|
2. **感知展示模式切换**:若管理员把 `quota_display_type` 切到 `CNY` 或 `TOKENS`,billing 接口(③④)返回的数字语义会变(CNY 是先 ÷QuotaPerUnit 再 ×`usd_exchange_rate`;TOKENS 直接返回 token 数),工具可提前探测到,避免拿旧逻辑解析新语义
|
||||||
|
3. **零成本**:完全公开、无鉴权、无调用门槛,可放心定期轮询
|
||||||
|
|
||||||
|
**注意事项**:
|
||||||
|
|
||||||
|
- 返回字段很多且含大量前端 UI 配置(`HeaderNavModules`、`SidebarModulesAdmin` 等),解析时只取计费相关字段,其余忽略
|
||||||
|
- 字段集合随版本演进(如开启公告开关会追加 `announcements`),解析需容忍未知字段
|
||||||
|
- 该接口不泄露敏感信息(secret 类 OptionMap 键已被 NewAPI 过滤),这也是它敢完全公开的原因
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
### ① GET `/api/usage/token` - token 级用量汇总
|
### ① GET `/api/usage/token` - token 级用量汇总
|
||||||
|
|
||||||
**鉴权**:`TokenAuthReadOnly()`(宽松版,key 过期/耗尽也能查)
|
**鉴权**:`TokenAuthReadOnly()`(宽松版,key 过期/耗尽也能查)
|
||||||
@@ -364,7 +412,7 @@ total_usage = 134683.8914 美分 ÷ 100 = 1346.84 美元
|
|||||||
### 核心换算公式
|
### 核心换算公式
|
||||||
|
|
||||||
```
|
```
|
||||||
原始 quota ÷ 500000 = 美元
|
原始 quota ÷ quota_per_unit(/api/status 实时读取,当前站点值 500000)= 美元
|
||||||
```
|
```
|
||||||
|
|
||||||
### 实测数据验证
|
### 实测数据验证
|
||||||
@@ -423,7 +471,9 @@ total_usage = 134683.8914 美分 ÷ 100 = 1346.84 美元
|
|||||||
| billing 查询 | `controller/billing.go:75` | `GetUsage` |
|
| billing 查询 | `controller/billing.go:75` | `GetUsage` |
|
||||||
| 显示开关 | `common/constants.go` | `DisplayTokenStatEnabled` |
|
| 显示开关 | `common/constants.go` | `DisplayTokenStatEnabled` |
|
||||||
| 日志上限 | `common/constants.go` | `MaxRecentItems = 1000` |
|
| 日志上限 | `common/constants.go` | `MaxRecentItems = 1000` |
|
||||||
| 配额单位 | `common/constants.go` | `QuotaPerUnit`(默认 500000) |
|
| 配额单位 | `common/constants.go:22` | `QuotaPerUnit`(var,默认 500000,`// $0.002 / 1K tokens`,可被 OptionMap 覆盖) |
|
||||||
|
| 配额单位(覆盖入口) | `model/option.go:605` | `case "QuotaPerUnit"` 运行时改写 |
|
||||||
|
| 站点配置暴露 | `controller/misc.go:44` + `router/api-router.go:26` | `GetStatus`(`/api/status`,无鉴权,含 `quota_per_unit`) |
|
||||||
| token 更新 | `model/token.go` | `Update()`(Select 不含 used_quota) |
|
| token 更新 | `model/token.go` | `Update()`(Select 不含 used_quota) |
|
||||||
| 额度增减 | `model/token.go` | `increaseTokenQuota` / `decreaseTokenQuota` |
|
| 额度增减 | `model/token.go` | `increaseTokenQuota` / `decreaseTokenQuota` |
|
||||||
|
|
||||||
|
|||||||
+5
-4
@@ -8,20 +8,21 @@
|
|||||||
|
|
||||||
## 1. 项目定位
|
## 1. 项目定位
|
||||||
|
|
||||||
替代 Postman 的专用调试台(bench):左侧选接口 → 右侧 tab 内看文档、填 key、发请求、看原始响应。对接 5 个接口,服务 karpool 拼车项目的接口调试与换算验证。
|
替代 Postman 的专用调试台(bench):左侧选接口 → 右侧 tab 内看文档、填 key、发请求、看原始响应。对接 6 个接口,服务 karpool 拼车项目的接口调试与换算验证。
|
||||||
|
|
||||||
**接口清单**(数据源:`docs/NewAPI-sk-key接口文档.md`,按此顺序展示,不分组):
|
**接口清单**(数据源:`docs/NewAPI-sk-key接口文档.md`,按此顺序展示,不分组):
|
||||||
|
|
||||||
| 序 | 路径 | 鉴权 | 响应特征 |
|
| 序 | 路径 | 鉴权 | 响应特征 |
|
||||||
|---|------|------|---------|
|
|---|------|------|---------|
|
||||||
|
| 0 | `GET /api/status` | 无(完全公开) | 站点配置与计费参数:`quota_per_unit`(换算比率权威来源)、`quota_display_type`、汇率等 |
|
||||||
| 1 | `GET /v1/dashboard/billing/subscription` | TokenAuth(严格) | 小 JSON,美元额度(无限额时 1 亿占位) |
|
| 1 | `GET /v1/dashboard/billing/subscription` | TokenAuth(严格) | 小 JSON,美元额度(无限额时 1 亿占位) |
|
||||||
| 2 | `GET /v1/dashboard/billing/usage` | TokenAuth(严格) | 小 JSON,累计美分 |
|
| 2 | `GET /v1/dashboard/billing/usage` | TokenAuth(严格) | 小 JSON,累计美分 |
|
||||||
| 3 | `GET /v1/models` | TokenAuth(严格) | 中等 JSON,模型数组 |
|
| 3 | `GET /v1/models` | TokenAuth(严格) | 中等 JSON,模型数组 |
|
||||||
| 4 | `GET /api/usage/token` | TokenAuthReadOnly(宽松) | 小 JSON,累计 quota |
|
| 4 | `GET /api/usage/token` | TokenAuthReadOnly(宽松) | 小 JSON,累计 quota |
|
||||||
| 5 | `GET /api/log/token` | TokenAuthReadOnly(宽松) | **大响应**:最近 1000 条日志,pretty-print 2~4MB |
|
| 5 | `GET /api/log/token` | TokenAuthReadOnly(宽松) | **大响应**:最近 1000 条日志,pretty-print 2~4MB |
|
||||||
|
|
||||||
> 4、5 宽松鉴权(key 过期/耗尽仍可查);1~3 严格鉴权(key 无效直接被拒)。tab 内标出该差异,避免误判「接口坏了」。
|
> 0 完全公开(无需任何凭证,面板隐藏 sk-key 输入行);4、5 宽松鉴权(key 过期/耗尽仍可查);1~3 严格鉴权(key 无效直接被拒)。tab 内标出该差异,避免误判「接口坏了」。
|
||||||
> 4 与 2 构成换算交叉验证对(total_used ÷ 500000 ≈ total_usage ÷ 100),Tabs 并行对账是核心工作流。
|
> 4 与 2 构成换算交叉验证对(total_used ÷ 500000 ≈ total_usage ÷ 100),Tabs 并行对账是核心工作流。0 是换算比率的权威来源(quota_per_unit 可被管理员修改,500000 只是默认值)。
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -52,7 +53,7 @@
|
|||||||
无分组无编号,按清单顺序)
|
无分组无编号,按清单顺序)
|
||||||
```
|
```
|
||||||
|
|
||||||
- **左 sider**(~300px):接口列表,**不分组、不编号**,按 §1 清单顺序平铺 5 个路径;选中态高亮
|
- **左 sider**(~300px):接口列表,**不分组、不编号**,按 §1 清单顺序平铺 6 个路径;选中态高亮
|
||||||
- **右主区**:`t-tabs` 顶部,激活 tab 内容 = 请求行(method + 路径 + 鉴权徽标)→ 调试行(sk-key 输入 + 发送按钮)→ 响应区(元信息 + 虚拟滚动 JSON)
|
- **右主区**:`t-tabs` 顶部,激活 tab 内容 = 请求行(method + 路径 + 鉴权徽标)→ 调试行(sk-key 输入 + 发送按钮)→ 响应区(元信息 + 虚拟滚动 JSON)
|
||||||
- 暗色默认(TDesign dark theme + CSS 变量)
|
- 暗色默认(TDesign dark theme + CSS 变量)
|
||||||
|
|
||||||
|
|||||||
@@ -3,17 +3,18 @@ import { describe, expect, it } from 'vitest';
|
|||||||
import { endpointList, findEndpointByPath } from './endpoints';
|
import { endpointList, findEndpointByPath } from './endpoints';
|
||||||
|
|
||||||
describe('endpointList 数据完整性', () => {
|
describe('endpointList 数据完整性', () => {
|
||||||
it('恰好 5 个接口(设计文档 §1 定案的对接清单)', () => {
|
it('恰好 6 个接口(设计文档 §1 定案的对接清单)', () => {
|
||||||
expect(endpointList).toHaveLength(5);
|
expect(endpointList).toHaveLength(6);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('路径唯一(path 是 tab / queryKey 的唯一标识)', () => {
|
it('路径唯一(path 是 tab / queryKey 的唯一标识)', () => {
|
||||||
const pathList = endpointList.map((endpoint) => endpoint.path);
|
const pathList = endpointList.map((endpoint) => endpoint.path);
|
||||||
expect(new Set(pathList).size).toBe(5);
|
expect(new Set(pathList).size).toBe(6);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('顺序与用户指定清单一致', () => {
|
it('顺序与用户指定清单一致', () => {
|
||||||
expect(endpointList.map((endpoint) => endpoint.path)).toEqual([
|
expect(endpointList.map((endpoint) => endpoint.path)).toEqual([
|
||||||
|
'/api/status',
|
||||||
'/v1/dashboard/billing/subscription',
|
'/v1/dashboard/billing/subscription',
|
||||||
'/v1/dashboard/billing/usage',
|
'/v1/dashboard/billing/usage',
|
||||||
'/v1/models',
|
'/v1/models',
|
||||||
@@ -25,13 +26,14 @@ describe('endpointList 数据完整性', () => {
|
|||||||
it('全部为 GET 且带鉴权元数据', () => {
|
it('全部为 GET 且带鉴权元数据', () => {
|
||||||
for (const endpoint of endpointList) {
|
for (const endpoint of endpointList) {
|
||||||
expect(endpoint.method).toBe('GET');
|
expect(endpoint.method).toBe('GET');
|
||||||
expect(['strict', 'lenient']).toContain(endpoint.authMode);
|
expect(['strict', 'lenient', 'none']).toContain(endpoint.authMode);
|
||||||
expect(endpoint.authMiddlewareName).toBeTruthy();
|
expect(endpoint.authMiddlewareName).toBeTruthy();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
it('严格/宽松鉴权分布正确(前 3 严格,后 2 宽松)', () => {
|
it('鉴权模式分布正确(公开 1 + 严格 3 + 宽松 2)', () => {
|
||||||
expect(endpointList.map((endpoint) => endpoint.authMode)).toEqual([
|
expect(endpointList.map((endpoint) => endpoint.authMode)).toEqual([
|
||||||
|
'none',
|
||||||
'strict',
|
'strict',
|
||||||
'strict',
|
'strict',
|
||||||
'strict',
|
'strict',
|
||||||
|
|||||||
+32
-3
@@ -1,17 +1,17 @@
|
|||||||
/**
|
/**
|
||||||
* 5 个对接接口的静态元数据
|
* 对接接口的静态元数据
|
||||||
* 数据源:docs/NewAPI-sk-key接口文档.md(源码实证调研)
|
* 数据源:docs/NewAPI-sk-key接口文档.md(源码实证调研)
|
||||||
* 顺序即 sider / tab 展示顺序(设计文档 §1 定案,不分组不编号)
|
* 顺序即 sider / tab 展示顺序(设计文档 §1 定案,不分组不编号)
|
||||||
*/
|
*/
|
||||||
|
|
||||||
export type AuthMode = 'strict' | 'lenient';
|
export type AuthMode = 'strict' | 'lenient' | 'none';
|
||||||
|
|
||||||
export interface EndpointDefinition {
|
export interface EndpointDefinition {
|
||||||
/** 接口路径(唯一标识,同时用作 tab 标签与 queryKey 组成部分) */
|
/** 接口路径(唯一标识,同时用作 tab 标签与 queryKey 组成部分) */
|
||||||
readonly path: string;
|
readonly path: string;
|
||||||
/** HTTP 方法(当前全部为 GET,保留扩展位) */
|
/** HTTP 方法(当前全部为 GET,保留扩展位) */
|
||||||
readonly method: 'GET';
|
readonly method: 'GET';
|
||||||
/** 鉴权模式:strict = TokenAuth(key 无效/过期/耗尽直接被拒);lenient = TokenAuthReadOnly(宽松,废 key 也能查) */
|
/** 鉴权模式:strict = TokenAuth(key 无效/过期/耗尽直接被拒);lenient = TokenAuthReadOnly(宽松,废 key 也能查);none = 完全公开(无需任何凭证) */
|
||||||
readonly authMode: AuthMode;
|
readonly authMode: AuthMode;
|
||||||
/** 鉴权中间件名(展示用) */
|
/** 鉴权中间件名(展示用) */
|
||||||
readonly authMiddlewareName: string;
|
readonly authMiddlewareName: string;
|
||||||
@@ -24,6 +24,35 @@ export interface EndpointDefinition {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export const endpointList: readonly EndpointDefinition[] = [
|
export const endpointList: readonly EndpointDefinition[] = [
|
||||||
|
{
|
||||||
|
path: '/api/status',
|
||||||
|
method: 'GET',
|
||||||
|
authMode: 'none',
|
||||||
|
authMiddlewareName: '无(公开接口)',
|
||||||
|
responseSummary: '站点配置与计费参数(含 quota_per_unit)',
|
||||||
|
notes: [
|
||||||
|
'完全公开:无任何鉴权,不持有 sk-key 也能访问(本工具对接清单中唯一)',
|
||||||
|
'quota_per_unit 是 QuotaPerUnit 当前生效值(默认 500000,源码 $0.002 / 1K tokens 锚点)——quota ÷ 该值 = 美元,换算比率的唯一权威来源,管理员可改,不要硬编码',
|
||||||
|
'接口①④交叉实测吻合验证不了管理员是否改过此值:billing 接口内部就是拿 quota ÷ QuotaPerUnit 换算,同一常量自证',
|
||||||
|
'quota_display_type 影响 billing 接口语义:USD 直接是美元;CNY 先 ÷QuotaPerUnit 再 ×usd_exchange_rate;TOKENS 是原始 token 数',
|
||||||
|
'返回 60+ 字段(版本、登录方式、前端模块开关、公告等),解析时只取计费相关字段,其余忽略;字段集合随版本演进,需容忍未知字段',
|
||||||
|
],
|
||||||
|
sampleResponse: {
|
||||||
|
success: true,
|
||||||
|
message: '',
|
||||||
|
data: {
|
||||||
|
quota_per_unit: 500000,
|
||||||
|
quota_display_type: 'USD',
|
||||||
|
usd_exchange_rate: 6.8,
|
||||||
|
display_in_currency: true,
|
||||||
|
custom_currency_symbol: '¤',
|
||||||
|
custom_currency_exchange_rate: 1,
|
||||||
|
price: 6.8,
|
||||||
|
system_name: 'AI Gateway',
|
||||||
|
version: 'v1.0.0-rc.35',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
{
|
{
|
||||||
path: '/v1/dashboard/billing/subscription',
|
path: '/v1/dashboard/billing/subscription',
|
||||||
method: 'GET',
|
method: 'GET',
|
||||||
|
|||||||
@@ -1,18 +1,33 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
/**
|
/**
|
||||||
* 接口信息行:method 徽标 + 路径 + 鉴权徽标(宽松/严格)
|
* 接口信息行:method 徽标 + 路径 + 鉴权徽标(公开/宽松/严格)
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import type { EndpointDefinition } from '@/api/endpoints';
|
import type { EndpointDefinition } from '@/api/endpoints';
|
||||||
|
|
||||||
const props = defineProps<{ endpoint: EndpointDefinition }>();
|
const props = defineProps<{ endpoint: EndpointDefinition }>();
|
||||||
|
|
||||||
const authBadgeText = props.endpoint.authMode === 'lenient' ? '宽松' : '严格';
|
const authBadgeTextMap: Record<string, string> = {
|
||||||
const authBadgeTitle = `${props.endpoint.authMiddlewareName}(${
|
lenient: '宽松',
|
||||||
props.endpoint.authMode === 'lenient'
|
strict: '严格',
|
||||||
? 'key 过期/禁用/额度耗尽仍可查询'
|
none: '公开',
|
||||||
: 'key 无效/过期/额度耗尽会被拒绝'
|
};
|
||||||
})`;
|
|
||||||
|
const authBadgeTitleMap: Record<string, string> = {
|
||||||
|
lenient: 'TokenAuthReadOnly(key 过期/禁用/额度耗尽仍可查询)',
|
||||||
|
strict: 'TokenAuth(key 无效/过期/额度耗尽会被拒绝)',
|
||||||
|
none: '无鉴权中间件(不持有任何 key 也能访问)',
|
||||||
|
};
|
||||||
|
|
||||||
|
const authBadgeText = authBadgeTextMap[props.endpoint.authMode] ?? '未知';
|
||||||
|
const authBadgeTitle =
|
||||||
|
props.endpoint.authMode === 'none'
|
||||||
|
? authBadgeTitleMap.none
|
||||||
|
: `${props.endpoint.authMiddlewareName}(${
|
||||||
|
props.endpoint.authMode === 'lenient'
|
||||||
|
? 'key 过期/禁用/额度耗尽仍可查询'
|
||||||
|
: 'key 无效/过期/额度耗尽会被拒绝'
|
||||||
|
})`;
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
@@ -64,6 +79,11 @@ const authBadgeTitle = `${props.endpoint.authMiddlewareName}(${
|
|||||||
border: 1px solid var(--td-success-color);
|
border: 1px solid var(--td-success-color);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.auth-badge.none {
|
||||||
|
color: var(--td-text-color-secondary);
|
||||||
|
border: 1px solid var(--td-component-border);
|
||||||
|
}
|
||||||
|
|
||||||
.response-summary {
|
.response-summary {
|
||||||
font-size: 12px;
|
font-size: 12px;
|
||||||
color: var(--td-text-color-placeholder);
|
color: var(--td-text-color-placeholder);
|
||||||
|
|||||||
@@ -44,12 +44,17 @@ const {
|
|||||||
sendRequest,
|
sendRequest,
|
||||||
} = useManualEndpointQuery(endpointPathRef, skKeyInput);
|
} = useManualEndpointQuery(endpointPathRef, skKeyInput);
|
||||||
|
|
||||||
|
/** 无鉴权接口(如 /api/status)不需要 sk-key:隐藏输入行、按钮不因空 key 禁用 */
|
||||||
|
const requiresKey = computed(() => endpoint.value.authMode !== 'none');
|
||||||
|
|
||||||
const keyInputStatus = computed(() =>
|
const keyInputStatus = computed(() =>
|
||||||
hasSent.value && !skKeyInput.value.trim() ? 'error' : 'default',
|
hasSent.value && requiresKey.value && !skKeyInput.value.trim() ? 'error' : 'default',
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const sendButtonDisabled = computed(() => requiresKey.value && !skKeyInput.value.trim());
|
||||||
|
|
||||||
function handleSend(): void {
|
function handleSend(): void {
|
||||||
if (!skKeyInput.value.trim()) return; // 空 key 不发(输入框已标红提示)
|
if (sendButtonDisabled.value) return; // 空 key 不发(输入框已标红提示);无鉴权接口直接发
|
||||||
sendRequest();
|
sendRequest();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -100,10 +105,11 @@ function downloadResponseAsFile(): void {
|
|||||||
</t-collapse>
|
</t-collapse>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
<!-- 调试行:sk-key + 发送 -->
|
<!-- 调试行:sk-key + 发送(无鉴权接口隐藏 key 输入) -->
|
||||||
<section class="panel-section">
|
<section class="panel-section">
|
||||||
<div class="request-row">
|
<div class="request-row">
|
||||||
<t-input
|
<t-input
|
||||||
|
v-if="requiresKey"
|
||||||
v-model="skKeyInput"
|
v-model="skKeyInput"
|
||||||
class="sk-key-input"
|
class="sk-key-input"
|
||||||
type="password"
|
type="password"
|
||||||
@@ -113,10 +119,11 @@ function downloadResponseAsFile(): void {
|
|||||||
:tips="keyInputStatus === 'error' ? '请填写 sk-key' : ''"
|
:tips="keyInputStatus === 'error' ? '请填写 sk-key' : ''"
|
||||||
@enter="handleSend"
|
@enter="handleSend"
|
||||||
/>
|
/>
|
||||||
|
<span v-else class="no-key-hint">公开接口,无需 sk-key</span>
|
||||||
<t-button
|
<t-button
|
||||||
theme="primary"
|
theme="primary"
|
||||||
:loading="isFetching"
|
:loading="isFetching"
|
||||||
:disabled="!skKeyInput.trim()"
|
:disabled="sendButtonDisabled"
|
||||||
@click="handleSend"
|
@click="handleSend"
|
||||||
>
|
>
|
||||||
发送请求
|
发送请求
|
||||||
@@ -156,7 +163,7 @@ function downloadResponseAsFile(): void {
|
|||||||
<JsonTextView :json="responseData.json" :raw-text="responseData.text" />
|
<JsonTextView :json="responseData.json" :raw-text="responseData.text" />
|
||||||
</section>
|
</section>
|
||||||
<section v-else-if="!hasSent" class="panel-section response-placeholder">
|
<section v-else-if="!hasSent" class="panel-section response-placeholder">
|
||||||
填写 sk-key 后点击「发送请求」
|
{{ requiresKey ? '填写 sk-key 后点击「发送请求」' : '点击「发送请求」查看站点配置' }}
|
||||||
</section>
|
</section>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
@@ -186,6 +193,12 @@ function downloadResponseAsFile(): void {
|
|||||||
max-width: 480px;
|
max-width: 480px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.no-key-hint {
|
||||||
|
align-self: center;
|
||||||
|
font-size: 12.5px;
|
||||||
|
color: var(--td-text-color-placeholder);
|
||||||
|
}
|
||||||
|
|
||||||
.response-section {
|
.response-section {
|
||||||
flex: 1;
|
flex: 1;
|
||||||
min-height: 220px; /* 响应区保底高度:窄窗口下不被上部固定分区压缩到不可用 */
|
min-height: 220px; /* 响应区保底高度:窄窗口下不被上部固定分区压缩到不可用 */
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
/**
|
/**
|
||||||
* 左侧接口导航:5 个路径平铺(不分组不编号,按 endpoints.ts 定案顺序)
|
* 左侧接口导航:路径平铺(不分组不编号,按 endpoints.ts 定案顺序)
|
||||||
* 点击 → openOrActivateTab(已开则激活,未开则新开)
|
* 点击 → openOrActivateTab(已开则激活,未开则新开)
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user