魔坊APP项目-17-种植园,商城页面、服务端提供商品api,解决App打包编译以后的跨域限制、客户端获取商品列表并进行展示,集成Alipayplus模块完成支付

种植园

一、商城页面

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',
          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>

新建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>

main.css,代码:

.shop .shop_list{
  margin-left: 1rem;
  margin-top: -5rem;
}

二、服务端提供商品api

商店的商品:
    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小时
    

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="商品图片")

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"
    name = db.Column(db.String(255), index=True, comment="用户账户")
    _password = db.Column(db.String(255), comment="登录密码")
    _transaction_password = db.Column(db.String(255), comment='交易密码')
    nickname = db.Column(db.String(255), comment="用户昵称")
    age = db.Column(db.SmallInteger, comment="年龄")
    money = db.Column(db.Numeric(7,2), comment="账户余额")
    credit = db.Column(db.Numeric(7, 2), default=0, comment="果子积分")
    ip_address = db.Column(db.String(255), default="", index=True, comment="登录IP")
    intro = db.Column(db.String(500), default="", comment="个性签名")
    avatar = db.Column(db.String(255), default="", comment="头像url地址")
    sex = db.Column(db.SmallInteger, default=0, comment="性别")  # 0表示未设置,保密, 1表示男,2表示女
    email = db.Column(db.String(32), index=True, default="", nullable=False, comment="邮箱地址")
    mobile = db.Column(db.String(32), index=True, nullable=False, comment="手机号码")
    unique_id = db.Column(db.String(255), index=True, default="", comment="客户端唯一标记符")
    province = db.Column(db.String(255), default="", comment="省份")
    city = db.Column(db.String(255), default="", comment="城市")
    area = db.Column(db.String(255), default="", comment="地区")
    info = db.relationship('UserProfile', backref='user', uselist=False)

    @property
    def password(self):
        return self._password

    @password.setter
    def password(self, rawpwd):
        """密码加密"""
        self._password = generate_password_hash(rawpwd)

    def check_password(self, rawpwd):
        """验证密码"""
        return check_password_hash(self.password, rawpwd)

    @property
    def transaction_password(self):
        return self._transaction_password

    @transaction_password.setter
    def transaction_password(self, rawpwd):
        """密码加密"""
        self._transaction_password = generate_password_hash(rawpwd)

    def check_transaction_password(self, rawpwd):
        """验证密码"""
        return check_password_hash(self.transaction_password, rawpwd)


class UserProfile(BaseModel):
    """用户详情信息表"""
    __tablename__ = "mf_user_profile"
    user_id = db.Column(db.Integer,db.ForeignKey('mf_user.id'), comment="用户ID")
    education = db.Column(db.Integer, comment="学历教育")
    middle_school = db.Column(db.String(255), default="", comment="初中/中专")
    high_school = db.Column(db.String(255), default="", comment="高中/高职")
    college_school = db.Column(db.String(255), default="", comment="大学/大专")
    profession_cate = db.Column(db.String(255), default="", comment="职业类型")
    profession_info = db.Column(db.String(255), default="", comment="职业名称")
    position = db.Column(db.SmallInteger, default=0, comment="职位/职称")
    emotion_status = db.Column(db.SmallInteger, default=0, comment="情感状态")
    birthday =db.Column(db.DateTime, default="", comment="生日")
    hometown_province = db.Column(db.String(255), default="", comment="家乡省份")
    hometown_city = db.Column(db.String(255), default="", comment="家乡城市")
    hometown_area = db.Column(db.String(255), default="", comment="家乡地区")
    hometown_address = db.Column(db.String(255), default="", comment="家乡地址")
    living_province = db.Column(db.String(255), default="", comment="现居住省份")
    living_city = db.Column(db.String(255), default="", comment="现居住城市")
    living_area = db.Column(db.String(255), default="", comment="现居住地区")
    living_address = db.Column(db.String(255), default="", comment="现居住地址")

    def __repr__(self):
        return "<%s %s>" % (self.__class__.__name__, self.user.name)

class UserRelation(BaseModel):
    """用户关系"""
    __tablename__ = 'mf_user_relation'
    relation_status_chioce = (
        (1, '好友'),
        (2, '关注'),
    )
    relation_type_chioce = (
        (1, '手机'),
        (2, '账号'),
        (3, '邮箱'),
        (4, '昵称'),
        (5, '群聊'),
        (6, '二维码邀请注册'),
    )
    send_user = db.Column(db.Integer, comment='用户1')  # 主动构建关系的用户
    receive_user = db.Column(db.Integer, comment='用户2')  # 接受关系请求的用户
    relation_type = db.Column(db.Integer, default=1, comment='构建关系类型')
    relation_status = db.Column(db.Integer, default=1, comment='关系状态')

    def __repr__(self):
        return '用户%s通过%s对%s进行了%s操作' % (self.send_user, self.relation_type, self.receive_user, self.status)

数据迁移,代码:

python manage.py db migrate -m "add goods table"
python manage.py db upgrade

在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="种植园"))

在数据库添加SQL数据,代码:

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);

解决App打包编译以后的跨域限制

解决app打包生成以后,页面无法请求服务端数据的跨域问题。
无法获取数据的原因是,当前APP中获取数据是通过ajax来发送请求的,因为我们当前的APP是混合APP,所以实际来说,这种混合APP就是一个浏览器内核构建的。因此也会存在同源策略的访问限制,因此我们需要在服务端实现跨域资源共享。

服务端终端运行:

pip install -U flask-cors

application/__init__.py,代码:

import os, sys

from flask import Flask
from flask_script import Manager
from flask_sqlalchemy import SQLAlchemy
from flask_redis import FlaskRedis
from flask_session import Session
from flask_migrate import Migrate, MigrateCommand
from flask_jsonrpc import JSONRPC
from flask_marshmallow import Marshmallow
from flask_jwt_extended import JWTManager
from flask_admin import Admin
from flask_babelex import Babel
from faker import Faker
from flask_pymongo import PyMongo
from flask_qrcode import QRcode
from flask_socketio import SocketIO
from flask_cors import CORS

from application.utils import init_blueprint
from application.utils.config import load_config
from application.utils.session import init_session
from application.utils.logger import Log
from application.utils.commands import load_command

# 创建终端脚本管理对象
manager = Manager()

# 创建数据库链接对象
db = SQLAlchemy()

# redis链接对象
redis = FlaskRedis()

# Session存储对象
session_store = Session()

# 数据迁移实例对象
migrate = Migrate()

# 日志对象
log = Log()

# jsonrpc模块实例对象
jsonrpc = JSONRPC()

# 数据转换器的对象创建
ma = Marshmallow()

# jwt认证模块实例化
jwt = JWTManager()

# flask_admin模块实例化
admin = Admin()

# flask_babelex模块实例化
babel = Babel()

# mongoDB
mongo = PyMongo()

# qrcode
QRCode = QRcode()

# socketio
socketio = SocketIO()

# flask_cors
cors = CORS()

def init_app(config_path):
    """全局初始化"""
    # 创建app应用对象
    app = Flask(__name__)
    # 项目根目录
    app.BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))

    # 加载导包路径
    sys.path.insert(0, os.path.join(app.BASE_DIR, "application/utils/language"))

    # 加载配置
    Config = load_config(config_path)
    app.config.from_object(Config)

    # 数据库初始化
    db.init_app(app)
    app.db = db
    redis.init_app(app)
    mongo.init_app(app)

    # 数据转换器的初始化
    ma.init_app(app)

    # session存储初始化
    init_session(app)
    session_store.init_app(app)

    # 数据迁移初始化
    migrate.init_app(app, db)
    # 添加数据迁移的命令到终端脚本工具中
    manager.add_command('db', MigrateCommand)

    # 日志初始化
    app.log = log.init_app(app)

    # 蓝图注册
    init_blueprint(app)

    # jsonrpc初始化
    jsonrpc.service_url = "/api"  # api接口的url地址前缀
    jsonrpc.init_app(app)

    # jwt初始化
    jwt.init_app(app)

    # admin初始化
    admin.init_app(app)

    # 国际化本地化模块的初始化
    babel.init_app(app)

    # 初始化终端脚本工具
    manager.app = app

    # 数据种子生成器[faker]
    app.faker = Faker(app.config.get('LANGUAGE'))

    # qrcode初始化配置
    QRCode.init_app(app)
    
    # cors
    cors.init_app(app, resources={r"/api/*": {'origins': "*"}})

    # socketio
    socketio.init_app(app, cors_allowed_origins=app.config['CORS_ALLOWED_ORIGINS'], async_mode=app.config['ASYNC_MODE'],debug=app.config['DEBUG'])
    # 改写runserver命令
    if sys.argv[1] == 'runserver':
        manager.add_command('run', socketio.run(app, host=app.config['HOST'], port=app.config['PORT']))

    # 注册自定义命令
    load_command(manager)

    return manager

提供商品列表给客户端,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
    }
    

marshmallow,代码:


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
    

三、客户端获取商品列表并进行展示

客户端就可以在打开商品的时候, 获取商品列表,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,
              }
            },{
              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>

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" @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',
          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>

settings.js代码:

function init(){
  if (Game) {
    var game = new Game("../mp3/bg1.mp3");
    Vue.prototype.game = game;
  }
  server_url = 'http://192.168.20.180:5000';
  if(axios){
    // 初始化axios
    axios.defaults.baseURL = server_url+"/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: server_url+"/users/avatar",
    static_url: server_url+"/static/",
    code_url: server_url,
    socket_server: 'ws://192.168.20.180:5000',
    socket_namespace: '/mofang',
  }
}

接下来,在APICloud中上传提交代码到云端进行云编译。
1.首先来到APIcloud开发者web后台,把Alipayplus模块加载到APP。

2.点击Alipayplus进入模块详情。

3.把模块使用到指定APP中。

集成Alipayplus模块完成支付

在这里插入图片描述
接下来,服务端中需要完成的就是生成订单参数和接收支付结果,所以我们先 安装alipay的sdk,集成到flask项目中。
终端运行:

pip install python-alipay-sdk --upgrade

配置支付宝的公钥和私钥,保存到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

保存商户公钥到支付宝开放平台, 并从开放平台中把支付宝公钥保存到项目中

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="流水号")

数据迁移,命令:

python manage.py db migrate -m "add recharge table"
python manage.py db upgrade
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值