小丑

小丑 查看完整档案

填写现居城市  |  填写毕业院校  |  填写所在公司/组织填写个人主网站
编辑
_ | |__ _ _ __ _ | '_ \| | | |/ _` | | |_) | |_| | (_| | |_.__/ \__,_|\__, | |___/ 该用户太懒什么也没留下

个人动态

小丑 提出了问题 · 9月1日

本地测试webRTC,对端视频死活开不了

本地测试webRTC,没报任何错,但对端视频就是死活都不开,问了几个群都没人答,自学者好难。有没有思否大佬指点一下啊,先谢为敬。

问题补充:前后端都没报任何错。我确定后端信令的转发是没有问题的。现在贴一下前端调试打印的信息:image.png

image.png

前端代码:

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
    <script data-original="./socket.io/socket.io.js"></script>
    <script data-original="./adapter.js"></script>
    <style>
        video {
            border: 1px solid #eee;
        }
    </style>
</head>

<body>
    <div>
        <div class="videoBox">
            <video class="video" id="localVideo" autoplay muted></video>
            <video class="video" id="remoteVideo" autoplay muted></video>
        </div>
        <div class="videoFooter row">
            <button id="startButton">Start</button>
            <button id="btnleave">leave</button>
        </div>
        <script>
            let localVideo = document.getElementById('localVideo')
            let remoteVideo = document.getElementById('remoteVideo')
            let startButton = document.getElementById('startButton')
            let btnleave = document.getElementById('btnleave')
            let localStream = null;
            let socket;
            let pc = false;
            var roomid = '111111';
            let state = 'init';

            function connSignalLerver() {
                startButton.disabled = true;
                btnleave.disabled = false;
                start();
                return true;
            }

            function createPeerConnection() {
                if (pc === false) {
                    console.log('开始创建PeerConnection')
                    console.log(pc)

                    pc = new RTCPeerConnection();
                    pc.onicecandidate = function (e) {
                        console.log('进入了监听ICE阶段')
                        console.log("ICE 收集状态: " + e.target.iceGatheringState);
                        if (e.candidate) {
                            console.log('本机ice', e.candidate)
                            sendMessage(roomid, {
                                type: 'candidate',
                                candidate: e.candidate
                            })

                        }
                    }
                    console.log("ICE connection state: " + pc.iceConnectionState);
                    pc.oniceconnectionstatechange = function (evt) {
                        console.log("ICE连接状态: " + evt.target.iceConnectionState);
                    }
                    localStream.getTracks().forEach((track) => {
                        console.log('进入了添加流阶段')
                        pc.addTrack(track);
                    })

                    pc.ontrack = (e) => {
                        console.log('监听到了远端流', e)
                        remoteVideo.srcObject = e.streams[0];
                    }
                    // pc.onaddstream = function (evt) {
                    //     var remote_video = document.getElementById('remote_video');
                    //     remote_video.src = window.URL.createObjectURL(evt.stream);
                    // }
                }

            }

            function closePeerConnection() {
                console.log('已关闭PeerConnection')
                if (pc) {
                    pc.close();
                    pc = null;
                }
            }
            //媒体协商之发送
            function sendMessage(roomid, data) {
                console.log('已进入发送阶段', data);
                if (socket) {
                    socket.emit('message', roomid, data);
                }
            }

            function getoffer(desc) {
                pc.setLocalDescription(desc);
                sendMessage(roomid, desc)
            }

            function getAnswer(desc) {
                console.log('发送了answer')
                pc.setLocalDescription(desc);
                sendMessage(roomid, desc);
            }

            function offerError(err) {
                console.error('offer创建失败:', err);
            }

            function answerError() {
                console.error('answer创建失败:', err);
            }

            function call() {
                console.log('state状态:', state)
                if (state === 'joinde_conn') {
                    // let offer_options = {
                    //     offerToReceiveAudio: 1,
                    //     offerToReceiveVideo: 1
                    // }
                    if (pc != null) {
                        console.log('发送了offer')
                        pc.createOffer()
                            .then(getoffer)
                            .catch(offerError)
                    }
                }
            }

            function conn() {
                socket = io.connect();
                socket.emit('join', roomid)
                socket.on('joinde', function (roomid, id) {
                    console.log('你加入了房间', roomid, id)
                    createPeerConnection();
                })
                socket.on('otherjoin', function (roomid, id) {
                    console.log('第二个加入了房间', id)
                    state = 'joinde_conn';
                    if (state === 'joinde_conn') {
                        createPeerConnection();
                        console.log('PeerConnection开启了')
                        call();
                    }
                    //到这步这止就可以做媒体协商了

                })
                socket.on('full', function (data) {
                    state = 'leaved'
                    console.log('满了', data)
                    socket.close();
                    alert('房间满了,你不能再进来了')
                    closeLocalMedia();
                    closePeerConnection();
                })
                socket.on('leaved', function (roomid, id) {
                    console.log('离开了房间', id)
                    state = 'leaved';
                    socket.close();
                    startButton.disabled = false;
                    btnleave.disabled = true;
                    closeLocalMedia();
                    closePeerConnection();
                })
                socket.on('bye', function (roomid, id) {
                    console.log('bye', id)
                    state = 'joinde_conn';
                    socket.close();
                    closeLocalMedia();
                    closePeerConnection();
                })
                //媒体协商之信令处理
                socket.on('rtc', function (roomid, data) {
                    console.log('收到了服务器转发:', data)
                    if (data) {
                        if (data.type === 'offer') {
                            console.log('收到了offer', data)
                            let offer = new RTCSessionDescription();
                            offer.sdp = data.sdp;
                            offer.type = "offer";
                            pc.setRemoteDescription(offer);
                            pc.createAnswer()
                                .then(getAnswer)
                                .catch(answerError);
                        } else if (data.type === 'answer') {
                            console.log('收到了answer', data)
                            let answer = new RTCSessionDescription();
                            answer.sdp = data.sdp;
                            answer.type = "answer";
                            pc.setRemoteDescription(answer);
                        } else if (data.type === 'candidate') {
                            pc.addIceCandidate(data.candidate);
                            console.log('添加候选人成功', typeof (data.candidate))
                        } else {
                            console.error('收到未知类型的信令!', data)
                        }
                    }
                })
            }

            function getMediaStream(stream) {
                localStream = stream;
                localVideo.srcObject = localStream;
                conn();
            }

            function handleError(err) {
                console.log('无法获取设备:', err)
            }

            function start() {
                if (!navigator.mediaDevices || !navigator.mediaDevices.getUserMedia) {
                    console.error('你的浏览器不支持WEBRTC')
                } else {
                    let constraints = {
                        video: true,
                        audio: true,
                    }
                    navigator.mediaDevices.getUserMedia(constraints)
                        .then(getMediaStream)
                        .catch(handleError)
                }
            }

            function closeLocalMedia() {
                if (localStream && localStream.getTracks()) {
                    localStream.getTracks().forEach((track) => {
                        track.stop();
                    });
                }
            }

            function leave() {
                if (socket) {
                    socket.emit('leave', '1111')
                    closePeerConnection();
                    closeLocalMedia();
                }
                startButton.disabled = false;
                btnleave.disabled = true;
            }
            startButton.onclick = connSignalLerver;
            btnleave.onclick = leave;
        </script>
</body>

</html>

node.js代码:

const express = require('express');
const app = express();
let server = require('http').Server(app)
let io = require('socket.io')(server);
app.set('view engine', 'ejs');
app.use(express.static('public'))
app.get('/', function (req, res) {
    res.render('index')
})
let num = 0;
io.on('connection', function (socket) {
    console.log('有人连接了')
    socket.on('join', function (data) {
        num = num + 1;
        if (num > 2) {
            socket.emit('full', '人满了')
        }
        socket.join(data)
        socket.roomid = data;
        socket.emit('joinde', data, socket.id)
        console.log(socket.id + '加入房间成功')
        if (num == 2) {
            socket.to(data).emit('otherjoin', data, socket.id)
        }
    })
    socket.on('message', function (roomid, data) {
        console.log(roomid, data)
        socket.to(roomid).emit('rtc', roomid, data)
    })
    socket.on('leave', function () {
        num = num - 1;
        socket.broadcast.emit('leaved', socket.id, socket.roomid)
    })
    socket.on('disconnect', function () {
        num = num - 1;
        socket.broadcast.emit('bye', socket.id, socket.roomid)
    })
    // socket.on('offer', function (data) {
    //     socket.broadcast.emit('offer', data)
    // })
    // socket.on('answer', function (data) {
    //     socket.broadcast.emit('ice', data)
    // })
})
server.listen(3000, '0.0.0.0', function () {
    console.log('服务器开始运行!,http://127.0.0.1:3000/')
});

关注 1 回答 0

小丑 提出了问题 · 8月21日

如何获得所有连接到某命名空间下的socket实例?

我用的socket.io版本是2.3。

关注 3 回答 1

小丑 收藏了文章 · 7月11日

vuex最简单、最详细的入门文档

如果你在使用 vue.js , 那么我想你可能会对 vue 组件之间的通信感到崩溃 。

我在使用基于 vue.js 2.0 的UI框架 ElementUI 开发网站的时候 , 就遇到了这种问题 : 一个页面有很多表单 , 我试图将表单写成一个单文件组件 , 但是表单 ( 子组件 ) 里的数据和页面 ( 父组件 ) 按钮交互的时候 , 它们之间的通讯很麻烦 :

<!--父组件中引入子组件-->
<template>
  <div>
    <a href="javascript:;" @click="show = true">点击</a>
    <t-dialog :show.sync="show"></t-dialog>
  </div>
</template>

<script>
import dialog from './components/dialog.vue'
export default {
  data(){
    return {
      show:false
    }
  },
  components:{
    "t-dialog":dialog
  }
}
</script>


<!--子组件-->
<template>
  <el-dialog :visible.sync="currentShow"></el-dialog>
</template>

<script>
export default {
  props:['show'],
  computed:{
      currentShow:{
          get(){
              return this.show
          },
          set(val){
              this.$emit("update:show",val)
          }
      }
  }
}
</script>

之所以这么麻烦 , 是因为父组件可以通过 props 给子组件传递参数 , 但子组件内却不能直接修改父组件传过来的参数。

这时候 , 使用 vuex 就可以比较方便的解决这种问题了 :

<!--父组件中引入子组件-->
<template>
  <div>
    <a href="javascript:;" @click="$store.state.show = true">点击</a>
    <t-dialog></t-dialog>
  </div>
</template>

<script>
import dialog from './components/dialog.vue'
export default {
  components:{
    "t-dialog":dialog
  }
}
</script>


<!--子组件-->
<template>
  <el-dialog :visible.sync="$store.state.show"></el-dialog>
</template>

<script>
export default {}
</script>

是不是方便了许多 , 这就是 vuex 最简单的应用 , 不要被网上其他教程吓到 , vuex 原来可以这么简单 !

安装、使用 vuex

首先我们在 vue.js 2.0 开发环境中安装 vuex :

npm install vuex --save

然后 , 在 main.js 中加入 :

import vuex from 'vuex'
Vue.use(vuex);
var store = new vuex.Store({//store对象
    state:{
        show:false
    }
})

再然后 , 在实例化 Vue对象时加入 store 对象 :

new Vue({
  el: '#app',
  router,
  store,//使用store
  template: '<App/>',
  components: { App }
})

完成到这一步 , 上述例子中的 $store.state.show 就可以使用了。

modules

前面为了方便 , 我们把 store 对象写在了 main.js 里面 , 但实际上为了便于日后的维护 , 我们分开写更好 , 我们在 src 目录下 , 新建一个 store 文件夹 , 然后在里面新建一个 index.js :

import Vue from 'vue'
import vuex from 'vuex'
Vue.use(vuex);

export default new vuex.Store({
    state:{
        show:false
    }
})

那么相应的 , 在 main.js 里的代码应该改成 :

//vuex
import store from './store'

new Vue({
  el: '#app',
  router,
  store,//使用store
  template: '<App/>',
  components: { App }
})

这样就把 store 分离出去了 , 那么还有一个问题是 : 这里 $store.state.show 无论哪个组件都可以使用 , 那组件多了之后 , 状态也多了 , 这么多状态都堆在 store 文件夹下的 index.js 不好维护怎么办 ?

我们可以使用 vuex 的 modules , 把 store 文件夹下的 index.js 改成 :

import Vue from 'vue'
import vuex from 'vuex'
Vue.use(vuex);

import dialog_store from '../components/dialog_store.js';//引入某个store对象

export default new vuex.Store({
    modules: {
        dialog: dialog_store
    }
})

这里我们引用了一个 dialog_store.js , 在这个 js 文件里我们就可以单独写 dialog 组件的状态了 :

export default {
    state:{
        show:false
    }
}

做出这样的修改之后 , 我们将之前我们使用的 $store.state.show 统统改为 $store.state.dialog.show 即可。

如果还有其他的组件需要使用 vuex , 就新建一个对应的状态文件 , 然后将他们加入 store 文件夹下的 index.js 文件中的 modules 中。

modules: {
    dialog: dialog_store,
    other: other,//其他组件
}

mutations

前面我们提到的对话框例子 , 我们对vuex 的依赖仅仅只有一个 $store.state.dialog.show 一个状态 , 但是如果我们要进行一个操作 , 需要依赖很多很多个状态 , 那管理起来又麻烦了 !

mutations 登场 , 问题迎刃而解 :

export default {
    state:{//state
        show:false
    },
    mutations:{
        switch_dialog(state){//这里的state对应着上面这个state
            state.show = state.show?false:true;
            //你还可以在这里执行其他的操作改变state
        }
    }
}

使用 mutations 后 , 原先我们的父组件可以改为 :

<template>
  <div id="app">
    <a href="javascript:;" @click="$store.commit('switch_dialog')">点击</a>
    <t-dialog></t-dialog>
  </div>
</template>

<script>
import dialog from './components/dialog.vue'
export default {
  components:{
    "t-dialog":dialog
  }
}
</script>

使用 $store.commit('switch_dialog') 来触发 mutations 中的 switch_dialog 方法。

这里需要注意的是:

  1. mutations 中的方法是不分组件的 , 假如你在 dialog_stroe.js 文件中的定义了
    switch_dialog 方法 , 在其他文件中的一个 switch_dialog 方法 , 那么
    $store.commit('switch_dialog') 会执行所有的 switch_dialog 方法。
  2. mutations里的操作必须是同步的。

你一定好奇 , 如果在 mutations 里执行异步操作会发生什么事情 , 实际上并不会发生什么奇怪的事情 , 只是官方推荐 , 不要在 mutationss 里执行异步操作而已。

actions

多个 state 的操作 , 使用 mutations 会来触发会比较好维护 , 那么需要执行多个 mutations 就需要用 action 了:

export default {
    state:{//state
        show:false
    },
    mutations:{
        switch_dialog(state){//这里的state对应着上面这个state
            state.show = state.show?false:true;
            //你还可以在这里执行其他的操作改变state
        }
    },
    actions:{
        switch_dialog(context){//这里的context和我们使用的$store拥有相同的对象和方法
            context.commit('switch_dialog');
            //你还可以在这里触发其他的mutations方法
        },
    }
}

那么 , 在之前的父组件中 , 我们需要做修改 , 来触发 action 里的 switch_dialog 方法:

<template>
  <div id="app">
    <a href="javascript:;" @click="$store.dispatch('switch_dialog')">点击</a>
    <t-dialog></t-dialog>
  </div>
</template>

<script>
import dialog from './components/dialog.vue'
export default {
  components:{
    "t-dialog":dialog
  }
}
</script>

使用 $store.dispatch('switch_dialog') 来触发 action 中的 switch_dialog 方法。

官方推荐 , 将异步操作放在 action 中。

getters

getters 和 vue 中的 computed 类似 , 都是用来计算 state 然后生成新的数据 ( 状态 ) 的。

还是前面的例子 , 假如我们需要一个与状态 show 刚好相反的状态 , 使用 vue 中的 computed 可以这样算出来 :

computed(){
    not_show(){
        return !this.$store.state.dialog.show;
    }
}

那么 , 如果很多很多个组件中都需要用到这个与 show 刚好相反的状态 , 那么我们需要写很多很多个 not_show , 使用 getters 就可以解决这种问题 :

export default {
    state:{//state
        show:false
    },
    getters:{
        not_show(state){//这里的state对应着上面这个state
            return !state.show;
        }
    },
    mutations:{
        switch_dialog(state){//这里的state对应着上面这个state
            state.show = state.show?false:true;
            //你还可以在这里执行其他的操作改变state
        }
    },
    actions:{
        switch_dialog(context){//这里的context和我们使用的$store拥有相同的对象和方法
            context.commit('switch_dialog');
            //你还可以在这里触发其他的mutations方法
        },
    }
}

我们在组件中使用 $store.state.dialog.show 来获得状态 show , 类似的 , 我们可以使用 $store.getters.not_show 来获得状态 not_show

注意 : $store.getters.not_show 的值是不能直接修改的 , 需要对应的 state 发生变化才能修改。

mapState、mapGetters、mapActions

很多时候 , $store.state.dialog.show$store.dispatch('switch_dialog') 这种写法又长又臭 , 很不方便 , 我们没使用 vuex 的时候 , 获取一个状态只需要 this.show , 执行一个方法只需要 this.switch_dialog 就行了 , 使用 vuex 使写法变复杂了 ?

使用 mapState、mapGetters、mapActions 就不会这么复杂了。

以 mapState 为例 :

<template>
  <el-dialog :visible.sync="show"></el-dialog>
</template>

<script>
import {mapState} from 'vuex';
export default {
  computed:{

    //这里的三点叫做 : 扩展运算符
    ...mapState({
      show:state=>state.dialog.show
    }),
  }
}
</script>

相当于 :

<template>
  <el-dialog :visible.sync="show"></el-dialog>
</template>

<script>
import {mapState} from 'vuex';
export default {
  computed:{
    show(){
        return this.$store.state.dialog.show;
    }
  }
}
</script>

mapGetters、mapActions 和 mapState 类似 , mapGetters 一般也写在 computed 中 , mapActions 一般写在 methods 中。

弄懂上面这些 , 你可以去看vuex文档了 , 应该能看懂了。

查看原文

小丑 赞了文章 · 7月11日

vuex最简单、最详细的入门文档

如果你在使用 vue.js , 那么我想你可能会对 vue 组件之间的通信感到崩溃 。

我在使用基于 vue.js 2.0 的UI框架 ElementUI 开发网站的时候 , 就遇到了这种问题 : 一个页面有很多表单 , 我试图将表单写成一个单文件组件 , 但是表单 ( 子组件 ) 里的数据和页面 ( 父组件 ) 按钮交互的时候 , 它们之间的通讯很麻烦 :

<!--父组件中引入子组件-->
<template>
  <div>
    <a href="javascript:;" @click="show = true">点击</a>
    <t-dialog :show.sync="show"></t-dialog>
  </div>
</template>

<script>
import dialog from './components/dialog.vue'
export default {
  data(){
    return {
      show:false
    }
  },
  components:{
    "t-dialog":dialog
  }
}
</script>


<!--子组件-->
<template>
  <el-dialog :visible.sync="currentShow"></el-dialog>
</template>

<script>
export default {
  props:['show'],
  computed:{
      currentShow:{
          get(){
              return this.show
          },
          set(val){
              this.$emit("update:show",val)
          }
      }
  }
}
</script>

之所以这么麻烦 , 是因为父组件可以通过 props 给子组件传递参数 , 但子组件内却不能直接修改父组件传过来的参数。

这时候 , 使用 vuex 就可以比较方便的解决这种问题了 :

<!--父组件中引入子组件-->
<template>
  <div>
    <a href="javascript:;" @click="$store.state.show = true">点击</a>
    <t-dialog></t-dialog>
  </div>
</template>

<script>
import dialog from './components/dialog.vue'
export default {
  components:{
    "t-dialog":dialog
  }
}
</script>


<!--子组件-->
<template>
  <el-dialog :visible.sync="$store.state.show"></el-dialog>
</template>

<script>
export default {}
</script>

是不是方便了许多 , 这就是 vuex 最简单的应用 , 不要被网上其他教程吓到 , vuex 原来可以这么简单 !

安装、使用 vuex

首先我们在 vue.js 2.0 开发环境中安装 vuex :

npm install vuex --save

然后 , 在 main.js 中加入 :

import vuex from 'vuex'
Vue.use(vuex);
var store = new vuex.Store({//store对象
    state:{
        show:false
    }
})

再然后 , 在实例化 Vue对象时加入 store 对象 :

new Vue({
  el: '#app',
  router,
  store,//使用store
  template: '<App/>',
  components: { App }
})

完成到这一步 , 上述例子中的 $store.state.show 就可以使用了。

modules

前面为了方便 , 我们把 store 对象写在了 main.js 里面 , 但实际上为了便于日后的维护 , 我们分开写更好 , 我们在 src 目录下 , 新建一个 store 文件夹 , 然后在里面新建一个 index.js :

import Vue from 'vue'
import vuex from 'vuex'
Vue.use(vuex);

export default new vuex.Store({
    state:{
        show:false
    }
})

那么相应的 , 在 main.js 里的代码应该改成 :

//vuex
import store from './store'

new Vue({
  el: '#app',
  router,
  store,//使用store
  template: '<App/>',
  components: { App }
})

这样就把 store 分离出去了 , 那么还有一个问题是 : 这里 $store.state.show 无论哪个组件都可以使用 , 那组件多了之后 , 状态也多了 , 这么多状态都堆在 store 文件夹下的 index.js 不好维护怎么办 ?

我们可以使用 vuex 的 modules , 把 store 文件夹下的 index.js 改成 :

import Vue from 'vue'
import vuex from 'vuex'
Vue.use(vuex);

import dialog_store from '../components/dialog_store.js';//引入某个store对象

export default new vuex.Store({
    modules: {
        dialog: dialog_store
    }
})

这里我们引用了一个 dialog_store.js , 在这个 js 文件里我们就可以单独写 dialog 组件的状态了 :

export default {
    state:{
        show:false
    }
}

做出这样的修改之后 , 我们将之前我们使用的 $store.state.show 统统改为 $store.state.dialog.show 即可。

如果还有其他的组件需要使用 vuex , 就新建一个对应的状态文件 , 然后将他们加入 store 文件夹下的 index.js 文件中的 modules 中。

modules: {
    dialog: dialog_store,
    other: other,//其他组件
}

mutations

前面我们提到的对话框例子 , 我们对vuex 的依赖仅仅只有一个 $store.state.dialog.show 一个状态 , 但是如果我们要进行一个操作 , 需要依赖很多很多个状态 , 那管理起来又麻烦了 !

mutations 登场 , 问题迎刃而解 :

export default {
    state:{//state
        show:false
    },
    mutations:{
        switch_dialog(state){//这里的state对应着上面这个state
            state.show = state.show?false:true;
            //你还可以在这里执行其他的操作改变state
        }
    }
}

使用 mutations 后 , 原先我们的父组件可以改为 :

<template>
  <div id="app">
    <a href="javascript:;" @click="$store.commit('switch_dialog')">点击</a>
    <t-dialog></t-dialog>
  </div>
</template>

<script>
import dialog from './components/dialog.vue'
export default {
  components:{
    "t-dialog":dialog
  }
}
</script>

使用 $store.commit('switch_dialog') 来触发 mutations 中的 switch_dialog 方法。

这里需要注意的是:

  1. mutations 中的方法是不分组件的 , 假如你在 dialog_stroe.js 文件中的定义了
    switch_dialog 方法 , 在其他文件中的一个 switch_dialog 方法 , 那么
    $store.commit('switch_dialog') 会执行所有的 switch_dialog 方法。
  2. mutations里的操作必须是同步的。

你一定好奇 , 如果在 mutations 里执行异步操作会发生什么事情 , 实际上并不会发生什么奇怪的事情 , 只是官方推荐 , 不要在 mutationss 里执行异步操作而已。

actions

多个 state 的操作 , 使用 mutations 会来触发会比较好维护 , 那么需要执行多个 mutations 就需要用 action 了:

export default {
    state:{//state
        show:false
    },
    mutations:{
        switch_dialog(state){//这里的state对应着上面这个state
            state.show = state.show?false:true;
            //你还可以在这里执行其他的操作改变state
        }
    },
    actions:{
        switch_dialog(context){//这里的context和我们使用的$store拥有相同的对象和方法
            context.commit('switch_dialog');
            //你还可以在这里触发其他的mutations方法
        },
    }
}

那么 , 在之前的父组件中 , 我们需要做修改 , 来触发 action 里的 switch_dialog 方法:

<template>
  <div id="app">
    <a href="javascript:;" @click="$store.dispatch('switch_dialog')">点击</a>
    <t-dialog></t-dialog>
  </div>
</template>

<script>
import dialog from './components/dialog.vue'
export default {
  components:{
    "t-dialog":dialog
  }
}
</script>

使用 $store.dispatch('switch_dialog') 来触发 action 中的 switch_dialog 方法。

官方推荐 , 将异步操作放在 action 中。

getters

getters 和 vue 中的 computed 类似 , 都是用来计算 state 然后生成新的数据 ( 状态 ) 的。

还是前面的例子 , 假如我们需要一个与状态 show 刚好相反的状态 , 使用 vue 中的 computed 可以这样算出来 :

computed(){
    not_show(){
        return !this.$store.state.dialog.show;
    }
}

那么 , 如果很多很多个组件中都需要用到这个与 show 刚好相反的状态 , 那么我们需要写很多很多个 not_show , 使用 getters 就可以解决这种问题 :

export default {
    state:{//state
        show:false
    },
    getters:{
        not_show(state){//这里的state对应着上面这个state
            return !state.show;
        }
    },
    mutations:{
        switch_dialog(state){//这里的state对应着上面这个state
            state.show = state.show?false:true;
            //你还可以在这里执行其他的操作改变state
        }
    },
    actions:{
        switch_dialog(context){//这里的context和我们使用的$store拥有相同的对象和方法
            context.commit('switch_dialog');
            //你还可以在这里触发其他的mutations方法
        },
    }
}

我们在组件中使用 $store.state.dialog.show 来获得状态 show , 类似的 , 我们可以使用 $store.getters.not_show 来获得状态 not_show

注意 : $store.getters.not_show 的值是不能直接修改的 , 需要对应的 state 发生变化才能修改。

mapState、mapGetters、mapActions

很多时候 , $store.state.dialog.show$store.dispatch('switch_dialog') 这种写法又长又臭 , 很不方便 , 我们没使用 vuex 的时候 , 获取一个状态只需要 this.show , 执行一个方法只需要 this.switch_dialog 就行了 , 使用 vuex 使写法变复杂了 ?

使用 mapState、mapGetters、mapActions 就不会这么复杂了。

以 mapState 为例 :

<template>
  <el-dialog :visible.sync="show"></el-dialog>
</template>

<script>
import {mapState} from 'vuex';
export default {
  computed:{

    //这里的三点叫做 : 扩展运算符
    ...mapState({
      show:state=>state.dialog.show
    }),
  }
}
</script>

相当于 :

<template>
  <el-dialog :visible.sync="show"></el-dialog>
</template>

<script>
import {mapState} from 'vuex';
export default {
  computed:{
    show(){
        return this.$store.state.dialog.show;
    }
  }
}
</script>

mapGetters、mapActions 和 mapState 类似 , mapGetters 一般也写在 computed 中 , mapActions 一般写在 methods 中。

弄懂上面这些 , 你可以去看vuex文档了 , 应该能看懂了。

查看原文

赞 682 收藏 603 评论 83

小丑 赞了问题 · 2019-09-29

求一个好用的html5网页富文本编辑器?

做前端开发的懂,求推荐。

.

关注 12 回答 9

小丑 收藏了问题 · 2019-09-29

求一个好用的html5网页富文本编辑器?

做前端开发的懂,求推荐。

.

小丑 赞了回答 · 2019-09-28

解决为什么说前后端分离不利于seo

seo 本质是一个服务器向另一个服务器发起请求,解析请求内容。但一般来说搜索引擎是不回去执行请求到的js的。也就是说,如果一个单页应用,html在服务器端还没有渲染部分数据数据,在浏览器才渲染出数据,而搜索引擎请求到的html是没有渲染数据的。 这样就很不利于内容被搜索引擎搜索到。 所以服务端渲染就是尽量在服务器发送到浏览器前 页面上就是有数据的。

第二个问题,一般的数据逻辑操作是放在后端的。排序这个如果仅仅是几条数据,前后端排序开起来是一样的,如果是有1000条数据,前端要排序就要都请求过来。这样显然是不合理的。

关注 10 回答 4

小丑 收藏了文章 · 2019-09-18

Web Socket & Socket.io

HTTP

HTTP无法轻松实现实时应用:

  • HTTP协议是无状态的,服务器只会响应来自客户端的请求,但是它与客户端之间不具备持续连接。
  • 我们可以非常轻松的捕获浏览器上发生的事件(比如用户点击了盒子),这个事件可以轻松产生与服务器的数据交互(比如Ajax)。但是,反过来却是不可能的:服务器端发生了一个事件,服务器无法将这个事件的信息实时主动通知它的客户端。只有在客户端查询服务器的当前状态的时候,所发生事件的信息才会从服务器传递到客户端。

但是,确实聊天室确实存在

方法:

  • 长轮询:客户端每隔很短的时间,都会对服务器发出请求,查看是否有新的消息,只要轮询速度足够快,例如1秒,就能给人造成交互是实时进行的印象。这种做法是无奈之举,实际上对服务器、客户端双方都造成了大量的性能浪费。
  • 长连接:客户端只请求一次,但是服务器会将连接保持,不会返回结果(想象一下我们没有写res.end()时,浏览器一直转小菊花)。服务器有了新数据,就将数据发回来,又有了新数据,就将数据发回来,而一直保持挂起状态。这种做法的也造成了大量的性能浪费。

WebSocket协议

WebSocket协议能够让浏览器和服务器全双工实时通信,互相的,服务器也能主动通知客户端

  • WebSocket的原理非常的简单:利用HTTP请求产生握手,HTTP头部中含有WebSocket协议的请求,所以握手之后,二者转用TCP协议进行交流(QQ的协议)。现在的浏览器和服务器之间,就是QQ和QQ服务器的关系了。所以WebSocket协议,需要浏览器支持,更需要服务器支持。
  • 支持WebSocket协议的浏览器有:Chrome 4、火狐4、IE10、Safari5
  • 支持WebSocket协议的服务器有:Node 0、Apach7.0.2、Nginx1.3
  • Socket.IO是业界良心,新手福音。它屏蔽了所有底层细节,让顶层调用非常简单。并且还为不支持WebSocket协议的浏览器,提供了长轮询的透明模拟机制。
  • Node.js上需要写一些程序,来处理TCP请求。 使用require('dgram') 模块
  • Node的单线程、非阻塞I/O、事件驱动机制,使它非常适合Socket服务器。

Socket.io

npm install socket.io

制作index.html页面。页面中必须引入 /socket.io/socket.io.js, 调用io函数,取得socket对象。

<script data-original="/socket.io/socket.io.js" type="text/javascript" charset="utf-8"></script>
<script type="text/javascript">
    var socket = io();  //socket 对象
</script>

服务器中:

var io = require('sockte.io')(server);
io.on('connection',function( socket ){
    //socket 对象
    socket.on('tiwen',function( msg ){
        console.log('服务器接受到了请求');
        //sockte.emit('huida','ok'); //单条返回
        //广播 , 就是给当前所有用户的发送信息。
        io.emit('huida','ok');
    });
});

客户端和服务器,都有socket对象。 两个对象都具有emit,和on的时间。emit用于发送,on用户接受。
发送的内容可以是任何类型的值。

案例

前台页面:

<body>

<h1>index页面</h1>
信息内容:<input type="text" name="" id="info" value="" />
发送: <input type="button" name="" id="btn" value="发送" />
<script data-original="/socket.io/socket.io.js" type="text/javascript" charset="utf-8"></script>

<script type="text/javascript">
  var socket = io();
  document.getElementById("btn").onclick = function (  ) {
    socket.emit('tiwen',document.getElementById("info").value);
  }
  socket.on('huida',function ( msg ) {
    console.log('回答:' + msg);
  });
</script>
</body>

后台:

var http = require('http');
var fs = require('fs');

var server = http.createServer(function ( req,res ) {
  if (  req.url == '/' ) {
    // 显示首页
    fs.readFile('./index.html',function ( err,data ) {
      res.end(data);
    });
  }
});


// 创建io对象
var io = require('socket.io')(server);

// 监听连接事件
io.on('connection',function ( socket ) {
  console.log( '一个客户端连接了' );
  socket.on('tiwen',function ( msg ) {
        // console.log( '提问为:' + msg );
        // socket.emit('huida','好呀');
    // 加上广播
    io.emit('huida',msg);
  });
});

server.listen(80);
查看原文

小丑 收藏了文章 · 2019-09-18

socket.io实现在线聊天页面

一.要点分析

(1) 对于socket.io,它是基于事件响应的socket,可以进行长时间的消息传递。其服务端使用的方法主要不过就是两个,on()和emit()

io.on('connetcion',function(socket) {
    socket.on('event',function(data) {
        /*执行相应的方法*/
        //通知客户端执行事件
        socket.emit('new event',{data:'mydata'});
    });
});

(2)搭建好服务器端后就进行客户端连接


//引入socket
var socket = io();

socket.on('event',function(data) {
    /*执行事件*/    
    //通知服务器端执行事件
    socket.emit('new event',{data:'mydata'});
});

(3)接下来就是对视图的搭建

二.源代码和注释分析

(1) app.js:

//引入express框架
var express = require('express');
var app = express();

//服务端创建
var server = require('http').createServer(app);
//使用socket.io进行通信
var io = require('socket.io')(server);
var port = process.env.POST || 8000;

//服务开启
server.listen(port,function() {
    console.log("The chatting room is running at port: " + port);
});

//使用静态文件目录
app.use(express.static(__dirname + '/public'));

//当前进入聊天室的人数
var usersNumber = 0;

//客户端通过socket链接服务端
io.on('connection',function(socket) {
    //默认没有用户进入
    var addUser = false;
    
    //客户端发送新的消息
    socket.on('new message',function(data) {
        //广播所有在线客户端新消息的产生
        socket.broadcast.emit('new message',{
            userName: socket.userName,
            message: data
        });
    });
    //客户端发送有用户加入的消息
    socket.on('add user',function(userName) {
        if(addUser) return;
        socket.userName = userName;
        usersNumber++;
        addUser=true;
        //向客户端发送登陆成功
        socket.emit('login',{
            userName: socket.userName,
            usersNumber:usersNumber
        });
        //广播有新用户加入
        socket.broadcast.emit('new user join',{
            userName:socket.userName,
            usersNumber:usersNumber
        });
    });
    //客户端断开链接
    socket.on('disconnect',function() {
        if(addUser) {
            usersNumber--;
            //通知所有客户端有用户离开
            socket.broadcast.emit('user left',{
                userName:socket.userName,
                usersNumber:usersNumber
            });
        }
    });
});

(2) main.js:

$(function(){
    //创建socket与服务端链接
    var socket = io();
    //通过jquery获取dom节点
    var $logPage = $('.logPage');
    var $logList = $('.logList');
    var $chatPage = $('.chatPage');
    var $messageContent = $('.messageContent');
    var $messageList = $('.messageList');
    var $messageInput = $('.messageInput');
    var $usernameInput = $('.usernameInput');
    var $sendMessage = $('.sendMessage');
    var $addUser = $('.addUser');
    var $loginPage = $('.loginPage');
    var $messageInputBar = $('.messageInputBar');

    //默认当前登陆输入框为焦点状态
    var $currentInput = $usernameInput.focus();

    //用于记录当前的用户名
    var userName;
    var connect = false;

    //监听服务器是否有新的消息产生,有的话就显示消息到列表
    socket.on('new message',function(data) {
        //在列表框中添加消息,类型为收取的消息
        addNewMessage(data,2);
    });

    //监听服务器是否登陆成功,成功就显示成功登陆
    socket.on('login',function(data) {
        userLogin(data);
    });

    //监听服务器是否有新的用户加入,有的话就显示
    socket.on('new user join',function(data) {
        newUserJoin(data);
    });

    //监听服务器是否有用户离开
    socket.on('user left',function(data){
        userLeft(data);
    });


    function userLogin(data) {
        //登陆成功,输出信息
        connect = true;
        $logList.empty();
        $logList.append('<li><p>Name: ' + data.userName + ' is joined</p><li>');
        $logList.append('<li><p>CurrentNumber: ' + data.usersNumber + '</p></li>');
    }


    function addNewMessage(data,state) {
        if(state == 1) {
            $messageList.append(
            '<div class="aui-chat-item aui-chat-right"><div class="aui-chat-inner"><div class="aui-chat-name">' + data.userName +'</div><div class="aui-chat-content">'+data.message+'</div></div></div>');
        }else {
            $messageList.append(
            '<div class="aui-chat-item aui-chat-left"><div class="aui-chat-inner"><div class="aui-chat-name">' + data.userName +'</div><div class="aui-chat-content">'+data.message+'</div></div></div>');
        }
    }

    function newUserJoin(data) {
        $logList.empty();
        $logList.append('<li><p>Name: ' + data.userName + ' is joined</p></li>');
        $logList.append('<li><p>CurrentNumber: ' + data.usersNumber + '</p></li>');
    }

    function userLeft(data) {
        $logList.empty();
        $logList.append('<li><p>User: ' + data.userName + ' has left</p><li>');
        $logList.append('<li><p>CurrentNumber: ' + data.usersNumber + '</p></li>');
    }

    //当用户点击发送消息时的事件
    $sendMessage.click(function(event) {
        /* Act on the event */
        var message = $messageInput.val();
        if (message && connect) {
      $messageInput.val('');
      addNewMessage({
        userName: userName,
        message: message
      },1);
      socket.emit('new message', message);
    }
    });    

    //当用户点击登陆事件
    $addUser.click(function(event) {
        /* Act on the event */
        userName = $usernameInput.val().trim();
        if (userName) {
      $loginPage.fadeOut();
      $chatPage.show();
      $messageInputBar.show();
      $loginPage.off('click');
      $currentInput = $messageInput.focus();
      socket.emit('add user', userName);
    }
    });

});

(3) index.html:

<!doctype html>
<html>
    <head>
    <meta charset="utf-8">
    <meta name="viewport" content="maximum-scale=1.0,minimum-scale=1.0,user-scalable=0,width=device-width,initial-scale=1.0"/>
    <meta name="format-detection" content="telephone=no,email=no,date=no,address=no">
    <link rel="stylesheet" type="text/css" href="./css/aui.2.0.css">
    <style type="text/css" media="screen">
        * {
            font-family: 'Microsoft Yahei';
        }
        .header {
            position: fixed;
        }
        .content {
            padding-top: 2rem;/*有顶部固定导航条时设置*/ padding-bottom: 5rem;/*有底部固定导航条时设置*/
        }
        .chatPage {
            display: none;
        }
        .logPage {
            background-color:#00FFFF;
            border-radius: .2rem;
        }
        .messageInputBar {
            display: none;
        }
            .chatItem {

            }
    </style>
    </head>
    <body>
        <header class="aui-bar aui-bar-nav header">Chatting Room</header>
        <div class="aui-content-padded content">
            <section class="logPage">
                <div class="">
                    <ul class="logList"></ul>
                </div>
            </section>
            <div class="chatPage aui-content">
                <section class="aui-chat messageList">
                </section>
            </div>
            <div class="loginPage">
                <div>Please input your name</div>
                <div class="aui-row">
                    <div class="aui-col-xs-9">
                        <input type="text" placeholder="Username" class="usernameInput">
                    </div>
                    <div class="aui-col-xs-3">
                        <div class="aui-btn aui-btn-primary addUser">Login</div>
                    </div>
                </div>
            </div>
        </div>
        <footer class="messageInputBar aui-bar aui-bar-tab aui-padded-l-15">
            <div class="aui-bar-tab-item">
                <input type="text" placeholder="Message" class="messageInput">
            </div>
            <div class="aui-bar-tab-item">
                <div class="aui-btn aui-btn-primary sendMessage">Send</div>
            </div>
        </footer>
    </body>
    <script data-original="https://cdn.socket.io/socket.io-1.4.5.js"></script>
    <script data-original="https://code.jquery.com/jquery-1.10.2.min.js"></script>
    <script data-original="./js/main.js"></script>
</html>

(4) 使用aui视图框架

github仓库链接
聊天室链接

查看原文

小丑 收藏了文章 · 2019-06-19

Nuxt.js 基础入门教程

原文链接

Vue 开发一个单页面应用,相信很多前端工程师都已经学会了,但是单页面应用有一个致命的缺点,就是 SEO 极不友好。除非,vue 能在服务端渲染(ssr)并直接返回已经渲染好的页面,而并非只是一个单纯的 <div id="app"></div>

Nuxt.js 就是一个极简的 vue 版的 ssr 框架。基于它,我们可以快速开发一个基于 vue 的 ssr 单页面应用。

安装

Nuxt.js 官方提供了一个模板,可以使用 vue-cli 直接安装。

$ vue init nuxt-community/starter-template <project-name>

目录结构

.
├── README.md
├── assets
├── components
├── layouts
├── middleware
├── node_modules
├── nuxt.config.js
├── package.json
├── pages
├── plugins
├── static
├── store
└── yarn.lock

其中:

  1. assets: 资源文件。放置需要经过 webpack 打包处理的资源文件,如 scss,图片,字体等。
  2. components: 组件。这里存放在页面中,可以复用的组件。
  3. layouts: 布局。页面都需要有一个布局,默认为 default。它规定了一个页面如何布局页面。所有页面都会加载在布局页面中的 <nuxt /> 标签中。如果需要在普通页面中使用下级路由,则需要在页面中添加 <nuxt-child />该目录名为Nuxt.js保留的,不可更改。
  4. middleware: 中间件。存放中间件。可以在页面中调用: middleware: 'middlewareName'
  5. pages: 页面。一个 vue 文件即为一个页面。index.vue 为根页面。

    1. 若需要二级页面,则添加文件夹即可。
    2. 如果页面的名称类似于 _id.vue (以 _ 开头),则为动态路由页面,_ 后为匹配的变量(params)。
    3. 若变量是必须的,则在文件夹下建立空文件 index.vue。更多的配置请移步至 官网
  6. plugin: 插件。用于组织那些需要在 根vue.js应用 实例化之前需要运行的 Javascript 插件。需要注意的是,在任何 Vue 组件的生命周期内, 只有 beforeCreatecreated 这两个钩子方法会在 客户端和服务端均被调用。其他钩子方法仅在客户端被调用。
  7. static: 静态文件。放置不需要经过 webpack 打包的静态资源。如一些 js, css 库。
  8. store: 状态管理。具体使用请移步至 官网
  9. nuxt.config.js: nuxt.config.js 文件用于组织Nuxt.js 应用的个性化配置,以便覆盖默认配置。具体配置请移步至 官网

Nuxt 特有函数

首先,了解一下在 nuxt 的页面中独有的函数/变量:

asyncData(context)

asyncData方法使得你能够在渲染组件之前异步获取数据。该方法在服务端中执行的,所以,请求数据时,不存在跨域问题。返回的数据将与 data() 返回的数据进行合并。由于asyncData方法是在组件 初始化 前被调用的,所以在方法内是没有办法通过 this 来引用组件的实例对象。

context 变量的可用属性一览:

属性字段类型可用描述
isClientBoolean客户端 & 服务端是否来自客户端渲染
isServerBoolean客户端 & 服务端是否来自服务端渲染
isDevBoolean客户端 & 服务端是否是开发(dev) 模式,在生产环境的数据缓存中用到
routevue-router 路由客户端 & 服务端vue-router 路由实例。
storevuex 数据流客户端 & 服务端Vuex.Store 实例。只有vuex 数据流存在相关配置时可用。
envObject客户端 & 服务端nuxt.config.js 中配置的环境变量, 见 环境变量 api
paramsObject客户端 & 服务端route.params 的别名
queryObject客户端 & 服务端route.query 的别名
reqhttp.Request服务端Node.js API 的 Request 对象。如果 nuxt 以中间件形式使用的话,这个对象就根据你所使用的框架而定。nuxt generate 不可用
reshttp.Response服务端Node.js API 的 Response 对象。如果 nuxt 以中间件形式使用的话,这个对象就根据你所使用的框架而定。nuxt generate 不可用
redirectFunction客户端 & 服务端用这个方法重定向用户请求到另一个路由。状态码在服务端被使用,默认 302。redirect([status,] path [, query])
errorFunction客户端 & 服务端用这个方法展示错误页:error(params)params 参数应该包含 statusCodemessage 字段。

fetch(context)

fetch 方法用于在渲染页面前填充应用的状态树(store)数据, 与 asyncData 方法类似,不同的是它不会设置组件的数据。为了让获取过程可以异步,你需要返回一个 Promise,Nuxt.js 会等这个 promise 完成后再渲染组件。

fetch 会在组件每次加载前被调用(在服务端或切换至目标路由之前)。

head

Nuxt.js 使用了 vue-meta 更新应用的 头部标签(Head)html 属性

用于更新 头部信息。如 title,descripe 等。head 方法里可通过 this 关键字来获取组件的数据。

layout

指定该页面使用哪个布局文件。默认值为 default

middleware

需要执行的中间件,如鉴权的 auth等。

transition

指定页面切换时的动画效果。支持传入 String, Object, Function。具体配置请移步至 官网

validate

Nuxt.js 可以让你在动态路由对应的页面组件中配置一个校验方法用于校验动态路由参数的有效性。

返回 true 说明路由有效,则进入路由页面。返回不是 true 则显示 404 页面。

Begin Coding

前置工作

API

在这里,我们使用 CNode API 进行开发 Demo.

axios

请求数据,我们使用 Nuxt 官方提供的 @nuxtjs/axios 安装后,在 nuxt.config.js 中加上:

export default {
  ...
  modules: [
    '@nuxtjs/axios'
  ],
  axios: {
    baseURL: 'https://cnodejs.org/api/v1',
    // or other axios configs.
  }
  ...
}

就可以在页面中通过 this.$axios.$get 来获取数据,不需要在每个页面都单独引入 axios.

scss

需要先安装 sass-loader 和 node-sass

$    yarn add sass-loader node-sass --dev

如果需要在项目中全局使用某个 scss 文件(如 mixins, vars 等),需要借助 sass-resources-loader : yarn add sass-resources-loader —dev, 还需要在 nuxt.config.js 的 build 配置中调整导出的 loader 配置:

export default {
  ...
  build: {
    extend(config, { isDev, isClient }) {
      const sassResourcesLoader = {  
        loader: 'sass-resources-loader',  
        options: {  
          resources: [
            // 填写需要全局注入 scss 的文件。引入后,所有页面均有效。
            'assets/styles/mixins.scss'  
          ]
        }  
      }
      // 修改 scss sass 引用的 loader。
      config.module.rules.forEach((rule) => {  
        if (rule.test.toString() === '/\\.vue$/') {  
          rule.options.loaders.sass.push(sassResourcesLoader)  
          rule.options.loaders.scss.push(sassResourcesLoader)  
        }  
        if (['/\\.sass$/', '/\\.scss$/'].indexOf(rule.test.toString()) !== -1) {  
          rule.use.push(sassResourcesLoader)  
        }  
      })  
    }
  }
  ...
}

首页

首页一般只需要简单的获取首页数据并渲染即可。

主要 代码:

asyncData({app, query}) {
  console.log(query)
  // 根据不用的标签获取不同的数据,最后返回话题列表。
  return app.$axios.$get(`topics?tab=${query.tab || ''}`).then(res => {
    // console.log(res)
    // console.log(JSON.parse(res))
    return {list: res.data}
  })
}

当进入首页时,该函数会被执行, nuxt 会等到获取数据后再和组件的 data 合并,进而渲染数据。在模板中,可以直接使用 list 变量获取数据。

<div class="card fluid topic" v-for="topic in list" :key="topic.id" >
  <div class="section">
    <h3><nuxt-link :to="{name: 'topic-id', params: {id: topic.id}}" class="topic-title">{{topic.title}}</nuxt-link></h3>
    <p class="topic-info">
      <mark v-if="topic.top" class="tertiary">精华</mark>
      <mark v-else>{{tabsObj[topic.tab]}}</mark>
      <span class="avatar">
        <img :data-original="topic.author.avatar_url" alt="">
      </span>
      <span class="username">
        {{topic.author.loginname}}
      </span>
    </p>
  </div>
</div>

在这里提及一下, <nuxt-link /><a /> 的区别是: nuxt-link 走的是 vue-router 的路由,即网页已为单页面,并且浏览器不会重定向。而 a 标签走的是 window.location.href,每一次点击 a 标签后的页面,都会进行一次服务端渲染,和普通的 PHP 混合开发没有太大的区别。

在这里使用了 nuxt-link 是因为 CNode 的 API 不存在跨域问题,因此可以作为一个单页面应用,体验更好。

因为列表页数据类型有多种,该页面可能会被复用,所以当路由对象发生变化时,需要重新获取数据,这时可以监听路由的变化以做出响应:

watch: {
  '$route': function() {
    console.log('$route has changed.')
    this.getData()
  }
}

配置 seo 优化(这里只是单纯的复制罢了,demo 使用,侵删):

head() {
  return {
    title: '首页' + (this.$route.query.tab ? `- ${this.tabsObj[this.$route.query.tab]}` : ''),
    meta: [{
      hid: 'description',
      name: 'description',
      content: 'CNode:Node.js专业中文社区'
    }]
  }
}

话题详情

同样的,使用 asyncData 函数进行获取数据,再渲染页面。

asyncData({app, params}) {
  console.log(params)
  return app.$axios.$get('topic/' + params.id).then(res => {
    // let data = res.data instanceof String ? JSON.parse(res.data) : res.data
    let data = res.data
    // console.log(res)
    // let div = document.createElement('div')
    // div.innerHTML = res.data.data.content
    // res.data.summary = div.innerText.substr(0, 120)
    data.summary = data.content.replace(/<[^>]+>/g,"").substr(0, 120).replace(/\s+/g, '')
    return {detail: data}
  }).catch(err => {
    console.log('axios.get failed.')
    console.error(err)
  })
}

在这里,踩过坑。想使用 div 的 innerText 来过滤掉正文中的 HTML 标签,但是,如果用户是直接进入这个页面的时候,执行 asyncData 时,document 对象是不存在的,从而会报错。也就是说,asyncData 在服务端执行时,是没有 documentwindow 对象的,请大家注意一下。

作为一个社区,seo 尤为重要,倘若每个页面都需要写一大堆的 head 对象,就会显得尤其的繁琐。所以可以借助 nuxt 的 plugin 机制,将其封装成一个函数,并注入到每一个页面当中:

// plugins/global.js
import Vue from 'vue'

Vue.mixin({
  methods: {
    // 必传 标题,描述。其他的 meta 标签通过 payload 注入,其中,每个 meta 的 hid 需要是唯一的。
    $seo(title, content, payload = []) {
      return {
        title,
        meta: [{
          hid: 'description',
          name: 'description',
          content
        }].concat(payload)
      }
    }
  }
})

在 nuxt.config.js 中加上:

export default {
  plugins: [
    '~plugins/global.js'
  ]
}

这样,只需要在页面的 head 的函数中,返回该函数即可:

head() {
    return this.$seo(this.detail.title, this.detail.summary)
}

详情页 seo

可见,详情页已经成功的设置了部分 seo 的标签。

以上是 Nuxt 的一些基础配置及应用。

我再去研究一下, fetch 和 store 的结合,将该 demo 继续完善。

Demo 线上地址
GitHub 地址

查看原文

认证与成就

  • 获得 0 次点赞
  • 获得 5 枚徽章 获得 0 枚金徽章, 获得 0 枚银徽章, 获得 5 枚铜徽章

擅长技能
编辑

(゚∀゚ )
暂时没有

开源项目 & 著作
编辑

(゚∀゚ )
暂时没有

注册于 2018-12-15
个人主页被 75 人浏览