资讯专栏INFORMATION COLUMN

前端小项目之在线便利贴

microelec / 1768人阅读

摘要:实现的效果如下界面可能不是太好看,考虑到容器的高度会被拉长,因此没有用图片做背景。

实现的效果如下:

界面可能不是太好看?,考虑到容器的高度会被拉长,因此没有用图片做背景。

预览

便利贴

涉及的知识点

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

然后如下操作:

开始!!

1.新建一个文件夹,名字自己起,打开终端,切换到自己新建文件夹,如

cd (文件夹名称)

2.生成 package.json

npm init -y

3.安装 express

npm i express --save

4.安装 express生成器:

npm install express-generator --save

5.生成 ejs 模板(类似 jsp 的写法)

express -f -e
npm i

其中public用来存放编译后的js文件以及编译好的css文件等,routes用来存放处理 ajax 的请求文件,views就是存放视图文件
然后新建 database 和 src:

其中 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,如果终端显示是这样:

就证明成功了

项目思路

逻辑比较简单

首先用户必须登录才能添加笔记,当用户失焦的时候,将数据插入数据库,并且重新布局(瀑布流)

用户不能更改其他用户的笔记,除了管理员?

用户更新笔记之后,数据库的数据重新更新,重新布局

用户可以删除笔记,数据从数据库中删除,重新布局

用户可以拖拽笔记,但不将位置存入数据库

实现

html,css就不讲了,可以看看我的源码,主要讲 js。

1.瀑布流的实现

思路:(前提是必须绝对定位)

获取元素的宽度

通过窗口的宽度除以元素的宽度来获取列数

初始化一个数组来获取每列的高度,初始化每列的高度为0

遍历元素,获取最小的的列数的高度和索引,对当前元素进行定位,列数高度加等于当前元素的高度

知道思路后,代码很快就写出来了:

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 = "
![](../../imgs/1.png)" + this.msg + "
"; this.$toast = $(html); $("body").append(this.$toast); } else { var html = "
![](../../imgs/0.png)" + this.msg + "
"; 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.笔记模块

思路:

初始化(如 id,username 等等)

创建节点

设置颜色

绑定事件

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 = "
" + "
×
" + "
" + "
" + this.username + "
" + this.createTime + "
" + "
"; 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的使用、路由、中间件、sqlite3、nodejs,在开发过程中还是有遇到许多问题,例如在连续声明变量的时候,不小心把逗号写成了分号,其他变量就变成了全局变量,于是就出错了,查了好久?

不过在这过程之中还是学到了许多,重要的是过程,继续往前端的路走下去?

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/91788.html

相关文章

  • 2016年总结 - 收藏集 - 掘金

    摘要:然而这次的文章,就像贺师俊所说的这篇文章是从程序员这个老年度总结前端掘金年对我来说,是重要的一年。博客导读总结个人感悟掘金此文着笔之时,已经在眼前了。今天,我就来整理一篇,我个人认为的年对开发有年终总结掘金又到 2016 Top 10 Android Library - 掘金 过去的 2016 年,开源社区异常活跃,很多个人与公司争相开源自己的项目,让人眼花缭乱,然而有些项目只是昙花一...

    DataPipeline 评论0 收藏0
  • 前端阅读 - 收藏集 - 掘金

    摘要:实现不定期更新技巧前端掘金技巧,偶尔更新。统一播放效果实现打字效果动画前端掘金前端开源项目周报前端掘金由出品的前端开源项目周报第四期来啦。 Web 推送技术 - 掘金腾讯云技术社区-掘金主页持续为大家呈现云计算技术文章,欢迎大家关注! 作者:villainthr 摘自 前端小吉米 伴随着今年 Google I/O 大会的召开,一个很火的概念--Progressive Web Apps ...

    lingdududu 评论0 收藏0
  • 【Java】广州三本秋招经历

    摘要:具体的时间线从月中旬,我开始关注牛客网的秋招内推信息。直至十月中下旬结束秋招。之前也写过自己在广州找实习的经历,那次把面试的过程都具体贴出来了。我今年就完美错过了春招实习经历。 前言 只有光头才能变强 离上次发文章已经快两个月时间了,最近一直忙着秋招的事。今天是2018年10月22日,对于互联网行业来说,秋招就基本结束了。我这边的流程也走完了(不再笔试/面试了),所以来写写我的秋招经历...

    qqlcbb 评论0 收藏1
  • 九款优秀的企业项目协作工具推荐

    摘要:钉钉钉钉是阿里巴巴集团专为中国企业打造的免费沟通和协同的多端平台,提供版,版和手机版,支持手机和电脑间文件互传。 1:@teamhttps://www.atteam.cn/项目协作管理,越复杂越有序,足够简单足够有效,@Team针对企业团队协作所遇到的困境而研发的新一代基于云服务的企业级协同工作平台,通过为每个企业或团队提供专属的私密网络空间和全新的协作方式,帮助企业实现高效便捷的跨部...

    lei___ 评论0 收藏0

发表评论

0条评论

microelec

|高级讲师

TA的文章

阅读更多
最新活动
阅读需要支付1元查看
<