vue3.0+express从无到有建一个8080苹果影院

3,159 阅读11分钟

image-20210823205309812.png image-20210823205537812.png image-20210823210032506.png image-20210823210210461.png image-20210823210336898.png image-20210823210439937.png image-20210823210641617.png image-20210823210736410.png image-20210823210856356.png image-20210823210940650.png image-20210823211043524.png

- 前言

​ 本篇文章从无到有讲述用vue3.0框架搭建一个8080苹果影院。分别从前端、比较基础的后端,简单的性能优化和项目在服务器部署上线四个方面讲述。

- 前期准备

我们需要如下工具及组件库:

  1. [vscode](Visual Studio Code - Code Editing. Redefined)
  2. [navcat](Navicat | 支持 MySQL、MariaDB、MongoDB、SQL Server、SQLite、Oracle 和 PostgreSQL 的数据库管理)
  3. [vant组件库](Vant - 轻量、可靠的移动端组件库 (gitee.io))
  4. [element-plus](安装 - Element Plus 组件帮助文档 - 文江博客 (wenjiangs.com))
  5. 阿里巴巴矢量图标库
  6. express

- 项目目录结构

1. 前端

下面介绍下本项目的目录结构,前端主界面包含如下10个。

image-20210823203315712.png

2. 后端

后端获取数据分别为14快模块

image-20210823204017882.png

- 具体实现

一、前端实现

​ 现在来到前端部分,我们需要要知道如何用最少的代码实现最好的效果。无非考虑以下问题,组件的复用css预处理器的选择逻辑代码复用。其中尤为重要的是组件的复用,vue框架本身是组件响应式开发,合理的运用组件才是王道。其次,我用的css预处理器为less预处理器。

1. 组件分析

​ 从以上效果图重复出现的组件,很清楚知道以下组件被复用

image-20210823204843028.png

下面对比较难的组件分析:

  1. 1头部header.vue

image-20210823215519880.png

​ logo部分是用[钙网](免费logo在线制作-字体logo-logo设计-U钙网 (uugai.com))免费生成的logo,右部分的图标引用阿里巴巴矢量库图标。引入方式:在public目录下的index.html在线引入自己阿里巴巴矢量库的在线地址

image-20210823220059370.png

​ 接下来的导航栏就比较简单利用flex布局,加上router-link标签默认和第一个点击时自带router-link-active,我们设置以下样式就OK

.router-link-active {
    color: #FDC81F;
    text-decoration: none;
}
.router-link-active::after{
    content: "●";
    color: #FDC81F;
    position: absolute;
    top: 14px;
    left: 12px;
}

1.2 视频图片列表List.vue

image-20210823221307105.png

这个组件复用性最高,电影、动漫、综艺、首页、连续剧、视频详情和视频播放页都有这组件,我们只要分清楚其中的区别就可以。

我们接收如下参数即可

props:{
        isDetial:{ // 在详情页面的List点击跳转还是详情页,所以需要区分详情数据
            type:Boolean,
            default:false
        },
        isHome:{ //首页有标题和更多,所以需要此字段
            type:Boolean,
            default:true
        },
        title:{//首页标题字段
            type:String,
            default:''
        },
        Data:{//视频图片的数据,注意里面为数组 vue3默认值需要箭头函数,否则项目会报错
            type:Array,
            default:() =>{ return [] }
        },
        link:{//更多> 的跳转页面的路由路径
            type:String,
            default:''
        }
    },

1.3 分类菜单Meun.vue和分页pagination.vue

image-20210823224005037.png

image-20210823224136302.png

实现功能:有点击分类列表会出现不同的视频图片数据和分页统计,点击分页可以看到不同的视频图片数据。

首先,分类菜单是用vant组件的[Tab 标签页](Tab 标签页 - Vant (gitee.io))标签栏滚动,大家会好奇vant组件效果是这样的,和我的有一些不一样。

image-20210823225008526.png

​ 没错,vant组件的样式在vue中能修改,但切记只能改一次,因为vue的css样式有作用域,改了一个会影响另一个所以我们一般只用一次,复用性优先。

下面讲述下修改vue修改ui框架的样式的方法:

  • 按照文档的添加属性改样式

image-20210823225848002.png

  • 自己利用浏览器打开检查元素找到元素自带的类改变其样式

image-20210823230300426.png

注意:在css部分中不能加上scoped属性,加上就不会作用到vant组件内。

其次,分页是用element-plus ui框架做的。样式也是通过上述的方法二魔改。小伙伴肯定会问为什么需要用两种框架,不直接用vant组件库,或者改用element-ui。element-ui对vue3真心不兼容,容易出bug。直接用vant组件库的话需要的样式组件太少,还有vant组件库pagination真心不适合本站的风格。既然讲到这,小伙伴们肯定想知道如何引用两种组件。

需要在babel.config.js复制下面代码

module.exports = {
  presets: [
    '@vue/cli-plugin-babel/preset',
  ],
  "plugins": [
    ["import", {
      "libraryName": "vant",
      "libraryDirectory": "es",
      "style": true
    }],
      [
        "component",
        {
          "libraryName": "element-plus",
          "styleLibraryName": "theme-chalk"
        }
      ]
    ]
}

1.4 历史记录history.vue

image-20210823232846869.png

只需要点击播放就实现存到浏览器的本地缓存,点击history小图标就取出本地缓存的观看过的电影信息。

  • 点击播放放进浏览器本地存储
const  history = async ()=>{//这是一个点击播放视频的事件
            let href = router.currentRoute.value.params.id

            let data = await localStorage.getItem('movietitle')//获取为movietitle的本地存储
            if (data!==null) {
                //console.log(JSON.parse(data));
                state.historyData = JSON.parse(data)//如果不为空直接先取出来
            }
            if (state.historyData.find(v => v.title === state.title)===undefined) {//如果点击的不存在的话push进去,再次用localStorage.setItem()方法存储
                let MovieData = {
                    href:href,
                    title:state.title
                }
                state.historyData.push(MovieData)
                await localStorage.setItem('movietitle',JSON.stringify(state.historyData))
            }
        }
  • 点击history小图标得到本地存储
const getHistory = async ()=>{
      let data = await localStorage.getItem('movietitle')//获取本地存储
      if (data!==null) {//如果不为空就取出浏览器本地存储
        state.history = JSON.parse(data)
      }
    }
2. 页面逻辑分析
  1. 1 子父组件组件的传值和调用其方法
//在vue3.0中
//父组件
<template>
    <v-child @refesh="refesh"></v-child> <--传方法给子组件-->
</template>
<script>
import child from '@/components/child.vue'
export default {
    components:{
       "v-child":child
    }
    setup(){
        const refesh = ()=>{//父组件的方法
            axios.get('url').then(res=>{
                //...请求数据的操作改变页面的数据
            })
        }
        return{
            refesh
        }
    }
}
</script>
//子组件child.vue
<template>
    <button @click="update"></button>
</template>
<script>
import child from '@/components/child.vue'
emits:['refesh'],//注意调用父组件的方法需要用emits接收,否则报错
export default {
    setup(props,context){//setup第二个参数为上下文
        const update = (parms)=>{//子组件的方法
            context.emit('refesh',params)
        }
        return{
            update
        }
    }
}
</script>

上面这个例子是一个简单的子父组件传值的案例,在本项目中正是采用上面这种方法子组件向父组件传值并调用其方法。其中资源列表组件ResourceList.vue、视频图片列表组件List.vue针对详情页和分页组件Pagination.vue涉及上述传值方式,小伙伴们在结尾处打开地址,可以看下项目源码。

2.2 主页面之间的交互

​ 主页面组件的交互时,必定路由跳转,我们这是就应该需要知道路由的传参的方式,接下来以代码示例的方式讲解下本项目交互的方法。

​ 第一种使用vue-router的useRouter方法

//页面A
import { useRouter } from 'vue-router'
export default {
    setup(){
        const router = useRouter()
        const toB = ()=>{
            router.push(name:'B',params:{})//params传参
            //router.push(path:'/B',query:{})//query传参
            //router.push(path:`/B${参数}`)//路由传参
        }
        return {
            toB
        }
    }
}
//页面B
import { useRouter } from 'vue-router'
export default {
    setup(){
        const router = useRouter()
        const getData = ()=>{//利用useRouter方法得到数据
            let href = router.currentRoute.value.params.id
            //let href = router.currentRoute.value.query.id
            axios.get(`url/${href}`).then(res=>{
                //向后端传取不同的值获得数据
            })
        }
        return {
            getData
        }
    }
}

​ 第二种路由守卫onBeforeRouteUpdate监听路由变化

import {onBeforeRouteUpdate} from 'vue-router'
export default {
    const refesh = ()=>{
        axios.get('url').then(res=>{
            //改变页面的数据
        })
    }
    onBeforeRouteUpdate( to =>{

          refesh()//当路由变化时调用方法刷新数据

          console.log(to.params, to.query)

       })
	return {
        refesh
    }
}

二、后端实现

​ 终于来到后端了,本站的后端采用express引擎模板和node爬虫获取数据,连接本地mysql数据库。这部分对于前端小白来讲很重要,不止停留在切页面上,而是自己写逻辑,自己写接口。当然本项目涉及的后端比较基础,下面进入主题。

1. express模板格式

后端入口文件代码

var express = require('express');
var app = express();

app.all('*', function (req, res, next) {//做跨域请求
    res.header('Access-Control-Allow-Origin', '*');
    res.header('Access-Control-Allow-Headers', 'Content-Type');
    res.header('Access-Control-Allow-Methods', '*');
    res.header('Content-Type', 'application/json;charset=utf-8');
    next();
});

app.get('路由',方法)
//例
app.get('/lunbo',lunbo);



app.listen(4000, function () {
    console.log('app is listenling at port 4000');
});

方法操作代码示例

//node爬虫
var superagent = require('superagent');
var cheerio = require('cheerio');//操作页面

exports.lunbo = function (req, res, next) {
    console.log(req.url);
    superagent.get(`url`)
        .end(function (err, sres) {
            if (err) {
                return next(err);
            }
        	// sres.text 里面存储着网页的 html 内容,将它传给 cheerio.load 之后
            // 就可以得到一个实现了 jquery 接口的变量,我们习惯性地将它命名为 `$`
            // 剩下就都是 jquery 的内容了
        	var $ = cheerio.load(sres.text);//操作页面
        	//下面就是jQuery语法提取页面数据操作了
            $('.carousel-inner .item img').each(function (index, element) {
                var $element = $(element);
                items.push({
                    title:$element.attr('alt'),
                    src: $element.attr('src'),
                });
            });
            res.send(items);
        }
}

效果展示

image-20210824122210755.png

前端可以通过axios两种方式获取数据

​ 第一种axios直接请求

axios.get('http://localhost:4000/lunbo').then(res=>{
//获取上述数据
})

​ 第二种代理请求

axios.get('api/lunbo').then(res=>{
//获取上述数据
})
//在项目根目录下手创vue.config.js文件
module.exports = {
    //...
    devServer: {
      proxy: {
        '/api': {
            target: 'http://localhost:4000/',
            changeOrigin: true,
            ws: true,
            pathRewrite: {
                '^/api': ''
            }
        }
      }
    }
  };

上述两种方式就看个人爱好了,第一种一定需要对后端入口文件做跨域处理。

2. mysql数据库模块分析

配置文件config.js

//config.js
module.exports = {
    port: 4000, // express 服务启动端口
    /* 数据库相关配置 */
    db: {
      host: 'localhost', // 主机名
      port: 3306,        // MySQL 默认端口为 3306
      user: 'root',          // 使用 root 用户登入 MySQL
      password: '123', // MySQL 密码,用你自己的
      database: 'message' // 使用自己的数据库
    }
  }
  

数据库实例模块db.js

const mysql = require('mysql')

const config = require('./config').db // 获取数据库配置信息

module.exports = mysql.createConnection(config) // mysql.createConnection 方法创建连接实例

数据库操作模块

const connection = require('./db') // 获取连接实例

exports.message = function (req, res, next) {
    console.log(req.query);
    var insertsql = 'INSERT INTO comments(content,time) VALUES(?,?)'
    connection.query(insertsql, [req.query.content,req.query.time],(err, result, fields) => {//插入数据操作
        if (err) { 
            console.log('INSERT ERROR - ', err.message);
            throw err
        }
        console.log("INSERT SUCCESS");
    })
    connection.query('select * from comments', (err, comments) => {//查询操作
        if (err) {
          res.send('query error')
        } else {
          // 将 MySQL 查询结果作为路由返回值
          res.send(comments)
        }
    })
}

我们在message路由下就可以看到如下结果了

image-20210824124053831.png

3. 用navcat创建表

​ 可视化工具,傻瓜式操作。拓展下服务器上没有可视化工具,这是用mysql 命令行一顿操作就OK。不记得上网查~

image-20210824124756395.png

​ 现在这么一讲后端似乎不是很难嘛,轻松的写接口,操作数据库,其实前端是可以完成这一些的。到这我们实现了8080苹果影院的搭建了。

- 性能优化

1. 路由懒加载

​ 我们平常是使用第一种,这种方式一开始home界面的时候回加载所有的界面,如果服务器反应慢的话,首屏渲染慢,需要时间过长,会导致用户体验差。

import Home  from '@/views/Home.vue'
import B  from '@/views/B.vue'
//...
const routes = [
    {
        path: '/home',
        name: 'home',
        component: Home
    },
    {
        path: '/b',
        name: 'b',
        component: B
    },
    //...
]

这是我们应该用路由懒加载,加快首屏渲染速度。

const routes = [
    {
        path: '/a',
        name: 'a',
        component: () => import('../views/A')
    },
    {
        path: '/b',
        name: 'b',
        component: () => import('../views/B')
    },
]

2. 注释控制台打印代码

​ 我们在项目的过程中会写很多的consle.log()打印,在开发的时候没事,当在上线的时候,打印这些无用的数据返回给控制台无疑增加服务器和浏览器压力,会消耗性能。所以,在上线的时候把所有控制台打印代码注释。

- 服务器部署

​ 来到这个阶段了,首先我们需要在腾讯云或者阿里云租一台服务器,个人推荐使用宝塔远程控制服务器,详细看[安装教程](宝塔面板安装教程 - 简书 (jianshu.com)),和一些操作教程。假装小伙伴们已经安装好了宝塔看到如下界面

image-20210824132329627.png

进入终端安装node环境,详细看[服务器安装node教程](服务器上安装搭建node环境 - coder_zyz - 博客园 (cnblogs.com)),现在进入我们的服务器部署的主题。

一、后端部署

首先开通防火墙

image-20210824140659638.png

  1. 安装pm2项目管理器
npm i pm2 -g
  1. 将写好的后端拖进服务器

image-20210824135030446.png

  1. 进入后端文件夹运行后端
pm2 start server.js//入口文件

二、前端部署

​ 首先,连接后端,如果用了代理,即配置vue.config.js,把代码中的api代理换成服务器公网地址,否则打包后dist不会识别vue.config.js的配置,连接不上后端。

image-20210824135717778.png

  1. 安装全局安装下express
npm i express -g
  1. 用express 创建一个项目
express myApp

打开express项目可以看到以下目录结构 image-20210824133521354.png

  1. 进入前端项目文件夹 打包 可以发现多出一个dist文件夹。
npm run build

image-20210824140020483

  1. 把打包dist文件夹的内容放入服务器express创建的myApp文件夹下的public里面

image-20210824140808604.png

  1. 进入myApp文件运行打包后的前端
pm2 start app.js
  1. 用命令行查看项目运行状态
pm2 list

image-20210824141609819.png

出现以上图说明status为online前后端运行。

最后输入自己的公网地址:3000可以看到如下效果,项目上线成功。

image-20210824141842796.png

- 总结

​ vue3项目前端父组件交互props传值,emits调用父组件的方法,组件的复用性可以写不错的前端界面。本项目后端superagent和cheerion 包node爬虫写的接口加一点数据库。写好后端取决于你使用哪种语言哪种框架提供地便利性更好,大项目要写的话还是数据库的操作,一些更深层的操作。性能优化也就是我们常说的写代码问题,选择不同的引入方式,去掉无用的代码项目的性能大大不一样。服务器部署只需要一次就能学会,有前面三部分打基础,你上线的项目一定会很快.

ps:本项目[仓库地址](xiaofeng-movie-server: 潇风影视 (gitee.com))