onPressed: () {
Navigator.pushNamed(
context,
‘/details/1’,
);
},
),
),
);
}
}
class DetailScreen extends StatelessWidget {
String id;
DetailScreen({
this.id,
});
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text(‘Viewing details for item $id’),
FlatButton(
child: Text(‘Pop!’),
onPressed: () {
Navigator.pop(context);
},
),
],
),
),
);
}
}
class UnknownScreen extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(),
body: Center(
child: Text(‘404!’),
),
);
}
}
这里,我们可以通过 RouteSettings
类型的对象 settings
可以拿到 Navigator.pushNamed
调用时传入的参数。
Navigator 2.0
Navigator 2.0 提供了一系列全新的接口,可以实现将路由状态成为应用状态的一部分,并能够通过底层 API 实现参数解析的功能,新增的 API 如下:
-
Page,用来表示
Navigator
路由栈中各个页面的配置信息。 -
Router,用来制定要由
Navigator
展示的页面列表,通常,该页面列表会根据系统或应用程序的状态改变而改变。 -
RouteInformationParser,持有
RouteInformationProvider
提供的RouteInformation
,可以将其解析为我们定义的数据类型。 -
RouterDelegate,定义应用程序中的路由行为,例如 Router 如何知道应用程序状态的变化以及如何响应。主要的工作就是监听
RouteInformationParser
和应用状态并通过当前页面列表构建 ·。 -
BackButtonDispatcher,响应后退按钮,并通知
Router
下图展示了 RouterDelegate
与 Router
、RouteInformationParser
以及用用状态的交互原理,
大致流程如下:
-
当系统打开新页面(如
“books / 2”
)时,RouteInformationParser
会将其转换为应用中的具体数据类型 T(如BooksRoutePath
)。 -
该数据类型会被传递给
RouterDelegate
的setNewRoutePath
方法,我们可以在这里更新路由状态(如通过设置selectedBookId
)并调用notifyListeners
响应该操作。 -
notifyListeners
会通知Router
重建RouterDelegate
(通过build()
方法). -
RouterDelegate.build()
返回一个新的Navigator
实例,并最终展示出我们想要打开的页面(如selectedBookId
)。
Navigator 2.0 实战
下面,我们就来使用 Navigator 2.0 做一个小小练习,我们将实现一个 Flutter 应用,该应用作用在 Web 上时路由状态会与浏览器中的 URL 连接保持一致,而且也能够处理浏览器的回退按钮,如下:
接下来,使用 flutter channel master
将 Flutter 切换到 master 版本,创建一个支持 Web 应用的 Flutter 项目,lib/main.dart
中的代码如下:
import ‘package:flutter/material.dart’;
void main() {
runApp(BooksApp());
}
class Book {
final String title;
final String author;
Book(this.title, this.author);
}
class BooksApp extends StatefulWidget {
@override
State createState() => _BooksAppState();
}
class _BooksAppState extends State {
void initState() {
super.initState();
}
@override
Widget build(BuildContext context) {
return MaterialApp(
title: ‘Books App’,
home: Navigator(
pages: [
MaterialPage(
key: ValueKey(‘BooksListPage’),
child: Scaffold(),
)
],
onPopPage: (route, result) => route.didPop(result),
),
);
}
}
Pages
Navigator
接受一个 pages
参数,如果 Page 列表发生变化,Navigator
也需要更新当前路由栈来保持同步,下面我们就来使用该性质,在新建的项目中开发一个可以展示书单列表的应用
_BooksAppState
中持有两个状态参数:书单列表和当前所选书籍:
class _BooksAppState extends State {
// New:
Book _selectedBook;
bool show404 = false;
List books = [
Book(‘Stranger in a Strange Land’, ‘Robert A. Heinlein’),
Book(‘Foundation’, ‘Isaac Asimov’),
Book(‘Fahrenheit 451’, ‘Ray Bradbury’),
];
// …
然后,在中_BooksAppState
,返回一个带有 Page
对象列表的 Navigator
:
@override
Widget build(BuildContext context) {
return MaterialApp(
title: ‘Books App’,
home: Navigator(
pages: [
MaterialPage(
key: ValueKey(‘BooksListPage’),
child: BooksListScreen(
books: books,
onTapped: _handleBookTapped,
),
),
],
),
);
}
void _handleBookTapped(Book book) {
setState(() {
_selectedBook = book;
});
}
// …
class BooksListScreen extends StatelessWidget {
final List books;
final ValueChanged onTapped;
BooksListScreen({
@required this.books,
@required this.onTapped,
});
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(),
body: ListView(
children: [
for (var book in books)
ListTile(
title: Text(book.title),
subtitle: Text(book.author),
onTap: () => onTapped(book),
)
],
),
);
}
}
由于此应用会有两个页面(一个书单列表也和一个详情的页面),如果选择了某本书(使用 collection if),则会加入详细页:
pages: [
MaterialPage(
key: ValueKey(‘BooksListPage’),
child: BooksListScreen(
books: books,
onTapped: _handleBookTapped,
),
),
// New:
if (show404)
MaterialPage(key: ValueKey(‘UnknownPage’), child: UnknownScreen())
else if (_selectedBook != null)
MaterialPage(
key: ValueKey(_selectedBook),
child: BookDetailsScreen(book: _selectedBook))
],
注意,这里的 key 会由 book 对象中的值定义作为 MaterialPage
的唯一标识,也就是说,book 对象不同这里的 MaterialPage
就不同。没有唯一的 key,框架就无法确定何时显示不同 Page 之间的过渡动画。
我们还可以继承 Page
来实现自定义行为,例如,在该页面添加了自定义过渡动画:
class BookDetailsPage extends Page {
final Book book;
BookDetailsPage({
this.book,
}) : super(key: ValueKey(book));
Route createRoute(BuildContext context) {
return PageRouteBuilder(
settings: this,
pageBuilder: (context, animation, animation2) {
final tween = Tween(begin: Offset(0.0, 1.0), end: Offset.zero);
final curveTween = CurveTween(curve: Curves.easeInOut);
return SlideTransition(
position: animation.drive(curveTween).drive(tween),
child: BookDetailsScreen(
key: ValueKey(book),
book: book,
),
);
},
);
}
}
还需要注意的是,只传入 pages 参数而不传入 onPopPage
也会报错,他接受一个回调函数,每次 Navigator.pop()
被调用时就会出发这个函数,我们可以在其中更新路由状态
最后,在pages
不提供onPopPage
回调的情况下提供参数是错误的。每次调用时都会Navigator.pop()
调用此函数。应该使用它来更新状态(修改页面列表),这里我们需要调用 didPop
方法确定是否 pop 成功:
onPopPage: (route, result) {
if (!route.didPop(result)) {
return false;
}
// Update the list of pages by setting _selectedBook to null
setState(() {
_selectedBook = null;
});
return true;
},
我们还必须在更新应用程序状态之前检查是否 pop 失败。这里,我们使用了 setState
方法来通知 Flutter 调用 build()
方法,该方法 _selectedBook
为 null 表示展示书单列表页。
完整代码如下:
import ‘package:flutter/material.dart’;
void main() {
runApp(BooksApp());
}
class Book {
final String title;
final String author;
Book(this.title, this.author);
}
class BooksApp extends StatefulWidget {
@override
State createState() => _BooksAppState();
}
class _BooksAppState extends State {
Book _selectedBook;
List books = [
Book(‘Stranger in a Strange Land’, ‘Robert A. Heinlein’),
Book(‘Foundation’, ‘Isaac Asimov’),
Book(‘Fahrenheit 451’, ‘Ray Bradbury’),
];
@override
Widget build(BuildContext context) {
return MaterialApp(
title: ‘Books App’,
home: Navigator(
pages: [
MaterialPage(
key: ValueKey(‘BooksListPage’),
child: BooksListScreen(
books: books,
onTapped: _handleBookTapped,
),
),
if (_selectedBook != null) BookDetailsPage(book: _selectedBook)
],
onPopPage: (route, result) {
if (!route.didPop(result)) {
return false;
}
// Update the list of pages by setting _selectedBook to null
setState(() {
_selectedBook = null;
});
return true;
},
),
);
}
void _handleBookTapped(Book book) {
setState(() {
_selectedBook = book;
});
}
}
class BookDetailsPage extends Page {
final Book book;
BookDetailsPage({
this.book,
}) : super(key: ValueKey(book));
Route createRoute(BuildContext context) {
return MaterialPageRoute(
settings: this,
builder: (BuildContext context) {
return BookDetailsScreen(book: book);
},
);
}
}
class BooksListScreen extends StatelessWidget {
final List books;
final ValueChanged onTapped;
BooksListScreen({
@required this.books,
@required this.onTapped,
});
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(),
body: ListView(
children: [
for (var book in books)
ListTile(
title: Text(book.title),
subtitle: Text(book.author),
onTap: () => onTapped(book),
)
],
),
);
}
}
class BookDetailsScreen extends StatelessWidget {
final Book book;
BookDetailsScreen({
@required this.book,
});
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(),
body: Padding(
padding: const EdgeInsets.all(8.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
if (book != null) …[
Text(book.title, style: Theme.of(context).textTheme.headline6),
Text(book.author, style: Theme.of(context).textTheme.subtitle1),
],
],
),
),
);
}
}
目前,我们就实现了声明式的路由管理,单此时我们还无法处理浏览器的后退按钮,也不能同步浏览器地址拦中的链接。
Router
本节,我们来实现通过 RouteInformationParser
, RouterDelegate
更新路由状态,实现与浏览器地址拦中的链接同步
数据类型
首先,我们需要通过 RouteInformationParser
将路由信息解析为指定的数据类型:
class BookRoutePath {
final int id;
final bool isUnknown;
-
BookRoutePath.home()
- id = null,
isUnknown = false;
BookRoutePath.details(this.id) : isUnknown = false;
-
BookRoutePath.unknown()
- id = null,
isUnknown = true;
bool get isHomePage => id == null;
bool get isDetailsPage => id != null;
}
在该应用程序中,可以使用 BookRoutePath
类来表示应用程序中的路由路径,我们也可以实现父子类来关系其他各类型的路由信息。
RouterDelegate
接下来,我们实现一个 RouterDelegate
的子类 BookRouterDelegate
:
class BookRouterDelegate extends RouterDelegate
with ChangeNotifier, PopNavigatorRouterDelegateMixin {
@override
Widget build(BuildContext context) {
// TODO
throw UnimplementedError();
}
@override
// TODO
GlobalKey get navigatorKey => throw UnimplementedError();
@override
Future setNewRoutePath(BookRoutePath configuration) {
// TODO
throw UnimplementedError();
}
}
BookRouterDelegate
的泛型为 BookRoutePath
,其中包含了决定显示哪个页面所需的所有状态。
此时,我们就可以将 _BooksAppState
中的路由相关的逻辑放到 BookRouterDelegate
中,这里,我们创建了一个 GlobalKey
对象,其他各个状态也都保存在这里面:
class BookRouterDelegate extends RouterDelegate
with ChangeNotifier, PopNavigatorRouterDelegateMixin {
final GlobalKey navigatorKey;
Book _selectedBook;
bool show404 = false;
List books = [
Book(‘Stranger in a Strange Land’, ‘Robert A. Heinlein’),
Book(‘Foundation’, ‘Isaac Asimov’),
Book(‘Fahrenheit 451’, ‘Ray Bradbury’),
];
BookRouterDelegate() : navigatorKey = GlobalKey();
// …
为了能在 URL 中显示正确的路径,我们也需要根据应用程序的当前状态返回一个 BookRoutePath
对象:
BookRoutePath get currentConfiguration {
if (show404) {
return BookRoutePath.unknown();
}
return _selectedBook == null
-
? BookRoutePath.home()
- BookRoutePath.details(books.indexOf(_selectedBook));
}
下面,build
方法返回一个 Navigator
组件:
@override
Widget build(BuildContext context) {
return Navigator(
key: navigatorKey,
pages: [
MaterialPage(
key: ValueKey(‘BooksListPage’),
child: BooksListScreen(
books: books,
onTapped: _handleBookTapped,
),
),
if (show404)
MaterialPage(key: ValueKey(‘UnknownPage’), child: UnknownScreen())
else if (_selectedBook != null)
BookDetailsPage(book: _selectedBook)
],
onPopPage: (route, result) {
if (!route.didPop(result)) {
return false;
}
// Update the list of pages by setting _selectedBook to null
_selectedBook = null;
show404 = false;
notifyListeners();
return true;
},
);
}
因为该类并不是组件,而是由 ChangeNotifier
实现,因此这里的 onPopPage
方法需要使用 notifyListeners
替代 setState
来改变状态,当 RouterDelegate
触发状态更新时,Router
同样会触发 RouterDelegate
的 currentConfiguration
方法并调用 build
方法创建出一个新的 Navigator
组件。
_handleBookTapped
方法也需要使用 notifyListeners
代替 setState
:
void _handleBookTapped(Book book) {
_selectedBook = book;
notifyListeners();
}
新页面打开后,Router
会调用setNewRoutePath
方法来更新应用程序的路由状态:
@override
Future setNewRoutePath(BookRoutePath path) async {
if (path.isUnknown) {
_selectedBook = null;
show404 = true;
return;
}
if (path.isDetailsPage) {
if (path.id < 0 || path.id > books.length - 1) {
show404 = true;
return;
}
_selectedBook = books[path.id];
} else {
_selectedBook = null;
}
show404 = false;
}
RouteInformationParser
RouteInformationParser
内部含有一个钩子函数,接受路由信息(RouteInformation
),我们可以在这里将它转换成指定的数据类型(BookRoutePath
)并使用 Uri 解析:
class BookRouteInformationParser extends RouteInformationParser {
@override
Future parseRouteInformation(
RouteInformation routeInformation) async {
final uri = Uri.parse(routeInformation.location);
// Handle ‘/’
if (uri.pathSegments.length == 0) {
return BookRoutePath.home();
}
// Handle ‘/book/:id’
if (uri.pathSegments.length == 2) {
if (uri.pathSegments[0] != ‘book’) return BookRoutePath.unknown();
var remaining = uri.pathSegments[1];
var id = int.tryParse(remaining);
if (id == null) return BookRoutePath.unknown();
return BookRoutePath.details(id);
}
// Handle unknown routes
return BookRoutePath.unknown();
}
@override
RouteInformation restoreRouteInformation(BookRoutePath path) {
if (path.isUnknown) {
return RouteInformation(location: ‘/404’);
}
if (path.isHomePage) {
return RouteInformation(location: ‘/’);
}
if (path.isDetailsPage) {
return RouteInformation(location: ‘/book/${path.id}’);
}
return null;
}
}
该实现仅针对此应用,并不是常规的路由解析解决方案,具体原理,我们以后再详细了解。最后,要使用这些定义好的类,我们还需要使用全新的 MaterialApp.router
构造函数并传入它们各自的实现:
return MaterialApp.router(
title: ‘Books App’,
routerDelegate: _routerDelegate,
routeInformationParser: _routeInformationParser,
);
完整代码如下:
import ‘package:flutter/material.dart’;
void main() {
runApp(BooksApp());
}
class Book {
final String title;
final String author;
Book(this.title, this.author);
}
class BooksApp extends StatefulWidget {
@override
State createState() => _BooksAppState();
}
class _BooksAppState extends State {
BookRouterDelegate _routerDelegate = BookRouterDelegate();
BookRouteInformationParser _routeInformationParser =
BookRouteInformationParser();
@override
Widget build(BuildContext context) {
return MaterialApp.router(
title: ‘Books App’,
routerDelegate: _routerDelegate,
routeInformationParser: _routeInformationParser,
);
}
}
class BookRouteInformationParser extends RouteInformationParser {
@override
Future parseRouteInformation(
RouteInformation routeInformation) async {
final uri = Uri.parse(routeInformation.location);
// Handle ‘/’
if (uri.pathSegments.length == 0) {
return BookRoutePath.home();
}
// Handle ‘/book/:id’
if (uri.pathSegments.length == 2) {
if (uri.pathSegments[0] != ‘book’) return BookRoutePath.unknown();
var remaining = uri.pathSegments[1];
var id = int.tryParse(remaining);
if (id == null) return BookRoutePath.unknown();
return BookRoutePath.details(id);
}
// Handle unknown routes
return BookRoutePath.unknown();
}
@override
RouteInformation restoreRouteInformation(BookRoutePath path) {
if (path.isUnknown) {
return RouteInformation(location: ‘/404’);
}
if (path.isHomePage) {
return RouteInformation(location: ‘/’);
}
if (path.isDetailsPage) {
return RouteInformation(location: ‘/book/${path.id}’);
}
return null;
}
}
class BookRouterDelegate extends RouterDelegate
with ChangeNotifier, PopNavigatorRouterDelegateMixin {
final GlobalKey navigatorKey;
Book _selectedBook;
bool show404 = false;
List books = [
Book(‘Stranger in a Strange Land’, ‘Robert A. Heinlein’),
Book(‘Foundation’, ‘Isaac Asimov’),
Book(‘Fahrenheit 451’, ‘Ray Bradbury’),
];
BookRouterDelegate() : navigatorKey = GlobalKey();
BookRoutePath get currentConfiguration {
if (show404) {
return BookRoutePath.unknown();
}
return _selectedBook == null
-
? BookRoutePath.home()
- BookRoutePath.details(books.indexOf(_selectedBook));
}
@override
Widget build(BuildContext context) {
return Navigator(
key: navigatorKey,
pages: [
MaterialPage(
key: ValueKey(‘BooksListPage’),
child: BooksListScreen(
books: books,
onTapped: _handleBookTapped,
),
),
if (show404)
MaterialPage(key: ValueKey(‘UnknownPage’), child: UnknownScreen())
else if (_selectedBook != null)
BookDetailsPage(book: _selectedBook)
],
onPopPage: (route, result) {
if (!route.didPop(result)) {
return false;
}
// Update the list of pages by setting _selectedBook to null
_selectedBook = null;
show404 = false;
notifyListeners();
return true;
},
);
}
@override
自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。
深知大多数初中级Android工程师,想要提升技能,往往是自己摸索成长或者是报班学习,但对于培训机构动则近万的学费,着实压力不小。自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!
因此收集整理了一份《2024年Android移动开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。
既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Android开发知识点,真正体系化!
由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且会持续更新!
如果你觉得这些内容对你有帮助,可以扫码获取!!(备注:Android)
![](https://img-blog.csdnimg.cn/img_convert/2f74bb093c2f6aba269819d5440bc8ee.jpeg)
最后
简历首选内推方式,速度快,效率高啊!然后可以在拉钩,boss,脉脉,大街上看看。简历上写道熟悉什么技术就一定要去熟悉它,不然被问到不会很尴尬!做过什么项目,即使项目体量不大,但也一定要熟悉实现原理!不是你负责的部分,也可以看看同事是怎么实现的,换你来做你会怎么做?做过什么,会什么是广度问题,取决于项目内容。但做过什么,达到怎样一个境界,这是深度问题,和个人学习能力和解决问题的态度有关了。大公司看深度,小公司看广度。大公司面试你会的,小公司面试他们用到的你会不会,也就是岗位匹配度。
面试过程一定要有礼貌!即使你觉得面试官不尊重你,经常打断你的讲解,或者你觉得他不如你,问的问题缺乏专业水平,你也一定要尊重他,谁叫现在是他选择你,等你拿到offer后就是你选择他了。
另外,描述问题一定要慢!不要一下子讲一大堆,慢显得你沉稳、自信,而且你还有时间反应思路接下来怎么讲更好。现在开发过多依赖ide,所以会有个弊端,当我们在面试讲解很容易不知道某个方法怎么读,这是一个硬伤…所以一定要对常见的关键性的类名、方法名、关键字读准,有些面试官不耐烦会说“你到底说的是哪个?”这时我们会容易乱了阵脚。正确的发音+沉稳的描述+好听的嗓音决对是一个加分项!
最重要的是心态!心态!心态!重要事情说三遍!面试时间很短,在短时间内对方要摸清你的底子还是比较不现实的,所以,有时也是看眼缘,这还是个看脸的时代。
希望大家都能找到合适自己满意的工作!
进阶学习视频
附上:我们之前因为秋招收集的二十套一二线互联网公司Android面试真题 (含BAT、小米、华为、美团、滴滴)和我自己整理Android复习笔记(包含Android基础知识点、Android扩展知识点、Android源码解析、设计模式汇总、Gradle知识点、常见算法题汇总。)
《Android学习笔记总结+移动架构视频+大厂面试真题+项目实战源码》,点击传送门即可获取!
培训机构动则近万的学费,着实压力不小。自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!**
因此收集整理了一份《2024年Android移动开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。
[外链图片转存中…(img-jf5TTwGM-1712260118475)]
[外链图片转存中…(img-9aTXd9M9-1712260118475)]
[外链图片转存中…(img-DaEW1nKk-1712260118476)]
[外链图片转存中…(img-aDYTcfZi-1712260118476)]
[外链图片转存中…(img-Pri6mDxm-1712260118476)]
既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Android开发知识点,真正体系化!
由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且会持续更新!
如果你觉得这些内容对你有帮助,可以扫码获取!!(备注:Android)
![](https://img-blog.csdnimg.cn/img_convert/2f74bb093c2f6aba269819d5440bc8ee.jpeg)
最后
简历首选内推方式,速度快,效率高啊!然后可以在拉钩,boss,脉脉,大街上看看。简历上写道熟悉什么技术就一定要去熟悉它,不然被问到不会很尴尬!做过什么项目,即使项目体量不大,但也一定要熟悉实现原理!不是你负责的部分,也可以看看同事是怎么实现的,换你来做你会怎么做?做过什么,会什么是广度问题,取决于项目内容。但做过什么,达到怎样一个境界,这是深度问题,和个人学习能力和解决问题的态度有关了。大公司看深度,小公司看广度。大公司面试你会的,小公司面试他们用到的你会不会,也就是岗位匹配度。
面试过程一定要有礼貌!即使你觉得面试官不尊重你,经常打断你的讲解,或者你觉得他不如你,问的问题缺乏专业水平,你也一定要尊重他,谁叫现在是他选择你,等你拿到offer后就是你选择他了。
另外,描述问题一定要慢!不要一下子讲一大堆,慢显得你沉稳、自信,而且你还有时间反应思路接下来怎么讲更好。现在开发过多依赖ide,所以会有个弊端,当我们在面试讲解很容易不知道某个方法怎么读,这是一个硬伤…所以一定要对常见的关键性的类名、方法名、关键字读准,有些面试官不耐烦会说“你到底说的是哪个?”这时我们会容易乱了阵脚。正确的发音+沉稳的描述+好听的嗓音决对是一个加分项!
最重要的是心态!心态!心态!重要事情说三遍!面试时间很短,在短时间内对方要摸清你的底子还是比较不现实的,所以,有时也是看眼缘,这还是个看脸的时代。
希望大家都能找到合适自己满意的工作!
进阶学习视频
[外链图片转存中…(img-DmpW0gDJ-1712260118477)]
附上:我们之前因为秋招收集的二十套一二线互联网公司Android面试真题 (含BAT、小米、华为、美团、滴滴)和我自己整理Android复习笔记(包含Android基础知识点、Android扩展知识点、Android源码解析、设计模式汇总、Gradle知识点、常见算法题汇总。)
[外链图片转存中…(img-ISEx5wEw-1712260118477)]