我的Vue之旅、05 导航栏、登录、注册 (Mobile)

  • 我的Vue之旅、05 导航栏、登录、注册 (Mobile)已关闭评论
  • 81 次浏览
  • A+
所属分类:Web前端
摘要

第一期 · 使用 Vue 3.1 + TypeScript + Router + Tailwind.css 构建手机底部导航栏、仿B站的登录、注册页面。

第一期 · 使用 Vue 3.1 + TypeScript + Router + Tailwind.css 构建手机底部导航栏、仿B站的登录、注册页面。

代码仓库

alicepolice/Vue-05 (github.com)

构建项目

新建项目

我的Vue之旅、05 导航栏、登录、注册 (Mobile)

导入bootstrap-icons-vue

bootstrap-icons-vue - npm (npmjs.com)

导入Tailwind

在 Vue 3 和 Vite 安装 Tailwind CSS - Tailwind CSS 中文文档

安装VSCODE插件

我的Vue之旅、05 导航栏、登录、注册 (Mobile)

构建目录文件

PS C:Users小能喵喵喵DesktopVueHomeworkhomework2src> tree /f C:. │   App.vue │   index.css │   main.ts │   shims-vue.d.ts │ ├───assets │       3.png │       4.png │       logo.png │ ├───components │       BottomBar.vue │ ├───router │       index.ts │ ├───store │       index.ts │ └───views         AboutView.vue         HomeLoginView.vue         HomeView.vue         LoginView.vue         RegisterView.vue 

构建底部导航栏

我的Vue之旅、05 导航栏、登录、注册 (Mobile)

Router

  • redirect用于访问网站根目录的时候跳转至特定哈希锚点对应的页面
const routes: Array<RouteRecordRaw> = [   {     path: '/',     name: '',     redirect: () => {       return { name: "home" }     }   },   {     path: '/home',     name: 'home',     component: HomeView   },   {     path: '/login',     name: 'login',     component: LoginViewVue   },   {     path: '/register',     name: 'register',     component: RegisterViewVue   },   {     path: '/about',     name: 'about',     component: AboutViewVue   } ] 

App.vue

使用 typescript 语法明确规定了setBottomFlag接收的布尔类型,同时严格规定 vue 应用实例 data 函数返回的对象中变量的类型,即 as 语法。

v-show="bottomFlag" 用于隐藏导航栏,setBottomFlag 由各个 router-view 负责 emit 触发。

<template>   <router-view @set-bottom-flag="setBottomFlag" />   <BottomBar v-show="bottomFlag" :items="bottomItems" /> </template>   <script lang="ts"> import { defineComponent } from "vue"; import BottomBar from "@/components/BottomBar.vue";  type BottomItem = {   text: string;   icon: string;   routerName: string; };  export default defineComponent({   name: "App",   components: {     BottomBar,   },   data() {     return {       bottomItems: [         { text: "首页", icon: "b-icon-house-heart", routerName: "home" },         { text: "理财", icon: "b-icon-coin", routerName: "about" },         { text: "消息", icon: "b-icon-chat-dots", routerName: "about" },         { text: "我的", icon: "b-icon-person-circle", routerName: "about" },       ] as BottomItem[],       bottomFlag: true as boolean,     };   },   methods: {     setBottomFlag(value: boolean): void {       this.bottomFlag = value;     },   }, }); </script> 

BottomBar.vue

这里使用了 windtail css 功能性类语法,具体信息可以通过官方文档查到。

在vue3.1中,router-link的tag已经被废除,需要使用插槽的方式。给 router-link 添加 custom v-slot="{ navigate }"。navigate 是触发导航的函数。 @click = "navigate" 通过对其他标签的点击间接触发router-link的导航函数。

custom -> <router-link> 是否不应将其内容包装在 <a> 标记中。

icon的生成使用了动态控件,依赖外部传进去的数组 ->:is

// 来自 App.vue 的数组传递给了当前的 props -> items bottomItems: [ { text: "首页", icon: "b-icon-house-heart", routerName: "home" }, { text: "理财", icon: "b-icon-coin", routerName: "about" }, { text: "消息", icon: "b-icon-chat-dots", routerName: "about" }, { text: "我的", icon: "b-icon-person-circle", routerName: "about" }, ] as BottomItem[], 
<template>   <div     class="       box-border       h-16       absolute       container       bg-blue-200       bottom-0       left-0       flex flex-nowrap       items-center     "   >     <div v-for="(item, index) in items" :key="index" style="width: 100%">       <router-link :to="{ name: item.routerName }" custom v-slot="{ navigate }">         <div @click="navigate" class="text-center">           <div class="pt-2">             <component :is="item.icon" class="m-auto text-2xl" />             <div class="text-lg">               {{ item.text }}             </div>           </div>         </div>       </router-link>     </div>   </div> </template> <script lang="ts"> export default {   props: {     items: Array,   }, }; </script> 

修改HomeView.vue

在Home页面下默认显示底部导航栏,在挂载的时候通知父组件事件。

    this.$emit("set-bottom-flag", true); 
<template>   <div class="text-6xl">主页面 HELLO WORLD</div> </template>  <script lang="ts"> import { defineComponent } from "vue";  export default defineComponent({   name: "HomeView",   components: {},   mounted() {     this.$emit("set-bottom-flag", true);   }, }); </script> 

构建登录、注册

我的Vue之旅、05 导航栏、登录、注册 (Mobile)

提取组件

对于按钮和表单元素之类的小型组件,与简单的 CSS 类相比,创建模板片断或 JavaScript 组件通常会感觉过重。

官方建议使用 @layer components { ... } 指令包装自定义组件样式,以告诉 Tailwind 这些样式属于哪一层。

在 src/index.css 中定义表单标签、按钮标签共用的 Tailwind CSS 样式集合

/* ./src/index.css */ @tailwind base; @tailwind components; @tailwind utilities;  @layer components {   .login-register-input {     @apply inline-block bg-white focus:outline-none py-3 pl-3 appearance-none leading-normal;   }   .login-register-solid-button{     @apply     focus:outline-none     text-white     bg-pink-400     font-medium     rounded-sm     text-lg     px-5     py-2.5     mb-2   }   .login-register-hollow-button{     @apply     focus:outline-none     text-pink-400     border-pink-400 border     font-medium     rounded-sm     text-lg     px-5     py-2.5     mb-2   }    .login-register-checkbox{     @apply     ml-2     text-sm     font-medium     text-gray-500     dark:text-gray-300     text-left   } } 

LoginView.vue

router-link

注意 router-link 的用法,这里分别绑定了左箭头、短信登录。主要靠如下语法。

 custom v-slot="{ navigate } // 上: router-link标签中的属性, 下: 绑定实现像a标签那样具备跳转功能的标签  @click="navigate" 

动态绑定背景图片方式

require 从依赖项返回导出。是同步过程,不会触发对服务器的请求。编译器会确保依赖项可用。

<div class="bg-cover bg-center h-24 shadow-inner" :style="{ 'background-image': 'url(' + banner + ')', }" ></div> 
  data() {     return {       banner: require("../assets/3.png"),     };   } 

更多资料可参考

https://stackoverflow.com/questions/67193179/how-can-i-link-background-image-vue-cli

https://stackoverflow.com/questions/35242272/vue-js-data-bind-style-backgroundimage-not-working

输入密码的时候切换背景

依托两个事件,通过当前光标对表单标签的进出实现。

@focusin="changeIMG('4.png')" @focusout="changeIMG('3.png')" 
  methods: {     changeIMG(src: string): void {       this.banner = require(`../assets/${src}`);     },   }, 

完整代码

<template>   <div class="container bg-gray-100 absolute inset-0">     <div class="box-border bg-white border-b-1 border-b-black h-16 p-2">       <router-link :to="{ name: 'home' }" custom v-slot="{ navigate }">         <b-icon-arrow-left-short           class="inline-block text-4xl align-middle mr-3 mt-2"           @click="navigate"         />       </router-link>        <span class="text-xl absolute top-5">密码登录</span>       <router-link :to="{ name: 'register' }" custom v-slot="{ navigate }">         <span           class="text-lg absolute right-4 top-5 text-gray-500"           @click="navigate"           >短信登录</span         >       </router-link>     </div>      <div       class="bg-cover bg-center h-24 shadow-inner"       :style="{         'background-image': 'url(' + banner + ')',       }"     ></div>     <div class="border-y">       <div class="login-register-input w-1/6">账号</div>       <input         id="username"         class="login-register-input w-5/6"         type="text"         placeholder="请输入手机号或邮箱"       />     </div>     <div class="border-b">       <div class="login-register-input w-1/6">密码</div>       <input         id="password"         class="login-register-input w-3/6"         type="text"         placeholder="请输入密码"         @focusin="changeIMG('4.png')"         @focusout="changeIMG('3.png')"       />       <div class="login-register-input pl-8 w-2/6 text-pink-400 text-center">         忘记密码?       </div>     </div>     <div class="text-center pt-6 flex justify-around">       <button type="button" class="login-register-hollow-button w-5/12">         注册       </button>       <button type="button" class="login-register-solid-button w-5/12">         登录       </button>     </div>     <div class="text-center pt-4">       <div class="flex items-center align-top">         <input           id="link-checkbox"           type="checkbox"           value=""           class="ml-4 w-5 h-5 bg-gray-100 rounded"         />         <label           for="link-checkbox"           class="ml-2 text-sm font-medium text-gray-500 text-left"           >我已阅读并同意<a href="#" class="text-blue-600">用户协议</a>和<a             href="#"             class="text-blue-600"             >隐私政策</a           ></label         >       </div>     </div>     <div class="text-center pt-6">       <label class="login-register-checkbox">         遇到问题?<a href="#" class="text-blue-600">查看帮助</a>       </label>     </div>   </div> </template>  <script lang="ts"> import { defineComponent } from "vue";  export default defineComponent({   name: "LoginView",   components: {},   data() {     return {       banner: require("../assets/3.png"),     };   },   methods: {     changeIMG(src: string): void {       this.banner = require(`../assets/${src}`);     },   },   mounted() {     this.$emit("set-bottom-flag", false);   }, }); </script> 

RegisterView.vue

部分功能与 Login.view 类似。

表单填入更改标签颜色

当输入手机号时,获取验证码会由灰变成粉色。将字体颜色从固定的class抽取出放入动态class绑定计算属性。每当phone发生变化即可改变颜色。

      <div         class="login-register-input w-2/6 text-center"         :class="changeGetCodeColor"       >         获取验证码       </div> 
  computed: {     changeGetCodeColor(): string {       if (this.phone == "") {         return "text-gray-400";       } else {         return "text-pink-400";       }     },   }, 

完整代码

<template>   <div class="container bg-gray-100 absolute inset-0">     <div class="box-border bg-white border-b-1 border-b-black h-16 p-2">       <router-link :to="{ name: 'home' }" custom v-slot="{ navigate }">         <b-icon-arrow-left-short           class="inline-block text-4xl align-middle mr-3 mt-2"           @click="navigate"         />       </router-link>        <span class="text-xl absolute top-5">手机号登录注册</span>       <router-link :to="{ name: 'login' }" custom v-slot="{ navigate }">         <span           class="text-lg absolute right-4 top-5 text-gray-500"           @click="navigate"           >密码登录</span         >       </router-link>     </div>      <div       class="bg-cover bg-center h-24 shadow-inner"       :style="{         'background-image': 'url(' + banner + ')',       }"     ></div>     <div>       <select id="countries" class="login-register-input w-full border-y">         <option selected value="CN">中国大陆</option>         <option value="US">美国</option>         <option value="CA">加拿大</option>         <option value="FR">法国</option>         <option value="DE">德国</option>       </select>     </div>     <div class="border-b">       <div class="login-register-input w-1/6">+86</div>       <input         id="phone"         class="login-register-input w-3/6"         type="text"         placeholder="请输入手机号码"         v-model="phone"       />       <div         class="login-register-input w-2/6 text-center"         :class="changeGetCodeColor"       >         获取验证码       </div>     </div>     <div class="border-b">       <div class="login-register-input w-1/6">验证码</div>       <input         id="code"         class="login-register-input w-5/6"         type="text"         placeholder="请输入验证码"         @focusin="changeIMG('4.png')"         @focusout="changeIMG('3.png')"       />     </div>     <div class="text-center pt-6">       <button type="button" class="login-register-solid-button w-11/12">         验证登录       </button>     </div>     <div class="text-center pt-4">       <div class="flex items-center align-top">         <input           id="link-checkbox"           type="checkbox"           value=""           class="login-register-checkbox"         />         <label           for="link-checkbox"           class="             ml-2             text-sm             font-medium             text-gray-500             dark:text-gray-300             text-left           "           >我已阅读并同意<a             href="#"             class="text-blue-600 dark:text-blue-500 hover:underline"             >用户协议</a           >和<a             href="#"             class="text-blue-600 dark:text-blue-500 hover:underline"             >隐私政策</a           >,未注册绑定的手机号验证成功后将自动注册</label         >       </div>     </div>     <div class="text-center pt-6">       <label         class="           ml-2           text-sm           font-medium           text-gray-500           dark:text-gray-300           text-left         "       >         遇到问题?<a           href="#"           class="text-blue-600 dark:text-blue-500 hover:underline"           >查看帮助</a         >       </label>     </div>   </div> </template>  <script lang="ts"> import { defineComponent } from "vue";  export default defineComponent({   name: "RegisterView",   components: {},   data() {     return {       banner: require("../assets/3.png"),       phone: "",     };   },   methods: {     changeIMG(src: string): void {       this.banner = require(`../assets/${src}`);     },   },   computed: {     changeGetCodeColor(): string {       if (this.phone == "") {         return "text-gray-400";       } else {         return "text-pink-400";       }     },   },   mounted() {     this.$emit("set-bottom-flag", false);   }, }); </script> 

一些零散的知识补充

Module not found: Error: Can't resolve 'sass-loader'

Module not found: Error: Can't resolve 'sass-loader'

解决方法: 运行如下命令后重新启动服务

npm install sass-loader -D npm install node-sass -D 

声明式、命令式

命令式UI:构建全功能UI实体,然后在UI更改时使用方法对其进行变更。

声明式UI:描述当前的UI状态,并且不需要关心它是如何过渡到框架的。

TS、ECMA、JS 关系

我的Vue之旅、05 导航栏、登录、注册 (Mobile)

配置NPM镜像

npm config set registry=http://registry.npm.taobao.org 

初试TS

var hello = "hello world" console.log(hello) 
npm install -g typescript tsc helloworld ::编译ts node helloworld ::运行js 

变量提升

当使用var声明一个变量的时候,该变量会被提升到作用域的顶端,但是赋值的部分并不会被提升

console.log(hello) var hello = "hello world" 

而let、const不会,实际开发中建议尽量使用用 let 和 const 代替var。

好用的网站

Tailwind CSS Select / Listbox Form - Free Examples (tailwind-elements.com)

Tailwind CSS Flowbite

Bootstrap Icons · Official open source SVG icon library for Bootstrap (getbootstrap.com)