生产管理系统 - 角色管理、员工管理主页面联调
This commit is contained in:
@@ -213,6 +213,26 @@ src/app/(app)/land-information/archive/statistics/
|
|||||||
通过遵循这些开发规范,我们可以确保代码的一致性、可维护性和用户体验的统一性。
|
通过遵循这些开发规范,我们可以确保代码的一致性、可维护性和用户体验的统一性。
|
||||||
|
|
||||||
---
|
---
|
||||||
|
### 9.注意乱码原则
|
||||||
|
生成的代码注意看看有没有乱码,必须遵守utf-8编码
|
||||||
|
### 10.接口调用原则。
|
||||||
|
接口必须调用 D:\code\repotest\smart-crop-ui\crop-x\src\lib\api\sdk.gen.ts 这个里面的接口
|
||||||
|
比如 /api/v1/departments/tree 这个路径,就是要调用export const getDepartmentTreeApiV1DepartmentsDepartmentsTreeGet = <ThrowOnError extends boolean = false>(options?: Options<GetDepartmentTreeApiV1DepartmentsDepartmentsTreeGetData, ThrowOnError>) => {
|
||||||
|
return (options?.client ?? client).get<GetDepartmentTreeApiV1DepartmentsDepartmentsTreeGetResponses, unknown, ThrowOnError>({
|
||||||
|
security: [
|
||||||
|
{
|
||||||
|
scheme: 'bearer',
|
||||||
|
type: 'http'
|
||||||
|
}
|
||||||
|
],
|
||||||
|
url: '/api/v1/departments/departments/tree',
|
||||||
|
...options
|
||||||
|
});
|
||||||
|
};
|
||||||
|
实际使用的时候,要参考D:\code\repotest\smart-crop-ui\crop-x\src\app\(app)\central-config\tenant\audit-history\components\auditHistoryApi.ts 里面
|
||||||
|
import {
|
||||||
|
getTenantAuditLogsApiV1TenantsAuditLogsGet,
|
||||||
|
} from "@/lib/api/sdk.gen"; 这个引入和用法。
|
||||||
|
|
||||||
## path:land-information/archive/statistics,name:统计分析页面开发经验与问题解决
|
## path:land-information/archive/statistics,name:统计分析页面开发经验与问题解决
|
||||||
|
|
||||||
|
|||||||
File diff suppressed because one or more lines are too long
@@ -103,12 +103,8 @@ export default function EmployeeManagementPage() {
|
|||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Failed to load employees:', error);
|
console.error('Failed to load employees:', error);
|
||||||
toast.error('加载员工数据失败');
|
toast.error('加载员工数据失败');
|
||||||
|
|
||||||
// 如果API失败,使用localStorage中的数据
|
// 如果API失败,使用localStorage中的数据
|
||||||
const data = localStorage.getItem('smart_agriculture_employees');
|
setEmployees([]);
|
||||||
if (data) {
|
|
||||||
setEmployees(JSON.parse(data));
|
|
||||||
}
|
|
||||||
} finally {
|
} finally {
|
||||||
setLoading(false);
|
setLoading(false);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,9 +7,14 @@ import { Badge } from '@/components/ui/badge';
|
|||||||
import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from '@/components/ui/table';
|
import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from '@/components/ui/table';
|
||||||
import { Eye, Edit, Trash2, Shield } from 'lucide-react';
|
import { Eye, Edit, Trash2, Shield } from 'lucide-react';
|
||||||
import { Role, RoleType } from '../types';
|
import { Role, RoleType } from '../types';
|
||||||
|
import { PaginationState } from './roleApi';
|
||||||
|
|
||||||
interface RoleListProps {
|
interface RoleListProps {
|
||||||
roles: Role[];
|
roles: Role[];
|
||||||
|
loading?: boolean;
|
||||||
|
pagination?: PaginationState;
|
||||||
|
onPageChange?: (page: number) => void;
|
||||||
|
onPageSizeChange?: (size: number) => void;
|
||||||
onViewDetail: (role: Role) => void;
|
onViewDetail: (role: Role) => void;
|
||||||
onEdit: (role: Role) => void;
|
onEdit: (role: Role) => void;
|
||||||
onDelete: (id: string) => void;
|
onDelete: (id: string) => void;
|
||||||
@@ -17,6 +22,10 @@ interface RoleListProps {
|
|||||||
|
|
||||||
export function RoleList({
|
export function RoleList({
|
||||||
roles,
|
roles,
|
||||||
|
loading = false,
|
||||||
|
pagination,
|
||||||
|
onPageChange,
|
||||||
|
onPageSizeChange,
|
||||||
onViewDetail,
|
onViewDetail,
|
||||||
onEdit,
|
onEdit,
|
||||||
onDelete
|
onDelete
|
||||||
@@ -52,7 +61,13 @@ export function RoleList({
|
|||||||
</TableRow>
|
</TableRow>
|
||||||
</TableHeader>
|
</TableHeader>
|
||||||
<TableBody>
|
<TableBody>
|
||||||
{roles.length === 0 ? (
|
{loading ? (
|
||||||
|
<TableRow>
|
||||||
|
<TableCell colSpan={7} className="text-center text-muted-foreground py-8">
|
||||||
|
加载中...
|
||||||
|
</TableCell>
|
||||||
|
</TableRow>
|
||||||
|
) : roles.length === 0 ? (
|
||||||
<TableRow>
|
<TableRow>
|
||||||
<TableCell colSpan={7} className="text-center text-muted-foreground py-8">
|
<TableCell colSpan={7} className="text-center text-muted-foreground py-8">
|
||||||
暂无数据
|
暂无数据
|
||||||
@@ -108,6 +123,63 @@ export function RoleList({
|
|||||||
)}
|
)}
|
||||||
</TableBody>
|
</TableBody>
|
||||||
</Table>
|
</Table>
|
||||||
|
|
||||||
|
{/* 分页控制 */}
|
||||||
|
{!loading && pagination && pagination.totalPages > 1 && (
|
||||||
|
<div className="flex items-center justify-between px-2 py-4">
|
||||||
|
<div className="flex items-center space-x-2 text-sm text-muted-foreground">
|
||||||
|
<span>显示第 {(pagination.page - 1) * pagination.size + 1} 至 {Math.min(pagination.page * pagination.size, pagination.total)} 条,共 {pagination.total} 条</span>
|
||||||
|
</div>
|
||||||
|
<div className="flex items-center space-x-2">
|
||||||
|
<Button
|
||||||
|
variant="outline"
|
||||||
|
size="sm"
|
||||||
|
onClick={() => onPageChange?.(pagination.page - 1)}
|
||||||
|
disabled={!pagination.hasPrev}
|
||||||
|
>
|
||||||
|
上一页
|
||||||
|
</Button>
|
||||||
|
|
||||||
|
<div className="flex items-center space-x-1">
|
||||||
|
{Array.from({ length: pagination.totalPages }, (_, i) => i + 1).map((pageNum) => (
|
||||||
|
<Button
|
||||||
|
key={pageNum}
|
||||||
|
variant={pageNum === pagination.page ? "default" : "outline"}
|
||||||
|
size="sm"
|
||||||
|
className="w-8 h-8 p-0"
|
||||||
|
onClick={() => onPageChange?.(pageNum)}
|
||||||
|
>
|
||||||
|
{pageNum}
|
||||||
|
</Button>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<Button
|
||||||
|
variant="outline"
|
||||||
|
size="sm"
|
||||||
|
onClick={() => onPageChange?.(pagination.page + 1)}
|
||||||
|
disabled={!pagination.hasNext}
|
||||||
|
>
|
||||||
|
下一页
|
||||||
|
</Button>
|
||||||
|
|
||||||
|
{onPageSizeChange && (
|
||||||
|
<>
|
||||||
|
<select
|
||||||
|
value={pagination.size}
|
||||||
|
onChange={(e) => onPageSizeChange(Number(e.target.value))}
|
||||||
|
className="ml-2 h-8 w-16 rounded-md border border-input bg-background text-sm"
|
||||||
|
>
|
||||||
|
<option value={10}>10</option>
|
||||||
|
<option value={20}>20</option>
|
||||||
|
<option value={50}>50</option>
|
||||||
|
</select>
|
||||||
|
<span className="text-sm text-muted-foreground">条/页</span>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
</Card>
|
</Card>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,143 @@
|
|||||||
|
/**
|
||||||
|
* filekorolheader: 角色管理API接口 - 角色数据查询接口服务
|
||||||
|
* 功能:API请求封装、数据转换、错误处理、分页查询
|
||||||
|
* 路径:/central-config/user/role/components/roleApi
|
||||||
|
* 规范:遵循crop-x/docs/开发项目规范.md,使用SDK API调用,TypeScript类型安全
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { getAuthToken } from "@/utils/token";
|
||||||
|
import {
|
||||||
|
getRolesApiV1UsersPermissionsRolesGet,
|
||||||
|
} from "@/lib/api/sdk.gen";
|
||||||
|
import {
|
||||||
|
RoleApiData,
|
||||||
|
Role,
|
||||||
|
} from '../types';
|
||||||
|
|
||||||
|
// 本地定义PaginationState以避免导入问题
|
||||||
|
export interface PaginationState {
|
||||||
|
page: number;
|
||||||
|
size: number;
|
||||||
|
total: number;
|
||||||
|
totalPages: number;
|
||||||
|
hasNext: boolean;
|
||||||
|
hasPrev: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Re-export types from types.ts for convenience
|
||||||
|
export type { RolesApiResponse, RolesQueryParams } from '../types';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取角色列表数据
|
||||||
|
*/
|
||||||
|
export async function fetchRoles(params: RolesQueryParams = {}): Promise<RolesApiResponse> {
|
||||||
|
try {
|
||||||
|
// 构建查询参数对象
|
||||||
|
const queryParams: any = {};
|
||||||
|
|
||||||
|
if (params.page) queryParams.page = params.page;
|
||||||
|
if (params.size) queryParams.size = params.size;
|
||||||
|
if (params.sort_order) queryParams.sort_order = params.sort_order;
|
||||||
|
|
||||||
|
// 默认参数
|
||||||
|
if (!params.page) queryParams.page = 1;
|
||||||
|
if (!params.size) queryParams.size = 10;
|
||||||
|
if (!params.sort_order) queryParams.sort_order = 'desc';
|
||||||
|
|
||||||
|
// 获取认证token
|
||||||
|
const token = getAuthToken();
|
||||||
|
console.log('角色管理API调用参数:', queryParams);
|
||||||
|
|
||||||
|
// 使用真正的SDK API调用
|
||||||
|
const response = await getRolesApiV1UsersPermissionsRolesGet({
|
||||||
|
query: {
|
||||||
|
...queryParams,
|
||||||
|
// 添加时间戳防止缓存
|
||||||
|
_t: Date.now(),
|
||||||
|
},
|
||||||
|
headers: token ? {
|
||||||
|
'Authorization': `Bearer ${token}`,
|
||||||
|
} : undefined,
|
||||||
|
});
|
||||||
|
|
||||||
|
if (response.error) {
|
||||||
|
throw new Error(`API error: ${response.error.message || 'Unknown error'}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
const data = response.data as any;
|
||||||
|
console.log('角色管理API响应:', data);
|
||||||
|
|
||||||
|
// 处理API响应数据
|
||||||
|
if (data && typeof data === 'object' && data.data) {
|
||||||
|
return {
|
||||||
|
data: data.data || [],
|
||||||
|
total: data.total || 0,
|
||||||
|
page: data.page || 1,
|
||||||
|
size: data.size || 10,
|
||||||
|
total_pages: data.total_pages || 0,
|
||||||
|
has_next: data.has_next || false,
|
||||||
|
has_prev: data.has_prev || false,
|
||||||
|
};
|
||||||
|
} else {
|
||||||
|
// 其他情况,返回空结果
|
||||||
|
return {
|
||||||
|
data: [],
|
||||||
|
total: 0,
|
||||||
|
page: 1,
|
||||||
|
size: 10,
|
||||||
|
total_pages: 0,
|
||||||
|
has_next: false,
|
||||||
|
has_prev: false,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Failed to fetch roles:', error);
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 将API数据转换为页面所需的角色数据格式
|
||||||
|
* 为了保持兼容性,这里转换数据格式
|
||||||
|
*/
|
||||||
|
export function transformRoleData(apiRole: RoleApiData): Role {
|
||||||
|
return {
|
||||||
|
id: apiRole.id,
|
||||||
|
name: apiRole.name,
|
||||||
|
code: apiRole.name.toLowerCase().replace(/\s+/g, '_'), // 生成code
|
||||||
|
description: apiRole.description,
|
||||||
|
type: 'custom', // 默认为自定义角色
|
||||||
|
menuIds: [],
|
||||||
|
permissionIds: [],
|
||||||
|
status: 'active',
|
||||||
|
createdAt: apiRole.created_at,
|
||||||
|
updatedAt: apiRole.updated_at,
|
||||||
|
tenant_id: apiRole.tenant_id,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 批量转换角色数据
|
||||||
|
*/
|
||||||
|
export function transformRolesList(apiRoles: RoleApiData[]): Role[] {
|
||||||
|
return apiRoles.map(transformRoleData);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 格式化日期
|
||||||
|
*/
|
||||||
|
export function formatDate(dateString: string): string {
|
||||||
|
try {
|
||||||
|
const date = new Date(dateString);
|
||||||
|
return date.toLocaleString('zh-CN', {
|
||||||
|
year: 'numeric',
|
||||||
|
month: '2-digit',
|
||||||
|
day: '2-digit',
|
||||||
|
hour: '2-digit',
|
||||||
|
minute: '2-digit',
|
||||||
|
}).replace(/\//g, '-');
|
||||||
|
} catch (error) {
|
||||||
|
return dateString;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@@ -1,3 +1,10 @@
|
|||||||
|
/**
|
||||||
|
* filekorolheader: 角色管理页面 - 系统角色访问控制管理
|
||||||
|
* 功能:角色列表管理、API数据加载、分页查询、角色搜索、详情查看
|
||||||
|
* 路径:/central-config/user/role
|
||||||
|
* 规范:遵循crop-x/docs/开发项目规范.md,使用API调用,shadcn语义化样式,支持翻页
|
||||||
|
*/
|
||||||
|
|
||||||
'use client';
|
'use client';
|
||||||
|
|
||||||
import { useState, useEffect } from 'react';
|
import { useState, useEffect } from 'react';
|
||||||
@@ -10,11 +17,29 @@ import { RoleList } from './components/RoleList';
|
|||||||
import { RoleFormDialog } from './components/RoleFormDialog';
|
import { RoleFormDialog } from './components/RoleFormDialog';
|
||||||
import { RoleDetailDialog } from './components/RoleDetailDialog';
|
import { RoleDetailDialog } from './components/RoleDetailDialog';
|
||||||
import { RoleManagementInstructions } from './components/RoleManagementInstructions';
|
import { RoleManagementInstructions } from './components/RoleManagementInstructions';
|
||||||
import { Role, RoleFormData } from './types';
|
import { Role, RoleFormData, RoleFilters } from './types';
|
||||||
|
import {
|
||||||
|
fetchRoles,
|
||||||
|
transformRolesList,
|
||||||
|
RolesApiResponse,
|
||||||
|
RolesQueryParams,
|
||||||
|
PaginationState
|
||||||
|
} from './components/roleApi';
|
||||||
|
|
||||||
export default function RoleManagementPage() {
|
export default function RoleManagementPage() {
|
||||||
const [roles, setRoles] = useState<Role[]>([]);
|
const [roles, setRoles] = useState<Role[]>([]);
|
||||||
const [searchKeyword, setSearchKeyword] = useState('');
|
const [loading, setLoading] = useState(false);
|
||||||
|
const [pagination, setPagination] = useState<PaginationState>({
|
||||||
|
page: 1,
|
||||||
|
size: 10,
|
||||||
|
total: 0,
|
||||||
|
totalPages: 0,
|
||||||
|
hasNext: false,
|
||||||
|
hasPrev: false,
|
||||||
|
});
|
||||||
|
const [filters, setFilters] = useState<RoleFilters>({
|
||||||
|
searchKeyword: ''
|
||||||
|
});
|
||||||
const [showForm, setShowForm] = useState(false);
|
const [showForm, setShowForm] = useState(false);
|
||||||
const [showDetailDialog, setShowDetailDialog] = useState(false);
|
const [showDetailDialog, setShowDetailDialog] = useState(false);
|
||||||
const [editingRole, setEditingRole] = useState<Role | null>(null);
|
const [editingRole, setEditingRole] = useState<Role | null>(null);
|
||||||
@@ -28,75 +53,67 @@ export default function RoleManagementPage() {
|
|||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
loadRoles();
|
loadRoles();
|
||||||
}, []);
|
}, [pagination.page, pagination.size, filters.searchKeyword]);
|
||||||
|
|
||||||
const loadRoles = () => {
|
const loadRoles = async () => {
|
||||||
const data = localStorage.getItem('smart_agriculture_roles');
|
setLoading(true);
|
||||||
if (data) {
|
try {
|
||||||
setRoles(JSON.parse(data));
|
const queryParams: RolesQueryParams = {
|
||||||
} else {
|
page: pagination.page,
|
||||||
const mockRoles: Role[] = [
|
size: pagination.size,
|
||||||
{
|
sort_order: 'desc'
|
||||||
id: 'role-1',
|
};
|
||||||
name: '超级管理员',
|
|
||||||
code: 'super_admin',
|
const response = await fetchRoles(queryParams);
|
||||||
description: '系统最高权限,可管理所有功能和数据',
|
|
||||||
type: 'system',
|
// 转换数据格式
|
||||||
menuIds: ['*'],
|
const transformedRoles = transformRolesList(response.data);
|
||||||
permissionIds: ['*'],
|
|
||||||
status: 'active',
|
setRoles(transformedRoles);
|
||||||
createdAt: '2024-01-01T00:00:00',
|
setPagination({
|
||||||
updatedAt: '2024-01-01T00:00:00',
|
page: response.page,
|
||||||
},
|
size: response.size,
|
||||||
{
|
total: response.total,
|
||||||
id: 'role-2',
|
totalPages: response.total_pages,
|
||||||
name: '企业管理员',
|
hasNext: response.has_next,
|
||||||
code: 'enterprise_admin',
|
hasPrev: response.has_prev,
|
||||||
description: '企业管理员,可管理本企业的员工和数据',
|
});
|
||||||
type: 'system',
|
} catch (error) {
|
||||||
menuIds: ['config-user', 'machinery', 'field'],
|
console.error('Failed to load roles:', error);
|
||||||
permissionIds: ['user:view', 'user:add', 'user:edit', 'machinery:*'],
|
toast.error('加载角色数据失败');
|
||||||
defaultHomePage: '/machinery/archive/entry',
|
|
||||||
status: 'active',
|
// 如果API失败,使用localStorage中的数据作为fallback
|
||||||
createdAt: '2024-01-01T00:00:00',
|
const data = localStorage.getItem('smart_agriculture_roles');
|
||||||
updatedAt: '2024-01-01T00:00:00',
|
if (data) {
|
||||||
},
|
setRoles(JSON.parse(data));
|
||||||
{
|
}
|
||||||
id: 'role-3',
|
} finally {
|
||||||
name: '操作员',
|
setLoading(false);
|
||||||
code: 'operator',
|
|
||||||
description: '一般操作员,可查看和操作农机设备',
|
|
||||||
type: 'system',
|
|
||||||
menuIds: ['machinery', 'field'],
|
|
||||||
permissionIds: ['machinery:view', 'machinery:control', 'field:view'],
|
|
||||||
defaultHomePage: '/machinery/monitoring/location',
|
|
||||||
status: 'active',
|
|
||||||
createdAt: '2024-01-01T00:00:00',
|
|
||||||
updatedAt: '2024-01-01T00:00:00',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 'role-4',
|
|
||||||
name: '维修员',
|
|
||||||
code: 'maintenance',
|
|
||||||
description: '农机维修人员,负责设备维护和故障诊断',
|
|
||||||
type: 'custom',
|
|
||||||
menuIds: ['machinery-fault', 'machinery-archive'],
|
|
||||||
permissionIds: ['machinery:view', 'fault:view', 'fault:handle'],
|
|
||||||
status: 'active',
|
|
||||||
createdAt: '2024-10-01T00:00:00',
|
|
||||||
updatedAt: '2024-10-01T00:00:00',
|
|
||||||
},
|
|
||||||
];
|
|
||||||
localStorage.setItem('smart_agriculture_roles', JSON.stringify(mockRoles));
|
|
||||||
setRoles(mockRoles);
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// 搜索处理函数
|
||||||
|
const handleSearch = (searchKeyword: string) => {
|
||||||
|
setFilters(prev => ({ ...prev, searchKeyword }));
|
||||||
|
// 重置到第一页
|
||||||
|
setPagination(prev => ({ ...prev, page: 1 }));
|
||||||
|
};
|
||||||
|
|
||||||
|
// 分页处理函数
|
||||||
|
const handlePageChange = (page: number) => {
|
||||||
|
setPagination(prev => ({ ...prev, page }));
|
||||||
|
};
|
||||||
|
|
||||||
|
const handlePageSizeChange = (size: number) => {
|
||||||
|
setPagination(prev => ({ ...prev, size, page: 1 }));
|
||||||
|
};
|
||||||
|
|
||||||
|
// 过滤角色数据(客户端过滤,用于搜索)
|
||||||
const filteredRoles = roles.filter(role => {
|
const filteredRoles = roles.filter(role => {
|
||||||
const matchKeyword = !searchKeyword ||
|
const matchKeyword = !filters.searchKeyword ||
|
||||||
role.name.includes(searchKeyword) ||
|
role.name.includes(filters.searchKeyword) ||
|
||||||
role.code.includes(searchKeyword) ||
|
role.code.includes(filters.searchKeyword) ||
|
||||||
(role.description && role.description.includes(searchKeyword));
|
(role.description && role.description.includes(filters.searchKeyword));
|
||||||
return matchKeyword;
|
return matchKeyword;
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -183,13 +200,17 @@ export default function RoleManagementPage() {
|
|||||||
|
|
||||||
{/* 搜索 */}
|
{/* 搜索 */}
|
||||||
<RoleSearch
|
<RoleSearch
|
||||||
searchKeyword={searchKeyword}
|
searchKeyword={filters.searchKeyword}
|
||||||
onSearchChange={setSearchKeyword}
|
onSearchChange={handleSearch}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
{/* 角色列表 */}
|
{/* 角色列表 */}
|
||||||
<RoleList
|
<RoleList
|
||||||
roles={filteredRoles}
|
roles={filteredRoles}
|
||||||
|
loading={loading}
|
||||||
|
pagination={pagination}
|
||||||
|
onPageChange={handlePageChange}
|
||||||
|
onPageSizeChange={handlePageSizeChange}
|
||||||
onViewDetail={handleViewDetail}
|
onViewDetail={handleViewDetail}
|
||||||
onEdit={handleEdit}
|
onEdit={handleEdit}
|
||||||
onDelete={handleDelete}
|
onDelete={handleDelete}
|
||||||
|
|||||||
@@ -1,5 +1,16 @@
|
|||||||
// 角色管理相关类型定义
|
// 角色管理相关类型定义
|
||||||
|
|
||||||
|
// API返回的角色数据类型
|
||||||
|
export interface RoleApiData {
|
||||||
|
id: string;
|
||||||
|
name: string;
|
||||||
|
description?: string;
|
||||||
|
tenant_id: string;
|
||||||
|
created_at: string;
|
||||||
|
updated_at: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 页面使用的角色数据类型(兼容原有功能)
|
||||||
export interface Role {
|
export interface Role {
|
||||||
id: string;
|
id: string;
|
||||||
name: string;
|
name: string;
|
||||||
@@ -12,6 +23,7 @@ export interface Role {
|
|||||||
status: 'active' | 'inactive';
|
status: 'active' | 'inactive';
|
||||||
createdAt: string;
|
createdAt: string;
|
||||||
updatedAt: string;
|
updatedAt: string;
|
||||||
|
tenant_id?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export type RoleType = 'system' | 'custom';
|
export type RoleType = 'system' | 'custom';
|
||||||
@@ -345,6 +357,39 @@ export interface RoleManagementStats {
|
|||||||
bg: string;
|
bg: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// API响应接口
|
||||||
|
export interface RolesApiResponse {
|
||||||
|
data: RoleApiData[];
|
||||||
|
total: number;
|
||||||
|
page: number;
|
||||||
|
size: number;
|
||||||
|
total_pages: number;
|
||||||
|
has_next: boolean;
|
||||||
|
has_prev: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 查询参数接口
|
||||||
|
export interface RolesQueryParams {
|
||||||
|
page?: number;
|
||||||
|
size?: number;
|
||||||
|
sort_order?: 'asc' | 'desc';
|
||||||
|
}
|
||||||
|
|
||||||
|
// 翻页状态
|
||||||
|
export interface PaginationState {
|
||||||
|
page: number;
|
||||||
|
size: number;
|
||||||
|
total: number;
|
||||||
|
totalPages: number;
|
||||||
|
hasNext: boolean;
|
||||||
|
hasPrev: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 筛选条件
|
||||||
|
export interface RoleFilters {
|
||||||
|
searchKeyword: string;
|
||||||
|
}
|
||||||
|
|
||||||
// 表单数据
|
// 表单数据
|
||||||
export interface RoleFormData {
|
export interface RoleFormData {
|
||||||
name?: string;
|
name?: string;
|
||||||
|
|||||||
@@ -1,127 +0,0 @@
|
|||||||
# API SDK 使用指南
|
|
||||||
|
|
||||||
## 🚀 概述
|
|
||||||
|
|
||||||
本项目提供了两种API调用方式:
|
|
||||||
|
|
||||||
1. **原始SDK** (`sdk.gen.ts`) - 直接API调用,无认证
|
|
||||||
2. **认证SDK** (`authenticated-sdk.ts`) - 自动添加Token的API调用
|
|
||||||
|
|
||||||
## 📝 使用方法
|
|
||||||
|
|
||||||
### 1. 无认证的API调用(如登录、获取验证码)
|
|
||||||
|
|
||||||
```typescript
|
|
||||||
import { api } from '@/lib/api/authenticated-sdk';
|
|
||||||
|
|
||||||
// 获取验证码 - 不需要token
|
|
||||||
const captchaResponse = await api.getCaptcha();
|
|
||||||
|
|
||||||
// 用户登录 - 不需要token
|
|
||||||
const loginResponse = await api.login({
|
|
||||||
body: {
|
|
||||||
identifier: 'admin',
|
|
||||||
password: 'password123',
|
|
||||||
captcha_id: 'captcha-id',
|
|
||||||
captcha_text: 'ABCD'
|
|
||||||
}
|
|
||||||
});
|
|
||||||
```
|
|
||||||
|
|
||||||
### 2. 需要认证的API调用
|
|
||||||
|
|
||||||
```typescript
|
|
||||||
import { authenticatedSdk } from '@/lib/api/authenticated-sdk';
|
|
||||||
|
|
||||||
// 获取当前用户信息 - 自动添加token
|
|
||||||
const userResponse = await authenticatedSdk.getCurrentUser();
|
|
||||||
|
|
||||||
// 获取用户列表 - 自动添加token
|
|
||||||
const usersResponse = await authenticatedSdk.getUsers({
|
|
||||||
query: { page: 1, size: 10 }
|
|
||||||
});
|
|
||||||
|
|
||||||
// 创建用户 - 自动添加token
|
|
||||||
const createResponse = await authenticatedSdk.createUser({
|
|
||||||
body: {
|
|
||||||
username: 'newuser',
|
|
||||||
email: 'user@example.com'
|
|
||||||
}
|
|
||||||
});
|
|
||||||
```
|
|
||||||
|
|
||||||
## 🔧 核心特性
|
|
||||||
|
|
||||||
### 自动Token管理
|
|
||||||
- 自动从localStorage获取token
|
|
||||||
- 自动添加到请求头 `Authorization: Bearer {token}`
|
|
||||||
- 请求日志记录
|
|
||||||
|
|
||||||
### 401错误处理
|
|
||||||
- 检测401响应自动清除本地认证信息
|
|
||||||
- 自动跳转到登录页面
|
|
||||||
|
|
||||||
### 类型安全
|
|
||||||
- 完整的TypeScript类型支持
|
|
||||||
- 智能代码提示
|
|
||||||
|
|
||||||
## 📋 API方法列表
|
|
||||||
|
|
||||||
### 认证相关
|
|
||||||
- `api.getCaptcha()` - 获取验证码
|
|
||||||
- `api.login(options)` - 用户登录
|
|
||||||
- `api.logout(options)` - 用户登出
|
|
||||||
- `authenticatedSdk.getCurrentUser()` - 获取当前用户
|
|
||||||
- `authenticatedSdk.refreshToken()` - 刷新token
|
|
||||||
|
|
||||||
### 用户管理
|
|
||||||
- `authenticatedSdk.getUsers()` - 获取用户列表
|
|
||||||
- `authenticatedSdk.getUser(options)` - 获取单个用户
|
|
||||||
- `authenticatedSdk.createUser(options)` - 创建用户
|
|
||||||
- `authenticatedSdk.updateUser(options)` - 更新用户
|
|
||||||
- `authenticatedSdk.deleteUser(options)` - 删除用户
|
|
||||||
|
|
||||||
### 部门管理
|
|
||||||
- `authenticatedSdk.getDepartments()` - 获取部门列表
|
|
||||||
- `authenticatedSdk.getDepartment(options)` - 获取单个部门
|
|
||||||
- `authenticatedSdk.createDepartment(options)` - 创建部门
|
|
||||||
- `authenticatedSdk.updateDepartment(options)` - 更新部门
|
|
||||||
- `authenticatedSdk.deleteDepartment(options)` - 删除部门
|
|
||||||
|
|
||||||
### 租户管理
|
|
||||||
- `authenticatedSdk.getTenants()` - 获取租户列表
|
|
||||||
- `authenticatedSdk.getTenant(options)` - 获取单个租户
|
|
||||||
- `authenticatedSdk.getCurrentTenant()` - 获取当前租户信息
|
|
||||||
|
|
||||||
## ⚠️ 重要说明
|
|
||||||
|
|
||||||
1. **API生成覆盖**: `sdk.gen.ts` 和 `client.gen.ts` 会被 `npm run api:generate` 覆盖
|
|
||||||
2. **安全文件**: `authenticated-sdk.ts` 不会被覆盖,可安全使用
|
|
||||||
3. **Token存储**: Token存储在localStorage的user对象中
|
|
||||||
4. **代理配置**: 所有请求通过Next.js代理到真实API地址
|
|
||||||
|
|
||||||
## 🔄 迁移指南
|
|
||||||
|
|
||||||
### 从原始SDK迁移到认证SDK
|
|
||||||
|
|
||||||
```typescript
|
|
||||||
// ❌ 旧方式 - 可能缺少认证
|
|
||||||
import { getUsersApiV1UsersGet } from '@/lib/api/sdk.gen';
|
|
||||||
const response = await getUsersApiV1UsersGet();
|
|
||||||
|
|
||||||
// ✅ 新方式 - 自动添加认证
|
|
||||||
import { authenticatedSdk } from '@/lib/api/authenticated-sdk';
|
|
||||||
const response = await authenticatedSdk.getUsers();
|
|
||||||
```
|
|
||||||
|
|
||||||
### 混合使用场景
|
|
||||||
|
|
||||||
```typescript
|
|
||||||
import { api, authenticatedSdk } from '@/lib/api/authenticated-sdk';
|
|
||||||
|
|
||||||
// 登录 - 不需要认证
|
|
||||||
const loginResponse = await api.login({ body: loginData });
|
|
||||||
|
|
||||||
// 登录成功后,获取用户信息 - 需要认证
|
|
||||||
const userInfo = await authenticatedSdk.getCurrentUser();
|
|
||||||
```
|
|
||||||
@@ -1,108 +0,0 @@
|
|||||||
/**
|
|
||||||
* 认证API SDK包装器
|
|
||||||
* 为所有API调用自动添加Token,不会被API生成工具覆盖
|
|
||||||
*/
|
|
||||||
|
|
||||||
import { client } from './client.gen';
|
|
||||||
import * as api from './sdk.gen';
|
|
||||||
import type { Options } from './sdk.gen';
|
|
||||||
|
|
||||||
// 获取存储的token
|
|
||||||
const getAuthToken = (): string | null => {
|
|
||||||
try {
|
|
||||||
const storedUser = localStorage.getItem('user');
|
|
||||||
const user = storedUser ? JSON.parse(storedUser) : null;
|
|
||||||
return user?.token || null;
|
|
||||||
} catch (error) {
|
|
||||||
console.error('获取token失败:', error);
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
// 创建带认证的客户端选项
|
|
||||||
const createAuthenticatedOptions = <TData = any, ThrowOnError extends boolean = false>(
|
|
||||||
originalOptions: Options<TData, ThrowOnError> = {} as Options<TData, ThrowOnError>
|
|
||||||
): Options<TData, ThrowOnError> => {
|
|
||||||
const token = getAuthToken();
|
|
||||||
|
|
||||||
const authenticatedOptions: Options<TData, ThrowOnError> = {
|
|
||||||
...originalOptions,
|
|
||||||
client: client
|
|
||||||
};
|
|
||||||
|
|
||||||
// 如果有token,添加到请求头
|
|
||||||
if (token) {
|
|
||||||
authenticatedOptions.headers = {
|
|
||||||
...originalOptions.headers,
|
|
||||||
Authorization: `Bearer ${token}`
|
|
||||||
};
|
|
||||||
console.log('🔑 为API请求添加Token:', (originalOptions as any).url || 'unknown endpoint');
|
|
||||||
} else {
|
|
||||||
console.log('🚫 无Token,API请求可能未授权:', (originalOptions as any).url || 'unknown endpoint');
|
|
||||||
}
|
|
||||||
|
|
||||||
return authenticatedOptions;
|
|
||||||
};
|
|
||||||
|
|
||||||
// 包装所有API方法,自动添加认证
|
|
||||||
export const authenticatedSdk = {
|
|
||||||
// 认证相关
|
|
||||||
getCaptcha: (options?: Options) => api.getCaptchaApiV1AuthCaptchaGet(options),
|
|
||||||
login: (options: Options) => api.loginApiV1AuthLoginPost(options),
|
|
||||||
logout: (options?: Options) => api.logoutApiV1AuthLogoutPost(createAuthenticatedOptions(options)),
|
|
||||||
getCurrentUser: (options?: Options) => api.getCurrentUserInfoApiV1AuthMeGet(createAuthenticatedOptions(options)),
|
|
||||||
refreshToken: (options?: Options) => api.refreshTokenApiV1AuthRefreshPost(createAuthenticatedOptions(options)),
|
|
||||||
|
|
||||||
// 用户管理
|
|
||||||
getUsers: (options?: Options) => api.getUsersApiV1UsersGet(createAuthenticatedOptions(options)),
|
|
||||||
getUser: (options: Options) => api.getUserApiV1UsersUserIdGet(createAuthenticatedOptions(options)),
|
|
||||||
createUser: (options: Options) => api.createUserApiV1UsersPost(createAuthenticatedOptions(options)),
|
|
||||||
updateUser: (options: Options) => api.updateUserApiV1UsersUserIdPut(createAuthenticatedOptions(options)),
|
|
||||||
deleteUser: (options: Options) => api.deleteUserApiV1UsersUserIdDelete(createAuthenticatedOptions(options)),
|
|
||||||
|
|
||||||
// 系统用户管理
|
|
||||||
getSystemUsers: (options?: Options) => api.listSystemUsersApiV1UsersSystemUsersGet(createAuthenticatedOptions(options)),
|
|
||||||
getSystemUser: (options: Options) => api.getSystemUserApiV1UsersSystemUsersUserIdGet(createAuthenticatedOptions(options)),
|
|
||||||
createSystemUser: (options: Options) => api.createSystemUserApiV1UsersSystemUsersPost(createAuthenticatedOptions(options)),
|
|
||||||
updateSystemUser: (options: Options) => api.updateSystemUserApiV1UsersSystemUsersUserIdPut(createAuthenticatedOptions(options)),
|
|
||||||
deleteSystemUser: (options: Options) => api.deleteSystemUserApiV1UsersSystemUsersUserIdDelete(createAuthenticatedOptions(options)),
|
|
||||||
|
|
||||||
// 部门管理
|
|
||||||
getDepartments: (options?: Options) => api.getDepartmentsApiV1DepartmentsDepartmentsGet(createAuthenticatedOptions(options)),
|
|
||||||
getDepartment: (options: Options) => api.getDepartmentApiV1DepartmentsDepartmentsDepartmentIdGet(createAuthenticatedOptions(options)),
|
|
||||||
createDepartment: (options: Options) => api.createDepartmentApiV1DepartmentsDepartmentsPost(createAuthenticatedOptions(options)),
|
|
||||||
updateDepartment: (options: Options) => api.updateDepartmentApiV1DepartmentsDepartmentsDepartmentIdPut(createAuthenticatedOptions(options)),
|
|
||||||
deleteDepartment: (options: Options) => api.deleteDepartmentApiV1DepartmentsDepartmentsDepartmentIdDelete(createAuthenticatedOptions(options)),
|
|
||||||
getDepartmentTree: (options?: Options) => api.getDepartmentTreeApiV1DepartmentsDepartmentsTreeGet(createAuthenticatedOptions(options)),
|
|
||||||
|
|
||||||
// 租户管理
|
|
||||||
getTenants: (options?: Options) => api.listTenantsApiV1TenantsGet(createAuthenticatedOptions(options)),
|
|
||||||
getTenant: (options: Options) => api.getTenantApiV1TenantsTenantIdGet(createAuthenticatedOptions(options)),
|
|
||||||
createTenant: (options: Options) => api.createTenantApiV1TenantsPost(createAuthenticatedOptions(options)),
|
|
||||||
updateTenant: (options: Options) => api.updateTenantApiV1TenantsTenantIdPut(createAuthenticatedOptions(options)),
|
|
||||||
deleteTenant: (options: Options) => api.deleteTenantApiV1TenantsTenantIdDelete(createAuthenticatedOptions(options)),
|
|
||||||
getCurrentTenant: (options?: Options) => api.getCurrentTenantApiV1TenantsMeGet(createAuthenticatedOptions(options)),
|
|
||||||
|
|
||||||
// 消息管理
|
|
||||||
sendMessage: (options: Options) => api.sendMessageApiV1MessagesSendPost(createAuthenticatedOptions(options)),
|
|
||||||
scheduleMessage: (options: Options) => api.scheduleMessageApiV1MessagesSchedulePost(createAuthenticatedOptions(options)),
|
|
||||||
getMessageLogs: (options?: Options) => api.listMessageLogsApiV1MessagesLogsMessageLogsGet(createAuthenticatedOptions(options)),
|
|
||||||
getMessageReceipts: (options: Options) => api.getMessageReceiptsApiV1MessagesLogsMessageLogsMessageIdReceiptsGet(createAuthenticatedOptions(options)),
|
|
||||||
|
|
||||||
// 系统信息
|
|
||||||
getSystemInfo: (options?: Options) => api.getSystemInfoApiV1SystemInfoGet(createAuthenticatedOptions(options)),
|
|
||||||
getSystemStats: (options?: Options) => api.getSystemStatsApiV1SystemStatsGet(createAuthenticatedOptions(options)),
|
|
||||||
getSystemMetrics: (options?: Options) => api.getSystemMetricsApiV1SystemMetricsGet(createAuthenticatedOptions(options)),
|
|
||||||
healthCheck: (options?: Options) => api.healthCheckApiV1HealthGet(createAuthenticatedOptions(options)),
|
|
||||||
|
|
||||||
// 日志管理
|
|
||||||
getLoginLogs: (options?: Options) => api.getLoginLogsApiV1LogsLogsLoginGet(createAuthenticatedOptions(options)),
|
|
||||||
getOperationLogs: (options?: Options) => api.getOperationLogsApiV1LogsLogsOperationGet(createAuthenticatedOptions(options)),
|
|
||||||
getNetworkLogs: (options?: Options) => api.getNetworkLogsApiV1LogsLogsNetworkGet(createAuthenticatedOptions(options)),
|
|
||||||
};
|
|
||||||
|
|
||||||
// 导出原始API以供特殊情况使用(如登录)
|
|
||||||
export { api };
|
|
||||||
|
|
||||||
// 导出类型
|
|
||||||
export type { Options };
|
|
||||||
@@ -14,5 +14,6 @@ import type { ClientOptions as ClientOptions2 } from './types.gen';
|
|||||||
export type CreateClientConfig<T extends ClientOptions = ClientOptions2> = (override?: Config<ClientOptions & T>) => Config<Required<ClientOptions> & T>;
|
export type CreateClientConfig<T extends ClientOptions = ClientOptions2> = (override?: Config<ClientOptions & T>) => Config<Required<ClientOptions> & T>;
|
||||||
|
|
||||||
export const client = createClient(createConfig<ClientOptions2>({
|
export const client = createClient(createConfig<ClientOptions2>({
|
||||||
baseUrl: '' // 使用相对路径,通过 Next.js 代理到真实 API
|
// 在浏览器环境中使用相对路径(通过Next.js代理),在服务器端使用完整URL
|
||||||
|
baseUrl: typeof window !== 'undefined' ? '' : 'https://gitea-admin-hm-smart-agri-app.dev.maimaiag.com'
|
||||||
}));
|
}));
|
||||||
|
|||||||
@@ -1,37 +0,0 @@
|
|||||||
/**
|
|
||||||
* filekorolheader: API配置文件 - API客户端配置和认证处理
|
|
||||||
* 功能:API基础配置、认证头部处理、错误处理
|
|
||||||
* 路径:/lib/api/config
|
|
||||||
* 规范:遵循crop-x/docs/开发项目规范.md,统一API调用配置
|
|
||||||
*/
|
|
||||||
|
|
||||||
import { getAuthToken } from '@/utils/token';
|
|
||||||
|
|
||||||
// API基础URL配置 - 开发环境直接使用真实API地址避免重定向问题
|
|
||||||
export const API_BASE_URL = 'https://gitea-admin-hm-smart-agri-app.dev.maimaiag.com';
|
|
||||||
|
|
||||||
// 获取认证头部
|
|
||||||
export const getAuthHeaders = () => {
|
|
||||||
const token = getAuthToken();
|
|
||||||
const headers: Record<string, string> = {
|
|
||||||
'Content-Type': 'application/json',
|
|
||||||
'Accept': 'application/json',
|
|
||||||
// 添加缓存控制头部
|
|
||||||
'Cache-Control': 'no-store, no-cache, must-revalidate',
|
|
||||||
'Pragma': 'no-cache',
|
|
||||||
'Expires': '0',
|
|
||||||
};
|
|
||||||
|
|
||||||
if (token) {
|
|
||||||
headers['Authorization'] = `Bearer ${token}`;
|
|
||||||
}
|
|
||||||
|
|
||||||
return headers;
|
|
||||||
};
|
|
||||||
|
|
||||||
// API请求配置
|
|
||||||
export const apiConfig = {
|
|
||||||
baseURL: API_BASE_URL,
|
|
||||||
timeout: 30000, // 30秒超时
|
|
||||||
headers: getAuthHeaders(),
|
|
||||||
};
|
|
||||||
File diff suppressed because one or more lines are too long
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user