用node-koa2-mysql-bootstrap搭建一個前端論壇

前言

在學習了koa2和express並寫了一些demo後,打算本身寫一個項目練練手,因爲是在校生,沒什麼好的項目作,即以開發一個前端論壇爲目標,功能需求參照一下一些社區擬定,主要有:javascript

  • 登陸註冊
  • 我的信息維護、頭像等基本信息
  • 發表文章,富文本編輯器採用wangEditor插件,編輯、刪除文章,文章分類等
  • 文章評論、文章收藏、點贊等
  • 支持文章分頁、評論分頁加載
  • 關注取關用戶
  • 資源(文件)上傳分享、下載、查看
  • 學習資源推薦.....
  • 做者我的日記
  • but。。。。因爲種種緣由,目前僅實現了部分功能,資源分享還沒寫

項目運行效果:




項目技術棧應用:node-koa2-ejs-bootstrap3—jquery,項目預覽地址: http://120.77.211.212/home

 github地址:github.com/Jay214/mybl…,若是以爲對你有幫助或者還看得下去,歡迎star~~鼓勵鼓勵我這前端渣渣輝。css

開發環境

node: v8.3.0html

koa: ^2.4.1
前端

mysql: 5.7.1java

npm: 5.3.0及以上node

如何運行項目

  • 將項目clone至本地 git clone git@github.com:Jay214/myblog-koa2.git
  • 安裝模塊中間件 npm install
  • 安裝mysql
    mysql版本推薦使用5.7如下的,5.7的有個bug,圖形化界面推薦使用navicat for mysql
  • 運行能夠安裝supervisor(npm install supervisor 項目運行工具,開啓後即處於監聽模式,修改文件後保存便可,無需再啓動項目) node index 或npm supervisor index
  • localhost:8080/home 端口號可自行修改

若發現項目有存在什麼bug或有比較好的建議歡迎多多提議,qq:2752402930。mysql

準備工做

因爲koa2是基於es6的promise和es7的await/async語法,因此若是對es6/es7不懂的話請先過一遍文檔,後臺搭建數據庫是關鍵,因此請先安裝好mysql,mysql建議安裝5.7版本如下的,由於5.7.0版本有個bug,須要更改配置文件,具體若大家安裝的時候便知道了。jquery

安裝node環境,使用node -v查看node版本,node須要較新版本可以支持es6的promise和es7的await/async語法,如今node版本都會自帶npm的,因此不須要再去安裝npm。git

項目結構

                      

  • 1.config存放默認文件(數據庫鏈接配置)
  • 2.lib存放數據庫文件
  • 3.middlewares存放判斷登錄註冊與否中間件
  • 4.public存放靜態文件,js,引用bootstrap框架等文件
  • 5.routers存放路由文件
  • 6.views存放模板文件
  • 7.index是程序主文件,定義接口,數據庫接口,引用模塊等
  • 8.package.json項目的配置文件,包括項目名,做者,依賴,模塊等
項目用vscode開發的,用起來很舒服,還沒嘗試過的小夥伴趕忙去試一下吧。

項目初始化:cd myblog1 -> npm init 此時已經建立好了package.json文件了。es6

因爲koa2是輕量級的框架,小巧精悍,因此咱們爲了促進咱們的開發效率和方便性,咱們須要安裝一些koa2的模塊中間件:

npm install i koa koa-bodyparser koa-mysql-session koa-router koa-session-minimal koa-static koa-views md5 moment mysql ejs koa-static-cache --save-dev複製代碼

各模塊用處

  1. koa node框架
  2. koa-bodyparser 表單解析中間件
  3. koa-mysql-sessionkoa-session-minimal 處理數據庫的中間件
  4. koa-router 路由中間件
  5. koa-static 靜態資源加載中間件
  6. ejs 模板引擎
  7. md5 密碼加密
  8. moment 時間中間件
  9. mysql 數據庫
  10. koa-views 模板呈現中間件
  11. koa-static-cache 文件緩存

項目基本框架搭建

配置數據庫鏈接

在config文件夾新建default.js :

const config = {   
 //啓動端口    
port: 8080,
    //數據庫配置   
 database: {      
  DATABASE: 'nodesql',        
  USERNAME: 'root',       
 PASSWORD: '123456',      
  PORT: '3306',        
HOST: 'localhost'  
  }
}
module.exports = config;  複製代碼

而後在lib文件夾新建mysql.js:

var mysql = require('mysql');
var config = require('../config/default.js')
//創建數據庫鏈接池
var pool = mysql.createPool({
    host: config.database.HOST,
    user: config.database.USERNAME,
    password: config.database.PASSWORD,
    database: config.database.DATABASE
});
let query = function(sql, values) {    
return new Promise((resolve, reject)=>{
        pool.getConnection(function (err,connection) {
            if(err){      reject(err);
                }else{                
connection.query(sql,values,(err,rows)=>{ 
                   if(err){ 
                       reject(err);
                    }else{
                        resolve(rows); 
                   }                   
 connection.release(); //爲每個請求都創建一個connection使用完後調用connection.release(); 直接釋放資源。 
                                         //query用來操做數據庫表
                })
            } 
            }) 
   })}複製代碼

這裏創建了一個數據庫鏈接池和封裝了一個操做數據庫表的函數,若是對於數據庫鏈接有不懂的話請自行百度。

創建入口文件

在主目錄新建index.js,即項目入口文件:

const koa = require("koa");   //node框架
const path = require("path");  
const bodyParser = require("koa-bodyparser"); //表單解析中間件
const ejs = require("ejs");   //模板引擎
const session = require("koa-session-minimal");   //處理數據庫的中間件
const MysqlStore = require("koa-mysql-session");  //處理數據庫的中間件
const router = require("koa-router");     //路由中間件
const config = require('./config/default.js');    //引入默認文件
const views = require("koa-views");   //模板呈現中間件
const koaStatic = require("koa-static");  //靜態資源加載中間件
const staticCache = require('koa-static-cache')
const app = new koa();

//session存儲配置,將session存儲至數據庫
const sessionMysqlConfig = {
    user: config.database.USERNAME,
    password: config.database.PASSWORD,
    database: config.database.DATABASE,
    host: config.database.HOST,
}

//配置session中間件
app.use(session({
    key: 'USER_SID',
    store: new MysqlStore(sessionMysqlConfig)
}))

//配置靜態資源加載中間件
app.use(koaStatic(
    path.join(__dirname , './public')
))

//配置服務端模板渲染引擎中間件
app.use(views(path.join(__dirname, './views'),{
    extension: 'ejs'
}))

//使用表單解析中間件
app.use(bodyParser({
    "formLimit":"5mb",
    "jsonLimit":"5mb",
    "textLimit":"5mb"
}));

//使用新建的路由文件
//登陸
app.use(require('./routers/signin.js').routes())
//註冊
app.use(require('./routers/signup.js').routes())
//退出登陸
app.use(require('./routers/signout.js').routes())
//首頁
app.use(require('./routers/home.js').routes())
//我的主頁
app.use(require('./routers/personal').routes())
//文章頁
app.use(require('./routers/articles').routes())
//資源分享
app.use(require('./routers/share').routes())
//我的日記
app.use(require('./routers/selfNote').routes())
//監聽在8080端口
app.listen(8080) 

console.log(`listening on port ${config.port}`)

複製代碼

上面代碼都有註釋,我就不一一說明了,因爲資源分享和我的日記還沒寫,因此暫時統一share...替代。

接下來向mysql.js添加數據庫操做語句,建表、增刪改查。。。

var users = `create table if not exists users(
    id INT(200) NOT NULL AUTO_INCREMENT,
    name VARCHAR(100) NOT NULL,
    pass VARCHAR(40) NOT NULL,
    avator VARCHAR(100) DEFAULT 'default.jpg', 
    job VARCHAR(40),
    company VARCHAR(40),
    introdu VARCHAR(255),
    userhome VARCHAR(100),
    github VARCHAR(100),
    PRIMARY KEY (id)
);`

var posts = `create table if not exists posts(
    id INT(200) NOT NULL AUTO_INCREMENT,
        name VARCHAR(100) NOT NULL,
        title VARCHAR(100) NOT NULL,
        content TEXT NOT NULL,
        uid INT(200) NOT NULL,
        moment VARCHAR(40) NOT NULL,
        comments VARCHAR(255) NOT NULL DEFAULT '0',
        pv VARCHAR(40) NOT NULL DEFAULT '0',
        likes INT(200) NOT NULL DEFAULT '0',
        type VARCHAR(20) NOT NULL,
        avator VARCHAR(100),
        collection INT(200) NOT NULL DEFAULT '0', 
        PRIMARY KEY (id) ,
        FOREIGN KEY (uid) REFERENCES users(id)
        ON DELETE CASCADE

);`

var comment= `create table if not exists comment(
 id INT(200) NOT NULL AUTO_INCREMENT,
 name VARCHAR(100) NOT NULL,
 content TEXT NOT NULL,
 moment VARCHAR(40) NOT NULL,
 postid INT(200) NOT NULL,
 avator VARCHAR(100),
 PRIMARY KEY ( id ),
 FOREIGN KEY (postid) REFERENCES posts(id)
 ON DELETE CASCADE
);`

var likes = `create table if not exists likes(
    id INT(200) NOT NULL AUTO_INCREMENT,
    name VARCHAR(100) NOT NULL,
    postid INT(200) NOT NULL,
    PRIMARY KEY (id),
    FOREIGN KEY (postid) REFERENCES posts(id)
    ON DELETE CASCADE
);`
 var collection = `create table if not exists collection(
     id INT(200) NOT NULL AUTO_INCREMENT,
     uid VARCHAR(100) NOT NULL,
     postid INT(200) NOT NULL,
     PRIMARY KEY (id),
     FOREIGN KEY (postid) REFERENCES posts(id) 
     ON DELETE CASCADE
 );`
 var follow = `create table if not exists follow(
         id INT(200) NOT NULL AUTO_INCREMENT,
         uid INT(200) NOT NULL,  
         fwid INT(200) NOT NULL DEFAULT '0',
         PRIMARY KEY (id),
         FOREIGN KEY (uid) REFERENCES users(id)
         ON DELETE CASCADE
     )
     `

let createTable = function(sql){
    return query(sql, []);
}

//建表
createTable(users);
createTable(posts);
createTable(comment);
createTable(likes);
createTable(collection);
createTable(follow);
//createTable(follower);
//註冊用戶
let insertData = function(value){
    let _sql = "insert into users(name,pass) values(?,?);"
    return query(_sql,value);
}
//更新頭像
let updateUserImg = function(value){
    let _sql = "update users set avator=? where id=?"
    return query(_sql,value);
}
//更新用戶信息
let updateUser = function(value){
    let _sql = "update users set name=?,job=?,company=?,introdu=?,userhome=?,github=? where id=?"
    return query(_sql,value);
}
//發表文章
let insertPost = function(value){
    let _sql = "insert into posts(name,title,content,uid,moment,type,avator) values(?,?,?,?,?,?,?);"
    return query(_sql,value);
}

//更新文章評論數
let updatePostComment = function(value){
    let _sql = "update posts set comments=? where id=?"
    return query(_sql,value);
}
.......複製代碼

總共六張表:用戶表、文章表、文章評論表、文章收藏表、文章點贊表、用戶關注表。

這裏引用了外鍵,可是如今的開發不推薦使用外鍵了,因此大家能夠自行修改,這裏在項目第一次啓動時會出現數據庫建立失敗(因爲外鍵緣由),只要從新啓動就ok了,若是對mysql還不瞭解的,這裏附送你們一個傳送門:mysql入門視頻教程 密碼:c2q7  。

前端頁面開發

項目基本結構搭建好後,就能夠進行前端頁面的編寫了。用node開發web時咱們通常會配合模板引擎,這個項目我採用的是ejs,除了ejs以外較爲經常使用的還有jade,可是jade相對ejs來講的話代碼結構不夠清晰。關於ejs語法,這裏作個簡單的介紹:


代碼複用

前端頁面開發也應該是有清晰的結構的,爲了實現代碼複用,這裏先創建四個公共文件:header 、 footer、 nav、 login:

  • header.ejs

<!DOCTYPE html>
<html lang="en">
    <head>
        <meta charset="utf-8">
        <title>Myblog</title>
        <link rel="stylesheet" href="/css/bootstrap.min.css">
        <link rel="stylesheet" href="/css/index.css">
        <script src="/js/jquery-3.2.1.min.js" type="text/javascript"></script>
        <script src="/js/bootstrap.min.js" type="text/javascript"></script>
      複製代碼

  • nav.ejs

</head>
<body>
    <header class="nav-head">
        <div class="nav container">
            <ul>
                <li><a href="/home">首頁</a></li>
                <li> <a href="/share">資源分享</a></li>
                <li> <a href="/share">推薦</a></li>
                <li> <a href="/share">我的日記</a></li>
                <li><a href="/about">關於做者</a></li>
                <li><input type="text" placeholder="搜索" class="input-sm search"></li>
              
                <% if(session.user){ %>
                    <li>
                        <img src="/images/<%= session.avator %>" alt="" class="img-circle img-title">
                       
                        <ul class="menu">
                            <li class="personal menulist"><a href="/personal/<%= session.user %>">主頁</a></li>
                         <!--   <li class="collection menulist"><a href="#">收藏集</a></li>
                         -->
                            <li class="menulist"><a href="/articles">寫文章</a></li>
                            <li class="out"><a href="/signout">登出</a></li>
                        </ul>
                    </li>
                    <script>
                          var imgTitle = document.getElementsByClassName('img-title')[0],
           
                          menu = document.getElementsByClassName('menu')[0];
                          imgTitle.onclick = function (event) {
                          showTap();
                          event.stopPropagation(); //阻止事件冒泡
                            }
                            
                            document.body.addEventListener('click',function (event) { 
                                menu.style.display = 'none';
                               // event.stopPropagation();
                             },true)
      
                              function showTap(){
                                 if(menu.style.display == 'block'){
                                     menu.style.display = 'none';
                                 }else {
                                     menu.style.display = 'block';
                                 }
                            }
                            //退出登陸
                            var signOut = document.getElementsByClassName('out')[0];
                        /*    signOut.onclick = function(){
                                ajax('get','/signout',null);
                                xhr.onreadystatechange = function () {
                              if(xhr.readyState==4&&xhr.status>=200&&xhr.status<300){
                                 let  text = xhr.responseText;    //服務器返回的對象
                            if(text){
                                window.location.reload = 'localhost:8080/home';
                                 }
                                 
                             }
                         }

                            }*/
                    </script>
                    <% }else{ %>
                        <li class="login">
                                <a class="loginup" href="javascript:;"><span class="glyphicon glyphicon-user"></span> 註冊 | 登陸</a>
                           
                            </li>
                            <% } %>
              
            </ul>
 
         </div>
    </header>
    <script>
      
    
        var searchInput = document.getElementsByClassName('search')[0];
        searchInput.onfocus = function () {
            this.style.width = "300px";
        }
        searchInput.onblur = function () {
            this.style.width = "180px";
        }
        
    </script>

複製代碼

  • login.ejs

<div class="sign">
    <a href="javascript:;" title="關閉" class="login-close close">×</a>
    <div class="sign-title">
        <h1>用戶註冊</h1>
        <h3>來吧騷年們!</h3>
    </div>
    <form class="form signup" role="form">

            <div class="form-group">
                <input type="text" name="username" placeholder="帳號很多於兩個字符" class="form-control">
            </div>
        <div class="form-group">
            <input type="password" name="pass" class="pass form-control" placeholder="密碼">
        </div>
        <div class="form-group">
            <input type="password" name="repeatpass" id="repeat" placeholder="重複密碼" class="form-control">
        </div>
            <div class="form-group">
            <input type="button" value="註冊" class="btn btn-primary login-up">
            </div>

    </form>
    <form class="form signin" role="form">
       <div class="form-group">
           <input type="text" name="username" placeholder="請輸入用戶名" class="form-control">
       </div>
       <div class="form-group">
           <input type="password" name="pass" class="pass form-control" placeholder="請輸入密碼">
       </div>
      <div class="form-group">
          <input type="button" value="登陸" class="btn btn-primary login-in">
      </div>
    </form>
    <div class="form-tips">
        <span>已有帳號?</span>
        <a href="javascript:;" class="register">登陸</a>
    </div>
</div>
<div class="login-form-mask"></div>
<script>
  //  $(document).ready(function () {
        var $close = $('.login-close');
        var $sign = $('.sign');
        $close.click(function () {
            $sign.css("display","none");
        })

        var $register = $('.register'), //login/loginup切換
            $span = $('.form-tips span'),
            $signup = $('.signup'),
            $signTitle = $('.sign-title h1'),
            $signin = $('.signin');

        $register.click(function () {
            if($span.html() == "已有帳號?"){

                $signin.css('display','block');
                $signup.css('display','none');
                $(this).html('註冊');
                $span.html("沒有帳號?");
                $signTitle.html("歡迎登陸");

            }else{

                $signin.css('display','none');
                $signup.css('display','block');
                $(this).html('登陸');
                $span.html("已有帳號?");
                $signTitle.html("歡迎註冊");
            }
        })

        var $loginup = $('.loginup');   //點擊登陸/註冊,阻止事件冒泡
        $loginup.click(function () {
            $mask.fadeIn(100);
            $sign.slideDown(200);
            return false;
        })

        var $close = $('.login-close'),
            $mask = $('.login-form-mask'),
            $sign = $('.sign');

        $sign.click(function () {
            return false;
        })

        $close.click(function (e) {
           // e.stopPropagation();
            fadeOut();

        })

        $(document).click(function (e) { //點擊任意位置取消登陸框
            //e.stopPropagation();
            fadeOut();
        })
        function fadeOut(){
            $mask.fadeOut(100);
            $sign.slideUp(200);
        }
    

    var loginUp = document.getElementsByClassName('login-up')[0],
        loginIn = document.getElementsByClassName('login-in')[0],
        signUp = document.getElementsByClassName('signup')[0],
        signIn = document.getElementsByClassName('signin')[0];

    
    
    loginUp.onclick = function () { //註冊
        var data1 = 'username=' + signUp["username"].value + '&' + 'pass='+ signUp["pass"].value + '&' + 'repeatpass=' + signUp["repeatpass"].value;
        var  reg = /^[\u4E00-\u9FA5]{2,5}$/;
      /*  if(!reg.test(signUp["username"].value)){
            signUp["username"].classList.add("tips");
            signUp['username'].value()
        } */
        ajax('post','/signup',data1,"application/x-www-form-urlencoded");
        xhr.onreadystatechange = function () {
            if(xhr.readyState==4&&xhr.status>=200&&xhr.status<300){
             let  text = JSON.parse(xhr.responseText).code;  
             console.log(text)  //服務器返回的對象
               if(text == 3){
                   fadeOut();
                   alert("註冊成功")
                   setTimeout(()=>{
                    window.location.reload();
                },1000)
               //    document.getElementsByClassName('login')[0].outerHTML = "<li class='users'><a href='/'>"+signUp["username"].value+ "(=^ ^=)" +"</a></li>"
               }else{
                fadeOut();
                alert("用戶已存在")
               }
               
            }
        }

    }

    loginIn.onclick = function () { //登陸
        var data2 = 'username=' + signIn["username"].value + '&' + 'pass=' + signIn["pass"].value;
        ajax('post','/signin',data2,"application/x-www-form-urlencoded");
        xhr.onreadystatechange = function () {
            if(xhr.readyState==4&&xhr.status>=200&&xhr.status<300){
             let  text = JSON.parse(xhr.responseText).code;    //服務器返回的對象
                console.log(text);
                
                 //  document.getElementsByClassName('login')[0].outerHTML = "<li class='users'><a href='/'>"+signUp["username"].value+ "(=^ ^=)" +"</a></li>"
               if(text===1){
                fadeOut();
                // let imgTitle = document.getElementsByClassName('img-title')[0];
                // imgTitle.setAttribute('src','/images/' + JSON.parse(xhr.responseText).avator)
                setTimeout(()=>{
                    window.location.reload();
                },1000)
               }else if(text === 2){
                   alert('密碼錯誤')
               }else{
                   alert('帳號不存在')
               }
               
            }
        }
      
    }
</script>

複製代碼

  • footer.ejs

</body>
</html>複製代碼

header爲頁面頭部結構,nav爲頁面導航條,login爲登陸、註冊內容、footer爲頁面頂部結構。能夠看到我在ejs文件裏有不少的if else 判斷語句,這是根據session來判斷用戶是否登陸渲染不一樣的內容。如今咱們須要咱們的頁面編寫樣式:分別是home.css和index.css

爲了加強對原生js的理解,在項目裏我用了大量的原生ajax(顯然jquery封裝的ajax比較好哈哈),所以這裏先封裝一個原生ajax請求:

  • ajax.js

var xhr = null;
    function ajax(method,url,data,types) {   //封裝一個ajax方法
      //  var text;
        if(window.XMLHttpRequest){
            xhr = new XMLHttpRequest();
        }else if(window.ActiveXObject){
            xhr = new ActiveXObject("Microsoft.XMLHTTP");
        }else {
            alert('你的瀏覽器不支持ajax');
            return false;
        }
      
        xhr.onerror = function (err) {
            alert("some err have hapened:",err);
        }
        xhr.open(method,url,true);
        if(method=="post"){
            xhr.setRequestHeader("Content-type",types);
         // xhr.setRequestHeader("Conent-Type",'application/json'"application/x-www-form-urlencoded")
        }
        try{
            setTimeout(()=>{
                xhr.send(data);
        },0);
        }catch(err) {
            alert("some error have hapened in font:",err);
        }
        return xhr;
    }
複製代碼

實現登陸註冊

前端基本頁面開發好後,咱們就能夠寫後臺登陸接口了:

  • 註冊:signup.js

var router = require('koa-router')();
var userModel = require('../lib/mysql.js');
var md5 = require('md5')

    // 註冊頁面
  
    // post 註冊
router.post('/signup', async(ctx, next) => {
    console.log(ctx.request.body)
    var user = {
        name: ctx.request.body.username,
        pass: ctx.request.body.pass,
        repeatpass: ctx.request.body.repeatpass
    }
    let flag = 0;
    await userModel.findDataByName(user.name)
        .then(result => {
            console.log(result)
            if (result.length) {
               
                    //處理err
                    console.log('用戶已存在')
                    ctx.body = {
                        code: 1
                    };               
                
            } else if (user.pass !== user.repeatpass || user.pass == '') {
                ctx.body = {        //應把這個邏輯放到前端
                    code: 2
                };

            } else {
                flag = 1;             
                
            }
        })
    if(flag==1){
        let res = await  userModel.insertData([user.name, md5(user.pass + 'asd&$BH&*') ])
       console.log(res.insertId)
        await  userModel.findDataByName(user.name)
        .then((result)=>{
            
          //  var res = JSON.parse(JSON.stringify(result))
            console.log(result[0]['avator'])
            ctx.session.id = res.insertId;
            ctx.session.user=user.name;
            ctx.session.avator = 'default.jpg';
            ctx.body = {
            code: 3
            };
            console.log('註冊成功')
         })
    }

})

module.exports = router複製代碼

密碼採用md5加密,註冊後爲用戶建立session並將其添加到數據庫,寫完別忘了在最後加上module.exports = router將接口暴露出來。

登陸:signin.js

var router = require('koa-router')();
var userModel = require('../lib/mysql.js')
var md5 = require('md5')

router.post('/signin', async(ctx, next) => {
    console.log(ctx.request.body)
    var name = ctx.request.body.username;
    var pass = ctx.request.body.pass;

    await userModel.findDataByName(name)
        .then(result => {

            var res = JSON.parse(JSON.stringify(result))

            if (name === res[0]['name']&&(md5(pass + 'asd&$BH&*') === res[0]['pass'])) {
                    console.log('登陸成功')
                    ctx.body = {
                        code: 1,
                    }
    
                    ctx.session.user = res[0]['name']
                    ctx.session.id = res[0]['id']
                    ctx.session.avator = res[0]['avator']   

            }else if(md5(pass + 'asd&$BH&*') != res[0]['pass']){
                ctx.body = {
                    code: 2 //密碼錯誤
                }
            }
        }).catch(err => {
            ctx.body = {
                code: 3 //帳號不存在+
            }
            console.log('用戶名或密碼錯誤!')

        })

})

module.exports = router複製代碼

  • 退出登陸:signout.js

var router = require('koa-router')();
var checkUser = require('../midllewares/checkUser');
router.get('/signout', async(ctx, next) => {
    ctx.session = null;
    console.log('退出成功');
    ctx.body = true;
    ctx.redirect('/home');
    //return;
    //ctx.redirect('/home');
})

module.exports = router
module.exports = router複製代碼

  • 退出登陸後將跳轉到首頁,登陸註冊寫好後須要在咱們的入口文件index.js註冊路由:

//使用新建的路由文件
//登陸
app.use(require('./routers/signin.js').routes())
//註冊
app.use(require('./routers/signup.js').routes())
//退出登陸
app.use(require('./routers/signout.js').routes())複製代碼

登陸註冊完成後下面就是咱們的首頁了,首頁部分咱們先寫後端接口:

  • home.js

var router = require('koa-router')();
var userModel = require('../lib/mysql');
router.get('/home', async(ctx, next)=>{
    let types;
    console.log(ctx.headers['accept'])
//判斷是否帶文章類型參數,若沒有則type=all
    if(!ctx.request.querystring){
        types = 'all';
        await userModel.findPostByPage(1)
        .then(result => {
            //console.log(result)
            post = result
        })
        await userModel.findAllPost()
        .then(result=>{
            postsLength = result.length
        })    
        if(ctx.session.user){
            await userModel.findDataByName(ctx.session.user)
            .then(res=>{
                ctx.session.avator = res[0]['avator'];
            })
        }

    }else{
        //若帶有文章類型參數則根據類型查詢數據庫
         types = ctx.request.querystring.split('=')[1];
        console.log(types)
        let _sql = `select * from posts where type = "${types}" limit 0,10`;
        await userModel.query(_sql)
            .then(result=>{
                post = result;
            })
            _sql = `select * from posts where type = "${types}"`;
            await userModel.query(_sql)
                .then(result=>{
                    postsLength = result.length;
                })              
    }
   
    await ctx.render('home', {
    session: ctx.session,
    articles: post,
    type: types,
    postsLength: postsLength,
    postsPageLength: Math.ceil(postsLength / 10),
    
    })
})
複製代碼

上面數據庫操做採用分頁查詢,首次僅查詢十條數據,以便前端文章列表分頁,咱們向前端傳遞所有文章總數也是爲了分頁的查詢。除此以外咱們還要爲前端分頁查詢再寫一個接口:

// 首頁分頁,每次輸出10條
router.post('/articles/page', async(ctx, next) => {
    let page = ctx.request.body.page,
        type = ctx.request.querystring.split('=')[1];
    console.log(type)
    if(type=='all'){
        await userModel.findPostByPage(page)
        .then(result=>{
            //console.log(result)
            ctx.body = result   
        }).catch(()=>{
        ctx.body = false;
    })  
    }else{
        let _sql = `select * from posts where type = "${type}" limit ${(page-1)*10},10`;
        await userModel.query(_sql)
            .then(result=>{
                ctx.body = result;
            }).catch(err=>{
                console.log(err);
                ctx.body = false;
            })
    }
  
    })
複製代碼

接口寫好後咱們就能夠來寫前端了: 

  • home.ejs

<% include header %>
<% if(!session.user){ %>
<link rel="stylesheet" href="/css/login.css">
<% } %>
<link rel="stylesheet" href="/css/home.css">
<script src="./js/ajax.js"></script>
<% include nav %>

<% if(!session.user){ %>
    <% include login %>
<% } %>

<header class="article-tap padmar">
    <ul class="tip">
        <li class="all"><a href="javascript:;" class="label label-default">所有</a></li>
        <li class="javascript"><a href="javascript:;" class="label label-default">javascript</a></li>
        <li class="html"><a href="javascript:;" class="label label-default">html</a></li>
        <li class="css"><a href="javascript:;" class="label label-default">css</a></li>
        <li class="node"><a href="javascript:;" class="label label-default">node</a></li>
        <li class="other"><a href="javascript:;" class="label label-default">其餘</a></li>
    </ul>
</header>

<article class="article-list">
        <% articles.forEach(function(post){ %>
    <div class="content">
        <a href="/personal/<%= post.name %>" target="_blank" title=" <%= post.name %> " class="post-author">
            <span class="author"><%= post.name %></span>
            <span class="times"><%= post.moment %></span> &nbsp;&nbsp;
            <span class="label label-info"><%= post.type %></span> 
        </a>
        <a href="/articledetail/<%= post.id %>" target="_blank">           
            <h4 class="title"><%= post.title %></h4>
        
        <div class="content-fo">
            <span class="glyphicon glyphicon-heart"></span><span><%= post.likes %></span>
            <span class="glyphicon glyphicon-comment"></span><span><%= post.comments %></span>&nbsp;&nbsp;&nbsp;
            <span class="pv-item">閱讀量&nbsp;<span class="pv"><%= post.pv %></span></span>
        </div>
    </a>
    </div>
        <% }) %>
        <% if(!postsPageLength){ %>
            <div class="nothing">
                    <p><span class="glyphicon glyphicon-list-alt"></span></p>
                    <p>這裏什麼都沒有</p>
            </div>
        <% } %>
</article>
<div style="width:50%;margin-left:25%;margin-top: 30px;text-align:center;" class="pagination" id="page"></div>	
<script src="/js/pagination.js"></script>
<script>
    window.onload = function () { 
        if('<%= type %>'==0){
            $('.tip li').eq(0).find('a').attr('class','label label-info');
        }else{
            $(".<%=type%>").find('a').attr('class','label label-info');
        }
     }
    	pagination({
			selector: '#page',
			totalPage: '<%= postsPageLength %>',
			currentPage: 1,
			prev: '上一頁',
			next: '下一頁',
			first: true,
			last: true,
			showTotalPage: true,
			count: 2//當前頁先後顯示的數量
		},function(val){
			// 當前頁
			$.ajax({
				url: "articles/page?type=<%=type%>",
				type: 'POST',
				data:{
					page: val
				},
				cache: false,
				success: function (msg) {
					console.log(msg)
					if (msg != false) {
						$('.article-list').html(' ')
						$.each(msg,function(i,val){
							console.log(val.name)
							$('.article-list').append(
                                "<div class='content'>" +
                                    '<a href="/personal/' +  val.name + '" title="' +val.name + '" target="_blank" class="post-author">' +
                                       
                                            "<span class='author'>" + val.name + "</span>" + "&nbsp;&nbsp;" +
                                            "<span class='times'>" + val.moment + '</span>' + "&nbsp;&nbsp;" +
                                            '<span class="label label-info">' + val.type + '</span> ' +
                                            "</a>" +
                                                '<a href="/articledetail/' + val.id + '" target="_blank">' +
                                            "<h4 class='title'>" + val.title + "</h4>" +
                                        
                                        "<div class='content-fo'>" +
                                            "<span class='glyphicon glyphicon-heart'></span><span>" + val.likes + "</span>" +
                                            "<span class='glyphicon glyphicon-comment'></span><span>" + val.comments + "</span>&nbsp;&nbsp;&nbsp;" +
                                           ' <span class="pv-item">閱讀量&nbsp;' + "<span class='pv'>" + val.pv + "</span>" + '</span>' +
                                       "</div>" +
                                       "</a>" +
                                    "</div>" 
							)
						})
					}else{
						alert('分頁不存在')
					} 
				}
			})
		
        })
        
        let articleTap = document.getElementsByClassName('article-tap')[0].getElementsByClassName('tip')[0];
        articleTap.onclick = function(e){
          //  let e = e||window.e;
            let target = e.target||e.srcElement;
            // let lis = articleTap.getElementsByTagName('li');
            // console.log(lis)
            if(target.nodeName=='A'){            
                let type = target.parentNode.getAttribute('class');
               if(type==='all'){
                   window.location.href = '/home';
               }else{
                window.location.href ='/home?type=' + type;
               target.setAttribute('class','label label-info');
               }
            }
        }
</script>
</body>
</html>複製代碼

使用<% if(!session.user){ %> <% include login %> <% } %>根據用戶是否登陸加載不一樣文件,文章列表直接for循環渲染輸出,pagination.js是一個封裝好的分頁組件,須要傳遞兩個參數:一個json對象表示分頁組件的內容;一個ajax請求實現分頁加載。

文章發表

下面介紹咱們的文章發表功能,文章發表的富文本編輯器我採用的是wangEditor.min.js,簡單易上手,直接引入壓縮文件便可,可根據本身的需求配置選項,話很少說:

  • articles.ejs

<% include header %>
<link rel="stylesheet" href="/css/articles.css">
<script src="./js/wangEditor.min.js"></script>
<script src="./js/ajax.js"></script>
<% include nav %>
<div class="editor1"></div>
<div id="edit">
    <span class="edit-tips">發表成功</span>
    <span class="num">5字</span>
    <select name="derection" id="derect">
        <option value="0">文章類型</option>
        <option value="javascript">javascript</option>
        <option value="html">html</option>
        <option value="css">css</option>
        <option value="node">node</option>
        <option value="other">其餘</option>
    </select>
    <a href="javascript:;" class="editorUp"><span class="glyphicon glyphicon-share-alt"></span>發佈文章</a>
</div>
<div id="article">
    <input type="text" placeholder="文章標題" name="title" class="article-title"/>

    <div class="editor2"></div>

</div>

<script src="/js/mask.js"></script>
<script>
//配置編輯器
var E = window.wangEditor
var editor = new E('.editor1','.editor2')
// 或者 var editor = new E( document.getElementById('editor') )

 editor.customConfig.uploadImgShowBase64 = true   // 使用 base64 保存圖片
//editor.customConfig.uploadImgServer = '/upload'  // 上傳圖片到服務器
// 將圖片大小限制爲 3M
editor.customConfig.uploadImgMaxSize = 3 * 1024 * 1024;
// 限制一次最多上傳 5 張圖片
editor.customConfig.uploadImgMaxLength = 5;
editor.customConfig.pasteFilterStyle = true;
editor.create();  //建立富文本編輯器
editor.txt.html('<span>編輯文章內容.......</span>');

//統計字數
var num = document.getElementsByClassName('num')[0],
    editType = document.getElementById('derect'),
    editContent = document.getElementsByClassName('editor2')[0];
    editContent.onkeyup = function () {
        num.innerHTML = (editContent.innerHTML.length - 120) + '字';
    }

//ajax上傳文章
var editorUp = document.getElementsByClassName('editorUp')[0],
    editTitle = document.getElementsByClassName('article-title')[0],
    editTips = document.getElementsByClassName('edit-tips')[0];

    editorUp.onclick = function () {
      //  console.log(editContent.innerHTML)
    
        if(editTitle.value&&editContent.innerHTML.length>132){
            console.log(editor.txt.text())
            if(editType.value==0){
          fadeout('請選擇文章類型');
            return false;
            }
        let data = { "title": editTitle.value,"content":editor.txt.html(),"type":editType.value };
       let data2 =  JSON.stringify(data);
        
        console.log(data2);
        ajax('post','/articles',data2,'application/json');
        xhr.onreadystatechange = function () {
            if(xhr.readyState==4&&xhr.status>=200&&xhr.status<300){
                let  text = xhr.responseText;    //服務器返回的對象
                console.log(text)
                if(text==1){
                    mask('發表成功');
                   setTimeout(()=>{
                    window.location.href = '/home';
                   },1500);
                
                }else{
                    fadeout('發表失敗');
              
                }
            }
        }
        }else{
            fadeout('文章內容和標題不能爲空');
            // editTips.innerHTML = '文章內容和標題不能爲空';
            // editTips.style.animation = 'fadeout 2s';
        }
     
    }

    
    function fadeout(text){
        editTips.innerHTML = text;
        editTips.style.opacity = 1;
        setTimeout(()=>{
            editTips.style.opacity = 0;
        },1000)
    }

</script>
<% include footer %>複製代碼

若插入圖片則使用 base64 保存圖片,由於文章內容(包括標籤)是直接以字符串形式存入數據庫的,注意上面我引入了一個mask.js文件,這個文件封裝了一個mask函數用於彈出提示信息,因爲沒有用jquery因此採用的是觸發的時候建立元素,setTimeout()定時器延遲刪除,這樣能夠避免頁面上有沒必要要的標籤存在。有點相似於單例模式。文章發表後臺很簡單我就不解釋了,如今發表文章的話就能夠在咱們的首頁看到內容了。

  • mask.js

function mask(text) {
    
    let div1 = document.createElement('div');
    div1.setAttribute('class','login-form-mask');
    let div2 = document.createElement('div');
    div2.setAttribute('class','tip-box');
    div2.innerHTML = text;
    document.body.appendChild(div1);
    document.body.appendChild(div2);
    setTimeout(()=>{
        let dd = document.getElementsByClassName('.login-form-mask')[0];
        console.log(dd);
        document.body.removeChild(document.getElementsByClassName('login-form-mask')[0]);
        document.body.removeChild(document.getElementsByClassName('tip-box')[0]);
    },1500)
 }
複製代碼

文章雖然發表了,可是咱們尚未寫評論和收藏等功能,下面開始入手,首先要先寫咱們的文章詳情頁:

  • articledetail.ejs

<% include header %>
<link rel="stylesheet" href="/css/login.css">
<link rel="stylesheet" href="/css/articledetail.css">
<script src="/js/ajax.js"></script>
<% include nav %>
<% if(!session.user){ %>
    <% include login %>
<% } %>
<div class="article-list padmar">
    <div class="post-head">
        <span class="post-type label label-info">
            <%= article.type %>
        </span>
        <a href="/personal/<%= article.name %>" class="author">
            <img src="/images/<%= article.avator %>" alt="" class="img-circle">
            <h4><%= article.name %></h4>
            <span><%= article.moment %></span>
        </a>
        <% if(session.user){ %>
            <% if(session.user === article.name){ %>
        <a href="/" class="editself">編輯</a>
        <a href="/delete?id=<%= article.id %>" class="delete"> 刪除</a>
        <% } %>
        <% if(session.user != article.name){ %>
        <span class="follow btn btn-default">關注做者</span>
        <% } %>
        <% } %>

    </div>
    <aside>
        <ul class="comment-taps">
            <li class="list"><a href="javascript:;" title="點贊"><span class="glyphicon glyphicon-heart-empty"></span></a></li>
            <li style="text-align: center;color: gray;" class="likes"><%= article.likes %></li>
            <li class="list"><a href="#comment" title="評論"><span class="glyphicon glyphicon-comment"></span></a></li>
            <li class="list"><a href="javascript:;" title="收藏"><span class="glyphicon glyphicon-tasks"></span></a></li>
        </ul>
    </aside>
    <h1 class="article-title"><%= article.title %><span class="article-pv glyphicon glyphicon-folder-open">&nbsp;<%= article.pv %></span></h1>
    <section class="content-section">
            <%- article.content %>
    </section>

</div>

<div class="comment padmar">
        <% if(session.user){ %>   
    <div class="create-comment">
         <form >
             <textarea name="comment" id="comment" cols="30" rows="5"></textarea>
             <input type="button" class="btn btn-success" value="發表評論">
         </form>
     </div>
     <% } %>
     <% if(commentPageLenght) { %>
     <div class="comment-list">
        <% commentPage.forEach(function(val){ %>
       <div class="comment-item">
         <h4><a href="/personal/<%= val['name'] %>"><img src="/images/<%= val['avator'] %>" alt="<%= val['name'] %>"><%= val['name'] %></a></h4>
         <span><%= val['moment'] %></span>
         <div class="comment-content">
                 <%= val['content'] %>
                 <% if(session.user == val['name']){ %>
                    <a href="javascript:deleteComment('/deleteComment/<%= article.id %>/<%= val['id'] %>',document.getElementsByClassName('deleteComment')[0])" class="deleteComment">刪除</a>
                    <% } %>
         </div>
       </div>
         <% }) %>
        
     </div>
     <div style="display:block;width:50%;margin:auto;margin-top: 30px;" class="pagination" id="page"></div>
     <% }else{ %>
        <p style="text-align:center;line-height:80px;fon-size:30px;color:gray">暫無任何評論</p>       
        <% } %>
            
 </div>
 
<script src="/js/pagination.js"></script>
<script src="/js/mask.js"></script>
<script>
   $(document).ready(function(){
        
        var userName = "<%- session.user %>"
     
		pagination({
			selector: '#page',
			totalPage: '<%= commentPageLenght %>',
			currentPage: 1,
			prev: '上一頁',
			next: '下一頁',
			first: true,
			last: true,
			showTotalPage:true,
			count: 2//當前頁前面顯示的數量
		},function(val){
			// 當前頁
			var _comment = ''
			$.ajax({
				url: "/article/<%= article.id %>/commentPage",
				type: 'POST',
				data:{
					page: val
				},
				cache: false,
				success: function (msg) {
					console.log(msg)
					_comment = ''
					if (msg != 'error') {
						$('.comment-list').html(' ')
						$.each(msg,function(i,val){
                
                            _comment +=
                            " <div class='comment-item'>" + 
                            "<h4><a href='/personal/" + val.name + "'>" + "<img src='images/" + val.avator + "' alt=" + val.name + "'>" + val.name + "</a></h4>" +
                            "<span>" + val.moment + "</span>" + 
                            "<div class='comment-content'>" ;
                                if(val.name == userName){
                                    _comment +=  val.content + "<a href='javascript:deleteComment('/deleteComment/<%= article.id%>/'" +  val.id +",this)' class='deleteComment'>" + "刪除</a>" +
                            "</div>" +
                            "</div>"
                                }else{
                                    _comment +=  val.content + 
                            "</div>" +
                            "</div>"
                                }
                                
                        })
                        console.log( _comment)
						$('.comment-list').append(_comment)
					}else{
						alert('分頁不存在')
					} 
				}
			})
		
		})
    })
    //點贊
    var postTaps = document.getElementsByClassName('comment-taps')[0];
    var addHeart = postTaps.getElementsByTagName('li')[0],
        gly = addHeart.getElementsByClassName('glyphicon')[0],
        likes = postTaps.getElementsByClassName('likes')[0];

    var collects = postTaps.getElementsByTagName('li')[3];
    
    '<% if(session.user){ %>'
        window.onload = function(){
            console.log('<%- session.id%>') 
            '<% if(session.user != article.name){ %>'
            var follow = document.getElementsByClassName('follow')[0];
            if('<%= follow %>'=='<%= session.id%>'){    //已關注
                follow.innerHTML = '已關注'
            }
            follow.onclick = function(){
                if(follow.innerHTML==='關注做者'){
                    ajax('get','/follow/<%= article.uid %>?flag=1',null);
                    xhr.onreadystatechange = function () {
                 if(xhr.readyState==4&&xhr.status>=200&&xhr.status<300){
                    console.log(xhr.responseText)
                     if(xhr.responseText){
                         follow.innerHTML = '已關注';
                         console.log('ok')
                     }
                 }
             }
                }else{
                    ajax('get','/follow/<%= article.uid %>?flag=2',null);
                    xhr.onreadystatechange = function () {
                 if(xhr.readyState==4&&xhr.status>=200&&xhr.status<300){
                     console.log(xhr.responseText)
                     if(xhr.responseText){
                         follow.innerHTML = '關注做者'
                         console.log('ok2')
                     }
                 }
             }
                }
            }
            '<% } %>'
            console.log('<%= likes %>') 
            if('<%= likes %>'==='<%= session.user %>'){ //已點贊
                gly.setAttribute('class','glyphicon '+'glyphicon-heart');
            }
        }

    addHeart.onclick = function () {
         if(gly.getAttribute('class').indexOf('glyphicon-heart-empty')>-1){
             ajax('get','/addHeart/<%= article.id %>?flag=1',null);
             xhr.onreadystatechange = function () {
                 if(xhr.readyState==4&&xhr.status>=200&&xhr.status<300){
                     if(xhr.responseText){
                         gly.setAttribute('class','glyphicon '+'glyphicon-heart');
                         likes.innerHTML = parseInt(likes.innerHTML) + 1;
                     }
                 }
             }
         }else{ //取消贊
             ajax('get','/addHeart/<%= article.id %>?flag=2',null);
             console.log('<%= likes %>')
             xhr.onreadystatechange = function () {
                 if(xhr.readyState==4&&xhr.status>=200&&xhr.status<300){
                     if(xhr.responseText){
                         gly.setAttribute('class','glyphicon '+'glyphicon-heart-empty');
                         likes.innerHTML = parseInt(likes.innerHTML) - 1; 
                     }
                 }
             }

         }
    }

     //收藏
     //取消收藏或收藏
    let cotitle = collects.getElementsByTagName('a')[0];
    if('<%= collects %>'=='<%= session.user %>'){
                cotitle.setAttribute('title','取消收藏');
    }
        collects.onclick = function () {
            if(cotitle.getAttribute('title')=='收藏'){
              // cotitle.setAttribute('title','收藏');
        ajax('get','/collects/<%= article.id %>?flag=1',null);
        xhr.onreadystatechange = function () {
            if(xhr.readyState==4&&xhr.status>=200&&xhr.status<300){
                if(xhr.responseText){
                   alert('收藏成功');
                   cotitle.setAttribute('title','取消收藏');
                }
            }
        }
    }else{
        ajax('get','/collects/<%= article.id %>?flag=2',null);
        xhr.onreadystatechange = function(){
            if(xhr.readyState==4&&xhr.status>=200&&xhr.status<300){
                if(xhr.responseText){
                    alert('取消成功');
                    cotitle.setAttribute('title','收藏');
                }
            }
        }
    }
     }

     //評論
     let comment = document.getElementsByClassName('create-comment')[0].getElementsByClassName('btn')[0];
     let Allow = true;
        comment.onclick = function(){
            let comments = document.getElementById('comment');
            if(comments.value==''){
                comments.value = '請輸入評論內容!';
                setTimeout(()=>{
                    comments.value = '';
                },500)
                return 0;
            }
            if(!Allow) return 0;
            Allow = false;
            // + '&articleId=' + '<%= article.id %>'
            ajax('post','/comment/<%= article.id %>',"comments=" + comments.value,'application/x-www-form-urlencoded');
            xhr.onreadystatechange = function(){
                if(xhr.readyState==4&xhr.status>=200&&xhr.status<300){
                    if(xhr.responseText){
                        mask('評論成功!');
                        setTimeout(()=>{
                            window.location.reload();
                        },1500)
                        Allow = true;
                    }
                }
            }

        }

        //刪除評論
        function deleteComment(url,item){
            ajax('get',url,null);
            console.log(item)
            xhr.onreadystatechange = function(){
                if(xhr.readyState==4&xhr.status>=200&&xhr.status<300){
                    if(xhr.responseText){
                        console.log('刪除評論成功');
                        mask('刪除成功!');
                        console.log(item)
                        console.log(item.parentNode)
                        item.parentNode.parentNode.outerHTML = '';
                    }
                }
            }
        }
     
    '<% }else{ %>'
    addHeart.onclick = function(){
        alert("請先登陸")
    }
    collects.onclick = function(){
        alert("請先登陸")
    }
    '<% } %>'
   
</script>
</body>
</html>複製代碼

文章詳情頁算是最複雜的一個頁面了,涉及到了訪問量,評論,點贊、收藏、關注用戶等等功能,因此能夠看到上面代碼很長很長,優秀的小夥伴能夠幫我整理整理這個模塊啊哈哈。

雖然看起來內容不少,可是慢慢分析的話仍是不難的,一句話,有登陸就給權限,沒登陸啥都不給,根據session來判斷用戶是否登陸以及是不是文章做者。還有根據後臺傳過來的信息判斷用戶是否已點贊或收藏文章:if('<%= follow %>'=='<%= session.id%>')   if('<%= likes %>'==='<%= session.user %>'){ //已點贊。

這裏較好的邏輯應該是:在用戶點擊收藏或點贊按鈕時請求後臺判斷用戶是否已點贊或收藏,這樣的話加載頁面時後臺就不用一會兒處理那麼多邏輯了。然而爲了方便,個人邏輯是在請求頁面時將所有邏輯信息查出來一次性傳給前端了。

這裏的評論也採用了分頁的形式,循環渲染評論列表時判斷本條評論是否是該用戶發表:

<% if(session.user == val['name']){ %>
                    <a href="javascript:deleteComment('/deleteComment/<%= article.id %>/<%= val['id'] %>',document.getElementsByClassName('deleteComment')[0])" class="deleteComment">刪除</a>
                    <% } %>
複製代碼

再看看咱們的後臺代碼:

/ 單篇文章頁
router.get('/articledetail/:postId', async(ctx, next) => {
    let comments,
        article,
        pageOne,
        article_pv,
        collects,
        follow,
        likes; 
    let postId = ctx.params.postId;
    console.log(postId,'potid')
    await userModel.findPostById(postId) 
        .then(result => {
            
            article = result;
    
            article_pv = parseInt(result[0]['pv'])
            article_pv += 1
       
        }).catch(err=>{
            console.log(err);
            ctx.body = false;
        })

    await userModel.updatePostPv([article_pv, postId])
    await userModel.findCommentByPage(1,postId)
        .then(result => {
            commentPage = result
        }).catch(err=>{
            console.log(err);
            ctx.body = false;
        })
    await userModel.findCommentById(postId)
        .then(result => {
            comments = result
            console.log('comment', Math.ceil(comments.length/10))
        }).catch(err=>{
            console.log(err);
            ctx.body = false;
        })
    
    if(ctx.session.user!=postId){
        await userModel.findFollowByUserId([ctx.session.id,article[0]['uid']])
        .then(result=>{
           // console.log(result[0])
            if(result[0]!=undefined){
               // console.log(result[0])
                follow = result[0]['uid'];
            }else{
                follow = null;
            }
        }).catch(err=>{
            console.log(err)
        })
    }
    await userModel.findLikeByPostId([ctx.session.user,article[0]['id']])
        .then((result)=>{
            if(result[0]!=undefined){
               // console.log(result[0])//未解決
                likes = result[0]['name']
            }else{
                likes = null;
            }
        })

    await userModel.findCollectionByData([ctx.session.id,article[0]['id']])
        .then((result)=>{
            if(result[0]!=undefined){
               // console.log(result[0])
                collects = result[0]['name']
            //    console.log(collects)
            }else{
                collects = null;
            }
        })
    await ctx.render('articledetail', {
        session: ctx.session,
        article: article[0],
        likes: likes,
        collects :collects,
        follow,
        commentLenght: comments.length,
        commentPageLenght: Math.ceil(comments.length/10),
        commentPage:commentPage
    })

})
複製代碼

首先是用戶進入該頁面,文章訪問量加一,而後根據文章id查詢文章的評論表,接着就是查詢用戶關注表、點贊表、收藏表了,最後將全部查詢的結果經過渲染ejs模板傳給前端。

下面是咱們文章詳情頁各個功能的後臺代碼:

//刪除文章
router.get('/delete',async(ctx,next)=>{
    let articleId = ctx.query.id;
    console.log(articleId)
    await userModel.deletePost(articleId)
        .then(()=>{
            ctx.redirect('/home');
            console.log('刪除成功')
        }).catch(err=>{
            console.log(err)
            ctx.body = false;
        })
})

    //關注
router.get('/follow/:articleAuthor',async(ctx,next)=>{
    console.log(ctx.params.articleAuthor)
    let flag = ctx.query.flag,
        fwid = ctx.params.articleAuthor;
      
    if(flag==1){
        await userModel.insertFollow([ctx.session.id,fwid])
            .then(()=>{
                console.log('關注成功');
                ctx.body = true;
            }).catch(err=>{
                console.log(err);
                ctx.body = false;
            })
     
    }else{
        await userModel.deleteFollow([ctx.session.id,fwid])
            .then(()=>{
                console.log('取消成功')
                ctx.body = 1;
            }).catch(err=>{ 
                console.log(err);
                ctx.body = 0;
            })
    }
})
    //點贊
router.get('/addHeart/:articleId',async(ctx,next)=>{
    console.log(ctx.query.flag)
    let flag = ctx.query.flag,
        likes,
        likeId,
        articleId = ctx.params.articleId;
        await userModel.findPostById(ctx.params.articleId)
        .then((result)=>{
            likes = parseInt(result[0]['likes']);
        })
    if(flag==1){
        likes += 1;
        await userModel.insertLikes([ctx.session.user,articleId])
            .then(()=>{
                console.log('點贊OK');
            }).catch((err)=>{
                console.log(err);
            })

      await userModel.updatePostLike([likes,articleId])
        .then(()=>{
            ctx.body = true;
            console.log('點同意功')
        }).catch((err)=>{
            console.log(err)
            ctx.body = false;
        })

    }else if(flag==2){  //取消贊
        await userModel.findLikeByPostId([ctx.session.user,articleId])
            .then((result)=>{
                 likeId = result[0]['id'];
            }).catch(err=>{
                console.log(err);    
            });
        await userModel.poseLikes(likeId)
            .then(()=>{
                console.log('取消同意功');
            }).catch((err)=>{
                console.log(err);   
            })
             
        likes -= 1;
        await userModel.updatePostLike([likes,articleId])
            .then(()=>{
                ctx.body = true;
                console.log('取消讚了')
            }).catch((err)=>{
                console.log(err)
                ctx.body = false;
            })
    }
})
 //收藏文章、取消收藏
 router.get('/collects/:articleId',async(ctx,next)=>{
        let flag = ctx.query.flag,
            articleId = ctx.params.articleId,
            collects,
            collectId;
            await userModel.findPostById(articleId)
            .then((result)=>{
                collects = result[0]['collection'];
            }).catch(err=>{
                console.log(err)
            })
            if(flag==1){
                await userModel.insertCollection([ctx.session.id,articleId])
                    .then(()=>{
                        console.log('收藏成功1')
                    }).catch((err)=>{
                        console.log(err)
                    })
                
                    collects++;
                await userModel.updatePostCollection([collects,articleId])
                    .then(()=>{
                        console.log('收藏成功')
                        ctx.body = true;
                    }).catch(err=>{
                        console.log(err)
                        ctx.body = false;
                    })
            }else{
                await userModel.findCollectionByNaId([ctx.session.id,articleId])
                    .then(result=>{
                        collectId = result[0]['id'];
                    }).catch(err=>{
                        console.log(err)
                    })
                
                await userModel.deleteCollection(collectId)
                    .then(()=>{
                        console.log('取消成功2')
                    }).catch(err=>{
                        console.log(err)
                    })
                    
                    collects--;
                await userModel.updatePostCollection([collects,articleId])
                    .then(()=>{
                        console.log('取消成功3')
                        ctx.body = true;
                    }).catch(err=>{
                        console.log(err);
                    })
            }
})

//評論
router.post('/comment/:articleId', async(ctx,next)=>{
    console.log('test')
    console.log(ctx.request.body.comments)
       let articleId = ctx.params.articleId,
           content = ctx.request.body.comments,
           name = ctx.session.user,
           avator = ctx.session.avator;
         //  moment = moment().format('YYYY-MM-DD HH:mm');
       let comments = 0;
           await userModel.insertComment([name,content,moment().format('YYYY-MM-DD HH:mm'),articleId,avator])
               .then(result=>{
                   console.log(result[0]);
               }).catch(err=>{
                   console.log(err);
               });
           await userModel.findPostById(articleId)
               .then(result=>{
                  // console.log(result[0]);
                  console.log(result[0]['comments'])
                   comments = parseInt(result[0]['comments']) + 1;

               }).catch(err=>{
                   console.log(err);
               });
           await userModel.updatePostComment([comments,articleId])
               .then(result=>{
                   console.log(result);
                   ctx.body = true;
               }).catch(err=>{
                   console.log(err);
                   ctx.body = false;
               });
})

//評論分頁
 router.post('/article/:articleId/commentPage', async(ctx,next)=>{
     let articleId = parseInt(ctx.params.articleId),
        page = parseInt(ctx.request.body.page);
        console.log(articleId,page)
        await userModel.findCommentByPage(page,articleId)
            .then(result=>{
                ctx.body = result;
                console.log(result);
            }).catch(err=>{
                ctx.body = 'error';
                console.log(err);
            })
 })

 //刪除評論
 router.get('/deleteComment/:articleId/:commentId', async(ctx,next)=>{
     let commentId = ctx.params.commentId;
     let articleId = ctx.params.articleId,
        comment = 0;
     await userModel.deleteComment(commentId)
        .then(result=>{
            console.log(result);//需更新文章評論數
            
        }).catch(err=>{
            console.log(err);
            ctx.body = false;
        })
     await userModel.findPostById(articleId)
        .then(result=>{
            console.log(result[0]['comments']);
            comment = parseInt(result[0]['comments']) -1;
        }).catch(err=>{
            console.log(err);
        })
     await userModel.updatePostComment([comment,articleId])
        .then(result=>{
            console.log(result);
            ctx.body = true;
        }).catch(err=>{
            console.log(err);
            ctx.body = false;
        })
 })
複製代碼



因爲學業繁忙,後續內容持續更新。。。

相關文章
相關標籤/搜索