Files
frontend/src/components/common/ResponsiveGrid.tsx
lan 3196972596
All checks were successful
Frontend CI / ota-android (push) Successful in 1m40s
Frontend CI / ota-ios (push) Successful in 1m39s
Frontend CI / build-and-push-web (push) Successful in 3m30s
Frontend CI / build-android-apk (push) Successful in 1h1m8s
refactor: restructure core architecture and responsive system
This commit implements a major architectural refactor to improve modularity, type safety, and maintainability across the codebase.

Key changes include:
- **Responsive System Refactor**: Migrated from a monolithic `useResponsive` hook to a set of specialized, granular hooks (`useBreakpoint`, `useOrientation`, `usePlatform`, etc.) located in `src/presentation/hooks/responsive`. This reduces unnecessary re-renders and improves developer experience.
- **Core Service Restructuring**: Introduced a new directory structure for services, including dedicated folders for `datasources`, `mappers`, and domain-specific types (`message`, `post`).
- **Data Layer Improvements**:
    - Centralized JSON parsing logic in `src/database/core/jsonUtils.ts`.
    - Cleaned up repository implementations by removing redundant local utility functions.
    - Refactored `MessageMapper` to use the new centralized JSON utility.
- **Type System Cleanup**:
    - Decomposed the large `src/types/dto.ts` into a modular `src/types/dto/` directory.
    - Simplified `src/types/index.ts` and introduced backward-compatible aliases for core entities.
- **Utility Consolidation**:
    - Created a centralized `src/utils/formatTime.ts` to replace fragmented date formatting logic across various screens and components.
    - Removed deprecated responsive utility files in favor of the new hook-based system.
- **Service Logic Refinement**: Refactored `ApiClient` and `WebSocketService` to use a centralized `showVerificationModal` service, removing duplicated state management for verification prompts.
2026-05-05 19:07:33 +08:00

169 lines
4.2 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/**
* 响应式网格布局组件
* 根据断点自动调整列数,支持间距配置
*/
import React, { useMemo } from 'react';
import {
View,
StyleProp,
ViewStyle,
StyleSheet,
Dimensions,
} from 'react-native';
import {
useResponsive,
useColumnCount,
useResponsiveSpacing,
FineBreakpointKey,
} from '../../hooks';
export interface ResponsiveGridProps {
/** 子元素 */
children: React.ReactNode[];
/** 自定义样式 */
style?: StyleProp<ViewStyle>;
/** 容器样式 */
containerStyle?: StyleProp<ViewStyle>;
/** 项目样式 */
itemStyle?: StyleProp<ViewStyle>;
/** 列数配置,根据断点设置 */
columns?: Partial<Record<FineBreakpointKey, number>>;
/** 间距配置 */
gap?: Partial<Record<FineBreakpointKey, number>>;
/** 行间距(默认等于 gap */
rowGap?: Partial<Record<FineBreakpointKey, number>>;
/** 列间距(默认等于 gap */
columnGap?: Partial<Record<FineBreakpointKey, number>>;
/** 是否启用等宽列 */
equalColumns?: boolean;
/** 自定义列宽计算函数 */
getColumnWidth?: (containerWidth: number, columns: number, gap: number) => number;
/** 渲染空状态 */
renderEmpty?: () => React.ReactNode;
/** key 提取函数 */
keyExtractor?: (item: React.ReactNode, index: number) => string;
}
/**
* 响应式网格布局组件
*
* 根据屏幕宽度自动调整列数,支持平板/桌面端的多列布局
*
* @example
* <ResponsiveGrid
* columns={{ xs: 1, sm: 2, md: 3, lg: 4 }}
* gap={{ xs: 8, md: 16 }}
* >
* {items.map(item => <Card key={item.id} {...item} />)}
* </ResponsiveGrid>
*/
export function ResponsiveGrid({
children,
style,
containerStyle,
itemStyle,
columns: columnsConfig,
gap: gapConfig,
rowGap: rowGapConfig,
columnGap: columnGapConfig,
equalColumns = true,
getColumnWidth,
renderEmpty,
keyExtractor,
}: ResponsiveGridProps) {
const { width } = useResponsive();
const columns = useColumnCount(columnsConfig);
const defaultGap = useResponsiveSpacing(gapConfig);
const rowGap = useResponsiveSpacing(rowGapConfig ?? gapConfig);
const columnGap = useResponsiveSpacing(columnGapConfig ?? gapConfig);
// 计算列宽
const columnWidth = useMemo(() => {
if (getColumnWidth) {
return getColumnWidth(width, columns, columnGap);
}
if (equalColumns) {
// 等宽列计算:(总宽度 - (列数 - 1) * 列间距) / 列数
return (width - (columns - 1) * columnGap) / columns;
}
return undefined;
}, [width, columns, columnGap, equalColumns, getColumnWidth]);
// 将子元素分组为行
const rows = useMemo(() => {
const items = React.Children.toArray(children);
if (items.length === 0) return [];
const result: React.ReactNode[][] = [];
for (let i = 0; i < items.length; i += columns) {
result.push(items.slice(i, i + columns));
}
return result;
}, [children, columns]);
// 空状态处理
if (rows.length === 0 && renderEmpty) {
return (
<View style={[styles.container, containerStyle]}>
{renderEmpty()}
</View>
);
}
return (
<View style={[styles.container, containerStyle]}>
{rows.map((row, rowIndex) => (
<View
key={`row-${rowIndex}`}
style={[
styles.row,
{
marginBottom: rowIndex < rows.length - 1 ? rowGap : 0,
},
style,
]}
>
{row.map((child, colIndex) => {
const index = rowIndex * columns + colIndex;
const key = keyExtractor?.(child, index) ?? `grid-item-${index}`;
return (
<View
key={key}
style={[
styles.item,
{
width: columnWidth,
marginRight: colIndex < row.length - 1 ? columnGap : 0,
},
itemStyle,
]}
>
{child}
</View>
);
})}
</View>
))}
</View>
);
}
const styles = StyleSheet.create({
container: {
width: '100%',
},
row: {
flexDirection: 'row',
alignItems: 'flex-start',
},
item: {
flexShrink: 0,
},
});
export default ResponsiveGrid;