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

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

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可以由父组件给子组件传递值

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

以下理解有误,留作历史吧,大家可以不看

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

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

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

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

然后:

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

将等价于:

<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 来决定我们是相对路径访问还是绝对路径访问

posted @ 2017-12-17 16:58  _白马非马  阅读(6085)  评论(0编辑  收藏  举报