生产管理系统 - 页面布局调整

This commit is contained in:
2025-10-31 10:47:00 +08:00
parent 8b7e86b8bf
commit 2fa64e66c9
6 changed files with 2325 additions and 1737 deletions

View File

@@ -9,6 +9,44 @@
const fs = require('fs');
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 颜色代码
const colors = {
reset: '\x1b[0m',
@@ -44,7 +82,8 @@ function logInfo(message) {
// 显示环境配置信息
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('自定义文件已恢复');
}
/**
* 下载并保存 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 命令生成客户端代码
*/
@@ -259,6 +351,9 @@ async function main() {
backupCustomFiles(customFiles);
}
// 下载 openapi.json 文件到本地
await downloadOpenApiJson();
// 使用 openapi-ts 生成代码
await generateWithOpenApiTS();

File diff suppressed because one or more lines are too long

View 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>
);
}

View File

@@ -72,14 +72,16 @@ import {
// 类型导入
type UserCreate,
type UserCreateWithCompany,
type UserLogin,
type UserLoginWithCaptcha,
type UserUpdate,
type UserUpdatePassword,
type TenantCreateRequest,
type TenantUpdateRequest,
type TenantAuditRequest,
type DepartmentCreate,
type DepartmentUpdate
type DepartmentUpdate,
type CaptchaResponse,
type Token
} from '@/lib/api';
import { Button } from '@/components/ui/button';
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 { Separator } from '@/components/ui/separator';
import { Label } from '@/components/ui/label';
import OpenApiExamples from './openapi-examples';
export default function ApiExamplePage() {
const [loading, setLoading] = useState(false);
@@ -97,18 +100,26 @@ export default function ApiExamplePage() {
const [errors, setErrors] = useState<string[]>([]);
// 登录表单状态
const [loginData, setLoginData] = useState<UserLogin>({
username: '',
const [loginData, setLoginData] = useState<UserLoginWithCaptcha>({
identifier: '',
password: '',
captcha: ''
captcha_id: '',
captcha_text: ''
});
// 验证码状态
const [captchaData, setCaptchaData] = useState<CaptchaResponse | null>(null);
// 注册表单状态
const [registerData, setRegisterData] = useState<UserCreate>({
username: '',
password: '',
email: '',
full_name: ''
full_name: '',
phone: '',
tenant_id: '',
scope: '',
department_id: ''
});
// 带企业注册表单状态
@@ -132,17 +143,17 @@ export default function ApiExamplePage() {
password: '',
email: '',
full_name: '',
department_id: '',
phone: ''
phone: '',
tenant_id: '',
scope: '',
department_id: ''
});
// 租户创建表单状态
const [createTenantData, setCreateTenantData] = useState<TenantCreateRequest>({
name: '',
code: '',
description: '',
contact_email: '',
contact_phone: ''
company_name: '',
tenant_code: '',
company_type: '有限责任公司'
});
// 部门创建表单状态
@@ -255,6 +266,8 @@ export default function ApiExamplePage() {
const response = await getCaptchaApiV1AuthCaptchaGet({});
if (response.data) {
setCaptchaData(response.data);
setLoginData(prev => ({ ...prev, captcha_id: response.data.captcha_id }));
addResult('获取验证码', '无参数', response.data);
} else if (response.error) {
addResult('获取验证码', '无参数', null, JSON.stringify(response.error));
@@ -450,9 +463,10 @@ export default function ApiExamplePage() {
</div>
<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="examples"></TabsTrigger>
<TabsTrigger value="documentation">OpenAPI </TabsTrigger>
</TabsList>
<TabsContent value="interactive" className="space-y-6">
@@ -467,24 +481,25 @@ export default function ApiExamplePage() {
</CardHeader>
<CardContent>
<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="user"></TabsTrigger>
<TabsTrigger value="tenant"></TabsTrigger>
<TabsTrigger value="department"></TabsTrigger>
<TabsTrigger value="system"></TabsTrigger>
</TabsList>
<TabsContent value="auth" className="space-y-4">
{/* 登录表单 */}
<div className="space-y-3">
<h4 className="font-medium"></h4>
<h4 className="font-medium"></h4>
<div className="space-y-2">
<Label htmlFor="username"></Label>
<Label htmlFor="identifier">//</Label>
<Input
id="username"
placeholder="请输入用户名"
value={loginData.username}
onChange={(e) => setLoginData(prev => ({ ...prev, username: e.target.value }))}
id="identifier"
placeholder="请输入用户名、邮箱或手机号"
value={loginData.identifier}
onChange={(e) => setLoginData(prev => ({ ...prev, identifier: e.target.value }))}
/>
</div>
<div className="space-y-2">
@@ -498,17 +513,40 @@ export default function ApiExamplePage() {
/>
</div>
<div className="space-y-2">
<Label htmlFor="captcha"></Label>
<Input
id="captcha"
placeholder="请输入验证码"
value={loginData.captcha}
onChange={(e) => setLoginData(prev => ({ ...prev, captcha: e.target.value }))}
/>
<Label htmlFor="captcha_text"></Label>
<div className="flex gap-2">
<Input
id="captcha_text"
placeholder="请输入验证码"
value={loginData.captcha_text}
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>
<Button
onClick={handleLogin}
disabled={loading || !loginData.username || !loginData.password}
disabled={loading || !loginData.identifier || !loginData.password || !loginData.captcha_text || !loginData.captcha_id}
className="w-full"
>
{loading ? '登录中...' : '登录'}
@@ -539,9 +577,29 @@ export default function ApiExamplePage() {
value={registerData.full_name}
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
onClick={handleRegister}
disabled={loading || !registerData.username || !registerData.password}
disabled={loading || !registerData.username || !registerData.password || !registerData.email || !registerData.phone}
variant="outline"
className="w-full"
>
@@ -607,9 +665,29 @@ export default function ApiExamplePage() {
value={createUserData.full_name}
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
onClick={handleCreateUser}
disabled={loading || !createUserData.username || !createUserData.password}
disabled={loading || !createUserData.username || !createUserData.password || !createUserData.email || !createUserData.phone}
className="w-full"
>
{loading ? '创建中...' : '创建用户'}
@@ -647,12 +725,74 @@ export default function ApiExamplePage() {
</TabsContent>
<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">
<Button
onClick={() => {
setTenantId('');
addResult('获取租户列表', 'GET /api/v1/tenants', '功能待实现');
onClick={async () => {
setLoading(true);
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}
variant="outline"
@@ -660,15 +800,165 @@ export default function ApiExamplePage() {
</Button>
<Button
onClick={() => {
setTenantId('');
addResult('获取当前租户', 'GET /api/v1/tenants/me', '功能待实现');
onClick={async () => {
setLoading(true);
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}
variant="outline"
>
</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>
</TabsContent>
@@ -767,6 +1057,10 @@ export default function ApiExamplePage() {
<TabsContent value="examples" className="space-y-6">
<ApiExamplesPage />
</TabsContent>
<TabsContent value="documentation" className="space-y-6">
<OpenApiExamples />
</TabsContent>
</Tabs>
</div>
</div>
@@ -789,7 +1083,7 @@ function ApiExamplesPage() {
description: '获取登录验证码',
exampleParams: null,
category: '认证',
expectedOutput: { captcha: 'abc123', captcha_id: 'xyz789' }
expectedOutput: { captcha_id: 'xyz789', image: 'base64_image_data' }
},
{
id: 'login',
@@ -798,12 +1092,13 @@ function ApiExamplesPage() {
title: '用户登录',
description: '用户登录(需要验证码)',
exampleParams: {
username: 'admin',
identifier: 'admin',
password: 'admin123',
captcha: 'test'
captcha_id: 'test_captcha_id',
captcha_text: 'test'
},
category: '认证',
expectedOutput: { access_token: 'jwt_token', token_type: 'bearer' }
expectedOutput: { access_token: 'jwt_token', token_type: 'bearer', expires_in: 3600 }
},
{
id: 'register',
@@ -815,10 +1110,14 @@ function ApiExamplesPage() {
username: 'newuser',
password: 'password123',
email: 'user@example.com',
full_name: '新用户'
full_name: '新用户',
phone: '13800138000',
tenant_id: 'tenant-uuid',
scope: 'user',
department_id: 'dept-uuid'
},
category: '认证',
expectedOutput: { id: 'uuid', username: 'newuser', email: 'user@example.com' }
expectedOutput: { id: 'uuid', username: 'newuser', email: 'user@example.com', phone: '13800138000' }
},
{
id: 'getCurrentUser',
@@ -872,10 +1171,14 @@ function ApiExamplesPage() {
username: 'testuser',
password: 'password123',
email: 'test@example.com',
full_name: '测试用户'
full_name: '测试用户',
phone: '13900139000',
tenant_id: 'tenant-uuid',
scope: 'user',
department_id: 'dept-uuid'
},
category: '用户管理',
expectedOutput: { id: 'uuid', username: 'testuser' }
expectedOutput: { id: 'uuid', username: 'testuser', email: 'test@example.com', phone: '13900139000' }
},
{
id: 'getUserStats',
@@ -885,7 +1188,46 @@ function ApiExamplesPage() {
description: '获取用户统计信息',
exampleParams: null,
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',
title: '获取租户列表',
description: '获取租户列表',
exampleParams: { page: 1, size: 20 },
exampleParams: { page: 1, size: 20, search: '', audit_status: 'approved' },
category: '租户管理',
expectedOutput: { data: [{ id: 'uuid', name: '测试企业' }], total: 1 }
expectedOutput: { data: [{ id: 'uuid', name: '测试企业' }], total: 1, page: 1, size: 20 }
},
{
id: 'getCurrentTenant',
@@ -907,7 +1249,31 @@ function ApiExamplesPage() {
description: '获取当前登录租户信息',
exampleParams: null,
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: '获取部门列表',
exampleParams: { page: 1, size: 20 },
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',
@@ -929,7 +1309,7 @@ function ApiExamplesPage() {
description: '获取部门树形结构',
exampleParams: null,
category: '部门管理',
expectedOutput: [{ id: 'uuid', name: '技术部', children: [] }]
expectedOutput: [{ id: 'uuid', code: 'TECH001', name: '技术开发部', description: '负责技术研发工作', children: [] }]
},
// 系统管理
@@ -1021,6 +1401,19 @@ function ApiExamplesPage() {
case 'getUserStats':
response = await getUserStatsApiV1UsersStatsSummaryGet({});
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':
@@ -1031,6 +1424,16 @@ function ApiExamplesPage() {
case 'getCurrentTenant':
response = await getCurrentTenantApiV1TenantsMeGet({});
break;
case 'createTenant':
response = await createTenantApiV1TenantsPost({
body: example.exampleParams
});
break;
case 'getTenantAuditLogs':
response = await getTenantAuditLogsApiV1TenantsAuditLogsGet({
query: example.exampleParams
});
break;
// 部门管理
case 'getDepartments':
@@ -1038,6 +1441,11 @@ function ApiExamplesPage() {
query: example.exampleParams
});
break;
case 'createDepartment':
response = await createDepartmentApiV1DepartmentsDepartmentsPost({
body: example.exampleParams
});
break;
case 'getDepartmentTree':
response = await getDepartmentTreeApiV1DepartmentsDepartmentsTreeGet({});
break;

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff