Files
SPlayer/src/utils/helper.ts
2024-12-23 11:21:22 +08:00

292 lines
8.2 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import type { SongType, UpdateLogType } from "@/types/main";
import { NTooltip, SelectOption } from "naive-ui";
import { h, VNode } from "vue";
import { useClipboard } from "@vueuse/core";
import { getCacheData } from "./cache";
import { updateLog } from "@/api/other";
import { isEmpty } from "lodash-es";
import { convertToLocalTime } from "./time";
import { useSettingStore } from "@/stores";
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 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 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: 10 });
if (!result || isEmpty(result)) return [];
const updateLogs = await Promise.all(
result.map(async (v: any) => ({
version: v.tag_name,
changelog: await marked(v.body),
time: convertToLocalTime(v.published_at),
url: v.html_url,
prerelease: v.prerelease,
})),
);
return updateLogs;
};
/**
* 更改本地目录
* @param delIndex 删除文件夹路径的索引
*/
export const changeLocalPath = async (delIndex?: number) => {
try {
if (!isElectron) return;
const settingStore = useSettingStore();
if (typeof delIndex === "number" && delIndex >= 0) {
settingStore.localFilesPath.splice(delIndex, 1);
} else {
const selectedDir = await window.electron.ipcRenderer.invoke("choose-path");
if (!selectedDir) return;
// 检查是否为子文件夹
const defaultMusicPath = await window.electron.ipcRenderer.invoke("get-default-dir", "music");
const allPath = [defaultMusicPath, ...settingStore.localFilesPath];
const isSubfolder = await window.electron.ipcRenderer.invoke(
"check-if-subfolder",
allPath,
selectedDir,
);
if (!isSubfolder) {
settingStore.localFilesPath.push(selectedDir);
} else {
window.$message.error("添加的目录与现有目录有重叠,请重新选择");
}
}
} catch (error) {
console.error("Error changing local path:", error);
window.$message.error("更改本地歌曲文件夹出错,请重试");
}
};