Browse Source

'第二版本'

hf;;
hflllll 6 months ago
parent
commit
ec22a24d8b
34 changed files with 4157 additions and 1573 deletions
  1. +39
    -0
      package-lock.json
  2. +3
    -0
      package.json
  3. BIN
      public/图.jpg
  4. +14
    -0
      src/api/modules/about.ts
  5. +6
    -0
      src/api/modules/community.ts
  6. +4
    -0
      src/api/modules/home.ts
  7. +63
    -67
      src/components/about/CompanyModule.vue
  8. +275
    -65
      src/components/about/MilestoneModule.vue
  9. +61
    -191
      src/components/about/PartnersModule.vue
  10. +96
    -97
      src/components/about/TeamModule.vue
  11. +205
    -42
      src/components/home/CoreValuesModule.vue
  12. +211
    -51
      src/components/home/MediaModule.vue
  13. +197
    -108
      src/components/home/NewsModule.vue
  14. +150
    -86
      src/components/home/PartnersModule.vue
  15. +186
    -71
      src/components/home/PostsModule.vue
  16. +284
    -27
      src/components/home/ProjectIntroModule.vue
  17. +2
    -2
      src/components/layout/NavBar.vue
  18. +82
    -0
      src/env.d.ts
  19. +17
    -13
      src/i18n/locales/en.json
  20. +6
    -2
      src/i18n/locales/zh.json
  21. +11
    -0
      src/main.ts
  22. +40
    -0
      src/types/global.d.ts
  23. +36
    -0
      src/types/module.d.ts
  24. +12
    -0
      src/types/swiper.d.ts
  25. +553
    -0
      src/types/vue.d.ts
  26. +20
    -0
      src/types/wow.d.ts
  27. +10
    -10
      src/views/About.vue
  28. +291
    -157
      src/views/Community.vue
  29. +493
    -263
      src/views/Ecosystem.vue
  30. +121
    -306
      src/views/Home.vue
  31. +550
    -11
      src/views/Technology.vue
  32. +113
    -0
      src/views/二次开发要求.txt
  33. +5
    -3
      tsconfig.json
  34. +1
    -1
      tsconfig.tsbuildinfo

+ 39
- 0
package-lock.json View File

@ -12,8 +12,10 @@
"@tailwindcss/vite": "^4.1.11",
"@videojs-player/vue": "^1.0.0",
"animate.css": "^4.1.1",
"aos": "^2.3.4",
"axios": "^1.10.0",
"dayjs": "^1.11.13",
"gsap": "^3.13.0",
"swiper": "^11.2.10",
"tailwindcss": "^4.1.11",
"video.js": "^7.21.7",
@ -24,6 +26,7 @@
"wowjs": "^1.1.3"
},
"devDependencies": {
"@types/aos": "^3.0.7",
"@types/node": "^24.0.13",
"@vitejs/plugin-vue": "^5.2.1",
"@vue/tsconfig": "^0.7.0",
@ -1251,6 +1254,12 @@
"vite": "^5.2.0 || ^6 || ^7"
}
},
"node_modules/@types/aos": {
"version": "3.0.7",
"resolved": "https://registry.npmmirror.com/@types/aos/-/aos-3.0.7.tgz",
"integrity": "sha512-sEhyFqvKauUJZDbvAB3Pggynrq6g+2PS4XB3tmUr+mDL1gfDJnwslUC4QQ7/l8UD+LWpr3RxZVR/rHoZrLqZVg==",
"dev": true
},
"node_modules/@types/cookie": {
"version": "0.6.0",
"resolved": "https://registry.npmmirror.com/@types/cookie/-/cookie-0.6.0.tgz",
@ -1613,6 +1622,16 @@
"url": "https://github.com/chalk/ansi-styles?sponsor=1"
}
},
"node_modules/aos": {
"version": "2.3.4",
"resolved": "https://registry.npmmirror.com/aos/-/aos-2.3.4.tgz",
"integrity": "sha512-zh/ahtR2yME4I51z8IttIt4lC1Nw0ktsFtmeDzID1m9naJnWXhCoARaCgNOGXb5CLy3zm+wqmRAEgMYB5E2HUw==",
"dependencies": {
"classlist-polyfill": "^1.0.3",
"lodash.debounce": "^4.0.6",
"lodash.throttle": "^4.0.1"
}
},
"node_modules/array-union": {
"version": "2.1.0",
"resolved": "https://registry.npmmirror.com/array-union/-/array-union-2.1.0.tgz",
@ -1690,6 +1709,11 @@
"node": ">=18"
}
},
"node_modules/classlist-polyfill": {
"version": "1.2.0",
"resolved": "https://registry.npmmirror.com/classlist-polyfill/-/classlist-polyfill-1.2.0.tgz",
"integrity": "sha512-GzIjNdcEtH4ieA2S8NmrSxv7DfEV5fmixQeyTmqmRmRJPGpRBaSnA2a0VrCjyT8iW8JjEdMbKzDotAJf+ajgaQ=="
},
"node_modules/cli-width": {
"version": "4.1.0",
"resolved": "https://registry.npmmirror.com/cli-width/-/cli-width-4.1.0.tgz",
@ -2278,6 +2302,11 @@
"node": "^12.22.0 || ^14.16.0 || ^16.0.0 || >=17.0.0"
}
},
"node_modules/gsap": {
"version": "3.13.0",
"resolved": "https://registry.npmmirror.com/gsap/-/gsap-3.13.0.tgz",
"integrity": "sha512-QL7MJ2WMjm1PHWsoFrAQH/J8wUeqZvMtHO58qdekHpCfhvhSL4gSiz6vJf5EeMP0LOn3ZCprL2ki/gjED8ghVw=="
},
"node_modules/has-symbols": {
"version": "1.1.0",
"resolved": "https://registry.npmmirror.com/has-symbols/-/has-symbols-1.1.0.tgz",
@ -2647,6 +2676,16 @@
"node": ">=8"
}
},
"node_modules/lodash.debounce": {
"version": "4.0.8",
"resolved": "https://registry.npmmirror.com/lodash.debounce/-/lodash.debounce-4.0.8.tgz",
"integrity": "sha512-FT1yDzDYEoYWhnSGnpE/4Kj1fLZkDFyqRb7fNt6FdYOSxlUWAtp42Eh6Wb0rGIv/m9Bgo7x4GhQbm5Ys4SG5ow=="
},
"node_modules/lodash.throttle": {
"version": "4.1.1",
"resolved": "https://registry.npmmirror.com/lodash.throttle/-/lodash.throttle-4.1.1.tgz",
"integrity": "sha512-wIkUCfVKpVsWo3JSZlc+8MB5it+2AN5W8J7YVMST30UrvcQNZ1Okbj+rbVniijTWE6FGYy4XJq/rHkas8qJMLQ=="
},
"node_modules/m3u8-parser": {
"version": "4.8.0",
"resolved": "https://registry.npmmirror.com/m3u8-parser/-/m3u8-parser-4.8.0.tgz",


+ 3
- 0
package.json View File

@ -14,8 +14,10 @@
"@tailwindcss/vite": "^4.1.11",
"@videojs-player/vue": "^1.0.0",
"animate.css": "^4.1.1",
"aos": "^2.3.4",
"axios": "^1.10.0",
"dayjs": "^1.11.13",
"gsap": "^3.13.0",
"swiper": "^11.2.10",
"tailwindcss": "^4.1.11",
"video.js": "^7.21.7",
@ -26,6 +28,7 @@
"wowjs": "^1.1.3"
},
"devDependencies": {
"@types/aos": "^3.0.7",
"@types/node": "^24.0.13",
"@vitejs/plugin-vue": "^5.2.1",
"@vue/tsconfig": "^0.7.0",


BIN
public/图.jpg View File

Before After
Width: 1000  |  Height: 368  |  Size: 20 KiB

+ 14
- 0
src/api/modules/about.ts View File

@ -68,6 +68,20 @@ export interface PartnerItem {
updateTime?: string;
}
// 媒体接口
export interface MediaItem {
id: string;
title: string;
image: string;
description: string;
source?: string;
date?: string;
createBy?: string;
createTime?: string;
updateBy?: string;
updateTime?: string;
}
// 处理API响应
function handleResponse<T>(response: any): T[] {
if (!response) return [];


+ 6
- 0
src/api/modules/community.ts View File

@ -14,6 +14,8 @@ export interface OfficialMediaItem {
description?: string;
image: string;
link: string;
url?: string;
username?: string;
createBy?: string;
createTime?: string;
updateBy?: string;
@ -26,6 +28,7 @@ export interface ForumItem {
title: string;
content: string;
image?: string;
likeCount?: number;
createBy?: string;
createTime?: string;
updateBy?: string;
@ -49,6 +52,7 @@ export interface MessageItem {
title: string;
content: string;
image?: string;
description?: string;
createBy?: string;
createTime?: string;
updateBy?: string;
@ -61,6 +65,8 @@ export interface CommunityItem {
title: string;
content: string;
image?: string;
description?: string;
url?: string;
createBy?: string;
createTime?: string;
updateBy?: string;


+ 4
- 0
src/api/modules/home.ts View File

@ -16,8 +16,10 @@ export interface BannerItem {
// 核心价值主张接口
export interface ValueItem {
id: string;
title: string;
image: string;
description: string;
icon?: string;
createBy?: string;
createTime?: string;
updateBy?: string;
@ -42,6 +44,8 @@ export interface MediaItem {
title: string;
image: string;
description: string;
source?: string;
date?: string;
createBy?: string;
createTime?: string;
updateBy?: string;


+ 63
- 67
src/components/about/CompanyModule.vue View File

@ -1,90 +1,86 @@
<script setup lang="ts">
import { ref } from 'vue';
import { useI18n } from 'vue-i18n';
import { ref, onMounted } from 'vue';
import { queryCompanyList } from '@/api';
import type { CompanyItem } from '@/api';
const { t } = useI18n();
//
const defaultCompanies: CompanyItem[] = [
//
const companyLocations = ref([
{
id: '1',
title: t('about.companies.company1.title'),
description: t('about.companies.company1.description'),
image: '/public/LOGO.png'
id: 1,
city: '香港',
country: '中国',
address: '香港中环金融街88号',
role: '区块链研发中心',
image: '/LOGO.png'
},
{
id: '2',
title: t('about.companies.company2.title'),
description: t('about.companies.company2.description'),
image: '/public/LOGO.png'
id: 2,
city: '新加坡',
country: '新加坡',
address: '新加坡金融区10号',
role: '全球运营总部',
image: '/LOGO.png'
},
{
id: '3',
title: t('about.companies.company3.title'),
description: t('about.companies.company3.description'),
image: '/public/LOGO.png'
}
];
//
const companies = ref<CompanyItem[]>(defaultCompanies);
const loading = ref(false);
const error = ref<string | null>(null);
//
const fetchCompanies = async () => {
loading.value = true;
error.value = null;
try {
const response = await queryCompanyList();
if (response && Array.isArray(response) && response.length > 0) {
companies.value = response;
}
} catch (err) {
console.error('获取公司数据失败:', err);
error.value = '获取公司数据失败';
} finally {
loading.value = false;
id: 3,
city: '伦敦',
country: '英国',
address: '伦敦金融城15号',
role: '欧洲市场拓展中心',
image: '/LOGO.png'
},
{
id: 4,
city: '迪拜',
country: '阿联酋',
address: '迪拜国际金融中心28号',
role: '中东市场拓展中心',
image: '/LOGO.png'
},
{
id: 5,
city: '东京',
country: '日本',
address: '东京涩谷区105号',
role: '亚太技术中心',
image: '/LOGO.png'
}
};
onMounted(() => {
fetchCompanies();
});
]);
</script>
<template>
<section class="py-16 px-6 md:px-12 lg:px-24">
<section class="py-16 px-6 md:px-12 lg:px-24 bg-background-light">
<div class="container mx-auto">
<h2 class="text-2xl md:text-3xl font-bold text-text mb-10 text-center wow animate__animated animate__fadeInUp animate__duration-fast">
{{ t('about.companies.title') }}
<h2 class="text-2xl md:text-3xl font-bold text-text mb-8 text-center wow animate__animated animate__fadeInUp animate__duration-fast">
全球战略部署
</h2>
<div v-if="loading" class="flex justify-center py-20">
<div class="animate-spin rounded-full h-12 w-12 border-t-2 border-b-2 border-primary"></div>
</div>
<div v-else-if="error" class="text-center py-20 text-red-500">
{{ error }}
</div>
<div v-else class="grid grid-cols-1 md:grid-cols-3 gap-8">
<div
v-for="(company, index) in companies"
:key="company.id"
class="bg-background rounded-xl overflow-hidden shadow-card hover:shadow-lg transition-all duration-300 wow animate__animated animate__fadeInUp animate__duration-fast"
<div class="grid grid-cols-1 md:grid-cols-2 gap-8">
<div
v-for="(location, index) in companyLocations"
:key="location.id"
class="flex bg-background rounded-xl overflow-hidden shadow-card hover:shadow-lg transition-all duration-300 wow animate__animated animate__fadeInUp"
:class="{
'animate__delay-xs': index === 1,
'animate__delay-sm': index === 2
'animate__delay-xs': index % 5 === 1,
'animate__delay-sm': index % 5 === 2,
'animate__delay-md': index % 5 === 3,
'animate__delay-lg': index % 5 === 4
}"
>
<img :src="company.image" :alt="company.title" class="w-full h-48 object-cover" />
<div class="p-5">
<h3 class="text-lg font-bold text-text mb-2">{{ company.title }}</h3>
<p class="text-text-secondary text-sm" v-html="company.description"></p>
<!-- 地点图片 -->
<div class="w-1/3">
<img :src="location.image" :alt="location.city" class="w-full h-full object-cover" />
</div>
<!-- 地点信息 -->
<div class="w-2/3 p-5">
<div class="flex items-center mb-2">
<h3 class="text-lg font-bold text-text">{{ location.city }}</h3>
<span class="text-text-secondary text-sm ml-2">{{ location.country }}</span>
</div>
<p class="text-primary-light text-sm font-medium mb-2">{{ location.role }}</p>
<p class="text-text-secondary text-sm">{{ location.address }}</p>
</div>
</div>
</div>


+ 275
- 65
src/components/about/MilestoneModule.vue View File

@ -1,8 +1,13 @@
<script setup lang="ts">
import { useI18n } from 'vue-i18n';
import { ref, onMounted } from 'vue';
import { ref, onMounted, nextTick } from 'vue';
import { queryCourseList } from '@/api';
import type { CourseItem } from '@/api';
import gsap from 'gsap';
import { ScrollTrigger } from 'gsap/ScrollTrigger';
// GSAP
gsap.registerPlugin(ScrollTrigger);
const { t } = useI18n();
@ -34,6 +39,9 @@ const defaultMilestones: CourseItem[] = [
const milestones = ref<CourseItem[]>(defaultMilestones);
const loading = ref(false);
const error = ref<string | null>(null);
const activeIndex = ref(0);
const timelineRef = ref<HTMLElement | null>(null);
const milestonesContainer = ref<HTMLElement | null>(null);
//
const fetchMilestones = async () => {
@ -50,102 +58,304 @@ const fetchMilestones = async () => {
error.value = '获取发展历程数据失败';
} finally {
loading.value = false;
nextTick(() => {
initAnimations();
});
}
};
//
const setActiveMilestone = (index: number) => {
activeIndex.value = index;
};
//
const initAnimations = () => {
if (!milestonesContainer.value) return;
//
const items = document.querySelectorAll('.milestone-item');
//
gsap.to('.timeline-progress', {
height: '100%',
scrollTrigger: {
trigger: milestonesContainer.value,
start: 'top 80%',
end: 'bottom 20%',
scrub: 0.6,
}
});
//
items.forEach((item, index) => {
//
gsap.from(item.querySelector('.milestone-image'), {
y: 100,
opacity: 0,
duration: 1,
scrollTrigger: {
trigger: item,
start: 'top 80%',
toggleActions: 'play none none reverse'
}
});
//
gsap.from(item.querySelector('.milestone-content'), {
x: index % 2 === 0 ? -50 : 50,
opacity: 0,
duration: 1,
delay: 0.3,
scrollTrigger: {
trigger: item,
start: 'top 80%',
toggleActions: 'play none none reverse'
}
});
//
gsap.from(item.querySelector('.milestone-marker'), {
scale: 0,
opacity: 0,
duration: 0.6,
delay: 0.6,
scrollTrigger: {
trigger: item,
start: 'top 80%',
toggleActions: 'play none none reverse'
}
});
});
//
items.forEach((item, index) => {
ScrollTrigger.create({
trigger: item,
start: 'top center',
end: 'bottom center',
onEnter: () => setActiveMilestone(index),
onEnterBack: () => setActiveMilestone(index)
});
});
};
// - 使
const getMilestoneImage = (index: number) => {
// 使LOGO
return '/LOGO.png';
};
// -
const getMilestoneYear = (index: number) => {
const currentYear = new Date().getFullYear();
return (currentYear - (milestones.value.length - 1) + index).toString();
};
onMounted(() => {
fetchMilestones();
});
</script>
<template>
<section class="py-16 px-6 md:px-12 lg:px-24">
<div class="container mx-auto">
<h2 class="text-2xl md:text-3xl font-bold text-text mb-10 text-center wow animate__animated animate__fadeInUp animate__duration-fast">
{{ t('about.milestones.title') }}
</h2>
<section class="py-24 px-6 md:px-12 lg:px-24 bg-background-dark relative overflow-hidden">
<!-- 背景装饰 -->
<div class="absolute inset-0 overflow-hidden">
<div class="absolute top-0 left-0 w-full h-full bg-gradient-to-b from-background-dark via-background to-background-dark opacity-40"></div>
<div class="absolute -top-24 -left-24 w-64 h-64 rounded-full bg-primary-light blur-3xl opacity-20"></div>
<div class="absolute bottom-0 right-0 w-80 h-80 rounded-full bg-secondary blur-3xl opacity-20"></div>
</div>
<div class="container mx-auto relative z-10">
<!-- 标题部分 -->
<div class="max-w-3xl mx-auto text-center mb-20">
<h2 class="text-3xl md:text-4xl lg:text-5xl font-bold text-text mb-6 wow animate__animated animate__fadeInDown animate__duration-fast">
{{ t('about.milestones.title') }}
</h2>
<p class="text-lg md:text-xl text-text-secondary wow animate__animated animate__fadeIn animate__delay-xs animate__duration-fast">
见证MOSE的成长历程从创立之初到现在的每一步
</p>
<!-- 装饰线 -->
<div class="w-24 h-1 bg-primary mx-auto mt-8"></div>
</div>
<!-- 加载状态 -->
<div v-if="loading" class="flex justify-center py-20">
<div class="animate-spin rounded-full h-12 w-12 border-t-2 border-b-2 border-primary"></div>
<div class="animate-spin rounded-full h-16 w-16 border-t-2 border-b-2 border-primary"></div>
</div>
<!-- 错误提示 -->
<div v-else-if="error" class="text-center py-20 text-red-500">
{{ error }}
</div>
<div v-else class="max-w-4xl mx-auto">
<div class="relative">
<!-- Timeline Line -->
<div class="absolute top-0 left-6 md:left-1/2 w-1 h-full bg-primary-light transform -translate-x-1/2 wow animate__animated animate__height"></div>
<!-- Timeline Items -->
<div class="space-y-16">
<!-- 里程碑时间轴 -->
<div
v-else
ref="milestonesContainer"
class="relative max-w-7xl mx-auto"
>
<!-- 时间轴线 -->
<div class="absolute top-0 left-1/2 w-1 h-full bg-gray-800/30 transform -translate-x-1/2 z-10">
<div class="timeline-progress absolute top-0 left-0 w-full bg-primary origin-top" style="height: 0%"></div>
</div>
<!-- 里程碑项 -->
<div class="space-y-32 md:space-y-64 pb-20">
<div
v-for="(milestone, index) in milestones"
:key="milestone.id"
class="milestone-item relative"
:class="{ 'active': activeIndex === index }"
>
<!-- 时间标记 -->
<div
v-for="(milestone, index) in milestones"
:key="milestone.id"
class="relative wow animate__animated animate__duration-fast"
:class="{
'animate__fadeInLeft': index % 2 === 0,
'animate__fadeInRight': index % 2 !== 0,
'animate__delay-xs': index === 1,
'animate__delay-sm': index === 2,
'animate__delay-md': index === 3
}"
class="milestone-marker absolute left-1/2 w-8 h-8 transform -translate-x-1/2 z-20"
:class="{ 'active': activeIndex === index }"
>
<!-- Year Bubble (中间年份) -->
<div
class="absolute left-6 md:left-1/2 w-14 h-14 bg-primary rounded-full flex items-center justify-center transform -translate-x-1/2 z-10 shadow-lg wow animate__animated animate__zoomIn animate__duration-fast"
:class="{
'animate__delay-xs': index === 1,
'animate__delay-sm': index === 2,
'animate__delay-md': index === 3
}"
>
<span class="text-text font-bold text-lg">{{ index + 1 }}</span>
</div>
<!-- 内容区域 - 桌面视图 -->
<div class="hidden md:flex">
<!-- 左侧内容 (偶数索引) -->
<div class="relative">
<!-- 外圈 -->
<div
v-if="index % 2 === 0"
class="w-1/2 pr-12 text-left"
>
<div class="bg-background-light p-6 rounded-xl shadow-card hover:shadow-lg transition-all duration-300">
<h3 class="text-xl font-bold text-text mb-2">{{ milestone.title }}</h3>
<p class="text-text-secondary" v-html="milestone.description"></p>
</div>
</div>
class="absolute w-8 h-8 rounded-full border-2 transition-all duration-500"
:class="activeIndex === index ? 'border-primary scale-125' : 'border-gray-500'"
></div>
<!-- 左侧空白 (奇数索引) -->
<div v-else class="w-1/2"></div>
<!-- 右侧空白 (偶数索引) -->
<div v-if="index % 2 === 0" class="w-1/2"></div>
<!-- 内圈 -->
<div
class="absolute w-4 h-4 top-1/2 left-1/2 transform -translate-x-1/2 -translate-y-1/2 rounded-full transition-all duration-500"
:class="activeIndex === index ? 'bg-primary scale-100' : 'bg-gray-500 scale-75'"
></div>
<!-- 右侧内容 (奇数索引) -->
<!-- 年份标签 -->
<div
v-else
class="w-1/2 pl-12 text-left"
class="absolute top-1/2 transform -translate-y-1/2 whitespace-nowrap font-bold text-xl transition-all duration-500"
:class="[
index % 2 === 0 ? 'left-12' : 'right-12',
activeIndex === index ? 'text-primary' : 'text-text-secondary'
]"
>
<div class="bg-background-light p-6 rounded-xl shadow-card hover:shadow-lg transition-all duration-300">
<h3 class="text-xl font-bold text-text mb-2">{{ milestone.title }}</h3>
<p class="text-text-secondary" v-html="milestone.description"></p>
</div>
{{ getMilestoneYear(index) }}
</div>
</div>
</div>
<!-- 内容区域 - 左右交替 -->
<div class="grid grid-cols-1 md:grid-cols-2 gap-8 items-center">
<!-- 左侧内容 (偶数索引) 右侧内容 (奇数索引) -->
<div
class="milestone-content order-2 md:order-none"
:class="{ 'md:order-1': index % 2 !== 0 }"
>
<div
class="p-8 rounded-2xl transition-all duration-500 transform hover:-translate-y-2"
:class="[
activeIndex === index
? 'bg-gradient-to-br from-background-light to-background-dark shadow-xl'
: 'bg-background-light/50 shadow-md'
]"
>
<!-- 标题 -->
<h3
class="text-2xl md:text-3xl font-bold mb-4 transition-colors duration-500"
:class="activeIndex === index ? 'text-primary' : 'text-text'"
>
{{ milestone.title }}
</h3>
<!-- 描述 -->
<p
class="text-base md:text-lg leading-relaxed"
:class="activeIndex === index ? 'text-text' : 'text-text-secondary'"
v-html="milestone.description"
></p>
<!-- 装饰线 -->
<div
class="w-16 h-1 mt-6 transition-all duration-500"
:class="activeIndex === index ? 'bg-primary w-24' : 'bg-gray-400 w-16'"
></div>
</div>
</div>
<!-- 移动端显示 -->
<div class="md:hidden ml-20">
<div class="bg-background-light p-4 rounded-xl shadow-card">
<h3 class="text-xl font-bold text-text mb-2">{{ milestone.title }}</h3>
<p class="text-text-secondary" v-html="milestone.description"></p>
<!-- 图片区域 -->
<div
class="milestone-image order-1 md:order-none"
:class="{ 'md:order-2': index % 2 === 0 }"
>
<div
class="relative overflow-hidden rounded-2xl shadow-xl transition-all duration-500 transform"
:class="activeIndex === index ? 'scale-105' : 'scale-100'"
>
<!-- 图片 -->
<img
:src="getMilestoneImage(index)"
:alt="milestone.title"
class="w-full h-64 md:h-80 object-cover transition-all duration-700 transform"
:class="activeIndex === index ? 'scale-110' : 'scale-100'"
/>
<!-- 渐变叠加 -->
<div
class="absolute inset-0 transition-opacity duration-500"
:class="[
index % 4 === 0 ? 'bg-gradient-to-tr from-primary/70 to-transparent' : '',
index % 4 === 1 ? 'bg-gradient-to-tr from-secondary/70 to-transparent' : '',
index % 4 === 2 ? 'bg-gradient-to-tr from-accent/70 to-transparent' : '',
index % 4 === 3 ? 'bg-gradient-to-tr from-primary-light/70 to-transparent' : '',
activeIndex === index ? 'opacity-100' : 'opacity-70'
]"
></div>
<!-- 序号装饰 -->
<div class="absolute top-4 right-4 w-12 h-12 rounded-full bg-white/20 backdrop-blur-sm flex items-center justify-center">
<span class="text-white font-bold text-xl">{{ index + 1 }}</span>
</div>
</div>
</div>
</div>
</div>
</div>
<!-- 底部装饰 -->
<div class="absolute bottom-0 left-1/2 transform -translate-x-1/2">
<div class="w-4 h-4 bg-primary rounded-full animate-ping"></div>
</div>
</div>
</div>
</section>
</template>
</template>
<style scoped>
/* 时间轴动画 */
@keyframes progress {
from { height: 0; }
to { height: 100%; }
}
.milestone-item {
position: relative;
}
/* 活跃状态的样式 */
.milestone-marker.active .outer-circle {
transform: scale(1.5);
border-color: var(--color-primary);
}
.milestone-marker.active .inner-circle {
background-color: var(--color-primary);
}
/* 悬停效果 */
.milestone-content:hover .milestone-title {
color: var(--color-primary);
}
/* 响应式调整 */
@media (max-width: 768px) {
.milestone-marker .year-label {
display: none;
}
}
</style>

+ 61
- 191
src/components/about/PartnersModule.vue View File

@ -1,210 +1,80 @@
<script setup lang="ts">
import { ref } from 'vue';
import { useI18n } from 'vue-i18n';
import { ref, onMounted } from 'vue';
import { queryPartnerList } from '@/api';
import type { PartnerItem } from '@/api';
const { t } = useI18n();
//
const defaultPartners = {
technology: [
{
id: 1,
name: 'Aleo',
description: t('about.partners.technology.aleo'),
logo: '/public/LOGO.png',
url: 'https://aleo.org'
},
{
id: 2,
name: 'StarkWare',
description: t('about.partners.technology.starkware'),
logo: '/public/LOGO.png',
url: 'https://starkware.co'
},
{
id: 3,
name: 'LayerZero',
description: t('about.partners.technology.layerzero'),
logo: '/public/LOGO.png',
url: 'https://layerzero.network'
},
{
id: 4,
name: 'Axelar',
description: t('about.partners.technology.axelar'),
logo: '/public/LOGO.png',
url: 'https://axelar.network'
}
],
investors: [
{
id: 1,
name: 'Binance Labs',
description: t('about.partners.investors.binance'),
logo: '/public/LOGO.png',
url: 'https://labs.binance.com'
},
{
id: 2,
name: 'Polychain Capital',
description: t('about.partners.investors.polychain'),
logo: '/public/LOGO.png',
url: 'https://polychain.capital'
}
],
compliance: [
{
id: 1,
name: 'Singapore MAS',
description: t('about.partners.compliance.mas'),
logo: '/public/LOGO.png',
url: 'https://www.mas.gov.sg'
},
{
id: 2,
name: 'Swiss FINMA',
description: t('about.partners.compliance.finma'),
logo: '/public/LOGO.png',
url: 'https://www.finma.ch'
}
]
};
//
const partners = ref(defaultPartners);
const loading = ref(false);
const error = ref<string | null>(null);
//
const fetchPartners = async () => {
loading.value = true;
error.value = null;
try {
const response = await queryPartnerList();
if (response && Array.isArray(response) && response.length > 0) {
// API
const technology: any[] = [];
const investors: any[] = [];
const compliance: any[] = [];
//
response.forEach((item: PartnerItem, index: number) => {
const partner = {
id: item.id,
name: item.title,
description: item.description,
logo: item.image || '/public/LOGO.png',
url: '#'
};
//
if (index % 3 === 0) {
technology.push(partner);
} else if (index % 3 === 1) {
investors.push(partner);
} else {
compliance.push(partner);
}
});
// API
if (technology.length > 0 || investors.length > 0 || compliance.length > 0) {
partners.value = {
technology: technology.length > 0 ? technology : defaultPartners.technology,
investors: investors.length > 0 ? investors : defaultPartners.investors,
compliance: compliance.length > 0 ? compliance : defaultPartners.compliance
};
}
}
} catch (err) {
console.error('Failed to fetch partners:', err);
error.value = 'Failed to load partners data';
} finally {
loading.value = false;
const partners = ref([
{
id: 1,
name: 'ABC区块链基金会',
description: '专注于区块链技术发展和应用推广的非营利组织,为MOSE提供技术支持和生态扩展。',
logo: '/LOGO.png',
url: 'https://example.com/partner1'
},
{
id: 2,
name: 'DEF科技集团',
description: '全球领先的科技集团,与MOSE共同推进区块链在企业级应用的落地和推广。',
logo: '/LOGO.png',
url: 'https://example.com/partner2'
},
{
id: 3,
name: 'GHI金融服务',
description: '创新型金融科技公司,与MOSE合作开发基于区块链的金融产品和服务。',
logo: '/LOGO.png',
url: 'https://example.com/partner3'
},
{
id: 4,
name: 'JKL研究院',
description: '专注于密码学和分布式系统研究的学术机构,为MOSE提供理论研究支持。',
logo: '/LOGO.png',
url: 'https://example.com/partner4'
}
};
onMounted(() => {
fetchPartners();
});
]);
</script>
<template>
<section class="py-16 px-6 md:px-12 lg:px-24 bg-background-light">
<section class="py-16 px-6 md:px-12 lg:px-24">
<div class="container mx-auto">
<h2 class="text-2xl md:text-3xl font-bold text-text mb-10 text-center wow animate__animated animate__fadeInUp animate__duration-fast">
<h2 class="text-2xl md:text-3xl font-bold text-text mb-8 text-center wow animate__animated animate__fadeInUp animate__duration-fast">
{{ t('about.partners.title') }}
</h2>
<div v-if="loading" class="flex justify-center py-20">
<div class="animate-spin rounded-full h-12 w-12 border-t-2 border-b-2 border-primary"></div>
</div>
<div v-else-if="error" class="text-center py-20 text-red-500">
{{ error }}
</div>
<div v-else>
<!-- 技术合作伙伴 -->
<div class="mb-12">
<h3 class="text-xl font-bold text-text mb-6 wow animate__animated animate__fadeInUp animate__duration-fast">
{{ t('about.partners.technology_title') }}
</h3>
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-6">
<div
v-for="partner in partners.technology"
:key="`tech-${partner.id}`"
class="bg-background rounded-xl p-6 shadow-card hover:shadow-lg transition-all duration-300 wow animate__animated animate__fadeInUp animate__duration-fast"
>
<div class="flex items-center justify-center mb-4 h-16">
<img :src="partner.logo" :alt="partner.name" class="max-h-full max-w-full" />
</div>
<h4 class="text-lg font-bold text-text text-center mb-2">{{ partner.name }}</h4>
<p class="text-text-secondary text-center text-sm" v-html="partner.description"></p>
</div>
<div class="space-y-8">
<div
v-for="(partner, index) in partners"
:key="partner.id"
class="flex flex-col md:flex-row bg-background rounded-xl overflow-hidden shadow-card hover:shadow-lg transition-all duration-300 wow animate__animated animate__fadeInUp"
:class="{
'animate__delay-xs': index % 4 === 1,
'animate__delay-sm': index % 4 === 2,
'animate__delay-md': index % 4 === 3
}"
>
<!-- 合作伙伴Logo -->
<div class="md:w-1/3 p-8 flex items-center justify-center bg-background-light">
<img :src="partner.logo" :alt="partner.name" class="max-w-full max-h-24 object-contain" />
</div>
</div>
<!-- 投资机构 -->
<div class="mb-12">
<h3 class="text-xl font-bold text-text mb-6 wow animate__animated animate__fadeInUp animate__duration-fast">
{{ t('about.partners.investors_title') }}
</h3>
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-6">
<div
v-for="partner in partners.investors"
:key="`inv-${partner.id}`"
class="bg-background rounded-xl p-6 shadow-card hover:shadow-lg transition-all duration-300 wow animate__animated animate__fadeInUp animate__duration-fast"
>
<div class="flex items-center justify-center mb-4 h-16">
<img :src="partner.logo" :alt="partner.name" class="max-h-full max-w-full" />
</div>
<h4 class="text-lg font-bold text-text text-center mb-2">{{ partner.name }}</h4>
<p class="text-text-secondary text-center text-sm" v-html="partner.description"></p>
</div>
</div>
</div>
<!-- 合规支持 -->
<div>
<h3 class="text-xl font-bold text-text mb-6 wow animate__animated animate__fadeInUp animate__duration-fast">
{{ t('about.partners.compliance_title') }}
</h3>
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-6">
<div
v-for="partner in partners.compliance"
:key="`comp-${partner.id}`"
class="bg-background rounded-xl p-6 shadow-card hover:shadow-lg transition-all duration-300 wow animate__animated animate__fadeInUp animate__duration-fast"
<!-- 合作伙伴信息 -->
<div class="md:w-2/3 p-6">
<h3 class="text-xl font-bold text-text mb-3">{{ partner.name }}</h3>
<p class="text-text-secondary mb-4">{{ partner.description }}</p>
<a
:href="partner.url"
target="_blank"
rel="noopener noreferrer"
class="inline-flex items-center text-primary-light hover:text-primary-dark transition-colors"
>
<div class="flex items-center justify-center mb-4 h-16">
<img :src="partner.logo" :alt="partner.name" class="max-h-full max-w-full" />
</div>
<h4 class="text-lg font-bold text-text text-center mb-2">{{ partner.name }}</h4>
<p class="text-text-secondary text-center text-sm" v-html="partner.description"></p>
</div>
<span>{{ t('about.partners.visitWebsite') }}</span>
<svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5 ml-1" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M10 6H6a2 2 0 00-2 2v10a2 2 0 002 2h10a2 2 0 002-2v-4M14 4h6m0 0v6m0-6L10 14" />
</svg>
</a>
</div>
</div>
</div>


+ 96
- 97
src/components/about/TeamModule.vue View File

@ -1,126 +1,125 @@
<script setup lang="ts">
import { ref, onMounted, computed } from 'vue';
import { useI18n } from 'vue-i18n';
import { ref, onMounted } from 'vue';
import { queryTeamList } from '@/api';
import type { TeamMember } from '@/api';
const { t } = useI18n();
//
const defaultTeamMembers: TeamMember[] = [
{
id: '1',
name: t('about.team.member1.name'),
post: t('about.team.member1.position'),
resume: t('about.team.member1.bio'),
image: '/public/LOGO.png',
},
//
const teamMembers = ref([
{
id: '2',
name: t('about.team.member2.name'),
post: t('about.team.member2.position'),
resume: t('about.team.member2.bio'),
image: '/public/LOGO.png',
id: 1,
name: '张明宇',
title: '创始人 & CEO',
bio: '张明宇拥有10年区块链技术经验,曾在多家知名区块链项目担任技术负责人,主导了多个成功的跨链项目,对密码学和分布式系统有深入研究。',
image: '/LOGO.png'
},
{
id: '3',
name: t('about.team.member3.name'),
post: t('about.team.member3.position'),
resume: t('about.team.member3.bio'),
image: '/public/LOGO.png',
id: 2,
name: '李华',
title: '首席技术官 (CTO)',
bio: '李华是密码学专家,拥有计算机科学博士学位,在零知识证明和安全多方计算领域发表过多篇论文,曾负责设计多个区块链项目的核心安全架构。',
image: '/LOGO.png'
},
{
id: '4',
name: t('about.team.member4.name'),
post: t('about.team.member4.position'),
resume: t('about.team.member4.bio'),
image: '/public/LOGO.png',
id: 3,
name: '王建国',
title: '首席运营官 (COO)',
bio: '王建国拥有20年互联网和金融科技公司运营经验,曾在多家上市公司担任高管职位,对区块链行业商业模式和市场策略有独到见解。',
image: '/LOGO.png'
}
];
]);
//
const teamMembers = ref<TeamMember[]>(defaultTeamMembers);
const loading = ref(false);
const error = ref<string | null>(null);
//
const currentMemberIndex = ref(0);
//
const fetchTeamMembers = async () => {
loading.value = true;
error.value = null;
try {
const response = await queryTeamList();
if (response && Array.isArray(response) && response.length > 0) {
teamMembers.value = response;
}
} catch (err) {
console.error('获取团队成员数据失败:', err);
error.value = '获取团队成员数据失败';
} finally {
loading.value = false;
}
//
const showNextMember = () => {
currentMemberIndex.value = (currentMemberIndex.value + 1) % teamMembers.value.length;
};
onMounted(() => {
fetchTeamMembers();
//
const showPrevMember = () => {
currentMemberIndex.value = (currentMemberIndex.value - 1 + teamMembers.value.length) % teamMembers.value.length;
};
//
const currentMember = computed(() => {
return teamMembers.value[currentMemberIndex.value];
});
</script>
<template>
<section class="py-16 px-6 md:px-12 lg:px-24 bg-background-light">
<section class="py-16 px-6 md:px-12 lg:px-24">
<div class="container mx-auto">
<h2 class="text-2xl md:text-3xl font-bold text-text mb-10 text-center wow animate__animated animate__fadeInUp animate__duration-fast">
<h2 class="text-2xl md:text-3xl font-bold text-text mb-8 text-center wow animate__animated animate__fadeInUp animate__duration-fast">
{{ t('about.team.title') }}
</h2>
<div v-if="loading" class="flex justify-center py-20">
<div class="animate-spin rounded-full h-12 w-12 border-t-2 border-b-2 border-primary"></div>
</div>
<div v-else-if="error" class="text-center py-20 text-red-500">
{{ error }}
</div>
<div v-else class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-8">
<div
v-for="(member, index) in teamMembers"
:key="member.id"
class="bg-background rounded-xl overflow-hidden shadow-card hover:shadow-lg transition-all duration-300 wow animate__animated animate__fadeInUp animate__duration-fast"
:class="{
'animate__delay-xs': index === 1,
'animate__delay-sm': index === 2,
'animate__delay-md': index === 3
}"
>
<img :src="member.image" :alt="member.name" class="w-full h-48 object-cover" />
<div class="p-5">
<h3 class="text-lg font-bold text-text mb-1">{{ member.name }}</h3>
<p class="text-text-secondary text-sm mb-3">{{ member.post }}</p>
<p class="text-text-secondary text-sm" v-html="member.resume"></p>
<div class="flex space-x-2 mt-2">
<a
href="https://twitter.com/"
target="_blank"
rel="noopener noreferrer"
class="text-primary-light hover:text-primary-dark transition-colors"
>
<svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5" fill="currentColor" viewBox="0 0 24 24">
<path d="M23.953 4.57a10 10 0 01-2.825.775 4.958 4.958 0 002.163-2.723c-.951.555-2.005.959-3.127 1.184a4.92 4.92 0 00-8.384 4.482C7.69 8.095 4.067 6.13 1.64 3.162a4.822 4.822 0 00-.666 2.475c0 1.71.87 3.213 2.188 4.096a4.904 4.904 0 01-2.228-.616v.06a4.923 4.923 0 003.946 4.827 4.996 4.996 0 01-2.212.085 4.936 4.936 0 004.604 3.417 9.867 9.867 0 01-6.102 2.105c-.39 0-.779-.023-1.17-.067a13.995 13.995 0 007.557 2.209c9.053 0 13.998-7.496 13.998-13.985 0-.21 0-.42-.015-.63A9.935 9.935 0 0024 4.59z"/>
</svg>
</a>
<a
href="https://linkedin.com/"
target="_blank"
rel="noopener noreferrer"
class="text-primary-light hover:text-primary-dark transition-colors"
>
<svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5" fill="currentColor" viewBox="0 0 24 24">
<path d="M20.447 20.452h-3.554v-5.569c0-1.328-.027-3.037-1.852-3.037-1.853 0-2.136 1.445-2.136 2.939v5.667H9.351V9h3.414v1.561h.046c.477-.9 1.637-1.85 3.37-1.85 3.601 0 4.267 2.37 4.267 5.455v6.286zM5.337 7.433c-1.144 0-2.063-.926-2.063-2.065 0-1.138.92-2.063 2.063-2.063 1.14 0 2.064.925 2.064 2.063 0 1.139-.925 2.065-2.064 2.065zm1.782 13.019H3.555V9h3.564v11.452zM22.225 0H1.771C.792 0 0 .774 0 1.729v20.542C0 23.227.792 24 1.771 24h20.451C23.2 24 24 23.227 24 22.271V1.729C24 .774 23.2 0 22.222 0h.003z"/>
</svg>
</a>
<!-- 领导团队轮播展示 -->
<div class="relative max-w-4xl mx-auto">
<div class="bg-background-light rounded-2xl overflow-hidden shadow-card">
<div class="grid grid-cols-1 md:grid-cols-2 gap-6 p-6">
<!-- 成员照片 -->
<div class="aspect-square relative overflow-hidden rounded-xl">
<img
:src="currentMember.image"
:alt="currentMember.name"
class="w-full h-full object-cover transition-all duration-500"
/>
</div>
<!-- 成员信息 -->
<div class="flex flex-col justify-center">
<h3 class="text-2xl font-bold text-text mb-2">{{ currentMember.name }}</h3>
<p class="text-primary-light font-medium mb-4">{{ currentMember.title }}</p>
<p class="text-text-secondary">{{ currentMember.bio }}</p>
</div>
</div>
</div>
<!-- 左右切换控制器 -->
<div class="absolute top-1/2 -left-5 transform -translate-y-1/2 z-10">
<button
@click="showPrevMember"
class="w-10 h-10 rounded-full bg-background-dark flex items-center justify-center hover:bg-primary hover:bg-opacity-20 transition-colors shadow-lg"
>
<svg xmlns="http://www.w3.org/2000/svg" class="h-6 w-6 text-text-secondary" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 19l-7-7 7-7" />
</svg>
</button>
</div>
<div class="absolute top-1/2 -right-5 transform -translate-y-1/2 z-10">
<button
@click="showNextMember"
class="w-10 h-10 rounded-full bg-background-dark flex items-center justify-center hover:bg-primary hover:bg-opacity-20 transition-colors shadow-lg"
>
<svg xmlns="http://www.w3.org/2000/svg" class="h-6 w-6 text-text-secondary" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 5l7 7-7 7" />
</svg>
</button>
</div>
<!-- 指示器 -->
<div class="flex justify-center mt-6 gap-2">
<button
v-for="(member, index) in teamMembers"
:key="member.id"
@click="currentMemberIndex = index"
class="w-3 h-3 rounded-full transition-all duration-300"
:class="currentMemberIndex === index ? 'bg-primary' : 'bg-background-dark'"
></button>
</div>
</div>
<div class="text-center mt-8">
<p class="text-text-secondary mb-6">{{ t('about.team.description') }}</p>
<a href="#" class="inline-flex items-center text-primary-light hover:text-primary-dark transition-colors">
<span>{{ t('about.team.viewAll') }}</span>
<svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5 ml-1" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M14 5l7 7m0 0l-7 7m7-7H3" />
</svg>
</a>
</div>
</div>
</section>

+ 205
- 42
src/components/home/CoreValuesModule.vue View File

@ -1,6 +1,7 @@
<script setup lang="ts">
import { useI18n } from 'vue-i18n';
import { ref, onMounted } from 'vue';
import { Icon } from '@iconify/vue';
import { queryValueList } from '@/api/modules/home';
import type { ValueItem } from '@/api/modules/home';
@ -10,7 +11,7 @@ const { t } = useI18n();
const coreValues = ref<any[]>([
{
id: 1,
icon: '🔒',
icon: 'carbon:security',
title: t('home.core_values.privacy.title'),
description: [
t('home.core_values.privacy.desc1'),
@ -19,7 +20,7 @@ const coreValues = ref<any[]>([
},
{
id: 2,
icon: '⛓️',
icon: 'carbon:connection',
title: t('home.core_values.interop.title'),
description: [
t('home.core_values.interop.desc1'),
@ -28,7 +29,7 @@ const coreValues = ref<any[]>([
},
{
id: 3,
icon: '🛡️',
icon: 'carbon:certificate',
title: t('home.core_values.compliance.title'),
description: [
t('home.core_values.compliance.desc1'),
@ -48,25 +49,15 @@ const fetchCoreValues = async () => {
try {
const response = await queryValueList();
if (response && Array.isArray(response)) {
// API
const icons = ['🔒', '⛓️', '🛡️']; //
console.log('response', response);
// const apiValues = response.map((item: ValueItem, index: number) => {
// // APIdescription
// const descLines = item.description ? item.description.split('\n').filter(line => line.trim()) : [];
// return {
// id: item.id,
// icon: icons[index % icons.length], // 使
// title: t(`home.core_values.item${index + 1}.title`), // 使i18n
// image: item.image,
// description: descLines.length > 0 ? descLines : [t(`home.core_values.item${index + 1}.desc1`)]
// };
// });
// API使API
if (response.length > 0) {
//
const icons = ['carbon:security', 'carbon:connection', 'carbon:certificate'];
response.forEach((item, index) => {
if (index < icons.length) {
item.icon = icons[index];
}
});
coreValues.value = response;
}
}
@ -86,7 +77,7 @@ onMounted(() => {
<template>
<section class="py-16 px-6 md:px-12 lg:px-24">
<div class="container mx-auto">
<h2 class="text-2xl md:text-3xl font-bold text-text mb-10 text-center wow animate__animated animate__fadeInUp animate__duration-fast">
<h2 class="text-2xl md:text-3xl font-bold text-text mb-16 text-center wow animate__animated animate__fadeInUp animate__duration-fast">
{{ t('home.core_values.title') }}
</h2>
@ -98,40 +89,212 @@ onMounted(() => {
{{ error }}
</div>
<div v-else class="grid grid-cols-1 md:grid-cols-3 gap-8">
<div v-else class="space-y-32">
<!-- 每个核心价值占据一行 -->
<div
v-for="(value, index) in coreValues"
:key="value.id"
class="bg-background-light rounded-xl p-6 shadow-card hover:transform hover:scale-105 transition-all duration-300 wow animate__animated animate__fadeInUp"
class="wow animate__animated animate__fadeInUp"
:class="{
'animate__delay-xs': index === 1,
'animate__delay-sm': index === 2
}"
>
<div class="flex flex-col h-full">
<div class="mb-4">
<div class="text-4xl mb-3">{{ value.icon }}</div>
<h3 class="text-xl font-bold text-text mb-2" v-html="value.description"></h3>
<!-- 交替左右布局 -->
<div :class="[
'flex flex-col gap-8',
index % 2 === 0 ? 'md:flex-row' : 'md:flex-row-reverse'
]">
<!-- 文本内容 -->
<div class="md:w-1/2 flex flex-col justify-center">
<div class="flex items-center mb-4">
<div class="icon-container w-16 h-16 rounded-full bg-primary bg-opacity-10 flex items-center justify-center mr-4">
<Icon :icon="value.icon" class="text-primary" width="32" height="32" />
</div>
<h3 class="text-2xl md:text-3xl font-bold text-text">{{ value.title }}</h3>
</div>
<div v-html="value.description" class="text-text-secondary mt-6 rich-text-content"></div>
</div>
<div class="flex-grow">
<ul class="space-y-2">
<img :src="value.image" alt="value.title" class="w-full h-full object-cover">
<!-- <li
v-for="(desc, descIndex) in value.description"
:key="`${value.id}-desc-${descIndex}`"
class="text-text-secondary flex items-start"
>
<svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5 text-primary-light mr-2 mt-0.5 flex-shrink-0" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 12l2 2 4-4m6 2a9 9 0 11-18 0 9 9 0 0118 0z" />
</svg>
<span>{{ desc }}</span>
</li> -->
</ul>
<!-- 图片 -->
<div class="md:w-1/2">
<div class="image-container relative overflow-hidden rounded-2xl shadow-xl h-full">
<img
:src="value.image"
:alt="value.title"
class="w-full h-full object-cover"
/>
<!-- 装饰元素 -->
<div :class="[
'decoration absolute w-32 h-32 rounded-full blur-2xl opacity-40',
index % 3 === 0 ? 'bg-primary top-0 right-0 decoration-float-1' :
index % 3 === 1 ? 'bg-secondary bottom-0 left-0 decoration-float-2' :
'bg-accent top-1/2 left-1/2 -translate-x-1/2 -translate-y-1/2 decoration-pulse'
]"></div>
<!-- 悬停时显示的图层 -->
<div class="hover-overlay absolute inset-0 bg-gradient-to-t from-black/60 to-transparent opacity-0 transition-opacity duration-300 flex items-end">
<div class="p-6 text-white">
<h4 class="text-xl font-bold mb-2">{{ value.title }}</h4>
<p class="text-white/80">{{ t('home.core_values.view_details') }}</p>
</div>
</div>
</div>
</div>
</div>
<!-- 分隔线最后一个项目不显示 -->
<div v-if="index !== coreValues.length - 1" class="separator w-1/3 h-px bg-background-light mx-auto mt-16"></div>
</div>
</div>
</div>
</section>
</template>
</template>
<style scoped>
.rich-text-content :deep(p) {
margin-bottom: 1rem;
line-height: 1.7;
}
.rich-text-content :deep(ul),
.rich-text-content :deep(ol) {
margin-left: 1.5rem;
margin-bottom: 1rem;
}
.rich-text-content :deep(li) {
margin-bottom: 0.5rem;
line-height: 1.7;
}
.rich-text-content :deep(a) {
color: var(--primary, #3B82F6);
text-decoration: underline;
transition: color 0.3s;
}
.rich-text-content :deep(a:hover) {
color: var(--primary-dark, #2563EB);
}
/* 图标容器动画 */
.icon-container {
position: relative;
overflow: hidden;
transition: all 0.5s ease;
}
.icon-container::before {
content: '';
position: absolute;
top: -50%;
left: -50%;
width: 200%;
height: 200%;
background: conic-gradient(transparent, rgba(59, 130, 246, 0.1), transparent 30%);
animation: rotate 4s linear infinite;
}
.icon-container:hover {
transform: scale(1.1);
box-shadow: 0 0 20px rgba(59, 130, 246, 0.3);
}
@keyframes rotate {
100% {
transform: rotate(360deg);
}
}
/* 图片容器效果 */
.image-container {
transition: transform 0.7s ease, box-shadow 0.7s ease;
overflow: hidden;
}
.image-container:hover {
transform: translateY(-10px);
box-shadow: 0 20px 40px rgba(0, 0, 0, 0.2);
}
.image-container:hover img {
transform: scale(1.05);
}
.image-container img {
transition: transform 1s ease;
}
.image-container:hover .hover-overlay {
opacity: 1;
}
/* 装饰元素动画 */
.decoration-float-1 {
animation: float1 6s ease-in-out infinite;
}
.decoration-float-2 {
animation: float2 8s ease-in-out infinite;
}
.decoration-pulse {
animation: pulse 4s ease-in-out infinite;
}
@keyframes float1 {
0% { transform: translateY(0) translateX(0); }
50% { transform: translateY(-15px) translateX(10px); }
100% { transform: translateY(0) translateX(0); }
}
@keyframes float2 {
0% { transform: translateY(0) translateX(0); }
50% { transform: translateY(15px) translateX(-10px); }
100% { transform: translateY(0) translateX(0); }
}
@keyframes pulse {
0% { transform: translate(-50%, -50%) scale(0.8); opacity: 0.3; }
50% { transform: translate(-50%, -50%) scale(1.2); opacity: 0.5; }
100% { transform: translate(-50%, -50%) scale(0.8); opacity: 0.3; }
}
/* 分隔线动画 */
.separator {
position: relative;
overflow: hidden;
}
.separator::after {
content: '';
position: absolute;
top: 0;
left: -100%;
width: 100%;
height: 100%;
background: linear-gradient(90deg, transparent, var(--primary, #3B82F6), transparent);
animation: separator-shine 3s infinite;
}
@keyframes separator-shine {
100% {
left: 100%;
}
}
/* 富文本图片效果 */
.rich-text-content :deep(img) {
border-radius: 0.75rem;
box-shadow: 0 10px 25px rgba(0, 0, 0, 0.1);
transition: transform 0.5s, box-shadow 0.5s;
}
.rich-text-content :deep(img:hover) {
transform: translateY(-5px) scale(1.02);
box-shadow: 0 15px 30px rgba(0, 0, 0, 0.15);
}
</style>

+ 211
- 51
src/components/home/MediaModule.vue View File

@ -1,7 +1,8 @@
<script setup lang="ts">
import { useI18n } from 'vue-i18n';
import { queryMediaList } from '@/api';
import { ref, onMounted } from 'vue';
import { queryMediaList } from '@/api/modules/home';
import { Icon } from '@iconify/vue';
import type { MediaItem } from '@/api/modules/home';
const { t } = useI18n();
@ -11,7 +12,7 @@ const mediaItems = ref<MediaItem[]>([]);
const loading = ref(false);
const error = ref<string | null>(null);
//
//
const fetchMediaItems = async () => {
loading.value = true;
error.value = null;
@ -25,27 +26,27 @@ const fetchMediaItems = async () => {
mediaItems.value = [
{
id: '1',
title: 'Media 1',
title: '区块链技术新突破:MOSE引领隐私计算革命',
image: '/LOGO.png',
description: 'Media description'
description: 'MOSE区块链平台推出全新隐私计算解决方案,获得业内广泛关注和认可。',
source: '区块链日报',
date: '2024-03-15'
},
{
id: '2',
title: 'Media 2',
title: 'MOSE获新加坡金融管理局支持,合规之路再进一步',
image: '/LOGO.png',
description: 'Media description'
description: '新加坡金融管理局(MAS)将MOSE纳入金融科技监管沙盒,标志着其合规发展取得重大突破。',
source: '亚洲金融时报',
date: '2024-02-20'
},
{
id: '3',
title: 'Media 3',
title: '全球区块链峰会:MOSE展示跨链隐私技术',
image: '/LOGO.png',
description: 'Media description'
},
{
id: '4',
title: 'Media 4',
image: '/LOGO.png',
description: 'Media description'
description: '在全球区块链峰会上,MOSE团队展示了其创新的跨链隐私技术,引发与会者热烈讨论。',
source: '科技前沿',
date: '2024-04-10'
}
];
}
@ -56,15 +57,11 @@ const fetchMediaItems = async () => {
mediaItems.value = [
{
id: '1',
title: 'Media 1',
title: '区块链技术新突破:MOSE引领隐私计算革命',
image: '/LOGO.png',
description: 'Media description'
},
{
id: '2',
title: 'Media 2',
image: '/LOGO.png',
description: 'Media description'
description: 'MOSE区块链平台推出全新隐私计算解决方案,获得业内广泛关注和认可。',
source: '区块链日报',
date: '2024-03-15'
}
];
} finally {
@ -72,19 +69,47 @@ const fetchMediaItems = async () => {
}
};
//
const formatDate = (dateString?: string) => {
if (!dateString) return '';
const date = new Date(dateString);
return new Intl.DateTimeFormat('zh-CN', {
year: 'numeric',
month: 'short',
day: 'numeric'
}).format(date);
};
onMounted(() => {
fetchMediaItems();
});
</script>
<template>
<section class="py-16 px-6 md:px-12 lg:px-24 bg-background-light">
<div class="container mx-auto">
<div class="text-center mb-12">
<h2 class="text-2xl md:text-3xl font-bold text-text mb-4 wow animate__animated animate__fadeInUp animate__duration-fast">
{{ t('home.media.title') }}
</h2>
<p class="text-text-secondary max-w-2xl mx-auto wow animate__animated animate__fadeIn animate__delay-xs">
<section class="py-16 px-6 md:px-12 lg:px-24 bg-background-light relative overflow-hidden">
<!-- 背景装饰 -->
<div class="absolute inset-0 overflow-hidden pointer-events-none">
<div class="absolute top-0 left-0 w-full h-full bg-[radial-gradient(circle_at_30%_20%,rgba(59,130,246,0.03),transparent_40%)]"></div>
<div class="absolute top-1/2 left-1/2 transform -translate-x-1/2 -translate-y-1/2 w-[800px] h-[800px] rounded-full border border-primary/5 opacity-30 wow animate__animated animate__zoomIn animate__duration-slow"></div>
<div class="absolute top-1/2 left-1/2 transform -translate-x-1/2 -translate-y-1/2 w-[600px] h-[600px] rounded-full border border-secondary/5 opacity-30 wow animate__animated animate__zoomIn animate__duration-slow animate__delay-xs"></div>
<div class="absolute top-1/2 left-1/2 transform -translate-x-1/2 -translate-y-1/2 w-[400px] h-[400px] rounded-full border border-accent/5 opacity-30 wow animate__animated animate__zoomIn animate__duration-slow animate__delay-sm"></div>
</div>
<!-- 浮动装饰元素 -->
<div class="absolute top-20 left-10 w-20 h-20 rounded-full bg-primary/10 blur-xl wow animate__animated animate__fadeIn animate__duration-slow animate__delay-md"></div>
<div class="absolute bottom-40 right-20 w-32 h-32 rounded-full bg-secondary/10 blur-xl wow animate__animated animate__fadeIn animate__duration-slow animate__delay-lg"></div>
<div class="absolute top-1/3 right-1/4 w-24 h-24 rounded-full bg-accent/10 blur-xl wow animate__animated animate__fadeIn animate__duration-slow animate__delay-xl"></div>
<div class="container mx-auto relative z-10">
<div class="text-center mb-16">
<div class="inline-block relative">
<h2 class="text-2xl md:text-3xl lg:text-4xl font-bold text-text mb-4 relative z-10 wow animate__animated animate__fadeInDown animate__duration-fast">
{{ t('home.media.title') }}
</h2>
<div class="absolute -bottom-2 left-0 w-full h-1 bg-gradient-to-r from-secondary via-accent to-primary opacity-70 rounded-full wow animate__animated animate__fadeInLeft animate__duration-normal animate__delay-xs"></div>
</div>
<p class="text-text-secondary max-w-2xl mx-auto mt-4 wow animate__animated animate__fadeIn animate__duration-normal animate__delay-sm">
{{ t('home.media.subtitle') }}
</p>
</div>
@ -97,47 +122,182 @@ onMounted(() => {
{{ error }}
</div>
<div v-else class="grid grid-cols-2 md:grid-cols-3 lg:grid-cols-6 gap-6">
<div v-else class="space-y-16">
<!-- 一行一个媒体项 -->
<div
v-for="(media, index) in mediaItems"
:key="media.id"
class="bg-background rounded-xl p-6 flex flex-col items-center justify-center hover:shadow-lg transition-all duration-300 wow animate__animated animate__fadeInUp group"
:class="{
'animate__delay-xs': index % 6 === 1,
'animate__delay-sm': index % 6 === 5,
'animate__delay-md': index % 6 === 2,
'animate__delay-lg': index % 6 === 4,
'animate__delay-xl': index % 6 === 3
}"
class="media-card bg-background rounded-xl overflow-hidden shadow-md hover:shadow-xl transition-all duration-500 flex flex-col md:flex-row wow animate__animated"
:class="[
index % 2 === 0 ? 'md:flex-row animate__fadeInLeft' : 'md:flex-row-reverse animate__fadeInRight',
{
'animate__delay-xs': index === 1,
'animate__delay-sm': index === 2
}
]"
>
<div class="relative w-full">
<a
href="#"
class="w-full h-16 flex items-center justify-center mb-3"
>
<!-- 图片容器 -->
<div class="relative w-full md:w-1/3 h-56 md:h-auto overflow-hidden">
<div class="media-image w-full h-full overflow-hidden rounded-t-xl md:rounded-t-none md:rounded-l-xl shadow-md transition-all duration-500">
<img
:src="media.image"
:alt="media.title"
class="max-w-full max-h-full object-contain"
class="w-full h-full object-cover hover:scale-105 transition-transform duration-700"
/>
</a>
<!-- 渐变覆盖层 -->
<div class="absolute inset-0 bg-gradient-to-t from-black/60 via-transparent to-black/30"></div>
<!-- 日期标签 -->
<div class="absolute bottom-4 left-4 bg-black/50 backdrop-blur-sm px-3 py-1 rounded-lg text-white text-xs">
{{ media.date ? formatDate(media.date) : '' }}
</div>
</div>
<!-- 标题和描述 -->
<div class="text-center mt-2">
<h3 class="text-sm font-medium text-text truncate">{{ media.title }}</h3>
<div class="opacity-0 group-hover:opacity-100 transition-opacity duration-300 absolute inset-0 bg-background-dark bg-opacity-90 rounded-lg flex items-center justify-center p-2">
<p class="text-xs text-white text-center" v-html="media.description"></p>
<!-- 装饰元素 -->
<div class="absolute -bottom-2 -right-2 w-12 h-12 rounded-full bg-primary/20 blur-lg"></div>
</div>
<!-- 内容容器 -->
<div class="media-content w-full md:w-2/3 p-6 md:p-8 flex flex-col justify-between">
<div>
<!-- 来源标签 -->
<div class="source-tag inline-block bg-primary/10 text-primary px-3 py-1 rounded-full text-sm font-medium mb-3 transition-all duration-300 hover:bg-primary/20 hover:scale-105">
<Icon icon="carbon:document" class="inline-block mr-1.5" width="14" height="14" />
{{ media.source || '媒体报道' }}
</div>
<h3 class="media-title text-xl md:text-2xl font-bold text-text mb-3 hover:text-primary transition-colors duration-300">
{{ media.title }}
</h3>
<p class="media-desc text-text-secondary mb-6">{{ media.description }}</p>
</div>
<!-- 阅读更多按钮 -->
<div class="flex justify-end">
<button class="read-more-btn group bg-gradient-to-r from-secondary to-primary text-white h-10 overflow-hidden rounded-full flex items-center justify-center transition-all duration-300 hover:shadow-glow px-4 hover:px-6">
<Icon icon="carbon:arrow-right" width="20" height="20" class="flex-shrink-0 group-hover:translate-x-1 transition-transform duration-300" />
<span class="ml-2 whitespace-nowrap">阅读全文</span>
</button>
</div>
</div>
</div>
</div>
<!-- 查看更多按钮 -->
<div class="text-center mt-16">
<button class="view-all-btn px-8 py-3 bg-gradient-to-r from-secondary to-primary text-white rounded-lg transition-all duration-300 transform hover:-translate-y-1 hover:shadow-glow flex items-center mx-auto wow animate__animated animate__fadeInUp animate__delay-md">
<span class="mr-2">{{ t('home.media.view_all') || '查看全部' }}</span>
<Icon icon="carbon:arrow-right" width="20" height="20" />
</button>
</div>
</div>
</section>
</template>
<style scoped>
.group:hover {
/* 媒体卡片样式 */
.media-card {
position: relative;
border: 1px solid rgba(255, 255, 255, 0.1);
}
/* 媒体卡片悬停效果 */
.media-card:hover {
border-color: rgba(59, 130, 246, 0.2);
transform: translateY(-5px);
}
/* 阅读更多按钮悬停效果 */
.read-more-btn:hover {
box-shadow: 0 0 15px rgba(16, 185, 129, 0.5);
}
/* 查看全部按钮动画 */
.view-all-btn {
position: relative;
overflow: hidden;
box-shadow: 0 4px 14px rgba(16, 185, 129, 0.3);
}
.view-all-btn::after {
content: '';
position: absolute;
top: -50%;
left: -50%;
width: 200%;
height: 200%;
background: linear-gradient(
to right,
rgba(255, 255, 255, 0) 0%,
rgba(255, 255, 255, 0.3) 50%,
rgba(255, 255, 255, 0) 100%
);
transform: rotate(30deg) translateY(100%);
transition: transform 0.7s;
}
.view-all-btn:hover::after {
transform: rotate(30deg) translateY(-100%);
}
/* 媒体图片悬停效果 */
@media (min-width: 768px) {
.media-card:nth-child(odd) .media-image {
border-top-left-radius: 0.75rem;
border-bottom-left-radius: 0.75rem;
border-top-right-radius: 0;
border-bottom-right-radius: 0;
}
.media-card:nth-child(even) .media-image {
border-top-right-radius: 0.75rem;
border-bottom-right-radius: 0.75rem;
border-top-left-radius: 0;
border-bottom-left-radius: 0;
}
}
/* 滚动指示器样式 */
.scroll-indicator {
position: absolute;
bottom: 20px;
left: 50%;
transform: translateX(-50%);
display: flex;
flex-direction: column;
align-items: center;
animation: bounce 2s infinite;
}
.scroll-dot {
width: 8px;
height: 8px;
background-color: var(--color-primary);
border-radius: 50%;
margin-bottom: 5px;
}
.scroll-text {
font-size: 12px;
color: var(--color-text-secondary);
}
@keyframes bounce {
0%, 20%, 50%, 80%, 100% {
transform: translateY(0);
}
40% {
transform: translateY(-10px);
}
60% {
transform: translateY(-5px);
}
}
/* 发光阴影效果 */
.shadow-glow {
box-shadow: 0 0 15px rgba(16, 185, 129, 0.5);
}
</style>

+ 197
- 108
src/components/home/NewsModule.vue View File

@ -1,8 +1,12 @@
<script setup lang="ts">
import { useI18n } from 'vue-i18n';
import { ref, onMounted, onBeforeUnmount } from 'vue';
import { ref, onMounted, onBeforeUnmount, nextTick } from 'vue';
import { queryPostList } from '@/api/modules/home';
import type { PostItem } from '@/api/modules/home';
import { Icon } from '@iconify/vue';
import { gsap } from 'gsap';
import AOS from 'aos';
import 'aos/dist/aos.css';
const { t } = useI18n();
@ -11,52 +15,29 @@ const news = ref([
{
id: 1,
title: t('home.news.mainnet_launch'),
date: '2024-03-15'
date: '2024-03-15',
image: '/public/LOGO.png',
description: '2025年7月,MOSE主网将正式上线,实现完整的跨链隐私交易功能。'
},
{
id: 2,
title: t('home.news.mas_license'),
date: '2024-02-20'
date: '2024-02-20',
image: '/public/LOGO.png',
description: 'MOSE获得新加坡金融管理局(MAS)颁发的支付服务牌照,合规发展迈出重要一步。'
},
{
id: 3,
title: t('home.news.hackathon'),
date: '2024-04-10'
date: '2024-04-10',
image: '/public/LOGO.png',
description: '首届MOSE全球开发者黑客松大赛正式启动,邀请全球开发者参与生态建设。'
}
]);
const loading = ref(false);
const error = ref<string | null>(null);
//
const currentNewsIndex = ref(0);
//
let autoScrollTimer: number | null = null;
//
const showNextNews = () => {
currentNewsIndex.value = (currentNewsIndex.value + 1) % news.value.length;
};
//
const showPrevNews = () => {
currentNewsIndex.value = (currentNewsIndex.value - 1 + news.value.length) % news.value.length;
};
//
const startAutoScroll = () => {
autoScrollTimer = window.setInterval(() => {
showNextNews();
}, 5000); // 5
};
//
const stopAutoScroll = () => {
if (autoScrollTimer !== null) {
clearInterval(autoScrollTimer);
autoScrollTimer = null;
}
};
const newsItems = ref<HTMLElement[]>([]);
//
const formatDate = (dateString: string) => {
@ -71,9 +52,11 @@ const formatDate = (dateString: string) => {
// API
const convertNewsData = (apiNews: any[]) => {
return apiNews.map(newsItem => ({
id: parseInt(newsItem.id) || Math.floor(Math.random() * 10000), // ID
id: parseInt(newsItem.id) || Math.floor(Math.random() * 10000),
title: newsItem.title,
date: newsItem.createTime?.split(' ')[0] || new Date().toISOString().split('T')[0]
date: newsItem.createTime?.split(' ')[0] || new Date().toISOString().split('T')[0],
image: newsItem.image || '/public/LOGO.png',
description: newsItem.description || '暂无描述'
}));
};
@ -87,8 +70,6 @@ const fetchNews = async () => {
if (response && Array.isArray(response) && response.length > 0) {
//
news.value = convertNewsData(response);
//
currentNewsIndex.value = 0;
}
} catch (err) {
console.error('Failed to fetch news:', err);
@ -98,97 +79,205 @@ const fetchNews = async () => {
}
};
//
//
const initAnimations = () => {
// AOS
AOS.init({
duration: 800,
easing: 'ease-out-cubic',
once: true
});
// 使GSAP
nextTick(() => {
newsItems.value.forEach(item => {
const image = item.querySelector('.news-image');
const overlay = item.querySelector('.news-overlay');
const content = item.querySelector('.news-content');
item.addEventListener('mouseenter', () => {
gsap.to(image, { scale: 1.05, duration: 0.5 });
gsap.to(overlay, { opacity: 0.7, duration: 0.5 });
gsap.to(content, { y: -5, duration: 0.3 });
});
item.addEventListener('mouseleave', () => {
gsap.to(image, { scale: 1, duration: 0.5 });
gsap.to(overlay, { opacity: 0.4, duration: 0.5 });
gsap.to(content, { y: 0, duration: 0.3 });
});
});
});
};
onMounted(() => {
fetchNews();
startAutoScroll();
});
onBeforeUnmount(() => {
stopAutoScroll();
initAnimations();
});
</script>
<template>
<section class="py-8 px-6 md:px-12 lg:px-24 bg-primary bg-opacity-10">
<section class="py-16 px-6 md:px-12 lg:px-24 bg-background">
<div class="container mx-auto">
<div class="flex flex-col md:flex-row items-center justify-between">
<div class="flex items-center mb-4 md:mb-0">
<div class="w-10 h-10 rounded-full bg-primary bg-opacity-20 flex items-center justify-center mr-3">
<svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5 text-primary-light" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M11 5.882V19.24a1.76 1.76 0 01-3.417.592l-2.147-6.15M18 13a3 3 0 100-6M5.436 13.683A4.001 4.001 0 017 6h1.832c4.1 0 7.625-1.234 9.168-3v14c-1.543-1.766-5.067-3-9.168-3H7a3.988 3.988 0 01-1.564-.317z" />
</svg>
</div>
<h2 class="text-xl font-bold text-text">{{ t('home.news.title') }}</h2>
</div>
<div v-if="loading" class="flex-grow mx-4 flex justify-center">
<div class="animate-spin rounded-full h-6 w-6 border-t-2 border-b-2 border-primary"></div>
</div>
<div v-else-if="error" class="flex-grow mx-4 text-center text-red-500">
{{ error }}
<!-- 标题 -->
<div class="flex items-center justify-center mb-12">
<div class="relative">
<h2 class="text-2xl md:text-3xl lg:text-4xl font-bold text-text text-center" data-aos="fade-up">
{{ t('home.news.title') }}
</h2>
<div class="absolute -bottom-3 left-0 w-full h-1 bg-primary opacity-70 rounded-full" data-aos="width" data-aos-delay="200"></div>
</div>
<div v-else class="flex-grow mx-4 relative overflow-hidden h-10">
<transition-group
name="news-slide"
tag="div"
class="absolute w-full"
>
<div
v-for="(item, index) in news"
:key="item.id"
v-show="index === currentNewsIndex"
class="flex items-center justify-between w-full"
>
<span class="text-text-secondary">{{ formatDate(item.date) }}</span>
<span class="text-text font-medium">{{ item.title }}</span>
</div>
<div v-if="loading" class="flex justify-center py-10">
<div class="animate-spin rounded-full h-10 w-10 border-t-2 border-b-2 border-primary"></div>
</div>
<div v-else-if="error" class="text-center py-10 text-red-500">
{{ error }}
</div>
<div v-else class="space-y-8">
<!-- 一行一个新闻项 -->
<div
v-for="(item, index) in news"
:key="item.id"
class="news-item bg-background-light rounded-xl overflow-hidden shadow-card hover:shadow-xl transition-all duration-500 flex flex-col md:flex-row"
:class="index % 2 === 0 ? 'md:flex-row' : 'md:flex-row-reverse'"
data-aos="fade-up"
:data-aos-delay="index * 100"
ref="el => { if(el) newsItems[index] = el }"
>
<!-- 图片容器 -->
<div class="relative w-full md:w-2/5 h-56 md:h-auto overflow-hidden">
<img
:src="item.image"
:alt="item.title"
class="news-image w-full h-full object-cover transition-transform duration-700"
/>
<div class="news-overlay absolute inset-0 bg-gradient-to-r from-primary/40 to-secondary/40 opacity-40 transition-opacity duration-500"></div>
<!-- 日期标签 -->
<div class="absolute top-4 left-4 bg-black/50 backdrop-blur-sm px-3 py-1 rounded-lg flex items-center">
<Icon icon="carbon:calendar" class="text-primary-light mr-1.5" width="16" height="16" />
<span class="text-white text-xs font-medium">{{ formatDate(item.date) }}</span>
</div>
</transition-group>
</div>
<div class="flex items-center space-x-2">
<button
@click="showPrevNews"
@mouseenter="stopAutoScroll"
@mouseleave="startAutoScroll"
class="w-8 h-8 rounded-full bg-background flex items-center justify-center hover:bg-primary hover:bg-opacity-20 transition-colors"
>
<svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5 text-text-secondary" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 19l-7-7 7-7" />
</svg>
</button>
<!-- 装饰元素 -->
<div class="absolute bottom-0 left-0 w-full h-1/3 bg-gradient-to-t from-black/60 to-transparent"></div>
</div>
<button
@click="showNextNews"
@mouseenter="stopAutoScroll"
@mouseleave="startAutoScroll"
class="w-8 h-8 rounded-full bg-background flex items-center justify-center hover:bg-primary hover:bg-opacity-20 transition-colors"
>
<svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5 text-text-secondary" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 5l7 7-7 7" />
</svg>
</button>
<!-- 内容容器 -->
<div class="news-content w-full md:w-3/5 p-6 md:p-8 flex flex-col justify-between">
<div>
<h3 class="text-xl md:text-2xl font-bold text-text mb-3 line-clamp-2 hover:text-primary transition-colors duration-300">
{{ item.title }}
</h3>
<p class="text-text-secondary line-clamp-3 mb-4">{{ item.description }}</p>
</div>
<div class="flex justify-between items-center">
<!-- 标签 -->
<div class="flex items-center">
<span class="bg-primary/10 text-primary px-3 py-1 rounded-full text-sm font-medium">
{{ t('home.news.category') }}
</span>
</div>
<!-- 阅读更多按钮 -->
<button class="group flex items-center text-primary hover:text-primary-dark transition-colors">
<span class="mr-2 font-medium">{{ t('home.news.readMore') }}</span>
<div class="relative w-6 h-6 rounded-full bg-primary/10 flex items-center justify-center overflow-hidden group-hover:bg-primary/20 transition-colors">
<Icon
icon="carbon:arrow-right"
class="text-primary transform group-hover:translate-x-4 transition-transform duration-300"
width="16"
height="16"
/>
<Icon
icon="carbon:arrow-right"
class="text-primary absolute -left-4 transform group-hover:translate-x-4 transition-transform duration-300"
width="16"
height="16"
/>
</div>
</button>
</div>
</div>
</div>
</div>
<!-- 查看更多按钮 -->
<div class="text-center mt-12" data-aos="fade-up" data-aos-delay="300">
<button class="px-8 py-3 bg-primary text-white rounded-lg hover:bg-primary-dark transition-all duration-300 shadow-button transform hover:-translate-y-1 hover:shadow-lg flex items-center mx-auto">
<span class="mr-2">{{ t('home.news.viewAll') }}</span>
<Icon icon="carbon:arrow-right" width="20" height="20" />
</button>
</div>
</div>
</section>
</template>
<style scoped>
.news-slide-enter-active,
.news-slide-leave-active {
transition: all 0.5s ease;
/* 行数限制 */
.line-clamp-2 {
display: -webkit-box;
-webkit-line-clamp: 2;
-webkit-box-orient: vertical;
overflow: hidden;
}
.news-slide-enter-from {
transform: translateY(20px);
opacity: 0;
.line-clamp-3 {
display: -webkit-box;
-webkit-line-clamp: 3;
-webkit-box-orient: vertical;
overflow: hidden;
}
/* AOS自定义动画 */
[data-aos="width"] {
width: 0;
transition-property: width;
}
.news-slide-leave-to {
transform: translateY(-20px);
[data-aos="width"].aos-animate {
width: 100%;
}
/* 新闻项悬停效果 */
.news-item {
position: relative;
z-index: 1;
}
.news-item::before {
content: '';
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
border-radius: 0.75rem;
padding: 2px;
background: linear-gradient(45deg, var(--primary, #3B82F6), var(--secondary, #10B981), var(--accent, #F59E0B));
-webkit-mask: linear-gradient(#fff 0 0) content-box, linear-gradient(#fff 0 0);
-webkit-mask-composite: xor;
mask-composite: exclude;
opacity: 0;
transition: opacity 0.5s ease;
z-index: -1;
}
.news-item:hover::before {
opacity: 1;
}
/* 按钮悬停效果 */
button.shadow-button {
box-shadow: 0 4px 14px rgba(59, 130, 246, 0.3);
}
button.shadow-button:hover {
box-shadow: 0 6px 20px rgba(59, 130, 246, 0.5);
}
</style>

+ 150
- 86
src/components/home/PartnersModule.vue View File

@ -1,83 +1,30 @@
<script setup lang="ts">
import { useI18n } from 'vue-i18n';
import { ref, onMounted } from 'vue';
import { queryInvestorList } from '@/api/modules/home';
import type { InvestorItem } from '@/api/modules/home';
import { ref, onMounted, nextTick } from 'vue';
import { queryPartnerList } from '@/api';
import type { PartnerItem } from '@/api/modules/about';
import { Icon } from '@iconify/vue';
import { gsap } from 'gsap';
import AOS from 'aos';
const { t } = useI18n();
//
const partners = ref([
{
id: 1,
name: 'Partner 1',
logo: '/LOGO.png', // public
url: 'https://partner1.com',
description: '战略合作伙伴描述信息'
},
{
id: 2,
name: 'Partner 2',
logo: '/LOGO.png',
url: 'https://partner2.com',
description: '战略合作伙伴描述信息'
},
{
id: 3,
name: 'Partner 3',
logo: '/LOGO.png',
url: 'https://partner3.com',
description: '战略合作伙伴描述信息'
},
{
id: 4,
name: 'Partner 4',
logo: '/LOGO.png',
url: 'https://partner4.com',
description: '战略合作伙伴描述信息'
},
{
id: 5,
name: 'Partner 5',
logo: '/LOGO.png',
url: 'https://partner5.com',
description: '战略合作伙伴描述信息'
},
{
id: 6,
name: 'Partner 6',
logo: '/LOGO.png',
url: 'https://partner6.com',
description: '战略合作伙伴描述信息'
}
]);
const partners = ref<PartnerItem[]>([]);
const loading = ref(false);
const error = ref<string | null>(null);
const partnerSection = ref<HTMLElement | null>(null);
const partnerCards = ref<HTMLElement[]>([]);
//
//
const fetchPartners = async () => {
loading.value = true;
error.value = null;
try {
const response = await queryInvestorList();
// API
const convertPartnerData = (apiPartners: any[]) => {
return apiPartners.map(partner => ({
id: parseInt(partner.id) || Math.floor(Math.random() * 10000), // ID
name: partner.title,
logo: partner.image || '/LOGO.png',
url: partner.url || '#',
description: partner.description || '战略合作伙伴'
}));
};
// API
const response = await queryPartnerList();
if (response && Array.isArray(response) && response.length > 0) {
const apiPartners = response;
//
partners.value = convertPartnerData(apiPartners);
partners.value = response;
}
} catch (err) {
console.error('Failed to fetch partners:', err);
@ -87,23 +34,100 @@ const fetchPartners = async () => {
}
};
//
const initAnimations = () => {
// AOS
AOS.init({
duration: 800,
easing: 'ease-out-cubic',
once: true
});
// 使GSAP
nextTick(() => {
partnerCards.value.forEach((card: HTMLElement) => {
const inner = card.querySelector('.partner-inner');
const logo = card.querySelector('.partner-logo');
if (!inner || !logo) return;
//
card.addEventListener('mouseenter', () => {
gsap.to(inner, {
rotationY: 180,
duration: 0.6,
ease: 'power2.out'
});
gsap.to(logo, {
scale: 1.1,
duration: 0.4
});
});
card.addEventListener('mouseleave', () => {
gsap.to(inner, {
rotationY: 0,
duration: 0.6,
ease: 'power2.out'
});
gsap.to(logo, {
scale: 1,
duration: 0.4
});
});
});
});
};
//
const initCardAnimations = () => {
if (!partnerSection.value) {
console.warn('Partner section element not found');
return;
}
const cards = partnerSection.value.querySelectorAll('.partner-card');
gsap.from(cards, {
opacity: 0,
y: 30,
stagger: 0.08,
duration: 0.5,
ease: "power2.out",
scrollTrigger: {
trigger: partnerSection.value,
start: "top 80%",
}
});
};
onMounted(() => {
fetchPartners();
initAnimations();
// DOM
nextTick(() => {
initCardAnimations();
});
});
</script>
<template>
<section class="py-16 px-6 md:px-12 lg:px-24 bg-background">
<div class="container mx-auto">
<section ref="partnerSection" class="py-16 px-6 md:px-12 lg:px-24 bg-background relative overflow-hidden">
<!-- 背景装饰 -->
<div class="absolute inset-0 overflow-hidden pointer-events-none">
<div class="absolute -top-40 -left-40 w-96 h-96 rounded-full bg-primary/5 blur-[100px]"></div>
<div class="absolute bottom-20 right-0 w-80 h-80 rounded-full bg-secondary/5 blur-[80px]"></div>
</div>
<div class="container mx-auto relative z-10">
<div class="text-center mb-12">
<div class="flex items-center justify-center mb-4">
<div class="h-1 w-12 bg-accent mr-4"></div>
<h2 class="text-2xl md:text-3xl font-bold text-text wow animate__animated animate__fadeInUp animate__duration-fast">
<div class="relative inline-block" data-aos="fade-up">
<h2 class="text-2xl md:text-3xl lg:text-4xl font-bold text-text mb-4">
{{ t('home.partners.title') }}
</h2>
<div class="h-1 w-12 bg-accent ml-4"></div>
<div class="absolute -bottom-2 left-0 w-full h-1 bg-gradient-to-r from-primary via-secondary to-accent opacity-70 rounded-full" data-aos="width" data-aos-delay="200"></div>
</div>
<p class="text-text-secondary max-w-2xl mx-auto wow animate__animated animate__fadeIn animate__delay-xs">
<p class="text-text-secondary max-w-2xl mx-auto" data-aos="fade-up" data-aos-delay="100">
{{ t('home.partners.subtitle') }}
</p>
</div>
@ -120,14 +144,9 @@ onMounted(() => {
<div
v-for="(partner, index) in partners"
:key="partner.id"
class="partner-card wow animate__animated animate__fadeInUp"
:class="{
'animate__delay-xs': index % 6 === 1,
'animate__delay-sm': index % 6 === 5,
'animate__delay-md': index % 6 === 2,
'animate__delay-lg': index % 6 === 4,
'animate__delay-xl': index % 6 === 3
}"
class="partner-card"
:data-aos-delay="index * 50"
ref="el => { if(el) partnerCards[index] = el }"
>
<div class="partner-inner">
<div class="partner-front">
@ -135,7 +154,7 @@ onMounted(() => {
rel="noopener noreferrer"
class="w-full h-20 flex items-center justify-center"
>
<img :src="partner.logo" :alt="partner.name" class="max-w-full max-h-full object-contain" />
<img :src="partner.logo" :alt="partner.name" class="partner-logo max-w-full max-h-full object-contain transition-transform duration-300" />
</div>
<h3 class="text-sm font-medium text-text truncate mt-2 text-center">{{ partner.name }}</h3>
</div>
@ -143,11 +162,28 @@ onMounted(() => {
<div class="text-center p-3">
<h4 class="text-sm font-bold text-white mb-2">{{ partner.name }}</h4>
<p class="text-xs text-white" v-html="partner.description"></p>
<!-- 添加链接按钮 -->
<a :href="partner.url" target="_blank" rel="noopener noreferrer" class="mt-2 inline-flex items-center justify-center px-3 py-1 bg-white/20 rounded-full text-xs text-white hover:bg-white/30 transition-colors">
<Icon icon="carbon:link" class="mr-1" width="12" height="12" />
<span>访问</span>
</a>
</div>
</div>
</div>
<!-- 添加发光效果 -->
<div class="glow"></div>
</div>
</div>
<!-- 查看更多按钮 -->
<div class="text-center mt-10" data-aos="fade-up" data-aos-delay="300">
<button class="px-8 py-3 bg-gradient-to-r from-primary to-primary-light text-white rounded-lg transition-all duration-300 transform hover:-translate-y-1 hover:shadow-lg flex items-center mx-auto">
<span class="mr-2">{{ t('home.partners.view_all') }}</span>
<Icon icon="carbon:arrow-right" width="20" height="20" />
</button>
</div>
</div>
</section>
</template>
@ -156,6 +192,7 @@ onMounted(() => {
.partner-card {
perspective: 1000px;
height: 120px;
position: relative;
}
.partner-inner {
@ -168,10 +205,6 @@ onMounted(() => {
box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -1px rgba(0, 0, 0, 0.06);
}
.partner-card:hover .partner-inner {
transform: rotateY(180deg);
}
.partner-front, .partner-back {
position: absolute;
width: 100%;
@ -179,6 +212,7 @@ onMounted(() => {
-webkit-backface-visibility: hidden;
backface-visibility: hidden;
border-radius: 0.75rem;
overflow: hidden;
}
.partner-front {
@ -187,14 +221,44 @@ onMounted(() => {
display: flex;
flex-direction: column;
justify-content: center;
border: 1px solid rgba(255, 255, 255, 0.1);
}
.partner-back {
background-color: var(--color-secondary);
background: linear-gradient(135deg, var(--color-primary), var(--color-secondary));
color: white;
transform: rotateY(180deg);
display: flex;
align-items: center;
justify-content: center;
}
/* 发光效果 */
.glow {
position: absolute;
top: -2px;
left: -2px;
right: -2px;
bottom: -2px;
background: linear-gradient(45deg, var(--color-primary), var(--color-secondary), var(--color-accent));
z-index: -1;
border-radius: 0.85rem;
opacity: 0;
transition: opacity 0.3s ease;
filter: blur(8px);
}
.partner-card:hover .glow {
opacity: 0.7;
}
/* AOS自定义动画 */
[data-aos="width"] {
width: 0;
transition-property: width;
}
[data-aos="width"].aos-animate {
width: 100%;
}
</style>

+ 186
- 71
src/components/home/PostsModule.vue View File

@ -1,9 +1,12 @@
<script setup lang="ts">
import { useI18n } from 'vue-i18n';
import { ref, onMounted } from 'vue';
import { queryPostList } from '@/api/modules/home';
import { ref, onMounted, nextTick } from 'vue';
import { queryPostList } from '@/api';
import type { PostItem } from '@/api/modules/home';
import dayjs from 'dayjs';
import { Icon } from '@iconify/vue';
import { gsap } from 'gsap';
import AOS from 'aos';
const { t } = useI18n();
@ -11,8 +14,10 @@ const { t } = useI18n();
const posts = ref<PostItem[]>([]);
const loading = ref(false);
const error = ref<string | null>(null);
const postSection = ref<HTMLElement | null>(null);
const postItems = ref<HTMLElement[]>([]);
//
//
const fetchPosts = async () => {
loading.value = true;
error.value = null;
@ -21,73 +26,81 @@ const fetchPosts = async () => {
const response = await queryPostList();
if (response && Array.isArray(response) && response.length > 0) {
posts.value = response;
} else {
// API使
posts.value = [
{
id: '1',
title: t('home.news.mainnet_launch'),
image: '/public/LOGO.png',
description: '2025年7月,MOSE主网将正式上线,实现完整的跨链隐私交易功能。',
createTime: '2024-03-15'
},
{
id: '2',
title: t('home.news.mas_license'),
image: '/public/LOGO.png',
description: 'MOSE获得新加坡金融管理局(MAS)颁发的支付服务牌照,合规发展迈出重要一步。',
createTime: '2024-02-20'
},
{
id: '3',
title: t('home.news.hackathon'),
image: '/public/LOGO.png',
description: '首届MOSE全球开发者黑客松大赛正式启动,邀请全球开发者参与生态建设。',
createTime: '2024-04-10'
}
];
}
} catch (err) {
console.error('Failed to fetch posts:', err);
error.value = 'Failed to load posts data';
// 使
posts.value = [
{
id: '1',
title: t('home.news.mainnet_launch'),
image: '/public/LOGO.png',
description: '2025年7月,MOSE主网将正式上线,实现完整的跨链隐私交易功能。',
createTime: '2024-03-15'
}
];
} finally {
loading.value = false;
}
};
//
// const formatDate = (dateString: string) => {
// if (!dateString) return '';
const formatDate = (dateString?: string) => {
if (!dateString) return '';
// const date = new Date(dateString);
// return new Intl.DateTimeFormat('zh-CN', {
// year: 'numeric',
// month: 'long',
// day: 'numeric'
// }).format(date);
// };
const date = new Date(dateString);
return new Intl.DateTimeFormat('zh-CN', {
year: 'numeric',
month: 'short',
day: 'numeric'
}).format(date);
};
//
const initAnimations = () => {
AOS.init({
duration: 800,
easing: 'ease-out-cubic',
once: true
});
// DOM
nextTick(() => {
if (!postSection.value) {
console.warn('Post section element not found');
return;
}
const staggerItems = postSection.value.querySelectorAll('.post-item');
gsap.from(staggerItems, {
opacity: 0,
y: 30,
stagger: 0.15,
duration: 0.8,
ease: "power2.out",
scrollTrigger: {
trigger: postSection.value,
start: "top 70%",
}
});
});
};
onMounted(() => {
fetchPosts();
initAnimations();
});
</script>
<template>
<section class="py-16 px-6 md:px-12 lg:px-24 bg-background-light">
<div class="container mx-auto">
<h2 class="text-2xl md:text-3xl font-bold text-text mb-8 text-center wow animate__animated animate__fadeInUp animate__duration-fast">
{{ t('home.posts.title') }}
</h2>
<section ref="postSection" class="py-16 px-6 md:px-12 lg:px-24 bg-background-light relative overflow-hidden">
<!-- 背景装饰 -->
<div class="absolute inset-0 overflow-hidden pointer-events-none">
<div class="absolute -top-20 -right-20 w-80 h-80 rounded-full bg-primary/5 blur-[100px]"></div>
<div class="absolute bottom-40 -left-20 w-96 h-96 rounded-full bg-secondary/5 blur-[120px]"></div>
</div>
<div class="container mx-auto relative z-10">
<!-- 标题 -->
<div class="flex items-center justify-center mb-12">
<div class="relative">
<h2 class="text-2xl md:text-3xl lg:text-4xl font-bold text-text text-center" data-aos="fade-up">
{{ t('home.posts.title') }}
</h2>
<div class="absolute -bottom-3 left-0 w-full h-1 bg-gradient-to-r from-primary via-secondary to-accent opacity-70 rounded-full" data-aos="width" data-aos-delay="200"></div>
</div>
</div>
<div v-if="loading" class="flex justify-center py-10">
<div class="animate-spin rounded-full h-10 w-10 border-t-2 border-b-2 border-primary"></div>
@ -97,42 +110,81 @@ onMounted(() => {
{{ error }}
</div>
<div v-else class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-8">
<div v-else class="space-y-8">
<!-- 一行一个动态项 -->
<div
v-for="post in posts"
v-for="(post, index) in posts"
:key="post.id"
class="bg-background rounded-xl overflow-hidden shadow-card hover:shadow-lg transition-all duration-300 hover:transform hover:scale-105 wow animate__animated animate__fadeIn"
class="post-item bg-background rounded-xl overflow-hidden shadow-card hover:shadow-xl transition-all duration-500 flex flex-col md:flex-row"
:class="index % 2 === 0 ? 'md:flex-row' : 'md:flex-row-reverse'"
ref="el => { if(el) postItems[index] = el }"
>
<div class="relative h-48 overflow-hidden">
<!-- 图片容器 -->
<div class="relative w-full md:w-2/5 h-56 md:h-auto overflow-hidden">
<img
:src="post.image"
:alt="post.title"
class="w-full h-full object-cover"
class="post-image w-full h-full object-cover transition-transform duration-700"
/>
<div class="absolute top-0 right-0 bg-primary text-white px-3 py-1 rounded-bl-lg text-sm">
{{ dayjs(post.createTime).format('YYYY-MM-DD') }}
<div class="post-overlay absolute inset-0 bg-gradient-to-r from-primary/40 to-secondary/40 opacity-40 transition-opacity duration-500"></div>
<!-- 日期标签 -->
<div class="date-badge absolute top-4 left-4 bg-black/50 backdrop-blur-sm px-3 py-1 rounded-lg flex items-center transition-all duration-300">
<Icon icon="carbon:calendar" class="text-primary-light mr-1.5" width="16" height="16" />
<span class="text-white text-xs font-medium">{{ formatDate(post.createTime) }}</span>
</div>
<!-- 装饰元素 -->
<div class="absolute bottom-0 left-0 w-full h-1/3 bg-gradient-to-t from-black/60 to-transparent"></div>
<!-- 动画装饰 -->
<div class="absolute -bottom-2 left-0 w-full h-1 bg-gradient-to-r from-primary via-secondary to-accent"></div>
</div>
<div class="p-6">
<h3 class="text-xl font-bold text-text mb-3 line-clamp-2">{{ post.title }}</h3>
<p class="text-text-secondary line-clamp-3" v-html="post.description"></p>
<!-- 内容容器 -->
<div class="post-content w-full md:w-3/5 p-6 md:p-8 flex flex-col justify-between relative">
<div>
<h3 class="text-xl md:text-2xl font-bold text-text mb-3 line-clamp-2 hover:text-primary transition-colors duration-300">
{{ post.title }}
</h3>
<p class="text-text-secondary line-clamp-3 mb-4" v-html="post.description"></p>
</div>
<div class="mt-4 flex justify-end">
<button class="text-primary hover:text-primary-dark transition-colors font-medium flex items-center">
{{ t('home.posts.readMore') }}
<svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5 ml-1" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M14 5l7 7m0 0l-7 7m7-7H3" />
</svg>
<div class="flex justify-between items-center">
<!-- 标签 -->
<div class="flex items-center">
<span class="bg-primary/10 text-primary px-3 py-1 rounded-full text-sm font-medium flex items-center">
<Icon icon="carbon:notification" class="mr-1.5" width="14" height="14" />
{{ t('home.news.category') }}
</span>
</div>
<!-- 阅读更多按钮 -->
<button class="group flex items-center text-primary hover:text-primary-dark transition-colors">
<span class="mr-2 font-medium">{{ t('home.posts.readMore') }}</span>
<div class="relative w-6 h-6 rounded-full bg-primary/10 flex items-center justify-center overflow-hidden group-hover:bg-primary/20 transition-colors">
<Icon
icon="carbon:arrow-right"
class="arrow-icon text-primary transform transition-transform duration-300 opacity-70"
width="16"
height="16"
/>
</div>
</button>
</div>
<!-- 装饰元素 -->
<div class="absolute top-0 right-0 w-20 h-20 bg-primary opacity-5 rounded-full -translate-y-1/2 translate-x-1/2"></div>
<div class="absolute bottom-0 left-0 w-16 h-16 bg-secondary opacity-5 rounded-full translate-y-1/2 -translate-x-1/2"></div>
</div>
</div>
</div>
<div class="text-center mt-8">
<button class="px-6 py-2 bg-primary text-white rounded-lg hover:bg-primary-dark transition-colors duration-300 shadow-button btn-hover-float wow animate__animated animate__fadeIn animate__delay-sm">
{{ t('home.posts.viewAll') }}
<!-- 查看更多按钮 -->
<div class="text-center mt-12" data-aos="fade-up" data-aos-delay="300">
<button class="px-8 py-3 bg-gradient-to-r from-primary to-primary-light text-white rounded-lg hover:bg-primary-dark transition-all duration-300 shadow-glow hover:shadow-glow-intense transform hover:-translate-y-1 hover:shadow-lg flex items-center mx-auto">
<span class="mr-2">{{ t('home.posts.viewAll') }}</span>
<Icon icon="carbon:arrow-right" width="20" height="20" />
</button>
</div>
</div>
@ -140,6 +192,7 @@ onMounted(() => {
</template>
<style scoped>
/* 行数限制 */
.line-clamp-2 {
display: -webkit-box;
-webkit-line-clamp: 2;
@ -153,4 +206,66 @@ onMounted(() => {
-webkit-box-orient: vertical;
overflow: hidden;
}
/* 动态项悬停效果 */
.post-item {
position: relative;
z-index: 1;
border: 1px solid rgba(255, 255, 255, 0.1);
}
.post-item::before {
content: '';
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
border-radius: 0.75rem;
padding: 2px;
background: linear-gradient(45deg, var(--primary, #3B82F6), var(--secondary, #10B981), var(--accent, #F59E0B));
-webkit-mask: linear-gradient(#fff 0 0) content-box, linear-gradient(#fff 0 0);
-webkit-mask-composite: xor;
mask-composite: exclude;
opacity: 0;
transition: opacity 0.5s ease;
z-index: -1;
}
.post-item:hover::before {
opacity: 1;
}
/* 按钮悬停效果 */
.shadow-glow {
box-shadow: 0 4px 14px rgba(59, 130, 246, 0.3);
}
.shadow-glow-intense {
box-shadow: 0 6px 20px rgba(59, 130, 246, 0.5);
}
/* 交替布局的特殊样式 */
.post-item:nth-child(even) .post-overlay {
background: linear-gradient(to left, var(--primary)/40%, var(--secondary)/40%);
}
/* AOS自定义动画 */
[data-aos="width"] {
width: 0;
transition-property: width;
}
[data-aos="width"].aos-animate {
width: 100%;
}
/* 日期标签悬停效果 */
.date-badge {
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.2);
}
.post-item:hover .date-badge {
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.3);
}
</style>

+ 284
- 27
src/components/home/ProjectIntroModule.vue View File

@ -1,51 +1,196 @@
<script setup lang="ts">
import { useI18n } from 'vue-i18n';
import { useSummary } from '@/utils/config';
import { ref, onMounted } from 'vue';
import { Icon } from '@iconify/vue';
const { t } = useI18n();
const { getSummaryDescription } = useSummary();
//
const videoRef = ref<HTMLVideoElement | null>(null);
const isPlaying = ref(false);
const isMuted = ref(false);
// /
const togglePlay = () => {
const video = videoRef.value;
if (!video) return;
if (video.paused) {
video.play();
isPlaying.value = true;
} else {
video.pause();
isPlaying.value = false;
}
};
// /
const toggleMute = () => {
const video = videoRef.value;
if (!video) return;
video.muted = !video.muted;
isMuted.value = video.muted;
};
//
onMounted(() => {
const video = videoRef.value;
if (video) {
//
isMuted.value = video.muted;
//
video.muted = true;
isMuted.value = true;
const playPromise = video.play();
if (playPromise !== undefined) {
playPromise.then(() => {
isPlaying.value = true;
}).catch((error: Error) => {
console.log('自动播放失败:', error);
isPlaying.value = false;
});
}
}
});
</script>
<template>
<section class="py-16 px-6 md:px-12 lg:px-24 bg-background-light">
<section class="py-16 px-6 md:px-12 lg:px-24">
<div class="container mx-auto">
<div class="max-w-4xl mx-auto bg-background p-8 md:p-10 rounded-2xl shadow-card relative overflow-hidden wow animate__animated animate__fadeIn animate__duration-normal">
<div class="relative z-10">
<h2 class="text-2xl md:text-3xl font-bold text-text mb-6 text-center wow animate__animated animate__fadeInUp animate__duration-fast">
<!-- 标题 -->
<h2 class="text-2xl md:text-3xl lg:text-4xl font-bold text-text mb-12 text-center wow animate__animated animate__fadeInUp animate__duration-fast">
{{ t('home.project_intro.title') }}
</h2>
<div class="prose prose-invert max-w-none text-text-secondary">
<!-- 使用富文本内容 -->
<div class="mb-6 text-lg leading-relaxed wow animate__animated animate__fadeIn animate__delay-xs rich-text-container" v-html="getSummaryDescription('config_Introduction_of_platform')"></div>
<div class="max-w-5xl mx-auto">
<!-- 视频容器 -->
<div class="video-wrapper mb-12 wow animate__animated animate__fadeIn animate__duration-normal">
<div class="relative rounded-2xl overflow-hidden shadow-2xl transform hover:scale-[1.01] transition-all duration-700">
<!-- 视频元素 -->
<video
ref="videoRef"
src="/public/MOSEVideo.mp4"
class="w-full h-full object-cover"
poster="/public/LOGO.png"
loop
playsinline
></video>
<!-- 永久显示的播放/暂停按钮 -->
<div class="absolute top-1/2 left-1/2 transform -translate-x-1/2 -translate-y-1/2 z-20">
<button
@click="togglePlay"
class="play-button w-16 h-16 md:w-20 md:h-20 bg-primary/80 rounded-full flex items-center justify-center hover:bg-primary transition-colors duration-300"
>
<Icon
:icon="isPlaying ? 'carbon:pause-filled' : 'carbon:play-filled'"
class="text-white"
width="32"
height="32"
/>
</button>
</div>
<!-- 视频右上角图标组 - 新增 -->
<div class="absolute top-4 right-4 flex space-x-3 z-10">
<div class="video-icon-badge flex items-center bg-black/50 backdrop-blur-sm px-3 py-1.5 rounded-full">
<Icon icon="carbon:video" class="text-primary-light mr-2" width="16" height="16" />
<span class="text-white text-xs font-medium">HD</span>
</div>
<button class="video-icon-btn bg-black/50 backdrop-blur-sm w-8 h-8 rounded-full flex items-center justify-center hover:bg-primary/70 transition-colors">
<Icon icon="carbon:information" class="text-white" width="16" height="16" />
</button>
</div>
<!-- 悬停显示的控制层 -->
<div class="absolute inset-0 bg-gradient-to-t from-black/70 via-transparent to-black/30 opacity-0 hover:opacity-100 transition-opacity duration-300">
<!-- 底部控制栏 -->
<div class="absolute bottom-0 left-0 right-0 p-4 flex justify-between items-center">
<div class="flex items-center space-x-3">
<button
@click="toggleMute"
class="text-white hover:text-primary-light transition-colors"
>
<Icon
:icon="isMuted ? 'carbon:volume-mute-filled' : 'carbon:volume-up-filled'"
width="24"
height="24"
/>
</button>
<!-- 新增视频时间指示器 -->
<div class="hidden md:flex items-center space-x-1">
<div class="video-time-indicator bg-black/40 backdrop-blur-sm px-2 py-0.5 rounded text-white text-xs">
<Icon icon="carbon:time" class="inline-block mr-1 text-primary-light" width="12" height="12" />
<span>01:45</span>
</div>
</div>
</div>
<div class="flex items-center space-x-3">
<div class="video-badge px-3 py-1 bg-primary/80 text-white text-sm rounded-full flex items-center">
<Icon icon="carbon:play-filled-alt" class="mr-1 text-white" width="14" height="14" />
{{ t('home.project_intro.watch_video') }}
</div>
<!-- 新增全屏按钮 -->
<button class="text-white hover:text-primary-light transition-colors hidden md:block">
<Icon icon="carbon:maximize" width="20" height="20" />
</button>
</div>
</div>
<!-- 新增顶部渐变阴影 -->
<div class="absolute top-0 left-0 right-0 h-16 bg-gradient-to-b from-black/60 to-transparent"></div>
</div>
<!-- 装饰元素 -->
<div class="absolute -top-10 -right-10 w-40 h-40 rounded-full bg-primary opacity-20 blur-3xl"></div>
<div class="absolute -bottom-10 -left-10 w-40 h-40 rounded-full bg-secondary opacity-20 blur-3xl"></div>
<!-- 新增视频角标 -->
<div class="absolute top-4 left-4 bg-primary/80 backdrop-blur-sm px-3 py-1 rounded-lg flex items-center z-10">
<Icon icon="carbon:badge" class="text-white mr-1.5" width="14" height="14" />
<span class="text-white text-xs font-medium">MOSE</span>
</div>
</div>
</div>
<!-- 文字介绍 -->
<div class="content-wrapper bg-background-light p-8 md:p-10 rounded-2xl shadow-card relative overflow-hidden wow animate__animated animate__fadeInUp animate__duration-fast animate__delay-xs">
<div class="relative z-10">
<!-- 富文本内容 -->
<div class="prose max-w-none text-text-secondary mb-8">
<div class="text-lg leading-relaxed rich-text-container" v-html="getSummaryDescription('config_Introduction_of_platform')"></div>
</div>
<div class="flex flex-col md:flex-row items-center justify-center gap-4 mt-8 wow animate__animated animate__fadeIn animate__delay-sm">
<div class="flex items-center bg-background-dark px-4 py-3 rounded-lg hover:bg-background-dark/80 transition-colors duration-300 transform hover:-translate-y-1">
<!-- 特点标签 -->
<div class="flex flex-wrap justify-center gap-4 mt-8">
<div class="feature-tag flex items-center bg-background-dark px-4 py-3 rounded-lg transform transition-all duration-300 hover:-translate-y-1 hover:shadow-lg">
<div class="w-10 h-10 rounded-full bg-primary bg-opacity-20 flex items-center justify-center mr-3">
<svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5 text-primary-light" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M3.055 11H5a2 2 0 012 2v1a2 2 0 002 2 2 2 0 012 2v2.945M8 3.935V5.5A2.5 2.5 0 0010.5 8h.5a2 2 0 012 2 2 2 0 104 0 2 2 0 012-2h1.064M15 20.488V18a2 2 0 012-2h3.064M21 12a9 9 0 11-18 0 9 9 0 0118 0z" />
</svg>
<Icon icon="carbon:earth" class="h-5 w-5 text-primary-light" />
</div>
<span class="text-text font-medium">{{ t('home.project_intro.global_presence') }}</span>
</div>
<div class="flex items-center bg-background-dark px-4 py-3 rounded-lg hover:bg-background-dark/80 transition-colors duration-300 transform hover:-translate-y-1">
<div class="feature-tag flex items-center bg-background-dark px-4 py-3 rounded-lg transform transition-all duration-300 hover:-translate-y-1 hover:shadow-lg">
<div class="w-10 h-10 rounded-full bg-secondary bg-opacity-20 flex items-center justify-center mr-3">
<svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5 text-secondary" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 12l2 2 4-4m5.618-4.016A11.955 11.955 0 0112 2.944a11.955 11.955 0 01-8.618 3.04A12.02 12.02 0 003 9c0 5.591 3.824 10.29 9 11.622 5.176-1.332 9-6.03 9-11.622 0-1.042-.133-2.052-.382-3.016z" />
</svg>
<Icon icon="carbon:security" class="h-5 w-5 text-secondary" />
</div>
<span class="text-text font-medium">{{ t('home.project_intro.security_focus') }}</span>
</div>
<div class="flex items-center bg-background-dark px-4 py-3 rounded-lg hover:bg-background-dark/80 transition-colors duration-300 transform hover:-translate-y-1">
<div class="feature-tag flex items-center bg-background-dark px-4 py-3 rounded-lg transform transition-all duration-300 hover:-translate-y-1 hover:shadow-lg">
<div class="w-10 h-10 rounded-full bg-accent bg-opacity-20 flex items-center justify-center mr-3">
<svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5 text-accent" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M13 10V3L4 14h7v7l9-11h-7z" />
</svg>
<Icon icon="carbon:idea" class="h-5 w-5 text-accent" />
</div>
<span class="text-text font-medium">{{ t('home.project_intro.innovation') }}</span>
</div>
</div>
</div>
</div>
@ -54,33 +199,145 @@ const { getSummaryDescription } = useSummary();
<div class="absolute top-0 left-0 w-full h-full overflow-hidden opacity-5">
<div class="absolute top-1/3 right-1/4 w-64 h-64 rounded-full bg-primary-light blur-3xl"></div>
<div class="absolute bottom-1/3 left-1/4 w-64 h-64 rounded-full bg-secondary blur-3xl"></div>
</div>
</div>
</div>
</div>
</section>
</template>
<style>
<style scoped>
/* 视频容器样式 */
.video-wrapper {
position: relative;
}
.video-wrapper::before {
content: '';
position: absolute;
top: -5px;
left: -5px;
right: -5px;
bottom: -5px;
background: linear-gradient(45deg, var(--primary, #3B82F6), var(--secondary, #10B981), var(--accent, #F59E0B));
border-radius: 1rem;
z-index: -1;
opacity: 0.5;
filter: blur(15px);
transition: opacity 0.3s ease;
}
.video-wrapper:hover::before {
opacity: 0.7;
}
/* 播放按钮动画 */
.play-button {
box-shadow: 0 0 0 0 rgba(59, 130, 246, 0.5);
animation: pulse-ring 2s infinite;
opacity: 0.9;
transition: opacity 0.3s ease, transform 0.3s ease;
}
.play-button:hover {
opacity: 1;
transform: scale(1.05);
}
@keyframes pulse-ring {
0% {
box-shadow: 0 0 0 0 rgba(59, 130, 246, 0.5);
}
70% {
box-shadow: 0 0 0 20px rgba(59, 130, 246, 0);
}
100% {
box-shadow: 0 0 0 0 rgba(59, 130, 246, 0);
}
}
/* 视频图标按钮 */
.video-icon-btn {
transition: all 0.3s ease;
}
.video-icon-btn:hover {
transform: translateY(-2px);
}
.video-icon-badge {
transition: all 0.3s ease;
}
.video-icon-badge:hover {
background-color: rgba(0, 0, 0, 0.7);
}
/* 特点标签动画 */
.feature-tag {
position: relative;
overflow: hidden;
}
.feature-tag::after {
content: '';
position: absolute;
top: 0;
left: -100%;
width: 100%;
height: 100%;
background: linear-gradient(90deg, transparent, rgba(255, 255, 255, 0.1), transparent);
transition: left 0.7s ease;
}
.feature-tag:hover::after {
left: 100%;
}
/* 富文本容器样式 */
.rich-text-container {
color: var(--text-secondary);
}
/* 增强标题样式 */
.rich-text-container [style*="color: #df9a33"],
.rich-text-container [style*="color: #f99807"],
.rich-text-container [style*="color: #f9990a"] {
.rich-text-container :deep([style*="color: #df9a33"]),
.rich-text-container :deep([style*="color: #f99807"]),
.rich-text-container :deep([style*="color: #f9990a"]) {
color: var(--primary-light) !important;
font-weight: 600;
}
/* 段落间距 */
.rich-text-container p {
.rich-text-container :deep(p) {
margin-bottom: 0.75rem;
line-height: 1.7;
}
/* 确保内容可读性 */
.rich-text-container div {
.rich-text-container :deep(div) {
margin-bottom: 0.5rem;
}
/* 链接样式 */
.rich-text-container :deep(a) {
color: var(--primary, #3B82F6);
text-decoration: underline;
transition: color 0.3s;
}
.rich-text-container :deep(a:hover) {
color: var(--primary-dark, #2563EB);
}
/* 列表样式 */
.rich-text-container :deep(ul),
.rich-text-container :deep(ol) {
margin-left: 1.5rem;
margin-bottom: 1rem;
}
.rich-text-container :deep(li) {
margin-bottom: 0.5rem;
line-height: 1.7;
}
</style>

+ 2
- 2
src/components/layout/NavBar.vue View File

@ -51,8 +51,8 @@ const emit = defineEmits(['changeLanguage']);
<div class="container mx-auto flex justify-between items-center">
<!-- Logo -->
<router-link to="/" class="flex items-center animate__animated animate__fadeIn animate__duration-fast">
<img src="/public/LOGO.png" alt="MOSE Logo" class="h-8 w-auto mr-2" />
<span class="text-xl font-bold text-primary-light">MOSE</span>
<img src="/public/图.jpg" alt="MOSE Logo" class="h-8 w-auto mr-2" />
<!-- <span class="text-xl font-bold text-primary-light">MOSE</span> -->
</router-link>
<!-- Desktop Navigation -->


+ 82
- 0
src/env.d.ts View File

@ -0,0 +1,82 @@
/// <reference types="vite/client" />
declare module '*.vue' {
import type { DefineComponent } from 'vue'
const component: DefineComponent<{}, {}, any>
export default component
}
// 全局声明
interface Window {
WOW: any;
}
// 声明模块
declare module 'vue-i18n' {
export const useI18n: any
export const createI18n: any
export * from '@intlify/vue-i18n-bridge'
}
declare module '@iconify/vue' {
import { DefineComponent } from 'vue'
export const Icon: DefineComponent<{
icon: string;
width?: string | number;
height?: string | number;
color?: string;
inline?: boolean;
}, {}, any>
}
declare module 'aos' {
interface AosOptions {
offset?: number;
delay?: number;
duration?: number;
easing?: string;
once?: boolean;
mirror?: boolean;
anchorPlacement?: string;
startEvent?: string;
animatedClassName?: string;
initClassName?: string;
useClassNames?: boolean;
disableMutationObserver?: boolean;
throttleDelay?: number;
debounceDelay?: number;
}
function init(options?: AosOptions): void;
function refresh(hard?: boolean): void;
const aos: {
init: typeof init;
refresh: typeof refresh;
};
export { init, refresh };
export default aos;
}
declare module 'gsap' {
export const gsap: any
export function to(target: any, vars: any): any
export function from(target: any, vars: any): any
export function fromTo(target: any, fromVars: any, toVars: any): any
export function set(target: any, vars: any): any
export function timeline(vars?: any): any
}
declare module 'gsap/ScrollTrigger' {
export const ScrollTrigger: any
export default ScrollTrigger
}
declare module 'swiper/bundle' {
import Swiper from 'swiper'
export { Swiper }
export default Swiper
}
declare module 'swiper/css/bundle' {}

+ 17
- 13
src/i18n/locales/en.json View File

@ -22,11 +22,11 @@
},
"media": {
"title": "Media Coverage",
"subtitle": "Global media coverage and recognition of MOSE"
"subtitle": "Global media attention and coverage of MOSE"
},
"posts": {
"title": "Latest Updates",
"subtitle": "Stay informed about MOSE's latest developments and important announcements",
"subtitle": "Learn about MOSE's latest developments and important announcements",
"readMore": "Read More",
"viewAll": "View All"
},
@ -34,12 +34,12 @@
"title": "Core Values",
"privacy": {
"title": "Privacy Protection",
"desc1": "Zero-knowledge proof-based anonymous transactions",
"desc1": "Zero-knowledge proof anonymous transactions",
"desc2": "Anonymous atomic swap technology"
},
"interop": {
"title": "Cross-chain Interoperability",
"desc1": "Support for 12+ major blockchains",
"desc1": "Support for 12+ mainstream blockchains",
"desc2": "Ethereum/Cosmos/Polkadot ecosystems"
},
"compliance": {
@ -50,25 +50,29 @@
},
"project_intro": {
"title": "Project Introduction",
"description": "Incubated by the Swiss Crypto Foundation and Singapore Digital Finance Alliance, MOSE focuses on cross-chain privacy technology research and development, providing decentralized privacy infrastructure for Web3. Global presence: Singapore headquarters with ten ecosystem hubs serving North America, Europe, Asia-Pacific, and other core regions.",
"description": "Incubated by Swiss Crypto Foundation and Singapore Digital Finance Alliance, focused on cross-chain privacy technology research and development, providing decentralized privacy infrastructure for Web3. Global layout: Singapore headquarters and ten ecosystem hubs serving North America, Europe, Asia-Pacific, and other core regions.",
"global_presence": "Global Presence",
"security_focus": "Security & Compliance",
"innovation": "Technical Innovation"
"innovation": "Technical Innovation",
"watch_video": "Watch Video"
},
"partners": {
"title": "Partners",
"subtitle": "Collaborating with industry-leading institutions to advance blockchain technology"
"subtitle": "Collaborating with industry-leading organizations to advance blockchain technology"
},
"news": {
"title": "Latest News",
"mainnet_launch": "MOSE mainnet scheduled to launch in July 2025",
"mas_license": "MOSE receives payment service license from Monetary Authority of Singapore (MAS)",
"hackathon": "First MOSE Global Developer Hackathon officially launched"
"mainnet_launch": "MOSE mainnet scheduled for official launch in July 2025",
"mas_license": "MOSE obtains payment service license from Singapore MAS",
"hackathon": "First MOSE Global Developer Hackathon officially launched",
"category": "Announcement",
"readMore": "Read More",
"viewAll": "View All News"
},
"milestone": {
"title": "Milestones",
"july_2026": {
"title": "July 2026 Mainnet Full Launch",
"title": "July 2026 Full Mainnet Launch",
"description": "MOSE mainnet will fully launch in July 2026, implementing complete cross-chain privacy transaction functionality."
},
"achievements": {
@ -77,12 +81,12 @@
"description": "Over 1,000 validator nodes distributed across 50+ countries and regions"
},
"two": {
"title": "Flash Transactions",
"title": "Lightning Transactions",
"description": "0.5-second transaction confirmation, processing 100,000+ transactions per second"
},
"three": {
"title": "Global Ecosystem",
"description": "Connecting 10+ major blockchains, supporting 100+ DApps"
"description": "Connecting 10+ mainstream blockchains, supporting 100+ DApps"
}
}
},


+ 6
- 2
src/i18n/locales/zh.json View File

@ -53,7 +53,8 @@
"description": "由瑞士加密合基金会、新加坡数字金融联盟等联合孵化,专注研发跨链隐私技术,为Web3提供去中心化隐私基础设施。全球布局:新加坡总部与十大生态枢纽,服务北美、欧洲、亚太等核心区域。",
"global_presence": "全球布局",
"security_focus": "安全合规",
"innovation": "技术创新"
"innovation": "技术创新",
"watch_video": "观看视频"
},
"partners": {
"title": "合作伙伴",
@ -63,7 +64,10 @@
"title": "最新动态",
"mainnet_launch": "MOSE主网即将于2025年7月正式上线",
"mas_license": "MOSE获新加坡金融管理局(MAS)支付服务牌照",
"hackathon": "首届MOSE全球开发者黑客松大赛正式启动"
"hackathon": "首届MOSE全球开发者黑客松大赛正式启动",
"category": "公告",
"readMore": "阅读更多",
"viewAll": "查看全部动态"
},
"milestone": {
"title": "里程碑",


+ 11
- 0
src/main.ts View File

@ -3,6 +3,8 @@ import './style.css'
import 'animate.css'
import './assets/animations.css'
import WOW from 'wow.js'
import AOS from 'aos'
import 'aos/dist/aos.css'
import App from './App.vue'
import router from './router'
import i18n from './i18n'
@ -15,6 +17,15 @@ const app = createApp(App)
// 初始化WOW.js
new WOW().init()
// 初始化AOS
AOS.init({
duration: 800,
easing: 'ease-out-cubic',
once: true,
offset: 100,
delay: 100
})
app.use(router)
app.use(i18n)


+ 40
- 0
src/types/global.d.ts View File

@ -0,0 +1,40 @@
// 全局类型声明
// WOW.js 全局声明
interface Window {
WOW: any;
}
// 为 MediaItem 扩展额外的属性
declare namespace API {
interface MediaItem {
source?: string;
date?: string;
}
interface ValueItem {
icon?: string;
}
interface CommunityItem {
description?: string;
url?: string;
username?: string;
likeCount?: number;
}
}
// 声明 GSAP 和 ScrollTrigger 模块
declare module 'gsap/ScrollTrigger' {
export const ScrollTrigger: any;
export default ScrollTrigger;
}
// 声明 Swiper 模块
declare module 'swiper/bundle' {
import Swiper from 'swiper';
export { Swiper };
export default Swiper;
}
declare module 'swiper/css/bundle' {}

+ 36
- 0
src/types/module.d.ts View File

@ -0,0 +1,36 @@
// 模块声明文件
// Vue相关模块
declare module 'vue' {
export * from 'vue/dist/vue'
}
declare module 'vue-router' {
export * from 'vue-router/dist/vue-router'
}
// 工具库
declare module 'dayjs' {
const dayjs: any
export default dayjs
}
// 解决TypeScript编译时的模块导入问题
declare module '@/*' {
const content: any
export default content
}
// 解决组件导入问题
declare module '@/components/*' {
import { DefineComponent } from 'vue'
const component: DefineComponent<{}, {}, any>
export default component
}
// 解决API导入问题
declare module '@/api/*' {
const api: any
export default api
export * from '@/api/modules/*'
}

+ 12
- 0
src/types/swiper.d.ts View File

@ -0,0 +1,12 @@
// Swiper 类型声明
declare module 'swiper/bundle' {
import Swiper from 'swiper';
export { Swiper };
export default Swiper;
}
declare module 'swiper/css/bundle' {
const styles: any;
export default styles;
}

+ 553
- 0
src/types/vue.d.ts View File

@ -0,0 +1,553 @@
// Vue相关类型声明
declare module 'vue' {
export interface GlobalComponents {
// 在这里添加全局组件的类型
}
export const ref: any;
export const reactive: any;
export const computed: any;
export const watch: any;
export const watchEffect: any;
export const onMounted: any;
export const onBeforeUnmount: any;
export const onUnmounted: any;
export const nextTick: any;
export const provide: any;
export const inject: any;
export const createApp: any;
export const defineComponent: any;
export const defineAsyncComponent: any;
export const defineProps: any;
export const defineEmits: any;
export const defineExpose: any;
export const withDefaults: any;
export const h: any;
export const Fragment: any;
export const Teleport: any;
export const Suspense: any;
export const Transition: any;
export const TransitionGroup: any;
export const KeepAlive: any;
export const toRef: any;
export const toRefs: any;
export const toRaw: any;
export const markRaw: any;
export const isRef: any;
export const isProxy: any;
export const isReactive: any;
export const isReadonly: any;
export const shallowRef: any;
export const shallowReactive: any;
export const shallowReadonly: any;
export const customRef: any;
export const triggerRef: any;
export const unref: any;
export const readonly: any;
export const useCssModule: any;
export const useCssVars: any;
export const useAttrs: any;
export const useSlots: any;
export const mergeProps: any;
export const getCurrentInstance: any;
export const useSSRContext: any;
export const defineCustomElement: any;
export const defineSSRCustomElement: any;
export const vModelText: any;
export const vModelCheckbox: any;
export const vModelRadio: any;
export const vModelSelect: any;
export const vModelDynamic: any;
export const vShow: any;
export const vHide: any;
export const withModifiers: any;
export const withKeys: any;
export const withDirectives: any;
export const resolveComponent: any;
export const resolveDirective: any;
export const resolveDynamicComponent: any;
export const renderList: any;
export const renderSlot: any;
export const createTextVNode: any;
export const createElementVNode: any;
export const createCommentVNode: any;
export const createStaticVNode: any;
export const createVNode: any;
export const resolveTransitionHooks: any;
export const setTransitionHooks: any;
export const getTransitionRawChildren: any;
export const initCustomFormatter: any;
export const warn: any;
export const callWithErrorHandling: any;
export const callWithAsyncErrorHandling: any;
export const handleError: any;
export const camelize: any;
export const capitalize: any;
export const toHandlerKey: any;
export const normalizeClass: any;
export const normalizeStyle: any;
export const normalizeProps: any;
export const toDisplayString: any;
export const ZoneSymbol: any;
export const pushScopeId: any;
export const popScopeId: any;
export const withScopeId: any;
export const withCtx: any;
export const renderVNodeChildren: any;
export const renderComponentRoot: any;
export const openBlock: any;
export const createBlock: any;
export const createBaseVNode: any;
export const createVNodeWithArgsTransform: any;
export const transformVNodeArgs: any;
export const createRenderer: any;
export const createHydrationRenderer: any;
export const queuePostFlushCb: any;
export const queueJob: any;
export const devtools: any;
export const setDevtoolsHook: any;
export const setBlockTracking: any;
export const setCurrentRenderingInstance: any;
export const setCurrentInstance: any;
export const isRuntimeOnly: any;
export const warn$1: any;
export const ErrorTypeStrings: any;
export const createCompilerError: any;
export const registerRuntimeCompiler: any;
export const resolveFilter: any;
export const isBuiltInTag: any;
export const isVSlot: any;
export const propsToAttrMap: any;
export const isNoUnitNumericStyleProp: any;
export const isOn: any;
export const isModelListener: any;
export const extend: any;
export const remove: any;
export const hasOwn: any;
export const isArray: any;
export const isMap: any;
export const isSet: any;
export const isDate: any;
export const isRegExp: any;
export const isFunction: any;
export const isString: any;
export const isSymbol: any;
export const isObject: any;
export const isPromise: any;
export const objectToString: any;
export const toTypeString: any;
export const toRawType: any;
export const isPlainObject: any;
export const isIntegerKey: any;
export const isReservedProp: any;
export const isBuiltInDirective: any;
export const cacheStringFunction: any;
export const camelizeRE: any;
export const hyphenateRE: any;
export const hyphenate: any;
export const invokeArrayFns: any;
export const def: any;
export const looseEqual: any;
export const looseIndexOf: any;
export const EMPTY_ARR: any;
export const EMPTY_OBJ: any;
export const NOOP: any;
export const NO: any;
export const onRE: any;
export const isOn$1: any;
export const isModelListener$1: any;
export const extend$1: any;
export const remove$1: any;
export const hasOwn$1: any;
export const isArray$1: any;
export const isMap$1: any;
export const isSet$1: any;
export const isDate$1: any;
export const isRegExp$1: any;
export const isFunction$1: any;
export const isString$1: any;
export const isSymbol$1: any;
export const isObject$1: any;
export const isPromise$1: any;
export const objectToString$1: any;
export const toTypeString$1: any;
export const toRawType$1: any;
export const isPlainObject$1: any;
export const isIntegerKey$1: any;
export const isReservedProp$1: any;
export const isBuiltInDirective$1: any;
export const cacheStringFunction$1: any;
export const camelizeRE$1: any;
export const hyphenateRE$1: any;
export const hyphenate$1: any;
export const invokeArrayFns$1: any;
export const def$1: any;
export const looseEqual$1: any;
export const looseIndexOf$1: any;
export const EMPTY_ARR$1: any;
export const EMPTY_OBJ$1: any;
export const NOOP$1: any;
export const NO$1: any;
export const onRE$1: any;
export const isOn$2: any;
export const isModelListener$2: any;
export const extend$2: any;
export const remove$2: any;
export const hasOwn$2: any;
export const isArray$2: any;
export const isMap$2: any;
export const isSet$2: any;
export const isDate$2: any;
export const isRegExp$2: any;
export const isFunction$2: any;
export const isString$2: any;
export const isSymbol$2: any;
export const isObject$2: any;
export const isPromise$2: any;
export const objectToString$2: any;
export const toTypeString$2: any;
export const toRawType$2: any;
export const isPlainObject$2: any;
export const isIntegerKey$2: any;
export const isReservedProp$2: any;
export const isBuiltInDirective$2: any;
export const cacheStringFunction$2: any;
export const camelizeRE$2: any;
export const hyphenateRE$2: any;
export const hyphenate$2: any;
export const invokeArrayFns$2: any;
export const def$2: any;
export const looseEqual$2: any;
export const looseIndexOf$2: any;
export const EMPTY_ARR$2: any;
export const EMPTY_OBJ$2: any;
export const NOOP$2: any;
export const NO$2: any;
export const onRE$2: any;
export const isOn$3: any;
export const isModelListener$3: any;
export const extend$3: any;
export const remove$3: any;
export const hasOwn$3: any;
export const isArray$3: any;
export const isMap$3: any;
export const isSet$3: any;
export const isDate$3: any;
export const isRegExp$3: any;
export const isFunction$3: any;
export const isString$3: any;
export const isSymbol$3: any;
export const isObject$3: any;
export const isPromise$3: any;
export const objectToString$3: any;
export const toTypeString$3: any;
export const toRawType$3: any;
export const isPlainObject$3: any;
export const isIntegerKey$3: any;
export const isReservedProp$3: any;
export const isBuiltInDirective$3: any;
export const cacheStringFunction$3: any;
export const camelizeRE$3: any;
export const hyphenateRE$3: any;
export const hyphenate$3: any;
export const invokeArrayFns$3: any;
export const def$3: any;
export const looseEqual$3: any;
export const looseIndexOf$3: any;
export const EMPTY_ARR$3: any;
export const EMPTY_OBJ$3: any;
export const NOOP$3: any;
export const NO$3: any;
export const onRE$3: any;
export const isOn$4: any;
export const isModelListener$4: any;
export const extend$4: any;
export const remove$4: any;
export const hasOwn$4: any;
export const isArray$4: any;
export const isMap$4: any;
export const isSet$4: any;
export const isDate$4: any;
export const isRegExp$4: any;
export const isFunction$4: any;
export const isString$4: any;
export const isSymbol$4: any;
export const isObject$4: any;
export const isPromise$4: any;
export const objectToString$4: any;
export const toTypeString$4: any;
export const toRawType$4: any;
export const isPlainObject$4: any;
export const isIntegerKey$4: any;
export const isReservedProp$4: any;
export const isBuiltInDirective$4: any;
export const cacheStringFunction$4: any;
export const camelizeRE$4: any;
export const hyphenateRE$4: any;
export const hyphenate$4: any;
export const invokeArrayFns$4: any;
export const def$4: any;
export const looseEqual$4: any;
export const looseIndexOf$4: any;
export const EMPTY_ARR$4: any;
export const EMPTY_OBJ$4: any;
export const NOOP$4: any;
export const NO$4: any;
export const onRE$4: any;
export const isOn$5: any;
export const isModelListener$5: any;
export const extend$5: any;
export const remove$5: any;
export const hasOwn$5: any;
export const isArray$5: any;
export const isMap$5: any;
export const isSet$5: any;
export const isDate$5: any;
export const isRegExp$5: any;
export const isFunction$5: any;
export const isString$5: any;
export const isSymbol$5: any;
export const isObject$5: any;
export const isPromise$5: any;
export const objectToString$5: any;
export const toTypeString$5: any;
export const toRawType$5: any;
export const isPlainObject$5: any;
export const isIntegerKey$5: any;
export const isReservedProp$5: any;
export const isBuiltInDirective$5: any;
export const cacheStringFunction$5: any;
export const camelizeRE$5: any;
export const hyphenateRE$5: any;
export const hyphenate$5: any;
export const invokeArrayFns$5: any;
export const def$5: any;
export const looseEqual$5: any;
export const looseIndexOf$5: any;
export const EMPTY_ARR$5: any;
export const EMPTY_OBJ$5: any;
export const NOOP$5: any;
export const NO$5: any;
export const onRE$5: any;
export type Ref<T = any> = { value: T }
export type ComputedRef<T = any> = { value: T }
export type WatchSource<T = any> = Ref<T> | (() => T)
export type WatchCallback<T = any> = (value: T, oldValue: T) => any
export type WatchOptions = {
immediate?: boolean
deep?: boolean
flush?: 'pre' | 'post' | 'sync'
}
export type WatchStopHandle = () => void
export type WatchEffect = (onInvalidate: (cb: () => void) => void) => void
export type WatchEffectOptions = {
flush?: 'pre' | 'post' | 'sync'
}
export type ComponentInstance = any
export type ComponentPublicInstance = any
export type ComponentOptions = any
export type ComponentOptionsMixin = any
export type ComponentOptionsWithArrayProps = any
export type ComponentOptionsWithObjectProps = any
export type ComponentOptionsWithoutProps = any
export type ComponentPropsOptions = any
export type ComponentCustomProps = any
export type ComponentCustomProperties = any
export type ComponentInternalOptions = any
export type ComponentInternalInstance = any
export type ComponentRenderContext = any
export type ComponentWrapperKey = any
export type ConcreteComponent = any
export type EmitFn<Events = {}> = any
export type EmitsOptions = any
export type ExtractPropTypes<Props> = any
export type FunctionalComponent<Props = {}, E extends EmitsOptions = {}> = any
export type InferDefaults<T> = any
export type MethodOptions = any
export type ObjectEmitsOptions = any
export type PropType<T> = any
export type RequiredKeys<T> = any
export type SetupContext<E extends EmitsOptions = {}> = any
export type SlotsType<S extends Record<string, any> = {}> = any
export type StyleValue = any
export type VNodeChild = any
export type VNodeProps = any
export type VNodeArrayChildren = any
export type VNodeNormalizedChildren = any
export type VNode = any
export type VNodeTypes = any
export type WatchCallback<T = any, Immediate extends Readonly<boolean> = false> = any
export type WatchOptionsBase = any
export type WatchOptions<Immediate extends Readonly<boolean> = false> = any
export type WatchStopHandle = any
export type TransitionProps = any
export type TransitionGroupProps = any
export type KeepAliveProps = any
export type TeleportProps = any
export type SuspenseProps = any
export type DirectiveModifiers = any
export type DirectiveBinding<V = any> = any
export type ObjectDirective<T = any, V = any> = any
export type FunctionDirective<T = any, V = any> = any
export type Directive<T = any, V = any> = ObjectDirective<T, V> | FunctionDirective<T, V>
export type App<HostElement = any> = any
export type CreateAppFunction<HostElement> = any
export type DefinitionComponent<Props = any> = any
export type DefineComponent<
PropsOrPropOptions = {},
RawBindings = {},
D = {},
C extends ComputedOptions = ComputedOptions,
M extends MethodOptions = MethodOptions,
Mixin extends ComponentOptionsMixin = ComponentOptionsMixin,
Extends extends ComponentOptionsMixin = ComponentOptionsMixin,
E extends EmitsOptions = {},
EE extends string = string,
PP = PublicProps,
Props = Readonly<ExtractPropTypes<PropsOrPropOptions>>,
Defaults = ExtractDefaultPropTypes<PropsOrPropOptions>
> = any
export type ComputedOptions = any
export type ExtractDefaultPropTypes<PropsOptions> = any
export type PublicProps = any
export type Plugin = any
export type AppContext = any
export type AppConfig = any
export type AppInstance = any
export type AsyncComponentLoader<T = any> = any
export type AsyncComponentOptions<T = any> = any
export type AsyncComponentResolveResult<T = any> = any
}
declare module 'vue-router' {
export const createRouter: any;
export const createWebHistory: any;
export const useRouter: any;
export const useRoute: any;
export const RouterLink: any;
export const RouterView: any;
export const START_LOCATION: any;
export const RouteRecordRaw: any;
export const RouteLocationRaw: any;
export const RouteLocationNormalized: any;
export const RouteLocationNormalizedLoaded: any;
export const NavigationGuard: any;
export const NavigationGuardNext: any;
export const NavigationFailureType: any;
export const isNavigationFailure: any;
export const onBeforeRouteLeave: any;
export const onBeforeRouteUpdate: any;
export const RouterOptions: any;
export const Router: any;
export const Route: any;
export const RouteRecord: any;
export const RouteConfig: any;
export const RouterMode: any;
export const RawLocation: any;
export const RedirectOption: any;
export const NavigationGuardWithThis: any;
export const NavigationHookAfter: any;
export const RouterHistory: any;
export const RouterScrollBehavior: any;
export const RouterMatcher: any;
export const RouterError: any;
export const RouterErrorCodes: any;
export const RouterErrorConstructor: any;
export const RouteComponent: any;
export const RouteProps: any;
export const RoutePropsFunction: any;
export const RouteRecordName: any;
export const RouteParamValue: any;
export const RouteParamsRaw: any;
export const RouteParams: any;
export const RouteLocationOptions: any;
export const RouteQueryAndHash: any;
export const LocationQuery: any;
export const LocationQueryRaw: any;
export const LocationQueryValue: any;
export const LocationQueryValueRaw: any;
export const RouteRecordRedirect: any;
export const RouteRecordMultipleViews: any;
export const RouteRecordSingleView: any;
export const RouteRecordRedirectOption: any;
export const _RouteRecordProps: any;
export const RouteRecordProps: any;
export const _RouteLocationBase: any;
export const RouteLocationMatched: any;
export const NavigationGuardReturn: any;
export const NavigationGuardWithThisNext: any;
export const NavigationFailure: any;
export const ErrorTypes: any;
export const MatcherLocation: any;
export const MatcherLocationAsName: any;
export const MatcherLocationAsPath: any;
export const MatcherLocationAsRelative: any;
export const RouteLocation: any;
export const RouteLocationAsName: any;
export const RouteLocationAsPath: any;
export const RouteLocationAsRelative: any;
export const RouteLocationResolved: any;
export const RouteLocationResolvedLoaded: any;
export const HistoryState: any;
export const NavigationType: any;
export const NavigationDirection: any;
export const NavigationInformation: any;
export const NavigationCallback: any;
export const RouterHistory: any;
export const RouterHistoryState: any;
export const HistoryLocation: any;
export const HistoryLocationNormalized: any;
export const HistoryStateValue: any;
export const ScrollPosition: any;
export const ScrollBehavior: any;
export const ScrollState: any;
export const RouterScrollBehavior: any;
export const RouteQueryAndHash: any;
export const RouteHash: any;
export const RouteLocationOptions: any;
export const PathParserOptions: any;
export const PathParser: any;
export const PathParserSegment: any;
export const PathParserSegmentType: any;
export const PathParserSegmentValue: any;
export const PathParserSegmentValueRaw: any;
export const PathParserSegmentValueResolved: any;
export const PathParserSegmentValueResolvedRaw: any;
export const PathParserSegmentValueResolvedNormalized: any;
export const PathParserSegmentValueResolvedNormalizedRaw: any;
export const PathParserSegmentValueResolvedNormalizedResolved: any;
export const PathParserSegmentValueResolvedNormalizedResolvedRaw: any;
export const PathParserSegmentValueResolvedNormalizedResolvedNormalized: any;
export const PathParserSegmentValueResolvedNormalizedResolvedNormalizedRaw: any;
export const PathParserSegmentValueResolvedNormalizedResolvedNormalizedResolved: any;
export const PathParserSegmentValueResolvedNormalizedResolvedNormalizedResolvedRaw: any;
export const PathParserSegmentValueResolvedNormalizedResolvedNormalizedResolvedNormalized: any;
export const PathParserSegmentValueResolvedNormalizedResolvedNormalizedResolvedNormalizedRaw: any;
export const PathParserSegmentValueResolvedNormalizedResolvedNormalizedResolvedNormalizedResolved: any;
export const PathParserSegmentValueResolvedNormalizedResolvedNormalizedResolvedNormalizedResolvedRaw: any;
export const PathParserSegmentValueResolvedNormalizedResolvedNormalizedResolvedNormalizedResolvedNormalized: any;
export const PathParserSegmentValueResolvedNormalizedResolvedNormalizedResolvedNormalizedResolvedNormalizedRaw: any;
export const PathParserSegmentValueResolvedNormalizedResolvedNormalizedResolvedNormalizedResolvedNormalizedResolved: any;
export const PathParserSegmentValueResolvedNormalizedResolvedNormalizedResolvedNormalizedResolvedNormalizedResolvedRaw: any;
export const PathParserSegmentValueResolvedNormalizedResolvedNormalizedResolvedNormalizedResolvedNormalizedResolvedNormalized: any;
export const PathParserSegmentValueResolvedNormalizedResolvedNormalizedResolvedNormalizedResolvedNormalizedResolvedNormalizedRaw: any;
export const PathParserSegmentValueResolvedNormalizedResolvedNormalizedResolvedNormalizedResolvedNormalizedResolvedNormalizedResolved: any;
export const PathParserSegmentValueResolvedNormalizedResolvedNormalizedResolvedNormalizedResolvedNormalizedResolvedNormalizedResolvedRaw: any;
export const PathParserSegmentValueResolvedNormalizedResolvedNormalizedResolvedNormalizedResolvedNormalizedResolvedNormalizedResolvedNormalized: any;
export const PathParserSegmentValueResolvedNormalizedResolvedNormalizedResolvedNormalizedResolvedNormalizedResolvedNormalizedResolvedNormalizedRaw: any;
export const PathParserSegmentValueResolvedNormalizedResolvedNormalizedResolvedNormalizedResolvedNormalizedResolvedNormalizedResolvedNormalizedResolved: any;
export const PathParserSegmentValueResolvedNormalizedResolvedNormalizedResolvedNormalizedResolvedNormalizedResolvedNormalizedResolvedNormalizedResolvedRaw: any;
export const PathParserSegmentValueResolvedNormalizedResolvedNormalizedResolvedNormalizedResolvedNormalizedResolvedNormalizedResolvedNormalizedResolvedNormalized: any;
export const PathParserSegmentValueResolvedNormalizedResolvedNormalizedResolvedNormalizedResolvedNormalizedResolvedNormalizedResolvedNormalizedResolvedNormalizedRaw: any;
export const PathParserSegmentValueResolvedNormalizedResolvedNormalizedResolvedNormalizedResolvedNormalizedResolvedNormalizedResolvedNormalizedResolvedNormalizedResolved: any;
export const PathParserSegmentValueResolvedNormalizedResolvedNormalizedResolvedNormalizedResolvedNormalizedResolvedNormalizedResolvedNormalizedResolvedNormalizedResolvedRaw: any;
export const PathParserSegmentValueResolvedNormalizedResolvedNormalizedResolvedNormalizedResolvedNormalizedResolvedNormalizedResolvedNormalizedResolvedNormalizedResolvedNormalized: any;
export const PathParserSegmentValueResolvedNormalizedResolvedNormalizedResolvedNormalizedResolvedNormalizedResolvedNormalizedResolvedNormalizedResolvedNormalizedResolvedNormalizedRaw: any;
export const PathParserSegmentValueResolvedNormalizedResolvedNormalizedResolvedNormalizedResolvedNormalizedResolvedNormalizedResolvedNormalizedResolvedNormalizedResolvedNormalizedResolved: any;
export const PathParserSegmentValueResolvedNormalizedResolvedNormalizedResolvedNormalizedResolvedNormalizedResolvedNormalizedResolvedNormalizedResolvedNormalizedResolvedNormalizedResolvedRaw: any;
export const PathParserSegmentValueResolvedNormalizedResolvedNormalizedResolvedNormalizedResolvedNormalizedResolvedNormalizedResolvedNormalizedResolvedNormalizedResolvedNormalizedResolvedNormalized: any;
export const PathParserSegmentValueResolvedNormalizedResolvedNormalizedResolvedNormalizedResolvedNormalizedResolvedNormalizedResolvedNormalizedResolvedNormalizedResolvedNormalizedResolvedNormalizedRaw: any;
export const PathParserSegmentValueResolvedNormalizedResolvedNormalizedResolvedNormalizedResolvedNormalizedResolvedNormalizedResolvedNormalizedResolvedNormalizedResolvedNormalizedResolvedNormalizedResolved: any;
}

+ 20
- 0
src/types/wow.d.ts View File

@ -0,0 +1,20 @@
// WOW.js 类型声明
interface WowOptions {
boxClass?: string;
animateClass?: string;
offset?: number;
mobile?: boolean;
live?: boolean;
callback?: Function;
scrollContainer?: string | null;
}
declare class WOW {
constructor(options?: WowOptions);
init(): void;
}
interface Window {
WOW: typeof WOW;
}

+ 10
- 10
src/views/About.vue View File

@ -2,7 +2,8 @@
import { useI18n } from 'vue-i18n';
import TeamModule from '@/components/about/TeamModule.vue';
import MilestoneModule from '@/components/about/MilestoneModule.vue';
import EcosystemModule from '@/components/about/EcosystemModule.vue';
//
// import EcosystemModule from '@/components/about/EcosystemModule.vue';
import CompanyModule from '@/components/about/CompanyModule.vue';
import PartnersModule from '@/components/about/PartnersModule.vue';
import { useSummary } from '@/utils/config';
@ -10,7 +11,6 @@ import { useSummary } from '@/utils/config';
const { t } = useI18n();
const { getSummaryDescription } = useSummary();
</script>
<template>
@ -57,19 +57,19 @@ const { getSummaryDescription } = useSummary();
</div>
</section>
<!-- Team Section -->
<!-- 领导团队 -->
<TeamModule />
<!-- Milestones Section -->
<!-- 里程碑 -->
<MilestoneModule />
<!-- Ecosystem Section -->
<EcosystemModule />
<!-- Company Section -->
<!-- 移除生态系统模块 -->
<!-- <EcosystemModule /> -->
<!-- 全球战略部署 -->
<CompanyModule />
<!-- Partners Section -->
<!-- 合作伙伴 -->
<PartnersModule />
</div>
</template>

+ 291
- 157
src/views/Community.vue View File

@ -51,6 +51,46 @@ const isSubmitting = ref(false);
// ID
const currentForumId = ref<string | null>(null);
//
const currentCommunityPage = ref(0);
const communitiesPerPage = 1;
//
const currentCommunities = computed(() => {
const start = currentCommunityPage.value * communitiesPerPage;
return communityList.value.slice(start, start + communitiesPerPage);
});
//
const nextCommunity = () => {
const totalPages = Math.ceil(communityList.value.length / communitiesPerPage);
currentCommunityPage.value = (currentCommunityPage.value + 1) % totalPages;
};
//
const prevCommunity = () => {
const totalPages = Math.ceil(communityList.value.length / communitiesPerPage);
currentCommunityPage.value = (currentCommunityPage.value - 1 + totalPages) % totalPages;
};
//
const goToMessage = (id: string) => {
console.log('查看公告详情:', id);
//
};
//
const formatDate = (dateString?: string) => {
if (!dateString) return '';
const date = new Date(dateString);
return new Intl.DateTimeFormat('zh-CN', {
year: 'numeric',
month: 'short',
day: 'numeric'
}).format(date);
};
//
const loadSocialMedia = async () => {
try {
@ -145,7 +185,6 @@ const submitComment = async (forumId: string) => {
//
await loadComments(forumId);
//
newMessage.value.createBy = '';
@ -217,11 +256,6 @@ const getTimeAgo = (timestamp: string) => {
}
};
//
const formatDate = (dateString: string) => {
return new Date(dateString).toLocaleDateString();
};
//
const getSocialIcon = (title: string) => {
const lowerTitle = title.toLowerCase();
@ -263,229 +297,322 @@ onMounted(async() => {
<div class="container mx-auto relative z-10">
<div class="max-w-3xl mx-auto text-center">
<h1 class="text-4xl md:text-5xl lg:text-6xl font-bold text-text mb-6 wow animate__animated animate__fadeInDown animate__duration-fast">
{{ t('community.hero.title') }}
{{ t('community.title') }}
</h1>
<p class="text-lg md:text-xl text-text-secondary mb-8 wow animate__animated animate__fadeIn animate__delay-sm">
{{ t('community.hero.subtitle') }}
<p class="text-lg md:text-xl text-text-secondary mb-8 wow animate__animated animate__fadeIn animate__delay-xs animate__duration-fast">
{{ t('community.subtitle') }}
</p>
</div>
</div>
<!-- Background Decoration -->
<div class="absolute top-0 left-0 w-full h-full overflow-hidden opacity-10">
<div class="absolute -top-24 -left-24 w-64 h-64 rounded-full bg-secondary blur-3xl wow animate__animated animate__pulse animate__infinite"></div>
<div class="absolute top-1/2 right-0 w-80 h-80 rounded-full bg-primary blur-3xl wow animate__animated animate__pulse animate__infinite animate__delay-sm"></div>
<div class="absolute -top-24 -left-24 w-64 h-64 rounded-full bg-primary-light blur-3xl wow animate__animated animate__pulse animate__infinite"></div>
<div class="absolute top-1/2 right-0 w-80 h-80 rounded-full bg-secondary blur-3xl wow animate__animated animate__pulse animate__infinite animate__delay-sm"></div>
<div class="absolute -bottom-24 left-1/3 w-72 h-72 rounded-full bg-accent blur-3xl wow animate__animated animate__pulse animate__infinite animate__delay-md"></div>
</div>
</section>
<!-- 1. 社交媒体墙 Section -->
<!-- 官方公告 Section -->
<section class="py-16 px-6 md:px-12 lg:px-24">
<div class="container mx-auto">
<h2 class="text-3xl font-bold text-text mb-10 text-center wow animate__animated animate__fadeInUp">
<h2 class="text-2xl md:text-3xl font-bold text-text mb-8 text-center wow animate__animated animate__fadeInUp animate__duration-fast">
官方公告
</h2>
<div v-if="messageLoading" class="flex justify-center py-10">
<div class="animate-spin rounded-full h-10 w-10 border-t-2 border-b-2 border-primary"></div>
</div>
<div v-else class="grid grid-cols-1 md:grid-cols-2 gap-6">
<div
v-for="message in messageList"
:key="message.id"
class="flex bg-background rounded-xl overflow-hidden shadow-card hover:shadow-lg transition-all duration-300 wow animate__animated animate__fadeInUp"
@click="goToMessage(message.id)"
>
<!-- 公告图片 -->
<div class="w-1/3">
<img :src="message.image || '/LOGO.png'" :alt="message.title" class="w-full h-full object-cover" />
</div>
<!-- 公告内容 -->
<div class="w-2/3 p-5">
<h3 class="text-lg font-bold text-text mb-2 line-clamp-2">{{ message.title }}</h3>
<p class="text-text-secondary text-sm mb-3 line-clamp-2">{{ message.description }}</p>
<div class="flex items-center text-text-secondary text-xs">
<Icon icon="carbon:time" class="mr-1" />
<span>{{ formatDate(message.createTime) }}</span>
</div>
</div>
</div>
</div>
</div>
</section>
<!-- 社区风采 Section (原社区亮点) -->
<section class="py-16 px-6 md:px-12 lg:px-24 bg-background-light">
<div class="container mx-auto">
<h2 class="text-2xl md:text-3xl font-bold text-text mb-8 text-center wow animate__animated animate__fadeInUp animate__duration-fast">
社区风采
</h2>
<div v-if="communityLoading" class="flex justify-center py-10">
<div class="animate-spin rounded-full h-10 w-10 border-t-2 border-b-2 border-primary"></div>
</div>
<div v-else class="relative max-w-4xl mx-auto">
<div class="grid grid-cols-1 gap-8">
<div
v-for="(community, index) in currentCommunities"
:key="community.id"
class="bg-background rounded-xl overflow-hidden shadow-card"
>
<div class="grid grid-cols-1 md:grid-cols-2 gap-6">
<img :src="community.image || '/LOGO.png'" :alt="community.title" class="w-full h-full max-h-64 object-cover" />
<div class="p-6 flex flex-col justify-center">
<h3 class="text-xl font-bold text-text mb-3">{{ community.title }}</h3>
<p class="text-text-secondary mb-4">{{ community.description }}</p>
<a
:href="community.url || '#'"
target="_blank"
rel="noopener noreferrer"
class="inline-flex items-center text-primary-light hover:text-primary-dark transition-colors"
>
<span>了解更多</span>
<svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5 ml-1" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M14 5l7 7m0 0l-7 7m7-7H3" />
</svg>
</a>
</div>
</div>
</div>
</div>
<!-- 左右切换控制器 -->
<div class="absolute top-1/2 -left-5 transform -translate-y-1/2 z-10">
<button
@click="prevCommunity"
class="w-10 h-10 rounded-full bg-background-dark flex items-center justify-center hover:bg-primary hover:bg-opacity-20 transition-colors shadow-lg"
>
<svg xmlns="http://www.w3.org/2000/svg" class="h-6 w-6 text-text-secondary" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 19l-7-7 7-7" />
</svg>
</button>
</div>
<div class="absolute top-1/2 -right-5 transform -translate-y-1/2 z-10">
<button
@click="nextCommunity"
class="w-10 h-10 rounded-full bg-background-dark flex items-center justify-center hover:bg-primary hover:bg-opacity-20 transition-colors shadow-lg"
>
<svg xmlns="http://www.w3.org/2000/svg" class="h-6 w-6 text-text-secondary" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 5l7 7-7 7" />
</svg>
</button>
</div>
<!-- 指示器 -->
<div class="flex justify-center mt-6 gap-2">
<button
v-for="i in Math.ceil(communityList.length / communitiesPerPage)"
:key="i"
@click="currentCommunityPage = i - 1"
class="w-3 h-3 rounded-full transition-all duration-300"
:class="currentCommunityPage === i - 1 ? 'bg-primary' : 'bg-background-dark'"
></button>
</div>
</div>
</div>
</section>
<!-- 社交媒体账号 Section -->
<section class="py-16 px-6 md:px-12 lg:px-24">
<div class="container mx-auto">
<h2 class="text-2xl md:text-3xl font-bold text-text mb-8 text-center wow animate__animated animate__fadeInUp animate__duration-fast">
{{ t('community.social_media.title') }}
</h2>
<!-- 加载中状态 -->
<div v-if="socialMediaLoading" class="flex justify-center items-center py-12">
<div class="animate-spin rounded-full h-12 w-12 border-t-2 border-b-2 border-primary"></div>
<div v-if="socialMediaLoading" class="flex justify-center py-10">
<div class="animate-spin rounded-full h-10 w-10 border-t-2 border-b-2 border-primary"></div>
</div>
<div v-else class="flex flex-wrap justify-center gap-6 mb-10">
<a
<div v-else-if="socialMediaAccounts.length === 0" class="text-center py-10 text-text-secondary">
{{ t('community.social_media.no_accounts') }}
</div>
<div v-else class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-8">
<div
v-for="account in socialMediaAccounts"
:key="account.id"
:href="account.link"
target="_blank"
rel="noopener noreferrer"
class="flex items-center bg-background-light p-4 rounded-xl shadow-card transition-all duration-300 hover:shadow-lg hover:-translate-y-1"
class="bg-background rounded-xl p-6 shadow-card hover:shadow-lg transition-all duration-300 wow animate__animated animate__fadeInUp"
>
<div class="h-12 w-12 bg-primary bg-opacity-10 rounded-full flex items-center justify-center mr-4">
<Icon :icon="getSocialIcon(account.title)" class="h-6 w-6 text-primary-light" />
<div class="flex items-center mb-4">
<div class="w-12 h-12 rounded-full bg-primary bg-opacity-10 flex items-center justify-center mr-4">
<Icon :icon="getSocialIcon(account.title)" class="h-6 w-6 text-primary" />
</div>
<div>
<span class="text-lg font-medium text-text block">{{ account.title }}</span>
<span class="text-sm text-text-secondary" v-html="account.description"></span>
<h3 class="text-lg font-bold text-text">{{ account.title }}</h3>
<p class="text-text-secondary text-sm">{{ account.username }}</p>
</div>
</div>
<p class="text-text-secondary mb-4" v-html="account.description"></p>
<a
:href="account.url"
target="_blank"
rel="noopener noreferrer"
class="inline-flex items-center text-primary-light hover:text-primary-dark transition-colors"
>
<span>{{ t('community.social_media.follow_us') }}</span>
<svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5 ml-1" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M10 6H6a2 2 0 00-2 2v10a2 2 0 002 2h10a2 2 0 002-2v-4M14 4h6m0 0v6m0-6L10 14" />
</svg>
</a>
</div>
<!-- 无数据状态 -->
<div v-if="!socialMediaLoading && socialMediaAccounts.length === 0" class="text-center py-10">
<Icon icon="carbon:no-content" class="mx-auto mb-4" width="48" height="48" />
<p class="text-text-secondary">暂无社交媒体数据</p>
</div>
</div>
</section>
<!-- 2. 论坛入口 Section -->
<!-- 社区论坛 Section -->
<section class="py-16 px-6 md:px-12 lg:px-24 bg-background-light">
<div class="container mx-auto">
<h2 class="text-3xl font-bold text-text mb-6 text-center wow animate__animated animate__fadeInUp">
<h2 class="text-2xl md:text-3xl font-bold text-text mb-8 text-center wow animate__animated animate__fadeInUp animate__duration-fast">
{{ t('community.forum.title') }}
</h2>
<p class="text-text-secondary text-center mb-8 max-w-2xl mx-auto">
{{ t('community.forum.description') }}
</p>
<div v-if="forumLoading" class="flex justify-center py-10">
<div class="animate-spin rounded-full h-10 w-10 border-t-2 border-b-2 border-primary"></div>
</div>
<!-- 加载中状态 -->
<div v-if="forumLoading" class="flex justify-center items-center py-12">
<div class="animate-spin rounded-full h-12 w-12 border-t-2 border-b-2 border-primary"></div>
<div v-else-if="forumList.length === 0" class="text-center py-10 text-text-secondary">
{{ t('community.forum.no_topics') }}
</div>
<!-- 优化后的论坛框 -->
<div v-else class="max-w-4xl mx-auto bg-background rounded-xl shadow-lg overflow-hidden border border-background-light">
<!-- 论坛标题栏 -->
<div class="bg-primary bg-opacity-10 p-4 border-b border-background-light">
<div class="flex items-center justify-between">
<h3 class="font-bold text-lg text-primary">MOSE Forum</h3>
<div class="flex items-center space-x-2">
<span class="inline-block w-2 h-2 rounded-full bg-green-500"></span>
<span class="text-sm text-text-secondary">{{ forumList.length }} {{ t('community.forum.topics') }}</span>
<div v-else class="space-y-6">
<div
v-for="forum in forumList"
:key="forum.id"
class="bg-background rounded-xl shadow-card overflow-hidden wow animate__animated animate__fadeInUp"
>
<!-- 帖子内容 -->
<div class="p-6">
<div class="flex items-center mb-4">
<div class="w-10 h-10 rounded-full bg-primary bg-opacity-10 flex items-center justify-center mr-3">
<svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5 text-primary" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M7 8h10M7 12h4m1 8l-4-4H5a2 2 0 01-2-2V6a2 2 0 012-2h14a2 2 0 012 2v8a2 2 0 01-2 2h-3l-4 4z" />
</svg>
</div>
<div>
<h3 class="text-lg font-bold text-text">{{ forum.title }}</h3>
<div class="flex items-center text-text-secondary text-sm">
<span>{{ forum.createBy }}</span>
<span class="mx-2"></span>
<span>{{ formatDate(forum.createTime) }}</span>
</div>
</div>
</div>
<!-- 无数据状态 -->
<div v-if="!forumLoading && forumList.length === 0" class="text-center py-16">
<Icon icon="carbon:no-content" class="mx-auto mb-4" width="48" height="48" />
<p class="text-text-secondary">暂无论坛数据</p>
<div class="mb-4">
<p class="text-text-secondary" v-html="forum.content"></p>
</div>
<!-- 论坛帖子列表区域 - 固定高度并可滚动 -->
<div v-else class="h-[600px] overflow-y-auto">
<div class="p-6">
<!-- 官方帖子列表 -->
<div
v-for="forum in forumList"
:key="forum.id"
class="mb-6 overflow-hidden"
>
<!-- 帖子卡片 -->
<div class="bg-background-light rounded-lg border border-background-light hover:border-primary-light transition-colors duration-300">
<!-- 帖子标题与内容 -->
<div class="p-5">
<div class="flex justify-between items-center mb-4">
<h3 class="text-xl font-bold text-text hover:text-primary transition-colors duration-300">{{ forum.title }}</h3>
<span class="text-sm text-text-secondary bg-background-dark bg-opacity-20 px-2 py-1 rounded-full">{{ getTimeAgo(forum.createTime || '') }}</span>
<div class="flex justify-between items-center">
<div class="flex items-center space-x-4">
<button class="flex items-center text-text-secondary hover:text-primary transition-colors">
<svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5 mr-1" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M14 10h4.764a2 2 0 011.789 2.894l-3.5 7A2 2 0 0115.263 21h-4.017c-.163 0-.326-.02-.485-.06L7 20m7-10V5a2 2 0 00-2-2h-.095c-.5 0-.905.405-.905.905 0 .714-.211 1.412-.608 2.006L7 11v9m7-10h-2M7 20H5a2 2 0 01-2-2v-6a2 2 0 012-2h2.5" />
</svg>
<span>{{ forum.likeCount || 0 }}</span>
</button>
<button class="flex items-center text-text-secondary hover:text-primary transition-colors" @click="prepareComment(forum.id)">
<svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5 mr-1" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M7 8h10M7 12h4m1 8l-4-4H5a2 2 0 01-2-2V6a2 2 0 012-2h14a2 2 0 012 2v8a2 2 0 01-2 2h-3l-4 4z" />
</svg>
<span>{{ getComments(forum.id).length }}</span>
</button>
</div>
<!-- 如果有图片则显示 -->
<div v-if="forum.image" class="mb-4">
<img :src="forum.image" :alt="forum.title" class="w-full h-48 object-cover rounded-lg" />
<button class="flex items-center text-primary-light hover:text-primary-dark transition-colors" @click="prepareComment(forum.id)">
<span>{{ t('community.forum.reply') }}</span>
<svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5 ml-1" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M3 10h10a8 8 0 018 8v2M3 10l6 6m-6-6l6-6" />
</svg>
</button>
</div>
</div>
<p class="text-text-secondary text-base mb-4 leading-relaxed" v-html="forum.content"></p>
<div class="flex items-center text-sm text-text-secondary">
<span class="font-medium flex items-center">
<span class="inline-block w-8 h-8 rounded-full bg-primary bg-opacity-20 text-primary flex items-center justify-center mr-2">
{{ forum.createBy?.charAt(0) || 'M' }}
</span>
{{ forum.createBy || 'MOSE Team' }}
</span>
<span class="mx-2"></span>
<span class="bg-primary bg-opacity-10 text-primary px-2 py-1 rounded-full text-xs">
{{ t('community.social_media.official_post') }}
</span>
</div>
<!-- 评论区 -->
<div v-if="currentForumId === forum.id">
<div class="border-t border-background-light">
<div class="p-6">
<h4 class="text-lg font-bold text-text mb-4 flex items-center justify-between">
<span>{{ t('community.forum.comments') }}</span>
<button class="text-text-secondary hover:text-primary transition-colors" @click="closeComment">
<svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12" />
</svg>
</button>
</h4>
<div v-if="commentsLoading" class="flex justify-center py-4">
<div class="animate-spin rounded-full h-6 w-6 border-t-2 border-b-2 border-primary"></div>
</div>
<!-- 帖子评论区 - 默认隐藏点击展开 -->
<div v-if="currentForumId === forum.id" class="border-t border-background-light">
<div class="p-5 bg-background bg-opacity-50">
<h4 class="font-bold text-base text-text mb-4 flex items-center">
<Icon icon="carbon:chat" class="mr-2 text-primary" width="20" height="20" />
{{ t('community.forum.comments') }} ({{ getComments(forum.id).length }})
</h4>
<div v-else-if="getComments(forum.id).length === 0" class="text-center py-4 text-text-secondary">
{{ t('community.forum.no_comments') }}
</div>
<!-- 评论列表 -->
<div v-if="getComments(forum.id).length > 0" class="space-y-4 mb-5 max-h-[250px] overflow-y-auto">
<div v-else class="space-y-4 mb-6">
<div
v-for="comment in getComments(forum.id)"
:key="comment.id"
class="bg-background rounded-lg p-4 border border-background-light"
class="bg-background-dark rounded-lg p-4"
>
<div class="flex justify-between items-center mb-2">
<span class="font-bold text-text text-sm flex items-center">
<span class="inline-block w-6 h-6 rounded-full bg-secondary bg-opacity-20 text-secondary flex items-center justify-center mr-2 text-xs">
{{ comment.createBy?.charAt(0) || 'U' }}
</span>
{{ comment.createBy || 'Anonymous' }}
</span>
<span class="text-xs text-text-secondary bg-background-dark bg-opacity-10 px-2 py-1 rounded-full">{{ getTimeAgo(comment.createTime || '') }}</span>
<div class="flex items-center mb-2">
<div class="w-8 h-8 rounded-full bg-primary bg-opacity-10 flex items-center justify-center mr-2">
<svg xmlns="http://www.w3.org/2000/svg" class="h-4 w-4 text-primary" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M16 7a4 4 0 11-8 0 4 4 0 018 0zM12 14a7 7 0 00-7 7h14a7 7 0 00-7-7z" />
</svg>
</div>
<p class="text-text-secondary text-sm pl-8" v-html="comment.content"></p>
<div>
<h5 class="text-sm font-medium text-text">{{ comment.createBy || t('community.forum.anonymous') }}</h5>
<p class="text-xs text-text-secondary">{{ formatDate(comment.createTime) }}</p>
</div>
</div>
<!-- 空评论提示 -->
<div v-else class="text-center text-text-secondary text-sm py-6 bg-background-dark bg-opacity-5 rounded-lg border border-dashed border-background-light">
<Icon icon="carbon:chat" class="mx-auto mb-2 text-text-secondary opacity-50" width="40" height="40" />
{{ t('community.forum.no_comments') }}
<p class="text-text-secondary" v-html="comment.content"></p>
</div>
</div>
<!-- 添加评论表单 -->
<div class="mt-6 bg-background p-4 rounded-lg border border-background-light">
<h5 class="font-medium text-sm text-text mb-3">{{ t('community.forum.add_comment') }}</h5>
<!-- <div class="mb-3">
<input
type="text"
v-model="newMessage.createBy"
class="w-full px-4 py-2 text-sm rounded-lg border border-background-light bg-background focus:outline-none focus:ring-1 focus:ring-primary"
:placeholder="t('community.forum.username')"
required
>
</div> -->
<div class="mb-3">
<!-- 评论表单 -->
<div class="bg-background-dark rounded-lg p-4">
<textarea
v-model="newMessage.content"
:placeholder="t('community.forum.comment_placeholder')"
class="w-full bg-background border border-background-light rounded-lg p-3 text-text-secondary resize-none focus:outline-none focus:ring-2 focus:ring-primary focus:border-transparent"
rows="3"
class="w-full px-4 py-2 text-sm rounded-lg border border-background-light bg-background focus:outline-none focus:ring-1 focus:ring-primary"
:placeholder="t('community.forum.comment')"
required
></textarea>
</div>
<div class="flex justify-between items-center">
<div class="flex justify-end mt-3">
<button
type="button"
class="px-4 py-2 border border-primary-light text-primary-light text-sm rounded-lg hover:bg-primary-light hover:bg-opacity-10 transition-colors duration-200"
@click="closeComment"
>
{{ t('community.forum.cancel') }}
</button>
<button
:disabled="isSubmitting"
@click="submitComment(forum.id)"
:class="{ 'opacity-50 cursor-not-allowed': isSubmitting }"
class="px-4 py-2 bg-primary text-text text-sm rounded-lg hover:bg-primary-dark transition-colors duration-200 flex items-center cursor-pointer"
:disabled="!newMessage.content || isSubmitting"
class="px-4 py-2 bg-primary text-white rounded-lg hover:bg-primary-dark transition-colors disabled:opacity-50 disabled:cursor-not-allowed"
>
<Icon icon="carbon:send" class="mr-1" width="16" height="16" />
{{ t('community.forum.submit') }}
<span v-if="isSubmitting">
<svg class="animate-spin -ml-1 mr-2 h-4 w-4 text-white inline-block" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24">
<circle class="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4"></circle>
<path class="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"></path>
</svg>
{{ t('community.forum.submitting') }}
</span>
<span v-else>{{ t('community.forum.submit') }}</span>
</button>
</div>
</div>
</div>
</div>
<!-- 查看评论按钮 -->
<div v-else class="p-4 flex justify-between items-center border-t border-background-light bg-background-dark bg-opacity-5">
<span class="text-sm text-text-secondary flex items-center">
<Icon icon="carbon:chat" class="mr-1" width="16" height="16" />
{{ t('community.forum.comments') }}: {{ getComments(forum.id).length }}
</span>
<button
@click="prepareComment(forum.id)"
class="px-4 py-2 text-sm bg-primary text-text rounded-lg hover:bg-primary-dark transition-colors duration-200 flex items-center"
>
<Icon icon="carbon:view" class="mr-1" width="16" height="16" />
{{ t('community.forum.view_comments') }}
</button>
</div>
</div>
</div>
</div>
@ -591,4 +718,11 @@ onMounted(async() => {
.shadow-card:hover {
box-shadow: 0 20px 25px -5px rgba(0, 0, 0, 0.1), 0 10px 10px -5px rgba(0, 0, 0, 0.04);
}
.line-clamp-2 {
display: -webkit-box;
-webkit-line-clamp: 2;
-webkit-box-orient: vertical;
overflow: hidden;
}
</style>

+ 493
- 263
src/views/Ecosystem.vue View File

@ -1,28 +1,116 @@
<script setup lang="ts">
import { ref, computed } from 'vue';
import { ref, computed, onMounted, reactive } from 'vue';
import { useI18n } from 'vue-i18n';
import EcosystemCard from '@/components/ecosystem/EcosystemCard.vue';
import CompanyCard from '@/components/ecosystem/CompanyCard.vue';
import AppListModule from '@/components/ecosystem/AppListModule.vue';
//
// import MarketDataModule from '@/components/rewards/MarketDataModule.vue';
// import TokenBurnModule from '@/components/rewards/TokenBurnModule.vue';
const { t, locale } = useI18n();
//
const selectedCategory = ref('all');
//
const selectedEcosystem = ref(1);
const totalEcosystems = 10; // 10
//
const categories = [
{ id: 'all', name: t('ecosystem.categories.all') },
{ id: 'defi', name: t('ecosystem.categories.defi') },
{ id: 'nft', name: t('ecosystem.categories.nft') },
{ id: 'dao', name: t('ecosystem.categories.dao') },
{ id: 'gaming', name: t('ecosystem.categories.gaming') },
{ id: 'infrastructure', name: t('ecosystem.categories.infrastructure') },
{ id: 'social', name: t('ecosystem.categories.social') },
];
// -
const ecosystems = reactive([
{
id: 1,
image: '/LOGO.png',
title: 'MOS交易生态',
description: '构建高效、安全的数字资产交易环境,支持多种交易方式和金融工具。',
layout: 'left',
style: 'large',
overlayColor: 'from-indigo-900/80 to-transparent',
bgColor: 'bg-gradient-to-br from-black to-indigo-900/30'
},
{
id: 2,
image: '/LOGO.png',
title: 'MOS公链生态',
description: '提供高性能、低延迟的区块链基础设施,支持大规模商业应用的快速部署。',
layout: 'right',
style: 'medium',
overlayColor: 'from-emerald-900/80 to-transparent',
bgColor: 'bg-gradient-to-tl from-black to-emerald-900/30'
},
{
id: 3,
image: '/LOGO.png',
title: 'MOS跨链生态',
description: '打破区块链孤岛,实现不同链之间的无缝互操作,构建互联互通的价值网络。',
layout: 'center',
style: 'full',
overlayColor: 'from-purple-900/70 via-purple-900/40 to-transparent',
bgColor: 'bg-gradient-to-r from-black via-purple-900/20 to-black'
},
{
id: 4,
image: '/LOGO.png',
title: 'MOS支付生态',
description: '革新支付体验,提供快速、低成本、全球化的数字支付解决方案。',
layout: 'left',
style: 'medium',
overlayColor: 'from-blue-900/80 to-transparent',
bgColor: 'bg-gradient-to-br from-black to-blue-900/30'
},
{
id: 5,
image: '/LOGO.png',
title: 'MOS隐私生态',
description: '保护用户数据隐私的同时,确保交易合规性,平衡隐私与监管需求。',
layout: 'right',
style: 'large',
overlayColor: 'from-gray-900/80 to-transparent',
bgColor: 'bg-gradient-to-tl from-black to-gray-900/30'
},
{
id: 6,
image: '/LOGO.png',
title: 'MOS DeFi生态',
description: '重构金融服务,提供去中心化借贷、流动性挖矿、资产管理等创新金融工具。',
layout: 'center',
style: 'medium',
overlayColor: 'from-amber-900/70 via-amber-900/40 to-transparent',
bgColor: 'bg-gradient-to-r from-black via-amber-900/20 to-black'
},
{
id: 7,
image: '/LOGO.png',
title: 'MOS NFT生态',
description: '为数字艺术、收藏品和虚拟资产提供认证和交易平台,释放创作者价值。',
layout: 'left',
style: 'full',
overlayColor: 'from-rose-900/80 to-transparent',
bgColor: 'bg-gradient-to-br from-black to-rose-900/30'
},
{
id: 8,
image: '/LOGO.png',
title: 'MOS DAO生态',
description: '创新治理模式,实现社区自治,让每个参与者都能参与生态系统的决策过程。',
layout: 'right',
style: 'medium',
overlayColor: 'from-cyan-900/80 to-transparent',
bgColor: 'bg-gradient-to-tl from-black to-cyan-900/30'
},
{
id: 9,
image: '/LOGO.png',
title: 'MOS游戏生态',
description: '将区块链技术融入游戏产业,创造真正玩家拥有的游戏资产和全新游戏体验。',
layout: 'center',
style: 'large',
overlayColor: 'from-green-900/70 via-green-900/40 to-transparent',
bgColor: 'bg-gradient-to-r from-black via-green-900/20 to-black'
},
{
id: 10,
image: '/LOGO.png',
title: 'MOS社交生态',
description: '重新定义社交网络,让用户掌控自己的数据和社交关系,构建去中心化社交平台。',
layout: 'left',
style: 'medium',
overlayColor: 'from-orange-900/80 to-transparent',
bgColor: 'bg-gradient-to-br from-black to-orange-900/30'
}
]);
// 使
const isZhLang = computed(() => locale.value === 'zh');
@ -58,116 +146,40 @@ const projects = computed(() => [
url: '#',
featured: true,
launchDate: '2026-07'
},
{
id: 4,
name: isZhLang.value ? 'MOSE NFT市场' : 'MOSE NFT Marketplace',
description: t('projects.mose_nft'),
category: 'nft',
image: '/LOGO.png',
url: 'https://mosenft.io',
featured: false,
launchDate: '2026-08'
},
{
id: 5,
name: isZhLang.value ? 'MOSE跨链桥' : 'MOSE Bridge',
description: t('projects.mose_bridge'),
category: 'infrastructure',
image: '/LOGO.png',
url: 'https://mosebridge.io',
featured: false,
launchDate: '2026-09'
},
{
id: 6,
name: isZhLang.value ? 'MOSE社交' : 'MOSE Social',
description: t('projects.mose_social'),
category: 'social',
image: '/LOGO.png',
url: 'https://mosesocial.io',
featured: false,
launchDate: '2026-10'
},
{
id: 7,
name: isZhLang.value ? 'MOSE借贷' : 'MOSE Lending',
description: t('projects.mose_lending'),
category: 'defi',
image: '/LOGO.png',
url: 'https://moselending.io',
featured: false,
launchDate: '2026-11'
},
{
id: 8,
name: isZhLang.value ? 'MOSE质押' : 'MOSE Staking',
description: t('projects.mose_staking'),
category: 'defi',
image: '/LOGO.png',
url: 'https://mosestaking.io',
featured: false,
launchDate: '2026-12'
}
]);
// - 使
const partners = computed(() => [
{
id: 1,
name: isZhLang.value ? '合作伙伴1' : 'Partner 1',
logo: '/LOGO.png',
url: 'https://partner1.com'
},
{
id: 2,
name: isZhLang.value ? '合作伙伴2' : 'Partner 2',
logo: '/LOGO.png',
url: 'https://partner2.com'
},
{
id: 3,
name: isZhLang.value ? '合作伙伴3' : 'Partner 3',
logo: '/LOGO.png',
url: 'https://partner3.com'
},
{
id: 4,
name: isZhLang.value ? '合作伙伴4' : 'Partner 4',
logo: '/LOGO.png',
url: 'https://partner4.com'
},
{
id: 5,
name: isZhLang.value ? '合作伙伴5' : 'Partner 5',
logo: '/LOGO.png',
url: 'https://partner5.com'
},
{
id: 6,
name: isZhLang.value ? '合作伙伴6' : 'Partner 6',
logo: '/LOGO.png',
url: 'https://partner6.com'
}
]);
//
const filteredProjects = computed(() => {
if (selectedCategory.value === 'all') {
return projects.value;
} else {
return projects.value.filter(project => project.category === selectedCategory.value);
}
});
//
const featuredProjects = computed(() => {
return projects.value.filter(project => project.featured);
});
//
const selectCategory = (category: string) => {
selectedCategory.value = category;
//
const selectEcosystem = (id: number) => {
selectedEcosystem.value = id;
};
//
const nextEcosystem = () => {
selectedEcosystem.value = selectedEcosystem.value === totalEcosystems ? 1 : selectedEcosystem.value + 1;
};
//
const prevEcosystem = () => {
selectedEcosystem.value = selectedEcosystem.value === 1 ? totalEcosystems : selectedEcosystem.value - 1;
};
//
const currentEcosystem = computed(() => {
return ecosystems.find(eco => eco.id === selectedEcosystem.value) || ecosystems[0];
});
//
const scrollToNext = (currentIndex: number) => {
const nextElement = document.querySelectorAll('.ecosystem-row')[currentIndex + 1];
if (nextElement) {
nextElement.scrollIntoView({ behavior: 'smooth' });
}
};
//
@ -186,6 +198,64 @@ const formatLaunchDate = (dateString: string, isZh: boolean) => {
return '2026 H2';
}
};
//
const showModal = ref(false);
const modalImage = ref('');
const modalTitle = ref('');
const modalDescription = ref('');
//
const openModal = (ecosystem: any) => {
modalImage.value = ecosystem.image;
modalTitle.value = ecosystem.title;
modalDescription.value = ecosystem.description;
showModal.value = true;
//
document.body.style.overflow = 'hidden';
};
//
const closeModal = () => {
showModal.value = false;
//
document.body.style.overflow = '';
};
//
const parallaxOffset = ref({ x: 0, y: 0 });
//
const handleMouseMove = (event: MouseEvent) => {
const x = (event.clientX / window.innerWidth - 0.5) * 20;
const y = (event.clientY / window.innerHeight - 0.5) * 20;
parallaxOffset.value = { x, y };
};
//
onMounted(() => {
// WOW.js
try {
const WOW = window.WOW;
if (WOW) {
new WOW({
boxClass: 'wow',
animateClass: 'animate__animated',
offset: 100,
mobile: true,
live: true
}).init();
}
} catch (error) {
console.error('Failed to initialize WOW.js:', error);
}
//
window.addEventListener('mousemove', handleMouseMove);
//
document.documentElement.style.scrollSnapType = 'y proximity';
});
</script>
<template>
@ -195,11 +265,16 @@ const formatLaunchDate = (dateString: string, isZh: boolean) => {
<div class="container mx-auto relative z-10">
<div class="max-w-3xl mx-auto text-center">
<h1 class="text-4xl md:text-5xl lg:text-6xl font-bold text-text mb-6 wow animate__animated animate__fadeInDown animate__duration-fast">
{{ t('ecosystem.title') }}
MOS生态系统
</h1>
<p class="text-lg md:text-xl text-text-secondary mb-8 wow animate__animated animate__fadeIn animate__delay-xs animate__duration-fast">
{{ t('ecosystem.subtitle') }}
</p>
<div class="flex justify-center space-x-2">
<span v-for="i in 10" :key="i"
class="w-2 h-2 rounded-full transition-all duration-300"
:class="i <= 5 ? 'bg-primary' : 'bg-primary/30'"></span>
</div>
</div>
</div>
@ -211,171 +286,326 @@ const formatLaunchDate = (dateString: string, isZh: boolean) => {
</div>
</section>
<!-- 十大生态卡片 -->
<EcosystemCard />
<!-- 中心化应用列表 -->
<AppListModule />
<!-- 十大分公司布局 -->
<!-- <CompanyCard /> -->
<!-- Featured Projects Section -->
<!-- <section class="py-16 px-6 md:px-12 lg:px-24">
<div class="container mx-auto">
<div class="flex justify-between items-center mb-10">
<h2 class="text-2xl md:text-3xl font-bold text-text wow animate__animated animate__fadeInUp animate__duration-fast">
{{ t('ecosystem.projects.featured') }}
</h2>
<a href="#all-projects" class="text-primary-light hover:text-primary-dark transition-colors duration-200 wow animate__animated animate__fadeInUp animate__duration-fast">
{{ t('ecosystem.projects.viewAll') }}
</a>
<!-- 自然流动的生态系统展示 -->
<div class="ecosystem-gallery">
<!-- 每个生态系统有不同的布局和样式 -->
<div
v-for="(ecosystem, index) in ecosystems"
:key="ecosystem.id"
class="ecosystem-section relative w-full overflow-hidden"
:class="[
ecosystem.bgColor,
ecosystem.style === 'full' ? 'h-screen' : (ecosystem.style === 'large' ? 'h-[85vh]' : 'h-[70vh]')
]"
>
<!-- 图片容器 - 视差效果 -->
<div class="absolute inset-0 w-full h-full overflow-hidden">
<!-- 图片 - 添加视差效果 -->
<img
:src="ecosystem.image"
:alt="ecosystem.title"
class="w-full h-full object-cover transition-transform duration-700 ease-out"
:style="{
transform: `scale(1.1) translate(${parallaxOffset.x * (index % 3 - 1) * 0.1}px, ${parallaxOffset.y * (index % 2 ? 1 : -1) * 0.1}px)`
}"
/>
<!-- 渐变叠加层 - 每个生态系统有不同的渐变 -->
<div
class="absolute inset-0 bg-gradient-to-r"
:class="ecosystem.overlayColor"
></div>
</div>
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-8">
<!-- 内容区域 - 根据布局调整位置 -->
<div
class="absolute inset-0 flex items-center"
:class="{
'justify-start': ecosystem.layout === 'left',
'justify-end': ecosystem.layout === 'right',
'justify-center': ecosystem.layout === 'center'
}"
>
<div
v-for="(project, index) in featuredProjects"
:key="project.id"
class="bg-background-light rounded-xl overflow-hidden shadow-card hover:transform hover:scale-105 transition-all duration-300 wow animate__animated animate__fadeInUp animate__duration-fast"
class="max-w-xl p-8 md:p-16 backdrop-blur-sm bg-black/10 rounded-xl border border-white/10"
:class="{
'animate__delay-xs': index === 1,
'animate__delay-sm': index === 2
'ml-0 md:ml-16': ecosystem.layout === 'left',
'mr-0 md:mr-16': ecosystem.layout === 'right',
'mx-auto text-center': ecosystem.layout === 'center'
}"
>
<img :src="project.image" :alt="project.name" class="w-full h-48 object-cover" />
<div class="p-6">
<h3 class="text-xl font-bold text-text mb-2">{{ project.name }}</h3>
<p class="text-text-secondary mb-4">{{ project.description }}</p>
<div class="mb-4">
<span class="px-3 py-1 bg-primary bg-opacity-10 text-primary-light text-sm rounded-full">
{{ isZhLang ? '预计上线: ' : 'Expected Launch: ' }}
{{ formatLaunchDate(project.launchDate, isZhLang) }}
</span>
<!-- 编号 - 不同样式 -->
<div
class="mb-6"
:class="{'flex items-center': ecosystem.layout !== 'center'}"
>
<div
class="rounded-full backdrop-blur-sm flex items-center justify-center border border-white/30"
:class="{
'w-16 h-16 bg-white/10': ecosystem.style !== 'full',
'w-20 h-20 bg-white/20': ecosystem.style === 'full',
'mx-auto': ecosystem.layout === 'center'
}"
>
<span
class="font-bold text-white"
:class="{
'text-3xl': ecosystem.style !== 'full',
'text-4xl': ecosystem.style === 'full'
}"
>{{ index + 1 }}</span>
</div>
<a
:href="project.url"
class="text-primary-light hover:text-primary-dark transition-colors duration-200 flex items-center"
target="_blank"
rel="noopener noreferrer"
<div
v-if="ecosystem.layout !== 'center'"
class="ml-4 h-px bg-gradient-to-r from-white to-transparent flex-grow"
></div>
</div>
<!-- 标题 - 不同大小和动画 -->
<h2
class="font-bold text-white mb-6 wow animate__animated"
:class="{
'text-4xl md:text-5xl': ecosystem.style === 'medium',
'text-5xl md:text-6xl': ecosystem.style === 'large',
'text-6xl md:text-7xl': ecosystem.style === 'full',
'animate__fadeInUp': ecosystem.layout === 'left' || ecosystem.layout === 'center',
'animate__fadeInRight': ecosystem.layout === 'right'
}"
>
{{ ecosystem.title }}
</h2>
<!-- 描述 - 不同样式和动画 -->
<p
class="text-white/80 mb-8 wow animate__animated"
:class="{
'text-base': ecosystem.style === 'medium',
'text-lg': ecosystem.style === 'large',
'text-xl': ecosystem.style === 'full',
'animate__fadeInUp animate__delay-xs': ecosystem.layout === 'left' || ecosystem.layout === 'center',
'animate__fadeInRight animate__delay-xs': ecosystem.layout === 'right'
}"
>
{{ ecosystem.description || '探索MOS生态系统的无限可能,连接全球资源,构建去中心化未来。' }}
</p>
<!-- 按钮 - 不同样式和位置 -->
<div
:class="{
'flex': ecosystem.layout !== 'center',
'flex justify-center': ecosystem.layout === 'center'
}"
>
<button
@click="openModal(ecosystem)"
class="px-8 py-4 backdrop-blur-sm text-white rounded-full border transition-all duration-300 flex items-center space-x-3 wow animate__animated"
:class="{
'bg-white/10 border-white/30 hover:bg-white/30': ecosystem.style !== 'full',
'bg-white/20 border-white/50 hover:bg-white/40': ecosystem.style === 'full',
'animate__fadeInUp animate__delay-sm': ecosystem.layout === 'left' || ecosystem.layout === 'center',
'animate__fadeInRight animate__delay-sm': ecosystem.layout === 'right'
}"
>
<span>{{ t('ecosystem.projects.visitProject') }}</span>
<svg xmlns="http://www.w3.org/2000/svg" class="h-4 w-4 ml-2" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M10 6H6a2 2 0 00-2 2v10a2 2 0 002 2h10a2 2 0 002-2v-4M14 4h6m0 0v6m0-6L10 14" />
<svg xmlns="http://www.w3.org/2000/svg" class="h-6 w-6" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 12a3 3 0 11-6 0 3 3 0 016 0z" />
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M2.458 12C3.732 7.943 7.523 5 12 5c4.478 0 8.268 2.943 9.542 7-1.274 4.057-5.064 7-9.542 7-4.477 0-8.268-2.943-9.542-7z" />
</svg>
</a>
<span>查看详情</span>
</button>
</div>
</div>
</div>
</div>
</section> -->
<!-- All Projects Section -->
<!-- <section id="all-projects" class="py-16 px-6 md:px-12 lg:px-24 bg-background-light">
<div class="container mx-auto">
<h2 class="text-2xl md:text-3xl font-bold text-text mb-10 wow animate__animated animate__fadeInUp animate__duration-fast">
{{ t('ecosystem.projects.all') }}
</h2>
<div class="flex flex-wrap gap-3 mb-10 wow animate__animated animate__fadeInUp animate__duration-fast">
<button
v-for="category in categories"
:key="category.id"
@click="selectCategory(category.id)"
class="px-4 py-2 rounded-full text-sm font-medium transition-all duration-200"
:class="selectedCategory === category.id
? 'bg-primary-light text-white shadow-md'
: 'bg-background-dark text-text-secondary hover:bg-background hover:text-text'"
>
{{ category.name }}
</button>
<!-- 装饰元素 - 随机位置 -->
<div
class="absolute"
:class="{
'bottom-8 right-8': index % 3 === 0,
'top-8 right-8': index % 3 === 1,
'bottom-8 left-8': index % 3 === 2
}"
>
<div class="flex items-center space-x-4">
<div class="w-3 h-3 rounded-full bg-white/40 animate-pulse"></div>
<div class="w-3 h-3 rounded-full bg-white/60 animate-pulse" style="animation-delay: 0.5s"></div>
<div class="w-3 h-3 rounded-full bg-white/80 animate-pulse" style="animation-delay: 1s"></div>
</div>
</div>
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-8">
<div
v-for="project in filteredProjects"
:key="`grid-${project.id}`"
class="bg-background rounded-xl overflow-hidden shadow-card hover:shadow-lg transition-all duration-300 wow animate__animated animate__fadeInUp animate__duration-fast"
>
<img :src="project.image" :alt="project.name" class="w-full h-48 object-cover" />
<div class="p-6">
<div class="flex items-center justify-between mb-3">
<h3 class="text-xl font-bold text-text">{{ project.name }}</h3>
<span class="px-3 py-1 bg-background-dark text-text-secondary text-xs rounded-full">
{{ t(`ecosystem.categories.${project.category}`) }}
</span>
</div>
<p class="text-text-secondary mb-4">{{ project.description }}</p>
<div class="mb-4">
<span class="px-3 py-1 bg-primary bg-opacity-10 text-primary-light text-sm rounded-full">
{{ isZhLang ? '预计上线: ' : 'Expected Launch: ' }}
{{ formatLaunchDate(project.launchDate, isZhLang) }}
</span>
</div>
<a
:href="project.url"
class="text-primary-light hover:text-primary-dark transition-colors duration-200 flex items-center"
target="_blank"
rel="noopener noreferrer"
>
<span>{{ t('ecosystem.projects.visitProject') }}</span>
<svg xmlns="http://www.w3.org/2000/svg" class="h-4 w-4 ml-2" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M10 6H6a2 2 0 00-2 2v10a2 2 0 002 2h10a2 2 0 002-2v-4M14 4h6m0 0v6m0-6L10 14" />
</svg>
</a>
</div>
</div>
<!-- 浮动装饰 -->
<div
v-if="index % 2 === 0"
class="absolute top-1/4 right-1/4 w-32 h-32 rounded-full border border-white/20 opacity-50 animate-float"
:style="{animationDelay: `${index * 0.2}s`}"
></div>
<div
v-if="index % 2 === 1"
class="absolute bottom-1/4 left-1/4 w-24 h-24 rounded-full border border-white/20 opacity-50 animate-float-reverse"
:style="{animationDelay: `${index * 0.2}s`}"
></div>
<!-- 滚动指示器 (除了最后一个) -->
<div
v-if="index < ecosystems.length - 1"
class="absolute bottom-8 left-1/2 transform -translate-x-1/2 text-white/70 text-sm flex flex-col items-center cursor-pointer hover:text-white transition-colors duration-300"
@click="scrollToNext(index)"
>
<span class="mb-1">探索更多</span>
<svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5 animate-bounce-soft" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 14l-7 7m0 0l-7-7m7 7V3" />
</svg>
</div>
</div>
</section> -->
</div>
<!-- Partners Section -->
<!-- <section class="py-16 px-6 md:px-12 lg:px-24">
<div class="container mx-auto">
<div class="text-center mb-12">
<h2 class="text-2xl md:text-3xl font-bold text-text mb-4 wow animate__animated animate__fadeInUp animate__duration-fast">
{{ t('ecosystem.partners.title') }}
</h2>
<p class="text-text-secondary max-w-2xl mx-auto wow animate__animated animate__fadeIn animate__delay-xs">
{{ t('ecosystem.partners.subtitle') }}
</p>
</div>
<div class="grid grid-cols-2 md:grid-cols-3 lg:grid-cols-6 gap-6">
<div
v-for="(partner, index) in partners"
:key="partner.id"
class="bg-background-light rounded-xl p-6 flex items-center justify-center hover:shadow-lg transition-all duration-300 wow animate__animated animate__fadeInUp"
:class="{
'animate__delay-xs': index % 6 === 1,
'animate__delay-sm': index % 6 === 2,
'animate__delay-md': index % 6 === 3,
'animate__delay-lg': index % 6 === 4,
'animate__delay-xl': index % 6 === 5
}"
<!-- 全屏弹窗 - 优化 -->
<div
v-if="showModal"
class="fixed inset-0 bg-black bg-opacity-90 z-50 flex items-center justify-center p-4 backdrop-blur-sm"
@click="closeModal"
>
<div
class="max-w-4xl w-full bg-gradient-to-br from-background-dark to-black rounded-xl overflow-hidden shadow-2xl transform transition-all duration-500 ease-out"
:class="showModal ? 'scale-100 opacity-100' : 'scale-95 opacity-0'"
@click.stop
>
<div class="relative">
<img :src="modalImage" :alt="modalTitle" class="w-full h-auto" />
<div class="absolute inset-0 bg-gradient-to-t from-black to-transparent"></div>
<button
class="absolute top-4 right-4 bg-black bg-opacity-50 text-white w-10 h-10 rounded-full flex items-center justify-center transition-all duration-300 hover:bg-white hover:text-black"
@click="closeModal"
>
<a
:href="partner.url"
target="_blank"
rel="noopener noreferrer"
class="w-full h-20 flex items-center justify-center"
>
<img :src="partner.logo" :alt="partner.name" class="max-w-full max-h-full object-contain" />
</a>
<svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12" />
</svg>
</button>
</div>
<div class="p-8">
<h3 class="text-2xl font-bold text-white mb-4">{{ modalTitle }}</h3>
<p class="text-white/80">{{ modalDescription }}</p>
<div class="mt-8 flex flex-wrap gap-4">
<div class="bg-white/5 rounded-lg p-4 flex-1 min-w-[200px] border border-white/10">
<h4 class="text-white/70 text-sm mb-2">发布时间</h4>
<p class="text-white font-medium">2025年第四季度</p>
</div>
<div class="bg-white/5 rounded-lg p-4 flex-1 min-w-[200px] border border-white/10">
<h4 class="text-white/70 text-sm mb-2">技术栈</h4>
<p class="text-white font-medium">区块链智能合约Web3</p>
</div>
<div class="bg-white/5 rounded-lg p-4 flex-1 min-w-[200px] border border-white/10">
<h4 class="text-white/70 text-sm mb-2">应用场景</h4>
<p class="text-white font-medium">金融科技数字资产去中心化应用</p>
</div>
</div>
<div class="mt-8 flex justify-end">
<button class="px-6 py-3 bg-primary/80 hover:bg-primary text-white rounded-full transition-all duration-300">
了解更多
</button>
</div>
</div>
</div>
</section> -->
</div>
</div>
</div>
</template>
<style scoped>
/* 添加一些额外的动画效果 */
.hover\:transform:hover {
transform: translateY(-5px);
.ecosystem-gallery {
scroll-snap-type: y proximity;
overflow-y: auto;
scroll-behavior: smooth;
}
.ecosystem-section {
scroll-snap-align: start;
position: relative;
}
/* 动画延迟类 */
.animate__delay-xs {
animation-delay: 0.2s;
}
.animate__delay-sm {
animation-delay: 0.4s;
}
.animate__delay-md {
animation-delay: 0.6s;
}
/* 脉冲动画 */
@keyframes pulse {
0%, 100% {
opacity: 0.6;
transform: scale(1);
}
50% {
opacity: 1;
transform: scale(1.2);
}
}
.animate-pulse {
animation: pulse 2s infinite;
}
/* 更柔和的弹跳动画 */
@keyframes bounce-soft {
0%, 20%, 50%, 80%, 100% {
transform: translateY(0);
}
40% {
transform: translateY(-8px);
}
60% {
transform: translateY(-4px);
}
}
.animate-bounce-soft {
animation: bounce-soft 2s infinite;
}
/* 浮动动画 */
@keyframes float {
0%, 100% {
transform: translateY(0) translateX(0);
}
25% {
transform: translateY(-10px) translateX(5px);
}
50% {
transform: translateY(0) translateX(10px);
}
75% {
transform: translateY(10px) translateX(5px);
}
}
.animate-float {
animation: float 8s ease-in-out infinite;
}
@keyframes float-reverse {
0%, 100% {
transform: translateY(0) translateX(0);
}
25% {
transform: translateY(10px) translateX(-5px);
}
50% {
transform: translateY(0) translateX(-10px);
}
75% {
transform: translateY(-10px) translateX(-5px);
}
}
.animate-float-reverse {
animation: float-reverse 8s ease-in-out infinite;
}
</style>

+ 121
- 306
src/views/Home.vue View File

@ -1,110 +1,82 @@
<script setup lang="ts">
import { ref } from 'vue';
import { useI18n } from 'vue-i18n';
import { useRouter } from 'vue-router';
import MilestoneModule from '@/components/home/MilestoneModule.vue';
import EventsModule from '@/components/home/EventsModule.vue';
import RewardModule from '@/components/rewards/RewardModule.vue';
import IncentiveModule from '@/components/rewards/IncentiveModule.vue';
import MarketDataModule from '@/components/rewards/MarketDataModule.vue';
import { ref, onMounted, nextTick } from 'vue';
import PartnersModule from '@/components/home/PartnersModule.vue';
import BannerModule from '@/components/home/BannerModule.vue';
import NewsModule from '@/components/home/NewsModule.vue';
import CoreValuesModule from '@/components/home/CoreValuesModule.vue';
import ProjectIntroModule from '@/components/home/ProjectIntroModule.vue';
import PartnersModule from '@/components/home/PartnersModule.vue';
import BannerModule from '@/components/home/BannerModule.vue';
import MediaModule from '@/components/home/MediaModule.vue';
import PostsModule from '@/components/home/PostsModule.vue';
import MilestoneModule from '@/components/home/MilestoneModule.vue';
import EventsModule from '@/components/home/EventsModule.vue';
import RewardModule from '@/components/rewards/RewardModule.vue';
import IncentiveModule from '@/components/rewards/IncentiveModule.vue';
import MarketDataModule from '@/components/rewards/MarketDataModule.vue';
import { Icon } from '@iconify/vue';
import { gsap } from 'gsap';
import AOS from 'aos';
const { t } = useI18n();
const router = useRouter();
const ctaSection = ref<HTMLElement | null>(null);
const goToEcosystem = () => {
router.push('/ecosystem');
};
//
const videoRef = ref<HTMLVideoElement | null>(null);
const isPlaying = ref(false);
const currentTime = ref(0);
const duration = ref(0);
const volume = ref(1);
const isMuted = ref(false);
const isFullscreen = ref(false);
// /
const togglePlay = () => {
const video = videoRef.value;
if (!video) return;
if (video.paused) {
video.play();
isPlaying.value = true;
} else {
video.pause();
isPlaying.value = false;
}
};
//
const updateProgress = () => {
const video = videoRef.value;
if (!video) return;
currentTime.value = video.currentTime;
duration.value = video.duration;
};
//
const setVideoProgress = (event: MouseEvent) => {
const video = videoRef.value;
if (!video) return;
const progressBar = event.target as HTMLElement;
const clickPosition = (event.offsetX / progressBar.offsetWidth);
video.currentTime = clickPosition * video.duration;
};
//
const setVolume = (event: Event) => {
const video = videoRef.value;
if (!video) return;
volume.value = parseFloat((event.target as HTMLInputElement).value);
video.volume = volume.value;
isMuted.value = volume.value === 0;
};
//
const toggleMute = () => {
const video = videoRef.value;
if (!video) return;
isMuted.value = !isMuted.value;
video.muted = isMuted.value;
};
//
const toggleFullscreen = () => {
const videoContainer = document.querySelector('.video-container');
if (!videoContainer) return;
//
const initAnimations = () => {
// AOS
AOS.init({
duration: 800,
easing: 'ease-out-cubic',
once: true
});
if (!document.fullscreenElement) {
videoContainer.requestFullscreen().catch(err => {
console.error(`Error attempting to enable fullscreen: ${err.message}`);
});
isFullscreen.value = true;
} else {
document.exitFullscreen();
isFullscreen.value = false;
}
// 使GSAPCTA
nextTick(() => {
if (ctaSection.value) {
//
const decorElements = ctaSection.value.querySelectorAll('.decor-element');
decorElements.forEach((el: Element, index: number) => {
gsap.to(el, {
x: (index % 2 === 0) ? 30 : -30,
y: (index % 3 === 0) ? 20 : -20,
duration: 10,
repeat: -1,
yoyo: true,
ease: 'sine.inOut'
});
});
//
const ctaButton = ctaSection.value.querySelector('.cta-button');
if (ctaButton) {
ctaButton.addEventListener('mouseenter', () => {
gsap.to(ctaButton, {
scale: 1.05,
boxShadow: '0 10px 25px rgba(59, 130, 246, 0.5)',
duration: 0.3
});
});
ctaButton.addEventListener('mouseleave', () => {
gsap.to(ctaButton, {
scale: 1,
boxShadow: '0 4px 14px rgba(59, 130, 246, 0.3)',
duration: 0.3
});
});
}
}
});
};
//
const formatTime = (timeInSeconds: number) => {
const minutes = Math.floor(timeInSeconds / 60);
const seconds = Math.floor(timeInSeconds % 60);
return `${minutes}:${seconds < 10 ? '0' : ''}${seconds}`;
};
onMounted(() => {
initAnimations();
});
</script>
<template>
@ -112,129 +84,24 @@ const formatTime = (timeInSeconds: number) => {
<!-- 使用Banner模块替换原来的Hero Section -->
<BannerModule />
<!-- 最新动态模块 -->
<!-- <NewsModule /> -->
<!-- 核心价值主张模块 -->
<CoreValuesModule />
<!-- 项目简介模块 -->
<ProjectIntroModule />
<!-- 最新动态模块 (可点击) -->
<!-- <NewsModule /> -->
<!-- 动态列表模块 -->
<PostsModule />
<!-- 合作伙伴LOGO墙模块 -->
<PartnersModule />
<!-- 媒体LOGO墙模块 -->
<!-- 媒体报道模块 -->
<MediaModule />
<!-- Video Section -->
<section class="py-16 px-6 md:px-12 lg:px-24">
<div class="container mx-auto">
<div class="max-w-4xl mx-auto wow animate__animated animate__fadeIn animate__duration-normal">
<h2 class="text-2xl md:text-3xl font-bold text-text mb-6 text-center">{{ t('home.video.title') }}</h2>
<!-- Custom Video Player -->
<div class="video-container bg-background-dark rounded-2xl overflow-hidden shadow-card relative">
<!-- Video Element -->
<video
ref="videoRef"
src="/public/MOSEVideo.mp4"
class="w-full h-full object-cover"
autoplay
muted
loop
playsinline
poster="/public/LOGO.png"
@timeupdate="updateProgress"
@click="togglePlay"
></video>
<!-- Video Controls -->
<div class="video-controls absolute bottom-0 left-0 right-0 bg-gradient-to-t from-black/80 to-transparent p-4 transition-opacity duration-300 opacity-0 hover:opacity-100">
<!-- Progress Bar -->
<div
class="progress-bar h-1 bg-gray-600 rounded-full mb-3 cursor-pointer"
@click="setVideoProgress"
>
<div
class="progress bg-primary h-full rounded-full"
:style="{ width: `${(currentTime / duration) * 100 || 0}%` }"
></div>
</div>
<!-- Control Buttons -->
<div class="flex justify-between items-center">
<div class="flex items-center space-x-3">
<!-- Play/Pause Button -->
<button @click.stop="togglePlay" class="text-white hover:text-primary-light transition-colors btn-hover-scale">
<svg v-if="!isPlaying" xmlns="http://www.w3.org/2000/svg" class="h-6 w-6" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M14.752 11.168l-3.197-2.132A1 1 0 0010 9.87v4.263a1 1 0 001.555.832l3.197-2.132a1 1 0 000-1.664z" />
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M21 12a9 9 0 11-18 0 9 9 0 0118 0z" />
</svg>
<svg v-else xmlns="http://www.w3.org/2000/svg" class="h-6 w-6" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M10 9v6m4-6v6m7-3a9 9 0 11-18 0 9 9 0 0118 0z" />
</svg>
</button>
<!-- Volume Control -->
<div class="flex items-center space-x-1">
<button @click.stop="toggleMute" class="text-white hover:text-primary-light transition-colors btn-hover-scale">
<svg v-if="!isMuted" xmlns="http://www.w3.org/2000/svg" class="h-5 w-5" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15.536 8.464a5 5 0 010 7.072m2.828-9.9a9 9 0 010 12.728M5.586 15H4a1 1 0 01-1-1v-4a1 1 0 011-1h1.586l4.707-4.707C10.923 3.663 12 4.109 12 5v14c0 .891-1.077 1.337-1.707.707L5.586 15z" />
</svg>
<svg v-else xmlns="http://www.w3.org/2000/svg" class="h-5 w-5" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M5.586 15H4a1 1 0 01-1-1v-4a1 1 0 011-1h1.586l4.707-4.707C10.923 3.663 12 4.109 12 5v14c0 .891-1.077 1.337-1.707.707L5.586 15z" />
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M17 14l2-2m0 0l2-2m-2 2l-2-2m2 2l2 2" />
</svg>
</button>
<input
type="range"
min="0"
max="1"
step="0.01"
:value="volume"
@input="setVolume"
class="w-16 accent-primary-light"
/>
</div>
<!-- Time Display -->
<div class="text-white text-sm">
{{ formatTime(currentTime) }} / {{ formatTime(duration) }}
</div>
</div>
<!-- Fullscreen Button -->
<button @click.stop="toggleFullscreen" class="text-white hover:text-primary-light transition-colors btn-hover-scale">
<svg v-if="!isFullscreen" xmlns="http://www.w3.org/2000/svg" class="h-5 w-5" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 8V4m0 0h4M4 4l5 5m11-1V4m0 0h-4m4 0l-5 5M4 16v4m0 0h4m-4 0l5-5m11 5v-4m0 4h-4m4 0l-5-5" />
</svg>
<svg v-else xmlns="http://www.w3.org/2000/svg" class="h-5 w-5" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12" />
</svg>
</button>
</div>
</div>
<!-- Play Button Overlay (visible when video is paused) -->
<div
v-if="!isPlaying"
class="absolute inset-0 flex items-center justify-center bg-black/30"
@click="togglePlay"
>
<div class="w-20 h-20 bg-primary/80 rounded-full flex items-center justify-center cursor-pointer hover:bg-primary transition-colors btn-hover-pulse">
<svg xmlns="http://www.w3.org/2000/svg" class="h-10 w-10 text-white" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M14.752 11.168l-3.197-2.132A1 1 0 0010 9.87v4.263a1 1 0 001.555.832l3.197-2.132a1 1 0 000-1.664z" />
</svg>
</div>
</div>
</div>
</div>
</div>
</section>
<!-- 里程碑模块 -->
<!-- <MilestoneModule /> -->
@ -252,138 +119,86 @@ const formatTime = (timeInSeconds: number) => {
<!-- <MarketDataModule /> -->
<!-- Call to Action -->
<!-- <section class="py-20 px-6 md:px-12 lg:px-24 bg-primary bg-opacity-10 relative overflow-hidden">
<!-- <section ref="ctaSection" class="py-20 px-6 md:px-12 lg:px-24 bg-gradient-to-br from-primary/10 via-secondary/10 to-accent/10 relative overflow-hidden">
<div class="absolute inset-0 overflow-hidden pointer-events-none">
<div class="decor-element absolute -top-24 -left-24 w-64 h-64 rounded-full bg-primary-light/20 blur-3xl"></div>
<div class="decor-element absolute top-1/3 right-1/4 w-64 h-64 rounded-full bg-secondary/20 blur-3xl"></div>
<div class="decor-element absolute bottom-1/3 left-1/4 w-64 h-64 rounded-full bg-accent/20 blur-3xl"></div>
</div>
<div class="container mx-auto relative z-10">
<div class="max-w-3xl mx-auto text-center">
<h2 class="text-2xl md:text-3xl font-bold text-text mb-4 wow animate__animated animate__fadeInUp animate__duration-fast">
{{ t('ecosystem.join.title') }}
</h2>
<p class="text-text-secondary mb-8 wow animate__animated animate__fadeInUp animate__delay-xs animate__duration-fast">
<div data-aos="fade-up">
<h2 class="text-2xl md:text-3xl lg:text-4xl font-bold text-text mb-4 relative inline-block">
{{ t('ecosystem.join.title') }}
<div class="absolute -bottom-2 left-0 w-full h-1 bg-gradient-to-r from-primary via-secondary to-accent opacity-70 rounded-full"></div>
</h2>
</div>
<p class="text-text-secondary mb-8 text-lg" data-aos="fade-up" data-aos-delay="100">
{{ t('ecosystem.join.subtitle') }}
</p>
<button
@click="goToEcosystem"
class="inline-block px-8 py-3 bg-primary text-text rounded-lg transition-colors duration-300 shadow-button wow animate__animated animate__fadeInUp animate__delay-sm animate__duration-fast btn-hover-float"
class="cta-button inline-block px-8 py-4 bg-gradient-to-r from-primary to-primary-light text-white rounded-lg transition-all duration-300 shadow-glow transform hover:-translate-y-1 flex items-center mx-auto"
data-aos="fade-up"
data-aos-delay="200"
>
{{ t('ecosystem.join.cta') }}
<Icon icon="carbon:arrow-right" class="mr-2" width="24" height="24" />
<span class="text-lg font-medium">{{ t('ecosystem.join.cta') }}</span>
</button>
<div class="mt-12 flex justify-center space-x-8" data-aos="fade-up" data-aos-delay="300">
<div class="flex flex-col items-center">
<div class="w-12 h-12 rounded-full bg-primary/20 flex items-center justify-center mb-2">
<Icon icon="carbon:earth" class="text-primary" width="24" height="24" />
</div>
<span class="text-sm text-text-secondary">全球生态</span>
</div>
<div class="flex flex-col items-center">
<div class="w-12 h-12 rounded-full bg-secondary/20 flex items-center justify-center mb-2">
<Icon icon="carbon:security" class="text-secondary" width="24" height="24" />
</div>
<span class="text-sm text-text-secondary">安全可靠</span>
</div>
<div class="flex flex-col items-center">
<div class="w-12 h-12 rounded-full bg-accent/20 flex items-center justify-center mb-2">
<Icon icon="carbon:growth" class="text-accent" width="24" height="24" />
</div>
<span class="text-sm text-text-secondary">快速发展</span>
</div>
</div>
</div>
</div>
<div class="absolute top-0 left-0 w-full h-full overflow-hidden opacity-5">
<div class="absolute -top-24 -left-24 w-64 h-64 rounded-full bg-primary-light blur-3xl"></div>
<div class="absolute top-1/3 right-1/4 w-64 h-64 rounded-full bg-primary-light blur-3xl"></div>
<div class="absolute bottom-1/3 left-1/4 w-64 h-64 rounded-full bg-primary-light blur-3xl"></div>
</div>
</section> -->
</div>
</template>
<style scoped>
.video-container {
position: relative;
overflow: hidden;
box-shadow: 0 10px 30px rgba(0, 0, 0, 0.2);
transition: transform 0.3s ease, box-shadow 0.3s ease;
}
.video-container:hover {
box-shadow: 0 15px 40px rgba(0, 0, 0, 0.3);
}
.video-container:hover .video-controls {
opacity: 1;
}
.progress-bar {
position: relative;
cursor: pointer;
transition: height 0.2s ease;
}
.progress-bar:hover {
height: 4px;
}
input[type=range] {
-webkit-appearance: none;
height: 4px;
background: rgba(255, 255, 255, 0.3);
border-radius: 5px;
outline: none;
}
input[type=range]::-webkit-slider-thumb {
-webkit-appearance: none;
width: 12px;
height: 12px;
border-radius: 50%;
background: var(--primary-light, #64B5F6);
cursor: pointer;
}
input[type=range]::-moz-range-thumb {
width: 12px;
height: 12px;
border-radius: 50%;
background: var(--primary-light, #64B5F6);
cursor: pointer;
border: none;
/* 按钮发光效果 */
.shadow-glow {
box-shadow: 0 4px 14px rgba(59, 130, 246, 0.3);
}
/* 按钮悬停效果 */
.btn-hover-scale {
transition: transform var(--btn-hover-transition) ease;
.cta-button:hover {
box-shadow: 0 10px 25px rgba(59, 130, 246, 0.5);
}
.btn-hover-scale:hover {
transform: scale(var(--btn-scale-amount));
/* 图标动画 */
.cta-button .iconify {
transition: transform 0.3s ease;
}
.btn-hover-float {
transition: transform var(--btn-hover-transition) ease;
.cta-button:hover .iconify {
transform: translateX(3px);
}
.btn-hover-float:hover {
transform: translateY(-5px);
}
.btn-hover-shadow {
transition: box-shadow var(--btn-hover-transition) ease, transform var(--btn-hover-transition) ease;
}
.btn-hover-shadow:hover {
box-shadow: 0 10px 20px rgba(0, 0, 0, 0.2);
/* 装饰图标悬停效果 */
.w-12:hover {
transform: translateY(-3px);
}
.btn-hover-glow {
position: relative;
overflow: hidden;
transition: all var(--btn-hover-transition) ease;
}
.btn-hover-glow:hover {
box-shadow: 0 0 var(--btn-glow-spread) var(--btn-glow-color);
}
.btn-hover-pulse {
animation: none;
}
.btn-hover-pulse:hover {
animation: btn-pulse var(--btn-pulse-duration) infinite;
}
@keyframes btn-pulse {
0% {
transform: scale(1);
}
50% {
transform: scale(var(--btn-pulse-scale));
}
100% {
transform: scale(1);
}
transition: transform 0.3s ease;
}
</style>

+ 550
- 11
src/views/Technology.vue View File

@ -1,12 +1,226 @@
<script setup lang="ts">
import { Icon } from '@iconify/vue';
import { ref, onMounted, reactive } from 'vue';
import ArchitectureModule from '@/components/technology/ArchitectureModule.vue';
import InnovationModule from '@/components/technology/InnovationModule.vue';
import EcosystemIntegrationModule from '@/components/technology/EcosystemIntegrationModule.vue';
import AppListModule from '@/components/ecosystem/AppListModule.vue';
import { useI18n } from 'vue-i18n';
const { t } = useI18n();
//
const currentAppIndex = ref(0);
const totalApps = 8; // 8
//
const showNextApp = () => {
currentAppIndex.value = (currentAppIndex.value + 1) % totalApps;
};
//
const showPrevApp = () => {
currentAppIndex.value = (currentAppIndex.value - 1 + totalApps) % totalApps;
};
//
const architectures = [
{
title: '聚合层',
description: '负责汇总并优化来自各个区块链的交易请求,提高系统整体处理效率。',
icon: 'carbon:data-1',
color: 'primary',
image: '/LOGO.png' //
},
{
title: '跨链层',
description: '实现不同区块链之间资产和信息的互通互联,支持多链生态系统整合。',
icon: 'carbon:connect',
color: 'secondary',
image: '/LOGO.png' //
},
{
title: '路由层',
description: '提供智能路径规划和优化,确定交易的最佳执行路径,降低成本并提高效率。',
icon: 'carbon:path-finder',
color: 'accent',
image: '/LOGO.png' //
},
{
title: '应用层',
description: '提供各类去中心化应用接口和服务,支持第三方开发者基于平台构建创新应用。',
icon: 'carbon:application',
color: 'primary',
image: '/LOGO.png' //
}
];
//
const technologies = [
{
title: '技术壁垒',
description: 'MOSE采用创新的多层零知识证明协议,确保交易隐私性的同时保持可验证性和安全性。该技术已申请多项国际专利,形成独特的技术壁垒。',
icon: 'carbon:security',
color: 'primary',
image: '/LOGO.png' //
},
{
title: '生态优势',
description: '基于创新的跨链技术,MOSE能够无缝连接多种主流区块链,使用户可以在不同链上自由转移和交易资产,同时保持完全隐私。',
icon: 'carbon:growth',
color: 'secondary',
image: '/LOGO.png' //
},
{
title: '混币器',
description: 'MOSE独创的"混币器"技术,将用户闲置的资产通过智能合约自动参与DeFi流动性挖矿,为用户创造被动收益,同时增强整个生态系统的流动性。',
icon: 'carbon:currency',
color: 'accent',
image: '/LOGO.png' //
},
{
title: '跨链桥',
description: 'MOSE跨链桥采用创新的多重签名和状态验证机制,实现资产在不同链间的安全高效转移,并保持交易隐私,解决了传统跨链桥容易成为黑客攻击目标的问题。',
icon: 'carbon:bridge',
color: 'primary',
image: '/LOGO.png' //
}
];
//
const apps = reactive([
{
id: 1,
name: 'MOSE Wallet',
description: '多链资产管理钱包,支持所有主流区块链,内置隐私保护功能。',
image: '/LOGO.png',
color: 'from-blue-600 to-indigo-900',
icon: 'carbon:wallet'
},
{
id: 2,
name: 'MOSE Exchange',
description: '去中心化交易所,支持跨链交易与隐私交易,流动性共享。',
image: '/LOGO.png',
color: 'from-purple-600 to-indigo-900',
icon: 'carbon:chart-line'
},
{
id: 3,
name: 'MOSE Bridge',
description: '安全高效的跨链桥,支持资产在不同区块链之间的无缝转移。',
image: '/LOGO.png',
color: 'from-green-600 to-teal-900',
icon: 'carbon:bridge'
},
{
id: 4,
name: 'MOSE Pay',
description: '隐私支付解决方案,为商家和用户提供安全、快速的支付服务。',
image: '/LOGO.png',
color: 'from-orange-600 to-red-900',
icon: 'carbon:purchase'
},
{
id: 5,
name: 'MOSE DAO',
description: '去中心化自治组织工具,支持社区治理和决策。',
image: '/LOGO.png',
color: 'from-amber-600 to-orange-900',
icon: 'carbon:group'
},
{
id: 6,
name: 'MOSE NFT',
description: '隐私NFT市场,支持创作者版权保护和数字资产交易。',
image: '/LOGO.png',
color: 'from-pink-600 to-rose-900',
icon: 'carbon:image'
},
{
id: 7,
name: 'MOSE DeFi',
description: '去中心化金融套件,包括借贷、质押、流动性挖矿等功能。',
image: '/LOGO.png',
color: 'from-cyan-600 to-blue-900',
icon: 'carbon:money'
},
{
id: 8,
name: 'MOSE ID',
description: '去中心化身份认证系统,保护用户隐私的同时满足合规要求。',
image: '/LOGO.png',
color: 'from-emerald-600 to-green-900',
icon: 'carbon:user'
}
]);
// Swiper
let swiper = null;
//
onMounted(() => {
// WOW.js
try {
const WOW = window.WOW;
if (WOW) {
new WOW({
boxClass: 'wow',
animateClass: 'animate__animated',
offset: 100,
mobile: true,
live: true
}).init();
}
} catch (error) {
console.error('Failed to initialize WOW.js:', error);
}
// Swiper
initSwiper();
});
// Swiper
const initSwiper = () => {
// Swiper
import('swiper/bundle').then(({ Swiper }) => {
import('swiper/css/bundle').then(() => {
// DOM
setTimeout(() => {
swiper = new Swiper('.swiper-container', {
effect: 'coverflow',
grabCursor: true,
centeredSlides: true,
slidesPerView: 'auto',
loop: true,
speed: 800,
autoplay: {
delay: 3000,
disableOnInteraction: false,
},
coverflowEffect: {
rotate: 0,
stretch: 80,
depth: 200,
modifier: 1,
slideShadows: true,
},
pagination: {
el: '.swiper-pagination',
clickable: true,
dynamicBullets: true,
},
navigation: {
nextEl: '.swiper-button-next',
prevEl: '.swiper-button-prev',
},
});
}, 500);
});
}).catch(err => {
console.error('Failed to load Swiper:', err);
});
};
</script>
<template>
@ -22,7 +236,7 @@ const { t } = useI18n();
MOSE通过创新的密码学和跨链技术为区块链世界构建隐私保护的基础设施
</p>
<!-- <div class="flex justify-center gap-4">
<div class="flex justify-center gap-4">
<a href="#architecture" class="inline-flex items-center gap-2 bg-primary hover:bg-primary-dark text-white font-medium py-3 px-6 rounded-lg transition-colors duration-200">
了解技术架构
<Icon icon="carbon:arrow-down" />
@ -31,7 +245,7 @@ const { t } = useI18n();
核心创新
<Icon icon="carbon:launch" />
</a>
</div> -->
</div>
</div>
</div>
@ -43,17 +257,342 @@ const { t } = useI18n();
</div>
</section>
<!-- 技术架构模块 -->
<div id="architecture">
<!-- 技术架构模块 - 4列布局 带底图 -->
<section id="architecture" class="py-16 px-6 md:px-12 lg:px-24 bg-background-light">
<div class="container mx-auto">
<h2 class="text-3xl md:text-4xl font-bold text-text mb-12 text-center wow animate__animated animate__fadeInUp animate__duration-fast">
技术架构
</h2>
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-6 mt-10">
<!-- 动态生成技术架构卡片 -->
<div
v-for="(arch, index) in architectures"
:key="index"
class="relative overflow-hidden rounded-xl shadow-xl hover:shadow-2xl transition-all duration-500 group wow animate__animated animate__fadeInUp"
:class="[`animate__delay-${index}00ms`]"
>
<!-- 背景图片 -->
<div class="absolute inset-0 w-full h-full">
<img :src="arch.image" :alt="arch.title" class="w-full h-full object-cover transition-transform duration-700 group-hover:scale-110" />
<div class="absolute inset-0 bg-gradient-to-t from-black via-black/70 to-transparent"></div>
</div>
<!-- 内容 -->
<div class="relative p-6 h-80 flex flex-col">
<div class="flex items-center mb-4 mt-auto">
<div class="w-12 h-12 rounded-full bg-white/20 backdrop-blur-sm flex items-center justify-center mr-3">
<Icon :icon="arch.icon" class="h-6 w-6" :class="`text-${arch.color}`" />
</div>
<h3 class="text-2xl font-bold text-white">{{ arch.title }}</h3>
</div>
<p class="text-white/80 text-lg">
{{ arch.description }}
</p>
<!-- 悬浮时显示的按钮 -->
<div class="mt-6 transform translate-y-8 opacity-0 group-hover:translate-y-0 group-hover:opacity-100 transition-all duration-300">
<button class="px-4 py-2 bg-white/20 hover:bg-white/30 backdrop-blur-sm rounded-full text-white text-sm flex items-center gap-2">
了解更多
<Icon icon="carbon:arrow-right" />
</button>
</div>
</div>
</div>
</div>
</div>
</section>
<!-- 核心技术模块 - 单行布局 带底图 -->
<section id="innovation" class="py-16 px-6 md:px-12 lg:px-24">
<div class="container mx-auto">
<h2 class="text-3xl md:text-4xl font-bold text-text mb-12 text-center wow animate__animated animate__fadeInUp animate__duration-fast">
核心技术
</h2>
<!-- 核心技术卡片 - 一行一个 -->
<div class="space-y-8">
<div
v-for="(tech, index) in technologies"
:key="index"
class="relative overflow-hidden rounded-2xl shadow-xl hover:shadow-2xl transition-all duration-500 group wow animate__animated animate__fadeInUp"
:class="[`animate__delay-${index}00ms`]"
>
<!-- 背景图片 -->
<div class="absolute inset-0 w-full h-full">
<img :src="tech.image" :alt="tech.title" class="w-full h-full object-cover transition-transform duration-700 group-hover:scale-105" />
<div
class="absolute inset-0"
:class="{
'bg-gradient-to-r from-indigo-900/90 via-indigo-900/50 to-transparent': tech.color === 'primary',
'bg-gradient-to-r from-emerald-900/90 via-emerald-900/50 to-transparent': tech.color === 'secondary',
'bg-gradient-to-r from-amber-900/90 via-amber-900/50 to-transparent': tech.color === 'accent',
'bg-gradient-to-r from-blue-900/90 via-blue-900/50 to-transparent': tech.color === 'primary' && index === 3
}"
></div>
</div>
<!-- 内容 -->
<div class="relative p-8 md:p-12 min-h-[300px] flex flex-col md:flex-row items-center">
<!-- 左侧内容 -->
<div class="md:w-1/2 mb-6 md:mb-0">
<div class="flex items-center mb-6">
<div class="w-16 h-16 rounded-full bg-white/20 backdrop-blur-sm flex items-center justify-center mr-4">
<Icon :icon="tech.icon" class="h-8 w-8" :class="`text-${tech.color}`" />
</div>
<h3 class="text-3xl md:text-4xl font-bold text-white">{{ tech.title }}</h3>
</div>
<p class="text-white/90 text-lg md:text-xl leading-relaxed">
{{ tech.description }}
</p>
<!-- 按钮 -->
<div class="mt-8">
<button class="px-6 py-3 bg-white/20 hover:bg-white/30 backdrop-blur-sm rounded-full text-white text-base flex items-center gap-2 transition-all duration-300 border border-white/30">
技术详情
<Icon icon="carbon:arrow-right" />
</button>
</div>
</div>
<!-- 右侧装饰元素 -->
<div class="md:w-1/2 md:pl-8 flex justify-center md:justify-end">
<div
class="w-48 h-48 md:w-64 md:h-64 rounded-full border-2 border-white/20 flex items-center justify-center relative overflow-hidden"
:class="{
'bg-indigo-900/20': tech.color === 'primary',
'bg-emerald-900/20': tech.color === 'secondary',
'bg-amber-900/20': tech.color === 'accent',
'bg-blue-900/20': tech.color === 'primary' && index === 3
}"
>
<!-- 内圈 -->
<div class="w-32 h-32 md:w-40 md:h-40 rounded-full border border-white/40 absolute animate-spin-slow"></div>
<!-- 技术图标 -->
<div class="w-20 h-20 md:w-24 md:h-24 rounded-full bg-white/10 backdrop-blur-md flex items-center justify-center">
<Icon :icon="tech.icon" class="h-10 w-10 md:h-12 md:w-12" :class="`text-${tech.color}`" />
</div>
<!-- 装饰点 -->
<div class="absolute top-1/4 right-1/4 w-3 h-3 rounded-full bg-white"></div>
<div class="absolute bottom-1/4 left-1/4 w-3 h-3 rounded-full bg-white"></div>
</div>
</div>
</div>
<!-- 序号装饰 -->
<div class="absolute bottom-4 right-8">
<span class="text-8xl font-bold text-white opacity-10">0{{ index + 1 }}</span>
</div>
</div>
</div>
</div>
</section>
<!-- 应用程序模块 - Swiper轮播 -->
<section class="py-16 px-6 md:px-12 lg:px-24 bg-background-light relative overflow-hidden">
<div class="container mx-auto relative z-10">
<h2 class="text-3xl md:text-4xl font-bold text-text mb-12 text-center wow animate__animated animate__fadeInUp animate__duration-fast">
生态应用
</h2>
<div class="relative wow animate__animated animate__fadeIn animate__duration-fast">
<!-- Swiper轮播 -->
<div class="swiper-container">
<div class="swiper-wrapper">
<!-- 应用卡片 -->
<div
v-for="(app, index) in apps"
:key="app.id"
class="swiper-slide"
>
<div class="app-card relative overflow-hidden rounded-2xl shadow-xl hover:shadow-2xl transition-all duration-500 mx-4 my-8 h-[500px]">
<!-- 背景渐变 -->
<div class="absolute inset-0 bg-gradient-to-br" :class="app.color"></div>
<!-- 装饰图形 -->
<div class="absolute top-0 right-0 w-64 h-64 bg-white/10 rounded-full blur-3xl transform translate-x-1/2 -translate-y-1/2"></div>
<div class="absolute bottom-0 left-0 w-48 h-48 bg-black/10 rounded-full blur-3xl transform -translate-x-1/2 translate-y-1/2"></div>
<!-- 内容 -->
<div class="relative p-8 flex flex-col h-full">
<!-- 应用图标 -->
<div class="mb-6 flex justify-center">
<div class="w-24 h-24 rounded-full bg-white/20 backdrop-blur-sm flex items-center justify-center border-4 border-white/30">
<Icon :icon="app.icon" class="h-12 w-12 text-white" />
</div>
</div>
<!-- 应用名称 -->
<h3 class="text-3xl font-bold text-white text-center mb-4">{{ app.name }}</h3>
<!-- 应用描述 -->
<p class="text-white/90 text-center text-lg mb-8">
{{ app.description }}
</p>
<!-- 装饰线 -->
<div class="w-24 h-1 bg-white/30 mx-auto mb-8"></div>
<!-- 功能列表 -->
<div class="space-y-4 mt-auto">
<div class="flex items-center text-white">
<Icon icon="carbon:checkmark" class="h-5 w-5 mr-3" />
<span>多链支持</span>
</div>
<div class="flex items-center text-white">
<Icon icon="carbon:checkmark" class="h-5 w-5 mr-3" />
<span>隐私保护</span>
</div>
<div class="flex items-center text-white">
<Icon icon="carbon:checkmark" class="h-5 w-5 mr-3" />
<span>安全加密</span>
</div>
</div>
<!-- 按钮 -->
<div class="mt-8 flex justify-center">
<button class="px-6 py-3 bg-white text-primary font-medium rounded-full hover:bg-opacity-90 transition-all duration-300 flex items-center gap-2">
立即体验
<Icon icon="carbon:launch" />
</button>
</div>
<!-- 应用编号 -->
<div class="absolute top-6 right-6 bg-white/20 backdrop-blur-sm w-8 h-8 rounded-full flex items-center justify-center">
<span class="text-white font-bold">{{ app.id }}</span>
</div>
</div>
</div>
</div>
</div>
<!-- 分页器 -->
<div class="swiper-pagination mt-8"></div>
</div>
<!-- 导航按钮 -->
<div class="swiper-button-prev !text-primary after:!text-lg"></div>
<div class="swiper-button-next !text-primary after:!text-lg"></div>
</div>
</div>
<!-- 背景装饰 -->
<div class="absolute top-0 left-0 w-full h-full overflow-hidden opacity-5 pointer-events-none">
<div class="absolute top-1/4 left-1/4 w-64 h-64 rounded-full border border-primary"></div>
<div class="absolute bottom-1/4 right-1/4 w-96 h-96 rounded-full border border-secondary"></div>
<div class="absolute top-3/4 left-3/4 w-48 h-48 rounded-full border border-accent"></div>
</div>
</section>
<!-- 保留原有模块 -->
<!-- <div class="py-16 px-6 md:px-12 lg:px-24 bg-background">
<ArchitectureModule />
</div>
</div> -->
<!-- 核心创新模块 -->
<div id="innovation">
<!-- 保留原有模块 -->
<!-- <div class="py-16 px-6 md:px-12 lg:px-24 bg-background-light">
<InnovationModule />
</div>
</div> -->
<!-- 生态系统集成模块 -->
<!-- <EcosystemIntegrationModule /> -->
<!-- 保留原有模块 -->
<!-- <div class="py-16 px-6 md:px-12 lg:px-24">
<EcosystemIntegrationModule />
</div> -->
</div>
</template>
</template>
<style scoped>
/* 动画延迟类 */
.animate__delay-0ms {
animation-delay: 0ms;
}
.animate__delay-100ms {
animation-delay: 100ms;
}
.animate__delay-200ms {
animation-delay: 200ms;
}
.animate__delay-300ms {
animation-delay: 300ms;
}
/* 慢速旋转 */
@keyframes spin-slow {
from {
transform: rotate(0deg);
}
to {
transform: rotate(360deg);
}
}
.animate-spin-slow {
animation: spin-slow 20s linear infinite;
}
/* Swiper样式覆盖 */
:deep(.swiper-container) {
padding: 30px 0;
overflow: visible;
}
:deep(.swiper-slide) {
width: 350px;
transition: transform 0.3s;
}
:deep(.swiper-slide-active) {
transform: scale(1.05);
z-index: 2;
}
:deep(.swiper-pagination-bullet) {
width: 10px;
height: 10px;
background: rgba(255, 255, 255, 0.5);
opacity: 1;
}
:deep(.swiper-pagination-bullet-active) {
background: var(--color-primary);
transform: scale(1.2);
}
:deep(.swiper-button-prev),
:deep(.swiper-button-next) {
color: var(--color-primary);
background: rgba(255, 255, 255, 0.3);
width: 50px;
height: 50px;
border-radius: 50%;
backdrop-filter: blur(4px);
transition: all 0.3s;
}
:deep(.swiper-button-prev:hover),
:deep(.swiper-button-next:hover) {
background: rgba(255, 255, 255, 0.5);
}
:deep(.swiper-button-prev:after),
:deep(.swiper-button-next:after) {
font-size: 20px;
font-weight: bold;
}
/* 卡片悬停效果 */
.app-card {
transition: all 0.5s;
box-shadow: 0 15px 30px rgba(0, 0, 0, 0.1);
}
.app-card:hover {
transform: translateY(-10px);
box-shadow: 0 20px 40px rgba(0, 0, 0, 0.2);
}
</style>

+ 113
- 0
src/views/二次开发要求.txt View File

@ -0,0 +1,113 @@
网站结构变更清单(二次开发)
全局调整
导航菜单保留原顺序(首页/生态系统/技术/关于/社区/常见问题/联系我们)
常见问题页需内容核查(标注"不实")
联系我们页完全保留
分页面结构变更
首页
diff
- 删除:底部视频模块
+ 新增:
• 核心价值主张(全宽图片布局)
• 项目简介(标题+视频+文字垂直布局)
• 最新动态(左图右标题,可点击)
• 媒体报道(左图右标题布局)
= 保留:Banner图、合作伙伴
生态系统页
diff
! 重命名:页面标题 → "MOS十大生态系统"
- 删除:应用程序模块(迁移至技术页)
+ 新增:全宽图片展示(10张,可点击)
= 保留:原有图片内容不变
技术页
diff
+ 新增:
• 技术架构(4列布局:聚合层/跨链层/路由层/应用层)
• 核心技术(单列布局:技术壁垒/生态优势/活币器/跨链桥)
• 应用程序模块(从生态系统页迁移至此,轮播展示)
= 保留:原有技术内容
关于页
diff
- 删除:生态系统模块
! 重命名:全球办公室 → "全球战略部署"
+ 新增:
• 领导团队(3成员+左右切换)
• 里程碑(单列图文布局)
= 调整:
• 合作伙伴 → 左图右文字布局
• 全球战略部署 → 直属分公司布局
社区页
diff
! 重命名:社区亮点 → "社区风采"
+ 新增:
• 官方公告(左图右标题+时间,可点击)
• 社区风采(3社区+左右切换)
= 保留:官方社交媒体、社区论坛
开发任务清单
首页结构调整
移除底部视频容器
新增4个模块容器(核心价值主张/项目简介/最新动态/媒体报道)
实现最新动态点击事件
生态系统页改造
修改页面标题DOM
移除应用程序模块
重构图片展示区(10图网格布局+点击事件)
技术页新增模块
创建技术架构网格布局(4列)
添加核心技术单列布局
移植应用程序模块并实现轮播
关于页重构
移除生态系统模块
重命名全球办公室模块
创建领导团队轮播组件
重构合作伙伴布局
重做全球战略部署为分公司布局
社区页更新
重命名社区亮点模块
新增官方公告列表
实现社区风采轮播
交互组件开发
左右切换控制器(领导团队/社区风采)
动态加载模块(最新动态/媒体报道/官方公告)
图片点击弹窗(生态系统)
所有图片资源保持原有路径和文件名不变,仅调整容器结构和布局样式。

+ 5
- 3
tsconfig.json View File

@ -1,6 +1,6 @@
{
"extends": "@vue/tsconfig/tsconfig.dom.json",
"include": ["env.d.ts", "src/**/*", "src/**/*.vue"],
"include": ["env.d.ts", "src/**/*", "src/**/*.vue", "src/types/*.d.ts"],
"exclude": ["src/**/__tests__/*"],
"compilerOptions": {
"composite": true,
@ -10,9 +10,11 @@
},
"types": ["vite/client"],
"verbatimModuleSyntax": false,
"moduleResolution": "bundler",
"moduleResolution": "node",
"allowSyntheticDefaultImports": true,
"esModuleInterop": true
"esModuleInterop": true,
"skipLibCheck": true,
"noImplicitAny": false
},
"references": [
{


+ 1
- 1
tsconfig.tsbuildinfo
File diff suppressed because it is too large
View File


Loading…
Cancel
Save