我的博客-后台管理系统 & 前台系统 & api系统 开发记录 未完 时刻更新中

喜大普奔,开源地址,请猛戳我html

1 登陆

登陆常见验证方式是token验证, 目前打算暂时用session验证   之后有时间研究token登陆,并添加第三方登陆

2 css变量的处理

//TODO: 一键换肤,白天黑夜模式 先占坑
颜色变量不要写死在单独某一个组件里面,方便之后统一换风格,能够参考element 等ui库

3 数据接口处理

a 首先全部的接口地址写在一个文件中,,切url接口地址最好不要写死,这样有两个好处
第一  方便查找咱们全部用到的接口
第二 后台接口便于修改,好比初期阶段后台接口可能较少,访问直接以 '/login' '/entry' 这样的形式访问,等之后台想把某一部分用户登陆相关的接口 访问时统一在前面加上’/v1' 若是咱们接口调用散落在各个文件里那就很是难以修改

b 利用 NODE_ENV 区分 当前的 baseUrl     当前前端webpackdevserver地址为 127.0.0.1:8080
例如 开发环境时个人 baseUrl 为 ‘’,全部接口的访问的为当前ip当前接口,模拟数据能够用 axios-mock-adapter  (与axios配合简单方便) --- 这是模拟数据方式一
这种数据访问地址为 127.0.0.1:8080  ->    127.0.0.1:8080

例如 我想用json-server,或者express 本身起一个server 作模拟数据服务器,地址为127.0.0.1:3005 开发环境时个人 baseUrl 则就改成 ‘127.0.0.1:3005 ’,
这种数据访问地址为 127.0.0.1:8080 -> 127.0.0.1:3005 存在跨域
因此须要在webpack-dev-config.js中加入前端

proxyTable: {
  '/': {
    target: 'http://127.0.0.1:3005',

  }
}
也就是将因此8080的访问 代理到3005上
小总结
so~ axios-mock-adapter  优势 使用简单 不存在跨域   缺点 不能动态根据前端传递参数不一样返回不一样结果,本身起的模拟服务器固然你本身想怎么干就怎么干了,上天都没人管你~

c 利用axios的全局配置功能,
添加baseurl, -- 能够方便修改根路径
添加请求、响应拦截器  首先这是符合 统一入口思想的,very good~~~   如此以来咱们能够对全部的请求统一处理 好比打印请求日志,过滤关键key,等等   对统一返回也能够 根据某些错误码进行全局warn处理

4一个逗号引起的血案~

错误很清楚的写明了 在setAttribute 时候 key 填入的是逗号,通过纠结的一一对比最终发现这个问题是 在元素的属性上 有逗号忘记了删除

你一眼就能看到吗? 若是能看到 恭喜你 ,不用(像我同样)再走弯路~

引起缘由是当初参照的其余template模板用的是 pug语法,其中每一个属性是用逗号隔开的
而咱们这里是html语法 都好必定要删除,不然 vue会当作属性填充,切记切记

5再看父子组件的传值(这一条以前理解稍微有误,能够跳过)

咱们都知道经过props能够由父组件给子组件传递值vue

<div>
  <input v-model="parentMsg">
  <br>
  <child v-bind:my-message="parentMsg"></child>
</div>

如下理解有误,留做历史吧,你们能够不看node

须要注意的是以上只适合传输字符串 而不能传输对象!webpack

须要注意的是以上只适合传输字符串 而不能传输对象!ios

若是你想把一个对象的全部属性做为 prop 进行传递,可使用不带任何参数的 v-bind (即用 v-bind 而不是 v-bind:prop-name)。例如,已知一个 todo 对象:css3

todo: {
  text: 'Learn Vue',
  isComplete: false
}

而后:nginx

<todo-item v-bind="todo"></todo-item>

将等价于:git

<todo-item
  v-bind:text="todo.text"
  v-bind:is-complete="todo.isComplete"
></todo-item>
>

子类接受属性时:

props: ['text', 'is-complete']

阔是!若是父组件data里面有一个目录属性 是数组结构

data () {
    return {
        cagalogs: [
        {},
        {},
        {}
        ]
    }
}

此时,咱们将没法经过父组件传递数据过去,

出现这种状况的场景是,管理系统 博客展现页面 展现了有多少个目录,已经获取了一遍目录数据, 在新增文章时候还会弹出编辑组件,此时让须要目录数据

这种状况下的解决方案:

  • 经过 props -----------> 走不通
  • 经过 vuex --------------> 不推荐,由于只是两个组件使用,不想都放到全局,避免常驻内存
  • 存放到 localstorage + mixin --------> 暂时采用此方案

mixin.js

import urls from '@/config/urls
import {storageKey, setStorage, getStorage} from '@/utils/storage'
export default {
  data () {
    return {
      catalogs: []
    }
  },
  methods: {
    async getCatalogs () {
      let catalogs = getStorage(storageKey.CATALOGS)
      if (catalogs) {
        this.catalogs = catalogs
        return
      }
      const res = await this.axios.get(urls.catalogList)
      if (res.data.status === 0) {
        setStorage(storageKey.CATALOGS, res.data.data)
        this.catalogs = res.data.data
      }
    }
  }
}

虽然最终数据都是取的 localstorage 下的 数据 , 但 两个组件其实是 本身维护了本身的 catalogs 这个 属性。

前台博客记录

如何找到其余网站的源码实现

主界面封面部分参考了 搜车大无线团队博客,其中有一个功能 封面 有一个下箭头,点击一下 实现滚屏到博客正文


直观发现此处是经过href锚点实现滚动,然而本身实现时发现这样并无滚动动画,可是他们是怎么作到的呢,开始我一直觉得确定是用了某个css3动画实现,可是根本找不到任何css相关的样式



如何肯定是否是用js实现的呢?



如图点击js即可跳转到相应js文件,并发现了用animate的地方,由此咱们得出了原来此处是用js,根本和css没有半毛线关系~


如何找到其余网站的源码实现2


不巧的是 博主还算是一个发(没)散(头)思(没)维(闹)的人,又去了饿了么 研究一下 回到顶部功能是如何实现的。



此次咱们轻车熟路的就找到了源码,只是不幸此次点进去是vue的源码,因此并无什么卵用, 咱们 发现 这个div 的类名 为 page-component-up 嗯,咱们有理由相信 源码是经过给这个类名添加点击事件实现的,(谁给你的自信?)

so~ ,咱们从github 下载element 源码, vscode 打开,全文搜索(ctrl shift f),这个类名, 在 component.tpl 文件下找到了源码 欧耶~

<style>
  .page-component__scroll {
    height: calc(100% - 80px);
    margin-top: 80px;

    .el-scrollbar__wrap {
      overflow-x: auto;
    }
  }

  .page-component {
    box-sizing: border-box;
    height: 100%;
  
    &.page-container {
      padding: 0;
    }

    .page-component__nav {
      width: 240px;
      position: fixed;
      top: 0;
      bottom: 0;
      margin-top: 80px;
      transition: padding-top .3s;

      .el-scrollbar__wrap {
        height: 100%;
      }

      &.is-extended {
        padding-top: 0;
      }
    }

    .side-nav {
      height: 100%;
      padding-top: 50px;
      padding-bottom: 50px;
      padding-right: 0;

      & > ul {
        padding-bottom: 50px;
      }
    }

    .page-component__content {
      padding-left: 270px;
      padding-bottom: 100px;
      box-sizing: border-box;
    }

    .content {
      padding-top: 50px;

      > {
        h3 {
          margin: 55px 0 20px;
        }

        table {
          border-collapse: collapse;
          width: 100%;
          background-color: #fff;
          font-size: 14px;
          margin-bottom: 45px;
          line-height: 1.5em;

          strong {
            font-weight: normal;
          }

          td, th {
            border-bottom: 1px solid #d8d8d8;
            padding: 15px;
            max-width: 250px;
          }

          th {
            text-align: left;
            white-space: nowrap;
            color: #666;
            font-weight: normal;
          }

          td {
            color: #333;
          }

          th:first-child, td:first-child {
            padding-left: 10px;
          }
        }

        ul:not(.timeline) {
          margin: 10px 0;
          padding: 0 0 0 20px;
          font-size: 14px;
          color: #5e6d82;
          line-height: 2em;
        }
      }
    }

    .page-component-up {
      background-color: #fff;
      position: fixed;
      right: 100px;
      bottom: 150px;
      size: 40px;
      border-radius: 20px;
      cursor: pointer;
      transition: .3s;
      box-shadow: 0 0 6px rgba(0,0,0, .12);

      i {
        color: #409EFF;
        display: block;
        line-height: 40px;
        text-align: center;
        font-size: 18px;
      }

      &.hover {
        opacity: 1;
      }
    }
    .back-top-fade-enter,
    .back-top-fade-leave-active {
      transform: translateY(-30px);
      opacity: 0;
    }
  }

  @media (max-width: 768px) {
    .page-component {
      .page-component__nav {
        width: 100%;
        position: static;
        margin-top: 0;
      }
      .side-nav {
        padding-top: 0;
        padding-left: 50px;
      }
      .page-component__content {
        padding-left: 10px;
        padding-right: 10px;
      }
      .content {
        padding-top: 0;
      }
      .content > table {
        overflow: auto;
        display: block;
      }
      .page-component-up {
        display: none;
      }
    }
  }
</style>
<template>
  <el-scrollbar class="page-component__scroll" ref="componentScrollBar">
  <div class="page-container page-component">
    <el-scrollbar class="page-component__nav">
      <side-nav :data="navsData[lang]" :base="`/${ lang }/component`"></side-nav>
    </el-scrollbar>
    <div class="page-component__content">
      <router-view class="content"></router-view>
      <footer-nav></footer-nav>
    </div>
    <transition name="back-top-fade">
      <div
        class="page-component-up"
        :class="{ 'hover': hover }"
        v-show="showBackToTop"
        @mouseenter="hover = true"
        @mouseleave="hover = false"
        @click="toTop">
        <i class="el-icon-caret-top"></i>
      </div>
    </transition>
  </div>
  </el-scrollbar>
</template>
<script>
  import bus from '../../bus';
  import navsData from '../../nav.config.json';
  import throttle from 'throttle-debounce/throttle';

  export default {
    data() {
      return {
        lang: this.$route.meta.lang,
        navsData,
        hover: false,
        showBackToTop: false,
        scrollTop: 0,
        showHeader: true,
        componentScrollBar: null,
        componentScrollBoxElement: null
      };
    },
    watch: {
      '$route.path'() {
        // 触发伪滚动条更新
        this.componentScrollBox.scrollTop = 0;
        this.$nextTick(() => {
          this.componentScrollBar.update();
        });
      }
    },
    methods: {
      renderAnchorHref() {
        if (/changelog/g.test(location.href)) return;
        const anchors = document.querySelectorAll('h2 a,h3 a');
        const basePath = location.href.split('#').splice(0, 2).join('#');

        [].slice.call(anchors).forEach(a => {
          const href = a.getAttribute('href');
          a.href = basePath + href;
        });
      },

      goAnchor() {
        if (location.href.match(/#/g).length > 1) {
          const anchor = location.href.match(/#[^#]+$/g);
          if (!anchor) return;
          const elm = document.querySelector(anchor[0]);
          if (!elm) return;

          setTimeout(_ => {
            this.componentScrollBox.scrollTop = elm.offsetTop;
          }, 50);
        }
      },
      toTop() {
        this.hover = false;
        this.showBackToTop = false;
        this.componentScrollBox.scrollTop = 0;
      },

      handleScroll() {
        const scrollTop = this.componentScrollBox.scrollTop;
        this.showBackToTop = scrollTop >= 0.5 * document.body.clientHeight;
        if (this.showHeader !== this.scrollTop > scrollTop) {
          this.showHeader = this.scrollTop > scrollTop;
        }
        if (scrollTop === 0) {
          this.showHeader = true;
        }
        if (!this.navFaded) {
          bus.$emit('fadeNav');
        }
        this.scrollTop = scrollTop;
      }
    },
    created() {
      bus.$on('navFade', val => {
        this.navFaded = val;
      });
      window.addEventListener('hashchange', () => {
        if (location.href.match(/#/g).length < 2) {
          document.documentElement.scrollTop = document.body.scrollTop = 0;
          this.renderAnchorHref();
        } else {
          this.goAnchor();
        }
      });
    },
    mounted() {
      this.componentScrollBar = this.$refs.componentScrollBar;
      this.componentScrollBox = this.componentScrollBar.$el.querySelector('.el-scrollbar__wrap');
      this.throttledScrollHandler = throttle(300, this.handleScroll);
      this.componentScrollBox.addEventListener('scroll', this.throttledScrollHandler);
      this.renderAnchorHref();
      this.goAnchor();
      document.body.classList.add('is-component');
    },
    destroyed() {
      document.body.classList.remove('is-component');
    },
    beforeDestroy() {
      this.componentScrollBox.removeEventListener('scroll', this.throttledScrollHandler);
    }
  };
</script>

后台 node api 系统

1如何使用es6语法

1 入口文件引入bable-core

2 在.babelrc 以下配置,经本人测试,stage-3 若是不配置 则 【扩展运算符】使用会报错

3 安装相关依赖

2继承使用问题 this问题

在类中 若是有须要用到内部this 的方法中  须要 在 constructor 中 经过bind 应绑定 this,
由于这些类的方法的调用形式为 以下图二调用,所以 若想用this,须要在constructure中 进行 硬绑定

图一,control对象声明

图二 路由调用 相应的control对象的方法的引用

3经过 vscode 进行断点调试

网上最多见的三种查询方法

1 node-inspector 以前尝试过 好像最终能够chrome 进行断点, 可是仍是偶尔失败,且麻烦 因此 舍弃

2 好像还能够经过 --xxx 加相似什么参数来着, 可是也没成功 因此 舍弃

3 webstorm 仍是算了吧……

4 咱们说一下 用vscode 调试

默认状况下直接按 f5 就会呼出调试界面,直接选择node 便可,也可经过配置,默认状况下 入口是 app.js
咱们根据须要修改便可。

4给 mongoose find 命令 返回的 数据 添加额外属性的两种方式

mongoose find 命令 返回的 数据结构如图

若是咱们想在find命令后返回的对象里面添加其定义属性,

好比 动态的给每个对象添加一个 uid属性, 咱们直接给对象添加是无效的,

即便当时你手动添加上打印出来能够看到,可是返回到客户端 却没有这个属性

由于mongoose内部会检查你要添加的这个属性是不是在scheme上,

一说能够经过 strict: false 让查询出的结果可修改,不过测试发现没有什么卵用

愿意是 mongoose 返回的 对象 其实实在 当前对象的 _doc 属性 下面

方式一 粗暴添加

因此 咱们能够 经过 给对对象的_doc属性下的对象添加自定义属性便可

方式二 温柔添加

mongoose 提供啦 toObject()方法 也能够添加

最终的代码相似于:

var model = obj.toObject();
 model.isBorrow = false;
cb(null, model);

5 循环+异步引发的

场景描述

查询文章列表接口
须要返回的数据格式以下

{
                "id": "0920892e-1512-401a-994e-5406a14aca0b",
                "title": "Vue2 + Nodejs  + WebSocket 完成你画我猜多人在线游戏",
                "summary": "使用 websocket + vue2 便可完成一个颇有意思的在线游戏做品。 你画我猜,相信你们对这个游戏都很熟悉。 我用Vue2 + mint-ui + nodejs + websocket 实现了你画我猜这个游戏。 建议移动端打开效果更佳(可扫下方二维码),PC端须要使用谷歌开发者模式,而后使用移动调试工具,才能够正常使用(主要是一些touch事件,pc不支持)。  你们能够拉上一两我的,来开个房间试试看,体验体验效果。 http://yd.diamondfsd.com   主要实现了如下这些功能  大厅功能  我的信息显示 顶部显示我的昵称,能够修改 暂时不支持上传头像,头像用昵称第一个",
                "createTime": 1487680940000,
                "updateTime": 1487686388000,
                "catalogId": "1d16334c-3231-4232-9adc-e57e5d74552e",
                "banner": "",
                "tagNames": "你画我猜手机游戏",
                "catalogName": "技术分享",
                "tags": [{
                    "id": "a0997aea-2a58-431c-8dad-f88843515587",
                    "name": "你画我猜手机游戏"
                }

这里面的大部分字段都在aritcle这个表中,咱们经过__db.article.find()__ 便可查出来全部文章

其中 catalogName 这个字段 在__catalog__表中 并无在__article__表中,所以咱们须要经过第一次查询__article__表出来的结果遍历获得 catalogId,而后再去查询 __catalog__表,

async _addCatalogName (articleArr) {
    let arr = []
    articleArr.forEach ( async (article,idx) => {
        let catalog_id = article.catalog_id
        let ret = await ArticleModel.getCatalogNameById(catalog_id)
        let name = ret[0].name
        let ob = article.toObject()
        ob.catalog_name = name
        arr.push(ob)
    })
    console.log(arr)
    return arr
}

咱们观察打印出来的结果 就会发现灵异现象,明明有10个数据,可是最外层居然显示的零个,

更诡异的是 当咱们 再一次调用 arr.push(1)

length是11,可是只能看到一个,意不意外?

解决办法:

伟大的阮老师早就为咱们想好啦解决办法

so~ 咱们最终的解决办法

for (let article of articleArr) {
        let query_ret = await ArticleModel.getCatalogNameById(article.catalog_id)
        // console.log(obj)
        let name = query_ret[0].name
        let article_copy = article.toObject()
        article_copy.catalog_name = name
        arr.push(article_copy)
      }
    console.log(arr)
    return arr

完美~

补充一下,一下这种方式再此处不合适咱们,由于咱们不能保证异步执行的顺序,也就没法正确的添加catalogname 到对应的对象上

let promises = articleArr.map((arrticle) => ArticleModel.getCatalogNameById(arrticle.catalog_id));

    let results = await Promise.all(promises);
    console.log(results);

5 开发过程当中调试解决跨域问题的两种方式

方式一

1 开发过程当中 以相对路径访问,如 this.get('/v1/userinfo')
2 经过 webpack-dev-server  配置 代理
![](http://images2017.cnblogs.com/blog/821507/201802/821507-20180201103959734-763359013.png)
3 上线时经过nginx 反向代理便可

方式二

1 开发过程当中 以相对路径访问,如 this.get('http://host:port/v1/userinfo')
2 服务端 配置相应的跨域配置便可
![](http://images2017.cnblogs.com/blog/821507/201802/821507-20180201104319765-1304935666.png)
3 此时请求资源头部能够看到服务端的设置
![](http://images2017.cnblogs.com/blog/821507/201802/821507-20180201104446296-1641890297.png)

补充
前端经过axios访问时 页面访问路径都以相对路径方式写 如 this.get('/v1/userinfo')
经过动态配置 baseurl 来决定咱们是相对路径访问仍是绝对路径访问

相关文章
相关标签/搜索