UNPKG

bmfe-mobile-template

Version:

BM 移动端模板

559 lines (460 loc) 18.3 kB
<!-- @Author: songqi @Date: 2017-01-09 @Last modified by: songqi @Last modified time: 2017-02-28 --> ## 使用安装 权限不足请使用 sudo BM init -f mobile 创建名称 版本 cnpm install ### 功能介绍 移动端底层已经切换至 vnoic,基于 WeUI,vnoic 又在此之上封装了很多,能很快的构建业务 对于 vnoic 服务指令的引入最好在 common 中进行引入,尽量减小包的体积,按需再向整个业务进行引用 ### 目录结构 · |-- .gitignore (需要忽略的文件:/node_modules、/bower_components、/dist) |-- .babelrc (babel 编译的文件) |-- .eslintrc (js 语法检测文件) |-- config.js (BM 配置文件) |-- node_modules (node 依赖) |-- html (对外输出的页面文件) |-- dist (对外输出的 js/css 资源文件) |-- src (工程目录) |-- css (css 文件目录) |-- html (html 文件目录) |-- mock (mock) |-- js (js 文件目录) |-baseLibs (此文件一般是外部一些大型的资源文件,且这个文件不会走编译) |-common (公共模块具有完整的页面周期的行为,如:登录) |-components (全局公共组件) |-config (公共配置文件:如路由等) |-pages (页面的业务) |-service (发送请求的服务方法) |-store (全局的 store 如登录信息) |-utils (工具类函数) |-widget (全局生命周期插件) |-app.vue (根节点) |-main.js (业务的根节点 js) |-vendor.js (公共的资源文件引入) ### 修改配置文件 修改文件目录中的```src\js\config\index.js``` ``` javascript // 独立开发时,mock 服务的路径 export const TESTPATH = '//fe.benmu-health.com' // 接口拦截到需要跳转登录页面的 code export const LOGIN_CODE = 1000 // 需要微信认证 export const WXCONFIG = true // 请求超时时间 export const AJAXTIMEOUT = 20000 // 请求是否会发送本地的请求 export const LOCAL_AJAX = false export const DEBUG = { // 请求打印 req: false, // 响应打印 res: false, //开启vconsole vconsole: false, // 开启vue debug v_debug: true, // 开启vue devtools v_devtools: true } ``` 将上面的配置文件,配置成适用于当前项目的参数 ### 启动 BM server // 启动该项目,该项目会自动打开浏览器 ## 规范 ### 页面配置 在`vue`的组件化中,我们还是需要标识页面级别的组件,单页面的应用更需要设置`title`和`微信分享参数`,页面界别的`vue`文件中必须含有一个`pageInfo`。 ``` javascript pageInfo: { // 当前页面的标题 title: '急诊地图', // 是否展示微信右上角的选项 showOptionMenu: true, // 分享信息 shareInfo: { // 分享需要记录的key shareKey: 'WX_JZDT0000002', // 分享的信息 title: '【收藏文】原来急诊也要跑对医院', iconUrl: 'https://img.benmu-health.com/wechatV2/img/mapShare.jpg', link: 'https://wechat.benmu-health.com/wechatV2/#/hosMap.intro', desc: '患者在线可查询医院急诊项目范畴,获得精准救助' } } ``` ### `ajax 规范` 底层使用`axios`,需要了解`API`自行了解`axios`库,需要在`service`中配置好自己的请求发送参数如 ``` javascript { // 接口名称,调用时会用到,注意重复 name: 'detail', // 发送请求类型 method: 'GET', // 接口描述 desc: '医生个人信息接口', // 本地地址 localPath: '/test/sectionDoctor/detail', // 线上地址 path: '/mobile/wx/individualDoc/queryById', // 发送出去的参数 params: { key: value } } ``` 业务中调用方式 ``` javascript this.$service['xxx/detail']({ key: value }, { // 额外的参数 如:header noShowDefaultError: true }).then((data) => { // 已经过滤了 resData.data,可直接使用,无需判断 // 具体的过滤器在 config/ajax 中 }, () => { // 失败的回调,错误已经自动报了,一般无需关心 }); ``` ### `router` 命名规范 命名尽量遵循`父级路由.当前路由`的规则,一般路由路径不能超过三层,需要考虑效率问题 路由配置文件在 `config/router` 中,路由配置规则尽量遵循父子路由的关系,子路由都配置在父路由的 `children` 属性中,配置自路由的好处是在打开子路由返回的时候,父级路由的转台都还保存着 订单模块,有搜索条件的页面,搜索结果列表页面,搜索详情页面,构造出来的路由如下: ``` javascript { path: '/order', component: Order, children: [{ path: '/search', component: Search },{ path: '/list', component: List, children: [{ path: '/detail', component: Detail }] }] } ``` 父路由中模板文件也需要增加对应的自路由组件 ``` javascript <transition name="child"> <router-view></router-view> </transition> ``` 路由中的`channel`和`from`参数会自动带到下一个页面,这个是为了每个业务自动记录当前业务的渠道 router 配置中还有有一些默认的 meta 参数 ``` javascript { name: '', path: '', component: , meta: { // 页面是否需要登录,如果需要登录且没有获取到用户登录信息就会弹到登录页面,登录成功之后自动返回 needLogin: true, // 当前页面的 pv 统计参数 pvKey: 'WX_JZDT0000003' } } ``` 下面是业务中新开页面和返回页面的使用: ``` javascript this.$router.forword({ // 不要路径,一般都写 name name: '' }) // 默认返回一步 this.$router.back() === history.go(-1) // 多级返回 this.$router.back({ // name,返回到堆栈中最近的一个 name 路由 name: '' // length 代表返回的路径长度 length: '' }) ``` 页面路由切换代码 ### `store` 文件夹下命名规范 我们的 `store` 的命名规范基本上是围绕着 `pages` 来做的,`pages` 确定之后,其实 `store` 就已经确定了。全局的`store`是放在根目录下的`store`里的,其他的业务`store`都是跟着业务走的 使用都是跟着路由的生命周期完成的,这里并不是真正的把`store`移除,实质上只是在当前的`store`树上解除了引用关系,下来再次加回来的时候状态都还在。如果想要每次都是新的状态,应该在`state`的声明的时候返回一个纯函数,每次使用的时候都是新的状态。 ``` javascript beforeRouteEnter(to, from, next) { store.install() next() }, beforeRouteLeave(to, from, next) { store.uninstall() next() } ``` ### `actions` 命名规范 由于项目中的 `store` 是合并到一个 `store` 上,获取 `actions` 的方式又是通过 `mapActions`,所以需要做好命名的区分。 一般我们是用,由于有命名空间,在多个模块间共用的`actions`不能重复: DO_something GET_HOSLIST ### `getter` 命名规范 由于项目中的 `store` 是合并到一个 `store` 上,获取 `getter` 的方式又是通过 `mapGetters`,所以需要做好命名的区分 有了命名空间,一般是不会重复,一般我们是用: ...mapGetters('tab', ['activeTab', 'showMask']) ### `pages` 文件夹下命名规范 我们认为 `pages` 下的子模块是遵循某种模块划分的,这样会更有利于之后维护代码,所以一般我们在 `pages` 下建立模块的时候一般是遵循以页面来划分。 那么 `pages` 下的目录结构其实就是后台功能菜单栏功能目录的一种体现。 比如有一个目录结构是: 医院管理 医院列表 对应的 `page` 下的文件目录结构就应该类似如下(我的英语不好,只能下面这样翻译了): hosManager hosList 上面这种这是模块划分映射目录划分的一种方式,我们还可以依照功能去划分,这样划分可能需要有更强的语义性 ### 模块的构成 当划分出一个子模块之后,我们不能简单粗暴的用一个 `.vue` 文件把所有业务逻辑完成,除非你的模块功能非常单一,其他的情况,我们希望把模块进行划分,由多个子 `component` 组成,划分的粒度也需要自己掌握,粒度越细越灵活,但也意味着 `component` 间的交互会变得复杂。 比如我们划分出了三个模块 `header`、`list`、`footer`,我们的目录结构按照上面的继续写就会是 ``` javascript hosManager hosList index.vue store index.js actions.js modules header.js list.js footer.js components header.vue list // 如果业务非常复杂可做一下拆分 index.vue // 参照 vue2 官网说明,这个文件是作为引入其他文件存在 index.js // js 逻辑文件 index.scss // 样式文件 index.html // html 文件 footer.vue ``` `hosList/index.vue` 仅仅是作为组织文件,将三个子模块引入,并且做好架子的角色,如 `html` 中的布局,如果 `component` 间需要事件交互,这个文件也可以充当中介者的角色。 ### `scroll` 的使用 `iscroll`放到统一的数组中,主要是因为每隔几秒需要更新,以防微信修改大字体。并且可以在某些情况下直接禁用当前页面所有的 `iscroll`,比如弹窗 ``` javascript $config: { // 标志当前业务中有 iscorll hasScroll: true, }, methods: { // iscroll 刷新 refresh(){ this.$scrollRefresh(); }, // iscroll 启用 able(){ this.$scrollAble(); }, // iscroll 禁用 disable(){ this.$scrollDisable(); } }, mounted () { // scroll 使用的时候需要 this.$config.scrollArr.push(new IScroll(el, { preventDefault: false, })); } ``` ### `beacon` 的使用 发送`beacon`记录打点,发送到`hive`中,各业务再去找对应的后端做查询,具体文件在`utils/beacon.js` ``` javascript $Beacon(key, page(可缺省), { // 保留参数,没有特殊需求这些参数不能覆盖 openId userId 时间戳 extra_[a-e]: '额外参数' // 额外需要记录的数据 extra_[d-g]: '额外参数' }); $Beacon('WX_DLZC0000005'); $Beacon('WX_DLZC0000005', '登录页', { extra_d: '注册业务' }); $Beacon('WX_DLZC0000005', { extra_d: '注册业务' }); ``` ## `vue` 使用规范 ### 组件交互 兄弟组件 父组件向子组件传递数据: props 子组件向父组件抛出事件: vm.$emit('xxx') 父组件用v-on:xxx="func"来接子组件触发的事件和暴露的数据 虽然vue还提供了`$ref`和`$parent`来让我们访问其他组件的数据和方法,但为了工程的可维护性,让我们的数据变化的追踪变得有规律可循,我们应尽量避免他们的使用 非兄弟组件 有时候非父子关系的组件也需要通信。在简单的场景下,使用一个空的 `Vue 实例作为中央事件总线` 业务简单: var bus = new Vue() // 触发组件 A 中的事件 bus.$emit('id-selected', 1) // 在组件 B 创建的钩子中监听事件 bus.$on('id-selected', function (id) { // ... }) 业务复杂: 请直接使用Vuex actions中只做异步和分发 commit应该按state结构来细分 尽量避免一个commit修改多个state ## 格式 如果不给代码做格式化就是等于代码没有写。请原谅我是 tab = 4空格档,就必须是这样的 ## 其他还没有想好,支持一切新的属性,你可以尽情的使用 ## 数组 ### 向数组增加元素时使用 Array#push 来替代直接赋值。 ```javascript var someStack = []; // bad someStack[someStack.length] = 'abracadabra'; // good someStack.push('abracadabra'); ``` ps:尽量不要频繁取值。 ### 当你需要拷贝数组时,使用 Array#slice ```javascript var item = [1,2,3]; var item_len = item.legth; var itemArr = []; var i = ''; // bad for(i = 0; i < item_len; i++){ itemArr[i] = item[i]; } // good itemArr = item.slice(); ``` ### 使用 Array#slice 将类数组对象转换成数组 ```javascript function trigger() { var args = Array.prototype.slice.call(arguments); ... } ``` ## 字符串 ### 使用单引号,包引字符串 ``` javascript var str = 'helloe world!'; ``` ### 超过 100 个字符的字符串应该使用连接符写成多行。 ```javascript var str = '<p>你好</p>'+ '<p>你好<p>'+ '<p>你好</p>'; ``` ps:尽量用“+”,不用“\”,若字符很长,需折行,不用折行,会影响性能读取问题。 ### 程序化生成的字符串使用 Array#join 连接而不是使用连接符。尤其是 IE 下 ```javascript for (i = 0; i < length; i++) { //bad items += '<li>' + messages[i].message + '</li>'; //good items[i] = '<li>' + messages[i].message + '</li>'; } //bad return '<ul>' + items + '</ul>'; //good return '<ul>' + items.join('') + '</ul>'; ``` ps:for循环,length提前取出。 ```javascript { "rules": { //官方文档 http://eslint.org/docs/rules/ //参数:0 关闭,1 警告,2 错误 // "quotes": [0, "single"], //建议使用单引号 // "no-inner-declarations": [0, "both"], //不建议在{}代码块内部声明变量或函数 "no-extra-boolean-cast": 1, //多余的感叹号转布尔型 "no-extra-semi": 1, //多余的分号 "no-extra-parens": 0, //多余的括号 "no-empty": 1, //空代码块 "no-use-before-define": [0, "nofunc"], //使用前未定义 "complexity": [1, 10], //圈复杂度大于10 警告 //常见错误 "comma-dangle": [1, "never"], //定义数组或对象最后多余的逗号 "no-debugger": 1, //debugger 调试代码未删除 "no-console": 0, //console 未删除 "no-constant-condition": 2, //常量作为条件 "no-dupe-args": 2, //参数重复 "no-dupe-keys": 2, //对象属性重复 "no-duplicate-case": 2, //case重复 "no-empty-character-class": 2, //正则无法匹配任何值 "no-invalid-regexp": 2, //无效的正则 "no-func-assign": 2, //函数被赋值 "valid-typeof": 1, //无效的类型判断 "no-unreachable": 2, //不可能执行到的代码 "no-unexpected-multiline": 2, //行尾缺少分号可能导致一些意外情况 "no-sparse-arrays": 1, //数组中多出逗号 "no-shadow-restricted-names": 2, //关键词与命名冲突 "no-undef": 0, //变量未定义 "no-unused-vars": 1, //变量定义后未使用 "no-cond-assign": 2, //条件语句中禁止赋值操作 "no-native-reassign": 2, //禁止覆盖原生对象 "no-mixed-spaces-and-tabs": 0, //代码风格优化 "no-irregular-whitespace": 0, "no-else-return": 0, //在else代码块中return,else是多余的 "no-multi-spaces": 0, //不允许多个空格 "key-spacing": [0, { "beforeColon": false, "afterColon": true }], //object直接量建议写法 : 后一个空格前面不留空格 "block-scoped-var": 1, //变量应在外部上下文中声明,不应在{}代码块中 "consistent-return": 1, //函数返回值可能是不同类型 "accessor-pairs": 1, //object getter/setter方法需要成对出现 "dot-location": [1, "property"], //换行调用对象方法 点操作符应写在行首 "no-lone-blocks": 1, //多余的{}嵌套 "no-empty-label": 1, //无用的标记 "no-extend-native": 1, //禁止扩展原生对象 "no-floating-decimal": 1, //浮点型需要写全 禁止.1 或 2.写法 "no-loop-func": 1, //禁止在循环体中定义函数 "no-new-func": 1, //禁止new Function(...) 写法 "no-self-compare": 1, //不允与自己比较作为条件 "no-sequences": 1, //禁止可能导致结果不明确的逗号操作符 "no-throw-literal": 1, //禁止抛出一个直接量 应是Error对象 "no-return-assign": [1, "always"], //不允return时有赋值操作 "no-redeclare": [1, { "builtinGlobals": true }], //不允许重复声明 "no-unused-expressions": [0, { "allowShortCircuit": true, "allowTernary": true }], //不执行的表达式 "no-useless-call": 1, //无意义的函数call或apply "no-useless-concat": 1, //无意义的string concat "no-void": 1, //禁用void "no-with": 1, //禁用with "space-infix-ops": 0, //操作符前后空格 "valid-jsdoc": [0, { "requireParamDescription": true, "requireReturnDescription": true }], //jsdoc "no-warning-comments": [1, { "terms": ["todo", "fixme", "any other term"], "location": "anywhere" }], //标记未写注释 "curly": 0 //if、else、while、for代码块用{}包围 }, "env": { "es6": true, "node": true, "browser": true, "jquery": true }, "parser": "babel-eslint", "ecmaFeatures": { "jsx": true }, "plugins": [ //"react",//写react安装该插件 "eslint-plugin-html" ] } ```