day105:MoFang:设置页面初始化&更新头像/上传头像&设置页面显示用户基本信息
目录
2.setting.js提供avatar_url头像访问地址
1.设置页面初始化
1.设置页面setting.html初始化
APP项目中对于用户的退出登录,一般都在设置中进行。
<!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> <!-- 1.设置页面的返回按钮 --> <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">></span> <span class="value"> <img src="../static/images/avatar.png" alt=""> </span> </div> <div class="item"> <span class="title">昵称</span> <span class="goto">></span> <span class="value">iR.Poke</span> </div> <div class="item"> <span class="title">手机号</span> <span class="goto">></span> <span class="value">134****9284</span> </div> <div class="item"> <span class="title">登陆密码</span> <span class="value"></span> <span class="goto">></span> </div> <div class="item"> <span class="title">交易密码</span> <span class="value"></span> <span class="goto">></span> </div> <div class="item"> <span class="title">地址管理</span> <span class="value"></span> <span class="goto">></span> </div> <div class="item"> <span class="title">设备管理</span> <span class="value"></span> <span class="goto">></span> </div> <div class="item logout"> <!-- 2.设置页面的切换账号 --> <img @click="change_account" src="../static/images/change_account.png" alt=""> <!--3.设置页面的退出账号 --> <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.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>
2.设置页面的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; }
3.用户界面点击设置按钮跳转到设置界面
<!-- 点击设置按钮去到设置界面 --> <img class="setting" @click="goto_setting" src="../static/images/setting.png" alt=""> <script> apiready = function(){ init(); new Vue({ el:"#app", data(){ return { prev:{name:"",url:"",params:{}}, current:{name:"user",url:"user.html",params:{}}, } }, methods:{ goto_setting(){ this.game.goFrame("setting","setting.html", this.current); } } }); } </script> </body> </html>
2.更新头像
1.点击头像进入更新头像界面
<span class="value"> <!-- 点击头像进入到更新头像界面 --> <img @click="update_avatar_frame" src="../static/images/avatar.png" alt=""> </span> <script> update_avatar_frame(){ // 点击头像,跳到更新头像的界面:avatar.html this.game.goFrame("avatar","avatar.html", this.current,null,{ type:"push", //动画类型(详见动画类型常量) subType:"from_top", //动画子类型(详见动画子类型常量) duration:300 //动画过渡时间,默认300毫秒 }) } </script>
2.更新头像页面初始化
<!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>
3.更新头像页面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; }
4.头像上传来源选择:相册/相机
<!-- 1.关闭更新头像界面: close_frame --> <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> <!-- 2.头像上传来源选择 :相册/相机 --> <img @click="update_avatar_confirm" class="btn" src="../static/images/yes.png" alt=""> </div> <script> 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 ); } }); }, </script>
<!-- 1.关闭更新头像界面: close_frame --> <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> <!-- 2.头像上传来源选择 :相册/相机 --> <img @click="update_avatar_confirm" class="btn" src="../static/images/yes.png" alt=""> </div> <script> 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; } // ***使用APIcloud提供的api.getPicture方法从相机/相册获取图片*** 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>
import base64, uuid,os @jsonrpc.method("User.avatar.update") @jwt_required # 验证jwt def update_avatar(avatar): """获取用户信息""" # 1.接受客户端上传的头像信息 ext = avatar[avatar.find("/")+1:avatar.find(";")] # 资源格式:jpeg/jpg/png.... b64_avatar = avatar[avatar.find(",")+1:] b64_image = base64.b64decode(b64_avatar) # 2.使用uuid给头像文件生成随机的文件名 filename = uuid.uuid4() # 3.拼接头像文件在后端存储的路径 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) # 4.查询当前用户是否存在 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, } # 5.将用户的头像数据存储到mysql数据库中 user.avatar = "%s.%s" % (filename,ext) db.session.commit() return { "errno": status.CODE_OK, "errmsg": message.avatar_save_success, "avatar": "%s.%s" % (filename,ext) }
application/settings/__init__.py
,代码:
# 静态文件目录存储路径 STATIC_DIR = "application/static"
<!-- 1.关闭更新头像界面: close_frame --> <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> <!-- 2.头像上传来源选择 :相册/相机 --> <img @click="update_avatar_confirm" class="btn" src="../static/images/yes.png" alt=""> </div> <script> 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({ ... }, (ret, err)=>{ if(ret){ // 1.获取本地图片成功后,开始上传图片 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 ; } // 2.前端基于axios上传头像 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
代码:
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){ // 1.将头像存储在前端 this.game.fsave({"avatar": response.data.result.avatar}); // 2.跳转到设置页面 this.game.goFrame("setting","setting.html", this.current); }else{ this.game.print(">>>> fail"); this.game.print(response.data); } }).catch(error=>{ // 网络等异常 this.game.print(error); }) } } }); }
function init(){ if(Game){ var game = new Game("../mp3/bg1.mp3"); Vue.prototype.game = game; } if(axios){ // 初始化axios axios.defaults.baseURL = "http://192.168.20.251: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: "2071340228", // 腾讯防水墙验证码应用ID avatar_url: "http://192.168.20.251:5000/users/avatar", // ***头像前端访问地址*** } }
application/apps/users/urls.py
,代码:
from . import views from application.utils import path urlpatterns = [ path("/avatar", views.avatar), ]
from application.utils import include urlpatterns = [ include("","home.urls"), include("/users","users.urls"), include("/marsh","marsh.urls"), ]
from flask import make_response,request @jwt_required # 验证jwt def avatar(): """获取头像信息""" # 1.获取前端存储头像的sign参数 avatar = request.args.get("sign") # 2.获取头像文件格式:jpg/jpeg/png ext = avatar[avatar.find(".")+1:] # 3.获取头像文件的文件名 filename = avatar[:avatar.find(".")] # 4.后端文件存储的路径 static_path = os.path.join(current_app.BASE_DIR, current_app.config["STATIC_DIR"]) # 5.读取后端文件数据 with open("%s/%s.%s" % (static_path,filename,ext), "rb") as f: content = f.read() # 此时content存储的是二进制文件 response = make_response(content) # response.headers["Content-Type"] = "image/%s" % ext return response
文档: https://docs.apicloud.com/Client-API/api#72
当前功能,我们需要根据文档了解关于sendEvent
和addEventListener
的使用.
// 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
代码
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); }) } } }); }
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}`; } }); },
</div> <div class="item"> <span class="title">昵称</span> <span class="goto">></span> <span class="value">{{nickname}}</span> </div> <div class="item"> <span class="title">手机号</span> <span class="goto">></span> <span class="value">{{mobile}}</span> </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; // avatar从前端获取传到:src上 this.avatar = `${this.settings.avatar_url}?sign=${res.avatar}&token=${token}`; this.mobile = res.mobile; } }) }, </script> </body> </html>
from .marshmallow import UserInfoSchema @jsonrpc.method("User.info") @jwt_required # 验证jwt def info(): """获取用户信息""" current_user_id = get_jwt_identity() # 根据token反解出当前用户id # 判断当前用户是否存在 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 }
from marshmallow import post_dump class UserInfoSchema(SQLAlchemyAutoSchema): id = auto_field() mobile = auto_field() nickname = auto_field() avatar = auto_field() class Meta: model = User include_fk = True include_relationships = True fields = ["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