Важная часть приложения для разработки Ionic

ionic
Важная часть приложения для разработки Ionic

написать впереди

APPОн был запущен до Праздника Весны, поэтому на этот раз мы поделимся использованиемIonic3 + Angular5построитьHybird Appопыт в процессе. чтоHybird AppИ выбор некоторых технологий здесь обсуждаться не будет. Я пишу раздел каждый раз, когда заканчиваю раздел, поэтому некоторые статьи немного длинные. Спасибо, что поправили меня, если я ошибаюсь~

Почему вы выбралиIonic ?

некоторые друзья говорятAngular/IonicНе очень хорошо, но я не думаю, что есть хорошая или плохая технология, только подходит она или нет. Сначала мне кажетсяIonicУже тутHybird AppПоле разработки наработано много лет, и оно достаточно зрелое, я думаю, что оно лучше большинства решений. Во-вторых, поскольку наше приложение слабо взаимодействует с несколькими дисплеями,Ionicудовлетворить наши потребности. Наконец, потому чтоесли хочешь безAndroidкоманда иIOSЗаполните APP самостоятельно при поддержке команды, затемIonicя думаю это лучший выбор. потому чтоIonic4ещеbetaверсия, и это проект компании, поэтому стабильная версия все равно выбрана.3.XВерсия.

Уведомление:Неосновное руководство по началу работыИтак, прежде чем прочитать эту статью, я предлагаю вам лучше понять[Angular](https://www.angular.cn/guide/quickstart), [TS](https://www.tslang.cn/docs/home.html), [Ionic](https://ionicframework.com/docs/)Базовые знания, здесь в основном надеюсь, что все используютIonicКогда можно пойти в обход.

Так как я не очень умею им пользоватьсяRxjsЯ не писал этот кусок, я проверю его позжеRxjsболее глубокое понимание

Угловой сводной раздел

Поскольку он основан на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Слот в , использование такое же:

    <!-- 父组件 -->
    <child-component>
      我是父组件中的内容默认映射过来的
    </child-component>
    <!-- 子组件 -->
    <!-- 插槽 -->
      <ng-content>
        
      </ng-content>
    

    Выше приведен самый простой способ использования сопоставления по умолчанию.

  • Целевое сопоставление (именованные слоты) мы также можем пройтиselectСвойство реализует наш именованный слот. Это может быть заполнено в соответствии с условиями.selectподдержка атрибута в соответствии сCSSСелектор (элемент, класс, [атрибут] ...), чтобы соответствовать вашему элементу, если не установлено, чтобы принять все, подобное это:

    <!-- 父组件 -->
    <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-container

  • ng-template

    Элементы — лучший выбор для динамической загрузки компонентов, потому что этоне будет отображать дополнительный вывод

    <div class="ad-banner-example">
      <h3>Advertisements</h3>
      <ng-template ad-host></ng-template>
    </div>
    
  • 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Инструкции в подразделяются на组件,属性指令а также结构形指令.属性型指令для измененияDOMВнешний вид или поведение элемента, напримерNgStyle.结构型指令обязанностиHTMLмакет. они формируют или изменяют формуDOMструктуры, такие как добавление, удаление или сохранение этих элементов, таких какNgForа такжеNgIf.

  1. директива атрибута
  • пройти через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;
  }
}
  1. Структурные директивы
  • Префикс звездочки (*): на самом деле это синтаксический сахар,AngularПучок*ngIfатрибут переводится как<ng-template>элемент и использовать его для переноса основного элемента.
  • <ng-template>: это элемент Angular, используемый для рендеринга HTML. Он никогда не проявляется напрямую. На самом деле перед рендерингом представления Angular заменяет и его содержимое комментарием.
  • <ng-container>: это группирующий элемент, но он не загрязняет стили или макет элемента, потому чтоAngularникак не вставлюDOMсередина.
  • TemplateRef: можно использоватьTemplateRefполучать<ng-template>Содержание,TemplateRef
  • 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

Сначала давайте посмотрим наNgModule

interface 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.ts
app.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Официальный сайтпредставлять

  • 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('genre*sold').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Соответствующий файл конфигурации отсутствует в , поэтому нам нужно вручную добавить файл конфигурации в соответствии со следующими шагами для настройки проекта.

  1. новыйconfigсодержание
src
  |__config
      |__config.dev.ts
      |__config.prod.ts

config.dev.ts / config.prod.ts

export const CONFIG = {
  BASE_URL            : 'http://XXXXX/api', // API地址
  VERSION             : '1.0.0'
}
  1. добавить в корневой каталогbuildпапка, добавить в папкуwebpack.config.jsфайл конфигурации
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))
  }
}
// 删除sourceMaps

module.exports = function () {
  return defaultConfig
}
  1. tsconfig.jsonСотрудничайте, добавьте в конфигурацию следующий контент, это место смешноэтоpathсоответствующие должны быть размещены наtsconfig.jsonвершина
"baseUrl": "./src",
  "paths": {
    "@app/env": [
      "environments/environment"
    ]
  }
  1. Исправлятьpackage.json. Добавьте следующее в конце конфигурации
"config": {
  "ionic_webpack": "./config/webpack.config.js"
}
  1. Использовать переменные конфигурации
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'))
}

Ионная маршрутизация

  • Домашние настройки Иногда нам нужно установить страницу, которую мы показываем в первый раз. Затем нам нужно использоватьNavControllerустанавливать

    // app.component.ts
    public rootPage: any = StartPage; // 
    
  • прыжок по маршруту

    1. 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() {
        
      }
    }
    
    1. Программная навигация: Программная навигация Мы можем использовать больше, ниже приведен базовый пример.

    Программная навигация осуществляетсяNavControllerконтроль

    NavController — это базовый класс для компонентов навигационного контроллера, таких как Nav и Tab. Вы можете использовать контроллер навигации для перехода к страницам в вашем приложении. На базовом уровне навигационный контроллер представляет собой массив страниц, представляющих определенную историю (например, Tab). Этим массивом можно манипулировать для навигации по приложению, выталкивая и выталкивая страницы или вставляя и удаляя их в любом месте истории. Текущая страница является последней страницей в массиве, вершиной стека, если так думать. Перемещение новой страницы в верхнюю часть стека навигации вызовет анимацию новой страницы, а выталкивание текущей страницы приведет к переходу на предыдущую страницу в стеке.

    если вы не используетеNavPushТакие инструкции или требуют конкретныхNavController, иначе большую часть времени вы будете вводить и использовать ближайшийNavControllerссылка для управления стеком навигации.

    // 引入NavController
    import { NavController } from 'ionic-angular';
    import { NewsPage } from '../news/news'
    export class HomePage {
      // 注入NavController
    constructor(public navCtrl: NavController) {
      // this.navCtrl.push(LoginPage)
    }
    goNews () {
        this.navCtrl.push(NewsPage, {
          title : '测试传参'
        })
      }
    }
    
  • Связанные общие API

    1. navCtrl.push(OtherPage, param): Перейти на страницу
    2. navCtrl.pop(): Removing a viewУдалить текущий вид, что эквивалентно возврату на предыдущую страницу
    3. Параметры, относящиеся к параметрам маршрутизации
    • 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就是用来管理和导航页面的一个controller
    constructor(public navCtrl: NavController, public navParams: NavParams) {
      //1: 通过NavParams get方法获取到单个对象
      this.titleName = navParams.get('name')
      //2: 直接获取所有的参数
      this.para = navParams.data
    }
    

использование провайдером(услугой)

Когда метод в классе требуется повторно, его можно инкапсулировать как класс службы для многократного использования, например http.

provider,Также известен какservice.前者是ionic, последнийngимя. Рекомендуется внимательно изучитьAngular

  • СоздайтеProvider IonicПредоставляются инструкции по созданию
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)
  }
}

Ионная система событий

События — это发布-订阅Система событий стиля для отправки и ответа на события уровня приложения в вашем приложении.

Это ядро ​​связи между разными страницами. В основном используется для компонентной связи. Вы также можете использоватьeventsПередача данных на любую страницу.

Eventsметод экземпляра

  • publish(topic, eventData): опубликоватьevent
  • subscribe(topic, handler): Подпишитесь наevent
  • unsubscribe(topic, handler)отписаться одинevent
// 发布event login.ts
// 发布event事件
submitEvent (data) {
  console.log(1)
  this.event.publish('user:login', data)
}
// 订阅页面  message.ts
constructor(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)
  })
}

будь осторожен:1: Подписка должна быть опубликована раньше, иначе она не будет получена. Если использовать аналогию: например, в общедоступной учетной записи WeChat вы должны подписаться на нее, прежде чем сможете получать ее твиты, иначе вы не сможете получать ее независимо от того, сколько она твитит. 2:subscribeпобедитьthisНаведение немного проблематично, тут нужно быть внимательным.

Событие действия пользователя

Basic gestures can be accessed from HTML by binding to tap, press, pan, swipe, rotate, and pinch events.

Объяснение Ionic событий жестов, по сути, всплеск.

Связь между компонентами

Связь между компонентами: пришло время дать компонентную структуру для игры 6. Обязательным условием является то, что коммуникация перед компонентом понята. существуетIonic, мы используемAngularспособ достижения.

  • 父 => 子:@Input()

    • пройти через输入型绑定Данные, передаваемые из подсборки родительского компонента: это наиболее широко используемый и распространенный вариант.recatРеквизит очень похож
    // 父组件定义值(用来传递)
    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 value
    set showContent(name: string) {
      if (name !== '4') {
        this._showContent = 'no'
      } else {
        this._showContent = name
      }
    }
    // get value
    get 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)
  }
  • 父 <=> 子:Родительский и дочерний компоненты взаимодействуют через службы

    если мы把一个服务实例的作用域被限制在父组件和其子组件内,这个组件子树之外的组件将无法访问该服务或者与它们通讯. Отец и сын совместно используют услугу, тогда мы можем использовать эту услугу всемейный интерьервыполнитьдвусторонняя связь.

    // service
    import { Injectable } from '@angular/core'; // 标记元数据
    // 使用service进行父子组件的双向交流
    @Injectable()
    export class MissionService {
      familyData: string = 'I am family data'
    }
    
    // father component
    import { MissionService } from './../../service/mission.service';
    export class NewsPage {
      constructor( public missionService: MissionService) {
      }
      ionViewDidLoad() {
        // 父组件数据更改
        setTimeout(() => {
          // 调用修改service中的数据 这个时候父子组件中的service都会改变
          this.missionService.familyData = 'change familyData'
        }, 2000)
      }
    }
    // child component
    import { 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.ts
    import { 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()
    }
    
  • 高级通信

    1. мы можем использоватьionic-angular中的Eventsмодуль для выполнения父 <=> 子 , 兄 <=> 弟продвинутые коммуникации.EventsМодули имеют уникальные преимущества в общении. Подробности смотрите в примере выше
    2. использоватьEventEmitterмодуль
    // service
    import { 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')
    }
    
    

Общие компоненты

настройки публичного компонента,AngularРекомендуется модульная разработка, поэтому регистрация общедоступных компонентов может немного отличаться.

Здесь мы базируемсяAngularкоторый предоставилCommonModuleОбщий модуль, нам нужно знать, что он сделал:

  1. он импортировалCommonModule, потому что этот модуль требует некоторых общих директив.
  2. Он объявляет и экспортирует некоторые служебные конвейеры, директивы и классы компонентов.
  3. он реэкспортировалCommonModuleа такжеFormsModule
  4. CommonModuleа такжеFormsModuleможет заменитьBrowserModuleиспользовать
  • определение существуетsharedновая папкаshared.module.ts
import { 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 part.

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('&')}`
  }
  /**
   * 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 -D
npm 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В конце концов, это гибридное приложение, ведь оно еще не достигло уровня открытия в секундах. Итак, в настоящее время нам нужна стартовая страница, чтобы помочь нам улучшить взаимодействие с пользователем, в первую очередь.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).
    • Обфускация: переписывание кода с короткими бессмысленными именами переменных и функций.
    • Удалите мертвый код: удалите неиспользуемые модули и неиспользуемый код.

Упаковка приложения

я думаю упаковкаAPKДля тех, кто не разбирается в сервере иAndroidДля фронтенд-инженеров это все еще довольно трудоемко. Давайте подробнее рассмотрим эту часть.

Конфигурация среды

Первый шаг — настроить каждую среду

  1. NodeУстановите/настройте переменные среды (думаю, вы уже это сделали)

  2. jdkУстановить (не нужно настраивать переменные среды)

    JDK этоjavaподдержка среды разработки, вы можете найти ее вздесьСкачать, извлечь код:9p74.

    После завершения загрузки разархивируйте его, установите прямо в соответствии с подсказками, нажмите ОК глобально, не случайно, окончательный путь установки:C:\Program Files\JavaУстановка jdk завершена, в cmd введитеjava -versionУбедитесь, что установка прошла успешно. Здесь я изменил путь установки. Если вы не знакомы с ним, не изменяйте путь установки. Появилось следующееlogУказывает, что установка прошла успешно

  1. SDKУстановка / настроить переменные среды: Эта часть является ключевым моментом, немного более хлопотным.

Первыйскачать.

Папка, которая будет переименована после распаковки, а затемjdkПоместите его в родительский каталог для легкого поиска:C:\Program Files\SDK

Затем настройте переменные среды,Мой компьютер — щелкните правой кнопкой мыши «Свойства» — «Дополнительные параметры системы» — «Переменные среды»..

В следующих **системных переменных** создайте новую пару "ключ-значение" следующим образом:

name: ANDROID_HOME
key: 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 platform

  1. gradleУстановить/настроить переменные среды

существуетSDKПосле завершения установки приступимgradleустановка и настройка.

доОфициальный сайтили вздесьскачать

Затем установите то же самое вJDK,SDKкаталог для удобного поиска. а такжеSDKТе же переменные среды конфигурации:

GRADLE_HOME=C:\Program Files\SDK\gradle-4.1
;%GRADLE_HOME%\bin

Тестовая команда (проверить версию):gradle -vПоявится следующий журнал, указывающий, что установка прошла успешно

упаковать

Работа по подготовке среды перед упаковкой завершена, а затем мы упакуем `apk.

  1. Установитьcordova
npm i cordova -g
  1. Создать в проектеAndroidмашиностроение, вIonicВыполните следующую команду в проекте
ionic cordova platform add android

Это может быть очень долгий процесс, вам придется терпеливо ждать, ведь перед вами рассвет.

  1. Создание концаAndroidпроект за проектомplatformПод папкой будет еще одинandroidпапка. Теперь выполните команду пакета.
ionic cordova build android

Затем вы увидите сумасшедший вывод консоли, и, наконец, следующая картинка показывает, что вы запаковали ее.неподписанныйинсталляционный пакет

  1. APKподписатьAPKВы не можете публиковать без подписи. Для этого есть два метода
  • использоватьjdkПодпись, здесь особо нечего сказать, если хотите узнать больше, можете прочитатьэта статья
  • использоватьAndroid StudioНажмите на пакет подписи.

существуетASверхняя панель инструментовbuildВыбратьGenerate Signed APKСначала создайте файл подписи

Можно использовать сразу после генерацииASпопал в пакет подписи

нажмитеlocateможет видеть нашapkУпаковано~ Пока что нашAndroidНичего, позже добавлю для IOS.

Простое обновление сервера приложений (простой пример)

из-заAndroidТребования не такие строгие, как у Apple, и мы также можем обновлять программу через собственный сервер. Ниже приведено относительно простое обновление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: используется для получения номера версии приложения
  • cordova-plugin-file / @ionic-native/file: Работа с файловой системой в приложении.
  • cordova-plugin-device / @ionic-native/device: Получить текущую информацию об устройстве, в основном используемую для определения платформы.

После загрузки плагина давайте реализуемотносительно простойобновление версииservice, я напишу конкретное объяснение в комментариях к коду, в основном разделенных на две части, одна часть - это конкретная операция обновления.update.service.ts, другая часть используется для хранения данныхdata.service.ts data.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", () => {
      // 当前运行平台
      this.currentSystem = this.device.platform
      // console.log(this.device.platform)
      // app版本相关信息
      this.app.getVersionNumber().then(data => {
        //当前app版本号  data,存储该版本号
        if (this.currentSystem) {
          // console.log(data)
          this.appCurrentVersion = data
        }
      }, error => console.error(error))
      this.app.getVersionCode().then((data) => {
        //当前app版本号数字代码 
        if (this.currentSystem) {
          this.appCurrentVersionCode = Number(data)
        }
      }, error => console.error(error))
      // app 包名
      this.app.getPackageName().then(data => {
          //当前应用的packageName:data,存储该包名
          if (this.currentSystem) {
            this.packageName = data;
          }
      }, error => 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 => 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 > currentVersion
  }
  /**
   * 查询是否有可更新程序
   * @param noUpdateShow  没有更新时显示提醒
   */
  public checkForUpdate (noUpdateShow: boolean = true) {
    // 拦截平台
    return new Promise((reslove, reject) => {
      // http://appupdate.ymhy.net.cn/appupdate/app/findAppInfo?appName=xcz&regionCode=370000
      // 查询app更新
      this.httpService.get(this.CONFIG.CHECK_URL, {}, true).then((result: any) => {
        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, ()=> {
              this.downLoadAppPackage(data.downloadPath)
            }, ()=> {
              console.log('取消');
            })
          } else {
            popObj.content = '已是最新版本!'
            if(!noUpdateShow) {
              this.popService.confirmDIY(popObj, data.isMust === '1' ? true: false)
            }
          }
        } else {
          // 接口响应出现问题 直接提醒默认最新版本
          if(!noUpdateShow) {
            this.popService.alert('版本更新', '已是最新版本!')
          }
        }
        }).catch((err) => {
          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&url=' + url;
      } else {
        const fileTransfer: FileTransferObject = this.transfer.create();
        fileTransfer.onProgress(progress =>{
          // 展示下载进度
          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()

Отладка приложения на реальной машине

Сказать правду,HybirdРеальная отладка машины действительно болезненна. В настоящее время более популярными методами являются следующие два метода отладки.

  • Chrome Inspectотладка полагаться наchromeмощная способность, мы можем поставитьAppсерединаWebViewСодержимое полностью отображается на хромированной стороне. Здорово иметь возможность управлять веб-страницами в нашем приложении через веб-интерфейс. Ниже приведены шаги операции

    1. открыть в хромеchrome://inspect/#devices
    2. Подключенные устройства, вниманиеЕсли вы подключаетесь в первый раз, вам нужна фан-стена, иначе она появится404ждать вопросы
    3. Установите приложение, которое необходимо отладить, на подключенном устройстве, а затемChromeАвтоматически найдет необходимость отладкиWebView
    4. Удачного начала отладки
  • использовать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()
        })
      }
    }
    

    Эффект следующий

Особая часть (яма) в ионическом языке

  • Проблема пути статического ресурса

Если есть проблема со статическим путем после упаковки пакета и он не загружается, обратите внимание на следующие ситуации

<!-- 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版本修改Версия SDK по умолчанию в Ionic слишком высока. Некоторые машины с более низкими версиями не были установлены и должны быть изменены. Есть следующие части:
<!-- 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.

Также доступно в каталоге приложенийbuild.gradleнастроить в

signingConfigs {
    debug {
        v1SigningEnabled true
        v2SigningEnabled true
    }
    release {
        v1SigningEnabled true
        v2SigningEnabled true
    }
}

Суммировать

После стольких метаний остается много ям. Но все они были решены. использоватьIonicСамое большое чувство этоTS+AngularМодульная модель разработки очень удобна. И скорость разработки не слишком медленная, даAngularДля тех, кто заинтересован, я думаю, вы все еще можете попробовать.

Весенний фестиваль скоро, я желаю всем разработчикам счастливого весеннего праздника и держитесь подальше от ошибок~😁😁😁

пример кода позже

исходный адресСтавьте ⭐, если считаете полезным