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={},
        )
    
    
  • 相关阅读:
    fetch数据请求
    React exact路由精确匹配
    React Router中的Link和NavLink组件有什么区别?
    new FileReader() 文件上传
    git stash部分文件
    主机屋的免费云虚拟主机
    MySQL数据库默认的存储引擎类型是MyISAM并不支持事务的坑,要把存储类型改为InnoDB
    使用ExcelPackage,OfficeOpenXml做EXCEL导入时一个方法的坑,对应类的字段只能定义为string类型
    mysql两个关联表,同名字段同时返回时,原字段名写靠前的表的字段名会可以保留原字段名,靠后的会被自动as为(1)这样的,
    MySql 里的IFNULL、NULLIF和ISNULL用法-区别于MSSQL
  • 原文地址:https://www.cnblogs.com/jia-shu/p/14977612.html
Copyright © 2011-2022 走看看