update bi改动添加组件
parent
e066d17483
commit
5c6a78a3bd
@ -0,0 +1,5 @@
|
|||||||
|
:root,
|
||||||
|
html,
|
||||||
|
html.dark {
|
||||||
|
@include varELColor($el-colors, -$w-step);
|
||||||
|
}
|
||||||
@ -0,0 +1,3 @@
|
|||||||
|
:root,html {
|
||||||
|
@include varELColor($el-colors, $w-step);
|
||||||
|
}
|
||||||
@ -0,0 +1,83 @@
|
|||||||
|
<template>
|
||||||
|
<div>
|
||||||
|
<div class="WColorHsl" :style="{ '--h': h, '--s': s + '%', '--l': l + '%' }">
|
||||||
|
<w-color-loop v-model="h1" />
|
||||||
|
<w-color-loop v-model="s1" />
|
||||||
|
<w-color-loop v-model="l1" />
|
||||||
|
<div style="width: 3em;
|
||||||
|
height: 3em ;
|
||||||
|
border: solid .15em #FFF; border-radius: 3em;
|
||||||
|
box-shadow: 0 0 .5em #0009 inset, 0 0 .5em #0009;
|
||||||
|
background-color: hsl(var(--h),var(--s),var(--l));
|
||||||
|
">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
<div style="width: 2rem; height: 2rem;" :style="{ 'background-color': `hsl(${deg},100%,50%)` }"></div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup>
|
||||||
|
import { computed } from 'vue'
|
||||||
|
const h = defineModel('h', { type: Number, default: 0 });
|
||||||
|
const s = defineModel('s', { type: Number, default: 100 });
|
||||||
|
const l = defineModel('l', { type: Number, default: 50 });
|
||||||
|
const h1 = computed({
|
||||||
|
get() {
|
||||||
|
return h.value;
|
||||||
|
},
|
||||||
|
set(val) {
|
||||||
|
h.value = Math.round(val);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
const s1 = computed({
|
||||||
|
get() {
|
||||||
|
return s.value * 3.6;
|
||||||
|
},
|
||||||
|
set(val) {
|
||||||
|
s.value = Math.round(val / 3.6);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
const l1 = computed({
|
||||||
|
get() {
|
||||||
|
return l.value * 3.6;
|
||||||
|
},
|
||||||
|
set(val) {
|
||||||
|
l.value = Math.round(val / 3.6);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
</script>
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
.WColorHsl {
|
||||||
|
width: 15em;
|
||||||
|
height: 15em;
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
position: relative;
|
||||||
|
|
||||||
|
&>div {
|
||||||
|
position: absolute;
|
||||||
|
|
||||||
|
&:nth-child(1) {
|
||||||
|
width: 15em;
|
||||||
|
}
|
||||||
|
|
||||||
|
&:nth-child(2) {
|
||||||
|
width: 11em;
|
||||||
|
--colors: conic-gradient(hsl(var(--h), 0%, 50%),
|
||||||
|
hsl(var(--h), 100%, 50%));
|
||||||
|
}
|
||||||
|
|
||||||
|
&:nth-child(3) {
|
||||||
|
width: 7em;
|
||||||
|
--colors: conic-gradient(hsl(var(--h), 100%, 0%),
|
||||||
|
hsl(var(--h), 100%, 50%),
|
||||||
|
hsl(var(--h), 100%, 100%));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@ -0,0 +1,89 @@
|
|||||||
|
<template>
|
||||||
|
<div class="color-loop" :class="{ disabled: props.disabled }" :style="{ '--color-offset': 360 - deg + 'deg' }"
|
||||||
|
@click.stop.prevent="changeDeg" />
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup>
|
||||||
|
|
||||||
|
|
||||||
|
const deg = defineModel({ type: Number, default: 0 });
|
||||||
|
|
||||||
|
const props = defineProps({
|
||||||
|
disabled: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
const changeDeg = (event) => {
|
||||||
|
if (props.disabled) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const m = event.target.clientWidth / 2;
|
||||||
|
const x = event.offsetX - m;
|
||||||
|
const y = event.offsetY - m;
|
||||||
|
const temp = Math.round(Math.atan2(x, -y) * (180 / Math.PI));
|
||||||
|
deg.value = (deg.value + temp + 360) % 360;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
</script>
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
@layer {
|
||||||
|
.color-loop {
|
||||||
|
width: 10em;
|
||||||
|
--width-loop: 2em;
|
||||||
|
--color-offset: 0deg;
|
||||||
|
aspect-ratio: 1/1;
|
||||||
|
position: relative;
|
||||||
|
overflow: hidden;
|
||||||
|
border-radius: 100vw;
|
||||||
|
cursor: crosshair;
|
||||||
|
border: solid .15em #FFF;
|
||||||
|
box-shadow: 0 0 .5em #0009 inset, 0 0 .5em #0009;
|
||||||
|
|
||||||
|
&.disabled {
|
||||||
|
cursor: default;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
&::before {
|
||||||
|
content: '';
|
||||||
|
position: absolute;
|
||||||
|
inset: 0;
|
||||||
|
|
||||||
|
background: var(--colors, conic-gradient(hsl(0, 100%, 50%),
|
||||||
|
hsl(60, 100%, 50%),
|
||||||
|
hsl(120, 100%, 50%),
|
||||||
|
hsl(180, 100%, 50%),
|
||||||
|
hsl(240, 100%, 50%),
|
||||||
|
hsl(300, 100%, 50%),
|
||||||
|
hsl(360, 100%, 50%)));
|
||||||
|
|
||||||
|
transform: rotate(var(--color-offset));
|
||||||
|
transition: transform .5s;
|
||||||
|
border-radius: 100vw;
|
||||||
|
mask-image: radial-gradient(circle closest-side,
|
||||||
|
#0000 0%,
|
||||||
|
#0000 calc(100% - var(--width-loop) - 1px),
|
||||||
|
#000 calc(100% - var(--width-loop)));
|
||||||
|
pointer-events: none;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
&::after {
|
||||||
|
content: '';
|
||||||
|
width: var(--width-loop);
|
||||||
|
height: var(--width-loop);
|
||||||
|
position: absolute;
|
||||||
|
background: #FFF;
|
||||||
|
box-shadow: 0 0 .5em #0009;
|
||||||
|
pointer-events: none;
|
||||||
|
left: 50%;
|
||||||
|
top: 0;
|
||||||
|
transform: translate(-50%, -95%) rotate(45deg);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@ -0,0 +1,109 @@
|
|||||||
|
<template>
|
||||||
|
<div class="WDragZoom" @wheel.stop.prevent="handleWheel">
|
||||||
|
<w-move v-model="xy" :show="false" style="width: 100%; height: 100%; box-shadow:none; opacity: 1; transition: all .3s;">
|
||||||
|
<div class="WDragZoom-zoom" :style="{ '--zoom': vzoom }">
|
||||||
|
<slot :x="vx" :y="vy" :zoom="vzoom">
|
||||||
|
<div class="WDragZoom-demo">x={{ vx }} | y={{ vy }} | zoom={{ vzoom }}</div>
|
||||||
|
</slot>
|
||||||
|
</div>
|
||||||
|
</w-move>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup>
|
||||||
|
import { useTemplateRef, watch, computed } from "vue";
|
||||||
|
|
||||||
|
const el = useTemplateRef("WDragZoomRef");
|
||||||
|
|
||||||
|
const vx = defineModel("x", {
|
||||||
|
type: Number,
|
||||||
|
default: 0,
|
||||||
|
});
|
||||||
|
const vy = defineModel("y", {
|
||||||
|
type: Number,
|
||||||
|
default: 0,
|
||||||
|
});
|
||||||
|
const vzoom = defineModel("zoom", {
|
||||||
|
type: Number,
|
||||||
|
default: 1,
|
||||||
|
});
|
||||||
|
|
||||||
|
const xy = computed({
|
||||||
|
get() {
|
||||||
|
return { x: vx.value, y: vy.value };
|
||||||
|
},
|
||||||
|
set(v) {
|
||||||
|
vx.value = v.x;
|
||||||
|
vy.value = v.y;
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
|
||||||
|
const props = defineProps({
|
||||||
|
max: {
|
||||||
|
type: Number,
|
||||||
|
default: 4,
|
||||||
|
},
|
||||||
|
min: {
|
||||||
|
type: Number,
|
||||||
|
default: 0.2,
|
||||||
|
},
|
||||||
|
show: {
|
||||||
|
type: Boolean,
|
||||||
|
defalut: false
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const handleWheel = (event) => {
|
||||||
|
console.debug(event);
|
||||||
|
if (event.deltaY < 0) {
|
||||||
|
vzoom.value = vzoom.value + (props.max - vzoom.value) / 10;
|
||||||
|
} else {
|
||||||
|
vzoom.value = vzoom.value - (vzoom.value - props.min) / 10;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
</script>
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
@layer {
|
||||||
|
.WDragZoom {
|
||||||
|
position: relative;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
overflow: hidden;
|
||||||
|
cursor: move;
|
||||||
|
|
||||||
|
&-zoom {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
transform: scale(var(--zoom, 1));
|
||||||
|
transition: transform 0.5s;
|
||||||
|
}
|
||||||
|
|
||||||
|
&-demo {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
|
||||||
|
background-image: repeating-linear-gradient(45deg,
|
||||||
|
#0000 0,
|
||||||
|
#0000 20px,
|
||||||
|
#f005 20px,
|
||||||
|
#f005 30px),
|
||||||
|
repeating-linear-gradient(-45deg,
|
||||||
|
#0000 0,
|
||||||
|
#0000 20px,
|
||||||
|
#00f5 20px,
|
||||||
|
#00f5 30px);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@ -0,0 +1,116 @@
|
|||||||
|
<template>
|
||||||
|
<div class="w-tabs" :class="{ up: props.up }">
|
||||||
|
<div v-for="(name, index) in list" :key="index" :class="{ 'selected': tabIndex == index }"
|
||||||
|
@click.stop.prevent="handleClick(index)" @dblclick.stop.prevent="emit('dblclick', index)">{{ name }}</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup>
|
||||||
|
import { ClickOutside } from 'element-plus';
|
||||||
|
import { computed } from 'vue';
|
||||||
|
|
||||||
|
const tabIndex = defineModel({ type: Number, default: 0 });
|
||||||
|
|
||||||
|
const emit = defineEmits(['change', 'dblclick'])
|
||||||
|
|
||||||
|
const props = defineProps({
|
||||||
|
names: { type: [String, Array], default: [] },
|
||||||
|
up: { type: Boolean, default: false },
|
||||||
|
})
|
||||||
|
|
||||||
|
const list = computed(() => {
|
||||||
|
if (Array.isArray(props.names)) {
|
||||||
|
return props.names;
|
||||||
|
} else {
|
||||||
|
return props.names.split(/[\/,;]/);
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
const handleClick = (index) => {
|
||||||
|
if (tabIndex.value != index) {
|
||||||
|
tabIndex.value = index;
|
||||||
|
emit('change', index);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
@layer {
|
||||||
|
.w-tabs {
|
||||||
|
display: flex;
|
||||||
|
font-size: 1rem;
|
||||||
|
font-weight: normal;
|
||||||
|
transform: scale(.75);
|
||||||
|
position: relative;
|
||||||
|
|
||||||
|
|
||||||
|
&>div {
|
||||||
|
padding: .5rem .8rem;
|
||||||
|
border: solid .01rem #0005;
|
||||||
|
background-color: #0002;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: all .3s;
|
||||||
|
font-weight: 300;
|
||||||
|
|
||||||
|
|
||||||
|
&:not(:first-child) {
|
||||||
|
border-left: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
&:first-child {
|
||||||
|
padding-left: 1rem;
|
||||||
|
border-top-left-radius: 2rem;
|
||||||
|
border-bottom-left-radius: 2rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
&:last-child {
|
||||||
|
padding-right: 1rem;
|
||||||
|
border-top-right-radius: 2rem;
|
||||||
|
border-bottom-right-radius: 2rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.selected {
|
||||||
|
// background-image: linear-gradient(to top, var(--w-main-light-5) 0%, var(--w-main) 70%, var(--w-main) 100%);
|
||||||
|
background-color: var(--w-selected-color,var(--w-main-light-3));
|
||||||
|
position: relative;
|
||||||
|
border-top-color: var(--w-selected-color,var(--w-main-light-3));
|
||||||
|
|
||||||
|
&:not(:first-child) {
|
||||||
|
border-top-left-radius: .1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
&:not(:last-child) {
|
||||||
|
border-top-right-radius: .1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
&::before {
|
||||||
|
content: '';
|
||||||
|
width: 0;
|
||||||
|
height: 0;
|
||||||
|
border: solid .5rem #0000;
|
||||||
|
border-top-color: var(--w-selected-color,var(--w-main-light-3));
|
||||||
|
border-bottom-color: #0000;
|
||||||
|
position: absolute;
|
||||||
|
bottom: -1rem;
|
||||||
|
top: unset;
|
||||||
|
left: 50%;
|
||||||
|
transform: translateX(-50%);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&.up>div.selected::before {
|
||||||
|
border-top-color: #0000;
|
||||||
|
border-bottom-color: var(--w-selected-color,var(--w-main-light-3));
|
||||||
|
position: absolute;
|
||||||
|
top: -1rem;
|
||||||
|
bottom: unset;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@ -0,0 +1,307 @@
|
|||||||
|
<template>
|
||||||
|
<div ref="container" class="WThree">
|
||||||
|
<div v-if="loading" class="loading">
|
||||||
|
<div>模型加载中...</div>
|
||||||
|
<div class="ps" :style="{ '--ps': ps + '%' }"></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<script setup>
|
||||||
|
import * as THREE from 'three';
|
||||||
|
import gsap from 'gsap';
|
||||||
|
import { GLTFLoader } from 'three/addons/loaders/GLTFLoader.js';
|
||||||
|
import { OrbitControls } from 'three/addons/controls/OrbitControls.js';
|
||||||
|
import { onMounted, getCurrentInstance, onUnmounted, ref, toRef } from 'vue'
|
||||||
|
import { useResizeObserver, useRafFn } from '@vueuse/core';
|
||||||
|
import { bigDataStore } from '@/store'
|
||||||
|
import axios from 'axios'
|
||||||
|
import { ElMessage } from 'element-plus';
|
||||||
|
|
||||||
|
const props = defineProps({
|
||||||
|
/**
|
||||||
|
* 需要加载的glb模型列表
|
||||||
|
*/
|
||||||
|
models: {
|
||||||
|
type: Array,
|
||||||
|
default: []
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
* 是否自由视角控制
|
||||||
|
*/
|
||||||
|
controls: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
* 是否自动加载glb中的模型
|
||||||
|
*/
|
||||||
|
auto: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false
|
||||||
|
},
|
||||||
|
/** 视角 */
|
||||||
|
fov: {
|
||||||
|
type: Number,
|
||||||
|
default: 50
|
||||||
|
},
|
||||||
|
/** 近视距 */
|
||||||
|
near: {
|
||||||
|
type: Number,
|
||||||
|
default: 0.1
|
||||||
|
},
|
||||||
|
/** 远视距 */
|
||||||
|
far: {
|
||||||
|
type: Number,
|
||||||
|
default: 2000
|
||||||
|
},
|
||||||
|
/** 场景是否透明 */
|
||||||
|
alpha: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false
|
||||||
|
},
|
||||||
|
/** 抗锯齿,值越大,效果越好,开销越大 */
|
||||||
|
samples: {
|
||||||
|
type: Number,
|
||||||
|
default: 5
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
const loading = ref(true);
|
||||||
|
|
||||||
|
const emit = defineEmits(['success', 'error', 'init', 'update', 'resize']);
|
||||||
|
|
||||||
|
|
||||||
|
const { proxy } = getCurrentInstance();
|
||||||
|
|
||||||
|
const exposes = { THREE, gsap, GLTFLoader, width: window.innerWidth, height: window.innerHeight };
|
||||||
|
|
||||||
|
const width = toRef(exposes, 'width');
|
||||||
|
const height = toRef(exposes, 'height');
|
||||||
|
|
||||||
|
const ps = ref(0);
|
||||||
|
const computedPs = () => {
|
||||||
|
let models = exposes.models || [];
|
||||||
|
let total = models.reduce((a, b) => a + (b.total || 0), 0);
|
||||||
|
if (total === 0) {
|
||||||
|
ps.value = 0;
|
||||||
|
}
|
||||||
|
let loaded = models.reduce((a, b) => a + (b.loaded || 0), 0);
|
||||||
|
ps.value = loaded / total * 100;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 视角调整给模型
|
||||||
|
* @param model 模型对象
|
||||||
|
*/
|
||||||
|
exposes.lookAt = (model) => {
|
||||||
|
model.getWorldPosition(new THREE.Vector3());
|
||||||
|
exposes.camera.lookAt(model.position);
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 加载gltf模型
|
||||||
|
* @param url 模型地址
|
||||||
|
* @param ps 进度回调
|
||||||
|
* @returns Promise
|
||||||
|
*/
|
||||||
|
exposes.loadGLTFModel = (url, ps = () => { }) => {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
const loader = new GLTFLoader();
|
||||||
|
bigDataStore.get(url).then(dbData => {
|
||||||
|
if (dbData) {
|
||||||
|
console.debug("加载缓存中的模型");
|
||||||
|
loader.parse(
|
||||||
|
dbData.data,
|
||||||
|
'',
|
||||||
|
(gltf) => resolve(gltf),
|
||||||
|
(error) => {
|
||||||
|
ElMessage.error('模型解析失败');
|
||||||
|
reject(error);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
axios.get(url, {
|
||||||
|
responseType: 'arraybuffer',
|
||||||
|
onDownloadProgress: function (progressEvent) {
|
||||||
|
ps(progressEvent.loaded, progressEvent.total);
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.then((response)=> {
|
||||||
|
bigDataStore.add(url, response.data);
|
||||||
|
console.debug("response",response);
|
||||||
|
loader.parse(
|
||||||
|
response.data,
|
||||||
|
'',
|
||||||
|
(gltf) => resolve(gltf),
|
||||||
|
(error) => {
|
||||||
|
ElMessage.error('模型解析失败');
|
||||||
|
reject(error);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
})
|
||||||
|
.catch(function (error) {
|
||||||
|
ElMessage.error('模型下载失败');
|
||||||
|
reject(error)
|
||||||
|
});
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 创建Renderer
|
||||||
|
* @param canvas
|
||||||
|
*/
|
||||||
|
exposes.newRenderer = (canvas) => {
|
||||||
|
return new THREE.WebGLRenderer({ canvas: canvas, alpha: props.alpha, antialias: true, samples: props.samples });
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 添加环境光
|
||||||
|
* @param color 颜色
|
||||||
|
* @param intensity 强度
|
||||||
|
*/
|
||||||
|
exposes.addAmbientLight = (color, intensity) => {
|
||||||
|
exposes.scene.add(new THREE.AmbientLight(color, intensity));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 添加点源光
|
||||||
|
* @param x x
|
||||||
|
* @param y y
|
||||||
|
* @param z z
|
||||||
|
* @param color 颜色
|
||||||
|
* @param intensity 强度
|
||||||
|
* @param distance 光照范围,默认:0,表示无穷远
|
||||||
|
* @param decay 削减强度,默认: 2
|
||||||
|
*/
|
||||||
|
exposes.addPointLight = (x, y, z, color, intensity, distance = 0, decay = 2) => {
|
||||||
|
const pointLight = new THREE.PointLight(color, intensity, distance, decay);
|
||||||
|
pointLight.position.set(x, y, z);
|
||||||
|
exposes.scene.add(pointLight);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
onMounted(async () => {
|
||||||
|
await proxy.$nextTick();
|
||||||
|
// 创建场景、相机和渲染器
|
||||||
|
exposes.container = proxy.$refs.container;
|
||||||
|
const w = exposes.container.clientWidth;
|
||||||
|
const h = exposes.container.clientHeight;
|
||||||
|
width.value = w;
|
||||||
|
height.value = h;
|
||||||
|
|
||||||
|
exposes.scene = new THREE.Scene();
|
||||||
|
exposes.camera = new THREE.PerspectiveCamera(props.fov, w / h, props.near, props.far);
|
||||||
|
exposes.renderer = new THREE.WebGLRenderer({ alpha: props.alpha, antialias: true, samples: props.samples });
|
||||||
|
if (props.controls) {
|
||||||
|
exposes.controls = new OrbitControls(exposes.camera, exposes.renderer.domElement);
|
||||||
|
}
|
||||||
|
exposes.renderer.setSize(w, h);
|
||||||
|
exposes.container.appendChild(exposes.renderer.domElement);
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
useResizeObserver(exposes.container, (es) => {
|
||||||
|
const rect = es[0].contentRect;
|
||||||
|
exposes.camera.aspect = rect.width / rect.height;
|
||||||
|
exposes.camera.updateProjectionMatrix();
|
||||||
|
exposes.renderer.setSize(rect.width, rect.height);
|
||||||
|
width.value = rect.width;
|
||||||
|
height.value = rect.height;
|
||||||
|
emit('resize', exposes);
|
||||||
|
});
|
||||||
|
|
||||||
|
// 渲染循环
|
||||||
|
const { pause, resume } = useRafFn(() => {
|
||||||
|
if (exposes.controls) {
|
||||||
|
exposes.controls.update();
|
||||||
|
}
|
||||||
|
exposes.renderer.render(exposes.scene, exposes.camera);
|
||||||
|
emit('update', exposes);
|
||||||
|
});
|
||||||
|
exposes.pause = pause;
|
||||||
|
exposes.resume = resume;
|
||||||
|
|
||||||
|
emit('init', exposes);
|
||||||
|
|
||||||
|
if (props.models.length == 0) {
|
||||||
|
emit('success', exposes);
|
||||||
|
} else {
|
||||||
|
exposes.models = props.models.map(a => ({ url: a }));
|
||||||
|
Promise.all(exposes.models.map(async (a) => {
|
||||||
|
const gltf = await exposes.loadGLTFModel(a.url, (loaded, total) => {
|
||||||
|
a.loaded = loaded;
|
||||||
|
a.total = total;
|
||||||
|
computedPs();
|
||||||
|
});
|
||||||
|
a.model = gltf;
|
||||||
|
if (props.auto) {
|
||||||
|
const model = a.model.scene;
|
||||||
|
exposes.scene.add(model);
|
||||||
|
}
|
||||||
|
})).then(() => {
|
||||||
|
if (props.auto && !props.controls) {
|
||||||
|
const model = exposes.models[0].model.scene;
|
||||||
|
model.getWorldPosition(new THREE.Vector3());
|
||||||
|
exposes.camera.lookAt(model.position);
|
||||||
|
}
|
||||||
|
emit('success', exposes);
|
||||||
|
}).catch((e) => {
|
||||||
|
console.error(e);
|
||||||
|
emit('error', exposes, e);
|
||||||
|
}).finally(() => {
|
||||||
|
loading.value = false;
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
})
|
||||||
|
|
||||||
|
defineExpose(exposes);
|
||||||
|
|
||||||
|
</script>
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
.WThree {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
overflow: hidden;
|
||||||
|
|
||||||
|
.loading {
|
||||||
|
position: absolute;
|
||||||
|
inset: 0;
|
||||||
|
background-color: #00000001;
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
flex-direction: column;
|
||||||
|
|
||||||
|
.ps {
|
||||||
|
width: 50%;
|
||||||
|
height: 2em;
|
||||||
|
border: .1em solid #fff2;
|
||||||
|
background-color: #fff1;
|
||||||
|
border-radius: 2em;
|
||||||
|
margin: .5em;
|
||||||
|
overflow: hidden;
|
||||||
|
|
||||||
|
&::before {
|
||||||
|
content: '';
|
||||||
|
display: flex;
|
||||||
|
overflow: visible;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
width: var(--ps);
|
||||||
|
height: 100%;
|
||||||
|
background-color: #fff5;
|
||||||
|
border-radius: 2em;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
Loading…
Reference in New Issue