mirror of
https://github.com/jxxghp/MoviePilot-Frontend.git
synced 2026-08-12 17:14:37 +08:00
feat: add 3D MetalLogo component and update dependencies
This commit is contained in:
@@ -69,6 +69,7 @@
|
||||
"qrcode": "^1.5.4",
|
||||
"sass": "^1.83.4",
|
||||
"tailwindcss": "^ 3.4.17",
|
||||
"three": "^0.185.1",
|
||||
"vue": "^3.5.13",
|
||||
"vue-router": "^4.5.0",
|
||||
"vue-toastification": "^2.0.0-rc.5",
|
||||
@@ -103,6 +104,7 @@
|
||||
"@types/node": "^20.1.4",
|
||||
"@types/nprogress": "^0.2.3",
|
||||
"@types/qrcode": "^1.5.6",
|
||||
"@types/three": "^0.185.1",
|
||||
"@types/webfontloader": "^1.6.34",
|
||||
"@typescript-eslint/eslint-plugin": "^8.20.0",
|
||||
"@typescript-eslint/parser": "^8.20.0",
|
||||
|
||||
461
src/components/misc/MetalLogo3D.vue
Normal file
461
src/components/misc/MetalLogo3D.vue
Normal file
@@ -0,0 +1,461 @@
|
||||
<script setup lang="ts">
|
||||
import { onBeforeUnmount, onMounted, ref } from 'vue'
|
||||
import * as THREE from 'three'
|
||||
import { RoomEnvironment } from 'three/examples/jsm/environments/RoomEnvironment.js'
|
||||
import logoUrl from '@images/logo.png'
|
||||
|
||||
type LogoPoint = readonly [number, number]
|
||||
|
||||
interface LogoPieceDefinition {
|
||||
points: readonly LogoPoint[]
|
||||
faceColor: number
|
||||
sideColor: number
|
||||
depth: number
|
||||
offsetZ: number
|
||||
}
|
||||
|
||||
const LOGO_VIEWBOX_CENTER = 96
|
||||
const LOGO_COORDINATE_SCALE = 1 / 80
|
||||
const AUTO_ROTATION_SPEED = 0.68
|
||||
const AUTO_ROTATION_RANGE = 0.24
|
||||
const MAX_TILT = 0.58
|
||||
|
||||
const LOGO_PIECES: readonly LogoPieceDefinition[] = [
|
||||
{
|
||||
points: [
|
||||
[96, 15],
|
||||
[24, 57],
|
||||
[24, 133],
|
||||
[48, 147],
|
||||
[48, 76],
|
||||
[96, 48],
|
||||
[120, 62],
|
||||
[120, 35],
|
||||
],
|
||||
faceColor: 0xa565ff,
|
||||
sideColor: 0xdde3ec,
|
||||
depth: 0.2,
|
||||
offsetZ: 0,
|
||||
},
|
||||
{
|
||||
points: [
|
||||
[144, 43],
|
||||
[168, 57],
|
||||
[168, 134],
|
||||
[96, 176],
|
||||
[72, 162],
|
||||
[72, 135],
|
||||
[96, 149],
|
||||
[144, 121],
|
||||
],
|
||||
faceColor: 0x7330dc,
|
||||
sideColor: 0xaeb7c5,
|
||||
depth: 0.22,
|
||||
offsetZ: 0.012,
|
||||
},
|
||||
{
|
||||
points: [
|
||||
[76, 64],
|
||||
[136, 96],
|
||||
[76, 128],
|
||||
],
|
||||
faceColor: 0xb879ff,
|
||||
sideColor: 0xe7ebf2,
|
||||
depth: 0.27,
|
||||
offsetZ: 0.075,
|
||||
},
|
||||
]
|
||||
|
||||
const rootRef = ref<HTMLDivElement | null>(null)
|
||||
const canvasRef = ref<HTMLCanvasElement | null>(null)
|
||||
const isReady = ref(false)
|
||||
const hasWebGLError = ref(false)
|
||||
const isDragging = ref(false)
|
||||
|
||||
let renderer: THREE.WebGLRenderer | null = null
|
||||
let scene: THREE.Scene | null = null
|
||||
let camera: THREE.PerspectiveCamera | null = null
|
||||
let logoGroup: THREE.Group | null = null
|
||||
let environmentTexture: THREE.Texture | null = null
|
||||
let resizeObserver: ResizeObserver | null = null
|
||||
let intersectionObserver: IntersectionObserver | null = null
|
||||
let reducedMotionQuery: MediaQueryList | null = null
|
||||
let animationFrameId = 0
|
||||
let previousFrameTime = 0
|
||||
let targetRotationX = -0.12
|
||||
let targetRotationY = -0.1
|
||||
let autoRotationPhase = -0.9
|
||||
let dragVelocityY = 0
|
||||
let lastPointerX = 0
|
||||
let lastPointerY = 0
|
||||
let isIntersecting = true
|
||||
let prefersReducedMotion = false
|
||||
|
||||
/** 将原 Logo 的二维坐标转换为以画布中心为原点的 Three.js 形状。 */
|
||||
function createLogoShape(points: readonly LogoPoint[]) {
|
||||
const shape = new THREE.Shape()
|
||||
|
||||
points.forEach(([sourceX, sourceY], index) => {
|
||||
const x = (sourceX - LOGO_VIEWBOX_CENTER) * LOGO_COORDINATE_SCALE
|
||||
const y = (LOGO_VIEWBOX_CENTER - sourceY) * LOGO_COORDINATE_SCALE
|
||||
if (index === 0) shape.moveTo(x, y)
|
||||
else shape.lineTo(x, y)
|
||||
})
|
||||
shape.closePath()
|
||||
|
||||
return shape
|
||||
}
|
||||
|
||||
/** 创建一段带银色倒角侧面的紫色金属 Logo 几何体。 */
|
||||
function createLogoPiece(definition: LogoPieceDefinition) {
|
||||
const geometry = new THREE.ExtrudeGeometry(createLogoShape(definition.points), {
|
||||
depth: definition.depth,
|
||||
steps: 1,
|
||||
bevelEnabled: true,
|
||||
bevelSegments: 5,
|
||||
bevelSize: 0.032,
|
||||
bevelThickness: 0.038,
|
||||
bevelOffset: -0.006,
|
||||
})
|
||||
geometry.translate(0, 0, definition.offsetZ - definition.depth / 2)
|
||||
geometry.computeVertexNormals()
|
||||
|
||||
const faceMaterial = new THREE.MeshPhysicalMaterial({
|
||||
color: definition.faceColor,
|
||||
metalness: 0.9,
|
||||
roughness: 0.16,
|
||||
clearcoat: 1,
|
||||
clearcoatRoughness: 0.1,
|
||||
envMapIntensity: 1.45,
|
||||
iridescence: 0.16,
|
||||
iridescenceIOR: 1.25,
|
||||
iridescenceThicknessRange: [120, 340],
|
||||
})
|
||||
const sideMaterial = new THREE.MeshPhysicalMaterial({
|
||||
color: definition.sideColor,
|
||||
metalness: 1,
|
||||
roughness: 0.2,
|
||||
clearcoat: 0.7,
|
||||
clearcoatRoughness: 0.14,
|
||||
envMapIntensity: 1.65,
|
||||
})
|
||||
|
||||
return new THREE.Mesh(geometry, [faceMaterial, sideMaterial])
|
||||
}
|
||||
|
||||
/** 组合 MoviePilot 外框与播放符号,形成完整立体 Logo。 */
|
||||
function createLogoModel() {
|
||||
const group = new THREE.Group()
|
||||
LOGO_PIECES.forEach(definition => group.add(createLogoPiece(definition)))
|
||||
group.rotation.set(
|
||||
targetRotationX,
|
||||
targetRotationY + Math.sin(autoRotationPhase) * AUTO_ROTATION_RANGE,
|
||||
0,
|
||||
)
|
||||
|
||||
return group
|
||||
}
|
||||
|
||||
/** 配置突出金属反射与倒角轮廓的环境贴图和三点布光。 */
|
||||
function configureLighting(activeRenderer: THREE.WebGLRenderer, activeScene: THREE.Scene) {
|
||||
const pmremGenerator = new THREE.PMREMGenerator(activeRenderer)
|
||||
const roomEnvironment = new RoomEnvironment()
|
||||
environmentTexture = pmremGenerator.fromScene(roomEnvironment, 0.035).texture
|
||||
activeScene.environment = environmentTexture
|
||||
roomEnvironment.dispose()
|
||||
pmremGenerator.dispose()
|
||||
|
||||
const keyLight = new THREE.DirectionalLight(0xffffff, 3.4)
|
||||
keyLight.position.set(-3.5, 4.5, 5)
|
||||
activeScene.add(keyLight)
|
||||
|
||||
const purpleFillLight = new THREE.DirectionalLight(0x8f5cff, 2.6)
|
||||
purpleFillLight.position.set(4, -2, 3)
|
||||
activeScene.add(purpleFillLight)
|
||||
|
||||
const rimLight = new THREE.DirectionalLight(0xdce8ff, 2.2)
|
||||
rimLight.position.set(2.5, 4, -3)
|
||||
activeScene.add(rimLight)
|
||||
|
||||
activeScene.add(new THREE.AmbientLight(0xcbd3df, 0.78))
|
||||
}
|
||||
|
||||
/** 按容器实际尺寸与设备像素比同步渲染器。 */
|
||||
function resizeRenderer() {
|
||||
if (!renderer || !camera || !rootRef.value) return
|
||||
|
||||
const { width, height } = rootRef.value.getBoundingClientRect()
|
||||
if (!width || !height) return
|
||||
|
||||
renderer.setPixelRatio(Math.min(window.devicePixelRatio || 1, 2))
|
||||
renderer.setSize(width, height, false)
|
||||
camera.aspect = width / height
|
||||
camera.updateProjectionMatrix()
|
||||
}
|
||||
|
||||
/** 逐帧更新 Logo 的轻微摆转、拖拽惯性和金属反光。 */
|
||||
function renderFrame(frameTime: number) {
|
||||
animationFrameId = window.requestAnimationFrame(renderFrame)
|
||||
if (!renderer || !scene || !camera || !logoGroup || !isIntersecting) return
|
||||
|
||||
const delta = previousFrameTime ? Math.min((frameTime - previousFrameTime) / 1000, 0.05) : 0
|
||||
previousFrameTime = frameTime
|
||||
|
||||
if (!isDragging.value && !prefersReducedMotion) {
|
||||
autoRotationPhase += AUTO_ROTATION_SPEED * delta
|
||||
targetRotationY += dragVelocityY * delta
|
||||
dragVelocityY *= Math.exp(-5.5 * delta)
|
||||
}
|
||||
|
||||
if (prefersReducedMotion) {
|
||||
logoGroup.rotation.set(targetRotationX, targetRotationY, 0)
|
||||
} else {
|
||||
const easing = 1 - Math.exp(-10 * delta)
|
||||
const autoRotationOffset = isDragging.value ? 0 : Math.sin(autoRotationPhase) * AUTO_ROTATION_RANGE
|
||||
logoGroup.rotation.x += (targetRotationX - logoGroup.rotation.x) * easing
|
||||
logoGroup.rotation.y += (targetRotationY + autoRotationOffset - logoGroup.rotation.y) * easing
|
||||
logoGroup.rotation.z = Math.sin(frameTime * 0.0004) * 0.018
|
||||
}
|
||||
|
||||
renderer.render(scene, camera)
|
||||
}
|
||||
|
||||
/** 初始化 Three.js 场景;不支持 WebGL 时切换到静态 Logo。 */
|
||||
function initializeScene() {
|
||||
const canvas = canvasRef.value
|
||||
if (!canvas) return
|
||||
|
||||
try {
|
||||
renderer = new THREE.WebGLRenderer({
|
||||
canvas,
|
||||
alpha: true,
|
||||
antialias: true,
|
||||
powerPreference: 'high-performance',
|
||||
})
|
||||
renderer.setClearColor(0x000000, 0)
|
||||
renderer.outputColorSpace = THREE.SRGBColorSpace
|
||||
renderer.toneMapping = THREE.ACESFilmicToneMapping
|
||||
renderer.toneMappingExposure = 1.08
|
||||
|
||||
scene = new THREE.Scene()
|
||||
camera = new THREE.PerspectiveCamera(25, 1, 0.1, 100)
|
||||
camera.position.set(0, 0, 5.7)
|
||||
configureLighting(renderer, scene)
|
||||
|
||||
logoGroup = createLogoModel()
|
||||
scene.add(logoGroup)
|
||||
resizeRenderer()
|
||||
isReady.value = true
|
||||
animationFrameId = window.requestAnimationFrame(renderFrame)
|
||||
} catch (error) {
|
||||
console.warn('无法初始化登录页 3D Logo,已回退到静态图标。', error)
|
||||
hasWebGLError.value = true
|
||||
isReady.value = false
|
||||
disposeScene()
|
||||
}
|
||||
}
|
||||
|
||||
/** 处理拖拽开始并捕获指针,保证触屏滑动连续。 */
|
||||
function handlePointerDown(event: PointerEvent) {
|
||||
if (!isReady.value) return
|
||||
isDragging.value = true
|
||||
dragVelocityY = 0
|
||||
lastPointerX = event.clientX
|
||||
lastPointerY = event.clientY
|
||||
rootRef.value?.setPointerCapture(event.pointerId)
|
||||
}
|
||||
|
||||
/** 根据指针位移更新 Logo 水平旋转与受限俯仰角。 */
|
||||
function handlePointerMove(event: PointerEvent) {
|
||||
if (!isDragging.value) return
|
||||
|
||||
const deltaX = event.clientX - lastPointerX
|
||||
const deltaY = event.clientY - lastPointerY
|
||||
targetRotationY += deltaX * 0.014
|
||||
targetRotationX = THREE.MathUtils.clamp(targetRotationX + deltaY * 0.01, -MAX_TILT, MAX_TILT)
|
||||
dragVelocityY = deltaX * 0.9
|
||||
lastPointerX = event.clientX
|
||||
lastPointerY = event.clientY
|
||||
}
|
||||
|
||||
/** 结束指针拖拽并释放捕获。 */
|
||||
function handlePointerUp(event: PointerEvent) {
|
||||
if (!isDragging.value) return
|
||||
isDragging.value = false
|
||||
if (rootRef.value?.hasPointerCapture(event.pointerId)) rootRef.value.releasePointerCapture(event.pointerId)
|
||||
}
|
||||
|
||||
/** 支持方向键旋转 Logo,提供无鼠标交互能力。 */
|
||||
function handleKeydown(event: KeyboardEvent) {
|
||||
const rotationStep = Math.PI / 10
|
||||
if (event.key === 'ArrowLeft') targetRotationY -= rotationStep
|
||||
else if (event.key === 'ArrowRight') targetRotationY += rotationStep
|
||||
else if (event.key === 'ArrowUp') targetRotationX = Math.max(targetRotationX - rotationStep / 2, -MAX_TILT)
|
||||
else if (event.key === 'ArrowDown') targetRotationX = Math.min(targetRotationX + rotationStep / 2, MAX_TILT)
|
||||
else return
|
||||
|
||||
event.preventDefault()
|
||||
}
|
||||
|
||||
/** 同步系统减少动态偏好,关闭自动摆转但保留手动交互。 */
|
||||
function handleReducedMotionChange(event?: MediaQueryListEvent) {
|
||||
prefersReducedMotion = event?.matches ?? reducedMotionQuery?.matches ?? false
|
||||
}
|
||||
|
||||
/** 仅在组件进入视口时持续渲染,降低后台 GPU 占用。 */
|
||||
function handleIntersection(entries: IntersectionObserverEntry[]) {
|
||||
isIntersecting = entries[0]?.isIntersecting ?? true
|
||||
if (isIntersecting) previousFrameTime = performance.now()
|
||||
}
|
||||
|
||||
/** WebGL 上下文丢失时停止渲染并显示静态回退 Logo。 */
|
||||
function handleContextLost(event: Event) {
|
||||
event.preventDefault()
|
||||
if (animationFrameId) window.cancelAnimationFrame(animationFrameId)
|
||||
animationFrameId = 0
|
||||
hasWebGLError.value = true
|
||||
isReady.value = false
|
||||
}
|
||||
|
||||
/** 释放指定 3D 对象树中的几何体、材质与贴图资源。 */
|
||||
function disposeObjectResources(root: THREE.Object3D) {
|
||||
const disposedGeometries = new Set<THREE.BufferGeometry>()
|
||||
const disposedMaterials = new Set<THREE.Material>()
|
||||
const disposedTextures = new Set<THREE.Texture>()
|
||||
|
||||
root.traverse(object => {
|
||||
if (!(object instanceof THREE.Mesh)) return
|
||||
|
||||
if (!disposedGeometries.has(object.geometry)) {
|
||||
object.geometry.dispose()
|
||||
disposedGeometries.add(object.geometry)
|
||||
}
|
||||
|
||||
const materials = Array.isArray(object.material) ? object.material : [object.material]
|
||||
materials.forEach(material => {
|
||||
if (disposedMaterials.has(material)) return
|
||||
const mappedMaterial = material as THREE.Material & { map?: THREE.Texture | null }
|
||||
if (mappedMaterial.map && !disposedTextures.has(mappedMaterial.map)) {
|
||||
mappedMaterial.map.dispose()
|
||||
disposedTextures.add(mappedMaterial.map)
|
||||
}
|
||||
material.dispose()
|
||||
disposedMaterials.add(material)
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
/** 释放场景中的几何体、材质、环境贴图和 WebGL 上下文。 */
|
||||
function disposeScene() {
|
||||
if (animationFrameId) window.cancelAnimationFrame(animationFrameId)
|
||||
animationFrameId = 0
|
||||
|
||||
if (scene) disposeObjectResources(scene)
|
||||
environmentTexture?.dispose()
|
||||
renderer?.dispose()
|
||||
scene = null
|
||||
camera = null
|
||||
logoGroup = null
|
||||
renderer = null
|
||||
environmentTexture = null
|
||||
}
|
||||
|
||||
/** 注册尺寸、可见性和动态偏好监听并启动场景。 */
|
||||
function handleMounted() {
|
||||
reducedMotionQuery = window.matchMedia('(prefers-reduced-motion: reduce)')
|
||||
handleReducedMotionChange()
|
||||
reducedMotionQuery.addEventListener('change', handleReducedMotionChange)
|
||||
|
||||
if (rootRef.value) {
|
||||
resizeObserver = new ResizeObserver(resizeRenderer)
|
||||
resizeObserver.observe(rootRef.value)
|
||||
intersectionObserver = new IntersectionObserver(handleIntersection, { threshold: 0.05 })
|
||||
intersectionObserver.observe(rootRef.value)
|
||||
}
|
||||
|
||||
canvasRef.value?.addEventListener('webglcontextlost', handleContextLost)
|
||||
initializeScene()
|
||||
}
|
||||
|
||||
/** 移除监听器并完整销毁 Three.js 场景。 */
|
||||
function handleBeforeUnmount() {
|
||||
resizeObserver?.disconnect()
|
||||
intersectionObserver?.disconnect()
|
||||
reducedMotionQuery?.removeEventListener('change', handleReducedMotionChange)
|
||||
canvasRef.value?.removeEventListener('webglcontextlost', handleContextLost)
|
||||
disposeScene()
|
||||
}
|
||||
|
||||
onMounted(handleMounted)
|
||||
onBeforeUnmount(handleBeforeUnmount)
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div
|
||||
ref="rootRef"
|
||||
class="metal-logo-3d"
|
||||
:class="{ 'metal-logo-3d--dragging': isDragging, 'metal-logo-3d--ready': isReady }"
|
||||
role="img"
|
||||
tabindex="0"
|
||||
aria-label="MoviePilot 3D metal logo"
|
||||
@keydown="handleKeydown"
|
||||
@pointerdown="handlePointerDown"
|
||||
@pointermove="handlePointerMove"
|
||||
@pointerup="handlePointerUp"
|
||||
@pointercancel="handlePointerUp"
|
||||
>
|
||||
<canvas ref="canvasRef" class="metal-logo-3d__canvas" aria-hidden="true" />
|
||||
<img v-if="hasWebGLError" :src="logoUrl" class="metal-logo-3d__fallback" alt="MoviePilot" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.metal-logo-3d {
|
||||
position: relative;
|
||||
display: block;
|
||||
overflow: visible;
|
||||
block-size: 96px;
|
||||
cursor: grab;
|
||||
inline-size: 96px;
|
||||
outline: none;
|
||||
touch-action: none;
|
||||
|
||||
&:focus-visible {
|
||||
border-radius: 8px;
|
||||
outline: 2px solid rgba(var(--v-theme-primary), 0.78);
|
||||
outline-offset: 4px;
|
||||
}
|
||||
}
|
||||
|
||||
.metal-logo-3d--dragging {
|
||||
cursor: grabbing;
|
||||
}
|
||||
|
||||
.metal-logo-3d__canvas,
|
||||
.metal-logo-3d__fallback {
|
||||
display: block;
|
||||
block-size: 100%;
|
||||
inline-size: 100%;
|
||||
}
|
||||
|
||||
.metal-logo-3d__canvas {
|
||||
filter: drop-shadow(0 11px 10px rgba(12, 10, 22, 34%));
|
||||
opacity: 0;
|
||||
transition: opacity 350ms ease;
|
||||
}
|
||||
|
||||
.metal-logo-3d--ready .metal-logo-3d__canvas {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
.metal-logo-3d__fallback {
|
||||
object-fit: contain;
|
||||
padding: 11px;
|
||||
}
|
||||
|
||||
@media (prefers-reduced-motion: reduce) {
|
||||
.metal-logo-3d__canvas {
|
||||
transition: none;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -4,7 +4,7 @@ import { useAuthStore, useUserStore } from '@/stores'
|
||||
import { authState, userState } from '@/stores/types'
|
||||
import api from '@/api'
|
||||
import router from '@/router'
|
||||
import logo from '@images/logo.png'
|
||||
import MetalLogo3D from '@/components/misc/MetalLogo3D.vue'
|
||||
import { bufferToBase64Url, base64UrlToUint8Array, urlBase64ToUint8Array } from '@/@core/utils/navigator'
|
||||
import { SUPPORTED_LOCALES, SupportedLocale } from '@/types/i18n'
|
||||
import { getCurrentLocale, setI18nLanguage } from '@/plugins/i18n'
|
||||
@@ -733,7 +733,7 @@ onUnmounted(() => {
|
||||
<!-- 卡片头部:Logo + 标题 + 欢迎语 -->
|
||||
<div class="login-head">
|
||||
<div class="login-logo-wrapper">
|
||||
<VImg :src="logo" width="72" height="72" class="login-logo" />
|
||||
<MetalLogo3D class="login-logo" />
|
||||
</div>
|
||||
<h1 class="login-title">MoviePilot</h1>
|
||||
<p class="login-subtitle">{{ t('login.welcomeBack') || 'Welcome Back' }}</p>
|
||||
@@ -1072,47 +1072,10 @@ onUnmounted(() => {
|
||||
justify-content: center;
|
||||
animation: logo-enter 700ms cubic-bezier(0.16, 1, 0.3, 1) 100ms both;
|
||||
margin-block-end: 8px;
|
||||
|
||||
/* Logo 背后的柔光环 */
|
||||
&::before {
|
||||
position: absolute;
|
||||
z-index: -1;
|
||||
border-radius: 50%;
|
||||
animation: logo-pulse 4s ease-in-out infinite;
|
||||
background: radial-gradient(circle, rgba(var(--v-theme-primary), 0.2) 0%, transparent 70%);
|
||||
block-size: 120px;
|
||||
content: '';
|
||||
inline-size: 120px;
|
||||
}
|
||||
}
|
||||
|
||||
.login-logo {
|
||||
animation: logo-float 6s ease-in-out infinite;
|
||||
filter: drop-shadow(0 8px 20px rgba(var(--v-theme-primary), 0.3));
|
||||
}
|
||||
|
||||
@keyframes logo-float {
|
||||
0%,
|
||||
100% {
|
||||
transform: translateY(0);
|
||||
}
|
||||
|
||||
50% {
|
||||
transform: translateY(-4px);
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes logo-pulse {
|
||||
0%,
|
||||
100% {
|
||||
opacity: 0.6;
|
||||
transform: scale(1);
|
||||
}
|
||||
|
||||
50% {
|
||||
opacity: 1;
|
||||
transform: scale(1.05);
|
||||
}
|
||||
flex: none;
|
||||
}
|
||||
|
||||
.login-title {
|
||||
@@ -1489,17 +1452,9 @@ onUnmounted(() => {
|
||||
display: none !important;
|
||||
}
|
||||
|
||||
.login-logo {
|
||||
animation: none !important;
|
||||
}
|
||||
|
||||
.login-orb {
|
||||
animation: none !important;
|
||||
}
|
||||
|
||||
.login-logo-wrapper::before {
|
||||
animation: none !important;
|
||||
}
|
||||
}
|
||||
|
||||
/* ===================== 小屏适配 ===================== */
|
||||
|
||||
47
yarn.lock
47
yarn.lock
@@ -933,6 +933,11 @@
|
||||
resolved "https://registry.npmjs.org/@csstools/selector-specificity/-/selector-specificity-5.0.0.tgz"
|
||||
integrity sha512-PCqQV3c4CoVm3kdPhyeZ07VmBRdH2EpMFA/pd9OASpOEC3aXNGoqPDAZ80D0cLpMBxnmk0+yNhGsEx31hq7Gtw==
|
||||
|
||||
"@dimforge/rapier3d-compat@~0.12.0":
|
||||
version "0.12.0"
|
||||
resolved "https://registry.yarnpkg.com/@dimforge/rapier3d-compat/-/rapier3d-compat-0.12.0.tgz#7b3365e1dfdc5cd957b45afe920b4ac06c7cd389"
|
||||
integrity sha512-uekIGetywIgopfD97oDL5PfeezkFpNhwlzlaEYNOA0N6ghdsOvh/HYjSMek5Q2O1PYvRSDFcqFVJl4r4ZBwOow==
|
||||
|
||||
"@dual-bundle/import-meta-resolve@^4.1.0":
|
||||
version "4.1.0"
|
||||
resolved "https://registry.npmjs.org/@dual-bundle/import-meta-resolve/-/import-meta-resolve-4.1.0.tgz"
|
||||
@@ -2160,6 +2165,11 @@
|
||||
resolved "https://registry.npmjs.org/@trysound/sax/-/sax-0.2.0.tgz"
|
||||
integrity sha512-L7z9BgrNEcYyUYtF+HaEfiS5ebkh9jXqbszz7pC0hRBPaatV0XjSD3+eHrpqFemQfgwiFF0QPIarnIihIDn7OA==
|
||||
|
||||
"@tweenjs/tween.js@~23.1.3":
|
||||
version "23.1.3"
|
||||
resolved "https://registry.yarnpkg.com/@tweenjs/tween.js/-/tween.js-23.1.3.tgz#eff0245735c04a928bb19c026b58c2a56460539d"
|
||||
integrity sha512-vJmvvwFxYuGnF2axRtPYocag6Clbb5YS7kLL+SO/TeVFzHqDIWrNKYtcsPMibjDx9O+bu+psAy9NKfWklassUA==
|
||||
|
||||
"@tybys/wasm-util@^0.10.0":
|
||||
version "0.10.1"
|
||||
resolved "https://registry.yarnpkg.com/@tybys/wasm-util/-/wasm-util-0.10.1.tgz#ecddd3205cf1e2d5274649ff0eedd2991ed7f414"
|
||||
@@ -2322,6 +2332,11 @@
|
||||
dependencies:
|
||||
"@types/node" "*"
|
||||
|
||||
"@types/stats.js@*":
|
||||
version "0.17.4"
|
||||
resolved "https://registry.yarnpkg.com/@types/stats.js/-/stats.js-0.17.4.tgz#1933e5ff153a23c7664487833198d685c22e791e"
|
||||
integrity sha512-jIBvWWShCvlBqBNIZt0KAshWpvSjhkwkEu4ZUcASoAvhmrgAUI2t1dXrjSL4xXVLB4FznPrIsX3nKXFl/Dt4vA==
|
||||
|
||||
"@types/statuses@^2.0.6":
|
||||
version "2.0.6"
|
||||
resolved "https://registry.yarnpkg.com/@types/statuses/-/statuses-2.0.6.tgz#66748315cc9a96d63403baa8671b2c124f8633aa"
|
||||
@@ -2335,6 +2350,18 @@
|
||||
"@types/node" "*"
|
||||
minipass "^4.0.0"
|
||||
|
||||
"@types/three@^0.185.1":
|
||||
version "0.185.1"
|
||||
resolved "https://registry.yarnpkg.com/@types/three/-/three-0.185.1.tgz#cc0e604e56f2c625deabb3984fff932c8d4f39f4"
|
||||
integrity sha512-db1xTb+EgYF2didW+eudSvVPtn75zo+fGsY8ShQrJY/B5ZBmC2Fiaykv3aImHAlCNEGuMPkPGXBJGLwzu5mC7A==
|
||||
dependencies:
|
||||
"@dimforge/rapier3d-compat" "~0.12.0"
|
||||
"@tweenjs/tween.js" "~23.1.3"
|
||||
"@types/stats.js" "*"
|
||||
"@types/webxr" ">=0.5.17"
|
||||
fflate "~0.8.2"
|
||||
meshoptimizer "~1.1.1"
|
||||
|
||||
"@types/trusted-types@^2.0.2":
|
||||
version "2.0.7"
|
||||
resolved "https://registry.npmjs.org/@types/trusted-types/-/trusted-types-2.0.7.tgz"
|
||||
@@ -2355,6 +2382,11 @@
|
||||
resolved "https://registry.npmjs.org/@types/webfontloader/-/webfontloader-1.6.38.tgz"
|
||||
integrity sha512-kUaF72Fv202suFx6yBrwXqeVRMx7hGtJTesyESZgn9sEPCUeDXm2p0SiyS1MTqW74nQP4p7JyrOCwZ7pNFns4w==
|
||||
|
||||
"@types/webxr@>=0.5.17":
|
||||
version "0.5.24"
|
||||
resolved "https://registry.yarnpkg.com/@types/webxr/-/webxr-0.5.24.tgz#734d5d90dadc5809a53e422726c60337fa2f4a44"
|
||||
integrity sha512-h8fgEd/DpoS9CBrjEQXR+dIDraopAEfu4wYVNY2tEPwk60stPWhvZMf4Foo5FakuQ7HFZoa8WceaWFervK2Ovg==
|
||||
|
||||
"@types/yauzl@^2.9.1":
|
||||
version "2.10.3"
|
||||
resolved "https://registry.npmjs.org/@types/yauzl/-/yauzl-2.10.3.tgz"
|
||||
@@ -4928,6 +4960,11 @@ fdir@^6.5.0:
|
||||
resolved "https://registry.yarnpkg.com/fdir/-/fdir-6.5.0.tgz#ed2ab967a331ade62f18d077dae192684d50d350"
|
||||
integrity sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==
|
||||
|
||||
fflate@~0.8.2:
|
||||
version "0.8.3"
|
||||
resolved "https://registry.yarnpkg.com/fflate/-/fflate-0.8.3.tgz#bc27d8eb30343d4d512abb03480202ce65d825fc"
|
||||
integrity sha512-tbZNuJrLwGUp3zshBtdy4W+ORxZuIh8a5ilyIEQDC5rY1f3U20JMry0Ll3WBzU58EZKsEuJFXhb5gwv8CsPvgA==
|
||||
|
||||
file-entry-cache@^10.0.8:
|
||||
version "10.0.8"
|
||||
resolved "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-10.0.8.tgz"
|
||||
@@ -6341,6 +6378,11 @@ merge2@^1.3.0, merge2@^1.4.1:
|
||||
resolved "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz"
|
||||
integrity sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==
|
||||
|
||||
meshoptimizer@~1.1.1:
|
||||
version "1.1.1"
|
||||
resolved "https://registry.yarnpkg.com/meshoptimizer/-/meshoptimizer-1.1.1.tgz#20c7d050d59bdc573154814f6a37d47d89908cca"
|
||||
integrity sha512-oRFNWJRDA/WTrVj7NWvqa5HqE1t9MYDj2VaWirQCzCCrAd2GHrqR/sQezCxiWATPNlKTcRaPRHPJwIRoPBAp5g==
|
||||
|
||||
methods@~1.1.2:
|
||||
version "1.1.2"
|
||||
resolved "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz"
|
||||
@@ -8355,6 +8397,11 @@ thenify-all@^1.0.0:
|
||||
dependencies:
|
||||
any-promise "^1.0.0"
|
||||
|
||||
three@^0.185.1:
|
||||
version "0.185.1"
|
||||
resolved "https://registry.yarnpkg.com/three/-/three-0.185.1.tgz#63e9e241a17b101e211965121a017b4b4d8054ae"
|
||||
integrity sha512-5aojFCXKwnjBRZvUnt3WFfEcvUJgkN5LlijRFN95hMy8WVkG4I0QNcJE+OuWvuJ0bOdStrbfXn0pkd6/QyiAlg==
|
||||
|
||||
tinybench@^2.9.0:
|
||||
version "2.9.0"
|
||||
resolved "https://registry.yarnpkg.com/tinybench/-/tinybench-2.9.0.tgz#103c9f8ba6d7237a47ab6dd1dcff77251863426b"
|
||||
|
||||
Reference in New Issue
Block a user