Răsfoiți Sursa

骑手注册

panhui 5 ani în urmă
părinte
comite
6bf5d5cb7c

+ 17 - 19
.eslintrc.json

@@ -1,22 +1,20 @@
 {
-    "env": {
-        "browser": true,
-        "es2020": true,
-        "node": true
+  "env": {
+    "browser": true,
+    "es2020": true,
+    "node": true
+  },
+  "extends": ["prettier"],
+  "parser": "@typescript-eslint/parser",
+  "parserOptions": {
+    "ecmaFeatures": {
+      "jsx": true
     },
-    "extends": [
-        "alloy", // 都需要
-        "alloy/react", //react项目需要
-        "alloy/typescript" //ts项目需要
-    ],
-    "parser": "@typescript-eslint/parser",
-    "parserOptions": {
-        "ecmaFeatures": {
-            "jsx": true
-        },
-        "ecmaVersion": 11,
-        "sourceType": "module"
-    },
-    "plugins": ["prettier"],
-    "rules": {}
+    "ecmaVersion": 11,
+    "sourceType": "module"
+  },
+  "plugins": ["prettier"],
+  "rules": {
+    "prettier/prettier": "error"
+  }
 }

+ 0 - 36
.prettierrc.js

@@ -1,36 +0,0 @@
-// .prettierrc.js
-module.exports = {
-    // 一行最多 100 字符
-    printWidth: 100,
-    // 使用 4 个空格缩进
-    tabWidth: 4,
-    // 不使用缩进符,而使用空格
-    useTabs: false,
-    // 行尾需要有分号
-    semi: true,
-    // 使用单引号
-    singleQuote: true,
-    // 对象的 key 仅在必要时用引号
-    quoteProps: 'as-needed',
-    // jsx 不使用单引号,而使用双引号
-    jsxSingleQuote: false,
-    // 末尾不需要逗号
-    trailingComma: 'none',
-    // 大括号内的首尾需要空格
-    bracketSpacing: true,
-    // jsx 标签的反尖括号需要换行
-    jsxBracketSameLine: false,
-    // 箭头函数,只有一个参数的时候,也需要括号
-    arrowParens: 'always',
-    // 每个文件格式化的范围是文件的全部内容
-    rangeStart: 0,
-    rangeEnd: Infinity,
-    // 不需要写文件开头的 @prettier
-    requirePragma: false,
-    // 不需要自动在文件开头插入 @prettier
-    insertPragma: false,
-    // 使用默认的折行标准
-    proseWrap: 'preserve',
-    // 根据显示样式决定 html 要不要折行
-    htmlWhitespaceSensitivity: 'css'
-};

+ 7 - 0
.prettierrc.json

@@ -0,0 +1,7 @@
+{
+  "trailingComma": "es5",
+  "tabWidth": 2,
+  "semi": true,
+  "singleQuote": true,
+  "endOfLine": "auto"
+}

+ 5 - 1
.vscode/settings.json

@@ -1,5 +1,9 @@
 {
 	"eslint.options": {
 	
-	}
+	},
+	"i18n-ally.localesPaths": "locales",
+	"i18n-ally.keystyle": "flat",
+	"react-i18n.i18nPaths": "locales",
+	"i18n-ally.sourceLanguage": "zh-CN"
 }

+ 32 - 28
App.tsx

@@ -4,6 +4,7 @@ import * as SplashScreen from 'expo-splash-screen';
 import { SafeAreaProvider } from 'react-native-safe-area-context';
 import { ThemeProvider } from 'react-native-magnus';
 import { UseRequestProvider, useRequest } from 'ahooks';
+import './i18n';
 
 import useCachedResources from './hooks/useCachedResources';
 import useColorScheme from './hooks/useColorScheme';
@@ -13,37 +14,40 @@ import useModel from 'flooks';
 import theme from './constants/Theme';
 import request from './utils/RequestUtils';
 
-import User from './models/User';
+import User from './stores/User';
 
 export default function App() {
-    const isLoadingComplete = useCachedResources();
-    const colorScheme = useColorScheme();
+  const isLoadingComplete = useCachedResources();
+  const colorScheme = useColorScheme();
 
-    const { id, getInit, initialRouteName } = useModel(User, ['id', 'initialRouteName']);
+  const { id, getInit, initialRouteName } = useModel(User, [
+    'id',
+    'initialRouteName',
+  ]);
 
-    const { loading, run } = useRequest(getInit, {
-        refreshDeps: [id],
-        debounceInterval: 2000
-    });
+  const { loading, run } = useRequest(getInit, {
+    refreshDeps: [id],
+    debounceInterval: 2000,
+  });
 
-    if (!isLoadingComplete || !initialRouteName) {
-        return null;
-    } else {
-        SplashScreen.hideAsync();
-        return (
-            <UseRequestProvider
-                value={{
-                    refreshOnWindowFocus: true,
-                    requestMethod: request
-                }}
-            >
-                <ThemeProvider theme={theme}>
-                    <SafeAreaProvider>
-                        <Navigation colorScheme={colorScheme} />
-                        <StatusBar />
-                    </SafeAreaProvider>
-                </ThemeProvider>
-            </UseRequestProvider>
-        );
-    }
+  if (!isLoadingComplete || !initialRouteName) {
+    return null;
+  } else {
+    SplashScreen.hideAsync();
+    return (
+      <UseRequestProvider
+        value={{
+          refreshOnWindowFocus: true,
+          requestMethod: request,
+        }}
+      >
+        <ThemeProvider theme={theme}>
+          <SafeAreaProvider>
+            <Navigation colorScheme={colorScheme} />
+            <StatusBar />
+          </SafeAreaProvider>
+        </ThemeProvider>
+      </UseRequestProvider>
+    );
+  }
 }

BIN
assets/images/autoimg1.png


BIN
assets/images/logo.png


+ 4 - 4
babel.config.js

@@ -1,6 +1,6 @@
 module.exports = function (api) {
-    api.cache(true);
-    return {
-        presets: ['babel-preset-expo']
-    };
+  api.cache(true);
+  return {
+    presets: ['babel-preset-expo'],
+  };
 };

+ 98 - 96
components/EditScreenInfo.tsx

@@ -8,110 +8,112 @@ import { Text, View } from './Themed';
 import { useNavigation } from '@react-navigation/native';
 
 export default function EditScreenInfo({ path }: { path: string }) {
-    const navigation = useNavigation();
+  const navigation = useNavigation();
 
-    return (
-        <View>
-            <View style={styles.getStartedContainer}>
-                <Text
-                    style={styles.getStartedText}
-                    lightColor="rgba(0,0,0,0.8)"
-                    darkColor="rgba(255,255,255,0.8)"
-                >
-                    Open up the code for this screen:
-                </Text>
+  return (
+    <View>
+      <View style={styles.getStartedContainer}>
+        <Text
+          style={styles.getStartedText}
+          lightColor="rgba(0,0,0,0.8)"
+          darkColor="rgba(255,255,255,0.8)"
+        >
+          Open up the code for this screen:
+        </Text>
 
-                <View
-                    style={[styles.codeHighlightContainer, styles.homeScreenFilename]}
-                    darkColor="rgba(255,255,255,0.05)"
-                    lightColor="rgba(0,0,0,0.05)"
-                >
-                    <MonoText>{path}</MonoText>
-                </View>
+        <View
+          style={[styles.codeHighlightContainer, styles.homeScreenFilename]}
+          darkColor="rgba(255,255,255,0.05)"
+          lightColor="rgba(0,0,0,0.05)"
+        >
+          <MonoText>{path}</MonoText>
+        </View>
 
-                <Text
-                    style={styles.getStartedText}
-                    lightColor="rgba(0,0,0,0.8)"
-                    darkColor="rgba(255,255,255,0.8)"
-                >
-                    Change any of the text, save the file, and your app will automatically update.
-                </Text>
-            </View>
+        <Text
+          style={styles.getStartedText}
+          lightColor="rgba(0,0,0,0.8)"
+          darkColor="rgba(255,255,255,0.8)"
+        >
+          Change any of the text, save the file, and your app will automatically
+          update.
+        </Text>
+      </View>
 
-            <View style={styles.helpContainer}>
-                <TouchableOpacity
-                    onPress={() => navigation.navigate('Login')}
-                    style={styles.helpLink}
-                >
-                    <Text style={styles.helpLinkText} lightColor={Colors.light.tint}>
-                        Tap here if your app doesn't automatically update after making changes
-                    </Text>
-                </TouchableOpacity>
-            </View>
-        </View>
-    );
+      <View style={styles.helpContainer}>
+        <TouchableOpacity
+          onPress={() => navigation.navigate('Login')}
+          style={styles.helpLink}
+        >
+          <Text style={styles.helpLinkText} lightColor={Colors.light.tint}>
+            Tap here if your app doesn't automatically update after making
+            changes
+          </Text>
+        </TouchableOpacity>
+      </View>
+    </View>
+  );
 }
 
 function handleHelpPress() {
-    WebBrowser.openBrowserAsync(
-        'https://docs.expo.io/get-started/create-a-new-app/#opening-the-app-on-your-phonetablet'
-    );
+  WebBrowser.openBrowserAsync(
+    'https://docs.expo.io/get-started/create-a-new-app/#opening-the-app-on-your-phonetablet'
+  );
 }
 
 const styles = StyleSheet.create({
-    container: {
-        flex: 1,
-        backgroundColor: '#fff'
-    },
-    developmentModeText: {
-        marginBottom: 20,
-        fontSize: 14,
-        lineHeight: 19,
-        textAlign: 'center'
-    },
-    contentContainer: {
-        paddingTop: 30
-    },
-    welcomeContainer: {
-        alignItems: 'center',
-        marginTop: 10,
-        marginBottom: 20
-    },
-    welcomeImage: {
-        width: 100,
-        height: 80,
-        resizeMode: 'contain',
-        marginTop: 3,
-        marginLeft: -10
-    },
-    getStartedContainer: {
-        alignItems: 'center',
-        marginHorizontal: 50
-    },
-    homeScreenFilename: {
-        marginVertical: 7
-    },
-    codeHighlightText: {
-        color: 'rgba(96,100,109, 0.8)'
-    },
-    codeHighlightContainer: {
-        borderRadius: 3,
-        paddingHorizontal: 4
-    },
-    getStartedText: {
-        fontSize: 17,
-        lineHeight: 24,
-        textAlign: 'center'
-    },
-    helpContainer: {
-        marginTop: 15,
-        marginHorizontal: 20,
-        alignItems: 'center'
-    },
-    helpLink: {
-        paddingVertical: 15
-    },
-    helpLinkText: {
-        textAlign: 'center'
-    }
+  container: {
+    flex: 1,
+    backgroundColor: '#fff',
+  },
+  developmentModeText: {
+    marginBottom: 20,
+    fontSize: 14,
+    lineHeight: 19,
+    textAlign: 'center',
+  },
+  contentContainer: {
+    paddingTop: 30,
+  },
+  welcomeContainer: {
+    alignItems: 'center',
+    marginTop: 10,
+    marginBottom: 20,
+  },
+  welcomeImage: {
+    width: 100,
+    height: 80,
+    resizeMode: 'contain',
+    marginTop: 3,
+    marginLeft: -10,
+  },
+  getStartedContainer: {
+    alignItems: 'center',
+    marginHorizontal: 50,
+  },
+  homeScreenFilename: {
+    marginVertical: 7,
+  },
+  codeHighlightText: {
+    color: 'rgba(96,100,109, 0.8)',
+  },
+  codeHighlightContainer: {
+    borderRadius: 3,
+    paddingHorizontal: 4,
+  },
+  getStartedText: {
+    fontSize: 17,
+    lineHeight: 24,
+    textAlign: 'center',
+  },
+  helpContainer: {
+    marginTop: 15,
+    marginHorizontal: 20,
+    alignItems: 'center',
+  },
+  helpLink: {
+    paddingVertical: 15,
+  },
+  helpLinkText: {
+    textAlign: 'center',
+  },
 });

+ 98 - 0
components/ImagePicker.tsx

@@ -0,0 +1,98 @@
+/* eslint-disable no-underscore-dangle */
+import * as React from 'react';
+import { Platform, View } from 'react-native';
+import { Button, Icon, Image, Div } from 'react-native-magnus';
+import * as ImagePicker from 'expo-image-picker';
+import useModel from 'flooks';
+
+import request from '../utils/RequestUtils';
+import { toastShow, toastHide } from '../utils/SystemUtils';
+
+const _pickImage = (aspect) => {
+  return ImagePicker.requestCameraRollPermissionsAsync()
+    .then((res) => {
+      if (!res.granted) {
+        return Promise.reject('notAllod');
+      } else {
+        return ImagePicker.launchImageLibraryAsync({
+          mediaTypes: ImagePicker.MediaTypeOptions.Images,
+          allowsEditing: true,
+          aspect: aspect || [1, 1],
+          quality: 0.6,
+          base64: true,
+        });
+      }
+    })
+    .then((res) => {
+      if (!res.cancelled) {
+        if (Platform.OS === 'web') {
+          toastShow();
+          return Promise.resolve(res.uri);
+        }
+        return Promise.resolve(res.base64);
+      } else {
+        return Promise.reject('cancel');
+      }
+    })
+    .then((img) => {
+      return request.post(`/upload/base64`, {
+        data: { base64: img },
+        requestType: 'form',
+      });
+    });
+};
+
+export default function ImagePickerCom(props) {
+  const { aspect, img, setImg, cancelEvent, h, w } = props;
+
+  return (
+    <Div mr="sm" mb="sm">
+      <Button
+        bg="yellow200"
+        h={h || 67}
+        w={w || 67}
+        rounded="xs"
+        onPress={() => {
+          _pickImage(aspect || [1, 1])
+            .then((newImg) => {
+              if (setImg) {
+                setImg(newImg);
+              }
+            })
+            .catch((e) => {
+              console.log(e);
+            })
+            .finally(() => {
+              toastHide();
+            });
+        }}
+      >
+        {!img && <Icon name="plus" color="white" fontSize="4xl" />}
+        {!!img && (
+          <Image
+            h={67}
+            w={67}
+            rounded="xs"
+            source={{
+              uri: img,
+            }}
+          />
+        )}
+      </Button>
+      {cancelEvent && (
+        <Button
+          bg="red500"
+          h={16}
+          w={16}
+          rounded="circle"
+          position="absolute"
+          right={-5}
+          top={-5}
+          onPress={cancelEvent}
+        >
+          <Icon name="minus" fontFamily="AntDesign" color="#fff" />
+        </Button>
+      )}
+    </Div>
+  );
+}

+ 173 - 172
constants/Theme.ts

@@ -1,178 +1,179 @@
 export default {
-    colors: {
-        gray900: '#262626',
-        gray800: '#404040',
-        gray700: '#595959',
-        gray600: '#737373',
-        gray500: '#8c8c8c',
-        gray400: '#a6a6a6',
-        gray300: '#bfbfbf',
-        gray200: '#d9d9d9',
-        gray100: '#f2f2f2',
-
-        brand900: '#420a2b',
-        brand800: '#6e1147',
-        brand700: '#9a1864',
-        brand600: '#c61f80',
-        brand500: '#e0399a',
-        brand400: '#e765b0',
-        brand300: '#ee91c7',
-        brand200: '#f5bddd',
-        brand100: '#fce9f4',
-
-        red100: '#fce8ea',
-        red200: '#f7bac1',
-        red300: '#f28c98',
-        red400: '#ed5e6f',
-        red500: '#e83045',
-        red600: '#cf172c',
-        red700: '#a11222',
-        red800: '#730d18',
-        red900: '#45080f',
-
-        yellow100: '#fef9e6',
-        yellow200: '#FFF2C7',
-        yellow300: '#fae084',
-        yellow400: '#f8d453',
-        yellow500: '#FFC21C',
-        yellow600: '#ddae09',
-        yellow700: '#ac8707',
-        yellow800: '#7b6005',
-        yellow900: '#4a3a03',
-
-        green100: '#eefbe9',
-        green200: '#cdf4bd',
-        green300: '#aced91',
-        green400: '#8be665',
-        green500: '#69df39',
-        green600: '#50c620',
-        green700: '#3e9a19',
-        green800: '#2c6e12',
-        green900: '#1b420b',
-
-        blue100: '#e8f2fc',
-        blue200: '#bad7f7',
-        blue300: '#8cbcf2',
-        blue400: '#5ea1ed',
-        blue500: '#3087e8',
-        blue600: '#176dcf',
-        blue700: '#1255a1',
-        blue800: '#0d3d73',
-        blue900: '#082445',
-
-        teal100: '#e8fdfc',
-        teal200: '#b9f8f5',
-        teal300: '#8bf4ee',
-        teal400: '#5cefe7',
-        teal500: '#2eebe0',
-        teal600: '#14d1c6',
-        teal700: '#10a39a',
-        teal800: '#0b746e',
-        teal900: '#074642',
-
-        white: '#FFFFFF',
-        black: '#000000',
-        transparent: 'rgba(0,0,0,0)'
+  colors: {
+    gray900: '#262626',
+    gray800: '#404040',
+    gray700: '#595959',
+    gray600: '#737373',
+    gray500: '#8c8c8c',
+    gray400: '#a6a6a6',
+    gray300: '#bfbfbf',
+    gray200: '#d9d9d9',
+    gray100: '#f2f2f2',
+
+    brand900: '#420a2b',
+    brand800: '#6e1147',
+    brand700: '#9a1864',
+    brand600: '#c61f80',
+    brand500: '#e0399a',
+    brand400: '#e765b0',
+    brand300: '#ee91c7',
+    brand200: '#f5bddd',
+    brand100: '#fce9f4',
+
+    red100: '#fce8ea',
+    red200: '#f7bac1',
+    red300: '#f28c98',
+    red400: '#ed5e6f',
+    red500: '#e83045',
+    red600: '#cf172c',
+    red700: '#a11222',
+    red800: '#730d18',
+    red900: '#45080f',
+
+    yellow100: '#fef9e6',
+    yellow200: '#FFF2C7',
+    yellow300: '#fae084',
+    yellow400: '#f8d453',
+    yellow500: '#FFC21C',
+    yellow600: '#ddae09',
+    yellow700: '#ac8707',
+    yellow800: '#7b6005',
+    yellow900: '#4a3a03',
+
+    green100: '#eefbe9',
+    green200: '#cdf4bd',
+    green300: '#aced91',
+    green400: '#8be665',
+    green500: '#69df39',
+    green600: '#50c620',
+    green700: '#3e9a19',
+    green800: '#2c6e12',
+    green900: '#1b420b',
+
+    blue100: '#e8f2fc',
+    blue200: '#bad7f7',
+    blue300: '#8cbcf2',
+    blue400: '#5ea1ed',
+    blue500: '#3087e8',
+    blue600: '#176dcf',
+    blue700: '#1255a1',
+    blue800: '#0d3d73',
+    blue900: '#082445',
+
+    teal100: '#e8fdfc',
+    teal200: '#b9f8f5',
+    teal300: '#8bf4ee',
+    teal400: '#5cefe7',
+    teal500: '#2eebe0',
+    teal600: '#14d1c6',
+    teal700: '#10a39a',
+    teal800: '#0b746e',
+    teal900: '#074642',
+
+    white: '#FFFFFF',
+    black: '#000000',
+    transparent: 'rgba(0,0,0,0)',
+    black600: 'rgba(0,0,0,0.6)',
+  },
+
+  fontSize: {
+    xs: 10,
+    sm: 12,
+    md: 14,
+    lg: 15,
+    xl: 16,
+    '2xl': 19,
+    '3xl': 21,
+    '4xl': 24,
+    '5xl': 27,
+    '6xl': 32,
+  },
+
+  shadowColor: '#000',
+
+  shadow: {
+    none: {},
+    xs: {
+      shadowOffset: {
+        width: 0,
+        height: 1,
+      },
+      shadowOpacity: 0.18,
+      shadowRadius: 1.0,
+
+      elevation: 1,
     },
-
-    fontSize: {
-        xs: 10,
-        sm: 12,
-        md: 14,
-        lg: 15,
-        xl: 16,
-        '2xl': 19,
-        '3xl': 21,
-        '4xl': 24,
-        '5xl': 27,
-        '6xl': 32
+    sm: {
+      shadowOffset: {
+        width: 0,
+        height: 2,
+      },
+      shadowOpacity: 0.23,
+      shadowRadius: 2.62,
+
+      elevation: 4,
     },
-
-    shadowColor: '#000',
-
-    shadow: {
-        none: {},
-        xs: {
-            shadowOffset: {
-                width: 0,
-                height: 1
-            },
-            shadowOpacity: 0.18,
-            shadowRadius: 1.0,
-
-            elevation: 1
-        },
-        sm: {
-            shadowOffset: {
-                width: 0,
-                height: 2
-            },
-            shadowOpacity: 0.23,
-            shadowRadius: 2.62,
-
-            elevation: 4
-        },
-        md: {
-            shadowOffset: {
-                width: 0,
-                height: 4
-            },
-            shadowOpacity: 0.3,
-            shadowRadius: 4.65,
-
-            elevation: 8
-        },
-        lg: {
-            shadowOffset: {
-                width: 0,
-                height: 6
-            },
-            shadowOpacity: 0.37,
-            shadowRadius: 7.49,
-
-            elevation: 12
-        },
-        xl: {
-            shadowOffset: {
-                width: 0,
-                height: 8
-            },
-            shadowOpacity: 0.44,
-            shadowRadius: 10.32,
-
-            elevation: 16
-        },
-        '2xl': {
-            shadowOffset: {
-                width: 0,
-                height: 10
-            },
-            shadowOpacity: 0.51,
-            shadowRadius: 13.16,
-
-            elevation: 20
-        }
+    md: {
+      shadowOffset: {
+        width: 0,
+        height: 4,
+      },
+      shadowOpacity: 0.3,
+      shadowRadius: 4.65,
+
+      elevation: 8,
     },
-
-    borderRadius: {
-        none: 0,
-        xs: 2,
-        sm: 4,
-        md: 6,
-        lg: 8,
-        xl: 12,
-        '2xl': 16,
-        circle: 1000000
+    lg: {
+      shadowOffset: {
+        width: 0,
+        height: 6,
+      },
+      shadowOpacity: 0.37,
+      shadowRadius: 7.49,
+
+      elevation: 12,
     },
-
-    spacing: {
-        none: 0,
-        xs: 4,
-        sm: 6,
-        md: 8,
-        lg: 12,
-        xl: 24,
-        '2xl': 32,
-        '3xl': 64
-    }
+    xl: {
+      shadowOffset: {
+        width: 0,
+        height: 8,
+      },
+      shadowOpacity: 0.44,
+      shadowRadius: 10.32,
+
+      elevation: 16,
+    },
+    '2xl': {
+      shadowOffset: {
+        width: 0,
+        height: 10,
+      },
+      shadowOpacity: 0.51,
+      shadowRadius: 13.16,
+
+      elevation: 20,
+    },
+  },
+
+  borderRadius: {
+    none: 0,
+    xs: 2,
+    sm: 4,
+    md: 6,
+    lg: 8,
+    xl: 12,
+    '2xl': 16,
+    circle: 1000000,
+  },
+
+  spacing: {
+    none: 0,
+    xs: 4,
+    sm: 6,
+    md: 8,
+    lg: 12,
+    xl: 24,
+    '2xl': 32,
+    '3xl': 64,
+  },
 };

+ 34 - 0
i18n.ts

@@ -0,0 +1,34 @@
+import i18n from 'i18next';
+import * as Localization from 'expo-localization';
+import { initReactI18next } from 'react-i18next';
+import en from './locales/en.json';
+import zh from './locales/zh-CN.json';
+import th from './locales/th.json';
+
+const resources = {
+  zh: {
+    translation: zh,
+  },
+  en: {
+    translation: en,
+  },
+  th: {
+    translation: th,
+  },
+};
+
+i18n
+  .use(initReactI18next) // passes i18n down to react-i18next
+  .init({
+    resources,
+    lng: Localization.locale,
+    fallbackLng: 'zh-CN', // use en if detected lng is not available
+
+    keySeparator: false, // we do not use keys in form messages.welcome
+
+    interpolation: {
+      escapeValue: false, // react already safes from xss
+    },
+  });
+
+export default i18n;

+ 65 - 0
locales/en.json

@@ -0,0 +1,65 @@
+{
+  "welcom": "Welcome to use Dingdong takeaway platform",
+  "login_form_1": "phone number",
+  "login_pla_1": "Enter merchant phone number",
+  "psd": "password",
+  "deng-lu": "log in",
+  "qi-shou-zhu-ce": "Rider registration",
+  "shou-ji-hao": "phone number",
+  "shu-ru-mi-ma": "enter password",
+  "wang-ji-mi-ma": "forget password",
+  "shu-ru-yan-zheng-ma": "Enter confirmation code",
+  "shu-ru-shang-jia-ming-cheng": "Enter business name",
+  "shu-ru-shou-ji-hao": "input mobile phone number",
+  "yan-zheng-ma-deng-lu": "Verification code login",
+  "yan-zheng-ma": "Captcha",
+  "mi-ma-deng-lu": "Password login",
+  "mi-ma": "password",
+  "dian-dong-che": "Electric car",
+  "ding-dong-wai-mai-qi-shou-yin-si-zheng-ce": "\"Privacy Policy for Ding Dong Takeaway Riders\"",
+  "ding-dong-wai-mai-qi-shou-zhu-ce-xie-yi": "\"Dingdong Takeaway Rider Registration Agreement\"",
+  "failresult": "Sorry, the verification information you submitted failed",
+  "failresult2": "You can re-apply for riders to settle in!",
+  "gong-zuo-di-dian": "work place",
+  "guide1": "Welcome to join the Dingdong family",
+  "guide2": "I believe that on the Dingdong platform, you will definitely gain a lot.",
+  "he": "with",
+  "jia-shi-zheng-zheng-fan-mian": "Front and back of driver's license",
+  "jia-zai-zhong": "Loading",
+  "jiao-tong-gong-ju-zhao-pian": "Vehicle photos",
+  "li-ji-bo-da": "Call now",
+  "lian-xi-ke-fu": "Contact Customer Service",
+  "mei-you": "No",
+  "mo-tuo-che": "motorcycle",
+  "nin-de-shen-qing-yi-ti-jiao-cheng-gong": "Your application has been submitted successfully",
+  "qi-shou-de-gong-zuo-qu-yu-wei-yi-gong-zuo-di-dian-ban-jing-3km-de-qu-yu": "The working area of ​​the rider is an area with a radius of 3km from the working place",
+  "qi-shou-ke-hu-duan": "Rider client",
+  "qi-shou-shen-he": "Rider review",
+  "qi-ta": "other",
+  "qing-nai-xin-deng-dai": "please wait patiently",
+  "qing-xuan-ze-di-dian": "Please choose a location",
+  "qu-xiao": "cancel",
+  "que-ren": "confirm",
+  "que-ren-mi-ma": "confirm password",
+  "shen-fen-ren-zheng-xin-xi": "Identity authentication information",
+  "shen-fen-zheng-hao": "ID number",
+  "shen-fen-zheng-zheng-fan-mian": "ID card front and back",
+  "shou-chi-shen-fen-zheng": "Holding ID card",
+  "shu-ru-xing-ming": "Enter name",
+  "shu-ru-zheng-jian-hao-ma": "Enter ID number",
+  "shuo-ming": "Explanation",
+  "song-can-jiao-tong-gong-ju": "Food delivery vehicles",
+  "song-can-jiao-tong-gong-ju-xin-xi": "Food delivery transportation information",
+  "ti-jiao-shen-he": "Submit review",
+  "ti-shi": "prompt",
+  "tips1": "Make sure to call customer service",
+  "xia-yi-bu": "Next step",
+  "xuan-ze-di-dian": "Choose a location",
+  "xuan-ze-gong-zuo-di-dian": "Choose work location",
+  "yi-jing-yue-du-bing-tong-yi": "Have read and agreed",
+  "you": "Have",
+  "you-wu-jia-shi-zheng": "Driver's license",
+  "zai-ci-shu-ru-que-ren-mi-ma": "Enter the confirm password again",
+  "zhen-shi-xing-ming": "actual name",
+  "zhong-xin-shen-qing": "Re-apply"
+}

+ 65 - 0
locales/th.json

@@ -0,0 +1,65 @@
+{
+  "welcom": "ยินดีต้อนรับสู่แพลตฟอร์มDing dong",
+  "login_form_1": "หมายเลขโทรศัพท์",
+  "login_pla_1": "ใส่หมายเลขโทรศัพท์",
+  "psd": "รหัสผ่าน",
+  "deng-lu": "เข้าสู่ระบบ",
+  "qi-shou-zhu-ce": "การลงทะเบียนผู้ขับขี่",
+  "shou-ji-hao": "หมายเลขโทรศัพท์",
+  "shu-ru-mi-ma": "ใส่รหัสผ่าน",
+  "wang-ji-mi-ma": "ลืมรหัสผ่าน",
+  "shu-ru-yan-zheng-ma": "ใส่รหัสยืนยัน",
+  "shu-ru-shang-jia-ming-cheng": "ป้อนชื่อธุรกิจ",
+  "shu-ru-shou-ji-hao": "ใส่หมายเลขโทรศัพท์มือถือ",
+  "yan-zheng-ma-deng-lu": "รหัสยืนยัน",
+  "yan-zheng-ma": "แจ้งลบความคิดเห็น",
+  "mi-ma-deng-lu": "รหัสผ่านเข้าสู่ระบบ",
+  "mi-ma": "รหัสผ่าน",
+  "dian-dong-che": "รถยนต์ไฟฟ้า",
+  "ding-dong-wai-mai-qi-shou-yin-si-zheng-ce": "\"นโยบายความเป็นส่วนตัวสำหรับนักเดินทางที่ต้องเดินทางอย่างจุงตง\"",
+  "ding-dong-wai-mai-qi-shou-zhu-ce-xie-yi": "\"ข้อตกลงการลงทะเบียนขับขี่ Dongdong Takeaway\"",
+  "failresult": "ขออภัยข้อมูลการยืนยันที่คุณส่งมาล้มเหลว",
+  "failresult2": "คุณสามารถสมัครใหม่เพื่อให้ผู้ขับขี่ลงหลักปักฐานได้!",
+  "gong-zuo-di-dian": "สถานที่ทำงาน",
+  "guide1": "ยินดีต้อนรับสู่เข้าร่วมตระกูล Dingdong",
+  "guide2": "ฉันเชื่อว่าบนแพลตฟอร์ม Dingdong คุณจะได้รับมากแน่นอน",
+  "he": "กับ",
+  "jia-shi-zheng-zheng-fan-mian": "ด้านหน้าและด้านหลังใบขับขี่",
+  "jia-zai-zhong": "กำลังโหลด",
+  "jiao-tong-gong-ju-zhao-pian": "รูปถ่ายยานพาหนะ",
+  "li-ji-bo-da": "โทรเลย",
+  "lian-xi-ke-fu": "ติดต่อฝ่ายบริการลูกค้า",
+  "mei-you": "ไม่",
+  "mo-tuo-che": "รถจักรยานยนต์",
+  "nin-de-shen-qing-yi-ti-jiao-cheng-gong": "ส่งใบสมัครของคุณเรียบร้อยแล้ว",
+  "qi-shou-de-gong-zuo-qu-yu-wei-yi-gong-zuo-di-dian-ban-jing-3km-de-qu-yu": "พื้นที่ทำงานของผู้ขับขี่เป็นพื้นที่ที่มีรัศมี 3 กม. จากสถานที่ทำงาน",
+  "qi-shou-ke-hu-duan": "ลูกค้าไรเดอร์",
+  "qi-shou-shen-he": "รีวิวไรเดอร์",
+  "qi-ta": "อื่น ๆ",
+  "qing-nai-xin-deng-dai": "โปรดอดใจรอ",
+  "qing-xuan-ze-di-dian": "กรุณาเลือกสถานที่",
+  "qu-xiao": "ยกเลิก",
+  "que-ren": "ยืนยัน",
+  "que-ren-mi-ma": "ยืนยันรหัสผ่าน",
+  "shen-fen-ren-zheng-xin-xi": "ข้อมูลการรับรองความถูกต้องของข้อมูลประจำตัว",
+  "shen-fen-zheng-hao": "หมายเลขประจำตัว",
+  "shen-fen-zheng-zheng-fan-mian": "บัตรประชาชนด้านหน้าและด้านหลัง",
+  "shou-chi-shen-fen-zheng": "ถือบัตรประชาชน",
+  "shu-ru-xing-ming": "ใส่ชื่อ",
+  "shu-ru-zheng-jian-hao-ma": "ป้อนหมายเลข ID",
+  "shuo-ming": "คำอธิบาย",
+  "song-can-jiao-tong-gong-ju": "ยานพาหนะส่งอาหาร",
+  "song-can-jiao-tong-gong-ju-xin-xi": "ข้อมูลการขนส่งการจัดส่งอาหาร",
+  "ti-jiao-shen-he": "ส่งคำวิจารณ์",
+  "ti-shi": "รวดเร็ว",
+  "tips1": "ตรวจสอบให้แน่ใจว่าได้ติดต่อฝ่ายบริการลูกค้า",
+  "xia-yi-bu": "ขั้นตอนต่อไป",
+  "xuan-ze-di-dian": "เลือกสถานที่",
+  "xuan-ze-gong-zuo-di-dian": "เลือกสถานที่ทำงาน",
+  "yi-jing-yue-du-bing-tong-yi": "ได้อ่านและตกลง",
+  "you": "มี",
+  "you-wu-jia-shi-zheng": "ใบขับขี่",
+  "zai-ci-shu-ru-que-ren-mi-ma": "ป้อนรหัสผ่านยืนยันอีกครั้ง",
+  "zhen-shi-xing-ming": "ชื่อจริง",
+  "zhong-xin-shen-qing": "ทาซ้ำ"
+}

+ 65 - 0
locales/zh-CN.json

@@ -0,0 +1,65 @@
+{
+  "welcom": "欢迎使⽤用叮咚外卖平台",
+  "login_form_1": "手机号",
+  "login_pla_1": "输入商家⼿手机号",
+  "psd": "密码",
+  "yan-zheng-ma-deng-lu": "验证码登录",
+  "shou-ji-hao": "手机号",
+  "shu-ru-shang-jia-ming-cheng": "输入商家名称",
+  "shu-ru-mi-ma": "输入密码",
+  "wang-ji-mi-ma": "忘记密码",
+  "qi-shou-zhu-ce": "骑手注册",
+  "deng-lu": "登录",
+  "shu-ru-shou-ji-hao": "输入手机号",
+  "yan-zheng-ma": "验证码",
+  "shu-ru-yan-zheng-ma": "输入验证码",
+  "mi-ma": "密码",
+  "mi-ma-deng-lu": "密码登录",
+  "qi-shou-ke-hu-duan": "骑手客户端",
+  "guide1": "欢迎加入叮咚的大家庭",
+  "guide2": "相信在叮咚平台上, 您一定会有很大的收获。",
+  "que-ren-mi-ma": "确认密码",
+  "zai-ci-shu-ru-que-ren-mi-ma": "再次输入确认密码",
+  "yi-jing-yue-du-bing-tong-yi": "已经阅读并同意",
+  "ding-dong-wai-mai-qi-shou-zhu-ce-xie-yi": "《叮咚外卖骑手注册协议》",
+  "he": "和",
+  "ding-dong-wai-mai-qi-shou-yin-si-zheng-ce": "《叮咚外卖骑手隐私政策》",
+  "xia-yi-bu": "下一步",
+  "lian-xi-ke-fu": "联系客服",
+  "ti-shi": "提示",
+  "qu-xiao": "取消",
+  "que-ren": "确认",
+  "tips1": "确定要拨打客服电话",
+  "li-ji-bo-da": "立即拨打",
+  "jia-zai-zhong": "加载中",
+  "mo-tuo-che": "摩托车",
+  "dian-dong-che": "电动车",
+  "qi-ta": "其他",
+  "mei-you": "没有",
+  "zhen-shi-xing-ming": "真实姓名",
+  "shu-ru-xing-ming": "输入姓名",
+  "shen-fen-zheng-hao": "身份证号",
+  "shu-ru-zheng-jian-hao-ma": "输入证件号码",
+  "shen-fen-zheng-zheng-fan-mian": "身份证正反面",
+  "shou-chi-shen-fen-zheng": "手持身份证",
+  "song-can-jiao-tong-gong-ju": "送餐交通工具",
+  "jiao-tong-gong-ju-zhao-pian": "交通工具照片",
+  "you-wu-jia-shi-zheng": "有无驾驶证",
+  "you": "有",
+  "jia-shi-zheng-zheng-fan-mian": "驾驶证正反面",
+  "gong-zuo-di-dian": "工作地点",
+  "qing-xuan-ze-di-dian": "请选择地点",
+  "shuo-ming": "说明",
+  "qi-shou-de-gong-zuo-qu-yu-wei-yi-gong-zuo-di-dian-ban-jing-3km-de-qu-yu": "骑手的工作区域为以工作地点半径3km的区域",
+  "ti-jiao-shen-he": "提交审核",
+  "nin-de-shen-qing-yi-ti-jiao-cheng-gong": "您的申请已提交成功",
+  "qing-nai-xin-deng-dai": "请耐心等待",
+  "failresult": "很抱歉,您提交的认证信息审核未通过",
+  "failresult2": "您可重新申请骑手入驻!",
+  "zhong-xin-shen-qing": "重新申请",
+  "xuan-ze-di-dian": "选择地点",
+  "qi-shou-shen-he": "骑手审核",
+  "xuan-ze-gong-zuo-di-dian": "选择工作地点",
+  "song-can-jiao-tong-gong-ju-xin-xi": "送餐交通工具信息",
+  "shen-fen-ren-zheng-xin-xi": "身份认证信息"
+}

+ 81 - 0
login/ApplyLocationScreen.tsx

@@ -0,0 +1,81 @@
+import { StackScreenProps } from '@react-navigation/stack';
+import * as React from 'react';
+import { Div, Button, Image, Text, Avatar, Icon } from 'react-native-magnus';
+import { ScrollView } from 'react-native-gesture-handler';
+import { useCreation } from 'ahooks';
+import useModel from 'flooks';
+import { useTranslation } from 'react-i18next';
+
+import Login from './model';
+import User from '../stores/User';
+
+export default function ApplyLocationScreen({ navigation }: StackScreenProps) {
+  const { t } = useTranslation();
+  const { applyInfo, saveRiderApply } = useModel(Login, ['applyInfo']);
+  const { area, longitude, latitude } = applyInfo;
+  const { riderInfo } = useModel(User, ['riderInfo']);
+  const canSubmit = useCreation(() => {
+    if (area && longitude && latitude) {
+      return true;
+    } else {
+      return false;
+    }
+  }, [area, longitude, latitude]);
+
+  return (
+    <Div bg="gray100" flex={1}>
+      <ScrollView
+        contentContainerStyle={{
+          flexGrow: 1,
+          backgroundColor: '#fff',
+        }}
+      >
+        <Div borderTopWidth={10} borderColor="gray100" px={15}>
+          <Div row py={10} overflow="hidden" alignItems="center">
+            <Text w={120} textAlign="right">
+              {t('gong-zuo-di-dian')}:
+            </Text>
+
+            <Button
+              flex={1}
+              bg="gray100"
+              color="black"
+              justifyContent="space-around"
+              ml={10}
+              onPress={() => navigation.navigate('SearchMap')}
+            >
+              <Div flex={1}>
+                <Text fontSize="sm" color={area ? 'black' : 'gray300'}>
+                  {area || t('qing-xuan-ze-di-dian')}
+                </Text>
+              </Div>
+            </Button>
+            <Icon name="right" ml="md" />
+          </Div>
+        </Div>
+        <Div alignSelf="center" py={20}>
+          <Text fontSize="sm" color="gray400">
+            {t('shuo-ming')}:
+          </Text>
+          <Text fontSize="sm" color="gray400">
+            {t(
+              'qi-shou-de-gong-zuo-qu-yu-wei-yi-gong-zuo-di-dian-ban-jing-3km-de-qu-yu'
+            )}
+          </Text>
+        </Div>
+
+        <Button
+          w={112}
+          bg="yellow500"
+          alignSelf="center"
+          fontSize="sm"
+          my={20}
+          // disabled={!canSubmit}
+          onPress={saveRiderApply}
+        >
+          {t('ti-jiao-shen-he')}
+        </Button>
+      </ScrollView>
+    </Div>
+  );
+}

+ 94 - 0
login/AuditResultScreen.tsx

@@ -0,0 +1,94 @@
+import { StackScreenProps } from '@react-navigation/stack';
+import * as React from 'react';
+import { RefreshControl } from 'react-native';
+import { Div, Button, Image, Text, Avatar } from 'react-native-magnus';
+import { ScrollView } from 'react-native-gesture-handler';
+import { useTranslation } from 'react-i18next';
+
+import { useRequest } from 'ahooks';
+
+import { connect } from '../utils/SystemUtils';
+import {
+  addAsyncStorage,
+  removeAsyncStorage,
+} from '../utils/AsyncStorageUtils';
+
+const img1 = require('../assets/images/autoimg1.png');
+
+export default function AuditResultScreen({ navigation }: StackScreenProps) {
+  const { t } = useTranslation();
+  const { data, refresh, loading } = useRequest('/rider/my');
+  const { status } = data || {};
+
+  React.useEffect(() => {
+    if (status === 'PENDING') {
+      addAsyncStorage('riderIsApply', true);
+    }
+  }, [status]);
+  return (
+    <Div bg="white" flex={1}>
+      <ScrollView
+        contentContainerStyle={{
+          flexGrow: 1,
+          backgroundColor: '#fff',
+          alignItems: 'center',
+        }}
+        refreshControl={
+          <RefreshControl refreshing={loading} onRefresh={refresh} />
+        }
+      >
+        <Image source={img1} w={91} h={78} mt={40} />
+        {status === 'PENDING' && (
+          <Div alignItems="center" py={30}>
+            <Text color="gray600" fontSize="xl">
+              {t('nin-de-shen-qing-yi-ti-jiao-cheng-gong')}
+            </Text>
+            <Text color="gray600" fontSize="xl">
+              {t('qing-nai-xin-deng-dai')}...
+            </Text>
+          </Div>
+        )}
+
+        {status === 'DENY' && (
+          <>
+            <Div alignItems="center" py={30}>
+              <Text color="gray600" fontSize="xl">
+                {t('failresult')}
+              </Text>
+              <Text color="gray600" fontSize="xl">
+                {t('failresult2')}
+              </Text>
+            </Div>
+
+            <Button
+              bg="yellow500"
+              fontSize="sm"
+              w={112}
+              alignSelf="center"
+              onPress={() => {
+                navigation.navigate('Certification');
+              }}
+            >
+              {t('zhong-xin-shen-qing')}
+            </Button>
+          </>
+        )}
+
+        <Div flex={1} w={10} />
+        <Button
+          w={112}
+          color="yellow500"
+          bg="none"
+          borderColor="gray100"
+          borderWidth={1}
+          alignSelf="center"
+          fontSize="sm"
+          my={20}
+          onPress={() => connect(navigation)}
+        >
+          {t('lian-xi-ke-fu')}
+        </Button>
+      </ScrollView>
+    </Div>
+  );
+}

+ 201 - 0
login/CertificationScreen.tsx

@@ -0,0 +1,201 @@
+import { StackScreenProps } from '@react-navigation/stack';
+import * as React from 'react';
+import { Div, Button, Image, Text, Avatar, Input } from 'react-native-magnus';
+import { TextInputMask } from 'react-native-masked-text';
+import { ScrollView } from 'react-native-gesture-handler';
+import ImagePicker from '../components/ImagePicker';
+
+import { useTranslation } from 'react-i18next';
+import { useMap, useCreation, useRequest } from 'ahooks';
+import { connect } from '../utils/SystemUtils';
+import useModel from 'flooks';
+import Login from './model';
+
+export default function CertificationScreen({ navigation }: StackScreenProps) {
+  const { t } = useTranslation();
+  const { data } = useRequest('/verified/my');
+
+  const [realName, setRealName] = React.useState<string>('');
+  const [idNo, setIdNo] = React.useState<string>('');
+  const [idNoImgMap, idNoImgMapEvent] = useMap<string, string>([
+    ['证件正面', ''],
+    ['证件反面', ''],
+  ]);
+  const [handheldIdNo, setHandheldIdNo] = React.useState<string>('');
+
+  const { verifiedSave } = useModel(Login, []);
+
+  const canSubmit = useCreation(() => {
+    if (
+      realName &&
+      idNo &&
+      handheldIdNo &&
+      idNoImgMapEvent.get('证件正面') !== '' &&
+      idNoImgMapEvent.get('证件反面') !== ''
+    ) {
+      return true;
+    } else {
+      return false;
+    }
+  }, [realName, idNo, idNoImgMap, handheldIdNo]);
+
+  React.useEffect(() => {
+    if (data && data.id) {
+      setRealName(data.realName);
+      setIdNo(data.idNo);
+      setHandheldIdNo(data.handheldIdNo);
+      idNoImgMapEvent.setAll([
+        ['证件正面', data.idNoImg.split(',')[0]],
+        ['证件反面', data.idNoImg.split(',')[1]],
+      ]);
+    }
+  }, [data]);
+
+  return (
+    <Div bg="#fff" flex={1}>
+      <ScrollView
+        contentContainerStyle={{
+          flexGrow: 1,
+          backgroundColor: '#fff',
+        }}
+      >
+        <Div borderTopWidth={10} borderColor="gray100" px={15}>
+          <Div row alignItems="center" py={10}>
+            <Text w={120} textAlign="right">
+              {t('zhen-shi-xing-ming')}:
+            </Text>
+            <Div
+              flex={1}
+              bg="gray100"
+              rounded="sm"
+              ml={5}
+              h={30}
+              alignItems="stretch"
+            >
+              <Input
+                flex={1}
+                bg="gray100"
+                rounded="sm"
+                loaderColor="gray400"
+                px={12}
+                ml={5}
+                h={30}
+                value={realName}
+                borderWidth={0}
+                fontSize="sm"
+                placeholder={t('shu-ru-xing-ming')}
+                onChangeText={(text) => {
+                  setRealName(text);
+                }}
+                style={{ flex: 1 }}
+              />
+            </Div>
+          </Div>
+
+          <Div row alignItems="center" py={10}>
+            <Text w={120} textAlign="right">
+              {t('shen-fen-zheng-hao')}:
+            </Text>
+            <Div
+              flex={1}
+              bg="gray100"
+              rounded="sm"
+              px={19}
+              ml={5}
+              h={30}
+              alignItems="stretch"
+            >
+              <TextInputMask
+                type={'custom'}
+                value={idNo}
+                keyboardType="numeric"
+                placeholderTextColor="#a6a6a6"
+                options={{
+                  mask: '999999999999999999',
+                }}
+                onChangeText={(text) => {
+                  setIdNo(text);
+                }}
+                style={{ flex: 1, fontSize: 12 }}
+                placeholder={t('shu-ru-zheng-jian-hao-ma')}
+              />
+            </Div>
+          </Div>
+
+          <Div row py={10}>
+            <Text w={120} textAlign="right">
+              {t('shen-fen-zheng-zheng-fan-mian')}:
+            </Text>
+
+            <Div flex={1} row pt={30}>
+              {[...idNoImgMap.keys()].map((item, index) => {
+                return (
+                  <Div w="50%" key={index} alignItems="center">
+                    <ImagePicker
+                      img={idNoImgMapEvent.get(item)}
+                      setImg={(img) => idNoImgMapEvent.set(item, img)}
+                    />
+                    <Text textAlign="center" color="gray200" fontSize="sm">
+                      {item}
+                    </Text>
+                  </Div>
+                );
+              })}
+            </Div>
+          </Div>
+
+          <Div row py={10}>
+            <Text w={120} textAlign="right">
+              {t('shou-chi-shen-fen-zheng')}:
+            </Text>
+
+            <Div flex={1} row pt={30}>
+              <Div w="50%" alignItems="center">
+                <ImagePicker
+                  img={handheldIdNo}
+                  setImg={(img) => setHandheldIdNo(img)}
+                />
+              </Div>
+            </Div>
+          </Div>
+
+          <Button
+            w={112}
+            bg="yellow500"
+            alignSelf="center"
+            fontSize="sm"
+            my={20}
+            disabled={!canSubmit}
+            onPress={() =>
+              verifiedSave(
+                realName,
+                idNo,
+                idNoImgMapEvent.get('证件正面') +
+                  ',' +
+                  idNoImgMapEvent.get('证件反面'),
+                handheldIdNo,
+                data ? data.id || '' : ''
+              )
+            }
+          >
+            {t('xia-yi-bu')}
+          </Button>
+
+          <Button
+            w={112}
+            color="yellow500"
+            bg="none"
+            borderColor="gray100"
+            borderWidth={1}
+            alignSelf="center"
+            fontSize="sm"
+            my={20}
+            onPress={() => connect(navigation)}
+          >
+            {t('lian-xi-ke-fu')}
+          </Button>
+        </Div>
+      </ScrollView>
+    </Div>
+  );
+}

+ 237 - 192
login/LoginScreen.tsx

@@ -6,215 +6,260 @@ import { ScrollView } from 'react-native-gesture-handler';
 import { TextInputMask } from 'react-native-masked-text';
 import { useMount } from 'ahooks';
 import { useNavigation } from '@react-navigation/native';
+import { useTranslation } from 'react-i18next';
+import useModel from 'flooks';
 
-import { RootStackParamList, LoginTabParamList } from '../types';
+import { MainStackParamList, LoginTabParamList } from '../types';
 import Navigation from '../navigation';
 
+import Login from './model';
+
 const LoginTab = createMaterialTopTabNavigator<LoginTabParamList>();
 
-export default function LoginScreen({ navigation }: StackScreenProps<RootStackParamList, 'Login'>) {
-    return (
-        <Div flex={1} bg="yellow200">
-            <ScrollView
-                contentContainerStyle={{
-                    flexGrow: 1,
-                    backgroundColor: '#FFF2C7',
-                    justifyContent: 'center'
-                }}
-                keyboardDismissMode="on-drag"
-            >
-                <Div bg="white" mx={13} px={10} py={20} rounded="xs">
-                    <Image
-                        source={require('../assets/images/loginImg2.png')}
-                        alignSelf="center"
-                        w={217}
-                        h={100}
-                    />
+export default function LoginScreen({
+  navigation,
+}: StackScreenProps<MainStackParamList, 'Login'>) {
+  const { t } = useTranslation();
+  return (
+    <Div flex={1} bg="yellow200">
+      <ScrollView
+        contentContainerStyle={{
+          flexGrow: 1,
+          backgroundColor: '#FFF2C7',
+          justifyContent: 'center',
+        }}
+        keyboardDismissMode="on-drag"
+      >
+        <Div bg="white" mx={13} px={10} py={20} rounded="xs">
+          <Image
+            source={require('../assets/images/loginImg2.png')}
+            alignSelf="center"
+            w={217}
+            h={100}
+          />
 
-                    <Image
-                        source={require('../assets/images/loginImg.png')}
-                        w={275}
-                        h={60}
-                        position="absolute"
-                        top={-59}
-                        left="50%"
-                        ml={-137}
-                        zIndex={2}
-                    />
+          <Image
+            source={require('../assets/images/loginImg.png')}
+            w={275}
+            h={60}
+            position="absolute"
+            top={-59}
+            left="50%"
+            ml={-137}
+            zIndex={2}
+          />
 
-                    <Div bg="white">
-                        <LoginTab.Navigator
-                            initialRouteName="Psd"
-                            tabBarOptions={{
-                                inactiveTintColor: '#000',
-                                activeTintColor: '#FFC21C',
-                                indicatorStyle: { height: 0 },
-                                labelStyle: { fontSize: 13 }
-                            }}
-                        >
-                            <LoginTab.Screen
-                                name="Psd"
-                                component={LoginByPsd}
-                                options={{
-                                    title: '密码登录',
-                                    backgroundColor: '#fff'
-                                }}
-                            />
-                            <LoginTab.Screen
-                                name="Code"
-                                component={LoginByCode}
-                                options={{
-                                    title: '验证码登录'
-                                }}
-                            />
-                        </LoginTab.Navigator>
-                    </Div>
-                </Div>
-            </ScrollView>
+          <Div bg="white">
+            <LoginTab.Navigator
+              initialRouteName="Psd"
+              tabBarOptions={{
+                inactiveTintColor: '#000',
+                activeTintColor: '#FFC21C',
+                indicatorStyle: { height: 0 },
+                labelStyle: { fontSize: 13 },
+              }}
+            >
+              <LoginTab.Screen
+                name="Psd"
+                component={LoginByPsd}
+                options={{
+                  title: t('mi-ma-deng-lu'),
+                  backgroundColor: '#fff',
+                }}
+              />
+              <LoginTab.Screen
+                name="Code"
+                component={LoginByCode}
+                options={{
+                  title: t('yan-zheng-ma-deng-lu'),
+                }}
+              />
+            </LoginTab.Navigator>
+          </Div>
         </Div>
-    );
+      </ScrollView>
+    </Div>
+  );
 }
 
 function LoginByPsd() {
-    const [phone, setphone] = React.useState<string>('');
-    const [password, setpassword] = React.useState<string>('');
+  const { t } = useTranslation();
+  const [phone, setphone] = React.useState<string>('');
+  const [password, setpassword] = React.useState<string>('');
+  const navigation = useNavigation();
 
-    const navigation = useNavigation();
-    return (
-        <Div bg="white">
-            <Div row alignItems="center" py={10}>
-                <Text w={96} textAlign="right">
-                    手机号:
-                </Text>
-                <Div flex={1} bg="gray100" rounded="sm" px={15} ml={5} h={30} alignItems="stretch">
-                    <TextInputMask
-                        type={'custom'}
-                        value={phone}
-                        keyboardType="numeric"
-                        options={{
-                            mask: '999 9999 9999'
-                        }}
-                        onChangeText={(text) => {
-                            console.log(text);
-                            setphone(text);
-                        }}
-                        style={{ flex: 1, fontSize: 14 }}
-                        placeholder="输入商家名称"
-                    />
-                </Div>
-            </Div>
-            <Div row alignItems="center" py={10}>
-                <Text w={96} textAlign="right">
-                    密&nbsp;&nbsp;&nbsp;&nbsp;码:
-                </Text>
-                <Input
-                    flex={1}
-                    bg="gray100"
-                    rounded="sm"
-                    px={15}
-                    ml={5}
-                    h={30}
-                    type={'custom'}
-                    value={password}
-                    borderWidth={0}
-                    fontSize="md"
-                    secureTextEntry
-                    color="gray700"
-                    onChangeText={(text) => {
-                        console.log(text);
-                        setpassword(text);
-                    }}
-                    style={{ flex: 1 }}
-                    placeholder="输入密码"
-                />
-            </Div>
+  const loginModel = useModel(Login, []);
 
-            <Div row justifyContent="space-between" pl={90}>
-                <Button
-                    color="gray400"
-                    fontSize="sm"
-                    bg="white"
-                    onPress={() => navigation.navigate('ForgetPsd')}
-                >
-                    忘记密码
-                </Button>
-                <Button
-                    color="gray400"
-                    fontSize="sm"
-                    bg="white"
-                    onPress={() => navigation.navigate('Root')}
-                >
-                    骑手注册
-                </Button>
-            </Div>
-
-            <Button block bg="yellow500" w={112} fontSize="sm" ml={96} mt={10}>
-                登录
-            </Button>
+  return (
+    <Div bg="white">
+      <Div row alignItems="center" py={10}>
+        <Text w={96} textAlign="right">
+          {t('shou-ji-hao')}:
+        </Text>
+        <Div
+          flex={1}
+          bg="gray100"
+          rounded="sm"
+          px={15}
+          ml={5}
+          h={30}
+          alignItems="stretch"
+        >
+          <TextInputMask
+            type={'custom'}
+            value={phone}
+            keyboardType="numeric"
+            options={{
+              mask: '999 9999 9999',
+            }}
+            onChangeText={(text) => {
+              console.log(text);
+              setphone(text);
+            }}
+            style={{ flex: 1, fontSize: 14 }}
+            placeholder={t('shu-ru-shang-jia-ming-cheng')}
+          />
         </Div>
-    );
+      </Div>
+      <Div row alignItems="center" py={10}>
+        <Text w={96} textAlign="right">
+          {t('mi-ma')}:
+        </Text>
+        <Input
+          flex={1}
+          bg="gray100"
+          rounded="sm"
+          px={15}
+          ml={5}
+          h={30}
+          type={'custom'}
+          value={password}
+          borderWidth={0}
+          fontSize="md"
+          secureTextEntry
+          color="gray700"
+          onChangeText={(text) => {
+            console.log(text);
+            setpassword(text);
+          }}
+          style={{ flex: 1 }}
+          placeholder={t('shu-ru-mi-ma')}
+        />
+      </Div>
+
+      <Div row justifyContent="space-between" pl={90}>
+        <Button
+          color="gray400"
+          fontSize="sm"
+          bg="white"
+          onPress={() => navigation.navigate('ForgetPsd')}
+        >
+          {t('wang-ji-mi-ma')}
+        </Button>
+        <Button
+          color="gray400"
+          fontSize="sm"
+          bg="white"
+          onPress={() => navigation.navigate('Register')}
+        >
+          {t('qi-shou-zhu-ce')}
+        </Button>
+      </Div>
+
+      <Button
+        block
+        bg="yellow500"
+        w={112}
+        fontSize="sm"
+        ml={96}
+        mt={10}
+        onPress={() => {
+          loginModel.loginByPsd();
+        }}
+      >
+        {t('deng-lu')}
+      </Button>
+    </Div>
+  );
 }
 
 function LoginByCode() {
-    const [phone, setphone] = React.useState<string>('');
-    const [code, setCode] = React.useState<string>('');
-    return (
-        <Div bg="white">
-            <Div row alignItems="center" py={10}>
-                <Text w={96} textAlign="right">
-                    手机号:
-                </Text>
-                <Div flex={1} bg="gray100" rounded="sm" px={15} ml={5} h={30} alignItems="stretch">
-                    <TextInputMask
-                        type={'custom'}
-                        value={phone}
-                        keyboardType="numeric"
-                        options={{
-                            mask: '999 9999 9999'
-                        }}
-                        onChangeText={(text) => {
-                            console.log(text);
-                            setphone(text);
-                        }}
-                        style={{ flex: 1, fontSize: 14 }}
-                        placeholder="输入手机号"
-                    />
-                </Div>
-            </Div>
-            <Div row alignItems="center" py={10}>
-                <Text w={96} textAlign="right">
-                    验证码:
-                </Text>
-                <Div flex={1} bg="gray100" rounded="sm" px={15} ml={5} h={30} alignItems="stretch">
-                    <TextInputMask
-                        type={'custom'}
-                        value={code}
-                        keyboardType="numeric"
-                        options={{
-                            mask: '9999'
-                        }}
-                        onChangeText={(text) => {
-                            console.log(text);
-                            setCode(text);
-                        }}
-                        style={{ flex: 1, fontSize: 14 }}
-                        placeholder="输入验证码"
-                    />
-                </Div>
-            </Div>
+  const { t } = useTranslation();
+  const [phone, setphone] = React.useState<string>('');
+  const [code, setCode] = React.useState<string>('');
+  return (
+    <Div bg="white">
+      <Div row alignItems="center" py={10}>
+        <Text w={96} textAlign="right">
+          {t('shou-ji-hao')}:
+        </Text>
+        <Div
+          flex={1}
+          bg="gray100"
+          rounded="sm"
+          px={15}
+          ml={5}
+          h={30}
+          alignItems="stretch"
+        >
+          <TextInputMask
+            type={'custom'}
+            value={phone}
+            keyboardType="numeric"
+            options={{
+              mask: '999 9999 9999',
+            }}
+            onChangeText={(text) => {
+              console.log(text);
+              setphone(text);
+            }}
+            style={{ flex: 1, fontSize: 14 }}
+            placeholder={t('shu-ru-shou-ji-hao')}
+          />
+        </Div>
+      </Div>
+      <Div row alignItems="center" py={10}>
+        <Text w={96} textAlign="right">
+          {t('yan-zheng-ma')}:
+        </Text>
+        <Div
+          flex={1}
+          bg="gray100"
+          rounded="sm"
+          px={15}
+          ml={5}
+          h={30}
+          alignItems="stretch"
+        >
+          <TextInputMask
+            type={'custom'}
+            value={code}
+            keyboardType="numeric"
+            options={{
+              mask: '9999',
+            }}
+            onChangeText={(text) => {
+              console.log(text);
+              setCode(text);
+            }}
+            style={{ flex: 1, fontSize: 14 }}
+            placeholder={t('shu-ru-yan-zheng-ma')}
+          />
+        </Div>
+      </Div>
 
-            <Div row justifyContent="space-between" pl={90}>
-                <Button color="gray400" fontSize="sm" bg="white">
-                    忘记密码
-                </Button>
-                <Button color="gray400" fontSize="sm" bg="white">
-                    骑手注册
-                </Button>
-            </Div>
+      <Div row justifyContent="space-between" pl={90}>
+        <Button color="gray400" fontSize="sm" bg="white">
+          {t('wang-ji-mi-ma')}
+        </Button>
+        <Button color="gray400" fontSize="sm" bg="white">
+          {t('qi-shou-zhu-ce')}
+        </Button>
+      </Div>
 
-            <Button block bg="yellow500" w={112} fontSize="sm" ml={96} mt={10}>
-                登录
-            </Button>
-        </Div>
-    );
+      <Button block bg="yellow500" w={112} fontSize="sm" ml={96} mt={10}>
+        {t('deng-lu')}
+      </Button>
+    </Div>
+  );
 }

+ 243 - 12
login/RegisterScreen.tsx

@@ -1,20 +1,251 @@
 import { StackScreenProps } from '@react-navigation/stack';
 import * as React from 'react';
-import { Div, Button, Image, Text, Avatar } from 'react-native-magnus';
+import {
+  Div,
+  Button,
+  Image,
+  Text,
+  Avatar,
+  Input,
+  Icon,
+  Radio,
+} from 'react-native-magnus';
+import { TextInputMask } from 'react-native-masked-text';
 import { ScrollView } from 'react-native-gesture-handler';
+import { useTranslation } from 'react-i18next';
 
-import { RootStackParamList } from '../types';
+import { useCreation } from 'ahooks';
+import useModel from 'flooks';
 
+import Login from './model';
+
+import { MainStackParamList } from '../types';
+import { connect } from '../utils/SystemUtils';
+import Navigation from '../navigation/MainStackNavigator';
 export default function RegisterScreen({
-    navigation
-}: StackScreenProps<RootStackParamList, 'Login'>) {
-    return (
-        <Div bg="gray100">
-            <ScrollView contentContainerStyle={{ flexGrow: 1, backgroundColor: '#f2f2f2' }}>
-                <Div>
-                    <Text></Text>
-                </Div>
-            </ScrollView>
+  navigation,
+}: StackScreenProps<MainStackParamList, 'Login'>) {
+  navigation.setOptions({
+    headerLeft: (props) => (
+      <Button
+        bg="none"
+        px={15}
+        py={0}
+        onPress={() => {
+          navigation.goBack();
+        }}
+      >
+        <Image w={50} h={50} source={require('../assets/images/logo.png')} />
+      </Button>
+    ),
+  });
+
+  const { loginByRegister } = useModel(Login, []);
+
+  const { t } = useTranslation();
+
+  const [phone, setPhone] = React.useState<string>('');
+  const [code, setCode] = React.useState<string>('');
+  const [password, setpassword] = React.useState<string>('');
+  const [password2, setpassword2] = React.useState<string>('');
+  const [sure, setSure] = React.useState<boolean>(false);
+
+  const canSubmit = useCreation(() => {
+    if (phone && code && password && password === password2 && sure) {
+      return true;
+    } else {
+      return false;
+    }
+  }, [phone, code, password, password2, sure]);
+
+  return (
+    <Div bg="gray100" flex={1}>
+      <ScrollView
+        contentContainerStyle={{
+          flexGrow: 1,
+          backgroundColor: '#f2f2f2',
+        }}
+      >
+        <Div
+          bg="white"
+          py={20}
+          px={10}
+          borderTopWidth={5}
+          borderColor="gray100"
+        >
+          <Text textAlign="center">{t('guide1')}!</Text>
+          <Text textAlign="center">{t('guide2')}</Text>
+        </Div>
+        <Div
+          bg="white"
+          py={20}
+          px={25}
+          borderTopWidth={5}
+          borderColor="gray100"
+          flex={1}
+        >
+          <Div row alignItems="center" py={10}>
+            <Text w={96} textAlign="right">
+              {t('shou-ji-hao')}:
+            </Text>
+            <Div
+              flex={1}
+              bg="gray100"
+              rounded="sm"
+              // px={15}
+              ml={5}
+              h={30}
+              alignItems="stretch"
+            >
+              <TextInputMask
+                type={'custom'}
+                loaderColor="gray400"
+                px={12}
+                value={phone}
+                keyboardType="numeric"
+                options={{
+                  mask: '999 9999 9999',
+                }}
+                onChangeText={(text) => {
+                  setPhone(text);
+                }}
+                style={{ flex: 1, fontSize: 14 }}
+                placeholder={t('shu-ru-shou-ji-hao')}
+              />
+            </Div>
+          </Div>
+          <Div row alignItems="center" py={10}>
+            <Text w={96} textAlign="right">
+              {t('yan-zheng-ma')}:
+            </Text>
+            <Div
+              flex={1}
+              bg="gray100"
+              rounded="sm"
+              px={15}
+              ml={5}
+              h={30}
+              alignItems="stretch"
+            >
+              <TextInputMask
+                type={'custom'}
+                value={code}
+                keyboardType="numeric"
+                placeholderTextColor="#a6a6a6"
+                options={{
+                  mask: '999999',
+                }}
+                onChangeText={(text) => {
+                  setCode(text);
+                }}
+                style={{ flex: 1, fontSize: 14 }}
+                placeholder={t('shu-ru-yan-zheng-ma')}
+              />
+            </Div>
+          </Div>
+          <Div row alignItems="center" py={10}>
+            <Text w={96} textAlign="right">
+              {t('mi-ma')}:
+            </Text>
+            <Input
+              flex={1}
+              bg="gray100"
+              rounded="sm"
+              loaderColor="gray400"
+              px={12}
+              ml={5}
+              h={30}
+              type={'custom'}
+              value={password}
+              borderWidth={0}
+              fontSize="md"
+              secureTextEntry
+              color="gray700"
+              onChangeText={(text) => {
+                setpassword(text);
+              }}
+              style={{ flex: 1 }}
+              placeholder={t('shu-ru-mi-ma')}
+            />
+          </Div>
+          <Div row alignItems="center" py={10}>
+            <Text w={96} textAlign="right">
+              {t('que-ren-mi-ma')}:
+            </Text>
+            <Input
+              flex={1}
+              bg="gray100"
+              rounded="sm"
+              loaderColor="gray400"
+              px={12}
+              ml={5}
+              h={30}
+              type={'custom'}
+              value={password2}
+              borderWidth={0}
+              fontSize="md"
+              secureTextEntry
+              color="gray700"
+              onChangeText={(text) => {
+                setpassword2(text);
+              }}
+              style={{ flex: 1 }}
+              placeholder={t('zai-ci-shu-ru-que-ren-mi-ma')}
+            />
+          </Div>
+
+          <Button bg="white" block mt={20} onPress={() => setSure(!sure)}>
+            <Div row alignItems="center" flex={1} justifyContent="center">
+              <Icon
+                fontFamily="Ionicons"
+                ml={30}
+                color={sure ? 'yellow500' : 'gray400'}
+                name={sure ? 'ios-radio-button-on' : 'ios-radio-button-off'}
+              />
+              <Div row flexWrap="wrap" ml={15} flex={1}>
+                <Text color="gray400" fontSize="xs">
+                  {t('yi-jing-yue-du-bing-tong-yi')}
+                </Text>
+                <Button bg="none" color="yellow300" fontSize="sm" p={0}>
+                  {t('ding-dong-wai-mai-qi-shou-zhu-ce-xie-yi')}
+                </Button>
+                <Text color="gray400" fontSize="xs">
+                  {t('he')}
+                </Text>
+                <Button bg="none" color="yellow300" p={0} fontSize="sm">
+                  {t('ding-dong-wai-mai-qi-shou-yin-si-zheng-ce')}
+                </Button>
+              </Div>
+            </Div>
+          </Button>
+
+          <Button
+            w={112}
+            bg="yellow500"
+            alignSelf="center"
+            fontSize="sm"
+            my={20}
+            disabled={!canSubmit}
+            onPress={() => loginByRegister(phone, password)}
+          >
+            {t('xia-yi-bu')}
+          </Button>
+
+          <Button
+            w={112}
+            color="yellow500"
+            bg="none"
+            borderColor="gray100"
+            borderWidth={1}
+            alignSelf="center"
+            fontSize="sm"
+            my={20}
+            onPress={() => connect(navigation)}
+          >
+            {t('lian-xi-ke-fu')}
+          </Button>
         </Div>
-    );
+      </ScrollView>
+    </Div>
+  );
 }

+ 276 - 0
login/TransportationScreen.tsx

@@ -0,0 +1,276 @@
+import { StackScreenProps } from '@react-navigation/stack';
+import * as React from 'react';
+import {
+  Div,
+  Button,
+  Image,
+  Text,
+  Avatar,
+  Radio,
+  RadioGroup,
+  Input,
+} from 'react-native-magnus';
+import { ScrollView } from 'react-native-gesture-handler';
+import { useTranslation } from 'react-i18next';
+import { useMap, useCreation } from 'ahooks';
+import useModel from 'flooks';
+import Login from './model';
+import User from '../stores/User';
+
+import ImagePicker from '../components/ImagePicker';
+import { connect } from '../utils/SystemUtils';
+
+import { RiderMap } from '../utils/RiderInfoUtils';
+
+export default function TransportationScreen({ navigation }: StackScreenProps) {
+  const { t } = useTranslation();
+
+  const [transport, setTransport] = React.useState<string>('');
+  const [extra, setExtra] = React.useState<string>('');
+  const { riderApplyPre } = useModel(Login, []);
+
+  const { riderInfo } = useModel(User, ['riderInfo']);
+
+  const [transportationImgMap, transportationImgMapEvent] = useMap<
+    string,
+    string
+  >([
+    ['正面', ''],
+    ['侧面', ''],
+  ]);
+
+  const [driverLicense, setDriverLicense] = React.useState<boolean>(false);
+
+  const [driverLicenseImgMap, driverLicenseImgEvent] = useMap<string, string>([
+    ['正面', ''],
+    ['背面', ''],
+  ]);
+  const canSubmit = useCreation(() => {
+    if (
+      transport === '没有' ||
+      (transport &&
+        transportationImgMapEvent.get('正面') &&
+        transportationImgMapEvent.get('侧面') &&
+        (!driverLicense ||
+          (driverLicense &&
+            driverLicenseImgEvent.get('正面') &&
+            driverLicenseImgEvent.get('背面'))))
+    ) {
+      return true;
+    } else {
+      return false;
+    }
+  }, [
+    transport,
+    extra,
+    transportationImgMap,
+    driverLicense,
+    driverLicenseImgMap,
+  ]);
+
+  React.useEffect(() => {
+    if (riderInfo && riderInfo.id) {
+      const keys = [...RiderMap.keys()];
+      if (keys.includes(riderInfo.transportation)) {
+        setTransport(riderInfo.transportation);
+      } else {
+        setTransport('其他');
+        setExtra(riderInfo.transportation.replace(/其他/, ''));
+      }
+      if (riderInfo.transportationImg) {
+        transportationImgMapEvent.setAll([
+          ['正面', riderInfo.transportationImg.split(',')[0]],
+          ['侧面', riderInfo.transportationImg.split(',')[1]],
+        ]);
+      }
+      if (riderInfo.driverLicenseImg) {
+        setDriverLicense(true);
+        driverLicenseImgEvent.setAll([
+          ['正面', riderInfo.driverLicenseImg.split(',')[0]],
+          ['背面', riderInfo.driverLicenseImg.split(',')[1]],
+        ]);
+      }
+    }
+  }, [riderInfo]);
+
+  return (
+    <Div bg="gray100" flex={1}>
+      <ScrollView
+        contentContainerStyle={{
+          flexGrow: 1,
+          backgroundColor: '#fff',
+        }}
+      >
+        <Div borderTopWidth={10} borderColor="gray100" px={15}>
+          <Div row py={10} overflow="hidden">
+            <Text w={120} textAlign="left">
+              {t('song-can-jiao-tong-gong-ju')}:
+            </Text>
+            <RadioGroup
+              value={transport}
+              onChange={(value: any) => {
+                if (value !== '其他') {
+                  setExtra('');
+                }
+                setTransport(value);
+              }}
+              flexDir="row"
+              flexWrap="wrap"
+              flex={1}
+            >
+              {[...RiderMap.keys()].map((item, index) => {
+                let info = RiderMap.get(item);
+                return (
+                  <Radio
+                    value={item}
+                    key={index}
+                    activeColor="yellow500"
+                    inactiveColor="gray300"
+                    pr={20}
+                    my={5}
+                  >
+                    <Text ml={6} color="gray300">
+                      {t(info.name)}
+                    </Text>
+
+                    {info.hasInput && (
+                      <Input
+                        w={96}
+                        h={25}
+                        value={extra}
+                        onChangeText={setExtra}
+                        onFocus={() => {
+                          setTransport(item);
+                        }}
+                        ml={5}
+                        bg="gray100"
+                      />
+                    )}
+                  </Radio>
+                );
+              })}
+            </RadioGroup>
+          </Div>
+          {transport != '' && transport != '没有' && (
+            <>
+              <Div row py={10} overflow="hidden">
+                <Text w={120} textAlign="left">
+                  {t('jiao-tong-gong-ju-zhao-pian')}:
+                </Text>
+                <Div flex={1} row pt={30}>
+                  {[...transportationImgMap.keys()].map((item, index) => {
+                    return (
+                      <Div w="50%" key={index} alignItems="center">
+                        <ImagePicker
+                          img={transportationImgMapEvent.get(item)}
+                          setImg={(img) =>
+                            transportationImgMapEvent.set(item, img)
+                          }
+                        />
+                      </Div>
+                    );
+                  })}
+                </Div>
+              </Div>
+              <Div row py={10} overflow="hidden">
+                <Text w={120} textAlign="left">
+                  {t('you-wu-jia-shi-zheng')}:
+                </Text>
+                <RadioGroup
+                  value={driverLicense}
+                  onChange={(value: any) => setDriverLicense(value)}
+                  flexDir="row"
+                  flexWrap="wrap"
+                  flex={1}
+                >
+                  <Radio
+                    value={true}
+                    activeColor="yellow500"
+                    inactiveColor="gray300"
+                    pr={20}
+                    my={5}
+                  >
+                    <Text ml={6} color="gray300">
+                      {t('you')}
+                    </Text>
+                  </Radio>
+                  <Radio
+                    value={false}
+                    activeColor="yellow500"
+                    inactiveColor="gray300"
+                    pr={20}
+                    my={5}
+                  >
+                    <Text ml={6} color="gray300">
+                      {t('mei-you')}
+                    </Text>
+                  </Radio>
+                </RadioGroup>
+              </Div>
+              {driverLicense && (
+                <Div row py={10} overflow="hidden">
+                  <Text w={120} textAlign="left">
+                    {t('jia-shi-zheng-zheng-fan-mian')}:
+                  </Text>
+                  <Div flex={1} row pt={30}>
+                    {[...driverLicenseImgMap.keys()].map((item, index) => {
+                      return (
+                        <Div w="50%" key={index} alignItems="center">
+                          <ImagePicker
+                            img={driverLicenseImgEvent.get(item)}
+                            setImg={(img) =>
+                              driverLicenseImgEvent.set(item, img)
+                            }
+                          />
+                          <Text>{item}</Text>
+                        </Div>
+                      );
+                    })}
+                  </Div>
+                </Div>
+              )}
+            </>
+          )}
+          <Button
+            w={112}
+            bg="yellow500"
+            alignSelf="center"
+            fontSize="sm"
+            my={20}
+            disabled={!canSubmit}
+            onPress={() => {
+              riderApplyPre(
+                transport + extra,
+                transportationImgMapEvent.get('正面') +
+                  ',' +
+                  transportationImgMapEvent.get('侧面'),
+                driverLicense
+                  ? driverLicenseImgEvent.get('正面') +
+                      ',' +
+                      driverLicenseImgEvent.get('背面')
+                  : ''
+              );
+              navigation.navigate('ApplyLocation');
+            }}
+          >
+            {t('xia-yi-bu')}
+          </Button>
+
+          <Button
+            w={112}
+            color="yellow500"
+            bg="none"
+            borderColor="gray100"
+            borderWidth={1}
+            alignSelf="center"
+            fontSize="sm"
+            my={20}
+            onPress={() => connect(navigation)}
+          >
+            {t('lian-xi-ke-fu')}
+          </Button>
+        </Div>
+      </ScrollView>
+    </Div>
+  );
+}

+ 167 - 0
login/model.ts

@@ -0,0 +1,167 @@
+import request from '../utils/RequestUtils';
+import {
+  toastShow,
+  toastHide,
+  toastInfo,
+  toastSuccess,
+} from '../utils/SystemUtils';
+import {
+  addAsyncStorage,
+  removeAsyncStorage,
+} from '../utils/AsyncStorageUtils';
+import User from '../stores/User';
+
+const MapModel = (now) => ({
+  applyInfo: {},
+  loginByPsd() {
+    toastShow();
+    return request
+      .post('/auth/phoneLogin', {
+        data: {
+          phone: '1562356',
+        },
+        requestType: 'form',
+      })
+      .then((res) => {
+        return addAsyncStorage('token', res);
+      })
+      .then(() => {
+        const { getInit } = now(User);
+        return getInit();
+      })
+      .then(() => {
+        toastHide();
+        toastSuccess('登录成功');
+      })
+      .catch((e) => {
+        toastHide();
+        toastInfo(e.error);
+      });
+  },
+  loginByRegister(phone, password) {
+    toastShow();
+    return request
+      .post('/auth/loginByRegister', {
+        data: {
+          phone: phone,
+          password,
+          identity: 'RIDER',
+        },
+        requestType: 'form',
+      })
+      .then((res) => {
+        return addAsyncStorage('token', res);
+      })
+      .then(() => {
+        const { getInit } = now(User);
+        return getInit();
+      })
+      .then(() => {
+        toastHide();
+        toastSuccess('注册成功');
+      })
+      .catch((e) => {
+        toastHide();
+        toastInfo(e.error);
+      });
+  },
+  verifiedSave(realName, idNo, idNoImg, handheldIdNo, verifiedId) {
+    const { id, getInit, riderInfo } = now(User);
+    console.log(id);
+    toastShow();
+
+    if (riderInfo.status === 'DENY') {
+      addAsyncStorage('seApply', 'Certification');
+    }
+    return request
+      .post('/verified/save', {
+        data: {
+          userId: id,
+          realName,
+          idNo,
+          idNoImg,
+          handheldIdNo,
+          id: verifiedId,
+        },
+      })
+      .then(() => {
+        if (riderInfo.status === 'DENY') {
+          addAsyncStorage('seApply', 'Transportation');
+        }
+        return getInit();
+      })
+      .then(() => {
+        toastHide();
+        return Promise.resolve();
+      })
+      .catch((e) => {
+        toastHide();
+        toastInfo(e.error);
+      });
+  },
+  riderApplyPre(transportation, transportationImg, driverLicenseImg) {
+    const { riderInfo } = now(User);
+    const data = { ...riderInfo };
+    delete data.user;
+    delete data.enabled;
+    delete data.status;
+    now({
+      applyInfo: {
+        ...data,
+        transportation,
+        transportationImg,
+        driverLicenseImg,
+      },
+    });
+  },
+  setLocation(area, lat, lng) {
+    const { applyInfo } = now();
+    now({
+      applyInfo: {
+        ...applyInfo,
+        area,
+        latitude: lat,
+        longitude: lng,
+      },
+    });
+  },
+  saveEvent() {
+    const { applyInfo } = now();
+    const { id } = now(User);
+    if (applyInfo.id) {
+      return request.post('/rider/save', {
+        data: {
+          ...applyInfo,
+          userId: id,
+        },
+      });
+    } else {
+      return request.get('/rider/apply', {
+        params: {
+          ...applyInfo,
+          userId: id,
+        },
+      });
+    }
+  },
+  saveRiderApply() {
+    const { saveEvent } = now();
+    const { id, getInit } = now(User);
+    toastShow();
+
+    return saveEvent()
+      .then(() => {
+        return getInit();
+      })
+      .then(() => {
+        toastHide();
+        return Promise.resolve();
+      })
+      .catch((e) => {
+        toastHide();
+        toastInfo(e.error);
+      });
+  },
+});
+
+export default MapModel;

+ 64 - 0
map/SearchMapScreen.tsx

@@ -0,0 +1,64 @@
+/* eslint-disable react/style-prop-object */
+import * as WebBrowser from 'expo-web-browser';
+import * as React from 'react';
+import { StatusBar } from 'expo-status-bar';
+import { Div } from 'react-native-magnus';
+import { Appbar } from 'react-native-paper';
+import { WebView } from 'react-native-webview';
+import { useTranslation } from 'react-i18next';
+
+import { useRoute } from '@react-navigation/native';
+import useModel from 'flooks';
+import MapModel from './model';
+import Login from '../login/model';
+import { useMount } from 'ahooks';
+import {
+  toastShow,
+  toastHide,
+  toastInfo,
+  toastSuccess,
+} from '../utils/SystemUtils';
+
+export default function SearchMapScreen({ navigation }) {
+  const { t } = useTranslation();
+  const { locationInfo, changeChooseInfo, getNowLocation } = useModel(
+    MapModel,
+    ['locationInfo']
+  );
+  const { setLocation } = useModel(Login, []);
+  const { location } = locationInfo;
+  const [show, setshow] = React.useState<boolean>(false);
+  useMount(() => {
+    toastShow();
+    getNowLocation().then(() => {
+      setTimeout(() => {
+        setshow(true);
+        setTimeout(() => {
+          toastHide();
+        }, 1000);
+      }, 300);
+    });
+  });
+  return (
+    <>
+      {show && (
+        <WebView
+          source={{
+            uri: `http://dingdong.izouma.com/map/chooseLocation?location=${location.lat},${location.lng}`,
+          }}
+          style={{ flexGrow: 1, width: '100%' }}
+          onMessage={({ nativeEvent }) => {
+            const info = JSON.parse(nativeEvent.data);
+            console.log(info);
+            setLocation(
+              info.poiaddress + info.poiname,
+              info.latlng.lat,
+              info.latlng.lng
+            );
+            navigation.goBack();
+          }}
+        />
+      )}
+    </>
+  );
+}

+ 55 - 0
map/model.ts

@@ -0,0 +1,55 @@
+import request from '../utils/RequestUtils';
+import { getLocation, getSearch } from '../utils/MapUtils';
+import {
+  toastShow,
+  toastHide,
+  toastInfo,
+  toastSuccess,
+} from '../utils/SystemUtils';
+
+const MapModel = (now) => ({
+  locationInfo: {}, // 当前定位信息
+  chooseInfo: null, // 当前选点信息,
+  city: '南京', // 选择城市
+  personImg:
+    'https%3a%2f%2fidingdong.oss-cn-hangzhou.aliyuncs.com%2fimage%2f2020-07-03-16-19-50ghUJhqpC.png',
+  merchatImg:
+    'https%3a%2f%2fidingdong.oss-cn-hangzhou.aliyuncs.com%2fimage%2f2020-07-03-16-18-00PhLyNwTp.png',
+  riderImg:
+    'https%3a%2f%2fidingdong.oss-cn-hangzhou.aliyuncs.com%2fimage%2f2020-07-03-16-50-16UqCgAfFD.png',
+  // 获取当前定位
+  getNowLocation() {
+    return getLocation().then((res) => {
+      now({ locationInfo: res });
+      return Promise.resolve(res);
+    });
+  },
+  searchInfo(searchKey, type, callBack) {
+    const { city, chooseInfo, getChooseInfo, searchInfo } = now();
+    let boundary = '';
+    if (type === 'city') {
+      boundary = `region(${city}, 0)`;
+    } else if (!chooseInfo) {
+      getChooseInfo().then(() => {
+        searchInfo(searchKey, type, callBack);
+      });
+      return;
+    } else {
+      boundary = `nearby(31.983908,118.730357,100000)`;
+    }
+    getSearch(searchKey, boundary).then(({ pois }) => callBack(pois));
+  },
+  getChooseInfo() {
+    const { getNowLocation } = now();
+    return getNowLocation().then((res) => {
+      now({ chooseInfo: res });
+    });
+  },
+  changeChooseInfo(info) {
+    now({
+      chooseInfo: info,
+    });
+  },
+});
+
+export default MapModel;

+ 64 - 0
modals/AlertModalScreen.tsx

@@ -0,0 +1,64 @@
+import { StackScreenProps } from '@react-navigation/stack';
+import * as React from 'react';
+import * as Animatable from 'react-native-animatable';
+import { Div, Button, Image, Text, Avatar } from 'react-native-magnus';
+import { ScrollView } from 'react-native-gesture-handler';
+import { useTranslation } from 'react-i18next';
+
+import { RootStackParamList } from '../types';
+
+export default function AlertModalScreen({
+  navigation,
+  route,
+}: StackScreenProps<RootStackParamList, 'AlertModal'>) {
+  const { params } = route;
+  const { title, msg, hasCancel, submitText, submitEvent } = params;
+  const { t } = useTranslation();
+
+  return (
+    <Div flex={1} bg="black600" alignItems="center" justifyContent="center">
+      <Animatable.View animation="slideInUp" duration={300}>
+        <Div bg="white" p={10} rounded="sm" minW={287}>
+          <Text fontSize="lg" textAlign="center">
+            {title || t('ti-shi')}
+          </Text>
+
+          <Text fontSize="md" p={10} textAlign="center">
+            {msg}
+          </Text>
+
+          <Div row py={10} mt={10}>
+            <Button
+              flex={1}
+              mx={3}
+              rounded="xs"
+              bg="white"
+              color="black"
+              borderWidth={1}
+              borderColor="yellow500"
+              fontSize="sm"
+              onPress={() => navigation.goBack()}
+            >
+              {hasCancel ? t('qu-xiao') : submitText || t('que-ren')}
+            </Button>
+            <Button
+              flex={1}
+              mx={3}
+              rounded="xs"
+              bg="yellow500"
+              fontSize="sm"
+              onPress={() => {
+                if (submitEvent) {
+                  submitEvent();
+                }
+                navigation.goBack();
+              }}
+            >
+              {submitText || t('que-ren')}
+            </Button>
+          </Div>
+        </Div>
+      </Animatable.View>
+    </Div>
+  );
+}

+ 0 - 124
models/User.ts

@@ -1,124 +0,0 @@
-import request from '../utils/RequestUtils';
-import { addAsyncStorage, removeAsyncStorage } from '../utils/AsyncStorageUtils';
-// import submitPhone from '../Utils/FormUtils';
-
-const user = (now) => ({
-    id: null,
-    userInfo: {},
-    initialRouteName: '',
-    getUser() {
-        return request
-            .get('/user/my')
-            .then((res) => {
-                now({
-                    id: res.id,
-                    userInfo: res
-                });
-                return Promise.resolve();
-            })
-            .catch((e) => {
-                now({
-                    id: 0
-                });
-                return Promise.reject(e);
-            });
-    },
-    loginByPsd(phone, password) {
-        const { loading, warnning, success } = now(Toast);
-        loading();
-        return request
-            .post('/auth/login', {
-                data: {
-                    username: submitPhone(phone),
-                    password
-                },
-                requestType: 'form'
-            })
-            .then((res) => {
-                return addAsyncStorage('token', res);
-            })
-            .then(() => {
-                const { getUser } = now();
-                return getUser();
-            })
-            .then(() => {
-                success('登录成功');
-            })
-            .catch((e) => {
-                warnning(e.error);
-            });
-    },
-    logout() {
-        removeAsyncStorage('token').then(() => {
-            // 清除用户信息
-            now({ id: 0, userInfo: {} });
-            success('退出成功');
-        });
-    },
-    uploadInfo({ ...info }) {
-        const { id, getUser } = now();
-        const { loading, warnning, success } = now(Toast);
-        loading();
-        return request
-            .post('/user/save', {
-                data: {
-                    ...info,
-                    id
-                }
-            })
-            .then(() => {
-                return getUser();
-            })
-            .then(() => {
-                success('更新成功');
-                return Promise.resolve();
-            })
-            .catch((e) => {
-                warnning(e.error);
-            });
-    },
-    loginByRegister(phone, password) {
-        return request
-            .post('/auth/loginByRegister', {
-                data: {
-                    phone: submitPhone(phone),
-                    password
-                },
-                requestType: 'form'
-            })
-            .then(() => {
-                return request.post('/auth/login', {
-                    data: {
-                        username: submitPhone(phone),
-                        password
-                    },
-                    requestType: 'form'
-                });
-            })
-            .then((res) => {
-                return addAsyncStorage('token', res);
-            })
-            .then(() => {
-                const { getUser } = now();
-                return getUser();
-            })
-            .then(() => {
-                success('注册成功');
-            })
-            .catch((e) => {
-                warnning(e.error);
-            });
-    },
-    getInit() {
-        const { getUser } = now();
-        return getUser()
-            .then(() => {
-                now({ initialRouteName: 'Root' });
-            })
-            .catch(() => {
-                now({ initialRouteName: 'Login' });
-            });
-    }
-});
-
-export default user;

+ 8 - 3
navigation/BottomTabNavigator.tsx

@@ -17,19 +17,24 @@ export default function BottomTabNavigator() {
   return (
     <BottomTab.Navigator
       initialRouteName="TabOne"
-      tabBarOptions={{ activeTintColor: Colors[colorScheme].tint }}>
+      tabBarOptions={{ activeTintColor: Colors[colorScheme].tint }}
+    >
       <BottomTab.Screen
         name="TabOne"
         component={TabOneNavigator}
         options={{
-          tabBarIcon: ({ color }) => <TabBarIcon name="ios-code" color={color} />,
+          tabBarIcon: ({ color }) => (
+            <TabBarIcon name="ios-code" color={color} />
+          ),
         }}
       />
       <BottomTab.Screen
         name="TabTwo"
         component={TabTwoNavigator}
         options={{
-          tabBarIcon: ({ color }) => <TabBarIcon name="ios-code" color={color} />,
+          tabBarIcon: ({ color }) => (
+            <TabBarIcon name="ios-code" color={color} />
+          ),
         }}
       />
     </BottomTab.Navigator>

+ 19 - 19
navigation/LinkingConfiguration.ts

@@ -1,25 +1,25 @@
 import * as Linking from 'expo-linking';
 
 export default {
-    prefixes: [Linking.makeUrl('/')],
-    config: {
+  prefixes: [Linking.makeUrl('/')],
+  config: {
+    screens: {
+      Root: {
         screens: {
-            Root: {
-                screens: {
-                    TabOne: {
-                        screens: {
-                            TabOneScreen: 'one'
-                        }
-                    },
-                    TabTwo: {
-                        screens: {
-                            TabTwoScreen: 'two'
-                        }
-                    }
-                }
+          TabOne: {
+            screens: {
+              TabOneScreen: 'one',
             },
-            Login: '*',
-            NotFound: '*'
-        }
-    }
+          },
+          TabTwo: {
+            screens: {
+              TabTwoScreen: 'two',
+            },
+          },
+        },
+      },
+      Login: '*',
+      NotFound: '*',
+    },
+  },
 };

+ 104 - 0
navigation/MainStackNavigator.tsx

@@ -0,0 +1,104 @@
+import {
+  NavigationContainer,
+  DefaultTheme,
+  DarkTheme,
+} from '@react-navigation/native';
+import {
+  createStackNavigator,
+  CardStyleInterpolators,
+} from '@react-navigation/stack';
+import * as React from 'react';
+import { ColorSchemeName } from 'react-native';
+import useModel from 'flooks';
+import { useTranslation } from 'react-i18next';
+
+//登录模块
+import LoginScreen from '../login/LoginScreen';
+import RegisterScreen from '../login/RegisterScreen';
+import CertificationScreen from '../login/CertificationScreen';
+import TransportationScreen from '../login/TransportationScreen';
+import ApplyLocationScreen from '../login/ApplyLocationScreen';
+import AuditResultScreen from '../login/AuditResultScreen';
+
+//地图模块
+import SearchMapScreen from '../map/SearchMapScreen';
+
+import { RootStackParamList } from '../types';
+import BottomTabNavigator from './BottomTabNavigator';
+import LinkingConfiguration from './LinkingConfiguration';
+
+import User from '../stores/User';
+
+const MainStack = createStackNavigator<RootStackParamList>();
+export default function Navigation({
+  colorScheme,
+}: {
+  colorScheme: ColorSchemeName;
+}) {
+  const { initialRouteName } = useModel(User, ['initialRouteName']);
+  const { t } = useTranslation();
+  return (
+    <MainStack.Navigator
+      initialRouteName={initialRouteName}
+      screenOptions={{
+        cardOverlayEnabled: true,
+        cardStyle: { backgroundColor: '#eee', flex: 1 },
+        contentStyle: { backgroundColor: '#eee', flex: 1 },
+        gestureEnabled: true,
+        stackPresentation: 'push',
+        cardStyleInterpolator: CardStyleInterpolators.forHorizontalIOS,
+        headerStyle: {
+          backgroundColor: '#FFC21C',
+        },
+        headerTintColor: '#fff',
+        headerTitleStyle: {
+          fontWeight: 'bold',
+        },
+        headerTitleAlign: 'center',
+        headerBackTitleVisible: false,
+      }}
+    >
+      <MainStack.Screen
+        name="Root"
+        component={BottomTabNavigator}
+        options={{ headerShown: false }}
+      />
+      <MainStack.Screen
+        name="Login"
+        component={LoginScreen}
+        options={{ headerShown: false }}
+      />
+      <MainStack.Screen
+        name="Register"
+        component={RegisterScreen}
+        options={{ title: t('qi-shou-ke-hu-duan') }}
+      />
+      <MainStack.Screen
+        name="Certification"
+        component={CertificationScreen}
+        options={{ title: t('shen-fen-ren-zheng-xin-xi') }}
+      />
+      <MainStack.Screen
+        name="Transportation"
+        component={TransportationScreen}
+        options={{ title: t('song-can-jiao-tong-gong-ju-xin-xi') }}
+      />
+      <MainStack.Screen
+        name="ApplyLocation"
+        component={ApplyLocationScreen}
+        options={{ title: t('xuan-ze-gong-zuo-di-dian') }}
+      />
+      <MainStack.Screen
+        name="AuditResult"
+        component={AuditResultScreen}
+        options={{ title: t('qi-shou-shen-he') }}
+      />
+
+      <MainStack.Screen
+        name="SearchMap"
+        options={{ title: t('xuan-ze-di-dian') }}
+        component={SearchMapScreen}
+      />
+    </MainStack.Navigator>
+  );
+}

+ 60 - 76
navigation/index.tsx

@@ -1,95 +1,79 @@
 import {
-    NavigationContainer,
-    DefaultTheme,
-    DarkTheme,
-    CommonActions
+  NavigationContainer,
+  DefaultTheme,
+  DarkTheme,
+  CommonActions,
 } from '@react-navigation/native';
-import { createStackNavigator, CardStyleInterpolators } from '@react-navigation/stack';
+import { createStackNavigator } from '@react-navigation/stack';
 import * as React from 'react';
 import { ColorSchemeName } from 'react-native';
+import { useTranslation } from 'react-i18next';
 import useModel from 'flooks';
 
-import NotFoundScreen from '../screens/NotFoundScreen';
+//弹窗模块
+import AlertModalScreen from '../modals/AlertModalScreen';
 
-import LoginScreen from '../login/LoginScreen';
+//主堆栈
+import MainStackNavigator from './MainStackNavigator';
 
 import { RootStackParamList } from '../types';
-import BottomTabNavigator from './BottomTabNavigator';
 import LinkingConfiguration from './LinkingConfiguration';
 
-import User from '../models/User';
+import User from '../stores/User';
 
-// If you are not familiar with React Navigation, we recommend going through the
-// "Fundamentals" guide: https://reactnavigation.org/docs/getting-started
-export default function Navigation({ colorScheme }: { colorScheme: ColorSchemeName }) {
-    const { initialRouteName } = useModel(User, ['initialRouteName']);
-    const navRef = React.useRef();
+export default function Navigation({
+  colorScheme,
+}: {
+  colorScheme: ColorSchemeName;
+}) {
+  const { initialRouteName } = useModel(User, ['initialRouteName']);
+  const navRef = React.useRef();
+  React.useEffect(() => {
+    if (navRef.current) {
+      navRef.current.dispatch(
+        CommonActions.reset({
+          index: 0,
+          routes: [
+            {
+              name: 'Main',
+              screen: __DEV__ ? 'Certification' : initialRouteName,
+            },
+          ],
+        })
+      );
+    }
+  }, [navRef, initialRouteName]);
 
-    React.useEffect(() => {
-        if (navRef.current) {
-            navRef.current.dispatch(
-                CommonActions.reset({
-                    index: 0,
-                    routes: [
-                        {
-                            name: initialRouteName
-                            // name: 'Search',
-                        }
-                    ]
-                })
-            );
-        }
-    }, [navRef, initialRouteName]);
-
-    return (
-        <NavigationContainer
-            ref={navRef}
-            linking={LinkingConfiguration}
-            theme={colorScheme === 'dark' ? DarkTheme : DefaultTheme}
-        >
-            <RootNavigator />
-        </NavigationContainer>
-    );
+  return (
+    <NavigationContainer
+      ref={navRef}
+      linking={LinkingConfiguration}
+      theme={colorScheme === 'dark' ? DarkTheme : DefaultTheme}
+    >
+      <RootNavigator />
+    </NavigationContainer>
+  );
 }
 
-// A root stack navigator is often used for displaying modals on top of all other content
-// Read more here: https://reactnavigation.org/docs/modal
 const Stack = createStackNavigator<RootStackParamList>();
 
 function RootNavigator() {
-    const { initialRouteName } = useModel(User, ['initialRouteName']);
-    return (
-        <Stack.Navigator
-            initialRouteName={initialRouteName}
-            screenOptions={{
-                cardOverlayEnabled: true,
-                cardStyle: { backgroundColor: '#eee', flex: 1 },
-                contentStyle: { backgroundColor: '#eee', flex: 1 },
-                gestureEnabled: true,
-                stackPresentation: 'push',
-                cardStyleInterpolator: CardStyleInterpolators.forHorizontalIOS,
-                headerStyle: {
-                    backgroundColor: '#FFC21C'
-                },
-                headerTintColor: '#fff',
-                headerTitleStyle: {
-                    fontWeight: 'bold'
-                },
-                headerTitleAlign: 'center',
-                headerBackTitleVisible: false
-            }}
-        >
-            <Stack.Screen
-                name="Root"
-                component={BottomTabNavigator}
-                options={{ headerShown: false }}
-            />
-            <Stack.Screen name="Login" component={LoginScreen} options={{ headerShown: false }} />
-            <Stack.Screen
-                name="NotFound"
-                component={NotFoundScreen}
-                options={{ title: '未知页面' }}
-            />
-        </Stack.Navigator>
-    );
+  const { t } = useTranslation();
+  return (
+    <Stack.Navigator
+      mode="modal"
+      initialRouteName="Main"
+      screenOptions={{
+        headerShown: false,
+      }}
+    >
+      <Stack.Screen name="Main" component={MainStackNavigator} />
+
+      <Stack.Screen
+        name="AlertModal"
+        options={{ cardStyle: { backgroundColor: 'transparent' } }}
+        component={AlertModalScreen}
+      />
+    </Stack.Navigator>
+  );
 }

+ 87 - 1
package-lock.json

@@ -5399,6 +5399,24 @@
       "integrity": "sha512-Z7gxdwDy2RfVUcDDJdmjxujlIr/T/SI6Y5kPyt8/hnq0QxEwwmcXLEjtv3jWCHguI1Qpd1C7v87SBW2/M5P0+w==",
       "dev": true
     },
+    "eslint-config-prettier": {
+      "version": "6.11.0",
+      "resolved": "https://registry.npmjs.org/eslint-config-prettier/-/eslint-config-prettier-6.11.0.tgz",
+      "integrity": "sha512-oB8cpLWSAjOVFEJhhyMZh6NOEOtBVziaqdDQ86+qhDHFbZXoRTM7pNSvFRfW/W/L/LrQ38C99J5CGuRBBzBsdA==",
+      "dev": true,
+      "requires": {
+        "get-stdin": "^6.0.0"
+      }
+    },
+    "eslint-plugin-prettier": {
+      "version": "3.1.4",
+      "resolved": "https://registry.npmjs.org/eslint-plugin-prettier/-/eslint-plugin-prettier-3.1.4.tgz",
+      "integrity": "sha512-jZDa8z76klRqo+TdGDTFJSavwbnWK2ZpqGKNZ+VvweMW516pDUMmQ2koXvxEE4JhzNvTv+radye/bWGBmA6jmg==",
+      "dev": true,
+      "requires": {
+        "prettier-linter-helpers": "^1.0.0"
+      }
+    },
     "eslint-plugin-react": {
       "version": "7.20.3",
       "resolved": "https://registry.npmjs.org/eslint-plugin-react/-/eslint-plugin-react-7.20.3.tgz",
@@ -6071,6 +6089,12 @@
       "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==",
       "dev": true
     },
+    "fast-diff": {
+      "version": "1.2.0",
+      "resolved": "https://registry.npmjs.org/fast-diff/-/fast-diff-1.2.0.tgz",
+      "integrity": "sha512-xJuoT5+L99XlZ8twedaRf6Ax2TgQVxvgZOYoPKqZufmJib0tL2tegPBOZb1pVNgIhlqDlA0eO0c3wBvQcmzx4w==",
+      "dev": true
+    },
     "fast-json-stable-stringify": {
       "version": "2.1.0",
       "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz",
@@ -6438,6 +6462,12 @@
       "integrity": "sha512-pjzuKtY64GYfWizNAJ0fr9VqttZkNiK2iS430LtIHzjBEr6bX8Am2zm4sW4Ro5wjWW5cAlRL1qAMTcXbjNAO2Q==",
       "dev": true
     },
+    "get-stdin": {
+      "version": "6.0.0",
+      "resolved": "https://registry.npmjs.org/get-stdin/-/get-stdin-6.0.0.tgz",
+      "integrity": "sha512-jp4tHawyV7+fkkSKyvjuLZswblUtz+SQKzSWnBbii16BuZksJlU1wuBYXY75r+duh/llF1ur6oNwi+2ZzjKZ7g==",
+      "dev": true
+    },
     "get-stream": {
       "version": "4.1.0",
       "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-4.1.0.tgz",
@@ -6601,6 +6631,14 @@
       "integrity": "sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==",
       "dev": true
     },
+    "html-parse-stringify2": {
+      "version": "2.0.1",
+      "resolved": "https://registry.npmjs.org/html-parse-stringify2/-/html-parse-stringify2-2.0.1.tgz",
+      "integrity": "sha1-3FZwtyksoVi3vJFsmmc1rIhyg0o=",
+      "requires": {
+        "void-elements": "^2.0.1"
+      }
+    },
     "http-errors": {
       "version": "1.7.3",
       "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.7.3.tgz",
@@ -6640,6 +6678,14 @@
       "resolved": "https://registry.npmjs.org/i18n-js/-/i18n-js-3.7.1.tgz",
       "integrity": "sha512-xrRzCeda5ZC0u0yRN+dMdidtda0N+f7t7Pek0ajWb+iyKqSGdrMmuBtbNpWJWY5N4Th0cxbp/BR57zSPdrM3Rw=="
     },
+    "i18next": {
+      "version": "19.6.2",
+      "resolved": "https://registry.npmjs.org/i18next/-/i18next-19.6.2.tgz",
+      "integrity": "sha512-Zyd/Z32FY+sD+Eg6sLj5DeDSlrIN3WZ4onuOBRGcjDx/rvodsyUZ9TJ2Y+3aD9Vu8MPbiMU2WesIER/rs1ioyw==",
+      "requires": {
+        "@babel/runtime": "^7.10.1"
+      }
+    },
     "iconv-lite": {
       "version": "0.6.2",
       "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.2.tgz",
@@ -12278,7 +12324,17 @@
     "prettier": {
       "version": "2.0.5",
       "resolved": "https://registry.npmjs.org/prettier/-/prettier-2.0.5.tgz",
-      "integrity": "sha512-7PtVymN48hGcO4fGjybyBSIWDsLU4H4XlvOHfq91pz9kkGlonzwTfYkaIEwiRg/dAJF9YlbsduBAgtYLi+8cFg=="
+      "integrity": "sha512-7PtVymN48hGcO4fGjybyBSIWDsLU4H4XlvOHfq91pz9kkGlonzwTfYkaIEwiRg/dAJF9YlbsduBAgtYLi+8cFg==",
+      "dev": true
+    },
+    "prettier-linter-helpers": {
+      "version": "1.0.0",
+      "resolved": "https://registry.npmjs.org/prettier-linter-helpers/-/prettier-linter-helpers-1.0.0.tgz",
+      "integrity": "sha512-GbK2cP9nraSSUF9N2XwUwqfzlAFlMNYYl+ShE/V+H8a9uNl/oUqB1w2EL54Jh0OlyRSd8RfWYJ3coVS4TROP2w==",
+      "dev": true,
+      "requires": {
+        "fast-diff": "^1.1.2"
+      }
     },
     "pretty-format": {
       "version": "23.6.0",
@@ -12423,6 +12479,15 @@
         "scheduler": "^0.17.0"
       }
     },
+    "react-i18next": {
+      "version": "11.7.0",
+      "resolved": "https://registry.npmjs.org/react-i18next/-/react-i18next-11.7.0.tgz",
+      "integrity": "sha512-8tvVkpuxQlubcszZON+jmoCgiA9gCZ74OAYli9KChPhETtq8pJsANBTe9KRLRLmX3ubumgvidURWr0VvKz1tww==",
+      "requires": {
+        "@babel/runtime": "^7.3.1",
+        "html-parse-stringify2": "2.0.1"
+      }
+    },
     "react-is": {
       "version": "16.13.1",
       "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz",
@@ -13002,6 +13067,22 @@
         "react-timer-mixin": "^0.13.4"
       }
     },
+    "react-native-webview": {
+      "version": "9.4.0",
+      "resolved": "https://registry.npmjs.org/react-native-webview/-/react-native-webview-9.4.0.tgz",
+      "integrity": "sha512-BBOFUuza0p04+7fNi7TJmB0arpDJzGxHYwTCgI4vj5n/fl7u4jbm7ETp88mf7lo9lP6C6HGLo38KnEy1aXCQkg==",
+      "requires": {
+        "escape-string-regexp": "2.0.0",
+        "invariant": "2.2.4"
+      },
+      "dependencies": {
+        "escape-string-regexp": {
+          "version": "2.0.0",
+          "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-2.0.0.tgz",
+          "integrity": "sha512-UpzcLCXolUWcNu5HtVMHYdXJjArjsF9C0aNnquZYY4uW/Vu0miy5YoWvbV345HauVvcAUnpRuhMMcqTcGOY2+w=="
+        }
+      }
+    },
     "react-refresh": {
       "version": "0.4.3",
       "resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.4.3.tgz",
@@ -14710,6 +14791,11 @@
       "resolved": "https://registry.npmjs.org/vlq/-/vlq-1.0.1.tgz",
       "integrity": "sha512-gQpnTgkubC6hQgdIcRdYGDSDc+SaujOdyesZQMv6JlfQee/9Mp0Qhnys6WxDWvQnL5WZdT7o2Ul187aSt0Rq+w=="
     },
+    "void-elements": {
+      "version": "2.0.1",
+      "resolved": "https://registry.npmjs.org/void-elements/-/void-elements-2.0.1.tgz",
+      "integrity": "sha1-wGavtYK7HLQSjWDqkjkulNXp2+w="
+    },
     "w3c-hr-time": {
       "version": "1.0.2",
       "resolved": "https://registry.npmjs.org/w3c-hr-time/-/w3c-hr-time-1.0.2.tgz",

+ 7 - 2
package.json

@@ -38,10 +38,11 @@
     "expo-web-browser": "~8.3.1",
     "flooks": "^3.0.0",
     "i18n-js": "^3.7.1",
-    "prettier": "^2.0.5",
+    "i18next": "^19.6.2",
     "qs": "^6.9.4",
     "react": "~16.11.0",
     "react-dom": "~16.11.0",
+    "react-i18next": "^11.7.0",
     "react-native": "https://github.com/expo/react-native/archive/sdk-38.0.2.tar.gz",
     "react-native-animatable": "^1.3.3",
     "react-native-gesture-handler": "~1.6.0",
@@ -55,7 +56,8 @@
     "react-native-vector-icons": "^7.0.0",
     "react-native-web": "~0.11.7",
     "teaset": "^0.7.4",
-    "umi-request": "^1.3.5"
+    "umi-request": "^1.3.5",
+    "react-native-webview": "9.4.0"
   },
   "devDependencies": {
     "@babel/core": "^7.8.6",
@@ -67,8 +69,11 @@
     "babel-preset-expo": "~8.1.0",
     "eslint": "^7.4.0",
     "eslint-config-alloy": "^3.7.3",
+    "eslint-config-prettier": "^6.11.0",
+    "eslint-plugin-prettier": "^3.1.4",
     "eslint-plugin-react": "^7.20.3",
     "jest-expo": "~38.0.0",
+    "prettier": "2.0.5",
     "typescript": "^3.9.6"
   },
   "private": true

+ 4 - 1
screens/NotFoundScreen.tsx

@@ -10,7 +10,10 @@ export default function NotFoundScreen({
   return (
     <View style={styles.container}>
       <Text style={styles.title}>This screen doesn't exist.</Text>
-      <TouchableOpacity onPress={() => navigation.replace('Root')} style={styles.link}>
+      <TouchableOpacity
+        onPress={() => navigation.replace('Root')}
+        style={styles.link}
+      >
         <Text style={styles.linkText}>Go to home screen!</Text>
       </TouchableOpacity>
     </View>

+ 5 - 1
screens/TabOneScreen.tsx

@@ -8,7 +8,11 @@ export default function TabOneScreen() {
   return (
     <View style={styles.container}>
       <Text style={styles.title}>Tab One</Text>
-      <View style={styles.separator} lightColor="#eee" darkColor="rgba(255,255,255,0.1)" />
+      <View
+        style={styles.separator}
+        lightColor="#eee"
+        darkColor="rgba(255,255,255,0.1)"
+      />
       <EditScreenInfo path="/screens/TabOneScreen.tsx" />
     </View>
   );

+ 135 - 0
stores/User.ts

@@ -0,0 +1,135 @@
+import request from '../utils/RequestUtils';
+import {
+  addAsyncStorage,
+  removeAsyncStorage,
+  getAsyncStorage,
+} from '../utils/AsyncStorageUtils';
+import {
+  toastShow,
+  toastHide,
+  toastInfo,
+  toastSuccess,
+} from '../utils/SystemUtils';
+
+const user = (now) => ({
+  id: null,
+  userInfo: {},
+  initialRouteName: '',
+  verifiedInfo: {},
+  riderInfo: {},
+  getUser() {
+    let userInfo = {
+      id: 0,
+    };
+    return request
+      .get('/user/my')
+      .then((res) => {
+        if (res.identity === 'RIDER') {
+          userInfo = res;
+          return request.get('/rider/my');
+        } else {
+          return Promise.reject();
+        }
+      })
+      .then((res) => {
+        now({
+          riderInfo: res,
+        });
+        return Promise.resolve();
+      })
+      .catch((e) => {
+        if (userInfo.id) {
+          return request.get('/verified/my');
+        } else {
+          now({
+            id: userInfo.id,
+            userInfo,
+          });
+          return Promise.reject(userInfo.id);
+        }
+      })
+      .then((res) => {
+        now({
+          id: userInfo.id,
+          userInfo,
+        });
+        if (res.id) {
+          now({
+            verifiedInfo: res,
+          });
+          return Promise.reject(userInfo.id);
+        } else {
+          return Promise.reject(userInfo.id);
+        }
+      });
+  },
+  logout() {
+    removeAsyncStorage('token').then(() => {
+      // 清除用户信息
+      now({ id: 0, userInfo: {} });
+      success('退出成功');
+    });
+  },
+  uploadInfo({ ...info }) {
+    const { id, getUser } = now();
+    const { loading, warnning, success } = now(Toast);
+    loading();
+    return request
+      .post('/user/save', {
+        data: {
+          ...info,
+          id,
+        },
+      })
+      .then(() => {
+        return getUser();
+      })
+      .then(() => {
+        success('更新成功');
+        return Promise.resolve();
+      })
+      .catch((e) => {
+        warnning(e.error);
+      });
+  },
+  getInit() {
+    const { getUser } = now();
+    return getUser()
+      .then(() => {
+        now({ initialRouteName: 'Root' });
+      })
+      .catch((id) => {
+        const { verifiedInfo, riderInfo } = now();
+        console.log(verifiedInfo);
+        if (id && riderInfo.id) {
+          if (riderInfo.status === 'PENDING') {
+            now({ initialRouteName: 'AuditResult' });
+          } else if (riderInfo.status === 'DENY') {
+            return getAsyncStorage('seApply');
+          } else {
+            return getAsyncStorage('riderIsApply');
+          }
+        } else if (id && verifiedInfo.id) {
+          now({ initialRouteName: 'Transportation' });
+        } else if (id) {
+          now({ initialRouteName: 'Certification' });
+        } else {
+          now({ initialRouteName: 'Login' });
+        }
+      })
+      .then((res) => {
+        const { riderInfo } = now();
+        console.log(res);
+        if (
+          (res && riderInfo.status === 'PASS' && res.toString() === 'true') ||
+          (!res && riderInfo.status === 'DENY')
+        ) {
+          now({ initialRouteName: 'AuditResult' });
+        } else if (res) {
+          now({ initialRouteName: res });
+        }
+      });
+  },
+});
+
+export default user;

+ 18 - 10
types.tsx

@@ -1,23 +1,31 @@
 export type RootStackParamList = {
-    Root: undefined;
-    NotFound: undefined;
-    Login: undefined;
+  Main: undefined;
+  AlertModal: {
+    msg: string;
+    title: string;
+    hasCancel: Boolean;
+    submitEvent: Event;
+  };
+};
+export type MainStackParamList = {
+  Login: undefined;
+  Register: undefined;
+  Root: undefined;
 };
-
 export type BottomTabParamList = {
-    TabOne: undefined;
-    TabTwo: undefined;
+  TabOne: undefined;
+  TabTwo: undefined;
 };
 
 export type LoginTabParamList = {
-    Psd: undefined;
-    Code: undefined;
+  Psd: undefined;
+  Code: undefined;
 };
 
 export type TabOneParamList = {
-    TabOneScreen: undefined;
+  TabOneScreen: undefined;
 };
 
 export type TabTwoParamList = {
-    TabTwoScreen: undefined;
+  TabTwoScreen: undefined;
 };

+ 6 - 6
utils/AsyncStorageUtils.ts

@@ -1,16 +1,16 @@
 import { AsyncStorage } from 'react-native';
 
 const addAsyncStorage = async (key, value) => {
-    await AsyncStorage.setItem(key, value);
-    return true;
+  await AsyncStorage.setItem(key, value);
+  return true;
 };
 const removeAsyncStorage = async (key) => {
-    await AsyncStorage.removeItem(key);
-    return true;
+  await AsyncStorage.removeItem(key);
+  return true;
 };
 const getAsyncStorage = async (key) => {
-    const val = await AsyncStorage.getItem(key);
-    return val;
+  const val = await AsyncStorage.getItem(key);
+  return val;
 };
 
 export { addAsyncStorage, removeAsyncStorage, getAsyncStorage };

+ 99 - 0
utils/MapUtils.ts

@@ -0,0 +1,99 @@
+import * as Location from 'expo-location';
+import * as Permissions from 'expo-permissions';
+import * as TaskManager from 'expo-task-manager';
+import request from './RequestUtils';
+import { alert } from './TotastUtils';
+
+const key = 'c4faf80125b298f93bbc1477db10e69c';
+const tengxunKey = 'GLFBZ-ZR2W6-76XSA-MF7CQ-GDJ6Z-6FB5K';
+
+let lat = '31.981746';
+let lng = '118.734661';
+
+async function getLocation() {
+  return Location.requestPermissionsAsync()
+    .then((res) => {
+      if (res.status === 'granted') {
+        return Location.getCurrentPositionAsync({
+          enableHighAccuracy: true,
+        });
+      } else {
+        return Promise.reject();
+      }
+    })
+    .then(({ coords }) => {
+      lat = coords.latitude;
+      lng = coords.longitude;
+
+      return request.get(
+        `https://apis.map.qq.com/ws/coord/v1/translate?locations=${lat},${lng}&type=1&key=${tengxunKey}&get_poi=1`,
+        {
+          prefix: '',
+          mode: 'no-cors',
+        }
+      );
+    })
+    .then((res) => {
+      if (res.status === 0) {
+        lat = res.locations[0].lat;
+        lng = res.locations[0].lng;
+      }
+      return request.get(
+        `https://apis.map.qq.com/ws/geocoder/v1/?location=${lat},${lng}&key=${tengxunKey}&get_poi=1`,
+        {
+          prefix: '',
+          mode: 'no-cors',
+        }
+      );
+    })
+    .then((res) => {
+      if (res.status === 0) {
+        return Promise.resolve({
+          addressName: res.result.address,
+          location: res.result.location,
+        });
+      } else {
+        return Promise.reject();
+      }
+    })
+    .catch((e) => {
+      return Promise.resolve({
+        addressName: '定位失败',
+        location: {
+          lat,
+          lng,
+        },
+      });
+    });
+}
+
+function getSearch(searchKey, boundary) {
+  return request
+    .get(
+      `https://apis.map.qq.com/ws/place/v1/search?boundary=${boundary}&keyword=${searchKey}&page_size=20&page_index=1&orderby=_distance&key=${tengxunKey}`,
+      {
+        prefix: '',
+        mode: 'no-cors',
+      }
+    )
+    .then((res) => {
+      if (res.status === 0) {
+        return Promise.resolve({
+          pois: res.data,
+        });
+      } else {
+        return Promise.reject();
+      }
+    })
+    .catch(() => {
+      return Promise.resolve({
+        pois: [],
+      });
+    });
+}
+
+function mapMarks(params) {
+  return `https://restapi.amap.com/v3/staticmap?zoom=15&size=500*500&paths=10,0x0000ff,1,,:116.31604,39.96491;116.320816,39.966606;116.321785,39.966827;116.32361,39.966957&key=${key}`;
+}
+
+export { getLocation, getSearch };

+ 41 - 40
utils/RequestUtils.ts

@@ -4,49 +4,50 @@ import { getAsyncStorage } from './AsyncStorageUtils';
 
 const baseUrl = 'http://dingdong.izouma.com';
 const request = extend({
-    prefix: baseUrl,
-    // mode: 'no-cors',
-    credentials: 'include',
-    errorHandler: (error) => {
-        let errorInfo = '';
-        if (error.response) {
-            errorInfo = error.data;
-        } else {
-            errorInfo = error.message;
-        }
-        throw errorInfo;
+  prefix: baseUrl,
+  // mode: 'no-cors',
+  credentials: 'include',
+  errorHandler: (error) => {
+    console.log(error);
+    let errorInfo = '';
+    if (error.response) {
+      errorInfo = error.data;
+    } else {
+      errorInfo = error.message;
     }
+    throw errorInfo;
+  },
 });
 
 request.interceptors.request.use(
-    (url, options) => {
-        if (options.requestType === 'form') {
-            options.headers = {
-                Accept: 'application/json',
-                'Content-Type': 'application/x-www-form-urlencoded;charset=UTF-8',
-                ...options.headers
-            };
-            options.data = qs.stringify(options.data);
-        }
-        return getAsyncStorage('token').then((token) => {
-            const headers = {
-                'Access-Control-Allow-Origin': '*',
-                'Content-Type': 'application/json',
-                'X-Content-Type-Options': 'nosniff',
-                ...options.headers
-            };
-            if (token) {
-                headers.Authorization = `Bearer ${token}`;
-            }
-            return Promise.resolve({
-                url,
-                options: {
-                    ...options,
-                    headers
-                }
-            });
-        });
-    },
-    { global: true }
+  (url, options) => {
+    if (options.requestType === 'form') {
+      options.headers = {
+        Accept: 'application/json',
+        'Content-Type': 'application/x-www-form-urlencoded;charset=UTF-8',
+        ...options.headers,
+      };
+      options.data = qs.stringify(options.data);
+    }
+    return getAsyncStorage('token').then((token) => {
+      const headers = {
+        'Access-Control-Allow-Origin': '*',
+        'Content-Type': 'application/json',
+        'X-Content-Type-Options': 'nosniff',
+        ...options.headers,
+      };
+      if (token) {
+        headers.Authorization = `Bearer ${token}`;
+      }
+      return Promise.resolve({
+        url,
+        options: {
+          ...options,
+          headers,
+        },
+      });
+    });
+  },
+  { global: true }
 );
 export default request;

+ 35 - 0
utils/RiderInfoUtils.ts

@@ -0,0 +1,35 @@
+const RiderMap = new Map([
+  [
+    '摩托车',
+    {
+      name: 'mo-tuo-che',
+    },
+  ],
+  [
+    '电动车',
+    {
+      name: 'dian-dong-che',
+    },
+  ],
+  [
+    '其他',
+    {
+      name: 'qi-ta',
+      hasInput: true,
+    },
+  ],
+  [
+    '没有',
+    {
+      name: 'mei-you',
+    },
+  ],
+]);
+
+const ApplyStatus = new Map([
+  ['DENY', { name: '未通过' }],
+  ['PASS', { name: '通过' }],
+  ['PENDING', { name: '审核中' }],
+]);
+
+export { RiderMap };

+ 36 - 0
utils/SystemUtils.ts

@@ -0,0 +1,36 @@
+import i18n from '../i18n';
+import * as Linking from 'expo-linking';
+
+// import { Toast, ModalIndicator } from 'teaset';
+import { Platform } from 'react-native';
+
+function connect(navigation) {
+  navigation.navigate('AlertModal', {
+    msg: `${i18n.t('tips1')}:0512-109291882?`,
+    hasCancel: true,
+    submitText: i18n.t('li-ji-bo-da'),
+    submitEvent: () => {
+      Linking.openURL('tel:+123456789');
+    },
+  });
+}
+
+function toastShow() {
+  if (Platform.OS === 'web') return;
+  ModalIndicator.show(i18n.t('jia-zai-zhong'));
+}
+function toastHide() {
+  if (Platform.OS === 'web') return;
+  ModalIndicator.hide();
+}
+
+function toastSuccess(title) {
+  if (Platform.OS === 'web') return;
+  Toast.success(title);
+}
+function toastInfo(title) {
+  if (Platform.OS === 'web') return;
+  Toast.message(title);
+}
+
+export { connect, toastShow, toastHide, toastInfo, toastSuccess };