{"id":526,"date":"2023-09-21T00:44:44","date_gmt":"2023-09-20T16:44:44","guid":{"rendered":"https:\/\/www.muxuetianyin.cn\/?p=526"},"modified":"2024-04-14T23:28:29","modified_gmt":"2024-04-14T15:28:29","slug":"react-native%e5%ba%94%e7%94%a8","status":"publish","type":"post","link":"https:\/\/www.muxuetianyin.cn\/?p=526","title":{"rendered":"React Native\u5e94\u7528"},"content":{"rendered":"\n<p>\u8bbe\u8ba1\u4e00\u4e2aReact Native\u5e94\u7528\u9700\u8981\u8003\u8651\u5f88\u591a\u56e0\u7d20\uff0c\u5305\u62ec\u5e94\u7528\u7684\u529f\u80fd\u3001\u7528\u6237\u754c\u9762\u3001\u6570\u636e\u7ba1\u7406\u3001\u72b6\u6001\u7ba1\u7406\u3001\u8def\u7531\u7ba1\u7406\u3001\u6d4b\u8bd5\u7b49\u3002\u4ee5\u4e0b\u662f\u4e00\u4e2a\u7b80\u5355\u7684React Native\u5e94\u7528\u8bbe\u8ba1\u65b9\u6848\uff0c\u4ee5\u53ca\u4e00\u4e9b\u63a8\u8350\u7684\u6280\u672f\u548c\u5e93\uff1a<\/p>\n\n\n\n<ol>\n<li><strong>\u5e94\u7528\u529f\u80fd<\/strong>\uff1a\u5047\u8bbe\u6211\u4eec\u8981\u5f00\u53d1\u4e00\u4e2a\u7b80\u5355\u7684\u4efb\u52a1\u7ba1\u7406\u5e94\u7528\uff0c\u7528\u6237\u53ef\u4ee5\u6dfb\u52a0\u4efb\u52a1\uff0c\u6807\u8bb0\u4efb\u52a1\u5b8c\u6210\uff0c\u67e5\u770b\u4efb\u52a1\u5217\u8868\u3002<\/li>\n\n\n\n<li><strong>\u7528\u6237\u754c\u9762<\/strong>\uff1a\u6211\u4eec\u53ef\u4ee5\u4f7f\u7528React Native\u7684\u5185\u7f6e\u7ec4\u4ef6\u6765\u521b\u5efa\u7528\u6237\u754c\u9762\uff0c\u5982View\u3001Text\u3001Button\u7b49\u3002\u5982\u679c\u9700\u8981\u66f4\u590d\u6742\u7684\u7ec4\u4ef6\uff0c\u53ef\u4ee5\u4f7f\u7528\u7b2c\u4e09\u65b9\u5e93\uff0c\u5982React Native Elements\u6216NativeBase\u3002<\/li>\n\n\n\n<li><strong>\u6570\u636e\u7ba1\u7406<\/strong>\uff1a\u6211\u4eec\u53ef\u4ee5\u4f7f\u7528React\u7684useState\u6216useReducer Hook\u6765\u7ba1\u7406\u5e94\u7528\u7684\u72b6\u6001\u3002\u5982\u679c\u5e94\u7528\u7684\u72b6\u6001\u66f4\u590d\u6742\uff0c\u53ef\u4ee5\u4f7f\u7528Redux\u6216MobX\u3002<\/li>\n\n\n\n<li><strong>\u8def\u7531\u7ba1\u7406<\/strong>\uff1a\u6211\u4eec\u53ef\u4ee5\u4f7f\u7528React Navigation\u6765\u7ba1\u7406\u5e94\u7528\u7684\u8def\u7531\u3002React Navigation\u63d0\u4f9b\u4e86\u4e00\u79cd\u5728React Native\u5e94\u7528\u4e2d\u521b\u5efa\u5bfc\u822a\u7ed3\u6784\u7684\u65b9\u5f0f\u3002<\/li>\n\n\n\n<li><strong>\u6d4b\u8bd5<\/strong>\uff1a\u6211\u4eec\u53ef\u4ee5\u4f7f\u7528Jest\u6765\u8fdb\u884c\u5355\u5143\u6d4b\u8bd5\uff0c\u4f7f\u7528Detox\u8fdb\u884c\u7aef\u5230\u7aef\u6d4b\u8bd5\u3002<\/li>\n\n\n\n<li><strong>\u96c6\u6210\u6846\u67b6<\/strong>\uff1a\u6211\u4eec\u53ef\u4ee5\u4f7f\u7528Expo\u4f5c\u4e3a\u5f00\u53d1\u548c\u6784\u5efa\u5de5\u5177\u3002Expo\u63d0\u4f9b\u4e86\u4e00\u79cd\u65e0\u9700\u914d\u7f6e\u5c31\u53ef\u4ee5\u521b\u5efaReact Native\u5e94\u7528\u7684\u65b9\u5f0f\uff0c\u540c\u65f6\u8fd8\u63d0\u4f9b\u4e86\u8bb8\u591a\u65b9\u4fbf\u7684\u7279\u6027\uff0c\u5982\u5b9e\u65f6\u91cd\u8f7d\u3001\u9519\u8bef\u62a5\u544a\u3001\u8c03\u8bd5\u5de5\u5177\u7b49\u3002<\/li>\n<\/ol>\n\n\n\n<h1 class=\"wp-block-heading\">\u9879\u76ee\u5f00\u59cb<\/h1>\n\n\n\n<p>\u6846\u67b6\u642d\u5efa\u91c7\u7528\u539f\u751fReact Native\uff0c\u6210\u672c\u4f4e\u6027\u80fd\u4e5f\u4e0d\u4f1a\u6bd4Flutter\u5dee\u5f88\u591a\uff01<\/p>\n\n\n\n<ol>\n<li><strong>Expo<\/strong>\uff1aExpo\u662f\u4e00\u4e2a\u5f00\u6e90\u7684React Native\u9879\u76ee\uff0c\u5b83\u63d0\u4f9b\u4e86\u4e00\u4e9b\u5de5\u5177\u548c\u670d\u52a1\uff0c\u53ef\u4ee5\u5e2e\u52a9\u4f60\u66f4\u5bb9\u6613\u5730\u4f7f\u7528React Native\u6784\u5efa\u548c\u90e8\u7f72\u5e94\u7528\u3002Expo\u63d0\u4f9b\u4e86\u4e00\u4e9b\u9884\u5b9a\u4e49\u7684\u6a21\u677f\uff0c\u53ef\u4ee5\u5e2e\u52a9\u4f60\u5feb\u901f\u5f00\u59cb\u65b0\u7684\u9879\u76ee\u3002<\/li>\n\n\n\n<li><strong>Ignite CLI<\/strong>\uff1aIgnite CLI\u662f\u4e00\u4e2aReact Native\u5e94\u7528\u751f\u6210\u5668\uff0c\u5b83\u63d0\u4f9b\u4e86\u4e00\u4e9b\u9884\u5b9a\u4e49\u7684\u6a21\u677f\u548c\u5de5\u5177\uff0c\u53ef\u4ee5\u5e2e\u52a9\u4f60\u5feb\u901f\u521b\u5efa\u548c\u5f00\u53d1React Native\u5e94\u7528\u3002<\/li>\n\n\n\n<li><strong>React Native Elements<\/strong>\uff1aReact Native Elements\u662f\u4e00\u4e2aReact Native\u7684UI\u5de5\u5177\u5305\uff0c\u5b83\u63d0\u4f9b\u4e86\u4e00\u4e9b\u9884\u5b9a\u4e49\u7684\u7ec4\u4ef6\uff0c\u53ef\u4ee5\u5e2e\u52a9\u4f60\u5feb\u901f\u521b\u5efa\u7528\u6237\u754c\u9762\u3002<\/li>\n\n\n\n<li><strong>NativeBase<\/strong>\uff1aNativeBase\u662f\u4e00\u4e2aReact Native\u7684UI\u5de5\u5177\u5305\uff0c\u5b83\u63d0\u4f9b\u4e86\u4e00\u4e9b\u9884\u5b9a\u4e49\u7684\u7ec4\u4ef6\uff0c\u53ef\u4ee5\u5e2e\u52a9\u4f60\u5feb\u901f\u521b\u5efa\u7528\u6237\u754c\u9762\u3002<\/li>\n\n\n\n<li><strong>React Native Paper<\/strong>\uff1aReact Native Paper\u662f\u4e00\u4e2aReact Native\u7684UI\u5de5\u5177\u5305\uff0c\u5b83\u9075\u5faaMaterial Design\u89c4\u8303\uff0c\u63d0\u4f9b\u4e86\u4e00\u4e9b\u9884\u5b9a\u4e49\u7684\u7ec4\u4ef6\uff0c\u53ef\u4ee5\u5e2e\u52a9\u4f60\u5feb\u901f\u521b\u5efa\u7528\u6237\u754c\u9762<\/li>\n<\/ol>\n\n\n\n<p>\u901b\u4e86\u4e00\u5708\u53d1\u73b0\u53ef\u4ee5\u4f7f\u7528Expo\u53ef\u4ee5\u5feb\u901f\u642d\u5efa<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">\u4ec0\u4e48\u662fExpo\uff1f<\/h2>\n\n\n\n<p>Expo\u662f\u4e00\u7ec4\u5de5\u5177\u3001\u5e93\u548c\u670d\u52a1\uff0c\u53ef\u4ee5\u901a\u8fc7\u7f16\u5199JavaScript\u6765\u6784\u5efa\u672c\u5730\u7684iOS\u548candroid\u5e94\u7528\u7a0b\u5e8f\u3002\u8bf4\u4eba\u8bdd\uff0c\u5c31\u662f\u5728React Native\u7684\u57fa\u7840\u4e0a\u518d\u5c01\u88c5\u4e86\u4e00\u5c42\uff0c\u8ba9\u6211\u4eec\u7684\u5f00\u53d1\u66f4\u65b9\u4fbf\uff0c\u66f4\u5feb\u901f\u3002<\/p>\n\n\n\n<ul>\n<li>\u505a\u8fc7\u79fb\u52a8\u7aef\u7684\u540c\u5b66\u5728\u505a\u8de8\u5e73\u53f0\u4e4b\u524d\u80af\u5b9a\u4f1a\u62c5\u5fc3\u4e00\u4e2a\u70b9\uff0c\u5c31\u662f\u5404\u79cd\u539f\u751f\u529f\u80fd\uff08\u76f8\u673a\uff0c\u76f8\u518c\uff0c\u5b9a\u4f4d\uff0c\u84dd\u7259\u7b49\u7b49\uff09\uff0c\u4f7f\u7528expo\u7684\u8bdd\uff0c\u4f1a\u6bd4\u4f60\u5f00\u53d1\u4e00\u4e2a\u88f8\u7684React Native\u771f\u7684\u4f1a\u5feb\u5f88\u591a\uff0c\u800c\u4e14\u4f1a\u5c11\u8e29\u5f88\u591a\u5751<\/li>\n\n\n\n<li>\u6ca1\u6709\u505a\u8fc7\u79fb\u52a8\u7aef\u7684\u524d\u7aef\u90a3\u5c31\u66f4\u9700\u8981\u8fd9\u4e2a\u4e86\uff0c\u4e0d\u7136\u79fb\u52a8\u7aef\u7684\u4e00\u4e9b\u9690\u85cf\u7684\u9650\u5236\u548c\u5751\uff0c\u4f1a\u8ba9\u4f60\u5f88\u5934\u75bc<\/li>\n<\/ul>\n\n\n\n<h2 class=\"wp-block-heading\">\u9879\u76ee\u642d\u5efa<\/h2>\n\n\n\n<p><strong>\u5b89\u88c5Expo<\/strong><\/p>\n\n\n\n<ol>\n<li>\u9996\u5148\uff0c\u4f60\u9700\u8981\u5728\u4f60\u7684\u673a\u5668\u4e0a\u5b89\u88c5Node.js\u548cnpm\u3002\u4f60\u53ef\u4ee5\u4eceNode.js\u5b98\u7f51\u4e0b\u8f7d\u5e76\u5b89\u88c5Node.js\uff0cnpm\u4f1a\u968f\u7740Node.js\u4e00\u8d77\u5b89\u88c5\u3002<\/li>\n\n\n\n<li>\u7136\u540e\uff0c\u4f60\u53ef\u4ee5\u4f7f\u7528npm\u5168\u5c40\u5b89\u88c5Expo CLI\uff1a<\/li>\n<\/ol>\n\n\n\n<p><\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>&nbsp;npm install -g expo-cli<\/code><\/pre>\n\n\n\n<ol start=\"3\">\n<li>\u521b\u5efa\u4e00\u4e2a\u65b0\u7684React Native\u9879\u76ee\uff1a<\/li>\n<\/ol>\n\n\n\n<p>&nbsp;expo init MyProject<\/p>\n\n\n\n<p>\u5728\u8fd9\u4e2a\u6b65\u9aa4\u4e2d\uff0cExpo CLI\u4f1a\u8ba9\u4f60\u9009\u62e9\u4e00\u4e2a\u6a21\u677f\u3002\u4f60\u53ef\u4ee5\u9009\u62e9&#8221;blank&#8221;\u6a21\u677f\u6765\u521b\u5efa\u4e00\u4e2a\u7a7a\u7684\u9879\u76ee\uff0c\u6216\u8005\u9009\u62e9\u5176\u4ed6\u7684\u6a21\u677f\u6765\u521b\u5efa\u4e00\u4e2a\u5305\u542b\u4e00\u4e9b\u9884\u5b9a\u4e49\u529f\u80fd\u7684\u9879\u76ee\u3002<\/p>\n\n\n\n<figure class=\"wp-block-image size-full\"><div class='fancybox-wrapper' data-fancybox='post-images' href='https:\/\/www.muxuetianyin.cn\/wp-content\/uploads\/2023\/09\/image-3.png'><img loading=\"lazy\" width=\"809\" height=\"372\" src=\"https:\/\/www.muxuetianyin.cn\/wp-content\/uploads\/2023\/09\/image-3.png\" alt=\"\" class=\"wp-image-527\" srcset=\"https:\/\/www.muxuetianyin.cn\/wp-content\/uploads\/2023\/09\/image-3.png 809w, https:\/\/www.muxuetianyin.cn\/wp-content\/uploads\/2023\/09\/image-3-300x138.png 300w, https:\/\/www.muxuetianyin.cn\/wp-content\/uploads\/2023\/09\/image-3-768x353.png 768w\" sizes=\"(max-width: 809px) 100vw, 809px\" \/><\/div><\/figure>\n\n\n\n<ol>\n<li><strong>blank<\/strong>\uff1a\u8fd9\u662f\u4e00\u4e2a\u6700\u5c0f\u7684\u5e94\u7528\uff0c\u5c31\u50cf\u4e00\u4e2a\u7a7a\u767d\u7684\u753b\u5e03\u3002\u5982\u679c\u4f60\u60f3\u4ece\u96f6\u5f00\u59cb\u521b\u5efa\u4f60\u7684\u5e94\u7528\uff0c\u6216\u8005\u4f60\u60f3\u5b8c\u5168\u63a7\u5236\u4f60\u7684\u5e94\u7528\u7684\u7ed3\u6784\u548c\u4ee3\u7801\uff0c\u90a3\u4e48\u8fd9\u53ef\u80fd\u662f\u4e00\u4e2a\u597d\u7684\u9009\u62e9\u3002<\/li>\n\n\n\n<li><strong>blank (TypeScript)<\/strong>\uff1a\u8fd9\u548c&#8221;blank&#8221;\u6a21\u677f\u76f8\u540c\uff0c\u4f46\u662f\u5b83\u4f7f\u7528TypeScript\u8fdb\u884c\u914d\u7f6e\u3002\u5982\u679c\u4f60\u559c\u6b22\u4f7f\u7528TypeScript\uff0c\u90a3\u4e48\u8fd9\u53ef\u80fd\u662f\u4e00\u4e2a\u597d\u7684\u9009\u62e9\u3002<\/li>\n\n\n\n<li><strong>tabs (TypeScript)<\/strong>\uff1a\u8fd9\u4e2a\u6a21\u677f\u5305\u542b\u4e86\u51e0\u4e2a\u793a\u4f8b\u5c4f\u5e55\u548c\u4f7f\u7528react-navigation\u548cTypeScript\u7684\u6807\u7b7e\u3002\u5982\u679c\u4f60\u60f3\u521b\u5efa\u4e00\u4e2a\u5305\u542b\u591a\u4e2a\u5c4f\u5e55\u548c\u6807\u7b7e\u7684\u5e94\u7528\uff0c\u90a3\u4e48\u8fd9\u53ef\u80fd\u662f\u4e00\u4e2a\u597d\u7684\u9009\u62e9\u3002<\/li>\n\n\n\n<li><strong>minimal (Bare workflow)<\/strong>\uff1a\u8fd9\u662f\u4e00\u4e2a\u6700\u5c0f\u7684\u5e94\u7528\uff0c\u53ea\u5305\u542b\u4e86\u4f60\u5f00\u59cb\u9700\u8981\u7684\u57fa\u672c\u5185\u5bb9\u3002\u8fd9\u4e2a\u6a21\u677f\u4f7f\u7528\u4e86Bare workflow\uff0c\u8fd9\u610f\u5473\u7740\u4f60\u53ef\u4ee5\u76f4\u63a5\u8bbf\u95ee\u548c\u4fee\u6539\u539f\u751f\u4ee3\u7801\u3002\u5982\u679c\u4f60\u9700\u8981\u4f7f\u7528\u4e00\u4e9bExpo\u4e0d\u652f\u6301\u7684\u539f\u751f\u6a21\u5757\uff0c\u6216\u8005\u4f60\u60f3\u5b8c\u5168\u63a7\u5236\u4f60\u7684\u5e94\u7528\u7684\u6784\u5efa\u548c\u90e8\u7f72\u8fc7\u7a0b\uff0c\u90a3\u4e48\u8fd9\u53ef\u80fd\u662f\u4e00\u4e2a\u597d\u7684\u9009\u62e9\u3002<\/li>\n\n\n\n<li>\u8fdb\u5165\u9879\u76ee\u76ee\u5f55\u5e76\u542f\u52a8\u9879\u76ee\uff1a<\/li>\n<\/ol>\n\n\n\n<figure class=\"wp-block-image size-full\"><div class='fancybox-wrapper' data-fancybox='post-images' href='https:\/\/www.muxuetianyin.cn\/wp-content\/uploads\/2023\/09\/image-4.png'><img loading=\"lazy\" width=\"864\" height=\"473\" src=\"https:\/\/www.muxuetianyin.cn\/wp-content\/uploads\/2023\/09\/image-4.png\" alt=\"\" class=\"wp-image-528\" srcset=\"https:\/\/www.muxuetianyin.cn\/wp-content\/uploads\/2023\/09\/image-4.png 864w, https:\/\/www.muxuetianyin.cn\/wp-content\/uploads\/2023\/09\/image-4-300x164.png 300w, https:\/\/www.muxuetianyin.cn\/wp-content\/uploads\/2023\/09\/image-4-768x420.png 768w\" sizes=\"(max-width: 864px) 100vw, 864px\" \/><\/div><\/figure>\n\n\n\n<p>&nbsp;<\/p>\n\n\n\n<p>cd MyProject<br>&nbsp;expo start<\/p>\n\n\n\n<p>\u8fd9\u4e2a\u547d\u4ee4\u4f1a\u542f\u52a8\u4e00\u4e2a\u5f00\u53d1\u670d\u52a1\u5668\uff0c\u5e76\u6253\u5f00\u4e00\u4e2a\u65b0\u7684\u6d4f\u89c8\u5668\u7a97\u53e3\uff0c\u4f60\u53ef\u4ee5\u5728\u8fd9\u4e2a\u7a97\u53e3\u4e2d\u9884\u89c8\u4f60\u7684\u5e94\u7528\u3002<\/p>\n\n\n\n<p><\/p>\n\n\n\n<p>&nbsp;npx expo install &#8211;fix<\/p>\n\n\n\n<ul>\n<li>s\uff1a\u5207\u6362\u5230\u5f00\u53d1\u6784\u5efa\u6a21\u5f0f<\/li>\n\n\n\n<li>a\uff1a\u5728Android\u8bbe\u5907\u6216\u6a21\u62df\u5668\u4e0a\u6253\u5f00\u5e94\u7528<\/li>\n\n\n\n<li>w\uff1a\u5728Web\u6d4f\u89c8\u5668\u4e2d\u6253\u5f00\u5e94\u7528<\/li>\n\n\n\n<li>j\uff1a\u6253\u5f00\u8c03\u8bd5\u5668<\/li>\n\n\n\n<li>r\uff1a\u91cd\u65b0\u52a0\u8f7d\u5e94\u7528<\/li>\n\n\n\n<li>m\uff1a\u5207\u6362\u83dc\u5355<\/li>\n\n\n\n<li>o\uff1a\u5728\u4f60\u7684\u7f16\u8f91\u5668\u4e2d\u6253\u5f00\u9879\u76ee\u4ee3\u7801<\/li>\n\n\n\n<li>?\uff1a\u663e\u793a\u6240\u6709\u547d\u4ee4<\/li>\n<\/ul>\n\n\n\n<p>\u5982\u679c\u4f7f\u7528web\u5f00\u53d1\u9700\u8981\u5b89\u88c5\u4f9d\u8d56&nbsp;npx expo install react-native-web@~0.19.6 react-dom@18.2.0 @expo\/webpack-config@^19.0.0<\/p>\n\n\n\n<figure class=\"wp-block-image size-full\"><div class='fancybox-wrapper' data-fancybox='post-images' href='https:\/\/www.muxuetianyin.cn\/wp-content\/uploads\/2023\/09\/image-5.png'><img loading=\"lazy\" width=\"478\" height=\"786\" src=\"https:\/\/www.muxuetianyin.cn\/wp-content\/uploads\/2023\/09\/image-5.png\" alt=\"\" class=\"wp-image-529\" srcset=\"https:\/\/www.muxuetianyin.cn\/wp-content\/uploads\/2023\/09\/image-5.png 478w, https:\/\/www.muxuetianyin.cn\/wp-content\/uploads\/2023\/09\/image-5-182x300.png 182w\" sizes=\"(max-width: 478px) 100vw, 478px\" \/><\/div><\/figure>\n\n\n\n<p><strong>Android \u6a21\u62df\u5668\uff1a<\/strong><\/p>\n\n\n\n<ol>\n<li>\u9996\u5148\uff0c\u4f60\u9700\u8981\u5728\u4f60\u7684\u7535\u8111\u4e0a\u5b89\u88c5 Android Studio \u548c Android SDK\u3002\u4f60\u53ef\u4ee5\u4ece Android Studio \u7684\u5b98\u65b9\u7f51\u7ad9\u4e0b\u8f7d\u5e76\u5b89\u88c5\u5b83\u3002<\/li>\n\n\n\n<li>\u5728 Android Studio \u4e2d\uff0c\u4f60\u53ef\u4ee5\u521b\u5efa\u5e76\u542f\u52a8\u4e00\u4e2a Android \u865a\u62df\u8bbe\u5907\uff08AVD\uff09\u3002<\/li>\n\n\n\n<li>\u5728\u4f60\u7684 Expo \u9879\u76ee\u7684\u76ee\u5f55\u4e2d\u6253\u5f00\u4e00\u4e2a\u547d\u4ee4\u884c\u7a97\u53e3\uff0c\u7136\u540e\u8fd0\u884c expo start \u547d\u4ee4\u6765\u542f\u52a8 Expo \u5f00\u53d1\u670d\u52a1\u5668\u3002<\/li>\n\n\n\n<li>\u5728 Expo \u5f00\u53d1\u670d\u52a1\u5668\u7684\u7f51\u9875\u754c\u9762\u4e2d\uff0c\u70b9\u51fb &#8220;Run on Android device\/emulator&#8221;\u3002<\/li>\n<\/ol>\n\n\n\n<p><strong>iOS \u6a21\u62df\u5668\uff08\u53ea\u9002\u7528\u4e8e Mac\uff09\uff1a<\/strong><\/p>\n\n\n\n<ol>\n<li>\u9996\u5148\uff0c\u4f60\u9700\u8981\u5728\u4f60\u7684 Mac \u4e0a\u5b89\u88c5 Xcode\u3002\u4f60\u53ef\u4ee5\u4ece Mac App Store \u4e0b\u8f7d\u5e76\u5b89\u88c5\u5b83\u3002<\/li>\n\n\n\n<li>\u5728 Xcode \u4e2d\uff0c\u4f60\u53ef\u4ee5\u4ece &#8220;Xcode &gt; Open Developer Tool &gt; Simulator&#8221; \u83dc\u5355\u4e2d\u542f\u52a8 iOS \u6a21\u62df\u5668\u3002<\/li>\n\n\n\n<li>\u5728\u4f60\u7684 Expo \u9879\u76ee\u7684\u76ee\u5f55\u4e2d\u6253\u5f00\u4e00\u4e2a\u547d\u4ee4\u884c\u7a97\u53e3\uff0c\u7136\u540e\u8fd0\u884c expo start \u547d\u4ee4\u6765\u542f\u52a8 Expo \u5f00\u53d1\u670d\u52a1\u5668\u3002<\/li>\n\n\n\n<li>\u5728 Expo \u5f00\u53d1\u670d\u52a1\u5668\u7684\u7f51\u9875\u754c\u9762\u4e2d\uff0c\u70b9\u51fb &#8220;Run on iOS simulator&#8221;<\/li>\n<\/ol>\n\n\n\n<h2 class=\"wp-block-heading\">\u5728\u5b89\u5353\u624b\u673a\u4e0a\u8fd0\u884c<\/h2>\n\n\n\n<p>\u542f\u52a8\u540e\uff0c\u8fde\u63a5wifi\uff08\u9700\u8981\u4e0e\u542f\u52a8\u9879\u76ee\u7684\u7535\u8111\u5728\u540c\u4e00\u4e2a\u5c40\u57df\u7f51\u4e0b\uff09\uff0c\u5e76\u626b\u63cf\u63a7\u5236\u53f0\u4e0a\u8f93\u51fa\u7684\u4e8c\u7ef4\u7801\u6765\u542f\u52a8\u9879\u76ee<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">\u5728IPhone\u4e0a\u8fd0\u884c<\/h2>\n\n\n\n<p>\u542f\u52a8\u540e\uff0c\u8fde\u63a5wifi\uff08\u9700\u8981\u4e0e\u542f\u52a8\u9879\u76ee\u7684\u7535\u8111\u5728\u540c\u4e00\u4e2a\u5c40\u57df\u7f51\u4e0b\uff09\uff0c\u5e76\u6253\u5f00\u7cfb\u7edf\u81ea\u5e26\u7684\u539f\u751f\u76f8\u673a\u626b\u7801\uff0c\u6ce8\u610f\u662f\u81ea\u5e26\u7684\u76f8\u673a\uff0c\u63d0\u793a\u524d\u5f80expogo\u540e\u70b9\u51fb\u4e8c\u7ef4\u7801\u6216\u6309\u94ae\u524d\u5f80expo go\u542f\u52a8\u9879\u76ee<\/p>\n\n\n\n<figure class=\"wp-block-image size-full\"><div class='fancybox-wrapper' data-fancybox='post-images' href='https:\/\/www.muxuetianyin.cn\/wp-content\/uploads\/2023\/09\/image-6.png'><img loading=\"lazy\" width=\"469\" height=\"210\" src=\"https:\/\/www.muxuetianyin.cn\/wp-content\/uploads\/2023\/09\/image-6.png\" alt=\"\" class=\"wp-image-530\" srcset=\"https:\/\/www.muxuetianyin.cn\/wp-content\/uploads\/2023\/09\/image-6.png 469w, https:\/\/www.muxuetianyin.cn\/wp-content\/uploads\/2023\/09\/image-6-300x134.png 300w\" sizes=\"(max-width: 469px) 100vw, 469px\" \/><\/div><\/figure>\n\n\n\n<h2 class=\"wp-block-heading\">\u9879\u76ee\u8bbe\u8ba1<\/h2>\n\n\n\n<p>\u8fd9\u4e2a\u9879\u76ee\u4e3b\u8981\u662f\u60f3\u505a\u4ec0\u4e48\u7684\uff0c\u7ed9\u8c01\u7528\u81ea\u5df1\uff0c\u4ed6\u4eba\u8fd8\u662f\u4ec0\u4e48\uff01<\/p>\n\n\n\n<p>1.\u81ea\u5df1\u4f7f\u7528\u670b\u53cb\u67e5\u770b\u53ef\u4ee5\u8bb0\u5f55\u751f\u6d3b\uff0c\u8bb0\u5f55\u81ea\u5df1\u5b66\u4e60\u70b9\u70b9\u6ef4\u6ef4<\/p>\n\n\n\n<p>2.\u53ef\u4ee5\u4e0a\u4f20\u56fe\u7247\uff0c\u76f8\u518c\u8fd9\u6837<\/p>\n\n\n\n<p>3.\u7136\u540e\u5c55\u793a\u4e2a\u4eba\u6280\u672f\u4eae\u70b9\u9879\u76ee<\/p>\n\n\n\n<p>\u8bbe\u8ba1\u53c2\u8003b\u7ad9\uff0c\u5c0f\u7ea2\u4e66\u8fd9\u6837\u7684<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">\u6587\u4ef6\u8bbe\u8ba1<\/h2>\n\n\n\n<h3 class=\"wp-block-heading\">1.\u8bbe\u8ba1\u6587\u4ef6\u76ee\u5f55<\/h3>\n\n\n\n<ul>\n<li>&nbsp;\/my-app<br>&nbsp; \/src<br>&nbsp; &nbsp; components<br>&nbsp; &nbsp; &nbsp; &#8211; CustomButton.tsx<br>&nbsp; &nbsp; &nbsp; &#8211; CustomCard.tsx<br>&nbsp; &nbsp; &nbsp; &#8211; CustomHeader.tsx<br>&nbsp; &nbsp; screens<br>&nbsp; &nbsp; &nbsp; &#8211; HomeScreen.tsx<br>&nbsp; &nbsp; &nbsp; &#8211; PhotoUploadScreen.tsx<br>&nbsp; &nbsp; &nbsp; &#8211; TechHighlightScreen.tsx<br>&nbsp; &nbsp; navigation<br>&nbsp; &nbsp; &nbsp; &#8211; Router.tsx<br>&nbsp; &nbsp; assets<br>&nbsp; &nbsp; &nbsp; \/images<br>&nbsp; &nbsp; &nbsp; \/fonts<br>&nbsp; &nbsp; utils<br>&nbsp; &nbsp; &nbsp; &#8211; api.ts<br>&nbsp; &nbsp; &nbsp; &#8211; helpers.ts<br>&nbsp;\u200b<br>&nbsp;&#8211; App.tsx<br>&nbsp;&#8211; package.json<\/li>\n\n\n\n<li>components\uff1a\u5b58\u653e\u6240\u6709\u7684\u5171\u4eab\u7ec4\u4ef6\uff0c\u5982\u6309\u94ae\u3001\u5361\u7247\u7b49\u3002<\/li>\n\n\n\n<li>screens\uff1a\u5b58\u653e\u6240\u6709\u7684\u5c4f\u5e55\u7ec4\u4ef6\uff0c\u6bcf\u4e2a\u5c4f\u5e55\u90fd\u662f\u4e00\u4e2a\u9875\u9762\u3002<\/li>\n\n\n\n<li>navigation\uff1a\u5b58\u653e\u5bfc\u822a\u76f8\u5173\u7684\u4ee3\u7801\uff0c\u5982\u5e95\u90e8\u6807\u7b7e\u5bfc\u822a\u5668\u3002<\/li>\n\n\n\n<li>assets\uff1a\u5b58\u653e\u9759\u6001\u8d44\u6e90\uff0c\u5982\u56fe\u7247\u548c\u5b57\u4f53\u3002<\/li>\n\n\n\n<li>utils\uff1a\u5b58\u653e\u5de5\u5177\u51fd\u6570\u548c\u670d\u52a1\uff0c\u5982API\u8c03\u7528\u548c\u5e2e\u52a9\u51fd\u6570\u3002<\/li>\n<\/ul>\n\n\n\n<h3 class=\"wp-block-heading\">2.\u6dfb\u52a0\u8def\u7531<\/h3>\n\n\n\n<p>Stack.Navigator\u662fReact Navigation\u5e93\u4e2d\u7684\u4e00\u4e2a\u7ec4\u4ef6\uff0c\u5b83\u7528\u4e8e\u521b\u5efa\u5806\u6808\u5bfc\u822a\u5668\u3002\u5806\u6808\u5bfc\u822a\u5668\u5141\u8bb8\u4f60\u5728\u4e0d\u540c\u7684\u5c4f\u5e55\u4e4b\u95f4\u8fdb\u884c\u5bfc\u822a\uff0c\u6bcf\u5f53\u4e00\u4e2a\u65b0\u7684\u5c4f\u5e55\u88ab\u6253\u5f00\u65f6\uff0c\u5b83\u5c31\u4f1a\u88ab\u653e\u5230\u5806\u6808\u7684\u9876\u90e8\u3002<\/p>\n\n\n\n<p>\u5b89\u88c5React Navigation\u5e93\uff1a&nbsp;cnpm install @react-navigation\/native<\/p>\n\n\n\n<p>\u5b89\u88c5\u6240\u9700\u7684\u4f9d\u8d56\uff1a&nbsp;cnpm install react-native-reanimated react-native-gesture-handler react-native-screens react-native-safe-area-context @react-native-community\/masked-view<\/p>\n\n\n\n<p>\u5b89\u88c5\u5806\u6808\u5bfc\u822a\u5668\uff1acnpm install @react-navigation\/stack<\/p>\n\n\n\n<p>\u5728app.ts<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>import React from 'react';\r\nimport { NavigationContainer } from '@react-navigation\/native';\r\nimport Router from '.\/src\/navigation\/Router';\r\n\r\nexport default function App() {\r\n  return (\r\n      &lt;NavigationContainer>\r\n        &lt;Router \/>\r\n      &lt;\/NavigationContainer>\r\n  );\r\n}<\/code><\/pre>\n\n\n\n<p>router<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>import React from 'react';\r\nimport { createStackNavigator } from '@react-navigation\/stack';\r\nimport HomeScreen from '..\/screens\/HomeScreen';\r\nimport DetailsScreen from '..\/screens\/DetailsScreen';\r\n\r\nconst Stack = createStackNavigator();\r\n\r\nexport default function Router() {\r\n    return (\r\n        &lt;Stack.Navigator initialRouteName=\"Home\">\r\n            &lt;Stack.Screen name=\"Home\" component={HomeScreen} \/>\r\n            &lt;Stack.Screen name=\"Details\" component={DetailsScreen} \/>\r\n        &lt;\/Stack.Navigator>\r\n    );\r\n}\r\n<\/code><\/pre>\n\n\n\n<p>HomeScreen<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>\/\/ src\/screens\/HomeScreen.tsx\r\nimport React from 'react';\r\nimport { Button, Text, View } from 'react-native';\r\n\r\nexport default function HomeScreen({ navigation }) {\r\n    return (\r\n        &lt;View>\r\n            &lt;Text>Home Screen&lt;\/Text>\r\n            &lt;Button\r\n                title=\"Go to Details\"\r\n                onPress={() => navigation.navigate('Details')}\r\n            \/>\r\n        &lt;\/View>\r\n    );\r\n}<\/code><\/pre>\n\n\n\n<h3 class=\"wp-block-heading\">3.\u8bbe\u7f6eTabBar<\/h3>\n\n\n\n<p>\u5b89\u88c5tab<\/p>\n\n\n\n<p><a href=\"https:\/\/reactnavigation.org\/docs\/bottom-tab-navigator\/\">https:\/\/reactnavigation.org\/docs\/bottom-tab-navigator\/<\/a><\/p>\n\n\n\n<p><code>yarn add @react-navigation\/bottom-tabs<\/code><\/p>\n\n\n\n<p>AnotherScreen\uff1a<\/p>\n\n\n\n<p>\/\/ src\/screens\/AnotherScreen.tsx<br>import React from &#8216;react&#8217;;<br>import { Text, View } from &#8216;react-native&#8217;;<\/p>\n\n\n\n<p>export default function AnotherScreen() {<br>return (<br>Another Screen<br>);<br>}<\/p>\n\n\n\n<p>router<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>import React from 'react';\r\nimport { createStackNavigator } from '@react-navigation\/stack';\r\nimport { createBottomTabNavigator } from '@react-navigation\/bottom-tabs';\r\nimport HomeScreen from '..\/screens\/HomeScreen';\r\nimport DetailsScreen from '..\/screens\/DetailsScreen';\r\nimport AnotherScreen from '..\/screens\/AnotherScreen';\r\n\r\nconst Stack = createStackNavigator();\r\nconst Tab = createBottomTabNavigator();\r\n\r\nfunction HomeStack() {\r\n    return (\r\n        &lt;Stack.Navigator initialRouteName=\"Home\">\r\n            &lt;Stack.Screen \r\n                name=\"Home\" \r\n                component={HomeScreen} \r\n                options={{ headerShown: false }} \/\/ \u9690\u85cf\u6807\u9898\r\n            \/>\r\n            &lt;Stack.Screen name=\"Details\" component={DetailsScreen} \/>\r\n        &lt;\/Stack.Navigator>\r\n    );\r\n}\r\n\r\nexport default function Router() {\r\n    return (\r\n        &lt;Tab.Navigator>\r\n            &lt;Tab.Screen name=\"Home\" component={HomeStack} \/>\r\n            &lt;Tab.Screen name=\"Another\" component={AnotherScreen} \/>\r\n        &lt;\/Tab.Navigator>\r\n    );\r\n}<\/code><\/pre>\n\n\n\n<p>\u66f4\u6362TabBar\u56fe\u6807<\/p>\n\n\n\n<p><code>npm install react-native-vector-icons yarn add react-native-vector-icons<\/code><\/p>\n\n\n\n<p>\u5b98\u65b9\u6587\u6863<\/p>\n\n\n\n<p><a href=\"https:\/\/oblador.github.io\/react-native-vector-icons\/\">https:\/\/oblador.github.io\/react-native-vector-icons\/<\/a><\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>\/\/ src\/navigation\/Router.tsx\r\nimport React from 'react';\r\nimport {createStackNavigator} from '@react-navigation\/stack';\r\nimport {createBottomTabNavigator} from '@react-navigation\/bottom-tabs';\r\nimport {Ionicons} from '@expo\/vector-icons'; \/\/ \u5bfc\u5165\u56fe\u6807\u5e93\r\nimport HomeScreen from '..\/screens\/HomeScreen';\r\nimport DetailsScreen from '..\/screens\/DetailsScreen';\r\nimport AnotherScreen from '..\/screens\/AnotherScreen';\r\nimport LifeScreen from '..\/screens\/LifeScreen';\r\nimport AlbumScreen from '..\/screens\/AlbumScreen';\r\nimport HighlightScreen from '..\/screens\/HighlightScreen';\r\nimport {View} from \"react-native\";\r\n\r\nconst Stack = createStackNavigator();\r\nconst Tab = createBottomTabNavigator();\r\n\r\nfunction HomeStack() {\r\n    return (\r\n        &lt;Stack.Navigator initialRouteName=\"Home\">\r\n            {\/*\u9690\u85cf\u6807\u9898*\/}\r\n            &lt;Stack.Screen name=\"Home\" component={HomeScreen} options={{headerShown: false}}\/>\r\n            &lt;Stack.Screen name=\"Details\" component={DetailsScreen}\/>\r\n        &lt;\/Stack.Navigator>\r\n    );\r\n}\r\n\r\nexport default function Router() {\r\n    return (\r\n        &lt;Tab.Navigator>\r\n            &lt;Tab.Screen\r\n                name=\"\u9996\u9875\"\r\n                component={HomeStack}\r\n                options={{\r\n                    tabBarIcon: ({color, size}) => (\r\n                        &lt;Ionicons name=\"home\" color={color} size={size}\/>\r\n                    ),\r\n                }}\r\n            \/>\r\n            &lt;Tab.Screen\r\n                name=\"\u52a8\u6001\"\r\n                component={LifeScreen}\r\n                options={{\r\n                    tabBarIcon: ({color, size}) => (\r\n                        &lt;Ionicons name=\"md-pulse\" color={color} size={size}\/>\r\n                    ),\r\n                }}\r\n            \/>\r\n            &lt;Tab.Screen\r\n                name=\"Another\"\r\n                component={AnotherScreen}\r\n                options={{\r\n                    tabBarIcon: () => (\r\n                        &lt;Ionicons name=\"add-circle\" color=\"#fb4a3e\" size={40}\/>\r\n                    ),\r\n                    tabBarLabel: () => null, \/\/ \u9690\u85cf\u6807\u7b7e\u540d\u5b57\r\n                }}\r\n            \/>\r\n            &lt;Tab.Screen\r\n                name=\"\u56fe\u5e93\"\r\n                component={AlbumScreen}\r\n                options={{\r\n                    tabBarIcon: ({color, size}) => (\r\n                        &lt;Ionicons name=\"images\" color={color} size={size}\/>\r\n                    ),\r\n                }}\r\n            \/>\r\n            &lt;Tab.Screen name=\"\u4e2a\u4eba\" component={HighlightScreen}\r\n                        options={{\r\n                            tabBarIcon: ({color, size}) => (\r\n                                &lt;Ionicons name=\"person\" color={color} size={size}\/>\r\n                            ),\r\n                        }}\r\n            \/>\r\n        &lt;\/Tab.Navigator>\r\n    );\r\n}\r\n<\/code><\/pre>\n\n\n\n<p>\u521d\u6b65\u8bbe\u8ba1<\/p>\n\n\n\n<figure class=\"wp-block-image size-full\"><div class='fancybox-wrapper' data-fancybox='post-images' href='https:\/\/www.muxuetianyin.cn\/wp-content\/uploads\/2023\/09\/image-7.png'><img loading=\"lazy\" width=\"387\" height=\"681\" src=\"https:\/\/www.muxuetianyin.cn\/wp-content\/uploads\/2023\/09\/image-7.png\" alt=\"\" class=\"wp-image-531\" srcset=\"https:\/\/www.muxuetianyin.cn\/wp-content\/uploads\/2023\/09\/image-7.png 387w, https:\/\/www.muxuetianyin.cn\/wp-content\/uploads\/2023\/09\/image-7-170x300.png 170w\" sizes=\"(max-width: 387px) 100vw, 387px\" \/><\/div><\/figure>\n\n\n\n<h2 class=\"wp-block-heading\">\u9875\u9762\u8bbe\u8ba1<\/h2>\n\n\n\n<h3 class=\"wp-block-heading\">1. <strong>\u8bbe\u8ba1\u4e0a\u4f20\u9875\u9762<\/strong><\/h3>\n\n\n\n<p>\u5e93expo install expo-image-picker<\/p>\n\n\n\n<p>\u9009\u62e9\u56fe\u7247<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>\/\/ src\/screens\/AnotherScreen.tsx\nimport React, { useEffect, useState } from 'react';\nimport { Button, Image, View } from 'react-native';\nimport * as ImagePicker from 'expo-image-picker';\nimport { useNavigation } from '@react-navigation\/native';\n\nexport default function AnotherScreen() {\n    const &#91;imageUri, setImageUri] = useState(null);\n    const navigation = useNavigation();\n\n    const takePhoto = async () =&gt; {\n        let result = await ImagePicker.launchCameraAsync({\n            mediaTypes: ImagePicker.MediaTypeOptions.All,\n            allowsEditing: true,\n            aspect: &#91;4, 3],\n            quality: 1,\n        });\n\n        if (!result.canceled) {\n            setImageUri(result.assets&#91;0].uri);\n            navigation.navigate('PostScreen', { imageUri: result.assets&#91;0].uri }); \/\/ replace 'NextScreen' with the name of your next screen\n        }\n    };\n    const pickImage = async () =&gt; {\n        let result = await ImagePicker.launchImageLibraryAsync({\n            mediaTypes: ImagePicker.MediaTypeOptions.All,\n            allowsEditing: true,\n            aspect: &#91;4, 3],\n            quality: 1,\n        });\n\n        if (!result.canceled) {\n            setImageUri(result.assets&#91;0].uri);\n            navigation.navigate('PostScreen', { imageUri: result.assets&#91;0].uri }); \/\/ replace 'NextScreen' with the name of your next screen\n        }\n    };\n\n    useEffect(() =&gt; {\n        pickImage();\n    }, &#91;]);\n\n    return (\n        &lt;View style={{ flex: 1, alignItems: 'center', justifyContent: 'center' }}&gt;\n            &lt;Button title=\"\u9009\u62e9\u56fe\u7247\" onPress={pickImage} \/&gt;\n            &lt;Button title=\"\u62cd\u7167\" onPress={takePhoto} \/&gt;\n            {imageUri &amp;&amp; &lt;Image source={{ uri: imageUri }} style={{ width: 200, height: 200 }} \/&gt;}\n        &lt;\/View&gt;\n    );\n}\n<\/code><\/pre>\n\n\n\n<h3 class=\"wp-block-heading\">2. \u53d1\u5e03\u52a8\u6001\u9875\u9762<\/h3>\n\n\n\n<p>\u914d\u7f6e\u8bf7\u6c42api<\/p>\n\n\n\n<p>\u4f7f\u7528axios<\/p>\n\n\n\n<p><code>cnpm install axios<\/code><\/p>\n\n\n\n<p>\u9996\u5148\u8bbe\u8ba1\u767b\u5f55\u9875\u9762\uff0c\u6ca1\u6709\u767b\u5f55\u7684\u65f6\u5019\u4e0d\u80fd\u53d1\u5e03\u52a8\u6001\uff0c\u5148\u8df3\u8f6c\u5230\u767b\u5f55\u9875\u9762<\/p>\n\n\n\n<p>\u6e05\u9664\u4f9d\u8d56\u91cd\u65b0\u4e0b\u8f7d<\/p>\n\n\n\n<p>expo start &#8211;clear<\/p>\n\n\n\n<p>\u4f7f\u7528asiox<\/p>\n\n\n\n<p>\u8bf7\u6c42\u62e6\u622a\u5668<\/p>\n\n\n\n<p><code>import axios from 'axios'; <\/code><\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>import axios from 'axios';\nimport AsyncStorage from '@react-native-async-storage\/async-storage';\n\n\/\/ \u521b\u5efa axios \u5b9e\u4f8b\nconst service = axios.create({\n    baseURL: 'http:\/\/localhost:8080',\n    timeout: 10000 \/\/ \u8bf7\u6c42\u8d85\u65f6\u65f6\u95f4\n});\n\n\/\/ \u8bf7\u6c42\u62e6\u622a\u5668\nservice.interceptors.request.use(\n    async config =&gt; {\n        \/\/ \u5728\u8fd9\u91cc\u53ef\u4ee5\u505a\u4e00\u4e9b\u8bf7\u6c42\u524d\u7684\u64cd\u4f5c\n        \/*\n        * \u8bbe\u7f6e\u5168\u5c40token\n        * *\/\n        \/\/ console.log(config,\"\u8bf7\u6c42\u62e6\u622a\u5668config\");\n        let token = await AsyncStorage.getItem('token');\n        console.log(token,\"token\")\n        if (token) {\n            config.headers&#91;'Authorization'] = `Bearer ${token}`;\n        }\n        console.log(service.defaults.baseURL + config.url, \"config\");\n        return config;\n    },\n    error =&gt; {\n        \/\/ \u5904\u7406\u8bf7\u6c42\u9519\u8bef\n        console.log(error,\"\u8bf7\u6c42\u62e6\u622a\u5668\"); \/\/ for debug\n\n        return Promise.reject(error);\n    }\n);\n\n\/\/ \u54cd\u5e94\u62e6\u622a\u5668\nservice.interceptors.response.use(\n    async response =&gt; {\n        \/\/ \u5728\u8fd9\u91cc\u53ef\u4ee5\u505a\u4e00\u4e9b\u54cd\u5e94\u6570\u636e\u7684\u64cd\u4f5c\n        console.log(response,\"\u54cd\u5e94\u62e6\u622a\u5668response\")\n        if (response.data &amp;&amp; response.data.code === 40101) {\n            \/\/ \u5982\u679c\u8fd4\u56de\u7684\u72b6\u6001\u7801\u4e3a40101\uff0c\u8bf4\u660etoken\u8fc7\u671f\uff0c\u6e05\u9664token\u5e76\u8df3\u8f6c\u5230\u767b\u5f55\u9875\u9762\n            await AsyncStorage.removeItem('token');\n\n        }\n        \/\/ \u5982\u679c\u8bf7\u6c42\u7684 URL \u5305\u542b '\/students\/export\/', \u5219\u8fd4\u56de\u6574\u4e2a\u54cd\u5e94\u5bf9\u8c61\n        if (response.config.url?.includes('\/students\/export\/')) {\n            return response;\n        }\n        return response.data;\n    },\n    error =&gt; {\n        \/\/ \u5904\u7406\u54cd\u5e94\u9519\u8bef\n        console.log(error,\"\u54cd\u5e94\u62e6\u622a\u5668\"); \/\/ for debug\n        \/\/ console.log(error.message); \/\/ \u6253\u5370\u9519\u8bef\u4fe1\u606f\n        \/\/ console.log(error.request); \/\/ \u6253\u5370\u8bf7\u6c42\u5bf9\u8c61\n        return Promise.reject(error);\n    }\n);\n\nexport default service;\n<\/code><\/pre>\n\n\n\n<p>\u767b\u5f55\u63a5\u53e3<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>import service from '.\/api';\nimport AsyncStorage from '@react-native-async-storage\/async-storage';\n\nexport const userLogin = async (userAccount: string, userPassword: string) =&gt; {\n    let data = {\n        userAccount: userAccount,\n        userPassword: userPassword\n    };\n\n    try {\n        let response = await service.post('\/api\/user\/login', data);\n\n        if (response.data.code === 200) {\n            \/\/ \u8bf7\u6c42\u6210\u529f\n            console.log('Login success!');\n            let token = response.data.data.token; \/\/ \u83b7\u53d6token\n            let refreshToken = response.data.data.refreshToken; \/\/ \u83b7\u53d6refreshToken\n            await AsyncStorage.setItem('token', token);\n            await AsyncStorage.setItem('refreshToken', refreshToken);\n        } else {\n            \/\/ \u8bf7\u6c42\u5931\u8d25\n            console.log('Login failed!');\n        }\n    } catch (error) {\n        console.error(error);\n    }\n};\n\nexport const sendSms = async (code: string, message: string, phoneNumber: string) =&gt; {\n    let data = {\n        code: code,\n        message: message,\n        phoneNumber: phoneNumber\n    };\n\n    try {\n        let response = await service.post('\/api\/user\/sendSms', data);\n\n        if (response.data.code === 200) {\n            \/\/ \u8bf7\u6c42\u6210\u529f\n            console.log('SMS sent successfully!');\n        } else {\n            \/\/ \u8bf7\u6c42\u5931\u8d25\n            console.log('SMS sending failed!');\n        }\n    } catch (error) {\n        console.error(error);\n    }\n};\n\nexport const phoneLogin = async (code: string, message: string, phoneNumber: string) =&gt; {\n    let data = {\n        code: code,\n        message: message,\n        phoneNumber: phoneNumber\n    };\n\n    try {\n        let response = await service.post('\/api\/user\/phoneLogin', data);\n\n        if (response.data.code === 200) {\n            \/\/ \u8bf7\u6c42\u6210\u529f\n            console.log('Phone login success!');\n            let token = response.data.data.token; \/\/ \u83b7\u53d6token\n            let refreshToken = response.data.data.refreshToken; \/\/ \u83b7\u53d6refreshToken\n            await AsyncStorage.setItem('token', token);\n            await AsyncStorage.setItem('refreshToken', refreshToken);\n        } else {\n            \/\/ \u8bf7\u6c42\u5931\u8d25\n            console.log('Phone login failed!');\n        }\n    } catch (error) {\n        console.error(error);\n    }\n};\n\nexport const uploadPhoto = async (imageUri: string) =&gt; {\n    let data = new FormData();\n    data.append('photo', {\n        uri: imageUri,\n        type: 'image\/jpeg',\n        name: 'photo.jpg'\n    });\n\n    try {\n        let response = await service.post('\/api\/photoAlbum\/upload', data, {\n            headers: {\n                'Content-Type': 'multipart\/form-data',\n            },\n        });\n\n        if (response.data.code === 200) {\n            \/\/ \u8bf7\u6c42\u6210\u529f\n            console.log('Upload success!');\n        } else {\n            \/\/ \u8bf7\u6c42\u5931\u8d25\n            console.log('Upload failed!');\n        }\n    } catch (error) {\n        console.error(error);\n    }\n};\n\nexport const savePhotoToAlbum = async (photoId: string, albumId: string) =&gt; {\n    let data = {\n        photoId: photoId,\n        albumId: albumId\n    };\n\n    try {\n        let response = await service.post('\/api\/photoAlbum\/save', data);\n\n        if (response.data.code === 200) {\n            \/\/ \u8bf7\u6c42\u6210\u529f\n            console.log('Save success!');\n        } else {\n            \/\/ \u8bf7\u6c42\u5931\u8d25\n            console.log('Save failed!');\n        }\n    } catch (error) {\n        console.error(error);\n    }\n};\n<\/code><\/pre>\n\n\n\n<p>\u767b\u5f55<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>import React, {useState} from 'react';\nimport {Button, Text, TextInput, View, StyleSheet,TouchableOpacity} from 'react-native';\nimport {phoneLogin, sendSms, userLogin} from '..\/utils\/login';\nimport { useNavigation } from '@react-navigation\/native';\nexport default function LoginScreen() {\n    const &#91;userAccount, setUserAccount] = useState('');\n    const &#91;userPassword, setUserPassword] = useState('');\n    const &#91;phoneNumber, setPhoneNumber] = useState('');\n    const &#91;code, setCode] = useState('');\n    const &#91;error, setError] = useState&lt;string | null&gt;(null);\n    const &#91;loginMethod, setLoginMethod] = useState('username');\n    const navigation = useNavigation();\n    const &#91;countdown, setCountdown] = useState(0);\n    React.useEffect(() =&gt; {\n        navigation.setOptions({\n            headerRight: () =&gt; (\n                &lt;TouchableOpacity onPress={() =&gt; setLoginMethod(loginMethod === 'username' ? 'phone' : 'username')}&gt;\n                    &lt;Text style={{ marginRight: 10, color: '#000', fontSize: 16 }}&gt;\n                        {loginMethod === 'username' ? '\u624b\u673a\u53f7\u767b\u5f55' : '\u7528\u6237\u540d\u767b\u5f55'}\n                    &lt;\/Text&gt;\n                &lt;\/TouchableOpacity&gt;\n            ),\n        });\n    }, &#91;navigation, loginMethod]);\n    const handleLogin = async () =&gt; {\n        if (!userAccount || !userPassword) {\n            setError('\u7528\u6237\u540d\u548c\u5bc6\u7801\u4e0d\u80fd\u4e3a\u7a7a');\n            return;\n        }\n\n        try {\n            await userLogin(userAccount, userPassword);\n        } catch (err) {\n            setError((err as Error).message);\n        }\n    };\n\n    const handleSendSms = async () =&gt; {\n        if (!phoneNumber) {\n            setError('\u624b\u673a\u53f7\u4e0d\u80fd\u4e3a\u7a7a');\n            return;\n        }\n\n        try {\n            await sendSms(code, '\u767b\u5f55\u77ed\u4fe1', phoneNumber);\n        } catch (err) {\n            setError((err as Error).message);\n        }\n        React.useEffect(() =&gt; {\n            if (countdown &gt; 0) {\n                const timerId = setTimeout(() =&gt; {\n                    setCountdown(countdown - 1);\n                }, 1000);\n                return () =&gt; clearTimeout(timerId); \/\/ \u6e05\u9664\u5b9a\u65f6\u5668\n            }\n        }, &#91;countdown]);\n    };\n\n    const handlePhoneLogin = async () =&gt; {\n        if (!phoneNumber || !code) {\n            setError('\u624b\u673a\u53f7\u548c\u9a8c\u8bc1\u7801\u4e0d\u80fd\u4e3a\u7a7a');\n            return;\n        }\n\n        try {\n            await phoneLogin(code, '', phoneNumber);\n        } catch (err) {\n            setError((err as Error).message);\n        }\n    };\n    return (\n        &lt;View style={styles.container}&gt;\n            {\/* &lt;Button title={loginMethod === 'username' ? '\u624b\u673a\u53f7\u767b\u5f55' : '\u7528\u6237\u540d\u767b\u5f55'} onPress={() =&gt; setLoginMethod(loginMethod === 'username' ? 'phone' : 'username')} \/&gt; *\/}\n            {loginMethod === 'username' ? (\n                &lt;View style={styles.inputContainer}&gt;\n                    &lt;TextInput\n                        style={styles.input}\n                        placeholder=\"\u7528\u6237\u540d\"\n                        value={userAccount}\n                        onChangeText={(text) =&gt; setUserAccount(text)}\n                    \/&gt;\n                    &lt;TextInput\n                        style={styles.input}\n                        placeholder=\"\u5bc6\u7801\"\n                        value={userPassword}\n                        onChangeText={(text) =&gt; setUserPassword(text)}\n                        secureTextEntry={true}\n                    \/&gt;\n                    &lt;View style={styles.button}&gt;\n                        &lt;Button title=\"\u767b\u5f55\" onPress={handleLogin} color=\"#FFFFFF\" \/&gt;\n                    &lt;\/View&gt;\n                &lt;\/View&gt;\n            ) : (\n                &lt;View style={styles.inputContainer}&gt;\n                &lt;View style={styles.inputWithButton}&gt;\n                    &lt;TextInput\n                        style={styles.input}\n                        placeholder=\"\u624b\u673a\u53f7\"\n                        value={phoneNumber}\n                        onChangeText={(text) =&gt; setPhoneNumber(text)}\n                        maxLength={11}\n                    \/&gt;\n                    &lt;TouchableOpacity \n                        style={styles.codeButton} \n                        onPress={handleSendSms}\n                        disabled={countdown &gt; 0} \/\/ \u5f53\u5012\u8ba1\u65f6\u65f6\uff0c\u7981\u7528\u6309\u94ae\n                    &gt;\n                        &lt;Text style={styles.codeButtonText}&gt;\n                            {countdown &gt; 0 ? `${countdown}\u79d2\u540e\u91cd\u53d1` : '\u53d1\u9001\u9a8c\u8bc1\u7801'}\n                        &lt;\/Text&gt;\n                    &lt;\/TouchableOpacity&gt;\n                &lt;\/View&gt;\n                &lt;TextInput\n                    style={styles.input}\n                    placeholder=\"\u9a8c\u8bc1\u7801\"\n                    value={code}\n                    onChangeText={(text) =&gt; setCode(text)}\n                \/&gt;\n                &lt;View style={styles.button}&gt;\n                    &lt;Button title=\"\u767b\u5f55\"  onPress={handlePhoneLogin} color=\"#FFFFFF\" \/&gt;\n                &lt;\/View&gt;\n            &lt;\/View&gt;\n            )}\n            {error &amp;&amp; &lt;Text style={{color: 'red'}}&gt;{error}&lt;\/Text&gt;}\n        &lt;\/View&gt;\n    );\n}\n\nconst styles = StyleSheet.create({\n    container: {\n        flex: 1,\n        justifyContent: 'center',\n        padding: 20,\n        backgroundColor: '#F4F5F7',\n    },\n    inputContainer: {\n        marginBottom: 20,\n    },\n    input: {\n        height: 50,\n        borderColor: '#FB7299',\n        borderWidth: 1,\n        marginBottom: 10,\n        paddingLeft: 10,\n        borderRadius: 10,\n        backgroundColor: '#FFFFFF',\n    },\n    button: {\n        marginTop: 10,\n        backgroundColor: '#FB7299',\n        justifyContent: 'center',\n        alignItems: 'center',\n        padding: 10,\n        borderRadius: 10,\n    },\n    row: {\n        flexDirection: 'row',\n    },\n    flex: {\n        flex: 1,\n    },\n    flex2: {\n        flex: 2,\n        \/\/ width:30\n    },\n    inputWithButton: {\n        position: 'relative',\n    },\n    codeButton: {\n        position: 'absolute',\n        right: 10,\n        \/\/ top: '50%',\n        height:40,\n        transform: &#91;{ translateY: 5 }],\n        backgroundColor: '#FB7299',\n        padding: 10,\n        borderRadius: 10,\n    },\n    codeContainer: {\n        flexDirection: 'row',\n        marginBottom: 10,\n    },\n    codeButtonText: {\n        color: '#FFFFFF',\n    },\n});\n<\/code><\/pre>\n\n\n\n<p>\u8def\u7531<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>\/\/ src\/navigation\/Router.tsx\nimport React, {useEffect} from 'react';\nimport {createStackNavigator} from '@react-navigation\/stack';\nimport {createBottomTabNavigator} from '@react-navigation\/bottom-tabs';\nimport {Ionicons} from '@expo\/vector-icons'; \/\/ \u5bfc\u5165\u56fe\u6807\u5e93\nimport HomeScreen from '..\/screens\/HomeScreen';\nimport DetailsScreen from '..\/screens\/DetailsScreen';\nimport PhotoPickerStack from '..\/screens\/PhotoPickerStack';\nimport LifeScreen from '..\/screens\/LifeScreen';\nimport AlbumScreen from '..\/screens\/AlbumScreen';\nimport HighlightScreen from '..\/screens\/HighlightScreen';\nimport PostScreen from \"..\/screens\/PostScreen\";\nimport {HeaderBackButton} from '@react-navigation\/elements';\nimport {StackActions, useNavigation} from '@react-navigation\/native';\nimport LoginScreen from '..\/screens\/LoginScreen';\nimport AsyncStorage from '@react-native-async-storage\/async-storage';\nimport {Text} from 'react-native';\n\ntype StackParamList = {\n    Home: undefined;\n    Details: undefined;\n    PhotoPickerStack: undefined;\n    PostScreen: { imageUri: string };\n    Login: undefined;\n    MainTabs: undefined;\n};\n\nconst Stack = createStackNavigator&lt;StackParamList&gt;();\nconst Tab = createBottomTabNavigator();\n\nfunction HomeStack() {\n    return (\n        &lt;Stack.Navigator initialRouteName=\"Home\"&gt;\n            &lt;Stack.Screen\n                name=\"Home\"\n                component={HomeScreen}\n                options={{ headerShown: false,title: '\u9996\u9875' }}\n\n            \/&gt;\n        &lt;\/Stack.Navigator&gt;\n    );\n}\n\nfunction PhotoPicker() {\n    const navigation = useNavigation();\n    useEffect(() =&gt; {\n        const unsubscribe = navigation.addListener('focus', async () =&gt; {\n            const token = await AsyncStorage.getItem('token');\n            if (!token) {\n                navigation.dispatch(StackActions.push('Login'));\n            }\n        });\n        return unsubscribe;\n    }, &#91;navigation]);\n    return (\n        &lt;Stack.Navigator initialRouteName=\"PhotoPickerStack\" &gt;\n            &lt;Stack.Screen\n                name=\"PhotoPickerStack\"\n                component={PhotoPickerStack}\n                options={{ headerShown: false }}\n            \/&gt;\n            &lt;Stack.Screen\n                name=\"PostScreen\"\n                component={PostScreen}\n                options={({ navigation }) =&gt; ({\n                    headerStyle: {\n                        backgroundColor: '#f4511e',\n                    },\n                    headerTintColor: '#fff',\n                    headerTitleStyle: {\n                        fontWeight: 'bold',\n                    },\n                    headerLeft: (props) =&gt; (\n                        &lt;HeaderBackButton\n                            {...props}\n                            onPress={() =&gt; {\n                                navigation.goBack();\n                            }}\n                        \/&gt;\n                    ),\n                })}\n            \/&gt;\n        &lt;\/Stack.Navigator&gt;\n    );\n}\nfunction OtherScreens() {\n    return (\n        &lt;Stack.Navigator initialRouteName=\"Details\"&gt;\n            &lt;Stack.Screen\n                name=\"Details\"\n                component={DetailsScreen}\n                options={{\n                    title: 'Details',\n                    headerShown: false\n                }}\n            \/&gt;\n        &lt;\/Stack.Navigator&gt;\n    );\n}\n\nfunction MainTabs() {\n    return (\n        &lt;Tab.Navigator&gt;\n            &lt;Tab.Screen\n                name=\"\u9996\u9875\"\n                component={HomeStack}\n                options={({route})=&gt;\n                    ({\n                        tabBarIcon: ({ color, size }) =&gt; (\n                            &lt;Ionicons name=\"home\" color={color} size={size} \/&gt;\n                        ),\n                    })\n                }\n            \/&gt;\n            &lt;Tab.Screen\n                name=\"\u52a8\u6001\"\n                component={LifeScreen}\n                options={{\n                    tabBarIcon: ({ color, size }) =&gt; (\n                        &lt;Ionicons name=\"md-pulse\" color={color} size={size} \/&gt;\n                    ),\n                }}\n            \/&gt;\n            &lt;Tab.Screen\n                name=\"\u53d1\u5e03\"\n                component={PhotoPicker}\n                options={({ route }) =&gt; ({\n                    tabBarIcon: () =&gt; (\n                        &lt;Ionicons name=\"add-circle\" color=\"#fb4a3e\" size={40} \/&gt;\n                    ),\n                    tabBarLabel: () =&gt; null,\n                    \/\/ title: '',\n                })}\n            \/&gt;\n            &lt;Tab.Screen\n                name=\"\u56fe\u5e93\"\n                component={AlbumScreen}\n                options={{\n                    tabBarIcon: ({ color, size }) =&gt; (\n                        &lt;Ionicons name=\"images\" color={color} size={size} \/&gt;\n                    ),\n                }}\n            \/&gt;\n            &lt;Tab.Screen name=\"\u4e2a\u4eba\" component={HighlightScreen}\n                        options={{\n                            tabBarIcon: ({ color, size }) =&gt; (\n                                &lt;Ionicons name=\"person\" color={color} size={size} \/&gt;\n                            ),\n                        }}\n            \/&gt;\n        &lt;\/Tab.Navigator&gt;\n    );\n}\nfunction Login() {\n    const navigation = useNavigation();\n    return (\n        &lt;Stack.Navigator initialRouteName=\"Login\" &gt;\n            &lt;Stack.Screen\n                name=\"Login\"\n                component={LoginScreen}\n                options={{\n                    title: '\u767b\u5f55',\n                    headerLeft: (props) =&gt; (\n                        &lt;HeaderBackButton\n                            {...props}\n                            backImage={() =&gt; &lt;Ionicons name=\"close\" size={24} color=\"black\" \/&gt;}\n                            onPress={async () =&gt; {\n                                \/\/ \u5728\u767b\u5f55\u9875\u9762\u7684\u8fd4\u56de\u6309\u94ae\u7684 onPress \u4e8b\u4ef6\u4e2d\uff0c\u83b7\u53d6\u4fdd\u5b58\u7684\u8def\u7531\u4fe1\u606f\uff0c\u5e76\u8df3\u8f6c\u5230\u8be5\u8def\u7531\n                                const lastRouteName = await AsyncStorage.getItem('lastRouteName');\n                                navigation.navigate(\"Home\");\n                            }}\n                        \/&gt;\n                    ),\n                    headerRight: () =&gt; (\n                        &lt;Text style={{ marginRight: 10, color: '#000', fontSize: 16 }}&gt;\u767b\u5f55&lt;\/Text&gt;\n                    ),\n                }}\n            \/&gt;\n        &lt;\/Stack.Navigator&gt;\n    );\n}\nexport default function Router() {\n    return (\n        &lt;Stack.Navigator&gt;\n            &lt;Stack.Screen\n                name=\"MainTabs\"\n                component={MainTabs}\n                options={{ headerShown: false }}\n            \/&gt;\n            &lt;Stack.Screen\n                name=\"Details\"\n                component={OtherScreens}\n            \/&gt;\n            &lt;Stack.Screen\n                name=\"Login\"\n                component={Login}\n                options={{ headerShown: false }}\n            \/&gt;\n        &lt;\/Stack.Navigator&gt;\n    );\n}\n<\/code><\/pre>\n\n\n\n<p>\u53d1\u5e03\u9875\u9762<\/p>\n\n\n\n<p>\u9700\u8981\u5b89\u88c5\u5e93\u6765\u663e\u793a\u8f93\u5165\u6846<\/p>\n\n\n\n<p>expo install react-native-keyboard-aware-scroll-view<\/p>\n\n\n\n<p>\u5b8c\u6574\u4ee3\u7801<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>\/\/ src\/screens\/PostScreen.tsx\nimport React, {useState} from 'react';\nimport {Text, TextInput, Image, TouchableOpacity, StyleSheet, Alert} from 'react-native';\nimport {useRoute, RouteProp, useNavigation} from '@react-navigation\/native';\nimport { uploadPhoto, savePhotoToAlbum } from '..\/utils\/login';\nimport { KeyboardAwareScrollView } from 'react-native-keyboard-aware-scroll-view';\ntype StackParamList = {\n    PostScreen: { imageUri: string };\n};\n\ntype PostScreenRouteProp = RouteProp&lt;StackParamList, 'PostScreen'&gt;;\nexport default function PostScreen() {\n    const route = useRoute&lt;PostScreenRouteProp&gt;();\n    const imageUri = route.params?.imageUri;\n    const navigation = useNavigation();\n    const &#91;name, setName] = useState('');\n    const &#91;title, setTitle] = useState('');\n    const &#91;category, setCategory] = useState('');\n    const handleSubmit = async () =&gt; {\n        try {\n            if (!imageUri){\n                return\n            }\n            \/\/ \u4e0a\u4f20\u56fe\u7247\n            const uploadResponse = await uploadPhoto(imageUri);\n            if (uploadResponse &amp;&amp; uploadResponse.code === 0) {\n                console.log('Upload success!');\n                \/\/ \u4fdd\u5b58\u5230\u76f8\u518c\n                const saveResponse = await savePhotoToAlbum(category, name, title, uploadResponse.data);\n                if (saveResponse &amp;&amp; saveResponse.code === 0) {\n                    console.log('Save success!');\n                    navigation.navigate('MainTabs', { screen: '\u56fe\u5e93' });\n                } else {\n                    Alert.alert('\u53d1\u5e03\u5931\u8d25', saveResponse ? saveResponse.description : '\u4fdd\u5b58\u5931\u8d25');\n                }\n            } else {\n                Alert.alert('\u53d1\u5e03\u5931\u8d25', uploadResponse ? uploadResponse.description : '\u4e0a\u4f20\u5931\u8d25');\n            }\n        } catch (error) {\n            console.error(error);\n        }\n    };\n    return (\n        &lt;KeyboardAwareScrollView\n            style={styles.container}\n            resetScrollToCoords={{ x: 0, y: 0 }}\n            contentContainerStyle={styles.container}\n            scrollEnabled={true}\n            keyboardShouldPersistTaps='always'\n            keyboardDismissMode='none'\n        &gt;\n            &lt;Image source={{uri: imageUri}} style={styles.image}\/&gt;\n            &lt;TextInput\n                style={styles.input}\n                placeholder=\"\u8bf7\u8f93\u5165\u6807\u9898\"\n                value={name}\n                onChangeText={setName}\n                returnKeyType=\"done\"\n            \/&gt;\n            &lt;TextInput\n                style={styles.input}\n                placeholder=\"\u8bf7\u8f93\u5165\u63cf\u8ff0\"\n                value={title}\n                onChangeText={setTitle}\n                returnKeyType=\"done\"\n            \/&gt;\n            &lt;TextInput\n                style={styles.input}\n                placeholder=\"\u8bf7\u9009\u62e9\u5206\u7c7b\"\n                value={category}\n                onChangeText={setCategory}\n                returnKeyType=\"done\"\n            \/&gt;\n            &lt;TouchableOpacity style={styles.button} onPress={handleSubmit}&gt;\n                &lt;Text style={styles.buttonText}&gt;\u53d1\u5e03&lt;\/Text&gt;\n            &lt;\/TouchableOpacity&gt;\n        &lt;\/KeyboardAwareScrollView&gt;\n    );\n}\n\nconst styles = StyleSheet.create({\n    container: {\n        flex: 1,\n        padding: 20,\n        backgroundColor: '#F5F5F5',\n    },\n    image: {\n        width: '100%',\n        height: 300,\n        borderRadius: 10,\n        marginBottom: 20,\n    },\n    input: {\n        height: 60,\n        borderColor: '#ddd',\n        borderWidth: 1,\n        marginTop: 20,\n        paddingHorizontal: 10,\n        fontSize: 20,\n        borderRadius: 10,\n        backgroundColor: '#FFFFFF',\n    },\n    button: {\n        backgroundColor: '#4CAF50',\n        padding: 15,\n        margin: 20,\n        borderRadius: 10,\n    },\n    buttonText: {\n        color: 'white',\n        textAlign: 'center',\n        fontSize: 20,\n    },\n});\n<\/code><\/pre>\n\n\n\n<p>\u4e0a\u4f20\u63a5\u53e3\u9700\u8981<\/p>\n\n\n\n<p>api<\/p>\n\n\n\n<p><code>base64expo install expo-file-system<\/code><\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>import * as FileSystem from 'expo-file-system';\n\n\nexport const uploadPhoto = async (imageUri: string) =&gt; {\n    try {\n        const fileContent = await FileSystem.readAsStringAsync(imageUri, { encoding: FileSystem.EncodingType.Base64 });\n        let data = new URLSearchParams({\n            file: fileContent,\n            mimeType: 'image\/jpeg', \/\/ \u6216\u8005 'image\/png'\uff0c\u53d6\u51b3\u4e8e\u4f60\u7684\u6587\u4ef6\u7c7b\u578b\n        });\n\n        let token = await AsyncStorage.getItem('token');\n        let headers = {\n            'Content-Type': 'application\/x-www-form-urlencoded',\n        };\n        if (token) {\n            headers&#91;'Authorization'] = `Bearer ${token}`;\n        }\n        let response = await fetch('https:\/\/muxuetianyin.cn\/api\/photoAlbum\/uploadBase64', {\n            method: 'POST',\n            headers: headers,\n            body: data.toString(),\n        });\n\n        if (response.ok) {\n            console.log('Upload success!');\n            let responseJson = await response.json();\n            console.log(responseJson)\n            return responseJson;\n        } else {\n            console.log('Upload failed!');\n        }\n    } catch (error) {\n        console.error('Error', error);\n    }\n};\n\n\nexport const savePhotoToAlbum = async (category: string, name: string, title: string, url: string) =&gt; {\n    let data = {\n        category: category,\n        name: name,\n        title: title,\n        url: url\n    };\n    try {\n        let response:result&lt;any&gt; = await service.post('\/api\/photoAlbum\/save', data);\n\n        if (response.code === 0) {\n            \/\/ \u8bf7\u6c42\u6210\u529f\n            console.log('Save success!');\n            return response\n        } else {\n            \/\/ \u8bf7\u6c42\u5931\u8d25\n            console.log('Save failed!');\n        }\n    } catch (error) {\n        console.error(error);\n    }\n};<\/code><\/pre>\n\n\n\n<h3 class=\"wp-block-heading\">\u540e\u7aef<\/h3>\n\n\n\n<pre class=\"wp-block-code\"><code>@ApiOperation(\"\u4e0a\u4f20\u6587\u4ef6\uff08Base64\uff09\")\n@PostMapping(\"\/uploadBase64\")\npublic Result handleFileUploadBase64(@RequestParam(\"file\") String base64File, @RequestParam(\"mimeType\") String mimeType) {\n    byte&#91;] decodedBytes;\n    try {\n        \/\/ \u89e3\u7801Base64\u6587\u4ef6\n        decodedBytes = Base64.getDecoder().decode(base64File);\n    } catch (IllegalArgumentException e) {\n        throw new BusinessException(ErrorCode.PARAMS_ERROR, \"Invalid Base64 input\");\n    }\n\n    \/\/ \u68c0\u67e5\u6587\u4ef6\u5927\u5c0f\uff0c\u8fd9\u91cc\u9650\u5236\u4e3a5MB\n    if (decodedBytes.length &gt; 5 * 1024 * 1024) {\n        throw new BusinessException(ErrorCode.PARAMS_ERROR, \"File size should not exceed 5MB\");\n    }\n\n    String savePath = System.getProperty(\"user.dir\") + \"\/uploads\/images\";\n    \/\/ \u786e\u4fdd\u8fd9\u4e2a\u76ee\u5f55\u662f\u771f\u5b9e\u5b58\u5728\u7684\n    File directory = new File(savePath);\n    if (!directory.exists()) {\n        directory.mkdirs(); \/\/ \u521b\u5efa\u76ee\u5f55\uff0c\u5305\u62ec\u4efb\u4f55\u5fc5\u8981\u4f46\u4e0d\u5b58\u5728\u7684\u7236\u76ee\u5f55\n    }\n    \/\/ \u6839\u636eMIME\u7c7b\u578b\u786e\u5b9a\u6587\u4ef6\u6269\u5c55\u540d\n    String extension = mimeType.split(\"\/\")&#91;1];\n    String filename = System.currentTimeMillis() + \".\" + extension;\n    File dest = new File(directory, filename);\n\n    try {\n        \/\/ \u4fdd\u5b58\u6587\u4ef6\n        Files.write(dest.toPath(), decodedBytes);\n\n        \/\/ \u8fd4\u56de\u6210\u529f\u7684url \u672c\u5730\u8bbe\u7f6e\u7684\u8def\u5f84\n        String fileUrl = serverAddress + \"uploads\/images\/\" + filename;\n        return Result.success(fileUrl);\n    } catch (IOException e) {\n        e.printStackTrace();\n    }\n\n    throw new BusinessException(ErrorCode.PARAMS_ERROR, \"Failed to upload file\");\n}<\/code><\/pre>\n\n\n\n<h3 class=\"wp-block-heading\">3.\u4e2a\u4eba\u9875\u9762\u8bbe\u8ba1<\/h3>\n\n\n\n<p>\u5b89\u88c5ui\u5e93cnpm install react-native-elements yarn add react-native-elements<\/p>\n\n\n\n<p>\u5b89\u88c5\u5e03\u7ec4\u4ef6yarn add react-native-super-grid<\/p>\n\n\n\n<p>\u589e\u52a0\u8def\u7531<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>function UserScreen() {\n    return (\n        &lt;Stack.Navigator initialRouteName=\"UserScreen\"&gt;\n            &lt;Stack.Screen\n                name=\"UserScreen\"\n                component={HighlightScreen}\n                options={{\n                    \/\/ title: '\u4e2a\u4eba',\n                    headerShown: false,\n                }}\n            \/&gt;\n            &lt;Stack.Screen\n                name=\"EditUser\"\n                component={EditUserScreen}\n            \/&gt;\n            &lt;Stack.Screen\n                name=\"ProjectScreen\"\n                component={ProjectScreen}\n            \/&gt;\n        &lt;\/Stack.Navigator&gt;\n    );\n}<\/code><\/pre>\n\n\n\n<p>\u4f18\u5316\u8def\u7531\u5bfc\u51fa\uff0c\u5728screen\u91cc\u9762\u65b0\u5efaindex.ts\u5bfc\u51fa\u6240\u6709\u9875\u9762<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>\/\/ src\/screens\/index.js\nexport { default as HomeScreen } from '.\/HomeScreen';\nexport { default as DetailsScreen } from '.\/DetailsScreen';\nexport { default as PhotoPickerStack } from '.\/PhotoPickerStack';\nexport { default as LifeScreen } from '.\/LifeScreen';\nexport { default as AlbumScreen } from '.\/AlbumScreen';\nexport { default as HighlightScreen } from '.\/HighlightScreen';\nexport { default as PostScreen } from '.\/PostScreen';\nexport { default as LoginScreen } from '.\/LoginScreen';\nexport { default as EditUserScreen } from '.\/EditUserScreen';\nexport { default as ProjectScreen } from '.\/ProjectScreen';\n<\/code><\/pre>\n\n\n\n<p>\u7136\u540e\u8def\u7531<\/p>\n\n\n\n<p>import {<br>HomeScreen,<br>DetailsScreen,<br>PhotoPickerStack,<br>LifeScreen,<br>AlbumScreen,<br>HighlightScreen,<br>PostScreen,<br>LoginScreen,<br>EditUserScreen,<br>ProjectScreen,<br>} from &#8216;..\/screens&#8217;;<\/p>\n\n\n\n<p>\u4e2a\u4eba\u9875\u9762\u8fd9\u91cc\u4f7f\u7528\u4e86react-native-elementsimport <\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>import React, { useState, useEffect } from 'react';\nimport { View, StyleSheet, Button } from 'react-native';\nimport { Avatar, ListItem, Card, Text } from 'react-native-elements';\nimport { useNavigation } from '@react-navigation\/native';\nimport { getCurrentUser, updateUser } from '..\/utils\/user';\n\nexport default function ProfileScreen() {\n    const &#91;user, setUser] = useState(null);\n    const navigation = useNavigation();\n\n    useEffect(() =&gt; {\n        fetchUser();\n    }, &#91;]);\n\n    const fetchUser = async () =&gt; {\n        const userData = await getCurrentUser();\n        setUser(userData);\n    };\n\n    const handleEdit = () =&gt; {\n        navigation.navigate('EditUser', {\n            user: user,\n            onGoBack: () =&gt; fetchUser(),\n        });\n    };\n    const handleViewProjects = () =&gt; {\n        navigation.navigate('ProjectScreen');\n    };\n    if (!user) {\n        return &lt;Card&gt;&lt;Card.Title&gt;Loading...&lt;\/Card.Title&gt;&lt;\/Card&gt;;\n    }\n\n    return (\n        &lt;View style={styles.container}&gt;\n            &lt;Card&gt;\n                &lt;Card.Title&gt;{user.username}&lt;\/Card.Title&gt;\n                &lt;Card.Divider\/&gt;\n                &lt;View style={styles.avatarContainer}&gt;\n                    &lt;Avatar\n                        rounded\n                        size=\"large\"\n                        source={{\n                            uri: user.avatarUrl,\n                        }}\n                    \/&gt;\n                &lt;\/View&gt;\n                &lt;Text style={styles.signature}&gt;{user.signature}&lt;\/Text&gt;\n                &lt;Card.Divider\/&gt;\n                &lt;ListItem bottomDivider&gt;\n                    &lt;ListItem.Content&gt;\n                        &lt;ListItem.Title&gt;Email&lt;\/ListItem.Title&gt;\n                        &lt;ListItem.Subtitle&gt;{user.email}&lt;\/ListItem.Subtitle&gt;\n                    &lt;\/ListItem.Content&gt;\n                &lt;\/ListItem&gt;\n                &lt;ListItem bottomDivider&gt;\n                    &lt;ListItem.Content&gt;\n                        &lt;ListItem.Title&gt;Phone&lt;\/ListItem.Title&gt;\n                        &lt;ListItem.Subtitle&gt;{user.phone}&lt;\/ListItem.Subtitle&gt;\n                    &lt;\/ListItem.Content&gt;\n                &lt;\/ListItem&gt;\n                &lt;ListItem bottomDivider&gt;\n                    &lt;ListItem.Content&gt;\n                        &lt;ListItem.Title&gt;Gender&lt;\/ListItem.Title&gt;\n                        &lt;ListItem.Subtitle&gt;{user.gender}&lt;\/ListItem.Subtitle&gt;\n                    &lt;\/ListItem.Content&gt;\n                &lt;\/ListItem&gt;\n                &lt;ListItem bottomDivider&gt;\n                    &lt;Button title=\"View Projects\" onPress={handleViewProjects} \/&gt;\n                &lt;\/ListItem&gt;\n                \n                &lt;Button title=\"Edit\" onPress={handleEdit} \/&gt;\n            &lt;\/Card&gt;\n        &lt;\/View&gt;\n    );\n}\n\nconst styles = StyleSheet.create({\n    container: {\n        flex: 1,\n        padding: 10,\n    },\n    avatarContainer: {\n        alignItems: 'center',\n    },\n    signature: {\n        marginTop: 10,\n        marginBottom: 10,\n        textAlign: 'center',\n    },\n});<\/code><\/pre>\n\n\n\n<p>\u7f16\u8f91\u4e2a\u4eba\u4fe1\u9875\u9762<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>\/\/ src\/screens\/EditUserScreen.js\nimport React, { useState } from 'react';\nimport { View, Text, TextInput, Button, StyleSheet } from 'react-native';\nimport { updateUser } from '..\/utils\/user'; \/\/ \u5bfc\u5165\u66f4\u65b0\u7528\u6237\u4fe1\u606f\u7684\u51fd\u6570\n\nexport default function EditUserScreen({ route, navigation }) {\n    const { user, onGoBack } = route.params;\n    const &#91;username, setUsername] = useState(user.username);\n    const &#91;email, setEmail] = useState(user.email);\n    const &#91;phone, setPhone] = useState(user.phone);\n    const &#91;signature, setSignature] = useState(user.signature);\n\n    const handleSave = async () =&gt; {\n        const updatedUser = {\n            ...user,\n            username,\n            email,\n            phone,\n            signature,\n        };\n        await updateUser(updatedUser);\n        navigation.dangerouslyGetParent().options.onGoBack();\n        navigation.goBack();\n    };\n\n    return (\n        &lt;View style={styles.container}&gt;\n            &lt;Text&gt;Username&lt;\/Text&gt;\n            &lt;TextInput\n                value={username}\n                onChangeText={setUsername}\n                style={styles.input}\n            \/&gt;\n            &lt;Text&gt;Email&lt;\/Text&gt;\n            &lt;TextInput\n                value={email}\n                onChangeText={setEmail}\n                style={styles.input}\n            \/&gt;\n            &lt;Text&gt;Phone&lt;\/Text&gt;\n            &lt;TextInput\n                value={phone}\n                onChangeText={setPhone}\n                style={styles.input}\n            \/&gt;\n            &lt;Text&gt;Signature&lt;\/Text&gt;\n            &lt;TextInput\n                value={signature}\n                onChangeText={setSignature}\n                style={styles.input}\n            \/&gt;\n            &lt;Button title=\"Save\" onPress={handleSave} \/&gt;\n        &lt;\/View&gt;\n    );\n}\n\nconst styles = StyleSheet.create({\n    container: {\n        flex: 1,\n        padding: 20,\n    },\n    input: {\n        height: 40,\n        borderColor: 'gray',\n        borderWidth: 1,\n        marginBottom: 10,\n    },\n});\n<\/code><\/pre>\n\n\n\n<p><code>userApi<\/code><\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>import service from '.\/api';\n\nexport const getCurrentUser = async () =&gt; {\n    try {\n        let response:result&lt;any&gt; = await service.get('\/api\/user\/current');\n\n        if (response.code === 0) {\n            \/\/ \u8bf7\u6c42\u6210\u529f\n            console.log('Get current user success!');\n            return response.data;\n        } else {\n            \/\/ \u8bf7\u6c42\u5931\u8d25\n            console.log('Get current user failed!');\n        }\n    } catch (error) {\n        console.error(error);\n    }\n};\n\nexport const updateUser = async (userUpdateRequest) =&gt; {\n    try {\n        let response:result&lt;any&gt; = await service.put('\/api\/user\/update', userUpdateRequest);\n\n        if (response.code === 0) {\n            \/\/ \u8bf7\u6c42\u6210\u529f\n            console.log('Update user success!');\n            return response.data;\n        } else {\n            \/\/ \u8bf7\u6c42\u5931\u8d25\n            console.log('Update user failed!');\n        }\n    } catch (error) {\n        console.error(error);\n    }\n};<\/code><\/pre>\n\n\n\n<p>\u9879\u76ee\u9875\u9762\u5e03\u5199\u6b7b<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>import React from 'react';\nimport { View, StyleSheet, Image, FlatList } from 'react-native';\nimport { Avatar, ListItem, Card, Text } from 'react-native-elements';\n\nconst projects = &#91;\n    {\n        title: '\u9879\u76ee\u4e00\uff1amuxue-user',\n        description: '\u8fd9\u662f\u4e00\u4e2a\u5168\u6808\u7684\u7528\u6237\u7ba1\u7406\u7cfb\u7edf\uff0c\u5305\u62ec\u7528\u6237\u7684\u589e\u5220\u6539\u67e5\u3001\u6743\u9650\u7ba1\u7406\u7b49\u529f\u80fd\u3002',\n        imageUrl: 'http:\/\/example.com\/project1.png',\n        tags: &#91;'React', 'Ant Design Pro', 'Spring Boot', 'MyBatis Plus', 'Docker'],\n    },\n    \/\/ \u5176\u4ed6\u9879\u76ee...\n];\n\nexport default function ProjectScreen() {\n    return (\n        &lt;View style={styles.container}&gt;\n            &lt;FlatList\n                data={projects}\n                keyExtractor={(item, index) =&gt; index.toString()}\n                renderItem={({ item }) =&gt; (\n                    &lt;Card&gt;\n                        &lt;Card.Title&gt;{item.title}&lt;\/Card.Title&gt;\n                        &lt;Card.Divider\/&gt;\n                        &lt;Image\n                            style={styles.image}\n                            source={{\n                                uri: item.imageUrl,\n                            }}\n                        \/&gt;\n                        &lt;Text&gt;{item.description}&lt;\/Text&gt;\n                        {item.tags.map((tag, index) =&gt; (\n                            &lt;Text key={index} style={styles.tag}&gt;\n                                {tag}\n                            &lt;\/Text&gt;\n                        ))}\n                    &lt;\/Card&gt;\n                )}\n            \/&gt;\n        &lt;\/View&gt;\n    );\n}\n\nconst styles = StyleSheet.create({\n    container: {\n        flex: 1,\n        padding: 10,\n    },\n    image: {\n        width: '100%',\n        height: 200,\n    },\n    tag: {\n        backgroundColor: '#eee',\n        padding: 5,\n        margin: 2,\n    },\n});<\/code><\/pre>\n\n\n\n<h3 class=\"wp-block-heading\">4.\u76f8\u518c\u9875\u9762<\/h3>\n\n\n\n<p>imagesApi<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>import * as FileSystem from \"expo-file-system\";\nimport AsyncStorage from \"@react-native-async-storage\/async-storage\";\nimport service from \".\/api\";\n\nexport const uploadPhoto = async (imageUri: string) =&gt; {\n    try {\n        const fileContent = await FileSystem.readAsStringAsync(imageUri, { encoding: FileSystem.EncodingType.Base64 });\n        let data = new URLSearchParams({\n            file: fileContent,\n            mimeType: 'image\/jpeg', \/\/ \u6216\u8005 'image\/png'\uff0c\u53d6\u51b3\u4e8e\u4f60\u7684\u6587\u4ef6\u7c7b\u578b\n        });\n\n        let token = await AsyncStorage.getItem('token');\n        let headers = {\n            'Content-Type': 'application\/x-www-form-urlencoded',\n        };\n        if (token) {\n            headers&#91;'Authorization'] = `Bearer ${token}`;\n        }\n        let response = await fetch('https:\/\/muxuetianyin.cn\/api\/photoAlbum\/uploadBase64', {\n            method: 'POST',\n            headers: headers,\n            body: data.toString(),\n        });\n\n        if (response.ok) {\n            console.log('Upload success!');\n            let responseJson = await response.json();\n            console.log(responseJson)\n            return responseJson;\n        } else {\n            console.log('Upload failed!');\n        }\n    } catch (error) {\n        console.error('Error', error);\n    }\n};\nexport const savePhotoToAlbum = async (category: string, name: string, title: string, url: string) =&gt; {\n    let data = {\n        category: category,\n        name: name,\n        title: title,\n        url: url\n    };\n    try {\n        let response:result&lt;any&gt; = await service.post('\/api\/photoAlbum\/save', data);\n\n        if (response.code === 0) {\n            \/\/ \u8bf7\u6c42\u6210\u529f\n            console.log('Save success!');\n            return response\n        } else {\n            \/\/ \u8bf7\u6c42\u5931\u8d25\n            console.log('Save failed!');\n        }\n    } catch (error) {\n        console.error(error);\n    }\n};\n\nexport const searchPhotoAlbum = async (category?: string, name?: string, page?: number, size?: number) =&gt; {\n    try {\n        let params = {};\n        if (category) params&#91;'category'] = category;\n        if (name) params&#91;'name'] = name;\n        if (page) params&#91;'page'] = page;\n        if (size) params&#91;'size'] = size;\n        console.log(params)\n        let response:RES&lt;any&gt; = await service.get('\/api\/photoAlbum\/search', { params });\n\n        if (response.code === 0) {\n            console.log('Fetch success!');\n            return response.data;\n        } else {\n            console.log('Fetch failed!');\n        }\n    } catch (error) {\n        console.error(error);\n    }\n};\nexport const getPhotoCategories = async () =&gt; {\n    try {\n        let response:result&lt;any&gt; = await service.get('\/api\/photoAlbum\/categories');\n\n        if (response.code === 0) {\n            \/\/ \u8bf7\u6c42\u6210\u529f\n            console.log('Fetch success!');\n            return response.data;\n        } else {\n            \/\/ \u8bf7\u6c42\u5931\u8d25\n            console.log('Fetch failed!');\n        }\n    } catch (error) {\n        console.error(error);\n    }\n};\n<\/code><\/pre>\n\n\n\n<p>\u8bbe\u8ba1\u53c2\u8003\u4e4b\u524d\u7684\u7f51\u7ad9\uff0creact-native\u4e0d\u597d\u5b9e\u73b0<\/p>\n\n\n\n<p>\u4ee3\u7801<\/p>\n\n\n\n<p>\u4e3b\u8981\u8bbe\u8ba1\u5206\u7c7b\u548c\u641c\u7d22<\/p>\n\n\n\n<p>\u5bf9\u6b64\u8fdb\u884c\u76d1\u542c<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>useEffect(() =&gt; {\n    fetchPhotos();\n}, &#91;selectedIndex]);\nuseEffect(() =&gt; {\n    if (search==''){\n        fetchPhotos();\n    }\n}, &#91;search]);<\/code><\/pre>\n\n\n\n<p>\u4f7f\u7528react-native-elements\u8fdb\u884c\u5e03\u5c40<\/p>\n\n\n\n<p>\u4ee3\u7801<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>import React, { useState, useEffect } from 'react';\nimport { View, FlatList, StyleSheet, ScrollView, ActivityIndicator } from 'react-native';\nimport { Card, Text, Avatar, Button, Overlay, SearchBar, Icon } from 'react-native-elements';\nimport { getPhotoCategories, searchPhotoAlbum } from '..\/utils\/images';\n\nexport default function AlbumScreen() {\n    const &#91;categories, setCategories] = useState(&#91;]);\n    const &#91;selectedIndex, setSelectedIndex] = useState(0);\n    const &#91;photos, setPhotos] = useState(&#91;]);\n    const &#91;page, setPage] = useState(1);\n    const &#91;total, setTotal] = useState(0);\n    const &#91;loading, setLoading] = useState(false);\n    const &#91;search, setSearch] = useState('');\n\n    useEffect(() =&gt; {\n        fetchCategories();\n        fetchPhotos();\n    }, &#91;]);\n\n    useEffect(() =&gt; {\n        fetchPhotos();\n    }, &#91;selectedIndex]);\n    useEffect(() =&gt; {\n        if (search==''){\n            fetchPhotos();\n        }\n    }, &#91;search]);\n    const fetchCategories = async () =&gt; {\n        setLoading(true);\n        const data = await getPhotoCategories();\n        setCategories(&#91;'All', ...data]);\n        setLoading(false);\n    };\n    const fetchPhotos = async () =&gt; {\n        setLoading(true);\n        let category = categories&#91;selectedIndex] === 'All' ? '' : categories&#91;selectedIndex];\n        const data = await searchPhotoAlbum(category, search, page, 10);\n        setPhotos(data.list);\n        setTotal(data.total);\n        if (data){\n            setTimeout(()=&gt;{\n                setLoading(false);\n            },200)\n        }\n    };\n\n    const handleCategoryChange = (selectedIndex) =&gt; {\n        setSelectedIndex(selectedIndex);\n        if (categories&#91;selectedIndex] === 'All') {\n            setSearch('');\n            setPage(1)\n            return\n        }\n        \/\/ fetchPhotos();\n    };\n\n    const handlePageChange = (newPage) =&gt; {\n        if (newPage &lt; 1 || newPage &gt; Math.ceil(total \/ 10)) {\n            return;\n        }\n        setPage(newPage);\n        fetchPhotos();\n    };\n\n    const handleSearchChange = (text) =&gt; {\n        setSearch(text);\n    };\n\n    const handleSearchSubmit = () =&gt; {\n        fetchPhotos();\n    };\n\n    const handleSearchClear = () =&gt; {\n        setSearch('');\n    };\n\n    return (\n        &lt;View style={styles.container}&gt;\n            &lt;SearchBar\n                placeholder=\"Search Photos...\"\n                onChangeText={handleSearchChange}\n                onSubmitEditing={handleSearchSubmit}\n                onClear={handleSearchClear}\n                value={search}\n                containerStyle={styles.searchBar}\n            \/&gt;\n            &lt;ScrollView horizontal showsHorizontalScrollIndicator={false}&gt;\n                {categories.map((category, index) =&gt; (\n                    &lt;Button\n                        key={index}\n                        title={category}\n                        onPress={() =&gt; handleCategoryChange(index)}\n                        containerStyle={styles.button}\n                        titleStyle={styles.buttonTitle}\n                    \/&gt;\n                ))}\n            &lt;\/ScrollView&gt;\n            &lt;FlatList\n                data={photos}\n                renderItem={({ item }) =&gt; (\n                    &lt;Card&gt;\n                        &lt;Card.Title&gt;{item.name}&lt;\/Card.Title&gt;\n                        &lt;Card.Divider\/&gt;\n                        &lt;Avatar source={{ uri: item.url }} size=\"large\" \/&gt;\n                        &lt;Text&gt;{item.title}&lt;\/Text&gt;\n                        &lt;Text&gt;{item.upload_time}&lt;\/Text&gt;\n                    &lt;\/Card&gt;\n                )}\n                keyExtractor={(item, index) =&gt; index.toString()}\n            \/&gt;\n            &lt;View style={styles.buttonContainer}&gt;\n                &lt;Button title=\"Previous\" onPress={() =&gt; handlePageChange(page - 1)} \/&gt;\n                &lt;Text&gt;Page {page} of {Math.ceil(total \/ 10)}&lt;\/Text&gt;\n                &lt;Button title=\"Next\" onPress={() =&gt; handlePageChange(page + 1)} \/&gt;\n            &lt;\/View&gt;\n            &lt;Overlay isVisible={loading}&gt;\n                &lt;ActivityIndicator size=\"large\" color=\"#0000ff\" \/&gt;\n            &lt;\/Overlay&gt;\n        &lt;\/View&gt;\n    );\n};\n\nconst styles = StyleSheet.create({\n    container: {\n        flex: 1,\n        padding: 10,\n    },\n    searchBar: {\n        marginBottom: 10,\n    },\n    button: {\n        marginRight: 10,\n        height: 40,\n    },\n    buttonTitle: {\n        fontSize: 14,\n    },\n    buttonContainer: {\n        flexDirection: 'row',\n        justifyContent: 'space-between',\n        alignItems: 'center',\n        marginTop: 10,\n    },\n});\n<\/code><\/pre>\n\n\n\n<p>\u8def\u7531\u66f4\u6539\uff0c\u8bbe\u8ba1\u5168\u90e8\u653e\u5230\u6808\u91cc\u9762\u53bb<\/p>\n\n\n\n<p>\u9ed8\u8ba4\u5bfc\u51fa<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>export default function Router() {\n    return (\n        &lt;Stack.Navigator&gt;\n            &lt;Stack.Screen\n                name=\"MainTabs\"\n                component={MainTabs}\n                options={{headerShown: false}}\n            \/&gt;\n            &lt;Stack.Screen\n                name=\"Details\"\n                component={OtherScreens}\n                options={{\n                    headerLeft: (props) =&gt; (\n                        &lt;HeaderBackButton\n                            {...props}\n                            labelVisible={false}\n                        \/&gt;\n                    ),\n                }}\n            \/&gt;\n            &lt;Stack.Screen\n                name=\"Login\"\n                component={Login}\n                options={{headerShown: false}}\n            \/&gt;\n        &lt;\/Stack.Navigator&gt;\n    );\n}<\/code><\/pre>\n\n\n\n<p>MainTabs\u662ftab\u5bfc\u822a\u8def\u7531,\u4ed6\u8fd9\u4e2a\u4f1a\u6709\u4e00\u4e2a\u6807\u9898\uff0c\u627e\u4e86\u597d\u4e45\u8fd8\u662f\u4e0d\u80fd\u56e0\u6b64\u53ea\u80fd\u9690\u85cfStack.Screen\u7684\uff0c\u7136\u540e\u5bfc\u822a\u91cc\u9762\u53ef\u4ee5\u914d\u7f6e\u65b9\u6cd5\u8bbe\u8ba1\u591a\u4e2aStack.Screen\u5c4f\u5e55\uff0c\u7136\u540e\u4e0b\u9762\u4e24\u4e2aStack.Screen\u5c31\u53ef\u4ee5\u6446\u8131\u5bfc\u822atab<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>function MainTabs() {\n    return (\n        &lt;Tab.Navigator&gt;\n            &lt;Tab.Screen\n                name=\"\u9996\u9875\"\n                component={HomeStack}\n                options={({route}) =&gt;\n                    ({\n                        tabBarIcon: ({color, size}) =&gt; (\n                            &lt;Ionicons name=\"home\" color={color} size={size}\/&gt;\n                        ),\n                    })\n                }\n            \/&gt;\n            &lt;Tab.Screen\n                name=\"\u52a8\u6001\"\n                component={LifeScreen}\n                options={{\n                    tabBarIcon: ({color, size}) =&gt; (\n                        &lt;Ionicons name=\"md-pulse\" color={color} size={size}\/&gt;\n                    ),\n                }}\n            \/&gt;\n            &lt;Tab.Screen\n                name=\"\u53d1\u5e03\"\n                component={PhotoPicker}\n                options={({route}) =&gt; ({\n                    tabBarIcon: () =&gt; (\n                        &lt;Ionicons name=\"add-circle\" color=\"#fb4a3e\" size={40}\/&gt;\n                    ),\n                    tabBarLabel: () =&gt; null,\n                    \/\/ title: '',\n                })}\n            \/&gt;\n            &lt;Tab.Screen\n                name=\"\u56fe\u5e93\"\n                component={AlbumScreen}\n                options={{\n                    tabBarIcon: ({color, size}) =&gt; (\n                        &lt;Ionicons name=\"images\" color={color} size={size}\/&gt;\n                    ),\n                }}\n            \/&gt;\n            &lt;Tab.Screen name=\"\u4e2a\u4eba\" component={UserScreen}\n                        options={{\n                            tabBarIcon: ({color, size}) =&gt; (\n                                &lt;Ionicons name=\"person\" color={color} size={size}\/&gt;\n                            ),\n                        }}\n            \/&gt;\n        &lt;\/Tab.Navigator&gt;\n    );\n}<\/code><\/pre>\n\n\n\n<h2 class=\"wp-block-heading\">\u6253\u5305\u6d4b\u8bd5<\/h2>\n\n\n\n<p>\u5b89\u53531. \u5b89\u88c5Android SDK\uff1a\u5982\u679c\u4f60\u8fd8\u6ca1\u6709\u5b89\u88c5Android SDK\uff0c\u4f60\u53ef\u4ee5\u901a\u8fc7\u5b89\u88c5Android Studio\u6765\u8fdb\u884c\u5b89\u88c5\u3002\u5728\u5b89\u88c5\u8fc7\u7a0b\u4e2d\uff0cAndroid SDK\u4f1a\u81ea\u52a8\u5b89\u88c5\u3002 2. \u8bbe\u7f6eANDROID_HOME\u73af\u5883\u53d8\u91cf\uff1a\u5b89\u88c5\u5b8cAndroid SDK\u540e\uff0c\u4f60\u9700\u8981\u8bbe\u7f6eANDROID_HOME\u73af\u5883\u53d8\u91cf\uff0c\u4f7f\u5176\u6307\u5411\u4f60\u7684Android SDK\u7684\u4f4d\u7f6e\u3002\u5728Windows\u4e0a\u7684\u9ed8\u8ba4\u4f4d\u7f6e\u662fC:\\Users\\&lt;\u4f60\u7684\u7528\u6237\u540d&gt;\\AppData\\Local\\Android\\Sdk\u3002 \u4ee5\u4e0b\u662f\u5728Windows\u4e0a\u8bbe\u7f6eANDROID_HOME\u73af\u5883\u53d8\u91cf\u7684\u65b9\u6cd5\uff1a &#8211; \u5728Windows\u641c\u7d22\u680f\u4e2d\u641c\u7d22&#8217;\u73af\u5883\u53d8\u91cf&#8217;\uff0c\u7136\u540e\u9009\u62e9&#8217;\u7f16\u8f91\u7cfb\u7edf\u73af\u5883\u53d8\u91cf&#8217;\u3002 &#8211; \u5728\u51fa\u73b0\u7684\u7cfb\u7edf\u5c5e\u6027\u7a97\u53e3\u4e2d\uff0c\u70b9\u51fb&#8217;\u73af\u5883\u53d8\u91cf&#8217;\u3002 &#8211; \u5728\u73af\u5883\u53d8\u91cf\u7a97\u53e3\u4e2d\uff0c\u70b9\u51fb&#8217;\u65b0\u5efa&#8217;\uff0c\u5728&#8217;\u7528\u6237\u53d8\u91cf&#8217;\u90e8\u5206\u3002 &#8211; \u5728\u65b0\u7528\u6237\u53d8\u91cf\u7a97\u53e3\u4e2d\uff0c\u8f93\u5165ANDROID_HOME\u4f5c\u4e3a\u53d8\u91cf\u540d\uff0c\u8f93\u5165\u4f60\u7684Android SDK\u7684\u8def\u5f84\u4f5c\u4e3a\u53d8\u91cf\u503c\uff08\u4f8b\u5982\uff0cC:\\Users\\&lt;\u4f60\u7684\u7528\u6237\u540d&gt;\\AppData\\Local\\Android\\Sdk\uff09\u3002 &#8211; \u5728\u6240\u6709\u7a97\u53e3\u4e2d\u70b9\u51fb&#8217;\u786e\u5b9a&#8217;\u4ee5\u5e94\u7528\u66f4\u6539\u3002 3. \u5c06Android SDK\u5de5\u5177\u6dfb\u52a0\u5230Path\uff1a\u4f60\u8fd8\u9700\u8981\u5c06Android SDK\u5de5\u5177\u6dfb\u52a0\u5230\u4f60\u7684Path\u53d8\u91cf\uff1a &#8211; \u5728\u540c\u4e00\u4e2a\u73af\u5883\u53d8\u91cf\u7a97\u53e3\u4e2d\uff0c\u6eda\u52a8\u5230&#8217;\u7cfb\u7edf\u53d8\u91cf&#8217;\u90e8\u5206\uff0c\u9009\u62e9&#8217;Path&#8217;\u53d8\u91cf\uff0c\u7136\u540e\u70b9\u51fb&#8217;\u7f16\u8f91&#8217;\u3002 &#8211; \u5728\u7f16\u8f91\u73af\u5883\u53d8\u91cf\u7a97\u53e3\u4e2d\uff0c\u70b9\u51fb&#8217;\u65b0\u5efa&#8217;\uff0c\u5e76\u6dfb\u52a0\u4ee5\u4e0b\u8def\u5f84\uff08\u5c06&lt;\u4f60\u7684SDK\u8def\u5f84&gt;\u66ff\u6362\u4e3a\u4f60\u7684Android SDK\u7684\u5b9e\u9645\u8def\u5f84\uff09\uff1a &lt;\u4f60\u7684SDK\u8def\u5f84&gt;\\platform-tools &lt;\u4f60\u7684SDK\u8def\u5f84&gt;\\tools &lt;\u4f60\u7684SDK\u8def\u5f84&gt;\\tools\\bin<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Android Studio \u5b89\u88c5<\/h2>\n\n\n\n<p>\u53cc\u51fb\u8fd0\u884c\u5b89\u88c5\u3002<\/p>\n\n\n\n<figure class=\"wp-block-image size-full\"><div class='fancybox-wrapper' data-fancybox='post-images' href='https:\/\/www.muxuetianyin.cn\/wp-content\/uploads\/2023\/09\/image-8.png'><img loading=\"lazy\" width=\"493\" height=\"382\" src=\"https:\/\/www.muxuetianyin.cn\/wp-content\/uploads\/2023\/09\/image-8.png\" alt=\"\" class=\"wp-image-532\" srcset=\"https:\/\/www.muxuetianyin.cn\/wp-content\/uploads\/2023\/09\/image-8.png 493w, https:\/\/www.muxuetianyin.cn\/wp-content\/uploads\/2023\/09\/image-8-300x232.png 300w\" sizes=\"(max-width: 493px) 100vw, 493px\" \/><\/div><\/figure>\n\n\n\n<figure class=\"wp-block-image size-full\"><div class='fancybox-wrapper' data-fancybox='post-images' href='https:\/\/www.muxuetianyin.cn\/wp-content\/uploads\/2023\/09\/image-9.png'><img loading=\"lazy\" width=\"494\" height=\"397\" src=\"https:\/\/www.muxuetianyin.cn\/wp-content\/uploads\/2023\/09\/image-9.png\" alt=\"\" class=\"wp-image-533\" srcset=\"https:\/\/www.muxuetianyin.cn\/wp-content\/uploads\/2023\/09\/image-9.png 494w, https:\/\/www.muxuetianyin.cn\/wp-content\/uploads\/2023\/09\/image-9-300x241.png 300w\" sizes=\"(max-width: 494px) 100vw, 494px\" \/><\/div><\/figure>\n\n\n\n<figure class=\"wp-block-image size-full\"><div class='fancybox-wrapper' data-fancybox='post-images' href='https:\/\/www.muxuetianyin.cn\/wp-content\/uploads\/2023\/09\/image-10.png'><img loading=\"lazy\" width=\"510\" height=\"399\" src=\"https:\/\/www.muxuetianyin.cn\/wp-content\/uploads\/2023\/09\/image-10.png\" alt=\"\" class=\"wp-image-534\" srcset=\"https:\/\/www.muxuetianyin.cn\/wp-content\/uploads\/2023\/09\/image-10.png 510w, https:\/\/www.muxuetianyin.cn\/wp-content\/uploads\/2023\/09\/image-10-300x235.png 300w\" sizes=\"(max-width: 510px) 100vw, 510px\" \/><\/div><\/figure>\n\n\n\n<figure class=\"wp-block-image size-full\"><div class='fancybox-wrapper' data-fancybox='post-images' href='https:\/\/www.muxuetianyin.cn\/wp-content\/uploads\/2023\/09\/image-11.png'><img loading=\"lazy\" width=\"489\" height=\"398\" src=\"https:\/\/www.muxuetianyin.cn\/wp-content\/uploads\/2023\/09\/image-11.png\" alt=\"\" class=\"wp-image-535\" srcset=\"https:\/\/www.muxuetianyin.cn\/wp-content\/uploads\/2023\/09\/image-11.png 489w, https:\/\/www.muxuetianyin.cn\/wp-content\/uploads\/2023\/09\/image-11-300x244.png 300w\" sizes=\"(max-width: 489px) 100vw, 489px\" \/><\/div><\/figure>\n\n\n\n<figure class=\"wp-block-image size-full\"><div class='fancybox-wrapper' data-fancybox='post-images' href='https:\/\/www.muxuetianyin.cn\/wp-content\/uploads\/2023\/09\/image-12.png'><img loading=\"lazy\" width=\"477\" height=\"238\" src=\"https:\/\/www.muxuetianyin.cn\/wp-content\/uploads\/2023\/09\/image-12.png\" alt=\"\" class=\"wp-image-536\" srcset=\"https:\/\/www.muxuetianyin.cn\/wp-content\/uploads\/2023\/09\/image-12.png 477w, https:\/\/www.muxuetianyin.cn\/wp-content\/uploads\/2023\/09\/image-12-300x150.png 300w\" sizes=\"(max-width: 477px) 100vw, 477px\" \/><\/div><\/figure>\n\n\n\n<figure class=\"wp-block-image size-full\"><div class='fancybox-wrapper' data-fancybox='post-images' href='https:\/\/www.muxuetianyin.cn\/wp-content\/uploads\/2023\/09\/image-13.png'><img loading=\"lazy\" width=\"640\" height=\"400\" src=\"https:\/\/www.muxuetianyin.cn\/wp-content\/uploads\/2023\/09\/image-13.png\" alt=\"\" class=\"wp-image-537\" srcset=\"https:\/\/www.muxuetianyin.cn\/wp-content\/uploads\/2023\/09\/image-13.png 640w, https:\/\/www.muxuetianyin.cn\/wp-content\/uploads\/2023\/09\/image-13-300x188.png 300w\" sizes=\"(max-width: 640px) 100vw, 640px\" \/><\/div><\/figure>\n\n\n\n<p>\u5148\u4e0d\u7ba1<\/p>\n\n\n\n<figure class=\"wp-block-image size-full\"><div class='fancybox-wrapper' data-fancybox='post-images' href='https:\/\/www.muxuetianyin.cn\/wp-content\/uploads\/2023\/09\/image-14.png'><img loading=\"lazy\" width=\"778\" height=\"585\" src=\"https:\/\/www.muxuetianyin.cn\/wp-content\/uploads\/2023\/09\/image-14.png\" alt=\"\" class=\"wp-image-538\" srcset=\"https:\/\/www.muxuetianyin.cn\/wp-content\/uploads\/2023\/09\/image-14.png 778w, https:\/\/www.muxuetianyin.cn\/wp-content\/uploads\/2023\/09\/image-14-300x226.png 300w, https:\/\/www.muxuetianyin.cn\/wp-content\/uploads\/2023\/09\/image-14-768x577.png 768w\" sizes=\"(max-width: 778px) 100vw, 778px\" \/><\/div><\/figure>\n\n\n\n<p>\u56e0\u4e3a\u662f\u8c37\u6b4c\u8bbe\u7f6e\u4ee3\u7406<\/p>\n\n\n\n<figure class=\"wp-block-image size-full\"><div class='fancybox-wrapper' data-fancybox='post-images' href='https:\/\/www.muxuetianyin.cn\/wp-content\/uploads\/2023\/09\/image-15.png'><img loading=\"lazy\" width=\"441\" height=\"558\" src=\"https:\/\/www.muxuetianyin.cn\/wp-content\/uploads\/2023\/09\/image-15.png\" alt=\"\" class=\"wp-image-539\" srcset=\"https:\/\/www.muxuetianyin.cn\/wp-content\/uploads\/2023\/09\/image-15.png 441w, https:\/\/www.muxuetianyin.cn\/wp-content\/uploads\/2023\/09\/image-15-237x300.png 237w\" sizes=\"(max-width: 441px) 100vw, 441px\" \/><\/div><\/figure>\n\n\n\n<p>\u4ee3\u7406\u817e\u8baf\uff1a https:\/\/mirrors.cloud.tencent.com\/AndroidSDK\/ <\/p>\n\n\n\n<p>\u963f\u91cc\uff1a https:\/\/mirrors.aliyun.com\/android.googlesource.com\/<\/p>\n\n\n\n<p>Android SDK\u5728\u7ebf\u66f4\u65b0\u955c\u50cf\u670d\u52a1\u5668<\/p>\n\n\n\n<p>1.\u4e2d\u56fd\u79d1\u5b66\u9662\u5f00\u6e90\u534f\u4f1a\u955c\u50cf\u7ad9\u5730\u5740:<\/p>\n\n\n\n<p>\u25e6IPV4\/IPV6: mirrors.opencas.cn \u7aef\u53e3\uff1a80<\/p>\n\n\n\n<p>\u25e6IPV4\/IPV6: mirrors.opencas.org \u7aef\u53e3\uff1a80<\/p>\n\n\n\n<p>\u25e6IPV4\/IPV6: mirrors.opencas.ac.cn \u7aef\u53e3\uff1a80<\/p>\n\n\n\n<p>2.\u4e0a\u6d77GDG\u955c\u50cf\u670d\u52a1\u5668\u5730\u5740:<\/p>\n\n\n\n<p>sdk.gdgshanghai.com \u7aef\u53e3\uff1a8000<\/p>\n\n\n\n<p>3.\u5317\u4eac\u5316\u5de5\u5927\u5b66\u955c\u50cf\u670d\u52a1\u5668\u5730\u5740:<\/p>\n\n\n\n<p>\u25e6IPv4: ubuntu.buct.edu.cn\/ \u7aef\u53e3\uff1a80<\/p>\n\n\n\n<p>\u25e6IPv4: ubuntu.buct.cn\/ \u7aef\u53e3\uff1a80<\/p>\n\n\n\n<p>\u25e6IPv6: ubuntu.buct6.edu.cn\/ \u7aef\u53e3\uff1a80<\/p>\n\n\n\n<p>4.\u5927\u8fde\u4e1c\u8f6f\u4fe1\u606f\u5b66\u9662\u955c\u50cf\u670d\u52a1\u5668\u5730\u5740:<\/p>\n\n\n\n<p>mirrors.neusoft.edu.cn \u7aef\u53e3\uff1a80<\/p>\n\n\n\n<p>5.\u817e\u8bafBugly \u955c\u50cf:<\/p>\n\n\n\n<p>android-mirror.bugly.qq.com \u7aef\u53e3\uff1a8080<\/p>\n\n\n\n<p>\u914d\u7f6e\u5b8c\u6210\u4e4b\u540e\u70b9\u51fb\u5b8c\u6210<\/p>\n\n\n\n<figure class=\"wp-block-image size-full\"><div class='fancybox-wrapper' data-fancybox='post-images' href='https:\/\/www.muxuetianyin.cn\/wp-content\/uploads\/2023\/09\/image-16.png'><img loading=\"lazy\" width=\"784\" height=\"561\" src=\"https:\/\/www.muxuetianyin.cn\/wp-content\/uploads\/2023\/09\/image-16.png\" alt=\"\" class=\"wp-image-540\" srcset=\"https:\/\/www.muxuetianyin.cn\/wp-content\/uploads\/2023\/09\/image-16.png 784w, https:\/\/www.muxuetianyin.cn\/wp-content\/uploads\/2023\/09\/image-16-300x215.png 300w, https:\/\/www.muxuetianyin.cn\/wp-content\/uploads\/2023\/09\/image-16-768x550.png 768w\" sizes=\"(max-width: 784px) 100vw, 784px\" \/><\/div><\/figure>\n\n\n\n<figure class=\"wp-block-image size-full\"><div class='fancybox-wrapper' data-fancybox='post-images' href='https:\/\/www.muxuetianyin.cn\/wp-content\/uploads\/2023\/09\/image-17.png'><img loading=\"lazy\" width=\"479\" height=\"598\" src=\"https:\/\/www.muxuetianyin.cn\/wp-content\/uploads\/2023\/09\/image-17.png\" alt=\"\" class=\"wp-image-541\" srcset=\"https:\/\/www.muxuetianyin.cn\/wp-content\/uploads\/2023\/09\/image-17.png 479w, https:\/\/www.muxuetianyin.cn\/wp-content\/uploads\/2023\/09\/image-17-240x300.png 240w\" sizes=\"(max-width: 479px) 100vw, 479px\" \/><\/div><\/figure>\n\n\n\n<figure class=\"wp-block-image size-large\"><div class='fancybox-wrapper' data-fancybox='post-images' href='https:\/\/www.muxuetianyin.cn\/wp-content\/uploads\/2023\/09\/image-18-1024x524.png'><img loading=\"lazy\" width=\"1024\" height=\"524\" src=\"https:\/\/www.muxuetianyin.cn\/wp-content\/uploads\/2023\/09\/image-18-1024x524.png\" alt=\"\" class=\"wp-image-542\" srcset=\"https:\/\/www.muxuetianyin.cn\/wp-content\/uploads\/2023\/09\/image-18-1024x524.png 1024w, https:\/\/www.muxuetianyin.cn\/wp-content\/uploads\/2023\/09\/image-18-300x153.png 300w, https:\/\/www.muxuetianyin.cn\/wp-content\/uploads\/2023\/09\/image-18-768x393.png 768w, https:\/\/www.muxuetianyin.cn\/wp-content\/uploads\/2023\/09\/image-18.png 1193w\" sizes=\"(max-width: 1024px) 100vw, 1024px\" \/><\/div><\/figure>\n\n\n\n<p>\u70b9\u51fb\u786e\u5b9a\uff0c\u7136\u540e\u70b9\u51fb\u7cfb\u7edf\u53d8\u91cf\u4e0b\u7684path\u53d8\u91cf\uff0c\u6dfb\u52a0\u8fd9\u4e24\u6761\u5185\u5bb9<code>E:\\Android\\SDK\\emulator<\/code>\uff0c<code>E:\\Android\\SDK\\tools<\/code>\uff0c<code>E:\\Android\\SDK\\tools\\bin<\/code>\uff0c<code>E:\\Android\\SDK\\platform-tools<\/code><\/p>\n\n\n\n<p>E:\\Android\\SDK\\platform-tools <\/p>\n\n\n\n<p>E:\\Android\\SDK\\tools <\/p>\n\n\n\n<p>E:\\Android\\SDK\\emulator <\/p>\n\n\n\n<p>E:\\Android\\SDK\\tools\\bin<\/p>\n\n\n\n<figure class=\"wp-block-image size-large\"><div class='fancybox-wrapper' data-fancybox='post-images' href='https:\/\/www.muxuetianyin.cn\/wp-content\/uploads\/2023\/09\/image-19-1024x539.png'><img loading=\"lazy\" width=\"1024\" height=\"539\" src=\"https:\/\/www.muxuetianyin.cn\/wp-content\/uploads\/2023\/09\/image-19-1024x539.png\" alt=\"\" class=\"wp-image-543\" srcset=\"https:\/\/www.muxuetianyin.cn\/wp-content\/uploads\/2023\/09\/image-19-1024x539.png 1024w, https:\/\/www.muxuetianyin.cn\/wp-content\/uploads\/2023\/09\/image-19-300x158.png 300w, https:\/\/www.muxuetianyin.cn\/wp-content\/uploads\/2023\/09\/image-19-768x404.png 768w, https:\/\/www.muxuetianyin.cn\/wp-content\/uploads\/2023\/09\/image-19.png 1037w\" sizes=\"(max-width: 1024px) 100vw, 1024px\" \/><\/div><\/figure>\n\n\n\n<figure class=\"wp-block-image size-full\"><div class='fancybox-wrapper' data-fancybox='post-images' href='https:\/\/www.muxuetianyin.cn\/wp-content\/uploads\/2023\/09\/image-20.png'><img loading=\"lazy\" width=\"630\" height=\"642\" src=\"https:\/\/www.muxuetianyin.cn\/wp-content\/uploads\/2023\/09\/image-20.png\" alt=\"\" class=\"wp-image-544\" srcset=\"https:\/\/www.muxuetianyin.cn\/wp-content\/uploads\/2023\/09\/image-20.png 630w, https:\/\/www.muxuetianyin.cn\/wp-content\/uploads\/2023\/09\/image-20-294x300.png 294w\" sizes=\"(max-width: 630px) 100vw, 630px\" \/><\/div><\/figure>\n\n\n\n<p>\u91cd\u65b0\u8fd0\u884c\u00b7expo start &#8211;android<\/p>\n\n\n\n<p>\u8fde\u63a5\u624b\u673a\u8c03\u8bd5<\/p>\n\n\n\n<p>\u6253\u5305npm install -g eas-cli<\/p>\n\n\n\n<p>\u914d\u7f6e\u8d26\u53f7\u5bc6\u7801eas build:configure<\/p>\n\n\n\n<p>\u6253\u5305\u5b89\u5353eas build -p android<\/p>\n\n\n\n<p>\u5982\u679c\u7f51\u7edc\u9519\u8bef\u53ef\u80fd\u9700\u8981vpn\uff0c\u540e\u9762\u8fd8\u662f\u6253\u5305\u4e0d\u6210\u529f\uff0c\u6ca1\u529e\u6cd5\u8fd9\u4e2a\u5751\u6ca1\u6cd5\u89e3\u51b3\uff0c\u53ea\u80fd\u9879\u76ee\u8fc1\u79fb\u4e86\uff0cExpo\u8fc1\u79fb\u5230\u539f\u751freact-native<\/p>\n\n\n\n<p><strong>\u6253\u5305\u6210\u529f<\/strong> eas build &#8211;platform all Loaded &#8220;env&#8221; configuration for the &#8220;production&#8221; profile: no environment variables specified. Learn more: https:\/\/docs.expo.dev\/build-reference\/variables\/ \ufffd\ufffd\ufffd Android build \u221a Using remote Android credentials (Expo server) \u221a Using Keystore from configuration: Build Credentials WZTpmXjDyE (default) Compressing project files and uploading to EAS Build. Learn more: https:\/\/expo.fyi\/eas-build-archive \u221a Uploaded to EAS 6s<\/p>\n\n\n\n<p>\u6253\u5305\u6210\u529f\u540e\u7684\u5b89\u5353\u5e94\u7528\uff08APK\u6587\u4ef6\uff09\u53ef\u4ee5\u5728Expo\u7684EAS Build\u9875\u9762\u4e0b\u8f7d\u3002\u4f60\u53ef\u4ee5\u6309\u7167\u4ee5\u4e0b\u6b65\u9aa4\u6765\u4e0b\u8f7d\u4f60\u7684\u5e94\u7528\uff1a<\/p>\n\n\n\n<ol>\n<li>\u6253\u5f00\u4f60\u7684\u6d4f\u89c8\u5668\uff0c\u8bbf\u95eeExpo\u7684\u7f51\u7ad9\u3002<\/li>\n\n\n\n<li>\u70b9\u51fb\u53f3\u4e0a\u89d2\u7684&#8221;Sign in&#8221;\u6309\u94ae\uff0c\u4f7f\u7528\u4f60\u7684\u8d26\u6237\u767b\u5f55\u3002<\/li>\n\n\n\n<li>\u5728\u767b\u5f55\u540e\u7684\u9875\u9762\uff0c\u70b9\u51fb\u5de6\u4fa7\u83dc\u5355\u7684&#8221;Projects&#8221;\u9009\u9879\u3002<\/li>\n\n\n\n<li>\u5728\u9879\u76ee\u5217\u8868\u4e2d\uff0c\u627e\u5230\u4f60\u521a\u521a\u6784\u5efa\u7684\u9879\u76ee\uff0c\u70b9\u51fb\u8fdb\u5165\u3002<\/li>\n\n\n\n<li>\u5728\u9879\u76ee\u7684\u9875\u9762\u4e2d\uff0c\u70b9\u51fb\u9876\u90e8\u7684&#8221;Builds&#8221;\u9009\u9879\u3002<\/li>\n\n\n\n<li>\u5728&#8221;Builds&#8221;\u9875\u9762\u4e2d\uff0c\u4f60\u53ef\u4ee5\u770b\u5230\u4f60\u7684\u6240\u6709\u6784\u5efa\u5386\u53f2\u3002\u627e\u5230\u4f60\u521a\u521a\u6784\u5efa\u7684Android\u5e94\u7528\uff0c\u70b9\u51fb\u53f3\u4fa7\u7684&#8221;Download&#8221;\u6309\u94ae\u3002<\/li>\n\n\n\n<li>\u6d4f\u89c8\u5668\u4f1a\u5f00\u59cb\u4e0b\u8f7d\u4f60\u7684\u5e94\u7528\u3002\u4e0b\u8f7d\u5b8c\u6210\u540e\uff0c\u4f60\u53ef\u4ee5\u5c06\u8fd9\u4e2aAPK\u6587\u4ef6\u5b89\u88c5\u5230\u4f60\u7684Android\u8bbe\u5907\u4e0a\u3002<\/li>\n\n\n\n<li> <div class='fancybox-wrapper' data-fancybox='post-images' href=''><img src=\"\"><\/div>\u7531\u4e8e\u4e0b\u8f7d\u4e0b\u6765\u7684\u8fd8\u662faad\u6587\u4ef6\uff0c\u4e0d\u80fd\u76f4\u63a5\u5b89\u88c5<div class='fancybox-wrapper' data-fancybox='post-images' href=''><img src=\"\"><\/div>\n<ol>\n<li><strong>\u6253\u5f00Android Studio<\/strong>\uff1a\u542f\u52a8Android Studio\uff0c\u7136\u540e\u5728\u6b22\u8fce\u754c\u9762\u9009\u62e9&#8221;Profile or debug APK&#8221;\u3002<\/li>\n\n\n\n<li><strong>\u9009\u62e9\u4f60\u7684.aab\u6587\u4ef6<\/strong>\uff1a\u5728\u5f39\u51fa\u7684\u6587\u4ef6\u9009\u62e9\u5668\u4e2d\uff0c\u627e\u5230\u5e76\u9009\u62e9\u4f60\u7684.aab\u6587\u4ef6\uff0c\u7136\u540e\u70b9\u51fb&#8221;OK&#8221;\u3002<\/li>\n\n\n\n<li><strong>\u751f\u6210APK\u6587\u4ef6<\/strong>\uff1a\u5728Android Studio\u7684\u83dc\u5355\u4e2d\uff0c\u9009\u62e9&#8221;Build &gt; Build Bundle(s) \/ APK(s) &gt; Build APK(s)&#8221;\u3002Android Studio\u4f1a\u5f00\u59cb\u751f\u6210APK\u6587\u4ef6\u3002<\/li>\n\n\n\n<li><strong>\u627e\u5230\u5e76\u5b89\u88c5APK\u6587\u4ef6<\/strong>\uff1a\u751f\u6210\u5b8c\u6210\u540e\uff0cAndroid Studio\u4f1a\u663e\u793a\u4e00\u4e2a\u901a\u77e5\uff0c\u544a\u8bc9\u4f60APK\u6587\u4ef6\u7684\u4f4d\u7f6e\u3002\u4f60\u53ef\u4ee5\u70b9\u51fb\u8fd9\u4e2a\u901a\u77e5\u6765\u6253\u5f00\u6587\u4ef6\u4f4d\u7f6e\uff0c\u7136\u540e\u5c06APK\u6587\u4ef6\u5b89\u88c5\u5230\u4f60\u7684\u8bbe\u5907\u4e0a\u3002 <div class='fancybox-wrapper' data-fancybox='post-images' href=''><img src=\"\"><\/div><\/li>\n<\/ol>\n<\/li>\n<\/ol>\n\n\n\n<p>\u4e0b\u8f7dbundletool\u6765\u8fdb\u884c\u5b89\u88c5https:\/\/github.com\/google\/bundletool\/releases<\/p>\n\n\n\n<p>1.<strong>\u4e0b\u8f7dbundletool<\/strong>\uff1a\u4f60\u53ef\u4ee5\u4ecebundletool\u7684GitHub\u9875\u9762\u4e0b\u8f7d\u6700\u65b0\u7684bundletool\u7684jar\u6587\u4ef6\u3002<\/p>\n\n\n\n<p>2.<strong>\u751f\u6210APKs\u6587\u4ef6<\/strong>\uff1a\u6253\u5f00\u7ec8\u7aef\uff0c\u8fd0\u884c\u4ee5\u4e0b\u547d\u4ee4\u6765\u751f\u6210APKs\u6587\u4ef6\u3002\u5176\u4e2d\uff0cpath\/to\/bundletool.jar\u662f\u4f60\u4e0b\u8f7d\u7684bundletool\u7684\u8def\u5f84\uff0cpath\/to\/your-app.aab\u662f\u4f60\u7684.aab\u6587\u4ef6\u7684\u8def\u5f84\uff0cpath\/to\/output.apks\u662f\u8f93\u51fa\u7684APKs\u6587\u4ef6\u7684\u8def\u5f84\u3002 java -jar path\/to\/bundletool.jar build-apks &#8211;bundle=path\/to\/your-app.aab &#8211;output=path\/to\/output.apks<\/p>\n\n\n\n<p>\u8fd9\u91cc\u6211\u653e\u4e00\u8d77\u4e86<\/p>\n\n\n\n<figure class=\"wp-block-image size-full\"><div class='fancybox-wrapper' data-fancybox='post-images' href='https:\/\/www.muxuetianyin.cn\/wp-content\/uploads\/2023\/09\/image-21.png'><img loading=\"lazy\" width=\"646\" height=\"175\" src=\"https:\/\/www.muxuetianyin.cn\/wp-content\/uploads\/2023\/09\/image-21.png\" alt=\"\" class=\"wp-image-545\" srcset=\"https:\/\/www.muxuetianyin.cn\/wp-content\/uploads\/2023\/09\/image-21.png 646w, https:\/\/www.muxuetianyin.cn\/wp-content\/uploads\/2023\/09\/image-21-300x81.png 300w\" sizes=\"(max-width: 646px) 100vw, 646px\" \/><\/div><\/figure>\n\n\n\n<p>java -jar bundletool-all-1.15.4.jar build-apks &#8211;bundle=application-bd6e0190-4a05-4178-a1c8-7fec07da40d3.aab &#8211;output=MuXueBeauseYou.apks<\/p>\n\n\n\n<p>\u8fd9\u91cc\u5148\u4e0d\u8981\u8fd0\u884c\u8fd9\u4e2a\u547d\u4ee4<\/p>\n\n\n\n<p>\u5728\u8fd9\u4e2a\u76ee\u5f55cmd<\/p>\n\n\n\n<ol>\n<li><strong>\u751f\u6210\u5bc6\u94a5\u5e93\u6587\u4ef6<\/strong>\uff1a\u4f60\u53ef\u4ee5\u4f7f\u7528keytool\u547d\u4ee4\u6765\u751f\u6210\u4e00\u4e2a\u65b0\u7684\u5bc6\u94a5\u5e93\u6587\u4ef6\u3002keytool\u662fJava Development Kit (JDK)\u7684\u4e00\u90e8\u5206\uff0c\u4f60\u5e94\u8be5\u5df2\u7ecf\u5728\u4f60\u7684\u7535\u8111\u4e0a\u5b89\u88c5\u4e86\u5b83\u3002\u5728\u4f60\u7684\u7ec8\u7aef\u4e2d\u8fd0\u884c\u4ee5\u4e0b\u547d\u4ee4\uff0c\u5176\u4e2dmy-release-key.jks\u662f\u4f60\u7684\u5bc6\u94a5\u5e93\u6587\u4ef6\u7684\u540d\u79f0\uff0calias_name\u662f\u4f60\u7684\u522b\u540d\uff0cpassword\u662f\u4f60\u7684\u5bc6\u7801\uff1a<\/li>\n<\/ol>\n\n\n\n<p>keytool -genkey -v -keystore my-release-key.jks -keyalg RSA -keysize 2048 -validity 10000 -alias alias_name<\/p>\n\n\n\n<p>\u6211\u8fd9\u91cckeytool -genkey -v -keystore muxuetianyin.jks -keyalg RSA -keysize 2048 -validity 10000 -alias muxue -storepass 123456 -keypass 123456 keytool -importkeystore -srckeystore muxuetianyin.jks -destkeystore muxuetianyin.p12 -deststoretype pkcs12 -srcstorepass 123456 -deststorepass 123456<\/p>\n\n\n\n<p>\u548c\u4e4b\u524d\u7684\u5bf9\u63a5\u8d77\u6765\u5b8c\u6574\u7684\u547d\u4ee4\u662fjava -jar bundletool-all-1.15.4.jar build-apks &#8211;bundle=application-bd6e0190-4a05-4178-a1c8-7fec07da40d3.aab &#8211;output=MuXueBeauseYou.apks &#8211;ks=muxuetianyin.jks &#8211;ks-key-alias=muxue &#8211;ks-pass=pass:123456 &#8211;key-pass=pass:123456<\/p>\n\n\n\n<figure class=\"wp-block-image size-large\"><div class='fancybox-wrapper' data-fancybox='post-images' href='https:\/\/www.muxuetianyin.cn\/wp-content\/uploads\/2023\/09\/image-22-1024x358.png'><img loading=\"lazy\" width=\"1024\" height=\"358\" src=\"https:\/\/www.muxuetianyin.cn\/wp-content\/uploads\/2023\/09\/image-22-1024x358.png\" alt=\"\" class=\"wp-image-546\" srcset=\"https:\/\/www.muxuetianyin.cn\/wp-content\/uploads\/2023\/09\/image-22-1024x358.png 1024w, https:\/\/www.muxuetianyin.cn\/wp-content\/uploads\/2023\/09\/image-22-300x105.png 300w, https:\/\/www.muxuetianyin.cn\/wp-content\/uploads\/2023\/09\/image-22-768x268.png 768w, https:\/\/www.muxuetianyin.cn\/wp-content\/uploads\/2023\/09\/image-22.png 1466w\" sizes=\"(max-width: 1024px) 100vw, 1024px\" \/><\/div><\/figure>\n\n\n\n<p>\u8fd9\u91cc\u7b7e\u540d\u5931\u8d25\u4e86PS E:\\code\\project\\Because-Of-You&gt; expo credentials:manager \u250c\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2510 \u2502 \u2502 \u2502 The global expo-cli package has been deprecated. \u2502 \u2502 \u2502 \u2502 The new Expo CLI is now bundled in your project in the expo package. \u2502 \u2502 Learn more: https:\/\/blog.expo.dev\/the-new-expo-cli-f4250d8e3421. \u2502 \u2502 \u2502 \u2502 To use the local CLI instead (recommended in SDK 46 and higher), run: \u2502 \u2502 \u203a npx expo &lt;command&gt; \u2502 \u2502 \u2502 \u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2518 Accessing credentials for muxue in project Because-Of-You \u221a Select platform \u00bb android \u221a You are currently in a directory with @muxue\/Because-Of-You experience. Do you want to select it? &#8230; yes No credentials available for @muxue\/Because-Of-You experience. \u221a What do you want to do? \u00bb Update upload Keystore \u221a Would you like to upload a Keystore or have us generate one for you? If you don&#8217;t know what this means, let us generate it! \ud83d\ude42 \u00bb Generate new keystore Keystore updated successfully \u221a Do you want to quit Credential Manager \u00bb Quit Credential Manager<\/p>\n\n\n\n<p>\u91cd\u65b0\u5c1d\u8bd5<\/p>\n\n\n\n<p>\u8fd9\u91cc\u751f\u6210\u4e86\u4e00\u4e2a\u7b7e\u540d\u5e93\uff0c\u91cd\u65b0\u83b7\u53d6\u5230java -jar bundletool-all-1.15.4.jar build-apks &#8211;bundle=muxue.aab &#8211;output=MuXueBeauseYou.apks &#8211;ks=E:\\code\\project\\Because-Of-You\\Because-Of-You.jks &#8211;ks-key-alias=QG11eHVlL0JlY2F1c2UtT2YtWW91 &#8211;ks-pass=pass:8c931db2aeea4336a2eb83c5e0610296 &#8211;key-pass=pass:fc307b4d8034469199c3645dabb2e065<\/p>\n\n\n\n<p>\u5b89\u88c5java -jar bundletool-all-1.15.4.jar install-apks &#8211;apks=MuXueBeauseYou.apks<\/p>\n\n\n\n<p>\u5b89\u5353\u65e5\u5fd7\u6253\u5370adb logcat | grep com.example.myapp adb logcat *:S ReactNative:V ReactNativeJS:V<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">\u53e6\u4e00\u79cd\u65b9\u6cd5(\u63a8\u8350)<\/h3>\n\n\n\n<p>\u76f4\u63a5\u6253\u5305apk\u4e0b\u8f7d\uff0c\u8fd9\u662f\u8e29\u4e86\u4e00\u5806\u5751\u624d\u77e5\u9053\u7684<\/p>\n\n\n\n<p>\u9879\u76ee\u4e2d\u521b\u5efa\u4e00\u4e2aeas.json\u6587\u4ef6\uff08\u5982\u679c\u8fd8\u6ca1\u6709\u7684\u8bdd\uff09\uff0c\u7136\u540e\u6dfb\u52a0\u4e00\u4e2a\u65b0\u7684\u6784\u5efa\u914d\u7f6e\u3002\u4f8b\u5982\uff1a<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code><code>{ \"build\": { \"release\": { \"distribution\": \"store\", \"android\": { \"gradleCommand\": \":app:assembleRelease\" } } } }<\/code><\/code><\/pre>\n\n\n\n<p>\u914d\u7f6e<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>{ \"cli\": { \"version\": \"&gt;= 5.1.0\" }, \"build\": { \"development\": { \"developmentClient\": true, \"distribution\": \"internal\" }, \"preview\": { \"distribution\": \"internal\" }, \"production\": {}, \"release\": { \"distribution\": \"store\", \"android\": { \"gradleCommand\": \":app:assembleRelease\" } } }, \"submit\": { \"production\": {} } }<\/code><\/pre>\n\n\n\n<p>\u8fd8\u8981\u5b89\u88c5\u51e0\u4e2a\u4e1c\u897f<\/p>\n\n\n\n<p>npm config set sharp_binary_host &#8220;https:\/\/npmmirror.com\/mirrors\/sharp&#8221; <\/p>\n\n\n\n<p>npm config set sharp_libvips_binary_host &#8220;https:\/\/npmmirror.com\/mirrors\/sharp-libvips&#8221;<\/p>\n\n\n\n<p> npm install -g sharp-cli <\/p>\n\n\n\n<p>yarn upgrade react-native-vector-icons@latest<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">ios\u6253\u5305<\/h2>\n\n\n\n<p>\u8fd9\u4e2a\u6709\u70b9\u9ebb\u70e6\u4e86\uff0c\u9700\u8981\u7533\u8bf7\u5f00\u53d1\u8005\u8fd8\u8981\u4ed8\u8d39\u4e00\u4e0b<\/p>\n\n\n\n<p>\u9700\u8981\u56e2\u961f\u652f\u6301\u624d\u80fd\u4e0a\u82f9\u679c\u5546\u5e97<\/p>\n\n\n\n<p>\u5728\u8fd9\u91cc\u7533\u8bf7<\/p>\n","protected":false},"excerpt":{"rendered":"<p>\u8bbe\u8ba1\u4e00\u4e2aReact Native\u5e94\u7528\u9700\u8981\u8003\u8651\u5f88\u591a\u56e0\u7d20\uff0c\u5305\u62ec\u5e94\u7528\u7684\u529f\u80fd\u3001\u7528\u6237\u754c\u9762\u3001\u6570\u636e\u7ba1\u7406\u3001\u72b6\u6001\u7ba1\u7406\u3001\u8def\u7531\u7ba1\u7406\u3001 [&hellip;]<\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"closed","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":[],"categories":[1],"tags":[],"_links":{"self":[{"href":"https:\/\/www.muxuetianyin.cn\/index.php?rest_route=\/wp\/v2\/posts\/526"}],"collection":[{"href":"https:\/\/www.muxuetianyin.cn\/index.php?rest_route=\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/www.muxuetianyin.cn\/index.php?rest_route=\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/www.muxuetianyin.cn\/index.php?rest_route=\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/www.muxuetianyin.cn\/index.php?rest_route=%2Fwp%2Fv2%2Fcomments&post=526"}],"version-history":[{"count":5,"href":"https:\/\/www.muxuetianyin.cn\/index.php?rest_route=\/wp\/v2\/posts\/526\/revisions"}],"predecessor-version":[{"id":555,"href":"https:\/\/www.muxuetianyin.cn\/index.php?rest_route=\/wp\/v2\/posts\/526\/revisions\/555"}],"wp:attachment":[{"href":"https:\/\/www.muxuetianyin.cn\/index.php?rest_route=%2Fwp%2Fv2%2Fmedia&parent=526"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/www.muxuetianyin.cn\/index.php?rest_route=%2Fwp%2Fv2%2Fcategories&post=526"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/www.muxuetianyin.cn\/index.php?rest_route=%2Fwp%2Fv2%2Ftags&post=526"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}