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

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 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

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 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>
<Input <div className="flex gap-2">
id="captcha" <Input
placeholder="请输入验证码" id="captcha_text"
value={loginData.captcha} placeholder="请输入验证码"
onChange={(e) => setLoginData(prev => ({ ...prev, captcha: e.target.value }))} 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> </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