注意:vite需要Node.js版本>=12.0.0
安装方式(不使用模板):
#使用 NPM:
npm init vite@latest
#yarn 【推荐】
yarn create vite
#pnpm
pnpm create vite
填写项目名->选择vue->选择TS->安装依赖(npm i
)->启动项目(npm run dev
)
重置样式:拷贝一些样式文件到assets/styles下
安装sass: npm i sass -D
在main.ts文件中引入重置样式文件
import { createApp } from 'vue'
import App from './App.vue'
import './assets/styles/index.scss'
const app = createApp(App)
app.mount('#app')
使用重置样式中的变量,要先在vite.config.ts中配置
export default defineConfig({
plugins: [vue()],
css: {
// CSS 预处理器
preprocessorOptions: {
scss: {
additionalData: `@import '@/assets/style/variables.scss';`
}
}
},
})
此时报错Can’t find stylesheet to import.,无法识别@ 继续配置别名
import { defineConfig, resolveBaseUrl } from 'vite'
import vue from '@vitejs/plugin-vue'
// 之所以飘红 是应为 #node不带ts提示 需要安装 @types/node
import { resolve } from 'path'
//引入插件
import { createSvgIconsPlugin } from 'vite-plugin-svg-icons'
// https://vitejs.dev/config/
export default defineConfig({
plugins: [
vue(),
createSvgIconsPlugin({
iconDirs: [resolve(process.cwd(), 'src/assets/icons')],
symbolId: '[name]'
})
],
css: {
// CSS 预处理器
preprocessorOptions: {
scss: {
additionalData: `@import '@/assets/style/variables.scss';`
}
}
},
resolve: {
alias: {
'@': resolve(__dirname, 'src')
}
}
})
此时样式文件中的变量可以使用成功,但是飘红,需要继续安装npm i @types/node
css样式配置:body or html or root 里面配置全局样式变量
body{
--primary: #007bff;
}
使用全局样式变量:
.app {
height: 100%;
#var 可以直接使用 全局样式变量
background: var(--muted);
}
// Symbol 引入iconfont.js
import './assets/icons/iconfont.js'
在.vue文件中使用
<template>
<div>
<svg class="icon" aria-hidden="true">
<use xlink:href="#icon-shuju"></use>
</svg>
</div>
</template>
<script setup lang="ts">
</script>
<style scoped lang="scss">
.icon {
width: 1em;
height: 1em;
vertical-align: -0.15em;
fill: currentColor;
overflow: hidden;
}
</style>
npm i vite-plugin-svg-icons -D
//or
pnpm i vite-plugin-svg-icons -D
#如果报错Cannot find package 'fast-glob'
安装插件 yarn add fast-glob -D
在vite.config.ts中引入插件
//引入插件
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
// 之所以飘红 是应为 #node不带ts提示 需要安装 @types/node
import { resolve } from 'path'
//引入插件并在plugins中配置
import {createSvgIconsPlugin} from 'vite-plugin-svg-icons'
// https://vitejs.dev/config/
export default defineConfig({
plugins: [
vue(),
createSvgIconsPlugin({
iconDirs: [resolve(process.cwd(), 'src/assets/icons')],
symbolId: '[name]'
})
],
css: {
// CSS 预处理器
preprocessorOptions: {
scss: {
additionalData: `@import '@/assets/style/variables.scss';`
}
}
},
resolve: {
alias: {
'@': resolve(__dirname, 'src')
}
}
})
报错的话就再安装插件
#如果报错Cannot find package 'fast-glob'
安装插件 yarn add fast-glob -D
在main.ts中注册svg图标
// 注册svg-icons
import 'virtual:svg-icons-register'
创建组件/components/SvgIcon.vue
在SvgIcon.vue中
<template>
<svg class="icon" aria-hidden="true" :width="props.size + 'em'" :height="props.size + 'em'">
<use :xlink:href="'#' + props.name" :fill="props.color"></use>
</svg>
</template>
<script setup lang="ts">
interface Props {
name: string
color?: string
size?: number
}
const props = withDefaults(defineProps<Props>(), {
color: '',
size: 1,
})
</script>
<style scoped lang="scss">
.icon {
vertical-align: -0.15em;
fill: currentColor;
overflow: hidden;
}
</style>
在main.ts中全局注册
const app = createApp(App)
// 引入SvgIcon组件
import SvgIcon from "@/components/SvgIcon.vue";
// 全局注册
app.component('SvgIcon', SvgIcon)
app.mount('#app')
使用
<svg-icon name="user" :size="3" color="pink"></svg-icon>
针对一下代码使用@飘红的问题
// 引入SvgIcon组件
import SvgIcon from "@/components/SvgIcon.vue";
在vite-env.d.ts中配置即可
declare module "*.vue" {
import type { DefineComponent } from "vue"
const vueComponent: DefineComponent<{}, {}, any>
export default vueComponent
}
官网:Element Plus
安装:
# NPM
$ npm install element-plus --save
# Yarn
$ yarn add element-plus
完整引入在main.ts中
import { createApp } from 'vue'
import App from './App.vue'
import './assets/style/index.scss'
// Symbol 引入iconfont.js
import './assets/icons/iconfont.js'
// 注册svg-icons
import 'virtual:svg-icons-register'
const app = createApp(App)
// 引入SvgIcon组件
import SvgIcon from "@/components/SvgIcon.vue";
// 全局注册
app.component('SvgIcon', SvgIcon)
//引入ElementPlus
import ElementPlus from 'element-plus'
import 'element-plus/dist/index.css'
app.use(ElementPlus)
app.mount('#app')
由于emlementPlus默认是英文,故还需配置成为中文
在main.ts中全局配置
// 全局注册
app.component('SvgIcon', SvgIcon)
//引入ElementPlus
import ElementPlus from 'element-plus'
import 'element-plus/dist/index.css'
//ElementPlus配置为中文
import zhCn from 'element-plus/es/locale/lang/zh-cn'
app.use(ElementPlus,{
locale: zhCn,
size: 'small',
})
app.mount('#app')
此时即可正常使用
<template>
<div>
<el-button type="success">Success</el-button>
<el-popconfirm title="Are you sure to delete this?">
<template #reference>
<el-button>Delete</el-button>
</template>
</el-popconfirm>
</div>
</template>
在views文件夹下建立页面
安装路由npm install vue-router
在router/index.ts中
import { createRouter,createWebHashHistory} from "vue-router"
//路由的配置数组,路径与页面的对映
const routes = []
//路由实例
const router = createRouter({
routes,
history:createWebHashHistory(),
})
//暴露
export default router
在main.ts中使用路由
const app = createApp(App)
// 引入SvgIcon组件
import SvgIcon from "@/components/SvgIcon.vue";
// 全局注册
app.component('SvgIcon', SvgIcon)
//引入ElementPlus
import ElementPlus from 'element-plus'
import 'element-plus/dist/index.css'
//ElementPlus配置为中文
import zhCn from 'element-plus/es/locale/lang/zh-cn'
app.use(ElementPlus,{
locale: zhCn,
size: 'small',
})
//导入路由
import router from './router';
app.use(router)
app.mount('#app')
在App.vue文件中配置一级路由出口
<!-- 一级路由出口 -->
<router-view></router-view>
在layout.vue文件中配置二级路由出口
<!-- 二级路由出口 -->
<router-view></router-view>
在types文件下新建文件router.d.ts
//路由扩展types/router.d.ts
import type { RouteRecordRaw } from "vue-router";
interface metaItem {
title?: string
path?: string
}
interface RouteItem {
hidden?: Boolean
obj?: metaItem // 测试使用
children?: RoutesType
}
type myRouteType = RouteRecordRaw & RouteItem
export type RoutesType = Array<myRouteType>
在router/index.ts中
import { createRouter,createWebHashHistory} from "vue-router";
// import type { RouteRecordRaw } from "vue-router";
import Layout from '@/views/layout/index.vue'
//路由的配置数组,路径与页面的对映
import { RoutesType } from "../../types/router";
const routes:RoutesType = [
//登录
{
path:'/login',
component:() => import('@/views/login/index.vue')
},
//数据中心
{
path:'/data',
component: Layout,
children: [
{
path: '',
component: () => import('@/views/data/index.vue')
}
]
},
//用户管理
{
path:'/account',
component: Layout,
redirect:'/account/list',
children: [
{
path: '/account/list',
component: () => import('@/views/account/account-list.vue')
},
{
path: '/account/add',
component: () => import('@/views/account/account-add.vue')
},
{
path: '/account/personal',
component: () => import('@/views/account/personal.vue')
}
]
},
//文章管理
{
path:'/article',
component: Layout,
children: [
{
path: '',
component: () => import('@/views/article/index.vue')
}
]
},
//excel管理
{
path:'/excel',
component: Layout,
redirect:'/excel/import',
children: [
{
path: '/excel/import',
component: () => import('@/views/excel/excel-import.vue')
},
{
path: '/excel/export',
component: () => import('@/views/excel/excel-export.vue')
}
]
},
//配置404
{
path: '/:pathMatch(.*)*',
component: () => import('@/views/404/index.vue')
}
]
//路由实例
const router = createRouter({
routes,
history:createWebHashHistory(),
})
//暴露
export default router
在leftMenu.vue中
引入menu组件
<template>
<div class="left-menu">
<el-menu
default-active="/data"
class="el-menu-vertical-demo"
background-color="var(--el-color-danger-light-3)"
text-color="var(--el-bg-color)"
active-text-color="var(--el-color-danger-light-8)"
unique-opened
router
>
<el-menu-item index="/data">
<el-icon>
<svg-icon name="icon-shuju"/>
</el-icon>
<span>数据中心</span>
</el-menu-item>
<el-sub-menu index="1">
<template #title>
<el-icon><svg-icon name="icon-yonghu"/></el-icon>
<span>用户管理</span>
</template>
<el-menu-item index="/account/list">用户列表</el-menu-item>
<el-menu-item index="/account/add">添加用户</el-menu-item>
<el-menu-item index="/account/personal">个人中心</el-menu-item>
</el-sub-menu>
<el-menu-item index="/article">
<el-icon><svg-icon name="icon-bianjiwenzhang_huaban"/></el-icon>
<span>文章管理</span>
</el-menu-item>
<el-sub-menu index="2">
<template #title>
<el-icon><svg-icon name="icon-file-excel"/></el-icon>
<span>excel管理</span>
</template>
<el-menu-item index="/excel/import">excel导入</el-menu-item>
<el-menu-item index="/excel/export">excel导出</el-menu-item>
</el-sub-menu>
</el-menu>
</div>
</template>
<script setup lang="ts">
</script>
<style scoped>
.left-menu{
background-color:#f89898;
}
</style>
解决菜单栏右侧边框问题
.el-menu {
border-right: none !important;
}
默认选中菜单第一个default-active="/data"
添加router,配置index为对应路径
拿到路由信息对象
<script setup lang="ts">
import { useRoute } from 'vue-router';
const $route = useRoute()
console.log($route.path);
</script>
再修改默认激活路径为:default-active="$route.path"
vue3中内置组件transition官网地址:transition
在mainContent.vue中
<template>
<div class="p15">
<!-- 二级路由出口 -->
<router-view v-slot="{ Component }">
<transition name="fade" mode="out-in">
<component :is="Component"></component>
</transition>
</router-view>
</div>
</template>
设置折叠收起按钮图标,安装:npm install @element-plus/icons-vue
引入:
<script setup lang="ts">
import { Expand } from "@element-plus/icons-vue"
</script>
使用:
<el-icon size="20" class="cursor-pointer"><Expand /></el-icon>
点击图标菜单栏折叠或收起,要修改collapse的值为true或false,将其存到pinia中
安装pinia:npm install pinia
在main.ts中
//引入pinia并创建
import { createPinia } from 'pinia'
app.use(createPinia())
在store/index.ts中定义
//定义仓库
import { defineStore } from 'pinia'
import { ref } from 'vue'
export const useStore = defineStore('app',() => {
//定义collapse动态数据
let collapse = ref<boolean>(false)
return {
collapse,
}
})
在leftMenu.vue中获取响应式collapse并修改:collapse="collapse"
import { useStore } from '@/store';
import { computed } from 'vue';
//获取store的实例
const $appStore = useStore()
//获取响应式collapse
let collapse = computed(() => $appStore.collapse)
console.log(collapse);
在store/index.ts中
//修改collapse
const changeCollapse = () =>{
collapse.value = !collapse.value
}
return {
collapse,
changeCollapse
}
在mainTo.vue中实现点击图标切换及左侧菜单栏改变
<template>
<div class="right-top df aic plr15">
<el-icon @click="changeCollapse" size="20" class="cursor-pointer">
<Expand v-show="collapse" />
<Fold v-show="!collapse"/>
</el-icon>
右侧顶部
</div>
</template>
<script setup lang="ts">
import { Expand,Fold } from "@element-plus/icons-vue"
import { useStore } from "@/store"
import { computed } from 'vue';
//获取store对象
const $appStore = useStore()
const { changeCollapse } = $appStore
// console.log(changeCollapse);
const collapse = computed(() => $appStore.collapse)
</script>
<style scoped lang="scss">
.right-top{
height:40px;
background-color:#fcd3d3;
}
</style>
若@符号无法识别,飘红找不到模块则在tsconfig,json中配置
"noEmit": true,
"baseUrl": "./",
"paths": {
"@/*": [
"src/*"
],
"@": [
"src"
],
"~/*": [
"types/*"
]
}
在store/index.ts中
//设置collapse的值
const setCollapse = (val : boolean) =>{
collapse.value=val
}
return {
collapse,
changeCollapse,
setCollapse
}
在hooks文件夹下新建useResize.ts文件
import { useStore } from "@/store"
import { onMounted, onUnmounted } from "vue"
//在函数中监听屏幕的变化
export const useResize = () => {
const minW = 700//屏幕固定值
const $appStore = useStore()
const { setCollapse } = $appStore
const $_resize = () => {
//获取屏幕的宽
const w = document.body.clientWidth
if (w > minW) {
setCollapse(false)
} else {
setCollapse(true)
}
}
onMounted(() => {
$_resize()//初始化时要走一次故要调用一次
window.addEventListener('resize', $_resize)
})
onUnmounted(() => {
window.removeEventListener('resize', $_resize)
})
}
在layout/index.ts中调用函数
//引入屏幕变化后 侧边栏状态变化的hooks函数
import {useResize} from "@/hooks/useResize"
useResize()
静态–>使路由动态起来->配置路由下的meta,拿到title和path,凑成一个数组循环面包屑
router/index.ts
const routes: RoutesType = [
//登录
{
path: '/login',
component: () => import('@/views/login/index.vue')
},
//数据中心
{
path: '/data',
component: Layout,
meta: { title: '数据中心', path: '/data' },
children: [
{
path: '',
component: () => import('@/views/data/index.vue')
}
]
},
//用户管理
{
path: '/account',
component: Layout,
meta: { title: '用户管理', path: '/account' },
redirect: '/account/list',
children: [
{
path: '/account/list',
meta: { title: '用户列表', path: '/account/list' },
component: () => import('@/views/account/account-list.vue')
},
{
path: '/account/add',
meta: { title: '用户添加', path: '/account/add' },
component: () => import('@/views/account/account-add.vue')
},
{
path: '/account/personal',
meta: { title: '个人中心', path: '/account/personal' },
component: () => import('@/views/account/personal.vue')
}
]
},
//文章管理
{
path: '/article',
component: Layout,
meta: { title: '文章管理', path: '/article' },
children: [
{
path: '',
component: () => import('@/views/article/index.vue')
}
]
},
//excel管理
{
path: '/excel',
component: Layout,
meta: { title: 'excel管理', path: '/excel' },
redirect: '/excel/import',
children: [
{
path: '/excel/import',
meta: { title: 'excel导入', path: '/excel/import' },
component: () => import('@/views/excel/excel-import.vue')
},
{
path: '/excel/export',
meta: { title: 'excel导出', path: '/excel/export' },
component: () => import('@/views/excel/excel-export.vue')
}
]
},
//配置404
{
path: '/:pathMatch(.*)*',
component: () => import('@/views/404/index.vue')
}
]
在mainTop.vue中,通过获取路由信息获取meta对象,判断matched是否为非空,非空则添加到循环数组中,点击菜单之后,面包屑也应该变化,故每点击一次,数据重新获取一次,将重新获取的数据添加到循环数组中,获取重新获取到的数据可以用:监听,计算属性,watchEffect
import { useRoute } from 'vue-router'
import { metaItem } from '~/router'
//获取store对象
const $appStore = useStore()
const { changeCollapse } = $appStore
// console.log(changeCollapse);
const collapse = computed(() => $appStore.collapse)
// 通过获取路由信息获取meta对象,判断matched是否为非空,非空则添加到循环数组中
//点击菜单之后,面包屑也应该变化,故每点击一次,数据重新获取一次,将重新获取的数据添加到循环数组中
//获取重新获取到的数据可以用:监听,计算属性,watchEffect
// 获取meta
const $route = useRoute()
console.log(111,$route.matched);
//计算属性设置面包屑
const breadList = computed(() => {
let arr:Array<metaItem> =[]
//循环当前路由下的meta
$route.matched.forEach(item => {
//判断当前meta是否有title或path
if(item.meta?.title){
arr.push(item.meta)
}
})
return arr
})
import { computed,watch,ref} from 'vue';
//侦听器设置面包屑-监听某一个属性(),回调,立即执行
let breadList2 = ref<Array<metaItem>>([])
watch(
$route,
()=>{
breadList2.value=[]
$route.matched.forEach(item => {
//判断当前meta是否有title或path
if(item.meta?.title){
breadList2.value.push(item.meta)
}
})
},
{
immediate:true //立即执行
}
)
import { computed,watch,ref,watchEffect} from 'vue';
//watcheffect设置面包屑 副作用监听 会初始化执行里面代码 里面有响应式数据变化都会执行代码
let breacList3 = ref<Array<metaItem>>([])
const stop = watchEffect((deCleanr) => {
breacList3.value = []
$route.matched.forEach(item => {
if (item.meta?.title) {
breacList3.value.push(item.meta)
}
})
deCleanr(() => {
// 清除副作用
})
})
// 调用stop函数后,watchEffect将停止监听
stop()
<el-dropdown @command="handleCommand">
<span class="el-dropdown-link">
欢迎您,xxx
<el-icon class="el-icon--right">
<arrow-down />
</el-icon>
</span>
<template #dropdown>
<el-dropdown-menu>
<el-dropdown-item command="/login">退出登录</el-dropdown-item>
</el-dropdown-menu>
</template>
</el-dropdown>
import { useRoute, useRouter } from "vue-router";
//下拉菜单的事件
const $router = useRouter();
const handleCommand = (command: string) => {
$router.push(command);
};
<template>
<div class="login df aic jcc">
<div class="mr-1">
<h2 class="font-weight">我是文字我是文字</h2>
<h1 class="font-weight mt-2 border-bottom-c pb-1">我是文字</h1>
<h2 class="font-weight mt-2">我是文字我是文字</h2>
</div>
<div class="right rounded p15">
<h2 class="font-weight mb-3">欢迎登录</h2>
<el-form :model="loginFormData" :rules="rules" status-icon ref="loginFormRef">
<el-form-item prop="account">
<el-input v-model="loginFormData.account">
<template #prefix>
<el-icon class="el-input__icon">
<svg-icon name="icon-yonghu1"></svg-icon>
</el-icon>
</template>
</el-input>
</el-form-item>
<el-form-item prop="password">
<el-input v-model="loginFormData.password" type="password">
<template #prefix>
<el-icon class="el-input__icon">
<svg-icon name="icon-tianchongxing-1"></svg-icon>
</el-icon> </template
></el-input>
</el-form-item>
<el-form-item>
<el-button type="primary" class="login-btn" @click="handleLogin(loginFormRef)">登录</el-button>
</el-form-item>
</el-form>
</div>
</div>
</template>
<script setup lang="ts">
import { ref,reactive } from "vue";
import type { FormInstance, FormRules } from 'element-plus';
import { useRouter } from "vue-router"
//登录数据约束
interface loginForm {
account: string;
password: string;
}
//登录数据
const loginFormData = ref<loginForm>({
account: "",
password: "",
});
const loginFormRef = ref<FormInstance>()
const $router = useRouter()
//验证规则
const rules = reactive<FormRules>({
account: [
{ required: true, message: "请输入账号", trigger: "blur" },
{ min: 3, max: 6, message: "账号需为3-6位", trigger: "blur" },
],
password: [
{ required: true, message: "请输入密码", trigger: "blur" },
{ min: 6, max: 12, message: "密码需为6-12位", trigger: "blur" },
],
});
//登录函数
const handleLogin = (formEl: FormInstance | undefined) => {
if(!formEl) return
formEl.validate((valid)=>{
$router.push('/data')
})
}
</script>
<style scoped lang="scss">
.login {
height: 100%;
background-color: #e5e5e5;
.right {
width: 240px;
height: 260px;
background-color: white;
.login-btn {
width: 100%;
}
}
}
</style>