# 一、介绍
Ionic是一款基于Angular、Cordova的强大的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的全部版本信息)

ionic start myApp tabs # 建议使用初始化
cd myApp 
ionic serve

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文件夹,点击运行项目

# 2.4 在安卓下体验
1. 添加android
ionic cordova platform add android
# 注意获取目录权限的问题
chmod -R 777 项目文件夹名
# 直接使用Android studio 进行调试链接
# 打包成apk拖入Genymotion调试
2. 下载android studio 打开/platform/andriod文件

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

# 2.5 在浏览器/微信下体验
1. 添加browser文件夹
ionic cordova platform add browser
2. 打包
ionic cordova build browser
 
3. 运行
npm run serve
# 浏览器打开 http://localhost:8100
4. 部署
把
www目录部署到服务器上即可
在微信下体验 注意微信
title问题
# 2.6 ionic的常用命令
1. 基本命令
ionic g page myPage创建页面ionic g provider MyData创建providerionic 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. 整体目录结构

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目录

app:应用根目录assets:资源目录(静态文件(图片,js 框架)pages:页面文件,放置编写的页面文件,包括:html,scss,tstheme:主题文件,里面有一个scss文件,设置主题信息。
3. Ionic3.x src 下面文件分析

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 创建组件
cd到我们的项目目录- 通过 
ionic g component组件名称创建组件 
输入
ionic g后,可以创建的组件如下

- 创建完成组件以后会在 
src目录下面多一个components的目录,这个目录里面有我们用命令创建的所有的组件 

- 如果我们要使用这些组件必须在 
app.module.ts里面注册我们的模块,注册完成后就可以在pages里面的其页面里面使用这些组件 
 
# 3.3 创建页面以及页面跳转
1. 创建页面
ionic g page news
2. 跳转
<button ion-button (click)="pushButton">执行button跳转</button>

# 四、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}} {{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>

# 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中设置

然后在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启动的时候设置夜间或者白天模式

# 九、组件化开发-自定义组件
# 9.1 自定义组件
1. 新建组件
# 新建组件
ionic g component emojipicker# 组件名称

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. 使用
在页面中使用即可

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}} 赞同  .  {{q.CommentCount}} 评论  .  关注
            </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}} 赞同  .  {{f.CommentCount}} 评论  .  关注
        </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
生成图标的过程可能需要翻墙

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

# 12.2 启动图生成
替换resource/splash.png图标为1024*1024
# 在项目根目录执行 不需要进到resources文件夹
ionic cordova resources
# 12.3 打包前细节处理
- 修改index.html中的title
 - 修改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

# 12.4 打包部署
# 将所有的平台打包
sudo ionic build
项目文件夹需要执行权限
# 这样才可以在xcode中打开调试
sudo chomd 777 your_project_name
打开
xcode进行ios下的调试
# 12.5 上架流程
# 12.5.1 IOS的打包

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

# 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';
- 把这个资源下载下来(具体下载哪个版本,根据
 distributionUrl来下载),放到platforms/android/gradle- 下载地址:https://services.gradle.org/distributions/gradle-4.1-all.zip
 

// 注释
// 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路径

第三步: 使环境变量生效
source ~/.bash_profile
- 如果你的终端使用
iTerm,需要加载一条命令 
sudo vi ~/.zshrc
# 新增
source .bash_profile

第四步:新开一个终端
# 执行下面命令看环境变量
echo $PATH

第五步:测试
gradle -v

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/

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

5. 正式版本打包
5.1 未签名版本
此时打包的
apk是没有签名的版本,不可以在手机上安装
# 生成未签名版
ionic cordova build android  --release

打包后输出
apk路径platforms/android/app/build/outputs/apk/release
在手机安装示意图,签名版不能安装

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. 接下来会让设置秘钥库口令(记住秘钥):
秘钥库就是你的密码

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

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

5.2 在项目根目录执行
# 自定义最终生成的apk的名字,如 ionicQa.apk
zipalign -v 4 app-release-unsigned.apk ionicQa.apk
6. 遇到的问题
6.1 无法打开 jar 文件
将 秘钥文件
xxx.jks与android-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?.ContentTitlequestion可能返回空
# 十四、更多参考
# 14.1 项目学习
https://github.com/poetries/ionic-qa-app