前端小項目之在線便利貼

實現的效果以下:

界面可能不是太好看?,考慮到容器的高度會被拉長,所以沒有用圖片作背景。css

預覽

便利貼html

涉及的知識點

  • sass(css 預編譯器)
  • webpack(自動化構建工具,實現LESS,CSS,JS編譯和壓縮代碼)
  • express (基於 Node.js 平臺的 web 開發框架)
  • html+css
  • Node.js(基於 Chrome V8 引擎的 JavaScript 運行環境)
  • jQuery(一個快速、簡潔的JavaScript框架)
  • sequelize(Node的ORM框架Sequelize操做數據庫)
  • passport(實現第三方登陸)

實現功能

  • github第三方登陸
  • 添加筆記(登陸成功後)
  • 刪除筆記
  • 修改筆記
  • 使用 markdown(相似 typroa)
  • 筆記拖拽

準備工做

  • 必要條件:已經安裝好了node環境,還沒安裝的能夠去node中文官網下載
  • 小提示:若是用 npm 下載感受慢的話,能夠下載一個切換鏡像源的工具nrm,在終端輸入:
npm i nrm -g

而後以下操做:前端

開始!!node

1.新建一個文件夾,名字本身起,打開終端,切換到本身新建文件夾,如jquery

cd (文件夾名稱)

2.生成 package.jsonwebpack

npm init -y

3.安裝 expressgit

npm i express --save

4.安裝 express生成器:github

npm install express-generator --save

5.生成 ejs 模板(相似 jsp 的寫法)web

express -f -e
npm i

其中public用來存放編譯後的js文件以及編譯好的css文件等,routes用來存放處理 ajax 的請求文件,views就是存放視圖文件
而後新建 database 和 src:ajax

其中 src/js 裏面 app 表明不一樣頁面的入口文件,lib 就是一些經常使用的庫,mod 就是你寫的一些模塊,database 用來存放數據庫數據的

6.輸入:

npm start

若是有出現下面的錯誤:

出現這個錯誤是由於你沒有下載模塊,只需在終端輸入:

npm i (模塊名) --save

就能夠了

7.打開瀏覽器,輸入localhost:3000
出現下面這樣就說明成功了:

8.接下來安裝webpack和相關依賴

npm i webpack --save-dev
npm i --save css-loader style-loader express-session express-flash node-sass passport sass sass-loader sequelize sqlite3 extract-text-webpack-plugin onchange

9.在 src 裏建一個 webpack.config.js,配置以下

var webpack = require('webpack');
var path = require('path');
var ExtractTextPlugin = require('extract-text-webpack-plugin')
var autoprefixer = require('autoprefixer');
    
module.exports = {
    entry: path.join(__dirname, "js/app/index"),
    output: {
        path: path.join(__dirname, "../public"),
        filename: "js/index.js"
    },
    module: {
        rules: [{
            test: /(\.scss)$/,
            use: ExtractTextPlugin.extract({
                fallback: "style-loader",
                use: ["css-loader", "sass-loader"]
            }) //把 css 抽離出來生成一個文件
        }]
    },
    resolve: {
        alias: {
            jquery: path.join(__dirname, "js/lib/jquery-2.0.3.min.js"),
            mod: path.join(__dirname, "js/mod"),
            sass: path.join(__dirname, "sass")
        }
    },
    plugins: [
        new webpack.ProvidePlugin({
            $: "jquery"
        }),
        new ExtractTextPlugin("css/index.css"),
        new webpack.LoaderOptionsPlugin({
            options: {
                css: [
                    autoprefixer(),
                ]
            }
        }),
        new webpack.optimize.UglifyJsPlugin({
            compress: {
                warnings: false
            }
        })
    ]
}

說明

  • entry:入口文件,也就是 src/js/app裏面的index.js,其中__dirname是得到當前文件所在目錄的完整目錄名
  • output:輸出編譯後的文件 index.js,輸出到 public/js 裏面
  • module:配置Loaders,經過使用不一樣的loader,webpack有能力調用外部的腳本或工具,實現對不一樣格式的文件的處理,好比說分析轉換scss爲css,或者把下一代的JS文件
  • resolve.alias:設置模塊別名,便於咱們更方便引用,好比說我在 js裏面的文件須要 jquery,在裏面的文件直接寫 require("jquery") 就好了
  • 若是全部文件都須要 jquery,那麼直接在 plugins裏面寫成這樣:

就不須要 require 了


這個是壓縮文件的

10.在 package.json 中,增長以下兩條:

寫成這樣,你在終端就能夠寫成npm run webpack 來編譯文件,
npm run watch來監控 src 裏面的 js 和 scss 的變化,只要一修改,進行編譯,提升了效率

11.測試

你能夠試試在 js 裏面的 index.js寫點東西,而後 npm run webpack,若是終端顯示是這樣:

就證實成功了

項目思路

邏輯比較簡單

  1. 首先用戶必須登陸才能添加筆記,當用戶失焦的時候,將數據插入數據庫,而且從新佈局(瀑布流)
  2. 用戶不能更改其餘用戶的筆記,除了管理員?
  3. 用戶更新筆記以後,數據庫的數據從新更新,從新佈局
  4. 用戶能夠刪除筆記,數據從數據庫中刪除,從新佈局
  5. 用戶能夠拖拽筆記,但不將位置存入數據庫

實現

html,css就不講了,能夠看看個人源碼,主要講 js。

1.瀑布流的實現

思路:(前提是必須絕對定位)

  1. 獲取元素的寬度
  2. 經過窗口的寬度除以元素的寬度來獲取列數
  3. 初始化一個數組來獲取每列的高度,初始化每列的高度爲0
  4. 遍歷元素,獲取最小的的列數的高度和索引,對當前元素進行定位,列數高度加等於當前元素的高度

知道思路後,代碼很快就寫出來了:

var WaterFall = (function () {
        var $ct, $items;
        function render($c) {
            $ct = $c;
            $items = $ct.children();
            var nodeWidth = $items.outerWidth(true),
                windowHeight = $(window).height(),
                colNum = parseInt($(window).width() / nodeWidth), //獲取列數
                colSumHeight = []; //獲取每列的高度
            //對每列的高度進行初始化
            for (var i = 0; i < colNum; i++) {
                colSumHeight[i] = 0;
            }
            $items.each(function () {
                var $current = $(this);
                var index = 0,
                    minSumHeight = colSumHeight[0];
                //獲取最小的的列數的高度和索引
                for (var i = 0; i < colSumHeight.length; i++) {
                    if (minSumHeight > colSumHeight[i]) {
                        index = i;
                        minSumHeight = colSumHeight[i];
                    }
                }            
                //改變窗口高度
                if (windowHeight < minSumHeight) {
                    $("body").height(minSumHeight);
                } else {
                    $("body").height(windowHeight - 72);
                }
                //對當前元素進行定位
                $current.animate({
                    left: nodeWidth * index,
                    top: minSumHeight
                }, 5);
                colSumHeight[index] += $current.outerHeight(true);
    
            });
        }
        //當窗口發生變化時,從新渲染
        $(window).on('resize', function () {
            render($ct);
        });
        return {
            init: render
        }
    })();

2.筆記的拖拽

咱們先看個圖

所以代碼以下:

//設置筆記的移動
            $noteHead.on('mousedown', function (e) {
                var evtX = e.pageX - $note.offset().left, //evtX 計算事件的觸發點在 dialog內部到 dialog 的左邊緣的距離
                    evtY = e.pageY - $note.offset().top;
                $note.addClass('draggable').data('evtPos', {
                    x: evtX,
                    y: evtY
                }); //把事件到 dialog 邊緣的距離保存下來
            }).on('mouseup', function () {
                $note.removeClass('draggable').removeData('pos');
            });
    
            $('body').on('mousemove', function (e) {
                $('.draggable').length && $('.draggable').offset({
                    top: e.pageY - $('.draggable').data('evtPos').y, // 當用戶鼠標移動時,根據鼠標的位置和前面保存的距離,計算 dialog 的絕對位置
                    left: e.pageX - $('.draggable').data('evtPos').x
                });
            });
        },

3.提示模塊

這個比較容易:

/* 
    提示模塊
    參數:狀態(1表示成功,0表示失敗),消息,出現時間(不寫默認是1s)
     */
    function toast(status, msg, time) {
        this.status = status;
        this.msg = msg;
        this.time = time || 1000;
        this.createToast();
        this.showToast();
    }
    
    toast.prototype = {
        createToast: function () {
            if (this.status === 1) {
                var html = '<div class="toast">![](../../imgs/1.png)</img><span class="toast_word">' + this.msg + '</span></div>';
                this.$toast = $(html);
                $('body').append(this.$toast);
            } else {
                var html = '<div class="toast">![](../../imgs/0.png)</img><span class="toast_word">' + this.msg + '</span></div>';
                this.$toast = $(html);
                $('body').append(this.$toast);
            }
        },
        showToast: function () {
            var _this = this;
            this.$toast.fadeIn(300, function () {
                setTimeout(function () {
                    _this.$toast.fadeOut(300, function () {
                        _this.$toast.remove();
                    });
                }, _this.time);
            })
        }
    }
    
    function Toast(status, msg, time) {
        return new toast(status, msg, time);
    }

4.筆記模塊

思路:

  1. 初始化(如 id,username 等等)
  2. 建立節點
  3. 設置顏色
  4. 綁定事件

function Note(opts) {
    this.initOpts(opts);
    this.createNode();
    this.setColor();
    this.bind();
}

Note.prototype = {
    colors: [
        ['#ea9b35', '#efb04e'], // headColor, containerColor
        ['#dd598b', '#e672a2'],
        ['#c24226', '#d15a39'],
        ['#c1c341', '#d0d25c'],
        ['#3f78c3', '#5591d2']
    ],
    defaultOpts: {
        id: '', //Note的 id
        $ct: $('#content').length > 0 ? $('#content') : $('body'), //默認存放 Note 的容器
        context: '請輸入內容', //Note 的內容
        createTime: new Date().toLocaleDateString().replace(/\//g, '-').match(/^\d{4}-\d{1,2}-\d{1,2}/),
        username: 'admin'
    },
    initOpts: function (opts) {
        this.opts = $.extend({}, this.defaultOpts, opts || {});
        if (this.opts.id) {
            this.id = this.opts.id;
        }
        this.createTime = this.opts.createTime ? this.opts.createTime : new Date().toLocaleDateString().replace(/\//g, '-').match(/^\d{4}-\d{1,2}-\d{1,2}/);
        this.username = this.opts.username ? this.opts.username : 'admin'
    },
    createNode: function () {
        var tpl = '<div class="note">' +
            '<div class="note-head"><span class="delete">×</span></div>' +
            '<div class="note-ct" contenteditable="true"></div>' +
            '<div class="note-info"><div class="note-name">' + this.username + '</div><div class="note-time">' + this.createTime + '</div>' +
            '</div>';
        this.$note = $(tpl);
        this.$note.find('.note-ct').html(this.opts.context);
        this.opts.$ct.append(this.$note);
        //if (!this.id) this.$note.css('bottom', '10px'); //新增放到右邊
        Event.fire('waterfall');
    },

    setColor: function () {
        var color = this.colors[Math.floor(Math.random() * 5)];
        this.$note.find(".note-head").css('background-color', color[0]);
        this.$note.find('.note-ct').css('background-color', color[1]);
        this.$note.find('.note-info').css('background-color', color[1]);
    },
    setLayout: function () {
        var self = this;
        if (self.clock) {
            clearTimeout(self.clock);
        }
        self.clock = setTimeout(function () {
            Event.fire('waterfall');
        }, 100);
    },
    bind: function () {
        var _this = this, //記錄下坑,以前末尾是分號不是逗號後面都變成了全局變量結果形成了最後一個才能修改?
            $note = this.$note,
            $noteHead = $note.find('.note-head'),
            $noteCt = $note.find('.note-ct'),
            $close = $note.find('.delete');

        $close.on('click', function () {
            _this.delete();
        });

        $noteCt.on('focus', function () {
            if ($noteCt.html() === '請輸入內容') $noteCt.html('');
            $noteCt.data('before', $noteCt.html());
        }).on('blur paste', function () {
            if ($noteCt.data('before') != $noteCt.html()) {
                $noteCt.data('before', $noteCt.html());
                _this.setLayout();
                if (_this.id) { //判斷是否有這個id,若是有就更新,若是沒有就添加
                    _this.edit($noteCt.html())
                } else {
                    _this.add($noteCt.html())
                }
            }
        });

        //設置筆記的移動
        $noteHead.on('mousedown', function (e) {
            var evtX = e.pageX - $note.offset().left, //evtX 計算事件的觸發點在 dialog內部到 dialog 的左邊緣的距離
                evtY = e.pageY - $note.offset().top;
            $note.addClass('draggable').data('evtPos', {
                x: evtX,
                y: evtY
            }); //把事件到 dialog 邊緣的距離保存下來
        }).on('mouseup', function () {
            $note.removeClass('draggable').removeData('pos');
        });

        $('body').on('mousemove', function (e) {
            $('.draggable').length && $('.draggable').offset({
                top: e.pageY - $('.draggable').data('evtPos').y, // 當用戶鼠標移動時,根據鼠標的位置和前面保存的距離,計算 dialog 的絕對位置
                left: e.pageX - $('.draggable').data('evtPos').x
            });
        });
    },





    /* 添加筆記到數據庫 */
    add: function (msg) {
        var _this = this;
        $.post('/api/notes/add', {
            note: msg
        }).done(function (res) {
            if (res.status === 1) {
                _this.id = res.id;
                Toast(1, '添加成功!');
            } else {
                _this.$note.remove();
                Event.fire('waterfall');
                Toast(0, res.errorMsg);
            }
        })
    },
    /* 編輯筆記數據庫 */
    edit: function (msg) {
        var _this = this;
        $.post('/api/notes/edit', {
            id: this.id,
            note: msg
        }).done(function (res) {
            if (res.status === 1) {
                Toast(1, '更新成功!');
            } else {
                Toast(0, res.errorMsg);
            }
        });
    },
    /* 刪除筆記 */
    delete: function () {
        var _this = this;
        if (confirm("確認要刪除嗎?")) {
            $.post('/api/notes/delete', {
                id: this.id
            }).done(function (res) {
                if (res.status === 1) {
                    Toast(1, '刪除成功!');
                    _this.$note.remove();
                    Event.fire('waterfall')
                } else {
                    Toast(0, res.errorMsg);
                }
            });
        }
    }
}

5.筆記管理模塊

var NoteManager = (function () {
    //頁面加載
    function load() {
        $.get('api/notes').done(function (res) {
            if (res.status === 1) {
                $.each(res.data, function (index, msg) {
                    new Note({
                        id: msg.id,
                        context: msg.text,
                        createTime: msg.createdAt.match(/^\d{4}-\d{1,2}-\d{1,2}/),
                        username: msg.username
                    });
                });

                Event.fire('waterfall');

            } else {
                Toast(0, res.errorMsg);
            }
        }).fail(function () {
            Toast(0, "網絡異常");
        });
    }

    /* 添加筆記 */
    function add() {
        $.get('/login').then(function (res) {//判斷是否登陸
            if (res.status === 1) {
                new Note({
                    username: res.username
                });
            } else {
                Toast(0, res.errorMsg);
            }
        });
    }
    return {
        load: load,
        add: add
    }
})();

6.發佈訂閱模式

/* 發佈訂閱模式 */
var Event = (function () {
    var events = {};

    function on(evt, handler) {
        events[evt] = events[evt] || [];
        events[evt].push({
            handler: handler
        });
    }

    function fire(evt, args) {
        if (!events[evt]) {
            return;
        }
        for (var i = 0; i < events[evt].length; i++) {
            events[evt][i].handler(args);
        }
    }

    function off(name) {
        delete events[name];
    }
    return {
        on: on,
        fire: fire,
        off: off
    }
})();

寫完模塊後,寫入口文件index.js

require('sass/index.scss');
var Toast = require('mod/toast.js').Toast;
var WaterFall = require('mod/waterfall.js');
var NoteManager = require('mod/note-manager');
var Event = require('mod/event.js');


NoteManager.load();
$('.add-note').on('click', function () {
    NoteManager.add();
})

Event.on('waterfall', function () {
    WaterFall.init($("#content"));
})

到這就差很少完成了70%了,接下來就建立數據庫,鏈接數據庫了

/*建立數據庫 運行 node note.js*/

var Sequelize = require('sequelize');
var path = require('path');

var sequelize = new Sequelize(undefined, undefined, undefined, {
    host: 'localhost',
    dialect: 'sqlite',
    // SQLite only
    storage: path.join(__dirname, '../database/database.sqlite')
});

/* 測試鏈接是否成功
node note.js

sequelize.authenticate()
    .then(() => {
        console.log('Connection has been established successfully.');
    })
    .catch(err => {
        console.error('Unable to connect to the database:', err);
    });

*/


var Note = sequelize.define('note', {
    text: {
        type: Sequelize.STRING
    },
    userid: {
        type: Sequelize.INTEGER
    },
    username: {
        type: Sequelize.STRING
    }
});

Note.sync();

/*
刪除表
Note.drop();
*/


/*
//建立數據庫

Note.sync().then(function(){
     Note.create({text:"sdsdsdsd"});
}).then(function(){
    //查詢表
    Note.findAll({raw:true}).then(function(notes){
        console.log(notes);
    })
});
*/




module.exports = Note;

而後是在routes 裏處理 ajax 請求,處理登陸信息,獲取 id,用戶名等等,到這就基本完成了

總結

通過一星期的開發,瞭解了先後端聯調,模塊化開發方式、webpack 及loader和插件的使用、npm 的使用,Express的使用、路由、中間件、sqlite三、nodejs,在開發過程當中仍是有遇到許多問題,例如在連續聲明變量的時候,不當心把逗號寫成了分號,其餘變量就變成了全局變量,因而就出錯了,查了很久?
不過在這過程之中仍是學到了許多,重要的是過程,繼續往前端的路走下去?

相關文章
相關標籤/搜索