ionic应用环信创建聊天_如何使用Ionic 3创建CRUD待办事项应用程序

ionic应用环信创建聊天

Hey all! This is a post on an up and coming tech topic — Ionic! By the end of this post you would learn how to create a simple CRUD (Create, Read, Update and Delete) to-do list app, which is also connected to Firebase.

大家好! 这是有关新兴技术主题的文章-Ionic! 在本文结尾处,您将学习如何创建一个简单的CRUD(创建,读取,更新和删除)待办事项列表应用程序,该应用程序也已连接到Firebase。

混合移动应用程序-它们是什么? (Hybrid Mobile Applications — What are they?)

Simply put, they are mobile apps created by the easier to learn languages; HTML, CSS, and JavaScript. The beauty of developing a hybrid mobile app is the fact that they can be compiled to work with any platform. If you are lazy, like yours truly, you’ll find it easier to use one code to build many apps, instead of developing separate apps for each platform.

简而言之,它们是由易于学习的语言创建的移动应用程序; HTML,CSS和JavaScript。 开发混合移动应用程序的好处在于,它们可以被编译为可在任何平台上使用的事实。 如果您像您一样真正地懒惰,您会发现使用一个代码来构建许多应用程序会更容易,而不是为每个平台开发单独的应用程序。

Ionic is one of the popular frameworks to make your own hybrid mobile app. It can be built into an Android, iOS, Windows phone, Progressive Web or Desktop application. And testing the app is so much easier since it can be live reloaded right onto your browser.

Ionic是制作您自己的混合移动应用程序的流行框架之一。 它可以内置到Android,iOS,Windows手机,Progressive Web或Desktop应用程序中。 由于可以将应用程序实时重新加载到您的浏览器中,因此测试该应用程序非常容易。

第1步-全部设置 (Step 1 — Setting it all up)

Initially, sign up for an Ionic Pro account, here. That will make building and shipping the app easier. You might have to sign in sometime during the process of project creation.

首先, 在此处注册一个Ionic Pro帐户。 这将使构建和发布应用程序更加容易。 您可能必须在项目创建过程中的某个时间登录。

To start coding your first Ionic App, there are a bunch of things you need;

要开始编写第一个Ionic App的代码,您需要做很多事情。

  1. Node.js — This is pretty easy. Simply go to the Node.js website and download the ideal installer for you. We need the Node Package Manager, aptly named npm, to install all the dependencies for the many modules you would want to use in your app. If you develop on a Mac and have Homebrew installed, simply type in the command brew install npm on the console.

    Node.js —这很容易。 只需访问Node.js 网站并为您下载理想的安装程序。 我们需要恰当地命名为npm的Node Package Manager,以安装要在应用程序中使用的许多模块的所有依赖项。 如果您在Mac上开发并安装了Homebrew,只需在控制台上键入命令brew install npm

  2. TypeScript — TypeScript, a superset of JavaScript, is used instead of JS for the majority of the code. After installing Node.js, on the console, type in npm install -g typescript .

    TypeScript — TypeScript是JavaScript的超集,用于大多数代码,而不是JS。 安装Node.js之后,在控制台上,键入npm install -g typescript

  3. Cordova — Cordova is a framework that builds the HTML, CSS and JS/TS code into an app. To install, type npm install -g cordova

    Cordova — Cordova是一个框架,可将HTML,CSS和JS / TS代码构建到应用程序中。 要安装,请键入npm install -g cordova

  4. And finally, Ionic — Type in npm install -g ionic .

    最后是npm install -g ionic

Bonus — You can download all three in one go with this command too! npm install -g typescript cordova ionic.

奖励-您也可以使用此命令一次性下载所有三个文件! npm install -g typescript cordova ionic

Now that you have set up the environment, let's get this party started!! ??

现在您已经设置了环境,让我们开始这个聚会吧!! ??

创建您的第一个应用 (Creating your first app)

From within the console, move to the folder in which you want to store the app. My personal preference is to have a dedicated folder for all my Ionic projects in my Documents.

从控制台中,移至要在其中存储应用程序的文件夹。 我个人的喜好是为我的文档中的所有Ionic项目创建一个专用文件夹。

Then, type in ionic start . The console then prompts you for a name for the project, like so, Project name: Tasks.

然后,键入ionic start 。 然后,控制台会提示您输入Project name: Tasks ,例如Project name: Tasks

It then prompts you to specify the type of application.

然后,它提示您指定应用程序的类型。

? Starter template: (Use arrow keys)
  tabs     | A starting project with a simple tabbed interface
> blank    | A blank starter project
  sidemenu | A starting project with a side menu with navigation in the content area
  super    | A starting project complete with pre-built pages, providers and best practices for Ionic development.
  tutorial | A tutorial based project that goes along with the Ionic documentation
  aws      | AWS Mobile Hub Starter

For now, let's make it a blank project, a to-do list with all CRUD functions in one page. It will then prompt you for permission to add the Android and iOS platforms.

现在,让我们使其成为一个空白项目,一个包含所有CRUD功能的任务列表在一页中。 然后,它将提示您获得添加Android和iOS平台的权限。

? Integrate your new app with Cordova to target native iOS and Android? (y/N) y

It will proceed to download extra dependencies that will allow you to live reload the app in emulators and devices. Once the native SDK’s are downloaded you are prompted to add the Ionic Pro SDK, if you wish to do so.

它将继续下载额外的依赖项,使您可以在模拟器和设备中实时重新加载应用程序。 下载本机SDK后,如果需要,将提示您添加Ionic Pro SDK。

? Install the free Ionic Pro SDK and connect your app? y

If you do pick yes, the console will then prompt you for your Ionic Pro email and password, set up at the beginning of this post.

如果选择“是”,则控制台将提示您输入在本文开头设置的Ionic Pro电子邮件和密码。

? Email: 
? Password:

Thereafter, you have the option to either link this app to an existing one, to create a new one entirely.

之后,您可以选择将此应用程序链接到现有应用程序,以完全创建一个新应用程序。

? What would you like to do? (Use arrow keys)
  Link an existing app on Ionic Pro
> Create a new app on Ionic Pro

The console then proceeds to ask your preferred git host, to store your repository. I prefer GitHub, as it’s something I’m more familiar with.

然后,控制台继续询问您首选的git主机,以存储您的存储库。 我更喜欢GitHub,因为它是我更熟悉的东西。

? Which git host would you like to use? (Use arrow keys)
> GitHub
  Ionic Pro

Depending on your choice above, if you picked GitHub as I have, you may require to open your browser to give your credentials and sign in. Once done, return back to the console. You then need to link this app to the repository or create a new one. If you don’t have a repository, go back to GitHub and create one now. Once the new repository is created, come back to the console and type y .

根据上面的选择,如果您选择了GitHub,则可能需要打开浏览器以提供凭据并登录。完成后,返回控制台。 然后,您需要将此应用程序链接到存储库或创建一个新的应用程序。 如果您没有存储库,请回到GitHub并立即创建一个。 创建新存储库后,返回控制台并输入y

? Does the repository exist on GitHub? y

Afterward, pick the correct repository from the list displayed on the console. I will be using only the master branch for now and will go with the former option.

然后,从控制台上显示的列表中选择正确的存储库。 我现在将仅使用master分支,并且将使用前一个选项。

? Which would you like to do? (Use arrow keys)
> Link to master branch only
  Link to specific branches

And FINALLY, we’re done creating the app!! ??

最后,我们已经完成了应用的创建!! ??

But, If you picked Ionic Pro as a git host, pick the option to generate an SSH key pair.

但是,如果您选择Ionic Pro作为git主机,请选择生成SSH密钥对的选项。

? How would you like to connect to Ionic Pro? (Use arrow keys)
> Automatically setup new a SSH key pair for Ionic Pro
  Use an existing SSH key pair
  Skip for now
  Ignore this prompt forever

And we’re done here too! Now to have a look at the app

而且我们在这里也完成了! 现在来看一下该应用程序

There are two different commands to view the app on the browser.

有两种不同的命令可在浏览器上查看应用程序。

  1. ionic serve

    ionic serve

  2. ionic serve -l

    ionic serve -l

ionic serve displays the app in the view of a web application.

ionic serve在Web应用程序视图中显示该应用程序。

ionic serve -l displays the app in the many mobile device platforms. You will need to download it from within the console, when prompted, to get this view.

ionic serve -l在许多移动设备平台上显示该应用程序。 出现提示时,您需要从控制台中下载它以获取此视图。

And that’s a wrap for today! We successfully created and linked an Ionic 4 app to a version control host.

这就是今天的包装! 我们成功创建了Ionic 4应用并将其链接到版本控制主机。

项目结构 (The Project Structure)

  1. app.module.ts — The entry point of the app. Any and all components, pages, modules, and providers need to be added to this file, as it keeps track and controls the many resources used by the app.

    app.module.ts —应用程序的入口点。 需要将所有组件,页面,模块和提供程序添加到此文件中,因为它可以跟踪并控制该应用程序使用的许多资源。
  2. app.components.ts — The first page that is loaded as the app starts running, with all the code you wish to execute first. Pages that you might wish the user to view first, like the login screen, are put in this component.

    app.components.ts —在应用开始运行时加载的第一页,其中包含您希望首先执行的所有代码。 您可能希望用户首先查看的页面(例如登录屏幕)位于此组件中。
  3. app.html — The template of the app, where the other UI pages will mount onto.

    app.html —应用程序的模板,其他UI页面将安装在该模板上。
  4. app.scss — The page that holds all the Sass variables and styles to be used globally within the app.

    app.scss-该页面包含所有在应用程序中全局使用的Sass变量和样式。

Let’s head on over to the main component that we will be amending for this application, home.

让我们进入将要为此应用程序修改的主要组件home。

As seen above, the home component has three pages;

如上所示,home组件具有三个页面;

  1. home.html — The view/UI of the page is coded here, using HTML.

    home.html —使用HTML在此处对页面的视图/ UI进行编码。
  2. home.scss — Any page-specific styling is to be added here, along with Sass variables to be used within the page.

    home.scss —任何特定于页面的样式都将在此处添加,以及将在页面内使用的Sass变量。
  3. home.ts — The operational logic, in our case adding new tasks to the list, is coded in TypeScript here.

    home.ts —在我们的情况下,将操作添加到列表中的操作逻辑在此处用TypeScript编码。

第2步-实施CRUD操作 (Step 2 - Implementing the CRUD operations)

What I hope to implement as seen above, is a very simple design; a text input to type the tasks, a button to add it to the list, a list view to view the items and finally a delete button to remove the items from the list. I might change up the design later.

我希望如上所述实现的是一个非常简单的设计。 一个用于输入任务的文本输入,一个将其添加到列表中的按钮,一个用于查看项目的列表视图以及最后一个用于从列表中删除项目的删除按钮。 我可能会在以后更改设计。

Go ahead and open your editor. Let's take a quick run through all the pages and components found in the current directory.

继续并打开您的编辑器。 让我们快速浏览一下当前目录中的所有页面和组件。

为C和R创建UI (Creating the UI for C and R)

To begin, let’s tackle the UI first. When you open up home.html, this is the current code in the page.

首先,让我们首先解决UI。 当您打开home.html时,这是页面中的当前代码。

<ion-header>
	<ion-navbar>
		<ion-title>Ionic Blank</ion-title>
	</ion-navbar>
</ion-header>
<ion-content padding>
	The world is your oyster.
	<p>If you get lost, the
		<a href="http://ionicframework.com/docs/v2">docs</a>
	will be your guide.
	</p>
</ion-content>

You can then remove everything within the <ion-content> tags. This is the body of the page and elements within those tags will be seen.

然后,您可以删除<ion-content>标记内的所有<ion-content> 。 这是页面的正文,将看到这些标记内的元素。

Now add an input tag in the body, so we can enter in the task, followed by a button, to call a method to add the task to the list.

现在在主体中添加一个输入标签,以便我们可以在任务中输入一个按钮,然后调用一个方法来将该任务添加到列表中。

<ion-content padding>
	<input type="text" placeholder="Enter task">
	<button>Add Task</button>
</ion-content>

Not pretty, right? Let’s add some styling now!

不漂亮吧? 让我们现在添加一些样式!

Ionic has a special input tag <ion-input> , that comes with some styling coded within it, so go ahead and switch boring old <input> to <ion-input> !

Ionic有一个特殊的输入标签<ion-input> ,其中带有一些编码样式,因此请继续将无聊的<input>切换为<ion-input>

Ionic also comes with certain special classes which have styling, like the ion-button. I also want to have the button to the end of the input, and not right below. The final changes look like this;

Ionic还带有某些具有样式的特殊类,例如ion-button 。 我还希望按钮位于输入的末尾,而不是在下方。 最终的更改如下所示;

<ion-content padding>
	<ion-item>
		<ion-input type="text" placeholder="Enter task" [(ngModel)]="taskName"/>
		<div class="item-note" item-end>
			<button ion-button>Add Task</button>
		</div>
	</ion-item>
</ion-content>

So much better, right!? And all this without writing any CSS! Let’s have another look at the code above.

好多了,对!! 而这一切都无需编写任何CSS! 让我们再看一下上面的代码。

<ion-item> tag is normally used with the <ion-list> element. But, using this here, with the input within this element, gives it an added style on focus or use. Using the class item-note for a div element allows the button to be in line with the input tag. Doing so, gives a more seamless and sleek design, compared to the first one. Since Angular is also integrated into Ionic, we can use ngModel to easily link values in the views to that in the TypeScript files.

<ion-item>标记通常与<ion-list>元素一起使用。 但是,在此使用此元素,并在此元素中输入内容,可以使其在焦点或使用上具有更多风格。 对div元素使用class item-note可以使按钮与输入标签保持一致。 与第一个相比,这样做可以提供更加无缝和时尚的设计。 由于Angular也已集成到Ionic中,因此我们可以使用ngModel轻松地将视图中的值链接到TypeScript文件中的值。

Ionic also comes with a built-in pack of icons, Ionicons. Its very simple to use, and a quick example would be substituting the Add task text with <ion-icon name="add"></ion-icon> . Find more on Ionicons, here.

Ionic还带有内置图标Ionicons。 它的使用非常简单,一个简单的示例就是用<ion-icon name="add"></ion-icon>代替“添加”任务文本。 在此处找到有关Ionicons的更多信息

The final result! I’m quite happy with what it looks like now, but feel free to play around more with colors and styling.

最终结果! 我对现在的样子感到很满意,但是可以随意使用更多颜色和样式。

实现创建和读取功能 (Implementing create and read functionality)

Now that the UI has been done, let's move on to giving this a function. It’s time to look at home.ts. You start off with code that looks like this;

现在已经完成了UI,让我们继续为它提供一个功能。 现在该看看home.ts。 您从看起来像这样的代码开始;

import { Component } from '@angular/core';
import { NavController } from 'ionic-angular';

@Component({
    selector: 'page-home',
    templateUrl: 'home.html'
})

export class HomePage {
    constructor(public navCtrl: NavController) {  }
}

Let’s get a quick look at what we have here. You import any components or external modules, that you may need to use in this page at the very top. The next few lines describe the template to which the many functions you may write belong to and manipulate. And lastly, all the logic you may code. Any code you wish to execute before viewing or interacting with the page must be written within the constructor.

让我们快速了解一下这里的内容。 导入可能需要在此页面顶部使用的所有组件或外部模块。 接下来的几行描述了您可能编写的许多功能所属并操纵的模板。 最后,您可以编写所有逻辑。 您希望在查看页面或与页面交互之前执行的任何代码都必须在构造函数中编写。

Since we will be adding new to-dos each time, we need a place to store it. The simplest way to do this is to initialize an array. If you have had experience with JavaScript previously, coding with TypeScript will be a piece of cake!

由于我们每次都会添加新的待办事项,因此我们需要一个存放它的地方。 最简单的方法是初始化一个数组。 如果您以前有使用JavaScript的经验,那么使用TypeScript进行编码将是小菜一碟!

Let’s call our list taskList, but since we need the list to be accessed from more than one method of the code, we need to initialize it outside the constructor taskList = [];. Now to write code to handle the Add Task button click, let's call it addTask. All we need to do is capture the text in the input, and push it onto the array. Since we have used ngModel for the input tag, we can easily get the value inside it by using this.taskName. And adding values to an array is as easy as taskList.push(task). We also need to ensure that no empty string is being added to the list, so wrap the above statement in an if condition, checking if the taskName truly exists. The final home.ts code;

我们将其称为列表taskList,但是由于我们需要从多个代码方法中访问列表,因此需要在构造函数taskList = [];之外对其进行初始化taskList = []; 。 现在编写代码来处理“添加任务”按钮的单击,我们将其addTask 。 我们需要做的就是捕获输入中的文本,并将其压入数组。 由于我们已将ngModel用于输入标签,因此可以使用this.taskName轻松获取其中的值。 向数组添加值就像taskList.push(task)一样容易。 我们还需要确保没有空字符串被添加到列表中,因此将以上语句包装在if条件中,检查taskName是否确实存在。 最终的home.ts代码;

import { Component } from '@angular/core';
import { NavController } from 'ionic-angular';

@Component({
    selector: 'page-home',
    templateUrl: 'home.html'
})

export class HomePage {
    taskList = [];

    constructor(public navCtrl: NavController) {}

    addTask() {
        if (this.taskName.length > 0) {
            let task = this.taskName;
            this.taskList.push(task);
            this.taskName = "";
        }
    }
}

Note: Using the keyword let in TypeScript is the equivalent of using var, for variable declaration.

注意:在TypeScript中使用关键字let等同于使用var进行变量声明。

Now we can begin adding new tasks!

现在我们可以开始添加新任务了!

But how do we know something is being added???

但是我们怎么知道要添加一些东西呢?

Easy Peasy, ?Squeezy! That’s what the R in CRUD is there for!

Easy Peasy,紧缩! 这就是CRUD中的R的作用!

运行代码,看看 (Run the code and have a look)

Time to C(reate) a way for us to R(ead) what we type! (See what I did there?)?

是时候C(玩)我们R(吃)我们输入的东西了! (看看我在那里做什么?)?

Let’s roll back to the home.html. So far, we have put an input tag and a button to add tasks; now to put a list to view it. We now need to link the method addTask() to the button in the (click) property, so that a list item is added to the array with each click.

让我们回滚到home.html。 到目前为止,我们已经放置了一个输入标签和一个按钮来添加任务。 现在放一个列表来查看它。 现在,我们需要将方法addTask()链接到(click)属性中的按钮,以便每次单击都会向列表添加一个列表项。

<ion-list> is a special Ionic element for list views. The <ion-item> tag is used within it to generate each item in said list. *ngFor is an easy method of showing all elements within a list, by setting a standard view for each list item.

<ion-list>是用于列表视图的特殊Ionic元素。 <ion-item>标签用于在其中生成所述列表中的每个项目。 *ngFor是通过为每个列表项设置标准视图来显示列表中所有元素的简便方法。

The final home.html code;

最终的home.html代码;

<ion-header>
	<ion-navbar>
		<ion-title>To-do List</ion-title>
	</ion-navbar>
</ion-header>
<ion-content padding>
	<ion-item>
		<ion-input type="text" [(ngModel)]="taskName" placeholder="Enter task"/>
		<div class="item-note" item-end>
			<button ion-button (click)="addTask()"><ion-icon name="add"></ion-icon></button>
		</div>
	</ion-item>
	<div padding>
		<ion-list>
			<ion-item *ngFor="let todo of taskList">
				{{todo}}
			</ion-item>
		</ion-list>
	</div>
</ion-content>

The variable todo is a temporary store for the element in the current index of the for loop (ngFor) within the list taskList, as declared in the home.ts.

变量todo是home.ts中声明的list taskList内for循环(ngFor)当前索引中元素的临时存储。

Ready to see our app so far?

准备到目前为止看到我们的应用程序吗?

We did it!! It works!!????

我们做到了!! 有用!!????

But that was just Create and Read. Will still have to implement Update and Delete.

但这仅仅是创建和读取。 仍将必须执行更新和删除。

We will first begin with changing the GUI so that it can fit both the update and delete features. Thereafter move onto the TypeScript code, to show its functionality.

首先,我们将更改GUI,使其既适合更新功能,也适合删除功能。 之后,移至TypeScript代码,以显示其功能。

更改外观以包括U和D的处理程序 (Change that look to include handlers for U and D)

Whoops! Little forgetful ole me! I didn’t change the app name on the home page… ???

哎呀! 小健忘了我! 我没有在首页上更改应用名称... ???

Go ahead and call it whatever you wish (I’ll be basic with ‘To-do List’).

继续并随便叫它(我将以“待办事项列表”作为基础)。

The first step, to be done in the home.html, is to add the delete button to the left side of each list item. That’s easy! Reuse the very same code I used to include the addTask button next to the input in the <ion-item>, nesting this button within the div with the class item-note, but change that + into an ?️icon (don’t want to get confused now, do we?). Since this is a button, give the event handler the name deleteTask(). The button will also have another style class clear, which gives it a clear background. Since this button will be within the <ion-item> that is in the <ion-list>, it will be generated for all items in the list.

第一步要在home.html中完成,是将删除按钮添加到每个列表项的左侧。 这很简单! 重用我addTask<ion-item>的输入旁边包含addTask按钮的相同代码,将此按钮与类item-note嵌套在div中,但是将+更改为?️icon(不想现在就感到困惑,是吗?)。 由于这是一个按钮,因此将事件处理程序命名为deleteTask() 。 该按钮还将具有另一个样式类clear ,这为其提供了清晰的背景。 由于此按钮位于<ion-list><ion-item>中,因此将为列表中的所有项目生成该按钮。

We need to add another button to the list to edit each task. Luckily more copying of code! Copy the whole button tag, but replace the icon ?️ to a ✏️ and the click handler to updateTask().

我们需要在列表中添加另一个按钮以编辑每个任务。 幸运的是,可以复制更多代码! 复制整个按钮标签,但将图标?️替换为✏️,然后将单击处理程序替换为updateTask updateTask()

The code for each <ion-item> tag now looks like this

每个<ion-item>标签的代码现在看起来像这样

<ion-item *ngFor="let todo of taskList; let i = index">
	{{todo}}
	<div class="item-note" item-end>
		<button ion-button clear (click)="updateTask(i)">
			<ion-icon name="create"></ion-icon>
		</button>
		<button ion-button clear (click)="deleteTask(i)">
			<ion-icon name="trash"></ion-icon>
		</button>
	</div>
</ion-item>

The statement let i = index takes the index of the specific element in the list, so that we can pass it over to the method, so only the element to be deleted would be affected.

let i = index语句获取列表中特定元素的索引,以便我们可以将其传递给方法,因此只有要删除的元素会受到影响。

Pretty neat, huh??

很好,是吗?

I quite like it and it looks so much better than the wireframe I originally designed.

我非常喜欢它,它看起来比我最初设计的线框好得多。

实现更新和删除功能 (Implementing update and delete functionality)

Now to add functionality to our ?️ and ✏️.

现在向我们的?️和✏️添加功能。

We need to create a new method within the home.ts called deleteTask(), as specified in home.html above. We pass the index of the array from within the ngFor loop, so we know the exact position of the task to be deleted. Hop on over to home.html, and pass the parameter i, which is the index of the element in the array, within the deleteTask method, like so deleteTask(i). As you have passed the index through to the home.ts, you simply need to use the splice() method on the array to remove the desired task, passing the index of the item to be removed as a parameter, like so this.taskList.splice(index, 1);.

我们需要在home.ts中创建一个名为deleteTask()的新方法,如上面home.html中所指定。 我们从ngFor循环中传递数组的索引,因此我们知道要删除的任务的确切位置。 跳转到home.html,并在deleteTask方法中传递参数i (即数组中元素的索引),就像deleteTask(i) 。 将索引传递到home.ts时,只需在数组上使用splice()方法即可删除所需的任务,将要删除项目的索引作为参数传递,就像this.taskList.splice(index, 1);

The code for the deleteTask method is;

deleteTask方法的代码为;

deleteTask(index){
    this.taskList.splice(index, 1);
}

Short and sweet! ? That’s all the coding we need to delete tasks!

简短而甜蜜! ? 这就是删除任务所需的全部代码!

Now to update, it will take a bit more typing (bear with me)!

现在进行更新,将需要更多的输入(与我同在)!

My plan is to open up an alert asking the user to enter the update text of the task. To do that, we need to import the AlertController, a module found in ionic-angular. You import it using this line of code.

我的计划是打开一个警报,要求用户输入任务的更新文本。 为此,我们需要导入AlertController ,该模块位于ionic-angular 。 您可以使用以下代码行将其导入。

import { NavController, AlertController } from 'ionic-angular';

You then need to initialize it in the constructor, like so;

然后,您需要在构造函数中对其进行初始化,如下所示:

constructor(public navCtrl: NavController, public alertCtrl: AlertController)

You will then need to create an alert in the updateTask method to capture the new task name. To do so, you will need to pass the following into the create method of the AlertController;

然后,您将需要在updateTask方法中创建一个警报以捕获新的任务名称。 为此,您需要将以下内容传递给AlertController的create方法;

  1. title — The title of the message.

    title-消息的标题。
  2. message — A longer message (if required).

    message —较长的消息(如果需要)。
  3. inputs — Input field with their name and placeholder (if any).

    输入-输入字段及其名称和占位符(如果有)。
  4. buttons — Buttons along with their role or handler (if any).

    button —按钮及其角色或处理程序(如果有)。

The alert can be displayed afterward with the simple alert.present()command. I will be having two buttons, one is a cancel button, the second is to edit and the handler code will simply take the entered task and switch it with the previous value in the array. The code for the updateTask() method;

随后可以使用简单的alert.present()命令显示警报。 我将有两个按钮,一个是取消按钮,第二个是编辑按钮,处理程序代码将简单地接受输入的任务,并使用数组中的前一个值进行切换。 updateTask()方法的代码;

updateTask(index) {
    let alert = this.alertCtrl.create({
        title: 'Update Task?',
        message: 'Type in your new task to update.',
        inputs: [{ name: 'editTask', placeholder: 'Task' }],
        buttons: [{ text: 'Cancel', role: 'cancel' },
                  { text: 'Update', handler: data => {
                      this.taskList[index] = data.editTask; }
                  }
                 ]
    });
    alert.present();
}

It should all work perfectly now!

现在应该一切正常!

Want to see the final CRUD app?

是否想查看最终的CRUD应用程序?

And there you have it! ??

在那里,您拥有了! ??

A fully operational CRUD to-do list, with minimal coding! That’s how easy Ionic can be.

完全可用的CRUD待办事项清单,只需最少的编码! 这就是Ionic的难易程度。

I still believe we can make it a bit more user-friendly. Scroll down for more add-on functionality.

我仍然相信我们可以使其更加人性化。 向下滚动以获取更多附加功能。

奖金!! —自动对焦 (Bonus!! — Auto-focus)

Do you know what I find annoying? I need to click on the input each time I want to add a new task, even at the beginning. Why not auto-focus the input after clicking the button?

你知道我觉得烦吗? 每当我想添加新任务时,甚至在开始时,都需要单击输入。 为什么在单击按钮后不自动聚焦输入?

That’s exactly what we will do!

这正是我们将要做的!

Auto-focus on Ionic is not as easy as it is in classic HTML/JavaScript interactions. You need to import an extra component called ViewChild. You can then easily connect the input from the view (home.html) to the controller (home.ts), and manipulate it as well. You import it, like so;

自动关注Ionic并不像经典HTML / JavaScript交互中那样容易。 您需要导入一个名为ViewChild的额外组件。 然后,您可以轻松地将视图(home.html)的输入连接到控制器(home.ts),并进行操作。 您可以像这样导入它;

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

You can then connect the input tag to the component, outside the constructor, using this line of code,

然后,您可以使用以下代码行将输入标签连接到构造函数外部的组件,

@ViewChild('taskInput') input;

taskInput is the id of the input tag on the home.html page. Go ahead and add#taskInput to the input tag. The input tag can now directly be handled from within the TypeScript file.

taskInput是home.html页面上的输入标签的ID。 继续并将#taskInput添加到输入标签。 现在可以从TypeScript文件中直接处理input标签。

Ionic comes with a few methods that can be invoked on certain app events, such as when a page loads onto the view, unloads and so on. These are called lifecycle events, and more about then can be found here. We can cause the app to auto-focus on the input from within the ionViewDidLoad(), by setting a timeout. The code would be;

Ionic带有一些可以在某些应用程序事件上调用的方法,例如,页面加载到视图上时,卸载时等等。 这些称为生命周期事件,有关更多信息,请参见此处 。 通过设置超时,我们可以使应用自动关注ionViewDidLoad()的输入。 该代码将是;

ionViewDidLoad(){
    setTimeout(() => {
        this.input.setFocus();
    },350);
}

For the auto-focus to work after you add the line this.input.setFocus(); as the last statement in the addTask() handler. Lets head on out to see the changes we made!

为了使自动对焦在添加以下行后this.input.setFocus(); 作为addTask()处理程序中的最后一条语句。 让我们去看看我们所做的更改!

Now that’s what you call seamless…?

现在这就是所谓的无缝……?

第3步-集成Firebase身份验证 (Step 3 - Integrating Firebase Authentication)

Firebase has everything, from authorization to a database to file storage, one of the many reasons it’s a good choice to add to mobile apps. In this post, we will explore Firebase, create a project and make a handler component for Firebase in the app.

Firebase拥有从授权到数据库再到文件存储的所有功能,这是将它添加到移动应用程序中的不错选择的众多原因之一。 在本文中,我们将探索Firebase,创建一个项目,并在应用程序中为Firebase创建处理程序组件。

设置Firebase控制台 (Setting up the Firebase console)

But first step’s first, you need to create a project on the Firebase console. All you need is a Google account to access Firebase. So head on over here to get started. Add a new project and give it a name (I just called mine ‘Tasks’), agree to everything they ask and hit Create Project.

但是第一步的第一步是,您需要在Firebase控制台上创建一个项目。 您只需要一个Google帐户即可访问Firebase。 因此,前往此处开始。 添加一个新项目并为其命名(我只是叫我的“任务”),同意他们的要求并点击创建项目。

Now to set up the project to fit our needs.

现在设置适合我们需求的项目。

All the areas of Firebase that we will be accessing will be found under Develop.

我们将访问的Firebase的所有区域都可以在“开发”下找到。

Namely;

  1. Authentication

    认证方式
  2. And Database.

    和数据库。

Let’s have a look at Authentication.

让我们看一下身份验证。

As you can see, all methods of authentication have been disabled. For now enable the very basic of types, Email/Password, so we can begin using it to register an account.

如您所见,所有身份验证方法均已禁用。 现在启用最基本的类型,电子邮件/密码,因此我们可以开始使用它来注册帐户。

Under templates, the many email templates for verification of email address to forget password are can be found. If you wish, you can change a few of the details, like the project name to be displayed and the name of the sender.

在模板下,可以找到许多用于验证电子邮件地址以忘记密码的电子邮件模板。 如果需要,您可以更改一些详细信息,例如要显示的项目名称和发送者的名称。

Now, onward to the Database section. Firebase has two types of databases;

现在,转到“数据库”部分。 Firebase有两种类型的数据库:

  1. Realtime Database — a NoSQL database, that looks like one big JSON Object.

    实时数据库— NoSQL数据库,看起来像一个大JSON对象。
  2. Cloud Firestore — A collection of documents, which are essentially JSON Objects.

    Cloud Firestore —文档的集合,本质上是JSON对象。

Firestore is the better option as it has a better structure compared to the normal Realtime Database. In the Realtime Database, anybody can write data anywhere, if they have the reference to the database, greatly affecting all the data stored. And for that reason, I picked Firestore and created the database in test mode, so we can assess the database.

与正常的实时数据库相比,Firestore具有更好的结构,因此它是更好的选择。 在实时数据库中,任何人只要有对数据库的引用,就可以在任何地方写入数据,从而极大地影响了所有存储的数据。 因此,我选择了Firestore并以测试模式创建了数据库,因此我们可以评估数据库。

Firestore in test mode does allow anyone to read and write into it, so let’s make it that only users who have registered to the app have access to the database. To do so, switch allow read, write: if false; for allow read, write:if request.auth.uid!=null;. Only registered users have a unique uid, with which to distinguish them. Most often, the uid is used as the ID to the users' object. I will be implementing the same for this project.

在测试模式下的Firestore确实允许任何人对其进行读写,因此让我们确保只有已注册应用程序的用户才能访问数据库。 为此,请切换“ allow read, write: if false; 对于allow read, write:if request.auth.uid!=null; 。 仅注册用户具有唯一的uid,可用来区分它们。 大多数情况下,uid用作用户对象的ID。 我将为此项目实施相同的操作。

Once the rules are changed, we need to create a collection, so all our user documents can be put into it. Since we cannot have a collection without at least one document, make a fake user. You can delete it from the dashboard later.

更改规则后,我们需要创建一个集合,以便将所有用户文档放入其中。 由于没有至少一个文档我们就无法拥有一个馆藏,因此请假冒用户。 您可以稍后从仪表板中将其删除。

As we have set up the Firebase dashboard, let’s move on the integrating Firebase into the app.

设置Firebase仪表板后,让我们继续将Firebase集成到应用程序中。

将Firebase链接到应用程序 (Linking Firebase to the app)

There is a special module AngularFire you can download using npm to incorporate Firebase into the Ionic app. To download, type npm install firebase angularfire2 --save.

您可以使用npm下载一个特殊的模块AngularFire ,以将Firebase整合到Ionic应用程序中。 要下载,请键入npm install firebase angularfire2 --save

To use this module, you need to import it into the app.module.ts page, like so

要使用此模块,您需要将其导入到app.module.ts页面,如下所示

import { AngularFireModule } from 'angularfire2';
import { AngularFireAuthModule } from 'angularfire2/auth';
import { AngularFirestoreModule } from 'angularfire2/firestore';

We also need to add the necessary config data for the app to access and use the correct database. This can be found in the Project Overview section, ‘Add Firebase to your web app’. You are required to call the JSON object firebaseConfig and initialize it after the imports.

我们还需要添加必要的配置数据,以使应用访问和使用正确的数据库。 可以在“项目概述”部分的“将Firebase添加到您的Web应用程序”中找到。 您需要调用JSON对象firebaseConfig并在导入后对其进行初始化。

export const firebaseConfig = {
    apiKey: "#######################################",
    authDomain: "###########.firebaseapp.com",
    databaseURL: "https://###########.firebaseio.com",
    projectId: "###########",
    storageBucket: "###########.appspot.com",
    messagingSenderId: "############"
};

One last step! You need to include the imported modules above, into the import array of @NgModule that contains all the components used in the app, initializing the AngularFireModule as well with the config object above.

最后一步! 您需要将上面导入的模块包含到@NgModule的导入数组中,该数组包含应用程序中使用的所有组件,并使用上面的config对象初始化AngularFireModule。

@NgModule({
    ...
    imports: [
        ...
        AngularFireModule.initializeApp(firebaseConfig), 
        AngularFireAuthModule, 
        AngularFirestoreModule
    ]
})

AngularFireAuthModule comes with many methods pertaining to authorization, like signup, sign in, forgot password, etc. All the methods we will be using will be found in the auth property of AngularFireAuth. The methods being used are;

AngularFireAuthModule附带了许多与授权有关的方法,例如注册,登录,忘记密码等。我们将使用的所有方法都可以在AngularFireAuth的auth属性中找到。 使用的方法是;

  1. signInWithEmailAndPassword() — Login

    signInWithEmailAndPassword() —登录

  2. createUserWithEmailAndPassword() — Register

    createUserWithEmailAndPassword() -注册

  3. sendPasswordResetEmail() — Reset Password

    sendPasswordResetEmail() -重置密码

  4. signOut() — Logout

    signOut() —注销

实施所有身份验证逻辑 (Implementing all the authentication logic)

We need to add a listener, to check if the user has logged in or not, and to display the correct response for either. We need to add the listener in the app.component.ts, as it’s the first page of the app that is loaded.

我们需要添加一个侦听器,以检查用户是否已登录,并显示正确的响应。 我们需要在app.component.ts中添加侦听器,因为它是已加载的应用程序的第一页。

const authObserver = afAuth.authState.subscribe(user => {
    if (user) {
        this.rootPage = HomePage;
        authObserver.unsubscribe();
    } else {
        this.rootPage = LoginPage;
        authObserver.unsubscribe();
    }
});

Import the necessary other modules, like the HomePage, LoginPage, and AngularFireAuth.

导入必要的其他模块,例如HomePage,LoginPage和AngularFireAuth。

Let’s start coding the Register page first.

让我们首先开始对“注册”页面进行编码。

First, to add a new page to the app. There are two ways to do this;

首先,向应用程序添加一个新页面。 有两种方法可以做到这一点;

  1. Create a new folder within the pages folder inside src and create separate .scss, .ts and .html files.

    在src内的pages文件夹内创建一个新文件夹,并创建单独的.scss,.ts和.html文件。
  2. Or, be lazy (like me ?) and just type ionic g page <name of page> in the console. All three files will be auto-generated!

    或者,要懒惰(像我一样?),只需在控制台中键入ionic g page <name of page> 。 这三个文件将自动生成!

Since we need to conduct many validations on the data entered in the login, register and forgot password pages, we need to utilize a form group to have a track of all the fields in the form and to add any and all validation to each field, such as checking if the email looks like an actual email, password lengths, the works. We’ll first design the view of the page. In register.html, the form tag looks like so;

由于我们需要对登录,注册和忘记密码页面中输入的数据进行多次验证,因此我们需要利用表单组来跟踪表单中的所有字段,并向每个字段添加所有验证,例如检查电子邮件是否看起来像实际的电子邮件,密码长度以及有效方式。 我们将首先设计页面视图。 在register.html中,表单标记看起来像这样;

<form [formGroup]="signupForm" (submit)="signupUser()" novalidate>

novalidate is used as the actual validation is being added in the .ts file to the form group signupForm.

novalidate ,因为实际验证是在.ts文件中添加到表单组signupForm

Then copy the exact item tag that we have been using to add task names in the home page (but remove that button, id and [(ngModule)] this time!). Add a tag for the users’ full name, email, password and confirm password. The type of input tag for the latter two is password and email for the email tag. You will also need to add a formControlName to each input tag. Add in a button as well of the type submit, to submit the form. The body of your register page must now look like this;

然后复制我们一直用于在主页中添加任务名称的确切项目标签(但这次删除该按钮,id和[(ngModule)] !)。 为用户的全名,电子邮件,密码添加标签并确认密码。 后两个输入标签的类型是password和email标签的email。 您还需要向每个输入标签添加一个formControlName 。 添加一个按钮,以及提交类型,以提交表单。 现在,您的注册页面的主体必须看起来像这样;

<form [formGroup]="signupForm" (submit)="signupUser()" novalidate>
  <ion-item>
    <ion-input formControlName="firstName" type="text" placeholder="First Name"></ion-input>
  </ion-item>
  <ion-item>
    <ion-input formControlName="lastName" type="text" placeholder="Last Name"></ion-input>
  </ion-item>  
  <ion-item>
    <ion-input formControlName="email" type="email" placeholder="Email"></ion-input>
  </ion-item>
  <ion-item>
    <ion-input formControlName="password" type="password" placeholder="Password"></ion-input>
  </ion-item>
  <ion-item>
    <ion-input formControlName="retype" type="password" placeholder="Confirm Password"></ion-input>
  </ion-item>
  <ion-grid>
    <ion-row>
      <ion-col style="text-align: center">
        <button ion-button center-all type="submit" [disabled]="!signupForm.valid">Create an Account</button>
      </ion-col>
    </ion-row>
  </ion-grid>
<form>

The Register button is disabled until the Lets now add validators to each input, in the register.ts page. We will need to import the following modules to the top of the page,

在register.ts页面中,现在让Lets将验证器添加到每个输入之前,将禁用Register按钮。 我们将需要将以下模块导入页面顶部,

import { FormBuilder, FormGroup, Validators } from '@angular/forms';

initialize the form group outside of the constructor, so it can be accessed from anywhere in the component; public signupForm: FormGroup and initialize the form builder inside the parameters passed to the constructor, like so;

在构造函数之外初始化表单组,以便可以从组件中的任何位置进行访问; public signupForm: FormGroup并在传递给构造函数的参数内初始化表单构建器,就像这样;

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

Validators will be added to the form within the constructor like so;

验证器将像这样在构造函数中添加到表单中;

this.signupForm = formBuilder.group({
  email: ['', Validators.compose([Validators.required])],
  password: ['', Validators.compose([Validators.minLength(6), Validators.required])],
  retype: ['', Validators.compose([Validators.minLength(6), Validators.required])],
  firstName: ['', Validators.compose([Validators.maxLength(30), Validators.pattern('[a-zA-Z ]*'), Validators.required])],
  lastName: ['', Validators.compose([Validators.maxLength(30), Validators.pattern('[a-zA-Z ]*'), Validators.required])]
});

Validators.compose creates a validation check for the value, according to the validations passed in its parameters. Most of these Validators are self-explanatory. The pattern Validator checks if the value fits a specific regex. But one question remains, how to validate if an email looks like an email? Apparently, we need to make one….

Validators.compose根据在其参数中传递的验证为该值创建一个验证检查。 这些验证器大多数都是不言自明的。 模式验证器检查该值是否适合特定的正则表达式。 但是还有一个问题,如何验证电子邮件看起来像电子邮件? 显然,我们需要制造一个……。

But don’t worry! It’s quite simple and the only logic to it is to see if it fits a certain regex.

但是不用担心! 它非常简单,唯一的逻辑就是查看它是否适合某个正则表达式。

We need to make a new folder ‘validators’ in the src folder and a file ‘email.ts’ within it. We will be declaring a static method to check the email. When validating the email, we send the formControl to the Validator, so in that case, we will need to import FormControl. Once the email is tested against the regex, we need to return a value to convey if the email is valid or not. The final code for the email validator is;

我们需要在src文件夹中新建一个文件夹“ validators”,并在其中创建文件“ email.ts ”。 我们将声明一种检查电子邮件的静态方法。 验证电子邮件时,我们将formControl发送到Validator,因此在这种情况下,我们将需要导入FormControl 。 根据正则表达式对电子邮件进行测试后,我们需要返回一个值以传达电子邮件是否有效。 电子邮件验证程序的最终代码是;

import { FormControl } from '@angular/forms';

export class EmailValidator {  
  static isValid(control: FormControl) {
    const re = /^([a-zA-Z0-9_\-\.]+)@([a-zA-Z0-9_\-\.]+)\.([a-zA-Z]{2,5})$/.test(control.value);
    if (re) {
      return null;
    }
    return {
      "invalidEmail": true
    };
  }
}

Now import the EmailValidator into the register.ts and add it to the array within the Validators.compose method for the email input.

现在,将EmailValidator导入register.ts中,并将其添加到Validators.compose方法内的数组中,以用于电子邮件输入。

this.signupForm = formBuilder.group({
    email: ['', Validators.compose([Validators.required, EmailValidator.isValid])],
    ...
});

That’s it on the validation side.

验证方面就是这样。

Another added feature you can do is show an error message right below the input, or even have the input tag turn red if the validation returns a false. The code for the error message;

您可以执行的另一项附加功能是在输入下方显示一条错误消息,或者,如果验证返回false,则输入标签甚至变为红色。 错误消息的代码;

<ion-item class="error-message" *ngIf="!signupForm.controls.email.valid  && signupForm.controls.email.dirty">
    <p>Please enter a valid email.</p>
</ion-item>

*ngIf allows you to display the error only if the validation is false. The errors should be put right below each tag, altering the message and input name (in the above example ‘email’) accordingly.

*ngIf允许您仅在验证为false时显示错误。 错误应放在每个标签的正下方,从而相应地更改消息和输入名称(在上面的示例中为“电子邮件”)。

The code for a red input on validation error;

验证错误时红色输入的代码;

[class.invalid]="!signupForm.controls.email.valid && signupForm.controls.email.dirty"

Add this inside each input, again changing the inputs’ name accordingly.

将其添加到每个输入中,再次更改输入的名称。

Now to handle the button click!

现在要处理按钮单击!

Create the method signupUser(). We will be using the AngularFireAuth modules’ method createUserWithEmailAndPassword(). This returns a promise, that we need to capture and according to the result, handle either the sign in of the user or display an error message. To make it more user-friendly, also show a loading carousel to the user as signup takes place.

创建方法signupUser() 。 我们将使用AngularFireAuth模块的方法createUserWithEmailAndPassword() 。 这将返回一个承诺,我们需要捕获这个承诺,并根据结果处理用户的登录或显示错误消息。 为了使其更加用户友好,还应在注册发生时向用户显示一个加载轮播。

As the button is only enabled when the whole form is valid, we do not need to recheck on that fact. We will first check if the password and the retyped password are the same, and if they are, create the new user and add their information to the Firestore. If the two are different, display an error message in the alert, stating that they are different.

由于仅在整个表单有效时才启用该按钮,因此我们无需重新检查该事实。 我们将首先检查密码和重新输入的密码是否相同,如果相同,请创建新用户并将其信息添加到Firestore。 如果两者不同,则在警报中显示一条错误消息,指出它们不同。

signupUser() {
  if (this.signupForm.value.password == this.signupForm.value.retype) {
    this.afAuth.auth.createUserWithEmailAndPassword(this.signupForm.value.email, this.signupForm.value.password)
      .then(() => {
        let userId = this.afAuth.auth.currentUser.uid;
        let userDoc = this.firestore.doc<any>('users/' + userId);
        userDoc.set({
          firstName: this.signupForm.value.firstName,
          lastName: this.signupForm.value.lastName,
          email: this.signupForm.value.email
        });
        this.navCtrl.setRoot(HomePage);
      }, (error) => {
        this.loading.dismiss().then(() => {
          let alert = this.alertCtrl.create({
            message: error.message,
            buttons: [{ text: "Ok", role: 'cancel' }]
          });
          alert.present();
        });
      });

    this.loading = this.loadingCtrl.create({
      dismissOnPageChange: true,
      content: "Signing up.."
    });
    this.loading.present();
  } else {
    let alert = this.alertCtrl.create({
      message: "The passwords do not match.",
      buttons: [{ text: "Ok", role: 'cancel' }]
    });
    alert.present();
  }
}

You will need to additionally import AlertController, Loading, LoadingController, AngularFirestore and HomePage.

您将需要另外导入AlertControllerLoadingLoadingControllerAngularFirestoreHomePage

loading needs to be declared outside the constructor, so that it can be accessed by all the methods. AlertController, LoadingController and AngularFirestore needs to be initialized in the constructor parameters.

loading需要在构造函数外部声明,以便所有方法都可以访问它。 AlertControllerLoadingControllerAngularFirestore需要在构造函数参数中初始化。

And (finally) the register page is done!

并且(最后)完成注册页面!

Whew! ?? This is the longest post I’ve ever written. And there’s still more to go…..

ew! ?? 这是我写过的最长的帖子。 还有更多的事情要做.....

But don’t worry! The rest is all just copy + paste.

但是不用担心! 其余的全部只是复制+粘贴。

The next page to tackle is the Login page. Copy the entire Register page form to login.html, coz its time to make some changes for it to fit Login. Remove the first name, last name and retyped passwords’ input tags and error messages. On the form tag, change all instances of signupForm to loginForm.

下一个要处理的页面是“登录”页面。 将整个“注册”页面表单复制到login.html,请花点时间进行一些更改以使其适合Login。 删除名字,姓氏和重新输入的密码输入标签和错误消息。 在form标记上,将signupForm所有实例signupFormloginForm

Change the submit buttons’ text to ‘Login’ and the onSubmit method to loginUser(). Add two buttons as well, outside the form, to navigate to the register and reset password pages. The final body of login.html;

将提交按钮的文本更改为“ Login”,将onSubmit方法更改为loginUser() 。 在表单外,还添加两个按钮,以导航到注册和重置密码页面。 login.html的最终正文;

<form [formGroup]="loginForm" (submit)="loginUser()" novalidate>
  <ion-item>
    <ion-input formControlName="email" type="email" placeholder="Email" [class.invalid]="!loginForm.controls.email.valid && loginForm.controls.email.dirty"></ion-input>
  </ion-item>
  <ion-item class="error-message" *ngIf="!loginForm.controls.email.valid  && loginForm.controls.email.dirty">
    <p>Please enter a valid email.</p>
  </ion-item>
  <ion-item>
    <ion-input formControlName="password" type="password" placeholder="Password" [class.invalid]="!loginForm.controls.password.valid && loginForm.controls.password.dirty"></ion-input>
  </ion-item>
  <ion-item class="error-message" *ngIf="!loginForm.controls.password.valid  && loginForm.controls.password.dirty">
    <p>Your password must be more than 6 characters long</p>
  </ion-item>
  <ion-grid>
    <ion-row>
      <ion-col style="text-align: center">
        <button ion-button center-all type="submit" [disabled]="!loginForm.valid">Login</button>
      </ion-col>
    </ion-row>
  </ion-grid>
</form>
<button ion-button block clear color="danger" (click)="resetPwd()">
  I forgot my password
</button>
<button ion-button block clear (click)="createAccount()">
  Create a new account
</button>

There you have it! The UI is done.

你有它! 用户界面已完成。

The loginForm has the same Validators for the email and password fields. So, proceed to copy the same formBuilder, omitting the first name, last name and retyped password fields.

loginForm的电子邮件和密码字段具有相同的验证器。 因此,继续复制相同的formBuilder ,省略名字,姓氏和重新输入的密码字段。

this.loginForm = formBuilder.group({
    email: ['', Validators.compose([Validators.required, EmailValidator.isValid])],
    password: ['', Validators.compose([Validators.minLength(6), Validators.required])]
});

The loginUser() method has similar code to that of the signupUser method. So copy that on to the login.ts as well. The change to be made, is to remove the password comparison and accessing the database.

loginUser()方法的代码与signupUser方法的代码相似。 因此也将其复制到login.ts上。 要进行的更改是删除密码比较并访问数据库。

loginUser() {
 this.afAuth.auth.signInWithEmailAndPassword(this.loginForm.value.email, this.loginForm.value.password).then(() => {
   this.navCtrl.setRoot(HomePage);
 }, (error) => {
   this.loading.dismiss().then(() => {
     let alert = this.alertCtrl.create({
       message: error.message,
       buttons: [{ text: "Ok", role: 'cancel' }]
     });
     alert.present();
   });
 });
 this.loading = this.loadingCtrl.create({
   dismissOnPageChange: true,
   content: "Logging in.."
 });
 this.loading.present();
}

You will need to import the exact extra modules to the login.ts as well, with the exception of the AngularFirestore, as you will not be accessing the database now.

除AngularFirestore外,您还需要将确切的额外模块也导入login.ts中,因为您现在不会访问数据库。

Now to handle the buttons to the reset password and the registration page;

现在处理重置密码和注册页面的按钮;

resetPwd() {
    this.navCtrl.push(ResetPasswordPage);
}

createAccount() {
    this.navCtrl.push(RegisterPage);
}

The pages work like a stack; you push the next page to the top of the stack and pop from the top as well.

这些页面就像堆栈一样工作; 您将下一页推到堆栈的顶部,并从顶部弹出。

Bear with me, we have one more page to go. Yay! More copy+paste!

忍受我,我们还有一页。 好极了! 更多复制+粘贴!

For the reset password, we only require the email field, but still, need a form to validate the email entered. Much like for the Login page, copy the entire login.html form, remove all fields except the email input tag and error message, change all instances of loginForm to resetPwdForm. You are left with;

对于重设密码,我们仅需要电子邮件字段,但仍然需要一个表格来验证输入的电子邮件。 与“登录”页面非常相似,复制整个login.html表单,删除除电子邮件输入标签和错误消息之外的所有字段,将loginForm所有实例更改为resetPwdForm。 你留下了;

<form [formGroup]="resetPwdForm" (submit)="resetUserPwd()" novalidate>
  <ion-item>
    <ion-input formControlName="email" type="email" placeholder="Email" [class.invalid]="!resetPwdForm.controls.email.valid && resetPwdForm.controls.email.dirty"></ion-input>
  </ion-item>
  <ion-item class="error-message" *ngIf="!resetPwdForm.controls.email.valid  && resetPwdForm.controls.email.dirty">
    <p>Please enter a valid email.</p>
  </ion-item>
  <ion-grid>
    <ion-row>
      <ion-col style="text-align: center">
        <button ion-button center-all type="submit" color="danger" [disabled]="!resetPwdForm.valid">Reset Password</button>
      </ion-col>
    </ion-row>
  </ion-grid>
</form>

The same is to be done for the reset-password.ts file. The form builder looks like this;

对reset-password.ts文件执行相同的操作。 表单生成器如下所示;

this.resetPwdForm = formBuilder.group({
    email: ['', Validators.compose([Validators.required, EmailValidator.isValid])]
});

while the resetUserPwd() method looks like so;

resetUserPwd()方法看起来像这样;

resetUserPwd() {
 this.afAuth.auth.sendPasswordResetEmail(this.resetPwdForm.value.email).then((user) => {
   let alert = this.alertCtrl.create({
     message: "We just sent a link to reset your password to your email.",
     buttons: [{ text: "Ok", role: 'cancel',
       handler: () => {
         this.navCtrl.pop();
       }}]
   });
   alert.present();
 }, (error) => {
   let errorAlert = this.alertCtrl.create({
     message: error.message,
     buttons: [{ text: "Ok", role: 'cancel' }]
   });
   errorAlert.present();
 });
}

The handler code above pops the reset password page to show the login page once the request for the link is sent.

发送链接请求后,上面的处理程序代码将弹出重置密码页面以显示登录页面。

One last part (I’m so sorry! I’m tired too)…??

最后一部分(我很抱歉!我也很累)…??

The logout button, the easiest and smallest code!

注销按钮,最简单,最小的代码!

You need to put a button at the end of the header on the home page as shown below;

您需要在首页标题的末尾放置一个按钮,如下所示;

<ion-header>
	<ion-navbar>
		<ion-title>To-do List</ion-title>
		<ion-buttons end>
			<button ion-button (click)="logout()">Logout</button>
		</ion-buttons>
	</ion-navbar>
</ion-header>

The code to handle the logout in home.ts;

在home.ts中处理注销的代码;

logout() {
    return this.afAuth.auth.signOut().then(authData => {
        this.app.getRootNav().setRoot(LoginPage);
    });
}

The code after the ‘then’ takes the user back to the login page.

“然后”之后的代码会将用户带回到登录页面。

And that’s it! Finally! ??

就是这样! 最后! ??

To allow the app to use these pages, you need to include them in the app.module.ts page, in both the declarations and entryComponents arrays, like so;

为了允许应用使用这些页面,您需要将它们包括在声明和entryComponents数组中的app.module.ts页面中,就像这样;

@NgModule({
    ...
    declarations: [
        ...
        LoginPage, 
        RegisterPage, 
        ResetPasswordPage
    ],
    ...
    entryComponents: [
        ...
        LoginPage, 
        RegisterPage, 
        ResetPasswordPage
    ]
})

Let’s have a look at all we have achieved so far.

让我们来看看到目前为止我们已经取得的一切。

And there you have it! ?? It’s not so easy on the eyes, but it is definitely functional.

在那里,您拥有了! ?? 在眼睛上并不是那么容易,但是绝对可以。

As you can see, when a particular fields’ validation returns false, the input turns red, and the error message shows as well. The buttons stay disabled until all fields of the form are valid!

如您所见,当特定字段的验证返回false时,输入将变为红色,并且还会显示错误消息。 在表单的所有字段均有效之前,按钮将保持禁用状态!

Below, the user object has also been stored in Firestore, with the current users’ uid as the key to the document. It all works!

下方,用户对象也已存储在Firestore中,其中当前用户的uid为文档的键。 一切正常!

Now that authentication and by extension user objects has been implemented, we now go on to syncing up the CRUD operations with Firebase Cloud Firestore.

现在已经实现了身份验证和扩展的用户对象,我们现在继续将CRUD操作与Firebase Cloud Firestore同步。

第4步-将CRUD操作与Cloud Firestore同步 (Step 4 - Syncing CRUD actions with Cloud Firestore)

The coding will be quite simple, as we have already integrated AngularFire into our app. The major changes will be made only to the back-end logic in the home.ts file, and one simple addition to the home.html to handle lists we get from Firestore.

编码将非常简单,因为我们已经将AngularFire集成到了我们的应用程序中。 主要更改将仅对home.ts文件中的后端逻辑进行,对home.html进行的简单添加将处理从Firestore获得的列表。

CRUD中的C到Firestore (The C in CRUD to Firestore)

We’ll first start with adding functionality to the addTask() method. But first we need to import AngularFirestore to the home.ts and initialize it in the constructor, like so;

我们首先从向addTask()方法添加功能开始。 但是首先我们需要将AngularFirestore导入到home.ts中,并在构造函数中对其进行初始化,就像这样;

constructor(...public firestore: AngularFirestore) {}

As mentioned in the previous post, Firestore is not like its predecessor, it is not one big JSON structure. Instead, it works with something called documents. Each document is one uniquely JSON object that holds only one type of data, for example, the user object will only hold user data, such as their name, date of birth and other personal information, but not any other data.

如前一篇文章所述,Firestore与它的前身不同,它不是一个大的JSON结构。 相反,它可以处理称为文档的文件。 每个文档都是一个唯一的JSON对象,仅保存一种类型的数据,例如,用户对象将仅保存用户数据,例如其姓名,生日和其他个人信息,但不保存任何其他数据。

Many documents of the same type make up a collection. And sometimes an object can have a collection of different objects inside it, and that’s what we are doing today; making a collection of task objects for each user.

许多相同类型的文档组成一个集合。 有时,一个对象内部可以包含不同对象的集合,这就是我们今天正在做的事情; 为每个用户收集任务对象。

If you can remember, in the previous post, we took the user’s uid, a unique ID that Firebase assigns all its users that sign up as the ID for the users’ JSON object. We will be requiring it heavily today as well, so the first thing to do is capture the uid from AngularFireAuth. As many methods will be using this value, it will be best to declare this variable outside the constructor, then initializing it inside ionViewDidLoad.

如果您还记得的话,在上一篇文章中,我们采用了用户的uid,即唯一的ID,Firebase会为其分配的所有用户分配为用户的JSON对象的ID。 今天我们也将非常需要它,因此首先要做的是从AngularFireAuth中捕获uid。 由于许多方法都将使用此值,因此最好在构造函数外部声明此变量,然后在ionViewDidLoad内部ionViewDidLoad进行初始化。

We put it in ionViewdidLoad(), because sometimes the user details from AngularFireAuth is not ready by the constructor. And since we will be accessing only that collection within the users' object, go ahead and grab that as well, similar to the register page. All this is added within the call to get the userId.

我们将其放在ionViewdidLoad() ,因为有时构造函数无法准备好AngularFireAuth中的用户详细信息。 而且由于我们将仅访问用户对象内的该集合,因此继续进行操作,也类似于注册页面。 所有这些都添加在调用中以获取userId。

this.afAuth.authState.subscribe(user => {
    if (user) {
        this.userId = user.uid;
        this.fireStoreTaskList = this.firestore.doc<any>('users/' + this.userId).collection('tasks').valueChanges();
        this.fireStoreList = this.firestore.doc<any>('users/' + this.userId).collection('tasks');
    }
});

The reason why we have two lists is the fireStoreTaskList holds the list that we view, while the fireStoreList is the reference to the collection where we directly add the new tasks. The method valueChanges() returns an Observable List, which we can display in the view.

之所以有两个列表,是因为fireStoreTaskList保存了我们要查看的列表,而fireStoreList是对集合的引用,我们直接在其中添加了新任务。 方法valueChanges()返回一个Observable List,我们可以在视图中显示它。

We can now use this reference anywhere in the page. Using it to add a task in the addTask method is very simple. There is a need to have a specific ID for each task, as we will require it when attempting to update the taskName, so we need to generate the ID and use the set() method of the firestore collection, to create a new task object, inside the if condition, replacing the previous code that pushes the task name into taskList.

现在,我们可以在页面中的任何地方使用此参考。 使用它在addTask方法中添加任务非常简单。 每个任务都需要有一个特定的ID,因为在尝试更新taskName时会需要它,因此我们需要生成ID并使用firestore集合的set()方法来创建新的任务对象,在if条件中,替换先前的将任务名称推送到taskList代码。

let id = this.firestore.createId();
this.fireStoreList.doc(id).set({
    id: id,
    taskName: task
});

应用中CRUD中的R (The R in CRUD in the App)

Now to set up viewing the firestore list. The main part, getting the collection was done above. So the changes now need to be made to the home.html to view the fireStoreTaskList.

现在设置查看消防站列表。 主体部分已在上面完成。 因此,现在需要对home.html进行更改以查看fireStoreTaskList

The first change is to be in the *ngFor, the name of the list. Since the list will be a response back by firebase, its asynchronous. The normal *ngFor, will cause errors. We need to add an async pipe as well, like so;

第一个更改是在列表名称*ngFor中。 由于列表将是firebase的响应,因此它是异步的。 正常的*ngFor会导致错误。 我们也需要添加一个异步管道,就像这样;

<ion-item *ngFor="let todo of fireStoreTaskList | async">

We no longer need to keep track of the index, as we will be using the task ID to either delete or update its value. And the second change is the value that we will view since todo will now be an object, we need to display todo.taskName, as that’s what we have named the task variable in the task object.

我们将不再需要跟踪索引,因为我们将使用任务ID删除或更新其值。 第二个更改是我们将要查看的值,因为todo现在将成为一个对象,我们需要显示todo.taskName,因为这就是我们在task对象中为task变量命名的名称。

{{todo.taskName}}

And that’s it! Lets now have a look at both the app and Firestore, to see if it gets saved.

就是这样! 现在让我们同时查看应用程序和Firestore,以查看是否已保存。

It’s got saved!

它已保存!

There’s nothing much to it for the C and R in CRUD. Now to update then delete.

CRUD中的C和R没什么大不了的。 现在更新然后删除。

CRUD中的U到Firestore (The U in CRUD to Firestore)

Luckily, AngularFirestore has its own update function, which, given the documents’ ID as well as the values to be updated, can be done in one single line. But first, a small change in the home.html file, to allow this to happen. As said earlier, you don’t need the index of the task in the list to update or delete, but instead the document ID, which we have simply stored in the variable id of a task object.

幸运的是,AngularFirestore拥有自己的更新功能,鉴于文档的ID和要更新的值,它可以在一行中完成。 但是首先,对home.html文件进行一些小的更改,以使这种情况发生。 如前所述,您不需要更新或删除列表中任务的索引,而是需要文档ID,而我们只需将其存储在任务对象的变量ID中即可。

Our first order of business is to send the tasks’ id to the method from the button, like so;

我们的首要任务是从按钮将任务的ID发送到方法,就像这样;

<button ion-button clear (click)="updateTask(todo.id)">

Move over to home.ts and replace the code in the handler of the alert to;

移至home.ts并将警报处理程序中的代码替换为;

this.fireStoreList.doc(index).update({ taskName: data.editTask });

We first create a reference to the specific object that the user wishes to update using the doc() method, then sending the relevant data we wish to update into the update() method.

我们首先使用doc()方法创建对用户希望更新的特定对象的引用,然后将我们希望更新的相关数据发送到update()方法。

Now to see this functionality in action!

现在来看一下此功能的实际效果!

This one works too!

这个也可以!

Now onto the last change, delete.

现在到最后的更改,删除。

CRUD中的D到Firestore (The D in CRUD to Firestore)

Deleting is just as easy (or easier, really) than updating.

删除与更新一样容易(或实际上更容易)。

You will again, need to pass the tasks’ ID onto the delete button;

You will again, need to pass the tasks' ID onto the delete button;

<button ion-button clear (click)=”deleteTask(todo.id)”>

Again like for update, AngularFirestore has a function delete(), that is run on the reference of the document to be deleted, like so;

Again like for update, AngularFirestore has a function delete() , that is run on the reference of the document to be deleted, like so;

this.fireStoreList.doc(index).delete();

And now to watch the last functionality….

And now to watch the last functionality….

This one is functional too!

This one is functional too!

As you can see, the ‘Fold Clothes’ task with an ID of ‘NSskIVHEg4gKsT3U0xAV’ is no longer there, as it has been successfully deleted

As you can see, the 'Fold Clothes' task with an ID of 'NSskIVHEg4gKsT3U0xAV' is no longer there, as it has been successfully deleted

There you have it! Firebase integrated into all the CRUD operations.

你有它! Firebase integrated into all the CRUD operations.

Step 5 - Bonus content styling (Step 5 - Bonus content styling)

This is a short checklist of basic things that weren’t covered in the previous posts;

This is a short checklist of basic things that weren't covered in the previous posts;

  1. Custom styles ?

    Custom styles ?
  2. Images ?️

    Images ?️
  3. Custom fonts ?

    Custom fonts ?

Prettify the UI (Prettify the UI)

Going through my app, I was able to see a few things I wanted to change.

Going through my app, I was able to see a few things I wanted to change.

Remember those little messages below the input fields in the login, register and reset password pages?

Remember those little messages below the input fields in the login, register and reset password pages?

I just realized that, since they are essentially <ion-item>, they have a line at the bottom. Not that great.

I just realized that, since they are essentially <ion-item> , they have a line at the bottom. Not that great.

Thankfully, it’s a simple fix! There’s a global property called no-lines, that you need to add to the <ion-item> like so;

Thankfully, it's a simple fix! There's a global property called no-lines , that you need to add to the <ion-item> like so;

<ion-item ... no-lines>

So go ahead and add this to all the error message <ion-item> tags.

So go ahead and add this to all the error message <ion-item> tags.

Your error message now looks like this.

Your error message now looks like this.

Let’s move on to colors!

Let's move on to colors!

If you snooped around the project structure, you would have seen a folder called theme. The variables.scss file within has a color variable with 5 set colors. Keep the colors light and dark as they are, as well as danger, as we are using it for the reset password button and page. I will be only changing the primary and secondary color. I normally use coolors.co to find complementary colors for all the projects I’ve ever done.

If you snooped around the project structure, you would have seen a folder called theme. The variables.scss file within has a color variable with 5 set colors. Keep the colors light and dark as they are, as well as danger, as we are using it for the reset password button and page. I will be only changing the primary and secondary color. I normally use coolors.co to find complementary colors for all the projects I've ever done.

Disclaimer: Do not add more than those 5 colors to the object, as this causes multiple copies of components to be made for each of these colors. It will eventually add unwanted bulk to the project, as not all components with all colors are used. If you need to use more colors, add a new variable to hold only that color literal.

Disclaimer: Do not add more than those 5 colors to the object, as this causes multiple copies of components to be made for each of these colors. It will eventually add unwanted bulk to the project, as not all components with all colors are used. If you need to use more colors, add a new variable to hold only that color literal.

The colors I will be using are;

The colors I will be using are;

$colors: (
	primary:    #32B596,
	secondary:  #fff350,
	danger:     #f53d3d,
	light:      #f4f4f4,
	dark:       #222
);

The first place to splash some color is the top navbar.

The first place to splash some color is the top navbar.

Looked so bland right??

Looked so bland right??

Not any more.??

Not any more.??

All you need to do is add the color primary to the ion-navbar tag, like so;

All you need to do is add the color primary to the ion-navbar tag, like so;

<ion-navbar color='primary'>

You can add the color property similarly to other components. Such as, give the delete icon the color stored in danger, or the add and logout button the color in secondary;

You can add the color property similarly to other components. Such as, give the delete icon the color stored in danger, or the add and logout button the color in secondary;

I still hate the way that the logout button looks… To make it a proper button, simply add the property solid to the tag, like so;

I still hate the way that the logout button looks… To make it a proper button, simply add the property solid to the tag, like so;

<button ion-button solid color='secondary' (click)="logout()">Logout</button>

Another cool UI design I saw previously, had icons before each input tag on the login, register and reset password pages, so I decided to give that a try as well! It’s a pretty simple code, that you need to add within the <ion-item> tag but before the <ion-input> tag, like so;

Another cool UI design I saw previously, had icons before each input tag on the login, register and reset password pages, so I decided to give that a try as well! It's a pretty simple code, that you need to add within the <ion-item> tag but before the <ion-input> tag, like so;

<ion-item>
	<div class="item-note" item-start>
		<ion-icon name="at" color='primary'></ion-icon>
	</div>
	<ion-input formControlName="email" ...></ion-input>
</ion-item>

There is no icon that screams password, so I decided to use ? just like in the UI design I had a look at; and ? for the users’ names

There is no icon that screams password, so I decided to use ? just like in the UI design I had a look at; 和? for the users' names

Adding images (Adding images)

A picture says a thousand words… But we have no need for such pictures…. ?No matter!

A picture says a thousand words… But we have no need for such pictures…. ?No matter!

Adding pictures are not necessarily tough, but the path may get a bit confusing sometimes. You would assume that you need to add the actual path from the page to the image folder, which is ../../assets/imgs/imagename.png. The path you really need to add is the path from the app.html to the image in the image folder, and that path looks like assets/imgs/imagename.png.

Adding pictures are not necessarily tough, but the path may get a bit confusing sometimes. You would assume that you need to add the actual path from the page to the image folder, which is ../../assets/imgs/imagename.png . The path you really need to add is the path from the app.html to the image in the image folder, and that path looks like assets/imgs/imagename.png .

Any and all images you wish to use needs to be added to the folder src/assets/imgs. You can then use the image as if this was HTML;

Any and all images you wish to use needs to be added to the folder src/assets/imgs . You can then use the image as if this was HTML;

<img src="assets/imgs/imagename.png"/>

I want to add an image, kinda like a logo, to the login, register and reset password pages.

I want to add an image, kinda like a logo, to the login, register and reset password pages.

So that the image doesn’t exceed the page, we will also need to code some styling, and as this image will be in more than one page, we are required to write the styling in the app.scss page like so;

So that the image doesn't exceed the page, we will also need to code some styling, and as this image will be in more than one page, we are required to write the styling in the app.scss page like so;

.imageTop {
    height: 200px;
    padding: 20px;
    margin: auto;
    display: block;
}

All you need to do now is simply add the class to the img tag, class='imageTop'.

All you need to do now is simply add the class to the img tag, class='imageTop' .

Another image (or two) that you might want to change, is the splash page and app icon. You will first need to add either (or both) Android and iOS platforms, to use this feature. The command to add a platform is

Another image (or two) that you might want to change, is the splash page and app icon. You will first need to add either (or both) Android and iOS platforms, to use this feature. The command to add a platform is

ionic cordova platform add android

Or ios, if that’s your cup of ☕.

Or ios , if that's your cup of ☕.

Ionic can easily generate different sized splash pages and icons according to different phones when you run the command ionic cordova resources in the terminal. You will need internet for this, as ionic uploads both images to be analyzed to generate the other splash pages and icons.

Ionic can easily generate different sized splash pages and icons according to different phones when you run the command ionic cordova resources in the terminal. You will need internet for this, as ionic uploads both images to be analyzed to generate the other splash pages and icons.

Before that you need to add both the images, named splash.png and icon.png to the resources folder. The sizes of both images should be 2732*2732 and 1024*1024 respectively, for the many splash pages and app icons to be generated.

Before that you need to add both the images, named splash.png and icon.png to the resources folder. The sizes of both images should be 2732*2732 and 1024*1024 respectively, for the many splash pages and app icons to be generated.

That’s all for images!

That's all for images!

Typography Rox(cks)! (Typography Rox(cks)!)

First, find a font that speaks to you. The latest trends stick to sans serif fonts that are pretty easy to read. As pretty as many handwritten flowy fonts are, they are just a fail waiting to happen, like this one….

First, find a font that speaks to you. The latest trends stick to sans serif fonts that are pretty easy to read. As pretty as many handwritten flowy fonts are, they are just a fail waiting to happen, like this one….

Or this one,

Or this one,

???

???

Jokes aside, I picked the font ‘Alegreya Sans’ to use for this app. It can be found, here.

Jokes aside, I picked the font 'Alegreya Sans' to use for this app. It can be found, here .

Unpack all the fonts to the folder assets/fonts.

Unpack all the fonts to the folder assets/fonts .

All you need to do now is add the code below to the variables.scss found in the src/theme folder.

All you need to do now is add the code below to the variables.scss found in the src/theme folder.

@font-face {
	font-family: 'Alegreya Sans Regular';
	src: url("../assets/fonts/AlegreyaSans-Regular.otf");
}
$font-family-base: 'Alegreya Sans Regular';
$font-family-ios-base: 'Alegreya Sans Regular';
$font-family-md-base: 'Alegreya Sans Regular';
$font-family-wp-base: 'Alegreya Sans Regular';

The @font-face imports your font and gives it a name, so it can be used throughout the application.

The @font-face imports your font and gives it a name, so it can be used throughout the application.

The variable $font-family-base assigns the default font.

The variable $font-family-base assigns the default font.

The app now looks like this;

The app now looks like this;

As you can only view the splash page and icon on a real device, I have brought in my trusty phone into the mix (Sadly it ain’t an Apple to fit with the rest of the gifs/pics).

As you can only view the splash page and icon on a real device, I have brought in my trusty phone into the mix (Sadly it ain't an Apple to fit with the rest of the gifs/pics).

And that’s it for this series!!!!!??

And that's it for this series!!!!!??

Find the repo for this post, here.

Find the repo for this post, here .

I hope you all had fun and learned a lot on this journey with me!

I hope you all had fun and learned a lot on this journey with me!

Thank you for the read, and see you soon!??

Thank you for the read, and see you soon!??

翻译自: https://www.freecodecamp.org/news/creating-a-crud-to-do-app-using-ionic-4/

ionic应用环信创建聊天

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值