Files
lgp-admin-plus/apps/web-antd/src/views/code-generation/components/mode-selector.vue

602 lines
18 KiB
Vue
Raw Normal View History

2025-11-28 10:55:29 +08:00
<script setup lang="ts">
export type GenerationMode = 'json' | 'manual' | 'ai';
interface ModeOption {
mode: GenerationMode;
title: string;
description: string;
iconColor: string;
borderGradient: string;
}
const modes: ModeOption[] = [
{
mode: 'json',
title: 'JSON导入',
description: '直接提供JSON数据,快速生成代码',
iconColor: '#6366f1',
borderGradient:
'linear-gradient(90deg, #6366f1, #8b5cf6, #a855f7, #8b5cf6, #6366f1)',
},
{
mode: 'manual',
title: '手动填写',
description: '逐步填写表单,精确控制每个字段',
iconColor: '#f59e0b',
borderGradient:
'linear-gradient(90deg, #f59e0b, #f97316, #fb923c, #f97316, #f59e0b)',
},
{
mode: 'ai',
title: 'AI提示词',
description: '使用AI智能生成代码结构(开发中)',
iconColor: '#10b981',
borderGradient:
'linear-gradient(90deg, #10b981, #059669, #34d399, #059669, #10b981)',
},
];
const emit = defineEmits<{
select: [mode: GenerationMode];
}>();
const handleSelect = (mode: GenerationMode) => {
emit('select', mode);
};
</script>
<template>
<div class="mode-selector">
<div class="grid grid-cols-1 gap-8 md:grid-cols-3">
<div
v-for="(item, index) in modes"
:key="item.mode"
class="mode-card enter-y group relative overflow-hidden"
:style="{ animationDelay: `${index * 0.15}s` }"
:data-mode="item.mode"
@click="handleSelect(item.mode)"
>
<!-- 装饰背景 -->
<div class="mode-card-decoration">
<!-- JSON模式装饰:网格 -->
<div v-if="item.mode === 'json'" class="json-decoration">
<svg
class="absolute inset-0 h-full w-full opacity-5"
xmlns="http://www.w3.org/2000/svg"
>
<defs>
<pattern
id="grid-json"
width="20"
height="20"
patternUnits="userSpaceOnUse"
>
<path
d="M 20 0 L 0 0 0 20"
fill="none"
stroke="currentColor"
stroke-width="1"
/>
</pattern>
</defs>
<rect width="100%" height="100%" fill="url(#grid-json)" />
</svg>
<!-- 浮动圆点 -->
<div class="absolute right-4 top-4 h-2 w-2 rounded-full bg-current opacity-20"></div>
<div class="absolute bottom-6 left-6 h-1.5 w-1.5 rounded-full bg-current opacity-30"></div>
<div class="absolute right-8 top-1/2 h-1 w-1 rounded-full bg-current opacity-25"></div>
</div>
<!-- 手动模式装饰:线条 -->
<div v-else-if="item.mode === 'manual'" class="manual-decoration">
<svg
class="absolute inset-0 h-full w-full opacity-5"
xmlns="http://www.w3.org/2000/svg"
>
<defs>
<pattern
id="lines-manual"
width="30"
height="30"
patternUnits="userSpaceOnUse"
>
<line
x1="0"
y1="0"
x2="30"
y2="30"
stroke="currentColor"
stroke-width="0.5"
/>
<line
x1="30"
y1="0"
x2="0"
y2="30"
stroke="currentColor"
stroke-width="0.5"
/>
</pattern>
</defs>
<rect width="100%" height="100%" fill="url(#lines-manual)" />
</svg>
<!-- 几何图形 -->
<div class="absolute right-6 top-6 h-8 w-8 rotate-45 border border-current opacity-10"></div>
<div class="absolute bottom-8 left-8 h-6 w-6 rounded-full border border-current opacity-15"></div>
</div>
<!-- AI模式装饰:点阵 -->
<div v-else-if="item.mode === 'ai'" class="ai-decoration">
<svg
class="absolute inset-0 h-full w-full opacity-5"
xmlns="http://www.w3.org/2000/svg"
>
<defs>
<pattern
id="dots-ai"
width="25"
height="25"
patternUnits="userSpaceOnUse"
>
<circle cx="12.5" cy="12.5" r="1.5" fill="currentColor" />
</pattern>
</defs>
<rect width="100%" height="100%" fill="url(#dots-ai)" />
</svg>
<!-- 星形装饰 -->
<div class="absolute right-5 top-5">
<svg
width="16"
height="16"
viewBox="0 0 16 16"
class="opacity-20"
fill="currentColor"
>
<path
d="M8 0L9.8 5.5L15.5 5.5L10.9 8.9L12.7 14.4L8 11L3.3 14.4L5.1 8.9L0.5 5.5L6.2 5.5L8 0Z"
/>
</svg>
</div>
<div class="absolute bottom-7 left-7">
<svg
width="12"
height="12"
viewBox="0 0 16 16"
class="opacity-15"
fill="currentColor"
>
<path
d="M8 0L9.8 5.5L15.5 5.5L10.9 8.9L12.7 14.4L8 11L3.3 14.4L5.1 8.9L0.5 5.5L6.2 5.5L8 0Z"
/>
</svg>
</div>
</div>
</div>
<!-- 内容 -->
<div class="relative z-10 flex h-full flex-col items-center justify-center text-center">
<!-- 图标容器 -->
<div
class="mb-6 flex h-20 w-20 items-center justify-center rounded-2xl transition-all duration-300 group-hover:scale-110 group-hover:shadow-lg"
:style="{
backgroundColor: item.iconColor + '15',
border: `2px solid ${item.iconColor}40`,
}"
>
<!-- JSON图标 -->
2025-11-28 14:10:43 +08:00
<svg v-if="item.mode === 'json'" xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 32 32"><path fill="#f5de19" d="M4.014 14.976a2.5 2.5 0 0 0 1.567-.518a2.38 2.38 0 0 0 .805-1.358a15.3 15.3 0 0 0 .214-2.944q.012-2.085.075-2.747a5.2 5.2 0 0 1 .418-1.686a3 3 0 0 1 .755-1.018A3.05 3.05 0 0 1 9 4.125A6.8 6.8 0 0 1 10.544 4h.7v1.96h-.387a2.34 2.34 0 0 0-1.723.468a3.4 3.4 0 0 0-.425 2.092a36 36 0 0 1-.137 4.133a4.7 4.7 0 0 1-.768 2.06A4.6 4.6 0 0 1 6.1 16a3.8 3.8 0 0 1 1.992 1.754a8.9 8.9 0 0 1 .618 3.865q0 2.435.05 2.9a1.76 1.76 0 0 0 .504 1.181a2.64 2.64 0 0 0 1.592.337h.387V28h-.7a5.7 5.7 0 0 1-1.773-.2a2.97 2.97 0 0 1-1.324-.93a3.35 3.35 0 0 1-.681-1.63a24 24 0 0 1-.165-3.234a16.5 16.5 0 0 0-.214-3.106a2.4 2.4 0 0 0-.805-1.361a2.5 2.5 0 0 0-1.567-.524Zm23.972 2.035a2.5 2.5 0 0 0-1.567.524a2.4 2.4 0 0 0-.805 1.361a16.5 16.5 0 0 0-.212 3.109a24 24 0 0 1-.169 3.234a3.35 3.35 0 0 1-.681 1.63a2.97 2.97 0 0 1-1.324.93a5.7 5.7 0 0 1-1.773.2h-.7V26.04h.387a2.64 2.64 0 0 0 1.592-.337a1.76 1.76 0 0 0 .506-1.186q.05-.462.05-2.9a8.9 8.9 0 0 1 .618-3.865A3.8 3.8 0 0 1 25.9 16a4.6 4.6 0 0 1-1.7-1.286a4.7 4.7 0 0 1-.768-2.06a36 36 0 0 1-.137-4.133a3.4 3.4 0 0 0-.425-2.092a2.34 2.34 0 0 0-1.723-.468h-.387V4h.7a6.8 6.8 0 0 1 1.54.125a3.05 3.05 0 0 1 1.149.581a3 3 0 0 1 .755 1.018a5.2 5.2 0 0 1 .418 1.686q.062.662.075 2.747a15.3 15.3 0 0 0 .212 2.947a2.38 2.38 0 0 0 .805 1.355a2.5 2.5 0 0 0 1.567.518Z"/></svg>
2025-11-28 10:55:29 +08:00
<!-- 手动填写图标 -->
<svg
v-else-if="item.mode === 'manual'"
width="48"
height="48"
viewBox="0 0 48 48"
fill="none"
xmlns="http://www.w3.org/2000/svg"
:style="{ color: item.iconColor }"
>
<path
d="M24 8L30 14H36C37.1046 14 38 14.8954 38 16V36C38 37.1046 37.1046 38 36 38H12C10.8954 38 10 37.1046 10 36V12C10 10.8954 10.8954 10 12 10H18L24 8Z"
fill="currentColor"
fill-opacity="0.1"
/>
<path
d="M24 8V14H30"
stroke="currentColor"
stroke-width="2.5"
stroke-linecap="round"
stroke-linejoin="round"
/>
<path
d="M16 22H32M16 28H28M16 34H24"
stroke="currentColor"
stroke-width="2.5"
stroke-linecap="round"
/>
<path
d="M20 18C20 19.1046 19.1046 20 18 20C16.8954 20 16 19.1046 16 18C16 16.8954 16.8954 16 18 16C19.1046 16 20 16.8954 20 18Z"
fill="currentColor"
/>
</svg>
<!-- AI图标 -->
<svg
v-else-if="item.mode === 'ai'"
width="48"
height="48"
viewBox="0 0 48 48"
fill="none"
xmlns="http://www.w3.org/2000/svg"
:style="{ color: item.iconColor }"
>
<circle
cx="24"
cy="24"
r="16"
fill="currentColor"
fill-opacity="0.1"
/>
<path
d="M20 18C20 19.1046 19.1046 20 18 20C16.8954 20 16 19.1046 16 18C16 16.8954 16.8954 16 18 16C19.1046 16 20 16.8954 20 18Z"
fill="currentColor"
/>
<path
d="M32 18C32 19.1046 31.1046 20 30 20C28.8954 20 28 19.1046 28 18C28 16.8954 28.8954 16 30 16C31.1046 16 32 16.8954 32 18Z"
fill="currentColor"
/>
<path
d="M24 28C26.2091 28 28 26.2091 28 24C28 21.7909 26.2091 20 24 20C21.7909 20 20 21.7909 20 24C20 26.2091 21.7909 28 24 28Z"
fill="currentColor"
/>
<path
d="M18 32C18 32 20 30 24 30C28 30 30 32 30 32"
stroke="currentColor"
stroke-width="2.5"
stroke-linecap="round"
/>
<path
d="M16 14L18 12M30 12L32 14M32 34L30 36M18 36L16 34"
stroke="currentColor"
stroke-width="2"
stroke-linecap="round"
/>
</svg>
</div>
<!-- 标题 -->
<h3 class="mb-3 text-2xl font-bold text-gray-800 dark:text-gray-200">
{{ item.title }}
</h3>
<!-- 描述 -->
<p class="mb-4 text-gray-600 dark:text-gray-400">
{{ item.description }}
</p>
<!-- 底部提示 -->
<div
class="mt-4 flex translate-y-2 items-center gap-2 text-sm font-medium opacity-0 transition-all duration-300 group-hover:translate-y-0 group-hover:opacity-100"
:style="{ color: item.iconColor }"
>
<span>点击开始</span>
<svg
width="16"
height="16"
viewBox="0 0 16 16"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M6 12L10 8L6 4"
stroke="currentColor"
stroke-width="2"
stroke-linecap="round"
stroke-linejoin="round"
/>
</svg>
</div>
</div>
</div>
</div>
</div>
</template>
<style scoped>
.mode-selector {
padding: 1rem 0;
}
.mode-card {
border: 1px solid rgba(0, 0, 0, 0.06);
border-radius: 16px;
cursor: pointer;
transition: all 0.4s cubic-bezier(0.4, 0, 0.2, 1);
background: #ffffff;
min-height: 320px;
height: 100%;
position: relative;
overflow: hidden;
display: flex;
flex-direction: column;
z-index: 1;
padding: 24px;
box-shadow: 0 1px 2px 0 rgba(0, 0, 0, 0.03), 0 1px 6px -1px rgba(0, 0, 0, 0.02),
0 2px 4px 0 rgba(0, 0, 0, 0.02);
}
/* 暗色模式卡片背景 */
.dark .mode-card {
background: #1f2937;
border-color: rgba(255, 255, 255, 0.1);
}
.mode-card:hover {
transform: translateY(-8px);
box-shadow: 0 20px 40px rgba(0, 0, 0, 0.1);
}
.mode-card:active {
transform: translateY(-4px);
}
/* 流光边框 - 使用圆锥渐变旋转效果 */
.mode-card::before {
content: '';
position: absolute;
inset: -3px;
border-radius: 18px;
padding: 3px;
opacity: 0;
transition: opacity 0.3s ease;
z-index: -1;
pointer-events: none;
background: transparent;
transform-origin: center center;
will-change: transform;
}
.mode-card::after {
content: '';
position: absolute;
inset: 3px;
background: #ffffff;
border-radius: 15px;
z-index: 0;
pointer-events: none;
}
/* 暗色模式遮挡层背景 */
.dark .mode-card::after {
background: #1f2937;
}
.mode-card {
position: relative;
overflow: hidden;
border-radius: 12px;
background: rgba(255, 255, 255, 0.05);
backdrop-filter: blur(10px);
/* 防止闪烁的关键属性 */
transform: translateZ(0);
isolation: isolate;
/* 添加边框防止内容溢出 */
border: 1px solid transparent;
}
/* 修复白色闪烁的关键 - 完全重写 ::before */
.mode-card::before {
content: '';
position: absolute;
inset: -5px; /* 增大边框宽度 */
opacity: 0;
border-radius: 16px; /* 增大圆角匹配边框 */
z-index: -1;
/* 只对opacity进行过渡,避免其他属性变化 */
transition: opacity 0.3s ease;
/* 硬件加速 */
transform: translateZ(0);
backface-visibility: hidden;
perspective: 1000px;
/* 初始状态 - 完全透明但保持渐变结构 */
background:
conic-gradient(
from 0deg at 50% 50%,
transparent 0%,
transparent 20%,
transparent 40%,
transparent 60%,
transparent 80%,
transparent 100%
);
background-size: 300% 300%;
/* 移除所有可能引起闪烁的滤镜 */
filter: none;
}
.mode-card:hover::before {
2025-11-28 13:31:21 +08:00
/*opacity: 1;
!*animation:
2025-11-28 10:55:29 +08:00
rotate-glow 2.5s linear infinite,
2025-11-28 13:31:21 +08:00
pulse-glow 4s ease-in-out infinite;*!
!* 悬停时才应用渐变和滤镜 *!
2025-11-28 10:55:29 +08:00
background:
radial-gradient(circle at 30% 30%, rgba(255,255,255,0.8) 0%, transparent 50%),
radial-gradient(circle at 70% 70%, rgba(255,255,255,0.6) 0%, transparent 50%),
conic-gradient(
from 0deg at 50% 50%,
var(--color-start, #6366f1) 0%,
var(--color-mid, #8b5cf6) 20%,
var(--color-end, #a855f7) 40%,
var(--color-mid, #8b5cf6) 60%,
var(--color-start, #6366f1) 80%,
var(--color-start, #6366f1) 100%
);
2025-11-28 13:31:21 +08:00
filter: blur(12px) brightness(1.3);*/
2025-11-28 10:55:29 +08:00
}
/* 颜色定义 */
.mode-card[data-mode='json'] {
--color-start: #6366f1;
--color-mid: #8b5cf6;
--color-end: #a855f7;
}
.mode-card[data-mode='manual'] {
--color-start: #f59e0b;
--color-mid: #f97316;
--color-end: #fb923c;
}
.mode-card[data-mode='ai'] {
--color-start: #10b981;
--color-mid: #059669;
--color-end: #34d399;
}
/* 旋转动画 - 优化性能 */
@keyframes rotate-glow {
0% {
transform: translateZ(0) rotate(0deg);
background-position: 0% 0%, 0% 0%, 0% 50%;
}
25% {
background-position: 100% 0%, 0% 100%, 25% 75%;
}
50% {
transform: translateZ(0) rotate(180deg);
background-position: 100% 100%, 100% 100%, 50% 100%;
}
75% {
background-position: 0% 100%, 100% 0%, 75% 75%;
}
100% {
transform: translateZ(0) rotate(360deg);
background-position: 0% 0%, 0% 0%, 100% 50%;
}
}
/* 脉动光晕 - 简化避免闪烁 */
@keyframes pulse-glow {
0%, 100% {
filter: blur(12px) brightness(1.3) saturate(1.2);
}
50% {
filter: blur(14px) brightness(1.5) saturate(1.4);
}
}
/* 内部光效 - 完全重写避免闪烁 */
.mode-card::after {
content: '';
position: absolute;
inset: 2px; /* 调整内部间距 */
border-radius: 12px;
background: transparent;
opacity: 0;
transition: opacity 0.4s ease 0.1s;
z-index: -1;
pointer-events: none;
/* 使用box-shadow替代背景渐变 */
box-shadow:
inset 0 0 20px rgba(255, 255, 255, 0.1),
inset 0 0 30px rgba(255, 255, 255, 0.05);
}
.mode-card:hover::after {
opacity: 1;
/* 简化动画,只做微妙的脉动 */
animation: inner-pulse 3s ease-in-out infinite;
}
@keyframes inner-pulse {
0%, 100% {
box-shadow:
inset 0 0 20px rgba(255, 255, 255, 0.1),
inset 0 0 30px rgba(255, 255, 255, 0.05);
}
50% {
box-shadow:
inset 0 0 25px rgba(255, 255, 255, 0.15),
inset 0 0 35px rgba(255, 255, 255, 0.08);
}
}
/* 添加一个额外的容器层确保内容在光效之上 */
.mode-card > * {
position: relative;
z-index: 1;
}
/* 性能优化 */
@media (prefers-reduced-motion: reduce) {
.mode-card:hover::before {
animation:
rotate-glow 6s linear infinite,
pulse-glow 8s ease-in-out infinite;
}
}
/* 针对某些浏览器的额外修复 */
@supports not (backdrop-filter: blur(10px)) {
.mode-card {
background: rgba(255, 255, 255, 0.02);
}
}
/* 装饰元素 */
.mode-card-decoration {
position: absolute;
inset: 0;
pointer-events: none;
z-index: 2;
color: currentColor;
}
.json-decoration,
.manual-decoration,
.ai-decoration {
position: absolute;
inset: 0;
width: 100%;
height: 100%;
}
.enter-y {
opacity: 0;
animation: enter-y-animation 0.6s cubic-bezier(0.4, 0, 0.2, 1) forwards;
}
@keyframes enter-y-animation {
from {
opacity: 0;
transform: translateY(30px) scale(0.95);
}
to {
opacity: 1;
transform: translateY(0) scale(1);
}
}
.dark .mode-card:hover {
box-shadow: 0 20px 40px rgba(0, 0, 0, 0.3);
}
.dark .mode-card-border-inner {
background: rgba(255, 255, 255, 0.02);
}
/* 响应式优化 */
@media (max-width: 768px) {
.mode-card {
min-height: 280px;
}
.mode-card:hover {
transform: translateY(-4px);
}
}
</style>