Initial commit

This commit is contained in:
wuzhenyu
2025-08-27 19:20:10 +08:00
parent 9f5b8552a0
commit 8d7fd34e1a
35 changed files with 3983 additions and 114 deletions

130
App.jsx Normal file
View File

@@ -0,0 +1,130 @@
import { lazy, useEffect, Suspense, useState } from 'react';
import {
NavigationContainer,
useNavigation,
useRoute,
} from '@react-navigation/native';
import { createStackNavigator } from '@react-navigation/stack';
import { createBottomTabNavigator } from '@react-navigation/bottom-tabs';
import { GestureHandlerRootView } from 'react-native-gesture-handler';
// import {I18nextProvider} from 'react-i18next';
import './locales/i18n.js';
import {
Grid,
Icon,
Provider,
SearchBar,
Text,
Toast,
} from '@ant-design/react-native';
import { AuthProvider, useContextHook } from './components/AuthContext/index';
import Login from '@/views/Login/index';
import Guide from '@/views/Guide/index';
import HomeIndex from '@/views/Home/Index/index';
import HomeInventory from '@/views/Home/Inventory/index';
import HomeProfile from '@/views/Home/Profile/index';
const Tab = createBottomTabNavigator();
const Stack = createStackNavigator();
function HomeTabs() {
const { themeColor } = useContextHook();
return (
<Tab.Navigator
screenOptions={{
tabBarActiveTintColor: themeColor.primaryColor,
tabBarInactiveTintColor: themeColor.colorTextBase,
headerShown: false,
}}>
<Tab.Screen
name="index"
options={{
title: 'Home',
tabBarIcon: ({ color, focused }) => (
<Icon
name="home"
color={color}
size={28}
style={[{ marginBottom: -3 }]}
/>
),
}}
component={HomeIndex}
/>
<Tab.Screen
name="inventory"
options={{
title: 'Inventory',
tabBarIcon: ({ color, focused }) => (
<Icon
name="appstore"
color={color}
size={28}
style={[{ marginBottom: -3 }]}
/>
),
}}
component={HomeInventory}
/>
<Tab.Screen
name="profile"
options={{
title: 'Profile',
tabBarIcon: ({ color, focused }) => (
<Icon
name="setting"
color={color}
size={28}
style={[{ marginBottom: -3 }]}
/>
),
}}
component={HomeProfile}
/>
</Tab.Navigator>
);
}
function RootStack() {
return (
<Stack.Navigator>
<Stack.Screen
name="Guide"
options={{
headerShown: false,
}}
component={Guide}
/>
<Stack.Screen
name="Login"
options={{
headerShown: false,
}}
component={Login}
/>
<Stack.Screen
name="Home"
component={HomeTabs}
options={{
headerShown: false,
}}
/>
</Stack.Navigator>
);
}
export default function App() {
return (
<GestureHandlerRootView>
<AuthProvider>
<NavigationContainer>
<RootStack />
</NavigationContainer>
</AuthProvider>
</GestureHandlerRootView>
);
}

45
App.tsx
View File

@@ -1,45 +0,0 @@
/**
* Sample React Native App
* https://github.com/facebook/react-native
*
* @format
*/
import { NewAppScreen } from '@react-native/new-app-screen';
import { StatusBar, StyleSheet, useColorScheme, View } from 'react-native';
import {
SafeAreaProvider,
useSafeAreaInsets,
} from 'react-native-safe-area-context';
function App() {
const isDarkMode = useColorScheme() === 'dark';
return (
<SafeAreaProvider>
<StatusBar barStyle={isDarkMode ? 'light-content' : 'dark-content'} />
<AppContent />
</SafeAreaProvider>
);
}
function AppContent() {
const safeAreaInsets = useSafeAreaInsets();
return (
<View style={styles.container}>
<NewAppScreen
templateFileName="App.tsx"
safeAreaInsets={safeAreaInsets}
/>
</View>
);
}
const styles = StyleSheet.create({
container: {
flex: 1,
},
});
export default App;

View File

@@ -4,7 +4,7 @@
import React from 'react';
import ReactTestRenderer from 'react-test-renderer';
import App from '../App';
import App from '@/App';
test('renders correctly', async () => {
await ReactTestRenderer.act(() => {

Binary file not shown.

Binary file not shown.

View File

@@ -0,0 +1,13 @@
{
"migIndex": 1,
"data": [
{
"path": "node_modules/@ant-design/icons-react-native/fonts/antfill.ttf",
"sha1": "56960e7721fc92b62e0f7c4d131ffe34ed042c49"
},
{
"path": "node_modules/@ant-design/icons-react-native/fonts/antoutline.ttf",
"sha1": "66720607b7496a48f145425386b2082b73662fd1"
}
]
}

66
api/request.js Normal file
View File

@@ -0,0 +1,66 @@
// api.js
import axios from 'axios';
import AsyncStorage from '@/storage/index';
import {filterEmptyValue} from '@/utils/common';
import Env from '@/config/env';
// const BASE_URL = 'http://129.226.148.140:8000'
// 测试环境
// const BASE_URL = 'https://me.dreamwork.site/eladmin/';
// 正式环境
// const BASE_URL = 'https://mesystem.online/eladmin/'
// 创建 Axios 实例
const api = axios.create({
baseURL: `${Env.URL}${Env.BASE}`, // 设置你的基地址
timeout: 10000, // 设置请求超时时间
headers: {
'Content-Type': 'application/json',
},
});
// 请求拦截器
api.interceptors.request.use(
async config => {
// config.url = config.url.replace('mesapi', '');
// console.log('🚀 ~ config:', config);
try {
// 假设获取 token 是一个异步操作
const token = await AsyncStorage.getItem('appToken');
// 将 token 添加到请求头
// config.headers.Authorization = `Bearer ${token}`;
if (token && token.appToken) {
config.headers.Authorization = token.appToken;
config.headers.token = token.appToken; // 让每个请求携带自定义token 请根据实际情况自行修改
}
} catch (error) {
console.error('获取 token 失败:', error);
// 处理错误,比如可以抛出错误或者记录日志
// 也可以重试以获取 token视需求而定
}
// console.log('🚀 ~ config:', config);
config.params = filterEmptyValue(config.params || {});
if (config.headers['Content-Type'] != 'multipart/form-data') {
config.data = filterEmptyValue(config.data || {});
}
return config;
},
error => Promise.reject(error),
);
// 响应拦截器
api.interceptors.response.use(
response => {
return response.data;
},
error => {
// 可以在这里统一处理错误(如错误提示、日志记录等)
console.error('API response error:',error , error && error.response);
return Promise.reject(error);
},
);
export default api;

38
api/workflowOrder.js Normal file
View File

@@ -0,0 +1,38 @@
import request from './request'
export function getWorkflowOrderTableList(params={}) {
return request({
url: "mesapi/workflow/work_orders",
method: "get",
params
});
}
export function addWorkflowOrder(data = {}) {
return request({
url: "mesapi/workflow/work_orders",
method: "post",
data,
});
}
export function editWorkflowOrder(data = {}) {
return request({
url: `mesapi/workflow/work_orders/${data.work_order_id}`,
method: "put",
data,
});
}
export function delWorkflowOrder(ids = []) {
return request({
url: `mesapi/workflow/work_orders/${ids[0]}`,
method: "delete",
});
}
export function workflowOrderProcess(data = {}) {
return request({
url: `mesapi/workflow/work_orders/process/${data.work_order_id}?action=${data.action}`,
method: "post",
data
});
}

Binary file not shown.

Binary file not shown.

BIN
assets/images/logo.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.4 KiB

200
assets/styles/css.js Normal file
View File

@@ -0,0 +1,200 @@
import { StyleSheet, StatusBar } from 'react-native';
// create useStyles hooks
export const useStyles = (props = {}) =>
StyleSheet.create({ value: props }).value;
export const paddingTopStatusBarHeight = useStyles({
paddingTop: StatusBar.currentHeight || 0,
});
export const statusBarHeight = useStyles({
height: StatusBar.currentHeight || 0,
});
export const flexRow = useStyles({
display: 'flex',
flexDirection: 'row',
});
export const flexColumn = useStyles({
display: 'flex',
flexDirection: 'column',
});
export const flexSub = useStyles({
flex: 1,
});
export const flexShrink = useStyles({
flexShrink: 0,
});
export const flexWrap = useStyles({
flexWrap: 'wrap',
});
export const flexnoWrap = useStyles({
flexWrap: 'nowrap',
});
export const justifyStart = useStyles({
justifyContent: 'flex-start',
});
export const justifyEnd = useStyles({
justifyContent: 'flex-end',
});
export const justifyCenter = useStyles({
justifyContent: 'center',
});
export const justifyBetween = useStyles({
justifyContent: 'space-between',
});
export const justifyAround = useStyles({
justifyContent: 'space-around',
});
export const justifyEvenly = useStyles({
justifyContent: 'space-evenly',
});
export const alignItemsStart = useStyles({
alignItems: 'flex-start',
});
export const alignItemsEnd = useStyles({
alignItems: 'flex-end',
});
export const alignItemsCenter = useStyles({
alignItems: 'center',
});
export const alignItemsStretch = useStyles({
alignItems: 'stretch',
});
export const positionRelative = useStyles({
position: 'relative',
});
export const positionAbsolute = useStyles({
position: 'absolute',
});
export const positionSticky = useStyles({
position: 'sticky',
});
export const positionStatic = useStyles({
position: 'static',
});
export const positionFixed = useStyles({
position: 'fixed',
});
export const getColor = (color = {brandPrimary: '#ff6600', brandPrimaryTap: '#ff6600' }) => {
const brandPrimary = color.brandPrimary || '#108ee9';
const brandPrimaryTap = color.brandPrimaryTap || '#1284d6';
return {
transparent: 'transparent',
brandPrimary,
brandPrimaryTap,
// 文字色
color_text_base: color.color_text_base || '#000000', // 基本
color_text_base_inverse: color.color_text_base_inverse || '#ffffff', // 基本 _ 反色
color_text_placeholder: color.color_text_placeholder || '#bbbbbb', // 文本框提示
color_text_disabled: color.color_text_disabled || '#bbbbbb', // 失效
color_text_caption: color.color_text_caption || '#888888', // 辅助描述
color_text_paragraph: color.color_text_paragraph || '#333333', // 段落
color_link: color.color_link || brandPrimary, // 链接
color_icon_base: color.color_icon_base || '#cccccc', // 许多小图标的背景,比如一些小圆点,加减号
// 背景色
fill_body: color.fill_body || '#f5f5f9', // 页面背景
fill_base: color.fill_base || '#ffffff', // 组件默认背景
fill_tap: color.fill_tap || '#dddddd', // 组件默认背景 _ 按下
fill_disabled: color.fill_disabled || '#dddddd', // 通用失效背景
fill_mask: color.fill_mask || 'rgba(0, 0, 0, .4)', // 遮罩背景
fill_grey: color.fill_grey || '#f7f7f7',
// 全局/品牌色
brand_primary: color.brand_primary || brandPrimary,
brand_primary_tap: color.brand_primary_tap || brandPrimaryTap,
brand_success: color.brand_success || '#6abf47',
brand_warning: color.brand_warning || '#faad14',
brand_error: color.brand_error || '#f4333c', // 错误(form validate)
brand_important: color.brand_important || '#ff5b05', // 用于小红点
// 边框色
border_color_base: color.border_color_base || '#dddddd', // 基础的
border_color_thin: color.border_color_thin || '#eeeeee', // 更细的
primary_button_fill: color.primary_button_fill || brandPrimary,
primary_button_fill_tap: color.primary_button_fill_tap || brandPrimaryTap,
ghost_button_color: color.ghost_button_color || brandPrimary, // 同时应用于背景、文字颜色、边框色
ghost_button_fill_tap: color.ghost_button_fill_tap || `${brandPrimary}99`, // alpha 60%
warning_button_fill: color.warning_button_fill || '#e94f4f',
warning_button_fill_tap: color.warning_button_fill_tap || '#d24747',
// tab_bar
tab_bar_fill: color.tab_bar_fill || '#ebeeef',
// toast
toast_fill: color.toast_fill || 'rgba(0, 0, 0, .8)',
// search_bar
search_bar_fill: color.search_bar_fill || '#efeff4',
search_color_icon: color.search_color_icon || '#bbbbbb', // input search icon 的背景色
// notice_bar
notice_bar_fill: color.notice_bar_fill || '#fffada',
// checkbox
checkbox_fill_disabled: color.checkbox_fill_disabled || '#f5f5f5',
checkbox_border: color.checkbox_border || '#d9d9d9',
checkbox_border_disabled: color.checkbox_border_disabled || '#b9b9b9',
// switch
switch_unchecked: color.switch_unchecked || '#cccccc',
switch_unchecked_disabled: color.switch_unchecked_disabled || '#cccccc66', // switch_fill的40%透明度
// tooltip
tooltip_dark: color.tooltip_dark || 'rgba(0, 0, 0, 0.9)',
};
};
export const getColorStyles = color => {
return {
default: useStyles({
color: color.default,
}),
defaultBg: useStyles({
backgroundColor: color.default,
}),
primary: useStyles({
color: color.primary,
}),
primaryBg: useStyles({
backgroundColor: color.primary,
}),
};
};
export default {
statusBarHeight,
paddingTopStatusBarHeight,
flexRow,
flexColumn,
flexSub,
flexShrink,
flexWrap,
flexnoWrap,
justifyStart,
justifyEnd,
justifyCenter,
justifyBetween,
justifyAround,
justifyEvenly,
alignItemsStart,
alignItemsEnd,
alignItemsCenter,
alignItemsStretch,
positionRelative,
positionAbsolute,
getColor,
getColorStyles,
useStyles,
};

View File

@@ -1,3 +1,17 @@
module.exports = {
presets: ['module:@react-native/babel-preset'],
presets: [
'module:@react-native/babel-preset',
],
plugins: [
[
'module-resolver',
{
root: ['./'],
alias: {
'@': './'
},
},
],
['import', { libraryName: '@ant-design/react-native' }], // 与 Web 平台的区别是不需要设置 style
],
};

View File

@@ -0,0 +1,41 @@
import { createContext, useState, useContext } from 'react';
import { getColor } from '@/assets/styles/css';
import { Provider } from '@ant-design/react-native';
import { SafeAreaView } from 'react-native-safe-area-context';
import { StatusBar } from 'react-native';
StatusBar.setHidden(true);
// 创建一个上下文
const AuthContext = createContext();
export const AuthProvider = ({ children }) => {
const [user, setUser] = useState(null);
const hasPerm = (roles = []) => {
if (user && user.roles) {
return (user.roles || []).some(role => {
return (roles || []).includes(role);
});
} else {
return false
}
}
const currentLocale = 'zh-CN';
const themeColor = getColor();
return (
<Provider locale={currentLocale} theme={themeColor}>
<SafeAreaView style={{ flex: 1}}>
<AuthContext.Provider value={{ user, setUser, hasPerm, themeColor, currentLocale }}>
{children}
</AuthContext.Provider>
</SafeAreaView>
</Provider>
);
};
// 自定义 Hook 来使用 AuthContext
export const useContextHook = () => {
return useContext(AuthContext);
};

View File

@@ -0,0 +1,32 @@
import React, { useEffect, useRef } from 'react';
import { Animated, View } from 'react-native';
import { Icon } from '@ant-design/react-native'
const RotatingIcon = () => {
const spinAnim = useRef(new Animated.Value(0)).current; // 创建一个新的 Animated.Value 实例
useEffect(() => {
Animated.loop(
Animated.timing(spinAnim, {
toValue: 1,
duration: 800,
useNativeDriver: true, // 这是推荐的做法
})
).start();
}, [spinAnim]);
const spin = spinAnim.interpolate({
inputRange: [0, 1],
outputRange: ['0deg', '360deg'],
});
return (
<View style={{ alignItems: 'center', justifyContent: 'center', flex: 1 }}>
<Animated.View style={{ transform: [{ rotate: spin }] }}>
<Icon name="loading-3-quarters" size={24} color="#999999" />
</Animated.View>
</View>
);
};
export default RotatingIcon;

13
config/env.js Normal file
View File

@@ -0,0 +1,13 @@
const dev = {
URL: 'https://me.dreamwork.site',
BASE: '/eladmin/',
// URL: 'http://127.0.0.1:8000',
// BASE: '',
};
const prod = {
URL: 'https://mesystem.online',
BASE: '/eladmin/',
};
export default dev;

64
hooks/useRouter.js Normal file
View File

@@ -0,0 +1,64 @@
import {
useIsFocused,
useRoute,
useNavigation,
} from '@react-navigation/native';
import AsyncStorage from '@/storage/index';
export const useRouter = props => {
const router = useNavigation();
console.log('🚀 ~ useRouter ~ router:', router)
const isFocused = useIsFocused();
const route = useRoute();
const linkTo = (path, params) => {
router.navigate(path, params);
};
const goBack = () => {
router.goBack();
};
const setParams = params => {
router.setParams(params);
};
const getParams = () => {
return router.getState().params;
};
// 获取当前路由
const getCurrentRoute = () => {
return router.getState().routes[router.getState().index].name;
};
// 重置所有路由
const resetAllRoutes = (name) => {
router.reset({
index: 0,
routes: [{ name: name || 'Guide' }],
});
};
// 判断是否 登录了再跳转 如果 没有登录就去登录页面
const isLogin = () => {
const user = AsyncStorage.getItemAsync('user');
if (!user) {
router.navigate('Login');
}
};
return {
router,
isFocused,
route,
linkTo,
goBack,
setParams,
getParams,
isLogin,
getCurrentRoute,
resetAllRoutes,
};
};

View File

@@ -5,5 +5,6 @@
import { AppRegistry } from 'react-native';
import App from './App';
import { name as appName } from './app.json';
import 'react-native-gesture-handler';
AppRegistry.registerComponent(appName, () => App);

View File

@@ -11,6 +11,8 @@
13B07FBF1A68108700A75B9A /* Images.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 13B07FB51A68108700A75B9A /* Images.xcassets */; };
761780ED2CA45674006654EE /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 761780EC2CA45674006654EE /* AppDelegate.swift */; };
81AB9BB82411601600AC10FF /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 81AB9BB72411601600AC10FF /* LaunchScreen.storyboard */; };
9B06ED4A4A7343B390CE52D1 /* antfill.ttf in Resources */ = {isa = PBXBuildFile; fileRef = A47F5CC487484D8E9ACED7FB /* antfill.ttf */; };
1F775F769E4C47D2BE44D107 /* antoutline.ttf in Resources */ = {isa = PBXBuildFile; fileRef = FFCC4D9CCC40417BAB83BA33 /* antoutline.ttf */; };
/* End PBXBuildFile section */
/* Begin PBXFileReference section */
@@ -24,6 +26,8 @@
761780EC2CA45674006654EE /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = AppDelegate.swift; path = app/AppDelegate.swift; sourceTree = "<group>"; };
81AB9BB72411601600AC10FF /* LaunchScreen.storyboard */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.storyboard; name = LaunchScreen.storyboard; path = app/LaunchScreen.storyboard; sourceTree = "<group>"; };
ED297162215061F000B7C4FE /* JavaScriptCore.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = JavaScriptCore.framework; path = System/Library/Frameworks/JavaScriptCore.framework; sourceTree = SDKROOT; };
A47F5CC487484D8E9ACED7FB /* antfill.ttf */ = {isa = PBXFileReference; name = "antfill.ttf"; path = "../node_modules/@ant-design/icons-react-native/fonts/antfill.ttf"; sourceTree = "<group>"; fileEncoding = undefined; lastKnownFileType = unknown; explicitFileType = undefined; includeInIndex = 0; };
FFCC4D9CCC40417BAB83BA33 /* antoutline.ttf */ = {isa = PBXFileReference; name = "antoutline.ttf"; path = "../node_modules/@ant-design/icons-react-native/fonts/antoutline.ttf"; sourceTree = "<group>"; fileEncoding = undefined; lastKnownFileType = unknown; explicitFileType = undefined; includeInIndex = 0; };
/* End PBXFileReference section */
/* Begin PBXFrameworksBuildPhase section */
@@ -74,6 +78,7 @@
83CBBA001A601CBA00E9B192 /* Products */,
2D16E6871FA4F8E400B85C8A /* Frameworks */,
BBD78D7AC51CEA395F1C20DB /* Pods */,
B056C340A7E240C19B8D27EB /* Resources */,
);
indentWidth = 2;
sourceTree = "<group>";
@@ -97,6 +102,16 @@
path = Pods;
sourceTree = "<group>";
};
B056C340A7E240C19B8D27EB /* Resources */ = {
isa = "PBXGroup";
children = (
A47F5CC487484D8E9ACED7FB /* antfill.ttf */,
FFCC4D9CCC40417BAB83BA33 /* antoutline.ttf */,
);
name = Resources;
sourceTree = "<group>";
path = "";
};
/* End PBXGroup section */
/* Begin PBXNativeTarget section */
@@ -159,6 +174,8 @@
files = (
81AB9BB82411601600AC10FF /* LaunchScreen.storyboard in Resources */,
13B07FBF1A68108700A75B9A /* Images.xcassets in Resources */,
9B06ED4A4A7343B390CE52D1 /* antfill.ttf in Resources */,
1F775F769E4C47D2BE44D107 /* antoutline.ttf in Resources */,
);
runOnlyForDeploymentPostprocessing = 0;
};

View File

@@ -26,14 +26,13 @@
<true/>
<key>NSAppTransportSecurity</key>
<dict>
<!-- Do not change NSAllowsArbitraryLoads to true, or you will risk app rejection! -->
<key>NSAllowsArbitraryLoads</key>
<false/>
<key>NSAllowsLocalNetworking</key>
<true/>
</dict>
<key>NSLocationWhenInUseUsageDescription</key>
<string></string>
<string/>
<key>UILaunchStoryboardName</key>
<string>LaunchScreen</string>
<key>UIRequiredDeviceCapabilities</key>
@@ -48,5 +47,10 @@
</array>
<key>UIViewControllerBasedStatusBarAppearance</key>
<false/>
<key>UIAppFonts</key>
<array>
<string>antfill.ttf</string>
<string>antoutline.ttf</string>
</array>
</dict>
</plist>

View File

@@ -0,0 +1,13 @@
{
"migIndex": 1,
"data": [
{
"path": "node_modules/@ant-design/icons-react-native/fonts/antfill.ttf",
"sha1": "56960e7721fc92b62e0f7c4d131ffe34ed042c49"
},
{
"path": "node_modules/@ant-design/icons-react-native/fonts/antoutline.ttf",
"sha1": "66720607b7496a48f145425386b2082b73662fd1"
}
]
}

10
jsconfig.json Normal file
View File

@@ -0,0 +1,10 @@
{
"include": ["**/*.js", "**/*.jsx"],
"exclude": ["**/node_modules", "**/Pods"],
"compilerOptions": {
"baseUrl": ".",
"paths": {
"@/*": ["./*"]
}
}
}

10
locales/en.json Normal file
View File

@@ -0,0 +1,10 @@
{
"welcome": "Welcome",
"greeting": "Hello, {{name}}!",
"login": "Login123",
"username": "Username",
"password": "Password",
"login_success": "Login Success",
"login_failed": "Login Failed",
"login_error_message": "Login Failed, Please Try Again"
}

60
locales/i18n.js Normal file
View File

@@ -0,0 +1,60 @@
import i18n from 'i18next';
import { initReactI18next } from 'react-i18next';
import * as RNLocalize from 'react-native-localize';
import AsyncStorage from '@/storage/index';
// 导入语言包
import en from './en.json';
import zh from './zh.json';
console.log('🚀 ~ RNLocalize:', RNLocalize)
// 导入语言资源
const resources = {
en: { translation: en },
zh: { translation: zh }
};
const languageDetector = {
type: 'languageDetector',
async: true,
detect: async callback => {
try {
// 从本地存储读取用户选择的语言
const saveLng = await AsyncStorage.getItemAsync('@APP_LANGUAGE');
const lng = saveLng ? saveLng.lng : null;
// 如果没有选择的语言,使用设备首选语言
const bestLanguageOption = RNLocalize.getLocales()[0].languageCode || 'en';
callback(lng || bestLanguageOption);
} catch (error) {
console.error("Failed to detect or load the language", error);
}
},
init: () => {},
cacheUserLanguage: async lng => {
console.log('🚀 ~ lng:', lng)
try {
// 将用户选择的语言存储到本地
await AsyncStorage.setItemAsync('@APP_LANGUAGE', {lng});
} catch (error) {
console.error("Failed to save the user's language choice", error);
}
},
};
i18n
.use(languageDetector)
.use(initReactI18next)
.init({
resources,
fallbackLng: 'en',
compatibilityJSON: 'v3',
interpolation: {
escapeValue: false, // react already saves from xss
},
});
export default i18n;

10
locales/zh.json Normal file
View File

@@ -0,0 +1,10 @@
{
"welcome": "欢迎",
"greeting": "你好, {{name}}!",
"login": "登录",
"username": "用户名",
"password": "密码",
"login_success": "登录成功",
"login_failed": "登录失败",
"login_error_message": "登录失败,请重试"
}

2776
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -10,10 +10,26 @@
"test": "jest"
},
"dependencies": {
"react": "19.1.0",
"react-native": "0.81.0",
"@ant-design/icons-react-native": "^2.3.2",
"@ant-design/react-native": "^5.4.3",
"@react-native-async-storage/async-storage": "^2.2.0",
"@react-native/new-app-screen": "0.81.0",
"react-native-safe-area-context": "^5.5.2"
"@react-navigation/bottom-tabs": "^7.4.6",
"@react-navigation/native": "^7.1.17",
"@react-navigation/stack": "^7.4.7",
"axios": "^1.11.0",
"babel-plugin-import": "^1.13.8",
"babel-plugin-module-resolver": "^5.0.2",
"i18next": "^25.4.2",
"react": "19.1.0",
"react-i18next": "^15.7.2",
"react-native": "0.81.0",
"react-native-gesture-handler": "^2.28.0",
"react-native-localize": "^3.5.2",
"react-native-reanimated": "^4.0.2",
"react-native-safe-area-context": "^5.6.1",
"react-native-screens": "^4.15.3",
"react-native-worklets": "^0.4.1"
},
"devDependencies": {
"@babel/core": "^7.25.2",

3
react-native.config.js Normal file
View File

@@ -0,0 +1,3 @@
module.exports = {
assets: ['node_modules/@ant-design/icons-react-native/fonts'],
};

26
storage/index.js Normal file
View File

@@ -0,0 +1,26 @@
// storage.js
import AsyncStorage from '@react-native-async-storage/async-storage';
const Storage = {
setItemAsync(key, value) {
return AsyncStorage.setItem(key, JSON.stringify(value || {}));
},
getItemAsync(key) {
return new Promise(resolve=>{
AsyncStorage.getItem(key).then(value=>{
resolve(value ? JSON.parse(value) : null)
})
})
},
removeItemAsync(key) {
return AsyncStorage.removeItem(key)
},
clearAllAsync() {
return AsyncStorage.clear();
}
};
export default Storage;

View File

@@ -1,5 +0,0 @@
{
"extends": "@react-native/typescript-config",
"include": ["**/*.ts", "**/*.tsx"],
"exclude": ["**/node_modules", "**/Pods"]
}

87
views/Guide/index.jsx Normal file
View File

@@ -0,0 +1,87 @@
import { useState, useEffect } from 'react';
import {
View,
Text,
TextInput,
TouchableOpacity,
Image,
StyleSheet,
Alert,
StatusBar
} from 'react-native';
import { Toast, Button } from '@ant-design/react-native';
import { useContextHook } from '@/components/AuthContext/index';
import AsyncStorage from '@/storage/index';
import { useRouter } from '@/hooks/useRouter';
import {
statusBarHeight,
paddingTopStatusBarHeight,
flexRow,
flexColumn,
flexSub,
flexShrink,
flexWrap,
flexnoWrap,
justifyStart,
justifyEnd,
justifyCenter,
justifyBetween,
justifyAround,
justifyEvenly,
alignItemsStart,
alignItemsEnd,
alignItemsCenter,
alignItemsStretch,
positionRelative,
positionAbsolute,
useStyles,
} from '@/assets/styles/css';
const Index = () => {
const { themeColor } = useContextHook();
const { linkTo, resetAllRoutes, isFocused } = useRouter();
useEffect(() => {
const user = AsyncStorage.getItemAsync('user');
// 这里请求配置后,再跳转
setTimeout(() => {
console.log('user', user);
if (user && user.token) {
resetAllRoutes('Home');
} else {
resetAllRoutes('Login');
}
}, 1000);
}, [isFocused]);
return (
<View style={[flexSub, {}]}>
<View
style={[
flexSub,
justifyCenter,
alignItemsCenter,
flexRow
]}>
<Text
style={{
fontSize: 24,
fontWeight: 'bold',
backgroundColor: 'red',
}}>
login111
</Text>
<Button type="primary" onPress={() => {
Toast.show({
content: 'Please enter user name',
position: 'center',
mask: true,
});
}}>Click me</Button>
</View>
</View>
);
};
export default Index;

View File

@@ -0,0 +1,64 @@
import { useState, useEffect } from 'react';
import {
View,
Text,
TextInput,
TouchableOpacity,
Image,
StyleSheet,
Alert,
} from 'react-native';
import { Provider, Toast } from '@ant-design/react-native';
import {
statusBarHeight,
paddingTopStatusBarHeight,
flexRow,
flexColumn,
flexSub,
flexShrink,
flexWrap,
flexnoWrap,
justifyStart,
justifyEnd,
justifyCenter,
justifyBetween,
justifyAround,
justifyEvenly,
alignItemsStart,
alignItemsEnd,
alignItemsCenter,
alignItemsStretch,
positionRelative,
positionAbsolute,
useStyles,
} from '@/assets/styles/css';
const Index = () => {
useEffect(() => {
}, []);
return (
<View
style={[
flexSub,
justifyCenter,
alignItemsCenter,
{
backgroundColor: '#000',
},
]}>
<Text
style={{
fontSize: 24,
fontWeight: 'bold',
marginBottom: 20,
}}>
index
</Text>
</View>
);
};
export default Index;

View File

@@ -0,0 +1,64 @@
import { useState, useEffect } from 'react';
import {
View,
Text,
TextInput,
TouchableOpacity,
Image,
StyleSheet,
Alert,
} from 'react-native';
import { Provider, Toast } from '@ant-design/react-native';
import {
statusBarHeight,
paddingTopStatusBarHeight,
flexRow,
flexColumn,
flexSub,
flexShrink,
flexWrap,
flexnoWrap,
justifyStart,
justifyEnd,
justifyCenter,
justifyBetween,
justifyAround,
justifyEvenly,
alignItemsStart,
alignItemsEnd,
alignItemsCenter,
alignItemsStretch,
positionRelative,
positionAbsolute,
useStyles,
} from '@/assets/styles/css';
const Index = () => {
useEffect(() => {
}, []);
return (
<View
style={[
flexSub,
justifyCenter,
alignItemsCenter,
{
backgroundColor: '#fff',
},
]}>
<Text
style={{
fontSize: 24,
fontWeight: 'bold',
marginBottom: 20,
}}>
Inventory
</Text>
</View>
);
};
export default Index;

View File

@@ -0,0 +1,64 @@
import { useState, useEffect } from 'react';
import {
View,
Text,
TextInput,
TouchableOpacity,
Image,
StyleSheet,
Alert,
} from 'react-native';
import { Provider, Toast } from '@ant-design/react-native';
import {
statusBarHeight,
paddingTopStatusBarHeight,
flexRow,
flexColumn,
flexSub,
flexShrink,
flexWrap,
flexnoWrap,
justifyStart,
justifyEnd,
justifyCenter,
justifyBetween,
justifyAround,
justifyEvenly,
alignItemsStart,
alignItemsEnd,
alignItemsCenter,
alignItemsStretch,
positionRelative,
positionAbsolute,
useStyles,
} from '@/assets/styles/css';
const Index = () => {
useEffect(() => {
}, []);
return (
<View
style={[
flexSub,
justifyCenter,
alignItemsCenter,
{
backgroundColor: '#fff',
},
]}>
<Text
style={{
fontSize: 24,
fontWeight: 'bold',
marginBottom: 20,
}}>
Profile
</Text>
</View>
);
};
export default Index;

195
views/Login/index.jsx Normal file
View File

@@ -0,0 +1,195 @@
import { useState, useEffect } from 'react';
import {
View,
Text,
TextInput,
TouchableOpacity,
Image,
StyleSheet,
Alert,
} from 'react-native';
import { Provider, Toast } from '@ant-design/react-native';
import AsyncStorage from '@/storage/index';
import { useNavigation, useIsFocused } from '@react-navigation/native';
import { useContextHook } from '@/components/AuthContext';
import { useTranslation } from 'react-i18next';
import {
statusBarHeight,
paddingTopStatusBarHeight,
flexRow,
flexColumn,
flexSub,
flexShrink,
flexWrap,
flexnoWrap,
justifyStart,
justifyEnd,
justifyCenter,
justifyBetween,
justifyAround,
justifyEvenly,
alignItemsStart,
alignItemsEnd,
alignItemsCenter,
alignItemsStretch,
positionRelative,
positionAbsolute,
useStyles,
} from '@/assets/styles/css';
const inputItem = useStyles({
width: '80%',
height: 40,
borderColor: 'gray',
borderWidth: 1,
borderRadius: 5,
paddingHorizontal: 10,
marginBottom: 20,
});
const LoginScreen = () => {
const { themeColor } = useContextHook();
const { t, i18n } = useTranslation();
console.log('🚀 ~ LoginScreen ~ i18n:', i18n)
const router = useNavigation();
const [language, setLanguage] = useState('en');
const isFocused = useIsFocused();
const [username, setUsername] = useState('');
const [password, setPassword] = useState('');
const [loading, setLoading] = useState(false);
useEffect(() => {
}, [isFocused]);
const handleLogin = () => {
if (!username) {
Toast.show({
content: t('Please enter user name'),
position: 'center',
mask: true,
});
return;
}
if (!password) {
Toast.show({
content: t('Please enter password'),
position: 'center',
mask: true,
});
return;
}
const key = Toast.show({
icon: 'loading',
content: t('loading'),
position: 'center',
mask: true,
duration: 0,
});
console.log(123,123);
// login({ username, password })
// .then(res => {
// Toast.remove(key);
// setTimeout(() => {
// Toast.show({
// content: t('login success'), //i18n.t('login_success'),
// position: 'center',
// mask: true,
// onClose: () => {
// // 设置全局的用户信息
// setUser(res.user)
// AsyncStorage.setItem('appToken', {
// appToken: res.token,
// user: res.user,
// }).then(() => {
// router.replace('Home');
// });
// },
// });
// }, 200);
// })
// .catch(err => {
// Toast.remove(key);
// setTimeout(() => {
// Toast.show({
// content: t('login_failed'), // i18n.t('login_failed'),
// position: 'center',
// mask: true,
// });
// }, 200);
// // i18n.t('login_error_message')
// });
};
return (
<View
style={[
flexSub,
justifyCenter,
alignItemsCenter,
{
backgroundColor: themeColor.colorBgBase,
},
]}>
<View style={[{ padding: 20 }]}>
<Image
source={require('@/assets/images/logo.png')}
style={[
{
width: 300,
height: 150,
resizeMode: 'contain',
},
]}
/>
</View>
<Text
style={{
fontSize: 24,
fontWeight: 'bold',
marginBottom: 20,
}}>
{t('login')}
</Text>
<TextInput
style={[inputItem]}
placeholder={t('username')}
value={username}
onChangeText={setUsername}
autoCapitalize="none"
keyboardType="default"
/>
<TextInput
style={[inputItem]}
placeholder={t('password')}
value={password}
onChangeText={setPassword}
secureTextEntry
/>
<TouchableOpacity
style={{
width: '80%',
backgroundColor: '#7ac0f6',
borderRadius: 5,
paddingVertical: 10,
}}
onPress={handleLogin}>
<Text
style={{
color: '#fff',
textAlign: 'center',
fontSize: 18,
}}>
{t('login')}
</Text>
</TouchableOpacity>
</View>
);
};
export default LoginScreen;