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

JayJunG發表於2018-05-02

前言

在學習了koa2和express並寫了一些demo後,打算自己寫一個專案練練手,由於是在校生,沒什麼好的專案做,即以開發一個前端論壇為目標,功能需求參照一下一些社群擬定,主要有:

  • 登入註冊
  • 個人資訊維護、頭像等基本資訊
  • 發表文章,富文字編輯器採用wangEditor外掛,編輯、刪除文章,文章分類等
  • 文章評論、文章收藏、點贊等
  • 支援文章分頁、評論分頁載入
  • 關注取關使用者
  • 資源(檔案)上傳分享、下載、檢視
  • 學習資源推薦.....
  • 作者個人日記
  • but。。。。由於種種原因,目前僅實現了部分功能,資源分享還沒寫

專案執行效果:

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

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

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

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

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

開發環境

node: v8.3.0

koa: ^2.4.1

mysql: 5.7.1

npm: 5.3.0及以上

如何執行專案

  • 將專案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。

準備工作

由於koa2是基於es6的promise和es7的await/async語法,所以如果對es6/es7不懂的話請先過一遍文件,後臺搭建資料庫是關鍵,所以請先安裝好mysql,mysql建議安裝5.7版本以下的,因為5.7.0版本有個bug,需要更改配置檔案,具體若你們安裝的時候便知道了。

安裝node環境,使用node -v檢視node版本,node需要較新版本能夠支援es6的promise和es7的await/async語法,現在node版本都會自帶npm的,所以不需要再去安裝npm。

專案結構

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

  • 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檔案了。

由於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語法,這裡做個簡單的介紹:

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

程式碼複用

前端頁面開發也應該是有清晰的結構的,為了實現程式碼複用,這裡先建立四個公共檔案: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;
        })
 })
複製程式碼



由於學業繁忙,後續內容持續更新。。。

相關文章