day113:MoFang:种植园商城页面&充值集成Alipay完成支付的准备工作
目录
1.种植园商城页面初始化
1.种植园点击商店会去到种植园商城页面
<!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> <script src="../static/js/socket.io.js"></script> </head> <body> <div class="app orchard" id="app"> <img class="music" :class="music_play?'music2':''" @click="music_play=!music_play" src="../static/images/player.png"> <div class="orchard-bg"> <img src="../static/images/bg2.png"> <img class="board_bg2" src="../static/images/board_bg2.png"> </div> <img class="back" @click="go_index" src="../static/images/user_back.png" alt=""> <div class="header"> <div class="info" @click="go_home"> <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="menu-list"> <div class="menu"> <img src="../static/images/menu1.png" alt=""> 排行榜 </div> <div class="menu"> <img src="../static/images/menu2.png" alt=""> 签到有礼 </div> <!-- ***种植园点击商城去到商城界面*** --> <div class="menu" @click="go_orchard_shop"> <img src="../static/images/menu3.png" alt=""> 道具商城 </div> <div class="menu"> <img src="../static/images/menu4.png" alt=""> 邮件中心 </div> </div> </div> <div class="footer" > <ul class="menu-list"> <li class="menu">新手</li> <li class="menu">背包</li> <li class="menu-center" @click="go_orchard_shop">商店</li> <li class="menu">消息</li> <li class="menu">好友</li> </ul> </div> </div> <script> apiready = function(){ init(); new Vue({ el:"#app", data(){ return { music_play:true, namespace: '/mofang_orchard', token:"", socket: null, timeout: 0, prev:{name:"",url:"",params:{}}, current:{name:"orchard",url:"orchard.html",params:{}}, } }, created(){ this.game.goFrame("orchard","my_orchard.html", this.current,{ x: 0, y: 180, w: 'auto', h: 'auto', },null); this.checkout(); }, methods:{ checkout(){ var token = this.game.get("access_token") || this.game.fget("access_token"); this.game.checkout(this,token,(new_access_token)=>{ this.connect(); }); }, connect(){ // socket连接 this.socket = io.connect(this.settings.socket_server + this.namespace, {transports: ['websocket']}); this.socket.on('connect', ()=>{ this.game.print("开始连接服务端"); }); }, go_index(){ this.game.outWin("orchard"); }, go_friends(){ this.game.goFrame("friends","friends.html",this.current); this.game.goFrame("friend_list","friend_list.html",this.current,{ x: 0, y: 190, w: 'auto', h: 'auto', },null,true); }, go_home(){ this.game.goWin("user","user.html", this.current); }, // ***种植园点击商城去到商城界面*** go_orchard_shop(){ // 种植园商店 this.game.goFrame("orchard_shop","shop.html", this.current,null,{ type:"push", subType:"from_top", duration:300 }); } } }); } </script> </body> </html>
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 update_nickname add_friend shop" id="app"> <div class="box"> <p class="title">商店</p> <img class="close" @click="close_frame" src="../static/images/close_btn1.png" alt=""> <div class="friends_list shop_list"> <div class="item"> <div class="avatar shop_item"> <img src="../static/images/fruit_tree.png" alt=""> </div> <div class="info"> <p class="username">果树</p> <p class="time">200</p> </div> <div class="status">200</div> </div> </div> </div> </div> <script> apiready = function(){ init(); new Vue({ el:"#app", data(){ return { user_id: "", // 当前登陆用户Id prev:{name:"",url:"",params:{}}, current:{name:"orchard",url:"shop.html",params:{}}, } }, created(){ this.user_id = this.game.get("id") || this.game.fget("id"); }, methods:{ close_frame(){ this.game.outFrame("orchard_shop"); }, } }); } </script> </body> </html>
.shop .shop_list{ margin-left: 1rem; margin-top: -5rem; }
2.规划商品种类并且构建关于商品的模型类
商店的商品:
1. 种子
果树
标题
价格
描述
图片
使用流程相关
状态: 种子期, 成长期, 成熟期, 树桩
种子期: 3 x 60 x 60
成长期: 6 x 60 x 60
成熟期: 12 x 60 x 60
树桩 : -1
2. 宠物
小狗1,小狗2,小狗3,小狗4
标题
价格
描述
图片
使用流程相关:
饱食度: <20%(饥饿) <50%(正常) <100%(饱腹)
生命期: -1(永久)
保护命中率 : 10% 0-1 <10%
3. 狗粮
狗粮1,狗粮2,狗粮3
标题
价格
描述
图片
使用流程相关:
饱食度: 20%
有效期:
4. 道具
化肥,....
标题
价格
描述
图片
使用流程相关:
缩短时间: 1小时
2.商品基本信息的模型类
from application.utils.models import BaseModel,db class Goods(BaseModel): """商品基本信息""" __tablename__ = "mf_goods" remark = db.Column(db.String(255), comment="商品描述") price = db.Column(db.Numeric(7,2), comment="商品价格") image = db.Column(db.String(255), comment="商品图片")
3.为用户添加果子积分字段
from werkzeug.security import generate_password_hash, check_password_hash from application.utils.models import BaseModel,db class User(BaseModel): """用户基本信息""" __tablename__ = "mf_user" # 为用户表添加果子积分字段 credit = db.Column(db.Numeric(7,2),default=0, comment="果子积分")
4.将商品基本信息模型注册到admin中
# 根据模型自动生成页面 from .models import Goods from flask_admin.contrib.sqla import ModelView from application import admin,db class GoodsAdminModel(ModelView): # 列表页显示字段列表 column_list = ["id","name","price"] # 列表页可以直接编辑的字段列表 column_editable_list = ["price"] # 是否允许查看详情 can_view_details = True # 列表页显示直接可以搜索数据的字典 column_searchable_list = ['name', 'price'] # 过滤器 column_filters = ['name'] # 单页显示数据量 page_size = 10 admin.add_view(GoodsAdminModel(Goods,db.session,name="商品", category="种植园"))
5.在商城里添加一些道具(植物 狗粮 化肥等)
INSERT INTO mofang.mf_goods (id, name, is_deleted, orders, status, created_time, updated_time, remark, price, image) VALUES (1, '果树', 0, 0, 1, '2020-12-28 16:09:39', '2020-12-28 16:09:39', '果树', 10.00, null), (2, '小狗1号', 0, 0, 1, '2020-12-28 16:09:39', '2020-12-28 16:09:39', '小狗1号', 100.00, null), (3, '小狗2号', 0, 0, 1, '2020-12-28 16:09:39', '2020-12-28 16:09:39', '小狗2号', 200.00, null), (4, '小狗3号', 0, 0, 1, '2020-12-28 16:09:39', '2020-12-28 16:09:39', '小狗3号', 300.00, null), (5, '小狗4号', 0, 0, 1, '2020-12-28 16:09:39', '2020-12-28 16:09:39', '小狗4号', 100.00, null), (6, '狗粮1号', 0, 0, 1, '2020-12-28 16:09:39', '2020-12-28 16:09:39', '狗粮1号', 10.00, null), (7, '狗粮2号', 0, 0, 1, '2020-12-28 16:09:39', '2020-12-28 16:09:39', '狗粮2号', 5.00, null), (8, '化肥1号', 0, 0, 1, '2020-12-28 16:09:39', '2020-12-28 16:09:39', '化肥1号', 3.00, null), (9, '化肥2号', 0, 0, 1, '2020-12-28 16:09:39', '2020-12-28 16:09:39', '化肥2号', 6.00, null), (10, '化肥3号', 0, 0, 1, '2020-12-28 16:09:39', '2020-12-28 16:09:39', '化肥3号', 9.00, null);
3.解决APP打包编译之后的跨域限制
解决app打包生成以后,页面无法请求服务端数据的跨域问题。
无法获取数据的原因是,当前APP中获取数据是通过ajax来发送请求的,因为我们当前的APP是混合APP,所以实际来说,这种混合APP就是一个浏览器内核构建的。因此也会存在同源策略的访问限制,因此我们需要在服务端实现跨域资源共享。
服务端终端运行:
pip install -U flask-cors
CORS初始化:
from flask_cors import CORS # flask_cors cors = CORS() def init_app(config_path): """全局初始化""" # cors cors.init_app(app,resources={r"/api/*": {"origins": "*"}})
4.商品列表后端接口实现
1.视图部分
from application import jsonrpc from status import APIStatus as status from message import ErrorMessage as message from application import redis from .models import Goods from application.apps.users.models import User from flask_jwt_extended import jwt_required,get_jwt_identity from .marshmallow import GoodsInfoSchema @jsonrpc.method(name="Orchard.goods.list") @jwt_required # 验证jwt def goods_list(page=1,limit=10): """商品列表""" 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, } pagination = Goods.query.filter( Goods.is_deleted==False, Goods.status==True ).paginate(page, per_page=limit) # 转换数据格式 gis = GoodsInfoSchema() goods_list = gis.dump(pagination.items,many=True) return { "errno": status.CODE_OK, "errmsg": message.ok, "goods_list": goods_list, "pages": pagination.pages }
2.序列化器部分
from message import ErrorMessage as Message from .models import Goods,db from marshmallow_sqlalchemy import SQLAlchemyAutoSchema,auto_field from marshmallow import post_dump class GoodsInfoSchema(SQLAlchemyAutoSchema): id = auto_field() name = auto_field() price = auto_field() image = auto_field() remark = auto_field() class Meta: model = Goods fields = ["id","name","price","image","remark"] sql_session = db.session @post_dump() def mobile_format(self, data, **kwargs): data["price"] = "%.2f" % data["price"] if data["image"] == None: data["image"] = "" return data
5.前端获取商品列表并显示
<!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 update_nickname add_friend shop" id="app"> <div class="box"> <p class="title">商店</p> <img class="close" @click="close_frame" src="../static/images/close_btn1.png" alt=""> <div class="friends_list shop_list"> <div class="item" v-for="goods in goods_list"> <div class="avatar shop_item"> <img :src="settings.static_url+goods.image" alt=""> </div> <div class="info"> <p class="username">{{goods.name}}</p> <p class="time">{{goods.remark}}</p> </div> <div class="status">{{goods.price}}</div> </div> </div> </div> </div> <script> apiready = function(){ init(); new Vue({ el:"#app", data(){ return { user_id: "", // 当前登陆用户Id goods_list:[], // 商品列表 page: 1, limit: 10, is_send_ajax:false, prev:{name:"",url:"",params:{}}, current:{name:"orchard",url:"shop.html",params:{}}, } }, created(){ this.user_id = this.game.get("id") || this.game.fget("id"); this.get_goods_list(); }, methods:{ close_frame(){ this.game.outFrame("orchard_shop"); }, // ***获取商品列表*** get_goods_list(){ if(this.is_send_ajax){ return ; } // 通过请求获取当前用户的好友列表 var token = this.game.get("access_token") || this.game.fget("access_token"); this.game.checkout(this, token, (new_access_token)=>{ this.is_send_ajax = true; this.axios.post("",{ "jsonrpc": "2.0", "id": this.uuid(), "method": "Orchard.goods.list", "params": { "page": this.page, "limit": this.limit, } },{ headers:{ Authorization: "jwt " + token, } }).then(response=>{ if(parseInt(response.data.result.errno)==1000){ if(this.page+1 == response.data.result.pages){ this.is_send_ajax = true; }else{ this.is_send_ajax = false; this.page+=1; } if(this.page>1){ api.refreshHeaderLoadDone(); } this.goods_list = response.data.result.goods_list.concat(this.goods_list); }else if(parseInt(response.data.result.errno) == 1008){ this.friends = []; }else{ this.game.print(response.data); } }).catch(error=>{ // 网络等异常 this.game.print(error); }); }) }, } }); } </script> </body> </html>
6.种植园点击充值允许用户选择充值金额
<!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> <script src="../static/js/socket.io.js"></script> </head> <body> <div class="app orchard" id="app"> <img class="music" :class="music_play?'music2':''" @click="music_play=!music_play" src="../static/images/player.png"> <div class="orchard-bg"> <img src="../static/images/bg2.png"> <img class="board_bg2" src="../static/images/board_bg2.png"> </div> <img class="back" @click="go_index" src="../static/images/user_back.png" alt=""> <div class="header"> <div class="info" @click="go_home"> <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"> <!-- ***为充值绑定一个user_recharge事件*** --> <div class="balance" @click="user_recharge"> <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="menu-list"> <div class="menu"> <img src="../static/images/menu1.png" alt=""> 排行榜 </div> <div class="menu"> <img src="../static/images/menu2.png" alt=""> 签到有礼 </div> <div class="menu" @click="go_orchard_shop"> <img src="../static/images/menu3.png" alt=""> 道具商城 </div> <div class="menu"> <img src="../static/images/menu4.png" alt=""> 邮件中心 </div> </div> </div> <div class="footer" > <ul class="menu-list"> <li class="menu">新手</li> <li class="menu">背包</li> <li class="menu-center" @click="go_orchard_shop">商店</li> <li class="menu">消息</li> <li class="menu">好友</li> </ul> </div> </div> <script> apiready = function(){ init(); new Vue({ el:"#app", data(){ return { music_play:true, namespace: '/mofang_orchard', token:"", socket: null, recharge_list: ['10','20','50','100','200','500','1000'] timeout: 0, prev:{name:"",url:"",params:{}}, current:{name:"orchard",url:"orchard.html",params:{}}, } }, created(){ this.game.goFrame("orchard","my_orchard.html", this.current,{ x: 0, y: 180, w: 'auto', h: 'auto', },null); this.checkout(); }, methods:{ // ***充值*** user_recharge(){ // 充值 api.actionSheet({ title: '余额充值', cancelTitle: '取消', buttons: this.recharge_list }, function(ret, err){ if( ret ){ // 充值金额 money = this.recharge_list[ret.buttonIndex-1]; // 调用支付宝重置 }else{ } }); }, checkout(){ var token = this.game.get("access_token") || this.game.fget("access_token"); this.game.checkout(this,token,(new_access_token)=>{ this.connect(); }); }, connect(){ // socket连接 this.socket = io.connect(this.settings.socket_server + this.namespace, {transports: ['websocket']}); this.socket.on('connect', ()=>{ this.game.print("开始连接服务端"); }); }, go_index(){ this.game.outWin("orchard"); }, go_friends(){ this.game.goFrame("friends","friends.html",this.current); this.game.goFrame("friend_list","friend_list.html",this.current,{ x: 0, y: 190, w: 'auto', h: 'auto', },null,true); }, go_home(){ this.game.goWin("user","user.html", this.current); }, go_orchard_shop(){ // 种植园商店 this.game.goFrame("orchard_shop","shop.html", this.current,null,{ type:"push", subType:"from_top", duration:300 }); } } }); } </script> </body> </html>
7.将AlipayPlus模块加载到APP上
1.首先来到APIcloud开发者web后台,把Alipayplus模块加载到APP。
2.点击Alipayplus进入模块详情。
3.
1.安装alipay的sdk
终端运行:
pip install python-alipay-sdk --upgrade
2.配置支付宝的公钥和私钥
cd application/apps/users/ mkdir keys cd keys openssl OpenSSL> genrsa -out app_private_key.pem 2048 # 私钥 OpenSSL> rsa -in app_private_key.pem -pubout -out app_public_key.pem # 导出公钥 OpenSSL> exit
保存商户公钥到支付宝开放平台, 并从开放平台中把支付宝公钥保存到项目中
class Recharge(BaseModel): __tablename__ = "mf_user_recharge" status = db.Column(db.Boolean, default=True, comment="状态(是否支付)") out_trade_number = db.Column(db.String(64), unique=True, comment="订单号") user_id = db.Column(db.Integer, comment="用户") money = db.Column(db.Numeric(7,2), comment="账户余额") trade_number = db.Column(db.String(64), unique=True, comment="流水号")