vue
vue
vue项目打包一键去掉所有console.log
在Vue项目中,可以通过使用babel插件来去除所有的console.log语句。以下是一种常用的方法:
- 安装babel插件:babel-plugin-transform-remove-console
npm install babel-plugin-transform-remove-console --save-dev
- 在项目的根目录下创建一个babel.config.js文件,并添加以下内容:
module.exports = {
presets: [
'@vue/cli-plugin-babel/preset'
],
plugins: [
process.env.NODE_ENV === 'production' ? 'transform-remove-console' : ''
]
}
- 运行打包命令,例如:
npm run build
这样,在生产环境下,所有的console.log语句都会被自动去除。
请注意,这只会在打包时去除console.log语句,而不会影响开发环境下的调试信息。
你可以参考以下链接获取更多关于此话题的信息:
vue中修改props传进来的值
vue中修改props传进来的值_使用计算属性更新prop的值-CSDN博客
在子组件修改props的方法:
- 子组件data中拷贝一份,注意引用类型需要深拷贝,根据需求可以watch监听
data() {
return {
newList: this.list.slice()
}
},
watch: {
list(newVal) {
this.newList = newVal
}
}
- 通过计算属性修改
computed: {
nList() {
return this.list.filter(item => {
return item.isChecked
})
}
}
- sync修饰符
父组件 穿进去的时候加上 .sync
子组件 通过this.$emit('update:xxx', params)
// 父组件
<todo-list :list.sync="list" />
// 子组件
methodName(index) {
this.$emit('update:list', this.newList)
}
Vue内置组件的component标签
@click的事件修饰符
修饰符 | 说明 |
---|---|
.stop | 阻止冒泡 调用 event.stopPropagation() |
.prevent | 阻止默认事件 调用 event.preventDefault() |
.capture | 添加事件侦听器时使用事件捕获模式 |
.once | 事件只触发一次 |
.self | 只有点击当前元素本身时才会触发回调 |
element ui按需导入
cnpm install element-ui --save-dev
main.js
import {
Button,
Popover,
Cascader,
CascaderPanel
} from 'element-ui'
Vue.use(Button)
Vue.use(Popover)
Vue.use(Cascader)
Vue.use(CascaderPanel)
在使用组件的页面导入样式
import 'element-ui/lib/theme-chalk/index.css'
组件二次封装
Vue 基础组件二次封装的高级技巧及方法,能更优雅的进行二次封装组件(Vue3组件封装--继承第三方组件的Methods方法)_vue moment时间处理二次封装-CSDN博客
vue实现登录后跳转到之前的页面
(202条消息) vue实现登录后跳转到之前的页面_longzhoufeng的博客-CSDN博客
main.js
router.beforeEach((to, from, next) => {
if (to.path == '/login' && from.path!='/register') {
//保存当前路由
localStorage.setItem("preRoute", router.currentRoute.fullPath)
}
next()
})
登录界面 login.vue
this.$store.dispatch("Login", this.loginForm).then(response => {
if (response.code == 200) {
const curr = localStorage.getItem('preRoute')
if (curr == null) {
this.$router.push({ path: "/user_center" });
} else {
this.$router.push({ path: curr });
}
this.$message({ message: response.msg, type: "success", "duration": 1000 });
} else {
this.$message.error(response.msg);
}
}).catch((response) => {
this.$message.error('请联系管理员!!!');
});
vue2和vue3响应式(简易版)
vue2通过遍历循环对象属性(for key in obj),在对每一个属性的值用 Object.defineProperty 进行getter和setter的改造
vue3通过Proxy(代理)和Reflect(反射)的使用完成 响应式。
function observe(data){
if(!data || typeof data !== 'object') return
for(let key in data){
let value = data[key]
Object.defineProperty(data, key, {
enumerable: true,
configurable: true,
get(){
return value
},
set(newVal){
value = newVal
}
})
if(typeof value === 'object'){
observe(value)
}
}
}
function reactive(obj){
const handler = {
get(target, key, receiver){
const value = Reflect.get(...arguments)
if(typeof value === 'object'){
return reactive(value)
}else{
return value
}
},
set(target, key, val, receiver){
return Reflect.set(...arguments)
}
}
return new Proxy(obj, handler)
}
vuex的基本使用 pinia的基本使用
官网: 安装 | Pinia 中文文档 (web3doc.top)
vuex的基本使用可以查看以下文章:
https://juejin.cn/post/6928468842377117709
https://juejin.cn/post/6994337441314242590#heading-15
https://juejin.cn/post/7087100496762109983#heading-16
简明易懂:https://juejin.cn/post/7280007176776204327?searchId=2024011109491811B46B930D323378F7D4
pinia的基本使用可以查看以下文章: (pinia相比vuex简单很多)
https://juejin.cn/post/7078281612013764616#heading-0
vuex
安装:npm install vuex --save
定义 store
import Vuex from 'vuex'
Vue.use( Vuex );
const store = new Vuex.Store({
//待添加
const store = new Vuex.Store({
state:{
products: [
{name: '鼠标', price: 20},
{name: '键盘', price: 40}
]
},
getters:{ //添加getters
saleProducts: (state) => {
let saleProducts = state.products.map( product => {
return {
name: product.name,
price: product.price / 2
}
})
return saleProducts;
}
},
mutations:{ //添加mutations,payload是函数接收到的参数
minusPrice (state, payload ) {
let newPrice = state.products.forEach( product => {
product.price -= payload
})
}
},
actions:{ //添加actions,payload是函数接收到的参数
minusPriceAsync( context, payload ) {
setTimeout( () => {
context.commit( 'minusPrice', payload ); //context提交,通过调用mutations里面的方法来改变数据
}, 2000)
}
}
})
})
new Vue({
el: '#app',
store,
render: h => h(App)
})
使用
this.$store.state.products //获取store中state的数据
this.$store.getters.saleProducts //获取store中getters的数据
this.$store.commit('minusPrice', 2); // 同步方法,调用mutations中的方法,2是传递的参数
this.$store.dispatch('minusPriceAsync', 5); //分发actions中的minusPriceAsync这个异步函数,5是参数
辅助函数
import { mapState,mapGetters ,mapMutations,mapActions } from "vuex"; //引入方法
export default {
name: "App",
computed: {
...mapState(["products"]), // 注意这里是数组
...mapGetters([
'saleProducts' // 将 saleProducts 映射为组件的 saleProducts 属性
])
},
methods: {
//在methods中使用拓展运算符展开函数
...mapMutations(["minusPrice"]),
...mapActions(["minusPriceAsync"]),
// 调用函数
changeBtn2() {
this.minusPrice("1");
this.minusPriceAsync("2");
},
},
};
Modules 模块
每个模块拥有自己的 state、mutation,action、getter、
甚至是嵌套子模块——从上至下进行同样方式的分割
const moduleA = {
state: { ... },
mutations: { ... },
actions: { ... },
getters: { ... }
}
const moduleB = {
state: { ... },
mutations: { ... },
actions: { ... }
}
const store = new Vuex.Store({
modules: {
a: moduleA,
b: moduleB
}
})
store.state.a // -> moduleA 的状态
store.state.b // -> moduleB 的状态
页面刷新后Vuex 状态丢失怎么解决?
Vuex 只是在内存中保存状态,刷新后就会丢失,如果要持久化就需要保存起来。
localStorage
就很合适,提交mutation
的时候同时存入localStorage
,在store
中把值取出来作为state
的初始值即可。
也可以使用第三方插件,推荐使用vuex-persist
插件,它是为 Vuex 持久化储存而生的一个插件,不需要你手动存取storage
,而是直接将状态保存至 cookie
或者 localStorage
中。
pinia(更轻便)
官网:https://pinia.vuejs.org/zh/introduction.html
npm install pinia
import { createApp } from 'vue'
import { createPinia } from 'pinia'
import App from './App.vue'
const pinia = createPinia()
const app = createApp(App)
app.use(pinia)
app.mount('#app')
定义
import { defineStore } from 'pinia'
// useStore 可以是 useUser、useCart 之类的任何东西
// 第一个参数是应用程序中 store 的唯一 id,可以自定义
//这个 name,也称为 id,是必要的,Pinia 使用它来将 store 连接到 devtools。 将返回的函数命名为 use... 是跨可组合项的约定,以使其符合你的使用习惯。
export const useStore = defineStore('useStore', {
// other options...
// state 为 store 的核心,表示当前模块的状态
// 为了完整类型推理,推荐使用箭头函数
state: () => {
return {
num: 10
}
},
getters: {
doubleNum: (state) => state.num * 2,
},
actions: {
add(){
this.num++
},
addMore(num){
this.num+=parseInt(num)
}
}
})
使用
<script setup lang='ts'>
import {useStore} from '@/store/index.js';
const store=useStore()
import { ref, onMounted } from 'vue';
console.log(store.num)
const add=(num)=>{
store.add()
store.addMore(num) // 带参数
}
const storeReset=()=>{
//您可以通过调用 store 上的 $reset() 方法将状态 重置 到其初始值
store.$reset()
}
</script>
Pinia
配套有个插件 pinia-plugin-persist进行数据持久化,否则一刷新就会造成数据丢失
vue实现全屏滚动
页面全屏滚动很多用于官网首页,使用的频率还挺高的,此处的代码实现的功能是 鼠标滚动,页面切换,点击键盘的上下键,页面切换。类似效果看 全屏滚动_jQuery之家-自由分享jQuery、html5、css3的插件库 (htmleaf.com) ,以下内容改自 vue实现全屏滚动,非fullpage.js-阿里云开发者社区 (aliyun.com) 。
.vue文件
@mousewheel.prevent 鼠标滚动,实现页面的切换
@keyup.prevent 点击键盘实现页面滚动,此处为键盘上下键点击实现页面滚动
<div class="fullPage" ref="fullPage">
<div
class="fullPageContainer"
ref="fullPageContainer"
@mousewheel.prevent="mouseWheelHandle"
@keyup.prevent="keyHandle"
@DOMMouseScroll.prevent="mouseWheelHandle" >
<div style="width:100%;height:100vh;overflow: hidden;">
<div>
// 这个div写页面里面的内容,页面的内容占满一整个屏幕
</div>
</div>
<div style="width:100%;height:100vh;overflow: hidden;"></div>
<div style="width:100%;height:100vh;overflow: hidden;"></div>
<div style="width:100%;height:100vh;overflow: hidden;"></div>
........
// 有几个全屏滚动的页面,就写几个div
</div>
</div>
css
<style lang="less" scoped>
.fullPage{
height: 100%;//一定要设置,保证占满
overflow: hidden;//一定要设置,多余的先隐藏
}
.fullPageContainer{
width: 100%;
height: 100%;
transition: all linear 0.5s;
}
</style>
script
fullpage: {
current: 1, // 当前的页面编号
isScrolling: false, // 是否在滚动,是为了防止滚动多页,需要通过一个变量来控制是否滚动
deltaY: 0 // 返回鼠标滚轮的垂直滚动量,保存的鼠标滚动事件的deleteY,用来判断是往下还是往上滚
}
mounted(){
// 没有在mounted里加上键盘点击事件,使用键盘点击切换页面会出bug。即点击完页面时能使用键盘实现页面滚动,当点击了网页外在点击回网页内,键盘上下键切换页面会失效。
window.addEventListener('keyup', this.keyHandle, false)
},
methods:{
next () { // 往下切换
const len = 4 // 页面的个数
if (this.fullpage.current + 1 <= len) { // 如果当前页面编号+1 小于总个数,则可以执行向下滑动
this.fullpage.current += 1 // 页面+1
this.move(this.fullpage.current) // 执行切换
}
},
pre () { // 往上切换
if (this.fullpage.current - 1 > 0) { // 如果当前页面编号-1 大于0,则可以执行向下滑动
this.fullpage.current -= 1// 页面+1
this.move(this.fullpage.current)// 执行切换
}
},
move (index) {
this.fullpage.isScrolling = true // 为了防止滚动多页,需要通过一个变量来控制是否滚动
this.directToMove(index) // 执行滚动
setTimeout(() => { // 这里的动画是1s执行完,使用setTimeout延迟1s后解锁
this.fullpage.isScrolling = false
}, 500)
},
directToMove (index) {
const height = document.body.clientHeight
// const height = document.documentElement.clientHeight; 上面的不管用,可以用这个
// const height = this.$refs['fullPage'].clientHeight // 获取屏幕的宽度
const scrollPage = this.$refs['fullPageContainer'] // 获取执行tarnsform的元素
let scrollHeight = '' // 计算滚动的告诉,是往上滚还往下滚
scrollHeight = -(index - 1) * height + 'px'
scrollPage.style.transform = `translateY(${scrollHeight})`
this.fullpage.current = index
},
mouseWheelHandle (event) { // 监听鼠标监听
// 添加冒泡阻止
const evt = event || window.event
if (evt.stopPropagation) {
evt.stopPropagation()
} else {
evt.returnValue = false
}
if (this.fullpage.isScrolling) { // 判断是否可以滚动
return false
}
const e = event.originalEvent || event
this.fullpage.deltaY = e.deltaY || e.detail // Firefox使用detail
if (this.fullpage.deltaY > 0) {
this.next()
} else if (this.fullpage.deltaY < 0) {
this.pre()
}
},
keyHandle (event) {
const evt = event || window.event
if (evt.keyCode === 40) { // 下滑
this.next()
}
if (evt.keyCode === 38) {
this.pre()
}
},
}
vue项目跳转时如何加密路由上面query传递的参数
vue-router路由中对query中的参数进行加密_router 参数加密_前端_小学生的博客-CSDN博客
vue-router中传递的参数进行自动加密显示,组件中获取自动解密 (github.com)
(实际使用有bug,在新页面打开带参数的url时,加密的参数并不能够解析出来)
后端返回很大的数据量,前端如何渲染
https://mp.weixin.qq.com/s/pWsntJbJnBip5CbP7HkvFg
vite创建vue3项目
十分钟搭建 Vite+Vue3 项目模板 - 掘金 (juejin.cn)
从 0 搭建 Vite 3 + Vue 3 前端工程化项目 - 掘金 (juejin.cn)
Vue3+Element-plus前端学习笔记-巨长版 (qq.com)
普通创建vite项目
npm init vite@latest
pnpm create vite // pnpm创建vite项目 https://blog.csdn.net/a486368464/article/details/133697214
模板创建项目
npm init vite@latest myproject -- --template vue
如果使用普通创建的方式,依次选择需要的配置项即可,如果模板创建,即可直接下载依赖运行
配置路由:
npm install vue-router
src目录下创建roter文件夹,文件夹下创建 index.js
src目录下创建views文件夹,用于存放页面。此处创建about.vue和list.vue用于测试
router/index.js内容如下:
import {
createRouter,
createWebHashHistory
} from 'vue-router'
const router = createRouter({
history: createWebHashHistory(),
// createWebHashHistory 用来配置内部使用hash模式路由
routes: [{
path: '/',
redirect: '/about'
},
{
path: '/about',
name: 'about',
component: () =>
import ('../views/about.vue')
},
{
path: '/list',
name: 'list',
component: () =>
import ('../views/list.vue')
},
]
})
export default router
在main.js引入路由文件并使用
import { createApp } from 'vue'
import './style.css' // 对于原始样式不需要可以注释掉
import App from './App.vue'
import router from './router/index'
createApp(App).use(router).mount('#app')
app.vue中只留下
<template>
<router-view></router-view>
</template>
在使用路由的页面中使用函数式路由跳转
<template>
<div>
{{$route.query}} //界面中可以通过 $route.query 来获取参数
</div>
</template>
<script setup lang="ts">
import { useRoute,useRouter } from "vue-router";
const router=useRouter()
const route=useRoute()
const go=()=>{
router.push({path:'/list',query:{name:'张三'}})
}
onMounted(()=>{
console.log("route",route.query)
}
</script>
至此,路由配置完成。
注意:vue3中引入的组件可以直接使用
vite.config.js 配置 server服务 @ 路径
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
import {
resolve
} from "path"
// https://vitejs.dev/config/
export default defineConfig({
plugins: [vue()],
server: {
// 启动后打开浏览器
open: true,
// 设置主机
host: '127.0.0.1',
// 设置端口
port: 3001
},
resolve: {
// ↓路径别名,主要是这部分
alias: {
"@": resolve(__dirname, "./src")
}
}
})
下载less依赖
npm i less-loader less style-resources-loader --save-dev
下载配置 element-plus
npm install element-plus --save
main.js
import ElementPlus from 'element-plus'
import 'element-plus/dist/index.css'
// element-plus默认英文,中文配置
// import zhCn from 'element-plus/lib/locale/lang/zh-cn' 这个路径会报错
import zhCn from 'element-plus/es/locale/lang/zh-cn'
createApp(App).use(router).use(ElementPlus, {locale: zhCn}).mount('#app')
下载配置axios
npm install axios qs
src/utils 下创建 request.js 封装axios请求
(写法1)
// 根据环境变量区分接口默认地址
switch(process.env.NODE_ENV){
case "production":
axios.defaults.baseURL="http://api.fanko.cn";
break;
case "test":
axios.defaults.baseURL="http://192.168.20.12:8080";
break;
default:
axios.defaults.baseURL="http://127.0.0.1:3000";
}
// 设置超时时间和跨域是否允许携带凭证
axios.defaults.timeout=10000;
axios.defaults.withCredentials=true;
/*
* 设置请求传输数据的格式(看服务器要求什么类型的数据格式) x-www-form-urlencoded是一种数据格式
* transformRequest 只对post请求有作用,根据实际要求决定设置不设置
*/
axios.defaults.headers['Content-Type'] = 'application/x-www-form-urlencoded';
axios.defaults.transformRequest = data => {qs.stringify(data)};
// 设置请求拦截器 客户端发送请求 => [请求拦截器] => 服务器 客户端发送请求给服务器的配置项就是 config
// token校验(JWT) 接受服务器返回的token 存储到vuex/本地存储中
// 每一次向服务器发请求应该把 token 带上
axios.interceptors.request.use(
config => { //请求成功时执行
//获取本地存储中的token
let token = localStorage.getItem('token');
// 验证token存在,请求头带上 token
token && (config.headers.Authorization = token);
return config;
},
error => { //请求失败时执行
return Primise.reject(error)
}
)
// 设置响应拦截器 服务器返回信息 => [响应拦截器统一处理] => 客户端
/* axios.defaults.validateStatus=status=>{ 基本公司不用配置
* // 自定义响应成功的HTTP状态码
* return /^(2|3)\d{2}$/.test(status);
*/ }
axios.interceptors.response.use(
response=>{
return response.data;
},error => {//拦截失败
return Primise.reject(error);
let {response } = error;
if(response) {
switch (response.status) {
case 401://权限问题,提示未登录或无权限等;
break;
case 403://服务器拒绝执行 (token过期)
break;
case 404://找不到页面
break;
}
} else {
//服务器连结果都没有返回
if(!window.navigator.onLine) {
//断网处理:跳转断网页面/提示网络错误等等
return;
}
return Promise.reject(error)
}
}
)
export default axios;
(写法2)
//创建一个新 axios 实力
// 请求拦截器,如果有 token 进行头部携带
// 响应拦截器, 剥离无效token 处理 token 失效
// 导出一个函数,调用当前 axios 实例,返回值 promise
import axios from "axios";
// 导出基准地址,原因: 其他地方不是通过 axios 发送请求的地方用上基准地址
export const baseURL='http://localhost:8084'
const instance=axios.create({
// axios 的一些配置,baseurl timeout
baseURL,
timeout:5000
})
instance.interceptors.request.use(config=>{
return config
},err=>{
return Promise.reject(err)
})
// res => res.data 取出data数据,将来调用接口的时候直接拿到的就是后台的数据
instance.interceptors.response.use(res=>res.data,error=>{
return Promise.reject(error);
let {response } = error;
if(response) {
switch (response.status) {
case 401://权限问题,提示未登录或无权限等;
break;
case 403://服务器拒绝执行 (token过期)
break;
case 404://找不到页面
break;
}
} else {
//服务器连结果都没有返回
if(!window.navigator.onLine) {
//断网处理:跳转断网页面/提示网络错误等等
return;
}
return Promise.reject(error)
}
})
export default(url,method,submitData)=>{
// 负责发请求:请求地址,请求方式, 提交数据
return instance({
url,
method,
// 1. 如果是get请求 需要使用params来传递submitData ?a=10&c=10
// 2. 如果不是get请求 需要使用data来传递submitData 请求体传参
// [] 设置一个动态的key, 写js表达式,js表达式的执行结果当作KEY
// method参数:get,Get,GET 转换成小写再来判断
// 在对象,['params']:submitData ===== params:submitData 这样理解
[method.toLowerCase() === 'get' ? 'params' : 'data']: submitData
})
}
api/use.js
import request from "@/utils/request.js";
export const addUser=(data)=>{
return request('/user','post',data)
}
export const getListTest=()=>{
return request('/user/string','get')
}
配置跨域(vite.config.js)
export default defineConfig({
plugins: [vue()],
server: {
// 启动后打开浏览器
open: true,
proxy: {
'/api': { //请求路径关键字
target: 'http://localhost:8084', //对应自己的接口
changeOrigin: true,//是否允许跨域,在本地会创建一个虚拟服务端,然后发送请求的数据,
// 并同时接收请求的数据,这样服务端和服务端进行数据的交互就不会有跨域问题
ws: true,
pathRewrite: {
'^/api': '' //这里理解成用‘/api’代替target里面的地址,后面组件中我们掉接口时直接用api代替
}
}
}
},
resolve: {
// ↓路径别名,主要是这部分
alias: {
"@": resolve(__dirname, "./src")
}
}
})
除了前端配置代理之外,后台也要配置跨域,否则请求不到,后台跨域解决可以参考下面的文章
SpringBoot 项目解决跨域的几种方案 - 掘金 (juejin.cn)
(vue3中使用消息提示 vue3中使用element-plus调用message_element-plus message_ )
配置pinia
npm install pinia
main.js
import { createPinia } from 'pinia'
createApp(App).use(router).use(ElementPlus).use(createPinia()).mount('#app')
store/index.js
Store 是使用 defineStore()
定义的,并且它需要一个唯一名称,作为第一个参数传递:
//pinia 中使用 definStore 方法来定义 store
import { defineStore } from 'pinia'
// useStore 可以是 useUser、useCart 之类的任何东西
// 第一个参数是应用程序中 store 的唯一 id,可以自定义
//这个 name,也称为 id,是必要的,Pinia 使用它来将 store 连接到 devtools。 将返回的函数命名为 use... 是跨可组合项的约定,以使其符合你的使用习惯。
export const useStore = defineStore('useStore', {
// other options...
// state 为 store 的核心,表示当前模块的状态
// 为了完整类型推理,推荐使用箭头函数
state: () => {
return {
num: 100
}
},
getters: {
doubleNum: (state) => state.num * 2,
},
// actions 可以定义同步和异步的方法
actions: {
add(){
this.num++
},
addMore(num){
this.num+=num
}
}
})
.vue 文件引入使用
<template>
<div>
<div>
{{ store.num }} {{ store.doubleNum }}
</div>
<div>
<el-button @click="add" size="mini" style="background-color: aquamarine;"> +1 </el-button>
<el-button @click="addMore(3)" size="mini" style="background-color: aquamarine;"> +3 </el-button>
<el-button @click="storeReset" size="mini" style="margin-left:20px;background-color: aquamarine;"> 重置 </el-button>
</div>
<div>
</div>
</div>
</template>
<script setup lang='ts'>
import {useStore} from '@/store/index.js';
const store=useStore()
onMounted(() => {
})
const add=()=>{
store.add()
}
// 传参
const addMore= (num) =>{
store.addMore(num)
}
const storeReset=()=>{
//您可以通过调用 store 上的 $reset() 方法将状态 重置 到其初始值
store.$reset()
}
</script>
使用拓展库
vue 图片预览
v-viewer
Vue图片预览插件v-viewer - 掘金 (juejin.cn)
lodash使用
import _ from 'lodash'
// 防抖
add: _.debounce(function () {
// 请求接口
console.log('123')
}, 800),
树形结构插件 vue2-org-tree(组织结构图)
Vue 树形结构插件 vue2-org-tree - 掘金 (juejin.cn)
详解树状结构图 vue-org-tree - 知乎 (zhihu.com)
Vue3 中引入wangeditor富文本编辑器
快速开始 | wangEditor wangeditor有vue2,vue3,react的版本 用于 Vue React | wangEditor
在vue2中封装一个wangeditor组件_,也可以查看 ruoyi 里面的封装
基本使用:
npm i wangeditor -S
在需要用到的页面引入
//这里名字无所谓, 就是待会new的时候要用这个名字new
import EWangEditor from "wangeditor";
在页面内使用
<template>
<div>
<!-- id要为new wangeditor对象时候使用的id,style设高度可以不设 -->
<div id="editor" name="editor" ref="editor" style="height:500px"></div>
</div>
</template>
<script setup lang="ts">
import { ref ,reactive} from '@vue/reactivity'
import EWangEditor from "wangeditor";
import { onMounted } from '@vue/runtime-core';
let data=reactive({})
onMounted(()=>{
console.log("onMounted",a,b,c)
//这里的"#editor"对应要渲染为编辑器的html元素的id, 就像以前的querySelector()
let editor=new EWangEditor("#editor")
editor.config.uploadImgShowBase64 = true;
// 你在wangeditor内写入的字符会被wangeditor自动转为HTML, 我们设定的更新频率, 即它每隔多久将你的文字提取并转换为HTML一次.
/* 配置检测字符变化的最短时间间隔,默认为 200ms */
editor.config.onchangeTimeout=400
//如果检测到错误的话就打印.
editor.config.customAlert=(err)=>{
console.log(err)
}
editor.customConfig=editor.customConfig ? editor.customConfig : editor.config
//设置customConfig属性
//设置customConfig对编辑器内文字的变化的处理方法
editor.customConfig.onchange = (html) => { //参数html即已经转化HTML格式的文本
data.editorContent = html;
//在data中提前声明editorContent来存储
console.log(html); //实时输出更新的html格式
};
//以下为新增
const menuItem = [ //工具栏里有哪些工具
"head",
"bold",
"fontSize",
"fontName",
"italic",
"underline",
"indent",
"lineHeight",
"foreColor",
"backColor",
"link",
"list",
"justify",
"image",
"video",
];
editor.config.menus = [...menuItem]; /* 应用设置好的工具栏 */
editor.config.colors = ["#000000", "#eeece0", "#1c487f", "#4d80bf","#fff0f0","#20a8ff"]; /* 文字颜色、背景色可以选择哪些颜色? */
editor.config.fontNames = [ /* 字体工具提供哪些字体? */
"黑体",
"仿宋",
"楷体",
"标楷体",
"华文仿宋",
"华文楷体",
"宋体",
"微软雅黑",
];
editor.config.height = 500; //你可能发现这个编辑器是没法用样式调高度的?
//新增至此
editor.create()
})
</script>
<style scoped>
</style>
vue中引入二维码( vue-qr )
// npm
npm install vue-qr --save
// 页面引入组件
// vue2.x
import VueQr from 'vue-qr'
// vue3.x
import vueQr from 'vue-qr/src/packages/vue-qr.vue'
<vue-qr
id="vueQrs"
:text="textValue"
:logoSrc="logoPath"
:logoScale="40"
:size="190"
:margin="10"
@click="downloadQR"
/>
text | 二维码要展示的内容 |
---|---|
logoSrc | 二维码中间的小logo |
logoScale | 小logo的大小(别搞太大,超过容错率识别不出来的) |
size | 整个二维码所占空间的大小,(宽高相等,包含margin) 可能需要你自己用css设置一下图片宽高100% |
margin | 二维码的外边距(默认 20px) |
// 事件处理函数(下载二维码)
const downloadQR=()=> {
const a = document.createElement('a')
// 下载的文件名
a.download = '二维码'
// url
let loadElement=document.getElementById("vueQrs")
a.href = loadElement.src
// 触发点击
a.click()
}
vue3中 setup语法糖使用组件级的路由导航守卫,定义组件name (unplugin-vue-define-options)
unplugin-vue-macros(github.com)
在
<script setup >
中可使用 defineOptions 宏,以便在<script setup >
中使用 Options API。 尤其是能够在一个函数中设置 name、props、emit 和 render 属性。 有了这个宏,你就可以在<script setup>
使用 Options API;
npm i unplugin-vue-define-options -D
vite.config.ts
import DefineOptions from 'unplugin-vue-define-options/vite'
// https://vitejs.dev/config/
export default defineConfig({
plugins: [vue(), DefineOptions()],
resolve: {
// ↓路径别名,主要是这部分
alias: {
"@": resolve(__dirname, "./src")
}
}
})
在需要的组件页面加入代码:
<script setup lang="ts">
defineOptions({
name: '***', // name可用于配置组件 名称
beforeRouteEnter(to, from, next) {
next((vm) => {
console.log(vm,to,from,'123456')
})
}
})
</script>
视频播放器
几款优秀开源的HTML5 视频播放器 - 体验盒子 - 不再关注网络安全 (uedbox.com)
Vue3VideoPlay(vue3可用,效果好)
Vue3-video-play视频播放器插件_视频播放插件-CSDN博客
npm i vue3-video-play --save
main.js
import vue3videoPlay from 'vue3-video-play' // 引入组件
import 'vue3-video-play/dist/style.css' // 引入css
app.use(vue3videoPlay)
报错:Failed to resolve entry for package "vue3-video-play". The package may have incorrect main/module/exports specified in its package.json.
步骤:找到node_modules\vue3-video-play\package.json 将package.json中 "module": "./dist/index.es.js"改为"module": "./dist/index.mjs"
然后重启项目,可以看到没有报错了
使用(具体参数看官网展示):
<template>
<vue3VideoPlay
v-bind="options"
:poster="options.poster"
@play="onPlay"
@pause="onPause"
@timeupdate="onTimeupdate"
@canplay="onCanplay"
/>
</template>
<script setup>
const options = reactive({
width: "800px", //播放器宽度
height: "450px", //播放器高度
color: "#409eff", //主题色
title: "测试", //视频名称
src: "http://1.94.16.149:9000/test/movie.mp4", //视频源
poster: "https://cdn.jsdelivr.net/gh/xdlumia/files/video-play/ironMan.jpg", // 视频封面
muted: false, //静音
speed: true, // 开启进度条拖动
webFullScreen: false,
speedRate: ["0.75", "1.0", "1.25", "1.5", "2.0"], //播放倍速
autoPlay: false, //自动播放
loop: false, //循环播放
mirror: false, //镜像画面
ligthOff: false, //关灯模式
volume: 0.3, //默认音量大小
control: true, //是否显示控制
controlBtns: [
"audioTrack",
"quality",
"speedRate",
"volume",
"setting",
"pip",
"pageFullScreen",
"fullScreen",
], //显示所有按钮,
});
/**
* 支持所有原生video事件
*/
const onPlay = (ev) => {
console.log("播放");
};
const onPause = (ev) => {
console.log(ev, "暂停");
};
const onTimeupdate = (ev) => {
console.log(ev, "时间更新");
};
const onCanplay = (ev) => {
console.log(ev, "可以播放");
};
</script>
Aliplayer阿里播放器
西瓜播放器(可音乐播放)
西瓜播放器 | 快速上手 (bytedance.com) vue3 集成西瓜视频播放器xgplayer_vue3 xiguaplay-CSDN博客
只需三步:安装、DOM占位、实例化即可完成播放器的使用。
Mui Player(多端可用,效果好)
MuiPlayer vue + mui-player视频播放器_vue+muiplayer-CSDN博客
npm i mui-player --save
npm i mui-player-desktop-plugin pc端拓展(插件)
页面引入:
import 'mui-player/dist/mui-player.min.css'
import MuiPlayer from 'mui-player'
// 使用模块管理器引入插件(pc端拓展)
import MuiPlayerDesktopPlugin from 'mui-player-desktop-plugin'
vue3使用
<div id="mui-player" ></div>
<script setup>
const customSetting=[{
functions:'清晰度',
model:'select',
show:true,
zIndex:0,
childConfig:[
{functions:'蓝光1080P'},
{functions:'超清'},
{functions:'高清',selected:true},
{functions:'标清'},
],
onToggle:function(data,selected,back) {
// Action
}
}]
onMounted(() => {
// 初始化 MuiPlayer 插件,MuiPlayer 方法传递一个对象,该对象包括所有插件的配置
var mp = new MuiPlayer({
container: "#mui-player", // 播放器容器
title: "标题", // 标题
src: "http://1.94.16.149:9000/test/movie.mp4", // 视频播放的资源地址
width: 300, // 初始化播放器宽度
height: 300, // 初始化播放器高度
// plugins 插件拓展(根据需要进行配置,具体配置查看官网)
plugins:[
new MuiPlayerDesktopPlugin({
customSetting:customSetting, // 设置组配置
// contextmenu, // 右键菜单组配置
// thumbnails, // 缩略图配置
})
]
});
// 监听播放器已创建完成
mp.on('ready',function(event) {
console.log(event);
});
});
</script >
vue 搭建组件库V2(简易)
一开始跟着视频编写代码,但是到了webpack打包,webpack.component.js文件和依赖引入时出现了挺多的报错(尝试了几个解决方法,最终没能解决,在打包完css后直接发布了),在打包发布到了npm网站后引入组件,打开页面控制台报红,果然失败了。
后来参考文章,用脚手架自带的打包,成功部署到npm网站上了。使用正常。
vue组件库制作:npm初始化项目+编写组件+webpack打包js+gulp打包css+npm发布
基于element-ui构建自定义组件库,发布到npm及安装使用,以及生成组件库说明文档 - 知乎 (zhihu.com)
搭建vue项目和运行环境(示例)vue2版本
1、首先,创建一个普通的vue2项目
2、打开项目,将src目录下的components文件夹放到项目根目录下,改名为packages,将src文件名改为examples。此时运行项目会报错。原因是vue寻找文件默认是从src目录下寻找,我们改了src名为examples,所以需要对配置进行修改。
修改方法:创建vue.config,js文件,代码如下
module.exports = {
pages: {
index: {
entry: 'examples/main.js',
template: 'examples/index.html',
filename: 'index.html'
}
}
// 扩展 webpack 配置,使 examples 加入编译
chainWebpack: config => {
config.module
.rule('js')
.include
.add('/examples')
.end()
.use('babel')
.loader('babel-loader')
}
}
修改配置文件后,重启项目,成功运行。
3、css文件夹下编写css样式,(如用到scss则需要引入依赖,注意版本问题)
创建vue组件文件 (packages/lib/demo/src/main.vue)
创建index.js(packages/lib/demo/index.js) ,这个js文件用于编写注册组件的代码,最终引入到main.js
如创建Demo组件,index.js代码如下:
import Demo from '../demo/src/main.vue'
Demo.install = function(Vue) {
Vue.component(Demo.name, Demo)
}
export default Demo
最终在main.js引入css文件和组件注册的js代码,(此处的demo.scss属于全局引入了,在组件内可以直接使用的)
import Vue from 'vue'
import App from './App.vue'
import '../packages/css/demo.scss'
import Demo from "../packages/lib/demo/index"
Vue.config.productionTip = false
Vue.use(Demo)
new Vue({
render: h => h(App),
}).$mount('#app')
当配置完成后,项目可正常运行。
组件制作
1、组件设计(通用性)
2、编写html和css样式
3、编写组件逻辑代码
4、测试组件
(card组件示例代码如下)
<template>
<div class="f-card">
<div class="f-card-img" :style="width?{width:width+'px'}:{}">
<img :src="imgSrc" alt="img" :style="imgHeight?{height:imgHeight+'px'}:{}"/>
</div>
<div v-if="summary" class="f-card-summary">
{{summary}}
</div>
<div v-else class="f-card-summary">
<slot></slot>
</div>
<!-- <div class="f-card-footer">
footer
</div> -->
<slot name="footer"></slot>
</div>
</template>
<script>
export default {
name:'Card',
props:{
width:{ //卡片宽度
type:Number,
default:0
},
imgSrc:{ //图片资源地址
type:String,
default:''
},
imgHeight:{ //图片高度
type:Number,
default:0
},
summary:{ //概述
type:String,
default:''
}
},
data() {
return {
}
},
methods: {
},
created() {
},
}
</script>
<style scoped>
</style>
.f-card {
width: 270px;
border-radius: 8px;
background: white;
overflow: hidden;
padding-bottom: 8px;
box-shadow: 10px 5px 5px #efefef;
&-img {
height: 152px;
img {
width: 100%;
height: 100%
}
}
&-summary {
padding: 8px;
font-size: 14px;
text-align: left;
}
&-footer {}
}
测试的组件代码如下,运行功能正常
<template>
<div id="app">
<img alt="Vue logo" src="./assets/logo.png">
<demo></demo>
<Card
imgSrc="123456.png"
summary="这是一个card组件"
width='570'
imgHeight="130"
>
123
<template v-slot:footer>
footer
</template>
</Card>
</div>
</template>
打包
在写组件的文件夹 同级目录创建index.js文件,用于汇总导出所有组件,便于打包后引用
import Demo from './demo'
import Card from "./card"
const components = {
Demo,
Card
}
const install = function(Vue) {
if (install.installed) return;
Object.keys(components).forEach(key => {
Vue.component(components[key].name, components[key])
})
}
export default {
install,
Card,
Demo
}
css也是同级创建index.css,引入其他的css文件,便于打包后引用
@import url('./demo.scss');
@import url('./card.scss');
创建打包命令行,并执行
webpack打包js为umd模块
"scripts": {
"serve": "vue-cli-service serve",
"build": "vue-cli-service build",
"lint": "vue-cli-service lint",
"build:lib": "vue-cli-service build --target lib --name dfk --dest lib packages/lib/index.js",
"build:css": "npx gulp sass"
},
运行 npm run build:lib 打包js代码,注意文件路径报错
创建gulpfile.js文件,用于打包css文件,使用的scss(下载下方需要的4个依赖,注意自己的文件路径)
const gulp = require('gulp');
// const sass = require('gulp-sass');
const sass = require('gulp-sass')(require('sass'));
const minifyCSS = require('gulp-minify-css');
gulp.task('sass', async function() {
return gulp.src('packages/**/*.scss')
.pipe(sass())
.pipe(minifyCSS())
.pipe(gulp.dest('lib/css'))
})
运行 npm run build:css 打包css代码,注意文件路径报错
发布
1、准备一个npm账号
2、npm login 登录npm账号
3、npm publish (推送代码,注意修改package.json的配置)
"name": "fankoui3",
"version": "0.1.0",
"description": "npm测试", //描述
"main": "lib/dfk.umd.js", //打包文件入口
"keywords": [ //关键字
"f-ui",
"vue",
"ui"
],
"author": "fanko", //作者
"files": [ //要打包的文件路径
"lib",
"packages"
],
"scripts": {
"serve": "vue-cli-service serve",
"build": "vue-cli-service build",
"lint": "vue-cli-service lint",
"build:lib": "vue-cli-service build --target lib --name dfk --dest lib packages/lib/index.js",
"build:css": "npx gulp sass"
},
发布成功后从npm网站账号内可看到
测试
创建一个vue项目
下载 fankoui3 依赖
main.js (引入依赖和css样式并使用)
import Vue from 'vue'
import App from './App.vue'
import "fankoui3/lib/css/css/index.css"
import fankoui3 from "fankoui3"
Vue.config.productionTip = false
Vue.use(fankoui3)
new Vue({
render: h => h(App),
}).$mount('#app')
在vue文件中使用Card组件,如可显示正常,则组件库创建成功。
如何写一个属于自己的Vue3组件库
pnpm 基本详细使用(安装、卸载、使用) - 掘金 (juejin.cn)
pnpm 修改默认安装包的仓库位置 pnpm config set store-dir E:/pnpmPackage
使用pnpm的项目最好不要有中文路径,否则下载依赖可能有报错
【前端工程化-组件库】从0-1构建Vue3组件库(打包发布) - 掘金 (juejin.cn)
前端工程化😼开源项目都在用的【Monorepo】🏋🏿 - 掘金 (juejin.cn)
如何写一个属于自己的vue3组件库 - 掘金 (juejin.cn)
参考,可用:全肝鸽鸽 的个人主页 - 文章 - 掘金 (juejin.cn) GitHub - yuanwen0327/learn-create-compoents-lib
一文读懂TS的(.d.ts)文件 - 掘金 (juejin.cn)
从0到1搭建自己的组件库,详解附源码 - 掘金 (juejin.cn)
Monorepo pnpm模式管理多个web项目(Vue3) - 掘金 (juejin.cn)
vue3实现 v-model (封装组件)
// 父组件 v-model 等于 :modelValue="inContent" @update:modelValue="inContent = $event"
<editorIt v-model="inContent" ></editorIt>
// 子组件
<template>
<ChildComponent v-model="valueHtml" />
</template>
<script setup>
import { onMounted ,defineProps, defineEmits,toRefs} from 'vue';
const props = defineProps(['modelValue'])
const emit = defineEmits(['update:modelValue'])
// 子组件内容 HTML
const valueHtml = ref('<p>hello</p>')
onMounted(() => {
valueHtml.value=props.modelValue
})
emit('update:modelValue',‘1234567’)
</script>
vue3.4版本简化了v-model的实现
通过 defineModel 来实现
官网查看:组件 v-model | Vue.js (vuejs.org)
vue3基本使用(用setup 语法糖)
https://cn.vuejs.org/guide/introduction.html
快速使用Vue3最新的15个常用API - 掘金 (juejin.cn)
计算属性
import { ref,reactive, computed } from 'vue'
const author = reactive({
name: 'John Doe',
books: [
'Vue 2 - Advanced Guide',
'Vue 3 - Basic Guide',
'Vue 4 - The Mystery'
]
})
// 一个计算属性 ref,随后访问publishedBooksMessage的值要用 publishedBooksMessage.value
const publishedBooksMessage = computed(() => {
return author.books.length
})
// 计算属性默认是只读的。当你尝试修改一个计算属性时,你会收到一个运行时警告。只在某些特殊场景中你可能才需要用 到“可写”的属性,你可以通过同时提供 getter 和 setter 来创建
const firstName = ref('John')
const lastName = ref('Doe')
const fullName = computed({
// getter
get() {
return firstName.value + ' ' + lastName.value
},
// setter
set(newValue) {
// 注意:我们这里使用的是解构赋值语法
[firstName.value, lastName.value] = newValue.split(' ')
}
})
watch
const x = ref(0)
const y = ref(0)
// 单个 ref
watch(x, (newX) => {
console.log(`x is ${newX}`)
})
// getter 函数
watch(
() => x.value + y.value,
(sum) => {
console.log(`sum of x + y is: ${sum}`)
}
)
// 多个来源组成的数组
watch([x, () => y.value], ([newX, newY]) => {
console.log(`x is ${newX} and y is ${newY}`)
})
//不能直接侦听响应式对象的属性值,这里需要用一个返回该属性的 getter 函数
// 提供一个 getter 函数
watch(
() => obj.count,
(count) => {
console.log(`count is: ${count}`)
},
// { deep: true } 深度监听
// { immediate: true } //立即执行
)
watchEffect()
侦听器的回调使用与源完全相同的响应式状态是很常见的。例如下面的代码,在每当 todoId
的引用发生变化时使用侦听器来加载一个远程资源
const todoId = ref(1)
const data = ref(null)
watch(todoId, async () => {
const response = await fetch(
`https://jsonplaceholder.typicode.com/todos/${todoId.value}`
)
data.value = await response.json()
}, { immediate: true })
特别是注意侦听器是如何两次使用 todoId
的,一次是作为源,另一次是在回调中。
用 watchEffect
函数 来简化上面的代码。watchEffect()
允许我们自动跟踪回调的响应式依赖
watchEffect(async () => {
const response = await fetch(
`https://jsonplaceholder.typicode.com/todos/${todoId.value}`
)
data.value = await response.json()
})
停止侦听器
// 它会自动停止
watchEffect(() => {})
// 手动停止
const unwatch = watchEffect(() => {})
// ...当该侦听器不再需要时
unwatch()
生命周期使用
<script setup>
import { onMounted } from 'vue'
onMounted(() => {
console.log(`the component is now mounted.`)
})
</script>
props
<script setup>
// defineProps里面声名props会有哪些值,例如父组件传给子组件一个'num'值
const props = defineProps(['num'])
// 在script里使用props的值要带'props.'',在template中直接 {{num}} 就可以获取
console.log(props.num)
</script>
// 对象式声明props,只是写法不同
defineProps({
title: String,
likes: Number
})
const props = defineProps(["num"]);
let num = ref(props.num);
然后操作 num 即可
【Vue3】组件props的使用_vue3 props使用-CSDN博客
emit
// 父组件 app.vue
<template>
<son @sonClick="getSon" @sonClick2="getSon2(num)"></son>
</template>
<script setup>
const getSon=()=>{}
const getSon2=(num)=>{}
</script>
//子组件 son.vue
// defineEmits里面声名 自定义事件
const emit = defineEmits(['sonClick','sonClick2'])
// emit触发事件,可携带参数
emit('sonClick')
emit('sonClick2','123')
$forceUpdate
import { getCurrentInstance } from "vue";
const { proxy } = getCurrentInstance();
proxy!.$forceUpdate();
父组件通过ref调用子组件
父组件 app.vue
<chuanzhi ref="myson" ></chuanzhi>
<button @click="mySonRef">{{myson}}</button>
<script setup>
// 定义一个和子组件同名的ref变量
let myson=ref()
const mySonRef=()=>{
// 通过myson.value获取子组件的属性,但只有子组件暴露出来的才能访问
console.log('myson',myson.value)
myson.value.ceshi()
}
</script>
子组件 chuanzhi.vue
<script setup>
let sons=ref('123')
const ceshi=()=>{
console.log('123')
}
// 子组件通过defineExpose向外暴露属性和方法,父组件才能访问
defineExpose({
sons,ceshi
})
</script>
</script>
nextTick
若要等待一个状态改变后的 DOM 更新完成 ,可以使用nextTick
import { nextTick } from 'vue'
nextTick(() => {
// 访问更新后的 DOM
})
依赖注入(provide,inject )
依赖提供方:provide
<script setup>
import { provide } from 'vue'
provide(/* 注入名 */ 'message', /* 值 */ 'hello!')
// 第二个参数是提供的值,值可以是任意类型,包括响应式的状态,比如一个 ref
const count = ref('0')
provide('message', count.value)
</script>
注入:inject 接收依赖
<script setup>
import { inject } from 'vue'
const message = inject('message')
// 如果没有祖先组件提供 "message", `value` 会是 "这是默认值"
const value = inject('message', '这是默认值')
</script>
vue3 hooks
鼠标跟踪器示例
// mouse.js(hook 完整示例)
import { ref, onMounted, onUnmounted } from 'vue'
// 按照惯例,组合式函数名以“use”开头
export function useMouse() {
// 被组合式函数封装和管理的状态
const x = ref(0)
const y = ref(0)
// 组合式函数可以随时更改其状态。
function update(event) {
x.value = event.pageX
y.value = event.pageY
}
// 一个组合式函数也可以挂靠在所属组件的生命周期上
// 来启动和卸载副作用
onMounted(() => window.addEventListener('mousemove', update))
onUnmounted(() => window.removeEventListener('mousemove', update))
// 通过返回值暴露所管理的状态
return { x, y }
}
(再拆分)我们可以将添加和清除 DOM 事件监听器的逻辑也封装进一个组合式函数中:
// event.js
import { onMounted, onUnmounted } from 'vue'
export function useEventListener(target, event, callback) {
// 如果你想的话,
// 也可以用字符串形式的 CSS 选择器来寻找目标 DOM 元素
onMounted(() => target.addEventListener(event, callback))
onUnmounted(() => target.removeEventListener(event, callback))
}
有了它,之前的 useMouse()
组合式函数可以被简化为:
// mouse.js
import { ref } from 'vue'
import { useEventListener } from './event'
export function useMouse() {
const x = ref(0)
const y = ref(0)
useEventListener(window, 'mousemove', (event) => {
x.value = event.pageX
y.value = event.pageY
})
return { x, y }
}
组件中使用
<script setup>
import { useMouse } from './mouse.js'
const { x, y } = useMouse()
</script>
<template>Mouse position is at: {{ x }}, {{ y }}</template>
异步状态示例
在做异步数据请求时,我们常常需要处理不同的状态:加载中、加载成功和加载失败。
接收响应式状态
useFetch()
接收一个静态 URL 字符串作为输入——因此它只会执行一次 fetch 并且就此结束。如果我们想要在 URL 改变时重新 fetch 呢?为了实现这一点,我们需要将响应式状态传入组合式函数,并让它基于传入的状态来创建执行操作的侦听器。
举例来说,useFetch()
应该能够接收一个 ref:或者接收一个 getter 函数:
我们可以用 watchEffect()
和 toValue()
API 来重构我们现有的实现:
toValue()
是一个在 3.3 版本中新增的 API。它的设计目的是将 ref 或 getter 规范化为值。如果参数是 ref,它会返回 ref 的值;如果参数是函数,它会调用函数并返回其返回值。否则,它会原样返回参数。它的工作方式类似于 unref()
,但对函数有特殊处理。
注意 toValue(url)
是在 watchEffect
回调函数的内部调用的。这确保了在 toValue()
规范化期间访问的任何响应式依赖项都会被侦听器跟踪。
这个版本的 useFetch()
现在能接收静态 URL 字符串、ref 和 getter,使其更加灵活。watch effect 会立即运行,并且会跟踪 toValue(url)
期间访问的任何依赖项。如果没有跟踪到依赖项(例如 url 已经是字符串),则 effect 只会运行一次;否则,它将在跟踪到的任何依赖项更改时重新运行。
输入参数
即便不依赖于 ref 或 getter 的响应性,组合式函数也可以接收它们作为参数。如果你正在编写一个可能被其他开发者使用的组合式函数,最好处理一下输入参数是 ref 或 getter 而非原始值的情况。可以利用 toValue()
工具函数来实现:
import { toValue } from 'vue'
function useFeature(maybeRefOrGetter) {
// 如果 maybeRefOrGetter 是一个 ref 或 getter,
// 将返回它的规范化值。
// 否则原样返回。
const value = toValue(maybeRefOrGetter)
}
useFetch()实现代码示例:
import { ref, watchEffect, toValue } from 'vue'
export function useFetch(url) {
const data = ref(null)
const error = ref(null)
watchEffect(async () => {
// reset state before fetching..
data.value = null
error.value = null
// resolve the url value synchronously so it's tracked as a
// dependency by watchEffect()
const urlValue = toValue(url)
try {
// artificial delay / random error
await timeout()
// unref() will return the ref value if it's a ref
// otherwise the value will be returned as-is
const res = await fetch(urlValue)
data.value = await res.json()
} catch (e) {
error.value = e
}
})
return { data, error }
}
// artificial delay
function timeout() {
return new Promise((resolve, reject) => {
setTimeout(() => {
if (Math.random() > 0.3) {
resolve()
} else {
reject(new Error('Random Error'))
}
}, 300)
})
}
使用
<script setup>
import { ref, computed } from 'vue'
import { useFetch } from './useFetch.js'
const baseUrl = 'https://jsonplaceholder.typicode.com/todos/'
const id = ref('1')
const url = computed(() => baseUrl + id.value)
const { data, error, retry } = useFetch(url)
</script>
<template>
Load post id:
<button v-for="i in 5" @click="id = i">{{ i }}</button>
<div v-if="error">
<p>Oops! Error encountered: {{ error.message }}</p>
<button @click="retry">Retry</button>
</div>
<div v-else-if="data">Data loaded: <pre>{{ data }}</pre></div>
<div v-else>Loading...</div>
</template>
getCurrentInstance
在Vue3中,getCurrentInstance() 可以用来获取当前组件实例
Vue3组合式API:getCurrentInstance_vue3 getcurrentinstance-CSDN博客
Vite vue 配置
vite打包视图分析(rollup-plugin-visualizer)
vue3+vite打包视图分析(rollup-plugin-visualizer)+gzip - 掘金 (juejin.cn)
分析项目中的js文件大小和引用情况,基于展示的依赖项 js 大小对前端项目做优化处理
npm install --save-dev rollup-plugin-visualizer
vite.config.js 增加代码
// 分析项目中的js文件大小和引用情况
import { visualizer } from "rollup-plugin-visualizer";
plugins: [vue(),vueJsx(), visualizer({
gzipSize: true,
brotliSize: true,
emitFile: false,
filename: "test.html", //分析图生成的文件名
open:true //如果存在本地服务端口,将在 npm run build 打包后自动展示
}),],
vite 项目优化 配置 CDN 服务
配置cdn加速,减小打包体积
【vite 4.3 + vue 3.2 项目优化实战】配置 CDN 服务 - 掘金 (juejin.cn)
Vue和React对比学习之路由角色权限
Vue和React对比学习之路由角色权限(页面、按钮权限控制) - 掘金 (juejin.cn)
如何在Vue.js中创建模态框(弹出框)
简易组件
移动端点击侧边展示导航的组件
用的ant-design-vue的 a-drawer
<template>
<div class="the-dot">
<div @click="showBannel" class="go-back"><a-icon type="menu-fold" /></div>
<a-drawer
class="drawer"
title="导航菜单"
:placement="placement"
:closable="false"
:visible="visible"
@close="onClose"
>
<div class="company-tab">
<div v-for="(item,index) in pathList" :key="index" class="tab" :class="path==item.path?'active':''" @click="goThePath(item)"><a-icon type="appstore" class="icon"/>{{ item.name }}</div>
</div>
</a-drawer>
</div>
</template>
<script>
export default {
data () {
return {
placement: 'left',
visible: false,
style: 'border-color: #167cf3;color: #167cf3;',
path: '',
pathList: [
{
name: '1',
path: '/index'
},
{
name: '2',
path: '/job/tallSchool',
type: 11
}, {
name: '3',
path: '/needCenter',
type: 8
}
]
}
},
methods: {
goThePath (item) {
this.path = item.path
this.$router.push({
path: item.path,
query: { type: item.type }
})
this.showBannel()
},
goBack () {
this.$router.go(-1)
},
showBannel () {
this.visible = !this.visible
},
onClose () {
this.visible = false
}
},
created () {
},
mounted () {
this.path = this.$route.path
}
}
</script>
<style scoped >
@media screen and (max-width: 600px) {
.the-dot{
display: block;
}
}
@media screen and (min-width: 600px) {
.the-dot{
display: none;
}
}
.go-back{
position: fixed;
background-color: #FFF;
width: 40px;
height: 40px;
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
font-size: 20px;
box-shadow: 0 0 6px rgb(0 0 0 / 12%);
cursor: pointer;
z-index: 1000000000;
top:10px;
right:10px
}
.company-tab {
margin: 0 auto;
background:#001529;
padding-left: 0;
}
.tab{
display: flex;
align-items: center;
padding-left:30px;
margin:10px 0;
color:white;
background: #001529;
width:100%;
height:40px;
margin-bottom: 2px;
font-size: 14px;
cursor: pointer;
}
.tab.active {
background: #167cf3;
/* background: rgba(0, 0, 0, 0.04); */
}
.icon{
margin-right:10px
}
>>>.ant-drawer-body {
padding:0
}
>>>.ant-drawer-wrapper-body {
background:#001529;
}
>>>.ant-drawer-header {
background:#001529;
color:white;
font-weight: 600;
}
>>>.ant-drawer-title {
color:white
}
</style>
门户头部导航组件
<template>
<div >
<div class="main">
<div class="nav-list">
<div @click="go(item)" :class="activePath==item.path?'nav-item-active nav-item':'nav-item'" v-for="(item,index) in navList" :key="index">
<div>{{ item.name }}</div>
<div class="nav-item-in-in" v-if="item.children " >
<div @click="goChild(item,itm)" v-for="(itm,idx) in item.children" :key="idx" style="width:100%;height:50px;" class="display-center " >
<div>{{ itm.name }}</div>
</div>
</div>
</div>
</div>
</div>
</div>
</template>
<script>
export default {
data () {
return {
navList: [
{
name: '首页',
path: '/index',
isFrame: false
},
{
name: '菜单2',
path: '/about',
isFrame: false
},
{
name: '菜单3',
path: '/index3',
// 有children的就是目录
children: [
{
name: '内1',
path: '/index31',
isFrame: false
},
{
name: '内2',
path: '/index32',
isFrame: true, // 是否外链
outUrl: 'http://www.baidu.com' // 外链地址
}, {
name: '内3',
path: '/index33',
isFrame: false
}
]
},
{
name: '菜单4',
path: '/index4',
isFrame: true,
outUrl: 'http://www.baidu.com'
}
],
activePath: '' // 选中的父节点
}
},
components: {
},
created () {
},
mounted () {
this.activePath = this.$route.path
},
methods: {
go (item) {
if (!item.children) {
if (item.isFrame) {
window.open(item.outUrl, '_blank')
} else {
this.$router.push(item.path)
this.activePath = item.path
this.$forceUpdate()
}
}
if (item.children) {
this.$forceUpdate()
}
},
goChild (item, itm) {
if (itm.isFrame) {
window.open(itm.outUrl, '_blank')
} else {
this.$router.push(itm.path)
this.$forceUpdate()
}
}
}
}
</script>
<style scoped>
.main{
width:100%;
height:60px;
/* background: red; */
display: flex;
justify-content: center;
}
.nav-list{
display: flex;
height: 100%;
}
.nav-item{
height: 100%;
display: flex;
align-items: center;
justify-content: center;
padding:0 20px;
margin:0 10px;
cursor: pointer;
position: relative;
}
.nav-item:hover{
display: flex;
align-items: center;
justify-content: center;
padding:0 20px;
color:#25AFF3;
}
.nav-item-active{
display: flex;
align-items: center;
justify-content: center;
padding:0 20px;
border-bottom:2px solid #25AFF3;
color:#25AFF3;
font-size:20px;
font-weight: bold;
}
.nav-item-in-in{
display: none;
color: black;
position: absolute;top:100%;left:0;background: #E5E5E5;width: 100%;
font-size:16px;
}
.nav-item:hover .nav-item-in-in{
display: block;
}
.child-path{
}
.active-child-path{
color:#25AFF3;
font-size:20px;
font-weight: bold;
}
.display-center{
display: flex;
justify-content: center;
align-items: center;
}
</style>
飘窗(vue)
<template>
<div style="position: relative; z-index: 102">
<div
v-show="show"
@mouseover="clear()"
@mouseout="set()"
ref="promptBox"
class="promptBox"
>
<div class="prompt" @click="goDetail()">
<div class="guanBi" @click.stop="show = false">X</div>
</div>
</div>
</div>
</template>
<script>
// import {getCnList} from '@/api/request/api';
export default {
data() {
return {
myl: 0,
w: 360,
h: 160,
step: 2,
bw: 0,
bh: 0,
myt: 50,
div: "",
show: false,
directionY: "down",
directionX: "right",
myVar: 0,
activeItem: {},
};
},
created() {
this.getCnInfo();
},
mounted() {},
methods: {
goDetail() {
window.open("http://photo.china.com.cn/node_9006497.html", "_blank");
},
go() {
//判断移动方向
if (this.directionX == "right") {
//判断向右移动时到最右侧
if (this.myl + this.w + this.step > this.bw) {
this.directionX = "left";
}
} else {
if (this.myl - this.step < 0) {
this.directionX = "right";
}
}
if (this.directionY == "down") {
//判断向下移动时到最下侧
if (this.myt + this.h + this.step > this.bh) {
this.directionY = "up";
}
} else {
if (this.myt - this.step < 0) {
this.directionY = "down";
}
}
//移动
if (this.directionX == "right") {
this.myl += this.step;
} else {
this.myl -= this.step;
}
if (this.directionY == "down") {
this.myt += this.step;
} else {
this.myt -= this.step;
}
this.div.style.left = this.myl + "px";
this.div.style.top = this.myt + "px";
},
clear() {
clearInterval(this.myVar);
},
set() {
this.myVar = setInterval(this.go, 80);
},
getCnInfo() {
// getCnList('piaochuang_index',1,1).then(res => {
// if(res.message.length) {
// this.activeItem = res.message[0];
this.$nextTick(() => {
this.show = true;
this.div = this.$refs.promptBox; //获取对象
this.bw = window.innerWidth;
this.bh = window.innerHeight; //获取浏览器可见窗口的宽高
this.myVar = setInterval(this.go, 80); //定时器
});
// }
// })
},
},
};
</script>
<style lang="scss" scoped>
.promptBox {
position: fixed;
z-index: 100000000000999;
width: 360px;
height: 160px;
padding: 5px;
cursor: pointer;
transition: all linear 0.08s;
}
.guanBi {
display: flex;
justify-content: flex-end;
width: 20px;
padding: 5px;
margin-right: 0;
margin-left: auto;
color: #999;
border: 1px solid transparent;
&:hover {
border-color: #999;
}
}
.prompt {
height: 100%;
background-image: url("@/assets/images/index/chunfeng.jpg");
background-repeat: no-repeat;
background-size: 100% 100%;
}
</style>
vue3使用jsx
如何在 vue3 中使用 jsx/tsx? - 掘金 (juejin.cn)
在 vue3 中优雅的使用 jsx/tsx - 掘金 (juejin.cn)
在Vue3中利用JSX+函数式组件做到更好的代码复用 - 掘金 (juejin.cn)
万字长文详解 Vue JSX,带你全面拥抱 JSX 特性!-讲述了jsx的优点 - 掘金 (juejin.cn)
一起玩转Vue中的JSX:让你一次性掌握它的特性! 微信公众平台 (qq.com)
想在项目中使用 JSX,我们需要安装一个插件@vitejs/plugin-vue-jsx
,这个插件可以让我们在项目中使用 JSX/TSX
npm i @vitejs/plugin-vue-jsx -D
vite.config.js(引入jsc)
import vueJsx from "@vitejs/plugin-vue-jsx";
// https://vitejs.dev/config/
export default defineConfig({
plugins: [vue(), vueJsx()],
});
setup语法糖内基本使用
<template>
<div>
jsx
<jsxtests></jsxtests>
</div>
</template>
<script setup lang="tsx">
// 引入外部样式文件,vue里面的style样式使用是不生效的
import styles from './jsx.module.css'
const msg=' i am msg'
const classNams={
color:'blue'
}
const list=['1','2','3']
const jsxtests=()=>{
return (
<div>
<div style={{color:'red',"font-weight":'bold'}}>{msg}</div>
<div style={classNams}>12332432</div>
{/* 这里的class使用的是外部样式文件里定义的类名,多个类名可以用数组 */}
<div class={styles.asc}>12334</div>
// twillwind css 样式 可以直接使用
<div class="bg-green-600 hover:bg-red-300 cursor-pointer ">文字样式3</div>
// twillwind css 样式 (两个class,会被样式覆盖)
<div onClick={tiao} className="bg-green-600" class={styles.asc}>jsx2跳转</div>
<div>
{
list.map((item,index)=>{
return <div key="index">{item}</div>
})
}
</div>
</div>
)
}
</script>
jsx.module.css
(模块css,隔绝样式的污染,相当于style标签里面的scoped)
.asc{
color:aqua
}
CSS Modules 引入局部样式,相当于 vue 组件中 `` 标签的 scoped 属性。
import styles from './jsx.module.css'
<div class={styles.asc}>12334</div>
Vue Vine
Main Navigation指引
Vue3.x 中使用 template + tsx 也许是最佳组合! (qq.com)
vue使用Tailwindcss
官方文档: Width - TailwindCSS中文文档 | TailwindCSS中文网
一次就能看懂的Tailwind CSS介绍 - 掘金 (juejin.cn)
当有不知道的类可以直接到官网文档查看
在vite-vue3中快速使用Tailwindcss - 掘金 (juejin.cn)
Vue中使用 Tailwind CSS - 掘金 (juejin.cn)
安装依赖
npm i -D tailwindcss postcss autoprefixer
创建配置文件
npx tailwindcss init
引入样式
// main.js
import "tailwindcss/tailwind.css"
配置tailwind.config.js文件
content: ['./index.html', './src/**/*.{vue,js,ts,jsx,tsx}']
vite.config.js增加配置(若不生效,则使用下一个方式)
css: {
postcss: {
plugins: [require('tailwindcss'), require('autoprefixer')],
},
},
若vite.config.js增加配置,tailwindcss并没有生效,则使用以下的配置
新建postcss.config.js,输入以下内容,查看样式生效。
export default {
plugins: {
tailwindcss: {},
autoprefixer: {},
},
}
使用示例:
<div class="bg-green-600 w-20 h-12 font-semibold">12345</div>
vue3 同时使用 element plus 和 Tailwindcss 可能会出现样式冲突(例如el-button显示异常),此时一种解决方法是先引入 Tailwindcss ,然后再引入 element plus 的样式文件
vscode 安装 Tailwind CSS IntelliSense 插件 ,没有提示功能,通过如下解决:
1 vscode点击设置
2 搜索 editor.quickSuggestions
3 strings 改为 on
权限
按钮级别权限
通过自定义指令来实现
https://juejin.cn/post/7320169878644113408?searchId=20240111091116F28599892BCAA27BF8FD
// 按钮权限--显示隐藏控制实现
Vue.directive('permission', {
// 在元素被插入到 DOM 中时触发
inserted (el, binding) {
let userInfo = localStorage.getItem('userInfo')
let btnPermission = JSON.parse(userInfo).buttons //本地存储中取后端返回权限数据
console.log(btnPermission)
if (binding.value) {
// 如果不包含绑定值,则从父节点中移除该元素
if (!btnPermission.includes(binding.value)) {
el.parentNode.removeChild(el) // 移除元素
}
}
}
})
vueUse
起步 | VueUse中文文档 (vueusejs.com)
npm i @vueuse/core
在使用的页面引入需要的钩子就行(具体钩子查看官网)
index.vue
<template>
<div>
<div>vueUse:useMouse: x{{ x }} y{{ y }} </div>
<div>
</template>
<script setup>
import { useMouse } from '@vueuse/core'
const { x, y, sourceType } = useMouse()
</script>