网站首页 > 技术文章 正文
前言
最近得空一直在捣鼓Vue3开发,想要快速上手,还是得写一些自定义组件。之前就有基于vue2.x写过一些自定义Navbar+Tabbar及弹窗组件。于是就撸起袖子开整。
今天主要是给大家分享一些如何快速上手Vue3开发。
目前Vue3的最新版本是V3.0.4,star高达19.7K+。而且更新还很频繁。
# vue3官网
https://v3.vuejs.org/
# vue3中文网
https://v3.cn.vuejs.org/
# vue3仓库地址
https://github.com/vuejs/vue-next
# Vite构建工具
https://vite-design.surge.sh/
通过如下2种方法快速创建Vue3项目。
1、Vite构建工具
$ npm init vite-app <project-name>
$ cd <project-name>
$ npm install
$ npm run dev
如果安装了Vite脚手架 create-vite-app,也可以通过下面方法快速创建。
$ create-vite-app <project-name>
2、@vue/cli脚手架
通过CLI创建需要版本是V4.5以上。升级CLI,npm i @vue/cli -g
$ vue create <project-name>
# 选择 Vue3选项 即可
$ npm run serve
下面进入今天的主题,Vue3实现自定义导航栏+底部Tab+弹窗组件。
在components目录下新建headerBar.vue和tabBar.vue两个页面。
并新建一个components.js页面用于引入公共组件。
然后在main.js中注册即可。
1、自定义headerBar.vue
其实这个组件和vue2.x创建没啥区别。
<template>
<div class="header-bar" :class="{'fixed': fixed, 'transparent fixed': transparent}">
<div class="header-bar__wrap flexbox flex-alignc" :style="{'background': bgcolor, 'color': color, 'z-index': zIndex}">
<!-- //返回 -->
<div class="action hdbar-action__left" v-if="back && back!='false'" @click="$router.go(-1)">
<slot name="backIco" /><slot name="backText" />
</div>
<!-- //标题 -->
<div class="hdbar-title" :class="{'center': center}">
<template v-if="$slots.title">
<slot name="title" />
</template>
<template v-else>
{{title}}
</template>
</div>
<!-- //搜索框 -->
<div class="action hdbar-action__search">
<slot name="search" />
</div>
<!-- //右边按钮 -->
<div class="action hdbar-action__right">
<slot name="right" />
</div>
</div>
</div>
</template>
<script>
export default {
props: {
// 是否返回
back: { type: [Boolean, String], default: true },
// 标题
title: { type: String, default: '' },
// 标题颜色
color: { type: String, default: '#fff' },
// 背景颜色
bgcolor: { type: String, default: '#22d59c' },
// 标题是否居中
center: { type: [Boolean, String], default: false },
// 是否固定
fixed: { type: [Boolean, String], default: false },
// 背景透明
transparent: { type: [Boolean, String], default: false },
// 设置层级
zIndex: { type: [Number, String], default: '2020' },
}
}
</script>
支持自定义背景色、文字颜色、左侧图标、标题、搜索框,右侧支持图标|文字|图片。
<header-bar :back="true" bgcolor="#f9f9f9" color="#ff0" center transparent>
<template #backIco><i class="iconfont icon-close"></i></template>
<template v-slot:backText>返回</template>
<!--标题-->
<template v-slot:title>
<img src="~/assets/img/logo.png" height="16" /> <em>Vue3.0</em>
</template>
<!--右侧按钮-->
<template #right><i class="iconfont icon-search"></i></template>
</header-bar>
2、自定义tabBar.vue
这里的逻辑代码主要是在setup里处理。
<template>
<div class="tab-bar" :class="{'fixed': fixed}">
<div class="tab-bar__wrap flexbox flex-alignc" :style="{'background': bgcolor}">
<div v-for="(item, index) in tabs" :key="index" class="navigator" :class="currentTabIndex == index ? 'on' : ''" @click="switchTabs(index, item)">
<div class="ico" :class="{'dock': item.dock}">
<i v-if="item.dock" class="dock-bg" :style="{'background': item.dockBg ? item.dockBg : activeColor}"></i>
<i v-if="item.icon" class="iconfont" :class="item.icon" :style="{'color': (currentTabIndex == index && !item.dock ? activeColor : color), 'font-size': item.iconSize}"></i>
<img v-if="item.img" class="iconimg" :src="currentTabIndex == index && !item.dock ? item.activeImg : item.img" :style="{'font-size': item.iconSize}" />
<em v-if="item.badge" class="nuxt__badge">{{item.badge}}</em>
<em v-if="item.dot" class="nuxt__badge-dot"></em>
</div>
<div class="txt" :style="{'color': (currentTabIndex == index ? activeColor: color)}">{{item.title}}</div>
</div>
</div>
</div>
</template>
<script>
import { ref } from 'vue'
import { useRouter, useRoute } from 'vue-router'
export default {
props: {
current: { type: [Number, String], default: 0 },
// 背景色
bgcolor: { type: String, default: '#fff' },
// 颜色
color: { type: String, default: '#999' },
// 激活颜色
activeColor: { type: String, default: '#22d59c' },
// 是否固定
fixed: { type: [Boolean, String], default: false },
// tab选项
tabs: {
type: Array,
default: () => null
},
},
emits: {
click: null
},
setup(props, context) {
const currentTabIndex = ref(props.current)
const router = useRouter()
const route = useRoute()
// 匹配当前路由页面
const _curPath = route.path
props.tabs.map((item, index) => {
if(item.path == _curPath) {
currentTabIndex.value = index
}
})
const switchTabs = (index, item) => {
currentTabIndex.value = index
context.emit('click', index)
if(item.path) {
router.push(item.path)
}
}
return {
currentTabIndex,
switchTabs,
}
}
}
</script>
支持自定义背景色、文字颜色|选中颜色、是否固定、点击选项(返回索引值)。
另外还支持dock效果,图标支持iconfont及图片。
<tab-bar bgcolor="#f9f9f9" color="#f00" @click="tabbarClicked" :tabs="[
{
icon: 'icon-home', title: '首页', path: '/index',
},
{
icon: 'icon-guanli', title: '管理', path: '/manage'
badge: 1
},
{
icon: 'icon-fabu', title: '发布',
dock: true, dockBg: '#f60',
iconSize: '20px',
},
{
icon: 'icon-guanli', title: '联系人',
img: 'http://www.xxx.com/contact.jpg',
activeImg: 'http://www.xxx.com/contact_on.jpg',
},
{
icon: 'icon-me', title: '我的',
dot: true
}]"
/>
// 点击事件
tabbarClicked(index) {
console.log('tabbar索引值:' + index)
}
3、自定义V3Popup组件
V3Popup 基于Vue3开发的一款集合Alert、Dialog、ActionSheet、Toast等功能的移动端Vue3.0弹出框组件。
支持标签式+函数式两种调用方式。
// 标签式调用
<v3-popup
v-model="showAlert"
title="标题"
content="弹窗内容信息"
type="ios"
shadeClose="false"
xclose
z-index="2021"
:btns="[
{...},
{...},
]"
/>
// 函数式调用
let $el = this.$v3popup({
title: '标题',
content: '弹窗内容信息',
type: 'ios',
shadeClose: false,
xclose: true,
zIndex: 2021,
btns: [
{text: '关闭'},
{
text: '确定',
style: 'color:#09f;',
click: () => {
$el.close()
}
},
]
});
template模板写法和vue2.x没什么区别,主要是逻辑处理部分,既可以使用vue2.x的写法,也可以使用vue3的Composition API写法。
下面主要是使用Vue3写法实现逻辑。
<script>
import { ref, reactive, watch, toRefs, nextTick, onMounted } from 'vue'
/**
* Vue3.0写法
*/
let $index = 0, $locknum = 0, $timer = {}
export default {
props: {
// 接收父组件v-model值,如果v-model:xxx,则这里需写xxx: {...}
modelValue: { type: Boolean, default: false },
// 弹框标识符,相同ID共享一个实例
id: {
type: String, default: ''
},
title: String,
content: String,
type: String,
popupStyle: String,
icon: String,
shade: { type: [Boolean, String], default: true },
shadeClose: { type: [Boolean, String], default: true },
opacity: { type: [Number, String], default: '' },
round: Boolean,
xclose: Boolean,
xposition: { type: String, default: 'right' },
xcolor: { type: String, default: '#333' },
anim: { type: String, default: 'scaleIn' },
position: String,
follow: { type: Array, default: null },
time: { type: [Number, String], default: 0 },
zIndex: { type: [Number, String], default: '8080' },
teleport: [String, Object],
btns: {
type: Array, default: null
},
onSuccess: { type: Function, default: null },
onEnd: { type: Function, default: null },
// 接收函数式移除指令
remove: Function,
},
emits: [
'update:modelValue',
],
setup(props, context) {
const elRef = ref(null)
const data = reactive({
opened: false,
closeCls: '',
toastIcon: {
//...
}
})
watch(() => props.modelValue, (val) => {
if(val) {
open()
} else {
close()
}
})
onMounted(() => {
if(props.modelValue) {
open()
}
})
// 打开弹框
const open = () => {
if(data.opened) return
data.opened = true
typeof props.onSuccess === 'function' && props.onSuccess()
const dom = elRef.value
dom.style.zIndex = getZIndex() + 1
if(JSON.parse(props.shade)) {
if(!document.body.classList.contains('vui__body-hidden')) {
document.body.classList.add('vui__body-hidden')
}
$locknum++
}
// 倒计时关闭
if(props.time) {
$index++
// 避免重复点击
if($timer[$index] !== null) clearTimeout($timer[$index])
$timer[$index] = setTimeout(() => {
close()
}, parseInt(props.time) * 1000)
}
//...
}
// 关闭弹框
const close = () => {
data.closeCls = true
setTimeout(() => {
data.opened = false
data.closeCls = false
if(JSON.parse(props.shade)) {
$locknum--
if(!$locknum) {
document.body.classList.remove('vui__body-hidden')
}
}
if(props.time) {
$index--
}
context.emit('update:modelValue', false)
typeof props.onEnd === 'function' && props.onEnd()
}, 200)
}
// 点击遮罩层
const shadeClicked = () => {
if(JSON.parse(props.shadeClose)) {
close()
}
}
// 按钮事件
const btnClicked = (e, index) => {
//...
}
// 获取弹窗层级
const getZIndex = () => {
//...
}
// 获取弹窗坐标点
const getPos = (x, y, ow, oh, winW, winH) => {
let l = (x + ow) > winW ? x - ow : x;
let t = (y + oh) > winH ? y - oh : y;
return [l, t];
}
return {
...toRefs(data),
shadeClicked,
btnClicked,
close,
elRef,
}
}
}
</script>
vue3中watch监听事件,可以监听一个、多个值(对象/数组)。
<script>
import { ref, computed, reactive, toRefs, watch, nextTick } from 'vue'
export default {
setup() {
const count = ref(0)
const data = reactive({
num: 0,
total: computed(() => data.num * 2),
handlePlus: () => {
data.num++
}
})
// 监听器watch使用
// 1、监听ref一个值count
// watch(count, () => {})
watch(count, (newVal, oldVal) => {
console.log('count updated:' + count.value)
console.log(newVal, oldVal, "--新旧值");
})
// 监听reactive中某一项
watch(() => data.num, (newVal, oldVal) => {
console.log('+++count updated:' + data.num)
console.log(newVal, oldVal, "==新旧值");
})
// 2、监听整个reactive中data对象
watch(data, () => {
console.log('==count updated:' + data.num)
})
// 3、监听多个值
// watch([count, data], () => {})
watch([count, () => data.num], () => {
console.log('某一项updated:' + count.value + '---' + data.num)
})
return {
//...
}
}
}
</script>
注意:Vue3不支持div的slot插槽。
// 不支持
<div slot="content"></div>
// 支持
<template v-slot:content>显示自定义插槽内容!</template>
<template #content>显示自定义插槽内容!</template>
ok,今天就分享到这里。篇幅有些长,感谢大家的阅读。
猜你喜欢
- 2025-01-23 自定义的拖拽式智能大屏(springboot+VUE)
- 2025-01-23 手把手教你在 Vue3 中自定义指令(vue自定义指令两种方式)
- 2025-01-23 如何实现 Vue 自定义组件中 hover 事件以及 v-model
- 2025-01-23 VUE工具箱:定制组件的 V-MODEL(vue必备插件)
- 2025-01-23 Vue短文:如何在HTTP请求时传递自定义头部
- 2025-01-23 20 道必看的 Vue 面试题 | 原力计划
- 2025-01-23 浅析vue封装自定义插件(vue封装一个组件需要哪些步骤)
- 2025-01-23 Vue3实战笔记(62)—Vue3自定义指令入门:10分钟学会基础用法
- 2025-01-23 vue自定义组件v-model几种实现方法,拿走不谢
- 2025-01-23 高质量 Vue.js 自定义美化滚动条VueScroll
- 02-21走进git时代, 你该怎么玩?_gits
- 02-21GitHub是什么?它可不仅仅是云中的Git版本控制器
- 02-21Git常用操作总结_git基本用法
- 02-21为什么互联网巨头使用Git而放弃SVN?(含核心命令与原理)
- 02-21Git 高级用法,喜欢就拿去用_git基本用法
- 02-21Git常用命令和Git团队使用规范指南
- 02-21总结几个常用的Git命令的使用方法
- 02-21Git工作原理和常用指令_git原理详解
- 最近发表
- 标签列表
-
- cmd/c (57)
- c++中::是什么意思 (57)
- sqlset (59)
- ps可以打开pdf格式吗 (58)
- phprequire_once (61)
- localstorage.removeitem (74)
- routermode (59)
- vector线程安全吗 (70)
- & (66)
- java (73)
- org.redisson (64)
- log.warn (60)
- cannotinstantiatethetype (62)
- js数组插入 (83)
- resttemplateokhttp (59)
- gormwherein (64)
- linux删除一个文件夹 (65)
- mac安装java (72)
- reader.onload (61)
- outofmemoryerror是什么意思 (64)
- flask文件上传 (63)
- eacces (67)
- 查看mysql是否启动 (70)
- java是值传递还是引用传递 (58)
- 无效的列索引 (74)