参考项目架构:yiming_chang.gitee.io/vue-pure-ad…
项目搭建
-
技术栈
Vue3+vue-router+Vuex/pinia+elementPlus+axios+ts+sass -
使用vue脚手架(webpack)创建项目
vue create 项目名称 -
选择项目预设
? Please pick a preset: (Use arrow keys) Default ([Vue 3] babel, eslint) Default ([Vue 2] babel, eslint) > Manually select features (手动选择功能) -
项目所需的功能
? Check the features needed for your project: (Press <space> to select, <a> to toggle all, <i> to invert selection, and <enter> to proceed) >(*) Babel (*) TypeScript ( ) Progressive Web App (PWA) Support ( ) Router ( ) Vuex ( ) CSS Pre-processors ( ) Linter / Formatter ( ) Unit Testing ( ) E2E Testing -
选择Vue版本
? Choose a version of Vue.js that you want to start the project with (Use arrow keys) > 3.x 2.x -
是否使用类样式组件语法
? Use class-style component syntax? (y/N) `No` -
是否使用Babel和Typescript
? Use Babel alongside TypeScript (required for modern mode, auto-detected polyfills, transpiling JSX)? (Y/n) `No` -
希望将Babel、ESLint等的配置放在哪
# Where do you prefer placing config for Babel, ESLint, etc.? (Use arrow keys) > In dedicated config files(专用配置文件中) In package.json (package.json中) -
保存为未来项目的预设
Save this as a preset for future projects? (y/N) `No` -
目录结构
node_modules: 项目依赖包 public: 静态资源 src: 项目核心代码 assets: 静态方法 components: 公共组件 app.vue: 根组件 main.ts: 入口文件 shims-vue.d.ts: 对vue组件的支持
安装element plus UI组件库
-
安装
# npm install element-plus --save -
按需导入
-
安装两个插件
# npm install -D unplugin-vue-components unplugin-auto-import -
新建
vue.config.ts配置文件const AutoImport = require('unplugin-auto-import/webpack') const Components = require('unplugin-vue-components/webpack') const { ElementPlusResolver } = require('unplugin-vue-components/resolvers') module.exports = { // ... plugins: [ AutoImport({ resolvers: [ElementPlusResolver()], }), Components({ resolvers: [ElementPlusResolver()], }), ], }
# 发现按需导入无效 解决方法: 1.使用手动导入的方式 2.自己写一个插件,实现按需导入 -
-
手写插件,实现按需导入
// src > useHooks >useHooks.ts // 自己封装的按需导入插件 // 本质:手动按需导入 import { ElButton, ElInput } from "element-plus"; let arrs = [ { com: ElButton }, { com: ElInput } ] // 注册 export let installPlugs = { install(app: any) { // app:当前vue实例 console.log(app); arrs.forEach((item) => { app.use(item.com) // 注册 }) } }// main.ts 全局挂载 ... import { installPlugs } from "./useHooks/useHooks"; // 按需导入插件 ... app.use(installPlugs) // 挂载按需导入插件 ...
安装sass
-
安装
# npm install sass sass-loader
处理全局样式
1.高度的继承
2.盒子模型
3.项目的公共样式
4.处理icon
5.字体样式
- 在
src目录下 新建一个style文件夹 - 处理项目的样式,并进行模块化
- 在
main.ts入口文件中进行全局引入
封装axios请求
-
安装
# npm install axios -
添加公共属性以及拦截器
// src > https > index.ts // 封装axios请求 import axios from "axios"; // 创建axios实例 let server = axios.create({ baseURL: 'https://www.fastmock.site/mock/925c830ca442f3af92806aa5128523eb/zero', // 请求基本地址 timeout: 6000 // 超时时间(ms) }) // 添加请求拦截器 server.interceptors.request.use( (config) => { // config:请求的配置对象 return config; }, (error) => { return Promise.reject(error); } ); // 添加响应拦截器 server.interceptors.response.use( (response) => { // response:响应成功的对象 return response.data; }, (error) => { return Promise.reject(error); } ); export default server
添加路由
-
安装
# npm install vue-router -
创建路由实例,创建路由表
// src > Route > index.ts // 引入路由方法 import { createRouter, createWebHistory, createWebHashHistory } from "vue-router"; // 创建路由表 let routes: any = [ {path: '/',redirect: '/login'}, {path: '/login',component: () => import('../view/Login/index.vue')}, {path: '/home',component: () => import('../view/Home/index.vue')} ] // 创建路由实例 let router = createRouter({ history: createWebHashHistory(), routes: routes }) export default router- 在
main入口文件中全局挂载
- 在
添加暗黑模式
-
新建一个模块
<!-- 暗黑模式 主题切换 --> <template> <div class="styleChange"> <!-- 引入element的switch开关 --> <el-switch v-model="value" @change="changeStyle" inline-prompt :active-icon="Sunny" :inactive-icon="Moon" /> </div> </template> <script lang='ts' setup> import { ref } from "vue"; // 引入vue的ref import { Sunny, Moon } from "@element-plus/icons-vue"; // icon图标 import { useDark, useToggle } from "@vueuse/core"; // 定义主题的方法 let value = ref(false) // 默认正常主题 // 切换主题 const changeStyle = () => { if (value.value) { // 定义主题样式 const isDark = useDark({ valueDark: "dark", }); const toggleDark = useToggle(isDark); toggleDark(); // 执行该主题样式 } else { // 定义主题样式 const isDark = useDark({ valueLight: "light", }); const toggleDark = useToggle(isDark); toggleDark(); // 执行该主题样式 } } </script> -
在单页面文件中添加一个类名
<!-- index.html --> <html lang="" class="light"> -
在入口文件中引入样式
// main.ts import 'element-plus/theme-chalk/src/dark/css-vars.scss' // 暗黑模式样式
登录模块
<template>
<div class="content">
<el-form ref="ruleFormRef" :model="ruleForm" status-icon :rules="rules" label-width="120px"
class="demo-ruleForm">
<!-- 账号 -->
<el-form-item prop="username">
<el-input v-model="ruleForm.username" autocomplete="off" :prefix-icon="User" />
</el-form-item>
<!-- 密码 -->
<el-form-item prop="password">
<el-input v-model="ruleForm.password" type="password" autocomplete="off" :prefix-icon="Lock" />
</el-form-item>
<!-- 验证码 -->
<!-- 子传父 -->
<ZinputVer @getVerify="getVerify" />
<!-- 登录 -->
<el-form-item>
<el-button type="primary" @click="submitForm()">登录</el-button>
</el-form-item>
</el-form>
</div>
</template>
<script lang='ts' setup>
import { reactive, ref } from 'vue'
import type { FormInstance } from 'element-plus'
import { User, Lock } from '@element-plus/icons-vue' // icon图标
import ZinputVer from "../../components/ZinputVer/index.vue"; // 封装的验证码组件
import { ElMessage } from 'element-plus' // 消息提示
import { useRouter, useRoute } from "vue-router"; // 引入路由表和路由对象
import { Login } from "../../https/api/login"; // 请求
const ruleFormRef = ref<FormInstance>()
// 使用路由表盒路由对象
let router = useRouter()
let route = useRoute()
// 账号校验逻辑
const validateName = (rule: any, value: any, callback: any) => {
if (value === '') {
callback(new Error('账号不能为空!'))
} else {
callback()
}
}
// 密码检验逻辑
const validatePass = (rule: any, value: any, callback: any) => {
if (value === '') {
callback(new Error('密码不能为空!'))
} else {
callback()
}
}
// 表单收集的数据
const ruleForm = reactive({
username: '', // 账号
password: '', // 密码
verify: false // 验证码是否正确
})
// 校验
const rules = reactive({
username: [{ validator: validateName, trigger: 'blur' }],
password: [{ validator: validatePass, trigger: 'blur' }]
})
// 父组件接收子组件的数据
const getVerify = (val: boolean) => {
ruleForm.verify = val
}
// 点击登录
const submitForm = () => {
if (!ruleForm.username || !ruleForm.password) {
return ElMessage.warning('账号或密码不能为空哦~')
}
if (!ruleForm.verify) {
return ElMessage.warning('验证码不正确哦~')
}
// 发起登录请求
loginData(ruleForm.username, ruleForm.password)
}
// 登录请求
const loginData = (name: string, password: string) => {
// 发起请求
Login({ name, password }).then((res: any) => {
if (res.code == 200) {
ElMessage.success('登录成功.')
// 保存token
sessionStorage.setItem('token', res.token)
// 无记录路由跳转到内部页面
router.replace('/layout')
}
})
}
</script>
-
封装的验证码组件
<!-- 封装验证码组件 --> <template> <div class="zinputver"> <div class="input"> <el-form ref="ruleFormRef" :model="ruleForm" status-icon :rules="rules" label-width="120px" class="demo-ruleForm"> <el-form-item prop="verify"> <el-input v-model="ruleForm.verify" autocomplete="off" :prefix-icon="Warning" /> </el-form-item> </el-form> </div> <div class="img"> <img :src="image"> </div> </div> </template> <script lang='ts' setup> import { reactive, ref } from 'vue' import type { FormInstance } from 'element-plus' import { Warning } from '@element-plus/icons-vue' // icon图标 import { ElMessage } from 'element-plus' // 消息提示 import { Verify } from "../../https/api/login"; // 请求 let content = ref('') // 验证码 let image = ref('') // 验证码图片 // 发起请求获取验证码 Verify().then((res: any) => { content.value = res.content image.value = res.img }) const ruleFormRef = ref<FormInstance>() // 收集表单数据 const ruleForm = reactive({ verify: "" // 验证码 }) // 接收父传子的方法 let emits = defineEmits(['getVerify']) // 验证码校验逻辑 const validateVerify = (rule: any, value: any, callback: any) => { if (value === '') { callback(new Error('验证码不能为空!')) } else if (value) { if (value != content.value) { ElMessage.warning('验证码输入错误.') callback(new Error()) // 触发父组件中的方法,并传递参数 emits('getVerify', false) } else { // 触发父组件中的方法,并传递参数 emits('getVerify', true) callback() } } } // 校验 const rules = reactive({ verify: [{ validator: validateVerify, trigger: 'blur' }] }) </script>
动态生成侧边栏
-
父组件
<!-- 侧边栏 --> <template> <div class="navList"> <!-- 侧边栏内容 --> <div class="navListContent"> <el-menu active-text-color="#ffd04b" background-color="#545c64" class="el-menu-vertical-demo" :default-active="defaultActive" :collapse="isCollapse" text-color="#fff"> <!-- 展示侧边栏 --> <LeftNav :navList="navList" /> </el-menu> </div> <!-- 展开和收缩 --> <div class="navBottom"> <i v-if="show" class="iconfont icon-shousuocaidan" @click="changShow"></i> <i v-else class="iconfont icon-shousuocaidan-copy" @click="changShow"></i> </div> </div> </template> <script lang='ts' setup> import { ref } from "vue"; import LeftNav from "./LeftNav.vue"; // 侧边栏组件 import { navlistData } from "../../https/api/layout"; // 请求 let navList = ref([]) // 侧边栏数据 let show = ref(true) // 侧边栏是否展开 let isCollapse = ref(false) // 是否水平折叠 let defaultActive = ref('/index') // 默认激活项 const getNavList = () => { // 发起请求 navlistData({ tokan: sessionStorage.getItem('token') }).then((res: any) => { navList.value = res.navList }) } getNavList() // 点击展开与收缩 const changShow = () => { show.value = !show.value if (show.value) { isCollapse.value = false } else { isCollapse.value = true } } </script> -
组件抽离,递归组件
<template> <div v-for="(item, index) in navList" :key="index"> <!-- 有嵌套 --> <el-sub-menu :index="(item as any).path" v-if="(item as any).children"> <!-- 标题 --> <template #title> <el-icon> <location /> </el-icon> <span>{{ (item as any).name }}</span> </template> <!-- 递归组件 --> <LeftNav :navList="(item as any).children" /> </el-sub-menu> <!-- 无嵌套 --> <el-menu-item :index="(item as any).path" v-else> <el-icon> <icon-menu /> </el-icon> <template #title>{{ (item as any).name }}</template> </el-menu-item> </div> </template> <script lang='ts' setup> import { Menu as IconMenu, Location, } from '@element-plus/icons-vue' // icon图标 // 接收父传子的数据 let props = defineProps({ navList: { type: Array } }) </script>
权限:判断是否登录(内外部页面)
-
前端:处理内部外部页面
-
后端:处理内部外部接口
-
方法:通过路由守卫实现
// Router > beforeRouter.ts // 路由守卫 import router from "./index"; // 引入路由 // 创建路由前置守卫 router.beforeEach((to, from, next) => { // 判断内部、外部页面 if (to.fullPath != '/login') { // 内部 //判断是否登录 if (sessionStorage.getItem('token')) { // 已登录 next() // 放行 } else { // 未登录 next('/login') // 跳转登录页 } } else { // 外部 next() // 放行 } })
添加进度条
-
安装第三方库:
# npm i nprogress -S -
在路由守卫中添加进度条
import router from "./index"; // 引入路由 // 引入进度条和样式 import NProgress from 'nprogress' import 'nprogress/nprogress.css' // 创建路由前置守卫 router.beforeEach((to, from, next) => { NProgress.start(); // 开启进度条 ... }) // 创建路由后置守卫 router.afterEach(() => { NProgress.done(); // 关闭进度条 })导出表格
-
export-from-json -
下包:
npm i --save export-from-json -
引包:
import exportFromJSON from 'export-from-json' -
使用:
- 导出的数据:
const data = [{ foo: 'foo'}, { bar: 'bar' }] - 导出的文件名:
const fileName = 'download' - 导出的文件后缀:
const exportType = exportFromJSON.types.csv - 导出:
exportFromJSON({ data, fileName, exportType })
- 导出的数据:
🌰:
const data = this.bannerList
const fileName = '轮播图列表'
const exportType = 'xls'
exportFromJSON({ data, fileName, exportType })
分页
- 每页显示的数据
arr.slice((页码-1)*每页条数,页码*每页条数)