实现的效果如下:

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

预览

便利贴

涉及的知识点

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

就证明成功了

项目思路

逻辑比较简单

  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 onlystorage: path.join(__dirname, '../database/database.sqlite')
});/* 测试连接是否成功
node note.jssequelize.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,在开发过程中还是有遇到许多问题,例如在连续声明变量的时候,不小心把逗号写成了分号,其他变量就变成了全局变量,于是就出错了,查了好久?

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

前端小项目之在线便利贴相关推荐

  1. 小项目----音乐在线播放器

    目录 音乐播放器 项目功能: 项目框架: 项目的创建很重要: 数据库设计 在idea中配置数据库和xml 一.登录板块设计 1创建User类 2创建UserMapper和Controller接口 3. ...

  2. 前端小项目(一)| 电影院座位预定(html,css,js)

    前端小项目(一)| 电影院座位预定 前言 开始好好学习前端啦.学紫色爱心记录一波!! 初步学了html,css,js,在github上找了几个前端小项目模仿着练练手.第一个就是电影院座位预定页面,主要 ...

  3. 白天、暗夜双重模式+自作潜水俱乐部前端小项目+学习经验总结(一)

    嗨,这里是X,今天带来最近写的一个前端小项目,还挺不错的,所以就在这里分享一下

  4. vue练手小项目--眼镜在线试戴

    最近看到了一个眼镜在线试戴小项目使用纯js手写的,本人刚学习vue.js没多久,便试试用vue做做看了,还没完善. 其中包括初始图片加载,使用keywords查找,父子组件之间传递信息,子组件之间传递 ...

  5. 前端小项目——模拟微信界面对话框

    最近看网课做了个小项目,用到了前端很多知识点 用到的知识点: HTML知识点: div大盒子,id为contentALL:包含所有的内容 div头部小盒子,id为header:包含整个对话框的头部信息 ...

  6. 618快到了送上自制前端小项目(html css js)

    目录 ??.自定义播放器 ??.图片自动消失 .小轮播图 ??.旋转音乐盒 前言:这些小项目全都是自创的. 如果需要应用,或则转发的话请与 博主联系,感谢你们的理解, 1.自定义播放器 在页面中放置2 ...

  7. 【前端小项目】基于Vue全家桶的在线音乐播放器(提供在线演示)

  8. web前端小项目个人实例_Web前端:小程序界面与逻辑项目实训

    大家好,我来了!本期为大家带来的Web前端学习知识是"Web前端:小程序界面与逻辑项目实训",喜欢Web前端的小伙伴,一起看看吧! 主要内容 数据绑定 渲染 界面层数据渲染 事件处 ...

  9. (云服务器+JQuery+JS+BootStrap+Navicat+AJAX+JavaScript)第一个前端小项目【面试】

    目录 第一步:登录云服务器 查看根目录​ 运行jar包 端口被占用 ps -ef:查看使用的端口号 kill -9 端口号:杀掉该进程,取消端口占用 1.配置数据库 2.启动jar包 3.测试swag ...

最新文章

  1. github用相对路径显示图片_url-图像未显示在GitHub的README.md中
  2. python画图代码彩虹-Python利用turtle库绘制彩虹代码示例
  3. 设计模式理解:模板方法
  4. 组合内容_剑与远征:亚龙组合成型,新的更新内容,比以前更强了?
  5. c mysql 返回自增id_mysql返回自增id
  6. bootstrap媒体查询类型的值_HTMLCSS学习笔记(二十一)-- 媒体查询 + rem用法
  7. 《C专家编程》阅读笔记
  8. 基于PaddlePaddle实现声纹识别
  9. 车型代号对照表_车型和VIN代号对照表3.24.doc
  10. w7怎么修改服务器dns,win7系统在哪修改dns?win7系统修改dns的详细步骤
  11. 20sccm_SCCM安装及配置过程总结
  12. 美团旅行数据质量监管平台实践
  13. IOS开发之延时执行的几种方法
  14. java项目导入jre不识别_Eclipse中的Java项目:无法解析类型java.lang.Object。 从所需的.class文件间接引用它...
  15. 2009年7月22日全食 日全食观测方法 发生原理
  16. AOP-Chap26-Sorting
  17. 数字孪生博物馆数字化展厅,可视化应用系统优势
  18. 回忆过去的六月-转自LinuxAid
  19. CCS 之 关于TI 28035 SPI的一点问题
  20. Laravel6.* 使用Guzzle执行HTTP请求

热门文章

  1. DevExpress v15.1:WPF控件升级(四)
  2. erlang 二进制
  3. MongoDB应用场景
  4. LVS +DR +keepalived高可用性web集群
  5. NSURL 基本方法 absoluteString
  6. UBuntu14.04下安装和卸载Qt5.3.1
  7. CentOS6.4之文本编辑器Vi/Vim
  8. java新特性对数组的支持
  9. SqlServer2005数据库分区
  10. js双等号探索(一): [] == ![]为Ture ?