在项目中需要编写多个对话框,为了方便复用,自己编写了一个SuperModal组件,并实现了通过使用一个SuperModal组件切换不同对话框的功能,减少了重复的代码量。
组件代码:
import {
View, Text, TouchableOpacity, TouchableWithoutFeedback,
GestureResponderEvent, Modal, StyleSheet,
} from "react-native";
import * as React from "react";
const SuperModal = ({ isModalVisible, closeModal, handleConfirm=null, title, content, confirmText="确认", customButton = null, current= "Main", next=null, }:
{ isModalVisible:boolean, closeModal:() => void, handleConfirm:(() => boolean)|null, title:string, content:any, confirmText:string, customButton: {textLeft:string,textRight:string,onPressLeft:(event: GestureResponderEvent) => void,onPressRight:(event: GestureResponderEvent) => void }|null, current: string, next:any, }) => {
return <Modal
transparent={true}
visible={isModalVisible}
onRequestClose={closeModal}
>
<TouchableWithoutFeedback onPress={closeModal}><View style={styles.overlay} >
<TouchableWithoutFeedback onPress={()=>{}}><View style={styles.modalContainer}>
{ current==='Main'?
<>
<Text style={styles.modalTitle}>{text}</Text>
{content}
<View style={styles.buttonsContainer}>
{customButton ?
<>
<TouchableOpacity onPress={customButton.onPressLeft}
style={[styles.menuButton, { borderRightWidth: 0.5 }]}>
<Text style={{ textAlign: "center" }}>{customButton.textLeft}</Text>
</TouchableOpacity>
<TouchableOpacity onPress={customButton.onPressRight}
style={[styles.menuButton, { borderLeftWidth: 0.5 }]}>
<Text style={{ textAlign: "center" }}>{customButton.textRight}</Text>
</TouchableOpacity>
</>
:
<>
<TouchableOpacity onPress={closeModal} style={[styles.menuButton, { borderRightWidth: 0.5 }]}>
<Text style={{ textAlign: "center" }}>取消</Text>
</TouchableOpacity>
<TouchableOpacity onPress={() => {
handleConfirm ? handleConfirm() && closeModal() : closeModal()
}} style={[styles.menuButton, { borderLeftWidth: 0.5 }]}>
<Text style={{ textAlign: "center" }}>{confirmText}</Text>
</TouchableOpacity>
</>
}
</View>
</>
:
<>{next}</>
}
</View></TouchableWithoutFeedback>
</View></TouchableWithoutFeedback>
</Modal>
}
export default SuperModal;
const styles = StyleSheet.create({
modalTitle: {
textAlign: 'center',
fontSize: 16,
marginBottom: 10,
},
overlay: {
flex: 1,
justifyContent: 'center',
alignItems: 'center',
backgroundColor: 'rgba(0, 0, 0, 0.5)',
zIndex: 200,
},
modalContainer: {
width: '80%',
paddingTop: 20,
justifyContent: 'center',
alignItems: 'center',
backgroundColor: '#ECECEC',
borderRadius: 34,
elevation: 1,
},
inputContainer: {
height: 50,
lineHeight: 25,
paddingLeft: 15,
flexDirection: 'row',
alignItems: 'center',
width: '85%',
backgroundColor: '#F7F6F6',
borderRadius: 28,
overflow: 'hidden',
},
input_new: {
flexGrow: 1,
overflow: 'hidden',
},
buttonsContainer: {
width: "100%",
justifyContent: 'space-between',
flexDirection: 'row',
borderTopWidth: 0.5,
borderColor: 'rgba(0,0,0,0.3)'
},
menuButton: {
flex: 1,
padding: 20,
borderRadius: 5,
alignItems: "center",
borderColor: 'rgba(0,0,0,0.3)'
},
});
参数说明:
-
isModalVisible:boolean, 父组件中用于控制对话框是否可见(弹出/关闭)的布尔值; -
closeModal:() => void, 用于关闭对话框的函数,其中应包含将isModalVisible设为false的操作; -
handleConfirm:(() => boolean)|null, 【可选】用于处理确认键点击的函数,返回布尔值决定是否关闭对话框(默认为null,效果为点击确认直接调用closeModal,一般使用customButton时才将其设为null); -
title:string, 对话框的标题; -
content:any, 对话框的内容(JSX); -
confirmText:string, 【可选】右下角确认键的文字(默认为“确认”); -
customButton: {textLeft:string,textRight:string,onPressLeft:(event: GestureResponderEvent) => void,onPressRight:(event: GestureResponderEvent) => void
}|null, 【可选】自定义底部左右按钮的文字与功能(默认为“null”,即不自定义);
-
current: string, 【可选】用于管理对话框内多级页面,表示当前对话框所在页面(默认为“Main”,即默认页面); -
next:any,【可选】对话框内次级页面的内容(JSX)(默认为“null”,即无次级页面);
使用示例:
- 普通使用:
<SuperModal isModalVisible={isSizeModalVisible}
closeModal={() => {setIsSizeModalVisible(false);}}
handleConfirm={() => {
//TODO: 处理确认逻辑
return true
}}
title={'长宽比'}
content={<View style={{flexDirection: 'row',width: '90%', marginBottom: 10, alignItems: 'center', justifyContent: 'space-between'}}>
<TextInput style={styles.input}/>
<Text>×</Text>
<TextInput style={styles.input}/>
</View>}
confirmText={"确认修改"}
/>
- 多重复用:
type ModalType = 'None'|'EditPassword'|'EditNickname'|'EditPhone'|'ToggleEnterPrise'
const [modalVisible, setModalVisible] = useState<ModalType>('None')
const [currentModalScreen,setCurrentModalScreen]=useState('Main')
let modalTitle=useMemo(()=>{
switch (modalVisible){
case 'EditPassword': return '修改密码'
case 'EditNickname': return '修改昵称'
case 'EditPhone': return '修改手机'
case 'ToggleEnterPrise': return '切换企业'
default: return ''
}
},[modalVisible])
const modalItems= {
'None': {
content: [],
confirm: null,
},
'EditPassword': {
content: [
{
title: "旧密码",
value: passwordOld,
onChangeValue: onChangePasswordOld,
secure: true,
tip: <Image style={[styles.judge, { opacity: passwordOld ? 1 : 0 }]}
source={userSlice.password === passwordOld ? require("../../../../assets/correct.png") : require("../../../../assets/error.png")} />
}, {
title: "新密码",
value: passwordNew,
onChangeValue: onChangePasswordNew,
secure: true,
tip: <View style={[styles.judge]} />
}, {
title: "确认密码",
value: passwordConfirm,
onChangeValue: onChangePasswordConfirm,
secure: true,
tip: <Image style={[styles.judge, { opacity: passwordConfirm ? 1 : 0 }]}
source={passwordNew === passwordConfirm ? require("../../../../assets/correct.png") : require("../../../../assets/error.png")} />
},],
confirm: () => {
if (userSlice.password !== passwordOld) {
alert('密码错误!')
return false;
}
if (passwordNew !== passwordConfirm) {
alert('两次输入的密码不一致!!')
return false;
}
setUserSlice({ ...userSlice, password: passwordNew });
alert('密码修改成功!')
return true;
},
},
'EditNickname': {
content: [{
title: "新昵称",
value: nicknameNew,
onChangeValue: onChangeNicknameNew,
secure: false,
tip: null,
}],
confirm: () => {
if (nicknameNew === '') {
alert('昵称不能为空!');
return false;
}
setUserSlice({ ...userSlice, username: nicknameNew });
alert('昵称修改成功!')
return true;
},
},
'EditPhone': {
content: [{
title: "新手机",
value: phoneNew,
onChangeValue: onChangePhoneNew,
secure: false,
tip: null
},{
title: "验证码",
value: captchaCode,
onChangeValue: onChangeCaptchaCode,
secure: false,
tip: <TouchableOpacity
activeOpacity={0.5}
onPress={sendCaptchaCode}
disabled={isCounting}
style={{width: 80, borderRadius: 20, borderWidth:1, borderColor: '#A5A5A5', height: 45, padding: 5, justifyContent: 'center', alignItems: 'center', marginLeft: 10}}
><Text style={{color: 'grey', fontSize: 12, fontFamily: 'Source Han Sans CN', fontWeight: '400',}}>
{isCounting ? ` ${remainingTime} 秒` : '发送验证码'}
</Text></TouchableOpacity>,
},],
confirm: () => {
/*验证码验证*/
if (0/*验证码错误*/) {
alert('请输入正确的验证码!');
return false;
}
setUserSlice({ ...userSlice, phone: phoneNew });
alert('手机修改成功!')
return true;
},
},
'ToggleEnterPrise': {
content: [{enterpriseName: '个人'},{enterpriseName: 'A企业'},{enterpriseName: 'B企业'},{enterpriseName: 'C企业'},],
confirm: null,
customButton: {
textLeft: '申请加入',
onPressLeft: ()=>{setCurrentModalScreen("Next")},
textRight: '企业注册',
onPressRight: ()=>{closeModal();navigation.navigate('EnterpriseRegister')},
},
next: <View style={{padding: 20, justifyContent: 'space-between', marginTop: -20 }}>
<View style={{width: '100%', flexDirection:'row', alignItems: 'center', marginBottom: 20}}>
<TouchableOpacity onPress={()=>{setCurrentModalScreen("Main")}}>
<Image source={require('../../../../assets/back.png')} style={{width: 30, height: 30, marginRight: 10}}/>
</TouchableOpacity>
<View style={{flexGrow: 1, height: 40, backgroundColor: 'rgba(255,255,255,0.5)',borderRadius: 28, paddingHorizontal: 10,paddingVertical:0, flexDirection: 'row', alignItems: 'center' }}>
<TextInput placeholder={"请输入企业名称"}
value={searchText}
onChangeText={setSearchText}
onBlur={handleSearch}
style={{flexGrow: 1}}
/>
<TouchableOpacity onPress={handleSearch}>
<Image source={require('../../../../assets/search.png')} style={{width: 30, height: 30}}/>
</TouchableOpacity>
</View>
</View>
<View style={{height: 250}}>
<FlatList style={{ width: '100%', borderRadius: 28}}
fadingEdgeLength={50}
data={filteredData}
keyExtractor={(item,index) => index.toString()}
renderItem={({ item }) => (
<TouchableOpacity style={{alignSelf: 'center', width:"100%",flexDirection: 'row', justifyContent: 'space-evenly', backgroundColor: 'white', borderRadius: 28, marginVertical: 10, paddingVertical:10}}>
<Text>{item.name}</Text>
<Text>{item.ceo}</Text>
</TouchableOpacity>
)}
/>
</View>
</View>,
current: currentModalScreen
}
}
<SuperModal isModalVisible={modalVisible!=='None'}
title={modalTitle}
closeModal={closeModal}
handleConfirm={modalItems[modalVisible].confirm}
customButton={modalItems[modalVisible].customButton]}
current={modalItems[modalVisible].current}
next={modalItems[modalVisible].next}
content={<>
{modalVisible!='None' &&
(modalVisible!=='ToggleEnterPrise' ?
modalItems[modalVisible].content.map((item,index)=>{
return <View style={{flexDirection:'row', alignItems: 'center',marginBottom: 20, width: '85%'}} key={index}>
<View style={styles.inputContainer}>
<Text>{item.title}:</Text>
<TextInput
secureTextEntry={item.secure}
style={styles.input_new}
onChangeText={(text)=>item.onChangeValue(text)}
value={item.value}
/>
</View>
{item.tip}
</View>
})
:
<ScrollView style={{width: '100%', height: 180}} fadingEdgeLength={20}>
{modalItems[modalVisible].content.map((item,index)=>{
return <TouchableOpacity style={{width: '60%', alignSelf: 'center', paddingVertical:10, marginVertical: 10, backgroundColor: 'white', borderRadius: 28, overflow: 'hidden'}}
onPress={()=>{
setEnterpriseName(item.enterpriseName);
/*切换企业*/
closeModal()
}}>
<Text style={{textAlign: 'center'}}>{item.enterpriseName}</Text>
</TouchableOpacity>
})}
</ScrollView>
)}
</>}
confirmText={"确认修改"}
/>
右图为点击“切换企业”对话框中的“申请加入”后的次级页面