Vue基础教程(214)电影购票APP开发实战之设计项目组件中的设计影院页面组件:码农翻身!用Vue设计影院页面,我居然写出了“票房冠军”代码

Vue影院页面组件设计实战

朋友们,最近我接了个私活——用Vue搞个电影购票APP。本以为凭我三年摸鱼经验,这不是手到擒来?结果刚设计影院页面就差点阵亡。这玩意儿可比写TodoList刺激多了!

一、为什么影院页面是“组件设计修罗场”?

刚开始,我天真的以为影院页面不就显示个影院列表嘛!上手才发现,这里面的水比《泰坦尼克号》还深。

首先,用户在这页面要完成“选影院-选电影-选时间-选座位”的完整决策。光是状态管理就够喝一壶的:当前选中影院、日期筛选、影片类型、场次时间...这哪是影院页面,这分明是Vue版《密室逃脱》!

更别提那些细碎需求:要显示距离排序、价格筛选、特色服务(IMAX/4DX)、会员优惠...产品经理笑眯眯地说:“这个很简单吧?”我默默看了眼需求文档,感觉头发又少了几根。

二、影院页面组件拆解:把大象装进冰箱

经过一番挣扎,我把影院页面拆成了三个核心组件:

  1. 影院筛选栏 (CinemaFilter) - 负责各种筛选条件
  2. 影院列表 (CinemaList) - 展示影院卡片信息
  3. 影院卡片 (CinemaCard) - 单个影院的详细信息

这就好比把大象装冰箱——分三步!每个组件各司其职,代码瞬间清爽多了。

三、手把手 coding:从零搭建影院页面

来吧,展示完整代码!咱们用Vue3的Composition API来写,毕竟这年头不用Composition API,出门都不好意思跟人打招呼。

<template>
  <div class="cinema-page">
    <!-- 筛选组件 -->
    <CinemaFilter 
      :filters="activeFilters"
      @update:filters="handleFilterChange"
    />
    
    <!-- 影院列表 -->
    <CinemaList 
      :cinemas="filteredCinemas"
      :loading="loading"
      @cinema-click="handleCinemaSelect"
    />
  </div>
</template>
<script setup>
import { ref, computed, onMounted } from 'vue'
import CinemaFilter from './components/CinemaFilter.vue'
import CinemaList from './components/CinemaList.vue'

// 状态管理 - 这才是重头戏!
const cinemaList = ref([]) // 影院列表
const loading = ref(false) // 加载状态
const activeFilters = ref({ // 当前筛选条件
  area: '',
  movieType: '',
  service: [],
  date: ''
})

// 模拟API请求
const fetchCinemas = async () => {
  loading.value = true
  try {
    // 这里假装调用了接口
    const response = await mockApiGetCinemas()
    cinemaList.value = response
  } catch (error) {
    console.error('获取影院列表失败:', error)
  } finally {
    loading.value = false
  }
}

// 计算属性 - 过滤后的影院列表
const filteredCinemas = computed(() => {
  let result = cinemaList.value
  
  // 区域筛选
  if (activeFilters.value.area) {
    result = result.filter(cinema => cinema.area === activeFilters.value.area)
  }
  
  // 服务类型筛选
  if (activeFilters.value.service.length > 0) {
    result = result.filter(cinema => 
      activeFilters.value.service.every(service => 
        cinema.services.includes(service)
      )
    )
  }
  
  return result
})

// 事件处理
const handleFilterChange = (newFilters) => {
  activeFilters.value = { ...activeFilters.value, ...newFilters }
}

const handleCinemaSelect = (cinema) => {
  // 跳转到场次选择页面
  router.push(`/schedule/${cinema.id}`)
}

onMounted(() => {
  fetchCinemas()
})
</script>

四、核心组件深度剖析

1. CinemaFilter组件 - 筛选界的瑞士军刀

这个组件负责所有的筛选逻辑,我把它设计得足够灵活:

<template>
  <div class="cinema-filter">
    <!-- 区域选择 -->
    <div class="filter-section">
      <h4>区域</h4>
      <div class="filter-options">
        <button 
          v-for="area in areas" 
          :key="area"
          :class="{ active: filters.area === area }"
          @click="updateFilter('area', area)"
        >
          {{ area }}
        </button>
      </div>
    </div>
    
    <!-- 特色服务 -->
    <div class="filter-section">
      <h4>特色服务</h4>
      <div class="filter-options">
        <button 
          v-for="service in services"
          :key="service.value"
          :class="{ active: filters.service.includes(service.value) }"
          @click="toggleService(service.value)"
        >
          {{ service.label }}
        </button>
      </div>
    </div>
  </div>
</template>
<script setup>
defineProps({
  filters: Object
})

const emit = defineEmits(['update:filters'])

const areas = ['全部', '朝阳区', '海淀区', '东城区', '西城区']
const services = [
  { label: 'IMAX', value: 'imax' },
  { label: '杜比影院', value: 'dolby' },
  { label: '4DX', value: '4dx' }
]

const updateFilter = (key, value) => {
  emit('update:filters', { [key]: value })
}

const toggleService = (service) => {
  const currentServices = [...props.filters.service]
  const index = currentServices.indexOf(service)
  
  if (index > -1) {
    currentServices.splice(index, 1)
  } else {
    currentServices.push(service)
  }
  
  emit('update:filters', { service: currentServices })
}
</script>

2. CinemaCard组件 - 颜值担当的影院名片

影院卡片要展示的信息最多,设计时我特别注意了信息层级:

<template>
  <div class="cinema-card" @click="$emit('cinema-click', cinema)">
    <div class="cinema-header">
      <h3 class="cinema-name">{{ cinema.name }}</h3>
      <span class="cinema-distance">{{ cinema.distance }}</span>
    </div>
    
    <div class="cinema-location">
      <span class="area">{{ cinema.area }}</span>
      <span class="address">{{ cinema.address }}</span>
    </div>
    
    <!-- 服务标签 -->
    <div class="service-tags">
      <span 
        v-for="service in cinema.services" 
        :key="service"
        class="service-tag"
        :class="getServiceClass(service)"
      >
        {{ getServiceLabel(service) }}
      </span>
    </div>
    
    <!-- 价格信息 -->
    <div class="price-info">
      <span class="current-price">¥{{ cinema.lowestPrice }}</span>
      <span class="original-price">¥{{ cinema.originalPrice }}</span>
    </div>
  </div>
</template>
<script setup>
defineProps({
  cinema: {
    type: Object,
    required: true
  }
})

defineEmits(['cinema-click'])

// 服务类型映射
const serviceMap = {
  imax: { label: 'IMAX', class: 'imax' },
  dolby: { label: '杜比', class: 'dolby' },
  '4dx': { label: '4DX', class: 'four-dx' }
}

const getServiceLabel = (service) => serviceMap[service]?.label || service
const getServiceClass = (service) => serviceMap[service]?.class || 'default'
</script>

五、状态管理:Vuex还是Composition API?

这是个经典问题。对于这种规模的项目,我选择直接用Composition API的自定义hook:

// hooks/useCinemaStore.js
import { ref, computed } from 'vue'

export function useCinemaStore() {
  const cinemas = ref([])
  const selectedCinema = ref(null)
  const filters = ref({})
  
  const filteredCinemas = computed(() => {
    // 复杂的筛选逻辑...
    return applyFilters(cinemas.value, filters.value)
  })
  
  const selectCinema = (cinema) => {
    selectedCinema.value = cinema
  }
  
  const updateFilters = (newFilters) => {
    filters.value = { ...filters.value, ...newFilters }
  }
  
  return {
    cinemas,
    selectedCinema,
    filters,
    filteredCinemas,
    selectCinema,
    updateFilters
  }
}

六、性能优化:让你的APP飞起来

在实际测试中,我发现当影院数据超过100条时,筛选会出现明显卡顿。解决方案:

  1. 虚拟滚动 - 只渲染可见区域的影院卡片
  2. 防抖搜索 - 用户输入时延迟执行筛选
  3. 计算属性缓存 - 合理使用computed
// 防抖实现
import { ref } from 'vue'

export function useDebounce(fn, delay) {
  const timeout = ref(null)
  
  return (...args) => {
    clearTimeout(timeout.value)
    timeout.value = setTimeout(() => fn(...args), delay)
  }
}

七、踩坑记录:那些年我交过的“学费”

  1. 响应式数据陷阱:直接修改props里的数组导致诡异bug
  2. 事件总线滥用:组件间通信混乱,后期难以维护
  3. CSS作用域:样式污染其他组件,建议使用scoped或CSS Modules

八、总结

通过这个影院页面组件的实战,我深刻体会到:好的组件设计就像搭乐高——每个零件独立且可组合。Vue的响应式系统让状态管理变得直观,而Composition API则提供了更好的逻辑复用。

现在我的电影APP已经上线,虽然用户还不多,但至少组件代码看起来挺专业的——毕竟,程序员最大的成就感不就是写出优雅的代码吗?

好了,我要去测试新功能了——顺便看场电影,这应该算工作吧?

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

值引力

持续创作,多谢支持!

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值