Get started directly with Vue3 from scratch (new detailed explanation in 2023)

This article will explain the following tutorials  

1. Build scaffolding 2. Create 404 page

3. Build login registration page 4. Type matching and code extraction

5. Extract the login component 6. Implement the registration form

7. Encapsulate axios


Preface

This tutorial helps readers quickly get started with Vue3 project practice by building a simple project, and master Vue3, TS, Element Plus, axios and other technology stacks.

1. Build scaffolding

vue -VCheck the vue version. It needs to be version 4.5.1 or later to perform the following operations.

1.1 Create project

(1) Use the command vue create vue3-elementplus-demo to create a Vue project.
(2) Enter the option configuration, select Manually select features, and perform manual configuration.

 (3) The configuration items are as follows 

 After selecting everything, press Enter and the project will be created. Use VsCode or follow the prompts to enter and start the project. 

Insert image description here

 

1.2 Clear redundant files and create a clean project

(1) Delete the following files

 (2) Create the Index.vue file in the views directory (for convenience later, Index.vue was modified to Home.vue) with the following content: 

(3) Modify the router/index.ts routing file:

 (4) Modify the App file:

 After modification, check the effect

(5) Create a new css/resset.css file (search the keyword reset.css on the Internet to find it), and introduce it into the index.html file to initialize the style. 

/**
 * 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.3 Create login page

Create the LoginRegister.vue file: 

<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>

 introduced in routing

  View the effect:

2. Create a 404 page (introducing sass) 

2.1 Introducing sass

(1) Check the current node version

 

(2) Introduce the corresponding versions of node-sass and sass-load. The  currently known node-sass and node versions correspond to the following: https://github.com/sass/node-sass 

 The common version correspondences between node-sass and sass-loader are as follows:

(3) If there is a problem with the introduction, it is basically caused by the inconsistency between the node version and the sass version. At this time, you need to create a new project, copy package.json and package-lock.json in the new project to the current project, and then re-npm i. 

2.2 Create a 404 page

(1) Create the img folder under assets and add 404.gif 

(2) Create 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>

 (3) The route that fails to match through regular expression matching in router/index.ts is a 404 page

 

3. Build a login registration page (introducing element-plus)

3.1 Implement layout switching animation left and right 

Because this article mainly explains the usage of Vue3 and element-plus, the explanation of the css part is omitted. Interested students can study it by themselves.

 

<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>

3.2 Introduction of element-plus

(1) Download the element-plus package:

npm i element-plus

 

 

(2)Introduced in main.ts 

 3.3 Using element-plus form component

(1) Add login form loginUser to setup

setup() {
    // 登录/注册模式
    const signUpMode = ref(false)
    // 登录表单
    const loginUser = reactive({
      email: '',
      password: ''
    })
    return { signUpMode, loginUser }
  }

 (2) Use form components

<!-- 登录 -->
<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>

 3.4 Form validation

(1) Form verification

 Key code:

// 校验规则
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' }
  ]
})

 (2) Click submit to trigger form verification

Key code:

<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>

 Click Submit to complete verification:

4. Type matching and code extraction

4.1 Code extraction 

Create the utils folder and loginValidators.tsfile, cut LoginRegister.vueand extract the loginUser and rules into the file, and import LoginRegister.vue through import. The test runs normally.

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' }
  ]
})

4.2 Type matching

4.2.1 ts file type matching

Hover the mouse over the reactive of loginUser, you can see the corresponding type prompt, write interface User and copy the prompt. rules Repeat the operation.         

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' }
  ]
})

 At this time, if you modify the field to a type that does not comply with the specification, an error message will appear.

This may not be suitable for students who are using ts for the first time, and they think it is completely unnecessary. But this is actually much more friendly than js for the maintenance of large projects.

4.2.2 ts type matching in vue files

(1) Add the lang="ts" mark to the script tag and find that the parameters have no type matching. 

(2) Type matching
1. For any type variables - ctx, use // @ts-ignore to ignore the type.
2. For other types of variables, just add: type after the variable, such as formName: string and valid: boolean

5. Remove the login component

(1) Create the component LoginForm.vue and copy the form content in LoginRegister.vue (including components, data, methods and styles, with some optimization of the style) to the file. 

 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>


6. Implement registration form

In fact, you can completely imitate the writing method of the login component. First write the form component in LoginRegister.vue, fill in the form data, implement form verification, and finally extract it. But if you are already proficient, it is recommended to write it directly in three parts (ts - corresponding data and verification rules, component - component, vue - introduced into the parent component)

6.1 Create registration form ts - store the registration form and its validation rules

Create the registerValidator.ts file to store the registration form and its validation rules.
You can first copy the contents of the previous loginValidator.ts file and then modify it (pay attention to the verification rules for confirming the password 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'
    }
  ],
})

After completion, introduce registerUser and registerRules into LoginRegister.vue for later use.

6.2 Create registration form component

Create the registration form component RegisterForm.vue, imitate or directly copy the LoginForm.vue component, and modify it slightly (including style, data and methods)

<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>

6.3 Introduced and used in LoginRegister.vue

Finally, introduce and use it in LoginRegister.vue

 

7. Encapsulate axios

7.1 Download axios

(1) Use the command npm i axios to download axios

7.2 Encapsulating axios

Create a utils/http.ts file to encapsulate axios requests (in order to avoid confusion, it is named http.ts, but it is okay to call it axios).

 http.ts:

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) => {}
  }
}

7.3 Solve cross-domain problems (configure vue.config.js, set proxy)

Create the vue.config.js file with the following configuration:

7.4 Use axios to initiate a request

API address: http://imissu.herokuapp.com/

7.4.1 Create an api folder and use the api in a standardized way (recommended)

(1) Create api/loginRegister.ts, introduce axios, and standardize the export registration interface

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
  })
}

(2) Used in registration components

<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>

The login function can imitate the registration function.

7.4.2 Register axios globally (not recommended and unnecessary)

(1) Mount axios globally in main.ts

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

(2) Used in the registration component, key code:

// @ts-ignore
const { 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
})

(3) The reason why it is not recommended is that with the change of versions, it seems that the location of axios global mount has also changed. It was previously in ctx, and later versions were changed to proxy; another reason is that this method does not It is convenient for one interface to be used in multiple places, but the reusability is poor.

8. Summary

There are several obvious differences between Vue3 and Vue2:
(1) Vue2 uses an optional API, while Vue3 uses a combined API. In the former, as the size of the project page increases, the management of the code will bring greater mental burden to the users; in the latter, the combined writing method combines data and methods, making it easier to understand and use. However, it may be a little uncomfortable for beginners.
(2) The source code and usage method built by js used by Vue2. The source code built by Vue3 using ts. The usage method also supports ts. It is more friendly for large projects, but for small projects, users often cannot see it at once. Without the benefits of strong type support brought by ts, on the contrary, it will feel troublesome and unnecessary.
(3) Since this project is to briefly explain the differences between Vue3 and Vue2 in pages, so that students who are eager to use Vue3 can build projects and pages, more features of Vue3 are not displayed. In the next article, we will provide an in-depth analysis and explanation of more other features of Vue3 and the differences with Vue2 through a more complete project, so stay tuned.

Guess you like

Origin blog.csdn.net/YN2000609/article/details/132511388