zoukankan      html  css  js  c++  java
  • 13. 种植园模块

    宠物栏

    1. 宠物的显示
    2. 宠物的使用
    3. 宠物的饱食度
    4. 宠物栏的解锁[默认每个用户只有一个宠物栏, 需要2个的话,需要激活新的宠物栏]
    

    服务端提供显示宠物的api接口

    1. 宠物信息,在保存的时候,因为不同的用户,拥有的宠物数量不一样,所以数据结构不稳定的,而且要记录宠物总数的同时,还要记录用户的宠物详细信息,需要保存的数据结果是多层,这种情况下我们可以考虑数据保存在mongoDB。
    数据格式
    user_pet: {
        "user_id": 1,      # 当前用户ID
        "pet_position": 1,  # 当前宠物栏位数量
        "pet_1":{           # 1号栏位的宠物信息
            "start_time": "2021-03-31 14:00:00",
            "pet_image": "pet1.png",
            "pet_id": 1,
            "life_time": -1, # 生命周期,
            "hunger_num": 20,  # 饱食度: 饥饿状态
            'setiety_num': 80  # 饱食度: 饱食状态
            "hit_rare": 10,    # 保护作物的成功率
        },
        "pet_2":{},       # 2号栏位的宠物信息, {} 表示没有宠物
    }
    
    2. 基于上面的模拟数据格式在终端下创建到mongoDB中,直接编写api接口
    
    pet = {
    	"user_id" : 1,
    	"pet_position" : 2,
    	"pet_1": {
    		"start_time" : "2021-03-31 14:00:00",
    		"pet_image" : "pet2.png",
    		"pet_id" : 1,
    		"life_time" : -1,
    		"hunger_num" : 20,
    		"satiety_num" : 80,
    		"hit_rate" : 10
    	},
    	"pet_2": {
    		"start_time" : "2021-07-02 14:00:30",
    		"pet_image" : "pet2.png",
    		"pet_id" : 3,
    		"life_time" : -1,
    		"hunger_num" : 20,
    		"satiety_num" : 80,
    		"hit_rate" : 10
    	}
    }
    
    3. 把计算宠物实际饱食度的数据保存到redis中
       user:2:pet:1 用户ID=2,宠物1号宠物,通过ttl计算获取宠物存活
       user:2:pet:2 用户ID=2,宠物2号宠物,通过ttl计算获取宠物存活
    
    1. 创建用户宠物文档模型,orchard.documents,代码:
    # 用户宠物信息文档模型
    class UserPetDocument(mgdb.Document):
        '''用户宠物信息文档模型'''
        meta = {
            'collection': 'user_pet',   # 集合名称
            'ordering': ['_id'],        # 排序字段
            'strict': False,            # 是否严格语法
        }
        # 字段声明
        user_id = mgdb.IntField(required=True, unique=True, verbose_name='用户ID')
        pet_position = mgdb.IntField(required=True, verbose_name='用户宠物栏容量')
        pet_1 = mgdb.DictField(null=True, verbose_name='用户1号宠物栏的宠物信息')
        pet_2 = mgdb.DictField(null=True, verbose_name='用户2号宠物栏的宠物信息')
    
    
    1. 终端命令生成manage测试数据,application.utils.commands,代码:
    # 批量生成测试数据
    class FakerCommand(Command):
        """批量生成测试数据"""
        name = 'faker' # 生成命令名称
        # 传递的参数
        option_list = [
            Option('--type', '-t', dest='type', default='user'), # 指定生成数据的类型(用户数据,其他数据)
            Option('--num', '-n', dest='num', default=1), # 生成数据的数量
        ]
    
        # 以后想要生成数据类型列表
        type_list = ['user','seed','pet','pet_food','plant','user_pet']
    
        def __call__(self, app, type, num):
            # 判断想要生成数据的类型是否在类型列表中
            if type not in self.type_list:
                print("数据类型不正确
    当前Faker生成数据类型仅支持: %s" % self.type_list)
                return None
    
            num = int(num)
            if num < 1:
                print("生成数量不正确
    当前Faker生成数据至少1个以上")
                return None
    
            if type == 'user':
                # 生成用户测试数据
                self.create_user(app, num)
    
            elif type == 'seed':
                # 生成种子道具测试数据
                self.create_seed(app, num)
    
            elif type == 'pet':
                # 生成宠物道具测试数据
                self.create_pet(app, num)
    
            elif type == 'pet_food':
                # 生成宠物食物测试数据
                self.create_pet_food(app, num)
    
            elif type == 'plant':
                # 生成植物加速成长道具测试数据
                self.create_plant(app, num)
    
            elif type == 'user_pet':
                # 生成用户宠物测试数据(mongodb)
                self.create_user_pet(app, num)
    
    
    
        # 1. 生成指定数量的测试用户信息
        def create_user(self,app,num):
            """生成指定数量的测试用户信息"""
            from application.apps.users.models import User,UserProfile
            faker = app.faker
            faker.add_provider(internet)
    
            user_list = [] # 用户模型对象列表
            # 从配置文件中读取默认测试用户的登录密码
            password = app.config.get("DEFAULT_TEST_USER_PASSWORD", "12345678")
    
            for i in range(num):
                sex = bool(random.randint(0,2))
                nickname = faker.name()
                # 登录账号[随机字母,6-16位]
                name = faker.pystr(min_chars=6, max_chars=16)
                age = random.randint(13, 50)
    
                birthday = faker.date_time_between(start_date="-%sy" % age, end_date="-12y", tzinfo=None)
    
                hometown_province = faker.province()
                hometown_city = faker.city()
                hometown_area = faker.district()
    
                living_province = faker.province()
                living_city = faker.city()
                living_area = faker.district()
    
                user = User(
                    nickname=nickname,
                    sex=sex,
                    name=name,
                    age=age,
                    password=password,
                    pay_password=password,
                    money=random.randint(100, 99999),
                    credit=random.randint(100, 99999),
                    ip_address=faker.ipv4_public(),
                    email=faker.ascii_free_email(),
                    mobile=faker.phone_number(),
                    unique_id=faker.uuid4(),
                    province=faker.province(),
                    city=faker.city(),
                    area=faker.district(),
                    info=UserProfile(
                        birthday=birthday,
                        hometown_province=hometown_province,
                        hometown_city=hometown_city,
                        hometown_area=hometown_area,
                        hometown_address=hometown_province + hometown_city + hometown_area + faker.street_address(),
                        living_province=living_province,
                        living_city=living_city,
                        living_area=living_area,
                        living_address=living_province + living_city + living_area + faker.street_address()
                    )
    
                )
    
                user_list.append(user)
            # 存储数据
            with app.app_context():
                User.add_all(user_list)
            print("生成%s个用户信息完成..." % num)
    
        # 2. 生成指定数量的种子道具测试数据
        def create_seed(self, app, num):
            """生成指定数量的种子道具测试数据"""
            from application.apps.orchard.models import SeedItem
    
            seed_list = []  # 模型对象列表
            for i in range(num):
                seed = SeedItem(
                    name = f'苹果{i}号',
                    price = random.randint(1, 20),
                    credit = random.randint(100, 1000),
                    image = 'fruit_tree.png',
                    remark = '果实饱满香甜',
                )
                seed_list.append(seed)
            # 存储数据
            with app.app_context():
                SeedItem.add_all(seed_list)
            print("生成%s个种子道具信息完成..." % num)
    
        # 3. 生成指定数量的宠物道具测试数据
        def create_pet(self, app, num):
            """生成指定数量的宠物道具测试数据"""
            from application.apps.orchard.models import PetItem
    
            pet_list = []  # 模型对象列表
            pet_name_list = ["中华田园犬", "贵宾犬", "哮天犬"]
            pet_img_list = ["pet1.png", "pet2.png", "pet3.png"]
            for i in range(num):
                name = random.choice(pet_name_list)
                if name == '中华田园犬':
                    image = pet_img_list[0]
                elif name == '贵宾犬':
                    image = pet_img_list[1]
                elif name == '哮天犬':
                    image = pet_img_list[2]
                pet = PetItem(
                    name = name,
                    price = random.randint(1, 20),
                    credit = random.randint(100, 1000),
                    remark = '看家护院好帮手',
                    image = image,
                )
                pet_list.append(pet)
            # 存储数据
            with app.app_context():
                PetItem.add_all(pet_list)
            print("生成%s个宠物道具信息完成..." % num)
    
        # 4. 生成指定数量的宠物食物道具测试数据
        def create_pet_food(self, app, num):
            """生成指定数量的宠物食物道具测试数据"""
            from application.apps.orchard.models import PetFoodItem
    
            pet_food_list = []  # 模型对象列表
            for i in range(num):
                pet_food = PetFoodItem(
                    name = f'牛奶{i}号',
                    price = random.randint(1, 20),
                    credit = random.randint(100, 1000),
                    remark = '补充体力',
                    image = 'prop4.png',
                )
                pet_food_list.append(pet_food)
            # 存储数据
            with app.app_context():
                PetFoodItem.add_all(pet_food_list)
            print("生成%s个宠物食物道具信息完成..." % num)
    
    
        # 5. 生成指定数量的植物成长加速道具测试数据
        def create_plant(self, app, num):
            """生成指定数量的植物成长加速道具测试数据"""
            from application.apps.orchard.models import PlantItem
    
            plant_list = []  # 模型对象列表
            for i in range(num):
                plant = PlantItem(
                    name = f'化肥{i}号',
                    price = random.randint(1, 20),
                    credit = random.randint(100, 1000),
                    remark = '补充体力',
                    image = 'prop1.png',
                )
                plant_list.append(plant)
            # 存储数据
            with app.app_context():
                PlantItem.add_all(plant_list)
            print("生成%s个植物加速成长道具信息完成..." % num)
    
        # 6.生成用户宠物测试数据(mongodb)
        def create_user_pet(self,app, num):
            # 引入用户宠物文档模型
            from application.apps.orchard.documents import UserPetDocument
            # 用户宠物信息
            pet = {
                "user_id": 1,
                "pet_position": 2,
                "pet_1": {
                    "start_time": "2021-03-31 14:00:00",
                    "pet_image": "pet1.png",
                    "pet_id": 1,
                    "life_time": -1,
                    "hunger_num": 20,
                    "satiety_num": 80,
                    "hit_rate": 10
                },
                "pet_2": {
                    "start_time": "2021-07-02 14:00:30",
                    "pet_image": "pet2.png",
                    "pet_id": 3,
                    "life_time": -1,
                    "hunger_num": 20,
                    "satiety_num": 80,
                    "hit_rate": 10
                }
            }
            # 添加数据
            UserPetDocument.objects.create(**pet)
            print("生成%s个用户宠物信息完成..." % num)
    

    终端命令生成用户宠物数据,

    python manage.py faker -tuser_pet
    

    Redis中记录用户宠物存活时间的数据。

    redis-cli
    # 选择种植园存储库
    select 3
    # 用户宠物1存活时间
    setex user:1:pet:1 1800 _
    # 用户宠物2存活时间
    setex user:1:pet:2 1800 _
    
    1. 服务端响应请求用户宠物信息数据 orchard.socket,代码:
    # 获取用户宠物信息数据
    def on_user_pet_info(self):
        '''获取用户宠物信息数据'''
        # 1.根据回话sid获取用户模型对象(检查回话状态)
        user = user_services.get_user_by_sid(request.sid)
        # 判断用户是否存在
        if user is None:
            # 响应数据
            emit('user_pet_info_response',{
                'errno':code.CODE_USER_NOT_EXISTS,
                'errmsg': message.user_not_exists,
            })
            # 停止程序继续运行
            return
    
        # 获取用户宠物信息
        user_pet_info = services.get_user_pet_info(user.id)
    
        emit('user_pet_info_response',{
            'errno': code.CODE_OK,
            'errmsg': message.ok,
            'user_pet_info': user_pet_info
        })
    
    1. 用户宠物模型构造器 orchard/marshmallow.py
    # 用户宠物信息构造器
    class UserPetSchema(Schema):
        '''用户宠物信息构造器'''
        user_id = fields.Integer()
        pet_position = fields.Integer()
        pet_1 = fields.Dict()
        pet_2 = fields.Dict()
    
    1. 数据服务层,orchard/services.py,代码:
    # 初始化用户宠物信息
    def init_user_pet_info(user_id):
        user_pet_info = UserPetDocument.objects.create(
            user_id = user_id,
            pet_position = 1,
            pet_1 = {},
            pet_2 = {}
        )
        return user_pet_info
    
    # 获取用户宠物信息
    def get_user_pet_info(user_id):
        '''
        获取用户宠物信息
        :param user_id: 用户ID
        :return:
        '''
        # 1.先从mongoDB中提取用户宠物信息
        user_pet_info = UserPetDocument.objects.filter(UserPetDocument.user_id == user_id).first()
        # 判断
        if user_pet_info is None:
            # 用户宠物信息不存在,初始化用户宠物信息
            user_pet_info = init_user_pet_info(user_id)
    
        # 2.使用构造器进行数据序列化
        from .marshmallow import UserPetSchema
        ups = UserPetSchema()
        pet_data = ups.dump(user_pet_info)
    
        # 3.从redis中提取用户宠物1的剩余生命值[剩余时间]
        # 剩余时间没用了,宠物挂了,返回值就是-2
        pet_data['pet_1']['has_time'] = redis_orchard.ttl(f'user:{user_id}:pet:1')
        # 宠物的最大存活时间
        pet_data['pet_1']['max_time'] = config.PET_MAX_TIME
        if pet_data['pet_position'] == 2:
            # 用户激活了2号宠物位置,则有可能有2号宠物
            pet_data['pet_2']['has_time'] = redis_orchard.ttl(f'user:{user_id}:pet:2')
            # 宠物的最大存活时间
            pet_data['pet_2']['max_time'] = config.PET_MAX_TIME
    
        # 4.返回用户宠物信息
        return pet_data
    
    
    1. 用户宠物存活时间配置信息 applicaiton.settings.plant,代码:
    # 宠物不喂养的情况下最大存活时间
    PET_MAX_TIME = 1 * 24 * 3600
    
    

    客户端展示宠物栏和宠物信息

    1. 种植园页面结束获取用户宠物信息通知, 获取成功后并发布广播, 使其他页面接收, 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/uuid.js"></script>
      <script src="../static/js/socket.io.js"></script>
    	<script src="../static/js/v-avatar-2.0.3.min.js"></script>
    	<script src="../static/js/main.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="back" src="../static/images/user_back.png" alt="">
        <div class="header">
    			<div class="info">
    				<div class="avatar" @click='to_user'>
    					<img class="avatar_bf" src="../static/images/avatar_bf.png" alt="">
    					<div class="user_avatar">
    						<v-avatar v-if="user_data.avatar" :src="user_data.avatar" :size="62" :rounded="true"></v-avatar>
    						<v-avatar v-else-if="user_data.nickname" :username="user_data.nickname" :size="62" :rounded="true"></v-avatar>
    						<v-avatar v-else :username="user_data.id" :size="62" :rounded="true"></v-avatar>
    					</div>
    					<img class="avatar_border" src="../static/images/avatar_border.png" alt="">
    				</div>
    				<p class="user_name">{{user_data.nickname}}</p>
    			</div>
    			<div class="wallet" @click='user_recharge'>
    				<div class="balance">
    					<p class="title"><img src="../static/images/money.png" alt="">钱包</p>
    					<p class="num">{{game.number_format(user_data.money)}}</p>
    				</div>
    				<div class="balance">
    					<p class="title"><img src="../static/images/integral.png" alt="">果子</p>
    					<p class="num">{{game.number_format(user_data.credit)}}</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">
              <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='to_package'>背包</li>
            <li class="menu-center" @click="to_shop">商店</li>
            <li class="menu">消息</li>
            <li class="menu">好友</li>
          </ul>
        </div>
    	</div>
    	<script>
    	apiready = function(){
    		Vue.prototype.game = new Game("../static/mp3/bg1.mp3");
    		new Vue({
    			el:"#app",
    			data(){
    				return {
    					user_data:{},  // 当前用户信息
              music_play:true,
              namespace: '/orchard', // websocket命名空间
              socket: null, // websocket连接对象
    					recharge_list: [], // 允许充值的金额列表
    					package_init_setting: {}, // 背包初始配置信息
    					user_info: {}, // 用户登陆初始化化信息
    					user_package_info: {}, // 用户背包信息
    					user_pet_info: {}, // 用户宠物信息
    				}
    			},
          created(){
    				// socket建立连接
    				this.socket_connect();
            // 自动加载我的果园页面
    				this.to_my_orchard();
    				// 获取用户数据
    				this.get_user_data()
    				// 监听事件变化
    				this.listen()
    				// 获取充值金额列表
    				this.get_recharge_list()
          },
    			methods:{
    				back(){
    					this.game.openWin("root");
    				},
    				// 监听事件
    				listen(){
    					// 1.监听token更新的通知
    					this.listen_update_token();
    					// 2.监听是否购买道具成功的通知
    					this.listen_buy_prop_success();
    					// 3.监听是否使用道具成功的通知
    					// this.listen_use_prop_success();
    					// 4.监听解锁背包容量的通知
    					this.listen_unlock_package();
    					// 5.监听是否用其他页面需要获取用户宠物信息
    					this.listen_get_user_pet_info();
    				},
    
    				// 1.监听token更新的通知
    				listen_update_token(){
    					api.addEventListener({
    							name: 'update_token'
    					}, (ret, err) => {
    						// 更新用户数据
    							this.get_user_data()
    					});
    				},
    
    				// 2.监听是否购买道具成功的通知
    				listen_buy_prop_success(){
    					api.addEventListener({
    					    name: 'buy_prop_success'
    					}, (ret, err)=>{
    							// 发送请求,更新用户背包数据
    							this.socket.emit('user_package');
    					});
    				},
    
    				// 3.监听解锁背包容量的通知
    				listen_unlock_package(){
    					api.addEventListener({
    							name: 'unlock_package'
    					}, (ret, err)=>{
    						// 发送解锁背包的请求
    						this.socket.emit('unlock_package');
    						// 获取解锁背包响应数据
    						this.socket.on('unlock_package_response',(response)=>{
    							if(response.errno === 1000){
    								// 更新用户背包数据
    								this.socket.emit('user_package')
    								// 用户积分改变,重新刷新token值
    								this.refresh_user_token();
    							}
    						})
    					});
    				},
    
    				// 5.监听是否用其他页面需要获取用户宠物信息
    				listen_get_user_pet_info(){
    					api.addEventListener({
    					    name: 'get_user_pet_info'
    					}, (ret, err)=>{
    						 // 获取用户宠物信息
    						 this.get_user_pet_info();
    					});
    				},
    
    				// 获取用户宠物信息
    				get_user_pet_info(){
    					// 发起请求用户宠物信息
    					this.socket.emit('user_pet_info');
    					// 获取响应
    					this.socket.on('user_pet_info_response', (response)=>{
    						if(response.errno === 1000){
    							// 响应成功,获取宠物信息
    							this.user_pet_info = response.user_pet_info
    							// 通知其他页面,已经获取用户宠物信息
    							this.game.sendEvent('get_user_pet_info_success',{
    								user_pet_info: this.user_pet_info
    							})
    						}else {
    							this.game.tips(response.errmsg)
    						}
    					})
    				},
    
    				// 用户积分改变,重新刷新token值
    				refresh_user_token(){
    					let self = this
    					self.game.check_user_login(self, ()=>{
    						let token = self.game.getdata('refresh_token') || self.game.getfs('refresh_token')
    						self.game.post(self,{
    							'method': 'Users.refresh',
    							'params':{},
    							'header':{
    								'Authorization': 'jwt ' + token
    							},
    							success(response){
    								let data = response.data;
    								if(data.result && data.result.errno === 1000){
    									// 刷新token值
    									let token = self.game.getfs("refresh_token");
    	                if(token){
    	                    // 记住密码的情况
    	                    self.game.deldata(["access_token","refresh_token"]);
    	                    self.game.setfs({
    	                        "access_token": data.result.access_token,
    	                        "refresh_token": data.result.refresh_token,
    	                    });
    	                }else{
    	                    // 不记住密码的情况
    	                    self.game.delfs(["access_token","refresh_token"]);
    	                    self.game.setdata({
    	                        "access_token": data.result.access_token,
    	                        "refresh_token": data.result.refresh_token,
    	                    });
    	                }
    									// 发布全局广播,token更新
    									self.game.sendEvent('update_token')
    								}
    							}
    						})
    					})
    				},
    
    
    				// 通过token值获取用户数据
    				get_user_data(){
    					let self = this;
    					// 检查token是否过期,过期从新刷新token
    					self.game.check_user_login(self, ()=>{
    						// 获取token
    						let token = this.game.getfs('access_token') || this.game.getdata('access_token')
    						// 根据token获取用户数据
    						this.user_data = this.game.get_user_by_token(token)
    					})
    				},
    
    				// websocket连接处理
            socket_connect(){
              this.socket = io.connect(this.game.config.SOCKET_SERVER + this.namespace, {transports: ['websocket']});
              this.socket.on('connect', ()=>{
                  this.game.print("开始连接服务端");
    							// 获取背包初始配置信息
    							this.get_package_setting()
    							// websocket登陆处理
    							this.user_websocket_login()
              });
            },
    
    				// 获取背包初始配置信息
    				get_package_setting(){
    					this.socket.on('init_config_response', (response)=>{
    						// this.game.print(response,1)
    						this.package_init_setting = response
    					})
    				},
    
    				// websocket登陆处理
    				user_websocket_login(){
    					// 客户端发送用户登陆请求
    					this.socket.emit('login',{'uid': this.user_data.unique_id});
    					// 接收登陆响应
    					this.login_response();
    					// 接收用户背包响应
    					this.user_package_response();
    				},
    
    				// 接收登陆初始化信息
    				login_response(){
    					this.socket.on('login_response',(response)=>{
    						// this.game.print(response,1)
    						if(response.errno === 1000){
    							this.user_info = response.user_info
    						}
    					})
    				},
    
    				// 接收用户背包信息
    				user_package_response(){
    					this.socket.on('user_package_response',(response)=>{
    						// this.game.print(response,1)
    						if(response.errno === 1000){
    							this.user_package_info = response.package_info;
    							// 全局广播用户背包更新
    							this.game.sendEvent('package_update',{
    								user_package_info: this.user_package_info
    							})
    						}else {
    							this.game.tips(response.errmsg)
    						}
    					})
    				},
    
    
            // 跳转我的果园页面
            to_my_orchard(){
              this.game.check_user_login(this, ()=>{
                this.game.openFrame("my_orchard","my_orchard.html","from_right",{
        					marginTop: 174,     //相对父页面上外边距的距离,数字类型
        					marginLeft: 0,      //相对父页面左外边距的距离,数字类型
        			    marginBottom: 54,     //相对父页面下外边距的距离,数字类型
        			    marginRight: 0     //相对父页面右外边距的距离,数字类型
        				});
              })
            },
    
            // 点击商店打开道具商城页面
    				to_shop(){
              this.game.check_user_login(this, ()=>{
                this.game.openFrame('shop', 'shop.html', 'from_top');
              })
            },
    
    				// 点击头像,跳转用户中心页面
    				to_user(){
    					this.game.check_user_login(this, ()=>{
                this.game.openFrame('user', 'user.html', 'from_right');
              })
    				},
    
    				// 获取充值金额列表
    				get_recharge_list(){
    					let self = this;
    					self.game.check_user_login(self,()=>{
    						self.game.post(self,{
    							'method': 'Users.recharge_list',
    							'params': {},
    							success(response){
    								let data = response.data;
    								if(data.result && data.result.errno === 1000){
    									self.recharge_list = data.result.recharge_list
    								}
    							}
    						})
    					})
    				},
    
    				// 点击钱包进行用户充值,设置充值金额
    				user_recharge(){
    					api.actionSheet({
    								title: '余额充值',
    								cancelTitle: '取消',
    								buttons: this.recharge_list
    						}, (ret, err)=>{
    								if( ret.buttonIndex <= this.recharge_list.length ){
    										 // 充值金额
    										 let money = this.recharge_list[ret.buttonIndex-1];
    										//  this.game.print(money,1);
    										// 发送支付宝充值请求
    										this.recharge_app_pay(money)
    								}
    					});
    				},
    
    				// 发送支付宝充值请求
    				recharge_app_pay(money){
    					// 获取支付宝支付对象
    					let aliPayPlus = api.require("aliPayPlus");
    					let self = this;
    					// 向服务端发送请求获取终止订单信息
    					self.game.check_user_login(self, ()=>{
    						let token = this.game.getdata("access_token") || this.game.getfs("access_token");
    						self.game.post(self,{
    							'method': 'Users.recharge',
    							'params':{
    								'money': money,
    							},
    							'header': {
    								'Authorization': 'jwt ' + token
    							},
    							success(response){
    								let data = response.data;
    								if(data.result && data.result.errno === 1000){
    									// 本次充值的订单参数
    									let order_string = data.result.order_string;
    
    									// 支付完成以后,支付APP返回的响应状态码
    									let resultCode = {
    										"9000": "支付成功!",
    										"8000": "支付正在处理中,请稍候!",
    										"4000": "支付失败!请联系我们的工作人员~",
    										"5000": "支付失败,重复的支付操作",
    										"6002": "网络连接出错",
    										"6004": "支付正在处理中,请稍后",
    									}
    									// 唤醒支付宝APP,发起支付
    									aliPayPlus.payOrder({
    										orderInfo: order_string,
    										sandbox: data.result.sandbox, // 将来APP上线需要修改成false
    									},(ret, err)=>{
    										if(resultCode[ret.code]){
    											// 提示支付结果
    											if(ret.code != 9000){
    												self.game.tips(resultCode[ret.code]);
    											}else {
    												// 支付成功,向服务端请求验证支付结果 - 参数订单号
    												self.check_recharge_result(data.result.order_number);
    											}
    										}
    									})
    								}
    							}
    						})
    					})
    				},
    
    				// 向服务端发送请求,校验充值是否成功
    				check_recharge_result(order_number){
    					let self = this;
    					self.game.check_user_login(self, ()=>{
    						let token = this.game.getdata("access_token") || this.game.getfs("access_token");
    						self.game.post(self,{
    							'method': 'Users.check_recharge_result',
    							'params':{
    								'order_number':order_number,
    							},
    							'header':{
    								'Authorization': 'jwt ' + token
    							},
    							success(response){
    								let data = response.data;
    								if(data.result && data.result.errno === 1000){
    									// 充值成功
    									self.game.tips('充值成功!')
    									// 用户数据更改过,重新刷新token值
    									let token = self.game.getfs("access_token");
    									// 删除token值
    									self.game.deldata(["access_token","refresh_token"]);
    									self.game.delfs(["access_token","refresh_token"]);
    									if(token){
                        // 记住密码的情况
                        self.game.setfs({
                          "access_token": data.result.access_token,
                          "refresh_token": data.result.refresh_token,
                        });
                      }else{
                        // 不记住密码的情况
                        self.game.setdata({
                          "access_token": data.result.access_token,
                          "refresh_token": data.result.refresh_token,
                        });
    									}
    									// 全局广播充值成功
    									self.game.sendEvent('recharge_success')
    									// 全局广播刷新token值
    									self.game.sendEvent('update_token')
    								}
    							}
    						})
    					})
    				},
    
    				// 点击背包,跳转到背包页面,并传递背包初始配置信息
    				to_package(){
    					this.game.check_user_login(this, ()=>{
    						this.game.openFrame('package', 'package.html', 'from_top',null, {
    							'package_init_setting': this.package_init_setting,
    							'user_package_info': this.user_package_info,
    							'user_info': this.user_info
    						})
    					})
    				},
    
    
    			}
    		});
    	}
    	</script>
    </body>
    </html>
    
    
    
    1. 我的种植园页面, 发起获取宠物信息广播, 监听宠物信息获取成功的通知, html/my_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/uuid.js"></script>
    	<script src="../static/js/socket.io.js"></script>
      <script src="../static/js/main.js"></script>
    
    </head>
    <body>
    	<div class="app orchard orchard-frame" id="app">
        <div class="background">
          <img class="grassland2" src="../static/images/grassland2.png" alt="">
          <img class="mushroom1" src="../static/images/mushroom1.png" alt="">
          <img class="stake1" src="../static/images/stake1.png" alt="">
          <img class="stake2" src="../static/images/stake2.png" alt="">
        </div>
        <div class="pet-box">
          <div class="pet">
            <img class="pet-item" v-if='user_pet_info.pet_1.has_time > 0' :src="`../static/images/${user_pet_info.pet_1.pet_image}`" alt="">
          </div>
          <div class="pet turned_off" v-if='user_pet_info.pet_position < 2'>
    				<img class="turned_image" src="../static/images/turned_off.png" alt="">
            <p>请购买宠物</p>
          </div>
    			<div class="pet" v-else>
            <img class="pet-item" v-if='user_pet_info.pet_2.has_time > 0' :src="`../static/images/${user_pet_info.pet_2.pet_image}`" alt="">
          </div>
        </div>
        <div class="tree-list">
          <div class="tree-box">
            <div class="tree">
              <img src="../static/images/tree4.png" alt="">
            </div>
            <div class="tree">
              <img src="../static/images/tree3.png" alt="">
            </div>
            <div class="tree">
              <img src="../static/images/tree4.png" alt="">
            </div>
          </div>
          <div class="tree-box">
            <div class="tree">
              <img src="../static/images/tree3.png" alt="">
            </div>
            <div class="tree">
              <img src="../static/images/tree2.png" alt="">
            </div>
            <div class="tree">
              <img src="../static/images/tree2.png" alt="">
            </div>
          </div>
          <div class="tree-box">
            <div class="tree">
              <img src="../static/images/tree1.png" alt="">
            </div>
            <div class="tree">
              <img src="../static/images/tree0.png" alt="">
            </div>
            <div class="tree">
              <img src="../static/images/tree0.png" alt="">
            </div>
          </div>
        </div>
        <div class="prop-list">
          <div class="prop">
            <img src="../static/images/prop1.png" alt="">
            <span>1</span>
            <p>化肥</p>
          </div>
          <div class="prop">
            <img src="../static/images/prop2.png" alt="">
            <span>0</span>
            <p>修剪</p>
          </div>
          <div class="prop">
            <img src="../static/images/prop3.png" alt="">
            <span>1</span>
            <p>浇水</p>
          </div>
          <div class="prop">
            <img src="../static/images/prop4.png" alt="">
            <span>1</span>
            <p>宠物粮</p>
          </div>
        </div>
        <div class="pet-hp-list">
          <div class="pet-hp" v-if='user_pet_info.pet_1.has_time > 0'>
            <p>宠物1 饱食度</p>
            <div class="hp">
              <div :style="{ pet_1_hp+'%'}" class="process">{{pet_1_hp}}%</div>
            </div>
          </div>
          <div class="pet-hp" v-if='user_pet_info.pet_2.has_time > 0'>
            <p>宠物2 饱食度</p>
            <div class="hp">
              <div :style="{ pet_2_hp+'%'}" class="process">{{pet_2_hp}}%</div>
            </div>
          </div>
        </div>
    	</div>
    	<script>
    	apiready = function(){
    		Vue.prototype.game = new Game("../static/mp3/bg1.mp3");
    		new Vue({
    			el:"#app",
    			data(){
    				return {
    					user_pet_info:{ // 用户宠物信息
    						pet_1:{},			// 防止调用时报错
    						pet_2:{},
    					}
    				}
    			},
    			// 计算数据
    			computed:{
    				// 计算用户宠物血量百分比
    				pet_1_hp(){
    					persent = ((this.user_pet_info.pet_1.has_time / this.user_pet_info.pet_1.max_time)*100).toFixed(2)
    					return persent
    				},
    				pet_2_hp(){
    					persent = ((this.user_pet_info.pet_2.has_time / this.user_pet_info.pet_2.max_time)*100).toFixed(2)
    					return persent
    				},
    			},
          created(){
    				// 监听事件
    				this.listen();
          },
    			methods:{
    				// 监听事件
    				listen(){
    					// 监听获取用户宠物信息成功的通知
    					this.listen_get_user_pet_info_success();
    				},
    
    				// 监听获取用户宠物信息成功的通知
    				listen_get_user_pet_info_success(){
    					let self = this
    					// 发起获取用户宠物信息的全局通知
    					self.game.sendEvent('get_user_pet_info');
    					// 监听获取用户宠物信息成功的通知
    					api.addEventListener({
    					    name: 'get_user_pet_info_success'
    					}, (ret, err)=>{
    						// 获取用户宠物信息
    						self.user_pet_info = ret.value.user_pet_info;
    						self.game.print(self.user_pet_info)
    						// 设置定时器,宠物存活时间需要递减
    						setInterval(()=>{
    								self.user_pet_info.pet_1.has_time--;
    								self.user_pet_info.pet_2.has_time--;
    							},1000);
    					});
    
    				},
    
    			}
    		});
    	}
    	</script>
    </body>
    </html>
    
    
    

    socket会话状态装饰器

    把对socket状态的判断代码进行封装成类方法的装饰器。

    1. 装饰器函数封装utils.decorator,代码:
    from flask_jwt_extended import get_jwt_identity
    from functools import wraps
    from flask import request
    
    from application.apps.users import services
    from application.utils import code, message
    
    # 根据token获取用户模型对象,装饰器
    def get_user_object(func):
        """获取用户模型对象"""
        def inner(*args, **kwargs):
            user_data = get_jwt_identity()
            user = services.get_user_by_id(id=user_data['id'])
            # 判断用户是否存在
            if user is None:
                return {
                    'errno': code.CODE_AUTOORIZATION_ERROR,
                    'errmsg': message.authorization_is_invalid
                }
            return func(user, *args, **kwargs)
        return inner
    
    # 根据websocket回话sid获取用户模型对象,装饰器
    # 要使用装饰器对类方法进行装饰,必须要考虑self参数的传递
    # functools模块中提供了一个wraps装饰器,这个装饰器的作用可以帮我们把类方法转换成偏函数
    def get_user_object_by_socket_sid(func):
        @wraps(func)
        def inner(self, *args, **kwargs):
            # 获取用户模型对象,检查回话id
            user = services.get_user_by_sid(request.sid)
            # 检查回话状态,在装饰的函数上判断
    
            return func(self, user, *args, **kwargs)
        return inner
    
    
    
    1. websocket会话视图方法加装装饰器 orchard.socket,代码:
    from flask_socketio import emit, Namespace, join_room
    from flask import request
    
    from application import code, message
    from application.apps.users import services as user_services
    from application.utils.decorator import get_user_object_by_socket_sid
    from . import services
    
    """基于类视图接口"""
    
    # 种植园模块命名空间
    class OrchardNamespace(Namespace):
        '''种植园模块命名空间'''
        # socket链接
        def on_connect(self):
            print("用户[%s]进入了种植园!" % request.sid)
            # 返回初始化信息[不涉及当前的用户,因为我们现在不知道当前用户是谁]
            self.init_config()
    
        def init_config(self, ):
            """系统基本信息初始化"""
            # 返回背包初始配置信息
            package_settings = services.get_package_settings()
            emit("init_config_response", package_settings)
    
        # socket断开连接
        def on_disconnect(self):
            print("用户[%s]退出了种植园!" % request.sid)
    
        # 接收登录信息
        def on_login(self, data):
            # 加入房间
            self.on_join_room(data['uid']) # 接受客户端发送过来的用户 unique id
    
            # 1.用户websocket登录处理,获取用户初始信息(uid,sid,背包初始容量)
            # request.sid websocket链接客户端的会话ID
            user_info = user_services.user_websocket_login(data['uid'],request.sid)
    
            # todo 返回种植园的相关配置参数[种植果树的数量上限]
    
            msg = {
                'errno': code.CODE_OK,
                'errmsg': message.ok,
                'user_info': user_info
            }
            # 响应登录信息
            emit("login_response", msg)
    
            # 2.用户登录获取用户背包信息
            self.on_user_package()
    
        # 房间分发
        def on_join_room(self, room):
            '''加入房间'''
            # 默认用户自己一个人一个房间,也就是一个单独的频道
            join_room(room)
    
        # 获取用户背包信息
        @get_user_object_by_socket_sid
        def on_user_package(self, user):
            '''获取用户背包信息'''
            # 1.判断用户是否存在(检查回话状态)
            if user is None:
                # 响应数据
                emit('user_package_response',{
                    'errno':code.CODE_USER_NOT_EXISTS,
                    'errmsg': message.user_not_exists,
                })
                # 停止程序继续运行
                return
    
            # 2.根据用户id获取背包信息
            package_info = user_services.get_package_info_by_id(user.id)
            # print("package_info =", package_info)
            # 响应数据
            emit('user_package_response',{
                'errno': code.CODE_OK,
                'errmsg': message.ok,
                'package_info': package_info
            })
    
        # 解锁背包格子
        @get_user_object_by_socket_sid
        def on_unlock_package(self, user):
            '''解锁背包格子'''
            # 1.判断用户是否存在(检查回话状态)
            if user is None:
                # 响应数据
                emit('unlock_package_response', {
                    'errno': code.CODE_USER_NOT_EXISTS,
                    'errmsg': message.user_not_exists,
                })
                # 停止程序继续运行
                return
    
            # 根据回话id获取websocket用户信息
            user_info = user_services.get_user_info_by_sid(request.sid)
    
            # 2.根据用户id获取背包信息
            package_info = user_services.get_package_info_by_id(user.id)
    
            # 3.获取用户背包初始配置信息
            package_settings = services.get_package_settings()
    
            # 4.判断用户是否满足激活背包格子的条件
            # 4.1 判断用户背包格子使用情况
            if package_info['capacity'] >= package_settings['max_package_capacity']:
                # 背包空间不足
                emit('unlock_package_response',{
                    'errno': code.CODE_PACKAGE_SPACE_NOT_ENOUGH,
                    'errmsg': message.package_space_not_enough
                })
                return
    
            # 4.2 # 是否有足够的积分可以激活[获取剩余可激活空间,查看本次第几次激活,剩余激活的次数]
            # 4.2.1 获取已经激活的次数?(现有格子 - 注册时初始数量)/ 每次激活数量 = 已激活次数
            # print('现有格子:', package_info['capacity'])
            # print('注册时初始数量:', user_info.package_number)
            # print('每次激活数量:', package_info['capacity'])
            this_time = (package_info['capacity'] - user_info.package_number) // package_settings['unlock_package_item']
            # 4.2.2 获取本次激活所需要的积分数量
            this_time_credit = package_settings['unlock_package'][this_time]
            # 4.2.3 判断用户积分是否足够
            if user.credit < this_time_credit:
                # 积分不足
                emit('unlock_package_response', {
                    'errno': code.CODE_NOT_ENOUGH_CREDIT,
                    'errmsg': message.not_enough_credit
                })
                return
    
            # 解锁背包格子 - 参数(用户对象, 解锁格子数量,需要的积分)
            user_services.unlock_package_num(user, package_settings["unlock_package_item"], this_time_credit)
    
            emit('unlock_package_response', {
                'errno': code.CODE_OK,
                'errmsg': message.ok
            })
    
        # 获取用户宠物信息数据
        @get_user_object_by_socket_sid
        def on_user_pet_info(self, user):
            '''获取用户宠物信息数据'''
            # 1.判断用户是否存在(检查回话状态)
            if user is None:
                # 响应数据
                emit('user_pet_info_response',{
                    'errno':code.CODE_USER_NOT_EXISTS,
                    'errmsg': message.user_not_exists,
                })
                # 停止程序继续运行
                return
    
            # 获取用户宠物信息
            user_pet_info = services.get_user_pet_info(user.id)
    
            emit('user_pet_info_response',{
                'errno': code.CODE_OK,
                'errmsg': message.ok,
                'user_pet_info': user_pet_info
            })
    
    
    
    

    宠物死亡处理

    服务端

    1. 宠物死亡, 更新用户宠物信息为空 orchard.socket,代码:
        # 用户宠物死亡处理
        @get_user_object_by_socket_sid
        def on_user_pet_die(self, user, data):
            """
            宠物死亡处理
            :param user: 根据回话ID获取的用户模型对象
            :param data: 客户端发送过来的宠物相关信息
            :return:
            """
            # 1.判断用户是否存在(检查回话状态)
            if user is None:
                # 响应数据
                emit('user_pet_die_response', {
                    'errno': code.CODE_USER_NOT_EXISTS,
                    'errmsg': message.user_not_exists,
                })
                # 停止程序继续运行
                return
    
            # 2.获取死亡宠物所在的宠物栏位置
            pet_position = data.get('pet_position')
            # 3.到redis提取当前对应的宠物剩余时间
            pet_ttl = services.get_pet_ttl(user.id, pet_position)
            # 判断宠物是否已经死了
            if pet_ttl <= 1:
                # 宠物已死,进行死亡处理,更改用户宠物信息
                services.user_pet_die(user.id, pet_position)
    
                emit('user_pet_die_response', {
                    'errno': code.CODE_OK,
                    'errmsg': message.ok,
                })
                # 停止程序继续运行
                return
    
            emit('user_pet_die_response', {
                'errno': code.CODE_PET_NOT_DIE,
                'errmsg': message.pet_not_die,
            })
    
    
    1. 数据服务层,orchard.services,代码:
    # 获取宠物的剩余时间
    def get_pet_ttl(user_id, pet_position):
        '''
        获取宠物的剩余时间
        :param user_id: 用户ID
        :param pet_position: 宠物对应的宠物栏位置
        :return:
        '''
        pet_ttl = redis_orchard.ttl(f'user:{user_id}:pet:{pet_position}')
        return pet_ttl
    
    # 用户宠物死亡处理
    def user_pet_die(user_id, pet_position):
        '''
        用户宠物死亡处理
        :param user_id: 用户ID
        :param pet_position: 宠物对应的宠物栏位置
        :return:
        '''
        # 获取用户宠物信息对象
        document = UserPetDocument.objects.get(user_id = user_id)
        # 更改死亡宠物信息为空
        updater = {}
        updater[f'pet_{pet_position}'] = {}
        document.update(**updater)
    
    
    
    1. 状态提示码与提示信息

    提示码 application/utils/code.py

    CODE_PET_NOT_DIE = 1106         # 宠物没有死亡
    
    

    提示信息 application/utils/message.py

    pet_not_die = '宠物没有死亡'
    
    

    客户端

    1. 我的种植园页面,发送宠物死亡的通知 html/my_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/uuid.js"></script>
    	<script src="../static/js/socket.io.js"></script>
      <script src="../static/js/main.js"></script>
    
    </head>
    <body>
    	<div class="app orchard orchard-frame" id="app">
        <div class="background">
          <img class="grassland2" src="../static/images/grassland2.png" alt="">
          <img class="mushroom1" src="../static/images/mushroom1.png" alt="">
          <img class="stake1" src="../static/images/stake1.png" alt="">
          <img class="stake2" src="../static/images/stake2.png" alt="">
        </div>
        <div class="pet-box">
          <div class="pet">
            <img class="pet-item" v-if='user_pet_info.pet_1.has_time > 0' :src="`../static/images/${user_pet_info.pet_1.pet_image}`" alt="">
          </div>
          <div class="pet turned_off" v-if='user_pet_info.pet_position < 2'>
    				<img class="turned_image" src="../static/images/turned_off.png" alt="">
            <p>请购买宠物</p>
          </div>
    			<div class="pet" v-else>
            <img class="pet-item" v-if='user_pet_info.pet_2.has_time > 0' :src="`../static/images/${user_pet_info.pet_2.pet_image}`" alt="">
          </div>
        </div>
        <div class="tree-list">
          <div class="tree-box">
            <div class="tree">
              <img src="../static/images/tree4.png" alt="">
            </div>
            <div class="tree">
              <img src="../static/images/tree3.png" alt="">
            </div>
            <div class="tree">
              <img src="../static/images/tree4.png" alt="">
            </div>
          </div>
          <div class="tree-box">
            <div class="tree">
              <img src="../static/images/tree3.png" alt="">
            </div>
            <div class="tree">
              <img src="../static/images/tree2.png" alt="">
            </div>
            <div class="tree">
              <img src="../static/images/tree2.png" alt="">
            </div>
          </div>
          <div class="tree-box">
            <div class="tree">
              <img src="../static/images/tree1.png" alt="">
            </div>
            <div class="tree">
              <img src="../static/images/tree0.png" alt="">
            </div>
            <div class="tree">
              <img src="../static/images/tree0.png" alt="">
            </div>
          </div>
        </div>
        <div class="prop-list">
          <div class="prop">
            <img src="../static/images/prop1.png" alt="">
            <span>1</span>
            <p>化肥</p>
          </div>
          <div class="prop">
            <img src="../static/images/prop2.png" alt="">
            <span>0</span>
            <p>修剪</p>
          </div>
          <div class="prop">
            <img src="../static/images/prop3.png" alt="">
            <span>1</span>
            <p>浇水</p>
          </div>
          <div class="prop">
            <img src="../static/images/prop4.png" alt="">
            <span>1</span>
            <p>宠物粮</p>
          </div>
        </div>
        <div class="pet-hp-list">
          <div class="pet-hp" v-if='user_pet_info.pet_1.has_time > 0'>
            <p>宠物1 饱食度</p>
            <div class="hp">
              <div :style="{ pet_1_hp+'%'}" class="process">{{pet_1_hp}}%</div>
            </div>
          </div>
          <div class="pet-hp" v-if='user_pet_info.pet_2.has_time > 0'>
            <p>宠物2 饱食度</p>
            <div class="hp">
              <div :style="{ pet_2_hp+'%'}" class="process">{{pet_2_hp}}%</div>
            </div>
          </div>
        </div>
    	</div>
    	<script>
    	apiready = function(){
    		Vue.prototype.game = new Game("../static/mp3/bg1.mp3");
    		new Vue({
    			el:"#app",
    			data(){
    				return {
    					user_pet_info:{ // 用户宠物信息
    						pet_1:{},			// 防止调用时报错
    						pet_2:{},
    					}
    				}
    			},
    			// 计算数据
    			computed:{
    				// 计算用户宠物血量百分比
    				pet_1_hp(){
    					persent = ((this.user_pet_info.pet_1.has_time / this.user_pet_info.pet_1.max_time)*100).toFixed(2)
    					return persent
    				},
    				pet_2_hp(){
    					persent = ((this.user_pet_info.pet_2.has_time / this.user_pet_info.pet_2.max_time)*100).toFixed(2)
    					return persent
    				},
    			},
    			// 监听用户信息
    			watch:{
    				// 监听用户宠物剩余时间,如果死亡发送通知
    				'user_pet_info.pet_1.has_time':function(){
    					// 提示用户宠物还剩1分钟死亡,尽快喂食
    					if(this.user_pet_info.pet_1.has_time == 300){
    						this.game.tips('您的宠物1还剩5分钟死亡,请尽快投喂!')
    					}
    					if(this.user_pet_info.pet_1.has_time < 1){
    						this.user_pet_die(1)
    					}
    				},
    				'user_pet_info.pet_2.has_time':function(){
    					if(this.user_pet_info.pet_2.has_time == 300){
    						this.game.tips('您的宠物2还剩5分钟死亡,请尽快投喂!')
    					}
    					if(this.user_pet_info.pet_2.has_time < 1){
    						this.user_pet_die(2)
    					}
    				},
    			},
          created(){
    				// 监听事件
    				this.listen();
          },
    			methods:{
    				// 监听事件
    				listen(){
    					// 监听获取用户宠物信息成功的通知
    					this.listen_get_user_pet_info_success();
    				},
    
    				// 监听获取用户宠物信息成功的通知
    				listen_get_user_pet_info_success(){
    					let self = this
    					// 发起获取用户宠物信息的全局通知
    					self.game.sendEvent('get_user_pet_info');
    					// 监听获取用户宠物信息成功的通知
    					api.addEventListener({
    					    name: 'get_user_pet_info_success'
    					}, (ret, err)=>{
    						// 获取用户宠物信息
    						self.user_pet_info = ret.value.user_pet_info;
    						self.game.print(self.user_pet_info)
    						// 设置定时器,宠物存活时间需要递减
    						setInterval(()=>{
    								// 宠物剩余时间小于0,就不必再减了
    								if(self.user_pet_info.pet_1.has_time > 0){
    									self.user_pet_info.pet_1.has_time--;
    								}
    								if(self.user_pet_info.pet_2.has_time > 0){
    									self.user_pet_info.pet_2.has_time--;
    								}
    							},1000);
    					});
    				},
    
    				// 用户宠物死亡,发送通知
    				user_pet_die(pet_position){
    					this.game.sendEvent('user_pet_die',{
    						pet_position:pet_position
    					})
    				},
    
    			}
    		});
    	}
    	</script>
    </body>
    </html>
    
    
    
    1. 种植园页面, 结束用户宠物死亡的通知, 发送请求,更改数据 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/uuid.js"></script>
      <script src="../static/js/socket.io.js"></script>
    	<script src="../static/js/v-avatar-2.0.3.min.js"></script>
    	<script src="../static/js/main.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="back" src="../static/images/user_back.png" alt="">
        <div class="header">
    			<div class="info">
    				<div class="avatar" @click='to_user'>
    					<img class="avatar_bf" src="../static/images/avatar_bf.png" alt="">
    					<div class="user_avatar">
    						<v-avatar v-if="user_data.avatar" :src="user_data.avatar" :size="62" :rounded="true"></v-avatar>
    						<v-avatar v-else-if="user_data.nickname" :username="user_data.nickname" :size="62" :rounded="true"></v-avatar>
    						<v-avatar v-else :username="user_data.id" :size="62" :rounded="true"></v-avatar>
    					</div>
    					<img class="avatar_border" src="../static/images/avatar_border.png" alt="">
    				</div>
    				<p class="user_name">{{user_data.nickname}}</p>
    			</div>
    			<div class="wallet" @click='user_recharge'>
    				<div class="balance">
    					<p class="title"><img src="../static/images/money.png" alt="">钱包</p>
    					<p class="num">{{game.number_format(user_data.money)}}</p>
    				</div>
    				<div class="balance">
    					<p class="title"><img src="../static/images/integral.png" alt="">果子</p>
    					<p class="num">{{game.number_format(user_data.credit)}}</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">
              <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='to_package'>背包</li>
            <li class="menu-center" @click="to_shop">商店</li>
            <li class="menu">消息</li>
            <li class="menu">好友</li>
          </ul>
        </div>
    	</div>
    	<script>
    	apiready = function(){
    		Vue.prototype.game = new Game("../static/mp3/bg1.mp3");
    		new Vue({
    			el:"#app",
    			data(){
    				return {
    					user_data:{},  // 当前用户信息
              music_play:true,
              namespace: '/orchard', // websocket命名空间
              socket: null, // websocket连接对象
    					recharge_list: [], // 允许充值的金额列表
    					package_init_setting: {}, // 背包初始配置信息
    					user_info: {}, // 用户登陆初始化化信息
    					user_package_info: {}, // 用户背包信息
    					user_pet_info: {}, // 用户宠物信息
    					pet_position:null, // 宠物栏位置
    				}
    			},
          created(){
    				// socket建立连接
    				this.socket_connect();
            // 自动加载我的果园页面
    				this.to_my_orchard();
    				// 获取用户数据
    				this.get_user_data()
    				// 监听事件变化
    				this.listen()
    				// 获取充值金额列表
    				this.get_recharge_list()
          },
    			methods:{
    				back(){
    					this.game.openWin("root");
    				},
    				// 监听事件
    				listen(){
    					// 1.监听token更新的通知
    					this.listen_update_token();
    					// 2.监听是否购买道具成功的通知
    					this.listen_buy_prop_success();
    					// 3.监听是否使用道具成功的通知
    					// this.listen_use_prop_success();
    					// 4.监听解锁背包容量的通知
    					this.listen_unlock_package();
    					// 5.监听是否用其他页面需要获取用户宠物信息
    					this.listen_get_user_pet_info();
    					// 6.监听用户宠物是否死亡的通知
    					this.listen_user_pet_die();
    				},
    
    				// 1.监听token更新的通知
    				listen_update_token(){
    					api.addEventListener({
    							name: 'update_token'
    					}, (ret, err) => {
    						// 更新用户数据
    							this.get_user_data()
    					});
    				},
    
    				// 2.监听是否购买道具成功的通知
    				listen_buy_prop_success(){
    					api.addEventListener({
    					    name: 'buy_prop_success'
    					}, (ret, err)=>{
    							// 发送请求,更新用户背包数据
    							this.socket.emit('user_package');
    					});
    				},
    
    				// 3.监听解锁背包容量的通知
    				listen_unlock_package(){
    					api.addEventListener({
    							name: 'unlock_package'
    					}, (ret, err)=>{
    						// 发送解锁背包的请求
    						this.socket.emit('unlock_package');
    						// 获取解锁背包响应数据
    						this.socket.on('unlock_package_response',(response)=>{
    							if(response.errno === 1000){
    								// 更新用户背包数据
    								this.socket.emit('user_package')
    								// 用户积分改变,重新刷新token值
    								this.refresh_user_token();
    							}
    						})
    					});
    				},
    
    				// 5.监听是否用其他页面需要获取用户宠物信息
    				listen_get_user_pet_info(){
    					api.addEventListener({
    					    name: 'get_user_pet_info'
    					}, (ret, err)=>{
    						 // 获取用户宠物信息
    						 this.get_user_pet_info();
    					});
    				},
    
    				// 6.监听用户宠物是否死亡的通知
    				listen_user_pet_die(){
    					api.addEventListener({
    					    name: 'user_pet_die'
    					}, (ret, err)=>{
    						 // 结束宠物死亡位置栏参数
    						 this.pet_position = ret.value.pet_position
    						 // 用户宠物死亡,发送请求
    						 this.socket.emit('user_pet_die',{
    							 pet_position:this.pet_position
    						 })
    						 // 响应用户宠物死亡
    						 this.socket.on('user_pet_die_response',(response)=>{
    							 if(response.errno === 1000){
    								 this.game.tips("宠物"+this.pet_position+"已经死亡了")
    							 }else {
    							 	this.game.tips(response.errmsg)
    							 }
    						 })
    					});
    				},
    
    				// 获取用户宠物信息
    				get_user_pet_info(){
    					// 发起请求用户宠物信息
    					this.socket.emit('user_pet_info');
    					// 获取响应
    					this.socket.on('user_pet_info_response', (response)=>{
    						if(response.errno === 1000){
    							// 响应成功,获取宠物信息
    							this.user_pet_info = response.user_pet_info
    							// 通知其他页面,已经获取用户宠物信息
    							this.game.sendEvent('get_user_pet_info_success',{
    								user_pet_info: this.user_pet_info
    							})
    						}else {
    							this.game.tips(response.errmsg)
    						}
    					})
    				},
    
    				// 用户积分改变,重新刷新token值
    				refresh_user_token(){
    					let self = this
    					self.game.check_user_login(self, ()=>{
    						let token = self.game.getdata('refresh_token') || self.game.getfs('refresh_token')
    						self.game.post(self,{
    							'method': 'Users.refresh',
    							'params':{},
    							'header':{
    								'Authorization': 'jwt ' + token
    							},
    							success(response){
    								let data = response.data;
    								if(data.result && data.result.errno === 1000){
    									// 刷新token值
    									let token = self.game.getfs("refresh_token");
    	                if(token){
    	                    // 记住密码的情况
    	                    self.game.deldata(["access_token","refresh_token"]);
    	                    self.game.setfs({
    	                        "access_token": data.result.access_token,
    	                        "refresh_token": data.result.refresh_token,
    	                    });
    	                }else{
    	                    // 不记住密码的情况
    	                    self.game.delfs(["access_token","refresh_token"]);
    	                    self.game.setdata({
    	                        "access_token": data.result.access_token,
    	                        "refresh_token": data.result.refresh_token,
    	                    });
    	                }
    									// 发布全局广播,token更新
    									self.game.sendEvent('update_token')
    								}
    							}
    						})
    					})
    				},
    
    
    				// 通过token值获取用户数据
    				get_user_data(){
    					let self = this;
    					// 检查token是否过期,过期从新刷新token
    					self.game.check_user_login(self, ()=>{
    						// 获取token
    						let token = this.game.getfs('access_token') || this.game.getdata('access_token')
    						// 根据token获取用户数据
    						this.user_data = this.game.get_user_by_token(token)
    					})
    				},
    
    				// websocket连接处理
            socket_connect(){
              this.socket = io.connect(this.game.config.SOCKET_SERVER + this.namespace, {transports: ['websocket']});
              this.socket.on('connect', ()=>{
                  this.game.print("开始连接服务端");
    							// 获取背包初始配置信息
    							this.get_package_setting()
    							// websocket登陆处理
    							this.user_websocket_login()
              });
            },
    
    				// 获取背包初始配置信息
    				get_package_setting(){
    					this.socket.on('init_config_response', (response)=>{
    						// this.game.print(response,1)
    						this.package_init_setting = response
    					})
    				},
    
    				// websocket登陆处理
    				user_websocket_login(){
    					// 客户端发送用户登陆请求
    					this.socket.emit('login',{'uid': this.user_data.unique_id});
    					// 接收登陆响应
    					this.login_response();
    					// 接收用户背包响应
    					this.user_package_response();
    				},
    
    				// 接收登陆初始化信息
    				login_response(){
    					this.socket.on('login_response',(response)=>{
    						// this.game.print(response,1)
    						if(response.errno === 1000){
    							this.user_info = response.user_info
    						}
    					})
    				},
    
    				// 接收用户背包信息
    				user_package_response(){
    					this.socket.on('user_package_response',(response)=>{
    						// this.game.print(response,1)
    						if(response.errno === 1000){
    							this.user_package_info = response.package_info;
    							// 全局广播用户背包更新
    							this.game.sendEvent('package_update',{
    								user_package_info: this.user_package_info
    							})
    						}else {
    							this.game.tips(response.errmsg)
    						}
    					})
    				},
    
    
            // 跳转我的果园页面
            to_my_orchard(){
              this.game.check_user_login(this, ()=>{
                this.game.openFrame("my_orchard","my_orchard.html","from_right",{
        					marginTop: 174,     //相对父页面上外边距的距离,数字类型
        					marginLeft: 0,      //相对父页面左外边距的距离,数字类型
        			    marginBottom: 54,     //相对父页面下外边距的距离,数字类型
        			    marginRight: 0     //相对父页面右外边距的距离,数字类型
        				});
              })
            },
    
            // 点击商店打开道具商城页面
    				to_shop(){
              this.game.check_user_login(this, ()=>{
                this.game.openFrame('shop', 'shop.html', 'from_top');
              })
            },
    
    				// 点击头像,跳转用户中心页面
    				to_user(){
    					this.game.check_user_login(this, ()=>{
                this.game.openFrame('user', 'user.html', 'from_right');
              })
    				},
    
    				// 获取充值金额列表
    				get_recharge_list(){
    					let self = this;
    					self.game.check_user_login(self,()=>{
    						self.game.post(self,{
    							'method': 'Users.recharge_list',
    							'params': {},
    							success(response){
    								let data = response.data;
    								if(data.result && data.result.errno === 1000){
    									self.recharge_list = data.result.recharge_list
    								}
    							}
    						})
    					})
    				},
    
    				// 点击钱包进行用户充值,设置充值金额
    				user_recharge(){
    					api.actionSheet({
    								title: '余额充值',
    								cancelTitle: '取消',
    								buttons: this.recharge_list
    						}, (ret, err)=>{
    								if( ret.buttonIndex <= this.recharge_list.length ){
    										 // 充值金额
    										 let money = this.recharge_list[ret.buttonIndex-1];
    										//  this.game.print(money,1);
    										// 发送支付宝充值请求
    										this.recharge_app_pay(money)
    								}
    					});
    				},
    
    				// 发送支付宝充值请求
    				recharge_app_pay(money){
    					// 获取支付宝支付对象
    					let aliPayPlus = api.require("aliPayPlus");
    					let self = this;
    					// 向服务端发送请求获取终止订单信息
    					self.game.check_user_login(self, ()=>{
    						let token = this.game.getdata("access_token") || this.game.getfs("access_token");
    						self.game.post(self,{
    							'method': 'Users.recharge',
    							'params':{
    								'money': money,
    							},
    							'header': {
    								'Authorization': 'jwt ' + token
    							},
    							success(response){
    								let data = response.data;
    								if(data.result && data.result.errno === 1000){
    									// 本次充值的订单参数
    									let order_string = data.result.order_string;
    
    									// 支付完成以后,支付APP返回的响应状态码
    									let resultCode = {
    										"9000": "支付成功!",
    										"8000": "支付正在处理中,请稍候!",
    										"4000": "支付失败!请联系我们的工作人员~",
    										"5000": "支付失败,重复的支付操作",
    										"6002": "网络连接出错",
    										"6004": "支付正在处理中,请稍后",
    									}
    									// 唤醒支付宝APP,发起支付
    									aliPayPlus.payOrder({
    										orderInfo: order_string,
    										sandbox: data.result.sandbox, // 将来APP上线需要修改成false
    									},(ret, err)=>{
    										if(resultCode[ret.code]){
    											// 提示支付结果
    											if(ret.code != 9000){
    												self.game.tips(resultCode[ret.code]);
    											}else {
    												// 支付成功,向服务端请求验证支付结果 - 参数订单号
    												self.check_recharge_result(data.result.order_number);
    											}
    										}
    									})
    								}
    							}
    						})
    					})
    				},
    
    				// 向服务端发送请求,校验充值是否成功
    				check_recharge_result(order_number){
    					let self = this;
    					self.game.check_user_login(self, ()=>{
    						let token = this.game.getdata("access_token") || this.game.getfs("access_token");
    						self.game.post(self,{
    							'method': 'Users.check_recharge_result',
    							'params':{
    								'order_number':order_number,
    							},
    							'header':{
    								'Authorization': 'jwt ' + token
    							},
    							success(response){
    								let data = response.data;
    								if(data.result && data.result.errno === 1000){
    									// 充值成功
    									self.game.tips('充值成功!')
    									// 用户数据更改过,重新刷新token值
    									let token = self.game.getfs("access_token");
    									// 删除token值
    									self.game.deldata(["access_token","refresh_token"]);
    									self.game.delfs(["access_token","refresh_token"]);
    									if(token){
                        // 记住密码的情况
                        self.game.setfs({
                          "access_token": data.result.access_token,
                          "refresh_token": data.result.refresh_token,
                        });
                      }else{
                        // 不记住密码的情况
                        self.game.setdata({
                          "access_token": data.result.access_token,
                          "refresh_token": data.result.refresh_token,
                        });
    									}
    									// 全局广播充值成功
    									self.game.sendEvent('recharge_success')
    									// 全局广播刷新token值
    									self.game.sendEvent('update_token')
    								}
    							}
    						})
    					})
    				},
    
    				// 点击背包,跳转到背包页面,并传递背包初始配置信息
    				to_package(){
    					this.game.check_user_login(this, ()=>{
    						this.game.openFrame('package', 'package.html', 'from_top',null, {
    							'package_init_setting': this.package_init_setting,
    							'user_package_info': this.user_package_info,
    							'user_info': this.user_info
    						})
    					})
    				},
    
    
    			}
    		});
    	}
    	</script>
    </body>
    </html>
    
    
    

    宠物道具使用

    宠物保存在背包,所以用户如果要使用宠物,则只需要在2个宠物栏的情况下,让用户选择宠物放置位置。

    服务端提供宠物道具使用api接口

    1. 用户使用宠物道具, 更改用户宠物信息, 更改用户背包宠物道具信息 , orchard.socket,新增代码:
    # 使用宠物道具
        @get_user_object_by_socket_sid
        def on_use_pet_prop(self, user, data):
            '''
            使用宠物道具
            :param user: 根据回话ID获取的用户模型对象
            :param data: 客户端传来的宠物道具相关信息
            :return:
            '''
            # 1.判断用户是否存在(检查回话状态)
            if user is None:
                # 响应数据
                emit('use_pet_prop_response', {
                    'errno': code.CODE_USER_NOT_EXISTS,
                    'errmsg': message.user_not_exists,
                })
                # 停止程序继续运行
                return
    
            prop_id = data.get('prop_id') # 获取宠物道具id
            pet_position = data.get('pet_position') # 获取宠物使用位置信息
    
            # 2. 获取宠物道具信息
            pet_prop = services.get_pet_item(prop_id)
            # 判断宠物道具是否存在
            if len(pet_prop) == 0:
                emit('use_pet_prop_response', {
                    'errno': code.CODE_NO_SUCH_PROP,
                    'errmsg': message.no_such_prop,
                })
                # 停止程序继续运行
                return
    
            # 3. 获取当前用户的宠物信息
            user_pet_info = services.get_user_pet_info(user.id)
            # 判断宠物位置只有一个,并且宠物1位置已经被占,就不能在使用宠物了
            if user_pet_info['pet_position'] == 1 and len(user_pet_info['pet_1']) > 2:
                emit('use_pet_prop_response', {
                    'errno': code.CODE_NO_SUCH_PROP,
                    'errmsg': message.no_such_prop,
                })
                # 停止程序继续运行
                return
    
            # 4. 获取当前位置的宠物的剩余时间
            pet_ttl = services.get_pet_ttl(user.id, pet_position)
            # 判断宠物是否死亡
            if pet_ttl >=0:
                emit('use_pet_prop_response', {
                    'errno': code.CODE_PET_NOT_DIE,
                    'errmsg': message.pet_not_die,
                })
                # 停止程序继续运行
                return
    
            # 5.获取当前用户背包信息,判断是否存在当前宠物道具
            user_package_info = user_services.get_package_info_by_id(user.id)
            prop_key = -1  # 默认道具不存在,所以在背包的道具列表中,下标为-1
            # 获取宠物道具在背包道具列表中的索引下标
            for key, prop in enumerate(user_package_info['props_list']):
                if prop_id == prop['prop_id']:
                    prop_key = key
                    break
    
            # 判断是否存在当前宠物道具
            if prop_key == -1:
                emit('use_pet_prop_response', {
                    'errno': code.CODE_NO_SUCH_PROP,
                    'errmsg': message.no_such_prop,
                })
                # 停止程序继续运行
                return
    
            # 6. MongoDB中添加用户宠物信息, 并在背包中减去已使用道具数量
            services.use_pet_prop(user.id, pet_prop, pet_position)
    
            # 7. 返回最新的用户宠物信息
            self.on_user_pet_info()
    
            # 8. 返回最新的用户背包信息
            self.on_user_package()
    
            # 响应数据
            emit('use_pet_prop_response', {
                'errno': code.CODE_OK,
                'errmsg': message.ok,
            })
    
    
    1. 数据库处理层代码,orchard/services.py,代码:
    from datetime import datetime
    from application.apps.users.documents import UserPackageDocument
    from .documents import UsePropLogDocument
    # 用户使用了宠物道具
    def use_pet_prop(user_id, pet_prop, pet_position):
        '''
        用户使用了宠物道具
        :param user_id: 用户ID
        :param pet_prop: 宠物道具信息
        :param pet_position: 宠物放置的位置
        :return:
        '''
        # 1.获取用户宠物信息对象
        document = UserPetDocument.objects.get(user_id=user_id)
        # 更新用户宠物信息
        updater = {}
        updater[f'pet_{pet_position}'] = {
            "start_time": datetime.now().strftime("%Y-%m-%d %H:%M:%S"),
            "pet_image": pet_prop["image"],
            "pet_id": pet_prop["id"],
            "life_time": pet_prop["life_time"],
            "hunger_num": pet_prop["hunger_num"],
            "satiety_num": pet_prop["satiety_num"],
            "hit_rate": pet_prop["hit_rate"]
        }
        document.update(**updater)
    
        # 2.redis给宠物设置生命时间
        redis_orchard.setex(f'user:{user_id}:pet:{pet_position}', config.PET_MAX_TIME, '_')
    
        # 3.获取用户背包对象
        user_package = UserPackageDocument.objects.get(user_id = user_id)
        current_prop = {}  # 背包道具列表中的当前使用的宠物信息
        current_key = -1   # 背包道具列表索引
        for key,prop in enumerate(user_package['props_list']):
            # 必须确定道具类型,
            if prop['type'] == 'pet':
                if pet_prop['id'] == prop['prop_id']:
                    current_prop = prop
                    current_key = key
                    break
    
        print('pet_prop=',pet_prop)
        print('current_prop=',current_prop)
        print('props_list',user_package['props_list'])
    
        if current_prop['num'] <= 1:
            # 如果只有一个道具的情况, 使用玩了以后直接删除道具项
            if len(user_package['props_list']) == 1:
                # 如果道具列表只剩一个道具,重置道具列表
                user_package['props_list'] = []
            else:
                # 否则删除该道具项
                user_package['props_list'].remove(current_prop)
        else:
            # 有多个道具,数据减1
            user_package['props_list'][current_key]['num'] = int(user_package["props_list"][current_key]["num"]) - 1
        # 保存更改的数据
        user_package.save()
    
        # 4. 添加道具使用记录
        UsePropLogDocument.objects.create(
            user_id=user_id,
            prop_type=current_prop["type"],
            prop_id=pet_prop["id"],
            prop_name=pet_prop["name"],
            prop_image=pet_prop["image"],
            prop_num=1,
            use_time=datetime.now().strftime("%Y-%m-%d %H:%M:%S"),
            remark={},
        )
    
    
    1. 添加道具使用日志的文档模型,orchard.documents,代码:
    # 道具使用日志记录
    class UsePropLogDocument(mgdb.Document):
        """道具使用日志记录"""
        meta = {
            'collection': 'use_prop_log',
            'ordering': ['_id'],          # 正序排列
            'strict': False,              # 是否严格语法
        }
        user_id = mgdb.IntField(required=True, verbose_name="用户ID")
        prop_type = mgdb.StringField(required=True, verbose_name="道具类型")
        prop_id = mgdb.IntField(required=True, verbose_name="道具ID")
        prop_name = mgdb.StringField(required=True, verbase_name="道具名称")
        prop_image = mgdb.StringField(required=True, verbase_name="道具图片地址")
        prop_num = mgdb.IntField(required=True, default=1, verbose_name="道具数量")
        use_time = mgdb.StringField(required=True, auto_add_now=True, verbose_name="道具使用时间")
        remark = mgdb.DictField(null=True, verbose_name="道具使用其他说明")
    
    
    

    客户端请求使用宠物道具

    1. 背包道具详情页 html/prop.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/uuid.js"></script>
    	<script src="../static/js/main.js"></script>
    </head>
    <body>
    	<div class="app frame avatar item" id="app">
        <div class="box">
          <p class="title">{{item.prop_name}}</p>
          <img class="close" @click="back" src="../static/images/close_btn1.png" alt="">
          <div class="content">
    				<img class="invite_code item_img" :src="`../static/images/${item.prop_image}`" alt="">
    			</div>
    			<div class="invite_tips item_remark">
            <p>{{item.prop_remark}}</p><br>
            <p>
              <span>库存:{{item.num}}</span>
              &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;
              <span>道具类型:{{prop_type_tips[item.type]}}</span>
            </p><br><br>
            <p>
    					<span @click="gotouse">立即使用</span>
    					&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
    					<span @click="gotopay">继续购买</span>
    				</p>
    			</div>
        </div>
    	</div>
    	<script>
    	apiready = function(){
        var game = new Game("../static/mp3/bg1.mp3");
        // 在 #app 标签下渲染一个按钮组件
        Vue.prototype.game = game;
    		new Vue({
    			el:"#app",
    			data(){
    				return {
              item: {},
              type: "",
    					prop_type_tips:{
    						"seed": "种子",
    						"food": "宠物粮",
    						"plant": "种植",
    						"pet": "宠物",
    					},
              buy_type: ['price', 'credit'], // 金钱或积分购买
    				}
    			},
          created(){
            // 接受传递过来的参数
            this.get_page_params();
          },
    			methods:{
            back(){
              this.game.closeFrame();
            },
            // 接受传递过来的参数
            get_page_params(){
              // 接受来自商品列表页面的参数
              this.item = api.pageParam.prop;
              this.type = api.pageParam.prop.type;
            },
    				gotouse(){
    					// 使用道具
    					if(this.type == 'pet'){
    						api.actionSheet({
    						    title: '请选择当前宠物的放养位置?',
    						    cancelTitle: '取消使用',
    						    buttons: ['1号位置','2号位置']
    						}, (ret, err)=>{
    							if(ret.buttonIndex != 3){
    								// 发送全局广播,使用宠物道具
    								this.game.sendEvent('use_prop',{
    									prop_id: this.item.prop_id,
    									prop_type: this.type,
    									pet_position: ret.buttonIndex
    								})
    								this.back();
    							}
    						});
    					}
    
    				},
    
            // 点击购买道具
            gotopay(){
    					let self = this
              let buttons = [1,2,5,10,20,50];
              api.actionSheet({
                  title: `购买${this.item.prop_name}的数量`,
                  cancelTitle: '取消',
                  buttons: buttons,
              }, (ret, err)=>{
                  if(ret.buttonIndex<=buttons.length){
                    let buy_num = buttons[ret.buttonIndex-1];
                    // this.game.print(buy_num,1);
    								api.actionSheet({
    								    title: '请选择购买方式!',
    								    cancelTitle: '取消购买',
    								    buttons: ['使用金钱购买','使用果子购买']
    								}, (ret, err)=>{
    										// 金钱是1, 果子是2
    								    if(ret.buttonIndex<=buttons.length){
    											// self.game.print(self.buy_type[ret.buttonIndex -1])
    											// 向服务端发送购买道具请求
    											self.pay_prop(buy_num, self.buy_type[ret.buttonIndex -1])
    										}
    								});
                  }
              });
            },
    
    				// 向服务端发送购买道具请求
    				pay_prop(buy_num, buy_type){
    					let self = this;
    					self.game.check_user_login(self, ()=>{
    						let token = self.game.getdata('access_token') || self.game.getfs('access_token')
    						self.game.post(self,{
    							'method': 'Orchard.pay_props',
    							'params':{
    								'prop_num': buy_num,
    								'prop_type': self.type,
    								'prop_id': self.item.prop_id,
    								'buy_type': buy_type,
    							},
    							'header':{
    								'Authorization': 'jwt ' + token
    							},
    							success(response){
    								let data = response.data;
    								if(data.result && data.result.errno === 1000){
    									// 刷新token值
    									let token = self.game.getfs("access_token");
                      if(token){
                          // 记住密码的情况
                          self.game.deldata(["access_token","refresh_token"]);
                          self.game.setfs({
                              "access_token": data.result.access_token,
                              "refresh_token": data.result.refresh_token,
                          });
                      }else{
                          // 不记住密码的情况
                          self.game.delfs(["access_token","refresh_token"]);
                          self.game.setdata({
                              "access_token": data.result.access_token,
                              "refresh_token": data.result.refresh_token,
                          });
                      }
    									// 发布全局广播,token更新
    									self.game.sendEvent('buy_prop_success')
    									self.game.sendEvent('update_token')
    									self.game.tips('购买道具成功~')
    									// 关闭页面
    									self.game.closeFrame()
    								}
    							}
    						})
    					})
    				},
    
    
    
    			}
    		});
    	}
    	</script>
    </body>
    </html>
    
    
    
    1. html/orchard.html中监听如果用户使用宠物道具,请求socket服务端使用宠物道具。
    <!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/uuid.js"></script>
      <script src="../static/js/socket.io.js"></script>
    	<script src="../static/js/v-avatar-2.0.3.min.js"></script>
    	<script src="../static/js/main.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="back" src="../static/images/user_back.png" alt="">
        <div class="header">
    			<div class="info">
    				<div class="avatar" @click='to_user'>
    					<img class="avatar_bf" src="../static/images/avatar_bf.png" alt="">
    					<div class="user_avatar">
    						<v-avatar v-if="user_data.avatar" :src="user_data.avatar" :size="62" :rounded="true"></v-avatar>
    						<v-avatar v-else-if="user_data.nickname" :username="user_data.nickname" :size="62" :rounded="true"></v-avatar>
    						<v-avatar v-else :username="user_data.id" :size="62" :rounded="true"></v-avatar>
    					</div>
    					<img class="avatar_border" src="../static/images/avatar_border.png" alt="">
    				</div>
    				<p class="user_name">{{user_data.nickname}}</p>
    			</div>
    			<div class="wallet" @click='user_recharge'>
    				<div class="balance">
    					<p class="title"><img src="../static/images/money.png" alt="">钱包</p>
    					<p class="num">{{game.number_format(user_data.money)}}</p>
    				</div>
    				<div class="balance">
    					<p class="title"><img src="../static/images/integral.png" alt="">果子</p>
    					<p class="num">{{game.number_format(user_data.credit)}}</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">
              <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='to_package'>背包</li>
            <li class="menu-center" @click="to_shop">商店</li>
            <li class="menu">消息</li>
            <li class="menu">好友</li>
          </ul>
        </div>
    	</div>
    	<script>
    	apiready = function(){
    		Vue.prototype.game = new Game("../static/mp3/bg1.mp3");
    		new Vue({
    			el:"#app",
    			data(){
    				return {
    					user_data:{},  // 当前用户信息
              music_play:true,
              namespace: '/orchard', // websocket命名空间
              socket: null, // websocket连接对象
    					recharge_list: [], // 允许充值的金额列表
    					package_init_setting: {}, // 背包初始配置信息
    					user_info: {}, // 用户登陆初始化化信息
    					user_package_info: {}, // 用户背包信息
    					user_pet_info: {}, // 用户宠物信息
    					pet_position:null, // 宠物栏位置
    				}
    			},
          created(){
    				// socket建立连接
    				this.socket_connect();
            // 自动加载我的果园页面
    				this.to_my_orchard();
    				// 获取用户数据
    				this.get_user_data()
    				// 监听事件变化
    				this.listen()
    				// 获取充值金额列表
    				this.get_recharge_list()
          },
    			methods:{
    				back(){
    					this.game.openWin("root");
    				},
    				// 监听事件
    				listen(){
    					// 1.监听token更新的通知
    					this.listen_update_token();
    					// 2.监听是否购买道具成功的通知
    					this.listen_buy_prop_success();
    					// 3.监听是否使用道具成功的通知
    					// this.listen_use_prop_success();
    					// 4.监听解锁背包容量的通知
    					this.listen_unlock_package();
    					// 5.监听是否用其他页面需要获取用户宠物信息
    					this.listen_get_user_pet_info();
    					// 6.监听用户宠物是否死亡的通知
    					this.listen_user_pet_die();
    					// 7.监听是否使用了道具
    					this.listen_use_prop();
    				},
    
    				// 1.监听token更新的通知
    				listen_update_token(){
    					api.addEventListener({
    							name: 'update_token'
    					}, (ret, err) => {
    						// 更新用户数据
    							this.get_user_data()
    					});
    				},
    
    				// 2.监听是否购买道具成功的通知
    				listen_buy_prop_success(){
    					api.addEventListener({
    					    name: 'buy_prop_success'
    					}, (ret, err)=>{
    							// 发送请求,更新用户背包数据
    							this.socket.emit('user_package');
    					});
    				},
    
    				// 3.监听解锁背包容量的通知
    				listen_unlock_package(){
    					api.addEventListener({
    							name: 'unlock_package'
    					}, (ret, err)=>{
    						// 发送解锁背包的请求
    						this.socket.emit('unlock_package');
    						// 获取解锁背包响应数据
    						this.socket.on('unlock_package_response',(response)=>{
    							if(response.errno === 1000){
    								// 更新用户背包数据
    								this.socket.emit('user_package')
    								// 用户积分改变,重新刷新token值
    								this.refresh_user_token();
    							}
    						})
    					});
    				},
    
    				// 5.监听是否用其他页面需要获取用户宠物信息
    				listen_get_user_pet_info(){
    					api.addEventListener({
    					    name: 'get_user_pet_info'
    					}, (ret, err)=>{
    						 // 获取用户宠物信息
    						 this.get_user_pet_info();
    					});
    				},
    
    				// 6.监听用户宠物是否死亡的通知
    				listen_user_pet_die(){
    					api.addEventListener({
    					    name: 'user_pet_die'
    					}, (ret, err)=>{
    						 // 结束宠物死亡位置栏参数
    						 this.pet_position = ret.value.pet_position
    						 // 用户宠物死亡,发送请求
    						 this.socket.emit('user_pet_die',{
    							 pet_position:this.pet_position
    						 })
    						 // 响应用户宠物死亡
    						 this.socket.on('user_pet_die_response',(response)=>{
    							 if(response.errno === 1000){
    								 this.game.tips("宠物"+this.pet_position+"已经死亡了")
    							 }else {
    							 	this.game.tips(response.errmsg)
    							 }
    						 })
    					});
    				},
    
    				// 7.监听是否使用了道具
    				listen_use_prop(){
    					api.addEventListener({
    					    name: 'use_prop'
    					}, (ret, err)=>{
    						// 使用宠物道具
    						if(ret.value.prop_type == 'pet'){
    							// 发送使用宠物道具请求
    							this.socket.emit('use_pet_prop',{
    								prop_id: ret.value.prop_id,
    								pet_position: ret.value.pet_position
    							});
    							// 响应数据
    							this.socket.on('use_pet_prop_response',(response)=>{
    								if(response.errno === 1000){
    									this.game.tips('宠物道具使用成功!')
    								}else {
    									this.game.tips(response.errmsg)
    								}
    							})
    						}
    					});
    
    				},
    
    				// 获取用户宠物信息
    				get_user_pet_info(){
    					// 发起请求用户宠物信息
    					this.socket.emit('user_pet_info');
    					// 获取响应
    					this.socket.on('user_pet_info_response', (response)=>{
    						if(response.errno === 1000){
    							// 响应成功,获取宠物信息
    							this.user_pet_info = response.user_pet_info
    							// 通知其他页面,已经获取用户宠物信息
    							this.game.sendEvent('get_user_pet_info_success',{
    								user_pet_info: this.user_pet_info
    							})
    						}else {
    							this.game.tips(response.errmsg)
    						}
    					})
    				},
    
    				// 用户积分改变,重新刷新token值
    				refresh_user_token(){
    					let self = this
    					self.game.check_user_login(self, ()=>{
    						let token = self.game.getdata('refresh_token') || self.game.getfs('refresh_token')
    						self.game.post(self,{
    							'method': 'Users.refresh',
    							'params':{},
    							'header':{
    								'Authorization': 'jwt ' + token
    							},
    							success(response){
    								let data = response.data;
    								if(data.result && data.result.errno === 1000){
    									// 刷新token值
    									let token = self.game.getfs("refresh_token");
    	                if(token){
    	                    // 记住密码的情况
    	                    self.game.deldata(["access_token","refresh_token"]);
    	                    self.game.setfs({
    	                        "access_token": data.result.access_token,
    	                        "refresh_token": data.result.refresh_token,
    	                    });
    	                }else{
    	                    // 不记住密码的情况
    	                    self.game.delfs(["access_token","refresh_token"]);
    	                    self.game.setdata({
    	                        "access_token": data.result.access_token,
    	                        "refresh_token": data.result.refresh_token,
    	                    });
    	                }
    									// 发布全局广播,token更新
    									self.game.sendEvent('update_token')
    								}
    							}
    						})
    					})
    				},
    
    
    				// 通过token值获取用户数据
    				get_user_data(){
    					let self = this;
    					// 检查token是否过期,过期从新刷新token
    					self.game.check_user_login(self, ()=>{
    						// 获取token
    						let token = this.game.getfs('access_token') || this.game.getdata('access_token')
    						// 根据token获取用户数据
    						this.user_data = this.game.get_user_by_token(token)
    					})
    				},
    
    				// websocket连接处理
            socket_connect(){
              this.socket = io.connect(this.game.config.SOCKET_SERVER + this.namespace, {transports: ['websocket']});
              this.socket.on('connect', ()=>{
                  this.game.print("开始连接服务端");
    							// 获取背包初始配置信息
    							this.get_package_setting()
    							// websocket登陆处理
    							this.user_websocket_login()
              });
            },
    
    				// 获取背包初始配置信息
    				get_package_setting(){
    					this.socket.on('init_config_response', (response)=>{
    						// this.game.print(response,1)
    						this.package_init_setting = response
    					})
    				},
    
    				// websocket登陆处理
    				user_websocket_login(){
    					// 客户端发送用户登陆请求
    					this.socket.emit('login',{'uid': this.user_data.unique_id});
    					// 接收登陆响应
    					this.login_response();
    					// 接收用户背包响应
    					this.user_package_response();
    				},
    
    				// 接收登陆初始化信息
    				login_response(){
    					this.socket.on('login_response',(response)=>{
    						// this.game.print(response,1)
    						if(response.errno === 1000){
    							this.user_info = response.user_info
    						}
    					})
    				},
    
    				// 接收用户背包信息
    				user_package_response(){
    					this.socket.on('user_package_response',(response)=>{
    						// this.game.print(response,1)
    						if(response.errno === 1000){
    							this.user_package_info = response.package_info;
    							// 全局广播用户背包更新
    							this.game.sendEvent('package_update',{
    								user_package_info: this.user_package_info
    							})
    						}else {
    							this.game.tips(response.errmsg)
    						}
    					})
    				},
    
    
            // 跳转我的果园页面
            to_my_orchard(){
              this.game.check_user_login(this, ()=>{
                this.game.openFrame("my_orchard","my_orchard.html","from_right",{
        					marginTop: 174,     //相对父页面上外边距的距离,数字类型
        					marginLeft: 0,      //相对父页面左外边距的距离,数字类型
        			    marginBottom: 54,     //相对父页面下外边距的距离,数字类型
        			    marginRight: 0     //相对父页面右外边距的距离,数字类型
        				});
              })
            },
    
            // 点击商店打开道具商城页面
    				to_shop(){
              this.game.check_user_login(this, ()=>{
                this.game.openFrame('shop', 'shop.html', 'from_top');
              })
            },
    
    				// 点击头像,跳转用户中心页面
    				to_user(){
    					this.game.check_user_login(this, ()=>{
                this.game.openFrame('user', 'user.html', 'from_right');
              })
    				},
    
    				// 获取充值金额列表
    				get_recharge_list(){
    					let self = this;
    					self.game.check_user_login(self,()=>{
    						self.game.post(self,{
    							'method': 'Users.recharge_list',
    							'params': {},
    							success(response){
    								let data = response.data;
    								if(data.result && data.result.errno === 1000){
    									self.recharge_list = data.result.recharge_list
    								}
    							}
    						})
    					})
    				},
    
    				// 点击钱包进行用户充值,设置充值金额
    				user_recharge(){
    					api.actionSheet({
    								title: '余额充值',
    								cancelTitle: '取消',
    								buttons: this.recharge_list
    						}, (ret, err)=>{
    								if( ret.buttonIndex <= this.recharge_list.length ){
    										 // 充值金额
    										 let money = this.recharge_list[ret.buttonIndex-1];
    										//  this.game.print(money,1);
    										// 发送支付宝充值请求
    										this.recharge_app_pay(money)
    								}
    					});
    				},
    
    				// 发送支付宝充值请求
    				recharge_app_pay(money){
    					// 获取支付宝支付对象
    					let aliPayPlus = api.require("aliPayPlus");
    					let self = this;
    					// 向服务端发送请求获取终止订单信息
    					self.game.check_user_login(self, ()=>{
    						let token = this.game.getdata("access_token") || this.game.getfs("access_token");
    						self.game.post(self,{
    							'method': 'Users.recharge',
    							'params':{
    								'money': money,
    							},
    							'header': {
    								'Authorization': 'jwt ' + token
    							},
    							success(response){
    								let data = response.data;
    								if(data.result && data.result.errno === 1000){
    									// 本次充值的订单参数
    									let order_string = data.result.order_string;
    
    									// 支付完成以后,支付APP返回的响应状态码
    									let resultCode = {
    										"9000": "支付成功!",
    										"8000": "支付正在处理中,请稍候!",
    										"4000": "支付失败!请联系我们的工作人员~",
    										"5000": "支付失败,重复的支付操作",
    										"6002": "网络连接出错",
    										"6004": "支付正在处理中,请稍后",
    									}
    									// 唤醒支付宝APP,发起支付
    									aliPayPlus.payOrder({
    										orderInfo: order_string,
    										sandbox: data.result.sandbox, // 将来APP上线需要修改成false
    									},(ret, err)=>{
    										if(resultCode[ret.code]){
    											// 提示支付结果
    											if(ret.code != 9000){
    												self.game.tips(resultCode[ret.code]);
    											}else {
    												// 支付成功,向服务端请求验证支付结果 - 参数订单号
    												self.check_recharge_result(data.result.order_number);
    											}
    										}
    									})
    								}
    							}
    						})
    					})
    				},
    
    				// 向服务端发送请求,校验充值是否成功
    				check_recharge_result(order_number){
    					let self = this;
    					self.game.check_user_login(self, ()=>{
    						let token = this.game.getdata("access_token") || this.game.getfs("access_token");
    						self.game.post(self,{
    							'method': 'Users.check_recharge_result',
    							'params':{
    								'order_number':order_number,
    							},
    							'header':{
    								'Authorization': 'jwt ' + token
    							},
    							success(response){
    								let data = response.data;
    								if(data.result && data.result.errno === 1000){
    									// 充值成功
    									self.game.tips('充值成功!')
    									// 用户数据更改过,重新刷新token值
    									let token = self.game.getfs("access_token");
    									// 删除token值
    									self.game.deldata(["access_token","refresh_token"]);
    									self.game.delfs(["access_token","refresh_token"]);
    									if(token){
                        // 记住密码的情况
                        self.game.setfs({
                          "access_token": data.result.access_token,
                          "refresh_token": data.result.refresh_token,
                        });
                      }else{
                        // 不记住密码的情况
                        self.game.setdata({
                          "access_token": data.result.access_token,
                          "refresh_token": data.result.refresh_token,
                        });
    									}
    									// 全局广播充值成功
    									self.game.sendEvent('recharge_success')
    									// 全局广播刷新token值
    									self.game.sendEvent('update_token')
    								}
    							}
    						})
    					})
    				},
    
    				// 点击背包,跳转到背包页面,并传递背包初始配置信息
    				to_package(){
    					this.game.check_user_login(this, ()=>{
    						this.game.openFrame('package', 'package.html', 'from_top',null, {
    							'package_init_setting': this.package_init_setting,
    							'user_package_info': this.user_package_info,
    							'user_info': this.user_info
    						})
    					})
    				},
    
    
    			}
    		});
    	}
    	</script>
    </body>
    </html>
    
    
    

    宠物栏解锁

    客户端发送请求

    1. 客户端在我的种植园页面中,当用户点击解锁宠物栏后,发起通知,告诉其他页面。当接收到解锁宠物栏成功的通知以后,修改宠物栏的数量。my_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/uuid.js"></script>
    	<script src="../static/js/socket.io.js"></script>
      <script src="../static/js/main.js"></script>
    
    </head>
    <body>
    	<div class="app orchard orchard-frame" id="app">
        <div class="background">
          <img class="grassland2" src="../static/images/grassland2.png" alt="">
          <img class="mushroom1" src="../static/images/mushroom1.png" alt="">
          <img class="stake1" src="../static/images/stake1.png" alt="">
          <img class="stake2" src="../static/images/stake2.png" alt="">
        </div>
        <div class="pet-box">
          <div class="pet">
            <img class="pet-item" v-if="pet_info.pet_1.has_time>0" :src="`../static/images/${pet_info.pet_1.pet_image}`" alt="">
          </div>
          <div class="pet turned_off" v-if="pet_info.pet_position<2" @click="unlock_pet_field">
            <img class="turned_image" src="../static/images/turned_off.png" alt="">
            <p>激活宠物栏</p>
          </div>
    			<div class="pet" v-else>
            <img class="pet-item" v-if="pet_info.pet_2.has_time>0" :src="`../static/images/${pet_info.pet_2.pet_image}`"  alt="">
          </div>
        </div>
        <div class="tree-list">
          <div class="tree-box">
            <div class="tree">
              <img src="../static/images/tree4.png" alt="">
            </div>
            <div class="tree">
              <img src="../static/images/tree3.png" alt="">
            </div>
            <div class="tree">
              <img src="../static/images/tree4.png" alt="">
            </div>
          </div>
          <div class="tree-box">
            <div class="tree">
              <img src="../static/images/tree3.png" alt="">
            </div>
            <div class="tree">
              <img src="../static/images/tree2.png" alt="">
            </div>
            <div class="tree">
              <img src="../static/images/tree2.png" alt="">
            </div>
          </div>
          <div class="tree-box">
            <div class="tree">
              <img src="../static/images/tree1.png" alt="">
            </div>
            <div class="tree">
              <img src="../static/images/tree0.png" alt="">
            </div>
            <div class="tree">
              <img src="../static/images/tree0.png" alt="">
            </div>
          </div>
        </div>
        <div class="prop-list">
          <div class="prop">
            <img src="../static/images/prop1.png" alt="">
            <span>1</span>
            <p>化肥</p>
          </div>
          <div class="prop">
            <img src="../static/images/prop2.png" alt="">
            <span>0</span>
            <p>修剪</p>
          </div>
          <div class="prop">
            <img src="../static/images/prop3.png" alt="">
            <span>1</span>
            <p>浇水</p>
          </div>
          <div class="prop">
            <img src="../static/images/prop4.png" alt="">
            <span>1</span>
            <p>宠物粮</p>
          </div>
        </div>
        <div class="pet-hp-list">
          <div class="pet-hp" v-if="pet_info.pet_1.has_time>0">
            <p>宠物1 饱食度</p>
            <div class="hp">
              <div :style="{ pet_1_hp+'%'}" class="process">{{pet_1_hp}}%</div>
            </div>
          </div>
          <div class="pet-hp" v-if="pet_info.pet_2.has_time>0">
            <p>宠物2 饱食度</p>
            <div class="hp">
              <div :style="{ pet_2_hp+'%'}" class="process">{{pet_2_hp}}%</div>
            </div>
          </div>
        </div>
    	</div>
    	<script>
    	apiready = function(){
    		Vue.prototype.game = new Game("../static/mp3/bg1.mp3");
    		new Vue({
    			el:"#app",
    			data(){
    				return {
    					pet_info:{ // 用户的信宠物息
    						pet_1:{},
    						pet_2:{},
    					},
    				}
    			},
    			computed:{
    				pet_1_hp(){
    					return ((this.pet_info.pet_1.has_time/this.pet_info.pet_1.max_time)*100).toFixed(2);
    				},
    				pet_2_hp(){
    					return ((this.pet_info.pet_2.has_time/this.pet_info.pet_2.max_time)*100).toFixed(2);
    				},
    			},
    			watch:{
    				"pet_info.pet_1.has_time":function(){
    					if(this.pet_info.pet_1.has_time<1){
    						this.pet_die(1);
    					}
    				},
    				"pet_info.pet_2.has_time":function(){
    					if(this.pet_info.pet_2.has_time<1){
    						this.pet_die(2);
    					}
    				},
    			},
          created(){
    				this.listen();
          },
    			methods:{
    				listen(){
    					this.get_pet_info();
    				},
    				get_pet_info(){
    					// 发起全局通知
    					api.sendEvent({
    					    name: 'get_pet_info',
    					    extra: {}
    					});
    					api.addEventListener({
    					    name: 'get_pet_info_response'
    					}, (ret, err)=>{
    							this.pet_info = ret.value.pet_info;
    							this.game.print(this.pet_info);
    							setInterval(()=>{
    								if(this.pet_info.pet_1.has_time>=0){
    									this.pet_info.pet_1.has_time-=0.5;
    								}
    								if(this.pet_info.pet_2.has_time>=0){
    									this.pet_info.pet_2.has_time-=0.5;
    								}
    							},500);
    					});
    				},
    				pet_die(position){
    					api.sendEvent({
    					    name: 'pet_die',
    					    extra: {
    					        position: position,
    					    }
    					});
    				},
    				unlock_pet_field(){
    					// 激活宠物栏
    					api.actionSheet({
    							title: '确认是否解锁新的宠物栏?',
    							cancelTitle: '取消',
    							destructiveTitle: '解锁',
    					}, (ret, err)=>{
    						if(ret.buttonIndex == 1){
    							api.sendEvent({
    									name: 'unlock_pet_field',
    									extra: {}
    							});
    						}
    					});
    				}
    			}
    		});
    	}
    	</script>
    </body>
    </html>
    
    
    
    1. 种植园页面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/uuid.js"></script>
      <script src="../static/js/socket.io.js"></script>
    	<script src="../static/js/main.js"></script>
    	<script src="../static/js/v-avatar-2.0.3.min.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="back" src="../static/images/user_back.png" alt="">
        <div class="header">
    			<div class="info">
    				<div class="avatar">
    					<img class="avatar_bf" src="../static/images/avatar_bf.png" alt="">
    					<div class="user_avatar">
    						<v-avatar v-if="user_info.avatar" :src="user_info.avatar" :size="62" :rounded="true"></v-avatar>
    						<v-avatar v-else-if="user_info.nickname" :username="user_info.nickname" :size="62" :rounded="true"></v-avatar>
    						<v-avatar v-else :username="user_info.id" :size="62" :rounded="true"></v-avatar>
    					</div>
    					<img class="avatar_border" src="../static/images/avatar_border.png" alt="">
    				</div>
    				<p class="user_name">{{user_info.nickname}}</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">{{user_info.money.toFixed(2).toLocaleString()}}</p>
    				</div>
    				<div class="balance">
    					<p class="title"><img src="../static/images/integral.png" alt="">果子</p>
    					<p class="num">{{user_info.credit.toFixed(2).toLocaleString()}}</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">
              <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="open_package">背包</li>
            <li class="menu-center" @click="open_shop">商店</li>
            <li class="menu">消息</li>
            <li class="menu">好友</li>
          </ul>
        </div>
    	</div>
    	<script>
    	apiready = function(){
    		Vue.prototype.game = new Game("../static/mp3/bg1.mp3");
    		new Vue({
    			el:"#app",
    			data(){
    				return {
    					user_info:{},
              music_play:true,
              namespace: '/orchard',
              socket: null,
    					recharge_list:[10,20,30,50,100,200,300,500,1000], // 允许充值的金额列表
    					package_init_setting:{},    // 配置初始信息
    					user_package: {},           // 用户背包信息
    					pet_info:{},                // 用户的宠物信息
    				}
    			},
          created(){
    				this.listen();
    				let token = this.game.getdata("access_token") || this.game.getfs("access_token");
    				this.user_info = this.game.get_user_by_token(token);
            this.open_my_chard();
    				this.connect();
          },
    			methods:{
    				listen(){
    					// 监听是否更新了token
    					this.listen_update_token();
    					// 监听是否有购买道具成功的通知
    					this.listen_buy_prop_success();
    					// 监听是否有使用道具成功的通知
    					this.listen_use_prop_success();
    					// 监听是否有其他页面需要宠物信息
    					this.listen_get_pet_info();
    					// 监听是否有宠物挂了
    					this.listen_pet_die();
    					// 监听是否有使用了道具
    					this.listen_use_prop();
    					// 监听解锁宠物栏
    					this.listen_unlock_pet_field();
    				},
    				listen_unlock_pet_field(){
    					// 监听解锁宠物栏的通知
    					api.addEventListener({
    							name: 'unlock_pet_field'
    					}, (ret, err)=>{
    						  // 发送请求
    							this.socket.emit("unlock_pet");
    							// 监听响应
    							this.socket.on("unlock_pet_response",this.unlock_pet_field_response);
    					});
    				},
    				unlock_pet_field_response(data){
    						// 解锁宠物栏的结果
    						if(data.errno==1000){
    							// 成功
    
    							// 通知其他页面token更新了
    							api.sendEvent({
    									name: 'update_token',
    									extra: {}
    							});
    
    							this.game.tips("解锁成功!");
    						}else{
    							this.game.tips(data.errmsg);
    						}
    					},
    				listen_use_prop(){
    					// 监听是否有使用了道具
    					api.addEventListener({
    					    name: 'use_prop'
    					}, (ret, err)=>{
    						this.game.print(ret.value,1);
    						if(ret.value.prop_type==="pet"){
    							// 使用了宠物道具
    							this.socket.emit("use_pet",{prop_id:ret.value.prop_id,position:ret.value.position});
    							this.socket.on("use_pet_response",(response)=>{
    								this.game.tips(response.errmsg);
    							})
    						}
    					});
    				},
    				listen_pet_die(){
    					api.addEventListener({
    					    name: 'pet_die'
    					}, (ret, err)=>{
    							this.pet_die(ret.value.position);
    					});
    
    				},
    				pet_die(position){
    					// 宠物死亡处理
    					this.socket.emit("pet_die", {pet_position:position});
    				},
    				listen_get_pet_info(){
    					// 监听是否有其他页面需要宠物信息
    					api.addEventListener({
    					    name: 'get_pet_info',
    					}, (ret, err)=>{
    						  // 获取宠物信息
    							this.get_pet_info();
    					});
    
    				},
    				listen_update_token(){
    					api.addEventListener({
    					    name: 'update_token',
    					}, (ret, err)=>{
    						let token = this.game.getdata("access_token") || this.game.getfs("access_token");
    						this.user_info = this.game.get_user_by_token(token);
    					});
    
    				},
    				listen_buy_prop_success(){
    					// 监听是否有购买道具成功的通知
    					api.addEventListener({
    					    name: 'buy_prop_success',
    					}, (ret, err)=>{
    					    this.socket.emit("user_package");
    							api.sendEvent({
    							    name: 'package_update',
    							    extra: {
    										user_package: this.user_package,
    							    }
    							});
    
    					});
    
    				},
    				listen_use_prop_success(){
    					// 监听是否有使用道具成功的通知
    				},
    				open_shop(){
              this.game.openFrame("shop","shop.html",null,null,{
                type: "push", //动画类型(详见动画类型常量)
                subType: "from_top", //动画子类型(详见动画子类型常量)
                duration: 300 //动画过渡时间,默认300毫秒
              });
            },
            open_my_chard(){
              this.game.openFrame("my_orchard","my_orchard.html",null,{
                marginTop: 174,     //相对父页面上外边距的距离,数字类型
                marginLeft: 0,      //相对父页面左外边距的距离,数字类型
                marginBottom: 54,   //相对父页面下外边距的距离,数字类型
                marginRight: 0     //相对父页面右外边距的距离,数字类型
              },{
                type: "push",
                subType: "from_right",
                duration: 300
              });
            },
            connect(){
              // socket连接
              this.socket = io.connect(this.game.config.SOCKET_SERVER + this.namespace, {transports: ['websocket']});
              this.socket.on('connect', ()=>{
                  this.init_config_response(); // 监听服务端返回的背包初始配置
    							this.login();
    							this.unlock_package(); // 识别是否有激活背包的通知
              });
            },
    				init_config_response(){
    					// 监听服务端返回的背包初始配置
    					this.socket.on("init_config_response",(response)=>{
    						this.package_init_setting = response;
    					});
    				},
    				login(){
    					this.socket.emit("login", {"uid":this.user_info.unique_id});
    					this.login_response();
    					// 监听来自服务端返回的用户异常
    					this.user_error_response();
    
    					this.user_package_response(); // 获取背包信息
    				},
    				user_error_response(){
    					// 监听来自服务端响应的用户异常
    					this.socket.on("user_error_response",(response)=>{
    						this.game.tips(response.errmsg);
    					});
    				},
    				get_pet_info(){
    					// 获取宠物信息
    					this.socket.emit("pet_info");
    					this.socket.on("pet_info_response",(response)=>{
    						this.pet_info = response.pet_info;
    						// 通知其他页面,已经获取到宠物信息
    						api.sendEvent({
    						    name: 'get_pet_info_response',
    						    extra: {
    									pet_info:this.pet_info,
    						    }
    						});
    
    					});
    				},
    				login_response(){
    					this.socket.on("login_response",(response)=>{
    						this.game.print(response);
    					});
    				},
    				user_package_response(){
    					// 获取背包信息
    					this.socket.on("user_package_response",(response)=>{
    						this.user_package = response.package_info;
    						// 发起通知,背包数据更新了
    						api.sendEvent({
    						    name: 'package_update',
    						    extra: {
    									user_package: this.user_package,
    						    }
    						});
    
    					});
    				},
    				unlock_package(){
    					// 激活背包
    					api.addEventListener({
    					    name: 'unlock_package'
    					}, (ret, err)=>{
    					  	this.socket.emit("unlock_package");
    							this.socket.on("unlock_package_response",(response)=>{
    								this.game.print(response);
    							})
    					});
    				},
            back(){
              this.game.openWin("root");
            },
    				user_recharge(){
    					// 用户选择充值,让用户设置充值的金额
    					api.actionSheet({
    							title: '余额充值',
    							cancelTitle: '取消',
    							buttons: this.recharge_list
    					}, (ret, err)=>{
    							if( ret.buttonIndex <= this.recharge_list.length ){
    									 // 充值金额
    									 money = this.recharge_list[ret.buttonIndex-1];
    									 // 调用支付宝充值
    									 // this.game.print(money,1);
    									 this.recharge_app_pay(money);
    							}
    					});
    				},
    				recharge_app_pay(money){
    					// 获取支付宝的支付信息,并发起支付。
    					var aliPayPlus = api.require('aliPayPlus');
    					// 唤醒支付宝发起支付
    					var self = this;
    					let token = this.game.getdata("access_token") || this.game.getfs("access_token");
    					// 从服务端获取充值订单的参数
    					this.game.check_user_login(this,()=>{
    						this.game.post(this, {
    							"method": "Users.recharge",
    							"params": {
    								money:money, // 充值金额
    							},
    							"header": {
    								"Authorization": "jwt " + token
    							},
    							success(response) {
    								let data = response.data;
    								if (data.result && data.result.errno == 1000) {
    									// 本次充值的订单参数
    									let order_string = data.result.order_string;
    									// 支付结果的提示码
    									let resultCode = {
    										"9000": "支付成功!",
    										"8000": "支付正在处理中,请稍候!",
    										"4000": "支付失败!请联系我们的工作人员~",
    										"5000": "支付失败,重复的支付操作",
    										"6002": "网络连接出错",
    										"6004": "支付正在处理中,请稍后",
    									}
    									// 唤醒支付宝APP,发起支付
    									aliPayPlus.payOrder({
    									    orderInfo: order_string,
    											sandbox: data.result.sandbox, // 将来APP上线需要修改成false
    									}, (ret, err)=>{
    											if(ret.code !== "9000"){
    													self.game.tips(resultCode[ret.code],4000);
    											}else{
    													self.check_recharge_result(data.result.order_number);
    											}
    									});
    								} else {
    									self.game.tips(data.result.errmsg);
    								}
    							}
    						});
    					});
    				},
    				check_recharge_result(order_number){
    					// 查询充值的结果
    					var self = this;
    					let token = this.game.getdata("access_token") || this.game.getfs("access_token");
    					// 从服务端获取充值订单的参数
    					this.game.check_user_login(this,()=>{
    						this.game.post(this, {
    							"method": "Users.check_recharge_result",
    							"params": {
    								order_number:order_number, // 订单号
    							},
    							"header": {
    								"Authorization": "jwt " + token
    							},
    							success(response) {
    								let data = response.data;
    								if (data.result && data.result.errno == 1000) {
    									// 支付完成以后,支付APP返回的响应状态码
    									self.game.tips("充值成功!",4000);
    									// 同步token
    									let token = self.game.getfs("access_token");
    									self.game.deldata(["access_token","refresh_token"]);
    									self.game.delfs(["access_token","refresh_token"]);
    									if(token){
                        // 记住密码的情况
                        self.game.setfs({
                          "access_token": data.result.access_token,
                          "refresh_token": data.result.refresh_token,
                        });
                      }else{
                        // 不记住密码的情况
                        self.game.setdata({
                          "access_token": data.result.access_token,
                          "refresh_token": data.result.refresh_token,
                        });
                      }
    									api.sendEvent({
    											name: 'pay_success',
    											extra: {}
    									});
    									api.sendEvent({
    											name: 'update_token',
    											extra: {}
    									});
    								}
    							}
    						});
    					});
    				},
    				open_package(){
    					// 打开背包,附带背包初始配置
    					this.game.openFrame("package","package.html",{
    						"package_init_setting":this.package_init_setting,
    						"user_package":this.user_package,
    					},null,{
    						type: "push", //动画类型(详见动画类型常量)
    						subType: "from_top", //动画子类型(详见动画子类型常量)
    						duration: 300 //动画过渡时间,默认300毫秒
    					});
    				}
    			}
    		});
    	}
    	</script>
    </body>
    </html>
    
    

    服务端接口

    服务端监听客户端的解锁宠物栏的事件通知,进行解锁

    1. 视图 解锁宠物栏 orchard.socket,代码:
        # 激活解锁宠物栏
        @get_user_object_by_socket_sid
        def on_unlock_pet_field(self,user):
            # 1.判断用户是否存在(检查回话状态)
            if user is None:
                # 响应数据
                emit('unlock_pet_field_response', {
                    'errno': code.CODE_USER_NOT_EXISTS,
                    'errmsg': message.user_not_exists,
                })
                # 停止程序继续运行
                return
    
            # 2. 判断是否到达宠物栏激活上限(一共两个宠物栏)
            # 获取用户宠物信息
            user_pet_info = services.get_user_pet_info(user.id)
            if user_pet_info['pet_position'] == 2:
                emit('unlock_pet_field_response', {
                    'errno': code.CODE_PACKAGE_SPACE_NOT_ENOUGH,
                    'errmsg': message.package_space_not_enough,
                })
                # 停止程序继续运行
                return
    
            # 3. 判断用户是否有足够的金钱或积分激活宠物栏
            res = services.check_user_unlock_pet_condition(user)
            if not res:
                emit('unlock_pet_field_response', {
                    'errno': code.CODE_NOT_ENOUGH_MONEY,
                    'errmsg': message.not_enough_money,
                })
                # 停止程序继续运行
                return
    
            # 4. 解锁宠物栏
            services.unlock_pet_field(user)
    
            # 5.刷新客户端新的用户宠物信息
            self.on_user_pet_info()
    
            # 响应
            emit('unlock_pet_field_response', {
                'errno': code.CODE_OK,
                'errmsg': message.ok,
            })
    
    
    1. 数据处理层,orchard.services,代码:
    # 判断用户是否有足够的金钱或积分激活宠物栏
    def check_user_unlock_pet_condition(user):
        '''
        判断用户是否有足够的金钱或积分激活宠物栏
        :param user: 用户模型对象
        :return:
        '''
        price_dict = config.UNLOCK_PET_FIELD_PRICE
        if user.money >= price_dict['money'] and user.credit >= price_dict['credit']:
            return True
        return None
    
    # 解锁宠物栏
    def unlock_pet_field(user):
        '''
        解锁宠物栏
        :param user: 用户模型对象
        :return:
        '''
        # 1. 用户宠物信息修改宠物栏数量[mongodb]
        user_pet_info = UserPetDocument.objects.get(user_id = user.id)
        user_pet_info.pet_position = 2
        user_pet_info.save()
    
        # 2. 扣除用户的积分或者金额[mysql]
        price_dict = config.UNLOCK_PET_FIELD_PRICE
        user.money = float(user.money) - price_dict['money']
        user.credit = int(user.credit) - price_dict['credit']
        db.session.commit()
    
    
    1. 种植园配置信息中新增解锁宠物栏条件的配置信息,setting.plant,代码:
    # 解锁宠物栏的条件[解锁价格]
    UNLOCK_PET_FIELD_PRICE = {
        "money": 100,         # 所需金额
        "credit": 1000,       # 所需积分
    }
    
    

    宠物喂养

    宠物喂养提示的图标显示

    1. 我的种植园页面, 添加道具样式, html/my_orchard.html
    <div class="pet-box">
          <div class="pet">
    				<span class="popped">
    					<img class="pet-prop" src="../static/images/prop4.png" alt="">
    				</span>
            <img class="pet-item" v-if="pet_info.pet_1.has_time>0" :src="`../static/images/${pet_info.pet_1.pet_image}`" alt="">
          </div>
          <div class="pet turned_off" v-if="pet_info.pet_position<2" @click="unlock_pet_field">
            <img class="turned_image" src="../static/images/turned_off.png" alt="">
            <p>激活宠物栏</p>
          </div>
    			<div class="pet" v-else>
    				<span class="popped">
    					<img class="pet-prop" src="../static/images/prop4.png" alt="">
    				</span>
            <img class="pet-item" v-if="pet_info.pet_2.has_time>0" :src="`../static/images/${pet_info.pet_2.pet_image}`"  alt="">
          </div>
        </div>
    
    
    1. 添加css样式, static/css/main.css, 代码;
    @keyframes prop_move {
      0% { top: 5.4rem; }
      50% { top: 6rem; }
      100%{ top: 5.4rem; }
    }
    
    .popped {
      transform: translate(-50%, -50%);
      border-radius: 5rem;
      height: 5rem;
       5rem;
      display: block;
      position: absolute;
      opacity: 1;
      transition: box-shadow .5s ease-in-out, transform .07s ease-out, opacity .04s ease-in;
      top: 6rem;
      right: 0rem;
      box-shadow: rgb(255, 200, 200) 0px 0px 3rem inset;
      z-index: 100;
      animation: prop_move 2s infinite;
    }
    .popped:after {
      content: '';
      position: absolute;
      top: 18%;
      left: 18%;
      background-color: rgba(191, 255, 255, 0.6);
       1.2rem;
      height: 1.5rem;
      border-radius: 50%;
      transform: rotate(45deg) scale(0.8);
    }
    .popped img{
       4rem;
      height: 4rem;
      position: absolute;
      top: 0;
      left: 0;
      right: 0;
      bottom: 0;
      margin: auto;
    }
    
    

    控制喂养图标的出现

    1.我的种植园页面 html/my_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/uuid.js"></script>
    	<script src="../static/js/socket.io.js"></script>
      <script src="../static/js/main.js"></script>
    
    </head>
    <body>
    	<div class="app orchard orchard-frame" id="app">
        <div class="background">
          <img class="grassland2" src="../static/images/grassland2.png" alt="">
          <img class="mushroom1" src="../static/images/mushroom1.png" alt="">
          <img class="stake1" src="../static/images/stake1.png" alt="">
          <img class="stake2" src="../static/images/stake2.png" alt="">
        </div>
        <div class="pet-box">
          <div class="pet">
    				<span class="popped" v-if='pet_1_hunger'>
    					<img class="pet-prop" src="../static/images/prop4.png" alt="">
    				</span>
            <img class="pet-item" v-if='user_pet_info.pet_1.has_time > 0' :src="`../static/images/${user_pet_info.pet_1.pet_image}`" alt="">
          </div>
          <div class="pet turned_off" v-if='user_pet_info.pet_position < 2' @click='unlock_pet_field'>
    				<img class="turned_image" src="../static/images/turned_off.png" alt="">
            <p>激活宠物栏</p>
          </div>
    			<div class="pet" v-else>
    				<span class="popped" v-if='pet_2_hunger'>
    					<img class="pet-prop" src="../static/images/prop4.png" alt="">
    				</span>
            <img class="pet-item" v-if='user_pet_info.pet_2.has_time > 0' :src="`../static/images/${user_pet_info.pet_2.pet_image}`" alt="">
          </div>
        </div>
        <div class="tree-list">
          <div class="tree-box">
            <div class="tree">
              <img src="../static/images/tree4.png" alt="">
            </div>
            <div class="tree">
              <img src="../static/images/tree3.png" alt="">
            </div>
            <div class="tree">
              <img src="../static/images/tree4.png" alt="">
            </div>
          </div>
          <div class="tree-box">
            <div class="tree">
              <img src="../static/images/tree3.png" alt="">
            </div>
            <div class="tree">
              <img src="../static/images/tree2.png" alt="">
            </div>
            <div class="tree">
              <img src="../static/images/tree2.png" alt="">
            </div>
          </div>
          <div class="tree-box">
            <div class="tree">
              <img src="../static/images/tree1.png" alt="">
            </div>
            <div class="tree">
              <img src="../static/images/tree0.png" alt="">
            </div>
            <div class="tree">
              <img src="../static/images/tree0.png" alt="">
            </div>
          </div>
        </div>
        <div class="prop-list">
          <div class="prop">
            <img src="../static/images/prop1.png" alt="">
            <span>1</span>
            <p>化肥</p>
          </div>
          <div class="prop">
            <img src="../static/images/prop2.png" alt="">
            <span>0</span>
            <p>修剪</p>
          </div>
          <div class="prop">
            <img src="../static/images/prop3.png" alt="">
            <span>1</span>
            <p>浇水</p>
          </div>
          <div class="prop">
            <img src="../static/images/prop4.png" alt="">
            <span>1</span>
            <p>宠物粮</p>
          </div>
        </div>
        <div class="pet-hp-list">
          <div class="pet-hp" v-if='user_pet_info.pet_1.has_time > 0'>
            <p>宠物1 饱食度</p>
            <div class="hp">
              <div :style="{ pet_1_hp+'%'}" class="process">{{pet_1_hp}}%</div>
            </div>
          </div>
          <div class="pet-hp" v-if='user_pet_info.pet_2.has_time > 0'>
            <p>宠物2 饱食度</p>
            <div class="hp">
              <div :style="{ pet_2_hp+'%'}" class="process">{{pet_2_hp}}%</div>
            </div>
          </div>
        </div>
    	</div>
    	<script>
    	apiready = function(){
    		Vue.prototype.game = new Game("../static/mp3/bg1.mp3");
    		new Vue({
    			el:"#app",
    			data(){
    				return {
    					user_pet_info:{ // 用户宠物信息
    						pet_1:{},			// 防止调用时报错
    						pet_2:{},
    					},
    					pet_1_satiety_notification: false, // 宠物感到饥饿
    					pet_1_hunger_notification: false,  // 宠物快饿死了
    					pet_2_satiety_notification: false,
    					pet_2_hunger_notification: false,
    				}
    			},
    			// 计算数据
    			computed:{
    				// 计算用户宠物血量百分比
    				pet_1_hp(){
    					persent = ((this.user_pet_info.pet_1.has_time / this.user_pet_info.pet_1.max_time)*100).toFixed(2)
    					return persent
    				},
    				pet_2_hp(){
    					persent = ((this.user_pet_info.pet_2.has_time / this.user_pet_info.pet_2.max_time)*100).toFixed(2)
    					return persent
    				},
    				// 计算宠物喂养图标的出现,饱食度低于一定饥饿度才可以喂养宠物
    				pet_1_hunger(){
    					if(this.user_pet_info.pet_1){
    						let pet_1 = this.user_pet_info.pet_1;
    						return pet_1.satiety_num > this.pet_1_hp;
    					}
    				},
    				pet_2_hunger(){
    					if(this.user_pet_info.pet_2){
    						let pet_2 = this.user_pet_info.pet_2;
    						return pet_2.satiety_num > this.pet_2_hp;
    					}
    				},
    			},
    			// 监听用户信息
    			watch:{
    				// 监听用户宠物剩余时间,如果死亡发送通知
    				'user_pet_info.pet_1.has_time':function(){
    					if(this.user_pet_info.pet_1.has_time < 1){
    						this.user_pet_die(1)
    					}
    				},
    				'user_pet_info.pet_2.has_time':function(){
    					if(this.user_pet_info.pet_2.has_time < 1){
    						this.user_pet_die(2)
    					}
    				},
    			},
          created(){
    				// 监听事件
    				this.listen();
          },
    			methods:{
    				// 监听事件
    				listen(){
    					// 监听获取用户宠物信息成功的通知
    					this.listen_get_user_pet_info_success();
    				},
    
    				// 监听获取用户宠物信息成功的通知
    				listen_get_user_pet_info_success(){
    					let self = this
    					// 发起获取用户宠物信息的全局通知
    					self.game.sendEvent('get_user_pet_info');
    					// 监听获取用户宠物信息成功的通知
    					api.addEventListener({
    					    name: 'get_user_pet_info_success'
    					}, (ret, err)=>{
    						// 获取用户宠物信息
    						self.user_pet_info = ret.value.user_pet_info;
    						self.game.print(self.user_pet_info)
    						// 设置定时器,宠物存活时间需要递减
    						setInterval(()=>{
    								// 宠物剩余时间小于0,就不必再减了
    								if(self.user_pet_info.pet_1.has_time > 0){
    									self.user_pet_info.pet_1.has_time--;
    									// 根据宠物饱食度发送系统通知
    									if(self.pet_1_hp < self.user_pet_info.pet_1.satiety_num && !self.pet_1_satiety_notification){
    										// 饥饿
    										self.game.notification("喂养宠物通知","主人,您的1号宠物已经饿了,快来照顾吧~");
    										self.pet_1_satiety_notification = true;
    									}
    									if(self.pet_1_hp < self.user_pet_info.pet_1.hunger_num && !self.pet_1_hunger_notification){
    										// 快饿死了
    										self.game.notification("喂养宠物通知","主人,您的1号宠物饿死了,快来救命~");
    										self.pet_1_hunger_notification = true;
    									}
    								}
    								if(self.user_pet_info.pet_2.has_time > 0){
    									self.user_pet_info.pet_2.has_time--;
    									// 根据宠物饱食度发送系统通知
    									if(self.pet_2_hp < self.user_pet_info.pet_2.satiety_num && !self.pet_2_satiety_notification){
    										// 饥饿
    										self.game.notification("喂养宠物通知","主人,您的2号宠物已经饿了,快来照顾吧~");
    										self.pet_2_satiety_notification = true;
    									}
    									if(self.pet_2_hp < self.user_pet_info.pet_2.hunger_num && !self.pet_2_hunger_notification){
    										// 快饿死了
    										self.game.notification("喂养宠物通知","主人,您的2号宠物饿死了,快来救命~");
    										self.pet_2_hunger_notification = true;
    									}
    								}
    							},1000);
    					});
    				},
    
    				// 用户宠物死亡,发送通知
    				user_pet_die(pet_position){
    					this.game.sendEvent('user_pet_die',{
    						pet_position:pet_position
    					})
    				},
    
    				// 点击解锁宠物栏,发送解锁宠物栏通知
    				unlock_pet_field(){
    					api.actionSheet({
    					    title: '确认花费一定的金钱和积分解锁宠物栏',
    					    cancelTitle: '取消',
    					    destructiveTitle: '解锁',
    					}, (ret, err)=>{
    						if(ret.buttonIndex == 1){
    							// 发送全局通知,解锁宠物栏
    							this.game.sendEvent('unlock_pet_field')
    						}
    					});
    				},
    
    			}
    		});
    	}
    	</script>
    </body>
    </html>
    
    
    

    static/js/main.js封装系统通知的发送

    	// 发起系统通知
    	notification(title,content){
    		api.notification({
    	    notify: {
    	    title: title,
    	        content: content
    	    }
    		});
    	}
    
    
    1. 因为消息提示的时候,有可能用户并非出于orchard种植园场景,所以我们需要在html/index.html主页也要进行监听识别, 点击通知,跳转种植园页面,进行宠物喂养。
    <!DOCTYPE html>
    <html lang="en">
    <head>
      <title>首页</title>
      <meta charset="UTF-8">
      <meta name="viewport" content="width=device-width,minimum-scale=1.0,maximum-scale=1.0,user-scalable=no">
      <meta name="format-detection" content="telephone=no,email=no,date=no,address=no">
      <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/uuid.js"></script>
      <script src="../static/js/main.js"></script>
    </head>
    <body>
      <div class="app" id="app">
        <img class="music" :class="music_play?'music2':''" @click="music_play=!music_play" src="../static/images/player.png">
        <div class="bg">
          <img src="../static/images/bg0.jpg">
        </div>
        <ul>
          <li><img class="module1" src="../static/images/image1.png" @click='to_orchard'></li>
          <li><img class="module2" src="../static/images/image2.png" @click='to_user'></li>
          <li><img class="module3" src="../static/images/image3.png"></li>
          <li><img class="module4" src="../static/images/image4.png" @click='to_login'></li>
        </ul>
      </div>
      <script>
    	apiready = function(){
        var game = new Game("../static/mp3/bg1.mp3");
        Vue.prototype.game = game;
    		new Vue({
    			el:"#app",
    			data(){
    				return {
              music_play:true,  // 默认播放背景音乐
    					prev:{name:"",url:"",params:{}}, // 上一页状态
    					current:{name:"index",url:"index.html","params":{}}, // 下一页状态
            }
    			},
          watch:{
            music_play(){
              if(this.music_play){
                this.game.play_music("../static/mp3/bg1.mp3");
              }else{
                this.game.stop_music();
              }
            }
          },
          created(){
            this.listen(); // 监听事件
          },
    			methods:{
            // 监听事件
            listen(){
              // 监听外部访问,唤醒app
              this.listen_invite();
              // 监听系统通知,点击处理
              this.listen_noticeclicked();
            },
    
            // 监听外部访问,唤醒app
            listen_invite(){
              // 使用系统方法appintenr监听并使用appParam接收URLScheme的路由参数
              // 收集操作保存起来,并跳转到登陆页面.
              api.addEventListener({
                  name: 'appintent' // 系统方法
              }, (ret, err)=>{
                  // 获取路由参数,保存到本地
                  let appParam = ret.appParam;
                  // this.game.print(appParam,1); //{"uid":xxx,"user_type":xxx};
                  this.game.setfs(appParam)
    
                  // 如果是其他用户邀请注册!
                  if(appParam.user_type == 'invite'){
                    // 跳转到登陆页面
                    this.game.openWin('login', 'login.html')
                  }
              });
            },
    
            // 监听系统通知,点击处理
            listen_noticeclicked(){
              api.addEventListener({
                  name: 'noticeclicked'
              }, (ret, err)=>{
                // 跳转种植园页面
                this.to_orchard();
              });
            },
    
            // 点击签到跳转登陆页面
            to_login(){
              this.game.openWin('login','login.html')
            },
            // 点击跳转到个人中心页面
            to_user(){
              // 判断用户是否登陆
              this.game.check_user_login(this,() => {
                this.game.openWin('user', 'user.html')
              });
            },
            // 点击跳转到种植园页面
            to_orchard(){
              this.game.check_user_login(this,() => {
                this.game.openWin('orchard', 'orchard0.html')
              });
            },
    
    
    			}
    		})
    	}
    	</script>
    </body>
    </html>
    
    
    

    点击喂养图标显示宠物粮道具喂养宠物

    实现步骤

    1. my_orchard-> 给宠物喂养图标绑定点击事件,发送喂养宠物的通知
    2. orchard -> 页面底部出现当前用户背包中存在的所有宠物粮
    3. 当用户点选了宠物粮 [哪个宠物?使用什么道具喂养的?]
       3.1 发送socket事件,向服务端请求喂养宠物
       3.2 扣除道具,增加宠物的饱食度
    4. 同步客户端
    
    
    1. 我的种植园页面, 发起喂养宠物的通知 html/my_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/uuid.js"></script>
    	<script src="../static/js/socket.io.js"></script>
      <script src="../static/js/main.js"></script>
    
    </head>
    <body>
    	<div class="app orchard orchard-frame" id="app">
        <div class="background">
          <img class="grassland2" src="../static/images/grassland2.png" alt="">
          <img class="mushroom1" src="../static/images/mushroom1.png" alt="">
          <img class="stake1" src="../static/images/stake1.png" alt="">
          <img class="stake2" src="../static/images/stake2.png" alt="">
        </div>
        <div class="pet-box">
          <div class="pet">
    				<span class="popped" v-if='pet_1_hunger' @click='feed_pet(1)'>
    					<img class="pet-prop" src="../static/images/prop4.png" alt="">
    				</span>
            <img class="pet-item" v-if='user_pet_info.pet_1.has_time > 0' :src="`../static/images/${user_pet_info.pet_1.pet_image}`" alt="">
          </div>
          <div class="pet turned_off" v-if='user_pet_info.pet_position < 2' @click='unlock_pet_field'>
    				<img class="turned_image" src="../static/images/turned_off.png" alt="">
            <p>激活宠物栏</p>
          </div>
    			<div class="pet" v-else>
    				<span class="popped" v-if='pet_2_hunger' @click='feed_pet(2)'>
    					<img class="pet-prop" src="../static/images/prop4.png" alt="">
    				</span>
            <img class="pet-item" v-if='user_pet_info.pet_2.has_time > 0' :src="`../static/images/${user_pet_info.pet_2.pet_image}`" alt="">
          </div>
        </div>
        <div class="tree-list">
          <div class="tree-box">
            <div class="tree">
              <img src="../static/images/tree4.png" alt="">
            </div>
            <div class="tree">
              <img src="../static/images/tree3.png" alt="">
            </div>
            <div class="tree">
              <img src="../static/images/tree4.png" alt="">
            </div>
          </div>
          <div class="tree-box">
            <div class="tree">
              <img src="../static/images/tree3.png" alt="">
            </div>
            <div class="tree">
              <img src="../static/images/tree2.png" alt="">
            </div>
            <div class="tree">
              <img src="../static/images/tree2.png" alt="">
            </div>
          </div>
          <div class="tree-box">
            <div class="tree">
              <img src="../static/images/tree1.png" alt="">
            </div>
            <div class="tree">
              <img src="../static/images/tree0.png" alt="">
            </div>
            <div class="tree">
              <img src="../static/images/tree0.png" alt="">
            </div>
          </div>
        </div>
        <div class="prop-list">
          <div class="prop">
            <img src="../static/images/prop1.png" alt="">
            <span>1</span>
            <p>化肥</p>
          </div>
          <div class="prop">
            <img src="../static/images/prop2.png" alt="">
            <span>0</span>
            <p>修剪</p>
          </div>
          <div class="prop">
            <img src="../static/images/prop3.png" alt="">
            <span>1</span>
            <p>浇水</p>
          </div>
          <div class="prop">
            <img src="../static/images/prop4.png" alt="">
            <span>1</span>
            <p>宠物粮</p>
          </div>
        </div>
        <div class="pet-hp-list">
          <div class="pet-hp" v-if='user_pet_info.pet_1.has_time > 0'>
            <p>宠物1 饱食度</p>
            <div class="hp">
              <div :style="{ pet_1_hp+'%'}" class="process">{{pet_1_hp}}%</div>
            </div>
          </div>
          <div class="pet-hp" v-if='user_pet_info.pet_2.has_time > 0'>
            <p>宠物2 饱食度</p>
            <div class="hp">
              <div :style="{ pet_2_hp+'%'}" class="process">{{pet_2_hp}}%</div>
            </div>
          </div>
        </div>
    	</div>
    	<script>
    	apiready = function(){
    		Vue.prototype.game = new Game("../static/mp3/bg1.mp3");
    		new Vue({
    			el:"#app",
    			data(){
    				return {
    					user_pet_info:{ // 用户宠物信息
    						pet_1:{},			// 防止调用时报错
    						pet_2:{},
    					},
    					pet_1_satiety_notification: false, // 宠物感到饥饿
    					pet_1_hunger_notification: false,  // 宠物快饿死了
    					pet_2_satiety_notification: false,
    					pet_2_hunger_notification: false,
    				}
    			},
    			// 计算数据
    			computed:{
    				// 计算用户宠物血量百分比
    				pet_1_hp(){
    					persent = ((this.user_pet_info.pet_1.has_time / this.user_pet_info.pet_1.max_time)*100).toFixed(2)
    					return persent
    				},
    				pet_2_hp(){
    					persent = ((this.user_pet_info.pet_2.has_time / this.user_pet_info.pet_2.max_time)*100).toFixed(2)
    					return persent
    				},
    				// 计算宠物喂养图标的出现,饱食度低于一定饥饿度才可以喂养宠物
    				pet_1_hunger(){
    					if(this.user_pet_info.pet_1){
    						let pet_1 = this.user_pet_info.pet_1;
    						return pet_1.satiety_num > this.pet_1_hp;
    					}
    				},
    				pet_2_hunger(){
    					if(this.user_pet_info.pet_2){
    						let pet_2 = this.user_pet_info.pet_2;
    						return pet_2.satiety_num > this.pet_2_hp;
    					}
    				},
    			},
    			// 监听用户信息
    			watch:{
    				// 监听用户宠物剩余时间,如果死亡发送通知
    				'user_pet_info.pet_1.has_time':function(){
    					if(this.user_pet_info.pet_1.has_time < 1){
    						this.user_pet_die(1)
    					}
    				},
    				'user_pet_info.pet_2.has_time':function(){
    					if(this.user_pet_info.pet_2.has_time < 1){
    						this.user_pet_die(2)
    					}
    				},
    			},
          created(){
    				// 监听事件
    				this.listen();
          },
    			methods:{
    				// 监听事件
    				listen(){
    					// 监听获取用户宠物信息成功的通知
    					this.listen_get_user_pet_info_success();
    				},
    
    				// 监听获取用户宠物信息成功的通知
    				listen_get_user_pet_info_success(){
    					let self = this
    					// 发起获取用户宠物信息的全局通知
    					self.game.sendEvent('get_user_pet_info');
    					// 监听获取用户宠物信息成功的通知
    					api.addEventListener({
    					    name: 'get_user_pet_info_success'
    					}, (ret, err)=>{
    						// 获取用户宠物信息
    						self.user_pet_info = ret.value.user_pet_info;
    						self.game.print(self.user_pet_info)
    						// 设置定时器,宠物存活时间需要递减
    						setInterval(()=>{
    								// 宠物剩余时间小于0,就不必再减了
    								if(self.user_pet_info.pet_1.has_time > 0){
    									self.user_pet_info.pet_1.has_time--;
    									// 根据宠物饱食度发送系统通知
    									if(self.pet_1_hp < self.user_pet_info.pet_1.satiety_num && !self.pet_1_satiety_notification){
    										// 饥饿
    										self.game.notification("喂养宠物通知","主人,您的1号宠物已经饿了,快来照顾吧~");
    										self.pet_1_satiety_notification = true;
    									}
    									if(self.pet_1_hp < self.user_pet_info.pet_1.hunger_num && !self.pet_1_hunger_notification){
    										// 快饿死了
    										self.game.notification("喂养宠物通知","主人,您的1号宠物饿死了,快来救命~");
    										self.pet_1_hunger_notification = true;
    									}
    								}
    								if(self.user_pet_info.pet_2.has_time > 0){
    									self.user_pet_info.pet_2.has_time--;
    									// 根据宠物饱食度发送系统通知
    									if(self.pet_2_hp < self.user_pet_info.pet_2.satiety_num && !self.pet_2_satiety_notification){
    										// 饥饿
    										self.game.notification("喂养宠物通知","主人,您的2号宠物已经饿了,快来照顾吧~");
    										self.pet_2_satiety_notification = true;
    									}
    									if(self.pet_2_hp < self.user_pet_info.pet_2.hunger_num && !self.pet_2_hunger_notification){
    										// 快饿死了
    										self.game.notification("喂养宠物通知","主人,您的2号宠物饿死了,快来救命~");
    										self.pet_2_hunger_notification = true;
    									}
    								}
    							},1000);
    					});
    				},
    
    				// 用户宠物死亡,发送通知
    				user_pet_die(pet_position){
    					this.game.sendEvent('user_pet_die',{
    						pet_position:pet_position
    					})
    				},
    
    				// 点击解锁宠物栏,发送解锁宠物栏通知
    				unlock_pet_field(){
    					api.actionSheet({
    					    title: '确认花费一定的金钱和积分解锁宠物栏',
    					    cancelTitle: '取消',
    					    destructiveTitle: '解锁',
    					}, (ret, err)=>{
    						if(ret.buttonIndex == 1){
    							// 发送全局通知,解锁宠物栏
    							this.game.sendEvent('unlock_pet_field')
    						}
    					});
    				},
    
    				// 点击喂养宠物图标,喂养宠物
    				feed_pet(pet_position){
    					// 发起喂养宠物的通知
    					this.game.sendEvent('feed_pet',{
    						pet_position:pet_position,
    					})
    				},
    
    			}
    		});
    	}
    	</script>
    </body>
    </html>
    
    
    
    1. 种植园页面,接收喂养宠物的通知, 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/uuid.js"></script>
      <script src="../static/js/socket.io.js"></script>
    	<script src="../static/js/v-avatar-2.0.3.min.js"></script>
    	<script src="../static/js/main.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="back" src="../static/images/user_back.png" alt="">
        <div class="header">
    			<div class="info">
    				<div class="avatar" @click='to_user'>
    					<img class="avatar_bf" src="../static/images/avatar_bf.png" alt="">
    					<div class="user_avatar">
    						<v-avatar v-if="user_data.avatar" :src="user_data.avatar" :size="62" :rounded="true"></v-avatar>
    						<v-avatar v-else-if="user_data.nickname" :username="user_data.nickname" :size="62" :rounded="true"></v-avatar>
    						<v-avatar v-else :username="user_data.id" :size="62" :rounded="true"></v-avatar>
    					</div>
    					<img class="avatar_border" src="../static/images/avatar_border.png" alt="">
    				</div>
    				<p class="user_name">{{user_data.nickname}}</p>
    			</div>
    			<div class="wallet" @click='user_recharge'>
    				<div class="balance">
    					<p class="title"><img src="../static/images/money.png" alt="">钱包</p>
    					<p class="num">{{game.number_format(user_data.money)}}</p>
    				</div>
    				<div class="balance">
    					<p class="title"><img src="../static/images/integral.png" alt="">果子</p>
    					<p class="num">{{game.number_format(user_data.credit)}}</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">
              <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='to_package'>背包</li>
            <li class="menu-center" @click="to_shop">商店</li>
            <li class="menu">消息</li>
            <li class="menu">好友</li>
          </ul>
        </div>
    	</div>
    	<script>
    	apiready = function(){
    		Vue.prototype.game = new Game("../static/mp3/bg1.mp3");
    		new Vue({
    			el:"#app",
    			data(){
    				return {
    					user_data:{},  // 当前用户信息
              music_play:true,
              namespace: '/orchard', // websocket命名空间
              socket: null, // websocket连接对象
    					recharge_list: [], // 允许充值的金额列表
    					package_init_setting: {}, // 背包初始配置信息
    					user_info: {}, // 用户登陆初始化化信息
    					user_package_info: {}, // 用户背包信息
    					user_pet_info: {}, // 用户宠物信息
    					pet_position:null, // 宠物栏位置
    				}
    			},
          created(){
    				// socket建立连接
    				this.socket_connect();
            // 自动加载我的果园页面
    				this.to_my_orchard();
    				// 获取用户数据
    				this.get_user_data()
    				// 监听事件变化
    				this.listen()
    				// 获取充值金额列表
    				this.get_recharge_list()
          },
    			methods:{
    				back(){
    					this.game.openWin("root");
    				},
    				// 监听事件
    				listen(){
    					// 1.监听token更新的通知
    					this.listen_update_token();
    					// 2.监听是否购买道具成功的通知
    					this.listen_buy_prop_success();
    					// 3.监听是否使用道具成功的通知
    					// this.listen_use_prop_success();
    					// 4.监听解锁背包容量的通知
    					this.listen_unlock_package();
    					// 5.监听是否用其他页面需要获取用户宠物信息
    					this.listen_get_user_pet_info();
    					// 6.监听用户宠物是否死亡的通知
    					this.listen_user_pet_die();
    					// 7.监听是否使用了道具
    					this.listen_use_prop();
    					// 8.监听解锁宠物栏的通知
    					this.listen_unlock_pet_field();
    					// 9.监听喂养宠物的通知
    					this.listen_feed_pet();
    				},
    
    				// 1.监听token更新的通知
    				listen_update_token(){
    					api.addEventListener({
    							name: 'update_token'
    					}, (ret, err) => {
    						// 更新用户数据
    							this.get_user_data()
    					});
    				},
    
    				// 2.监听是否购买道具成功的通知
    				listen_buy_prop_success(){
    					api.addEventListener({
    					    name: 'buy_prop_success'
    					}, (ret, err)=>{
    							// 发送请求,更新用户背包数据
    							this.socket.emit('user_package');
    					});
    				},
    
    				// 3.监听解锁背包容量的通知
    				listen_unlock_package(){
    					api.addEventListener({
    							name: 'unlock_package'
    					}, (ret, err)=>{
    						// 发送解锁背包的请求
    						this.socket.emit('unlock_package');
    						// 获取解锁背包响应数据
    						this.socket.on('unlock_package_response',(response)=>{
    							if(response.errno === 1000){
    								// 更新用户背包数据
    								this.socket.emit('user_package')
    								// 用户积分改变,重新刷新token值
    								this.refresh_user_token();
    							}
    						})
    					});
    				},
    
    				// 5.监听是否用其他页面需要获取用户宠物信息
    				listen_get_user_pet_info(){
    					api.addEventListener({
    					    name: 'get_user_pet_info'
    					}, (ret, err)=>{
    						 // 获取用户宠物信息
    						 this.get_user_pet_info();
    					});
    				},
    
    				// 6.监听用户宠物是否死亡的通知
    				listen_user_pet_die(){
    					api.addEventListener({
    					    name: 'user_pet_die'
    					}, (ret, err)=>{
    						 // 结束宠物死亡位置栏参数
    						 this.pet_position = ret.value.pet_position
    						 // 用户宠物死亡,发送请求
    						 this.socket.emit('user_pet_die',{
    							 pet_position:this.pet_position
    						 })
    						 // 响应用户宠物死亡
    						 this.socket.on('user_pet_die_response',(response)=>{
    							 if(response.errno === 1000){
    								 this.game.tips("宠物"+this.pet_position+"已经死亡了")
    							 }else {
    							 	this.game.tips(response.errmsg)
    							 }
    						 })
    					});
    				},
    
    				// 7.监听是否使用了道具
    				listen_use_prop(){
    					api.addEventListener({
    					    name: 'use_prop'
    					}, (ret, err)=>{
    						// 使用宠物道具
    						if(ret.value.prop_type == 'pet'){
    							// 发送使用宠物道具请求
    							this.socket.emit('use_pet_prop',{
    								prop_id: ret.value.prop_id,
    								pet_position: ret.value.pet_position
    							});
    							// 响应数据
    							this.socket.on('use_pet_prop_response',(response)=>{
    								if(response.errno === 1000){
    									this.game.tips('宠物道具使用成功!')
    								}else {
    									this.game.tips(response.errmsg)
    								}
    							})
    						}
    					});
    				},
    
    				// 8.监听解锁宠物栏的通知
    				listen_unlock_pet_field(){
    					let self = this
    					api.addEventListener({
    					    name: 'unlock_pet_field'
    					}, (ret, err)=>{
    						// 发送解锁宠物栏的请求
    						self.socket.emit('unlock_pet_field')
    						// 接收响应
    						self.socket.on('unlock_pet_field_response',(response)=>{
    							if(response.errno === 1000){
    								self.game.tips('宠物栏解锁成功!')
    								// 用户金钱积分信息改变,重新刷新token值
    								self.refresh_user_token();
    							}else {
    								self.game.tips(response.errmsg)
    							}
    						})
    					});
    				},
    
    				// 9.监听喂养宠物的通知
    				listen_feed_pet(){
    					api.addEventListener({
    					    name: 'feed_pet'
    					}, (ret, err)=>{
    						// 选择喂养宠物的食物
    						this.select_pet_food(ret.value.pet_position)
    					});
    				},
    
    				// 9.1 提供背包中所有的宠物粮让用户选择使用
    				select_pet_food(pet_position){
    					let food_list = []
    					let buttons = []
    					// 循环背包列表
    					this.user_package_info.props_list.forEach((food,key)=>{
    						// this.game.print(food,1)
    						if(food.type == 'pet_food'){
    							food_list.push(food)
    							buttons.push(`${food.prop_name} - 数量: ${food.num}`)
    						}
    					})
    					// 寻找想要使用的宠物粮
    					api.actionSheet({
    					    title: '请选择使用的宠物粮道具',
    					    cancelTitle: '取消',
    					    buttons: buttons,
    					}, (ret, err)=>{
    						// 选择好喂养的宠物粮,发送请求与响应
    						if(ret.buttonIndex <= buttons.length){
    							// 发送请求喂养宠物
    							this.socket.emit('feed_pet',{
    								'pet_position':pet_position,
    								'prop_id': food_list[ret.buttonIndex-1].prop_id
    							})
    							// 响应数据
    							this.socket.on('feed_pet_response',(response)=>{
    								if(response.errno === 1000){
    									this.game.tips('喂养宠物成功~')
    								}else {
    									this.game.tips(response.errmsg)
    								}
    							})
    						}
    					});
    				},
    
    				// 获取用户宠物信息
    				get_user_pet_info(){
    					// 发起请求用户宠物信息
    					this.socket.emit('user_pet_info');
    					// 获取响应
    					this.socket.on('user_pet_info_response', (response)=>{
    						if(response.errno === 1000){
    							// 响应成功,获取宠物信息
    							this.user_pet_info = response.user_pet_info
    							// 通知其他页面,已经获取用户宠物信息
    							this.game.sendEvent('get_user_pet_info_success',{
    								user_pet_info: this.user_pet_info
    							})
    						}else {
    							this.game.tips(response.errmsg)
    						}
    					})
    				},
    
    				// 用户积分改变,重新刷新token值
    				refresh_user_token(){
    					let self = this
    					self.game.check_user_login(self, ()=>{
    						let token = self.game.getdata('refresh_token') || self.game.getfs('refresh_token')
    						self.game.post(self,{
    							'method': 'Users.refresh',
    							'params':{},
    							'header':{
    								'Authorization': 'jwt ' + token
    							},
    							success(response){
    								let data = response.data;
    								if(data.result && data.result.errno === 1000){
    									// 刷新token值
    									let token = self.game.getfs("refresh_token");
    	                if(token){
    	                    // 记住密码的情况
    	                    self.game.deldata(["access_token","refresh_token"]);
    	                    self.game.setfs({
    	                        "access_token": data.result.access_token,
    	                        "refresh_token": data.result.refresh_token,
    	                    });
    	                }else{
    	                    // 不记住密码的情况
    	                    self.game.delfs(["access_token","refresh_token"]);
    	                    self.game.setdata({
    	                        "access_token": data.result.access_token,
    	                        "refresh_token": data.result.refresh_token,
    	                    });
    	                }
    									// 发布全局广播,token更新
    									self.game.sendEvent('update_token')
    								}
    							}
    						})
    					})
    				},
    
    
    				// 通过token值获取用户数据
    				get_user_data(){
    					let self = this;
    					// 检查token是否过期,过期从新刷新token
    					self.game.check_user_login(self, ()=>{
    						// 获取token
    						let token = this.game.getfs('access_token') || this.game.getdata('access_token')
    						// 根据token获取用户数据
    						this.user_data = this.game.get_user_by_token(token)
    					})
    				},
    
    				// websocket连接处理
            socket_connect(){
              this.socket = io.connect(this.game.config.SOCKET_SERVER + this.namespace, {transports: ['websocket']});
              this.socket.on('connect', ()=>{
                  this.game.print("开始连接服务端");
    							// 获取背包初始配置信息
    							this.get_package_setting()
    							// websocket登陆处理
    							this.user_websocket_login()
              });
            },
    
    				// 获取背包初始配置信息
    				get_package_setting(){
    					this.socket.on('init_config_response', (response)=>{
    						// this.game.print(response,1)
    						this.package_init_setting = response
    					})
    				},
    
    				// websocket登陆处理
    				user_websocket_login(){
    					// 客户端发送用户登陆请求
    					this.socket.emit('login',{'uid': this.user_data.unique_id});
    					// 接收登陆响应
    					this.login_response();
    					// 接收用户背包响应
    					this.user_package_response();
    				},
    
    				// 接收登陆初始化信息
    				login_response(){
    					this.socket.on('login_response',(response)=>{
    						// this.game.print(response,1)
    						if(response.errno === 1000){
    							this.user_info = response.user_info
    						}
    					})
    				},
    
    				// 接收用户背包信息
    				user_package_response(){
    					this.socket.on('user_package_response',(response)=>{
    						// this.game.print(response,1)
    						if(response.errno === 1000){
    							this.user_package_info = response.package_info;
    							// 全局广播用户背包更新
    							this.game.sendEvent('package_update',{
    								user_package_info: this.user_package_info
    							})
    						}else {
    							this.game.tips(response.errmsg)
    						}
    					})
    				},
    
    
            // 跳转我的果园页面
            to_my_orchard(){
              this.game.check_user_login(this, ()=>{
                this.game.openFrame("my_orchard","my_orchard.html","from_right",{
        					marginTop: 174,     //相对父页面上外边距的距离,数字类型
        					marginLeft: 0,      //相对父页面左外边距的距离,数字类型
        			    marginBottom: 54,     //相对父页面下外边距的距离,数字类型
        			    marginRight: 0     //相对父页面右外边距的距离,数字类型
        				});
              })
            },
    
            // 点击商店打开道具商城页面
    				to_shop(){
              this.game.check_user_login(this, ()=>{
                this.game.openFrame('shop', 'shop.html', 'from_top');
              })
            },
    
    				// 点击头像,跳转用户中心页面
    				to_user(){
    					this.game.check_user_login(this, ()=>{
                this.game.openFrame('user', 'user.html', 'from_right');
              })
    				},
    
    				// 获取充值金额列表
    				get_recharge_list(){
    					let self = this;
    					self.game.check_user_login(self,()=>{
    						self.game.post(self,{
    							'method': 'Users.recharge_list',
    							'params': {},
    							success(response){
    								let data = response.data;
    								if(data.result && data.result.errno === 1000){
    									self.recharge_list = data.result.recharge_list
    								}
    							}
    						})
    					})
    				},
    
    				// 点击钱包进行用户充值,设置充值金额
    				user_recharge(){
    					api.actionSheet({
    								title: '余额充值',
    								cancelTitle: '取消',
    								buttons: this.recharge_list
    						}, (ret, err)=>{
    								if( ret.buttonIndex <= this.recharge_list.length ){
    										 // 充值金额
    										 let money = this.recharge_list[ret.buttonIndex-1];
    										//  this.game.print(money,1);
    										// 发送支付宝充值请求
    										this.recharge_app_pay(money)
    								}
    					});
    				},
    
    				// 发送支付宝充值请求
    				recharge_app_pay(money){
    					// 获取支付宝支付对象
    					let aliPayPlus = api.require("aliPayPlus");
    					let self = this;
    					// 向服务端发送请求获取终止订单信息
    					self.game.check_user_login(self, ()=>{
    						let token = this.game.getdata("access_token") || this.game.getfs("access_token");
    						self.game.post(self,{
    							'method': 'Users.recharge',
    							'params':{
    								'money': money,
    							},
    							'header': {
    								'Authorization': 'jwt ' + token
    							},
    							success(response){
    								let data = response.data;
    								if(data.result && data.result.errno === 1000){
    									// 本次充值的订单参数
    									let order_string = data.result.order_string;
    
    									// 支付完成以后,支付APP返回的响应状态码
    									let resultCode = {
    										"9000": "支付成功!",
    										"8000": "支付正在处理中,请稍候!",
    										"4000": "支付失败!请联系我们的工作人员~",
    										"5000": "支付失败,重复的支付操作",
    										"6002": "网络连接出错",
    										"6004": "支付正在处理中,请稍后",
    									}
    									// 唤醒支付宝APP,发起支付
    									aliPayPlus.payOrder({
    										orderInfo: order_string,
    										sandbox: data.result.sandbox, // 将来APP上线需要修改成false
    									},(ret, err)=>{
    										if(resultCode[ret.code]){
    											// 提示支付结果
    											if(ret.code != 9000){
    												self.game.tips(resultCode[ret.code]);
    											}else {
    												// 支付成功,向服务端请求验证支付结果 - 参数订单号
    												self.check_recharge_result(data.result.order_number);
    											}
    										}
    									})
    								}
    							}
    						})
    					})
    				},
    
    				// 向服务端发送请求,校验充值是否成功
    				check_recharge_result(order_number){
    					let self = this;
    					self.game.check_user_login(self, ()=>{
    						let token = this.game.getdata("access_token") || this.game.getfs("access_token");
    						self.game.post(self,{
    							'method': 'Users.check_recharge_result',
    							'params':{
    								'order_number':order_number,
    							},
    							'header':{
    								'Authorization': 'jwt ' + token
    							},
    							success(response){
    								let data = response.data;
    								if(data.result && data.result.errno === 1000){
    									// 充值成功
    									self.game.tips('充值成功!')
    									// 用户数据更改过,重新刷新token值
    									let token = self.game.getfs("access_token");
    									// 删除token值
    									self.game.deldata(["access_token","refresh_token"]);
    									self.game.delfs(["access_token","refresh_token"]);
    									if(token){
                        // 记住密码的情况
                        self.game.setfs({
                          "access_token": data.result.access_token,
                          "refresh_token": data.result.refresh_token,
                        });
                      }else{
                        // 不记住密码的情况
                        self.game.setdata({
                          "access_token": data.result.access_token,
                          "refresh_token": data.result.refresh_token,
                        });
    									}
    									// 全局广播充值成功
    									self.game.sendEvent('recharge_success')
    									// 全局广播刷新token值
    									self.game.sendEvent('update_token')
    								}
    							}
    						})
    					})
    				},
    
    				// 点击背包,跳转到背包页面,并传递背包初始配置信息
    				to_package(){
    					this.game.check_user_login(this, ()=>{
    						this.game.openFrame('package', 'package.html', 'from_top',null, {
    							'package_init_setting': this.package_init_setting,
    							'user_package_info': this.user_package_info,
    							'user_info': this.user_info
    						})
    					})
    				},
    
    
    			}
    		});
    	}
    	</script>
    </body>
    </html>
    
    
    
    服务端
    1. 喂养宠物视图 ,orchard.socket,代码:
    # 喂养宠物
        @get_user_object_by_socket_sid
        def on_feed_pet(self,user,data):
            '''
            喂养宠物
            :param user: 根据回话ID获取的用户模型对象
            :param data: 客户端传回的数据,宠物位置,宠物粮道具id
            :return:
            '''
            # print(data)
            pet_position = data.get('pet_position')
            prop_id = data.get('prop_id')
    
            # 1.判断用户是否存在(检查回话状态)
            if user is None:
                # 响应数据
                emit('feed_pet_response', {
                    'errno': code.CODE_USER_NOT_EXISTS,
                    'errmsg': message.user_not_exists,
                })
                # 停止程序继续运行
                return
    
            # 2.使用宠物粮道具,并判断道具是否存在,增加宠物的饱食度,减少背包道具数量
            try:
                services.use_pet_food_prop(user.id, pet_position, prop_id)
            except services.PropNoExit:
                emit('feed_pet_response', {
                    'errno': code.CODE_NO_SUCH_PROP,
                    'errmsg': message.no_such_prop,
                })
                # 停止程序继续运行
                return
    
            # 3. 更新用户背包和用户宠物信息
            self.on_user_package()
            self.on_user_pet_info()
    
            # 响应数据
            emit('feed_pet_response', {
                'errno': code.CODE_OK,
                'errmsg': message.ok,
            })
    
    
    1. 数据处理层,orchard.serivces,代码:
    class PropNoExit(Exception):
        """道具不存在"""
        pass
    
    # 使用宠物粮道具,增加用户宠物的饱食度,减少背包道具数量
    def use_pet_food_prop(user_id, pet_position, prop_id):
        '''
        使用宠物粮道具
        :param user_id: 用户ID
        :param pet_position: 宠物位置
        :param prop_id: 使用宠物粮道具ID
        :return:
        '''
        # 1. 判断用户背包中是否有对应的宠物粮道具
        user_package = UserPackageDocument.objects.get(user_id=user_id)
        current_prop = {}  # 背包道具列表中的当前使用的宠物信息
        current_key = -1  # 背包道具列表索引
        for key, prop in enumerate(user_package['props_list']):
            # 必须确定道具类型,
            if prop['type'] == 'pet_food':
                if prop_id == prop['prop_id']:
                    current_prop = prop
                    current_key = key
                    break
    
        if current_key == -1:
            # 道具不存在
            raise PropNoExit
    
        # 减少背包中道具数量
        if current_prop['num'] <= 1:
            # 如果只有一个道具的情况, 使用玩了以后直接删除道具项
            if len(user_package['props_list']) == 1:
                # 如果道具列表只剩一个道具,重置道具列表
                user_package['props_list'] = []
            else:
                # 否则删除该道具项
                user_package['props_list'].remove(current_prop)
        else:
            # 有多个道具,数据减1
            user_package['props_list'][current_key]['num'] = int(user_package["props_list"][current_key]["num"]) - 1
        # 保存更改的数据
        user_package.save()
    
        # 2.增加宠物的饱食度
        # 2.1 获取当前宠物的存活时间
        pet_ttl = get_pet_ttl(user_id,pet_position)
        if pet_ttl < 1:
            # 宠物道具不存在
            user_pet_die(user_id, pet_position)
            raise PropNoExit
        # 2.2 获取宠物粮信息
        pet_food = get_pet_food_item(prop_id)
        # 2.3 添加用户宠物的饱食度
        new_ttl = int(pet_ttl) + int(pet_food['food_dot'] * config.PET_MAX_TIME / 100)
        # 判断时间是否超出宠物最大存活时间
        if new_ttl > config.PET_MAX_TIME:
            new_ttl = config.PET_MAX_TIME
    
        redis_orchard.expire(f'user:{user_id}:pet:{pet_position}', new_ttl)
    
        # 3.添加道具使用记录
        UsePropLogDocument.objects.create(
            user_id=user_id,
            prop_type=current_prop["type"],
            prop_id=current_prop["prop_id"],
            prop_name=current_prop["prop_name"],
            prop_image=current_prop["prop_image"],
            prop_num=1,
            use_time=datetime.now().strftime("%Y-%m-%d %H:%M:%S"),
            remark={},
        )
    
    
  • 相关阅读:
    OK335x mksd.sh hacking
    Qt jsoncpp 对象拷贝、删除、函数调用 demo
    OK335xS 256M 512M nand flash make ubifs hacking
    Qt QScrollArea and layout in code
    JsonCpp Documentation
    Qt 4.8.5 jsoncpp lib
    Oracle数据库生成UUID
    freemarker得到数组的长度
    FreeMarker中if标签内的判断条件
    freemarker语法
  • 原文地址:https://www.cnblogs.com/jia-shu/p/14977612.html
Copyright © 2011-2022 走看看