智能电影推荐系统详解——前端篇
本电影推荐系统项目前端使用组件式开发,采用了现代化的Vue 3开发模式,代码结构清晰,功能完善,具有良好的用户体验。每个页面是一个独立的Vue组件,API请求集中管理在src/api/index.js,路由配置在src/main.js,样式采用Scoped CSS,避免全局污染。这种响应式设计,适配不同设备,图片动态映射,保证一致性,具有完善的表单验证和清空逻辑,平滑的页面过渡和交互效果。
前言
今天本文将会详细讲解智能电影推荐系统项目中的前端部分,包括其页面设计,算法设计,不同功能的实现,以及各页面之间是如何实现跳转的,并会附上相关核心代码。
技术栈
前端使用的技术栈有:
Vue 3(页面渲染)、Vue Router 4(路由管理)、Axios(接口请求)、CSS3(样式设计)。
项目配置依赖如下:
{
"name": "movie-front",
"version": "0.0.0",
"scripts": {
"dev": "vite", // 启动开发服务器
"build": "vite build", // 构建生产版本
"preview": "vite preview" // 预览生产构建
},
"dependencies": {
"axios": "^1.6.0", // HTTP客户端库
"vue": "^3.3.4", // Vue 3框架
"vue-router": "^4.2.4" // Vue路由
},
"devDependencies": {
"@vitejs/plugin-vue": "^4.2.3", // Vite的Vue插件
"vite": "^4.4.5" // 构建工具
}
}
前端详细设计
核心功能的设计思想
前端采用 Vue 3 的<script setup>语法,核心页面开发:
(1)首页(Home.vue):集成推荐列表、排行榜、搜索、登录弹窗,支持无刷新路由跳转;
(2)详情页(Detail.vue):沉浸式背景虚化设计,展示电影核心信息与相关推荐;
(3)AI 对话页(AiChat.vue):消息列表 + 输入框布局,支持自动滚动与异常兜底;
(4)管理员后台(Admin.vue):侧边栏 + 主面板布局,实现电影管理、用户审计、数据统计。
用户(未登录)→首页浏览(随机推荐)→搜索电影→查看详情→AI咨询→登录/注册 →查看最近观看。
用户(登录)→首页浏览(相似电影推荐)→搜索电影→查看详情→AI咨询→登录/注册 →查看最近观看。
管理员 → 管理员登录页 → 后台(数据统计/电影管理/用户审计)→ 退出。
实现方法
(1)全局路由与管理员权限首位
导入Vue与所有页面组件并定义路由规则。创建路由实例,配置 Hash 模式,挂载路由规则。
(2)统一 API 封装与工具函数内聚,登录/注册弹窗交互
通过axios调用HTTP请求库。创建axios实例并配置核心参数,导出 axios 实例封装业务接口。
(3)首页综合交互(Home.vue)
调用login/register接口,根据角色跳转(管理员→后台,普通用户→刷新首页),表单状态自动清空。输入关键词+选择类型,调用searchMovies接口、getRankings接口进行页面操作。Hero 页面hover放大、AI 悬浮入口hover显示提示、电影卡片hover 缩放。
(4)电影详情沉浸式展示(Detail.vue)
通过props接收路由传递的电影 ID。定义响应式数据movie存储电影详情。页面挂载时调用getDetail接口,加载对应电影的完整数据。
实现源代码
(1)全局路由与管理员权限首位
// 1. 定义路由映射
const routes = [
{ path: '/', component: Home }, // 首页路由
{ path: '/movie/:id', component: Detail }, // 电影详情页,:id是动态参数
{ path: '/admin', component: Admin }, // 管理页面
{ path: '/ai-chat', component: AiChat } // AI聊天页面
]
// 2. 创建路由实例
const router = createRouter({
history: createWebHashHistory(), // 使用hash模式路由(URL中有#)
routes // 路由配置
})
// 3. 创建并启动 Vue 应用
const app = createApp(App) // 创建Vue应用实例
app.use(router) // 注入路由
app.mount('#app') // 挂载到 index.html 中的 #app 节点
路由配置的核心部分,实现了能够在页面之间跳转的功能,点击Button按钮后应用路由实列进行页面交互。
(2)首页核心交互
#————————搜索交互————————
<!-- 模板:搜索栏交互触发 -->
<select v-model="searchState.type" class="search-type">...</select>
<input v-model="searchState.keyword" @keyup.enter="handleSearch" />
<button @click="handleSearch">...</button>
<!-- 脚本:搜索逻辑实现 -->
const handleSearch = async () => {
if (!searchState.keyword) return;
searchState.isSearching = true;
constres=awaitapi.get(`/movies/search?q=${searchState.keyword}&type=${searchState.type}`);
searchResults.value = res.data;
};
// 重置搜索(返回首页)
const resetHome = () => {
searchState.isSearching = false;
searchState.keyword = "";
};
#————————登录/注册交互————————
<!-- 模板:弹窗显隐+表单交互 -->
<button v-else class="btn-login" @click="showAuth = true">登录 / 注册</button>
<div v-if="showAuth" class="auth-overlay" @click.self="closeAuthAndClear">
<input v-model="authForm.username" placeholder="用户名" />
<input v-model="authForm.password" type="password" @keyup.enter="handleAuth" />
<button class="btn-submit" @click="handleAuth">确认</button>
</div>
<!-- 脚本:登录/注册核心逻辑 -->
const handleAuth = async() => {
if (!authForm.username || !authForm.password) return alert("请填写完整");
const path = isLoginMode.value ? "/auth/login" : "/auth/register";
try {
const res = await api.post(path, { username: authForm.username, password: authForm.password, role: authForm.role });
if (res.data.role === "admin") router.push("/admin"); // 管理员跳转后台
else location.reload(); // 普通用户刷新加载个性化数据
} catch (e) { alert("账号或密码错误"); }
};
// 表单清空/弹窗关闭交互
const clearAuthForm = () => { authForm.username = ""; authForm.password = ""; };
const closeAuthAndClear = () => { showAuth.value = false; clearAuthForm(); };
#————————电影跳转交互————————
<!-- 模板:全页面电影卡片/巨幕跳转 -->
<div class="hero" @click="$router.push('/movie/' + recs[0].id)">...</div>
<div class="card" v-for="m in recs" :key="m.id" @click="$router.push('/movie/' + m.id)">...</div>
<div class="rank-item" v-for="(m, idx) in rankList" :key="m.id" @click="$router.push('/movie/' + m.id)">...</div>
#————————个性化推荐加载交互————————
<!-- 模板:全页面电影卡片/巨幕跳转 -->
<div class="hero" @click="$router.push('/movie/' + recs[0].id)">...</div>
<div class="card" v-for="m in recs" :key="m.id" @click="$router.push('/movie/' + m.id)">...</div>
<div class="rank-item" v-for="(m, idx) in rankList" :key="m.id" @click="$router.push('/movie/' + m.id)">...</div>
#————————AI 咨询入口交互————————
<!-- 模板:推荐列表渲染 -->
<div class="container" v-if="recs.length > 0">
<div class="movie-scroll">
<div class="card" v-for="m in recs" :key="m.id">...</div>
</div>
</div>
<!-- 脚本:页面挂载加载推荐数据 -->
onMounted(async () => { initPage(); });
const initPage = async () => {
const res = await api.get("/movies/home"); // 请求后端推荐算法结果
recs.value = res.data.recs; // 渲染“为你推荐”列表
fetchRankings(); // 加载电影榜单
// 登录用户加载观影足迹(个性化数据)
const userRes = await api.get("/auth/user_info");
if (userRes.data.isLogin) {
Object.assign(user, userRes.data);
const fRes = await api.get("/user/footprints");
footprints.value = fRes.data;
}
};
首页,包含导航栏、搜索、登录/注册、电影推荐列表、排行榜等。
模板:结构复杂,包含多个部分,如导航栏、登录注册弹窗、AI悬浮入口、英雄区、推荐列表、排行榜等。
脚本:处理用户认证、搜索、加载推荐电影和排行榜等。
样式:首页的样式,使用flex和grid布局,响应式设计。
(3)电影详情页(Detail.vue)
<template>
<div class="detail-root" v-if="movie">
<!-- 注释:背景虚化+海报+核心信息卡片,非核心渲染结构 -->
<div class="blur-bg" :style="{ backgroundImage: `url(${getImg(movie.id)})` }"></div>
<div class="content">
<!-- 核心:电影详情卡片 -->
<div class="card">
<img :src="getImg(movie.id)" class="poster" />
<div class="info">
<h1>{{ movie.title }}</h1>
<p class="meta">评分: <span> {{ movie.score }}</span></p>
<!-- 注释:导演/主演/标签/简介,非核心渲染 -->
</div>
</div>
<!-- 核心:相似电影推荐(跳转逻辑) -->
<div class="movie-scroll">
<div class="card-small" v-for="r in movie.related" :key="r.id" @click="reload(r.id)">
<img :src="getImg(r.id)" />
</div>
</div>
</div>
</div>
</template>
<script setup>
import { ref, onMounted } from "vue";
import { useRoute, useRouter } from "vue-router";
import { getDetail } from "../api/index.js";
// 1. 关键性代码:路由与响应式数据定义
const route = useRoute();
const router = useRouter();
const movie = ref(null); // 电影详情核心数据
// 注释:图片映射算法,与首页一致
const getImg = (id) => `/images/${((id * 17) % 300) + 1}.jpg`;
// 2. 关键性代码:加载电影详情核心逻辑
const load = async (id) => {
try {
// 核心:调用详情接口,获取电影数据
const detailRes = await getDetail(id);
movie.value = detailRes;
window.scrollTo({ top: 0, behavior: "smooth" }); // 核心:滚动至顶部,优化体验
} catch (e) {
alert("加载详情失败!");
router.push("/");
}
};
// 3. 关键性代码:页面挂载与相似电影跳转
onMounted(() => load(route.params.id)); // 初始化加载
const reload = (id) => { // 相似电影跳转
router.push("/movie/" + id);
load(id);
};
</script>
<style scoped>
.detail-root { background: #000; min-height: 100vh; color: #fff; }
.blur-bg { position: fixed; top: 0; width: 100%; height: 100%; filter: blur(60px) brightness(0.2); }
.card { background: rgba(255,255,255,0.05); backdrop-filter: blur(20px); border-radius: 24px; padding: 40px; }
</style>
电影详情页面,展示电影的详细信息及相关推荐。
模板:电影海报、标题、导演、演员、简介等,以及相关推荐电影列表。
脚本:根据路由参数加载电影详情,并定义图片获取方法(与首页一致)。
样式:电影详情页的样式,包括背景虚化、卡片布局等。
(4)AI 对话页(AiChat.vue)
<template>
# 聊天页面整体容器
<div class="ai-page">
# 导航栏(返回、标题)
<nav class="ai-nav">...</nav>
# 聊天窗口(消息列表)
<div class="chat-container">
<div class="chat-window" id="chatBox">
# 消息项循环渲染
<div v-for="(msg, idx) in messages" :key="idx" :class="['msg-wrapper', msg.role]">
<div class="bubble">{{ msg.content }}</div>
</div>
# 加载中动画
<div v-if="loading" class="loading">...</div>
</div>
# 输入框
<div class="input-area">
<input
v-model="userInput"
@keyup.enter="sendMessage"
placeholder="输入你想聊的电影话题..."
:disabled="loading"
/>
<button @click="sendMessage" :disabled="loading">发送</button>
</div>
</div>
</div>
</template>
<script setup>
# 导入核心依赖
# 响应式数据定义
const userInput = ref("");
const loading = ref(false);
const messages = reactive([
{ role: "assistant", content: "你是一个电影专家,请简洁地回答用户关于电影的问题。" }
]);
# 核心:发送消息方法
const sendMessage = async () => {
# 输入校验(
if (!userInput.value.trim() || loading.value) return;
# 添加用户消息,清空输入框
messages.push({ role: "user", content: userInput.value });
userInput.value = "";
loading.value = true;
# 滚动到底部
scrollToBottom();
try {
const res = await aiChat(messages[messages.length - 1].content);
messages.push({ role: "assistant", content: res.reply });
} catch (e) {
# 异常兜底
messages.push({ role: "assistant", content: "抱歉,暂时无法回复你的问题。" });
} finally {
# 关闭加载状态,滚动到底部
loading.value = false;
scrollToBottom();
}
};
# 自动滚动到底部方法
const scrollToBottom = () => {
nextTick(() => {
const box = document.getElementById("chatBox");
box.scrollTop = box.scrollHeight;
});
};
</script>
<style scoped>
# 页面样式
</style>
AI聊天页面,用户可以与AI助手对话。
模板:聊天消息列表和输入框。
脚本:维护消息列表,发送用户消息到后端AI接口,并接收回复。
样式:聊天界面的样式,包括消息气泡、加载动画。
(5)后台登入页面(AdminLogin.vue)
<template>
<div class="admin-auth-container">
<div class="login-box">
<h1 class="logo">CINEMA<span>ADMIN</span></h1>
<p class="subtitle">管理后台安全认证</p>
<div class="form">
<!-- 用户名输入框 -->
<input
v-model="form.username"
placeholder="管理员账号"
class="admin-input"
/>
<!-- 密码输入框,type="password"隐藏输入 -->
<input
v-model="form.password"
type="password"
placeholder="认证密码"
class="admin-input"
@keyup.enter="handleLogin" <!-- 按回车键触发登录 -->
/>
<!-- 登录按钮 -->
<button class="admin-btn" @click="handleLogin">认证并进入</button>
</div>
<!-- 返回首页链接 -->
<p class="back-link" @click="$router.push('/')">← 返回普通用户首页</p>
</div>
</div>
</template>
<script setup>
import { ref } from "vue";
import api from "../api";
import { useRouter } from "vue-router";
const router = useRouter();
const form = ref({ username: "", password: "", role: "admin" }); // 表单数据
const handleLogin = async () => {
try {
const res = await api.post("/auth/login", form.value);
alert(res.data.msg);
router.push("/admin"); // 登录成功跳转到管理页面
} catch (e) {
alert("认证失败:非管理员账号或密码错误");
}
};
</script>
后台登录页面,用于管理员登录。
模板:一个简单的登录表单,包含用户名和密码输入框。
脚本:处理登录逻辑,提交表单到后端,登录成功后跳转到后台管理页面。
样式:设置了登录框的样式。
(6)后台管理页面模板部分(AdminLogin.vue)
<template>
<div class="admin-wrapper"> <!-- 管理员页面容器 -->
<!-- 侧边栏 -->
<aside class="sidebar">
<div class="logo">ADMIN<span>SYSTEM</span></div> <!-- 系统Logo -->
<nav>
<!-- 导航菜单,点击切换tab -->
<div :class="{ active: tab === 'stats' }" @click="tab = 'stats'">
📊 数据统计
</div>
<!-- 其他菜单项... -->
</nav>
<button class="exit-btn" @click="handleLogout">退出回到首页</button>
</aside>
<!-- 主面板 -->
<main class="main-panel">
<!-- 1. 统计面板 -->
<div v-if="tab === 'stats'">
<h2>系统运行状态</h2>
<div class="stats-grid">
<!-- 统计卡片,显示电影总量 -->
<div class="box">
<h3>{{ stats.movie_count || 0 }}</h3>
<p>电影总量</p>
</div>
<!-- 其他统计卡片... -->
</div>
</div>
<!-- 2. 电影管理面板 -->
<div v-if="tab === 'movies'">
<div class="table-header">
<h2>影片库管理</h2>
<!-- 新增影片按钮 -->
<button class="add-btn" @click="openAdd">+ 新增影片</button>
</div>
<!-- 电影列表表格 -->
<table class="table">
<thead>
<tr>
<th>ID</th>
<th>标题</th>
<th>导演</th>
<th>操作</th>
</tr>
</thead>
<tbody>
<!-- 循环渲染电影列表 -->
<tr v-for="m in movies" :key="m.id">
<td>{{ m.id }}</td>
<td>{{ m.title }}</td>
<td>{{ m.director }}</td>
<td>
<!-- 编辑和删除按钮 -->
<button class="btn-edit" @click="openEdit(m)">修改</button>
<button class="btn-del" @click="doDelete(m.id)">删除</button>
</td>
</tr>
</tbody>
</table>
</div>
<!-- 3. 用户管理面板 -->
<div v-if="tab === 'users'">
<h2>系统会员审计</h2>
<table class="table">
<thead>
<tr>
<th>ID</th>
<th>用户名</th>
<th>设置密码(明文)</th> <!-- 显示明文密码(安全风险) -->
<th>注册日期</th>
</tr>
</thead>
<tbody>
<!-- 循环渲染用户列表 -->
<tr v-for="c in clients" :key="c.id">
<td>{{ c.id }}</td>
<td>{{ c.username }}</td>
<td style="color: #e50914; font-family: monospace; font-weight: bold;">
{{ c.password }} <!-- 明文密码显示为红色 -->
</td>
<td>{{ c.date }}</td>
</tr>
</tbody>
</table>
</div>
</main>
<!-- 弹窗组件(添加/编辑电影) -->
<div v-if="showModal" class="modal-mask">
<div class="modal-box">
<h3>{{ form.id ? "编辑影片信息" : "录入新影片" }}</h3>
<!-- 表单输入项 -->
<div class="form-item">
<label>影片名称</label><input v-model="form.title" />
</div>
<!-- 其他表单字段... -->
<div class="modal-btns">
<button class="save" @click="saveMovie">确认提交</button>
<button class="cancel" @click="showModal = false">取消</button>
</div>
</div>
</div>
</div>
</template>
后台管理页面,包含数据统计、电影管理和用户管理三个标签页。
接口交互模块
设计思想
通过开启withCredentials: true,保障跨域请求中Session信息不丢失,支撑项目中的用户/管理员登录认证功能,是整个项目身份校验的核心基础。封装项目中实际调用到的接口,沿用 Axios 原生调用格式,降低组件调用的学习和适配成本。
实现方法
导入项目 Axios模块,作为接口通信的基础工具。配置baseURL(统一接口前缀)和withCredentials: true两个核心参数,形成项目统一的接口请求入口。采用 Axios 原生get()/post()方法进行简单封装。默认暴露 Axios 实例,按需暴露封装好的接口函数,供页面组件直接导入调用。
实现源代码
import axios from 'axios'; // 导入axios库
// 创建axios实例,配置基础URL和跨域设置
const api = axios.create({
baseURL: 'http://127.0.0.1:5000/api', // 后端API地址
withCredentials: true // 允许跨域携带Session/Cookie
});
// 导出默认的api实例
export default api;
// 导出具体的API请求函数
// 获取首页数据
export const getHome = () => api.get('/movies/home');
// 获取电影详情
export const getDetail = (id) => api.get(`/movies/detail/${id}`);
// 用户登录
export const login = (data) => api.post('/auth/login', data);
// 获取用户足迹
export const getFootprints = () => api.get('/user/footprints');
封装所有与后端API通信的请求,API接口地址起到请求通信的作用,前端通过API向后端请求数据,后端收到请求后将各种数据(比如电影详情数据等)返回给前端,就像在前端和后端之间修建了一条隧道,让数据可以在两者之间直接往返,并且在axios配置中设置了withCredentials: true来允许携带凭证,同时后端需要配置CORS,解决了跨域问题。
总结
本电影推荐系统项目前端使用组件式开发,采用了现代化的Vue 3开发模式,代码结构清晰,功能完善,具有良好的用户体验。每个页面是一个独立的Vue组件,API请求集中管理在src/api/index.js,路由配置在src/main.js,样式采用Scoped CSS,避免全局污染。
这种响应式设计,适配不同设备,图片动态映射,保证一致性,具有完善的表单验证和清空逻辑,平滑的页面过渡和交互效果。
更多推荐



所有评论(0)