沉淀一下最近开发工作中遇到的技术易错点、易忽略点。
图片标签 <img />
-
加载失败时,不显示默认缺省图、不显示默认边框
js<img :src="vehiclePicUrl || require('@/assets/images/1_1.png')" onerror="this.src=''" width="348px" height="192px" alt="" />
-
src
为空、无src
属性时jsimg[src=""], img:not([src]){ opacity: 0; }
-
静态图片url、动态url
flex
布局,宽度失效问题
- 首先,
flex-shrink
属性定义了项目的缩小比例,默认为1。即,如果空间不足,该项目缩小; - 当一个项目的
flex-shrink
属性为0,其他项目都为1,则为0的项目不缩小,其他的等比例缩小; - 所以我们需要给设置了宽度
width
的元素,再加上flex-shrink: 0
;则宽度不会失效了
js
.cardInfoItem div {
width: 120px;
margin-right: 16px;
flex-shrink: 0;
}
css
宽度超出显示省略号,一行、多行
js
// 文字超出省略号
.ellipsis {
overflow: hidden;
white-space: nowrap;
text-overflow: ellipsis;
}
// 文字2行超出省略号
.ellipsis-two {
overflow: hidden;
text-overflow: ellipsis;
display: box;
display: -webkit-box;
-webkit-line-clamp: 2;
-webkit-box-orient: vertical;
}
// 文字3行超出省略号
.ellipsis-three {
overflow: hidden;
text-overflow: ellipsis;
display: box;
display: -webkit-box;
-webkit-line-clamp: 3;
-webkit-box-orient: vertical;
}
滚动条美化
js
/* 为滚动条整体设置样式 */
div::-webkit-scrollbar {
width: 10px;
height: 10px;
}
/* 为滚动条的轨道设置样式 */
div::-webkit-scrollbar-track {
background: rgba(#101F1C, 0.1);
border-radius: 2em;
}
/* 为滚动条的滑块设置样式 */
div::-webkit-scrollbar-thumb {
background-color: rgba(144,147,153,.5);
border-radius: 2em;
transition: background-color .3s;
cursor: pointer;
}
/* 当鼠标悬停在滑块上时改变滑块的颜色 */
div::-webkit-scrollbar-thumb:hover {
background-color: rgba(144,147,153,.3);
}
滚动条隐藏
js
/* 隐藏 Chrome、Safari 和 Opera 的滚动条 */
div::-webkit-scrollbar {
display: none;
}
vue-seamless-scroll
的使用
js
// 首先,npm install vue-seamless-scroll --save
<template>
<!-- 列表 -->
<div :class="['oxygen-proportion-list', oxygenAlarm ? 'oxygen-alarm-red': '' ]">
<div v-if="oxygenList.length === 0" style="display: flex; flex-direction: column; align-items: center;opacity: 0.5;">
<img src="./images/error404.png" width="64px" height="64px" alt="" />
数据为空
</div>
<vue-seamless-scroll :data="oxygenList" :class-option="classOption">
<div :class="['oxygen-item', ox.alarm ? 'alarm-item-red' : '' ]" v-for="(ox, i) in oxygenList" :key="i + ox.name">
<div class="title" :title="ox.name">{{ ox.name }}</div>
<div class="content">{{ ox.valueStr }}</div>
</div>
</vue-seamless-scroll>
<div class="mask-bar"></div>
</div>
</template>
<script>
import vueSeamlessScroll from 'vue-seamless-scroll'
export default {
data () {
return {
oxygenList: [],
classOption: {
step: 1, // 数值越大速度滚动越快
limitMoveNum: 5, // 开启无缝滚动的数据量
hoverStop: false, // 是否启用鼠标 hover 控制
direction: 1, // 方向: 0 往下 1 往上 2 向左 3 向右
openTouch: true, // 移动端开启 touch 滑动
singleHeight: 0, // 单步运动停止的高度(默认值 0 是无缝不停止的滚动),direction 为 0|1 时生效
singleWidth: 0, // 单步运动停止的宽度(默认值 0 是无缝不停止的滚动),direction 为 2|3 时生效
waitTime: 1000, // 单步停止等待时间(默认值 1000ms)
switchOffset: 30, // 左右切换按钮距离左右边界的边距(px)
autoPlay: true, // 需要实现手动切换左右滚动的时候,必须设置autoPlay:false
switchSingleStep: 134, // 手动单步切换 step 值(px)
switchDelay: 400, // 单步切换的动画时间(ms)
switchDisabledClass: '', // 不可以点击状态的 switch 按钮父元素的类名
isSingleRemUnit: false, // singleHeight and singleWidth 是否开启 rem 度量
navigation: false, // 左右方向的滚动是否显示控制器按钮,true 的时候 autoPlay 自动变为 false
}
}
},
components: { vueSeamlessScroll },
}
</script>
websokets
使用案例
js
<template>
<div class="contain">Home</div>
</template>
<script>
import { notifyAddress } from '@/api/index'
export default {
name: 'homeName',
components: {},
data() {
return {
websockets: null,
reconnectInterval: 5000,
heartbeatInterval: null,
}
},
computed: {},
watch: {},
created() {},
mounted() {
// 初始化websockets连接
this.initWsConnect()
},
methods: {
/**
* 初始化websocket连接
*/
async initWsConnect() {
// const str = this.getRandomString(8);
// console.log(str);
// this.websockets = new WebSocket(`ws://10.194.107.13:17071/tmxcpb-web/ws/notify/${str}`);
const proto = location.protocol; // https:
const params = {
scheme: proto.substring(0, proto.length - 1)
}
const res = await notifyAddress(params);
console.log('获得socket的url', res, params);
let socketUrl = 'ws://10.19.185.102:17071/tmxcpb-web/ws/notify/6af5fa00ssdllpol';
if (res.data) {
socketUrl = res.data
}
this.websockets = new WebSocket(socketUrl);
this.websockets.onopen = this.onWebSocketOpen;
this.websockets.onmessage = this.onWebSocketMessage;
this.websockets.onclose = this.onWebSocketClose;
},
/**
* 关闭
*/
closeWebSocket() {
console.log('websockets关闭连接');
this.websockets && this.websockets.close()
},
/**
* 监听打开
*/
onWebSocketOpen() {
console.log('websockets建立连接onWebSocketOpen');
this.startHeartbeat()
const initMessage = {}
if (this.websockets && this.websockets.readyState === WebSocket.OPEN) {
// 发送消息到服务器
this.websockets.send(initMessage)
}
},
/**
* 监听发送
*/
onWebSocketMessage(event) {
console.log('接收到websocket消息, 刷新一下页面', event);
// 停掉
// this.closeWebSocket();
// this.stopHeartbeat();
// 整个页面刷新
// location.reload();
this.$EventBus.$emit('RefreshPage');
},
/**
* 监听关闭
*/
onWebSocketClose() {
console.log('监听websocket已经关闭');
this.startHeartbeat()
setTimeout(this.initWsConnect(), this.reconnectInterval)
},
/**
* 开始心跳
*/
startHeartbeat() {
this.heartbeatInterval = setInterval(() => {
if (this.websockets && this.websockets.readyState === WebSocket.OPEN) {
this.websockets.send('ping')
}
}, 10* 1000)
},
/**
* 结束心跳
*/
stopHeartbeat() {
this.heartbeatInterval && clearInterval(this.heartbeatInterval)
}
},
destroyed() {
this.closeWebSocket()
this.stopHeartbeat()
},
}
</script>
<style scoped>
.contain {
width: 100%;
height: 100%;
position: relative;
background: rgb(22, 24, 35);
user-select: none;
}
</style>
<style>
::-webkit-scrollbar {
width: 4px;
height: 6px;
}
::-webkit-scrollbar-track {
-webkit-box-shadow: inset 0 0 6px #202735;
border-radius: 8px;
}
::-webkit-scrollbar-thumb {
-webkit-box-shadow: inset 0 0 6px rgba(113, 139, 222, 0.27);
background: rgb(22, 24, 35);
border: none;
border-radius: 12.5px;
}
</style>
开发列表、图片等要数据为空要有对应的缺省图
千分符函数
js
export function toThousand(num) {
// 使用正则表达式匹配数字,并在每个千位后添加逗号
return String(num).replace(/\B(?=(\d{3})+(?!\d))/g, ',');
}
CSS
选择同级最后一个元素的问题
js
知识点一:
:last-child 选择器用于选择某个元素的父元素中的最后一个子元素
/* 选择父元素中的最后一个 <p> 元素 */
.parent p:last-child {
color: red;
}
// 只有当 <p> 元素是其.parent 的最后一个子元素时,其文本颜色才会被设置为红色
知识点二:
p:last-of-type 选择器用于选择同类型兄弟元素中的最后一个元素
/* 选择同类型兄弟元素中的最后一个 <li> 元素 */
ul li:last-of-type {
background-color: yellow;
}
// 会选择 ul 中的最后一个 <li> 元素,并将其背景颜色设置为黄色
// 与 :last-child 不同的是,它不考虑其他类型的兄弟元素,只关注同类型的元素。
// 例如:如果 <ul> 中有其他元素(如 <span>)夹杂在 <li> 元素之间,last-of-type 仍然能正确选择最后一个 <li>
知识点三:
p:nth-last-of-type(n) 选择器用于从一组兄弟元素中,选择同类型元素里从后往前数的第n个元素
知识点四:
p:first-of-type 选择器用于选择一组兄弟元素中同类型的第一个元素
知识点五:
p:nth-of-type(1) 选择器可以根据元素在同类型兄弟元素中的位置来选择元素
知识点六:
:nth-child(3) 选择器是基于所有兄弟元素来计数的,不管元素类型是否相同。它会选择父元素下的第 3 个子元素
知识点七:
:first-child 和 :last-child 选择父元素的第一个/最后一个子元素,不考虑元素类型
// 如果要选择一个元素,该元素必须是其父元素的第一个/最后一个子元素,无论元素类型如何,此选择器都将其选中
总结:
这几个中,带type的选择器,都是会考虑是否是元素同类型
npm
更新依赖等命令
js
npm update
// 这个命令会根据 package.json 文件中指定的依赖范围,更新依赖包到符合版本范围的最新版本
npm cache clean --force
// 清除 npm 的缓存
npm uninstall moment
// 卸载moment
-dev、--save-dev 或 -D
// 它会将安装的包作为开发依赖添加到 package.json 文件的 devDependencies 部分
// 开发依赖是指在开发过程中需要使用,但在项目部署到生产环境时并不需要的包
-save 或 -S
// 它会将安装的包作为项目的生产依赖添加到 package.json 文件的 dependencies 部分
// 生产依赖是指在运行时(即在生产环境中)需要的包,对于项目的正常运行是必需的
// 不写省略时,默认是 -
向iframe
页面触发事件、传参的两种方式:一种postMessage
、一种放在src
的路径后面带过去
postMessage发送消息:
js
<template>
<div class="current-duty-cls">
<!-- 第三方通信工具的页面 -->
<iframe
src="https://ip/thpaidpldoc/thirdPartCommunicate"
id="thirdPardPage"
scrolling="no"
allowtransparency="true"
style="background-color: transparent;"
width="auto"
height="auto"
name="otheriframe"
allowfullscreen="true"
frameborder="0"
class="other-page"
v-if="showOtherPage"
></iframe>
</div>
</template>
<script>
export default {
name: "current-duty-name",
data() {
return {
showOtherPage: false,
timer: null
}
},
mounted() {},
methods: {
async callPhone(member) {
console.log("member: ", member);
if (member.mobile) {
const target = {
number: member.mobile,
title: "拨打电话"
}
// 先判断之前有没有
this.showOtherPage = false;
const iframeDom = document.getElementById('thirdPardPage');
if (!iframeDom) {
this.showOtherPage = false;
this.timer && clearInterval(this.timer);
this.timer = null;
}
// 改为第三方的
this.showOtherPage = true;
const that = this;
setTimeout(() => {
const iframeDom = document.getElementById('thirdPardPage');
console.log(iframeDom);
iframeDom.onload = function() {
console.log('iframe页面onload加载完成');
iframeDom.contentWindow.postMessage(JSON.stringify(target), 'https://ip/thpaidpldoc/thirdPartCommunicate');
// 监听iframe页面是否需要关闭
that.watchIframePage();
}
}, 100)
} else {
console.log('member.mobile:', member.mobile);
}
},
/**
* 监听iframe页面是否需要关闭
*/
watchIframePage () {
this.timer = setInterval(() => {
const iframeDom = document.getElementById('thirdPardPage');
const iframeDoc = iframeDom.contentWindow.document;
const iframeApp = iframeDoc.getElementById('app');
const activeElement = iframeApp.getElementsByClassName('modal-box callbox');
console.log('66666666666666', activeElement);
if (activeElement && activeElement.length === 0) {
// 获取不到这个标签,表示可以销毁iframe了
this.showOtherPage = false;
this.timer && clearInterval(this.timer);
this.timer = null;
}
}, 3 * 1000)
}
},
beforeDestroy() {
this.timer && clearInterval(this.timer);
}
};
</script>
<style lang="scss" scoped>
.other-page {
width: 440px;
height: 860px;
position: fixed;
top: 0px;
left: 25%;
z-index: 999;
}
</style>
被嵌入iframe的页面,监听message事件:
js
mounted(){
/**
* 三方通信页面接收到message发送的数据
*/
window.addEventListener('message', e => {
console.log('iframe页面接收到', e);
if (e.data && typeof(e.data) === 'string') {
const dataObj = JSON.parse(e.data);
// 其他业务逻辑
}
}, false)
}
车牌号码校验正则表达式
js
/**
* 正则校验
* 判断输入的车牌号码是否规范
* 包括新能源车牌号,这个校验规则很宽松
*/
checkPlateNoPattern (plateNo) {
const platePattern1 = /^[京津沪渝冀豫云辽黑湘皖鲁新苏浙赣鄂桂甘晋蒙陕吉闽贵粤青藏川宁琼使领A-Z]{1}[A-Z0-9]{4,7}[A-Z0-9挂学警港澳]{1}$/;
return platePattern1.test(plateNo);
}
<div class="plate-no-item" v-for="car in carNoList" :key="car.id" @click="deletePlateNo(car)">
{{ car.plateNo.replace(/(.{2})/, '$1 • ') }}
<el-button icon="h-icon-close" />
</div>
/**
* 正则规则:
* 1、用这两个斜杠包裹起来; /正则/
* 2、^:表示以什么开头
* 3、$:表示以什么结尾
* 4、[]:包裹内容
* 5、{n,m}:表示内容至少有n个,至多有m个
* 6、{n,}:表示内容至少有n个,至多到无穷大
* 7、/ab*c/:表示b有零个或多个,例如:ac、abc、abbc
* 8、/ab+c/:表示b有一个或多个,例如:abc、abbc
* 9、-:表示内容范围,例如:a-z、0-9、A-Z
**/
el-select添加一个全选功能
js
<template>
<div>
<span class="title" style="text-align: left;">选择驱动类型</span>
<el-select
v-model="download.treatyTypeList"
placeholder="请选择"
multiple
clearable
multiple-nowrap
>
<el-checkbox
class="all-checkbox"
style="padding: 4px"
:indeterminate="isIndeterminate"
@change="handleCheckAllChange"
v-model="checkAllType"
>
<span style="padding-left: 4px;">全选</span>
</el-checkbox>
<el-option
v-for="item in driveTypeList"
:key="item.value"
:label="item.label"
:value="item.value"
>
</el-option>
</el-select>
</div>
</template>
<script>
export default {
data() {
return {
driveTypeList: [
// {
// label: "hiksdk_net",
// value: "hiksdk_net"
// }
],
checkAllType: false,
isIndeterminate: false,
download: {
treatyTypeList: []
}
};
},
watch: {
// 选择驱动类型后,加载
"download.treatyTypeList": "freshedTypeList"
},
methods: {
/**
* 全选
*/
handleCheckAllChange(val) {
this.download.treatyTypeList = val
? this.driveTypeList.map(i => i.value)
: [];
this.isIndeterminate = false;
this.checkAllType = val;
},
/**
* 选择驱动类型后,加载
*/
freshedTypeList(newVal) {
// debugger
if (newVal.length === this.driveTypeList.length) {
this.checkAllType = true;
this.isIndeterminate = false;
} else if (newVal.length === 0) {
this.checkAllType = false;
this.isIndeterminate = false;
} else {
this.checkAllType = false;
this.isIndeterminate = true;
}
// 加载其他数据
}
}
}
</script>