写在前面

本期内容是适合第一次使用vue或者golang开发的,内容会以实战的形式来讲解。看懂本段内容需要了解基础内容有html,css,最好可以看一下vue的基础。并且这里的每个知识点不可能详细解说,只会告诉你大概做什么的,入门切记要不求甚解,不然学到自闭

远征的第一步,启动一个vue项目

我们这次主要是写一个以vue为前端框架的登陆和注册,因此我们的第一步也就是创建一个vue项目,并且这个内容在上一节已经讲过了,不过这里我们可以再操作一番。

vue create your_projectname

然后根据之前的配置即可,甚至你可以使用默认配置,接下来假设我们已经创建好了一个项目,然后我们需要怎么启动他呢?

cd your_projectname

npm run serve

用这个命令一段时间之后,就可以看到项目被启动在了localhost:8080上(也有可能不是8080端口),在浏览器输入链接即可访问。

基础的vue操作

写代码一定需要一个ide的,因此这里推荐一个ide叫vscode,可在这里下载安装:vscode官网,然后也可以看看这篇大佬的内容把插件装好:Vue推荐VSCode开发必备的插件

装完之后在刚刚vue项目目录下面输入code .,就可以看到ide下的环境。你应该会大概看到这样的文件结构:

一看有一大堆文件,一时间肯定讲不完,所以通过实际操作一个个演示到底是作什么的?

首先可以打开一下App.vue,可以看到这样几个两个结构:<templarte>和<style>,template的就是网页html中body里面的内容,而style就是你平时写的css文件,然后可以把App.vue改成这样:

<template>
<div id="app">
<router-view/>
</div>
</template>

这步操作到底在做什么呢?其实是我们之后写的vue文件是这个文件的子模板,就是router-view,所以我们可以在这里把不必要的元素给删了,就保留我上面那段即可。以后甚至可以添加一些公共的元素在里面。

接下来可以在views里面新建一个文件叫login.vue,这就是我们的登陆注册界面了。如果说权限不够不给你新建文件的可以在上一层文件夹输入

sudo chmod -R 777 your_projectname

这样即可获得权限去创建文件,输入完之后可以先来一套<template>,<script>和<style>的标签,这里script就是平时的js脚本,但是在格式上又有所差别,之后写再讲。我们这里写一个跟icloud类似的登录界面来实战演练好了。

首先写一个主题框架,注意:每一个template下面只有由一大的div包住,不能写2个及以上!这里我就写了一个main的div包住其他内容,之后写了一个bg作为背景,一个page作为页面。

<template>
<div class="main">
<div class="bg">
</div>
<div class="page">
</div>
</div>
</template>

如果你写成这样,就包含了2个div,这样会直接报错的。

<template>
<div class="bg">
</div>
<div class="page">
</div>
</template>

接下来我们给bg加一个背景,在style添加如下的基础样式这段样式不是重点,觉得太多不能理解的可以跳过。

.main{
width: 99vw;
height: 95vh;
display: flex;
align-items: center;
justify-content: center;
} .bg{
width: 100%;
height: 100%;
background: url("https://i.loli.net/2019/09/28/ytGhbzr3Xe7TsAc.png") no-repeat;
position: absolute;
background-size: cover;
-webkit-filter: blur(5px);
-moz-filter: blur(5px);
-ms-filter: blur(5px);
-o-filter: blur(5px);
filter: blur(5px);
} .page{
width: 300px;
height: 300px;
position: relative;
background-color: rgba(255,255,255,0.6);
border-radius: 8px;
box-shadow: 0 10px 20px rgba(0,0,0,0.5);
overflow: hidden;
z-index: 2;
padding: 30px;
box-sizing: border-box;
}

这样就完成了最初的样子,给背景加了点高斯模糊,什么?你不知道怎么看到这个界面,这里的话就要讲一下router.js了。routers是管理浏览器路由的,你可以看到文件中有2个例子,他们的path就是代表路由路径,name的话代表这个路径的名字表示,这两个例子有一个区别,在component中,这个东西是对应的vue文件的物理路径,可能难以理解的是后者:

component: () => import(/* webpackChunkName: "about" */ './views/About.vue')

这个的意思代表懒加载,其实不用这个的时候,他是有预先加载的,而用了懒加载的时候只有到走到这个路由的时候才会真的加载这个文件,这可以省下不少的初次加载时间。所以我们添加一下我们的路由在routes下。

    {
path:'/login',
name:'login',
component:()=>import("./views/login.vue")
}

写出来一个扁平化的登陆界面

我们要作一个好康的界面首先要有一个好康的组件工具,这里我推荐一下上手方便的element,这里可以康他的文档:vue官方文档,我们可以直接按他文档的方法进行安装

sudo npm i element-ui -S

然后我们在main.js弄成全局的组件,这个main.js实际上就是一个初始化的地方,我们可以在这里加各种全局用组件。

import ElementUI from 'element-ui';
import 'element-ui/lib/theme-chalk/index.css'; Vue.use(ElementUI);

接下来我们给这个中间的框加上账号和密码输入框,在page下写入,就会出现输入框和密码框了,这里的输入框是用element做的:Input 输入框

<div class="login">
<el-input v-model="username" placeholder="邮箱账号" style="margin-top:70px"></el-input>
<el-input placeholder="请输入密码" v-model="password" show-password></el-input>
</div>

但是你只写这么点东西八成报错,这里有个叫v-model的东西,他是用于绑定一个变量的,只要这个元素的值变化了,变量的值也相应发生变化,但是我们的变量在哪呢?所以我们这里得在script的export default下面加入如下的内容。

    data(){
return{
username: '',
password: '',
}
},

这里的data是固定格式,在return中输入变量即可,这样上面就能拿到变量了,所以也就不会报错了。

给输入框弄成弹入弹出的效果

这段内容需要一点动画基础,不过实际上并不复杂,我会对其讲解一下,首先我们先跟icloud一样,在输入框弄2个按钮。

<el-input v-model="username" placeholder="邮箱账号" style="margin-top:70px">
<template slot="append"><el-button type="primary" icon="el-icon-check"></el-button></template>
</el-input>
<el-input placeholder="请输入密码" v-model="password" show-password v-if="showPwd">
<template slot="append"><el-button type="primary" icon="el-icon-check"></el-button></template>
</el-input>

这里有2个知识点,slot和v-if,slot的话理解起来很复杂,现在可以理解成一个可以插入任何东西的地方,这里就把button插入到el-input的末尾,刚入门就理解到这里就好了。而v-if就很明显,只有if里面的内容是true,他才会被渲染,false的时候他就会被移除出Dom,所以也要在data中加入相应的变量。

接下来我们给按钮添加一个事件,当我们按下去的时候判断邮箱格式是否正确,因此我们在button加入如下内容:

<el-button type="primary" icon="el-icon-check" @click="checkEmail">

这个@click就是点击这个按钮触发里面的方法,以后看到@或者v-on就是事件的监听器,所以@click顾名思义,就是监听一个click事件,当被点击就触发里面的方法。因此我们也要写这么一个方法,自然是写在script下面了。

    methods:{
checkEmail(){
var pattern = /^([a-zA-Z0-9]+[_|\_|\.]?)*[a-zA-Z0-9]+@([a-zA-Z0-9]+[_|\_|\.]?)*[a-zA-Z0-9]+\.[a-zA-Z]{2,3}$/;
if(pattern.test(this.username)){
this.showPwd = true;
}else{
this.$message.error('邮箱格式错了哦~');
}
},
},

在script下面的methods是存放各种方法的,这里的方法都可以被template中的标签所使用,所以我们在这里加入。这里可以讲解一下邮箱验证的方法:

首先你需要明白正则表达式,明白了基本就解决了,不明白的可以看:30分钟正则表达式教程。因此这里就是拿正则表达式匹配,匹配了呢就让v-if的变量为true,请注意,我们在方法中用到data中的变量都是this.变量名,而下面的那个this.$message是element中的错误提示框,详情可以看消息提示

这还不够,人家做的东西是有动画的,你的咋就没有呢?所以得给他加个动画,那么这里可能要用到transition组件了,这个组件是通过检测v-if和v-show的状态,在状态变化的时候给动画的,我现在给密码框加动画就是如此:

<transition name="showpwd">
<el-input placeholder="请输入密码" v-model="password" show-password v-if="showPwd" style="z-index:-2">
<template slot="append"><el-button type="primary" icon="el-icon-check"></el-button></template>
</el-input>
</transition>

这里name务必独一无二,因为接下来要在css用到这个名字。

然后我们在css写上动画,css的名字是根据你的name属性来取的,而他又分为进入和离开状态,因为内容太多了,详细请看:进入/离开 & 列表过渡。不过注意的是这里的translateY中的值要跟我们的input高度一致,这样效果才可以出来。

.showpwd-enter,.showpwd-leave-to{
transform:translateY(-40px);
} .showpwd-enter-active,.showpwd-leave-active{
transition:all .8s ease-out;
}

现在你输入邮箱试一试,是不是能看到效果了呢?

让注册界面融入进来

说是让注册界面融入进来,但是我们好像连个注册界面都还没有。因此我们先写一个注册界面,先贴写好的源码:

<div class="register" v-else key="register">
<el-form :model="register_form" status-icon :rules="rules" ref="ruleForm" label-width="70px">
<el-form-item label="用户名" prop="username" style="margin-top:70px">
<el-input type="text" v-model="register_form.username"></el-input>
</el-form-item>
<el-form-item label="邮箱" prop="email" style="margin-top:40px">
<el-input type="text" v-model="register_form.email"></el-input>
</el-form-item>
<el-form-item label="密码" prop="pass" style="margin-top:40px">
<el-input type="password" v-model="register_form.password" autocomplete="off"></el-input>
</el-form-item>
<el-form-item label="确认密码" prop="checkPass" style="margin-top:40px">
<el-input type="password" v-model="register_form.checkPass" autocomplete="off"></el-input>
</el-form-item>
<el-form-item style="margin-top:40px;">
<el-button type="primary" @click="submitForm('ruleForm')">注册</el-button>
</el-form-item>
</el-form>
</div>

这个界面是一个form表单:element表单。这里呢他是一个带有验证机制的form,就是说你每次输入一个内容的时候他都会去匹配规则,看看是否输入了正确的内容。这里在el-form有个一个:rules的东西,这个就是规则列表,注意,这里的:代表的是里面的内容是变量,假设不加:,他的意思就是rules这个字符串,而加上了他会代表data()中的变量。接下来可以看一看rules是怎么写的。

            rules: {
username: [
{validator: (rule, value, callback) => {
if(value==""){
callback(new Error('请输入用户名'));
}
callback();
}, trigger: 'blur' }
],
email: [
{ validator: (rule, value, callback) => {
var pattern = /^([a-zA-Z0-9]+[_|\_|\.]?)*[a-zA-Z0-9]+@([a-zA-Z0-9]+[_|\_|\.]?)*[a-zA-Z0-9]+\.[a-zA-Z]{2,3}$/;
if(!pattern.test(value)){
callback(new Error('请输入正确的邮箱格式'));
}
callback();
}, trigger: 'blur' }
],
pass: [
{ validator: (rule, value, callback) => {
if (value === '') {
callback(new Error('请输入密码'));
}
callback();
}, trigger: 'blur' }
],
checkPass: [
{ validator: (rule, value, callback) => {
if (value === '') {
callback(new Error('请再次输入密码'));
}else if (value !== this.register_form.password) {
callback(new Error('两次输入密码不一致!'));
}
callback();
}, trigger: 'blur' }
],
},

这里的代码每一种验证规则都是由validator: (rule, value, callback) 组成,接下来用一个匿名函数=>{}来写内容,value代表的是值,而callback代表回调的内容,如果匹配不正确需要callback(new Error),正确的话回调一个空的即可。最后还有一个trigger属性,这个表示触发这个判断的时机,这里我都使用了blur,就是焦点离开时触发。最后只需要在你需要进行校验的表单元素写上prop="",里面的内容写上你的校验规则的名称,就可以进行校验了。最后强调一点,仔细看,他只是一个dict变量,因此我们要把它在data()里面

接下来有了注册界面了,那我们怎么弄到页面里面呢?我就想了想把他们做成一个翻转的特效,让他出现在这里面,所以我们最后的代码结构如下:

 <div class="box_3d">
<transition name="page" mode="out-in">
<div class="login" v-if="sideLogin" key="login">
</div>
<div class="register" v-else key="register">
</div>
</transition>
</div>

为什么要作一个box_3d在这里呢,他其实是来控制一个3d的效果的,他的css如下:

.box_3d{
perspective: 500px;
-webkit-perspectiv: 500px;
transform-style: preserve-3d;
}

使用perspective和transform-style可以给他的子物体获得一个3d的效果,这里可能需要一些透视的知识,perspective代表是焦距,他的等于你的眼睛,而我们平时看到的画面是你的眼睛观察物体的投影,所以越大他越靠后,看到的物体会越小。而transform-style是选择子物体的模式,这里选3d才可以让子物体3d化,perspective要发挥效果必须设置成3d。只要设置了2者,你有了子物体的3d化,也有了自己的眼睛,因此就能看到3d的效果了。

其次这里的transition有个mode,选择out-in的话就是先消失原物体,之后再载入新的物体,这里就会有2个动画,对于下面的2个div,用v-if和v-else既可以控制2者的开关,并且key一定要设置,因为transition是通过key的不同区分物体的,最后贴一下2个页面的css:

.page-enter-active, .page-leave-active {
transition: all .5s linear;
} .page-leave-to{
transform: rotateY(-90deg);
} .page-enter{
transform: rotateY(90deg);
}

接下来的计划

做到这里界面差不多做完了,但是你会问,这就只有一个样子啊,我又不能注册又不能登陆的。那是必须的,我们后端都没有哪来的登陆注册可言,因此接下来应该用golang开始写一套后端了,这会在接下来开始写的。下面会贴出这里的全部源码,如果按上面的思路没做出来可以先复制下来看看效果,然后再研究哪里的思路有问题:

<template>
<div class="main">
<div class="bg">
</div>
<div class="box_3d"> <transition name="page" mode="out-in">
<div class="login" v-if="sideLogin" key="login">
<el-input placeholder="邮箱账号" v-model="username" style="margin-top:70px;z-index:18;" @input="checkEmailEmp" @keyup.enter.native="checkEmail">
<template slot="append">
<el-button ttype="primary" icon="el-icon-check" circle @click="checkEmail" v-if="showButton"></el-button>
</template>
</el-input>
<transition name="fade">
<el-input type="password" placeholder="密码" v-model="password" v-if="showpsd" key="pwd-open" @keyup.enter.native="dologin">
<template slot="append">
<el-button type="primary" icon="el-icon-check" circle @click="dologin" v-if="!showButton"></el-button>
</template>
</el-input>
</transition>
</div> <div class="register" v-else key="register">
<el-form :model="register_form" status-icon :rules="rules" ref="ruleForm" label-width="70px">
<el-form-item label="用户名" prop="username" style="margin-top:70px">
<el-input type="text" v-model="register_form.username"></el-input>
</el-form-item>
<el-form-item label="邮箱" prop="email" style="margin-top:40px">
<el-input type="text" v-model="register_form.email"></el-input>
</el-form-item>
<el-form-item label="密码" prop="password" style="margin-top:40px">
<el-input type="password" v-model="register_form.password" autocomplete="off"></el-input>
</el-form-item>
<el-form-item label="确认密码" prop="checkPass" style="margin-top:40px">
<el-input type="password" v-model="register_form.checkPass" autocomplete="off"></el-input>
</el-form-item>
<el-form-item style="margin-top:40px;">
<el-button type="primary" @click="submitForm('ruleForm')">注册</el-button>
</el-form-item>
</el-form>
</div>
</transition>
</div>
<div class="footer">
<p style="font" @click="changePage">{{sideLogin? "注册账号": "登录账号"}}</p >
</div>
</div> </template> <script>
export default {
name:"login",
data(){
return{
username: "",
password: "",
showpsd: false,
showButton: true,
sideLogin: true,
view: 'login',
register_form:{
username:"",
email:"",
password:"",
checkpass:"",
},
rules: {
username: [
{validator: (rule, value, callback) => {
if(value==""){
callback(new Error('请输入用户名'));
}
callback();
}, trigger: 'blur' }
],
email: [
{ validator: (rule, value, callback) => {
var pattern = /^([a-zA-Z0-9]+[_|\_|\.]?)*[a-zA-Z0-9]+@([a-zA-Z0-9]+[_|\_|\.]?)*[a-zA-Z0-9]+\.[a-zA-Z]{2,3}$/;
if(!pattern.test(value)){
callback(new Error('请输入正确的邮箱格式'));
}
callback();
}, trigger: 'blur' }
],
password: [
{ validator: (rule, value, callback) => {
if (value === '') {
callback(new Error('请输入密码'));
}
callback();
}, trigger: 'blur' }
],
checkPass: [
{ validator: (rule, value, callback) => {
if (value === '') {
callback(new Error('请再次输入密码'));
}else if (value !== this.register_form.password) {
callback(new Error('两次输入密码不一致!'));
}
callback();
}, trigger: 'blur' }
],
},
}
},
methods:{
checkEmail(){
var pattern = /^([a-zA-Z0-9]+[_|\_|\.]?)*[a-zA-Z0-9]+@([a-zA-Z0-9]+[_|\_|\.]?)*[a-zA-Z0-9]+\.[a-zA-Z]{2,3}$/;
if(pattern.test(this.username)){
this.showpsd = true;
this.showButton = false;
}else{
this.$message.error('邮箱格式不正确!');
}
},
checkEmailEmp(newstr){
if(newstr == ""){
this.showpsd = false;
this.showButton = true;
}
},
dologin(){ },
changePage(){
this.sideLogin = !this.sideLogin
if(this.sideLogin){
this.view = "login"
}else{
this.view = "register"
}
},
submitForm(){ },
resetForm(){ }
}
}
</script> <style>
.main{
width:99vw;
height:95vh;
display: flex;
align-items: center;
justify-content: center;
}
.bg{
width: 100%;
height: 100%;
background: url("https://i.loli.net/2019/09/28/ytGhbzr3Xe7TsAc.png") no-repeat;
background-size: cover;
position: absolute;
-webkit-filter: blur(5px);
-moz-filter: blur(5px);
-ms-filter: blur(5px);
-o-filter: blur(5px);
filter: blur(5px);
} .login{
width: 300px;
height: 300px;
position: relative;
border-radius: 8px;
background-color: rgba(255,255,255,0.6);
box-shadow: 0 10px 20px rgba(0,0,0,0.5);
overflow: hidden;
z-index: 2;
padding: 30px;
box-sizing: border-box;
} .register{
width: 400px;
height: 500px;
position: relative;
border-radius: 8px;
background-color: rgba(255,255,255,0.6);
box-shadow: 0 10px 20px rgba(0,0,0,0.5);
overflow: hidden;
z-index: 2;
padding: 10px 50px 10px 20px;
box-sizing: border-box;
} .fade-enter-active,.fade-leave-active{
transition: all .5s ease;
} .fade-enter, .fade-leave-to /* .fade-leave-active below version 2.1.8 */ {
transform: translateY(-40px);
/* opacity: 0; */
} .footer{
width: 200px;
height: 30px;
position: absolute;
top: 90%;
left: 49%;
} .footer > p{
color: floralwhite;
text-shadow:5px 2px 6px #000;
} .footer >p:hover{
cursor: pointer;
} .page-enter-active, .page-leave-active {
transition: all .5s linear;
} .page-leave-to{
transform: rotateY(-90deg);
} .page-enter{
transform: rotateY(90deg);
} .box_3d{
perspective: 500px;
-webkit-perspectiv: 500px;
transform-style: preserve-3d;
}
</style>

[终极巨坑]golang+vue开发日记【二】,登陆界面制作(一)的更多相关文章

  1. [终极巨坑]golang+vue开发日记【三】,登陆界面制作(二)

    写在前面 本期内容是承接上期已经做好了登陆界面来写的,不过本期是以golang为主,可能需要大家把最基本的语法结构熟悉一下:菜鸟教程.这样的话方便展开,自然而然的,本篇也是直接实战为主.这次需要依赖m ...

  2. [终极巨坑]golang+vue开发日记【一】,环境搭建篇

    写在前面 这个golang+vue大部分的内容是基于bydmm(橙卡)大佬的视频学来的,我在这里只是做一下个人开发的笔记,就是图一个乐,毕竟我只是个应届毕业生,如果真的要学请:bydmm的b站空间. ...

  3. 基于Golang的游戏服务器框架cellnet开发日记(二)

    看官们肯定还有大部分不是很熟悉Actor模型. 我这里基于Erlang, Skynet等语言和框架库来实战型解释下Actor模型.  Actor概念 Actor模型和OO类似, 都是符合人的思维模式进 ...

  4. VUE开发(二)nginx配合vue来实现前后端分离部署

    一.引言 由于本地是采用vue+spring boot实现的前后端分离项目,本机启动的时候先启动后场服务,再单独启动vue工程,然后可以实现全流程贯穿,但是我们要部署到服务器上的时候,一般都是打一个j ...

  5. 基于Html5 Plus + Vue + Mui 移动App 开发(二)

    基于Html5 Plus + Vue + Mui 移动App 开发(二) 界面效果: 本页面采用Html5 Plus + Vue + Mui 开发移动界面,本页面实现: 1.下拉刷新.上拉获取更多功能 ...

  6. 使用vue开发项目需要注意的问题和可能踩到的坑

    最近,在公司给一些刚刚使用vue进行开发的同学做了一次分享, 其中包括一些vue开发中需要注意的点, 以及一些可能会踩到的坑.具体内容如下: 一.生命钩子使用需要注意的地方 1.beforeCreat ...

  7. oracle入坑日记<二>认识oracle(含sqlplus基础使用)

    1.SID(数据库实例) 1.1. oracle安装的时候有一项叫[全局数据库名]的填写项,这个就是oracle的SID也是数据库的唯一标识符: 1.2.一个oracle数据库有且只有一个SID(一般 ...

  8. 使用vue开发微信公众号下SPA站点的填坑之旅

    原文发表于本人博客,点击进入使用vue开发微信公众号下SPA站点的填坑之旅 本文为我创业过程中,开发项目的填坑之旅.作为一个技术宅男,我的项目是做一个微信公众号,前后端全部自己搞定,不浪费国家一分钱^ ...

  9. 【原创】shadowebdict开发日记:基于linux的简明英汉字典(二)

    全系列目录: [原创]shadowebdict开发日记:基于linux的简明英汉字典(一) [原创]shadowebdict开发日记:基于linux的简明英汉字典(二) [原创]shadowebdic ...

随机推荐

  1. Linux执行shell脚本的方法

    Linux下有个脚本/home/start.sh,常用的两种执行方法如下: 1../start.sh.注意此时start.sh脚本文件必须有可执行权限-x.类似的有以绝对路径来执行:/home/sta ...

  2. JavaScript sort函数

    默认排序法则: 按照String类型ASCII码大小排序 如果要倒序排序,我们可以把大的数放前面: var arr = [10, 20, 1, 2]; arr.sort(function (x, y) ...

  3. vlc for mac设置中文的方法

    VLC for mac是一款mac系统下的多媒体播放器,支持播放MPEG-1.MPEG-2.MPEG-4.DivX.MP3和OGG,以及DVD.VCD.等各种流媒体协议在内的多种协议格式,并且能够对电 ...

  4. Django纪要

    Django流程 mvt 创建工程 路由的引导 web 应用程序b发送请求 -->uwsgi-->Django框架-->接收请求处理业务逻辑返回响应-->b本质 接收请求 业务 ...

  5. UDP丢包原因总结

    丢包检查方法 给每个UDP包编号,对比收发端的接收到的包.对于UDP协议层上的包,例如RTP包,可以从RTP包中读出包的序列号进行判断. 抓包.发送端和接收端分别抓包.linux下可以使用tcpdum ...

  6. Server Tomcat v8.5 Server at localhost was unable to start within 45 seconds. If the server requires more time, try increasing the timeout in the server editor.

    Server Tomcat v9.0 Server at localhost was unable to start within 45 seconds. If the server requires ...

  7. linux查找历史命令

    1.ctr+r  输入搜索关键词 2.&history 3.上箭头翻看

  8. 010-Spring aop 001-核心说明-拦截指定类与方法、基于自定义注解的切面

    一.概述 面向切面编程(AOP)是针对面向对象编程(OOP)的补充,可以非侵入式的为多个不具有继承关系的对象引入相同的公共行为例如日志.安全.事务.性能监控等等.SpringAOP允许将公共行为从业务 ...

  9. linux安装redis时报collect2: fatal error: cannot find 'ld'和In file included from adlist.c:34:0:

    如题,看了下该ld命令所在文件: [root@centos redis-]# whereis ld ld: /usr/bin/ld.gold /usr/bin/ld /usr/bin/ld.bfd / ...

  10. [ERROR] ionic-app-scripts has unexpectedly closed (exit code 1).

    这个错误是因为缺失 '@ionic/app-scripts',只要安装 '@ionic/app-scripts' 即可. 解决方法:npm install @ionic/app-scripts@lat ...