前端小知识点笔记

540 阅读7分钟

const obj = { a:1, b:2, c:3, d:4, e:5, }

const {a:a1} = obj;

console.log(a1);// 1 解构赋值

去重
const a = [1,2,3];
const b = [1,5,6];
const c = [...new Set([...a,...b])];//[1,2,3,5,6]

const obj1 = {
  a:1,
}
const obj2 = {
  b:1,
}
const obj = {...obj1,...obj2};//{a:1,b:1}

Set 对象存储的值总是唯一的
方法描述
add添加某个值,返回Set对象本身。
clear删除所有的键/值对,没有返回值。
delete删除某个键,返回true。如果删除失败,返回false。
forEach对每个元素执行指定操作。
has返回一个布尔值,表示某个键是否在当前 Set 对象之中。
交集
var a = new Set([1, 2, 3]); 
var b = new Set([4, 3, 2]); 
var intersect = new Set([...a].filter(x => b.has(x))); // {2, 3} 

差集
var a = new Set([1, 2, 3]); 
var b = new Set([4, 3, 2]); 
var intersect = new Set([...a].filter(x => !b.has(x))); // {1,4}
const nestedArray = [1, [2, 3], [4, [5]]];
const flattened = nestedArray.flat();

console.log(flattened); // 输出: [1, 2, 3, 4, [5]]
// 注意:深度默认为 1,所以 [5] 未被展开

数组扁平化 flat() 可以搭配Object.values()使用,Object.values(deps).flat(Infinity);其中使用Infinity作为flat的参数,使得无需知道被扁平化的数组的维度。

flatMap

扁平化和map的组合 let arr = ["科比 詹姆斯 安东尼", "利拉德 罗斯 麦科勒姆"]; console.log(arr.flatMap(x=>x.split(' '))) //  ['科比', '詹姆斯', '安东尼', '利拉德', '罗斯', '麦科勒姆']

可选链操作符 ?. const name = obj?.name;

可选链操作符(  ?.  )允许读取位于连接对象链深处的属性的值,而不必明确验证链中的每个引用是否有效。?. 操作符的功能类似于 . 链式操作符,不同之处在于,在引用为空sh) ) 的情况下不会引起错误,该表达式短路返回值是 undefined。与函数调用一起使用时,如果给定的函数不存在,则返回 undefined

空值合并运算符 ??
if(value??'' !== ''){
//... 
}
?? 左边为null或者undefined的时候返回右边

console.time 和 console.timeEnd 两个方法是结合在一起使用的,他们接受一个相同的参数,输出两句表达式中间的代码的执行时间。

console.time('计时器1');
for (var i = 0; i < 1000; i++) { 
for (var j = 0; j < 1000; j++) {
}
}
console.timeEnd('计时器1');

console.table 会将复合数据类型(对象,数组等)在控制台中以表格的形式打印输出,并且你可以将对象数组嵌套乃至结合使用,他都能够将其解析为表格形式

console.trace

追溯你的逻辑执行过程,追溯执行过什么函数

function d(a) { console.trace(); return a; } 
function b(a) { return c(a); } 
function c(a) { return d(a); } 
let a = b('123');

image.png

当逻辑执行到let a = b('123') 语句时,b函数会调用c函数,c函数再调用d函数,d函数中调用了console.trace(),这时console.trace()会将整个执行过程自底向上追溯打印出来。

修改数组,视图不变特殊方法 this.arr = this.arr.map(item => item) 把数组重新赋值一下

vue响应式

需要注意的是只有提前在 data 中添加的数据才是响应式数据,在其他地方往 data 中塞的数据不是响应式的,也就是未来该数据更新的时候并不会触发试图的更新。

如果后续想添加响应式数据,可以通过 Vue 提供的 set 方法。

默认对象采用函数返回

错误的

const default = {
  name: '',
}
export default {
    data() {
        return {
            a:default,
            b:default
        };
    },
    created () {
      this.a.name = 'demo'
      // 值的混淆
      console.log(this.b) // {name:demo}
    }
};

函数返回

 const default = () => ({
      name: ''
    })
export default {
    data() {
        return {
            a:default(),
            b:default()
        };
    },
    created () {
      this.a.name = 'demo'
      // 每次返回的新值
      console.log(this.b) // {name:''}
    }
};
箭头函数缩写

image.png

省略return的写法 const a = () => { return { demo: '123' } } 省略 const a = () => ({ demo: '123' })

if判断多个是否等于
v-if="[2,1,-1].includes(item.casestatus)"
隐性转换

只有null,undefined,0,false,NaN,空字符串,这6种情况转为布尔值的结果为false,其余都是true , 0 === false 为false而 0 == false为true ,1 === true (false) 1== true (true)同理

三元特殊情况

如果三元运算符中某一部分成立不需要任何的处理我们用null/underfined/void 0...占位即可 var num=12; num>10?num++ : null; 如果需要执行多项任务,我们用小括号包裹起来,每条语句用逗号隔开 num=10; num>10?(num++,num*=10):null;

多重三元

判断1 ? "返回1":判断2?"返回2":"否则返回3"

解构参数
function setCookie(name, value, options) {
    options = options || {};
    let secure = options.secure,
        path = options.path,
        domain = options.domain,
        expires = options.expires;
    //其他代码
}

因为options有多次使用到,是个对象类型,但是使用者无法知道是个对象类型,不方便。所以可以直接在参数那结构赋值。

function setCookie(name, value, {secure,path,domain,expires}) {
    let secure = secure,
        path = path,
        domain = domain,
        expires = expires;
}
也可以给整个options默认值
function setCookie(name, value, {secure,path,domain,expires}= {}) {
}
也可以给整个解构赋值默认值
function setCookie(name, value, {secure='default',path,domain,expires}= {}) {
}
Objecct.defineProperty
    const person = {}
    Object.defineProperty(person, 'name', {
      value: 'jack'
    })  // 三个参数都是必填
    console.log(person.name) // jack

除了value,其他值没设置的话,writable,configurable, enumerable会默认给false writable 可编写属性,设置false,就变成只读,configurable总开关,控制是否可以删除和配置属性值,false为不能,当configurable为false时,当writable为true时,可编辑,enumerable为false时,不会出现在遍历中

    const person = {}
    // 必须定义一个变量来存修改的值,set中直接返回的话,会陷入无限循环
    let values = null
    Object.defineProperty(person, 'name', {
      set (newVal) {
        console.log('赋值的时候调用' + newVal)
        values = newVal
      },
      get () {
        console.log('取值的时候调用')
        return values
      }
    })
    person.name = 3
    console.log(person.name) // 3
    
日期格式转换

日期格式化 toLocaleDateString() 可以把new Date()转化成 2021/12/6 这种。toLocaleString() 是 2021/12/6 下午2:54:54这种。toLocaleTimeString是下午2:54:54

数组处理 reduce

给数组执行回调

4个参数
   1、previousValue (上一次调用回调返回的值,或者是提供的初始值(initialValue))
    2、currentValue (数组中当前被处理的元素)
    3、index (当前元素在数组中的索引)
    4array (调用 reduce 的数组)
var arr = [1, 2, 3, 4];
var sum = arr.reduce(function(prev, cur, index, arr) {
    console.log(prev, cur, index);
    return prev + cur;
},0)
// 0是previousValue的初始值
console.log(arr, sum);

1 2 1
3 3 2
6 4 3
[1, 2, 3, 4] 10
ES异步
  function fn (time) {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      console.log(time) // 3000 1000 1000 2000 500
      resolve(`${time}毫秒后我成功啦!!!`)
    }, time)
  })
}
// 数组里面可以直接放事件,  for of 循环可以带上异步,循环时会自动调动数组里面的事件
async function asyncFn () {
  const arr = [fn(3000), fn(1000), fn(1000), fn(2000), fn(500)]
  for await (let x of arr) {
    console.log(x) // 这里输出的是resolve的值
    //3000毫秒后我成功啦!!! 1000毫秒后我成功啦!!! 1000毫秒后我成功啦!!! 2000毫秒后我成功啦!!! 500毫秒后我成功啦!!!
  } 
}
asyncFn()

JavaScript数据类型8种,基础6种,es6加的Symbol 和 es10的BigInt

ES 数字分隔符 _

const number = 192_123_123 console.log(number)// 192123123

es Symbol 新增的基础数据类型,Symbol是唯一的,因此可以做object唯一属性,防止被覆盖。但是object取Symbol属性的时候,不能用.来读属性,只能用[],symbol属性不会被for循环循环到,也不会被Obeject.keys()循环到,Object.getOwnPropertySymbols 可以拿到对象的Symbol集合数组

prototype的constructor直接指向类的本身

position定位属性,sticky粘性定位,监听scroll事件,在滑动过程中,达到了要求粘性距离,效果就会相当于fixed定位,固定到某个位置。

hasOwnProperty新应用,直接引用会报错,新引用方法,Object.prototype.hasOwnProperty.call(obj,'aaa')

slice第一个参数是开始位置,第二个参数是结束位置,为负数的话则是从末尾开始选择 '123456' 的slice(1,-1)返回 2345

粘性布局 sticky

类似absolute和fixed定位的混合,元素在跨越特定阈值前为相对定位,之后为固定定位

.sticky-header {  position: sticky;  top: 10px; }

在 视口滚动到元素 top 距离小于 10px 之前,元素为相对定位。之后,元素将固定在与顶部距离 10px 的位置,直到视口回滚到阈值以下。

不生效的情况

情况1: 未指定 top, right, top 和 bottom 中的任何一个值

情况2: 垂直滚动时,粘性约束元素高度小于等于 sticky 元素高度

情况3: 粘性约束元素和容器元素之间存在 overflow: hidden 的元素

回调函数的妙用

作为参数传入 image.png

背景图调整

根据屏幕大小,自动调整背景图 background-size: cover 也可以设置高度固定,宽度自适应background-size: 100% 140px;

import引用

import 语句用于引入资源,而是否需要在 data 中定义这些资源取决于它们如何被使用。如果资源是组件、常量、函数或全局状态的一部分,通常不需要在 data 中定义;如果资源是组件的本地状态或需要被模板绑定的数据,则需要在 data 中定义。

map根据条件判断return

map本身除了抛错不能中断循环,可以加条件再return,但是会返回underfine,这时候可以跟filter配合,.filter(item => item) 可以去除数组里面的null,'',undefined

  this.menuLists = menu.map(res => {
      if (res.meta?.hideInMenu !== true) {
        return { name: res.meta.title, url: res.path }
      }
    }).filter(item => item)
根据屏幕大小自适应字体大小

用VW

node版本过高,导致项目启动报错

error:0308010C:digital envelope routines::unsupported 最近在升级nodejs版本到v18.7.0后启动项目报digital envelope routines::unsupported因为 node.js V17版本中最近发布的OpenSSL3.0, 而OpenSSL3.0对允许算法和密钥大小增加了严格的限制,可能会对生态系统造成一些影响。故此以前的项目在升级 nodejs 版本后会报错

解决方法

方法一:

export NODE_OPTIONS=--openssl-legacy-provider **(试过好像无效)**

方法二:

修改package.json,在相关构建命令之前加入set NODE_OPTIONS=–openssl-legacy-provider

"scripts": {
   "serve": "SET NODE_OPTIONS=--openssl-legacy-provider && vue-cli-service serve",
   "build": "SET NODE_OPTIONS=--openssl-legacy-provider && vue-cli-service build"
},
Object.defineProperty(obj, prop, descriptor)

定义新属性

//默认是不可枚举的(for in打印打印不出来),可:enumerable: true    

//默认不可以修改,可:wirtable:true    

//默认不可以删除,可:configurable:true

   let person = {}
let personName = 'lihua'

//在person对象上添加属性namep,值为personName
Object.defineProperty(person, 'namep', {
    //但是默认是不可枚举的(for in打印打印不出来),可:enumerable: true
    //默认不可以修改,可:wirtable:true
    //默认不可以删除,可:configurable:true
    get: function () {
        console.log('触发了get方法')
        return personName
    },
    set: function (val) {
        console.log('触发了set方法')
        personName = val
    }
})

//当读取person对象的namp属性时,触发get方法
console.log(person.namep)

//当修改personName时,重新访问person.namep发现修改成功
personName = 'liming'
console.log(person.namep)

// 对person.namep进行修改,触发set方法
person.namep = 'huahua'
console.log(person.namep)

可以添加get set方法来监听属性变化

如果要监听多个属性,就只能进行遍历,但是会导致栈溢出,在访问person身上的属性时,就会触发get方法,返回person[key],但是访问person[key]也会触发get方法,导致递归调用,最终栈溢出。,我们需要设置一个中转Obsever,来让get中return的值并不是直接访问obj[key]

滚动条样式修改

overflow:auto 是根据超出不超出显示滚动条,scroll是一直显示滚动条

设置overflow:auto的时候注意,会上下左右都能移动,如果只要横向,只用设置overflow-x:auto就行了

innerbox是有滚动条的元素

.innerbox {
width: 100px;
height: 300px;
overflow-x: hidden;
overflow-y: auto;\
}
/*滚动条样式*/
.innerbox::-webkit-scrollbar {/*滚动条整体样式*/
width: 4px;     /*高宽分别对应横竖滚动条的尺寸*/
height: 4px;
}
.innerbox::-webkit-scrollbar-thumb {/*滚动条里面滚动条*/
border-radius: 5px;
\-webkit-box-shadow: inset 0 0 5px rgba(0,0,0,0.2);
background-color: rgba(0,0,0,0.2);
}
.innerbox::-webkit-scrollbar-track {/*滚动条里面轨道(背景)*/
\-webkit-box-shadow: inset 0 0 5px rgba(0,0,0,0.2);
border-radius: 0;
background-color: rgba(0,0,0,0.1);

隐藏滚动条 .demo::-webkit-scrollbar {   width: 0 !important; }

localStorage,sessionStorage

localStorage生命周期是永久,这意味着除非用户显示在浏览器提供的UI上清除localStorage信息,否则这些信息将永远存在。

sessionStorage生命周期为当前窗口或标签页,一旦窗口或标签页被永久关闭了,那么所有通过sessionStorage存储的数据也就被清空了。

不同浏览器无法共享localStorage或sessionStorage中的信息。相同浏览器的不同页面间可以共享相同的 localStorage(页面属于相同域名和端口),但是不同页面或标签页间无法共享sessionStorage的信息。这里需要注意的是,页面及标 签页仅指顶级窗口,如果一个标签页包含多个iframe标签且他们属于同源页面,那么他们之间是可以共享sessionStorage的。

calc无效

1. height:calc(100vh-60);  无效

2.height:calc(100vh-60px); 无效

3.height:calc(100vh - 60px);  终于起效

总结calc()使用

  • 必须加上单位
  • 必须在运算符左右用空格隔开
删除字符串中的字符

使用replace()方法

string.replace('characterToReplace', '');

使用splice()方法

// 删除第一个字符
string.splice(1);

// 删除最后一个字符
string.splice(0, string.length - 1);
给默认参数之外添加参数
<template slot="demo"  slot-scope="text, record, index" >
  <a-date-picker  @change="dateChange.call(this, ...arguments, 自定义参数1, 自定义参数2)" />
</template>

// or 

<template slot="demo"  slot-scope="text, record, index" >
  <a-date-picker  @change="dateChange.apply(this, [...arguments, 自定义参数1, 自定义参数2])" />
</template>

对应的事件代码:

dateChange(date, dateString, 自定义参数1, 自定义参数2) {
  // ...
}
CSS3变量

css3变量可以放到root里面,html文档任何地方都能访问,也可以放在class里面,其他class访问不到,并且自定义属性值可以继承父元素,如果本身未定义自定元素的话

css3备用值,background-color: var(--my-var, var(--my-background, pink));当自定义属性无效时,可以使用第二个备用值,备用值可以使用逗号,从第一个逗号到最后的全部内容,都当做备用值的一部分

修改css3值

getPropertyValue //获取元素的某个css属性值
setProPerty  //设置元素的css属性
removeProperty  //移除元素的css属性

例如:
document.documentElement.style.getPropertyValue('font-size') //获取字体大小js
document.documentElement.style.getPropertyValue('--font-size') //也可以是css变量
这种方式只能获得内联样式,如果无内联样式则获取到的值为空,与document.documentElement.style.fontSize相同,都是只能获取内联样式属性值

getComputedStyle(node) //此方法获取元素的所有样式表,是一个大对象

//如果getPropertyValue方法用在此处则可以获取到元素的真实样式
//(即哪个样式的权重高,生效的样式,获取的就是那个值)
getComputedStyle(document.documentElement).getPropertyValue()
vue2中使用css变量
<template>
  <!-- 如果要该组件都可以使用,则必须放置在根元素下 -->
  <div class="hello" :style="styleVar">
    <div class="child-1">I am Child 1</div>
    <div class="child-2">I am Child 2</div>
    <div @click="onClick">Change Red TO Blue</div>
  </div>
</template>

<script>
export default {
  name: "HelloWorld",
  props: {
    msg: String,
  },
  data() {
    return {
      styleVar: {
        "--colorBlue": "blue",
        "--colorRed": "red",
        "--fontSize": "30px",
        "--fontSizeTest": "30px",
      },
    };
  },
  methods: {
    onClick() {
      this.styleVar["--fontSizeTest"] = "40px";
    },
  },
};
</script>

<!-- Add "scoped" attribute to limit CSS to this component only -->
<style scoped>
.child-1 {
  color: var(--colorBlue);
  font-size: var(--fontSize);
}
.child-2 {
  color: var(--colorRed);
  font-size: var(--fontSizeTest);
}
</style>

image.png

权限控制

简单讲下我司的系统(大型B端后台):
1、权限控制: 根据不用用户编辑不同的页面权限 - 精确到按钮权限、甚至某一个文案的权限;
2、用户登录(刷新)后会请求权限接口,分为menulist 和 codelist;
3、menuList返回的参数较多,可用于动态路由等等参数,当前也包含当前路由是否有权限或 无权限菜单接口干脆就不返回;
4、codelist 就是个string []格式,如某个权限为"ec#app.store.stylize.shareSetting" , 当然,按钮权限也可以同时满足多个codelist 条件控制,(个人认为接口返回json格式,权限 "ec#app.store.stylize.shareSetting" 为key更优);
5、利用前端localhost或其他简单做一下权限缓存;
6、然后在页面渲染的时候,判断下页面路径、按钮、或者某个字段的权限是否展示。

npm run build 之后生成的index.html页面打开为空白

image.png 这个报错很显然是找不到文件,因此我猜可能是构建项目过程中路径出错了。打开index.html的源码来看,发现路径用的是’/‘,但index.html文件和js文件夹是同级目录,如果从index.html进入到js文件夹内的文件,需要用’./'。 也就是说,对比dist文件夹结构可以看到资源路径的引入是错误的,应该用’./‘而不是’/'。

结合了百度,发现一个有效的解决方案:

在项目根目录下创建一个vue.config.js文件,写入:

module.exports = {
    publicPath: './',
}

然后再次npm run build进行打包。打包完成后,再打开dist文件夹内的index.html文件,就可以正常显示项目了。

手机浏览器看报错信息

在页面加入两行代码即可搞定:

<script src="*https:* //cdn.jsdelivr.net/npm/eruda"></script>  

<script>eruda.init();</script>
修改placeholder 颜色

::placeholder

.ant-time-picker-input[disabled]::placeholder {
  color:red; //颜色
  visibility: hidden !important; // 隐藏
}
lib包打包

npm run lib

npm publish

js异常处理

可以抛异常 throw new Error('异常信息') 然后处理异常

    try {
      foo()
      console.log('我不会继续执行')
    } catch (error) {
      console.log('error:', error.message)
    } finally {
      console.log('我无论是否有异常抛出都会执行')
    }
updated 生命周期

不管是通过父组件props接收的数据还是组件本身data里的数据,只要在页面中使用这些数据,这些数据变化,都会触发组件的updated生命周期;如果数据不在页面中使用,那么不会触发组件的updated生命周期。

grdi设定每格占位

grid-column-start: span 3 设定元素占3小格

页面自适应
   function () {
      // 当前浏览器字体大小
      const resizeEvent = 'orientationchange' in window ? 'orientationchange' : 'resize'
      const reCalc = function () {
        // 根节点
        const documentElement = document.documentElement
        // 当前屏幕宽度,通过获取当前浏览器宽度来修改font-szie,以此来修改rem大小
        const clientWidth = documentElement.clientWidth
        if (!clientWidth) return
        documentElement.style.fontSize = (clientWidth < 1920 ? clientWidth : 1920) + 'px'
      }
      reCalc()
      if (!document.addEventListener) return
      // false为冒泡,true为捕获
      window.addEventListener(resizeEvent, reCalc, false)
      // 当一个 HTML 文档被加载和解析完成后,DOMContentLoaded 事件便会被触发。
      document.addEventListener('DOMContentLoaded', reCalc, false)
    }
    
    css 里面
  .fontSize (@size) {
  // 因为1rem 等于html的font-size大小,在上面被修改成了屏幕实际宽度大小,所以字体大小是根据实际宽度来的
    font-size: calc(@size / 1920);
  }

.more {
  .fontSize(13rem);
}
    
yarn build打包出现map文件

运行:npm run build,随后会生成一个dist文件夹

项目打包之后,代码都是经过压缩加密的,如果运行报错,输出的错误信息无法准确得知是哪里的代码报错

有了.map文件就可以像未加密的代码一样,准确的输出是哪一行哪一列有错,但是.map文件通常比较大,如果不需要这个文件可以去掉

说白了,.map文件可以指出哪里报错

在vue.config.js文件中可以配置,以便打包的时候不会生出.map文件

配置以下代码就不会生出.map文件

productionSourceMap:false

此时在进行打包的时候就不会生出.map文件

v-for从1开始,不是从0开始
使用axios添加请求头的几种方式

传入配置参数

单次请求

const config = {
  headers:{
    header1: value1,
    header2: value2
  }
};
const url = "api endpoint";

const data ={
  name: "Jake Taper",
  email: "taperjake@gmail.com"
}
//GET
axios.get(url, config)
  .then(res=> console.log(res))
  .catch(err=> console.log(err))

//POST
axios.post(url, data, config)
  .then(res => console.log(res))
  .catch(err => console.log(err))

多次请求

//给所有请求添加请求头
axios.defaults.headers.common['Authorization'] = `Bearer ${localStorage.getItem('access_token')}`;

//给所有POST请求添加请求头
axios.defaults.headers.post['Authorization'] = `Bearer ${localStorage.getItem('access_token')}`;

2.创建axios实例

let reqInstance = axios.create({
  headers: {
    Authorization : `Bearer ${localStorage.getItem(‘access_token’)}`
    }
  }
})
reqInstance.get(url);

3.使用axios拦截器

axios.interceptors.request.use(
  config => {
    config.headers['Authorization'] = `Bearer ${localStorage.getItem('access_token')}`;
        return config;
    },
    error => {
        return Promise.reject(error);
    }
);

使用拦截器的另一个好处是可以设置token过期时自动刷新token:

const refreshToken= ()=>{
  // gets new access token
}

// 403请求时自动刷新access token
axios.interceptors.response.use(
    response => {
        return response;
    },
    error => {
        if(error.response.status == 403){
            refreshToken()
        }
    }
);
less
后代

image.png

子代选择器以及伪元素和伪类选择器

可以使用 & 来代替父元素

image.png

deep scoped

scoped是什么意思呢?其实就是给每条样式的 最后面 加上一个属性选择器[哈希值]

image.png

deep 能够让被scoped限制住的样式属性穿透到子组件

页面渐变

如果页面有收起展开按钮,可以给加一个渐变

transition: width 300ms linear;

表示元素的宽度将在300毫秒内线性变化。

解释如下:

  • transition: 是CSS过渡效果属性,可以将一个元素的某个属性从一种样式逐渐改变为另一种样式。
  • width: 是你要改变的元素的宽度属性。
  • 300ms: 是过渡效果的持续时间,即从开始变化到变化完成的时间。
  • linear: 是过渡效果的速度曲线,表示变化的速度在全程中保持一致。

请注意,为了让这个过渡效果能够起作用,你需要确保这个元素的宽度是可改变的,例如通过JavaScript或者其它CSS属性(如max-widthmin-width等)来控制。同时,需要至少有两种状态(即元素的宽度需要有变化),否则这个过渡效果不会有任何效果。

添加下划线,上划线,删除线

text-decoration: none:默认值,表示没有任何装饰,文本正常显示。

underline:在文本下方添加一条线。

overline:在文本上方添加一条线。

line-through:在文本中间添加一条线,通常用于表示删除或取消的文本。

blink:使文本闪烁。需要注意的是,一些浏览器(如IE、Chrome或Safari)不支持这个值。

下划线距离文本间隔 text-underline-offset: 5px

vue项目中axios请求数据的时候请求失败,出现跨域问题。

blog.csdn.net/zmx729618/a… 跨域文章

axios.defaults.withCredentials = true

这一条是要求在请求头里带上cookie,如果前端配置了这个withCredentials=true,后段设置Access-Control-Allow-Origin不能为 " * ",必须是源地址

文字不可选中
\-webkit-user-select: none;
/\* Safari */
\-moz-user-select: none;
/* Firefox */
\-ms-user-select: none;
/* IE10+/Edge */
user-select: none;
/* 标准语法 \*/
echarts二次渲染有残留问题

比如嵌套饼图切换成普通饼图,有嵌套残留 在echarts进行 setOption()的方式时,有3个参数,其中第一个为要设定的option内容,而第2第3个是对setOption的参数进行配置:

  • chart.setOption( option , notMerge , lazyUpdate );
  • option:图表的配置项和数据
  • notMerge:可选,是否不跟之前设置的option进行合并,默认为false,即合并。(这里是导致二次渲染不成功的根本
  • lazyUpdate:可选,在设置完option后是否不立即更新图表,默认为false,即立即更新。

问题解决:

​ 在渲染图片时,将notMerge参数配置为true,(默认是false),问题解决

在 Vue 中,slot(插槽)是一种高级功能,允许你自定义组件的某一部分内容。通过使用插槽,你可以让组件更加灵活和可重用。

Vue 提供了两种插槽:默认插槽(匿名插槽)和具名插槽。

git拉代码无效路径

git config --global http.sslVerify false

1. 默认插槽(匿名插槽)

默认插槽没有名字,它会在组件的 <slot> 标签的位置被渲染。

组件定义

子组件
<template>
  <div>
    <h2>我是组件的标题</h2>
    <slot>默认内容</slot> <!-- 这里是默认插槽的位置 -->
  </div>
</template>

<script>
export default {
  name: 'MyComponent'
}
</script>

使用组件

父组件
<template>
  <MyComponent>
    <p>这是默认插槽的内容。</p>
  </MyComponent>
</template>

<script>
import MyComponent from './MyComponent.vue'
export default {
  components: {
    MyComponent
  }
}
</script>

2. 具名插槽

具名插槽有一个名字,你可以在组件内部使用 <slot name="xxx"></slot> 来定义它,然后在父组件中使用 <template v-slot:xxx> 或简写为 <#xxx> 来指定内容。

组件定义

<template>
  <div>
    <h2>我是组件的标题</h2>
    <slot name="header"></slot> <!-- 具名插槽 header -->
    <slot></slot> <!-- 默认插槽 -->
    <slot name="footer"></slot> <!-- 具名插槽 footer -->
  </div>
</template>

<script>
export default {
  name: 'MyComponent'
}
</script>

使用组件

<template>
  <MyComponent>
    <template v-slot:header>
      <p>这是 header 插槽的内容。</p>
    </template>
    <p>这是默认插槽的内容。</p>
    <template v-slot:footer>
      <p>这是 footer 插槽的内容。</p>
    </template>
  </MyComponent>
</template>

<script>
import MyComponent from './MyComponent.vue'
export default {
  components: {
    MyComponent
  }
}
</script>

注意:在 Vue 3 中,v-slot 的语法有所变化,可以简写为 #,如 <#header>。但在 Vue 2 中,你需要使用完整的 v-slot:header 语法。

插槽从父组件传到孙组件

image.png

数组所有值异步操作
// 异步过滤函数
async function asyncFilter(array, predicate) {
    const results = await Promise.all(array.map(predicate));

    return array.filter((_value, index) => results[index]);
}

// 示例
async function isOddNumber(n) {
    await delay(100); // 模拟异步操作
    return n % 2 !== 0;
}

async function filterOddNumbers(numbers) {
    return asyncFilter(numbers, isOddNumber);
}

filterOddNumbers([1, 2, 3, 4, 5]).then(console.log); // 输出: [1, 3, 5]

在JavaScript中,Array.prototype.map() 方法通常用于创建一个新数组,其结果是该数组中的每个元素是调用一次提供的函数后的返回值。当你将一个异步函数(如返回Promise的函数)作为map()的参数时,map()函数会立即执行这个函数,但因为它不会等待Promise解决(即不会等待异步操作完成),所以map()的返回值会是一个包含所有Promise对象的数组。

在你提供的asyncFilter函数中,map()函数被用来对array中的每个元素调用predicate函数,这里predicate是一个异步函数。重要的是,map()本身不会等待这些Promise解决;它只是创建了它们并返回了一个Promise数组。

javascript复制代码
	const results = await Promise.all(array.map(predicate));

这一行是关键。Promise.all()方法接受一个Promise的iterable(例如数组)作为输入,并返回一个Promise,该Promise在所有给定的Promises都被解决时解决,并且解决结果是一个数组,该数组包含所有输入Promises的解决值(按相同的顺序)。

因此,await Promise.all(array.map(predicate))会等待所有由map()创建的Promise都解决,并将解决的值收集到一个新的数组中,这个数组被赋值给results

然后,array.filter((_value, index) => results[index])部分使用filter()函数来根据results数组中的值(即每个Promise的解决结果)来过滤原始数组array。这里,_value参数实际上在过滤逻辑中没有被使用,因为results[index]已经包含了过滤决策所需的所有信息(即每个元素是否满足predicate条件)。

需要注意的是,虽然map()里直接“丢”方法(即传递一个函数)是常见的做法,但在处理异步函数时需要特别小心,确保你正确地处理了异步操作的结果。在这个例子中,通过Promise.all()来实现了这一点。

此外,如果predicate函数不是异步的(如isOddNumber),那么使用map()Promise.all()可能会引入不必要的性能开销,因为你可以直接使用filter()来处理同步逻辑。但在需要处理异步谓词(predicate)的情况下,这种方法是非常有用的。

domainName: selectedData.map(item => item.title)?.join(';') map里面其实是一个匿名函数

sessionStorage 不能在多个窗口或标签页之间共享数据,但是当通过 window.open 或链接打开新页面时(不能是新窗口),新页面会复制前一页的 sessionStorage。 sessionStorage 就是会话级别的存储(关键在于会话)

如何定义一个会话?
在A页面点击超链接或者在控制台window.open打开页面B,都是属于当前页面的延续,属于一个会话。
在A页面已经打开的前提下,然后在新tab打开同域页面C,此时C和A页面无直接关系,不属于一个会话。

sessionSession并不是共享的,而是复制的。

也就是说,B页面打开的时候复制了A页面的sessionSession,仅仅是复制
此时,无论修改A页面的sessionStorage还是修改B页面的SessionStorage,都不会彼此影响。
也就是说两个页面存储的SessionStorage数据都不会同步变化(各自都是自己的存储,存储独立存在)。

try catch执行时间

try catch是同步的,无法捕捉异步的报错,例如 setTimeout promise(promise有自带的catch)

image.png

如果想捕捉到promise报错,必须给promise加上 await

不加async await时,promise执行完,在等待执行promise中的回调时,try catch已经执行完了; 加了async await时,try catch需要等待promise中的回调执行完才能往下走,所以被catch捕获到异常

日期过滤调休日和周末节假日

// 引入 moment.js const moment = require('moment');

// 定义一个包含节假日的数组(格式为YYYY-MM-DD) const holidays = [ '2023-10-01', // 示例节假日 // ... 其他节假日 ];

// 定义一个包含调休日的数组(格式为YYYY-MM-DD) const adjustedWorkdays = [ '2023-10-07', // 示例调休日(周末上班) // ... 其他调休日 ];

// 函数:检查给定日期是否是节假日或周末 function isNonWorkingDay(date, holidays, adjustedWorkdays) { const dayOfWeek = date.isoWeekday(); // 1表示星期一,7表示星期日 const isHoliday = holidays.includes(date.format('YYYY-MM-DD')); const isAdjustedWorkday = adjustedWorkdays.includes(date.format('YYYY-MM-DD'));

return (dayOfWeek === 6 || dayOfWeek === 7) && !isAdjustedWorkday || isHoliday; }

// 函数:计算第n个工作日后的日期 function getNthWorkday(startDate, n, holidays, adjustedWorkdays) { let currentDate = moment(startDate); let workdayCount = 0;

while (workdayCount < n) { currentDate = currentDate.add(1, 'days');

if (!isNonWorkingDay(currentDate, holidays, adjustedWorkdays)) {
  workdayCount++;
}

}

return currentDate; }

// 示例使用 const currentDate = moment(); const tenthWorkday = getNthWorkday(currentDate, 10, holidays, adjustedWorkdays); console.log(tenthWorkday.format('YYYY-MM-DD')); // 输出第10个工作日的日期

获取字符串某个位置的值

demo.charAt(0) 获取第0个值

v-show 和 v-if 的区别?

触发生命周期不同。v-show由 false 变为 true 的时候不会触发组件的生命周期;v-if由 false 变为 true 的时候,触发组件的beforeCreatecreatedbeforeMountmounted钩子,由 true 变为 false 的时候触发组件的beforeDestorydestoryed钩子。

通过key值来重新加载组件

在 Vue 里,组件可以通过key值来实现重新加载。在 Vue 中,key是一个特殊的属性,它主要用于给 Vue 一个提示,以便在比较新旧虚拟节点时能更准确、高效地识别节点,进而决定是否复用节点。当key值发生变化时,Vue 会认为这是一个全新的组件实例,从而销毁旧的组件实例并重新创建一个新的。

实现原理

Vue 在进行虚拟 DOM 的 Diff 算法比较时,会根据key值来判断节点是否可以复用。如果key值改变,Vue 会认为当前节点是一个全新的节点,从而销毁旧节点并创建新节点,这样组件就会重新加载。

示例代码

下面是一个简单的示例,展示如何通过改变key值来重新加载组件:

<template>
  <div>
    <!-- 点击按钮时,改变componentKey的值,从而触发组件重新加载 -->
    <button @click="reloadComponent">重新加载组件</button>
    <!-- 动态绑定key属性 -->
    <MyComponent :key="componentKey" />
  </div>
</template>

<script>
import MyComponent from './MyComponent.vue';

export default {
  components: {
    MyComponent
  },
  data() {
    return {
      // 初始化key值
      componentKey: 1
    };
  },
  methods: {
    reloadComponent() {
      // 每次点击按钮,让key值加1
      this.componentKey++;
    }
  }
};
</script>

MyComponent.vue组件代码

<template>
  <div>
    <!-- 显示当前组件的加载时间 -->
    <p>组件加载时间: {{ loadTime }}</p>
  </div>
</template>

<script>
export default {
  data() {
    return {
      // 记录组件的加载时间
      loadTime: new Date().toLocaleString()
    };
  }
};
</script>
  1. App.vue组件

    • 在模板中,有一个按钮和一个MyComponent组件,MyComponent组件绑定了key属性,其值为componentKey
    • data选项中,初始化了componentKey的值为 1。
    • 定义了reloadComponent方法,每次点击按钮时,componentKey的值会加 1。
  2. MyComponent.vue组件

    • data选项中,定义了loadTime变量,用于记录组件的加载时间。

当点击 “重新加载组件” 按钮时,componentKey的值会改变,Vue 会销毁旧的MyComponent实例并重新创建一个新的实例,此时MyComponent组件中的loadTime会更新为新的时间,从而实现组件的重新加载。

箭头函数

const fn = () => {}

// 如果只有一个参数,可以省略括号 const fn = name => {}

// 如果函数体里只有一句return const fn = name => { return 2 * name } // 可简写为 const fn = name => 2 * name // 如果返回的是对象 const fn = name => ({ name: name })

普通函数和箭头函数的区别:

  • 1、箭头函数不可作为构造函数,不能使用new
  • 2、箭头函数没有自己的this
  • 3、箭头函数没有arguments对象
  • 4、箭头函数没有原型对象
find 和 findIndex
  • find:找到返回被找元素,找不到返回undefined
  • findIndex:找到返回被找元素索引,找不到返回-1

const findArr = [
  { name: '科比', no: '24' },
  { name: '罗斯', no: '1' },
  { name: '利拉德', no: '0' }
]

const kobe = findArr.find(({ name }) => name === '科比')
const kobeIndex = findArr.findIndex(({ name }) => name === '科比')
console.log(kobe) // { name: '科比', no: '24' }
console.log(kobeIndex) // 0
await 后面跟foreach

await 后面不能直接跟 forEach 来达到等待其完成后再执行后续代码的目的,这是因为 forEach 本身并不支持异步操作,它不会等待异步操作完成,而是会立即遍历数组并执行回调函数,所以会导致后续代码先执行。下面为你详细分析原因并给出解决办法。

原因分析

forEach 是数组的一个迭代方法,它会为数组中的每个元素同步执行提供的回调函数。当回调函数中包含异步操作时,forEach 不会等待这些异步操作完成,就会继续执行后续代码。当 await 用于这种情况时,它实际上等待的是 forEach 方法的返回值(forEach 返回 undefined),而不是其中的异步操作完成,因此后续代码会先执行。

示例代码

function asyncTask(item) {
    return new Promise((resolve) => {
        setTimeout(() => {
            console.log(`处理 ${item}`);
            resolve();
        }, 1000);
    });
}

async function main() {
    const arr = [1, 2, 3];
    console.log('开始处理数组');
    await arr.forEach(async (item) => {
        await asyncTask(item);
    });
    console.log('数组处理完成');
}

main();

在上述代码中,main 函数里使用 await 和 forEach 尝试处理数组元素。forEach 会立即开始遍历数组,并为每个元素调用 asyncTask 函数。由于 forEach 不会等待异步操作完成,所以 await 不会等待所有异步任务结束,'数组处理完成' 会在所有异步任务完成之前输出。

解决方案

可以使用 for...of 循环来替代 forEach,因为 for...of 循环支持 await,它会等待每次迭代中的异步操作完成后再进行下一次迭代。

function asyncTask(item) {
    return new Promise((resolve) => {
        setTimeout(() => {
            console.log(`处理 ${item}`);
            resolve();
        }, 1000);
    });
}

async function main() {
    const arr = [1, 2, 3];
    console.log('开始处理数组');
    for (const item of arr) {
        await asyncTask(item);
    }
    console.log('数组处理完成');
}

main();

代码解释

在这个改进后的代码中,使用 for...of 循环遍历数组。await 会等待每次迭代中的 asyncTask 异步操作完成后,才会进行下一次迭代。这样,当所有异步操作都完成后,才会输出 '数组处理完成'

但是for of不支持下标索引,需结合entries() 方法会返回一个新的 Array Iterator 对象,该对象包含数组中每个索引的键 / 值对。entries() 方法会遍历稀疏数组中的所有位置,包括那些没有赋值的空位,空位的值会被当作 undefined

const array = ['apple', 'banana', 'cherry'];

// 使用 for...of 循环结合 entries() 方法
for (const [index, value] of array.entries()) {
    console.log(`Index: ${index}, Value: ${value}`);
}

Array Iterator 对象是一个符合迭代器协议的对象。迭代器协议规定,一个对象必须实现一个 next() 方法,该方法返回一个包含 value 和 done 两个属性的对象。

  • value:表示当前迭代位置的值。
  • done:是一个布尔值,true 表示迭代结束,false 表示还有更多元素可以迭代。

创建方式

可以通过数组的一些方法来创建 Array Iterator 对象,常见的方法有:

  • entries() :返回一个包含数组每个索引位置的键值对 [index, value] 的迭代器对象。
const arr = ['a', 'b', 'c'];
const iteratorEntries = arr.entries();
  • keys() :返回一个包含数组所有索引的迭代器对象。
const iteratorKeys = arr.keys();
  • values() :返回一个包含数组所有值的迭代器对象。
const iteratorValues = arr.values();

使用扩展运算符(...

扩展运算符可以将迭代器中的元素展开成一个数组。

const arr = ['apple', 'banana', 'cherry'];
const iterator = arr.values();
const newArr = [...iterator];
console.log(newArr); // 输出: ['apple', 'banana', 'cherry']

通过上述方式,你可以方便地使用 Array Iterator 对象来遍历数组元素。

可选链作用于数组下标判断

可以,可选链操作符(?.)可以用于数组的下标判断,以此来确定数组中指定位置的元素是否存在。下面为你详细介绍使用方法和示例。

语法和原理

可选链操作符允许在访问对象属性或调用方法时,在引用为 null 或 undefined 的情况下不会抛出错误,而是直接返回 undefined。对于数组来说,我们可以将其看作是一种特殊的对象,数组的下标就是对象的属性名,所以可选链操作符同样适用。

在 JavaScript 中,数组下标是从 0 开始的,所以第二个元素的下标为 1。以下是使用可选链操作符判断数组第二个元素是否存在的代码示例:

// 示例数组
const arr1 = [10, 20, 30];
const arr2 = [10];
const arr3 = [];

// 使用可选链操作符判断数组第二个元素是否存在
const element1 = arr1?.[1];
const element2 = arr2?.[1];
const element3 = arr3?.[1];

console.log('arr1 的第二个元素:', element1); 
console.log('arr2 的第二个元素:', element2); 
console.log('arr3 的第二个元素:', element3); 
  • arr1 数组arr1 包含三个元素,使用 arr1?.[1] 时,由于 arr1 存在且下标 1 对应的元素(即第二个元素)为 20,所以 element1 的值为 20
  • arr2 数组arr2 只包含一个元素,使用 arr2?.[1] 时,虽然 arr2 存在,但下标 1 对应的位置没有元素,所以 element2 的值为 undefined
  • arr3 数组arr3 是一个空数组,使用 arr3?.[1] 时,由于 arr3 存在但没有下标为 1 的元素,所以 element3 的值为 undefined

为了让判断结果更具可读性,你还可以结合空值合并操作符(??)来为 undefined 或 null 情况提供默认值,示例如下:

const arr = [10];
const element = arr?.[1]?? '元素不存在';
console.log('数组的第二个元素:', element); 

在这个示例中,当数组的第二个元素不存在时,element 的值会被设置为 '元素不存在'

||= 和 &&=
或等于(||=)   a ||= b 等同于 a || (a = b);

且等于(&&=)   a &&= b 等同于 a && (a = b);

属性选择器

在 CSS 里,属性选择器的确要用方括号 [] 包裹起来。属性选择器能够依据元素的属性及其值来选择 HTML 元素。下面为你介绍几种常见的属性选择器及其用法:

 [attribute]

选择带有指定属性的元素。

/* 选择所有带有 title 属性的元素 */
[title] {
    color: blue;
}

 [attribute=value]

选择属性值精确等于指定值的元素。

css

/* 选择所有 lang 属性值为 "en" 的元素 */
[lang="en"] {
    font-style: italic;
}

 [attribute^=value]

选择属性值以指定值开头的元素。

/* 选择所有 class 属性值以 "btn-" 开头的元素 */
[class^="btn-"] {
    background-color: lightgray;
}

 [attribute$=value]

选择属性值以指定值结尾的元素。

/* 选择所有 src 属性值以 ".jpg" 结尾的元素 */
[src$=".jpg"] {
    border: 1px solid black;
}

 [attribute*=value]

选择属性值包含指定值的元素。

/* 选择所有 href 属性值包含 "example" 的元素 */
[href*="example"] {
    text-decoration: underline;
}
监听元素尺寸变化,区别去resize 一般用于大屏
const element = document.getElementById('your-element');
const observer = new ResizeObserver(entries => {
  for (let entry of entries) {
    const { width, height } = entry.contentRect;
    console.log(`Element size changed to: ${width}px x ${height}px`);
  }
});

observer.observe(element);
背景图亮度变化
/* 基础样式 */
.container {
    background-image: url('example.jpg');
    background-size: cover;
}

/* 应用亮度滤镜 */
.brightened {
    filter: brightness(1.2); /* 亮度提升20% */
}

.darkened {
    filter: brightness(0.7); /* 亮度降低30% */
}
大屏动画效果
    @keyframes fadeInLeft {
      from {
        opacity: 0;
        transform: translateX(-20px);
      }

      to {
        opacity: 1;
        transform: translateX(0);
      }
    }

    @keyframes fadeIn {
      from {
        opacity: 0;
      }

      to {
        opacity: 1;
      }
    }

    @keyframes fadeInRight {
      from {
        opacity: 0;
        transform: translateX(20px);
      }

      to {
        opacity: 1;
        transform: translateX(0);
      }
    }

    .leftBox {
      animation: fadeInLeft 1s ease forwards;
      opacity: 0;
    }

    .centerBox {
      animation: fadeIn 1s ease 0.3s forwards;
      opacity: 0;
    }

    .rightBox {
      animation: fadeInRight 1s ease 0.6s forwards;
      opacity: 0;
    }
扩展组件的方法

1、mixins

2、slots

<!-- MyComponent.vue -->
<template>
  <div>
    <slot name="user" :user="userInfo" :role="roleInfo"></slot>
  </div>
</template>
<script>
export default {
  data() {
    return {
      userInfo: {
        name: '张三',
        age: 25
      },
      roleInfo: {
        name: '张三',
        age: 25
      }
    }
  }
}
</script>

<template>
  <MyComponent>
  // 另一种写法 #user="{user,rule}"
    <template #user="slotProps"> // slotProps是个对象,包含子组件传过来的参
      <p>姓名:{{ slotProps.user.name }}</p>
      <p>年龄:{{ slotProps.role.age }}</p>
    </template>
  </MyComponent>
</template>

3、 extend

1. Vue.extend() 的作用

Vue.extend() 是 Vue 构造函数的一个静态方法,它接收一个组件选项对象(比如 axUploadModal),返回一个新的子类构造函数(可以理解为 “组件类”)。

这个新的构造函数继承了 Vue 的所有功能,但预先包含了传入的组件选项(数据、方法、模板等)。后续可以通过 new 关键字创建该类的实例,每个实例都是一个独立的 Vue 组件实例。 个人理解是把一个vue组件变成了动态创建多个独立实例,每个实例有独立的状态(数据,方法)

  1. 这里的 axUploadModal 是什么?

axUploadModal 是导入的组件选项对象(通常来自单文件组件 .vue),它包含了该组件的所有配置:

  • template/render:组件的结构
  • data:组件内部数据(比如控制弹窗显示的 visible
  • methods:组件方法(比如 getUploadedFileList
  • props/computed/ 生命周期钩子等

后续使用:实例化组件

有了 AxUploadModal 这个构造函数后,就可以通过 new 创建实例:

// 创建组件实例,传入参数(会合并到组件的 data 中)
const instance = new AxUploadModal({
  data: { title: '上传文件' } // 这里的参数会覆盖组件原 data 中的同名属性
})

// 手动挂载并插入 DOM
instance.$mount() // 生成 DOM 节点(未插入页面)
document.body.appendChild(instance.$el) // 插入到页面中显示

总结:Vue.extend(axUploadModal) 的核心是将组件配置转换为可实例化的 “组件类”,为后续动态创建、挂载组件提供了基础,这是实现全局弹窗、动态加载组件的常用手段(类似 Element UI 的 this.$alert 实现原理)。

vue绑定事件带不带()的区别
  • 1. @click="init()"(带括号)

  • 这是显式调用函数的写法,相当于直接执行 init() 函数

  • 特点:

    • 可以主动传递自定义参数,例如 @click="init(id, name)"
    • 如果需要用到事件对象 $event,必须显式传入:@click="init($event)"
    • 即使函数有默认参数(如事件对象),如果没显式传递,函数内部无法获取到 $event
  • 2. @click="reset"(不带括号)

  • 这是绑定函数引用的写法,Vue 会自动将事件对象 $event 作为参数传递给函数

  • 特点:

    • 函数会自动接收事件对象 $event,例如: methods: { reset(e) { console.log(e) // 这里能获取到点击事件对象 } }

    • 不能直接传递自定义参数(如果需要传参,必须用带括号的写法)

总结对比

写法函数参数默认值能否自定义传参适用场景
@click="init()"无(需显式传 $event需要传递自定义参数时
@click="reset"自动接收事件对象 $event不能只需要事件对象或无参数时

有时候e可能输出undefined,这是因为如果是自定义组件,内部可能没有把原生点击事件传递出来,this.$emit('click'),这种情况下,父组件接收不到事件对象 e,所以 reset(e) 中的 e 会是 undefined this.$emit('click', e) 传递事件对象给父组件,或者使用 .native 修饰符直接绑定原生点击事件(仅 Vue 2 支持):

  <!-- 绑定原生事件,绕过组件内部的事件处理 -->
  <ax-button @click.native="reset">重置</ax-button>
  
  
内部元素disabled,导致点击事件无效
<template>
  <div class="inputParent" style="position: relative;">
    <!-- 双击覆盖层 - 始终能接收双击事件 -->
    <div 
      class="dblclick-overlay"
      @dblclick="dblclick"
      v-if="currentInput === 'a-textarea' && propsOptions.disabled"
    ></div>
    
    <component 
      :disabled="propsOptions.disabled"
    >
     // 内部组件
    </component>
    
    <a-modal 
      v-model="visible" 
      :title="propsOptions.label" 
      :footer="null" 
      :width="800"
    >
     // 点击弹窗
    </a-modal>
  </div>
</template>

<style lang="less" scoped>
.inputParent {
  position: relative;
}

.dblclick-overlay {
  position: absolute;
  top: 0;
  left: 0;
  right: 0;
  bottom: 0;
  z-index: 1; /* 确保在输入框上方但不遮挡其他交互元素 */
  pointer-events: auto; /* 确保能接收鼠标事件 */
}

</style>
    1. 根本问题:禁用元素的 “事件吞噬” 特性

当输入框(component 渲染的 a-input/a-textarea)设置 disabled=true 时,浏览器会默认让该元素完全拦截所有鼠标事件(包括 dblclickclickmouseover 等),且这些事件不会向上冒泡到父容器。
这就导致你原本绑定在父容器 inputParent 上的 dblclick 事件,只有在点击父容器的 “空白边缘”(未被禁用输入框覆盖的区域)时才能触发 —— 因为点击输入框区域时,事件直接被禁用的输入框 “吞掉” 了,根本传不到父容器。

    1. 解决方案:添加 “事件代理覆盖层”

我们新增的 .dblclick-overlay 元素,本质是一个漂浮在禁用输入框上方的 “透明事件接收器” ,它的作用是:

  • 位置覆盖:通过 position: absolute + top/left/right/bottom: 0,让覆盖层的尺寸与父容器完全一致,刚好 “盖” 在禁用的输入框上方(但不影响视觉,因为它没有背景和边框)。

  • 事件接管:覆盖层本身没有 disabled 状态,能正常接收 dblclick 事件;我们把原本绑定在父容器的 dblclick 逻辑,转移到覆盖层上 —— 这样用户双击输入框区域时,实际是点击了覆盖层,事件能被正常捕获。

  • 条件显示:通过 v-if="currentInput === 'a-textarea' && propsOptions.disabled" 控制,只在 “文本域类型” 且 “禁用状态” 下显示覆盖层,避免影响其他正常状态(如可编辑输入框)的交互。

    1. 关键细节:z-index 层级控制

覆盖层的 z-index: 1 是核心细节:

  • 它的层级高于下方的输入框(默认 z-index: auto,相当于 0),所以能优先接收鼠标事件;
  • 同时层级不会过高(比如不设置 z-index: 999),避免遮挡输入框的 prefix/suffix 等内置元素,保证 UI 完整性。
父元素设置了margin-top: -18px;子元素会有一部分点不到

给父元素设置position: relative, 当父元素设置 position: relative 后,子元素原本点不到的区域变得可点击,核心原理与CSS 定位上下文对 “点击区域(hitbox)” 的影响有关。具体来说:

  1. 默认情况下(父元素无定位):

父元素未设置 position 时,其 “点击区域” 严格限制在自身的盒模型边界内(content + padding + border)。

  • 当父元素设置 margin-top: -18px 向上偏移后,它在文档流中的 “占位空间” 被压缩(相当于顶部被 “切掉” 18px)。
  • 若子元素向上延伸超出了父元素的原始盒模型边界(比如子元素高度较高,或有向上的偏移),超出的部分会脱离父元素的 “点击范围”—— 即使视觉上可见,也不会触发父元素或子元素的点击事件(因为这部分不在父元素的盒模型边界内)。
  • 此外,若父元素的父元素(祖父元素)有 overflow: hidden,超出部分还会被裁剪,进一步导致点击失效。
  1. 父元素设置 position: relative 后:

position: relative 会让父元素成为一个 “定位容器(containing block)”,此时其 “点击区域” 规则发生变化:

  • 父元素的点击区域不再局限于自身的盒模型边界,而是会包含所有子元素(即使子元素超出父元素的视觉边界)
  • 原因是:定位容器会 “管理” 其内部所有子元素的布局和交互范围,无论子元素是否超出父元素本身的尺寸,只要子元素没有被其他更高层级的元素(z-index 更高)遮挡,其点击事件都会被正常捕获。
  • 同时,position: relative 不会改变父元素在文档流中的占位空间,但会建立一个局部的坐标系,让子元素的定位(如 position: absolute)以父元素为基准,间接避免了子元素因父元素偏移而 “脱离管理” 的问题。

简单总结:

position: relative 给父元素 “赋予了管理子元素交互范围的能力”,让父元素的点击区域从 “自身盒模型边界” 扩展到 “包含所有子元素的范围”,从而解决了子元素超出部分无法点击的问题。

props绑定值导致弹窗异常

仅修改子组件的 props(key 不变)→ 不会触发 created/mounted,也不会 “重建” 子组件,只会触发 “更新渲染”

  • ✅ 会发生:子组件执行 updated 钩子、重新渲染模板(更新视图);
  • ❌ 不会发生:created/mounted 钩子执行、组件实例销毁 / 重建。

只有当 key 变化,或子组件被 v-iffalsetrue(销毁后重建),created/mounted 才会重新执行。 如果弹窗的 v-model 直接绑定 props 的值,哪怕变化的是其他 props(比如 plantCode/sType),也一定会导致弹窗显隐异常 —— 这是 Vue 单向数据流规则和组件更新机制共同作用的必然结果。

一、核心结论:一定会导致弹窗异常(且是最主要诱因)

哪怕你只是修改了 plantCode/sType 这类和弹窗显隐无关的 props,只要弹窗的 v-model 直接绑定 props(比如 v-model="editDialog1",而 editDialog1 是 props),就会触发弹窗异常,原因如下:

二、为什么会异常?(拆解核心逻辑)

1. 先明确:v-model 的本质(Vue2 自定义组件)

自定义组件的 v-model 等价于:

vue

<!-- 你写的:v-model="editDialog1"(editDialog1 是 props) -->
<!-- Vue 实际解析为: -->
<Invest
  :value="editDialog1"  <!--  props 传给子组件的 value -->
  @input="val => editDialog1 = val"  <!-- 监听子组件的 input 事件试图修改 props -->
/>

而 Vue 规定:props 是单向数据流,子组件绝对不能修改 props(只能由父组件修改)。

2. 其他 props 变化触发更新渲染时,矛盾就爆发了

当你修改 plantCode/sType 时:

  1. 子组件触发更新渲染(执行 updated 钩子、重新解析模板);

  2. Vue 解析模板时发现:v-model 绑定的是只读的 props(editDialog1);

  3. 此时 Vue 的响应式系统会陷入「状态不一致」:

    • 一方面,子组件想通过 v-model 维护弹窗的显隐状态,但 props 是只读的,无法修改;
    • 另一方面,更新渲染时 Vue 会强制对比「模板绑定值」和「props 实际值」,一旦发现不匹配,就会强制重置弹窗的 visible 状态(比如默认设为 true,或继承 props 的初始值);
  4. 最终表现:弹窗毫无征兆地显示 / 隐藏,也就是你遇到的 “异常”。

三、举个通俗的例子帮你理解

把弹窗的 v-model 绑定 props 比作:

  • 你(子组件)想控制一个房间的灯(弹窗显隐),但灯的开关(editDialog1)是房东(父组件)锁死的(props 只读);
  • 你只是换了房间的窗帘(修改 plantCode/sType),但房东(Vue)来检查时发现你碰了锁死的开关(v-model 绑定 props),于是直接把灯的状态重置了(弹窗异常)—— 哪怕你没碰开关,只是换了窗帘。

四、如何彻底避免这种异常?(唯一正确的写法)

核心原则:子组件永远不要把 v-model 直接绑定到 props 上,必须内部维护弹窗状态,步骤如下(针对你的弹窗):

vue

<template>
  <!-- 1. v-model 绑定子组件内部状态 innerVisible -->
  <vxe-modal v-model="innerVisible">
    <!-- 弹窗内容 -->
  </vxe-modal>
</template>

<script>
export default {
  props: {
    // 接收父组件传递的弹窗显隐状态(仅接收,不直接绑定)
    editDialog1: {
      type: Boolean,
      default: false
    },
    plantCode: String,
    sType: Array
  },
  data() {
    return {
      // 2. 内部维护弹窗状态
      innerVisible: this.editDialog1
    };
  },
  watch: {
    // 3. 监听父组件传递的 props,同步到内部状态(单向数据流)
    editDialog1(newVal) {
      this.innerVisible = newVal;
    },
    // 4. 内部状态变化时,通知父组件更新(双向绑定的本质)
    innerVisible(newVal) {
      this.$emit("update:editDialog1", newVal);
    }
  }
};
</script>

父组件使用时,用 .sync 实现双向绑定:

vue

<Invest
  :editDialog1.sync="parentDialogVisible"
  :plantCode="parentPlantCode"
  :sType="parentSType"
/>

总结

  1. 核心结论:弹窗 v-model 绑定 props 值时,任何 props 变化(哪怕是无关的)都会触发弹窗异常 —— 因为违反了 Vue 单向数据流规则,更新渲染时会导致状态不一致;
  2. 根本原因v-model 会试图修改绑定值,但 props 是只读的,更新渲染时 Vue 会强制重置弹窗状态;
  3. 唯一解法:子组件内部维护弹窗显隐状态(如 innerVisible),通过 watch 同步父组件的 props,再通过 $emit 回传状态,绝对不要直接绑定 props 到 v-model

这种写法能保证:哪怕你频繁修改 plantCode/sType,弹窗的显隐状态也只由父组件的 editDialog1 控制,不会再出现任何异常。