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

代码仓库

alicepolice/Vue-05 (github.com)

构建项目

新建项目

导入bootstrap-icons-vue

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

导入Tailwind

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

安装VSCODE插件

构建目录文件

PS C:\Users\小能喵喵喵\Desktop\Vue\Homework\homework2\src> 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

构建底部导航栏

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 }"。那么会在 router-link 这个树节点中的子节点搜索绑定了 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>

构建登录、注册

提取组件

对于按钮和表单元素之类的小型组件,与简单的 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 关系

配置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)

我的Vue之旅、05 导航栏、登录、注册 (Mobile)的更多相关文章

  1. 前端Vue项目——初始化及导航栏

    一.项目初始化 创建webpack模板项目如下所示: MacBook-Pro:PycharmProjects hqs$ vue init webpack luffy_project ? Project ...

  2. 使用vue给导航栏添加链接

    如下面的导航栏,使用vue技术给该导航栏增加链接: js代码为: navigation:function(){ new Vue({ el: '#navUl', data: { menuData:{ ' ...

  3. 我的Vue之旅 06 超详细、仿 itch.io 主页设计(Mobile)

    第二期 · 使用 Vue 3.1 + TypeScript + Router + Tailwind.css 仿 itch.io 平台主页. 我的主题 HapiGames 是仿 itch.io 的 in ...

  4. Vue 事件监听实现导航栏吸顶效果(页面滚动后定位)

    Vue 事件监听实现导航栏吸顶效果(页面滚动后定位) Howie126313 关注 2017.11.19 15:05* 字数 100 阅读 3154评论 0喜欢 0 所说的吸顶效果就是在页面没有滑动之 ...

  5. 使用vue封装一个tab栏切换的左侧导航栏的公共组件

     首先看最终效果图: 1.compent文件夹里添加tab文件夹,里面创建index.vue index.js index.css index.vue内的template部份代码如下:(最新更正:代码 ...

  6. iOS之旅--隐藏(去除)导航栏底部横线

    iOS之旅--隐藏(去除)导航栏底部横线 iOS开发大部分情况下会使用到导航栏,由于我司的app导航栏需要与下面紧挨着的窗口颜色一致,导航栏底部的横线就会影响这个美观,LZ使用了以下方法.觉得不错,分 ...

  7. vue中滚动页面,改变样式&&导航栏滚动时,样式透明度修改

    vue中滚动页面,改变样式&&导航栏滚动时,样式透明度修改.vue <div class="commonHeader" v-bind:class=" ...

  8. vue+el-menu实现路由刷新和导航栏菜单状态保持(局部刷新页面)

    一.菜单项激活状态保持 有时,我们在项目中会有这样一个需求,即实现 一个侧导航栏,点击不同的菜单项,右边内容会跟着变化,而页面手动刷新后想要使菜单激活状态保持,那么这个功能该如何实现呢? 现在给出以下 ...

  9. 超详细Vue实现导航栏绑定内容锚点+滚动动画+vue-router(hash模式可用)

    超详细Vue实现导航栏绑定内容锚点+滚动动画+vue-router(hash模式可用) 转载自:https://www.jianshu.com/p/2ad8c8b5bf75 亲测有效~ <tem ...

随机推荐

  1. ooday01类_对象_访问成员

    笔记: 什么是类?什么是对象? 现实生活中是由很多很多对象组成的,基于对象抽出了类 对象:软件中真实存在的单个个体/东西 类:类别/类型,代表一类个体 类是对象的模子,对象是类的具体的实例 类中可以包 ...

  2. Solution -「Luogu 3959」 宝藏

    果真是宝藏题目. 0x01 前置芝士 这道题我是真没往状压dp上去想.题目来源. 大概看了一下结构.盲猜直接模拟退火!\xyx 所需知识点:模拟退火,贪心. 0x02 分析 题目大意:给你一个图,可能 ...

  3. GitLab:Your account has been blocked.

    使用git pull 出现"GitLab:Your account has been blocked."错误 背景 多人使用服务器同一用户,在~/.ssh 目录下的公私钥是之前一个 ...

  4. Mybatis源码解读-配置加载和Mapper的生成

    问题 Mybatis四大对象的创建顺序? Mybatis插件的执行顺序? 工程创建 环境:Mybatis(3.5.9) mybatis-demo,参考官方文档 简单示例 这里只放出main方法的示例, ...

  5. python不同平台进程的启动与终止

    Liunx进程的启动与终止 在使用subprocess创建进程时需要将所有进程设置为一个进程组 preexec_fn:只在 Unix 平台下有效,用于指定一个可执行对象(callable object ...

  6. CentOS7桥接模式上不了外网的配置

    电脑VM10装了CentOS7后用NAT模式可以上网,但我想要的是桥接模式,因为我要用Xshell5进行远程访问.所以要 ifconfig 找到ip .那么为什么桥接模式上不了外网呢? 首先参考了 h ...

  7. 在docker中出现的僵尸进程怎么处理

    GreatSQL社区原创内容未经授权不得随意使用,转载请联系小编并注明来源. 一.发现问题 小玲是一名数据库测试人员,这一天她尝试在docker环境中部署GreatDB集群,结果在对greatsqld ...

  8. Luogu1088 火星人 (康托展开)

    皮一波 #include <iostream> #include <cstdio> #include <cstring> #include <algorith ...

  9. 什么是云计算?云计算三种模式Sass、Paas、Iaas

    云计算 云计算的"云"指的是计算机网络(一般指的是 Internet),"计算"指的是多个计算机共同计算巨大的数据的过程.通过云计算平台把任务分解成一个个小的任 ...

  10. apk编辑器测评

    hi你好,我今天要介绍的就是apk编辑器 这里我用的是apk编辑器专业版 APK编辑器 关于 APK 编辑器智友汉化组论坛:bbs.zhiyoo.com修改应用程序名称美化 UI: 更改背景图片删除广 ...