finish 123

This commit is contained in:
wzp 2024-09-05 20:18:11 +08:00
parent 2bcc22bfe1
commit 0ea11b60fc
10 changed files with 529 additions and 9 deletions

View File

@ -1,7 +1,13 @@
import 'dart:convert';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:model_of_the_times/entities/ViewInformation.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/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/home.dart';
import 'package:model_of_the_times/views/learned.dart';
void main() { void main() {
runApp(const MyApp()); runApp(const MyApp());
@ -16,10 +22,19 @@ class MyApp extends StatelessWidget {
return MaterialApp( return MaterialApp(
title: '时代楷模', title: '时代楷模',
theme: ThemeData( theme: ThemeData(
colorScheme: ColorScheme.fromSeed(seedColor: Colors.white), colorScheme: ColorScheme.fromSeed(seedColor: Colors.red, surface: Colors.white),
useMaterial3: true, 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<MainPage> { class _MainPageState extends State<MainPage> {
int _nowPageIndex = 0; int _nowPageIndex = 0;
final List<ViewInformation> _pages = [ final List<ViewInformation> _pages = [
ViewInformation(const HomeView(), "时代楷模") ViewInformation(const HomeView(), "时代楷模"),
ViewInformation(const Activity(), "公益活动"),
ViewInformation(const Learned(), "学习心得")
]; ];
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
@ -52,10 +69,14 @@ class _MainPageState extends State<MainPage> {
body: page.view, body: page.view,
bottomNavigationBar: BottomNavigationBar( bottomNavigationBar: BottomNavigationBar(
onTap: (newIndex){ onTap: (newIndex){
if (newIndex >= _pages.length) {
return;
}
setState(() { setState(() {
_nowPageIndex = newIndex; _nowPageIndex = newIndex;
}); });
}, },
currentIndex: _nowPageIndex,
type: BottomNavigationBarType.fixed, type: BottomNavigationBarType.fixed,
items: const [ items: const [
BottomNavigationBarItem( BottomNavigationBarItem(
@ -64,7 +85,7 @@ class _MainPageState extends State<MainPage> {
), ),
BottomNavigationBarItem( BottomNavigationBarItem(
icon: Icon(MaterialDesign.feedback), icon: Icon(MaterialDesign.feedback),
label: "" label: ""
), ),
BottomNavigationBarItem( BottomNavigationBarItem(
icon: Icon(MaterialDesign.favorite), icon: Icon(MaterialDesign.favorite),

View File

@ -10,7 +10,7 @@ class CustomRequesterClient extends BaseClient {
if (information.token.isNotEmpty){ if (information.token.isNotEmpty){
request.headers.addAll({"Authorization": information.token}); 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"; request.headers['Content-Type'] = "application/json";
} }
return client.send(request); return client.send(request);
@ -37,3 +37,7 @@ dynamic jsonFromResponse(Response response) {
var str = utf8.decode(response.bodyBytes); var str = utf8.decode(response.bodyBytes);
return jsonDecode(str); return jsonDecode(str);
} }
dynamic jsonFromStreamResponse(StreamedResponse response) async {
var str = utf8.decode(await response.stream.toBytes());
return jsonDecode(str);
}

5
lib/utils/toast.dart Normal file
View File

@ -0,0 +1,5 @@
import 'package:flutter/material.dart';
void toast(String message, BuildContext context){
ScaffoldMessenger.of(context).showSnackBar(SnackBar(content: Text(message)));
}

196
lib/views/activity.dart Normal file
View File

@ -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<ActivityCard> createState() => _ActivityCardState();
}
class _ActivityCardState extends State<ActivityCard> {
@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<Activity> createState() => _ActivityState();
}
class _ActivityState extends State<Activity> {
@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<Widget>((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<ActivityDetails> createState() => _ActivityDetailsState();
}
class _ActivityDetailsState extends State<ActivityDetails> {
@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("报名")
)
],
),
)
],
);
}
)
);
}
}

View File

@ -11,10 +11,10 @@ class DataRequired extends StatefulWidget {
const DataRequired({super.key, required this.fetchData, required this.afterLoading, this.height = -1, this.width = -1}); const DataRequired({super.key, required this.fetchData, required this.afterLoading, this.height = -1, this.width = -1});
@override @override
State<DataRequired> createState() => _DataRequiredState(); State<DataRequired> createState() => DataRequiredState();
} }
class _DataRequiredState extends State<DataRequired> { class DataRequiredState extends State<DataRequired> {
bool isLoading = true; bool isLoading = true;
dynamic data = {}; dynamic data = {};
void _fetch() async { void _fetch() async {
@ -70,4 +70,8 @@ class _DataRequiredState extends State<DataRequired> {
} }
return const Center(child: Text("加载失败,网络错误!")); return const Center(child: Text("加载失败,网络错误!"));
} }
void update() {
_fetch();
}
} }

View File

@ -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),
);
}
}

View File

@ -53,8 +53,6 @@ class _HomeViewState extends State<HomeView> {
bool onChange(index) { bool onChange(index) {
var func = functions[index]; var func = functions[index];
var ret = func(context); var ret = func(context);
print(func);
print(ret);
if (ret != null){ if (ret != null){
setState(() { setState(() {
lastWidget = ret; lastWidget = ret;

263
lib/views/learned.dart Normal file
View File

@ -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<Learned> createState() => _LearnedState();
}
class _LearnedState extends State<Learned> with SingleTickerProviderStateMixin {
late final TabController _controller;
int _index = 0;
List<Widget> 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<DataRequiredState> parentKey;
const Testimonial({super.key, required this.id, required this.title, required this.content, required this.parentKey});
@override
State<Testimonial> createState() => _TestimonialState();
}
class _TestimonialState extends State<Testimonial> {
@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<Testimonials> createState() => _TestimonialsState();
}
class _TestimonialsState extends State<Testimonials> {
final GlobalKey<DataRequiredState> _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<Widget>((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<TestimonialsEditor> createState() => _TestimonialsEditorState();
}
class _TestimonialsEditorState extends State<TestimonialsEditor> {
final _formKey = GlobalKey<FormState>();
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<AddTestimonials> createState() => _AddTestimonialsState();
}
class _AddTestimonialsState extends State<AddTestimonials> {
@override
Widget build(BuildContext context) {
return const FullPagedStruct(title: "新建感言", child: TestimonialsEditor());
}
}
class LearnHistory extends StatefulWidget {
const LearnHistory({super.key});
@override
State<LearnHistory> createState() => _LearnHistoryState();
}
class _LearnHistoryState extends State<LearnHistory> {
@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();
}
);
}
}

View File

@ -70,6 +70,14 @@ packages:
url: "https://pub.flutter-io.cn" url: "https://pub.flutter-io.cn"
source: hosted source: hosted
version: "3.0.2" 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: flutter_test:
dependency: "direct dev" dependency: "direct dev"
description: flutter description: flutter

View File

@ -36,6 +36,7 @@ dependencies:
# Use with the CupertinoIcons class for iOS style icons. # Use with the CupertinoIcons class for iOS style icons.
cupertino_icons: ^1.0.6 cupertino_icons: ^1.0.6
http: ^1.2.2 http: ^1.2.2
flutter_slidable: ^3.1.1
dev_dependencies: dev_dependencies:
flutter_test: flutter_test: