提交 cd4702cb authored 作者: yueweilu's avatar yueweilu

笔记逻辑

上级 ca470cd3
......@@ -260,4 +260,36 @@ abstract class LibraryAPI {
return false;
}
/// 13、添加笔记、高亮、划线的内容
static Future <bool> addNote({
required String bookId,
required String chapterId,
String types = '3',
required String content,
required String isOpen,
String color= '',
required String positioning,
required String noteContent,
}) async {
final result = await HttpService.to.post(
'/v1/book/Information/addNotes',
params: {
'book_id':bookId,
'chapter_id':chapterId,
'types':types,
'content':content,
'is_open':isOpen,
'color':content,
'positioning':positioning,
'note_content':noteContent
},
);
if (result.data is Map && result.data['is_success'] == 1){
return true;
}
return false;
}
}
\ No newline at end of file
......@@ -17,43 +17,45 @@ class ReadController extends FullLifeCycleController with GetSingleTickerProvide
late ToolModel toolModel = tools.first;
// 输入框窗口是否展示
bool showChat = false;
// 0 讨论 1 笔记
late int chatType = 0;
// 笔记是否公开
bool isPublic = false;
// 笔记位置信息
String notePosition = '';
// 话题焦点
final FocusNode discussTitleFocusNode = FocusNode();
// 内容焦点
final FocusNode discussContentFocusNode = FocusNode();
late AnimationController _controller;
bool _show = true;
bool get show => _show;
// 讨论添加的图片数组
List <String> discussInputImages= [];
// 讨论话题标题
final TextEditingController titleInput = TextEditingController();
// 讨论内容
final TextEditingController contentInput = TextEditingController();
//
// 讨论添加的图片path数组
List <String> discussInputImages= [];
// 讨论添加的语音path数组
List <String> discussInputAudios= [];
// 笔记标题
String noteTitle = '';
bool _show = true;
bool get show => _show;
///------------------------------------------ 页面 生命周期--------------------------------------------------------
@override
void onInit() {
// pageController = PageController(initialPage: currentPage);
discussTitleFocusNode.addListener(_onCommentFocusChanged);
/// 默认不显示状态栏
// SystemChrome.setEnabledSystemUIMode(SystemUiMode.immersiveSticky);
/// 初始化
_controller = AnimationController(
vsync: this,
duration: const Duration(milliseconds: 100),
);
super.onInit();
}
@override
void onReady() {
// 上报开始阅读时间
print('000000000000000000000--------------------------------$bookId');
_addReadTime(type: 'open');
_getChapters();
super.onReady();
......@@ -63,7 +65,6 @@ class ReadController extends FullLifeCycleController with GetSingleTickerProvide
void onClose() {
// 上报阅读结束时间
_addReadTime(type: 'close');
_controller.dispose();
discussTitleFocusNode.removeListener(_onCommentFocusChanged);
discussTitleFocusNode.dispose();
titleInput.dispose();
......@@ -74,37 +75,40 @@ class ReadController extends FullLifeCycleController with GetSingleTickerProvide
void setShow(bool value) {
_show = !value;
if (_show) {
/// 显示状态栏
SystemChrome.setEnabledSystemUIMode(SystemUiMode.edgeToEdge);
/// 开启动画
_controller.forward();
}
else {
/// 不显示状态栏
// SystemChrome.setEnabledSystemUIMode(SystemUiMode.immersiveSticky);
/// 收回动画
_controller.reverse();
}
update();
}
// 添加讨论图片
void addDiscussInputImages(String path){
discussInputImages.add(path);
print('discussInputImages--------------------------------$path');
Console.log('discussInputImages--------------------------------$path');
update();
}
// 删除讨论图片
void delDiscussInputImages(String path){
discussInputImages.remove(path);
print('delDiscussInputImages--------------------------------$path');
Console.log('delDiscussInputImages--------------------------------$path');
update();
}
// 清空讨论图片
void clearDiscussInputImages(){
discussInputImages.clear();
print('clearDiscussInputImages--------------------------------');
Console.log('clearDiscussInputImages--------------------------------');
update();
}
// 情况语音
void clearDiscussAudios(){
discussInputAudios.clear();
Console.log('clearDiscussAudios--------------------------------');
update();
}
// 清空所有已经填写的数据
void clearAllDiscussInput(){
discussInputImages.clear();
discussInputAudios.clear();
titleInput.text = '';
contentInput.text = '';
Console.log('clearAllDiscussInput--------------------------------');
update();
}
......@@ -117,6 +121,81 @@ class ReadController extends FullLifeCycleController with GetSingleTickerProvide
return result;
}
// 提交
Future<bool> submit() async {
// 音频链接数组
List<String> audios = [];
// 图片链接数组
List<String> images = [];
// 循环上传图片获取地址
for(String path in discussInputImages){
final url = await upload(path: path);
images.add(url);
}
// 循环上传音频获取地址
for(String path in discussInputAudios){
final url = await upload(path: path);
audios.add(url);
}
Map<String,dynamic> contentMap = {
'text':contentInput.text,
'audio':audios,
'image':images
};
// 话题
if (chatType == 0){
final result = await addDiscuss();
return result;
}
// 笔记
else if (chatType == 1){
final result = addNote();
return result;
}
return false;
}
// 添加笔记
Future<bool> addNote() async {
// 音频链接数组
List<String> audios = [];
// 图片链接数组
List<String> images = [];
// 循环上传图片获取地址
for(String path in discussInputImages){
final url = await upload(path: path);
images.add(url);
}
// 循环上传音频获取地址
for(String path in discussInputAudios){
final url = await upload(path: path);
audios.add(url);
}
Map<String,dynamic> contentMap = {
'text':contentInput.text,
'audio':audios,
'image':images
};
final result = await LibraryAPI.addNote(
bookId: bookId,
chapterId: chapterId,
content: noteTitle,
isOpen: isPublic?'1':'0',
positioning: notePosition,
noteContent: jsonEncode(contentMap)
);
return result;
}
// 发表评论
// {String commentId ='0',String quoteContent ='',String title = ''}
Future<bool> addDiscuss() async{
......@@ -131,6 +210,12 @@ class ReadController extends FullLifeCycleController with GetSingleTickerProvide
images.add(url);
}
// 循环上传音频获取地址
for(String path in discussInputAudios){
final url = await upload(path: path);
audios.add(url);
}
Map<String,dynamic> contentMap = {
'text':contentInput.text,
'audio':audios,
......@@ -177,7 +262,7 @@ class ReadController extends FullLifeCycleController with GetSingleTickerProvide
}
AnimationController get controller => _controller;
// AnimationController get controller => _controller;
void _onCommentFocusChanged() {
if (discussTitleFocusNode.hasFocus || discussContentFocusNode.hasFocus) {
......@@ -187,10 +272,25 @@ class ReadController extends FullLifeCycleController with GetSingleTickerProvide
}
}
// 展示输入框
void setShowChat(bool value) {
showChat = value;
update();
}
// 显示输入框类型
void setChatType(int type){
chatType = type;
}
// 设置笔记是否公开
void setIsPublic(){
isPublic = !isPublic;
update();
}
// void setNoteTitle(String title){
// noteTitle = title;
// // update();
// }
......@@ -209,7 +309,7 @@ class ReadController extends FullLifeCycleController with GetSingleTickerProvide
// 当应用程序从后台切换到前台并变为活动状态时调用。这通常在用户从其他应用程序返回到你的应用程序时发生
void onResumed(){
print('onResumed');
Console.log('onResumed');
// open
// 上报开始阅读时间
_addReadTime(type: 'open');
......@@ -217,20 +317,20 @@ class ReadController extends FullLifeCycleController with GetSingleTickerProvide
// 当应用程序失去焦点并进入非活动状态时调用。这可能是因为用户切换到其他应用程序或将应用程序最小化
void onPaused(){
// close
print('onPaused');
Console.log('onPaused');
// 上报阅读结束时间
_addReadTime(type: 'close');
}
// 当应用程序失去焦点但仍然可见时调用。通常,在用户切换到另一个应用程序或显示系统对话框时,应用程序可能会处于非活动状态,但仍然是可见的
void onInactive(){
print('onInactive');
Console.log('onInactive');
// close
// 上报阅读结束时间
_addReadTime(type: 'close');
}
// 当应用程序被挂起,可能是由于用户关闭应用程序或系统资源不足时调用。在这个状态下,应用程序的代码将不再运行,并且可能被系统终止
void onDetached(){
print('onDetached');
Console.log('onDetached');
// close
// 上报阅读结束时间
_addReadTime(type: 'close');
......
......@@ -23,20 +23,30 @@ class _ReadPageState extends State<ReadPage> {
}
@override
Widget build(BuildContext context) {
print('+++++++++++++++++++++++${widget.bookId}');
return GetBuilder<ReadController>(
init: ReadController(bookId: widget.bookId, chapterId: widget.chapterId,chapterName: widget.chapterName),
builder: (readController) => Scaffold(
appBar: AppBar(
appBar: CustomAppBar(
title: Text(widget.chapterName),
centerTitle: false,
actions: [
GestureDetector(
onTap: () {
},
child: Text(
'离线阅读',
style: TextStyle(
fontSize: 14.w, color: Colours.c3),
))
],
),
resizeToAvoidBottomInset: false,
floatingActionButton: readController.show?GestureDetector(
onTap: (){
readController.setShowChat(true);
// FocusScope.of(context).requestFocus(readController.commentFocusNode);
readController.noteTitle = '你好你问你你等您第五年对哦in我ID呢哦win地哦为内地那打卡你打困哪';
readController.setChatType(1);
},
child: Image.asset('assets/images/chat.png'),
):null,
......@@ -55,20 +65,10 @@ class _ReadPageState extends State<ReadPage> {
initialUrlRequest: URLRequest(
url: Uri.parse('http://192.168.11.46:9200/read.html'),
),
onWebViewCreated: (InAppWebViewController controller) {
},
contextMenu: ContextMenu(
options: ContextMenuOptions(hideDefaultSystemContextMenuItems: true),
),
// onLoadStart: (InAppWebViewController controller, Uri? url) async {
//
// },
// onConsoleMessage: (controller, consoleMessage) {
// print("Received message from WebView: ${consoleMessage.message}");
// },
onLoadStop: (controller, url) {
// flutter 主动给 js 传参数
Map<String, dynamic> param = {
'book_id': 110,
......@@ -92,22 +92,20 @@ class _ReadPageState extends State<ReadPage> {
// 监听笔记回调
controller.addJavaScriptHandler(handlerName: 'noteCallBack', callback: (args){
print('----------------------noteCallBack--------------------------$args');
});
// 监听百科回调
controller.addJavaScriptHandler(handlerName: 'baikeCallBack', callback: (args){
print('----------------------baikeCallBack--------------------------$args');
context.pushNamed(Routes.baiKe,queryParameters: {'keyword':args});
});
// 监听讨论回调
controller.addJavaScriptHandler(handlerName: 'discussCallBack', callback: (args){
print('----------------------discussCallBack--------------------------$args');
readController.setShowChat(true);
readController.setChatType(0);
readController.titleInput.text = '11111';
});
},
),
// AnimatedPositioned(
......@@ -127,29 +125,70 @@ class _ReadPageState extends State<ReadPage> {
left: 0,
right: 0,
top: 0,
bottom: 49 + MediaQuery.of(context).viewInsets.bottom,
bottom: 49,
child: _showContent(readController,readController.toolModel)
),
AnimatedPositioned(
duration: readController.controller.duration!,
curve: Curves.easeInOut,
bottom: readController.show ? 0 : -49 - MediaQuery.of(context).viewInsets.bottom, // 负值隐藏,0 显示
left: 0,
right: 0,
height: 49,
child: Container(
color: Colors.limeAccent,
alignment: Alignment.center,
child: _createToolBar(readController)
),
// AnimatedPositioned(
// duration: readController.controller.duration!,
// curve: Curves.easeInOut,
// bottom: readController.show ? 0 : -49 - MediaQuery.of(context).viewInsets.bottom, // 负值隐藏,0 显示
// left: 0,
// right: 0,
// height: 49,
// child: Container(
// color: Colors.limeAccent,
// alignment: Alignment.center,
// child: _createToolBar(readController)
// ),
// ),
/// 底部工具栏布局
Visibility(
visible: readController.show,
child:Positioned(
left: 0,
right: 0,
bottom: 0,
child: SafeArea(
child: Container(
height: 49,
color: Colors.limeAccent,
alignment: Alignment.center,
child: _createToolBar(readController)
),
),
)
),
/// 悬浮按钮点击发起话题布局
Visibility(
visible: readController.showChat,
child: Positioned(
left: 0,
right: 0,
bottom:MediaQuery.of(context).viewInsets.bottom,
child: ReadInputDiscuss(controller: readController,)
top: 0,
bottom:0,
child: GestureDetector(
onTap: (){
readController.setShowChat(false);
readController.clearAllDiscussInput();
},
child: Container(
color: const Color(0xFF000000).withOpacity(0.5),
child: SingleChildScrollView(
reverse: true,
child: Container(
color: Colors.white,
padding: EdgeInsets.only(bottom: MediaQuery.of(context).viewInsets.bottom),
// alignment:Alignment.bottomCenter,
child: GestureDetector(
behavior: HitTestBehavior.opaque,
onTap: (){},
child: ReadInputDiscuss(controller: readController,)
)
),
),
),
)
),
),
],
......@@ -162,42 +201,40 @@ class _ReadPageState extends State<ReadPage> {
/// 目录 笔记 讨论 工具栏
Widget _createToolBar(ReadController controller){
return Container(
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceAround,
children: controller.tools.map((model){
return Expanded(
child: GestureDetector(
onTap: (){
controller.chooseTool(model);
},
child: Container(
color: Colors.white,
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
mainAxisSize: MainAxisSize.max,
children: [
SizedBox(
width: 17,
height: 17,
child: Image.asset(model.selected?model.activeIcon:model.icon)
),
// SizedBox(height: 2.5.w,),
model.selected?Text(model.name,style: TextStyle(fontSize: 10.w,height: 1.4,fontWeight: Fonts.medium,color: AppTheme.primary),)
:Text(model.name,style: TextStyle(fontSize: 10.w,height: 1.4,fontWeight: Fonts.medium,color: Colours.c6))
],
),
return Row(
mainAxisAlignment: MainAxisAlignment.spaceAround,
children: controller.tools.map((model){
return Expanded(
child: GestureDetector(
onTap: (){
controller.chooseTool(model);
},
child: Container(
color: Colors.white,
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
mainAxisSize: MainAxisSize.max,
children: [
SizedBox(
width: 17,
height: 17,
child: Image.asset(model.selected?model.activeIcon:model.icon)
),
// SizedBox(height: 2.5.w,),
model.selected?Text(model.name,style: TextStyle(fontSize: 10.w,height: 1.4,fontWeight: Fonts.medium,color: AppTheme.primary),)
:Text(model.name,style: TextStyle(fontSize: 10.w,height: 1.4,fontWeight: Fonts.medium,color: Colours.c6))
],
),
),
);
}).toList()
),
),
);
}).toList()
);
}
/// 目录、评论、笔记
Widget _showContent(ReadController controller,ToolModel model) {
print('++++++++++++++++++++++++${model.tag}');
Console.log('++++++++++++++++++++++++${model.tag}');
if (controller.show){
if (model.tag == 0){
return model.selected? Container(
......@@ -217,7 +254,7 @@ class _ReadPageState extends State<ReadPage> {
}
else if (model.tag == 1){
return model.selected? Container(
color: Color(0xFF000000).withOpacity(0.5),
color: const Color(0xFF000000).withOpacity(0.5),
padding: EdgeInsets.only(top: MediaQuery.of(context).size.height * 0.2),
child: ClipRRect(
borderRadius: BorderRadius.only(topRight: Radius.circular(8.w),topLeft: Radius.circular(8.w)),
......@@ -233,7 +270,7 @@ class _ReadPageState extends State<ReadPage> {
}
else if (model.tag == 2){
return model.selected? Container(
color: Color(0xFF000000).withOpacity(0.5),
color: const Color(0xFF000000).withOpacity(0.5),
padding: EdgeInsets.only(top: MediaQuery.of(context).size.height * 0.2),
child: ClipRRect(
borderRadius: BorderRadius.only(topRight: Radius.circular(8.w),topLeft: Radius.circular(8.w)),
......@@ -251,158 +288,10 @@ class _ReadPageState extends State<ReadPage> {
return const SizedBox();
}
Widget _discussInput(ReadController controller){
return Container(
margin: EdgeInsets.symmetric(horizontal: 15.w),
child: Column(
children: [
Container(
margin: EdgeInsets.symmetric(vertical: 10.w),
child: Row(
children: [
Text('话题',style: TextStyle(fontSize: 14.w,color: Colours.c3,height: 1.5,fontWeight: Fonts.medium),),
Gaps.hGaps5,
Expanded(
child: ClipRRect(
borderRadius: BorderRadius.circular(4),
child: TextField(
focusNode: controller.discussTitleFocusNode,
autofocus: true,
decoration: InputDecoration(
hintText: '请输入话题名称',
hintStyle:TextStyle(fontSize: 12.w,height: 1.5,color: Colours.c9,),
filled: true,
fillColor: Colours.cF8,
),
),
),
),
],
),
),
ClipRRect(
borderRadius: BorderRadius.circular(4.w),
child: Container(
color: Colours.cF8,
constraints: BoxConstraints(
minHeight: 100.w
),
child: Column(
children: [
TextField(
focusNode: controller.discussContentFocusNode,
maxLines: null,
decoration: InputDecoration(
border: InputBorder.none,
enabledBorder: InputBorder.none,
focusedBorder: InputBorder.none,
hintText: '请输入内容',
hintStyle:TextStyle(fontSize: 12.w,height: 1.5,color: Colours.c9,),
filled: true,
fillColor: Colours.cF8,
),
),
Column(
children: [
MediaQuery.removePadding(
context: context,
removeTop: true,
child: GridView.builder(
// padding: const EdgeInsets.only(left: 13,top: 10),
physics: const NeverScrollableScrollPhysics(),
shrinkWrap: true,
gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
crossAxisCount: 6,
crossAxisSpacing: 2.w,
mainAxisSpacing: 2.w,
childAspectRatio: 1
),
itemBuilder: (BuildContext context, int index) {
return Container(
color: Colors.red,
child: Center(child: Text('图片')),
);
},
itemCount: 0,
),
),
Container(
color: Colors.red,
child: MediaQuery.removePadding(
context: context,
removeTop: true,
child: ListView.builder(
physics: const NeverScrollableScrollPhysics(),
shrinkWrap: true,
itemBuilder: (BuildContext context, int index){
return Container(
height: 20.w,
margin: EdgeInsets.only(right: 130.w),
child: Container(
margin: EdgeInsets.only(top: 5.w),
padding: EdgeInsets.only(right:20.w,left: 10.w),
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(10.w),
color: Colours.cF9,
),
child: Row(
// mainAxisSize: MainAxisSize.min,
mainAxisAlignment:MainAxisAlignment.spaceBetween,
children: [
Image.asset('assets/images/audio.png'),
Text('0:00/1:52',style: TextStyle(fontSize: 10.w,height: 1.4,color: Colours.c9),)
],
),
),
);
},
itemCount: 0,
),
),
)
],
)
],
),
),
),
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
crossAxisAlignment: CrossAxisAlignment.center,
children: [
Row(
children: [
Image.asset('assets/images/read_add_img.png'),
Gaps.hGaps10,
Image.asset('assets/images/read_add_audio.png'),
],
),
GestureDetector(
onTap: (){},
child: Container(
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(15.w),
color: AppTheme.primary,
gradient: LinearGradient(
colors: [const Color(0xFFD53676).withOpacity(0.9),AppTheme.primary] , // 不可点击时的颜色,透明度为0.7
begin: Alignment.topCenter,
end: Alignment.bottomCenter,
),
),
padding: EdgeInsets.symmetric(horizontal:13.5.w,vertical: 4.w),
child: Text('发表',style: TextStyle(fontSize: 14.w,fontWeight: Fonts.medium,color: Colors.white),),
),
)
],
)
],
)
);
}
}
/// 定制 悬浮按钮位置
class MyFloatingActionButtonLocation extends FloatingActionButtonLocation {
@override
Offset getOffset(ScaffoldPrelayoutGeometry scaffoldGeometry) {
......@@ -415,24 +304,4 @@ class MyFloatingActionButtonLocation extends FloatingActionButtonLocation {
}
// class NoAnimationFabAnimator extends FloatingActionButtonAnimator {
// // 通过构造函数参数提供动画
// const NoAnimationFabAnimator();
//
// @override
// Animation<double> getRotationAnimation({required Animation<double> parent}) {
// return AlwaysStoppedAnimation<double>(0);
// }
//
// @override
// Animation<double> getScaleAnimation({required Animation<double> parent}) {
// return AlwaysStoppedAnimation<double>(1);
// }
//
// @override
// Offset getOffset({required Offset begin, required Offset end, required double progress}) {
// return begin;
// }
// }
......@@ -15,13 +15,13 @@ class _ReadInputDiscussState extends State<ReadInputDiscuss> {
@override
Widget build(BuildContext context) {
return Container(
color: Colors.green,
color: Colors.white,
margin: EdgeInsets.symmetric(horizontal: 15.w),
child: Column(
children: [
Container(
margin: EdgeInsets.symmetric(vertical: 10.w),
child: Row(
child: widget.controller.chatType ==0?Row(
children: [
Text('话题',style: TextStyle(fontSize: 14.w,color: Colours.c3,height: 1.5,fontWeight: Fonts.medium),),
Gaps.hGaps5,
......@@ -41,11 +41,14 @@ class _ReadInputDiscussState extends State<ReadInputDiscuss> {
filled: true,
fillColor: Colours.cF8,
),
onSubmitted: (_){
FocusScope.of(context).requestFocus(widget.controller.discussContentFocusNode);
},
),
),
),
],
),
):Text('"${widget.controller.noteTitle}"',style: TextStyle(fontSize: 12.w,height: 1.4,color: Colours.c9),),
),
ClipRRect(
borderRadius: BorderRadius.circular(4.w),
......@@ -59,6 +62,7 @@ class _ReadInputDiscussState extends State<ReadInputDiscuss> {
TextField(
focusNode: widget.controller.discussContentFocusNode,
maxLines: null,
autofocus: widget.controller.chatType ==0?false:true,
controller: widget.controller.contentInput,
decoration: InputDecoration(
border: InputBorder.none,
......@@ -174,11 +178,25 @@ class _ReadInputDiscussState extends State<ReadInputDiscuss> {
},
child: Image.asset('assets/images/read_add_audio.png')
),
widget.controller.chatType ==0?const SizedBox():GestureDetector(
onTap: (){
widget.controller.setIsPublic();
},
child: Row(
crossAxisAlignment: CrossAxisAlignment.center,
children: [
Gaps.hGaps10,
Image.asset(widget.controller.isPublic?'assets/images/pay_check.png':'assets/images/pay_uncheck.png'),
SizedBox(width: 3.w,),
Text('公开',style: TextStyle(fontSize: 13.w,height: 1.3,color: Colours.c9),)
],
),
)
],
),
GestureDetector(
onTap: (){
widget.controller.addDiscuss();
widget.controller.submit();
},
child: Container(
decoration: BoxDecoration(
......
......@@ -206,6 +206,7 @@ class _RequestInterceptor extends Interceptor {
var msg = '服务器错误';
switch (statusCode) {
case 403:
print('-------------------access_token-------------------------${UserStore.to.accessToken}--------------------');
msg = '$statusCode - Unauthorized';
final newToken = await refreshToken();
if (newToken != null) {
......@@ -233,6 +234,8 @@ class _RequestInterceptor extends Interceptor {
} else {
UserStore.to.logout();
CustomToast.fail('登录已失效,请重新登录');
print('-------------------access_token-------------------------${UserStore.to.accessToken}--------------------');
}
break;
case 404:
......
......@@ -19,7 +19,7 @@ class CustomCard extends StatelessWidget {
Widget build(BuildContext context) {
return Container(
decoration: BoxDecoration(
color: Colors.cyan,
color: Colors.white,
borderRadius: borderRadius ?? BorderRadius.circular(3),
boxShadow: boxShadow ?? [
BoxShadow(
......@@ -33,7 +33,7 @@ class CustomCard extends StatelessWidget {
height: height,
width: width,
child: Container(
padding: const EdgeInsets.all(1),
padding: const EdgeInsets.all(2),
child: CustomImage.network(url: url,placeholder: Image.asset('assets/images/book_placeholder.png'),),
),
);
......
Markdown 格式
0%
您添加了 0 到此讨论。请谨慎行事。
请先完成此评论的编辑!
注册 或者 后发表评论