生产管理系统 - 页面布局调整
This commit is contained in:
@@ -9,6 +9,44 @@
|
|||||||
const fs = require('fs');
|
const fs = require('fs');
|
||||||
const path = require('path');
|
const path = require('path');
|
||||||
|
|
||||||
|
// 从环境配置文件中读取 API_BASE_URL
|
||||||
|
function getApiBaseUrl() {
|
||||||
|
try {
|
||||||
|
// 首先尝试从 env/.env.dev 文件读取
|
||||||
|
const envDevPath = path.join(process.cwd(), 'env', '.env.dev');
|
||||||
|
if (fs.existsSync(envDevPath)) {
|
||||||
|
const envContent = fs.readFileSync(envDevPath, 'utf8');
|
||||||
|
const apiBaseUrlMatch = envContent.match(/API_BASE_URL=([^\r\n]+)/);
|
||||||
|
|
||||||
|
if (apiBaseUrlMatch && apiBaseUrlMatch[1]) {
|
||||||
|
return apiBaseUrlMatch[1].trim();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 如果上面的文件不存在,尝试从 TypeScript 环境配置文件读取
|
||||||
|
const envConfigPath = path.join(process.cwd(), 'src', 'env', 'index.ts');
|
||||||
|
if (fs.existsSync(envConfigPath)) {
|
||||||
|
const envContent = fs.readFileSync(envConfigPath, 'utf8');
|
||||||
|
const devConfigMatch = envContent.match(/dev:\s*\{[\s\S]*?BACKEND_BASE_URL:\s*['"`]([^'"`]+)['"`]/);
|
||||||
|
|
||||||
|
if (devConfigMatch && devConfigMatch[1]) {
|
||||||
|
return devConfigMatch[1].trim();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
throw new Error('无法找到 API_BASE_URL 或 BACKEND_BASE_URL 配置');
|
||||||
|
} catch (error) {
|
||||||
|
console.log(`读取环境配置失败: ${error.message}`);
|
||||||
|
return 'http://localhost:8080'; // 默认值
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 获取 API 基础 URL
|
||||||
|
const API_BASE_URL = getApiBaseUrl();
|
||||||
|
|
||||||
|
// 设置环境变量供后续使用
|
||||||
|
process.env.API_BASE_URL = API_BASE_URL;
|
||||||
|
|
||||||
// ANSI 颜色代码
|
// ANSI 颜色代码
|
||||||
const colors = {
|
const colors = {
|
||||||
reset: '\x1b[0m',
|
reset: '\x1b[0m',
|
||||||
@@ -44,7 +82,8 @@ function logInfo(message) {
|
|||||||
|
|
||||||
// 显示环境配置信息
|
// 显示环境配置信息
|
||||||
logInfo(`当前环境: ${process.env.NODE_ENV || 'development'}`);
|
logInfo(`当前环境: ${process.env.NODE_ENV || 'development'}`);
|
||||||
logInfo(`API 服务器: ${process.env.API_BASE_URL || 'http://localhost:8080'}`);
|
logInfo(`API 服务器: ${API_BASE_URL}`);
|
||||||
|
logInfo(`已从 env/.env.dev 读取 API_BASE_URL 配置`);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 检查自定义文件是否存在
|
* 检查自定义文件是否存在
|
||||||
@@ -153,6 +192,59 @@ function restoreCustomFiles(customFiles) {
|
|||||||
logSuccess('自定义文件已恢复');
|
logSuccess('自定义文件已恢复');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 下载并保存 openapi.json 文件到本地
|
||||||
|
*/
|
||||||
|
function downloadOpenApiJson() {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
const https = require('https');
|
||||||
|
const fs = require('fs');
|
||||||
|
const path = require('path');
|
||||||
|
|
||||||
|
const fileUrl = `${API_BASE_URL}/openapi.json`;
|
||||||
|
const outputPath = path.join(process.cwd(), 'scripts', 'openapi.json');
|
||||||
|
|
||||||
|
logInfo(`下载 OpenAPI 规范文件: ${fileUrl}`);
|
||||||
|
|
||||||
|
const startTime = Date.now();
|
||||||
|
|
||||||
|
// 创建 HTTPS 代理(如果需要,可以忽略 SSL 证书验证)
|
||||||
|
const agent = new https.Agent({
|
||||||
|
rejectUnauthorized: false // 跳过 SSL 证书验证
|
||||||
|
});
|
||||||
|
|
||||||
|
https.get(fileUrl, { agent }, (response) => {
|
||||||
|
if (response.statusCode !== 200) {
|
||||||
|
reject(new Error(`HTTP ${response.statusCode}: ${response.statusMessage}`));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let data = '';
|
||||||
|
|
||||||
|
response.on('data', (chunk) => {
|
||||||
|
data += chunk;
|
||||||
|
});
|
||||||
|
|
||||||
|
response.on('end', () => {
|
||||||
|
try {
|
||||||
|
// 验证 JSON 格式
|
||||||
|
JSON.parse(data);
|
||||||
|
|
||||||
|
// 写入文件
|
||||||
|
fs.writeFileSync(outputPath, data, 'utf8');
|
||||||
|
const executionTime = Date.now() - startTime;
|
||||||
|
logSuccess(`OpenAPI 规范已保存到: scripts/openapi.json (${executionTime}ms)`);
|
||||||
|
resolve();
|
||||||
|
} catch (error) {
|
||||||
|
reject(new Error(`JSON 格式错误: ${error.message}`));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}).on('error', (error) => {
|
||||||
|
reject(error);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 使用 openapi-ts 命令生成客户端代码
|
* 使用 openapi-ts 命令生成客户端代码
|
||||||
*/
|
*/
|
||||||
@@ -259,6 +351,9 @@ async function main() {
|
|||||||
backupCustomFiles(customFiles);
|
backupCustomFiles(customFiles);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 下载 openapi.json 文件到本地
|
||||||
|
await downloadOpenApiJson();
|
||||||
|
|
||||||
// 使用 openapi-ts 生成代码
|
// 使用 openapi-ts 生成代码
|
||||||
await generateWithOpenApiTS();
|
await generateWithOpenApiTS();
|
||||||
|
|
||||||
|
|||||||
File diff suppressed because one or more lines are too long
420
crop-x/src/app/(app)/api-example/openapi-examples.tsx
Normal file
420
crop-x/src/app/(app)/api-example/openapi-examples.tsx
Normal file
@@ -0,0 +1,420 @@
|
|||||||
|
'use client';
|
||||||
|
|
||||||
|
import { useState } from 'react';
|
||||||
|
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card';
|
||||||
|
import { Button } from '@/components/ui/button';
|
||||||
|
import { Badge } from '@/components/ui/badge';
|
||||||
|
import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs';
|
||||||
|
import { Copy, Eye, EyeOff, Code, FileJson } from 'lucide-react';
|
||||||
|
|
||||||
|
interface OpenApiExample {
|
||||||
|
path: string;
|
||||||
|
method: string;
|
||||||
|
summary?: string;
|
||||||
|
description?: string;
|
||||||
|
parameters?: any[];
|
||||||
|
requestBody?: any;
|
||||||
|
responses?: any;
|
||||||
|
examples?: any;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface OpenApiExamplesProps {
|
||||||
|
className?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 示例数据 - 这些通常从 OpenAPI 规范中提取
|
||||||
|
const openApiExamples: OpenApiExample[] = [
|
||||||
|
{
|
||||||
|
path: '/api/v1/auth/login',
|
||||||
|
method: 'POST',
|
||||||
|
summary: '用户登录',
|
||||||
|
description: '用户登录(需要验证码)',
|
||||||
|
requestBody: {
|
||||||
|
'application/json': {
|
||||||
|
example: {
|
||||||
|
identifier: 'admin',
|
||||||
|
password: 'admin123',
|
||||||
|
captcha_id: 'test-captcha-id',
|
||||||
|
captcha_text: '1234'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
responses: {
|
||||||
|
200: {
|
||||||
|
description: 'Successful Response',
|
||||||
|
content: {
|
||||||
|
'application/json': {
|
||||||
|
example: {
|
||||||
|
access_token: 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...',
|
||||||
|
token_type: 'bearer',
|
||||||
|
expires_in: 3600
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: '/api/v1/auth/register',
|
||||||
|
method: 'POST',
|
||||||
|
summary: '用户注册',
|
||||||
|
description: '用户注册',
|
||||||
|
requestBody: {
|
||||||
|
'application/json': {
|
||||||
|
example: {
|
||||||
|
username: 'newuser',
|
||||||
|
password: 'password123',
|
||||||
|
email: 'user@example.com',
|
||||||
|
full_name: '新用户',
|
||||||
|
phone: '13800138000',
|
||||||
|
tenant_id: 'tenant-uuid',
|
||||||
|
scope: 'user',
|
||||||
|
department_id: 'dept-uuid'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
responses: {
|
||||||
|
200: {
|
||||||
|
description: 'Successful Response',
|
||||||
|
content: {
|
||||||
|
'application/json': {
|
||||||
|
example: {
|
||||||
|
id: 'uuid-12345',
|
||||||
|
username: 'newuser',
|
||||||
|
email: 'user@example.com',
|
||||||
|
phone: '13800138000',
|
||||||
|
is_active: true,
|
||||||
|
created_at: '2024-01-01T00:00:00Z'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: '/api/v1/users',
|
||||||
|
method: 'POST',
|
||||||
|
summary: '创建用户',
|
||||||
|
description: '创建用户(需要管理员权限)',
|
||||||
|
requestBody: {
|
||||||
|
'application/json': {
|
||||||
|
example: {
|
||||||
|
username: 'testuser',
|
||||||
|
password: 'password123',
|
||||||
|
email: 'test@example.com',
|
||||||
|
full_name: '测试用户',
|
||||||
|
phone: '13900139000',
|
||||||
|
tenant_id: 'tenant-uuid',
|
||||||
|
scope: 'user',
|
||||||
|
department_id: 'dept-uuid'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
responses: {
|
||||||
|
200: {
|
||||||
|
description: 'Successful Response',
|
||||||
|
content: {
|
||||||
|
'application/json': {
|
||||||
|
example: {
|
||||||
|
id: 'uuid-67890',
|
||||||
|
username: 'testuser',
|
||||||
|
email: 'test@example.com',
|
||||||
|
phone: '13900139000',
|
||||||
|
is_active: true,
|
||||||
|
created_at: '2024-01-01T00:00:00Z'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: '/api/v1/tenants',
|
||||||
|
method: 'POST',
|
||||||
|
summary: '创建租户',
|
||||||
|
description: '创建新租户',
|
||||||
|
requestBody: {
|
||||||
|
'application/json': {
|
||||||
|
example: {
|
||||||
|
company_name: '新企业有限责任公司',
|
||||||
|
tenant_code: 'NEW001',
|
||||||
|
company_type: '有限责任公司'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
responses: {
|
||||||
|
201: {
|
||||||
|
description: 'Successful Response',
|
||||||
|
content: {
|
||||||
|
'application/json': {
|
||||||
|
example: {
|
||||||
|
id: 'tenant-uuid',
|
||||||
|
company_name: '新企业有限责任公司',
|
||||||
|
tenant_code: 'NEW001',
|
||||||
|
company_type: '有限责任公司',
|
||||||
|
audit_status: 'pending',
|
||||||
|
created_at: '2024-01-01T00:00:00Z'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
];
|
||||||
|
|
||||||
|
export default function OpenApiExamples({ className }: OpenApiExamplesProps) {
|
||||||
|
const [copiedCode, setCopiedCode] = useState<string | null>(null);
|
||||||
|
const [visibleExamples, setVisibleExamples] = useState<Set<string>>(new Set());
|
||||||
|
|
||||||
|
const copyToClipboard = async (text: string, id: string) => {
|
||||||
|
try {
|
||||||
|
await navigator.clipboard.writeText(text);
|
||||||
|
setCopiedCode(id);
|
||||||
|
setTimeout(() => setCopiedCode(null), 2000);
|
||||||
|
} catch (err) {
|
||||||
|
console.error('Failed to copy:', err);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const toggleExampleVisibility = (id: string) => {
|
||||||
|
setVisibleExamples(prev => {
|
||||||
|
const newSet = new Set(prev);
|
||||||
|
if (newSet.has(id)) {
|
||||||
|
newSet.delete(id);
|
||||||
|
} else {
|
||||||
|
newSet.add(id);
|
||||||
|
}
|
||||||
|
return newSet;
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const getMethodColor = (method: string) => {
|
||||||
|
switch (method.toUpperCase()) {
|
||||||
|
case 'GET': return 'bg-green-100 text-green-800 border-green-200';
|
||||||
|
case 'POST': return 'bg-blue-100 text-blue-800 border-blue-200';
|
||||||
|
case 'PUT': return 'bg-yellow-100 text-yellow-800 border-yellow-200';
|
||||||
|
case 'DELETE': return 'bg-red-100 text-red-800 border-red-200';
|
||||||
|
default: return 'bg-gray-100 text-gray-800 border-gray-200';
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const formatJson = (obj: any) => {
|
||||||
|
return JSON.stringify(obj, null, 2);
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Card className={className}>
|
||||||
|
<CardHeader>
|
||||||
|
<CardTitle className="flex items-center gap-2">
|
||||||
|
<FileJson className="h-5 w-5" />
|
||||||
|
OpenAPI 示例文档
|
||||||
|
</CardTitle>
|
||||||
|
<CardDescription>
|
||||||
|
基于 OpenAPI 规范的接口示例,展示了请求参数和响应格式
|
||||||
|
</CardDescription>
|
||||||
|
</CardHeader>
|
||||||
|
<CardContent>
|
||||||
|
<Tabs defaultValue="all" className="w-full">
|
||||||
|
<TabsList className="grid w-full grid-cols-4">
|
||||||
|
<TabsTrigger value="all">全部示例</TabsTrigger>
|
||||||
|
<TabsTrigger value="auth">认证</TabsTrigger>
|
||||||
|
<TabsTrigger value="users">用户管理</TabsTrigger>
|
||||||
|
<TabsTrigger value="tenants">租户管理</TabsTrigger>
|
||||||
|
</TabsList>
|
||||||
|
|
||||||
|
<TabsContent value="all" className="space-y-4">
|
||||||
|
{openApiExamples.map((example) => (
|
||||||
|
<OpenApiExampleCard
|
||||||
|
key={`${example.method}-${example.path}`}
|
||||||
|
example={example}
|
||||||
|
getMethodColor={getMethodColor}
|
||||||
|
formatJson={formatJson}
|
||||||
|
copyToClipboard={copyToClipboard}
|
||||||
|
copiedCode={copiedCode}
|
||||||
|
visibleExamples={visibleExamples}
|
||||||
|
toggleExampleVisibility={toggleExampleVisibility}
|
||||||
|
/>
|
||||||
|
))}
|
||||||
|
</TabsContent>
|
||||||
|
|
||||||
|
<TabsContent value="auth" className="space-y-4">
|
||||||
|
{openApiExamples
|
||||||
|
.filter(e => e.path.includes('/auth/'))
|
||||||
|
.map((example) => (
|
||||||
|
<OpenApiExampleCard
|
||||||
|
key={`${example.method}-${example.path}`}
|
||||||
|
example={example}
|
||||||
|
getMethodColor={getMethodColor}
|
||||||
|
formatJson={formatJson}
|
||||||
|
copyToClipboard={copyToClipboard}
|
||||||
|
copiedCode={copiedCode}
|
||||||
|
visibleExamples={visibleExamples}
|
||||||
|
toggleExampleVisibility={toggleExampleVisibility}
|
||||||
|
/>
|
||||||
|
))}
|
||||||
|
</TabsContent>
|
||||||
|
|
||||||
|
<TabsContent value="users" className="space-y-4">
|
||||||
|
{openApiExamples
|
||||||
|
.filter(e => e.path.includes('/users'))
|
||||||
|
.map((example) => (
|
||||||
|
<OpenApiExampleCard
|
||||||
|
key={`${example.method}-${example.path}`}
|
||||||
|
example={example}
|
||||||
|
getMethodColor={getMethodColor}
|
||||||
|
formatJson={formatJson}
|
||||||
|
copyToClipboard={copyToClipboard}
|
||||||
|
copiedCode={copiedCode}
|
||||||
|
visibleExamples={visibleExamples}
|
||||||
|
toggleExampleVisibility={toggleExampleVisibility}
|
||||||
|
/>
|
||||||
|
))}
|
||||||
|
</TabsContent>
|
||||||
|
|
||||||
|
<TabsContent value="tenants" className="space-y-4">
|
||||||
|
{openApiExamples
|
||||||
|
.filter(e => e.path.includes('/tenants'))
|
||||||
|
.map((example) => (
|
||||||
|
<OpenApiExampleCard
|
||||||
|
key={`${example.method}-${example.path}`}
|
||||||
|
example={example}
|
||||||
|
getMethodColor={getMethodColor}
|
||||||
|
formatJson={formatJson}
|
||||||
|
copyToClipboard={copyToClipboard}
|
||||||
|
copiedCode={copiedCode}
|
||||||
|
visibleExamples={visibleExamples}
|
||||||
|
toggleExampleVisibility={toggleExampleVisibility}
|
||||||
|
/>
|
||||||
|
))}
|
||||||
|
</TabsContent>
|
||||||
|
</Tabs>
|
||||||
|
</CardContent>
|
||||||
|
</Card>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
interface OpenApiExampleCardProps {
|
||||||
|
example: OpenApiExample;
|
||||||
|
getMethodColor: (method: string) => string;
|
||||||
|
formatJson: (obj: any) => string;
|
||||||
|
copyToClipboard: (text: string, id: string) => void;
|
||||||
|
copiedCode: string | null;
|
||||||
|
visibleExamples: Set<string>;
|
||||||
|
toggleExampleVisibility: (id: string) => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
function OpenApiExampleCard({
|
||||||
|
example,
|
||||||
|
getMethodColor,
|
||||||
|
formatJson,
|
||||||
|
copyToClipboard,
|
||||||
|
copiedCode,
|
||||||
|
visibleExamples,
|
||||||
|
toggleExampleVisibility
|
||||||
|
}: OpenApiExampleCardProps) {
|
||||||
|
const exampleId = `${example.method}-${example.path}`;
|
||||||
|
const isVisible = visibleExamples.has(exampleId);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="border rounded-lg p-4 space-y-3">
|
||||||
|
<div className="flex items-start justify-between">
|
||||||
|
<div className="flex items-center gap-3">
|
||||||
|
<Badge className={getMethodColor(example.method)}>
|
||||||
|
{example.method}
|
||||||
|
</Badge>
|
||||||
|
<div>
|
||||||
|
<h3 className="font-semibold text-lg">{example.summary}</h3>
|
||||||
|
<p className="text-sm text-muted-foreground font-mono">
|
||||||
|
{example.path}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<Button
|
||||||
|
variant="ghost"
|
||||||
|
size="sm"
|
||||||
|
onClick={() => toggleExampleVisibility(exampleId)}
|
||||||
|
>
|
||||||
|
{isVisible ? <EyeOff className="h-4 w-4" /> : <Eye className="h-4 w-4" />}
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{example.description && (
|
||||||
|
<p className="text-sm text-muted-foreground">{example.description}</p>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{isVisible && (
|
||||||
|
<div className="space-y-4">
|
||||||
|
{/* Request Body Examples */}
|
||||||
|
{example.requestBody && (
|
||||||
|
<div className="space-y-2">
|
||||||
|
<h4 className="font-medium text-sm flex items-center gap-2">
|
||||||
|
<Code className="h-4 w-4" />
|
||||||
|
请求示例
|
||||||
|
</h4>
|
||||||
|
{Object.entries(example.requestBody).map(([contentType, content]: [string, any]) => (
|
||||||
|
<div key={contentType} className="space-y-2">
|
||||||
|
<div className="flex items-center justify-between">
|
||||||
|
<Badge variant="outline">{contentType}</Badge>
|
||||||
|
<Button
|
||||||
|
variant="ghost"
|
||||||
|
size="sm"
|
||||||
|
onClick={() => copyToClipboard(formatJson(content.example), `request-${exampleId}`)}
|
||||||
|
>
|
||||||
|
{copiedCode === `request-${exampleId}` ? '已复制!' : <Copy className="h-4 w-4" />}
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
<pre className="bg-muted p-3 rounded-md overflow-x-auto text-xs">
|
||||||
|
<code>{formatJson(content.example)}</code>
|
||||||
|
</pre>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{/* Response Examples */}
|
||||||
|
{example.responses && (
|
||||||
|
<div className="space-y-2">
|
||||||
|
<h4 className="font-medium text-sm flex items-center gap-2">
|
||||||
|
<Code className="h-4 w-4" />
|
||||||
|
响应示例
|
||||||
|
</h4>
|
||||||
|
{Object.entries(example.responses).map(([statusCode, response]: [string, any]) => (
|
||||||
|
<div key={statusCode} className="space-y-2">
|
||||||
|
<div className="flex items-center gap-2">
|
||||||
|
<Badge variant={statusCode.startsWith('2') ? 'default' : 'destructive'}>
|
||||||
|
{statusCode}
|
||||||
|
</Badge>
|
||||||
|
<span className="text-sm text-muted-foreground">
|
||||||
|
{response.description}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
{response.content && Object.entries(response.content).map(([contentType, content]: [string, any]) => (
|
||||||
|
<div key={contentType} className="space-y-2">
|
||||||
|
<div className="flex items-center justify-between">
|
||||||
|
<Badge variant="outline">{contentType}</Badge>
|
||||||
|
{content.example && (
|
||||||
|
<Button
|
||||||
|
variant="ghost"
|
||||||
|
size="sm"
|
||||||
|
onClick={() => copyToClipboard(formatJson(content.example), `response-${exampleId}-${statusCode}`)}
|
||||||
|
>
|
||||||
|
{copiedCode === `response-${exampleId}-${statusCode}` ? '已复制!' : <Copy className="h-4 w-4" />}
|
||||||
|
</Button>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
{content.example && (
|
||||||
|
<pre className="bg-muted p-3 rounded-md overflow-x-auto text-xs">
|
||||||
|
<code>{formatJson(content.example)}</code>
|
||||||
|
</pre>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -72,14 +72,16 @@ import {
|
|||||||
// 类型导入
|
// 类型导入
|
||||||
type UserCreate,
|
type UserCreate,
|
||||||
type UserCreateWithCompany,
|
type UserCreateWithCompany,
|
||||||
type UserLogin,
|
type UserLoginWithCaptcha,
|
||||||
type UserUpdate,
|
type UserUpdate,
|
||||||
type UserUpdatePassword,
|
type UserUpdatePassword,
|
||||||
type TenantCreateRequest,
|
type TenantCreateRequest,
|
||||||
type TenantUpdateRequest,
|
type TenantUpdateRequest,
|
||||||
type TenantAuditRequest,
|
type TenantAuditRequest,
|
||||||
type DepartmentCreate,
|
type DepartmentCreate,
|
||||||
type DepartmentUpdate
|
type DepartmentUpdate,
|
||||||
|
type CaptchaResponse,
|
||||||
|
type Token
|
||||||
} from '@/lib/api';
|
} from '@/lib/api';
|
||||||
import { Button } from '@/components/ui/button';
|
import { Button } from '@/components/ui/button';
|
||||||
import { Input } from '@/components/ui/input';
|
import { Input } from '@/components/ui/input';
|
||||||
@@ -90,6 +92,7 @@ import { Alert, AlertDescription } from '@/components/ui/alert';
|
|||||||
import { ScrollArea } from '@/components/ui/scroll-area';
|
import { ScrollArea } from '@/components/ui/scroll-area';
|
||||||
import { Separator } from '@/components/ui/separator';
|
import { Separator } from '@/components/ui/separator';
|
||||||
import { Label } from '@/components/ui/label';
|
import { Label } from '@/components/ui/label';
|
||||||
|
import OpenApiExamples from './openapi-examples';
|
||||||
|
|
||||||
export default function ApiExamplePage() {
|
export default function ApiExamplePage() {
|
||||||
const [loading, setLoading] = useState(false);
|
const [loading, setLoading] = useState(false);
|
||||||
@@ -97,18 +100,26 @@ export default function ApiExamplePage() {
|
|||||||
const [errors, setErrors] = useState<string[]>([]);
|
const [errors, setErrors] = useState<string[]>([]);
|
||||||
|
|
||||||
// 登录表单状态
|
// 登录表单状态
|
||||||
const [loginData, setLoginData] = useState<UserLogin>({
|
const [loginData, setLoginData] = useState<UserLoginWithCaptcha>({
|
||||||
username: '',
|
identifier: '',
|
||||||
password: '',
|
password: '',
|
||||||
captcha: ''
|
captcha_id: '',
|
||||||
|
captcha_text: ''
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// 验证码状态
|
||||||
|
const [captchaData, setCaptchaData] = useState<CaptchaResponse | null>(null);
|
||||||
|
|
||||||
// 注册表单状态
|
// 注册表单状态
|
||||||
const [registerData, setRegisterData] = useState<UserCreate>({
|
const [registerData, setRegisterData] = useState<UserCreate>({
|
||||||
username: '',
|
username: '',
|
||||||
password: '',
|
password: '',
|
||||||
email: '',
|
email: '',
|
||||||
full_name: ''
|
full_name: '',
|
||||||
|
phone: '',
|
||||||
|
tenant_id: '',
|
||||||
|
scope: '',
|
||||||
|
department_id: ''
|
||||||
});
|
});
|
||||||
|
|
||||||
// 带企业注册表单状态
|
// 带企业注册表单状态
|
||||||
@@ -132,17 +143,17 @@ export default function ApiExamplePage() {
|
|||||||
password: '',
|
password: '',
|
||||||
email: '',
|
email: '',
|
||||||
full_name: '',
|
full_name: '',
|
||||||
department_id: '',
|
phone: '',
|
||||||
phone: ''
|
tenant_id: '',
|
||||||
|
scope: '',
|
||||||
|
department_id: ''
|
||||||
});
|
});
|
||||||
|
|
||||||
// 租户创建表单状态
|
// 租户创建表单状态
|
||||||
const [createTenantData, setCreateTenantData] = useState<TenantCreateRequest>({
|
const [createTenantData, setCreateTenantData] = useState<TenantCreateRequest>({
|
||||||
name: '',
|
company_name: '',
|
||||||
code: '',
|
tenant_code: '',
|
||||||
description: '',
|
company_type: '有限责任公司'
|
||||||
contact_email: '',
|
|
||||||
contact_phone: ''
|
|
||||||
});
|
});
|
||||||
|
|
||||||
// 部门创建表单状态
|
// 部门创建表单状态
|
||||||
@@ -255,6 +266,8 @@ export default function ApiExamplePage() {
|
|||||||
const response = await getCaptchaApiV1AuthCaptchaGet({});
|
const response = await getCaptchaApiV1AuthCaptchaGet({});
|
||||||
|
|
||||||
if (response.data) {
|
if (response.data) {
|
||||||
|
setCaptchaData(response.data);
|
||||||
|
setLoginData(prev => ({ ...prev, captcha_id: response.data.captcha_id }));
|
||||||
addResult('获取验证码', '无参数', response.data);
|
addResult('获取验证码', '无参数', response.data);
|
||||||
} else if (response.error) {
|
} else if (response.error) {
|
||||||
addResult('获取验证码', '无参数', null, JSON.stringify(response.error));
|
addResult('获取验证码', '无参数', null, JSON.stringify(response.error));
|
||||||
@@ -450,9 +463,10 @@ export default function ApiExamplePage() {
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<Tabs defaultValue="interactive" className="w-full">
|
<Tabs defaultValue="interactive" className="w-full">
|
||||||
<TabsList className="grid w-full grid-cols-2">
|
<TabsList className="grid w-full grid-cols-3">
|
||||||
<TabsTrigger value="interactive">交互式测试</TabsTrigger>
|
<TabsTrigger value="interactive">交互式测试</TabsTrigger>
|
||||||
<TabsTrigger value="examples">接口示例</TabsTrigger>
|
<TabsTrigger value="examples">接口示例</TabsTrigger>
|
||||||
|
<TabsTrigger value="documentation">OpenAPI 文档</TabsTrigger>
|
||||||
</TabsList>
|
</TabsList>
|
||||||
|
|
||||||
<TabsContent value="interactive" className="space-y-6">
|
<TabsContent value="interactive" className="space-y-6">
|
||||||
@@ -467,24 +481,25 @@ export default function ApiExamplePage() {
|
|||||||
</CardHeader>
|
</CardHeader>
|
||||||
<CardContent>
|
<CardContent>
|
||||||
<Tabs defaultValue="auth" className="w-full">
|
<Tabs defaultValue="auth" className="w-full">
|
||||||
<TabsList className="grid w-full grid-cols-4">
|
<TabsList className="grid w-full grid-cols-5">
|
||||||
<TabsTrigger value="auth">认证操作</TabsTrigger>
|
<TabsTrigger value="auth">认证操作</TabsTrigger>
|
||||||
<TabsTrigger value="user">用户管理</TabsTrigger>
|
<TabsTrigger value="user">用户管理</TabsTrigger>
|
||||||
<TabsTrigger value="tenant">租户管理</TabsTrigger>
|
<TabsTrigger value="tenant">租户管理</TabsTrigger>
|
||||||
|
<TabsTrigger value="department">部门管理</TabsTrigger>
|
||||||
<TabsTrigger value="system">系统管理</TabsTrigger>
|
<TabsTrigger value="system">系统管理</TabsTrigger>
|
||||||
</TabsList>
|
</TabsList>
|
||||||
|
|
||||||
<TabsContent value="auth" className="space-y-4">
|
<TabsContent value="auth" className="space-y-4">
|
||||||
{/* 登录表单 */}
|
{/* 登录表单 */}
|
||||||
<div className="space-y-3">
|
<div className="space-y-3">
|
||||||
<h4 className="font-medium">用户登录</h4>
|
<h4 className="font-medium">用户登录(需要验证码)</h4>
|
||||||
<div className="space-y-2">
|
<div className="space-y-2">
|
||||||
<Label htmlFor="username">用户名</Label>
|
<Label htmlFor="identifier">用户名/邮箱/手机号</Label>
|
||||||
<Input
|
<Input
|
||||||
id="username"
|
id="identifier"
|
||||||
placeholder="请输入用户名"
|
placeholder="请输入用户名、邮箱或手机号"
|
||||||
value={loginData.username}
|
value={loginData.identifier}
|
||||||
onChange={(e) => setLoginData(prev => ({ ...prev, username: e.target.value }))}
|
onChange={(e) => setLoginData(prev => ({ ...prev, identifier: e.target.value }))}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div className="space-y-2">
|
<div className="space-y-2">
|
||||||
@@ -498,17 +513,40 @@ export default function ApiExamplePage() {
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div className="space-y-2">
|
<div className="space-y-2">
|
||||||
<Label htmlFor="captcha">验证码</Label>
|
<Label htmlFor="captcha_text">验证码</Label>
|
||||||
|
<div className="flex gap-2">
|
||||||
<Input
|
<Input
|
||||||
id="captcha"
|
id="captcha_text"
|
||||||
placeholder="请输入验证码"
|
placeholder="请输入验证码"
|
||||||
value={loginData.captcha}
|
value={loginData.captcha_text}
|
||||||
onChange={(e) => setLoginData(prev => ({ ...prev, captcha: e.target.value }))}
|
onChange={(e) => setLoginData(prev => ({ ...prev, captcha_text: e.target.value }))}
|
||||||
|
className="flex-1"
|
||||||
/>
|
/>
|
||||||
|
<Button
|
||||||
|
onClick={handleGetCaptcha}
|
||||||
|
disabled={loading}
|
||||||
|
variant="outline"
|
||||||
|
className="whitespace-nowrap"
|
||||||
|
>
|
||||||
|
获取验证码
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
{captchaData && (
|
||||||
|
<div className="mt-2 p-2 bg-muted rounded">
|
||||||
|
<img
|
||||||
|
src={`data:image/png;base64,${captchaData.image}`}
|
||||||
|
alt="验证码"
|
||||||
|
className="h-12 mx-auto"
|
||||||
|
/>
|
||||||
|
<p className="text-xs text-center text-muted-foreground mt-1">
|
||||||
|
验证码ID: {captchaData.captcha_id}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
<Button
|
<Button
|
||||||
onClick={handleLogin}
|
onClick={handleLogin}
|
||||||
disabled={loading || !loginData.username || !loginData.password}
|
disabled={loading || !loginData.identifier || !loginData.password || !loginData.captcha_text || !loginData.captcha_id}
|
||||||
className="w-full"
|
className="w-full"
|
||||||
>
|
>
|
||||||
{loading ? '登录中...' : '登录'}
|
{loading ? '登录中...' : '登录'}
|
||||||
@@ -539,9 +577,29 @@ export default function ApiExamplePage() {
|
|||||||
value={registerData.full_name}
|
value={registerData.full_name}
|
||||||
onChange={(e) => setRegisterData(prev => ({ ...prev, full_name: e.target.value }))}
|
onChange={(e) => setRegisterData(prev => ({ ...prev, full_name: e.target.value }))}
|
||||||
/>
|
/>
|
||||||
|
<Input
|
||||||
|
placeholder="手机号"
|
||||||
|
value={registerData.phone}
|
||||||
|
onChange={(e) => setRegisterData(prev => ({ ...prev, phone: e.target.value }))}
|
||||||
|
/>
|
||||||
|
<Input
|
||||||
|
placeholder="租户ID (可选)"
|
||||||
|
value={registerData.tenant_id || ''}
|
||||||
|
onChange={(e) => setRegisterData(prev => ({ ...prev, tenant_id: e.target.value || undefined }))}
|
||||||
|
/>
|
||||||
|
<Input
|
||||||
|
placeholder="用户作用域 (可选)"
|
||||||
|
value={registerData.scope || ''}
|
||||||
|
onChange={(e) => setRegisterData(prev => ({ ...prev, scope: e.target.value || undefined }))}
|
||||||
|
/>
|
||||||
|
<Input
|
||||||
|
placeholder="部门ID (可选)"
|
||||||
|
value={registerData.department_id || ''}
|
||||||
|
onChange={(e) => setRegisterData(prev => ({ ...prev, department_id: e.target.value || undefined }))}
|
||||||
|
/>
|
||||||
<Button
|
<Button
|
||||||
onClick={handleRegister}
|
onClick={handleRegister}
|
||||||
disabled={loading || !registerData.username || !registerData.password}
|
disabled={loading || !registerData.username || !registerData.password || !registerData.email || !registerData.phone}
|
||||||
variant="outline"
|
variant="outline"
|
||||||
className="w-full"
|
className="w-full"
|
||||||
>
|
>
|
||||||
@@ -607,9 +665,29 @@ export default function ApiExamplePage() {
|
|||||||
value={createUserData.full_name}
|
value={createUserData.full_name}
|
||||||
onChange={(e) => setCreateUserData(prev => ({ ...prev, full_name: e.target.value }))}
|
onChange={(e) => setCreateUserData(prev => ({ ...prev, full_name: e.target.value }))}
|
||||||
/>
|
/>
|
||||||
|
<Input
|
||||||
|
placeholder="手机号"
|
||||||
|
value={createUserData.phone}
|
||||||
|
onChange={(e) => setCreateUserData(prev => ({ ...prev, phone: e.target.value }))}
|
||||||
|
/>
|
||||||
|
<Input
|
||||||
|
placeholder="租户ID (可选)"
|
||||||
|
value={createUserData.tenant_id || ''}
|
||||||
|
onChange={(e) => setCreateUserData(prev => ({ ...prev, tenant_id: e.target.value || undefined }))}
|
||||||
|
/>
|
||||||
|
<Input
|
||||||
|
placeholder="用户作用域 (可选)"
|
||||||
|
value={createUserData.scope || ''}
|
||||||
|
onChange={(e) => setCreateUserData(prev => ({ ...prev, scope: e.target.value || undefined }))}
|
||||||
|
/>
|
||||||
|
<Input
|
||||||
|
placeholder="部门ID (可选)"
|
||||||
|
value={createUserData.department_id || ''}
|
||||||
|
onChange={(e) => setCreateUserData(prev => ({ ...prev, department_id: e.target.value || undefined }))}
|
||||||
|
/>
|
||||||
<Button
|
<Button
|
||||||
onClick={handleCreateUser}
|
onClick={handleCreateUser}
|
||||||
disabled={loading || !createUserData.username || !createUserData.password}
|
disabled={loading || !createUserData.username || !createUserData.password || !createUserData.email || !createUserData.phone}
|
||||||
className="w-full"
|
className="w-full"
|
||||||
>
|
>
|
||||||
{loading ? '创建中...' : '创建用户'}
|
{loading ? '创建中...' : '创建用户'}
|
||||||
@@ -647,12 +725,74 @@ export default function ApiExamplePage() {
|
|||||||
</TabsContent>
|
</TabsContent>
|
||||||
|
|
||||||
<TabsContent value="tenant" className="space-y-4">
|
<TabsContent value="tenant" className="space-y-4">
|
||||||
{/* 租户操作 */}
|
{/* 创建租户表单 */}
|
||||||
|
<div className="space-y-2">
|
||||||
|
<h4 className="font-medium">创建租户</h4>
|
||||||
|
<Input
|
||||||
|
placeholder="企业名称"
|
||||||
|
value={createTenantData.company_name}
|
||||||
|
onChange={(e) => setCreateTenantData(prev => ({ ...prev, company_name: e.target.value }))}
|
||||||
|
/>
|
||||||
|
<Input
|
||||||
|
placeholder="企业编码"
|
||||||
|
value={createTenantData.tenant_code}
|
||||||
|
onChange={(e) => setCreateTenantData(prev => ({ ...prev, tenant_code: e.target.value }))}
|
||||||
|
/>
|
||||||
|
<select
|
||||||
|
value={createTenantData.company_type}
|
||||||
|
onChange={(e) => setCreateTenantData(prev => ({ ...prev, company_type: e.target.value as any }))}
|
||||||
|
className="w-full p-2 border rounded"
|
||||||
|
>
|
||||||
|
<option value="个体工商户">个体工商户</option>
|
||||||
|
<option value="有限责任公司">有限责任公司</option>
|
||||||
|
<option value="股份有限公司">股份有限公司</option>
|
||||||
|
<option value="合伙企业">合伙企业</option>
|
||||||
|
<option value="其他">其他</option>
|
||||||
|
</select>
|
||||||
|
<Button
|
||||||
|
onClick={async () => {
|
||||||
|
setLoading(true);
|
||||||
|
try {
|
||||||
|
addResult('创建租户', 'POST /api/v1/tenants', '发送中...');
|
||||||
|
const response = await createTenantApiV1TenantsPost({
|
||||||
|
body: createTenantData
|
||||||
|
});
|
||||||
|
if (response.data) {
|
||||||
|
addResult('创建租户', createTenantData, response.data);
|
||||||
|
} else if (response.error) {
|
||||||
|
addResult('创建租户', createTenantData, null, JSON.stringify(response.error));
|
||||||
|
}
|
||||||
|
} catch (error: any) {
|
||||||
|
addResult('创建租户', createTenantData, null, error.message);
|
||||||
|
} finally {
|
||||||
|
setLoading(false);
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
disabled={loading || !createTenantData.company_name || !createTenantData.tenant_code}
|
||||||
|
className="w-full"
|
||||||
|
>
|
||||||
|
{loading ? '创建中...' : '创建租户'}
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* 其他租户操作 */}
|
||||||
<div className="grid grid-cols-1 gap-2">
|
<div className="grid grid-cols-1 gap-2">
|
||||||
<Button
|
<Button
|
||||||
onClick={() => {
|
onClick={async () => {
|
||||||
setTenantId('');
|
setLoading(true);
|
||||||
addResult('获取租户列表', 'GET /api/v1/tenants', '功能待实现');
|
try {
|
||||||
|
addResult('获取租户列表', 'GET /api/v1/tenants', '发送中...');
|
||||||
|
const response = await listTenantsApiV1TenantsGet({});
|
||||||
|
if (response.data) {
|
||||||
|
addResult('获取租户列表', '无参数', response.data);
|
||||||
|
} else if (response.error) {
|
||||||
|
addResult('获取租户列表', '无参数', null, JSON.stringify(response.error));
|
||||||
|
}
|
||||||
|
} catch (error: any) {
|
||||||
|
addResult('获取租户列表', '无参数', null, error.message);
|
||||||
|
} finally {
|
||||||
|
setLoading(false);
|
||||||
|
}
|
||||||
}}
|
}}
|
||||||
disabled={loading}
|
disabled={loading}
|
||||||
variant="outline"
|
variant="outline"
|
||||||
@@ -660,15 +800,165 @@ export default function ApiExamplePage() {
|
|||||||
获取租户列表
|
获取租户列表
|
||||||
</Button>
|
</Button>
|
||||||
<Button
|
<Button
|
||||||
onClick={() => {
|
onClick={async () => {
|
||||||
setTenantId('');
|
setLoading(true);
|
||||||
addResult('获取当前租户', 'GET /api/v1/tenants/me', '功能待实现');
|
try {
|
||||||
|
addResult('获取当前租户', 'GET /api/v1/tenants/me', '发送中...');
|
||||||
|
const response = await getCurrentTenantApiV1TenantsMeGet({});
|
||||||
|
if (response.data) {
|
||||||
|
addResult('获取当前租户', '无参数', response.data);
|
||||||
|
} else if (response.error) {
|
||||||
|
addResult('获取当前租户', '无参数', null, JSON.stringify(response.error));
|
||||||
|
}
|
||||||
|
} catch (error: any) {
|
||||||
|
addResult('获取当前租户', '无参数', null, error.message);
|
||||||
|
} finally {
|
||||||
|
setLoading(false);
|
||||||
|
}
|
||||||
}}
|
}}
|
||||||
disabled={loading}
|
disabled={loading}
|
||||||
variant="outline"
|
variant="outline"
|
||||||
>
|
>
|
||||||
获取当前租户
|
获取当前租户
|
||||||
</Button>
|
</Button>
|
||||||
|
<Button
|
||||||
|
onClick={async () => {
|
||||||
|
setLoading(true);
|
||||||
|
try {
|
||||||
|
addResult('获取租户审计日志', 'GET /api/v1/tenants/audit-logs', '发送中...');
|
||||||
|
const response = await getTenantAuditLogsApiV1TenantsAuditLogsGet({});
|
||||||
|
if (response.data) {
|
||||||
|
addResult('获取租户审计日志', '无参数', response.data);
|
||||||
|
} else if (response.error) {
|
||||||
|
addResult('获取租户审计日志', '无参数', null, JSON.stringify(response.error));
|
||||||
|
}
|
||||||
|
} catch (error: any) {
|
||||||
|
addResult('获取租户审计日志', '无参数', null, error.message);
|
||||||
|
} finally {
|
||||||
|
setLoading(false);
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
disabled={loading}
|
||||||
|
variant="outline"
|
||||||
|
>
|
||||||
|
获取租户审计日志
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</TabsContent>
|
||||||
|
|
||||||
|
<TabsContent value="department" className="space-y-4">
|
||||||
|
{/* 创建部门表单 */}
|
||||||
|
<div className="space-y-2">
|
||||||
|
<h4 className="font-medium">创建部门</h4>
|
||||||
|
<Input
|
||||||
|
placeholder="部门编码"
|
||||||
|
value={createDepartmentData.code}
|
||||||
|
onChange={(e) => setCreateDepartmentData(prev => ({ ...prev, code: e.target.value }))}
|
||||||
|
/>
|
||||||
|
<Input
|
||||||
|
placeholder="部门名称"
|
||||||
|
value={createDepartmentData.name}
|
||||||
|
onChange={(e) => setCreateDepartmentData(prev => ({ ...prev, name: e.target.value }))}
|
||||||
|
/>
|
||||||
|
<Input
|
||||||
|
placeholder="部门描述"
|
||||||
|
value={createDepartmentData.description}
|
||||||
|
onChange={(e) => setCreateDepartmentData(prev => ({ ...prev, description: e.target.value }))}
|
||||||
|
/>
|
||||||
|
<Button
|
||||||
|
onClick={async () => {
|
||||||
|
setLoading(true);
|
||||||
|
try {
|
||||||
|
addResult('创建部门', 'POST /api/v1/departments/departments', '发送中...');
|
||||||
|
const response = await createDepartmentApiV1DepartmentsDepartmentsPost({
|
||||||
|
body: createDepartmentData
|
||||||
|
});
|
||||||
|
if (response.data) {
|
||||||
|
addResult('创建部门', createDepartmentData, response.data);
|
||||||
|
} else if (response.error) {
|
||||||
|
addResult('创建部门', createDepartmentData, null, JSON.stringify(response.error));
|
||||||
|
}
|
||||||
|
} catch (error: any) {
|
||||||
|
addResult('创建部门', createDepartmentData, null, error.message);
|
||||||
|
} finally {
|
||||||
|
setLoading(false);
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
disabled={loading || !createDepartmentData.code || !createDepartmentData.name}
|
||||||
|
className="w-full"
|
||||||
|
>
|
||||||
|
{loading ? '创建中...' : '创建部门'}
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* 其他部门操作 */}
|
||||||
|
<div className="grid grid-cols-1 gap-2">
|
||||||
|
<Button
|
||||||
|
onClick={async () => {
|
||||||
|
setLoading(true);
|
||||||
|
try {
|
||||||
|
addResult('获取部门列表', 'GET /api/v1/departments/departments', '发送中...');
|
||||||
|
const response = await getDepartmentsApiV1DepartmentsDepartmentsGet({});
|
||||||
|
if (response.data) {
|
||||||
|
addResult('获取部门列表', '无参数', response.data);
|
||||||
|
} else if (response.error) {
|
||||||
|
addResult('获取部门列表', '无参数', null, JSON.stringify(response.error));
|
||||||
|
}
|
||||||
|
} catch (error: any) {
|
||||||
|
addResult('获取部门列表', '无参数', null, error.message);
|
||||||
|
} finally {
|
||||||
|
setLoading(false);
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
disabled={loading}
|
||||||
|
variant="outline"
|
||||||
|
>
|
||||||
|
获取部门列表
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
onClick={async () => {
|
||||||
|
setLoading(true);
|
||||||
|
try {
|
||||||
|
addResult('获取部门树', 'GET /api/v1/departments/departments/tree', '发送中...');
|
||||||
|
const response = await getDepartmentTreeApiV1DepartmentsDepartmentsTreeGet({});
|
||||||
|
if (response.data) {
|
||||||
|
addResult('获取部门树', '无参数', response.data);
|
||||||
|
} else if (response.error) {
|
||||||
|
addResult('获取部门树', '无参数', null, JSON.stringify(response.error));
|
||||||
|
}
|
||||||
|
} catch (error: any) {
|
||||||
|
addResult('获取部门树', '无参数', null, error.message);
|
||||||
|
} finally {
|
||||||
|
setLoading(false);
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
disabled={loading}
|
||||||
|
variant="outline"
|
||||||
|
>
|
||||||
|
获取部门树
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
onClick={async () => {
|
||||||
|
setLoading(true);
|
||||||
|
try {
|
||||||
|
addResult('获取部门选项', 'GET /api/v1/users/departments/options', '发送中...');
|
||||||
|
const response = await getDepartmentOptionsApiV1UsersDepartmentsOptionsGet({});
|
||||||
|
if (response.data) {
|
||||||
|
addResult('获取部门选项', '无参数', response.data);
|
||||||
|
} else if (response.error) {
|
||||||
|
addResult('获取部门选项', '无参数', null, JSON.stringify(response.error));
|
||||||
|
}
|
||||||
|
} catch (error: any) {
|
||||||
|
addResult('获取部门选项', '无参数', null, error.message);
|
||||||
|
} finally {
|
||||||
|
setLoading(false);
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
disabled={loading}
|
||||||
|
variant="outline"
|
||||||
|
>
|
||||||
|
获取部门选项
|
||||||
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
</TabsContent>
|
</TabsContent>
|
||||||
|
|
||||||
@@ -767,6 +1057,10 @@ export default function ApiExamplePage() {
|
|||||||
<TabsContent value="examples" className="space-y-6">
|
<TabsContent value="examples" className="space-y-6">
|
||||||
<ApiExamplesPage />
|
<ApiExamplesPage />
|
||||||
</TabsContent>
|
</TabsContent>
|
||||||
|
|
||||||
|
<TabsContent value="documentation" className="space-y-6">
|
||||||
|
<OpenApiExamples />
|
||||||
|
</TabsContent>
|
||||||
</Tabs>
|
</Tabs>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -789,7 +1083,7 @@ function ApiExamplesPage() {
|
|||||||
description: '获取登录验证码',
|
description: '获取登录验证码',
|
||||||
exampleParams: null,
|
exampleParams: null,
|
||||||
category: '认证',
|
category: '认证',
|
||||||
expectedOutput: { captcha: 'abc123', captcha_id: 'xyz789' }
|
expectedOutput: { captcha_id: 'xyz789', image: 'base64_image_data' }
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: 'login',
|
id: 'login',
|
||||||
@@ -798,12 +1092,13 @@ function ApiExamplesPage() {
|
|||||||
title: '用户登录',
|
title: '用户登录',
|
||||||
description: '用户登录(需要验证码)',
|
description: '用户登录(需要验证码)',
|
||||||
exampleParams: {
|
exampleParams: {
|
||||||
username: 'admin',
|
identifier: 'admin',
|
||||||
password: 'admin123',
|
password: 'admin123',
|
||||||
captcha: 'test'
|
captcha_id: 'test_captcha_id',
|
||||||
|
captcha_text: 'test'
|
||||||
},
|
},
|
||||||
category: '认证',
|
category: '认证',
|
||||||
expectedOutput: { access_token: 'jwt_token', token_type: 'bearer' }
|
expectedOutput: { access_token: 'jwt_token', token_type: 'bearer', expires_in: 3600 }
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: 'register',
|
id: 'register',
|
||||||
@@ -815,10 +1110,14 @@ function ApiExamplesPage() {
|
|||||||
username: 'newuser',
|
username: 'newuser',
|
||||||
password: 'password123',
|
password: 'password123',
|
||||||
email: 'user@example.com',
|
email: 'user@example.com',
|
||||||
full_name: '新用户'
|
full_name: '新用户',
|
||||||
|
phone: '13800138000',
|
||||||
|
tenant_id: 'tenant-uuid',
|
||||||
|
scope: 'user',
|
||||||
|
department_id: 'dept-uuid'
|
||||||
},
|
},
|
||||||
category: '认证',
|
category: '认证',
|
||||||
expectedOutput: { id: 'uuid', username: 'newuser', email: 'user@example.com' }
|
expectedOutput: { id: 'uuid', username: 'newuser', email: 'user@example.com', phone: '13800138000' }
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: 'getCurrentUser',
|
id: 'getCurrentUser',
|
||||||
@@ -872,10 +1171,14 @@ function ApiExamplesPage() {
|
|||||||
username: 'testuser',
|
username: 'testuser',
|
||||||
password: 'password123',
|
password: 'password123',
|
||||||
email: 'test@example.com',
|
email: 'test@example.com',
|
||||||
full_name: '测试用户'
|
full_name: '测试用户',
|
||||||
|
phone: '13900139000',
|
||||||
|
tenant_id: 'tenant-uuid',
|
||||||
|
scope: 'user',
|
||||||
|
department_id: 'dept-uuid'
|
||||||
},
|
},
|
||||||
category: '用户管理',
|
category: '用户管理',
|
||||||
expectedOutput: { id: 'uuid', username: 'testuser' }
|
expectedOutput: { id: 'uuid', username: 'testuser', email: 'test@example.com', phone: '13900139000' }
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: 'getUserStats',
|
id: 'getUserStats',
|
||||||
@@ -885,7 +1188,46 @@ function ApiExamplesPage() {
|
|||||||
description: '获取用户统计信息',
|
description: '获取用户统计信息',
|
||||||
exampleParams: null,
|
exampleParams: null,
|
||||||
category: '用户管理',
|
category: '用户管理',
|
||||||
expectedOutput: { total_users: 10, active_users: 8 }
|
expectedOutput: { total_users: 10, active_users: 8, inactive_users: 2 }
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'getDepartmentOptions',
|
||||||
|
method: 'GET',
|
||||||
|
path: '/api/v1/users/departments/options',
|
||||||
|
title: '获取部门选项',
|
||||||
|
description: '获取部门选择列表(用于用户管理中的部门选择)',
|
||||||
|
exampleParams: null,
|
||||||
|
category: '用户管理',
|
||||||
|
expectedOutput: [{ id: 'uuid', code: 'TECH001', name: '技术开发部' }]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'listSystemUsers',
|
||||||
|
method: 'GET',
|
||||||
|
path: '/api/v1/users/system/users',
|
||||||
|
title: '获取系统用户列表',
|
||||||
|
description: '获取系统用户列表(需要系统权限)',
|
||||||
|
exampleParams: { page: 1, size: 20, search: '', is_active: true },
|
||||||
|
category: '用户管理',
|
||||||
|
expectedOutput: { data: [{ id: 'uuid', username: 'admin', is_active: true }], total: 1 }
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'createSystemUser',
|
||||||
|
method: 'POST',
|
||||||
|
path: '/api/v1/users/system/users',
|
||||||
|
title: '创建系统用户',
|
||||||
|
description: '创建系统级用户(需要系统权限)',
|
||||||
|
exampleParams: {
|
||||||
|
username: 'system_admin',
|
||||||
|
password: 'admin123',
|
||||||
|
email: 'admin@system.com',
|
||||||
|
full_name: '系统管理员',
|
||||||
|
phone: '13700137000',
|
||||||
|
tenant_id: null,
|
||||||
|
scope: 'system',
|
||||||
|
department_id: null
|
||||||
|
},
|
||||||
|
category: '用户管理',
|
||||||
|
expectedOutput: { id: 'uuid', username: 'system_admin', scope: 'system' }
|
||||||
},
|
},
|
||||||
|
|
||||||
// 租户管理
|
// 租户管理
|
||||||
@@ -895,9 +1237,9 @@ function ApiExamplesPage() {
|
|||||||
path: '/api/v1/tenants',
|
path: '/api/v1/tenants',
|
||||||
title: '获取租户列表',
|
title: '获取租户列表',
|
||||||
description: '获取租户列表',
|
description: '获取租户列表',
|
||||||
exampleParams: { page: 1, size: 20 },
|
exampleParams: { page: 1, size: 20, search: '', audit_status: 'approved' },
|
||||||
category: '租户管理',
|
category: '租户管理',
|
||||||
expectedOutput: { data: [{ id: 'uuid', name: '测试企业' }], total: 1 }
|
expectedOutput: { data: [{ id: 'uuid', name: '测试企业' }], total: 1, page: 1, size: 20 }
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: 'getCurrentTenant',
|
id: 'getCurrentTenant',
|
||||||
@@ -907,7 +1249,31 @@ function ApiExamplesPage() {
|
|||||||
description: '获取当前登录租户信息',
|
description: '获取当前登录租户信息',
|
||||||
exampleParams: null,
|
exampleParams: null,
|
||||||
category: '租户管理',
|
category: '租户管理',
|
||||||
expectedOutput: { id: 'uuid', name: '测试企业', code: 'TEST001' }
|
expectedOutput: { id: 'uuid', name: '测试企业', code: 'TEST001', audit_status: 'approved' }
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'createTenant',
|
||||||
|
method: 'POST',
|
||||||
|
path: '/api/v1/tenants',
|
||||||
|
title: '创建租户',
|
||||||
|
description: '创建新租户',
|
||||||
|
exampleParams: {
|
||||||
|
company_name: '新企业有限责任公司',
|
||||||
|
tenant_code: 'NEW001',
|
||||||
|
company_type: '有限责任公司'
|
||||||
|
},
|
||||||
|
category: '租户管理',
|
||||||
|
expectedOutput: { id: 'uuid', company_name: '新企业有限责任公司', tenant_code: 'NEW001', company_type: '有限责任公司' }
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'getTenantAuditLogs',
|
||||||
|
method: 'GET',
|
||||||
|
path: '/api/v1/tenants/audit-logs',
|
||||||
|
title: '获取租户审计日志',
|
||||||
|
description: '获取租户审计日志',
|
||||||
|
exampleParams: { page: 1, size: 20, tenant_id: 'uuid' },
|
||||||
|
category: '租户管理',
|
||||||
|
expectedOutput: { data: [{ id: 'uuid', action: 'create', timestamp: '2024-01-01T00:00:00Z' }], total: 1 }
|
||||||
},
|
},
|
||||||
|
|
||||||
// 部门管理
|
// 部门管理
|
||||||
@@ -919,7 +1285,21 @@ function ApiExamplesPage() {
|
|||||||
description: '获取部门列表',
|
description: '获取部门列表',
|
||||||
exampleParams: { page: 1, size: 20 },
|
exampleParams: { page: 1, size: 20 },
|
||||||
category: '部门管理',
|
category: '部门管理',
|
||||||
expectedOutput: { data: [{ id: 'uuid', name: '技术部' }], total: 1 }
|
expectedOutput: { data: [{ id: 'uuid', name: '技术部', code: 'TECH' }], total: 1, page: 1, size: 20 }
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'createDepartment',
|
||||||
|
method: 'POST',
|
||||||
|
path: '/api/v1/departments/departments',
|
||||||
|
title: '创建部门',
|
||||||
|
description: '创建新部门',
|
||||||
|
exampleParams: {
|
||||||
|
code: 'TECH001',
|
||||||
|
name: '技术开发部',
|
||||||
|
description: '负责技术研发工作'
|
||||||
|
},
|
||||||
|
category: '部门管理',
|
||||||
|
expectedOutput: { id: 'uuid', code: 'TECH001', name: '技术开发部', description: '负责技术研发工作' }
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: 'getDepartmentTree',
|
id: 'getDepartmentTree',
|
||||||
@@ -929,7 +1309,7 @@ function ApiExamplesPage() {
|
|||||||
description: '获取部门树形结构',
|
description: '获取部门树形结构',
|
||||||
exampleParams: null,
|
exampleParams: null,
|
||||||
category: '部门管理',
|
category: '部门管理',
|
||||||
expectedOutput: [{ id: 'uuid', name: '技术部', children: [] }]
|
expectedOutput: [{ id: 'uuid', code: 'TECH001', name: '技术开发部', description: '负责技术研发工作', children: [] }]
|
||||||
},
|
},
|
||||||
|
|
||||||
// 系统管理
|
// 系统管理
|
||||||
@@ -1021,6 +1401,19 @@ function ApiExamplesPage() {
|
|||||||
case 'getUserStats':
|
case 'getUserStats':
|
||||||
response = await getUserStatsApiV1UsersStatsSummaryGet({});
|
response = await getUserStatsApiV1UsersStatsSummaryGet({});
|
||||||
break;
|
break;
|
||||||
|
case 'getDepartmentOptions':
|
||||||
|
response = await getDepartmentOptionsApiV1UsersDepartmentsOptionsGet({});
|
||||||
|
break;
|
||||||
|
case 'listSystemUsers':
|
||||||
|
response = await listSystemUsersApiV1UsersSystemUsersGet({
|
||||||
|
query: example.exampleParams
|
||||||
|
});
|
||||||
|
break;
|
||||||
|
case 'createSystemUser':
|
||||||
|
response = await createSystemUserApiV1UsersSystemUsersPost({
|
||||||
|
body: example.exampleParams
|
||||||
|
});
|
||||||
|
break;
|
||||||
|
|
||||||
// 租户管理
|
// 租户管理
|
||||||
case 'listTenants':
|
case 'listTenants':
|
||||||
@@ -1031,6 +1424,16 @@ function ApiExamplesPage() {
|
|||||||
case 'getCurrentTenant':
|
case 'getCurrentTenant':
|
||||||
response = await getCurrentTenantApiV1TenantsMeGet({});
|
response = await getCurrentTenantApiV1TenantsMeGet({});
|
||||||
break;
|
break;
|
||||||
|
case 'createTenant':
|
||||||
|
response = await createTenantApiV1TenantsPost({
|
||||||
|
body: example.exampleParams
|
||||||
|
});
|
||||||
|
break;
|
||||||
|
case 'getTenantAuditLogs':
|
||||||
|
response = await getTenantAuditLogsApiV1TenantsAuditLogsGet({
|
||||||
|
query: example.exampleParams
|
||||||
|
});
|
||||||
|
break;
|
||||||
|
|
||||||
// 部门管理
|
// 部门管理
|
||||||
case 'getDepartments':
|
case 'getDepartments':
|
||||||
@@ -1038,6 +1441,11 @@ function ApiExamplesPage() {
|
|||||||
query: example.exampleParams
|
query: example.exampleParams
|
||||||
});
|
});
|
||||||
break;
|
break;
|
||||||
|
case 'createDepartment':
|
||||||
|
response = await createDepartmentApiV1DepartmentsDepartmentsPost({
|
||||||
|
body: example.exampleParams
|
||||||
|
});
|
||||||
|
break;
|
||||||
case 'getDepartmentTree':
|
case 'getDepartmentTree':
|
||||||
response = await getDepartmentTreeApiV1DepartmentsDepartmentsTreeGet({});
|
response = await getDepartmentTreeApiV1DepartmentsDepartmentsTreeGet({});
|
||||||
break;
|
break;
|
||||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user