分类 技术文章 下的文章

UPDATE 2023-05-04

某些情况下,css 属性inset 不被支持,造成本文介绍的方法失效。此时只要用 lefttop 加上宽高替换 inset 就好了。下面的代码已经修改。


Element Plus 的 Table 组件文档写道

只要在 el-table 元素中定义了 height 属性,即可实现固定表头的表格,而不需要额外的代码。

可是有几个场景能知道 Table 的固定高度?所以文档中的方法不太可行。

尝试使用 :deep() 深度选择器改变 el-table 的样式,使它形成 overflow-y: auto 的区域,就会触发一个非常奇怪的行为:el-table 的宽度会一直慢慢变大!因为看起来是脚本控制的,也就没有精力深究。

最后还是在 CSDN 上找到的办法,使用绝对定位。我的实践,下面的代码是最方便的:

HTML:

<div class="box">
    <div class="menu">菜单之类的顶部区域,不需要可以去掉</div>
    <div class="table-box">
        <el-table class="el-table"></el-table>
    </div>
</div>

CSS:

.box {
    display: flex;
    flex-direction: column;
}

.menu {
    flex: none;
    height: 100px;
}

.table-box {
    flex: auto;
    overflow: hidden;
    position: relative;
}

.el-table {
    position: absolute;
    - inset: 0;
    + left: 0;
    + top: 0;
    + width: 100%;
    + height: 100%;
}

现在,el-table 就会在固定表头的同时,使用内置的 el-scrollbar 来滚动表格内容了。

End

uni-app的坑还是挺多的,这是今天遇到的一个坑,话不多说,上代码:

<view v-if="condition1"> 1 </view>
<template v-else-if="condition2">
    <view> 2 </view>
</template>

上面的代码中,template中的内容即使条件为 true,也可能在真机上不会显示出来。

解决的办法就是不要混用,如果确实要用到 template,那就全部用 template:

<template v-if="condition1">
    <view> 1 </view>
</template>
<template v-else-if="condition2">
    <view> 2 </view>
</template>

对于reactive类型的响应式数据,只需要按照文档中介绍的方法建立侦听器:

const x = reactive({ a: 1 });

watch(() => x.a, val => alert(1));

对于ref类型的数据,则需要多加一个value来获取到正确的侦听对象:

const x = ref({ a: 1 });

watch(() => x.value.a, val => console.log(val););

使用时必须注意,否则无法正确侦听。

对于数组、对象类型的数据,使用reactive比较方便,因为不需要使用.value获取此类数据的值。

element-plus 的图标其实是一个个的组件:

JavaScript:

import { IconName, IconName2 } from '@element-plus/icons-vue';

Template:

<el-icon>
    <IconName/>
</el-icon>

使用 :is 切换组件:

<component :is="bool ? IconName : IconName2"></component>

然而 :is 的值必须是引入的组件,不能是字符串,否则就会渲染成这个样子:

<iconname></iconname>

如果菜单结构单独放在一个文件中,比如 menu.js

export default [
    {
        name: 'Menu 1',
        icon: 'IconName'
    }
]

这里的 icon的值就不能是字符串,需要是一个组件对象的引用。这就需要先在 menu.js 中引入图标组件:

import { IconName } from '@element-plus/icons-vue';

然后将组件传给 icon

export default [
    {
        name: 'Menu 1',
        icon: IconName
    }
]

这样,就能够在组件中正确显示图标了。当然,这时候组件中就不需要再引入图标了。


End

好多小伙伴都看到过这样一段全是由各种括号,以及有限的几个操作符组成的代码,完整代码贴在下面。为了好看,我给它格式化了一下:

[]
[
    (![] + [])[+[]] + 
    ([![]] + [][[]])[+!+[] + [+[]]] +
    (![] + [])[!+[] + !+[]] +
    (!![] + [])[+[]] +
    (!![] + [])[!+[] + !+[] + !+[]] +
    (!![] + [])[+!+[]]
]
[
    (
        [][
        (![] + [])[+[]] +
        ([![]] + [][[]])[+!+[] + [+[]]] +
        (![] + [])[!+[] + !+[]] +
        (!![] + [])[+[]] +
        (!![] + [])[!+[] + !+[] + !+[]] +
        (!![] + [])[+!+[]]] + []
    )[!+[] + !+[] + !+[]] +
    (
        !![] +
        [][(![] + [])[+[]] +
        ([![]] + [][[]])[+!+[] + [+[]]] +
        (![] + [])[!+[] + !+[]] +
        (!![] + [])[+[]] +
        (!![] + [])[!+[] + !+[] + !+[]] +
        (!![] + [])[+!+[]]]
    )[+!+[] + [+[]]] +
    ([][[]] + [])[+!+[]] +
    (![] + [])[!+[] + !+[] + !+[]] +
    (!![] + [])[+[]] +
    (!![] + [])[+!+[]] +
    ([][[]] + [])[+[]] +
    (
        [][(![] + [])[+[]] +
        ([![]] + [][[]])[+!+[] + [+[]]] +
        (![] + [])[!+[] + !+[]] +
        (!![] + [])[+[]] +
        (!![] + [])[!+[] + !+[] + !+[]] +
        (!![] + [])[+!+[]]] + []
    )[!+[] + !+[] + !+[]] +
    (!![] + [])[+[]] +
    (
        !![] +
        [][(![] + [])[+[]] +
        ([![]] + [][[]])[+!+[] +
        [+[]]] +
        (![] + [])[!+[] + !+[]] +
        (!![] + [])[+[]] + (!![] + [])[!+[] + !+[] + !+[]] +
        (!![] + [])[+!+[]]]
    )[+!+[] + [+[]]] +
    (!![] + [])[+!+[]]
]
(
    (![] + [])[+!+[]] +
    (![] + [])[!+[] + !+[]] +
    (!![] + [])[!+[] + !+[] + !+[]] +
    (!![] + [])[+!+[]] +
    (!![] + [])[+[]] +
    (
        ![] +
        [][
        (![] + [])[+[]] +
        ([![]] + [][[]])[+!+[] + [+[]]] +
        (![] + [])[!+[] + !+[]] +
        (!![] + [])[+[]] +
        (!![] + [])[!+[] + !+[] + !+[]] +
        (!![] + [])[+!+[]]
        ]
    )[!+[] + !+[] + [+[]]] +
    [+!+[]] +
    (
        !![] +
        [][(![] + [])[+[]] +
        ([![]] + [][[]])[+!+[] + [+[]]] +
        (![] + [])[!+[] + !+[]] +
        (!![] + [])[+[]] +
        (!![] + [])[!+[] + !+[] + !+[]] +
        (!![] + [])[+!+[]]]
    )[!+[] + !+[] + [+[]]]
)
();

这是一坨什么玩意儿?看得人脑瓜子嗡嗡的。
今天上班第一天,不想干活,只想摸鱼。趁着这个时间娱乐一下,看看这段代码到底是什么东西。

从上面的代码中可以看出来,我已经把它的主要结构展示地非常清晰:

//① ②    ③    ④   ⑤
 [][...][...](...)();

非常明显,第一个[]肯定是提供了一个存在于它的原型链上的 方法 或者 属性 供第二个[]使用。既然是用方括号调用方法或者属性,那么第二个[]里面必定是一个字符串,就像这样:

[]['map'];
//等价于
Array.prototype.map;

当然,第二个[]内的字符串不一定是map,我只是用map举个栗子,具体内容还要进一步分析。

以此类推,第三个[]类似第二个[],举例来说就是这样:

[]['map']['name'] //'map'

上面那行示例代码获取到了函数Array.prototype.mapname属性,但本文讨论的代码究竟获取到了什么东西,还是留待进一步分析。

咱们继续往下看。第四部分是一个小括号()。小括号除了分块外,最大的作用是调用函数。综合上文的分析,第三部分应该是返回一个方法,而第四部分的这个小括号就是调用第三部分返回的方法,小括号里面的内容呢,那肯定就是参数了。

至于第五部分也就是最后一个小括号,很明显是调用方法。既然这样,那么前四部分整体返回的就是一个方法,也就是说,第四部分执行了一个返回方法的方法。

什么方法会返回一个方法呢?继续深入分析。

第一部分[]是一个空的数组实例,除了继承了Array的一些属性和方法,其他没什么可分析的。

第二部分是由5个+操作符连接的语句。下面以第二部分的第一行为例进行分析。
第一行是(![] + [])[+[]],又可以分解成(![] + [])[+[]],其中涉及到的知识就是隐式转换。其中:

![]
//等价于
!''

结果是false,于是

(![] + [])
//等价于
(false + [])
//等价于
(false + '')
//等价于
('false' + '')
//等于
'false'

隐式转换的具体规则网上已经有很多文章了,这里就不重复了。

[+[]]
//等价于
[+'']
//等价于
[0]

所以,第一行最后的结果是

'false'[0]
//等于
'f'

搞了半天,原来是为了获取'f'这个字符。

实际上,分析完上面这行代码,其他的就不言自明了,套路都是一样的,第二部分和第三部分的目的,是为了拼凑'filter''constructor'这两个字符串。

经过分析,整个代码相当于:

[]['filter']['constructor']('alert(1)')();

等于

[].filter.constructor('alert(1)')()

那么为啥拼凑'filter'这个字符串,而不是拼凑'map''sort'……呢?这里必须要好好说道说道,这是因为,'filter'这个字符串,它容易得到……

[].filter.constructor指向的是构造函数Function,利用它,可以使用字符串构造函数,类似于eval(),所以[].filter.constructor('alert(1)')相当于

Function('alert(1)')

执行Function('alert(1)'),就会弹出 1 了:

Function('alert(1)')()

好了,本文到此结束了。


End

0、开始

本篇文章介绍如何使用高德地图 Web API,在地图中显示具有高度和面的自定义多边形标注。

1、准备工作

首先申请高德地图的key。申请完毕之后,新建一个空白的 html 页面,引入需要的资源:

<!DOCTYPE html>
<html>

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>高德地图开发示例</title>
    <style>
        html,
        body,
        #map {
            height: 100%;
        }
        body {
            margin: 0;
        }
    </style>
</head>

<body>
    <div id="map"></div>
</body>

</html>
<script src="https://webapi.amap.com/maps?v=2.0&key=your_key"></script>
<script src="https://webapi.amap.com/loca?v=2.0.0&key=your_key"></script>
<script>
    //我们就在这里编写代码
</script>

2、初始化

初始化地图:

var map = new AMap.Map('map', {
    zoom: 17.6,
    viewMode: '3D', //使用3D模式才能显示3D标注
    pitch: 45, //倾斜角度
    mapStyle: 'amap://styles/darkblue', //使用黑色地图看起来更神秘…
    center: [116.40,39.92], //使用故宫作为地图中心点
    showBuildingBlock: false, //不显示建筑块
    showLabel: false, //不显示街道名之类的标签
});

接着初始化Loca

var loca = window.loca = new Loca.Container({ map });

3、获取多边形数据

接受的数据是标准GeoJSON格式的,我们需要准备GeoJSON格式的数据。格式如下:

{
    "type": "FeatureCollection",
    "features": [
        {
            "type": "Feature",
            "properties": {},
            "geometry": {
                "type": "Polygon",
                "coordinates": [
                    [
                        [
                            lng,
                            lat
                        ],
                        ......
                    ]
                ]
            }
        },
        ......
    ]
}

有4种方法获取数据

  1. 使用阿里的DataV.GeoAtlas地理小工具,不过,对于一些偏远地区,地图上没有道路和建筑,一片空白,没办法按照所需目标的轮廓标注多边形,而卫星地图模式清晰度非常差。
  2. 使用高德地图官方提供的地图快速生成工具,利用“添加标注”功能,把需要标注的区域标注好,然后点击“获取代码”按钮,找到features变量,这里面包含了我们想要的数据,但并不是GeoJSON格式,不过这对于程序员来说非常简单,只需要非常简单的处理,就能获取到上面代码中coordinates部分需要的数组,这里不再赘述。
  3. 使用这个GeoJSON在线编辑器,它的卫星地图比较清晰,但有三个缺点:1是可能需要科学上网,它自带3种地图模式,第一种不能显示。另外,地图缩放后,需要拖动一下地图,才能触发地图的加载。2是标注对于高德地图有所偏移,需要处理,一般是将获取到的经纬度统一加减一些比较小的数值,大约是在小数点后3、4位。3是经度的表示方法可能与高德地图不同,例如-243.63281249999997,需要如下处理:

    360 - Math.abs(-243.63281249999997)

  4. 当然,也可以使用坐标拾取器,一个点一个点地获取和填写coordinates数组。需要注意的是,必须要登录高德地图网站,才能获取精确的位置。

这里使用第二种方法,获取到故宫的外形如下:

{
    "type": "FeatureCollection",
    "features": [
        {
            "type": "Feature",
            "id": 10647,
            "properties": {},
            "geometry": {
                "type": "Polygon",
                "coordinates": [
                    [
                        [
                            116.39156454,
                            39.92287908
                        ],
                        [
                            116.40190578,
                            39.92322019
                        ],
                        [
                            116.40229494,
                            39.91337002
                        ],
                        [
                            116.39189816,
                            39.91294358
                        ],
                        [
                            116.39156454,
                            39.92287908
                        ]
                    ]
                ]
            },
            "bbox": [
                116.39156454,
                39.91294358,
                116.40229494,
                39.92322019
            ]
        }
    ],
    "gid": 7711
}

新建polygon.json,将上面的数据粘贴进去。

4、将数据添加到地图上

代码如下:

var geo = new Loca.GeoJSONSource({
    url: './polygon.json',
});

var pl = new Loca.PolygonLayer({
    zIndex: 120,
    opacity: 0.8,
    shininess: 10,
    hasSide: true,
});

pl.setSource(geo);

pl.setStyle({
    //设置不同面的颜色
    topColor: 'rgba(43,83,144,0.5)',
    sideTopColor: 'rgba(43,83,144,0.5)',
    sideBottomColor: 'rgba(43,83,144,0.5)',
    height: 2000, //物体的高度
    altitude: 0,
});

loca.add(pl);

这段代码非常简单。需要注意的是,高德的文档或者示例代码可能不一致,如果你遇到代码不起作用的情况,需要尝试改为 API 文档中的代码或者示例代码中的代码。

下面是显示效果:

1.png

这是某工厂的建筑:

2.png

除了三维多边形,还可以添加许多东西,详情参考Loca数据可视化API

End

啥也不说,直接上链接:https://developer.mozilla.org/zh-CN/docs/Web/API/structuredClone
如果你懒得点,那只要知道下面的东西就行了。

全局的 structuredClone() 方法使用结构化克隆算法将给定的值进行深拷贝,说人话就是使用了 JavaScript 内部的一种算法深拷贝对象。这个内部的方法其实也是递归,它有一些限制,不能拷贝一些东西,这些距离咱们码农很远,一般不用考虑。如果你很好奇,结构化克隆算法的介绍在这里:

结构化克隆算法:
https://developer.mozilla.org/zh-CN/docs/Web/API/Web_Workers_API/Structured_clone_algorithm

返回值就是你想要的那个东西:新对象:

structuredClone 的语法如下:
structuredClone(value)
structuredClone(value, { transfer })

可选参数 transfer 是啥?是可转移对象数组。啥是可转移对象数组?大概是跟内存有关的数据,例如 arrayBuffer 之类的,一般情况下也用不到。介绍在这里:

可转移对象:
https://developer.mozilla.org/zh-CN/docs/Glossary/Transferable_objects

structuredClone 示例如下:

let obj = { x: 1 };
let newObj = structuredClone(obj);
console.log(obj === newObj); //false

END

svg标签既能设置widthheight属性,又能设置viewBox属性,而svg作为一个 html 标签,也能在 css 或者 style 中设置widthheight。这三者的关系是什么?

1、只有viewBox属性

这里有一个 svg 制作的鸡蛋黄(有点黄,可能是一个咸鸡蛋黄),代码如下:

<style>
    #svg{
        background-color:antiquewhite;
    }
</style>

<svg id="svg" viewBox="0 0 200 200">
    <circle cx="100" cy="100" r="100" fill="darkorange"></circle>
</svg>

效果如下:

1.png

可以看到,半径是100的圆,填满了整个svg,因为svgviewBox的长宽都是200。而svg标签作为 html 元素,又填满了整个页面宽度,并不是200*200的尺寸。

下面我们改变一下viewBox的大小:

...viewBox="0 0 100 100"

效果是这样的:

1 (1).png

viewBox变大一些呢?

...viewBox="0 0 300 300"

效果是这样的:

1 (2).png

现在这个鸡蛋黄已经不能占满整个 svg “画布”了。如果修改鸡蛋黄的圆心坐标为viewBox的中心点,是不是会让鸡蛋黄居中呢?

<circle cx="150" cy="150"...>

确实居中了:

1 (3).png

看来圆的坐标系是viewBox

2、给 svg 标签添加widthheight属性

将代码恢复到最原始的样子,并进行下面的修改:

<svg height="100" width="100"...

变化如下:

1 (4).png

svg 作为 html 元素,长宽都变成了100px,但鸡蛋黄还是占满整个svg标签。看来svgwidthheight属性是用来控制svg作为 html 元素的宽高的,不会影响svg里面的形状等元素的大小。

3、设置 css 宽高

在上面代码的基础上,再添加下面的代码:

...
#svg{
    height: 300px;
    width: 300px;
    ...
}
...

这时候有了下面这种变化:

1.png

svg 元素的宽高变成了 css 中设置的300px

4、总结

根据上面的表现,可以得出下面的结论:

  1. 不提各种坐标,简单理解,可以将viewBox看作是svg画布大小。画布变大了,里面的形状当然就显得小了;如果画布变小了,就有可能将里面的形状裁切。这跟 Photoshop 中的图像 - 画布大小命令表现是一样的。
  2. svg 的widthheight属性设置的是svg这个 html元素 的大小。因为 svg 是矢量图形,所以 svg 的viewBox,包括viewBox中的内容会等比缩放,但并不会变形。当然,widthheight要和viewBox等比例,否则会出现裁切
  3. css 中的widthheight会覆盖 svg 标签本身的widthheight属性,也就是 css 的宽高具有更高的优先级。

在制作svg图形时,可以任意定义viewBox大小,并在其中放置各种形状,这些形状(包括文字等)的定位都相对viewBox。而svg实际应用后的大小,取决于widthheight属性,或者css样式。

是不是非常简单?


End

由于 v-if 在条件为 false 时,会销毁它内部的所有内容,当条件为 true 时,重新渲染内部的内容,所以 v-if 可以用作刷新某些区域用。

例子1

例如,input[type=file] 控件,如果你选择的文件跟上次选择的是同一个,它就不会触发 changeinput 事件,除非你选择一个新的文件。即使大多数情况下,这种情形并不需要特殊处理,因为用户不可能老是选择同一个文件,但总是会遇到特殊的时候。这个时候就可以用 v-if 来销毁原来的 file 控件,并渲染一个新 file,这个新的 file 上面绑定的数据、事件都跟原来一模一样:

<!-- template -->
<input type="file" v-if="someCondition" @change="onChange">

/* Data */
data(){
    return {
        someCondition: true
    }
}

/* Methods */
//file的change事件:
onChange(e){
    //想办法让someCondition经历 false - true 的转换
    this.someCondition = false;
    ...some code
    this.someCondition = true
}

现在,input[type=file] 已经是一个全新的了,即使你选择同一个文件,它也会响应 change 事件。

例子2

element-uitree 控件不是很完善,不提供刷新方法(也许是我没有发现)。如果你修改了某一个子项,然后想要重新加载整个树,就可以使用 v-if 来达到目的。用法跟上面是一样的。

End