欢迎来到十九分快乐的博客

生死看淡,不服就干。

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("数据类型不正确\n当前Faker生成数据类型仅支持: %s" % self.type_list)
            return None

        num = int(num)
        if num < 1:
            print("生成数量不正确\n当前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="{width: 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="{width: 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="{width: 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="{width: 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="{width: 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="{width: 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;
  width: 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);
  width: 1.2rem;
  height: 1.5rem;
  border-radius: 50%;
  transform: rotate(45deg) scale(0.8);
}
.popped img{
  width: 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="{width: 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="{width: 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="{width: 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="{width: 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={},
    )

posted @ 2021-07-06 16:36  十九分快乐  阅读(73)  评论(0编辑  收藏  举报