albertgao.common-electron
Version:
个人常用的nodejs/electron模块
539 lines (531 loc) • 17.7 kB
JavaScript
const exportObject = {
/**
*
* @returns {string} 获得的uuid
*/
getUuid: () => `${new Date().getTime()}-${~~(Math.random() * 9999999999)}`,
/**
* 获取对象的类型
*
* @param {*} obj 要检查的对象
* @returns {string} 'String', 'Object', 'Array'....
*/
getInstance: function (obj) {
const result = Object.prototype.toString.call(obj)
const {
groups: { type }
} = /^\[object\s+(?<type>\w+(\d\w)*)\]$/.exec(result)
return type
},
/**
* 根据最大行数对Array进行分段
*
* @param {*[]}[arr=[]] 传入的Array
* @param {number}[maxRow=[]] 最大行数
* @returns {Array[]} 返回分段后的Array
*/
splitArrByRow: function (arr = [], maxRow = 5) {
maxRow = ~~(((+maxRow) ** 2) ** 0.5) || 1 // 先转换为正整数
const devider = Math.ceil(arr.length / maxRow) // 例如总公共有6个,每份5个,分成2份
if (arr.length > maxRow) {
const arrOut = []
for (let i = 0; i < devider; i++) {
const [from, to] = [i * maxRow, (i + 1) * maxRow]
if (to < arr.length) {
// 如果范围没超,从from截取到to但不含to
arrOut.push(arr.slice(from, to))
} else {
// 如果是最后一列,截取到结尾
arrOut.push(arr.slice(from))
}
}
return arrOut
} else {
return [arr]
}
},
/**
* 根据列数数对Array进行分段
*
* @param {*[]}[arr=[]] 传入的Array
* @param {number}[cols=[]] 列数
* @returns {Array[]} 返回分段后的Array
*/
splitArrByCol: function (arr = [], cols = 2) {
cols = ~~(((+cols) ** 2) ** 0.5) || 1 // 先转换为正整数
const maxRow = Math.ceil(arr.length / cols) // 例如总公共有5个,每份2列,分成3份
if (arr.length > maxRow) {
const arrOut = []
for (let i = 0; i < cols; i++) {
const [from, to] = [i * maxRow, (i + 1) * maxRow]
if (to < arr.length) {
// 如果范围没超,从from截取到to但不含to
arrOut.push(arr.slice(from, to))
} else {
// 如果是最后一列,截取到结尾
arrOut.push(arr.slice(from))
}
}
return arrOut
} else {
return [arr]
}
},
/**
* 将img标签转为base64
*
* @description 在electron, 有些网站需要设置webPreferences-webSecurity为false
* @description 如果需要导出图片到文件需要设置 {encoding: "base64"}
* @see {@link https://www.electronjs.org/zh/docs/latest/api/browser-window#new-browserwindowoptions | BrowserWindow设置}
* @param {Element}img <img>标签
* @returns {object} - .ext 格式(jpg等,不含".") - .data Base64内容 .base64 包含data:image/png;base64开头的文件
*/
getImgBase64: function (img) {
// 创建一个空的canvas元素
const canvas = document.createElement('canvas')
canvas.width = img.width
canvas.height = img.height
// Copy the image contents to the canvas
const ctx = canvas.getContext('2d')
ctx.drawImage(img, 0, 0)
// Get the data-URL formatted image
// Firefox supports PNG and JPEG. You could check img.src to
// guess the original format, but be aware the using "image/jpg"
// will re-encode the image.
const dataURL = canvas.toDataURL('image/png')
const reg = /^data:image\/(?<ext>\w+);base64,/
const {
groups: { ext }
} = reg.exec(dataURL)
return { data: dataURL.replace(reg, ''), ext, base64: dataURL }
},
/**
* 在页面中是否存在某个元素
*
* @param {string}css css选择器
* @returns {boolean} 该元素是否存在
*/
exists: function (css) {
const $ = require('jquery')
return $(css).length > 0
},
/**
* 显示Url窗口,如果需要跳转,则输入回车
*/
showUrl: function () {
const $ = require('jquery')
$(document.body).prepend(
`<div style="position:fixed;width: calc(100vw - 20px);bottom:0px;z-index:9999;background-color:#fff;">
<div class="example-showcase">
<div class="el-input el-input--default el-input-group el-input-group--append">
<input
id="my-preload-common-href"
class="el-input__inner"
type="text"
autocomplete="off"
placeholder="Please input"
/>
<div id="my-preload-common-href-go" class="el-input-group__append" style="cursor:pointer;">转到</div>
</div>
</div>
</div>`
)
let history = ''
setInterval(() => {
history !== window.location.href &&
$('#my-preload-common-href').val(window.location.href)
history = window.location.href
}, 500)
const cb = () => {
let inputVal = $('#my-preload-common-href').val()
;/https?:\/{2}/i.test(inputVal) || (inputVal = 'http://' + inputVal)
window.location.href = inputVal
}
$('#my-preload-common-href-go').on('click', cb)
$('#my-preload-common-href').each((i, n) => {
n.addEventListener('keydown', ({ key }) => /enter/i.test(key) && cb())
})
},
/**
* 创建一个倒计时窗
*
* @param {object}param0 选项
* @param {string}param0.spanId 倒计时内的span的id
* @param {number}param0.duration 倒计时的总毫秒数
* @param {number}param0.interval 倒计时所使用的减小间隔
* @param {Function}param0.countDownMessage (timeLeft) => 用于显示timeleft的函数,返回字符串
* @param {Function}param0.beforeStart 在倒计时开始之前运行的内容
* @param {Function}param0.fail 倒计时取消时运行的内容
* @param {Function}param0.success 倒计时结束时运行的内容
* @param {Function}param0.every 每间隔都运行的内容
* @param {boolean}param0.showCancel 是否显示"取消"
* @param {boolean}param0.showStart 是否显示"立即"
* @param {boolean}param0.showRestart 是否显示"重新"
* @returns {object} [msg=>ElMessage对象] [promise=Promise对象,将会在倒计时结束时resolve]
*/
startCountDown: function ({
spanId = 'my-spider-countdown',
duration = 10 * 1000,
interval = 100,
countDownMessage = (timeLeft) => `剩余${timeLeft}毫秒`,
showCancel = true,
showStart = true,
showRestart = false,
beforeStart = () => 0,
fail = () => 0,
success = () => 0,
every = () => 0
}) {
beforeStart()
const { ElMessage } = require('element-plus')
const { h } = require('vue')
const $ = require('jquery')
const innerMsg = []
const styleConfig = {
class: 'el-button el-button--text',
style: 'display:inline;'
}
showRestart &&
innerMsg.push(
h('span', { id: `${spanId}Restart`, ...styleConfig }, '重新')
)
showStart &&
innerMsg.push(h('span', { id: `${spanId}Start`, ...styleConfig }, '立即'))
showCancel &&
innerMsg.push(
h('span', { id: `${spanId}Cancel`, ...styleConfig }, '取消')
)
const message = h('div', { style: 'line-height:1.5;' }, [
h(
'span',
{ id: `${spanId}`, style: 'margin-right:15px;' },
countDownMessage(duration)
),
...innerMsg
])
const msg = ElMessage({ duration: duration + 10, message })
return {
msg,
promise: new Promise((resolve, reject) => {
let _timeLeft = duration
let _timer = ''
/**
*
*/
function _successCallback() {
clearInterval(_timer)
$(`#${spanId}`).text(countDownMessage(0))
$(`#${spanId}Cancel`).remove()
$(`#${spanId}Start`).remove()
msg.close()
success()
resolve(1)
}
/**
*
*/
function _failCallback() {
clearInterval(_timer)
$(`#${spanId}Cancel`).remove()
$(`#${spanId}Start`).remove()
msg.close()
fail() // 如果点击了取消就运行fail这个function
// eslint-disable-next-line prefer-promise-reject-errors
reject('Countdown Cancelled') // 并且返回catch
}
$(`#${spanId}Restart`).click(() => {
_timeLeft = duration
$(`#${spanId}`).text(countDownMessage(_timeLeft))
})
$(`#${spanId}Start`).click(_successCallback)
$(`#${spanId}Cancel`).click(_failCallback)
_timer = setInterval(() => {
every(_timeLeft)
if (_timeLeft < interval) {
_successCallback()
} else {
_timeLeft -= interval
$(`#${spanId}`).text(countDownMessage(_timeLeft))
}
}, interval)
})
}
},
/**
* 等待多少ms
*
* @param {number} interval 单位毫秒
* @returns {Promise} undefined
*/
sleep: function (interval) {
return new Promise((resolve) => {
setTimeout(resolve, interval)
})
},
/**
* 在数字前面补0,如果数字已经够大了,就不补了
*
* @param {number|string} num - 要计算的数字
* @param {number} minLength - 补到多少位数
* @returns {string} - 补充完0的字符串
*/
keep0s(num, minLength) {
const isMinus = +num < 0
let str = (isMinus ? -num : num) + ''
const { length: len } = str.split('.')[0]
if (len < minLength) {
str =
Array.from({ length: minLength - len })
.map(() => '0')
.join('') + str
}
return `${isMinus ? '-' : ''}${str}`
},
/**
* @function 将60进制字符串转化成秒
* @param {object|string}options 第一个参数可以使选项,也可以是时间字符串
* @param {number}[options.base=60] - 多少进制
* @param {string|RegExp}[options.delimeter=/[::]{1}/g] - 分割符
* @param {...string} args 60进制时间字符串
* @returns {number} 总秒数
*/
secOf60s(options, ...args) {
const times = [options, ...args] // 按值传递
// 第一个参数是否为Object也就是选项
const isFirstArgOptions =
Object.prototype.toString.call(times[0]) === '[object Object]'
const { base = 60, delimeter = /[::]{1}/g } = isFirstArgOptions
? times.shift()
: {}
const reg = /^\s*-\s*/g
let secs = 0 // 总秒数
for (const str of times) {
const sgn = reg.test(str) ? -1 : 1 // 是否为负
if (str) {
// eslint-disable-next-line no-unused-vars
const arr = [
...str.replace(reg, '').replace(/\s+/g, '').split(delimeter)
]
.reverse()
.forEach((ele, i) => (secs += base ** i * sgn * +ele))
}
}
return secs
},
/**
* @function 将总秒数转化为60进制字符串
* @param {object|number}options - 第一个参数可以是选项,也可以是秒数
* @param {number}[options.digits=0] - 保留小数点后几位
* @param {number}[options.colons=1] - 用几个冒号分割
* @param {number}[options.base=60] - 多少进制
* @param {string}[options.delimeter=":"] - 分割符
* @param {boolean}[options.keep0=true] - 是否补0,直到和base的位数一致
* @param {...number} args - 数字
* @returns {string}返回计算结果
*/
to60sFromSec(options, ...args) {
const times = [options, ...args] // 按值传递
// 第一个参数是否为Object也就是选项
const isFirstArgOptions =
Object.prototype.toString.call(times[0]) === '[object Object]'
const {
digits = 0, //
colons = 1,
base = 60,
delimeter = ':',
keep0 = true
} = isFirstArgOptions ? times.shift() : {}
let total = 0 // 总共有多少秒
if (Object.prototype.toString.call(times) === '[object Array]') {
for (const time of times) {
total += time
}
} else {
total = +times
}
const isMinus = total < 0
isMinus && (total = -total) // 如果为负数,则变为正数
let arr = Array.from({ length: colons + 1 })
.map((_, i) => i)
.reverse()
.map((i) => {
if (i === 0) {
return `${~~(total * 10 ** digits) / 10 ** digits}`
} else {
const floored = Math.floor(total / base ** i)
total %= base ** i
return floored + ''
}
})
if (keep0) {
arr = arr.map((e) => exportObject.keep0s(e, `${~~base}`.length))
}
return `${isMinus ? '-' : ''}${arr.join(delimeter)}`
},
/**
* @param {object|string|number}options - 第一个参数可以是选项,也可以是秒数
* @param {number}[options.digits=0] - 保留小数点后几位
* @param {number}[options.colons=1] - 用几个冒号分割
* @param {number}[options.base=60] - 多少进制
* @param {string}[options.delimeter=":"] - 分割符
* @param {boolean}[options.keep0=true] - 是否补0,直到和base的位数一致
* @param {...string|number} args 要加的数字或字符串
* @returns {string}计算结果
*/
sumOf60s(options, ...args) {
let total = 0
const strs = [...arguments].filter(
(e) => Object.prototype.toString.call(e) === '[object String]'
) // 筛选出字符串部分
const nums = [...arguments].filter(
(e) => Object.prototype.toString.call(e) === '[object Number]'
) // 筛选出数字部分
nums.forEach((e) => (total += e)) // 计算数字部分相加结果
if (Object.prototype.toString.call(options) === '[object Object]') {
// 如果有options
total += exportObject.secOf60s(options, ...strs)
return exportObject.to60sFromSec(options, total)
} else {
// 如果不带options
total += exportObject.secOf60s(...strs)
return exportObject.to60sFromSec(total)
}
},
/**
* 判断是否为HTMl元素
*
* @param {*}obj 任何变量
* @returns {boolean} 是否为html元素
*/
isHTMLElement(obj) {
try {
const d = document.createElement('div')
d.appendChild(obj.cloneNode(true))
return obj.nodeType === 1
} catch (e) {
try {
return obj === window || obj === document
} catch (error) {
return false
}
}
},
getWsAddress() {
const { protocol, host } = location
const wsProtocol = protocol.replace('http', 'ws')
return `${wsProtocol}//${host}`
},
/**
* 批量处理ws.onmessage
*
* @param {Function[]}handlers (msg)=>{ ...todo}其中msg已经parse过了
* @param {WebSocket}ws 传入一个weWebSocket实例
*/
wsOn(handlers = [], ws = new WebSocket(exportObject.getWsAddress())) {
ws.onmessage = ({ data }) => {
for (const func of handlers) {
try {
func(JSON.parse(data))
} catch (error) {
func(data)
}
}
}
},
/**
* 封装 ws.send
*
* @param {*} msg 向服务器发送的对象,会经过JSON.stringify处理
* @param {WebSocket}ws 传入一个weWebSocket实例
*/
wsSend(msg, ws = new WebSocket(exportObject.getWsAddress())) {
ws.send(JSON.stringify(msg))
},
/**
* 由模拟点击来跳转到某个页面
*
* @param {string}[href="/"] 要跳转的link
* @param {string}[target="_blank"] target
*/
clickOpenLink(href = '/', target = '_blank') {
const id = 'a' + exportObject.getUuid()
const $ = require('jquery')
$(document.body).append(`<a id ="${id}" href="${href}"></a>`)
$('#' + id).each((i, n) => n.click())
$('#' + id).remove()
},
/**
* @param {number}percentage 百分比
* @returns {string}根据百分比返回的颜色字符串
*/
getColorByPercentage(percentage) {
if (percentage > 0) {
const { max } = Math
const r = 0 + ((255 - 0) * percentage) / max(percentage, 100)
const g = 240 + ((150 - 240) * percentage) / max(percentage, 100)
const b = 180 + ((101 - 180) * percentage) / max(percentage, 100)
return `rgb(${r},${g},${b})`
} else {
return '#8696a7'
}
},
/**
* 用于element-ui的el-table的filter-method
*
* @param {*} value No discription
* @param {*} row No discription
* @param {*} column No discription
* @returns {boolean} No discription
*/
generalFilterHandle(value, row, column) {
const property = column.property
return row[property] === value
},
/**
* 将Date对象转为字符串
*
* @param {string} [fmt='YY-mm-dd'] <Y,m,d,H,M,S>组成的字符串
* @param {Date} [date=new Date()] Date对象
* @returns {string} 返回日期对应的format之后的字符串
*/
dateFormat(fmt = 'YY-mm-dd', date = new Date()) {
let ret
const opt = {
'Y+': date.getFullYear().toString(), // 年
'm+': (date.getMonth() + 1).toString(), // 月
'd+': date.getDate().toString(), // 日
'H+': date.getHours().toString(), // 时
'M+': date.getMinutes().toString(), // 分
'S+': date.getSeconds().toString() // 秒
// 有其他格式化字符需求可以继续添加,必须转化成字符串
}
for (const k in opt) {
ret = new RegExp('(' + k + ')').exec(fmt)
if (ret) {
fmt = fmt.replace(
ret[1],
ret[1].length === 1 ? opt[k] : opt[k].padStart(ret[1].length, '0')
)
}
}
return fmt
},
/**
*
* @param {string | number | Date} a 被减数
* @param {string | number | Date} b 减数
* @returns {number}计算出两个时间的月份差
*/
getMonthDifference(a, b) {
if (/(string|number)/i.test(a)) {
a = new Date(a)
}
if (/(string|number)/i.test(b)) {
b = new Date(b)
}
return (
a.getFullYear() * 12 + a.getMonth() - b.getFullYear() * 12 - b.getMonth()
)
}
}
module.exports = exportObject