上期链接:vue3从0开始搭建的学习记录(一)-CSDN博客
这里先发现了vscode的扩展插件Vetur对vite+vue3并不友好,所以改成了使用Vue - Official
了解到vue3是需要使用ref()作为响应式声明的函数,引入了unplugin-auto-import依赖,以便自动添加常用的方法和函数到开发页面,而不用每个页面去引入了
npm i -D unplugin-auto-import
需要在vite.config.ts文件里加入要自动引入的一些方法和函数
使用resolve这里需要先引入nodejs的path的依赖,也可以用其他方案,这里就不说了,可以自己去查一下
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
import { resolve } from 'path'
import AutoImport from 'unplugin-auto-import/vite'
// https://vitejs.dev/config/
export default defineConfig({
plugins: [
vue(),
AutoImport({
imports: [
'vue',
'vue-router',
],
dts: resolve('./auto-imports.d.ts')
}),
],
resolve: {
alias: {
'@': resolve('src'),
}
}
})
还需要在tsconfig.json文件里加入
"include": [
"scripts/**/*.ts",
"./**/*.ts",
"./**/*.d.ts",
"./**/*.tsx",
"./**/*.vue",
"auto-imports.d.ts", // 引入auto-import.d.ts文件
],
下面是我的auto-imports.d.ts文件,需要放在根目录下,可以参考参考
/* eslint-disable */
/* prettier-ignore */
// @ts-nocheck
// noinspection JSUnusedGlobalSymbols
// Generated by unplugin-auto-import
export {}
declare global {
const EffectScope: typeof import('vue')['EffectScope']
const acceptHMRUpdate: typeof import('pinia')['acceptHMRUpdate']
const computed: typeof import('vue')['computed']
const createApp: typeof import('vue')['createApp']
const createPinia: typeof import('pinia')['createPinia']
const customRef: typeof import('vue')['customRef']
const defineAsyncComponent: typeof import('vue')['defineAsyncComponent']
const defineComponent: typeof import('vue')['defineComponent']
const defineStore: typeof import('pinia')['defineStore']
const effectScope: typeof import('vue')['effectScope']
const getActivePinia: typeof import('pinia')['getActivePinia']
const getCurrentInstance: typeof import('vue')['getCurrentInstance']
const getCurrentScope: typeof import('vue')['getCurrentScope']
const h: typeof import('vue')['h']
const inject: typeof import('vue')['inject']
const isProxy: typeof import('vue')['isProxy']
const isReactive: typeof import('vue')['isReactive']
const isReadonly: typeof import('vue')['isReadonly']
const isRef: typeof import('vue')['isRef']
const mapActions: typeof import('pinia')['mapActions']
const mapGetters: typeof import('pinia')['mapGetters']
const mapState: typeof import('pinia')['mapState']
const mapStores: typeof import('pinia')['mapStores']
const mapWritableState: typeof import('pinia')['mapWritableState']
const markRaw: typeof import('vue')['markRaw']
const nextTick: typeof import('vue')['nextTick']
const onActivated: typeof import('vue')['onActivated']
const onBeforeMount: typeof import('vue')['onBeforeMount']
const onBeforeRouteLeave: typeof import('vue-router')['onBeforeRouteLeave']
const onBeforeRouteUpdate: typeof import('vue-router')['onBeforeRouteUpdate']
const onBeforeUnmount: typeof import('vue')['onBeforeUnmount']
const onBeforeUpdate: typeof import('vue')['onBeforeUpdate']
const onDeactivated: typeof import('vue')['onDeactivated']
const onErrorCaptured: typeof import('vue')['onErrorCaptured']
const onMounted: typeof import('vue')['onMounted']
const onRenderTracked: typeof import('vue')['onRenderTracked']
const onRenderTriggered: typeof import('vue')['onRenderTriggered']
const onScopeDispose: typeof import('vue')['onScopeDispose']
const onServerPrefetch: typeof import('vue')['onServerPrefetch']
const onUnmounted: typeof import('vue')['onUnmounted']
const onUpdated: typeof import('vue')['onUpdated']
const provide: typeof import('vue')['provide']
const reactive: typeof import('vue')['reactive']
const readonly: typeof import('vue')['readonly']
const ref: typeof import('vue')['ref']
const resolveComponent: typeof import('vue')['resolveComponent']
const setActivePinia: typeof import('pinia')['setActivePinia']
const setMapStoreSuffix: typeof import('pinia')['setMapStoreSuffix']
const shallowReactive: typeof import('vue')['shallowReactive']
const shallowReadonly: typeof import('vue')['shallowReadonly']
const shallowRef: typeof import('vue')['shallowRef']
const storeToRefs: typeof import('pinia')['storeToRefs']
const toRaw: typeof import('vue')['toRaw']
const toRef: typeof import('vue')['toRef']
const toRefs: typeof import('vue')['toRefs']
const toValue: typeof import('vue')['toValue']
const triggerRef: typeof import('vue')['triggerRef']
const unref: typeof import('vue')['unref']
const useAttrs: typeof import('vue')['useAttrs']
const useCssModule: typeof import('vue')['useCssModule']
const useCssVars: typeof import('vue')['useCssVars']
const useLink: typeof import('vue-router')['useLink']
const useRoute: typeof import('vue-router')['useRoute']
const useRouter: typeof import('vue-router')['useRouter']
const useSlots: typeof import('vue')['useSlots']
const watch: typeof import('vue')['watch']
const watchEffect: typeof import('vue')['watchEffect']
const watchPostEffect: typeof import('vue')['watchPostEffect']
const watchSyncEffect: typeof import('vue')['watchSyncEffect']
}
// for type re-export
declare global {
// @ts-ignore
export type { Component, ComponentPublicInstance, ComputedRef, ExtractDefaultPropTypes, ExtractPropTypes, ExtractPublicPropTypes, InjectionKey, PropType, Ref, VNode, WritableComputedRef } from 'vue'
import('vue')
}
引入vue-router
这里使用的是vue-router 4.X版本
先贴官网:入门 | Vue Router
npm install vue-router@4
main.ts 引入
import { createApp } from 'vue'
import App from './App.vue'
//引入vue-router
import router from './router'
const app= createApp(App)
app.use(router)
app.mount('#app')
修改app.vue
<template>
<div>
<!-- 使用router-view加入路由跳转组件 -->
<router-view></router-view>
</div>
</template>
<style scoped>
</style>
整体布局页面
在src目录下添加layout文件夹,分别加入index主页面,myMenu菜单组件,menuTab导航栏组件
index.vue文件
<template>
<div>
<div class="layout">
<div class="headLine"></div>
<div class="layout-mainbox">
<div class="layout-menu">
<myMenu :menuList="menuList" :collapse="collapse" @handleOpen="handleOpen" @handleClose="handleClose"></myMenu>
<div class="menu-head">
<el-button @click="collapse=!collapse">
<el-icon>
<DArrowLeft v-show="!collapse"/>
<DArrowRight v-show="collapse"/>
</el-icon>
</el-button>
</div>
</div>
<menuTab></menuTab>
<router-view v-slot="{ Component }">
<keep-alive>
<component :is="Component" />
</keep-alive>
</router-view>
</div>
</div>
</div>
</template>
<script setup lang="ts">
import menuTab from './menuTab.vue';
import myMenu from './myMenu.vue'
const collapse=ref(false)
const menuList=ref([{
name:'单页面',
code:'menu1',
icon:'Eleme',
},{
name:'菜单',
code:'menu2',
icon:'sd',
children:[{
name:'子菜单1',
code:'sonmenu1',
icon:'Edit',
children:[{
name:'子菜单1',
code:'sonmenu3',
icon:'asdd',
}]
}]
}])
const handleOpen = (key: string, keyPath: string[]) => {
console.log(key, keyPath)
}
const handleClose = (key: string, keyPath: string[]) => {
console.log(key, keyPath)
}
</script>
<style scoped lang="scss">
$headheight:50px;
.layout{
height:100vh;
width:100vw;
}
.headLine{
width:100%;
height:$headheight;
border-bottom: 1px solid var(--el-border-color);
box-sizing: border-box;
}
.layout-mainbox{
height:calc(100% - $headheight);
width:100%;
display: flex;
}
.layout-menu{
height:100%;
max-width:300px;
.menu-head{
height:$headheight;
display:flex;
justify-content:right;
padding:15px 10px;
box-sizing: border-box;
border-right:1px solid var(--el-border-color);
.el-button{
padding:0 10px;
}
}
.menu-main{
height:calc(100% - $headheight);
}
}
</style>
这里创建的是经典的管理系统,分为头部,左侧菜单树和右侧上部页签栏、下部页面主体
知识点:vue3组合式api、组件的引入、响应式变量声明和使用
菜单组件
myMenu.vue文件
<template>
<template v-if="props.isChild">
<template v-if="props.menuList.length>0">
<template v-for="menu in props.menuList">
<el-sub-menu v-if="menu.children&&menu.children.length>0" :index="menu.code" :key="menu.code">
<template #title>
<el-icon>
<component :is="menu.icon" /></el-icon>
<span>{{menu.name}}</span>
</template>
<myMenu :isChild="true" :menuList="menu.children"></myMenu>
</el-sub-menu>
<el-menu-item v-else :index="menu.code" :key="menu.code+'n'">
<el-icon>
<component :is="menu.icon" />
</el-icon>
<span>{{menu.name}}</span>
</el-menu-item>
</template>
</template>
</template>
<template v-else>
<el-menu
default-active="2"
class="menu-main"
:collapse="props.collapse"
@open="handleOpen"
@close="handleClose"
>
<myMenu :isChild="true" :menuList="props.menuList"></myMenu>
</el-menu>
</template>
</template>
<script lang="ts" setup>
const props=defineProps(['menuList','isChild','collapse'])
const emit = defineEmits<{
(e: 'handleOpen', key: string,keyPath:string[]): void
(e: 'handleClose', key: string,keyPath:string[]): void
}>()
const handleOpen = (key: string, keyPath: string[]) => {
console.log(key, keyPath)
emit('handleOpen',key,keyPath)
}
const handleClose = (key: string, keyPath: string[]) => {
console.log(key, keyPath)
emit('handleClose',key,keyPath)
}
</script>
<style scoped lang="scss">
</style>
这里使用了递归嵌套组件来实现菜单
知识点:vue3组合式api、组件的props参数传递和使用、组件的自我调用、事件的传递(Vue2的$emit())
页签组件
<template>
<div>
<el-breadcrumb separator=" ">
<el-breadcrumb-item :class="{ 'active-page': $route.path === '/page/homepage' }" :to="{ path: '/homepage' }" >首页</el-breadcrumb-item>
<!-- <draggable
v-model="pageList"
animation="300"
ghost-class="ghostClass"
@end="onEnd"
> -->
<el-breadcrumb-item
@contextmenu.prevent.native="rightBtnClick($event,page)"
:class="{ 'active-page': $route.path === page.path }"
:to="{ path: page.path }"
v-for="page in pageList"
:key="page.code"
>{{ page.name}}<el-icon
v-if="element.path!=='/pages/homepage'"
class="deleteIcon"
@click.stop="deletePage(element)"
:size="14"
><CircleCloseFilled/></el-icon>></el-breadcrumb-item>
<!-- </draggable> -->
</el-breadcrumb>
<div class="rightbox" v-show="showRightBox" :style="`top:${rightBoxTop}px;left:${rightBoxLeft}px`">
<div @click="deleteOthers">关闭其他标签</div>
<div @click="deleteAll">关闭所有</div>
</div>
</div>
</template>
<script setup lang="ts">
import draggable from 'vuedraggable'
const rightBoxTop=ref(0)
const rightBoxLeft=ref(0)
const showRightBox=ref(false)
const currentPage=ref({})
const pageList=ref([])
function rightBtnClick(e,page) {
if(e.button===2&&this.$route.path === page.path){
if(e.clientY-this.originY||e.clientX-this.originx)return
rightBoxTop.value=e.clientY
rightBoxLeft.value=e.clientX
showRightBox.value=true
currentPage.value=page
}
}
function deleteOthers() {
}
function deleteAll() {
}
function deletePage(page:object) {
}
function onEnd() {
}
</script>
<style lang="scss" scoped>
:deep(.el-breadcrumb) {
margin: 8px 6px 6px 6px;
display: flex;
justify-content: left;
align-items: center;
.el-breadcrumb__item {
padding:0;
border-radius: 6px;
text-align: left;
user-select: none !important;
cursor: pointer !important;
border: 1px solid #e4e4e4;
margin:0 2px;
span {
user-select: none !important;
cursor: pointer !important;
font-weight: 400;
}
.deleteIcon {
margin-left: 4px;
color:#e4e4e4;
}
}
.el-breadcrumb__inner{
padding: 10px 8px 10px 10px;
height:100%;
// width:100%;
display: inline-block;
}
.el-breadcrumb__separator {
display: none;
}
.active-page {
border: 1px solid #66B1FFa0;
// padding: 10px;
background: #fff;
.deleteIcon {
margin-left: 4px;
color:#66B1FFc0;
}
span {
font-weight: bold !important;
}
}
}
/*定义要拖拽元素的样式*/
.ghostClass {
display: inline-block;
// background-color: #CCE4FF !important;
opacity: 0.5;
}
/*右键菜单 */
.rightbox{
position: fixed;
border-radius: 8px;
border:2px solid #c2c2c2a0;
box-shadow: 0 0 3px 3px #85858530;
background: #fff;
width:140px;
padding:3px 0;
transform: translate(10px,10px);
z-index: 200;
div{
border-bottom: 1px solid #c2c2c2;
height:20px;
font-size:14px;
color:#000;
padding:6px 10px;
user-select: none;
}
div:hover{
background: #85858520;
cursor: pointer;
}
}
</style>
原本想加上Vue2中的vuedraggble拖拽,结果发现有点问题所以先注释掉了,下次再调试他爸
路由表
简单页面布局画好后,开始配置路由
当然,需要在src下面创建一个空的页面文件做首页使用
src目录下创建文件夹router,加入index主文件和guard路由守卫文件(暂时只用到index)
因为目前是做纯前端页面,所以路由表直接先写死了,这里直接粘贴代码了,引入layout,使用的HTML5 模式
index.js
import {createRouter,createWebHistory } from 'vue-router'
import Layout from "../layout/index.vue";
const routes=[
{
path:'/page',
component:Layout,
redirect:'/page/homepage',
children:[
{
path:'homepage',
name:'homepage',
component:()=>import('@/view/homePage/index.vue')
}
]
}
]
const router = createRouter({
history:createWebHistory(),
routes:routes
})
export default router;
知识点:router4.x的几种路由模式
简单效果图
后续的逻辑下次在记录
下期连接:vue3从0开始搭建的学习记录(三)-CSDN博客