diff --git a/.vscode/settings.json b/.vscode/settings.json index 56d685094a914bc4d75b97fb0bb08bae5a886467..f5bbbf1b1ae46af4e129f8317c0d86d2b86aa550 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -24,7 +24,7 @@ "editor.formatOnSave": true, "editor.defaultFormatter": "astro-build.astro-vscode" }, - "cSpell.words": ["Flashlist", "Lato"], + "cSpell.words": ["eucloud", "Flashlist", "Lato", "subtx"], "i18n-ally.localesPaths": ["src/translations/"], "i18n-ally.keystyle": "nested", "i18n-ally.disabled": false, // make sure to disable i18n-ally in your global setting and only enable it for such projects diff --git a/README.md b/README.md index 7b04e6fafa52b305257bc6f618ec60be157e0eae..56e08e96b2020b3bfb22ab3ddc1afffae662d0dc 100644 --- a/README.md +++ b/README.md @@ -6,6 +6,8 @@ EU-Admin App > This Project is based on [Expo](https://docs.expo.dev/) +> 👉 Supports hot updates + ![image-1](./doc/images/android/1.png) ![image-2](./doc/images/android/2.png) ![image-3](./doc/images/android/3.png) @@ -27,7 +29,7 @@ EU-Admin App Clone the repo to your machine and install deps : ```sh -git clone https://gitee.com/xiaochanghai520/eu.admin.reactnative +git clone https://github.com/xiaochanghai/eu.admin.reactnative cd ./eu.admin.reactnative diff --git a/android/app/build.gradle b/android/app/build.gradle index b59e7aec87fed806a65e07b9406212c6099b169e..723a62e0dd76e99203ed967a9fb44796a55ffbd8 100644 --- a/android/app/build.gradle +++ b/android/app/build.gradle @@ -86,13 +86,13 @@ android { buildToolsVersion rootProject.ext.buildToolsVersion compileSdk rootProject.ext.compileSdkVersion - namespace 'com.eu_cloud.erp' + namespace 'com.eucloud.erp' defaultConfig { - applicationId 'com.eu_cloud.erp' + applicationId 'com.eucloud.erp' minSdkVersion rootProject.ext.minSdkVersion targetSdkVersion rootProject.ext.targetSdkVersion versionCode 1 - versionName "1.0.2" + versionName "1.0.1" ndk { abiFilters "arm64-v8a"//,"x86","arm64-v8a""armeabi-v7a' } diff --git a/android/app/src/main/AndroidManifest.xml b/android/app/src/main/AndroidManifest.xml index 6520bdd3ae8a96c79af0c713e4a59c920e6ffa6e..20683ad2898e3b936e8824f53e1bed1542f771f7 100644 --- a/android/app/src/main/AndroidManifest.xml +++ b/android/app/src/main/AndroidManifest.xml @@ -13,9 +13,12 @@ - + + + + @@ -25,8 +28,8 @@ - - + + diff --git a/android/app/src/main/java/com/eu_cloud/erp/MainActivity.kt b/android/app/src/main/java/com/eucloud/erp/MainActivity.kt similarity index 98% rename from android/app/src/main/java/com/eu_cloud/erp/MainActivity.kt rename to android/app/src/main/java/com/eucloud/erp/MainActivity.kt index 683711f6099e5d4dfe2dd59e617d0f1d0bab42fb..4d1da17376087e735d2cfa93d0571ac2445f6932 100644 --- a/android/app/src/main/java/com/eu_cloud/erp/MainActivity.kt +++ b/android/app/src/main/java/com/eucloud/erp/MainActivity.kt @@ -1,4 +1,4 @@ -package com.eu_cloud.erp +package com.eucloud.erp import expo.modules.splashscreen.SplashScreenManager import android.os.Build diff --git a/android/app/src/main/java/com/eu_cloud/erp/MainApplication.kt b/android/app/src/main/java/com/eucloud/erp/MainApplication.kt similarity index 98% rename from android/app/src/main/java/com/eu_cloud/erp/MainApplication.kt rename to android/app/src/main/java/com/eucloud/erp/MainApplication.kt index 9b0578608af5e28e93cb5f41af91a9e65d7476c9..b86bf7eeeb6baec3bbfb65c910ad5990b0082599 100644 --- a/android/app/src/main/java/com/eu_cloud/erp/MainApplication.kt +++ b/android/app/src/main/java/com/eucloud/erp/MainApplication.kt @@ -1,4 +1,4 @@ -package com.eu_cloud.erp +package com.eucloud.erp import android.app.Application import android.content.res.Configuration diff --git a/android/app/src/main/res/values/strings.xml b/android/app/src/main/res/values/strings.xml index d1b3707eb7cc75a86858be593dce6a32261fc77e..99538865b04875a449bac04456b3897010a5bc2c 100644 --- a/android/app/src/main/res/values/strings.xml +++ b/android/app/src/main/res/values/strings.xml @@ -3,4 +3,5 @@ automatic contain false + 1.0.1 \ No newline at end of file diff --git a/app.config.ts b/app.config.ts index c1eadde80007a7bbdd098c8617691ff8d7a7c117..6341c92aaa67766c277a5c4529a33f280733f73c 100644 --- a/app.config.ts +++ b/app.config.ts @@ -32,8 +32,16 @@ export default ({ config }: ConfigContext): ExpoConfig => ({ icon: './assets/icon.png', userInterfaceStyle: 'automatic', newArchEnabled: true, + runtimeVersion: '1.0.1', updates: { + url: 'https://u.expo.dev/9d0f9588-d00f-40cf-a15c-ffd7e8bc7654', // 这里的project-id会由eas update:configure命令生成 + enabled: true, + checkAutomatically: 'ON_LOAD', fallbackToCacheTimeout: 0, + requestHeaders: { + 'expo-runtime-version': '1.0.1', + 'expo-channel-name': 'production', + }, }, assetBundlePatterns: ['**/*'], ios: { @@ -49,7 +57,7 @@ export default ({ config }: ConfigContext): ExpoConfig => ({ android: { adaptiveIcon: { foregroundImage: './assets/adaptive-icon.png', - backgroundColor: '#2E3C4B', + backgroundColor: '#185A56', }, package: Env.PACKAGE, }, @@ -61,7 +69,7 @@ export default ({ config }: ConfigContext): ExpoConfig => ({ [ 'expo-splash-screen', { - backgroundColor: '#2E3C4B', + backgroundColor: '#185A56', image: './assets/splash-icon.png', imageWidth: 150, }, diff --git a/eas.json b/eas.json index 79b47e1d7a872b878f69e4e93bccf2bd99267f3a..c2abde4ed746a8c0940c5d36327e898494b00528 100644 --- a/eas.json +++ b/eas.json @@ -50,7 +50,8 @@ "env": { "APP_ENV": "development", "EXPO_NO_DOTENV": "1" - } + }, + "channel": "development" }, "simulator": { "pnpm": "9.12.3", @@ -64,7 +65,8 @@ "env": { "APP_ENV": "development", "EXPO_NO_DOTENV": "1" - } + }, + "channel": "simulator" } }, "submit": { diff --git a/env.js b/env.js index 9b0e3554f5b7a17c09144eaaf5a46d850f2eaef8..8dde3be4a1de1d2838ae6a03c2a2e6a93ab40f1a 100644 --- a/env.js +++ b/env.js @@ -34,12 +34,12 @@ require('dotenv').config({ // TODO: Replace these values with your own -const BUNDLE_ID = 'com.eu_cloud.erp'; // ios bundle id -const PACKAGE = 'com.eu_cloud.erp'; // android package name +const BUNDLE_ID = 'com.eucloud.erp'; // ios bundle id +const PACKAGE = 'com.eucloud.erp'; // android package name const NAME = '优智云'; // app name const EXPO_ACCOUNT_OWNER = 'hsiaosah'; // expo account owner const EAS_PROJECT_ID = '9d0f9588-d00f-40cf-a15c-ffd7e8bc7654'; // eas project id -const SCHEME = 'eu_cloud'; // app scheme +const SCHEME = 'eucloud'; // app scheme /** * We declare a function withEnvSuffix that will add a suffix to the variable name based on the APP_ENV diff --git a/ios/EUCloudERP/Info.plist b/ios/EUCloudERP/Info.plist index 568773a34789aa239c5dab587c70280a5885bd4c..5a9c6251c370a2b011a9b012b59112394ecacef3 100644 --- a/ios/EUCloudERP/Info.plist +++ b/ios/EUCloudERP/Info.plist @@ -19,7 +19,7 @@ CFBundlePackageType $(PRODUCT_BUNDLE_PACKAGE_TYPE) CFBundleShortVersionString - 1.0.2 + 1.0.1 CFBundleSignature ???? CFBundleURLTypes diff --git a/ios/EUCloudERP/Supporting/Expo.plist b/ios/EUCloudERP/Supporting/Expo.plist index 750be020cfc2d242e01b44e9d7054495b8357937..143ece9da04a9d8d5daa9fdb7b12907e62f11e06 100644 --- a/ios/EUCloudERP/Supporting/Expo.plist +++ b/ios/EUCloudERP/Supporting/Expo.plist @@ -5,8 +5,12 @@ EXUpdatesCheckOnLaunch ALWAYS EXUpdatesEnabled - + EXUpdatesLaunchWaitMs 0 + EXUpdatesRuntimeVersion + 2 + EXUpdatesURL + https://u.expo.dev/9d0f9588-d00f-40cf-a15c-ffd7e8bc7654 \ No newline at end of file diff --git a/package.json b/package.json index 752d588c3b74aeb1103a48452b0013e68713e94b..6a6ea5335406fa77bb8589f5419db5f90aea416d 100644 --- a/package.json +++ b/package.json @@ -1,5 +1,5 @@ { - "name": "eu_cloud", + "name": "eucloud", "version": "1.0.1", "private": true, "main": "node_modules/expo-router/entry", @@ -28,6 +28,7 @@ "build:staging:android": "cross-env APP_ENV=staging EXPO_NO_DOTENV=1 eas build --profile staging --platform android ", "build:production:ios": "cross-env APP_ENV=production EXPO_NO_DOTENV=1 eas build --profile production --platform ios", "build:production:android": "cross-env APP_ENV=production EXPO_NO_DOTENV=1 eas build --profile production --platform android ", + "build:production:android:local": "cd android&&gradlew assembleRelease", "prepare": "husky", "app-release": "cross-env SKIP_BRANCH_PROTECTION=true np --no-publish --no-cleanup --no-release-draft", "version": "pnpm run prebuild && git add .", @@ -64,7 +65,7 @@ "expo-splash-screen": "~0.29.22", "expo-status-bar": "~2.0.1", "expo-system-ui": "~4.0.8", - "expo-updates": "~0.26.19", + "expo-updates": "~0.27.4", "i18next": "^23.14.0", "lodash.memoize": "^4.1.2", "moti": "^0.29.0", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 984173cc21a5db36046ddf19a0f28c1414e2d200..79ca77c392b016e6b4f0f33f20691f72883f4855 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -75,8 +75,8 @@ importers: specifier: ~4.0.8 version: 4.0.9(expo@52.0.44(@babel/core@7.26.10)(@babel/preset-env@7.26.9(@babel/core@7.26.10))(@expo/metro-runtime@4.0.1(react-native@0.76.9(@babel/core@7.26.10)(@babel/preset-env@7.26.9(@babel/core@7.26.10))(@types/react@18.3.20)(react@18.3.1)))(react-native@0.76.9(@babel/core@7.26.10)(@babel/preset-env@7.26.9(@babel/core@7.26.10))(@types/react@18.3.20)(react@18.3.1))(react@18.3.1))(react-native-web@0.19.13(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react-native@0.76.9(@babel/core@7.26.10)(@babel/preset-env@7.26.9(@babel/core@7.26.10))(@types/react@18.3.20)(react@18.3.1)) expo-updates: - specifier: ~0.26.19 - version: 0.26.19(expo@52.0.44(@babel/core@7.26.10)(@babel/preset-env@7.26.9(@babel/core@7.26.10))(@expo/metro-runtime@4.0.1(react-native@0.76.9(@babel/core@7.26.10)(@babel/preset-env@7.26.9(@babel/core@7.26.10))(@types/react@18.3.20)(react@18.3.1)))(react-native@0.76.9(@babel/core@7.26.10)(@babel/preset-env@7.26.9(@babel/core@7.26.10))(@types/react@18.3.20)(react@18.3.1))(react@18.3.1))(react@18.3.1) + specifier: ~0.27.4 + version: 0.27.4(expo@52.0.44(@babel/core@7.26.10)(@babel/preset-env@7.26.9(@babel/core@7.26.10))(@expo/metro-runtime@4.0.1(react-native@0.76.9(@babel/core@7.26.10)(@babel/preset-env@7.26.9(@babel/core@7.26.10))(@types/react@18.3.20)(react@18.3.1)))(react-native@0.76.9(@babel/core@7.26.10)(@babel/preset-env@7.26.9(@babel/core@7.26.10))(@types/react@18.3.20)(react@18.3.1))(react@18.3.1))(react@18.3.1) i18next: specifier: ^23.14.0 version: 23.16.8 @@ -3949,8 +3949,8 @@ packages: peerDependencies: expo: '*' - expo-updates@0.26.19: - resolution: {integrity: sha512-h40UrG0n1nCb2na1ffz+mNQtsnr7/BxxK+EtXJSqCaD9PIGaTGe20tasmo1oVskv3s37zfv0x93+6uTjanieQg==} + expo-updates@0.27.4: + resolution: {integrity: sha512-0rg4L2fFPEjTR/qnZ9Te4Q4irVC8uvNcTZW1pWnWbadG1SLv2PKjS1MYX5BboKzC3ao0H7m++5TP3hWhNg9org==} hasBin: true peerDependencies: expo: '*' @@ -12814,7 +12814,7 @@ snapshots: dependencies: expo: 52.0.44(@babel/core@7.26.10)(@babel/preset-env@7.26.9(@babel/core@7.26.10))(@expo/metro-runtime@4.0.1(react-native@0.76.9(@babel/core@7.26.10)(@babel/preset-env@7.26.9(@babel/core@7.26.10))(@types/react@18.3.20)(react@18.3.1)))(react-native@0.76.9(@babel/core@7.26.10)(@babel/preset-env@7.26.9(@babel/core@7.26.10))(@types/react@18.3.20)(react@18.3.1))(react@18.3.1) - expo-updates@0.26.19(expo@52.0.44(@babel/core@7.26.10)(@babel/preset-env@7.26.9(@babel/core@7.26.10))(@expo/metro-runtime@4.0.1(react-native@0.76.9(@babel/core@7.26.10)(@babel/preset-env@7.26.9(@babel/core@7.26.10))(@types/react@18.3.20)(react@18.3.1)))(react-native@0.76.9(@babel/core@7.26.10)(@babel/preset-env@7.26.9(@babel/core@7.26.10))(@types/react@18.3.20)(react@18.3.1))(react@18.3.1))(react@18.3.1): + expo-updates@0.27.4(expo@52.0.44(@babel/core@7.26.10)(@babel/preset-env@7.26.9(@babel/core@7.26.10))(@expo/metro-runtime@4.0.1(react-native@0.76.9(@babel/core@7.26.10)(@babel/preset-env@7.26.9(@babel/core@7.26.10))(@types/react@18.3.20)(react@18.3.1)))(react-native@0.76.9(@babel/core@7.26.10)(@babel/preset-env@7.26.9(@babel/core@7.26.10))(@types/react@18.3.20)(react@18.3.1))(react@18.3.1))(react@18.3.1): dependencies: '@expo/code-signing-certificates': 0.0.5 '@expo/config': 10.0.11 diff --git a/src/app/(app)/index.tsx b/src/app/(app)/index.tsx index 68939806d64e7a4f1b4ff526da80d90260b7bdc8..bee34c73fb8be6b2d2ece2574bfe1cab29a61232 100644 --- a/src/app/(app)/index.tsx +++ b/src/app/(app)/index.tsx @@ -1,4 +1,5 @@ /* eslint-disable react/react-in-jsx-scope */ +import { useRouter } from 'expo-router'; import React, { useEffect } from 'react'; import { SafeAreaView, @@ -71,6 +72,8 @@ const Home: React.FC = () => { useEffect(() => { if (userInfo == null) setInfo(null); }); + const router = useRouter(); + return ( {/* */} @@ -132,6 +135,12 @@ const Home: React.FC = () => { 功能模块 + router.push('/material')} + /> - {/* 其他选项 */} - - {}} - /> - {}} - /> - {}} - isLast={true} - /> - - {/* 退出登录按钮 */} { - signOut(); - }} + onPress={() => signOut()} className="m-10" > @@ -316,18 +289,18 @@ const styles = StyleSheet.create({ borderRadius: 4, }, logoutButton: { - backgroundColor: 'white', + backgroundColor: '#dc2626', borderWidth: 1, borderColor: '#e5e7eb', borderRadius: 12, paddingVertical: 12, alignItems: 'center', marginTop: 24, - marginBottom: 16, + marginBottom: 30, }, logoutButtonText: { - color: '#dc2626', + color: '#FFFFFF', fontWeight: '500', - fontSize: 16, + // fontSize: 16, }, }); diff --git a/src/app/_layout.tsx b/src/app/_layout.tsx index 535c9a96f6e3613f98ebed951d135f064c83fcef..1f61ee51d293174419231d6d73888c54479ef3cb 100644 --- a/src/app/_layout.tsx +++ b/src/app/_layout.tsx @@ -7,14 +7,18 @@ import { BottomSheetModalProvider } from '@gorhom/bottom-sheet'; import { ThemeProvider } from '@react-navigation/native'; import { Stack } from 'expo-router'; import * as SplashScreen from 'expo-splash-screen'; -import React from 'react'; -import { StyleSheet } from 'react-native'; +import * as Updates from 'expo-updates'; +import React, { useEffect } from 'react'; +import { Platform, StyleSheet } from 'react-native'; +import { getUniqueId } from 'react-native-device-info'; import FlashMessage from 'react-native-flash-message'; import { GestureHandlerRootView } from 'react-native-gesture-handler'; import { KeyboardProvider } from 'react-native-keyboard-controller'; import { APIProvider } from '@/api'; import { hydrateAuth, loadSelectedTheme } from '@/lib'; +import { setUniqueId } from '@/lib/auth/utils'; +import { recordDevice } from '@/lib/device'; import { useThemeConfig } from '@/lib/use-theme-config'; export { ErrorBoundary } from 'expo-router'; @@ -35,6 +39,32 @@ SplashScreen.setOptions({ Toast.config({ duration: 2 }); export default function RootLayout() { + const checkForUpdate = async () => { + try { + const update = await Updates.checkForUpdateAsync(); + + // alert('update.isAvailable:' + update.isAvailable); + if (update.isAvailable) { + await Updates.fetchUpdateAsync(); + await Updates.reloadAsync(); + } + } catch (error) { + // alert('检查更新失败:' + error); + // if (error instanceof Error) { + // alert('错误信息:' + error.message); + // alert('错误堆栈:' + error.stack); + // } + } + }; + useEffect(() => { + if (Platform.OS !== 'web') { + checkForUpdate(); + getUniqueId().then((uniqueId) => { + setUniqueId(uniqueId); + recordDevice(uniqueId); + }); + } + }); return ( diff --git a/src/app/login.tsx b/src/app/login.tsx index 1c466514b4881f2f6bdbcf7c291d58d233d44c76..72a054476b5eb06c0015e6e05d8c7e395b3dce32 100644 --- a/src/app/login.tsx +++ b/src/app/login.tsx @@ -1,24 +1,13 @@ // import { useRouter } from 'expo-router'; -import React, { useEffect } from 'react'; -import { Platform } from 'react-native'; -import { getUniqueId } from 'react-native-device-info'; +import React from 'react'; +import { StatusBar } from 'react-native'; import { LoginForm } from '@/components/login-form'; import { FocusAwareStatusBar } from '@/components/ui'; -import { setUniqueId } from '@/lib/auth/utils'; -import { recordDevice } from '@/lib/device'; - export default function Login() { - useEffect(() => { - if (Platform.OS !== 'web') - getUniqueId().then((uniqueId) => { - //console.log('1211:' + uniqueId) - setUniqueId(uniqueId); - recordDevice(uniqueId); - }); - }); return ( <> + diff --git a/src/app/material/[id].tsx b/src/app/material/[id].tsx new file mode 100644 index 0000000000000000000000000000000000000000..09ccdfb582ba4bac45177f70853ec7d82683cbc7 --- /dev/null +++ b/src/app/material/[id].tsx @@ -0,0 +1,50 @@ +import { + Stack, + // useLocalSearchParams +} from 'expo-router'; +import * as React from 'react'; + +// import { usePost } from '@/api'; +import { + // ActivityIndicator, + FocusAwareStatusBar, + Text, + View, +} from '@/components/ui'; + +export default function Post() { + // const local = useLocalSearchParams<{ id: string }>(); + + // const { data, isPending, isError } = usePost({ + // //@ts-ignore + // variables: { id: local.id }, + // }); + + // if (isPending) { + // return ( + // + // + // + // + // + // ); + // } + // if (isError) { + // return ( + // + // + // + // Error loading post + // + // ); + // } + + return ( + + + + 11111 + 1111 + + ); +} diff --git a/src/app/material/index.tsx b/src/app/material/index.tsx new file mode 100644 index 0000000000000000000000000000000000000000..d6a6e82b1645fd124507b12d91b302b2bd174dcb --- /dev/null +++ b/src/app/material/index.tsx @@ -0,0 +1,323 @@ +import { useRouter } from 'expo-router'; +import React, { useState } from 'react'; +import { + SafeAreaView, + StyleSheet, + TextInput, + TouchableOpacity, +} from 'react-native'; + +import { RefreshListView } from '@/components'; +import { NavHeader, ScrollView, Text, View } from '@/components/ui'; +import { FontAwesome, GroupEnum } from '@/components/ui/icons'; + +type MaterialProps = { + id: string; + name: string; + code: string; + category: string; + stock: number; + unit: string; + status: string; + lastUpdated: string; +}; +// 模拟物料数据 +const materialData: MaterialProps[] = [ + { + id: '1', + name: '铝合金型材', + code: 'M001', + category: '原材料', + stock: 2500, + unit: '米', + status: '正常', + lastUpdated: '2023-05-15', + }, + { + id: '2', + name: '不锈钢板材', + code: 'M002', + category: '原材料', + stock: 1200, + unit: '张', + status: '正常', + lastUpdated: '2023-05-14', + }, + { + id: '3', + name: '塑料颗粒', + code: 'M003', + category: '原材料', + stock: 500, + unit: '千克', + status: '低库存', + lastUpdated: '2023-05-13', + }, + { + id: '4', + name: '电机', + code: 'M004', + category: '零部件', + stock: 350, + unit: '个', + status: '正常', + lastUpdated: '2023-05-12', + }, + { + id: '5', + name: '控制板', + code: 'M005', + category: '零部件', + stock: 120, + unit: '块', + status: '低库存', + lastUpdated: '2023-05-11', + }, + { + id: '6', + name: '螺丝', + code: 'M006', + category: '辅料', + stock: 10000, + unit: '个', + status: '正常', + lastUpdated: '2023-05-10', + }, + { + id: '7', + name: '包装盒', + code: 'M007', + category: '包装材料', + stock: 800, + unit: '个', + status: '正常', + lastUpdated: '2023-05-09', + }, + { + id: '8', + name: '标签', + code: 'M008', + category: '包装材料', + stock: 5000, + unit: '张', + status: '正常', + lastUpdated: '2023-05-08', + }, +]; + +// 物料分类数据 +const categories = [ + { id: '1', name: '全部', icon: 'apps' }, + { id: '2', name: '原材料', icon: 'cube' }, + { id: '3', name: '零部件', icon: 'cog' }, + { id: '4', name: '辅料', icon: 'tools' }, + { id: '5', name: '包装材料', icon: 'box' }, +]; + +const Materials = () => { + const router = useRouter(); + + const [searchText, setSearchText] = useState(''); + const [selectedCategory, setSelectedCategory] = useState('全部'); + const [filteredMaterials, setFilteredMaterials] = useState(materialData); + + // 筛选物料 + const filterMaterials = (category: string, searchQuery: string) => { + let filtered = materialData; + + if (category !== '全部') { + filtered = filtered.filter((item) => item.category === category); + } + + if (searchQuery) { + filtered = filtered.filter( + (item) => + item.name.toLowerCase().includes(searchQuery.toLowerCase()) || + item.code.toLowerCase().includes(searchQuery.toLowerCase()) + ); + } + + setFilteredMaterials(filtered); + }; + + // 处理搜索 + const handleSearch = (text: string) => { + setSearchText(text); + filterMaterials(selectedCategory, text); + }; + + // 处理分类选择 + const handleCategorySelect = (category: string) => { + setSelectedCategory(category); + filterMaterials(category, searchText); + }; + + // 处理物料点击 + // const handleMaterialPress = (materialId: string) => { + const handleMaterialPress = (materialId: string) => { + router.push(`/material/${materialId}`); + }; + + // 渲染物料项 + const renderMaterialItem = ({ item }: { item: any }) => ( + handleMaterialPress(item.id)} + > + + + {item.name} + + 编码: {item.code} + + + + 库存: {item.stock} {item.unit} + + + + {item.status} + + + + + + {item.lastUpdated} + + + + + + ); + + return ( + + + + + + + + + + + + + } + /> + {/* 头部 */} + + {/* 搜索框 */} + + + + {searchText ? ( + handleSearch('')}> + + + ) : null} + + + + {/* 分类选择 */} + + + {categories.map((category) => ( + handleCategorySelect(category.name)} + > + + {/* */} + + {category.name} + + + + ))} + + + {/* 物料列表 */} + item.id} + // contentContainerStyle={tw('p-4')} + showsVerticalScrollIndicator={false} + ListEmptyComponent={ + + + + 未找到匹配的物料{'\n'}请尝试其他搜索条件 + + + } + /> + + ); +}; + +const styles = StyleSheet.create({ + headerButton: { + marginLeft: 16, + }, +}); + +export default Materials; diff --git a/src/app/settings/about.tsx b/src/app/settings/about.tsx new file mode 100644 index 0000000000000000000000000000000000000000..7ce18b0a1b983bdc160b47bc2dce6d147b5178e2 --- /dev/null +++ b/src/app/settings/about.tsx @@ -0,0 +1,95 @@ +import { Env } from '@env'; +import * as Updates from 'expo-updates'; +import React, { useEffect, useState } from 'react'; +import { Platform, SafeAreaView, ScrollView, StyleSheet } from 'react-native'; +import { getVersion } from 'react-native-device-info'; +import { KeyboardAvoidingView } from 'react-native-keyboard-controller'; + +import { Image, NavHeader, Text, View } from '@/components/ui'; +import { translate } from '@/lib/i18n'; + +export default function LoginForm() { + const [updateId, setUpdateId] = useState(null); + useEffect(() => { + if (Platform.OS !== 'web') { + const updateId1 = Updates?.updateId; + setUpdateId(updateId1); + } + }, []); + return ( + + + + + {/* 顶部空间 */} + + + {/* Logo和标题 */} + + + + + {Env.NAME + ' V' + getVersion()} + {translate('login.sub_title')} + {updateId ? ( + 版本ID:{updateId} + ) : null} + + + + + ); +} + +const styles = StyleSheet.create({ + container: { + flex: 1, + backgroundColor: 'white', + }, + keyboardAvoidingView: { + flex: 1, + }, + scrollView: { + flexGrow: 1, + paddingHorizontal: 16, + }, + topSpace: { + height: '10%', + }, + logoContainer: { + alignItems: 'center', + marginBottom: 32, + }, + logoCircle: { + width: 80, + height: 80, + borderRadius: 40, + backgroundColor: '#EBF5FF', + alignItems: 'center', + justifyContent: 'center', + marginBottom: 16, + }, + title: { + fontSize: 18, + fontWeight: 'bold', + color: '#333', + }, + subtitle: { + fontSize: 16, + color: '#6b7280', + marginTop: 8, + }, + copyright: { + fontSize: 14, + color: '#6b7280', + marginTop: 8, + }, +}); diff --git a/src/app/settings/components/setting-item.tsx b/src/app/settings/components/setting-item.tsx index cd346ced34407dc3349425ec02e12ec5c8a7d24f..f0401349a6e0b9dac9ffadbc976271555f510a08 100644 --- a/src/app/settings/components/setting-item.tsx +++ b/src/app/settings/components/setting-item.tsx @@ -2,6 +2,8 @@ import React, { useState } from 'react'; import { StyleSheet, Switch, Text, TouchableOpacity, View } from 'react-native'; import { FontAwesome } from '@/components/ui/icons'; +import type { TxKeyPath } from '@/lib/i18n'; +import { translate } from '@/lib/i18n'; /** * 设置项组件属性定义 @@ -19,7 +21,7 @@ import { FontAwesome } from '@/components/ui/icons'; type SettingItemProps = { icon: string; iconBgColor: string; - title: string; + title?: string; subtitle?: string; hasToggle?: boolean; defaultToggleValue?: boolean; @@ -27,6 +29,8 @@ type SettingItemProps = { hasNavigation?: boolean; onPress?: () => void; isLast?: boolean; + tx?: TxKeyPath; + subtx?: TxKeyPath; }; /** @@ -44,6 +48,8 @@ const SettingItem: React.FC = ({ hasNavigation = false, onPress, isLast = false, + tx, + subtx, }) => { const [isEnabled, setIsEnabled] = useState(defaultToggleValue); @@ -69,8 +75,12 @@ const SettingItem: React.FC = ({ - {title} - {subtitle && {subtitle}} + {tx ? translate(tx) : title} + {(subtitle || subtx) && ( + + {subtx ? translate(subtx) : subtitle} + + )} diff --git a/src/app/settings/index.tsx b/src/app/settings/index.tsx index f37cc8b87185c71da85082208ac310562ce0eb2e..3f403a28ad061462488536147478d61e395e45ee 100644 --- a/src/app/settings/index.tsx +++ b/src/app/settings/index.tsx @@ -1,33 +1,36 @@ +import { useRouter } from 'expo-router'; import React from 'react'; import { SafeAreaView, ScrollView, StyleSheet, - Text, TouchableOpacity, View, } from 'react-native'; +import { getVersion } from 'react-native-device-info'; import { NavHeader } from '@/components/ui'; +import { Text } from '@/components/ui'; import SettingItem from './components/setting-item'; const Settings: React.FC = () => { + const router = useRouter(); return ( {/* */} {/* 顶部导航 */} - + {/* 系统设置 */} - 系统设置 + {}} @@ -36,7 +39,7 @@ const Settings: React.FC = () => { {}} @@ -46,6 +49,7 @@ const Settings: React.FC = () => { icon="font" iconBgColor="#22c55e" title="字体大小" + tx="settings.system.font_size" subtitle="标准" hasNavigation onPress={() => {}} @@ -54,13 +58,13 @@ const Settings: React.FC = () => { {/* 通知设置 */} - 通知设置 + console.log('推送通知:', value)} @@ -69,8 +73,8 @@ const Settings: React.FC = () => { console.log('邮件通知:', value)} @@ -79,7 +83,7 @@ const Settings: React.FC = () => { console.log('声音提醒:', value)} @@ -88,12 +92,15 @@ const Settings: React.FC = () => { {/* 隐私与安全 */} - 隐私与安全 + {}} /> @@ -101,7 +108,7 @@ const Settings: React.FC = () => { console.log('双因素认证:', value)} @@ -110,7 +117,7 @@ const Settings: React.FC = () => { {}} isLast @@ -118,12 +125,12 @@ const Settings: React.FC = () => { {/* 数据与存储 */} - 数据与存储 + {}} @@ -132,7 +139,7 @@ const Settings: React.FC = () => { console.log('自动同步数据:', value)} @@ -141,7 +148,7 @@ const Settings: React.FC = () => { console.log('仅在WIFI下同步:', value)} @@ -150,29 +157,37 @@ const Settings: React.FC = () => { {/* 关于 */} - 关于 + {}} + onPress={() => { + router.push('/settings/about'); + }} /> {}} /> + {}} + /> {}} isLast diff --git a/src/app/test/device.tsx b/src/app/test/device.tsx index 849060abe7f2a83370ec472a80747a484b6d36f4..774db18d438a4ae9700156c1f07da4d364534403 100644 --- a/src/app/test/device.tsx +++ b/src/app/test/device.tsx @@ -1,5 +1,7 @@ import { Stack } from 'expo-router'; +import * as Updates from 'expo-updates'; import React, { useEffect, useState } from 'react'; +import { TouchableOpacity } from 'react-native'; import { getApplicationName, getBrand, @@ -14,13 +16,46 @@ import { import { SafeAreaView, ScrollView, Text, View } from '@/components/ui'; export default function Style() { + const { currentlyRunning, isUpdateAvailable, isUpdatePending } = + Updates.useUpdates(); const [uniqueId, setUniqueId] = useState(''); + const checkForUpdate = async () => { + try { + const update = await Updates.checkForUpdateAsync(); + alert('update.isAvailable:' + update.isAvailable); + if (update.isAvailable) { + await Updates.fetchUpdateAsync(); + await Updates.reloadAsync(); + } + } catch (error) { + alert('检查更新失败:' + error); + if (error instanceof Error) { + alert('错误信息:' + error.message); + alert('错误堆栈:' + error.stack); + } + } + }; useEffect(() => { getUniqueId().then((uniqueId) => { setUniqueId(uniqueId); }); + // checkForUpdate(); }); + + useEffect(() => { + if (isUpdatePending) { + // Update has successfully downloaded; apply it now + Updates.reloadAsync(); + } + }, [isUpdatePending]); + + // If true, we show the button to download and run the update + const showDownloadButton = isUpdateAvailable; + + const runTypeMessage = currentlyRunning.isEmbeddedLaunch + ? 'This app is running from built-in code' + : 'This app is running an update'; return ( <> 获取应用版本号,Android中对应versionName:{getVersion()} + + + Updates Demo-updateId:{Updates?.updateId} + {runTypeMessage} + { + checkForUpdate(); + }} + > + checkForUpdate + + { + alert(2); + Updates.checkForUpdateAsync(); + }} + > + Check manually for updates + + {showDownloadButton ? ( + { + alert(1); + Updates.fetchUpdateAsync(); + }} + > + Download and run update + + ) : null} + diff --git a/src/components/login-form.tsx b/src/components/login-form.tsx index b7bc7cbe0bdbf54b1a4a2a1607b8aa638fe798b9..0f9fa7b6a7aaa748b19f2149bba70858dd339642 100644 --- a/src/components/login-form.tsx +++ b/src/components/login-form.tsx @@ -21,6 +21,7 @@ import * as z from 'zod'; import { loginApi } from '@/api'; import { Image, Text, View } from '@/components/ui'; import { signIn } from '@/lib'; +import { translate } from '@/lib/i18n'; import { setUserInfo } from '@/lib/user'; import { message } from '@/utils'; @@ -55,14 +56,14 @@ export const LoginForm = () => { // 处理登录 const handleLogin = async () => { if (!username) { - message.info('用户名/手机号不能为空!'); + message.info(translate('login.username_null')); return; } if (!password) { - message.info('密码不能为空!'); + message.info(translate('login.password_null')); return; } - message.loading('用户验证中...'); + message.loading(translate('login.login_loading')); setSubmitBtnDisable(true); const { Success, Data } = await loginApi({ UserAccount: username, @@ -76,7 +77,7 @@ export const LoginForm = () => { }); setUserInfo(Data.UserInfo); - message.info('登录成功!'); + message.info(translate('login.login_success')); setTimeout(() => { router.push('/'); }, 100); @@ -113,7 +114,7 @@ export const LoginForm = () => { /> {Env.NAME} - 智能制造管理系统 + {translate('login.sub_title')} {/* 登录表单 */} @@ -133,7 +134,7 @@ export const LoginForm = () => { /> { /> { disabled={submitBtnDisable} // onPress={onSubmit1(1)} > - 登 录 + {/* 其他登录方式 */} - 其他登录方式 + @@ -236,7 +237,7 @@ export const LoginForm = () => { 还没有账号? 联系管理员 - © 2025 苏州优智云科技有限公司 + diff --git a/src/components/refresh-list-view.tsx b/src/components/refresh-list-view.tsx index 5b1da3d18fec9eec4e8fe4123af79cebd58bd820..877b6c615c034404b96aa77b83478b8638a3f71c 100644 --- a/src/components/refresh-list-view.tsx +++ b/src/components/refresh-list-view.tsx @@ -1,31 +1,86 @@ import React, { useState } from 'react'; -import { FlatList, RefreshControl, StyleSheet, Text, View } from 'react-native'; +import { + FlatList, + type FlatListProps, + RefreshControl, + StyleSheet, + Text, + View, +} from 'react-native'; -interface PageableFlatListProps { +/** + * RefreshListView 组件属性接口 + * + * 该组件扩展了 FlatList 的基础功能,添加了下拉刷新和上拉加载更多的能力 + * 通过 Omit 排除了需要自定义处理的 FlatList 原生属性 + */ +interface RefreshListViewProps + extends Omit< + FlatListProps, + | 'renderItem' + | 'data' + | 'refreshControl' + | 'onEndReached' + | 'onEndReachedThreshold' + | 'ListFooterComponent' + > { + /** 列表数据源 */ data: T[]; - renderItem: ({ item }: { item: T }) => React.ReactElement; // 确保这里接收的对象包含item属性 + /** 渲染列表项的函数 */ + renderItem: ({ item }: { item: T }) => React.ReactElement; + /** 提取列表项唯一键的函数 */ keyExtractor: (item: T) => string; - onRefresh: () => void; - onLoadMore: () => void; - refreshing: boolean; - hasMore: boolean; + /** 下拉刷新回调函数 */ + onRefresh?: () => void; + /** 上拉加载更多回调函数 */ + onLoadMore?: () => void; + /** 是否正在刷新中 */ + refreshing?: boolean; + /** 是否还有更多数据可加载 */ + hasMore?: boolean; } -const PageableFlatList = ({ - data, - renderItem, - keyExtractor, - onRefresh, - onLoadMore, - refreshing, - hasMore, -}: PageableFlatListProps) => { +/** + * 增强型列表组件,支持下拉刷新和上拉加载更多功能 + * + * @example + * ```tsx + * } + * keyExtractor={(item) => item.id} + * onRefresh={handleRefresh} + * onLoadMore={handleLoadMore} + * refreshing={isRefreshing} + * hasMore={hasMoreItems} + * // 可传入其他 FlatList 支持的属性 + * contentContainerStyle={{ padding: 16 }} + * /> + * ``` + */ +const RefreshListView = (props: RefreshListViewProps) => { + const { + data, + renderItem, + keyExtractor, + onRefresh, + onLoadMore, + refreshing, + hasMore, + ...restProps + } = props; + + // 控制加载更多状态,防止重复触发 const [isLoadMore, setIsLoadMore] = useState(false); + /** + * 处理滚动到底部的事件,触发加载更多 + * 只有当前不在加载状态且还有更多数据时才会触发 + */ const handleEndReached = () => { if (!isLoadMore && hasMore) { setIsLoadMore(true); - onLoadMore(); + if (onLoadMore) onLoadMore(); // 假设onLoadMore执行完成后会调用setIsLoadMore(false)来重置状态 setIsLoadMore(false); // 这里为了简化,直接重置,实际应用中可能需要在onLoadMore回调中处理 } @@ -34,13 +89,16 @@ const PageableFlatList = ({ return ( keyExtractor(item)} refreshControl={ - + } onEndReached={handleEndReached} - onEndReachedThreshold={0.1} + onEndReachedThreshold={0.1} // 当距离底部还有10%时触发加载更多 ListFooterComponent={ hasMore ? ( @@ -48,6 +106,7 @@ const PageableFlatList = ({ ) : null } + {...restProps} // 传递其他FlatList支持的属性 /> ); }; @@ -60,4 +119,4 @@ const styles = StyleSheet.create({ }, }); -export default PageableFlatList; +export default RefreshListView; diff --git a/src/components/settings/item.tsx b/src/components/settings/item.tsx index 270fd2b47376490d99ef9d84dfeefa137acd8a00..790360a477c3cd3180a166593c00c2a0232ef4bf 100644 --- a/src/components/settings/item.tsx +++ b/src/components/settings/item.tsx @@ -2,7 +2,7 @@ import * as React from 'react'; import { Pressable, Text, View } from '@/components/ui'; import { ArrowRight } from '@/components/ui/icons'; -import type { TxKeyPath } from '@/lib'; +import type { TxKeyPath } from '@/lib/i18n'; type ItemProps = { text: TxKeyPath; diff --git a/src/components/ui/icons/font-awesome.tsx b/src/components/ui/icons/font-awesome.tsx index b1b65ab3238630fa374ba51424879ce215937041..281d7be805911db36a9496487f6458caf7aa33f6 100644 --- a/src/components/ui/icons/font-awesome.tsx +++ b/src/components/ui/icons/font-awesome.tsx @@ -1,26 +1,52 @@ -import { FontAwesome as FontAwesomeIcon } from '@expo/vector-icons'; -import FontAwesome5 from '@expo/vector-icons/FontAwesome5'; +import { + Entypo, + EvilIcons, + FontAwesome as FontAwesomeIcon, + FontAwesome5, +} from '@expo/vector-icons'; import React from 'react'; +/** + * 图标组类型枚举 + * 用于指定使用哪个图标库 + */ +export enum GroupEnum { + FontAwesome = 'FontAwesome', + Entypo = 'Entypo', + FontAwesome5 = 'FontAwesome5', + EvilIcons = 'EvilIcons', +} + +/** + * 图标组类型 + * 用于限制可用的图标库类型 + */ +type Group = + | GroupEnum.Entypo + | GroupEnum.FontAwesome5 + | GroupEnum.FontAwesome + | GroupEnum.EvilIcons; + /** * FontAwesome图标组件的属性接口 + * @property {string} name - 图标名称 + * @property {number} [size=24] - 图标大小 + * @property {string} [color] - 图标颜色 + * @property {React.CSSProperties} [style] - 自定义样式 + * @property {Group} [group] - 指定图标组,不指定时会自动判断 */ type FontAwesomeIconProps = { name: string; size?: number; color?: string; - style?: any; + style?: React.CSSProperties; // 使用更精确的类型替代 any + group?: Group; }; /** - * 统一的FontAwesome图标组件 - * 自动识别并使用正确的图标库(FontAwesome或FontAwesome5) - * @param name - 图标名称 - * @param size - 图标大小,默认为24 - * @param color - 图标颜色,默认为当前文本颜色 - * @param style - 自定义样式 + * FontAwesome5专用图标列表 + * 当图标名称在此列表中时,将自动使用FontAwesome5库 */ -// FontAwesome5专用图标列表 const FA5_ICONS = [ // 原有图标 'palette', @@ -55,39 +81,76 @@ const FA5_ICONS = [ /** * 判断图标是否属于FontAwesome5 - * @param iconName - 图标名称 - * @returns 是否为FontAwesome5图标 + * @param {string} iconName - 图标名称 + * @returns {boolean} 是否为FontAwesome5图标 */ const isFA5Icon = (iconName: string): boolean => { return FA5_ICONS.includes(iconName); }; /** - * FontAwesome组件 - * 自动识别图标类型并选择正确的图标库 - * @param name - 图标名称 - * @param size - 图标大小,默认为24 - * @param color - 图标颜色,默认为当前文本颜色 - * @param style - 自定义样式 + * 统一的FontAwesome图标组件 + * 根据图标名称或指定的组类型自动选择正确的图标库 + * + * @example + * // 基本用法 + * + * + * // 指定图标组 + * + * + * @param {FontAwesomeIconProps} props - 组件属性 + * @returns {React.ReactElement} 渲染的图标组件 */ export const FontAwesome: React.FC = ({ name, size = 24, color, style, + group, }) => { - // 自动判断是否使用FontAwesome5 + // 优先根据图标名称自动判断图标库 if (isFA5Icon(name)) { return ; } + // 其次根据指定的组类型选择图标库 + if (group === GroupEnum.Entypo) { + return ( + + ); + } else if (group === GroupEnum.FontAwesome5) { + return ( + + ); + } else if (group === GroupEnum.EvilIcons) { + return ( + + ); + } + // 默认使用FontAwesome return ( ); }; diff --git a/src/components/ui/nav-header.tsx b/src/components/ui/nav-header.tsx index 36f92836e784d6f3fe572ec95cfc1815cf0934bf..8e684df4eb2e0879ee2d311adb270b241bfdc151 100644 --- a/src/components/ui/nav-header.tsx +++ b/src/components/ui/nav-header.tsx @@ -1,33 +1,52 @@ import AntDesign from '@expo/vector-icons/AntDesign'; import { Stack, useRouter } from 'expo-router'; import React from 'react'; -import { StyleSheet, Text, TouchableOpacity, View } from 'react-native'; +import { + StatusBar, + StyleSheet, + Text, + TouchableOpacity, + View, +} from 'react-native'; import { Platform } from 'react-native'; +import type { TxKeyPath } from '@/lib/i18n'; +import { translate } from '@/lib/i18n'; export type NavHeaderProps = { leftShown?: boolean; title?: string; + headerBackTitle?: string; right?: React.ReactNode; + tx?: TxKeyPath; }; export const NavHeader = ({ leftShown = true, title = 'Demo', + headerBackTitle = 'Demo', right = null, + tx, }: NavHeaderProps) => { const router = useRouter(); return ( <> - {Platform.OS === 'ios' ? ( + + + {Platform.OS === 'ios' || Platform.OS === 'android' ? ( right && ( - {right && <>{right}} + + {right && <>{right}} + ), }} /> @@ -94,14 +113,18 @@ const styles = StyleSheet.create({ justifyContent: 'flex-end', }, headerRight1: { - marginRight: 20, + marginRight: 10, flexDirection: 'row', alignItems: 'center', width: 90, // 固定宽度,确保与左侧空白区域平衡 justifyContent: 'flex-end', }, - headerButton: { - marginLeft: 16, + + headerRight2: { + flexDirection: 'row', + alignItems: 'center', + width: 90, // 固定宽度,确保与左侧空白区域平衡 + justifyContent: 'flex-end', }, }); diff --git a/src/lib/i18n/resources.ts b/src/lib/i18n/resources.ts index ac05206b3b2f97fd94ac9a1083df0edb92d3e49b..55b0600c549e7d63e644c3d3b3c956253a96746e 100644 --- a/src/lib/i18n/resources.ts +++ b/src/lib/i18n/resources.ts @@ -1,12 +1,12 @@ -import ar from '@/translations/ar.json'; import en from '@/translations/en.json'; +import zh from '@/translations/zh.json'; export const resources = { en: { translation: en, }, - ar: { - translation: ar, + zh: { + translation: zh, }, }; diff --git a/src/lib/i18n/utils.tsx b/src/lib/i18n/utils.tsx index ac699720652f40562bf2cb1c34c5f1728e9d7f4e..4c6b1e18fe5b5d7db96e820fba00e16344d46bc6 100644 --- a/src/lib/i18n/utils.tsx +++ b/src/lib/i18n/utils.tsx @@ -10,7 +10,7 @@ import { storage } from '../storage'; import type { Language, resources } from './resources'; import type { RecursiveKeyOf } from './types'; -type DefaultLocale = typeof resources.en.translation; +type DefaultLocale = typeof resources.zh.translation; export type TxKeyPath = RecursiveKeyOf; export const LOCAL = 'local'; @@ -26,7 +26,7 @@ export const translate = memoize( export const changeLanguage = (lang: Language) => { i18n.changeLanguage(lang); - if (lang === 'ar') { + if (lang === 'en') { I18nManager.forceRTL(true); } else { I18nManager.forceRTL(false); diff --git a/src/translations/ar.json b/src/translations/ar.json deleted file mode 100644 index c53a32a043c0cf5e1c244d27efd2df07b15f23f1..0000000000000000000000000000000000000000 --- a/src/translations/ar.json +++ /dev/null @@ -1,33 +0,0 @@ -{ - "onboarding": { - "message": "مرحبا بكم في موقع تطبيق obytes" - }, - "settings": { - "about": "حول التطبيق ", - "app_name": "اسم التطبيق", - "arabic": "عربي", - "english": "إنجليزي", - "generale": "عام", - "github": "جيثب", - "language": "لغة", - "links": "الروابط", - "logout": "تسجيل خروج", - "more": "أكثر", - "privacy": "سياسة الخصوصية", - "rate": "تقييم", - "share": "شارك", - "support": "الدعم", - "support_us": "ادعمنا", - "terms": "شروط الخدمة", - "theme": { - "dark": "مظلم", - "light": "خفيفة", - "system": "System", - "title": "سمة" - }, - "title": "إعدادات", - "version": "إصدار", - "website": "موقع الكتروني" - }, - "welcome": "test arabic" -} diff --git a/src/translations/en.json b/src/translations/en.json index a3a282fa5561c8216803172c88392413cc0dcccf..a8f8f0824076a99c74c70229cfc7e5941311ee30 100644 --- a/src/translations/en.json +++ b/src/translations/en.json @@ -1,28 +1,72 @@ { + "copyright": "© 2025 苏州优智云科技有限公司", + "login": { + "login_button": "登 录", + "login_loading": "用户验证中...", + "login_success": "登录成功!", + "other_login_way": "其他登录方式", + "password_null": "密码不能为空!", + "password_placeholder": "密码", + "sub_title": "智能制造管理系统", + "username_null": "用户名/手机号不能为空!", + "username_placeholder": "用户名/手机号" + }, "onboarding": { "message": "Welcome to eu-cloud app site" }, "profile": { - "about": "About", + "about": "关于我们", "feedback": "Feedback", - "help": "Help" + "help": "帮助中心" }, "settings": { - "about": "About", + "about": { + "about_us": "关于优智云", + "contact_support": "联系客服", + "help": "帮助中心", + "title": "关于", + "user_agreement": "用户协议" + }, "app_name": "App Name", "arabic": "Arabic", + "data_storage": { + "auto_sync": "自动同步数据", + "clear_cache": "清除缓存", + "title": "数据与存储", + "wifi_only_sync": "仅在WIFI下同步" + }, "english": "English", "generale": "General", "github": "Github", - "language": "Language", + "language": "语言", "links": "Links", - "logout": "Logout", + "logout": "退出登录", "more": "More", + "notification": { + "email": "邮件通知", + "notification": "推送通知", + "sound": "声音提醒", + "sub_email": "仅接收重要通知", + "sub_notification": "接收所有通知", + "title": "通知设置" + }, "privacy": "Privacy Policy", + "privacy_security": { + "2FA": "双因素认证", + "password": "修改密码", + "privacy": "隐私设置", + "title": "隐私与安全" + }, "rate": "Rate", "share": "Share", "support": "Support", "support_us": "Support Us", + "system": { + "font_size": "字体大小", + "language": "语言", + "theme": "主题", + "title": "系统设置" + }, "terms": "Terms of Service", "theme": { "dark": "Dark", @@ -30,9 +74,9 @@ "system": "System", "title": "Theme" }, - "title": "Settings", + "title": "设置", "version": "Version", "website": "Website" }, - "welcome": "Welcome to obytes app site" + "welcome": "Welcome to eucloud" } diff --git a/src/translations/zh.json b/src/translations/zh.json new file mode 100644 index 0000000000000000000000000000000000000000..a8f8f0824076a99c74c70229cfc7e5941311ee30 --- /dev/null +++ b/src/translations/zh.json @@ -0,0 +1,82 @@ +{ + "copyright": "© 2025 苏州优智云科技有限公司", + "login": { + "login_button": "登 录", + "login_loading": "用户验证中...", + "login_success": "登录成功!", + "other_login_way": "其他登录方式", + "password_null": "密码不能为空!", + "password_placeholder": "密码", + "sub_title": "智能制造管理系统", + "username_null": "用户名/手机号不能为空!", + "username_placeholder": "用户名/手机号" + }, + "onboarding": { + "message": "Welcome to eu-cloud app site" + }, + "profile": { + "about": "关于我们", + "feedback": "Feedback", + "help": "帮助中心" + }, + "settings": { + "about": { + "about_us": "关于优智云", + "contact_support": "联系客服", + "help": "帮助中心", + "title": "关于", + "user_agreement": "用户协议" + }, + "app_name": "App Name", + "arabic": "Arabic", + "data_storage": { + "auto_sync": "自动同步数据", + "clear_cache": "清除缓存", + "title": "数据与存储", + "wifi_only_sync": "仅在WIFI下同步" + }, + "english": "English", + "generale": "General", + "github": "Github", + "language": "语言", + "links": "Links", + "logout": "退出登录", + "more": "More", + "notification": { + "email": "邮件通知", + "notification": "推送通知", + "sound": "声音提醒", + "sub_email": "仅接收重要通知", + "sub_notification": "接收所有通知", + "title": "通知设置" + }, + "privacy": "Privacy Policy", + "privacy_security": { + "2FA": "双因素认证", + "password": "修改密码", + "privacy": "隐私设置", + "title": "隐私与安全" + }, + "rate": "Rate", + "share": "Share", + "support": "Support", + "support_us": "Support Us", + "system": { + "font_size": "字体大小", + "language": "语言", + "theme": "主题", + "title": "系统设置" + }, + "terms": "Terms of Service", + "theme": { + "dark": "Dark", + "light": "Light", + "system": "System", + "title": "Theme" + }, + "title": "设置", + "version": "Version", + "website": "Website" + }, + "welcome": "Welcome to eucloud" +}