关于ionic:ionic解决picker选择器组件动态刷新选项重叠消失的问题

本地环境 Ionic: Ionic CLI : 6.17.1 Ionic Framework : @ionic/angular 5.9.2 @angular-devkit/build-angular : 12.0.0 @angular-devkit/schematics : 12.0.5 @angular/cli : 12.0.5 @ionic/angular-toolkit : 4.0.0问题阐明多列抉择的时候,动静刷新其余列,呈现选项全挤在一行的状况,滑动后恢复正常。属于官网未解决的bug:https://github.com/ionic-team... 解决方案每列的选项数量固定为可能性的最大值,理论选项有余的时候填补空白选项,并设置disabled属性。这样空选项不会显示进去。 if (newOptions.length < max) { for (let j = newOptions.length - 1; j < max; j++) { const disabledOption: PickerColumnOption = { text: '', value: -1, disabled: true, // 空白选项不显示 transform: '', // 解决切换选项后,有时选项不会显示的问题 } newOptions.push(disabledOption) } }触发刷新还须要保障列PickerColumn的prevSelected != selectedIndex,比方新建列前者不设置,后者设为0.

December 16, 2021 · 1 min · jiezi

关于ionic:我用ionic撸了一个USB转串口的调试工具

因为最近应产品经理的需要,须要做一个Android版的上位机APP,为此专门到某宝上购买了一个Type-C转串口的小设施,而后就开始折腾了。花了几天的工夫就把上位机APP做进去了,起初在闲暇工夫又做了一个串口调试的小工具,成果如下图 创立我的项目ionic start blank创立一个空白我的项目 装置串口插件要做一个串口通信的工具,那就得和硬件打交道,正好依据ionic官网文档,我找到了一个串口通信的插件,名为cordovarduino,通过尝试之后,发现此插件因为久年失修,尽管能够应用,然而在收发数据的时候总是无奈残缺接管到数据。依据对其代码查看,发现其中lib目录下有一个usbseriallibrary.jar文件,这个应该就是USB串口的驱动文件了吧。 久年失修的插件,预计就是这个jar包有问题,应该更新一下这个jar包就能够了,因而,通过usb-serial-for-android这个我的项目的介绍我从新打包了一个jar包,实现后尝试了一下,的确很完满,并且收发数据也没有任何问题了。因而,本人依据cordovarduino我的项目从新copy了一个我的项目cordova-plugin-usbserialport,因而你只须要装置我提供的插件即可 装置串口插件ionic cordova plugin add cordova-plugin-usbserialport装置本地数据存储插件ionic cordova plugin add cordova-plugin-nativestoragenpm install @ionic-native/native-storage装置状态栏插件ionic cordova plugin add cordova-plugin-statusbarnpm install @ionic-native/status-bar装置设施信息插件ionic cordova plugin add cordova-plugin-devicenpm install @ionic-native/device装置获取版本号插件ionic cordova plugin add cordova-plugin-app-versionnpm install @ionic-native/app-version装置APP最小化插件ionic cordova plugin add cordova-plugin-appminimizenpm install @ionic-native/app-minimize装置后盾运行插件ionic cordova plugin add cordova-plugin-background-modenpm install @ionic-native/background-mode串口操作次要代码declare let usbSerialPort: any; // 引入串口插件// 关上串口async openSerialPort() { const config = await this.nativeStorage.getItem('config'); // First request permission usbSerialPort.requestPermission(() => { console.log('get permission success.'); usbSerialPort.getDevice(data => { this.title = data.name; }); // open serial usbSerialPort.open(config, () => { console.log('Serial connection opened'); // get open status this.isOpen(); // read listener usbSerialPort.readListener(data => { clearTimeout(this.timer); const view = new Uint8Array(data); console.log(this.utils.bytes2HexString(view)); this.receiveDataArray.push(view); this.timer = setTimeout(() => { const now = new Date(); const dateMs = now.getMilliseconds(); this.zone.run(() => { const date = `<span style="color: #2fdf75">${this.utils.formatDate(now, 'hh:mm:ss')}.${dateMs} > </span>`; const resultUint8Array = this.utils.concatUint(Uint8Array, ...this.receiveDataArray); if (!this.utils.bytes2HexString(resultUint8Array)) { return; } this.receiveData += `<div style="-webkit-user-select: auto;-moz-user-select: auto;-ms-user-select: auto;user-select: auto;">${date}${this.utils.strDivision(this.utils.bytes2HexString(resultUint8Array), 2)}</div>`; this.receiveData += `<div style="margin-top:8px"></div>`; this.receiveLength = this.utils.bytes2HexString(resultUint8Array).length / 2; this.scrollToBottom(); }); }, 500); }, err => { console.log(`Read listener error: ${err}`); }); }); }, err => { console.log(`Get permission error: ${err}`); if (this.openStatus) { this.zone.run(() => { this.openStatus = false; this.title = this.translate.instant('SERIAL_DEVICE_TITLE'); }); } this.presentToast(this.translate.instant('NO_DEVICE_CONNECTED')); });}// 串口写入writerSerial() { if (!this.openStatus) { if (this.pack) { this.presentAlert(); } return; } this.receiveDataArray = []; const now = new Date(); const dateMs = now.getMilliseconds(); if (this.isWriterHex) { usbSerialPort.writeHex(this.pack, (res: any) => { console.log('writer res: ', res); const date = `<span style="color:#3880ff">${this.utils.formatDate(now, 'hh:mm:ss')}.${dateMs} < </span>`; this.receiveData += `<div>${date}${this.utils.strDivision(this.pack, 2)}</div>`; this.sendLength = this.pack.length / 2; }, err => { console.log('writer hex err: ', err); this.presentToast(); this.closeSerial(); }); } else { usbSerialPort.write(this.pack, (res: any) => { console.log('writer res: ', res); const date = `<span style="color:#3880ff">${this.utils.formatDate(now, 'hh:mm:ss')}.${dateMs} < </span>`; this.receiveData += `<div>${date}${this.utils.strDivision(this.utils.bufToHex(this.utils.stringToBytes(this.pack)), 2)}</div>`; this.sendLength = this.utils.getStringByteLength(this.pack); }, err => { console.log('writer string err: ', err); this.presentToast(); this.closeSerial(); }); }}// 串口开启状态isOpen() { usbSerialPort.isOpen(status => { console.log(`Serial open status: ${status}`); this.zone.run(() => { this.openStatus = status; }); });}// 敞开串口closeSerial(isOpenSerial?: boolean) { usbSerialPort.close(() => { this.isOpen(); this.receiveDataArray = []; if (isOpenSerial) { this.openSerialPort(); } });}其余为了可能对串口波特率进行设置,我还做了一个设置页面,次要用于设置波特率、数据位、进行位、以及收发数据记录的背景色彩切换、语言切换等性能。 ...

September 1, 2021 · 3 min · jiezi

关于ionic:Angluar5ionic3实践五

背景:复宏汉霖我的项目CR做完后.来整顿下须要优化的中央和给其余页面发送音讯知识点. 优化我的项目启动工夫先记一下:最开始运行的时候所需工夫:删完99%的console.log和解决掉所有tslint的报错后我的项目跑起来的工夫: 整整少了14.78s.真得不要小看console.log... 发送音讯理论开发场景 : 以后登录人的信息是保留在主页面的.最开始登陆进去的时候存入主页面的,而后其余页面用到的人员信息是主页面传进去的.在其中一个模块批改了人员岗位后.主页面没有从新获取人员信息.则会导致其余模块的信息有误.解决思路: 在其中一个模块批改了人员岗位后,给主页面传一个音讯.通知它须要从新获取登录人的信息.(其余任意页面都能通过Events获取到这个音讯) // 批改人员岗位的模块代码内容:import {Events} from 'ionic-angular';export class SetupPage { constructor(public ev: Events){} // 岗位切换 chooseJob = ()=>{ if (this.territoryList.length <= 1) { return; } let modal = this.modalCtrl.create("ChooseJobPage", { jobList:this.territoryList ,territoryID},{ cssClass: 'inset-modal'}); modal.onDidDismiss(data => { if (data.action == 'save') { // 在岗位切换胜利后通知主页面要从新获取人员信息 // 利用this.ev.publish('selectedStff')发消息. this.staffService.SelectedTerritoryToken(json).then((info) => {this.ev.publish('selectedStff')})} }); modal.present(); }}// 主页面代码内容:import {Events} from 'ionic-angular';export class MyApp { constructor(public ev: Events){ // 接管到音讯后,去调取获取人员信息接口 ev.subscribe("selectedStff", () =>{ this.getStaffInfo(); }) } // 获取以后用户信息 getStaffInfo = () => this.staffService.GetStaffInfo().then((info) => { // 从新赋值人员信息 this.currentStaff = info; });}

September 25, 2020 · 1 min · jiezi

关于ionic:只需三步即可创建出你的ionic项目简单使用

背景:之前我的项目是用React+AntDesignPro写的.再过两个礼拜就有一个新我的项目要写了.这个我的项目主管要求用Angular+ionic写,于是与开始了我的学习之旅.从创立一个ionic我的项目开始!加油!! 第一步:装置ionic用管理员的身份关上命令窗口肯定要先装置node环境运行命令: npm install -g ionic如下图 : 第二步:新建ionic我的项目2.1:切到你须要装置的我的项目文件夹下2.2:运行新建ionic我的项目的命令 ionic start命令介绍: // 给你的新我的项目取个名字 例:testIonicProject name: testIonic// 抉择新我的项目应用的框架:React/Angular 例:AngularFramework: Angular// 抉择下载我的项目模板 例:blank(空白)Starter template: blank如下图: 我的项目创立胜利后会呈现[Info]提醒: 第三步:运行ionic我的项目3.1:切到你刚刚创立的我的项目文件夹目录下3.2:运行我的项目运行命令: ionic serve运行胜利后会呈现一个地址http://localhost:8100页面上关上这个地址就会呈现以下页面代表整个步骤运行胜利! 应用vscode运行ionic的我的项目第一步:用vscode关上你的我的项目文件夹第二步:运行我的项目关上NPM文件夹鼠标移至start下面右侧会呈现一个小箭头,点击运行(因为懒的敲命令,点击start就能够运行了.也能够在vscode上方的终端,新建终端,输出ionic serve,是一样的成果~ 都是运行我的项目) 因为没有写过Angular的我的项目,所以首次运行的时候会有个确认装置Angular的提醒,抉择Y就好.运行胜利后会呈现一个窗口地址.http://localhost:4200这样就代表你用vscode运行我的项目胜利了~~浏览器关上窗口地址呈现了上面的页面.真香~~ 我的项目目录介绍我的项目目录如下: src // 开发工作目录,页面、款式、脚本和图片都放在这个目录下src/app // 利用根目录(组件、页面、服务、模块)src/assets // 资源目录(动态文件,图片,js框架...)src/environments // 环境配置src/theme // 主题文件,外面有一个scss文件,设置主题信息src/global.scss // 全局 css 文件src/index.html // index 入口文件src/global.scss // 全局款式src/main.ts // 主入口文件src/polyfills.ts // 这个文化蕴含 Angular 须要的填充,并在应用程序之前加载angular.json // angular配置文件ionic.config.json // ionic配置文件package.json // 配置我的项目的元数据和治理我的项目所须要的依赖package-lock.json // 本次配置我的项目的元数据和治理我的项目所须要的依赖tsconfig.json // TypeScript 我的项目的根目录,指定用来编译这个我的项目的问根文件和编译选项tslint.json // 格式化和校验 typescript ...

July 29, 2020 · 1 min · jiezi

关于ionic:只需三步即可创建出你的ionic项目简单使用

背景:之前我的项目是用React+AntDesignPro写的.再过两个礼拜就有一个新我的项目要写了.这个我的项目主管要求用Angular+ionic写,于是与开始了我的学习之旅.从创立一个ionic我的项目开始!加油!! 第一步:装置ionic用管理员的身份关上命令窗口肯定要先装置node环境运行命令: npm install -g ionic如下图 : 第二步:新建ionic我的项目2.1:切到你须要装置的我的项目文件夹下2.2:运行新建ionic我的项目的命令 ionic start命令介绍: // 给你的新我的项目取个名字 例:testIonicProject name: testIonic// 抉择新我的项目应用的框架:React/Angular 例:AngularFramework: Angular// 抉择下载我的项目模板 例:blank(空白)Starter template: blank如下图: 我的项目创立胜利后会呈现[Info]提醒: 第三步:运行ionic我的项目3.1:切到你刚刚创立的我的项目文件夹目录下3.2:运行我的项目运行命令: ionic serve运行胜利后会呈现一个地址http://localhost:8100页面上关上这个地址就会呈现以下页面代表整个步骤运行胜利! 应用vscode运行ionic的我的项目第一步:用vscode关上你的我的项目文件夹第二步:运行我的项目关上NPM文件夹鼠标移至start下面右侧会呈现一个小箭头,点击运行(因为懒的敲命令,点击start就能够运行了.也能够在vscode上方的终端,新建终端,输出ionic serve,是一样的成果~ 都是运行我的项目) 因为没有写过Angular的我的项目,所以首次运行的时候会有个确认装置Angular的提醒,抉择Y就好.运行胜利后会呈现一个窗口地址.http://localhost:4200这样就代表你用vscode运行我的项目胜利了~~浏览器关上窗口地址呈现了上面的页面.真香~~ 我的项目目录介绍我的项目目录如下: src // 开发工作目录,页面、款式、脚本和图片都放在这个目录下src/app // 利用根目录(组件、页面、服务、模块)src/assets // 资源目录(动态文件,图片,js框架...)src/environments // 环境配置src/theme // 主题文件,外面有一个scss文件,设置主题信息src/global.scss // 全局 css 文件src/index.html // index 入口文件src/global.scss // 全局款式src/main.ts // 主入口文件src/polyfills.ts // 这个文化蕴含 Angular 须要的填充,并在应用程序之前加载angular.json // angular配置文件ionic.config.json // ionic配置文件package.json // 配置我的项目的元数据和治理我的项目所须要的依赖package-lock.json // 本次配置我的项目的元数据和治理我的项目所须要的依赖tsconfig.json // TypeScript 我的项目的根目录,指定用来编译这个我的项目的问根文件和编译选项tslint.json // 格式化和校验 typescript ...

July 29, 2020 · 1 min · jiezi

Ionic34文件选择插件filechooser选择图片上传的问题

抉择图库中的文件上传请看另一文章在挪动我的项目开发中,应用的是Ionic框架,当中波及到文件上传。 开发思路: 应用文件抉择插件获取门路应用门路上传文件开发流程波及到的组件有(Ionic官网文档) 文件抉择 https://ionicframework.com/do...文件关上 https://ionicframework.com/do... (在已上传的文件列表中可能你会应用上)文件门路 https://ionicframework.com/do...插件装置上面只列举文件抉择的插件装置办法。文件关上和文件门路装置形式同理,文档有具体形容,文件抉择插件有版本的注意事项 Ionic3.x $ ionic cordova plugin add cordova-plugin-filechooser$ npm install --save @ionic-native/file-chooser@4Ionic4.x $ ionic cordova plugin add cordova-plugin-filechooser$ npm install @ionic-native/file-chooser文件抉择 filechooser文件抉择filechooser插件能调用手机中的文件治理,抉择文件夹外面的文件,胜利则返回文件的门路。应用办法如下 Ionic3.x import { FileChooser } from '@ionic-native/file-chooser';constructor(private fileChooser: FileChooser) { }...this.fileChooser.open() .then(uri => { // uri 文件的门路 console.log(uri) }) .catch(e => console.log(e));Ionic4.x import { FileChooser } from '@ionic-native/file-chooser/ngx';constructor(private fileChooser: FileChooser) { }...this.fileChooser.open() .then(uri => { // uri 文件的门路 console.log(uri) }) .catch(e => console.log(e));这时候,你能够应用取得的uri,联合http申请 或者 file-transfer插件进行文件上传。然而,你在抉择图片文件进行上传时,发现会上传失败,具体调试时发现并没上进行文件上传,其实起因是没有找到文件。请持续往下看。 ...

July 10, 2020 · 1 min · jiezi

ionic初体验

搞了一波cordova后,算是对Hybrid有了一点点微小的认知。为了快速开发,ionic无疑是更好的选择,它底层的打包和通信机制基于cordova实现,在上层实现了自己的UI组件,可以结合Angular或React使用,并且宣称将在未来支持Vue。 环境准备如果已经安装了cordova,则单独安装ionic即可,否则需要一并安装。 npm install -g ionic cordova创建项目通过start命令来新建一个ionic项目。 ionic start my-app并且可以支持传入模板,以及项目类型,具体参考ionic start。 我们在这里创建一个基于angular的tabs导航的app。 ionic start myapp tabs --type=ionic-angular当然也可以直接从一个更完善的模板开始。 ionic start myapp super --type=ionic-angular这几种方式可以都试试看。 运行项目在浏览器运行web版在尝试npm start调用ionic-app-scripts serve启动项目时,发现报错找不到@ionic/app-scripts模块,尝试重新安装该模块,node-gyp模块又报了这个错: Error: Can't find Python executable "python", you can set the PYTHON env variable.查询node-gyp后,官方提供了两种解决方案 我采用了第一种方案: npm install --global --production windows-build-toolsps: 必须以系统管理员方式运行命令行。 接着重新安装一遍@ionic/app-scripts,然后重新运行项目,冇问题啦。 npm uninstall @ionic/app-scriptsnpm install --save-dev @ionic/app-scriptsnpm start 支持android和iosionic cordova platform add iosionic cordova platform add androidandroid调试首先检查下设备连接是否正常 D:\robin\frontend\hybrid\ionic\ionic-blog> adb devicesList of devices attached5fdba1e7 device使用ionic cli提供的命令运行app ...

November 5, 2019 · 1 min · jiezi

PWA入门手把手教你制作一个PWA应用

简介Web前端的同学是否想过学习app开发,以弥补自己移动端能力的不足?但在面对一众的选择时很多同学略感迷茫,是学习ios还是android开发?是学习原生开发、混合开发(比如:Ionic),还是使用react native或者flutter这样的跨平台框架?而app开发的学习周期长、学习成本高也让一部分人望而却步。得益于前端技术的飞速发展、浏览器性能的不断提高,使用网页技术开发出接近原生体验的应用得以变为现实,PWA就在这样的背景下应运而生。可以用自己熟悉的HTML、CSS、Javascript开发出媲美原生app的网站,不仅拥有接近原生app的流畅程度,并且具备一些原生app才有的特性,比如:a. 可以在主屏上安装应用图标,b. 离线状态下访问,c. 获取消息通知,等等。。PWA的出现让大家看到了希望! 对比原生应用那PWA和原生应用相比到底有何竞争力呢?我们分别看一下原生应用和PWA的特点: 原生应用: 使用原生SDK和开发工具开发需要考虑跨平台,不同系统往往需要独立开发需要发布到应用商店才能下载使用可以安装到手机主屏,生成应用图标直接运行于操作系统上,访问系统资源方便可以离线使用可以获取消息通知PWA应用: 使用HTML,CSS,JS开发无需考虑跨平台,只需要考虑浏览器兼容性通过url访问,无需发布到应用商店可以安装到手机主屏,生成应用图标运行于浏览器中,可访问系统资源可以离线使用可以获取消息通知可以发现PWA具备了原生应用的主要能力,但是开发流程却比原生应用更加简洁:a. html/css/js的群众基础更好,开发效率更高;b. 省去了为不同系统开发独立版本的大量成本;c. 省去了上架到应用市场的繁琐流程;d. 无需前往应用商店下载,用户使用起来也更加方便。但是值得注意的是,PWA还是相对比较新的技术,实现规范还有很多调整的空间,部分浏览器对PWA的支持也还不完善,但是PWA是一个趋势,所以现在学习正合适! 本文将通过一个简单的列子(一个简单的邮编查询app)向大家展示PWA的开发流程,项目参考:Traversy Media - Build a PWA With Vue & Ionic4。完成后的效果是 这样的 。 创建项目项目使用Vue + Ionic的组合进行开发。本文主要关注PWA的搭建,因此vue、ionic等技术不做过多描述。使用VSCode的同学,建议安装Vetur插件增加开发效率。 1. 首先全局安装 @vue/cli: npm install -g @vue/cli2. 初始化vue项目: vue create vue-ionic-pwa3. 因为ionic的路由依赖于vue-router,所以接下来安装 vue-router: vue add router4. 安装 @ionic/vue npm install @ionic/vue5. 在 src/main.js 中添加对ionic的引用: ...import Ionic from '@ionic/vue'import '@ionic/core/css/ionic.bundle.css'Vue.use(Ionic)...6. 在 src/router.js 中使用 IonicVueRouter 替换默认的vue router: import Vue from 'vue'import { IonicVueRouter } from '@ionic/vue';import Home from './views/Home.vue'Vue.use(IonicVueRouter)export default new IonicVueRouter({ mode: 'history', base: process.env.BASE_URL, routes: [ { path: '/', name: 'home', component: Home } ]})7. 将 src/App.vue 内容修改为: ...

June 7, 2019 · 3 min · jiezi

在Ionic 或者 Angular 中用 Google Map API 实现自动补全地址(autocomplete)

先上效果图:Github: https://github.com/luchenwei9…实现步骤:环境安装就不提了,无非就是用npm全局安装Ionic 或者 Angular。本文是以Ionic为例。1. 安装type/googlemapsnpm install type/googlemaps -save2. 把Google API Key 声明在你的index.html里申请地址 https://developers.google.com/maps/documentation/javascript/get-api-key在key处的值替换成你的的key,然后将这段代码放到index.html里<script src=“https://maps.googleapis.com/maps/api/js?key=your-google-key&libraries=places"></script>3. 编写代码我这里直接用home了4. 运行查看效果几个注意事项1. 如果你是Angular6或者以上的版本,请一定要在相关ts文件里的第一行声明这个/// <reference types="@types/googlemaps” />如果不是,请声明这一句代码import {} from “googlemaps”;具体讨论请看这里:https://stackoverflow.com/questions/51084724/types-googlemaps-index-d-ts-is-not-a-module2. 我这里用的是<ion-input></ion-input>标签,而这个API支持的是原生的HTML<input />标签。如果不是原生的话,会报这个错误:所以我的在home.page.ts文件里的getPlaceAutocomplete()获取DOM的代码是这样写的let ele = document.getElementById(‘addresstext’).getElementsByTagName(‘input’)[0];如果是原生<input />标签,还可以这样写:(详细代码请参考github地址)html文件<input #addresstext style=“border:1px solid #111;width: 100%” />ts文件/// <reference types="@types/googlemaps" />import { Component, ViewChild, OnInit, AfterViewInit , NgZone } from ‘@angular/core’;@Component({ selector: ‘app-home’, templateUrl: ‘home.page.html’, styleUrls: [‘home.page.scss’],})export class HomePage implements OnInit, AfterViewInit { @ViewChild(‘addresstext’) addresstext: any; … private getPlaceAutocomplete() { let ele = this.addresstext.nativeElement; const autocomplete = new google.maps.places.Autocomplete(ele, { … }}顺便放一下两者的效果图,让大家看一下效果图,可以发现区别是,如果是原生的HTML标签,google会自动帮你添加placeholder :)3*.虽然是在ngAfterViewInit()里调用的googleMap初始化函数,理论上此时视图已经初始化好了,所以DOM应该渲染出来了。但实际项目中,还是会如下所示的错误猜想原因,应该一开始google找不到相关的DOM的节点,所以我在这里加了一个setTimeout()ngAfterViewInit() { setTimeout(() => { this.getPlaceAutocomplete(); },1000);} ...

March 27, 2019 · 1 min · jiezi

ionic3 + 时间控件Date Picker的运用

1.安装安装Cordova和Ionic Native插件:$ ionic cordova plugin add cordova-plugin-datepicker$ npm install –save @ionic-native/date-picker2.在app.module.ts中引入date-picker:import { DatePicker } from ‘@ionic-native/date-picker’;…@NgModule({ … providers: [ … DatePicker … ] …})export class AppModule { }3.用法html中:<ion-item> <ion-label>日期</ion-label> <ion-input disabled=true type=“text” [(ngModel)]=“tDate” text-right (click)=“getDate()"></ion-input></ion-item>ts中:import { DatePicker,DatePickerOptions } from ‘@ionic-native/date-picker’;//导入constructor( private datePicker: DatePicker){}getDate() { let options : DatePickerOptions ={ date: new Date(), mode: ‘datetime’, titleText:‘请选择日期’, okText:‘选择’, cancelText: ‘取消’, todayText:‘今天’, nowText: ‘现在’, is24Hour:true, allowOldDates:true, doneButtonLabel:‘确定’, minuteInterval:10, androidTheme: this.datePicker.ANDROID_THEMES.THEME_HOLO_DARK } this.datePicker.show(options).then( date => { let time = new Date(date.getTime() + 8 * 60 * 60 * 1000).toISOString(); this.tDate = String(time.substring(0, time.length - 5)).replace(‘T’, ’ ‘) //格式时间显示样式 }, err => console.log(‘Error occurred while getting date: ‘, err) ); }最后在真机或模拟机上进行测试 ...

March 26, 2019 · 1 min · jiezi

Ionic开发App中重要的部分

写在前面APP赶在了春节之前上线了,所以这次我们分享一下使用Ionic3 + Angular5构建一个Hybird App过程中的经验。什么是Hybird App以及一些技术的选型这里就不讨论了。我每次完成一个部分就写一部分,所以有文章有点长。如果有错误的地方感谢大家指正为什么选了Ionic ?有些朋友说Angular/Ionic不大行,但是我觉的技术没有好坏之分,只有适合不适合。首先在我看来Ionic已经在Hybird App开发领域立足多年,已经相当的成熟了,我觉的比大部分的解决方案都要好。其次因为我们的App是一个弱交互多展示类型的,Ionic满足我们的需求。最后是因为如果你想在没有Android团队和IOS团队支持的情况下独立完成一款APP,那么Ionic我觉的是不二之选。因为Ionic4还在beta版本,并且是公司项目所以依然选用了稳定的3.X版本。注意:非基础入门教程,所以在读这篇文章之前建议你最好先了解Angular, TS, Ionic的基础知识,这里主要是希望大家在使用Ionic的时候能少走一些弯路。由于我自己用的不是很熟练Rxjs这一块就没有写,等以后对Rxjs的理解更加深刻了再加上Angular汇总部分既然是基于Angular那我们首先来了解一下Angular,这个地方积累的是Angular中零散的部分。如果内容多的话后期会拆分为单独的部分Angular组件生命周期Angular的生命周期Hooks官方介绍constructor() : 在任何其它生命周期钩子之前调用。可以用它来注入依赖项,但不要在这里做正事。ngOnChanges(changes: SimpleChanges) => void: 当被绑定的输入属性的值发生变化时调用,首次调用一定会发生在 ngOnInit() 之前ngOnInit() => void: 在第一轮 ngOnChanges() 完成之后调用。只调用一次ngDoCheck() => void: 在每个变更检测周期中调用,ngOnChanges() 和 ngOnInit() 之后ngAfterContentInit() => void: Angular 把外部内容投影进组件/指令的视图之后调用。可以认为是外部内容初始化ngAfterContentChecked() => void: Angular 完成被投影组件内容的变更检测之后调用。可以认为是外部内容更新ngAfterViewInit() => void: 每当 Angular 初始化完组件视图及其子视图之后调用。只调用一次。ngAfterViewChecked() => void:每当 Angular 做完组件视图和子视图的变更检测之后调用, ngAfterViewInit() 和每次 ngAfterContentChecked() 之后都会调用。ngOnDestroy() => void:在 Angular 销毁指令/组件之前调用。Angular中内容映射(插槽)的实现<ng-content></ng-content>默认映射这个内容映射方向是由父组件映射到子组件中这个就相当于vue中的slot,用法也都是一样的:<!– 父组件 –><child-component> 我是父组件中的内容默认映射过来的</child-component><!– 子组件 –><!– 插槽 –> <ng-content> </ng-content>上面是最简单的默认映射使用方式针对性映射(具名插槽)我们也可以通过<ng-content>的select属性实现我们的具名插槽。这个是可以根据条件进行填充。select属性支持根据CSS选择器(ELement, Class, [attribute]…)来匹配你的元素,如果不设置就全部接受,就像下面这样:<!– 父组件 –><child-component> 我是父组件中的内容默认映射过来的 <header> 我是根据header来映射的 </header> <div class=“class”> 我是根据class来映射的 </div> <div name=“attr”> 我是根据attr来映射的 </div></child-component><!– 子组件 –><!– 具名插槽 –><ng-content select=“header”></ng-content><ng-content select=".class"></ng-content><ng-content select="[name=attr]"></ng-content>ngProjectAs上面那些都是映射都是作为直接子元素进行的映射,那要不是呢? 我想在外面再套一层呢?<!– 父组件 –><child-component> <!– 这个时不是直接子节点了 这肯定是不行的 那我们就用到ngProjectAs了–> <div> <header> 我是根据header来映射的 </header> </div></child-component>使用ngProjectAs,它可以作用于任何元素上。<!– 父组件 –><child-component> <div ngProjectAs=“header”> <header> 我是根据ngProjectAs header来映射的 </header> </div></child-component>ng-content有一个@ContentChild装饰器,可以用来调用和投影内容。但是要注意:只有在ngAfterContentInit声明周期中才能成功获取到通过ContentChild查询的元素。既然提到了ng-content那我们就来聊一聊ng-template和ng-containerng-template<ng-template> 元素是动态加载组件的最佳选择,因为它不会渲染任何额外的输出<div class=“ad-banner-example”> <h3>Advertisements</h3> <ng-template ad-host></ng-template></div>ng-container<ng-container> 是一个由 Angular 解析器负责识别处理的语法元素。 它不是一个指令、组件、类或接口,更像是 JavaScript 中 if 块中的花括号。一般用来把一些兄弟元素归为一组,它不会污染样式或元素布局,因为 Angular 压根不会把它放进 DOM 中。<p> I turned the corner <ng-container ngIf=“hero”><!– ng-container不会被渲染 –> and saw {{hero.name}}. I waved </ng-container> and continued on my way.</p>Angular指令Angular中的指令分为组件,属性指令和结构形指令。属性型指令用于改变一个 DOM 元素的外观或行为,例如NgStyle。结构型指令的职责是 HTML 布局。 它们塑造或重塑 DOM 的结构,比如添加、移除或维护这些元素,例如NgFor和NgIf。属性型指令通过Directive装饰符把一个类标记为 Angular 指令, 该选项提供配置元数据,用于决定该指令在运行期间要如何处理、实例化和使用。@Directive通过ElementRef获取绑定元素的DOM对象,ElementRef。通过HostListener响应用户引发的事件,把一个事件绑定到一个宿主监听器,并提供配置元数据。 当宿主元素发出特定的事件时,Angular 就会执行所提供的处理器方法,并使用其结果更新所绑定到的元素。 如果该事件处理器返回 false,则在所绑定的元素上执行 preventDefault。HostListener通过Input装饰符把某个类字段标记为输入属性,并且提供配置元数据。 声明一个可供数据绑定的输入属性,在变更检测期间,Angular 会自动更新它,@Input。@Input(‘appHighlight’) highlightColor: string;下面是一个完整的属性形指令的例子import {Directive, ElementRef, HostListener, Input} from ‘@angular/core’;@Directive({ selector: ‘[sxylight]’})export class SxylightDirective { constructor(private el: ElementRef) { el.nativeElement.style.backgroundColor = ‘yellow’; } // 指令绑定的值 @Input(‘sxylight’) highlightColor: string; // 在指令内部,该属性叫 highlightColor,在外部,你绑定到它地方,它叫 sxylight 这个是绑定的别名 // 指令宿主绑定的值 @Input() defaultColor: string; // 监听宿主事件 @HostListener(‘mouseenter’) onMouseEnter() { this.highlight(this.highlightColor || this.defaultColor || ‘red’); } @HostListener(‘mouseleave’) onMouseLeave() { this.highlight(null); } private highlight(color: string) { this.el.nativeElement.style.backgroundColor = color; }}结构型指令星号()前缀:这个东西其实是语法糖,Angular 把 ngIf 属性 翻译成一个 <ng-template> 元素 并用它来包裹宿主元素。<ng-template>: 它是一个 Angular 元素,用来渲染 HTML。 它永远不会直接显示出来。 事实上,在渲染视图之前,Angular 会把 <ng-template> 及其内容替换为一个注释。<ng-container>: 它是一个分组元素,但它不会污染样式或元素布局,因为 Angular 压根不会把它放进 DOM 中。TemplateRef: 可以使用TemplateRef取得 <ng-template> 的内容,TemplateRef<any>ViewContainerRef: 可以通过ViewContainerRef来访问这个视图容器,ViewContainerRef。完整示例import { Directive, Input, TemplateRef, ViewContainerRef } from ‘@angular/core’;/** Input, TemplateRef, ViewContainerRef 这三个模块是构建一个结构型指令必须的模块* Input: 传值* TemplateRef: 表示一个内嵌模板,它可用于实例化内嵌的视图。 要想根据模板实例化内嵌的视图,请使用 ViewContainerRef 的 createEmbeddedView() 方法。* ViewContainerRef: 表示可以将一个或多个视图附着到组件中的容器。/@Directive({ selector: ‘[structure]’ // Attribute selector})export class StructureDirective { private hasView = false @Input() set structure(contion: boolean) { console.log(contion) if (!contion && !this.hasView) { this.viewCon.createEmbeddedView(this.template) // 实例化内嵌视图并插入到容器中 this.hasView = true } else if (contion && this.hasView) { this.viewCon.clear() // 销毁容器中的所有试图 this.hasView = false } } constructor( private template: TemplateRef<any>, private viewCon: ViewContainerRef ) { console.log(‘Hello StructureDirective Directive’); }}Angular中的Module首先我们来看看NgModuleinterface NgModule { // providers: 这个选项是一个数组,需要我们列出我们这个模块的一些需要共用的服务 // 然后我们就可以在这个模块的各个组件中通过依赖注入使用了. providers : Provider[] // declarations: 数组类型的选项, 用来声明属于这个模块的指令,管道等等. // 然后我们就可以在这个模块中使用它们了. declarations : Array<Type<any>|any[]> // imports: 数组类型的选项,我们的模块需要依赖的一些其他的模块,这样做的目的使我们这个模块 // 可以直接使用别的模块提供的一些指令,组件等等. imports : Array<Type<any>|ModuleWithProviders|any[]> // exports: 数组类型的选项,我们这个模块需要导出的一些组件,指令,模块等; // 如果别的模块导入了我们这个模块, // 那么别的模块就可以直接使用我们在这里导出的组件,指令模块等. exports : Array<Type<any>|any[]> // entryComponents: 数组类型的选项,指定一系列的组件,这些组件将会在这个模块定义的时候进行编译 // Angular会为每一个组件创建一个ComponentFactory然后把它存储在ComponentFactoryResolver entryComponents : Array<Type<any>|any[]> // bootstrap: 数组类型选项, 指定了这个模块启动的时候应该启动的组件.当然这些组件会被自动的加入到entryComponents中去 bootstrap : Array<Type<any>|any[]> // schemas: 不属于Angular的组件或者指令的元素或者属性都需要在这里进行声明. schemas : Array<SchemaMetadata|any[]> // id: 字符串类型的选项,模块的隐藏ID,它可以是一个名字或者一个路径;用来在getModuleFactory区别模块,如果这个属性是undefined // 那么这个模块将不会被注册. id : string}app.module.tsapp.module.ts└───@NgModule └───declarations // 告诉Angular哪些模块属于NgModule │───imports // 导入需要使用的模块 │───bootstrap // 启动模块 │───entryComponents // 定义组建时应该被编译的组件 └───providers // 服务配置entryComponents:Angular使用entryComponents来启用tree-shaking,即只编译项目中实际使用的组件,而不是编译所有在ngModule中声明但从未使用的组件。离线模板编译器(OTC)只生成实际使用的组件。如果组件不直接用于模板,OTC不知道是否需要编译。有了entryComponents,你可以告诉OTC也编译这些组件,以便在运行时可用。Ionic工程目录结构首先来看项目目录Ionic-frame│ build // 打包扩展│ platforms // Android/IOS 平台代码│ plugins // cordova插件│ resources└───src // 业务逻辑代码│ │ app // 启动组件│ │ assets // 资源│ │ components // 公共组件│ │ config // 配置文件│ │ directive // 公共指令│ │ interface // interface配置中心│ │ pages // 页面│ │ providers // 公共service│ │ service // 业务逻辑service│ │ shared // 共享模块│ │ theme // 样式模块│ │ index.d.ts // 声明文件└───www // 打包后静态资源Ionic视图生命周期生命周期的重要性不用多说,这是Ionic官网的介绍constrctor => void: 构造函数启动,构造函数在ionViewDidLoad之前被触发ionViewDidLoad => void: 资源加载完毕时触发。ionViewDidLoad只在第一次进入页面时触发只触发一次ionViewWillEnter => void: 页面即将给进入时触发每次都会触发ionViewDidEnter => void: 进入视图之后出发每次都会触发ionViewWillLeave => void: 即将离开(仅仅是触发要离开的动作)时触发每次都会触发ionViewDidLeave => void: 已经离开页面时触发每次都会触发ionViewWillUnload => void: 在页面即将被销毁并删除其元素时触发ionViewCanEnter => boolean:在视图可以进入之前运行。 这可以在经过身份验证的视图中用作一种“保护”,您需要在视图可以进入之前检查权限ionViewCanLeave => boolean:在视图可以离开之前运行。 这可以在经过身份验证的视图中用作一种“防护”,您需要在视图离开之前检查权限注意: 当你想使用ionViewCanEnter/ionViewCanLeave进行对路由的拦截时,你需要返回一个Boolen。返回true进入下一个视图,返回fasle留在当前视图。可以按照下面的代码感受一下生命周期的顺序constructor(public navCtrl: NavController) { console.log(‘触发构造函数’)}/** * 页面加载完成触发,这里的“加载完成”指的是页面所需的资源已经加载完成,但还没进入这个页面的状态(用户看到的还是上一个页面)。全程只会调用一次 /ionViewDidLoad () { console.log(Ionic触发ionViewDidLoad); // Step 1: 创建 Chart 对象 const chart = new F2.Chart({ id: ‘myChart’, pixelRatio: window.devicePixelRatio // 指定分辨率 }) // Step 2: 载入数据源 chart.source(data) chart.interval().position(‘genresold’).color(‘genre’) chart.render()}/** * 即将进入Ionic视图 这时对页面的数据进行预处理 每次都会触发 /ionViewWillEnter(){ console.log(Ionic触发ionViewWillEnter)}/* * 已经进入Ionic视图 每次都会触发 /ionViewDidEnter(){ console.log(Ionic触发ionViewDidEnter)}/* * 页面即将 (has finished) 离开时触发 每次都会触发 /ionViewWillLeave(){ console.log(Ionic触发ionViewWillLeave)}/* * 页面已经 (has finished) 离开时触发,页面处于非激活状态了。 每次都会触发 /ionViewDidLeave(){ console.log(Ionic触发ionViewDidLeave)}/* * 页面中的资源即将被销毁 一般用处不大 /ionViewWillUnload(){ console.log(Ionic触发ionViewWillUnload)}//守卫导航钩子: 返回true或者false/* * 在视图可以进入之前运行。 这可以在经过身份验证的视图中用作一种“保护”,您需要在视图可以进入之前检查权限 /ionViewCanEnter(){ console.log(Ionic触发ionViewCanEnter) const date = new Date().getHours() console.log(date) if (date > 22) { return false } return true}/* * 在视图可以离开之前运行。 这可以在经过身份验证的视图中用作一种“防护”,您需要在视图离开之前检查权限 /ionViewCanLeave(){ console.log(Ionic触发ionViewCanLeave) const date = new Date().getHours() console.log(date) if (date > 10) { return false } return true}项目配置文件设置Ionic3.X中并没有提供相应的的配置文件,所以我们需要自己按照下面步骤手动去添加配置文件来对项目进行配置。新增config目录src |__config |__config.dev.ts |__config.prod.tsconfig.dev.ts / config.prod.tsexport const CONFIG = { BASE_URL : ‘http://XXXXX/api’, // API地址 VERSION : ‘1.0.0’}在根目录下新增build文件夹,在文件夹中新增webpack.config.js config文件const fs = require(‘fs’)const chalk =require(‘chalk’)const webpack = require(‘webpack’)const path = require(‘path’)const defaultConfig = require(’@ionic/app-scripts/config/webpack.config.js’)const env = process.env.IONIC_ENV/* * 获取配置文件 * @param {} env /function configPath(env) { const filePath = ./src/config/config.${env}.ts if (!fs.existsSync(filePath)) { console.log(chalk.red(’\n’ + filePath + ’ does not exist!’)); } else { return filePath; }}// 定位当前文件const resolveDir = filename => path.join(__dirname, ‘..’, filename)// 其他文件夹别名let alias ={ “@”: resolveDir(‘src’), “@components”: resolveDir(‘src/components’), “@directives”: resolveDir(‘src/directives’), “@interface”: resolveDir(‘src/interface’), “@pages”: resolveDir(‘src/pages’), “@service”: resolveDir(‘src/service’), “@providers”: resolveDir(‘src/providers’), “@theme”: resolveDir(‘src/theme’)}console.log(“当前APP环境为:"+process.env.APP_ENV)let definePlugin = new webpack.DefinePlugin({ ‘process.env’: { APP_ENV: ‘”’+process.env.APP_ENV+’"’ }})// 设置别名defaultConfig.prod.resolve.alias = { “@config”: path.resolve(configPath(‘prod’)), // 配置文件 …alias}defaultConfig.dev.resolve.alias = { “@config”: path.resolve(configPath(‘dev’)), …alias}// 其他环境if (env !== ‘prod’ && env !== ‘dev’) { defaultConfig[env] = defaultConfig.dev defaultConfig[env].resolve.alias = { “@config”: path.resolve(configPath(env)) }}// 删除sourceMapsmodule.exports = function () { return defaultConfig}tsconfig.json配合,配置中新增如下内容 这个地方很扯 这个path相关的需要放在tsconfig.json的最上面"baseUrl": “./src”, “paths”: { “@app/env”: [ “environments/environment” ] }修改package.json。配置末尾新增如下内容"config": { “ionic_webpack”: “./config/webpack.config.js”}使用配置变量import {CONFIG} from “@app/env"如果过我们想修改Ionic中其他的webpack配置, 那么可以像上面那种形式来进行修改。// 拿到webpack 的默认配置 剩下的还不是为所欲为const defaultConfig = require(’@ionic/app-scripts/config/webpack.config.js’);// 像这样去修改配置defaultConfig.prod.resolve.alias = { “@config”: path.resolve(configPath(‘prod’))}defaultConfig.dev.resolve.alias = { “@config”: path.resolve(configPath(‘dev’))}Ionic路由首页设置有时候我们需要设置我们第一次显示得页面。那这样我们就需要使用NavController来设置// app.component.tspublic rootPage: any = StartPage; // 路由跳转href方式跳转:直接在dom中指定要跳转的页面,以tabs中的代码为例<!– 单个跳转按钮 [root]=“HomeRoot” 是最重要的 –><ion-tab [root]=“HomeRoot” tabTitle=“Home” tabIcon=“home”></ion-tab>import { HomePage } from ‘../home/home’export class TabsPage { // 声明变量地址 HomeRoot = HomePage constructor() { }}编程式导航:编程式导航我们可能会用的更多,下面是一个基础的例子编程式导航是由NavController控制NavController是Nav和Tab等导航控制器组件的基类。 您可以使用导航控制器导航到应用中的页面。 在基本级别,导航控制器是表示特定历史(例如Tab)的页面数组。 通过推送和弹出页面或在历史记录中的任意位置插入和删除它们,可以操纵此数组以在整个应用程序中导航。当前页面是数组中的最后一页,如果我们这样想的话,它是堆栈的顶部。 将新页面推送到导航堆栈的顶部会导致新页面被动画化,而弹出当前页面将导航到堆栈中的上一页面。除非您使用NavPush之类的指令,或者需要特定的NavController,否则大多数时候您将注入并使用对最近的NavController的引用来操纵导航堆栈。// 引入NavControllerimport { NavController } from ‘ionic-angular’;import { NewsPage } from ‘../news/news’export class HomePage { // 注入NavControllerconstructor(public navCtrl: NavController) { // this.navCtrl.push(LoginPage)}goNews () { this.navCtrl.push(NewsPage, { title : ‘测试传参’ }) }}相关常用APInavCtrl.push(OtherPage, param): 跳转页面navCtrl.pop(): Removing a view 移除当前View,相当于返回上一个页面路由中参参数相关push(Page, param)传参: 这个很简单也很明白this.navCtrl.push(NewsPage, { title : ‘测试传参’})[navParams]属性:和HTML配合进行传参import {LoginPage } from’./login’;@Component()class MyPage { params; pushPage: any; constructor(){ this.pushPage= LoginPage; this.params ={ id:123, name: “Carl” } }}<button ion-button [navPush]=“pushPage” [navParams]=“params”> Go</button><!– 同理在root page上传递参数就是下面这种方式 –><ion-tab [root]=“tab1Root” tabTitle=“home” tabIcon=“home” [rootParams]=“userInfo”></ion-tab获取参数//NavController就是用来管理和导航页面的一个controllerconstructor(public navCtrl: NavController, public navParams: NavParams) { //1: 通过NavParams get方法获取到单个对象 this.titleName = navParams.get(’name’) //2: 直接获取所有的参数 this.para = navParams.data}provider(service)使用当重复的需要一个类中的方法时,可封装它为服务类,以便重复使用,如http。provider,也叫service。前者是ionic的叫法,后者是ng的叫法。建议仔细得学一下Angular创建ProviderIonic提供了创建指令ionic g provider http 自动创建的Provider会自主动在app.module中导入注意这个需要在app.module中注入首先导入装饰器,再用装饰器装饰,这样,该类就可以作为提供者注入到其他类中以使用:import { Injectable } from ‘@angular/core’;@Injectable()export class StorageService { constructor() { console.log(‘Hello StorageService’); } myAlert(){ alert(“服务类的方法”) }}使用provider如果是顶级的服务(全局通用服务),需要在app.module.ts的providers中注册后然后使用import { StorageService } from ‘./../../service/storage.service’;export class LoginPage { userName: string = ‘demo’ password: string = ‘123456’ constructor( public storageService: StorageService ) { } doLogin () { const para = { userName: this.userName, password: this.password } console.log(para) if (para.userName === ‘demo’ && para.password === ‘123456’) { this.storageService.setStorage(‘user’, para) } setTimeout(() => { console.log(this.storageService.getStorage(‘user’)) }, 3000) }}Ionic事件系统Events是一个发布-订阅样式事件系统,用于在您的应用程序中发送和响应应用程序级事件。这个是不同页面之间交流的核心。主要用于组件的通信。你也可以用events传递数据到任何一个页面。Events实例方法publish(topic, eventData): 发布一个eventsubscribe(topic, handler): 订阅一个eventunsubscribe(topic, handler) 取消订阅一个event// 发布event login.ts// 发布event事件submitEvent (data) { console.log(1) this.event.publish(‘user:login’, data)}// 订阅页面 message.tsconstructor(public event: Events ) { // 订阅event事件 event.subscribe(‘user:login’, (data) => { console.log(data) let obj = { url: ‘assets/imgs/logo.png’, name: data.username } this.messages.push(obj) })}注意点: <font color=“red”>1: 订阅必须再发布之前,不然接收不到。打个比喻:比如微信公众号,你要先关注才能接收到它的推文,不然它再怎么发推文,你也收不到。2: subscribe中得this指向是有点问题的,这里需要注意一下。</font>用户操作事件Basic gestures can be accessed from HTML by binding to tap, press, pan, swipe, rotate, and pinch events.Ionic对手势事件的解释基本是一笔带过。组件间通信组件之间的通信:要把一个组件化的框架给玩6了。组件之前的通信搞明白了是个前提。在Ionic中,我们使用Angular中的方式来实现。父 => 子: @Input()通过输入型绑定把数据从父组件传到子组件:这个用途最广泛和常见,和recat中的props非常相似// 父组件定义值(用来传递)export class NewsPage { father: number = 1 // 父组件数据 /* * Ionic生命周期函数 / ionViewDidLoad() { // 父组件数据更改 setTimeout(() => { this.father ++ }, 2000) }}// 子组件定义属性(用来接收)@Input() child: number // @Input装饰器标识child是一个输入性属性<!– 父组件使用 –><backtop [child]=“father”></backtop><!– 子组件定义 –><div class=“backtop”> <p (click)=“click()">back</p> father数据: {{child}}</div>通过get, set在子组件中对父组件得数据进行拦截来达到我们想要得结果// 拦截父组件得值private _showContent: string @Input()// set valueset showContent(name: string) { if (name !== ‘4’) { this._showContent = ’no’ } else { this._showContent = name }}// get valueget showContent () :string { return this._showContent}通过ngOnChanges监听值得变化// 监听所有属性值得变化ngOnChanges(changes: SimpleChange): void { /* * 从旧值到新值得一次变更 * class SimpleChange { constructor(previousValue: any, currentValue: any, firstChange: boolean) previousValue: any // 变化前得值 currentValue: any // 当前值 firstChange: boolean isFirstChange(): boolean // 检查该新值是否从首次赋值得来的。 } / // changes props集合对象 console.log(changes[‘child’].currentValue) // }父组件与子组件通过本地变量互动父组件不能使用数据绑定来读取子组件的属性或调用子组件的方法。但可以在父组件模板里,新建一个本地变量来代表子组件,然后利用这个变量来读取子组件的属性和调用子组件的方法.通过#childComponent定义这个组件。然后直接使用childComponent.XXX去调用。这个的话就有点强大了,但是这个交流时页面级别的。仅限于在html定义本地变量然后在html中进行操作和通信。也就是父组件-子组件的连接必须全部在父组件的模板中进行。父组件本身的代码对子组件没有访问权。<!– 父组件 –><button ion-button color=“secondary” full (click)=“childComponent.fromFather()">测试本地变量</button><backtop #childComponent [child]=“father” [showContent] = “father” (changeChild)=“childCome($event)"></backtop>// 子组件// 父子组件通过本地变量交互fromFather () { console.log(I am from father) this.show = !this.show}父组件调用@ViewChild()互动如果父组件的类需要读取子组件的属性值或调用子组件的方法,可以把子组件作为 ViewChild,注入到父组件里面。也就是说@ViewChild()是为了解决上面的短板而出现的。// 父组件import { Component, ViewChild } from ‘@angular/core’;export class NewsPage { //定义子组件数据 @ViewChild(BacktopComponent) private childComponent: BacktopComponent ionViewDidLoad() { setTimeout(() => { // 通过child调用子组件方法 this.childComponent.formChildView() }, 2000) }}子 => 父: @Output(): 最常用的方法子组件暴露一个 EventEmitter 属性,当事件发生时,子组件利用该属性 emits(向上弹射)事件。父组件绑定到这个事件属性,并在事件发生时作出回应。// 父组件// 接收儿子组件得来得值 并把儿子得值赋给父亲childCome (data: number) { this.father = data}// 字组件// 子向父传递得事件对象@Output() changeChild: EventEmitter<number> = new EventEmitter() // 定义事件传播器对象// 执行子组件向父组件通信click () { this.changeChild.emit(666)}<!– 父组件 –><backtop [child]=“father” [showContent] = “father” (changeChild)=“childCome($event)"></backtop>获取父组件实例有的时候我们也可以暴力一点获取父组件的实例去使用它(未验证)。constructor( // 注册父组件 @Host() @Inject(forwardRef(() => NewsPage)) father: NewsPage ) { this.text = ‘Hello World’; setTimeout(() => { // 直接通过对象来修改父组件 father.father++ }, 3000) }父 <=> 子:父子组件通过服务来通信如果我们把一个服务实例的作用域被限制在父组件和其子组件内,这个组件子树之外的组件将无法访问该服务或者与它们通讯。父子共享一个服务,那么我们可以利用该服务在家庭内部实现双向通讯。// serviceimport { Injectable } from ‘@angular/core’; // 标记元数据// 使用service进行父子组件的双向交流@Injectable()export class MissionService { familyData: string = ‘I am family data’}// father componentimport { MissionService } from ‘./../../service/mission.service’;export class NewsPage { constructor( public missionService: MissionService) { } ionViewDidLoad() { // 父组件数据更改 setTimeout(() => { // 调用修改service中的数据 这个时候父子组件中的service都会改变 this.missionService.familyData = ‘change familyData’ }, 2000) }}// child componentimport { Component} from ‘@angular/core’;import { MissionService } from ‘./../../service/mission.service’;@Component({ selector: ‘backtop’, templateUrl: ‘backtop.html’})export class BacktopComponent { constructor( public missionService:MissionService ) { console.log(missionService) this.text = ‘Hello World’; } // 执行子组件向父组件通信 click () { // 修改共享信息 this.missionService.familyData = ‘change data by child’ }}<!– 父组件直接使用 –>{{missionService.familyData}}<!– 子组件 –><div> servicedata: {{missionService.familyData}}</div>在service中使用订阅也可以同样的实现数据的通信// mission.service.tsimport { Subject } from ‘rxjs/Subject’;import { Injectable } from ‘@angular/core’; // 标记元数据// 使用service进行父子组件的双向交流@Injectable()export class MissionService { familyData: string = ‘I am family data’ // 订阅式的共享数据 private Source = new Subject() Status$=this.Source.asObservable() statusMission (msg: string) { this.Source.next(msg) }}// 父组件// 通过service的订阅提交信息emitByService () { this.missionService.statusMission(’emitByService’)}// 子组件// 返回一个订阅器this.subscription = missionService.Status$.subscribe((msg:string) => { this.text = msg})ionViewWillLeave(){ // 取消订阅 this.subscription.unsubscribe()}高级通信我们可以使用ionic-angular中的Events模块来进行 父 <=> 子 , 兄 <=> 弟的高级通信。Events模块在通信方面具有得天独厚的优势。具体可以看上面的示例使用EventEmitter模块// serviceimport { EventEmitter } from ‘@angular/core’; // 标记元数据// 使用service进行父子组件的双向交流@Injectable()export class MissionService { // Event通信 来自angular serviceEvent = new EventEmitter()}// 父组件// 通过Events 模块高级通信 接收信息this.missionService.serviceEvent.subscribe((msg: string) => { this.messgeByEvent = msg})// 子组件// 通过emit 进行高级通信 发送新emitByEvent () { this.missionService.serviceEvent.emit(’emit by event’)}Shared组件公共组件设置,Angular倡导的是模块化开发,所以公共组件的注册可能稍有不同。在这里我们根据Angular提供的CommonModule共享模块,我们要知道他干了什么事儿:它导入了 CommonModule,因为该模块需要一些常用指令。它声明并导出了一些工具性的管道、指令和组件类。它重新导出了 CommonModule 和 FormsModuleCommonModule 和 FormsModule可以代替BrowserModule去使用定义在shared文件夹下新建shared.module.tsimport { NgModule } from ‘@angular/core’;import { CommonModule } from ‘@angular/common’;import { FormsModule } from ‘@angular/forms’; // 通过重新导出 CommonModule 和 FormsModule,任何导入了这个 SharedModule 的其它模块,就都可以访问来自 CommonModule 的 NgIf 和 NgFor 等指令了,也可以绑定到来自 FormsModule 中的 [(ngModel)] 的属性了。// 自定义的模块和指令import { ComponentsModule } from ‘./../components/components.module’;import { DirectivesModule } from ‘./../directives/directives.module’;@NgModule({ declarations: [], imports: [ CommonModule, FormsModule ], exports:[ // 导出模块 CommonModule, FormsModule, ComponentsModule, DirectivesModule ], entryComponents: [ ]})export class SharedModule {}注意: 服务要通过单独的依赖注入系统进行处理,而不是模块系统使用了shared模块仅仅需要在xxx.module.ts中引用即可,然后又就可以使用shared中所有引入的公共模块。import { NgModule } from ‘@angular/core’;import { IonicPageModule } from ‘ionic-angular’;import { XXXPage } from ‘./findings’;import { SharedModule } from ‘@shared/shared.module’;@NgModule({ declarations: [ XXXPage, ], imports: [ SharedModule, IonicPageModule.forChild(FindingsPage), ]})export class XXXPageModule {}http部分Ionic中的http模块是直接采用的HttpClient这个模块。这个没什么可说的,我们只需要根据我们的需求对service进行修改即可,例如可以把http改成了更加灵活的Promise模式。你也可以用Rxjs的模式来实现。下面这个是个简单版本的实现:import { TokenServie } from ‘./token.service’;import { StorageService } from ‘./storage.service’;import { HttpClient, HttpHeaders, HttpParams } from ‘@angular/common/http’import { Injectable, Inject } from ‘@angular/core’import {ReturnObject, Config} from ‘../interface/index’ // 返回数据类型和配置文件/Generated class for the HttpServiceProvider provider./@Injectable()export class HttpService{ /* * @param CONFIG * @param http * @param navCtrl / constructor( @Inject(“CONFIG”) public CONFIG:Config, public storage: StorageService, public tokenService: TokenServie, public http: HttpClient ) { console.log(this.CONFIG) } /* * key to ’name=‘qweq’’ * @param key * @param value / private toPairString (key, value): string { if (typeof value === ‘undefined’) { return key } return ${key}=${encodeURIComponent(value === null ? '' : value.toString())} } /* * objetc to url params * @param param / private toQueryString (param, type: string = ‘get’) { let temp = [] for (const key in param) { if (param.hasOwnProperty(key)) { let encodeKey = encodeURIComponent(key) temp.push(this.toPairString(encodeKey, param[key])) } } return ${type === 'get' ? '?' : ''}${temp.join('&amp;')} } /* * set http header / private getHeaders () { let token = this.tokenService.getToken() return new HttpHeaders({ ‘Content-Type’: ‘application/x-www-form-urlencoded’, ’tokenheader’: token ? token : ’’ }) } /* * http post请求 for promise * @param url * @param body / public post (url: string, body ? : any): Promise<ReturnObject> { const fullUrl = this.CONFIG.BASE_URL + url console.log(this.toQueryString(body, ‘post’)) return new Promise<ReturnObject>((reslove, reject) =>{ this.http.post(fullUrl, body, { // params, headers: this.getHeaders() }).subscribe((res: any) => { reslove(res) }, err => { // this.handleError(err) reject(err) }) }) } /* * get 请求 return promise * @param url * @param param / public get(url: string, params: any = null): Promise<ReturnObject> { const fullUrl = this.CONFIG.BASE_URL + url let realParams = new HttpParams() for (const key in params) { if (params.hasOwnProperty(key)) { realParams.set(${key}, params[key]) } } // add time map realParams.set( ’timestamp’, (new Date().getTime()).toString() ) return new Promise<ReturnObject>((reslove, reject) =>{ this.http.get(fullUrl, { params, headers: this.getHeaders() }).subscribe((res: any) => { console.log(res) reslove(res) }, err => { // this.handleError(err) reject(err) }) }) }}Cordova插件使用Ionic提供了丰富的基于cordova的插件,官网介绍,使用起来也很简单。下载Cordova插件cordova add plugin plugin-name -Dnpm install @ionic-native/plugin-name使用插件(从@ionic-native/plugin-name中导入)import { StatusBar } from ‘@ionic-native/status-bar’;constructor(private statusBar: StatusBar) { //沉浸式并且悬浮透明 statusBar.overlaysWebView(true); // 设置状态栏颜色为默认得黑色 适合浅色背景 statusBar.styleDefault() // 浅色状态栏 适合深色背景 // statusBar.styleLightContent() }优化部分项目写完了,不优化一下 心里怪难受的。App启动页体验优化Ionic App毕竟是个混合App,毕竟还没有达到秒开级别。所以这个时候我们需要启动页来帮助我们提升用户体验,首先在config.xml种配子我们的启动页相关配置<preference name=“ShowSplashScreenSpinner” value=“false” /> <!– 隐藏加载时的loader –><preference name=“ScrollEnabled” value=“false” /> <!– 禁用启动屏滚动 –><preference name=“SplashMaintainAspectRatio” value=“true” /> <!– 如果值设置为 true,则图像将不会伸展到适合屏幕。如果设置为 false ,它将被拉伸 –><preference name=“FadeSplashScreenDuration” value=“1000” /><!– fade持续时长 –><preference name=“FadeSplashScreen” value=“true” /><!– fade动画 –><preference name=“SplashShowOnlyFirstTime” value=“false” /><!– 是否只第一次显示 –><preference name=“AutoHideSplashScreen” value=“false” /><!– 自动隐藏SplashScreen –><preference name=“SplashScreen” value=“screen” /><platform name=“android”> <allow-intent href=“market:” /> <icon src=“resources/android/icon/icon.png” /> <splash src=“resources/android/splash/screen.png” /><!– 启动页路径 –> <!– 下面是各个分辨率的兼容 –> <splash height=“800” src=“resources/android/splash/screenh.png” width=“480” /> <splash height=“1280” src=“resources/android/splash/screenm.png” width=“720” /> <splash height=“1600” src=“resources/android/splash/screenxh.png” width=“960” /> <splash height=“1920” src=“resources/android/splash/screenxxh.png” width=“1280” /> <splash height=“2048” src=“resources/android/splash/screenxxxh.png” width=“1536” /></platform>我在这里关闭了自动隐藏SplashScreen,因为她的判定条件是一旦App出事还完毕就隐藏,这显然不符合我们的要求。我们需要的是我们的Ionic WebView程序启动之后再隐藏。所以我们在app.component.ts中借助@ionic-native/splash-screen来进行这个操作.platform.ready().then(() => { // 延迟1s隐藏启动屏幕 setTimeout(() => { splashScreen.hide() }, 1000) })这样一来我们就可以完美的欺骗用户,体验能好点。打包优化新增–prod参数"build:android”: “ionic cordova build android –prod –release”,预(AOT)编译:预编译 Angular 组件的模板。生产模式:启用生产模式部署到生产环境。打捆(Bundle):把这些模块串接成一个单独的捆文件(bundle)。最小化:移除不必要的空格、注释和可选令牌(Token)。混淆:使用短的、无意义的变量名和函数名来重写代码。消除死代码:移除未引用过的模块和未使用过的代码.App打包我认为打包APK对于一些不了解服务端和Android的前端工程师来说还是比较费劲的。下面我们来仔细的说一说这个部分。环境配置第一步进行各个环境的配置Node安装/配置环境变量(我相信这个你已经弄完了)jdk安装 (无需配置环境变量)jdk是java的开发环境支持,你可以在这里下载, 提取码:9p74。下载完成后,解压,直接按照提示安装,全局点确定,不出意外,最后的安装路径为:C:\Program Files\Javajdk安装完成,在cmd中,输入java -version验证是否安装成功。我这边是修改了安装路径,如果你不熟悉的话还是不要修改安装路径。出现了下面的log表示安装成功SDK安装/配置环境变量:这一部分是重点,稍微麻烦一些。先下载。解压后将重命名的文件夹,跟jdk放在一个父目录,便于查找:C:\Program Files\SDK接着配置环境变量,我的电脑——右键属性——-高级系统设置——-环境变量。在下面的系统变量(s)中,新建,键值对如下:name: ANDROID_HOMEkey: C:\Program Files\SDK新建完系统变量之后在path中加入全局变量。在控制台中输入android -h,出现下面的日志,表示sdk安装成功接下来我们使用Android Studio进行SDK下载,Adnroid Studio下载地址,studio安装完之后就要安装Android SDK Tools,Android SDK platform-tools,Android SDK Build-tools这些工具包和SDK platformgradle安装/配置环境变量在SDK都安装完了之后我们再进行gradle的安装和配置。先在官网或者在这里下载然后同样安装在JDK,SDK的目录下,便于查找。和SDK同样的配置环境变量:GRADLE_HOME=C:\Program Files\SDK\gradle-4.1;%GRADLE_HOME%\bin测试命令(查看版本):gradle -v 出现下面的日志,表示安装成功进行打包打包之前的环境准备工作都已经做完了,接下来我们进行打包apk。安装cordovanpm i cordova -g在项目中创建Android工程,在Ionic项目中执行下面命令ionic cordova platform add android这可能是一个很漫长的过程,你要耐心等待,毕竟曙光就在眼前了。创建完Android项目之后项目的platform文件夹下会多出来一个android文件夹。这下接着执行打包命令。ionic cordova build android然后你会看到控制台疯狂输出,最后出现下图表明你已经打包出来一个未签名的安装包APK签名APK不签名是没法发布的。这个有两种方法使用jdk签名,这里不多说,想了解的可以看这篇文章使用Android Studio打签名包。在AS上方工具栏build中选取Generate Signed APK首先创建一个签名文件生成完之后可以直接用AS打签名包点击locate就能看到我们的apk包了~ 至此我们的Android就ok了,IOS的之后再补上。简单APP服务器更新(简单示例)由于Android的要求不如苹果那么严,我们也可以通过自己的服务器进行程序的更新。下面就是实现一个比较简单的更新Service更新我们主要是使用到下面几个Cordova插件cordova-plugin-file-transfer / @ionic-native/file-transfer: 线上文件的下载和存储(官方推荐使用XHR2,有兴趣的可以看一看)cordova-plugin-file-opener2 / @ionic-native/file-opener: 用于打开APK文件cordova-plugin-app-version / @ionic-native/app-version: 用于获取app的版本号cordova-plugin-file / @ionic-native/file:操作app上的文件系统cordova-plugin-device / @ionic-native/device:获取当前设备信息,主要用于平台的区分在下载完插件之后我们来实现一个比较简陋的版本更新service,具体解释我会写在代码注释中,主要分成两部分,一部分是具体的更新操作update.service.ts, 另一部分是用于存放数据的data.service.tsdata.service.ts/* * @Author: etongfu * @Description: 设备信息 * @youWant: add you want info here */import { Injectable } from '@angular/core';import { Device } from '@ionic-native/device';import { File } from '@ionic-native/file';import { TokenServie } from './token.service';import { AppVersion } from '@ionic-native/app-version';@Injectable()export class DataService { /******************************APP数据模块******************************/ // app 包名 private packageName: string = '' // app 版本号 private appCurrentVersion: string = '---' // app 版本code private appCurrentVersionCode:number = 0 // 当前程序运行平台 private currentSystem: string // 当前userId // app 下载资源存储路径 private savePath: string // 当前app uuid private uuid: string /******************************通用数据模块******************************/ constructor ( public device: Device, public file: File, public app: AppVersion, public token: TokenServie, public http: HttpService ) { // 必须在设备准备完之后才能进行获取 document.addEventListener("deviceready", () =&gt; { // 当前运行平台 this.currentSystem = this.device.platform // console.log(this.device.platform) // app版本相关信息 this.app.getVersionNumber().then(data =&gt; { //当前app版本号 data,存储该版本号 if (this.currentSystem) { // console.log(data) this.appCurrentVersion = data } }, error =&gt; console.error(error)) this.app.getVersionCode().then((data) =&gt; { //当前app版本号数字代码 if (this.currentSystem) { this.appCurrentVersionCode = Number(data) } }, error =&gt; console.error(error)) // app 包名 this.app.getPackageName().then(data =&gt; { //当前应用的packageName:data,存储该包名 if (this.currentSystem) { this.packageName = data; } }, error =&gt; console.error(error)) // console.log(this.currentSystem) // file中的save path 根据平台进行修改地址 this.savePath = this.currentSystem === 'iOS' ? this.file.documentsDirectory : this.file.externalDataDirectory; }, false); } /** * 获取app 包名 */ public getPackageName () { return this.packageName } /** * 获取当前app版本号 * @param hasV 是否加上V标识 */ public getAppVersion (hasV: boolean = true): string { return hasV ? V${this.appCurrentVersion} : this.appCurrentVersion } /** * 获取version 对应的nuamber 1.0.0 =&gt; 100 */ public getVersionNumber ():number { const temp = this.appCurrentVersion.split('.').join('') return Number(temp) } /** * 获取app version code 用于比较更新使用 */ public getAppCurrentVersionCode (): number{ return this.appCurrentVersionCode } /** * 获取当前运行平台 */ public getCurrentSystem (): string { return this.currentSystem } /** * 获取uuid */ public getUuid ():string { return this.uuid } /** * 获取存储地址 */ public getSavePath ():string { return this.savePath }}update.service.ts/* * @Author: etongfu * @Email: 13583254085@163.com * @Description: APP简单更新服务 * @youWant: add you want info here */import { HttpService } from './../providers/http.service';import { Injectable, Inject } from '@angular/core'import { AppVersion } from '@ionic-native/app-version';import { PopSerProvider } from './pop.service';import { DataService } from './data.service';import {Config} from '@interface/index'import { FileTransfer, FileTransferObject } from '@ionic-native/file-transfer';import { FileOpener } from '@ionic-native/file-opener';import { LoadingController } from 'ionic-angular';@Injectable()export class AppUpdateService { constructor ( @Inject("CONFIG") public CONFIG:Config, public httpService: HttpService, public appVersion: AppVersion, private fileOpener: FileOpener, private transfer: FileTransfer, private popService: PopSerProvider, // 这就是个弹窗的service private dataService: DataService, private loading:LoadingController ) { } /** * 通过当前的字符串code去进行判断是否有更新 * @param currentVersion 当前app version * @param serverVersion 服务器上版本 */ private hasUpdateByCode (currentVersion: number, serverVersion:number):Boolean { return serverVersion &gt; currentVersion } /** * 查询是否有可更新程序 * @param noUpdateShow 没有更新时显示提醒 */ public checkForUpdate (noUpdateShow: boolean = true) { // 拦截平台 return new Promise((reslove, reject) =&gt; { // http://appupdate.ymhy.net.cn/appupdate/app/findAppInfo?appName=xcz&amp;regionCode=370000 // 查询app更新 this.httpService.get(this.CONFIG.CHECK_URL, {}, true).then((result: any) =&gt; { reslove(result) if (result.succeed) { const data = result.appUpload const popObj = { title: '版本更新', content: `` } console.log(当前APP版本:${this.dataService.getVersionNumber()}) // 存在更新的情况下 if (this.hasUpdateByCode(this.dataService.getVersionNumber(), data.versionCode)) { // if (this.hasUpdateByCode(101, data.versionCode)) { let title = 新版本<b>V${data.appVersion}</b>可用,是否立即下载?<h5 class=“text-left”>更新日志</h5> // 更新日志部分 let content = data.releaseNotes popObj.content = title + content // 生成弹窗 this.popService.confirmDIY(popObj, data.isMust === '1' ? true: false, ()=&gt; { this.downLoadAppPackage(data.downloadPath) }, ()=&gt; { console.log('取消'); }) } else { popObj.content = '已是最新版本!' if(!noUpdateShow) { this.popService.confirmDIY(popObj, data.isMust === '1' ? true: false) } } } else { // 接口响应出现问题 直接提醒默认最新版本 if(!noUpdateShow) { this.popService.alert('版本更新', '已是最新版本!') } } }).catch((err) =&gt; { console.error(err) reject(err) }) }) } /** * 下载新版本App * @param url: string 下载地址 */ public downloadAndInstall (url: string) { let loading = this.loading.create({ spinner: 'crescent', content: '下载中' }) loading.present() try { if (this.dataService.getCurrentSystem() === 'iOS') { // IOS跳转相应的下载页面 // window.location.href = 'itms-services://?action=download-manifest&amp;url=' + url; } else { const fileTransfer: FileTransferObject = this.transfer.create(); fileTransfer.onProgress(progress =&gt;{ // 展示下载进度 const present = new Number((progress.loaded / progress.total) * 100); const presentInt = present.toFixed(0); if (present.toFixed(0) === '100') { loading.dismiss() } else { loading.data.content =已下载 ${presentInt}%` } }) const savePath = this.dataService.getSavePath() + ‘xcz.apk’; // console.log(savePath) // 下载并且保存 fileTransfer.download(url,savePath).then((entry) => { // this.fileOpener.open(entry.toURL(), “application/vnd.android.package-archive”) .then(() => console.log(‘打开apk包成功!’)) .catch(e => console.log(‘打开apk包失败!’, e)) }).catch((err) => { console.error(err) console.log(“下载失败”); loading.dismiss() this.popService.alert(‘下载失败’, ‘下载异常’) }) } } catch (error) { this.popService.alert(‘下载失败’, ‘下载异常’) // 有异常直接取消dismiss loading.dismiss() } }}以上我们就可以根据直接调用service去进行更新app.component.ts// 调用更新this.appUpdate.checkForUpdate()App真机调试说实在的,Hybird真机调试是真的痛苦。目前比较流行的方式是以下两种调试方式Chrome Inspect调试依靠chrome的强大能力,我们可以把App中的WebView中的内容完全的显示在chrome端。可以在web端控制我们的app中的网页,还是先当的炫酷的。以下是操作步骤在chrome中打开chrome://inspect/#devices连接设备,注意第一次连接的话,是需要fan墙的,否则会出现404等等的问题在连接的设备中安装需要调试的App,接着Chrome会自动找到需要调试的WebView愉快的开始调试使用VConsole进行调试这个就更简单了,直接npm install vconsole这个库, 然后在app.component.ts进行引用import VConsole from ‘vconsole’export class MyApp {constructor() { platform.ready().then(() => { console.log(APP_ENV) // 调试程序 APP_ENV === ‘debug’ && new VConsole() }) }}效果如下Ionic中的特殊部分(坑)静态资源路径问题如果在打完包之后静态路径出来问题,没有加载出来的话要注意以下情况<!– html中的img标签直接引用图片处理 –><img src=”./assets/xxx.jpg”/><!– 或者这样 –><img src=“assets/imgs/timeicon.png” style=“width: 1rem;">/scss文件中要使用绝对路径/.bg{ background-image: url(”../assets/xxx.jpg”)}Android API版本修改Ionic中现在默认的SDK版本太高了,有些低版本的机器没发安装需要修改的有以下这么几个部分<!– platforms/android/project.properties –>target=android-26<!– 和platforms/android/CordovaLib/project.properties –>target=android-26关于SDK和cordova插件中的坑(暂时不写)这个东西真的是坑的一塌糊涂,以cordova-plugin-file-opener2为例AS3.0打包之后Android7.0以下的手机无法安装这个不能算是Ionic的坑,要算也得是Android Studio3.0的坑,之前因为不了解在打包的时候下面的选项并没有勾选上不加上的时候一直在Android7.0以下都没法安装,一直以为是项目代码的问题,没想到是设置的问题,加上了V1选项之后打也就可以了,查了一下原因如下。上图中提供的选项其实是签名版本选择,在AS3.0的时候新增的选项。Android 7.0中引入了APK Signature Scheme v2,v1呢是jar Signature来自JDKV1:应该是通过ZIP条目进行验证,这样APK 签署后可进行许多修改 - 可以移动甚至重新压缩文件。V2:验证压缩文件的所有字节,而不是单个 ZIP 条目,因此,在签名后无法再更改(包括 zipalign)。正因如此,现在在编译过程中,我们将压缩、调整和签署合并成一步完成。好处显而易见,更安全而且新的签名可缩短在设备上进行验证的时间(不需要费时地解压缩然后验证),从而加快应用安装速度。如果不勾选V1,那么在7.0以下会直接安装完显示未安装,7.0以上则使用了V2的方式验证。如果勾选了V1,那么7.0以上就不会使用更加安全的快速的验证方式。也可以在app目录下的build.gradle中进行配置signingConfigs { debug { v1SigningEnabled true v2SigningEnabled true } release { v1SigningEnabled true v2SigningEnabled true }}总结这么一番折腾下来,越到了不少坑。但是也都一一解决了。使用Ionic最大的感触就是TS+Angular的模块化开发模式很舒服。而且开发速度上也不至于太慢,对Angular感兴趣的朋友我认为还是可以一试的。示例代码请稍后春节马上到了,祝各位开发者春节快乐远离BUG????????????原文地址 如果觉得有用得话给个⭐吧 ...

January 30, 2019 · 14 min · jiezi

Ionic 环境搭建和安装教程

Ionic 环境搭建和安装教程本篇教程基于 Ionic 官方文档编写,希望对初次安装 Ionic 的你们有所帮助。环境搭建在使用 Ionic 框架之前,唯一的前提条件是 Node 和 npm 环境。当然,你同样需要一个代码编辑器。这里推荐使用 VS Code。Node & npm安装 Node.js 和 npm下载(推荐下载 LTS 版本):Node.js安装:双击下载的文件直接安装即可(npm 是和 Node 捆绑安装的,无需单独安装)验证是否安装成功:打开新的cmd窗口,输入:$ node –version$ npm –version出现版本号说明安装成功安装 Ionic CLI直接在 cmd 中输入下方命令安装:$ npm install -g ionic验证是否安装成功:$ ionic -v出现版本号等信息说明安装成功。安装 Cordova直接在 cmd 中输入下方命令安装:$ npm install -g cordova验证是否安装成功:$ cordova -v出现版本号信息说明安装成功。创建项目这里有三种最常见的启动器可供选择,分别是blank启动器,tabs启动器和sidemenu启动器。需要在指定路径中创建项目的话,使用cd进入指定路径即可,默认为当前路径。直接在cmd中输入下方命令安装:$ ionic start myApp tabs耐心等待创建完成,进入对应的文件夹即可看到创建的项目。运行项目在cmd中cd进入到刚才创建的项目文件夹(myApp)$ cd myApp使用ionic serve命令来启动项目$ ionic serve项目启动成功之后会自动在浏览器中打开

January 25, 2019 · 1 min · jiezi

ionic初学者引导(三)

前两篇文章大概介绍了ionic以及页面的写法,这篇文章主要讲一下cordova的用法(其实也没啥好讲的)和项目结尾工作1. cordova的使用由于之前那个简单的项目有一个更换头像的功能,可以拍照也可以选择本地图片。这个涉及到调用设备的相册和相机。在ionic官网上的Native中的列表中搜索camera,正好这个既可以拍照也可以选择照片(单选,可以满足我们的需求)。在App.module.ts全局引入Camera在所需的页面使用Camera写代码写完代码需要打包在模拟器或真机上测试测试????了就可以继续其他功能了本项目相关Cordova部分代码如下:selectPicture() { const options: CameraOptions = { quality: 100, destinationType: this.camera.DestinationType.DATA_URL, mediaType: this.camera.MediaType.PICTURE, sourceType: this.camera.PictureSourceType.PHOTOLIBRARY } this.camera.getPicture(options).then( (imageData) => { // 这里处理图片并上传 // 相关接口和处理不写了 this.avatarUrl = ‘data:image/jpeg;base64,’ + imageData }, (err) => { console.log(err) } ) } takePhoto() { const options: CameraOptions = { quality: 100, destinationType: this.camera.DestinationType.NATIVE_URI, mediaType: this.camera.MediaType.PICTURE, sourceType: this.camera.PictureSourceType.CAMERA } this.camera.getPicture(options).then( (imageData) => { // 这里处理图片并上传 // 相关接口和处理不写了 this.avatarUrl = imageData }, (err) => { console.log(err) } ) }2. 项目打包前我们用的app都有一个默认图标和启动页,ionic有一个统一设置的,首先我们需要ui设计给出10241024的图标和27322732的启动页的图片,然后分别命名为icon.png,splash.png,并将图片放入resources文件夹中。输入命令ionic cordova resources ios -icon –force(以ios示例,android类似),这个是生成图标,启动页和这个类似。项目一般还有欢迎页,欢迎页就是页面,所以是自己写的,没有快速生成的方法。3. 项目打包这个简单的项目到这里也没有什么好讲的,接下来就是要打包成一个可用的app了,这里简单的讲一下android打包签名;ios的我会贴个链接(因为这个比较麻烦,按照给的步骤来其实也差不多了,有不知道的也可以私信我)添加平台ionic cordova platform add android生成Android包ionic cordova build android 这个是生成debug包,这个可以连接谷歌浏览器,更明显的看输出,还可以修改cssdebug没什么问题的了,可以给安装包签名了(这里只介绍文件配置签名,不知道叫啥名字)输入命令keytool -genkey -alias demo.keystore -keyalg RSA -validity 40000 -keystore demo.keystoreandroid自动签名,在platform\android目录新建名为release-signing.properties的文件生成签名包ionic cordova build android –releaseios项目打包上线链接https://www.jianshu.com/p/cf2… (做链接步骤前也要add ios和build ios,然后用xcode打开*.xcodeproj,接下来的步骤就差不多了)说明: -genkey 产生密钥 -alias demo.keystore 别名 demo.keystore-keyalg RSA 使用RSA算法对签名加密-validity 40000 有效期限4000天-keystore demo.keystorerelease-signing.properties内容storeFile=E:/demo.keystorekey.alias=demo.keystorekey.store.password=****key.alias.password=****4. 总结其实我说的在官网上基本都可以找到,包括Android和iOS的打包上线以及等等,有什么不太了解和问题先看官网,如果是cordova出错的话,去github上看issue。最后给上demo的地址:https://github.com/MonicaTang… ...

January 18, 2019 · 1 min · jiezi

ionic 初学者引导(二)

上一篇把ionic的大概介绍讲解了,这一篇我们开始来具体的开发这个demo只写了三个页面,一个列表一个个人中心,主要介绍组件和Cordova的使用,成图如下1. 创建一个空的ionic项目全局安装ionic,cordova;npm install -g ionic cordova创建空项目demo ionic start demo blank;这个blank不固定,具体可以看CLI中的相关命令ionic serve运行项目。会默认打开浏览器,这时就可以看到默认页面了2. 写一个tab底部在home页面根据components 里面的tab组件写再创建首页和我的的页面。命令ionic g page index,ionic g page my相关代码如下home.html <ion-tabs> <ion-tab tabIcon=“home” [root]=“index” tabTitle=‘首页’></ion-tab> <ion-tab tabIcon=“person” [root]=“my” tabTitle=‘我的’></ion-tab> </ion-tabs>home.tsexport class HomePage { index = ‘IndexPage’ my = ‘MyPage’ constructor(public navCtrl: NavController) { }}3. 写首页列表在首页写相关页面布局调整样式。这里使用的是ionic卡片列表式,上拉加载(一般使用list做列表,要根据自己的实际用途,想要的效果选择)使用了easyMock模拟服务器返回数据(实际开发使用具体接口)。使用命令ionic g providers inter-ser创建一个接口服务,在这里面统一写接口对接。(实际开发中建议增加一个http处理serve,可以对接口访问前做拦截处理,对返回后的数据做统一处理比如返回错误处理,异常处理等等)。写http请求需要在app.module.ts全局引入HttpClientModule。接口数据对接继续写我的页面和上传头像页面部分代码如下:.tsionViewWillEnter() { this.loaded = false this.list = [] this.params.pageNum = 1 this.setLoader() this.getData() } getData() { if (this.loading) return this.inter.getJokeList(this.params).subscribe((data) => { this.loaded = true this.list = this.list.concat(data[‘data’]) this.total = data[’total’] if (this.params.pageNum === 1) this.loader.dismiss() if (this.params.pageNum > 1) this.infiniteScroll.complete() }) } doInfinite(infiniteScroll) { if (this.list.length < this.total) { this.infiniteScroll = infiniteScroll this.params.pageNum++ this.getData() } else { infiniteScroll.enable(false) } } /** * 这种类型的方法建议写成全局方法 */ setLoader() { this.loader = this.loadingCtrl.create({ content: ‘加载中…’, spinner: ‘ios-small’ }) this.loader.present() }.html<div *ngIf=“loaded && list.length > 0”> <div *ngFor=“let item of list”> <ion-card> <ion-card-header> {{item.title}} </ion-card-header> <ion-card-content> {{item.content}} </ion-card-content> <p class=“update-time”>{{item.updateTime}}</p> </ion-card> </div> </div> <div class=“no-data” *ngIf=“loaded && list.length === 0”>暂无数据</div> <ion-infinite-scroll (ionInfinite)=“doInfinite($event)"> <ion-infinite-scroll-content loadingText=“加载中…"></ion-infinite-scroll-content> </ion-infinite-scroll>本章小结这章主要操作的还是webview,不涉及到移动设备的,基本可以在浏览器上完成。接下来的一章大概说一下Cordova的使用以及部分结尾相关工作 ...

January 18, 2019 · 1 min · jiezi

ionic 初学者引导(一)

前提:需要有angular基础。讲解的内容为ionic3版本。官网有一个相关的开发资源,就在首页目录DEVELOPER RESOURCES1. 需要的环境和相关配置(官网DEVELOPER RESOURCES也有)mac请看 https://www.cnblogs.com/zhu-l...windows https://blog.csdn.net/gucheng… 版本不需要保持一致,具体的步骤是差不多的2. ionic官网区域讲解这个区域和左侧菜单基本一致,我们开发过程中最经常访问的是UI Components,API,IonicIntroduction 点进去按照命令,初始化一个ionic项目。一个基本可运行ionic项目可以运行起来UI Components 这个就是一些组件。包含一些alert,checkbox等通用组件,点进去可以看到,组件列表API这里的api是一些组件更详细的信息,有这个组件的属性和方法等。比如button在api中会把它的一些属性都列出来(这些属性可以改变我们按钮的显示形式),在阅读component的时候可能有时候觉得这上面的demo不能满足我们的需求,一般在相关组件介绍最后有一个Check out the API docs可以导航到相应的API页面Ionic Native 这个就是和设备有关的插件。比如蓝牙,相机,相册;使用场景:当我们需要操作和设备有关功能的时候一般就要点进去找了,比如需要保存文件、拍照、打开相册、打开文件等等。(这个需要打包后在模拟器或真机上测试)Theming ionic提供的一些便捷的页面样式设置方法。比如:左对齐,居中,app的主题颜色设置,平台风格设置,覆盖默认组件样式的方法等等IonIcons 一些ionic的iconsCLI ionic相关的命令FAQ 关于ionic的一些问题的解决方法。在我们遇到一些奇怪的问题的时候,可以来这边查查。(之前有遇到一次ios点击两次有效的问题,这边就有答案)Forum 论坛。 相关介绍差不多了,接下来以一个简单的demo来讲解ionic的使用。

January 16, 2019 · 1 min · jiezi

ionic 开发中的一些错误

1.打包问题不要轻易删除package-lock.json文件,指定某个平台的版本。我android指定6.3.0 ,ios4.4.0(4.5.0会和cordova-plugin-console重复,使用4.5.0需要删除cordova-plugin-console插件)。2.npm install的问题node-sass 安装的问题直接自己百度,百度再解决不了就重新git clone新建个项目,再解决不了,我也不知道存在cordova-plugin-video-capture-plus这个插件的时候,npm install会报错,先在package.json删掉和这个有关的安装包名字3.cordova-plugin-photo-library的错误安卓暂时没有发现错误,ios在xcode里面打包的时候会报错,网上的解决方案是在build settings里面的swift language version里面更改版本,改完了确实不报错了,但是调用方法的时候会出现未定义的问题。相关解决是找到报错的信息,按照他的提示来修改。我当时是只改了一个函数名。4. 使用html2canvas保存图片这个和上面是结合在一起的,主要是传一个dom给canvas,当时保存图片的时候,有时候成功有时候失败。后来猜估计是dom的问题,估计是复用了,当时因为多个地方需要保存功能,他们的id名字相同(页面不相同),可能使用第二个的时候取了第一个,但是第一个当时已经隐藏了,所以一直保存失败,html2canvas操作的元素宽高都是0,dataurl不能parse,所以一直保存失败5. cordova-plugin-camera-preview需要自定义相机外层部分区域,选择了这个,但是这个拍照很糊,有部分手机根本不能正常显示内容,查到的解决方案是是修改CameraPreviewPictureOptions 的width,height,我后来改的是设备支持最大的值,但是没有用,还是很糊,这个不知道怎么解决。知道的麻烦告诉我一声。(PS:如果不需要拍完照后把照片显示出来,只需要背地里默默的处理,可以借用截屏,截的图非常的清晰,完美)6. cordova-plugin-file-opener2这个问题是针对Android版本的,下载完apk后需要打开安装,android低于8.0版本的能顺利打开,但是高于这个版本的,不能打开,也不会报错,而是走正常流程,还显示OK。解决方法在作者github上,readme里面有。7. ios部分元素需要点击两次才会有反应在ionic官方文档首页上有一个FAQ,点进去有一个click delays按照我的翻译是点击延迟,但是这个我觉得根本就不是延迟,就是没有反应,反正按照这个来修改,可以解决这个问题。8. ios时间问题这个应该不属于ionic,只要是苹果的好像都有问题,苹果不能识别-时间的字符串,会异常总结遇到问题后,多去GitHub上看,大部分都有解决方案,少部分没有的只能提高自己解决问题的技能了。

January 15, 2019 · 1 min · jiezi

ionic echarts引入和使用及报错解决

1.项目中安装echartscnpm install echarts –save但是ionic项目依赖于angular和typeScript,所以再安装ts支持包cnpm install @types/echarts –save官网给出的一段建议:在 3.1.1 版本之前 ECharts 在 npm 上的 package 是非官方维护的,从 3.1.1 开始由官方 EFE 维护 npm 上 ECharts 和 zrender 的 package。所以还得安装zrendercnpm install zrender2.引入使用在所需页面的name.ts引入如下import echarts from ’echarts’;使用如下initCharts(){ echarts.init(this.myCharts.nativeElement).setOption({ xAxis: { type: ‘category’, data: [‘Mon’, ‘Tue’, ‘Wed’, ‘Thu’, ‘Fri’, ‘Sat’, ‘Sun’] }, yAxis: { type: ‘value’ }, series: [{ data: [820, 932, 901, 934, 1290, 1330, 1320], type: ’line’ }] },true);}调用constructor(public navCtrl: NavController, public navParams: NavParams) { console.log(echarts); this.initCharts();}页面显示找到name.html定义一个呈现图表的div如下<ion-header> <ion-navbar> <ion-title>charts</ion-title> </ion-navbar></ion-header><ion-content padding>// #charts通过ViewChild获取dom节点 <div class=“charts” #charts> </div></ion-content>在name.ts文件一如dom节点 @ViewChild(‘charts’) myCharts:ElementRef运行ionic serve报错如下图大概意思是说获取不到dom节点,也就是我们展示地图的那个div,打印了之后发现是null或者undefine。后面就打印了一下ionic页面的生命周期,测试了一下ionic页面的生命周期函数。生命周期函数 触发时刻 顺序constructor() 创建页面时触发 1ionViewDidLoad() 当页面加载时触发 2ionViewWillEnter() 当将要进入页面时触发 3ionViewDidEnter() 进入页面时触发 4在constructor() 和 ionViewDidLoad()initCharts()函数中是获取不到dom节点的,因为页面还没有加载完成,在ionViewWillEnter()和ionViewDidEnter()是能获取到dom节点的,顾名思义只有当页面加载完成时才能获取到dom节点,所以再在页面加载完成后的生命周期函数里可以获取到改为ionViewWillEnter(){ }或ionViewDidEnter(){ this.initCharts();}运行ionic serve 完美解决3.完整代码示例Charts.ts如下import {Component, ElementRef, ViewChild} from ‘@angular/core’;import { IonicPage, NavController, NavParams } from ‘ionic-angular’;import echarts from ’echarts’;/** * Generated class for the ChartsPage page. * * See https://ionicframework.com/docs/components/#navigation for more info on * Ionic pages and navigation. */@IonicPage()@Component({ selector: ‘page-charts’, templateUrl: ‘charts.html’,})export class ChartsPage { @ViewChild(‘charts’) myCharts:ElementRef constructor(public navCtrl: NavController, public navParams: NavParams) { console.log(echarts); } ionViewDidLoad() { // this.initCharts(); console.log(‘ionViewDidLoad ChartsPage’); } // ionViewDidEnter(){ // this.initCharts(); // } ionViewWillEnter(){ this.initCharts(); } initCharts(){ console.log(document.getElementById(‘charts’)); echarts.init(this.myCharts.nativeElement).setOption({ xAxis: { type: ‘category’, data: [‘Mon’, ‘Tue’, ‘Wed’, ‘Thu’, ‘Fri’, ‘Sat’, ‘Sun’] }, yAxis: { type: ‘value’ }, series: [{ data: [820, 932, 901, 934, 1290, 1330, 1320], type: ’line’ }] },true); }}charts.html<!– Generated template for the ChartsPage page. See http://ionicframework.com/docs/components/#navigation for more info on Ionic pages and navigation.–><ion-header> <ion-navbar> <ion-title>charts</ion-title> </ion-navbar></ion-header><ion-content padding> <div class=“charts” #charts> </div></ion-content>charts.scsspage-charts { .charts{ height: 500px; }} ...

December 29, 2018 · 2 min · jiezi

ionic3 toastController使用封装

说明toastController是ionic官方提供的消息提示框组件,用于给用户操作后结果反馈和提示。官网地址:https://ionicframework.com/do…如下是默认的样式,项目中使用则需要去改变很多样式则需要讲解一些入参。2. 使用控制台运行命令,创建服务ionic g provicer ToastService编写程序import { Injectable } from ‘@angular/core’;/导入ionic消息提示框模块ToastController/import { ToastController } from “ionic-angular”;/ Generated class for the ToastServiceProvider provider. See https://angular.io/guide/dependency-injection for more info on providers and Angular DI.*/@Injectable()export class ToastServiceProvider {//自己定义的三种消息框样式 errorCss=‘errorToast’ generalCss=‘generalToast’ successCss=‘successToast’/构造函数引入/ constructor(public toast:ToastController) { console.log(‘Hello ToastServiceProvider Provider’); } /* * 错误信息提示框 * @param message 消息 / errorToast(message:any){ this.presentToast(message,this.errorCss); } /* * 普通信息提示框 * @param message 消息 / generalToast(message:any){ this.presentToast(message,this.generalCss); } /* * 成功信息提示框 * @param message / successToast(message:any){ this.presentToast(message,this.successCss); } /* * * @param message需要展示的信息 * @param css 自定义的背景颜色 */ presentToast(message:any,css:string) { let toast = this.toast.create({ message: message,//提示消息内容 duration: 3000,//显示时长,单位毫秒 position: ‘bottom’,//消息框出现的位置,bottom就是底端的意思,自然就有top和中间了 showCloseButton:true,//是否有关闭按钮,true就是有 cssClass:css,//自己给消息框定义的样式,css样式名称 closeButtonText:‘关闭’//关闭按钮上的文字 }); toast.onDidDismiss(() => { console.log(‘Dismissed toast’); }); toast.present();//出发消息提示框 }}对应的css文件.errorToast{ //.toast-message{ // color: #a94442; //} .toast-wrapper { //background: #eba6ac; background: #f53d3d; }}.generalToast{ .toast-wrapper { background: #488aff; }}.successToast{ .toast-wrapper { background: #32db64; }}3. 导入app.module.ts中声明服务,那个页面需要使用,引用即可。4.效果成功提示消息:失败提示:

December 21, 2018 · 1 min · jiezi

angularjs http与后台交互

1.描述无论是使用angularjs做前端或是结合ionic混合开发移动端开发app都需要与后台进行交互,而angular给我提供了httpModule模块供我们使用。今天就展现一个http的封装和使用的一个具体流程。2. HttpModule引入找到app.module.ts文件import { NgModule, ErrorHandler } from ‘@angular/core’;import { BrowserModule } from ‘@angular/platform-browser’;import { IonicApp, IonicModule, IonicErrorHandler } from ‘ionic-angular’;import { MyApp } from ‘./app.component’;import { LoginPage } from “../pages/login/login”;/引入HttpClientModule模块/import { HttpClientModule } from “@angular/common/http”;import { RequestServiceProvider } from “../providers/request-service/request-service”;import { StatusBar } from ‘@ionic-native/status-bar’;import { SplashScreen } from ‘@ionic-native/splash-screen’;@NgModule({ declarations: [ MyApp, LoginPage, ], imports: [ BrowserModule, /* 导入模块 / HttpClientModule, IonicModule.forRoot(MyApp,{ tabsHideOnSubPages:’true’, backButtonText:’’ }) ], bootstrap: [IonicApp], entryComponents: [ MyApp, LoginPage, ], providers: [ StatusBar, SplashScreen, {provide: ErrorHandler, useClass: IonicErrorHandler}, RequestServiceProvider, ]})export class AppModule {}按照自己的项目导入HttpClientModule模块即可,我导入其他组件,不用考虑。3.创建服务ionic g provider RequestService执行完成后则会出现如下文件4.封装服务/导入http相关/import { HttpClient,HttpHeaders } from ‘@angular/common/http’;import { Injectable } from ‘@angular/core’;import {Observable} from “rxjs”;/ Generated class for the RequestServiceProvider provider. See https://angular.io/guide/dependency-injection for more info on providers and Angular DI./@Injectable()export class RequestServiceProvider { /** 讲基础路径提取说出来,配置ip和端口时只需要在这修改 / //basePath:string=‘http://10.4.0.205:8081’ reserveBasePath:string=‘http://10.6.254.110:8081’ basePath=this.reserveBasePath; /* 封装固定的消息头相关 / private headers = new HttpHeaders({‘Content-Type’: ‘application/json’}) // private headers = new HttpHeaders({‘Access-Control-Allow-Origin’:’’});/初始化http变量/ constructor(public http: HttpClient) { console.log(‘Hello RequestServiceProvider Provider’); } /* 给外界提供了四个基础的方法只需要传入uri和data即可 / get(req:any):Observable<any> { return this.http.get(this.basePath+req.uri,{headers:this.headers}); } post(req:any):Observable<any>{ return this.http.post(this.basePath+req.uri,req.data,{headers:this.headers}); } put(req:any):Observable<any>{ return this.http.put(this.basePath+req.uri,req.data,{headers:this.headers}); } delete(req:any):Observable<any>{ return this.http.delete(this.basePath+req.uri,{headers:this.headers}); }}5.导入声明封装服务找到app.module.ts文件和第一部类似import { NgModule, ErrorHandler } from ‘@angular/core’;import { BrowserModule } from ‘@angular/platform-browser’;import { IonicApp, IonicModule, IonicErrorHandler } from ‘ionic-angular’;import { MyApp } from ‘./app.component’;import { LoginPage } from “../pages/login/login”;/引入HttpClientModule模块/import { HttpClientModule } from “@angular/common/http”;/导入自定的服务/import { RequestServiceProvider } from “../providers/request-service/request-service”;import { StatusBar } from ‘@ionic-native/status-bar’;import { SplashScreen } from ‘@ionic-native/splash-screen’;@NgModule({ declarations: [ MyApp, LoginPage, ], imports: [ BrowserModule, / 导入模块 / HttpClientModule, IonicModule.forRoot(MyApp,{ tabsHideOnSubPages:’true’, backButtonText:’’ }) ], bootstrap: [IonicApp], entryComponents: [ MyApp, LoginPage, ], providers: [ StatusBar, SplashScreen, {provide: ErrorHandler, useClass: IonicErrorHandler}, / 声明服务 / RequestServiceProvider, ]})export class AppModule {}6.使用服务找到自己的页面所对应的ts文件如下面代码一样import { Component } from ‘@angular/core’;import { IonicPage, NavController, NavParams } from ‘ionic-angular’;/导入声明/import {RequestServiceProvider} from “../../providers/request-service/request-service”;/ * Generated class for the LoginPage page. * * See https://ionicframework.com/docs/components/#navigation for more info on * Ionic pages and navigation. /@IonicPage()@Component({ selector: ‘page-login’, templateUrl: ’login.html’,})export class LoginPage { title:string = ‘登录’ promptMessage:string = ’’ user={ username:’’, password:’’ } req={ login:{ uri:’/user/login’ } } constructor(public navCtrl: NavController, public navParams: NavParams, /* 初始化服务对象 / private requestService:RequestServiceProvider) { } ionViewDidLoad() { console.log(‘ionViewDidLoad LoginPage’); } login(){ /* 调用post方法,subscribe()方法可以出发请求,调用一次发送一次,调用多次发多次 */ this.requestService.post({uri:this.req.login.uri,data:user}).subscribe((res:any)=>{ console.log(res); if (res.code == 0){ this.promptMessage = res.message; } else { this.promptMessage = res.message; } }, error1 => { alert(JSON.stringify(error1)) }); } } ...

December 20, 2018 · 2 min · jiezi

ionic环境搭建

环境依赖关系叙述移动端混合开发的一个明显优势就是,一套代码两套部署,开发一套项目代码,可分别打成Android的包和ios的包。无论是混合开发还是原生开发,都是会需要原生的平台。我们先以Android平台为例,首先肯定需要AndroidSDK,Android环境缘起于java,所以必须先安装JDk,这是平台的环境.混合开发顾名思义需要前端和原生两块环境内容。混合开发平台,我们选择的是cordova,那么它依赖于node.js环境,并且需要node.js的npm模块来帮它下载插件。创建项目还需调试运行,那么就会需要Android模拟器。由于原生的Android模拟器启动过于慢,而且由于网络限制,google的cpu渲染加速器环境也难以下载。我们这里使用的是一个国外的好用且免费的第三方模拟器Genymotion。东西是免费的,但是需要注册和登陆。一共需要搭建的环境也就这么几个JDK,AndroidSDK,node.js,cordova, Genymotion。开发环境:node.jscordova6.0.0ionic测试运行环境:JDKAndroidSDKGenymotion2.安装说明2.1 JDK安装教程很多,记住安装时下载1.8版本菜鸟教程:http://www.runoob.com/java/ja…2.2 AndroidSDKandroidSDK由于国内限网,推荐一个国内的一个下载网站:http://www.androiddevtools.cn/下载后根据提示安装,推荐下载的包就不要取消。系统一般会帮你默认勾选安卓最新版本Android9.0,但是Android9.0会出现模拟器启动不了的问题,坑很多,建议安装9.0以下的,我选的Android6.0,勾选下图的选项即可。下载完配置Android环境变量打开安卓的安卓目录如图,我画圈的两个目录,需要加入到path里面D:Program Filesandroidplatform-tools; D:Program Filesandroidtools;2.3Genymotion 安装官网下载官方只需注册即可免费使用,使用下面链接注册即可。注册:https://www.genymotion.com/下载:https://www.genymotion.com/do…下载后按照提示安装即可,打开软件时登录选择个人登录即可。下载安卓镜像打开后如图,点击add。找自己需要的版本下载即可2.4安装nodejs官网下载nodejs免安装版https://nodejs.org/en/download/选择windows免安装版64位解压到要安装的目录添加环境变量计算机(右键)–>属性(左键)–>高级系统设置(左键)–>环境变量(左键)检查是否配置成功node -vnpm -v正常显示出版本号则说明安装成功更换npm镜像源首先来说为什么要更换镜像源,由于npm的镜像源是国外的,我们使用npm工具安装软件环境时,由于网速问题会导致很多难以解决的问题,而且下载巨慢,故将镜像源更换为淘宝的镜像源。执行下面命令更换软件源npm install -g cnpm –registry=https://registry.npm.taobao.org查看是否更换成功cnpm -v不报错且出来一段信息则说明更换成功。参考学习菜鸟教程:http://www.runoob.com/nodejs/…w3school:https://www.w3cschool.cn/node…npm基本使用:https://www.w3cschool.cn/node…2.5安装cordova平台官网https://cordova.apache.org/使用npm安装平台cnpm install -g cordova@6.0.0上面安装指定版本的cordova,我们这里安装cordova6.0.0,建议不要安装7版本和8版本,后面创建项目时会出现很多问题。cordova -v检查是否安装成功,正确显示出版本号则说明安装成功。项目相关命令#1.创建项目cordova create MyAppcd ./MyApp#2.添加平台cordova platform add browser #添加浏览器平台cordova platform add android #安卓平台cordova platform add ios #ios平台#注意添加相关平台需要拥有相关平台的环境#3.编译打包cordova build android #打成android平台的包 .apkcordova build ios #打成ios平台的包#4.运行到androidSDK自带的模拟器cordova emulate android#5.运行到第三方模拟器或真机cordova run android参考学习w3school:https://www.w3cschool.cn/cord…2.6angularjs环境搭建官网https://www.angular.cn/guide/…安装项目脚手架npm install -g @angular/cli 这里只是为了给ionic创建项目提供环境,但是要使用ionic开发就必须学会angularjs。2.7ionic安装配置官网https://ionicframework.com/do…安装-g是全局的意思,latest是最新版的意思。cnpm install -g ionic@latest项目相关命令#1创建项目ionic start myNewProject tabs #tabs是项目模板的一种,ionic平台自身会提供几种项目模板#进入到项目cd ./myNewProject#2.添加平台ionic cordova platform add android #平台同上一样可选#3.打包ionic cordova build android#4.运行ionic serve #运行在浏览器ionic cordova run android #运行到android,ios可能会出现的问题创建项目时,会问你是否使用ionic4创建项目,选择n即可,也可创建尝试下,但运行性项目到android模拟器显示空白。参考学习菜鸟教程:http://www.runoob.com/ionic/i…中文文档:http://www.ionic.wang/js_doc-…参考学习w3school:https://www.w3cschool.cn/cuhk…3.可能会遇到的问题node.js 使用免安装版能避免许多未知错误。cordova安装6.0.0不要安装7.0或8.0的,后面出现的错误会很多。cordova添加android平台,会多次失败,由于资源在国外会失败多次,插件下载完成就可以成功。安卓模拟器需要cpu加速器,不然会一直黑屏,所以选用了第三方模拟器。谷歌真机调试chrome://inspect。android9.0不能用使用。

December 14, 2018 · 1 min · jiezi