memowake-front/components/cascader.tsx

225 lines
7.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, { useEffect, useState } from 'react';
import { ScrollView, StyleProp, StyleSheet, TextStyle, TouchableOpacity, View, ViewStyle } from 'react-native';
import { ThemedText } from './ThemedText';
export type CascaderItem = {
name: string;
[key: string]: any; // 允许其他自定义属性
children?: CascaderItem[];
};
type CascaderProps = {
data: CascaderItem[]; // 级联数据
value?: CascaderItem[]; // 选中的值
onChange?: (value: CascaderItem[]) => void; // 选中项变化时的回调
displayRender?: (selectedItems: CascaderItem[]) => React.ReactNode; // 自定义显示内容
style?: StyleProp<ViewStyle>; // 容器样式
itemStyle?: StyleProp<ViewStyle>; // 选项样式
activeItemStyle?: StyleProp<ViewStyle>; // 选中项样式
textStyle?: StyleProp<TextStyle>; // 文字样式
activeTextStyle?: StyleProp<TextStyle>; // 选中文字样式
columnWidth?: number; // 列宽
showDivider?: boolean; // 是否显示分割线
dividerColor?: string; // 分割线颜色
showArrow?: boolean; // 是否显示箭头
};
const CascaderComponent: React.FC<CascaderProps> = ({
data,
value = [],
onChange,
displayRender,
style,
activeItemStyle,
textStyle,
activeTextStyle,
columnWidth = 120,
showDivider = true,
dividerColor = '#e0e0e0',
showArrow = false,
}) => {
const [selectedItems, setSelectedItems] = useState<CascaderItem[]>(value);
const [allLevelsData, setAllLevelsData] = useState<CascaderItem[][]>([]);
// 初始化数据
useEffect(() => {
setAllLevelsData([data]);
}, [data]);
// 处理选择
const handleSelect = (item: CascaderItem, level: number) => {
const newSelectedItems = [...selectedItems.slice(0, level), item];
setSelectedItems(newSelectedItems);
// 如果有子项,添加下一级数据
if (item.children?.length) {
setAllLevelsData(prev => {
const newLevels = [...prev.slice(0, level + 1)];
// 确保 children 存在且是数组
if (item.children && Array.isArray(item.children)) {
newLevels.push(item.children);
}
return newLevels;
});
} else {
setAllLevelsData(prev => prev.slice(0, level + 1));
}
// 触发onChange回调
onChange?.(newSelectedItems);
};
// 渲染某一级选项
const renderLevel = (items: CascaderItem[], level: number) => {
return (
<View style={[styles.levelContainer, { width: columnWidth }]}>
{items.map((item, index) => {
const isActive = selectedItems[level]?.name === item.name;
return (
<View key={`${level}-${index}`} style={[
styles.item,
isActive && [styles.activeItem, activeItemStyle]
]}>
<ScrollView
horizontal
showsHorizontalScrollIndicator={false}
contentContainerStyle={{ flexGrow: 1 }}
>
<TouchableOpacity
style={styles.itemContent}
onPress={() => handleSelect(item, level)}
>
<ThemedText
style={[
styles.text,
textStyle,
isActive && [styles.activeText, activeTextStyle]
]}
>
{item.name}
</ThemedText>
{showArrow && item.children?.length ? (
<ThemedText style={styles.arrow}></ThemedText>
) : null}
</TouchableOpacity>
</ScrollView>
</View>
);
})}
</View>
);
};
// 渲染所有级联列
const renderColumns = () => {
return allLevelsData.map((items, level) => (
<View
key={`column-${level}`}
style={[
styles.column,
{ width: columnWidth },
showDivider && level < allLevelsData.length - 1 && [
styles.columnWithDivider,
{ borderRightColor: dividerColor }
]
]}
>
<ScrollView
style={{ flex: 1 }}
showsVerticalScrollIndicator={true}
contentContainerStyle={{ paddingBottom: 20 }}
>
{renderLevel(items, level)}
</ScrollView>
</View>
));
};
// 自定义显示内容
const renderDisplay = () => {
if (displayRender) {
return displayRender(selectedItems);
}
return selectedItems.map(item => item.name).join(' / ');
};
return (
<View style={[styles.container, style]}>
<ScrollView
horizontal
showsHorizontalScrollIndicator={false}
contentContainerStyle={styles.scrollContent}
style={{ flex: 1 }}
>
{renderColumns()}
</ScrollView>
{displayRender && (
<View style={styles.displayContainer}>
{renderDisplay()}
</View>
)}
</View>
);
};
const styles = StyleSheet.create({
container: {
flex: 1,
backgroundColor: '#fff',
height: 300, // Set a fixed height for the container
},
scrollContent: {
flexGrow: 1,
height: '100%',
},
column: {
height: '100%',
maxHeight: '100%',
},
columnWithDivider: {
borderRightWidth: 1,
},
levelContainer: {
height: '100%',
maxHeight: '100%',
},
item: {
flexDirection: 'row',
justifyContent: 'space-between',
alignItems: 'center',
paddingVertical: 12,
paddingHorizontal: 16,
minWidth: '100%',
overflow: 'hidden',
},
text: {
fontSize: 15,
color: '#333',
flexShrink: 0, // 禁止收缩
paddingRight: 4,
},
itemContent: {
flexDirection: 'row',
alignItems: 'center',
paddingRight: 16, // 确保有足够的右边距
},
activeItem: {
backgroundColor: '#F6F6F6',
},
activeText: {
color: '#AC7E35',
fontWeight: '500',
},
arrow: {
fontSize: 18,
color: '#999',
marginLeft: 8,
},
displayContainer: {
padding: 12,
borderTopWidth: 1,
borderTopColor: '#f0f0f0',
},
});
export default CascaderComponent;