mirror of
https://github.com/imsyy/SPlayer.git
synced 2025-11-25 11:29:26 +08:00
🦄 refactor: convert to TypeScript #174
This commit is contained in:
277
src/utils/helper.ts
Normal file
277
src/utils/helper.ts
Normal file
@@ -0,0 +1,277 @@
|
||||
import { type ImageRenderToolbarProps, NTooltip, SelectOption } from "naive-ui";
|
||||
import type { SongType, UpdateLogType } from "@/types/main";
|
||||
import { h, VNode } from "vue";
|
||||
import { useClipboard } from "@vueuse/core";
|
||||
import { getCacheData } from "./cache";
|
||||
import { updateLog } from "@/api/other";
|
||||
import { marked } from "marked";
|
||||
import SvgIcon from "@/components/Global/SvgIcon.vue";
|
||||
|
||||
type AnyObject = { [key: string]: any };
|
||||
|
||||
// 必要数据
|
||||
let imageBlobURL: string = "";
|
||||
|
||||
// 环境判断
|
||||
export const isDev = import.meta.env.MODE === "development" || import.meta.env.DEV;
|
||||
|
||||
// 系统判断
|
||||
const userAgent = window.navigator.userAgent;
|
||||
export const isWin = userAgent.includes("Windows");
|
||||
export const isMac = userAgent.includes("Macintosh");
|
||||
export const isLinux = userAgent.includes("Linux");
|
||||
export const isElectron = userAgent.includes("Electron");
|
||||
|
||||
// 链接跳转
|
||||
export const openLink = (url: string, target: "_self" | "_blank" = "_blank") => {
|
||||
window.open(url, target);
|
||||
};
|
||||
|
||||
// 图标渲染
|
||||
export const renderIcon = (
|
||||
iconName: string,
|
||||
option: {
|
||||
size?: number;
|
||||
style?: AnyObject;
|
||||
} = {},
|
||||
) => {
|
||||
const { size, style } = option;
|
||||
return () => {
|
||||
return h(SvgIcon, { name: iconName, size, style });
|
||||
};
|
||||
};
|
||||
|
||||
/**
|
||||
* 延时函数
|
||||
* @param ms 延时时间(毫秒)
|
||||
*/
|
||||
export const sleep = (ms: number): Promise<void> => {
|
||||
return new Promise((resolve) => setTimeout(resolve, ms));
|
||||
};
|
||||
|
||||
// 选项渲染
|
||||
export const renderOption = ({ node, option }: { node: VNode; option: SelectOption }) =>
|
||||
h(
|
||||
NTooltip,
|
||||
{ placement: "left" },
|
||||
{
|
||||
trigger: () => node,
|
||||
default: () => option.label,
|
||||
},
|
||||
);
|
||||
|
||||
// 排序选项
|
||||
export const sortOptions = {
|
||||
default: { name: "默认排序", show: "all", icon: renderIcon("Sort") },
|
||||
titleAZ: { name: "标题升序( A - Z )", show: "all", icon: renderIcon("SortAZ") },
|
||||
titleZA: { name: "标题降序( Z - A )", show: "all", icon: renderIcon("SortZA") },
|
||||
arAZ: { name: "歌手升序( A - Z )", show: "song", icon: renderIcon("SortAZ") },
|
||||
arZA: { name: "歌手降序( Z - A )", show: "song", icon: renderIcon("SortZA") },
|
||||
timeUp: { name: "时长升序", show: "all", icon: renderIcon("SortClockUp") },
|
||||
timeDown: { name: "时长降序", show: "all", icon: renderIcon("SortClockDown") },
|
||||
dateUp: { name: "日期升序", show: "radio", icon: renderIcon("SortDateUp") },
|
||||
dateDown: { name: "日期降序", show: "radio", icon: renderIcon("SortDateDown") },
|
||||
};
|
||||
|
||||
// 模糊搜索
|
||||
export const fuzzySearch = (keyword: string, data: SongType[]): SongType[] => {
|
||||
try {
|
||||
const result: SongType[] = [];
|
||||
const regex = new RegExp(keyword, "i");
|
||||
|
||||
/**
|
||||
* 递归函数:遍历对象及其嵌套属性,过滤包含关键词的对象
|
||||
* @param {Object} obj - 要检查的对象
|
||||
* @returns {boolean} - 如果找到匹配的属性值,返回 true;否则返回 false
|
||||
*/
|
||||
const searchInObject = (obj: AnyObject): boolean => {
|
||||
for (const key in obj) {
|
||||
if (Object.prototype.hasOwnProperty.call(obj, key)) {
|
||||
const value = obj[key];
|
||||
// 如果属性值是对象,则递归调用
|
||||
if (typeof value === "object" && value !== null) {
|
||||
if (searchInObject(value)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
// 检查属性值是否是字符串并包含关键词
|
||||
if (value && typeof value === "string" && regex.test(value)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
};
|
||||
|
||||
if (!data) return [];
|
||||
|
||||
// 如果传入的是数组,遍历数组
|
||||
if (Array.isArray(data)) {
|
||||
for (const item of data) {
|
||||
if (searchInObject(item)) {
|
||||
result.push(item);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// 如果传入的是对象,直接调用递归函数
|
||||
if (searchInObject(data)) {
|
||||
result.push(data);
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
} catch (error) {
|
||||
console.error("模糊搜索出现错误:", error);
|
||||
return [];
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* 将 32 位 ARGB 颜色值转换为 24 位 RGB 颜色值
|
||||
*
|
||||
* @param {number} x - 32位ARGB颜色值
|
||||
* @returns {number[]} - 包含红色、绿色和蓝色分量的24位RGB颜色值数组(0-255)
|
||||
*/
|
||||
export const argbToRgb = (x: number): number[] => {
|
||||
// 提取红色、绿色和蓝色分量
|
||||
const r = (x >> 16) & 0xff;
|
||||
const g = (x >> 8) & 0xff;
|
||||
const b = x & 0xff;
|
||||
// 返回24位RGB颜色值数组
|
||||
return [r, g, b];
|
||||
};
|
||||
|
||||
// 封面加载完成
|
||||
export const coverLoaded = (e: Event) => {
|
||||
const target = e.target as HTMLElement | null;
|
||||
if (target && target.nodeType === Node.ELEMENT_NODE) {
|
||||
target.style.opacity = "1";
|
||||
}
|
||||
};
|
||||
|
||||
// 数字处理
|
||||
export const formatNumber = (num: number): string => {
|
||||
if (num < 10000) {
|
||||
return num.toString();
|
||||
} else if (num < 100000000) {
|
||||
return `${(num / 10000).toFixed(1)}万`;
|
||||
} else {
|
||||
return `${(num / 100000000).toFixed(1)}亿`;
|
||||
}
|
||||
};
|
||||
|
||||
// 文件大小处理
|
||||
export const formatFileSize = (bytes: number): string => {
|
||||
if (bytes < 1024) {
|
||||
return `${bytes} B`;
|
||||
} else if (bytes < 1024 * 1024) {
|
||||
return `${(bytes / 1024).toFixed(1)} KB`;
|
||||
} else if (bytes < 1024 * 1024 * 1024) {
|
||||
return `${(bytes / 1024 / 1024).toFixed(1)} MB`;
|
||||
} else {
|
||||
return `${(bytes / 1024 / 1024 / 1024).toFixed(1)} GB`;
|
||||
}
|
||||
};
|
||||
|
||||
// 将图片链接转为 BlobUrl
|
||||
export const convertImageUrlToBlobUrl = async (imageUrl: string) => {
|
||||
const response = await fetch(imageUrl);
|
||||
if (!response.ok) {
|
||||
throw new Error("Network response was not ok");
|
||||
}
|
||||
// 将响应数据转换为 Blob 对象
|
||||
const blob = await response.blob();
|
||||
// 撤销之前生成的对象 URL
|
||||
if (imageBlobURL) URL.revokeObjectURL(imageBlobURL);
|
||||
// 生成对象 URL
|
||||
imageBlobURL = URL.createObjectURL(blob);
|
||||
return imageBlobURL;
|
||||
};
|
||||
|
||||
// 自定义图片工具栏
|
||||
export const renderToolbar = ({ nodes }: ImageRenderToolbarProps) => {
|
||||
return [
|
||||
nodes.prev,
|
||||
nodes.next,
|
||||
nodes.rotateCounterclockwise,
|
||||
nodes.rotateClockwise,
|
||||
nodes.resizeToOriginalSize,
|
||||
nodes.zoomOut,
|
||||
nodes.zoomIn,
|
||||
nodes.close,
|
||||
];
|
||||
};
|
||||
|
||||
// 复制文本
|
||||
export const copyData = async (text: any, message?: string) => {
|
||||
const { copy, copied, isSupported } = useClipboard({ legacy: true });
|
||||
if (!isSupported.value) {
|
||||
window.$message.error("暂时无法使用复制功能");
|
||||
return;
|
||||
}
|
||||
// 开始复制
|
||||
try {
|
||||
if (!text) return;
|
||||
text = typeof text === "string" ? text.trim() : JSON.stringify(text, null, 2);
|
||||
await copy(text);
|
||||
if (copied.value) {
|
||||
window.$message.success(message ?? "已复制到剪贴板");
|
||||
} else {
|
||||
window.$message.error("复制出错,请重试");
|
||||
}
|
||||
} catch (error) {
|
||||
window.$message.error("复制出错,请重试");
|
||||
console.error("复制出错:", error);
|
||||
}
|
||||
};
|
||||
|
||||
// 获取剪贴板内容
|
||||
export const getClipboardData = async (): Promise<string | null> => {
|
||||
try {
|
||||
const text = await navigator.clipboard.readText();
|
||||
return text;
|
||||
} catch (error) {
|
||||
console.error("Failed to read clipboard content:", error);
|
||||
return null;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* 格式化为 Electron 快捷键
|
||||
* @param shortcut 快捷键
|
||||
* @returns Accelerator
|
||||
*/
|
||||
export const formatForGlobalShortcut = (shortcut: string): string => {
|
||||
return shortcut
|
||||
.split("+")
|
||||
.map((part) => {
|
||||
// 字母
|
||||
if (part.startsWith("Key")) {
|
||||
return part.replace("Key", "");
|
||||
}
|
||||
// 数字
|
||||
if (part.startsWith("Digit")) {
|
||||
return part.replace("Digit", "num");
|
||||
}
|
||||
if (part.startsWith("Numpad")) {
|
||||
return part.replace("Numpad", "num");
|
||||
}
|
||||
// 方向键
|
||||
if (part.startsWith("Arrow")) {
|
||||
return part.replace("Arrow", "");
|
||||
}
|
||||
return part;
|
||||
})
|
||||
.join("+");
|
||||
};
|
||||
|
||||
// 获取更新日志
|
||||
export const getUpdateLog = async (): Promise<UpdateLogType> => {
|
||||
const result = await getCacheData(updateLog, { key: "updateLog", time: 60 });
|
||||
return {
|
||||
version: result.tag_name,
|
||||
changelog: await marked(result.body),
|
||||
time: result.published_at,
|
||||
url: result.html_url,
|
||||
};
|
||||
};
|
||||
Reference in New Issue
Block a user