生产管理系统前端 - fetchapi 基础提交

This commit is contained in:
2025-10-27 11:08:23 +08:00
parent 1f1d94ed84
commit 2b39c1dd1a
12 changed files with 2191 additions and 7 deletions

View File

@@ -0,0 +1,14 @@
import ApiExample from '@/components/examples/ApiExample';
export default function ApiExamplePage() {
return (
<div className="min-h-screen bg-background">
<ApiExample />
</div>
);
}
export const metadata = {
title: 'API 调用示例 - 智慧农业生产管理系统',
description: '测试和展示 OpenAPI 客户端的类型安全 API 调用',
};

View File

@@ -0,0 +1,231 @@
'use client';
import React, { useState, useEffect } from 'react';
import { api, testConnection, type User, type Machinery } from '@/lib/api/client';
export default function ApiExample() {
const [users, setUsers] = useState<User[]>([]);
const [machinery, setMachinery] = useState<Machinery[]>([]);
const [loading, setLoading] = useState(false);
const [error, setError] = useState<string | null>(null);
const [connectionStatus, setConnectionStatus] = useState<'testing' | 'connected' | 'disconnected'>('testing');
// 获取用户列表
const fetchUsers = async () => {
setLoading(true);
setError(null);
try {
const result = await api.users.getList({ page: 1, limit: 10 });
if (result?.data?.users) {
setUsers(result.data.users);
}
} catch (err) {
setError(err instanceof Error ? err.message : '获取用户失败');
} finally {
setLoading(false);
}
};
// 获取农机列表
const fetchMachinery = async () => {
setLoading(true);
setError(null);
try {
const result = await api.machinery.getList({ page: 1, limit: 10 });
if (result?.data?.machinery) {
setMachinery(result.data.machinery);
}
} catch (err) {
setError(err instanceof Error ? err.message : '获取农机失败');
} finally {
setLoading(false);
}
};
// 创建新农机
const createMachinery = async () => {
try {
const newMachinery = await api.machinery.create({
name: '新拖拉机',
type: 'tractor',
model: 'John Deere 6M',
serial_number: 'JD6M123456',
purchase_date: new Date().toISOString().split('T')[0],
});
console.log('创建成功:', newMachinery);
// 刷新列表
fetchMachinery();
} catch (err) {
setError(err instanceof Error ? err.message : '创建农机失败');
}
};
// 测试 API 连接
const testApiConnection = async () => {
setConnectionStatus('testing');
const result = await testConnection();
if (result.success) {
setConnectionStatus('connected');
// 连接成功后获取数据
fetchUsers();
fetchMachinery();
} else {
setConnectionStatus('disconnected');
setError(`API 连接失败: ${result.error}`);
}
};
useEffect(() => {
testApiConnection();
}, []);
if (loading) {
return <div className="p-4">...</div>;
}
if (error) {
return (
<div className="p-4">
<div className="text-red-600">: {error}</div>
<button
onClick={() => {
setError(null);
fetchUsers();
fetchMachinery();
}}
className="mt-2 px-4 py-2 bg-blue-500 text-white rounded hover:bg-blue-600"
>
</button>
</div>
);
}
// 连接状态显示
const renderConnectionStatus = () => {
switch (connectionStatus) {
case 'testing':
return (
<div className="mb-4 p-3 bg-blue-50 border border-blue-200 rounded-lg">
<div className="flex items-center">
<div className="animate-spin rounded-full h-4 w-4 border-b-2 border-blue-600 mr-2"></div>
<span className="text-blue-800"> API ...</span>
</div>
</div>
);
case 'connected':
return (
<div className="mb-4 p-3 bg-green-50 border border-green-200 rounded-lg">
<div className="flex items-center justify-between">
<div className="flex items-center">
<div className="w-3 h-3 bg-green-500 rounded-full mr-2"></div>
<span className="text-green-800">API </span>
</div>
<button
onClick={testApiConnection}
className="px-3 py-1 bg-green-600 text-white text-sm rounded hover:bg-green-700"
>
</button>
</div>
</div>
);
case 'disconnected':
return (
<div className="mb-4 p-3 bg-red-50 border border-red-200 rounded-lg">
<div className="flex items-center justify-between">
<div>
<div className="flex items-center">
<div className="w-3 h-3 bg-red-500 rounded-full mr-2"></div>
<span className="text-red-800">API </span>
</div>
<div className="text-sm text-red-600 mt-1">{error}</div>
</div>
<button
onClick={testApiConnection}
className="px-3 py-1 bg-red-600 text-white text-sm rounded hover:bg-red-700"
>
</button>
</div>
</div>
);
}
};
return (
<div className="p-6">
<h1 className="text-2xl font-bold mb-6">API </h1>
{/* 连接状态 */}
{renderConnectionStatus()}
{/* 服务器信息 */}
<div className="mb-6 p-4 bg-muted rounded-lg">
<h3 className="font-semibold mb-2"></h3>
<div className="text-sm text-muted-foreground space-y-1">
<div><strong>Base URL:</strong> https://gitea-admin-test-app-app.dev.maimaiag.com/api/v1</div>
<div><strong>//:</strong> 使</div>
</div>
</div>
<div className="grid grid-cols-1 lg:grid-cols-2 gap-6">
{/* 用户列表 */}
<div className="bg-card border rounded-lg p-4">
<h2 className="text-xl font-semibold mb-4"></h2>
<div className="space-y-2">
{users.map((user) => (
<div key={user.id} className="p-3 bg-muted rounded">
<div className="font-medium">{user.full_name || user.username}</div>
<div className="text-sm text-muted-foreground">
{user.email} {user.role} {user.status}
</div>
</div>
))}
</div>
</div>
{/* 农机列表 */}
<div className="bg-card border rounded-lg p-4">
<div className="flex justify-between items-center mb-4">
<h2 className="text-xl font-semibold"></h2>
<button
onClick={createMachinery}
className="px-3 py-1 bg-primary text-primary-foreground rounded hover:bg-primary/90"
>
</button>
</div>
<div className="space-y-2">
{machinery.map((machine) => (
<div key={machine.id} className="p-3 bg-muted rounded">
<div className="font-medium">{machine.name}</div>
<div className="text-sm text-muted-foreground">
{machine.type} {machine.model} {machine.status}
</div>
{machine.operator && (
<div className="text-xs text-muted-foreground mt-1">
: {machine.operator.full_name || machine.operator.username}
</div>
)}
</div>
))}
</div>
</div>
</div>
{/* 类型安全示例 */}
<div className="mt-8 p-4 bg-muted rounded-lg">
<h3 className="text-lg font-semibold mb-2"></h3>
<ul className="list-disc list-inside space-y-1 text-sm text-muted-foreground">
<li> API </li>
<li> </li>
<li> </li>
<li> IDE </li>
</ul>
</div>
</div>
);
}

View File

@@ -112,6 +112,12 @@ const navbarData = {
description: "租户管理、用户管理、系统监控",
icon: <Settings className="size-5 shrink-0" />,
},
{
title: "API 测试示例",
url: "/api-example",
description: "测试和展示 OpenAPI 客户端调用",
icon: <Brain className="size-5 shrink-0" />,
},
],
auth: {
login: { title: "登录", url: "/login" },

View File

@@ -0,0 +1,204 @@
import createClient from 'openapi-fetch';
import type { paths } from './v1.d.ts';
// 创建 API 客户端
const client = createClient<paths>({
baseUrl: 'https://gitea-admin-test-app-app.dev.maimaiag.com/docs',
// 可以添加默认 headers
headers: {
'Content-Type': 'application/json',
},
});
// 添加认证的客户端
export const authClient = {
...client,
// 包装添加 token 的方法
withAuth: (token: string) => ({
...client,
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${token}`,
},
}),
};
// 测试连接
export const testConnection = async () => {
try {
// 尝试获取一个简单的接口来测试连接
const { data, error, response } = await client.GET('/users', {
params: {
query: { page: 1, limit: 1 }
}
});
if (error) {
console.warn('API 连接测试失败:', error);
return { success: false, error };
}
console.log('API 连接测试成功:', { status: response?.status, data });
return { success: true, data };
} catch (err) {
console.error('API 连接测试出错:', err);
return { success: false, error: err instanceof Error ? err.message : '未知错误' };
}
};
// API 方法封装
export const api = {
// 用户管理 API
users: {
// 获取用户列表
getList: async (params?: {
page?: number;
limit?: number;
search?: string;
}) => {
const { data, error, response } = await client.GET('/users', {
params: {
query: params,
},
});
if (error) {
console.error('获取用户列表失败:', error);
throw new Error(`API Error: ${error}`);
}
return data;
},
// 获取用户详情
getDetail: async (id: number) => {
const { data, error } = await client.GET('/users/{id}', {
params: {
path: { id },
},
});
if (error) {
console.error('获取用户详情失败:', error);
throw new Error(`API Error: ${error}`);
}
return data;
},
},
// 农机管理 API
machinery: {
// 获取农机列表
getList: async (params?: {
page?: number;
limit?: number;
status?: 'running' | 'idle' | 'maintenance' | 'error' | 'offline';
}) => {
const { data, error } = await client.GET('/machinery', {
params: {
query: params,
},
});
if (error) {
console.error('获取农机列表失败:', error);
throw new Error(`API Error: ${error}`);
}
return data;
},
// 获取农机详情
getDetail: async (id: number) => {
const { data, error } = await client.GET('/machinery/{id}', {
params: {
path: { id },
},
});
if (error) {
console.error('获取农机详情失败:', error);
throw new Error(`API Error: ${error}`);
}
return data;
},
// 创建农机
create: async (machineryData: {
name: string;
type: 'tractor' | 'harvester' | 'planter' | 'sprayer' | 'irrigation';
model: string;
serial_number?: string;
operator_id?: number;
purchase_date?: string;
}) => {
const { data, error } = await client.POST('/machinery', {
body: machineryData,
});
if (error) {
console.error('创建农机失败:', error);
throw new Error(`API Error: ${error}`);
}
return data;
},
// 更新农机
update: async (
id: number,
updateData: {
name?: string;
status?: 'running' | 'idle' | 'maintenance' | 'error' | 'offline';
operator_id?: number;
last_maintenance?: string;
next_maintenance?: string;
}
) => {
const { data, error } = await client.PUT('/machinery/{id}', {
params: {
path: { id },
},
body: updateData,
});
if (error) {
console.error('更新农机失败:', error);
throw new Error(`API Error: ${error}`);
}
return data;
},
// 删除农机
delete: async (id: number) => {
const { error } = await client.DELETE('/machinery/{id}', {
params: {
path: { id },
},
});
if (error) {
console.error('删除农机失败:', error);
throw new Error(`API Error: ${error}`);
}
return true; // 删除成功
},
},
};
// 类型导出(供组件使用)
export type {
User,
Machinery,
Location,
Coordinates,
CreateMachineryRequest,
UpdateMachineryRequest,
Error as ApiError
} from './v1.d.ts';
export default client;

526
crop-x/src/lib/api/v1.d.ts vendored Normal file
View File

@@ -0,0 +1,526 @@
/**
* This file was auto-generated by openapi-typescript.
* Do not make direct changes to the file.
*/
export interface paths {
"/users": {
parameters: {
query?: never;
header?: never;
path?: never;
cookie?: never;
};
/**
* 获取用户列表
* @description 获取所有用户的分页列表
*/
get: {
parameters: {
query?: {
/** @description 页码 */
page?: number;
/** @description 每页数量 */
limit?: number;
/** @description 搜索关键词 */
search?: string;
};
header?: never;
path?: never;
cookie?: never;
};
requestBody?: never;
responses: {
/** @description 成功获取用户列表 */
200: {
headers: {
[name: string]: unknown;
};
content: {
"application/json": {
/** @example 200 */
code?: number;
/** @example success */
message?: string;
data?: {
users?: components["schemas"]["User"][];
/** @example 100 */
total?: number;
/** @example 1 */
page?: number;
/** @example 20 */
limit?: number;
};
};
};
};
};
};
put?: never;
post?: never;
delete?: never;
options?: never;
head?: never;
patch?: never;
trace?: never;
};
"/users/{id}": {
parameters: {
query?: never;
header?: never;
path?: never;
cookie?: never;
};
/**
* 获取用户详情
* @description 根据用户ID获取用户详细信息
*/
get: {
parameters: {
query?: never;
header?: never;
path: {
/** @description 用户ID */
id: number;
};
cookie?: never;
};
requestBody?: never;
responses: {
/** @description 成功获取用户详情 */
200: {
headers: {
[name: string]: unknown;
};
content: {
"application/json": {
/** @example 200 */
code?: number;
/** @example success */
message?: string;
data?: components["schemas"]["User"];
};
};
};
/** @description 用户不存在 */
404: {
headers: {
[name: string]: unknown;
};
content: {
"application/json": components["schemas"]["Error"];
};
};
};
};
put?: never;
post?: never;
delete?: never;
options?: never;
head?: never;
patch?: never;
trace?: never;
};
"/machinery": {
parameters: {
query?: never;
header?: never;
path?: never;
cookie?: never;
};
/**
* 获取农机列表
* @description 获取所有农机的分页列表
*/
get: {
parameters: {
query?: {
page?: number;
limit?: number;
/** @description 农机状态筛选 */
status?: "running" | "idle" | "maintenance" | "error" | "offline";
};
header?: never;
path?: never;
cookie?: never;
};
requestBody?: never;
responses: {
/** @description 成功获取农机列表 */
200: {
headers: {
[name: string]: unknown;
};
content: {
"application/json": {
/** @example 200 */
code?: number;
/** @example success */
message?: string;
data?: {
machinery?: components["schemas"]["Machinery"][];
/** @example 50 */
total?: number;
};
};
};
};
};
};
put?: never;
/**
* 创建农机
* @description 创建新的农机记录
*/
post: {
parameters: {
query?: never;
header?: never;
path?: never;
cookie?: never;
};
requestBody: {
content: {
"application/json": components["schemas"]["CreateMachineryRequest"];
};
};
responses: {
/** @description 农机创建成功 */
201: {
headers: {
[name: string]: unknown;
};
content: {
"application/json": {
/** @example 201 */
code?: number;
/** @example 农机创建成功 */
message?: string;
data?: components["schemas"]["Machinery"];
};
};
};
};
};
delete?: never;
options?: never;
head?: never;
patch?: never;
trace?: never;
};
"/machinery/{id}": {
parameters: {
query?: never;
header?: never;
path?: never;
cookie?: never;
};
/** 获取农机详情 */
get: {
parameters: {
query?: never;
header?: never;
path: {
id: number;
};
cookie?: never;
};
requestBody?: never;
responses: {
/** @description 成功获取农机详情 */
200: {
headers: {
[name: string]: unknown;
};
content: {
"application/json": {
/** @example 200 */
code?: number;
/** @example success */
message?: string;
data?: components["schemas"]["Machinery"];
};
};
};
};
};
/** 更新农机信息 */
put: {
parameters: {
query?: never;
header?: never;
path: {
id: number;
};
cookie?: never;
};
requestBody: {
content: {
"application/json": components["schemas"]["UpdateMachineryRequest"];
};
};
responses: {
/** @description 农机更新成功 */
200: {
headers: {
[name: string]: unknown;
};
content: {
"application/json": {
/** @example 200 */
code?: number;
/** @example 农机更新成功 */
message?: string;
data?: components["schemas"]["Machinery"];
};
};
};
};
};
post?: never;
/** 删除农机 */
delete: {
parameters: {
query?: never;
header?: never;
path: {
id: number;
};
cookie?: never;
};
requestBody?: never;
responses: {
/** @description 农机删除成功 */
204: {
headers: {
[name: string]: unknown;
};
content?: never;
};
};
};
options?: never;
head?: never;
patch?: never;
trace?: never;
};
}
export type webhooks = Record<string, never>;
export interface components {
schemas: {
User: {
/**
* @description 用户ID
* @example 1
*/
id?: number;
/**
* @description 用户名
* @example john_doe
*/
username?: string;
/**
* Format: email
* @description 邮箱地址
* @example john@example.com
*/
email?: string;
/**
* @description 全名
* @example John Doe
*/
full_name?: string;
/**
* @description 手机号码
* @example 13800138000
*/
phone?: string;
/**
* @description 用户角色
* @example operator
* @enum {string}
*/
role?: "admin" | "manager" | "operator" | "viewer";
/**
* @description 用户状态
* @example active
* @enum {string}
*/
status?: "active" | "inactive" | "suspended";
/**
* Format: date-time
* @description 创建时间
* @example 2024-01-15T10:30:00Z
*/
created_at?: string;
/**
* Format: date-time
* @description 更新时间
* @example 2024-01-15T10:30:00Z
*/
updated_at?: string;
};
Machinery: {
/**
* @description 农机ID
* @example 1
*/
id?: number;
/**
* @description 农机名称
* @example 拖拉机-001
*/
name?: string;
/**
* @description 农机类型
* @example tractor
* @enum {string}
*/
type?: "tractor" | "harvester" | "planter" | "sprayer" | "irrigation";
/**
* @description 型号
* @example John Deere 6M Series
*/
model?: string;
/**
* @description 序列号
* @example JD6M123456
*/
serial_number?: string;
/**
* @description 农机状态
* @example idle
* @enum {string}
*/
status?: "running" | "idle" | "maintenance" | "error" | "offline";
location?: components["schemas"]["Location"];
operator?: components["schemas"]["User"];
/**
* Format: date
* @description 购买日期
* @example 2024-01-01
*/
purchase_date?: string;
/**
* Format: date
* @description 上次维护日期
* @example 2024-06-15
*/
last_maintenance?: string;
/**
* Format: date
* @description 下次维护日期
* @example 2024-09-15
*/
next_maintenance?: string;
/**
* Format: date-time
* @description 创建时间
*/
created_at?: string;
/**
* Format: date-time
* @description 更新时间
*/
updated_at?: string;
};
Location: {
/**
* @description 地块ID
* @example 1
*/
field_id?: number;
/**
* @description 地块名称
* @example 北区A地块
*/
field_name?: string;
coordinates?: components["schemas"]["Coordinates"];
};
Coordinates: {
/**
* Format: double
* @description 纬度
* @example 39.9042
*/
latitude?: number;
/**
* Format: double
* @description 经度
* @example 116.4074
*/
longitude?: number;
};
CreateMachineryRequest: {
/** @description 农机名称 */
name: string;
/**
* @description 农机类型
* @enum {string}
*/
type: "tractor" | "harvester" | "planter" | "sprayer" | "irrigation";
/** @description 型号 */
model: string;
/** @description 序列号 */
serial_number?: string;
/**
* @description 操作员ID
* @example 1
*/
operator_id?: number;
/**
* Format: date
* @description 购买日期
*/
purchase_date?: string;
};
UpdateMachineryRequest: {
/** @description 农机名称 */
name?: string;
/**
* @description 农机状态
* @enum {string}
*/
status?: "running" | "idle" | "maintenance" | "error" | "offline";
/** @description 操作员ID */
operator_id?: number;
/**
* Format: date
* @description 上次维护日期
*/
last_maintenance?: string;
/**
* Format: date
* @description 下次维护日期
*/
next_maintenance?: string;
};
Error: {
/**
* @description 错误代码
* @example 404
*/
code?: number;
/**
* @description 错误信息
* @example 资源不存在
*/
message?: string;
/**
* @description 错误详情
* @example {
* "field": "id",
* "reason": "用户ID不存在"
* }
*/
details?: Record<string, never>;
};
};
responses: never;
parameters: never;
requestBodies: never;
headers: never;
pathItems: never;
}
export type $defs = Record<string, never>;
export type operations = Record<string, never>;

View File

@@ -25,7 +25,7 @@
--accent-foreground: 240 10% 10%;
--destructive: 0 84% 60%;
--destructive-foreground: 240 10% 98%;
--border: 240 4% 90%;
--border: rgba(0, 0, 0, 0.1);
--input: 240 4% 90%;
--ring: 142 76% 36%;
--radius: 0.5rem;
@@ -91,7 +91,7 @@
--accent-foreground: 240 10% 98%;
--destructive: 0 63% 31%;
--destructive-foreground: 240 10% 98%;
--border: 240 3% 15%;
--border: rgba(255, 255, 255, 0.1);
--input: 240 3% 15%;
--ring: 142 70% 45%;
--sidebar: hsl(240 5.9% 10%);