目录
1.种植园商城页面初始化
1.种植园点击商店会去到种植园商城页面
orchard.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> <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.种植园商城页面初始化
新建种植园商城页面shop.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 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.html
种植园商城页面CSS样式main.css
,代码:
.shop .shop_list{ margin-left: 1rem; margin-top: -5rem; }
2.规划商品种类并且构建关于商品的模型类
1.先规划好商品的种类以及相关参数
商店的商品: 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.商品基本信息的模型类
apps/orchard/models.py
,模型,代码:
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.为用户添加果子积分字段
apps/users/models.py
,模型代码:
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中
在admin站点中进行模型管理注册,apps/orchard/admin.py
,代码:
# 根据模型自动生成页面 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初始化:
application/__init__.py
,代码:
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.视图部分
提供商品列表给客户端,apps/orchard/views.py
, 代码:
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.序列化器部分
marshmallow.py
,代码:
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.前端获取商品列表并显示
客户端就可以在打开商品的时候, 获取商品列表,shop.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 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.种植园点击充值允许用户选择充值金额
在orchard.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> <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.把模块使用到指定APP中。[下图只做参看, 项目已定义叫Mofang
]
8.集成Alipayplus模块完成支付
1.安装alipay的sdk
接下来,服务端中需要完成的就是生成订单参数和接收支付结果,所以我们先 安装alipay的sdk,集成到flask项目中。
终端运行:
pip install python-alipay-sdk --upgrade
2.配置支付宝的公钥和私钥
配置支付宝的公钥和私钥,保存到application/apps/users/keys
目录下。
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
保存商户公钥到支付宝开放平台, 并从开放平台中把支付宝公钥保存到项目中
9.增加用户充值订单模型类
users/models.py
,代码:
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="流水号")