day116:MoFang:顯示背包解鎖/未解鎖格子數&顯示背包的道具物品&背包解鎖

目錄

1.顯示背包的已解鎖/未解鎖格子數

2.顯示背包中的道具物品

3.用戶購買道具的時候,判斷背包存儲是否達到上限

4.道具也可以使用積分購買

5.在商城介面根據金額/積分顯示不同商品

6.背包解鎖

1.顯示背包的已解鎖/未解鎖格子數

1.設計種植園的配置參數

在背包中顯示道具,會涉及到用戶的背包格子的顯示以及解鎖問題,所以我們需要在服務端準備一個參數資訊, 用於保存種植園中用戶的業務參數,例如:

格子的初始化數量, 每次解鎖背包格子的價格等等.

參數資訊的保存與之前項目配置的資訊有所不同, 不同的地方在於, 參數資訊僅僅是種植園額業務參數,會在項目運營的時候允許有所改動,而項目配置的變數參數則在項目上線以後基本不做改動.

所以我們可以才用資料庫表的方式來保存種植園的參數資訊.orchard/models.py程式碼:

 

class Setting(BaseModel):
    """參數資訊"""
    __tablename__ = "mf_orchard_setting"
    title=db.Column(db.String(255), comment="提示文本")
    value=db.Column(db.String(255), comment="數值")

2.在配置表中添加種植園公共配置參數

添加測試數據,sql語句:

INSERT INTO mofang.mf_orchard_setting
(id, name, is_deleted, orders, status, created_time, updated_time, title, value) VALUES
(1, 'package_number_base', 0, 1, 1, '2020-12-30 17:40:46', '2020-12-30 17:40:44', '背包格子基礎數量', '4'),
(2, 'package_number_max', 0, 1, 1, '2020-12-30 17:40:46', '2020-12-30 17:40:44', '背包格子上限數量', '32'),
(3, 'td_prop_max', 0, 1, 1, '2020-12-30 17:40:46', '2020-12-30 17:40:44', '單個格子存儲道具數量上限', '10'),
(4, 'package_unlock_price_1', 0, 1, 1, '2020-12-30 17:40:46', '2020-12-30 17:40:44', '解鎖背包格子價格5-8', '10'),
(5, 'package_unlock_price_2', 0, 1, 1, '2020-12-30 17:40:46', '2020-12-30 17:40:44', '解鎖背包格子價格9-12', '50'),
(6, 'package_unlock_price_3', 0, 1, 1, '2020-12-30 17:40:46', '2020-12-30 17:40:44', '解鎖背包格子價格13-16', '100'),
(7, 'package_unlock_price_4', 0, 1, 1, '2020-12-30 17:40:46', '2020-12-30 17:40:44', '解鎖背包格子價格17-20', '200'),
(8, 'package_unlock_price_5', 0, 1, 1, '2020-12-30 17:40:46', '2020-12-30 17:40:44', '解鎖背包格子價格21-24', '500'),
(9, 'package_unlock_price_6', 0, 1, 1, '2020-12-30 17:40:46', '2020-12-30 17:40:44', '解鎖背包格子價格25-28', '1000'),
(10, 'package_unlock_price_7', 0, 1, 1, '2020-12-30 17:40:46', '2020-12-30 17:40:44', '解鎖背包格子價格29-32', '5000');

3.後端:用戶應該在登錄時獲取到後端發送過來的種植園公共參數和用戶私人蔘數

服務端在用戶登陸種植園得到時候需要返回種植園的公共參數以及用戶的私有參數,orchard/socket.py, 程式碼:

from application import socketio
from flask import request
from application.apps.users.models import User
from flask_socketio import join_room, leave_room
from application import mongo
from .models import Goods,Setting
from status import APIStatus as status
from message import ErrorMessage as errmsg

# 斷開socket通訊
@socketio.on("disconnect", namespace="/mofang")
def user_disconnect():
    print("用戶%s退出了種植園" % request.sid )

@socketio.on("login", namespace="/mofang")
def user_login(data):
    # 分配房間
    room = data["uid"]
    join_room(room)
    # 保存當前用戶和sid的綁定關係
    # 判斷當前用戶是否在mongo中有記錄
    query = {
        "_id": data["uid"]
    }
    ret = mongo.db.user_info_list.find_one(query)
    if ret:
        mongo.db.user_info_list.update_one(query,{"$set":{"sid": request.sid}})
    else:
        mongo.db.user_info_list.insert_one({
        "_id": data["uid"],
        "sid": request.sid,
    })

    # A.返回種植園的相關配置參數
    orchard_settings = {} 
    # 1.查詢公共配置表得到種植園所有公共配置
    setting_list = Setting.query.filter(Setting.is_deleted==False, Setting.status==True).all()
    # 2.重新構造數據結構類型{配置名1:配置對應的值1,配置名2:配置對應的值2}
    for item in setting_list:
        orchard_settings[item.name] = item.value

    # B.返回當前用戶相關的配置參數
    user_settings = {}
    # 1.從mongo中查找用戶資訊
    dict = mongo.db.user_info_list.find_one({"sid":request.sid})
    # 2.新用戶如果在mongo查詢不到格子數,則用戶的默認格子數為4
    if dict.get("package_number") is None:
        user_settings["package_number"]  = orchard_settings.get("package_number_base",4)
        # 3.將用戶格子數更新到mongo中
        mongo.db.user_info_list.update_one({"sid":request.sid},{"$set":{"package_number": user_settings["package_number"]}})
    # 4.如果在mongo中能查詢到當前用戶的格子數,則將格子數取出來賦值給user_setting
    else:
        user_settings["package_number"]  = dict.get("package_number")
    # 5.將種植園公共配置和用戶私有配置返回給前端
    socketio.emit("login_response", {
        "errno":status.CODE_OK,
        "errmsg":errmsg.ok,
        "orchard_settings":orchard_settings,
        "user_settings":user_settings
    }, namespace="/mofang", room=room)

4.前端:接收到種植園公共配置和用戶私有配置並通過fsave保存在前端

<!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">{{money}}</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" @click="go_my_package">背包</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:"",
                    money:"",
                    settings_info:{
                        orchard: {},  // 種植園公共參數
                        user:{},      // 用戶私有相關參數
                    },
          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: 410,
        },null);
        this.checkout();
                this.money = this.game.fget("money");
                this.buy_prop();
      },
            methods:{
                user_recharge(){
                    // 發起充值請求
                    api.actionSheet({
                        title: '餘額充值',
                        cancelTitle: '取消',
                        buttons: this.recharge_list
                    }, (ret, err)=>{
                        if( ret ){
                                     if(ret.buttonIndex <= this.recharge_list.length){
                                             // 充值金額
                                             money = this.recharge_list[ret.buttonIndex-1];
                                             // 調用支付寶充值
                                             this.create_recharge(money);
                                     }
                        }else{

                        }
                    });

                },
                create_recharge(money){
                    // 獲取歷史資訊記錄
                    var token = this.game.get("access_token") || this.game.fget("access_token");
                    this.game.checkout(this, token, (new_access_token)=>{
                        this.axios.post("",{
                            "jsonrpc": "2.0",
                            "id": this.uuid(),
                            "method": "Recharge.create",
                            "params": {
                                "money": money,
                            }
                        },{
                            headers:{
                                Authorization: "jwt " + token,
                            }
                        }).then(response=>{
                            if(parseInt(response.data.result.errno)==1000){
                                // 前往支付寶
                                var aliPayPlus = api.require('aliPayPlus');
                                aliPayPlus.payOrder({
                                     orderInfo: response.data.result.order_string,
                                     sandbox: response.data.result.sandbox, // 將來APP上線需要修改成false
                                 }, (ret, err)=>{
                                      pay_result = {
                                            9000:"支付成功",
                          8000:"正在處理中",
                          4000:"訂單支付失敗",
                          5000:"重複請求",
                          6001:"取消支付",
                          6002:"網路連接出錯",
                                            6004:"支付結果未知",
                                        }
                                    api.alert({
                                        title: '支付結果',
                                        msg: pay_result[ret.code],
                                        buttons: ['確定']
                                    });
                                        // 通知服務端, 修改充值結果
                                        this.return_recharge(response.data.result.order_number,token);
                                });
                            }else{
                                    this.game.print(response.data);
                            }
                        }).catch(error=>{
                            // 網路等異常
                            this.game.print(error);
                        });
                    })
                },
                return_recharge(out_trade_number,token){
                    this.axios.post("",{
                        "jsonrpc": "2.0",
                        "id": this.uuid(),
                        "method": "Recharge.return",
                        "params": {
                            "out_trade_number": out_trade_number,
                        }
                    },{
                        headers:{
                            Authorization: "jwt " + token,
                        }
                    }).then(response=>{
                        if(parseInt(response.data.result.errno)==1000){
                            this.money = response.data.result.money.toFixed(2);
                        }
                    })
                },
        checkout(){
          var token = this.game.get("access_token") || this.game.fget("access_token");
          this.game.checkout(this,token,(new_access_token)=>{
            this.connect();
                        this.login();
          });
        },
        connect(){
          // socket連接
          this.socket = io.connect(this.settings.socket_server + this.namespace, {transports: ['websocket']});
          this.socket.on('connect', ()=>{
              this.game.print("開始連接服務端");
                            var id = this.game.fget("id");
                            this.socket.emit("login",{"uid":id});
          });
        },
                 // ***用戶登錄種植園後獲取種植園公有配置和私有配置***
                login(){
                    this.socket.on("login_response",(message)=>{
                        this.settings_info.orchard = message.orchard_settings;
                        this.settings_info.user=message.user_settings;
                        this.game.fsave({
                            "orchard_settings":message.orchard_settings,
                            "user_settings":message.user_settings
                        });
                    });
                },
        go_index(){
          this.game.goWin("root");
        },
        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
          });
                },
                go_my_package(){
                    // 我的背包
                    this.game.goFrame("package","package.html", this.current,null,{
              type:"push",
              subType:"from_top",
              duration:300
          });
                },
                buy_prop(){
                    api.addEventListener({
                            name: 'buy_prop'
                    }, (ret, err)=>{
                            if( ret ){
                                // 用戶購買道具
                                this.socket.emit("user_buy_prop",ret.value);
                            }
                    });
                },
            }
        });
    }
    </script>
</body>
</html>

用戶登錄種植園後獲取種植園公有配置和私有配置

5.前端:背包顯示用戶真實的解鎖格子和未解鎖格子

1.在背包頁面獲取種植園公有配置和用戶私有配置

2.通過computed計算屬性計算出用戶解鎖格子數和用戶未解鎖格子數

3.在HTML頁面通過for循環顯示解鎖格子和未解鎖格子

package.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 add_friend package" id="app">
    <div class="box">
      <p class="title">我的背包</p>
      <img class="close" @click="close_frame" src="../static/images/close_btn1.png" alt="">
      <div class="prop_list">
        <div class="item" v-for="number in unlock_td_number"></div>
        <div class="item lock" v-for="number in lock_td_number"></div>
      </div>
    </div>
    </div>
    <script>
    apiready = function(){
        init();
        new Vue({
            el:"#app",
            data(){
                return {
                    td: 36,      // 背包格子總數量
                    user_id: "", // 當前登陸用戶Id
                    orchard_settings:{}, // 種植園相關公共參數
                    user_settings:{}, // 用戶相關私有參數
                    prev:{name:"",url:"",params:{}},
                    current:{name:"package",url:"package.html",params:{}},
                }
            },
            computed:{// 計算屬性
                lock_td_number(){
                    // 未解鎖的格子
                    return parseInt(this.orchard_settings.package_number_max-this.user_settings.package_number);
                },
                unlock_td_number(){
                    // 解鎖的格子
                    return parseInt( this.user_settings.package_number);
                }
            },
            created(){
                this.user_id = this.game.get("id") || this.game.fget("id");
                this.orchard_settings = JSON.parse(this.game.fget("orchard_settings"));
                this.user_settings = JSON.parse(this.game.fget("user_settings"));
            },
            methods:{
        close_frame(){
          this.game.outFrame("package");
        },
            }
        });
    }
    </script>
</body>
</html>

背包顯示真實的解鎖格子和未解鎖格子

2.顯示背包中的道具物品

接著往下就應用顯示背包中的道具物品了

在顯示道具的時候, 因為每個格子有存儲上限,所以我們可以在參數配置中新增一項參數限制存儲道具的數量, (在上面添加測試數據時已經設置了.)

INSERT INTO mofang.mf_orchard_setting
(id, name, is_deleted, orders, status, created_time, updated_time, title, value) VALUES
(3, 'td_prop_max', 0, 1, 1, '2020-12-30 17:40:46', '2020-12-30 17:40:44', '單個格子存儲道具數量上限', '10'),

orchard/socket.py ,程式碼:

1.後端獲取用戶當前道具物品並且按照格子規則進行分開

socket.py,程式碼

from application import socketio
from flask import request
from application.apps.users.models import User
from flask_socketio import join_room, leave_room
from application import mongo
from .models import Goods,Setting
from status import APIStatus as status
from message import ErrorMessage as errmsg


@socketio.on("user_prop", namespace="/mofang")
def user_prop():
    """用戶道具"""
    # 1.查詢當前用戶的mongo對象
    userinfo = mongo.db.user_info_list.find_one({"sid":request.sid})
    # 2.獲取當前用戶的道具物品列表
    prop_list = userinfo.get("prop_list")
    prop_id_list = [] # 用戶所擁有所有道具的id值
    for prop_str,num in prop_list.items():
        pid = int(prop_str[5:]) # 獲取每個道具的id值
        prop_id_list.append(pid) # prop_id_list用來存儲用戶所擁有所有道具的id值

    data = []
    # 3.查詢用戶所擁有的所有道具的所有資訊(道具價格、道具圖片等資訊)
    prop_list_data = Goods.query.filter(Goods.id.in_(prop_id_list)).all()
    # 4.查詢種植園公共配置:每個格子可容納的最大數量
    setting = Setting.query.filter(Setting.name == "td_prop_max").first()
    if setting is None:
        td_prop_max = 10
    else:
        td_prop_max = int(setting.value)
    
    # 5.循環用戶擁有的每個道具
    for prop_data in prop_list_data:
        # 查詢用戶所擁有每個道具對應的數量
        num = int( prop_list[("prop_%s" % prop_data.id)])
        if td_prop_max > num: # 如果道具數量小於單個格子最大容納值
            data.append({ # 構造data數據結構:包括道具id/道具數量/道具圖片
                "num": num,
                "image": prop_data.image,
                "pid": prop_data.id
            })
        else: # 如果道具數量大於單個格子最大容納值
            padding_time = num // td_prop_max  # 545//100=5
            padding_last = num % td_prop_max   # 545%100=45
            # 容量已滿的整格子
            arr = [{
                "num": td_prop_max,
                "image": prop_data.image,
                "pid": prop_data.id
            }] * padding_time
            # 餘下的未滿的單獨再佔一個格子
            if padding_last != 0:
                arr.append({
                    "num": padding_last,
                    "image": prop_data.image,
                    "pid": prop_data.id
                })
            data = data + arr

    room = request.sid
    # 6.將背包中的道具物品/道具數量/所佔格子等返回給前端
    socketio.emit("user_prop_response", {
        "errno": status.CODE_OK,
        "errmsg": errmsg.ok,
        "data":data,
    }, namespace="/mofang",
                  room=room)

2.前端獲取用戶道具

1.用戶登錄種植園後,前端向後端發送請求(user_prop),請求去獲取用戶背包的道具數據

connect(){
        this.socket.emit("user_prop");
    });
},

2.後端接收請求(user_prop),將用戶道具數據響應(user_prop_response)給前端

@socketio.on("user_prop", namespace="/mofang")
def user_prop():
    
    socketio.emit("user_prop_response", {
        "errno": status.CODE_OK,
        "errmsg": errmsg.ok,
        "data":data,
    }, namespace="/mofang",
                  room=room)

3.前端接收來自後端的響應數據(user_prop_response),並將用戶道具數據存儲在前端

user_package(){
    // 用戶背包道具列表
    this.socket.on("user_prop_response",(message)=>{
        this.game.fsave({
            "user_package":message.data,
        })
    })
},

在客戶端中獲取道具, 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">{{money}}</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" @click="go_my_package">背包</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:"",
                    money:"",
                    settings_info:{
                        orchard: {},  // 種植園公共參數
                        user:{},      // 用戶私有相關參數
                    },
          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: 410,
        },null);
        this.checkout();
                this.money = this.game.fget("money");
                this.buy_prop();
      },
            methods:{
                user_recharge(){
                    // 發起充值請求
                    api.actionSheet({
                        title: '餘額充值',
                        cancelTitle: '取消',
                        buttons: this.recharge_list
                    }, (ret, err)=>{
                        if( ret ){
                                     if(ret.buttonIndex <= this.recharge_list.length){
                                             // 充值金額
                                             money = this.recharge_list[ret.buttonIndex-1];
                                             // 調用支付寶充值
                                             this.create_recharge(money);
                                     }
                        }else{

                        }
                    });

                },
                create_recharge(money){
                    // 獲取歷史資訊記錄
                    var token = this.game.get("access_token") || this.game.fget("access_token");
                    this.game.checkout(this, token, (new_access_token)=>{
                        this.axios.post("",{
                            "jsonrpc": "2.0",
                            "id": this.uuid(),
                            "method": "Recharge.create",
                            "params": {
                                "money": money,
                            }
                        },{
                            headers:{
                                Authorization: "jwt " + token,
                            }
                        }).then(response=>{
                            if(parseInt(response.data.result.errno)==1000){
                                // 前往支付寶
                                var aliPayPlus = api.require('aliPayPlus');
                                aliPayPlus.payOrder({
                                     orderInfo: response.data.result.order_string,
                                     sandbox: response.data.result.sandbox, // 將來APP上線需要修改成false
                                 }, (ret, err)=>{
                                      pay_result = {
                                            9000:"支付成功",
                          8000:"正在處理中",
                          4000:"訂單支付失敗",
                          5000:"重複請求",
                          6001:"取消支付",
                          6002:"網路連接出錯",
                                            6004:"支付結果未知",
                                        }
                                    api.alert({
                                        title: '支付結果',
                                        msg: pay_result[ret.code],
                                        buttons: ['確定']
                                    });
                                        // 通知服務端, 修改充值結果
                                        this.return_recharge(response.data.result.order_number,token);
                                });
                            }else{
                                    this.game.print(response.data);
                            }
                        }).catch(error=>{
                            // 網路等異常
                            this.game.print(error);
                        });
                    })
                },
                return_recharge(out_trade_number,token){
                    this.axios.post("",{
                        "jsonrpc": "2.0",
                        "id": this.uuid(),
                        "method": "Recharge.return",
                        "params": {
                            "out_trade_number": out_trade_number,
                        }
                    },{
                        headers:{
                            Authorization: "jwt " + token,
                        }
                    }).then(response=>{
                        if(parseInt(response.data.result.errno)==1000){
                            this.money = response.data.result.money.toFixed(2);
                        }
                    })
                },
        checkout(){
          var token = this.game.get("access_token") || this.game.fget("access_token");
          this.game.checkout(this,token,(new_access_token)=>{
            this.connect();
                        this.login();
                        this.user_package();
          });
        },
        connect(){
          // socket連接
          this.socket = io.connect(this.settings.socket_server + this.namespace, {transports: ['websocket']});
          this.socket.on('connect', ()=>{
              this.game.print("開始連接服務端");
                            var id = this.game.fget("id");
                            this.socket.emit("login",{"uid":id});
                            this.socket.emit("user_prop");
          });
        },
                login(){
                    this.socket.on("login_response",(message)=>{
                        this.settings_info.orchard = message.orchard_settings;
                        this.settings_info.user=message.user_settings;
                        this.game.fsave({
                            "orchard_settings":message.orchard_settings,
                            "user_settings":message.user_settings
                        });
                    });
                },
                user_package(){
                    // 用戶背包道具列表
                    this.socket.on("user_prop_response",(message)=>{
                        this.game.fsave({
                            "user_package":message.data,
                        })
                    })
                },
        go_index(){
          this.game.goWin("root");
        },
        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
          });
                },
                go_my_package(){
                    // 我的背包
                    this.game.goFrame("package","package.html", this.current,null,{
              type:"push",
              subType:"from_top",
              duration:300
          });
                },
                buy_prop(){
                    api.addEventListener({
                            name: 'buy_prop'
                    }, (ret, err)=>{
                            if( ret ){
                                // 用戶購買道具
                                this.socket.emit("user_buy_prop",ret.value);
                            }
                    });
                },

            }
        });
    }
    </script>
</body>
</html>

客戶端獲取道具

3.在背包頁面顯示用戶所擁有的道具

package.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 add_friend package" id="app">
    <div class="box">
      <p class="title">我的背包</p>
      <img class="close" @click="close_frame" src="../static/images/close_btn1.png" alt="">
      <div class="prop_list">
        <div class="item" v-for="prop in user_package" @click="use_prop(prop.pid)">
            <img :src="settings.static_url+prop.image" alt="">
                    <span>{{prop.num}}</span>
        </div>
        <div class="item" v-for="number in unlock_td_number"></div>
        <div class="item lock" v-for="number in lock_td_number"></div>
      </div>
    </div>
    </div>
    <script>
    apiready = function(){
        init();
        new Vue({
            el:"#app",
            data(){
                return {
                    td: 36,      // 背包格子總數量
                    user_id: "", // 當前登陸用戶Id
                    orchard_settings:{}, // 種植園相關公共參數
                    user_settings:{}, // 用戶相關私有參數
                    user_package:[],  // 用戶背包資訊
                    prev:{name:"",url:"",params:{}},
                    current:{name:"package",url:"package.html",params:{}},
                }
            },
            computed:{// 計算屬性
                lock_td_number(){
                    // 未解鎖的格子
                    return parseInt(this.orchard_settings.package_number_max-this.user_settings.package_number);
                },
                unlock_td_number(){
                    // 解鎖的格子
                    return parseInt( this.user_settings.package_number - this.user_package.length);
                }
            },
            created(){
                this.user_id = this.game.get("id") || this.game.fget("id");
                this.orchard_settings = JSON.parse(this.game.fget("orchard_settings"));
                this.user_settings = JSON.parse(this.game.fget("user_settings"));
                this.user_package = JSON.parse(this.game.fget("user_package"));
            },
            methods:{
                use_prop(pid){
                    // 發起使用道具的通知
                },
        close_frame(){
          this.game.outFrame("package");
        },
            }
        });
    }
    </script>
</body>
</html>

背包頁面顯示用戶所擁有的道具

4.如果道具已經被使用/新增道具購買,服務端響應數據到本地

在道具如果被使用或者新增購買, 則服務端響應新的數據到本地,socket.py程式碼

from application import socketio
from flask import request
from application.apps.users.models import User
from flask_socketio import join_room, leave_room
from application import mongo
from .models import Goods,Setting
from status import APIStatus as status
from message import ErrorMessage as errmsg


# 斷開socket通訊
@socketio.on("disconnect", namespace="/mofang")
def user_disconnect():
    print("用戶%s退出了種植園" % request.sid )

@socketio.on("login", namespace="/mofang")
def user_login(data):
    # 分配房間
    room = data["uid"]
    join_room(room)
    # 保存當前用戶和sid的綁定關係
    # 判斷當前用戶是否在mongo中有記錄
    query = {
        "_id": data["uid"]
    }
    ret = mongo.db.user_info_list.find_one(query)
    if ret:
        mongo.db.user_info_list.update_one(query,{"$set":{"sid": request.sid}})
    else:
        mongo.db.user_info_list.insert_one({
        "_id": data["uid"],
        "sid": request.sid,
    })

    # 返回種植園的相關配置參數
    orchard_settings = {}
    setting_list = Setting.query.filter(Setting.is_deleted==False, Setting.status==True).all()
    """
    現在的格式:
        [<Setting package_number_base>, <Setting package_number_max>, <Setting package_unlock_price_1>]
    需要返回的格式:
        {
            package_number_base:4,
            package_number_max: 32,
            ...
        }
    """
    for item in setting_list:
        orchard_settings[item.name] = item.value

    # 返回當前用戶相關的配置參數
    user_settings = {}
    # 從mongo中查找用戶資訊,判斷用戶是否激活了背包格子
    dict = mongo.db.user_info_list.find_one({"sid":request.sid})
    # 背包格子
    if dict.get("package_number") is None:
        user_settings["package_number"]  = orchard_settings.get("package_number_base",4)
        mongo.db.user_info_list.update_one({"sid":request.sid},{"$set":{"package_number": user_settings["package_number"]}})
    else:
        user_settings["package_number"]  = dict.get("package_number")

    socketio.emit("login_response", {
        "errno":status.CODE_OK,
        "errmsg":errmsg.ok,
        "orchard_settings":orchard_settings,
        "user_settings":user_settings
    }, namespace="/mofang", room=room)

@socketio.on("user_buy_prop", namespace="/mofang")
def user_buy_prop(data):
    """用戶購買道具"""
    room = request.sid
    # 從mongo中獲取當前用戶資訊
    user_info = mongo.db.user_info_list.find_one({"sid":request.sid})
    user = User.query.get(user_info.get("_id"))
    if user is None:
        socketio.emit("user_buy_prop_response", {"errno":status.CODE_NO_USER,"errmsg":errmsg.user_not_exists}, namespace="/mofang", room=room)
        return
    # 從mysql中獲取商品價格
    prop = Goods.query.get(data["pid"])
    if float(user.money) < float(prop.price) * int(data["num"]):
        socketio.emit("user_buy_prop_response", {"errno":status.CODE_NO_MONEY,"errmsg":errmsg.money_no_enough}, namespace="/mofang", room=room)
        return
    # 從mongo中獲取用戶列表資訊,提取購買的商品數量進行累加和餘額
    query = {"sid": request.sid}
    if user_info.get("prop_list") is None:
        """此前沒有購買任何道具"""
        message = {"$set":{"prop_list":{"prop_%s" % prop.id:int(data["num"])}}}
        mongo.db.user_info_list.update_one(query,message)
    else:
        """此前有購買了道具"""
        prop_list = user_info.get("prop_list") # 道具列表
        if ("prop_%s" % prop.id) in prop_list:
            """如果再次同一款道具"""
            prop_list[("prop_%s" % prop.id)] = prop_list[("prop_%s" % prop.id)] + int(data["num"])
        else:
            """此前沒有購買過這種道具"""
            prop_list[("prop_%s" % prop.id)] = int(data["num"])

        mongo.db.user_info_list.update_one(query, {"$set":{"prop_list":prop_list}})
    get_user_prop()
    socketio.emit("user_buy_prop_response", {"errno":status.CODE_OK,"errmsg":errmsg.ok}, namespace="/mofang", room=room)

@socketio.on("user_prop", namespace="/mofang")
def user_prop():
    get_user_prop()

def get_user_prop():
    """用戶道具"""
    userinfo = mongo.db.user_info_list.find_one({"sid":request.sid})
    prop_list = userinfo.get("prop_list")
    prop_id_list = []
    for prop_str,num in prop_list.items():
        pid = int(prop_str[5:])
        prop_id_list.append(pid)

    data = []
    prop_list_data = Goods.query.filter(Goods.id.in_(prop_id_list)).all()
    setting = Setting.query.filter(Setting.name == "td_prop_max").first()
    if setting is None:
        td_prop_max = 10
    else:
        td_prop_max = int(setting.value)

    for prop_data in prop_list_data:
        num = int( prop_list[("prop_%s" % prop_data.id)])
        if td_prop_max > num:
            data.append({
                "num": num,
                "image": prop_data.image,
                "pid": prop_data.id
            })
        else:
            padding_time = num // td_prop_max
            padding_last = num % td_prop_max
            arr = [{
                "num": td_prop_max,
                "image": prop_data.image,
                "pid": prop_data.id
            }] * padding_time
            if padding_last != 0:
                arr.append({
                    "num": padding_last,
                    "image": prop_data.image,
                    "pid": prop_data.id
                })
            data = data + arr

    room = request.sid
    socketio.emit("user_prop_response", {
        "errno": status.CODE_OK,
        "errmsg": errmsg.ok,
        "data":data,
    }, namespace="/mofang",
                  room=room)

3.用戶購買道具的時候,判斷背包存儲是否達到上限

from application import socketio
from flask import request
from application.apps.users.models import User
from flask_socketio import join_room, leave_room
from application import mongo
from .models import Goods,Setting
from status import APIStatus as status
from message import ErrorMessage as errmsg


@socketio.on("user_buy_prop", namespace="/mofang")
def user_buy_prop(data):
    """用戶購買道具"""
    room = request.sid
    user_info = mongo.db.user_info_list.find_one({"sid":request.sid})
    user = User.query.get(user_info.get("_id"))
    if user is None:
        socketio.emit("user_buy_prop_response", {"errno":status.CODE_NO_USER,"errmsg":errmsg.user_not_exists}, namespace="/mofang", room=room)
        return

    # 判斷背包物品存儲是否達到上限
    use_package_number = int(user_info.get("use_package_number",0)) # 當前已經使用的格子數量
    package_number = int(user_info.get("package_number",0))         # 當前用戶已經解鎖的格子數量

    
    setting = Setting.query.filter(Setting.name == "td_prop_max").first()
    if setting is None:
        td_prop_max = 10
    else:
        td_prop_max = int(setting.value)

    # ***計算購買道具以後需要額外佔用的格子數量***
    if ("prop_%s" % data["pid"]) in user_info.get("prop_list"): # 如果用戶購買過當前道具
        """A.曾經購買過當前道具"""
        
        # 1.獲取購買前的道具數量
        prop_num = int( user_info.get("prop_list")["prop_%s" % data["pid"]]) 
        # 2.如果成功購買道具以後的數量
        new_prop_num = prop_num+int(data["num"]) 
        
        # 3.計算購買前道具所佔用的格子數量
        old_td_num = prop_num // td_prop_max
        if prop_num % td_prop_max > 0:
            old_td_num+=1
        # 4.計算如果成功購買後道具所佔用的格子數量
        new_td_num = new_prop_num // td_prop_max
        if new_prop_num % td_prop_max > 0:
            new_td_num+=1
        # 5.計算本次購買道具所需要佔用的格子數量
        td_num = new_td_num - old_td_num
    else:
        """B.新增購買的道具"""
        # 計算本次購買道具需要佔用的格子數量
        if int(data["num"]) > td_prop_max:
            """需要多個格子"""
            td_num = int(data["num"]) // td_prop_max
            if int(data["num"]) % td_prop_max > 0:
                td_num+=1
        else:
            """需要一個格子"""
            td_num = 1

    if use_package_number+td_num > package_number:
        """如果超出存儲上限,提示錯誤:達到背包上限"""
        socketio.emit("user_buy_prop_response", {"errno": status.CODE_NO_PACKAGE, "errmsg": errmsg.no_package},
                      namespace="/mofang", room=room)
        return

    # 從mysql中獲取商品價格
    prop = Goods.query.get(data["pid"])
    if float(user.money) < float(prop.price) * int(data["num"]):
        socketio.emit("user_buy_prop_response", {"errno":status.CODE_NO_MONEY,"errmsg":errmsg.money_no_enough}, namespace="/mofang", room=room)
        return
    # 從mongo中獲取用戶列表資訊,提取購買的商品數量進行累加和餘額
    query = {"sid": request.sid}
    if user_info.get("prop_list") is None:
        """此前沒有購買任何道具"""
        message = {"$set":{"prop_list":{"prop_%s" % prop.id:int(data["num"])}}}
        mongo.db.user_info_list.update_one(query,message)
    else:
        """此前有購買了道具"""
        prop_list = user_info.get("prop_list") # 道具列表
        if ("prop_%s" % prop.id) in prop_list:
            """如果再次同一款道具"""
            prop_list[("prop_%s" % prop.id)] = prop_list[("prop_%s" % prop.id)] + int(data["num"])
        else:
            """此前沒有購買過這種道具"""
            prop_list[("prop_%s" % prop.id)] = int(data["num"])

        mongo.db.user_info_list.update_one(query, {"$set":{"prop_list":prop_list}})

    # 返回購買成功的資訊
    socketio.emit("user_buy_prop_response", {"errno":status.CODE_OK,"errmsg":errmsg.ok}, namespace="/mofang", room=room)
    # 返回最新的用戶道具列表
    user_prop()

@socketio.on("user_prop", namespace="/mofang")
def user_prop():
    """用戶道具"""
    userinfo = mongo.db.user_info_list.find_one({"sid":request.sid})
    prop_list = userinfo.get("prop_list")
    prop_id_list = []
    for prop_str,num in prop_list.items():
        pid = int(prop_str[5:])
        prop_id_list.append(pid)

    data = []
    prop_list_data = Goods.query.filter(Goods.id.in_(prop_id_list)).all()
    setting = Setting.query.filter(Setting.name == "td_prop_max").first()
    if setting is None:
        td_prop_max = 10
    else:
        td_prop_max = int(setting.value)

    for prop_data in prop_list_data:
        num = int( prop_list[("prop_%s" % prop_data.id)])
        if td_prop_max > num:
            data.append({
                "num": num,
                "image": prop_data.image,
                "pid": prop_data.id
            })
        else:
            padding_time = num // td_prop_max
            padding_last = num % td_prop_max
            arr = [{
                "num": td_prop_max,
                "image": prop_data.image,
                "pid": prop_data.id
            }] * padding_time
            if padding_last != 0:
                arr.append({
                    "num": padding_last,
                    "image": prop_data.image,
                    "pid": prop_data.id
                })
            data = data + arr
    # 保存當前用戶已經使用的格子數量
    mongo.db.user_info_list.update_one({"sid":request.sid},{"$set":{"use_package_number":len(data)}})
    room = request.sid
    socketio.emit("user_prop_response", {
        "errno": status.CODE_OK,
        "errmsg": errmsg.ok,
        "data":data,
    }, namespace="/mofang",
                  room=room)

用戶購買道具的時候,判斷背包存儲是否達到上限

4.道具也可以使用積分購買

在購買道具的流程中, 增加積分(果子)商品.

orchard/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()
    credit = auto_field()

    class Meta:
        model = Goods
        fields = ["id","name","price","image","remark","credit"]
        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" @click="buy_prop(goods.id)" 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">
                        <span v-if="goods.price>0">{{goods.price}}</span>
                        <span v-else>{{goods.credit}}個果子</span>
                    </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);
                        });
                    })
                },
                buy_prop(prop_id){
                    // 購買商品道具
                    // 讓用戶選擇購買的數量
                    api.prompt({
                            text: 1,
                            title:"請輸入購買數量",
                            type: "number",
                        buttons: ['確定', '取消']
                    }, (ret, err)=>{
                            if(ret.buttonIndex == 1){
                                // 通過通知告知socket進行商品購買
                                api.sendEvent({
                                        name: 'buy_prop',
                                        extra: {
                                            "pid": prop_id,
                                            "num": ret.text
                                        }
                                });
                            }
                    });
                }
            }
        });
    }
    </script>
</body>
</html>

在商城頁面根據金額/積分顯示不同商品

在購買商品成功以後, 計算金額和積分的扣除, socket.py程式碼:

from application import socketio
from flask import request
from application.apps.users.models import User
from flask_socketio import join_room, leave_room
from application import mongo
from .models import Goods,Setting,db
from status import APIStatus as status
from message import ErrorMessage as errmsg
# 建立socket通訊
# @socketio.on("connect", namespace="/mofang")
# def user_connect():
#     """用戶連接"""
#     print("用戶%s連接過來了!" % request.sid)
#     # 主動響應數據給客戶端
#     socketio.emit("server_response","hello",namespace="/mofang")

# 斷開socket通訊
@socketio.on("disconnect", namespace="/mofang")
def user_disconnect():
    print("用戶%s退出了種植園" % request.sid )

@socketio.on("login", namespace="/mofang")
def user_login(data):
    # 分配房間
    room = data["uid"]
    join_room(room)
    # 保存當前用戶和sid的綁定關係
    # 判斷當前用戶是否在mongo中有記錄
    query = {
        "_id": data["uid"]
    }
    ret = mongo.db.user_info_list.find_one(query)
    if ret:
        mongo.db.user_info_list.update_one(query,{"$set":{"sid": request.sid}})
    else:
        mongo.db.user_info_list.insert_one({
        "_id": data["uid"],
        "sid": request.sid,
    })

    # 返回種植園的相關配置參數
    orchard_settings = {}
    setting_list = Setting.query.filter(Setting.is_deleted==False, Setting.status==True).all()
    """
    現在的格式:
        [<Setting package_number_base>, <Setting package_number_max>, <Setting package_unlock_price_1>]
    需要返回的格式:
        {
            package_number_base:4,
            package_number_max: 32,
            ...
        }
    """
    for item in setting_list:
        orchard_settings[item.name] = item.value

    # 返回當前用戶相關的配置參數
    user_settings = {}
    # 從mongo中查找用戶資訊,判斷用戶是否激活了背包格子
    dict = mongo.db.user_info_list.find_one({"sid":request.sid})
    # 背包格子
    if dict.get("package_number") is None:
        user_settings["package_number"]  = orchard_settings.get("package_number_base",4)
        mongo.db.user_info_list.update_one({"sid":request.sid},{"$set":{"package_number": user_settings["package_number"]}})
    else:
        user_settings["package_number"]  = dict.get("package_number")

    socketio.emit("login_response", {
        "errno":status.CODE_OK,
        "errmsg":errmsg.ok,
        "orchard_settings":orchard_settings,
        "user_settings":user_settings
    }, namespace="/mofang", room=room)

@socketio.on("user_buy_prop", namespace="/mofang")
def user_buy_prop(data):
    """用戶購買道具"""
    room = request.sid
    # 從mongo中獲取當前用戶資訊
    user_info = mongo.db.user_info_list.find_one({"sid":request.sid})
    user = User.query.get(user_info.get("_id"))
    if user is None:
        socketio.emit("user_buy_prop_response", {"errno":status.CODE_NO_USER,"errmsg":errmsg.user_not_exists}, namespace="/mofang", room=room)
        return

    # 判斷背包物品存儲是否達到上限
    use_package_number = int(user_info.get("use_package_number",0)) # 當前詩經使用的格子數量
    package_number = int(user_info.get("package_number",0))         # 當前用戶已經解鎖的格子數量
    # 本次購買道具需要使用的格子數量
    setting = Setting.query.filter(Setting.name == "td_prop_max").first()
    if setting is None:
        td_prop_max = 10
    else:
        td_prop_max = int(setting.value)

    # 計算購買道具以後需要額外佔用的格子數量
    if ("prop_%s" % data["pid"]) in user_info.get("prop_list",{}):
        """曾經購買過當前道具"""
        prop_num = int( user_info.get("prop_list")["prop_%s" % data["pid"]]) # 購買前的道具數量
        new_prop_num = prop_num+int(data["num"]) # 如果成功購買道具以後的數量
        old_td_num = prop_num // td_prop_max
        if prop_num % td_prop_max > 0:
            old_td_num+=1
        new_td_num = new_prop_num // td_prop_max
        if new_prop_num % td_prop_max > 0:
            new_td_num+=1
        td_num = new_td_num - old_td_num
    else:
        """新增購買的道具"""
        # 計算本次購買道具需要佔用的格子數量

        if int(data["num"]) > td_prop_max:
            """需要多個格子"""
            td_num = int(data["num"]) // td_prop_max
            if int(data["num"]) % td_prop_max > 0:
                td_num+=1
        else:
            """需要一個格子"""
            td_num = 1

    if use_package_number+td_num > package_number:
        """超出存儲上限"""
        socketio.emit("user_buy_prop_response", {"errno": status.CODE_NO_PACKAGE, "errmsg": errmsg.no_package},
                      namespace="/mofang", room=room)
        return

    # 從mysql中獲取商品價格
    prop = Goods.query.get(data["pid"])
    if user.money > 0: # 當前商品需要通過RMB購買
        if float(user.money) < float(prop.price) * int(data["num"]):
            socketio.emit("user_buy_prop_response", {"errno":status.CODE_NO_MONEY,"errmsg":errmsg.money_no_enough}, namespace="/mofang", room=room)
            return
    else:
        """當前通過果子進行購買"""
        if int(user.credit) < int(prop.credit) * int(data["num"]):
            socketio.emit("user_buy_prop_response", {"errno": status.CODE_NO_CREDIT, "errmsg": errmsg.credit_no_enough},
                          namespace="/mofang", room=room)
            return

    # 從mongo中獲取用戶列表資訊,提取購買的商品數量進行累加和餘額
    query = {"sid": request.sid}
    if user_info.get("prop_list") is None:
        """此前沒有購買任何道具"""
        message = {"$set":{"prop_list":{"prop_%s" % prop.id:int(data["num"])}}}
        mongo.db.user_info_list.update_one(query,message)
    else:
        """此前有購買了道具"""
        prop_list = user_info.get("prop_list") # 道具列表
        if ("prop_%s" % prop.id) in prop_list:
            """如果再次同一款道具"""
            prop_list[("prop_%s" % prop.id)] = prop_list[("prop_%s" % prop.id)] + int(data["num"])
        else:
            """此前沒有購買過這種道具"""
            prop_list[("prop_%s" % prop.id)] = int(data["num"])

        mongo.db.user_info_list.update_one(query, {"$set":{"prop_list":prop_list}})

    # 扣除餘額或果子
    if prop.price > 0:
        user.money = float(user.money) - float(prop.price) * int(data["num"])
    else:
        user.credit = int(user.credit) - int(prop.credit) * int(data["num"])

    db.session.commit()

    # 返回購買成功的資訊
    socketio.emit("user_buy_prop_response", {"errno":status.CODE_OK,"errmsg":errmsg.ok}, namespace="/mofang", room=room)
    # 返回最新的用戶道具列表
    user_prop()

@socketio.on("user_prop", namespace="/mofang")
def user_prop():
    """用戶道具"""
    userinfo = mongo.db.user_info_list.find_one({"sid":request.sid})
    prop_list = userinfo.get("prop_list",{})
    prop_id_list = []
    for prop_str,num in prop_list.items():
        pid = int(prop_str[5:])
        prop_id_list.append(pid)

    data = []
    prop_list_data = Goods.query.filter(Goods.id.in_(prop_id_list)).all()
    setting = Setting.query.filter(Setting.name == "td_prop_max").first()
    if setting is None:
        td_prop_max = 10
    else:
        td_prop_max = int(setting.value)

    for prop_data in prop_list_data:
        num = int( prop_list[("prop_%s" % prop_data.id)])
        if td_prop_max > num:
            data.append({
                "num": num,
                "image": prop_data.image,
                "pid": prop_data.id
            })
        else:
            padding_time = num // td_prop_max
            padding_last = num % td_prop_max
            arr = [{
                "num": td_prop_max,
                "image": prop_data.image,
                "pid": prop_data.id
            }] * padding_time
            if padding_last != 0:
                arr.append({
                    "num": padding_last,
                    "image": prop_data.image,
                    "pid": prop_data.id
                })
            data = data + arr
    mongo.db.user_info_list.update_one({"sid":request.sid},{"$set":{"use_package_number":len(data)}})
    room = request.sid
    socketio.emit("user_prop_response", {
        "errno": status.CODE_OK,
        "errmsg": errmsg.ok,
        "data":data,
    }, namespace="/mofang",
                  room=room)

購買商品成功後,計算金額和積分的扣除

6.背包解鎖

orchard/socket.py, 程式碼:

@socketio.on("unlock_package", namespace="/mofang")
def unlock_package():
    """解鎖背包"""
    # 從mongo獲取當前用戶解鎖的格子數量
    user_info = mongo.db.user_info_list.find_one({"sid":request.sid})
    user = User.query.get(user_info.get("_id"))
    if user is None:
        socketio.emit("unlock_package_response", {"errno":status.CODE_NO_USER,"errmsg":errmsg.user_not_exists}, namespace="/mofang", room=room)
        return

    package_number = int(user_info.get("package_number"))
    num = 7 - (32 - package_number) // 4  # 沒有解鎖的格子

    # 從資料庫中獲取解鎖背包的價格
    setting = Setting.query.filter(Setting.name == "package_unlock_price_%s" % num).first()
    if setting is None:
        unlock_price = 0
    else:
        unlock_price = int(setting.value)

    # 判斷是否有足夠的積分或者價格
    room = request.sid
    if user.money < unlock_price:
        socketio.emit("unlock_package_response", {"errno": status.CODE_NO_MONEY, "errmsg": errmsg.money_no_enough},
                      namespace="/mofang", room=room)
        return

    # 解鎖成功
    user.money = float(user.money) - float(unlock_price)
    db.session.commit()

    # mongo中調整數量
    mongo.db.user_info_list.update_one({"sid":request.sid},{"$set":{"package_number": package_number+1}})

    # 返回解鎖的結果
    socketio.emit("unlock_package_response", {
        "errno": status.CODE_OK,
        "errmsg": errmsg.ok},
                  namespace="/mofang", room=room)

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">{{money}}</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" @click="go_my_package">背包</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:"",
                    money:"",
                    settings_info:{
                        orchard: {},  // 種植園公共參數
                        user:{},      // 用戶私有相關參數
                    },
          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: 410,
        },null);
        this.checkout();
                this.money = this.game.fget("money");
      },
            methods:{
                user_recharge(){
                    // 發起充值請求
                    api.actionSheet({
                        title: '餘額充值',
                        cancelTitle: '取消',
                        buttons: this.recharge_list
                    }, (ret, err)=>{
                        if( ret ){
                                     if(ret.buttonIndex <= this.recharge_list.length){
                                             // 充值金額
                                             money = this.recharge_list[ret.buttonIndex-1];
                                             // 調用支付寶充值
                                             this.create_recharge(money);
                                     }
                        }else{

                        }
                    });

                },
                create_recharge(money){
                    // 獲取歷史資訊記錄
                    var token = this.game.get("access_token") || this.game.fget("access_token");
                    this.game.checkout(this, token, (new_access_token)=>{
                        this.axios.post("",{
                            "jsonrpc": "2.0",
                            "id": this.uuid(),
                            "method": "Recharge.create",
                            "params": {
                                "money": money,
                            }
                        },{
                            headers:{
                                Authorization: "jwt " + token,
                            }
                        }).then(response=>{
                            if(parseInt(response.data.result.errno)==1000){
                                // 前往支付寶
                                var aliPayPlus = api.require('aliPayPlus');
                                aliPayPlus.payOrder({
                                     orderInfo: response.data.result.order_string,
                                     sandbox: response.data.result.sandbox, // 將來APP上線需要修改成false
                                 }, (ret, err)=>{
                                      pay_result = {
                                            9000:"支付成功",
                          8000:"正在處理中",
                          4000:"訂單支付失敗",
                          5000:"重複請求",
                          6001:"取消支付",
                          6002:"網路連接出錯",
                                            6004:"支付結果未知",
                                        }
                                    api.alert({
                                        title: '支付結果',
                                        msg: pay_result[ret.code],
                                        buttons: ['確定']
                                    });
                                        // 通知服務端, 修改充值結果
                                        this.return_recharge(response.data.result.order_number,token);
                                });
                            }else{
                                    this.game.print(response.data);
                            }
                        }).catch(error=>{
                            // 網路等異常
                            this.game.print(error);
                        });
                    })
                },
                return_recharge(out_trade_number,token){
                    this.axios.post("",{
                        "jsonrpc": "2.0",
                        "id": this.uuid(),
                        "method": "Recharge.return",
                        "params": {
                            "out_trade_number": out_trade_number,
                        }
                    },{
                        headers:{
                            Authorization: "jwt " + token,
                        }
                    }).then(response=>{
                        if(parseInt(response.data.result.errno)==1000){
                            this.money = response.data.result.money.toFixed(2);
                        }
                    })
                },
        checkout(){
          var token = this.game.get("access_token") || this.game.fget("access_token");
          this.game.checkout(this,token,(new_access_token)=>{
            this.connect();
                        this.login();
                        this.user_package();
                        this.buy_prop();
                        this.unlock_package_number();
          });
        },
        connect(){
          // socket連接
          this.socket = io.connect(this.settings.socket_server + this.namespace, {transports: ['websocket']});
          this.socket.on('connect', ()=>{
              this.game.print("開始連接服務端");
                            var id = this.game.fget("id");
                            this.socket.emit("login",{"uid":id});
                            this.socket.emit("user_prop");
          });
        },
                login(){
                    this.socket.on("login_response",(message)=>{
                        this.settings_info.orchard = message.orchard_settings;
                        this.settings_info.user=message.user_settings;
                        this.game.fsave({
                            "orchard_settings":message.orchard_settings,
                            "user_settings":message.user_settings
                        });
                    });
                },
                user_package(){
                    // 用戶背包道具列表
                    this.socket.on("user_prop_response",(message)=>{
                        this.game.fsave({
                            "user_package":message.data,
                        })
                    })
                },
        go_index(){
          this.game.goWin("root");
        },
        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
          });
                },
                go_my_package(){
                    // 我的背包
                    this.game.goFrame("package","package.html", this.current,null,{
              type:"push",
              subType:"from_top",
              duration:300
          });
                },
                buy_prop(){
                    api.addEventListener({
                            name: 'buy_prop'
                    }, (ret, err)=>{
                            if( ret ){
                                // 用戶購買道具
                                this.socket.emit("user_buy_prop",ret.value);
                            }
                    });
                    this.socket.on("user_buy_prop_response",(message)=>{
                        alert(message.errmsg);
                    })
                },
                unlock_package_number(){
                    api.addEventListener({
                            name: 'unlock_package_number'
                    }, (ret, err)=>{
                            if( ret ){
                                // 用戶購買道具
                                this.socket.emit("unlock_package");
                            }
                    });

                    this.socket.on("unlock_package_response",(message)=>{
                        if(parseInt(message.errno) === 1000){
                            api.sendEvent({
                                name: 'unlock_package_success',
                                extra: {
                                }
                            });
                        }else{
                            api.alert({
                                    title: '提示',
                                    msg: message.errmsg,
                            });

                        }

                    })
                }
            }
        });
    }
    </script>
</body>
</html>

背包解鎖:orchard.html

package.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 add_friend package" id="app">
    <div class="box">
      <p class="title">我的背包</p>
      <img class="close" @click="close_frame" src="../static/images/close_btn1.png" alt="">
      <div class="prop_list">
        <div class="item" v-for="prop in user_package" @click="use_prop(prop.pid)">
            <img :src="settings.static_url+prop.image" alt="">
                    <span>{{prop.num}}</span>
        </div>
        <div class="item" v-for="number in unlock_td_number"></div>
        <div class="item lock" @click="unlock_package()" v-for="number in lock_td_number"></div>
      </div>
    </div>
    </div>
    <script>
    apiready = function(){
        init();
        new Vue({
            el:"#app",
            data(){
                return {
                    td: 36,      // 背包格子總數量
                    user_id: "", // 當前登陸用戶Id
                    orchard_settings:{}, // 種植園相關公共參數
                    user_settings:{}, // 用戶相關私有參數
                    user_package:[],  // 用戶背包資訊
                    prev:{name:"",url:"",params:{}},
                    current:{name:"package",url:"package.html",params:{}},
                }
            },
            computed:{// 計算屬性
                lock_td_number(){
                    // 未解鎖的格子
                    return parseInt(this.orchard_settings.package_number_max-this.user_settings.package_number);
                },
                unlock_td_number(){
                    // 解鎖的格子
                    return parseInt( this.user_settings.package_number - this.user_package.length);
                }
            },
            created(){
                this.user_id = this.game.get("id") || this.game.fget("id");
                this.orchard_settings = JSON.parse(this.game.fget("orchard_settings"));
                this.user_settings = JSON.parse(this.game.fget("user_settings"));
                this.user_package = JSON.parse(this.game.fget("user_package"));
            },
            methods:{
                use_prop(pid){
                    // 發起使用道具的通知
                },
                unlock_package(){
                    // 解鎖格子上限
                    api.confirm({
                        title: '提示',
                        msg: '解鎖背包上限',
                        buttons: ['確定', '取消']
                    }, (ret, err)=>{
                        if( ret.buttonIndex == 1 ){
                                api.sendEvent({
                                        name: 'unlock_package_number',
                                        extra: {}
                                });
                                
                                api.addEventListener({
                                    name: 'unlock_package_success'
                                }, (ret, err)=>{
                                    this.user_settings.package_number+=1;
                                });

                        }
                    });



                },
        close_frame(){
          this.game.outFrame("package");
        },
            }
        });
    }
    </script>
</body>
</html>

背包解鎖:package.html