全景图转立方体六面图片
- 作用:在球体上贴一张全景图,图片太大时可能会有性能问题,切成多张图并行加载速度会快一点。
项目构建
- 看vuejs教程
npm create vue@latest
npm install -D sass
- threejs依赖
npm install three
npm install -D @types/three
核心文件
入口~Index.vue
<script setup lang="ts">
import { ref } from 'vue'
import ImgSelect from './components/ImgSelect.vue'
import SixFaceView from './components/SixFaceView.vue'
import CssCube from './components/CssCube.vue'
import ThreeDCube from './components/ThreeDCube.vue'
const threeDCubeRef = ref<InstanceType<typeof ThreeDCube>>();
const cssCubeRef = ref<InstanceType<typeof CssCube>>();
const sixFaceViewRef = ref<InstanceType<typeof SixFaceView>>();
const selectChange = (file: File)=>{
if(sixFaceViewRef.value){
sixFaceViewRef.value?.selectFileChange(file);
}
}
const changeUrl = (imgUrlArr:Array<string>)=>{
const [pxUrl, nxUrl, nyUrl, pyUrl, nzUrl, pzUrl] = imgUrlArr;
cssCubeRef.value?.imgUrlChange([pxUrl, nxUrl, nyUrl, pyUrl, nzUrl, pzUrl]);
threeDCubeRef.value?.setCubeImg([pxUrl, nxUrl, pyUrl, nyUrl, pzUrl, nzUrl]);
}
</script>
<template>
<div class="content-in">
<div class="left">
<div class="left-top">
<ImgSelect @change="selectChange"/>
</div>
<div class="left-bottom">
<SixFaceView ref="sixFaceViewRef" @change="changeUrl"/>
</div>
</div>
<div class="right">
<div class="right-top">
<CssCube ref="cssCubeRef"/>
</div>
<div class="right-bottom">
<ThreeDCube ref="threeDCubeRef"/>
</div>
</div>
</div>
</template>
<style scoped lang="scss">
.content-in {
flex: 1;
display: flex;
overflow: hidden;
.left {
flex: 1;
display: flex;
flex-direction: column;
overflow: hidden;
.left-top {
flex: 1;
padding: 10px;
position: relative;
overflow: hidden;
}
.left-bottom {
flex: 2;
position: relative;
overflow: auto;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
}
}
.right {
flex: 1;
display: flex;
flex-direction: column;
overflow: hidden;
.right-top {
flex: 1;
position: relative;
overflow: hidden;
display: flex;
align-items: center;
justify-content: center;
}
.right-bottom {
flex: 1;
position: relative;
overflow: hidden;
}
}
}
</style>
组件~ImgSelect.vue
<script setup lang="ts">
import { ref } from 'vue'
const emit = defineEmits(['change']);
const dragFileDiv = ref<HTMLDivElement>();
const imgFileRef = ref<HTMLImageElement>();
const inputFileRef = ref<HTMLInputElement>();
const h1Ref = ref<HTMLHeadElement>();
const dragoverFile = (e: DragEvent) => {
e.preventDefault();
dragFileDiv.value?.classList.add('drag-over');
}
const dropFile = (e: DragEvent) => {
e.preventDefault();
dragFileDiv.value?.classList.remove('drag-over');
if (e.dataTransfer && e.dataTransfer.files) {
handleFile(e.dataTransfer.files[0])
}
}
const dragleaveFile = (e: DragEvent) => {
e.preventDefault();
dragFileDiv.value?.classList.remove('drag-over');
}
const clickSelectFile = (e: MouseEvent) => {
e.preventDefault();
inputFileRef.value?.click();
}
const changeFileInput = (e: Event) => {
const target = e.target as HTMLInputElement;
if (target && target.files) {
handleFile(target.files[0]);
}
}
function handleFile(file: File) {
if (!file) {
return;
}
const fileType = file.name.split(".").pop() || "";
if (!["png", "jpg", "jpeg"].includes(fileType.toLowerCase()) || file.size > 1024 * 1024 * 50) {
alert("请上传小于10M的 png, jpg,jpeg文件");
return;
}
createObjectUrl(file)
emit('change', file);
}
function createObjectUrl(file: File) {
const url = URL.createObjectURL(file)
displayImg(url)
}
function displayImg(url: string) {
console.log(url)
if (!imgFileRef.value) {
return;
}
imgFileRef.value.src = url;
imgFileRef.value.style.display = "block";
if (h1Ref.value) {
h1Ref.value.style.display = "none";
}
if (dragFileDiv.value) {
dragFileDiv.value.style.border = "none";
}
}
</script>
<template>
<input ref="inputFileRef" @change="changeFileInput" type="file" accept=".png,jpg,.jpeg">
<div ref="dragFileDiv" @dragover="dragoverFile" @drop="dropFile" @dragleave="dragleaveFile"
@click="clickSelectFile" class="img-input">
<img ref="imgFileRef" />
<h1 ref="h1Ref">请点击或者拖拽文件到该区域</h1>
</div>
</template>
<style scoped lang="scss">
input {
display: none;
}
.img-input {
width: 100%;
height: 100%;
border: 2px dashed #131212;
border-radius: 5px;
display: flex;
align-items: center;
justify-content: center;
user-select: none;
img {
width: 100%;
height: 100%;
display: none;
pointer-events: none; // 不能被鼠标选中
}
h1 {
position: absolute;
pointer-events: none; // 不能被鼠标选中
}
}
.drag-over {
background-color: rgb(240, 227, 204);
}
</style>
组件~SixFaceView.vue
<script setup lang="ts">
import { ref } from 'vue'
import { get6FaceImgUrl, downloadUrlFile } from '../../../utils/ConvertPanorama.ts'
const emit = defineEmits(['change']);
const imgPzRef = ref<HTMLImageElement>();
const imgNzRef = ref<HTMLImageElement>();
const imgPxRef = ref<HTMLImageElement>();
const imgNxRef = ref<HTMLImageElement>();
const imgPyRef = ref<HTMLImageElement>();
const imgNyRef = ref<HTMLImageElement>();
const h1Ref = ref<HTMLHeadElement>();
const imgUrlArr = ref<Array<string>>([]);
const selectFileChange = async (file: File) => {
console.log(111)
if (h1Ref.value) {
h1Ref.value.innerHTML = "解析中~~~";
}
const [pxUrl, nxUrl, nyUrl, pyUrl, nzUrl, pzUrl] = await get6FaceImgUrl(file);
console.log(pxUrl, nxUrl, nyUrl, pyUrl, nzUrl, pzUrl)
imgUrlArr.value = [pxUrl, nxUrl, nyUrl, pyUrl, nzUrl, pzUrl];
emit('change', [pxUrl, nxUrl, nyUrl, pyUrl, nzUrl, pzUrl]);
if (imgPzRef.value) {
imgPzRef.value.src = pzUrl;
}
if (imgNzRef.value) {
imgNzRef.value.src = nzUrl;
}
if (imgPxRef.value) {
imgPxRef.value.src = pxUrl;
}
if (imgNxRef.value) {
imgNxRef.value.src = nxUrl;
}
if (imgPyRef.value) {
imgPyRef.value.src = pyUrl;
}
if (imgNyRef.value) {
imgNyRef.value.src = nyUrl;
}
if (h1Ref.value) {
h1Ref.value.innerHTML = "解析完成~~~";
}
}
const downloadFile = (face: string) => {
const [pxUrl, nxUrl, nyUrl, pyUrl, nzUrl, pzUrl] = imgUrlArr.value;
switch (face) {
case 'pz': {
downloadUrlFile(pzUrl, `${face}.png`);
break;
}
case 'nz': {
downloadUrlFile(nzUrl, `${face}.png`);
break;
}
case 'px': {
downloadUrlFile(pxUrl, `${face}.png`);
break;
}
case 'nx': {
downloadUrlFile(nxUrl, `${face}.png`);
break;
}
case 'py': {
downloadUrlFile(pyUrl, `${face}.png`);
break;
}
case 'ny': {
downloadUrlFile(nyUrl, `${face}.png`);
break;
}
}
}
defineExpose({
selectFileChange
})
</script>
<template>
<h1 ref="h1Ref"></h1>
<div class="cube-img-area">
<div class="cube-face pz" @click="downloadFile('pz')">
<h1>pz</h1>
<img ref="imgPzRef" />
</div>
<div class="cube-face nz" @click="downloadFile('nz')">
<h1>nz</h1>
<img ref="imgNzRef" />
</div>
<div class="cube-face px" @click="downloadFile('px')">
<h1>px</h1>
<img ref="imgPxRef" />
</div>
<div class="cube-face nx" @click="downloadFile('nx')">
<h1>nx</h1>
<img ref="imgNxRef" />
</div>
<div class="cube-face py" @click="downloadFile('py')">
<h1>py</h1>
<img ref="imgPyRef" />
</div>
<div class="cube-face ny" @click="downloadFile('ny')">
<h1>ny</h1>
<img ref="imgNyRef" />
</div>
</div>
</template>
<style scoped lang="scss">
.cube-img-area {
position: relative;
width: 415px;
height: 310px;
overflow: hidden;
user-select: none;
.cube-face {
width: 100px;
height: 100px;
overflow: hidden;
border: none;
display: flex;
align-items: center;
justify-content: center;
cursor: pointer;
h1{
position: absolute;
z-index: 100;
color: #ffffff;
pointer-events: none; // 不能被鼠标选中
user-select: none;
}
img {
width: 100%;
height: 100%;
pointer-events: none; // 不能被鼠标选中
border: none;
}
}
.pz {
position: absolute;
left: 105px;
top: 105px;
}
.nz {
position: absolute;
left: 315px;
top: 105px;
}
.py {
position: absolute;
left: 105px;
top: 0px;
}
.ny {
position: absolute;
left: 105px;
top: 210px;
}
.px {
position: absolute;
left: 0px;
top: 105px;
}
.nx {
position: absolute;
left: 210px;
top: 105px;
}
}
</style>
组件~CssCube.vue
<script setup lang="ts">
import { ref } from 'vue'
const imgPzRef = ref<HTMLImageElement>();
const imgNzRef = ref<HTMLImageElement>();
const imgPxRef = ref<HTMLImageElement>();
const imgNxRef = ref<HTMLImageElement>();
const imgPyRef = ref<HTMLImageElement>();
const imgNyRef = ref<HTMLImageElement>();
const imgUrlChange = (imgUrlArr:Array<string>) => {
const [pxUrl, nxUrl, nyUrl, pyUrl, nzUrl, pzUrl] = imgUrlArr;
if (imgPzRef.value) {
imgPzRef.value.src = pzUrl;
}
if (imgNzRef.value) {
imgNzRef.value.src = nzUrl;
}
if (imgPxRef.value) {
imgPxRef.value.src = pxUrl;
}
if (imgNxRef.value) {
imgNxRef.value.src = nxUrl;
}
if (imgPyRef.value) {
imgPyRef.value.src = pyUrl;
}
if (imgNyRef.value) {
imgNyRef.value.src = nyUrl;
}
}
defineExpose({
imgUrlChange
})
</script>
<template>
<div class="loader3d">
<div class="cube">
<div class="face ny">
<h1>ny</h1>
<img ref="imgNyRef" />
</div>
<div class="face py">
<h1>py</h1>
<img ref="imgPyRef" />
</div>
<div class="face nx">
<h1>nx</h1>
<img ref="imgNxRef" />
</div>
<div class="face px">
<h1>px</h1>
<img ref="imgPxRef" />
</div>
<div class="face nz">
<h1>nz</h1>
<img ref="imgNzRef" />
</div>
<div class="face pz">
<h1>pz</h1>
<img ref="imgPzRef" />
</div>
</div>
</div>
</template>
<style scoped lang="scss">
.loader3d {
perspective: 600px;
width: 200px;
height: 200px;
user-select: none;
pointer-events: none; // 不能被鼠标选中
}
.cube {
width: 100%;
height: 100%;
transform-style: preserve-3d;
animation: rotate 10s linear infinite;
}
.face {
position: absolute;
width: 100%;
height: 100%;
// background: linear-gradient(45deg, #3498db, #e74c3c);
// opacity: 0.8;
border: 0.5px solid #fff;
// border-radius: 25%;
text-align: center;
line-height: 200px;
display: flex;
align-items: center;
justify-content: center;
h1{
position: absolute;
z-index: 100;
color: #ffffff;
pointer-events: none; // 不能被鼠标选中
user-select: none;
}
img {
width: 100%;
height: 100%;
border: 0.5px solid #fff;
// border-radius: 25%;
}
}
.py {
transform: rotateX(90deg) translateZ(100px);
}
.ny {
transform: rotateX(-90deg) translateZ(100px);
}
.pz {
transform: translateZ(100px);
}
.nx {
transform: rotateY(90deg) translateZ(100px);
}
.px {
transform: rotateY(-90deg) translateZ(100px);
}
.nz {
transform: rotateY(180deg) translateZ(100px);
}
@keyframes rotate {
0% {
transform: rotateX(0deg) rotateY(0deg);
}
100% {
transform: rotateX(360deg) rotateY(360deg);
}
}
</style>
组件~ThreeDCube.vue
<script setup lang="ts">
import * as THREE from 'three'
import { OrbitControls } from 'three/examples/jsm/controls/OrbitControls.js';
import { ref, onBeforeUnmount, onMounted } from 'vue'
const threeRef = ref<HTMLDivElement>();
let camera: THREE.PerspectiveCamera, controls: OrbitControls;
let renderer: THREE.WebGLRenderer;
let scene: THREE.Scene;
function init() {
if (!threeRef.value) {
console.error('threeRef找不到')
return;
}
const divWidth = threeRef.value.offsetWidth;
const divHeight = threeRef.value.offsetHeight;
scene = new THREE.Scene();
camera = new THREE.PerspectiveCamera(70, divWidth / divHeight, 0.1, 10);
camera.position.set(0, 0, 0.1);
renderer = new THREE.WebGLRenderer();
renderer.setPixelRatio(window.devicePixelRatio);
renderer.setSize(divWidth, divHeight);
renderer.setAnimationLoop(() => {
controls.update();
renderer.render(scene, camera);
});
threeRef.value.appendChild(renderer.domElement);
controls = new OrbitControls(camera, renderer.domElement);
controls.addEventListener('start', () => {
renderer.domElement.style.cursor = 'move'
})
controls.addEventListener('end', () => {
renderer.domElement.style.cursor = 'auto'
})
}
function destory() {
try {
console.log('---->全景图-->destroy')
renderer?.domElement?.parentElement?.removeChild(renderer?.domElement);
renderer.setAnimationLoop(null);
renderer.dispose();
renderer.forceContextLoss();
const gl = renderer.domElement.getContext("webgl");
if (gl && gl.getExtension("WEBGL_lose_context")) {
gl.getExtension("WEBGL_lose_context")?.loseContext();
}
scene.traverse((child: any) => {
if (child.material) {
if (child.material.dispose) {
child.material.dispose();
}
child.material = null;
}
if (child.geometry) {
if (child.geometry.dispose) {
child.geometry.dispose();
}
child.geometry = null;
if (child.bufferGeometry && child.bufferGeometry.dispose) {
child.bufferGeometry.dispose();
child.bufferGeometry = null;
}
}
});
scene.clear();
} catch (e) {
console.error(e);
}
}
function setCubeImg([left, right, up, down, back, front]: Array<string>) {
const textures: Array<THREE.Texture> = [];
[left, right, up, down, back, front].forEach(i => {
const texture = new THREE.TextureLoader().load(i);
texture.colorSpace = THREE.SRGBColorSpace;
textures.push(texture);
});
const materials = [];
for (let i = 0; i < 6; i++) {
materials.push(new THREE.MeshBasicMaterial({ map: textures[i] }));
}
let skyBox:THREE.Mesh = scene.getObjectByName('skyBox') as THREE.Mesh;
if (skyBox) {
skyBox.material = materials;
for (let i = 0; i < 6; i++) {
skyBox.material[i].needsUpdate = true;
}
} else {
skyBox = new THREE.Mesh(new THREE.BoxGeometry(1, 1, 1), materials);
skyBox.name = 'skyBox';
skyBox.geometry.scale(1, 1, -1);
scene.add(skyBox);
}
}
defineExpose({
setCubeImg
})
onMounted(() => init())
onBeforeUnmount(() => destory())
</script>
<template>
<div ref="threeRef" class="three"></div>
</template>
<style scoped lang="scss">
.three {
width: 100%;
height: 100%;
}
</style>
核心工具类~ConvertPanorama.ts
const clamp = (x: number, min: number, max: number): number => {
return Math.min(max, Math.max(x, min));
}
const mod = (x: number, n: number): number => {
return ((x % n) + n) % n;
}
export enum FaceEnum {
PZ,
NZ,
PX,
NX,
PY,
NY,
};
type Vector3 = { x: number, y: number, z: number };
type OrientationFunction = (a: number, b: number) => Vector3;
const orientations: { [key in FaceEnum]: OrientationFunction } = {
[FaceEnum.PZ]: (x: number, y: number) => ({ x: -1, y: -x, z: -y }),
[FaceEnum.NZ]: (x: number, y: number) => ({ x: 1, y: x, z: -y }),
[FaceEnum.PX]: (x: number, y: number) => ({ x: x, y: -1, z: -y }),
[FaceEnum.NX]: (x: number, y: number) => ({ x: -x, y: 1, z: -y }),
[FaceEnum.PY]: (x: number, y: number) => ({ x: -y, y: -x, z: 1 }),
[FaceEnum.NY]: (x: number, y: number) => ({ x: y, y: -x, z: -1 })
};
export enum InterpolationEnum {
Bilinear,
Bicubic,
Lanczos,
Nearest
}
type InterpolationFunction = (read: ImageData, write: ImageData, xFrom: number, yFrom: number, to: number) => void;
type KernelFunction = (x: number) => number;
const interpolations: { [key in InterpolationEnum]: InterpolationFunction } = {
[InterpolationEnum.Bilinear]: copyPixelBilinear,
[InterpolationEnum.Bicubic]: copyPixelBicubic,
[InterpolationEnum.Lanczos]: copyPixelLanczos,
[InterpolationEnum.Nearest]: copyPixelNearest
};
function copyPixelNearest(read: ImageData, write: ImageData, xFrom: number, yFrom: number, to: number) {
const { width, height, data } = read;
const readIndex = (x: number, y: number) => 4 * (y * width + x);
const nearest = readIndex(
clamp(Math.round(xFrom), 0, width - 1),
clamp(Math.round(yFrom), 0, height - 1)
);
for (let channel = 0; channel < 3; channel++) {
write.data[to + channel] = data[nearest + channel];
}
}
function copyPixelBilinear(read: ImageData, write: ImageData, xFrom: number, yFrom: number, to: number) {
const { width, height, data } = read;
const readIndex = (x: number, y: number) => 4 * (y * width + x);
const xl = clamp(Math.floor(xFrom), 0, width - 1);
const xr = clamp(Math.ceil(xFrom), 0, width - 1);
const xf = xFrom - xl;
const yl = clamp(Math.floor(yFrom), 0, height - 1);
const yr = clamp(Math.ceil(yFrom), 0, height - 1);
const yf = yFrom - yl;
const p00 = readIndex(xl, yl);
const p10 = readIndex(xr, yl);
const p01 = readIndex(xl, yr);
const p11 = readIndex(xr, yr);
for (let channel = 0; channel < 3; channel++) {
const p0 = data[p00 + channel] * (1 - xf) + data[p10 + channel] * xf;
const p1 = data[p01 + channel] * (1 - xf) + data[p11 + channel] * xf;
write.data[to + channel] = Math.ceil(p0 * (1 - yf) + p1 * yf);
}
}
function kernelResample(read: ImageData, write: ImageData, xFrom: number, yFrom: number, to: number, filterSize: number, kernel: KernelFunction) {
const { width, height, data } = read;
const readIndex = (x: number, y: number) => 4 * (y * width + x);
const twoFilterSize = 2 * filterSize;
const xMax = width - 1;
const yMax = height - 1;
const xKernel = new Array(4);
const yKernel = new Array(4);
const xl = Math.floor(xFrom);
const yl = Math.floor(yFrom);
const xStart = xl - filterSize + 1;
const yStart = yl - filterSize + 1;
for (let i = 0; i < twoFilterSize; i++) {
xKernel[i] = kernel(xFrom - (xStart + i));
yKernel[i] = kernel(yFrom - (yStart + i));
}
for (let channel = 0; channel < 3; channel++) {
let q = 0;
for (let i = 0; i < twoFilterSize; i++) {
const y = yStart + i;
const yClamped = clamp(y, 0, yMax);
let p = 0;
for (let j = 0; j < twoFilterSize; j++) {
const x = xStart + j;
const index = readIndex(clamp(x, 0, xMax), yClamped);
p += data[index + channel] * xKernel[j];
}
q += p * yKernel[i];
}
write.data[to + channel] = Math.round(q);
}
}
function copyPixelBicubic(read: ImageData, write: ImageData, xFrom: number, yFrom: number, to: number) {
const b = -0.5;
const kernel = (x: number) => {
x = Math.abs(x);
const x2 = x * x;
const x3 = x * x * x;
return x <= 1 ?
(b + 2) * x3 - (b + 3) * x2 + 1 :
b * x3 - 5 * b * x2 + 8 * b * x - 4 * b;
};
kernelResample(read, write, xFrom, yFrom, to, 2, kernel);
}
function copyPixelLanczos(read: ImageData, write: ImageData, xFrom: number, yFrom: number, to: number) {
const filterSize = 5;
const kernel = (x: number) => {
if (x === 0) {
return 1;
}
else {
const xp = Math.PI * x;
return filterSize * Math.sin(xp) * Math.sin(xp / filterSize) / (xp * xp);
}
};
kernelResample(read, write, xFrom, yFrom, to, filterSize, kernel);
}
export function copyFaceData(readData: ImageData, face: FaceEnum, rotation: number, interpolation: InterpolationEnum, maxWidth = Infinity): ImageData {
const faceWidth = Math.min(maxWidth, readData.width / 4);
const faceHeight = faceWidth;
const writeData = new ImageData(faceWidth, faceHeight);
for (let x = 0; x < faceWidth; x++) {
for (let y = 0; y < faceHeight; y++) {
const to = 4 * (y * faceWidth + x);
writeData.data[to + 3] = 255;
const cube: Vector3 = orientations[face]((2 * (x + 0.5) / faceWidth - 1), (2 * (y + 0.5) / faceHeight - 1));
const r = Math.sqrt(cube.x * cube.x + cube.y * cube.y + cube.z * cube.z);
const lon = mod(Math.atan2(cube.y, cube.x) + rotation, 2 * Math.PI);
const lat = Math.acos(cube.z / r);
interpolations[interpolation](readData, writeData, readData.width * lon / Math.PI / 2 - 0.5, readData.height * lat / Math.PI - 0.5, to);
}
}
return writeData;
}
const mimeType: { [key in string]: string } = {
'jpg': 'image/jpeg',
'png': 'image/png'
};
export function imgDataToBlob(imageData: ImageData, extension: string): Promise<Blob> {
const canvas = document.createElement('canvas');
canvas.width = imageData.width;
canvas.height = imageData.height;
const ctx = canvas.getContext('2d');
ctx?.putImageData(imageData, 0, 0);
return new Promise((resolve, reject) => {
canvas.toBlob(blob => blob ? resolve(blob) : reject("blob is null..."), mimeType[extension], 0.92);
});
}
export function getImgDataFromFile(file: File): Promise<ImageData> {
const url = URL.createObjectURL(file);
const img = new Image();
img.src = url;
return new Promise((resolve, reject) => {
img.onload = function () {
const canvas = document.createElement('canvas');
const ctx = canvas.getContext('2d');
if (!ctx) {
reject('canvas2d is null');
return;
}
canvas.width = img.width;
canvas.height = img.height;
ctx.drawImage(img, 0, 0);
const imageData = ctx.getImageData(0, 0, canvas.width, canvas.height);
URL.revokeObjectURL(url);
resolve(imageData);
};
});
}
export async function getFaceImgData(file: File, face: FaceEnum): Promise<ImageData> {
try {
const imgData = await getImgDataFromFile(file);
return copyFaceData(imgData, face, 0, InterpolationEnum.Bilinear);
} catch (e) {
throw e;
}
}
export async function getImgImgUrl(imgData: ImageData): Promise<string> {
try {
const blob = await imgDataToBlob(imgData, 'png');
return URL.createObjectURL(blob);
} catch (e) {
throw e;
}
}
export async function get6FaceImgUrl(file: File): Promise<Array<string>> {
try {
const [pxData, nxData, nyData, pyData, nzData, pzData] = await Promise.all([getFaceImgData(file, FaceEnum.PX), getFaceImgData(file, FaceEnum.NX),
getFaceImgData(file, FaceEnum.NY), getFaceImgData(file, FaceEnum.PY), getFaceImgData(file, FaceEnum.NZ), getFaceImgData(file, FaceEnum.PZ)]);
const [ pxUrl, nxUrl, nyUrl, pyUrl, nzUrl, pzUrl ] = await Promise.all([getImgImgUrl(pxData), getImgImgUrl(nxData), getImgImgUrl(nyData), getImgImgUrl(pyData), getImgImgUrl(nzData), getImgImgUrl(pzData)])
return [ pxUrl, nxUrl, nyUrl, pyUrl, nzUrl, pzUrl ];
} catch (e) {
throw e;
}
}
export function downloadUrlFile(url:string, name: string){
const a = document.createElement('a');
a.href = url;
a.download = name;
a.click();
}
结果展示

