dayjs有bug
- 对于单次定时的cron表达式,在dayjs官网的控制台里可以成功解析,本地安装包之后就解析不出来
dayjs('9 20 11 24 6 ? 2025','s m h D M ? YYYY').$d
- 弥补方法:
// 仅支持"0 9 12 25 3 ? 2025"这种固定时间的cron表达式
isCronInPast(cronExpression) {
const parts = cronExpression.split(' ')
const [second, minute, hour, day, month, , year] = parts.map((part) => {
return Number(part)
})
// 构造 Date 对象(注意月份是 -1)
const cronDate = new Date(year, month - 1, day, hour, minute, second)
const now = new Date() // 当前时间
return cronDate <= now
}
面试网站
数字限制
- Number.MAX_SAFE_INTEGER 9007199254740991 //16位 9开头
- 注意:后端传给前端的值若为数字时不可超过该值,不然接受到那一刻就已经丢失精度,String()是转不对的
- 若后端需要大于该数字的值时,前端交互期间可以通过BigInt构造大数字变量
vue2里data不在return里的情况
测试方法:页面里触发changeVal修改这两个值,然后切换到其他页面再切回来
- script和export default之间的变量
- 一直存在,无法被销毁,慎用, 尤其是 存对象时,可能会遗漏对象属性
- 页面里修改值后,切其它页面再切回来时,是修改后的值!
- 不同组件里 可以在这个位置声明同名的变量,打包后会在不同的js文件里,名字也不一样
- 该组件如果被复用,这个变量是联通的,修改一个全都会变,所有用该组件的里面的变量是同一个值
- data内return外的变量
- 会随着页面销毁重现 重新初始化
- 模板里可以渲染该变量的初始值,之后修改的话,不会响应式渲染
this.$forceUpdate()
可以使变动后的值渲染出来- 实际上这种写法,等价于在created里写一个this.cVal='xxx',就是在组件实例上挂载一个变量
想让变量随组件初始化,同时不需要响应式时 可以用第二种
<script>
let outerVar = 1 //从出现后,被修改后的值就一直存在,不会随组件销毁
export default {
data() {
this.dataVar = 100 //组件销毁重建时会恢复初始值
return {
normal:'seeFloor'
}
},
methods: {
outVal() {
console.log(`dataVar:${this.dataVar},outerVar:${outerVar}`)
},
changeVal() {
this.dataVar += 9
outerVar += 2
},
短边为准填充,顶部对齐
- img元素设置object-fit:cover;会导致短边为准填充,但居中对齐
- 想要实现顶部对齐,需要
<div class="card-info">
<div class="cover-img"
:style="`background-image: url(${coverImage});`"
v-if="coverImage">
</div>
<!-- <img :src="coverImage"
alt=""
v-if="coverImage"> -->
<img src="@/assets/no-pic.png"
alt=""
v-else>
</div>
.card-info {
margin: 8px 0 12px;
height: 155px;
width: 200px;
.cover-img {
background-size: cover;
background-position: top;
height: 100%;
width: 100%;
}
// img {
// width: 100%;
// height: 100%;
// object-fit: cover;
// }
}
element-ui的message组件增加关闭按钮
- this.$message.success('添加成功').showClose = true
svg颜色
- svg里path设置的是 fill="currentColor" 时,外层包裹的元素可以通过class 设置color,使得svg图标继承颜色,若fill已经指定了#ff0000这种颜色,外面class里的color也改变不了它的颜色
<svg data-v-284f8c0e="" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 1024 1024">
<path fill="currentColor" d="M480 480V128a32 32 0 0 1 64 0v352h352a32 32 0 1 1 0 64H544v352a32 32 0 1 1-64 0V544H128a32 32 0 0 1 0-64z"></path>
</svg>
file对象的type不可信
- file对象的type和文件后缀名对应,改了后缀名,type就会变
- 前端校验格式,校验后缀名即可,type里的mime类型和文件后缀对应
- 真正校验类型,还需要后端判断
pickFile(e) {
let file = e.target.files[0]
e.target.value = ''
const type = file.name.match(/\.(?:\w+)$/)
const isType = this.acceptTypes.includes(type[0])
if (!isType) {
this.$message.error('不支持上传该类型文件!')
return
}
const overSize = file.size / 1024 / 1024 > this.sizeLimit
if (overSize) {
this.$message.error(`文件大小不可超过${this.typeObj.size}MB!`)
return
}
this.upLoadGetUrl(file)
},
ajax请求个数限制
- 在Chrome浏览器中同域名同一时间请求的最大并发数为6个
- 分片上传大文件时要注意,拆的太多时,要搞个请求池
文字匹配高亮展示
- mark.js必须要有容器挂载文本后,才能高亮该文本,太死板
- 下面这个是通义千问生成的,由所有的关键字以或的关系生成正则,可防止replaceAll的多次重复匹配之类的问题
-
/[.*+?^${}()|[]\]/g
: 这是一个正则表达式,用来匹配所有需要转义的特殊字符。这些特殊字符包括.
(任何单个字符)、*
(前面的字符重复任意次)、+
(前面的字符重复至少一次)、?
(前面的字符出现0次或1次)、^
(字符串的开始)、$
(字符串的结束)、()
(捕获组)、|
(或者)、[]
(字符集)和 ``(转义字符)。'\$&'
: 在替换字符串中,`` 是转义字符,$&
表示匹配到的内容本身。因此'\$&'
的意思是保留匹配到的特殊字符但先对其进行转义,即在特殊字符前加上反斜杠。- 如果
keywords
数组为["hello", "world"]
,那么生成的正则表达式将为/hello|world/gi
,这可以匹配任何包含 "hello" 或 "world" 的文本,不论大小写如何。
function highlightKeywords(text, keywords) {
// 创建一个正则表达式,用于匹配所有关键字
const regex = new RegExp(keywords.map(kw => kw.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')).join('|'), 'gi');
// 使用正则表达式替换关键字
const highlightedText = text.replace(regex, function (match) {
return '<span class="highlight">' + match + '</span>';
});
// 文本输入,富文本展示,换行要注意
return highlightedText.replaceAll('\n', '<br />');
}
// 示例使用
const originalText = "This is some sample text with important words and another important sentence here.";
const keywords = ["important", "sample"];
const highlightedText = highlightKeywords(originalText, keywords);
console.log(highlightedText);
vuex的Module说明
- 默认情况下,模块内部的 action、mutation 和 getter 是注册在全局命名空间的——这样使得多个模块能够对同一 mutation 或 action 作出响应。
- 注意,state默认是模块私有的,并不会在全局
- 使用命名空间,
namespaced: true
后 getter、action 及 mutation会自动根据模块注册的路径调整命名
加命名空间后
mapState
,mapGetters
,mapActions
和mapMutations
store.state.muduleName.stateName
computed:{
...mapState('moduleName',['ver1','ver2'])
...mapState('moduleName',{ver2:'ver1'}) //map同时重命名
...mapGeeters('moduleName',['ver1','ver2'])
}
methods:{
...mapActions('some/nested/module', ['foo','bar'])
...mapMutations('moduleName',['methodsName'])
}
- getter 的第 三和第四参数
rootState
和rootGetters
是全局的 - action的
context
对象里包含rootState
和rootGetters
属性 - 需要在全局命名空间内分发 action 或提交 mutation,将
{ root: true }
作为第三参数传给dispatch
或commit
即可。 - action,可添加
root: true
注册为全局 action
modules: {
foo: {
namespaced: true,
getters: {
// 在这个模块的 getter 中,`getters` 被局部化了
// 你可以使用 getter 的第四个参数来调用 `rootGetters`
someGetter (state, getters, rootState, rootGetters) {
getters.someOtherGetter // -> 'foo/someOtherGetter'
rootGetters.someOtherGetter // -> 'someOtherGetter'
},
},
actions: {
// 在这个模块中, dispatch 和 commit 也被局部化了
// 他们可以接受 `root` 属性以访问根 dispatch 或 commit
someAction ({ dispatch, commit, getters, rootGetters }) {
getters.someGetter // -> 'foo/someGetter'
rootGetters.someGetter // -> 'someGetter'
dispatch('someOtherAction') // -> 'foo/someOtherAction'
dispatch('someOtherAction', null, { root: true }) // -> 'someOtherAction'
commit('someMutation') // -> 'foo/someMutation'
commit('someMutation', null, { root: true }) // -> 'someMutation'
},
someOtherAction (ctx, payload) { ... }
}
}
}
拖动更换列表位置方案
- vuedraggable github.com/SortableJS/…
- 加过渡动画 github.com/SortableJS/…
qrcode.js的坑
- 生成二维码,指定类型后,生成的其实是base64
- 虽然a标签href赋值base64也能下载,最好还是变成blob然后URL.createObjectURL变成链接再下载
- 如果指定生成的是image/jpeg,注意a标签download指定的文件名要加后缀 xx.jpg 不然可能存成jfif
- www.npmjs.com/package/qrc…
- www.cnblogs.com/zhanglw456/…
import QRCode from 'qrcode'
QRCode.toDataURL(totalUrl, {
errorCorrectionLevel: 'M',
type: 'image/png',
width: 500,
height: 500,
margin: 0,
})
.then((base64Url) => {
const blob = base64ToBlob(base64Url)
this.picUrl = URL.createObjectURL(blob)
})
.catch((err) => {
console.error(err)
})
export function downloadUrl(url, fileName = 'download') {
const aEl = document.createElement('a');
aEl.href = url
aEl.download = fileName
aEl.style.display = "none";
document.body.appendChild(aEl);
aEl.click();
aEl.remove();
}
export function downloadUrlAndClear(url, fileName = 'download') {
downloadUrl(url, fileName)
window.URL.revokeObjectURL(url)
}
export function base64ToBlob(base64) {
// 去除头部信息(如 "data:image/png;base64,")
const parts = base64.split(';base64,');
const contentType = parts[0].split(':')[1];
const byteCharacters = atob(parts[1]);
// 将 base64 字符串转换为字节数组
const byteArrays = [];
for (let offset = 0; offset < byteCharacters.length; offset += 1024) {
const slice = byteCharacters.slice(offset, offset + 1024);
const byteNumbers = new Array(slice.length);
for (let i = 0; i < slice.length; i++) {
byteNumbers[i] = slice.charCodeAt(i);
}
const byteArray = new Uint8Array(byteNumbers);
byteArrays.push(byteArray);
}
// 创建 Blob 对象
return new Blob(byteArrays, { type: contentType });
}
节流函数
- 定时器写法
function throttle(fn, millisecond) {
let canRun = true; // 标记是否可以立即执行
return function () {
if (canRun) { // 如果可以立即执行
fn.apply(this, arguments); // 立即执行原函数
canRun = false; // 设置为不可以立即执行
setTimeout(() => { // 启动定时器
canRun = true; // 定时器完成后重置标记
}, millisecond);
}
}
}
- 时间戳写法
function throttle(fn, millisecond) {
let start = 0; // 先把开始时间定义为 0
return function () {
let end = Date.now(); // 结束时间直接获取当前
if ((end - start) >= millisecond) { // 然后结束时间 减去 开始时间 如果是大于等于时间参数则可以执行(第一次肯定会立刻执行,因为start为0)
start = end; // 这里结束时间 赋值 给开始时间,为一下次执行做准备
fn.apply(this, arguments);
}
}
}
跨域访问localStorage//待验证
- 设计一个子页面,专门接收数据设置读取,作为其它页面共同的数据源
- 子页面 son.com
<!DOCTYPE html>
<html lang="en-US">
<head>
<script type="text/javascript">
window.addEventListener(
"message",
function (event) {
if (event.origin === "https://father.com") {
const { key } = event.data;
const value = localStorage.getItem(key);
event.source.postMessage({ wallets: wallets }, event.origin);
}
},
false
);
</script>
</head>
<body>
This page is for sharing localstorage.
</body>
</html>
- 父组件里的写法 father.com
<iframe id="son-iframe" src="https://son.com/page1.html" style="display:none;"></iframe>
<script type="text/javascript">
window.onload = function () {
const sonIframe = document.getElementById("son-iframe");
sonIframe.contentWindow.postMessage({ key: "item1" }, "https://father.com");
};
window.addEventListener(
"message",
function (event) {
if (event.origin === "https://son.com") {
console.log(event.data);
}
},
false
);
</script>
postMessage: developer.mozilla.org/zh-CN/docs/…
语法
otherWindow.postMessage(message, targetOrigin, [transfer]);
-
其他窗口的一个引用,比如 iframe 的 contentWindow 属性、执行window.open返回的窗口对象、或者是命名过或数值索引的window.frames。
-
将要发送到其他 window 的数据。它将会被结构化克隆算法序列化。这意味着你可以不受什么限制的将数据对象安全的传送给目标窗口而无需自己序列化。[1]
-
通过窗口的 origin 属性来指定哪些窗口能接收到消息事件,其值可以是字符串"*"(表示无限制)或者一个 URI。在发送消息的时候,如果目标窗口的协议、主机地址或端口这三者的任意一项不匹配 targetOrigin 提供的值,那么消息就不会被发送;只有三者完全匹配,消息才会被发送。这个机制用来控制消息可以发送到哪些窗口;例如,当用 postMessage 传送密码时,这个参数就显得尤为重要,必须保证它的值与这条包含密码的信息的预期接受者的 origin 属性完全一致,来防止密码被恶意的第三方截获。*如果你明确的知道消息应该发送到哪个窗口,那么请始终提供一个有确切值的 targetOrigin,而不是 。不提供确切的目标将导致数据泄露到任何对数据感兴趣的恶意站点。
-
transfer
可选是一串和 message 同时传递的
Transferable
对象。这些对象的所有权将被转移给消息的接收方,而发送一方将不再保有所有权。