魔方APP项目-03-服务端基于Flask-JSONRPC提供RPC接口、接口文档编写、客户端展示页面,首页显示,登陆页显示,注册页显示,在APP进行窗口和页面操作

一、服务端基于Flask-JSONRPC提供RPC接口

JSON-RPC是一个无状态的、轻量级的远程过程调用(RPC)协议。

所谓的RPC,Remote Procedure Call的简写,中文译作远程过程调用或者远程服务调用

直观的理解就是,通过网络请求远程服务,获取指定接口的数据,而不用知晓底层网络协议的细节。

RPC支持的格式很多,比如XML格式,JSON格式等等。最常用的肯定是json-rpc。

JSON-RPC协议中的客户端一般是为了向远程服务器请求执行某个方法/函数。客户端向实现了JSON-RPC协议的服务端发送请求,多个输入参数能够通过数组或者对象传递到远程方法,这个远程方法也能返回多个输出数据,具体是什么,当然要看具体的方法实现。因为RPC可以通俗理解为:
客户端请求服务端完成某一个服务行为,所以RPC规范要求: 客户端发送的所有请求都是POST请求!!!

所有的传输数据都是单个对象,用JSON格式进行序列化。
请求要求包含三个特定属性:

jsonrpc: 用来声明JSON-RPC协议的版本,现在基本固定为“2.0”

method,方法,是等待调用的远程方法名,字符串类型

params,参数,对象类型或者是数组,向远程方法传递的多个参数值

id,任意类型值,用于和最后的响应进行匹配,也就是这里设定多少,后面响应里这个值也设定为相同的
响应的接收者必须能够给出所有请求以正确的响应。这个值一般不能为Null,且为数字时不能有小数。

响应也有三个属性:

jsonrpc, 用来声明JSON-RPC协议的版本,现在基本固定为“2.0”

result,结果,是方法的返回值,调用方法出现错误时,必须不包含该成员。

error,错误,当出现错误时,返回一个特定的错误编码,如果没有错误产生,必须不包含该成员。

id,就是请求带的那个id值,必须与请求对象中的id成员的值相同。请求对象中的id时发生错误(如:转换错误或无效的请求),它必须为Null

当然,有一些场景下,是不用返回值的,比如只对客户端进行通知,由于不用对请求的id进行匹配,所以这个id就是不必要的,置空或者直接不要了。

在flask中要实现提供json-rpc接口,开发中一般使用Flask JSON-RPC模块来实现。
git地址:https://github.com/cenobites/flask-jsonrpc
文档:http://wiki.geekdream.com/Specification/json-rpc_2.0.html

1.安装Flask-JSONRPC模块

# pip install Flask-JSONRPC          # 如果出现问题,则降低版本到0.3.1
pip install Flask-JSONRPC==0.3.1

快速实现一个测试的RPC接口。
例如,我们直接在application/__init__.py项目初始化文件中进行初始化jsonrpc并关闭csrf防范机制,代码。

import os,logging

from flask import Flask
from flask_script import Manager
from flask_sqlalchemy import SQLAlchemy
from flask_redis import FlaskRedis
from flask_session import Session
from flask_migrate import Migrate,MigrateCommand
from flask_jsonrpc import JSONRPC

from application.utils import init_blueprint
from application.utils.config import load_config
from application.utils.session import init_session
from application.utils.logger import Log
from application.utils.commands import load_command
# 创建终端脚本管理对象
manager = Manager()

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

# redis链接对象
redis = FlaskRedis()

# Session存储对象
session_store = Session()

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

# 日志对象
log = Log()

# 初始化jsonrpc模块
jsonrpc = JSONRPC(service_url='/api')

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

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

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

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

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

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

    # 蓝图注册
    init_blueprint(app)

    # 初始化json-rpc
    jsonrpc.init_app(app)

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

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

    return manager

application/apps/home/views.py,编写接口代码:

# 实现rpc接口
from application import jsonrpc
@jsonrpc.method(name="Home.index")
def index():
    return "hello world!"

客户端需要发起post请求,访问地址为:http://服务器地址:端口/api
注意:
默认情况下,/api接口只能通过post请求访问。如果要使用jsonrpc提供的界面调试工具,则访问地址为:

http://服务器地址端口/api/browse/

可以通过postman发送请求接口,访问数据格式应是:

请求地址:http://127.0.0.1:5000/api
请求体:
{
    "jsonrpc": "2.0",
    "method": "Home.index",
    "params": {},
    "id": "1"
}

请求效果:

错误提示:如果访问浏览器页面空白,终端显示如下:

则是因为Flask版本与 Werkzeug 组件版本不兼容的问题导致。
解决方案1,降低Werkzeug版本到0.16以下版本:

pip uninstall Werkzeug
pip install Werkzeug==0.16.1

解决方案2,修改flask/json.py文件中源码的判断条件,把is_xhr改成is_json

if current_app.config['JSONIFY_PRETTYPRINT_REGULAR'] and not request.is_json:

2.基于api接口接受来自客户端的参数

接口代码:

from application import jsonrpc
@jsonrpc.method(name="Home.index")
def index(id):
    return "hello world!id=%s" % id

postman, 发起请求:

3.移动端访问测试接口

因为当前我们的服务端项目安装在虚拟机里面,并且我们设置了虚拟机的网络连接模式为NAT,所以一般情况下,我们无法直接通过手机访问虚拟机。因此,我们需要配置一下。
1.打开VM的“编辑“菜单,选中虚拟网络编辑器。

2.打开编辑器窗口,使用管理员权限,并点击“NAT设置”。

3.填写网关IP地址,必须和子网IP在同一网段。末尾一般为1。接着在端口转发下方点击“添加”。

4在映射传入端口中,填写转发的端口和实际虚拟机的IP端口,填写完成以后,全部点击“确定”,关闭所有窗口。将来,手机端访问PC主机的5000端口就自动访问到虚拟机。5000是自定义的,可以是其他端口。

5.查看手机wifi连接中,当前手机的IP地址。那么接下来,手机要访问到虚拟机的地址就是当前手机的前面三段+:5000即可。
6.在APP中通过访问192.168.204.129:5000/api/browse

出现页面则表示访问成功。 # 二、接口文档编写 这里我们使用showdoc接口文档编写工具. 官网:

安装完成以后,第一次使用需要配置一个服务端地址, 这个服务端地址,可以是本地开发的真实服务端地址,也可以官方提供的测试地址.
接口文档编写示例效果:

##### 简要描述

- 首页轮播图列表接口

##### 请求URL
- ` http://xx.com/api
  
##### 请求方式
- POST 

##### 参数
|参数名|类型|值|
| :-----  |:-----|-----
| id |uuid   | 19be78d5-2301-4c85-98cb-ee99562ecf57 |
| jsonrpc | string  | "2.0" |
| method | string   | "Home.banner" |
| params | string   | {} |
| length | string   | params列表成员, 轮播图长度, 默认为2 |

##### 请求示例 
```javascript
{
    "jsonrpc": "2.0",
    "id": "148c96a5-456c-43ba-a534-ebb0b54311cc",
    "method": "Home.banner",
    "params": {
		"length": 3
	}
}
```
##### 备注 
- id只需要根据uuid格式生成即可。
- jsonrpc必须值为2
- 如果当前方法不需要参数,则params值为{}


##### 返回示例 

```javascript
{
  "id": "19be78d5-2301-4c85-98cb-ee99562ecf57",
  "jsonrpc": "2.0",
  "result": [
    {
      "image": "/static/banner/hero1.jpg",
      "is_http": false,
      "link": "/"
    },
    {
      "image": "/static/banner/hero2.jpg",
      "is_http": false,
      "link": "/login"
    },
    {
      "image": "/static/banner/hero1.jpg",
      "is_http": true,
      "link": "http://www.baidu.com"
    }
  ]
}
```

##### 返回参数说明 

|参数名|类型|说明|
|:-----  |:-----|-----                           |
|id |uuid   |唯一标记ID |
|jsonrpc | int   | rpc协议版本,值固定为2 |
|result | array   | 轮播图列表数据 |
|image | string   | 图片链接地址 |
|is_http | boolean  | 图片点击跳转的链接是否属于站外地址,true表示站外 |
|link | string   | 点击图片后,页面跳转的链接地址 |

##### 备注 

- 空

那么接下来,我们只需要创建一个磨方项目的接口即可.后面有了接口以后就可以编写接口文档了.

三、客户端展示页面

1.首页显示

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/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"></li>
      <li><img class="module2" src="../static/images/image2.png"></li>
      <li><img class="module3" src="../static/images/image3.png"></li>
      <li><img class="module4" src="../static/images/image4.png"></li>
    </ul>
  </div>
  <script>
	apiready = function(){
		var game = new Game("../static/mp3/bg1.mp3");
		// 允许ajax发送请求时附带cookie,设置为不允许
		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();
          }
        }
      },
			methods:{

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

static/css/main.css,代码:

*{user-select: none;}
body,form,input,table,ul,li{margin: 0;padding: 0;}
body,th,td,.p1,.p2{font-family:arial}
p,form,ol,ul,li,dl,dt,dd,h3{margin:0;padding:0;list-style:none;}
table,img{border:0;}
img{max-height: 100%;max-width: 100%;width:100%}
td{font-size:9pt;line-height:18px;}
em{font-style:normal;}
em{font-style:normal;color:#c00;}
a em{text-decoration:underline;}
input,button{outline: 0;}

@keyframes scaleDraw {  /*定义关键帧、scaleDrew是需要绑定到选择器的关键帧名称*/
    0%{ transform: scale(1);  }
    25%{ transform: scale(1.5);  }
    50%{ transform: scale(1);  }
    75%{ transform: scale(1.5);  }
}
@keyframes rotation{
	from {-webkit-transform: rotate(0deg);}
	to {-webkit-transform: rotate(360deg);}
}
html{
	font-size: 12px;
}

.app{
	margin: 0 auto;
	max-width: 1125px;
  width: 100%;
	overflow: hidden;
	max-height: 100%;
  height: 100rem;
	position: relative;
}
.app .music{
	position: absolute;
	width: 5rem;
	height: 5rem;
	top: 3rem;
	right: 3rem;
	z-index: 100;
	border-radius: 50%;
}
.app .music2{
	animation: rotation 6s ease-in infinite;
}
.app .bg{
	margin: 0 auto;
	width: 100%;
	max-width: 100rem;
	position: relative;
	z-index: -1;
}

.app .bg img{
	width: 100rem;
	animation: scaleDraw 120s ease-in infinite;
}
.app .module1{
	position: absolute;
	top: 24.17rem;
	left: 0;
	width: 16.39rem;
	height: 29.72rem;
}
.app .module2{
	position: absolute;
	top: 13.5rem;
	right: 0;
	width: 16rem;
	height: 20.39rem;
}
.app .module3{
	position: absolute;
	width: 11.94rem;
	height: 18.06rem;
	top: 40.56rem;
	right: 2.67rem;
}
.app .module4{
	position: absolute;
	top: 6.94rem;
	left: 5.17rem;
	width: 7.83rem;
	height: 10.72rem;
}

.form{
	margin: 0 auto;
	position: absolute;
	top: 16rem;
	width: 26.94rem;
	left: 0;
	right: 0;
}
.form-title{
	margin: 0 auto;
	width: 16.86rem;
	height: 5.33rem;
	position: relative;
	z-index: 0;
}
.form-title .back{
	position: absolute;
	right: -3rem;
	top: 0.67rem;
	width: 3.83rem;
	height: 3.89rem;
}
.form-data{
	position: relative;
	padding-top: 3.78rem;
  z-index: 0;
}
.form-data-bg{
	position: absolute;
	top: -2.39rem;
	width: 26.94rem;
	left: 0;
	right: 0;
	margin: auto;
	z-index: -1;
}
.form-item{
	width: 23.1rem;
	margin: 0 auto 1.5rem;
}
.form-item label.text{
	width: 6.67rem;
	letter-spacing: 0.13rem;
	text-align: right;
	display: inline-block;
	font-size: 1.33rem;
	margin-bottom: 0.33rem;
	font-weight: bold;
}
.form-item input[type=text],
.form-item input[type=password]
{
	background-color: transparent;
	border-radius: 3px;
	width: 14.44rem;
	border: 0.08rem #666 solid;
	height: 2.33rem;
	font-size: 1rem;
	text-indent: 1rem;
	vertical-align: bottom;
}
.form-item input.code{
	width: 10.67rem;
}
.form-item .refresh{
	width: 2.56rem;
	height: 2.44rem;
	margin-left: 0.67rem;
	vertical-align: middle;
}
.form-item .refresh:active{
	margin-top: .5px;
	margin-bottom: -.5px;
}
.form-item input.agree{
	margin-left: 2.4rem;
	margin-bottom: 0.33rem;
	width: 1rem;
  height: 1rem;
  vertical-align: sub;
}
.form-item .agree_text{
	font-size: 1rem;
}
.form-item input.remember{
	margin-left: 5.83rem;
	vertical-align: sub;
}
.form-item .commit{
	width: 11.5rem;
	height: 3.94rem;
	margin: 0 auto;
	display: block;
}
.form-item .commit:active{
	margin-top: .5px;
	margin-bottom: -.5px;
}
.form-item .toreg{
	margin-left: 3.11rem;
	background: url("../images/btn1.png") no-repeat 0 0;
	background-size: 100% 100%;
}
.form-item .tofind{
	background: url("../images/btn2.png") no-repeat 0 0;
	background-size: 100% 100%;
}
.form-item .toreg,
.form-item .tofind
{
	display: inline-block;
	width: 7.94rem;
	height: 2.5rem;
	margin-right: 1.67rem;
	font-size: 1rem;
	color: #fff;
	line-height: 2.5rem;
	text-align: center;
	user-select: none;
}

static/js/main.js,代码:

class Game{
	constructor(bg_music){
		// 构造函数,相当于python中类的__init__方法
		this.init();
		if(bg_music){
    	this.play_music(bg_music);
		}
	}
	open(){

	}
	init(){
		// 初始化
		console.log("系统初始化");
    this.rem();
	}
	print(data){
		// 打印数据
		console.log(JSON.stringify(data));
	}
	back(prev){
		// 返回上一页
		this.go(prev.name,prev.url,{...prev});
	}
	go(name,url,pageParam){
		// 页面跳转
		this.print(name)
		this.print(url)
		this.print(pageParam)
		// 跳转到指定页面
		api.openFrame({
				name: name,
				url: url,
				rect: {
						x: 0,
						y: 0,
						w: 'auto',
						h: 'auto'
				},
				useWKWebView:true,
				historyGestureEnabled:true,
				bounces:false,
				animation:{
							type:"push",
							subType:"from_right",
							duration:300
				},
				pageParam: pageParam
		});
	}
  rem(){
    if(window.innerWidth<1200){
			this.UIWidth = document.documentElement.clientWidth;
      this.UIHeight = document.documentElement.clientHeight;
      document.documentElement.style.fontSize = (0.01*this.UIWidth*3)+"px";
			document.querySelector("#app").style.height=this.UIHeight+"px"
    }
    window.onresize = ()=>{
      if(window.innerWidth<1200){
        this.UIWidth = document.documentElement.clientWidth;
        this.UIHeight = document.documentElement.clientHeight;
        document.documentElement.style.fontSize = (0.01*this.UIWidth*3)+"px";
      }
    }
  }
	bg(){
		// 背景

	}
	music(){

	}
	stop_music(){
		this.print("停止背景音乐");
		document.body.removeChild(this.audio);
	}
  play_music(src){
		this.print("播放背景音乐");
    this.audio = document.createElement("audio");
    this.source = document.createElement("source");
    this.source.type = "audio/mp3";
    this.audio.autoplay = "autoplay";
    this.source.src=src;
    this.audio.appendChild(this.source);

		/*
     <audio autoplay="autoplay">
		 		<source type="audio/mp3" src="../static/mp3/bg1.mp3">
		 </audio>
		 */
		document.body.appendChild(this.audio);

		// 自动暂停关闭背景音乐
    var t = setInterval(()=>{
      if(this.audio.readyState > 0){
        if(this.audio.ended){
          clearInterval(t);
          document.body.removeChild(this.audio);
        }
      }
    },100);
  }
}

2.登陆页显示

html/login.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/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>
		<div class="form">
			<div class="form-title">
				<img src="../static/images/login.png">
				<img class="back" src="../static/images/back.png">
			</div>
			<div class="form-data">
				<div class="form-data-bg">
					<img src="../static/images/bg1.png">
				</div>
				<div class="form-item">
					<label class="text">手机</label>
					<input type="text" name="mobile" placeholder="请输入手机号">
				</div>
				<div class="form-item">
					<label class="text">密码</label>
					<input type="password" name="password" placeholder="请输入密码">
				</div>
				<div class="form-item">
					<input type="checkbox" class="agree remember" name="agree" checked>
					<label><span class="agree_text ">记住密码,下次免登录</span></label>
				</div>
				<div class="form-item">
					<img class="commit" @click="game.play_music('../static/mp3/btn1.mp3')" src="../static/images/commit.png">
				</div>
				<div class="form-item">
					<p class="toreg">立即注册</p>
					<p class="tofind">忘记密码</p>
				</div>
			</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 {
          music_play:true,
					prev:{name:"",url:"",params:{}},
					current:{name:"login",url:"login.html",params:{}},
				}
			},
			watch:{
        music_play(){
          if(this.music_play){
            this.game.play_music("../static/mp3/bg1.mp3");
          }else{
            this.game.stop_music();
          }
        }
      },
			methods:{
				
			}
		})
	}
	</script>
</body>
</html>

3.注册页显示

<!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/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>
		<div class="form">
			<div class="form-title">
				<img src="../static/images/register.png">
				<img class="back" @click="backpage" src="../static/images/back.png">
			</div>
			<div class="form-data">
				<div class="form-data-bg">
					<img src="../static/images/bg1.png">
				</div>
				<div class="form-item">
					<label class="text">手机</label>
					<input type="text" name="mobile" placeholder="请输入手机号">
				</div>
				<div class="form-item">
					<label class="text">验证码</label>
					<input type="text" class="code" name="code" placeholder="请输入验证码">
					<img class="refresh" src="../static/images/refresh.png">
				</div>
				<div class="form-item">
					<label class="text">密码</label>
					<input type="password" name="password" placeholder="请输入密码">
				</div>
				<div class="form-item">
					<label class="text">确认密码</label>
					<input type="password" name="password2" placeholder="请再次输入密码">
				</div>
				<div class="form-item">
					<input type="checkbox" class="agree" name="agree" checked>
					<label><span class="agree_text">同意磨方《用户协议》和《隐私协议》</span></label>
				</div>
				<div class="form-item">
					<img class="commit" @click="game.play_music('../static/mp3/btn1.mp3')" src="../static/images/commit.png"/>
				</div>
			</div>
		</div>
	</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:"register",url:"register.html","params":{}},
				}
			},
			watch:{
        music_play(){
          if(this.music_play){
            this.game.play_music("../static/mp3/bg1.mp3");
          }else{
            this.game.stop_music();
          }
        }
      },
			methods:{
				backpage(){
					this.prev.name = api.pageParam.name;
					this.prev.url = api.pageParam.url;
					this.prev.params = api.pageParam.params;
					this.game.back(this.prev);
				}
			}
		})
	}
	</script>
</body>
</html>

4.在APP进行窗口和页面操作

在APICloud中提供了2种类型,三种方式给开发者打开或者新建页面.

(1).window 窗口

window是APICloud提供的最顶级的页面单位.一个APP至少会存在一个以上的window窗口,在用户打开APP应用,应用在初始化的时候默认就会创建了一个name=root 的顶级window窗口显示当前APP配置的首页.

①.新建window窗口

注意: 如果之前已经存在同名的窗口,则APP不会再次创建新窗口,而是显示对应名称的窗口.
代码:

api.openWin({
    name: 'page1',        // 自定义窗口名称
    bounces: false,       // 窗口是否上下拉动
    reload: true,         // 如果页面已经在之前被打开了,是否要重新加载当前窗口中的页面
    url: './page1.html',  // 窗口创建时展示的html页面的本地路径[相对于当前代码所在文件的路径]
    animation:{           // 打开新建窗口时的过渡动画效果
    	type:"none",                //动画类型(详见动画类型常量)
    	subType:"from_right",       //动画子类型(详见动画子类型常量)
    	duration:300                //动画过渡时间,默认300毫秒
    },
    pageParam: {          // 传递给下一个窗口使用的参数.将来可以在新窗口中通过 api.pageParam.name 获取
        name: 'test'      // name只是举例, 将来可以传递更多自定义数据的.
    }
});

接下来, 我们就可以在客户端APP的main.js主程脚本中, 添加一个新建窗口的方法了.

class Game{
	constructor(bg_music){
		// 构造函数,相当于python中类的__init__方法
		this.init();
		if(bg_music){
    	this.play_music(bg_music);
		}
	}
	open(){

	}
	init(){
		// 初始化
		console.log("系统初始化");
    this.rem();
	}
	print(data){
		// 打印数据
		console.log(JSON.stringify(data));
	}
	back(prev){
		// 返回上一页
		this.go(prev.name,prev.url,{...prev});
	}
	goWin(name,url,pageParam){
		api.openWin({
		    name: name,            // 自定义窗口名称
		    bounces: false,        // 窗口是否上下拉动
		    reload: true,          // 如果页面已经在之前被打开了,是否要重新加载当前窗口中的页面
		    url: url,              // 窗口创建时展示的html页面的本地路径[相对于当前代码所在文件的路径]
		    animation:{            // 打开新建窗口时的过渡动画效果
		    	type: "push",                //动画类型(详见动画类型常量)
		    	subType: "from_right",       //动画子类型(详见动画子类型常量)
		    	duration:300                //动画过渡时间,默认300毫秒
		    },
		    pageParam: pageParam   // 传递给下一个窗口使用的参数.将来可以在新窗口中通过 api.pageParam.name 获取
		});
	}
	goFrame(name,url,pageParam){
		// 页面跳转
		this.print(name)
		this.print(url)
		this.print(pageParam)
		// 跳转到指定页面
		api.openFrame({
				name: name,
				url: url,
				rect: {
						x: 0,
						y: 0,
						w: 'auto',
						h: 'auto'
				},
				useWKWebView:true,
				historyGestureEnabled:true,
				bounces:false,
				animation:{
							type:"push",
							subType:"from_right",
							duration:300
				},
				pageParam: pageParam
		});
	}
  rem(){
    if(window.innerWidth<1200){
			this.UIWidth = document.documentElement.clientWidth;
      this.UIHeight = document.documentElement.clientHeight;
      document.documentElement.style.fontSize = (0.01*this.UIWidth*3)+"px";
			document.querySelector("#app").style.height=this.UIHeight+"px"
    }
    window.onresize = ()=>{
      if(window.innerWidth<1200){
        this.UIWidth = document.documentElement.clientWidth;
        this.UIHeight = document.documentElement.clientHeight;
        document.documentElement.style.fontSize = (0.01*this.UIWidth*3)+"px";
      }
    }
  }
	bg(){
		// 背景

	}
	music(){

	}
	stop_music(){
		this.print("停止背景音乐");
		document.body.removeChild(this.audio);
	}
  play_music(src){
		this.print("播放背景音乐");
    this.audio = document.createElement("audio");
    this.source = document.createElement("source");
    this.source.type = "audio/mp3";
    this.audio.autoplay = "autoplay";
    this.source.src=src;
    this.audio.appendChild(this.source);

		/*
     <audio autoplay="autoplay">
		 		<source type="audio/mp3" src="../static/mp3/bg1.mp3">
		 </audio>
		 */
		document.body.appendChild(this.audio);

		// 自动暂停关闭背景音乐
    var t = setInterval(()=>{
      if(this.audio.readyState > 0){
        if(this.audio.ended){
          clearInterval(t);
          document.body.removeChild(this.audio);
        }
      }
    },100);
  }
}

在login.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/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>
		<div class="form">
			<div class="form-title">
				<img src="../static/images/login.png">
				<img class="back" src="../static/images/back.png">
			</div>
			<div class="form-data">
				<div class="form-data-bg">
					<img src="../static/images/bg1.png">
				</div>
				<div class="form-item">
					<label class="text">手机</label>
					<input type="text" name="mobile" placeholder="请输入手机号">
				</div>
				<div class="form-item">
					<label class="text">密码</label>
					<input type="password" name="password" placeholder="请输入密码">
				</div>
				<div class="form-item">
					<input type="checkbox" class="agree remember" name="agree" checked>
					<label><span class="agree_text ">记住密码,下次免登录</span></label>
				</div>
				<div class="form-item">
					<img class="commit" @click="game.play_music('../static/mp3/btn1.mp3')" src="../static/images/commit.png">
				</div>
				<div class="form-item">
					<p class="toreg" @click="goto_register">立即注册</p>
					<p class="tofind">忘记密码</p>
				</div>
			</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 {
                    music_play:true,
					prev:{name:"",url:"",params:{}},
					current:{name:"login",url:"login.html",params:{}},
				}
			},
			watch:{
                music_play(){
                  if(this.music_play){
                    this.game.play_music("../static/mp3/bg1.mp3");
                  }else{
                    this.game.stop_music();
                  }
                }
      		},
			methods:{
				goto_register(){
            		this.game.goWin("register","./register.html", this.current);
            	}
			}
		})
	}
	</script>
</body>
</html>
②.关闭指定名称的窗口

如果当前APP中只有剩下一个顶级窗口root,则无法通过当前方法关闭! 也有部分手机直接退出APP了

//关闭当前window,使用默认动画
api.closeWin();

//关闭指定window,若待关闭的window不在最上面,则无动画
api.closeWin({
    name: 'page1'
});

接下来我们可以把关闭窗口的代码封装到主程脚本main.js中.

class Game{
	constructor(bg_music){
		// 构造函数,相当于python中类的__init__方法
		this.init();
		if(bg_music){
    	this.play_music(bg_music);
		}
	}
	open(){

	}
	init(){
		// 初始化
		console.log("系统初始化");
    this.rem();
	}
	print(data){
		// 打印数据
		console.log(JSON.stringify(data));
	}
	back(prev){
		// 返回上一页
		this.go(prev.name,prev.url,{...prev});
	}
	outWin(name){
		// 关闭窗口
		api.closeWin(name);
	}
	goWin(name,url,pageParam){
		// 新建窗口
		api.openWin({
		    name: name,            // 自定义窗口名称
		    bounces: false,        // 窗口是否上下拉动
		    reload: true,          // 如果页面已经在之前被打开了,是否要重新加载当前窗口中的页面
		    url: url,              // 窗口创建时展示的html页面的本地路径[相对于当前代码所在文件的路径]
		    animation:{            // 打开新建窗口时的过渡动画效果
		    	type: "push",                //动画类型(详见动画类型常量)
		    	subType: "from_right",       //动画子类型(详见动画子类型常量)
		    	duration:300                //动画过渡时间,默认300毫秒
		    },
		    pageParam: pageParam   // 传递给下一个窗口使用的参数.将来可以在新窗口中通过 api.pageParam.name 获取
		});
	}
	goFrame(name,url,pageParam){
		// 页面跳转
		this.print(name)
		this.print(url)
		this.print(pageParam)
		// 跳转到指定页面
		api.openFrame({
				name: name,
				url: url,
				rect: {
						x: 0,
						y: 0,
						w: 'auto',
						h: 'auto'
				},
				useWKWebView:true,
				historyGestureEnabled:true,
				bounces:false,
				animation:{
							type:"push",
							subType:"from_right",
							duration:300
				},
				pageParam: pageParam
		});
	}
  rem(){
    if(window.innerWidth<1200){
			this.UIWidth = document.documentElement.clientWidth;
      this.UIHeight = document.documentElement.clientHeight;
      document.documentElement.style.fontSize = (0.01*this.UIWidth*3)+"px";
			document.querySelector("#app").style.height=this.UIHeight+"px"
    }
    window.onresize = ()=>{
      if(window.innerWidth<1200){
        this.UIWidth = document.documentElement.clientWidth;
        this.UIHeight = document.documentElement.clientHeight;
        document.documentElement.style.fontSize = (0.01*this.UIWidth*3)+"px";
      }
    }
  }
	bg(){
		// 背景

	}
	music(){

	}
	stop_music(){
		this.print("停止背景音乐");
		document.body.removeChild(this.audio);
	}
  play_music(src){
		this.print("播放背景音乐");
    this.audio = document.createElement("audio");
    this.source = document.createElement("source");
    this.source.type = "audio/mp3";
    this.audio.autoplay = "autoplay";
    this.source.src=src;
    this.audio.appendChild(this.source);

		/*
     <audio autoplay="autoplay">
		 		<source type="audio/mp3" src="../static/mp3/bg1.mp3">
		 </audio>
		 */
		document.body.appendChild(this.audio);

		// 自动暂停关闭背景音乐
    var t = setInterval(()=>{
      if(this.audio.readyState > 0){
        if(this.audio.ended){
          clearInterval(t);
          document.body.removeChild(this.audio);
        }
      }
    },100);
  }
}

html/register.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/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>
		<div class="form">
			<div class="form-title">
				<img src="../static/images/register.png">
				<img class="back" @click="back" src="../static/images/back.png">
			</div>
			<div class="form-data">
				<div class="form-data-bg">
					<img src="../static/images/bg1.png">
				</div>
				<div class="form-item">
					<label class="text">手机</label>
					<input type="text" name="mobile" placeholder="请输入手机号">
				</div>
				<div class="form-item">
					<label class="text">验证码</label>
					<input type="text" class="code" name="code" placeholder="请输入验证码">
					<img class="refresh" src="../static/images/refresh.png">
				</div>
				<div class="form-item">
					<label class="text">密码</label>
					<input type="password" name="password" placeholder="请输入密码">
				</div>
				<div class="form-item">
					<label class="text">确认密码</label>
					<input type="password" name="password2" placeholder="请再次输入密码">
				</div>
				<div class="form-item">
					<input type="checkbox" class="agree" name="agree" checked>
					<label><span class="agree_text">同意磨方《用户协议》和《隐私协议》</span></label>
				</div>
				<div class="form-item">
					<img class="commit" @click="game.play_music('../static/mp3/btn1.mp3')" src="../static/images/commit.png"/>
				</div>
			</div>
		</div>
	</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:"register",url:"register.html","params":{}},
				}
			},
			watch:{
        music_play(){
          if(this.music_play){
            this.game.play_music("../static/mp3/bg1.mp3");
          }else{
            this.game.stop_music();
          }
        }
      },
			methods:{
				back(){
          			this.game.outWin();
				}
			}
		})
	}
	</script>
</body>
</html>

(2).frame 帧页面

如果APP中所有的页面全部窗口进行展开,则APP需要耗费大量的内存来维护这个窗口列表,从而导致, 用户操作APP时,APP响应
缓慢甚至卡顿的现象.所以APP中除了通过新建窗口的方式展开页面以外, 还提供了帧的方式来展开页面.
帧,代表的就是一个窗口下开打的某个页面记录.所谓的帧就有点类似于浏览器中窗口通过地址栏新建的一个页面一样.
使用的时候注意: 
    1. APP每一个window窗口都可以打开1到多个帧.新建窗口的时候,系统会默认顺便创建第一帧出来.
    2. 每一帧代表的都是一个html页面,
    3. 默认情况下, APP的window的窗口会自动默认满屏展示.而帧可以设置矩形的宽高.如果顶层的帧页面没有满屏显示,则
用户可以看到当前这一帧下的其他帧的内容.
①.新建帧页面
api.openFrame({
    name: 'page2',        // 帧页面的名称
    url: './page2.html',  // 帧页面打开的url地址
    data: '',             // 可选参数,如果填写了data,则不要使用url, data表示页面数据,可以是html代码
    bounces:false,        // 页面是否可以下拉拖动
    reload: true,         // 帧页面如果已经存在,是否重新刷新加载
    useWKWebView:true,
    historyGestureEnabled:true,
    animation:{
        type:"push",             //动画类型(详见动画类型常量)
    	subType:"from_right",    //动画子类型(详见动画子类型常量)
    	duration:300             //动画过渡时间,默认300毫秒
    },
    rect: {               // 当前帧的宽高范围
        // 方式1,设置矩形大小宽高
        x: 0,             // 左上角x轴坐标
        y: 0,             // 左上角y轴坐标
        w: 'auto',        // 当前帧页面的宽度, auto表示满屏
        h: 'auto'         // 当前帧页面的高度, auto表示满屏
		// 方式2,设置矩形大小宽高
        marginLeft:,    //相对父页面左外边距的距离,数字类型
        marginTop:,     //相对父页面上外边距的距离,数字类型
        marginBottom:,  //相对父页面下外边距的距离,数字类型
        marginRight:    //相对父页面右外边距的距离,数字类型
    },
    pageParam: {          // 要传递新建帧页面的参数,在新页面可通过 api.pageParam.name 获取
        name: 'test'      // name只是举例, 可以传递任意自定义参数
    }
});
②.关闭帧页面
// 关闭当前 frame页面
api.closeFrame();

// 关闭指定名称的frame页面
api.closeFrame({
    name: 'page2'
});

在主程脚本main.js中, 创建一个方法专门创建frame和删除frame页面.

class Game{
	constructor(bg_music){
		// 构造函数,相当于python中类的__init__方法
		this.init();
		if(bg_music){
    	this.play_music(bg_music);
		}
	}
	open(){

	}
	init(){
		// 初始化
		console.log("系统初始化");
    this.rem();
	}
	print(data){
		// 打印数据
		console.log(JSON.stringify(data));
	}
	back(prev){
		// 返回上一页
		this.go(prev.name,prev.url,{...prev});
	}
	outWin(name){
		// 关闭窗口
		api.closeWin(name);
	}
	goWin(name,url,pageParam){
		// 新建窗口
		api.openWin({
		    name: name,            // 自定义窗口名称
		    bounces: false,        // 窗口是否上下拉动
		    reload: true,          // 如果页面已经在之前被打开了,是否要重新加载当前窗口中的页面
				useWKWebView:true,
				historyGestureEnabled:true,
		    url: url,              // 窗口创建时展示的html页面的本地路径[相对于当前代码所在文件的路径]
		    animation:{            // 打开新建窗口时的过渡动画效果
		    	type: "push",                //动画类型(详见动画类型常量)
		    	subType: "from_right",       //动画子类型(详见动画子类型常量)
		    	duration:300                //动画过渡时间,默认300毫秒
		    },
		    pageParam: pageParam   // 传递给下一个窗口使用的参数.将来可以在新窗口中通过 api.pageParam.name 获取
		});
	}
	goFrame(name,url,pageParam,rect=null){
		// 创建帧页面
		if(rect === null){
				rect = {
					// 方式1,设置矩形大小宽高
					x: 0,             // 左上角x轴坐标
					y: 0,             // 左上角y轴坐标
					w: 'auto',        // 当前帧页面的宽度, auto表示满屏
					h: 'auto'         // 当前帧页面的高度, auto表示满屏
				}
		}

		api.openFrame({
		    name: name,        // 帧页面的名称
		    url: url,          // 帧页面打开的url地址
		    bounces:false,     // 页面是否可以下拉拖动
		    reload: true,      // 帧页面如果已经存在,是否重新刷新加载
		    useWKWebView: true,
		    historyGestureEnabled:true,
		    animation:{
		        type:"push",             //动画类型(详见动画类型常量)
		    		subType:"from_right",    //动画子类型(详见动画子类型常量)
		    		duration:300             //动画过渡时间,默认300毫秒
		    },
		    rect: rect,                  // 当前帧的宽高范围
		    pageParam: pageParam,        // 要传递新建帧页面的参数,在新页面可通过 api.pageParam.name 获取

		});
	}
	outFrame(name){
		// 关闭帧页面
		api.closeFrame({
		    name: name,
		});
	}
  rem(){
    if(window.innerWidth<1200){
			this.UIWidth = document.documentElement.clientWidth;
      this.UIHeight = document.documentElement.clientHeight;
      document.documentElement.style.fontSize = (0.01*this.UIWidth*3)+"px";
			document.querySelector("#app").style.height=this.UIHeight+"px"
    }
    window.onresize = ()=>{
      if(window.innerWidth<1200){
        this.UIWidth = document.documentElement.clientWidth;
        this.UIHeight = document.documentElement.clientHeight;
        document.documentElement.style.fontSize = (0.01*this.UIWidth*3)+"px";
      }
    }
  }
	bg(){
		// 背景

	}
	music(){

	}
	stop_music(){
		this.print("停止背景音乐");
		document.body.removeChild(this.audio);
	}
  play_music(src){
		this.print("播放背景音乐");
    this.audio = document.createElement("audio");
    this.source = document.createElement("source");
    this.source.type = "audio/mp3";
    this.audio.autoplay = "autoplay";
    this.source.src=src;
    this.audio.appendChild(this.source);

		/*
     <audio autoplay="autoplay">
		 		<source type="audio/mp3" src="../static/mp3/bg1.mp3">
		 </audio>
		 */
		document.body.appendChild(this.audio);

		// 自动暂停关闭背景音乐
    var t = setInterval(()=>{
      if(this.audio.readyState > 0){
        if(this.audio.ended){
          clearInterval(t);
          document.body.removeChild(this.audio);
        }
      }
    },100);
  }
}

html/login.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/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>
		<div class="form">
			<div class="form-title">
				<img src="../static/images/login.png">
				<img class="back" src="../static/images/back.png">
			</div>
			<div class="form-data">
				<div class="form-data-bg">
					<img src="../static/images/bg1.png">
				</div>
				<div class="form-item">
					<label class="text">手机</label>
					<input type="text" name="mobile" placeholder="请输入手机号">
				</div>
				<div class="form-item">
					<label class="text">密码</label>
					<input type="password" name="password" placeholder="请输入密码">
				</div>
				<div class="form-item">
					<input type="checkbox" class="agree remember" name="agree" checked>
					<label><span class="agree_text ">记住密码,下次免登录</span></label>
				</div>
				<div class="form-item">
					<img class="commit" @click="game.play_music('../static/mp3/btn1.mp3')" src="../static/images/commit.png">
				</div>
				<div class="form-item">
					<p class="toreg" @click="goto_register">立即注册</p>
					<p class="tofind">忘记密码</p>
				</div>
			</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 {
          music_play:true,
					prev:{name:"",url:"",params:{}},
					current:{name:"login",url:"login.html",params:{}},
				}
			},
			watch:{
        music_play(){
          if(this.music_play){
            this.game.play_music("../static/mp3/bg1.mp3");
          }else{
            this.game.stop_music();
          }
        }
      },
			methods:{
				  goto_register(){
            // this.game.goWin("register","./register.html", this.current);
            this.game.goFrame("register","./register.html", this.current)
          }
			}
		})
	}
	</script>
</body>
</html>

html/register.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/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>
		<div class="form">
			<div class="form-title">
				<img src="../static/images/register.png">
				<img class="back" @click="back" src="../static/images/back.png">
			</div>
			<div class="form-data">
				<div class="form-data-bg">
					<img src="../static/images/bg1.png">
				</div>
				<div class="form-item">
					<label class="text">手机</label>
					<input type="text" name="mobile" placeholder="请输入手机号">
				</div>
				<div class="form-item">
					<label class="text">验证码</label>
					<input type="text" class="code" name="code" placeholder="请输入验证码">
					<img class="refresh" src="../static/images/refresh.png">
				</div>
				<div class="form-item">
					<label class="text">密码</label>
					<input type="password" name="password" placeholder="请输入密码">
				</div>
				<div class="form-item">
					<label class="text">确认密码</label>
					<input type="password" name="password2" placeholder="请再次输入密码">
				</div>
				<div class="form-item">
					<input type="checkbox" class="agree" name="agree" checked>
					<label><span class="agree_text">同意磨方《用户协议》和《隐私协议》</span></label>
				</div>
				<div class="form-item">
					<img class="commit" @click="game.play_music('../static/mp3/btn1.mp3')" src="../static/images/commit.png"/>
				</div>
			</div>
		</div>
	</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:"register",url:"register.html","params":{}},
				}
			},
			watch:{
        music_play(){
          if(this.music_play){
            this.game.play_music("../static/mp3/bg1.mp3");
          }else{
            this.game.stop_music();
          }
        }
      },
			methods:{
				back(){
          // this.game.outWin();
          this.game.outFrame();
				}
			}
		})
	}
	</script>
</body>
</html>

(3).framegroup 帧页面组

①.新建帧页面组
api.openFrameGroup({
    name: 'group1',   // 组名
    rect: {           // 帧页面组的显示矩形范围
        // 方式1:
        x:,             //左上角x坐标,数字类型
        y:,             //左上角y坐标,数字类型
        w:,             //宽度,若传'auto',页面从x位置开始自动充满父页面宽度,数字或固定值'auto'
        h:,             //高度,若传'auto',页面从y位置开始自动充满父页面高度,数字或固定值'auto'
        
        // 方式2: 
        marginLeft:,    //相对父页面左外边距的距离,数字类型
    	marginTop:,     //相对父页面上外边距的距离,数字类型
    	marginBottom:,  //相对父页面下外边距的距离,数字类型
    	marginRight:    //相对父页面右外边距的距离,数字类型
    },
    frames: [{
        name:'',         //frame名字,字符串类型,不能为空字符串
        url:'',          // 页面地址
		useWKWebView:true,
        historyGestureEnabled:false,  //(可选项)是否可以通过手势来进行历史记录前进后退。
        pageParam:{},    // 页面参数
        bounces:true,    // 是否能下拉拖动
    }, {
        name:'',         //frame名字,字符串类型,不能为空字符串
        url:'',          // 页面地址
		useWKWebView:true,
        historyGestureEnabled:false,  //(可选项)是否可以通过手势来进行历史记录前进后退。
        pageParam:{},    // 页面参数
        bounces:true,    // 是否能下拉拖动
    },{
        ...
      
    },...
    ]
}, function(ret, err) {
    // 当前帧页面发生页面显示变化时,当前帧的索引.
    var index = ret.index;
});
②.关闭帧页面组
api.closeFrameGroup({
    name: 'group1' // 组名
});
④.切换当前帧页面组显示的帧页面
api.setFrameGroupIndex({
    name: 'group1', // 组名
    index: 2        // 索引,从0开始
});

在主程脚本中把管理gramegroup的相关代码封装方法.

class Game{
	constructor(bg_music){
		// 构造函数,相当于python中类的__init__方法
		this.init();
		if(bg_music){
    	this.play_music(bg_music);
		}
	}
	open(){

	}
	init(){
		// 初始化
		console.log("系统初始化");
    this.rem();

		// 帧页面组的初始化
		this.groupname = null;
		this.groupindex = 0;
	}
	print(data){
		// 打印数据
		console.log(JSON.stringify(data));
	}
	back(prev){
		// 返回上一页
		this.go(prev.name,prev.url,{...prev});
	}
	outWin(name){
		// 关闭窗口
		api.closeWin(name);
	}
	goWin(name,url,pageParam){
		// 新建窗口
		api.openWin({
		    name: name,            // 自定义窗口名称
		    bounces: false,        // 窗口是否上下拉动
		    reload: true,          // 如果页面已经在之前被打开了,是否要重新加载当前窗口中的页面
				useWKWebView:true,
				historyGestureEnabled:true,
		    url: url,              // 窗口创建时展示的html页面的本地路径[相对于当前代码所在文件的路径]
		    animation:{            // 打开新建窗口时的过渡动画效果
		    	type: "push",                //动画类型(详见动画类型常量)
		    	subType: "from_right",       //动画子类型(详见动画子类型常量)
		    	duration:300                //动画过渡时间,默认300毫秒
		    },
		    pageParam: pageParam   // 传递给下一个窗口使用的参数.将来可以在新窗口中通过 api.pageParam.name 获取
		});
	}
	goFrame(name,url,pageParam,rect=null){
		// 创建帧页面
		if(rect === null){
				rect = {
					// 方式1,设置矩形大小宽高
					x: 0,             // 左上角x轴坐标
					y: 0,             // 左上角y轴坐标
					w: 'auto',        // 当前帧页面的宽度, auto表示满屏
					h: 'auto'         // 当前帧页面的高度, auto表示满屏
				}
		}

		api.openFrame({
		    name: name,        // 帧页面的名称
		    url: url,          // 帧页面打开的url地址
		    bounces:false,     // 页面是否可以下拉拖动
		    reload: true,      // 帧页面如果已经存在,是否重新刷新加载
		    useWKWebView: true,
		    historyGestureEnabled:true,
		    animation:{
		        type:"push",             //动画类型(详见动画类型常量)
		    		subType:"from_right",    //动画子类型(详见动画子类型常量)
		    		duration:300             //动画过渡时间,默认300毫秒
		    },
		    rect: rect,                  // 当前帧的宽高范围
		    pageParam: pageParam,        // 要传递新建帧页面的参数,在新页面可通过 api.pageParam.name 获取

		});
	}
	outFrame(name){
		// 关闭帧页面
		api.closeFrame({
		    name: name,
		});
	}
	openGroup(name,frames,preload=1,rect=null){
		// 创建frame组
		if(rect === null){
			rect = {           // 帧页面组的显示矩形范围
					x:0,             //左上角x坐标,数字类型
					y:0,             //左上角y坐标,数字类型
					w:'auto',             //宽度,若传'auto',页面从x位置开始自动充满父页面宽度,数字或固定值'auto'
					h:'auto',             //高度,若传'auto',页面从y位置开始自动充满父页面高度,数字或固定值'auto'
			};
		}
		api.openFrameGroup({
		    name: name,            // 组名
				scrollEnabled: false,  // 页面组是否可以左右滚动
				index: 0,              // 默认显示页面的索引
				rect: rect,            // 页面宽高范围
				preload: preload,      // 默认预加载的页面数量
		    frames: frames,        // 帧页面组的帧页面成员
		}, (ret, err)=>{
		    // 当前帧页面发生页面显示变化时,当前帧的索引.
		    this.groupindex = ret.index;
		});
	}
	outGroup(name){
		// 关闭 frame组
		api.closeFrameGroup({
    	name: name // 组名
		});
	}
	goGroup(name,index){
		// 切换显示frame组下某一个帧页面
		api.setFrameGroupIndex({
    	name: name,  // 组名
    	index: index // 索引,从0开始
		});
	}
  rem(){
    if(window.innerWidth<1200){
			this.UIWidth = document.documentElement.clientWidth;
      this.UIHeight = document.documentElement.clientHeight;
      document.documentElement.style.fontSize = (0.01*this.UIWidth*3)+"px";
			document.querySelector("#app").style.height=this.UIHeight+"px"
    }
    window.onresize = ()=>{
      if(window.innerWidth<1200){
        this.UIWidth = document.documentElement.clientWidth;
        this.UIHeight = document.documentElement.clientHeight;
        document.documentElement.style.fontSize = (0.01*this.UIWidth*3)+"px";
      }
    }
  }
	bg(){
		// 背景

	}
	music(){

	}
	stop_music(){
		this.print("停止背景音乐");
		document.body.removeChild(this.audio);
	}
  play_music(src){
		this.print("播放背景音乐");
    this.audio = document.createElement("audio");
    this.source = document.createElement("source");
    this.source.type = "audio/mp3";
    this.audio.autoplay = "autoplay";
    this.source.src=src;
    this.audio.appendChild(this.source);

		/*
     <audio autoplay="autoplay">
		 		<source type="audio/mp3" src="../static/mp3/bg1.mp3">
		 </audio>
		 */
		document.body.appendChild(this.audio);

		// 自动暂停关闭背景音乐
    var t = setInterval(()=>{
      if(this.audio.readyState > 0){
        if(this.audio.ended){
          clearInterval(t);
          document.body.removeChild(this.audio);
        }
      }
    },100);
  }
}

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/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"></li>
      <li><img class="module2" @click="gohome" src="../static/images/image2.png"></li>
      <li><img class="module3" src="../static/images/image3.png"></li>
      <li><img class="module4" src="../static/images/image4.png"></li>
    </ul>
  </div>
  <script>
	apiready = function(){
		var game = new Game("../static/mp3/bg1.mp3");
		// 允许ajax发送请求时附带cookie,设置为不允许
		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(){
      },
			methods:{
        gohome(){
          frames = [{
            name: 'login',
            url:   './login.html',
          },{
            name: 'register',
            url:   './register.html',
          }]
          this.game.openGroup("user",frames,frames.length);
        }
			}
		})
	}
	</script>
</body>
</html>

登陆页面html/login.html,代码:

class Game{
	constructor(bg_music){
		// 构造函数,相当于python中类的__init__方法
		this.init();
		if(bg_music){
    	this.play_music(bg_music);
		}
	}
	open(){

	}
	init(){
		// 初始化
		console.log("系统初始化");
    this.rem();

		// 帧页面组的初始化
		this.groupname = null;
		this.groupindex = 0;
	}
	print(data){
		// 打印数据
		console.log(JSON.stringify(data));
	}
	back(prev){
		// 返回上一页
		this.go(prev.name,prev.url,{...prev});
	}
	outWin(name){
		// 关闭窗口
		api.closeWin(name);
	}
	goWin(name,url,pageParam){
		// 新建窗口
		api.openWin({
		    name: name,            // 自定义窗口名称
		    bounces: false,        // 窗口是否上下拉动
		    reload: true,          // 如果页面已经在之前被打开了,是否要重新加载当前窗口中的页面
				useWKWebView:true,
				historyGestureEnabled:true,
		    url: url,              // 窗口创建时展示的html页面的本地路径[相对于当前代码所在文件的路径]
		    animation:{            // 打开新建窗口时的过渡动画效果
		    	type: "push",                //动画类型(详见动画类型常量)
		    	subType: "from_right",       //动画子类型(详见动画子类型常量)
		    	duration:300                //动画过渡时间,默认300毫秒
		    },
		    pageParam: pageParam   // 传递给下一个窗口使用的参数.将来可以在新窗口中通过 api.pageParam.name 获取
		});
	}
	goFrame(name,url,pageParam,rect=null){
		// 创建帧页面
		if(rect === null){
				rect = {
					// 方式1,设置矩形大小宽高
					x: 0,             // 左上角x轴坐标
					y: 0,             // 左上角y轴坐标
					w: 'auto',        // 当前帧页面的宽度, auto表示满屏
					h: 'auto'         // 当前帧页面的高度, auto表示满屏
				}
		}

		api.openFrame({
		    name: name,        // 帧页面的名称
		    url: url,          // 帧页面打开的url地址
		    bounces:false,     // 页面是否可以下拉拖动
		    reload: true,      // 帧页面如果已经存在,是否重新刷新加载
		    useWKWebView: true,
		    historyGestureEnabled:true,
		    animation:{
		        type:"push",             //动画类型(详见动画类型常量)
		    		subType:"from_right",    //动画子类型(详见动画子类型常量)
		    		duration:300             //动画过渡时间,默认300毫秒
		    },
		    rect: rect,                  // 当前帧的宽高范围
		    pageParam: pageParam,        // 要传递新建帧页面的参数,在新页面可通过 api.pageParam.name 获取

		});
	}
	outFrame(name){
		// 关闭帧页面
		api.closeFrame({
		    name: name,
		});
	}
	openGroup(name,frames,preload=1,rect=null){
		// 创建frame组
		if(rect === null){
			rect = {           // 帧页面组的显示矩形范围
					x:0,             //左上角x坐标,数字类型
					y:0,             //左上角y坐标,数字类型
					w:'auto',             //宽度,若传'auto',页面从x位置开始自动充满父页面宽度,数字或固定值'auto'
					h:'auto',             //高度,若传'auto',页面从y位置开始自动充满父页面高度,数字或固定值'auto'
			};
		}
		api.openFrameGroup({
		    name: name,            // 组名
				scrollEnabled: false,  // 页面组是否可以左右滚动
				index: 0,              // 默认显示页面的索引
				rect: rect,            // 页面宽高范围
				preload: preload,      // 默认预加载的页面数量
		    frames: frames,        // 帧页面组的帧页面成员
		}, (ret, err)=>{
		    // 当前帧页面发生页面显示变化时,当前帧的索引.
		    this.groupindex = ret.index;
		});
	}
	outGroup(name){
		// 关闭 frame组
		api.closeFrameGroup({
    	name: name // 组名
		});
	}
	goGroup(name,index){
		// 切换显示frame组下某一个帧页面
		api.setFrameGroupIndex({
    	name: name,  // 组名
    	index: index // 索引,从0开始
		});
	}
  rem(){
    if(window.innerWidth<1200){
			this.UIWidth = document.documentElement.clientWidth;
      this.UIHeight = document.documentElement.clientHeight;
      document.documentElement.style.fontSize = (0.01*this.UIWidth*3)+"px";
			document.querySelector("#app").style.height=this.UIHeight+"px"
    }
    window.onresize = ()=>{
      if(window.innerWidth<1200){
        this.UIWidth = document.documentElement.clientWidth;
        this.UIHeight = document.documentElement.clientHeight;
        document.documentElement.style.fontSize = (0.01*this.UIWidth*3)+"px";
      }
    }
  }
	bg(){
		// 背景

	}
	music(){

	}
	stop_music(){
		this.print("停止背景音乐");
		document.body.removeChild(this.audio);
	}
  play_music(src){
		this.print("播放背景音乐");
    this.audio = document.createElement("audio");
    this.source = document.createElement("source");
    this.source.type = "audio/mp3";
    this.audio.autoplay = "autoplay";
    this.source.src=src;
    this.audio.appendChild(this.source);

		/*
     <audio autoplay="autoplay">
		 		<source type="audio/mp3" src="../static/mp3/bg1.mp3">
		 </audio>
		 */
		document.body.appendChild(this.audio);

		// 自动暂停关闭背景音乐
    var t = setInterval(()=>{
      if(this.audio.readyState > 0){
        if(this.audio.ended){
          clearInterval(t);
          document.body.removeChild(this.audio);
        }
      }
    },100);
  }
}

注册页面,html/register.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/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>
		<div class="form">
			<div class="form-title">
				<img src="../static/images/register.png">
				<img class="back" @click="back" src="../static/images/back.png">
			</div>
			<div class="form-data">
				<div class="form-data-bg">
					<img src="../static/images/bg1.png">
				</div>
				<div class="form-item">
					<label class="text">手机</label>
					<input type="text" name="mobile" placeholder="请输入手机号">
				</div>
				<div class="form-item">
					<label class="text">验证码</label>
					<input type="text" class="code" name="code" placeholder="请输入验证码">
					<img class="refresh" src="../static/images/refresh.png">
				</div>
				<div class="form-item">
					<label class="text">密码</label>
					<input type="password" name="password" placeholder="请输入密码">
				</div>
				<div class="form-item">
					<label class="text">确认密码</label>
					<input type="password" name="password2" placeholder="请再次输入密码">
				</div>
				<div class="form-item">
					<input type="checkbox" class="agree" name="agree" checked>
					<label><span class="agree_text">同意磨方《用户协议》和《隐私协议》</span></label>
				</div>
				<div class="form-item">
					<img class="commit" @click="game.play_music('../static/mp3/btn1.mp3')" src="../static/images/commit.png"/>
				</div>
			</div>
		</div>
	</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:"register",url:"register.html","params":{}},
				}
			},
			watch:{
        music_play(){
          if(this.music_play){
            this.game.play_music("../static/mp3/bg1.mp3");
          }else{
            this.game.stop_music();
          }
        }
      },
			methods:{
				back(){
          // this.game.outWin();
          // this.game.outFrame();
          this.game.goGroup("user",0); 
				}
			}
		})
	}
	</script>
</body>
</html>

APP配置文件, config.xml,代码:

    <content src="html/index.html" />
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值