生产管理系统 nextjs标准化改造

This commit is contained in:
2025-11-11 19:50:37 +08:00
parent 260917dfb4
commit 9778a0f26a
42 changed files with 13892 additions and 17578 deletions

View File

@@ -234,7 +234,7 @@ export class NetworkLogService {
]
// 应用筛选器
let filteredLogs = mockLogs.filter(log => {
const filteredLogs = mockLogs.filter(log => {
if (params.filters?.searchKeyword) {
const keyword = params.filters.searchKeyword.toLowerCase()
if (!log.url.toLowerCase().includes(keyword) &&

View File

@@ -266,7 +266,7 @@ export default function MenuManagementPage() {
const expandAll = () => {
const getAllMenuIds = (menus: Menu[]): string[] => {
let ids: string[] = [];
const ids: string[] = [];
menus.forEach(menu => {
if (menu.children && menu.children.length > 0) {
ids.push(menu.id);

View File

@@ -96,7 +96,7 @@ export function PaginationComponent({
// 否则生成智能的页码显示范围
const half = Math.floor(maxVisiblePages / 2);
let start = Math.max(1, page - half);
let end = Math.min(totalPages, start + maxVisiblePages - 1);
const end = Math.min(totalPages, start + maxVisiblePages - 1);
// 调整开始位置,确保显示足够数量的页码
if (end - start < maxVisiblePages - 1) {

View File

@@ -1,289 +0,0 @@
/**
* 环境变量管理系统
*
* 这个模块提供统一的环境配置管理,支持多环境切换
* 根据构建时的 NODE_ENV 自动加载对应的环境配置文件
*/
// 环境类型定义
export type Environment = 'dev' | 'test' | 'uat' | 'prod';
// 环境配置接口
export interface EnvironmentConfig {
// 基础配置
NODE_ENV: string;
FRONTEND_BASE_URL: string;
BACKEND_BASE_URL: string;
API_VERSION: string;
// 功能配置
DEBUG: boolean;
USE_MOCK: boolean;
// 应用信息
APP_NAME: string;
ENV_DESCRIPTION: string;
// 高级配置
API_TIMEOUT?: number;
ENABLE_ERROR_LOGGING?: boolean;
ENABLE_PERFORMANCE_MONITORING?: boolean;
ENABLE_USER_BEHAVIOR_TRACKING?: boolean;
SENTRY_DSN?: string;
CDN_BASE_URL?: string;
// 生产环境特有
ENABLE_MAINTENANCE_MODE?: boolean;
ENABLE_NEW_FEATURES?: boolean;
ENABLE_BETA_FEATURES?: boolean;
ENABLE_ANALYTICS?: boolean;
CACHE_TTL?: number;
ENABLE_SERVICE_WORKER?: boolean;
}
// 环境配置映射
const ENV_CONFIGS: Record<Environment, EnvironmentConfig> = {
dev: {
NODE_ENV: 'development',
FRONTEND_BASE_URL: 'https://cavin-smart-crop-ui-app.dev.maimaiag.com',
BACKEND_BASE_URL: 'https://cavin-smart-crop-backend-app.dev.maimaiag.com',
API_VERSION: 'v1',
DEBUG: true,
USE_MOCK: false,
APP_NAME: '智慧农业生产管理系统',
ENV_DESCRIPTION: '开发环境',
API_TIMEOUT: 10000,
ENABLE_ERROR_LOGGING: true,
ENABLE_PERFORMANCE_MONITORING: true,
},
test: {
NODE_ENV: 'test',
FRONTEND_BASE_URL: 'https://cavin-smart-crop-ui-app.test.maimaiag.com',
BACKEND_BASE_URL: 'https://cavin-smart-crop-backend-app.dev.maimaiag.com', // 临时使用开发环境后端
API_VERSION: 'v1',
DEBUG: true,
USE_MOCK: false,
APP_NAME: '智慧农业生产管理系统',
ENV_DESCRIPTION: '测试环境',
API_TIMEOUT: 15000,
ENABLE_ERROR_LOGGING: true,
ENABLE_PERFORMANCE_MONITORING: true,
ENABLE_USER_BEHAVIOR_TRACKING: true,
},
uat: {
NODE_ENV: 'production',
FRONTEND_BASE_URL: 'https://cavin-smart-crop-ui-app.uat.maimaiag.com',
BACKEND_BASE_URL: 'https://cavin-smart-crop-backend-app.uat.maimaiag.com',
API_VERSION: 'v1',
DEBUG: false,
USE_MOCK: false,
APP_NAME: '智慧农业生产管理系统',
ENV_DESCRIPTION: 'UAT 环境',
API_TIMEOUT: 20000,
ENABLE_ERROR_LOGGING: true,
ENABLE_PERFORMANCE_MONITORING: true,
ENABLE_USER_BEHAVIOR_TRACKING: true,
SENTRY_DSN: 'https://your-sentry-dsn.uat@sentry.io/project-id',
},
prod: {
NODE_ENV: 'production',
FRONTEND_BASE_URL: 'https://cavin-smart-crop-ui-app.prod.maimaiag.com',
BACKEND_BASE_URL: 'https://cavin-smart-crop-backend-app.prod.maimaiag.com',
API_VERSION: 'v1',
DEBUG: false,
USE_MOCK: false,
APP_NAME: '智慧农业生产管理系统',
ENV_DESCRIPTION: '生产环境',
API_TIMEOUT: 30000,
ENABLE_ERROR_LOGGING: true,
ENABLE_PERFORMANCE_MONITORING: true,
ENABLE_USER_BEHAVIOR_TRACKING: true,
ENABLE_ANALYTICS: true,
SENTRY_DSN: 'https://your-sentry-dsn.prod@sentry.io/project-id',
CDN_BASE_URL: 'https://cdn.cavin-smart-crop.com',
ENABLE_MAINTENANCE_MODE: false,
ENABLE_NEW_FEATURES: true,
ENABLE_BETA_FEATURES: false,
CACHE_TTL: 3600,
ENABLE_SERVICE_WORKER: true,
},
};
/**
* 获取当前环境
*
* 优先级:
* 1. 环境变量 NEXT_PUBLIC_ENV
* 2. 环境变量 NODE_ENV
* 3. 默认 'dev' 环境
*/
export const getEnv = (): Environment => {
// 浏览器环境
if (typeof window !== 'undefined') {
const env = process.env.NEXT_PUBLIC_ENV as Environment;
if (env && Object.keys(ENV_CONFIGS).includes(env)) {
return env;
}
}
// 服务端环境
const nodeEnv = process.env.NODE_ENV;
if (nodeEnv === 'development') return 'dev';
if (nodeEnv === 'test') return 'test';
if (nodeEnv === 'production') {
// 生产环境下需要进一步区分 UAT 和 PROD
const env = process.env.NEXT_PUBLIC_ENV as Environment;
return (env === 'uat' || env === 'prod') ? env : 'prod';
}
return 'dev'; // 默认开发环境
};
/**
* 获取当前环境配置
*/
export const getEnvConfig = (): EnvironmentConfig => {
const currentEnv = getEnv();
return ENV_CONFIGS[currentEnv];
};
/**
* 获取当前环境名称
*/
export const getEnvironmentName = (): string => {
const config = getEnvConfig();
return config.ENV_DESCRIPTION;
};
/**
* 获取当前服务器域名(前端地址)
*/
export const getLocalhost = (): string => {
const config = getEnvConfig();
return config.FRONTEND_BASE_URL;
};
/**
* 获取后端 API 地址
*/
export const getBackendUrl = (): string => {
const config = getEnvConfig();
return config.BACKEND_BASE_URL;
};
/**
* 获取完整的 API 地址
*/
export const getApiUrl = (path?: string): string => {
const backendUrl = getBackendUrl();
const apiVersion = getEnvConfig().API_VERSION;
const apiPath = `/api/${apiVersion}${path || ''}`;
return `${backendUrl}${apiPath}`;
};
/**
* 是否为开发环境
*/
export const isDevelopment = (): boolean => {
return getEnv() === 'dev';
};
/**
* 是否为测试环境
*/
export const isTest = (): boolean => {
return getEnv() === 'test';
};
/**
* 是否为 UAT 环境
*/
export const isUAT = (): boolean => {
return getEnv() === 'uat';
};
/**
* 是否为生产环境
*/
export const isProduction = (): boolean => {
return getEnv() === 'prod';
};
/**
* 是否为非开发环境
*/
export const isNonDevelopment = (): boolean => {
return !isDevelopment();
};
/**
* 是否开启调试模式
*/
export const isDebugMode = (): boolean => {
return getEnvConfig().DEBUG;
};
/**
* 是否使用 Mock 数据
*/
export const shouldUseMock = (): boolean => {
return getEnvConfig().USE_MOCK;
};
/**
* 获取 API 超时时间(毫秒)
*/
export const getApiTimeout = (): number => {
return getEnvConfig().API_TIMEOUT || 10000;
};
/**
* 获取应用名称
*/
export const getAppName = (): string => {
return getEnvConfig().APP_NAME;
};
/**
* 环境信息调试输出
*/
export const debugEnvInfo = (): void => {
if (isDebugMode()) {
const config = getEnvConfig();
const env = getEnv();
console.group('🌍 环境信息');
console.log('当前环境:', env);
console.log('环境描述:', config.ENV_DESCRIPTION);
console.log('前端地址:', config.FRONTEND_BASE_URL);
console.log('后端地址:', config.BACKEND_BASE_URL);
console.log('调试模式:', config.DEBUG);
console.log('使用 Mock:', config.USE_MOCK);
console.log('NODE_ENV:', process.env.NODE_ENV);
console.log('NEXT_PUBLIC_ENV:', process.env.NEXT_PUBLIC_ENV);
console.groupEnd();
}
};
// 默认导出主要函数
export default {
getEnv,
getEnvConfig,
getEnvironmentName,
getLocalhost,
getBackendUrl,
getApiUrl,
isDevelopment,
isTest,
isUAT,
isProduction,
isNonDevelopment,
isDebugMode,
shouldUseMock,
getApiTimeout,
getAppName,
debugEnvInfo,
};

View File

@@ -16,7 +16,6 @@ export type {
Config,
CreateClientConfig,
Options,
OptionsLegacyParser,
RequestOptions,
RequestResult,
ResolvedRequestOptions,

View File

@@ -194,7 +194,7 @@ type BuildUrlFn = <
url: string;
},
>(
options: Pick<TData, 'url'> & Options<TData>,
options: TData & Options<TData>,
) => string;
export type Client = CoreClient<
@@ -238,31 +238,4 @@ export type Options<
RequestOptions<TResponse, TResponseStyle, ThrowOnError>,
'body' | 'path' | 'query' | 'url'
> &
Omit<TData, 'url'>;
export type OptionsLegacyParser<
TData = unknown,
ThrowOnError extends boolean = boolean,
TResponseStyle extends ResponseStyle = 'fields',
> = TData extends { body?: any }
? TData extends { headers?: any }
? OmitKeys<
RequestOptions<unknown, TResponseStyle, ThrowOnError>,
'body' | 'headers' | 'url'
> &
TData
: OmitKeys<
RequestOptions<unknown, TResponseStyle, ThrowOnError>,
'body' | 'url'
> &
TData &
Pick<RequestOptions<unknown, TResponseStyle, ThrowOnError>, 'headers'>
: TData extends { headers?: any }
? OmitKeys<
RequestOptions<unknown, TResponseStyle, ThrowOnError>,
'headers' | 'url'
> &
TData &
Pick<RequestOptions<unknown, TResponseStyle, ThrowOnError>, 'body'>
: OmitKeys<RequestOptions<unknown, TResponseStyle, ThrowOnError>, 'url'> &
TData;
([TData] extends [never] ? unknown : Omit<TData, 'url'>);

View File

@@ -22,6 +22,17 @@ export type Field =
*/
key?: string;
map?: string;
}
| {
/**
* Field name. This is the name we want the user to see and use.
*/
key: string;
/**
* Field mapped name. This is the name we want to use in the request.
* If `in` is omitted, `map` aliases `key` to the transport layer.
*/
map: Slot;
};
export interface Fields {
@@ -41,10 +52,14 @@ const extraPrefixes = Object.entries(extraPrefixesMap);
type KeyMap = Map<
string,
{
in: Slot;
map?: string;
}
| {
in: Slot;
map?: string;
}
| {
in?: never;
map: Slot;
}
>;
const buildKeyMap = (fields: FieldsConfig, map?: KeyMap): KeyMap => {
@@ -60,6 +75,10 @@ const buildKeyMap = (fields: FieldsConfig, map?: KeyMap): KeyMap => {
map: config.map,
});
}
} else if ('key' in config) {
map.set(config.key, {
map: config.map,
});
} else if (config.args) {
buildKeyMap(config.args, map);
}
@@ -111,7 +130,9 @@ export const buildClientParams = (
if (config.key) {
const field = map.get(config.key)!;
const name = field.map || config.key;
(params[field.in] as Record<string, unknown>)[name] = arg;
if (field.in) {
(params[field.in] as Record<string, unknown>)[name] = arg;
}
} else {
params.body = arg;
}
@@ -120,8 +141,12 @@ export const buildClientParams = (
const field = map.get(key);
if (field) {
const name = field.map || key;
(params[field.in] as Record<string, unknown>)[name] = value;
if (field.in) {
const name = field.map || key;
(params[field.in] as Record<string, unknown>)[name] = value;
} else {
params[field.map] = value;
}
} else {
const extra = extraPrefixes.find(([prefix]) =>
key.startsWith(prefix),
@@ -132,10 +157,8 @@ export const buildClientParams = (
(params[slot] as Record<string, unknown>)[
key.slice(prefix.length)
] = value;
} else {
for (const [slot, allowed] of Object.entries(
config.allowExtra ?? {},
)) {
} else if ('allowExtra' in config && config.allowExtra) {
for (const [slot, allowed] of Object.entries(config.allowExtra)) {
if (allowed) {
(params[slot as Slot] as Record<string, unknown>)[key] = value;
break;