App开发之账号系统实战篇

漂泊80

共 54278字,需浏览 109分钟

 · 2020-11-20

数据库操作

  1. 创建数据库[songbei]


  2. 创建用户表[user]


3.验证码表[validate]

注册功能

注册用到两个协议:注册和发送验证码


客户端注册页

import 'package:common_utils/common_utils.dart';import 'package:flutter/material.dart';import 'package:country_code_picker/country_code_picker.dart';import 'package:flutter_songbei/custom/login_head.dart';import 'package:flutter_songbei/network/chttp.dart';import 'package:flutter_songbei/network/params.dart';import 'package:flutter_songbei/utils/toast_util.dart';
import '../../app_theme.dart';import '../app_web_page.dart';
class SignupPage extends StatefulWidget { @override State createState() { return _SignupState(); }}
class _SignupState extends State { TextEditingController phoneController = TextEditingController(); TextEditingController pwdController = TextEditingController(); TextEditingController codeController = TextEditingController(); String country_code = '+86'; String _verifyCodeTips = '发送验证码';
bool _checkboxSelected = false; bool b_phone_clear = false; bool b_pwd_clear = false; bool b_code_clear = false;
TimerUtil timerCountDown;
///倒计时 int _verifyCodeCD = 0;
@override void dispose() { super.dispose(); if (timerCountDown != null) timerCountDown.cancel(); }
@override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( title: Text('注册'), centerTitle: true, ), body: Container( decoration: BoxDecoration(), padding: EdgeInsets.fromLTRB(20.0, 40.0, 20.0, 0), child: SingleChildScrollView( child: Column( mainAxisAlignment: MainAxisAlignment.spaceEvenly, children: [ LoginHead(), Column( children: [// TextField(// controller: phoneController,// decoration: InputDecoration(// contentPadding: EdgeInsets.all(10.0),// hintText: '请输入手机号',// filled: true,// fillColor: AppTheme.textWhite)), Stack( children: [ Row(children: [ Container( color: Colors.white, child: CountryCodePicker( onChanged: _onCountryChange, // Initial selection and favorite can be one of code ('IT') OR dial_code('+39') initialSelection: 'CN', favorite: ['+86', 'china'], // optional. Shows only country name and flag showCountryOnly: false, // optional. Shows only country name and flag when popup is closed. showOnlyCountryWhenClosed: false, // optional. aligns the flag and the Text left alignLeft: false, ), ), Expanded( child: TextField( controller: phoneController, keyboardType: TextInputType.phone, decoration: InputDecoration( contentPadding: EdgeInsets.all(10.0), hintText: '请输入手机号', filled: true, fillColor: AppTheme.loginFillColor), onChanged: (String text) { //new 通过onChanged事件更新_isComposing 标志位的值 setState(() { //new 调用setState函数重新渲染受到_isComposing变量影响的IconButton控件 b_phone_clear = text.length > 0; //new 如果文本输入框中的字符串长度大于0则允许发送消息 }); //new }, ), ) ]), Positioned( right: 0, child: Offstage( offstage: !b_phone_clear, child: IconButton( iconSize: 20.0, icon: ImageIcon( AssetImage('assets/user/qingchu.png'), color: AppTheme.mainColor, ), onPressed: () { phoneController.text = ''; setState(() { b_phone_clear = false; }); }, ), ), ) ], ), Padding( padding: EdgeInsets.fromLTRB(0, 10, 0, 0), child: Stack( children: [ TextField( controller: pwdController,// obscureText:true, decoration: InputDecoration( contentPadding: EdgeInsets.all(10.0), hintText: '请输入密码', filled: true, fillColor: AppTheme.loginFillColor), onChanged: (String text) { setState(() { b_pwd_clear = text.length > 0; }); }, ), Positioned( right: 0, child: Offstage( offstage: !b_pwd_clear, child: IconButton( iconSize: 20.0, icon: ImageIcon( AssetImage('assets/user/qingchu.png'), color: AppTheme.mainColor, ), onPressed: () { pwdController.text = ''; setState(() { b_pwd_clear = false; }); }, ), ), ) ], ), ), Row( mainAxisSize: MainAxisSize.min, children: [ Expanded( flex: 1, child: Stack( children: [ TextField( controller: codeController, decoration: InputDecoration( contentPadding: EdgeInsets.all(10.0), hintText: '请输入验证码',// helperText: '验证码' filled: true, fillColor: AppTheme.loginFillColor), onChanged: (String text) { setState(() { b_code_clear = text.length > 0; }); }, ), Positioned( right: 0, child: Offstage( offstage: !b_code_clear, child: IconButton( iconSize: 20.0, icon: ImageIcon( AssetImage('assets/user/qingchu.png'), color: AppTheme.mainColor, ), onPressed: () { codeController.text = ''; setState(() { b_code_clear = false; }); }, ), ), ) ], )), Container( margin: EdgeInsets.all(10.0), child: OutlineButton( borderSide: BorderSide(color: AppTheme.mainColor), child: Text( _verifyCodeTips, style: TextStyle(color: AppTheme.mainColor), ), onPressed: _onVeriCode, ), ) ], ), Container( margin: EdgeInsets.fromLTRB(0, 20, 0, 0), width: 260, child: RaisedButton( color: AppTheme.mainColor, child: Text( '注册', style: AppTheme.loginButtonStyle, ), onPressed: () { _onSignup(context); }, )), Container( alignment: Alignment.center, child: Row( mainAxisSize: MainAxisSize.min, children: [ Checkbox( value: _checkboxSelected, activeColor: AppTheme.mainColor, //选中时的颜色 onChanged: (value) { setState(() { _checkboxSelected = value; }); }, ), Text('已阅读并同意'), GestureDetector( child: Text( '《用户服务协议》', style: TextStyle(color: AppTheme.mainColor), ), onTap: () { Navigator.of(context).push(MaterialPageRoute( builder: (BuildContext context) => AppWebPage( '用户服务协议', CHttp.getAppWeb(CHttp.APPH5_AGREEMENT)))); }, ) ], ), ) ], ) ], ), )), ); }
void _onSignup(BuildContext context) { if (phoneController.text.length == 0) { ToastUtil.showToast(context, '请输入手机号'); return; } if (pwdController.text.length == 0) { ToastUtil.showToast(context, '请输入密码'); return; } if (codeController.text.length == 0) { ToastUtil.showToast(context, '请输入验证码'); return; } if (!_checkboxSelected) { ToastUtil.showToast(context, '请阅读并同意用户服务协议'); return; } _reqSingnup(); }
void _onVeriCode() { if (phoneController.text.length == 0) { ToastUtil.showToast(context, '请输入手机号'); return; } if (_verifyCodeCD > 0) { ToastUtil.showToast(context, _verifyCodeCD.toString() + '秒后可重新发送'); return; } _reqVerifyCode(); }
///请求验证码 verifycode _reqVerifyCode() { CHttp.post( CHttp.USER_VALIDATE, (data) { if (!mounted) { return; } _verifyCodeCD = data['time']; setState(() { _verifyCodeTips = _verifyCodeCD.toString() + '秒后重新发送'; }); //倒计时test timerCountDown = TimerUtil(mInterval: 1000, mTotalTime: _verifyCodeCD * 1000); timerCountDown.setOnTimerTickCallback((int value) { double tick = (value / 1000);// LogUtil.e("CountDown: " + tick.toInt().toString()); if (value == 0) { setState(() { _verifyCodeCD = tick.toInt(); _verifyCodeTips = '发送验证码'; }); } else { setState(() { _verifyCodeCD = tick.toInt(); _verifyCodeTips = tick.toInt().toString() + '秒后重新发送'; }); } }); timerCountDown.startCountDown(); }, params: PUserVerifyCode(country_code, phoneController.text, 1).toJson(), errorCallback: (err) { ToastUtil.showToast(context, err); }); }
void _onCountryChange(CountryCode countryCode) { //TODO : manipulate the selected country code here print("New Country selected: " + countryCode.toString()); country_code = countryCode.toString(); }
/** * 注册用户 */ _reqSingnup() { CHttp.post( CHttp.USER_REGISTER, (data) { if (!mounted) { return; } LogUtil.e(data); String userid = data['userid']; Navigator.of(context).pop(userid); ToastUtil.showToast(context, '请前往登录页进行登录'); }, params: PUserSignup(country_code, phoneController.text, pwdController.text, codeController.text) .toJson(), errorCallback: (err) { ToastUtil.showToast(context, err); }); }}


服务器注册协议

router.post('/register', function(req, res, next) {    try {        ru.logReq(req);        var country_code = req.body.country_code;        var phone = req.body.phone;        var password = req.body.password;        var code = req.body.code;        var os = req.body.os;        var userid = utils.getUserid(phone);        if (!phone || !password || !code || !country_code) {            ru.resError(res, '参数错误!');        } else if (!utils.checkPhone(phone)) {            ru.resError(res, '请填写正确的手机号格式');        } else {            userDao.queryUser(phone, function(err, result) {                if (err) {                    ru.resError(res, err)                } else {                    if (result.length > 0) {                        ru.resError(res, '用户已经注册')                    } else {                        if (conf_validate) {                            validateDao.queryValidate(country_code, phone, 1, function(err, objs) {                                if (err) {                                    ru.resError(res, err);                                } else {                                    var length = objs.length;                                    if (length > 0) {                                        var validate = objs[0];                                        if (validate.phone == phone && validate.code == code) {                                            userDao.addUser(userid, country_code, phone, password, os, function(err, result) {                                                if (err) {                                                    ru.resError(res, err);                                                } else {                                                    var title = deftext.register_title;                                                    var content = deftext.register_content;                                                    var data = {                                                        type: 0,                                                        userid: userid,                                                        title: title,                                                        content: content,                                                        extend: {},                                                    };                                                    httputil.requstPSPost('/message/actionmsg', data, function(err, result) {                                                        if (err) {                                                            logger.error(err)                                                        }                                                    })                                                    var user = result.length > 0 ? result[0] : {};                                                    ru.resSuccess(res, user);                                                }                                            });                                        } else {                                            ru.resError(res, '验证码错误');                                        }                                    } else {                                        ru.resError(res, '验证码错误');                                    }                                }                            });                        } else {                            userDao.addUser(userid, country_code, phone, password, os, function(err, result) {                                if (err) {                                    ru.resError(res, err);                                } else {                                    var title = deftext.register_title;                                    var content = deftext.register_content;                                    var data = {                                        type: 0,                                        userid: userid,                                        title: title,                                        content: content,                                        extend: {},                                    };                                    httputil.requstPSPost('/message/actionmsg', data, function(err, result) {                                        if (err) {                                            logger.error(err)                                        }                                    })                                    var user = result.length > 0 ? result[0] : {};                                    ru.resSuccess(res, user);                                }                            });                        }                    }                }            });        }    } catch (err) {        ru.resError(res, err.message);    }});

数据库userDao.js

queryUser:function(phone,callback){    console.log("phone:",phone)    var sql = 'SELECT * FROM user WHERE phone = ? LIMIT 1';        pool.getConnection(function(err, connection) {            try{                connection.query(sql, phone, function(err, result) {                    callback(err, result)                    connection.release();                });            }catch(err){                logger.error(err);                callback(err,null);            }        });  },      /**     * 添加用户     */    addUser:function(userid,country_code,phone,password,os,callback){        var sql = 'INSERT INTO user(userid,country_code,phone,password,os) VALUES(?,?,?,?,?)';        pool.getConnection(function(err, connection) {            connection.query(sql, [userid,country_code,phone,password,os], function(err, result) {                if(err){                    callback(err, result)                    connection.release();                                   }else{                    var id = result.insertId;                    sql = 'SELECT * FROM '+USER_TABLE+' WHERE id = ?';                    connection.query(sql,[id],function(err, result){                        callback(err, result)                        connection.release();                      })                }
}); }); },


登录功能

客户端登录页

import 'dart:ui';
import 'package:common_utils/common_utils.dart';import 'package:country_code_picker/country_code_picker.dart';import 'package:flutter/material.dart';import 'package:flutter_songbei/custom/login_head.dart';import 'package:flutter_songbei/custom/network_loading.dart';import 'package:flutter_songbei/network/chttp.dart';import 'package:flutter_songbei/network/params.dart';import 'package:flutter_songbei/provider/app.dart';import 'package:flutter_songbei/provider/user.dart';import 'package:flutter_songbei/shared/app_shared.dart';import 'package:flutter_songbei/utils/toast_util.dart';import 'package:provider/provider.dart';
import '../../app_theme.dart';import 'forgot_password_page.dart';import 'modify_info_page.dart';import 'signup_page.dart';
class LoginPage extends StatefulWidget { @override State createState() { return _LoginState(); }}
class _LoginState extends State<LoginPage> { TextEditingController phoneController = TextEditingController(); TextEditingController pwdController = TextEditingController(); TextEditingController codeController = TextEditingController();
String country_code = '+86';
/** * 登录方式 0 密码登录 1 验证码登录 */ int login_type = 0;
String _verifyCodeTips = '发送验证码';
TimerUtil timerCountDown;
///倒计时 int _verifyCodeCD = 0;
bool b_visible = false; bool b_clear = false; bool b_phone_clear = false;
@override void dispose() { super.dispose(); if (timerCountDown != null) timerCountDown.cancel(); }
@override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( title: Text('登录'), centerTitle: true, ), body: Container( decoration: BoxDecoration(), padding: EdgeInsets.fromLTRB(20.0, 40.0, 20.0, 0), child: SingleChildScrollView( child: Column( mainAxisAlignment: MainAxisAlignment.spaceEvenly, children: [ LoginHead(), Column( children: [// Row(// mainAxisAlignment: MainAxisAlignment.spaceEvenly,// children: [// FlatButton(// child: Text(// '密码登录',// style: TextStyle(// color: login_type == 0// ? AppTheme.mainColor// : AppTheme.grayColor,// decoration: TextDecoration.underline,// decorationStyle: TextDecorationStyle.solid,// fontSize: 16.0,// ),// ),// onPressed: () {// setState(() {// login_type = 0;// phoneController.text = '';// pwdController.text = '';// });// },// ),// FlatButton(// child: Text(// '验证码登录',// style: TextStyle(// color: login_type == 1// ? AppTheme.mainColor// : AppTheme.grayColor,// decoration: TextDecoration.underline,// decorationStyle: TextDecorationStyle.solid,// fontSize: 16.0,// ),// ),// onPressed: () {// setState(() {// login_type = 1;// phoneController.text = '';// pwdController.text = '';// });// },// ),// ]// ), _buildLoginType(), Row( mainAxisAlignment: MainAxisAlignment.end, children: [ FlatButton( child: Text( '注册', style: TextStyle( color: AppTheme.mainColor, decoration: TextDecoration.underline, decorationStyle: TextDecorationStyle.solid, fontSize: 16.0, ), ), onPressed: () { Navigator.of(context).push(MaterialPageRoute( builder: (BuildContext context) => SignupPage())); }, ),// FlatButton(// child: Text(// '邮箱注册',// style: TextStyle(// color: AppTheme.mainColor,// decoration: TextDecoration.underline,// decorationStyle: TextDecorationStyle.solid,// fontSize: 16.0,// ),// ),// onPressed: () {//// Navigator.of(context).push(MaterialPageRoute(//// builder: (BuildContext context) => EmailSignupPage()));// },// ), FlatButton( child: Text( '忘记密码', style: TextStyle( color: AppTheme.mainColor, decoration: TextDecoration.underline, decorationStyle: TextDecorationStyle.solid, fontSize: 16.0, ), ), onPressed: () { Navigator.of(context).push(MaterialPageRoute( builder: (BuildContext context) => ForgotPasswordPage())); }, ) ], ) ], ) ], ), )), ); }
void _onCountryChange(CountryCode countryCode) { //TODO : manipulate the selected country code here print("New Country selected: " + countryCode.toString()); country_code = countryCode.toString(); }
_buildLoginType() { if (login_type == 0) { return Column( children: [ Stack( children: [ Row(children: [ Expanded( child: TextField( controller: phoneController, keyboardType: TextInputType.phone, decoration: InputDecoration( contentPadding: EdgeInsets.all(10.0), hintText: '手机号', filled: true, fillColor: AppTheme.loginFillColor), onChanged: (String text) { //new 通过onChanged事件更新_isComposing 标志位的值 setState(() { //new 调用setState函数重新渲染受到_isComposing变量影响的IconButton控件 b_phone_clear = text.length > 0; //new 如果文本输入框中的字符串长度大于0则允许发送消息 }); //new }, ), ) ]), Positioned( right: 0, child: Offstage( offstage: !b_phone_clear, child: IconButton( iconSize: 20.0, icon: ImageIcon( AssetImage('assets/user/qingchu.png'), color: AppTheme.mainColor, ), onPressed: () { phoneController.text = ''; setState(() { b_phone_clear = false; }); }, ), ), ) ], ), Padding( padding: EdgeInsets.fromLTRB(0, 20, 0, 0), child: Stack( children: [ TextField( controller: pwdController, obscureText: !b_visible, decoration: InputDecoration( contentPadding: EdgeInsets.all(10.0), hintText: '请输入密码', filled: true, fillColor: AppTheme.loginFillColor), onChanged: (value) { setState(() { b_clear = value.length > 0; }); }, ), Positioned( right: 0, child: IconButton( iconSize: 25.0, icon: ImageIcon( AssetImage(b_visible ? 'assets/user/kejian.png' : 'assets/user/bukejian.png'), color: AppTheme.mainColor, ), onPressed: () { setState(() { b_visible = !b_visible; }); }, ), ), Positioned( right: 35, child: Offstage( offstage: !b_clear, child: IconButton( iconSize: 20.0, icon: ImageIcon( AssetImage('assets/user/qingchu.png'), color: AppTheme.mainColor, ), onPressed: () { setState(() { pwdController.text = ''; b_clear = false; }); }, ), ), ) ], ), ), Container( margin: EdgeInsets.fromLTRB(0, 20, 0, 0), width: 260, child: RaisedButton( color: AppTheme.mainColor, child: Text( '登录', style: AppTheme.loginButtonStyle, ), onPressed: () { _onLogin(context); }, )), ], ); } else if (login_type == 1) { return Column( children: [ Stack( children: [ Row(children: [ Container( color: Colors.white, child: CountryCodePicker( onChanged: _onCountryChange, // Initial selection and favorite can be one of code ('IT') OR dial_code('+39') initialSelection: 'CN', favorite: ['+86', 'china'], // optional. Shows only country name and flag showCountryOnly: false, // optional. Shows only country name and flag when popup is closed. showOnlyCountryWhenClosed: false, // optional. aligns the flag and the Text left alignLeft: false, ), ), Expanded( child: TextField( controller: phoneController, keyboardType: TextInputType.phone, decoration: InputDecoration( contentPadding: EdgeInsets.all(10.0), hintText: '请输入手机号', filled: true, fillColor: AppTheme.loginFillColor), onChanged: (String text) { //new 通过onChanged事件更新_isComposing 标志位的值 setState(() { //new 调用setState函数重新渲染受到_isComposing变量影响的IconButton控件 b_phone_clear = text.length > 0; //new 如果文本输入框中的字符串长度大于0则允许发送消息 }); //new }, ), ) ]), Positioned( right: 0, child: Offstage( offstage: !b_phone_clear, child: IconButton( iconSize: 20.0, icon: ImageIcon( AssetImage('assets/user/qingchu.png'), color: AppTheme.mainColor, ), onPressed: () { phoneController.text = ''; setState(() { b_phone_clear = false; }); }, ), ), ) ], ), Padding( padding: EdgeInsets.fromLTRB(0.0, 10.0, 0.0, 0.0), child: Row( mainAxisSize: MainAxisSize.min, children: [ Expanded( flex: 1, child: TextField( controller: codeController, decoration: InputDecoration( contentPadding: EdgeInsets.all(10.0), hintText: '请输入验证码',// helperText: '验证码' filled: true, fillColor: AppTheme.loginFillColor)), ), Container( margin: EdgeInsets.all(10.0), child: OutlineButton( borderSide: BorderSide(color: AppTheme.mainColor), child: Text( _verifyCodeTips, style: TextStyle(color: AppTheme.mainColor), ), onPressed: _onVeriCode, ), ) ], ), ), Container( margin: EdgeInsets.fromLTRB(0, 20, 0, 0), width: 260, child: RaisedButton( color: AppTheme.mainColor, child: Text( '登录', style: AppTheme.loginButtonStyle, ), onPressed: () { _onPhoneLogin(context); }, )), ], ); } }
void _onLogin(BuildContext context) { if (phoneController.text.length == 0) { ToastUtil.showToast(context, '请输入手机号'); return; } if (pwdController.text.length == 0) { ToastUtil.showToast(context, '请输入密码'); return; } _reqLogin(); }
///密码登录 _reqLogin() { showDialog( context: context, barrierDismissible: false, builder: (BuildContext context) { return NetworkLoading(); }); CHttp.post( CHttp.USER_LOGIN, (data) { if (!mounted) { return; } LogUtil.v(data); Navigator.pop(context); onLoginSuccess(data); }, params: PUserLogin(phoneController.text, pwdController.text).toJson(), errorCallback: (err) {// Fluttertoast.showToast(msg:err); Navigator.pop(context); ToastUtil.showToast(context, err); }, completeCallback: () {// Navigator.pop(context); }); }
_onPhoneLogin(BuildContext context) { if (phoneController.text.length == 0) { ToastUtil.showToast(context, '请输入手机号'); return; } if (codeController.text.length == 0) { ToastUtil.showToast(context, '请输入验证码'); return; } _reqPhoneLogin(); }
/** * 手机验证码登录 */ _reqPhoneLogin() {// CHttp.post(// CHttp.USER_PHONELOGIN,// (data) {// LogUtil.v(data);// onLoginSuccess(data);// },// params:// PUserPhoneLogin(country_code,phoneController.text, codeController.text).toJson(),// errorCallback: (err) {// ToastUtil.showToast(context, err);// }); }
void _onVeriCode() { if (phoneController.text.length == 0) { ToastUtil.showToast(context, '请输入手机号'); return; } if (_verifyCodeCD > 0) { ToastUtil.showToast(context, _verifyCodeCD.toString() + '秒后可重新发送'); return; } _reqVerifyCode(); }
///请求验证码 verifycode _reqVerifyCode() {// CHttp.post(// CHttp.USER_VERIFYCODE,// (data) {// ResUserVerifyCode resUserVerifyCode =// ResUserVerifyCode.fromJson(data);// setState(() {// _verifyCodeTips = resUserVerifyCode.time.toString() + '秒后重新发送';// });// _verifyCodeCD = resUserVerifyCode.time;// //倒计时test// timerCountDown =// new TimerUtil(mInterval: 1000, mTotalTime: _verifyCodeCD * 1000);// timerCountDown.setOnTimerTickCallback((int value) {// double tick = (value / 1000);//// LogUtil.e("CountDown: " + tick.toInt().toString());// if (value == 0) {// setState(() {// _verifyCodeCD = tick.toInt();// _verifyCodeTips = '发送验证码';// });// } else {// setState(() {// _verifyCodeCD = tick.toInt();// _verifyCodeTips = tick.toInt().toString() + '秒后重新发送';// });// }// });// timerCountDown.startCountDown();// },// params: PUserVerifyCode(country_code,phoneController.text, 2).toJson(),// errorCallback: (err) {// ToastUtil.showToast(context, err);// }); }
_reqPushId(userid, rid) {// CHttp.post(CHttp.NOTICE_PUSHID, (data) {},// params: PPush(userid, rid).toJson(), errorCallback: (err) {// ToastUtil.showToast(context, err);// }); }
onLoginSuccess(data) { print('----data:${data}'); String userid = data['userid']; print('----userid:${userid}'); AppShared.saveUserid(userid); AppShared.getRid().then((rid) { if (rid != null && rid.length > 0) {// JPush jpush = new JPush();// jpush.resumePush(); _reqPushId(userid, rid); } }); Provider.of(context, listen: false).saveUserId(userid); Provider.of(context, listen: false).upUser(data); var perfect = data['perfect'] != null && data['perfect'] == 1 ? 1 : 0; ToastUtil.showToast(context, '登录成功'); print('perfect:${perfect}'); Navigator.of(context).pop(perfect); if (perfect == 0) { Navigator.of(context).push(MaterialPageRoute( builder: (BuildContext context) => ModifyInfoPage())); } else { ToastUtil.showToast(context, '登录成功'); } }}

服务器登录协议逻辑

router.post('/login', function(req, res, next) {    try {        ru.logReq(req);        var phone = req.body.phone;        var password = req.body.password;        var os = req.body.os;        userDao.queryUser(phone, function(err, users) {            if (err) {                ru.resError(res, err);            } else {                var length = users.length;                if (length > 0) {                    var user = users[0];                    if (user.password == password) {                        if (user.os != os) {                            userDao.updateUserOs(user.userid, os, function(err, result) {
}) } ru.resSuccess(res, user); } else { ru.resError(res, '密码错误'); } } else { ru.resError(res, '用户未注册'); } } }) } catch (err) { ru.resError(res, err.message); }});


登录成功后,[我的]页面会显示个人信息

完善资料功能

客户端页面

import 'dart:io';
import 'package:common_utils/common_utils.dart';import 'package:flutter/material.dart';import 'package:flutter_songbei/custom/common_bottom_sheet.dart';import 'package:flutter_songbei/custom/network_loading.dart';import 'package:flutter_songbei/manager/ui_manager.dart';import 'package:flutter_songbei/network/chttp.dart';import 'package:flutter_songbei/network/params.dart';import 'package:flutter_songbei/provider/app.dart';import 'package:flutter_songbei/provider/user.dart';import 'package:flutter_songbei/utils/qiniu.dart';import 'package:flutter_songbei/utils/toast_util.dart';import 'package:image_cropper/image_cropper.dart';import 'package:image_picker/image_picker.dart';import 'package:provider/provider.dart';import 'package:cached_network_image/cached_network_image.dart';import 'package:sy_flutter_qiniu_storage/sy_flutter_qiniu_storage.dart';
import '../../app_theme.dart';
class ModifyInfoPage extends StatefulWidget { @override State createState() { return _ModifyInfoState(); }}
class _ModifyInfoState extends State<ModifyInfoPage> { User user = User();
TextEditingController nameController = TextEditingController();
TextEditingController profileController = TextEditingController();
String token = ''; double _process = 0.0; String qiniu_key = ''; bool b_head_file = false;
final ImagePicker _picker = ImagePicker();
@override void initState() { super.initState(); WidgetsBinding.instance.addPostFrameCallback((_) { user = Provider.of(context, listen: false); nameController.text = user.nickname; profileController.text = user.profile; _reqQiniuToken(); }); }
@override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( title: Text( '完善资料', style: TextStyle(color: Colors.white), ), centerTitle: true, leading: IconButton( icon: Icon( Icons.arrow_back_ios, color: Colors.white, ), onPressed: () { Navigator.pop(context); }, ), actions: [ FlatButton( child: Text( '保存', style: TextStyle(color: AppTheme.loginFillColor), ), onPressed: () { _onUpPerson(); }, ) ]), body: SingleChildScrollView( child: Container( color: Colors.white, padding: EdgeInsets.all(10.0), child: Column( children: [ Column( children: [ Container( // padding: EdgeInsets.all(2), width: 82, height: 82, // color: Colors.white, decoration: new BoxDecoration( border: new Border.all(color: Colors.black26, width: 1), // 边色与边宽度 color: Colors.white, // 底色 // borderRadius: new BorderRadius.circular((20.0)), // 圆角度 // borderRadius: BorderRadius.circular(50), // 也可控件一边圆角大小 ), child: GestureDetector( child: _buildHead(), onTap: (){ showPhotos(); }, ), ), Padding( padding: EdgeInsets.fromLTRB(0, 5, 0, 5), child: Text('点击更换头像'), ) ], ), Theme( data: Theme.of(context).copyWith( hintColor: Colors.grey[800], //定义下划线颜色 ), child: TextField( controller: nameController, decoration: InputDecoration( hintText: '请输入昵称', hintStyle: TextStyle( color: Colors.grey[800], fontSize: 14, ), filled: true, fillColor: Colors.white, ), onChanged: (str) {// setState(() {// user.nickname = str;// }); }, autofocus: false, style: AppTheme.f_s14_grey, ), ), Row( children: [ Text( '性别', style: AppTheme.f_s14_grey, ), Center( child: new Row( mainAxisAlignment: MainAxisAlignment.center, children: [ Theme( data: Theme.of(context).copyWith( unselectedWidgetColor: Colors.grey[800], ), child: Radio( value: 1, groupValue: user.gender, onChanged: (int rval) { setState(() { user.gender = rval; }); }, activeColor: AppTheme.mainColor, ), ), Text( '男', style: TextStyle(color: Colors.grey[800]), ), Theme( data: Theme.of(context).copyWith( unselectedWidgetColor: Colors.grey[800], ), child: Radio( value: 0, groupValue: user.gender, onChanged: (int rval) { setState(() { user.gender = rval; }); }, activeColor: AppTheme.mainColor, )), Text( '女', style: TextStyle(color: Colors.grey[800]), ), ], ), ) ], ), Row( children: [ Text( '出生日期', style: AppTheme.f_s14_grey, ), FlatButton( child: Text( user.birth_date != null && user.birth_date.length > 0 ? user.birth_date : '请选择出生日期', style: AppTheme.f_s14_blue, ), onPressed: () { _showDatePicker(context); }, ) ], ), Theme( data: Theme.of(context).copyWith( hintColor: Colors.grey[800], //定义下划线颜色 ), child: TextField( style: AppTheme.f_s14_grey, controller: profileController, decoration: InputDecoration( hintText: '请输入简介', hintStyle: TextStyle( color: Colors.grey[800], fontSize: 14, ), filled: true, fillColor: Colors.white, ), cursorColor: Colors.white, onChanged: (str) { setState(() { user.profile = str; }); }, autofocus: false, maxLines: 3, ), ), ], ), ), ), ); }
_buildHead() { print('----_buildHead:${user.head}'); if (user.head != null && user.head.length > 0) { return Container( height: 80, child: b_head_file ? Image.file(File(user.head)) : CachedNetworkImage(imageUrl: UIManager.getHeadurl(user.head)), ); } else { return Icon(Icons.add_a_photo); } }
_showDatePicker(BuildContext content) async { var _dateTime = DateTime(1990); final DateTime _picked = await showDatePicker( context: content, initialDate: _dateTime, firstDate: DateTime(1900), lastDate: DateTime.now(), ); if (_picked != null) { LogUtil.v(_picked); LogUtil.v(_picked.year); LogUtil.v(_picked.month); LogUtil.v(_picked.day); setState(() { user.birth_date = _picked.year.toString() + '-' + _picked.month.toString() + '-' + _picked.day.toString(); }); } }
void showPhotos() { showDialog( barrierDismissible: true, //是否点击空白区域关闭对话框,默认为true,可以关闭 context: context, builder: (BuildContext context) { var list = List(); list.add('相册'); list.add('相机'); return CommonBottomSheet( list: list, onItemClickListener: (index) async { print('-----index:$index'); if (index == 0 || index == 2) { var source = ImageSource.gallery; if (index == 2) source = ImageSource.camera; _onUpload(source); } Navigator.pop(context); }, ); }); }
_onUpload(ImageSource source) async { print('---_onUpload:${token}'); PickedFile imageFile = await _picker.getImage(source: source); File file = await ImageCropper.cropImage( sourcePath: imageFile.path, aspectRatio: CropAspectRatio(ratioX: 1, ratioY: 1), aspectRatioPresets: [ CropAspectRatioPreset.square,// CropAspectRatioPreset.ratio3x2,// CropAspectRatioPreset.original,// CropAspectRatioPreset.ratio4x3,// CropAspectRatioPreset.ratio16x9 ], androidUiSettings: AndroidUiSettings( toolbarTitle: '裁剪', toolbarColor: Colors.deepOrange, toolbarWidgetColor: Colors.white, initAspectRatio: CropAspectRatioPreset.square, lockAspectRatio: true), iosUiSettings: IOSUiSettings( minimumAspectRatio: 1.0, )); setState(() { user.head = file.path; b_head_file = true; }); if (file == null) { ToastUtil.showToast(context, '选择图片失败'); return; } if (token == null || (token != null && token.length == 0)) { ToastUtil.showToast(context, 'token 无效!'); return; } showDialog( context: context, barrierDismissible: false, builder: (BuildContext context) { return NetworkLoading(); }); final syStorage = new SyFlutterQiniuStorage(); //监听上传进度 syStorage.onChanged().listen((dynamic percent) { double p = percent; setState(() { _process = p; });// print(percent); LogUtil.e('---percent:${percent}'); if (p >= 1.0) {// LogUtil.e('---qiuniu_link:${qiniu_link}'); setState(() { user.head = Qiniu.getUpUrl(qiniu_key); b_head_file = false; }); Navigator.pop(context); } });
String key = Qiniu.getUpPath(file.path);// LogUtil.e('---key:${key}'); setState(() { qiniu_key = key; }); //上传文件 UploadResult result = await syStorage.upload(file.path, token, key); }
_onUpPerson() { if(nameController.text.length == 0){ ToastUtil.showToast(context, '请输入昵称'); return; } if(b_head_file == true){ ToastUtil.showToast(context, '头像上传中...'); return; } user.nickname = nameController.text; user.profile = profileController.text; if (user.gender != 0 && user.gender != 1) { ToastUtil.showToast(context, '请选择性别'); return; } _reqUpUserInfo(); }
_reqUpUserInfo() { user.userid = Provider.of(context, listen: false).userid; CHttp.post( CHttp.USER_UPINFO, (data) { LogUtil.v(data); Provider.of(context, listen: false).upInfo(data); ToastUtil.showToast(context, '修改成功'); Navigator.of(context).pop(); }, params: PUpUser( Provider.of(context, listen: false).userid, user.head, nameController.text, profileController.text, user.gender, user.birth_date) .toJson(), errorCallback: (err) { ToastUtil.showToast(context, err); }); }
_reqQiniuToken() { CHttp.post(CHttp.QINIU_UPTOKEN, (data) { LogUtil.e('-----data:${data}'); setState(() { token = data; }); }, errorCallback: (err) { LogUtil.e('-----err:${err}'); }); }}

服务器逻辑

router.post('/upinfo', function(req, res, next) {    try {        ru.logReq(req);        var body = req.body;        var userid = body.userid;        var nickname = body.nickname;        var head = body.head;        var gender = body.gender;        var birth_date = body.birth_date;        var profile = body.profile;        if (!userid) {            ru.resError(res, '参数错误')            return;        }        userDao.upUser(userid, nickname, head, gender, birth_date, profile, function(err, result) {            if (err) {                ru.resError(res, err);                return;            }            userDao.queryUserInfo(userid, function(err, result) {                if (err) {                    ru.resError(res, err);                    return;                }                console.log(result)                var user = {};                if (result.length > 0) {                    user = result[0][0];                    user.myfollow = result[1][0].count;                    user.followme = result[2][0].count;                }                ru.resSuccess(res, user);            })        })    } catch (err) {        ru.resError(res, err.message);    }
});


至此,账号系统基本完成,代码部分并没有完整展示,因为会不断修改完善。这个等项目写的七七八八的时候,我会放到github上,方面查阅。其中很多细节没法一篇介绍完,后边逐一介绍。

浏览 7
点赞
评论
收藏
分享

手机扫一扫分享

举报
评论
图片
表情
推荐
点赞
评论
收藏
分享

手机扫一扫分享

举报