如图所示,在手机控制工具栏一侧增加扫码标签页。点击呈现扫码页。
stf 的前端是 用pug(原jade)模板,angularjs 编写, webpack打包构建的。
angularjs的语法可以从相关网站获取。
说下 新增文件 先:
res/app下是前端部分。
在res/app/control-panes下新建:
qrscan目录
1 index.js //载入依赖,加载页面pug模板,设置逻辑控制器
2 qrscan.pug //页面
3 qrscan.css
4 qrscan-controller.js //控制器
5 qrscan-spec.js //
当在control-panes添加一个新的模块时, 要添加到control-panes依赖中,否则不能被识别到。
//res/app/control-panes/index.js
module.exports = angular.module('control-panes', [
......
require('./qrscan').name
])
UI 图上新增的扫码标签页,在control-panes-controller里添加
//res/app/control-panes/control-pane-controller.js
module.exports =
function ControlPanesController(......) {
var sharedTabs = [
......
//添加扫码标签页
{
title: gettext('Scan'),
templateUrl: 'control-panes/qrscan/qrscan.pug', //指定渲染的页面
filters: ['native', 'web']
}
]
}
在qrscan/index.js里扫码模块 的 载入依赖,加载页面pug模板,设置逻辑控制器。
//res/app/control-pane/qrscan/index.js
require('./qrscan.css')
require('ng-file-upload') //上传文件所需模块
module.exports = angular.module('stf.qrscan', [
'angularFileUpload', //上传文件所需依赖
require('stf/settings').name,
require('stf/storage').name,
require('stf/upload').name,
require('stf/common-ui').name,
require('stf/image-onload').name,
require('gettext').name
])
.run(['$templateCache', function($templateCache) {
$templateCache.put(
'control-panes/qrscan/qrscan.pug'
, require('./qrscan.pug') //渲染页面设置
)
}])
.controller('QrscanCtrl', require('./qrscan-controller')) //QrscanCtrl 为 qrscan-controller的别名。 设置控制器
.fill-height(ng-controller="QrscanCtrl").stf-qrscan
.widget-content.padded
.drop-area(ng-file-drop='dropFile($files)', ng-file-drag-over-class='dragover').file-input.btn-file
input(type='file', ng-file-select='uploadQRcode($files)', ng-model="data.file")#img-input
i.fa.fa-2x.fa-download.drop-area-icon
.drop-area-text(translate) Drop Qrcode to upload
div.picture
img(ng-src='{{ imgSrc }}').qrscan-image.drop-area
div(id="tribtn").widget-content.padded.singleline
button.btn.btn-sm.btn-primary-outline.btn_menu(ng-click='saveToDcim()',
title='{{"saveToDcim"|translate}}')
i.fa.fa-save
span(translate) saveToDcim
button.btn.btn-sm.btn-primary-outline.btn_menu(onclick='openBrowser()'
title='{{"scan_qrcode"|translate}}')
i.fa.fa-camera
span(translate) scan_qrcode
button.btn.btn-sm.btn-primary-outline.btn_menu(onclick='pasteToClipboard()'
title='{{"pasteToClipboard"|translate}}')
i.fa.fa-copy
span(translate) pasteToClipboard
.clearfix
比较简单,分为四个部分:
可能一开始对pug模板这种类型,写着不熟悉,可以先写好html,再根据Pug的语法再写一遍。
这里提一个 命令行编译pug,编译成html
npm install -g pug-cli
pug index.pug
,输出 "rendered index.html"这就表示编译成功,我们去文件夹中看,就会发现多了一个index.html文件pug -P -w index.pug
, 实时编译就是当我们修改index.pug时,index.html也会被改变.说下这个页面的交互事件:
input选择文件后,触发ng-file-select 标注的 uploadQRcode方法(调用qrscan-controller.js里的($scope.uploadQRcode方法)
$files是文件形参.
img(ng-src=’{{ imgSrc }}’)
接受qrscan-controller.js的$scope.imgSrc变量, 使用imgSrc的值渲染预览图片
button.btn.btn-sm.btn-primary-outline.btn_menu(ng-click=‘saveToDcim()’,
ng-click 是当该button点击时,调用qrscan-controller.js (angularjs)的$scope.saveToDcim函数。
οnclick=‘openBrowser()’ ,οnclick=‘pasteToClipboard()’
而这两个button , 调用的是纯Js函数。
qrscan-controller.js代码
var Promise = require('bluebird')
Promise.longStackTraces()
module.exports = function QrscanCtrl(
$scope
, $filter
, StorageService
, $http
, $upload
) {
$scope.mReader = new FileReader()//处理二维码文件对象
$scope.mFiles //保存input标签传入的文件对象
$scope.uploadQRcode = function($file){
if ($files.length) {
$scope.mFiles = $files
/**
* 处理文件,预览二维码图片
*/
$scope.mReader.readAsDataURL($files[0])//传入File对象,Blob
$scope.mReader.onloadend = function(ev) {
$scope.$apply(function() {
// $scope.thumb = ev.target.result;
$scope.imgSrc = ev.target.result//接收base64
})
}
return true
}
}
$scope.saveToDcim = function(){...}
$scope.openBrowser = function(url){...}
$scope.pasteToClipboard = function(text){...}
当用户拖动或选择文件上传时,html的input标签的ng-file-select 标注的 uploadQRcode方法被触发,也就是$scope.uploadQRcode函数。
上述函数先判断参数$files数组长度,通过FileReader (scope.mReader)读取文件数据,然后在onLoadend函数里接受base64赋值给imgSrc, 直接反映在qrscan.pug里的img标签,这样完成对上传图片的预览。
还有一点是保存$files 给 $scope.mFIles, 供后面解码用。
index.pug是stf首页主页面 res/app/views/index.pug
script(src='static/app/views/js/reqrcode.js') #引入reqrcodejs
script.
function scanQrcode(files) {
//根据文件生成url,供reqrcodejs库的decode方法解析
var getObjectURL = function(file) {
var url = null;
if (window.createObjectURL != undefined) { // basic
url = window.createObjectURL(file);
}
else if (window.URL != undefined) { // mozilla(firefox)
try {
url = window.URL.createObjectURL(file);
} catch (e) {
}
}
else if (window.webkitURL != undefined) { // webkit or chrome
url = window.webkitURL.createObjectURL(file);
}
return url;
}
if (files != null && files.length > 0) {
qrcode.decode(getObjectURL(files[0]));//解析二维码
return qrcode;
}
else {
alert("请先上传二维码图片文件!")
return null;
}
}
将reqrcode.js源码文件放在res/app/views/js/下,然后script(src)引入(代码第一行).然后如注释所示,使用qrcode api解析二维码,封装成scanQrcode函数,供openBrowser函数调用。
//res/app/views/index.pug
function openBrowser() {
var appElement = document.querySelector('[ng-controller=QrscanCtrl]');
var $scope = angular.element(appElement).scope();
var reqrcode = scanQrcode($scope.mFiles)
if (reqrcode) {
reqrcode.callback = function(imgMsg) {
$scope.openBrowser(imgMsg);
}
}
}
虽然在webstorm中, qrscan.pug里的 openBrowser 事件不能导航到index.pug里,但是不影响运行。
这个逻辑是:
1.qrscan.pug里的openBrowser点击事件 调用 index.pug里的openBrowser js函数。2.通过document.querySelector和angular.element分别获取到angular controller和scope,拿到$scope.mFiles二维码文件传给scanQrcode函数解码
3.解码后再通过scope调用到qrscan-controller.js里的$scope.openBrowser方法进行后续处理。
综上,完成html调js对二维码解码,然后再调到angular controller里的方法并传参。后面就是向后台发起请求。一系列过程。
写完前端 , 执行 gulp clean
, gulp build
,这样 webpack构建,不然修改是不会生效的。
如果提示 gulp not found , 执行 npm install gulp -g
安装gulp.
然后执行stf local
查看效果。
接着说下解码之后,拿到链接信息,如何打开手机浏览器访问,也就是$scope.openBrowser所做的事情。
$scope.openBrowser = function(url) {
if (url.indexOf('http') != -1) {
alert('网页链接解析成功,正在跳转浏览器...')
return $scope.control.openBrowser(url, null)
}
else {
alert('该二维码不是正确的链接,二维码文本为' + imgMsg)
}
}
关于scope.control 通过IDE查看 能发现是 继承的 control-panes-controller的scope.control. (angularjs 子scope能继承父scope)
在control-panes-controller89行,scope.control = ControlService.create(device, device.channel)
所以$scope.control.openBrowser,也就是 ControlService的openBrowser函数。
打开浏览器功能在控制面板的导航里有实现,所以在做这部分功能就可以借鉴导航 navigation-controller这部分。
进入 control-service.js ,有这样代码
//res/app/components/stf/control/control-service.js
this.openBrowser = function(url, browser) {
return sendTwoWay('browser.open', {
url: url
, browser: browser ? browser.id : null
})
}
继续追踪sendTwoWay,其实就是socket发送出去。
那发送出去的信息,哪里接收的呢??
我们发现 browser.open 这样的标志,全局搜它,发现在lib/units/websocket/index.js里有处理.
//lib/units/websocket/index.js
.on('browser.open', function(channel, responseChannel, data) {
joinChannel(responseChannel)
push.send([
channel
, wireutil.transaction(
responseChannel
, new wire.BrowserOpenMessage(data)
)
])
})
这里又再次封装protobuf,将BrowserOpenMessage 结构体发出去。
同样,全局搜 BrowserOpenMessage。
找到 lib/units/device/plugins/browser.js
//lib/units/device/plugins/browser.js
router.on(wire.BrowserOpenMessage, function(channel, message) {
message.url = ensureHttpProtocol(message.url)
......
var reply = wireutil.reply(options.serial)
adb.startActivity(options.serial, {
action: 'android.intent.action.VIEW'
, component: message.browser
, data: message.url
})
.then(function() {
push.send([
channel
, reply.okay()
])
})
.catch(function(err) {
if (message.browser) {
log.error(
'Failed to open "%s" in "%s"'
, message.url
, message.browser
, err.stack
)
}
else {
log.error('Failed to open "%s"', message.url, err.stack)
}
push.send([
channel
, reply.fail()
])
})
})
router接收到BrowserOpenMessage, 解析出Message里的browser和url数据,通过adbkit startActivity传入action , browsername, url打开浏览器访问链接。
文件脉络为 contro-pane/index.js - control-pane-controller.js - qrscan/index.js - qrscan.pug - index.pug - qrscan-controller.js - control-service.js - websocket/index.js - plugins/browser.js
过程走完了。 完成上传二维码-预览-解码-打开浏览器。
最后提下
语言脚本路径: res/common/lang/translations/stf.zh_CN.json。
在里面直接元素即可。 比如 “scan”:‘扫码’,“drop files to upload”:“拖动文件以上传”,“openbrowser”:“打开浏览器”
修改后,一定要 gulp build 才会生效。