0%

开课吧项目说明

kkb-ccb

  • 开课吧学习的几个项目,纯手打学习
  • 下面是核心知识点和demo的展示
  • nodejs-angular项目部署到了服务器上,其他项目用截图演示

Nodejs&Angular

地址

功能

  • nodejs后台渲染展示课程,后台管理添加课程等
  • angular前台登录,进入个人课程中心,查看课程进度

核心知识点:

demo

node-angular


Vue

功能

  • 购物车demo
  • 登录,商品展示/添加购物车

核心知识点:

  • Cube-ui
  • Vue组件化: 表单组件原理及实现

  • Vue-router&&Vuex

  • token+http拦截器

  • Vue源码:响应式原理

demo

vue-buy


React

功能

  • 购物车demo
  • 登录,商品展示/添加购物车

核心知识点:

  • Umi+Dva+Antd
  • React组件化: 表单组件原理及实现

  • JSX

  • 组件:函数/类 组件,高阶组件

  • Redux基本原理实现

  • React源码:简单虚拟dom的生成和渲染实现

demo

react-buy


Minp

功能

  • 购物车小程序demo
  • 商品展示/添加购物车

核心知识点:

  • 小程序云开发:云函数/云数据库/云存储 基本使用

  • Taro: React语法开发

  • Mobx 数据管理

demo

taro-buy

nodejs后台渲染核心知识点

express

express路由

模块化

  • app.use(path, ‘exported router’)
  • 分文件嵌套使用,按功能命名划分,放置在routes

路由传参

  • 见url参数一节

  • 重定向: res.redirect('/admin/open-course')

自定义中间件

  • 自定义一个模块并导出
  • app中require引入该模块,
  • 并且用app.use注册该中间件
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
//功能:初始化全局变量, 应用级中间件,在每个路由生效
module.exports.initLocals = function(req, res, next) {
//存储数据到全局属性res.locals.courses中,res和req是贯穿整个server的各个阶段,,不需要render传递参数, 模板引擎中就可用,
//app.locals是存储在app实例上的全局属性
res.locals.courses = [{ // 数据需要从数据库中查询得到
url: '/vip-course/web', //注意路径,不能少了前面的/,
icon: 'https://www.kaikeba.com/vipcourse/web',
name: 'web全栈架构师',
desc: '深度对标百度'
},
{
url: '/vip-course/python',
icon: 'https://www.kaikeba.com/vipcourse/python',
name: 'python全栈架构师',
desc: '深度对标百度'
}
];
//存储布局页layout.hbs中的导航栏视图名称, 根据用户是否登陆,返回不同的导航视图名,布局页所有的路由都要使用,所以不用具路由render变量了
const isLogin=true; //实际应根据session或数据库获取用户是否登陆
res.locals.navName=isLogin? 'nav': 'nav-noAuth';
res.locals.obj={key:'value'};

next(); // 进入下一个中间件
};

-------------------------------------------------------------------------------------
//全局中间件优化:使用数据库数据(先判断缓存)

const {query}=require ('../models/db');
//缓存courses 存在则不查询,服务器重启才会消失
let coursesCache=null; //会改变courses的指向 所以值会变 用let

module.exports.initLocals=async function(req,res,next){
//存储布局页layout.hbs中的导航栏视图名称, 根据用户是否登陆,返回不同的导航视图名,布局页所有的路由都要使用,在全局中间件中存储变量
const isLogin=true; //实际应根据session或数据库获取用户是否登陆
res.locals.navName=isLogin? 'nav': 'nav-noAuth';
res.locals.obj={key:'value'};
if(coursesCache) {
res.locals.courses = coursesCache;
next();// 进入后续中间件
} else {
const sql = 'SELECT * FROM kkb.vip_course';
try {
const courses = await query(sql);//query必须返回Promise才能使用await
// cooperation处理一下
courses.forEach(course =>
course.cooperation = course.cooperation.split(','));
//console.log(courses);
coursesCache = res.locals.courses = courses;
next();// 进入后续中间件
} catch (err) {
next(err)
}
}
};

错误处理:

  • createError+error handler(带err参数的callback)
  • 一般放在最后,按中间件的顺序,若 执行到这,说明有错误,createError进入 error handler
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// createError
app.use(function(req, res, next) {
console.log('createerror')
//next() //带err的中间件 是错误处理,,createError时才会触发进入error handler
next(createError(404));
});

// error handler
app.use(function(err, req, res, next) {
console.log(' error handler')
// set locals, only providing error in development
res.locals.message = err.message;
res.locals.error = req.app.get('env') === 'development' ? err : {};

// render the error page
res.status(err.status || 500); //没有指定错误码时 便是500
console.log('render error')
res.render('error');
});

全局变量

  • 见自定义中间件例子,除了res.locals还有app.locals
  • 全局变量的使用: 比如 在layout.hbs使用按登录状态的nav whichPartial navName

模板引擎

设置hbs模板引擎

1
2
3
4
5
6
// 设置视图模板目录
app.set('views', path.join(__dirname, 'views'));
// 设置模板引擎
app.set('view engine', 'hbs');
// 设置模板文件的后缀名
//

render

  • 以views目录为基准 res.render('path' , options)
  • options中layout默认为’layout’,渲染布局页,布局页用 三括号 body三括号 承载其他模板

hbs语法

  • 官方文档:https://handlebarsjs.com/guide/partials.html#basic-partials

  • 插值表达式

  • 条件判断if switch

  • 循环each (数组/对象)

  • partials/helpers

    • 注册partial目录:hbs.registerPartials(path.join(__dirname, '../views/partials'));

    • 自定义helper: var helper = require('./helper');,app中引入即可,hbs是单例

    • 如代码搬家context.fn(this)/context.inverse(this)

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    const blocks = {}; //代码块缓存对象  全局变量,服务器开着就不会消失,, 
    hbs.registerHelper('extend', function(name, context) {
    // context 是上下文,保存有用方法和数据,最后一个参数永远是context,,hbs内部调用时传入
    let block = blocks[name]; // block用来存放代码块
    if (!block) {
    block = blocks[name] = [];
    }
    // 编译指令中代码块并放入block
    block.push(context.fn(this)); //context.fn方法将数据编译到非else的模板中 传递this,块级helper中的变量才会编译进模板内容
    // 与context.fn()配对还有一个方法
    //block.push(context.inverse(this));// //context.inverse()方法将数据编译到else的分支模板中,,块级partial中可以用else
    //总之:context.fn和inverse是取得块级模板中的模板内容,传递this则会把块级模板内的变量也编译进模板内容中,最后输出到页面(helper有返回值才行,没有返回值就不会有东西输出给页面)
    });

    hbs.registerHelper('block', function(name) {
    const val = (blocks[name] || []).join('');
    blocks[name] = []; //清空缓存
    return val;
    });
  • 行级partial

    1
    2
    {{> nav}}   // 行级partial,像这种不需要传参可用
    {{> (whichPartial navName)}} // 动态行级partial
  • 块级partial

    1
    2
    3
    4
    <!--用法1 块级partial 错误处理-->
    {{#> ooxx}}
    因为没有ooxx视图,所以出现这句话
    {{/ooxx}}
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    <!--用法2 块级partial 传递内容: 封装窗口为例-->
    {{#> win obj }}<!--传参方式1:哈希,键=值(变量)如title='wintitle' a='a' b='b';传参方式2:对象,则win视图中可直接使用相关属性-->
    <div style="color:yellow">这是窗口的内容</div> // 块级partail里 没有命名的默认为@partial-block, 组件中通过> @partial-block引用内容
    {{/win}}

    //对应hbs
    <div class="win">
    <div class="win-title">{{key}} {{title}} {{a}} {{b}} </div>
    {{!-- 插槽@partial-block --}}
    {{> @partial-block}}
    {{> @partial-block}}
    </div>
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    <!--用法2 块级partial 布局组件-->
    {{#> layout}}
    {{!-- partial调用时命名多个插槽 #*inline--}}
    {{#*inline 'top'}}
    这是top
    {{/inline}}
    {{#*inline 'foo'}}
    foo
    {{/inline}}
    {{#*inline 'bar'}}
    bar
    {{/inline}}
    {{/layout}}

    对应hbs
    <div class="top">
    {{> top}} // partial内部按插槽名引用内容
    </div>
    <div class="content">
    <div class="left">
    {{> foo}}
    </div>
    <div class="right">
    {{> bar}}
    </div>
    </div>

组件化

  • 参考模板引擎partial使用
  • 主要模式: 组件提取+代码搬家,将某一功能html/css/js写成一个组件,用代码搬家,最终整合到layout模板的指定位置
  • 案例参考: views/partials/pager 分页组件的使用

url参数

完整url

  • http://user:pass@sub.example.com:8080/p/a/t/h?query=string#hash'

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    // url 中间件
    url.parse结果: 一切url参数的处理 可以参考
    {
    protocol: 'http:', // 协议
    slashes: true, //
    auth: 'user:pass', // 用户登录才能访问
    host: 'sub.example.com:8080', // 域名(包含端口)
    port: '8080', //端口
    hostname: 'sub.example.com', // 域名(不包含端口)
    hash: '#hash', // 哈希值
    search: '?query=string', // ?及?后面的部分
    query: 'query=string', // ?后面的部分
    pathname: '/p/a/t/h', //端口后面, ?之前的部分
    path: '/p/a/t/h?query=string', // 端口后面的部分,不包含哈希
    href:'http://user:pass@sub.example.com:8080/p/a/t/h?query=string#hash' // 完整请求url
    }

参数类型

get(url占位参,查询参), post(urlencoded, json)

get传参

  • 原生: url模块 : let urlObj = url.parse(str, true)

  • express: 封装在req.query(查询参), req.params(url参数)

post传参

  • 原生: querystring模块 let obj = qs.parse(param); querystring是用来处理urlencoded形式的数据的方法,,不论get,post,, post的数据默认也是这种形式

  • express

    • express自带: app.use(express.json()) app.use(express.urlencoded({ extended: false }));
    • body-parser: app.use(bodyParser.urlencoded({ extended: false })) app.use(bodyParser.json());
    • 其他: 如multiparty,,则支持表单普通类型和文件类型数据的提交
    • 除了文件类型,都是通过req.body获取表单字段数据,,,文件通过req.file;; 响应json数据可以通过res.send或res.json

表单(todo)

数据提交

表单默认方式

  • get
  • post
  • enctype

ajax restful提交

  • urlencoded
  • json
  • datatype/content-type

文件上传/数据校验

文件上传和数据校验是通过针对某一个路由,添加问价上传和数据校验的中间件进行实现

文件上传

express中multer模块为例

文件上传: 表单设置multi-formdata, 一般文件用file获取,表单项用name获取

  • multer: const multer = require('multer');

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    // 设置存储目录和filename
    const storage = multer.diskStorage({ //比单个dest内容更丰富
    destination: function(req, file, cb) { // 存储目录
    cb(null, 'public/images');
    },
    filename: function(req, file, cb) {
    let extname = '';
    switch (file.mimetype) {
    case 'image/jpeg':
    extname = '.jpg';
    break;
    case 'image/png':
    extname = '.png';
    break;
    case 'image/gif':
    extname = '.gif';
    break;
    }
    cb(null, Date.now() + extname);
    }
    });


    // 实例化multer
    const upload = multer({
    // dest: 'public/images',
    storage,
    limits: { fileSize: 2 * 1024 * 1024 }, //最大2M
    fileFilter: function(req, file, cb) {
    console.log(file);
    // 判断文件是否合法,合法则处理,不合法则拒绝
    if (file.mimetype === 'image/gif' ||
    file.mimetype === 'image/jpeg' ||
    file.mimetype === 'image/png') {
    // 接收文件
    cb(null, true);
    } else {
    cb(new Error('请上传图片格式'), false);
    }
    }
    });

数据校验

1
2
3
4
5
6
7
const { body, validationResult } = require('express-validator/check'); //解构得到body(req.body) 和 validationResult  ;body针对post data,还有query params对应不同传参方式的参数
const validations = [ //validations每一项都是一个中间件
body('name').not().isEmpty().withMessage('名称必填'), //链式方法
body('description').not().isEmpty().withMessage('描述信息必填'),
body('time').not().isEmpty().withMessage('时间必填')
.isAfter(new Date().toString()).withMessage('截止日期必须晚于当前时间')
];

中间件综合上传和校验

1
2
3
4
5
6
7
8
9
10
11
12
13
router.post('/open-course', [upload.single('file'), ...validations],  // 中间件callback多个的应用
async(req, res, next) => {
})


// 文件获取
upload.single('id')将单文件存放到静态资源目录,数据库存储文件名req.file.filename

// 校验结果
let errors = validationResult(req);
errors = errors.formatWith(({ msg }) => msg); // 简化校验信息
errors.array() //查看校验信息
errors.isEmpty() //

mysql

crud sql

  • 常规query

    1
    2
    3
    4
    5
    await query('select * from open_course');

    await query(sql, [offset, pageSize])

    await query('insert into open_course set ?', req.body)
  • mysql 格式化sql+query,防止注入

    1
    2
    sql = mysql.format('update  open_course set ? where id =?', [req.body, id]);
    const result=await query(sql);

普通查询封装promise

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
//db.js
const mysql=require('mysql');
const cfg={
host:'localhost',
user:'kkb_admin',
password:'admin',
database:'kkb'
};

module.exports={
query:function(sql, value){
return new Promise(function(resolve, reject){
const conn =mysql.createConnection(cfg);
conn.connect();//可省略
conn.query(sql, value, (err, results)=>{
if(err)
reject(err); //err是promise的值
else
resolve(results);//results是promise的值
});
conn.end();//end方法会在所有查询结束后执行 比较智能 不用写在回调里
});
}
};

// 外部使用db async await
const {query}= require('../models/db');
router.get('/', async (req, res, next)=>{
const sql = 'SELECT * FROM open_course ORDER BY time DESC LIMIT ?,?';// LIMIT offset,pageSize
const results = await query(sql, [offset, pageSize]);//sql多个参数,用数组
}

连接池

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
//连接池  普通创建连接池的写法
// const pool = mysql.createPool(cfg);//创建连接池
// module.exports={
// query:function(sql, value){//不传回调,用promise的resolve和reject替代回调 返回promise, 在外部把之前要传的回调传给promise的resolve和reject
// return new Promise(function(resolve, reject){
// pool.getConnection((err, conn)=>{//获取连接
// conn.query(sql, value, (err, results)=>{
// if(err)
// reject(err); //err是promise的值
// else
// resolve(results);//results是promise的值
// });
// conn.release(); // 释放连接 不是end
// }) ;
//
// });
// }
// };


//连接池 精简版 pool去查询 等价于创建连接 连接去查询 和释放连接
const pool = mysql.createPool(cfg);//创建连接池
module.exports={
query:function(sql, value){//不传回调,用promise的resolve和reject替代回调 返回promise, 在外部把之前要传的回调传给promise的resolve和reject
return new Promise(function(resolve, reject){
//直接使用pool去查询
pool.query(sql, value,(err, results)=>{
if(err) reject(err);
else resolve(results);
})

});
}
};

sequelize

  • 安装: npm i sequelize mysql2 -S // mysql2 为mysql对应的驱动

  • orm,不用再写具体sql,语义化查询

  • 基本使用

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    //1 引入
    const Sequelize = require('sequelize');
    //2 实例化
    const sequelize = new Sequelize('kkb', 'kkb_admin', 'admin', {
    host: 'localhost',
    dialect: 'mysql', //方言
    pool: { max: 5, acquire: 30000, idle: 10000 }, //连接池
    timestamps: false
    });

    //3 定义模型
    const User = sequelize.define('user', { //模型名!, 字段名
    firstName: Sequelize.STRING, //更多设置使用对象
    lastName: Sequelize.STRING,
    age: Sequelize.INTEGER

    }, { //其他参数
    //freezeTableName:true //表名默认是模型名+s 冻结则不会
    });

    //4 同步数据库 force:true 强制更新 先删表
    // model.sync建表 model.create插入数据
    User.sync({ force: false }).then(() => { //返回的promise,直接then
    return User.create({ // 插入数据
    firstName: 'Tom',
    lastName: 'Cruise',
    age: 30
    })
    }).then(() => { // 查询
    //查询插入的数据 findAll 看执行的sql
    User.findAll().then(user => {
    console.log(user);
    })
    });
  • 模块化

    将每个模型单独存放一个文件,利用import整合,统一导出

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    //封装
    const fs = require('fs');
    const path = require('path');
    const db = [Sequelize, sequelize]; //导出类和实例对象, Sequelize为了定义数据类型
    //模型导入 除了db.js index.js 其他文件都是单个模型
    fs.readdirSync(__dirname)
    .filter(file => (file !== 'index.js' && file !== 'db.js'))
    .forEach(file => {
    const model = sequelize.import(path.join(__dirname, file));
    db[model.name] = model; // 增加到db
    });
    module.exports = db;
    -------------------------------------------------------------------------------
    // 使用
    //sequelize 模型使用案例,,动态分页
    const {OpenCourse} = require('../models');
    router.get('/bySeq', async (req, res, next)=>{
    try{
    const page = +req.query.page || 1;// 获取当前页码,如没有则默认1
    const size = +req.query.size || 3;// 每页条数
    //返回带总条数的对象 {rows: [], count}
    const results= await OpenCourse.findAndCountAll({ // 不再需要提供sql
    offset:(page-1)*size,//偏移量
    limit:size,//每页大小
    order:[['time','DESC']]//支持多字段排序
    });
    res.render('open-course', {
    title:'公开课',
    openCourses:results.rows,//查询结果时RowDataPacket对象数组
    pagination: getPagination(results.count, page, size)
    })
    }
    catch(err){
    console.log(err);
    next(err);
    }
    })

    事务

  • 连接池+事务 Promise封装

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
const pool = mysql.createPool(cfg);//创建连接池
module.exports={
query:function(sql, value){
return new Promise(function(resolve, reject){
pool.query(sql, value,(err, results)=>{
if(err) reject(err);
else resolve(results);
})

});
},
pool,
query2: function (conn, sql, value) { // 不使用连接池的查询,事务得明确得知道哪个连接
return new Promise((resolve, reject) => {
conn.query(sql, value, (err, results) => {
if (err) reject(err);
else resolve(results);
})
});
},
getConnection: function () {
return new Promise((resolve, reject) => {
pool.getConnection((err, conn) => { // 但获取连接 还是可以用pool获取连接, 事务的时候知道是哪个conn使用即可
if (err) reject(err);
else resolve(conn);
})
})
},
beginTransaction: function (conn) {
return new Promise((resolve, reject) => {
conn.beginTransaction(err => {
if (err) reject(err);
else resolve();
});
})
},
rollback: function (conn) {
return new Promise((resolve, reject) => {
conn.rollback(resolve);
conn.end();
});
},
commit: function (conn) {
return new Promise((resolve, reject) => {
conn.commit(err => {
if (err) reject(err);
else resolve();
});
conn.end();
});
}
};

跨域/restful

restful

  • get/post/put/delete/header/option
  • 简单请求/preflight
  • restful api : api是提供接口数据,不对页面进行渲染

跨域

  • 同源策略: 针对脚本
  • 解决方式:
    • jsonp(同源对script无效,传递callback search参数,返回callback(data)执行); ajax回调和jsonp不同,ajax是xhr根据状态监听执行回调
    • cors: Access-Control-Allow-Origin/Headers/Methods/Credentials

markdown图片配置

  • 场景: hexo博客中显示图片
  • 使用typro+Front Matter
  • 设置typro根目录为typora-root-url: ./,也可以在编辑-图片工具-设置根目录里设置后生成
  • 设置图片插入时复制到指定目录: 自定义 : ./images
  • 根目录在images上一级,便可以得到想要的图片路径: /images/xxx.jpg

二十三 Vue2.5实战模版语法组件化

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30

-

- **promise ==》 async await**

```javascript
//promise写法
axios.get('api/goods').then((response)=>{
console.log(response);
this.goods= response.data.list;
}).catch(()=>{

})
//console.log('我是请求后面的内容!'); //先执行这句, 后输出response



//改为async await 同步写法, 错误处理用普通的try catch
async created(){
try{
//console.log('开始请求');
const res = await axios.get('/api/goods')
//console.log(res);
this.goods = res.data.list;
}
catch(e){

}
//console.log('我是请求后面的内容!'); // 从上到下按顺序执行
}

二十四 Vue2.5实战UI组件库 定制组件

  • form form-item input组件的定制
  • props既可以是数组,也可以是对象: 猜测应该是通过获取属性时调用方式是一样的吧, props属性为单值时用数组,props属性为对象时,props用对象
  • provide既可以是函数,也可以是对象: 猜测函数时相当于同名字段属性(应该就是get属性吧,注意不是方法,方法都放在method中),要有返回值return 一个对象
  • props传递的数据不能修改,需要赋值给组件中的变量,然后是修改使用
  • 单向数据流: 通过props传递的值,得通过父元素修改,再传给自己
  • 自定义组件 实现v-model 需要实现两方面 , 组件中接受value, 组件修改值后派发input事件,修改传递的外部value ,, 具体看 k-input的实现
1
2
3
4
5
* 实现v-model: 
<!--
v-model 等价于
:value="valuetext" @input="valuetext=$event"
-->
  • provide inject 和props 对比

    1
    2
    3
    4
    5
    provide(){
    return {
    form: this // 给子级提供form实例 ,子级注入使用form实例; 体会provide inject和props的区别 , provide inject封装在组件内部,是一般底层开发控件时用到的,开发用到所以必须得提供给下级,而props是在外部,使用该控件时可选择性的输入属性,一个是开发必须,”偷偷摸摸“,一个是可选择,正大光明;一个面向底层实现,一个面向用户使用;; 如果是强制用props开发,会造成用户使用时得强制传入一个固定的输入属性,用于底层的实现,,这样用户体验不好,能底层默认实现的,就直接提供注入就好了
    }
    }

二十五 Vue2.5实战 Vuex单向数据流 Vue-router单页应用

  • Use history mode for router? (Requires proper server setup for index fallback in production)

    history部署时要和后台 index ??? todo ,, vue是前台控制路由,打包后只有一个index文件作为路口

  • 查看源码:比如 view-source:http://localhost:8080/dashboard/page1 ,发现无论哪个路由都是这一个文件,后台需要根据这个文件配置;;

  • routerLink和href

    • routerLink 会局部刷新,只刷新目标路由页面,url里不会刷新

    • href 会刷新整个页面,url会刷新,重新加载整个页面

      1
      2
      3
      4
      5
      6
           <div>
            <router-link to="/page1">页面1</router-link>|
            <router-link to="/page2">页面2</router-link>|
            <a href="/page1">href页面1</a>|
            <a href="/page2">href页面2</a>
          </div>
  • 路由异步加载: 打包时不经常访问的路由可以使用异步路由,打包的文件则不包含异步路由, 异步路由是在点击时异步加载;; 清除缓存,点击其他路由时不会有请求,因为一开始都加载了(第一次加载首页也有0.js啊?),, ,第一次点击登录时会加载0.js;;;;; 和require , script标签加载js 原理一致 .. ??

1571376664080

  • 数据处理: props(层层传递) ; provide/inject(子孙共享); this.$emit(组件内部派发,外部使用组件处事件监听); this.$bus.emit/on (组件间进行通信); vuex: 数据管理中心(storage/stage/mutation)
  • vue使用插件use, vue-router属于插件级别 ; vuex也是插件

    import VueRouter from ‘vue-router’ // 不加/ 是node_modules

    // use启用插件

    Vue.use(VueRouter); //this.$route use将$route挂载到了Vue原型上

  • 形参使用解构赋值

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    actions: { // 异步的mutations  数据的修改还是要调用mutations修改
    // incrementAsync(store) {
    // store.commit('increment');
    // }
    incrementAsync({commit}){ // 传参时可以形参可以直接解构出对应的变量
    commit('increment')
    }
    }

    注: 给incrementAsync传递参数为store对象,可以用一个形参store接收, 也可以用{commit}接收,,意思是实参是store对象,而执行的函数只要store对象中的commit,, 这样些要求store对象中有响应属性, 形参可以直接解构得到,, 使用时省些步骤
  • import/require 引入的文件, 如何调试

    • import Vuex from ‘vuex’ // es6 用import

      //const Vuex = require(‘vuex’); // 两者写法等价吗, 用require方式也正常执行,, es6之前和node用require, node为什么不用import?

    • 以 vuex mapState为例, 找到vuex,package.json中, 有一个main和moudle对应了两个脚本, 在这两个脚本的开头alert 一下,发现用的是esm.js , 因此想要调试的话找到方法console就行了,但是如何一步一步调试还不清楚。

    • 另外想调试mapState 转到定义到了 helpers.d.ts中,而实际mapState函数在esm.js中,, 待研究

      1571386987674

二十六 电商项目实战

  • ui框架:移动端/web端 选型;;; vue: cube element

    bootstrap过时,基于jquery,class方式使用,不是写标签;;; 响应式针对移动端,,,一般pc和移动要写两套代码

  • ui框架的使用: 先安装, 看文档快速上手, 在webpack中做配置(按需加载,自定义主题)

  • 启动项目编译报错 –fix; 在package.json中添加

    1
    2
    3
    4
    5
    "rules": {
    "indent": [1, 4]
    },

    // 不行 还是会报错 , 把package.json中的 "@vue/standard" 干掉
  • axios添加到全局

  • //main.js入口文件中添加到Vue原型之上
    import axios from 'axios'
    Vue.prototype.$axios = axios
    
    其他组件中使用: this.$axios
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31

    - 基本的权限校验: 路由守卫+**拦截器**

    - **拦截器**: 比如给所有的请求头加上token,所有响应返回处理之前根据状态码进行全局处理(比如: axios的响应结果是http对象,不单单是数据,包含了响应的各种参数,全局设置一个拦截器, 只将符合要求的data返回即可)

    - token

    - cookie有同源策略的限制,token没有,但需要自己处理实现

    # 二十七 电商项目实战

    - cube-ui 异步校验

    ```js
    rules:{ // 校验规则
    required: true,
    type: 'string', //对应的自定义校验信息写在messages,,
    min: 3,
    max: 15,
    // 自定义异步校验: 校验登录用户名是否存在
    // 异步校验的规则: 函数,接收一个resolve参数, 或者是一个promise
    // 意义: 就是返回给控件一个异步校验的函数,将异步校验的函数resolve出去即可,控件会调用异步校验函数,并得到校验结果,写入相关校验信息
    usercheck:(name)=>{
    return (resolve)=>{ // 带上new Promise 也可以,这样更省事
    //resolve(false); // 会一直不存在,这是同步校验
    this.$axios.get('/api/check?username='+name).then(res=>{
    resolve(res.code==0) ; // 0为true/为校验通过
    })
    }
    }
    },
  • this.$router 路由器 负责路由的跳转,,this.$route 路由 获取当前路由信息

  • 父组件直接使用子组件整个实例: ref

  • stylus css用法 ; scoped

二十八 电商项目实战

  • header组件: 定位样式 props / slot
  • v-if v-show hidden
  • 阻止冒泡和默认事件 stop prevent 直接写在事件名中 @click.stop.prevent=”addCart($event,item)”
  • 事件对象的传递 $event $event.target为dom对象

组件封装

文件调试

  • 报个错,然后定位到相关文件,加断点

  • 在webpack://下找 ./src , 加断点

1571716212400

el 和 mount 挂载组件区别

  • 自定义消息提示中 将Notice.vue挂载到了body上,先$mount(),然后再原生append
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
两者在使用效果上没有任何区别,都是为了将实例化后的vue挂载到指定的dom元素中。

如果在实例化vue的时候指定el,则该vue将会渲染在此el对应的dom中,反之,若没有指定el,则vue实例会处于一种“未挂载”的状态,此时可以通过$mount来手动执行挂载。

注:如果$mount没有提供参数,模板将被渲染为文档之外的的元素,并且你必须使用原生DOM API把它插入文档中。

例如:

var MyComponent = Vue.extend({
template: '<div>Hello!</div>'
})

// 创建并挂载到 #app (会替换 #app)
new MyComponent().$mount('#app')

// 同上
new MyComponent({ el: '#app' })

// 或者,在文档之外渲染并且随后挂载
var component = new MyComponent().$mount()
document.getElementById('app').appendChild(component.$el)




---脚手架生成 入口main.js中
new Vue({
el: '#app', //el和mount基本等价,el会自动挂载,mount可以选择何时手动挂载,mount不带参数,则需要原生appendChild插入到dom树中
router,
store,
render: h => h(App)
})
//.$mount('#app') // 把vue实例的dom对象($el)挂载到id=‘app’的节点处

二十九 VUE源码

30 React JSX 组件化 React开发模式 生命周期

  • 默认端口3000

  • react代码保存后自动格式化,一堆错误

    点击右下角的javaScript, 在弹出的选择框中, 输入选择JavascriptReact或者TypescriptReact

jsx的本质

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
jsx的写法  : babel左了转换
class HelloMessage extends React.Component {
render() {
return (
<div id='name' class='red'>
Hello {this.props.name} {this.props.age}
</div>
);
}
}

ReactDOM.render(
<HelloMessage name="Taylor" />,
document.getElementById('hello-example')
);
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
非jsx的写法
class HelloMessage extends React.Component {
render() {
return React.createElement(
'div', // 标签
{ id: 'name', 'class': 'red' }, // 属性
'Hello ', // 内容
this.props.name,
' ',
this.props.age
);
}
}

ReactDOM.render(React.createElement(HelloMessage, { name: 'Taylor' }), document.getElementById('hello-example'));
  • 前端框架使用成员好像一律都用this, 不存在之前c#那样可以直接成员变量的
  • 在构造函数中初始化数据成员白能量,类外部没有数据成员变量,可以有函数成员变量,,es6 class应该就是这样, ts中好像可以先声明成员变量,构造函数中初始化 todo

react双向数据绑定及事件this的写法

  • react是单向数据流,需要实现onChange事件
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
render() {  // {}类似vue的{{}}
return <div>
<input type="text" value={this.state.text} onChange={
(e)=>{
// console.log(e)
// this.setState({
// text:e.target.value // 手动双向数据绑定 ,,MV是默认的,需要实现VM
// })
this.vmHandler(e) // 内层的话 必须传递e,, 自定义参数时也可以使用嵌套函数的方式传递参数,外层的参数是事件调用时默认会传递event
}

//this.vmHandler2 // 方式二


}/>

</div>
}



vmHandler(e){
// console.log(e);
// console.log(this)
this.setState({
text:e.target.value // 手动双向数据绑定 ,,MV是默认的,需要实现VM
})
}


vmHandler2=(e)=>{ // 如果写e,会获取到event 方式二
console.log(e);
// console.log(this)
this.setState({
text:e.target.value // 手动双向数据绑定 ,,MV是默认的,需要实现VM
})
}
  • react里this用的很多,模板和脚本在一起,模板里的变量也得带this,, 事件的话最好用()=>{this.method},箭头函数的this就是组件,传递event的话,加上(e)=>{}即可。

    • 其他调用方式: 方法简单的话,可以直接在render里写方法体

      方式二: 事件{}中 直接写方法名,此时该方法是别人调用的,this不代表当前组件了, 但方法中使用this时,方法也要用箭头函数保留this指向

      分析:如果是放在了箭头函数中 调用this.method(),则method中的this肯定就是定义时的上下文this,代表当前组件, 否则的话需要注意method函数体中的this指向; 事件绑定机制的处理造成了event的传递,this的问题; 暂时就用箭头函数把 需要使用定义时的this 上下文保留; 之前vue模板中绑定的数据变量/事件中的变量都不用写this,,应该是框架处理机制的不同,vue只是解析得到方法名或变量名,不需要this, react render里是要后面直接按变量用的

      方式三: 在构造函数中绑定函数的this指向,因为肯定先经过构造函数,通过bind绑定的函数的this指向不再变

      this.vmHandler = this.vmHandler .bind(this),, 此种方式也可以在onChange中直接用bind(this),不过那样每次render都要bind,性能不好

    • 注意react事件里,不要写函数执行的方式,,那样会执行再赋值,事件是无效的,这和html中好像不一样

  • react标签没有子元素,可以用单标签

  • 单向数据流的优点:: 防止bug,, js中有些数据是会互相影响的(引用类型?),,每次修改数据都setState, 都使用新的数据,不会有这种问题

函数组件

  • 适用于局部 返回一个简单的小片段组件
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
// 函数组件: 如果一个组件只是根据props进行渲染,没有内部的state,可以用函数形式代替(hook 函数发展方向)

app.js
export function Title({title}){ // 参数解构赋值
return <div>
<h2>{title}</h2>
<hr/>
</div>
}


index.js
ReactDom.render( // render 中组件的标签名起始大写
<Title title='我是函数组件'></Title>, // 函数组件,仅依赖props,不需要state时可以使用
document.querySelector('#root')
)


-------------------------------
cart.js 总价
// 函数组件: 返回商品总价, react没有计算属性,直接把计算逻辑写在模板里,,类似的小片段可用函数组件实现,,参数接收的是props
function Total({cart}){
return <span>总价:{cart.reduce((sum,a)=>{
return sum+=a.price * a.count;
}, 0)}</span>
}

<tfoot>
<Total cart={this.props.data}></Total>
</tfoot>

31 React JSX 组件化 React开发模式 生命周期

按需加载插件

组件拆分 ,嵌套

  • 组件可以按行为和展示进行拆分,, 组件render时render另外一个组件,组件按组件类名作为标签进行使用; 解耦/复用/易于测试
  • 体会一下本质,, 递归实现
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
jsx语法
class HelloMessage extends React.Component {
render() {
return (
<Hello name="ccb"></Hello>
);
}
}
class Hello extends React.Component {
render() {
return (
<div>
Hello {this.props.name}
</div>
);
}
}

ReactDOM.render(
<HelloMessage name="Taylor" />,
document.getElementById('hello-example')
);
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
非jsx语法

class HelloMessage extends React.Component {
render() {
return React.createElement(Hello, { name: "ccb" }); //发现Hello是组件标签,则会按Hello的render去React.createElement,,递归
}
}
class Hello extends React.Component {
render() {
return React.createElement(
"div",
null,
"Hello ",
this.props.name
);
}
}

ReactDOM.render(React.createElement(HelloMessage, { name: "Taylor" }), document.getElementById('hello-example'));

react三点运算符

  • <Component {…props}/>
    • react中的语法糖 等价于 ;;
    • es6中 {…props} 等价于复制一份props, 而且数据不会相互影响,, 常用于给对象增加属性, 如 var obj = {…props, ‘foo’:’bar’} ,, 这里的… 展开的效果是 key:value的形式
    • … 运算符一般是用于数组较多,,还有一种用于形参(叫剩余运算符)
  • <Component {…this.props}/>
    • 这个是用于类组件中

hook

32课 Redux单向数据流 React-router4单页应用

  • 上节作业 createContext的实现(见该节pdf),体会下闭包

箭头函数return的简写形式

1
2
3
4
5
6
7
 一般写法 ()=>{},,参数只有一个时可以省略(), 函数体只有一句话时可以省略{},且默认是return的
const mapStatetoProps = state=>{
return {num: state} // {}中要明确写上是否return,不会默认return
}

//可以简写为: 当return的是{}包裹的对象时,加上()代表一个整体 return
const mapStatetoPropsstate=>({num: state}),,因为后面直接return数据时可以不用{},但是return的是对象,所以用()包裹,不用写return

vuex redux 修改数据对比(单向数据流)

  • 单向数据流: 修改数据时是一份新的数据,比如 给state 增加name属性redux的写法 {…state, name:’ccb’},相当于复制原来的state给新的state再添加属性,

    • {…state, name:’ccb’} 相当于 Object.assign({},state,{name:’ccb’})
  • vuex的写法的就state.name=‘ccb’ // state还是原来的state

redux-logger

  • F12会记录每次dispatch的action

路由

  • HashRouter和BrowserRouter 相当于vue是否使用model:history
  • 最后半小时原理 todo

33 Redux单向数据流 React-router4单页应用

umi

  • 单页应用

  • 安装后,package.json启动脚本修改为umi

    1
    2
    3
    4
    5
    "scripts": {
    "start": "umi dev",
    "build": "umi build",
    "test": "echo \"Error: no test specified\" && exit 1"
    },
  • antd layout this.props.children

    • 联系render-props对比下
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    class KLayout extends React.Component{
    render(){
    return <Layout>
    <Sider>
    侧边栏
    </Sider>
    <Layout>
    <Header>头部信息</Header>
    <Content>
    {this.props.children} // 外界使用Klayout,Klayout中的节点,相当于vue中的slot,,layout是最外层的路由
    </Content>
    <Footer>{this.props.children}</Footer>
    </Layout>

    </Layout>
    }
    }
  • umi 命令创建页面

    npm i -g umi 需要全局安装

    项目路径下 npm g page login 自动生成login.js 和login.css

  • 支持less ,,sass需要loader ?

dva

  • model中多个数据用命名空间区分 todo
  • dva connect 会默认提供dispatch , this.props.dispatch,, 数据的话可以自己映射dva默认映射dispatch到props, 直接使用dispatch调用想用namespace/reducer,,不需要单独映射

34 react 项目实战

  • component文件夹放可以复用的组件, pages放的是业务页面, layout是布局

    和vue中component和views文件夹的意义差不多

  • 进度条npm install nprogress –save

  • 手写promise vue原理 vue组件设计 react 原理 react组件设计 redux原理 vue/react区别

35 react 项目实战

  • 拦截器 全局文件执行
  • 多model
  • 异步effects generator
  • umi/router js路由跳转 router.push
  • 进度条设置在拦截器中 请求时开始和响应时结束,, 有个微交互的过程
  • ant design pro === vue admin 适合做cms
  • ant design pro exception处理
  • ant pro tag 栅格系统 flex布局 card
  • 作业: 主页默认加载全部商品:使用 componentWillReceiveProps,,组件dispatch后,props会传递变更的数据

36 react 项目实战

  • antd Skeleton 骨架屏
  • react请求数据一般放componentDidMount里, 加上初次render, 要render两次
  • KHeader: antd-pro noticeIcon购物车
  • 登录状态的逻辑, 某些业务逻辑需要登录的状态下, 比如购物时需要校验token, 未登录跳转到登录页或显示登录按钮,已经登录显示退出按钮等;; 首页可以不用登录, 但购买时需要登录
  • react 动画
    • npm i react-motion –save 跨平台 推荐;; about组件中 演示
    • react-tran’sition-group
  • css属性如fontSize html style中用驼峰

37 源码

  • vscode调试源码,,精简版源码
  • createelement domdiff
  • redux原理 react-redux原理 单向数据流
  • hook 函数组件管理state
  • dva router原理

写在前面

typora几个命令

安装

https://jingyan.baidu.com/article/48b37f8dd141b41a646488bc.html 包含了环境变量的设置

系统变量相关

##查看环境变量

path

npm config ls -l

默认环境变量

nodejs安装后的全局路径默认为(自动添加进了Path用户系统变量):C:\Users\ccb\AppData\Roaming\npm

修改nodejs缓存目录和全局目录(区别全局安装和本地安装两个路径)

全局安装和本地安装的区别:https://www.jianshu.com/p/e839326ce30d

npm config set prefix “D:\ProgramFiles\nodejs\node_global” 最终模块配置位置,注意在这个就下会再次创建一个node_modules 文件夹(这个属于全局安装路径,类似全局环境变量),而安装目录原有nodejs\node_modules 这个文件夹属于本地安装,程序内相对路径,只对整个程序中放在该路径下的模块起作用。

npm config set cache “D:\Program Files\nodejs\node_cache” 存放缓存文件的位置

设置和修改环境变量

修改系统变量:计算机 高级 编辑或新建系统变量,有命令可以设置路径变量(todo)

NODE_PATH :D:\Program Files\nodejs\node_modules ;npm的环境变量,个人理解应该是给使用模块定义了系统变量,寻找本地安装模块时使用(局部安装?)

同样,修改系统变量Path中 原来的C:\Users\ccb\AppData\Roaming\npm 为:D:\ProgramFiles\nodejs\node_global 个人理解这里应该对应的是全局系统变量,调用全局安装模块时用到

node命令

命令行下输入node
输入 require(‘cluster’) 执行,能够正常输出 环境配置完成; cluster是通过 npm install装的一个模块进行的试验

命令相关

node 进入node

require(‘moudulename’) 引入模块

npm install npm安装命令 -g代表全局安装

npm i bootstrap@3 @ 指定版本

  • node 中 ctrl 悬停变量,点击会转到定义
  • 修改后自动启动服务器:npm i -g node-dev (nodejs作为服务器,js文件修改后不会自动生效,html修改后会自动生效),这个命令全局安装,只要文件修改,crtl s后会自动重启服务器,之前的启动命令是npm start ,其实最终是node ./bin/www,因此修改package.json中的start,即可修改为node-dev执行

hbs语法

  1. Handlebars
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    > 插值绑定 {{prop}}
    >
    > 注释 {{!content}}
    >
    > HTML内容 {{{htmlStr}}} ,不对内容进行转义,原模原样的输出,则最终按html解析
    >
    > 条件语句 {{#if condition}}...{{/if}}
    >
    > ** condition只能是布尔值或者可以转换为布尔值的值,他不能是表达式
    >
    > ** 可以结合{{else if condition}}、{{else}}使用
    >
    > 循环语句 {{#each arr}}....{{/each}}
    >
    > ** each可嵌套
    >
    > ** 使用this或者.表示上下文,常用语数据是值的情况
    >
    > ** 使用@index,@key
    >
    > ** 遍历对象 @key
    >
    > ** 结合{{else}},当数组为空时显示特别信息
    >
    > - {{with 变量名}} {{/with}} 限制当前属性为变量的,省略.的写法

    webstorm

  • ctrl + alt + l 对齐代码

  • CTRL k 清空控制台

  • 鼠标中键或ctrl+单击 到定义

  • 别人给的项目 先用npm -i 安装一下依赖(根据packagejson)

  • iter  回车  自动生成  for of 循环
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16

    - script:src tab键

    - alt+enter 快速import

    # node连接数据库



    - 安装mysql 运行时依赖:npm i mysql -S

    - 引入mysql 设置连接

    - 查询 query(sql, value, function) sql语句 参数 回调

    -
    //连接数据库测试 const mysql=require('mysql'); const conn =mysql.createConnection({ host:'localhost', user:'kkb_admin', password:'admin', database:'kkb' }); conn.connect();//可省略 conn.query('select 1+1 as solution', (err,data)=>{ if(err) throw err; console.log(data);//[ RowDataPacket { solution: 2 } ] });
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39

    # 异步asycn await

    // 2s 之后返回双倍的值
    function doubleAfter2seconds(num) {
    ​ return new Promise((resolve, reject) => {
    ​ setTimeout(() => {
    ​ resolve(2 * num)
    ​ }, 2000);
    ​ } )
    }

    async function testResult() {
    ​ let first = await doubleAfter2seconds(30);
    ​ console.log('after first');
    ​ let second = await doubleAfter2seconds(50);
    ​ console.log('after second');
    ​ let third = await doubleAfter2seconds(30);
    ​ console.log('after third');
    ​ console.log(first + second + third);
    }

    testResult();//里面只解决异步的回调需要嵌套的情况,和此并行的其他代码放到后面
    console.log('after testResult');//因为testResult异步,所以一边去执行testResult,一边继续往下执行到这,所以会先输出after testResult

    结果:
    after testResult
    after first
    after second
    after third
    220



    # 文件操作

    - 读取当前文件夹下所有文件夹名,根据文件名过过滤 filter和foreach是数组方法

    -
    fs.readdirSync(__dirname) .filter(file=>(file!=='index.js'&&file!=='db.js')) .forEach(file=>{ const model= sequelize.import(path.join(__dirname, file)); db[model.name]=model; });
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10

    - 文件上传

    - npm i multer -S

    # git

    - 官网中 然后直接搜索相关的库 比如handlebars

    # node路由传参
    // //路由传参方式1 url参数占位符 :course,代表基路由http://localhost:3000/vip-course/后的第一个值 // //router会将占位符的参数存储到req.params中 // console.log(req.params);//服务器后台输出url参数 { course: 'web' } // //传参方式二:url请求参 // console.log(req.query);//{ name: '\'abc\'', age: '\'20\'' } http://localhost:3000/vip-course/abc?name=%27abc%27&age=%2720%27 url中单引号自动被编码为%27 // //传参方式三:body请求体传参 console.log(req.body);//{ name: 'ccb', sex: '男', age: '20' } res.send('收到了post请求:'+'name:'+req.body.name+' age:'+req.body.age+' sex:'+req.body.sex);


# restful api

- 新建http文件  手写  执行






hexo静态博客

博客搭建部署hexo+github.io

安装初始化

1
2
3
npm install -g hexo-cli
hexo init blog
cd blog

创建新博客

1
hexo new title

监视生成静态资源

  • 修改完成前监听,保存自动更新静态资源
    1
    hexo generate -w

启动

  • 另起一个终端,开启服务,一边监视生成静态资源,一边loalhost:4000查看效果
    1
    2
    hexo server
    访问localhost:4000

    部署到git

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    配置_config.yml:
    deploy:
    type: 'git'
    repo: git@github.com:alittlegit/alittlegit.github.io.git
    安装部署工具:
    npm install hexo-deployer-git --save
    执行
    hexo deploy // 不更新则使用下面的命令
    hexo deploy --generate

    hexo generate --deploy

更换主题

  • github搜索hexo theme
    1
    2
    3
    4
    cd source/themes
    git clone https://github.com/theme-next/hexo-theme-next themes/next
    更改配置文件:
    theme: next

hexo-next使用

设置分类

  • next主题, _config.yml中开启相关设置
    1
    2
    3
    4
    5
    6
    7
    8
    9
    menu:
    home: / || home
    #about: /about/ || user
    #tags: /tags/ || tags
    categories: /categories/ || th
    archives: /archives/ || archive
    #schedule: /schedule/ || calendar
    #sitemap: /sitemap.xml || sitemap
    #commonweal: /404/ || heartbeat
  • 按文档说明添加分类页面
    1
    2
    3
    hexo new page categories
    设置type类型
    type: "categories"


markdown图片配置

  • 使用typro+Front Matter
  • 设置typro根目录为typro-root-url: ./,也可以在编辑-图片工具-设置根目录里设置后生成
  • 设置图片插入时复制到指定目录: 自定义 : ./images
  • 根目录在images上一级,便可以得到想要的图片路径: /images/xxx.jpg

博客部署hexo+服务器(非docker和docker)

github+git静态博客

hexo静态博客部署到服务器:hexo+node+webhook

hexo静态博客部署到服务器:hexo+node(docker)+webhook

workflow

  • 本地hexo更新文章==>hexo deploy上传git,更新静态资源==>alittlegit.github.io 文章更新
  • 本地hexo更新文章==>hexo deploy上传git,更新静态资源==>webhook==>服务器git pull==>http://onepiece.suprise.top/ 文章更新
  • 本地hexo更新文章==>hexo deploy上传git,更新静态资源==>webhook==>服务器git pull+”resbulid docker”==>http://onepiece.suprise.top/ 文章更新

其他

  • 服务器/docker/webhook分别使用一个端口,nginx转发,webhook是设置在项目的setting中的
  • 注意docker build的路径,可以指定context,ADD/COPY 文件不能超出context

vscode中使用deploy上传服务器

  • 下载插件

    1576317902008

  • 新建.vscode文件夹, 新建settings.json

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
// settings.json,,json中不能写注释  这里只是为了记录
{
"deploy": {
"packages": [{
"files": [
"**/*",
],

"exclude": [ //不包含的文件
"node_modules/**",
".git/**",
".vscode/**",
"**/node_modules/**",
],
"deployOnSave": false //不自动上传,右键某个文件或文件夹上传,服务器上检查最终确认,相当于scp
}],
"targets": [{
"type": "sftp",
"name": "AliyunServer",
"dir": "/source/deploy/test",
"host": "101.132.98.248", //设置上传服务器
"port": 22,
"user": "root",
"privateKey": "C:/Users/ccb/.ssh/id_rsa"// ssh 密钥
}],
},
}
  • 右键deploy上传

    1576318564149

vscode使用git

  • vs中的git是本地仓库的管理

  • 新建项目后,还是需要使用git clone或git init(需要与远程仓库关联)进行初始化

  • 新建.gitignore文件,设置不需要git托管的文件,如node_modules

  • 文件修改后可以使用vs集成的git图形化按钮,也可以在终端使用git命令

  • packagejson中的git仓库配置是程序读取时用到的,不是说就可以直接git push/pull了,如果不是git clone的仓库还是需要关联

  • vs git界面

    • 侧边栏第三个按钮是git管理面板,更改==》git add==>暂存更改==》git commit(对勾),,还有一系列下拉项,,当然直接使用命令也可以

    1576318419291

Welcome to Hexo! This is your very first post. Check documentation for more info. If you get any problems when using Hexo, you can find the answer in troubleshooting or you can ask me on GitHub.

Quick Start

Create a new post

1
$ hexo new "My New Post"

More info: Writing

Run server

1
$ hexo server

More info: Server

Generate static files

1
$ hexo generate

More info: Generating

Deploy to remote sites

1
$ hexo deploy

More info: Deployment