написать впереди
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
.
- директива атрибута
- пройти через
Directive
декоратор помечает класс какAngular
Директива, этот параметр предоставляет метаданные конфигурации, которые определяют, как директива должна обрабатываться, создаваться и использоваться во время выполнения.@Directive - пройти через
ElementRef
Получить объект DOM связанного элемента,ElementRef. - пройти через
HostListener
В ответ на события, инициированные пользователем, привяжите событие к прослушивателю хоста и предоставьте метаданные конфигурации. Когда хост-элемент генерирует определенное событие, Angular выполняет предоставленный метод обработчика и обновляет элемент, к которому он привязан, своим результатом. Если обработчик события возвращаетfalse
, выполняется на связанном элементеpreventDefault
.HostListener - пройти через
Input
Декоратор помечает поле класса как входное свойство и предоставляет метаданные конфигурации. Объявите входное свойство, доступное для привязки данных, во время обнаружения изменений,Angular
автоматически обновит его,@Input.
@Input('appHighlight') highlightColor: string;
Ниже приведен пример полной директивы в форме атрибута.
import {Directive, ElementRef, HostListener, Input} from '@angular/core';
@Directive({
selector: '[sxylight]'
})
export class SxylightDirective {
constructor(private el: ElementRef) {
el.nativeElement.style.backgroundColor = 'yellow';
}
// 指令绑定的值
@Input('sxylight') highlightColor: string;
// 在指令内部,该属性叫 highlightColor,在外部,你绑定到它地方,它叫 sxylight 这个是绑定的别名
// 指令宿主绑定的值
@Input() defaultColor: string;
// 监听宿主事件
@HostListener('mouseenter') onMouseEnter() {
this.highlight(this.highlightColor || this.defaultColor || 'red');
}
@HostListener('mouseleave') onMouseLeave() {
this.highlight(null);
}
private highlight(color: string) {
this.el.nativeElement.style.backgroundColor = color;
}
}
- Структурные директивы
- Префикс звездочки (*): на самом деле это синтаксический сахар,
Angular
Пучок*ngIf
атрибут переводится как<ng-template>
элемент и использовать его для переноса основного элемента. -
<ng-template>
: это элемент Angular, используемый для рендеринга HTML. Он никогда не проявляется напрямую. На самом деле перед рендерингом представления Angular заменяет и его содержимое комментарием. -
<ng-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
Соответствующий файл конфигурации отсутствует в , поэтому нам нужно вручную добавить файл конфигурации в соответствии со следующими шагами для настройки проекта.
- новый
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'
}
- добавить в корневой каталог
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
}
-
tsconfig.json
Сотрудничайте, добавьте в конфигурацию следующий контент, это место смешноэтоpath
соответствующие должны быть размещены наtsconfig.json
вершина
"baseUrl": "./src",
"paths": {
"@app/env": [
"environments/environment"
]
}
- Исправлять
package.json
. Добавьте следующее в конце конфигурации
"config": {
"ionic_webpack": "./config/webpack.config.js"
}
- Использовать переменные конфигурации
import {CONFIG} from "@app/env"
Если мы когда-нибудь захотим изменитьIonic
в другомwebpack
конфигурации, то вы можете изменить ее в форме выше.
// 拿到webpack 的默认配置 剩下的还不是为所欲为
const defaultConfig = require('@ionic/app-scripts/config/webpack.config.js');
// 像这样去修改配置
defaultConfig.prod.resolve.alias = {
"@config": path.resolve(configPath('prod'))
}
defaultConfig.dev.resolve.alias = {
"@config": path.resolve(configPath('dev'))
}
Ионная маршрутизация
-
Домашние настройки Иногда нам нужно установить страницу, которую мы показываем в первый раз. Затем нам нужно использовать
NavController
устанавливать// app.component.ts public rootPage: any = StartPage; //
-
прыжок по маршруту
-
href方式跳转
: Укажите страницу, на которую нужно перейти непосредственно в dom.tabs
Пример кода в
<!-- 单个跳转按钮 [root]="HomeRoot" 是最重要的 --> <ion-tab [root]="HomeRoot" tabTitle="Home" tabIcon="home"></ion-tab>
import { HomePage } from '../home/home' export class TabsPage { // 声明变量地址 HomeRoot = HomePage constructor() { } }
- Программная навигация: Программная навигация Мы можем использовать больше, ниже приведен базовый пример.
Программная навигация осуществляется
NavController
контрольNavController — это базовый класс для компонентов навигационного контроллера, таких как Nav и Tab. Вы можете использовать контроллер навигации для перехода к страницам в вашем приложении. На базовом уровне навигационный контроллер представляет собой массив страниц, представляющих определенную историю (например, Tab). Этим массивом можно манипулировать для навигации по приложению, выталкивая и выталкивая страницы или вставляя и удаляя их в любом месте истории. Текущая страница является последней страницей в массиве, вершиной стека, если так думать. Перемещение новой страницы в верхнюю часть стека навигации вызовет анимацию новой страницы, а выталкивание текущей страницы приведет к переходу на предыдущую страницу в стеке.
если вы не используете
NavPush
Такие инструкции или требуют конкретныхNavController
, иначе большую часть времени вы будете вводить и использовать ближайшийNavController
ссылка для управления стеком навигации.// 引入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
-
navCtrl.push(OtherPage, param)
: Перейти на страницу -
navCtrl.pop()
:Removing a view
Удалить текущий вид, что эквивалентно возврату на предыдущую страницу - Параметры, относящиеся к параметрам маршрутизации
-
push(Page, param)
Передача параметров: это очень просто и очень понятно
this.navCtrl.push(NewsPage, { title : '测试传参' })
-
[navParams]
атрибуты: иHTML
Совместная передача параметров
import {LoginPage } from'./login'; @Component() class MyPage { params; pushPage: any; constructor(){ this.pushPage= LoginPage; this.params ={ id:123, name: "Carl" } } }
<button ion-button [navPush]="pushPage" [navParams]="params"> Go </button> <!-- 同理在root page上传递参数就是下面这种方式 --> <ion-tab [root]="tab1Root" tabTitle="home" tabIcon="home" [rootParams]="userInfo"> </ion-tab
- получить параметры
//NavController就是用来管理和导航页面的一个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
, andpinch
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() }
-
高级通信
- мы можем использовать
ionic-angular中的Events
модуль для выполнения父 <=> 子 , 兄 <=> 弟
продвинутые коммуникации.Events
Модули имеют уникальные преимущества в общении. Подробности смотрите в примере выше - использовать
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
Общий модуль, нам нужно знать, что он сделал:
- он импортировал
CommonModule
, потому что этот модуль требует некоторых общих директив. - Он объявляет и экспортирует некоторые служебные конвейеры, директивы и классы компонентов.
- он реэкспортировал
CommonModule
а такжеFormsModule
-
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
Для фронтенд-инженеров это все еще довольно трудоемко. Давайте подробнее рассмотрим эту часть.
Конфигурация среды
Первый шаг — настроить каждую среду
-
Node
Установите/настройте переменные среды (думаю, вы уже это сделали) -
jdk
Установить (не нужно настраивать переменные среды)JDK это
java
поддержка среды разработки, вы можете найти ее вздесьСкачать, извлечь код:9p74
.После завершения загрузки разархивируйте его, установите прямо в соответствии с подсказками, нажмите ОК глобально, не случайно, окончательный путь установки:
C:\Program Files\Java
Установка jdk завершена, в cmd введитеjava -version
Убедитесь, что установка прошла успешно. Здесь я изменил путь установки. Если вы не знакомы с ним, не изменяйте путь установки. Появилось следующееlog
Указывает, что установка прошла успешно
-
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
gradle
Установить/настроить переменные среды
существуетSDK
После завершения установки приступимgradle
установка и настройка.
доОфициальный сайтили вздесьскачать
Затем установите то же самое вJDK,SDK
каталог для удобного поиска.
а такжеSDK
Те же переменные среды конфигурации:
GRADLE_HOME=C:\Program Files\SDK\gradle-4.1
;%GRADLE_HOME%\bin
Тестовая команда (проверить версию):gradle -v
Появится следующий журнал, указывающий, что установка прошла успешно
упаковать
Работа по подготовке среды перед упаковкой завершена, а затем мы упакуем `apk.
- Установить
cordova
npm i cordova -g
- Создать в проекте
Android
машиностроение, вIonic
Выполните следующую команду в проекте
ionic cordova platform add android
Это может быть очень долгий процесс, вам придется терпеливо ждать, ведь перед вами рассвет.
- Создание конца
Android
проект за проектомplatform
Под папкой будет еще одинandroid
папка. Теперь выполните команду пакета.
ionic cordova build android
Затем вы увидите сумасшедший вывод консоли, и, наконец, следующая картинка показывает, что вы запаковали ее.неподписанныйинсталляционный пакет
-
APK
подписатьAPK
Вы не можете публиковать без подписи. Для этого есть два метода
- использовать
jdk
Подпись, здесь особо нечего сказать, если хотите узнать больше, можете прочитатьэта статья - использовать
Android Studio
Нажмите на пакет подписи.
существуетAS
верхняя панель инструментовbuild
ВыбратьGenerate Signed APK
Сначала создайте файл подписи
Можно использовать сразу после генерацииAS
попал в пакет подписи
нажмитеlocate
может видеть нашapk
Упаковано~ Пока что нашAndroid
Ничего, позже добавлю для 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®ionCode=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
Содержимое полностью отображается на хромированной стороне. Здорово иметь возможность управлять веб-страницами в нашем приложении через веб-интерфейс. Ниже приведены шаги операции- открыть в хроме
chrome://inspect/#devices
- Подключенные устройства, вниманиеЕсли вы подключаетесь в первый раз, вам нужна фан-стена, иначе она появится
404
ждать вопросы - Установите приложение, которое необходимо отладить, на подключенном устройстве, а затем
Chrome
Автоматически найдет необходимость отладкиWebView
- Удачного начала отладки
- открыть в хроме
-
использовать
VConsole
отлаживатьЭто еще проще, прямо
npm install vconsole
этой библиотеке, затем вapp.component.ts
Цитироватьimport VConsole from 'vconsole' export class MyApp { constructor() { platform.ready().then(() => { console.log(APP_ENV) // 调试程序 APP_ENV === 'debug' && new VConsole() }) } }
Эффект следующий
Особая часть (яма) в ионическом языке
- Проблема пути статического ресурса
Если есть проблема со статическим путем после упаковки пакета и он не загружается, обратите внимание на следующие ситуации
<!-- 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
отJDK
V1: Должна быть подтверждена записью 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
Для тех, кто заинтересован, я думаю, вы все еще можете попробовать.
Весенний фестиваль скоро, я желаю всем разработчикам счастливого весеннего праздника и держитесь подальше от ошибок~😁😁😁
пример кода позже
исходный адресСтавьте ⭐, если считаете полезным