diff --git a/App.tsx b/App.tsx index 282d85e..fa593d5 100644 --- a/App.tsx +++ b/App.tsx @@ -5,15 +5,17 @@ * @format */ -import React, {useState, useEffect} from 'react'; +import React, {useState, useEffect, useRef} from 'react'; import Navigation from './src/navigation'; import {ThemeProvider} from './src/contexts/ThemeContext'; import MaterialIcons from 'react-native-vector-icons/MaterialIcons'; import {View, Text, ActivityIndicator, StyleSheet} from 'react-native'; +import {BeautifulDialog, DialogUtils} from './src/utils/DialogUtils'; function App(): JSX.Element { const [fontLoaded, setFontLoaded] = useState(false); const [fontError, setFontError] = useState(null); + const dialogRef = useRef(null); useEffect(() => { const loadFont = async () => { @@ -44,6 +46,13 @@ function App(): JSX.Element { return () => clearTimeout(timeout); }, []); + // 设置弹窗引用 + useEffect(() => { + if (dialogRef.current) { + DialogUtils.setDialogRef(dialogRef.current); + } + }, [fontLoaded]); + // 显示加载状态或错误信息 if (!fontLoaded || fontError) { return ( @@ -60,6 +69,7 @@ function App(): JSX.Element { return ( + ); } diff --git a/package-lock.json b/package-lock.json index 2bb544b..f87b2a0 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "pda_react_native_template", - "version": "0.0.1", + "version": "1.0.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "pda_react_native_template", - "version": "0.0.1", + "version": "1.0.0", "dependencies": { "@expo/vector-icons": "^14.1.0", "@react-native-async-storage/async-storage": "^2.2.0", @@ -20,6 +20,7 @@ "react-native": "0.75.5", "react-native-chart-kit": "^6.12.0", "react-native-linear-gradient": "^2.8.3", + "react-native-modal": "^14.0.0-rc.1", "react-native-safe-area-context": "^5.5.1", "react-native-screens": "^4.5.0", "react-native-svg": "^15.12.0", @@ -13322,6 +13323,15 @@ } } }, + "node_modules/react-native-animatable": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/react-native-animatable/-/react-native-animatable-1.4.0.tgz", + "integrity": "sha512-DZwaDVWm2NBvBxf7I0wXKXLKb/TxDnkV53sWhCvei1pRyTX3MVFpkvdYBknNBqPrxYuAIlPxEp7gJOidIauUkw==", + "license": "MIT", + "dependencies": { + "prop-types": "^15.8.1" + } + }, "node_modules/react-native-chart-kit": { "version": "6.12.0", "resolved": "https://registry.npmjs.org/react-native-chart-kit/-/react-native-chart-kit-6.12.0.tgz", @@ -13368,6 +13378,19 @@ "react-native": "*" } }, + "node_modules/react-native-modal": { + "version": "14.0.0-rc.1", + "resolved": "https://registry.npmjs.org/react-native-modal/-/react-native-modal-14.0.0-rc.1.tgz", + "integrity": "sha512-v5pvGyx1FlmBzdHyPqBsYQyS2mIJhVmuXyNo5EarIzxicKhuoul6XasXMviGcXboEUT0dTYWs88/VendojPiVw==", + "license": "MIT", + "dependencies": { + "react-native-animatable": "1.4.0" + }, + "peerDependencies": { + "react": "*", + "react-native": ">=0.70.0" + } + }, "node_modules/react-native-safe-area-context": { "version": "5.5.1", "resolved": "https://registry.npmjs.org/react-native-safe-area-context/-/react-native-safe-area-context-5.5.1.tgz", diff --git a/package.json b/package.json index dae9194..7dc0929 100644 --- a/package.json +++ b/package.json @@ -22,6 +22,7 @@ "react-native": "0.75.5", "react-native-chart-kit": "^6.12.0", "react-native-linear-gradient": "^2.8.3", + "react-native-modal": "^14.0.0-rc.1", "react-native-safe-area-context": "^5.5.1", "react-native-screens": "^4.5.0", "react-native-svg": "^15.12.0", diff --git a/src/screens/stockIn/StockInEmpty.tsx b/src/screens/stockIn/StockInEmpty.tsx index dc227de..1a2d98a 100644 --- a/src/screens/stockIn/StockInEmpty.tsx +++ b/src/screens/stockIn/StockInEmpty.tsx @@ -5,7 +5,6 @@ import { StyleSheet, TextInput, TouchableOpacity, - Alert, SafeAreaView, ActivityIndicator, } from 'react-native'; @@ -17,6 +16,7 @@ import Icon from 'react-native-vector-icons/MaterialIcons'; import LinearGradient from 'react-native-linear-gradient'; import Svg, {Path} from 'react-native-svg'; import axios from 'axios'; +import {DialogUtils} from '../../utils'; interface StockInEmptyResponse { code: number; @@ -41,9 +41,7 @@ export const StockInEmpty: React.FC = () => { // 空托入库方法 const handleEmptyIn = async () => { if (!vehicleNo.trim()) { - Alert.alert('警告', '请先填写载具号', [ - {text: '返回填写', style: 'cancel'}, - ]); + DialogUtils.showWarningMessage('警告', '请先填写载具号', '返回填写'); return; } @@ -55,29 +53,29 @@ export const StockInEmpty: React.FC = () => { ); if (response.status !== 200) { - Alert.alert('警告', '服务器请求失败', [ - {text: '我知道了', style: 'cancel'}, - ]); + DialogUtils.showWarningMessage('警告', '服务器请求失败', '我知道了'); return; } const responseData = response.data; if (responseData.code === 200) { - Alert.alert('成功', '', [{text: '我知道了', style: 'default'}]); + DialogUtils.showSuccessMessage('成功', '空载具入库成功', '我知道了'); setVehicleNo(''); } else { - Alert.alert('警告', `服务器返回失败:${responseData.message}`, [ - {text: '我知道了', style: 'cancel'}, - ]); + DialogUtils.showWarningMessage( + '警告', + `服务器返回失败:${responseData.message}`, + '我知道了', + ); } } catch (error) { - Alert.alert( + DialogUtils.showErrorMessage( '请求发生错误', `请求服务器发生错误:${ error instanceof Error ? error.message : String(error) }`, - [{text: '我知道了', style: 'cancel'}], + '我知道了', ); } finally { setLoading(false); diff --git a/src/screens/stockIn/StockInManual.tsx b/src/screens/stockIn/StockInManual.tsx index 1810170..66b53aa 100644 --- a/src/screens/stockIn/StockInManual.tsx +++ b/src/screens/stockIn/StockInManual.tsx @@ -6,7 +6,6 @@ import { ScrollView, StyleSheet, TouchableOpacity, - Alert, ActivityIndicator, } from 'react-native'; import {useNavigation} from '@react-navigation/native'; @@ -14,6 +13,7 @@ import Icon from 'react-native-vector-icons/MaterialIcons'; import {useTheme} from '../../contexts/ThemeContext'; import LinearGradient from 'react-native-linear-gradient'; import Svg, {Path} from 'react-native-svg'; +import {DialogUtils, MessageItem} from '../../utils'; interface PackageDataItem { id: string; @@ -55,21 +55,24 @@ export const StockInManual: React.FC = () => { if (code.length === 15) { setLastProcessedVehicleCode(code); - Alert.alert('扫码成功', `绑定托盘: ${code}`); + DialogUtils.showSuccessMessage('扫码成功', `绑定托盘: ${code}`); } else { - Alert.alert('绑定托盘失败', '无效的载具号长度'); + DialogUtils.showErrorMessage('绑定托盘失败', '无效的载具号长度'); } }, [vehicleCode, lastProcessedVehicleCode]); const resolveCode = useCallback(() => { if (!goodsCode) { - Alert.alert('警告', '条码文本框内无数据,请先扫描或者输入数据'); + DialogUtils.showWarningMessage( + '警告', + '条码文本框内无数据,请先扫描或者输入数据', + ); return; } const codeData = goodsCode.split(','); if (![6, 7, 8].includes(codeData.length)) { - Alert.alert('警告', '条码格式错误'); + DialogUtils.showWarningMessage('警告', '条码格式错误'); return; } @@ -101,7 +104,7 @@ export const StockInManual: React.FC = () => { setPackageData(prev => [...prev, newItem]); setPackageDataId(prev => prev + 1); } else { - Alert.alert('警告', '该物料批次已存在,不能重复添加'); + DialogUtils.showWarningMessage('警告', '该物料批次已存在,不能重复添加'); } setGoodsCode(''); @@ -130,44 +133,38 @@ export const StockInManual: React.FC = () => { }; const modifyNumber = (_id: string) => { - Alert.alert('修改数量', '请输入新的数量', [ - {text: '取消', style: 'cancel'}, - { - text: '确定', - onPress: () => { - // TODO: 需要实现一个自定义的输入对话框 - Alert.alert('提示', '此功能暂不可用,请稍后再试'); - }, + DialogUtils.showConfirmMessage('修改数量', '请输入新的数量', { + cancelLabel: '取消', + confirmLabel: '确定', + confirm: () => { + // TODO: 需要实现一个自定义的输入对话框 + DialogUtils.showMessage('提示', '此功能暂不可用,请稍后再试'); }, - ]); + }); }; const showDetails = (item: PackageDataItem) => { - const message = [ - {label: '序号:', msg: item.id}, - {label: '采购单号:', msg: item.segment1}, - {label: '物料号:', msg: item.itemId}, - {label: '批次号:', msg: item.batch}, - {label: '数量:', msg: item.quantity}, - {label: '重量:', msg: item.weight}, - {label: '生产日期:', msg: item.productData}, + const message: MessageItem[] = [ + {label: '序号', msg: item.id}, + {label: '采购单号', msg: item.segment1}, + {label: '物料号', msg: item.itemId}, + {label: '批次号', msg: item.batch}, + {label: '数量', msg: item.quantity}, + {label: '重量', msg: item.weight}, + {label: '生产日期', msg: item.productData}, ]; - Alert.alert( - '数据详情', - message.map(m => `${m.label} ${m.msg}`).join('\n'), - [{text: '我知道了'}], - ); + DialogUtils.showMessageList('数据详情', message, '我知道了'); }; const wheelComplete = async () => { if (packageData.length === 0) { - Alert.alert('警告', '您的码盘数据为空'); + DialogUtils.showWarningMessage('警告', '您的码盘数据为空'); return; } if (!vehicleCode.trim()) { - Alert.alert('警告', '请先扫描载具号'); + DialogUtils.showWarningMessage('警告', '请先扫描载具号'); return; } @@ -175,16 +172,18 @@ export const StockInManual: React.FC = () => { const factoryIndex = 2; // 默认二厂 const statusIndex = 1; // 默认合格 - const confirm = await new Promise(resolve => - Alert.alert( + const confirm = await new Promise(resolve => { + DialogUtils.showConfirmMessage( '码盘完成', `载具:${vehicleCode} 码盘 ${packageData.length} 条数据,是否继续?`, - [ - {text: '取消', style: 'cancel', onPress: () => resolve(false)}, - {text: '继续', onPress: () => resolve(true)}, - ], - ), - ); + { + cancelLabel: '取消', + confirmLabel: '继续', + cancel: () => resolve(false), + confirm: () => resolve(true), + }, + ); + }); if (!confirm) return; @@ -211,17 +210,24 @@ export const StockInManual: React.FC = () => { setPackageDataId(0); setVehicleCode(''); setPackageData([]); - Alert.alert('码盘成功', '', [{text: '我知道了'}]); + DialogUtils.showSuccessMessage( + '码盘成功', + '码盘操作已完成', + '我知道了', + ); } else { - Alert.alert('警告', `服务器返回失败:${data.message}`); + DialogUtils.showWarningMessage( + '警告', + `服务器返回失败:${data.message}`, + ); } } catch (error) { - Alert.alert( + DialogUtils.showErrorMessage( '请求发生错误', `请求服务器发生错误:${ error instanceof Error ? error.message : String(error) }`, - [{text: '我知道了', style: 'cancel'}], + '我知道了', ); } finally { setLoading(false); diff --git a/src/utils/DialogUtils.tsx b/src/utils/DialogUtils.tsx new file mode 100644 index 0000000..104e9e9 --- /dev/null +++ b/src/utils/DialogUtils.tsx @@ -0,0 +1,893 @@ +import React, {useState, useRef} from 'react'; +import { + Modal, + View, + Text, + TextInput, + TouchableOpacity, + StyleSheet, + Dimensions, + ScrollView, + Animated, + StatusBar, +} from 'react-native'; +import Icon from 'react-native-vector-icons/MaterialIcons'; +import LinearGradient from 'react-native-linear-gradient'; +import {theme} from '../constants/theme'; + +const {width, height} = Dimensions.get('window'); + +export interface MessageItem { + label: string; + msg: string; +} + +export interface DialogConfig { + type: 'success' | 'warning' | 'error' | 'info' | 'confirm'; + title: string; + message?: string; + messageList?: MessageItem[]; + buttons?: DialogButton[]; + onConfirm?: () => void; + onCancel?: () => void; + confirmText?: string; + cancelText?: string; + showIcon?: boolean; +} + +interface DialogButton { + text: string; + onPress?: () => void; + style?: 'default' | 'cancel' | 'destructive'; +} + +// 美观的弹窗组件 +interface BeautifulDialogProps { + ref?: any; +} + +export const BeautifulDialog: React.FC = React.forwardRef( + (props, ref) => { + const [visible, setVisible] = useState(false); + const [config, setConfig] = useState(null); + const scaleAnim = useRef(new Animated.Value(0)).current; + const opacityAnim = useRef(new Animated.Value(0)).current; + + React.useImperativeHandle(ref, () => ({ + show: (dialogConfig: DialogConfig) => { + setConfig(dialogConfig); + setVisible(true); + // 动画效果 + Animated.parallel([ + Animated.spring(scaleAnim, { + toValue: 1, + tension: 100, + friction: 8, + useNativeDriver: true, + }), + Animated.timing(opacityAnim, { + toValue: 1, + duration: 200, + useNativeDriver: true, + }), + ]).start(); + }, + hide: () => { + Animated.parallel([ + Animated.spring(scaleAnim, { + toValue: 0, + tension: 100, + friction: 8, + useNativeDriver: true, + }), + Animated.timing(opacityAnim, { + toValue: 0, + duration: 150, + useNativeDriver: true, + }), + ]).start(() => { + setVisible(false); + setConfig(null); + }); + }, + })); + + const handleClose = () => { + const dialogRef = (ref as any)?.current || ref; + if (dialogRef?.hide) { + dialogRef.hide(); + } + }; + + const handleConfirm = () => { + config?.onConfirm?.(); + handleClose(); + }; + + const handleCancel = () => { + config?.onCancel?.(); + handleClose(); + }; + + const getIconConfig = () => { + switch (config?.type) { + case 'success': + return { + name: 'check-circle', + color: theme.colors.cyan, + bgColor: '#e6f7ff', + }; + case 'warning': + return { + name: 'warning', + color: theme.colors.cyan, + bgColor: '#e6f7ff', + }; + case 'error': + return {name: 'error', color: theme.colors.cyan, bgColor: '#e6f7ff'}; + case 'confirm': + return {name: 'help', color: theme.colors.cyan, bgColor: '#e6f7ff'}; + default: + return {name: 'info', color: theme.colors.cyan, bgColor: '#e6f7ff'}; + } + }; + + const getGradientColors = () => { + switch (config?.type) { + case 'success': + return [theme.colors.cyan, theme.colors.aqua]; + case 'warning': + return [theme.colors.cyan, theme.colors.aqua]; + case 'error': + return [theme.colors.cyan, theme.colors.aqua]; + case 'confirm': + return [theme.colors.cyan, theme.colors.aqua]; + default: + return [theme.colors.cyan, theme.colors.aqua]; + } + }; + + if (!visible || !config) return null; + + const iconConfig = getIconConfig(); + const gradientColors = getGradientColors(); + + return ( + + + + + {/* 头部图标区域 */} + + + + + + + {/* 内容区域 */} + + {config.title} + + {config.message && ( + {config.message} + )} + + {config.messageList && ( + + {config.messageList.map((item, index) => ( + + {item.label}: + {item.msg} + + ))} + + )} + + + {/* 按钮区域 */} + + {config.type === 'confirm' ? ( + <> + + + {config.cancelText || '取消'} + + + + + + {config.confirmText || '确定'} + + + + + ) : ( + + + + {config.confirmText || '确定'} + + + + )} + + + + + ); + }, +); + +// 输入框弹窗组件 +interface InputDialogProps { + visible: boolean; + title: string; + message?: string; + hintText?: string; + cancelLabel?: string; + confirmLabel?: string; + onCancel: () => void; + onConfirm: (value: string) => void; +} + +export const InputDialog: React.FC = ({ + visible, + title, + message, + hintText, + cancelLabel = '取消', + confirmLabel = '确定', + onCancel, + onConfirm, +}) => { + const [inputValue, setInputValue] = useState(''); + const scaleAnim = useRef(new Animated.Value(0)).current; + const opacityAnim = useRef(new Animated.Value(0)).current; + + React.useEffect(() => { + if (visible) { + Animated.parallel([ + Animated.spring(scaleAnim, { + toValue: 1, + tension: 100, + friction: 8, + useNativeDriver: true, + }), + Animated.timing(opacityAnim, { + toValue: 1, + duration: 200, + useNativeDriver: true, + }), + ]).start(); + } else { + scaleAnim.setValue(0); + opacityAnim.setValue(0); + } + }, [visible, opacityAnim, scaleAnim]); + + const handleConfirm = () => { + onConfirm(inputValue); + setInputValue(''); + }; + + const handleCancel = () => { + onCancel(); + setInputValue(''); + }; + + if (!visible) return null; + + return ( + + + + + {/* 头部图标 */} + + + + + + + {/* 内容区域 */} + + {title} + {message && {message}} + + + + + + + {/* 按钮区域 */} + + + {cancelLabel} + + + + {confirmLabel} + + + + + + + ); +}; + +// 单选框弹窗组件 +interface SingleSelectDialogProps { + visible: boolean; + title: string; + message?: string; + options: string[]; + selectedIndex?: number; + submitText?: string; + onSubmit: (index: number, value: string) => void; + onCancel: () => void; +} + +export const SingleSelectDialog: React.FC = ({ + visible, + title, + message, + options, + selectedIndex = 0, + submitText = '确定', + onSubmit, + onCancel, +}) => { + const [selected, setSelected] = useState(selectedIndex); + const scaleAnim = useRef(new Animated.Value(0)).current; + const opacityAnim = useRef(new Animated.Value(0)).current; + + React.useEffect(() => { + if (visible) { + Animated.parallel([ + Animated.spring(scaleAnim, { + toValue: 1, + tension: 100, + friction: 8, + useNativeDriver: true, + }), + Animated.timing(opacityAnim, { + toValue: 1, + duration: 200, + useNativeDriver: true, + }), + ]).start(); + } else { + scaleAnim.setValue(0); + opacityAnim.setValue(0); + setSelected(selectedIndex); + } + }, [visible, selectedIndex, opacityAnim, scaleAnim]); + + const handleSubmit = () => { + onSubmit(selected, options[selected]); + }; + + if (!visible) return null; + + return ( + + + + + {/* 头部 */} + + + + + + + + {title} + + + + + + + {message && ( + + {message} + + )} + + {/* 选项列表 */} + + {options.map((option, index) => ( + setSelected(index)}> + + + {selected === index && ( + + )} + + + {option} + + + + ))} + + + {/* 提交按钮 */} + + + + {submitText} + + + + + + + ); +}; + +// 全新的 DialogUtils 类 +export class DialogUtils { + private static dialogRef: any = null; + + static setDialogRef(ref: any) { + DialogUtils.dialogRef = ref; + } + + /** + * 弹出一个信息展示框 + */ + static showMessage( + title: string, + message: string, + btnLabel: string = '确定', + onPress?: () => void, + ): void { + if (DialogUtils.dialogRef) { + DialogUtils.dialogRef.show({ + type: 'info', + title, + message, + confirmText: btnLabel, + onConfirm: onPress, + }); + } + } + + /** + * 展示一个信息列表,message 必须是 label 和 msg + */ + static showMessageList( + title: string, + message: MessageItem[], + btnLabel: string = '确定', + onPress?: () => void, + ): void { + if (DialogUtils.dialogRef) { + DialogUtils.dialogRef.show({ + type: 'info', + title, + messageList: message, + confirmText: btnLabel, + onConfirm: onPress, + }); + } + } + + /** + * 弹出一个成功的提示框 + */ + static showSuccessMessage( + title: string, + message: string, + btnLabel: string = '确定', + onPress?: () => void, + ): void { + if (DialogUtils.dialogRef) { + DialogUtils.dialogRef.show({ + type: 'success', + title, + message, + confirmText: btnLabel, + onConfirm: onPress, + }); + } + } + + /** + * 弹出一个警告提示框 + */ + static showWarningMessage( + title: string, + message: string, + btnLabel: string = '确定', + onPress?: () => void, + ): void { + if (DialogUtils.dialogRef) { + DialogUtils.dialogRef.show({ + type: 'warning', + title, + message, + confirmText: btnLabel, + onConfirm: onPress, + }); + } + } + + /** + * 弹出一个错误提示框 + */ + static showErrorMessage( + title: string, + message: string, + btnLabel: string = '确定', + onPress?: () => void, + ): void { + if (DialogUtils.dialogRef) { + DialogUtils.dialogRef.show({ + type: 'error', + title, + message, + confirmText: btnLabel, + onConfirm: onPress, + }); + } + } + + /** + * 弹出一个确认框 + */ + static showConfirmMessage( + title: string, + message: string, + options: { + cancel?: () => void; + confirm?: () => void; + cancelLabel?: string; + confirmLabel?: string; + } = {}, + ): void { + const { + cancel, + confirm, + cancelLabel = '取消', + confirmLabel = '确定', + } = options; + + if (DialogUtils.dialogRef) { + DialogUtils.dialogRef.show({ + type: 'confirm', + title, + message, + cancelText: cancelLabel, + confirmText: confirmLabel, + onCancel: cancel, + onConfirm: confirm, + }); + } + } +} + +const styles = StyleSheet.create({ + overlay: { + flex: 1, + backgroundColor: 'rgba(0, 0, 0, 0.5)', + justifyContent: 'center', + alignItems: 'center', + paddingHorizontal: 20, + }, + dialogContainer: { + backgroundColor: '#fff', + borderRadius: 16, + minWidth: width * 0.8, + maxWidth: width * 0.9, + maxHeight: height * 0.8, + shadowColor: '#000', + shadowOffset: {width: 0, height: 8}, + shadowOpacity: 0.25, + shadowRadius: 16, + elevation: 8, + }, + iconContainer: { + alignItems: 'center', + paddingTop: 24, + paddingBottom: 16, + borderTopLeftRadius: 16, + borderTopRightRadius: 16, + }, + iconWrapper: { + width: 60, + height: 60, + borderRadius: 30, + backgroundColor: '#fff', + justifyContent: 'center', + alignItems: 'center', + shadowColor: '#000', + shadowOffset: {width: 0, height: 2}, + shadowOpacity: 0.1, + shadowRadius: 4, + elevation: 2, + }, + contentContainer: { + paddingHorizontal: 24, + paddingBottom: 24, + }, + title: { + fontSize: 20, + fontWeight: 'bold', + color: '#333', + textAlign: 'center', + marginBottom: 12, + }, + message: { + fontSize: 16, + color: '#666', + textAlign: 'center', + lineHeight: 24, + marginBottom: 8, + }, + messageListContainer: { + backgroundColor: '#f8f9fa', + borderRadius: 8, + padding: 16, + marginTop: 12, + }, + messageItem: { + flexDirection: 'row', + alignItems: 'center', + marginBottom: 8, + }, + messageLabel: { + fontSize: 14, + color: '#666', + fontWeight: '500', + minWidth: 80, + }, + messageValue: { + fontSize: 14, + color: '#333', + flex: 1, + }, + buttonContainer: { + flexDirection: 'row', + paddingHorizontal: 24, + paddingBottom: 24, + gap: 12, + }, + button: { + flex: 1, + height: 48, + borderRadius: 12, + justifyContent: 'center', + alignItems: 'center', + }, + buttonTouchable: { + width: '100%', + height: '100%', + justifyContent: 'center', + alignItems: 'center', + }, + cancelButton: { + backgroundColor: '#f5f5f5', + borderWidth: 1, + borderColor: '#d9d9d9', + }, + confirmButton: { + shadowColor: theme.colors.cyan, + shadowOffset: {width: 0, height: 4}, + shadowOpacity: 0.3, + shadowRadius: 8, + elevation: 4, + }, + singleButton: { + shadowColor: theme.colors.cyan, + shadowOffset: {width: 0, height: 4}, + shadowOpacity: 0.3, + shadowRadius: 8, + elevation: 4, + }, + cancelButtonText: { + color: '#666', + fontSize: 16, + fontWeight: '600', + }, + confirmButtonText: { + color: '#fff', + fontSize: 16, + fontWeight: '600', + }, + inputContainer: { + marginTop: 16, + }, + textInput: { + borderWidth: 1, + borderColor: '#d9d9d9', + borderRadius: 8, + padding: 12, + fontSize: 16, + color: '#333', + backgroundColor: '#fafafa', + }, + // 单选框相关样式 + selectDialogContainer: { + maxHeight: height * 0.7, + }, + selectHeader: { + paddingBottom: 8, + }, + headerContent: { + flexDirection: 'row', + justifyContent: 'space-between', + alignItems: 'center', + paddingHorizontal: 24, + paddingTop: 8, + }, + closeButton: { + width: 32, + height: 32, + borderRadius: 16, + backgroundColor: '#f5f5f5', + justifyContent: 'center', + alignItems: 'center', + }, + messageContainer: { + paddingHorizontal: 24, + paddingBottom: 12, + }, + optionsContainer: { + maxHeight: height * 0.4, + paddingHorizontal: 24, + }, + optionItem: { + paddingVertical: 16, + borderBottomWidth: 1, + borderBottomColor: '#f0f0f0', + }, + selectedOptionItem: { + backgroundColor: '#e6f7ff', + borderRadius: 8, + borderBottomColor: 'transparent', + marginVertical: 2, + }, + optionContent: { + flexDirection: 'row', + alignItems: 'center', + paddingHorizontal: 8, + }, + radioButton: { + width: 20, + height: 20, + borderRadius: 10, + borderWidth: 2, + borderColor: '#d9d9d9', + marginRight: 16, + justifyContent: 'center', + alignItems: 'center', + }, + radioButtonSelected: { + borderColor: theme.colors.cyan, + }, + radioButtonInner: { + width: 10, + height: 10, + borderRadius: 5, + backgroundColor: theme.colors.cyan, + }, + optionText: { + fontSize: 16, + color: '#333', + flex: 1, + }, + selectedOptionText: { + color: theme.colors.cyan, + fontWeight: '600', + }, + selectButtonContainer: { + paddingHorizontal: 24, + paddingTop: 16, + paddingBottom: 24, + }, + submitButton: { + height: 48, + borderRadius: 12, + shadowColor: theme.colors.cyan, + shadowOffset: {width: 0, height: 4}, + shadowOpacity: 0.3, + shadowRadius: 8, + elevation: 4, + }, +}); diff --git a/src/utils/index.ts b/src/utils/index.ts new file mode 100644 index 0000000..5c8ecb7 --- /dev/null +++ b/src/utils/index.ts @@ -0,0 +1,6 @@ +// 弹窗工具导出 +export {DialogUtils, InputDialog, SingleSelectDialog} from './DialogUtils'; +export type {MessageItem} from './DialogUtils'; + +// 存储工具导出 +export * from './storage';