This commit is contained in:
2026-08-04 17:57:01 +08:00
parent 768fd4a3a9
commit f77034edd7
641 changed files with 26137 additions and 0 deletions
+19
View File
@@ -0,0 +1,19 @@
let baseURL = 'https://image.dnftools.com'
// 避免远程开发的时候冲突
if (process.env.NODE_ENV == 'development') {
baseURL = '/image'
}
if (import.meta.env.MODE == 'web' || import.meta.env.MODE == 'show') {
baseURL = 'https://image.dnftools.com'
}
export const getImageURL = (image: string) => {
image = image.startsWith('/') ? image : `/${image}`
return `${baseURL}${image}`
}
export const getLocalImageURL = (url: string) => {
return new URL(url, import.meta.url).href
}
+362
View File
@@ -0,0 +1,362 @@
export function in_range(num: number, start: number, end: number) {
return start <= num && num <= end
}
export function to_percent(num?: number, defaultValue: number = 0, suffix = '', withPlus = false) {
num = num ?? defaultValue
return (num > 0 && withPlus ? '+' : '') + num?.multiply(100).round(2).toString().concat(suffix)
}
export function get_num(num?: number, defaultValue: number = 0) {
return num ?? defaultValue
}
export function format_string(str: string, params: any = {}, ...args: any[]) {
let num = 0
str = str.replace(/{}/g, (match) => {
return args[num++] ?? match
})
str = str.replace(/{\d+}/g, (match) => {
const i = Number.parseInt(match.slice(1, -1))
return args[i] ?? match
})
str = str.replace(/{\w+}/g, (match) => {
const name = match.slice(1, -1)
return params[name] ?? match
})
return str
}
export function format_bytes(bytes: number, decimals = 2) {
if (bytes === 0) {
return '0 Bytes'
}
const k = 1024
const dm = decimals < 0 ? 0 : decimals
const sizes = ['Bytes', 'KB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB']
const i = Math.floor(Math.log(bytes) / Math.log(k))
return `${Number.parseFloat((bytes / k ** i).toFixed(dm))} ${sizes[i]}`
}
export function getFloat(value: any, n: any) {
value = Number(value)
n = n ? Number.parseInt(n) : 0
if (n <= 0) {
return Math.round(value)
}
value = Math.round(value * 10 ** n) / 10 ** n
value = Number(value).toFixed(n)
return value
}
export function cartesian<T = any>(...arr: T[][]): T[][] {
if (arr.length < 2) {
return arr
}
return arr.reduce(
(a, b) => {
const ret: T[][] = []
a.forEach((a) => {
b.forEach((b) => {
ret.push(a.concat([b]))
})
})
return ret
},
[[]] as T[][],
)
}
export function groupBy<T, K>(array: T[], callback: (t: T) => K) {
const map = new Map<K, T[]>()
for (let i = 0; i < array.length; i++) {
const t = array[i]
const key = callback(t)
const temp: T[] = map.get(key) || []
if (!map.has(key)) {
map.set(key, temp)
}
temp.push(t)
}
return Array.from(map.values())
}
export function debounce_last(func: Function, wait: number) {
let timer = Number.NaN
return function () {
if (timer) {
clearTimeout(timer)
}
timer = setTimeout(func, wait)
}
}
export function padding(num: number | string, length: number): string {
if (`${num}`.length >= length) {
return `${num}`
}
return padding(`0${num}`, length)
}
export function format_float(f: number, digit: number = 2) {
const m = 10 ** digit
return (f * m).round(10) / m
}
export function mapRecord<T extends keyof any, K>(
status: Record<T, K>,
callback: (value: K, key: T) => K,
) {
const new_status = {} as Record<T, K>
for (const key in status) {
const value = callback(status[key], key)
if (value) {
new_status[key] = value
}
}
return new_status
}
Array.prototype.max = function <T>(callback: (t: T) => number): [number, T | undefined] {
let rs: [number, T | undefined] = [-1, undefined]
let max = -1
for (let i = 0; i < this.length; i++) {
const item = this.at(i) as T
const count = callback(item)
if (count > max) {
max = count
rs = [i, item]
}
}
return rs
}
Array.prototype.min = function <T>(callback: (t: T) => number): [number, T | undefined] {
let rs: [number, T | undefined] = [-1, undefined]
let min = -1
for (let i = 0; i < this.length; i++) {
const item = this.at(i) as T
const count = callback(item)
if (count < min) {
min = count
rs = [i, item]
}
}
return rs
}
Number.prototype.multiply = function (value: number) {
return Number(this) * value
}
Number.prototype.round = function (digit?: number) {
const m = 10 ** (digit ?? 0)
return Math.round(Number(this) * m) / m
}
export function formatStr(template: string, ...params: any) {
let str = template
if (params.length == 1 && params[0].length > 0) {
params = params[0]
}
for (let i = 0; i < params.length; i++) {
str = str.replaceAll(`{${i}}`, params[i])
}
return str
}
export function toObj(data: any): any {
const temp = {}
for (const key in data) {
try {
if (typeof data[key] === 'number' || typeof data[key] === 'string') {
Object.defineProperty(temp, key, {
value: data[key],
writable: true,
enumerable: true,
configurable: true,
})
} else {
Object.defineProperty(temp, key, {
value: RecordToObj(data[key]),
writable: true,
enumerable: true,
configurable: true,
})
}
} catch {
Object.defineProperty(temp, key, {
value: data[key],
writable: true,
enumerable: true,
configurable: true,
})
}
}
return temp
}
export function toMap(data: any, except: string[] = []) {
const temp = {}
for (const key in data) {
if (typeof data[key] === 'object' && data[key].constructor != Array && !except.includes(key)) {
// 需要转换部分,不能是Array,也会被转换
const sub = {}
for (const subkey in data[key]) {
Object.defineProperty(sub, subkey, {
value: new Map(Object.entries(data[key][subkey])),
writable: true,
enumerable: true,
configurable: true,
})
}
Object.defineProperty(temp, key, {
value: sub,
writable: true,
enumerable: true,
configurable: true,
})
} else {
Object.defineProperty(temp, key, {
value: data[key],
writable: true,
enumerable: true,
configurable: true,
})
}
}
return temp
}
export function RecordToObj(todo: Record<string, Map<any, any>>) {
const temp = {}
for (const key in todo) {
Object.defineProperty(temp, key, {
value: Object.fromEntries(todo[key]),
writable: true,
enumerable: true,
configurable: true,
})
}
return temp
}
export function getUuid() {
if (typeof crypto === 'object') {
if (typeof crypto.randomUUID === 'function') {
return crypto.randomUUID()
}
if (typeof crypto.getRandomValues === 'function' && typeof Uint8Array === 'function') {
const callback = (c: any) => {
const num = Number(c)
return (num ^ (crypto.getRandomValues(new Uint8Array(1))[0] & (15 >> (num / 4)))).toString(
16,
)
}
return '10000000-1000-4000-8000-100000000000'.replace(/[018]/g, callback)
}
}
let timestamp = new Date().getTime()
let perforNow =
(typeof performance !== 'undefined' && performance.now && performance.now() * 1000) || 0
return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, (c) => {
let random = Math.random() * 16
if (timestamp > 0) {
random = Math.trunc((timestamp + random) % 16)
timestamp = Math.floor(timestamp / 16)
} else {
random = Math.trunc((perforNow + random) % 16)
perforNow = Math.floor(perforNow / 16)
}
return (c === 'x' ? random : (random & 0x3) | 0x8).toString(16)
})
}
export function rarityClass(type: string) {
switch (type) {
case '太初':
return 'primeval'
case '史诗':
return 'epic'
case '神话':
return 'myth'
case '智慧产物':
return 'epic'
case '神器':
return 'artifact'
case '稀有':
return 'rare'
case '传说':
return 'legend'
case '勇者':
return 'chronicle'
case '高级':
return 'advanced'
default:
return 'epic'
}
}
export function formatAttr(type: string, value: number) {
switch (type) {
case 'STR':
return `力量 ${value}`
case 'INT':
return `智力 ${value}`
case 'Vitality':
return `体力 ${value}`
case 'Spirit':
return `精神 ${value}`
case 'AtkP':
return `物理攻击力 ${value}`
case 'AtkM':
return `魔法攻击力 ${value}`
case 'AtkI':
return `独立攻击力 ${value}`
case 'SkillAttack':
return `技能伤害 增加 ${(value * 100).toFixed(2)}%`
case 'Attack':
return `攻击强化 +${value.toFixed(1)}%`
case 'Buffer':
return `增益量 ${value}`
default:
return `${type} ${value}`
}
}
export const decrypt = (encryptedData: string) => {
try {
encryptedData = decodeURIComponent(encryptedData)
.split('')
.map((a) => {
if (a == '=') {
return a
} else {
return String.fromCharCode(a.charCodeAt(0) - 1)
}
})
.join('')
const decryptedData = atob(encryptedData)
const len = decryptedData.length
const decryptedBinaryOffset = decryptedData.slice(0, len - 13)
const decryptedTimestamp = decryptedData.slice(len - 13, len)
return {
uid: parseInt(decryptedBinaryOffset, 2),
time: parseInt(decryptedTimestamp),
}
} catch {
// console.log(e)
return {
uid: -1,
time: 0,
}
}
}
+112
View File
@@ -0,0 +1,112 @@
import type { MaybeRef } from 'vue'
import querystring from 'query-string'
import { h, isRef } from 'vue'
import { useRouter } from 'vue-router'
import { useDialog } from '@/components/hooks/dialog'
interface UseOpenOption {
/*
*/
width?: number
height?: number
scale?: number
query?: Record<string, string | string[] | undefined>
/**
* 是否打开新页面
*/
target?: '_blank' | '_self' | false
title?: string
}
export function useOpenWindow() {
const { show, close, randomId } = useDialog()
const router = useRouter()
return async (
maybeUrl: MaybeRef<string> | (() => string),
{
width = 0,
height = 0,
target = '_self',
query = {},
title = 'DNF计算器 & Colg',
scale = 1.0,
}: UseOpenOption = {},
) => {
let url = isRef(maybeUrl)
? maybeUrl.value
: typeof maybeUrl === 'function'
? maybeUrl()
: maybeUrl
if (!url) {
return
}
if (url.startsWith('/')) {
url = router.resolve(url).href
}
query.r = `${Math.random()}`
url = querystring.stringifyUrl({ url, query })
try {
let _target = target
if (!_target) {
const rs = await show({
content: '请确认打开新页面的方式',
okButton: '当前窗口',
rejectButton: '新窗口',
cancelButton: true,
defaultStatus: 'cancel',
})
if (rs.isOk) {
_target = '_self'
} else if (rs.isReject) {
_target = '_blank'
}
}
if (_target == '_blank' || width * height < 1) {
window.open(url, _target as string)
} else if (_target == '_self') {
const id = randomId()
const onClose = (e: MessageEvent) => {
if (e.data == 'close') {
close(id)
}
}
window.addEventListener('message', onClose)
const temp = await show({
id,
header: title,
mask: true,
scale,
content() {
return h('iframe', {
name: Math.random().toString(36).slice(2),
referrerpolicy: 'no-referrer',
frameborder: 0,
src: url,
style: {
width: `${width}px`,
height: `${height}px`,
},
})
},
})
window.removeEventListener('message', onClose)
return temp
}
} catch (err) {
// let routerURL = router?.resolve({
// path: url
// })
// window.open(routerURL?.href)
// router.push(url)
// 网页端打开
router?.push(url)
console.error(err)
}
}
}
+32
View File
@@ -0,0 +1,32 @@
export function resolveTeleportTarget(target?: string | Element | null) {
if (typeof window === 'undefined') {
return null
}
if (typeof target === 'string') {
return document.querySelector(target)
}
return target ?? document.body
}
export function getElementZoom(target?: string | Element | null) {
if (typeof window === 'undefined') {
return 1
}
let zoom = 1
let current = resolveTeleportTarget(target) as HTMLElement | null
while (current) {
const currentZoom = Number.parseFloat(window.getComputedStyle(current).zoom)
if (Number.isFinite(currentZoom) && currentZoom > 0) {
zoom *= currentZoom
}
current = current.parentElement
}
return zoom || 1
}