CSS
实现卡片多行排列布局
使用align-content: space-between结合nth-child来实现
假设如果永远是第3n的元素是最后一列
<template>
<div class="container">
<div class="crad">1</div>
<div class="crad">2</div>
<div class="crad">3</div>
<div class="crad">4</div>
<div class="crad">5</div>
<div class="crad">6</div>
</div>
</template>
<style lang="less" scoped>
.container{
display: flex;
width:630px;
align-content: space-between;
flex-flow: wrap;
.crad{
height:100px;
background: blueviolet;
width:200px;
margin-bottom: 16px;
margin-right: 16px;
&:nth-child(3n) {
margin-right: 0;
}
}
</style>
}
依然使用align-content: space-between结合nth-child来实现,但是容器宽度可变化,每行卡片数量也可变化
<template>
<div class="card-content-box">
<div class="container">
<div class="crad">1</div>
<div class="crad">2</div>
<div class="crad">3</div>
<div class="crad">4</div>
<div class="crad">5</div>
</div>
</div>
</template>
<style lang="less" scoped>
.card-content-box{
width:100%;
background:red;
overflow: hidden;
.container{
width: calc(100% + 16px); // 加上每个card的margin-right值
display: flex;
align-content: space-between;
flex-flow: wrap;
.crad{
height:100px;
background: blueviolet;
width:200px;
margin-right: 16px;
}
}
}
</style>
使用align-content: space-between结合gap来实现(推荐)
<template>
<div class="container">
<div class="crad">1</div>
<div class="crad">2</div>
<div class="crad">3</div>
<div class="crad">4</div>
<div class="crad">5</div>
</div>
</template>
<style lang="less" scoped>
.container{
width: 100%;
display: flex;
align-content: space-between;
flex-flow: wrap;
gap: 16px
.crad{
height:100px;
background: blueviolet;
width:200px;
}
}
</style>
使用grid来实现(推荐)
<template>
<div class="container">
<div class="crad">1</div>
<div class="crad">2</div>
<div class="crad">3</div>
<div class="crad">4</div>
<div class="crad">5</div>
</div>
</template>
<style lang="less" scoped>
.container{
width: 100%;
display: grid;
grid-template-columns: repeat(auto-fill, 200px);
gap: 16px
.crad{
height:100px;
background: blueviolet;
width:200px;
}
}
</style>
grid-template-columns: repeat()
grid-template-columns: repeat()
是CSS Grid布局中的一个属性值,用于定义网格容器中水平方向(列)的网格轨道(track)结构。具体语法如下:
grid-template-columns: repeat(number, size1, size2, ...);
number
: 表示每行中重复的次数。例如,如果要创建一行3次循环排布,则填入数字 3。[size]...
: 可以是具体的长度值(如像素、百分比、fr 等)、auto
或者是一个 Track-Breadth(网格轨道大小)。这个参数可以有多个,每个值对应一个循环中一列的大小。如果为一个值,则该循环所有列将使用相同的默认大小。
auto-fill
自动填充,让一行或一列中尽可能容纳更多的单元格。
fr
网格容器中可用空间的几等份。拿到剩余部分,看当前网格容器写了几个fr
,就表示均为多少分,根据fr
的数值进行划分。
/* fr - 关键字 - 单位 */
.grid-container {
/* 声明了3列 */
/* 第一列宽度:200px */
/* 第二列宽度:将网格容器剩余空间均分为3等份,第二列占一份 */
/* 第三列宽度:将网格容器剩余空间均分为3等份,第三列占两份 */
grid-template-columns: 200px 1fr 2fr;
grid-auto-rows: 100px;
}
minmax() 函数
minmax()
函数产生一个长度范围,表示长度在这个范围之中都可以应用到网格项目中。
-
它接收两个参数:
- 第一个参数:最小值;
- 第二个参数:最大值;
/* minmax() - 最大最小范围函数 */
.grid-container {
grid-template-columns: 1fr 1fr minmax(200px, 2fr);
grid-auto-rows: 100px;
}
auto关键字
通过 auto
关键字,我们可以轻松实现两列甚至更多列的布局(其中的一列或某几列宽度固定不变,其他的列宽度随着屏幕的宽度自适应)。
/* auto关键字 */
.grid-container {
/* 声明了三列 */
/* 第一列 和 第三列 列宽维 100px */
/* 第二列的宽度由浏览器的可视化窗口的宽度决定 */
grid-template-columns: 100px auto 100px;
grid-auto-rows: 100px;
}
设置 行间距 和 列间距(gap + row-gap + column-gap)
属性 | 作用 |
---|---|
gap | 设置 行间距 和 列间距(先行后列) |
row-gap | 设置 行间距 |
column-gap | 设置 列间距 |
/* row-gap: 30px; */
/* column-gap: 60px; */
/* 上面的代码等同于下面这行代码 */
gap: 30px 60px; /* 行间距, 列间距 */
元素排列方式(grid-auto-flow)
属性名 | 属性值 | 描述 |
---|---|---|
grid-auto-flow | row | 默认值,按 行 开始排列,即先填满一行,再开始下一行 |
grid-auto-flow | column | 按 列 开始排列 |
grid-auto-flow | dense | 该关键字指定自动布局算法使用一种"稠密"堆积算法,如果后面出现了稍小的元素,则会试图去填充网格中前面留下的空白。这样做会填上稍大元素留下的空白,但同时也可能导致原来出现的次序被打乱 |
grid-auto-flow | row dense | 按行来填充网格中前面留下的空白 |
grid-auto-flow | column dense | 按列来填充网格中前面留下的空白 |
设置网格在容器中的位置(justify-content + align-content)
-
justify-content
:- 该属性用于对齐容器内的网格,设置如何分配顺着弹性容器主轴(或者网格行轴)的元素之间及其周围的空间;
- 简单来说就是根据 网格行轴 设置 网格位置 以及 列间距;
-
align-content
:- 该属性用于设置 垂直方向 上的 网格元素 在容器中的 对齐方式;
- 简单来说就是根据 网格列轴 设置 网格位置 及其 行间距;
-
❗❗ 注意:
- 网格的总宽度 必须小于 容器的宽度 才能使
justify-content
属性生效; - 网格元素的总高度 必须小于 容器的高度 才能使
align-content
属性生效; justify-content
与align-content
的属性值是一样的;
- 网格的总宽度 必须小于 容器的宽度 才能使
设置网格内容在网格中的位置(justify-items + align-items)
定位项目位置(grid-area)
-
语法:
grid-area: grid-row-start / grid-column-start / grid-row-end / grid-column-end | itemname;
值 | 描述 |
---|---|
grid-row-start | 指定在 哪一行 开始显示 网格元素 |
grid-column-start | 指定在 哪一列 开始显示 网格元素 |
grid-row-end | 指定 哪一行 停止显示 网格元素 |
grid-column-end | 指定 哪一列 停止显示 网格元素 |
justify-self、align-self、place-self
justify-self
属性设置单元格内容的水平位置(左中右),与justify-items
属性的用法完全一致,但只作用于单个项目;align-self
属性设置单元格内容的垂直位置(上中下),与align-items
属性的用法完全一致,但只作用于单个项目;place-self
属性 是 上述两个属性的复合属性 先justify-self
后align-self
;- 上述属性的属性值是一样的:
start(对齐单元格的起始边缘) | center(居中) | end(对齐单元格的结束边缘) |stretch(默认值,拉伸,占满整个单元格)
;
横竖屏样式变更
<style>
@media all and (orientation : landscape) {
body {
background-color: #ff0000;
}
}
@media all and (orientation : portrait) {
body {
background-color: #00ff00;
}
}
</style>
背景动画效果
利用CSS的过渡和动画属性,创建平滑的过渡、淡入淡出效果。通过定义动画的持续时间、延迟时间和重复次数,可以控制动画的表现方式。
.box {
transition: background-color 0.3s ease-in-out;
}
.box:hover {
background-color: #ff5500;
}
平滑滚动效果
通过使用CSS的scroll-behavior属性,可以添加平滑滚动效果,使页面在滚动时更加流畅和舒适。
html {
scroll-behavior: smooth;
}
渐变边框样式
使用CSS的border-image属性,你可以创建具有渐变效果的边框样式。定义渐变图像或渐变颜色作为边框的源,以及边框的切片方式和宽度。
.border {
border: 10px solid;
border-image: linear-gradient(to right, #ff5500, #ffd200) 1;
}
渐变文本效果
使用CSS的background-clip属性和渐变背景色,可以为文本创建渐变效果。将渐变应用到文本的背景区域,形成独特的渐变文本效果。
.text {
background: linear-gradient(to right, #ff5500, #ffd200);
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
}
:is伪类选择器
通常需要将相同的样式应用于多个元素。:is()
不能匹配::before
和::after
伪元素。
/* default black */
p {
color: #000;
}
/* gray in <article>, <section>, or <aside> */
article p,
section p,
aside p {
color: #444;
}
:is(article, section, aside) p {
color: #444;
}
:where伪类选择器
:where()
选择器语法与:is()
相同,并且在所有现代浏览器(不包括IE)中都支持。这通常会导致相同的样式。区别在于特异性,在:where()
的情况下,特异性为零。基于:where()
的零特异性,可以用于初始样式设置。
/* reset */
:where(h2) {
margin-block-start: 1em;
}
:where(article :first-child) {
margin-block-start: 0;
}
:has伪类选择器(兼容性差不建议使用)
/* 选择 h2 中间第一行 */
h2 + :has(~ h2){
color:green;
}
/* 选择 h2 中间最后一行 */
h2 ~ :has(+ h2){
color:red;
}
h2 + :has(~ h2)
表示选择紧跟着 h2
的并且后面还有 h2
元素的兄弟元素。也就选择到了 h2
范围内的第一个元素。
h2 ~ :has(+ h2)
表示选择 h2
后面的兄弟元素,并且该兄弟元素的下一个兄弟元素是 h2
,也就选择到了 h2
范围内最后一个元素
transform-style: preserve-3d实现3d效果
-
transform-style: preserve-3d;
表示所有子元素在3D空间中呈现 -
perspective: 400;
3D元素的透视效果 -
transition: transform 1.5s linear;
过渡效果 -
backface-visibility: hidden;
定义当元素背面向屏幕时不可见。 -
transform: rotateY(180deg);
沿Y轴旋转180度
<div class="film">
<!-- 正面 -->
<div class="face">
<img src="https://i.postimg.cc/RCQsrfyN/lyf1.jpg" alt="">
</div>
<!-- 反面 -->
<div class="face back">
<div class="content">
刘亦菲,1987年8月25日出生于湖北省武汉市,华语影视女演员、歌手,毕业于北京电影学院2002级表演系本科。2002年因出演电视剧《金粉世家》中白秀珠一角踏入演艺圈。2003年因出演武侠剧《天龙八部》中王语嫣一角崭露头角。2004年凭借仙侠剧《仙剑奇侠传》赵灵儿一角获得了高人气与关注度。
</div>
</div>
</div>
.film {
position: relative;
width: 220px;
height: 320px;
margin: 200px auto 0;
transform-style: preserve-3d; /*指定所有子元素在3D空间内呈现*/
perspective: 400; /*元素距离视图的距离*/
transition: transform 1.5s linear; /*设置过渡效果*/
}
.face {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
backface-visibility: hidden; /*定义当元素背面向屏幕时不可见*/
}
img {
width: 100%;
height: 100%;
}
.back {
background: linear-gradient(to bottom, rgb(0, 0, 0), rgb(17, 17, 16));
transform: rotateY(180deg);/*旋转180度,转向背面*/
}
.film:hover {
cursor: pointer;
transform: rotateY(180deg) scale(1.5); /*鼠标悬停时,旋转180度,将背面展示出来*/
}
.content {
padding: 10px 20px;
color: #fff;
box-sizing: border-box;
}
nth-child选中指定的元素
// 前三个
li:nth-child(-n + 3) {
text-decoration: underline;
}
// 选中 2-5 的列表项
li:nth-child(n + 2):nth-child(-n + 5) {
color: #2563eb;
}
// 倒数两个
li:nth-last-child(-n + 2) {
text-decoration-line: line-through;
}
省略号
/*单行*/
.truncate {
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
/*多行*/
.truncate {
display: -webkit-box;
-webkit-box-orient: vertical;
-webkit-line-clamp: 2;
overflow: hidden;
}
设置文字撑满盒子
text-align-last:justify;
word-break: keep-all;
JS
复制
let copy_text = document.querySelector(".draw-geojson-data").innerHTML; //拿到想要复制的值
navigator.clipboard.writeText(copy_text);
alert("复制成功!");//弹出提示信息,不同组件可能存在写法不同
// 低版本浏览器不支持navigator.clipboard.writeText 考虑到兼容性 建议使用下面方法
const input = document.createElement('input')
input.style.cssText = 'opacity: 0;'
input.type = 'text'
input.value = copy_text
document.body.appendChild(input)
input.select() // 选中文本
document.execCommand('copy') // 执行浏览器复制命令
document.body.removeChild(input)
alert("复制成功!");//弹出提示信息,不同组件可能存在写法不同
检查对象是空对象
const isEmptyObject = Object.keys(object).length === 0 && object.constructor === Object;
判断数字
function isNumber(value) {
return typeof value === 'number';
}
判读数字是否为int2 int4 int8 float4 float8
function isint2(num) {
return typeof num === 'number' && Number.isInteger(num) && num >= -32768 && num <= 32767
}
function isint4(num) {
return typeof num === 'number' && Number.isInteger(num) && num >= -2147483648 && num <= 2147483647
}
function isint8(num) {
return (
typeof num === 'number' && Number.isInteger(num) && num >= -9223372036854775808 && num <= 9223372036854775807
)
}
// js判断 float4
function isfloat4(num) {
// 整数也认为是浮点型
if (Number.isFinite(num) && typeof num === 'number' && num.indexOf('.') === -1) return true
// 检查数字是否是NaN或无穷大
if (isNaN(num) || !isFinite(num)) {
return false
}
// 将数字转换为二进制字符串
let binaryStr = num.toString(2)
// 检查小数点后是否有足够的位数
let dotIndex = binaryStr.indexOf('.')
/* if (dotIndex === -1) {
return false // 没有小数点,不是float
} else */ if (
dotIndex + 23 <
binaryStr.length
) {
return false // 小数点后有23位以上,是double
} else {
// 尝试通过移除尾随零来增加精度
let precision = binaryStr.length - dotIndex - 1
let exponent = 23 + dotIndex
let mantissa = parseInt(binaryStr, 2)
// 通过指数和尾数重建浮点数
let reconstructed = ((Math.pow(2, exponent - 127) - 1) * mantissa) / Math.pow(2, 23)
// 如果重建的数字与原始数字非常接近,则认为它可能是float
return Math.abs(num - reconstructed) < Math.pow(2, -52)
}
}
function isfloat8(num) {
return Number.isFinite(num) && typeof num === 'number'
}
判断字符串是否为数字
// 判断是否为数字 包括字符串
function isNumeric(str) {
return Number.isFinite(+str)
}
日期格式化
const formattedDate = new Date().toISOString().slice(0, 10);
将字符串转为整数类型
const intValue = +str;
打印dom元素
当你需要在开发过程中打印dom元素时,使用console.log往往只会打印出整个dom元素,而无法查看该dom元素的内部属性。你可以尝试使用console.dir
console.dir(document.body);
元素可编辑
当你需要在某个dom元素进行编辑,让它点击的时候就像一个textarea
<div contenteditable="true">这里是可编辑的内容</div>
递归函数名解耦
当你需要写一个递归函数的时候,你声明了一个函数名,但是每次修改函数名的时候总会忘记修改内部的函数名。argument为函数内部对象,包含传入函数的所有参数,arguments.callee代表函数名。
// 这是一个最基础的斐波那契数列
function fibonacci (n) {
const fn = arguments.callee
if (n <= 1) return 1
return fn(n - 1) + fn(n - 2)
}
?. 和 ?? 和 !.
?.
称为链判断运算,它允许开发人员读取深度嵌套在对象链中的属性值,而无需验证每个引用,当引用为空时,表达式停止计算并返回 undefined,而??
则比较类似于||
,但是专门作用于undefined
和null
。
data?.layer1?.layer2?.[1]
undefined??true // true
!.
是TypeScript的语法,叫非空断言操作符(non-null assertion operator)
,和?.相反,这个符号表示对象后面的属性一定不是null或undefined。
!.: 非空类型断言 表示确定某个标识符是有值的,跳过ts在编译阶段对它的检测
function aa(value?:string) {
console.log(value!.length);
// console.log(value.length); //错误提醒: value is possibly 'undefined'.
}
aa('ppp');
//实际上这个东东不好用, 因为不传值, 编译JS后, 会报错, 所以建议使用?. 替代 !.
生成随机颜色
function getRandomColor() {
const colorAngle = Math.floor(Math.random() * 360);
return `hsla(${colorAngle},100%,50%,1)`;
}
Promise.allSettled
Promise.allSettled() 方法接受一组 Promise 实例作为参数,包装到一个新的 Promise 实例中。在所有这些参数实例都返回结果(已完成或已拒绝)之前,包装器实例不会结束。
有时候,我们并不关心异步请求的结果,我们只关心是否所有请求都结束了。这时候,Promise.allSettled() 方法就非常有用了。
const promises = [fetch("index.html"), fetch("https://does-not-exist/")];
const results = await Promise.allSettled(promises);
// Filter out successful requests
const successfulPromises = results.filter((p) => p.status === "fulfilled");
// Filter out failed requests and output the reason
const errors = results.filter((p) => p.status === "rejected").map((p) => p.reason);
获取列表中的最后一项
let array = [0, 1, 2, 3, 4, 5, 6, 7];
console.log(array.slice(-1)) >>> [7];
console.log(array.slice(-2)) >>> [6, 7];
console.log(array.slice(-3)) >>> [5, 6, 7];
slice
slice(start, end)
start
: 开始位置的索引(从0开始)
end
: 结束位置的索引(但该位置的元素本身不包括在内) 而不是截取的字符串或者数组长度
indexOf的按位化简
// bad
if (arr.indexOf(item) > -1) {
// item found
}
if (arr.indexOf(item) === -1) {
// item not found
}
// better
if (~arr.indexOf(item)) {
// item found
}
if (!~arr.indexOf(item)) {
// item not found
}
//The bitwise (~) operator will return true (except for -1), //the reverse operation only requires !~. Alternatively, the includes() function can be used.
if (arr.includes(item)) {
// true if the item found
}
计算字符串某字符的数量
function countCharacter(str, charToCount) {
// 创建一个正则表达式,它将匹配我们想要计数的字符
const regex = new RegExp(charToCount, 'g');
// 使用正则表达式的match方法来获取匹配到的所有字符,然后返回数组的长度
return (str.match(regex) || []).length;
}
经纬度的范围校验
// 经度校验
function isValidLongitude(longitude) {
return /^(\-|\+)?(((\d|[1-9]\d|1[0-7]\d|0{1,3})\.\d{0,15})|(\d|[1-9]\d|1[0-7]\d|0{1,3})|180\.0{0,15}|180)$/.test(
longitude
)
}
// 纬度校验
function isValidLatitude(latitude) {
return /^(\-|\+)?([0-8]?\d{1}\.\d{0,15}|90\.0{0,15}|[0-8]?\d{1}|90)$/.test(latitude)
}
js 数组删除指定数据
使用filter()函数
arr = arr.filter(item => item !== target);
使用splice()函数
let index = arr.indexOf(target);
if (index > -1) {
arr.splice(index, 1);
}
正则-判断字符串中是否包含特殊字符以及空格
var pattern = new RegExp("[`~!@#$^&*()=|{}':;',\\[\\].<>《》/?~!@#¥……&*()——|{}【】‘;:”“'。,、? ]")
if (pattern.test(value)) {
callback('表名不允许有特殊字符以及空格!')
}
正则-以字母开头的包含字母或下划线的字符串
最后一位可为下划线
new RegExp(/^(?!_)[A-Za-z_]+$/).test(value)
最后一位不能是下划线(?!.*?_$)
new RegExp(/^(?!_)(?!.*?_$)[A-Za-z_]+$/).test(value)
正则-以字母开头且仅包含字母数字以及下划线
new RegExp(/^[A-Za-z](?!.*?_$)[A-Za-z0-9_]+$/).test(value)
字符串中指定字符的数量
(str.match(/指定字符/g) || []).length
处理查询条件输入含有数据库的通配符
在JavaScript中,如果用户输入的查询条件包含数据库中使用的通配符(例如 %
或 _
),你需要对这些字符进行转义,以避免SQL注入攻击
function escapeQueryValueForSQL(value) {
// 对 '%' 和 '_' 进行转义
return value.replace(/[%_]/g, (match) => {
// 使用 '\\' 进行转义
return '\\' + match;
});
}
大数处理
- 数据转成字符串
- bignumber.js json-bigint
通过拼接的函数名调用函数
eval(funcName + '(' + arg + ')') // funcName:函数名 arg:参数
文件下载
使用 a
标签的 download
属性,同源才能触发下载,IE 不支持,移动端兼容性也不太好。
<a href="/path/to/file" download>Download</a>
// 或者 js 临时生成 a
function download(url) {
const link = document.createElement('a')
link.download = 'file name'
link.href = 'url'
document.body.appendChild(link)
link.click()
document.body.removeChild(link)
}
前端安全
浏览器打开新的标签页
看似平平无奇的打开页签,但是需要关注下 rel
,如果要打开外链,建议设置为 noopener noreferrer
,避免一些恶意网站通过 window.opener.location
重定向你的网站地址。window.open
方法同理。
js
// 高版本浏览器 rel 默认为 noopener,不过建议显示设置,兼容低版本。
<a target="_blank" rel="noopener noreferrer">...</a>
// window.open rel 默认为 opener,需要自己设置
window.open('https://baidu.com', '_blank', 'noopener,noreferrer')
// 以下有安全漏洞,打开的新页签可以通过 window.opener.location 重定向你的网站
<a target="_blank" rel="opener">...</a>
window.opener.location = 'http://fake.website.here';
URL 参数转化为对象
获取 url 参数有个热门的库 query-string,如果不想使用的话,可以通过 URLSearchParams
API 实现。
const getUrlParams = (query) =>
Array.from(new URLSearchParams(query)).reduce(
(p, [k, v]) =>
Object.assign({}, p, { [k]: p[k] ? (Array.isArray(p[k]) ? p[k] : [p[k]]).concat(v) : v }),
{}
)
// 获取 query 参数
getUrlParams(location.query)
// { a: ['1', '4'], b: '2', c: '3' }
getUrlParams('?a=1&b=2&c=3&a=4')
// 获取 hash 参数
getUrlParams(location.hash.split('?')[1])
清理数组中所有假值( 0
,false
,null
,undefined
,''
,NaN
)
const arr = [ 0, 1, false, 2, '', 3 ];
const cleanedArray = arr.filter(Boolean);
将 NodeList 转换为数组
const nodesArray = [ ...document.querySelectorAll('div') ];
const nodesArray = Array.from(document.querySelectorAll('div'));
取两个数组的交集
const arr1 = [1, 2, 3, 4];
const arr2 = [2, 4, 6, 8];
// 取两个数组中公共的元素
const intersection = arr1.filter(value => arr2.includes(value));
// 结果: intersection = [2, 4]
使用变量作为对象的键
const dynamicKey = 'name';
const value = '张三';
// 使用一个动态的变量作为 key
const obj = {[dynamicKey]: value};
// 结果: obj = { name: '张三' }
离开页面弹出确认对话框
当用户离开页面时会弹出一个确认对话框,一般用于防止用户因未保存更改就关闭页面而导致数据丢失。
window.onbeforeunload = () => '你确定要离开吗?';
切换元素的 class
const element = document.querySelector('.my-element');
const toggleClass = (el, className) => el.classList.toggle(className);
toggleClass(element, 'active');
提取文件扩展名
先找到最后一次出现点号 (.
) 位置,然后截取从该位置到末尾的字符串。位运算符 (>>>
) 确保了即使未找到点号 (.
) ,操作也是安全的,因为在这种情况下仍然会返回一个空字符串。
const fileName = 'example.png';
const getFileExtension = str => str.slice(((str.lastIndexOf(".") - 1) >>> 0) + 2);
// 结果: getFileExtension = 'png'
库
tesseract.js
分析出图片上的文字
<template>
<div><Input v-model="value" /></div>
<div class="img-container">
<img v-for="img in filterImgs" :src="img.src" />
</div>
</template>
<script setup lang="ts">
import { ref, onMounted, corputed } from 'vue';
import { Input } from "ant-design-vue';
import Tesseract fron 'tesseract.js';
const requestImgs=() => {
return [
"http://127.8.0.1:3006/img1.png'
"http://127.8.0.1:3086/img2.png'
"http://127.8.0.1:3006/img3.png',
'http://127.8.0.1:3006/img4.png',
'http://127.8.0.1:3006/img5.png'
].map(img => ({
name: img,
src: img,
}));
});
const value = ref("');
const allImgs = ref<any[]>([]);
//过滤出符合条件的图片
const filterImgs = computed(() =>
allImgs.value.filter(({ keyword })=> keyword.includes(value.value))
);
const resolveImg = (obj: any) => {
const o = { ...obj };
return Tesseract.recognize(o.src, 'eng')
.then(({ data: { text }}) => {
//成功则把 text 放在 keyword 中
return { ...o, keyword: text };
})
.catch(()=>{
//失败则把 src 放 keyword 中
return{ ...o, keyword: o.src };
});
};
const getImgs =async()=> {
const res = requestImgs();
//解析图片文字
allImgs.value = await Promise.all(res.map(o > resolveImg(o)));console.log(allIrgs.value);
};
onMounted(()=>{
getImgs();
}
</script>
Vue2
组件中定义的方法会覆盖mixins中定义的方法,即组件中的同名方法优先级高于mixins中的同名方法
Vue3
将es6打包成es5 解决低版本浏览器兼容问题
为了将ES6代码转换为ES5,可以使用Babel。Vite是一个基于ESM的构建工具,它可以快速地构建现代Web应用程序。下面是在Vite中配置Babel以将ES6转换为ES5的步骤:
1.安装依赖
npm install @babel/core @babel/preset-env -D
2.在项目根目录下创建.babelrc
文件,并添加以下内容:
{
"presets": [
[
"@babel/preset-env",
{
"targets": {
"chrome": "58",
"ie": "11"
}
}
]
]
}
3.在vite.config.js
文件中添加以下内容:
export default defineConfig({
build: {
target: "es2015",
minify: "terser",
sourcemap: false,
rollupOptions: {
output: {
format: "es",
manualChunks: undefined,
},
input: {
main: "./index.html",
},
plugins: [],
}
},
});
VueUse
VueUse
是一个基于 Composition API
的实用函数集合。通俗的来说,这就是一个工具函数包,它可以帮助你快速实现一些常见的功能,免得你自己去写,解决重复的工作内容。以及进行了基于 Composition API
的封装。让你在 vue3
中更加得心应手。
useElementBounding
动态获取元素的宽高、位置等属性
<template>
<div ref="el" />
</template>
<script>
import { ref } from 'vue'
import { useElementBounding } from '@vueuse/core'
export default {
setup() {
const el = ref(null)
const { x, y, top, right, bottom, left, width, height } = useElementBounding(el)
return {
el,
/* ... */
}
}
}
</script>
useDraggable
使元素可拖拽。
<script setup lang="ts">
import { ref } from 'vue'
import { useDraggable } from '@vueuse/core'
const el = ref<HTMLElement | null>(null)
// `style` will be a helper computed for `left: ?px; top: ?px;`
const { x, y, style } = useDraggable(el, {
initialValue: { x: 40, y: 40 },
})
</script>
<template>
<div ref="el" :style="style" style="position: fixed">
Drag me! I am at {{x}}, {{y}}
</div>
</template>
Vue UI
Ant Design Vue(1.7.8)
upload上传
beforeUpload
支持返回一个 Promise 对象,Promise 对象 reject 时则停止上传(return Promise.reject()
)。注意:使用return false不能停止上传
。
通过accept=".geojson"
指定上传的文件必须为geojson格式
<a-upload
name="file"
accept=".geojson"
>
......
</a-upload>
select根据label进行搜索
1、通过showSearch
属性开启搜索功能,再通过设置optionFilterProp="label"
结合给每个a-select-option标签添加:label="item.layerName"
属性来实现
<a-select
show-search
optionFilterProp="label"
>
<a-select-option
v-for="(item, i) in layerOptions"
:key="item.id"
:value="item.id"
:label="item.layerName"
>
{{ item.layerName }}
</a-select-option>
</a-select>
2、通过filterOption
方法进行搜索过滤。这个方法接收输入值和每个选项,然后比较选项的文本内容是否包含输入值,从而决定是否显示该选项。
<template>
<a-select
v-model="selectedValue"
show-search
placeholder="Select a option"
:filter-option="filterOption"
>
<a-select-option v-for="option in options" :key="option.value" :value="option.value">
{{ option.label }}
</a-select-option>
</a-select>
</template>
<script>
export default {
data() {
return {
selectedValue: undefined,
options: [
{ value: 'option1', label: 'Option 1' },
{ value: 'option2', label: 'Option 2' },
{ value: 'option3', label: 'Option 3' },
// ...更多选项
],
};
},
methods: {
filterOption(input, option) {
return option.componentOptions.children[0].text.toLowerCase().indexOf(input.toLowerCase()) >= 0;
},
},
};
</script>
tree-select 搜索
项目开发的过程中出现无法通过名称
进行搜索,原因是treeNodeFilterProp
默认值为'value'
,应改为'title'
<a-tree-select
v-model="selectLayerGroup"
style="width: 250px"
show-search
treeNodeFilterProp="title"
:dropdown-style="{ maxHeight: '400px', overflow: 'auto' }"
:tree-data="layerGroupList"
:replace-fields="{ children: 'layerList', key: 'uuid', value: 'uuid', title: 'name' }"
placeholder="Please select"
tree-default-expand-all
@select="changeLayerGroup"
>
</a-tree-select>
解决 ant-design 输入框显示历史输入记录的问题
给 Form 表单添加 autocomplete ="off"
<a-form-model ref="ruleForm' :model="form" :label-col="labelCol" :wrapper-col="wrapperCol" autocomplete ="off">
实时监听a-input输入
通过@change
获取的值是改变之前的值
通过@input
能够实时获取改变之后的值
取消按钮点击效果
/deep/ [ant-click-animating-without-extra-node='true']::after {
display: none;
}
给a-date-picker设置默认值 报TypeError: _vm.moment is not a function in Vuejs
- 可以在data中定义并赋值一下moment(需验证)
data: () => ({
moment: moment
})
- 可以在methods中定义一下,将moment转为方法
methods: {
moment
}
GIS
投影、坐标系
EPSG:4326 (WGS84)
EPSG:3857 (Pseudo-Mercator)
伪墨卡托投影,也被称为球体墨卡托,Web Mercator。最初使用EPSG:900913(Google的数字变形) 的非官方代码来代表它
Web地图服务规范(WMS、WMTS、TMS)
1.概况
Web地图服务规范包括WMS(网络地图服务)、WMTS(网络地图瓦片服务)、TMS(瓦片地图服务)等。WMTS服务和WMS服务都是由开发地理信息联盟(OGC)指定。其不同在于,WMTS服务采用缓存技术能够缓解WebGis服务器端数据处理的压力
。TMS服务由开源空间信息基金会(OSGEO)指定。TMS与WMTS服务的差异主要体现在:
- TMS是纯Restful,而WMTS可以有KVP、SOAP和Restful三种。
- TMS瓦片是
正方形
的,而WMTS是矩形
的(正方形是特殊的矩形) - 在纵轴方向上方向相反,
TMS瓦片以左下角为原点,WMTS瓦片以左上角为原点
。 - WMTS中对应的不同比例尺瓦片可以尺寸不同。
在地图服务中,为了处理方便,瓦片均采用正方形,一般像素为256(居多)或者512。
这里需要说明一下,256的意思是对应瓦片(图片)是由横方向有256行像素点,纵方向256个像素,也就是说,对于256的一个瓦片,总共有256^2=65536个像素。由此也可以算出一个瓦片的大小,一个像素有RBG三个颜色通道组成,每个通道范围为0~255(即三个字节),也就是说一个像素为9个字节,那么一个瓦片的大小是589,824B。不过jpg,png等都采用了压缩技术,实际大小要小很多。
2. 基本知识
本次记录不是简单的介绍一下这三个协议,而是要要对地址做一下解析说明,这样可以更好的理解Web服务协议。为了更好的说明,这里需要引入几个知识点,以EPSG:4326网格集为例。
2.1 地球平面化
地球是一个球体,如果把其展开,可以近似看成一个矩形。宽近似为周长的一半。所以就有了如下图:
2.2 瓦片形成
为了满足TMS协议,瓦片采用正方形存放,所以,在切瓦片之前应该在中间先切一刀,保证基础为正方向。
此时沿着每个区域横方向和总方向各切一刀,这样就出现8块图片,也就是0级别有8块瓦片。
按照如此方式,每增加一个层级,每个瓦片就被分成4分。所以,层级1有8*4=32块瓦片。
而在实际tif切瓦片
的过程中,他是从最高一级往下切。比如一个tif文件可切成0-10级瓦片,他会先切10级瓦片,然后通过融合的方式把4×4的瓦片合并为一张瓦片,依次类推。
3. Web地图服务地址解析
地图服务提供的服务地址分别为:
-
WMS:
http://ip:port/geo/service/wms?layer={layername}&TILED=true
-
WMTS:
http://ip:port/geo/service/wmts?layer={layername}
-
TMS:
http://ip:port/geo/service/tms/1.0.0/{layername}@EPSG:4326@png
然而,如果要获取一个瓦片,需要如下的地址:
-
WMS:
http://ip:port/geo/service/wms?layer={layname}&TILED=true&version=1.3.0&request=GetMap&format=image/png&transparent=true&width=256&heigh=256&CRS=EPSG:4326&styles=&bbox=minx,miny,maxx,maxy
-
WMTS:
http://ip:port/geo/service/wmts?layer={layname}&tilematrixset=EPSG:4326&service=WMTS&Request=GetTile&Version=1.0.0&Format=image/png&TileMatrix=EPSG:4326:2&TileCol=5&TileRow=1
-
TMS:
http://ip:port/geo/service/tms/1.0.0/{layname}@EPSG:4326@jpeg/2/6/1.jpeg
我们知道,一个瓦片是通过层级范围、瓦片X坐标和瓦片Y坐标,对于WMTS和TMS,我们很容易找到这三个要素,然而WMS就没有那么容易了。下面就做一一分析。
3.1 WMTS
通过上的地址我们分析到,WMTS服务
三要素体现在TileMatrix=EPSG:4326:2&TileCol=5&TileRow=1
。其中层级范围为2(EPSG:4326:2),瓦片X坐标为TileCol=5,瓦片Y坐标为TileRow=1。现在我们取层级范围为3,第三列第六行的瓦片,那么参数应该就是(注意瓦片编号从0开始):
TileMatrix=EPSG:4326:3&TileCol=2&TileRow=5
3.2 TMS
通过上的地址我们分析到,TMS服务三要素体现在/2/6/1.jpeg,其中2为层级范围,6为瓦片X坐标,1为瓦片Y坐标。由上面说到,TMS的原点在左下角,所以如果取到和上面WMTS一样的瓦片,地址应该为:
/3/2/2.jpeg
那么WMTS和TMS的关系是什么呢? 层及范围和X坐标(也就是行号一致),Y坐标是,这两个的Y坐标相加等于2^(层级范围)-1,上面层级范围为3,所以两者相加等于7即可。
3.3 WMS
通过上面观察我们发现这个地址中并没有这三个要素,但是我们发现有个参数是bbox.这里传入了瓦片的左下角和右上角的坐标。那么通过这两个坐标就可以计算出三要素。需要注意的事,参数如果传入有偏差,将不会反返回正确结果。那么我们现在在算一下上面相同的瓦片的坐标该是多少呢?
- 首先,层级范围为3,应该有16列,8行瓦片。
- 那么我们现在计算黄色瓦片的左下角和右上角的坐标。对于X轴,总共有16个瓦片,长度为360°,所以每个瓦片的角度为22.5°。
- 对于X轴方向:最左边的坐标为-180°,所以黄色块的左下角X坐标为-180+22.5*2=-135;右上角X坐标为加一个瓦片的度数,为-112.5。
- 对于Y轴方向:最下边的坐标为-90度,所以黄色块的左下角Y坐标为-90+22.25*2=-45.5;右上角Y坐标为加一个瓦片的度数,为-56.25。
所以通过上面分析,WMS的参数中的Bbox参数应该为:
bbox=-135,-67.5,-112.5,-56.25
maptalks
删除文本图标
let layerDraw = this.mapView.getLayer('drawLayer') // 文本图标所在图层
const marker = this.selectDraw.marker // 文本图标
if (marker) {
marker.endEditText && marker.endEditText() // 文本取消编辑状态 不然编辑时生成的div无法删除
setTimeout(() => {
layerDraw.removeMarker(marker._id) // 根据id删除文本图标
}, 100)
}
修改绘制图形样式(线条颜色、填充色、透明度)
图形非'MultiPolygon', 'Polygon'类型,geom.setSymbol传递的参数只能是一个对象,而非数组(数组中包含字体样式)。
if (['MultiPolygon', 'Polygon'].includes(this.drawType)) {
geom.setSymbol([
{
lineColor: this.lineColor,
polygonOpacity: this.fillOpacity,
polygonFill: this.fillColor
},
{
textName: this.selectedGeomName,
textFill: this.textColor,
textSize: this.textSize,
textHaloFill: this.textHaloFill,
textHaloRadius: this.textHaloRadius
}
])
} else {
geom.setSymbol({
lineColor: this.lineColor,
polygonOpacity: this.fillOpacity,
polygonFill: this.fillColor
})
}
根据json数据在图层上加载图形
json数据结构
{
"feature": {
"type": "Feature",
"geometry": {
"type": "MultiPolygon",
"coordinates": [
[
[
[119.58981409031688, 32.2704284218924],
[119.59075316842927, 32.27022231058618],
[119.5906478037065, 32.26988161729536],
[119.5912705998392, 32.269742546572296],
[119.59137596254115, 32.27008557165805],
[119.59220909108149, 32.2699024541709],
[119.59147524640426, 32.267482318068325],
[119.58929260734978, 32.267967403503405],
[119.58908354249076, 32.26801295340617],
[119.58981409031688, 32.2704284218924]
]
]
]
},
"properties": {
"OBJECTID_1": 1,
"OBJECTID": 0,
"Entity": "Group",
"Layer": "JMD",
"Color": "#0000ff",
"Linetype": "CONTINUOUS",
"Elevation": 0,
"LineWt": 0,
"RefName": "制造部(预舾装及分段堆场)",
"Shape_Leng": 1098.11139164,
"Shape_Le_1": 1098.11139164,
"Shape_Area": 62116.857064
}
},
"options": {},
"symbol": [
{ "lineColor": "#948250", "polygonOpacity": 1, "polygonFill": "#0000ff" },
{
"textName": "制造部(预舾装及分段堆场)",
"textFill": "black",
"textSize": 12,
"textHaloFill": "#fff",
"textHaloRadius": 2
}
]
}
加载图形
var vlayer = new BDLayers.Lib.Layer.CBVectorLayer('93e284550890404981eb01ab239904bf'); // 图层用于加载图形
mapView.addCustomLayers(vlayer);
data.geoDataList.forEach((geoJsonStr, index) => {
let geoJson = JSON.parse(geoJsonStr)
geoJson.options = {} // 这段代码一定要加上 因为添加或编辑图形 保存时会给图形json数据加上options:{draggable:false, visible:false} 从而导致图形不展示
const geom = new BDLayers.Lib.Overlays.FromGeoJson(geoJson) // 根据json数据生成对应的geom图形
vlayer.addGeometry(geom)
})