222
|
|
@ -13,6 +13,7 @@
|
|||
"dependencies": {
|
||||
"@element-plus/icons-vue": "^2.3.2",
|
||||
"@google/genai": "^1.27.0",
|
||||
"@splinetool/runtime": "^1.12.6",
|
||||
"@stripe/stripe-js": "^4.8.0",
|
||||
"@twind/core": "^1.1.3",
|
||||
"@twind/preset-autoprefix": "^1.0.7",
|
||||
|
|
@ -20,11 +21,13 @@
|
|||
"@types/three": "^0.180.0",
|
||||
"@vuelidate/core": "^2.0.3",
|
||||
"@vuelidate/validators": "^2.0.4",
|
||||
"@vueuse/core": "^14.1.0",
|
||||
"axios": "^1.13.2",
|
||||
"country-state-city": "^3.2.1",
|
||||
"dayjs": "^1.11.13",
|
||||
"element-plus": "^2.11.7",
|
||||
"jose": "^6.1.1",
|
||||
"motion-v": "^1.7.4",
|
||||
"normalize.css": "^8.0.1",
|
||||
"nprogress": "^0.2.0",
|
||||
"pinia": "^3.0.4",
|
||||
|
|
@ -40,11 +43,16 @@
|
|||
},
|
||||
"devDependencies": {
|
||||
"@iconify-json/feather": "^1.2.1",
|
||||
"@inspira-ui/plugins": "^0.0.1",
|
||||
"@tailwindcss/postcss": "^4.1.17",
|
||||
"@vitejs/plugin-vue": "^6.0.1",
|
||||
"autoprefixer": "^10.4.22",
|
||||
"class-variance-authority": "^0.7.1",
|
||||
"clsx": "^2.1.1",
|
||||
"postcss": "^8.5.6",
|
||||
"tailwind-merge": "^3.4.0",
|
||||
"tailwindcss": "^4.1.17",
|
||||
"tailwindcss-animate": "^1.0.7",
|
||||
"terser": "^5.44.1",
|
||||
"unplugin-auto-import": "^20.2.0",
|
||||
"unplugin-icons": "^22.5.0",
|
||||
|
|
|
|||
|
|
@ -58,10 +58,10 @@ onMounted(() => {
|
|||
'fullscreen-mode': isFullScreenPage,
|
||||
'homepage-mode': isHomePage
|
||||
}">
|
||||
<div class="sidebar-overlay" :class="{ 'sidebar-overlay-active': qmLoading }"></div>
|
||||
<div v-if="qmLoading" class="sidebar-overlay" :class="{ 'sidebar-overlay-active': qmLoading }"></div>
|
||||
<!-- 登录页面全屏显示 -->
|
||||
<main style="position: relative;" v-if="isLoginPage">
|
||||
<div class="sidebar-overlay" :class="{ 'sidebar-overlay-active': loading }"></div>
|
||||
<div v-if="loading" class="sidebar-overlay" :class="{ 'sidebar-overlay-active': loading }"></div>
|
||||
<router-view />
|
||||
</main>
|
||||
<!-- 全屏页面(如创建项目) -->
|
||||
|
|
|
|||
|
Before Width: | Height: | Size: 1.4 MiB After Width: | Height: | Size: 759 KiB |
|
Before Width: | Height: | Size: 1.1 MiB After Width: | Height: | Size: 246 KiB |
|
Before Width: | Height: | Size: 1.4 MiB After Width: | Height: | Size: 320 KiB |
|
Before Width: | Height: | Size: 2.1 MiB After Width: | Height: | Size: 475 KiB |
|
Before Width: | Height: | Size: 1.1 MiB After Width: | Height: | Size: 291 KiB |
|
Before Width: | Height: | Size: 1.6 MiB After Width: | Height: | Size: 343 KiB |
|
Before Width: | Height: | Size: 1.2 MiB After Width: | Height: | Size: 361 KiB |
|
After Width: | Height: | Size: 443 KiB |
|
After Width: | Height: | Size: 413 KiB |
|
After Width: | Height: | Size: 462 KiB |
|
After Width: | Height: | Size: 462 KiB |
|
After Width: | Height: | Size: 436 KiB |
|
After Width: | Height: | Size: 455 KiB |
|
After Width: | Height: | Size: 400 KiB |
|
After Width: | Height: | Size: 391 KiB |
|
After Width: | Height: | Size: 390 KiB |
|
After Width: | Height: | Size: 444 KiB |
|
Before Width: | Height: | Size: 28 KiB After Width: | Height: | Size: 27 KiB |
|
Before Width: | Height: | Size: 5.2 MiB |
|
Before Width: | Height: | Size: 5.4 MiB |
|
Before Width: | Height: | Size: 5.5 MiB |
|
Before Width: | Height: | Size: 8.0 MiB |
|
Before Width: | Height: | Size: 12 MiB |
|
Before Width: | Height: | Size: 346 KiB After Width: | Height: | Size: 49 KiB |
|
Before Width: | Height: | Size: 1.4 MiB After Width: | Height: | Size: 268 KiB |
|
Before Width: | Height: | Size: 1.5 MiB After Width: | Height: | Size: 389 KiB |
|
Before Width: | Height: | Size: 1.5 MiB After Width: | Height: | Size: 342 KiB |
|
Before Width: | Height: | Size: 1.4 MiB After Width: | Height: | Size: 347 KiB |
|
Before Width: | Height: | Size: 1.5 MiB After Width: | Height: | Size: 371 KiB |
|
|
@ -1,24 +1,67 @@
|
|||
/* Tailwind CSS 基础样式 */
|
||||
@import "tailwindcss";
|
||||
|
||||
/* 自定义基础样式 */
|
||||
@tailwind base;
|
||||
@tailwind components;
|
||||
@tailwind utilities;
|
||||
@layer base {
|
||||
/* 设置默认字体 */
|
||||
html {
|
||||
font-family: 'Inter', system-ui, sans-serif;
|
||||
:root {
|
||||
--background: 0 0% 100%;
|
||||
--foreground: 222.2 84% 4.9%;
|
||||
|
||||
--card: 0 0% 100%;
|
||||
--card-foreground: 222.2 84% 4.9%;
|
||||
|
||||
--popover: 0 0% 100%;
|
||||
--popover-foreground: 222.2 84% 4.9%;
|
||||
|
||||
--primary: 221.2 83.2% 53.3%;
|
||||
--primary-foreground: 210 40% 98%;
|
||||
|
||||
--secondary: 210 40% 96.1%;
|
||||
--secondary-foreground: 222.2 47.4% 11.2%;
|
||||
|
||||
--muted: 210 40% 96.1%;
|
||||
--muted-foreground: 215.4 16.3% 46.9%;
|
||||
|
||||
--accent: 210 40% 96.1%;
|
||||
--accent-foreground: 222.2 47.4% 11.2%;
|
||||
|
||||
--destructive: 0 84.2% 60.2%;
|
||||
--destructive-foreground: 210 40% 98%;
|
||||
|
||||
--border: 214.3 31.8% 91.4%;
|
||||
--input: 214.3 31.8% 91.4%;
|
||||
--ring: 221.2 83.2% 53.3%;
|
||||
--radius: 0.5rem;
|
||||
}
|
||||
|
||||
/* 设置基础文本颜色 */
|
||||
body {
|
||||
@apply text-gray-800 bg-gray-50;
|
||||
|
||||
.dark {
|
||||
--background: 222.2 84% 4.9%;
|
||||
--foreground: 210 40% 98%;
|
||||
|
||||
--card: 222.2 84% 4.9%;
|
||||
--card-foreground: 210 40% 98%;
|
||||
|
||||
--popover: 222.2 84% 4.9%;
|
||||
--popover-foreground: 210 40% 98%;
|
||||
|
||||
--primary: 217.2 91.2% 59.8%;
|
||||
--primary-foreground: 222.2 47.4% 11.2%;
|
||||
|
||||
--secondary: 217.2 32.6% 17.5%;
|
||||
--secondary-foreground: 210 40% 98%;
|
||||
|
||||
--muted: 217.2 32.6% 17.5%;
|
||||
--muted-foreground: 215 20.2% 65.1%;
|
||||
|
||||
--accent: 217.2 32.6% 17.5%;
|
||||
--accent-foreground: 210 40% 98%;
|
||||
|
||||
--destructive: 0 62.8% 30.6%;
|
||||
--destructive-foreground: 210 40% 98%;
|
||||
|
||||
--border: 217.2 32.6% 17.5%;
|
||||
--input: 217.2 32.6% 17.5%;
|
||||
--ring: 224.3 76.3% 48%;
|
||||
}
|
||||
|
||||
/* 暗黑模式基础样式 */
|
||||
html.dark {
|
||||
@apply bg-gray-900;
|
||||
}
|
||||
|
||||
html.dark body {
|
||||
@apply text-gray-200 bg-gray-900;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -21,12 +21,13 @@
|
|||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div style="height: 10px;width: 100%"></div>
|
||||
<el-scrollbar height="83vh" @end-reached="loadMore">
|
||||
<div class="agents-section">
|
||||
<!-- 智能体列表 -->
|
||||
<div class="agents-grid">
|
||||
<!-- 占位智能体卡片 -->
|
||||
<div class="agent-card-placeholder">
|
||||
<div class="agent-card-placeholder" v-for="(item,index) in 10" :key="index">
|
||||
<div class="agent-card-content">
|
||||
<div class="agent-avatar-placeholder">
|
||||
<el-icon size="48"><Avatar /></el-icon>
|
||||
|
|
@ -150,6 +151,7 @@
|
|||
</template>
|
||||
</el-dialog>
|
||||
</div>
|
||||
</el-scrollbar>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,88 @@
|
|||
<!-- ParentSize.vue -->
|
||||
<template>
|
||||
<div
|
||||
ref="target"
|
||||
:style="mergedStyles"
|
||||
v-bind="attrsWithoutClassAndStyle"
|
||||
>
|
||||
<slot />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, reactive, computed, useAttrs } from "vue";
|
||||
import { useDebounceFn, useResizeObserver } from "@vueuse/core";
|
||||
|
||||
const props = defineProps({
|
||||
class: String,
|
||||
debounceTime: {
|
||||
type: Number,
|
||||
default: 300,
|
||||
},
|
||||
ignoreDimensions: {
|
||||
type: [Array, String],
|
||||
default: () => [],
|
||||
},
|
||||
parentSizeStyles: Object,
|
||||
enableDebounceLeadingCall: {
|
||||
type: Boolean,
|
||||
default: true,
|
||||
},
|
||||
});
|
||||
|
||||
const attrs = useAttrs();
|
||||
const target = ref(null);
|
||||
const state = reactive({
|
||||
width: 0,
|
||||
height: 0,
|
||||
top: 0,
|
||||
left: 0,
|
||||
});
|
||||
|
||||
const mergedStyles = computed(() => ({
|
||||
...props.parentSizeStyles,
|
||||
...attrs.style,
|
||||
}));
|
||||
|
||||
const mergedClass = computed(() => ["w-full h-full", props.class]);
|
||||
|
||||
const attrsWithoutClassAndStyle = computed(() => {
|
||||
const { class: _, style: __, ...rest } = attrs;
|
||||
return rest;
|
||||
});
|
||||
|
||||
const normalizedIgnore = computed(() =>
|
||||
Array.isArray(props.ignoreDimensions) ? props.ignoreDimensions : [props.ignoreDimensions],
|
||||
);
|
||||
|
||||
function updateDimensions(rect) {
|
||||
const { width, height, top, left } = rect;
|
||||
const newState = { width, height, top, left };
|
||||
|
||||
const hasChange = Object.keys(newState).some(
|
||||
(key) => state[key] !== newState[key],
|
||||
);
|
||||
|
||||
if (!hasChange) return;
|
||||
|
||||
const shouldUpdate = !Object.keys(newState).every((key) =>
|
||||
normalizedIgnore.value.includes(key),
|
||||
);
|
||||
|
||||
if (shouldUpdate) {
|
||||
Object.assign(state, newState);
|
||||
}
|
||||
}
|
||||
|
||||
const debouncedUpdate = useDebounceFn(updateDimensions, props.debounceTime);
|
||||
|
||||
useResizeObserver(target, (entries) => {
|
||||
const entry = entries[0];
|
||||
if (entry) debouncedUpdate(entry.contentRect);
|
||||
});
|
||||
</script>
|
||||
<style>
|
||||
.a{
|
||||
position: relative;
|
||||
}
|
||||
</style>
|
||||
|
|
@ -0,0 +1,257 @@
|
|||
<template>
|
||||
<div class="page-container">
|
||||
<!-- 1. 新增:星空 Canvas 背景 -->
|
||||
<canvas ref="starCanvas" class="star-bg"></canvas>
|
||||
<!-- 原有内容包裹层 (确保内容在背景之上) -->
|
||||
<div class="content-wrapper">
|
||||
<slot></slot>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, onMounted, onUnmounted } from 'vue';
|
||||
|
||||
// 模拟原有数据
|
||||
const links = [{ name: 'Home' }, { name: 'Showcase' }, { name: 'Docs' }];
|
||||
|
||||
// --- 星空背景逻辑开始 ---
|
||||
const starCanvas = ref(null);
|
||||
let ctx = null;
|
||||
let animationFrameId = null;
|
||||
let stars = [];
|
||||
|
||||
// 配置参数
|
||||
const config = {
|
||||
starCount: 150, // 星星数量
|
||||
speedBase: 0.2, // 基础流速
|
||||
starColor: 'rgba(255, 255, 255, 0.8)',
|
||||
};
|
||||
|
||||
class Star {
|
||||
constructor(width, height) {
|
||||
this.width = width;
|
||||
this.height = height;
|
||||
this.init(true);
|
||||
}
|
||||
|
||||
init(randomY = false) {
|
||||
this.x = Math.random() * this.width;
|
||||
// 如果是初始化,Y轴随机;如果是重置,从底部出现
|
||||
this.y = randomY ? Math.random() * this.height : this.height;
|
||||
this.size = Math.random() * 2; // 星星大小
|
||||
this.speed = Math.random() * 0.5 + config.speedBase; // 移动速度
|
||||
this.opacity = Math.random();
|
||||
this.fadeDir = Math.random() > 0.5 ? 0.01 : -0.01; // 闪烁方向
|
||||
}
|
||||
|
||||
update() {
|
||||
// 向上流动效果 (y 减小)
|
||||
this.y -= this.speed;
|
||||
|
||||
// 闪烁效果
|
||||
this.opacity += this.fadeDir;
|
||||
if (this.opacity >= 1 || this.opacity <= 0.2) {
|
||||
this.fadeDir = -this.fadeDir;
|
||||
}
|
||||
|
||||
// 边界检查:如果飞出顶部,重置到底部
|
||||
if (this.y < 0) {
|
||||
this.init(false);
|
||||
}
|
||||
}
|
||||
|
||||
draw(context) {
|
||||
context.beginPath();
|
||||
context.arc(this.x, this.y, this.size, 0, Math.PI * 2);
|
||||
context.fillStyle = `rgba(255, 255, 255, ${this.opacity})`;
|
||||
context.fill();
|
||||
}
|
||||
}
|
||||
|
||||
const initStars = () => {
|
||||
const canvas = starCanvas.value;
|
||||
if (!canvas) return;
|
||||
|
||||
ctx = canvas.getContext('2d');
|
||||
|
||||
const resize = () => {
|
||||
canvas.width = window.innerWidth;
|
||||
canvas.height = window.innerHeight;
|
||||
// 重置星星
|
||||
stars = [];
|
||||
for (let i = 0; i < config.starCount; i++) {
|
||||
stars.push(new Star(canvas.width, canvas.height));
|
||||
}
|
||||
};
|
||||
|
||||
window.addEventListener('resize', resize);
|
||||
resize(); // 初始化尺寸
|
||||
|
||||
const animate = () => {
|
||||
ctx.clearRect(0, 0, canvas.width, canvas.height);
|
||||
|
||||
stars.forEach(star => {
|
||||
star.update();
|
||||
star.draw(ctx);
|
||||
});
|
||||
|
||||
animationFrameId = requestAnimationFrame(animate);
|
||||
};
|
||||
|
||||
animate();
|
||||
};
|
||||
|
||||
onMounted(() => {
|
||||
initStars();
|
||||
});
|
||||
|
||||
onUnmounted(() => {
|
||||
cancelAnimationFrame(animationFrameId);
|
||||
window.removeEventListener('resize', () => {});
|
||||
});
|
||||
// --- 星空背景逻辑结束 ---
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
/* 1. 核心背景样式 */
|
||||
.page-container {
|
||||
position: relative;
|
||||
min-height: 100vh;
|
||||
/* 深空渐变背景 */
|
||||
background: radial-gradient(ellipse at bottom, #1b2735 0%, #090a0f 100%);
|
||||
color: #ffffff; /* 确保文字在深色背景上可见 */
|
||||
}
|
||||
|
||||
.star-bg {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
z-index: 0; /* 放在最底层 */
|
||||
pointer-events: none; /* 让鼠标事件穿透 Canvas */
|
||||
}
|
||||
|
||||
/* 2. 内容层样式 (确保内容浮在星空上) */
|
||||
.content-wrapper {
|
||||
position: relative;
|
||||
z-index: 1; /* 放在 Canvas 之上 */
|
||||
}
|
||||
|
||||
/* --- 以下是页面基础排版样式,按需调整 --- */
|
||||
.navbar {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
padding: 20px 40px;
|
||||
}
|
||||
.nav-links a {
|
||||
color: #ccc;
|
||||
margin: 0 15px;
|
||||
text-decoration: none;
|
||||
}
|
||||
.nav-links a:hover { color: #fff; }
|
||||
|
||||
.hero {
|
||||
text-align: center;
|
||||
padding: 80px 20px;
|
||||
}
|
||||
.hero h1 {
|
||||
font-size: 3.5rem;
|
||||
margin-bottom: 10px;
|
||||
background: linear-gradient(to right, #fff, #a5b4fc);
|
||||
-webkit-background-clip: text;
|
||||
-webkit-text-fill-color: transparent;
|
||||
}
|
||||
.subtitle {
|
||||
font-size: 1.2rem;
|
||||
color: #94a3b8;
|
||||
margin-bottom: 30px;
|
||||
}
|
||||
|
||||
.section {
|
||||
max-width: 1000px;
|
||||
margin: 60px auto;
|
||||
text-align: center;
|
||||
background: rgba(255, 255, 255, 0.03); /* 轻微的磨砂玻璃背景 */
|
||||
backdrop-filter: blur(10px);
|
||||
padding: 40px;
|
||||
border-radius: 16px;
|
||||
border: 1px solid rgba(255, 255, 255, 0.1);
|
||||
}
|
||||
|
||||
.btn-primary {
|
||||
background: #6366f1;
|
||||
color: white;
|
||||
border: none;
|
||||
padding: 10px 24px;
|
||||
border-radius: 30px;
|
||||
cursor: pointer;
|
||||
font-weight: bold;
|
||||
transition: transform 0.2s;
|
||||
}
|
||||
.btn-primary:hover { background: #4f46e5; transform: scale(1.05); }
|
||||
|
||||
.btn-outline {
|
||||
background: transparent;
|
||||
color: white;
|
||||
border: 1px solid rgba(255,255,255,0.3);
|
||||
padding: 10px 24px;
|
||||
border-radius: 30px;
|
||||
margin-right: 10px;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
/* 流程图样式 */
|
||||
.demo-flow {
|
||||
display: flex;
|
||||
justify-content: space-around;
|
||||
align-items: center;
|
||||
margin-top: 30px;
|
||||
flex-wrap: wrap;
|
||||
gap: 10px;
|
||||
}
|
||||
.flow-item {
|
||||
background: rgba(0,0,0,0.3);
|
||||
padding: 10px;
|
||||
border-radius: 8px;
|
||||
font-size: 0.9rem;
|
||||
border: 1px solid #333;
|
||||
}
|
||||
.flow-item.active {
|
||||
border-color: #6366f1;
|
||||
box-shadow: 0 0 15px rgba(99, 102, 241, 0.3);
|
||||
}
|
||||
|
||||
/* 卡片网格 */
|
||||
.card-grid {
|
||||
display: flex;
|
||||
gap: 20px;
|
||||
justify-content: center;
|
||||
margin-top: 30px;
|
||||
}
|
||||
.card {
|
||||
width: 200px;
|
||||
background: rgba(255,255,255,0.05);
|
||||
border-radius: 12px;
|
||||
overflow: hidden;
|
||||
}
|
||||
.card-img-placeholder {
|
||||
height: 120px;
|
||||
background: #2d3748;
|
||||
}
|
||||
.card-info {
|
||||
padding: 10px;
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
.footer {
|
||||
margin-top: 100px;
|
||||
border-top: 1px solid rgba(255,255,255,0.1);
|
||||
padding: 40px;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
color: #64748b;
|
||||
}
|
||||
</style>
|
||||
|
|
@ -1,5 +1,5 @@
|
|||
<template>
|
||||
<div class="page-container">
|
||||
<div class="page-container" >
|
||||
<!-- 1. 新增:星空 Canvas 背景 -->
|
||||
<canvas ref="starCanvas" class="star-bg"></canvas>
|
||||
<!-- 原有内容包裹层 (确保内容在背景之上) -->
|
||||
|
|
@ -10,6 +10,7 @@
|
|||
</template>
|
||||
|
||||
<script setup>
|
||||
import spline from './spline.vue';
|
||||
import { ref, onMounted, onUnmounted } from 'vue';
|
||||
|
||||
// 模拟原有数据
|
||||
|
|
@ -116,7 +117,6 @@ onUnmounted(() => {
|
|||
<style scoped>
|
||||
/* 1. 核心背景样式 */
|
||||
.page-container {
|
||||
position: relative;
|
||||
min-height: 100vh;
|
||||
/* 深空渐变背景 */
|
||||
background: radial-gradient(ellipse at bottom, #1b2735 0%, #090a0f 100%);
|
||||
|
|
|
|||
|
|
@ -1,10 +1,15 @@
|
|||
<template>
|
||||
<Bg>
|
||||
<div class=" min-h-screen flex flex-col w-full selection:bg-purple-500 selection:text-white">
|
||||
<!-- Navbar -->
|
||||
<!-- <div :style="getResponsiveWidthStyle()">
|
||||
<div style="position: sticky;top: 0;">
|
||||
<spline :scene="'https://prod.spline.design/kZDDjO5HuC9GJUM2/scene.splinecode'"/>
|
||||
</div>
|
||||
</div> -->
|
||||
<div style="position: relative;" class="min-h-screen flex flex-col w-full selection:bg-purple-500 selection:text-white">
|
||||
<!-- Navbar -->
|
||||
<header
|
||||
:class="[
|
||||
'fixed top-0 left-0 right-0 z-50 transition-all duration-300',
|
||||
'fixed top-0 left-0 right-0 z-900 transition-all duration-300',
|
||||
isScrolled ? 'bg-black/90 backdrop-blur-md py-4' : 'bg-transparent py-6'
|
||||
]"
|
||||
>
|
||||
|
|
@ -93,46 +98,79 @@
|
|||
|
||||
<!-- Layer 1: Background Animation (Grid) -->
|
||||
<div class="absolute inset-0 flex items-center justify-center overflow-hidden">
|
||||
|
||||
<div
|
||||
:style="{ scale }"
|
||||
|
||||
class="origin-center flex items-center justify-center"
|
||||
>
|
||||
<!-- Grid Layout -->
|
||||
<div class="grid grid-cols-3 md:grid-cols-5 gap-4 md:gap-6 w-[200vw] md:w-[140vw] h-auto p-4">
|
||||
<div v-if="!isMobile" class="grid grid-cols-3 md:grid-cols-5 gap-2 md:gap-3 w-full h-auto p-4" >
|
||||
<!-- --- ROW 1 --- -->
|
||||
<div class="aspect-[9/16] rounded-xl overflow-hidden bg-gray-900/50"><img :src="gridImages[0]" class="w-full h-full object-cover opacity-60" alt="Robot Companion" /></div>
|
||||
<div class="aspect-[9/16] rounded-xl overflow-hidden bg-gray-900/50"><img :src="gridImages[1]" class="w-full h-full object-cover opacity-60" alt="Electronics" /></div>
|
||||
<div class="aspect-[9/16] rounded-xl overflow-hidden bg-gray-900/50"><img :src="gridImages[2]" class="w-full h-full object-cover opacity-60" alt="Retro Bot" /></div>
|
||||
<div class="hidden md:block aspect-[9/16] rounded-xl overflow-hidden bg-gray-900/50"><img :src="gridImages[3]" class="w-full h-full object-cover opacity-60" alt="Toy Bot" /></div>
|
||||
<div class="hidden md:block aspect-[9/16] rounded-xl overflow-hidden bg-gray-900/50"><img :src="gridImages[4]" class="w-full h-full object-cover opacity-60" alt="Cyberpunk" /></div>
|
||||
<div class="aspect-[9/16] md:aspect-[1/1] rounded-xl overflow-hidden bg-gray-900/50"><img :src="center1" class="w-full h-full object-cover" alt="Robot Companion" /></div>
|
||||
<div class="aspect-[9/16] md:aspect-[1/1] rounded-xl overflow-hidden bg-gray-900/50"><img :src="center3" class="w-full h-full object-cover" alt="Electronics" /></div>
|
||||
<div :style="{ scale: scale*1.5<=1?1:scale*1.5, zIndex:20 }" class="aspect-[9/16] md:aspect-[1/1] rounded-xl overflow-hidden bg-gray-900/50">
|
||||
<img :src="center" class="w-full h-full object-cover" alt="Main Hero Robot" />
|
||||
<!-- <div style="position: absolute;bottom: 0%;width: 100vw;left: 50%;transform: translateX(-50%);">
|
||||
</div> -->
|
||||
</div>
|
||||
<div class="aspect-[9/16] md:aspect-[1/1] rounded-xl overflow-hidden bg-gray-900/50"><img :src="center4" class="w-full h-full object-cover" alt="Retro Bot" /></div>
|
||||
<div class=" md:block aspect-[9/16] md:aspect-[1/1] rounded-xl overflow-hidden bg-gray-900/50"><img :src="center2" class="w-full h-full object-cover" alt="Toy Bot" /></div>
|
||||
<div class=" md:block aspect-[9/16] md:aspect-[1/1] rounded-xl overflow-hidden bg-gray-900/50"><img :src="center8" class="w-full h-full object-cover" alt="Cyberpunk" /></div>
|
||||
|
||||
<!-- --- ROW 2 (Middle) --- -->
|
||||
<div class="aspect-[9/16] rounded-xl overflow-hidden bg-gray-900/50"><img :src="gridImages[5]" class="w-full h-full object-cover opacity-60" alt="Interactive" /></div>
|
||||
<div class="hidden md:block aspect-[9/16] rounded-xl overflow-hidden bg-gray-900/50"><img :src="gridImages[6]" class="w-full h-full object-cover opacity-60" alt="Small Bot" /></div>
|
||||
<div class="aspect-[9/16] md:aspect-[1/1] rounded-xl overflow-hidden bg-gray-900/50"><img :src="center5" class="w-full h-full object-cover" alt="Interactive" /></div>
|
||||
<div class=" md:block aspect-[9/16] md:aspect-[1/1] rounded-xl overflow-hidden bg-gray-900/50"><img :src="center4" class="w-full h-full object-cover" alt="Small Bot" /></div>
|
||||
|
||||
<!-- --- CENTER HERO IMAGE (Always Visible) --- -->
|
||||
<div class="col-span-1 row-span-1 aspect-[9/16] rounded-xl overflow-hidden shadow-2xl relative z-10 bg-gray-800 border border-gray-700">
|
||||
<img :src="heroImage" class="w-full h-full object-cover" alt="Main Hero Robot" />
|
||||
</div>
|
||||
|
||||
<div class="hidden md:block aspect-[9/16] rounded-xl overflow-hidden bg-gray-900/50"><img :src="gridImages[7]" class="w-full h-full object-cover opacity-60" alt="3D Print" /></div>
|
||||
<div class="aspect-[9/16] rounded-xl overflow-hidden bg-gray-900/50"><img :src="gridImages[8]" class="w-full h-full object-cover opacity-60" alt="Glowing Eye" /></div>
|
||||
|
||||
<!-- :style="{ scale: scale*1.2 }" -->
|
||||
<div class="aspect-[9/16] md:aspect-[1/1] rounded-xl overflow-hidden bg-gray-900/50"><img :src="center9" class="w-full h-full object-cover" alt="Glowing Eye" /></div>
|
||||
<div class="hidden md:block aspect-[9/16] md:aspect-[1/1] rounded-xl overflow-hidden bg-gray-900/50"><img :src="center8" class="w-full h-full object-cover" alt="3D Print" /></div>
|
||||
<!-- --- ROW 3 --- -->
|
||||
<div class="hidden md:block aspect-[9/16] rounded-xl overflow-hidden bg-gray-900/50"><img :src="gridImages[9]" class="w-full h-full object-cover opacity-60" alt="Tech Texture" /></div>
|
||||
<div class="aspect-[9/16] rounded-xl overflow-hidden bg-gray-900/50"><img :src="gridImages[10]" class="w-full h-full object-cover opacity-60" alt="Robot Hand" /></div>
|
||||
<div class="aspect-[9/16] rounded-xl overflow-hidden bg-gray-900/50"><img :src="gridImages[11]" class="w-full h-full object-cover opacity-60" alt="Circuit" /></div>
|
||||
<div class="hidden md:block aspect-[9/16] rounded-xl overflow-hidden bg-gray-900/50"><img :src="gridImages[12]" class="w-full h-full object-cover opacity-60" alt="Display" /></div>
|
||||
<div class="hidden md:block aspect-[9/16] rounded-xl overflow-hidden bg-gray-900/50"><img :src="gridImages[13]" class="w-full h-full object-cover opacity-60" alt="Robotics" /></div>
|
||||
<!-- <div class="hidden md:block aspect-[9/16] md:aspect-[1/1] rounded-xl overflow-hidden bg-gray-900/50"><img :src="center1" class="w-full h-full object-cover" alt="Tech Texture" /></div>
|
||||
<div class="aspect-[9/16] md:aspect-[1/1] rounded-xl overflow-hidden bg-gray-900/50"><img :src="center2" class="w-full h-full object-cover" alt="Robot Hand" /></div>
|
||||
<div class="aspect-[9/16] md:aspect-[1/1] rounded-xl overflow-hidden bg-gray-900/50"><img :src="center6" class="w-full h-full object-cover" alt="Circuit" /></div>
|
||||
<div class="hidden md:block aspect-[9/16] md:aspect-[1/1] rounded-xl overflow-hidden bg-gray-900/50"><img :src="center4" class="w-full h-full object-cover" alt="Display" /></div>
|
||||
<div class=" aspect-[9/16] md:aspect-[1/1] rounded-xl overflow-hidden bg-gray-900/50"><img :src="center5" class="w-full h-full object-cover" alt="Robotics" /></div> -->
|
||||
</div>
|
||||
<div v-else class="grid grid-cols-3 md:grid-cols-5 gap-2 md:gap-3 w-full h-auto p-4" >
|
||||
<!-- --- ROW 1 --- -->
|
||||
<div class="aspect-[9/16] md:aspect-[1/1] rounded-xl overflow-hidden bg-gray-900/50"><img :src="center1" class="w-full h-full object-cover" alt="Robot Companion" /></div>
|
||||
<div class="aspect-[9/16] md:aspect-[1/1] rounded-xl overflow-hidden bg-gray-900/50"><img :src="center3" class="w-full h-full object-cover" alt="Electronics" /></div>
|
||||
|
||||
<div class="aspect-[9/16] md:aspect-[1/1] rounded-xl overflow-hidden bg-gray-900/50"><img :src="center2" class="w-full h-full object-cover" alt="Retro Bot" /></div>
|
||||
<div class=" md:block aspect-[9/16] md:aspect-[1/1] rounded-xl overflow-hidden bg-gray-900/50"><img :src="center4" class="w-full h-full object-cover" alt="Toy Bot" /></div>
|
||||
<div :style="{ scale: scale*1.5<=1?1:scale*1.5, zIndex:20 }" class="aspect-[9/16] md:aspect-[1/1] rounded-xl overflow-hidden bg-gray-900/50">
|
||||
<img :src="center" class="w-full h-full object-cover" alt="Main Hero Robot" />
|
||||
<!-- <div style="position: absolute;bottom: 0%;width: 100vw;left: 50%;transform: translateX(-50%);">
|
||||
</div> -->
|
||||
</div>
|
||||
<div class=" md:block aspect-[9/16] md:aspect-[1/1] rounded-xl overflow-hidden bg-gray-900/50"><img :src="center5" class="w-full h-full object-cover" alt="Cyberpunk" /></div>
|
||||
<!-- --- ROW 2 (Middle) --- -->
|
||||
<div class="aspect-[9/16] md:aspect-[1/1] rounded-xl overflow-hidden bg-gray-900/50"><img :src="center7" class="w-full h-full object-cover" alt="Interactive" /></div>
|
||||
<div class=" md:block aspect-[9/16] md:aspect-[1/1] rounded-xl overflow-hidden bg-gray-900/50"><img :src="center9" class="w-full h-full object-cover" alt="Small Bot" /></div>
|
||||
|
||||
<!-- --- CENTER HERO IMAGE (Always Visible) --- -->
|
||||
<!-- :style="{ scale: scale*1.2 }" -->
|
||||
<div class="aspect-[9/16] md:aspect-[1/1] rounded-xl overflow-hidden bg-gray-900/50"><img :src="center8" class="w-full h-full object-cover" alt="Glowing Eye" /></div>
|
||||
<!-- --- ROW 3 --- -->
|
||||
<!-- <div class="hidden md:block aspect-[9/16] md:aspect-[1/1] rounded-xl overflow-hidden bg-gray-900/50"><img :src="center1" class="w-full h-full object-cover" alt="Tech Texture" /></div>
|
||||
<div class="aspect-[9/16] md:aspect-[1/1] rounded-xl overflow-hidden bg-gray-900/50"><img :src="center2" class="w-full h-full object-cover" alt="Robot Hand" /></div>
|
||||
<div class="aspect-[9/16] md:aspect-[1/1] rounded-xl overflow-hidden bg-gray-900/50"><img :src="center6" class="w-full h-full object-cover" alt="Circuit" /></div>
|
||||
<div class="hidden md:block aspect-[9/16] md:aspect-[1/1] rounded-xl overflow-hidden bg-gray-900/50"><img :src="center4" class="w-full h-full object-cover" alt="Display" /></div>
|
||||
<div class=" aspect-[9/16] md:aspect-[1/1] rounded-xl overflow-hidden bg-gray-900/50"><img :src="center5" class="w-full h-full object-cover" alt="Robotics" /></div> -->
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<!-- Layer 2: Static Dark Overlay -->
|
||||
|
||||
<div class="absolute inset-0 z-10 pointer-events-none" />
|
||||
|
||||
<!-- Layer 3: Static Content Layer -->
|
||||
<div class="absolute inset-0 z-20 flex flex-col items-center justify-center pointer-events-none">
|
||||
|
||||
<MotionCom>
|
||||
<div class="pointer-events-auto flex flex-col items-center justify-center text-center px-4 w-full max-w-5xl mx-auto">
|
||||
<h1 class="text-5xl md:text-7xl lg:text-8xl font-bold text-white mb-6 tracking-tighter drop-shadow-2xl">
|
||||
{{ t('hero.title') }}
|
||||
|
|
@ -155,17 +193,20 @@
|
|||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</MotionCom>
|
||||
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Creation Canvas Section -->
|
||||
<section class="py-24 relative overflow-hidden">
|
||||
<section class="py-0 md:py-24 relative overflow-hidden">
|
||||
<!-- Background gradient hint -->
|
||||
<div class="absolute top-0 left-1/2 -translate-x-1/2 w-[800px] h-[500px] bg-gray-900/50 blur-[120px] rounded-full pointer-events-none" />
|
||||
|
||||
<div class="container mx-auto px-6 relative z-10">
|
||||
<MotionCom>
|
||||
<div class="text-center mb-16">
|
||||
<h2 class="text-4xl md:text-5xl font-bold mb-4 text-white">
|
||||
{{ t('canvas.title') }}
|
||||
|
|
@ -174,7 +215,8 @@
|
|||
{{ t('canvas.subtitle') }}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
</MotionCom>
|
||||
<MotionCom>
|
||||
<!-- Workflow Visualization Container -->
|
||||
<div class="relative w-full max-w-6xl mx-auto bg-gray-900/40 border border-gray-800 rounded-3xl p-8 md:p-12 backdrop-blur-sm">
|
||||
|
||||
|
|
@ -253,9 +295,10 @@
|
|||
|
||||
</div>
|
||||
</div>
|
||||
</MotionCom>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<MotionCom>
|
||||
<!-- Companionship Section -->
|
||||
<section class="py-24 border-t border-gray-900">
|
||||
<div class="container mx-auto px-6 flex flex-col items-center text-center">
|
||||
|
|
@ -282,7 +325,7 @@
|
|||
<!-- Optional Visual Element below -->
|
||||
<div class="mt-16 w-full max-w-4xl h-64 md:h-96 rounded-3xl overflow-hidden relative">
|
||||
<img
|
||||
:src="lopi"
|
||||
src="https://draft-user.s3.us-east-2.amazonaws.com/images/ca6a57e3-85b1-4aa7-b032-78d54d4850ea.jpg"
|
||||
alt="Robot Companion Context"
|
||||
class="w-full h-full object-cover opacity-90 hover:opacity-100 transition-opacity duration-500"
|
||||
/>
|
||||
|
|
@ -290,7 +333,8 @@
|
|||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
</MotionCom>
|
||||
<MotionCom>
|
||||
<!-- Middle Text Section -->
|
||||
<section class="py-12 text-center">
|
||||
<div class="container mx-auto px-6 max-w-3xl">
|
||||
|
|
@ -300,7 +344,8 @@
|
|||
</p>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
</MotionCom>
|
||||
<MotionCom>
|
||||
<!-- Robot Cards Section -->
|
||||
<section class="py-20 overflow-hidden">
|
||||
<div class="container mx-auto px-6">
|
||||
|
|
@ -324,7 +369,7 @@
|
|||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
</MotionCom>
|
||||
<!-- Features Section -->
|
||||
<section class="py-24 bg-gray-900/30">
|
||||
<div class="container mx-auto px-6">
|
||||
|
|
@ -451,7 +496,9 @@
|
|||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div >
|
||||
|
||||
</div>
|
||||
<div class="mt-16 pt-8 border-t border-gray-900 text-center md:text-left text-sm text-gray-600">
|
||||
{{ t('footer.copyright') }}
|
||||
</div>
|
||||
|
|
@ -462,16 +509,34 @@
|
|||
</template>
|
||||
|
||||
<script setup>
|
||||
import MotionCom from './motion.vue'
|
||||
import spline from './spline.vue';
|
||||
import { ref, onMounted, onUnmounted, computed } from 'vue';
|
||||
import Bg from './bg.vue'
|
||||
import lopi from '@/assets/home/lopi.jpg'
|
||||
import dog from '@/assets/home/dog.jpg'
|
||||
import dog3 from '@/assets/home/dog3.jpg'
|
||||
import qdog from '@/assets/home/qdog.jpg'
|
||||
import footer1 from '@/assets/home/footer1.png'
|
||||
import footer2 from '@/assets/home/footer2.png'
|
||||
import footer3 from '@/assets/home/footer3.png'
|
||||
|
||||
import center from '@/assets/home/center.png'
|
||||
import center1 from '@/assets/home/center1.png'
|
||||
import center2 from '@/assets/home/center2.png'
|
||||
import center3 from '@/assets/home/center3.png'
|
||||
import center4 from '@/assets/home/center4.png'
|
||||
import center5 from '@/assets/home/center5.png'
|
||||
import center6 from '@/assets/home/center6.png'
|
||||
import center7 from '@/assets/home/center7.png'
|
||||
import center8 from '@/assets/home/center8.png'
|
||||
import center9 from '@/assets/home/center9.png'
|
||||
// Window size reactive state
|
||||
const getResponsiveWidthStyle = () => {
|
||||
const isMobile = window.innerWidth < 768;
|
||||
return {
|
||||
position: 'fixed',
|
||||
width: isMobile ? '300vw' : '100vw',
|
||||
left: '50%',
|
||||
top: '50%',
|
||||
transform: 'translate(-50%, -50%)'
|
||||
};
|
||||
};
|
||||
const isMobile = ref(window.innerWidth < 768);
|
||||
// 中英文库
|
||||
const i18n = {
|
||||
en: {
|
||||
|
|
@ -620,7 +685,8 @@ const scrollYProgress = ref(0);
|
|||
|
||||
// Scroll event handler
|
||||
const handleScroll = () => {
|
||||
isScrolled.value = window.scrollY > 50;
|
||||
// isScrolled.value = window.scrollY > 50;
|
||||
isScrolled.value = false;
|
||||
|
||||
// Calculate scroll progress for hero section
|
||||
if (containerRef.value) {
|
||||
|
|
@ -646,11 +712,11 @@ const scrollToTop = () => {
|
|||
|
||||
// Scale transformation based on scroll progress
|
||||
const scale = computed(() => {
|
||||
// Zoom out from scale 3.5 to 1 based on scroll (0 to 0.8 progress)
|
||||
// When progress reaches 0.8, we've completed the zoom out
|
||||
// Zoom out more aggressively to ensure all 15 images are visible
|
||||
// Final scale is 0.2 for mobile and 0.3 for desktop
|
||||
const mappedProgress = Math.min(scrollYProgress.value / 0.8, 1);
|
||||
let initNum = window.innerWidth < 768 ? 3.5 : 5.5;
|
||||
let outNum = window.innerWidth < 768 ? 2.5 : 4.9;
|
||||
let outNum = window.innerWidth < 768 ? 3.2 : 5.0;
|
||||
return initNum - (mappedProgress * outNum);
|
||||
});
|
||||
|
||||
|
|
@ -693,13 +759,13 @@ const gridImages = [
|
|||
// Creation Canvas Images
|
||||
const refImage = dog;
|
||||
const model3dImage = qdog;
|
||||
const realRobotImage = dog3;
|
||||
const realRobotImage = 'https://draft-user.s3.us-east-2.amazonaws.com/images/fd7ab938-d101-4ddd-9b01-f40a3180004c.jpg';
|
||||
|
||||
// Robot Cards
|
||||
const cards = [
|
||||
{ id: 1, title: 'Custom Robot', user: '@Wownny wolf', img:footer3 },
|
||||
{ id: 2, title: 'Custom Robot', user: '@Lil Moods', img: footer2 },
|
||||
{ id: 3, title: 'Custom Robot', user: '@Deo Monkey', img:footer1 },
|
||||
{ id: 1, title: 'Custom Robot', user: '@Wownny wolf', img:'https://draft-user.s3.us-east-2.amazonaws.com/images/ba284669-6e9c-4a10-ae8d-21954629788d.png' },
|
||||
{ id: 2, title: 'Custom Robot', user: '@Lil Moods', img: 'https://draft-user.s3.us-east-2.amazonaws.com/images/72109af5-7ff7-47a1-ba7d-5e18ab855479.png' },
|
||||
{ id: 3, title: 'Custom Robot', user: '@Deo Monkey', img:'https://draft-user.s3.us-east-2.amazonaws.com/images/fd21fc66-8cae-417f-9e9a-617f18af9406.png' },
|
||||
];
|
||||
|
||||
// Features List
|
||||
|
|
|
|||
|
|
@ -0,0 +1,22 @@
|
|||
<template>
|
||||
<Motion
|
||||
as="div"
|
||||
:initial="{ opacity: 0, y: 40, filter: 'blur(10px)' }"
|
||||
:while-in-view="{
|
||||
opacity: 1,
|
||||
y: 0,
|
||||
filter: 'blur(0px)',
|
||||
}"
|
||||
:transition="{
|
||||
delay: 0.3,
|
||||
duration: 0.8,
|
||||
ease: 'easeInOut',
|
||||
}"
|
||||
class="relative flex flex-col items-center justify-center gap-4 px-4"
|
||||
>
|
||||
<slot></slot>
|
||||
</Motion>
|
||||
</template>
|
||||
<script setup>
|
||||
import { Motion } from "motion-v";
|
||||
</script>
|
||||
|
|
@ -0,0 +1,162 @@
|
|||
<template>
|
||||
<ParentSize
|
||||
:parent-size-styles="parentSizeStyles"
|
||||
:debounce-time="50"
|
||||
v-bind="$attrs"
|
||||
>
|
||||
<template #default>
|
||||
<canvas
|
||||
ref="canvasRef"
|
||||
:style="canvasStyle"
|
||||
/>
|
||||
<slot v-if="isLoading" />
|
||||
</template>
|
||||
</ParentSize>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
/* eslint-disable no-console */
|
||||
import { ref, onMounted, onUnmounted, computed, watch, nextTick } from "vue";
|
||||
import { Application } from "@splinetool/runtime";
|
||||
import { useDebounceFn, useIntersectionObserver } from "@vueuse/core";
|
||||
import ParentSize from "./ParentSize.vue";
|
||||
|
||||
const props = defineProps({
|
||||
scene: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
onLoad: Function,
|
||||
renderOnDemand: {
|
||||
type: Boolean,
|
||||
default: true,
|
||||
},
|
||||
style: Object,
|
||||
});
|
||||
|
||||
const emit = defineEmits([
|
||||
"error",
|
||||
"spline-mouse-down",
|
||||
"spline-mouse-up",
|
||||
"spline-mouse-hover",
|
||||
"spline-key-down",
|
||||
"spline-key-up",
|
||||
"spline-start",
|
||||
"spline-look-at",
|
||||
"spline-follow",
|
||||
"spline-scroll",
|
||||
]);
|
||||
|
||||
const canvasRef = ref(null);
|
||||
const isLoading = ref(false);
|
||||
const splineApp = ref(null);
|
||||
const isVisible = ref(true);
|
||||
|
||||
// eslint-disable-next-line func-style
|
||||
let cleanup = () => {};
|
||||
|
||||
const parentSizeStyles = computed(() => ({
|
||||
overflow: "hidden",
|
||||
...props.style,
|
||||
}));
|
||||
|
||||
const canvasStyle = computed(() => ({
|
||||
display: "block",
|
||||
width: "100%",
|
||||
height: "100%",
|
||||
}));
|
||||
|
||||
// Use IntersectionObserver to detect when component is visible
|
||||
const { stop: stopIntersectionObserver } = useIntersectionObserver(
|
||||
canvasRef,
|
||||
([{ isIntersecting }]) => {
|
||||
isVisible.value = isIntersecting;
|
||||
if (isIntersecting && splineApp.value) {
|
||||
// When becoming visible again, force a resize
|
||||
nextTick(() => {
|
||||
if (canvasRef.value && splineApp.value) {
|
||||
splineApp.value.requestRender();
|
||||
splineApp.value.setSize(canvasRef.value.clientWidth, canvasRef.value.clientHeight);
|
||||
}
|
||||
});
|
||||
}
|
||||
},
|
||||
{ threshold: 0.1 },
|
||||
);
|
||||
|
||||
function eventHandler(name, handler) {
|
||||
if (!handler || !splineApp.value) return;
|
||||
const debouncedHandler = useDebounceFn(handler, 50, { maxWait: 100 });
|
||||
splineApp.value.addEventListener(name, debouncedHandler);
|
||||
return () => splineApp.value?.removeEventListener(name, debouncedHandler);
|
||||
}
|
||||
|
||||
async function initSpline() {
|
||||
if (!canvasRef.value) return;
|
||||
|
||||
isLoading.value = true;
|
||||
|
||||
try {
|
||||
// Clean up previous instance if exists
|
||||
if (splineApp.value) {
|
||||
splineApp.value.dispose();
|
||||
splineApp.value = null;
|
||||
}
|
||||
|
||||
splineApp.value = new Application(canvasRef.value, {
|
||||
renderOnDemand: props.renderOnDemand,
|
||||
});
|
||||
|
||||
await splineApp.value.load(props.scene);
|
||||
|
||||
// Set up event listeners
|
||||
const cleanUpFns = [
|
||||
eventHandler("mouseDown", (e) => emit("spline-mouse-down", e)),
|
||||
eventHandler("mouseUp", (e) => emit("spline-mouse-up", e)),
|
||||
eventHandler("mouseHover", (e) => emit("spline-mouse-hover", e)),
|
||||
eventHandler("keyDown", (e) => emit("spline-key-down", e)),
|
||||
eventHandler("keyUp", (e) => emit("spline-key-up", e)),
|
||||
eventHandler("start", (e) => emit("spline-start", e)),
|
||||
eventHandler("lookAt", (e) => emit("spline-look-at", e)),
|
||||
eventHandler("follow", (e) => emit("spline-follow", e)),
|
||||
eventHandler("scroll", (e) => emit("spline-scroll", e)),
|
||||
].filter(Boolean);
|
||||
|
||||
isLoading.value = false;
|
||||
props.onLoad?.(splineApp.value);
|
||||
|
||||
return () => {
|
||||
cleanUpFns.forEach((fn) => fn?.());
|
||||
};
|
||||
} catch (err) {
|
||||
console.error("Spline initialization error:", err);
|
||||
emit("error", err);
|
||||
isLoading.value = false;
|
||||
return () => {};
|
||||
}
|
||||
}
|
||||
|
||||
async function initialize() {
|
||||
cleanup();
|
||||
cleanup = (await initSpline()) ?? (() => {});
|
||||
}
|
||||
|
||||
onMounted(async () => {
|
||||
await initialize();
|
||||
|
||||
// Reinitialize when becoming visible again
|
||||
watch(isVisible, (visible) => {
|
||||
if (visible) {
|
||||
initialize();
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
onUnmounted(() => {
|
||||
stopIntersectionObserver();
|
||||
if (splineApp.value) {
|
||||
splineApp.value.dispose();
|
||||
splineApp.value = null;
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
|
@ -1,79 +1,56 @@
|
|||
/** @type {import('tailwindcss').Config} */
|
||||
import animate from "tailwindcss-animate";
|
||||
import { setupInspiraUI } from "@inspira-ui/plugins";
|
||||
|
||||
export default {
|
||||
content: [
|
||||
"./index.html",
|
||||
"./src/**/*.{vue,js,ts,jsx,tsx}",
|
||||
],
|
||||
darkMode: "selector",
|
||||
safelist: ["dark"],
|
||||
prefix: "",
|
||||
content: ["./index.html","./public/**/*.html", "./src/**/*.{vue,js,ts,jsx,tsx}","./pages/**/*.{vue,js}" ],
|
||||
theme: {
|
||||
extend: {
|
||||
// 自定义颜色主题
|
||||
colors: {
|
||||
border: "hsl(var(--border))",
|
||||
input: "hsl(var(--input))",
|
||||
ring: "hsl(var(--ring))",
|
||||
background: "hsl(var(--background))",
|
||||
foreground: "hsl(var(--foreground))",
|
||||
primary: {
|
||||
DEFAULT: '#6B46C1', // 深紫色
|
||||
light: '#A78BFA', // 浅紫色
|
||||
dark: '#553C9A', // 更深的紫色
|
||||
DEFAULT: "hsl(var(--primary))",
|
||||
foreground: "hsl(var(--primary-foreground))",
|
||||
},
|
||||
secondary: {
|
||||
DEFAULT: '#1F2937', // 深灰色
|
||||
light: '#4B5563', // 中灰色
|
||||
lighter: '#9CA3AF', // 浅灰色
|
||||
DEFAULT: "hsl(var(--secondary))",
|
||||
foreground: "hsl(var(--secondary-foreground))",
|
||||
},
|
||||
destructive: {
|
||||
DEFAULT: "hsl(var(--destructive))",
|
||||
foreground: "hsl(var(--destructive-foreground))",
|
||||
},
|
||||
muted: {
|
||||
DEFAULT: "hsl(var(--muted))",
|
||||
foreground: "hsl(var(--muted-foreground))",
|
||||
},
|
||||
accent: {
|
||||
DEFAULT: "hsl(var(--accent))",
|
||||
foreground: "hsl(var(--accent-foreground))",
|
||||
},
|
||||
popover: {
|
||||
DEFAULT: "hsl(var(--popover))",
|
||||
foreground: "hsl(var(--popover-foreground))",
|
||||
},
|
||||
card: {
|
||||
DEFAULT: "hsl(var(--card))",
|
||||
foreground: "hsl(var(--card-foreground))",
|
||||
},
|
||||
background: {
|
||||
DEFAULT: '#F3F4F6', // 浅灰色背景
|
||||
dark: '#111827', // 深色背景
|
||||
card: '#FFFFFF', // 卡片背景
|
||||
}
|
||||
},
|
||||
// 响应式断点配置
|
||||
screens: {
|
||||
'xs': '475px', // 超小屏
|
||||
'sm': '640px', // 小屏(手机横屏)
|
||||
'md': '768px', // 中屏(平板)
|
||||
'lg': '1024px', // 大屏(桌面)
|
||||
'xl': '1280px', // 超大屏
|
||||
'2xl': '1536px', // 超超大屏
|
||||
},
|
||||
// 字体配置
|
||||
fontFamily: {
|
||||
'sans': ['Inter', 'system-ui', 'sans-serif'],
|
||||
},
|
||||
// 间距配置(8px网格系统)
|
||||
spacing: {
|
||||
'18': '4.5rem',
|
||||
'88': '22rem',
|
||||
'128': '32rem',
|
||||
},
|
||||
// 圆角配置
|
||||
borderRadius: {
|
||||
'4xl': '2rem',
|
||||
},
|
||||
// 阴影配置
|
||||
boxShadow: {
|
||||
'card': '0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -1px rgba(0, 0, 0, 0.06)',
|
||||
'card-hover': '0 10px 15px -3px rgba(0, 0, 0, 0.1), 0 4px 6px -2px rgba(0, 0, 0, 0.05)',
|
||||
},
|
||||
// 动画配置
|
||||
animation: {
|
||||
'fade-in': 'fadeIn 0.2s ease-in-out',
|
||||
'slide-up': 'slideUp 0.3s ease-out',
|
||||
},
|
||||
keyframes: {
|
||||
fadeIn: {
|
||||
'0%': { opacity: '0' },
|
||||
'100%': { opacity: '1' },
|
||||
},
|
||||
slideUp: {
|
||||
'0%': { transform: 'translateY(10px)', opacity: '0' },
|
||||
'100%': { transform: 'translateY(0)', opacity: '1' },
|
||||
},
|
||||
xl: "calc(var(--radius) + 4px)",
|
||||
lg: "var(--radius)",
|
||||
md: "calc(var(--radius) - 2px)",
|
||||
sm: "calc(var(--radius) - 4px)",
|
||||
},
|
||||
},
|
||||
},
|
||||
plugins: [],
|
||||
// 暗黑模式支持
|
||||
darkMode: 'class',
|
||||
// 兼容性配置
|
||||
corePlugins: {
|
||||
preflight: true,
|
||||
},
|
||||
}
|
||||
|
||||
plugins: [animate, setupInspiraUI],
|
||||
};
|
||||
|
|
|
|||
|
|
@ -30,6 +30,7 @@
|
|||
"dependencies": {
|
||||
"@element-plus/icons-vue": "^2.3.2",
|
||||
"@google/genai": "^1.27.0",
|
||||
"@splinetool/runtime": "^1.12.6",
|
||||
"@stripe/stripe-js": "^4.8.0",
|
||||
"@twind/core": "^1.1.3",
|
||||
"@twind/preset-autoprefix": "^1.0.7",
|
||||
|
|
@ -37,11 +38,13 @@
|
|||
"@types/three": "^0.180.0",
|
||||
"@vuelidate/core": "^2.0.3",
|
||||
"@vuelidate/validators": "^2.0.4",
|
||||
"@vueuse/core": "^14.1.0",
|
||||
"axios": "^1.13.2",
|
||||
"country-state-city": "^3.2.1",
|
||||
"dayjs": "^1.11.13",
|
||||
"element-plus": "^2.11.7",
|
||||
"jose": "^6.1.1",
|
||||
"motion-v": "^1.7.4",
|
||||
"normalize.css": "^8.0.1",
|
||||
"nprogress": "^0.2.0",
|
||||
"pinia": "^3.0.4",
|
||||
|
|
@ -57,11 +60,16 @@
|
|||
},
|
||||
"devDependencies": {
|
||||
"@iconify-json/feather": "^1.2.1",
|
||||
"@inspira-ui/plugins": "^0.0.1",
|
||||
"@tailwindcss/postcss": "^4.1.17",
|
||||
"@vitejs/plugin-vue": "^6.0.1",
|
||||
"autoprefixer": "^10.4.22",
|
||||
"class-variance-authority": "^0.7.1",
|
||||
"clsx": "^2.1.1",
|
||||
"postcss": "^8.5.6",
|
||||
"tailwind-merge": "^3.4.0",
|
||||
"tailwindcss": "^4.1.17",
|
||||
"tailwindcss-animate": "^1.0.7",
|
||||
"terser": "^5.44.1",
|
||||
"unplugin-auto-import": "^20.2.0",
|
||||
"unplugin-icons": "^22.5.0",
|
||||
|
|
@ -69,6 +77,65 @@
|
|||
"vite": "^7.2.2"
|
||||
}
|
||||
},
|
||||
"apps/frontend/node_modules/@types/web-bluetooth": {
|
||||
"version": "0.0.21",
|
||||
"resolved": "https://registry.npmmirror.com/@types/web-bluetooth/-/web-bluetooth-0.0.21.tgz",
|
||||
"integrity": "sha512-oIQLCGWtcFZy2JW77j9k8nHzAOpqMHLQejDA48XXMWH6tjCQHz5RCFz1bzsmROyL6PUm+LLnUiI4BCn221inxA==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"apps/frontend/node_modules/@vueuse/core": {
|
||||
"version": "14.1.0",
|
||||
"resolved": "https://registry.npmmirror.com/@vueuse/core/-/core-14.1.0.tgz",
|
||||
"integrity": "sha512-rgBinKs07hAYyPF834mDTigH7BtPqvZ3Pryuzt1SD/lg5wEcWqvwzXXYGEDb2/cP0Sj5zSvHl3WkmMELr5kfWw==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@types/web-bluetooth": "^0.0.21",
|
||||
"@vueuse/metadata": "14.1.0",
|
||||
"@vueuse/shared": "14.1.0"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/antfu"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"vue": "^3.5.0"
|
||||
}
|
||||
},
|
||||
"apps/frontend/node_modules/@vueuse/metadata": {
|
||||
"version": "14.1.0",
|
||||
"resolved": "https://registry.npmmirror.com/@vueuse/metadata/-/metadata-14.1.0.tgz",
|
||||
"integrity": "sha512-7hK4g015rWn2PhKcZ99NyT+ZD9sbwm7SGvp7k+k+rKGWnLjS/oQozoIZzWfCewSUeBmnJkIb+CNr7Zc/EyRnnA==",
|
||||
"license": "MIT",
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/antfu"
|
||||
}
|
||||
},
|
||||
"apps/frontend/node_modules/@vueuse/shared": {
|
||||
"version": "14.1.0",
|
||||
"resolved": "https://registry.npmmirror.com/@vueuse/shared/-/shared-14.1.0.tgz",
|
||||
"integrity": "sha512-EcKxtYvn6gx1F8z9J5/rsg3+lTQnvOruQd8fUecW99DCK04BkWD7z5KQ/wTAx+DazyoEE9dJt/zV8OIEQbM6kw==",
|
||||
"license": "MIT",
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/antfu"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"vue": "^3.5.0"
|
||||
}
|
||||
},
|
||||
"apps/frontend/node_modules/motion-v": {
|
||||
"version": "1.7.4",
|
||||
"resolved": "https://registry.npmmirror.com/motion-v/-/motion-v-1.7.4.tgz",
|
||||
"integrity": "sha512-YNDUAsany04wfI7YtHxQK3kxzNvh+OdFUk9GpA3+hMt7j6P+5WrVAAgr8kmPPoVza9EsJiAVhqoN3YYFN0Twrw==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"framer-motion": "12.23.12",
|
||||
"hey-listen": "^1.0.8",
|
||||
"motion-dom": "12.23.12"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@vueuse/core": ">=10.0.0",
|
||||
"vue": ">=3.0.0"
|
||||
}
|
||||
},
|
||||
"apps/FrontendDesigner": {
|
||||
"name": "frontenddesigner",
|
||||
"version": "0.0.0",
|
||||
|
|
@ -1185,6 +1252,16 @@
|
|||
"url": "https://github.com/sponsors/sindresorhus"
|
||||
}
|
||||
},
|
||||
"node_modules/@inspira-ui/plugins": {
|
||||
"version": "0.0.1",
|
||||
"resolved": "https://registry.npmmirror.com/@inspira-ui/plugins/-/plugins-0.0.1.tgz",
|
||||
"integrity": "sha512-gM4iZptDoStA7QT1lltC6Jl4qRLhkZwVtcXomQy/PPxe0lAxhrZx40KXEUJakRZLOSmyfVJCExzZhRzOrE6DBQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"mini-svg-data-uri": "^1.4.4"
|
||||
}
|
||||
},
|
||||
"node_modules/@intlify/core-base": {
|
||||
"version": "9.14.5",
|
||||
"resolved": "https://registry.npmmirror.com/@intlify/core-base/-/core-base-9.14.5.tgz",
|
||||
|
|
@ -1707,6 +1784,15 @@
|
|||
"win32"
|
||||
]
|
||||
},
|
||||
"node_modules/@splinetool/runtime": {
|
||||
"version": "1.12.6",
|
||||
"resolved": "https://registry.npmmirror.com/@splinetool/runtime/-/runtime-1.12.6.tgz",
|
||||
"integrity": "sha512-oBybkcit6Ythcyq9XzdQ1KSSJ8E6sqFBjt2SxociOE/A3hWv/k25ESy4LolahF2g48yl/XiLK8kS1EGbh5Bbhw==",
|
||||
"dependencies": {
|
||||
"on-change": "^4.0.0",
|
||||
"semver-compare": "^1.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@stripe/stripe-js": {
|
||||
"version": "4.10.0",
|
||||
"resolved": "https://registry.npmmirror.com/@stripe/stripe-js/-/stripe-js-4.10.0.tgz",
|
||||
|
|
@ -2796,6 +2882,19 @@
|
|||
"url": "https://paulmillr.com/funding/"
|
||||
}
|
||||
},
|
||||
"node_modules/class-variance-authority": {
|
||||
"version": "0.7.1",
|
||||
"resolved": "https://registry.npmmirror.com/class-variance-authority/-/class-variance-authority-0.7.1.tgz",
|
||||
"integrity": "sha512-Ka+9Trutv7G8M6WT6SeiRWz792K5qEqIGEGzXKhAE6xOWAY6pPH8U+9IY3oCMv6kqTmLsv7Xh/2w2RigkePMsg==",
|
||||
"dev": true,
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"clsx": "^2.1.1"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://polar.sh/cva"
|
||||
}
|
||||
},
|
||||
"node_modules/cliui": {
|
||||
"version": "8.0.1",
|
||||
"resolved": "https://registry.npmmirror.com/cliui/-/cliui-8.0.1.tgz",
|
||||
|
|
@ -2851,6 +2950,16 @@
|
|||
"url": "https://github.com/chalk/wrap-ansi?sponsor=1"
|
||||
}
|
||||
},
|
||||
"node_modules/clsx": {
|
||||
"version": "2.1.1",
|
||||
"resolved": "https://registry.npmmirror.com/clsx/-/clsx-2.1.1.tgz",
|
||||
"integrity": "sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=6"
|
||||
}
|
||||
},
|
||||
"node_modules/color-convert": {
|
||||
"version": "2.0.1",
|
||||
"resolved": "https://registry.npmmirror.com/color-convert/-/color-convert-2.0.1.tgz",
|
||||
|
|
@ -3791,6 +3900,33 @@
|
|||
"url": "https://github.com/sponsors/rawify"
|
||||
}
|
||||
},
|
||||
"node_modules/framer-motion": {
|
||||
"version": "12.23.12",
|
||||
"resolved": "https://registry.npmmirror.com/framer-motion/-/framer-motion-12.23.12.tgz",
|
||||
"integrity": "sha512-6e78rdVtnBvlEVgu6eFEAgG9v3wLnYEboM8I5O5EXvfKC8gxGQB8wXJdhkMy10iVcn05jl6CNw7/HTsTCfwcWg==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"motion-dom": "^12.23.12",
|
||||
"motion-utils": "^12.23.6",
|
||||
"tslib": "^2.4.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@emotion/is-prop-valid": "*",
|
||||
"react": "^18.0.0 || ^19.0.0",
|
||||
"react-dom": "^18.0.0 || ^19.0.0"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"@emotion/is-prop-valid": {
|
||||
"optional": true
|
||||
},
|
||||
"react": {
|
||||
"optional": true
|
||||
},
|
||||
"react-dom": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/frontend": {
|
||||
"resolved": "apps/frontend",
|
||||
"link": true
|
||||
|
|
@ -4094,6 +4230,12 @@
|
|||
"node": ">= 0.4"
|
||||
}
|
||||
},
|
||||
"node_modules/hey-listen": {
|
||||
"version": "1.0.8",
|
||||
"resolved": "https://registry.npmmirror.com/hey-listen/-/hey-listen-1.0.8.tgz",
|
||||
"integrity": "sha512-COpmrF2NOg4TBWUJ5UVyaCU2A88wEMkUPK4hNqyCkqHbxT92BbvfjoSozkAIIm6XhicGlJHhFdullInrdhwU8Q==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/hookable": {
|
||||
"version": "5.5.3",
|
||||
"resolved": "https://registry.npmmirror.com/hookable/-/hookable-5.5.3.tgz",
|
||||
|
|
@ -4786,6 +4928,16 @@
|
|||
"node": ">= 0.6"
|
||||
}
|
||||
},
|
||||
"node_modules/mini-svg-data-uri": {
|
||||
"version": "1.4.4",
|
||||
"resolved": "https://registry.npmmirror.com/mini-svg-data-uri/-/mini-svg-data-uri-1.4.4.tgz",
|
||||
"integrity": "sha512-r9deDe9p5FJUPZAk3A59wGH7Ii9YrjjWw0jmw/liSbHl2CHiyXj6FcDXDu2K3TjVAXqiJdaw3xxwlZZr9E6nHg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"bin": {
|
||||
"mini-svg-data-uri": "cli.js"
|
||||
}
|
||||
},
|
||||
"node_modules/minimatch": {
|
||||
"version": "3.1.2",
|
||||
"resolved": "https://registry.npmmirror.com/minimatch/-/minimatch-3.1.2.tgz",
|
||||
|
|
@ -4846,6 +4998,21 @@
|
|||
"pathe": "^2.0.1"
|
||||
}
|
||||
},
|
||||
"node_modules/motion-dom": {
|
||||
"version": "12.23.12",
|
||||
"resolved": "https://registry.npmmirror.com/motion-dom/-/motion-dom-12.23.12.tgz",
|
||||
"integrity": "sha512-RcR4fvMCTESQBD/uKQe49D5RUeDOokkGRmz4ceaJKDBgHYtZtntC/s2vLvY38gqGaytinij/yi3hMcWVcEF5Kw==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"motion-utils": "^12.23.6"
|
||||
}
|
||||
},
|
||||
"node_modules/motion-utils": {
|
||||
"version": "12.23.6",
|
||||
"resolved": "https://registry.npmmirror.com/motion-utils/-/motion-utils-12.23.6.tgz",
|
||||
"integrity": "sha512-eAWoPgr4eFEOFfg2WjIsMoqJTW6Z8MTUCgn/GZ3VRpClWBdnbjryiA3ZSNLyxCTmCQx4RmYX6jX1iWHbenUPNQ==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/ms": {
|
||||
"version": "2.1.3",
|
||||
"resolved": "https://registry.npmmirror.com/ms/-/ms-2.1.3.tgz",
|
||||
|
|
@ -4963,6 +5130,18 @@
|
|||
"url": "https://github.com/fb55/nth-check?sponsor=1"
|
||||
}
|
||||
},
|
||||
"node_modules/on-change": {
|
||||
"version": "4.0.2",
|
||||
"resolved": "https://registry.npmmirror.com/on-change/-/on-change-4.0.2.tgz",
|
||||
"integrity": "sha512-cMtCyuJmTx/bg2HCpHo3ZLeF7FZnBOapLqZHr2AlLeJ5Ul0Zu2mUJJz051Fdwu/Et2YW04ZD+TtU+gVy0ACNCA==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": "^12.20.0 || ^14.13.1 || >=16.0.0"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sindresorhus/on-change?sponsor=1"
|
||||
}
|
||||
},
|
||||
"node_modules/once": {
|
||||
"version": "1.4.0",
|
||||
"resolved": "https://registry.npmmirror.com/once/-/once-1.4.0.tgz",
|
||||
|
|
@ -5504,6 +5683,12 @@
|
|||
"node": ">=10"
|
||||
}
|
||||
},
|
||||
"node_modules/semver-compare": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmmirror.com/semver-compare/-/semver-compare-1.0.0.tgz",
|
||||
"integrity": "sha512-YM3/ITh2MJ5MtzaM429anh+x2jiLVjqILF4m4oyQB18W7Ggea7BfqdH/wGMK7dDiMghv/6WG7znWMwUDzJiXow==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/shebang-command": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmmirror.com/shebang-command/-/shebang-command-2.0.0.tgz",
|
||||
|
|
@ -5745,6 +5930,17 @@
|
|||
"url": "https://github.com/chalk/supports-color?sponsor=1"
|
||||
}
|
||||
},
|
||||
"node_modules/tailwind-merge": {
|
||||
"version": "3.4.0",
|
||||
"resolved": "https://registry.npmmirror.com/tailwind-merge/-/tailwind-merge-3.4.0.tgz",
|
||||
"integrity": "sha512-uSaO4gnW+b3Y2aWoWfFpX62vn2sR3skfhbjsEnaBI81WD1wBLlHZe5sWf0AqjksNdYTbGBEd0UasQMT3SNV15g==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"funding": {
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/dcastil"
|
||||
}
|
||||
},
|
||||
"node_modules/tailwindcss": {
|
||||
"version": "4.1.17",
|
||||
"resolved": "https://registry.npmmirror.com/tailwindcss/-/tailwindcss-4.1.17.tgz",
|
||||
|
|
@ -5752,6 +5948,16 @@
|
|||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/tailwindcss-animate": {
|
||||
"version": "1.0.7",
|
||||
"resolved": "https://registry.npmmirror.com/tailwindcss-animate/-/tailwindcss-animate-1.0.7.tgz",
|
||||
"integrity": "sha512-bl6mpH3T7I3UFxuvDEXLxy/VuFxBk5bbzplh7tXI68mwMokNYd1t9qPBHlnyTwfa4JGC4zP516I1hYYtQ/vspA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peerDependencies": {
|
||||
"tailwindcss": ">=3.0.0 || insiders"
|
||||
}
|
||||
},
|
||||
"node_modules/tapable": {
|
||||
"version": "2.3.0",
|
||||
"resolved": "https://registry.npmmirror.com/tapable/-/tapable-2.3.0.tgz",
|
||||
|
|
@ -5839,7 +6045,6 @@
|
|||
"version": "2.8.1",
|
||||
"resolved": "https://registry.npmmirror.com/tslib/-/tslib-2.8.1.tgz",
|
||||
"integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==",
|
||||
"dev": true,
|
||||
"license": "0BSD"
|
||||
},
|
||||
"node_modules/turbo": {
|
||||
|
|
|
|||
|
|
@ -10,6 +10,50 @@ export class FileServer {
|
|||
concatUrl(url) {
|
||||
return urlRule.replace('IMGURL',url)
|
||||
}
|
||||
|
||||
//生成唯一的缓存键
|
||||
generateUniqueCacheKey(input) {
|
||||
let content = '';
|
||||
|
||||
if (typeof input === 'string') {
|
||||
if (input.startsWith('data:')) {
|
||||
// 对于base64字符串,使用文件类型、原始长度和内容哈希
|
||||
const mimeMatch = input.match(/data:([^;]+)/);
|
||||
const mimeType = mimeMatch ? mimeMatch[1] : 'unknown';
|
||||
const contentLength = input.length;
|
||||
// 取base64内容的前100个字符进行唯一标识
|
||||
const contentPreview = input.substring(0, 100);
|
||||
content = `base64_${mimeType}_${contentLength}_${contentPreview}`;
|
||||
} else if (input.startsWith('http://') || input.startsWith('https://')) {
|
||||
// 对于URL,使用完整URL
|
||||
content = input;
|
||||
} else {
|
||||
// 对于本地路径
|
||||
content = input;
|
||||
}
|
||||
} else if (input instanceof File) {
|
||||
// 对于File对象,使用文件名、大小、类型和最后修改时间
|
||||
content = `file_${input.name}_${input.size}_${input.type}_${input.lastModified}`;
|
||||
}
|
||||
|
||||
// 使用同步哈希算法生成16位缓存键
|
||||
return this.generateSimpleHash(content).substring(0, 16);
|
||||
}
|
||||
|
||||
//简单的字符串哈希函数(同步)
|
||||
generateSimpleHash(content) {
|
||||
// 使用MurmurHash或简单的多项式哈希
|
||||
let hash = 0;
|
||||
const prime = 31;
|
||||
|
||||
for (let i = 0; i < content.length; i++) {
|
||||
const char = content.charCodeAt(i);
|
||||
hash = (prime * hash + char) | 0; // 使用位运算确保32位整数
|
||||
}
|
||||
|
||||
// 转换为16进制字符串
|
||||
return (hash >>> 0).toString(16).padStart(8, '0');
|
||||
}
|
||||
//文件压缩
|
||||
/**
|
||||
* 压缩文件 - 支持多种格式
|
||||
|
|
@ -228,7 +272,7 @@ export class FileServer {
|
|||
throw error;
|
||||
}
|
||||
}
|
||||
const cacheKey = url.slice(-8);
|
||||
const cacheKey = this.generateUniqueCacheKey(url);//生成唯一的缓存key
|
||||
return new Promise(async (resolve, reject) => {
|
||||
// 如果是网络路径直接返回
|
||||
if (typeof url === 'string' && (url.startsWith('http://') || url.startsWith('https://'))) {
|
||||
|
|
|
|||