This commit is contained in:
13121765685 2025-12-09 13:05:48 +08:00
parent 4f86ebb93c
commit e70102de13
42 changed files with 1013 additions and 139 deletions

View File

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

View File

@ -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>
<!-- 全屏页面如创建项目 -->

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.4 MiB

After

Width:  |  Height:  |  Size: 759 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.1 MiB

After

Width:  |  Height:  |  Size: 246 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.4 MiB

After

Width:  |  Height:  |  Size: 320 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.1 MiB

After

Width:  |  Height:  |  Size: 475 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.1 MiB

After

Width:  |  Height:  |  Size: 291 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.6 MiB

After

Width:  |  Height:  |  Size: 343 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.2 MiB

After

Width:  |  Height:  |  Size: 361 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 443 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 413 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 462 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 462 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 436 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 455 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 400 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 391 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 390 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 444 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 28 KiB

After

Width:  |  Height:  |  Size: 27 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.2 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.4 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.5 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 8.0 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 12 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 346 KiB

After

Width:  |  Height:  |  Size: 49 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.4 MiB

After

Width:  |  Height:  |  Size: 268 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.5 MiB

After

Width:  |  Height:  |  Size: 389 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.5 MiB

After

Width:  |  Height:  |  Size: 342 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.4 MiB

After

Width:  |  Height:  |  Size: 347 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.5 MiB

After

Width:  |  Height:  |  Size: 371 KiB

View File

@ -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%;
/* 暗黑模式基础样式 */
html.dark {
@apply bg-gray-900;
}
--card: 222.2 84% 4.9%;
--card-foreground: 210 40% 98%;
html.dark body {
@apply text-gray-200 bg-gray-900;
--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%;
}
}

View File

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

View File

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

View File

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

View File

@ -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%);

View File

@ -1,10 +1,15 @@
<template>
<Bg>
<div class=" min-h-screen flex flex-col w-full selection:bg-purple-500 selection:text-white">
<!-- <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

View File

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

View File

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

View File

@ -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))",
},
background: {
DEFAULT: '#F3F4F6', // 浅灰色背景
dark: '#111827', // 深色背景
card: '#FFFFFF', // 卡片背景
}
destructive: {
DEFAULT: "hsl(var(--destructive))",
foreground: "hsl(var(--destructive-foreground))",
},
// 响应式断点配置
screens: {
'xs': '475px', // 超小屏
'sm': '640px', // 小屏(手机横屏)
'md': '768px', // 中屏(平板)
'lg': '1024px', // 大屏(桌面)
'xl': '1280px', // 超大屏
'2xl': '1536px', // 超超大屏
muted: {
DEFAULT: "hsl(var(--muted))",
foreground: "hsl(var(--muted-foreground))",
},
// 字体配置
fontFamily: {
'sans': ['Inter', 'system-ui', 'sans-serif'],
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))",
},
// 间距配置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],
};

207
package-lock.json generated
View File

@ -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": {

View File

@ -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://'))) {