{"success":true,"data":[{"id":"58abbd21a9c1282817afc28d","author_id":"5873a573a9c1282817afbf7f","tab":"share","content":"
初学vue时曾在网上搜索vue的实战项目源码,无奈大部分都是简单的demo,对于深究vue没有太大的帮助,剩下的一些大部分都是像音乐播放器之类的展示型项目,交互没有预期那么复杂。但我们实际在工作中,经常会遇到有购物车的项目,这类项目因为涉及到money,所以对逻辑严谨度要求高,页面之间交互复杂,又会伴随着登陆、注册、用户信息等等,常常会让我们很头疼。既然还没人用vue写过这样的项目,那不如我来写,开源出来对能看到的人也会有帮助。
\n这种功能性的项目很实用但是往往也很枯燥,没有音乐播放器那么看起来绚丽,思来想去发现饿了么是一个不错的素材,一来它足够复杂,开放的外卖平台比一般的公司独有商店更加复杂。二来 见到那么多美食,大家也不会感觉到厌烦。
\n为啥是饿了么,而不是百度,美团?原因很简单,三个外卖大佬里,饿了么的色调和布局是最漂亮的,看起来最舒服。
\n此项目大大小小共 45 个页面,涉及注册、登陆、商品展示、购物车、下单等等,是一个完整的流程。一般公司即便是官网的单页面项目都没这么复杂,如果这个项目能驾驭的了,相信大部分公司的其他单页面应用也就不在话下,即便更复杂,也不会比这个高到哪里去。
\n因为利用业余时间来做,年前就开始写,又跨个年,周期有点长,项目从零布局到完成共用了2个多月的时间,目前项目已经完成,正在进行一些性能的优化,增加详细的注释。
\n另外,这个项目和慕课网视频的那个饿了么没有任何关系,慕课网的项目只有一个页面,我在看完vue的官方文档后直接写了这个项目,没有参照任何人的代码,请大家不要混为一谈。
\n注:此项目纯属个人瞎搞,正常下单请选择饿了么官方客户端。
\nhttps://github.com/bailicangdu/vue2-elm
\nvue2 + vuex + vue-router + webpack + ES6/7 + fetch + sass + flex + svg
\ngit clone https://github.com/bailicangdu/vue2-elm.git \n\ncd vue2-elm\n\nnpm install\n\n
npm run dev\n\n访问 http://localhost:8088\n
npm run build\n\n生成的elm文件夹放在服务器即可正常访问\n
\n\n本项目主要用于熟悉如何用 vue2 架构一个大型项目
\n
\n\n如果对您有帮助,您可以点右上角 “Star” 支持一下 谢谢! ^_^
\n
\n\n或者您可以 “follow” 一下,我会不断开源更多的有趣的项目
\n
\n\n开发环境 macOS 10.12.3 Chrome 55
\n
\n\n特别感谢辰妹子,在百忙之中抽出时间和我一起完成了这个项目,辛苦了🌹
\n
\n\n如有问题请直接在 Issues 中提,或者您发现问题并有非常好的解决方案,欢迎 PR 👍
\n
\n\n推荐一个 react + redux 开源项目,对react感兴趣的朋友赶紧去看看。地址在这里
\n
\n\n另外一个 vue2 + vuex 的入门项目,比当前的项目简单很多,非常适合入门练习。地址在这里
\n
1、下载代码运行后,因为开启了反向代理,可以获取真实的官方数据,最终可以进行下单(真实的下单,而不是模拟,下单后可以在官方App中查看并付款,亲自试过,且成功付款点餐),但是为了安全起见,登陆的帐号为固定的帐号,以免泄露个人信息,不过照样可以点餐。
\n2、demo的数据为模拟的固定数据,只做为效果演示,因为反向代理必须在PC端运行代码才行。
\n查看demo请戳这里(请用chrome手机模式预览)
\n1、因为并不是elm官方,而且因为要开代理,必须在pc端打开,最多只能做到下单这一步,下单成功后可以在手机客户端查看并付款。
\n2、一般涉及到money的网页逻辑都比较复杂,尤其像饿了么这样一个开放的平台,商家和食品种类繁多,页面与页面之间交互复杂,在写到 购物车 和 下单 功能时众多的数据和逻辑一度让人很头疼,又没有设计和接口api文档,只能一步步摸索。
\n3、vue因其轻量级的框架在中小型项目中表现亮眼,在大型单页面应用中因为vuex的存在,表现依然出色,在处理复杂交互逻辑的时候,vuex的存在是不可或缺的。所以说利用 vue + vuex 完全可以去做大型的单页面项目。
\n4、项目写到现在,从 登陆注册到、首页、搜索、商家列表、购物车、下单、订单列表、个人中心 一个流程走完之后、不但对vue的理解更深一层,而且对以后掌控大型项目的时候也有非常多的帮助,做一个实际的项目才能对自己有很大的提升。
\n5、曾一度怀疑,花几个月的时间做这样一个项目到底有没有意义,本来只是想做一个小项目练练手,没想到后来越写越多,不过坚持下来后我相信一切都是值得的。
\n6、项目已经完成,共45个页面。
\n1、用node.js构建一个模拟外卖平台的后台系统。(已经开始制作)
\n2、利用 react-native 写出跨 Android 和 IOS 的原生APP版本。
\n3、如果时间来的及,会出一个pc端的网页版。
\n所以我的目的是构建一个横跨前后端,移动IOS、Android的完整生态圈。
\n。。。敬请期待
\n\n
\n
\n
\n
\n
\n
\n
\n
|-- build // webpack配置文件\n|-- config // 项目打包路径\n|-- elm \t // 上线项目文件,放在服务器即可正常访问\n|-- screenshots // 项目截图\n|-- src // 源码目录\n| |-- components // 组件\n| |-- common // 公共组件\n|\t\t\t|-- buyCart.js // 购物车组件\n|\t\t\t|-- loading.js // 页面初始化加载数据的动画组件\n|\t\t\t|-- mixin.js // 组件混合(包括:指令-下拉加载更多,处理图片地址)\n|\t\t\t|-- ratingStar.js // 评论的五颗星组件\n|\t\t\t|-- shoplist.js // msite和shop页面的餐馆列表公共组件\n| |-- footer // 底部公共组件\n| |-- header \t // 头部公共组件\n| |-- config // 基本配置\n| |-- env.js // 环境切换配置\n| |-- fetch.js // 获取数据\n| |-- mUtils.js // 常用的js方法\n| |-- rem.js // px转换rem\n| |-- images // 公共图片\n| |-- pages // 页面组件\n| |-- city // 当前城市页\n|\t\t|-- food \t // 食品筛选排序页\n|\t\t|-- confirmOrder // 确认订单页\n|\t\t |--children\n|\t\t\t|--invoice\t\t\t //\t选择发票页\n|\t\t\t|--remark\t\t\t //\t订单备注页\n|\t\t\t|--payment\t\t\t //\t付款页\n|\t\t\t|--userValidation\t\t //\t用户验证页\n|\t\t\t|--chooseAddress //\t选择地址页\n|\t\t |--children\n|\t\t\t\t|--addAddress //\t添加地址页\n|\t\t\t\t |--children\n|\t\t\t\t\t|--searchAddress // 搜索地址页\n| |-- find // 发现页\n| |-- forget // 忘记密码,修改密码页\n| |-- home // 首页\n| |-- login // 登陆注册页\n| |-- msite // 商铺列表页\n| |-- order // 订单列表页\n|\t\t\t|--children\n|\t\t\t\t|--orderDetail\t\t // 订单详情页\n| |-- profile // 个人中心\n|\t\t\t|--children\n|\t\t\t\t|--balance\t\t\t // 我的余额\n|\t\t\t\t|--benefit\t\t\t // 我的优惠\n|\t\t\t\t|--info\t\t\t\t // 帐户信息\n|\t\t\t\t|--points\t\t\t // 我的积分\n|\t\t\t\t|--service\t\t\t // 服务中心\n| |-- search // 搜索页\n| |-- shop // 商铺筛选页\n|\t\t\t|-- children \n|\t\t\t \t|-- foodDetail // 商铺信息页 \n|\t\t\t \t|-- shopDetail // 单个商铺信息页\n|\t\t\t\t\t|-- children \n|\t\t\t\t \t\t|-- shopSafe // 商铺认证信息页 \n| |-- vipcard // vip办理页\n|\n| |-- plugins // 引用的插件\n|\n| |-- router // 路由配置\n|\n| |-- service // 数据交互统一调配\n|\t\t|-- template // 开发阶段的临时数据\n|\t\t|-- getData.js // 获取数据的统一调配文件,对接口进行统一管理\n|\n| |-- store // vuex的状态管理\n| |-- modules // store模块\n| |-- action.js // 配置actions\n| |-- getters.js // 配置getters\n| |-- index.js // 引用vuex,创建store\n| |-- mutation-types.js // 定义常量muations名\n| |-- mutations.js // 配置mutations\n|\n| |-- style // 各种样式文件\n| |-- common.scss // 公共样式文件\n| |-- mixin.scss // 样式配置文件\n|\n| |-- App.vue // 页面入口文件\n|\n| |-- main.js // 程序入口文件,加载各种公共组件\n|\n|-- .babelrc // ES6语法编译配置\n|-- .editorconfig // 代码编写规格\n|-- .gitignore // 忽略的文件\n|-- favicon.ico // 页面左上角小图标\n|-- index.html // 入口html文件\n|-- package.json // 项目及工具的依赖配置文件\n|-- README.md // 说明\n
伴随着vite2的迭代推出,越来越多的开发者选择使用vite.js来构建vue3项目。之前有给大家分享一个vue3+electron跨平台仿QQ聊天项目,这次带来的是使用vite2+electron集成开发vue3跨端仿抖音短视频。
\n\n\n\n基于Vite.js和Electron融合构建vue3.x桌面端exe应用,使用了vite2+electron12+vant3+swiper等技术开发。\nhttps://segmentfault.com/a/1190000039725671
\n
electron-douyin 支持鼠标左右上下拖拽滑动和键盘上下按键切换短视频效果。
\n\nelectron实现登录/注册界面效果。
\n\n/**\n * 主进程入口配置background.js\n */\n'use strict'\n \nimport { app, BrowserWindow, globalShortcut } from 'electron'\nimport { createProtocol } from 'vue-cli-plugin-electron-builder/lib'\n \nimport Windows from './module/windows'\n \nconst isDevelopment = process.env.NODE_ENV !== 'production'\n \nasync function createWindow() {\n let window = new Windows()\n \n window.listen()\n window.createWin({isMainWin: true, resize: false})\n window.createTray()\n}\n \n// Quit when all windows are closed.\napp.on('window-all-closed', () => {\n if (process.platform !== 'darwin') {\n app.quit()\n }\n})\n \napp.on('activate', () => {\n if (BrowserWindow.getAllWindows().length === 0) createWindow()\n})\n \n// This method will be called when Electron has finished\napp.on('ready', async () => {\n createWindow()\n})\n \n// Exit cleanly on request from parent process in development mode.\nif (isDevelopment) {\n if (process.platform === 'win32') {\n process.on('message', (data) => {\n if (data === 'graceful-exit') {\n app.quit()\n }\n })\n } else {\n process.on('SIGTERM', () => {\n app.quit()\n })\n }\n}\n
\n如上图:项目整体采用了自定义顶部导航条,设置-webkit-app-region:drag实现局部自定义拖拽功能。
\n<WinBar bgcolor="transparent" transparent>\n <template #wbtn>\n <a class="wbtn" title="二维码名片" @click="isShowPersonalCard=true"><i class="iconfont icon-erweima"></i></a>\n <a class="wbtn" title="设置" @click="isShowSideMenu=true"><i class="iconfont icon-menu"></i></a>\n </template>\n</WinBar>\n \n<WinBar bgcolor="linear-gradient(to right, #36384a, #36384a)">\n <template #title>视频预览</template>\n <template #wbtn>\n <a class="wbtn" title="另存为" @click="handleDownLoad"><i class="iconfont icon-down"></i></a>\n </template>\n</WinBar>\n
至于具体的实现方式,大家可以去参考之前的这篇分享文章。\nvue3+electron实现无边框导航栏菜单
\n/**\n * @Desc vite2+electron打包配置\n * @Time andy by 2021-03\n */\n \n{\n "productName": "electron-douyin", //项目名称 打包生成exe的前缀名\n "appId": "com.example.electrondouyin", //包名\n "compression": "maximum", //store|normal|maximum 打包压缩情况(store速度较快)\n "artifactName": "${productName}-${version}-${platform}-${arch}.${ext}", //打包后安装包名称\n // "directories": {\n // "output": "build", //输出文件夹(默认dist_electron)\n // },\n "asar": false, //asar打包\n // 拷贝静态资源目录到指定位置(如根目录下的static文件夹会拷贝至打包后的dist_electron/win-unpacked/resources/static目录)\n "extraResources": [\n {\n "from": "/static",\n "to": "static"\n },\n ],\n "nsis": {\n "oneClick": false, //一键安装\n "allowToChangeInstallationDirectory": true, //允许修改安装目录\n "perMachine": true, //是否开启安装时权限设置(此电脑或当前用户)\n "artifactName": "${productName}-${version}-${platform}-${arch}-setup.${ext}", //打包后安装包名称\n "deleteAppDataOnUninstall": true, //卸载时删除数据\n "createDesktopShortcut": true, //创建桌面图标\n "createStartMenuShortcut": true, //创建开始菜单图标\n "shortcutName": "ElectronDouYin", //桌面快捷键图标名称\n },\n "win": {\n "icon": "/static/shortcut.ico", //图标路径\n }\n}\n
ending,运用vite.js+electron跨端仿制抖音短视频项目就分享到这里。
\n\n\n距离2021新年越来越近了,你是否已经无心工作了,哈哈哈~~ 是的,每当这个时候,♥早已飞回家了。\n今天给大家分享的是基于vue3.0开发网页版聊天实例项目。
\n\n\n\n基于vue3全家桶vue3.0.5+vuex+vueRouter+elementPlus+v3Layer+v3Scroll等技术架构的一款仿制QQ/微信网页端界面聊天实战案例vue3Webchat。实现了常见的一些聊天功能、所有页面使用vue3最新语法编码开发。\nhttps://www.cnblogs.com/xiaoyan2017/p/14307849.html
\n
微信风格\n
\nQQ风格\n
\n大家看到的项目中弹窗和滚动条都是vue3自定义组件来实现。\n
\n\n之前有过一篇分享文章,大家感兴趣的话可以去看看。\nvue3.0组件系列:vue3自定义全局对话框/弹窗\nvue3.0组件系列:vue3.x自定义模拟滚动条/美化滚动条组件
\nimport { createApp } from 'vue'\nimport App from './App.vue'\n \n// 引入vuex和地址路由\nimport store from './store'\nimport router from './router'\n \n// 引入公共组件\nimport Plugins from './plugins'\n \n/* 引入公共样式 */\nimport '@assets/fonts/iconfont.css'\nimport '@assets/css/reset.css'\nimport '@assets/css/layout.css'\n \nconst app = createApp(App)\n \napp.use(store)\napp.use(router)\napp.use(Plugins)\n \napp.mount('#app')\n
vue3.0中实现表单的操作验证及倒计时控制。
\n/**\n * @Desc vue3表单验证\n * @Time andy by 2021-01\n * @About Q:282310962 wx:xy190310\n */\n<script>\nimport { reactive, toRefs, inject, getCurrentInstance } from 'vue'\nexport default {\n components: {},\n setup() {\n const { ctx } = getCurrentInstance()\n const v3layer = inject('v3layer')\n const utils = inject('utils')\n \n const formObj = reactive({})\n const data = reactive({\n vcodeText: '获取验证码',\n disabled: false,\n time: 0,\n })\n \n const VTips = (content) => {\n v3layer({\n content: content, layerStyle: 'background:#ff5151;color:#fff;', time: 2\n })\n }\n \n const handleSubmit = () => {\n if(!formObj.tel){\n VTips('手机号不能为空!')\n }else if(!utils.checkTel(formObj.tel)){\n VTips('手机号格式不正确!')\n }else if(!formObj.pwd){\n VTips('密码不能为空!')\n }else if(!formObj.vcode){\n VTips('验证码不能为空!')\n }else{\n ctx.$store.commit('SET_TOKEN', utils.setToken());\n ctx.$store.commit('SET_USER', formObj.tel);\n \n // ...\n }\n }\n \n // 60s倒计时\n const handleVcode = () => {\n if(!formObj.tel) {\n VTips('手机号不能为空!')\n }else if(!utils.checkTel(formObj.tel)) {\n VTips('手机号格式不正确!')\n }else {\n data.time = 60\n data.disabled = true\n countDown()\n }\n }\n const countDown = () => {\n if(data.time > 0) {\n data.vcodeText = '获取验证码('+ data.time +')'\n data.time--\n setTimeout(countDown, 1000)\n }else{\n data.vcodeText = '获取验证码'\n data.time = 0\n data.disabled = false\n }\n }\n \n return {\n formObj,\n ...toRefs(data),\n handleSubmit,\n handleVcode\n }\n }\n}\n</script>\n
项目中的图片预览是使用了element-plus组件库中的image组件。\n
\n<el-image class="img__pic" \n :src="item.imgsrc"\n :preview-src-list="[item.imgsrc]"\n hide-on-click-modal\n/>\n
视频预览使用了v3layer组件弹窗来实现。\n
\n<v3-layer v-model="isShowVideoPlayer"\n title="<i class='iconfont icon-bofang'></i> 视频预览"\n layerStyle="background:#f9f9f9"\n opacity=".2"\n :area="['550px', '450px']"\n xclose\n resize\n :maximize="true"\n>\n <video class="vplayer" ref="playerRef" autoplay preload="auto" controls\n :src="videoList.videosrc"\n :poster="videoList.imgsrc"\n x5-video-player-fullscreen="true"\n webkit-playsinline="true"\n x-webkit-airplay="true"\n playsinline="true"\n x5-playsinline\n />\n</v3-layer>\n
项目中点击链接会出现弹窗来预览网页内容。使用了v3layer弹窗功能来实现这一效果。\n
\nconst handleMsgClicked = (e) => {\n let target = e.target\n // 链接\n if(target.tagName === 'A') {\n e.preventDefault()\n // console.log('触发点击链接事件!')\n\n v3layer({\n type: 'iframe',\n title: '<i class="iconfont icon-link"></i> 网址预览',\n content: target.href,\n opacity: .2,\n area: ['860px', '600px'],\n xclose: true,\n resize: true,\n maximize: true\n })\n }\n // 图片\n if (target.tagName === 'IMG' && target.classList.contains('img-view')) {\n // ...\n }\n}\n
okay,使用vue3开发网页端聊天就分享到这里。后续还会分享一些实战项目。希望能喜欢~~\n\n链接:https://segmentfault.com/a/1190000039067243\n著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
\n最近收到的很多问题都是关于Vue3组件库的问题
\n今天就给大家推荐几个基于Vue3重构的开源组件库
\n目前状态都处于Beta阶段,建议大家抱着学习的心态入场,勿急于用到生产环境
\nant-design-vue 是 Ant Design 的 Vue 实现,组件的风格与 Ant Design 保持同步,组件的 html 结构和 css 样式也保持一致,真正做到了样式 0 修改,组件 API 也尽量保持了一致
\n现状:支持 Vue 3.0 的 2.0.0 测试版 已发布
\n地址:https://antdv.com/docs/vue/introduce-cn/
\nVant 是有赞前端团队开源的移动端组件库,于 2016 年开源,已持续维护 4 年时间。
\nVant 对内承载了有赞所有核心业务,对外服务十多万开发者,是业界主流的移动端组件库之一
\n现状:目前 Vant 已完成了对 Vue 3.0 的适配工作,并发布了 Vant 3.0 Beta 版本,计划在十月底或十一月发布 Vant 3.0 正式版
\n地址:https://vant-contrib.gitee.io/vant/next
\nelementui风格的组件库,Vue3.0 重构版
\n现状:没有明确发布计划,目前还在紧急开发中,有兴趣参与开源项目的也可以去issues认领任务
\n地址:https://element-plus.org/#/zh-CN/component/installation
\n最后扔一个中文文档地址: vue3js.cn/docs/zh
\n京东物流目前在 M 端的业务越来越多,如公众号、移动官网。这对 M 端的 UI 框架要求就越高,组件丰富、接入友好,稳定性高,拓展性及性能上都有较高的要求。自研的 pandora-mobile 目前看不符合现有需求,相较而言 NutUI 更适合物流侧的业务研发需求,经内部讨论决定协同物流侧用户体验部联合 NutUI 团队打造出物流视觉规范的 M 端 UI 框架。
\nJDC & 京东物流技术发展部经历了为期一个多月的紧密合作开发迭代,NutUI-JDL 终于和大家见面了。作为继 2019 年 1 月 15 日 NutUI 2.0 正式发布以来的第二次大版本发布,NutUI-JDL 仍坚守着【基于京东风格】同时在产品的功能、体验、易用性和灵活性等各个方面进行了全面提升。
\n需要注意的是,NutUI-JDL
版本是 NutUI 中的一个生态,目前我们两个生态(NutUI、NutUI-JDL)会同时更新维护,发现问题我们还是保持第一时间迭代修复,请大家放心使用。
NutUI-JDL 的目标是让移动端的开发更加容易,基础组件交给我们让开发人员更加专注于业务,提升研发效率。
\n\n\n呼声最高的设计资源对外开放,并且引入优质的相关技术沉淀文章
\n
\n
\n随着京东物流移动端业务的拓展,设计师不仅仅需要完成业务需求,也需要思考设计的价值。设计师也不应该陷入重复的设计中,消耗设计时间,降低工作效率,所以推行组件化的设计模式迫在眉睫。
\nNutUI-JDL 京东物流版是一套基于移动端的组件库,减少冗余组件,从实际项目入手,梳理出最通用的 30+ 基础组件,覆盖多场景,体积小巧、设计精美,提供了全新的设计以及交互体验,提高界面模块化的通用程度。
\n新版组件库从 设计语言
和 基础组件
两大模块,重新定义了布局、颜色、图标、字体、间距和通用组件规范,提升了 UI 展示及交互方式,建立了新的设计标准,为推动物流侧移动端产品的体验优化和迭代带来了高效路径。
在 NutUI v2.2.6 之前,因底层的 WebPack 配置老旧、版本较低、设计不足 这大大增加了造成构建出的 npm 包大小达到了 17.4 MB, 2020Q2 通过我们不断打磨 @nutui/cli 接入后,将 npm 包大小减少到了 7.58 MB,大幅提升了性能及可配置项。同样我们在 NutUI-JDL 版本中也统一此插件。
\n\n\n\n
\n本次版本 NutUI-JDL 与 NutUI 2.x 属于不同生态,一个是基于 JD APP 视觉规范、另一个是 JDL APP 视觉规范,对应的 npm 包分别是 @nutui/nutui
和 @nutui/nutui-jdl
\n我们建议您为了尽可能简化升级,直接采用以下命令进行安装使用:
npm install @nutui/nutui-jdl -S\n
详细的使用文档请参考 https://nutui.jd.com/jdl/#/start
\n众人拾柴火焰高,在这里我们非常欢迎感兴趣的同学参与到 NutUI 项目的开发,建议通过提 Pull Request 的方式参与,通过 Code Review 之后,我们会合并你的代码。
\nNutUI 的持续迭代,离不开每一位参与帮助开发的人,开发者每一次对我们的认可,就是对我们最大的鼓励。
\n版权声明:著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。\n作者: 京东设计中心JDC\n原文链接:https://juejin.im/post/6856557213419487240\n
\nVue 提供了各种各样的通讯,其中包括兄弟间
的通讯和非兄弟间
的通讯,借此机会做个总结,查阅起来方便。如果喜欢的话可以帮忙点个赞 👍 或者关注一下 😋
components\n ├── Parent.vue // 父亲\n ├── Son1.vue // 儿子1\n
在父亲组件中使用儿子组件,给儿子通过:date="xxx"
单向传值
<template>\n <div>\n <div>爸爸:{{date}}</div>\n <Son1 :date="date"></Son1>\n </div>\n</template>\n<script>\nimport Son1 from "./son1";\nexport default {\n components: { Son1 },\n data() {\n return {\n date: 1,\n };\n },\n};\n</script>\n
儿子组件通过props
接受父组件传过来的值
<template>\n <div>儿子:{{date}}</div>\n</template>\n<script>\nexport default {\n props: {\n date: {\n type: Number, //校验类型\n default: "1",\n },\n },\n};\n</script>\n
components\n ├── Parent.vue // 父亲\n ├── Son1.vue // 儿子1\n
子组件通过触自身的方法来触发$emit
方法,再触发父组件的方法,通过回调传参
的方式将修改的内容传递给父组件
<template>\n <div>\n <div>儿子:{{date}}</div>\n <button @click="changeNum">修改</button>\n </div>\n</template>\n<script>\nexport default {\n props: {\n date: {\n type: Number,\n default: "1",\n },\n },\n methods: {\n changeNum() {\n this.$emit("changeNum", 2);\n },\n },\n};\n</script>\n\n
父组件接受回调params
参数,即爸爸需要给儿子绑定了一个自定义的事件,$on("changeNum",params)
<template>\n <div>\n <div>爸爸:{{date}}</div>\n <Son1 :date="date" @changeNum="changeNum"></Son1>\n </div>\n</template>\n<script>\nimport Son1 from "./son1";\nexport default {\n components: { Son1 },\n data() {\n return {\n date: 1,\n };\n },\n methods: {\n changeNum(params) {\n this.date = params;\n },\n },\n};\n</script>\n
components\n ├── Parent.vue // 父亲\n ├── Son1.vue // 儿子1\n
子组件通过$emit("update:xxx")
发射事件
<template>\n <div>\n <div>儿子:{{date}}</div>\n <button @click="changeNum">修改</button>\n </div>\n</template>\n<script>\nexport default {\n props: {\n date: {\n type: Number,\n default: "1",\n },\n },\n methods: {\n changeNum() {\n this.$emit("update:date", 2);\n },\n },\n};\n</script>\n
父组件通过:xxx.sync="xxx"
接受参数
<template>\n <div>\n <div>爸爸:{{date}}</div>\n <Son1 :date.sync="date"></Son1>\n </div>\n</template>\n<script>\nimport Son1 from "./son1";\nexport default {\n components: { Son1 },\n data() {\n return {\n date: 1,\n };\n },\n};\n</script>\n
<Son1 :date.sync="date"></Son1>\n//这个写法是上面的替代品 默认组件内部触发 update:count 规定写法\n<Son1 :date="date" @update:date="val=>date=val"></Son1>\n
components\n ├── Parent.vue // 父亲\n ├── Son1.vue // 儿子1\n
子组件触发的事件只能是input
事件,接收props
的属性名只能叫value
<template>\n <div>\n <div>儿子:{{value}}</div>\n <button @click="changeNum">修改</button>\n </div>\n</template>\n<script>\nexport default {\n props: {\n value: {\n type: Number,\n default: 1,\n },\n },\n methods: {\n changeNum() {\n this.$emit("input", 2);\n },\n },\n};\n</script>\n
父组件通过v-model
接收参数
<template>\n <div>\n <div>爸爸:{{value}}</div>\n <Son1 v-model="value"></Son1>\n </div>\n</template>\n<script>\nimport Son1 from "./son1";\nexport default {\n components: { Son1 },\n data() {\n return {\n value: 1,\n };\n },\n};\n</script>\n
<Son1 :value="value" @input="val=>value=val"></Son1>\n//这个写法是上面的替代品 默认组件内部触发 input 规定写法\n<Son1 v-model="value"></Son1>\n
\n\n\n
v-model
局限只能传递一个属性 如果只有一个 可以使用v-model
多个依然需要使用.sync
$parent和 $children
components\n ├── Parent.vue // 父亲\n ├── Son1.vue // 儿子1\n ├── Grandson1.vue //孙子1\n
如下场景:孙子想要给爷爷传递数据,孙子需要找到爷爷身上的事件去传递$parent.$emit
<template>\n <div>\n <div>孙子{{value}}</div>\n <button @click="$parent.$emit('change',3)">修改</button>\n </div>\n</template>\n<script>\nexport default {\n props: {\n value: {\n type: Number,\n default: "",\n },\n },\n};\n</script>\n
儿子组件使用孙子组件
\n<template>\n <div>\n <div>儿子:{{value}}</div>\n <grandson1 :value="value"></grandson1>\n </div>\n</template>\n<script>\nimport grandson1 from "./grandson1";\nexport default {\n components: {\n grandson1,\n },\n props: {\n value: {\n type: Number,\n default: 1,\n },\n },\n};\n</script>\n
爸爸身上给孙子自定义change事件
\n<template>\n <div>\n <div>爸爸:{{value}}</div>\n <Son1 @change="val=>value=val" :value="value"></Son1>\n </div>\n</template>\n<script>\nimport Son1 from "./son1";\nexport default {\n components: { Son1 },\n data() {\n return {\n value: 1,\n };\n },\n};\n</script>\n
\n\n如果层级很深那么就会出现
\n$parent.$parent.....
我们可以封装一个$dispatch
方法向上进行派发
Vue.prototype.$dispatch = function $dispatch(eventName, data) {\n let parent = this.$parent;\n while (parent) {\n parent.$emit(eventName, data);\n parent = parent.$parent;\n }\n};\n
\n\n相同的道理,如果既然能够向上寻找父亲,就能向下寻找儿子,也可以封装一个向下派发的方法
\n$broadcast
Vue.prototype.$broadcast = function $broadcast(eventName, data) {\n const broadcast = function () {\n this.$children.forEach((child) => {\n child.$emit(eventName, data);\n if (child.$children) {\n $broadcast.call(child, eventName, data);\n }\n });\n };\n broadcast.call(this, eventName, data);\n};\n
$attrs和 $listeners
components\n ├── Parent.vue // 父亲\n ├── Son1.vue // 儿子1\n ├── Grandson1.vue //孙子1\n
$attrs
批量向下传入属性
<template>\n <div>\n <div>爸爸:{{value}}</div>\n <Son1 @change="val=>value=val" :value="value"></Son1>\n </div>\n</template>\n<script>\nimport Son1 from "./son1";\nexport default {\n components: { Son1 },\n data() {\n return {\n value: 1,\n };\n },\n};\n</script>\n
在儿子组件中使用 $attrs属性,配合v-bind
可以将属性继续向下传递
<template>\n <div>\n <div>儿子:{{$attrs.value}}</div>\n <grandson1 v-bind="$attrs"></grandson1>\n </div>\n</template>\n<script>\nimport grandson1 from "./grandson1";\nexport default {\n components: {\n grandson1,\n },\n mounted() {\n console.log(this.$attrs);\n },\n};\n</script>\n
\n\n注意一点:在使用 $attrs的时候,如果组件中使用了
\nprops
就会将属性从当前attrs
移除掉
在孙子组件中使用 $attrs属性,可以将属性继续向下传递
\n<template>\n <div>\n <div>孙子{{$attrs.value}}</div>\n </div>\n</template>\n<script>\nexport default {\n //props: {\n // value: Number,\n //},\n mounted() {\n console.log(this.$attrs);\n },\n};\n</script>\n\n
\n\n如果爸爸传递给儿子元素, 儿子有三个属性用不到, 孙子传递给孙子,但是不想在页面上这个属性,可以设置
\ninheritAttrs: false
$listeners
批量向下传入方法
<template>\n <div>\n <div>爸爸:{{value}}</div>\n <Son1 @click="change" :value="value"></Son1>\n </div>\n</template>\n<script>\nimport Son1 from "./son1";\nexport default {\n components: { Son1 },\n data() {\n return {\n value: 1,\n };\n },\n\n methods: {\n change() {\n this.value = 2;\n },\n },\n};\n</script>\n
可以在son1组件中使用$listeners
属性,配合v-on
可以将方法继续向下传递
<template>\n <div>\n <div>儿子:{{$attrs.value}}</div>\n <grandson1 v-bind="$attrs" v-on="$listeners"></grandson1>\n </div>\n</template>\n<script>\nimport grandson1 from "./grandson1";\nexport default {\n components: {\n grandson1,\n },\n mounted() {\n console.log(this.$attrs);\n console.log(this.$listeners);\n },\n};\n</script>\n
孙子组件可以直接使用$listeners
上的方法
<template>\n <div>\n <div>孙子{{$attrs.value}}</div>\n <button @click="$listeners.click"></button>\n </div>\n</template>\n<script>\nexport default {\n mounted() {\n console.log(this.$attrs);\n console.log(this.$listeners);\n },\n};\n</script>\n
app.vue\ncomponents\n ├── Parent.vue // 父亲\n ├── Son1.vue // 儿子1\n ├── Grandson1.vue //孙子1\n
在父级声明一个公共数据
\nexport default {\n provide() {\n return { vm: this };\n },\n};\n
在子组件中可以注入原理,会将数据挂载在当前实例上
\n<template>\n <div>\n <div>孙子</div>\n </div>\n</template>\n<script>\nexport default {\n inject: ["vm"],\n mounted() {\n console.log(this);\n },\n};\n</script>\n
\n\n\n注意事项:这个方法使用之后比较混乱,它一般
\n不会在业务代码中使用
,经常是在组件库或者多级通信,为了方便你可以使用provide
。
components\n ├── Parent.vue // 父亲\n ├── Son1.vue // 儿子1\n
ref
获取的是真实的dom元素,如果放到组件上代表的是当前组件的实例
。 父组件中可以直接获取子组件的方法或者数据。
<template>\n <div>\n <div>爸爸</div>\n <Son1 ref="son"></Son1>\n </div>\n</template>\n<script>\nimport Son1 from "./son1";\nexport default {\n components: { Son1 },\n mounted() {\n this.$refs.son.show();\n },\n};\n</script>\n
<template>\n <div>\n <div>儿子</div>\n </div>\n</template>\n<script>\nexport default {\n methods: {\n show() {\n console.log(1);\n },\n },\n};\n</script>\n
\n\n注意事项:
\nref
不要重名
, 但是当且仅当使用v-for
的时候会导致出现数组
情况
main.js\ncomponents\n ├── Grandson1.vue // 孙子1\n ├── Son2.vue // 儿子2\n
EventBus
可用于跨组件通知(不复杂的项目可以使用这种方式)
Vue.prototype.$bus = new Vue();\n
Grandson1组件和Son2相互通信
\n<template>\n <div>孙子1</div>\n</template>\n<script>\nexport default {\n mounted() {\n this.$nextTick(() => {\n this.$bus.$emit("test", "go");\n });\n },\n};\n</script>\n
这里的儿子2组件只能使用$on
来触发Grandson1组件事件
<template>\n <div>儿子2</div>\n</template>\n<script>\nexport default {\n mounted() {\n this.$bus.$on("test", (data) => {\n console.log(data);\n });\n },\n};\n</script>\n
Vuex
是一个专为 Vue.js 应用程序开发的状态管理模式。
\n具体文档查阅
\nVue.observable
可以让返回的对象响应式,返回的对象可以直接用于渲染函数
和计算属性
内,并且会在发生变更时触发相应的更新,可以作为一个简单的store使用
store\n ├── index.js\ncomponents\n ├── hello.vue //函数式组件\n ├── home.vue \n
store/index.js
import Vue from 'vue';\n\nexport const state = Vue.observable({msg:"hello world"});\n\nexport const mutation = {\n setMsg(paylod){\n state.msg = paylod\n }\n}\n
利用计算属性,响应式的通讯\nhome.vue
<template>\n <div @click="handleClick">home332131{{msg}}</div>\n</template>\n<script>\nimport { state, mutation } from "@/store";\nexport default {\n computed:{\n msg(){\n return state.msg\n }\n },\n methods: {\n handleClick() {\n mutation.setMsg("你好,世界");\n },\n },\n};\n</script>\n
利用渲染函数,响应式通讯\nhome.vue
<template>\n <div>\n <hello />\n </div>\n</template>\n<script>\nimport hello from './hello'\nexport default {\n components:{\n hello\n },\n};\n</script>\n
hello.vue
<script>\nimport { state, mutation } from "@/store";\nexport default {\n props:{\n render:Function\n },\n render(h) {\n return h("div",{on:{click:this.handleClick}}, `${state.msg}`);\n },\n methods:{\n handleClick() {\n mutation.setMsg("你好,世界");\n },\n }\n}\n</script>\n
以上就是全部内容,欢迎评论指点,一起交流一起进步 🙏🙏🙏🙏
\n\n 版权声明:著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。\n 作者: 小丑同学\n 原文链接:https://juejin.im/post/6861547167358648327\n
\n\nvue-next(3.0)版本开源已经很长时间了,虽然还没有发布正式版,但不影响我们在自己的项目中尝试使用。本文是对自己的「电影预告片」项目刚刚用vue3重写后的总结。
\n
我在掘金上发布的第一篇文章就是关于此项目的介绍,没想到已经过去俩年了,回头看看这俩年的成长… 打死自己的心都有了!!!🙃
\n项目整体的视觉/交互效果没有做改变,如下:
\n\n\n使用 vue-cli
工具可以方便的创建一个vue项目,其中选择技术栈时,勾选上TypeScript就好。
项目初始化好,通过 vue add vue-next
安装官方插件,使其vue版本升级到vue3.0
修改 shims-vue.d.ts
文件,引入 .vue
组件,类型是 defineComponent
函数返回的类型
declare module "*.vue" {\n import { defineComponent } from "vue";\n const Component: ReturnType<typeof defineComponent>;\n export default Component;\n}\n
移除 filters
过滤器特性,官方原因是说 filters
功能完全可以用函数、计算属性来完成,没必要增加学习成本,并且 |
与位运算符是冲突的,同时还增加了模板解析复杂度。具体可以看 rfc
修改了v-model
的API,同时移除了 model
选项。在之前我们开发组件实现双向绑定,是通过 props.value
,调用 this.$emit('input')
来实现双向绑定。vue3.0将使用 modelValue
代替 value
,emit('update:modelValue')
来代替 input
事件。具体可以看 rfc
<!-- 使用组件 -->\n<Comp v-mode="text" />\n
// modelValue 通过 attrs 获取,emit用于触发事件\nexport default defineComponent({\n setup(props, { attrs, emit }) {\n const onChange = (e) => {\n emit('update:modelValue', e.target.value)\n }\n return { onChange } \t\n }\n})\n
Transition
组件的 className
改变,之前过渡效果是写在 xxx-enter
、 xxx-leave-to
css类名中,现在 xxx-enter
改为 xxx-enter-from
。具体可以看 rfc
插件install的参数改变。之前写一个插件,可以直接使用install方法的入参 Vue
,将方法或属性挂载到 Vue.prototype
上。 现在install方法的入参是app。
// 之前插件挂载属性的方式\nexport default {\n install: (Vue, option) => {\n Vue.prototype.$axios = xxx;\n }\n}\n// vue3.0的方式\nexport default {\n install: (app, option) => {\n app.config.globalProperties.$axios = xxxx\n }\n}\n// 另一种方式 也可以使用 provide 、inject , 在composition API中使用\nexport const symbolKey = Symbol('_axios_');\n// setup 函数里使用composition API\nexport function useAxios() {\n return inject(symbolKey)\n}\n// 执行插件insall方法注入\nexport default {\n install: (app, option) => {\n app.provide(symbolKey, xxx)\n }\n}\n
其实vue3.0
版本改动很多,但因为项目不是很复杂,所以涉及的不是很全面,大家可以去阅读rfcs查看改变的一些讨论。
该项目所有组件都使用了 Composition API
开发,基于 Composition API
我们可以更好的封装公用逻辑。
在此之前,项目的异步请求的loading要额外定义和维护,有了 Composition API
我们可以封装一个类似于React swr
取数库的hook函数。
// 类似于这样\nexport function useRequest(url, params, config) {\n // 统一维护的变量,最后return出去\n const state = reactive({\n loading: false,\n error: false,\n data: config.initialData\n })\n \n const fetchFunc = () => {\n state.loading = true;\n \t// 做请求的公用逻辑\n axios().then(response => {\n const result = response.data;\n state.data = result.data;\n state.loading = false;\n }).catch(err => {\n state.error = true;\n })\n }\n \n onMounted(() => {\n if (config.immediate) {\n fetchFunc();\n }\n });\n \n // toRefs 可以将state,拆解成多个ref,这样调用者就可以使用解构来拿到变量\n return { ...toRefs(state), fetch: fetchFunc };\n}\n
比如 touch
事件,我们不光要绑定事件和解绑时间,还需要在不同回调中共用一个副作用变量,我们就可以把它拆成 composable
函数
// 类似于这样\nexport function useTouch(domRef, ref) {\n // 是否touch进行中标志位\n let initiated = false;\n // 监听domRef改变\n watch(domRef, (el, prev, onCleanup) => {\n const touchStart = (e: TouchEvent) => {\n e.preventDefault();\n initiated = true;\n callbacks.touchStart(e);\n };\n \tconst touchMove = (e: TouchEvent) => {\n e.preventDefault();\n if (!initiated) return;\n callbacks.touchMove(e);\n };\n const touchEnd = (e: TouchEvent) => {\n initiated = false;\n callbacks.touchEnd(e);\n };\n \n el.addEventListener("touchstart", touchStart);\n el.addEventListener("touchmove", touchMove);\n el.addEventListener("touchend", touchEnd);\n\t// 取消绑定\n onCleanup(() => {\n el.removeEventListener("touchstart", touchStart);\n el.removeEventListener("touchmove", touchMove);\n el.removeEventListener("touchend", touchEnd);\n });\n })\n}\n
增加了travis实现了提交自动构建发布,通过sshpass实现将静态资源发送到远程服务器。
\nlanguage: node_js\nnode_js:\n - 12\nbranchs:\n - master\naddons:\n apt:\n packages:\n - sshpass\ninstall:\n "npm install"\nscript:\n - "npm run build"\nafter_success:\n - ./script/deploy.sh\n
其中 serverPass
和 serverIP
都是在travis上配置的环境变量。
#!/usr/bin/env sh\n\n# 确保脚本抛出遇到的错误\nset -e\n\n# 打包静态资源\nnpm run build\n\n# 将dist文件发送到远程\nsshpass -p ${serverPass} scp -o stricthostkeychecking=no -r dist/ root@${serverIP}:/home/web/movie-trailer\n
后端从使用 koa
框架升级成 Egg
框架,使用 Koa
开发应用时,需要开发者下载或开发各种中间件来实现功能的增强,而 Egg
选择了 Koa
作为其基础框架,帮助开发者对其进行了一些增强。
Egg
除了提供了 Controller
、 Service
层做业务处理之外,还提供了 extend
用于扩展自身的功能(多种扩展点)。比如我们可以扩展 Context
,在其上下文上挂载返回结果的通用函数。
// app/extends/context.js\nmodule.exports = {\n sendSuccess: function(result) {\n this.body = {\n code: 200,\n errMsg: '',\n data: result\n }\n },\n sendError: function(errorMsg) {\n this.body = {\n code: 300,\n errMsg: errorMsg,\n }\n }\n};\n// app/controller/keyword.js\nclass KeyWordController extends Controller {\n async index() {\n const { ctx, service } = this;\n\n const keywordList = await service.keyword.index();\n\t// 返回200成功\n ctx.sendSuccess(keywordList)\n }\n}\n
在此之前,我是通过Python进行爬取豆瓣电影,然后通过crontab手动写入脚本, Egg
框架方便了开发者设置定时任务,使用了 Egg
, 我们只需要在 app/schedule
目录下创建文件导出基于Subscription
的类。
class CrawlingDouban extends Subscription {\n // 静态方法,定义执行时机\n static get schedule() {\n return {\n cron: '0 8 * * *', // 每天8点\n type: 'worker',\n };\n }\n async subscribe() {\n \t// 爬取逻辑\n }\n}\n
重构过程中,还是会遇到一些问题,大部分是文档没读导致的,所以文档过一遍还是很重要的。希望此文章对大家有所帮助,感觉项目还算可以的,不要吝啬start哦!。
\n\n\n\n 版权声明:著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。\n 作者: lihaozecq\n 原文链接:https://juejin.im/post/6862582779419459598\n
笔者去年曾写过一个类似的拼拼乐小游戏,技术栈采用自己的Xuery框架和原生javascript实现的,脚手架采用gulp来实现,为了满足对vue的需求,笔者再次使用vue生态将其重构,脚手架采用比较火的vue-cli。
\n为了加深大家对vue的了解和vue项目实战,笔者采用vue生态来重构此项目,方便大家学习和探索。技术栈如下:
\n因为该应用属于H5游戏,为了清亮化笔者没有采用第三方ui库, 如果大家想采用基于vue的第三方移动端ui库,笔者推荐如下:
\n以上笔者推荐的都是社区比较完善,bug比较少的组件库,大家可以感受一下。
\n回到我们的小游戏开发,我们更多的是javascript和css3的掌握程度,在学习完这篇文章之后相信大家对javascript和css3的编程能力都会有极大的提升,后面还会介绍如何使用canvas实现生成战绩海报图的功能。
\n我们先来看看游戏的预览界面:\n\n\n\n\n在线体验地址:传送门
\n本文的算法实现方式在之前的拼拼乐文章中已经说明,这里主要介绍核心算法, 至于vue-cli的使用方法,笔者之前也写过对应的文章,大家可以研究学习一下。vue-cli搭建项目方式如下:
\n// 安装\nyarn global add @vue/cli\n\n// 创建项目\nvue create pinpinle\n\n// 进入项目并启动\ncd pinpinle && yarn start\n
关于vue-cli3配置实战,可以移步\n一张图教你快速玩转vue-cli3
\n目前笔者主要整理乐如下核心功能,接下来笔者会一一带大家实现:
\n文件上传预览主要采用FileReader API实现,原理就是将file对象传给FileReader的readAsDataURL然后转化为data:URL格式的字符串(base64编码)以表示所读取文件的内容。\n具体代码如下:
\n// 2.文件上传解析\nlet file = $('#file');\nfile.on('change', function(e){\n var file = this.files[0];\n var fileReader = new FileReader();\n // 读取完成触发的事件\n fileReader.onload = function(e) {\n $('.file-wrap')[0].style.backgroundImage = 'url(' + fileReader.result + ')';\n imgSrc = fileReader.result;\n }\n\n file && fileReader.readAsDataURL(file);\n})\n
一般我们处理这种拼图游戏都会有如下方案:
\n经过权衡,笔者想出了第三种方法,也是自认为比较优雅的方法,即动态背景分割,我们只需要使用1张图片,然后利于css的方式切割图片,有点经典的雪碧图的感觉,如下:\n\n本质就是我们设置九个div,每个div都使用同一张图片,并且图片大小等于游戏画布大小,但是我们通过backgroundPosition(背景定位)的方式来实现切割图片。这样做的另一个好处是方便我们实现洗牌逻辑。
\n洗牌逻辑依托于随机算法,这里我们结合坐标系,实现一个随机生成二维坐标系的逻辑,然后通过改变每个切片的translate位置,配合过渡动画,即可实现洗牌功能和洗牌动画。
\n数组乱序比较简单,代码如下:
\n// 数组乱序\nfunction upsetArr(arr) {\n arr.sort(function(a,b){\n return Math.random() > 0.5 ? -1 : 1\n })\n}\n
洗牌逻辑基于数组乱序,具体逻辑如下:
\n// 洗牌方法\nfunction shuffle(els, arr) {\n upsetArr(arr);\n for(var i=0, len=els.length; i< len; i++) {\n var el = els[i];\n el.setAttribute('index', i); // 将打乱后的数组索引缓存到元素中\n el.style.transform = 'translate(' + arr[i].x + 'vw,' + arr[i].y + 'vh'+ ')';\n }\n}\n
n维矩阵主要用来做洗牌和计算成功率的,具体实现如下:
\n// 生成n维矩阵坐标\nfunction generateMatrix(n, dx, dy) {\n var arr = [], index = 0;\n for(var i = 0; i< n; i++) {\n for(var j=0; j< n; j++) {\n arr.push({x: j*dx, y: i*dy, index: index});\n index++;\n }\n }\n return arr\n}\n
置换算法主要用来切换拼图的,比如用户想移动拼图,可以用过置换来实现:
\n // 数组置换\nfunction swap(arr, indexA, indexB) {\n let cache = arr[indexA];\n arr[indexA] = arr[indexB];\n arr[indexB] = cache;\n}\n
生成战绩海报笔者采用canvas来实现,对于canvas的api不熟悉的可以查看MDN,讲的比较详细。这里笔者简单实现一个供大家参考:
\nfunction generateImg() {\n var canvas = document.createElement("canvas");\n\n if(canvas.getContext) {\n var winW = window.innerWidth,\n winH = window.innerHeight,\n ctx = canvas.getContext('2d');\n canvas.width = winW;\n canvas.height = winH;\n\n // 绘制背景\n // ctx.fillStyle = '#06c';\n var linear = ctx.createLinearGradient(0, 0, 0, winH);\n linear.addColorStop(0, '#a1c4fd');\n linear.addColorStop(1, '#c2e9fb');\n ctx.fillStyle = linear;\n ctx.fillRect(0, 0, winW, winH);\n ctx.fill();\n\n // 绘制顶部图像\n var imgH = 0;\n img = new Image();\n img.src = imgSrc;\n img.onload = function(){\n // 绘制的图片宽为.7winW, 根据等比换算绘制的图片高度为 .7winW*imgH/imgW\n imgH = .6*winW*this.height/this.width;\n ctx.drawImage(img, .2*winW, .1*winH, .6*winW, imgH);\n\n drawText();\n drawTip();\n drawCode();\n }\n\n // 绘制文字\n function drawText() {\n ctx.save();\n ctx.fillStyle = '#fff';\n ctx.font = 20 + 'px Helvetica';\n ctx.textBaseline = 'hanging';\n ctx.textAlign = 'center';\n ctx.fillText('我只用了' + (180 -dealtime) + 's,' + '快来挑战!', winW/2, .15*winH + imgH);\n ctx.restore();\n }\n\n // 绘制提示文字\n function drawTip() {\n ctx.save();\n ctx.fillStyle = '#000';\n ctx.font = 14 + 'px Helvetica';\n ctx.textBaseline = 'hanging';\n ctx.textAlign = 'center';\n ctx.fillText('关注下方二维码开始游戏', winW/2, .25*winH + imgH);\n ctx.restore();\n }\n\n\n // 绘制二维码\n function drawCode() {\n var imgCode = new Image();\n imgCode.src = '/piecePlay/images/logo.png';\n imgCode.onload = function(){\n ctx.drawImage(imgCode, .35*winW, .3*winH + imgH, .3*winW, .3*winW);\n\n // 生成预览图\n var img = new Image();\n img.src= convertCanvasToImage(canvas, 1).src;\n img.className = 'previewImg';\n img.onload = function(){\n $('.preview-page')[0].appendChild(this);\n startDx = startDx - 100;\n transformX(wrap, startDx + 'vw');\n }\n }\n } \n } else {\n alert('浏览器不支持canvas!')\n }\n}\n
H5拼图小游戏笔者已在github开源, 感兴趣的可以学习参考。以上的逻辑部分的代码可以直接整合到vue项目中即可,由于实现比较简单, 这里笔者就不详细介绍了。
\n目前笔者实现的H5可视化编辑器H5-Dooring功能新增如下:
\n预览地址:基于React+Koa实现一个h5页面可视化编辑器-Dooring
\ngithub地址:基于React+Koa实现一个h5页面可视化编辑器-Dooring
\n如果想学习更多H5游戏, webpack,node,gulp,css3,javascript,nodeJS,canvas数据可视化等前端知识和实战,欢迎在《趣谈前端》一起学习讨论,共同探索前端的边界。
\n\n版权声明:著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。\n作者: 徐小夕\n原文链接:https://juejin.im/post/6866802944697139213\n
很多时候,我们需要基于一些 UI
框架进行二次封装,这里以 Element UI
为例,封装一个 Input
组件类似如下:
<!-- Input 组件 -->\n<template>\n <div>\n <el-input v-model="myc"></el-input>\n </div>\n</template>\n
这个时候,我们需要保证外面能够直接设置 el-input
的属性,比如 placeholder
、clearable
等等,最好能够透传
第一反应,我们想到的就是,通过 props
传值进来,然后一个个的设置,如下所示:
<template>\n <div>\n <el-input v-model="myc"\n :placeholder="configProps.placeholder"\n :clearable="configProps.clearable"></el-input>\n </div>\n</template>\n
上面就是通过传入的 props
—— configProps
,来设置 placeholder
和 clearable
但是这样代码可读性差、维护不方便、而且还会有遗漏的点
\n其实我们在一个组件内部没有声明任何 prop
时,调用该组件,传入相关的属性,会直接将属性传到根节点上,如下:
<!-- Input 组件 -->\n<template>\n <div class="hello input-con">\n <label>输入框:</label>\n <el-input ></el-input>\n </div>\n</template>\n<script>\nexport default {\n name: 'Input'\n}\n</script>\n
调用上面的组件
\n<Input placeholder="我是默认值"\n :clearable="true"/> \n
\n那怎么才能将属性传递到内部的 el-input
组件中呢,直接给 el-input
加一个 v-bind="$attrs"
即可。
先看官方对 vm.$attrs
的定义:
\n\n包含了父作用域中不作为 prop 被识别 (且获取) 的 attribute 绑定 (class 和 style 除外)。当一个组件没有声明任何 prop 时,这里会包含所有父作用域的绑定 (class 和 style 除外),并且可以通过 v-bind="$attrs" 传入内部组件——在创建高级别的组件时非常有用。
\n
大白话:调用一个组件的时候传入属性 (class
和 style
除外),而且不在该组件内部的 props
中声明,就可以通过 v-bind="$attrs"
传入该组件的内部组件
比如,上面调用 Input
组件的时候传入了 placeholder
、clearable
,然后 Input
组件内部并没有声明 placeholder
、clearable
去接收这两个 props
,这个时候可以通过 v-bind="$attrs"
传入内部组件 el-input
中
具体的代码示例如下
\n<template>\n <div>\n <el-input v-bind="$attrs" ></el-input>\n </div>\n</template>\n
看到的结果如下:
\n\n完整的代码示例放在了 codesandbox
中了,可以在线看下——普通的 v-bind="$attrs",建议大家自己试下
虽然上面可以解决了大部分的问题了,但同事发现并不能满足场景,主要是他用了动态组件 component
。他的想法是通过 JSON Schema
的方式生成表单,其中应用了动态组件 component
,这是一个很棒的想法,相信现在很多公司应该都有类似的做法。
我们来看下他的实现思路:
\n<!-- 动态组件,根据 JSON 配置 调用 Input 或者 Select 组件等等 -->\n<template>\n <div class="hello">\n <div v-for="(config, index) in configJsonArr" :key="config.type + index">\n <!-- 动态组件,根据配置中的 Type 来决定调用的是 Input 还是 Select -->\n <component :is="config.type" :configProps="config.props"></component>\n </div>\n </div>\n</template>\n\n<script>\nimport Input from './Input'\nimport Select from './Select'\n\nexport default {\n name: 'Config',\n components: {\n Input,\n Select\n },\n props: {\n \t// 动态组件 JSON schema 的配置\n configJsonArr: {\n type: Array,\n required: true,\n default: () => []\n }\n }\n}\n</script>\n
其中 configJsonArr
为如下:
[{\n type: 'Input',\n props: {\n placeholder: '我是默认值',\n clearable: true\n }\n}, {\n type: 'Select',\n props: {\n placeholder: '我是默认值'\n }\n}, {\n type: 'Input',\n props: {\n placeholder: '我是默认值',\n suffixIcon: 'el-icon-delete'\n }\n}]\n
其中 Input
组件类似如下:
<template>\n <div class="hello input-con">\n <label>输入框:</label>\n <el-input ></el-input>\n </div>\n</template>\n\n<script>\nexport default {\n name: 'Input'\n}\n</script>\n
这个时候,假如我们直接在 el-input
设置 v-bind="$attrs"
是不行的,原因在于动态组件传入的属性 configProps
是一个对象,而不是解构后的对象属性,那怎么办呢?
我们可以使用渲染函数!
\n说来惭愧,之前很少用到渲染函数 (render
) 函数,来了新的公司之后,发现这边用得还挺多(大家都好厉害),自己也学习了一下。
上面提到的在标签中没法解构属性,在渲染(render
)函数中就可以解决,先来大致的了解下渲染函数,这里主要还是参考官方文档
渲染函数中的第一个参数是 createElement
,其接受的参数如下(注意第一个和第二个参数):
第一个参数可以是一个 HTML
标签名、组件选项对象,或者 resolve
了上述任何一种的一个 async
函数。必填项。这里我们挂载的是我们的 Input
组件等。
// @returns {VNode}\ncreateElement(\n // {String | Object | Function}\n // 一个 HTML 标签名、组件选项对象,或者\n // resolve 了上述任何一种的一个 async 函数。必填项。\n 'div',\n\n // {Object}\n // 一个与模板中 attribute 对应的数据对象。可选。\n {\n // (详情见下一节)\n },\n\n // {String | Array}\n // 子级虚拟节点 (VNodes),由 `createElement()` 构建而成,\n // 也可以使用字符串来生成“文本虚拟节点”。可选。\n [\n '先写一些文字',\n createElement('h1', '一则头条'),\n createElement(MyComponent, {\n props: {\n someProp: 'foobar'\n }\n })\n ]\n)\n
我们再将重点放在第二个参数 Object
中,我们可以在这个 Object
中指定相关的属性值,比如 class
、style
、attrs
(普通的 HTML attribute
)、组件的 props
【这个就是我们这一期重点关注的属性值】…。具体如下:
{\n // 与 `v-bind:class` 的 API 相同,\n // 接受一个字符串、对象或字符串和对象组成的数组\n 'class': {\n foo: true,\n bar: false\n },\n // 与 `v-bind:style` 的 API 相同,\n // 接受一个字符串、对象,或对象组成的数组\n style: {\n color: 'red',\n fontSize: '14px'\n },\n // 普通的 HTML attribute\n attrs: {\n id: 'foo'\n },\n // 组件 prop\n props: {\n myProp: 'bar'\n },\n // DOM property\n domProps: {\n innerHTML: 'baz'\n },\n // 事件监听器在 `on` 内,\n // 但不再支持如 `v-on:keyup.enter` 这样的修饰器。\n // 需要在处理函数中手动检查 keyCode。\n on: {\n click: this.clickHandler\n },\n // 仅用于组件,用于监听原生事件,而不是组件内部使用\n // `vm.$emit` 触发的事件。\n nativeOn: {\n click: this.nativeClickHandler\n },\n // 自定义指令。注意,你无法对 `binding` 中的 `oldValue`\n // 赋值,因为 Vue 已经自动为你进行了同步。\n directives: [\n {\n name: 'my-custom-directive',\n value: '2',\n expression: '1 + 1',\n arg: 'foo',\n modifiers: {\n bar: true\n }\n }\n ],\n // 作用域插槽的格式为\n // { name: props => VNode | Array<VNode> }\n scopedSlots: {\n default: props => createElement('span', props.text)\n },\n // 如果组件是其它组件的子组件,需为插槽指定名称\n slot: 'name-of-slot',\n // 其它特殊顶层 property\n key: 'myKey',\n ref: 'myRef',\n // 如果你在渲染函数中给多个元素都应用了相同的 ref 名,\n // 那么 `$refs.myRef` 会变成一个数组。\n refInFor: true\n}\n
可以看到,我们可以在上面这个对象中设置 props
属性的值的时候,将它解构掉就可以了。
核心代码实现,如下所示:
\n// 这其实就是一个组件\nconst CompFormItem = {\n components: {\n Input, Select\n },\n name: 'FormItem',\n props: {\n \t// 传入配置\n configJson: {\n required: true\n }\n },\n // h 实际上就是 createElement 参数\n render (h) {\n \t// 第一个参数就是配置中的 type,也就是我们的组件名称\n return h(`${this.configJson.type}`, {\n props: {\n \t// 针对 props 进行解构\n ...this.configJson.props || {}\n },\n attrs: {\n \t// 针对 attrs 进行解构\n ...this.configJson.props || {}\n }\n })\n }\n}\n
这样我们再在 Input
组件中写上 v-bind="$attrs"
就可以了
<template>\n <div class="hello input-con">\n <label>输入框:</label>\n <el-input v-bind="$attrs"></el-input>\n </div>\n</template>\n\n<script>\nexport default {\n name: 'Input'\n}\n</script>\n
\n以上通过渲染函数就可以完全解决透传属性的问题了,具体的我也放在了 codesandbox
中了——动态组件透传属性。也提供一下 GitHub 地址【这么用心,求个赞应该可以吧?】
其实我也还在想,是不是还有其他的方法?欢迎大家评论提出自己的想法和建议
\n\n 版权声明:著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。\n 作者: Gopal\n 原文链接:https://juejin.im/post/6865451649817640968\n
基于Vue.js 2.x系列 + Element UI 的后台管理系统解决方案。
\n\n\n\n\n
之前在公司用了Vue + Element组件库做了个后台管理系统,基本很多组件可以直接引用组件库的,但是也有一些需求无法满足。像图片裁剪上传、富文本编辑器、图表等这些在后台管理系统中很常见的功能,就需要引用其他的组件才能完成。从寻找组件,到使用组件的过程中,遇到了很多问题,也积累了宝贵的经验。所以我就把开发这个后台管理系统的经验,总结成这个后台管理系统解决方案。
\n该方案作为一套多功能的后台框架模板,适用于绝大部分的后台管理系统(Web Management System)开发。基于vue.js,使用vue-cli脚手架快速生成项目目录,引用Element UI组件库,方便开发快速简洁好看的组件。分离颜色样式,支持手动切换主题色,而且很方便使用自定义主题色。
\ngit clone https://github.com/lin-xin/manage-system.git\t\t// 把模板下载到本地\ncd manage-system\t\t\t\t\t\t\t\t\t\t\t// 进入模板目录\nnpm install\t\t\t\t\t\t\t\t\t\t\t\t\t// 安装项目依赖,等待安装完成之后\n
// 开启服务器,浏览器访问 http://localhost:8080\nnpm run dev\n
// 执行构建命令,生成的dist文件夹放在服务器下即可访问\nnpm run build\n
一套基于vue.js2.0的桌面组件库。访问地址:element
\n一个用于动态创建表格的vue.js服务端组件。访问地址:vue-datasource
\n<template>\n\t<div>\n\t\t<datasource language="en" :table-data="information.data"\n\t :columns="columns"\n\t :pagination="information.pagination"\n\t :actions="actions"\n\t v-on:change="changePage"\n\t v-on:searching="onSearch"></datasource>\n\t</div>\n</template>\n\n<script>\n\timport Datasource from 'vue-datasource';\t\t\t\t\t// 导入quillEditor组件\n export default {\n data: function(){\n return {\n information: {\n\t pagination: {...},\t\t\t\t\t\t// 页码配置\n\t data: [...]\n\t },\n\t columns: [...],\t\t\t\t\t\t\t\t// 列名配置\n\t actions: [...]\t\t\t\t\t\t\t\t// 功能配置\n }\n },\n components: {\n Datasource\t\t\t\t\t\t\t\t\t\t// 声明组件Datasource\n },\n\t methods: {\n\t changePage(values) {...},\n\t onSearch(searchQuery) {...}\n\t }\n\t}\n</script>\n
基于Quill、适用于Vue2的富文本编辑器。访问地址:vue-quill-editor
\n<template>\n\t<div>\n\t\t<quill-editor ref="myTextEditor" v-model="content" :config="editorOption"></quill-editor>\n\t</div>\n</template>\n\n<script>\n\timport { quillEditor } from 'vue-quill-editor';\t\t\t// 导入quillEditor组件\n export default {\n data: function(){\n return {\n content: '',\t\t\t\t\t\t\t\t// 编辑器的内容\n editorOption: {\t\t\t\t\t\t\t\t// 编辑器的配置\n // something config\n }\n }\n },\n components: {\n quillEditor\t\t\t\t\t\t\t\t\t\t// 声明组件quillEditor\n }\n\t}\n</script>\n
Vue.js的Markdown Editor组件。访问地址:Vue-SimpleMDE
\n<template>\n <div>\n <markdown-editor v-model="content" :configs="configs" ref="markdownEditor"></markdown-editor>\n </div>\n</template>\n\n<script>\n import { markdownEditor } from 'vue-simplemde';\t\t\t// 导入markdownEditor组件\n export default {\n data: function(){\n return {\n content:'',\t\t\t\t\t\t\t\t\t// markdown编辑器内容\n configs: {\t\t\t\t\t\t\t\t\t// markdown编辑器配置参数\n status: false,\t\t\t\t\t\t\t// 禁用底部状态栏\n initialValue: 'Hello BBK',\t\t\t\t// 设置初始值\n renderingConfig: {\n codeSyntaxHighlighting: true,\t\t// 开启代码高亮\n highlightingTheme: 'atom-one-light' // 自定义代码高亮主题\n }\n }\n }\n },\n components: {\n markdownEditor\t\t\t\t\t\t\t\t\t// 声明组件markdownEditor\n }\n }\n</script>\n
一款轻量级的vue上传插件,支持裁剪。访问地址:Vue-Core-Image-Upload
\n\n<template>\n <div>\n\t\t<img :src="src">\t\t\t\t\t\t\t\t\t// 用于显示上传的图片\n <vue-core-image-upload :class="['pure-button','pure-button-primary','js-btn-crop']"\n :crop="true"\t\t\t\t\t\t\t\t\t\t// 是否裁剪\n text="上传图片"\n url=""\t\t\t\t\t\t\t\t\t\t\t// 上传路径\n extensions="png,gif,jpeg,jpg"\t\t\t\t\t// 限制文件类型\n @:imageuploaded="imageuploaded">\t\t\t\t\t// 监听图片上传完成事件\n\t\t</vue-core-image-upload>\n </div>\n</template>\n\n<script>\n import VueCoreImageUpload from 'vue-core-image-upload';\t// 导入VueCoreImageUpload组件\n export default {\n data: function(){\n return {\n src:'../img/1.jpg'\t\t\t\t\t\t\t// 默认显示图片地址\n }\n },\n components: {\n VueCoreImageUpload\t\t\t\t\t\t\t\t// 声明组件VueCoreImageUpload\n },\n methods:{\n imageuploaded(res) {\t\t\t\t\t\t\t// 定义上传完成执行的方法\n console.log(res)\n }\n }\n }\n</script>\n\n
基于vue2和eCharts.js3的图表组件。访问地址:vue-echarts-v3
\n<template>\n <div>\n <IEcharts :option="bar"></IEcharts>\n </div>\n</template>\n\t\n<script>\n import IEcharts from 'vue-echarts-v3';\t\t\t\t\t// 导入IEcharts组件\n export default {\n data: function(){\n return {\n bar: {\n\t\t\t title: {\n\t\t\t text: '柱状图'\t\t\t\t\t\t\t// 图标标题文本\n\t\t\t },\n\t\t\t tooltip: {},\t\n\t\t\t xAxis: {\t\t\t\t\t\t\t\t// 横坐标\n\t\t\t data: ['衬衫', '羊毛衫', '雪纺衫', '裤子', '高跟鞋', '袜子']\n\t\t\t },\n\t\t\t yAxis: {},\t\t\t\t\t\t\t\t// 纵坐标\n\t\t\t series: [{\n\t\t\t name: '销量',\n\t\t\t type: 'bar',\t\t\t\t\t\t\t// 图标类型\n\t\t\t data: [5, 20, 36, 10, 10, 20]\n\t\t\t }]\n\t\t\t \t}\n }\n },\n components: {\n IEcharts\t\t\t\t\t\t\t\t// 声明组件VueCoreImageUpload\n }\n }\n</script>\n
举个栗子,我不想用 vue-datasource 这个组件,那我需要分四步走。
\n第一步:删除该组件的路由,在目录 src/router/index.js 中,找到引入改组件的路由,删除下面这段代码。
\n{\n path: '/vuetable',\n component: resolve => require(['../components/page/VueTable.vue'], resolve) // vue-datasource组件\n},\n
第二步:删除引入该组件的文件。在目录 src/components/page/ 删除 VueTable.vue 文件。
\n第三步:删除该页面的入口。在目录 src/components/common/Sidebar.vue 中,找到该入口,删除下面这段代码。
\n<el-menu-item index="vuetable">Vue表格组件</el-menu-item>\n
第四步:卸载该组件。执行以下命令:
\nnpm un vue-datasource -S\n
完成。
\n第一步:打开 src/main.js 文件,找到引入 element 样式的地方,换成浅绿色主题。
\nimport 'element-ui/lib/theme-default/index.css'; // 默认主题\n// import '../static/css/theme-green/index.css'; // 浅绿色主题\n
第二步:打开 src/App.vue 文件,找到 style 标签引入样式的地方,切换成浅绿色主题。
\n@import "../static/css/main.css";\n@import "../static/css/color-dark.css"; /*深色主题*/\n/*@import "../static/css/theme-green/color-green.css"; !*浅绿色主题*!*/\n
第三步:打开 src/components/common/Sidebar.vue 文件,找到 el-menu 标签,把 theme=“dark” 去掉即可。
\n各位道友,开源一个Vue2+项目:\nAwesome douban DEMO created with Vue2.x + Vuex + Vue-router + vue-resource
项目地址:\nhttps://github.com/jeneser/douban
\n在线演示:\nhttps://jeneser.github.io/douban
\n翻译过来呢就是一个涉及面较广的使用豆瓣API作为数据源的Vue2+DEMO。使用了Vue团队所推荐的一些流行的周边插件或库,从路由,到http请求,再到集中式状态管理,总之你要的,都在这。如果你还不清楚怎样将这些库和插件整合在一起,同时使用SCSS来编写具有漂亮UI界面的完整项目,又或者不能简单的勾勒出整体的架构,那么这个项目很可能和你胃口,又或者,你想了解一下Vue的各个API到底怎么用,用在哪里?呐,给你,都在这了…
\n部分截图,更多特性,可以直接在线体验…\n
\n\n\n\nwebpack+vue+vueRouter+vuex+es6 构建的简单实例项目
\n\n\n\n
\n\n如果对您有帮助,您可以点右上角 “Star” 支持一下 谢谢! ^_^
\n
\n\n如果你觉得对你有帮助,可以点击folk,或者follow一下,我会不定时跟新一些有趣的东西.~~~ 0.0
\n
对于vuex,就像rudux的作者所说的"您自会知道什么时候需要它"
\n为了理解vuex,demo中运用了vuex来对底部Icon进行变化。
\n\nVuex是专门为 Vue.js 设计的状态管理库
\n
\n\n首先是创建一个 store ,里面有:
\n
\n\n然后在每次对数据对象进行操作的时候,进行dispatch(action 的方法名)用来触发mutations的方法来改变state状态…
\n
\n\nvue的组件中都有computed,当state改变的时候会触发computed,所以我们就可以根据state的值,对页面进行修改。
\n
暂时只增加了6个功能,虽然是个小demo,不过能用上的技术,基本上都用上了,适合初学者学习。
\n\n\n[vue]
\n
\n\n[vue-router]
\n
\n\n[vuex]
\n
\n\n[vue-resource]
\n
\n\n[webpack]
\n
\n\n[es6-babel]
\n
\n\n[less]
\n
\n.\n├── README.md \n├── dist // 项目build目录\n├── package.json // 项目配置文件\n├── src // 生产目录\n│ ├── assets // css js 和图片资源\n│ ├── components // 各种组件\n│ ├── views // 各种页面\n│ ├── router.js // 路由配置\n│ └── app.vue // 根组件\n│ └── main.js // Webpack 预编译入口 \n├── index.html // 项目入口文件\n├── webpack.config.js //webpack配置文件\n├── .gitignore //git忽略文件\n\n
项目地址:(使用git clone
)
git clone https://github.com/193Eric/webpack-vue-vueRouter.git\n
通过npm
安装本地服务第三方依赖模块(需要已安装Node.js),使用npm安装依赖模块可能会很慢,建议换成cnpm
npm install -g cnpm --registry=http://registry.npm.taobao.org\n
# 安装依赖模块\ncnpm install\n\nnpm run build\n\nnpm run dev\n\n\n然后会自动弹出浏览器地址 http://localhost:8081
\n\n这是用 vue.js 2.0 高仿 今日头条 的移动端项目,结合了原生app的部分功能以及网页版。
\n
本人是 今日头条 的重度用户,在学习vue.js过程中,在GitHub上看到了很多高仿webapp的好项目。由此在有了一定的技术积累后,开始构思使用Vue写今日头条,一是自己对于头条的喜爱,另外也是对于自己学习成果的检验。
\nnpm install
\nnpm run dev
\n如果您也是头条的重度用户,感觉项目对您有学习帮助,麻烦给个star吧,嘿嘿^_^
\n4-23 更新
\n4-25 再分享一点小东西
\n首先,重要的逻辑和操作都是在 home页(首页) 。
\n这个项目很关键的一环便是数据的获取,而且现在网上很少有现成的新闻数据接口,当然也有,但是返回的数据无法满足我们的需求。
\n后来我在刷新今日头条(f12移动模式)时,在控制台network中捕捉到了数据接口,现在直接分享给大家。\nhttp://m.toutiao.com/list/?tag='+ payload.kind +' &ac=wap&count=20&format=json_raw&as=A125A8CEDCF8987&cp=58EC18F948F79E1& min_behot_time= parseInt((new Date().getTime()) / 1000)
这个接口其实很简单,主要修改tag和min_behot_time这两个字段。
\n\n\n说一说这 tag
\n\n\ntag中news_×××的 ××× 内容需要几分钟去复制一下,\n比如“热点”:news_hot; “军事”:news_military。。。等等
\n
另外这个请求是有跨域问题的,可用代理(设置proxyTable)和jsonp实现。\n其实大家仔细看都能发现,我这里就献丑了,希望对您以后做一些新闻相关的项目有帮助。
\n在线观看\nhttp://www.qiufengh.com:8081/#/\n继上一个项目用vuejs仿网易云音乐(实现听歌以及搜索功能)后,发现上一个项目单纯用vue的model管理十分混乱,然后我去看了看vuex,打算做一个项目练练手,又不想做一个重复的项目,这次我就放弃颜值,打算走心派。结合了后台nodejs,以及数据库mongodb来开发了一个实时聊天系统。这个系统可以说是一统江山,也算是实现前端程序员的一个梦了,前后通吃。自认为是一个比全的项目。项目地址:https://github.com/hua1995116/webchat 觉得好的请顺手来个star。\n\n功能实现\n1.注册与登录\n2.实时聊天\n3.与机器人聊天\n4.个人中心\n技术栈
\nvue init webpack my-project-name\n
结构大致是这样的\n\n好!既然我们是实战项目,我就不多说这些配置问题。不然又跑题了。不然又要被小哥哥小姐姐们打了。\n\n前端\ncomponents/Chat.vue
\ncreated() {\n const that = this\n this.socket = io.connect('http://qiufengh.com:8081')\n this.socket.on('message', function(obj) {\n that.$store.commit('addroomdetailinfos', obj)\n window.scrollTo(0, 900000)\n })\n this.socket.on('logout', function (obj) {\n that.$store.commit('setusers', obj)\n })\n},\n
this.socket = io.connect('http://qiufengh.com:8081')\n
这一句,主要用于连接你当前的服务,到时候下载后面的项目时,记得改成自己的服务以及端口。因为是在Index和Chat都有设置,所以你需要在Index.vue和Chat里的connect都改成你自己的服务。socket.on()用于接受消息。socket.emit() 用于发送消息。不懂的socket.io的看这里socket.io。有了这个就可以和服务端进行交互。等会讲解服务端。\n由于字数问题,不能讲解服务器端,服务器端请大家移步,我的github地址。https://github.com/hua1995116/webchat/\n地址:https://github.com/hua1995116/webchat\n在线观看地址:http://www.qiufengh.com:8081/#/
\nnpm install -----安装依赖\nnpm run dev -----运行\nnpm run build -----打包\nnode prod.server.js -----打包后运行\n//记得替换\nIndex.vue和Chat.vue下的io.connect('http://qiufengh.com:8081')\nhttp://qiufengh.com:8081改成自己的项目地址。\n
最后上几张图。\n\n\n\n
\n从去年10月份之后Vue 2.0发布了正式版,便将React全线转到了Vue上开发,陆陆续续开发了四五个项目,多多少少积累了一些心得,现在拿出来和大家分享一下和探讨一下。本人英语水平太low,如有错误,还望指正。
\n这两年前端发展迅速,日新月异,各种框架层出不穷,这是一个坏时代,也是一个最好的时代,有幸的是能够身处在这个时代去亲眼见证它、实践它。
\nVue官方虽然提供了vue-cli的脚手架,供我们得以快速开发,但是如果有多个项目,每个项目都生成一套打包配置,很容易造成各个项目都进行一些定制,进而造成项目之间的一些偏离,然而这些都不是我们想要的,所以有必要单独提取出来一套内部的打包配置,创建一个单独的git包,然后让其他项目依赖这个git包即可。
\n\n很抱歉,在我的项目中,Vuex仅仅是用来存储用户信息等一些全局通用的数据,我不建议将每一个页面的数据存储到Store中,那样会导致程序极其的复杂,让我每新增一个页面,都感觉到倍增压力。
\n在SPA应用中,最离不开的就是如何方便快捷的获取页面的数据,统一处理路由的变化去更新数据,为页面进行数据缓存,实现列表、详情、编辑、新增共用一个model,而且有的时候列表的字段不如详情的字段多,所以还要增加一个字段补全的机制。\n
\n // 一个简单的例子\n export default function MixinPagesGet () {\n const opt = {\n list: []\n // 各种配置的参数等等\n }\n Object.assign(opt, arguments[0])\n return {\n list: {\n // 列表的mixin各种逻辑\n },\n detail: {\n // 详情、修改、新增的mixin各种逻辑\n }\n }\n }\n\n\n
为了避免回调地狱,Promise简直就是一个神器,一旦遇上,便恨初见晚,不管是Fetch还是第三方的请求模块,都普遍支持了Promise,我们可以使用vue-methods-promise模块对各种错误进行统一处理
\n在JS中,object对象的key值是没有顺序之分的,但是人的阅读顺序是有顺序的,按照钩子的执行顺序来书写可以让别人更好的读懂你的代码,如果不是钩子则放到最后。如:
\nexport default {\n mixins: [],\n data () {\n return {}\n },\n mounted () {\n },\n destroyed () {\n },\n watch: {\n },\n methods: {\n }\n}\n
在日常开发中,我们有开发环境、测试环境、正式环境等等,我们需要打出一个标准包,让运维进行配置不同环境的。\n我们可以在入口的html文件中设置
\n <% if (process.env.NODE_ENV == 'production') { %>\n <script src="<%=webpack.publicPath %>configs.js?v=<%=new Date().getTime()%>"></script>\n <% } %>\n
在config的JS中设置
\n let configs = {}\n if (process.env.NODE_ENV === 'production') {\n configs = window.__configs // 在config.js中设置的\n }\n export default configs\n
在vue里,组件之间的作用域是独立的,父组件跟子组件之间的通讯可以通过prop属性来传参,但是在兄弟组件之间通讯就比较麻烦了。比如A组件要告诉一件事给B组件,那么A就要先告诉他们的爸组件,然后爸组件再告诉B。当组件比较多,要互相通讯的事情很多的话,爸组件要管他们那么多事,很累的。vuex正是为了解决这个问题,让多个子组件之间可以方便的通讯。
\n待办事项中的一个事件,它可能拥有几个状态,未完成、已完成、已取消或被删除等。这个事件需要在这多种状态之间切换,那么使用vuex来管理也是非常方便的。
\n来看一下vuex怎么完成状态管理的:
\n\n所有组件都是调用actions,分发mutation去修改state,然后state经过getter又更新到各个组件里。state又通过localStorage存储数据到本地,下次重新打开时再读取保存的数据。
\n为什么要用模块化?当我们的项目比较大,组件很多,功能也多,会导致state里要存放很多内容,整个 store 都会很庞大,很难管理。
\n我模块化的store目录如下:
\n|-store/ // 存放vuex代码\n| |-eventModule // 事件模块\n| | |-actions.js\n| | |-getters.js\n| | |-index.js\n| | |-mutations.js\n| | |-state.js\n| |-themeModule // 主题颜色模块\n| | |-actions.js\n| | |-getters.js\n| | |-index.js\n| | |-mutations.js\n| | |-state.js\n| |-index.js // vuex的核心,创建一个store\n
可以看到,每个模块拥有自己的state、mutation、action、getter,这样子我们就可以把我们的项目根据功能划分为多个模块去使用vuex了,而且后期维护也不会一脸懵逼。
\n接下来,我们来看看vuex完成状态管理的一个流程。\n举个栗子:一个待办事项,勾选之后,会在未完成列表里移除,并在已完成的列表里出现。这个过程,是这个待办事项的状态发生了改变。勾选的时候,是执行了一个方法,那我们就先写这个方法。在 event_list.vue 文件里新建一个moveToDone方法。
\nmethods: {\n moveToDone(id){ //移至已完成\n this.$store.dispatch('eventdone', id);\n }\n}\n
在 moveToDone 方法中通过 store.dispatch 方法触发 action, 接下来我们在 eventModule/actions.js 中来注册这个 action, 接受一个 id 的参数。
\nexport default {\n eventdone = ({ commit }, param) =>{\n commit('EVENTDONE',{id: param});\n }\n}\n
action 通过调用 store.commit 提交载荷(也就是{id: param}这个对象)到名为’EVENTDONE’的 mutation,那我们再来注册这个 mutation
\nexport default {\n EVENTDONE(states,obj){\n for (let i = 0; i < states.event.length; i++) {\n if (states.event[i].id === obj.id) {\n states.event[i].type = 2;\n states.event[i].time = getDate();\n var item = states.event[i];\n states.event.splice(i, 1); // 把该事件在数组中删除\n break;\n }\n }\n states.event.unshift(item); // 把该事件存到数组的第一个元素\n local.set(states); // 将整个状态存到本地\n }\n}\n
通过 mutation 去修改 state, state里我们存放了一个 event 属性
\nexport default {\n event: []\n};\n
在组件中要获得这个 state 里的 event, 那就需要写个getters
\nexport default {\n getDone(states){\n return states.event.filter(function (d) {\n if (d.type === 2) { // type == 2表示已完成\n return d; // 返回已完成的事件\n }\n });\n }\n};\n
然后每个module里都有一个index.js文件,把自己的state、mutation、action、getters都集合起来,就是一个module
\nimport * as func from '../function';\nimport actions from './actions.js';\nimport mutations from './mutations.js';\nimport state from './state.js';\nimport getters from './getters.js';\n\nexport default {\n state,\n getters,\n actions,\n mutations\n}\n
在 store/index.js 里创建一个 store 对象来存放这个module
\nimport Vue from 'vue';\nimport Vuex from 'vuex';\nimport event from './eventModule';\nVue.use(Vuex);\nexport default new Vuex.Store({\n modules: {\n event\n }\n});\n
最后在 event_list.vue 组件上,我们通过计算属性 computed 来获取到这个从未完成的状态改变到已完成的状态,我们要用到 store 这个对象里的getters
\ncomputed: {\n getDone(){\n return this.$store.getters.getDone;\n }\n}\n
这样子,完成了 ‘未完成’ => ‘已完成’ 从提交修改到更新视图读取的整个流程,也是 vuex 工作的整个流程。通过 module 的封装,更加方便多模块项目的开发和维护。
\n项目灵感的最初来源是@shinygang来自的Vue-cnodejs,\n感谢cnodejs社区提供的API。\ngithub:https://github.com/lzxb/vue-cnode
\n在vue-cnode升级vue2的时候,在公司内部已经有两个正式项目使用vue2,\n遇到的一个最难的问题,就是如何能在页面后退时还原数据和滚动条位置,\n虽然vue2内置了keep-alive组件,vue-router也提供了scrollBehavior方法进行设置,\n但是仍然无法满足需求,后来阅读vue-router的源码发现,\n每个页面都会自动在history.state对象中存储一个对应的key值,\n便利用这个特性实现了页面后退时,数据和滚动条还原,\n不过目前只是实现了页面的顶级组件还原,\n如果需要对顶级组件下的子组件实现数据还原,\n可以利用$options._scopeId来实现。\n哈哈,具体如何实现就要靠大家自己发挥想象力了\n
基于vue2 + vue-router + vuex + ES6 + less + flex.css重写vue版cnode社区,使用webpack打包\n
1.克隆项目: git clone https://github.com/lzxb/vue-cnode.git\n2.安装nodejs\n3.安装依赖: npm install\n4.启动服务: npm run dev\n5.发布代码: npm run dist\n
.\n|-- config // 项目开发环境配置\n| |-- index.js // 项目打包部署配置\n|-- src // 源码目录\n| |-- components // 公共组件\n| |-- content.vue // 页面内容公共组件\n| |-- data-null.vue // 数据为空时公共组件\n| |-- footer.vue // 底部导航栏公共组件\n| |-- header.vue // 页面头部公共组件\n| |-- index.js // 加载各种公共组件\n| |-- loading.vue // 页面数据加载公共组件\n| |-- config // 路由配置和程序的基本信息配置\n| |-- config.js // 配置项目的基本信息\n| |-- routes.js // 配置页面路由\n| |-- css // 各种css文件\n| |-- common.css // 全局通用css文件\n| |-- iconfont // 各种字体图标\n| |-- images // 公共图片\n| |-- less // 各种less文件\n| |-- common.less // 全局通用less文件\n| |-- config.less // 全局通用less配置文件\n| |-- lib // 各种插件\n| |-- route-data // 实现页面后退数据还原,滚动位置还原\n| |-- mixins // 各种全局mixins\n| |-- pull-list.js // 上拉加载\n| |-- pages // 各种页面组件\n| |-- about // 关于\n| |-- index // 首页\n| |-- login // 登录\n| |-- my // 我的主页,和消息列表\n| |-- signout // 退出\n| |-- topic // 主题详情,主题新建\n| |-- user // 查看用户资料\n| |-- store // vuex的状态管理\n| |-- index.js // 加载各种store模块\n| |-- user.js // 用户store\n| |-- template // 各种html文件\n| |-- index.html // 程序入口html文件\n| |-- util // 公共的js方法\n| |-- app.vue // 页面入口文件\n| |-- main.js // 程序入口文件,加载各种公共组件\n|-- .babelrc // ES6语法编译配置\n|-- webpack.config.js // 程序打包配置\n|-- server.js // 开发时使用的服务器\n|-- README.md // 项目说明\n|-- package.json // 配置项目相关信息,通过执行 npm init 命令创建\n.\n