Files
spa-view/src/views/frontend/Projects.vue
2025-04-24 19:07:24 +08:00

257 lines
11 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<script setup>
import { ref, computed, onMounted } from 'vue'
import AOS from 'aos'
import 'aos/dist/aos.css'
onMounted(() => {
AOS.init({
duration: 1200,
offset: 120,
easing: 'ease-out-cubic',
once: false
})
})
// 模拟项目数据
const projects = ref(Array.from({ length: 12 }, (_, i) => ({
id: i + 1,
title: `项目 ${i + 1}`,
cover: 'https://pic.rmb.bdstatic.com/bjh/80852bfe7c321988191838517ba64e309354.jpeg@h_1280',
tags: ['Vue3', 'Node.js', 'TypeScript'].slice(0, Math.floor(Math.random() * 3) + 1),
category: ['Web应用', '移动端', '小程序', '企业系统'][i % 4],
author: '开发者' + (i + 1),
price: (Math.random() * 10000 + 5000).toFixed(2),
createdAt: new Date(Date.now() - Math.floor(Math.random() * 10000000000)).toISOString(),
description: '这是一个示例项目描述,包含了项目的主要功能和技术特点。实际项目中会有更详细的描述信息。'
})))
// 筛选排序逻辑
const selectedCategory = ref('all')
const sortBy = ref('date')
const searchQuery = ref('')
const categories = [
{ value: 'all', label: '全部分类' },
{ value: 'Web应用', label: 'Web应用' },
{ value: '移动端', label: '移动端' },
{ value: '小程序', label: '小程序' },
{ value: '企业系统', label: '企业系统' }
]
const sortOptions = [
{ value: 'date', label: '最新发布' },
{ value: 'price-high', label: '价格从高到低' },
{ value: 'price-low', label: '价格从低到高' }
]
const filteredProjects = computed(() => {
return projects.value
.filter(p =>
(selectedCategory.value === 'all' || p.category === selectedCategory.value) &&
(searchQuery.value === '' || p.title.toLowerCase().includes(searchQuery.value.toLowerCase()) ||
p.description.toLowerCase().includes(searchQuery.value.toLowerCase()))
)
.sort((a, b) => {
if (sortBy.value === 'date') {
return new Date(b.createdAt) - new Date(a.createdAt)
} else if (sortBy.value === 'price-high') {
return parseFloat(b.price) - parseFloat(a.price)
} else if (sortBy.value === 'price-low') {
return parseFloat(a.price) - parseFloat(b.price)
}
return 0
})
})
const formatDate = (dateString) => {
const date = new Date(dateString)
return date.toLocaleDateString('zh-CN', { year: 'numeric', month: '2-digit', day: '2-digit' })
}
const formatPrice = (price) => {
return '¥ ' + parseFloat(price).toLocaleString('zh-CN', { minimumFractionDigits: 2, maximumFractionDigits: 2 })
}
</script>
<template>
<div class="bg-gradient-to-b from-gray-50 to-white dark:from-gray-900 dark:to-gray-800 min-h-screen py-12">
<div class="max-w-6xl mx-auto px-6">
<!-- 页面标题 -->
<div class="text-center mb-16" data-aos="fade-up">
<h1 class="text-4xl md:text-5xl font-bold mb-6 text-gray-900 dark:text-white">
<span class="relative">
项目展示
<span class="absolute -bottom-2 left-0 right-0 mx-auto w-1/3 h-1 bg-gradient-to-r from-rose-400 to-rose-600 rounded-full"></span>
</span>
</h1>
<p class="text-lg text-gray-600 dark:text-gray-300 max-w-3xl mx-auto">
探索我们的精选项目从Web应用到移动端解决方案满足您的各种数字化需求
</p>
</div>
<!-- 过滤排序栏 -->
<div class="mb-10" data-aos="fade-up">
<div class="bg-white dark:bg-gray-800 rounded-2xl shadow-lg p-6">
<div class="grid grid-cols-1 md:grid-cols-3 gap-6">
<!-- 搜索框 -->
<div class="relative">
<div class="absolute inset-y-0 left-0 pl-3 flex items-center pointer-events-none">
<svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5 text-gray-400" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0z" />
</svg>
</div>
<input
v-model="searchQuery"
type="text"
placeholder="搜索项目..."
class="pl-10 pr-4 py-2 w-full bg-gray-50 dark:bg-gray-700 border border-gray-200 dark:border-gray-600 rounded-xl focus:ring-rose-500 focus:border-rose-500 dark:text-white"
/>
</div>
<!-- 分类选择 -->
<div>
<select
v-model="selectedCategory"
class="w-full bg-gray-50 dark:bg-gray-700 border border-gray-200 dark:border-gray-600 rounded-xl px-4 py-2 focus:ring-rose-500 focus:border-rose-500 dark:text-white"
>
<option
v-for="category in categories"
:key="category.value"
:value="category.value"
>
{{ category.label }}
</option>
</select>
</div>
<!-- 排序选择 -->
<div>
<select
v-model="sortBy"
class="w-full bg-gray-50 dark:bg-gray-700 border border-gray-200 dark:border-gray-600 rounded-xl px-4 py-2 focus:ring-rose-500 focus:border-rose-500 dark:text-white"
>
<option
v-for="option in sortOptions"
:key="option.value"
:value="option.value"
>
{{ option.label }}
</option>
</select>
</div>
</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="project.id"
class="group bg-white dark:bg-gray-800 rounded-3xl shadow-lg hover:shadow-xl transition-all duration-500 overflow-hidden transform hover:-translate-y-2"
data-aos="fade-up"
>
<div class="relative h-56 overflow-hidden">
<img
:src="project.cover"
class="w-full h-full object-cover transform transition-transform duration-700 group-hover:scale-110"
:alt="project.title"
/>
<div class="absolute inset-0 bg-gradient-to-t from-black/60 via-black/20 to-transparent opacity-0 group-hover:opacity-100 transition-opacity duration-500"></div>
<div class="absolute top-4 right-4">
<span
class="px-3 py-1 bg-black/50 backdrop-blur-md text-white text-xs font-medium rounded-full"
>
{{ project.category }}
</span>
</div>
</div>
<div class="p-6">
<div class="flex flex-wrap gap-2 mb-3">
<span
v-for="tag in project.tags"
:key="tag"
class="px-2 py-1 bg-rose-50 dark:bg-rose-900/20 text-rose-600 dark:text-rose-300 text-xs font-medium rounded-lg"
>
{{ tag }}
</span>
</div>
<h3 class="text-xl font-bold mb-2 text-gray-900 dark:text-white group-hover:text-rose-600 dark:group-hover:text-rose-400 transition-colors">
{{ project.title }}
</h3>
<p class="text-gray-600 dark:text-gray-300 text-sm mb-4 line-clamp-2">
{{ project.description }}
</p>
<div class="flex justify-between items-center">
<div>
<p class="text-lg font-bold text-rose-600 dark:text-rose-400">{{ formatPrice(project.price) }}</p>
<p class="text-xs text-gray-500 dark:text-gray-400">{{ formatDate(project.createdAt) }}</p>
</div>
<button class="w-10 h-10 rounded-full bg-rose-50 dark:bg-rose-900/20 text-rose-600 dark:text-rose-400 flex items-center justify-center hover:bg-rose-100 dark:hover:bg-rose-900/40 transition-colors">
<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="M14 5l7 7m0 0l-7 7m7-7H3" />
</svg>
</button>
</div>
</div>
</div>
</div>
<!-- 分页 -->
<div class="mt-16 flex justify-center" data-aos="fade-up">
<nav class="flex items-center space-x-2">
<button class="w-10 h-10 rounded-full bg-white dark:bg-gray-800 border border-gray-200 dark:border-gray-700 flex items-center justify-center text-gray-500 dark:text-gray-400 hover:bg-gray-50 dark:hover:bg-gray-700 transition-colors">
<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="M15 19l-7-7 7-7" />
</svg>
</button>
<button class="w-10 h-10 rounded-full bg-rose-600 text-white flex items-center justify-center">
1
</button>
<button class="w-10 h-10 rounded-full bg-white dark:bg-gray-800 border border-gray-200 dark:border-gray-700 flex items-center justify-center text-gray-700 dark:text-gray-300 hover:bg-gray-50 dark:hover:bg-gray-700 transition-colors">
2
</button>
<button class="w-10 h-10 rounded-full bg-white dark:bg-gray-800 border border-gray-200 dark:border-gray-700 flex items-center justify-center text-gray-700 dark:text-gray-300 hover:bg-gray-50 dark:hover:bg-gray-700 transition-colors">
3
</button>
<span class="text-gray-500 dark:text-gray-400">...</span>
<button class="w-10 h-10 rounded-full bg-white dark:bg-gray-800 border border-gray-200 dark:border-gray-700 flex items-center justify-center text-gray-700 dark:text-gray-300 hover:bg-gray-50 dark:hover:bg-gray-700 transition-colors">
10
</button>
<button class="w-10 h-10 rounded-full bg-white dark:bg-gray-800 border border-gray-200 dark:border-gray-700 flex items-center justify-center text-gray-500 dark:text-gray-400 hover:bg-gray-50 dark:hover:bg-gray-700 transition-colors">
<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="M9 5l7 7-7 7" />
</svg>
</button>
</nav>
</div>
</div>
</div>
</template>
<style scoped>
/* 添加轻奢风格的渐变背景和阴影效果 */
.bg-gradient-luxury {
background: linear-gradient(135deg, #f6f8fb 0%, #f0f4f8 100%);
}
/* 暗色模式下的轻奢风格 */
@media (prefers-color-scheme: dark) {
.bg-gradient-luxury {
background: linear-gradient(135deg, #1a1d23 0%, #111827 100%);
}
}
</style>