用户中心
一、退出登录
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">></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">清蒸小帅锅</span>
</div>
<div class="item">
<span class="title">手机号</span>
<span class="goto">></span>
<span class="value">139****5901</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">
<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">></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">></span>
<span class="value">清蒸小帅锅</span>
</div>
<div class="item">
<span class="title">手机号</span>
<span class="goto">></span>
<span class="value">139****5901</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">
<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
# redis
REDIS_URL = "redis://@127.0.0.1:6379/0"
# session存储配置
SESSION_REDIS_HOST = "127.0.0.1"
SESSION_REDIS_PORT = 6379
SESSION_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" # 应用ID
SMS_TEMPLATE_ID = 1 # 短信模板ID
SMS_EXPIRE_TIME = 60 * 5 # 短信有效时间,单位:秒/s
SMS_INTERVAL_TIME = 60 # 短信发送冷却时间,单位:秒/s
# jwt 相关配置
# 加密算法,默认: HS256
JWT_ALGORITHM = "HS256"
# 秘钥,默认是flask配置中的SECRET_KEY
JWT_SECRET_KEY = "y58Rsqzmts6VCBRHes1Sf2DHdGJaGqPMi6GYpBS4CKyCdi42KLSs9TQVTauZMLMw"
# token令牌有效期,单位: 秒/s,默认: datetime.timedelta(minutes=15) 或者 15 * 60
JWT_ACCESS_TOKEN_EXPIRES = 60 * 60
# refresh刷新令牌有效期,单位: 秒/s,默认:datetime.timedelta(days=30) 或者 30*24*60*60
JWT_REFRESH_TOKEN_EXPIRES = 30*24*60*60
# 设置通过哪种方式传递jwt,默认是http请求头,也可以是query_string,json,cookies
JWT_TOKEN_LOCATION = ["headers", "query_string"]
# 当通过http请求头传递jwt时,请求头参数名称设置,默认值: Authorization
JWT_HEADER_NAME = "Authorization"
# 当通过查询字符串传递jwt时,查询字符串的参数名称设置,默认:jwt
JWT_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">></span>
<span class="value">
<img @click="update_avatar_frame" :src="avatar" alt="">
</span>
</div>
<div class="item">
<span class="title">昵称</span>
<span class="goto">></span>
<span class="value">清蒸小帅锅</span>
</div>
<div class="item">
<span class="title">手机号</span>
<span class="goto">></span>
<span class="value">139****5901</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">
<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); // 取消为2
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(){
// 显示修改头像的页面frame
this.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){
// 初始化axios
axios.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", // 腾讯防水墙验证码应用ID
avatar_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' % ext
return 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
当前功能,我们需要根据文档了解关于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
页面中,发送一个自定义事件,告知其他页面,当前永固的头像发生了改变.
<!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">></span>
<span class="value">
<img @click="update_avatar_frame" :src="avatar" alt="">
</span>
</div>
<div class="item">
<span class="title">昵称</span>
<span class="goto">></span>
<span class="value">清蒸小帅锅</span>
</div>
<div class="item">
<span class="title">手机号</span>
<span class="goto">></span>
<span class="value">139****5901</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">
<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); // 取消为2
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(){
// 显示修改头像的页面frame
this.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">></span>
<span class="value">
<img @click="update_avatar_frame" :src="avatar" alt="">
</span>
</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>
<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">
<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); // 取消为2
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(){
// 显示修改头像的页面frame
this.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 data
from 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&xxxxx
params = 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&xxxxx
content = 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 token
access_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()
# 重新生成token
access_token = create_access_token(identity=current_user)
return access_token
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_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' % ext
return response
marshmallow.py
,代码:
...
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
执行数据库迁移