网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。
一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!
/>
<span class="show-pwd" @click="showPwd">
<svg-icon
:icon-class="passwordType === 'password' ? 'eye' : 'eye-open'"
/>
</span>
</el-form-item>
const validateUsername = (rule, value, callback) => {
if (value.length < 5) {
callback(new Error(‘用户名最少5位’))
} else if (value.length > 12) {
callback(new Error(‘用户名最长12位’))
} else {
callback()
}
}
const validatePassword = (rule, value, callback) => {
if (value.length < 5) {
callback(new Error(‘用户名最少5位’))
} else if (value.length > 16) {
callback(new Error(‘用户名最长16位’))
} else {
callback()
}
}
loginRules: {
username: [{ required: true, trigger: ‘blur’, validator: validateUsername }],
password: [
{ required: true, trigger: ‘blur’, validator: validatePassword },
{ min: 5, max: 12, trigger: ‘blur’, message: ‘密码长度应该在5-12位之间’ }
]
}
#### Vue-Cli配置跨域代理
出现跨域的原因是什么呢?
因为当下流行的是前后端分离单独开发,前端项目和后端接口不在同域名之下,那前端访问后端接口就出现跨域了
那么问题就来了 如何解决呢?
我们所遇到的这种跨域是位于开发环境的,真正部署上线时的跨域是生产环境的,解决方式又不同
我们先解决开发环境,生产环境在打包上线事可以解决,后面再讲
##### 解决开发环境的跨域问题
开发环境的跨域,也就是在**vue-cli脚手架环境下开发启动服务时,我们访问接口所遇到的跨域问题,vue-cli为我们在本地开启了一个服务,可以通过这个服务帮我们代理请求**,解决跨域问题
也就是vue-cli配置webpack的反向代理
在`vue.config.js`中进行反向代理配置
module.exports = {
devServer: {
proxy: {
‘api/private/v1/’: {
target: ‘http://127.0.0.1:8888’, // 我们要代理的地址,当匹配到上面的’api/private/v1/'时,会将http://localhost:9528 替换成 http://127.0.0.1:8888
changeOrigin: true, // 是否跨越 需要设置此值为 true 才可以让本地服务代理我们发送请求
pathRewrite: {
// 重新路由 localhost:8888/api/login => http://127.0.0.1:8888/api/login
‘^/api’: ‘/api’,
‘/hr’: ‘’
}
}
}
}
}
同时,还需要注意的是,我们同时需要注释掉 mock的加载,因为mock-server会导致代理服务的异常
// before: require(‘./mock/mock-server.js’), // 注释mock-server加载
#### 封装单独的登录接口
export function login(data) {
// 返回一个axios对象 => promise // 返回了一个promise对象
return request({
url: ‘login’, // 因为所有的接口都要跨域 表示所有的接口要带 /api
method: ‘post’,
data
})
}
#### 封装Vuex的登录Action并处理token
##### 在Vuex中对token进行管理
![在这里插入图片描述](https://img-blog.csdnimg.cn/20210526093131489.png)
上图中,组件直接和接口打交道,这并没有什么问题,但是ta用的**钥匙**来进行相互传递,我们需要让vuex来介入,将用户的token状态共享,更方便的读取
![在这里插入图片描述](https://img-blog.csdnimg.cn/2021052609330162.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L2JsdWVfNjk4,size_16,color_FFFFFF,t_70)
`store/modules/user.js配置`
// 状态
const state = {}
// 修改状态
const mutations = {}
// 执行异步
const actions = {}
export default {
namespaced: true,
state,
mutations,
actions
}
设置token共享状态
const state = {
token: null
}
##### 操作 token
`utils/auth.js` 中,基础模板已经为我们提供了获取 token ,设置 token ,删除 token 的方法,可以直接使用
const TokenKey = ‘haitun_token’
export function getToken() {
// return Cookies.get(TokenKey)
return localStorage.getItem(TokenKey)
}
export function setToken(token) {
// return Cookies.set(TokenKey, token)
return localStorage.setItem(TokenKey, token)
}
export function removeToken() {
// return Cookies.remove(TokenKey)
return localStorage.removeItem(TokenKey)
}
##### 初始化token状态
`store/modules/user.js`
import { getToken, setToken, removeToken } from ‘@/utils/auth’
const state = {
token: getToken() // 设置token初始状态 token持久化 => 放到缓存中
}
##### 提供修改token的mutations
// 修改状态
const mutations = {
// 设置token
setToken(state, token) {
state.token = token // 设置token 只是修改state的数据 123 =》 1234
setToken(token) // vuex和 缓存数据的同步
},
// 删除缓存
removeToken(state) {
state.token = null // 删除vuex的token
removeToken() // 先清除 vuex 再清除缓存 vuex和 缓存数据的同步
}
}
##### 封装登录的Action
登录action要做的事情,调用登录接口,成功后设置token到vuex,失败则返回失败
// 执行异步
const actions = {
// 定义login action 也需要参数 调用action时 传递过来的参数
async login(context, data) {
const result = await login(data) // 实际上是一个promise result是执行的结果
// axios默认给数据加了一层data
if (result.data.success) {
// 表示登录接口调用成功 也就是意味着你的用户名和密码是正确的
// 现在有用户token
// actions 修改state 必须通过mutations
context.commit(‘setToken’, result.data.data)
}
}
}
为了更好的让其他模块和组件更好的获取token数据,我们要在`store/getters.js`中将token值作为公共的访问属性放出
const getters = {
sidebar: state => state.app.sidebar,
device: state => state.app.device,
token: state => state.user.token // 在根级的getters上 开发子模块的属性给别人看 给别人用
}
export default getters
通过此内容,我们可以有个脑图画面了
![在这里插入图片描述](https://img-blog.csdnimg.cn/20210526094337720.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L2JsdWVfNjk4,size_16,color_FFFFFF,t_70)
##### 区分axios在不同环境中的请求基础地址
![在这里插入图片描述](https://img-blog.csdnimg.cn/20210526094459623.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L2JsdWVfNjk4,size_16,color_FFFFFF,t_70)
前端两个主要区分环境,开发环境,生产环境
环境变量 `$ process.env.NODE_ENV # 当为production时为生产环境 为development时为开发环境`
我们可以在\*\*.env.development和.env.production\*\*定义变量,变量自动就为当前环境的值
基础模板在以上文件定义了变量VUE\_APP\_BASE\_API,该变量可以作为axios请求的baseURL
开发环境的基础地址和代理对应
VUE_APP_BASE_API = ‘/api’
这里配置了/api,意味着需要在Nginx服务器上为该服务配置 nginx的反向代理对应/prod-api的地址
VUE_APP_BASE_API = ‘/prod-api’
也可以都写成一样的 方便管理
在request中设置baseUrl–基准
// 创建一个axios的实例
const service = axios.create({
baseURL: process.env.VUE_APP_BASE_API, // 设置axios请求的基础的基础地址
timeout: 5000 // 定义5秒超时
})
##### 处理axios的响应拦截器
![在这里插入图片描述](https://img-blog.csdnimg.cn/2021052609500735.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L2JsdWVfNjk4,size_16,color_FFFFFF,t_70)
// 响应拦截器
service.interceptors.response.use(response => {
// axios默认加了一层data
const { success, message, data } = response.data
// 要根据success的成功与否决定下面的操作
if (success) {
return data
} else {
// 业务已经错误了 还能进then ? 不能 ! 应该进catch
Message.error(message) // 提示错误消息
return Promise.reject(new Error(message))
}
}, error => {
Message.error(error.message) // 提示错误信息
return Promise.reject(error) // 返回执行错误 让当前的执行链跳出成功 直接进入 catch
})
#### 登录页面调用登录action,处理异常
![在这里插入图片描述](https://img-blog.csdnimg.cn/20210526095342356.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L2JsdWVfNjk4,size_16,color_FFFFFF,t_70)
引入辅助函数
import { mapActions } from ‘vuex’ // 引入vuex的辅助函数
methods: {
…mapActions([‘user/login’])
}
调用登录
this.KaTeX parse error: Expected '}', got 'EOF' at end of input: … this.router.push(‘/’)
} catch (error) {
console.log(error)
} finally {
// 不论执行try 还是catch 都去关闭转圈
this.loading = false
}
}
})
#### 解析
首先使用到了elementUI的from表单进行编写
中间在前台使用表单验证进行对用户输入的账户密码进行对比,是否符合标准,如果不符合我们定义的标准进行一个提示
我们对表单里面的输入框进行双向数据绑定使用`v-model`
用户输入完毕之后点击登录按钮时也要进行后台验证,当我们点击登录发送请求到后台入库查询账户密码是否正确,如不正确会弹出提示
在表单里面使用了`<svg>`标签引入 icon 图标
我们首先在`src\components`下创建了SvgIcon组件
![](https://img-blog.csdnimg.cn/img_convert/ef10f53728e56584dd523fe7e8a38646.png)
我们向外暴露了两个属性
![](https://img-blog.csdnimg.cn/img_convert/4ac990cf54891b57cf5059318c7695dd.png)
通过 computed 监控 icon 的名字和其自定义的样式,当没有指定自定义样式时候,会采用默认样式,否则会再加上自定义 class
iconName() {
return #icon-${this.iconClass}
},
svgClass() {
if (this.className) {
return 'svg-icon ’ + this.className
} else {
return ‘svg-icon’
}
}
然后进行默认样式的编写
![在这里插入图片描述](https://img-blog.csdnimg.cn/2021052608131774.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L2JsdWVfNjk4,size_16,color_FFFFFF,t_70)
在 `src\icons` 中的 `index.js` 中引入 svg 组件 `import IconSvg from '@/components/IconSvg'`
使用全局注册 icon-svg `Vue.component('icon-svg', IconSvg)`
这样就可以在项目中任意地方使用
为了便于集中管理图标,所有图标均放在 `@/icons/svg`
`@`代表找到src目录
![在这里插入图片描述](https://img-blog.csdnimg.cn/20210526081328410.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L2JsdWVfNjk4,size_16,color_FFFFFF,t_70)
require.context 有三个参数:
* 参数一:说明需要检索的目录
* 参数二:是否检索子目录
* 参数三: 匹配文件的正则表达式
在`@/main.js`中引入`import '@/icons'`这样在任意页面就可以成功使用组件了
![在这里插入图片描述](https://img-blog.csdnimg.cn/20210526081338811.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L2JsdWVfNjk4,size_16,color_FFFFFF,t_70)
在页面中使用就可以进行使用了
#### 完整代码
海豚电商后台管理平台
<el-form-item prop="username">
<span class="svg-container">
<svg-icon icon-class="user" />
</span>
<el-input
ref="username"
v-model="loginForm.username"
v-focus
placeholder="Username"
name="username"
type="text"
tabindex="1"
auto-complete="on"
/>
</el-form-item>
<el-form-item prop="password">
<span class="svg-container">
<svg-icon icon-class="password" />
</span>
<el-input
:key="passwordType"
ref="password"
v-model="loginForm.password"
:type="passwordType"
placeholder="Password"
name="password"
tabindex="2"
auto-complete="on"
@keyup.enter.native="handleLogin"
/>
<span class="show-pwd" @click="showPwd">
<svg-icon
:icon-class="passwordType === 'password' ? 'eye' : 'eye-open'"
/>
</span>
</el-form-item>
<el-button
:loading="loading"
type="primary"
style="width: 100%; margin-bottom: 30px"
@click.native.prevent="handleLogin"
>立即登录</el-button
>
<!-- <div class="tips">
<span style="margin-right: 20px">username: admin</span>
<span> password: any</span>
</div> -->
</el-form>
### 主页模块
![在这里插入图片描述](https://img-blog.csdnimg.cn/20210526095734458.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L2JsdWVfNjk4,size_16,color_FFFFFF,t_70)
#### 主页token拦截并进行处理
##### 权限拦截的流程图
我们已经完成了登录的过程,并且存储了token,但是此时主页并没有因为token的有无而被控制访问权限
![在这里插入图片描述](https://img-blog.csdnimg.cn/20210526095814710.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L2JsdWVfNjk4,size_16,color_FFFFFF,t_70)
#### 拦截处理代码
`src/permission.js`
import Vue from ‘vue’
import ‘normalize.css/normalize.css’ // A modern alternative to CSS resets
import ElementUI from ‘element-ui’
import ‘element-ui/lib/theme-chalk/index.css’
import locale from ‘element-ui/lib/locale/lang/zh-CN’ // lang i18n
import ‘@/styles/index.scss’ // global css
import App from ‘./App’
import store from ‘./store’
import router from ‘./router’
import i18n from ‘@/lang/index’
import ‘@/icons’ // icon
import ‘@/permission’ // permission control
import directives from ‘./directives’
import Commponent from ‘@/components’
import filters from ‘./filter’
import Print from ‘vue-print-nb’ // 引入打印
// set ElementUI lang to EN
Vue.use(ElementUI, { locale })
// 如果想要中文版 element-ui,按如下方式声明
// Vue.use(ElementUI)
Vue.use(Print)
Vue.config.productionTip = false
// 遍历注册自定义指令
for (const key in directives) {
Vue.directive(key, directives[key])
}
Vue.use(Commponent) // 注册自己的插件
// 注册全局的过滤器
// 遍历注册过滤器
for (const key in filters) {
Vue.filter(key, filters[key])
}
// 设置element为当前的语言
Vue.use(ElementUI, {
i18n: (key, value) => i18n.t(key)
})
new Vue({
el: ‘#app’,
router,
store,
i18n,
render: h => h(App)
})
#### 左侧导航
样式文件`styles/siderbar.scss`
设置背景图片
.scrollbar-wrapper {
background: url(‘~@/assets/common/leftnavBg.png’) no-repeat 0 100%;
}
左侧logo图片`src/setttings.js`
module.exports = {
title: ‘海豚电商后台管理平台’,
/**
- @type {boolean} true | false
- @description Whether fix the header
*/
fixedHeader: false,
/**
- @type {boolean} true | false
- @description Whether show the logo in sidebar
*/
sidebarLogo: true // 显示logo
}
设置头部图片结构 `src/layout/components/Sidebar/Logo.vue`
完整代码
#### 头部内容的布局和样式
头部组件位置`layout/components/Navbar.vue`
![在这里插入图片描述](https://img-blog.csdnimg.cn/20210526100908405.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L2JsdWVfNjk4,size_16,color_FFFFFF,t_70)
添加公司名称时要面包屑
<div class="app-breadcrumb">
北京梦呓网络有限公司
<span class="breadBtn">v1.0.0</span>
</div>
右侧头像和下拉菜单等设置
完整代码:样式+事件
#### 储存用户信息
新增变量:`src/store/modules/user.js`
const getDefaultState = () => {
return {
token: getToken(),
userInfo: {}, // 储存用户信息
}
}
设置和删除用户资料 mutations
// 设置用户信息
set_userInfo (state, user) {
state.userInfo = user
setUSERINFO(user)
}
// 删除用户信息
removeUserInfo (state) {
this.userInfo = {}
}
建立用户名的映射 `src/store/getters.js`
const getters = {
token: state => state.user.token,
username: state => state.user.userInfo.username
}
export default getters
最后我们换成真实名称即可
![](@/assets/common/bigUserHeader.png)
这里可能会出现问题,在页面刷新拿不到数据,我们可以将其保存到本地中,然后取出
##### 实现退出功能
![在这里插入图片描述](https://img-blog.csdnimg.cn/20210526102329717.png)
退出:`src/store/modules/user.js`
// user logout
logout (context) {
// 删除token
context.commit(‘removeToken’) // 不仅仅删除了vuex中的 还删除了缓存中的
// 删除用户资料
context.commit(‘removeUserInfo’) // 删除用户信息
},
mutation
removeToken (state) {
state.token = null
removeToken()
removeUSERINFO()
removeLocalMenus()
},
removeUserInfo (state) {
this.userInfo = {}
},
头部菜单调用 `src/layout/components/Navbar.vue`
async logout () {
await this.
s
t
o
r
e
.
d
i
s
p
a
t
c
h
(
′
u
s
e
r
/
l
o
g
o
u
t
′
)
t
h
i
s
.
store.dispatch('user/logout') this.
store.dispatch(′user/logout′)this.router.push(/login
)
}
完整代码:
#### token失效介入
![在这里插入图片描述](https://img-blog.csdnimg.cn/20210526102932693.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L2JsdWVfNjk4,size_16,color_FFFFFF,t_70)
`src/utils/auth.js`
const timeKey = ‘haitun-setTimeStamp’ // 设置一个独一无二的key
// 存储 token 的时间戳(存的是 setToken 方法执行的时间)
// 获取时间戳
export function setTimeStamp () {
return localStorage.setItem(timeKey, Date.now())
}
// 获取 token 的过期时间
export function getTimeStamp () {
return localStorage.getItem(timeKey)
}
`src/utils/request.js`
import axios from ‘axios’
import { Message } from ‘element-ui’
import store from ‘@/store’
import router from ‘…/router’
import { getToken, getTimeStamp, removeToken } from ‘@/utils/auth’
// 定义 token 超时时间
const timeOut = 3600 * 24 * 3
// create an axios instance
const service = axios.create({
baseURL: process.env.VUE_APP_BASE_API, // url = base url + request url
timeout: 5000 // request timeout
})
// request interceptor
service.interceptors.request.use(
// 注入token
config => {
// do something before request is sent
if (store.getters.token) {
// 判断当前 token 的时间戳是否过期
// 获取 token 设置的时间
const tokenTime = getTimeStamp()
// 获取当前时间
const currenTime = Date.now()
if ((currenTime - tokenTime) / 1000 > timeOut) {
// 如果它为true表示 过期了
// token没用了 因为超时了
store.dispatch('user/logout') // 登出操作
// 跳转到登录页
router.push('/login')
return Promise.reject(new Error('登录过期了,请重新登录'))
}
config.headers['Authorization'] = getToken()
}
return config
},
error => {
// do something with request error
console.log(error) // for debug
return Promise.reject(error)
}
)
// response interceptor
service.interceptors.response.use(
response => {
const { meta: { status, msg }, data } = response.data
// if the custom code is not 20000, it is judged as an error.
if (status !== 200 && status !== 201) {
// 处理 token 过期问题
if (status === 400 && msg === ‘无效的token’) {
removeToken()
store.dispatch(‘user/logout’)
router.push(‘login’)
}
Message({
message: msg || ‘Error’,
type: ‘error’,
duration: 5 * 1000
})
return Promise.reject(new Error(msg || ‘Error’))
} else {
return data
}
},
error => {
console.log(‘err’ + error) // for debug
Message({
message: error.message,
type: ‘error’,
duration: 5 * 1000
})
return Promise.reject(error)
}
)
export default service
在登录的时候,如果登录成功,我们就应该设置时间戳
`src/store/modules`
async login (context, userInfo) {
const { username, password } = userInfo
const res = await login({ username: username.trim(), password: password })
// 设置用户信息
const token = res.token
context.commit(‘set_token’, token)
context.commit(‘set_userInfo’, res)
// 设置用户权限信息
const permission = await getMenus()
const menus = filterPermission(permission)
context.commit(‘set_menus’, menus)
},
token失效处理
![在这里插入图片描述](https://img-blog.csdnimg.cn/20210526103451414.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L2JsdWVfNjk4,size_16,color_FFFFFF,t_70)
`src/utils/request.js`
response => {
const { meta: { status, msg }, data } = response.data
// if the custom code is not 20000, it is judged as an error.
if (status !== 200 && status !== 201) {
// 处理 token 过期问题
if (status === 400 && msg === ‘无效的token’) {
removeToken()
store.dispatch(‘user/logout’)
router.push(‘login’)
}
Message({
message: msg || ‘Error’,
type: ‘error’,
duration: 5 * 1000
})
return Promise.reject(new Error(msg || ‘Error’))
} else {
return data
}
路由、页面、用户管理、权限管理等等需要什么页面自己开发即可,步骤相似的
### 多语言切换、tab页全屏
#### 全屏插件的引用
安装全局插件screenfull
npm i screenfull
封装全屏插件`src/components/ScreenFull/index.vue`
全局注册该组件 `src/components/index.js`
import ScreenFull from ‘./ScreenFull’
Vue.component(‘ScreenFull’, ScreenFull) // 注册全屏组件
放置`layout/navbar.vue`
.right-menu-item {
vertical-align: middle;
}
#### 设置动态主题
封装全屏插件 `src/components/ThemePicker/index.vue`
![img](https://img-blog.csdnimg.cn/img_convert/cc47208a91baa75e98a25d50d720cad5.png)
![img](https://img-blog.csdnimg.cn/img_convert/22e7936ffd88d65afc983f69b27431df.png)
**网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。**
**[需要这份系统化的资料的朋友,可以添加戳这里获取](https://bbs.csdn.net/topics/618668825)**
**一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!**
Message({
message: msg || 'Error',
type: 'error',
duration: 5 * 1000
})
return Promise.reject(new Error(msg || 'Error'))
} else {
return data
}
路由、页面、用户管理、权限管理等等需要什么页面自己开发即可,步骤相似的
多语言切换、tab页全屏
全屏插件的引用
安装全局插件screenfull
npm i screenfull
封装全屏插件src/components/ScreenFull/index.vue
<template>
<!-- 放置一个图标 -->
<div>
<!-- 放置一个svg的图标 -->
<svg-icon
icon-class="fullscreen"
style="color: #fff; width: 20px; height: 20px"
@click="changeScreen"
/>
<!-- <i class="el-icon-rank" @click="changeScreen" /> -->
</div>
</template>
<script>
import ScreenFull from 'screenfull'
export default {
methods: {
// 改变全屏
changeScreen () {
if (!ScreenFull.isEnabled) {
// 此时全屏不可用
this.$message.warning('此时全屏组件不可用')
return
}
// document.documentElement.requestFullscreen() 原生js调用
// 如果可用 就可以全屏
ScreenFull.toggle()
}
}
}
</script>
<style>
</style>
全局注册该组件 src/components/index.js
import ScreenFull from './ScreenFull'
Vue.component('ScreenFull', ScreenFull) // 注册全屏组件
放置layout/navbar.vue
<screen-full class="right-menu-item" />
-------------------------------
.right-menu-item {
vertical-align: middle;
}
设置动态主题
封装全屏插件 src/components/ThemePicker/index.vue
[外链图片转存中…(img-9wtZJ9SP-1715824328049)]
[外链图片转存中…(img-FubGjMYR-1715824328049)]
网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。
一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!