混合App开发之Ionic3

文章发表于 http://blog.poetries.top/2019/01/10/ionic3-summary

学习Ionic3需要懂AngularTypescript,没基础建议看一下这两篇文章入门一下

一、介绍

Ionic 是一款基于 AngularCordova 的强大的 HTML5 移动应用开发框架 , 可以快速创建一 个跨平台的移动应用。可以快速开发移动 App、移动端 WEB 页面、微信公众平台应用,混 合 app web页面。

1.1 ionic 特点

  • ionic 基于Angular语法,简单易学。
  • ionic 是一个轻量级框架。
  • ionic 完美的融合下一代移动框架,支持 Angularjs 的特性, MVC ,代码易维护。
  • ionic 提供了漂亮的设计,通过 SASS 构建应用程序,它提供了很多 UI 组件来帮助开发者开发强大的应用。
  • ionic 专注原生,让你看不出混合应用和原生的区别
  • ionic 提供了强大的命令行工具。
  • ionic 性能优越,运行速度快。

1.2 Ionic 和 Cordova(phonegap)、Angular 关系

ionic = Cordova + Angular + ionic CSS

Ionic是完全基于谷歌的 Angular 框架,在 Angular 基础上面做了一些封装,让我们可以更快 速和容易的开发移动的项目。Ionic 调用原生的功能是基于 Cordova,Cordova 提供了使用 JavaScript 调用 Native 功能,ionic 自己也封装了一套漂亮的 CSS UI 库。

二、环境搭建

2.1 Ionic初始化构建

https://ionicframework.com/getting-started#cli

# 全局安装
npm install -g ionic
  • ionic info (查看当前ionic的全部版本信息)
image.png
ionic start myApp tabs # 建议使用初始化

cd myApp 

ionic serve
image.png
  • ionic serve 运行项目

2.2 Genymotion 安卓模拟器

使用方法 https://www.jianshu.com/p/aabc4fd01311

2.3 在IOS环境下体验

需要配备mac,安装xcode

# mac下需要添加sudo
sudo ionic cordova platform add ios

# 注意获取目录权限的问题
chmod -R 777 项目文件夹名

真机调试与发布需要Apple开发者账号

打开xcode选择platform下中ios文件夹,点击运行项目

image.png

2.4 在安卓下体验

1. 添加android

ionic cordova platform add android

# 注意获取目录权限的问题
chmod -R 777 项目文件夹名

# 直接使用Android studio 进行调试链接

# 打包成apk拖入Genymotion调试

2. 下载android studio 打开/platform/andriod文件

image.png

3. 然后连接android studio结合geny生成apk调试

image.png

2.5 在浏览器/微信下体验

1. 添加browser文件夹

ionic cordova platform add browser

2. 打包

ionic cordova build browser

image.png
image.png

3. 运行

npm run serve

# 浏览器打开 http://localhost:8100

4. 部署

www目录部署到服务器上即可

在微信下体验 注意微信title问题

2.6 ionic的常用命令

1. 基本命令

  • ionic g page myPage 创建页面
  • ionic g provider MyData 创建provider
  • ionic serve 在浏览器中看
  • ionic platform add/remove android/ios 添加删除平台
  • ionic build android/ios 快捷打包(IOS最好通过xcode打包发布)

2. 辅助命令

  • ionic info 查看关于ionic的系统消息
  • ionic emulate android/ios 模拟器中打开
  • ionic cordova plugin list 查看插件安装列表

3. 正式发布需要的命令

  • ionic cordova platforms add android 添加安卓平台
  • ionic cordova build android --release 打包成apk

三、Ionic3.x+ 目录结构分析及创建组件

3.1 Ionic3.x 目录结构分析

1. 整体目录结构

image.png
  • hooks:编译 cordova 时自定义的脚本命令,方便整合到我们的编译系统和版本控制系统中
  • node_modules :node 各类依赖包
  • resources :android/ios 资源(更换图标和启动动画)
  • src:开发工作目录,页面、样式、脚本和图片都放在这个目录下
  • www:静态文件
  • platforms:生成 android 或者 ios 安装包路径(platforms\android\build\outputs\apk:apk
    所在位置)执行 cordova platform add android 后会生成
  • plugins:插件文件夹,里面放置各种 cordova 安装的插件
  • config.xml: 打包成 app 的配置文件
  • package.json: 配置项目的元数据和管理项目所需要的依赖
  • tsconfig.json: TypeScript 项目的根目录,指定用来编译这个项目的根文件和编译选项
  • tslint.json:格式化和校验 typescript

2. src目录

image.png
  • app:应用根目录
  • assets:资源目录(静态文件(图片,js 框架)
  • pages:页面文件,放置编写的页面文件,包括:htmlscssts
  • theme:主题文件,里面有一个 scss 文件,设置主题信息。

3. Ionic3.x src 下面文件分析

image.png

4. app.module.ts 分析

//这个根模块会告诉 ionic 如何组装该应用

import { NgModule, ErrorHandler } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
import { HttpClientModule } from '@angular/common/http';
import { IonicApp, IonicModule, IonicErrorHandler } from 'ionic-angular';
import { IonicStorageModule } from '@ionic/storage';
import { MyApp } from './app.component';

// 其他组件
import { HomePage } from '../pages/home/home';
import { DiscoveryPage } from '../pages/discovery/discovery';
import { ChatPage } from '../pages/chat/chat';
import { NotificationPage } from '../pages/notification/notification';
import { MorePage } from '../pages/more/more';
import { LoginPage } from '../pages/login/login';
import { TabsPage } from '../pages/tabs/tabs';

import { StatusBar } from '@ionic-native/status-bar';
import { SplashScreen } from '@ionic-native/splash-screen';
import { ApiProvider } from '../providers/api/api';

@NgModule({
  declarations: [
    MyApp,
    HomePage,
    DiscoveryPage,
    ChatPage,
    NotificationPage,
    MorePage,
    LoginPage,
    TabsPage
  ],
  imports: [
    BrowserModule,
    HttpClientModule,
    IonicModule.forRoot(MyApp),
    IonicStorageModule.forRoot()
  ],
  bootstrap: [IonicApp],
  entryComponents: [
    MyApp,
    HomePage,
    DiscoveryPage,
    ChatPage,
    NotificationPage,
    MorePage,
    LoginPage,
    TabsPage
  ],
  providers: [
    StatusBar,
    SplashScreen,
    {provide: ErrorHandler, useClass: IonicErrorHandler},
    ApiProvider
  ]
})
export class AppModule {}

3.2 创建组件

  1. cd 到我们的项目目录
  2. 通过 ionic g component 组件名称创建组件

输入ionic g后,可以创建的组件如下

image.png
  1. 创建完成组件以后会在 src 目录下面多一个 components 的目录,这个目录里面有我们用命令创建的所有的组件
image.png
  1. 如果我们要使用这些组件必须在 app.module.ts 里面注册我们的模块,注册完成后就可以在 pages 里面的其页面里面使用这些组件
image.png
image.png

3.3 创建页面以及页面跳转

1. 创建页面

ionic g page news

2. 跳转

<button ion-button (click)="pushButton">执行button跳转</button>
image.png

四、Ionic页面生命周期

// 页面被加载完成后调用的函数,切换页面时并不会进行重新加载,因为有cache的存在
onPageLoaded() {
  console.log('page 1: page loaded.');
}
 
// 页面即将进入的时候
onPageWillEnter() {
  // 在这里可以做页面初始化的一些事情
  console.log('page 1: page will enter.');
}
 
// 页面已经进入的时候
onPageDidEnter() {
  console.log('page 1: page did enter.');
}
 
// 页面即将离开的时候
onPageWillLeave() {
  console.log('page 1: page will leave.');
}
 
// 页面已经离开的时候
onPageDidLeave() {
  console.log('page 1: page did leave.');
}
 
// 从 DOM 中移除的时候执行的生命周期
onPageWillUnload() {
 
}
 
// 从 DOM 中移除执行完成的时候
onPageDidUnload() {
 
}

五、API的使用

5.1 图片上传

文档 https://ionicframework.com/docs/native

npm i --save @ionic-native/camera @ionic-native/file @ionic-native/file-path @ionic-native/transfer

# 插件 mac下需要sudo
sudo ionic cordova plugin add cordova-plugin-camera 
sudo ionic cordova plugin add cordova-plugin-file 
sudo ionic cordova plugin add cordova-plugin-file-transfer
sudo ionic cordova plugin add cordova-plugin-filepath

5.2 Icon 本地存储的使用

https://ionicframework.com/docs/storage

1. 安装插件

ionic cordova plugin add cordova-sqlite-storage

npm install --save @ionic/storage

2. src/app/app.module.ts导入配置

import { IonicStorageModule } from '@ionic/storage';

@NgModule({
  declarations: [
    // ...
  ],
  imports: [
    BrowserModule,
    IonicModule.forRoot(MyApp),
    IonicStorageModule.forRoot()
  ],
  bootstrap: [IonicApp],
  entryComponents: [
    // ...
  ],
  providers: [
    // ...
  ]
})
export class AppModule {}

3. 在你的页面中使用

import { Storage } from '@ionic/storage';

export class MyApp {
  constructor(private storage: Storage) { }

  ...

  // set a key/value
  storage.set('name', 'Max');

  // Or to get a key/value pair
  storage.get('age').then((val) => {
    console.log('Your age is', val);
  });
}

5.3 二维码扫描

https://ionicframework.com/docs/native/qr-scanner

1. 安装插件

# 安装插件
$ ionic cordova plugin add cordova-plugin-qrscanner
$ npm install --save @ionic-native/qr-scanner

2. 在src/app/app.module.ts中导入

import { QRScanner, QRScannerStatus } from '@ionic-native/qr-scanner';

  providers: [
    StatusBar,
    SplashScreen,
    {provide: ErrorHandler, useClass: IonicErrorHandler},
    QRScanner // 导入插件
  ]

3. 在页面中使用

# 新建扫描页面
ionic g page scan
/** scan.scss **/
page-scan {
html,
body,
ion-app,
ion-content,
ion-page,
.nav-decor {
  background-color: transparent !important;
}
.line {
  position: absolute;
  z-index: 999999;
  top: 15px;
  height: 2px;
  width: 100%;
  background-color: #009900; //动画
  animation: scan 1s infinite alternate;
  -webkit-animation: scan 1s infinite alternate;
}
@keyframes scan {
  from {
    top: 20%;
  }
  to {
    top: 80%;
  }
}
}
<!--scan.html-->
<ion-header>
<ion-navbar>
  <ion-title></ion-title>
</ion-navbar>
</ion-header>

<div class="line"></div>
// scan.ts
import { Component } from '@angular/core';
import { IonicPage, NavController, NavParams, AlertController } from 'ionic-angular';
import { QRScanner, QRScannerStatus } from '@ionic-native/qr-scanner';

@Component({
  selector: 'page-scan',
  templateUrl: 'scan.html',
})
export class ScanPage {

  constructor(public navCtrl: NavController,
    public navParams: NavParams,
    public alertCtrl: AlertController,
    public qrScanner: QRScanner) {
  }

  ionViewDidLoad() {
    console.log('ionViewDidLoad ScanPage');
  }
  ionViewDidEnter() {
    this.scanQRCode();
  }

  scanQRCode() {
    this.qrScanner.prepare()
      .then((status: QRScannerStatus) => {
        if (status.authorized) {
          window.document.querySelector('body').classList.add('transparent-body');
          let scanSub = this.qrScanner.scan().subscribe((text: string) => {
            let alert = this.alertCtrl.create({
              title: '二维码内容',
              subTitle: text,
              buttons: ['OK']
            });
            alert.present();
            scanSub.unsubscribe();
          });

          this.qrScanner.show();

        }
        else if (status.denied) {
          //提醒用户权限没有开
        }
        else {

        }
      })
      .catch((e: any) => console.error('Error :', e));
  }

}

4. 调用scan页面

<button ion-item (click)="gotoScanQRCode()">
  <ion-icon name="qr-scanner" item-start color="dark"></ion-icon>
  <ion-label>扫描二维码</ion-label>
</button>
import { ScanPage } from '../scan/scan'; // 引入新建的扫描页面

constructor(public navCtrl: NavController, 
  public navParams: NavParams) {
    super()
}

// 跳转到二维码扫描界面,加上'animate': false参数是为了相机能够在整个屏幕显示,否则相机出不来
gotoScanQRCode() {
  this.navCtrl.push(ScanPage, null, {'animate': false})
}

5.4 读取版本信息

https://ionicframework.com/docs/native/app-version

1. 安装插件

$ ionic cordova plugin add cordova-plugin-app-version
$ npm install --save @ionic-native/app-version

导入app.module.ts

import { AppVersion } from '@ionic-native/app-version';
providers: [
    AppVersion
]

2. 新建一个页面展示version

ionic g page versions

3. 在app.modules.ts中导入

4. version页面配置

在浏览器中不可以调试,需要真机调试

<ion-header>

  <ion-navbar>
    <ion-title>版本信息</ion-title>
  </ion-navbar>

</ion-header>

<ion-content>
  <ion-list>
    <ion-item>
      AppName: {{appName}}
    </ion-item>
    <ion-item>
      PackageName: {{packageName}}
    </ion-item>
    <ion-item>
      VersionCode: {{versionCode}}
    </ion-item>
    <ion-item>
      VersionNumber: {{versionNumber}}
    </ion-item>
  </ion-list>

</ion-content>
import { Component } from '@angular/core';
import { IonicPage, NavController, NavParams } from 'ionic-angular';
import { AppVersion } from '@ionic-native/app-version';

@Component({
  selector: 'page-versions',
  templateUrl: 'versions.html',
})
export class VersionsPage {

  appName: string;
  packageName: string;
  versionCode: string;
  versionNumber: string;

  constructor(public navCtrl: NavController,
    private appVersion: AppVersion,
    public navParams: NavParams) {
  }

  ionViewDidLoad() {
    this.appVersion.getAppName().then(v => {
      this.appName = v;
    });

    this.appVersion.getPackageName().then(v => {
      this.packageName = v;
    });

    this.appVersion.getVersionCode().then(v => {
      this.versionCode = v;
    });

    this.appVersion.getVersionNumber().then(v => {
      this.versionNumber = v;
    });
  }
}

5. 其他组件中使用

<button ion-item (click)="gotoVersions()">
<ion-icon name="help-circle" item-start color="dark"></ion-icon>
<ion-label>关于</ion-label>
</button>
gotoVersions(){
  this.navCtrl.push(newPage)
}

六、页面之间的传参

6.1 js跳转方式

路由跳转通过NavController

1. 导入NavController

import { NavController} from 'ionic-angular';

2. 注入

constructor(public navCtrl: NavController) {}

3. 传参

 // 参数可以说任意 这里对象形式
 this.navCtrl.push(DetailsPage, {id: questionId}) 

navCtrl传参和ModalCtr传参一样

this.ModalCtrl.create(AnswerPage, {
  id: this.id
})

4. 接收参数

public id:string;

constructor(public navCtrl: NavController, public navParams: NavParams) {
  this.id = navParams.get('id') // 接收传递过来的参数
}

// 或者这样写
ionViewDidLoad(){
   this.id = this.navParams.get('id')
}

6.2 HTML传参

1. 传参

通过[navPush]打开新页面,[navParams]传递参数。navPush文档

import { ChatdetailsPage } from '../chatdetails/chatdetails' // 1.导入页面

public ChatdetailsPage: any; // 2. 声明类型

constructor(){
    userinfo = {
        userId: '1234',
        username: 'poetries'
    }
    this.ChatdetailsPage = ChatdetailsPage; // 3. 
}
<!--4. 使用-->
<ion-item [navPush]="ChatdetailsPage" [navParams]="userinfo">
  <ion-avatar>
    <img src="https://blog.poetries.top/images/avatar.jpg">
  </ion-avatar>
  <h2>poetries</h2>
  <p>聊天组件开发</p>
</ion-item>

2. 获取传递的参数

  constructor(public navCtrl: NavController, public navParams: NavParams) {
    this.username = navParams.get('username')
    this.userid = navParams.get('userid')
  }

七、管道的使用

1. 新建管道

ionic g pipe realativetime # 管道名称

2. 配置

import { Pipe, PipeTransform } from '@angular/core';
import * as moment from 'moment'

/**
 * Generated class for the RelativetimePipe pipe.
 *
 * See https://angular.io/api/core/Pipe for more info on Angular Pipes.
 */
@Pipe({
  name: 'relativetime',
})
export class RelativetimePipe implements PipeTransform {
  /**
   * Takes a value and makes it lowercase.
   */
  transform(value: string, ...args) {
    return moment(value).toNow() // 将过去时间变成距离现在多久
  }
}

3. src/App/modules.ts中全局导入

import { RelativetimePipe } from '../pipes/relativetime/relativetime'


@NgModule({
  declarations: [
    RelativetimePipe
  ]
...

4. 使用

<!--relativetime管道名称-->
<div class="msg-info">
  <p>{{msg.username}}&nbsp;{{msg.time | relativetime}}</p>
</div>

八、Theme主题全局样式

8.1 全局图标颜色配置

1. theme/variables.scss中定义

$colors: (
  primary:    #488aff, /**蓝色。优先匹配**/
  secondary:  #32db64, /**第二匹配**/
  danger:     #f53d3d, /**第三匹配**/
  light:      #f4f4f4,
  dark:       #222
);

2. 使用

<!--在color上使用primary-->
<ion-icon name="paper" item-start color="primary"></ion-icon>
image.png

8.2 自定义样式

定义两套样式

theme中新建light.scss。在variables.scss中导入

// 自定义样式
@import "light";

8.3 全局设置夜间模式切换

**1. 在theme中新建theme.dark.scss。在variables.scss中导入 **

/** 根据dark-theme 作用域切换主题 **/
.dark-theme {
    ion-content,.card,.floatMenu {
      background-color: #1e2446 !important;
      color: #fff !important;
    }
  
    .toolbar-title {
      color: #fff !important;
    }
   
    .header .toolbar-background {
      border-color: #140414 !important;
      background-color: #3a3c4b !important;
    }
  
    .list,.label,.item{
      background-color: #3a3c4b !important;
      color:#FFFFFF !important;
    }
    .item{
      border-bottom: 0.55px solid #2e2749 !important;
    }
    .item-inner,{
      border: none !important;
    }
    .item-block{
      border-bottom: 0.55px solid #2e2749 !important;
    }
 }

2. 在theme中新建theme.light.scss。在variables.scss中导入

.light-theme {
    ion-content {
        background-color: #e3e4e7
      }
     
      .toolbar-background {
        background-color: #fff;
      }
 }

3. 在variables.scss中导入

/** 这里导入的是两套主题样式 **/

@import "theme.light";
@import "theme.dark";
<ion-list class="marginTop">
  <ion-list-header>
   设置
  </ion-list-header>
  <ion-item>
    <ion-icon name="cloudy-night" item-start color="purple"></ion-icon>
    <ion-label>夜间模式</ion-label>
    <ion-toggle color="purple" (ionChange)="toggleChangeTheme()"></ion-toggle>
  </ion-item>
</ion-list>

4. 新建一个provider来控制app主题的切换

ionic provider settings
// settings.ts
import { HttpClient } from '@angular/common/http';
import { Injectable } from '@angular/core';
import { BehaviorSubject } from 'rxjs/Rx'

@Injectable()
export class SettingsProvider {

  private theme: BehaviorSubject<string>
  constructor(public http: HttpClient) {
    this.theme = new BehaviorSubject('light-theme')
  }
  setActiveTheme(val) {
    this.theme.next(val)
  }
  getActiveTheme() {
    return this.theme.asObservable()
  }
}

5. 在app.component.ts中设置

image.png

然后在src/app/app.html中设置selectedTheme

<ion-nav [root]="rootPage" [class]="selectedTheme"></ion-nav>

6. 在对应组件页面中设置

// 1. 引入settings providers
import { SettingsProvider } from '../../providers/settings/settings';

export class UserCenterPage {

  // 2. 定义变量
  public selectedTheme: string;

  constructor(public navCtrl: NavController, 
                      public navParams: NavParams,
                      // 3. 注入
                      public settings: SettingsProvider
                      public ModalCtrl: ModalController ) {
                        super()
                        
                         // 4. 获取主题
                        this.settings.getActiveTheme().subscribe(val => this.selectedTheme = val)
  }
  // 5.切换主题
  toggleChangeTheme() {
    if(this.selectedTheme == 'dark-theme') {
      this.settings.setActiveTheme('light-theme')
    }else{
      this.settings.setActiveTheme('dark-theme')
    }
  }
}

7. 在整个app启动的时候设置夜间或者白天模式

image.png

九、组件化开发-自定义组件

9.1 自定义组件

1. 新建组件

# 新建组件

ionic g component emojipicker# 组件名称
image.png

2. 在src/app/app.module.ts中导入

import { ComponentsModule } from '../components/components.module'

@NgModule({
  declarations: [
  
  ],
  imports: [
  BrowserModule,
    HttpClientModule,
    ComponentsModule, // 导入自定义组件
    IonicStorageModule.forRoot()
  ],
  bootstrap: [IonicApp],
  entryComponents: [
  ],
  providers: [
    StatusBar,
    SplashScreen,
    {provide: ErrorHandler, useClass: IonicErrorHandler}
  ]
})
export class AppModule {}

3. 使用

在页面中使用即可

image.png

4. 组件通过@input()接收外部参数

4.1 定义组件

import { Component, Input } from '@angular/core';

// datatype 外部传递进来,dataSourceType 本地接收之后的参数命名
@Input('dataType') dataSourceType;

//这里没有 ionViewDidLoad 生命周期的函数
ngAfterContentInit(){
  console.log(this.dataSourceType) 
}

4.2 使用组件

<!--dataType传递给question-list中的Input 接收-->
<question-list dataType="{{dataType}}"></question-list>

9.2 [hidden] [style] [class] 动态控制组件

1. 动态显示隐藏hidden

<ion-content padding>
  <img src="{{pathForImage(lastImage)}}"  class="img" [hidden]="lastImage === null" />
  <h3 [hidden]="lastImage !== null">请从图片库选择一个图片</h3>
</ion-content>

2. 动态绑定style

<ion-footer no-border [style.height]="isOpenEmojiPicker ? '255px': '55px' ">
</ion-footer>

3. 动态绑定class属性

<div class="message right" 
    *ngFor="let msg of messageList"
    [class.right] = "msg.userId === userId"
    [class.left] = "msg.userId === chatUserId"
>
</div>

十、指令

采用了angular语法 建议参考这篇文章

十一、常用组件使用

组件文档 https://ionicframework.com/docs/components

11.1 ModalController/LoadingController/ToastController

1. 导入

import {ModalController, LoadingController,  ToastController, ViewController} from 'ionic-angular';

2. 注入

 constructor(
    public loadingCtr: LoadingController,
    public viewCtr: ViewController,
    public toastCtrl: ToastController,
    public ModalCtrl: ModalController 
  ) {
      
 }

3. 使用

// loading使用
let loader = loadingCtr.create({
    content: message, // 加载的内容
    dismissOnPageChange: true 
})
loader.present() // // 触发生效
loading.dismiss() // 关闭loading
 
// toastCtrl使用
let toast = toastCtrl.create({
    message, // 提示的信息
    duration: 3000, // 间隔时间
    position: 'bottom' // top buttom left right
})
toast.present() // 触发生效

// ModalController 

let modal = this.ModalCtrl.create(QuestionPage) // 弹出的页面
modal.present() // 生效
// 关闭后进行父页面刷新
modal.onDidDismiss(()=>{
    // 刷新页面
    this.loadPage()
})


// viewCtr
// 关闭当前页面
this.viewCtr.dismiss()

4. 封装loading, toast

// common/baseui
import { Loading, LoadingController, ToastController, Toast} from 'ionic-angular';

export abstract class BaseUI {
constructor() {

}

protected showLoading(loadingCtr: LoadingController, message: string): Loading {
    let loader = loadingCtr.create({
        content: message,
        dismissOnPageChange: true
    })
    loader.present()
    return loader
}
protected showToast(toastCtrl: ToastController, message: string):Toast {
    let toast = toastCtrl.create({
        message,
        duration: 3000,
        position: 'bottom'
    })
    toast.present()
    return toast
}
}
// 使用
import { Component } from '@angular/core';
import { ModalController, LoadingController,  ToastController} from 'ionic-angular';
import { ApiProvider } from '../../providers/api/api'
import { BaseUI } from '../../common/baseui' // 1.导入封装的组件

@Component({
  selector: 'page-home',
  templateUrl: 'home.html'
})
export class HomePage extends BaseUI { // 2. 继承BaseUI

  feeds: string[];
  errorMessage: string;

  constructor(
    public loadingCtr: LoadingController, // 3. 构造
    public api: ApiProvider,
    public toastCtrl: ToastController,  // 3. 构造
    public ModalCtrl: ModalController 
    ) {
      super() // 调用子类方法
  }
  
  getFeeds() {
    let loading = super.showLoading(this.loadingCtr, '加载中...') // 4. 使用loading
    this.api.getFeeds().subscribe(data=>{
    
      if(data['UserId']) {
        this.feeds = data
        loading.dismiss() // 关闭loading
      }else{
        // 5. 使用toast
        super.showToast(this.toastCtrl, data['StatusContent'])
      }
    },err=>this.errorMessage = <any>err)
  }
}

11.2 Refresh组件

刷新组件

<ion-content>
    <!--刷新组件-->
    <ion-refresher (ionRefresh)="doRefresh($event)">
      <ion-refresher-content
         pullingIcon="arrow-down"
         pullingText="下拉刷新"
         refreshingSpinner="circles"
         refreshingText="数据加载中..."
       >
      </ion-refresher-content>
    </ion-refresher>
    <ion-card *ngFor="let q of questions" (click)="gotoDetails(q.IdentityId)">
        <ion-item>
          <ion-avatar item-start>
            <img src="{{q.HeadFace}}">
          </ion-avatar>
          <p>
              {{q.UserNickName}}发布了该问题
            <ion-icon class="more-button" name="more"></ion-icon>
          </p>
        </ion-item>
        <h2>{{q.ContentTitle}}</h2>
        <ion-card-content>
          <p>{{q.ContentSummary}}</p>
        </ion-card-content>
        <ion-row>
          <ion-col col-8 center text-left>
            <ion-note>
                {{q.LikeCount}}&nbsp;赞同&nbsp;&nbsp;.&nbsp;&nbsp;{{q.CommentCount}}&nbsp;评论&nbsp;&nbsp;.&nbsp;&nbsp;关注
            </ion-note>
          </ion-col>
          <ion-col col-4></ion-col>
        </ion-row>
      </ion-card>
</ion-content>
doRefresh(refresher) {
    this.getQuestions() // 再次请求数据
    refresher.complete() // 停止刷新
}

11.3 List组件

https://ionicframework.com/docs/components/#lists

11.4 button组件

https://ionicframework.com/docs/components/#buttons

11.5 card组件

https://ionicframework.com/docs/components/#cards

<ion-card *ngFor="let f of feeds" (click)="gotoDetails(f.IdentityId)">
    <ion-item>
      <ion-avatar item-start>
        <img src="{{f.HeadFace}}">
      </ion-avatar>
      <p>
          {{f.UserNickName}}回答了该问题
        <ion-icon class="more-button" name="more"></ion-icon>
      </p>
    </ion-item>
    <h2>{{f.ContentTitle}}</h2>
    <ion-card-content>
      <p>{{f.ContentSummary}}</p>
    </ion-card-content>
    <ion-row>
      <ion-col col-8 center text-left>
        <ion-note>
            {{f.LikeCount}}&nbsp;赞同&nbsp;&nbsp;.&nbsp;&nbsp;{{f.CommentCount}}&nbsp;评论&nbsp;&nbsp;.&nbsp;&nbsp;关注
        </ion-note>
      </ion-col>
      <ion-col col-4></ion-col>
    </ion-row>
</ion-card>

11.6 表单组件

https://ionicframework.com/docs/components/#inputs

十二、Ionic打包上线流程

12.1 图标生成

替换resource/icon.png图标为1024*1024

生成图标

第一次生成,需要注册账号才可生成 https://dashboard.ionicframework.com/signup

生成图标的过程可能需要翻墙

image.png
# 在项目根目录执行 不需要进到resources文件夹
ionic cordova resources
image.png

12.2 启动图生成

替换resource/splash.png图标为1024*1024

# 在项目根目录执行 不需要进到resources文件夹
ionic cordova resources

12.3 打包前细节处理

  1. 修改index.html中的title
  2. 修改config.xml 中的信息
  • widget id="io.ionic.starter" version="0.0.1" 修改id(包名)以及version
  • <name>MyApp</name> 修改app名字
  • <description>An awesome Ionic/Cordova app.</description> 修改app描述
  • <author email="hi@ionicframework" href="http://ionicframework.com/">Ionic Framework Team</author> 修改email
image.png

12.4 打包部署

# 将所有的平台打包
sudo ionic build

项目文件夹需要执行权限

# 这样才可以在xcode中打开调试
sudo chomd 777 your_project_name

打开xcode进行ios下的调试

12.5 上架流程

12.5.1 IOS的打包

image.png

app store上架,需要注册开发者账号

image.png

12.5.2 安卓版本打包

最后生成apk文件。

打包方式

  • 通过Android studio生成
  • 通过命令生成

1. 需要安装jdk环境,并设置环境变量

# mac上安装
brew install jdk java

2. 配置Gradle

# 打开 GradleBuilder.js

platforms/android/cordova/lib/builders/GradleBuilder.js

找到distributionUrl

var distributionUrl = process.env['CORDOVA_ANDROID_GRADLE_DISTRIBUTION_URL'] || 'https\\://services.gradle.org/distributions/gradle-4.1-all.zip';
image.png
// 注释
// var distributionUrl = process.env['CORDOVA_ANDROID_GRADLE_DISTRIBUTION_URL'] || 'https\\://services.gradle.org/distributions/gradle-4.1-all.zip';

// 新增
var distributionUrl = process.env['CORDOVA_ANDROID_GRADLE_DISTRIBUTION_URL'] || '../gradle-4.1-all.zip';

3. 配置Gradle的环境变量

第一步

把上一步下载的Gradle把放到任意目录,这里我放的路径/Users/poetry/cordova/gradle-4.1

第二步: 新增环境变量

mac下编辑.bash_profile配置文件, sudo vi ~/.bash_profile。新增一个环境变量,这里的$HOME就是/Users/poetry路径

image.png

第三步: 使环境变量生效

source ~/.bash_profile
  • 如果你的终端使用iTerm,需要加载一条命令
sudo vi ~/.zshrc

# 新增
source .bash_profile
image.png

第四步:新开一个终端

# 执行下面命令看环境变量

echo $PATH
image.png

第五步:测试

gradle -v
image.png

4. debug测试版打包

查看当前打包环境ionic info

Ionic:

   ionic (Ionic CLI)  : 4.7.1
   Ionic Framework    : ionic-angular 3.9.2
   @ionic/app-scripts : 3.2.1

Cordova:

   cordova (Cordova CLI) : 8.1.2 (cordova-lib@8.1.1)
   Cordova Platforms     : android 7.1.4, browser 5.0.4, ios 4.5.5
   Cordova Plugins       : cordova-plugin-ionic-webview 1.2.1, (and 11 other plugins)

System:

   Android SDK Tools : 26.1.1
   NodeJS            : v9.10.0
   npm               : 5.6.0
   OS                : macOS Mojave
   Xcode             : Xcode 10.1 Build version 10B61

注意:需要前面几步环境配置好了,才可以正常执行打包

# 执行这条命令默认打包的是debug版本
cordova build android 

打包后输出apk路径 platforms/android/app/build/outputs/apk/debug/

image.png

生成的apk。此时可以把apk,拖入Genymotion模拟器调试

image.png

5. 正式版本打包

5.1 未签名版本

此时打包的apk是没有签名的版本,不可以在手机上安装

# 生成未签名版
ionic cordova build android  --release
image.png

打包后输出apk路径 platforms/android/app/build/outputs/apk/release

在手机安装示意图,签名版不能安装

image.png

5.2 签名版本apk打包

签名步骤

1. 创建私钥,项目根目录下执行命令(记住设置的别名)

keytool -genkey -v -keystore [自定义秘钥文件名,如 my-app].jks -keyalg RSA -keysize 2048 -validity 36500 -alias [自定义app别名,如 my-alias]
  • -genkey 意味着执行的是生成数字证书操作
  • v 表示将生成证书的详细信息打印出来,显示在dos窗口中
  • -keyalg RSA 表示生成密钥文件所采用的算法为RSA
  • -validity 36500 表示该数字证书的有效期为36500

2. 接下来会让设置秘钥库口令(记住秘钥):

秘钥库就是你的密码

image.png

3. 设置秘钥库口令后会让输入一些APP信息

image.png

4. 按照提示依次输入后会在你的项目根目录生成秘钥文件 my-app.jks

把之前生成好的platforms/android/app/build/outputs/apk/release/app-release-unsigned.apk复制到项目根目录,这样和my-app.jks同一目录签名

jarsigner -verbose -sigalg SHA1withRSA -digestalg SHA1 -keystore [上步生成的xxx.jks] app-release-unsigned.apk [步骤1命令中设置的app别名,如 my-alias]
# 这里是上面的示例:执行签名命令
keytool -genkey -v -keystore my-app.jks -keyalg RSA -keysize 2048 -validity 36500 -alias my-app

5. 验证应用是否签名成功

jarsigner -verify -verbose -certs 你的apk名
# 示例
jarsigner -verify -verbose -certs app-release-unsigned.apk

5. 优化 apk 文件

5.1 首先需要配置环境变量

# 这里以mac下的iTerm2来配置
vi ~/.bash_profile

# 增加一行
export PATH=$PATH:$ANDROID_HOME/build-tools/27.0.3

# 这里其实是安卓的路径,我们需要给zipalign配置环境变量
/Users/poetry/Library/Android/sdk/build-tools/27.0.3/zipalign

# 使得bash_profile生效
source ~/.bash_profile

# 因为使用到iTerm2需要在.zshrc加入.bash_profile
vi ~/.zshrc

# 加入bash_profile
source .bash_profile

# 最后新建窗口echo $PATH就看到环境变量配置了

# 查看是否生效
zipalign -v
echo $PATH

5.2 在项目根目录执行

# 自定义最终生成的apk的名字,如 ionicQa.apk
zipalign -v 4 app-release-unsigned.apk ionicQa.apk

6. 遇到的问题

6.1 无法打开 jar 文件

将 秘钥文件 xxx.jksandroid-release-unsigned.apk 放在同一目录下,放到项目根目录就好了

此时就构建好了应用,这里是构建的应用

12.5.3 网站微信端发布

打包成一个静态站点方便部署

sudo ionic build 

静态网站部署的站点资源路径 platform/browser/www

十三、一些问题记录

13.1 问题

1. 【ionic3】刷新页面,ws中断

解决办法升级@ionic/app-scripts

npm install @ionic/app-scripts@latest --save-dev

13.2 技巧

读取可空对象

question?.ContentTitle question可能返回空

十四、更多参考

14.1 项目学习

https://github.com/poetries/ionic-qa-app

14.2 文档参考

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,029评论 6 492
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,395评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,570评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,535评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,650评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,850评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,006评论 3 408
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,747评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,207评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,536评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,683评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,342评论 4 330
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,964评论 3 315
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,772评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,004评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,401评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,566评论 2 349