deotalandAi/apps/frontend/src/views/ModernHome/ModernHome.vue

947 lines
20 KiB
Vue

<template>
<div class="modern-home">
<!-- 欢迎区域 -->
<section class="welcome-section">
<div class="welcome-content">
<div class="welcome-left">
<div class="welcome-logo">
<img src="@/assets/logo.webp" alt="Logo" class="welcome-logo-image" />
<div class="logo-glow"></div>
</div>
<h1 class="welcome-title">
<span class="greeting" v-if="isLoggedIn">{{ t('home.welcome.title', { name: userName }) }}</span>
<span class="greeting" v-else>{{ t('home.welcome.title', { name: t('home.welcome.defaultName') }) }}</span>
</h1>
<p class="welcome-subtitle">
{{ t('home.welcome.greetingMessage') }}
</p>
<div class="welcome-actions">
<el-button
v-if="isLoggedIn"
type="primary"
size="large"
class="action-btn primary-btn create-btn-large"
@click="navigateToFeature({ path: `/project/new/Done` })">
{{ t('home.welcome.startCreating') }}
</el-button>
<div v-else class="guest-actions">
<el-button
type="primary"
size="large"
class="action-btn primary-btn create-btn-large"
@click="navigateToFeature({ path: '/login' })">
{{ t('home.welcome.loginToStart') }}
</el-button>
<el-button
size="large"
class="action-btn secondary-btn"
@click="navigateToFeature({ path: '/register' })">
{{ t('home.welcome.register') }}
</el-button>
</div>
</div>
</div>
<div class="welcome-right">
<div class="welcome-visual">
<div class="animated-bg">
<div class="bg-circle circle-1"></div>
<div class="bg-circle circle-2"></div>
<div class="bg-circle circle-3"></div>
<div class="bg-circle circle-4"></div>
<div class="bg-circle circle-5"></div>
</div>
<div class="floating-cards">
<div class="floating-card card-1" @click="navigateToFeature({ path: '/order-management' })">
<el-icon><Tickets /></el-icon>
<span>{{ t('home.floatingCards.orders') }}</span>
</div>
<div class="floating-card card-2" @click="navigateToFeature({ path: '/agent-management' })">
<el-icon><Cpu /></el-icon>
<span>{{ t('home.floatingCards.settings') }}</span>
</div>
<div class="floating-card card-3" @click="navigateToFeature({ path: '/creation-workspace' })">
<el-icon><Picture /></el-icon>
<span>{{ t('sidebar.projects') }}</span>
</div>
</div>
<div class="welcome-avatar">
<el-avatar :size="100" :src="userAvatar" v-if="isLoggedIn">
<el-icon size="50"><User /></el-icon>
</el-avatar>
<div v-else class="guest-avatar" @click="navigateToFeature({ path: '/login' })">
<el-icon size="50"><User /></el-icon>
<div class="guest-login-tip">{{ t('home.welcome.clickToLogin') }}</div>
</div>
</div>
</div>
</div>
</div>
</section>
<!-- 统计卡片区 -->
<section class="stats-section">
<div class="stats-container">
<div
v-for="(stat, index) in statsData"
:key="stat.id"
class="stat-card"
:style="{ '--delay': index * 0.1 + 's' }"
>
<div class="stat-icon" :style="{ '--icon-color': stat.color }">
<component :is="iconComponents[stat.icon]" />
</div>
<div class="stat-content">
<div class="stat-value">
<count-up :end-val="stat.value" :duration="2" />
<span class="stat-unit">{{ stat.unit }}</span>
</div>
<div class="stat-label">{{ stat.label }}</div>
</div>
<!-- <div class="stat-trend" :class="stat.trend">
<component :is="iconComponents[stat.trendIcon]" />
<span>{{ stat.trendValue }}</span>
</div> -->
</div>
</div>
</section>
</div>
</template>
<script setup>
import { computed, onMounted } from 'vue'
import { useRouter } from 'vue-router'
import { useI18n } from 'vue-i18n'
import { useAuthStore } from '@/stores/auth'
import CountUp from 'vue-countup-v3'
import { ModernHome } from './index.js'
import { dateUtils } from '@deotaland/utils';
const modernHome = new ModernHome();
// 图标组件
import {
User,
Star,
Clock,
Document,
MagicStick,
ArrowUp,
ArrowDown,
Minus,
VideoPlay,
ChatDotRound,
Tickets,
Setting,
Picture,
Cpu
} from '@element-plus/icons-vue'
const { t } = useI18n()
const router = useRouter()
const authStore = useAuthStore()
// 创建图标组件映射
const iconComponents = {
MagicStick,
Star,
Clock,
Document,
ArrowUp,
ArrowDown,
Minus,
VideoPlay,
ChatDotRound,
Tickets,
Setting,
Picture,
Cpu
}
// 响应式数据 - 使用计算属性来支持国际化
const statsData = computed(() => [
// {
// id: 'creations',
// value: homeData.value.agent_count,
// unit: '',
// label: t('home.stats.creations'),
// icon: 'MagicStick',
// color: '#6B46C1',
// trend: 'up',
// trendIcon: 'ArrowUp',
// trendValue: '12%'
// },
// {
// id: 'credits',
// value: 2840,
// unit: '',
// label: t('home.stats.credits'),
// icon: 'Star',
// color: '#F59E0B',
// trend: 'up',
// trendIcon: 'ArrowUp',
// trendValue: '5%'
// },
{
id: 'hours',
value: dateUtils.secondsToTime(homeData.value.total_duration_seconds),
unit: 'm',
label: t('home.stats.hours'),
icon: 'Clock',
color: '#10B981',
trend: 'down',
trendIcon: 'ArrowDown',
trendValue: '8%'
},
{
id: 'projects',
value: homeData.value.project_count,
unit: '',
label: t('home.stats.projects'),
icon: 'Document',
color: '#3B82F6',
trend: 'stable',
trendIcon: 'Minus',
trendValue: '0%'
}
])
// 计算属性
const currentUser = computed(() => authStore.user)
const userName = computed(() => currentUser.value?.nickname || '')
const userAvatar = computed(() => currentUser.value?.avatarUrl || '')
const isLoggedIn = computed(() => authStore.token)
// 方法
const navigateToFeature = (feature) => {
router.push(feature.path)
}
const homeData = ref({
total_duration_seconds: 0,
project_count: 0,
agent_count: 0,
});
const init = () => {
modernHome.getUserStatistics().then(res => {
if (res.code === 0) {
homeData.value = res.data;
}
});
// orderManagement.orderStatistics().then(res => {
// if (res.code === 0) {
// }
// });
}
onMounted(() => {
let token = window.localStorage.getItem('token');
token&&init();
})
</script>
<style scoped>
.modern-home {
padding: 24px;
background: var(--bg-color, #f9fafb);
/* min-height: 100vh; */
}
/* 欢迎区域 */
.welcome-section {
position: relative;
padding: 80px 24px;
background: linear-gradient(135deg,
#6B46C1 0%,
#8B5CF6 25%,
#A78BFA 50%);
background-size: 100% 100%;
overflow: hidden;
border-radius: 24px;
box-shadow:
0 20px 60px rgba(107, 70, 193, 0.3),
0 8px 24px rgba(107, 70, 193, 0.2),
0 4px 12px rgba(107, 70, 193, 0.1),
inset 0 1px 0 rgba(255, 255, 255, 0.2);
border: 1px solid rgba(255, 255, 255, 0.1);
backdrop-filter: blur(12px);
transition: all 0.4s cubic-bezier(0.25, 0.8, 0.25, 1);
}
.welcome-section:hover {
transform: translateY(-4px);
box-shadow:
0 32px 80px rgba(107, 70, 193, 0.4),
0 12px 32px rgba(107, 70, 193, 0.25),
0 6px 16px rgba(107, 70, 193, 0.15),
inset 0 1px 0 rgba(255, 255, 255, 0.3);
}
.welcome-section::before {
content: '';
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
background:
radial-gradient(circle at 25% 25%, rgba(255, 255, 255, 0.1) 0%, transparent 50%),
radial-gradient(circle at 75% 75%, rgba(255, 255, 255, 0.05) 0%, transparent 50%),
radial-gradient(circle at 40% 60%, rgba(255, 255, 255, 0.05) 0%, transparent 50%);
}
/* 暗色主题支持 */
.dark .welcome-section {
background: linear-gradient(135deg,
#1a1625 0%,
#2d1b3d 25%,
#3730a3 50%,
#3730a3 75%,
#1e1b4b 100%);
box-shadow:
0 20px 60px rgba(0, 0, 0, 0.6),
0 8px 24px rgba(0, 0, 0, 0.4),
0 4px 12px rgba(0, 0, 0, 0.3),
inset 0 1px 0 rgba(255, 255, 255, 0.05);
border-color: rgba(255, 255, 255, 0.05);
}
.dark .welcome-section:hover {
box-shadow:
0 32px 80px rgba(0, 0, 0, 0.7),
0 12px 32px rgba(0, 0, 0, 0.5),
0 6px 16px rgba(0, 0, 0, 0.4),
inset 0 1px 0 rgba(255, 255, 255, 0.08);
}
.welcome-content {
max-width: 1200px;
margin: 0 auto;
display: grid;
grid-template-columns: 1fr 1fr;
gap: 60px;
align-items: center;
position: relative;
z-index: 1;
}
.welcome-left {
max-width: 500px;
}
.welcome-logo {
position: relative;
width: 120px;
height: 120px;
margin-bottom: 32px;
animation: logoFloat 6s ease-in-out infinite;
}
.welcome-logo-image {
width: 100%;
height: 100%;
object-fit: contain;
border-radius:50%;
position: relative;
z-index: 2;
}
.logo-glow {
position: absolute;
top: -20px;
left: -20px;
right: -20px;
bottom: -20px;
background: rgba(255, 255, 255, 0.2);
border-radius: 50%;
animation: glowPulse 3s ease-in-out infinite;
z-index: 1;
}
.welcome-title {
font-size: 2.5rem;
font-weight: 700;
margin: 0 0 16px 0;
line-height: 1.2;
/* 初始隐藏状态 */
opacity: 0;
transform: translateY(30px);
animation: fadeInUp 0.8s ease-out both;
color: #ffffff;
}
.greeting {
background: linear-gradient(45deg, #ffffff, #F3F4F6);
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
background-clip: text;
}
.welcome-subtitle {
font-size: 1.125rem;
opacity: 0.9;
margin: 0 0 32px 0;
line-height: 1.6;
/* 初始隐藏状态 */
opacity: 0;
transform: translateY(30px);
animation: fadeInUp 0.8s ease-out 0.2s both;
color: rgba(255, 255, 255, 0.9);
}
.welcome-actions {
display: flex;
gap: 16px;
flex-wrap: wrap;
/* 初始隐藏状态 */
opacity: 0;
transform: translateY(30px);
animation: fadeInUp 0.8s ease-out 0.4s both;
}
.action-btn {
display: flex;
align-items: center;
gap: 8px;
padding: 12px 24px;
border-radius: 12px;
font-weight: 600;
transition: all 0.3s ease;
position: relative;
overflow: hidden;
/* 拆件动画 */
opacity: 0;
transform: translateY(40px);
animation: buttonFloatIn 0.6s ease-out 0.2s forwards;
}
.action-btn::before {
content: '';
position: absolute;
top: 0;
left: -100%;
width: 100%;
height: 100%;
background: linear-gradient(90deg, transparent, rgba(255, 255, 255, 0.2), transparent);
transition: left 0.5s;
}
.action-btn:hover::before {
left: 100%;
}
.primary-btn {
background: rgba(255, 255, 255, 0.2);
border: 2px solid rgba(255, 255, 255, 0.3);
color: #ffffff;
backdrop-filter: blur(10px);
}
.primary-btn:hover {
background: rgba(255, 255, 255, 0.3);
border-color: rgba(255, 255, 255, 0.5);
transform: translateY(-2px);
box-shadow: 0 10px 30px rgba(0, 0, 0, 0.2);
}
.create-btn-large {
font-size: 18px !important;
padding: 16px 32px !important;
min-width: 200px !important;
height: 56px !important;
}
.create-btn-large:hover {
transform: translateY(-3px) scale(1.02) !important;
box-shadow: 0 15px 40px rgba(0, 0, 0, 0.25) !important;
}
.secondary-btn {
background: transparent;
border: 2px solid rgba(255, 255, 255, 0.3);
color: #ffffff;
}
.secondary-btn:hover {
background: rgba(255, 255, 255, 0.1);
border-color: rgba(255, 255, 255, 0.5);
transform: translateY(-2px);
}
.welcome-right {
position: relative;
}
.welcome-visual {
position: relative;
width: 100%;
height: 400px;
display: flex;
align-items: center;
justify-content: center;
}
.animated-bg {
position: absolute;
width: 100%;
height: 100%;
top: 0;
left: 0;
}
.bg-circle {
position: absolute;
border-radius: 50%;
background: rgba(255, 255, 255, 0.1);
backdrop-filter: blur(10px);
border: 1px solid rgba(255, 255, 255, 0.2);
/* 拆件动画 */
opacity: 0;
transform: translateY(30px) scale(0.8);
animation: circleFloatIn 0.8s ease-out forwards;
}
.circle-1 {
width: 120px;
height: 120px;
top: 10%;
left: 20%;
animation: circleFloatIn 0.8s ease-out 0.1s forwards, circleFloat1 8s ease-in-out infinite 0.8s;
}
.circle-2 {
width: 80px;
height: 80px;
top: 60%;
left: 70%;
animation: circleFloatIn 0.8s ease-out 0.3s forwards, circleFloat2 10s ease-in-out infinite 1.0s;
}
.circle-3 {
width: 60px;
height: 60px;
top: 30%;
left: 10%;
animation: circleFloatIn 0.8s ease-out 0.5s forwards, circleFloat3 12s ease-in-out infinite 1.2s;
}
.circle-4 {
width: 100px;
height: 100px;
top: 70%;
left: 20%;
animation: circleFloatIn 0.8s ease-out 0.7s forwards, circleFloat4 9s ease-in-out infinite 1.4s;
}
.circle-5 {
width: 40px;
height: 40px;
top: 50%;
left: 60%;
animation: circleFloatIn 0.8s ease-out 0.9s forwards, circleFloat5 7s ease-in-out infinite 1.6s;
}
.floating-cards {
position: relative;
z-index: 3;
width: 100%;
height: 100%;
}
.floating-card {
position: absolute;
display: flex;
align-items: center;
gap: 8px;
padding: 12px 16px;
background: rgba(255, 255, 255, 0.15);
backdrop-filter: blur(20px);
border: 1px solid rgba(255, 255, 255, 0.2);
border-radius: 12px;
color: white;
font-size: 14px;
font-weight: 500;
animation: cardFloat 6s ease-in-out infinite;
cursor: pointer;
transition: all 0.3s ease;
/* 初始隐藏状态 */
opacity: 0;
transform: translateY(30px);
}
.floating-card:hover {
background: rgba(255, 255, 255, 0.25);
border-color: rgba(255, 255, 255, 0.4);
transform: translateY(-8px) scale(1.05);
box-shadow: 0 15px 35px rgba(0, 0, 0, 0.2);
}
.floating-card:active {
transform: translateY(-4px) scale(1.02);
transition: all 0.15s ease;
}
.card-1 {
top: 20%;
left: 10%;
animation: cardFloatIn 0.6s ease-out 0.4s forwards, cardFloat 6s ease-in-out infinite 1.0s;
}
.card-2 {
top: 60%;
right: 15%;
animation: cardFloatIn 0.6s ease-out 0.6s forwards, cardFloat 6s ease-in-out infinite 1.2s;
}
.card-3 {
bottom: 30%;
left: 20%;
animation: cardFloatIn 0.6s ease-out 0.8s forwards, cardFloat 6s ease-in-out infinite 1.4s;
}
.welcome-avatar {
position: relative;
z-index: 4;
/* 初始隐藏状态 */
opacity: 0;
transform: translateY(30px);
animation: fadeInUp 0.8s ease-out 1.0s both;
}
.guest-avatar {
width: 100px;
height: 100px;
border-radius: 50%;
background: rgba(255, 255, 255, 0.2);
backdrop-filter: blur(10px);
border: 2px solid rgba(255, 255, 255, 0.3);
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
cursor: pointer;
transition: all 0.3s ease;
position: relative;
color: rgba(255, 255, 255, 0.8);
}
.guest-avatar:hover {
background: rgba(255, 255, 255, 0.3);
border-color: rgba(255, 255, 255, 0.5);
transform: scale(1.05);
box-shadow: 0 10px 30px rgba(0, 0, 0, 0.2);
}
.guest-login-tip {
font-size: 10px;
color: rgba(255, 255, 255, 0.8);
margin-top: 4px;
text-align: center;
line-height: 1.2;
}
.guest-actions {
display: flex;
gap: 16px;
flex-wrap: wrap;
}
/* 响应式设计 */
@media (max-width: 768px) {
.welcome-content {
grid-template-columns: 1fr;
gap: 40px;
text-align: center;
}
.welcome-title {
font-size: 2rem;
}
.welcome-actions {
justify-content: center;
}
.action-btn {
flex: 1;
min-width: 120px;
}
.welcome-visual {
height: 300px;
}
}
/* 统计卡片区 */
.stats-section {
padding: 40px 0px;
width: 100%;
box-sizing: border-box;
}
.stats-container {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
gap: 24px;
}
.stat-card {
background: white;
border-radius: 16px;
padding: 24px;
box-shadow: 0 4px 20px rgba(0, 0, 0, 0.05);
display: flex;
align-items: center;
gap: 16px;
position: relative;
overflow: hidden;
transition: all 0.3s ease;
animation: fadeInUp 0.6s ease-out both;
animation-delay: var(--delay);
}
.stat-card:hover {
transform: translateY(-5px);
box-shadow: 0 12px 30px rgba(0, 0, 0, 0.1);
}
.stat-card::before {
content: '';
position: absolute;
top: 0;
left: 0;
right: 0;
height: 4px;
background: var(--icon-color);
}
.stat-icon {
width: 60px;
height: 60px;
border-radius: 12px;
display: flex;
align-items: center;
justify-content: center;
background: rgba(var(--icon-color), 0.1);
color: var(--icon-color);
font-size: 24px;
}
.stat-content {
flex: 1;
}
.stat-value {
font-size: 2rem;
font-weight: 700;
color: var(--text-primary, #1f2937);
margin-bottom: 4px;
display: flex;
align-items: baseline;
gap: 4px;
}
.stat-unit {
font-size: 1rem;
font-weight: 400;
color: var(--text-secondary, #6b7280);
}
.stat-label {
font-size: 0.875rem;
color: var(--text-secondary, #6b7280);
}
.stat-trend {
display: flex;
flex-direction: column;
align-items: flex-end;
gap: 4px;
font-size: 0.875rem;
font-weight: 600;
}
.stat-trend.up {
color: #10B981;
}
.stat-trend.down {
color: #EF4444;
}
.stat-trend.stable {
color: #6B7280;
}
/* 动画 */
@keyframes fadeInUp {
from {
opacity: 0;
transform: translateY(30px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
@keyframes circleFloatIn {
from {
opacity: 0;
transform: translateY(30px) scale(0.8);
}
to {
opacity: 1;
transform: translateY(0) scale(1);
}
}
@keyframes buttonFloatIn {
from {
opacity: 0;
transform: translateY(40px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
@keyframes cardFloatIn {
from {
opacity: 0;
transform: translateY(30px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
@keyframes float {
0%, 100% {
transform: translateY(0);
}
50% {
transform: translateY(-15px);
}
}
@keyframes gradientShift {
0% {
background-position: 0% 50%;
}
25% {
background-position: 100% 25%;
}
50% {
background-position: 100% 100%;
}
75% {
background-position: 0% 75%;
}
100% {
background-position: 0% 50%;
}
}
@keyframes backgroundFloat {
0%, 100% {
transform: translate(0, 0) rotate(0deg);
opacity: 0.8;
}
25% {
transform: translate(10px, -15px) rotate(2deg);
opacity: 1;
}
50% {
transform: translate(-5px, 10px) rotate(-1deg);
opacity: 0.9;
}
75% {
transform: translate(15px, 5px) rotate(1deg);
opacity: 0.95;
}
}
@keyframes backgroundPulse {
0%, 100% {
transform: scale(1) rotate(0deg);
opacity: 0.6;
}
33% {
transform: scale(1.05) rotate(1deg);
opacity: 0.8;
}
66% {
transform: scale(0.95) rotate(-1deg);
opacity: 0.7;
}
}
@keyframes logoFloat {
0%, 100% {
transform: translateY(0px);
}
50% {
transform: translateY(-8px);
}
}
@keyframes glowPulse {
0%, 100% {
transform: scale(1);
opacity: 0.3;
}
50% {
transform: scale(1.1);
opacity: 0.6;
}
}
/* 深色主题 */
.dark .modern-home {
--bg-color: #111827;
--text-primary: #f9fafb;
--text-secondary: #d1d5db;
--border-color: #374151;
}
.dark .stat-card {
background: #1f2937;
box-shadow: 0 4px 20px rgba(0, 0, 0, 0.2);
}
.dark .stat-card:hover {
box-shadow: 0 12px 30px rgba(0, 0, 0, 0.3);
}
/* 响应式设计 */
@media (max-width: 768px) {
.welcome-content {
grid-template-columns: 1fr;
gap: 40px;
text-align: center;
}
.welcome-title {
font-size: 2rem;
}
.welcome-actions {
justify-content: center;
}
.action-btn {
flex: 1;
min-width: 120px;
}
.welcome-visual {
height: 300px;
}
.stats-container {
grid-template-columns: 1fr;
}
}
</style>