├── .babelrc ├── .eslintrc.json ├── .gitignore ├── .npmignore ├── README.md ├── dist └── com.xunlei.movie.rpk ├── doc └── demo.mp4 ├── package-lock.json ├── package.json ├── sign └── debug │ ├── certificate.pem │ └── private.pem └── src ├── About └── index.ux ├── Common ├── api │ └── index.js ├── assets │ ├── css │ │ ├── article.css │ │ └── webview.css │ └── img │ │ └── default.png └── logo.png ├── Detail └── index.ux ├── List └── index.ux ├── app.ux ├── manifest.json └── util.js /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": [ 3 | "env" 4 | ] 5 | } -------------------------------------------------------------------------------- /.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "env": { 3 | "commonjs": true 4 | }, 5 | "extends": "eslint:recommended", 6 | "parser": "babel-eslint", 7 | "parserOptions": { 8 | "sourceType": "module", 9 | "ecmaFeatures": { 10 | "experimentalObjectRestSpread": true, 11 | "jsx": true 12 | } 13 | }, 14 | "globals": { 15 | "loadData": false, 16 | "saveData": false, 17 | "history": false, 18 | "console": false, 19 | "setTimeout": false, 20 | "clearTimeout": false, 21 | "setInterval": false, 22 | "clearInterval": false, 23 | "natives": false 24 | }, 25 | "plugins": [ 26 | "hybrid" 27 | ], 28 | "rules": { 29 | "indent": [ 30 | "warn", 31 | 2 32 | ], 33 | "no-console": [ 34 | "warn", 35 | { 36 | "allow": [ 37 | "info", 38 | "warn", 39 | "error" 40 | ] 41 | } 42 | ], 43 | "no-unused-vars": [ 44 | "warn", 45 | { 46 | "varsIgnorePattern": "prompt" 47 | } 48 | ], 49 | "quotes": [ 50 | "warn", 51 | "single", 52 | { 53 | "avoidEscape": true, 54 | "allowTemplateLiterals": true 55 | } 56 | ], 57 | "linebreak-style": [ 58 | "warn", 59 | "unix" 60 | ], 61 | "semi": [ 62 | "warn", 63 | "never" 64 | ] 65 | } 66 | } -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # dependencies 2 | node_modules 3 | 4 | # logs 5 | npm-debug.log 6 | 7 | # build 8 | build 9 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | node_modules -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | ## 什么是快应用 4 | 5 | 简单地说快应用是国内的十大主流手机厂商比如小米、华为、ov等联合推出的一种新型应用。无需安装,秒开,体验媲美原生。还提供了像原生应用一样的入口:应用商店,搜索页等。 6 | 7 | ## 开发前准备 8 | 9 | 接下来会教大家如何搭建、启动、预览和调试快应用项目。和[官方文档](https://doc.quickapp.cn/)类似,这里我增加了一些我在这过程中遇到的坑及解决方法。 10 | 11 | ### 创建项目 12 | 13 | #### 安装 NodeJS 14 | 15 | 官方说需安装 6.0 以上版本的 NodeJS,推荐 v6.11.3,但我本机 NodeJS 是 v9.3.0,暂时没发现异常就没切到 6.0。 16 | 17 | #### 安装 hap-toolkit 18 | 19 | hap-toolkit 是快应用的开发者工具,帮助开发者通过命令行工具辅助开发工作的完成,主要包括创建模板工程,升级工程,编译,调试等功能。类似 vue-cli。 20 | 21 | ```bash 22 | npm install -g hap-toolkit 23 | ``` 24 | 25 | 安装之后查看下版本号看是否安装成功。 26 | 27 | ```bash 28 | hap -V 29 | ``` 30 | 31 | #### 创建项目 32 | 33 | 运行如下命令会在当前目录下创建 `` 目录作为项目的根目录。 34 | 35 | ```bash 36 | hap init 37 | ``` 38 | 39 | 这个项目已经包含了项目配置与示例页面的初始代码,项目根目录主要结构如下。 40 | 41 | ``` 42 | ├── sign rpk 包签名模块 43 | │ └── debug 调试环境 44 | │ ├── certificate.pem 证书文件 45 | │ └── private.pem 私钥文件 46 | ├── src 47 | │ ├── Common 公用的资源和组件文件 48 | │ │ └── logo.png 应用图标 49 | │ ├── Demo 页面目录 50 | │ | └── index.ux 页面文件,可自定义页面名称 51 | │ ├── app.ux APP 文件,可引入公共脚本,暴露公共数据和方法等 52 | │ └── manifest.json 项目配置文件,配置应用图标、页面路由等 53 | └── package.json 定义项目需要的各种模块及配置信息 54 | ``` 55 | 56 | #### 安装依赖 57 | 58 | ```bash 59 | npm install 60 | ``` 61 | 62 | ### 启动项目 63 | 64 | 编译 65 | 66 | ``` 67 | npm run build 68 | ``` 69 | 70 | 编译生成的 `dist` 目录里才是最终产物:`rpk` 文件。 71 | 72 | 这一步可能会遇到报错(我就遇到了)。 73 | 74 | ``` 75 | Cannot find module '.../node_modules/hap-tools/webpack.config.js' 76 | ``` 77 | 78 | 主要是因为创建项目后就有一个 `node_module` 文件夹了,里面有一个 `hap-tools` 包。如果 `npm install` 安装依赖,高版本的 `npm` 可能会把 `node_module` 原有的包清空再安装依赖,这时只要再手动安装下 `hap-tools` 就行了 79 | 80 | ```bash 81 | npm install hap-tools 82 | ``` 83 | 84 | 如果要监听源码变化自动编译,可以运行 watch 命令。 85 | 86 | ```bash 87 | npm run watch 88 | ``` 89 | 90 | 到这一步一个 hello world 的快应用就打包好了,下面需要在手机上把它跑起来。 91 | 92 | ### 预览 93 | 94 | 首先需要安装手机[调试器](https://www.quickapp.cn/docCenter/post/69)。 95 | 96 | ![](https://user-gold-cdn.xitu.io/2018/3/30/16275620ebed1583?w=360&h=640&f=png&s=28441) 97 | 98 | 只安装这个快应用调试器会发现上面的按钮都是灰色不可点击的,这时还需要安装平台预览版调试器,总之[快应用文档](https://www.quickapp.cn/docCenter/post/69)上的手机调试器都要安装才能调试。 99 | 100 | 安装好调试器后就把快应用安装包安装到手机上就可以了。 101 | 102 | #### 扫码安装 103 | 104 | 需要启动一个本地 HTTP 服务器。 105 | 106 | ```bash 107 | npm run server 108 | ``` 109 | 110 | ![](https://user-gold-cdn.xitu.io/2018/3/30/1627564b7eb7d4d5?w=1174&h=828&f=png&s=197137) 111 | 112 | 如果命令行中的二维码扫了没反应,可以把那个地址在浏览器中打开在扫码试试(我就是这样),因为命令行中的二维码可能绘制的有问题。 113 | 114 | #### 本地安装 115 | 116 | 把 `rpk` 文件传到手机上安装即可。 117 | 118 | #### 在线更新 119 | 120 | 快应用调试器右上角可以设置服务器地址,运行以下命令每次改了代码就可以点击在线更新就可以更新了,不用每次都扫码或本地安装。 121 | 122 | ```bash 123 | npm run server 124 | npm run watch 125 | ``` 126 | 127 | ### 调试 128 | 129 | 可以手机上预览,也可以使用 chrome devtools 调试界面,还可以查看调试日志。手机上预览上面说了,其他调试按[官方步骤](https://doc.quickapp.cn/tools/debugging-tools.html)来就好了。 130 | 131 | **可能的坑**:在用chrome devtools调试的时候可能打不开调试界面,或者调试界面空白。这时需检查: 132 | 133 | - 在手机调试器上点击了开始调试(点了就会自动在 pc chrome 上打开 devtools) 134 | - 确保手机和电脑在同一个网段 135 | - 检查代理,设置了代理的把代理关了试试(我就是因为设置了代理 devtools 空白) 136 | 137 | ![](https://user-gold-cdn.xitu.io/2018/3/30/1627568b38356ef9?w=835&h=822&f=png&s=56240) 138 | 139 | ## 5 分钟上手教程 140 | 141 | 以一个列表页和详情页为例说明快应用的代码,数据来源[迅雷影评](http://movie.xunlei.com/)。 142 | 143 | 先看下[demo运行效果](./doc/demo.mp4) 144 | 145 | ### Manifest.json 146 | 147 | 在 `manifest.json` 中配置路由后就可以写代码了,生成的模板有例子。注意不能配置动态路由。 148 | 149 | 注意用到的[系统接口](https://doc.quickapp.cn/features/)要先在 `manifest.json` 的 `feature` 中声明。看 [manifest 的文档](https://doc.quickapp.cn/framework/manifest.html)了解具体的配置项。 150 | 151 | ```json 152 | { 153 | "package": "com.xunlei.movie", 154 | "name": "迅雷影评", 155 | "versionName": "1.0.0", 156 | "versionCode": "1", 157 | "minPlatformVersion": "101", 158 | "icon": "/Common/logo.png", 159 | "features": [ 160 | { "name": "system.prompt" }, 161 | { "name": "system.router" }, 162 | { "name": "system.shortcut" }, 163 | { "name": "system.fetch" }, 164 | { "name": "system.webview" } 165 | ], 166 | "permissions": [ 167 | { "origin": "*" } 168 | ], 169 | "config": { 170 | "logLevel": "debug", 171 | "designWidth": 640 172 | }, 173 | "router": { 174 | "entry": "List", 175 | "pages": { 176 | "List": { 177 | "component": "index" 178 | }, 179 | "Detail": { 180 | "component": "index" 181 | }, 182 | "About": { 183 | "component": "index" 184 | } 185 | } 186 | } 187 | } 188 | ``` 189 | 190 | ### 列表 191 | 列表使用了快应用的list组件,这个组件是Native组件,对长列表滚动性能更好,list组件还有一个onscrollbottom事件,方便下拉加载。 192 | 193 | image组件和前端的img标签类似,但是alt属性不同,alt是用来显示占位图的,只能是本地图片,在图片没加载出来前显示。 194 | 195 | list-item组件中的type是必填的,要实现DOM片段的复用,要求相同type属性的DOM结构完全相同;所以设置相同type属性的list-item是优化列表滚动性能的关键。 196 | ```html 197 | 213 | ``` 214 | 快应用的网络请求是用fetch方法,是callback的形式,不方便调用,官方给了一个封装成promise的例子,可以用async/await的方式调用。 215 | 216 | 将封装好的fetch方法在app.ux中导出就可以全局使用了,由于我使用的接口都返回json,所以直接就在这一层解析了。实际开发时要注意JSON.parse的报错处理。 217 | ```javascript 218 | // app.ux 219 | const natives = { 220 | /** 221 | * 网络请求 222 | * @param options 223 | * @return {Promise} 224 | */ 225 | async fetch (options) { 226 | const p1 = new Promise((resolve, reject) => { 227 | options.success = function (data, code) { 228 | data = JSON.parse(data.data) 229 | resolve({ data, code }) 230 | } 231 | options.fail = function (data, code) { 232 | reject({ data, code }) 233 | } 234 | nativeFetch.fetch(options) 235 | }) 236 | return p1 237 | } 238 | } 239 | // 注入到全局 240 | const hookTo = global.__proto__ || global 241 | hookTo.natives = natives 242 | 243 | export default { 244 | natives 245 | } 246 | ``` 247 | 路由跳转 248 | 249 | ```html 250 | 255 | 267 | ``` 268 | ### webview 269 | 270 | 详情页只是加载了一个 webview, 用列表页传过来的 id 去请求影评详情,影评正文是存在 cdn 上的一个地址。使用 `web` 组件前需在 `manifest.json` 中声明使用 webview 接口。 271 | 272 | ```html 273 | 274 | 280 | 281 | 306 | ``` 307 | ## 与前端开发比较 308 | 309 | 快应用与前端开发的最大的区别就是 html 和 css 部分,因为快应用是用原生的方式实现的,但没有实现html的所有标签,而且与 html 相同的标签在用法上也有一些差别。 310 | 311 | ### html 312 | 313 | 快应用中很多 html 都不能用,比如没有 p,h1~h2 等,因为它只是模拟了部分 html 标签,最终会转化成原生组件。 314 | 315 | 而且快应用中的组件嵌套子组件是有限制的,不是所有的组件都能嵌套子组件,如果嵌套不正确编译的时候会报错。比如下面就是不正确的写法: 316 | 317 | ```html 318 | 319 | 320 | 321 | 322 | ``` 323 | 324 | #### 文本组件 325 | 326 | 只能使用 a、span、text、label 放置文本内容 327 | 328 | #### 图片组件 329 | 330 | 图片组件是 image 不是 img,用法与 img 类似,只是 alt 的含义不同,在快应用中 alt 是指图片没加载出来前的占位图,只能是本地地址。 331 | ``` 332 | 1.jpg 333 | ``` 334 | #### 其他 335 | 336 | 表单组件、video 组件等与前端一致,还有一些快应用特有的组件,比如星级评分组件、进度条组件、list 组件等。 337 | 338 | ### css 339 | 340 | - display 只能是 flex 或 none 341 | - position 只能是 fixed 或 none 342 | - 长度单位只有 px 和 % 343 | > 与传统web页面不同,px 是相对于项目配置基准宽度的单位,已经适配了移动端屏幕,其原理类似于 rem。基准宽度可以在 mainifest.json 中配置。 344 | 345 | ### javascript 346 | 347 | 基本语法都能用,ES6 也可以用,项目中已经安装了 babel 依赖。一些浏览器特有的 API 可能不同。比如数据存储用的是快应用的接口 storage。 348 | 349 | ## 与 Vue 比较 350 | 351 | 由于我们团队主要是用 Vue 技术栈开发,所以比较下快应用在语法上和 Vue 的共同点和差异之处。快应用看起来和 Vue 类似,其实还是有很大的差别。 352 | 353 | - 都有指令的概念,只是写法不同, 目前不能自定义指令 354 | ```html 355 | 356 | v-for => for 357 | v-show => show 358 | v-if => if 359 | template => block 360 | slot => slot 361 | ``` 362 | - 快应用的路由是通过配置文件 `manifest.json` 配置的,在实例中的用法与vue-router 一致 363 | - 都有组件概念,组件引入的方式略有不同 364 | ```javascript 365 | // vue 366 | import child from './childComponent' 367 | // 快应用 368 | 369 | ``` 370 | - 事件的监听和触发与 Vue 类似,都是 `$on` `$off` `$emit`,监听原生组件的事件写法不同 371 | ```html 372 | 373 |
374 |
375 | 376 |
377 | ``` 378 | - 组件间通信和纯 Vue 类似,可以通过 props,也可以挂载在全局对象上 379 | - Vue 生态系统都不能用,比如 Vuex,目前没有插件机制 380 | 381 | ## 优缺点 382 | 383 | ### 优点 384 | 385 | - 提供了很多系统的功能,比如分享、通知、扫描二维码、添加图标到桌面 386 | - 用户体验好,无需下载,秒开,占用内存小 387 | - 可以关联原生应用 388 | 389 | ### 缺点 390 | - 每个平台都要注册个账号 391 | - 没有一个集成开发环境,调试麻烦,且 devtools 很卡 392 | - rpk 文件最大1M 393 | - 国内手机厂商推出的,自然是不支持 ios 了 394 | 395 | ## 总结 396 | 397 | 写demo的时候还是遇到了不少坑,主要是html和css部分。像我们公司前端和重构是分开的,重构只负责写 html+css,前端负责写逻辑调接口等杂七杂八的事情,快应用和小程序这种形式对重构来说很麻烦,不能写一份代码到处用了。 398 | 399 | 还有就是详情页显示影评正文的时候遇到了一个问题。我们影评的正文是存在cdn上的一堆html标签,无样式,可能有一些和快应用不兼容的标签,所以用 webview 的方式加载页面。但是不知道怎么向 webview 中注入 css ,所以页面是乱的。 400 | 401 | 总的来说,快应用这种形态对用户来说还是很好的,在下载 APP 前就可以体验到应用的一些功能。快应用的快在于它进行了很多原生的优化,也在于它小,小到用户感觉不到,这也注定它不能做的很复杂,所以快应用只是一个导流的方式。 402 | 403 | ## 代码地址 404 | 405 | [https://github.com/greenfavo/quickapp-demo](https://github.com/greenfavo/quickapp-demo) 406 | 407 | ## 参考文档 408 | 409 | [快应用开发文档](https://doc.quickapp.cn/) 410 | 411 | ## 扫一扫关注迅雷前端公众号 412 | 413 | ![](https://user-gold-cdn.xitu.io/2017/9/18/a61c018adbf0a3e865643c51e91251bb?imageView2/0/w/1280/h/960/format/webp/ignore-error/1) 414 | -------------------------------------------------------------------------------- /dist/com.xunlei.movie.rpk: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/greenfavo/quickapp-demo/ef0db849750a3b465d2e221874f60b935bbac5d8/dist/com.xunlei.movie.rpk -------------------------------------------------------------------------------- /doc/demo.mp4: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/greenfavo/quickapp-demo/ef0db849750a3b465d2e221874f60b935bbac5d8/doc/demo.mp4 -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "quickapp-demo", 3 | "version": "1.0.0", 4 | "subversion": { 5 | "toolkit": "0.0.26", 6 | "packager": "0.0.5" 7 | }, 8 | "description": "快应用demo", 9 | "scripts": { 10 | "build": "cross-env NODE_PLATFORM=na NODE_PHASE=dv webpack --config ./node_modules/hap-tools/webpack.config.js", 11 | "release": "cross-env NODE_PLATFORM=na NODE_PHASE=ol webpack --config ./node_modules/hap-tools/webpack.config.js", 12 | "clear": "rm -rf build/* && rm -rf dist/*", 13 | "server": "cross-env NODE_MOUNTED_ROUTER=\"debug bundle\" node ./node_modules/hap-tools/debugger/server/index.js", 14 | "debug": "npm run server -- --debug-only --port=8081", 15 | "notify": "node ./node_modules/hap-tools/debugger/command/notify.js", 16 | "watch": "cross-env NODE_PLATFORM=na NODE_PHASE=dv webpack --config ./node_modules/hap-tools/webpack.config.js --watch", 17 | "watch:na": "npm run na:dv -- --watch", 18 | "na": "npm run na:dv -- --watch", 19 | "lint": "./node_modules/.bin/eslint src/", 20 | "na:dv": "cross-env NODE_PLATFORM=na NODE_PHASE=dv webpack --config ./node_modules/hap-tools/webpack.config.js", 21 | "na:qa": "cross-env NODE_PLATFORM=na NODE_PHASE=qa webpack --config ./node_modules/hap-tools/webpack.config.js", 22 | "na:ol": "cross-env NODE_PLATFORM=na NODE_PHASE=ol webpack --config ./node_modules/hap-tools/webpack.config.js", 23 | "postinstall": "npm run postinstall:koaStatic && npm run postinstall:koaSend", 24 | "postinstall:koaStatic": "babel -d ./node_modules/koa-static ./node_modules/koa-static", 25 | "postinstall:koaSend": "babel -d ./node_modules/koa-send ./node_modules/koa-send" 26 | }, 27 | "dependencies": { 28 | "archiver": "^1.3.0", 29 | "babel-plugin-transform-runtime": "^6.9.0", 30 | "babel-polyfill": "^6.26.0", 31 | "babel-preset-env": "^1.6.0", 32 | "babel-runtime": "^6.9.2", 33 | "babel-template": "^6.24.1", 34 | "babel-traverse": "^6.24.1", 35 | "babel-types": "^6.24.1", 36 | "babylon": "^6.17.0", 37 | "babylon-jsx": "^1.0.0", 38 | "browserify": "^13.1.1", 39 | "chalk": "^1.1.3", 40 | "css": "~2.2.1", 41 | "escodegen": "~1.7.1", 42 | "esprima": "~2.7.0", 43 | "fs-extra": "^3.0.1", 44 | "fsmonitor": "^0.2.4", 45 | "hap-tools": "0.0.2", 46 | "hash-sum": "^1.0.2", 47 | "jsrsasign": "^7.1.2", 48 | "jsrsasign-util": "^1.0.0", 49 | "loader-utils": "~0.2.14", 50 | "md5": "^2.1.0", 51 | "parse5": "^3.0.0", 52 | "prompt": "^1.0.0", 53 | "qr-image": "^3.2.0", 54 | "qrcode-terminal": "^0.11.0", 55 | "resolve-bin": "^0.4.0", 56 | "serve": "^3.4.0", 57 | "socket.io": "^2.0.3", 58 | "source-map": "^0.5.6", 59 | "tar": "^3.1.5", 60 | "xtoolkit": "^0.1.7", 61 | "yargs": "^6.6.0" 62 | }, 63 | "devDependencies": { 64 | "babel-cli": "^6.10.1", 65 | "babel-core": "^6.10.4", 66 | "babel-eslint": "^8.2.1", 67 | "babel-loader": "^6.2.4", 68 | "babel-plugin-syntax-jsx": "^6.18.0", 69 | "babel-polyfill": "^6.23.0", 70 | "hybrid-chai": "~0.0.1", 71 | "cross-env": "^3.2.4", 72 | "eslint": "^4.3.0", 73 | "eslint-plugin-hybrid": "~0.0.1", 74 | "file-loader": "^0.9.0", 75 | "html-webpack-plugin": "^2.28.0", 76 | "js-base64": "^2.1.9", 77 | "hybrid-mocha": "~0.0.1", 78 | "sinon": "^1.17.3", 79 | "sinon-chai": "^2.8.0", 80 | "url-loader": "^0.5.7", 81 | "webpack": "~1.13.0", 82 | "webpack-dev-server": "^1.16.5", 83 | "webdriverio": "^4.8.0", 84 | "css-what": "^2.1.0", 85 | "koa": "^2.3.0", 86 | "koa-send": "^4.1.1", 87 | "koa-static": "^4.0.1", 88 | "koa-body": "^2.5.0", 89 | "koa-router": "^7.2.1" 90 | } 91 | } 92 | -------------------------------------------------------------------------------- /sign/debug/certificate.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN CERTIFICATE----- 2 | MIIDMTCCAhmgAwIBAgIJAMKpjyszxkDpMA0GCSqGSIb3DQEBCwUAMC4xCzAJBgNV 3 | BAYTAkNOMQwwCgYDVQQKDANSUEsxETAPBgNVBAMMCFJQS0RlYnVnMCAXDTE3MDQx 4 | OTAyMzE0OVoYDzIxMTYwMzI2MDIzMTQ5WjAuMQswCQYDVQQGEwJDTjEMMAoGA1UE 5 | CgwDUlBLMREwDwYDVQQDDAhSUEtEZWJ1ZzCCASIwDQYJKoZIhvcNAQEBBQADggEP 6 | ADCCAQoCggEBAK3kPd9jzvTctTIA3XNZVv9cHHDbAc6nTBfdZp9mtPOTkXFpvyCb 7 | kL0QjOog0+1pv8D7dFeP4ptWXU5CT3ImvaPR+16dAtMRcsxEr5q4zieJzx3O6huL 8 | UBa1k+xrzjXpRzkcOysmc8fTxt0tAwbDgJ2AA5TlXLTcVyb7GmJ+hl5CjnhoG5NN 9 | LrkqI7S29c1U3uokj8Q7hzaj0TURu/uB5ZIMCLZY9KFDugqaEcvmUyJiD0fuV6sA 10 | O/4kpiZUOnhV8/xWpRbMI4WFQsfgLOCV+X9uzUa29D677y//46t/EDSuQTHyBZbl 11 | AcNMENkpMWZsH7J/+F19+U0/Hd5bJgneVRkCAwEAAaNQME4wHQYDVR0OBBYEFKDN 12 | SZtt47ttOBDQzIchFYyxsg3mMB8GA1UdIwQYMBaAFKDNSZtt47ttOBDQzIchFYyx 13 | sg3mMAwGA1UdEwQFMAMBAf8wDQYJKoZIhvcNAQELBQADggEBABaZctNrn4gLmNf/ 14 | eNJ3x5CJIPjPwm6j9nwKhtadJ6BF+TIzSkJuHSgxULjW436F37otv94NPzT5PCBF 15 | WxgXoqgLqnWwvsaqC4LUEjsZviWW4CB824YDUquEUVGFLE/U5KTZ7Kh1ceyUk4N8 16 | +mtkXkanWoBBk0OF24lNrAsNLB63yTLr9HxEe75+kmvxf1qVJUGtaOEWIhiFMiAB 17 | 5D4w/j2EFWktumjuy5TTwU0zhl52bc8V9SNixM1IaqzNrVPrdjv8viUX548pU3WT 18 | xZ5ylDsxhMC1q4BXQVeIY8C0cMEX+WHOmOCvWrkxCkP91pKsSPkuVrWlzrkn8Ojo 19 | swP6sBw= 20 | -----END CERTIFICATE----- 21 | -------------------------------------------------------------------------------- /sign/debug/private.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN PRIVATE KEY----- 2 | MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQCt5D3fY8703LUy 3 | AN1zWVb/XBxw2wHOp0wX3WafZrTzk5Fxab8gm5C9EIzqINPtab/A+3RXj+KbVl1O 4 | Qk9yJr2j0ftenQLTEXLMRK+auM4nic8dzuobi1AWtZPsa8416Uc5HDsrJnPH08bd 5 | LQMGw4CdgAOU5Vy03Fcm+xpifoZeQo54aBuTTS65KiO0tvXNVN7qJI/EO4c2o9E1 6 | Ebv7geWSDAi2WPShQ7oKmhHL5lMiYg9H7lerADv+JKYmVDp4VfP8VqUWzCOFhULH 7 | 4Czglfl/bs1GtvQ+u+8v/+OrfxA0rkEx8gWW5QHDTBDZKTFmbB+yf/hdfflNPx3e 8 | WyYJ3lUZAgMBAAECggEBAJTnCBBdUB+fSs1prjeS/gsmnfgJoY+K9H7PCIxgj3yw 9 | FXAvZAmRDKzJGlF2EOOQlTG0YNiGDj6EAtv7rjoKcINyULSg8IU6wLmn61MrAuUa 10 | fa+Bujgh4E/B5swhOHAztNhzkzsM70Hi17wXSislh+HWd7qteOgqcbqgdOR4gaj+ 11 | HUqtcxG3H3hCL3dWugnjLZMtestLKGHSSZvbQNjYM3kKy2LvO8NpxmDE4a+TXygK 12 | qhaZjmS/dc/nJBJzOfkzby58RvGbzlJflfW/Uu3/gizj13GFQKWonq1xh630RAhv 13 | xX5ySok2aAx/+/SiJSpNXvM09grQuoORSr7D1tm+5rECgYEA3vf0hRfua0XAOu6f 14 | pyzNvLRRJ/pEew7XpNPCyS2TuMTd1yvXjGVxQfP46N6x1IM3SRU0zE+LSk80EF7l 15 | u1Or7GyCEhabYNe/7P2F8ENP73Do0HwvcI1jGrgr6r9oK0J27Xei+f6Q0bgJOPI2 16 | qaLj+V37cOjkNSM1mhTjtDwK8k0CgYEAx6cMrkjHl1+lDIIOc3qAEL3jb3xQveYk 17 | WrMF/B+j048k6boU4VvFJAIyQxOxMNxLjw3/9+zXCFJT4WaZK3TMXlg614ASGx3H 18 | tKjJM9O07ywwMq1gbutFS4nHCg3L3Os6esL0SPwMdATR3Yh22n5OGI9o+/aURulL 19 | GPEXef1Z2/0CgYEAgmwp5LxV4vu+8Pnp+4DSq4ISQr861XyeGTUhKEp3sUm+tgFY 20 | KTChakHKpHS3Mqa6bQ5xft08je/8dWL9IHFWDIqAHxKIOsKY6oh1k0/cbyPtmx45 21 | Ja4efV+jmMHzrfJH3KnxdCg7D+GFy4CrBtlYXuJhlO81pft9fC6h7yh8ArUCgYBq 22 | gvkl5Zftbs4rnRq+iqTVyagTKvwcQzIz3PwdZHfO/rfPpUFMdNv4eN99n3zRN0Vs 23 | HSjoiEazntA3GLgwUdBRqLpDi4SdSMbo337vkksdqbJQ5uPiaMuAIBG6kF+pDSkW 24 | ovkWErlGD+gySoI10FozihaVDRhPuFgjB0PiBcIxtQKBgGNSzX+Bx5+ux1Qny0Sn 25 | SUcBtepLnO8M8wafoGNyehbMnLzfuMbaDiJOdozGlBNHZTtPB3r4AYb8WnltdKW0 26 | 7i3fk26YZGiMVeUJvewA6/LOBEaqMdwoNwnoptvbR6ehHeE/PPtRtge2cD3bPIM7 27 | U9VlWgfgj9Dxfwhslqb9hmyp 28 | -----END PRIVATE KEY----- 29 | -------------------------------------------------------------------------------- /src/About/index.ux: -------------------------------------------------------------------------------- 1 | 38 | 39 | 135 | 136 | 162 | -------------------------------------------------------------------------------- /src/Common/api/index.js: -------------------------------------------------------------------------------- 1 | const SL_API = 'http://api-xl9-ssl.xunlei.com/sl' 2 | 3 | export default { 4 | getList ({ cursor = 0, size = 10, category= 'new'}) { 5 | return natives.fetch({ 6 | url:`${SL_API}/cinecism/list`, 7 | method: 'GET', 8 | data: { 9 | cursor, 10 | size, 11 | category 12 | } 13 | }).then(res => { 14 | return res.data 15 | }) 16 | }, 17 | getReview (id) { 18 | return natives.fetch({ 19 | url:`${SL_API}/cinecism/info`, 20 | method: 'GET', 21 | data: { 22 | id, 23 | recommend_size: 0 24 | } 25 | }).then(res => res.data) 26 | } 27 | } -------------------------------------------------------------------------------- /src/Common/assets/css/article.css: -------------------------------------------------------------------------------- 1 | .list-main { 2 | display: flex; 3 | flex-direction: column; 4 | width: 100%; 5 | margin: 0 10px; 6 | } 7 | .list-item { 8 | display: flex; 9 | flex-direction: row; 10 | margin-top: 20px; 11 | } 12 | .art-pic { 13 | width: 350px; 14 | height: 200px; 15 | margin-right: 10px; 16 | } 17 | .art-title { 18 | flex-grow: 1; 19 | } 20 | .load-more { 21 | text-align: center; 22 | display: flex; 23 | justify-content: center; 24 | margin-top: 10px; 25 | } 26 | .round { 27 | width: 50px; 28 | height: 50px; 29 | } -------------------------------------------------------------------------------- /src/Common/assets/css/webview.css: -------------------------------------------------------------------------------- 1 | p { 2 | line-height: 10px; 3 | color: red 4 | } 5 | img { 6 | width: 400px; 7 | height: 300px; 8 | } -------------------------------------------------------------------------------- /src/Common/assets/img/default.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/greenfavo/quickapp-demo/ef0db849750a3b465d2e221874f60b935bbac5d8/src/Common/assets/img/default.png -------------------------------------------------------------------------------- /src/Common/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/greenfavo/quickapp-demo/ef0db849750a3b465d2e221874f60b935bbac5d8/src/Common/logo.png -------------------------------------------------------------------------------- /src/Detail/index.ux: -------------------------------------------------------------------------------- 1 | 7 | 8 | 37 | -------------------------------------------------------------------------------- /src/List/index.ux: -------------------------------------------------------------------------------- 1 | 18 | 19 | 66 | -------------------------------------------------------------------------------- /src/app.ux: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "package": "com.xunlei.movie", 3 | "name": "迅雷影评", 4 | "versionName": "1.0.0", 5 | "versionCode": "1", 6 | "minPlatformVersion": "101", 7 | "icon": "/Common/logo.png", 8 | "features": [ 9 | { "name": "system.prompt" }, 10 | { "name": "system.router" }, 11 | { "name": "system.shortcut" }, 12 | { "name": "system.fetch" }, 13 | { "name": "system.webview" } 14 | ], 15 | "permissions": [ 16 | { "origin": "*" } 17 | ], 18 | "config": { 19 | "logLevel": "debug", 20 | "designWidth": 640 21 | }, 22 | "router": { 23 | "entry": "List", 24 | "pages": { 25 | "List": { 26 | "component": "index" 27 | }, 28 | "Detail": { 29 | "component": "index" 30 | }, 31 | "About": { 32 | "component": "index" 33 | } 34 | } 35 | }, 36 | "display": { 37 | "titleBarBackgroundColor": "#f2f2f2", 38 | "titleBarTextColor": "#414141", 39 | "menu": true, 40 | "pages": { 41 | "List": { 42 | "titleBarText": "迅雷影评", 43 | "menu": true 44 | }, 45 | "Detail": { 46 | "titleBarText": "详情页", 47 | "menu": true 48 | }, 49 | "About": { 50 | "titleBarText": "关于", 51 | "menu": false 52 | } 53 | } 54 | } 55 | } -------------------------------------------------------------------------------- /src/util.js: -------------------------------------------------------------------------------- 1 | /** 2 | * 显示菜单 3 | */ 4 | export function showMenu () { 5 | var prompt = require('@system.prompt') 6 | var router = require('@system.router') 7 | var appInfo = require('@system.app').getInfo() 8 | prompt.showContextMenu({ 9 | itemList: ['保存桌面', '关于', '取消'], 10 | success: function (ret) { 11 | switch (ret.index) { 12 | case 0: 13 | // 保存桌面 14 | createShortcut() 15 | break 16 | case 1: 17 | // 关于 18 | router.push({ 19 | uri: '/About', 20 | params: { name: appInfo.name, icon: appInfo.icon } 21 | }) 22 | break 23 | case 2: 24 | // 取消 25 | break 26 | default: 27 | prompt.showToast({ message: 'error' }) 28 | } 29 | } 30 | }) 31 | } 32 | 33 | /** 34 | * 创建桌面图标 35 | * 注意:使用加载器测试`创建桌面快捷方式`功能时,请先在`系统设置`中打开`应用加载器`的`桌面快捷方式`权限 36 | */ 37 | export function createShortcut () { 38 | var prompt = require('@system.prompt') 39 | var shortcut = require('@system.shortcut') 40 | shortcut.hasInstalled({ 41 | success: function (ret) { 42 | if (ret) { 43 | prompt.showToast({ message: '已创建桌面图标' }) 44 | } else { 45 | shortcut.install({ 46 | success: function () { 47 | prompt.showToast({ message: '成功创建桌面图标' }) 48 | }, 49 | fail: function (errmsg, errcode) { 50 | prompt.showToast({ message: 'error: ' + errcode + '---' + errmsg }) 51 | } 52 | }) 53 | } 54 | } 55 | }) 56 | } 57 | 58 | /** 七牛图片缩略、剪裁 */ 59 | export function qiniuImageView2 (url, mode, w, h, q = 90) { 60 | return `${url}?imageView2/${mode}` + (w ? `/w/${w}` : '') + (h ? `/h/${h}` : '') + `/q/${q}/interlace/1` 61 | } 62 | 63 | /** 64 | * 获取缩略图url 65 | * @param {string} url 66 | * @param {number} width 67 | * @param {number} height 68 | */ 69 | export function getThumbUrl (url, width, height) { 70 | const plugins = { 71 | qiniu: { 72 | hosts: [ 73 | 'sl.wangpan.7niu.n0808.com' 74 | ], 75 | handler: () => { 76 | return qiniuImageView2(url, 0, width, height) 77 | } 78 | }, 79 | dnion: { 80 | hosts: [ 81 | 'static-xl9-ssl.xunlei.com' 82 | ], 83 | handler: () => { 84 | return `${url}?w=${width}&h=${height}` 85 | } 86 | } 87 | } 88 | 89 | for (let i in plugins) { 90 | let { hosts, handler } = plugins[i] 91 | if (hosts.some(host => url.match(host))) { 92 | return handler() 93 | } 94 | } 95 | 96 | // 如果没有匹配,就返回原url 97 | return url 98 | } 99 | --------------------------------------------------------------------------------