软件开发定制定制Vue3实战教程(快速入门)

实战教程(快速入门)

前言

软件开发定制定制本教程通过搭建一个简单项目,软件开发定制定制帮助读者快速入门Vue3项目实战,掌握Vue3、TS、Element Plus、axios等技术栈。

1.搭建

vue -V查看vue版本,需要在4.5.1版本之后,软件开发定制定制即可进行以下操作。

1.1 创建项目

(1)使用命令 vue create vue3-elementplus-demo 创建Vue项目。
(2)软件开发定制定制进入选项配置,选择 Manually select features,软件开发定制定制进行手动配置


(3)软件开发定制定制配置项如下

软件开发定制定制都选择完毕后,回车,软件开发定制定制项目即可创建完毕,使用VsCode软件开发定制定制或者按照提示进入和启动项目


1.2 软件开发定制定制清除多余文件,软件开发定制定制创建干净项目

(1)软件开发定制定制删除以下文件

(2)在views软件开发定制定制目录下创建Index.vue文件(后面处于方便,又将Index.vue修改成了Home.vue),内容如下:

<template>  <div>首页</div></template><script>export default {  name: 'Index'}</script><style scoped></style>
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11

(3)修改router/index.ts路由文件:

import { createRouter, createWebHistory, RouteRecordRaw } from 'vue-router'import Home from '../views/Home.vue'const routes: Array<RouteRecordRaw> = [  {    path: '/',    name: 'Index',    component: () => import('../views/Index.vue')  },]const router = createRouter({  history: createWebHistory(process.env.BASE_URL),  routes})export default router
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18

(4)修改App文件:

<template>  <div id="app">    <router-view />  </div></template><style>html,body,#app {  width: 100%;  height: 100%;}</style>
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15

修改完毕后,查看效果

(5)新建css/resset.css文件(上网搜关键词reset.css就有),并在index.html文件中引入,初始化样式

/** * Eric Meyer's Reset CSS v2.0 (http://meyerweb.com/eric/tools/css/reset/) * http://cssreset.com */  html, body, div, span, applet, object, iframe, h1, h2, h3, h4, h5, h6, p, blockquote, pre, a, abbr, acronym, address, big, cite, code, del, dfn, em, img, ins, kbd, q, s, samp, small, strike, strong, sub, sup, tt, var, b, u, i, center, dl, dt, dd, ol, ul, li, fieldset, form, label, legend, table, caption, tbody, tfoot, thead, tr, th, td, article, aside, canvas, details, embed,  figure, figcaption, footer, header, hgroup,  menu, nav, output, ruby, section, summary, time, mark, audio, video{   margin: 0;   padding: 0;   border: 0;   font-size: 100%;   font: inherit;   font-weight: normal;   vertical-align: baseline; } /* HTML5 display-role reset for older browsers */ article, aside, details, figcaption, figure,  footer, header, hgroup, menu, nav, section{   display: block; } ol, ul, li{   list-style: none; } blockquote, q{   quotes: none; } blockquote:before, blockquote:after, q:before, q:after{   content: '';   content: none; } table{   border-collapse: collapse;   border-spacing: 0; }   /* custom */ a{   color: #7e8c8d;   text-decoration: none;   -webkit-backface-visibility: hidden; } ::-webkit-scrollbar{   width: 5px;   height: 5px; } ::-webkit-scrollbar-track-piece{   background-color: rgba(0, 0, 0, 0.2);   -webkit-border-radius: 6px; } ::-webkit-scrollbar-thumb:vertical{   height: 5px;   background-color: rgba(125, 125, 125, 0.7);   -webkit-border-radius: 6px; } ::-webkit-scrollbar-thumb:horizontal{   width: 5px;   background-color: rgba(125, 125, 125, 0.7);   -webkit-border-radius: 6px; } html, body{   width: 100%;   font-family: "Arial", "Microsoft YaHei", "黑体", "宋体", "微软雅黑", sans-serif; } body{   line-height: 1;   -webkit-text-size-adjust: none;   -webkit-tap-highlight-color: rgba(0, 0, 0, 0); } html{   overflow-y: scroll; }   /*清除浮动*/ .clearfix:before, .clearfix:after{   content: " ";   display: inline-block;   height: 0;   clear: both;   visibility: hidden; } .clearfix{   *zoom: 1; }   /*隐藏*/ .dn{   display: none; }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 89
  • 90
  • 91
  • 92
  • 93
  • 94
  • 95
  • 96
  • 97
  • 98
  • 99
  • 100
  • 101


1.3 创建登录页面

创建LoginRegister.vue文件:

<template>  <div class="container">    <!-- form表单容器 -->    <div class="form-container">      <div class="signin-signup">        <!-- 登录 -->        <h1>登录</h1>        <!-- 注册 -->        <h1>注册</h1>      </div>    </div>  </div></template><script>export default {  name: 'LoginRegister'}</script><style scoped>.container {  position: relative;  width: 100%;  min-height: 100vh;  background-color: #fff;  overflow: hidden;}.form-container {  position: absolute;  left: 0;  top: 0;  width: 100%;  height: 100%;}.signin-signup {  position: relative;  top: 50%;  left: 75%;  transform: translate(-50%, -50%);  width: 44%;  transition: 1s 0.7s ease-in-out;  display: grid;  grid-template-columns: 1fr;  z-index: 5;}</style>
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47

在路由中引入

查看效果:

2.创建(引入sass)

2.1 引入sass

(1)查看当前node版本

(2)引入对应版本的node-sass和sass-load

当前已知 node-sass 与 node 版本对应如下:

node-sass 和 sass-loader 的常见版本对应关系如下:

node-sasssass-loader
4.3.04.1.1
4.7.2$7.0.3/7.3.1
6.0.110.0.1

(3)如果引入出现了问题,基本上就是node版本和sass版本不一致导致。此时需要创建一个新项目,将新项目中的package.json和package-lock.json复制到当前项目中,然后重新 npm i 即可。

2.2 创建404页面

(1)assets下创建img文件夹,加入404.gif

(2)创建404.vue

<template>  <div class="not-found">    <img src="../assets/img/404.gif" alt="" />  </div></template><script>export default {  name: '404'}</script><style lang="scss" scoped>.not-found {  width: 100%;  height: 100%;  overflow: hidden;  img {    width: 100%;    height: 100%;  }}</style>
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23

(3)router/index.ts中通过正则表达式匹配匹配失败的路由为404页面

3.构建登录注册页面(引入element-plus)

3.1 实现布局左右切换动画

因为本篇文章主要讲解的是Vue3和element-plus的用法,css部分就省略说明,有兴趣的同学可以自行研究。

<template>  <div class="container" :class="{ 'sign-up-mode': signUpMode }">    <!-- form表单容器 -->    <div class="form-container">      <div class="signin-signup">        <!-- 登录 -->        <h1>登录</h1>        <!-- 注册 -->        <h1>注册</h1>      </div>    </div>    <!-- 左右切换动画 -->    <div class="panels-container">      <div class="panel left-panel">        <div class="content">          <h3>Row,row,row your boat</h3>          <p>Gentlely down the stream</p>          <button @click="signUpMode = !signUpMode" class="btn transparent">            注册          </button>        </div>        <!-- <img src="@/assets" alt=""> -->      </div>      <div class="panel right-panel">        <div class="content">          <h3>Merrily,merrily,merrily,merrily,</h3>          <p>Life is but a dream</p>          <button @click="signUpMode = !signUpMode" class="btn transparent">            登录          </button>        </div>        <!-- <img src="@/assets" alt=""> -->      </div>    </div>  </div></template><script>import { ref } from 'vue'export default {  name: 'LoginRegister',  components: {},  // Vue3语法糖  // Vue2是通过data和methods传递数据和方法  // Vue3将data和methods直接耦合在了一起  setup() {    // 登录/注册模式    const signUpMode = ref(false)    return { signUpMode }  }}</script><style scoped>.container {  position: relative;  width: 100%;  min-height: 100vh;  background-color: #fff;  overflow: hidden;}.form-container {  position: absolute;  left: 0;  top: 0;  width: 100%;  height: 100%;}.signin-signup {  position: relative;  top: 50%;  left: 75%;  transform: translate(-50%, -50%);  width: 44%;  transition: 1s 0.7s ease-in-out;  display: grid;  grid-template-columns: 1fr;  z-index: 5;}/* 左右切换动画 */.social-text {  padding: 0.7rem 0;  font-size: 1rem;}.social-media {  display: flex;  justify-content: center;}.social-icon {  height: 46px;  width: 46px;  display: flex;  justify-content: center;  align-items: center;  margin: 0 0.45rem;  color: #333;  border-radius: 50%;  border: 1px solid #333;  text-decoration: none;  font-size: 1.1rem;  transition: 0.3s;}.social-icon:hover {  color: #4481eb;  border-color: #4481eb;}.btn {  width: 150px;  background-color: #5995fd;  border: none;  outline: none;  height: 49px;  border-radius: 49px;  color: #fff;  text-transform: uppercase;  font-weight: 600;  margin: 10px 0;  cursor: pointer;  transition: 0.5s;}.btn:hover {  background-color: #4d84e2;}.panels-container {  position: absolute;  height: 100%;  width: 100%;  top: 0;  left: 0;  display: grid;  grid-template-columns: repeat(2, 1fr);}.container:before {  content: '';  position: absolute;  height: 2000px;  width: 2000px;  top: -10%;  right: 48%;  transform: translateY(-50%);  background-image: linear-gradient(-45deg, #4481eb 0%, #04befe 100%);  transition: 1.8s ease-in-out;  border-radius: 50%;  z-index: 6;}.image {  width: 100%;  transition: transform 1.1s ease-in-out;  transition-delay: 0.4s;}.panel {  display: flex;  flex-direction: column;  align-items: flex-end;  justify-content: space-around;  text-align: center;  z-index: 6;}.left-panel {  pointer-events: all;  padding: 3rem 17% 2rem 12%;}.right-panel {  pointer-events: none;  padding: 3rem 12% 2rem 17%;}.panel .content {  color: #fff;  transition: transform 0.9s ease-in-out;  transition-delay: 0.6s;}.panel h3 {  font-weight: 600;  line-height: 1;  font-size: 1.5rem;}.panel p {  font-size: 0.95rem;  padding: 0.7rem 0;}.btn.transparent {  margin: 0;  background: none;  border: 2px solid #fff;  width: 130px;  height: 41px;  font-weight: 600;  font-size: 0.8rem;}.right-panel .image,.right-panel .content {  transform: translateX(800px);}/* ANIMATION */.container.sign-up-mode:before {  transform: translate(100%, -50%);  right: 52%;}.container.sign-up-mode .left-panel .image,.container.sign-up-mode .left-panel .content {  transform: translateX(-800px);}.container.sign-up-mode .signin-signup {  left: 25%;}.container.sign-up-mode form.sign-up-form {  opacity: 1;  z-index: 2;}.container.sign-up-mode form.sign-in-form {  opacity: 0;  z-index: 1;}.container.sign-up-mode .right-panel .image,.container.sign-up-mode .right-panel .content {  transform: translateX(0%);}.container.sign-up-mode .left-panel {  pointer-events: none;}.container.sign-up-mode .right-panel {  pointer-events: all;}@media (max-width: 870px) {  .container {    min-height: 800px;    height: 100vh;  }  .signin-signup {    width: 100%;    top: 95%;    transform: translate(-50%, -100%);    transition: 1s 0.8s ease-in-out;  }  .signin-signup,  .container.sign-up-mode .signin-signup {    left: 50%;  }  .panels-container {    grid-template-columns: 1fr;    grid-template-rows: 1fr 2fr 1fr;  }  .panel {    flex-direction: row;    justify-content: space-around;    align-items: center;    padding: 2.5rem 8%;    grid-column: 1 / 2;  }  .right-panel {    grid-row: 3 / 4;  }  .left-panel {    grid-row: 1 / 2;  }  .image {    width: 200px;    transition: transform 0.9s ease-in-out;    transition-delay: 0.6s;  }  .panel .content {    padding-right: 15%;    transition: transform 0.9s ease-in-out;    transition-delay: 0.8s;  }  .panel h3 {    font-size: 1.2rem;  }  .panel p {    font-size: 0.7rem;    padding: 0.5rem 0;  }  .btn.transparent {    width: 110px;    height: 35px;    font-size: 0.7rem;  }  .container:before {    width: 1500px;    height: 1500px;    transform: translateX(-50%);    left: 30%;    bottom: 68%;    right: initial;    top: initial;    transition: 2s ease-in-out;  }  .container.sign-up-mode:before {    transform: translate(-50%, 100%);    bottom: 32%;    right: initial;  }  .container.sign-up-mode .left-panel .image,  .container.sign-up-mode .left-panel .content {    transform: translateY(-300px);  }  .container.sign-up-mode .right-panel .image,  .container.sign-up-mode .right-panel .content {    transform: translateY(0px);  }  .right-panel .image,  .right-panel .content {    transform: translateY(300px);  }  .container.sign-up-mode .signin-signup {    top: 5%;    transform: translate(-50%, 0);  }}@media (max-width: 570px) {  form {    padding: 0 1.5rem;  }  .image {    display: none;  }  .panel .content {    padding: 0.5rem 1rem;  }  .container {    padding: 1.5rem;  }  .container:before {    bottom: 72%;    left: 50%;  }  .container.sign-up-mode:before {    bottom: 28%;    left: 50%;  }}/* 控制login & register显示 */form {  padding: 0rem 5rem;  transition: all 0.2s 0.7s;  overflow: hidden;}form.sign-in-form {  z-index: 2;}form.sign-up-form {  opacity: 0;  z-index: 1;}/* register */.loginForm,.registerForm {  margin-top: 20px;  background-color: #fff;  padding: 20px 40px 20px 20px;  border-radius: 5px;  box-shadow: 0px 5px 10px #cccc;}.submit-btn {  width: 100%;}.tiparea {  text-align: right;  font-size: 12px;  color: #333;  width: 100%;}.tiparea a {  color: #409eff;}</style>
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 89
  • 90
  • 91
  • 92
  • 93
  • 94
  • 95
  • 96
  • 97
  • 98
  • 99
  • 100
  • 101
  • 102
  • 103
  • 104
  • 105
  • 106
  • 107
  • 108
  • 109
  • 110
  • 111
  • 112
  • 113
  • 114
  • 115
  • 116
  • 117
  • 118
  • 119
  • 120
  • 121
  • 122
  • 123
  • 124
  • 125
  • 126
  • 127
  • 128
  • 129
  • 130
  • 131
  • 132
  • 133
  • 134
  • 135
  • 136
  • 137
  • 138
  • 139
  • 140
  • 141
  • 142
  • 143
  • 144
  • 145
  • 146
  • 147
  • 148
  • 149
  • 150
  • 151
  • 152
  • 153
  • 154
  • 155
  • 156
  • 157
  • 158
  • 159
  • 160
  • 161
  • 162
  • 163
  • 164
  • 165
  • 166
  • 167
  • 168
  • 169
  • 170
  • 171
  • 172
  • 173
  • 174
  • 175
  • 176
  • 177
  • 178
  • 179
  • 180
  • 181
  • 182
  • 183
  • 184
  • 185
  • 186
  • 187
  • 188
  • 189
  • 190
  • 191
  • 192
  • 193
  • 194
  • 195
  • 196
  • 197
  • 198
  • 199
  • 200
  • 201
  • 202
  • 203
  • 204
  • 205
  • 206
  • 207
  • 208
  • 209
  • 210
  • 211
  • 212
  • 213
  • 214
  • 215
  • 216
  • 217
  • 218
  • 219
  • 220
  • 221
  • 222
  • 223
  • 224
  • 225
  • 226
  • 227
  • 228
  • 229
  • 230
  • 231
  • 232
  • 233
  • 234
  • 235
  • 236
  • 237
  • 238
  • 239
  • 240
  • 241
  • 242
  • 243
  • 244
  • 245
  • 246
  • 247
  • 248
  • 249
  • 250
  • 251
  • 252
  • 253
  • 254
  • 255
  • 256
  • 257
  • 258
  • 259
  • 260
  • 261
  • 262
  • 263
  • 264
  • 265
  • 266
  • 267
  • 268
  • 269
  • 270
  • 271
  • 272
  • 273
  • 274
  • 275
  • 276
  • 277
  • 278
  • 279
  • 280
  • 281
  • 282
  • 283
  • 284
  • 285
  • 286
  • 287
  • 288
  • 289
  • 290
  • 291
  • 292
  • 293
  • 294
  • 295
  • 296
  • 297
  • 298
  • 299
  • 300
  • 301
  • 302
  • 303
  • 304
  • 305
  • 306
  • 307
  • 308
  • 309
  • 310
  • 311
  • 312
  • 313
  • 314
  • 315
  • 316
  • 317
  • 318
  • 319
  • 320
  • 321
  • 322
  • 323
  • 324
  • 325
  • 326
  • 327
  • 328
  • 329
  • 330
  • 331
  • 332
  • 333
  • 334
  • 335
  • 336
  • 337
  • 338
  • 339
  • 340
  • 341
  • 342
  • 343
  • 344
  • 345
  • 346
  • 347
  • 348
  • 349
  • 350
  • 351
  • 352
  • 353
  • 354
  • 355
  • 356
  • 357
  • 358
  • 359
  • 360
  • 361
  • 362
  • 363
  • 364
  • 365
  • 366
  • 367
  • 368
  • 369
  • 370
  • 371
  • 372
  • 373
  • 374
  • 375
  • 376
  • 377
  • 378
  • 379
  • 380
  • 381
  • 382
  • 383
  • 384
  • 385
  • 386
  • 387
  • 388
  • 389
  • 390
  • 391
  • 392
  • 393
  • 394
  • 395
  • 396
  • 397
  • 398
  • 399
  • 400
  • 401
  • 402
  • 403
  • 404
  • 405
  • 406
  • 407
  • 408
  • 409
  • 410
  • 411
  • 412
  • 413
  • 414
  • 415
  • 416
  • 417
  • 418

3.2 引入element-plus

(1)下载element-plus包:

npm i element-plus
  • 1


(2)在main.ts中引入

3.3 使用element-plus表单组件

(1)setup中加入登录表单loginUser

setup() {    // 登录/注册模式    const signUpMode = ref(false)    // 登录表单    const loginUser = reactive({      email: '',      password: ''    })    return { signUpMode, loginUser }  }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10

(2)使用表单组件

<!-- 登录 --><el-form  :model="loginUser"  label-width="100px"  class="login-form sign-in-form">  <el-form-item label="邮箱" prop="email">    <el-input v-model="loginUser.email" placeholder="Enter Email..." />  </el-form-item>  <el-form-item label="密码" prop="password">    <el-input      v-model="loginUser.password"      type="password"      placeholder="Enter Password..."    />  </el-form-item>  <el-form-item>    <el-button type="primary" class="submit-btn">提交</el-button>  </el-form-item>  <!-- 找回密码 -->  <el-form-item>    <p class="tiparea">忘记密码<a>立即找回</a></p>  </el-form-item></el-form>
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24

3.4 表单验证

(1)表单验证

关键代码:

// 校验规则const rules = reactive({  email: [    {      required: true,      type: 'email',      message: 'email格式错误',      trigger: 'blur'    }  ],  password: [    { required: true, message: '密码不得为空', trigger: 'blur' },    { min: 6, max: 30, message: '密码长度必须在6到30之间', trigger: 'blur' }  ]})
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15

(2)点击提交触发表单验证

关键代码:

<template>  <div class="container" :class="{ 'sign-up-mode': signUpMode }">    <!-- form表单容器 -->    <div class="form-container">      <div class="signin-signup">        <!-- 登录 -->        <el-form          :model="loginUser"          :rules="rules"          ref="loginForm"          label-width="100px"          class="login-form sign-in-form"        >          <el-form-item label="邮箱" prop="email">            <el-input v-model="loginUser.email" placeholder="Enter Email..." />          </el-form-item>          <el-form-item label="密码" prop="password">            <el-input              v-model="loginUser.password"              type="password"              placeholder="Enter Password..."            />          </el-form-item>          <el-form-item>            <el-button              @click="handleLogin('loginForm')"              type="primary"              class="submit-btn"              >提交</el-button            >          </el-form-item>          <!-- 找回密码 -->          <el-form-item>            <p class="tiparea">忘记密码<a>立即找回</a></p>          </el-form-item>        </el-form>        <!-- 注册 -->        <!-- <h1>注册</h1> -->      </div>    </div>    <!-- 左右切换动画 -->    <div class="panels-container">      <div class="panel left-panel">        <div class="content">          <h3>Row,row,row your boat</h3>          <p>Gentlely down the stream</p>          <button @click="signUpMode = !signUpMode" class="btn transparent">            注册          </button>        </div>        <!-- <img src="@/assets" alt=""> -->      </div>      <div class="panel right-panel">        <div class="content">          <h3>Merrily,merrily,merrily,merrily,</h3>          <p>Life is but a dream</p>          <button @click="signUpMode = !signUpMode" class="btn transparent">            登录          </button>        </div>        <!-- <img src="@/assets" alt=""> -->      </div>    </div>  </div></template><script>import { ref, reactive, getCurrentInstance } from 'vue'export default {  name: 'LoginRegister',  components: {},  // Vue3语法糖  // Vue2是通过data和methods传递数据和方法  // Vue3将data和methods直接耦合在了一起  setup() {    // 通过解构getCurrentInstance,获取this,这里的this就是ctx    const { ctx } = getCurrentInstance()    // 登录/注册模式    const signUpMode = ref(false)    // 登录表单    const loginUser = reactive({      email: '',      password: ''    })    // 校验规则    const rules = reactive({      email: [        {          required: true,          type: 'email',          message: 'email格式错误',          trigger: 'blur'        }      ],      password: [        { required: true, message: '密码不得为空', trigger: 'blur' },        { min: 6, max: 30, message: '密码长度必须在6到30之间', trigger: 'blur' }      ]    })    // 触发登录方法    const handleLogin = (formName) => {      console.log(ctx)      ctx.$refs[formName].validate((valid) => {        if (valid) {          console.log('submit!')        } else {          console.log('error submit!')          return false        }      })    }    return { signUpMode, loginUser, rules, handleLogin }  }}</script>
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 89
  • 90
  • 91
  • 92
  • 93
  • 94
  • 95
  • 96
  • 97
  • 98
  • 99
  • 100
  • 101
  • 102
  • 103
  • 104
  • 105
  • 106
  • 107
  • 108
  • 109
  • 110
  • 111
  • 112
  • 113
  • 114
  • 115

点击提交,完成验证:

4.类型匹配和代码抽离

4.1 代码抽离

创建utils文件夹和loginValidators.ts文件,将LoginRegister.vue中的loginUser和rules剪切抽离到该文件中,LoginRegister.vue再通过import导入。测试正常运行。

loginValidators.ts

import { ref, reactive  } from 'vue'// 登录表单export const loginUser = reactive({  email: '',  password: ''})// 校验规则export const rules = reactive({  email: [    {      required: true,      type: 'email',      message: 'email格式错误',      trigger: 'blur'    }  ],  password: [    { required: true, message: '密码不得为空', trigger: 'blur' },    { min: 6, max: 30, message: '密码长度必须在6到30之间', trigger: 'blur' }  ]})
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23

4.2 类型匹配

4.2.1 ts文件类型匹配

将鼠标悬浮在 loginUserreactive 上,可以看到对应的类型提示,写入interface User 并复制提示。rules重复操作。

import { ref, reactive  } from 'vue'interface User{  email: string;  password: string;}// 登录表单export const loginUser = reactive<User>({  email: '',  password: ''})interface Rules{  email: {      required: boolean;      type: string;      message: string;      trigger: string;  }[];  password: ({      required: boolean;      message: string;      trigger: string;      min?: undefined;      max?: undefined;  } | {      min: number;      max: number;      message: string;      trigger: string;      required?: undefined;  })[];}// 校验规则export const rules = reactive<Rules>({  email: [    {      required: true,      type: 'email',      message: 'email格式错误',      trigger: 'blur'    }  ],  password: [    { required: true, message: '密码不得为空', trigger: 'blur' },    { min: 6, max: 30, message: '密码长度必须在6到30之间', trigger: 'blur' }  ]})
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50

此时如果将字段修改为不符合规范的类型,则会有报错提示

这对于初次使用ts的同学可能不太适应,认为完全没有必要。但是这其实对于大型项目的维护来说,相对比js友好许多。

4.2.2 vue文件中的ts类型匹配

(1)在script标签中加入lang="ts"标识,发现参数都没有类型匹配。

(2)类型匹配
1、对于any类型的变量——ctx,使用// @ts-ignore进行类型忽略
2、对于其他类型的变量,在变量后加入:类型即可,比如formName:stringvalid:boolean

5.抽离登录组件

(1)创建组件LoginForm.vue,将LoginRegister.vue中的表单内容(包括组件、数据、方法和样式,顺便对样式做了点优化)复制到该文件中。

LoginForm

<template>  <!-- 登录 -->  <el-form    :model="loginUser"    :rules="rules"    ref="loginForm"    label-width="100px"    class="login-form sign-in-form"  >    <el-form-item label="邮箱" prop="email">      <el-input v-model="loginUser.email" placeholder="Enter Email..." />    </el-form-item>    <el-form-item label="密码" prop="password">      <el-input        v-model="loginUser.password"        type="password"        placeholder="Enter Password..."      />    </el-form-item>    <el-form-item>      <el-button        @click="handleLogin('loginForm')"        type="primary"        class="submit-btn"        >提交</el-button      >    </el-form-item>    <!-- 找回密码 -->    <el-form-item>      <p class="tiparea">忘记密码<a>立即找回</a></p>    </el-form-item>  </el-form></template><script lang="ts">import { getCurrentInstance } from 'vue'export default {  name: 'LoginForm',  props: {    loginUser: {      type: Object,      required: true    },    rules: {      type: Object,      required: true    }  },  setup() {    // 通过解构getCurrentInstance,获取this,这里的this就是ctx    // @ts-ignore    const { ctx } = getCurrentInstance()    // 触发登录方法    const handleLogin = (formName: string) => {      console.log(ctx)      ctx.$refs[formName].validate((valid: boolean) => {        if (valid) {          console.log('submit!')        } else {          console.log('error submit!')          return false        }      })    }    return { handleLogin }  }}</script><style scoped>/* register */.login-form,.register-form {  background-color: #fff;  padding: 50px 80px 20px 20px;  border-radius: 5px;  box-shadow: 0px 5px 10px #cccc;}.submit-btn {  width: 100%;}.tiparea {  text-align: right;  font-size: 12px;  color: #333;  width: 100%;}.tiparea a {  color: #409eff;}</style>
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 89
  • 90
  • 91
  • 92
  • 93

6. 实现注册表单

其实完全可以仿照登录组件的写法,先在LoginRegister.vue中写出form组件,填充form数据,实现表单验证,最后再抽离出来。但是如果已经熟练的话,建议可以直接分为3个部分写完(ts——对应的数据和验证规则,component——组件,vue——引入到父组件中)

6.1 创建注册表单ts——存放注册表单及其验证规则

创建registerValidator.ts文件,用于存放注册表单及其验证规则。
可以先复制之前的loginValidator.ts文件的内容,然后进行修改(注意确认密码password2的验证规则写法)。

registerValidator.ts

import { reactive  } from 'vue'interface RegisterUser{  name:string;  email: string;  password: string;  password2: string;  role:string}// 登录表单export const registerUser = reactive<RegisterUser>({  name:'',  email: '',  password: '',  password2: '',  role:''})interface RegisterRules{  name: {    required: boolean;    message: string;    trigger: string;}[];  email: {      required: boolean;      type: string;      message: string;      trigger: string;  }[];  password: ({      required: boolean;      message: string;      trigger: string;  } | {      min: number;      max: number;      message: string;      trigger: string;  })[];  password2: ({      required: boolean;      message: string;      trigger: string;  } | {      min: number;      max: number;      message: string;      trigger: string;  } | {    validator:(rule: RegisterRules, value: string, callback: any)=>any;    trigger:string  })[];  role: {    required: boolean;    message: string;    trigger: string;}[];}const validatePass2 = (rule: RegisterRules, value: string, callback: any) => {  if (value === '') {    callback(new Error('请再次输入密码'))  } else if (value !== registerUser.password) {    callback(new Error("两次输入的密码不一致!"))  } else {    callback()  }}// 校验规则export const registerRules = reactive<RegisterRules>({  name: [    {      required: true,      message: '用户名不得为空',      trigger: 'blur'    }  ],  email: [    {      required: true,      type: 'email',      message: 'email格式错误',      trigger: 'blur'    }  ],  password: [    { required: true, message: '密码不得为空', trigger: 'blur' },    { min: 6, max: 30, message: '密码长度必须在6到30之间', trigger: 'blur' }  ],  password2: [    { required: true, message: '确认密码不得为空', trigger: 'blur' },    { min: 6, max: 30, message: '密码长度必须在6到30之间', trigger: 'blur' },    { validator: validatePass2, trigger: 'blur' }  ],  role: [    {      required: true,      message: '角色不得为空',      trigger: 'blur'    }  ],})
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 89
  • 90
  • 91
  • 92
  • 93
  • 94
  • 95
  • 96
  • 97
  • 98
  • 99
  • 100
  • 101
  • 102
  • 103
  • 104
  • 105

完成后将 registerUserregisterRules 引入到LoginRegister.vue中备用。

6.2 创建注册表单组件

创建注册表单组件RegisterForm.vue,模仿或者直接复制LoginForm.vue组件,稍作修改(包括样式、数据和方法)

<template>  <!-- 登录 -->  <el-form    :model="registerUser"    :rules="registerRules"    ref="registerForm"    label-width="100px"    class="register-form sign-up-form"  >    <el-form-item label="用户名" prop="name">      <el-input v-model="registerUser.name" placeholder="Enter Name..." />    </el-form-item>    <el-form-item label="邮箱" prop="email">      <el-input v-model="registerUser.email" placeholder="Enter Email..." />    </el-form-item>    <el-form-item label="密码" prop="password">      <el-input        v-model="registerUser.password"        type="password"        placeholder="Enter Password..."      />    </el-form-item>    <el-form-item label="确认密码" prop="password2">      <el-input        v-model="registerUser.password2"        type="password"        placeholder="Enter Password again..."      />    </el-form-item>    <el-form-item label="角色" prop="role">      <el-select v-model="registerUser.role">        <el-option label="管理员" value="admin"></el-option>        <el-option label="用户" value="user"></el-option>        <el-option label="游客" value="visitor"></el-option>      </el-select>    </el-form-item>    <el-form-item>      <el-button        @click="handleRegister('registerForm')"        type="primary"        class="submit-btn"        >提交</el-button      >    </el-form-item>  </el-form></template><script lang="ts">import { getCurrentInstance } from 'vue'export default {  name: 'registerForm',  props: {    registerUser: {      type: Object,      required: true    },    registerRules: {      type: Object,      required: true    }  },  setup() {    // 通过解构getCurrentInstance,获取this,这里的this就是ctx    // @ts-ignore    const { ctx } = getCurrentInstance()    // 触发登录方法    const handleRegister = (formName: string) => {      console.log(ctx)      ctx.$refs[formName].validate((valid: boolean) => {        if (valid) {          console.log('submit!')        } else {          console.log('error submit!')          return false        }      })    }    return { handleRegister }  }}</script><style scoped>/* register */.login-form,.register-form {  background-color: #fff;  padding: 50px 80px 20px 20px;  border-radius: 5px;  box-shadow: 0px 5px 10px #cccc;}.submit-btn {  width: 100%;}.tiparea {  text-align: right;  font-size: 12px;  color: #333;  width: 100%;}.tiparea a {  color: #409eff;}</style>
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 89
  • 90
  • 91
  • 92
  • 93
  • 94
  • 95
  • 96
  • 97
  • 98
  • 99
  • 100
  • 101
  • 102
  • 103
  • 104
  • 105

6.3 在LoginRegister.vue中引入使用

最后在LoginRegister.vue中引入使用即可


7. 封装axios

7.1 下载axios

(1)使用命令npm i axios下载axios

7.2 封装axios

创建utils/http.ts文件,用于封装axios请求(为了避免混乱,所以取名http.ts,不过叫做axios也无不可)。

http.ts

import axios,{AxiosRequestConfig,AxiosResponse} from 'axios'import { ElLoading } from 'element-plus'import { ElMessage } from 'element-plus'let loading:any;const startLoading = () =>{  interface Options{    lock: boolean;    text: string;    background: string;}  const options:Options = {    lock: true,    text: 'Loading',    background: 'rgba(0, 0, 0, 0.7)'  }  loading = ElLoading.service(options)}const endLoading = ()=>{  loading.close()}// 请求拦截axios.interceptors.request.use((config:AxiosRequestConfig<any>)=>{  // 开始Loading  startLoading()  return config})// 响应拦截axios.interceptors.response.use((res:AxiosResponse<any, any>)=>{  // 结束Loading  endLoading()  // console.log('成功响应拦截',res)  // 如果请求成功直接返回响应数据  if(res.status === 200){    return res.data  }},error=>{  // 结束Loading  endLoading()  // console.log('失败响应拦截',error)  const { response: res } = error  const msg = typeof res.data === 'string' ? res.data: res.data.error || '请求失败,请稍后重试'  ElMessage.error(msg)  // 错误提醒  return Promise.reject(error)})export default axios
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51

7.3 解决跨域问题(配置vue.config.js,设置代理)

创建vue.config.js文件,配置如下:

module.exports = {  devServer: {    open: true,    host: 'localhost',    port: 8080,    https: false,    hotOnly: false,    // 设置跨域    proxy: {      '/api': {        target: 'http://imissu.herokuapp.com',        ws: true,        changeOrigin: true,        pathRewrite: {          '^api': ''        }      }    },    before: (app) => {}  }}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22

7.4 使用axios发起请求

API地址:

7.4.1 创建api文件夹,规范使用api(推荐)

(1)创建api/loginRegister.ts,引入axios,规范export注册接口

import axios from '@/utils/http'// 注册接口export function register(params: any) {  return axios({    url: '/api/v1/auth/register',    method: 'post',    headers: {      'Content-Type': 'application/json;charset=UTF-8'    },    data: params  })}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14

(2)在注册组件中使用

<template>  <!-- 登录 -->  <el-form    :model="registerUser"    :rules="registerRules"    ref="registerForm"    label-width="100px"    class="register-form sign-up-form"  >    <el-form-item label="用户名" prop="name">      <el-input v-model="registerUser.name" placeholder="Enter Name..." />    </el-form-item>    <el-form-item label="邮箱" prop="email">      <el-input v-model="registerUser.email" placeholder="Enter Email..." />    </el-form-item>    <el-form-item label="密码" prop="password">      <el-input        v-model="registerUser.password"        type="password"        placeholder="Enter Password..."      />    </el-form-item>    <el-form-item label="确认密码" prop="password2">      <el-input        v-model="registerUser.password2"        type="password"        placeholder="Enter Password again..."      />    </el-form-item>    <el-form-item label="角色" prop="role">      <el-select v-model="registerUser.role">        <el-option label="管理员" value="admin"></el-option>        <el-option label="用户" value="user"></el-option>        <el-option label="游客" value="visitor"></el-option>      </el-select>    </el-form-item>    <el-form-item>      <el-button        @click="handleRegister('registerForm')"        type="primary"        class="submit-btn"        >提交</el-button      >    </el-form-item>  </el-form></template><script lang="ts">import { getCurrentInstance } from 'vue'import { useRouter } from 'vue-router'import { register } from '@/api/loginRegister'// import { ElMessage } from 'element-plus'export default {  name: 'registerForm',  props: {    registerUser: {      type: Object,      required: true    },    registerRules: {      type: Object,      required: true    }  },  setup(props: any) {    // 通过解构getCurrentInstance,获取this,这里的this就是ctx    // @ts-ignore    const { ctx, proxy } = getCurrentInstance()    const router = useRouter()    // 触发登录方法    const handleRegister = (formName: string) => {      console.log(ctx)      ctx.$refs[formName].validate(async (valid: boolean) => {        if (valid) {          // const res = await proxy.$http({          //   url: '/api/v1/auth/register',          //   method: 'post',          //   headers: {          //     'Content-Type': 'application/json;charset=UTF-8'          //   },          //   data: props.registerUser          // })          const res = await register(props.registerUser)          proxy.$message.success(res.data)          router.push('/')        } else {          return false        }      })    }    return { handleRegister }  }}</script><style scoped>/* register */.login-form,.register-form {  background-color: #fff;  padding: 50px 80px 20px 20px;  border-radius: 5px;  box-shadow: 0px 5px 10px #cccc;}.submit-btn {  width: 100%;}.tiparea {  text-align: right;  font-size: 12px;  color: #333;  width: 100%;}.tiparea a {  color: #409eff;}</style>
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 89
  • 90
  • 91
  • 92
  • 93
  • 94
  • 95
  • 96
  • 97
  • 98
  • 99
  • 100
  • 101
  • 102
  • 103
  • 104
  • 105
  • 106
  • 107
  • 108
  • 109
  • 110
  • 111
  • 112
  • 113
  • 114
  • 115
  • 116
  • 117
  • 118

登录功能模仿注册功能即可。

7.4.2 全局注册axios(不推荐,也没必要)

(1)在main.ts中全局挂载axios

import axios from '@/utils/http'app.config.globalProperties.$http = axios
  • 1
  • 2

(2)在注册组件中使用,关键代码:

// @ts-ignoreconst { ctx, proxy } = getCurrentInstance()const res = await proxy.$http({  url: '/api/v1/auth/register',  method: 'post',  headers: {    'Content-Type': 'application/json;charset=UTF-8'  },  data: props.registerUser})
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11

(3)不推荐的原因是,随着版本的更替,貌似axios全局挂载的位置也发生了变化,之前是在ctx,之后的版本又换成了proxy;另外一个原因是,这种方式不便于一个接口多处使用,复用性较差。

8.总结

Vue3 和 Vue2 的几个明显的区别:
(1)Vue2使用的选项式API,Vue3使用的是组合式API。前者随着项目页面体积的增大,对于代码的管理会给使用者带来更大的心智负担;后者组合式的写法,将data和methods等组合在一起,更容易理解和使用。不过对于初学者而言,可能会有些不太适应。
(2)Vue2使用的js构建的源码和使用方式,Vue3使用ts构建的源码,使用方式也支持ts,对于大型项目而言,更加友好,不过对于小型项目而言,往往使用者无法一下子看出ts对于强类型支持带来的好处,相反会觉得麻烦和没有必要。
(3)由于该项目是为了简要说明Vue3和Vue2在页面中的区别,方便急于使用Vue3的同学构建项目和页面,所以没有将Vue3更多的特性展示出来。在下一篇文章中,将会通过一个更加完整的项目,对Vue3的更多其他特性以及和Vue2的区别进行深入的解析和说明,敬请期待。

网站建设定制开发 软件系统开发定制 定制软件开发 软件开发定制 定制app开发 app开发定制 app开发定制公司 电商商城定制开发 定制小程序开发 定制开发小程序 客户管理系统开发定制 定制网站 定制开发 crm开发定制 开发公司 小程序开发定制 定制软件 收款定制开发 企业网站定制开发 定制化开发 android系统定制开发 定制小程序开发费用 定制设计 专注app软件定制开发 软件开发定制定制 知名网站建设定制 软件定制开发供应商 应用系统定制开发 软件系统定制开发 企业管理系统定制开发 系统定制开发