2026-03-09 21:29:03 +08:00
|
|
|
|
/**
|
|
|
|
|
|
* 响应式网格布局组件
|
|
|
|
|
|
* 根据断点自动调整列数,支持间距配置
|
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
|
|
|
|
import React, { useMemo } from 'react';
|
|
|
|
|
|
import {
|
|
|
|
|
|
View,
|
|
|
|
|
|
StyleProp,
|
|
|
|
|
|
ViewStyle,
|
|
|
|
|
|
StyleSheet,
|
|
|
|
|
|
Dimensions,
|
|
|
|
|
|
} from 'react-native';
|
|
|
|
|
|
import {
|
|
|
|
|
|
useResponsive,
|
|
|
|
|
|
useColumnCount,
|
|
|
|
|
|
useResponsiveSpacing,
|
|
|
|
|
|
FineBreakpointKey,
|
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
|
|
|
|
} from '../../hooks';
|
2026-03-09 21:29:03 +08:00
|
|
|
|
|
|
|
|
|
|
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;
|