参考一
参考二
在线便利贴使用sequelize对数据进行增删改查,通过OAuth2.0实现GitHub的第三方登录,通过req.session.user判断用户是否登录的设定权限,使用瀑布流进行布局,当用户添加、修改、删除会弹出Toast,使用pm2持久化网站。
一、前端如何实现
1. 发布订阅模式
2. 瀑布流布局
3.Toast
4. Note
5. Webpack配置
6. 预处理起Less
二、后端如何实现
1. 搭建node安装环境
2. 安装express应用生成器
3. ejs模版引擎
4. 中间件使用
5. 定前后端的借口
6. 安装数据库sequelize
7. OAuth2.0实现第三方登录
三、如何部署服务器
————————————————————————————
1. 使用Express搭建框架
1.下面进行我们正式的开始:
cd ~/desktop
mkdir express-sticky-note
cd express-sticky-note
1、npm init -y
初始化
2、npm i express --save
本地下载express
可以使用nrm use cnpm
切换到淘宝镜像,发布npm包的时候再用nrm use npm
切换回来。这样可以在下载npm包的时候更快。
3、npm install express-generator --save-dev
下载express应用生成器
4、使用experss的命令行,并安装模板引擎ejs ./node_modules/express-generator/bin/express-cli.js . -f -e
生成app的应用
5、然后安装所有依赖包:npm install
运行:npm start
,然后打开http://localhost:3000/,出现初始页面
express要学习的是中间件,路由和模板引擎
2、完善文件目录
mkdir src
mkdir src/less src/js
mkdir src/js/lib src/js/mod src/js/app
touch src/webpack.config.js
- 目标代码:public > 文件夹stylesheets、文件夹javascripts、文件夹images
- 源文件:新建 src > 文件夹less、文件夹imgs,写代码就在这里面写,写好之后经过编译,就跑到目标代码里面,运行时,整个网站访问的是public里面的东西。
- 在src文件夹里面的js文件夹里面:
(1) 新建文件夹lib,主要放一些与自己写的功能无关的文件,比如引用的jquery、Vue等。
(2)新建文件夹app:代表不同页面的入口,比如首页index.js、详情页detail.js,如果有多个页面,就有多个入口。
(3)新建文件夹mod:代表通用的模块。
(目录层级划分:
(1)按应用划分:比如js里面有很多文件夹,每一个文件夹就代表一个页面所使用到的js;
(2)按功能划分:比如把非页面类的东西放到一起,页面类的放到一起)
3、前端:定义页面
- 搭建一个简单的测试环境,把webpack跑通
- src的目录mod里面新建a.js和b.js,目录app里面新建index.js
a.js里代码为module.exports.a = 'aaaa'
b.js里代码为
var a = require('./a.js').a
module.exports = {
b:'bbbb',
a:a
}
index.js里代码为
var obj = require('../mod/b.js')
console.log(obj)
- src的里面新建webopack.config.js,并配置它
var webpack = require('webpack')
var path = require('path')
module.exports = {
entry: path.join(__dirname,"js/app/index.js"),
output: {
path: path.join(__dirname,"../public"),
filename: 'javascripts/index.js'
}
};
//把js/app/index.js输出到public/js下
2、安装webpack 和 实时监控watch
npm install webpack webpack-cli --save-dev
以及npm install onchange --save-dev
在package.json文件下"script":{里面添加
"webpack":"webpack --config=src/webpack.config.js",
"watch": "onchange 'src/**/*js' 'src/**/*.less' -- npm run webpack"
每行后面加逗号,
实现输入 npm run webpack
就能运行 webpack
如果出现模式警告,在webpack.config.js里增加一条:
module.exports = {
mode: 'production', //加这一句就可以了
};
再开一个终端,npm run watch
只要有修改,watch就会跟进运行webpack
3、安装loader
npm install less less-loader css-loader style-loader --save
,用于将less文件导出成css文件,渲染到页面上
4、src>js>lib新添一份jquery文件,我的是jquery-3.4.1.min.js
5、改写webpack.config.js,增加
var webpack = require('webpack')
var path = require('path')
module.exports = {
mode: 'production',
entry: path.join(__dirname, "js/app/index.js"),
output: {
path: path.join(__dirname, "../public"),
filename: "javascripts/index.js"
},
module: {
rules: [{ //转义,把.less转成less字符串,再转成语义化css,再将样式加入页面中
test: /\.less$/,
use: ["style-loader","css-loader", "less-loader",]
}]
},
resolve: {
alias: { //alias:别名
jquery: path.join(__dirname, "js/lib/jquery-3.4.1.min"),
mod: path.join(__dirname, "js/mod"),
less: path.join(__dirname, "less")
}
},
plugins: [
new webpack.ProvidePlugin({ //安装插件,在所有有需求jquery的js文件里,不要需要require("jquery")
$: "jquery"
}),
]
};
6、这些工作准备好之后,
(1)在src>js>mod新建 toast.js文件
require('less/toast.less');
function toast(msg, time){
this.msg = msg;
this.dismissTime = time||1000; //ms
this.createToast();
this.showToast();
}
toast.prototype = {
createToast: function(){
var tpl = ''+this.msg+'';
this.$toast = $(tpl);
$('body').append(this.$toast); //放到页面body中
},
showToast: function(){
var self = this;
this.$toast.fadeIn(300, function(){ //有300ms的时间出现
setTimeout(function(){
self.$toast.fadeOut(300,function(){
self.$toast.remove();
});
}, self.dismissTime);//过了消失时间后自己就把自己删除
});
}
};
function Toast(msg,time){ //创建Toast是个函数
return new toast(msg, time);
}
module.exports.Toast = Toast; //module.exports曝光导出的是对象,可以require
(2)在src>less新建 toast.less
.toast {
position: fixed;
left: 50%;
transform: translateX(-50%);
bottom: 20px;
color: #D15A39;
background: #fff;
padding: 5px 10px;
border-radius: 3px;
box-shadow: 0px 0px 3px 1px rgba(255,255,255,0.6);
display: none;
}
body{
background: grey;
}
(3)在public文件夹下新建一个test.html,
Document
(4)删除之前的mod里面的a.js和b.js,在src>js>app新建 index.js
let Toast = require('../mod/toast.js').Toast
Toast("你好");
(5)这个时候打开两个终端,一个npm start,一个npm run watch,
在浏览器打开http://localhost:3000/test.html
页面显示如下
我们在开发者工具中可以看到,html中多了style标签,里面有css样式,这是因为下载了loader,通过loader,将less文件转成css文件,并生成style标签。
(6)在src>js>mod新建 event.js
var EventCenter = (function(){ //发布订阅,用于对事件进行解耦
var events = {};
function on(evt, handler){ //在需要监听的地方,直接写一个 on某个事件,写个回调函数
events[evt] = events[evt] || [];
events[evt].push({
handler: handler
});
}
function fire(evt, args){ //对触发的点,就直接fire某个事件
if(!events[evt]){
return;
}
for(var i=0; i
(7)瀑布流,布局,在src>js>mod新建 waterfall.js
var WaterFall = (function(){
var $ct;
var $items;
function render($c){
$ct = $c;
$items = $ct.children();
var nodeWidth = $items.outerWidth(true),
colNum = parseInt($(window).width()/nodeWidth),
colSumHeight = [];
for(var i = 0; i
(8)在src>less新建 note.less,便利贴样式
.note{
position: absolute;;
color: #333;
width: 160px;
margin: 20px 10px;
transition: all 0.5s;
.note-head{
height: 20px;
background-color: #ea9b35;
cursor: move;
font-size: 12px;
line-height: 20px;
padding-left: 10px;
color: #666;
&:hover .delete{
opacity: 1;
}
&:before{
position: absolute;
left: 50%;
top: -11px;
margin-left: -32px;
content: ' ';
display: block;
width: 64px;
height: 18px;
background: #35bba3;
}
&:after {
position: absolute;
left: 50%;
margin-left: 32px;
top: -11px;
z-index: -1;
content: '';
display: block;
width: 0;
height: 0;
border-left: 5px solid #299683;
border-top: 18px solid transparent;
}
}
.note-ct{
padding: 10px;
background-color: #efb04e;
outline: none;
}
.delete {
position: absolute;
top: 4px;
right: 4px;
font-size: 12px;
color: #fff;
cursor: pointer;
opacity: 0;
transition: opacity .3s;
}
}
.draggable{
opacity: 0.8;
cursor:move;
transition: none;
}
(9)在src>js>mod新建 note.js ,一个便利贴
require('less/note.less');
var Toast = require('./toast.js').Toast;
var Event = require('mod/event.js');
function Note(opts){
this.initOpts(opts); //初始化
this.createNote(); //创建节点
this.setStyle(); //设置样式
this.bindEvent();
}
Note.prototype = {
colors: [
['#ea9b35','#efb04e'], // headColor, containerColor
['#dd598b','#e672a2'],
['#eee34b','#f2eb67'],
['#c24226','#d15a39'],
['#c1c341','#d0d25c'],
['#3f78c3','#5591d2']
],
defaultOpts: { //默认样式
id: '', //Note的 id
$ct: $('#content').length>0?$('#content'):$('body'), //默认存放 Note 的容器
context: 'input here' //Note 的内容
},
initOpts: function (opts) { //初始化
this.opts = $.extend({}, this.defaultOpts, opts||{});
if(this.opts.id){
this.id = this.opts.id;
}
},
createNote: function () { //创建节点
var tpl = ''
+ '×'
+ ''
+'';
this.$note = $(tpl);
this.$note.find('.note-ct').text(this.opts.context);
this.$note.find('.username').text(this.opts.username);
this.opts.$ct.append(this.$note);
if(!this.id) this.$note.css('bottom', '10px'); //新增放到右边
},
setStyle: function () { //设置样式
var color = this.colors[Math.floor(Math.random()*6)];
this.$note.find('.note-head').css('background-color', color[0]);
this.$note.find('.note-ct').css('background-color', color[1]);
},
setLayout: function(){ //设置布局
var self = this;
if(self.clk){
clearTimeout(self.clk);
}
self.clk = setTimeout(function(){
Event.fire('waterfall'); //发送瀑布流,告诉瀑布流组件需要去布局
},100);
},
bindEvent: function () { //绑定事件
var self = this,
$note = this.$note,
$noteHead = $note.find('.note-head'),
$noteCt = $note.find('.note-ct'),
$delete = $note.find('.delete');
$delete.on('click', function(){
self.delete(); //删除
})
//contenteditable没有 change 事件,所有这里做了模拟通过判断元素内容变动,执行 save
$noteCt.on('focus', function() {
if($noteCt.html()=='input here') $noteCt.html(''); //无输入,就不添加
$noteCt.data('before', $noteCt.html());
}).on('blur paste', function() {
if( $noteCt.data('before') != $noteCt.html() ) {
$noteCt.data('before',$noteCt.html());
self.setLayout();
if(self.id){
self.edit($noteCt.html()) //编辑
}else{
self.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
});
});
},
edit: function (msg) {
var self = this;
$.post('/api/notes/edit',{
id: this.id,
note: msg
}).done(function(ret){
if(ret.status === 0){
Toast('update success');
}else{
Toast(ret.errorMsg);
}
})
},
add: function (msg){
console.log('addd...');
var self = this;
$.post('/api/notes/add', {note: msg})
.done(function(ret){
if(ret.status === 0){
Toast('add success');
}else{
self.$note.remove();
Event.fire('waterfall')
Toast(ret.errorMsg);
}
});
//todo
},
delete: function(){
var self = this;
$.post('/api/notes/delete', {id: this.id})
.done(function(ret){
if(ret.status === 0){
Toast('delete success');
self.$note.remove();
Event.fire('waterfall')
}else{
Toast(ret.errorMsg);
}
});
}
};
module.exports.Note = Note;
(10)在src>js>mod新建 manager.js
var Toast = require('./toast.js').Toast;
var Note = require('./note.js').Note;
var Toast = require('./toast.js').Toast;
var Event = require('mod/event.js');
var NoteManager = (function(){
function load() {
$.get('/api/notes') //调用这个接口,得到数据
.done(function(ret){
if(ret.status == 0){
$.each(ret.data, function(idx, article) {
new Note({ //对于每一个数据,进行new Note
id: article.id,
context: article.text,
username: article.username
});
});
Event.fire('waterfall'); //上面渲染好之后,发送一个waterfall,需要做瀑布流布局
}else{
Toast(ret.errorMsg);
}
})
.fail(function(){
Toast('网络异常');
});
}
function add(){
new Note();
}
return {
load: load, //下载,获取数据,渲染页面
add: add //点“添加”时,添加便利贴
}
})();
module.exports.NoteManager = NoteManager
(11)修改 src>js>app>index.js,首页
require("less/index.less");
var NoteManager = require("mod/note-manager.js").NoteManager;
var Event = require("mod/event.js");
var WaterFall = require("mod/waterfall.js");
NoteManager.load(); //首先NoteManager加载所有的数据,进行渲染
$(".add-note").on("click", function() { //点击添加按钮,执行 添加
NoteManager.add();
});
Event.on("waterfall", function() { //事件听到waterfall,执行瀑布流
WaterFall.init($("#content"));
});
(12)补充 src>less 新建index.less
html,body{
margin: 0;
height: 100%;
}
ul,li{
margin: 0;
padding: 0;
list-style: none;
}
body{
font: 14px/1.4 'Arial';
}
a{
text-decoration: none;
color: #fff;
}
#header {
height: 30px;
font-size: 12px;
a {
display: block;
font-size: 12px;
margin-top: 6px;
}
.user-area {
padding-right: 16px;
float: right;
li {
float: left;
margin-left: 5px;
span {
color: #fff;
display: block;
margin-top: 6px;
}
}
img {
height: 18px;
margin-top: 5px;
border-radius: 50%;
}
}
.setting{
float: right;
margin-left: 9px;
display: none;
}
.add-note{
float: left;
margin-left: 16px;
border: 1px solid #fff;
border-radius: 4px;
padding: 2px 4px;
}
.login{
float: right;
margin-left: 16px;
}
}
#content{
position: relative;
height: -webkit-calc(100% - 30px);
height: calc(100% - 30px);
}
@keyframes move-twink-back {
from {background-position:0 0;}
to {background-position:-10000px 5000px;}
}
.stars, .twinkling{
position:absolute;
top:0;
left:0;
right:0;
bottom:0;
width:100%;
height:100%;
display:block;
}
.stars {
background:#000 url(http://7xpvnv.com2.z0.glb.qiniucdn.com/ba25c630-1c91-4ac1-a3de-65555d78c147.png) repeat top center;
z-index:-2;
}
.twinkling{
background:transparent url(http://7xpvnv.com2.z0.glb.qiniucdn.com/493b97e6-c499-4b41-a26b-8942873615b0.png) repeat top center;
z-index:-1;
animation:move-twink-back 200s linear infinite;
}
4、后端:定义接口
(1)修改views > index.ejs 模版引擎
<%= title %>
(2)处理路由
首先确定原则,即和后台去约定接口:
1. 获取所有的 note:GET /api/notes req:{} res:{status:0,data:[{},{}]} res:{status:1,errorMsg:"失败的原因“}
2. 创建一个 note:POST /api/note/create req:{note:"hello world"} res:{status:0} res:{status:1,errorMsg:"失败的原因“}
3. 修改一个 note:POST /api/note/edit req:{note:'new note',id:100}
4. 删除一个 note:POST /api/note/delete req:{id:100}
(3)安装数据库sequelize
https://www.npmjs.com/package/sequelize
https://sequelize.readthedocs.io/en/v3/
- 安装sequelize
npm install --save sequelize
npm install --save sqlite3
如果第二个安装失败,可以使用cnpm install sqlite3。反复多装几次。
- 连接数据库
-- 新建文件models/note.js
-- 新建文件夹database 里面会自动创建文件的
-- 下面内容拷贝到note.js
var sequelize = new Sequelize(undefined,undefined,undefined, {
host: 'localhost',
dialect: 'sqlite',
storage: '../database/database.sqlite' //这是储存位置
});
sequelize
.authenticate()
.then(() => {
console.log('Connection has been established successfully.');
})
.catch(err => {
console.error('Unable to connect to the database:', err);
});
测试连接情况
cd models
node note.js
成功
测试增删改查
var Note = sequelize.define('note', {
text:{
type:Sequelize.STRING
}
});
Note.sync().then(function(){
Note.create({
text:'hello world'
}).then(function(){
Note.findAll({raw:true}).then(function(notes){
console.log(notes)
})
})
});
查找所有数据,findAll()
查找所有真实数据,findAll({raw:true})
查找所有真实数据,findAll({raw:true,where:{id:2}})
删除某个数据,destroy({where:{text:'xxx'}},function(){})
创建某个数据,create({text:notes})
编辑更新某个数据,update({text:req.body.note},{where:{id:req.body.id}})
遇到bug,调不出来
https://zhuanlan.zhihu.com/p/45747336
以后再弄,反正就是public里的index.js