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

238 lines
11 KiB
HTML

<!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; padding: 20px; }
.container { max-width: 1000px; margin: 0 auto; }
.header { background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); color: white; padding: 20px; border-radius: 8px; margin-bottom: 20px; }
.header h1 { font-size: 24px; font-weight: 600; }
.header p { font-size: 14px; opacity: 0.9; margin-top: 5px; }
.card { background: white; border-radius: 8px; padding: 20px; margin-bottom: 20px; box-shadow: 0 2px 8px rgba(0,0,0,0.08); }
.card-title { font-size: 16px; font-weight: 600; color: #333; margin-bottom: 15px; padding-bottom: 10px; border-bottom: 1px solid #eee; }
.form-group { margin-bottom: 15px; }
.form-group label { display: block; font-size: 14px; color: #555; margin-bottom: 6px; }
.form-group input, .form-group textarea, .form-group select { width: 100%; padding: 10px; border: 1px solid #ddd; border-radius: 4px; font-size: 14px; transition: border-color 0.2s; }
.form-group input:focus, .form-group textarea:focus { outline: none; border-color: #667eea; }
.form-group textarea { resize: vertical; min-height: 80px; }
.grid { display: grid; grid-template-columns: repeat(2, 1fr); gap: 15px; }
.btn { display: inline-block; padding: 10px 24px; border: none; border-radius: 4px; font-size: 14px; font-weight: 500; cursor: pointer; transition: all 0.2s; }
.btn-primary { background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); color: white; }
.btn-primary:hover { transform: translateY(-1px); box-shadow: 0 4px 12px rgba(102, 126, 234, 0.4); }
.btn-success { background: #28a745; color: white; }
.btn-success:hover { background: #218838; }
.btn-group { display: flex; gap: 10px; }
.result-area { margin-top: 15px; }
.result-area pre { background: #f8f9fa; border-radius: 4px; padding: 15px; font-family: 'Consolas', 'Monaco', monospace; font-size: 13px; white-space: pre-wrap; word-break: break-all; max-height: 500px; overflow-y: auto; color: #333; }
.signature-box { background: #e8f5e9; border: 1px solid #c8e6c9; border-radius: 4px; padding: 15px; margin-top: 10px; }
.signature-box .label { font-size: 12px; color: #2e7d32; margin-bottom: 8px; }
.signature-box .value { font-family: 'Consolas', monospace; font-size: 13px; color: #1b5e20; word-break: break-all; }
.endpoint-tabs { display: flex; gap: 8px; margin-bottom: 15px; }
.endpoint-tabs button { padding: 8px 16px; border: 1px solid #ddd; background: white; border-radius: 4px; font-size: 13px; cursor: pointer; transition: all 0.2s; }
.endpoint-tabs button.active { background: #667eea; color: white; border-color: #667eea; }
.loading { display: inline-block; width: 20px; height: 20px; border: 2px solid #f3f3f3; border-top: 2px solid #667eea; border-radius: 50%; animation: spin 1s linear infinite; }
@keyframes spin { 0% { transform: rotate(0deg); } 100% { transform: rotate(360deg); } }
</style>
</head>
<body>
<div class="container">
<div class="header">
<h1>API测试工具</h1>
<p>签名算法: HMAC-SHA256 | 支持出库(chuku)和入库(ruku)接口</p>
</div>
<div class="card">
<div class="card-title">1. 配置API密钥</div>
<div class="grid">
<div class="form-group">
<label>API Key</label>
<input type="text" id="apiKey" value="" placeholder="输入API Key">
</div>
<div class="form-group">
<label>API Secret</label>
<input type="text" id="apiSecret" value="" placeholder="输入API Secret">
</div>
</div>
</div>
<div class="card">
<div class="card-title">2. 请求参数</div>
<div class="endpoint-tabs">
<button class="active" onclick="switchEndpoint('chuku')">出库接口 /api/v1/chuku</button>
<button onclick="switchEndpoint('ruku')">入库接口 /api/v1/ruku</button>
</div>
<div class="grid">
<div class="form-group">
<label>开始日期 (start_date)</label>
<input type="date" id="startDate" value="2024-02-02">
</div>
<div class="form-group">
<label>结束日期 (end_date)</label>
<input type="date" id="endDate" value="2026-06-22">
</div>
<div class="form-group">
<label>页码 (page)</label>
<input type="number" id="page" value="1" min="1">
</div>
<div class="form-group">
<label>每页数量 (limit)</label>
<input type="number" id="limit" value="100" min="1" max="500">
</div>
</div>
</div>
<div class="card">
<div class="card-title">3. 签名生成</div>
<div class="btn-group">
<button class="btn btn-primary" onclick="generateSignature()">生成签名</button>
<button class="btn btn-success" onclick="executeQuery()">执行查询</button>
</div>
<div class="signature-box" id="signatureBox" style="display: none;">
<div class="label">生成的请求信息:</div>
<div class="value" id="signatureInfo"></div>
</div>
</div>
<div class="card">
<div class="card-title">4. 查询结果</div>
<div id="resultArea"></div>
</div>
</div>
<script>
let currentEndpoint = 'chuku';
function switchEndpoint(endpoint) {
currentEndpoint = endpoint;
document.querySelectorAll('.endpoint-tabs button').forEach(btn => btn.classList.remove('active'));
event.target.classList.add('active');
}
async function generateSignature() {
const apiKey = document.getElementById('apiKey').value;
const apiSecret = document.getElementById('apiSecret').value;
const startDate = document.getElementById('startDate').value;
const endDate = document.getElementById('endDate').value;
const page = document.getElementById('page').value;
const limit = document.getElementById('limit').value;
const params = {
start_date: startDate,
end_date: endDate,
page: page,
limit: limit
};
const timestamp = Math.floor(Date.now() / 1000).toString();
const sortedKeys = Object.keys(params).sort();
let paramStr = '';
for (let i = 0; i < sortedKeys.length; i++) {
const k = sortedKeys[i];
const v = params[k];
if (v !== null && v !== '') {
if (paramStr) paramStr += '&';
paramStr += k + '=' + v;
}
}
const fullStr = paramStr + '&timestamp=' + timestamp;
const sign = await hmacSha256(fullStr, apiSecret);
const url = `https://yisheng.cpolar.cn/api/v1/${currentEndpoint}?${paramStr}`;
const info = `
请求URL: ${url}
Headers:
X-API-Key: ${apiKey}
X-Timestamp: ${timestamp}
X-Sign: ${sign}
签名字符串:
${fullStr}
签名有效期: 7天 (当前时间戳: ${new Date(parseInt(timestamp) * 1000).toLocaleString()})
`.trim();
document.getElementById('signatureInfo').textContent = info;
document.getElementById('signatureBox').style.display = 'block';
window.signatureCache = {
url: url,
headers: {
'X-API-Key': apiKey,
'X-Timestamp': timestamp,
'X-Sign': sign
}
};
}
async function executeQuery() {
if (!window.signatureCache) {
await generateSignature();
}
const resultArea = document.getElementById('resultArea');
resultArea.innerHTML = '<div style="text-align:center; padding:20px;"><div class="loading"></div> 查询中...</div>';
try {
const response = await fetch(window.signatureCache.url, {
method: 'GET',
headers: {
'Content-Type': 'application/json',
'X-API-Key': window.signatureCache.headers['X-API-Key'],
'X-Timestamp': window.signatureCache.headers['X-Timestamp'],
'X-Sign': window.signatureCache.headers['X-Sign']
}
});
const result = await response.json();
let html = '<pre>' + JSON.stringify(result, null, 2) + '</pre>';
if (result.code === 200) {
html = '<div style="color:#28a745; margin-bottom:10px;">✅ 查询成功</div>' + html;
} else {
html = '<div style="color:#dc3545; margin-bottom:10px;">❌ 查询失败: ' + (result.msg || '未知错误') + '</div>' + html;
}
resultArea.innerHTML = html;
} catch (error) {
resultArea.innerHTML = '<div style="color:#dc3545; margin-bottom:10px;">❌ 请求失败: ' + error.message + '</div><pre>' + error.stack + '</pre>';
}
}
function hmacSha256(message, secret) {
const encoder = new TextEncoder();
const messageData = encoder.encode(message);
const secretData = encoder.encode(secret);
return crypto.subtle.importKey(
'raw',
secretData,
{ name: 'HMAC', hash: 'SHA-256' },
false,
['sign']
).then(key => {
return crypto.subtle.sign('HMAC', key, messageData);
}).then(signature => {
return Array.from(new Uint8Array(signature))
.map(b => b.toString(16).padStart(2, '0'))
.join('');
});
}
</script>
</body>
</html>