Skip to content

Vue

官网地址

环境搭建

注意

第三个命令为兼容低版本

bash
npm install vue
bash
npm install @vue/cli -g
bash
npm install -g @vue/cli-init

搜索的关键字高亮

提示

此时我们只需将调整 <em> 标签的样式即可

jsx
const list = [];
const reg = new RegExp(name, 'gi');
const node = list.map((item) => {
	let name = item.name.replace(reg, (key) => {
		return <em>{key}</em>;
	});
	return <li>{name}</li>;
});

本地开启 https

bash
npm install vite-plugin-mkcert

vite.config.ts

ts
import mkcert from 'vite-plugin-mkcert';

export default defineConfig({
	plugins: [mkcert()],
	server: {
		https: true,
	},
});

读取本地文件

vue
<script setup lang="ts">
import hljs from 'highlight.js';
import 'highlight.js/styles/github-dark.css';

import { ref, computed } from 'vue';

const expandedKeys = ref([]);
const treeData = ref();

const result = ref();

const selectFile = async () => {
	const handle = await showDirectoryPicker();
	await processHandle(handle);
	treeData.value = handle;
};

const processHandle = async (handle) => {
	if (handle.kind === 'file') return;

	handle.children = [];
	handle.isLeaf = handle.kind === 'file';
	const values = await handle.values();
	for await (const item of values) {
		handle.children.push(item);
		await processHandle(item);
	}
};

const openFile = async (_, { node }) => {
	const item = node.dataRef;

	if (item.kind === 'directory') return;
	const file = await item.getFile();

	if (file.name.endsWith('.exe') || file.name.endsWith('.zip')) return;

	//读取文件,具体解析什么类型的文件这里写
	const reader = new FileReader();
	reader.onload = (e) => {
		result.value = hljs.highlightAuto(e.target?.result).value;
	};
	reader.readAsText(file);
};

const innerHeight = computed(() => window.innerHeight - 32);
</script>

<template>
	<a-button @click="selectFile">选择文件</a-button>
	<div class="container">
		<a-directory-tree
			v-model:expandedKeys="expandedKeys"
			:tree-data="treeData?.children"
			:field-names="{ title: 'name' }"
			@select="openFile"
			class="catalog"
			:style="{ height: innerHeight + 'px' }"
		/>
		<div class="content" v-html="result" :style="{ height: innerHeight + 'px' }" />
	</div>
</template>

<style>
.container {
	display: flex;

	.catalog {
		width: 300px;
		overflow: auto;
	}

	.content {
		flex: 1;
		overflow: auto;

		/* 自动换行 */
		word-break: normal;

		/* 保留原内容的换行和缩进 */
		white-space: pre-wrap;

		/* 溢出的内容显示为滚动条,而不是溢出页面 */
		word-wrap: break-word;
	}
}
.ant-tree-node-content-wrapper {
	white-space: nowrap;
	overflow: hidden;
	text-overflow: ellipsis;
}
</style>

左右拖拽布局

vue
<template>
	<div class="main">
		<div class="left" :style="{ width: left_width + 'px' }">
			<slot name="left" />
		</div>
		<div class="dragLine" :style="{ width: dragLine_width + 'px' }" />
		<div class="right" :style="{ width: right_width + 'px' }">
			<slot name="right" />
		</div>
	</div>
</template>
<script setup>
import { ref, onMounted, onUnmounted, computed } from 'vue';

const current_width = ref(200);

const left_width = computed(() => {
	return current_width.value;
});
const dragLine_width = ref(5);
const right_width = computed(() => {
	return window.innerWidth - current_width.value - dragLine_width.value;
});
const isTarget = ref(false);

const mousedown = (event) => {
	const targetDom = event.target;
	if (targetDom.className === 'dragLine') {
		isTarget.value = true;
		event.preventDefault();
		event.stopPropagation();
	} else {
		isTarget.value = false;
	}
};
const mouseMove = (event) => {
	if (isTarget.value) {
		requestAnimationFrame(() => {
			current_width.value = event.x;
			event.preventDefault();
			event.stopPropagation();
		});
	}
};
const mouseUp = (event) => {
	isTarget.value = false;
	event.preventDefault();
	event.stopPropagation();
};
onMounted(() => {
	document.addEventListener('mousedown', mousedown);
	document.addEventListener('mousemove', mouseMove);
	document.addEventListener('mouseup', mouseUp);
});

onUnmounted(() => {
	document.removeEventListener('mousedown', mousedown);
	document.removeEventListener('mousemove', mouseMove);
	document.removeEventListener('mouseup', mouseUp);
});
</script>
<style>
.main {
	display: flex;
	height: 100vh;
	.left {
		height: 100%;
		min-width: 200px;
		overflow-y: hidden;
		&:hover {
			overflow-y: overlay;
		}
	}
	.dragLine {
		height: 100%;
		background-color: rgb(0, 183, 255);
		cursor: w-resize;
		&:hover {
			background-color: rgb(0, 102, 255);
		}
	}
	.right {
		height: 100%;
		min-width: 200px;
		overflow-y: hidden;
		&:hover {
			overflow-y: overlay;
		}
	}
}
</style>

音频可视化

vue
<script setup lang="ts">
import { useTemplateRef, onMounted } from 'vue';

const audioRef = useTemplateRef('audio');
const cvsRef = useTemplateRef('cvs');
let ctx: CanvasRenderingContext2D;
let isInit = false;
const initCvs = () => {
	if (!cvsRef.value) return;
	cvsRef.value.width = window.innerWidth * devicePixelRatio;
	cvsRef.value.height = (window.innerHeight / 2) * devicePixelRatio;
	ctx = cvsRef.value?.getContext('2d')!;
};
let analyser: AnalyserNode, buffer: Uint8Array<ArrayBufferLike>;
let bufferLength: number;
onMounted(() => {
	if (!audioRef.value || !cvsRef.value) return;
	initCvs();
	audioRef.value?.addEventListener('play', () => {
		if (isInit) return;
		const audioCtx = new AudioContext();
		const source = audioCtx.createMediaElementSource(audioRef.value!);
		analyser = audioCtx.createAnalyser();
		source.connect(analyser);
		analyser.connect(audioCtx.destination);
		analyser.fftSize = 2048;
		bufferLength = analyser.frequencyBinCount;
		buffer = new Uint8Array(bufferLength);
		isInit = true;
	});
	draw();
});

const draw = () => {
	requestAnimationFrame(draw);
	if (!isInit) return;
	const { width, height } = cvsRef.value!;
	ctx?.clearRect(0, 0, width, height);
	analyser.getByteFrequencyData(buffer);

	const barWidth = width / bufferLength;
	let barHeight;
	for (let i = 0; i < bufferLength; i++) {
		barHeight = (buffer[i] / 255) * height;
		ctx.fillStyle = `rgb(255,50,${i})`;
		ctx?.fillRect(i * barWidth, height - barHeight, barWidth, barHeight);
	}
};
</script>

<template>
	<div class="container">
		<canvas ref="cvs" />
		<audio src="/a.mp3" controls ref="audio" />
	</div>
</template>

<style>
.container {
	display: flex;
	flex-direction: column;
	height: 100vh;
	width: 100%;
	background-color: #000000;
	canvas {
		flex: 1;
		height: 100%;
		width: 100%;
	}
}
</style>

异步代码转同步代码

首先备份老的fetch请求,然后创建一个新的fetch,用新的替换掉老的fetch,然后执行异步代码,最后把老的fetch还原回去。

新的fetch先判断有没有缓存,有缓存直接返回缓存结果就可以,没有缓存,就执行老的fetch,在老的fetch中,等待结果将其写入缓存中,同是将这个Promise当做异常抛出,通过catch可以捕获到该异常,此时异常得到的是刚刚返回的Promise, 只需要执行finally就可以再次执行原函数,将新fetch重新赋值下,然后执行原函数,最后将老fetch还原即可。

js
const main = () => {
	const user = fetch('https://jsonplaceholder.typicode.com/users/1');
};

const run = (func) => {
	const oldFetch = window.fetch;

	const cache = {
		status: 'pending',
		value: null,
	};
	function newFetch(...args) {
		if (cache.status == 'fulfilled') {
			return cache.value;
		} else if (cache.status == 'rejected') {
			throw cache.value;
		}
		const p = oldFetch(...args)
			.then((res) => {
				cache.status = 'fulfilled';
				cache.value = res;
			})
			.catch((err) => {
				cache.status = 'rejected';
				cache.value = err;
			});
		throw p;
	}

	window.fetch = newFetch;

	try {
		func();
	} catch (err) {
		if (err instanceof Promise) {
			err.finally(() => {
				window.fetch = newFetch;
				func();
				window.fetch = oldFetch;
			});
		}
	}

	window.fetch = oldFetch;
};

run(main);

worker 进行大文件分片上传

vue
<script setup lang="ts">
const fileChange = async (event) => {
	const file = event.target.files[0];
	if (file) {
		const chunks = await cutFile(file);
		console.log(chunks); //此处得到要上传的文件
	}
};

const CHUNK_SIZE = 5 * 1024 * 1024; // 5MB 分片大小
const THREAD_COUNT = navigator.hardwareConcurrency || 4; // 获取硬件线程数,默认为4

const cutFile = (file: File) => {
	return new Promise((resolve) => {
		const chunkCount = Math.ceil(file.size / CHUNK_SIZE); //分片数量
		const threadChunkCount = Math.ceil(chunkCount / THREAD_COUNT); //每个线程处理的分片数量

		const result: any[] = [];
		let finshCount = 0;

		for (let i = 0; i < THREAD_COUNT; i++) {
			const worker = new Worker(new URL('./worker.js', import.meta.url), {
				type: 'module', // 让worker模块化,可以通过import引入一些别的代码
			});
			let start = i * threadChunkCount;
			let end = Math.min((i + 1) * threadChunkCount, chunkCount);
			worker.postMessage({
				file,
				start,
				end,
				chunkSize: CHUNK_SIZE,
			});
			worker.onmessage = (e) => {
				worker.terminate();
				result[i] = e.data;
				finshCount++;
				if (finshCount === THREAD_COUNT) resolve(result.flat());
			};
		}
	});
};
</script>

<template>
	<input type="file" ref="fileInput" @change="fileChange" />
</template>

<style>
.container {
	display: flex;
	flex-direction: column;
	height: 100vh;
	width: 100%;
	background-color: #000000;
	canvas {
		flex: 1;
		height: 100%;
		width: 100%;
	}
}
</style>
js
//worker.js
import SparkMD5 from 'spark-md5-es';

onmessage = async (e) => {
	const { file, start, end, chunkSize } = e.data;
	const result = [];
	for (let i = start; i < end; i++) {
		const p = creatChunk(file, i, chunkSize);
		result.push(p);
	}
	const chunks = await Promise.all(result);
	postMessage(chunks);
};

const creatChunk = (file, index, chunkSize) => {
	return new Promise((resolve) => {
		const start = index * chunkSize;
		const end = start + chunkSize;
		const spark = new SparkMD5.ArrayBuffer();
		const fileReader = new FileReader();
		const blob = file.slice(start, end);
		fileReader.onload = (e) => {
			spark.append(e.target?.result);
			resolve({
				start,
				end,
				index,
				blob,
				hash: spark.end(),
			});
		};
		fileReader.readAsArrayBuffer(blob);
	});
};

贡献者

暂无相关贡献者

变更记录

暂无最近变更历史

Released under the MIT License.