Chever John f61e216c43 feat: multi-tenant CRM portal with tenant login, supplier/customer views, and graph visualization
- Login page: tenantId mandatory, ruby logo (木羽清風), brand rename to 木羽清風
- Tenant session: localStorage tenantId + X-Tenant-Id header on all requests
- CRM Relations page: "我的供应商" / "我的客户" tabs with Chinese relation type mapping
- CRM Graph page: Terminal CLI aesthetic force-directed supply chain visualization (admin only)
- Menu filtering: empty menuPaths hides all menus (security fix)
- Error handler: 403 no longer triggers logout (security fix)
- API layer: added CRM endpoints (upstream/downstream/relations/history/full graph)
- Route config: added /crm/relations and /crm/graph routes
- app.ts renamed to app.tsx for JSX support in layout config

Made-with: Cursor
2026-03-30 02:56:16 +00:00

182 lines
8.3 KiB
TypeScript

import { request } from '@umijs/max';
import type {
ApiResponse,
CurrentUser,
LoginParams,
LoginResult,
PaginatedData,
PaginationParams,
Product,
StockSummary,
StockGroup,
StockCheck,
StockAdjust,
User,
Role,
Menu,
OperationLog,
SystemConfig,
Relation,
RelationHistory,
} from '@/types/api';
// ── Auth ──────────────────────────────────────────
export const login = (data: LoginParams) =>
request<ApiResponse<LoginResult>>('/api/v1/auth/login', { method: 'POST', data });
export const logout = () =>
request<ApiResponse>('/api/v1/auth/logout', { method: 'POST' });
export const getUserInfo = () =>
request<ApiResponse<CurrentUser>>('/api/v1/auth/info', { method: 'GET' });
export const changePassword = (data: { oldPassword: string; newPassword: string }) =>
request<ApiResponse>('/api/v1/auth/password', { method: 'PUT', data });
// ── Users ─────────────────────────────────────────
export const listUsers = (params: PaginationParams & { username?: string; realName?: string }) =>
request<ApiResponse<PaginatedData<User>>>('/api/v1/system/users', { method: 'GET', params });
export const getUser = (id: string) =>
request<ApiResponse<User>>(`/api/v1/system/users/${id}`, { method: 'GET' });
export const createUser = (data: Partial<User> & { password?: string }) =>
request<ApiResponse>('/api/v1/system/users', { method: 'POST', data });
export const updateUser = (id: string, data: Partial<User>) =>
request<ApiResponse>(`/api/v1/system/users/${id}`, { method: 'PUT', data });
export const deleteUser = (id: string) =>
request<ApiResponse>(`/api/v1/system/users/${id}`, { method: 'DELETE' });
export const updateUserStatus = (id: string, status: number) =>
request<ApiResponse>(`/api/v1/system/users/${id}/status`, { method: 'PUT', data: { status } });
// ── Roles ─────────────────────────────────────────
export const listRoles = (params?: PaginationParams & { roleName?: string }) =>
request<ApiResponse<PaginatedData<Role>>>('/api/v1/system/roles', { method: 'GET', params });
export const createRole = (data: Partial<Role>) =>
request<ApiResponse>('/api/v1/system/roles', { method: 'POST', data });
export const updateRole = (id: string, data: Partial<Role>) =>
request<ApiResponse>(`/api/v1/system/roles/${id}`, { method: 'PUT', data });
export const deleteRole = (id: string) =>
request<ApiResponse>(`/api/v1/system/roles/${id}`, { method: 'DELETE' });
export const setRolePermissions = (id: string, menuIds: string[]) =>
request<ApiResponse>(`/api/v1/system/roles/${id}/permissions`, { method: 'PUT', data: { menuIds } });
// ── Menus ─────────────────────────────────────────
export const listMenus = () =>
request<ApiResponse<PaginatedData<Menu>>>('/api/v1/system/menus', { method: 'GET' });
export const createMenu = (data: Partial<Menu>) =>
request<ApiResponse>('/api/v1/system/menus', { method: 'POST', data });
export const updateMenu = (id: string, data: Partial<Menu>) =>
request<ApiResponse>(`/api/v1/system/menus/${id}`, { method: 'PUT', data });
export const deleteMenu = (id: string) =>
request<ApiResponse>(`/api/v1/system/menus/${id}`, { method: 'DELETE' });
// ── Logs ──────────────────────────────────────────
export const listLogs = (params: PaginationParams & { username?: string; module?: string; operation?: string }) =>
request<ApiResponse<PaginatedData<OperationLog>>>('/api/v1/system/logs', { method: 'GET', params });
// ── Configs ───────────────────────────────────────
export const listConfigs = () =>
request<ApiResponse<PaginatedData<SystemConfig>>>('/api/v1/system/configs', { method: 'GET' });
export const updateConfig = (key: string, value: string) =>
request<ApiResponse>(`/api/v1/system/configs/${key}`, { method: 'PUT', data: { configValue: value } });
// ── Products ──────────────────────────────────────
export const listProducts = (params: PaginationParams & { productName?: string; spec?: string; color?: string; location?: string }) =>
request<ApiResponse<PaginatedData<Product>>>('/api/v1/inventory/products', { method: 'GET', params });
export const getProduct = (id: string) =>
request<ApiResponse<Product>>(`/api/v1/inventory/products/${id}`, { method: 'GET' });
export const createProduct = (data: Partial<Product>) =>
request<ApiResponse>('/api/v1/inventory/products', { method: 'POST', data });
export const updateProduct = (id: string, data: Partial<Product>) =>
request<ApiResponse>(`/api/v1/inventory/products/${id}`, { method: 'PUT', data });
export const deleteProduct = (id: string) =>
request<ApiResponse>(`/api/v1/inventory/products/${id}`, { method: 'DELETE' });
export const exportProducts = () =>
request('/api/v1/inventory/products/export', { method: 'GET', responseType: 'blob' });
// ── Stocks ────────────────────────────────────────
export const getStockSummary = () =>
request<ApiResponse<StockSummary>>('/api/v1/inventory/stocks/summary', { method: 'GET' });
export const getStockByColor = () =>
request<ApiResponse<PaginatedData<StockGroup>>>('/api/v1/inventory/stocks/by-color', { method: 'GET' });
export const getStockByLocation = () =>
request<ApiResponse<PaginatedData<StockGroup>>>('/api/v1/inventory/stocks/by-location', { method: 'GET' });
// ── Stock Checks ──────────────────────────────────
export const listStockChecks = (params: PaginationParams) =>
request<ApiResponse<PaginatedData<StockCheck>>>('/api/v1/inventory/checks', { method: 'GET', params });
export const getStockCheck = (id: string) =>
request<ApiResponse<StockCheck>>(`/api/v1/inventory/checks/${id}`, { method: 'GET' });
export const createStockCheck = (data: Partial<StockCheck>) =>
request<ApiResponse>('/api/v1/inventory/checks', { method: 'POST', data });
export const confirmStockCheck = (id: string) =>
request<ApiResponse>(`/api/v1/inventory/checks/${id}/confirm`, { method: 'POST' });
// ── Stock Adjusts ─────────────────────────────────
export const listStockAdjusts = (params: PaginationParams) =>
request<ApiResponse<PaginatedData<StockAdjust>>>('/api/v1/inventory/adjusts', { method: 'GET', params });
export const getStockAdjust = (id: string) =>
request<ApiResponse<StockAdjust>>(`/api/v1/inventory/adjusts/${id}`, { method: 'GET' });
export const createStockAdjust = (data: Partial<StockAdjust>) =>
request<ApiResponse>('/api/v1/inventory/adjusts', { method: 'POST', data });
export const approveStockAdjust = (id: string, action: number) =>
request<ApiResponse>(`/api/v1/inventory/adjusts/${id}/approve`, { method: 'POST', data: { action } });
// ── CRM Relations ─────────────────────────────────
export const listUpstream = (depth = 1) =>
request<ApiResponse<PaginatedData<Relation>>>('/api/v1/crm/graph/upstream', { method: 'GET', params: { depth } });
export const listDownstream = (depth = 1) =>
request<ApiResponse<PaginatedData<Relation>>>('/api/v1/crm/graph/downstream', { method: 'GET', params: { depth } });
export const createRelation = (data: {
fromTenantId: string;
toTenantId: string;
relationType: string;
validFrom?: string;
validTo?: string;
}) =>
request<ApiResponse>('/api/v1/crm/relationships', { method: 'POST', data });
export const updateRelation = (id: string, data: { status: number; validFrom?: string; validTo?: string }) =>
request<ApiResponse>(`/api/v1/crm/relationships/${id}`, { method: 'PUT', data });
export const listRelationHistory = (params?: PaginationParams) =>
request<ApiResponse<PaginatedData<RelationHistory>>>('/api/v1/crm/relationships/history', { method: 'GET', params });