文章目录
前言
视频讲解为 B 站UP主:混吃等死的咸鱼仔
视频链接
一、使用 vue3
1.创建项目
1.创建空白目录
2.cmd 输入 vue ui
3.在界面中创建项目
创建-输入目录地址-再次创建-起一个名字-手动配置-勾选 Router、VueX、CSS Pre-processors、使用配置文件 - 使用vue 3x、Sass/SCSS (with dart-sass)、ESLint + Standard config - 再次创建
-不保存预设
4.安装依赖 axions
5.回到项目当中-终端输入- npm i vue@3.2.8 vue-router@4.0.11 vuex@4.0.2
6.重新 npm i
7.启动 npm run serve
项目创建完成
2.代码格式化
1.在vscode 中安装 Prettier - Code formatter
2.设置-搜索 save - 勾选 Edlitor:Format On Save
3.在 Home.vue 界面中右击 - 使用…格式化文档-配置默认格式化程序-选择 Prettier - Code formatter
4.新建 .prettierrc (跟 package.json 同级)
{
“semi”: false, //是否使用分号来结尾
“singleQuote”: true, //是否使用单引号
“trailingComma”: “none” //是否最后一句话使用逗哈来结尾
}
5.在 .eslintrc.js 中写入
rules: {
‘indent’: 0,
‘space-before-function-paren’: 0
}
6.在home.vue中输入一个分号测试是否自动删除
3.commit规范
1.安装 commitizen和cz-customizable
npm install -g commitizen@4.2.4
npm i cz-customizable@6.3.0 --save-dev
2.在 package.json 里最下方写入
“config”: {
“commitizen”: {
“path”: “node_modules/cz-customizable”
}
}
3.将.cz-config.js 拖入到根目录中
资源链接
4.终端输入 git add .
git cz (替代 git commit)
feat
可修改范围不选
commit 提交了那些东西
git push
4.强制 commit
1.使用husky进行强制git代码提交规范
npm install --save-dev @commitlint/config-conventional@12.1.4 @commitlint/cli@12.1.4
npm install husky@7.0.1 --save-dev
npx husky install
根目录多出一个 .husky文件
2.将 commitlint.config.js 文件拖入根目录
3.在 package.json 中 加入
“scripts”: {
“prepare”: “husky install”
}
4.执行 npm run prepare
5.新增husky配置文件 并往里面写入
终端输入:npx husky add .husky/commit-msg
.husky 目录下多出一个 commit-msg 配置文件
用house key 和 commit list进行一个关联
在commit-msg中(把undefined覆盖)写入 npx --no-install commitlint --edit
6.终端输入一个 不符合规矩的
git add .
git commit -m ‘新功能’
7.输入一个符合规矩的
git commit -m ‘feat: 新功能’
5.强制代码规范
1.使用husky强制代码格式化 创建配置文件
终端执行:npx husky add .husky/pre-commit
2.在 .husky 目录下 .pre-commit 中输入
npx lint-staged
3.在 package.json中配置
最下方加入
“lint-staged”: {
“src/**/*.{js,vue}”: [ //src目录下所有的js和vue文件
“eslint --fix”, // 自动修复
“git add” // 自动提交时修复
]
}
6.按需引入 elementplus
elementplus官网
1.安装: npm install element-plus --save
2.按需导入:npm install -D unplugin-vue-components unplugin-auto-import
3.我们使用的是 Webpack,在根目录下创建 vue.config.js
将一下代码写入
// webpack.config.js
const { defineConfig } = require('@vue/cli-service')
const AutoImport = require('unplugin-auto-import/webpack')
const Components = require('unplugin-vue-components/webpack')
const { ElementPlusResolver } = require('unplugin-vue-components/resolvers')
module.exports = defineConfig({
transpileDependencies: true,
configureWebpack: config => {
config.plugins.push(AutoImport({
resolvers: [ElementPlusResolver()]
}))
config.plugins.push(Components({
resolvers: [ElementPlusResolver()]
}))
}
})
7.vue3.2新特性
1.不用再使用根标签包括
2.自己看,css变量可以用在script
<template>
<div class="box"></div>
<router-view />
</template>
<script setup>
const boxWidth = '100px'
</script>
<style lang="scss">
.box {
width: v-bind(boxWidth);
height: 100px;
background: red;
}
</style>
8.初始化项目
1.App.vue清空
<template>
<router-view></router-view>
</template>
<style lang="sass"></style>
2.将 styles文件夹 复制到 src 目录下
3.在 main.js 导入 scss文件 import '@/styles/index.scss
4.创建登录页面,在views目录下创建 login 目录,目录下创建 index.vue
5.运行 访问 login
9.登录页面静态
<template>
<div class="login-container">
<el-form ref="formRef" :model="form" class="login-form">
<div class="title-container">
<h3 class="title">用户登录</h3>
</div>
<el-form-item>
<!-- <el-icon :size="20" class="svg-container">
<edit />
</el-icon> -->
<svg-icon icon="user" class="svg-container"></svg-icon>
<el-input v-model="form.name"></el-input>
</el-form-item>
<el-form-item>
<svg-icon icon="password" class="svg-container"></svg-icon>
<el-input v-model="form.password"></el-input>
</el-form-item>
<el-button type="primary" class="login-button">登录</el-button>
</el-form>
</div>
</template>
<script setup>
import { ref } from 'vue'
import { Edit } from '@element-plus/icons-vue'
const form = ref({
name: '',
password: ''
})
</script>
<style lang="scss" scoped>
$bg: #2d3a4b;
$dark_gray: #889aa4;
$light_gray: #eee;
$cursor: #fff;
:deep(.el-input) {
input {
box-shadow: none; //去除白边
&:focus {
box-shadow: none; //输入内容原本样式
}
}
}
.login-container {
min-height: 100%;
width: 100%;
background-color: $bg;
overflow: hidden;
.login-form {
position: relative;
width: 520px;
max-width: 100%;
padding: 160px 35px 0;
margin: 0 auto;
overflow: hidden;
::v-deep .el-form-item {
border: 1px solid rgba(255, 255, 255, 0.1);
background: rgba(0, 0, 0, 0.1);
border-radius: 5px;
color: #454545;
}
::v-deep .el-input {
display: inline-block;
height: 47px;
width: 85%;
input {
background: transparent;
border: 0px;
-webkit-appearance: none;
border-radius: 0px;
padding: 12px 5px 12px 15px;
color: $light_gray;
height: 47px;
caret-color: $cursor;
}
}
.login-button {
width: 100%;
box-sizing: border-box;
}
}
.tips {
font-size: 16px;
line-height: 28px;
color: #fff;
margin-bottom: 10px;
span {
&:first-of-type {
margin-right: 16px;
}
}
}
.svg-container {
padding: 6px 5px 6px 15px;
color: $dark_gray;
vertical-align: middle;
display: inline-block;
}
.title-container {
position: relative;
.title {
font-size: 26px;
color: $light_gray;
margin: 0px auto 40px auto;
text-align: center;
font-weight: bold;
}
::v-deep .lang-select {
position: absolute;
top: 4px;
right: 0;
background-color: white;
font-size: 22px;
padding: 4px;
border-radius: 4px;
cursor: pointer;
}
}
.show-pwd {
// position: absolute;
// right: 10px;
// top: 7px;
font-size: 16px;
color: $dark_gray;
cursor: pointer;
user-select: none;
}
}
</style>
10.svg-icon
记住先安装 npm install @element-plus/icons-vue
1.将 icons 复制到 src 目录下
2.在 components 目录下创建 SvgIcon,目录下再创建 index.vue
<template>
<svg class="svg-icon" aria-hidden="true">
<use :xlink:href="iconName"></use>
</svg>
</template>
<script setup>
import { defineProps, computed } from 'vue'
const props = defineProps({
icon: {
type: String,
required: true
}
})
const iconName = computed(() => {
return `#icon-${props.icon}`
})
</script>
<style lang="scss" scoped>
.svg-icon {
width: 1em;
height: 1em;
vertical-align: -0.15em;
fill: currentColor;
overflow: hidden;
}
</style>
3.在 icons 目录下 创建 index.js
import SvgIcon from '@/components/SvgIcon'
const svgRequired = require.context('./svg', false, /\.svg$/)
svgRequired.keys().forEach((item) => svgRequired(item))
export default (app) => {
app.component('svg-icon', SvgIcon)
}
4.在 main.js 中导入创建好的 index.js
import { createApp } from 'vue'
import App from './App.vue'
import router from './router'
import store from './store'
import '@/styles/index.scss'
import SvgIcon from '@/icons'
const app = createApp(App)
SvgIcon(app)
app.use(store).use(router).mount('#app')
5.安装 svg loader,终端输入:npm i --save-dev svg-sprite-loader@6.0.9
6.在 vue.config.js 中配置
7.使用 svg-cion
上方:
const path = require('path')
function resolve(dir) {
return path.join(__dirname, dir)
}
const webpack = require('webpack')
下方:
chainWebpack(config) {
// 设置 svg-sprite-loader
// config 为 webpack 配置对象
// config.module 表示创建一个具名规则,以后用来修改规则
config.module
// 规则
.rule('svg')
// 忽略
.exclude.add(resolve('src/icons'))
// 结束
.end()
// config.module 表示创建一个具名规则,以后用来修改规则
config.module
// 规则
.rule('icons')
// 正则,解析 .svg 格式文件
.test(/\.svg$/)
// 解析的文件
.include.add(resolve('src/icons'))
// 结束
.end()
// 新增了一个解析的loader
.use('svg-sprite-loader')
// 具体的loader
.loader('svg-sprite-loader')
// loader 的配置
.options({
symbolId: 'icon-[name]'
})
// 结束
.end()
config
.plugin('ignore')
.use(
new webpack.ContextReplacementPlugin(/moment[/\\]locale$/, /zh-cn$/)
)
config.module
.rule('icons')
.test(/\.svg$/)
.include.add(resolve('src/icons'))
.end()
.use('svg-sprite-loader')
.loader('svg-sprite-loader')
.options({
symbolId: 'icon-[name]'
})
.end()
}
11.表单校验
1.el-form 标签上加上 :rules=“rules”
2.script上定义校验规则
const rules = ref({
username: [
{
required: true,
message: '请输入用户名',
trigger: 'blur'
}
],
password: [
{
required: true,
message: '请输入密码',
trigger: 'blur'
}
]
})
3.el-form-item 上绑定校验规则 prop=" "
4.统一校验,在 el-button 定义一个点击事件 @click=“handleLogin”
5.script 定义函数
const formRef = ref(null)
const handleLogin = () => {
formRef.value.validate((valid) => {
if (valid) {
alert('submit!')
} else {
console.log('error submit!!')
return false
}
})
}
12.发送登录请求
1.在 src 目录下新建 api 文件夹 - 新建 request.js
import axios from "axios";
const service = axios.create({
baseURL: process.env.VUE_APP_BASE_API,
timeout: 5000
})
2.根目录新建 开发环境 .env.development
ENV = 'development'
VUE_APP_BASE_API = '/api'
3.再创建生产环境 .env.production
ENV = 'production'
VUE_APP_BASE_API = '/prod-api'
4.解决跨域问题,在 vue.config 中配置代理
devServer: {
https: false,
hotOnly: false,
proxy: {
'/api': {
target: 'https://lianghj.top:8888/api/private/vi/',
changeOrigin: true,
pathRewrite: {
'^/api': ''
}
}
}
}
5.修改 request.js baseURL: process.env.VUE_APP_BASE_API
(记得去掉引号)
6.api 目录下新建 login.js
import request from './request'
export const login = (data) => {
return request({
url: '/login',
method: 'POST',
data
})
}
7.重启项目
8.在 login.vue 导入import { login } from '@/api/login'
9.发送请求
const handleLogin = () => {
formRef.value.validate(async (valid) => {
if (valid) {
// alert('submit!')
await login(form.value)
} else {
console.log('error submit!!')
return false
}
})
}
这里出现了一个 BUG
Uncaught (in promise) Error: Request failed with status code 404
解决办法:在 babel.config.js 加入以下下配置
module.exports = {
"env": {
"development": {
"sourceMaps": true,
"retainLines": true,
}
},
presets: [
'@vue/cli-plugin-babel/preset'
]
}
响应拦截器
1.将密码框 改为暗文,在 input 上加上一个 type值为 password,input 标签后加上一个小图标,<svg-cion icon="eye" @click="changeType"></svg-icon>
,定义方法,完整代码如下
<el-input v-model="form.password" :type="passwordType"></el-input>
<svg-icon
:icon="passwordType === ‘password’ ? 'eye' : 'eye-open'"
@click="changeType"
</svg-icon>
<script setup>
const passwordType = ref('password')
const changeType = () => {
if (passwordType.value === 'password') {
passwordType.value = 'text'
} else {
passwordType.value = 'password'
}
}
</script>
2.需要知道请求回来的数据
const res = await login(form.value)
excess响应请求拦截器
- 在 request.js 中配置
import { ElMessage } from 'element-plus'
service.interceptors.response.use(
(response) => {
const { data, meta } = response.data
if (meta.status === 200 || meta.status === 201) {
return data
} else {
ElMessage.error(meta.msg)
return Promise.reject(new Error(meta.msg))
}
},
(error) => {
error.response && ElMessage.error(error.response.data)
return Promise.reject(new Error(error.response.data))
}
}
- 在 main.js 中 引入
import 'element-plus/dist/index.css'
登录
存储 Token VueX
1.将 store 目录下是 index.js 修改
import default createStore({
modules: {
}
})
2.在 store 目录下建一个文件夹 modules下建一个 app.js
3.login 包的 index.vue 找到 login(form.value),注释掉登录,剪切 import { login } from ‘@/api/login’,粘贴到 app.js,修改 login 名称 import { login as loginApi } from '@/api/login'
在 app.js 进行登录操作
import { login as loginApi } from '@/api/login'
import router from '@/router'
import { setTokenTime } from '@/utils/auth'
export default {
namespaced: true,
state: () => ({
// 取出 token
token: localStorage.getItem('token') || '',
siderType: true,
lang: localStorage.getItem('lang') || 'zh'
}),
mutations: {
// state, 外界传入的 token
// token,从哪里来,从登陆获取
setToken(state, token) {
state.token = token
localStorage.setItem('token', token)
},
changeSiderType(state) {
state.siderType = !state.siderType
},
changLang(state, lang) {
state.lang = lang
}
},
actions: {
// 执行登录操作
login({commit}, userInfo) {
// resolve 正常调用该方法
// reject 异常调用
return new Promise((resolve, reject) => {
// userInfo 将用户信息传入进去
loginApi(userInfo).then(res => {
commit('setToken', res.token)
setTokenTime()
// 成功之后跳转
router.replace('/')
resolve()
}).catch(err => {
reject(err)
})
})
},
// 退出
logout ({commit}) {
commit('setToken', '')
localStorage.clear()
router.replace('/login')
}
}
}
4.在index.js 中导入 app.js
import app from './modules/app'
export default createStore({
modules: {
app
}
}
5.返回 login包下 index.vue,触发,使用 Vuex,引入 vuex
import { useStore } from 'vuex'
const store = useStore()
store.dispathch('app/login', from.value)
请求拦截器
.在 request.js 中设置 service 请求拦截器
service.interceptors.request.use(
(config) => {
config.headers.Authorization = localStorage.getItem('token')
return config
},
(error) => {
return Promise.reject(new Error(error))
}
)
路由守卫
1.在 router 目录下 新建一个 permission.js 文件
import router from './index'
import store from '@/store'
// 定义一些白名单,让一些不用登录也能进去的页面
const whiteList = ['/login']
router.beforeEach((to, from, next) => {
console.log(store.getters.token)
if (store.getters.token) {
if (to.path === '/login') {
next('/')
} else {
next()
}
} else {
// 如果有数组里有就跳转
if (whiteList.includes(to.path)) {
next()
} else {
next('/login')
}
}
})
2.为了方便拿到 token,在 store 目录下创建一个 getters.js 文件
export default {
token: (state) => state.app.token,
siderType: (state) => state.app.siderType,
lang: (state) => state.app.lang
}
3.在 store 目录下 index.js 中使用 getter.js
import { createStore } from 'vuex'
import app from './modules/app'
import getters from './getters'
export default createStore({
modules: {
app
},
getters
})
4.使用,在 main.js 中导入 import '@/router/permission'
Layout布局
1.src 目录下新建 layout 目录 - index.vue
<template>
<el-container class="app-wrapper">
<el-aside width="200px" class="sidebar-container">Aside</el-aside>
<el-container class="container">
<el-header>Header</el-header>
<el-main>Main</el-main>
</el-container>
</el-container>
</template>
<script setup></script>
<style lang="scss" scoped>
.app-container {
position: relative;
width: 100%;
height: 100%;
}
.container {
width: calc(100% - $sideBarWidth);
height: 100%;
position: fixed;
top: 0;
right: 0;
z-index: 9;
transition: all 0.28s;
&.hidderContainer {
width: calc(100% - $hideSideBarWidth);
}
}
::v-deep .el-header {
padding: 0;
}
</style>
2.router 目录下新建路由
3.导入 sass 样式,在 vue.config.js 中配置
css: {
loaderOptions: {
sass: {
// 8版本用prependData:
additionalData: `
@import "@/styles/variables.scss"; // scss文件地址
@import "@/styles/mixin.scss"; // scss文件地址
`
}
}
}
menus菜单
- layout目录下新建一个Menu - index.vue,官网找到 menu 菜单,复制
<template>
<el-menu
active-text-color="#ffd04b"
background-color="#545c64"
class="el-menu-vertical-demo"
default-active="2"
text-color="#fff"
@open="handleOpen"
@close="handleClose"
>
<el-sub-menu index="1">
<template #title>
<el-icon><location /></el-icon>
<span>Navigator One</span>
</template>
<el-menu-item-group title="Group One">
<el-menu-item index="1-1">item one</el-menu-item>
<el-menu-item index="1-2">item one</el-menu-item>
</el-menu-item-group>
<el-menu-item-group title="Group Two">
<el-menu-item index="1-3">item three</el-menu-item>
</el-menu-item-group>
<el-sub-menu index="1-4">
<template #title>item four</template>
<el-menu-item index="1-4-1">item one</el-menu-item>
</el-sub-menu>
</el-sub-menu>
<el-menu-item index="2">
<el-icon><icon-menu /></el-icon>
<span>Navigator Two</span>
</el-menu-item>
<el-menu-item index="3" disabled>
<el-icon><document /></el-icon>
<span>Navigator Three</span>
</el-menu-item>
<el-menu-item index="4">
<el-icon><setting /></el-icon>
<span>Navigator Four</span>
</el-menu-item>
</el-menu>
</template>
<script setup></script>
<style lang="scss" scoped></style>
- layout - index.vue,导入 Menu使用
<template>
<el-container class="app-wrapper">
<el-aside width="200px" class="sidebar-container">
<Menu />
</el-aside>
<el-container class="container">
<el-header>Header</el-header>
<el-main>Main</el-main>
</el-container>
</el-container>
</template>
<script setup>
import Menu from './Menu'
</script>
- 发送请求拿到数据,src - api - 新建 menu.js
import request from './request'
export const menuList = () => {
return request({
url: '/menu'
})
}
- 回到 layout - Menu - index.vue中,导入
<template>
<el-menu
active-text-color="#ffd04b"
background-color="#545c64"
class="el-menu-vertical-demo"
default-active="2"
text-color="#fff"
router
>
<el-sub-menu :index="item.id" v-for="item in menusList" :key="item.id">
<template #title>
<el-icon><location /></el-icon>
<span>{{ item.authName }}</span>
</template>
<el-menu-item
:index="'/' + it.path"
v-for="it in item.children"
:key="it.id"
>
<template #title>
<el-icon>
<component :is="icon"></component>
</el-icon>
<span>{{ it.authName }}</span>
</template>
</el-menu-item>
</el-sub-menu>
</el-menu>
</template>
<script setup>
import { menuList } from '@/api/menu'
import { ref } from 'vue'
const menusList = ref([])
const initMenusList = async () => {
menusList.value = await menuList()
}
initMenusList()
</script>
<style lang="scss" scoped></style>
- 将 所有路由页面粘贴到 views 目录下,router - index.js 路由配置,在 layout - index.vue中的 el-main 标签中写入
<router-view />
,菜单点开了很多项,我们只展开一项,在el-menu 标签加入 unique-opened - 因为 router - index.js 中 加入了 redirect,在 Menu-index.vue 中设置默认选中的内容
const defaultActive = ref(sessionStoreage.getItem('path') || '/users')
const savePath = (path) =>{
sessionStoreage.setItem('path', `/${path}`)
}
<el-menu
:default-active="defaultActive"
>
<el-sub-menu>
<el-menu-item
@click="savePath(it.path)"
>
</el-menu-item>
</el-sub-menu>
</el-menu>
- 使用小图标,安装
npm install @element-plus/icons-vue
,main.js 配置import * as ElIcons from '@element-plus/icons-vue'
,把所有的图片作为全局图片循环, Menu - index.vue 中定义小图标,删除原来的 ,,加入新的<componenet :is="iconList[index]"></component>
,复制,二级菜单显示
for(const iconName in ElIcons) {
app.componnent(iconName, ElIcons[iconName])
}
// 一级图标
const iconList = ref(['user', 'setting', 'shop', 'tickets', 'pie-chart'])
// 二级图标
const icon = ref('menu')
<template #title>
<el-icon>
<component :is="iconList[index]"></component>
</el-icon>
<span>{{ item.authName }}</span>
</template>
<template #title>
<el-icon>
<component :is="icon"></component>
</el-icon>
<span>{{ it.authName }}</span>
</template>
</el-menu-item>
被动退出
1.src 目录下创建一个 tuils文件夹下创建一个 auth.js,再定义一个常量值的文件 constant.js
auth.js
import { TOKEN_TIME, TOKEN_TIME_VALUE } from './constant'
// 登录时设置时间
export const setTokenTime = () => {
localStorage.setItem(TOKEN_TIME, Date.now())
})
// 获取
export const getTokenTime = () => {
return localStorage.getItem(TOKEN_TIME)
}
// 是否已经过期
export const diffTokenTime = () => {
const currentTIme = Date.now()
const tokenTime = getTokenTime()
return currentTIme - tokenTime > TOKEN_TIME_VALUE
}
constant.js
export const TOKEN_TIME = 'tokenTime'
export const TOKEN_TIME_VALUE = 2 * 60 * 60 * 1000
2.在发起请求时比较 , request.js,在 vuex 中做退出的操作,在 sotre - modules - app.js 中定义 logout 方法,设置 一下登录时间
request.js
import { diffTokenTime } from '@/utils/auth'
import store from "@/store"
service.interceptors.request.use(
(config) => {
if (localStorage.getItem('token')) {
if (diffTokenTime()) {
store.dispatch('app/logout')
}
}
app.js
// 执行登录操作
login({commit}, userInfo) {
// resolve 正常调用该方法
// reject 异常调用
return new Promise((resolve, reject) => {
// userInfo 将用户信息传入进去
loginApi(userInfo).then(res => {
commit('setToken', res.token)
setTokenTime()
// 成功之后跳转
router.replace('/')
resolve()
}).catch(err => {
reject(err)
})
})
},
// 退出
logout ({commit}) {
commit('setToken', '')
localStorage.clear()
router.replace('/login')
}
汉堡闹钟伸缩项
1.layout - headers - index.vue
2. 设置 layout -index.vue
<el-aside :width="asideWidth" class="sidebar-container">
<el-header><Headers /><el-header>
import Headers from './headers'
import variables from '@/styles/variables.scss'
import { ref } from 'vue'
const asideWidth = ref(variables.sidBarWidth)
- headers - component是- hamburger.vue,headers - index.vue 导入 hamburger.vue
- store - modules - app.js ,定义小图标初始值,在 headers - components - hamburger.vue 定义事件,为了方便拿到 siderType 值,在 store - getters.js 中定义
app.js
state: () => ({
// 初始值
siderType: true
)}
mutations: {
changeSiderType(state) {
state.siderType = !state.siderType
}
}
hamburger.vue
<div class="hamburger-container" @click="toggleClick">
<svg-icon :icon="icon"></svg-icon>
import { useStore } from 'vuex'
import { computed } from 'vue'
const store = useStore()
const toggleClick = () => {
store.commit('app/changeSiderType')
}
const icon = computed(() => {
return store.getters.siderType ? 'hamburger-opened' : 'hamburger-closed'
})
getters.js
export default {
siderType: (state) => state.app.siderType
}
- 实现 menus 伸缩, 在 Menu - index.vue,在 layout - index.vue 将 ref 改成 computed
Menu - index.vue
<el-menu
:collapse="!$store.getters.siderType"
></el-menu>
layout -index.vue
import { ref } from ‘vue’
<el-container
:class="{ hidderContainer: !$store.getters.siderType }"
>
import { computed } from 'vue'
import { useStore } from 'vuex'
const store = useStore()
// const asideWidth = ref(variables.sideBarWidth)
const asideWidth = computed(() => {
return store.getters.siderType ? variables.sideBarWidth : variables.hideSideBarWidth
})
动态面板屑导航
- layout - headers - components - breadcrumb.vue
<template>
<el-breadcrumb separator="/">
<el-breadcrumb-item v-for="(item, index) in breadcrumbList" :key="index">
<span class="no-redirect" v-if="index === breadcrumbList.length - 1">
{{ item.name }}
</span>
<span class="redirect" v-else @click="handleRedirect(item.path)">{{
item.name
}}</span>
</el-breadcrumb-item>
</el-breadcrumb>
</template>
<script setup>
import { watch, ref } from 'vue'
import { useRoute, useRouter } from 'vue-router'
const route = useRoute()
const router = useRouter()
const breadcrumbList = ref([])
const initBreadcrumblist = () => {
breadcrumbList.value = route.matched
}
const handleRedirect = (path) => {
router.push(path)
}
watch(
route,
() => {
initBreadcrumblist()
},
{ deep: true, immediate: true }
)
</script>
<style lang="scss" scoped>
.no-redirect {
color: #97a8be;
cursor: text;
}
.redirect {
color: #666;
font-weight: 600;
cursor: pointer;
&:hover {
color: $menuBg;
}
}
</style>
- headers - index.vue 导入 breadcrumb.vue
头像退出
- layout - headers - components - avatar.vue,headers - index.vue 导入 avatar.vue,avatar.vue 实现下拉菜单,添加退出事件
avatar.vue
<template>
<el-dropdown>
<span class="el-dropdown-link">
<el-avatar shape="square" :size="40" :src="squareUrl"></el-avatar>
</span>
<template #dropdown>
<el-dropdown-menu>
<el-dropdown-item @click="logout">退出</el-dropdown-item>
</el-dropdown-menu>
</template>
</el-dropdown>
</template>
<script setup>
import { ref } from 'vue'
import { useStore } from 'vuex'
const store = useStore()
const squareUrl = ref(
'https://img0.baidu.com/it/u=1056811702,4111096278&fm=253&fmt=auto&app=138&f=JPEG?w=500&h=500'
)
const logout = () => {
store.dispatch('app/logout')
}
</script>
<style lang="scss" scoped></style>
headers - index.vue,import Avatar from './components/avatar.vue'
<div class="navbar">
<Hamburger />
<Breadcrumb />
<div class="navbar-right">
<Avatar />
</div>
</div>
i18n 初使用
1.安装i18n,npm i vue-i18n@next
2.src - i18n - index.js
import { createI18n } from 'vue-i18n'
const messages = {
en: {
msg: {
title: 'user login'
}
},
zh: {
msg: {
title: '用户登录'
}
}
}
const getCurrentLanguage = () => {
const UAlang = navigator.language
const langCode = UAlang.indexOf('zh') !== -1 ? 'zh' : 'en'
localStorage.setItem('lang', langCode)
return 'en'
}
const i18n = createI18n({
legacy: false,
globalInjection: true,
locale: getCurrentLanguage() || 'zh',
messages: messages
})
export default i18n
3.main.js 中导入 import i18n from
import i18n from '@/i18n'
app.use(store).use(router).use(i18n).mount('#app')
4.修改 views - login - index.vue
<h3 class="title">{{ $t('msg.title') }}</h3>
5.导入资源,将 en.js 和 zh.js,修改 i18n - index.js
import EN from './en'
import ZH from './zh'
const messages = {
en: {
...EN
},
zh: {
...ZH
}
}
6.修改 login - index.vue
<div class="title-container">
<h3 class="title">{{ $t('login.title') }}</h3>
</div>
<el-button type="primary" class="login-button" @click="handleLogin">{{
$t('login.btnTitle')
}}</el-button>
7.修改 layout - headers - components - breadcrumb.vue
<el-breadcrumb-item v-for="(item, index) in breadcrumbList" :key="index">
<span class="no-redirect" v-if="index === breadcrumbList.length - 1">
{{ $t(`menus.${item.name}`) }}
</span>
<span class="redirect" v-else @click="handleRedirect(item.path)">{{
$t(`menus.${item.name}`)
}}</span>
</el-breadcrumb-item>
8.修改 Menu - index.vue
<template #title>
<el-icon>
<component :is="icon"></component>
</el-icon>
<span>{{ $t(`menus.${it.path}`) }}</span>
</template>
中英文切换
1.layout - headers - components - lang.vue
<template>
<el-dropdown @command="handleCommand">
<svg-icon icon="language"></svg-icon>
<template #dropdown>
<el-dropdown-menu>
<el-dropdown-item command="zh" :disabled="currentLanguage === 'zh'"
>中文</el-dropdown-item
>
<el-dropdown-item command="en" :disabled="currentLanguage === 'en'"
>English</el-dropdown-item
>
</el-dropdown-menu>
</template>
</el-dropdown>
</template>
<script setup>
import { useI18n } from 'vue-i18n'
import { computed } from 'vue'
import { useStore } from 'vuex'
const i18n = useI18n()
const store = useStore()
const currentLanguage = computed(() => {
return i18n.locale.value
})
const handleCommand = (val) => {
i18n.locale.value = val
store.commit('app/changLang', val)
localStorage.setItem('lang', val)
}
</script>
<style lang="scss" scoped></style>
2.headers - index.vue 引入 lang.vue
3.store - modules - app.js
state: () => ({
lang: localStoreage.getItem('lang') || 'zh'
}),
mutations: {
changLang (state, lang) {
state.lang = lang
}
}
全屏
1.安装 npm install screenfull@5.1.0
2.layout - headers - components - screenFull.vue, headers - index.vue 导入 使用
screenFull.vue
<template>
<div @click="handleFullScreen">
<svg-icon :icon="icon ? 'exit-fullscreen' : 'fullscreen'"></svg-icon>
</div>
</template>
<script setup>
import screenfull from 'screenfull'
import { ref, onMounted, onBeforeMount } from 'vue'
const icon = ref(screenfull.isFullscreen)
const handleFullScreen = () => {
if (screenfull.isEnabled) {
screenfull.toggle()
}
}
const changeIcon = () => {
icon.value = screenfull.isFullscreen
}
onMounted(() => {
screenfull.on('change', changeIcon)
})
onBeforeMount(() => {
screenfull.off('change')
})
</script>
<style lang="scss" scoped></style>
headers - index.vue
<div class="navbar-right">
<screen-full class="navbar-item" />
<Lang class="navbar-item" />
<Avatar class="navbar-item" />
</div>
import ScreenFull from './components/screenFull.vue'
引导页
1.安装 npm install driver.js
2.layout - headers - components - driver - index.vue,headers - index.vue 引入
driver - index.vue
headers - index.vue
import Driver from './components/driver'
<div class="navbar-right">
<Driver class="navbar-item" />
<screen-full class="navbar-item" />
<Lang class="navbar-item" />
<Avatar class="navbar-item" />
</div>
3.driver - steps.js
4.监听语言的一个改变, i18n - watchlang.js
,store - getters.js定义 lang,方便我们拿到语言
export default {
lang: state => state.app.lang
}
表格静态页
1.views - users - index.vue
<template>
<el-card>
<el-row :gutter="20" class="header">
<el-col :span="7">
<el-input
:placeholder="$t('table.placeholder')"
clearable
v-model="queryForm.query"
></el-input>
</el-col>
<el-button type="primary" :icon="Search">{{
$t('table.search')
}}</el-button>
<el-button type="primary">{{ $t('table.adduser') }}</el-button>
</el-row>
<el-table :data="tableData" stripe style="width: 100%">
<el-table-column
:width="item.width"
:prop="item.prop"
:label="$t(`table.${item.label}`)"
v-for="(item, index) in options"
:key="index"
>
<template v-slot="{ row }" v-if="item.prop === 'mg_state'">
<el-switch v-model="row.mg_state" />
</template>
<template #default v-else-if="item.prop === 'action'">
<el-button type="primary" size="small">Primary</el-button>
<el-button type="warning" size="small">Warning</el-button>
<el-button type="danger" size="small">Danger</el-button>
</template>
</el-table-column>
</el-table>
</el-card>
</template>
<script setup>
import { Search } from '@element-plus/icons-vue'
import { ref } from 'vue'
import { getUser } from '@/api/users'
import { options } from './options'
const queryForm = ref({
query: '',
pagenum: 1,
pagesize: 2
})
const tableData = ref([])
const initGetUsersList = async () => {
const res = await getUser(queryForm.value)
console.log(res)
tableData.value = res.users
}
initGetUsersList()
</script>
<style lang="scss" scoped>
.header {
padding-bottom: 16px;
box-sizing: border-box;
}
</style>
2.src - api -users.js
import request from './request'
export const getUser = (params) => {
return request({
url: '/users',
params
})
}
3.循环遍历 users - options.js
export const options = [
{
label: 'username',
prop: 'username'
},
{
label: 'email',
prop: 'email',
width: 200
},
{
label: 'mobile',
prop: 'mobile'
},
{
label: 'role_name',
prop: 'role_name'
},
{
label: 'mg_state',
prop: 'mg_state'
},
{
label: 'create_time',
prop: 'create_time'
},
{
label: 'action',
prop: 'action',
width: 300
},
]
全局属性
1.处理时间, utils - filters.js,安装第三方库:npm install dayjs --save
const dayjs = require('dayjs')
const filetrTimes = (val, format = 'YYYY-MM-DD') => {
if (!isNull(val)) {
val = parseInt(val) * 1000
return dayjs(val).format(format)
} else {
return '--'
}
}
export const isNull = (date) => {
if (!date) return true
if (JSON.stringify(date) === '{}') return true
if (JSON.stringify(date) === '[]') return true
}
// 使用 vue 全局属性
export default (app) => {
app.config.globalProperties.$filters = {
filetrTimes
}
}
2.main.js 导入
import filters from './utils/filters'
filters(app)
分页器
1.编辑 users - index.vue,加上 @click,实现搜索功能,加上 el-pagination 放在 el-table 下方,实现分页
<el-button type="primary" :icon="Search" @click="initGetUsersList">{{
$t('table.search')
}}</el-button>
<el-button type="primary">{{ $t('table.adduser') }}</el-button>
<el-pagination
v-model:currentPage="queryForm.pagenum"
v-model:page-size="pageSize4"
:page-sizes="[2, 5, 10, 15]"
:small="small"
:disabled="disabled"
:background="background"
layout="total, sizes, prev, pager, next, jumper"
:total="total"
@size-change="handleSizeChange"
@current-change="handleCurrentChange"
/>
const total = ref(0)
const tableData = ref([])
const initGetUsersList = async () => {
const res = await getUser(queryForm.value)
total.value = res.total
tableData.value = res.users
}
initGetUsersList()
const handleSizeChange = (pageSize) => {
queryForm.value.pagenum = 1
queryForm.value.pagesize = pageSize
initGetUsersList()
}
const handleCurrentChange = (pagenum) => {
queryForm.value.pagenum = pagenum
initGetUsersList()
}
添加用户
1.src - api - user.js
export const changeUserState = (uid, type) => {
return request({
url: `users/${uid}/state/${type}`,
method: 'put'
})
}
2.users - index.vue,导入 changeUserState
import { getUser, changeUserState } from '@/api/users'
<template v-slot="{ row }" v-if="item.prop === 'mg_state'">
<el-switch v-model="row.mg_state" @change="changeState(row)" />
</template>
3.不是自己的,使用 i18n
import { useI18n } from 'vue-i18n'
const i18n = useI18n()
const changeState = async (info) => {
await changeUserState(info.id, info.mg_state)
ElMessage({
message: i18n.t('message.updeteSuccess'),
type: info.mg_state ? 'success' : 'error'
})
}
4.users - components - dialog.vue,users - index.vue 导入 dialog.vue,dialog 父组件加上 v-mode, 子组件改为 :mode-value,给添加按钮加上点击事件,定义事件,定义数据
5.点击编辑时, users - index.vue,定义一个值
<Dialog v-model="dialogVisible" :dialogTitle="dialogTitle" />
const dialogTitle = ref('')
const handieDialogValue = () => {
dialogTitle.value = '添加用户'
dialogVisible.value = true
}
6.users - components - dialog.vue 接受
import { defineEmits, ref, defineProps } from 'vue'
defineProps({
dialogTitle: {
type: String,
default: '',
required: true
}
})
7.src - api -user.js 创建接口
export const addUser = (data) => {
return request({
url: 'users',
method: 'post',
data
})
}
export const deleteUser = (id) => {
return request({
url: `users/${id}`,
method: 'delete'
})
}
8.users - components - dialog.vue 导入
import { addUser } from '@/api/users'
const handleConfirm = async () => {
await addUser(form.value)
ElMessage({
message: i18n.global.t('message.updeteSuccess'),
type: 'success'
})
handleClose()
}
9.使用自己的 i18n
import i18n from '@/i18n'
import { ElMessage } from 'element-plus'
const handleConfirm = async () => {
await addUser(form.value)
ElMessage({
message: i18n.global.t('message.updeteSuccess'),
type: 'success'
})
handleClose()
}
10.清空上一次添加的值,users - index.vue,加上 v-if
<Dialog
v-model="dialogVisible"
:dialogTitle="dialogTitle"
v-if="dialogVisible"
/>
编辑用户
1.统一校验,users - components - dialog.vue
const handleConfirm = () => {
formRef.value.validate(async (valid) => {
if (valid) {
await addUser(form.value)
ElMessage({
message: i18n.global.t('message.updeteSuccess'),
type: 'success'
})
handleClose()
} else {
console.log('error submit!!')
return false
}
})
}
2.分发事件,users - components - dialog.vue,users.index.vue 绑定
const emits = defineEmits(['update:modelValue', 'initUserList'])
const handleConfirm = () => {
formRef.value.validate(async (valid) => {
if (valid) {
await addUser(form.value)
ElMessage({
message: i18n.global.t('message.updeteSuccess'),
type: 'success'
})
emits('initUserList')
handleClose()
} else {
console.log('error submit!!')
return false
}
})
}
3.users - index.vue,方法加一个()不带值,#default="{ row }" 解构出 row,
<el-button type="primary" @click="handieDialogValue()">{{
$t('table.adduser')
}}</el-button>
<el-button
type="primary"
size="small"
:icon="Edit"
@click="handleDialogValue(row)"
></el-button>
<Dialog
v-model="dialogVisible"
:dialogTitle="dialogTitle"
v-if="dialogVisible"
@initUserList="initGetUsersList"
:dialogTableValue="dialogTableValue"
/>
import { isNull } from '@/utils/filters'
const dialogTableValue = ref({})
const handleDialogValue = () => {
if (isNull(row)) {
dialogTitle.value = '添加用户'
dialogTableValue.value = {}
} else {
dialogTitle.value = '编辑用户'
dialogTableValue.value = JSON.parse(JSON.stringify(row))
}
dialogVisible.value = true
}
4.users - components - dialog.vue,导入 watch ,监听值的改变
import { defineEmits, ref, defineProps, watch } from 'vue'
watch(
() => props.dialogTableValue,
() => {
form.value = props.dialogTableValue
},
{ deep: true, immediate: true }
)
<el-form-item label="密码" prop="password" v-if="dialogTitle === '添加用户'">
5.添加编辑接口, src - api - users.js
export const editUser = (data) => {
return request({
url: `users/${data.id}`,
method: 'put',
data
})
}
6.users - components - dialog.vue,导入接口
import { addUser,editUser } from '@/api/users'
props.dialogTitle === '添加用户' ? await addUser(form.value) : await editUser(form.value)
删除用户
1.users - index.vue
<el-button
type="danger"
size="small"
:icon="Delete"
@click="delUser(row)"
></el-button>
import { getUser, changeUserState, deleteUser } from '@/api/users'
const delUser = (row) => {
ElMessageBox.confirm(i18n.t('dialog.deleteTitle'), 'Warning', {
confirmButtonText: 'OK',
cancelButtonText: 'Cancel',
type: 'warning'
})
.then(async () => {
await deleteUser(row.id)
ElMessage({
type: 'success',
message: 'Delete completed'
})
initGetUsersList()
})
.catch(() => {
ElMessage({
type: 'info',
message: 'Delete canceled'
})
})
}