使用APICloud AVM框架開發人事檔案管理助手app實戰
由於人事檔案具有涉密性,所以本應用沒有使用後臺服務,全部功能都在APP本地實現。
開發工具採用APICloud Studio3,基於VSCode的(PS:比基於Atom的autio2好用太多);
資料庫採用sqllite,沒有使用UI框架,個人覺得AVM本身支援的flex佈局配合自寫CSS樣式,完全可以實現市面上所有的UI框架的元素,這個取決於個人功力。
一、專案思維腦圖
二、功能介紹
1、人員花名冊
2、編制情況
3、個人中心
三、技術要點
手勢密碼驗證,本地資料庫操作,語音播報。
用到的模組
專案檔案目錄
引用一下官方的關於目錄結構的介紹
四、功能開發詳解
1、首頁導航
系統首頁使用tabLayout,可以將相關引數配置在JSON檔案中,再在config.xml中將content的值設定成該JSON檔案的路徑。如果底部導航沒有特殊需求這裡強烈建議大家使用tabLayout為APP進行佈局,官方已經將各類手機螢幕及不同的解析度進行了適配,免去了很多關於適配方面的問題。
app.json檔案內容,關於json檔案的命名是沒有限制的,我習慣用app。
{ "name": "root", "textOffset": 6, "color": "#999999", "selectedColor": "#006aff", "scrollEnabled": false, "hideNavigationBar": false, "bgColor": "#fff", "navigationBar": { "background": "#006aff", "shadow": "rgba(0,0,0,0)", "color": "#fff", "fontSize": 18, "hideBackButton": true }, "tabBar": { "background": "#fff", "shadow": "#eee", "color": "#5E5E5E", "selectedColor": "#006aff", "textOffset": 3, "fontSize": 11, "scrollEnabled": true, "index": 1, "preload": 0, "frames": [ { "title": "編制情況", "name": "home", "url": "./pages/records/organ" }, { "title": "人員花名冊", "name": "course", "url": "./pages/person/organ" }, { "title": "個人中心", "name": "user", "url": "./pages/main/main" } ], "list": [ { "text": "編制", "iconPath": "./image/authoried-o.png", "selectedIconPath": "./image/authoried.png" }, { "text": "人員", "iconPath": "./image/person-o.png", "selectedIconPath": "./image/person.png" }, { "text": "我的", "iconPath": "./image/user-o.png", "selectedIconPath": "./image/user.png" } ] } }
2、列表顯示及分頁
通過上拉重新整理和下拉操作,配合JS方法實現分頁查詢功能。
<template name='list'> <scroll-view scroll-y class="main" enable-back-to-top refresher-enabled refresher-triggered={refresherTriggered} onrefresherrefresh={this.onrefresherrefresh} onscrolltolower={this.onscrolltolower}> <view class="item-box"> <view class="item" data-id={item.id} v-for="(item, index) in personList" tapmode onclick="openTab"> <image class="avator" src={item.photo} mode="widthFix"></image> <text class="item-title">{item.name}</text> <text class="item-sub-title">{item.nation}</text> </view> </view> <view class="footer"> <text class="loadDesc">{loadStateDesc}</text> </view> </scroll-view> </template>
<script> import $util from "../../utils/utils.js" export default { name: 'list', data() { return{ personList:[], skip: 0, refresherTriggered: false, haveMoreData: true, loading: false, organid:0 } }, computed: { loadStateDesc(){ if (this.data.loading || this.data.haveMoreData) { return '載入中...'; } else if (this.personList.length > 0) { return '沒有更多啦'; } else { return '暫時沒有內容'; } } }, methods: { apiready(){ this.data.organid = api.pageParam.id; this.loadData(false); //更換頭像 api.addEventListener({ name: 'setavator' }, (ret, err) => { this.loadData(); }); //新增人員資訊 api.addEventListener({ name: 'addperson' }, (ret, err) => { this.loadData(); }); //刪除人員資訊 api.addEventListener({ name: 'delperson' }, (ret, err) => { this.loadData(); }); if(api.getPrefs({sync: true,key: 'role'})=='99'){ //新增編輯按鈕 api.setNavBarAttr({ rightButtons: [{ text: '新增' }] }); //監聽右上角按鈕點選事件 api.addEventListener({ name: 'navitembtn' }, (ret, err) => { if (ret.type == 'right') { $util.openWin({ name: 'personadd', url: 'personadd.stml', title: '新增人員資訊', pageParam:{ organid:this.data.organid } }); } }); } }, loadData(loadMore) { if (this.data.loading) { return; } api.showProgress(); this.data.loading = true; var limit = 15; var skip = loadMore?(this.data.skip+1)*limit:0; // console.log('select id,name,grade,sex,nation,photo from authority where organ = '+this.data.organid+' order by id limit '+limit+' offset '+skip); var db = api.require('db'); db.selectSql({ name: 'doc', sql: 'select id,name,grade,sex,nation,photo from authorized where organ = '+this.data.organid+' order by id limit '+limit+' offset '+skip }, (ret, err)=> { // console.log(JSON.stringify(ret)); // console.log(JSON.stringify(err)); if (ret.status) { let records = ret.data; this.data.haveMoreData = records.length == limit; if (loadMore) { this.data.personList = this.data.personList.concat(records); } else { this.data.personList = records; } this.data.skip = skip; } else { this.data.recordsList = records; api.toast({ msg:err.msg }) } this.data.loading = false; this.data.refresherTriggered = false; api.hideProgress(); }); }, /*下拉重新整理頁面*/ onrefresherrefresh(){ this.data.refresherTriggered = true; this.loadData(false); }, onscrolltolower() { if (this.data.haveMoreData) { this.loadData(true); } }, openTab(e){ let id = e.currentTarget.dataset.id; $util.openWin({ name: "personinfo", url: 'personinfo.stml', title: '人員資訊', pageParam:{ id:id } }); } } } </script>
3、表單提交
採用AVM自帶的from控制元件,通過onsubmit進行資料提交
4、 頭像圖片上傳及base64轉碼
由於是本地sqllite資料庫,人員頭像圖片需要轉成base64編碼儲存到資料庫中。通過官方模組trans進行圖片轉碼操作。
<image class="avator" src={this.data.src} mode="widthFix" onclick="setavator"></image> setavator(){ api.actionSheet({ cancelTitle: '取消', buttons: ['拍照', '開啟相簿'] }, (ret, err) => { if (ret.buttonIndex == 3) { return false; } var sourceType = (ret.buttonIndex == 1) ? 'camera' : 'album'; api.getPicture({ sourceType: sourceType, allowEdit: true, quality: 20, destinationType:'url' }, (ret, err) => { if (ret && ret.data) { var trans = api.require('trans'); trans.decodeImgToBase64({ imgPath: ret.data }, (ret, err) => { // console.log(JSON.stringify(ret)); // console.log(JSON.stringify(err)); if (ret.status) { let b64 = "data:image/jpeg;base64,"+ret.base64Str; this.data.src = b64; } else { api.toast({ msg:'照片上傳失敗,請重新選擇!' }) } }); } }); }); },
5、sqllite資料庫 db模組
由於資料庫檔案需要儲存的應用安裝檔案中,所有需要官方fs模組配合使用來進行資料庫檔案的操作。
copyDB(){ var fs = api.require('fs'); fs.copyTo({ oldPath: 'widget://db/doc.db', newPath: 'fs://db' }, function(ret, err) { // console.log(JSON.stringify(ret)); // console.log(JSON.stringify(err)); if (ret.status) { // console.log(JSON.stringify(ret)); api.toast({ msg:'拷貝資料庫成功!' }) } else { // console.log(JSON.stringify(err)); api.toast({ msg:JSON.stringify(err) }) } }); }, openDB(){ var db = api.require('db'); db.subfile({ directory:'fs://db' }, (ret, err)=> { // console.log(JSON.stringify(ret)); // console.log(JSON.stringify(err)); if (ret.status) { // console.log(JSON.stringify(ret)); //開啟資料庫 db.openDatabase({ name: 'doc', path: ret.files[0] }, (ret, err)=> { // console.log(JSON.stringify(ret)); // console.log(JSON.stringify(err)); if (ret.status) { // console.log(JSON.stringify(ret)); api.toast({ msg:'開啟資料庫成功!' }) } else { // console.log(JSON.stringify(err)); api.toast({ msg:JSON.stringify(err) }) } }); } else { // console.log(JSON.stringify(err)); api.toast({ msg:JSON.stringify(err) }) } }); }, closeDB(){ var db = api.require('db'); db.closeDatabase({ name: 'doc' }, function(ret, err) { if (ret.status) { console.log(JSON.stringify(ret)); api.toast({ msg:'關閉資料庫成功' }) } else { // console.log(JSON.stringify(err)); api.toast({ msg:JSON.stringify(err) }) } }); }, updateDB(){ var fs = api.require('fs'); var db = api.require('db'); db.closeDatabase({ name: 'doc' }, (ret, err) => { if (ret.status) { //拷貝檔案 fs.copyTo({ oldPath: 'widget://doc.db', newPath: 'fs://db/' }, (ret, err) => { if (ret.status) { db.subfile({ directory:'fs://db' }, (ret, err)=> { if(ret.status){ //開啟資料庫 db.openDatabase({ name: 'doc', path: ret.files[0] }, (ret, err)=> { if (ret.status) { api.toast({ msg:'資料庫更新成功!' }) } else { api.toast({ msg:JSON.stringify(err) }) } }); } else{ api.toast({ msg:JSON.stringify(err) }) } }) } else { api.toast({ msg:JSON.stringify(err) }) } }); } else { api.toast({ msg:JSON.stringify(err) }) } }); },
6、語音播報功能
採用官方提供的IFLyVoice模組,需要注意的是,基礎資原始檔和發音人資原始檔(.jet檔案)需要到科大訊飛開發者平臺進行下載匯入的專案中。還有對數字的解讀不是精確,尤其是年份,最好不要用數字,而是用中文。
//新增朗讀按鈕 api.setNavBarAttr({ rightButtons: [{ text: '朗讀' }] }); //監聽右上角按鈕點選事件 api.addEventListener({ name: 'navitembtn' }, (ret, err) => { // console.log(JSON.stringify(this.data.info)); if (ret.type == 'right') { var IFlyVoice = api.require('IFlyVoice'); IFlyVoice.initSpeechSynthesizer((ret)=>{ // console.log(JSON.stringify(ret)); }); IFlyVoice.startSynthetic({ text:this.data.info, commonPath_Android:'widget://res/android/common.jet', pronouncePath_Android:'widget://res/android/xiaoyan.jet', pronounceName:'xiaoyan', speed:40 },(ret,err)=>{ // console.log(JSON.stringify(ret)); // console.log(JSON.stringify(err)); if (ret.status) { // console.log('合成成功'); } else { // console.log(JSON.stringify(err)); } }); } });
7、手勢密碼保護
手勢密碼保護由於官方模組存在樣式問題,及原生模組存在遮罩問題,所以採用了平臺上提供的H5模組。APICloud強大之處在這裡進行了淋漓盡致的體現,通過AVM及原生模組無法實現的功能,可以再用H5的方式來實現!牛逼!!!!,通過設定全域性變數來記錄是否已設定手機密碼,每次應用啟動通過這個變數來判斷是否開啟手勢密碼保護。
this.data.islock =api.getPrefs({sync: true,key: 'islock'}); if(this.data.islock=='Y'){ api.openFrame({ name: 'h5lock', url:'../../html/h5lock.html' }) } else{ api.toast({ msg:'您還沒有設定手勢密碼,為了資料安全,請儘快設定。' }) }
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=no"> <title>H5lock</title> <style type="text/css"> body { text-align: center; background-color: #000000; } .title { /*color: #87888a;*/ margin-top: 85px; font-size: 20px; font-weight:lighter; } </style> </head> <body> <script type="text/javascript" src="../script/H5lock.js"></script> <script type="text/javascript"> var opt = { chooseType: 3, // 3 , 4 , 5, width: 300, // lock wrap width height: 300, // lock wrap height container: 'element', // the id attribute of element inputEnd: function (psw){} // when draw end param is password string } var lock = new H5lock(opt); lock.init(); </script> </body> </html>
(function(){ window.H5lock = function(obj){ this.height = obj.height; this.width = obj.width; this.chooseType = Number(window.localStorage.getItem('chooseType')) || obj.chooseType; this.devicePixelRatio = window.devicePixelRatio || 1; }; H5lock.prototype.drawCle = function(x, y) { // 初始化解鎖密碼面板 小圓圈 this.ctx.strokeStyle = '#87888a';//密碼的點點預設的顏色 this.ctx.lineWidth = 2; this.ctx.beginPath(); this.ctx.arc(x, y, this.r, 0, Math.PI * 2, true); this.ctx.closePath(); this.ctx.stroke(); } H5lock.prototype.drawPoint = function(style) { // 初始化圓心 for (var i = 0 ; i < this.lastPoint.length ; i++) { this.ctx.fillStyle = style; this.ctx.beginPath(); this.ctx.arc(this.lastPoint[i].x, this.lastPoint[i].y, this.r / 2.5, 0, Math.PI * 2, true); this.ctx.closePath(); this.ctx.fill(); } } H5lock.prototype.drawStatusPoint = function(type) { // 初始化狀態線條 for (var i = 0 ; i < this.lastPoint.length ; i++) { this.ctx.strokeStyle = type; this.ctx.beginPath(); this.ctx.arc(this.lastPoint[i].x, this.lastPoint[i].y, this.r, 0, Math.PI * 2, true); this.ctx.closePath(); this.ctx.stroke(); } } H5lock.prototype.drawLine = function(style, po, lastPoint) {//style:顏色 解鎖軌跡 this.ctx.beginPath(); this.ctx.strokeStyle = style; this.ctx.lineWidth = 3; this.ctx.moveTo(this.lastPoint[0].x, this.lastPoint[0].y); for (var i = 1 ; i < this.lastPoint.length ; i++) { this.ctx.lineTo(this.lastPoint[i].x, this.lastPoint[i].y); } this.ctx.lineTo(po.x, po.y); this.ctx.stroke(); this.ctx.closePath(); } H5lock.prototype.createCircle = function() {// 建立解鎖點的座標,根據canvas的大小來平均分配半徑 var n = this.chooseType; var count = 0; this.r = this.ctx.canvas.width / (1 + 4 * n);// 公式計算 this.lastPoint = []; this.arr = []; this.restPoint = []; var r = this.r; for (var i = 0 ; i < n ; i++) { for (var j = 0 ; j < n ; j++) { count++; var obj = { x: j * 4 * r + 3 * r, y: i * 4 * r + 3 * r, index: count }; this.arr.push(obj); this.restPoint.push(obj); } } this.ctx.clearRect(0, 0, this.ctx.canvas.width, this.ctx.canvas.height); for (var i = 0 ; i < this.arr.length ; i++) { this.drawCle(this.arr[i].x, this.arr[i].y); } //return arr; } H5lock.prototype.getPosition = function(e) {// 獲取touch點相對於canvas的座標 var rect = e.currentTarget.getBoundingClientRect(); var po = { x: (e.touches[0].clientX - rect.left)*this.devicePixelRatio, y: (e.touches[0].clientY - rect.top)*this.devicePixelRatio }; return po; } H5lock.prototype.update = function(po) {// 核心變換方法在touchmove時候呼叫 this.ctx.clearRect(0, 0, this.ctx.canvas.width, this.ctx.canvas.height); for (var i = 0 ; i < this.arr.length ; i++) { // 每幀先把面板畫出來 this.drawCle(this.arr[i].x, this.arr[i].y); } this.drawPoint('#27AED5');// 每幀花軌跡 this.drawStatusPoint('#27AED5');// 每幀花軌跡 this.drawLine('#27AED5',po , this.lastPoint);// 每幀畫圓心 // if (this.lastPoint.length == 4) { // // debugger // } for (var i = 0 ; i < this.restPoint.length ; i++) { if (Math.abs(po.x - this.restPoint[i].x) < this.r && Math.abs(po.y - this.restPoint[i].y) < this.r) { this.drawPoint(this.restPoint[i].x, this.restPoint[i].y); this.lastPoint.push(this.restPoint[i]); this.restPoint.splice(i, 1); break; } } } H5lock.prototype.checkPass = function(psw1, psw2) {// 檢測密碼 var p1 = '', p2 = ''; for (var i = 0 ; i < psw1.length ; i++) { p1 += psw1[i].index + psw1[i].index; } for (var i = 0 ; i < psw2.length ; i++) { p2 += psw2[i].index + psw2[i].index; } return p1 === p2; } H5lock.prototype.storePass = function(psw) {// touchend結束之後對密碼和狀態的處理 if (this.pswObj.step == 1) { if (this.checkPass(this.pswObj.fpassword, psw)) { this.pswObj.step = 2; this.pswObj.spassword = psw; document.getElementById('title').innerHTML = '密碼儲存成功'; this.drawStatusPoint('#2CFF26'); this.drawPoint('#2CFF26'); window.localStorage.setItem('passwordxx', JSON.stringify(this.pswObj.spassword)); window.localStorage.setItem('chooseType', this.chooseType); } else { document.getElementById('title').innerHTML = '兩次不一致,重新輸入'; this.drawStatusPoint('red'); this.drawPoint('red'); delete this.pswObj.step; } } else if (this.pswObj.step == 2) { if (this.checkPass(this.pswObj.spassword, psw)) { var title = document.getElementById("title"); title.style.color = "#2CFF26"; title.innerHTML = '解鎖成功'; this.drawStatusPoint('#2CFF26');//小點點外圈高亮 this.drawPoint('#2CFF26'); this.drawLine('#2CFF26',this.lastPoint[this.lastPoint.length-1] , this.lastPoint);// 每幀畫圓心 api.closeFrame(); } else if (psw.length < 4) { this.drawStatusPoint('red'); this.drawPoint('red'); this.drawLine('red',this.lastPoint[this.lastPoint.length-1] , this.lastPoint);// 每幀畫圓心 var title = document.getElementById("title"); title.style.color = "red"; title.innerHTML = '請連線4個點'; } else { this.drawStatusPoint('red'); this.drawPoint('red'); this.drawLine('red',this.lastPoint[this.lastPoint.length-1] , this.lastPoint);// 每幀畫圓心 var title = document.getElementById("title"); title.style.color = "red"; title.innerHTML = '手勢密碼錯誤,請重試'; } } else { this.pswObj.step = 1; this.pswObj.fpassword = psw; document.getElementById('title').innerHTML = '再次輸入'; } } H5lock.prototype.makeState = function() { if (this.pswObj.step == 2) { // document.getElementById('updatePassword').style.display = 'block'; //document.getElementById('chooseType').style.display = 'none'; var title = document.getElementById("title"); title.style.color = "#87888a"; title.innerHTML = '請解鎖'; } else if (this.pswObj.step == 1) { //document.getElementById('chooseType').style.display = 'none'; // document.getElementById('updatePassword').style.display = 'none'; } else { // document.getElementById('updatePassword').style.display = 'none'; //document.getElementById('chooseType').style.display = 'block'; } } H5lock.prototype.setChooseType = function(type){ chooseType = type; init(); } H5lock.prototype.updatePassword = function(){ window.localStorage.removeItem('passwordxx'); window.localStorage.removeItem('chooseType'); this.pswObj = {}; document.getElementById('title').innerHTML = '繪製解鎖圖案'; this.reset(); } H5lock.prototype.initDom = function(){ var wrap = document.createElement('div'); var str = '<h4 id="title" class="title" style="color:#87888a">請繪製您的圖形密碼</h4>'; wrap.setAttribute('style','position: absolute;top:0;left:0;right:0;bottom:0;'); var canvas = document.createElement('canvas'); canvas.setAttribute('id','canvas'); canvas.style.cssText = 'background-color: #000;display: inline-block;margin-top: 76px;'; wrap.innerHTML = str; wrap.appendChild(canvas); var width = this.width || 320; var height = this.height || 320; document.body.appendChild(wrap); // 高清屏鎖放 canvas.style.width = width + "px"; canvas.style.height = height + "px"; canvas.height = height * this.devicePixelRatio; canvas.width = width * this.devicePixelRatio; } H5lock.prototype.init = function() { this.initDom(); this.pswObj = window.localStorage.getItem('passwordxx') ? { step: 2, spassword: JSON.parse(window.localStorage.getItem('passwordxx')) } : {}; this.lastPoint = []; this.makeState(); this.touchFlag = false; this.canvas = document.getElementById('canvas'); this.ctx = this.canvas.getContext('2d'); this.createCircle(); this.bindEvent(); } H5lock.prototype.reset = function() { this.makeState(); this.createCircle(); } H5lock.prototype.bindEvent = function() { var self = this; this.canvas.addEventListener("touchstart", function (e) { e.preventDefault();// 某些android 的 touchmove不宜觸發 所以增加此行程式碼 var po = self.getPosition(e); for (var i = 0 ; i < self.arr.length ; i++) { if (Math.abs(po.x - self.arr[i].x) < self.r && Math.abs(po.y - self.arr[i].y) < self.r) { self.touchFlag = true; self.drawPoint(self.arr[i].x,self.arr[i].y); self.lastPoint.push(self.arr[i]); self.restPoint.splice(i,1); break; } } }, false); this.canvas.addEventListener("touchmove", function (e) { if (self.touchFlag) { self.update(self.getPosition(e)); } }, false); this.canvas.addEventListener("touchend", function (e) { if (self.touchFlag) { self.touchFlag = false; self.storePass(self.lastPoint); setTimeout(function(){ self.reset(); }, 1000); } }, false); // document.getElementById('updatePassword').addEventListener('click', function(){ // self.updatePassword(); // }); } })();
8、設定手勢密碼
登入成功之後,在個人中心來設定手勢密碼。可通過引數設定來初始化密碼強度。
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=no"> <title>H5lock</title> <style type="text/css"> body { text-align: center; background-color: #000000; } .title { /*color: #87888a;*/ margin-top: 85px; font-size: 20px; font-weight:lighter; } .reset{ position: relative; top: 200px; font-size: 20px; text-align: center; } </style> </head> <body> <script type="text/javascript" src="../script/setH5lock.js"></script> <script type="text/javascript"> var opt = { chooseType: 3, // 3 , 4 , 5, width: 300, // lock wrap width height: 300, // lock wrap height container: 'element', // the id attribute of element inputEnd: function (psw){} // when draw end param is password string } var lock = new H5lock(opt); lock.init(); </script> </body> </html>
9、修改密碼
系統預設設定了使用者的初始密碼,使用者登入系統後會提示進行密碼修改,修改後的密碼進行了MD5加密,由於沒有後臺系統,所以密碼的MD5加密,採用了JS來進行加密。通過開發工具除錯控制檯安裝js外掛
安裝成功之後會在檔案目錄中顯示
然後在用的地方直接引入即可。
import $md5 from '../../node_modules/js-md5/build/md5.min.js' var db = api.require('db'); db.executeSql({ name: 'doc', sql: "update user set password = '"+ md5(ret.text) +"' where id = 1" }, (ret, err)=> { // console.log(JSON.stringify(ret)); // console.log(JSON.stringify(err)); if (ret.status) { api.alert({ title: '訊息提醒', msg: '密碼修改成功,請重新登陸', }, (ret, err) => { //清除使用者資訊 api.removePrefs({ key: 'username' }); api.removePrefs({ key: 'userid' }); api.removePrefs({ key: 'password' }); $util.openWin({ name: 'login', url: '../main/login.stml', title: '', hideNavigationBar:true }); }); } else { api.toast({ msg:JSON.stringify(err) }) } });
10、封裝工具類外掛 utils.js
在需要用到外掛中通用方法的地方,直接引用即可。
import $util from "../../utils/utils.js"
const $util = { openWin(param){ var param = { name: param.name, url: param.url, title: param.title||'', pageParam: param.pageParam||{}, hideNavigationBar: param.hideNavigationBar || false, navigationBar:{ background:'#1492ff', shadow: '#fff', color: '#fff' } }; if (this.isApp()) { api.openTabLayout(param); } else { api.openWin(param); } }, isApp(){ if (api.platform && api.platform == 'app') { return true; } return false; }, fitRichText(richtext, width){ var str = `<img style="max-width:${width}px;"`; var result = richtext.replace(/\<img/gi, str); return result; }, isLogin(){ if(api.getPrefs({sync: true,key: 'userid'})){ return true; } return false; }, openDataBase(){ var fs = api.require('fs'); var db = api.require('db'); db.subfile({ directory:'fs://db' }, (ret, err)=> { if(ret.status){ //開啟資料庫 db.openDatabase({ name: 'doc', path: ret.files[0] }, (ret, err)=> { if (ret.status) { // api.toast({ // msg:'開啟資料庫成功!' // }) } else { api.toast({ msg:JSON.stringify(err) }) } }); } else{ //拷貝檔案 fs.copyTo({ oldPath: 'widget://doc.db', newPath: 'fs://db/' }, function(ret, err) { if (ret.status) { db.subfile({ directory:'fs://db' }, (ret, err)=> { if(ret.status){ //開啟資料庫 db.openDatabase({ name: 'doc', path: ret.files[0] }, (ret, err)=> { if (ret.status) { } else { api.toast({ msg:JSON.stringify(err) }) } }); } else{ api.toast({ msg:JSON.stringify(err) }) } }) } else { api.toast({ msg:JSON.stringify(err) }) } }); } }) } } export default $util;
11、使用者功能許可權
系統分為2級使用者,管理員賬號和領導賬號。通過角色ID進行區分,管理員賬號有資訊的增刪改查功能,領導賬號只有資訊的查詢功能。
用於登入成功之後將使用者資訊進行快取。
//登陸APP submit() { api.showProgress(); // console.log( "select id,username,password from user where username = '"+this.data.user+"' and password = '"+md5(this.data.psw)+"'"); var db = api.require('db'); db.selectSql({ name: 'doc', sql: "select id,username,password,role from user where username = '"+this.data.user+"' and password = '"+md5(this.data.psw)+"'" }, (ret, err)=> { // console.log(JSON.stringify(ret)); // console.log(JSON.stringify(err)); if (ret.status) { if(ret.data.length==1){ api.setPrefs({key:'username',value:ret.data[0].username}); api.setPrefs({key:'userid',value:ret.data[0].id}); api.setPrefs({key:'password',value:ret.data[0].password}); api.setPrefs({key:'role',value:ret.data[0].role}); api.sendEvent({ name: 'loginsuccess', }); api.closeWin(); } else{ api.toast({ msg:'登陸失敗,請輸入正確的使用者名稱和密碼' }) } } else { api.toast({ msg:JSON.stringify(err) }) } api.hideProgress(); }); }
在需要驗證使用者許可權的地方通過獲取角色ID,進行邏輯判斷。
if(api.getPrefs({sync: true,key: 'role'})=='99'){ //新增編輯按鈕 api.setNavBarAttr({ rightButtons: [{ text: '編輯' }] }); //監聽右上角按鈕點選事件 api.addEventListener({ name: 'navitembtn' }, (ret, err) => { if (ret.type == 'right') { $util.openWin({ name: 'personedit', url: 'personedit.stml', title: '人員資訊編輯', pageParam:{ id:this.data.id } }); } }); } else{ //新增朗讀按鈕 api.setNavBarAttr({ rightButtons: [{ text: '朗讀' }] }); //監聽右上角按鈕點選事件 api.addEventListener({ name: 'navitembtn' }, (ret, err) => { // console.log(JSON.stringify(this.data.info)); if (ret.type == 'right') { var IFlyVoice = api.require('IFlyVoice'); IFlyVoice.initSpeechSynthesizer((ret)=>{ // console.log(JSON.stringify(ret)); }); IFlyVoice.startSynthetic({ text:this.data.info, commonPath_Android:'widget://res/android/common.jet', pronouncePath_Android:'widget://res/android/xiaoyan.jet', pronounceName:'xiaoyan', speed:40 },(ret,err)=>{ // console.log(JSON.stringify(ret)); // console.log(JSON.stringify(err)); if (ret.status) { // console.log('合成成功'); } else { // console.log(JSON.stringify(err)); } }); } }); }
12、雙擊退出應用程式
應用如果不做任何處理,在應用初始頁面出發keyback事件,會彈出提示框提示是否退出程式,體驗感極差。針對此進行了優化,由於應用首頁採用了tablayout,所以只需要在tablayout預設選中項的索引頁面中新增雙擊keyback事件的監聽,並通過api.toast進行提示。
在登入頁面也需要新增此監聽,應為使用者退出登入之後,會自動跳轉至登入頁,如果不做處理,使用者點選物理返回鍵就會導致使用者在沒有登入的情況下跳回上一頁。加了此監聽事件使用者點選返回鍵不做處理,雙擊會提示退出程式。
//監聽返回 雙擊退出程式 api.setPrefs({ key: 'time_last', value: '0' }); api.addEventListener({ name : 'keyback' }, (ret, err) => { var time_last = api.getPrefs({sync: true,key: 'time_last'}); var time_now = Date.parse(new Date()); if (time_now - time_last > 2000) { api.setPrefs({key:'time_last',value:time_now}); api.toast({ msg : '再按一次退出APP', duration : 2000, location : 'bottom' }); } else { api.closeWidget({ silent : true }); } });
13、應用動態許可權
安卓10之後,對應用的許可權要求提高,不在像老版本一樣配置上就會自動獲取,必須進行提示。
依據官方給出的教程進行了動態許可權的設定。
新增 mianfest.xml檔案
<?xml version="1.0" encoding="UTF-8"?> <manifest> <application name="targetSdkVersion" value="28"/> </manifest>
在系統索引頁進行動態許可權獲取提醒,本系統只涉及到了檔案儲存許可權的獲取,如需要獲取多個許可權,在List[]陣列中繼續新增需要的許可權,然後根據新增的許可權個數,做相應的幾個判斷即可。
let limits=[]; //獲取許可權 var resultList = api.hasPermission({ list: ['storage'] }); if (resultList[0].granted) { // 已授權,可以繼續下一步操作 } else { limits.push(resultList[0].name); } if(limits.length>0){ api.requestPermission({ list: limits, }, (res) => { }); }
- 設計模式之狀態模式
- 如何實現資料庫讀一致性
- 我是怎麼入行做風控的
- C 11精要:部分語言特性
- 吳恩達來信:人工智慧領域的求職小 tips
- EasyCV帶你復現更好更快的自監督演算法-FastConvMAE
- 某車聯網App 通訊協議加密分析(四) Trace Code
- 帶你瞭解CANN的目標檢測與識別一站式方案
- EasyNLP玩轉文字摘要(新聞標題)生成
- PostgreSQL邏輯複製解密
- 基於 CoreDNS 和 K8s 構建雲原生場景下的企業級 DNS
- 迴圈神經網路(RNN)可是在語音識別、自然語言處理等其他領域中引起了變革!
- 技術分享| 分散式系統中服務註冊發現元件的原理及比較
- 利用谷歌地圖採集外貿客戶的電話和手機號碼
- 跟我學Python影象處理丨關於影象金字塔的影象向下取樣和向上取樣
- 帶你掌握如何使用CANN 運算元ST測試工具msopst
- 一招教你如何高效批量匯入與更新資料
- 一步步搞懂MySQL元資料鎖(MDL)
- 你知道如何用 PHP 實現多程序嗎?
- KubeSphere 閘道器的設計與實現(解讀)