quicktab
Version:
Multi IFrame tab plugin. operate IFrame like operating browser tabs
442 lines (377 loc) • 11.7 kB
JavaScript
import * as v from 'valibot'
export default {
/**
* 通过字符串创建节点
* @param htmlStr
* @returns {Element}
*/
createNode(htmlStr) {
const node = document.createElement('div')
node.innerHTML = htmlStr.trim() // 去除字符串两端的空白
return node.firstElementChild
},
/**
* 判断是否是同源的、可以操作 contentWindow 对象的 iframe
* @param {HTMLIFrameElement} iframe - 要检查的 iframe 元素
* @returns {boolean} - 如果是同源且可以操作 contentWindow 返回 true,否则返回 false
*/
isSameOriginIframe(iframe) {
try {
// 尝试获取 iframe 的 contentWindow
const contentWindow = iframe.contentWindow
// 检查是否有跨域安全性限制
return contentWindow.location.origin === window.location.origin
} catch (error) {
// 处理可能的异常,比如跨域安全性限制
return false
}
},
/**
* 给一个元素的某些特定后代元素设置属性
*/
setProperty(element, selectorArr, name, value) {
if (!Array.isArray(selectorArr)) {
console.error('Invalid arguments')
return
}
const type = !(element instanceof Element)
if (type) {
const tempElement = document.createElement('div')
tempElement.innerHTML = element
element = tempElement
}
element.querySelectorAll(selectorArr).forEach((tabBarItem) => {
tabBarItem.style.setProperty(name, value)
})
return type ? element.innerHTML : element
},
sprintf(_str, ...args) {
let flag = true
let i = 0
const str = _str.replace(/%s/g, () => {
const arg = args[i++]
if (typeof arg === 'undefined') {
flag = false
return ''
}
return arg
})
return flag ? str : ''
},
isStr(str) {
return Object.prototype.toString.call(str) === '[object String]'
},
//数组对象去重
arrUnique(arr, objKey) {
//临时数组
let temp = []
return arr.reduce(function (prev, curr) {
if (temp.indexOf(curr[objKey]) === -1) {
temp.push(curr[objKey])
prev.push(curr)
}
return prev
}, [])
},
// 类似jQuery的$(document).ready(function () {});
ready(callback) {
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', callback)
} else {
callback()
}
},
//可以让单击事件具备双击的能力
handleClickAndDoubleClick(clickCallback, dbClickCallback, options) {
let clicks = 0
let timer = null
// 默认配置
const Default = {
//延时
delay: 200,
//单机事件是是否阻止默认事件
clickPreventDefault: false,
clickStopPropagation: false,
dbClickPreventDefault: false,
dbClickStopPropagation: false,
}
options = Object.assign(Default, typeof options === 'object' && options)
return function (event) {
if (typeof dbClickCallback !== 'function') {
//如果参数2没有传递,那么直接使用真实的单击
options.clickPreventDefault && event.preventDefault()
options.clickStopPropagation && event.stopPropagation()
typeof clickCallback === 'function' && clickCallback.call(this, event)
} else {
clicks++
if (clicks === 1) {
//模拟单击
options.clickPreventDefault && event.preventDefault()
options.clickStopPropagation && event.stopPropagation()
timer = setTimeout(() => {
typeof clickCallback === 'function' &&
clickCallback.call(this, event)
clicks = 0
}, options.delay)
} else {
//模拟双击
options.dbClickPreventDefault && event.preventDefault()
options.dbClickStopPropagation && event.stopPropagation()
clearTimeout(timer)
typeof dbClickCallback === 'function' &&
dbClickCallback.call(this, event)
clicks = 0
}
}
}
},
isJSON(str) {
try {
JSON.parse(str)
return true
} catch (e) {
return false
}
},
parseAttributeValue(value) {
try {
return JSON.parse(value)
} catch (error) {
// 尝试获取全局函数
const globalFunction = window[value]
if (typeof globalFunction === 'function') {
return globalFunction
}
return value
}
},
//对几个需要转换成字符串属性进行处理
stringTypeOptions(obj) {
const objKey = ['minHeight', 'height', 'width']
objKey.forEach((item) => {
this.updateObjDataByKey(
obj,
item,
this.getObjDataByKey(obj, item).toString(),
)
})
return obj
},
//解析data上的选项
parseDataOptions(element, defaultOption, prefix = '', options = {}) {
for (const key in defaultOption) {
const attrKey = prefix + key
let attrVal = element.getAttribute(attrKey)
let parseVal = this.parseAttributeValue(attrVal)
if (
Object.prototype.toString.call(defaultOption[key]) !== '[object Object]'
) {
if (attrVal !== null) {
//如果获取到值了
options[key] = parseVal
}
} else {
this.parseDataOptions(
element,
defaultOption[key],
attrKey + '-',
(options[key] = {}),
)
if (Object.keys(options[key]).length === 0) {
//如果是空对象
delete options[key]
}
}
}
return options
},
//判断数组的对象元素中的某个key是否具有唯一值
hasDuplicateValues(arr, key) {
const valueSet = new Set()
for (const obj of arr) {
const value = obj[key]
if (valueSet.has(value)) {
// 发现重复的值
return true
}
valueSet.add(value)
}
// 没有重复的值
return false
},
onResize(element, callback, options) {
const Default = {
//是否立即执行
immediate: false,
//监听变化类型 width height both
type: 'both',
}
options = Object.assign(Default, typeof options === 'object' && options)
const resizeObserver = new ResizeObserver((entries) => {
// 处理大小变化的回调函数
entries.forEach((entry) => {
if (options.immediate === false) {
// entry.target 是发生大小变化的元素 entry.contentRect 包含元素的新大小信息
if (!entry.target.firstResize) {
//优化:第一次不执行
entry.target.firstResize = true
return
}
}
// 获取当前元素的宽度和高度
const newWidth = entry.contentRect.width
const newHeight = entry.contentRect.height
// 获取之前保存的宽度和高度,如果没有保存过则设置为初始值0
const oldWidth = entry.target.__resizeObserverLastWidth || 0
const oldHeight = entry.target.__resizeObserverLastHeight || 0
// 保存当前宽度和高度以备下次比较
entry.target.__resizeObserverLastWidth = newWidth
entry.target.__resizeObserverLastHeight = newHeight
// 只在宽度变化时触发处理逻辑
if (newWidth !== oldWidth && newHeight === oldHeight) {
options.type === 'width' && callback.call(element, entry)
}
if (newHeight !== oldHeight && newWidth === oldWidth) {
options.type === 'height' && callback.call(element, entry)
}
options.type === 'both' && callback.call(element, entry)
})
})
resizeObserver.observe(element)
return resizeObserver
},
// 获取开启和激活的选项
getEnabledAndSortedOpsKey(options, keyClassMap) {
return Object.keys(options)
.filter((key) => {
return (
Object.keys(keyClassMap).includes(key) &&
options[key].enable !== false
)
})
.sort((a, b) => options[a].order - options[b].order)
},
isDOMElement(obj) {
return obj instanceof Element || obj instanceof Document
},
//实现类似jquery的prevAll
prevAll(element) {
const result = []
let currentElement = element.previousElementSibling
while (currentElement) {
result.push(currentElement)
currentElement = currentElement.previousElementSibling
}
return result
},
//实现类似jquery的nextAll
nextAll(element) {
const result = []
let currentElement = element.nextElementSibling
while (currentElement) {
result.push(currentElement)
currentElement = currentElement.nextElementSibling
}
return result
},
// 获取元素在父元素中的index
index(el) {
let index = 0
if (!el || !el.parentNode) {
return -1
}
// previousElementSibling:上一个兄弟元素
while (el && (el = el.previousElementSibling)) {
index++
}
return index
},
// 触发动画
animate(prevRect, target) {
let ms = 300
if (ms) {
let currentRect = target.getBoundingClientRect()
if (prevRect.nodeType === 1) {
prevRect = prevRect.getBoundingClientRect()
}
target.style.setProperty('transition', 'none')
target.style.setProperty(
'transform',
`translate3d(${prevRect.left - currentRect.left}px,${
prevRect.top - currentRect.top
}px,0)`,
)
target.offsetWidth // 触发重绘
target.style.setProperty('transition', `transform ${ms}ms`)
target.style.setProperty('transform', 'translate3d(0,0,0)')
// 时间到了之后把transition和transform清空
clearTimeout(target.animated)
target.animated = setTimeout(function () {
target.style.setProperty('transition', '')
target.style.setProperty('transform', '')
target.animated = false
}, ms)
}
},
timeAgo(
timestamp,
customText = {
second: '秒前',
minutes: '分钟前',
hours: '小时前',
days: '天前',
months: '月前',
years: '年前',
},
) {
const seconds = Math.floor((Date.now() - Math.floor(timestamp)) / 1000)
const minute = 60
const hour = 60 * minute
const day = 24 * hour
const month = 30 * day
const year = 365 * day
if (seconds < minute) {
return `${seconds} ${customText.second}`
} else if (seconds < hour) {
const minutes = Math.floor(seconds / minute)
return `${minutes} ${customText.minutes}`
} else if (seconds < day) {
const hours = Math.floor(seconds / hour)
return `${hours} ${customText.hours}`
} else if (seconds < month) {
const days = Math.floor(seconds / day)
return `${days} ${customText.days}`
} else if (seconds < year) {
const months = Math.floor(seconds / month)
return `${months} ${customText.months}`
} else {
const years = Math.floor(seconds / year)
return `${years} ${customText.years}`
}
},
//判断数字是否有小数点
hasDecimal(number) {
return !Number.isInteger(number)
},
// 通知程序
notify(msg, type = 'error') {
console[type](`Quicktab:${msg}`)
},
validate(Schema, input) {
// #https://valibot.dev/guides/parse-data/
const result = v.safeParse(Schema, input, {
abortEarly: true,
lang: 'zh-CN',
})
if (result.success) {
return true
} else {
const flatErrors = v.flatten(result.issues).nested
let error = ''
Object.keys(flatErrors).forEach((key) => {
error += `option: [${key}] ${flatErrors[key]}`
})
return error
}
},
}