用户中心

一、退出登录

APP项目中对于用户的退出登录,一般都在设置中进行。
客户端新增配置页面setting.html,代码:

<!DOCTYPE html>
<html>
<head><title>用户中心</title><meta name="viewport" content="width=device-width,minimum-scale=1.0,maximum-scale=1.0,user-scalable=no"><meta charset="utf-8"><link rel="stylesheet" href="../static/css/main.css"><script src="../static/js/vue.js"></script><script src="../static/js/axios.js"></script><script src="../static/js/main.js"></script><script src="../static/js/uuid.js"></script><script src="../static/js/settings.js"></script>
</head>
<body><div class="app user setting" id="app"><div class="bg"><img src="../static/images/form_bg.png"></div><img class="back" @click="goto_home" src="../static/images/user_back.png" alt=""><div class="form"><div class="item avatar"><span class="title">头像</span><span class="goto">&gt;</span><span class="value"><img src="../static/images/avatar.png" alt=""></span></div><div class="item"><span class="title">昵称</span><span class="goto">&gt;</span><span class="value">清蒸小帅锅</span></div><div class="item"><span class="title">手机号</span><span class="goto">&gt;</span><span class="value">139****5901</span></div><div class="item"><span class="title">登陆密码</span><span class="value"></span><span class="goto">&gt;</span></div><div class="item"><span class="title">交易密码</span><span class="value"></span><span class="goto">&gt;</span></div><div class="item"><span class="title">地址管理</span><span class="value"></span><span class="goto">&gt;</span></div><div class="item"><span class="title">设备管理</span><span class="value"></span><span class="goto">&gt;</span></div><div class="item logout"><img @click="change_account" src="../static/images/change_account.png" alt=""><p @click="logout">退出账号</p></div></div></div><script>apiready = function(){init();new Vue({el:"#app",data(){return {prev:{name:"",url:"",params:{}},current:{name:"setting",url:"setting.html",params:{}},}},methods:{goto_home(){// this.game.outFrame("setting");this.game.goFrame("user","user.html",this.current);},change_account(){// 切换账号this.game.goFrame("login","login.html", this.current);},logout(){// 退出账号api.actionSheet({title: '您确认要退出当前登录吗?',cancelTitle: '取消',destructiveTitle: '退出登录'}, (ret, err)=>{if( ret ){this.game.print(ret);if(ret.buttonIndex==1){this.game.save({"access_token":"","refresh_token":""});this.game.fremove(["access_token","refresh_token"]);this.game.outWin("user");}}});}}});}</script>
</body>
</html>

static/css/main.css,样式代码:


.setting .bg img{animation: normal;
}
.setting .back{top: 4rem;
}
.setting .form {top: 9rem;
}
.setting .form .item{height: 3.9rem;line-height: 3.9rem;font-size: 1.25rem;text-indent: 0.6rem;border-bottom: 1px solid rgba(204,153,102,0.2);
}
.setting .form .avatar{height: 6.11rem;line-height: 6.11rem;
}
.setting .form .avatar img{width: 4.56rem;height: 4.56rem;vertical-align: middle;
}
.setting .form .item .value,
.setting .form .item .goto{float: right;
}
.setting .form .logout{margin-top: 3rem;text-align: center;height: 4rem;line-height: 2rem;
}
.setting .form .logout img{width: 11rem;
}

在用户中心实现页面跳转user.html,代码:

<!DOCTYPE html>
<html>
<head><title>用户中心</title><meta name="viewport" content="width=device-width,minimum-scale=1.0,maximum-scale=1.0,user-scalable=no"><meta charset="utf-8"><link rel="stylesheet" href="../static/css/main.css"><script src="../static/js/vue.js"></script><script src="../static/js/axios.js"></script><script src="../static/js/main.js"></script><script src="../static/js/uuid.js"></script><script src="../static/js/settings.js"></script>
</head>
<body><div class="app user" id="app"><div class="bg"><img src="../static/images/bg0.jpg"></div><img class="back" @click="goto_index" src="../static/images/user_back.png" alt=""><img class="setting" @click='goto_setting' src="../static/images/setting.png" alt=""><div class="header"><div class="info"><div class="avatar"><img class="avatar_bf" src="../static/images/avatar_bf.png" alt=""><img class="user_avatar" src="../static/images/avatar.png" alt=""><img class="avatar_border" src="../static/images/avatar_border.png" alt=""></div><p class="user_name">清蒸小帅锅</p></div><div class="wallet"><div class="balance"><p class="title"><img src="../static/images/money.png" alt="">钱包</p><p class="num">99,999.00</p></div><div class="balance"><p class="title"><img src="../static/images/integral.png" alt="">果子</p><p class="num">99,999.00</p></div></div><div class="invite"><img class="invite_btn" src="../static/images/invite.png" alt=""></div></div><div class="menu"><div class="item"><span class="title">我的主页</span><span class="value">查看</span></div><div class="item"><span class="title">任务列表</span><span class="value">75%</span></div><div class="item"><span class="title">收益明细</span><span class="value">查看</span></div><div class="item"><span class="title">实名认证</span><span class="value">未认证</span></div><div class="item"><span class="title">问题反馈</span><span class="value">去反馈</span></div></ul></div></div><script>apiready = function(){init();new Vue({el:"#app",data(){return {prev:{name:"",url:"",params:{}},current:{name:"user",url:"user.html",params:{}},}},methods:{goto_index(){// 返回首页this.game.outWin("user");},goto_setting(){this.game.goFrame('setting', 'setting.html', this.current);}}});}</script>
</body>
</html>

main.js,代码:

class Game{constructor(bg_music){// 构造函数,相当于python中类的__init__方法this.init();if(bg_music){this.play_music(bg_music);}}open(){}init(){// 初始化// console.log("系统初始化");this.rem();// 帧页面组的初始化this.groupname = null;this.groupindex = 0;}print(data){// 打印数据console.log(JSON.stringify(data));}back(prev){// 返回上一页this.go(prev.name,prev.url,{...prev});}outWin(name){// 关闭窗口api.closeWin(name);}goWin(name,url,pageParam){// 新建窗口api.openWin({name: name,            // 自定义窗口名称bounces: false,        // 窗口是否上下拉动reload: true,          // 如果页面已经在之前被打开了,是否要重新加载当前窗口中的页面useWKWebView:true,historyGestureEnabled:true,url: url,              // 窗口创建时展示的html页面的本地路径[相对于当前代码所在文件的路径]animation:{            // 打开新建窗口时的过渡动画效果type: "push",                //动画类型(详见动画类型常量)subType: "from_right",       //动画子类型(详见动画子类型常量)duration:300                //动画过渡时间,默认300毫秒},pageParam: pageParam   // 传递给下一个窗口使用的参数.将来可以在新窗口中通过 api.pageParam.name 获取});}goFrame(name,url,pageParam,rect=null,animation=null){// 创建帧页面if(rect === null){rect = {// 方式1,设置矩形大小宽高x: 0,             // 左上角x轴坐标y: 0,             // 左上角y轴坐标w: 'auto',        // 当前帧页面的宽度, auto表示满屏h: 'auto'         // 当前帧页面的高度, auto表示满屏}}if (animation === null) {animation = {type: 'push',subType:'from_right',duration:300}}api.openFrame({name: name,        // 帧页面的名称url: url,          // 帧页面打开的url地址bounces:false,     // 页面是否可以下拉拖动reload: true,      // 帧页面如果已经存在,是否重新刷新加载useWKWebView: true,historyGestureEnabled:true,animation:animation,rect: rect,                  // 当前帧的宽高范围pageParam: pageParam,        // 要传递新建帧页面的参数,在新页面可通过 api.pageParam.name 获取});}outFrame(name){// 关闭帧页面api.closeFrame({name: name,});}openGroup(name,frames,preload=1,rect=null){// 创建frame组if(rect === null){rect = {           // 帧页面组的显示矩形范围x:0,             //左上角x坐标,数字类型y:0,             //左上角y坐标,数字类型w:'auto',             //宽度,若传'auto',页面从x位置开始自动充满父页面宽度,数字或固定值'auto'h:'auto',             //高度,若传'auto',页面从y位置开始自动充满父页面高度,数字或固定值'auto'};}api.openFrameGroup({name: name,            // 组名scrollEnabled: false,  // 页面组是否可以左右滚动index: 0,              // 默认显示页面的索引rect: rect,            // 页面宽高范围preload: preload,      // 默认预加载的页面数量frames: frames,        // 帧页面组的帧页面成员}, (ret, err)=>{// 当前帧页面发生页面显示变化时,当前帧的索引.this.groupindex = ret.index;});}outGroup(name){// 关闭 frame组api.closeFrameGroup({name: name // 组名});}goGroup(name,index){// 切换显示frame组下某一个帧页面api.setFrameGroupIndex({name: name,  // 组名index: index // 索引,从0开始});}rem(){if(window.innerWidth<1200){this.UIWidth = document.documentElement.clientWidth;this.UIHeight = document.documentElement.clientHeight;document.documentElement.style.fontSize = (0.01*this.UIWidth*3)+"px";document.querySelector("#app").style.height=this.UIHeight+"px"}window.onresize = ()=>{if(window.innerWidth<1200){this.UIWidth = document.documentElement.clientWidth;this.UIHeight = document.documentElement.clientHeight;document.documentElement.style.fontSize = (0.01*this.UIWidth*3)+"px";}}}save(data){// 保存数据到内存中for(var key in data){api.setGlobalData({key: key,value: data[key]})}}get(data){// 从内存中获取数据if(!Array.isArray(data)){data = [data];}var result = {};for(var key of data){result[key] = api.getGlobalData({'key': key});}// 如果只是获取一个数据的情况,直接返回值,不需要返回json对象if(data.length == 1){return result[key];}return result;}fsave(data){// 保存数据到文件中for(var key in data){api.setPrefs({'key': key,value: data[key]});}}fremove(data){// 从文件中删除数据if(!Array.isArray(data)){data = [data];}for(var key of data){api.removePrefs({'key': key,});}}fget(data){// 从文件中获取数据if(!Array.isArray(data)){data = [data];}var value;var result = {}for(var key of data){result[key] = api.getPrefs({sync: true,key: key});}// 如果只是获取一个数据的情况,直接返回值,不需要返回json对象if(data.length == 1){return result[key];}return result;}stop_music(){// this.print("停止背景音乐");document.body.removeChild(this.audio);}play_music(src){// this.print("播放背景音乐");this.audio = document.createElement("audio");this.source = document.createElement("source");this.source.type = "audio/mp3";this.audio.autoplay = "autoplay";this.source.src=src;this.audio.appendChild(this.source);/*<audio autoplay="autoplay"><source type="audio/mp3" src="../static/mp3/bg1.mp3"></audio>*/document.body.appendChild(this.audio);// 自动暂停关闭背景音乐var t = setInterval(()=>{if(this.audio.readyState > 0){if(this.audio.ended){clearInterval(t);try{document.body.removeChild(this.audio);}catch(error){// 不处理}}}},100);}
}

二、更新头像

点击头像进入更新头像页面,setting.html,代码:

<!DOCTYPE html>
<html>
<head><title>用户中心</title><meta name="viewport" content="width=device-width,minimum-scale=1.0,maximum-scale=1.0,user-scalable=no"><meta charset="utf-8"><link rel="stylesheet" href="../static/css/main.css"><script src="../static/js/vue.js"></script><script src="../static/js/axios.js"></script><script src="../static/js/main.js"></script><script src="../static/js/uuid.js"></script><script src="../static/js/settings.js"></script>
</head>
<body><div class="app user setting" id="app"><div class="bg"><img src="../static/images/form_bg.png"></div><img class="back" @click="goto_home" src="../static/images/user_back.png" alt=""><div class="form"><div class="item avatar"><span class="title">头像</span><span class="goto">&gt;</span><span class="value"><img @click="update_avatar_frame" src="../static/images/avatar.png" alt=""></span></div><div class="item"><span class="title">昵称</span><span class="goto">&gt;</span><span class="value">清蒸小帅锅</span></div><div class="item"><span class="title">手机号</span><span class="goto">&gt;</span><span class="value">139****5901</span></div><div class="item"><span class="title">登陆密码</span><span class="value"></span><span class="goto">&gt;</span></div><div class="item"><span class="title">交易密码</span><span class="value"></span><span class="goto">&gt;</span></div><div class="item"><span class="title">地址管理</span><span class="value"></span><span class="goto">&gt;</span></div><div class="item"><span class="title">设备管理</span><span class="value"></span><span class="goto">&gt;</span></div><div class="item logout"><img @click="change_account" src="../static/images/change_account.png" alt=""><p @click="logout">退出账号</p></div></div></div><script>apiready = function(){init();new Vue({el:"#app",data(){return {prev:{name:"",url:"",params:{}},current:{name:"setting",url:"setting.html",params:{}},}},methods:{goto_home(){// this.game.outFrame("setting");this.game.goFrame("user","user.html",this.current);},change_account(){// 切换账号this.game.goFrame("login","login.html", this.current);},logout(){// 退出账号api.actionSheet({title: '您确认要退出当前登录吗?',cancelTitle: '取消',destructiveTitle: '退出登录'}, (ret, err)=>{if( ret ){this.game.print(ret);if(ret.buttonIndex==1){this.game.save({"access_token":"","refresh_token":""});this.game.fremove(["access_token","refresh_token"]);this.game.outWin("user");}}});},update_avatar_frame(){// 更换头像this.game.goFrame('avatar','avatar.html', this.current, null,{type:'push',  // 动画类型(详见动画类型常量)subType:'from_top',  // 动画子类型(详见动画子类型常量)duration:300  // 动画过渡时间,默认300毫秒})}}});}</script>
</body>
</html>

avatar.html,显示页面,代码:

<!DOCTYPE html>
<html>
<head><title>用户中心</title><meta name="viewport" content="width=device-width,minimum-scale=1.0,maximum-scale=1.0,user-scalable=no"><meta charset="utf-8"><link rel="stylesheet" href="../static/css/main.css"><script src="../static/js/vue.js"></script><script src="../static/js/axios.js"></script><script src="../static/js/main.js"></script><script src="../static/js/uuid.js"></script><script src="../static/js/settings.js"></script>
</head>
<body><div class="app frame avatar" id="app"><div class="box"><p class="title">上传头像</p><img class="close" src="../static/images/close_btn1.png" alt=""><div class="content"><p class="header">!注意事项</p><p class="text">禁止使用有诱导性的内容,二维码,联系方式等违规违法违约的图片,一经发现,永久封号,并保留追究法律责任的权利。</p></div><img @click="update_avatar_confirm" class="btn" src="../static/images/yes.png" alt=""></div></div><script>apiready = function(){init();new Vue({el:"#app",data(){return {prev:{name:"",url:"",params:{}},current:{name:"avatar",url:"avatar.html",params:{}},}},methods:{update_avatar_confirm(){// 确认上传头像},upload_avatar(ret){// 头像上传处理}}});}</script>
</body>
</html>

main.css,页面样式,代码:

.avatar.frame{background-color: rgba(0,0,0,0.6);
}
.avatar{overflow: hidden;
}
.avatar .box{width: 28.89rem;height: 34.44rem;background: url("../images/board_bg1.png") no-repeat 0 0;background-size: 100%;position: absolute;top: 11rem;margin: 0 auto;left: 0;right: 0;
}.avatar .box .title{color: #fff;font-size: 2rem;text-align: center;margin-top: 2.8rem;
}
.avatar .box .close{width: 5.22rem;height: 5.78rem;position: absolute;right: 0;top: 8rem;
}
.avatar .box .header{margin-top: 3.6rem;font-size: 1.8rem;text-align: center;color: #ff3333;font-weight: bold;
}
.avatar .box .text{width: 16.67rem;margin: 1.4rem auto 0;font-size: 1.22rem;color: #ffffcc;
}
.avatar .box .btn{display: block;width: 12.22rem;height: 4.55rem;margin: 1.4rem auto 0;
}

头像上传来源选择,avatar.html,代码:

<!DOCTYPE html>
<html>
<head><title>用户中心</title><meta name="viewport" content="width=device-width,minimum-scale=1.0,maximum-scale=1.0,user-scalable=no"><meta charset="utf-8"><link rel="stylesheet" href="../static/css/main.css"><script src="../static/js/vue.js"></script><script src="../static/js/axios.js"></script><script src="../static/js/main.js"></script><script src="../static/js/uuid.js"></script><script src="../static/js/settings.js"></script>
</head>
<body><div class="app frame avatar" id="app"><div class="box"><p class="title">上传头像</p><img class="close" @click="close_frame" src="../static/images/close_btn1.png" alt=""><div class="content"><p class="header">!注意事项</p><p class="text">禁止使用有诱导性的内容,二维码,联系方式等违规违法违约的图片,一经发现,永久封号,并保留追究法律责任的权利。</p></div><img @click="update_avatar_confirm" class="btn" src="../static/images/yes.png" alt=""></div></div><script>apiready = function(){init();new Vue({el:"#app",data(){return {prev:{name:"",url:"",params:{}},current:{name:"avatar",url:"avatar.html",params:{}},}},methods:{close_frame(){this.game.outFrame("avatar");},update_avatar_confirm(){// 确认上传头像的方式api.actionSheet({title: '请选择上传头像的来源',cancelTitle: '取消',buttons: ['相册','相机']}, function(ret, err){if( ret ){alert( JSON.stringify( ret ) );}else{this.game.print( err );}});},upload_avatar(ret){// 头像上传处理}}});}</script>
</body>
</html>

接下来,调用apicloud提供的本地接口从相册或者相机中提取图片.

注意: 如果使用模拟器开发APP的话, 就不能使用相机!相机只能用于真机测试!

avatar.html代码:

<!DOCTYPE html>
<html>
<head><title>用户中心</title><meta name="viewport" content="width=device-width,minimum-scale=1.0,maximum-scale=1.0,user-scalable=no"><meta charset="utf-8"><link rel="stylesheet" href="../static/css/main.css"><script src="../static/js/vue.js"></script><script src="../static/js/axios.js"></script><script src="../static/js/main.js"></script><script src="../static/js/uuid.js"></script><script src="../static/js/settings.js"></script>
</head>
<body><div class="app frame avatar" id="app"><div class="box"><p class="title">上传头像</p><img class="close" @click="close_frame" src="../static/images/close_btn1.png" alt=""><div class="content"><p class="header">!注意事项</p><p class="text">禁止使用有诱导性的内容,二维码,联系方式等违规违法违约的图片,一经发现,永久封号,并保留追究法律责任的权利。</p></div><img @click="update_avatar_confirm" class="btn" src="../static/images/yes.png" alt=""></div></div><script>apiready = function(){init();new Vue({el:"#app",data(){return {prev:{name:"",url:"",params:{}},current:{name:"avatar",url:"avatar.html",params:{}},}},methods:{close_frame(){this.game.outFrame("avatar");},update_avatar_confirm(){// 确认上传头像的方式api.actionSheet({title: '请选择上传头像的来源',cancelTitle: '取消',buttons: ['相册','相机']}, (ret, err)=>{if( ret ){// 本地获取相册/相机的图片var sourceType = ['album', 'camera'];if (ret.buttonIndex > sourceType.length) {// 如果用户选择了取消,则关闭当前修改头像的页面this.game.outFrame('avatar');return;}api.getPicture({sourceType: sourceType[ret.buttonIndex - 1],mediaValue: 'pic',destinationType: 'base64',allowEdit: true,preview: true,quality: 50,targetWidth: 100,targetHeight: 100,saveToPhotoAlbum: true,}, (ret, err)=>{if(ret){this.game.print(ret));}else{this.game.print(err));}});alert( JSON.stringify( ret ) );}else{this.game.print( err );}});},upload_avatar(ret){// 头像上传处理}}});}</script>
</body>
</html>

服务端提供头像更新接口,application/apps/users/views.py,代码:

import base64, uuid, os
@jsonrpc.method('User.avatar.update')
@jwt_required  # 验证jwt
def update_avatar(avatar):"""更新用户头像"""# 1.接收客户端上传的头像信息ext = avatar[avatar.find('/') + 1: avatar.find(';')]  # 资源格式b64_avatar = avatar[avatar.find(',') + 1:]b64_image = base64.b64decode(b64_avatar)filename = uuid.uuid4()static_path = os.path.join(current_app.BASE_DIR, current_app.config['STATIC_DIR'])with open('%s/%s.%s' % (static_path, filename, ext), 'wb') as f:f.write(b64_image)return 'ok'

application/settings/dev.py,代码:

from . import InitConfig
class Config(InitConfig):"""项目开发环境下的配置"""DEBUG = True# 数据库SQLALCHEMY_DATABASE_URI = "mysql://mofang_user:mofang@127.0.0.1:3306/mofang?charset=utf8mb4"SQLALCHEMY_ECHO = True# redisREDIS_URL = "redis://@127.0.0.1:6379/0"# session存储配置SESSION_REDIS_HOST = "127.0.0.1"SESSION_REDIS_PORT = 6379SESSION_REDIS_DB = 1# 日志配置LOG_LEVEL = "INFO"  # 日志输出到文件中的最低等级LOG_DIR = "/logs/mofang.log"  # 日志存储目录LOG_MAX_BYTES = 300 * 1024 * 1024   # 单个日志文件的存储上限[单位: b]LOG_BACKPU_COUNT = 20   # 日志文件的最大备份数量LOG_NAME = "mofang"  # 日志器名称# 注册蓝图INSTALLED_APPS = ["application.apps.home","application.apps.users","application.apps.marsh",]# 短信相关配置SMS_ACCOUNT_ID = "8aaf0708754a3ef2017563ddb22d0773"  # 接口主账号SMS_ACCOUNT_TOKEN = "0b41612bc8a8429d84b5d37f29178743"  # 认证token令牌SMS_APP_ID = "8aaf0708754a3ef2017563ddb3110779"  # 应用IDSMS_TEMPLATE_ID = 1  # 短信模板IDSMS_EXPIRE_TIME = 60 * 5  # 短信有效时间,单位:秒/sSMS_INTERVAL_TIME = 60   # 短信发送冷却时间,单位:秒/s# jwt 相关配置# 加密算法,默认: HS256JWT_ALGORITHM = "HS256"# 秘钥,默认是flask配置中的SECRET_KEYJWT_SECRET_KEY = "y58Rsqzmts6VCBRHes1Sf2DHdGJaGqPMi6GYpBS4CKyCdi42KLSs9TQVTauZMLMw"# token令牌有效期,单位: 秒/s,默认: datetime.timedelta(minutes=15) 或者 15 * 60JWT_ACCESS_TOKEN_EXPIRES = 60 * 60# refresh刷新令牌有效期,单位: 秒/s,默认:datetime.timedelta(days=30) 或者 30*24*60*60JWT_REFRESH_TOKEN_EXPIRES = 30*24*60*60# 设置通过哪种方式传递jwt,默认是http请求头,也可以是query_string,json,cookiesJWT_TOKEN_LOCATION = ["headers", "query_string"]# 当通过http请求头传递jwt时,请求头参数名称设置,默认值: AuthorizationJWT_HEADER_NAME = "Authorization"# 当通过查询字符串传递jwt时,查询字符串的参数名称设置,默认:jwtJWT_QUERY_STRING_NAME = 'token'# 当通过http请求头传递jwt时,令牌的前缀。# 默认值为 "Bearer",例如:Authorization: Bearer <JWT>JWT_HEADER_TYPE = "jwt"# 防水墙验证码CAPTCHA_GATEWAY = "https://ssl.captcha.qq.com/ticket/verify"CAPTCHA_APP_ID = "2041284967"CAPTCHA_APP_SECRET_KEY = "0FrDthTnnU8vG-jSwz7DOAA**"

application/settings/__init__.py,代码:

    # 静态文件目录存储路径STATIC_DIR = "application/static"

客户端基于axios上传图片数据,代码:
avatar.html

<!DOCTYPE html>
<html>
<head><title>用户中心</title><meta name="viewport" content="width=device-width,minimum-scale=1.0,maximum-scale=1.0,user-scalable=no"><meta charset="utf-8"><link rel="stylesheet" href="../static/css/main.css"><script src="../static/js/vue.js"></script><script src="../static/js/axios.js"></script><script src="../static/js/main.js"></script><script src="../static/js/uuid.js"></script><script src="../static/js/settings.js"></script>
</head>
<body><div class="app frame avatar" id="app"><div class="box"><p class="title">上传头像</p><img class="close" @click="close_frame" src="../static/images/close_btn1.png" alt=""><div class="content"><p class="header">!注意事项</p><p class="text">禁止使用有诱导性的内容,二维码,联系方式等违规违法违约的图片,一经发现,永久封号,并保留追究法律责任的权利。</p></div><img @click="update_avatar_confirm" class="btn" src="../static/images/yes.png" alt=""></div></div><script>apiready = function(){init();new Vue({el:"#app",data(){return {prev:{name:"",url:"",params:{}},current:{name:"avatar",url:"avatar.html",params:{}},}},methods:{close_frame(){this.game.outFrame("avatar");},update_avatar_confirm(){// 确认上传头像的方式api.actionSheet({title: '请选择上传头像的来源',cancelTitle: '取消',buttons: ['相册','相机'],}, (ret, err)=>{if( ret ){// 本地获取相册/相机的图片var sourceType = ['album', 'camera'];if (ret.buttonIndex > sourceType.length) {// 如果用户选择了取消,则关闭当前修改头像的页面this.game.outFrame('avatar');return;}api.getPicture({sourceType: sourceType[ret.buttonIndex - 1],mediaValue: 'pic',destinationType: 'base64',allowEdit: true,preview: true,quality: 100,targetWidth: 100,targetHeight: 100,saveToPhotoAlbum: true,}, (ret, err)=>{if(ret){this.upload_avatar(ret));}else{this.game.print(err));}});alert( JSON.stringify( ret ) );}else{this.game.print( err );}});},upload_avatar(ret){// 头像上传处理var token = this.game.get('access_token') || this.game.fget('access_token');if(!token){this.game.goFrame('login', 'login.html', this.current);return;}this.axios.post('', {'jsonrpc': '2.0','id': this.uuid(),'method': 'User.avatar.update','params': {'avatar': ret.base64Data,}},{headers:{Authorization: 'jwt ' + token,}}).then(response=>{if (parseInt(response.data.result.errno) === 1000) {this.game.print(response);}else {this.game.print(response.data.result);}}).catch(error=>{// 网络异常this.game.print(error);})}}});}</script>
</body>
</html>

三、本地更新头像信息

关闭avatar.html页面,展示底下的setting.html,并更新头像.
客户端的avatar.html代码:

<!DOCTYPE html>
<html>
<head><title>用户中心</title><meta name="viewport" content="width=device-width,minimum-scale=1.0,maximum-scale=1.0,user-scalable=no"><meta charset="utf-8"><link rel="stylesheet" href="../static/css/main.css"><script src="../static/js/vue.js"></script><script src="../static/js/axios.js"></script><script src="../static/js/main.js"></script><script src="../static/js/uuid.js"></script><script src="../static/js/settings.js"></script>
</head>
<body><div class="app frame avatar" id="app"><div class="box"><p class="title">上传头像</p><img class="close" @click="close_frame" src="../static/images/close_btn1.png" alt=""><div class="content"><p class="header">!注意事项</p><p class="text">禁止使用有诱导性的内容,二维码,联系方式等违规违法违约的图片,一经发现,永久封号,并保留追究法律责任的权利。</p></div><img @click="update_avatar_confirm" class="btn" src="../static/images/yes.png" alt=""></div></div><script>apiready = function(){init();new Vue({el:"#app",data(){return {prev:{name:"",url:"",params:{}},current:{name:"avatar",url:"avatar.html",params:{}},}},methods:{close_frame(){this.game.outFrame("avatar");},update_avatar_confirm(){// 确认上传头像的方式api.actionSheet({title: '请选择上传头像的来源',cancelTitle: '取消',buttons: ['相册','相机'],}, (ret, err)=>{if( ret ){// 本地获取相册/相机的图片var sourceType = ['album', 'camera'];if (ret.buttonIndex > sourceType.length) {// 如果用户选择了取消,则关闭当前修改头像的页面this.game.outFrame('avatar');return;}api.getPicture({sourceType: sourceType[ret.buttonIndex - 1],mediaValue: 'pic',destinationType: 'base64',allowEdit: true,preview: true,quality: 100,targetWidth: 100,targetHeight: 100,saveToPhotoAlbum: true,}, (ret, err)=>{if(ret){this.upload_avatar(ret);}else{this.game.print(err);}});}else{this.game.print( err );}});},upload_avatar(ret){// 头像上传处理var token = this.game.get('access_token') || this.game.fget('access_token');if(!token){this.game.goFrame('login', 'login.html', this.current);return;}this.axios.post('', {'jsonrpc': '2.0','id': this.uuid(),'method': 'User.avatar.update','params': {'avatar': ret.base64Data,}},{headers:{Authorization: 'jwt ' + token,}}).then(response=>{if (parseInt(response.data.result.errno) === 1000) {this.game.fsave({'avatar': response.data.result.avatar});// this.game.outFrame('avatar');this.game.goFrame('setting', 'setting.html', this.current);}else {// this.game.print('>>> fail');this.game.print(response.data);}}).catch(error=>{// 网络等异常this.game.print(error);})}}});}</script>
</body>
</html>

setting.html,代码:

<!DOCTYPE html>
<html>
<head><title>用户中心</title><meta name="viewport" content="width=device-width,minimum-scale=1.0,maximum-scale=1.0,user-scalable=no"><meta charset="utf-8"><link rel="stylesheet" href="../static/css/main.css"><script src="../static/js/vue.js"></script><script src="../static/js/axios.js"></script><script src="../static/js/main.js"></script><script src="../static/js/uuid.js"></script><script src="../static/js/settings.js"></script>
</head>
<body><div class="app user setting" id="app"><div class="bg"><img src="../static/images/form_bg.png"></div><img class="back" @click="goto_home" src="../static/images/user_back.png" alt=""><div class="form"><div class="item avatar"><span class="title">头像</span><span class="goto">&gt;</span><span class="value"><img @click="update_avatar_frame" :src="avatar" alt=""></span></div><div class="item"><span class="title">昵称</span><span class="goto">&gt;</span><span class="value">清蒸小帅锅</span></div><div class="item"><span class="title">手机号</span><span class="goto">&gt;</span><span class="value">139****5901</span></div><div class="item"><span class="title">登陆密码</span><span class="value"></span><span class="goto">&gt;</span></div><div class="item"><span class="title">交易密码</span><span class="value"></span><span class="goto">&gt;</span></div><div class="item"><span class="title">地址管理</span><span class="value"></span><span class="goto">&gt;</span></div><div class="item"><span class="title">设备管理</span><span class="value"></span><span class="goto">&gt;</span></div><div class="item logout"><img @click="change_account" src="../static/images/change_account.png" alt=""><p @click="logout">退出账号</p></div></div></div><script>apiready = function(){init();new Vue({el:"#app",data(){return {avatar:"",prev:{name:"",url:"",params:{}},current:{name:"setting",url:"setting.html",params:{}},}},created(){var token = this.game.get('access_token') || this.game.fget('access_token');this.avatar = `{this.settings.avatar_url}?sign=${this.game.fget('avatar')}&token{token}`},methods:{goto_home(){// this.game.outFrame("setting");this.game.goFrame("user","user.html",this.current);},change_account(){// 切换账号this.game.goFrame("login","login.html", this.current);},logout(){// 退出账号api.actionSheet({title: '您确认要退出当前登录吗?',cancelTitle: '取消',destructiveTitle: '退出登录'}, (ret, err)=>{if( ret ){this.game.print(ret);  // 取消为2if(ret.buttonIndex==1){this.game.save({"access_token":"","refresh_token":""});this.game.fremove(["access_token","refresh_token"]);this.game.outWin("user");}}});},update_avatar_frame(){// 显示修改头像的页面framethis.game.goFrame('avatar','avatar.html', this.current, null,{type:'push',  // 动画类型(详见动画类型常量)subType:'from_top',  // 动画子类型(详见动画子类型常量)duration:300  // 动画过渡时间,默认300毫秒})}}});}</script>
</body>
</html>

客户端的配置文件static/js/settings.js新增配置项avatar_url提供头像访问地址,代码:

function init(){if (Game) {var game = new Game("../mp3/bg1.mp3");Vue.prototype.game = game;}if(axios){// 初始化axiosaxios.defaults.baseURL = "http://192.168.20.158:5000/api" // 服务端api接口网关地址axios.defaults.timeout = 2500; // 请求超时时间axios.defaults.withCredentials = false; // 跨域请求资源的情况下,忽略cookie的发送Vue.prototype.axios = axios;Vue.prototype.uuid  = UUID.generate;}// 接口相关的配置项Vue.prototype.settings = {captcha_app_id: "2041284967",  // 腾讯防水墙验证码应用IDavatar_url: "http://192.168.20.158:5000/users/avatar",}
}

服务端提供展示图片的视图方法地址
application/apps/users/urls.py,代码:

from . import views
from application.utils import path
urlpatterns = [path('/avatar', views.avatar),
]

确认总路由设置了访问蓝图的url前缀,application/urls.py,代码:

from application.utils import include
urlpatterns = [include("", "home.urls"),include("/users", "users.urls"),include("/marsh", "marsh.urls"),
]

视图中, 显示图片时验证用户身份,application/apps/users/views.py代码:

import base64, uuid, os
@jsonrpc.method('User.avatar.update')
@jwt_required  # 验证jwt
def update_avatar(avatar):"""更新用户头像"""# 1.接收客户端上传的头像信息ext = avatar[avatar.find('/') + 1: avatar.find(';')]  # 资源格式b64_avatar = avatar[avatar.find(',') + 1:]b64_image = base64.b64decode(b64_avatar)filename = uuid.uuid4()static_path = os.path.join(current_app.BASE_DIR, current_app.config['STATIC_DIR'])with open('%s/%s.%s' % (static_path, filename, ext), 'wb') as f:f.write(b64_image)current_user_ip = get_jwt_identity()user = User.query.get(current_user_ip)if user is None:return {'errno': status.CODE_NO_USER,'errmsg': message.user_not_exists,}user.avatar = '%s.%s' % (filename, ext)db.session.commit()return {'errno': status.CODE_OK,'errmsg': message.avatar_save_success,'avatar': '%s.%s' % (filename, ext)}from flask import make_response, request@jwt_required  # 验证jwt
def avatar():"""获取头像信息"""avatar = request.args.get('sign')ext = avatar[avatar.find('.') + 1:]filename = avatar[:avatar.find('.')]static_path = os.path.join(current_app.BASE_DIR, current_app.config['STATIC_DIR'])with open('%s/%s.%s' % (static_path, filename, ext), 'rb') as f:content = f.read()response = make_response(content)response.headers['Content-Type'] = 'image/%s' % extreturn response

application/utils/language/message.py,代码:

    avatar_save_success = "用户头像保存成功!"

完成了上面步骤代码以后, 我们现在能在客户端修改用户头像了,但是在关闭avatar.html页面以后, setting.html页面中的头像并没有及时发生变化,.所以我们在avatar.html页面更新了头像以后, 我们借住apicloud提供的自定义事件和事件监听接口,来实现,通知setting.html页面,用户头衔已经发生改变了.
文档: https://docs.apicloud.com/Client-API/api#72

当前功能,我们需要根据文档了解关于sendEventaddEventListener的使用.

# a页面可以通过sendEvent发起一个自定义事件
api.sendEvent({name: 'myEvent', # 自定义事件名称extra: {key1: 'value1',  # 事件传参key2: 'value2'}
});# b页面可以通过addEventListener进行监听是否有对应名称的自定义事件进行发送了,一旦监听到,则自动执行回调函数:
api.addEventListener({name: 'myEvent' # 监听指定名称的事件
}, function(ret, err) {   # ret接收指定名称的事件参数alert(JSON.stringify(ret.value));
});# c页面也可以通过addEventListener进行监听是否有对应名称的自定义事件进行发送了,一旦监听到,则自动执行回调函数:
api.addEventListener({name: 'myEvent'
}, function(ret, err) {alert(JSON.stringify(ret.value));
});# b.c 页面都将收到 myEvent 事件

接下来,我们就可以在avatar.html页面中,发送一个自定义事件,告知其他页面,当前永固的头像发生了改变.

<!DOCTYPE html>
<html>
<head><title>用户中心</title><meta name="viewport" content="width=device-width,minimum-scale=1.0,maximum-scale=1.0,user-scalable=no"><meta charset="utf-8"><link rel="stylesheet" href="../static/css/main.css"><script src="../static/js/vue.js"></script><script src="../static/js/axios.js"></script><script src="../static/js/main.js"></script><script src="../static/js/uuid.js"></script><script src="../static/js/settings.js"></script>
</head>
<body><div class="app frame avatar" id="app"><div class="box"><p class="title">上传头像</p><img class="close" @click="close_frame" src="../static/images/close_btn1.png" alt=""><div class="content"><p class="header">!注意事项</p><p class="text">禁止使用有诱导性的内容,二维码,联系方式等违规违法违约的图片,一经发现,永久封号,并保留追究法律责任的权利。</p></div><img @click="update_avatar_confirm" class="btn" src="../static/images/yes.png" alt=""></div></div><script>apiready = function(){init();new Vue({el:"#app",data(){return {prev:{name:"",url:"",params:{}},current:{name:"avatar",url:"avatar.html",params:{}},}},methods:{close_frame(){this.game.outFrame("avatar");},update_avatar_confirm(){// 确认上传头像的方式api.actionSheet({title: '请选择上传头像的来源',cancelTitle: '取消',buttons: ['相册','相机'],}, (ret, err)=>{if( ret ){// 本地获取相册/相机的图片var sourceType = ['album', 'camera'];if (ret.buttonIndex > sourceType.length) {// 如果用户选择了取消,则关闭当前修改头像的页面this.game.outFrame('avatar');return;}api.getPicture({sourceType: sourceType[ret.buttonIndex - 1],mediaValue: 'pic',destinationType: 'base64',allowEdit: true,preview: true,quality: 100,targetWidth: 100,targetHeight: 100,saveToPhotoAlbum: true,}, (ret, err)=>{if(ret){this.upload_avatar(ret);}else{this.game.print(err);}});}else{this.game.print( err );}});},upload_avatar(ret){// 头像上传处理var token = this.game.get('access_token') || this.game.fget('access_token');if(!token){this.game.goFrame('login', 'login.html', this.current);return;}this.axios.post('', {'jsonrpc': '2.0','id': this.uuid(),'method': 'User.avatar.update','params': {'avatar': ret.base64Data,}},{headers:{Authorization: 'jwt ' + token,}}).then(response=>{if (parseInt(response.data.result.errno) == 1000) {this.game.fsave({'avatar': response.data.result.avatar});// 发送自定义时间api.sendEvent({name: 'change_avatar',extra: {'avatar': response.data.result.avatar}});this.game.outFrame('avatar');}else {// this.game.print('>>> fail');this.game.print(response.data);}}).catch(error=>{// 网络等异常this.game.print(error);})}}});}</script>
</body>
</html>

setting.html页面中,监听自定义事件,这个监听过程是不需要刷新页面的.

<!DOCTYPE html>
<html>
<head><title>用户中心</title><meta name="viewport" content="width=device-width,minimum-scale=1.0,maximum-scale=1.0,user-scalable=no"><meta charset="utf-8"><link rel="stylesheet" href="../static/css/main.css"><script src="../static/js/vue.js"></script><script src="../static/js/axios.js"></script><script src="../static/js/main.js"></script><script src="../static/js/uuid.js"></script><script src="../static/js/settings.js"></script>
</head>
<body><div class="app user setting" id="app"><div class="bg"><img src="../static/images/form_bg.png"></div><img class="back" @click="goto_home" src="../static/images/user_back.png" alt=""><div class="form"><div class="item avatar"><span class="title">头像</span><span class="goto">&gt;</span><span class="value"><img @click="update_avatar_frame" :src="avatar" alt=""></span></div><div class="item"><span class="title">昵称</span><span class="goto">&gt;</span><span class="value">清蒸小帅锅</span></div><div class="item"><span class="title">手机号</span><span class="goto">&gt;</span><span class="value">139****5901</span></div><div class="item"><span class="title">登陆密码</span><span class="value"></span><span class="goto">&gt;</span></div><div class="item"><span class="title">交易密码</span><span class="value"></span><span class="goto">&gt;</span></div><div class="item"><span class="title">地址管理</span><span class="value"></span><span class="goto">&gt;</span></div><div class="item"><span class="title">设备管理</span><span class="value"></span><span class="goto">&gt;</span></div><div class="item logout"><img @click="change_account" src="../static/images/change_account.png" alt=""><p @click="logout">退出账号</p></div></div></div><script>apiready = function(){init();new Vue({el:"#app",data(){return {avatar:"../static/images/avatar.png",prev:{name:"",url:"",params:{}},current:{name:"setting",url:"setting.html",params:{}},}},created(){this.change_avatar();},methods:{change_avatar(){api.addEventListener({name: 'change_avatar'}, (ret, err)=>{if( ret ){var token = this.game.get('access_token') || this.game.fget('access_token');this.avatar = `{this.settings.avatar_url}?sign=${ret.value.avatar}&token${token}`;}});},goto_home(){// this.game.outFrame("setting");this.game.goFrame("user","user.html",this.current);},change_account(){// 切换账号this.game.goFrame("login","login.html", this.current);},logout(){// 退出账号api.actionSheet({title: '您确认要退出当前登录吗?',cancelTitle: '取消',destructiveTitle: '退出登录'}, (ret, err)=>{if( ret ){this.game.print(ret);  // 取消为2if(ret.buttonIndex==1){this.game.save({"access_token":"","refresh_token":""});this.game.fremove(["access_token","refresh_token"]);this.game.outWin("user");}}});},update_avatar_frame(){// 显示修改头像的页面framethis.game.goFrame('avatar','avatar.html', this.current, null,{type:'push',  // 动画类型(详见动画类型常量)subType:'from_top',  // 动画子类型(详见动画子类型常量)duration:300  // 动画过渡时间,默认300毫秒})}}});}</script>
</body>
</html>

setting.html页面提供当前登陆用户的基本信息,代码:

<!DOCTYPE html>
<html>
<head><title>用户中心</title><meta name="viewport" content="width=device-width,minimum-scale=1.0,maximum-scale=1.0,user-scalable=no"><meta charset="utf-8"><link rel="stylesheet" href="../static/css/main.css"><script src="../static/js/vue.js"></script><script src="../static/js/axios.js"></script><script src="../static/js/main.js"></script><script src="../static/js/uuid.js"></script><script src="../static/js/settings.js"></script>
</head>
<body><div class="app user setting" id="app"><div class="bg"><img src="../static/images/form_bg.png"></div><img class="back" @click="goto_home" src="../static/images/user_back.png" alt=""><div class="form"><div class="item avatar"><span class="title">头像</span><span class="goto">&gt;</span><span class="value"><img @click="update_avatar_frame" :src="avatar" alt=""></span></div><div class="item"><span class="title">昵称</span><span class="goto">&gt;</span><span class="value">{{nickname}}</span></div><div class="item"><span class="title">手机号</span><span class="goto">&gt;</span><span class="value">{{mobile}}</span></div><div class="item"><span class="title">登陆密码</span><span class="value"></span><span class="goto">&gt;</span></div><div class="item"><span class="title">交易密码</span><span class="value"></span><span class="goto">&gt;</span></div><div class="item"><span class="title">地址管理</span><span class="value"></span><span class="goto">&gt;</span></div><div class="item"><span class="title">设备管理</span><span class="value"></span><span class="goto">&gt;</span></div><div class="item logout"><img @click="change_account" src="../static/images/change_account.png" alt=""><p @click="logout">退出账号</p></div></div></div><script>apiready = function(){init();new Vue({el:"#app",data(){return {nickname: '',mobile: '',avatar:"../static/images/avatar.png",prev:{name:"",url:"",params:{}},current:{name:"setting",url:"setting.html",params:{}},}},created(){this.get_user_info();this.change_avatar();},methods:{get_user_info(){var token = this.game.get('access_token') || this.game.fget('access_token');// 获取当前登录用户基本信息this.axios.post('',{'jsonrpc': '2.0','id': this.uuid(),'method': 'User.info','params': {}},{headers:{Authorization: 'jwt' + token,}}).then(response=>{var res = response.data.result;if (parseInt(res.errno) === 1000) {this.nickname = res.nickname;this.avatar = `${this.settings.avatar_url}?sign=${res.avatar}&token=${token}`;this.mobile = res.mobile;}})},change_avatar(){api.addEventListener({name: 'change_avatar'}, (ret, err)=>{if( ret ){var token = this.game.get('access_token') || this.game.fget('access_token');this.avatar = `${this.settings.avatar_url}?sign=${ret.value.avatar}&token=${token}`;}});},goto_home(){// this.game.outFrame("setting");this.game.goFrame("user","user.html",this.current);},change_account(){// 切换账号this.game.goFrame("login","login.html", this.current);},logout(){// 退出账号api.actionSheet({title: '您确认要退出当前登录吗?',cancelTitle: '取消',destructiveTitle: '退出登录'}, (ret, err)=>{if( ret ){this.game.print(ret);  // 取消为2if(ret.buttonIndex==1){this.game.save({"access_token":"","refresh_token":""});this.game.fremove(["access_token","refresh_token"]);this.game.outWin("user");}}});},update_avatar_frame(){// 显示修改头像的页面framethis.game.goFrame('avatar','avatar.html', this.current, null,{type:'push',  // 动画类型(详见动画类型常量)subType:'from_top',  // 动画子类型(详见动画子类型常量)duration:300  // 动画过渡时间,默认300毫秒})}}});}</script>
</body>
</html>

服务端提供用户基本信息APi接口,application/apps/users/views.py,代码:


from application import jsonrpc,db
from .marshmallow import MobileSchema,UserSchema
from marshmallow import ValidationError
from message import ErrorMessage as Message
from status import APIStatus as status@jsonrpc.method("User.mobile")
def mobile(mobile):"""验证手机号码是否已经注册"""ms = MobileSchema()try:ms.load({"mobile":mobile})ret = {"errno":status.CODE_OK, "errmsg":Message.ok}except ValidationError as e:ret = {"errno":status.CODE_VALIDATE_ERROR, "errmsg": e.messages["mobile"][0]}return ret@jsonrpc.method("User.register")
def register(mobile,password,password2, sms_code):"""用户信息注册"""try:ms = MobileSchema()ms.load({"mobile": mobile})us = UserSchema()user = us.load({"mobile":mobile,"password":password,"password2":password2,"sms_code": sms_code})data = {"errno": status.CODE_OK,"errmsg":us.dump(user)}except ValidationError as e:data = {"errno": status.CODE_VALIDATE_ERROR,"errmsg":e.messages}return datafrom flask_jwt_extended import create_access_token,create_refresh_token,jwt_required,get_jwt_identity,jwt_refresh_token_required
from flask import jsonify,json
from sqlalchemy import or_
from .models import User
from message import ErrorMessage as message
from status import APIStatus as status
from flask import current_app, request
from urllib.parse import urlencode
from urllib.request import urlopen@jsonrpc.method("User.login")
def login(ticket, randstr, account, password):"""根据用户登录信息生成token"""# 校验防水墙验证码params = {'aid': current_app.config.get('CAPTCHA_APP_ID'),'AppSecretKey': current_app.config.get('CAPTCHA_APP_SECRET_KEY'),'Ticket': ticket,'Randstr': randstr,'UserIP': request.remote_addr}# 把字典数据转换成地址栏的查询字符串格式# aid=xxx&AppSecretKey=xxx&xxxxxparams = urlencode(params)url = current_app.config.get('CAPTCHA_GATEWAY')# 发送http的get请求f = urlopen('%s?%s' % (url, params))# https://ssl.captcha.qq.com/ticket/verify?aid=xxx&AppSecretKey=xxx&xxxxxcontent = f.read()res = json.loads(content)print(res)if int(res.get('response')) != 1:# 验证失败return {'errno': status.CODE_CAPTCHA_ERROR, 'errmsg': message.captcaht_no_match}# 1. 根据账户信息和密码获取用户if len(account) < 1:return {"errno":status.CODE_NO_ACCOUNT,"errmsg":message.account_no_data}user = User.query.filter(or_(User.mobile == account,User.email == account,User.name == account)).first()if user is None:return {"errno": status.CODE_NO_USER,"errmsg":message.user_not_exists}# 验证密码if not user.check_password(password):return {"errno": status.CODE_PASSWORD_ERROR, "errmsg":message.password_error}# 2. 生成jwt tokenaccess_token = create_access_token(identity=user.id)refresh_token = create_refresh_token(identity=user.id)return {'errno': status.CODE_OK,'errmsg': message.ok,'id': user.id,'nickname': user.nickname if user.nickname else account,"access_token": access_token,"refresh_token": refresh_token}from .marshmallow import UserInfoSchema
@jsonrpc.method("User.info")
@jwt_required # 验证jwt
def info():"""获取用户信息"""current_user_id = get_jwt_identity()  # get_jwt_identity 用于获取载荷中的数据user = User.query.get(current_user_id)if user is None:return {'errno': status.CODE_NO_USER,'errmsg': message.user_not_exists,}uis = UserInfoSchema()data = uis.dump(user)return {'errno': status.CODE_OK,'errmsg': message.ok,**data}@jsonrpc.method("User.refresh")
@jwt_refresh_token_required
def refresh():"""重新获取新的认证令牌token"""current_user = get_jwt_identity()# 重新生成tokenaccess_token = create_access_token(identity=current_user)return access_tokenimport base64, uuid, os
@jsonrpc.method('User.avatar.update')
@jwt_required  # 验证jwt
def update_avatar(avatar):"""更新用户头像"""# 1.接收客户端上传的头像信息ext = avatar[avatar.find('/') + 1: avatar.find(';')]  # 资源格式b64_avatar = avatar[avatar.find(',') + 1:]b64_image = base64.b64decode(b64_avatar)filename = uuid.uuid4()static_path = os.path.join(current_app.BASE_DIR, current_app.config['STATIC_DIR'])with open('%s/%s.%s' % (static_path, filename, ext), 'wb') as f:f.write(b64_image)current_user_id = get_jwt_identity()user = User.query.get(current_user_id)if user is None:return {'errno': status.CODE_NO_USER,'errmsg': message.user_not_exists,}user.avatar = '%s.%s' % (filename, ext)db.session.commit()return {'errno': status.CODE_OK,'errmsg': message.avatar_save_success,'avatar': '%s.%s' % (filename, ext)}from flask import make_response, request@jwt_required  # 验证jwt
def avatar():"""获取头像信息"""avatar = request.args.get('sign')ext = avatar[avatar.find('.') + 1:]filename = avatar[:avatar.find('.')]static_path = os.path.join(current_app.BASE_DIR, current_app.config['STATIC_DIR'])with open('%s/%s.%s' % (static_path, filename, ext), 'rb') as f:content = f.read()response = make_response(content)response.headers['Content-Type'] = 'image/%s' % extreturn response

marshmallow.py,代码:

...
from marshmallow import post_dumpclass UserInfoSchema(SQLAlchemyAutoSchema):id = auto_field()mobile = auto_field()nickname = auto_field()avatar = auto_field()class Meta:model = Userinclude_fk = Trueinclude_relationships = Truefields = ['id', 'mobile', 'nickname', 'avatar']sql_session = db.session@post_dump()def mobile_format(self, data, **kwargs):data['mobile'] = data['mobile'][:3] + '****' + data['mobile'][-4:]return data

执行数据库迁移

魔方APP项目-09-用户中心,退出登录、更新头像、本地更新头像信息相关推荐

  1. 魔方APP项目-07-客户端提交登录信息、在APICloud中集成防水墙验证码,前端获取显示并校验验证码、服务端校验验证码、保存用户登录状态,APICloud提供的数据存储、客户端保存用户登陆数据

    用户登录 一.客户端提交登录信息 html/login.html,代码: <!DOCTYPE html> <html> <head><title>登录& ...

  2. 【Axure电商原型】电商app高保真原型、移动端通用版电商app模板、用户中心、会员体系、签到、高保真商城app、rp原型、直播、运营活动、订单管理、售后退款、电商系统、购物车、高保真移动端电商

    [Axure电商原型]电商app高保真原型.移动端通用版电商app模板.用户中心.会员体系.高保真商城app.rp原型.订单流程.运营活动.订单管理.售后及服务.电商系统.购物车.高保真移动端电商.店 ...

  3. 魔方APP项目-06-用户注册,完成短信验证码的校验、基于Celery实现短信异步发送、用户登录,jwt登陆认证、服务端提供用户登录的API接口

    一.用户注册- 1.完成短信验证码的校验 application.utils.language.message,代码: class ErrorMessage():ok = "ok" ...

  4. 魔方APP项目-05-注册功能实现,手机号唯一验证接口、客户端进行手机号验证、保存用户注册信息接口、客户端用户进行注册、使用云通讯发送短信,服务端实现发送短信验证码的api接口、客户端实现点击发送短信

    用户模块 注册功能实现 1.手机号码唯一验证接口 在开发中,针对客户端提交的数据进行验证或提供模型数据转换格式成字典给客户端.可以使用Marshmallow模块来进行. 为了方便导包,所以我们设置当前 ...

  5. 魔方APP项目-01-移动端开发相关概念、移动端自适配、元信息(meta)、开发准备、移动端项目搭建(模拟器调试)、APICloud(APICloud 前端框架,获取服务端API接口)

    一.移动端开发相关概念 1.APP类型 ①.Native APP Native APP又称原生APP,就是我们平时说的手机应用软件. 原生APP 是针对IOS.Android.Windows等不同的手 ...

  6. 滴滴 App 强制调取用户通讯录;子弹短信仍能泄露信息;特朗普炮轰谷歌 | 极客头条...

    「CSDN 极客头条」,是从 CSDN 网站延伸至官方微信公众号的特别栏目,专注于一天业界事报道.风里雨里,我们将每天为朋友们,播报最新鲜有料的新闻资讯,让所有技术人,时刻紧跟业界潮流. 快讯速知 滴 ...

  7. WordPress foxpay 收费下载资源插件 vip会员功能/收费下载/收费查看/联盟推广+前端用户中心 支付宝/财付通/贝宝/网银[更新至v4.5]

    Foxpay是一款资源商城插件,  可以给你的用户分享一些收费资源,wordpress插件(Foxpay),经过完美测试运行于wordpress 3.0.1-4.1版本.本插件特点:高级VIP会员系统 ...

  8. 魔方APP项目-04-用户模块API接口、Marshmallow,基本构造器(Schema),Schema数据序列化、Schema数据反序列化、反序列化对数据验证、模型构造器(ModelSchema)

    用户模块 当前开发的项目属于社交类型项目,所以关于用户的信息和功能直接贯穿了整个项目.所以此处实现用户模块功能,我们先把用户基本信息构建起来,并通过基本信息实现用户注册登录相关功能,后面遇到业务再继续 ...

  9. 魔方APP项目-02-服务端项目搭建,创建manage.py文件、构建全局初始化函数创建app应用对象、通过终端脚本启动项目、项目加载配置、数据库初始化、日志初始化、蓝图初始化

    服务端项目搭建 新建项目目录mofangapi,并创建虚拟环境 mkvirtualenv mofang 安装开发中使用的依赖模块 pip install flask==0.12.4 pip insta ...

最新文章

  1. 对于oracle varchar 的种种
  2. PHP程序员上相亲节目,结果遭女嘉宾瞬间全灭灯
  3. wireshark从入门到精通(协议排错安全篇)4
  4. ASCII码表在线查询进制转换
  5. Codeforces Round #572 (Div. 2)(ABCD1D2E)
  6. 大数据数据量估算_如何估算数据科学项目的数据收集成本
  7. 打造自己博客(wordpress)的wap手机版本
  8. [10] ADB 修改设置
  9. Lc.exe已退出 代码为-1
  10. android dao设计模式,DAO设计模式
  11. orm框架设计、分析与开发
  12. python进度条代码怎么写_Python实现控制台中的进度条功能代码
  13. 使用Java调用默认浏览器打开指定网址
  14. 中比较两个时间的月份差值_测量血压时,一天中什么时间测比较准?控制血压又有哪些好方式?...
  15. 高中会考计算机网络技术试题,高中信息技术会考练习复习试题及答案
  16. linux火狐快捷键设置,使用火狐浏览器所有的快捷键大全
  17. 利用MATLAB模拟光纤通信,利用matlab模拟光纤传光
  18. 伸缩盒header固定content变更,footer固定
  19. 叉积 微分 恒等式_一个斜三角中的恒等式
  20. Mybatis多条件筛选

热门文章

  1. Intellij IEDA 常用快捷键
  2. Windows系统装linux双系统,无需U盘
  3. cad字体 草体_50种免费的草书和手写字体,丰富您的设计
  4. ABP vNext 对接 Ant Design Vue 实现分页查询
  5. 西电Linux用户名和密码,在西电使用校内Linux 开源软件镜像
  6. 如何做好企业流程管理,提高团队工作效率
  7. DIY强大的虚拟化环境-前言与目录
  8. Wireshark,Scapy等出现failed to set hardware filter to promiscuous mode解决办法
  9. java兔子繁殖总数_【Java基础编程练习】01:兔子繁殖问题(斐波那契数列)的分析及实现...
  10. Selenium获取虎牙直播英雄联盟分区相关信息