From 0ea11b60fc8b100d651197baed1f8ad27fc7c255 Mon Sep 17 00:00:00 2001 From: wzp Date: Thu, 5 Sep 2024 20:18:11 +0800 Subject: [PATCH] finish 123 --- lib/main.dart | 29 ++- lib/requester/requester.dart | 6 +- lib/utils/toast.dart | 5 + lib/views/activity.dart | 196 ++++++++++++++++++ lib/views/common/data_required.dart | 8 +- lib/views/common/full_paged_struct.dart | 20 ++ lib/views/home.dart | 2 - lib/views/learned.dart | 263 ++++++++++++++++++++++++ pubspec.lock | 8 + pubspec.yaml | 1 + 10 files changed, 529 insertions(+), 9 deletions(-) create mode 100644 lib/utils/toast.dart create mode 100644 lib/views/activity.dart create mode 100644 lib/views/common/full_paged_struct.dart create mode 100644 lib/views/learned.dart diff --git a/lib/main.dart b/lib/main.dart index ff54898..249d7a7 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -1,7 +1,13 @@ +import 'dart:convert'; + import 'package:flutter/material.dart'; import 'package:model_of_the_times/entities/ViewInformation.dart'; import 'package:model_of_the_times/icons/material_design_icons.dart'; +import 'package:model_of_the_times/requester/requester.dart'; +import 'package:model_of_the_times/views/activity.dart'; +import 'package:model_of_the_times/views/common/data_required.dart'; import 'package:model_of_the_times/views/home.dart'; +import 'package:model_of_the_times/views/learned.dart'; void main() { runApp(const MyApp()); @@ -16,10 +22,19 @@ class MyApp extends StatelessWidget { return MaterialApp( title: '时代楷模', theme: ThemeData( - colorScheme: ColorScheme.fromSeed(seedColor: Colors.white), + colorScheme: ColorScheme.fromSeed(seedColor: Colors.red, surface: Colors.white), useMaterial3: true, ), - home: const MainPage(), + home: DataRequired( + fetchData: (global) async { + var data = await global.requester.post(resolve("/app/login"), body: jsonEncode({'username': 'WUvFG3gY','password': 'ZogPgBF6'})); + return jsonFromResponse(data); + }, + afterLoading: (data){ + GlobalInformation.getInstance().token = data['token']; + return const MainPage(); + } + ) ); } } @@ -34,7 +49,9 @@ class MainPage extends StatefulWidget { class _MainPageState extends State { int _nowPageIndex = 0; final List _pages = [ - ViewInformation(const HomeView(), "时代楷模") + ViewInformation(const HomeView(), "时代楷模"), + ViewInformation(const Activity(), "公益活动"), + ViewInformation(const Learned(), "学习心得") ]; @override Widget build(BuildContext context) { @@ -52,10 +69,14 @@ class _MainPageState extends State { body: page.view, bottomNavigationBar: BottomNavigationBar( onTap: (newIndex){ + if (newIndex >= _pages.length) { + return; + } setState(() { _nowPageIndex = newIndex; }); }, + currentIndex: _nowPageIndex, type: BottomNavigationBarType.fixed, items: const [ BottomNavigationBarItem( @@ -64,7 +85,7 @@ class _MainPageState extends State { ), BottomNavigationBarItem( icon: Icon(MaterialDesign.feedback), - label: "公告" + label: "公益" ), BottomNavigationBarItem( icon: Icon(MaterialDesign.favorite), diff --git a/lib/requester/requester.dart b/lib/requester/requester.dart index 63289e8..1844e1b 100644 --- a/lib/requester/requester.dart +++ b/lib/requester/requester.dart @@ -10,7 +10,7 @@ class CustomRequesterClient extends BaseClient { if (information.token.isNotEmpty){ request.headers.addAll({"Authorization": information.token}); } - if (request.headers.containsKey("Content-Type")){ + if (request.headers['Content-Type'] == 'text/plain; charset=utf-8'){ request.headers['Content-Type'] = "application/json"; } return client.send(request); @@ -36,4 +36,8 @@ Uri resolve(String url){ dynamic jsonFromResponse(Response response) { var str = utf8.decode(response.bodyBytes); return jsonDecode(str); +} +dynamic jsonFromStreamResponse(StreamedResponse response) async { + var str = utf8.decode(await response.stream.toBytes()); + return jsonDecode(str); } \ No newline at end of file diff --git a/lib/utils/toast.dart b/lib/utils/toast.dart new file mode 100644 index 0000000..8a19852 --- /dev/null +++ b/lib/utils/toast.dart @@ -0,0 +1,5 @@ +import 'package:flutter/material.dart'; + +void toast(String message, BuildContext context){ + ScaffoldMessenger.of(context).showSnackBar(SnackBar(content: Text(message))); +} \ No newline at end of file diff --git a/lib/views/activity.dart b/lib/views/activity.dart new file mode 100644 index 0000000..2225183 --- /dev/null +++ b/lib/views/activity.dart @@ -0,0 +1,196 @@ +import 'package:flutter/foundation.dart'; +import 'package:flutter/material.dart'; +import 'package:model_of_the_times/requester/requester.dart'; +import 'package:model_of_the_times/utils/toast.dart'; +import 'package:model_of_the_times/views/common/data_required.dart'; +import 'package:model_of_the_times/views/common/full_paged_struct.dart'; + +class ActivityCard extends StatefulWidget { + final String picPath; + final int id; + final String startDate; + final String endDate; + final int status; + final String title; + final String content; + final String sponsor; + final int signUpNum; + const ActivityCard({super.key, required this.picPath, required this.id, required this.startDate, required this.endDate, required this.status, required this.title, required this.content, required this.sponsor, required this.signUpNum}); + + @override + State createState() => _ActivityCardState(); +} + +class _ActivityCardState extends State { + @override + Widget build(BuildContext context) { + return Card( + child: Padding(padding: const EdgeInsets.all(10), child: Column( + children: [ + Image.network(resolve(widget.picPath).toString(), height: 100, fit: BoxFit.fitHeight), + ListTile( + title: Text(widget.title), + subtitle: Column( + children: [ + Text("活动时间:${widget.startDate} - ${widget.endDate}"), + Text("发起方:${widget.sponsor}"), + Text("简介:${widget.content}", maxLines: 1, overflow: TextOverflow.ellipsis) + ], + ), + ), + const Divider(), + Flex( + direction: Axis.horizontal, + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Flex( + direction: Axis.horizontal, + children: [ + const Icon(Icons.add), + Text("已报名${widget.signUpNum}人") + ], + ), + OutlinedButton(onPressed: (){ + if (widget.status == 1) { + Navigator.push(context, MaterialPageRoute(builder: (context) => ActivityDetails(id: widget.id))); + } + }, child: Text(widget.status == 1 ? "去报名" : widget.status == 2 ? "报名截止" : "已报名")) + ], + ) + ], + )), + ); + } +} + + + +class Activity extends StatefulWidget { + const Activity({super.key}); + + @override + State createState() => _ActivityState(); +} + +class _ActivityState extends State { + @override + Widget build(BuildContext context) { + return DataRequired( + fetchData: (global) async { + var req = global.requester; + var response = await req.get(resolve("/activity/app-o/list")); + return jsonFromResponse(response); + }, + afterLoading: (data) { + if (kDebugMode) { + print(data); + } + data['rows'].sort((a,b){return (a['status'] - b['status'] )as int;}); + return ListView( + children: data['rows'].map((e) { + return Container( + margin: const EdgeInsets.only(top: 10), + child: ActivityCard(picPath: e['picPath'], id: e['id'], startDate: e['startDate'], endDate: e['endDate'], status: e['status'], title: e['title'], content: e['content'], sponsor: e['sponsor'], signUpNum: e['signUpNum']), + ); + }).toList(), + ); + } + ); + } +} + +class ActivityDetails extends StatefulWidget { + final int id; + const ActivityDetails({super.key, required this.id}); + + @override + State createState() => _ActivityDetailsState(); +} + +class _ActivityDetailsState extends State { + @override + Widget build(BuildContext context) { + return FullPagedStruct( + title: "活动详情", + child: DataRequired( + fetchData: (global) async { + return jsonFromResponse(await global.requester.get(resolve("/activity/app-o/detail?id=${widget.id}"))); + }, + afterLoading: (data) { + data = data['data']; + return Stack( + children: [ + ListView( + children: [ + Image.network(resolve(data['picPath']).toString()), + ListTile( + title: Text(data['title']), + subtitle: Column( + children: [ + Text("活动时间:${data['startDate']} - ${data['endDate']}"), + Text("报名截止时间:${data['signUpEndDate']}"), + Text("发起方:${data['sponsor']}"), + ], + ), + ), + const Divider(), + Flex( + direction: Axis.vertical, + children: [ + const Flex( + direction: Axis.horizontal, + children: [ + SizedBox( + height: 20, + child: VerticalDivider(thickness: 8), + ), + Text("活动详情"), + ], + ), + Text(data['content']) + ], + ) + ], + ), + Positioned.fill( + bottom: 0, + child: Flex( + direction: Axis.horizontal, + mainAxisAlignment: MainAxisAlignment.spaceBetween, + crossAxisAlignment: CrossAxisAlignment.end, + children: [ + Flex( + direction: Axis.horizontal, + children: [ + Text(data['signUpNum'].toString()), + const Text("人已报名") + ], + ), + TextButton( + onPressed: () async { + var data = jsonFromResponse(await GlobalInformation.getInstance().requester.get(resolve("/activity/app/signUp?id=${widget.id}"))); + if (kDebugMode) { + print(data); + } + if (data['code'] == 200){ + if (context.mounted){ + Navigator.pop(context); + return; + } + } + if (context.mounted){ + toast("报名失败,请重试!", context); + } + }, + child: const Text("报名") + ) + ], + ), + ) + ], + ); + } + ) + ); + } +} diff --git a/lib/views/common/data_required.dart b/lib/views/common/data_required.dart index a96f13b..d557704 100644 --- a/lib/views/common/data_required.dart +++ b/lib/views/common/data_required.dart @@ -11,10 +11,10 @@ class DataRequired extends StatefulWidget { const DataRequired({super.key, required this.fetchData, required this.afterLoading, this.height = -1, this.width = -1}); @override - State createState() => _DataRequiredState(); + State createState() => DataRequiredState(); } -class _DataRequiredState extends State { +class DataRequiredState extends State { bool isLoading = true; dynamic data = {}; void _fetch() async { @@ -70,4 +70,8 @@ class _DataRequiredState extends State { } return const Center(child: Text("加载失败,网络错误!")); } + + void update() { + _fetch(); + } } diff --git a/lib/views/common/full_paged_struct.dart b/lib/views/common/full_paged_struct.dart new file mode 100644 index 0000000..3779f51 --- /dev/null +++ b/lib/views/common/full_paged_struct.dart @@ -0,0 +1,20 @@ +import 'package:flutter/material.dart'; +import 'package:model_of_the_times/icons/material_design_icons.dart'; + +class FullPagedStruct extends StatelessWidget { + final Widget child; + final String title; + const FullPagedStruct({super.key, required this.child, required this.title}); + + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: AppBar( + leading: IconButton(onPressed: (){Navigator.pop(context);}, icon: const Icon(MaterialDesign.keyboard_arrow_left)), + title: Text(title), + centerTitle: true, + ), + body: Padding(padding: const EdgeInsets.all(10), child: child), + ); + } +} diff --git a/lib/views/home.dart b/lib/views/home.dart index 04b68e9..fdd38c0 100644 --- a/lib/views/home.dart +++ b/lib/views/home.dart @@ -53,8 +53,6 @@ class _HomeViewState extends State { bool onChange(index) { var func = functions[index]; var ret = func(context); - print(func); - print(ret); if (ret != null){ setState(() { lastWidget = ret; diff --git a/lib/views/learned.dart b/lib/views/learned.dart new file mode 100644 index 0000000..ffc4c4a --- /dev/null +++ b/lib/views/learned.dart @@ -0,0 +1,263 @@ +import 'package:flutter/foundation.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_slidable/flutter_slidable.dart'; +import 'package:http/http.dart'; +import 'package:model_of_the_times/requester/requester.dart'; +import 'package:model_of_the_times/utils/toast.dart'; +import 'package:model_of_the_times/views/common/data_required.dart'; +import 'package:model_of_the_times/views/common/full_paged_struct.dart'; + +class Learned extends StatefulWidget { + const Learned({super.key}); + + @override + State createState() => _LearnedState(); +} + +class _LearnedState extends State with SingleTickerProviderStateMixin { + late final TabController _controller; + int _index = 0; + List items = [ + const Testimonials(), + const LearnHistory() + ]; + @override + void initState() { + super.initState(); + _controller = TabController(length: 2, vsync: this); + } + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: TabBar( + controller: _controller, + indicatorSize: TabBarIndicatorSize.tab, + labelPadding: const EdgeInsetsDirectional.all(10), + tabs: const [ + Text("学习感言"), + Text("学习历史"), + ], + onTap: (e){ + setState(() { + _index = e; + }); + }, + ), + body: items[_index], + floatingActionButton: _index == 0 ? FloatingActionButton.extended(onPressed: (){Navigator.push(context, MaterialPageRoute(builder: (BuildContext context) { return const AddTestimonials(); }));}, shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(10)), label: const Text("新建感言")) : null, + floatingActionButtonLocation: FloatingActionButtonLocation.centerFloat, + ); + } +} + +class Testimonial extends StatefulWidget { + final int id; + final String title; + final String content; + final GlobalKey parentKey; + const Testimonial({super.key, required this.id, required this.title, required this.content, required this.parentKey}); + + @override + State createState() => _TestimonialState(); +} + +class _TestimonialState extends State { + @override + Widget build(BuildContext context) { + return Slidable( + endActionPane: ActionPane( + motion: const ScrollMotion(), + children: [ + SlidableAction( + onPressed: (e){ + var req = GlobalInformation.getInstance().requester; + req.get(resolve("/appStudy/app/deleteStatement?id=${widget.id}")).then((value) { + var data = jsonFromResponse(value); + if (data['code'] == 200){ + widget.parentKey.currentState?.update(); + return; + } + if (context.mounted){ + toast("message", context); + } + }); + }, + backgroundColor: Colors.red, + label: "删除", + spacing: 1, + ) + ] + ), + child: Card( + child: Padding(padding: const EdgeInsets.all(10), child: ListTile( + title: Text( + widget.title + ), + subtitle: Text( + widget.content, + maxLines: 3, + overflow: TextOverflow.ellipsis, + ), + )), + ) + ); + } +} + + + +class Testimonials extends StatefulWidget { + const Testimonials({super.key}); + + @override + State createState() => _TestimonialsState(); +} + +class _TestimonialsState extends State { + final GlobalKey _key = GlobalKey(); + @override + Widget build(BuildContext context) { + return DataRequired( + key: _key, + fetchData: (global) async { + var response = await global.requester.get(resolve("/appStudy/app/statementList")); + return jsonFromResponse(response); + }, + afterLoading: (data) { + return ListView( + children: data['rows'].map((e){ + var content = e['content']; + var id = e['id']; + var title = e['title']; + return Testimonial(id: id, title: title, content: content, parentKey: _key); + }).toList() + ); + } + ); + } +} + +class TestimonialsEditor extends StatefulWidget { + final String defaultTitle; + final String defaultContent; + const TestimonialsEditor({super.key, this.defaultContent = "", this.defaultTitle = ""}); + + @override + State createState() => _TestimonialsEditorState(); +} + +class _TestimonialsEditorState extends State { + final _formKey = GlobalKey(); + late String _title; + late String _content; + @override + void initState() { + super.initState(); + _title = widget.defaultTitle; + _content = widget.defaultContent; + } + @override + Widget build(BuildContext context) { + return Form( + key: _formKey, + child: ListView( + children: [ + TextFormField( + initialValue: _title, + decoration: const InputDecoration( + labelText: "标题" + ), + validator: (value){ + if (value == null || value.isEmpty){ + return "请输入标题"; + } + return null; + }, + onSaved: (value) {if (value != null){_title = value;}}, + ), + TextFormField( + initialValue: _content, + decoration: const InputDecoration( + labelText: "内容" + ), + validator: (value) { + if (value == null || value.isEmpty){ + return "请输入内容"; + } + return null; + }, + onSaved: (value) {if (value != null){_content = value;}}, + ), + SizedBox.fromSize(size: const Size.fromHeight(20),), + ElevatedButton( + onPressed: () async { + if (_formKey.currentState == null){ + return; + } + if (!_formKey.currentState!.validate()) { + return; + } + _formKey.currentState!.save(); + var global = GlobalInformation.getInstance(); + var createUri = resolve("appStudy/app/createStatement"); + var req = MultipartRequest("POST", createUri); + req.fields.addAll({"title": _title, "content": _content, "picPath": ""}); + var result = await global.requester.send(req); + var data = await jsonFromStreamResponse(result); + if (context.mounted){ + if (data['code'] != 200){ + toast("添加失败,请重试!", context); + } + Navigator.pop(context); + } + }, + child: const Text("提交") + ) + ], + ), + ); + } +} + + +class AddTestimonials extends StatefulWidget { + const AddTestimonials({super.key}); + + @override + State createState() => _AddTestimonialsState(); +} + +class _AddTestimonialsState extends State { + @override + Widget build(BuildContext context) { + return const FullPagedStruct(title: "新建感言", child: TestimonialsEditor()); + } +} + + + +class LearnHistory extends StatefulWidget { + const LearnHistory({super.key}); + + @override + State createState() => _LearnHistoryState(); +} + +class _LearnHistoryState extends State { + @override + Widget build(BuildContext context) { + return DataRequired( + fetchData: (global) async { + var req = global.requester; + var historyResponse = await req.get(resolve("/appStudy/app/historyList")); + return jsonFromResponse(historyResponse); + }, + afterLoading: (data){ + if (kDebugMode) { + print(data); + } + return ListView(); + } + ); + } +} diff --git a/pubspec.lock b/pubspec.lock index 9611616..dcc0c72 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -70,6 +70,14 @@ packages: url: "https://pub.flutter-io.cn" source: hosted version: "3.0.2" + flutter_slidable: + dependency: "direct main" + description: + name: flutter_slidable + sha256: "2c5611c0b44e20d180e4342318e1bbc28b0a44ad2c442f5df16962606fd3e8e3" + url: "https://pub.flutter-io.cn" + source: hosted + version: "3.1.1" flutter_test: dependency: "direct dev" description: flutter diff --git a/pubspec.yaml b/pubspec.yaml index c4ee9e9..5b4d263 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -36,6 +36,7 @@ dependencies: # Use with the CupertinoIcons class for iOS style icons. cupertino_icons: ^1.0.6 http: ^1.2.2 + flutter_slidable: ^3.1.1 dev_dependencies: flutter_test: