├── lib ├── route │ └── routers.dart ├── models │ ├── reply.dart │ ├── topic.dart │ ├── vo.dart │ ├── response.dart │ ├── user.dart │ ├── comment.dart │ ├── user_message.dart │ └── moving.dart ├── utils │ ├── convert_server_data.dart │ ├── bju_timeline_util.dart │ └── db_util.dart ├── settings │ └── settings.dart ├── providers │ ├── bju_app_provider.dart │ └── login_provider.dart ├── pages │ ├── details │ │ ├── person_moving_details.dart │ │ └── message_details.dart │ ├── browser.dart │ ├── back_management │ │ └── management.dart │ ├── message_page.dart │ ├── modules │ │ ├── campus_voice.dart │ │ ├── part_time_recruitment.dart │ │ └── wall_and_you.dart │ ├── main_page.dart │ ├── at_choose_page.dart │ ├── mine │ │ ├── draft_moving_page.dart │ │ ├── all_moving_page.dart │ │ └── safty_setting_page.dart │ └── login_page.dart ├── constants │ └── bju_constant.dart ├── main.dart ├── net │ └── bju_net.dart └── api │ └── api.dart ├── ios ├── Flutter │ ├── Debug.xcconfig │ ├── Release.xcconfig │ └── AppFrameworkInfo.plist ├── Runner │ ├── Runner-Bridging-Header.h │ ├── Assets.xcassets │ │ ├── LaunchImage.imageset │ │ │ ├── LaunchImage.png │ │ │ ├── LaunchImage@2x.png │ │ │ ├── LaunchImage@3x.png │ │ │ ├── README.md │ │ │ └── Contents.json │ │ └── AppIcon.appiconset │ │ │ ├── Icon-App-20x20@1x.png │ │ │ ├── Icon-App-20x20@2x.png │ │ │ ├── Icon-App-20x20@3x.png │ │ │ ├── Icon-App-29x29@1x.png │ │ │ ├── Icon-App-29x29@2x.png │ │ │ ├── Icon-App-29x29@3x.png │ │ │ ├── Icon-App-40x40@1x.png │ │ │ ├── Icon-App-40x40@2x.png │ │ │ ├── Icon-App-40x40@3x.png │ │ │ ├── Icon-App-60x60@2x.png │ │ │ ├── Icon-App-60x60@3x.png │ │ │ ├── Icon-App-76x76@1x.png │ │ │ ├── Icon-App-76x76@2x.png │ │ │ ├── Icon-App-1024x1024@1x.png │ │ │ ├── Icon-App-83.5x83.5@2x.png │ │ │ └── Contents.json │ ├── AppDelegate.swift │ ├── Base.lproj │ │ ├── Main.storyboard │ │ └── LaunchScreen.storyboard │ └── Info.plist ├── Runner.xcworkspace │ └── contents.xcworkspacedata ├── Runner.xcodeproj │ ├── project.xcworkspace │ │ └── contents.xcworkspacedata │ └── xcshareddata │ │ └── xcschemes │ │ └── Runner.xcscheme └── .gitignore ├── assets ├── gif │ ├── loading.gif │ └── loading.jpg ├── avatar │ ├── bju_xh.jpg │ └── default_avatar.jpg ├── icon │ └── bju_app.png ├── swiper │ ├── bju_1.jpg │ ├── bju_2.jpg │ ├── bju_3.jpg │ └── bju_4.jpg ├── splash │ └── bju_splash.gif └── background_imgs │ ├── bg_login.png │ └── bg_personal.jpg ├── android ├── gradle.properties ├── app │ ├── src │ │ ├── main │ │ │ ├── res │ │ │ │ ├── drawable │ │ │ │ │ ├── notice_icon.png │ │ │ │ │ └── launch_background.xml │ │ │ │ ├── mipmap-hdpi │ │ │ │ │ ├── bju_app.png │ │ │ │ │ └── ic_launcher.png │ │ │ │ ├── mipmap-mdpi │ │ │ │ │ └── ic_launcher.png │ │ │ │ ├── mipmap-xhdpi │ │ │ │ │ └── ic_launcher.png │ │ │ │ ├── mipmap-xxhdpi │ │ │ │ │ └── ic_launcher.png │ │ │ │ ├── mipmap-xxxhdpi │ │ │ │ │ └── ic_launcher.png │ │ │ │ └── values │ │ │ │ │ └── styles.xml │ │ │ ├── kotlin │ │ │ │ └── com │ │ │ │ │ └── example │ │ │ │ │ └── bju_information_app │ │ │ │ │ └── MainActivity.kt │ │ │ └── AndroidManifest.xml │ │ ├── debug │ │ │ └── AndroidManifest.xml │ │ └── profile │ │ │ └── AndroidManifest.xml │ ├── proguard-rules.pro │ └── build.gradle ├── .gitignore ├── key.properties ├── gradle │ └── wrapper │ │ └── gradle-wrapper.properties ├── settings.gradle └── build.gradle ├── readme └── 功能分析.md ├── .metadata ├── .gitignore ├── widgets └── bju_widgets.dart ├── test └── widget_test.dart ├── README.md └── pubspec.yaml /lib/route/routers.dart: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /ios/Flutter/Debug.xcconfig: -------------------------------------------------------------------------------- 1 | #include "Generated.xcconfig" 2 | -------------------------------------------------------------------------------- /ios/Flutter/Release.xcconfig: -------------------------------------------------------------------------------- 1 | #include "Generated.xcconfig" 2 | -------------------------------------------------------------------------------- /ios/Runner/Runner-Bridging-Header.h: -------------------------------------------------------------------------------- 1 | #import "GeneratedPluginRegistrant.h" -------------------------------------------------------------------------------- /lib/models/reply.dart: -------------------------------------------------------------------------------- 1 | 2 | class Reply{ 3 | 4 | } 5 | 6 | class ReplyVO{ 7 | 8 | } -------------------------------------------------------------------------------- /assets/gif/loading.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/H-Mongo/flutterApp/HEAD/assets/gif/loading.gif -------------------------------------------------------------------------------- /assets/gif/loading.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/H-Mongo/flutterApp/HEAD/assets/gif/loading.jpg -------------------------------------------------------------------------------- /assets/avatar/bju_xh.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/H-Mongo/flutterApp/HEAD/assets/avatar/bju_xh.jpg -------------------------------------------------------------------------------- /assets/icon/bju_app.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/H-Mongo/flutterApp/HEAD/assets/icon/bju_app.png -------------------------------------------------------------------------------- /assets/swiper/bju_1.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/H-Mongo/flutterApp/HEAD/assets/swiper/bju_1.jpg -------------------------------------------------------------------------------- /assets/swiper/bju_2.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/H-Mongo/flutterApp/HEAD/assets/swiper/bju_2.jpg -------------------------------------------------------------------------------- /assets/swiper/bju_3.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/H-Mongo/flutterApp/HEAD/assets/swiper/bju_3.jpg -------------------------------------------------------------------------------- /assets/swiper/bju_4.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/H-Mongo/flutterApp/HEAD/assets/swiper/bju_4.jpg -------------------------------------------------------------------------------- /assets/splash/bju_splash.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/H-Mongo/flutterApp/HEAD/assets/splash/bju_splash.gif -------------------------------------------------------------------------------- /assets/avatar/default_avatar.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/H-Mongo/flutterApp/HEAD/assets/avatar/default_avatar.jpg -------------------------------------------------------------------------------- /lib/utils/convert_server_data.dart: -------------------------------------------------------------------------------- 1 | /// 2 | /// 服务器数据格式化类 3 | /// 2020/04/04 16:30 4 | /// 5 | class Util{ 6 | 7 | } -------------------------------------------------------------------------------- /assets/background_imgs/bg_login.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/H-Mongo/flutterApp/HEAD/assets/background_imgs/bg_login.png -------------------------------------------------------------------------------- /assets/background_imgs/bg_personal.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/H-Mongo/flutterApp/HEAD/assets/background_imgs/bg_personal.jpg -------------------------------------------------------------------------------- /android/gradle.properties: -------------------------------------------------------------------------------- 1 | org.gradle.jvmargs=-Xmx1536M 2 | android.enableR8=true 3 | android.useAndroidX=true 4 | android.enableJetifier=true 5 | -------------------------------------------------------------------------------- /android/app/src/main/res/drawable/notice_icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/H-Mongo/flutterApp/HEAD/android/app/src/main/res/drawable/notice_icon.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-hdpi/bju_app.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/H-Mongo/flutterApp/HEAD/android/app/src/main/res/mipmap-hdpi/bju_app.png -------------------------------------------------------------------------------- /android/.gitignore: -------------------------------------------------------------------------------- 1 | gradle-wrapper.jar 2 | /.gradle 3 | /captures/ 4 | /gradlew 5 | /gradlew.bat 6 | /local.properties 7 | GeneratedPluginRegistrant.java 8 | -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-hdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/H-Mongo/flutterApp/HEAD/android/app/src/main/res/mipmap-hdpi/ic_launcher.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-mdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/H-Mongo/flutterApp/HEAD/android/app/src/main/res/mipmap-mdpi/ic_launcher.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-xhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/H-Mongo/flutterApp/HEAD/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/H-Mongo/flutterApp/HEAD/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /lib/settings/settings.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | class SettingsProvider extends ChangeNotifier{ 4 | 5 | // 用于显示指定的 6 | 7 | 8 | } -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/H-Mongo/flutterApp/HEAD/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /lib/models/topic.dart: -------------------------------------------------------------------------------- 1 | /// 2 | /// 话题 3 | /// 2020/02/28 15:15 4 | class Topic { 5 | 6 | int topicId; 7 | String topicName; 8 | 9 | 10 | 11 | 12 | } -------------------------------------------------------------------------------- /android/key.properties: -------------------------------------------------------------------------------- 1 | storePassword=mongo0516. 2 | keyPassword=mongo0516. 3 | keyAlias=key 4 | storeFile=E:/graduationDesign/back/flutter-apk-settting/Users/H-Mongo/key.jks -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/H-Mongo/flutterApp/HEAD/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/H-Mongo/flutterApp/HEAD/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/H-Mongo/flutterApp/HEAD/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/H-Mongo/flutterApp/HEAD/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/H-Mongo/flutterApp/HEAD/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/H-Mongo/flutterApp/HEAD/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/H-Mongo/flutterApp/HEAD/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/H-Mongo/flutterApp/HEAD/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/H-Mongo/flutterApp/HEAD/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/H-Mongo/flutterApp/HEAD/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/H-Mongo/flutterApp/HEAD/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/H-Mongo/flutterApp/HEAD/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/H-Mongo/flutterApp/HEAD/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/H-Mongo/flutterApp/HEAD/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/H-Mongo/flutterApp/HEAD/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/H-Mongo/flutterApp/HEAD/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/H-Mongo/flutterApp/HEAD/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/H-Mongo/flutterApp/HEAD/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png -------------------------------------------------------------------------------- /ios/Runner.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /android/gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | #Fri Jun 23 08:50:38 CEST 2017 2 | distributionBase=GRADLE_USER_HOME 3 | distributionPath=wrapper/dists 4 | zipStoreBase=GRADLE_USER_HOME 5 | zipStorePath=wrapper/dists 6 | distributionUrl=https\://services.gradle.org/distributions/gradle-5.6.2-all.zip 7 | -------------------------------------------------------------------------------- /readme/功能分析.md: -------------------------------------------------------------------------------- 1 | #主要模块 2 | ##校园快讯 3 | ##校园之声 4 | ##墙上有你 5 | ##兼职招聘 6 | ##个人主页 7 | #页面设计 8 | ##底部导航栏 9 | -首页:主要包括用户头像,搜索框,滑动页,校园介绍,热点新闻等 10 | -广场:包括最新动态,热点动态 11 | -加号:用于发表文章,动态等 12 | -消息:艾特用户提示信息,系统提醒信息,评论信息,回复信息等 13 | -我的:包括用户个人信息,系统版本等,修改密码,注销登录,个人资料编辑等等 -------------------------------------------------------------------------------- /android/app/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | ## Flutter wrapper 混淆器规则 2 | -keep class io.flutter.app.** { *; } 3 | -keep class io.flutter.plugin.** { *; } 4 | -keep class io.flutter.util.** { *; } 5 | -keep class io.flutter.view.** { *; } 6 | -keep class io.flutter.** { *; } 7 | -keep class io.flutter.plugins.** { *; } 8 | -dontwarn io.flutter.embedding.** -------------------------------------------------------------------------------- /.metadata: -------------------------------------------------------------------------------- 1 | # This file tracks properties of this Flutter project. 2 | # Used by Flutter tool to assess capabilities and perform upgrades etc. 3 | # 4 | # This file should be version controlled and should not be manually edited. 5 | 6 | version: 7 | revision: 27321ebbad34b0a3fafe99fac037102196d655ff 8 | channel: stable 9 | 10 | project_type: app 11 | -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md: -------------------------------------------------------------------------------- 1 | # Launch Screen Assets 2 | 3 | You can customize the launch screen with your own desired assets by replacing the image files in this directory. 4 | 5 | You can also do it by opening your Flutter project's Xcode project with `open ios/Runner.xcworkspace`, selecting `Runner/Assets.xcassets` in the Project Navigator and dropping in the desired images. -------------------------------------------------------------------------------- /android/app/src/debug/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 3 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /android/app/src/profile/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 3 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /android/app/src/main/res/values/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 8 | 9 | -------------------------------------------------------------------------------- /ios/Runner/AppDelegate.swift: -------------------------------------------------------------------------------- 1 | import UIKit 2 | import Flutter 3 | 4 | @UIApplicationMain 5 | @objc class AppDelegate: FlutterAppDelegate { 6 | override func application( 7 | _ application: UIApplication, 8 | didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? 9 | ) -> Bool { 10 | GeneratedPluginRegistrant.register(with: self) 11 | return super.application(application, didFinishLaunchingWithOptions: launchOptions) 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /android/app/src/main/res/drawable/launch_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 12 | 13 | -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images": [ 3 | { 4 | "idiom": "universal", 5 | "filename": "LaunchImage.png", 6 | "scale": "1x" 7 | }, 8 | { 9 | "idiom": "universal", 10 | "filename": "LaunchImage@2x.png", 11 | "scale": "2x" 12 | }, 13 | { 14 | "idiom": "universal", 15 | "filename": "LaunchImage@3x.png", 16 | "scale": "3x" 17 | } 18 | ], 19 | "info": { 20 | "version": 1, 21 | "author": "xcode" 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /android/app/src/main/kotlin/com/example/bju_information_app/MainActivity.kt: -------------------------------------------------------------------------------- 1 | package com.example.bju_information_app 2 | 3 | import androidx.annotation.NonNull; 4 | import io.flutter.embedding.android.FlutterActivity 5 | import io.flutter.embedding.engine.FlutterEngine 6 | import io.flutter.plugins.GeneratedPluginRegistrant 7 | 8 | class MainActivity : FlutterActivity() { 9 | override fun configureFlutterEngine(@NonNull flutterEngine: FlutterEngine) { 10 | GeneratedPluginRegistrant.registerWith(flutterEngine); 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /lib/providers/bju_app_provider.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | /// 3 | /// BJU APP的全局状态及settings管理 4 | /// 5 | class BjuAppSettingsProvider extends ChangeNotifier{ 6 | 7 | // 主题色彩 8 | ThemeData _bjuThemeData; 9 | String _appTitle; 10 | 11 | ThemeData get bjuThemeData => _bjuThemeData; 12 | String get appTitle => _appTitle; 13 | 14 | /// 初始化Setting 15 | void init(){ 16 | _bjuThemeData = ThemeData( 17 | primaryColor: Color(0xFF42A5F5) 18 | ); 19 | _appTitle = '宝鸡大学信息服务平台'; 20 | } 21 | 22 | } -------------------------------------------------------------------------------- /android/settings.gradle: -------------------------------------------------------------------------------- 1 | include ':app' 2 | 3 | def flutterProjectRoot = rootProject.projectDir.parentFile.toPath() 4 | 5 | def plugins = new Properties() 6 | def pluginsFile = new File(flutterProjectRoot.toFile(), '.flutter-plugins') 7 | if (pluginsFile.exists()) { 8 | pluginsFile.withReader('UTF-8') { reader -> plugins.load(reader) } 9 | } 10 | 11 | plugins.each { name, path -> 12 | def pluginDirectory = flutterProjectRoot.resolve(path).resolve('android').toFile() 13 | include ":$name" 14 | project(":$name").projectDir = pluginDirectory 15 | } 16 | -------------------------------------------------------------------------------- /ios/.gitignore: -------------------------------------------------------------------------------- 1 | *.mode1v3 2 | *.mode2v3 3 | *.moved-aside 4 | *.pbxuser 5 | *.perspectivev3 6 | **/*sync/ 7 | .sconsign.dblite 8 | .tags* 9 | **/.vagrant/ 10 | **/DerivedData/ 11 | Icon? 12 | **/Pods/ 13 | **/.symlinks/ 14 | profile 15 | xcuserdata 16 | **/.generated/ 17 | Flutter/App.framework 18 | Flutter/Flutter.framework 19 | Flutter/Flutter.podspec 20 | Flutter/Generated.xcconfig 21 | Flutter/app.flx 22 | Flutter/app.zip 23 | Flutter/flutter_assets/ 24 | Flutter/flutter_export_environment.sh 25 | ServiceDefinitions.json 26 | Runner/GeneratedPluginRegistrant.* 27 | 28 | # Exceptions to above rules. 29 | !default.mode1v3 30 | !default.mode2v3 31 | !default.pbxuser 32 | !default.perspectivev3 33 | -------------------------------------------------------------------------------- /lib/pages/details/person_moving_details.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | 4 | /// 5 | /// 用户动态详情页面 6 | /// 7 | class PersonMovingDetailsPage extends StatefulWidget { 8 | PersonMovingDetailsPage({Key key}) : super(key: key); 9 | 10 | @override 11 | _PersonMovingDetailsPageState createState() => _PersonMovingDetailsPageState(); 12 | } 13 | 14 | class _PersonMovingDetailsPageState extends State { 15 | @override 16 | Widget build(BuildContext context) { 17 | return Scaffold( 18 | appBar: AppBar( 19 | leading: BackButton(onPressed:()=>Navigator.of(context).pop()), 20 | title: Text('个人主页'), 21 | ), 22 | ); 23 | } 24 | } -------------------------------------------------------------------------------- /lib/constants/bju_constant.dart: -------------------------------------------------------------------------------- 1 | /// 2 | /// BJU常量 3 | /// 2020/04/19 12:13 4 | /// 5 | class BJUConstants{ 6 | // SharedPreferences的常量键值的定义=======start 7 | 8 | /// 登录用户手机号码的存储key 9 | static final String loginUserMobile = 'LOGIN_USER_MOBILE'; 10 | 11 | /// 应用通知DB存储时的owner 12 | static final String alertOwner = 'APP_ALERT'; 13 | 14 | /// 应用通知Count 15 | static final String appAlertCount = 'APP_ALERT_COUNT'; 16 | 17 | /// @我的Count 18 | static final String atMineCount = 'AT_MINE_COUNT'; 19 | 20 | /// 评论我Count 21 | static final String commentMeCount = 'COMMENT_ME_COUNT'; 22 | 23 | /// 赞我Count 24 | static final String likeMeCount = 'LIKE_ME_COUNT'; 25 | 26 | // SharedPreferences的常量键值的定义=======end 27 | 28 | 29 | 30 | 31 | 32 | 33 | } -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Miscellaneous 2 | *.class 3 | *.log 4 | *.pyc 5 | *.swp 6 | .DS_Store 7 | .atom/ 8 | .buildlog/ 9 | .history 10 | .svn/ 11 | 12 | # IntelliJ related 13 | *.iml 14 | *.ipr 15 | *.iws 16 | .idea/ 17 | 18 | # The .vscode folder contains launch configuration and tasks you configure in 19 | # VS Code which you may wish to be included in version control, so this line 20 | # is commented out by default. 21 | #.vscode/ 22 | 23 | # Flutter/Dart/Pub related 24 | **/doc/api/ 25 | .dart_tool/ 26 | .flutter-plugins 27 | .flutter-plugins-dependencies 28 | .packages 29 | .pub-cache/ 30 | .pub/ 31 | /build/ 32 | 33 | # Web related 34 | lib/generated_plugin_registrant.dart 35 | 36 | # Exceptions to above rules. 37 | !/packages/flutter_tools/test/data/dart_dependencies_test/**/.packages 38 | -------------------------------------------------------------------------------- /ios/Flutter/AppFrameworkInfo.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | $(DEVELOPMENT_LANGUAGE) 7 | CFBundleExecutable 8 | App 9 | CFBundleIdentifier 10 | io.flutter.flutter.app 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | App 15 | CFBundlePackageType 16 | FMWK 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | 1.0 23 | MinimumOSVersion 24 | 8.0 25 | 26 | 27 | -------------------------------------------------------------------------------- /lib/models/vo.dart: -------------------------------------------------------------------------------- 1 | 2 | /* 3 | * 视图模型对应的dart文件 4 | * 5 | * 时间:2020/04/15 12:19 6 | * 描述: 主要用来存储用于视图展示所需要的View Model类型 7 | * 8 | */ 9 | 10 | 11 | /// 12 | /// 用于展示 @ 用户的视图模型,返回存储 @ 时所需要的信息 13 | /// 2020/04/15 12:20 14 | /// 15 | class AtUserVO{ 16 | 17 | /// @用户手机号 18 | String phone; 19 | 20 | /// @用户昵称 21 | String nickname; 22 | 23 | /// @用户头像 24 | String avatar; 25 | 26 | /// @用户个性签名 27 | String motto; 28 | 29 | AtUserVO({this.phone,this.nickname,this.avatar,this.motto}); 30 | 31 | /// 将Map数据模型化 32 | factory AtUserVO.fromJson(Map json){ 33 | return AtUserVO( 34 | phone: json.putIfAbsent('phone', () => ''), 35 | nickname: json.putIfAbsent('nickname', () => ''), 36 | avatar: json.putIfAbsent('avatar', () => ''), 37 | motto: json.putIfAbsent('motto', () => '') 38 | ); 39 | } 40 | 41 | @override 42 | String toString() { 43 | 44 | return '{' 45 | + ', phone:' + this.phone 46 | + ', nickname:' + this.nickname 47 | + ', avatar:' + this.avatar 48 | + ', motto:' + this.motto 49 | +'}'; 50 | } 51 | 52 | 53 | } 54 | 55 | 56 | -------------------------------------------------------------------------------- /widgets/bju_widgets.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:flutter_screenutil/flutter_screenutil.dart'; 3 | import 'package:oktoast/oktoast.dart'; 4 | 5 | /// 6 | /// 加载动画 7 | /// 8 | void showLoading(BuildContext context){ 9 | showToastWidget( 10 | Container( 11 | width: MediaQuery.of(context).size.width, 12 | height: MediaQuery.of(context).size.height, 13 | color: Colors.white54, 14 | child: Center( 15 | child: Column( 16 | mainAxisSize: MainAxisSize.min, 17 | mainAxisAlignment: MainAxisAlignment.center, 18 | children: [ 19 | CircularProgressIndicator(), 20 | SizedBox(height: ScreenUtil().setHeight(6),), 21 | Text('加载中...', style: TextStyle( 22 | fontSize: 16, 23 | fontWeight: FontWeight.w500, 24 | wordSpacing: 2, 25 | color: Colors.lightBlue, 26 | ), 27 | ), 28 | ], 29 | ), 30 | ), 31 | ), 32 | duration: Duration(seconds: 3), 33 | // position: ToastPosition.center, 34 | ); 35 | } -------------------------------------------------------------------------------- /test/widget_test.dart: -------------------------------------------------------------------------------- 1 | // This is a basic Flutter widget test. 2 | // 3 | // To perform an interaction with a widget in your test, use the WidgetTester 4 | // utility that Flutter provides. For example, you can send tap and scroll 5 | // gestures. You can also use WidgetTester to find child widgets in the widget 6 | // tree, read text, and verify that the values of widget properties are correct. 7 | 8 | import 'package:flutter/material.dart'; 9 | import 'package:flutter_test/flutter_test.dart'; 10 | 11 | import 'package:bju_information_app/main.dart'; 12 | 13 | void main() { 14 | testWidgets('Counter increments smoke test', (WidgetTester tester) async { 15 | // Build our app and trigger a frame. 16 | await tester.pumpWidget(BJUInformationServiceApp()); 17 | 18 | // Verify that our counter starts at 0. 19 | expect(find.text('0'), findsOneWidget); 20 | expect(find.text('1'), findsNothing); 21 | 22 | // Tap the '+' icon and trigger a frame. 23 | await tester.tap(find.byIcon(Icons.add)); 24 | await tester.pump(); 25 | 26 | // Verify that our counter has incremented. 27 | expect(find.text('0'), findsNothing); 28 | expect(find.text('1'), findsOneWidget); 29 | }); 30 | } 31 | -------------------------------------------------------------------------------- /lib/models/response.dart: -------------------------------------------------------------------------------- 1 | 2 | import 'dart:convert'; 3 | 4 | /// 5 | /// 服务端返回的response对象 6 | /// 2020-3-2 16:11 7 | /// 8 | class ResponseData { 9 | 10 | 11 | /// 版本号 12 | String ver; 13 | 14 | /// 提示信息 15 | String message; 16 | 17 | /// 状态码 18 | /// * [0] 成功 19 | /// * [1] 失败 20 | int statusCode; 21 | 22 | /// 返回的数据 23 | dynamic res; 24 | 25 | ResponseData({this.ver,this.message,this.statusCode,this.res}); 26 | 27 | 28 | @override 29 | String toString() { 30 | return '{' 31 | + 'ver: ' + this.ver 32 | +', message: ' + this.message 33 | +', statusCode: ' + this.statusCode.toString() 34 | +', res: ' + res.toString() 35 | + '}'; 36 | } 37 | 38 | 39 | /// Json转对象 40 | factory ResponseData.fromJson(Map json){ 41 | return ResponseData( 42 | ver:json['ver'], 43 | message:json['message'], 44 | statusCode:int.parse(json['statusCode'].toString()), 45 | res:json.putIfAbsent('res', () => null) 46 | ); 47 | } 48 | 49 | 50 | Map toJson(){ 51 | return { 52 | 'ver':this.ver, 53 | 'message':this.message, 54 | 'statusCode':this.statusCode, 55 | 'res':this.res 56 | }; 57 | } 58 | 59 | 60 | } -------------------------------------------------------------------------------- /android/build.gradle: -------------------------------------------------------------------------------- 1 | buildscript { 2 | ext.kotlin_version = '1.3.50' 3 | repositories { 4 | // google() 5 | // jcenter() 6 | maven { url 'https://maven.aliyun.com/repository/google' } 7 | maven { url 'https://maven.aliyun.com/repository/jcenter' } 8 | maven { url 'http://maven.aliyun.com/nexus/content/groups/public'} 9 | maven { url 'https://maven.aliyun.com/repository/gradle-plugin'} 10 | } 11 | 12 | dependencies { 13 | classpath 'com.android.tools.build:gradle:3.5.0' 14 | classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" 15 | } 16 | } 17 | 18 | allprojects { 19 | repositories { 20 | // google() 21 | // jcenter() 22 | maven { url 'https://maven.aliyun.com/repository/google' } 23 | maven { url 'https://maven.aliyun.com/repository/jcenter' } 24 | maven { url 'http://maven.aliyun.com/nexus/content/groups/public'} 25 | maven { url 'https://maven.aliyun.com/repository/gradle-plugin'} 26 | } 27 | } 28 | 29 | rootProject.buildDir = '../build' 30 | subprojects { 31 | project.buildDir = "${rootProject.buildDir}/${project.name}" 32 | } 33 | subprojects { 34 | project.evaluationDependsOn(':app') 35 | } 36 | 37 | task clean(type: Delete) { 38 | delete rootProject.buildDir 39 | } 40 | -------------------------------------------------------------------------------- /lib/pages/browser.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:webview_flutter/webview_flutter.dart'; 3 | 4 | /// 5 | /// 网页视图 6 | /// 2020/04/02 15:20 7 | /// 8 | class BrowserPage extends StatefulWidget { 9 | 10 | /// 页面标题 11 | final String _pageTitle; 12 | /// 网页连接 13 | final String _url; 14 | 15 | BrowserPage(this._pageTitle,this._url,{Key key}) : super(key: key); 16 | 17 | @override 18 | _BrowserPageState createState() => _BrowserPageState(); 19 | } 20 | 21 | class _BrowserPageState extends State { 22 | 23 | 24 | 25 | 26 | /// 27 | /// web页面体 28 | Widget _webBody(){ 29 | 30 | return widget._url == null || widget._url == '' 31 | ? Center(child: Text('无效页面!连接地址错误...', style: TextStyle(color: Colors.red),),) 32 | : WebView( 33 | /* onWebViewCreated: (webViewController){ 34 | webViewController. 35 | }, */ 36 | initialUrl: widget._url, 37 | javascriptMode: JavascriptMode.unrestricted, 38 | 39 | ); 40 | 41 | } 42 | 43 | 44 | @override 45 | Widget build(BuildContext context) { 46 | return Scaffold( 47 | appBar: AppBar( 48 | leading: BackButton(onPressed:(){ 49 | Navigator.of(context).pop(); 50 | }), 51 | title: Text(widget._pageTitle??'无效页面'), 52 | ), 53 | body: _webBody(), 54 | ); 55 | } 56 | } -------------------------------------------------------------------------------- /lib/utils/bju_timeline_util.dart: -------------------------------------------------------------------------------- 1 | import 'package:flustars/flustars.dart'; 2 | 3 | /// 4 | /// 应用时间线工具类 5 | /// 2020/04/07 12:55 6 | /// 7 | class BjuTimelineUtil{ 8 | 9 | /// 将时间格式化为时间线的形式 10 | /// * dateStr 时间字符串 11 | static String formatDateStr(String dateStr){ 12 | // print('时间字符串为: dateStr=' + dateStr); 13 | // 设置时间线信息 14 | setLocaleInfo('zh_BjuApp',ZHBjuAppTimelineInfo()); 15 | // 将时间字符串转为DateTime对象 16 | final DateTime dateTime = DateTime.parse(dateStr); 17 | // print('转换时间为:'); 18 | // print(dateTime); 19 | // 时间线工具 20 | // TimelineUtil.format(); 21 | final String timelineStr = TimelineUtil.formatByDateTime(dateTime,locale: 'zh_BjuApp', dayFormat: DayFormat.Common); 22 | print('时间线形式:timelineStr= ' + timelineStr); 23 | return timelineStr; 24 | } 25 | 26 | } 27 | 28 | /// 29 | /// 自定义的时间线格式类 30 | /// 2020/04/07 13:18 31 | /// 32 | class ZHBjuAppTimelineInfo implements TimelineInfo { 33 | String suffixAgo() => '前'; 34 | String suffixAfter() => '后'; 35 | String lessThanTenSecond() => '刚刚'; 36 | String customYesterday() => '昨天'; 37 | bool keepOneDay() => true; 38 | bool keepTwoDays() => false; 39 | String oneMinute(int minutes) => '$minutes分'; 40 | String minutes(int minutes) => '$minutes分'; 41 | String anHour(int hours) => '$hours小时'; 42 | String hours(int hours) => '$hours小时'; 43 | String oneDay(int days) => '$days天'; 44 | String days(int days) => '$days天'; 45 | } 46 | -------------------------------------------------------------------------------- /ios/Runner/Base.lproj/Main.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /ios/Runner/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | $(DEVELOPMENT_LANGUAGE) 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | bju_information_app 15 | CFBundlePackageType 16 | APPL 17 | CFBundleShortVersionString 18 | $(FLUTTER_BUILD_NAME) 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | $(FLUTTER_BUILD_NUMBER) 23 | LSRequiresIPhoneOS 24 | 25 | UILaunchStoryboardName 26 | LaunchScreen 27 | UIMainStoryboardFile 28 | Main 29 | UISupportedInterfaceOrientations 30 | 31 | UIInterfaceOrientationPortrait 32 | UIInterfaceOrientationLandscapeLeft 33 | UIInterfaceOrientationLandscapeRight 34 | 35 | UISupportedInterfaceOrientations~ipad 36 | 37 | UIInterfaceOrientationPortrait 38 | UIInterfaceOrientationPortraitUpsideDown 39 | UIInterfaceOrientationLandscapeLeft 40 | UIInterfaceOrientationLandscapeRight 41 | 42 | UIViewControllerBasedStatusBarAppearance 43 | 44 | 45 | 46 | -------------------------------------------------------------------------------- /lib/providers/login_provider.dart: -------------------------------------------------------------------------------- 1 | import 'package:bju_information_app/constants/bju_constant.dart'; 2 | import 'package:bju_information_app/models/user.dart'; 3 | import 'package:bju_information_app/net/bju_net.dart'; 4 | import 'package:flustars/flustars.dart'; 5 | import 'package:flutter/material.dart'; 6 | 7 | 8 | /// 9 | /// 登录状态Provider 10 | /// 11 | class LoginProvider extends ChangeNotifier{ 12 | /// 登录否 13 | bool _isLogin = false; 14 | /// 登录的用户信息 15 | User _user; 16 | /// 用户主页计数项 17 | Map _allCounts = Map(); 18 | 19 | bool get isLogin => _isLogin; 20 | 21 | User get loginUser => _user; 22 | 23 | int get userId => loginUser.userId; 24 | 25 | Map get allCounts => _allCounts; 26 | 27 | 28 | 29 | /// 30 | /// 用户登录操作 31 | /// 32 | void doLogin(User user){ 33 | _user = user; 34 | _isLogin = user != null ? true : false; 35 | // 存储用户的信息 36 | SpUtil.putString(BJUConstants.loginUserMobile, user.userMobile); 37 | notifyListeners(); 38 | } 39 | 40 | /// 41 | /// 更新用户信息 42 | /// 43 | void updateUserInfo(User user){ 44 | _user = user; 45 | notifyListeners(); 46 | } 47 | 48 | /// 更新用户头像信息 49 | /// * [avatarUrl] 用户头像地址 50 | void refreshUserAvatar(String avatarUrl){ 51 | _user.userAvatar = avatarUrl; 52 | notifyListeners(); 53 | } 54 | 55 | /// 用户退出登录 56 | void logout(){ 57 | // 消除dio中的token信息 58 | BjuHttp.disposeToken(); 59 | // 删除登录的用户信息 60 | _user = null; 61 | // 登录标志为false 62 | _isLogin = false; 63 | // 删除本地持久化的用户号码 64 | SpUtil.remove(BJUConstants.loginUserMobile); 65 | notifyListeners(); 66 | } 67 | 68 | /// 刷新用户主页计数项 69 | void refreshAllCounts(Map counts){ 70 | 71 | // 更新counts 72 | _allCounts = counts; 73 | notifyListeners(); 74 | 75 | } 76 | 77 | 78 | @override 79 | String toString() { 80 | 81 | return '[isLogin : '+this.isLogin.toString()+' ; user: '+this._user.toString()+']'; 82 | } 83 | 84 | } 85 | -------------------------------------------------------------------------------- /android/app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 3 | 8 | 13 | 20 | 21 | 22 | 23 | 24 | 25 | 27 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | -------------------------------------------------------------------------------- /ios/Runner/Base.lproj/LaunchScreen.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images": [ 3 | { 4 | "size": "20x20", 5 | "idiom": "iphone", 6 | "filename": "Icon-App-20x20@2x.png", 7 | "scale": "2x" 8 | }, 9 | { 10 | "size": "20x20", 11 | "idiom": "iphone", 12 | "filename": "Icon-App-20x20@3x.png", 13 | "scale": "3x" 14 | }, 15 | { 16 | "size": "29x29", 17 | "idiom": "iphone", 18 | "filename": "Icon-App-29x29@1x.png", 19 | "scale": "1x" 20 | }, 21 | { 22 | "size": "29x29", 23 | "idiom": "iphone", 24 | "filename": "Icon-App-29x29@2x.png", 25 | "scale": "2x" 26 | }, 27 | { 28 | "size": "29x29", 29 | "idiom": "iphone", 30 | "filename": "Icon-App-29x29@3x.png", 31 | "scale": "3x" 32 | }, 33 | { 34 | "size": "40x40", 35 | "idiom": "iphone", 36 | "filename": "Icon-App-40x40@2x.png", 37 | "scale": "2x" 38 | }, 39 | { 40 | "size": "40x40", 41 | "idiom": "iphone", 42 | "filename": "Icon-App-40x40@3x.png", 43 | "scale": "3x" 44 | }, 45 | { 46 | "size": "60x60", 47 | "idiom": "iphone", 48 | "filename": "Icon-App-60x60@2x.png", 49 | "scale": "2x" 50 | }, 51 | { 52 | "size": "60x60", 53 | "idiom": "iphone", 54 | "filename": "Icon-App-60x60@3x.png", 55 | "scale": "3x" 56 | }, 57 | { 58 | "size": "20x20", 59 | "idiom": "ipad", 60 | "filename": "Icon-App-20x20@1x.png", 61 | "scale": "1x" 62 | }, 63 | { 64 | "size": "20x20", 65 | "idiom": "ipad", 66 | "filename": "Icon-App-20x20@2x.png", 67 | "scale": "2x" 68 | }, 69 | { 70 | "size": "29x29", 71 | "idiom": "ipad", 72 | "filename": "Icon-App-29x29@1x.png", 73 | "scale": "1x" 74 | }, 75 | { 76 | "size": "29x29", 77 | "idiom": "ipad", 78 | "filename": "Icon-App-29x29@2x.png", 79 | "scale": "2x" 80 | }, 81 | { 82 | "size": "40x40", 83 | "idiom": "ipad", 84 | "filename": "Icon-App-40x40@1x.png", 85 | "scale": "1x" 86 | }, 87 | { 88 | "size": "40x40", 89 | "idiom": "ipad", 90 | "filename": "Icon-App-40x40@2x.png", 91 | "scale": "2x" 92 | }, 93 | { 94 | "size": "76x76", 95 | "idiom": "ipad", 96 | "filename": "Icon-App-76x76@1x.png", 97 | "scale": "1x" 98 | }, 99 | { 100 | "size": "76x76", 101 | "idiom": "ipad", 102 | "filename": "Icon-App-76x76@2x.png", 103 | "scale": "2x" 104 | }, 105 | { 106 | "size": "83.5x83.5", 107 | "idiom": "ipad", 108 | "filename": "Icon-App-83.5x83.5@2x.png", 109 | "scale": "2x" 110 | }, 111 | { 112 | "size": "1024x1024", 113 | "idiom": "ios-marketing", 114 | "filename": "Icon-App-1024x1024@1x.png", 115 | "scale": "1x" 116 | } 117 | ], 118 | "info": { 119 | "version": 1, 120 | "author": "xcode" 121 | } 122 | } 123 | -------------------------------------------------------------------------------- /lib/main.dart: -------------------------------------------------------------------------------- 1 | import 'package:bju_information_app/net/bju_net.dart'; 2 | import 'package:bju_information_app/pages/main_page.dart'; 3 | import 'package:bju_information_app/providers/bju_app_provider.dart'; 4 | import 'package:bju_information_app/providers/jpush_provider.dart'; 5 | import 'package:bju_information_app/providers/login_provider.dart'; 6 | import 'package:flustars/flustars.dart'; 7 | import 'package:flutter/cupertino.dart'; 8 | import 'package:flutter/material.dart'; 9 | import 'package:oktoast/oktoast.dart'; 10 | import 'package:provider/provider.dart'; 11 | import 'package:provider/single_child_widget.dart'; 12 | import 'package:splashscreen/splashscreen.dart'; 13 | 14 | void main() { 15 | 16 | runApp(BJUInformationServiceApp()); 17 | 18 | } 19 | 20 | class BJUInformationServiceApp extends StatefulWidget { 21 | BJUInformationServiceApp({Key key}) : super(key: key); 22 | 23 | @override 24 | _BJUInformationServiceAppState createState() => _BJUInformationServiceAppState(); 25 | } 26 | 27 | class _BJUInformationServiceAppState extends State { 28 | 29 | List _providers = [ 30 | buildProvider(BjuAppSettingsProvider()..init()), 31 | buildProvider(LoginProvider()), 32 | buildProvider(JPushProvider()..initNotificationPlugin()..initJPush()..setUpJPush()) 33 | ]; 34 | 35 | 36 | static ChangeNotifierProvider buildProvider(T value){ 37 | return ChangeNotifierProvider.value(value: value); 38 | } 39 | 40 | @override 41 | void initState() { 42 | super.initState(); 43 | // 初始化项目中的各种配置信息 44 | _initConfig(); 45 | 46 | 47 | } 48 | 49 | void _initConfig() async { 50 | // 初始化请求Dio配置 51 | BjuHttp.initConfig(); 52 | // 初始化SharedPreferences工具类 53 | await SpUtil.getInstance(); 54 | 55 | } 56 | 57 | 58 | @override 59 | Widget build(BuildContext context) { 60 | 61 | return MultiProvider( 62 | providers: _providers, 63 | child: Consumer(builder: (context, BjuAppSettingsProvider, child){ 64 | return OKToast( 65 | child: MaterialApp( 66 | title: BjuAppSettingsProvider.appTitle, 67 | debugShowCheckedModeBanner: false, 68 | theme: BjuAppSettingsProvider.bjuThemeData, 69 | // home: Scaffold( 70 | // body:this._buildSplashScreenPage(MainPage()), 71 | // ), 72 | home: this._buildSplashScreenPage(MainPage()), 73 | ), 74 | ); 75 | }) 76 | ); 77 | } 78 | 79 | SplashScreen _buildSplashScreenPage(Widget rootPage){ 80 | return SplashScreen( 81 | seconds: 3, 82 | navigateAfterSeconds: rootPage, 83 | title: new Text('欢迎使用宝大IS平台', 84 | style: new TextStyle( 85 | fontWeight: FontWeight.bold, 86 | fontSize: 20.0 87 | ),), 88 | image: new Image.asset('assets/splash/bju_splash.gif'), 89 | backgroundColor: Colors.white, 90 | styleTextUnderTheLoader: new TextStyle(), 91 | photoSize: 100.0, 92 | onClick: ()=>print("Flutter Egypt"), 93 | loaderColor: Colors.red 94 | ); 95 | } 96 | 97 | } 98 | 99 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # flutterApp 2 | 基于flutter框架的微校园实战项目。 3 | 4 | #### 项目介绍 5 | 该项目算是本人对flutter学习的一个实战项目,同时又是作为毕业的课题项目。主要是针对校园信息输出的一个小APP,功能比较简单,重点在于对flutter技术的运用以及如何通过flutter快速开发跨平台的移动端APP程序。这算是一次学习,也算是我个人对flutter的致敬! 6 | *** 7 | #### 微校园部分截图 8 | 以下便是微校园的部分页面截图信息,由于截图过多就不全贴了 9 | | ![在这里插入图片描述](https://img-blog.csdnimg.cn/20200606121029453.jpg?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzQxNDM4MjIz,size_16,color_FFFFFF,t_70#pic_center) | ![在这里插入图片描述](https://img-blog.csdnimg.cn/20200606121105159.jpg?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzQxNDM4MjIz,size_16,color_FFFFFF,t_70#pic_center) | ![在这里插入图片描述](https://img-blog.csdnimg.cn/20200606121202200.jpg?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzQxNDM4MjIz,size_16,color_FFFFFF,t_70#pic_center) | ![在这里插入图片描述](https://img-blog.csdnimg.cn/2020060612133714.jpg?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzQxNDM4MjIz,size_16,color_FFFFFF,t_70#pic_center) | ![在这里插入图片描述](https://img-blog.csdnimg.cn/20200606121620121.jpg?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzQxNDM4MjIz,size_16,color_FFFFFF,t_70#pic_center) | 10 | | ------------------------------------------------------------ | ------------------------------------------------------------ | ------------------------------------------------------------ | ------------------------------------------------------------ | ------------------------------------------------------------ | 11 | | ![在这里插入图片描述](https://img-blog.csdnimg.cn/2020060612185828.jpg?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzQxNDM4MjIz,size_16,color_FFFFFF,t_70#pic_center) | ![在这里插入图片描述](https://img-blog.csdnimg.cn/20200606121923599.jpg?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzQxNDM4MjIz,size_16,color_FFFFFF,t_70#pic_center) | ![在这里插入图片描述](https://img-blog.csdnimg.cn/20200606122000273.jpg?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzQxNDM4MjIz,size_16,color_FFFFFF,t_70#pic_center) | ![在这里插入图片描述](https://img-blog.csdnimg.cn/2020060612205462.jpg?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzQxNDM4MjIz,size_16,color_FFFFFF,t_70#pic_center) | ![在这里插入图片描述](https://img-blog.csdnimg.cn/20200606122343163.jpg?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzQxNDM4MjIz,size_16,color_FFFFFF,t_70#pic_center) | 12 | *** 13 | #### 使用指南 14 | 该项目采用的前后分离开发,此处为移动端程序,后端已在阿里云中完成了部署,打开即可使用体验!假如您也想了解服务端程序,请移步到:[微校园服务端程序入口](https://github.com/H-Mongo/flutterAppServer) 15 | - 首先您需要将该项目download本地,打开git客户端,执行如下命令,完成项目的下载。 16 | ```git 17 | $ git clone https://github.com/H-Mongo/flutterApp.git 18 | ``` 19 | - 打开VSCode,导入已经download的微校园项目。然后打开命令面板,如下操作: 20 | * 真实手机调试,确保您手机开启了,开发者模式(未开启的话,自行百度打开即可)。执行命令:`flutter run`即可运行。 21 | * 模拟器测试,保证您的电脑安装了模拟器软件,(未安装的话,可以使用夜神模拟器,挺不错的!)同样执行命令:`flutter run`即可。 22 | * 您也可以选择打包程序,通过安装包的方式完成软件安装:`flutter build apk`(安装打包方式,IOS打包与命令有点不同)。 23 | 24 | *** 25 | 26 | #### 联系作者 27 | * E-Mail:h2uwei@163.com | 1290499013@qq.com 28 | * QQ:1290499013(WeChat同) 29 | 30 | -------------------------------------------------------------------------------- /lib/models/user.dart: -------------------------------------------------------------------------------- 1 | 2 | import 'dart:convert'; 3 | 4 | /// 5 | /// 用户模型 6 | /// 用于登录,视图渲染,修改更新用户信息 7 | /// 8 | /// [userId] 用户ID 9 | /// [roleId] 角色ID 10 | /// 11 | class User { 12 | int userId; 13 | int roleId; 14 | String userAvatar; 15 | String userNickname; 16 | String userMobile; 17 | String userBorth; 18 | String userAddress; 19 | String userHobby; 20 | String userMotto; 21 | int facultyId; 22 | int specialtyId; 23 | /// 院系名称 24 | String facultyName; 25 | /// 专业名称 26 | String specialtyName; 27 | 28 | User({ 29 | this.userId, 30 | this.roleId, 31 | this.userAvatar, 32 | this.userNickname, 33 | this.userMobile, 34 | this.userBorth, 35 | this.userAddress, 36 | this.userHobby, 37 | this.userMotto, 38 | this.facultyId, 39 | this.specialtyId, 40 | this.facultyName, 41 | this.specialtyName 42 | }); 43 | 44 | // /// 45 | // /// 创建用户登录模型 46 | // /// 47 | // factory User.createLoginUser(String loginMoblie, String loginPwd){ 48 | // return User(userMobile: loginMoblie, userPwd: loginPwd); 49 | // } 50 | 51 | @override 52 | String toString() { 53 | return '{userId: '+this.userId.toString() 54 | +', roleId: '+this.roleId.toString() 55 | +', userAvatar: '+this.userAvatar 56 | +', userNickname: '+this.userNickname 57 | +', userMobile: '+this.userMobile 58 | +', userBorth: '+this.userBorth 59 | +', userAddress: '+this.userAddress 60 | +', userHobby: '+this.userHobby 61 | +', userMotto: '+this.userMotto 62 | +', facultyId: '+this.facultyId.toString() 63 | +', specialtyId: '+this.specialtyId.toString() 64 | +', facultyName: '+this.facultyName 65 | +', specialtyName: '+this.specialtyName+'}'; 66 | } 67 | 68 | /// 69 | /// Json数据转换 70 | /// 71 | factory User.fromJson(Map json){ 72 | print('用户model获取服务器数据信息:json=$json'); 73 | return User( 74 | userId:int.parse(json['userId'].toString()), // 非空 75 | roleId:int.parse(json['roleId'].toString()), // 非空 76 | userAvatar:json.putIfAbsent('userAvatar', () => ''), 77 | userNickname:json.putIfAbsent('userNickname',() => null), // 非空 78 | userMobile:json.putIfAbsent('userMobile',() => null), // 非空 79 | userBorth:json.putIfAbsent('userBorth', () => ''), 80 | userAddress:json.putIfAbsent('userAddress', () => ''), 81 | userHobby:json.putIfAbsent('userHobby', () => ''), 82 | userMotto:json.putIfAbsent('userMotto', () => ''), 83 | facultyId:int.parse(json['facultyId'].toString()), // 非空 84 | specialtyId:int.parse(json['specialtyId'].toString()), // 非空 85 | facultyName:json.putIfAbsent('facultyName', () => null), 86 | specialtyName:json.putIfAbsent('specialtyName', () => null) 87 | ); 88 | } 89 | 90 | /// 91 | /// 将Model转为Json 92 | /// 93 | Map toJson(User user){ 94 | return { 95 | 'userId':userId, 96 | 'roleId':roleId, 97 | 'userAvatar':userAvatar, 98 | 'userNickname':userNickname, 99 | 'userMobile':userMobile, 100 | 'userBorth':userBorth, 101 | 'userAddress':userAddress, 102 | 'userHobby':userHobby, 103 | 'userMotto':userMotto, 104 | 'facultyId':facultyId, 105 | 'specialtyId':facultyId 106 | }; 107 | } 108 | 109 | 110 | 111 | } -------------------------------------------------------------------------------- /android/app/build.gradle: -------------------------------------------------------------------------------- 1 | def localProperties = new Properties() 2 | def localPropertiesFile = rootProject.file('local.properties') 3 | if (localPropertiesFile.exists()) { 4 | localPropertiesFile.withReader('UTF-8') { reader -> 5 | localProperties.load(reader) 6 | } 7 | } 8 | 9 | def flutterRoot = localProperties.getProperty('flutter.sdk') 10 | if (flutterRoot == null) { 11 | throw new GradleException("Flutter SDK not found. Define location with flutter.sdk in the local.properties file.") 12 | } 13 | 14 | def flutterVersionCode = localProperties.getProperty('flutter.versionCode') 15 | if (flutterVersionCode == null) { 16 | flutterVersionCode = '1' 17 | } 18 | 19 | def flutterVersionName = localProperties.getProperty('flutter.versionName') 20 | if (flutterVersionName == null) { 21 | flutterVersionName = '1.0' 22 | } 23 | 24 | apply plugin: 'com.android.application' 25 | apply plugin: 'kotlin-android' 26 | apply from: "$flutterRoot/packages/flutter_tools/gradle/flutter.gradle" 27 | 28 | // APK秘钥配置 29 | def keystoreProperties = new Properties() 30 | def keystorePropertiesFile = rootProject.file('key.properties') 31 | if (keystorePropertiesFile.exists()) { 32 | keystoreProperties.load(new FileInputStream(keystorePropertiesFile)) 33 | } 34 | 35 | 36 | android { 37 | compileSdkVersion 28 38 | 39 | sourceSets { 40 | main.java.srcDirs += 'src/main/kotlin' 41 | } 42 | 43 | lintOptions { 44 | disable 'InvalidPackage' 45 | } 46 | 47 | defaultConfig { 48 | // TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html). 49 | applicationId "com.hzuwei.bju" // JPush推送设置 50 | minSdkVersion 19 51 | targetSdkVersion 28 52 | versionCode flutterVersionCode.toInteger() 53 | versionName flutterVersionName 54 | testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" 55 | 56 | ndk { 57 | //选择要添加的对应 cpu 类型的 .so 库。 58 | abiFilters 'armeabi', 'armeabi-v7a', 'x86', 'x86_64', 'mips', 'mips64', 'arm64-v8a' 59 | } 60 | 61 | manifestPlaceholders = [ 62 | JPUSH_PKGNAME : "com.hzuwei.bju", 63 | JPUSH_APPKEY : "2180c05fa8c52f21d907cbf3", // NOTE: JPush 上注册的包名对应的 Appkey. 64 | JPUSH_CHANNEL : "developer-default", //暂时填写默认值即可. 65 | ] 66 | 67 | } 68 | 69 | // 配置APK秘钥信息 70 | signingConfigs { 71 | release { 72 | keyAlias keystoreProperties['keyAlias'] 73 | keyPassword keystoreProperties['keyPassword'] 74 | storeFile keystoreProperties['storeFile'] ? file(keystoreProperties['storeFile']) : null 75 | storePassword keystoreProperties['storePassword'] 76 | } 77 | } 78 | buildTypes { 79 | release { 80 | // TODO: Add your own signing config for the release build. 81 | // Signing with the debug keys for now, so `flutter run --release` works. 82 | signingConfig signingConfigs.release 83 | // 启用混淆以及/或压缩 84 | minifyEnabled true 85 | useProguard true 86 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' 87 | } 88 | } 89 | } 90 | 91 | flutter { 92 | source '../..' 93 | } 94 | 95 | dependencies { 96 | implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" 97 | testImplementation 'junit:junit:4.12' 98 | androidTestImplementation 'androidx.test:runner:1.1.1' 99 | androidTestImplementation 'androidx.test.espresso:espresso-core:3.1.1' 100 | } 101 | -------------------------------------------------------------------------------- /ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 30 | 31 | 32 | 33 | 39 | 40 | 41 | 42 | 43 | 44 | 54 | 56 | 62 | 63 | 64 | 65 | 66 | 67 | 73 | 75 | 81 | 82 | 83 | 84 | 86 | 87 | 90 | 91 | 92 | -------------------------------------------------------------------------------- /lib/pages/back_management/management.dart: -------------------------------------------------------------------------------- 1 | import 'package:bju_information_app/api/api.dart'; 2 | import 'package:bju_information_app/models/response.dart'; 3 | import 'package:bju_information_app/net/bju_net.dart'; 4 | import 'package:flutter/material.dart'; 5 | import 'package:oktoast/oktoast.dart'; 6 | 7 | /// 8 | /// 推送信息到全员用户 9 | /// 2020/04/20 15:15 10 | /// 11 | class BackManagement extends StatefulWidget { 12 | BackManagement({Key key}) : super(key: key); 13 | 14 | @override 15 | _BackManagementState createState() => _BackManagementState(); 16 | } 17 | 18 | class _BackManagementState extends State { 19 | 20 | 21 | /// 文本编辑控制器 22 | TextEditingController _contentTextController = TextEditingController(); 23 | 24 | 25 | 26 | @override 27 | Widget build(BuildContext context) { 28 | return Scaffold( 29 | appBar: AppBar( 30 | leading: BackButton(onPressed:(){ 31 | Navigator.of(context).pop(); 32 | }), 33 | title: Text('后台管理'), 34 | ), 35 | body: SingleChildScrollView( 36 | child: ListView( 37 | shrinkWrap: true, 38 | children: [ 39 | Padding( 40 | padding: EdgeInsets.all(5), 41 | child: TextField( 42 | controller: _contentTextController, 43 | keyboardType: TextInputType.text, 44 | maxLines: 3, 45 | decoration: InputDecoration( 46 | hintText: '请输入全平台推送通知的信息...', 47 | border: OutlineInputBorder(), 48 | ), 49 | ), 50 | ), 51 | Divider(), 52 | Padding( 53 | padding: EdgeInsets.only(left: 10, right: 10,), 54 | child: RaisedButton( 55 | padding: EdgeInsets.fromLTRB(0, 3, 0, 3), 56 | color: Colors.lightBlue[400], 57 | shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(10)), 58 | child: Text('全员推送(ALL STAFF PUSH)',style: TextStyle( 59 | fontSize: 18, 60 | fontWeight: FontWeight.w500, 61 | letterSpacing: 2, 62 | color: Colors.white, 63 | ), 64 | ), 65 | onPressed: () async { 66 | print('全员信息推送开始...'); 67 | if(!(_contentTextController.text.trim().length > 0)){ 68 | showToast('请输入推送的内容!'); 69 | return; 70 | } 71 | // 向服务器发起推送请求 72 | ResponseData resData = await BjuHttp.post(API.pushAll,params: { 73 | 'messageType' : 0, 74 | 'content' : _contentTextController.text 75 | }).then((onValue) => ResponseData.fromJson(onValue.data)) 76 | .catchError((onError){ 77 | print('全员推送,请求服务器异常!'); 78 | print(onError); 79 | showToast('服务器请求异常!'); 80 | return; 81 | }); 82 | 83 | if(resData == null){ 84 | Future.delayed(Duration(milliseconds: 1500),() => showToast('请求失败!')); 85 | return; 86 | } 87 | // 弹出提示 88 | showToast(resData.message); 89 | if(resData.statusCode == 0){ 90 | // 清空输入框文本 91 | _contentTextController.clear(); 92 | } 93 | 94 | 95 | 96 | } 97 | ), 98 | ) 99 | ], 100 | ), 101 | ), 102 | ); 103 | } 104 | } -------------------------------------------------------------------------------- /pubspec.yaml: -------------------------------------------------------------------------------- 1 | name: bju_information_app 2 | description: A new Flutter project. 3 | 4 | # The following defines the version and build number for your application. 5 | # A version number is three numbers separated by dots, like 1.2.43 6 | # followed by an optional build number separated by a +. 7 | # Both the version and the builder number may be overridden in flutter 8 | # build by specifying --build-name and --build-number, respectively. 9 | # In Android, build-name is used as versionName while build-number used as versionCode. 10 | # Read more about Android versioning at https://developer.android.com/studio/publish/versioning 11 | # In iOS, build-name is used as CFBundleShortVersionString while build-number used as CFBundleVersion. 12 | # Read more about iOS versioning at 13 | # https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html 14 | version: 1.0.0+1 15 | 16 | environment: 17 | sdk: ">=2.1.0 <3.0.0" 18 | 19 | dependencies: 20 | flutter: 21 | sdk: flutter 22 | 23 | # The following adds the Cupertino Icons font to your application. 24 | # Use with the CupertinoIcons class for iOS style icons. 25 | cupertino_icons: ^0.1.2 26 | provider: ^4.0.4 27 | # 闪屏 28 | splashscreen: 29 | # 网络 30 | dio: ^3.0.8 31 | # 轮播图 32 | flutter_swiper: ^1.1.6 33 | # 屏幕适配插件 34 | flutter_screenutil: ^1.0.2 35 | # 第三方 icon 图标插件 36 | font_awesome_flutter: ^8.5.0 37 | # 本地通知 38 | flutter_local_notifications: ^1.1.6 39 | # 路由 40 | fluro: ^1.5.2 41 | # JPush 42 | jpush_flutter: ^0.1.0 43 | # UI 44 | getflutter: ^1.0.8 45 | # 图片上传 46 | image_picker: ^0.6.5 47 | # 多图片上传 48 | multi_image_picker: ^4.6.1 49 | # 设备信息 50 | device_info: ^0.4.1+5 51 | # 获取定位 52 | # location: ^2.5.3 53 | # 徽章 54 | badges: ^1.1.1 55 | # 自定义对话框 56 | flutter_custom_dialog: ^1.0.16 57 | # 省市县三级联动 58 | city_pickers: ^0.1.30 59 | # 日期选择器 60 | flutter_datetime_picker: ^1.3.4 61 | # flutter picker 62 | flutter_picker: ^1.1.0 63 | # 地理编码 64 | # geocoder: ^0.2.1 65 | # 定位 66 | geolocator: ^5.3.0 67 | # toast 68 | oktoast: ^2.3.1+1 69 | # 打开网页 70 | webview_flutter: ^0.3.19+9 71 | # 工具类 72 | flustars: ^0.2.6+1 73 | # 底部动态导航 74 | curved_navigation_bar: ^0.3.2 75 | #SQLLite数据库 76 | sqflite: ^1.3.0 77 | #文件路径工具 78 | path_provider: ^1.6.5 79 | #路径 80 | # path: ^1.7.0 81 | 82 | 83 | dev_dependencies: 84 | flutter_test: 85 | sdk: flutter 86 | 87 | 88 | # For information on the generic Dart part of this file, see the 89 | # following page: https://dart.dev/tools/pub/pubspec 90 | 91 | # The following section is specific to Flutter. 92 | flutter: 93 | uses-material-design: true 94 | # 添加固定的图片信息 95 | assets: 96 | - assets/swiper/ 97 | - assets/splash/ 98 | - assets/avatar/ 99 | - assets/icon/ 100 | - assets/background_imgs/ 101 | - assets/gif/ 102 | # - assets/background_imgs/bg_login.png 103 | # - images/a_dot_ham.jpeg 104 | 105 | # An image asset can refer to one or more resolution-specific "variants", see 106 | # https://flutter.dev/assets-and-images/#resolution-aware. 107 | 108 | # For details regarding adding assets from package dependencies, see 109 | # https://flutter.dev/assets-and-images/#from-packages 110 | 111 | # To add custom fonts to your application, add a fonts section here, 112 | # in this "flutter" section. Each entry in this list should have a 113 | # "family" key with the font family name, and a "fonts" key with a 114 | # list giving the asset and other descriptors for the font. For 115 | # example: 116 | # fonts: 117 | # - family: Schyler 118 | # fonts: 119 | # - asset: fonts/Schyler-Regular.ttf 120 | # - asset: fonts/Schyler-Italic.ttf 121 | # style: italic 122 | # - family: Trajan Pro 123 | # fonts: 124 | # - asset: fonts/TrajanPro.ttf 125 | # - asset: fonts/TrajanPro_Bold.ttf 126 | # weight: 700 127 | # 128 | # For details regarding fonts from package dependencies, 129 | # see https://flutter.dev/custom-fonts/#from-packages 130 | -------------------------------------------------------------------------------- /lib/pages/message_page.dart: -------------------------------------------------------------------------------- 1 | import 'package:bju_information_app/constants/bju_constant.dart'; 2 | import 'package:bju_information_app/pages/details/message_details.dart'; 3 | import 'package:bju_information_app/providers/jpush_provider.dart'; 4 | import 'package:bju_information_app/providers/login_provider.dart'; 5 | import 'package:bju_information_app/utils/db_util.dart'; 6 | import 'package:flutter/cupertino.dart'; 7 | import 'package:flutter/material.dart'; 8 | import 'package:flutter_screenutil/flutter_screenutil.dart'; 9 | import 'package:font_awesome_flutter/font_awesome_flutter.dart'; 10 | import 'package:provider/provider.dart'; 11 | 12 | class MessagePage extends StatefulWidget { 13 | MessagePage({Key key}) : super(key: key); 14 | 15 | @override 16 | _MessagePageState createState() => _MessagePageState(); 17 | } 18 | 19 | class _MessagePageState extends State { 20 | 21 | final List _msgItems = ['平台通知','@我的','评论我','赞我']; 22 | 23 | final List _msgItemIcons = [ 24 | Icon(FontAwesomeIcons.bullhorn, color: Colors.lightBlue[400],), 25 | Icon(FontAwesomeIcons.at, color: Colors.lightBlue[400]), 26 | Icon(FontAwesomeIcons.comments, color: Colors.lightBlue[400]), 27 | Icon(FontAwesomeIcons.thumbsUp,color: Colors.lightBlue[400]) 28 | ]; 29 | 30 | 31 | 32 | 33 | @override 34 | Widget build(BuildContext context) { 35 | 36 | // 初始化屏幕大小用于适配 37 | ScreenUtil.init(context, width:750,height:1334); 38 | 39 | return Scaffold( 40 | appBar: AppBar( 41 | centerTitle: true, 42 | //leading:Icon(Icons.account_circle), 43 | title: Text('消息'), 44 | actions: [ 45 | Padding( 46 | padding: EdgeInsets.only(right: 15), 47 | child: Icon(Icons.help_outline), 48 | ) 49 | ], 50 | ), 51 | body: Consumer2( 52 | builder: (context, loginProvider, jpushProvider, child){ 53 | // loginProvider.isLogin ? Center(child: Text('同学,您还没有登录唷~')) : 54 | return ListView.separated( 55 | padding: EdgeInsets.only(left:5, right:5), 56 | itemBuilder: (context,index){ 57 | 58 | return ListTile( 59 | leading: _msgItemIcons[index], 60 | title: Text(_msgItems[index]), 61 | trailing: Icon(FontAwesomeIcons.chevronRight, size: 32, color: Colors.grey[400],), 62 | onTap: (){ 63 | // 用户未登录并点击了 ['@我','评论我','赞我'],给出提示信息 64 | if(!loginProvider.isLogin && index != 0){ 65 | showCupertinoDialog(context: context, builder: (context){ 66 | return CupertinoAlertDialog( 67 | title: Icon(Icons.warning,color: Colors.deepOrangeAccent,), 68 | content: Text('同学请先登录,在查看!'), 69 | actions:[ 70 | FlatButton( 71 | onPressed: () => Navigator.pop(context), 72 | child: Text('知道了'), 73 | ), 74 | ] 75 | ); 76 | }); 77 | return; 78 | } 79 | print('用户点击了---$index]'); 80 | print('messageList:'+jpushProvider.messageList(index).toString()); 81 | 82 | // 跳转到消息详情页面 83 | Navigator.push(context, MaterialPageRoute( 84 | builder: (context) => MessageDetailsPage(index) 85 | ) 86 | ); 87 | 88 | }, 89 | ); 90 | }, 91 | separatorBuilder: (context,index){ 92 | return child; 93 | }, 94 | itemCount: _msgItems.length 95 | ); 96 | }, 97 | child: Divider(height:10), 98 | ), 99 | ); 100 | } 101 | 102 | 103 | 104 | 105 | 106 | } 107 | 108 | -------------------------------------------------------------------------------- /lib/models/comment.dart: -------------------------------------------------------------------------------- 1 | 2 | /// 3 | /// 评论实体 4 | /// 2020/03/26 15:56 5 | /// 6 | class Comment{ 7 | 8 | 9 | } 10 | 11 | /// 12 | /// 评论VO 13 | /// 2020/03/24 16:45 14 | /// 15 | class CommentVO { 16 | 17 | 18 | /// 评论ID 19 | int commentId; 20 | /// 评论内容 21 | String commentContent; 22 | /// 评论点赞量 23 | int commentLike; 24 | /// 点赞用户列表 25 | List commentLikeUsers; 26 | /// 评论时间 27 | String commentCreateTime; 28 | /// 评论人ID 29 | int commentAuthorId; 30 | /// 评论人头像 31 | String commentAuthorAvatar; 32 | /// 评论人昵称 33 | String commentAuthorName; 34 | /// 评论人手机号 35 | String commentAuthorPhone; 36 | /// 父评论ID 37 | int parentCommentId; 38 | /// 父评论内容 39 | String parentCommentContent; 40 | /// 父评论点赞量 41 | int parentCommentLike; 42 | /// 父评论创建时间 43 | String parentCommentCreateTime; 44 | /// 父评论作者ID 45 | int parentCommentAuthorId; 46 | /// 父评论作者头像 47 | String parentCommentAuthorAvatar; 48 | /// 父评论作者昵称 49 | String parentCommentAuthorName; 50 | /// 父评论作者手机号码 51 | String parentCommentAuthorPhone; 52 | 53 | 54 | CommentVO({ 55 | this.commentId, 56 | this.commentContent, 57 | this.commentLike, 58 | this.commentLikeUsers, 59 | this.commentCreateTime, 60 | this.commentAuthorId, 61 | this.commentAuthorAvatar, 62 | this.commentAuthorName, 63 | this.commentAuthorPhone, 64 | this.parentCommentId, 65 | this.parentCommentContent, 66 | this.parentCommentLike, 67 | this.parentCommentCreateTime, 68 | this.parentCommentAuthorId, 69 | this.parentCommentAuthorAvatar, 70 | this.parentCommentAuthorName, 71 | this.parentCommentAuthorPhone, 72 | }); 73 | 74 | 75 | factory CommentVO.fromJson(Map json) => CommentVO( 76 | commentId: int.parse(json['commentId'].toString()), 77 | commentContent: json['commentContent'], 78 | commentLike: int.parse(json['commentLike'].toString()), 79 | commentLikeUsers: json.putIfAbsent('commentLikeUsers', () => [])?.toString()?.split(','), 80 | commentCreateTime: json['commentCreateTime'], 81 | commentAuthorId: int.parse(json['commentLike'].toString()), 82 | commentAuthorAvatar: json['commentAuthorAvatar'], 83 | commentAuthorName: json['commentAuthorName'], 84 | commentAuthorPhone: json['commentAuthorPhone'], 85 | parentCommentId: json.putIfAbsent('parentCommentId', () => null), 86 | parentCommentContent: json.putIfAbsent('parentCommentContent', () => null), 87 | parentCommentLike: json.putIfAbsent('parentCommentLike', () => null), 88 | parentCommentCreateTime: json.putIfAbsent('parentCommentCreateTime', () => null), 89 | parentCommentAuthorId: json.putIfAbsent('parentCommentAuthorId', () => null), 90 | parentCommentAuthorAvatar: json.putIfAbsent('parentCommentAuthorAvatar', () => null), 91 | parentCommentAuthorName: json.putIfAbsent('parentCommentAuthorName', () => null), 92 | parentCommentAuthorPhone: json.putIfAbsent('parentCommentAuthorPhone', () => null), 93 | ); 94 | 95 | @override 96 | String toString() { 97 | return '{: commentId: ' + this.commentId.toString() 98 | +', commentContent: ' + this.commentContent 99 | +', commentLike: ' + this.commentLike.toString() 100 | +', commentLikeUsers: ' + this.commentLikeUsers.toString() 101 | +', commentCreateTime: ' + this.commentCreateTime 102 | +', commentAuthorId: ' + this.commentAuthorId.toString() 103 | +', commentAuthorAvatar: ' + this.commentAuthorAvatar 104 | +', commentAuthorName: ' + this.commentAuthorName 105 | +', commentAuthorPhone: ' + this.commentAuthorPhone 106 | +', parentCommentId: ' + this.parentCommentId.toString()??'' 107 | +', parentCommentContent: ' + this.parentCommentContent??'' 108 | +', parentCommentLike: ' + this.parentCommentLike.toString()??'' 109 | +', parentCommentCreateTime: ' + this.parentCommentCreateTime??'' 110 | +', parentCommentAuthorId: ' + this.parentCommentAuthorId.toString()??'' 111 | +', parentCommentAuthorAvatar: ' + this.parentCommentAuthorAvatar??'' 112 | +', parentCommentAuthorName: ' + this.parentCommentAuthorName??'' 113 | +', parentCommentAuthorPhone: ' + this.parentCommentAuthorPhone??'' + '}'; 114 | } 115 | 116 | } -------------------------------------------------------------------------------- /lib/net/bju_net.dart: -------------------------------------------------------------------------------- 1 | 2 | import 'package:bju_information_app/api/api.dart'; 3 | import 'package:dio/dio.dart'; 4 | 5 | /// 6 | /// BJU网络 7 | /// 8 | class BjuHttp { 9 | 10 | /// Dio 请求的配置 11 | // static final BaseOptions baseOptions = BaseOptions( 12 | // // baseUrl: API.baseUri, 13 | // connectTimeout: 6000, 14 | // receiveTimeout: 4000, 15 | // contentType: Headers.jsonContentType, 16 | // headers: { 17 | // Headers.contentTypeHeader: Headers.jsonContentType, 18 | // 'Authorization': 'Bearer ' 19 | // } 20 | // ); 21 | 22 | /// Dio请求 23 | static final Dio dio = Dio(); 24 | 25 | 26 | 27 | /// 初始化Dio配置 28 | static void initConfig(){ 29 | // 基础选项设置 30 | // dio.options = BaseOptions( 31 | // // baseUrl: API.baseUri, 32 | // // connectTimeout: 6000, 33 | // // receiveTimeout: 4000, 34 | // // contentType: Headers.jsonContentType, 35 | // // headers: { 36 | // // // Headers.contentTypeHeader: Headers.jsonContentType, 37 | // // // 'Authorization': 'Bearer ' 38 | // // } 39 | // ); 40 | // print('DIO请求配置信息为: '); 41 | // print('connectTimeout: ' + dio.options?.connectTimeout.toString()); 42 | // print('receiveTimeout: ' + dio.options?.receiveTimeout.toString()); 43 | // print('contentType: ' + dio?.options?.contentType??null); 44 | // print('headers: ' + dio?.options?.headers.toString()??null); 45 | // print('params: ' + dio?.options?.queryParameters.toString()??null); 46 | // 请求拦截器设置 47 | dio.interceptors.add(InterceptorsWrapper( 48 | onRequest:(RequestOptions options) async { 49 | print('请求前打印options: '); 50 | print('contentType: ' + options.contentType); 51 | print('headers: ' + options.headers.toString()); 52 | print('data: ' + options.data.toString()); 53 | print('params: ' + options.queryParameters.toString()); 54 | // Do something before request is sent 55 | return options; //continue 56 | // If you want to resolve the request with some custom data, 57 | // you can return a `Response` object or return `dio.resolve(data)`. 58 | // If you want to reject the request with a error message, 59 | // you can return a `DioError` object or return `dio.reject(errMsg)` 60 | }, 61 | onResponse:(Response response) async { 62 | // Do something with response data 63 | return response; // continue 64 | }, 65 | onError: (DioError e) async { 66 | // Do something with response error 67 | return e;//continue 68 | } 69 | )); 70 | 71 | } 72 | /// 设置访问Token 73 | static token(dynamic token){ 74 | dio.options.contentType = 'application/json'; 75 | dio.options.headers['Authorization'] = token; 76 | print('[set token] dio header :'+dio.options.headers.toString()); 77 | } 78 | 79 | // 销毁token 80 | static disposeToken(){ 81 | dio.options.contentType = 'application/json'; 82 | dio.options.headers['Authorization'] = 'Bearer '; 83 | print('[dispose token] dio header :'+dio.options.headers.toString()); 84 | } 85 | 86 | 87 | //Dio get instance => _dio; 88 | 89 | /// 90 | /// GET请求 91 | /// * [path] 路径字符串 92 | /// * [params] 请求参数 93 | /// 94 | static Future get(String path,{Map params}) async => await dio.get(path, queryParameters: params); 95 | 96 | /// 97 | /// POST请求 98 | /// * [path] 路径字符串 99 | /// * [data] body数据 100 | /// * [params] 请求参数(URL编码后) 101 | /// * [options] 请求设置 102 | /// 103 | static Future post(String path,{dynamic data, Map params, Options options}) async => await dio.post(path, queryParameters: params, options: options); 104 | 105 | /// 106 | /// POST请求 107 | /// * [path] 路径字符串 108 | /// * [data] body数据(支持FormData) 109 | /// * [params] 请求参数(URL编码后) 110 | /// 111 | static Future put(String path,{dynamic data, Map params}) async => await dio.put(path, queryParameters: params); 112 | 113 | /// 114 | /// POST请求 115 | /// * [path] 路径字符串 116 | /// * [data] body数据 117 | /// * [params] 请求参数(URL编码后) 118 | /// 119 | static Future delete(String path,{dynamic data, Map params}) async => await dio.delete(path, queryParameters: params); 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | } -------------------------------------------------------------------------------- /lib/api/api.dart: -------------------------------------------------------------------------------- 1 | 2 | /// 3 | /// BJU应用的API接口 4 | /// 5 | class API { 6 | 7 | // ****************** 服务器信息 *********************** 8 | static final String protocol = 'http://'; 9 | // static final String hostName = '192.168.85.1'; // 服务器IP地址 (本机模拟器使用) 10 | static final String hostName = 'bju.server.hzwei.top'; // 服务器IP地址 手机(真机)使用 11 | static final int port = 8080; 12 | static final String appName = 'bjuInformationService'; // app name 13 | // static final String baseUri = '$protocol$hostName:$port/$appName'; // 基本路径地址(本机模拟器使用) 14 | static final String baseUri = '$protocol$hostName/$appName'; // 基本路径地址 手机(真机)使用 15 | 16 | 17 | 18 | 19 | // ******************** passport ******************** 20 | /// 账号密码登录 === [POST] 21 | static final String npLogin = '$baseUri/passport/login/upLogin'; 22 | /// 新用户注册接口 === [POST] 23 | static final String register = '$baseUri/passport/register/doRegister'; 24 | /// 阿里手机验证码发送接口 [SMS/verifyCode/{phoneNumber}]===[POST] 25 | static final String sms = '$baseUri/SMS/verifyCode/'; 26 | /// 验证码验证 === [GET] 27 | static final String verifyCode = '$baseUri/passport/register/validate/code'; 28 | /// 检测昵称唯一性接口 [passport/register/validate/nickname] === [POST] 29 | static final String existNickname = '$baseUri/passport/register/validate/nickname'; 30 | /// 检测手机号码唯一性接口 [passport/register/validate/mobile] === [POST] 31 | static final String existMobile = '$baseUri/passport/register/validate/mobile'; 32 | /// JPush推送接口 === [POST] 33 | static final String pushAll = '$baseUri/notification/all'; // 用于管理员发送公告推送信息 34 | 35 | /// 动态发布接口 === [POST] 36 | static final String publishMoving = '$baseUri/moving/publish'; 37 | 38 | /// 用户中心计数项接口 === [GET] 39 | static final String allCounts = "$baseUri/user/info/counts"; 40 | 41 | /// 获取模块下话题接口 === [GET] 42 | static final String topics = '$baseUri/module/topic'; 43 | 44 | /// 获取院系及专业信息 === [GET] 45 | static final String allFacultyAndSpecialty = '$baseUri/faculties'; 46 | 47 | /// 获取最新动态 === [GET] 48 | static final String newMoving = '$baseUri/moving/new'; 49 | 50 | /// 获取最新动态 === [GET] 51 | static final String hotMoving = '$baseUri/moving/hot'; 52 | 53 | /// 获取指定动态的详情(路径参数) === [GET] 54 | static final String movingDetailsById = '$baseUri/moving/details/'; 55 | 56 | /// 添加评论 === [POST] 57 | static final String addComment = '$baseUri/moving/comment/add'; 58 | 59 | /// 模糊搜索@用户列表 === [GET] 60 | static final String searchAtUser = '$baseUri/user/fuzzy'; 61 | 62 | /// 按照id获取用户信息(路径参数)=== [GET] 63 | static final String getUserInfoById = '$baseUri/user/info/'; 64 | 65 | /// 校园新闻地址 66 | static final String campusNews = 'http://www.bjwlxy.edu.cn/index/xwdt/xyzx.htm'; 67 | 68 | /// 平台系统用户默认头像地址 69 | static final String defaultAvatarURL = '$baseUri/static/avatars/default_avatar.jpg'; 70 | 71 | /// 修改用户信息 === [PUT] 72 | static final String updateUserInfo = '$baseUri/user/info/edit'; 73 | 74 | /// 模糊搜索动态信息({searchKeyword}) === [GET] 75 | static final String fuzzySearchMoving = '$baseUri/moving/fuzzy/'; 76 | 77 | /// 按照模块ID获取动态信息({moduleId}) === [GET] 78 | static final String queryMovingByModuleId = '$baseUri/moving/module/'; 79 | 80 | /// 退出登录APP == [POST] 81 | static final String logout = '$baseUri/passport/login/logout'; 82 | 83 | /// 动态浏览量更新 === [PUT] 84 | static final String updateBrowseCount = '$baseUri/refresh/browse'; 85 | 86 | /// 动态点赞量及点赞用户列表的更新 === [PUT] 87 | static final String updateMovingLikeData = '$baseUri/moving/refresh/like'; 88 | 89 | /// 评论点赞量及点赞用户列表的更新 === [PUT] 90 | static final String updateCommentLikeData = '$baseUri/moving/comment/like'; 91 | 92 | /// 动态图片访问的URL前缀 93 | static final String movingImagesBaseUrl = '$baseUri'; 94 | 95 | /// 用户密码修改 === [PUT] 96 | static final String modifyPassword = '$baseUri/user/safe/password/modify'; 97 | 98 | /// 用户头像修改 === [POST] 99 | static final String modifyAvatar = '$baseUri/user/info/avatar'; 100 | 101 | /// 获取指定用户所有已发布的动态 === [GET] 102 | /// * 路径参数 userId 103 | static final String allMovingWithUser = '$baseUri/moving/published/'; 104 | 105 | /// 获取指定用户草稿箱 === [GET] 106 | /// * 路径参数 userId 107 | static final String draftMovingWithUser = '$baseUri/moving/draft/'; 108 | 109 | /// 保存动态到草稿箱 === [POST] 110 | /// * multipart/form-data 111 | static final String saveDraftMoving = '$baseUri/moving/temporary'; 112 | 113 | /// 重新发布动态(草稿箱中的)=== [PUT] 114 | /// * 路径参数 movingId 115 | static final String republishMoving = '$baseUri/moving/republish/'; 116 | 117 | /// 删除动态(已发布or草稿箱)=== [DELETE] 118 | /// * 路径参数 movingId 119 | static final String deleteMoving = '$baseUri/moving/delete/'; 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | } -------------------------------------------------------------------------------- /lib/models/user_message.dart: -------------------------------------------------------------------------------- 1 | /// 2 | /// 用户消息(服务端通知用户,并携带的用户消息)2020/04/17 21:02 3 | /// * 0 ==> 应用通知 4 | /// * 1 ==> mongo@大宝 5 | /// * 2 ==> mongo评论了你的动态 6 | /// * 3 ==> mongo回复了你的评论 7 | /// * 4 ==> mongo赞了你的动态 8 | /// * 5 ==> mongo赞了你的评论 9 | /// * **其中,[2,3]均为APP端评论我类型;[4,5]均为APP端赞我类型** 10 | /// 11 | /// 12 | class UserMessage { 13 | 14 | /// 15 | /// 消息的编号 16 | /// 17 | //String sendno; 18 | /// 19 | /// 消息头 20 | /// 例如: 21 | /// 0. 应用通知 22 | /// 1. mongo@大宝 23 | /// 2. mongo评论了你的动态 24 | /// 3. mongo回复了你的评论 25 | /// 4. mongo赞了你的动态 26 | /// 5. mongo赞了你的评论 27 | /// 28 | /// 29 | String title; // 30 | 31 | /// 32 | /// 消息体 33 | /// 显示的具体内容,用于在消息页面中进行显示 34 | String content; 35 | 36 | /// 37 | /// 消息类型 38 | /// 参照消息头中的编号 39 | int messageType; 40 | 41 | /// 目标用户名 42 | String toUserName; 43 | 44 | /// 来源用户名 45 | String fromUserName; 46 | 47 | /// 来源用户的ID 48 | int fromUserId; 49 | 50 | /// 来源用户头像 51 | String fromUserAvatar; 52 | 53 | /// 用于查询消息context所对应的具体moving 54 | int queryId; 55 | 56 | /// 接收时间 57 | String receivedTime; 58 | 59 | /// 是否已读 60 | String read; 61 | 62 | /* int commentId; // 评论ID 63 | 64 | int movingId; // 动态ID 65 | 66 | int replyId; // 评论回复ID */ 67 | /* 68 | String commentContext; // 评论内容 69 | 70 | String movingContext; // 动态内容 71 | 72 | String replyContext; // 评论回复内容 73 | */ 74 | 75 | UserMessage({this.title,this.content,this.messageType,this.fromUserId,this.fromUserName,this.fromUserAvatar,this.read = 'false',this.toUserName = '',this.queryId = 0,this.receivedTime}); 76 | 77 | @override 78 | String toString() { 79 | return '{' 80 | +', title= ' + this.title 81 | +', content= ' + this.content 82 | +', messageType= ' + this.messageType.toString() 83 | +', fromUserName= ' + this.fromUserName 84 | +', fromUserId= ' + this.fromUserId.toString() 85 | +', fromUserAvatar= ' + this.fromUserAvatar 86 | +', toUserName= ' + this.toUserName 87 | +', queryId= ' + this.queryId.toString() 88 | +', receivedTime= ' + this.receivedTime 89 | +', read= ' + this.read.toString()+ 90 | '}'; 91 | } 92 | 93 | /// 94 | /// Json模型化 95 | /// 96 | factory UserMessage.fromJson(Map json){ 97 | // print('UserMessage接收消息:'+json.toString()); 98 | // print(json.runtimeType.toString()); 99 | if(json.isEmpty){ 100 | print('没有信息可以构造UserMessage!'); 101 | return null; 102 | } 103 | 104 | // final String tilte = json['title']; 105 | // final String content = json['content']; 106 | // final int messageType = int.parse(json['messageType'].toString()); 107 | // final String fromUserName = json.putIfAbsent('fromUserName', () => null); 108 | // final int fromUserId = int.parse(json['fromUserId'].toString()); 109 | // final String fromUserAvatar = json.putIfAbsent('fromUserAvatar',() => null); 110 | // final int queryId = int.parse(json['queryId'].toString()); 111 | // final String receivedTime = json['receivedTime']; 112 | // final String read = json['read']; 113 | // print('UserMessage中的Map取出来的信息值:'); 114 | // print(tilte); 115 | // print(content); 116 | // print(messageType.toString()); 117 | // print(fromUserName); 118 | // print(fromUserId.toString()); 119 | // print(fromUserAvatar); 120 | // print(queryId.toString()); 121 | // print(receivedTime); 122 | // print(read); 123 | UserMessage userMessage; 124 | 125 | 126 | 127 | try{ 128 | userMessage = UserMessage( 129 | title : json['title'], 130 | content : json['content'], 131 | messageType : int.parse(json['messageType'].toString()), 132 | fromUserName : json.putIfAbsent('fromUserName', () => ''), 133 | fromUserId : int.parse(json['fromUserId'].toString()), 134 | fromUserAvatar : json.putIfAbsent('fromUserAvatar',() => ''), 135 | // toUserName : null, 136 | queryId : int.parse(json['queryId'].toString()), 137 | receivedTime : json['receivedTime'], 138 | read : json['read'], 139 | ); 140 | }catch (e){ 141 | print('构造用户信息模型异常了!'); 142 | print(e); 143 | } 144 | print('用户信息类中创建的对象为:'); 145 | print(userMessage); 146 | 147 | return userMessage; 148 | } 149 | 150 | /// 151 | /// UserMessage转为Map 152 | /// 153 | Map toMap(){ 154 | 155 | Map messageMap = { 156 | "title" : this.title, 157 | "content" : this.content, 158 | "messageType" : this.messageType, 159 | "fromUserName" : this.fromUserName, 160 | "fromUserId" : this.fromUserId, 161 | "fromUserAvatar" : this.fromUserAvatar, 162 | "toUserName" : this.toUserName, 163 | "queryId" : this.queryId, 164 | "receivedTime" : this.receivedTime, 165 | "read" : this.read 166 | }; 167 | print('当前UserMessage转为Map,转换结果:messageMap= ' + messageMap.toString()); 168 | return messageMap; 169 | 170 | } 171 | 172 | } -------------------------------------------------------------------------------- /lib/models/moving.dart: -------------------------------------------------------------------------------- 1 | import 'dart:convert'; 2 | 3 | import 'package:bju_information_app/models/comment.dart'; 4 | 5 | 6 | /// 7 | /// 动态模型 8 | /// 2020/2/23 22:06 9 | /// 10 | class Moving { 11 | 12 | int movingAuthorId; 13 | String movingAuthorAvatar; 14 | String movingAuthorName; 15 | String movingAuthorPhone; 16 | String movingContent; 17 | int movingId; 18 | /// 图片组:英文‘,’分割 19 | List movingImages; 20 | String movingType; 21 | /// 话题组:英文‘,’分割 22 | List movingTopics; 23 | int movingLike; 24 | /// 点赞用户组:英文‘,’分割 25 | List movingLikeUsers; 26 | int movingBrowse; 27 | int movingCommentCount; 28 | String movingCreateTime; 29 | 30 | Moving({ 31 | this.movingAuthorId, 32 | this.movingAuthorAvatar, 33 | this.movingAuthorName, 34 | this.movingAuthorPhone, 35 | this.movingContent, 36 | this.movingId, 37 | this.movingImages, 38 | this.movingType, 39 | this.movingTopics, 40 | this.movingLike, 41 | this.movingLikeUsers, 42 | this.movingBrowse, 43 | this.movingCommentCount, 44 | this.movingCreateTime 45 | }); 46 | 47 | factory Moving.fromJson(Map json) => Moving( 48 | movingAuthorId: int.parse(json['movingAuthorId'].toString()), 49 | movingAuthorAvatar: json['movingAuthorAvatar'], 50 | movingAuthorName: json['movingAuthorName'], 51 | movingAuthorPhone: json['movingAuthorPhone'], 52 | movingContent: json['movingContent'], 53 | movingId: int.parse(json['movingId'].toString()), 54 | movingImages: json.putIfAbsent('movingImages', () => [])?.toString()?.split(','), 55 | movingType: json['movingType'], 56 | movingTopics: json.putIfAbsent('movingTopics', () => [])?.toString()?.split(','), 57 | movingLike: int.parse(json['movingLike'].toString()), 58 | movingLikeUsers: json.putIfAbsent('movingLikeUsers', () => [])?.toString()?.split(','), 59 | movingBrowse: int.parse(json['movingBrowse'].toString()), 60 | movingCommentCount: int.parse(json['movingCommentCount'].toString()), 61 | movingCreateTime: json['movingCreateTime'] 62 | ); 63 | 64 | @override 65 | String toString() { 66 | 67 | return '{movingAuthorId: '+this.movingAuthorId.toString() 68 | +', movingAuthorAvatar: '+this.movingAuthorAvatar 69 | +', movingAuthorName: '+this.movingAuthorName 70 | +', movingAuthorPhone: '+ this.movingAuthorPhone 71 | +', movingContent: '+this.movingContent 72 | +', movingId: '+this.movingId.toString() 73 | +', movingImages: '+ this.movingImages.toString() 74 | +', movingType: '+this.movingType 75 | +', movingTopics: '+ this.movingTopics.toString() 76 | +', movingLike: '+ this.movingLike.toString() 77 | +', movingLikeUsers: '+ this.movingLikeUsers.toString() 78 | +', movingBrowse: '+this.movingBrowse.toString() 79 | +', movingCommentCount: '+ this.movingCommentCount.toString() 80 | +', movingCreateTime: '+ this.movingCreateTime+'}'; 81 | } 82 | 83 | } 84 | 85 | /// 动态详情模型 86 | /// 2020/3/20 15:42 87 | class MovingDetails { 88 | 89 | 90 | int movingAuthorId; 91 | // String movingAuthorAvatar; 92 | String movingAuthorName; 93 | String movingAuthorPhone; 94 | String movingContent; 95 | int movingId; 96 | /// 图片组:英文‘,’分割 97 | List movingImages; 98 | String movingType; 99 | /// 话题组:英文‘,’分割 100 | List movingTopics; 101 | int movingLike; 102 | /// 点赞用户组:英文‘,’分割 103 | List movingLikeUsers; 104 | int movingBrowse; 105 | String movingCreateTime; 106 | 107 | // 评论列表 108 | List commentReplyList; 109 | 110 | 111 | MovingDetails({ 112 | this.movingAuthorId, 113 | // this.movingAuthorAvatar, 114 | this.movingAuthorName, 115 | this.movingAuthorPhone, 116 | this.movingContent, 117 | this.movingId, 118 | this.movingImages, 119 | this.movingType, 120 | this.movingTopics, 121 | this.movingLike, 122 | this.movingLikeUsers, 123 | this.movingBrowse, 124 | this.movingCreateTime, 125 | this.commentReplyList 126 | }); 127 | 128 | factory MovingDetails.fromJson(Map json) => MovingDetails( 129 | movingAuthorId: int.parse(json['movingAuthorId'].toString()), 130 | // movingAuthorAvatar: json['movingAuthorAvatar'], 131 | movingAuthorName: json['movingAuthorName'], 132 | movingAuthorPhone: json['movingAuthorPhone'], 133 | movingContent: json['movingContent'], 134 | movingId: int.parse(json['movingId'].toString()), 135 | movingImages: json.putIfAbsent('movingImages', () => [])?.toString()?.split(','), 136 | movingType: json['movingType'], 137 | movingTopics: json.putIfAbsent('movingTopics', () => [])?.toString()?.split(','), 138 | movingLike: int.parse(json['movingLike'].toString()), 139 | movingLikeUsers: json.putIfAbsent('movingLikeUsers', () => [])?.toString()?.split(','), 140 | movingBrowse: int.parse(json['movingBrowse'].toString()), 141 | movingCreateTime: json['movingCreateTime'], 142 | commentReplyList: (json.putIfAbsent('commentReplyList', () => []) as List)?.map((m)=> CommentVO.fromJson(m))?.toList() 143 | ); 144 | 145 | @override 146 | String toString() { 147 | 148 | return '{movingAuthorId: ' + this.movingAuthorId.toString() 149 | // +', movingAuthorAvatar:' + this.movingAuthorAvatar 150 | +', movingAuthorName:' + this.movingAuthorName 151 | +', movingAuthorPhone:' + this.movingAuthorPhone 152 | +', movingContent:' + this.movingContent 153 | +', movingId:' + this.movingId.toString() 154 | +', movingImages:' + this.movingImages.toString() 155 | +', movingType:' + this.movingType 156 | +', movingTopics:' + this.movingTopics.toString() 157 | +', movingLike:' + this.movingLike.toString() 158 | +', movingLikeUsers:' + this.movingLikeUsers.toString() 159 | +', movingBrowse:' + this.movingBrowse.toString() 160 | +', movingCreateTime:' + this.movingCreateTime 161 | +', commentReplyList:' + this.commentReplyList.toString() + '}'; 162 | } 163 | 164 | 165 | 166 | 167 | 168 | 169 | 170 | 171 | 172 | 173 | 174 | 175 | 176 | } 177 | 178 | -------------------------------------------------------------------------------- /lib/pages/modules/campus_voice.dart: -------------------------------------------------------------------------------- 1 | import 'package:bju_information_app/api/api.dart'; 2 | import 'package:bju_information_app/models/moving.dart'; 3 | import 'package:bju_information_app/models/response.dart'; 4 | import 'package:bju_information_app/net/bju_net.dart'; 5 | import 'package:bju_information_app/pages/details/moving_details.dart'; 6 | import 'package:bju_information_app/utils/bju_timeline_util.dart'; 7 | import 'package:flutter/material.dart'; 8 | import 'package:flutter_screenutil/flutter_screenutil.dart' as SU; 9 | import 'package:oktoast/oktoast.dart'; 10 | 11 | /// 12 | /// 校园之声 13 | /// 2020/04/06 17:30 14 | /// 15 | class CampusVoiceModule extends StatefulWidget { 16 | CampusVoiceModule({Key key}) : super(key: key); 17 | 18 | @override 19 | _CampusVoiceModuleState createState() => _CampusVoiceModuleState(); 20 | } 21 | 22 | class _CampusVoiceModuleState extends State { 23 | 24 | // 模块Id号码 25 | int _moduleId = 3; 26 | 27 | /// 校园之声数据列表 28 | List _campusVoiceList = []; 29 | 30 | 31 | @override 32 | void initState() { 33 | super.initState(); 34 | 35 | } 36 | 37 | @override 38 | void didChangeDependencies() { 39 | super.didChangeDependencies(); 40 | // 初始化数据 41 | _loadData(); 42 | } 43 | 44 | @override 45 | void dispose() { 46 | 47 | super.dispose(); 48 | } 49 | 50 | /// 加载数据 51 | void _loadData() async { 52 | // 向服务器获取数据信息 53 | ResponseData resData = await BjuHttp.get(API.queryMovingByModuleId + _moduleId.toString()) 54 | .then((onValue) => ResponseData.fromJson(onValue.data)) 55 | .catchError((onError){ 56 | print('获取校园之声模块数据异常!'); 57 | print(onError); 58 | showToast('请求服务器异常!'); 59 | }); 60 | if(resData == null){ 61 | Future.delayed(Duration(microseconds: 1500), () => showToast('网络请求失败!')); 62 | return; 63 | } 64 | if(resData != null && resData.statusCode == 1){ 65 | showToast(resData.message); 66 | return; 67 | } 68 | if(!mounted) return; 69 | setState(() { 70 | // 刷新数据 71 | _campusVoiceList = (resData.res as List).map((m) => Moving.fromJson(m)).toList(); 72 | }); 73 | 74 | } 75 | 76 | /// 创建模块体 77 | Widget _buildModuleBody(){ 78 | print('校园之声模块的数据为:'); 79 | print(_campusVoiceList); 80 | return RefreshIndicator( 81 | color: Colors.lightBlue, 82 | backgroundColor: Colors.white54, 83 | child: _campusVoiceList.isEmpty 84 | ? Center(child: Text('没有谏言类相关信息!', style: TextStyle( 85 | fontSize: 18, 86 | fontWeight: FontWeight.w500, 87 | color: Colors.black38, 88 | wordSpacing: 2, 89 | ) 90 | ) 91 | ) 92 | : ListView.separated( 93 | itemBuilder: (context,index){ 94 | return Card( 95 | elevation: 4, 96 | shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(6)), 97 | margin: EdgeInsets.fromLTRB(4, 3, 4, index == (_campusVoiceList.length -1) ? 10 : 0), 98 | child: Column( 99 | mainAxisSize: MainAxisSize.min, 100 | mainAxisAlignment: MainAxisAlignment.center, 101 | children: [ 102 | // 发布时间 103 | Row( 104 | mainAxisAlignment: MainAxisAlignment.start, 105 | children: [ 106 | Padding( 107 | padding: EdgeInsets.only(left: 10, top: 5), 108 | child: Text(BjuTimelineUtil.formatDateStr(_campusVoiceList[index].movingCreateTime) + ' 发布', style: TextStyle( 109 | fontSize: 14, 110 | fontWeight: FontWeight.w300, 111 | wordSpacing: 2, 112 | color: Colors.lightBlue, 113 | ),), 114 | ) 115 | ], 116 | ), 117 | Divider(), 118 | // 主要内容 119 | Padding( 120 | padding: EdgeInsets.fromLTRB(8, 0, 8, 0), 121 | child: Text(_campusVoiceList[index].movingContent, style: TextStyle( 122 | fontSize: 14, 123 | fontWeight: FontWeight.w400, 124 | wordSpacing: 2, 125 | // color: Colors.lightBlue, 126 | ), 127 | ) 128 | ), 129 | Divider(), 130 | // 详情链接 131 | GestureDetector( 132 | child: Padding( 133 | padding: EdgeInsets.only(bottom: 8), 134 | child: Text('详情信息', style: TextStyle( 135 | fontSize: 16, 136 | fontWeight: FontWeight.w500, 137 | wordSpacing: 4, 138 | color: Colors.lightBlue, 139 | ), 140 | ), 141 | ), 142 | onTap: (){ 143 | // 进入详情页面 144 | print('校园之声点击了详情:' + _campusVoiceList[index].movingId.toString()); 145 | final int movingId = _campusVoiceList[index].movingId; 146 | if(movingId == 0){ 147 | print('跳转的动态ID不存在!'); 148 | showToast('动态信息有误!'); 149 | return; 150 | } 151 | // 跳转到动态详情页面 152 | Navigator.push>(context, MaterialPageRoute(builder: (context)=> MovingDetailsPage(movingId) )); 153 | return; 154 | 155 | }, 156 | ), 157 | ], 158 | ), 159 | ); 160 | }, 161 | separatorBuilder: (context,index) => SizedBox(height: SU.ScreenUtil().setHeight(10)), 162 | itemCount: _campusVoiceList.length, 163 | ), 164 | onRefresh: (){ 165 | // 刷新数据 166 | _loadData(); 167 | return; 168 | } 169 | ); 170 | } 171 | 172 | 173 | @override 174 | Widget build(BuildContext context) { 175 | // 屏幕适配 176 | SU.ScreenUtil.init(context, width:750,height:1334); 177 | 178 | return Scaffold( 179 | appBar: AppBar( 180 | leading: BackButton(onPressed:(){ 181 | Navigator.of(context).pop(); 182 | }), 183 | title: Text('校园之声'), 184 | ), 185 | body: _buildModuleBody(), 186 | ); 187 | } 188 | } -------------------------------------------------------------------------------- /lib/pages/main_page.dart: -------------------------------------------------------------------------------- 1 | import 'package:bju_information_app/pages/personal_center_page.dart'; 2 | import 'package:bju_information_app/pages/message_page.dart'; 3 | import 'package:bju_information_app/pages/publish_page.dart'; 4 | import 'package:bju_information_app/pages/square_page.dart'; 5 | import 'package:flutter/cupertino.dart'; 6 | import 'package:flutter/material.dart'; 7 | 8 | import 'home_page.dart'; 9 | 10 | class MainPage extends StatefulWidget { 11 | MainPage({Key key}) : super(key: key); 12 | 13 | @override 14 | _MainPageState createState() => _MainPageState(); 15 | } 16 | 17 | class _MainPageState extends State { 18 | /// 底部导航栏的索引值 19 | int _currentIndex = 0; 20 | /// BottomIconSize 21 | final double _bottomIconSize = 32; 22 | // BottomIconColor 23 | 24 | /// body 25 | final List _bodys = [HomePage(),SquarePage(),MessagePage(),PersonalCenterPage()]; 26 | 27 | /// 28 | GlobalKey _scaffoldKey = GlobalKey(); 29 | 30 | 31 | 32 | /// 33 | /// 创建底部导航栏 34 | /// 35 | BottomAppBar _createBottomAppBar(){ 36 | return BottomAppBar( 37 | color: Colors.lightBlueAccent, 38 | shape: CircularNotchedRectangle(), // 凹槽 39 | child: Row( 40 | mainAxisSize: MainAxisSize.max, 41 | mainAxisAlignment: MainAxisAlignment.spaceAround, 42 | children: [ 43 | IconButton( 44 | iconSize:_bottomIconSize, 45 | icon: Icon(Icons.apps), onPressed: () { 46 | setState(() { 47 | _currentIndex = 0; 48 | }); 49 | }), 50 | IconButton( 51 | iconSize:_bottomIconSize, 52 | icon: Icon(Icons.camera),onPressed: () { 53 | setState(() { 54 | // _currentIndex = 1; 55 | // 跳转到朋友圈页面 56 | Navigator.push(context, MaterialPageRoute(builder: (context) => SquarePage())); 57 | }); 58 | }), 59 | IconButton( 60 | iconSize:_bottomIconSize, 61 | icon: Icon(Icons.mail), onPressed: () { 62 | setState(() { 63 | _currentIndex = 2; 64 | }); 65 | }), 66 | IconButton( 67 | iconSize:_bottomIconSize, 68 | icon: Icon(Icons.person), onPressed: () { 69 | setState(() { 70 | _currentIndex = 3; 71 | }); 72 | }), 73 | ], 74 | ), 75 | ); 76 | } 77 | 78 | 79 | /// 发布栏目 80 | Widget _showPublishItem(){ 81 | 82 | return Container( 83 | padding: EdgeInsets.all(10), 84 | /* decoration: BoxDecoration( 85 | color: Colors.white54, 86 | borderRadius: BorderRadius.all(Radius.circular(20)) 87 | ), */ 88 | child: Column( 89 | mainAxisSize:MainAxisSize.min, 90 | mainAxisAlignment: MainAxisAlignment.spaceEvenly, 91 | crossAxisAlignment: CrossAxisAlignment.stretch, 92 | children: [ 93 | 94 | FlatButton( 95 | child: Text( 96 | "表白墙", 97 | style: TextStyle( 98 | fontSize: 18, 99 | fontWeight: FontWeight.bold, 100 | wordSpacing: 5, 101 | ), 102 | ), 103 | onPressed: (){ 104 | print("****表白墙--1****"); 105 | // 手动关闭模态框 106 | Navigator.pop(context); 107 | Navigator.push(context, MaterialPageRoute(builder: (context){ 108 | return PublishPage(1); 109 | })); 110 | 111 | }, 112 | ), 113 | Divider(height: 2,color: Colors.white,), 114 | FlatButton( 115 | child: Text("万能墙", 116 | style: TextStyle( 117 | fontSize: 18, 118 | fontWeight: FontWeight.bold, 119 | wordSpacing: 5, 120 | ), 121 | ), 122 | onPressed: (){ 123 | print("****万能墙--2****"); 124 | // 手动关闭模态框 125 | Navigator.pop(context); 126 | Navigator.push(context, MaterialPageRoute(builder: (context){ 127 | return PublishPage(2); 128 | })); 129 | 130 | }, 131 | ), 132 | Divider(height: 2,color: Colors.white,), 133 | FlatButton( 134 | child: Text("谏言贴", 135 | style: TextStyle( 136 | fontSize: 18, 137 | fontWeight: FontWeight.bold, 138 | wordSpacing: 5, 139 | ), 140 | ), 141 | onPressed: (){ 142 | print("****谏言贴--3****"); 143 | // 手动关闭模态框 144 | Navigator.pop(context); 145 | Navigator.push(context, MaterialPageRoute(builder: (context){ 146 | return PublishPage(3); 147 | })); 148 | 149 | }, 150 | ), 151 | Divider(height: 2,color: Colors.white,), 152 | FlatButton( 153 | child: Text("兼职汇", 154 | style: TextStyle( 155 | fontSize: 18, 156 | fontWeight: FontWeight.bold, 157 | wordSpacing: 5, 158 | ), 159 | ), 160 | onPressed: (){ 161 | print("****兼职汇---4****"); 162 | // 手动关闭模态框 163 | Navigator.pop(context); 164 | Navigator.push(context, MaterialPageRoute(builder: (context){ 165 | return PublishPage(4); 166 | })); 167 | 168 | }, 169 | ), 170 | 171 | ], 172 | 173 | ), 174 | ); 175 | } 176 | 177 | 178 | @override 179 | Widget build(BuildContext context) { 180 | return Scaffold( 181 | // key: _scaffoldKey, 182 | body: this._bodys[this._currentIndex], 183 | bottomNavigationBar: this._createBottomAppBar(), 184 | floatingActionButton: FloatingActionButton( 185 | onPressed: () { 186 | // 挑转到发布页面 187 | /* Scaffold.of(context).showBottomSheet( 188 | (context){ 189 | return _showPublishItem(); 190 | } 191 | ); */ 192 | 193 | // showCupertinoModalPopup(context: context, builder: (context) => _showPublishItem()); 194 | showModalBottomSheet( 195 | backgroundColor: Colors.lightBlue, 196 | shape: RoundedRectangleBorder( 197 | borderRadius: BorderRadius.only(topLeft:Radius.circular(20),topRight: Radius.circular(20),) 198 | ), 199 | context: context, 200 | builder: (context){ 201 | return _showPublishItem(); 202 | } 203 | ); 204 | 205 | 206 | }, 207 | tooltip: '发布', 208 | child: Icon( 209 | Icons.add, 210 | color: Colors.white, 211 | ), 212 | ), 213 | floatingActionButtonLocation: FloatingActionButtonLocation.centerDocked, 214 | ); 215 | } 216 | 217 | 218 | 219 | } 220 | 221 | /// 222 | /// 顶部BannerBar组件 223 | /// 224 | class BjuISAppTopBar extends StatefulWidget { 225 | BjuISAppTopBar({Key key}) : super(key: key); 226 | 227 | @override 228 | _BjuISAppTopBarState createState() => _BjuISAppTopBarState(); 229 | } 230 | 231 | class _BjuISAppTopBarState extends State { 232 | // 文本编辑控制器 233 | final TextEditingController _textEditingController = new TextEditingController(); 234 | 235 | @override 236 | Widget build(BuildContext context) { 237 | return Row( 238 | mainAxisSize: MainAxisSize.max, 239 | children: [ 240 | Expanded(child: TextField( 241 | controller:_textEditingController, 242 | onChanged: null, 243 | onSubmitted: null, 244 | decoration: InputDecoration(), 245 | )) 246 | ], 247 | ); 248 | } 249 | } 250 | -------------------------------------------------------------------------------- /lib/utils/db_util.dart: -------------------------------------------------------------------------------- 1 | import 'package:bju_information_app/constants/bju_constant.dart'; 2 | import 'package:bju_information_app/models/user_message.dart'; 3 | import 'package:sqflite/sqflite.dart'; 4 | import 'package:path/path.dart'; 5 | 6 | /// 7 | /// SqlLite数据库工具类 8 | /// 2020/04/17 22:54 9 | /// 10 | class DBUtil{ 11 | 12 | /// 数据库版本号 13 | static final int _DB_VERSION = 1; 14 | 15 | /// 数据库名称 16 | static final String _DB_NAME = 'BJU.db'; 17 | 18 | /// 数据库中表的名称 19 | static final String _TABLE_NAME = 'user_message'; 20 | 21 | /// 建表的SQL语句 (与UserMessage字段基本一致,多了owner字段,该字段标识UserMessage所属的用户) 22 | static final String _CREATE_TABLE_SQL = ''' 23 | CREATE TABLE $_TABLE_NAME ( 24 | title CHAR(30) NOT NULL, 25 | content TEXT NOT NULL, 26 | messageType INT NOT NULL, 27 | fromUserName CHAR(25), 28 | fromUserId INT, 29 | fromUserAvatar TEXT, 30 | toUserName CHAR(25), 31 | queryId INT NOT NULL, 32 | receivedTime TEXT NOT NULL, 33 | read CHAR(10) NOT NULL, 34 | owner CHAR(25) 35 | ) 36 | '''; 37 | 38 | /// 数据库实例 39 | static Database _database; 40 | 41 | get database => _database; 42 | 43 | // 打开数据库 44 | static Future openDB() async { 45 | 46 | // 获取数据库路径databasesPath 47 | final String databasesPath = await getDatabasesPath(); 48 | print('获取的SqlLite数据库路径为:dbPath=' + databasesPath); 49 | String dbPath = join(databasesPath,_DB_NAME); 50 | print('获取的SqlLite数据库文件的路径为:dbPath=' + dbPath); 51 | // 打开并创建数据库 52 | final Database db = await openDatabase(dbPath, version: _DB_VERSION, onCreate: (db,version) async { 53 | // 数据库创建的时候创建表 54 | final bool isExist = await _isExistTable(db,_TABLE_NAME); 55 | print('数据库工具类中,建表前判断表是否存在:isExist=' + isExist.toString()); 56 | // 表已经存在 57 | if(isExist) return; 58 | // 执行建表语句 59 | await db.execute(_CREATE_TABLE_SQL); 60 | 61 | }); 62 | print('获取的数据库实例为 db=' + db.toString()); 63 | // 设置数据库实例 64 | // _database = db; 65 | return db; 66 | } 67 | 68 | /// 判断数据库中是否存在待创建的表 69 | /// * [db] 当前数据库 70 | /// * [tableName] 表的名称 71 | static Future _isExistTable(Database db,String tableName) async { 72 | // 执行查询操作,表中是否有数据 73 | final int count = await db.query(tableName) 74 | .then((onValue) => onValue?.length??0) 75 | .catchError((onError) { 76 | print('判断表是否存在出错了,错误信息如下:'); 77 | print(onError); 78 | }); 79 | print('数据库工具类中,建表前判断表是否存在,查询表中的记录数:count=' + count.toString()); 80 | return count != null && count > 0; 81 | } 82 | 83 | /// 84 | /// 插入UserMessage到表中(包含用户信息) 85 | /// * [values] 插入的数据 86 | /// *__返回执行的结果(true?false)__ 87 | /// 88 | static Future insertMessage(Map values) async { 89 | print('数据库工具类,插入UserMessage到表中,入参:type=' + values.toString()); 90 | if((values?.isEmpty??false)){ 91 | print('数据库工具类,插入UserMessage到表中,等待插入的数据无效!'); 92 | return false; 93 | } 94 | /// 获取数据库实例 95 | Database db = (_database??await openDB()); 96 | print('数据库工具类,插入UserMessage到表中:获得 DB=' + db.toString()); 97 | // 插入数据到SQLLite表中 98 | int count = await db.insert(_TABLE_NAME, values, conflictAlgorithm:ConflictAlgorithm.replace); 99 | // 关闭数据库 100 | close(); 101 | print('数据库工具类,插入UserMessage到表:插入结果 count=' + count.toString()); 102 | return count == null || count == 0 ? false : true; 103 | } 104 | 105 | /// 106 | /// 更新信息列表为已读 107 | /// * [type] 消息类型 108 | /// * [owner] 登录的用户手机号码 109 | /// *__返回执行的结果(true?false)__ 110 | /// 111 | static Future readMessagesWithTypeAndOwner(int type,String owner) async { 112 | print('数据库工具类,更新UserMessage表中的数据为已读,入参:type=$type,owner=$owner'); 113 | if(type == null || (owner?.isEmpty??true)){ 114 | print('数据库工具类,更新UserMessage表中的数据为已读,更新条件无效!'); 115 | return false; 116 | } 117 | /// 获取数据库实例 118 | Database db = (_database??await openDB()); 119 | print('数据库工具类,更新UserMessage表中的数据为已读,获得 DB=' + db.toString()); 120 | // 标记表中的数据为已读状态 121 | int count = await db.update(_TABLE_NAME,{'read':'true'},where: 'owner = ? and messageType = ?',whereArgs: [owner,type]); 122 | // 关闭数据库 123 | close(); 124 | print('数据库工具类,更新UserMessage表中的数据为已读,更新结果 count=' + count.toString()); 125 | return count == null || count == 0 ? false : true; 126 | } 127 | 128 | /// 129 | /// 查询user_message表中数据(按照时间排序) 130 | /// * [type] 消息类型 131 | /// * [owner] 登录的用户手机号码 132 | /// *__返回执行的结果(List)__ 133 | /// 134 | static Future> queryMessagesWithTypeAndOwner(int type,String owner) async { 135 | print('数据库工具类,按照类别与拥有者查询,入参:type=$type,owner=$owner'); 136 | // if(type == null || (owner?.isEmpty??true)){ 137 | // print('数据库工具类,按照类别与拥有者查询,,查询条件无效!'); 138 | // return null; 139 | // } 140 | if((type == null || type != 0) && (owner == null || owner == '')){ 141 | print('数据库工具类,按照类别与拥有者查询,非应用消息查询,owner无效'); 142 | return null; 143 | } 144 | /// 获取数据库实例 145 | Database db = (_database??await openDB()); 146 | print('数据库工具类,按照类别与拥有者查询:获得 DB=' + db.toString()); 147 | // 执行查询操作 148 | List> queryList = await db.query(_TABLE_NAME, 149 | columns: [ 150 | 'title', 151 | 'content', 152 | 'messageType', 153 | 'fromUserName', 154 | 'fromUserId', 155 | 'fromUserAvatar', 156 | 'toUserName', 157 | 'queryId', 158 | 'receivedTime', 159 | 'read' 160 | ], 161 | where:'owner = ? and messageType = ?', 162 | whereArgs:[type == 0 ? BJUConstants.alertOwner : owner,type], 163 | orderBy: 'receivedTime DESC' 164 | ); 165 | // 关闭数据库 166 | close(); 167 | // 转换结果 168 | final List resultList = transformList2UserMessage(queryList); 169 | print('数据库工具类,按照类别与拥有者查询:获得结果 resultList=' + resultList.toString()); 170 | return resultList; 171 | } 172 | 173 | 174 | static Future queryCountWithTypeAndOwner(int type,String owner) async { 175 | print('数据库工具类,按照类别与拥有者查询,入参:type=$type,owner=$owner'); 176 | // if(type == null || (owner?.isEmpty??true)){ 177 | // print('数据库工具类,按照类别与拥有者查询,,查询条件无效!'); 178 | // return null; 179 | // } 180 | if((type == null || type != 0) && (owner == null || owner == '')){ 181 | print('数据库工具类,按照类别与拥有者查询,非应用消息查询,owner无效'); 182 | return null; 183 | } 184 | /// 获取数据库实例 185 | Database db = (_database??await openDB()); 186 | print('数据库工具类,按照类别与拥有者查询:获得 DB=' + db.toString()); 187 | // 执行查询操作 188 | List> queryList = await db.query(_TABLE_NAME, 189 | columns: [ 190 | 'title', 191 | 'content', 192 | 'messageType', 193 | 'fromUserName', 194 | 'fromUserId', 195 | 'fromUserAvatar', 196 | 'toUserName', 197 | 'queryId', 198 | 'receivedTime', 199 | 'read' 200 | ], 201 | where:'owner = ? and messageType = ?', 202 | whereArgs:[type == 0 ? BJUConstants.alertOwner : owner,type], 203 | orderBy: 'receivedTime DESC' 204 | ); 205 | // 关闭数据库 206 | close(); 207 | // 转换结果 208 | final List resultList = transformList2UserMessage(queryList); 209 | print('数据库工具类,按照类别与拥有者查询:获得结果 resultList=' + resultList.toString()); 210 | return 0; 211 | } 212 | 213 | /// 214 | /// 查询得到的数据转换并包装 215 | /// * **List>** 转为 **UserMessage** 216 | /// 217 | static List transformList2UserMessage(List> mapList){ 218 | // map封装为具体类型 219 | final List list = mapList.map((m) => UserMessage.fromJson(m)).toList(); 220 | print('数据库工具类,查询结果转换模型,结果为:list=' + list.toString()); 221 | return list; 222 | } 223 | // static Future deleteDatabase(){ 224 | 225 | // } 226 | 227 | 228 | static Future deleteTable() async { 229 | int count = await (_database??await openDB()).delete(_TABLE_NAME); 230 | print('数据库工具类,删除数据表,结果:count= ' + count.toString()); 231 | } 232 | 233 | 234 | 235 | /// 关闭数据库 236 | static Future close(){ 237 | // 关闭数据库连接 238 | _database?.close(); 239 | // 空引用 240 | _database = null; 241 | } 242 | 243 | 244 | 245 | } -------------------------------------------------------------------------------- /lib/pages/at_choose_page.dart: -------------------------------------------------------------------------------- 1 | import 'package:bju_information_app/api/api.dart'; 2 | import 'package:bju_information_app/models/response.dart'; 3 | import 'package:bju_information_app/models/vo.dart'; 4 | import 'package:bju_information_app/net/bju_net.dart'; 5 | import 'package:bju_information_app/providers/login_provider.dart'; 6 | import 'package:flutter/material.dart'; 7 | import 'package:flutter_screenutil/flutter_screenutil.dart'; 8 | import 'package:font_awesome_flutter/font_awesome_flutter.dart'; 9 | import 'package:getflutter/getflutter.dart'; 10 | import 'package:oktoast/oktoast.dart'; 11 | import 'package:provider/provider.dart'; 12 | 13 | /// 14 | /// 用户艾特选择页面 15 | ///2020/03/26 14:53 16 | /// 17 | class AtChoosePage extends StatefulWidget { 18 | AtChoosePage({Key key}) : super(key: key); 19 | 20 | @override 21 | _AtChoosePageState createState() => _AtChoosePageState(); 22 | } 23 | 24 | class _AtChoosePageState extends State { 25 | 26 | 27 | /// 输入框key 28 | GlobalKey _atSearchTextFieldKey = GlobalKey(); 29 | 30 | /// 模糊查询 @ 用户列表 31 | // [{'avatar':avatar,'nickname':nickname,'phone':phone,'motto',motto}] 32 | List _atList = List(); 33 | 34 | /// 搜索文本控制器 35 | TextEditingController _searchTextController = TextEditingController(); 36 | /// 搜索焦点控制器 37 | FocusNode _searchTextFocusNode = FocusNode(); 38 | // 搜索文本是否为空 39 | bool _isSearchTextEmpty = true; 40 | 41 | 42 | 43 | @override 44 | void initState() { 45 | super.initState(); 46 | 47 | // 搜索文本监听器 48 | _searchTextController.addListener((){ 49 | // 显示清除按钮 50 | if(_searchTextController.text.trim().length > 0){ 51 | if(!mounted) return; 52 | setState(() { 53 | _isSearchTextEmpty = false; 54 | }); 55 | } else { 56 | if(!mounted) return; 57 | setState(() { 58 | _isSearchTextEmpty = true; 59 | }); 60 | } 61 | }); 62 | 63 | } 64 | 65 | @override 66 | void dispose() { 67 | super.dispose(); 68 | 69 | } 70 | 71 | /// 构建搜索Bar 72 | Widget _buildSearchBar(){ 73 | 74 | // 输入框获取焦点 75 | // FocusScope.of(context).requestFocus(_searchTextFocusNode); 76 | 77 | return Row( 78 | mainAxisSize: MainAxisSize.max, 79 | mainAxisAlignment: MainAxisAlignment.start, 80 | children: [ 81 | Expanded( 82 | child: TextField( 83 | key: _atSearchTextFieldKey, 84 | controller: _searchTextController, 85 | focusNode: _searchTextFocusNode, 86 | autofocus: true, 87 | keyboardType: TextInputType.text, 88 | decoration: InputDecoration( 89 | hintText: "输入@的用户昵称...", 90 | // prefixIcon: Icon(FontAwesomeIcons.search), 91 | suffixIcon: _isSearchTextEmpty ? null : IconButton( 92 | icon: Icon(Icons.close), 93 | onPressed: (){ 94 | // 清空为输入的文本 95 | WidgetsBinding.instance.addPostFrameCallback((_) => _searchTextController.clear()); 96 | 97 | }, 98 | ), 99 | border: InputBorder.none, 100 | ), 101 | // textInputAction: TextInputAction.search, 102 | ) 103 | ), 104 | GestureDetector( 105 | child: Padding( 106 | padding: EdgeInsets.only(left: 12, right: 5), 107 | child: Text("搜索", style: TextStyle( 108 | fontSize: 18, 109 | wordSpacing: 4, 110 | fontWeight: FontWeight.w500, 111 | color: Colors.lightBlue, 112 | ),), 113 | ), 114 | onTap: () async { 115 | if(Provider.of(context,listen: false).loginUser == null){ 116 | showToast('请先登录!'); 117 | return; 118 | } 119 | if(_searchTextController.text == null || _searchTextController.text.trim().length == 0){ 120 | showToast('请输入搜索内容!'); 121 | return; 122 | } 123 | final String searchText = _searchTextController.text; 124 | // 发送请求数据 125 | ResponseData resData = await BjuHttp.get(API.searchAtUser,params: {"keywords":searchText}) 126 | .then((onValue) => ResponseData.fromJson(onValue.data)) 127 | .catchError((onError){ 128 | print('搜索艾特用户失败!'); 129 | print(onError); 130 | showToast('请求服务器异常!'); 131 | return; 132 | }); 133 | if(resData == null) { 134 | // 延迟提示 135 | Future.delayed(Duration(milliseconds : 1500),() => showToast('网络错误!')); 136 | return; 137 | } 138 | print('@用户搜索数据为:' + resData.toString()); 139 | if(resData != null && resData.statusCode == 1){ 140 | showToast(resData.message); 141 | return; 142 | } 143 | print('@用户模糊搜索列表的数据类型为: ' + resData.res.runtimeType.toString()); 144 | if(!mounted) return; 145 | // 刷新列表 146 | setState(() { 147 | _atList = (resData.res as List).map((m) => AtUserVO.fromJson(m)).toList(); 148 | 149 | }); 150 | print('@用户的包装列表为: ' + _atList.toString()); 151 | } 152 | ), 153 | 154 | ], 155 | ); 156 | } 157 | 158 | /// 创建搜索出来得到内容体 159 | Widget _buildBody(){ 160 | 161 | return _atList == null || _atList.length == 0 162 | ? Center( 163 | child: Text('没有符合条件的搜索结果!', style: TextStyle( 164 | fontSize: 18, 165 | fontWeight: FontWeight.w500, 166 | color: Colors.black38, 167 | wordSpacing: 2, 168 | ) 169 | ) 170 | ) 171 | : ListView.separated( 172 | itemBuilder: (context,index){ 173 | return Padding( 174 | padding: EdgeInsets.only(top: index == 0 ? ScreenUtil().setHeight(10) : 0, bottom: ScreenUtil().setHeight(2),), 175 | child: GFListTile( 176 | color: Colors.lightBlue[300], 177 | avatar:GFAvatar( 178 | backgroundImage:NetworkImage(API.baseUri + _atList[index].avatar??'/static/avatars/default_avatar.jpg'), 179 | shape: GFAvatarShape.standard 180 | ), 181 | title: Text(_atList[index].nickname??'无效数据', 182 | style: TextStyle( 183 | fontSize: 16, 184 | fontWeight: FontWeight.w500, 185 | wordSpacing: 2, 186 | // color: Colors.lightBlue[400], 187 | color: Colors.white, 188 | ), 189 | ), 190 | subTitle: Text(_atList[index].motto??'无效数据', 191 | style: TextStyle( 192 | fontSize: 14, 193 | fontWeight: FontWeight.w400, 194 | wordSpacing: 2, 195 | color: Colors.white54, 196 | ), 197 | ), 198 | icon: Padding( 199 | padding: EdgeInsets.only(right: 15), 200 | child: GestureDetector( 201 | child: Icon(FontAwesomeIcons.at,color: Colors.white70,), 202 | onTap: (){ 203 | // 选中的参数 204 | final Map chooseMap = { 205 | 'nickname': _atList[index].nickname, 206 | 'phone': _atList[index].phone 207 | }; 208 | print('选中的艾特用户信息: chooseMap='+chooseMap.toString()); 209 | Navigator.of(context).pop(chooseMap); 210 | return; 211 | }, 212 | ), 213 | ) 214 | ), 215 | ); 216 | }, 217 | separatorBuilder: (context,index) => SizedBox(), 218 | // Divider(indent: 2, endIndent: 2,height: ScreenUtil().setHeight(10), thickness: 1,color: Colors.lightBlue[100],), 219 | itemCount: _atList.length, 220 | ); 221 | } 222 | 223 | @override 224 | 225 | Widget build(BuildContext context) { 226 | 227 | // 初始化屏幕大小用于适配 228 | ScreenUtil.init(context, width:750,height:1334); 229 | 230 | return Scaffold( 231 | appBar: AppBar( 232 | backgroundColor: Colors.white54, 233 | leading: BackButton(onPressed:()=>Navigator.of(context).pop()), 234 | title: _buildSearchBar(), 235 | ), 236 | body: _buildBody(), 237 | ); 238 | } 239 | } -------------------------------------------------------------------------------- /lib/pages/details/message_details.dart: -------------------------------------------------------------------------------- 1 | import 'package:bju_information_app/api/api.dart'; 2 | import 'package:bju_information_app/constants/bju_constant.dart'; 3 | import 'package:bju_information_app/models/user_message.dart'; 4 | import 'package:bju_information_app/pages/details/moving_details.dart'; 5 | import 'package:bju_information_app/providers/jpush_provider.dart'; 6 | import 'package:bju_information_app/utils/bju_timeline_util.dart'; 7 | import 'package:bju_information_app/utils/db_util.dart'; 8 | import 'package:flustars/flustars.dart' hide ScreenUtil; 9 | import 'package:flutter/material.dart'; 10 | import 'package:flutter_screenutil/flutter_screenutil.dart'; 11 | import 'package:font_awesome_flutter/font_awesome_flutter.dart'; 12 | import 'package:provider/provider.dart'; 13 | 14 | /// 15 | /// 消息详情页面 16 | /// 2020/2/23 19:48 17 | /// 18 | class MessageDetailsPage extends StatefulWidget { 19 | 20 | // messageType 21 | int _messageType; 22 | 23 | MessageDetailsPage(this._messageType,{Key key}) : super(key: key); 24 | 25 | @override 26 | _MessageDetailsPageState createState() => _MessageDetailsPageState(this._messageType); 27 | } 28 | 29 | class _MessageDetailsPageState extends State { 30 | 31 | // 无消息时的提示信息 32 | final List _advices = ['没有相关通知!','您还没有被@呦!','没有与您相关的评论!','还没被赞过喔!']; 33 | // AppBar title text 34 | final List _titles = ['系统通知','@我的','相关评论','赞我']; 35 | // 信息类型 36 | int _messageType; 37 | // 页面信息集合 38 | List _messages = []; 39 | 40 | _MessageDetailsPageState(this._messageType); 41 | 42 | @override 43 | void initState() { 44 | super.initState(); 45 | } 46 | 47 | @override 48 | void didChangeDependencies() { 49 | super.didChangeDependencies(); 50 | print('messageType:'+this._messageType.toString()); 51 | _loadMessageByMessageType(); 52 | print('_messages:'+this._messages.toString()); 53 | 54 | } 55 | 56 | 57 | /// 58 | /// 通过消息类型ID加载消息 59 | Future _loadMessageByMessageType() async { 60 | 61 | // 查询数据 62 | final List messageList = await DBUtil.queryMessagesWithTypeAndOwner(_messageType, SpUtil.getString(BJUConstants. loginUserMobile, defValue: null)) 63 | .catchError((onError){ 64 | print('信息详情页获得信息出错了!'); 65 | print(onError); 66 | }); 67 | print('信息详情页面,获得信息数据为:messageList= ' + messageList.toString()); 68 | if(!mounted) return; 69 | setState(() { 70 | // 查找的信息不为空, 71 | if(messageList != null && messageList.isNotEmpty){ 72 | _messages = messageList; 73 | } 74 | }); 75 | print('信息详情页面,信息详情页的模型为:_messages= ' + _messages.toString()); 76 | 77 | } 78 | 79 | 80 | @override 81 | Widget build(BuildContext context) { 82 | 83 | // 初始化屏幕大小用于适配 84 | ScreenUtil.init(context, width:750,height:1334); 85 | 86 | return Scaffold( 87 | appBar: AppBar( 88 | leading: BackButton(onPressed:() => Navigator.of(context).pop()), 89 | title: Text(this._titles[this._messageType]), 90 | ), 91 | body: Consumer( 92 | builder: (context, jpushProvider, child){ 93 | return this._messages.isEmpty 94 | ? Center( 95 | child: Text(_advices[this._messageType],style: TextStyle( 96 | fontSize: 16, 97 | fontWeight: FontWeight.w400, 98 | wordSpacing: 2, 99 | color: Colors.black38, 100 | ), 101 | ), 102 | ) 103 | : RefreshIndicator( 104 | child: ListView.separated( 105 | itemBuilder: (context,index){ 106 | 107 | return GestureDetector( 108 | child: ListTile( 109 | leading: CircleAvatar( 110 | backgroundImage: NetworkImage(API.baseUri + (this._messages[index].fromUserAvatar.isNotEmpty ? this._messages[index].fromUserAvatar : '/images/avatars/bju_app.png'), 111 | ), 112 | ), 113 | title: Text(this._messages[index].title, style: TextStyle( 114 | fontSize: 16, 115 | fontWeight: FontWeight.w600, 116 | // letterSpacing: 0.8, 117 | color: Colors.black54 118 | ), 119 | ), 120 | subtitle: Column( 121 | mainAxisSize: MainAxisSize.min, 122 | mainAxisAlignment: MainAxisAlignment.center, 123 | crossAxisAlignment: CrossAxisAlignment.start, 124 | children: [ 125 | // 评论内容 126 | Text(this._messages[index].content, 127 | maxLines: 2, 128 | overflow: TextOverflow.ellipsis, 129 | style: TextStyle( 130 | fontSize: 14, 131 | fontWeight: FontWeight.w500, 132 | // letterSpacing: 1, 133 | color: Colors.black38, 134 | ), 135 | ), 136 | // 时间 137 | Text(BjuTimelineUtil.formatDateStr(this._messages[index].receivedTime), 138 | maxLines: 2, 139 | style:TextStyle( 140 | fontSize: 10, 141 | fontWeight: FontWeight.w500, 142 | color: Colors.lightBlue[400] 143 | ) 144 | ) 145 | ], 146 | ), 147 | trailing: this._messageType == 0 ? null : GestureDetector( 148 | child: Icon(FontAwesomeIcons.chevronRight, size: 32, color: Colors.lightBlue[300],), 149 | onTap: (){ 150 | print('去动态详情页面'); 151 | 152 | Navigator.push(context, MaterialPageRoute(builder: (context)=> MovingDetailsPage(this._messages[index].queryId))); 153 | 154 | }, 155 | ), 156 | ), 157 | onTap: (){ 158 | print('用户点击了评论列表$index'); 159 | }, 160 | ); 161 | }, 162 | separatorBuilder: (context,index){ 163 | return Divider(height: 5, thickness: 0.8, color: Colors.blueGrey[200],); 164 | }, 165 | itemCount: this._messages.length == 0 ? 1: this._messages.length 166 | ), 167 | onRefresh: _loadMessageByMessageType, 168 | ); 169 | 170 | } 171 | ), 172 | ); 173 | } 174 | } 175 | 176 | 177 | 178 | 179 | 180 | 181 | 182 | 183 | 184 | 185 | // (this._messages??List()).isNotEmpty ? ListView.separated( 186 | // itemBuilder: (context,index){ 187 | // return GestureDetector( 188 | // child: ListTile( 189 | // leading: ClipOval( 190 | // child: this._messages[index].fromUserAvatar.isNotEmpty ? Image.asset(this._messages[index].fromUserAvatar, fit: BoxFit.fill,) 191 | // : Image.asset('assets/avatar/default_avatar.jpg', fit: BoxFit.fill,), 192 | // ), 193 | // title: Text(this._messages[index].fromUserName), 194 | // subtitle: Column( 195 | // mainAxisSize: MainAxisSize.min, 196 | // mainAxisAlignment: MainAxisAlignment.center, 197 | // crossAxisAlignment: CrossAxisAlignment.start, 198 | // children: [ 199 | // // 评论内容 200 | // Text(this._messages[index].content), 201 | // // 时间 202 | // Text(this._messages[index].receivedTime, 203 | // maxLines: 2, 204 | // style:TextStyle( 205 | // fontSize: 10, 206 | // color: Colors.black54 207 | // ) 208 | // ) 209 | // ], 210 | // ), 211 | // trailing: GestureDetector( 212 | // child: Icon(FontAwesomeIcons.chevronCircleRight), 213 | // onTap: this._messageType == 0 ? null : (){ 214 | // print('去动态详情页面'); 215 | 216 | // //Navigator.push(context, MaterialPageRoute(builder: (context)=> MovingDetailsPage(int.parse(this._messages[index].queryId)))); 217 | 218 | // }, 219 | // ), 220 | // ), 221 | // onTap: (){ 222 | // print('用户点击了评论列表$index'); 223 | // }, 224 | // ); 225 | // }, 226 | // separatorBuilder: (context,index){ 227 | // return Divider(height: 5,color: Colors.black54,); 228 | // }, 229 | // itemCount: this._messages.length == 0 ? 1: this._messages.length 230 | // ) : Expanded( 231 | // child: Center( 232 | // child: Text(_advices[this._messageType]), 233 | // ) 234 | // ) 235 | 236 | 237 | 238 | 239 | 240 | 241 | 242 | 243 | 244 | -------------------------------------------------------------------------------- /lib/pages/mine/draft_moving_page.dart: -------------------------------------------------------------------------------- 1 | import 'package:bju_information_app/api/api.dart'; 2 | import 'package:bju_information_app/models/moving.dart'; 3 | import 'package:bju_information_app/models/response.dart'; 4 | import 'package:bju_information_app/net/bju_net.dart'; 5 | import 'package:bju_information_app/providers/login_provider.dart'; 6 | import 'package:bju_information_app/utils/bju_timeline_util.dart'; 7 | import 'package:flutter/material.dart'; 8 | import 'package:flutter_screenutil/flutter_screenutil.dart'; 9 | import 'package:getflutter/components/carousel/gf_carousel.dart'; 10 | import 'package:oktoast/oktoast.dart'; 11 | import 'package:provider/provider.dart'; 12 | 13 | /// 14 | /// 动态草稿页面 15 | /// 2020/05/03 09:59 16 | /// 17 | 18 | class DraftMovingPage extends StatefulWidget { 19 | DraftMovingPage({Key key}) : super(key: key); 20 | 21 | @override 22 | _DraftMovingPageState createState() => _DraftMovingPageState(); 23 | } 24 | 25 | class _DraftMovingPageState extends State { 26 | 27 | 28 | 29 | 30 | 31 | /// 动态集合 32 | List _movingList = []; 33 | 34 | 35 | 36 | @override 37 | void initState() { 38 | super.initState(); 39 | _initPageData(); 40 | } 41 | 42 | 43 | 44 | 45 | 46 | /// 初始化页面数据 47 | Future _initPageData() async { 48 | final int userId = Provider.of(context, listen: false).loginUser.userId; 49 | final ResponseData resData = await BjuHttp.get(API.draftMovingWithUser + userId.toString()) 50 | .then((onValue) => ResponseData.fromJson(onValue.data)) 51 | .catchError((onError){ 52 | print('请求草稿箱列表出错了!'); 53 | print(onError); 54 | showToast('服务器请求异常!'); 55 | }); 56 | if(resData == null){ 57 | showToast('请求失败!'); 58 | return; 59 | } 60 | print(resData.message); 61 | if(resData.statusCode == 1) return; 62 | if(!mounted) return; 63 | setState(() { 64 | _movingList = (resData.res as List).map((m) => Moving.fromJson(m)).toList(); 65 | }); 66 | } 67 | 68 | /// 69 | /// 主体布局 70 | /// 71 | Widget _bodyLayout(){ 72 | 73 | final TextStyle noticeTextStyle = TextStyle( 74 | fontSize: 18, 75 | fontWeight: FontWeight.w500, 76 | color: Colors.black38, 77 | wordSpacing: 2, 78 | ); 79 | 80 | return _movingList.isEmpty ? Center(child: Text('暂无动态信息!',style: noticeTextStyle,),) : ListView.separated( 81 | itemBuilder: (context,index){ 82 | return Card( 83 | elevation: 4, 84 | margin: EdgeInsets.fromLTRB(2, 5, 2, index != (_movingList.length - 1) ? 0 : 10), 85 | shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(6)), 86 | child: Column( 87 | mainAxisAlignment: MainAxisAlignment.center, 88 | crossAxisAlignment: CrossAxisAlignment.start, 89 | children: [ 90 | // 时间线 91 | Padding( 92 | padding: EdgeInsets.only(top: 5, left: 10), 93 | child: Text(BjuTimelineUtil.formatDateStr(_movingList[index].movingCreateTime), style: TextStyle( 94 | fontSize: 14, 95 | fontWeight: FontWeight.w600, 96 | color: Colors.lightBlue, 97 | letterSpacing: 2.0, 98 | ),), 99 | ), 100 | Divider(color: Colors.grey[400], thickness: 0.5,), 101 | // 内容 102 | Padding( 103 | padding: EdgeInsets.fromLTRB(10,0,10,10), 104 | child: Text(_movingList[index].movingContent, style: TextStyle( 105 | fontSize: 15, 106 | fontWeight: FontWeight.w500, 107 | color: Colors.black54, 108 | ),), 109 | ), 110 | // 图片区域 111 | _movingList[index].movingImages == null || _movingList[index].movingImages.isEmpty 112 | ? SizedBox() 113 | : GFCarousel( 114 | items: _movingList[index].movingImages.map( 115 | (url) => Container( 116 | margin: EdgeInsets.all(8.0), 117 | child: ClipRRect( 118 | borderRadius: BorderRadius.all(Radius.circular(8.0)), 119 | child: FadeInImage.assetNetwork( 120 | placeholder: 'assets/gif/loading.jpg', 121 | image: API.baseUri + url, 122 | fit: BoxFit.cover, 123 | width: ScreenUtil().setWidth(1000.0), 124 | height: ScreenUtil().setHeight(500.0), 125 | ), 126 | ), 127 | ) 128 | ).toList(), 129 | // onPageChanged: (index) { 130 | // setState(() { 131 | // index; 132 | // }); 133 | // }, 134 | ), 135 | Divider(color: Colors.grey[400], thickness: 0.5,), 136 | // 动态类型及标签 137 | Row( 138 | mainAxisSize: MainAxisSize.max, 139 | mainAxisAlignment: MainAxisAlignment.start, 140 | children: [ 141 | Padding( 142 | padding: EdgeInsets.only(left:6,right:3), 143 | child: Text(_movingList[index].movingType??'出错了',style: TextStyle( 144 | fontSize: 16, 145 | color: Colors.orange, 146 | fontWeight: FontWeight.w500, 147 | ),), 148 | ), 149 | Expanded( 150 | child: Padding( 151 | padding: EdgeInsets.all(0), 152 | child: _movingList[index].movingTopics == null 153 | ? Text("没有标签信息!") 154 | : Wrap( 155 | spacing: 1, 156 | runAlignment: WrapAlignment.center, 157 | children: _movingList[index].movingTopics.map( 158 | (t) => ChoiceChip( 159 | // avatar: Icon(FontAwesomeIcons.heart), 160 | selectedColor: Colors.grey[300], // 选中时的颜色 161 | label: Text(t, 162 | style: TextStyle( 163 | color: Colors.lightBlue, 164 | fontSize: 12, 165 | fontWeight: FontWeight.w300 166 | ), 167 | ), 168 | selected: true, 169 | ) 170 | ).toList(), 171 | ), 172 | ), 173 | ) 174 | ], 175 | ), 176 | // 操作 177 | Padding( 178 | padding: EdgeInsets.all(4), 179 | child: Row( 180 | mainAxisSize: MainAxisSize.max, 181 | mainAxisAlignment: MainAxisAlignment.spaceEvenly, 182 | children: [ 183 | RaisedButton( 184 | color: Colors.lightBlue, 185 | child: Text('发布', style: TextStyle( 186 | fontSize: 15, 187 | fontWeight: FontWeight.w500, 188 | letterSpacing: 2.0, 189 | color: Colors.white, 190 | ),), 191 | onPressed: () async { 192 | 193 | // showToastWidget( 194 | // CircularProgressIndicator(backgroundColor: Colors.black38), 195 | // context: context, 196 | // duration: Duration(milliseconds: 1500), 197 | // ); 198 | 199 | final int movingId = _movingList[index].movingId??0; 200 | print('草稿箱动态发布:movingId=' + movingId.toString()); 201 | if(movingId == 0){ 202 | showToast('无效ID'); 203 | return; 204 | } 205 | final ResponseData resData = await BjuHttp.put(API.republishMoving + movingId.toString()) 206 | .then((onValue) => ResponseData.fromJson(onValue.data)) 207 | .catchError((onError){ 208 | print('草稿箱动态发布请求异常!'); 209 | print(onError); 210 | showToast('请求服务器异常!'); 211 | }); 212 | if(resData == null){ 213 | showToast('请求失败!'); 214 | return; 215 | } 216 | showToast(resData.message); 217 | if(resData.statusCode == 1){ 218 | return; 219 | } 220 | if(!mounted) return; 221 | setState(() { 222 | _movingList.removeAt(index); 223 | }); 224 | }, 225 | ), 226 | RaisedButton( 227 | color: Colors.red[400], 228 | child: Text('删除',style: TextStyle( 229 | fontSize: 15, 230 | fontWeight: FontWeight.w500, 231 | letterSpacing: 2.0, 232 | color: Colors.white, 233 | )), 234 | onPressed: () async { 235 | 236 | // showToastWidget( 237 | // CircularProgressIndicator(backgroundColor: Colors.black38), 238 | // context: context, 239 | // duration: Duration(milliseconds: 1500), 240 | // ); 241 | 242 | final int movingId = _movingList[index].movingId??0; 243 | print('草稿箱删除动态:movingId=' + movingId.toString()); 244 | if(movingId == 0){ 245 | showToast('无效ID'); 246 | return; 247 | } 248 | final ResponseData resData = await BjuHttp.delete(API.deleteMoving + movingId.toString()) 249 | .then((onValue) => ResponseData.fromJson(onValue.data)) 250 | .catchError((onError){ 251 | print('草稿箱动态删除请求异常!'); 252 | print(onError); 253 | showToast('请求服务器异常!'); 254 | }); 255 | if(resData == null){ 256 | showToast('请求失败!'); 257 | return; 258 | } 259 | showToast(resData.message); 260 | if(resData.statusCode == 1){ 261 | return; 262 | } 263 | if(!mounted) return; 264 | setState(() { 265 | _movingList.removeAt(index); 266 | }); 267 | }, 268 | ), 269 | ], 270 | ), 271 | ) 272 | 273 | ], 274 | ), 275 | ); 276 | }, 277 | separatorBuilder: (context,index) => Divider(indent: 2, endIndent: 2,), 278 | itemCount: _movingList.length 279 | ); 280 | 281 | } 282 | 283 | 284 | 285 | 286 | 287 | @override 288 | Widget build(BuildContext context) { 289 | 290 | // 初始化屏幕大小用于适配 291 | ScreenUtil.init(context, width:750,height:1334); 292 | 293 | return Scaffold( 294 | appBar: AppBar( 295 | leading: BackButton(onPressed:()=>Navigator.of(context).pop()), 296 | title: Text('草稿箱'), 297 | ), 298 | body: _bodyLayout(), 299 | ); 300 | } 301 | } 302 | -------------------------------------------------------------------------------- /lib/pages/mine/all_moving_page.dart: -------------------------------------------------------------------------------- 1 | import 'package:bju_information_app/api/api.dart'; 2 | import 'package:bju_information_app/models/moving.dart'; 3 | import 'package:bju_information_app/models/response.dart'; 4 | import 'package:bju_information_app/net/bju_net.dart'; 5 | import 'package:bju_information_app/pages/details/moving_details.dart'; 6 | import 'package:bju_information_app/providers/login_provider.dart'; 7 | import 'package:bju_information_app/utils/bju_timeline_util.dart'; 8 | import 'package:flutter/material.dart'; 9 | import 'package:flutter_screenutil/flutter_screenutil.dart'; 10 | import 'package:font_awesome_flutter/font_awesome_flutter.dart'; 11 | import 'package:oktoast/oktoast.dart'; 12 | import 'package:provider/provider.dart'; 13 | 14 | /// 15 | /// 所有动态页面 16 | /// 2020/05/03 09:43 17 | /// 18 | 19 | class AllMovingPage extends StatefulWidget { 20 | AllMovingPage({Key key}) : super(key: key); 21 | 22 | @override 23 | _AllMovingPageState createState() => _AllMovingPageState(); 24 | } 25 | 26 | class _AllMovingPageState extends State { 27 | 28 | 29 | 30 | /// 动态集合 31 | List _movingList = []; 32 | 33 | 34 | 35 | @override 36 | void initState() { 37 | super.initState(); 38 | _initPageData(); 39 | } 40 | 41 | 42 | /// 初始化页面数据 43 | Future _initPageData() async { 44 | final int userId = Provider.of(context, listen: false).loginUser.userId; 45 | final ResponseData resData = await BjuHttp.get(API.allMovingWithUser + userId.toString()) 46 | .then((onValue) => ResponseData.fromJson(onValue.data)) 47 | .catchError((onError){ 48 | print('请求用户动态列表出错了!'); 49 | print(onError); 50 | showToast('服务器请求异常!'); 51 | }); 52 | if(resData == null){ 53 | showToast('请求失败!'); 54 | return; 55 | } 56 | print(resData.message); 57 | if(resData.statusCode == 1) return; 58 | if(!mounted) return; 59 | setState(() { 60 | _movingList = (resData.res as List).map((m) => Moving.fromJson(m)).toList(); 61 | }); 62 | } 63 | 64 | /// 65 | /// 主体布局 66 | /// 67 | Widget _bodyLayout(){ 68 | 69 | final TextStyle noticeTextStyle = TextStyle( 70 | fontSize: 18, 71 | fontWeight: FontWeight.w500, 72 | color: Colors.black38, 73 | wordSpacing: 2, 74 | ); 75 | 76 | return _movingList.isEmpty ? Center(child: Text('暂无动态信息!',style: noticeTextStyle,),) : ListView.separated( 77 | itemBuilder: (context,index){ 78 | return Container( 79 | margin: EdgeInsets.only(top: index == 0 ? 10 : 0,bottom: index == _movingList.length - 1 ? 15 : 0,), 80 | height: ScreenUtil().setHeight(300), 81 | child: Row( 82 | mainAxisSize: MainAxisSize.max, 83 | mainAxisAlignment: MainAxisAlignment.start, 84 | children: [ 85 | Container( 86 | margin: EdgeInsets.only(left: 10), 87 | color: Colors.white54, 88 | child: Text(BjuTimelineUtil.formatDateStr(_movingList[index].movingCreateTime), style: TextStyle( 89 | fontSize: 16, 90 | fontWeight: FontWeight.w500, 91 | color: Colors.lightBlue, 92 | ),), 93 | ), 94 | VerticalDivider(color: Colors.lightBlue,), 95 | Expanded( 96 | child: Column( 97 | mainAxisSize: MainAxisSize.max, 98 | mainAxisAlignment: MainAxisAlignment.start, 99 | crossAxisAlignment: CrossAxisAlignment.start, 100 | children: [ 101 | Expanded( 102 | child: Row( 103 | mainAxisSize: MainAxisSize.max, 104 | mainAxisAlignment: MainAxisAlignment.start, 105 | children: [ 106 | Expanded(child: Text(_movingList[index].movingContent, style: TextStyle( 107 | fontSize: 15, 108 | fontWeight: FontWeight.w400, 109 | color: Colors.black54 110 | ),)), 111 | Padding( 112 | padding: EdgeInsets.only(left: 3, right: 10), 113 | child: Column( 114 | mainAxisSize: MainAxisSize.max, 115 | mainAxisAlignment: MainAxisAlignment.center, 116 | children: [ 117 | MaterialButton( 118 | minWidth: ScreenUtil().setWidth(60), 119 | height: ScreenUtil().setHeight(60), 120 | color: Colors.red[400], 121 | child: Text('删除',style: TextStyle( 122 | fontSize: 12, 123 | color: Colors.white, 124 | letterSpacing: 1.0, 125 | ),), 126 | onPressed: () async { 127 | 128 | // showToastWidget( 129 | // CircularProgressIndicator(backgroundColor: Colors.black38), 130 | // context: context, 131 | // duration: Duration(milliseconds: 1500), 132 | // ); 133 | 134 | final int movingId = _movingList[index].movingId??0; 135 | print('删除动态: movingId=' + movingId.toString()); 136 | if(movingId == 0){ 137 | showToast('无效ID'); 138 | return; 139 | } 140 | final ResponseData resData = await BjuHttp.delete(API.deleteMoving + movingId.toString()) 141 | .then((onValue) => ResponseData.fromJson(onValue.data)) 142 | .catchError((onError){ 143 | print('删除动态请求异常!'); 144 | print(onError); 145 | showToast('请求服务器异常!'); 146 | }); 147 | if(resData == null) { 148 | showToast('请求失败!'); 149 | return; 150 | } 151 | showToast(resData.message); 152 | if(resData.statusCode == 1){ 153 | return; 154 | } 155 | if(!mounted) return; 156 | setState(() { 157 | _movingList.removeAt(index); 158 | }); 159 | }, 160 | ), 161 | MaterialButton( 162 | minWidth: ScreenUtil().setWidth(60), 163 | height: ScreenUtil().setHeight(60), 164 | color: Colors.lightBlue, 165 | child: Text('详情',style: TextStyle( 166 | fontSize: 12, 167 | color: Colors.white 168 | ),), 169 | onPressed: (){ 170 | final int movingId =_movingList[index]?.movingId??0; 171 | if(movingId == 0){ 172 | showToast('无效ID'); 173 | return; 174 | } 175 | // 跳转到动态详情页面 176 | Navigator.push>(context, MaterialPageRoute(builder: (context)=> MovingDetailsPage(movingId))); 177 | }, 178 | ) 179 | ], 180 | ), 181 | ), 182 | ], 183 | ) 184 | ), 185 | Divider(endIndent: 5, color: Colors.grey[400],), 186 | Row( 187 | mainAxisSize: MainAxisSize.max, 188 | mainAxisAlignment: MainAxisAlignment.end, 189 | children: [ 190 | Expanded( 191 | child: Text(_movingList[index].movingType,style: TextStyle( 192 | fontSize: 16, 193 | fontWeight: FontWeight.w600, 194 | color: Colors.lightBlue, 195 | letterSpacing: 0.5, 196 | ),) 197 | ), 198 | Padding( 199 | padding: EdgeInsets.only(left: 5), 200 | child: Row( 201 | mainAxisSize: MainAxisSize.min, 202 | mainAxisAlignment: MainAxisAlignment.spaceAround, 203 | children: [ 204 | Icon(FontAwesomeIcons.eye, color: Colors.lightBlue[400],size: 14), 205 | Padding( 206 | padding: EdgeInsets.only(left: 3), 207 | child: Text(_movingList[index]?.movingBrowse?.toString()??'0',style: TextStyle( 208 | fontSize: 12, 209 | color: Colors.black38 210 | ),), 211 | ), 212 | ], 213 | ), 214 | ), 215 | Padding( 216 | padding: EdgeInsets.only(left: 5), 217 | child: Row( 218 | mainAxisSize: MainAxisSize.min, 219 | mainAxisAlignment: MainAxisAlignment.spaceAround, 220 | children: [ 221 | Icon(FontAwesomeIcons.solidThumbsUp, color: Colors.lightBlue[400],size: 14), 222 | Padding( 223 | padding: EdgeInsets.only(left: 3), 224 | child: Text(_movingList[index]?.movingLike?.toString()??'0',style: TextStyle( 225 | fontSize: 12, 226 | color: Colors.black38 227 | ),), 228 | ), 229 | ], 230 | ), 231 | ), 232 | Padding( 233 | padding: EdgeInsets.only(left: 5, right: 20), 234 | child: Row( 235 | mainAxisSize: MainAxisSize.min, 236 | mainAxisAlignment: MainAxisAlignment.spaceAround, 237 | children: [ 238 | Icon(FontAwesomeIcons.commentDots, color: Colors.lightBlue[400],size: 14), 239 | Padding( 240 | padding: EdgeInsets.only(left: 3), 241 | child: Text(_movingList[index]?.movingCommentCount?.toString()??'0',style: TextStyle( 242 | fontSize: 12, 243 | color: Colors.black38 244 | ),), 245 | ), 246 | ], 247 | ), 248 | ), 249 | ], 250 | ) 251 | ], 252 | ) 253 | ), 254 | ], 255 | ) 256 | ); 257 | }, 258 | separatorBuilder: (context,index) => Divider(indent: 2, endIndent: 2, color: Colors.lightBlue[600], thickness: 1.0,), 259 | itemCount: _movingList.length 260 | ); 261 | 262 | } 263 | 264 | 265 | 266 | 267 | @override 268 | Widget build(BuildContext context) { 269 | 270 | // 初始化屏幕大小用于适配 271 | ScreenUtil.init(context, width:750,height:1334); 272 | 273 | return Scaffold( 274 | appBar: AppBar( 275 | leading: BackButton(onPressed:()=>Navigator.of(context).pop()), 276 | title: Text('全部动态'), 277 | ), 278 | body: _bodyLayout(), 279 | ); 280 | } 281 | } 282 | 283 | -------------------------------------------------------------------------------- /lib/pages/modules/part_time_recruitment.dart: -------------------------------------------------------------------------------- 1 | import 'package:bju_information_app/api/api.dart'; 2 | import 'package:bju_information_app/models/moving.dart'; 3 | import 'package:bju_information_app/models/response.dart'; 4 | import 'package:bju_information_app/net/bju_net.dart'; 5 | import 'package:bju_information_app/pages/details/moving_details.dart'; 6 | import 'package:bju_information_app/pages/publish_page.dart'; 7 | import 'package:bju_information_app/utils/bju_timeline_util.dart'; 8 | import 'package:flutter/material.dart'; 9 | import 'package:flutter_screenutil/flutter_screenutil.dart' as SU; 10 | import 'package:font_awesome_flutter/font_awesome_flutter.dart'; 11 | import 'package:oktoast/oktoast.dart'; 12 | 13 | /// 14 | /// 兼职招聘 15 | /// 2020/04/06 17:32 16 | /// 17 | class RecruitmentModule extends StatefulWidget { 18 | RecruitmentModule({Key key}) : super(key: key); 19 | 20 | @override 21 | _RecruitmentModuleState createState() => _RecruitmentModuleState(); 22 | } 23 | 24 | class _RecruitmentModuleState extends State { 25 | 26 | /// 模块ID编号 27 | int _moduleId = 4; 28 | 29 | /// 滑动控制器 30 | ScrollController _scrollController = ScrollController(); 31 | 32 | /// 显示编辑按钮 33 | bool _showEditButton = true; 34 | 35 | /// 兼职招聘模块页面数据列表 36 | List _recruitmentDataList = []; 37 | 38 | @override 39 | void initState() { 40 | super.initState(); 41 | // 为滑动控制器添加监听器 42 | _scrollController.addListener(_scrollControllerListener); 43 | } 44 | 45 | @override 46 | void didChangeDependencies() { 47 | super.didChangeDependencies(); 48 | // 初始化模块页面数据 49 | _loadModuleData(); 50 | } 51 | 52 | @override 53 | void dispose() { 54 | 55 | super.dispose(); 56 | } 57 | 58 | /// 滑动控制器的监听器 59 | void _scrollControllerListener(){ 60 | 61 | // print('滑动方向:' + _scrollController.position.axis.toString()); 62 | // 获取当前的偏移量 63 | final double currentOffset = _scrollController.offset; 64 | // print('兼职招聘,最滚动滑动距离:maxScrollExtent= ' + ( _scrollController.position.maxScrollExtent.toString())); 65 | // print('兼职招聘,当前滑动位置:currentOffset= ' + currentOffset.toString()); 66 | // 获取设备的Size 67 | final Size size = MediaQuery.of(context).size; 68 | // print('兼职招聘,窗口大小:height= ' + (1 * size.height).toString()); 69 | // 判断当前的距离是否滑出第一个页面 70 | if(currentOffset > (size.height / 2)){ 71 | // 改按钮状态 72 | if(!mounted) return; 73 | setState(() { 74 | _showEditButton = false; 75 | }); 76 | } else { 77 | // 改按钮状态 78 | if(!mounted) return; 79 | setState(() { 80 | _showEditButton = true; 81 | }); 82 | } 83 | 84 | } 85 | 86 | Future _loadModuleData() async { 87 | // 向服务器请求数据信息 88 | ResponseData resData = await BjuHttp.get(API.queryMovingByModuleId + _moduleId.toString()) 89 | .then((onValue) => ResponseData.fromJson(onValue.data)) 90 | .catchError((onError){ 91 | print('兼职招聘模块请求数据异常!'); 92 | print(onError); 93 | showToast('请求服务器异常!'); 94 | }); 95 | // if(resData == null){ 96 | // showToast('网络请求失败!'); 97 | // return; 98 | // } 99 | // 失败响应 100 | if(resData != null && resData.statusCode == 1){ 101 | showToast(resData.message); 102 | return; 103 | } 104 | if(!mounted) return; 105 | setState(() { 106 | // 刷新数据信息 107 | _recruitmentDataList = (resData.res as List).map((m) => Moving.fromJson(m)).toList(); 108 | }); 109 | } 110 | 111 | /// 构建模块体 112 | Widget _moduleBodyLayout(){ 113 | 114 | return RefreshIndicator( 115 | child: _recruitmentDataList.isEmpty 116 | ? Center(child: Text('暂无招聘兼职类信息!', style: TextStyle( 117 | fontSize: 18, 118 | fontWeight: FontWeight.w500, 119 | color: Colors.black38, 120 | wordSpacing: 2, 121 | ) 122 | ) 123 | ) 124 | : ListView.separated( 125 | controller: _scrollController, 126 | itemBuilder: (context,index){ 127 | 128 | // 底部左侧数字的样式(赞,浏览,评论) 129 | final TextStyle bottomCountTextStyle = TextStyle( 130 | fontSize: 13, 131 | color: Colors.black38, 132 | ); 133 | 134 | return Card( 135 | elevation: 4, 136 | margin: EdgeInsets.fromLTRB(4, 10, 4, index != (_recruitmentDataList.length - 1) ? 0 : 10), 137 | shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(6)), 138 | child: Column( 139 | mainAxisSize: MainAxisSize.min, 140 | mainAxisAlignment: MainAxisAlignment.center, 141 | children: [ 142 | 143 | // 用户于时间信息 144 | Row( 145 | mainAxisAlignment: MainAxisAlignment.start, 146 | children: [ 147 | Padding( 148 | padding: EdgeInsets.fromLTRB(5, 8, 5, 0), 149 | child: Text('该兼职信息由 ' + _recruitmentDataList[index].movingAuthorName + ' 发布', style: TextStyle( 150 | fontSize: 14, 151 | fontWeight: FontWeight.w500, 152 | color: Colors.lightBlue 153 | ),), 154 | ), 155 | Padding( 156 | padding: EdgeInsets.fromLTRB(5, 11, 5, 0), 157 | child: Text(BjuTimelineUtil.formatDateStr(_recruitmentDataList[index].movingCreateTime), style: TextStyle( 158 | fontSize: 12, 159 | fontWeight: FontWeight.w300, 160 | color: Colors.blueGrey[300], 161 | //textBaseline: TextBaseline.ideographic 162 | ),), 163 | ) 164 | ], 165 | ), 166 | Divider(color: Colors.blueGrey,), 167 | // 信息主体 168 | Padding( 169 | padding: EdgeInsets.fromLTRB(8, 0, 8, 0), 170 | child: Text(_recruitmentDataList[index].movingContent, style: TextStyle( 171 | fontSize: 14, 172 | // fontWeight: FontWeight.w500, 173 | // color: Colors.white54, 174 | wordSpacing: 2, 175 | ),), 176 | ), 177 | Divider(), 178 | // 底部信息 179 | Row( 180 | mainAxisAlignment: MainAxisAlignment.end, 181 | children: [ 182 | // 左侧(赞,浏览,评论) 183 | Expanded( 184 | child: Row( 185 | mainAxisAlignment: MainAxisAlignment.start, 186 | children: [ 187 | // 赞 188 | Padding( 189 | padding: EdgeInsets.fromLTRB(10, 0, 5, 5), 190 | child: Row( 191 | mainAxisSize: MainAxisSize.min, 192 | // mainAxisAlignment: MainAxisAlignment.spaceBetween, 193 | children: [ 194 | Padding( 195 | padding: EdgeInsets.only(right: 2), 196 | child: Icon(FontAwesomeIcons.thumbsUp, size: 14, color: Colors.black38,), 197 | ), 198 | Text(_recruitmentDataList[index].movingLike.toString(), style: bottomCountTextStyle,), 199 | ], 200 | ), 201 | ), 202 | // 浏览 203 | Padding( 204 | padding: EdgeInsets.fromLTRB(2, 0, 5, 5), 205 | child: Row( 206 | mainAxisSize: MainAxisSize.min, 207 | mainAxisAlignment: MainAxisAlignment.center, 208 | children: [ 209 | Padding( 210 | padding: EdgeInsets.only(right: 2), 211 | child: Icon(FontAwesomeIcons.eye, size: 14, color: Colors.black38,), 212 | ), 213 | Text(_recruitmentDataList[index].movingBrowse.toString(), style: bottomCountTextStyle,), 214 | ], 215 | ), 216 | ), 217 | // 评论 218 | Padding( 219 | padding: EdgeInsets.fromLTRB(2, 0, 5, 5), 220 | child: Row( 221 | mainAxisSize: MainAxisSize.min, 222 | mainAxisAlignment: MainAxisAlignment.center, 223 | children: [ 224 | Padding( 225 | padding: EdgeInsets.only(right: 2), 226 | child: Icon(FontAwesomeIcons.comment, size: 14, color: Colors.black38,), 227 | ), 228 | Text(_recruitmentDataList[index].movingCommentCount.toString(), style: bottomCountTextStyle,), 229 | ], 230 | ), 231 | ), 232 | 233 | ], 234 | ) 235 | ), 236 | // 右侧(详情点击) 237 | Padding( 238 | padding: EdgeInsets.fromLTRB(10, 5, 10, 5), 239 | child: GestureDetector( 240 | child: Text('查看详情', style: TextStyle( 241 | fontSize: 16, 242 | fontWeight: FontWeight.w500, 243 | color: Colors.lightBlue, 244 | wordSpacing: 2, 245 | ),), 246 | onTap: () { 247 | final int movingId = _recruitmentDataList[index].movingId??0; 248 | // 前往详情页面 249 | print('兼职招聘模块前往详情页面: ' + movingId.toString()); 250 | if(movingId == 0){ 251 | print('跳转的动态ID不存在!'); 252 | showToast('动态信息有误!'); 253 | return; 254 | } 255 | // 跳转到动态详情页面 256 | Navigator.push>(context, MaterialPageRoute(builder: (context)=> MovingDetailsPage(movingId) )); 257 | return; 258 | }, 259 | ), 260 | ) 261 | ], 262 | ) 263 | 264 | ], 265 | ), 266 | ); 267 | }, 268 | separatorBuilder: (context,index) => SizedBox(height: SU.ScreenUtil().setHeight(10)), 269 | itemCount: _recruitmentDataList.length, 270 | ), 271 | onRefresh: _loadModuleData, 272 | ); 273 | } 274 | 275 | 276 | @override 277 | Widget build(BuildContext context) { 278 | 279 | // 屏幕适配 280 | SU.ScreenUtil.init(context, width:750,height:1334); 281 | 282 | return Scaffold( 283 | appBar: AppBar( 284 | leading: BackButton(onPressed:(){ 285 | Navigator.of(context).pop(); 286 | }), 287 | title: Text('兼职招聘'), 288 | ), 289 | body: _moduleBodyLayout(), 290 | // bottomNavigationBar: _bottomNav(), 291 | floatingActionButton: _showEditButton 292 | ? FlatButton( 293 | color: Colors.lightBlue, 294 | padding: EdgeInsets.all(12), 295 | shape: CircleBorder(side: BorderSide(color: Colors.white70)), 296 | child: Icon(FontAwesomeIcons.solidEdit, color: Colors.white,), 297 | onPressed: (){ 298 | // 跳转到发布页面 299 | Navigator.of(context).push(MaterialPageRoute(builder: (context) => PublishPage(4))); 300 | }, 301 | ) 302 | : FlatButton( 303 | color: Colors.lightBlue, 304 | padding: EdgeInsets.all(12), 305 | shape: CircleBorder(side: BorderSide(color: Colors.white70)), 306 | child: Icon(FontAwesomeIcons.arrowUp, color: Colors.white), 307 | onPressed: (){ 308 | // 返回顶部 309 | _scrollController.jumpTo(0); 310 | print('跳转到页面顶部...'); 311 | return; 312 | }, 313 | ), 314 | floatingActionButtonLocation: FloatingActionButtonLocation.endFloat, 315 | ); 316 | 317 | } 318 | } -------------------------------------------------------------------------------- /lib/pages/modules/wall_and_you.dart: -------------------------------------------------------------------------------- 1 | import 'package:bju_information_app/api/api.dart'; 2 | import 'package:bju_information_app/models/moving.dart'; 3 | import 'package:bju_information_app/models/response.dart'; 4 | import 'package:bju_information_app/net/bju_net.dart'; 5 | import 'package:bju_information_app/pages/details/moving_details.dart'; 6 | import 'package:bju_information_app/utils/bju_timeline_util.dart'; 7 | import 'package:flutter/material.dart'; 8 | import 'package:flutter_screenutil/flutter_screenutil.dart'; 9 | import 'package:font_awesome_flutter/font_awesome_flutter.dart'; 10 | import 'package:getflutter/components/carousel/gf_carousel.dart'; 11 | import 'package:oktoast/oktoast.dart'; 12 | 13 | /// 14 | /// 墙上有你 15 | /// 2020/04/06 17:31 16 | /// 17 | class WallModule extends StatefulWidget { 18 | WallModule({Key key}) : super(key: key); 19 | 20 | @override 21 | _WallModuleState createState() => _WallModuleState(); 22 | } 23 | 24 | class _WallModuleState extends State { 25 | 26 | /// 模块ID号 [1:表白墙;2:万能墙] 27 | int _moduleId = 1; 28 | 29 | /// 页面索引号 [0:表白墙;1:万能墙] 30 | int _wallType = 0; 31 | 32 | /// 滚动控制器 33 | ScrollController _wallScrollController = ScrollController(); 34 | 35 | /// 数据列表 36 | List _wallDataList = []; 37 | 38 | 39 | @override 40 | void initState() { 41 | super.initState(); 42 | 43 | } 44 | 45 | @override 46 | void didChangeDependencies() { 47 | super.didChangeDependencies(); 48 | // 初始化页面数据 49 | _loadData(); 50 | } 51 | 52 | @override 53 | void dispose() { 54 | _wallScrollController.dispose(); 55 | super.dispose(); 56 | } 57 | 58 | 59 | /// 加载数据列表 60 | void _loadData() async { 61 | // 向服务器请求数据 62 | ResponseData resData = await BjuHttp.get(API.queryMovingByModuleId + _moduleId.toString()) 63 | .then((onValue) => ResponseData.fromJson(onValue.data)) 64 | .catchError((onError){ 65 | print('墙上有你模块请求数据异常!'); 66 | print(onError); 67 | showToast('请求服务器异常!'); 68 | }); 69 | if(resData == null){ 70 | // showToast('网络请求失败!'); 71 | return; 72 | } 73 | // 失败响应 74 | if(resData != null && resData.statusCode == 1){ 75 | showToast(resData.message); 76 | return; 77 | } 78 | if(!mounted) return; 79 | setState(() { 80 | // 刷新数据信息 81 | _wallDataList = (resData.res as List).map((m) => Moving.fromJson(m)).toList(); 82 | }); 83 | 84 | } 85 | 86 | /// 加载动画 87 | void _showLoadding(){ 88 | // 弹出加载框 89 | showToastWidget( 90 | Container( 91 | width: MediaQuery.of(context).size.width, 92 | height: MediaQuery.of(context).size.height, 93 | color: Colors.white54, 94 | child: Center( 95 | child: Column( 96 | mainAxisSize: MainAxisSize.min, 97 | mainAxisAlignment: MainAxisAlignment.center, 98 | children: [ 99 | CircularProgressIndicator(), 100 | SizedBox(height: ScreenUtil().setHeight(6),), 101 | Text('加载中...', style: TextStyle( 102 | fontSize: 16, 103 | fontWeight: FontWeight.w500, 104 | wordSpacing: 2, 105 | color: Colors.lightBlue, 106 | ),), 107 | ], 108 | ), 109 | ), 110 | ), 111 | duration: Duration(seconds: 3), 112 | // position: ToastPosition.center, 113 | ); 114 | } 115 | 116 | /// 底部导航栏 117 | Widget _bottomNav(){ 118 | 119 | // 底部导航的文字样式 120 | /* final TextStyle bottomNavTextStyle = TextStyle( 121 | fontSize: 16, 122 | fontWeight: FontWeight.w400, 123 | wordSpacing: 3, 124 | color: Colors.lightBlue, 125 | ); */ 126 | return Container( 127 | width: MediaQuery.of(context).size.width, 128 | height: ScreenUtil().setHeight(100), 129 | decoration: BoxDecoration( 130 | color: Colors.white, 131 | boxShadow: kElevationToShadow[2], 132 | borderRadius: BorderRadius.vertical(top:Radius.circular(10)) 133 | ), 134 | child: Row( 135 | mainAxisAlignment: MainAxisAlignment.spaceAround, 136 | children:[ 137 | Padding( 138 | padding: EdgeInsets.only(top: 8, bottom: 8), 139 | child: GestureDetector( 140 | child: Text('表白墙',style: TextStyle( 141 | fontSize: 20, 142 | fontWeight: FontWeight.w500, 143 | wordSpacing: 3, 144 | color: _wallType != 0 ? Colors.black54 : Colors.lightBlue, 145 | // backgroundColor: Colors.white54, 146 | ), 147 | ), 148 | onTap: (){ 149 | if(_wallType == 0){ 150 | print('点击了表白墙(重复点击时显示)'); 151 | return; 152 | } 153 | if(!mounted) return; 154 | setState(() { 155 | _wallType = 0; 156 | _moduleId = 1; 157 | print('点击了表白墙:'+_wallType.toString()); 158 | // 查询数据 159 | _loadData(); 160 | }); 161 | }, 162 | onDoubleTap: (){ 163 | _showLoadding(); 164 | // 刷新数据 165 | _loadData(); 166 | }, 167 | ), 168 | ), 169 | VerticalDivider(indent: ScreenUtil().setHeight(30), endIndent: ScreenUtil().setHeight(30),color: Colors.grey[400],), 170 | Padding( 171 | padding: EdgeInsets.only(top: 8, bottom: 8), 172 | child: GestureDetector( 173 | child: Text('万能墙',style: TextStyle( 174 | fontSize: 20, 175 | fontWeight: FontWeight.w500, 176 | wordSpacing: 3, 177 | color: _wallType != 1 ? Colors.black54 : Colors.lightBlue, 178 | // backgroundColor: Colors.white54, 179 | ), 180 | ), 181 | onTap: () async { 182 | if(_wallType == 1){ 183 | print('点击了万能墙(重复点击时显示)'); 184 | return; 185 | } 186 | if(!mounted) return; 187 | setState(() { 188 | _wallType = 1; 189 | _moduleId = 2; 190 | print('点击了万能墙:'+_wallType.toString()); 191 | // 查询数据 192 | _loadData(); 193 | 194 | }); 195 | }, 196 | onDoubleTap: (){ // 双击刷新 197 | _showLoadding(); 198 | // 请求数据 199 | _loadData(); 200 | }, 201 | ), 202 | ), 203 | ] 204 | ), 205 | ); 206 | } 207 | 208 | /// 图片布局 209 | Widget _imageLayout(List imageList){ 210 | 211 | return GFCarousel( 212 | items: imageList.map( 213 | (url) { 214 | return Container( 215 | margin: EdgeInsets.all(8.0), 216 | child: ClipRRect( 217 | borderRadius: BorderRadius.all(Radius.circular(8.0)), 218 | child: Image.network( 219 | url, 220 | fit: BoxFit.cover, 221 | width: ScreenUtil().setWidth(1000.0), 222 | height: ScreenUtil().setHeight(500.0), 223 | ), 224 | ), 225 | ); 226 | }, 227 | ).toList(), 228 | // onPageChanged: (index) { 229 | // setState(() { 230 | // index; 231 | // }); 232 | // }, 233 | ); 234 | } 235 | /// 创建页面体 236 | Widget _buildWallBody(){ 237 | 238 | return _wallDataList == null || _wallDataList.length == 0 239 | ? Center(child:Text('没有数据可提供!', style: TextStyle( 240 | fontSize: 18, 241 | fontWeight: FontWeight.w500, 242 | color: Colors.black38, 243 | wordSpacing: 2, 244 | ))) 245 | : ListView.separated( 246 | itemBuilder: (context,index){ 247 | return Card( 248 | elevation: 4, 249 | margin: EdgeInsets.fromLTRB(4, 4, 4, index != (_wallDataList.length - 1) ? 0 : 12), 250 | shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(6)), 251 | child: Column( 252 | mainAxisAlignment: MainAxisAlignment.center, 253 | children: [ 254 | 255 | // 图片区域 256 | (_wallDataList[index].movingImages != null && _wallDataList[index].movingImages.length > 0) 257 | ? _imageLayout(_wallDataList[index].movingImages.map((imgUrl) => (API.baseUri + imgUrl)).toList()) 258 | : SizedBox(), 259 | 260 | // 文本内容 261 | Padding( 262 | padding: EdgeInsets.fromLTRB(8, 4, 8, 0), 263 | child: Text( 264 | _wallDataList[index].movingContent, 265 | style: TextStyle( 266 | fontSize: 16, 267 | fontWeight: FontWeight.w300, 268 | wordSpacing: 2, 269 | ), 270 | maxLines: 2, 271 | overflow: TextOverflow.ellipsis, 272 | ) 273 | ), 274 | Divider(), 275 | // 作者信息等 276 | Row( 277 | mainAxisAlignment: MainAxisAlignment.end, 278 | children: [ 279 | Expanded( 280 | child: Row( 281 | mainAxisAlignment: MainAxisAlignment.start, 282 | children: [ 283 | Padding(padding: EdgeInsets.only(left: 8, right: 5, bottom: 5),child: Text(_wallDataList[index].movingAuthorName),), 284 | Padding( 285 | padding: EdgeInsets.only(left: 10, right: 10, bottom: 5), 286 | child: Text(BjuTimelineUtil.formatDateStr(_wallDataList[index].movingCreateTime), style: TextStyle( 287 | color: Colors.black38, 288 | ),), 289 | ) 290 | ], 291 | ) 292 | ), 293 | Padding( 294 | padding: EdgeInsets.only(left: 8, right: 20, bottom: 5), 295 | child: GestureDetector( 296 | child: Row( 297 | mainAxisSize: MainAxisSize.min, 298 | mainAxisAlignment: MainAxisAlignment.center, 299 | children: [ 300 | Text('查看详情', style: TextStyle( 301 | fontSize: 16, 302 | color: Colors.lightBlue, 303 | wordSpacing: 2, 304 | ), 305 | ), 306 | Icon(FontAwesomeIcons.angleDoubleRight, color: Colors.lightBlue,size: 16,) 307 | ], 308 | ), 309 | onTap: (){ 310 | print('墙上有你前往详情页面:' + _wallDataList[index].movingId.toString()); 311 | final int movingId = _wallDataList[index].movingId; 312 | if(movingId == 0){ 313 | print('跳转的动态ID不存在!'); 314 | showToast('动态信息有误!'); 315 | return; 316 | } 317 | // 跳转到动态详情页面 318 | Navigator.push>(context, MaterialPageRoute(builder: (context)=> MovingDetailsPage(movingId) )); 319 | return; 320 | }, 321 | ), 322 | ), 323 | ], 324 | ) 325 | 326 | 327 | ], 328 | ), 329 | ); 330 | }, 331 | separatorBuilder: (context,index){ 332 | return SizedBox(height: ScreenUtil().setHeight(10)); 333 | }, 334 | itemCount: _wallDataList.length 335 | ); 336 | } 337 | 338 | @override 339 | Widget build(BuildContext context) { 340 | 341 | // 屏幕适配 342 | ScreenUtil.init(context, width:750,height:1334); 343 | 344 | return Scaffold( 345 | appBar: AppBar( 346 | leading: BackButton(onPressed:(){ 347 | Navigator.of(context).pop(); 348 | }), 349 | title: Text('墙上有你'), 350 | ), 351 | body: _buildWallBody(), 352 | bottomNavigationBar: _bottomNav(), 353 | ); 354 | } 355 | } -------------------------------------------------------------------------------- /lib/pages/mine/safty_setting_page.dart: -------------------------------------------------------------------------------- 1 | import 'package:bju_information_app/api/api.dart'; 2 | import 'package:bju_information_app/models/response.dart'; 3 | import 'package:bju_information_app/models/user.dart'; 4 | import 'package:bju_information_app/net/bju_net.dart'; 5 | import 'package:bju_information_app/pages/login_page.dart'; 6 | import 'package:bju_information_app/providers/login_provider.dart'; 7 | import 'package:flutter/material.dart'; 8 | import 'package:flutter_screenutil/flutter_screenutil.dart'; 9 | import 'package:oktoast/oktoast.dart'; 10 | import 'package:provider/provider.dart'; 11 | 12 | /// 13 | /// 我的栏目下的安全设置项 14 | /// 2020/04/17 12:24 15 | /// 16 | /// * 用户登录密码修改 17 | /// 18 | 19 | class SaftySettingPage extends StatefulWidget { 20 | SaftySettingPage({Key key}) : super(key: key); 21 | 22 | @override 23 | _SaftySettingPageState createState() => _SaftySettingPageState(); 24 | } 25 | 26 | 27 | class _SaftySettingPageState extends State { 28 | 29 | /// form表单key 30 | GlobalKey _formKey = GlobalKey(); 31 | 32 | /// 密码输入文本控制器 33 | TextEditingController _passwordController = TextEditingController(); 34 | 35 | /// 新密码 36 | String _newPassword = ''; 37 | 38 | /// 密码可见 39 | bool _isShowNewPassword = false; 40 | 41 | /// 密码可见 42 | bool _isShowComfirmPassword = false; 43 | 44 | 45 | 46 | 47 | /// 48 | /// 输入域布局 49 | /// 50 | Widget _formLayout(){ 51 | 52 | return Form( 53 | key: _formKey, 54 | child: Column( 55 | mainAxisSize: MainAxisSize.min, 56 | mainAxisAlignment: MainAxisAlignment.center, 57 | children: [ 58 | // 密码 59 | Padding( 60 | padding: EdgeInsets.only(bottom: 8), 61 | child: Row( 62 | mainAxisSize: MainAxisSize.max, 63 | mainAxisAlignment: MainAxisAlignment.start, 64 | children: [ 65 | Padding( 66 | padding: EdgeInsets.only(top: ScreenUtil().setHeight(10)), 67 | child: Text('新密码:', style: TextStyle( 68 | fontSize: 15, 69 | fontWeight: FontWeight.w500, 70 | wordSpacing: 2, 71 | color: Colors.black54, 72 | // backgroundColor: Colors.lightBlue[400], 73 | ), 74 | ), 75 | ), 76 | Expanded( 77 | child: TextFormField( 78 | controller: _passwordController, 79 | obscureText: !_isShowNewPassword, 80 | keyboardType: TextInputType.number, 81 | decoration: InputDecoration( 82 | hintText: '请输入6-12有效数字', 83 | border: UnderlineInputBorder(), 84 | suffixIcon: IconButton( 85 | icon:Icon(_isShowNewPassword ? Icons.visibility : Icons.visibility_off), 86 | onPressed: (){ 87 | setState(() { 88 | _isShowNewPassword = !_isShowNewPassword; 89 | }); 90 | }, 91 | ) 92 | ), 93 | validator: (val) { 94 | RegExp exp = RegExp(r'^\d{6,12}$'); 95 | if(val.isEmpty){ 96 | return '密码不能为空!'; 97 | } else if(!exp.hasMatch(val)){ 98 | return '密码只能是6-12位数字'; 99 | } 100 | return null; 101 | }, 102 | onSaved: (value){ 103 | if(!mounted) return; 104 | setState(() { 105 | _newPassword = value; 106 | }); 107 | }, 108 | ), 109 | ) 110 | ], 111 | ), 112 | ), 113 | // 确认密码 114 | Padding( 115 | padding: EdgeInsets.only(bottom: 8), 116 | child: Row( 117 | mainAxisSize: MainAxisSize.max, 118 | mainAxisAlignment: MainAxisAlignment.start, 119 | children: [ 120 | Padding( 121 | padding: EdgeInsets.only(top: ScreenUtil().setHeight(10)), 122 | child: Text('确认密码:', style: TextStyle( 123 | fontSize: 15, 124 | fontWeight: FontWeight.w500, 125 | wordSpacing: 2, 126 | color: Colors.black54, 127 | // backgroundColor: Colors.lightBlue[400], 128 | ), 129 | ), 130 | ), 131 | Expanded( 132 | child: TextFormField( 133 | obscureText: !_isShowComfirmPassword, 134 | keyboardType: TextInputType.number, 135 | decoration: InputDecoration( 136 | hintText: '请输入6-12有效数字', 137 | border: UnderlineInputBorder(), 138 | suffixIcon: IconButton( 139 | icon:Icon(_isShowComfirmPassword ? Icons.visibility : Icons.visibility_off), 140 | onPressed: (){ 141 | setState(() { 142 | _isShowComfirmPassword = !_isShowComfirmPassword; 143 | }); 144 | }, 145 | ), 146 | ), 147 | validator: (val) { 148 | RegExp exp = RegExp(r'^\d{6,12}$'); 149 | if(val.isEmpty){ 150 | return '密码不能为空!'; 151 | } else if(!exp.hasMatch(val)){ 152 | return '密码只能是6-12位数字'; 153 | } else if(_passwordController.text.compareTo(val) != 0){ 154 | return '两次密码输入不一致!'; 155 | } 156 | return null; 157 | }, 158 | ), 159 | ) 160 | ], 161 | ), 162 | ), 163 | // 修改按钮 164 | Padding( 165 | padding: EdgeInsets.fromLTRB(10,8,10,0), 166 | child: Row( 167 | mainAxisSize: MainAxisSize.max, 168 | children: [ 169 | Expanded( 170 | child: Container( 171 | height: ScreenUtil().setHeight(80), 172 | child: RaisedButton( 173 | child: Text('修改(MODIFY PASSWORD)', style: TextStyle( 174 | fontSize: 18, 175 | color: Colors.white, 176 | letterSpacing: 2, 177 | ), 178 | ), 179 | color: Colors.blue[400], 180 | shape: RoundedRectangleBorder( 181 | borderRadius: BorderRadius.circular(8) 182 | ), 183 | onPressed: () async { 184 | print('安全设置,修改密码'); 185 | // 验证表单 186 | if(!_formKey.currentState.validate()){ 187 | print('密码修改:表单验证不通过!'); 188 | return; 189 | } 190 | // 保存表单中的状态 191 | _formKey.currentState.save(); 192 | final User user = Provider.of(context, listen: false).loginUser; 193 | // 向服务器请求 194 | ResponseData resData = await BjuHttp.put(API.modifyPassword, params: { 195 | "userMobile" : user.userMobile, 196 | "userNickname" : user.userNickname, 197 | "newPassword" : _newPassword, 198 | }).then((onValue) => ResponseData.fromJson(onValue.data)) 199 | .catchError((onError){ 200 | print('密码修改,请求服务器异常!'); 201 | print(onError); 202 | showToast('服务器请求异常!'); 203 | return; 204 | }); 205 | if(resData == null){ 206 | Future.delayed(Duration(milliseconds: 1500), () => showToast('请求失败!')); 207 | return; 208 | } 209 | if(resData.statusCode == 1){ 210 | showToast(resData.message); 211 | return; 212 | } 213 | // 先退出登录,后跳转登录页面 214 | ResponseData logoutRes = await BjuHttp.post(API.logout) 215 | .then((onValue) => ResponseData.fromJson(onValue.data)) 216 | .catchError((onError){ 217 | print('密码修改,退出登录请求服务器异常!'); 218 | print(onError); 219 | showToast('服务器请求异常!'); 220 | }); 221 | print('密码修改成功后退出登录的结果,logoutRes= ' + logoutRes.toString()); 222 | if(logoutRes != null && logoutRes.statusCode == 0){ 223 | showToast('即将跳转到登录页面...'); 224 | Future.delayed(Duration(milliseconds: 3000), 225 | () => Navigator.pushReplacement(context, MaterialPageRoute(builder: (context) => LoginPage())) 226 | ); 227 | return; 228 | } 229 | 230 | 231 | }, 232 | ), 233 | ), 234 | ) 235 | ], 236 | ), 237 | ) 238 | 239 | ], 240 | ), 241 | 242 | ); 243 | } 244 | 245 | /// 246 | /// 密码修改设置布局 247 | Widget _passwordSettingLayout(){ 248 | 249 | // 屏幕的Size 250 | final Size size = MediaQuery.of(context).size; 251 | 252 | return Container( 253 | margin: EdgeInsets.all(8), 254 | child: Stack( 255 | children: [ 256 | Align( 257 | alignment: Alignment.topLeft, 258 | child: Padding( 259 | padding: EdgeInsets.fromLTRB(0, 5, 0, 10), 260 | child: Text('密码修改',style: TextStyle( 261 | fontSize: 18, 262 | fontWeight: FontWeight.w500, 263 | color: Colors.black54, 264 | wordSpacing: 4, 265 | ), 266 | ), 267 | ), 268 | ), 269 | Positioned( 270 | top: 20, 271 | child: Divider(), 272 | ), 273 | Positioned( 274 | top: 30, 275 | width: size.width - 20, 276 | child: _formLayout() 277 | ), 278 | ], 279 | ), 280 | ); 281 | 282 | } 283 | 284 | 285 | 286 | 287 | 288 | @override 289 | Widget build(BuildContext context) { 290 | // 初始化屏幕大小用于适配 291 | ScreenUtil.init(context, width:750,height:1334); 292 | 293 | return Scaffold( 294 | appBar: AppBar( 295 | leading:BackButton(onPressed: () => Navigator.of(context).pop(),), 296 | title: Text('安全设置'), 297 | // actions: [ 298 | // Padding( 299 | // padding: EdgeInsets.only(right: 15), 300 | // child: InkWell( 301 | // child: Text('修改'), 302 | // onTap: () => null, 303 | // ), 304 | // ) 305 | // ], 306 | ), 307 | body: PageView( 308 | scrollDirection: Axis.horizontal, 309 | children: [ 310 | // 密码设置布局 311 | _passwordSettingLayout(), 312 | ], 313 | ), 314 | ); 315 | } 316 | } -------------------------------------------------------------------------------- /lib/pages/login_page.dart: -------------------------------------------------------------------------------- 1 | import 'package:bju_information_app/api/api.dart'; 2 | import 'package:bju_information_app/models/response.dart'; 3 | import 'package:bju_information_app/models/user.dart'; 4 | import 'package:bju_information_app/net/bju_net.dart'; 5 | import 'package:bju_information_app/pages/register_page.dart'; 6 | import 'package:bju_information_app/providers/jpush_provider.dart'; 7 | import 'package:bju_information_app/providers/login_provider.dart'; 8 | import 'package:dio/dio.dart'; 9 | import 'package:flutter/material.dart'; 10 | import 'package:flutter_screenutil/flutter_screenutil.dart'; 11 | import 'package:font_awesome_flutter/font_awesome_flutter.dart'; 12 | import 'package:oktoast/oktoast.dart'; 13 | import 'package:provider/provider.dart'; 14 | 15 | /// 16 | /// 登录页 17 | /// 18 | class LoginPage extends StatefulWidget { 19 | LoginPage({Key key}) : super(key: key); 20 | 21 | @override 22 | _LoginPageState createState() => _LoginPageState(); 23 | } 24 | 25 | class _LoginPageState extends State { 26 | 27 | /// 焦点 28 | FocusNode _focusNodeUserName = new FocusNode(); 29 | FocusNode _focusNodePassWord = new FocusNode(); 30 | //用户名输入框控制器,此控制器可以监听用户名输入框操作 31 | TextEditingController _userNameController = new TextEditingController(); 32 | /// 表单状态 33 | GlobalKey _formKey = GlobalKey(); 34 | /// 密码 35 | String _password = ''; 36 | /// 用户名 37 | String _username = ''; 38 | /// 是否显示密码 39 | bool _isShowPwd = false; 40 | /// 41 | /// 是否显示输入框尾部的清除按钮 42 | bool _isShowClear = false; 43 | @override 44 | void initState() { 45 | // TODO: implement initState 46 | //设置焦点监听 47 | _focusNodeUserName.addListener(_focusNodeListener); 48 | _focusNodePassWord.addListener(_focusNodeListener); 49 | //监听用户名框的输入改变 50 | _userNameController.addListener((){ 51 | //print(_userNameController.text); 52 | // 监听文本框输入变化,当有内容的时候,显示尾部清除按钮,否则不显示 53 | if (_userNameController.text.length > 0) { 54 | _isShowClear = true; 55 | }else{ 56 | _isShowClear = false; 57 | } 58 | setState(() { 59 | 60 | }); 61 | }); 62 | super.initState(); 63 | } 64 | @override 65 | void dispose() { 66 | // TODO: implement dispose 67 | // 移除焦点监听 68 | _focusNodeUserName.removeListener(_focusNodeListener); 69 | _focusNodePassWord.removeListener(_focusNodeListener); 70 | _userNameController.dispose(); 71 | super.dispose(); 72 | } 73 | // 监听焦点 74 | Future _focusNodeListener() async{ 75 | if(_focusNodeUserName.hasFocus){ 76 | print("用户名框获取焦点"); 77 | // 取消密码框的焦点状态 78 | _focusNodePassWord.unfocus(); 79 | } 80 | if (_focusNodePassWord.hasFocus) { 81 | print("密码框获取焦点"); 82 | // 取消用户名框焦点状态 83 | _focusNodeUserName.unfocus(); 84 | } 85 | } 86 | /** 87 | * 验证用户名 88 | */ 89 | String validateUserName(value){ 90 | // 正则匹配手机号 91 | RegExp exp = RegExp(r'^((13[0-9])|(14[0-9])|(15[0-9])|(16[0-9])|(17[0-9])|(18[0-9])|(19[0-9]))\d{8}$'); 92 | if (value.isEmpty) { 93 | return '用户名不能为空!'; 94 | }else if (!exp.hasMatch(value)) { 95 | return '请输入正确手机号'; 96 | } 97 | return null; 98 | } 99 | /** 100 | * 验证密码 101 | */ 102 | String validatePassWord(value){ 103 | if (value.isEmpty) { 104 | return '密码不能为空'; 105 | }else if(value.trim().toString().length < 6){ 106 | return '密码长度不正确'; 107 | } 108 | return null; 109 | } 110 | @override 111 | Widget build(BuildContext context) { 112 | ScreenUtil.init(context, width:750,height:1334); 113 | //print(ScreenUtil().scaleHeight); 114 | // logo 图片区域 115 | Widget logoImageArea = new Container( 116 | alignment: Alignment.topCenter, 117 | // 设置图片为圆形 118 | child: ClipOval( 119 | child: Image.asset( 120 | "assets/avatar/bju_xh.jpg", 121 | height: 100, 122 | width: 100, 123 | fit: BoxFit.fill, 124 | ), 125 | ), 126 | ); 127 | 128 | //输入文本框区域 129 | Widget inputTextArea = new Container( 130 | margin: EdgeInsets.only(left: 20,right: 20), 131 | decoration: new BoxDecoration( 132 | borderRadius: BorderRadius.all(Radius.circular(8)), 133 | color: Colors.white 134 | ), 135 | child: new Form( 136 | key: _formKey, 137 | child: new Column( 138 | mainAxisSize: MainAxisSize.min, 139 | children: [ 140 | new TextFormField( 141 | controller: _userNameController, 142 | focusNode: _focusNodeUserName, 143 | //设置键盘类型 144 | keyboardType: TextInputType.number, 145 | decoration: InputDecoration( 146 | labelText: "用户名", 147 | hintText: "请输入手机号", 148 | prefixIcon: Icon(Icons.person), 149 | //尾部添加清除按钮 150 | suffixIcon:(_isShowClear) 151 | ? IconButton( 152 | icon: Icon(Icons.clear), 153 | onPressed: (){ 154 | // 清空输入框内容 155 | _userNameController.clear(); 156 | }, 157 | ) 158 | : null , 159 | ), 160 | //验证用户名 161 | validator: validateUserName, 162 | //保存数据 163 | onSaved: (String value){ 164 | _username = value; 165 | }, 166 | ), 167 | new TextFormField( 168 | focusNode: _focusNodePassWord, 169 | keyboardType: TextInputType.number, 170 | decoration: InputDecoration( 171 | labelText: "密码", 172 | hintText: "请输入密码", 173 | prefixIcon: Icon(Icons.lock), 174 | // 是否显示密码 175 | suffixIcon: IconButton( 176 | icon: Icon((_isShowPwd) ? Icons.visibility : Icons.visibility_off), 177 | // 点击改变显示或隐藏密码 178 | onPressed: (){ 179 | setState(() { 180 | _isShowPwd = !_isShowPwd; 181 | }); 182 | }, 183 | ) 184 | ), 185 | obscureText: !_isShowPwd, 186 | //密码验证 187 | validator:validatePassWord, 188 | //保存数据 189 | onSaved: (String value){ 190 | _password = value; 191 | }, 192 | ) 193 | ], 194 | ), 195 | ), 196 | ); 197 | // 登录按钮区域 198 | Widget loginButtonArea = new Container( 199 | margin: EdgeInsets.only(left: 20,right: 20), 200 | height: 45.0, 201 | child: new RaisedButton( 202 | color: Colors.blue[300], 203 | child: Text( 204 | "登录", 205 | style: Theme.of(context).primaryTextTheme.headline, 206 | ), 207 | // 设置按钮圆角 208 | shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(10.0)), 209 | onPressed: () async { 210 | //点击登录按钮,解除焦点,回收键盘 211 | _focusNodePassWord.unfocus(); 212 | _focusNodeUserName.unfocus(); 213 | if (_formKey.currentState.validate()) { 214 | //只有输入通过验证,才会执行这里 215 | _formKey.currentState.save(); 216 | 217 | //todo 登录操作 218 | print('输入数据:'); 219 | print("$_username + $_password"); 220 | 221 | // 调用API接口执行登录操作 222 | ResponseData resData = await BjuHttp.post(API.npLogin,params: { 223 | 'userMobile':_username, 224 | 'password':_password 225 | }).then((onValue) => ResponseData.fromJson(onValue.data)) 226 | .catchError((onError){ 227 | print('登录异常:'); 228 | print(onError); 229 | showToast('请求服务器异常!'); 230 | }); 231 | if(resData == null) { 232 | showToast('网络请求失败!'); 233 | return; 234 | } 235 | print('----返回的数据'+resData.toString()); 236 | // final var data = res.data; 237 | print('----返回的数据data: ' + resData.toJson().toString()); 238 | if(resData.statusCode == 1){ 239 | showToast(resData.message); 240 | return; 241 | } 242 | // 登录成功 243 | LoginProvider loginProvider = Provider.of(context,listen: false); 244 | JPushProvider jPushProvider = Provider.of(context,listen: false); 245 | final User user = User.fromJson(resData.res['user']); 246 | print('用户数据model:user:$user'); 247 | // 设置登录数据 248 | loginProvider.doLogin(user); 249 | loginProvider.refreshAllCounts(resData.res['allCounts']); 250 | // 设置JPush的别名 251 | // print('jpush:'); 252 | jPushProvider.setAlias(_username); 253 | // print(await jPushProvider.jpush.getAllTags()); 254 | print("用户登录,获取到的token为:token= " + resData.res['token'].toString()); 255 | print(resData.res['token']); 256 | BjuHttp.token(resData.res['token']); 257 | // 返回 258 | Navigator.pop(context); 259 | 260 | } 261 | }, 262 | ), 263 | ); 264 | //第三方登录区域 265 | Widget thirdLoginArea = new Container( 266 | margin: EdgeInsets.only(left: 20,right: 20), 267 | child: new Column( 268 | children: [ 269 | new Row( 270 | mainAxisSize: MainAxisSize.max, 271 | mainAxisAlignment: MainAxisAlignment.spaceEvenly, 272 | children: [ 273 | Container( 274 | width: 80, 275 | height: 1.0, 276 | color: Colors.grey, 277 | 278 | ), 279 | Text( 280 | '第三方登录' 281 | ), 282 | Container( 283 | width: 80, 284 | height: 1.0, 285 | color: Colors.grey, 286 | ), 287 | ], 288 | ), 289 | new SizedBox( 290 | height: 18, 291 | ), 292 | new Row( 293 | mainAxisAlignment: MainAxisAlignment.spaceEvenly, 294 | children: [ 295 | IconButton( 296 | color: Colors.green[200], 297 | // 第三方库icon图标 298 | icon: Icon(FontAwesomeIcons.weixin), 299 | iconSize: 40.0, 300 | onPressed: (){ 301 | }, 302 | ), 303 | IconButton( 304 | color: Colors.green[200], 305 | icon: Icon(FontAwesomeIcons.facebook), 306 | iconSize: 40.0, 307 | onPressed: (){ 308 | }, 309 | ), 310 | IconButton( 311 | color: Colors.green[200], 312 | icon: Icon(FontAwesomeIcons.qq), 313 | iconSize: 40.0, 314 | onPressed: (){ 315 | }, 316 | ) 317 | ], 318 | ) 319 | ], 320 | ), 321 | ); 322 | //忘记密码 立即注册 323 | Widget bottomArea = new Container( 324 | margin: EdgeInsets.only(right: 20,left: 30), 325 | child: new Row( 326 | mainAxisSize: MainAxisSize.max, 327 | mainAxisAlignment: MainAxisAlignment.spaceBetween, 328 | children: [ 329 | FlatButton( 330 | child: Text( 331 | "忘记密码?", 332 | style: TextStyle( 333 | color: Colors.blue[400], 334 | fontSize: 16.0, 335 | ), 336 | ), 337 | //忘记密码按钮,点击执行事件 338 | onPressed: (){ 339 | }, 340 | ), 341 | FlatButton( 342 | child: Text( 343 | "快速注册", 344 | style: TextStyle( 345 | color: Colors.blue[400], 346 | fontSize: 16.0, 347 | ), 348 | ), 349 | //点击快速注册、执行事件 350 | onPressed: () async { 351 | String res = await Navigator.push(context, MaterialPageRoute(builder: (context) => RegisterPage())); 352 | if(!mounted) return; 353 | setState(() { 354 | // 刚注册的手机号码 355 | _userNameController.text = res; 356 | }); 357 | }, 358 | ) 359 | ], 360 | ), 361 | ); 362 | 363 | return Scaffold( 364 | appBar: AppBar( 365 | leading: BackButton(onPressed: () => Navigator.of(context).pop(),), 366 | title: Text('登录'), 367 | ), 368 | backgroundColor: Colors.white, 369 | // 外层添加一个手势,用于点击空白部分,回收键盘 370 | body: GestureDetector( 371 | onTap: (){ 372 | // 点击空白区域,回收键盘 373 | print("点击了空白区域"); 374 | _focusNodePassWord.unfocus(); 375 | _focusNodeUserName.unfocus(); 376 | }, 377 | child: ListView( 378 | children: [ 379 | SizedBox(height: ScreenUtil().setHeight(80),), 380 | logoImageArea, 381 | SizedBox(height: ScreenUtil().setHeight(70),), 382 | inputTextArea, 383 | SizedBox(height: ScreenUtil().setHeight(80),), 384 | loginButtonArea, 385 | SizedBox(height: ScreenUtil().setHeight(60),), 386 | thirdLoginArea, 387 | SizedBox(height: ScreenUtil().setHeight(60),), 388 | bottomArea, 389 | ], 390 | ), 391 | ), 392 | ); 393 | } 394 | } --------------------------------------------------------------------------------