You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
458 lines
9.3 KiB
Vue
458 lines
9.3 KiB
Vue
<script setup>
|
|
import { ref, onMounted, onBeforeUnmount } from 'vue'
|
|
import { hotQueryService } from '@/api/hot'
|
|
import { typeQueryService } from '@/api/type'
|
|
import { Location, Timer } from '@element-plus/icons-vue'
|
|
import BaseLayout from '@/components/BaseLayout.vue'
|
|
// 数据初始化
|
|
const showDropdown = ref(false)
|
|
// 轮播图数据
|
|
const carouselItems = ref([])
|
|
// 加载轮播图数据
|
|
const imgLoading = ref(false)
|
|
const getCarouselItems = async () => {
|
|
imgLoading.value = true
|
|
const {
|
|
data: { data }
|
|
} = await hotQueryService({ current: 1, size: 10 })
|
|
carouselItems.value = data.list
|
|
imgLoading.value = false
|
|
}
|
|
getCarouselItems()
|
|
// 场馆数据
|
|
const venues = ref([])
|
|
// 加载场馆信息
|
|
const venueLoading = ref(false)
|
|
const getVenues = async () => {
|
|
venueLoading.value = true
|
|
const {
|
|
data: { data }
|
|
} = await typeQueryService({ current: 1, size: 10 })
|
|
venues.value = data.list
|
|
venueLoading.value = false
|
|
}
|
|
|
|
// 点击页面其他区域关闭下拉菜单
|
|
const handleClickOutside = (event) => {
|
|
const profileElement = document.querySelector('.user-profile')
|
|
const dropdownElement = document.querySelector('.user-dropdown')
|
|
|
|
if (profileElement && dropdownElement) {
|
|
if (
|
|
!profileElement.contains(event.target) &&
|
|
!dropdownElement.contains(event.target)
|
|
) {
|
|
showDropdown.value = false
|
|
}
|
|
}
|
|
}
|
|
|
|
// 添加点击事件监听
|
|
document.addEventListener('click', handleClickOutside)
|
|
|
|
// 组件卸载时移除事件监听
|
|
onBeforeUnmount(() => {
|
|
document.removeEventListener('click', handleClickOutside)
|
|
})
|
|
onMounted(() => {
|
|
getVenues()
|
|
})
|
|
</script>
|
|
<template>
|
|
<BaseLayout>
|
|
<div class="app-container">
|
|
<!-- 主要内容区域 -->
|
|
<main class="main-content">
|
|
<!-- 轮播图 -->
|
|
<div class="carousel-container">
|
|
<el-carousel
|
|
height="60vh"
|
|
indicator-position=""
|
|
arrow="always"
|
|
v-loading="imgLoading"
|
|
>
|
|
<el-carousel-item
|
|
v-for="(item, index) in carouselItems"
|
|
:key="index"
|
|
>
|
|
<div class="carousel-item-content">
|
|
<img
|
|
:src="item.imgUrl"
|
|
:alt="item.title"
|
|
class="carousel-image"
|
|
/>
|
|
<div class="carousel-text">
|
|
<h3 class="carousel-title">{{ item.title }}</h3>
|
|
<p class="carousel-desc">{{ item.description }}</p>
|
|
<!-- <p class="carousel-desc">{{ item.addTime }}</p> -->
|
|
</div>
|
|
</div>
|
|
</el-carousel-item>
|
|
</el-carousel>
|
|
</div>
|
|
<hr />
|
|
<!-- 场馆列表 -->
|
|
<div class="venue-section">
|
|
<div class="section-header">
|
|
<h2 class="section-title">场馆列表</h2>
|
|
</div>
|
|
|
|
<div class="venue-grid">
|
|
<div
|
|
v-for="venue in venues"
|
|
:key="venue.id"
|
|
class="venue-card"
|
|
v-loading="venueLoading"
|
|
>
|
|
<div class="card-image-container">
|
|
<img
|
|
:src="venue.imgUrl"
|
|
:alt="venue.vname"
|
|
class="card-image"
|
|
/>
|
|
<div class="card-status status-available">
|
|
{{ '可预约' }}
|
|
</div>
|
|
</div>
|
|
<div class="card-content">
|
|
<h3 class="card-title">{{ venue.vname }}</h3>
|
|
<div class="card-info">
|
|
<div class="info-item">
|
|
<i class="el-icon-location-outline info-icon"></i>
|
|
<span
|
|
><el-icon><Location /></el-icon>位置:{{
|
|
venue.location
|
|
}}</span
|
|
>
|
|
</div>
|
|
|
|
<div class="info-item">
|
|
<i class="el-icon-user info-icon"></i>
|
|
<span>
|
|
<el-icon><Timer /></el-icon>
|
|
开馆时间:{{ venue.openTime }}</span
|
|
>
|
|
</div>
|
|
<div class="info-item">
|
|
<i class="el-icon-time info-icon"></i>
|
|
<span>
|
|
<el-icon><Timer /></el-icon>
|
|
闭馆时间:{{ venue.closeTime }}</span
|
|
>
|
|
</div>
|
|
</div>
|
|
<p class="card-desc">{{ venue.description }}</p>
|
|
</div>
|
|
<div class="card-footer">
|
|
<router-link
|
|
:to="{ path: '/mobile/detail', query: { id: venue.id } }"
|
|
><el-button type="primary" class="reserve-btn"
|
|
>点击预约
|
|
</el-button></router-link
|
|
>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</main>
|
|
</div>
|
|
</BaseLayout>
|
|
</template>
|
|
<style scoped>
|
|
/* 全局样式 */
|
|
:root {
|
|
--primary-color: #1e40af;
|
|
--secondary-color: #3b82f6;
|
|
--text-color: #334155;
|
|
--bg-gradient: linear-gradient(135deg, #1e40af 0%, #3b82f6 100%);
|
|
--card-shadow: 0 4px 12px rgba(0, 0, 0, 0.08);
|
|
--card-hover-shadow: 0 8px 24px rgba(0, 0, 0, 0.12);
|
|
}
|
|
a {
|
|
color: black;
|
|
text-decoration: none;
|
|
}
|
|
.router-link-active {
|
|
text-decoration: none;
|
|
}
|
|
/* 应用容器 */
|
|
.app-container {
|
|
display: flex;
|
|
flex-direction: column;
|
|
min-height: 100vh;
|
|
background: linear-gradient(135deg, #f8fafc 0%, #6c83a1 100%);
|
|
}
|
|
|
|
/* 主要内容区域 */
|
|
.main-content {
|
|
flex: 1;
|
|
max-width: 1440px;
|
|
width: 100%;
|
|
margin: 0 auto;
|
|
padding: 32px;
|
|
}
|
|
|
|
/* 轮播图 */
|
|
.carousel-container {
|
|
border: 1px solid black;
|
|
margin-bottom: 40px;
|
|
border-radius: 12px;
|
|
/* height: 6vh; */
|
|
overflow: hidden;
|
|
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.08);
|
|
}
|
|
|
|
.carousel-item-content {
|
|
position: relative;
|
|
height: 100%;
|
|
}
|
|
|
|
.carousel-image {
|
|
width: 100%;
|
|
height: 100%;
|
|
object-fit: cover;
|
|
}
|
|
|
|
.carousel-text {
|
|
position: absolute;
|
|
bottom: 0;
|
|
left: 0;
|
|
right: 0;
|
|
background: linear-gradient(to top, rgba(0, 0, 0, 0.7), transparent);
|
|
padding: 24px;
|
|
color: white;
|
|
}
|
|
|
|
.carousel-title {
|
|
font-size: 24px;
|
|
font-weight: 600;
|
|
margin-bottom: 8px;
|
|
}
|
|
|
|
.carousel-desc {
|
|
font-size: 16px;
|
|
margin-bottom: 0;
|
|
}
|
|
|
|
/* 场馆列表部分 */
|
|
.venue-section {
|
|
margin-bottom: 40px;
|
|
}
|
|
|
|
.section-header {
|
|
display: flex;
|
|
justify-content: space-between;
|
|
align-items: center;
|
|
margin-bottom: 24px;
|
|
}
|
|
|
|
.section-title {
|
|
font-size: 24px;
|
|
font-weight: 600;
|
|
color: var(--text-color);
|
|
}
|
|
|
|
.section-filter {
|
|
display: flex;
|
|
gap: 16px;
|
|
}
|
|
|
|
.search-input {
|
|
width: 280px;
|
|
}
|
|
|
|
.search-icon {
|
|
color: #94a3b8;
|
|
}
|
|
|
|
.filter-select {
|
|
width: 180px;
|
|
}
|
|
|
|
.venue-grid {
|
|
display: grid;
|
|
grid-template-columns: repeat(auto-fill, minmax(300px, 1fr));
|
|
gap: 24px;
|
|
}
|
|
|
|
.venue-card {
|
|
background: white;
|
|
border-radius: 12px;
|
|
overflow: hidden;
|
|
box-shadow: var(--card-shadow);
|
|
transition:
|
|
transform 0.3s ease,
|
|
box-shadow 0.3s ease;
|
|
display: flex;
|
|
flex-direction: column;
|
|
height: 100%;
|
|
}
|
|
|
|
.venue-card:hover {
|
|
transform: translateY(-4px);
|
|
box-shadow: var(--card-hover-shadow);
|
|
}
|
|
|
|
.card-image-container {
|
|
position: relative;
|
|
height: 200px;
|
|
overflow: hidden;
|
|
}
|
|
|
|
.card-image {
|
|
width: 100%;
|
|
height: 100%;
|
|
object-fit: cover;
|
|
transition: transform 0.5s ease;
|
|
}
|
|
|
|
.venue-card:hover .card-image {
|
|
transform: scale(1.05);
|
|
}
|
|
|
|
.card-status {
|
|
position: absolute;
|
|
top: 12px;
|
|
right: 12px;
|
|
padding: 4px 8px;
|
|
border-radius: 4px;
|
|
font-size: 12px;
|
|
font-weight: 500;
|
|
}
|
|
|
|
.status-available {
|
|
background: #10b981;
|
|
color: white;
|
|
}
|
|
|
|
.status-booked {
|
|
background: #ef4444;
|
|
color: white;
|
|
}
|
|
|
|
.card-content {
|
|
padding: 16px;
|
|
flex: 1;
|
|
display: flex;
|
|
flex-direction: column;
|
|
}
|
|
|
|
.card-title {
|
|
font-size: 18px;
|
|
font-weight: 600;
|
|
color: var(--text-color);
|
|
margin-bottom: 12px;
|
|
}
|
|
|
|
.card-info {
|
|
/* display: flex; */
|
|
/* flex-wrap: wrap; */
|
|
gap: 12px;
|
|
margin-bottom: 12px;
|
|
}
|
|
|
|
.info-item {
|
|
display: flex;
|
|
align-items: center;
|
|
font-size: 14px;
|
|
color: #64748b;
|
|
margin-top: 20px;
|
|
}
|
|
|
|
.info-icon {
|
|
margin-right: 4px;
|
|
color: var(--secondary-color);
|
|
}
|
|
|
|
.card-desc {
|
|
font-size: 14px;
|
|
color: #64748b;
|
|
line-height: 1.6;
|
|
flex: 1;
|
|
}
|
|
|
|
.card-footer {
|
|
padding: 16px;
|
|
border-top: 1px solid #e2e8f0;
|
|
display: flex;
|
|
justify-content: center;
|
|
}
|
|
|
|
.reserve-btn {
|
|
width: 100%;
|
|
padding: 10px 16px;
|
|
font-size: 16px;
|
|
font-weight: 500;
|
|
border-radius: 6px;
|
|
}
|
|
|
|
/* 动画效果 */
|
|
@keyframes fadeIn {
|
|
from {
|
|
opacity: 0;
|
|
transform: translateY(-10px);
|
|
}
|
|
to {
|
|
opacity: 1;
|
|
transform: translateY(0);
|
|
}
|
|
}
|
|
/* 响应式布局 */
|
|
@media (max-width: 1024px) {
|
|
.header-content,
|
|
.main-content,
|
|
.footer-content {
|
|
padding: 0 24px;
|
|
}
|
|
|
|
.title {
|
|
display: none;
|
|
}
|
|
|
|
.section-header {
|
|
flex-direction: column;
|
|
align-items: flex-start;
|
|
gap: 16px;
|
|
}
|
|
|
|
.section-filter {
|
|
width: 100%;
|
|
flex-direction: column;
|
|
gap: 12px;
|
|
}
|
|
|
|
.search-input,
|
|
.filter-select {
|
|
width: 100%;
|
|
}
|
|
|
|
.footer-content {
|
|
flex-direction: column;
|
|
gap: 32px;
|
|
}
|
|
|
|
.footer-links {
|
|
flex-direction: column;
|
|
gap: 32px;
|
|
}
|
|
}
|
|
|
|
@media (max-width: 768px) {
|
|
.carousel-container {
|
|
height: 200px;
|
|
}
|
|
|
|
.carousel-title {
|
|
font-size: 18px;
|
|
}
|
|
|
|
.carousel-desc {
|
|
font-size: 14px;
|
|
}
|
|
|
|
.venue-grid {
|
|
grid-template-columns: 1fr;
|
|
}
|
|
}
|
|
</style>
|