JS工具函数整理
2023-07-12 143 次阅读
JavaScripttypeScript
前端工具函数库
基于原文档整理优化:统一代码风格、补齐类型标注、修复已知问题、规范 JSDoc 示例。
依赖说明:除
TransformDate(dayjs)、MakeDir(Node.jsfs/path)外,其余均为零依赖的纯 TypeScript 函数。
目录
优化说明
| 问题 | 处理方式 |
|---|---|
JsonToQuery 参数顺序与文档示例矛盾(示例第 2 个参数传布尔值,代码实际为 excludeKeys) |
参数调整为 (json, includeEmpty, excludeKeys),与示例保持一致 |
QueryToJson 无法解析纯 query 字符串(如 "a=1&b=2") |
增加兼容逻辑,无 ? 时整体按 query 解析 |
QueryToJson 对值为 "0" 等 falsy 字符串会误返回 "" |
改用 ?? 判断,仅对缺失字段返回 "" |
FixFlatArr 同名方法重复定义互相覆盖,且方法 1 逻辑错误 |
保留并优化方法 2(Set + filter 分组) |
makeArrByNum 未导出、未处理 start > end |
导出并支持降序生成 |
SortArr 使用 sort 直接修改原数组,未匹配项排在最前 |
返回新数组,未匹配项置于末尾 |
MakeDir 递归实现复杂易错 |
改用 path.dirname + fs.mkdirSync({ recursive: true }) |
getTimeLong 秒数出现小数、倒计时结束不清除定时器 |
重写:整秒输出、归零后自动 clearInterval |
| 函数命名不统一(小驼峰未导出与 PascalCase 导出混用) | 统一为 PascalCase 并全部导出,见下表 |
函数重命名对照
| 原函数名 | 优化后函数名 |
|---|---|
filterArrByStartStrMax |
FilterArrByStartStrMax |
flatArr |
FlatArr |
makeArrByNum |
MakeArrByNum |
getTimeLong |
GetTimeLong |
1. URL 参数处理
query 转 json
typescript
/**
* query 字符串转 JSON 对象
*
* @param key 要获取的字段名;不传则返回整个解析后的对象
* @param url 目标 URL 或纯 query 字符串,如 "/aaa?a=1&b=2" 或 "a=1&b=2";默认读取 location.href
* @param joinSymbol 连接符数组 [path与query的连接符, query之间的连接符, kv连接符],默认 ["?", "&", "="]
* @returns 传入 key 时返回对应值(不存在返回 ""),否则返回对象(值为字符串)
*
* @example
* QueryToJson("", "/aaa?a=1&b=2&c=3") // => { a: "1", b: "2", c: "3" }
* QueryToJson("a", "/aaa?a=1&b=2") // => "1"
* QueryToJson("a", "a=1&b=2") // => "1"(纯 query 字符串也可解析)
*/
export const QueryToJson = (
key = "",
url = "",
joinSymbol: string[] = ["?", "&", "="]
): Record<string, string> | string => {
const [s1, s2, s3] = joinSymbol;
if (!url) url = location.href;
// 若 url 中不包含 s1(如直接传入 "a=1&b=2"),则整体视为 query 字符串
const queryStr = url.includes(s1) ? url.split(s1).slice(1).join(s1) : url;
const items = queryStr.split(s2).filter(Boolean);
if (!items.length) return key ? "" : {};
const json = items.reduce<Record<string, string>>((acc, item) => {
const [k, value = ""] = item.split(s3);
if (k) acc[k] = value;
return acc;
}, {});
return key ? (json[key] ?? "") : json;
};
json 转 query
typescript
/**
* JSON 对象转 query 字符串
*
* @param json 源对象
* @param includeEmpty 是否包含空字符串字段,默认 false;为 true 时 "" 会以 "key=" 形式输出
* @param excludeKeys 需要排除的字段名
* @returns 拼接好的 query 字符串(不含开头的 "?",值会经过 encodeURIComponent 编码)
*
* @example
* JsonToQuery({ a: 1, b: 2, c: "" }) // => "a=1&b=2"
* JsonToQuery({ a: 1, b: 2, c: "" }, true) // => "a=1&b=2&c="
* JsonToQuery({ a: 1, b: 2, c: "" }, true, ["b"]) // => "a=1&c="
*/
export const JsonToQuery = (
json: Record<string, any> = {},
includeEmpty = false,
excludeKeys: string[] = []
): string => {
const filterFn = includeEmpty
? (value: any) => value != null && value !== ""
: (value: any) => Boolean(value);
return Object.entries(json)
.filter(([key, value]) => !excludeKeys.includes(key) && filterFn(value))
.map(([key, value]) => `${key}=${encodeURIComponent(value ?? "")}`)
.join("&");
};
2. 字符串操作
根据下标返回对应文字
typescript
/**
* 根据下标返回对应文字(常用于行政级别 / 层级标签)
*
* @param level 级别,从 1 开始
* @param textArr 级别对应的文字数组
* @returns 对应文字;越界时返回 ""
*
* @example
* LevelToLabel(1, ["省", "市", "区县", "街道"]) // => "省"
*/
export const LevelToLabel = (
level: number,
textArr: string[] = ["省", "市", "区县", "街道"]
): string => textArr[level - 1] ?? "";
3. 数字操作
数字转千分位
typescript
/**
* 数字转千分位
*
* @param num 数值
* @returns 千分位字符串;无效值返回 0
*
* @example
* NumberToLocal(260000) // => "260,000"
*/
export const NumberToLocal = (num?: number): string | number =>
num ? num.toLocaleString() : 0;
补位函数
typescript
/**
* 补位函数:大于 9 的数值原样返回,否则前面补 0
*
* @param count 数值或数字字符串
* @returns 补零后的字符串;大于 9 时原样返回
*
* @example
* RepairZero(9) // => "09"
* RepairZero(10) // => 10
*/
export const RepairZero = (count: string | number): string | number =>
Number(count) > 9 ? count : "0" + count;
4. 数组操作
数组转对象
typescript
/**
* 数组转对象
*
* @param arr 原数组
* @param keyValCb 回调函数:(上一项, 当前项, 当前下标) => [key, value]
* @returns 转换后的对象
*
* @example
* ArrayToJson(["a=1", "b=2"], (_, item) => item.split("="))
* // => { a: "1", b: "2" }
*
* ArrayToJson([1, 2, 3], (_, item, index) => [`tagId${index}`, item])
* // => { tagId0: 1, tagId1: 2, tagId2: 3 }
*/
export const ArrayToJson = <T>(
arr: T[],
keyValCb: (prev: Record<string, any>, curr: T, index: number) => [string, any]
): Record<string, any> =>
arr.reduce<Record<string, any>>((acc, curr, index) => {
const [key, value] = keyValCb(acc, curr, index);
acc[key] = value;
return acc;
}, {});
扁平数组按字段分组为二维数组
typescript
/**
* 将扁平数组按指定字段分组为二维数组
*
* @param data 原数组
* @param key 分组字段名
* @returns 二维数组,每组保持原数组内的相对顺序
*
* @example
* FixFlatArr([{a:1,b:1},{a:1,b:2},{a:2,b:1}], "a")
* // => [[{a:1,b:1},{a:1,b:2}], [{a:2,b:1}]]
*/
export const FixFlatArr = <T extends Record<string, any>>(
data: T[] = [],
key = "id"
): T[][] => {
const fieldValues = [...new Set(data.map((e) => e[key]))];
return fieldValues.map((v) => data.filter((e) => e[key] === v));
};
注:分组字段值请保证类型一致,使用
===严格比较。
过滤出首字母相同且出现次数最多的项
typescript
/**
* 从字符串数组中过滤出首字母相同且出现次数最多的项
*
* @param arr 原数组
* @param isRel 是否区分大小写,默认 true
* @returns 过滤后的数组
*
* @example
* FilterArrByStartStrMax(
* ["adwafda", "Addddw", "swdA", "adawf", "sDWW", "Addfawf", "Afffff"]
* )
* // => ["Addddw", "Addfawf", "Afffff"]
*
* FilterArrByStartStrMax(
* ["adwafda", "Addddw", "swdA", "adawf", "sDWW", "Addfawf", "Afffff"],
* false
* )
* // => ["adwafda", "Addddw", "adawf", "Addfawf", "Afffff"]
*/
export const FilterArrByStartStrMax = (
arr: string[] = [],
isRel = true
): string[] => {
const countMap = arr.reduce<Record<string, number>>((acc, str) => {
const firstChar = isRel ? str.charAt(0) : str.charAt(0).toLowerCase();
acc[firstChar] = (acc[firstChar] ?? 0) + 1;
return acc;
}, {});
const maxCount = Math.max(...Object.values(countMap));
const maxFirstChar = Object.entries(countMap).find(
([, count]) => count === maxCount
)![0];
return arr.filter((item) => {
const firstChar = isRel ? item.charAt(0) : item.charAt(0).toLowerCase();
return firstChar === maxFirstChar;
});
};
树形结构扁平化(保留叶子节点)
typescript
/**
* 树形结构扁平化,只保留叶子节点
*
* @param data 树形数组
* @param childField 子节点字段名
* @returns 扁平化后的叶子节点数组
*
* @example
* FlatArr([{ id: 1, children: [{ id: 2 }, { id: 3 }] }, { id: 4 }])
* // => [{ id: 2 }, { id: 3 }, { id: 4 }]
*/
export const FlatArr = <T extends Record<string, any>>(
data: T[] = [],
childField = "children"
): T[] => {
const result: T[] = [];
const deep = (arr: T[]) => {
arr.forEach((item) => {
const children = item[childField];
if (Array.isArray(children) && children.length > 0) {
deep(children);
} else {
result.push(item);
}
});
};
deep(data);
return result;
};
注:如需保留含子节点的父节点,可在
else分支改为result.push(item)后继续递归,按需调整。
多个等长数组合并为对象数组
typescript
/**
* 将多个等长数组按索引合并为对象数组
*
* @param obj 键为字段名、值为等长数组的对象
* @returns 合并后的对象数组;对象为空时返回 []
*
* @example
* AssignArr({ a: [1,2,3], b: [4,5,6], c: [7,8,9] })
* // => [{a:1,b:4,c:7}, {a:2,b:5,c:8}, {a:3,b:6,c:9}]
*/
export const AssignArr = <T = any>(
obj: Record<string, T[]> = {}
): Record<string, T>[] => {
const keys = Object.keys(obj);
const firstArr = obj[keys[0]];
if (!keys.length || !firstArr) return [];
return firstArr.map((_, index) =>
keys.reduce((acc, key) => {
acc[key] = obj[key][index];
return acc;
}, {} as Record<string, T>)
);
};
对象数组按指定字段顺序排序
typescript
/**
* 对象数组按指定字段顺序排序
*
* @param dataSource 原数组
* @param order 字段值的期望顺序
* @param field 参与排序的字段名
* @returns 排序后的新数组(不修改原数组);未出现在 order 中的项排在末尾
*
* @example
* SortArr([{id:"aaa"},{id:"ccc"},{id:"bbb"}], ["aaa","bbb","ccc"], "id")
* // => [{id:"aaa"},{id:"bbb"},{id:"ccc"}]
*/
export const SortArr = <T extends Record<string, any>>(
dataSource: T[] = [],
order: string[] = [],
field = "id"
): T[] => {
const orderMap = new Map(order.map((v, i) => [v, i]));
return [...dataSource].sort(
(a, b) =>
(orderMap.get(a[field]) ?? Number.MAX_SAFE_INTEGER) -
(orderMap.get(b[field]) ?? Number.MAX_SAFE_INTEGER)
);
};
对象数组去重
typescript
/**
* 对象数组按指定字段去重(保留首次出现的项)
*
* @param dataSource 原数组
* @param key 去重依据的字段名
* @returns 去重后的新数组
*
* @example
* ReRepeat([{id:1,name:"张三"},{id:2,name:"李四"},{id:1,name:"王五"}], "id")
* // => [{id:1,name:"张三"},{id:2,name:"李四"}]
*/
export const ReRepeat = <T extends Record<string, any>>(
dataSource: T[],
key: string
): T[] => {
const map = new Map();
return dataSource.filter(
(item) => !map.has(item[key]) && map.set(item[key], item)
);
};
生成树结构
typescript
/**
* 生成树结构
*
* @param data 扁平数组
* @param relation 父子关系字段 [子字段, 父字段],默认 ["dictCode", "parentCode"]
* @param moreSon 是否允许多个父级共享同一子级;为 true 时父字段应为逗号拼接的多个父值
* @returns 树形结构
*
* @example
* MakeTree([
* { dictCode: "1", parentCode: "" },
* { dictCode: "11", parentCode: "1" },
* { dictCode: "12", parentCode: "1" },
* ])
* // => [{ dictCode: "1", children: [{ dictCode: "11" }, { dictCode: "12" }] }]
*/
export const MakeTree = <T extends Record<string, any>>(
data: T[],
relation: [string, string] = ["dictCode", "parentCode"],
moreSon = false
): T[] => {
const [sonField, parentField] = relation;
// 清除 children,防止重复调用时数据累加
data.forEach((item) => delete item.children);
// 以子字段值为 key 建立索引
const map = new Map<string, T>();
data.forEach((item) => map.set(item[sonField], item));
const result: T[] = [];
const setVal = (parent: T | undefined, item: T) => {
if (parent) {
parent.children || (parent.children = []);
parent.children.push(item);
} else {
result.push(item);
}
};
data.forEach((item) => {
const parentCodes = moreSon
? String(item[parentField]).split(",")
: [item[parentField]];
parentCodes.forEach((code) => setVal(map.get(code), item));
});
return result;
};
获取树节点指定级别数据集合
typescript
/**
* 获取树中指定层级的所有节点
*
* @param treeData 树形数据
* @param level 目标层级(根节点为 0)
* @param childField 子节点字段名
* @returns 该层级下的节点数组
*
* @example
* GetDatasByTreeLevel([{ id: 1, children: [{ id: 11 }, { id: 12 }] }], 1)
* // => [{ id: 11 }, { id: 12 }]
*/
export const GetDatasByTreeLevel = <T extends Record<string, any>>(
treeData: T[] = [],
level = 0,
childField = "children"
): T[] => {
const result: T[] = [];
const deepGet = (dataSource: T[], currentLevel: number) => {
if (currentLevel === level) {
result.push(...dataSource);
return;
}
dataSource.forEach((item) =>
deepGet(item[childField] || [], currentLevel + 1)
);
};
deepGet(treeData, 0);
return result;
};
根据起止数值生成数组
typescript
/**
* 生成从 start 到 end 的数值数组
*
* @param start 起始值
* @param end 结束值
* @param includeEnd 是否包含结束值,默认 true
* @returns 数值数组(start > end 时降序生成)
*
* @example
* MakeArrByNum(1, 5) // => [1, 2, 3, 4, 5]
* MakeArrByNum(1, 5, false) // => [1, 2, 3, 4]
* MakeArrByNum(5, 1) // => [5, 4, 3, 2, 1]
*/
export const MakeArrByNum = (
start: number,
end: number,
includeEnd = true
): number[] => {
const result: number[] = [];
const step = start <= end ? 1 : -1;
for (let i = start; i !== end; i += step) {
result.push(i);
}
if (includeEnd) result.push(end);
return result;
};
5. 时间处理
相对时间转换
typescript
import dayjs from "dayjs";
export interface TransformDateOptions {
justnow: number; // 多少秒内显示 "x 秒前"
minute: number; // 多少分钟内显示 "x 分钟前"
hour: number; // 多少小时内显示 "x 小时前"
day: number; // 多少天内显示 "x 天前"
month: number; // 多少个月内显示 "x 个月前"
format: string; // 超出上述范围时输出的日期格式
}
/**
* 时间转相对描述:"x 秒前 / x 分钟前 / x 小时前 / x 天前 / x 个月前"
*
* @param date 目标时间
* @param options 各档位阈值与兜底格式,默认
* { justnow: 60, minute: 60, hour: 24, day: 30, month: 12, format: "YYYY/MM/DD" }
* @returns 相对时间字符串
*
* @example
* TransformDate("2026-08-21 12:00:00", { day: 10 }) // 超过 10 天则输出日期
*/
export const TransformDate = (
date: string,
options?: Partial<TransformDateOptions>
): string => {
const defaultOptions: TransformDateOptions = {
justnow: 60,
minute: 60,
hour: 24,
day: 30,
month: 12,
format: "YYYY/MM/DD",
};
const opts = { ...defaultOptions, ...options };
const specifiedDate = dayjs(date);
const diffInSecond = Math.abs(specifiedDate.diff(dayjs(), "second"));
const diffInMinute = Math.abs(specifiedDate.diff(dayjs(), "minute"));
const diffInHours = Math.abs(specifiedDate.diff(dayjs(), "hour"));
const diffInDay = Math.abs(specifiedDate.diff(dayjs(), "day"));
const diffInMonth = Math.abs(specifiedDate.diff(dayjs(), "month"));
if (diffInSecond < opts.justnow) return `${diffInSecond} 秒前`;
if (diffInMinute <= opts.minute) return `${diffInMinute} 分钟前`;
if (diffInHours <= opts.hour) return `${diffInHours} 小时前`;
if (diffInDay <= opts.day) return `${diffInDay} 天前`;
if (diffInMonth <= opts.month) return `${diffInMonth} 个月前`;
return specifiedDate.format(opts.format);
};
注:默认档位
justnow=60秒、minute=60分钟、hour=24小时、day=30天、month=12个月,与代码实现保持一致(原文档描述与默认值不符,已修正)。
6. 网络操作
文件下载
typescript
/**
* 文件下载(Blob 方式,跨域场景兼容性更好)
*
* @param href 文件地址
* @param title 保存的文件名
*/
export const DownloadFile = (href: string, title: string): void => {
const xhr = new XMLHttpRequest();
xhr.open("GET", href, true);
xhr.responseType = "blob";
xhr.onload = () => {
const url = URL.createObjectURL(xhr.response);
const link = document.createElement("a");
link.href = url;
link.setAttribute("download", title);
link.style.display = "none";
document.body.appendChild(link);
link.click();
URL.revokeObjectURL(url); // 释放内存
link.remove();
};
xhr.onerror = () => console.error(`下载失败:${href}`);
xhr.send();
};
跨域 GET 获取页面内容
typescript
/**
* 跨域 GET 请求获取页面内容(含 IE8/9 XDomainRequest 兜底,仅历史项目需要)
*
* @param url 请求地址
* @param callback 请求成功回调
* @param timeout 超时时间(毫秒),默认 5000
*/
export const MakeCorsRequest = (
url: string,
callback: (responseText: string) => void,
timeout = 5000
): void => {
let xhr: XMLHttpRequest | any;
if ("withCredentials" in new XMLHttpRequest()) {
// withCredentials 是 XMLHttpRequest2 独有的属性
xhr = new XMLHttpRequest();
xhr.open("GET", url, true);
} else if (typeof (window as any).XDomainRequest !== "undefined") {
xhr = new (window as any).XDomainRequest();
xhr.open("GET", url);
} else {
console.error("当前环境不支持 CORS");
return;
}
let timedout = false;
const timer = setTimeout(() => {
timedout = true;
xhr.abort();
}, timeout);
xhr.onreadystatechange = () => {
if (xhr.readyState !== 4 || timedout) return;
clearTimeout(timer);
if (xhr.status === 200) callback(xhr.responseText);
else if (xhr.status === 404) console.error("路径请求错误");
};
xhr.onerror = () => {
clearTimeout(timer);
console.error("请求错误");
};
xhr.send();
};
现代浏览器建议直接使用
fetch(url, { mode: "cors" }),仅兼容 IE 时需要上面的实现。
递归创建目录(Node.js)
typescript
import fs from "fs";
import path from "path";
/**
* 递归创建目录(Node.js 环境)
*
* @param filePath 文件或目录路径,如 "/a/b/c/1.png"
*
* @example
* MakeDir("/a/b/c/1.png") // 创建 ./a/b/c
*/
export const MakeDir = (filePath: string): void => {
fs.mkdirSync(path.dirname(filePath), { recursive: true });
};
7. 枚举与常量
定义枚举
typescript
/** 检测类型枚举 */
export enum DETECTION {
LIUL = 10, // 流量
JWND = 20, // 甲烷浓度
GDYL = 30, // 管道压力
YEWE = 40, // 液位
}
定义常量
typescript
/** 检测类型元信息 */
export const DETECTION_TYPE = {
[DETECTION.LIUL]: { LABEL: "流量" },
[DETECTION.JWND]: { LABEL: "甲烷浓度" },
[DETECTION.GDYL]: { LABEL: "管道压力" },
[DETECTION.YEWE]: { LABEL: "液位" },
};
查询方法
typescript
/**
* 根据嵌套对象的 key 和 value 反查外层 key 及嵌套对象
*
* @param data 嵌套对象
* @param key 嵌套对象内的字段名
* @param value 嵌套对象内字段对应的值
* @returns [外层key, 嵌套对象];未找到返回 []
*
* @example
* GET_DATA(DETECTION_TYPE, "LABEL", "流量")
* // => ["10", { LABEL: "流量" }]
*/
export const GET_DATA = <T extends Record<string, any>>(
data: T = {} as T,
key: string,
value: string
): [string, T[keyof T]] | [] => {
const target = Object.entries(data).find(([, obj]) => obj[key] === value);
return target ? (target as [string, T[keyof T]]) : [];
};
/**
* 收集所有嵌套对象中指定字段的值
*
* @param data 嵌套对象
* @param key 需要的字段名
* @param excludes 需要排除的值
* @returns 值数组
*
* @example
* GET_DATA_KEYS(DETECTION_TYPE, "LABEL", ["流量"])
* // => ["甲烷浓度", "管道压力", "液位"]
*/
export const GET_DATA_KEYS = <T extends Record<string, any>>(
data: T = {} as T,
key: string,
excludes: string[] = []
): string[] =>
Object.values(data)
.map((obj) => obj[key])
.filter((value) => !excludes.includes(value));
8. Vite + Vue3
获取 assets 下的文件
typescript
/**
* 获取 assets 目录下资源(Vite 专属)
*
* @param src assets 下的相对路径
* @returns 构建后的资源地址
*
* @example
* GetImage("images/arrow.png")
* // => "http://192.168.6.192:8080/src/assets/images/arrow.png"
*/
export const GetImage = (src: string): string =>
new URL(`../assets/${src}`, import.meta.url).href;
9. 杂项
距指定时刻的倒计时
typescript
/**
* 持续输出当前时间距离今日指定时刻(h:m:s)的剩余时间
*
* @param h 目标时(0-23)
* @param m 目标分(0-59)
* @param s 目标秒(0-59)
*
* @example
* GetTimeLong(18, 0, 0) // 每秒输出一次 "距离下班还有:x时x分x秒",归零后自动停止
*/
export const GetTimeLong = (h: number, m: number, s: number): void => {
const target = new Date();
target.setHours(h, m, s, 0);
const timer = setInterval(() => {
const diff = target.getTime() - Date.now();
if (diff <= 0) {
console.log("距离下班还有:0时0分0秒");
clearInterval(timer);
return;
}
const hours = Math.floor(diff / 3_600_000);
const minutes = Math.floor((diff % 3_600_000) / 60_000);
const seconds = Math.floor((diff % 60_000) / 1000);
console.log(`距离下班还有:${hours}时${minutes}分${seconds}秒`);
}, 1000);
};
设置页面标题
typescript
/**
* 设置页面标题
*
* @param title 标题内容,默认 "webxue"
*/
export const SetTitle = (title = "webxue"): void => {
document.title = title;
};
全屏 / 取消全屏
typescript
/**
* 全屏控制
*/
export const Fullscreen: {
open: (element?: HTMLElement) => void;
close: () => void;
} = {
open: (element: HTMLElement = document.body) => {
element.requestFullscreen().catch(() => {});
},
close: () => {
document.exitFullscreen().catch(() => {});
},
};
/**
* 监听全屏状态(Vue3 + ref 用法示例)
* 开发环境:宽或高等于屏幕即视为全屏(便于开 F12 调试)
* 生产环境:宽高需同时等于屏幕
*/
// 假设全局存在 const isFullScreen = ref(false)
// window.addEventListener("resize", () => {
// if (import.meta.env.DEV) {
// isFullScreen.value =
// document.body.clientHeight === window.screen.height ||
// document.body.clientWidth === window.screen.width;
// } else {
// isFullScreen.value =
// document.body.clientHeight === window.screen.height &&
// document.body.clientWidth === window.screen.width;
// }
// });
函数式选择文件
typescript
/**
* 函数式选择文件(返回 Promise)
*
* @param accept 可选的文件类型过滤,如 "image/*"、".jpg,.png"
* @returns 选中的第一个文件
*
* @example
* const file = await GetFile("image/*");
*/
export const GetFile = (accept = ""): Promise<File> =>
new Promise((resolve) => {
const input = document.createElement("input");
input.type = "file";
input.accept = accept;
input.onchange = () => {
resolve(input.files![0]);
input.remove();
};
input.click();
});
