update bi改动添加组件

master
管理员 5 months ago
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);
}

@ -37,8 +37,8 @@ $w-z-index: 100;
$w-z-index-top: 1000; $w-z-index-top: 1000;
$colors: ( $colors: (
"main": #2781bf, "main": hsl(185, 80%, 50%),
"text": #333, "text": hsl(185, 80%, 5%),
"bg": #F8F8F8, "bg": #F8F8F8,
"primary": #3c9cff, "primary": #3c9cff,
"warn": #eaa339, "warn": #eaa339,
@ -61,6 +61,16 @@ $dark-colors: (
"danger": map.get($colors, "danger") "danger": map.get($colors, "danger")
); );
$el-colors: (
"primary":"main",
"warn": "warn",
"success": "success",
"info": "info",
"error": "error",
"danger": "danger"
);
@mixin varColor($map, $step) { @mixin varColor($map, $step) {
@ -68,21 +78,33 @@ $dark-colors: (
$s: color.channel($value, "saturation", $space: hsl); $s: color.channel($value, "saturation", $space: hsl);
$h: color.channel($value, "hue", $space: hsl); $h: color.channel($value, "hue", $space: hsl);
$l: color.channel($value, "lightness", $space: hsl); $l: color.channel($value, "lightness", $space: hsl);
--w-#{$name}:#{$value}; --w-#{$name}-h: #{$h};
--w-#{$name}-h:#{$h}; --w-#{$name}-s: #{$s};
--w-#{$name}-s:#{$s}; --w-#{$name}-l: #{$l};
--w-#{$name}-l:#{$l}; --w-#{$name}: hsl(var(--w-#{$name}-h), var(--w-#{$name}-s), var(--w-#{$name}-l));
--w-#{$name}-light:#{hsl($h,$s,$l + $step)}; //
--w-#{$name}-light-1:#{hsl($h,$s,$l + ($step * 2))}; @for $n from 1 through 6 {
--w-#{$name}-light-2:#{hsl($h,$s,$l + ($step * 3))}; @if $n == 1 {
--w-#{$name}-light-3:#{hsl($h,$s,$l + ($step * 4))}; --w-#{$name}-light: hsl(var(--w-#{$name}-h), var(--w-#{$name}-s), calc(var(--w-#{$name}-l) + #{$n} * var(--w-step) * 1%));
--w-#{$name}-light-4:#{hsl($h,$s,$l + ($step * 5))}; --w-#{$name}-dark: hsl(var(--w-#{$name}-h), var(--w-#{$name}-s), calc(var(--w-#{$name}-l) - #{$n} * var(--w-step) * 1%));
--w-#{$name}-light-5:#{hsl($h,$s,$l + ($step * 6))}; } @else {
--w-#{$name}-dark:#{hsl($h,$s,$l - $step)}; --w-#{$name}-light-#{$n - 1}: hsl(var(--w-#{$name}-h), var(--w-#{$name}-s), calc(var(--w-#{$name}-l) + #{$n} * var(--w-step) * 1%));
--w-#{$name}-dark-1:#{hsl($h,$s,$l - ($step * 2))}; --w-#{$name}-dark-#{$n - 1}: hsl(var(--w-#{$name}-h), var(--w-#{$name}-s), calc(var(--w-#{$name}-l) - #{$n} * var(--w-step) * 1%));
--w-#{$name}-dark-2:#{hsl($h,$s,$l - ($step * 3))}; }
--w-#{$name}-dark-3:#{hsl($h,$s,$l - ($step * 4))}; }
--w-#{$name}-dark-4:#{hsl($h,$s,$l - ($step * 5))};
--w-#{$name}-dark-5:#{hsl($h,$s,$l - ($step * 6))}; }
}
@mixin varELColor($map, $step) {
@each $name , $value in $map {
--el-color-#{$name}: hsl(var(--w-#{$value}-h), var(--w-#{$value}-s), var(--w-#{$value}-l));
@for $n from 1 through 9 {
--el-color-#{$name}-light-#{$n}: hsl(var(--w-#{$value}-h), var(--w-#{$value}-s), calc(var(--w-#{$value}-l) + #{$n} * var(--w-step) * 1%));
--el-color-#{$name}-dark-#{$n}: hsl(var(--w-#{$value}-h), var(--w-#{$value}-s), calc(var(--w-#{$value}-l) - #{$n} * var(--w-step) * 1%));
}
} }
} }

@ -89,6 +89,8 @@
border-radius: 10em; border-radius: 10em;
border: .25em solid currentColor; border: .25em solid currentColor;
background-color: #FFF; background-color: #FFF;
z-index: 1; z-index: 1;
transform: rotate(-25deg); transform: rotate(-25deg);
padding: 0; padding: 0;
@ -258,6 +260,7 @@
&::before { &::before {
content: ''; content: '';
display: inline-block;
font-size: var(--btn-size, inherit); font-size: var(--btn-size, inherit);
width: 1em; width: 1em;
height: 1em; height: 1em;

@ -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>

@ -70,7 +70,7 @@ const getDefaultOption = () => ({
}, },
xAxis: { xAxis: {
type: 'category', type: 'category',
boundaryGap: false, boundaryGap: true,
// data: [], // data: [],
axisLine: { axisLine: {
lineStyle: { lineStyle: {

@ -1,5 +1,5 @@
<template> <template>
<div class="WEffectNoise" ref="WEffectNoiseRef" :class="{ 'am': props.am }" :style="{ filter: 'url(#noiseFilter' + uid + ')' }"> <div class="WEffectNoise" ref="WEffectNoiseRef" :style="{ filter: 'url(#noiseFilter' + uid + ')' }">
<svg style="display: none;"> <svg style="display: none;">
<defs> <defs>
<filter :id="'noiseFilter' + uid" color-interpolation-filters="linearRGB" filterUnits="objectBoundingBox" <filter :id="'noiseFilter' + uid" color-interpolation-filters="linearRGB" filterUnits="objectBoundingBox"
@ -18,12 +18,7 @@
import { onMounted, useTemplateRef } from 'vue' import { onMounted, useTemplateRef } from 'vue'
import { IdGenerator } from "@/util"; import { IdGenerator } from "@/util";
const props = defineProps({
am: {
type: Boolean,
default: true
}
})
// //
const uid = IdGenerator.next(); const uid = IdGenerator.next();
@ -58,11 +53,10 @@ noisePlayer();
position: relative; position: relative;
inset: 0; inset: 0;
--noise-base: 0; --noise-base: 0;
animation: none; animation: am-WEffectNoise var(--time, 5s) linear infinite;
animation-play-state: inherit;
&.am {
animation: am-WEffectNoise var(--time, 5s) linear infinite;
}
} }
@keyframes am-WEffectNoise { @keyframes am-WEffectNoise {

@ -1,9 +1,10 @@
<template> <template>
<div class="logo" :class="['logo' + (poStore.logo % 5)]" @click="poStore.logo++"></div> <div class="logo" :class="['logo' + (poStore.logo % 5)]" @click="poStore.logo++" :style="{'--logo1':`url(${configStore.config.logo1})`,'--logo2':`url(${configStore.config.logo2})`}"></div>
</template> </template>
<script setup> <script setup>
import { usePoStore } from '@/store' import { usePoStore, useConfigStore } from '@/store';
const poStore = usePoStore() const poStore = usePoStore()
const configStore = useConfigStore()
</script> </script>
<style lang="scss" scoped> <style lang="scss" scoped>
.logo { .logo {
@ -13,25 +14,27 @@ const poStore = usePoStore()
background-size: auto 7em; background-size: auto 7em;
background-position: center; background-position: center;
background-origin: content-box; background-origin: content-box;
--logo1: url(@/assets/imgs/logo1.png);
--logo2: url(@/assets/imgs/logo2.png);
&.logo0 { &.logo0 {
background-image: url(@/assets/imgs/logo1.png); background-image: var(--logo1);
} }
&.logo1 { &.logo1 {
background-image: url(@/assets/imgs/logo2.png); background-image: var(--logo2);
} }
&.logo2 { &.logo2 {
background-image: url(@/assets/imgs/logo1.png), url(@/assets/imgs/logo2.png); background-image: var(--logo1), var(--logo2);
background-repeat: no-repeat, no-repeat; background-repeat: no-repeat, no-repeat;
background-size: auto 4.6em, auto 4.6em; background-size: auto 4.6em, auto 4.6em;
background-position: center bottom, center top; background-position: center bottom, center top;
} }
&.logo3 { &.logo3 {
background-image: url(@/assets/imgs/logo1.png), url(@/assets/imgs/logo2.png); background-image: var(--logo1), var(--logo2);
background-repeat: no-repeat, no-repeat; background-repeat: no-repeat, no-repeat;
background-size: auto 4.6em, auto 4.6em; background-size: auto 4.6em, auto 4.6em;
background-position: center top, center bottom; background-position: center top, center bottom;

@ -1,38 +1,41 @@
<template> <template>
<w-text am time="15s" style="--move: 15em" class="main-title"> <w-text am time="15s" class="main-title">
<div :contenteditable="editable" @dblclick="edit"> <div :contenteditable="editable" @dblclick="edit">
{{ poStore.po.mainTitle || title }} {{ poStore.po.mainTitle || configStore.config.title || title }}
</div> </div>
</w-text> </w-text>
</template> </template>
<script setup> <script setup>
import { ref } from 'vue' import { ref } from 'vue'
import { usePoStore } from '@/store' import { usePoStore, useConfigStore } from '@/store'
const title = import.meta.env.VITE_APP_NAME const title = import.meta.env.VITE_APP_NAME
const poStore = usePoStore() const poStore = usePoStore()
const configStore = useConfigStore()
const editable = ref(false) const editable = ref(false)
const edit = (event) => { const edit = (event) => {
if (editable.value == false) { if (editable.value == false) {
editable.value = true editable.value = true
} else { } else {
editable.value = false editable.value = false
poStore.po.mainTitle = event.target.textContent; poStore.po.mainTitle = event.target.textContent;
} }
} }
</script> </script>
<style lang="scss" scoped> <style lang="scss" scoped>
.main-title { .main-title {
color: var(--w-main-light-3); color: var(--w-main-dark-3);
display: flex; --c: var(--w-text-light);
align-items: center; display: flex;
justify-content: center; align-items: center;
font-family: "黑体"; justify-content: center;
font-weight: bold; font-family: "黑体";
font-size: 2.5rem; font-weight: bold;
letter-spacing: 0.2rem; font-size: 2.5rem;
// transform: scale(1, 1.1); letter-spacing: 0.2rem;
--move: 15em;
--c-l: var(--w-text-dark-5);
} }
</style> </style>

@ -6,12 +6,12 @@
'z-index': zIndex 'z-index': zIndex
}"> }">
<slot>可移动内容</slot> <slot>可移动内容</slot>
<div :hidden="!props.move" class="w-move-hover">{{ xy.x }}:{{ xy.y }}</div> <div v-if="show" :hidden="!props.move" class="w-move-hover">{{ xy.x }}:{{ xy.y }}</div>
</div> </div>
</template> </template>
<script setup> <script setup>
import { computed, ref } from 'vue'; import { computed, ref, onMounted, onUnmounted } from 'vue';
const props = defineProps({ const props = defineProps({
modelValue: { modelValue: {
type: Object, type: Object,
@ -21,6 +21,10 @@ const props = defineProps({
type: Boolean, type: Boolean,
default: true default: true
}, },
show: {
type: Boolean,
default: true
}
}); });
const emit = defineEmits(['update:modelValue', 'moveStart', 'moveEnd']); const emit = defineEmits(['update:modelValue', 'moveStart', 'moveEnd']);
@ -62,39 +66,55 @@ const handleTouchstart = (e) => {
emit('moveStart'); emit('moveStart');
} }
window.addEventListener("mousemove", (event) => { const mousemove = (event) => {
if (currentxy != null) { if (currentxy != null) {
movexy = { x: event.x, y: event.y }; movexy = { x: event.x, y: event.y };
xy.value.x += movexy.x - currentxy.x; xy.value.x += movexy.x - currentxy.x;
xy.value.y += movexy.y - currentxy.y; xy.value.y += movexy.y - currentxy.y;
currentxy = movexy; currentxy = movexy;
xy.value = xy.value;
} }
}); }
window.addEventListener("touchmove", (event) => { const touchmove = (event) => {
if (currentxy != null) { if (currentxy != null) {
console.info(event); console.info(event);
movexy = { x: event.touches[0].screenX, y: event.touches[0].screenY }; movexy = { x: event.touches[0].screenX, y: event.touches[0].screenY };
xy.value.x += movexy.x - currentxy.x; xy.value.x += movexy.x - currentxy.x;
xy.value.y += movexy.y - currentxy.y; xy.value.y += movexy.y - currentxy.y;
currentxy = movexy; currentxy = movexy;
xy.value = xy.value;
} }
}); }
window.addEventListener("mouseup", () => { const mouseup = () => {
if (currentxy) { if (currentxy) {
currentxy = null; currentxy = null;
emit('moveEnd'); emit('moveEnd');
} }
}); };
window.addEventListener("touchend", () => {
const touchend = () => {
if (currentxy) { if (currentxy) {
currentxy = null; currentxy = null;
emit('moveEnd'); emit('moveEnd');
} }
}); }
onMounted(() => {
window.addEventListener("touchend", touchend);
window.addEventListener("mouseup", mouseup);
window.addEventListener("touchmove", touchmove);
window.addEventListener("mousemove", mousemove);
})
onUnmounted(() => {
window.removeEventListener("touchend", touchend);
window.removeEventListener("mouseup", mouseup);
window.removeEventListener("touchmove", touchmove);
window.removeEventListener("mousemove", mousemove);
})
</script> </script>
<style> <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>

@ -1,5 +1,5 @@
<template> <template>
<el-select v-model="modelValue" :clearable="clearable" :size="size" :placeholder="placeholder" :disabled="disabled" > <el-select v-model="modelValue" :clearable="clearable" @change="emit('change')" :size="size" :placeholder="placeholder" :disabled="disabled" >
<el-option v-for="item in dictStore.list(props.name)" :key="item.value" :label="item.label" :value="item.value" /> <el-option v-for="item in dictStore.list(props.name)" :key="item.value" :label="item.label" :value="item.value" />
</el-select> </el-select>
</template> </template>
@ -12,6 +12,7 @@ const dictStore = useDictStore();
const modelValue = defineModel({type: Number}); const modelValue = defineModel({type: Number});
const props = defineProps({ const props = defineProps({
name: { name: {
type: String, type: String,

@ -1,5 +1,5 @@
<template> <template>
<div @click="isDark = !isDark" title="主题切换"> <div v-if="false" @click="isDark = !isDark" title="主题切换">
<slot :isDark="isDark"> <slot :isDark="isDark">
<w-icon icon-class="lb" style="margin-right: 0.2em" />{{ <w-icon icon-class="lb" style="margin-right: 0.2em" />{{
isDark ? "暗" : "亮" isDark ? "暗" : "亮"
@ -19,6 +19,9 @@ watch(
} }
); );
onMounted(() => { onMounted(() => {
if(true){
return;
}
let darkStr = localStorage.getItem("isDark"); let darkStr = localStorage.getItem("isDark");
if(darkStr) { if(darkStr) {
isDark.value = darkStr === "true"; isDark.value = darkStr === "true";

@ -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>

@ -1,9 +1,9 @@
<template> <template>
<div class="w-time-root"> <div class="w-time-root">
{{ {{
d.format("yyyy年MM月dd日 星期"+ws[d.getDay()]+" HH:mm:ss") d.format("yyyy年MM月dd日 星期"+ws[d.getDay()]+" HH:mm:ss")
}} }}
</div> </div>
</template> </template>
<script setup> <script setup>
import { onMounted, ref } from 'vue'; import { onMounted, ref } from 'vue';
@ -12,22 +12,22 @@ const ws= ref("日一二三四五六");
const d = ref(new Date()); const d = ref(new Date());
onMounted(()=>{ onMounted(()=>{
window.setInterval(()=>{ window.setInterval(()=>{
d.value = new Date(new Date().getTime()); d.value = new Date(new Date().getTime());
},1000); },1000);
}); });
</script> </script>
<style scoped> <style scoped>
.w-time-root { .w-time-root {
display: flex; display: flex;
justify-content: center; justify-content: center;
align-items: center; align-items: center;
line-height: 2; line-height: 2;
text-shadow: 0 0 .2em var(--w-text); text-shadow: 0 0 .2em var(--w-bg-dark);
color: var(--w-bg-light); color: var(--w-text-light-2);
font-family: '黑体'; font-family: '黑体';
letter-spacing: .1em; letter-spacing: .1em;
font-size: 1.3em; font-size: 1.3em;
line-height: 1; line-height: 1;
} }
</style> </style>

@ -1,22 +1,41 @@
<!--
工具栏组件
提供页面刷新后退全屏登录/退出管理后台等功能
-->
<template> <template>
<div class="w-toolbar"> <div class="w-toolbar">
<!-- 主题切换 -->
<w-theme /> <w-theme />
<!-- 刷新页面 -->
<div onclick="location.reload()" title="刷新"> <div onclick="location.reload()" title="刷新">
<w-icon icon-class="reload" /> <w-icon icon-class="reload" />
</div> </div>
<!-- 后退 -->
<div @click="proxy.$router.back()" title="后退"> <div @click="proxy.$router.back()" title="后退">
<w-icon icon-class="back" /> <w-icon icon-class="back" />
</div> </div>
<!-- 全屏切换 -->
<w-fullscreen /> <w-fullscreen />
<!-- 已登录状态下的操作 -->
<template v-if="authStore.isLogin"> <template v-if="authStore.isLogin">
<!-- 管理后台 -->
<div onclick="window.open('/admin/')" title="管理后端"> <div onclick="window.open('/admin/')" title="管理后端">
<w-icon icon-class="sys" /> <w-icon icon-class="sys" />
</div> </div>
<!-- 退出登录 -->
<div @click="logout()" title="退出"> <div @click="logout()" title="退出">
<w-icon icon-class="exit" /> <w-icon icon-class="exit" />
</div> </div>
</template> </template>
<!-- 未登录且不在登录页面 -->
<template v-else-if="proxy.$route.path != '/login'"> <template v-else-if="proxy.$route.path != '/login'">
<!-- 登录 -->
<div @click="proxy.$router.push({ path: '/login' })" title="登录"> <div @click="proxy.$router.push({ path: '/login' })" title="登录">
<w-icon icon-class="logininfor" /> <w-icon icon-class="logininfor" />
</div> </div>
@ -24,13 +43,25 @@
</div> </div>
</template> </template>
<script setup> <script setup>
/**
* 工具栏组件
* 提供页面刷新后退全屏登录/退出管理后台等功能
*/
import { getCurrentInstance } from "vue"; import { getCurrentInstance } from "vue";
import { useAuthStore } from "@/store"; import { useAuthStore } from "@/store";
import { ElMessage } from "element-plus"; import { ElMessage } from "element-plus";
//
const { proxy } = getCurrentInstance(); const { proxy } = getCurrentInstance();
//
const authStore = useAuthStore(); const authStore = useAuthStore();
/**
* 退出登录
* 清除认证信息并跳转到登录页面
*/
const logout = async () => { const logout = async () => {
await authStore.logout(); await authStore.logout();
ElMessage.success("成功退出"); ElMessage.success("成功退出");
@ -41,6 +72,9 @@ const logout = async () => {
}; };
</script> </script>
<style lang="scss" scoped> <style lang="scss" scoped>
/**
* 工具栏样式
*/
.w-toolbar { .w-toolbar {
display: flex; display: flex;
justify-content: space-around; justify-content: space-around;
@ -48,17 +82,21 @@ const logout = async () => {
padding: 0.5em; padding: 0.5em;
width: auto; width: auto;
//
& > div { & > div {
cursor: pointer; cursor: pointer;
opacity: 0.9; opacity: 0.9;
transition: all 0.3s; transition: all 0.3s;
margin-left: 0.7em; margin-left: 0.7em;
//
&:hover { &:hover {
opacity: 1; opacity: 1;
transform: translate(0.05em, 0.05em); transform: translate(0.05em, 0.05em);
} }
} }
//
svg { svg {
width: 1em; width: 1em;
height: 1em; height: 1em;

@ -1,64 +1,61 @@
/**
* 项目入口文件
* 负责初始化Vue应用配置全局插件和组件
*/
import { createApp } from 'vue' import { createApp } from 'vue'
import App from './App.vue' import App from './App.vue'
//导入路由配置
import router from './router/'
// 导入路由配置
import router from './router/'
//导入element-plus // 导入Element Plus UI组件库
import ElementPlus from 'element-plus' import ElementPlus from 'element-plus'
import zhCn from 'element-plus/dist/locale/zh-cn.mjs' import zhCn from 'element-plus/dist/locale/zh-cn.mjs' // 中文语言包
import 'normalize.css' import 'normalize.css' // CSS重置样式
import 'element-plus/dist/index.css' import 'element-plus/dist/index.css' // Element Plus样式
import 'element-plus/theme-chalk/dark/css-vars.css' import 'element-plus/theme-chalk/dark/css-vars.css' // Element Plus暗色主题
import "./assets/css/main.scss" import "./assets/css/main.scss" // 项目全局样式
import './assets/css/element-plus-dark.scss'
// import dataV from '@jiaminghi/data-view'
// 导入Element Plus图标
//导入element-plus的图标
import * as ElementPlusIconsVue from '@element-plus/icons-vue' import * as ElementPlusIconsVue from '@element-plus/icons-vue'
//npm uninstall vuex // 导入Pinia状态管理
//npm install pinia
//npm i pinia-plugin-persist 持久化
import { createPinia } from "pinia"; import { createPinia } from "pinia";
import piniaPluginPersist from 'pinia-plugin-persist' import piniaPluginPersist from 'pinia-plugin-persist' // Pinia持久化插件
// 导入SVG图标注册
import 'virtual:svg-icons-register' import 'virtual:svg-icons-register'
//导入自定义
// 导入自定义指令、工具和组件
import { permissions } from "./directives"; import { permissions } from "./directives";
import { util } from "./util"; import { util } from "./util";
import { components } from "./components" import { components } from "./components"
// 创建Pinia实例并使用持久化插件
const store = createPinia() const store = createPinia()
store.use(piniaPluginPersist) store.use(piniaPluginPersist)
// 创建Vue应用实例
let app = createApp(App) let app = createApp(App)
// 全局注册Element Plus图标组件
//全局注册Element图标
for (const [key, component] of Object.entries(ElementPlusIconsVue)) { for (const [key, component] of Object.entries(ElementPlusIconsVue)) {
app.component(key, component) app.component(key, component)
} }
// 配置应用实例
app app
.use(router)//使用路由 .use(router) // 使用路由
.use(ElementPlus, { .use(ElementPlus, {
locale: zhCn, locale: zhCn, // 设置Element Plus语言为中文
})//使用ElementPlus,并使用中文语言 })
// .use(dataV) .use(store) // 使用Pinia状态管理
.use(store) //使用pinia数据仓库 .use(permissions) // 使用自定义指令
.use(util) // 使用自定义工具
.use(permissions) //使用自定义指令 .use(components) // 自动注册全局组件
.use(util)//使用自定义工具 .mount('#app') // 挂载到id为app的HTML元素
.use(components)//自动注册全局组件
.mount('#app')//挂载到id=app的html元素中

@ -76,41 +76,44 @@ list.value.unshift("index");
display: flex; display: flex;
font-size: 0.9rem; font-size: 0.9rem;
position: relative; position: relative;
height: 2rem; height: 2.5rem;
text-decoration: none; text-decoration: none;
align-items: center; align-items: center;
justify-content: center; justify-content: center;
transition: all 0.5s; // transition: all 0.5s;
margin: 0.3rem 0; margin: 0.3rem 0;
color: var(--el-color-primary-dark-2); color: var(--w-main-dark);
border: solid 0.1rem #0000; border: solid 0.2rem #0000;
&:not(.router-link-exact-active):hover { &:not(.router-link-exact-active):hover {
background-color: var(--w-bg-light); background-color: var(--w-main-light-5);
color: var(--w-primary-dark-3); color: var(--w-main-dark-5);
border: solid 0.1rem var(--w-primary-dark-3); border: solid 0.01rem var(--w-main-dark-5);
transform: scale(0.95); // transform: scale(0.95);
} }
&::after { &::after {
content: ""; content: "";
position: absolute; position: absolute;
box-sizing: border-box; box-sizing: border-box;
right: 0rem; right: 0rem;
width: 1rem; width: 0rem;
height: 2rem; height: 0rem;
border: solid 1rem #0000; border: solid 1rem #0000;
border-right-width: 0; border-right-width: 0;
border-left-color: var(--c, #0000); border-left-color: var(--c, #0000);
transition: all 0.5s; // transition: all 0.5s;
} }
&.router-link-exact-active { &.router-link-exact-active {
--c: var(--w-primary-dark-2); --c: var(--w-main-light-3);
background-color: var(--c); background-color: var(--c);
color: #fff; color: #fff;
transform: scale(1.05); transform: scale(1.05);
border-top-right-radius: .65rem;
border-bottom-right-radius: .65rem;
&:hover { &:hover {
--c: var(--w-primary-dark-3); --c: var(--w-main-light-2);
} }
&::after { &::after {
right: -1rem; right: -1rem;

Loading…
Cancel
Save