目录css
喜大普奔,开源地址,请猛戳我html
登陆常见验证方式是token验证, 目前打算暂时用session验证 之后有时间研究token登陆,并添加第三方登陆
//TODO: 一键换肤,白天黑夜模式 先占坑 颜色变量不要写死在单独某一个组件里面,方便之后统一换风格,能够参考element 等ui库
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处理
错误很清楚的写明了 在setAttribute 时候 key 填入的是逗号,通过纠结的一一对比最终发现这个问题是 在元素的属性上 有逗号忘记了删除
你一眼就能看到吗? 若是能看到 恭喜你 ,不用(像我同样)再走弯路~ 引起缘由是当初参照的其余template模板用的是 pug语法,其中每一个属性是用逗号隔开的 而咱们这里是html语法 都好必定要删除,不然 vue会当作属性填充,切记切记
咱们都知道经过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: [ {}, {}, {} ] } }
此时,咱们将没法经过父组件传递数据过去,
出现这种状况的场景是,管理系统 博客展现页面 展现了有多少个目录,已经获取了一遍目录数据, 在新增文章时候还会弹出编辑组件,此时让须要目录数据
这种状况下的解决方案:
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没有半毛线关系~
不巧的是 博主还算是一个发(没)散(头)思(没)维(闹)的人,又去了饿了么 研究一下 回到顶部功能是如何实现的。
此次咱们轻车熟路的就找到了源码,只是不幸此次点进去是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>
1 入口文件引入bable-core
2 在.babelrc 以下配置,经本人测试,stage-3 若是不配置 则 【扩展运算符】使用会报错
3 安装相关依赖
在类中 若是有须要用到内部this 的方法中 须要 在 constructor 中 经过bind 应绑定 this, 由于这些类的方法的调用形式为 以下图二调用,所以 若想用this,须要在constructure中 进行 硬绑定
图一,control对象声明
图二 路由调用 相应的control对象的方法的引用
网上最多见的三种查询方法
1 node-inspector 以前尝试过 好像最终能够chrome 进行断点, 可是仍是偶尔失败,且麻烦 因此 舍弃
2 好像还能够经过 --xxx 加相似什么参数来着, 可是也没成功 因此 舍弃
3 webstorm 仍是算了吧……
4 咱们说一下 用vscode 调试
默认状况下直接按 f5
就会呼出调试界面,直接选择node 便可,也可经过配置,默认状况下 入口是 app.js
咱们根据须要修改便可。
mongoose find 命令 返回的 数据结构如图
若是咱们想在find命令后返回的对象里面添加其定义属性,
好比 动态的给每个对象添加一个 uid属性, 咱们直接给对象添加是无效的,
即便当时你手动添加上打印出来能够看到,可是返回到客户端 却没有这个属性
由于mongoose内部会检查你要添加的这个属性是不是在scheme上,
一说能够经过 strict: false 让查询出的结果可修改,不过测试发现没有什么卵用
愿意是 mongoose 返回的 对象 其实实在 当前对象的 _doc
属性 下面
方式一 粗暴添加
因此 咱们能够 经过 给对对象的_doc属性下的对象添加自定义属性便可
方式二 温柔添加
mongoose 提供啦 toObject()方法 也能够添加
最终的代码相似于:
var model = obj.toObject(); model.isBorrow = false; cb(null, model);
场景描述
查询文章列表接口
须要返回的数据格式以下
{ "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);
方式一
1 开发过程当中 以相对路径访问,如 this.get('/v1/userinfo') 2 经过 webpack-dev-server 配置 代理  3 上线时经过nginx 反向代理便可
方式二
1 开发过程当中 以相对路径访问,如 this.get('http://host:port/v1/userinfo') 2 服务端 配置相应的跨域配置便可  3 此时请求资源头部能够看到服务端的设置 
补充
前端经过axios访问时 页面访问路径都以相对路径方式写 如 this.get('/v1/userinfo')
经过动态配置 baseurl 来决定咱们是相对路径访问仍是绝对路径访问