文章目录
vue高级用法
1.混入mixin
(共用,每个组件引入独立存在)
//本质:扩展组件的对象与方法,可以共用
优先级: 值为对象,methods,components中,若发生冲突,取组件
值为函数,如created,mounted,混入先于组件调用
用法:
1.src/mixins文件夹/myMixins.js
export const myMixin {
data() {
return {
name: 'mixin'
}
},
created() {
console.log('mixin...', this.name);
},
mounted() {},
methods: {}
}
2.在vue文件中使用
import {myMixin} '@/mixins/myMixins.js'; // 引入mixin文件
export default {
mixins: [myMixin]
}
2.父子组件传值
第一种:
父组件v-bind绑定属性
<myComponent :title="data"></myComponent>
子组件通过props接收绑定属性,可以通过数组或者对象形式接收
props:{
title:{
type: String, //可指定接收类型,如:Array.
default:"this is default" //可设置默认值
}
}
第二种:
通过provide inject 无论层级多深都可以传递 数据不是响应式的
父组件provide
provide(){
return {
yeye:this
}
}
子组件inject引入
inject:['yeye']
通过watch中监听获取到的方法,实现响应式数据
父组件:
<template>
<div class="parent-container">
Parent组件
<br/>
<button type="button" @click="changeName">改变name</button>
<br/>
Parent组件中 name的值: {{name}}
<Child v-bind="{name: 'k3vvvv'}" />
</div>
</template>
<script>
import Child from './Child'
export default {
name: 'Parent',
data () {
return {
name: 'Kevin'
}
},
methods: {
changeName (val) {
this.name = 'New Kevin'
}
},
provide () {
return {
nameFromParent: this.name,
getReaciveNameFromParent: () => this.name //将一个函数赋值给provide的一个值,这个函数返回父组件的动态数据
}
},
components: {
Child
}
}
</script>
子组件:
<template>
<div class="child-container">
Child组件
<br/>
<GrandSon />
</div>
</template>
<script>
import GrandSon from './GrandSon'
export default {
components: {
GrandSon
}
}
</script><template>
<div class="child-container">
Child组件
<br/>
<GrandSon />
</div>
</template>
<script>
import GrandSon from './GrandSon'
export default {
components: {
GrandSon
}
}
</script>
孙组件:
<template>
<div class="grandson-container">
Grandson组件
<br/>
{{nameFromParent}}
<br/>
{{reactiveNameFromParent}}
</div>
</template>
<script>
export default {
inject: ['nameFromParent', 'getReaciveNameFromParent'],
computed: {
reactiveNameFromParent () {
return this.getReaciveNameFromParent()//子孙组件里面调用这个函数
}
},
watch: {
'reactiveNameFromParent': function (val) {
console.log('来自Parent组件的name值发生了变化', val)//监听数据变化
}
},
mounted () {
console.log(this.nameFromParent, 'nameFromParent')
}
}
</script>
第三种
$parent 可以获取父组件数据和方法
$children 可以获取子组件数据和方法
3.子组件触发父组件事件
第一种:父组件调用方法
父组件定义一个事件
示例:<menu-item @show="showFather"></menu-item>
子组件通过$emit触发
<button @click="$emit('show',111)">触发父组件并传值</button>
第二种:父组件通过加.sync然后 data中定义属性 子组件通过update:xxx
如果是用sync写法 可以如下:
子组件
<button @click="$emit('update:show',111)">触发父组件并传</button>
<!--
下面为简写
<menu-item @update:show="newVal=>showFather=newVal"></menu-item>
-->
<menu-item :show.sync="showFather"></menu-item>
showFather在data中声明
4.eventBus事件总线
//1.先定义一个事件中心
var eventBus=new Vue()
//监听与销毁
eventBus.$on('add',(getData)=>{//靠回调接收数据(j)
console.log(`接收得数据为:${getData}`)
})
evnetBus.$off('add')
//触发
eventBus.$emit('add',参数) //传递数据方
5.vue组件绑定原生事件
//组件注册事件时添加.native,事件逻辑写在methods中即可
比如:
<child @click.native="handleClick"></child>
6.插槽
<!--1.匿名插槽(默认插槽)-->
父组件
<template>
<div>
<h3>这是父组件</h3>
<son>实践slot</son> //此处的实践slot会在子组件中slot标签展示
</div>
</template>
-------------------------------------------------------------
子组件
<template>
<div>
<h4>这是子组件</h4>
<input type="text" placeholder="请输入">
<slot></slot> //展示实践slot
</div>
</template>
<!--2.具名插槽-->
父组件
<template>
<div>
<h3>这是父组件</h3>
<son>
<template slot="myslot">
<div>实践具名slot</div>
</template>
</son>
</div>
</template>
----------------------------------------------------
子组件
<template>
<div>
<h4>这是子组件</h4>
<input type="text" placeholder="请输入">
<slot name="myslot"></slot>
</div>
</template>
<!--3.作用域插槽-->
父组件
<template lang="">
<div>
<h3>这是父组件</h3>
<son>
<template slot="myslot" slot-scope="scope">
<!--通过scope.data拿子组件数据 ,这个data是子组件自定义属性名,需与子组件保持一致-->
<ul>
<li v-for="item in scope.data">{{item}}</li>
</ul>
</template>
</son>
</div>
</template>
---------------------------------------------------------
子组件
<template>
<div>
<h4>这是子组件</h4>
<slot name="myslot" :data='list'></slot> <!--属性名为data-->
</div>
</template>
<script>
export default {
name:'Son',
data(){
return{
list:[
{name:"Tom",age:15},
{name:"Jim",age:25},
{name:"Tony",age:13}
]
}
}
}
</script>
7.动态组件
//通过使用保留的 <component> 元素,动态地绑定到它的 is 特性,可以实现动态组件
<component :is="组件名称"></component>
8.组件缓存
//注意:要求同时只有一个子元素被渲染。若其中有v-for则无效
常用方式
方案一 动态组件 内置组件<component></component>
<keep-alive>
<component :is="view"></component>
</keep-alive>
方案二:当出现条件判断
<keep-alive>
<comp-a v-if="a > 1"></comp-a>
<comp-b v-else></comp-b>
</keep-alive>
//方案三 结合路由使用
<keep-alive>
<router-view></router-view>
</keep-alive>
属性:
include 只有名称包含才缓存
exclude 名称包含不缓存
示例:可以使用正则,分隔字符串,数组
<keep-alive include="a,b"></keep-alive>
<keep-alive :include="/a|b/"></keep-alive>
<keep-alive :include="['a', 'b']"></keep-alive>
9.watch监听
常规用法
data:{
a:1,
b:{
c:1
}
},
watch:{
a(val,oldVal){ //普通监听
console.log('a'+val,oldVal)
},
b:{//深度监听,可监听到对象、数组的变化
handler(val,oldVal){
console.log('b.c:'+val.c,old.c)
},
deep:true, //深度监听
immediate: true //立即监听(默认false)
}
}
高阶使用
1.触发监听执行多个方法
使用数组可以设置多项,形式包括字符串、函数、对象
export default {
data: {
name: 'Joe'
},
watch: {
name: [
'sayName1',
function(newVal, oldVal) {
this.sayName2()
},
{
handler: 'sayName3',
immaediate: true
}
]
},
methods: {
sayName1() {
console.log('sayName1==>', this.name)
},
sayName2() {
console.log('sayName2==>', this.name)
},
sayName3() {
console.log('sayName3==>', this.name)
}
}
}
2.watch监听多个变量
//watch本身无法监听多个变量。但我们可以将需要监听的多个变量通过计算属性返回对象,再监听这个对象来实现“监听多个变量”
export default {
data() {
return {
msg1: 'apple',
msg2: 'banana'
}
},
compouted: {
msgObj() {
const { msg1, msg2 } = this
return {
msg1,
msg2
}
}
},
watch: {
msgObj: {
handler(newVal, oldVal) {
if (newVal.msg1 != oldVal.msg1) {
console.log('msg1 is change')
}
if (newVal.msg2 != oldVal.msg2) {
console.log('msg2 is change')
}
},
deep: true
}
}
}
10.vue中配置开发环境、生产环境、测试环境
//1、根目录下新建文件:.env.development(开发环境)、.env.test(测试环境)、.env.production文件(生产环境)
.env.development(开发环境)配置内容
NODE_ENV = 'development' //模式
VUE_APP_MODE = 'development' //通过"VUE_APP_MODE"变量来区分环境
VUE_APP_BASE_URL = 'http://192.****:8008/' //api地址
.env.test(测试环境)配置内容
NODE_ENV = 'test'
VUE_APP_MODE = 'test'
VUE_APP_BASE_URL = 'http://xxx.xxx.xxx.xx:8008/'
//打包输出目录
outputDir = dist-test
.env.production文件(生产环境)配置内容
NODE_ENV = 'production'
VUE_APP_MODE = 'production'
VUE_APP_BASE_URL = 'http://xxx.xxx.xxx.xx:8008/'
outputDir = dist-production
//2.修改vue.config.js
console.log(process.env.VUE_APP_BASE_URL)//打印环境
module.exports = {
// 基本路径,相对路径
publicPath: "./",
// 输出文件目录
outputDir: process.env.outputDir,
productionSourceMap:process.env.NODE_ENV==='production'?false:true,//生产环境下设置source map为false加速构建
devServer:{
proxy:{
'/api':{
target:process.env.VUE_APP_BASE_URL,//使用的时候调用process.env
changeOrigin:true,//开启跨域(修改y
ws:false,//websocket不支持
pathRewrite:{
'^/api':''
}
}
}
}
}
//3.配置package.json (执行对应的npm run xxx)
"scripts":{
//打包开发环境
"serve":"vue-cli-service build --mode development",
//打包生产环境
"build":"vue-cli-service build --mode production",
//打包测试环境
"test": "vue-cli-service build --mode test",
"lint":"vue-cli-service lint"
}
-----------------------------------------
// 4.判断并使用不用的开发环境配置
if(process.env.VUE_APP_MODE==='development'){
//开发环境下的执行操作
}else if(process.env.VUE_APP_MODE==='test'){
//测试环境下的执行操作
}else{
//生产环境下的执行操作
}
自定义环境变量必须以VUE_APP_开头
NODE_ENV
BASE_URL
//只有z三种才能生效
11.观察组件中的任何内容
export default {
computed: {
someComputedProperty() {
// 更新计算道具
},
},
watch: {
someComputedProperty() {
// 当计算的 prop 更新时做一些事情
}
}
};
11.nextTick()
//this.$nextTick()作用:帮助我们在改变组件中属性后,立刻拿到渲染以后的dom节点对象
this.$nextTick(()=>{
})
12.监听组件生命周期
//使用 @hook 即可监听组件生命周期,组件内无需做任何改变。同样的, created 、 updated 等也可以使用此方法
<template>
<List @hook:mounted="listenMounted" />
</template>
<script>
export default{
methods:{
listenMounted(){
}
}
}
</script>
13.定时器优雅清除方式
//我们可以通过 $on 或 $once 监听页面生命周期销毁来解决这个问题:
export default {
mounted() {
this.creatInterval('hello')
this.creatInterval('world')
},
methods:{
creatInterval(msg) {
let timer = setInterval(() => {
console.log(msg)
}, 1000)
this.$once('hook:beforeDestroy', function() {
clearInterval(timer)
})
}
}
}
vue-cli配置
//3x版本中需要新建vue.config.js
const path = require('path');
const IS_PROD = ['production', 'prod'].includes(process.env.NODE_ENV);
const resolve = (dir) => path.join(__dirname, dir);
module.exports = {
publicPath: process.env.NODE_ENV === 'production' ? '/site/vue-demo/' : '/', // 公共路径
indexPath: 'index.html' , // 相对于打包路径index.html的路径
outputDir: process.env.outputDir || 'dist', // 'dist', 生产环境构建文件的目录
assetsDir: 'static', // 相对于outputDir的静态资源(js、css、img、fonts)目录
lintOnSave: false, // 是否在开发环境下通过 eslint-loader 在每次保存时 lint 代码
runtimeCompiler: true, // 是否使用包含运行时编译器的 Vue 构建版本
productionSourceMap: !IS_PROD, // 生产环境的 source map
parallel: require("os").cpus().length > 1, // 是否为 Babel 或 TypeScript 使用 thread-loader。该选项在系统的 CPU 有多于一个内核时自动启用,仅作用于生产构建。
pwa: {}, // 向 PWA 插件传递选项。
chainWebpack: config => {
config.resolve.symlinks(true); // 修复热更新失效
// 如果使用多页面打包,使用vue inspect --plugins查看html是否在结果数组中
config.plugin("html").tap(args => {
// 修复 Lazy loading routes Error
args[0].chunksSortMode = "none";
return args;
});
config.resolve.alias // 添加别名
.set('@', resolve('src'))
.set('@assets', resolve('src/assets'))
.set('@components', resolve('src/components'))
.set('@views', resolve('src/views'))
.set('@store', resolve('src/store'));
},
css: {
extract: IS_PROD,
requireModuleExtension: false,// 去掉文件名中的 .module
loaderOptions: {
// 给 less-loader 传递 Less.js 相关选项
less: {
// `globalVars` 定义全局对象,可加入全局变量
globalVars: {
primary: '#333'
}
}
}
},
devServer: {
overlay: { // 让浏览器 overlay 同时显示警告和错误
warnings: true,
errors: true
},
host: "localhost",
port: 8080, // 端口号
https: false, // https:{type:Boolean}
open: false, //配置自动启动浏览器
hotOnly: true, // 热更新
// proxy: 'http://localhost:8080' // 配置跨域处理,只有一个代理
proxy: { //配置多个跨域
"/api": {
target: "http://172.11.11.11:7071",
changeOrigin: true,
// ws: true,//websocket支持
secure: false,
pathRewrite: {
"^/api": "/"
}
},
"/api2": {
target: "http://172.12.12.12:2018",
changeOrigin: true,
//ws: true,//websocket支持
secure: false,
pathRewrite: {
"^/api2": "/"
}
},
}
}
}
移动端调试
<script src="https://cdn.bootcss.com/vConsole/3.3.4/vconsole.min.js"></script>
//var vConsole = new VConsole();
1.npm install vconsole -D
2.在vue项目的main.js文件中写入
import VConsole from 'vconsole';
Vue.prototype.$vconsole = new VConsole()
//方式二:
统一局域网下,可以配置package.json
"dev": "cross-env NODE_ENV=development webpack-dev-server --hot --port 8999 --host 192.168.0.105(主机ip)",
//在后面加上 --host加主机ip地址,然后将路径发给手机就可以了 window+R 打开 然后cmd 然后输入ipconfig 看ipv4地址
有一些需要在配置的index.js里面配置一些东西
localhost也要改成对应主机ip
Vuex
(状态管理,任一组件修改值,其他组件随之修改)
基础用法
第一步:npm i vuex -s
//store.js:
import Vue from 'vue'
import Vuex from 'vuex'
//挂载Vuex
Vue.use(Vuex)
//创建VueX对象
const store = new Vuex.Store({
state:{
//存放的键值对就是所要管理的状态
name:'helloVueX'
}
})
export default store
//在main.js中挂载Vuex
import Vue from 'vue'
import App from './App'
import router from './router'
import store from './store'
Vue.config.productionTip = false
/* eslint-disable no-new */
new Vue({
el: '#app',
router,
store, //store:store 和router一样,将我们创建的Vuex实例挂载到这个vue实例中
render: h => h(App)
})
第二步:vue组件使用
//1.State取值
第一种方式
this.$store.state.name //使用state值
第二种方式:
import {mapState} from 'vuex'
computed:{
...mapState(['name'])
}
//2.mutation(变更state中的数据)
第一种方式:
const store=new Vuex.State({
state:{
count:0
},
mutation:{
add(state){
state.count++
}
}
})
//组件中使用
第一种方式
methods:{
handle(){
this.$store.commit('add') //利用commit触发
}
}
第二种方式
import {mapMutations}from 'vuex'
methods:{
...mapMutations(['add']) //作为方法直接使用过
}
//Actions(异步任务)
const store =new Vuex.store({
mtations:{
add(state){
state.count++
}
}
})
actions:{
addAsync(context,payload){
setTimeout(()=>{
context.commit('add',payload)//触发mutations中函数操作state数据
},1000)
}
}
//组件中使用
第一种方式
methods:{
handle(){
this.$store.dispatch('addAsync')//触发actions
}
}
第二种方式
import {mapActions} from 'vuex'
methods:{
...mapActions(['addAsync'])
}
//getters(对store中得数据加工处理形成新的数据)
const store =new Vuex.store({
mtations:{
add(state){
state.count++
}
}
})
getters:{
showNum:State=>{
return '当前最新数量【'+State.count++'】'
}
}
//组件中使用
第一种方式
this.$store.getters.showNum
第二种方式
import {mapGetters} from 'vuex'
computed:{
...mapGetters(['showNum'])
}
解决页面刷新数据丢失问题
方式一:
在vue项目中用vuex来做全局的状态管理, 发现当刷新网页后,保存在vuex实例store里的数据会丢失。
//原因:
因为store里的数据是保存在运行内存中的,当页面刷新时,页面会重新加载vue实例,store里面的数据就会被重新赋值初始化
//解决思路:配合本地存储做数据持久化
1.localStorage:永久存储
2.sessionSrorage:窗口关闭销毁
//vue单页面应用,适合sessionStorage
1.sessionStorage可以保证页面打开时数据为空
2.每次打开页面localStorage存储着上一次打开页面的值,因此需要清空之前的数据
//在app.vue中
export default {
name: 'App',
created () {
//在页面加载时读取sessionStorage里的状态信息
if (sessionStorage.getItem("store") ) {
//vuex.store的replaceState方法可以替换store的
this.$store.replaceState(Object.assign({}, this.$store.state,JSON.parse(sessionStorage.getItem("store"))))
}
//在页面刷新时将vuex里的信息保存到sessionStorage里
window.addEventListener("beforeunload",()=>{
//监听beforeunload事件,此事件可以在页面刷新前触发
sessionStorage.setItem("store",JSON.stringify(this.$store.state))
})
}
}
方式二
const state = {
//取值从缓存中拿
authInfo: JSON.parse(sessionStorage.getItem("COMPANY_AUTH_INFO")) || {}
}
const getters = {
authInfo: state => state.authInfo,
}
const mutations = {
SET_COMPANY_AUTH_INFO(state, data) {
state.authInfo = data
sessionStorage.setItem("COMPANY_AUTH_INFO", JSON.stringify(data))
}
}
//actions 模块里无需使用 sessionStorage
export default {
namespaced: true,
state,
getters,
mutations,
//actions,
}
vuex数据持久化(插件方式)
//1.安装vuex-persist
npm install --save vuex-persist
//2.store.js中引入
import VuexPersistence from 'vuex-persist'
//创建对象并进行配置
const vuexLocal=new VuexPersistence({
storage:window.loaclStorage
//storage:window.sessionStorage
})
//引入vuex插件
const store=new Vuex.Store({
state:{...},
mutations:{...},
actions:{...},
plugins:[vuexLocal.plugin]
})
vue内外边距的初始化
//APP.vue根组件中
<template>
<div id="app">
<router-view/>
</div>
</template>
<style lang="less" > //注意此处不能加scoped 因为需要影响全部页面
html,body,#app{
height: 100%;
width: 100%;
padding: 0;
margin: 0;
}
</style>
vue-router(路由)
1.路由传参
1.query传参 (路径拼接方式 刷新一般不会丢失数据 name和path都可以
this.$router.push({ name:"admin",query:{id:item.id}})
this.$router.push({ path:"/admin",query:{id:item.id}})
接收参数:this.$route.query
如果通过query方式传递的是数组或对象,需要通过JSON.stringify()转换
2.params传参 (只能使用name
this.$router.push({ name:"second",params:{id:item.id}})
接收参数:this.$route.params
必须在路由中写入需要传递的参数 否则刷新会丢失数据
路由 {
path:'/second/:id/:name', //若后面加?代表参数是可选的
name:'second',
component:()=>import('@/view/second')
}
3.使用props配合组件路由解耦
// 路由配置
{
path: '/detail/:id',
name: 'detail',
component: Detail,
props: true // 如果props设置为true,$route.params将被设置为组件属性
}
// 列表页(路由传参)
goDetail(row) {
this.$router.push({
path: '/detail',
query: {
id: row.id
}
})
}
-------------------------------------
// 详情页(获取传递参数)
export default {
props: { // 将路由中传递的参数id解耦到组件的props属性上
id: String
},
mounted: {
console.log(this.id)
}
}
VUE项目美化滚动条
::-webkit-scrollbar-track {
background: rgba(0, 0, 0, 0.1);
border-radius: 0;
}
::-webkit-scrollbar {
-webkit-appearance: none;
width: 6px;
height: 6px;
}
::-webkit-scrollbar-thumb {
cursor: pointer;
border-radius: 5px;
background: rgba(0, 0, 0, 0.15);
transition: color 0.2s ease;
}
组件封装使用
1.滚动词云
<!--
* @Description:会动的词云
* @Author: Vergil
* @Date: 2021-08-25 14:17:45
* @LastEditTime: 2021-08-25 17:08:15
* @LastEditors: Vergil
-->
<template>
<div class="wordCloud" ref="wordCloud">
</div>
</template>
<script>
export default {
name: 'word-cloud',
data () {
return {
hotWord: ['万事如意', '事事如意 ', '万事亨通', '一帆风顺', '万事大吉', '吉祥如意', '步步高升', '步步登高', '三羊开泰', '得心应手', '财源广进', '陶未媲美', '阖家安康', '龙马精神', '锦绣前程', '吉祥如意', '生龙活虎', '神采奕奕', '五谷丰登', '马到成功', '飞黄腾达', ' 步步高升', '福禄寿禧'],
color: [
'#a18cd1', '#fad0c4', '#ff8177',
'#fecfef', '#fda085', '#f5576c',
'#fe9a8b', '#30cfd0', '#38f9d7'
],
wordArr: [],
timer: null,
resetTime: 10,
ContainerSize: ''
};
},
mounted () {
this.init();
},
methods: {
init () {
this.dealSpan();
this.initWordPos();
this.render();
},
dealSpan () {
const wordArr = [];
this.hotWord.forEach((value) => {
// 根据词云数量生成span数量设置字体颜色和大小
const spanDom = document.createElement('span');
spanDom.style.position = 'relative';
spanDom.style.display = 'inline-block';
spanDom.style.color = this.randomColor();
spanDom.style.fontSize = this.randomNumber(15, 30) + 'px';
spanDom.innerHTML = value;
spanDom.local = {
position: {
// 位置
x: 0,
y: 0
},
direction: {
// 方向 正数往右 负数往左
x: 1,
y: 1
},
velocity: {
// 每次位移初速度
x: -0.5 + Math.random(),
y: -0.5 + Math.random()
},
};
this.$refs.wordCloud.appendChild(spanDom);
wordArr.push(spanDom);
});
this.wordArr = wordArr;
},
randomColor () {
// 获取随机颜色
var colorIndex = Math.floor(this.color.length * Math.random());
return this.color[colorIndex];
},
randomNumber (lowerInteger, upperInteger) {
// 获得一个包含最小值和最大值之间的随机数。
const choices = upperInteger - lowerInteger + 1;
return Math.floor(Math.random() * choices + lowerInteger);
},
render () {
if (this.resetTime < 100) {
this.resetTime = this.resetTime + 1;
this.timer = requestAnimationFrame(this.render.bind(this));
this.resetTime = 0;
}
this.wordFly();
},
wordFly () {
this.wordArr.forEach((value) => {
// 设置运动方向 大于边界或者小于边界的时候换方向
if (value.local.realPos.minx + value.local.position.x < this.ContainerSize.leftPos.x || value.local.realPos.maxx + value.local.position.x > this.ContainerSize.rightPos.x) value.local.direction.x = -value.local.direction.x;
if (value.local.realPos.miny + value.local.position.y < this.ContainerSize.leftPos.y || value.local.realPos.maxy + value.local.position.y > this.ContainerSize.rightPos.y) value.local.direction.y = -value.local.direction.y;
value.local.position.x += value.local.velocity.x * value.local.direction.x;
value.local.position.y += value.local.velocity.y * value.local.direction.y;
// 给每个词云加动画过渡
value.style.transform = 'translateX(' + value.local.position.x + 'px) translateY(' + value.local.position.y + 'px)';
});
},
initWordPos () {
// 计算每个词的真实位置和容器的位置
this.wordArr.forEach((value) => {
value.local.realPos = {
minx: value.offsetLeft,
maxx: value.offsetLeft + value.offsetWidth,
miny: value.offsetTop,
maxy: value.offsetTop + value.offsetHeight
};
});
this.ContainerSize = this.getContainerSize();
},
getContainerSize () {
// 判断容器大小控制词云位置
const el = this.$refs.wordCloud;
return {
leftPos: {
// 容器左侧的位置和顶部位置
x: el.offsetLeft,
y: el.offsetTop
},
rightPos: {
// 容器右侧的位置和底部位置
x: el.offsetLeft + el.offsetWidth,
y: el.offsetTop + el.offsetHeight
}
};
}
},
destroyed () {
// 组件销毁,关闭定时执行
cancelAnimationFrame(this.timer);
},
};
</script>
<style lang="less" scoped>
.wordCloud{
width:100%;
height:100%;
}
</style>
2.封装按钮button
//src/components下创建my-button.vue
<template>
<button class="my-button ellipsis" :class="[size, type]">
<slot />
</button>
</template>
<script>
export default {
name: 'MyButton',
props: {
size: {
type: String,
default: 'middle'
},
type: {
type: String,
default: 'default'
}
}
}
</script>
<style scoped lang="less">
.my-button {
margin-right: 5px;
appearance: none;
border: none;
outline: none;
background: #fff;
text-align: center;
border: 1px solid transparent;
border-radius: 4px;
cursor: pointer;
}
.ellipsis {
white-space: nowrap;
text-overflow: ellipsis;
overflow: hidden;
}
.large {
width: 240px;
height: 50px;
font-size: 16px;
}
.middle {
width: 180px;
height: 50px;
font-size: 16px;
}
.small {
width: 100px;
height: 32px;
font-size: 14px;
}
.mini {
width: 60px;
height: 32px;
font-size: 14px;
}
.default {
border-color: #e4e4e4;
color: #666;
}
.primary {
border-color: #27ba9b;
background: #27ba9b;
color: #fff;
}
.plain {
border-color: #27ba9b;
color: #27ba9b;
background: lighten(#27ba9b, 50%);
}
.gray {
border-color: #ccc;
background: #ccc;
color: #fff;
}
</style>
//使用
<template>
<div class="home-banner">
<my-button type="default" size="mini">默认</my-button>
<my-button type="plain" size="mini">空心</my-button>
<my-button type="primary" size="mini">实心</my-button>
<my-button type="gray" size="mini">灰色</my-button>
<br /><br />
<my-button type="default" size="mini">迷你</my-button>
<my-button type="plain" size="small">小号</my-button>
<my-button type="primary" size="middle">中号</my-button>
<my-button type="gray" size="large">大号</my-button>
</div>
</template>
<script>
import MyButton from '@/components/my-button.vue'
export default {
name: 'App',
components: {
MyButton
},
setup() {
return {}
}
}
</script>
<style lang="less">
.home-banner {
margin: 100px auto;
width: 700px;
height: 300px;
}
</style>
3.侧滑组件
npm install @hyjiacan/vue-slideout //可以使用slideout组件
4.表单封装
<!--1.封装表单-->
<template>
<div>
<el-form ref="ruleForm" class="demo-ruleForm" :model="form" :label-width="formData.labelWidth" :inline="formData.inline" :rules="formData.rules" :size="formData.size" :label-position="formData.labelPosition">
<el-form-item v-for="(item, index) in formData.formItem" :key="index" :label="item.label" :prop="item.prop">
<!-- 文本框 -->
<el-input v-if="item.type === 'text'" v-model="form[item.prop]" :placeholder="item.placeholder" :disabled="item.isDisabled" />
<!-- 文本框支持模糊查询 -->
<el-select v-if="item.type==='remote'" v-model="form[item.prop]" filterable remote reserve-keyword :placeholder="item.placeholder" :remote-method="remoteMethod" :loading="loading">
<el-option v-for="item in options" :key="item.value" :label="item.label" :value="item.value">
</el-option>
</el-select>
<!-- 密码框 -->
<el-input v-if="item.type === 'password'" v-model="form[item.prop]" type="password" :disabled="item.isDisabled" />
<!-- 单选框 -->
<el-radio-group v-if="item.type==='radio'" v-model="form[item.prop]">
<el-radio v-for="item in item.options" :key="item.value" :label="item.value">
{{ item.name }}
</el-radio>
</el-radio-group>
<!-- 单选按钮 -->
<el-radio-group v-if="item.type==='radioButton'" v-model="form[item.prop]" :disabled="item.isDisabled">
<el-radio-button v-for="item in item.options" :key="item.value" :label="item.value">
{{ item.name }}
</el-radio-button>
</el-radio-group>
<!-- 多选框组 -->
<el-checkbox-group v-if="item.type==='checkbox'" v-model="form[item.prop]">
<el-checkbox v-for="item in item.options" :key="item.value" :disabled="item.isDisabled" :label="item.value">
{{item.name}}
</el-checkbox>
</el-checkbox-group>
<!-- 下拉框 -->
<el-select v-if="item.type==='select'" v-model="form[item.prop]" :multiple="item.multiple" collapse-tags clearable :disabled="item.isDisabled" :placeholder="item.placeholder">
<el-option v-for="item in item.options" :key="item.value" :label="item.name" :value="item.value" :disabled="item.isDisabled" />
</el-select>
<!-- 联级面板 -->
<el-cascader v-if="item.type==='cascader'" v-model="form[item.prop]" :options="item.options" :props="item.isMore" clearable />
<!-- 开关 -->
<el-switch v-if="item.type==='switch'" v-model="form[item.prop]" />
<!-- 日期选择器 -->
<el-date-picker v-if="item.type==='date'" v-model="form[item.prop]" type="date" value-format="yyyy-MM-dd" placeholder="选择日期" />
<!-- 时间选择器 -->
<el-time-picker v-if="item.type==='time'" v-model="form[item.prop]" placeholder="请选择时间" />
<!-- 日期时间选择器 -->
<el-date-picker v-if="item.type==='dateTime'" v-model="form[item.prop]" type="datetime" placeholder="选择日期时间" />
<!-- 日期和时间范围选择器 -->
<el-date-picker v-if="item.type==='datetimerange'" v-model="form[item.prop]" type="datetimerange" range-separator="至" start-placeholder="开始日期" end-placeholder="结束日期" />
</el-form-item>
<el-form-item>
<el-button type="primary" @click="onSubmit('ruleForm')">
查询
</el-button>
<el-button @click="resetForm('ruleForm')">
重置
</el-button>
</el-form-item>
</el-form>
<el-button type="primary">+{{btnName}}</el-button>
</div>
</template>
<script>
export default {
props: {
btnName: {
type: String,
},
formData: {
type: Object,
required: true,
},
},
data() {
return {
form: {},
options: [],
loading: false,
};
},
created() {
this.bindValue();
},
methods: {
onSubmit(formName) {
this.$refs[formName].validate((valid) => {
if (valid) {
console.log(this.form);
alert("发送请求去");
} else {
return false;
}
});
},
resetForm(formName) {
this.$refs[formName].resetFields();
},
bindValue() {
const obj = {};
this.formData.formItem.forEach((item, index) => {
// 这里不能写成this.form = obj 因为传递的不是值,而是引用,他们指向了同一个空间!
obj[item.prop] = item.value;
});
this.form = { ...obj };
console.log(this.form["screenName"]);
},
//模糊查询
remoteMethod(query) {
console.log(query);
if (query !== "") {
this.loading = true;
setTimeout(() => {
//发送请求获取数据
var list = [
{
label: "你好",
value: 0,
},
{
label: "哈哈哈",
value: 1,
},
{
label: "我是谁",
value: 2,
},
{
label: "吱吱吱",
value: 3,
},
];
this.loading = false;
this.options = list.filter((item) => {
return item.label.toLowerCase().indexOf(query.toLowerCase()) > -1;
});
}, 200);
} else {
}
},
},
};
</script>
<style scoped lang="less">
</style>
<!--2.使用-->
<template>
<div>
<form-icon :form-data="formData" :btnName="btnName" />
</div>
</template>
<script>
import formIcon from "@/components/form/Ele_form";
import { baseRules } from "@/utils/rules";
export default {
components: {
formIcon,
},
data() {
return {
btnName: "新增横幅",
formData: {
rules: baseRules,
labelWidth: "100px",
inline: true,
labelPosition: "right",
size: "medium",
formItem: [
{
type: "text",
label: "横幅名称",
isDisabled: false,
placeholder: "请输入名称",
prop: "bannerName",
value: "",
required: true,
},
{type: 'password', label: '密码', isDisabled: false, placeholder: '请输入密码', prop: 'password', value: '', required: true},
{type: 'radio', label: '性别', isDisabled: false, prop: 'sex', value: '', options: [{name: '男', value: '1'}, {name: '女', value: '0'}]},
{type: 'switch', label: '状态', isDisabled: false, prop: 'status', value: '0'},
{
type: 'radioButton',
isDisabled: true,
label: '选择城市',
prop: 'city',
value: 'huaian',
options: [
{name: '上海', value: 'shanghai'},
{name: '北京', value: 'beijing'},
{name: '淮安', value: 'huaian'}
]
},
{
type: 'checkbox',
isDisabled: false,
label: '爱好',
prop: 'hoppies',
value: [],
options: [
{name: '游戏', value: 'LOL', isDisabled: true},
{name: '健身', value: 'fitness', isDisabled: false},
{name: '娱乐', value: 'bath', isDisabled: false},
{name: 'Code', value: 'code', isDisabled: true}
]
},
{
type: "select",
isDisabled: false,
// 是否开启多选
multiple: false,
label: "横幅类型",
placeholder: "选择类型",
prop: "bannerType",
value: [],
options: [
{ name: "胶囊横幅", value: 0, isDisabled: false },
{ name: "异形横幅", value: 1, isDisabled: false },
{ name: "常规横幅", value: 2, isDisabled: false },
],
},
{
type: "select",
isDisabled: false,
// 是否开启多选
multiple: false,
label: "发布状态",
placeholder: "选择状态",
prop: "status",
value: [],
options: [
{ name: "生效中", value: 0, isDisabled: false },
{ name: "已启用", value: 1, isDisabled: false },
{ name: "已过期", value: 2, isDisabled: false },
{ name: "未生效", value: 3, isDisabled: false },
],
},
{
type: "select",
isDisabled: false,
// 是否开启多选
multiple: false,
label: "生效期限",
placeholder: "选择期限",
prop: "effectivePeriod",
value: [],
options: [
{ name: "长期有效", value: 0, isDisabled: false },
{ name: "三个月", value: 1, isDisabled: false },
{ name: "一个月", value: 2, isDisabled: false },
{ name: "自定义期限", value: 3, isDisabled: false },
],
},
{
type: 'cascader',
label: '地址',
prop: 'dizhi',
isMore: {multiple: false},
isDisabled: false,
value: [],
options: [
{
value: 'js',
label: '江苏省',
children: [
{
value: 'nanjing',
label: '南京市'
},
{
value: 'suzhou',
label: '苏州市'
},
{
value: 'wuxi',
label: '无锡市'
},
{
value: 'huaian',
label: '淮安市',
children: [
{
value: 'pjpq',
label: '清江浦区'
},
{
value: 'hyq',
label: '淮阴区'
}
]
}
]
},
{
value: 'sh',
label: '上海市',
children: [
{
value: 'pudong',
label: '浦东新区'
},
{
value: 'xuhui',
label: '徐汇区'
},
{
value: 'minhang',
label: '闵行区'
},
{
value: 'songjiang',
label: '松江区',
children: [
{
value: 'dongjing',
label: '洞泾'
},
{
value: 'jiuting',
label: '九亭'
}
]
}
]
}
]
},
{type: 'date', label: '日期', prop: 'starTime', value: ''},
{type: 'time', label: '时间', prop: 'time', value: ''},
{type: 'dateTime', label: '日期时间', prop: 'dateTime', value: ''},
{type: 'datetimerange', label: '范围选择器', prop: 'datetimerange', value: ''}
],
},
};
},
};
</script>
<style lang="scss" scoped>
</style>
拓展:css变量
body {
--color: red; /** 声明全局变量 **/
}
h1 {
color: var(--color); /** 这里获取到的是全局声明的变量,值为red **/
}
div1 {
--color: blue; /** 局部变量 **/
color: var(--color); /** 这里获取到的是局部声明的变量,值为blue **/
}
div2 {
width: var(--width, 100px)/** 如果未定义--width,则取第二个参数值(默认参数) **/
}
//在vue项目中进行使用css变量(旧写法)
<template>
<h1>Vue</h1>
</template>
<script>
export default {
data () {
return {
//data中进行声明
border: '1px solid black',
color: 'red'
}
}
}
</script>
//首先要在<style>标签中写个vars="{}",再在大括号里写上你在data中声明过的值。
<style vars="{ border, color }" scoped>
h1 {
color: var(--color); /* 进行使用 */
border: var(--border);
}
</style>
scoped属性限制下,使用全局css变量可以使用global:,如下:
color:var(--global:color)
<template>
<h1> shit! </h1>
</template>
<script>
export default {
data () {
return {
color: 'yellow',
font:{
weight:100
}
}
}
}
</script>
<style>
h1 {
color: v-bind(color),
font-weight:v-bind('font:weight')
/* 第一:这次更新改变了写法,在<style>标签中不需要再写vars="{ xxx }"了,在 CSS 代码中也不需要再写原生的 CSS 变量引用了(var(--xxx)),取而代之的是v-bind()这个函数,注意 ⚠️ 这个函数虽然跟 JS 里的v-bind很像,但只能用在 CSS 中 */
}
</style>
多个线上环境配置方案
问题: 一般情况下项目开发线上环境只存在一个,而我司情况比较特殊, 项目需在各个厂区上线, 各个厂区使用的数据库, 域名端口皆不相同, 若配置多个线上环境, 每次项目更新都需打包多份代码重新部署
//解决方案: 我司通过字符串拼接的方式对项目的基本地址进行拼接, 在入口函数main.js中:
const host = window.location.host // 获取当前主机名称
const protocol = window.location.protocol // 获取当前URL协议
axios.defaults.baseURL = protocol + '//' + host // 对基本地址进行拼接
利用vscode插件添加与删除console.log
shift+ctrl+l(选中变量名)
shift+ctrl+d
element ui相关
1.el-button防抖
//在main.js中
// 提交以后禁用按钮一段时间,防止重复提交
import Vue from 'vue'
Vue.directive('noMoreClick', {
inserted(el, binding) {
el.addEventListener('click', e => {
el.classList.add('is-disabled')
el.disabled = true
setTimeout(() => {
el.disabled = false
el.classList.remove('is-disabled')
}, 2000)//我这里设置的是2000毫秒也就是2秒
})
}
})
//使用
<el-button v-no-more-click type="primary" @click="submitForm('ruleForm')">确 定</el-button>
---------------------------
若出现报错 可以看下eslint
1: vue.config.js 中增加 lintOnSave: false
2:package.json -> eslintConfig ->rules ->
{
"no-": "off",
"no-debugger":"off",
"no-console": "off",
"no-empty":"off",
"no-unused-vars":"off"
}
3: 新建 .eslintrc.json
{
"rules": {
"no-unused-vars": 0
}
}
2.dialog封装
//子组件
<template>
<div>
<el-dialog
title="新增"
:visible.sync="show"
@close="$emit('update:visible', false)"
>
<el-form>
<el-form-item label="姓名">
<el-input v-model="name" />
</el-form-item>
</el-form>
</el-dialog>
</div>
</template>
<script>
export default {
props: {
visible: {
type: Boolean,
default: false
}
},
data() {
return {
name: ' ',
show: this.visible,
};
},
watch: {
visible(val) {
this.show = val;
}
}
}
</script>
//父组件
<template>
<div>
<button @click="handleAdd">打开/关闭</button>
<addDialog :visible.sync="addshow" />
</div>
</template>
<script>
import addDialog from "./addDialog.vue";
export default {
components: { addDialog },
data() {
return {
addshow: false,
};
},
methods: {
handleAdd() {
this.addshow = true;
}
}
};
</script>
实现的思路是 父组件传递的参数添加 .sync 修饰,子组件中关闭弹框时使用 $emit('update:visible', false) 这样更改父组件中传递的 visible 的值,子组件中watch 到visible改变,就改变中间值show达到关闭的目的
--------------------------------------------------------
<addDialog :visible.sync="addshow" /> 中 .sync 是下面代码的语法糖
<addDialog :visible="addshow" @update:visible="newVal => addshow = newVal"/>
3.分页封装
// 分页子组件
<template>
<div class="paging">
<el-pagination @size-change="handleSizeChange" @current-change="handleCurrentChange" :current-page="currentPage" :page-sizes="[10, 20, 50, 100]" :page-size="pageSize" background
layout="total, sizes, prev, pager, next, jumper" :total="total">
</el-pagination>
</div>
</template>
<script>
export default {
name: "Pagination",
props: ['total','pageSize','currentPage'],
methods: {
handleSizeChange(val) {
this.$emit('pagination2', val )
},
handleCurrentChange(val) {
this.$emit('pagination1', val)
},
},
};
</script>
--------------------------------------------------------------------------
父组件
<template>
<!-- 分页按钮 -->
<Pagination :total="total" :currentPage="form.pageNum" :pageSize="form.pageSize" @pagination2="handleSizeChange" @pagination1="handleCurrentChange">
</Pagination>
<!-- /分页按钮 -->
</template>
<script>
import Pagination from "@/components/pagination/index.vue";
export default {
components: {
Pagination
},
data() {
return {
total: 0,
//表单项
form: {
...
pageNum: 1,
pageSize: 10,
}
},
};
},
methods: {
//改变每页条数
handleSizeChange(val) {
this.form.pageSize = val;
this.search();//列表查询
},
//改变当前页
handleCurrentChange(val) {
this.form.pageNum = val;
this.search();//列表查询
},
};
</script>
4.表格序号
<el-table-column min-width="50px" fixed label="序号" align="center">
<template slot-scope="scope" >
<span v-if="scope.$index-1<0" >合计</span>
<span v-else> {{(form.pageNum - 1) * form.pageSize + scope.$index }}</span>
</template>
</el-table-column>
5.表单规则rules封装
//element表单验证规则
export const baseRules={
userId: [
{
pattern: /[0-9a-zA-Z]|[\u4e00-\u9fa5]/im, //可以写正则表达式呦呦呦
message: "不允许输入特殊字符",
trigger: "blur",
},
],
phone: [
{
pattern: /^1[34578]\d{9}$/, //可以写正则表达式呦呦呦
message: "目前只支持中国大陆的手机号码",
trigger: "blur",
},
],
date: [
{
required:true,
message: "日期必填",
trigger: "blur",
},
],
}
//2使用
<template>
<div id="Carbon">
<el-form ref="form" :inline="true" :model="form" :rules="rules">
<el-row>
<el-form-item label="用户ID" prop="userId">
<el-input v-model="form.userId" maxlength="50" placeholder="请输入用户ID"> </el-input>
</el-form-item>
<el-form-item label="手机号码" prop="phone">
<el-input v-model="form.phone" placeholder="请输入手机号码"></el-input>
</el-form-item>
</el-row>
<el-row>
<el-form-item label="选择日期" prop="date">
<el-date-picker :picker-options="pickerOptions" v-model="form.date" value-format="yyyy-MM-dd HH:mm:ss" type="daterange" range-separator="至" start-placeholder="开始日期" end-placeholder="结束日期">
</el-date-picker>
</el-form-item>
</el-row>
</el-form>
</template>
<script>
import { baseRules } from "@/utils/rules"; //表单验证规则
export default {
name: "",
components: {
},
data() {
return {
// 校验规则
rules: baseRules,
form: {
userId: "", //用户ID
phone: "", //手机号码
date: "", //日期
startTime: "", //日期选择开始时间
endTime: "", //日期选择结束时间
pageNum: 1,
pageSize: 10,
},
minDate: "",
maxDate: "",
pickerOptions: {
onPick: ({ maxDate, minDate }) => {
this.minDate = minDate;
this.maxDate = maxDate;
},
disabledDate: (time) => {
//查询时间跨度为90天
if (this.minDate) {
let range = 90 * 24 * 3600 * 1000;
return (
time.getTime() > Date.now() ||
time.getTime() > this.minDate.getTime() + range ||
time.getTime() < this.minDate.getTime() - range
);
}
return time.getTime() > Date.now();
},
},
}
}
}
</script>