vue+node.js手把手教你搭建一个直播平台(二) 搭建前端框架

4,237 阅读6分钟

上一期,帅气的小羽给老铁们介绍了直播平台的项目的后端搭建,这期就让小羽带大家来搭建一下前端的框架。

1.创建前端工程

毫无疑问,搭建一个项目的框架,那第一步肯定是得创建一个工程啦。cmd命令,输入vue create mylive ,然后一直回车就好了。然后等待一小会,我们的初始化工程就创建完成啦。

image-20200905154457722

接着我们使用编辑器打开我们刚刚创建好的项目。不用我提醒了吧,vs code天下无敌。这就是我们刚刚创建的工程啦。下图是我们的项目目录。

image-20200905154740703

然后我们可以在cmd中输入npm run serve,然后通过提示就可以打开相关的网站,看到我们初始化的配置啦。

2.初始化工程

小伙伴们看到上面的那个界面了嘛?是不是跟我们的最终目标完全不一致,让我们来一场棒子的整容手术,将它慢慢的整成我们最终想要看到的样子。

2.1 初始化相关插件模块

替换package.json文件中的内容,这里的话我们是把所有的插件模块都安装了,后面就不用一个个的安装啦,方便快捷,值得信赖!接着就是cmd中输入cnpm install安装插件啦。

{
  "name": "mylive",
  "version": "0.1.0",
  "private": true,
  "scripts": {
    "serve": "vue-cli-service serve --open",
    "build": "vue-cli-service build",
    "lint": "vue-cli-service lint"
  },
  "dependencies": {
    "axios": "^0.20.0",
    "core-js": "^3.6.5",
    "feather-common": "^1.0.0",
    "flv.js": "^1.5.0",
    "less": "^3.12.2",
    "less-loader": "^7.0.0",
    "node-sass": "^4.13.1",
    "sass-loader": "^8.0.2",
    "socket.io-client": "^2.3.0",
    "view-design": "^4.1.1",
    "vue": "^2.6.10",
    "vue-baberrage": "^3.1.0",
    "vue-router": "^3.1.3",
    "vue-video-player": "^5.0.2",
    "vuex": "^3.5.1"
  },
  "devDependencies": {
    "@vue/cli-plugin-babel": "~4.5.0",
    "@vue/cli-plugin-eslint": "~4.5.0",
    "@vue/cli-service": "~4.5.0",
    "babel-eslint": "^10.1.0",
    "eslint": "^6.7.2",
    "eslint-plugin-vue": "^6.2.2",
    "node-sass": "^4.13.1",
    "style-resources-loader": "^1.3.3",
    "vue-template-compiler": "^2.6.11"
  },
  "eslintConfig": {
    "root": true,
    "env": {
      "node": true
    },
    "extends": [
      "plugin:vue/essential",
      "eslint:recommended"
    ],
    "parserOptions": {
      "parser": "babel-eslint"
    },
    "rules": {}
  },
  "browserslist": [
    "> 1%",
    "last 2 versions",
    "not dead"
  ]
}

2.2 初始化ui框架

人靠衣装,人靠马鞍。既让咱们的老铁们都这么帅啦,总不能让自己写的程序变丑了,那样就烘托不了咱们高贵的气质了。所以,我们采用了业界中比较高大上的iview。在main.js 中加入以下几行(在new Vue()之前)。

//引入iview
import ViewUI from 'view-design';
import 'view-design/dist/styles/iview.css';
Vue.use(ViewUI);

既然都引入了这么好看的ui组件,怎能不进行一番测试呢?在app.vue中添加button组件。然后打开网页就可以看到效果啦~

image-20200905163456890

image-20200905163515034

2.3 初始化axios

在src/assets/js中创建http.js,然后初始化axios。

/*
 * @description: 
 * @author: 小羽
 * @lastEditors: 小羽
 * @Date: 2020-08-31 15:49:05
 * @LastEditTime: 2020-09-03 10:33:20
 * @Copyright: 1.0.0
 */
import axios from 'axios'; // 引入axios
import Qs from 'qs'; // 引入qs模块,用来序列化post类型的数据
let myAxios = axios.create({})
myAxios.defaults.headers.post['Content-Type'] = 'application/json';

// http request 拦截器
myAxios.interceptors.request.use(async config => {
        /* const token = await getSync("token").then(res=>{
            return res.token
        }) */ 
        let token = localStorage.getItem("living_token")
        if (token) { // 判断是否存在token,如果存在的话,则每个http header都加上token
            config.headers.authorization = decodeURI(token)  //请求头加上token
        }
        return config
    },
    err => {
        return Promise.reject(err)
    }
)

// http response 拦截器
myAxios.interceptors.response.use(
    response => {
        //拦截响应,做统一处理 
        //console.log("response.data", response.data)
        if (response.data.code) {
            switch (response.data.code) {
                case 1002:
                    store.state.isLogin = false
                    router.replace({
                    path: 'login',
                    query: {
                            redirect: router.currentRoute.fullPath
                        }
                    })
                }
        }
        return response
    },
    //接口错误状态处理,也就是说无响应时的处理
    error => {
        return Promise.reject(error) // 返回接口返回的错误信息
    }
)

export default myAxios

2.4 替换网页的相关文件

2.4.1 替换favicon.ico

把public下的favicon.ico文件替换成自己喜欢的图片。偷偷告诉你们,这个其实只需要把你喜欢的图片换成这个名字后再丢进来就阔以啦,你们可不准告诉别人哦~然后重启vue,就可以看到我们的图标变啦,发现没变化的小伙伴们可以ctrl+f5(清缓存刷新)。

2.4.2 修改tab标题

public下的index.html,修改title标签中的内容为小羽直播。

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="utf-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width,initial-scale=1.0">
    <link rel="icon" href="<%= BASE_URL %>favicon.ico">
    <title>小羽直播</title>
  </head>
  <body>
    <noscript>
      <strong>We're sorry but <%= htmlWebpackPlugin.options.title %> doesn't work properly without JavaScript enabled. Please enable it to continue.</strong>
    </noscript>
    <div id="app"></div>
    <!-- built files will be auto injected -->
  </body>
</html>

最终结果如下

image-20200905165250004

2.5 引入相关的js文件

2.5.1 引入bus线程

什么是bus线程?用个人对bus线程的理解来说,bus线程是发布订阅模式的一种实现。由于目前老铁们在搭建自己的直播间嘛,所以小羽就用直播间来举个栗子,解析一下什么是发布订阅模式。比如说老铁喜欢玩快乐风男,然后看到有一个快乐风男玩的贼帅的主播(比如小羽,疯狂暗示),然后一天不看就睡不着觉的那种。但是这个主播有个习惯,就是不喜欢定时直播。那现在广大观众老爷们总不能一天到晚守在直播间吧?那怎么办呢?这里就涉及到了发布订阅模式。假如路人甲和路人乙都订阅了这个主播A,而路人甲和路人丙又订阅了另外一个主播B。此时就会有以下两种情况:

1.当主播A上线的时候,就会自动发送一条消息给路人甲和路人乙,而路人丙则不会收到任何信息。

2.当主播B上线的时候,就会自动发送一条消息给路人甲和路人乙,而路人丙则不会收到任何信息。

以上则是个人对发布订阅模式和bus线程的一些见解。

在src/assets/js下新增bus.js

import Vue from "vue";
const bus = new Vue();
export default bus;

2.5.2 引入common.js

common.js是小羽封装的一些常用的方法。在src/assets/js下新增common.js

/*
 * @description: 公共方法
 * @author: 小羽
 * @lastEditors: 小羽
 * @Date: 2020-09-06 15:24:07
 * @LastEditTime: 2020-09-06 16:25:28
 * @Copyright: 1.0.0
 */

class Common{
    //生成len位随机字符串
    getCode(len){
        var chars = ['0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z'];
        var nums = "";
        for (var i = 0; i < len; i++) {
        var id = parseInt(Math.random() * 62);
        nums += chars[id];
        }
        return nums;
    }

    //获取url中的单个数据
    getUrlParam(name){
        var reg = new RegExp("(^|&)"+ name +"=([^&]*)(&|$)");
        var r = window.location.search.substr(1).match(reg);
        if(r!=null)return decodeURI(r[2]); return null;
    }

    //获取url中的所有数据
    getUrlParams(){
        let url = window.location.search;  //url中?之后的部分
        console.log(window.location)
        url = url.substring(1);    //去掉?
        let dataObj = {};
        if(url.indexOf('&')>-1){
            url = url.split('&');
            for(let i=0; i<url.length; i++){
                let arr = url[i].split('=');
                dataObj[arr[0]] = arr[1];
            }
        }else{
            url = url.split('=');
            dataObj[url[0]]= url[1];
        }
        return dataObj;
    }

    //队列
    myQueue(){
        let items = []
        this.list = ()=>{
            return items
        }
        //向队列的尾部添加元素
        this.enqueue = (ele)=>{
            items.push(ele)
        }
        //从队列的头部移除元素
        this.dequeue = (ele)=>{
            items.shift()
        }
        //返回队列最前面的项
        this.front = ()=>{
            return items[0]
        }
        //返回队列最后一项
        this.end = ()=>{
            return items[items.length-1]
        }
        //返回队列是否为空
        this.isEmpty = ()=>{
            return items.length === 0
        }
        //返回队列的长度
        this.size = () =>{
            return items.length
        }
        //打印队列
        this.print = () =>{
            console.log(items.toString())
        }
    }
}


// 防抖 (只执行最后一次点击)
export const Debounce = (fn, t) => {
    let delay = t || 500;
    let timer;
    return function () {
        let args = arguments;
        if(timer){
            clearTimeout(timer);
        }
        timer = setTimeout(() => {
            timer = null;
            fn.apply(this, args);
        }, delay);
    }
};

//节流(先执行一次,过了t/1000秒后,有操作再执行执行第二次))
export const Throttle = (fn, t) => {
    let last;
    let timer;
    let interval = t || 500;
    return function () {
        let args = arguments;
        let now = +new Date();
        if (last && now - last < interval) {
            clearTimeout(timer);
            timer = setTimeout(() => {
                last = now;
                fn.apply(this, args);
            }, interval);
        } else {
            last = now;
            fn.apply(this, args);
        }
    }
};

const common = new Common();
export {common}

在main.js中,将common.js注入到vue原型中。

image-20200907170208521

2.5.3 引入config.js

config.js是一个配置文件主要用来配置生产环境和开发环境中的不用url

/*
 * @description: 
 * @author: 小羽
 * @github: https://github.com/lyff1006
 * @lastEditors: 小羽
 * @Date: 2020-09-01 20:20:07
 * @LastEditTime: 2020-09-07 15:24:43
 * @Copyright: 1.0.0
 */
const env = process.env
const baseUrl = env.NODE_ENV==="development"?"http://127.0.0.1":"http://www.example.com"
const baseEnv = {
    env:env.NODE_ENV,//当前环境
    mode:env.VUE_APP_CURRENTMODE,//当前模式
    webUrl : env.VUE_APP_CURRENTMODE==="electron"?`${baseUrl}:8512`:"/webserve",
    socketUrl : {
        //barrage:`${baseUrl}:8511/barrage`,
        barrage:env.NODE_ENV==="development"?`${baseUrl}:8511/barrage`:`${baseUrl}/barrage`,
    },
    //livingUrl : env.NODE_ENV==="development"?`${baseUrl}:8000/live`:`${baseUrl}/live/live`
    livingUrl : env.NODE_ENV==="development"?`${baseUrl}:8000/live`:`${baseUrl}/live/live`

}

export default baseEnv

在main.js中将config.js注入到vue原型中。

image-20200907170258769

3.配置vue.config.js

在根目录下新建vue.config.js。用过vuecli3的老哥们应该都知道,这个文件是vuecli的默认文件,主要是用来配置与webpack相关的内容。

/*
 * @description: 
 * @author: 小羽
 * @github: https://github.com/lyff1006
 * @lastEditors: 小羽
 * @Date: 2019-10-09 21:55:04
 * @LastEditTime: 2020-09-07 15:31:10
 * @Copyright: 1.0.0
 */

const path = require('path');

function addStyleResource(rule) {
    rule.use('style-resource')
        .loader('style-resources-loader')
        .options({
            patterns: [path.resolve(__dirname, "./src/assets/style/common.less")]
        })
}

module.exports = {
    //eslint开关
    lintOnSave: false,
    //生成环境是否生成map文件
    productionSourceMap: false,
    devServer: {
        host: '0.0.0.0',
        //代理配置
        proxy: {
            '/webserve': {
                target: 'http://127.0.0.1:8512',
                ws: true,
                changeOrigin: true,
                pathRewrite: {
                    '^/webserve': ''
                }
            },
        },
    },
    chainWebpack: (config) => {
        //配置less
        const types = ['vue-modules', 'vue', 'normal-modules', 'normal']
        types.forEach(type => addStyleResource(config.module.rule('less').oneOf(type)))
    },
}

4.配置.gitinore

该文件是配置git相关的,主要提醒git,哪些文件需要上传到git仓库,那些文件不需要。非必须项,如果不使用git的老铁可以忽略哦~

.DS_Store
node_modules
/dist


# local env files
.env.local
.env.*.local

# Log files
npm-debug.log*
yarn-debug.log*
yarn-error.log*
pnpm-debug.log*

# Editor directories and files
.idea
.vscode
*.suo
*.ntvs*
*.njsproj
*.sln
*.sw?

小结

本期主要介绍了如何搭建一个前端工程,并对该工程进行一些初始化的配置。本来是想直接开始撸界面的,但后面又想了一下,其实应该按照正常的开发逻辑来讲比较好。也就是先搭框架再撸代码(一个好用的框架真的能让开发的过程变得事半功倍),所以界面的就往后推了。

觉得小羽教得还阔以得话就来波点赞和关注呗~

ps:纯原创,转载请标明出处