Compare commits

..

5 Commits

Author SHA1 Message Date
484b9c332b Merge branch 'master' of http://112.4.208.194:3000/liyuqi/pda_react_native_template 2025-07-16 08:11:33 +08:00
b80d368d5e 修改弹窗模块及配色 2025-07-09 17:16:24 +08:00
b6c8db84a8 优化项目结构 2025-07-09 16:21:15 +08:00
e937988771 优化项目结构 2025-07-09 14:47:04 +08:00
1705ace101 修改配色 2025-07-09 10:35:33 +08:00
20 changed files with 1336 additions and 525 deletions

42
App.tsx
View File

@ -1,25 +1,3 @@
// /**
// * Sample React Native App
// * https://github.com/facebook/react-native
// *
// * @format
// */
//
// import React from 'react';
// import Navigation from './src/navigation';
// import {ThemeProvider} from './src/contexts/ThemeContext';
//
// function App(): JSX.Element {
// return (
// <ThemeProvider>
// <Navigation />
// </ThemeProvider>
// );
// }
//
// export default App;
/** /**
* Sample React Native App * Sample React Native App
* https://github.com/facebook/react-native * https://github.com/facebook/react-native
@ -27,15 +5,17 @@
* @format * @format
*/ */
import React, { useState, useEffect } from 'react'; import React, {useState, useEffect, useRef} from 'react';
import Navigation from './src/navigation'; import Navigation from './src/navigation';
import {ThemeProvider} from './src/contexts/ThemeContext'; import {ThemeProvider} from './src/contexts/ThemeContext';
import MaterialIcons from 'react-native-vector-icons/MaterialIcons'; import MaterialIcons from 'react-native-vector-icons/MaterialIcons';
import { View, Text, ActivityIndicator, StyleSheet } from 'react-native'; import {View, Text, ActivityIndicator, StyleSheet} from 'react-native';
import {BeautifulDialog, DialogUtils} from './src/utils/DialogUtils';
function App(): JSX.Element { function App(): JSX.Element {
const [fontLoaded, setFontLoaded] = useState(false); const [fontLoaded, setFontLoaded] = useState(false);
const [fontError, setFontError] = useState<string | null>(null); const [fontError, setFontError] = useState<string | null>(null);
const dialogRef = useRef<any>(null);
useEffect(() => { useEffect(() => {
const loadFont = async () => { const loadFont = async () => {
@ -66,14 +46,19 @@ function App(): JSX.Element {
return () => clearTimeout(timeout); return () => clearTimeout(timeout);
}, []); }, []);
// 设置弹窗引用
useEffect(() => {
if (dialogRef.current) {
DialogUtils.setDialogRef(dialogRef.current);
}
}, [fontLoaded]);
// 显示加载状态或错误信息 // 显示加载状态或错误信息
if (!fontLoaded || fontError) { if (!fontLoaded || fontError) {
return ( return (
<View style={styles.container}> <View style={styles.container}>
<ActivityIndicator size="large" /> <ActivityIndicator size="large" />
<Text style={styles.statusText}> <Text style={styles.statusText}>{fontError || 'Loading icons...'}</Text>
{fontError || 'Loading icons...'}
</Text>
<Text style={styles.debugText}> <Text style={styles.debugText}>
Using MaterialIcons font family: {MaterialIcons.getFontFamily()} Using MaterialIcons font family: {MaterialIcons.getFontFamily()}
</Text> </Text>
@ -84,6 +69,7 @@ function App(): JSX.Element {
return ( return (
<ThemeProvider> <ThemeProvider>
<Navigation /> <Navigation />
<BeautifulDialog ref={dialogRef} />
</ThemeProvider> </ThemeProvider>
); );
} }
@ -108,4 +94,4 @@ const styles = StyleSheet.create({
}, },
}); });
export default App; export default App;

View File

@ -1,6 +1,6 @@
{ {
"name": "pda_react_native_template", "name": "pda_react_native_template",
"version": "0.0.1", "version": "1.0.0",
"private": true, "private": true,
"scripts": { "scripts": {
"android": "react-native run-android", "android": "react-native run-android",
@ -22,6 +22,7 @@
"react-native": "0.75.5", "react-native": "0.75.5",
"react-native-chart-kit": "^6.12.0", "react-native-chart-kit": "^6.12.0",
"react-native-linear-gradient": "^2.8.3", "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-safe-area-context": "^5.5.1",
"react-native-screens": "^4.5.0", "react-native-screens": "^4.5.0",
"react-native-svg": "^15.12.0", "react-native-svg": "^15.12.0",

View File

@ -1,26 +0,0 @@
/**
* Sample React Native App
* https://github.com/facebook/react-native
*
* @format
*/
import React from 'react';
import {StatusBar} from 'react-native';
import Navigation from './navigation';
import {ThemeProvider} from './contexts/ThemeContext';
import {theme} from './constants/theme';
const App = () => {
return (
<ThemeProvider theme={theme}>
<StatusBar
backgroundColor={theme.colors.primary}
barStyle="light-content"
/>
<Navigation />
</ThemeProvider>
);
};
export default App;

View File

@ -2,4 +2,4 @@ export const ENV = {
API_URL: __DEV__ ? 'http://dev-api.example.com' : 'https://api.example.com', API_URL: __DEV__ ? 'http://dev-api.example.com' : 'https://api.example.com',
APP_NAME: 'PdaRnTemplate', APP_NAME: 'PdaRnTemplate',
VERSION: '1.0.0', VERSION: '1.0.0',
}; };

View File

@ -1,45 +1,31 @@
// 将Flutter的颜色值转换为React Native可用的格式
// Flutter: 0xff05dcef -> React Native: #05dcef
export const theme = { export const theme = {
colors: { colors: {
// 基础颜色 // 基础颜色
primary: '#4158D0', // 主色调 aqua: '#05DCEF', // 青色主色调 (0xff05dcef)
secondary: '#C850C0', // 次要色调 background: '#ffffff', // 背景色
tertiary: '#FFCC70', // 第三色调 backgroundGray: '#F5F5F5', // 灰色背景
background: '#ffffff', // 背景色 text: '#333333', // 文本颜色
text: '#333333', // 文本颜色 textLight: '#666666', // 次要文本颜色
textLight: '#666666', // 次要文本颜色 border: '#dddddd', // 边框颜色
border: '#dddddd', // 边框颜色 error: '#ff3b30', // 错误颜色
error: '#ff3b30', // 错误颜色 cyan: '#00FFFF', // 青色背景
success: '#4cd964', // 成功颜色 lightGreen: '#7FFFAA', // 浅绿色
warning: '#ff9500', // 警告颜色
cyan: '#00FFFF', // 青色背景
// 渐变色配置 // 渐变色配置
gradients: { gradients: {
primary: ['#4158D0', '#C850C0', '#FFCC70'], // 主要渐变(蓝紫金) primary: ['#05DCEF', '#7DE2F5', '#B8F2FF'], // 主要渐变(青蓝白,小清新)
contrast: ['#00F5A0', '#00D9F5'], // 对比渐变(绿青) contrast: ['#00F5A0', '#00D9F5'], // 对比渐变(绿青)
card: ['#9795F0', '#E3C3F1'], // 卡片渐变(柔和紫色) header: ['#05DCEF', '#7DE2F5'], // 头部渐变
login: ['#4158D0', '#C850C0', '#FFCC70'], // 登录页面专用渐变 button: ['#05DCEF', '#7DE2F5'], // 按钮渐变
loginWave: ['rgba(255, 255, 255, 0.3)', 'rgba(255, 255, 255, 0.2)'], // 登录页面波浪渐变
header: ['#4158D0', '#C850C0'], // 头部渐变
button: ['#4158D0', '#C850C0'], // 按钮渐变
// 波浪背景渐变
wave: {
start: 'rgba(255, 255, 255, 0.3)',
middle: 'rgba(255, 255, 255, 0.2)',
end: 'rgba(255, 255, 255, 0.1)'
}
}, },
// 图表颜色 // 图表颜色
chart: { chart: {
blue: '#0077B6', // 深蓝 cyan: '#00FFFF', // 青色
orange: '#FB8500', // 明亮的橙 lightGreen: '#7FFFAA', // 浅绿色
}, },
}, },
// 字体大小 // 字体大小
fontSize: { fontSize: {
small: 12, small: 12,
@ -50,7 +36,7 @@ export const theme = {
xxlarge: 24, xxlarge: 24,
title: 32, title: 32,
}, },
// 间距 // 间距
spacing: { spacing: {
xs: 4, xs: 4,
@ -59,7 +45,7 @@ export const theme = {
large: 24, large: 24,
xl: 32, xl: 32,
}, },
// 圆角 // 圆角
borderRadius: { borderRadius: {
small: 4, small: 4,
@ -68,7 +54,7 @@ export const theme = {
xl: 16, xl: 16,
circle: 999, circle: 999,
}, },
// 阴影 // 阴影
shadow: { shadow: {
small: { small: {
@ -87,7 +73,7 @@ export const theme = {
width: 0, width: 0,
height: 4, height: 4,
}, },
shadowOpacity: 0.30, shadowOpacity: 0.3,
shadowRadius: 4.65, shadowRadius: 4.65,
elevation: 4, elevation: 4,
}, },
@ -102,12 +88,6 @@ export const theme = {
elevation: 6, elevation: 6,
}, },
}, },
// 波浪效果配置
wave: {
height: 60,
opacity: 1,
},
}; };
// 类型定义 // 类型定义
@ -116,4 +96,4 @@ export type Theme = typeof theme;
// 导出类型 // 导出类型
declare module '@react-navigation/native' { declare module '@react-navigation/native' {
export type ExtendedTheme = Theme; export type ExtendedTheme = Theme;
} }

View File

@ -18,4 +18,4 @@ export const useTheme = () => {
throw new Error('useTheme must be used within a ThemeProvider'); throw new Error('useTheme must be used within a ThemeProvider');
} }
return theme; return theme;
}; };

View File

@ -1,23 +0,0 @@
class ScannerService {
async init(): Promise<void> {
// 初始化扫描器
console.log('Scanner initialized');
}
async startScan(): Promise<string> {
// 开始扫描
return new Promise((resolve) => {
// 模拟扫描结果
setTimeout(() => {
resolve('Scanned barcode result');
}, 1000);
});
}
async stopScan(): Promise<void> {
// 停止扫描
console.log('Scanner stopped');
}
}
export const scannerService = new ScannerService();

View File

@ -1,38 +0,0 @@
import {useState, useCallback} from 'react';
import {scannerService} from '../device/scanner';
export const useScanner = () => {
const [isScanning, setIsScanning] = useState(false);
const [result, setResult] = useState<string | null>(null);
const [error, setError] = useState<Error | null>(null);
const startScan = useCallback(async () => {
try {
setIsScanning(true);
setError(null);
const scanResult = await scannerService.startScan();
setResult(scanResult);
} catch (err) {
setError(err as Error);
} finally {
setIsScanning(false);
}
}, []);
const stopScan = useCallback(async () => {
try {
await scannerService.stopScan();
setIsScanning(false);
} catch (err) {
setError(err as Error);
}
}, []);
return {
isScanning,
result,
error,
startScan,
stopScan,
};
};

View File

@ -4,7 +4,7 @@ import {createNativeStackNavigator} from '@react-navigation/native-stack';
import {LoginScreen} from '../screens/auth/LoginScreen'; import {LoginScreen} from '../screens/auth/LoginScreen';
import {HomeScreen} from '../screens/home/HomeScreen'; import {HomeScreen} from '../screens/home/HomeScreen';
import {StockInEmpty} from '../screens/stockIn/StockInEmpty'; import {StockInEmpty} from '../screens/stockIn/StockInEmpty';
import StockInWheelManual from '../screens/stockIn/StockInWheelManual'; import {StockInManual} from '../screens/stockIn/StockInManual';
import {RootStackParamList} from './types'; import {RootStackParamList} from './types';
import {Platform} from 'react-native'; import {Platform} from 'react-native';
import {screensEnabled, enableScreens} from 'react-native-screens'; import {screensEnabled, enableScreens} from 'react-native-screens';
@ -50,8 +50,8 @@ const Navigation = () => {
}} }}
/> />
<Stack.Screen <Stack.Screen
name="StockInWheelManual" name="StockInManual"
component={StockInWheelManual} component={StockInManual}
options={{ options={{
animation: 'none', animation: 'none',
}} }}

View File

@ -4,8 +4,8 @@ export type RootStackParamList = {
Login: undefined; Login: undefined;
Home: undefined; Home: undefined;
StockInEmpty: undefined; StockInEmpty: undefined;
StockInWheelEBS: undefined; StockInEBS: undefined;
StockInWheelManual: undefined; StockInManual: undefined;
Pick: undefined; Pick: undefined;
StockCheck: undefined; StockCheck: undefined;
// 在这里添加其他页面的路由参数定义 // 在这里添加其他页面的路由参数定义
@ -18,4 +18,4 @@ declare global {
namespace ReactNavigation { namespace ReactNavigation {
interface RootParamList extends RootStackParamList {} interface RootParamList extends RootStackParamList {}
} }
} }

View File

@ -28,7 +28,7 @@ export const LoginScreen: React.FC = () => {
const theme = useTheme(); const theme = useTheme();
const handleLogin = useCallback(() => { const handleLogin = useCallback(() => {
if (!isFocused) return; if (!isFocused) {return;}
InteractionManager.runAfterInteractions(() => { InteractionManager.runAfterInteractions(() => {
if (isFocused) { if (isFocused) {
@ -41,9 +41,9 @@ export const LoginScreen: React.FC = () => {
<Screen style={styles.screen}> <Screen style={styles.screen}>
{/* 背景区域 */} {/* 背景区域 */}
<View style={styles.backgroundContainer}> <View style={styles.backgroundContainer}>
<WaveBackground <WaveBackground
height={height * 0.6} height={height * 0.6}
gradientColors={theme.colors.gradients.primary} gradientColors={theme.colors.gradients.primary}
/> />
</View> </View>
@ -51,7 +51,7 @@ export const LoginScreen: React.FC = () => {
<View style={styles.contentContainer}> <View style={styles.contentContainer}>
{/* 标题区域 */} {/* 标题区域 */}
<View style={styles.titleContainer}> <View style={styles.titleContainer}>
<Text style={styles.title}></Text> <Text style={styles.title}></Text>
<Text style={styles.subtitle}></Text> <Text style={styles.subtitle}></Text>
</View> </View>
@ -60,8 +60,8 @@ export const LoginScreen: React.FC = () => {
{/* Logo */} {/* Logo */}
<View style={styles.logoWrapper}> <View style={styles.logoWrapper}>
<View style={[styles.logoCircle, theme.shadow.medium]}> <View style={[styles.logoCircle, theme.shadow.medium]}>
<Text style={[styles.logoText, {color: theme.colors.primary}]}> <Text style={[styles.logoText, {color: theme.colors.aqua}]}>
JW BK
</Text> </Text>
</View> </View>
</View> </View>
@ -83,7 +83,7 @@ export const LoginScreen: React.FC = () => {
{/* 底部版权信息 */} {/* 底部版权信息 */}
<View style={styles.footer}> <View style={styles.footer}>
<Text style={[styles.footerText, {color: theme.colors.textLight}]}> <Text style={[styles.footerText, {color: theme.colors.textLight}]}>
© 2024 © 2025
</Text> </Text>
</View> </View>
</View> </View>
@ -183,4 +183,4 @@ const styles = StyleSheet.create({
footerText: { footerText: {
fontSize: 12, fontSize: 12,
}, },
}); });

View File

@ -1,4 +1,4 @@
import React, { useState, useEffect } from 'react'; import React, {useState, useEffect} from 'react';
import { import {
View, View,
Text, Text,
@ -8,16 +8,16 @@ import {
ScrollView, ScrollView,
Dimensions, Dimensions,
} from 'react-native'; } from 'react-native';
import { useNavigation } from '@react-navigation/native'; import {useNavigation} from '@react-navigation/native';
import { NativeStackNavigationProp } from '@react-navigation/native-stack'; import {NativeStackNavigationProp} from '@react-navigation/native-stack';
import { RootStackParamList } from '../../navigation/types'; import {RootStackParamList} from '../../navigation/types';
import { PieChart } from 'react-native-chart-kit'; import {PieChart} from 'react-native-chart-kit';
import Icon from 'react-native-vector-icons/MaterialIcons'; import Icon from 'react-native-vector-icons/MaterialIcons';
import { useTheme } from '../../contexts/ThemeContext'; import {useTheme} from '../../contexts/ThemeContext';
import LinearGradient from 'react-native-linear-gradient'; import LinearGradient from 'react-native-linear-gradient';
import Svg, { Path } from 'react-native-svg'; import Svg, {Path} from 'react-native-svg';
const { width } = Dimensions.get('window'); const {width} = Dimensions.get('window');
type HomeScreenNavigationProp = NativeStackNavigationProp< type HomeScreenNavigationProp = NativeStackNavigationProp<
RootStackParamList, RootStackParamList,
@ -48,22 +48,22 @@ export const HomeScreen: React.FC = () => {
}, []); }, []);
const menuItems: MenuItem[] = [ const menuItems: MenuItem[] = [
{ title: '空载具入库', icon: 'local-shipping', route: 'StockInEmpty' }, {title: '空载具入库', icon: 'local-shipping', route: 'StockInEmpty'},
{ title: '手动码盘入库', icon: 'inventory', route: 'StockInWheelManual' }, {title: '手动码盘入库', icon: 'inventory', route: 'StockInManual'},
]; ];
const stockData = [ const stockData = [
{ {
name: '空闲', name: '空闲',
population: 40, population: 40,
color: theme.colors.chart.blue, color: theme.colors.chart.cyan,
legendFontColor: theme.colors.textLight, legendFontColor: theme.colors.textLight,
legendFontSize: theme.fontSize.regular, legendFontSize: theme.fontSize.regular,
}, },
{ {
name: '占用', name: '占用',
population: 60, population: 60,
color: theme.colors.chart.orange, color: theme.colors.chart.lightGreen,
legendFontColor: theme.colors.textLight, legendFontColor: theme.colors.textLight,
legendFontSize: theme.fontSize.regular, legendFontSize: theme.fontSize.regular,
}, },
@ -77,23 +77,30 @@ export const HomeScreen: React.FC = () => {
if (!fontLoaded) { if (!fontLoaded) {
return ( return (
<View style={[styles.loadingContainer, { backgroundColor: theme.colors.background }]}> <View
<Text style={{ color: theme.colors.text }}>...</Text> style={[
styles.loadingContainer,
{backgroundColor: theme.colors.backgroundGray},
]}>
<Text style={{color: theme.colors.text}}>...</Text>
</View> </View>
); );
} }
return ( return (
<SafeAreaView style={[styles.container, { backgroundColor: theme.colors.background }]}> <SafeAreaView
style={[
styles.container,
{backgroundColor: theme.colors.backgroundGray},
]}>
{/* 头部区域 */} {/* 头部区域 */}
<View style={styles.headerSection}> <View style={styles.headerSection}>
{/* 渐变背景 */} {/* 渐变背景 */}
<LinearGradient <LinearGradient
colors={theme.colors.gradients.primary} colors={theme.colors.gradients.primary}
style={StyleSheet.absoluteFill} style={StyleSheet.absoluteFill}
start={{ x: 0, y: 0 }} start={{x: 0, y: 0}}
end={{ x: 1, y: 1 }} end={{x: 1, y: 1}}>
>
{/* 半透明波浪效果 */} {/* 半透明波浪效果 */}
<View style={styles.waveOverlay}> <View style={styles.waveOverlay}>
<Svg height="100%" width={width} style={styles.waveSvg}> <Svg height="100%" width={width} style={styles.waveSvg}>
@ -131,17 +138,25 @@ export const HomeScreen: React.FC = () => {
style={styles.menuButton}> style={styles.menuButton}>
<Icon name="menu" size={24} color={theme.colors.background} /> <Icon name="menu" size={24} color={theme.colors.background} />
</TouchableOpacity> </TouchableOpacity>
<Text style={[styles.headerTitle, { color: theme.colors.background }]}> <Text style={[styles.headerTitle, {color: theme.colors.background}]}>
WMS移动终端 WMS移动终端
</Text> </Text>
<TouchableOpacity style={styles.iconButton}> <TouchableOpacity style={styles.iconButton}>
<Icon name="notifications" size={24} color={theme.colors.background} /> <Icon
name="notifications"
size={24}
color={theme.colors.background}
/>
</TouchableOpacity> </TouchableOpacity>
</View> </View>
</View> </View>
{/* 主体内容 */} {/* 主体内容 */}
<View style={[styles.mainContent, { backgroundColor: '#FFFFFF' }]}> <View
style={[
styles.mainContent,
{backgroundColor: theme.colors.backgroundGray},
]}>
<ScrollView contentContainerStyle={styles.scrollContent}> <ScrollView contentContainerStyle={styles.scrollContent}>
{/* 快捷操作区 */} {/* 快捷操作区 */}
<View style={styles.quickActions}> <View style={styles.quickActions}>
@ -157,9 +172,17 @@ export const HomeScreen: React.FC = () => {
end={{x: 1, y: 0}} end={{x: 1, y: 0}}
/> />
<View style={styles.iconContainer}> <View style={styles.iconContainer}>
<Icon name={item.icon} size={32} color={theme.colors.background} /> <Icon
name={item.icon}
size={32}
color={theme.colors.background}
/>
</View> </View>
<Text style={[styles.actionTitle, {color: theme.colors.background}]}> <Text
style={[
styles.actionTitle,
{color: theme.colors.background},
]}>
{item.title} {item.title}
</Text> </Text>
</TouchableOpacity> </TouchableOpacity>
@ -190,29 +213,39 @@ export const HomeScreen: React.FC = () => {
{/* 侧边菜单 */} {/* 侧边菜单 */}
{isDrawerOpen && ( {isDrawerOpen && (
<> <>
<View style={[styles.drawer, { backgroundColor: theme.colors.background }]}> <View
style={[styles.drawer, {backgroundColor: theme.colors.background}]}>
<LinearGradient <LinearGradient
colors={theme.colors.gradients.primary} colors={theme.colors.gradients.primary}
style={styles.drawerHeader}> style={styles.drawerHeader}>
<Text style={[styles.drawerTitle, { color: theme.colors.background }]}> <Text
style={[styles.drawerTitle, {color: theme.colors.background}]}>
</Text> </Text>
<Text style={[styles.drawerSubtitle, { color: theme.colors.background }]}> <Text
style={[
styles.drawerSubtitle,
{color: theme.colors.background},
]}>
使WMS移动终端 使WMS移动终端
</Text> </Text>
</LinearGradient> </LinearGradient>
{menuItems.map((item, index) => ( {menuItems.map((item, index) => (
<TouchableOpacity <TouchableOpacity
key={index} key={index}
style={[styles.drawerItem, { borderBottomColor: theme.colors.border }]} style={[
styles.drawerItem,
{borderBottomColor: theme.colors.border},
]}
onPress={() => { onPress={() => {
setIsDrawerOpen(false); setIsDrawerOpen(false);
navigation.navigate(item.route); navigation.navigate(item.route);
}}> }}>
<View style={styles.drawerIconContainer}> <View style={styles.drawerIconContainer}>
<Icon name={item.icon} size={28} color={theme.colors.primary} /> <Icon name={item.icon} size={28} color={theme.colors.aqua} />
</View> </View>
<Text style={[styles.drawerItemText, { color: theme.colors.text }]}> <Text
style={[styles.drawerItemText, {color: theme.colors.text}]}>
{item.title} {item.title}
</Text> </Text>
</TouchableOpacity> </TouchableOpacity>

View File

@ -5,11 +5,9 @@ import {
StyleSheet, StyleSheet,
TextInput, TextInput,
TouchableOpacity, TouchableOpacity,
Alert,
SafeAreaView, SafeAreaView,
ActivityIndicator, ActivityIndicator,
} from 'react-native'; } from 'react-native';
import {httpService} from '../../services/http';
import {NativeStackNavigationProp} from '@react-navigation/native-stack'; import {NativeStackNavigationProp} from '@react-navigation/native-stack';
import {useNavigation} from '@react-navigation/native'; import {useNavigation} from '@react-navigation/native';
import {RootStackParamList} from '../../navigation/types'; import {RootStackParamList} from '../../navigation/types';
@ -17,11 +15,16 @@ import {useTheme} from '../../contexts/ThemeContext';
import Icon from 'react-native-vector-icons/MaterialIcons'; import Icon from 'react-native-vector-icons/MaterialIcons';
import LinearGradient from 'react-native-linear-gradient'; import LinearGradient from 'react-native-linear-gradient';
import Svg, {Path} from 'react-native-svg'; import Svg, {Path} from 'react-native-svg';
import axios from 'axios';
import {DialogUtils} from '../../utils';
interface StockInEmptyResponse { interface StockInEmptyResponse {
code: number; code: number;
message: string; message: string;
data: string; data: {
code: number;
message: string;
};
} }
type StockInEmptyScreenNavigationProp = NativeStackNavigationProp< type StockInEmptyScreenNavigationProp = NativeStackNavigationProp<
@ -38,55 +41,53 @@ export const StockInEmpty: React.FC = () => {
// 空托入库方法 // 空托入库方法
const handleEmptyIn = async () => { const handleEmptyIn = async () => {
if (!vehicleNo.trim()) { if (!vehicleNo.trim()) {
Alert.alert('警告', '请先填写载具号', [ DialogUtils.showWarningMessage('警告', '请先填写载具号', '返回填写');
{text: '返回填写', style: 'cancel'},
]);
return; return;
} }
try { try {
setLoading(true); setLoading(true);
const response = await httpService.post<StockInEmptyResponse>( const response = await axios.post<StockInEmptyResponse>(
'/api/vehicle/empty-in', '/api/vehicle/empty-in',
{vehicleNo: vehicleNo.trim()} {vehicleNo: vehicleNo.trim()},
); );
if (response.code !== 200) { if (response.status !== 200) {
Alert.alert('警告', '服务器请求失败', [ DialogUtils.showWarningMessage('警告', '服务器请求失败', '我知道了');
{text: '我知道了', style: 'cancel'},
]);
return; return;
} }
// 确保response.data是字符串类型 const responseData = response.data;
const responseData = typeof response.data === 'string'
? JSON.parse(response.data)
: response.data;
if (responseData.code === 200) { if (responseData.code === 200) {
Alert.alert('成功', '', [ DialogUtils.showSuccessMessage('成功', '空载具入库成功', '我知道了');
{text: '我知道了', style: 'default'},
]);
setVehicleNo(''); setVehicleNo('');
} else { } else {
Alert.alert('警告', `服务器返回失败:${responseData.message}`, [ DialogUtils.showWarningMessage(
{text: '我知道了', style: 'cancel'}, '警告',
]); `服务器返回失败:${responseData.message}`,
'我知道了',
);
} }
} catch (error) { } catch (error) {
Alert.alert('请求发生错误', `请求服务器发生错误:${error}`, [ DialogUtils.showErrorMessage(
{text: '我知道了', style: 'cancel'}, '请求发生错误',
]); `请求服务器发生错误:${
error instanceof Error ? error.message : String(error)
}`,
'我知道了',
);
} finally { } finally {
setLoading(false); setLoading(false);
} }
}; };
return ( return (
<SafeAreaView style={[styles.container, {backgroundColor: theme.colors.background}]}> <SafeAreaView
style={[styles.container, {backgroundColor: theme.colors.background}]}>
{/* 头部导航栏 */} {/* 头部导航栏 */}
<LinearGradient <LinearGradient
colors={[theme.colors.primary, theme.colors.secondary, theme.colors.tertiary]} colors={theme.colors.gradients.primary}
start={{x: 0, y: 0}} start={{x: 0, y: 0}}
end={{x: 1, y: 0}} end={{x: 1, y: 0}}
style={styles.header}> style={styles.header}>
@ -95,7 +96,9 @@ export const StockInEmpty: React.FC = () => {
style={styles.backButton}> style={styles.backButton}>
<Icon name="arrow-back" size={24} color={theme.colors.background} /> <Icon name="arrow-back" size={24} color={theme.colors.background} />
</TouchableOpacity> </TouchableOpacity>
<Text style={[styles.headerTitle, {color: theme.colors.background}]}></Text> <Text style={[styles.headerTitle, {color: theme.colors.background}]}>
</Text>
<View style={styles.headerRight} /> <View style={styles.headerRight} />
</LinearGradient> </LinearGradient>
@ -108,23 +111,25 @@ export const StockInEmpty: React.FC = () => {
style={styles.waveSvg} style={styles.waveSvg}
preserveAspectRatio="none"> preserveAspectRatio="none">
<Path <Path
fill={`${theme.colors.tertiary}40`} fill={`${theme.colors.aqua}30`}
d="M0,96L48,112C96,128,192,160,288,186.7C384,213,480,235,576,213.3C672,192,768,128,864,128C960,128,1056,192,1152,213.3C1248,235,1344,213,1392,202.7L1440,192L1440,0L1392,0C1344,0,1248,0,1152,0C1056,0,960,0,864,0C768,0,672,0,576,0C480,0,384,0,288,0C192,0,96,0,48,0L0,0Z" d="M0,96L48,112C96,128,192,160,288,186.7C384,213,480,235,576,213.3C672,192,768,128,864,128C960,128,1056,192,1152,213.3C1248,235,1344,213,1392,202.7L1440,192L1440,0L1392,0C1344,0,1248,0,1152,0C1056,0,960,0,864,0C768,0,672,0,576,0C480,0,384,0,288,0C192,0,96,0,48,0L0,0Z"
/> />
</Svg> </Svg>
</View> </View>
{/* 主体内容 */} {/* 主体内容 */}
<View style={styles.content}> <View
<LinearGradient style={[
colors={theme.colors.gradients.contrast} styles.content,
style={StyleSheet.absoluteFill} {backgroundColor: theme.colors.backgroundGray},
start={{x: 0, y: 0}} ]}>
end={{x: 1, y: 0}}
/>
{/* 说明文字 */} {/* 说明文字 */}
<View style={[styles.infoSection, {backgroundColor: `${theme.colors.primary}15`}]}> <View
<Icon name="info-outline" size={24} color={theme.colors.primary} /> style={[
styles.infoSection,
{backgroundColor: `${theme.colors.aqua}15`},
]}>
<Icon name="info-outline" size={24} color={theme.colors.aqua} />
<Text style={[styles.infoText, {color: theme.colors.text}]}> <Text style={[styles.infoText, {color: theme.colors.text}]}>
</Text> </Text>
@ -135,11 +140,20 @@ export const StockInEmpty: React.FC = () => {
<Text style={[styles.inputLabel, {color: theme.colors.text}]}> <Text style={[styles.inputLabel, {color: theme.colors.text}]}>
<Text style={styles.required}>*</Text> <Text style={styles.required}>*</Text>
</Text> </Text>
<View style={[styles.inputWrapper, { <View
borderColor: theme.colors.border, style={[
backgroundColor: `${theme.colors.background}CC`, styles.inputWrapper,
}]}> {
<Icon name="qr-code-scanner" size={24} color={theme.colors.primary} style={styles.inputIcon} /> borderColor: theme.colors.border,
backgroundColor: `${theme.colors.background}CC`,
},
]}>
<Icon
name="qr-code-scanner"
size={24}
color={theme.colors.aqua}
style={styles.inputIcon}
/>
<TextInput <TextInput
style={[styles.input, {color: theme.colors.text}]} style={[styles.input, {color: theme.colors.text}]}
value={vehicleNo} value={vehicleNo}
@ -160,7 +174,7 @@ export const StockInEmpty: React.FC = () => {
{/* 提交按钮 */} {/* 提交按钮 */}
<LinearGradient <LinearGradient
colors={[theme.colors.primary, theme.colors.secondary]} colors={theme.colors.gradients.button}
start={{x: 0, y: 0}} start={{x: 0, y: 0}}
end={{x: 1, y: 0}} end={{x: 1, y: 0}}
style={[styles.submitButton, loading && styles.submitButtonDisabled]}> style={[styles.submitButton, loading && styles.submitButtonDisabled]}>
@ -172,8 +186,17 @@ export const StockInEmpty: React.FC = () => {
<ActivityIndicator color={theme.colors.background} /> <ActivityIndicator color={theme.colors.background} />
) : ( ) : (
<> <>
<Icon name="save" size={24} color={theme.colors.background} style={styles.submitIcon} /> <Icon
<Text style={[styles.submitButtonText, {color: theme.colors.background}]}> name="save"
size={24}
color={theme.colors.background}
style={styles.submitIcon}
/>
<Text
style={[
styles.submitButtonText,
{color: theme.colors.background},
]}>
</Text> </Text>
</> </>

View File

@ -6,7 +6,6 @@ import {
ScrollView, ScrollView,
StyleSheet, StyleSheet,
TouchableOpacity, TouchableOpacity,
Alert,
ActivityIndicator, ActivityIndicator,
} from 'react-native'; } from 'react-native';
import {useNavigation} from '@react-navigation/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 {useTheme} from '../../contexts/ThemeContext';
import LinearGradient from 'react-native-linear-gradient'; import LinearGradient from 'react-native-linear-gradient';
import Svg, {Path} from 'react-native-svg'; import Svg, {Path} from 'react-native-svg';
import {DialogUtils, MessageItem} from '../../utils';
interface PackageDataItem { interface PackageDataItem {
id: string; id: string;
@ -34,7 +34,7 @@ interface PackageDataItem {
lineLocationId: string; lineLocationId: string;
} }
const StockInWheelManual: React.FC = () => { export const StockInManual: React.FC = () => {
const theme = useTheme(); const theme = useTheme();
const navigation = useNavigation(); const navigation = useNavigation();
const [vehicleCode, setVehicleCode] = useState(''); const [vehicleCode, setVehicleCode] = useState('');
@ -42,9 +42,6 @@ const StockInWheelManual: React.FC = () => {
const [packageData, setPackageData] = useState<PackageDataItem[]>([]); const [packageData, setPackageData] = useState<PackageDataItem[]>([]);
const [packageDataId, setPackageDataId] = useState(0); const [packageDataId, setPackageDataId] = useState(0);
const [lastProcessedVehicleCode, setLastProcessedVehicleCode] = useState(''); const [lastProcessedVehicleCode, setLastProcessedVehicleCode] = useState('');
const [areaID, setAreaID] = useState('有卤');
const [status, setStatus] = useState('合格');
const [factory, setFactory] = useState('二厂');
const [loading, setLoading] = useState(false); const [loading, setLoading] = useState(false);
const vehicleInputRef = useRef<TextInput>(null); const vehicleInputRef = useRef<TextInput>(null);
@ -52,25 +49,30 @@ const StockInWheelManual: React.FC = () => {
const resolveVehicle = useCallback(() => { const resolveVehicle = useCallback(() => {
const code = vehicleCode.trim(); const code = vehicleCode.trim();
if (code.length === 0 || code === lastProcessedVehicleCode) {return;} if (code.length === 0 || code === lastProcessedVehicleCode) {
return;
}
if (code.length === 15) { if (code.length === 15) {
setLastProcessedVehicleCode(code); setLastProcessedVehicleCode(code);
Alert.alert('扫码成功', `绑定托盘: ${code}`); DialogUtils.showSuccessMessage('扫码成功', `绑定托盘: ${code}`);
} else { } else {
Alert.alert('绑定托盘失败', '无效的载具号长度'); DialogUtils.showErrorMessage('绑定托盘失败', '无效的载具号长度');
} }
}, [vehicleCode, lastProcessedVehicleCode]); }, [vehicleCode, lastProcessedVehicleCode]);
const resolveCode = useCallback(() => { const resolveCode = useCallback(() => {
if (!goodsCode) { if (!goodsCode) {
Alert.alert('警告', '条码文本框内无数据,请先扫描或者输入数据'); DialogUtils.showWarningMessage(
'警告',
'条码文本框内无数据,请先扫描或者输入数据',
);
return; return;
} }
const codeData = goodsCode.split(','); const codeData = goodsCode.split(',');
if (![6, 7, 8].includes(codeData.length)) { if (![6, 7, 8].includes(codeData.length)) {
Alert.alert('警告', '条码格式错误'); DialogUtils.showWarningMessage('警告', '条码格式错误');
return; return;
} }
@ -102,7 +104,7 @@ const StockInWheelManual: React.FC = () => {
setPackageData(prev => [...prev, newItem]); setPackageData(prev => [...prev, newItem]);
setPackageDataId(prev => prev + 1); setPackageDataId(prev => prev + 1);
} else { } else {
Alert.alert('警告', '该物料批次已存在,不能重复添加'); DialogUtils.showWarningMessage('警告', '该物料批次已存在,不能重复添加');
} }
setGoodsCode(''); setGoodsCode('');
@ -131,71 +133,57 @@ const StockInWheelManual: React.FC = () => {
}; };
const modifyNumber = (_id: string) => { const modifyNumber = (_id: string) => {
Alert.alert( DialogUtils.showConfirmMessage('修改数量', '请输入新的数量', {
'修改数量', cancelLabel: '取消',
'请输入新的数量', confirmLabel: '确定',
[ confirm: () => {
{text: '取消', style: 'cancel'}, // TODO: 需要实现一个自定义的输入对话框
{ DialogUtils.showMessage('提示', '此功能暂不可用,请稍后再试');
text: '确定', },
onPress: () => { });
// TODO: 需要实现一个自定义的输入对话框
Alert.alert('提示', '此功能暂不可用,请稍后再试');
},
},
],
);
}; };
const showDetails = (item: PackageDataItem) => { const showDetails = (item: PackageDataItem) => {
const message = [ const message: MessageItem[] = [
{label: '序号:', msg: item.id}, {label: '序号', msg: item.id},
{label: '采购单号:', msg: item.segment1}, {label: '采购单号', msg: item.segment1},
{label: '物料号:', msg: item.itemId}, {label: '物料号', msg: item.itemId},
{label: '批次号:', msg: item.batch}, {label: '批次号', msg: item.batch},
{label: '数量:', msg: item.quantity}, {label: '数量', msg: item.quantity},
{label: '重量:', msg: item.weight}, {label: '重量', msg: item.weight},
{label: '生产日期:', msg: item.productData}, {label: '生产日期', msg: item.productData},
]; ];
Alert.alert( DialogUtils.showMessageList('数据详情', message, '我知道了');
'数据详情',
message.map(m => `${m.label} ${m.msg}`).join('\n'),
[{text: '我知道了'}],
);
}; };
const wheelComplete = async () => { const wheelComplete = async () => {
if (packageData.length === 0) { if (packageData.length === 0) {
Alert.alert('警告', '您的码盘数据为空'); DialogUtils.showWarningMessage('警告', '您的码盘数据为空');
return; return;
} }
if (!vehicleCode.trim()) { if (!vehicleCode.trim()) {
Alert.alert('警告', '请先扫描载具号'); DialogUtils.showWarningMessage('警告', '请先扫描载具号');
return; return;
} }
const areaIDIndex = areaID === '有卤' ? 1 : 2; const areaIDIndex = 1; // 默认有卤
const factoryIndex = factory === '二厂' ? 2 : 3; const factoryIndex = 2; // 默认二厂
const statusIndex = { const statusIndex = 1; // 默认合格
合格: 1,
不合格: 2,
封存: 3,
待检: 4,
进口物料: 5,
}[status] || 1;
const confirm = await new Promise(resolve => const confirm = await new Promise<boolean>(resolve => {
Alert.alert( DialogUtils.showConfirmMessage(
'码盘完成', '码盘完成',
`载具:${vehicleCode} 码盘 ${packageData.length} 条数据,是否继续?`, `载具:${vehicleCode} 码盘 ${packageData.length} 条数据,是否继续?`,
[ {
{text: '取消', style: 'cancel', onPress: () => resolve(false)}, cancelLabel: '取消',
{text: '继续', onPress: () => resolve(true)}, confirmLabel: '继续',
], cancel: () => resolve(false),
), confirm: () => resolve(true),
); },
);
});
if (!confirm) return; if (!confirm) return;
@ -222,28 +210,40 @@ const StockInWheelManual: React.FC = () => {
setPackageDataId(0); setPackageDataId(0);
setVehicleCode(''); setVehicleCode('');
setPackageData([]); setPackageData([]);
Alert.alert('码盘成功', '', [{text: '我知道了'}]); DialogUtils.showSuccessMessage(
'码盘成功',
'码盘操作已完成',
'我知道了',
);
} else { } else {
Alert.alert('警告', `服务器返回失败:${data.message}`); DialogUtils.showWarningMessage(
'警告',
`服务器返回失败:${data.message}`,
);
} }
} catch (error) { } catch (error) {
Alert.alert('请求发生错误', `请求服务器发生错误:${error instanceof Error ? error.message : String(error)}`, [ DialogUtils.showErrorMessage(
{text: '我知道了', style: 'cancel'}, '请求发生错误',
]); `请求服务器发生错误:${
error instanceof Error ? error.message : String(error)
}`,
'我知道了',
);
} finally { } finally {
setLoading(false); setLoading(false);
} }
}; };
return ( return (
<View style={[styles.container, {backgroundColor: theme.colors.background}]}> <View
style={[styles.container, {backgroundColor: theme.colors.background}]}>
{/* 头部导航栏 */} {/* 头部导航栏 */}
<LinearGradient <LinearGradient
colors={[theme.colors.primary, theme.colors.secondary, theme.colors.tertiary]} colors={theme.colors.gradients.primary}
start={{x: 0, y: 0}} start={{x: 0, y: 0}}
end={{x: 1, y: 0}} end={{x: 1, y: 0}}
style={styles.header}> style={styles.header}>
<TouchableOpacity <TouchableOpacity
style={styles.backButton} style={styles.backButton}
onPress={() => navigation.goBack()}> onPress={() => navigation.goBack()}>
<Icon name="arrow-back" size={24} color={theme.colors.background} /> <Icon name="arrow-back" size={24} color={theme.colors.background} />
@ -263,23 +263,25 @@ const StockInWheelManual: React.FC = () => {
style={styles.waveSvg} style={styles.waveSvg}
preserveAspectRatio="none"> preserveAspectRatio="none">
<Path <Path
fill={`${theme.colors.tertiary}40`} fill={`${theme.colors.aqua}30`}
d="M0,96L48,112C96,128,192,160,288,186.7C384,213,480,235,576,213.3C672,192,768,128,864,128C960,128,1056,192,1152,213.3C1248,235,1344,213,1392,202.7L1440,192L1440,0L1392,0C1344,0,1248,0,1152,0C1056,0,960,0,864,0C768,0,672,0,576,0C480,0,384,0,288,0C192,0,96,0,48,0L0,0Z" d="M0,96L48,112C96,128,192,160,288,186.7C384,213,480,235,576,213.3C672,192,768,128,864,128C960,128,1056,192,1152,213.3C1248,235,1344,213,1392,202.7L1440,192L1440,0L1392,0C1344,0,1248,0,1152,0C1056,0,960,0,864,0C768,0,672,0,576,0C480,0,384,0,288,0C192,0,96,0,48,0L0,0Z"
/> />
</Svg> </Svg>
</View> </View>
<View style={styles.mainContent}> <View
<LinearGradient style={[
colors={theme.colors.gradients.contrast} styles.mainContent,
style={StyleSheet.absoluteFill} {backgroundColor: theme.colors.backgroundGray},
start={{x: 0, y: 0}} ]}>
end={{x: 1, y: 0}}
/>
<ScrollView contentContainerStyle={styles.scrollContent}> <ScrollView contentContainerStyle={styles.scrollContent}>
{/* 信息提示区 */} {/* 信息提示区 */}
<View style={[styles.infoSection, {backgroundColor: `${theme.colors.primary}15`}]}> <View
<Icon name="info-outline" size={24} color={theme.colors.primary} /> style={[
styles.infoSection,
{backgroundColor: `${theme.colors.aqua}15`},
]}>
<Icon name="info-outline" size={24} color={theme.colors.aqua} />
<Text style={[styles.infoText, {color: theme.colors.text}]}> <Text style={[styles.infoText, {color: theme.colors.text}]}>
</Text> </Text>
@ -287,12 +289,23 @@ const StockInWheelManual: React.FC = () => {
{/* 载具号输入区 */} {/* 载具号输入区 */}
<View style={styles.inputContainer}> <View style={styles.inputContainer}>
<Text style={[styles.label, {color: theme.colors.text}]}></Text> <Text style={[styles.label, {color: theme.colors.text}]}>
<View style={[styles.inputWrapper, {
borderColor: theme.colors.border, </Text>
backgroundColor: `${theme.colors.background}CC`, <View
}]}> style={[
<Icon name="qr-code-scanner" size={24} color={theme.colors.primary} style={styles.inputIcon} /> styles.inputWrapper,
{
borderColor: theme.colors.border,
backgroundColor: `${theme.colors.background}CC`,
},
]}>
<Icon
name="qr-code-scanner"
size={24}
color={theme.colors.aqua}
style={styles.inputIcon}
/>
<TextInput <TextInput
ref={vehicleInputRef} ref={vehicleInputRef}
style={[styles.input, {color: theme.colors.text}]} style={[styles.input, {color: theme.colors.text}]}
@ -305,7 +318,11 @@ const StockInWheelManual: React.FC = () => {
<TouchableOpacity <TouchableOpacity
onPress={() => setVehicleCode('')} onPress={() => setVehicleCode('')}
style={styles.clearButton}> style={styles.clearButton}>
<Icon name="cancel" size={20} color={theme.colors.textLight} /> <Icon
name="cancel"
size={20}
color={theme.colors.textLight}
/>
</TouchableOpacity> </TouchableOpacity>
)} )}
</View> </View>
@ -313,12 +330,23 @@ const StockInWheelManual: React.FC = () => {
{/* 条码输入区 */} {/* 条码输入区 */}
<View style={styles.inputContainer}> <View style={styles.inputContainer}>
<Text style={[styles.label, {color: theme.colors.text}]}></Text> <Text style={[styles.label, {color: theme.colors.text}]}>
<View style={[styles.inputWrapper, {
borderColor: theme.colors.border, </Text>
backgroundColor: `${theme.colors.background}CC`, <View
}]}> style={[
<Icon name="qr-code-2" size={24} color={theme.colors.primary} style={styles.inputIcon} /> styles.inputWrapper,
{
borderColor: theme.colors.border,
backgroundColor: `${theme.colors.background}CC`,
},
]}>
<Icon
name="qr-code-2"
size={24}
color={theme.colors.aqua}
style={styles.inputIcon}
/>
<TextInput <TextInput
ref={goodsInputRef} ref={goodsInputRef}
style={[styles.input, {color: theme.colors.text}]} style={[styles.input, {color: theme.colors.text}]}
@ -331,155 +359,55 @@ const StockInWheelManual: React.FC = () => {
<TouchableOpacity <TouchableOpacity
onPress={() => setGoodsCode('')} onPress={() => setGoodsCode('')}
style={styles.clearButton}> style={styles.clearButton}>
<Icon name="cancel" size={20} color={theme.colors.textLight} /> <Icon
name="cancel"
size={20}
color={theme.colors.textLight}
/>
</TouchableOpacity> </TouchableOpacity>
)} )}
</View> </View>
</View> </View>
{/* 选项区域 */}
<View style={styles.optionsContainer}>
{/* 区域选择 */}
<View style={styles.optionGroup}>
<Text style={[styles.optionLabel, {color: theme.colors.text}]}></Text>
<View style={styles.radioGroup}>
<TouchableOpacity
style={[
styles.radioButton,
{borderColor: theme.colors.border},
areaID === '有卤' && styles.radioButtonActive,
areaID === '有卤' && {backgroundColor: theme.colors.primary},
]}
onPress={() => setAreaID('有卤')}>
<Text
style={[
styles.radioText,
{color: theme.colors.text},
areaID === '有卤' && {color: theme.colors.background},
]}>
</Text>
</TouchableOpacity>
<TouchableOpacity
style={[
styles.radioButton,
{borderColor: theme.colors.border},
areaID === '无卤' && styles.radioButtonActive,
areaID === '无卤' && {backgroundColor: theme.colors.primary},
]}
onPress={() => setAreaID('无卤')}>
<Text
style={[
styles.radioText,
{color: theme.colors.text},
areaID === '无卤' && {color: theme.colors.background},
]}>
</Text>
</TouchableOpacity>
</View>
</View>
{/* 工厂选择 */}
<View style={styles.optionGroup}>
<Text style={[styles.optionLabel, {color: theme.colors.text}]}></Text>
<View style={styles.radioGroup}>
<TouchableOpacity
style={[
styles.radioButton,
{borderColor: theme.colors.border},
factory === '二厂' && styles.radioButtonActive,
factory === '二厂' && {backgroundColor: theme.colors.primary},
]}
onPress={() => setFactory('二厂')}>
<Text
style={[
styles.radioText,
{color: theme.colors.text},
factory === '二厂' && {color: theme.colors.background},
]}>
</Text>
</TouchableOpacity>
<TouchableOpacity
style={[
styles.radioButton,
{borderColor: theme.colors.border},
factory === '三厂' && styles.radioButtonActive,
factory === '三厂' && {backgroundColor: theme.colors.primary},
]}
onPress={() => setFactory('三厂')}>
<Text
style={[
styles.radioText,
{color: theme.colors.text},
factory === '三厂' && {color: theme.colors.background},
]}>
</Text>
</TouchableOpacity>
</View>
</View>
{/* 状态选择 - 改为与区域、工厂相同的样式 */}
<View style={styles.optionGroup}>
<Text style={[styles.optionLabel, {color: theme.colors.text}]}></Text>
<View style={styles.radioGroup}>
<ScrollView horizontal showsHorizontalScrollIndicator={false}>
<View style={styles.radioGroup}>
{['合格', '不合格', '封存', '待检', '进口物料'].map((item) => (
<TouchableOpacity
key={item}
style={[
styles.radioButton,
{borderColor: theme.colors.border},
status === item && styles.radioButtonActive,
status === item && {backgroundColor: theme.colors.primary},
]}
onPress={() => setStatus(item)}>
<Text
style={[
styles.radioText,
{color: theme.colors.text},
status === item && {color: theme.colors.background},
]}>
{item}
</Text>
</TouchableOpacity>
))}
</View>
</ScrollView>
</View>
</View>
</View>
{/* 操作按钮区 */} {/* 操作按钮区 */}
<View style={styles.buttonGroup}> <View style={styles.buttonGroup}>
<LinearGradient <LinearGradient
colors={[theme.colors.primary, theme.colors.secondary]} colors={theme.colors.gradients.button}
start={{x: 0, y: 0}} start={{x: 0, y: 0}}
end={{x: 1, y: 0}} end={{x: 1, y: 0}}
style={styles.button}> style={styles.button}>
<TouchableOpacity <TouchableOpacity
style={styles.buttonContent} style={styles.buttonContent}
onPress={resolveCode}> onPress={resolveCode}>
<Icon name="add-circle-outline" size={24} color={theme.colors.background} style={styles.buttonIcon} /> <Icon
<Text style={[styles.buttonText, {color: theme.colors.background}]}> name="add-circle-outline"
size={24}
color={theme.colors.background}
style={styles.buttonIcon}
/>
<Text
style={[styles.buttonText, {color: theme.colors.background}]}>
</Text> </Text>
</TouchableOpacity> </TouchableOpacity>
</LinearGradient> </LinearGradient>
<LinearGradient <LinearGradient
colors={[theme.colors.success, theme.colors.secondary]} colors={theme.colors.gradients.button}
start={{x: 0, y: 0}} start={{x: 0, y: 0}}
end={{x: 1, y: 0}} end={{x: 1, y: 0}}
style={styles.button}> style={styles.button}>
<TouchableOpacity <TouchableOpacity
style={styles.buttonContent} style={styles.buttonContent}
onPress={wheelComplete}> onPress={wheelComplete}>
<Icon name="check-circle-outline" size={24} color={theme.colors.background} style={styles.buttonIcon} /> <Icon
<Text style={[styles.buttonText, {color: theme.colors.background}]}> name="check-circle-outline"
size={24}
color={theme.colors.background}
style={styles.buttonIcon}
/>
<Text
style={[styles.buttonText, {color: theme.colors.background}]}>
</Text> </Text>
</TouchableOpacity> </TouchableOpacity>
@ -489,42 +417,65 @@ const StockInWheelManual: React.FC = () => {
{/* 物料列表 */} {/* 物料列表 */}
<View style={styles.tableContainer}> <View style={styles.tableContainer}>
<View style={styles.tableHeader}> <View style={styles.tableHeader}>
<Icon name="list-alt" size={24} color={theme.colors.primary} /> <Icon name="list-alt" size={24} color={theme.colors.aqua} />
<Text style={[styles.tableTitle, {color: theme.colors.text}]}> <Text style={[styles.tableTitle, {color: theme.colors.text}]}>
({packageData.length}) ({packageData.length})
</Text> </Text>
</View> </View>
{packageData.map((item, index) => ( {packageData.map(item => (
<View <View key={item.id} style={[styles.card]}>
key={item.id} <View
style={[styles.card]}> style={[
<LinearGradient StyleSheet.absoluteFill,
colors={theme.colors.gradients.contrast} {
style={[StyleSheet.absoluteFill, {borderRadius: 12}]} borderRadius: 12,
start={{x: 0, y: 0}} backgroundColor: theme.colors.background,
end={{x: 1, y: 0}} },
]}
/> />
{/* 卡片头部 */} {/* 卡片头部 */}
<View style={styles.cardHeader}> <View style={styles.cardHeader}>
<View style={styles.cardHeaderLeft}> <View style={styles.cardHeaderLeft}>
<Text style={[styles.cardHeaderText, {color: theme.colors.background}]}> <Text
style={[
styles.cardHeaderText,
{color: theme.colors.text},
]}>
{item.id} {item.id}
</Text> </Text>
<Text style={[styles.cardHeaderText, {color: theme.colors.background}]}> <Text
style={[
styles.cardHeaderText,
{color: theme.colors.text},
]}>
{item.segment1} {item.segment1}
</Text> </Text>
</View> </View>
<View style={styles.cardActions}> <View style={styles.cardActions}>
<TouchableOpacity <TouchableOpacity
style={[styles.cardActionButton, {backgroundColor: `${theme.colors.primary}15`}]} style={[
styles.cardActionButton,
{backgroundColor: `${theme.colors.aqua}15`},
]}
onPress={() => showDetails(item)}> onPress={() => showDetails(item)}>
<Icon name="info-outline" size={20} color={theme.colors.primary} /> <Icon
name="info-outline"
size={20}
color={theme.colors.aqua}
/>
</TouchableOpacity> </TouchableOpacity>
<TouchableOpacity <TouchableOpacity
style={[styles.cardActionButton, {backgroundColor: `${theme.colors.error}15`}]} style={[
styles.cardActionButton,
{backgroundColor: `${theme.colors.error}15`},
]}
onPress={() => deleteItem(item.id)}> onPress={() => deleteItem(item.id)}>
<Icon name="delete-outline" size={20} color={theme.colors.error} /> <Icon
name="delete-outline"
size={20}
color={theme.colors.error}
/>
</TouchableOpacity> </TouchableOpacity>
</View> </View>
</View> </View>
@ -533,37 +484,83 @@ const StockInWheelManual: React.FC = () => {
<View style={styles.cardContent}> <View style={styles.cardContent}>
<View style={styles.cardRow}> <View style={styles.cardRow}>
<View style={styles.cardField}> <View style={styles.cardField}>
<Text style={[styles.cardLabel, {color: theme.colors.background}]}></Text> <Text
<Text style={[styles.cardValue, {color: theme.colors.background}]}>{item.itemId}</Text> style={[
styles.cardLabel,
{color: theme.colors.textLight},
]}>
</Text>
<Text
style={[styles.cardValue, {color: theme.colors.text}]}>
{item.itemId}
</Text>
</View> </View>
<View style={styles.cardField}> <View style={styles.cardField}>
<Text style={[styles.cardLabel, {color: theme.colors.background}]}></Text> <Text
<Text style={[styles.cardValue, {color: theme.colors.background}]}>{item.batch}</Text> style={[
styles.cardLabel,
{color: theme.colors.textLight},
]}>
</Text>
<Text
style={[styles.cardValue, {color: theme.colors.text}]}>
{item.batch}
</Text>
</View> </View>
</View> </View>
<View style={styles.cardRow}> <View style={styles.cardRow}>
<View style={styles.cardField}> <View style={styles.cardField}>
<Text style={[styles.cardLabel, {color: theme.colors.background}]}></Text> <Text
style={[
styles.cardLabel,
{color: theme.colors.textLight},
]}>
</Text>
<TouchableOpacity <TouchableOpacity
style={styles.editableValue} style={styles.editableValue}
onPress={() => modifyNumber(item.id)}> onPress={() => modifyNumber(item.id)}>
<Text style={[styles.cardValue, {color: theme.colors.background}]}> <Text
style={[
styles.cardValue,
{color: theme.colors.text},
]}>
{item.quantity} {item.quantity}
</Text> </Text>
<Icon name="edit" size={16} color={theme.colors.background} /> <Icon name="edit" size={16} color={theme.colors.text} />
</TouchableOpacity> </TouchableOpacity>
</View> </View>
<View style={styles.cardField}> <View style={styles.cardField}>
<Text style={[styles.cardLabel, {color: theme.colors.background}]}></Text> <Text
<Text style={[styles.cardValue, {color: theme.colors.background}]}>{item.weight}</Text> style={[
styles.cardLabel,
{color: theme.colors.textLight},
]}>
</Text>
<Text
style={[styles.cardValue, {color: theme.colors.text}]}>
{item.weight}
</Text>
</View> </View>
</View> </View>
<View style={styles.cardRow}> <View style={styles.cardRow}>
<View style={styles.cardField}> <View style={styles.cardField}>
<Text style={[styles.cardLabel, {color: theme.colors.background}]}></Text> <Text
<Text style={[styles.cardValue, {color: theme.colors.background}]}>{item.productData}</Text> style={[
styles.cardLabel,
{color: theme.colors.textLight},
]}>
</Text>
<Text
style={[styles.cardValue, {color: theme.colors.text}]}>
{item.productData}
</Text>
</View> </View>
</View> </View>
</View> </View>
@ -574,9 +571,17 @@ const StockInWheelManual: React.FC = () => {
</View> </View>
{loading && ( {loading && (
<View style={[styles.loadingOverlay, {backgroundColor: 'rgba(0, 0, 0, 0.7)'}]}> <View
<View style={[styles.loadingCard, {backgroundColor: theme.colors.background}]}> style={[
<ActivityIndicator size="large" color={theme.colors.primary} /> styles.loadingOverlay,
{backgroundColor: 'rgba(0, 0, 0, 0.7)'},
]}>
<View
style={[
styles.loadingCard,
{backgroundColor: theme.colors.background},
]}>
<ActivityIndicator size="large" color={theme.colors.aqua} />
<Text style={[styles.loadingText, {color: theme.colors.text}]}> <Text style={[styles.loadingText, {color: theme.colors.text}]}>
... ...
</Text> </Text>
@ -675,36 +680,7 @@ const styles = StyleSheet.create({
clearButton: { clearButton: {
padding: 4, padding: 4,
}, },
optionsContainer: {
marginBottom: 20,
},
optionGroup: {
marginBottom: 16,
},
optionLabel: {
fontSize: 16,
marginBottom: 8,
fontWeight: '500',
},
radioGroup: {
flexDirection: 'row',
gap: 12,
},
radioButton: {
borderWidth: 1,
borderRadius: 8,
paddingVertical: 8,
paddingHorizontal: 16,
minWidth: 80,
alignItems: 'center',
},
radioButtonActive: {
borderColor: 'transparent',
},
radioText: {
fontSize: 14,
fontWeight: '500',
},
buttonGroup: { buttonGroup: {
flexDirection: 'row', flexDirection: 'row',
gap: 12, gap: 12,
@ -821,4 +797,4 @@ const styles = StyleSheet.create({
}, },
}); });
export default StockInWheelManual; export default StockInManual;

View File

@ -18,4 +18,4 @@ export interface DeviceInfo {
model: string; model: string;
osVersion: string; osVersion: string;
serialNumber: string; serialNumber: string;
} }

893
src/utils/DialogUtils.tsx Normal file
View File

@ -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<BeautifulDialogProps> = React.forwardRef(
(props, ref) => {
const [visible, setVisible] = useState(false);
const [config, setConfig] = useState<DialogConfig | null>(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 (
<Modal
visible={visible}
transparent
animationType="none"
statusBarTranslucent
onRequestClose={handleClose}>
<StatusBar
backgroundColor="rgba(0, 0, 0, 0.5)"
barStyle="light-content"
/>
<Animated.View style={[styles.overlay, {opacity: opacityAnim}]}>
<Animated.View
style={[styles.dialogContainer, {transform: [{scale: scaleAnim}]}]}>
{/* 头部图标区域 */}
<View
style={[
styles.iconContainer,
{backgroundColor: iconConfig.bgColor},
]}>
<View style={styles.iconWrapper}>
<Icon
name={iconConfig.name}
size={32}
color={iconConfig.color}
/>
</View>
</View>
{/* 内容区域 */}
<View style={styles.contentContainer}>
<Text style={styles.title}>{config.title}</Text>
{config.message && (
<Text style={styles.message}>{config.message}</Text>
)}
{config.messageList && (
<View style={styles.messageListContainer}>
{config.messageList.map((item, index) => (
<View key={index} style={styles.messageItem}>
<Text style={styles.messageLabel}>{item.label}</Text>
<Text style={styles.messageValue}>{item.msg}</Text>
</View>
))}
</View>
)}
</View>
{/* 按钮区域 */}
<View style={styles.buttonContainer}>
{config.type === 'confirm' ? (
<>
<TouchableOpacity
style={[styles.button, styles.cancelButton]}
onPress={handleCancel}>
<Text style={styles.cancelButtonText}>
{config.cancelText || '取消'}
</Text>
</TouchableOpacity>
<LinearGradient
colors={gradientColors}
start={{x: 0, y: 0}}
end={{x: 1, y: 0}}
style={[styles.button, styles.confirmButton]}>
<TouchableOpacity
style={styles.buttonTouchable}
onPress={handleConfirm}>
<Text style={styles.confirmButtonText}>
{config.confirmText || '确定'}
</Text>
</TouchableOpacity>
</LinearGradient>
</>
) : (
<LinearGradient
colors={gradientColors}
start={{x: 0, y: 0}}
end={{x: 1, y: 0}}
style={[styles.button, styles.singleButton]}>
<TouchableOpacity
style={styles.buttonTouchable}
onPress={handleConfirm}>
<Text style={styles.confirmButtonText}>
{config.confirmText || '确定'}
</Text>
</TouchableOpacity>
</LinearGradient>
)}
</View>
</Animated.View>
</Animated.View>
</Modal>
);
},
);
// 输入框弹窗组件
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<InputDialogProps> = ({
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 (
<Modal
visible={visible}
transparent
animationType="none"
statusBarTranslucent
onRequestClose={handleCancel}>
<StatusBar
backgroundColor="rgba(0, 0, 0, 0.5)"
barStyle="light-content"
/>
<Animated.View style={[styles.overlay, {opacity: opacityAnim}]}>
<Animated.View
style={[styles.dialogContainer, {transform: [{scale: scaleAnim}]}]}>
{/* 头部图标 */}
<View style={[styles.iconContainer, {backgroundColor: '#e6f7ff'}]}>
<View style={styles.iconWrapper}>
<Icon name="edit" size={32} color={theme.colors.cyan} />
</View>
</View>
{/* 内容区域 */}
<View style={styles.contentContainer}>
<Text style={styles.title}>{title}</Text>
{message && <Text style={styles.message}>{message}</Text>}
<View style={styles.inputContainer}>
<TextInput
style={styles.textInput}
placeholder={hintText}
placeholderTextColor="#999"
value={inputValue}
onChangeText={setInputValue}
maxLength={1000}
autoFocus
multiline={false}
/>
</View>
</View>
{/* 按钮区域 */}
<View style={styles.buttonContainer}>
<TouchableOpacity
style={[styles.button, styles.cancelButton]}
onPress={handleCancel}>
<Text style={styles.cancelButtonText}>{cancelLabel}</Text>
</TouchableOpacity>
<LinearGradient
colors={[theme.colors.cyan, theme.colors.aqua]}
start={{x: 0, y: 0}}
end={{x: 1, y: 0}}
style={[styles.button, styles.confirmButton]}>
<TouchableOpacity
style={styles.buttonTouchable}
onPress={handleConfirm}>
<Text style={styles.confirmButtonText}>{confirmLabel}</Text>
</TouchableOpacity>
</LinearGradient>
</View>
</Animated.View>
</Animated.View>
</Modal>
);
};
// 单选框弹窗组件
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<SingleSelectDialogProps> = ({
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 (
<Modal
visible={visible}
transparent
animationType="none"
statusBarTranslucent
onRequestClose={onCancel}>
<StatusBar
backgroundColor="rgba(0, 0, 0, 0.5)"
barStyle="light-content"
/>
<Animated.View style={[styles.overlay, {opacity: opacityAnim}]}>
<Animated.View
style={[
styles.dialogContainer,
styles.selectDialogContainer,
{transform: [{scale: scaleAnim}]},
]}>
{/* 头部 */}
<View style={styles.selectHeader}>
<View style={[styles.iconContainer, {backgroundColor: '#e6f7ff'}]}>
<View style={styles.iconWrapper}>
<Icon name="list" size={28} color={theme.colors.cyan} />
</View>
</View>
<View style={styles.headerContent}>
<Text style={styles.title}>{title}</Text>
<TouchableOpacity onPress={onCancel} style={styles.closeButton}>
<Icon name="close" size={24} color="#666" />
</TouchableOpacity>
</View>
</View>
{message && (
<View style={styles.messageContainer}>
<Text style={styles.message}>{message}</Text>
</View>
)}
{/* 选项列表 */}
<ScrollView
style={styles.optionsContainer}
showsVerticalScrollIndicator={false}>
{options.map((option, index) => (
<TouchableOpacity
key={index}
style={[
styles.optionItem,
selected === index && styles.selectedOptionItem,
]}
onPress={() => setSelected(index)}>
<View style={styles.optionContent}>
<View
style={[
styles.radioButton,
selected === index && styles.radioButtonSelected,
]}>
{selected === index && (
<View style={styles.radioButtonInner} />
)}
</View>
<Text
style={[
styles.optionText,
selected === index && styles.selectedOptionText,
]}>
{option}
</Text>
</View>
</TouchableOpacity>
))}
</ScrollView>
{/* 提交按钮 */}
<View style={styles.selectButtonContainer}>
<LinearGradient
colors={[theme.colors.cyan, theme.colors.aqua]}
start={{x: 0, y: 0}}
end={{x: 1, y: 0}}
style={styles.submitButton}>
<TouchableOpacity
style={styles.buttonTouchable}
onPress={handleSubmit}>
<Text style={styles.confirmButtonText}>{submitText}</Text>
</TouchableOpacity>
</LinearGradient>
</View>
</Animated.View>
</Animated.View>
</Modal>
);
};
// 全新的 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,
},
});

6
src/utils/index.ts Normal file
View File

@ -0,0 +1,6 @@
// 弹窗工具导出
export {DialogUtils, InputDialog, SingleSelectDialog} from './DialogUtils';
export type {MessageItem} from './DialogUtils';
// 存储工具导出
export * from './storage';

View File

@ -35,4 +35,4 @@ export const storage = {
console.error('Error clearing data', error); console.error('Error clearing data', error);
} }
}, },
}; };