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

时间:2022-10-23 12:44:46

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

[TOC]

1 登录

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

2 css变量的处理

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

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

3 数据接口处理

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

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

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

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

例如 我想用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  优点 使用简单 不存在跨域   缺点 不能动态根据前端传递参数不同返回不同结果,自己起的模拟服务器当然你自己想怎么干就怎么干了,上天都没人管你~

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

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

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

4一个逗号引发的血案~

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

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

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

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

引发原因是当初参照的其他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 这个 属性。

前台博客记录

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

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

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


直观发现此处是通过href锚点实现滚动,然而自己实现时发现这样并没有滚动动画,但是他们是怎么做到的呢,开始我一直以为肯定是用了某个css3动画实现,但是根本找不到任何css相关的样式


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


如何确定是不是用js实现的呢?


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


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


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


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


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


这次我们轻车熟路的就找到了源码,只是不幸这次点进去是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

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

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

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

3 安装相关依赖

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

2继承使用问题 this问题

在类中 如果有需要用到内部this 的方法中  需要 在 constructor 中 通过bind 应绑定 this,
因为这些类的方法的调用形式为 如下图二调用,因此 若想用this,需要在constructure中 进行 硬绑定

图一,control对象声明

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

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

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

3通过 vscode 进行断点调试

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

1 node-inspector 之前尝试过 好像最终可以chrome 进行断点, 但是还是偶尔失败,且麻烦 所以 舍弃

2 好像还可以通过 --xxx 加类似什么参数来着, 但是也没成功 所以 舍弃

3 webstorm 还是算了吧……

4 我们说一下 用vscode 调试

默认情况下直接按 f5 就会呼出调试界面,直接选择node 即可,也可通过配置,默认情况下 入口是 app.js
我们根据需要修改即可。
个人博客-后台管理系统 & 前台系统 & api系统 开发记录 未完 时刻更新中

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

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

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

如果我们想在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个数据,但是最外层竟然显示的零个,

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

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

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

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

解决办法:

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

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

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 来决定我们是相对路径访问还是绝对路径访问
个人博客-后台管理系统 & 前台系统 & api系统 开发记录 未完 时刻更新中