Vue3实战教程(快速入门)

铁佛  金牌会员 | 2024-6-20 16:13:56 | 显示全部楼层 | 阅读模式
打印 上一主题 下一主题

主题 542|帖子 542|积分 1626

前言

本教程通过搭建一个简朴项目,帮助读者快速入门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),内容如下:

  1. <template>
  2.   <div>首页</div>
  3. </template>
  4. <script>
  5. export default {
  6.   name: 'Index'
  7. }
  8. </script>
  9. <style scoped></style>
复制代码
(3)修改router/index.ts路由文件:

  1. import { createRouter, createWebHistory, RouteRecordRaw } from 'vue-router'
  2. import Home from '../views/Home.vue'
  3. const routes: Array<RouteRecordRaw> = [
  4.   {
  5.     path: '/',
  6.     name: 'Index',
  7.     component: () => import('../views/Index.vue')
  8.   },
  9. ]
  10. const router = createRouter({
  11.   history: createWebHistory(process.env.BASE_URL),
  12.   routes
  13. })
  14. export default router
复制代码
(4)修改App文件:

  1. <template>
  2.   <div id="app">
  3.     <router-view />
  4.   </div>
  5. </template>
  6. <style>
  7. html,
  8. body,
  9. #app {
  10.   width: 100%;
  11.   height: 100%;
  12. }
  13. </style>
复制代码
修改完毕后,查看结果

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

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


1.3 创建登录页面

创建LoginRegister.vue文件:

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

查看结果:

2.创建404页面(引入sass)

2.1 引入sass

(1)查看当前node版本

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

当前已知 node-sass 与 node 版本对应如下:https://github.com/sass/node-sass

node-sass 和 sass-loader 的常见版本对应关系如下:
node-sasssass-loader4.3.04.1.14.7.2$7.0.3/7.3.16.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

  1. <template>
  2.   <div class="not-found">
  3.     <img src="../assets/img/404.gif" alt="" />
  4.   </div>
  5. </template>
  6. <script>
  7. export default {
  8.   name: '404'
  9. }
  10. </script>
  11. <style lang="scss" scoped>
  12. .not-found {
  13.   width: 100%;
  14.   height: 100%;
  15.   overflow: hidden;
  16.   img {
  17.     width: 100%;
  18.     height: 100%;
  19.   }
  20. }
  21. </style>
复制代码
(3)router/index.ts中通过正则表达式匹配匹配失败的路由为404页面


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

3.1 实现布局左右切换动画

因为本篇文章重要讲解的是Vue3和element-plus的用法,css部分就省略阐明,有爱好的同砚可以自行研究。


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

(1)下载element-plus包:
  1. npm i element-plus
复制代码

(2)在main.ts中引入

3.3 利用element-plus表单组件

(1)setup中加入登录表单loginUser
  1. setup() {
  2.     // 登录/注册模式
  3.     const signUpMode = ref(false)
  4.     // 登录表单
  5.     const loginUser = reactive({
  6.       email: '',
  7.       password: ''
  8.     })
  9.     return { signUpMode, loginUser }
  10.   }
复制代码
(2)利用表单组件

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

3.4 表单验证

(1)表单验证

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

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

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

4.范例匹配和代码抽离

4.1 代码抽离

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

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

4.2.1 ts文件范例匹配

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

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

这对于初次利用ts的同砚可能不太适应,认为完全没有必要。但是这其实对于大型项目标维护来说,相对比js友好许多。
4.2.2 vue文件中的ts范例匹配

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

(2)范例匹配
1、对于any范例的变量——ctx,利用// @ts-ignore进行范例忽略
2、对于其他范例的变量,在变量后加入:范例即可,比如formName:string和valid:boolean

5.抽离登录组件

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

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

6. 实现注册表单

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

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

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

6.2 创建注册表单组件

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

  1. <template>
  2.   <!-- 登录 -->
  3.   <el-form
  4.     :model="registerUser"
  5.     :rules="registerRules"
  6.     ref="registerForm"
  7.     label-width="100px"
  8.     class="register-form sign-up-form"
  9.   >
  10.     <el-form-item label="用户名" prop="name">
  11.       <el-input v-model="registerUser.name" placeholder="Enter Name..." />
  12.     </el-form-item>
  13.     <el-form-item label="邮箱" prop="email">
  14.       <el-input v-model="registerUser.email" placeholder="Enter Email..." />
  15.     </el-form-item>
  16.     <el-form-item label="密码" prop="password">
  17.       <el-input
  18.         v-model="registerUser.password"
  19.         type="password"
  20.         placeholder="Enter Password..."
  21.       />
  22.     </el-form-item>
  23.     <el-form-item label="确认密码" prop="password2">
  24.       <el-input
  25.         v-model="registerUser.password2"
  26.         type="password"
  27.         placeholder="Enter Password again..."
  28.       />
  29.     </el-form-item>
  30.     <el-form-item label="角色" prop="role">
  31.       <el-select v-model="registerUser.role">
  32.         <el-option label="管理员" value="admin"></el-option>
  33.         <el-option label="用户" value="user"></el-option>
  34.         <el-option label="游客" value="visitor"></el-option>
  35.       </el-select>
  36.     </el-form-item>
  37.     <el-form-item>
  38.       <el-button
  39.         @click="handleRegister('registerForm')"
  40.         type="primary"
  41.         class="submit-btn"
  42.         >提交</el-button
  43.       >
  44.     </el-form-item>
  45.   </el-form>
  46. </template>
  47. <script lang="ts">
  48. import { getCurrentInstance } from 'vue'
  49. export default {
  50.   name: 'registerForm',
  51.   props: {
  52.     registerUser: {
  53.       type: Object,
  54.       required: true
  55.     },
  56.     registerRules: {
  57.       type: Object,
  58.       required: true
  59.     }
  60.   },
  61.   setup() {
  62.     // 通过解构getCurrentInstance,获取this,这里的this就是ctx
  63.     // @ts-ignore
  64.     const { ctx } = getCurrentInstance()
  65.     // 触发登录方法
  66.     const handleRegister = (formName: string) => {
  67.       console.log(ctx)
  68.       ctx.$refs[formName].validate((valid: boolean) => {
  69.         if (valid) {
  70.           console.log('submit!')
  71.         } else {
  72.           console.log('error submit!')
  73.           return false
  74.         }
  75.       })
  76.     }
  77.     return { handleRegister }
  78.   }
  79. }
  80. </script>
  81. <style scoped>
  82. /* register */
  83. .login-form,
  84. .register-form {
  85.   background-color: #fff;
  86.   padding: 50px 80px 20px 20px;
  87.   border-radius: 5px;
  88.   box-shadow: 0px 5px 10px #cccc;
  89. }
  90. .submit-btn {
  91.   width: 100%;
  92. }
  93. .tiparea {
  94.   text-align: right;
  95.   font-size: 12px;
  96.   color: #333;
  97.   width: 100%;
  98. }
  99. .tiparea a {
  100.   color: #409eff;
  101. }
  102. </style>
复制代码
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:
  1. import axios,{AxiosRequestConfig,AxiosResponse} from 'axios'
  2. import { ElLoading } from 'element-plus'
  3. import { ElMessage } from 'element-plus'
  4. let loading:any;
  5. const startLoading = () =>{
  6.   interface Options{
  7.     lock: boolean;
  8.     text: string;
  9.     background: string;
  10. }
  11.   const options:Options = {
  12.     lock: true,
  13.     text: 'Loading',
  14.     background: 'rgba(0, 0, 0, 0.7)'
  15.   }
  16.   loading = ElLoading.service(options)
  17. }
  18. const endLoading = ()=>{
  19.   loading.close()
  20. }
  21. // 请求拦截
  22. axios.interceptors.request.use((config:AxiosRequestConfig<any>)=>{
  23.   // 开始Loading
  24.   startLoading()
  25.   return config
  26. })
  27. // 响应拦截
  28. axios.interceptors.response.use((res:AxiosResponse<any, any>)=>{
  29.   // 结束Loading
  30.   endLoading()
  31.   // console.log('成功响应拦截',res)
  32.   // 如果请求成功直接返回响应数据
  33.   if(res.status === 200){
  34.     return res.data
  35.   }
  36. },error=>{
  37.   // 结束Loading
  38.   endLoading()
  39.   // console.log('失败响应拦截',error)
  40.   const { response: res } = error
  41.   const msg = typeof res.data === 'string' ? res.data: res.data.error || '请求失败,请稍后重试'
  42.   ElMessage.error(msg)
  43.   // 错误提醒
  44.   return Promise.reject(error)
  45. })
  46. export default axios
复制代码
7.3 办理跨域问题(配置vue.config.js,设置代理)

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

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

API地址:http://imissu.herokuapp.com/
7.4.1 创建api文件夹,规范利用api(推荐)

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

  1. import axios from '@/utils/http'
  2. // 注册接口
  3. export function register(params: any) {
  4.   return axios({
  5.     url: '/api/v1/auth/register',
  6.     method: 'post',
  7.     headers: {
  8.       'Content-Type': 'application/json;charset=UTF-8'
  9.     },
  10.     data: params
  11.   })
  12. }
复制代码
(2)在注册组件中利用

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

(1)在main.ts中全局挂载axios
  1. import axios from '@/utils/http'
  2. app.config.globalProperties.$http = axios
复制代码
(2)在注册组件中利用,关键代码:
  1. // @ts-ignore
  2. const { ctx, proxy } = getCurrentInstance()
  3. const res = await proxy.$http({
  4.   url: '/api/v1/auth/register',
  5.   method: 'post',
  6.   headers: {
  7.     'Content-Type': 'application/json;charset=UTF-8'
  8.   },
  9.   data: props.registerUser
  10. })
复制代码
(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的区别进行深入的解析和阐明,敬请期待。

免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。

本帖子中包含更多资源

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

x
回复

使用道具 举报

0 个回复

倒序浏览

快速回复

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

本版积分规则

铁佛

金牌会员
这个人很懒什么都没写!

标签云

快速回复 返回顶部 返回列表