Files
docs/frontend/public/api_docs.html
cenzuhai 3a04eede84 feat: 初始化进销存数据开放平台项目
初始化完整的前后端项目结构,包含ThinkPHP后端API服务和Next.js前端管理系统,添加基础配置、中间件、模型、路由等核心代码,以及项目文档和静态资源。
2026-07-08 17:48:00 +08:00

404 lines
17 KiB
HTML
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>API接口文档</title>
<style>
* { margin: 0; padding: 0; box-sizing: border-box; }
body { font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif; background: #f5f7fa; color: #333; line-height: 1.6; }
.container { max-width: 1200px; margin: 0 auto; padding: 20px; }
.header { background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); color: white; padding: 40px 20px; border-radius: 8px; margin-bottom: 30px; }
.header h1 { font-size: 32px; font-weight: 700; margin-bottom: 10px; }
.header p { font-size: 16px; opacity: 0.9; }
.section { background: white; border-radius: 8px; padding: 30px; margin-bottom: 20px; box-shadow: 0 2px 8px rgba(0,0,0,0.08); }
.section-title { font-size: 22px; font-weight: 600; color: #333; margin-bottom: 20px; padding-bottom: 10px; border-bottom: 2px solid #667eea; }
.endpoint { background: #f8f9fa; border-left: 4px solid #667eea; padding: 20px; margin: 20px 0; border-radius: 4px; }
.endpoint-header { display: flex; align-items: center; gap: 15px; margin-bottom: 15px; }
.method { background: #28a745; color: white; padding: 4px 12px; border-radius: 4px; font-weight: 600; font-size: 14px; }
.url { font-family: 'Consolas', monospace; font-size: 16px; color: #333; }
.param-table { width: 100%; border-collapse: collapse; margin: 15px 0; }
.param-table th, .param-table td { padding: 12px; text-align: left; border-bottom: 1px solid #eee; }
.param-table th { background: #f8f9fa; font-weight: 600; color: #555; }
.param-table tr:hover { background: #f8f9fa; }
.required { color: #dc3545; font-weight: 600; }
.optional { color: #28a745; }
.code-block { background: #282c34; color: #abb2bf; padding: 20px; border-radius: 6px; overflow-x: auto; font-family: 'Consolas', monospace; font-size: 14px; margin: 15px 0; }
.code-block .keyword { color: #c678dd; }
.code-block .string { color: #98c379; }
.code-block .comment { color: #5c6370; font-style: italic; }
.code-block .function { color: #61afef; }
.response-example { background: #e8f5e9; border: 1px solid #c8e6c9; border-radius: 4px; padding: 15px; margin: 15px 0; }
.response-title { font-weight: 600; color: #2e7d32; margin-bottom: 10px; }
.signature-steps { counter-reset: step; }
.signature-step { position: relative; padding-left: 40px; margin: 15px 0; }
.signature-step::before { counter-increment: step; content: counter(step); position: absolute; left: 0; top: 0; width: 28px; height: 28px; background: #667eea; color: white; border-radius: 50%; text-align: center; line-height: 28px; font-weight: 600; }
.alert { background: #fff3cd; border: 1px solid #ffc107; border-radius: 4px; padding: 15px; margin: 15px 0; }
.alert-title { font-weight: 600; color: #856404; margin-bottom: 5px; }
.nav { display: flex; gap: 10px; margin-bottom: 20px; }
.nav a { padding: 8px 16px; background: white; border: 1px solid #ddd; border-radius: 4px; text-decoration: none; color: #333; transition: all 0.2s; }
.nav a:hover { background: #667eea; color: white; border-color: #667eea; }
.test-btn { display: inline-block; background: #28a745; color: white; padding: 12px 24px; border-radius: 6px; text-decoration: none; font-weight: 600; transition: all 0.2s; margin-top: 15px; }
.test-btn:hover { background: #218838; transform: translateY(-2px); box-shadow: 0 4px 12px rgba(40, 167, 69, 0.4); }
</style>
</head>
<body>
<div class="container">
<div class="header">
<h1>进销存开放平台 API 文档</h1>
<p>版本: v1.0 | 基础URL: https://yisheng.cpolar.cn</p>
</div>
<div class="nav">
<a href="#auth">认证方式</a>
<a href="#chuku">出库接口</a>
<a href="#ruku">入库接口</a>
<a target="_blank" href="test_api.html">点击测试</a>
<a target="_blank" href="client.html">客户端查询</a>
</div>
<div class="section" id="auth">
<div class="section-title">认证方式</div>
<p style="margin-bottom: 20px;">所有API请求都需要通过HMAC-SHA256签名验证确保请求的合法性和完整性。</p>
<h3 style="margin: 20px 0 10px;">请求头</h3>
<table class="param-table">
<thead>
<tr>
<th>参数名</th>
<th>类型</th>
<th>必填</th>
<th>说明</th>
</tr>
</thead>
<tbody>
<tr>
<td>X-API-Key</td>
<td>String</td>
<td><span class="required"></span></td>
<td>API密钥用于标识调用方身份</td>
</tr>
<tr>
<td>X-Timestamp</td>
<td>String</td>
<td><span class="required"></span></td>
<td>时间戳(秒级),用于防止重放攻击</td>
</tr>
<tr>
<td>X-Sign</td>
<td>String</td>
<td><span class="required"></span></td>
<td>请求签名HMAC-SHA256算法生成</td>
</tr>
</tbody>
</table>
<h3 style="margin: 20px 0 10px;">签名生成步骤</h3>
<div class="signature-steps">
<div class="signature-step">
<strong>1. 参数排序</strong><br>
将所有请求参数不包括签名相关参数按参数名ASCII码升序排列
</div>
<div class="signature-step">
<strong>2. 拼接参数字符串</strong><br>
将排序后的参数按 key=value 格式拼接,参数之间用 & 连接
</div>
<div class="signature-step">
<strong>3. 添加时间戳</strong><br>
在参数字符串末尾追加 &timestamp={时间戳}
</div>
<div class="signature-step">
<strong>4. 生成签名</strong><br>
使用 HMAC-SHA256 算法,以 API Secret 为密钥,对完整字符串进行签名
</div>
</div>
<div class="alert">
<div class="alert-title">⚠️ 注意事项</div>
<ul style="margin-left: 20px;">
<li>时间戳有效期7天</li>
<li>签名大小写敏感</li>
<li>参数值不进行URL编码</li>
<li>空值参数不参与签名</li>
</ul>
</div>
<h3 style="margin: 20px 0 10px;">签名示例JavaScript</h3>
<div class="code-block">
<span class="keyword">async function</span> <span class="function">generateSignature</span>(params, timestamp, apiSecret) {
<span class="comment">// 1. 参数排序</span>
<span class="keyword">const</span> sortedKeys = Object.keys(params).sort();
<span class="comment">// 2. 拼接参数字符串</span>
<span class="keyword">let</span> paramStr = <span class="string">""</span>;
<span class="keyword">for</span> (<span class="keyword">const</span> k <span class="keyword">of</span> sortedKeys) {
<span class="keyword">const</span> v = params[k];
<span class="keyword">if</span> (v !== null && v !== <span class="string">""</span>) {
<span class="keyword">if</span> (paramStr) paramStr += <span class="string">"&"</span>;
paramStr += k + <span class="string">"="</span> + v;
}
}
<span class="comment">// 3. 添加时间戳</span>
<span class="keyword">const</span> fullStr = paramStr + <span class="string">"&timestamp="</span> + timestamp;
<span class="comment">// 4. 生成签名</span>
<span class="keyword">const</span> encoder = <span class="keyword">new</span> TextEncoder();
<span class="keyword">const</span> messageData = encoder.encode(fullStr);
<span class="keyword">const</span> secretData = encoder.encode(apiSecret);
<span class="keyword">const</span> key = <span class="keyword">await</span> crypto.subtle.importKey(
<span class="string">"raw"</span>,
secretData,
{ name: <span class="string">"HMAC"</span>, hash: <span class="string">"SHA-256"</span> },
<span class="keyword">false</span>,
[<span class="string">"sign"</span>]
);
<span class="keyword">const</span> signature = <span class="keyword">await</span> crypto.subtle.sign(<span class="string">"HMAC"</span>, key, messageData);
<span class="keyword">return</span> Array.from(<span class="keyword">new</span> Uint8Array(signature))
.map(b => b.toString(16).padStart(2, <span class="string">"0"</span>))
.join(<span class="string">""</span>);
}
</div>
</div>
<div class="section" id="chuku">
<div class="section-title">出库接口</div>
<div class="endpoint">
<div class="endpoint-header">
<span class="method">GET</span>
<span class="url">/api/v1/chuku</span>
</div>
<p>查询出库(销售)数据,支持分页和日期范围过滤</p>
</div>
<h3>请求参数</h3>
<table class="param-table">
<thead>
<tr>
<th>参数名</th>
<th>类型</th>
<th>必填</th>
<th>说明</th>
</tr>
</thead>
<tbody>
<tr>
<td>start_date</td>
<td>String</td>
<td><span class="optional"></span></td>
<td>开始日期格式YYYY-MM-DD</td>
</tr>
<tr>
<td>end_date</td>
<td>String</td>
<td><span class="optional"></span></td>
<td>结束日期格式YYYY-MM-DD</td>
</tr>
<tr>
<td>page</td>
<td>Integer</td>
<td><span class="optional"></span></td>
<td>页码默认1</td>
</tr>
<tr>
<td>limit</td>
<td>Integer</td>
<td><span class="optional"></span></td>
<td>每页数量默认50最大500</td>
</tr>
</tbody>
</table>
<h3>请求示例</h3>
<div class="code-block">
GET /api/v1/chuku?start_date=2024-02-02&end_date=2026-06-22&page=1&limit=100
Headers:
X-API-Key: 4e2bf206a82f846c699e978cea8c2f87
X-Timestamp: 1782119268
X-Sign: 3f1b80b43f2bd83f85fc08ade76515773a58b8673b137b8f77e4e90d3dab9acc
</div>
<h3>响应示例</h3>
<div class="response-example">
<div class="response-title">成功响应 (200)</div>
<div class="code-block" style="background: #f8f9fa; color: #333;">
{
"code": 200,
"msg": "success",
"total": 1500,
"data": [
{
"type": "销售登记",
"ticket_no": "A01C202606160002",
"register_time": "2026-06-16",
"customer_code": "001",
"customer_name": "喀喇沁旗中医蒙医医院",
"total_amount": 2035.0,
...
}
]
}
</div>
</div>
<div class="response-example" style="background: #f8d7da; border-color: #f5c6cb;">
<div class="response-title" style="color: #721c24;">错误响应 (500)</div>
<div class="code-block" style="background: #f8f9fa; color: #333;">
{
"code": 500,
"msg": "数据查询失败: SQL Server 连接不可用"
}
</div>
</div>
</div>
<div class="section" id="ruku">
<div class="section-title">入库接口</div>
<div class="endpoint">
<div class="endpoint-header">
<span class="method">GET</span>
<span class="url">/api/v1/ruku</span>
</div>
<p>查询入库(采购)数据,支持分页和日期范围过滤</p>
</div>
<h3>请求参数</h3>
<table class="param-table">
<thead>
<tr>
<th>参数名</th>
<th>类型</th>
<th>必填</th>
<th>说明</th>
</tr>
</thead>
<tbody>
<tr>
<td>start_date</td>
<td>String</td>
<td><span class="optional"></span></td>
<td>开始日期格式YYYY-MM-DD</td>
</tr>
<tr>
<td>end_date</td>
<td>String</td>
<td><span class="optional"></span></td>
<td>结束日期格式YYYY-MM-DD</td>
</tr>
<tr>
<td>page</td>
<td>Integer</td>
<td><span class="optional"></span></td>
<td>页码默认1</td>
</tr>
<tr>
<td>limit</td>
<td>Integer</td>
<td><span class="optional"></span></td>
<td>每页数量默认50最大500</td>
</tr>
</tbody>
</table>
<h3>请求示例</h3>
<div class="code-block">
GET /api/v1/ruku?start_date=2024-02-02&end_date=2026-06-22&page=1&limit=100
Headers:
X-API-Key: 4e2bf206a82f846c699e978cea8c2f87
X-Timestamp: 1782119268
X-Sign: 3f1b80b43f2bd83f85fc08ade76515773a58b8673b137b8f77e4e90d3dab9acc
</div>
<h3>响应示例</h3>
<div class="response-example">
<div class="response-title">成功响应 (200)</div>
<div class="code-block" style="background: #f8f9fa; color: #333;">
{
"code": 200,
"msg": "success",
"total": 800,
"data": [
{
"type": "采购入库",
"ticket_no": "A01Q202606160002",
"register_time": "2026-06-16",
"supplier_code": "1533",
"supplier_name": "通化华夏药业有限责任公司",
"total_amount": 101890.8,
...
}
]
}
</div>
</div>
<div class="response-example" style="background: #f8d7da; border-color: #f5c6cb;">
<div class="response-title" style="color: #721c24;">错误响应 (500)</div>
<div class="code-block" style="background: #f8f9fa; color: #333;">
{
"code": 500,
"msg": "数据查询失败: SQL Server 连接不可用"
}
</div>
</div>
</div>
<div class="section">
<div class="section-title">常见错误码</div>
<table class="param-table">
<thead>
<tr>
<th>错误码</th>
<th>说明</th>
</tr>
</thead>
<tbody>
<tr>
<td>200</td>
<td>请求成功</td>
</tr>
<tr>
<td>401</td>
<td>未授权API Key无效或签名错误</td>
</tr>
<tr>
<td>403</td>
<td>禁止访问,权限不足或数据隔离配置错误</td>
</tr>
<tr>
<td>429</td>
<td>请求过于频繁,已触发限流</td>
</tr>
<tr>
<td>500</td>
<td>服务器内部错误,通常是数据库连接或查询失败</td>
</tr>
</tbody>
</table>
</div>
<div style="text-align: center; color: #999; margin-top: 40px; padding-bottom: 20px;">
<p>© 2026 进销存开放平台 | 文档版本 v1.0</p>
</div>
</div>
</body>
</html>