这个项目更能深刻理解Vue.js

  1. 数据异步加载与状态管理: 需要处理更复杂的加载状态、错误状态。
  2. 组件化思想的深度应用: 我们会将页面拆分为多个可复用的组件(如电影卡片、评分组件、评论列表)。
  3. 动态路由与参数传递: 通过路由传递电影ID,展示不同电影的详情页。
  4. 更丰富的用户交互: 实现打分、发表评论、实时搜索等功能。
  5. 数据驱动视图的完美体现: 用户的每一个操作都会动态地、响应式地更新页面视图。

项目目标: 创建一个单页面应用,用户可以浏览热门电影列表,点击查看电影详情,并对其进行评分和发表评论。

技术栈: Vue 3 + Vite + Pinia (状态管理) + Element Plus (UI库) + Axios


第一步:项目初始化与环境准备

【操作】: 这部分与上一个项目类似,但我们会加入Pinia来进行状态管理。

  1. 创建Vue项目:
    在你的工作目录下,打开命令行/终端,运行:

    npm create vue@latest
    

    根据提示操作:

    • Project name: movie-dash
    • Select a framework: Vue
    • Select a variant: Customize with create-vue...
    • Add TypeScript?: No
    • Add JSX Support?: No
    • Add Vue Router for Single Page Application development?: Yes <-- 必须
    • Add Pinia for state management?: Yes <-- 必须
    • Add Vitest for Unit Testing?: No
    • Add an End-to-End Testing Solution?: No
    • Add ESLint for code quality?: Yes选上
    • Add Prettier for code formatting?: Yes选上
    • 试验特性两个都不选
  2. 安装依赖:

    cd movie-dash
    npm install
    npm install element-plus axios
    
  3. 配置Element Plus:
    打开 src/main.js,引入Element Plus并全局注册。

    // src/main.js
    import { createApp } from 'vue'
    import { createPinia } from 'pinia'
    import ElementPlus from 'element-plus'
    import 'element-plus/dist/index.css'
    
    import App from './App.vue'
    import router from './router'
    
    const app = createApp(App)
    
    app.use(createPinia())
    app.use(router)
    app.use(ElementPlus)
    
    app.mount('#app')
    
  4. 启动开发服务器:

    npm run dev
    

    你的新项目已经在本地运行起来了。
    如果打开一些文件发现有报错,就把主目录下的eslin那个文件重新命名.eslintrc.cjs


第二步:后端API模拟

为了让前端能独立开发,我们先不急着连接真实后端,而是使用一个API模拟工具json-server是一个绝佳的选择,它能让你用一个json文件快速启动一个功能完备的RESTful API。

【操作】:

  1. 全局安装json-server:
    npm install -g json-server
    
  2. 创建数据文件: 在你的项目根目录(movie-dash/)下,创建一个db.json文件,并粘贴以下内容。
    {
      "movies": [
        {
          "id": 1,
          "title": "肖申克的救赎",
          "director": "弗兰克·德拉邦特",
          "year": 1994,
          "poster": "https://img1.doubanio.com/view/photo/s_ratio_poster/public/p480747492.webp",
          "rating": 9.7,
          "plot": "20世纪40年代末,小有成就的青年银行家安迪因涉嫌杀害妻子及她的情人而锒铛入狱..."
        },
        {
          "id": 2,
          "title": "霸王别姬",
          "director": "陈凯歌",
          "year": 1993,
          "poster": "https://img1.doubanio.com/view/photo/s_ratio_poster/public/p2561716440.webp",
          "rating": 9.6,
          "plot": "段小楼与程蝶衣是一对打小一起长大的师兄弟,两人一个演生,一个饰旦,一向配合天衣无缝..."
        },
        {
          "id": 3,
          "title": "阿甘正传",
          "director": "罗伯特·泽米吉斯",
          "year": 1994,
          "poster": "https://img1.doubanio.com/view/photo/s_ratio_poster/public/p2372307693.webp",
          "rating": 9.5,
          "plot": "阿甘于二战结束后不久出生在美国南方阿拉巴马州一个闭塞的小镇,他先天弱智,智商只有75..."
        }
      ],
      "comments": [
        {
          "id": 1,
          "movieId": 1,
          "author": "影迷张三",
          "content": "永恒的经典,每次看都有新的感动!",
          "rating": 5,
          "createdAt": "2023-10-28T10:00:00Z"
        },
        {
          "id": 2,
          "movieId": 1,
          "author": "观众李四",
          "content": "希望与自由,永远是人类最光辉的主题。",
          "rating": 5,
          "createdAt": "2023-10-28T11:30:00Z"
        }
      ]
    }
    
  3. 启动API服务器: 在项目根目录打开新的命令行窗口,运行:
    json-server --watch db.json
    
    现在,你就拥有了一个运行在http://localhost:3000的API服务器!你可以通过以下地址访问数据:
    • http://localhost:3000/movies (获取所有电影)
    • http://localhost:3000/movies/1 (获取ID为1的电影)
    • http://localhost:3000/comments?movieId=1 (获取ID为1的电影的所有评论)

第三步:页面与组件设计

我们将构建两个主要页面:电影列表页 (HomeView) 和 电影详情页 (MovieDetailView)。

3.1 改造主布局 App.vue

【操作】: 打开src/App.vue,将其修改为一个经典的后台布局,包含头部和主内容区。

<template>
  <el-container class="app-container">
    <el-header class="app-header">
      <div class="logo">
        <img src="@/assets/logo.svg" alt="logo" />
        <span>MovieDash 电影看板</span>
      </div>
    </el-header>
    <el-main class="app-main">
      <!-- 路由视图:所有页面将在这里被渲染 -->
      <router-view />
    </el-main>
  </el-container>
</template>

<script setup>
import { RouterView } from 'vue-router';
</script>

<style scoped>
.app-container {
  height: 100vh;
}
.app-header {
  background-color: #2c3e50;
  color: white;
  display: flex;
  align-items: center;
  padding: 0 20px;
}
.logo {
  display: flex;
  align-items: center;
}
.logo img {
  height: 40px;
  margin-right: 10px;
}
.logo span {
  font-size: 20px;
  font-weight: bold;
}
.app-main {
  padding: 20px;
  background-color: #f4f7f6;
}
</style>
3.2 创建电影卡片组件 MovieCard.vue

这是一个可复用的组件,用于在列表页展示单个电影的信息。

【操作】: 在src/components目录下创建MovieCard.vue

<template>
  <el-card class="movie-card" shadow="hover">
    <div class="poster-wrapper">
      <img :src="movie.poster" class="poster" alt="poster" />
    </div>
    <div class="info">
      <h3 class="title">{{ movie.title }} ({{ movie.year }})</h3>
      <p class="director">导演: {{ movie.director }}</p>
      <div class="rating">
        <el-rate
          :model-value="movie.rating"
          disabled
          show-score
          text-color="#ff9900"
          :score-template="`${movie.rating} 分`"
        />
      </div>
    </div>
  </el-card>
</template>

<script setup>
// 使用defineProps来接收从父组件传递过来的数据
defineProps({
  movie: {
    type: Object,
    required: true,
  },
});
</script>

<style scoped>
.movie-card {
  cursor: pointer;
  transition: all 0.3s;
}
.movie-card:hover {
  transform: translateY(-5px);
}
.poster-wrapper {
  width: 100%;
  padding-top: 140%; /* 保持图片比例 */
  position: relative;
}
.poster {
  position: absolute;
  top: 0;
  left: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
}
.info {
  padding: 14px;
}
.title {
  margin: 0 0 8px 0;
  font-size: 18px;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.director {
  color: #606266;
  font-size: 14px;
  margin: 0 0 8px 0;
}
</style>
3.3 构建电影列表页 HomeView.vue

这个页面将获取所有电影数据,并使用MovieCard组件来展示它们。

【操作】: 打开src/views/HomeView.vue,用以下内容覆盖。

<template>
  <div v-loading="loading" class="home-view">
    <el-row :gutter="20">
      <el-col
        v-for="movie in movies"
        :key="movie.id"
        :xs="24" :sm="12" :md="8" :lg="6" :xl="4"
      >
        <!-- 使用v-for循环渲染MovieCard组件,并通过props传递movie数据 -->
        <router-link :to="`/movie/${movie.id}`">
          <movie-card :movie="movie" />
        </router-link>
      </el-col>
    </el-row>
  </div>
</template>

<script setup>
import { ref, onMounted } from 'vue';
import axios from 'axios';
import MovieCard from '../components/MovieCard.vue';
import { RouterLink } from 'vue-router';

const movies = ref([]);
const loading = ref(true);

const fetchMovies = async () => {
  try {
    const response = await axios.get('http://localhost:3000/movies');
    movies.value = response.data;
  } catch (error) {
    console.error('获取电影列表失败', error);
  } finally {
    loading.value = false;
  }
};

// 在组件挂载后执行数据获取
onMounted(fetchMovies);
</script>

<style scoped>
.home-view .el-col {
  margin-bottom: 20px;
}
a {
  text-decoration: none;
}
</style>

第四步:构建电影详情页与路由

4.1 创建电影详情页 MovieDetailView.vue

这个页面将根据URL中的电影ID,获取并展示特定电影的详细信息和评论。

【操作】: 在src/views目录下创建MovieDetailView.vue

<template>
  <div v-loading="loading" class="movie-detail-view">
    <el-page-header @back="goBack" title="返回列表" />
    <el-card v-if="movie" class="box-card">
      <el-row :gutter="20">
        <el-col :span="8">
          <img :src="movie.poster" class="detail-poster" />
        </el-col>
        <el-col :span="16">
          <h1>{{ movie.title }} ({{ movie.year }})</h1>
          <p><strong>导演:</strong> {{ movie.director }}</p>
          <div class="rating-section">
            <strong>豆瓣评分:</strong>
            <el-rate v-model="movie.rating" disabled size="large" show-score score-template="{value} 分" />
          </div>
          <p><strong>剧情简介:</strong></p>
          <p class="plot">{{ movie.plot }}</p>
        </el-col>
      </el-row>
    </el-card>

    <!-- 评论区 (后续可以添加更多交互) -->
    <el-card v-if="comments.length > 0" class="box-card">
      <template #header><h3>观众评论</h3></template>
      <div v-for="comment in comments" :key="comment.id" class="comment-item">
        <p><strong>{{ comment.author }}</strong> <el-rate :model-value="comment.rating" disabled size="small" /></p>
        <p>{{ comment.content }}</p>
        <el-divider />
      </div>
    </el-card>
  </div>
</template>

<script setup>
import { ref, onMounted } from 'vue';
import { useRoute, useRouter } from 'vue-router';
import axios from 'axios';

const route = useRoute();
const router = useRouter();

const movie = ref(null);
const comments = ref([]);
const loading = ref(true);
const movieId = route.params.id; // 从路由中获取ID

const fetchData = async () => {
  try {
    // 并行获取电影详情和评论
    const [movieRes, commentsRes] = await Promise.all([
      axios.get(`http://localhost:3000/movies/${movieId}`),
      axios.get(`http://localhost:3000/comments?movieId=${movieId}`)
    ]);
    movie.value = movieRes.data;
    comments.value = commentsRes.data;
  } catch (error) {
    console.error('获取详情失败', error);
  } finally {
    loading.value = false;
  }
};

const goBack = () => {
  router.back();
};

onMounted(fetchData);
</script>

<style scoped>
.box-card {
  margin-top: 20px;
}
.detail-poster {
  width: 100%;
}
.rating-section {
  display: flex;
  align-items: center;
  gap: 10px;
  margin: 10px 0;
}
.plot {
  line-height: 1.8;
  color: #606266;
}
.comment-item {
  margin-bottom: 15px;
}
</style>
4.2 配置动态路由

我们需要在路由配置中,告诉Vue Router如何处理像/movie/1, /movie/2这样的动态URL。

【操作】: 打开src/router/index.js,添加详情页的路由规则。

import { createRouter, createWebHistory } from 'vue-router'
import HomeView from '../views/HomeView.vue'

const router = createRouter({
  history: createWebHistory(import.meta.env.BASE_URL),
  routes: [
    {
      path: '/',
      name: 'home',
      component: HomeView
    },
    // 【新增】动态路由规则
    // :id 是一个动态段,它会匹配/movie/后面的任何字符串,并作为参数存储在路由中
    {
      path: '/movie/:id',
      name: 'movie-detail',
      // 路由懒加载,只有当访问这个页面时,才会加载对应的组件文件
      component: () => import('../views/MovieDetailView.vue')
    }
  ]
})

export default router

第五步:运行与验证

现在,你的交互式电影看板项目已经基本完成了!

  1. 确保你的json-server仍在运行。
  2. 确保你的Vue开发服务器(npm run dev)也在运行。
  3. 打开浏览器:
    • 访问 http://localhost:5173/ (或你的端口),你应该能看到电影列表。
    • 点击任意一张电影卡片,页面应该会跳转到详情页,URL会变成类似/movie/1的形式,并展示该电影的详细信息和评论。
    • 点击详情页的“返回列表”按钮,可以回到主页。

在这里插入图片描述

总结与下一步

恭喜你!你刚刚从零开始,完整地构建了一个比CRUD更复杂的、交互性更强的Vue单页面应用。

通过这个项目,你深刻实践了:

  • 组件化开发: 将UI拆分为可复用的MovieCard组件。
  • 父子组件通信: 通过props将电影数据传递给卡片组件。
  • 异步数据获取: 使用axiosasync/await从模拟API加载数据,并处理加载状态。
  • 动态路由: 使用Vue Router处理带参数的URL,实现页面间的跳转和数据传递。
  • Vue响应式系统: 你所有的操作都是在修改JS数据(如movies.value = ...),而Vue自动帮你更新了整个复杂的DOM视图。

下一步你可以挑战的功能:

  1. 在详情页添加一个“发表评论”的表单,通过POST请求将新评论提交到json-server
  2. 引入Pinia,创建一个movieStore来集中管理电影列表的状态,避免在多个组件中重复请求。
  3. 在列表页添加搜索框和排序功能,通过修改API请求参数来实现动态筛选和排序。
Logo

这里是“一人公司”的成长家园。我们提供从产品曝光、技术变现到法律财税的全栈内容,并连接云服务、办公空间等稀缺资源,助你专注创造,无忧运营。

更多推荐