活动公告

系统通知
05-18 21:22
系统通知
通知:本站资源由网友上传分享,如有违规等问题请到版务模块进行投诉,资源失效请在帖子内回复要求补档,会尽快处理!
10-23 09:31

Ionic 4入门教程视频零基础学习跨平台移动应用开发从安装到项目实战

SunJu_FaceMall

3万

主题

2860

科技点

3万

积分

白金月票

碾压王

积分
32872

塔罗立华奏

<font color=白金月票" /> 发表于 2025-9-29 02:40:02 | 显示全部楼层 |阅读模式

马上注册,结交更多好友,享用更多功能,让你轻松玩转社区。

您需要 登录 才可以下载或查看,没有账号?立即注册

x
1. Ionic 4简介

Ionic是一个开源的移动应用开发框架,允许开发者使用Web技术(HTML、CSS和JavaScript)构建跨平台的移动应用。Ionic 4是这个框架的一个重要版本,它带来了许多改进和新特性。

1.1 Ionic 4的特点

• 基于Web Components:Ionic 4完全基于Web Components标准,使其更加模块化和可重用。
• 框架无关:Ionic 4不再仅限于Angular,支持与React、Vue等主流前端框架集成。
• 性能优化:相比之前的版本,Ionic 4在性能方面有显著提升。
• 改进的UI组件:提供了更丰富、更美观的UI组件库。
• 更好的工具支持:增强的CLI工具和开发体验。

1.2 跨平台开发的优势

使用Ionic 4进行跨平台开发有以下优势:

• 一次编写,多平台运行:同一套代码可以部署到iOS、Android和Web平台。
• 降低开发成本:无需为每个平台单独维护代码库。
• 快速开发:利用Web技术栈,大多数前端开发者可以快速上手。
• 丰富的生态系统:可以访问大量的Cordova和Capacitor插件,调用原生设备功能。

2. 开发环境安装

在开始Ionic 4开发之前,我们需要安装和配置必要的开发环境。

2.1 安装Node.js和npm

Ionic 4需要Node.js环境。请访问Node.js官网下载并安装最新的LTS版本。

安装完成后,打开终端或命令提示符,验证安装:
  1. node -v
  2. npm -v
复制代码

2.2 安装Ionic CLI

Ionic CLI是开发Ionic应用的命令行工具。使用npm全局安装:
  1. npm install -g @ionic/cli
复制代码

验证安装:
  1. ionic -v
复制代码

2.3 安装其他必要工具

Cordova和Capacitor是Ionic的运行时环境,用于访问原生设备功能。Ionic 4默认使用Capacitor,但也支持Cordova。
  1. # 安装Capacitor
  2. npm install -g @capacitor/cli @capacitor/core
  3. # 或安装Cordova
  4. npm install -g cordova
复制代码

推荐使用Visual Studio Code作为开发环境,它提供了丰富的插件支持:
  1. # 安装VS Code(根据操作系统选择相应的安装方式)
  2. # Windows: 下载并运行安装程序
  3. # Mac: 使用Homebrew安装
  4. brew cask install visual-studio-code
  5. # Linux: 根据发行版选择相应的安装方式
复制代码

安装VS Code后,推荐安装以下插件:

• Ionic Snippets
• Angular Essentials
• TSLint
• Prettier - Code formatter

3. 创建第一个Ionic 4项目

现在我们已经安装了必要的工具,让我们创建第一个Ionic 4项目。

3.1 使用Ionic CLI创建项目

在终端中运行以下命令:
  1. ionic start myFirstApp blank
复制代码

这个命令会创建一个名为”myFirstApp”的新项目,使用”blank”模板。Ionic提供了多种模板:

• blank:空模板,适合从零开始
• tabs:带有标签页的模板
• sidemenu:带有侧边菜单的模板

创建过程中,CLI会询问一些问题:

• 是否集成Angular框架?(选择Yes)
• 是否使用Capacitor?(选择Yes)

3.2 项目结构解析

创建完成后,进入项目目录:
  1. cd myFirstApp
复制代码

查看项目结构:
  1. myFirstApp/
  2. ├── e2e/                 # 端到端测试文件
  3. ├── node_modules/        # 依赖包
  4. ├── src/                 # 源代码
  5. │   ├── app/             # 应用根目录
  6. │   │   ├── app.module.ts    # 应用根模块
  7. │   │   └── app-routing.module.ts  # 路由配置
  8. │   ├── assets/          # 静态资源
  9. │   ├── environments/    # 环境配置
  10. │   ├── theme/           # 主题文件
  11. │   └── pages/           # 页面组件
  12. ├── www/                 # 构建输出目录
  13. ├── angular.json         # Angular配置文件
  14. ├── capacitor.config.json # Capacitor配置文件
  15. ├── ionic.config.json    # Ionic配置文件
  16. ├── package.json         # 项目依赖和脚本
  17. ├── tsconfig.json        # TypeScript配置
  18. └── tslint.json          # TSLint配置
复制代码

3.3 运行应用

在浏览器中运行应用:
  1. ionic serve
复制代码

这个命令会启动开发服务器,并在默认浏览器中打开应用。应用会自动监听文件变化并刷新。

4. Ionic 4基本组件和布局

Ionic 4提供了丰富的UI组件,让我们可以快速构建美观的移动应用界面。

4.1 基本布局组件

IonContent是应用的主要内容区域,通常包含可滚动的内容。
  1. <ion-content>
  2.   <p>这里是应用的主要内容</p>
  3. </ion-content>
复制代码

IonHeader是应用的固定头部,通常包含IonToolbar。
  1. <ion-header>
  2.   <ion-toolbar>
  3.     <ion-title>我的应用</ion-title>
  4.   </ion-toolbar>
  5. </ion-header>
复制代码

IonFooter是应用的固定底部。
  1. <ion-footer>
  2.   <ion-toolbar>
  3.     <ion-title>底部信息</ion-title>
  4.   </ion-toolbar>
  5. </ion-footer>
复制代码

4.2 常用UI组件

按钮组件,支持多种样式和功能。
  1. <!-- 基本按钮 -->
  2. <ion-button>默认按钮</ion-button>
  3. <!-- 不同颜色的按钮 -->
  4. <ion-button color="primary">主要按钮</ion-button>
  5. <ion-button color="secondary">次要按钮</ion-button>
  6. <ion-button color="danger">危险按钮</ion-button>
  7. <!-- 不同大小的按钮 -->
  8. <ion-button size="small">小按钮</ion-button>
  9. <ion-button size="large">大按钮</ion-button>
  10. <!-- 块级按钮 -->
  11. <ion-button expand="block">块级按钮</ion-button>
  12. <!-- 圆形按钮 -->
  13. <ion-button shape="round">圆形按钮</ion-button>
  14. <!-- 带图标的按钮 -->
  15. <ion-button>
  16.   <ion-icon slot="start" name="star"></ion-icon>
  17.   收藏
  18. </ion-button>
复制代码

列表组件,用于显示数据列表。
  1. <ion-list>
  2.   <ion-item>
  3.     <ion-label>项目1</ion-label>
  4.   </ion-item>
  5.   <ion-item>
  6.     <ion-label>项目2</ion-label>
  7.   </ion-item>
  8.   <ion-item>
  9.     <ion-label>项目3</ion-label>
  10.   </ion-item>
  11. </ion-list>
复制代码

卡片组件,用于展示相关信息的集合。
  1. <ion-card>
  2.   <ion-card-header>
  3.     <ion-card-subtitle>卡片副标题</ion-card-subtitle>
  4.     <ion-card-title>卡片标题</ion-card-title>
  5.   </ion-card-header>
  6.   <ion-card-content>
  7.     这里是卡片的内容,可以包含文本、图片等任何内容。
  8.   </ion-card-content>
  9. </ion-card>
复制代码

网格系统,用于创建响应式布局。
  1. <ion-grid>
  2.   <ion-row>
  3.     <ion-col>
  4.       列1
  5.     </ion-col>
  6.     <ion-col>
  7.       列2
  8.     </ion-col>
  9.   </ion-row>
  10.   <ion-row>
  11.     <ion-col size="8">
  12.       宽度为8的列
  13.     </ion-col>
  14.     <ion-col size="4">
  15.       宽度为4的列
  16.     </ion-col>
  17.   </ion-row>
  18. </ion-grid>
复制代码

4.3 表单组件

输入框组件,用于接收用户输入。
  1. <ion-item>
  2.   <ion-label position="floating">用户名</ion-label>
  3.   <ion-input type="text" [(ngModel)]="username"></ion-input>
  4. </ion-item>
复制代码

多行文本输入组件。
  1. <ion-item>
  2.   <ion-label position="floating">描述</ion-label>
  3.   <ion-textarea [(ngModel)]="description"></ion-textarea>
  4. </ion-item>
复制代码

选择器组件,用于从多个选项中选择一个或多个值。
  1. <ion-item>
  2.   <ion-label>性别</ion-label>
  3.   <ion-select [(ngModel)]="gender">
  4.     <ion-select-option value="male">男</ion-select-option>
  5.     <ion-select-option value="female">女</ion-select-option>
  6.     <ion-select-option value="other">其他</ion-select-option>
  7.   </ion-select>
  8. </ion-item>
复制代码

开关组件,用于切换开关状态。
  1. <ion-item>
  2.   <ion-label>启用通知</ion-label>
  3.   <ion-toggle [(ngModel)]="enableNotifications"></ion-toggle>
  4. </ion-item>
复制代码

复选框组件。
  1. <ion-item>
  2.   <ion-label>记住我</ion-label>
  3.   <ion-checkbox [(ngModel)]="rememberMe"></ion-checkbox>
  4. </ion-item>
复制代码

5. 页面导航和路由

在Ionic 4应用中,页面导航和路由是非常重要的部分。Ionic 4使用Angular的路由系统来实现页面之间的导航。

5.1 创建新页面

使用Ionic CLI创建新页面:
  1. ionic generate page pages/about
复制代码

这个命令会在src/app/pages目录下创建一个名为about的新页面,包含以下文件:

• about.page.html:页面的HTML模板
• about.page.scss:页面的样式文件
• about.page.ts:页面的TypeScript逻辑文件
• about.module.ts:页面的模块文件

5.2 配置路由

在Ionic 4中,路由配置通常在app-routing.module.ts文件中进行。
  1. import { NgModule } from '@angular/core';
  2. import { PreloadAllModules, RouterModule, Routes } from '@angular/router';
  3. const routes: Routes = [
  4.   {
  5.     path: '',
  6.     redirectTo: 'home',
  7.     pathMatch: 'full'
  8.   },
  9.   {
  10.     path: 'home',
  11.     loadChildren: () => import('./pages/home/home.module').then(m => m.HomePageModule)
  12.   },
  13.   {
  14.     path: 'about',
  15.     loadChildren: () => import('./pages/about/about.module').then(m => m.AboutPageModule)
  16.   }
  17. ];
  18. @NgModule({
  19.   imports: [
  20.     RouterModule.forRoot(routes, { preloadingStrategy: PreloadAllModules })
  21.   ],
  22.   exports: [RouterModule]
  23. })
  24. export class AppRoutingModule { }
复制代码

5.3 页面导航

在组件中注入Router服务,并使用它进行导航:
  1. import { Component } from '@angular/core';
  2. import { Router } from '@angular/router';
  3. @Component({
  4.   selector: 'app-home',
  5.   templateUrl: 'home.page.html',
  6.   styleUrls: ['home.page.scss'],
  7. })
  8. export class HomePage {
  9.   constructor(private router: Router) {}
  10.   navigateToAbout() {
  11.     this.router.navigate(['/about']);
  12.   }
  13. }
复制代码

在HTML模板中:
  1. <ion-button (click)="navigateToAbout()">关于我们</ion-button>
复制代码

Ionic 4提供了NavController服务,用于更高级的导航控制:
  1. import { Component } from '@angular/core';
  2. import { NavController } from '@ionic/angular';
  3. @Component({
  4.   selector: 'app-home',
  5.   templateUrl: 'home.page.html',
  6.   styleUrls: ['home.page.scss'],
  7. })
  8. export class HomePage {
  9.   constructor(private navCtrl: NavController) {}
  10.   navigateToAbout() {
  11.     this.navCtrl.navigateForward('/about');
  12.   }
  13.   goBack() {
  14.     this.navCtrl.navigateBack('/home');
  15.   }
  16.   goToRoot() {
  17.     this.navCtrl.navigateRoot('/home');
  18.   }
  19. }
复制代码

在导航时传递参数:
  1. // 发送页面
  2. navigateToDetails(id: number) {
  3.   this.router.navigate(['/details', { id: id }]);
  4. }
复制代码

在路由配置中:
  1. {
  2.   path: 'details/:id',
  3.   loadChildren: () => import('./pages/details/details.module').then(m => m.DetailsPageModule)
  4. }
复制代码

接收参数:
  1. import { Component, OnInit } from '@angular/core';
  2. import { ActivatedRoute } from '@angular/router';
  3. @Component({
  4.   selector: 'app-details',
  5.   templateUrl: './details.page.html',
  6.   styleUrls: ['./details.page.scss'],
  7. })
  8. export class DetailsPage implements OnInit {
  9.   id: number;
  10.   constructor(private route: ActivatedRoute) { }
  11.   ngOnInit() {
  12.     this.id = this.route.snapshot.params['id'];
  13.     // 或者使用订阅方式
  14.     this.route.params.subscribe(params => {
  15.       this.id = params['id'];
  16.     });
  17.   }
  18. }
复制代码

5.4 导航堆栈

Ionic 4维护了一个导航堆栈,用于跟踪用户的导航历史。可以使用NavController来管理这个堆栈:
  1. // 获取当前导航堆栈
  2. const stack = this.navCtrl.getCurrentNavigation();
  3. // 获取导航堆栈中的页面数量
  4. const stackLength = this.navCtrl.getLength();
  5. // 移除导航堆栈中的页面
  6. this.navCtrl.pop();
复制代码

6. 数据管理和状态

在Ionic 4应用中,数据管理和状态处理是非常重要的部分。本节将介绍如何在Ionic 4应用中管理和共享数据。

6.1 使用服务共享数据

创建一个服务来管理应用的数据:
  1. ionic generate service services/data
复制代码

在服务中定义数据和操作数据的方法:
  1. import { Injectable } from '@angular/core';
  2. import { BehaviorSubject, Observable } from 'rxjs';
  3. @Injectable({
  4.   providedIn: 'root'
  5. })
  6. export class DataService {
  7.   private itemsSource = new BehaviorSubject<any[]>([]);
  8.   currentItems = this.itemsSource.asObservable();
  9.   constructor() { }
  10.   updateItems(items: any[]) {
  11.     this.itemsSource.next(items);
  12.   }
  13.   addItem(item: any) {
  14.     const currentItems = this.itemsSource.getValue();
  15.     currentItems.push(item);
  16.     this.itemsSource.next(currentItems);
  17.   }
  18.   removeItem(index: number) {
  19.     const currentItems = this.itemsSource.getValue();
  20.     currentItems.splice(index, 1);
  21.     this.itemsSource.next(currentItems);
  22.   }
  23. }
复制代码

在组件中使用这个服务:
  1. import { Component, OnInit } from '@angular/core';
  2. import { DataService } from '../../services/data.service';
  3. @Component({
  4.   selector: 'app-home',
  5.   templateUrl: 'home.page.html',
  6.   styleUrls: ['home.page.scss'],
  7. })
  8. export class HomePage implements OnInit {
  9.   items: any[] = [];
  10.   constructor(private dataService: DataService) { }
  11.   ngOnInit() {
  12.     this.dataService.currentItems.subscribe(items => {
  13.       this.items = items;
  14.     });
  15.   }
  16.   addItem() {
  17.     const newItem = {
  18.       id: this.items.length + 1,
  19.       name: `项目 ${this.items.length + 1}`
  20.     };
  21.     this.dataService.addItem(newItem);
  22.   }
  23.   removeItem(index: number) {
  24.     this.dataService.removeItem(index);
  25.   }
  26. }
复制代码

6.2 使用RxJS进行响应式编程

RxJS是响应式编程的库,在Ionic 4中被广泛使用。下面是一些常用的RxJS操作符:
  1. import { Component, OnInit } from '@angular/core';
  2. import { fromEvent, interval } from 'rxjs';
  3. import { map, filter, debounceTime, distinctUntilChanged } from 'rxjs/operators';
  4. @Component({
  5.   selector: 'app-rxjs-demo',
  6.   templateUrl: './rxjs-demo.page.html',
  7.   styleUrls: ['./rxjs-demo.page.scss'],
  8. })
  9. export class RxjsDemoPage implements OnInit {
  10.   constructor() { }
  11.   ngOnInit() {
  12.     // 使用interval创建一个每隔1秒发出一个递增数字的Observable
  13.     const counter = interval(1000);
  14.    
  15.     // 使用pipe应用操作符
  16.     counter.pipe(
  17.       // 只取前10个值
  18.       take(10),
  19.       // 过滤掉偶数
  20.       filter(value => value % 2 === 1),
  21.       // 对值进行映射
  22.       map(value => `奇数: ${value}`)
  23.     ).subscribe(value => console.log(value));
  24.     // 监听输入框的输入事件
  25.     const inputElement = document.getElementById('search-input');
  26.     if (inputElement) {
  27.       fromEvent(inputElement, 'input').pipe(
  28.         // 获取输入值
  29.         map((event: any) => event.target.value),
  30.         // 防抖,等待300毫秒没有新输入后才处理
  31.         debounceTime(300),
  32.         // 只有当值发生变化时才继续
  33.         distinctUntilChanged()
  34.       ).subscribe(searchTerm => {
  35.         console.log('搜索词:', searchTerm);
  36.         // 这里可以执行搜索操作
  37.       });
  38.     }
  39.   }
  40. }
复制代码

6.3 使用NgRx进行状态管理

对于大型应用,可以使用NgRx进行状态管理。NgRx是一个基于RxJS的状态管理库,灵感来自Redux。

首先安装NgRx:
  1. npm install @ngrx/store @ngrx/effects @ngrx/entity @ngrx/store-devtools
复制代码

创建一个简单的计数器状态管理:
  1. // src/app/actions/counter.actions.ts
  2. import { createAction } from '@ngrx/store';
  3. export const increment = createAction('[Counter] Increment');
  4. export const decrement = createAction('[Counter] Decrement');
  5. export const reset = createAction('[Counter] Reset');
复制代码
  1. // src/app/reducers/counter.reducer.ts
  2. import { createReducer, on } from '@ngrx/store';
  3. import { increment, decrement, reset } from '../actions/counter.actions';
  4. export const initialState = 0;
  5. export const counterReducer = createReducer(
  6.   initialState,
  7.   on(increment, state => state + 1),
  8.   on(decrement, state => state - 1),
  9.   on(reset, state => 0)
  10. );
复制代码
  1. // src/app/app.module.ts
  2. import { NgModule } from '@angular/core';
  3. import { BrowserModule } from '@angular/platform-browser';
  4. import { RouterModule } from '@angular/router';
  5. import { IonicModule } from '@ionic/angular';
  6. import { AppComponent } from './app.component';
  7. import { StoreModule } from '@ngrx/store';
  8. import { StoreDevtoolsModule } from '@ngrx/store-devtools';
  9. import { counterReducer } from './reducers/counter.reducer';
  10. @NgModule({
  11.   declarations: [AppComponent],
  12.   imports: [
  13.     BrowserModule,
  14.     IonicModule.forRoot(),
  15.     RouterModule.forRoot([]),
  16.     StoreModule.forRoot({ count: counterReducer }),
  17.     StoreDevtoolsModule.instrument({
  18.       maxAge: 25
  19.     })
  20.   ],
  21.   bootstrap: [AppComponent]
  22. })
  23. export class AppModule { }
复制代码
  1. // src/app/pages/counter/counter.page.ts
  2. import { Component } from '@angular/core';
  3. import { Store, select } from '@ngrx/store';
  4. import { Observable } from 'rxjs';
  5. import { increment, decrement, reset } from '../../actions/counter.actions';
  6. @Component({
  7.   selector: 'app-counter',
  8.   templateUrl: './counter.page.html',
  9.   styleUrls: ['./counter.page.scss'],
  10. })
  11. export class CounterPage {
  12.   count$: Observable<number>;
  13.   constructor(private store: Store<{ count: number }>) {
  14.     this.count$ = store.pipe(select('count'));
  15.   }
  16.   increment() {
  17.     this.store.dispatch(increment());
  18.   }
  19.   decrement() {
  20.     this.store.dispatch(decrement());
  21.   }
  22.   reset() {
  23.     this.store.dispatch(reset());
  24.   }
  25. }
复制代码

在模板中:
  1. <ion-header>
  2.   <ion-toolbar>
  3.     <ion-title>计数器</ion-title>
  4.   </ion-toolbar>
  5. </ion-header>
  6. <ion-content>
  7.   <div class="ion-text-center ion-padding">
  8.     <h1>当前计数: {{ count$ | async }}</h1>
  9.     <ion-button (click)="increment()">增加</ion-button>
  10.     <ion-button (click)="decrement()">减少</ion-button>
  11.     <ion-button (click)="reset()">重置</ion-button>
  12.   </div>
  13. </ion-content>
复制代码

7. 与后端API交互

在移动应用开发中,与后端API交互是非常常见的需求。Ionic 4提供了多种方式来处理HTTP请求。

7.1 使用HttpClient

Angular的HttpClient是处理HTTP请求的推荐方式。

首先,在app.module.ts中导入HttpClientModule:
  1. import { HttpClientModule } from '@angular/common/angular';
  2. @NgModule({
  3.   // ...
  4.   imports: [
  5.     // ...
  6.     HttpClientModule
  7.   ],
  8.   // ...
  9. })
  10. export class AppModule { }
复制代码

创建一个API服务:
  1. // src/app/services/api.service.ts
  2. import { Injectable } from '@angular/core';
  3. import { HttpClient, HttpHeaders } from '@angular/common/http';
  4. import { Observable } from 'rxjs';
  5. @Injectable({
  6.   providedIn: 'root'
  7. })
  8. export class ApiService {
  9.   private apiUrl = 'https://api.example.com'; // 替换为你的API地址
  10.   constructor(private http: HttpClient) { }
  11.   // GET请求
  12.   get(endpoint: string): Observable<any> {
  13.     return this.http.get(`${this.apiUrl}/${endpoint}`);
  14.   }
  15.   // POST请求
  16.   post(endpoint: string, data: any): Observable<any> {
  17.     return this.http.post(`${this.apiUrl}/${endpoint}`, data);
  18.   }
  19.   // PUT请求
  20.   put(endpoint: string, data: any): Observable<any> {
  21.     return this.http.put(`${this.apiUrl}/${endpoint}`, data);
  22.   }
  23.   // DELETE请求
  24.   delete(endpoint: string): Observable<any> {
  25.     return this.http.delete(`${this.apiUrl}/${endpoint}`);
  26.   }
  27.   // 带有自定义头的请求
  28.   getWithAuth(endpoint: string, token: string): Observable<any> {
  29.     const headers = new HttpHeaders().set('Authorization', `Bearer ${token}`);
  30.     return this.http.get(`${this.apiUrl}/${endpoint}`, { headers });
  31.   }
  32. }
复制代码

在组件中使用这个服务:
  1. import { Component, OnInit } from '@angular/core';
  2. import { ApiService } from '../../services/api.service';
  3. @Component({
  4.   selector: 'app-posts',
  5.   templateUrl: './posts.page.html',
  6.   styleUrls: ['./posts.page.scss'],
  7. })
  8. export class PostsPage implements OnInit {
  9.   posts: any[] = [];
  10.   loading: boolean = false;
  11.   error: string;
  12.   constructor(private apiService: ApiService) { }
  13.   ngOnInit() {
  14.     this.loadPosts();
  15.   }
  16.   loadPosts() {
  17.     this.loading = true;
  18.     this.error = null;
  19.    
  20.     this.apiService.get('posts').subscribe(
  21.       (response) => {
  22.         this.posts = response;
  23.         this.loading = false;
  24.       },
  25.       (err) => {
  26.         this.error = '加载数据失败,请稍后再试。';
  27.         this.loading = false;
  28.         console.error('API错误:', err);
  29.       }
  30.     );
  31.   }
  32.   createPost() {
  33.     const newPost = {
  34.       title: '新文章',
  35.       body: '这是文章的内容',
  36.       userId: 1
  37.     };
  38.    
  39.     this.apiService.post('posts', newPost).subscribe(
  40.       (response) => {
  41.         console.log('文章创建成功:', response);
  42.         this.loadPosts(); // 重新加载文章列表
  43.       },
  44.       (err) => {
  45.         console.error('创建文章失败:', err);
  46.       }
  47.     );
  48.   }
  49. }
复制代码

在模板中:
  1. <ion-header>
  2.   <ion-toolbar>
  3.     <ion-title>文章列表</ion-title>
  4.   </ion-toolbar>
  5. </ion-header>
  6. <ion-content>
  7.   <ion-refresher (ionRefresh)="loadPosts()">
  8.     <ion-refresher-content></ion-refresher-content>
  9.   </ion-refresher>
  10.   <div *ngIf="loading" class="ion-text-center ion-padding">
  11.     <ion-spinner></ion-spinner>
  12.     <p>加载中...</p>
  13.   </div>
  14.   <div *ngIf="error" class="ion-text-center ion-padding">
  15.     <p color="danger">{{ error }}</p>
  16.     <ion-button (click)="loadPosts()">重试</ion-button>
  17.   </div>
  18.   <ion-list>
  19.     <ion-item *ngFor="let post of posts">
  20.       <ion-label>
  21.         <h2>{{ post.title }}</h2>
  22.         <p>{{ post.body }}</p>
  23.       </ion-label>
  24.     </ion-item>
  25.   </ion-list>
  26.   <ion-fab vertical="bottom" horizontal="end" slot="fixed">
  27.     <ion-fab-button (click)="createPost()">
  28.       <ion-icon name="add"></ion-icon>
  29.     </ion-fab-button>
  30.   </ion-fab>
  31. </ion-content>
复制代码

7.2 处理认证和授权

大多数应用都需要用户认证,下面是一个简单的认证服务示例:
  1. // src/app/services/auth.service.ts
  2. import { Injectable } from '@angular/core';
  3. import { HttpClient } from '@angular/common/http';
  4. import { BehaviorSubject, Observable, of } from 'rxjs';
  5. import { map, tap, catchError } from 'rxjs/operators';
  6. import { Storage } from '@ionic/storage';
  7. import { JwtHelperService } from '@auth0/angular-jwt';
  8. @Injectable({
  9.   providedIn: 'root'
  10. })
  11. export class AuthService {
  12.   private apiUrl = 'https://api.example.com/auth';
  13.   private currentUserSubject: BehaviorSubject<any>;
  14.   public currentUser: Observable<any>;
  15.   private jwtHelper = new JwtHelperService();
  16.   constructor(
  17.     private http: HttpClient,
  18.     private storage: Storage
  19.   ) {
  20.     this.currentUserSubject = new BehaviorSubject<any>(null);
  21.     this.currentUser = this.currentUserSubject.asObservable();
  22.    
  23.     // 检查存储中是否有token
  24.     this.storage.get('token').then(token => {
  25.       if (token && !this.jwtHelper.isTokenExpired(token)) {
  26.         const user = this.jwtHelper.decodeToken(token);
  27.         this.currentUserSubject.next(user);
  28.       }
  29.     });
  30.   }
  31.   public get currentUserValue(): any {
  32.     return this.currentUserSubject.value;
  33.   }
  34.   login(username: string, password: string): Observable<any> {
  35.     return this.http.post<any>(`${this.apiUrl}/login`, { username, password })
  36.       .pipe(
  37.         map(response => {
  38.           // 登录成功,保存token和用户信息
  39.           if (response && response.token) {
  40.             this.storage.set('token', response.token);
  41.             const user = this.jwtHelper.decodeToken(response.token);
  42.             this.currentUserSubject.next(user);
  43.           }
  44.           return response;
  45.         })
  46.       );
  47.   }
  48.   logout(): void {
  49.     this.storage.remove('token');
  50.     this.currentUserSubject.next(null);
  51.   }
  52.   isAuthenticated(): boolean {
  53.     const token = this.storage.get('token');
  54.     return token && !this.jwtHelper.isTokenExpired(token);
  55.   }
  56.   getToken(): Promise<string> {
  57.     return this.storage.get('token');
  58.   }
  59. }
复制代码

创建一个登录页面:
  1. // src/app/pages/login/login.page.ts
  2. import { Component } from '@angular/core';
  3. import { AuthService } from '../../services/auth.service';
  4. import { Router } from '@angular/router';
  5. import { ToastController } from '@ionic/angular';
  6. @Component({
  7.   selector: 'app-login',
  8.   templateUrl: './login.page.html',
  9.   styleUrls: ['./login.page.scss'],
  10. })
  11. export class LoginPage {
  12.   credentials = {
  13.     username: '',
  14.     password: ''
  15.   };
  16.   constructor(
  17.     private authService: AuthService,
  18.     private router: Router,
  19.     private toastController: ToastController
  20.   ) { }
  21.   login() {
  22.     this.authService.login(this.credentials.username, this.credentials.password)
  23.       .subscribe(
  24.         async () => {
  25.           const toast = await this.toastController.create({
  26.             message: '登录成功',
  27.             duration: 2000,
  28.             position: 'bottom'
  29.           });
  30.           toast.present();
  31.           this.router.navigate(['/home']);
  32.         },
  33.         async (error) => {
  34.           const toast = await this.toastController.create({
  35.             message: '登录失败,请检查用户名和密码',
  36.             duration: 2000,
  37.             position: 'bottom',
  38.             color: 'danger'
  39.           });
  40.           toast.present();
  41.         }
  42.       );
  43.   }
  44. }
复制代码

登录页面模板:
  1. <ion-header>
  2.   <ion-toolbar>
  3.     <ion-title>登录</ion-title>
  4.   </ion-toolbar>
  5. </ion-header>
  6. <ion-content>
  7.   <div class="ion-padding">
  8.     <form (ngSubmit)="login()">
  9.       <ion-item>
  10.         <ion-label position="floating">用户名</ion-label>
  11.         <ion-input type="text" [(ngModel)]="credentials.username" name="username" required></ion-input>
  12.       </ion-item>
  13.       
  14.       <ion-item>
  15.         <ion-label position="floating">密码</ion-label>
  16.         <ion-input type="password" [(ngModel)]="credentials.password" name="password" required></ion-input>
  17.       </ion-item>
  18.       
  19.       <div class="ion-margin-top">
  20.         <ion-button expand="block" type="submit" [disabled]="!credentials.username || !credentials.password">登录</ion-button>
  21.       </div>
  22.     </form>
  23.   </div>
  24. </ion-content>
复制代码

7.3 使用拦截器

拦截器允许我们在请求发送前或响应接收后进行一些操作,比如添加认证头、处理错误等。

创建一个认证拦截器:
  1. // src/app/interceptors/auth.interceptor.ts
  2. import { Injectable } from '@angular/core';
  3. import {
  4.   HttpRequest,
  5.   HttpHandler,
  6.   HttpEvent,
  7.   HttpInterceptor
  8. } from '@angular/common/http';
  9. import { Observable, from } from 'rxjs';
  10. import { switchMap } from 'rxjs/operators';
  11. import { AuthService } from '../services/auth.service';
  12. @Injectable()
  13. export class AuthInterceptor implements HttpInterceptor {
  14.   constructor(private authService: AuthService) {}
  15.   intercept(request: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
  16.     // 获取token
  17.     return from(this.authService.getToken()).pipe(
  18.       switchMap(token => {
  19.         // 如果有token,添加到请求头
  20.         if (token) {
  21.           request = request.clone({
  22.             setHeaders: {
  23.               Authorization: `Bearer ${token}`
  24.             }
  25.           });
  26.         }
  27.         
  28.         // 继续处理请求
  29.         return next.handle(request);
  30.       })
  31.     );
  32.   }
  33. }
复制代码

注册拦截器:
  1. // src/app/app.module.ts
  2. import { HTTP_INTERCEPTORS } from '@angular/common/http';
  3. import { AuthInterceptor } from './interceptors/auth.interceptor';
  4. @NgModule({
  5.   // ...
  6.   providers: [
  7.     {
  8.       provide: HTTP_INTERCEPTORS,
  9.       useClass: AuthInterceptor,
  10.       multi: true
  11.     }
  12.   ],
  13.   // ...
  14. })
  15. export class AppModule { }
复制代码

8. 调试和测试

在开发Ionic 4应用时,调试和测试是非常重要的环节。本节将介绍如何有效地调试和测试Ionic 4应用。

8.1 浏览器调试

Ionic应用可以在浏览器中运行和调试,这是最方便的调试方式。

在Chrome或其他现代浏览器中,可以使用开发者工具进行调试:

1. 在浏览器中运行应用:ionic serve
2. 右键点击页面,选择”检查”或按F12打开开发者工具
3. 在”Console”标签页查看日志和错误
4. 在”Elements”标签页检查和修改DOM
5. 在”Network”标签页监控网络请求
6. 在”Application”标签页查看本地存储和缓存

Augury是一个Angular应用的调试工具,可以帮助你理解应用的结构和状态。

1. 在Chrome扩展商店安装Augury
2. 打开开发者工具,会出现一个新的”Augury”标签页
3. 使用Augury可以查看组件树、路由状态和NgRx状态等

8.2 设备调试

在真实设备上调试应用可以更好地了解应用在实际环境中的表现。

1. 启用开发者选项和USB调试在设备上,进入”设置” > “关于手机”连续点击”版本号”7次,启用开发者选项返回”设置”,进入”开发者选项”启用”USB调试”
2. 在设备上,进入”设置” > “关于手机”
3. 连续点击”版本号”7次,启用开发者选项
4. 返回”设置”,进入”开发者选项”
5. 启用”USB调试”
6. 连接设备并运行应用ionic cordova run android --device
7. 使用Chrome调试在Chrome中访问chrome://inspect/#devices找到你的设备,点击”inspect”这将打开一个开发者工具窗口,可以调试运行在设备上的应用
8. 在Chrome中访问chrome://inspect/#devices
9. 找到你的设备,点击”inspect”
10. 这将打开一个开发者工具窗口,可以调试运行在设备上的应用

启用开发者选项和USB调试

• 在设备上,进入”设置” > “关于手机”
• 连续点击”版本号”7次,启用开发者选项
• 返回”设置”,进入”开发者选项”
• 启用”USB调试”

连接设备并运行应用
  1. ionic cordova run android --device
复制代码

使用Chrome调试

• 在Chrome中访问chrome://inspect/#devices
• 找到你的设备,点击”inspect”
• 这将打开一个开发者工具窗口,可以调试运行在设备上的应用

1. 连接iOS设备到Mac
2. 在Xcode中打开项目:ionic cordova open ios
3. 在Xcode中选择你的设备作为目标
4. 点击运行按钮
5. 使用Safari调试在Mac上打开Safari进入”开发”菜单,选择你的设备和应用这将打开一个Web检查器窗口,可以调试应用
6. 在Mac上打开Safari
7. 进入”开发”菜单,选择你的设备和应用
8. 这将打开一个Web检查器窗口,可以调试应用

• 在Mac上打开Safari
• 进入”开发”菜单,选择你的设备和应用
• 这将打开一个Web检查器窗口,可以调试应用

8.3 单元测试

Ionic 4应用使用Jasmine和Karma进行单元测试。

创建一个简单的服务测试:
  1. // src/app/services/data.service.spec.ts
  2. import { TestBed } from '@angular/core/testing';
  3. import { DataService } from './data.service';
  4. describe('DataService', () => {
  5.   let service: DataService;
  6.   beforeEach(() => {
  7.     TestBed.configureTestingModule({});
  8.     service = TestBed.inject(DataService);
  9.   });
  10.   it('should be created', () => {
  11.     expect(service).toBeTruthy();
  12.   });
  13.   it('should add item to list', () => {
  14.     const initialLength = service.getItems().length;
  15.     service.addItem({ id: 1, name: 'Test Item' });
  16.     expect(service.getItems().length).toBe(initialLength + 1);
  17.   });
  18.   it('should remove item from list', () => {
  19.     service.addItem({ id: 1, name: 'Test Item' });
  20.     const initialLength = service.getItems().length;
  21.     service.removeItem(0);
  22.     expect(service.getItems().length).toBe(initialLength - 1);
  23.   });
  24. });
复制代码

运行所有测试:
  1. ng test
复制代码

运行特定文件的测试:
  1. ng test --include='src/app/services/data.service.spec.ts'
复制代码

8.4 端到端测试

Ionic 4应用使用Protractor进行端到端测试。

创建一个简单的端到端测试:
  1. // e2e/src/app.e2e-spec.ts
  2. import { AppPage } from './app.po';
  3. describe('workspace-project App', () => {
  4.   let page: AppPage;
  5.   beforeEach(() => {
  6.     page = new AppPage();
  7.   });
  8.   it('should display welcome message', () => {
  9.     page.navigateTo();
  10.     expect(page.getTitleText()).toEqual('Welcome to myFirstApp!');
  11.   });
  12.   it('should navigate to about page', () => {
  13.     page.navigateTo();
  14.     page.getAboutButton().click();
  15.     expect(page.getAboutTitle()).toEqual('About');
  16.   });
  17. });
复制代码

首先启动应用:
  1. ionic serve
复制代码

然后在另一个终端中运行测试:
  1. ng e2e
复制代码

8.5 使用Ionic Native插件

Ionic Native提供了对设备原生功能的访问,如相机、GPS等。下面是一个使用相机的例子:

首先安装插件:
  1. ionic cordova plugin add cordova-plugin-camera
  2. npm install @ionic-native/camera
复制代码

在app.module.ts中导入插件:
  1. import { Camera } from '@ionic-native/camera/ngx';
  2. @NgModule({
  3.   // ...
  4.   providers: [
  5.     // ...
  6.     Camera
  7.   ],
  8.   // ...
  9. })
  10. export class AppModule { }
复制代码

在组件中使用相机:
  1. import { Component } from '@angular/core';
  2. import { Camera, CameraOptions } from '@ionic-native/camera/ngx';
  3. @Component({
  4.   selector: 'app-camera',
  5.   templateUrl: './camera.page.html',
  6.   styleUrls: ['./camera.page.scss'],
  7. })
  8. export class CameraPage {
  9.   image: string;
  10.   constructor(private camera: Camera) { }
  11.   takePicture() {
  12.     const options: CameraOptions = {
  13.       quality: 100,
  14.       destinationType: this.camera.DestinationType.DATA_URL,
  15.       encodingType: this.camera.EncodingType.JPEG,
  16.       mediaType: this.camera.MediaType.PICTURE
  17.     };
  18.     this.camera.getPicture(options).then((imageData) => {
  19.       // imageData is either a base64 encoded string or a file URI
  20.       this.image = 'data:image/jpeg;base64,' + imageData;
  21.     }, (err) => {
  22.       // Handle error
  23.       console.error('Camera error:', err);
  24.     });
  25.   }
  26. }
复制代码

在模板中:
  1. <ion-header>
  2.   <ion-toolbar>
  3.     <ion-title>相机</ion-title>
  4.   </ion-toolbar>
  5. </ion-header>
  6. <ion-content>
  7.   <div class="ion-text-center ion-padding">
  8.     <ion-button (click)="takePicture()">拍照</ion-button>
  9.    
  10.     <div *ngIf="image" class="ion-margin-top">
  11.       <img [src]="image" style="max-width: 100%;">
  12.     </div>
  13.   </div>
  14. </ion-content>
复制代码

9. 构建和发布应用

开发完成后,我们需要构建和发布应用。本节将介绍如何为不同平台构建和发布Ionic 4应用。

9.1 构建Web应用

Ionic应用可以作为Web应用发布:
  1. # 构建生产版本
  2. ionic build --prod
  3. # 构建完成后,文件会在www目录中
  4. # 可以将这些文件部署到任何Web服务器
复制代码

9.2 构建Android应用
  1. ionic cordova platform add android
复制代码
  1. # 构建调试版本
  2. ionic cordova build android
  3. # 构建发布版本
  4. ionic cordova build android --prod --release
复制代码

1. 生成密钥库:keytool -genkey -v -keystore my-release-key.keystore -alias alias_name -keyalg RSA -keysize 2048 -validity 10000
2. 使用jarsigner签名APK:jarsigner -verbose -sigalg SHA1withRSA -digestalg SHA1 -keystore my-release-key.keystore platforms/android/app/build/outputs/apk/release/app-release-unsigned.apk alias_name
3. 使用zipalign优化APK:zipalign -v 4 platforms/android/app/build/outputs/apk/release/app-release-unsigned.apk my-app.apk

生成密钥库:
  1. keytool -genkey -v -keystore my-release-key.keystore -alias alias_name -keyalg RSA -keysize 2048 -validity 10000
复制代码

使用jarsigner签名APK:
  1. jarsigner -verbose -sigalg SHA1withRSA -digestalg SHA1 -keystore my-release-key.keystore platforms/android/app/build/outputs/apk/release/app-release-unsigned.apk alias_name
复制代码

使用zipalign优化APK:
  1. zipalign -v 4 platforms/android/app/build/outputs/apk/release/app-release-unsigned.apk my-app.apk
复制代码

1. 登录Google Play Console
2. 创建应用
3. 上传APK文件
4. 填写应用信息、截图等
5. 提交审核

9.3 构建iOS应用
  1. ionic cordova platform add ios
复制代码
  1. # 构建iOS应用
  2. ionic cordova build ios --prod
复制代码
  1. ionic cordova open ios
复制代码

1. 在Xcode中配置签名证书和配置文件
2. 选择”Generic iOS Device”作为目标
3. 选择”Product” > “Archive”
4. 在Organizer窗口中,选择”Upload to App Store”
5. 按照提示完成上传

1. 登录App Store Connect
2. 创建应用
3. 填写应用信息、截图等
4. 选择构建版本
5. 提交审核

9.4 使用Capacitor

Capacitor是Ionic团队开发的替代Cordova的解决方案,提供了更好的开发体验和原生功能访问。
  1. npm install @capacitor/core @capacitor/cli @capacitor/ios @capacitor/android
  2. npx cap init
复制代码
  1. # 构建Web资源
  2. ionic build
  3. # 同步到原生项目
  4. npx cap sync
  5. # 在Android Studio中打开
  6. npx cap open android
  7. # 在Xcode中打开
  8. npx cap open ios
复制代码

Capacitor提供了许多原生插件,下面是一个使用Geolocation插件的例子:
  1. import { Component } from '@angular/core';
  2. import { Geolocation, Position } from '@capacitor/geolocation';
  3. @Component({
  4.   selector: 'app-geolocation',
  5.   templateUrl: './geolocation.page.html',
  6.   styleUrls: ['./geolocation.page.scss'],
  7. })
  8. export class GeolocationPage {
  9.   position: Position;
  10.   constructor() { }
  11.   async getCurrentPosition() {
  12.     try {
  13.       this.position = await Geolocation.getCurrentPosition();
  14.       console.log('Current position:', this.position);
  15.     } catch (error) {
  16.       console.error('Error getting location', error);
  17.     }
  18.   }
  19. }
复制代码

在模板中:
  1. <ion-header>
  2.   <ion-toolbar>
  3.     <ion-title>地理位置</ion-title>
  4.   </ion-toolbar>
  5. </ion-header>
  6. <ion-content>
  7.   <div class="ion-text-center ion-padding">
  8.     <ion-button (click)="getCurrentPosition()">获取当前位置</ion-button>
  9.    
  10.     <div *ngIf="position" class="ion-margin-top">
  11.       <p>纬度: {{ position.coords.latitude }}</p>
  12.       <p>经度: {{ position.coords.longitude }}</p>
  13.       <p>精度: {{ position.coords.accuracy }} 米</p>
  14.     </div>
  15.   </div>
  16. </ion-content>
复制代码

10. 实战项目:待办事项应用

现在,让我们通过一个完整的待办事项应用来综合运用前面所学的知识。

10.1 项目概述

我们将创建一个待办事项应用,具有以下功能:

• 添加、编辑和删除待办事项
• 标记待办事项为完成或未完成
• 按类别过滤待办事项
• 本地存储待办事项
• 响应式设计,适配不同屏幕尺寸

10.2 创建项目

首先,创建一个新的Ionic项目:
  1. ionic start todoApp tabs --type=angular
  2. cd todoApp
复制代码

10.3 设计数据模型

创建一个待办事项的数据模型:
  1. // src/app/models/todo.model.ts
  2. export interface Todo {
  3.   id: string;
  4.   title: string;
  5.   description: string;
  6.   completed: boolean;
  7.   category: string;
  8.   createdAt: Date;
  9.   updatedAt: Date;
  10. }
复制代码

10.4 创建服务

创建一个待办事项服务:
  1. ionic generate service services/todo
复制代码

在服务中实现待办事项的管理逻辑:
  1. // src/app/services/todo.service.ts
  2. import { Injectable } from '@angular/core';
  3. import { BehaviorSubject, Observable } from 'rxjs';
  4. import { map } from 'rxjs/operators';
  5. import { Todo } from '../models/todo.model';
  6. import { Storage } from '@ionic/storage';
  7. @Injectable({
  8.   providedIn: 'root'
  9. })
  10. export class TodoService {
  11.   private todosSubject = new BehaviorSubject<Todo[]>([]);
  12.   private todos$: Observable<Todo[]> = this.todosSubject.asObservable();
  13.   private STORAGE_KEY = 'todos';
  14.   constructor(private storage: Storage) {
  15.     this.loadTodos();
  16.   }
  17.   private async loadTodos() {
  18.     const storedTodos = await this.storage.get(this.STORAGE_KEY);
  19.     if (storedTodos) {
  20.       this.todosSubject.next(storedTodos);
  21.     }
  22.   }
  23.   private async saveTodos(todos: Todo[]) {
  24.     await this.storage.set(this.STORAGE_KEY, todos);
  25.     this.todosSubject.next(todos);
  26.   }
  27.   getTodos(): Observable<Todo[]> {
  28.     return this.todos$;
  29.   }
  30.   getTodosByCategory(category: string): Observable<Todo[]> {
  31.     return this.todos$.pipe(
  32.       map(todos => todos.filter(todo => todo.category === category))
  33.     );
  34.   }
  35.   getCompletedTodos(): Observable<Todo[]> {
  36.     return this.todos$.pipe(
  37.       map(todos => todos.filter(todo => todo.completed))
  38.     );
  39.   }
  40.   getActiveTodos(): Observable<Todo[]> {
  41.     return this.todos$.pipe(
  42.       map(todos => todos.filter(todo => !todo.completed))
  43.     );
  44.   }
  45.   async addTodo(todo: Omit<Todo, 'id' | 'createdAt' | 'updatedAt'>) {
  46.     const newTodo: Todo = {
  47.       ...todo,
  48.       id: this.generateId(),
  49.       createdAt: new Date(),
  50.       updatedAt: new Date()
  51.     };
  52.     const currentTodos = this.todosSubject.getValue();
  53.     const updatedTodos = [...currentTodos, newTodo];
  54.     await this.saveTodos(updatedTodos);
  55.    
  56.     return newTodo;
  57.   }
  58.   async updateTodo(id: string, updates: Partial<Todo>) {
  59.     const currentTodos = this.todosSubject.getValue();
  60.     const updatedTodos = currentTodos.map(todo => {
  61.       if (todo.id === id) {
  62.         return {
  63.           ...todo,
  64.           ...updates,
  65.           updatedAt: new Date()
  66.         };
  67.       }
  68.       return todo;
  69.     });
  70.    
  71.     await this.saveTodos(updatedTodos);
  72.    
  73.     return updatedTodos.find(todo => todo.id === id);
  74.   }
  75.   async deleteTodo(id: string) {
  76.     const currentTodos = this.todosSubject.getValue();
  77.     const updatedTodos = currentTodos.filter(todo => todo.id !== id);
  78.     await this.saveTodos(updatedTodos);
  79.   }
  80.   async toggleTodoComplete(id: string) {
  81.     const currentTodos = this.todosSubject.getValue();
  82.     const updatedTodos = currentTodos.map(todo => {
  83.       if (todo.id === id) {
  84.         return {
  85.           ...todo,
  86.           completed: !todo.completed,
  87.           updatedAt: new Date()
  88.         };
  89.       }
  90.       return todo;
  91.     });
  92.    
  93.     await this.saveTodos(updatedTodos);
  94.    
  95.     return updatedTodos.find(todo => todo.id === id);
  96.   }
  97.   private generateId(): string {
  98.     return Math.random().toString(36).substring(2, 15) + Math.random().toString(36).substring(2, 15);
  99.   }
  100. }
复制代码

10.5 创建页面
  1. ionic generate page pages/todos
复制代码

在页面组件中:
  1. // src/app/pages/todos/todos.page.ts
  2. import { Component, OnInit } from '@angular/core';
  3. import { Observable } from 'rxjs';
  4. import { Todo } from '../../models/todo.model';
  5. import { TodoService } from '../../services/todo.service';
  6. import { ModalController } from '@ionic/angular';
  7. import { TodoModalPage } from '../todo-modal/todo-modal.page';
  8. @Component({
  9.   selector: 'app-todos',
  10.   templateUrl: './todos.page.html',
  11.   styleUrls: ['./todos.page.scss'],
  12. })
  13. export class TodosPage implements OnInit {
  14.   todos$: Observable<Todo[]>;
  15.   activeTodos$: Observable<Todo[]>;
  16.   completedTodos$: Observable<Todo[]>;
  17.   selectedCategory: string = 'all';
  18.   categories = [
  19.     { id: 'all', name: '全部' },
  20.     { id: 'work', name: '工作' },
  21.     { id: 'personal', name: '个人' },
  22.     { id: 'shopping', name: '购物' }
  23.   ];
  24.   constructor(
  25.     private todoService: TodoService,
  26.     private modalController: ModalController
  27.   ) { }
  28.   ngOnInit() {
  29.     this.todos$ = this.todoService.getTodos();
  30.     this.activeTodos$ = this.todoService.getActiveTodos();
  31.     this.completedTodos$ = this.todoService.getCompletedTodos();
  32.   }
  33.   async presentTodoModal(todo?: Todo) {
  34.     const modal = await this.modalController.create({
  35.       component: TodoModalPage,
  36.       componentProps: {
  37.         todo: todo || null
  38.       }
  39.     });
  40.     modal.onDidDismiss().then((data) => {
  41.       if (data.data) {
  42.         if (todo) {
  43.           // 更新待办事项
  44.           this.todoService.updateTodo(todo.id, data.data);
  45.         } else {
  46.           // 添加新的待办事项
  47.           this.todoService.addTodo(data.data);
  48.         }
  49.       }
  50.     });
  51.     return await modal.present();
  52.   }
  53.   toggleTodoComplete(id: string) {
  54.     this.todoService.toggleTodoComplete(id);
  55.   }
  56.   deleteTodo(id: string) {
  57.     this.todoService.deleteTodo(id);
  58.   }
  59.   filterByCategory(category: string) {
  60.     this.selectedCategory = category;
  61.   }
  62.   getFilteredTodos(): Observable<Todo[]> {
  63.     if (this.selectedCategory === 'all') {
  64.       return this.todos$;
  65.     }
  66.     return this.todoService.getTodosByCategory(this.selectedCategory);
  67.   }
  68. }
复制代码

在页面模板中:
  1. <ion-header>
  2.   <ion-toolbar>
  3.     <ion-title>待办事项</ion-title>
  4.     <ion-buttons slot="end">
  5.       <ion-button (click)="presentTodoModal()">
  6.         <ion-icon slot="icon-only" name="add"></ion-icon>
  7.       </ion-button>
  8.     </ion-buttons>
  9.   </ion-toolbar>
  10. </ion-header>
  11. <ion-content>
  12.   <!-- 类别过滤器 -->
  13.   <ion-segment [(ngModel)]="selectedCategory" (ionChange)="filterByCategory(selectedCategory)">
  14.     <ion-segment-button *ngFor="let category of categories" [value]="category.id">
  15.       {{ category.name }}
  16.     </ion-segment-button>
  17.   </ion-segment>
  18.   <!-- 待办事项列表 -->
  19.   <ion-list>
  20.     <ion-item-sliding *ngFor="let todo of (getFilteredTodos() | async)">
  21.       <ion-item>
  22.         <ion-label>
  23.           <h2 [class.completed]="todo.completed">{{ todo.title }}</h2>
  24.           <p>{{ todo.description }}</p>
  25.           <p>
  26.             <ion-chip>
  27.               <ion-label>{{ categories.find(c => c.id === todo.category)?.name }}</ion-chip>
  28.             </ion-chip>
  29.           </p>
  30.         </ion-label>
  31.         <ion-checkbox
  32.           slot="start"
  33.           [checked]="todo.completed"
  34.           (ionChange)="toggleTodoComplete(todo.id)">
  35.         </ion-checkbox>
  36.       </ion-item>
  37.       <ion-item-options side="end">
  38.         <ion-item-option (click)="presentTodoModal(todo)" color="primary">
  39.           <ion-icon slot="icon-only" name="create"></ion-icon>
  40.         </ion-item-option>
  41.         <ion-item-option (click)="deleteTodo(todo.id)" color="danger">
  42.           <ion-icon slot="icon-only" name="trash"></ion-icon>
  43.         </ion-item-option>
  44.       </ion-item-options>
  45.     </ion-item-sliding>
  46.   </ion-list>
  47.   <!-- 空状态 -->
  48.   <div *ngIf="(getFilteredTodos() | async)?.length === 0" class="ion-text-center ion-padding">
  49.     <ion-icon name="checkmark-circle-outline" size="large" color="medium"></ion-icon>
  50.     <p>没有待办事项</p>
  51.     <ion-button fill="outline" (click)="presentTodoModal()">添加第一个待办事项</ion-button>
  52.   </div>
  53. </ion-content>
复制代码

在页面样式中:
  1. // src/app/pages/todos/todos.page.scss
  2. .completed {
  3.   text-decoration: line-through;
  4.   color: var(--ion-color-medium);
  5. }
复制代码
  1. ionic generate page pages/todo-modal
复制代码

在页面组件中:
  1. // src/app/pages/todo-modal/todo-modal.page.ts
  2. import { Component, OnInit, Input } from '@angular/core';
  3. import { ModalController } from '@ionic/angular';
  4. import { Todo } from '../../models/todo.model';
  5. @Component({
  6.   selector: 'app-todo-modal',
  7.   templateUrl: './todo-modal.page.html',
  8.   styleUrls: ['./todo-modal.page.scss'],
  9. })
  10. export class TodoModalPage implements OnInit {
  11.   @Input() todo: Todo | null = null;
  12.   formData = {
  13.     title: '',
  14.     description: '',
  15.     category: 'personal',
  16.     completed: false
  17.   };
  18.   categories = [
  19.     { id: 'work', name: '工作' },
  20.     { id: 'personal', name: '个人' },
  21.     { id: 'shopping', name: '购物' }
  22.   ];
  23.   constructor(private modalController: ModalController) { }
  24.   ngOnInit() {
  25.     if (this.todo) {
  26.       this.formData = {
  27.         title: this.todo.title,
  28.         description: this.todo.description,
  29.         category: this.todo.category,
  30.         completed: this.todo.completed
  31.       };
  32.     }
  33.   }
  34.   dismiss() {
  35.     this.modalController.dismiss();
  36.   }
  37.   save() {
  38.     this.modalController.dismiss(this.formData);
  39.   }
  40. }
复制代码

在页面模板中:
  1. <ion-header>
  2.   <ion-toolbar>
  3.     <ion-title>{{ todo ? '编辑待办事项' : '添加待办事项' }}</ion-title>
  4.     <ion-buttons slot="end">
  5.       <ion-button (click)="dismiss()">取消</ion-button>
  6.     </ion-buttons>
  7.   </ion-toolbar>
  8. </ion-header>
  9. <ion-content>
  10.   <form (ngSubmit)="save()">
  11.     <ion-list>
  12.       <ion-item>
  13.         <ion-label position="floating">标题</ion-label>
  14.         <ion-input
  15.           type="text"
  16.           [(ngModel)]="formData.title"
  17.           name="title"
  18.           required>
  19.         </ion-input>
  20.       </ion-item>
  21.       
  22.       <ion-item>
  23.         <ion-label position="floating">描述</ion-label>
  24.         <ion-textarea
  25.           [(ngModel)]="formData.description"
  26.           name="description">
  27.         </ion-textarea>
  28.       </ion-item>
  29.       
  30.       <ion-item>
  31.         <ion-label>类别</ion-label>
  32.         <ion-select
  33.           [(ngModel)]="formData.category"
  34.           name="category">
  35.           <ion-select-option *ngFor="let category of categories" [value]="category.id">
  36.             {{ category.name }}
  37.           </ion-select-option>
  38.         </ion-select>
  39.       </ion-item>
  40.       
  41.       <ion-item *ngIf="todo">
  42.         <ion-label>已完成</ion-label>
  43.         <ion-toggle
  44.           [(ngModel)]="formData.completed"
  45.           name="completed">
  46.         </ion-toggle>
  47.       </ion-item>
  48.     </ion-list>
  49.    
  50.     <div class="ion-padding">
  51.       <ion-button expand="block" type="submit" [disabled]="!formData.title">
  52.         {{ todo ? '更新' : '添加' }}
  53.       </ion-button>
  54.     </div>
  55.   </form>
  56. </ion-content>
复制代码
  1. ionic generate page pages/stats
复制代码

在页面组件中:
  1. // src/app/pages/stats/stats.page.ts
  2. import { Component, OnInit } from '@angular/core';
  3. import { Observable } from 'rxjs';
  4. import { map } from 'rxjs/operators';
  5. import { Todo } from '../../models/todo.model';
  6. import { TodoService } from '../../services/todo.service';
  7. @Component({
  8.   selector: 'app-stats',
  9.   templateUrl: './stats.page.html',
  10.   styleUrls: ['./stats.page.scss'],
  11. })
  12. export class StatsPage implements OnInit {
  13.   todos$: Observable<Todo[]>;
  14.   totalTodos$: Observable<number>;
  15.   completedTodos$: Observable<number>;
  16.   activeTodos$: Observable<number>;
  17.   completionRate$: Observable<number>;
  18.   categories = [
  19.     { id: 'work', name: '工作' },
  20.     { id: 'personal', name: '个人' },
  21.     { id: 'shopping', name: '购物' }
  22.   ];
  23.   categoryStats$: Observable<{ category: string; name: string; total: number; completed: number; completionRate: number }[]>;
  24.   constructor(private todoService: TodoService) { }
  25.   ngOnInit() {
  26.     this.todos$ = this.todoService.getTodos();
  27.     this.totalTodos$ = this.todos$.pipe(map(todos => todos.length));
  28.     this.completedTodos$ = this.todoService.getCompletedTodos().pipe(map(todos => todos.length));
  29.     this.activeTodos$ = this.todoService.getActiveTodos().pipe(map(todos => todos.length));
  30.    
  31.     this.completionRate$ = this.todos$.pipe(
  32.       map(todos => {
  33.         if (todos.length === 0) return 0;
  34.         const completed = todos.filter(todo => todo.completed).length;
  35.         return Math.round((completed / todos.length) * 100);
  36.       })
  37.     );
  38.     this.categoryStats$ = this.todos$.pipe(
  39.       map(todos => {
  40.         return this.categories.map(category => {
  41.           const categoryTodos = todos.filter(todo => todo.category === category.id);
  42.           const completed = categoryTodos.filter(todo => todo.completed).length;
  43.           const completionRate = categoryTodos.length > 0
  44.             ? Math.round((completed / categoryTodos.length) * 100)
  45.             : 0;
  46.          
  47.           return {
  48.             category: category.id,
  49.             name: category.name,
  50.             total: categoryTodos.length,
  51.             completed,
  52.             completionRate
  53.           };
  54.         });
  55.       })
  56.     );
  57.   }
  58. }
复制代码

在页面模板中:
  1. <ion-header>
  2.   <ion-toolbar>
  3.     <ion-title>统计</ion-title>
  4.   </ion-toolbar>
  5. </ion-header>
  6. <ion-content>
  7.   <!-- 总体统计 -->
  8.   <ion-card>
  9.     <ion-card-header>
  10.       <ion-card-title>总体统计</ion-card-title>
  11.     </ion-card-header>
  12.     <ion-card-content>
  13.       <ion-grid>
  14.         <ion-row>
  15.           <ion-col>
  16.             <div class="stat-card">
  17.               <div class="stat-value">{{ (totalTodos$ | async) || 0 }}</div>
  18.               <div class="stat-label">总计</div>
  19.             </div>
  20.           </ion-col>
  21.           <ion-col>
  22.             <div class="stat-card">
  23.               <div class="stat-value">{{ (activeTodos$ | async) || 0 }}</div>
  24.               <div class="stat-label">进行中</div>
  25.             </div>
  26.           </ion-col>
  27.           <ion-col>
  28.             <div class="stat-card">
  29.               <div class="stat-value">{{ (completedTodos$ | async) || 0 }}</div>
  30.               <div class="stat-label">已完成</div>
  31.             </div>
  32.           </ion-col>
  33.         </ion-row>
  34.       </ion-grid>
  35.       
  36.       <div class="ion-margin-top">
  37.         <ion-label>完成率: {{ (completionRate$ | async) || 0 }}%</ion-label>
  38.         <ion-progress-bar [value]="(completionRate$ | async) / 100"></ion-progress-bar>
  39.       </div>
  40.     </ion-card-content>
  41.   </ion-card>
  42.   <!-- 分类统计 -->
  43.   <ion-card>
  44.     <ion-card-header>
  45.       <ion-card-title>分类统计</ion-card-title>
  46.     </ion-card-header>
  47.     <ion-card-content>
  48.       <ion-list>
  49.         <ion-item *ngFor="let stat of (categoryStats$ | async)">
  50.           <ion-label>
  51.             <h2>{{ stat.name }}</h2>
  52.             <p>{{ stat.completed }} / {{ stat.total }} ({{ stat.completionRate }}%)</p>
  53.           </ion-label>
  54.           <ion-progress-bar
  55.             slot="end"
  56.             [value]="stat.completionRate / 100"
  57.             style="width: 100px;">
  58.           </ion-progress-bar>
  59.         </ion-item>
  60.       </ion-list>
  61.     </ion-card-content>
  62.   </ion-card>
  63. </ion-content>
复制代码

在页面样式中:
  1. // src/app/pages/stats/stats.page.scss
  2. .stat-card {
  3.   text-align: center;
  4.   padding: 10px;
  5.   
  6.   .stat-value {
  7.     font-size: 24px;
  8.     font-weight: bold;
  9.     color: var(--ion-color-primary);
  10.   }
  11.   
  12.   .stat-label {
  13.     font-size: 14px;
  14.     color: var(--ion-color-medium);
  15.   }
  16. }
复制代码

修改tabs.router.module.ts以包含新页面:
  1. // src/app/tabs/tabs.router.module.ts
  2. import { NgModule } from '@angular/core';
  3. import { RouterModule, Routes } from '@angular/router';
  4. import { TabsPage } from './tabs.page';
  5. const routes: Routes = [
  6.   {
  7.     path: 'tabs',
  8.     component: TabsPage,
  9.     children: [
  10.       {
  11.         path: 'todos',
  12.         loadChildren: () => import('../pages/todos/todos.module').then(m => m.TodosPageModule)
  13.       },
  14.       {
  15.         path: 'stats',
  16.         loadChildren: () => import('../pages/stats/stats.module').then(m => m.StatsPageModule)
  17.       },
  18.       {
  19.         path: '',
  20.         redirectTo: '/tabs/todos',
  21.         pathMatch: 'full'
  22.       }
  23.     ]
  24.   },
  25.   {
  26.     path: '',
  27.     redirectTo: '/tabs/todos',
  28.     pathMatch: 'full'
  29.   }
  30. ];
  31. @NgModule({
  32.   imports: [RouterModule.forChild(routes)],
  33.   exports: [RouterModule]
  34. })
  35. export class TabsPageRoutingModule {}
复制代码

修改tabs.page.html以更新标签:
  1. <ion-tabs>
  2.   <ion-tab-bar slot="bottom">
  3.     <ion-tab-button tab="todos">
  4.       <ion-icon name="list"></ion-icon>
  5.       <ion-label>待办事项</ion-label>
  6.     </ion-tab-button>
  7.    
  8.     <ion-tab-button tab="stats">
  9.       <ion-icon name="stats-chart"></ion-icon>
  10.       <ion-label>统计</ion-label>
  11.     </ion-tab-button>
  12.   </ion-tab-bar>
  13. </ion-tabs>
复制代码

10.6 添加存储服务

为了确保数据持久化,我们需要添加Ionic Storage服务:
  1. npm install @ionic/storage
  2. npm install @ionic/storage-angular
复制代码

在app.module.ts中导入并配置StorageModule:
  1. // src/app/app.module.ts
  2. import { NgModule } from '@angular/core';
  3. import { BrowserModule } from '@angular/platform-browser';
  4. import { RouteReuseStrategy } from '@angular/router';
  5. import { IonicModule, IonicRouteStrategy } from '@ionic/angular';
  6. import { SplashScreen } from '@ionic-native/splash-screen/ngx';
  7. import { StatusBar } from '@ionic-native/status-bar/ngx';
  8. import { AppComponent } from './app.component';
  9. import { AppRoutingModule } from './app-routing.module';
  10. import { IonicStorageModule } from '@ionic/storage';
  11. @NgModule({
  12.   declarations: [AppComponent],
  13.   entryComponents: [],
  14.   imports: [
  15.     BrowserModule,
  16.     IonicModule.forRoot(),
  17.     AppRoutingModule,
  18.     IonicStorageModule.forRoot()
  19.   ],
  20.   providers: [
  21.     StatusBar,
  22.     SplashScreen,
  23.     { provide: RouteReuseStrategy, useClass: IonicRouteStrategy }
  24.   ],
  25.   bootstrap: [AppComponent]
  26. })
  27. export class AppModule {}
复制代码

10.7 运行应用

现在,我们可以运行应用了:
  1. ionic serve
复制代码

10.8 构建和发布

当应用开发完成后,可以按照前面章节介绍的方法构建和发布应用。

总结

通过本教程,我们学习了Ionic 4的基础知识和实战技能,从环境搭建到项目实战,涵盖了Ionic 4开发的各个方面。我们了解了Ionic 4的组件系统、导航和路由、数据管理、与后端API交互、调试和测试,以及如何构建和发布应用。最后,我们通过一个完整的待办事项应用项目,综合运用了所学知识。

Ionic 4是一个强大的跨平台移动应用开发框架,它允许开发者使用Web技术构建高性能、美观的移动应用。通过本教程的学习,你应该已经掌握了Ionic 4开发的基本技能,可以开始构建自己的移动应用了。

继续学习和实践,探索Ionic 4的更多特性和功能,你将能够开发出更加复杂和功能丰富的移动应用。祝你在Ionic 4开发的道路上取得成功!
「七転び八起き(ななころびやおき)」
回复

使用道具 举报

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则