某项目总结

总结项目中遇到的一些问题和解决思路,并不一定有好的解决方式,有些只是纯挖坑,还没找到解决方法

Ajax

问题:

  • 接口区分 GET、POST 等请求类型,但是为什么调用的时候要写明是哪种类型呢?不好记,也不好维护
  • loading 注入
  • 错误怎么处理(网络错误、abort、404、500...etc),哪些错误需要拦截(登录失效跳转)
  • 如何取消

解决方式一:

使用 axios 封装 ajax,并作为 vue 的插件,方便引用(比如 this.$ajax 或者 this.$http)

  • api 抽离管理,抹平调用时 GET、POST 的细节(一般来讲,一个项目的 POST 配置是一致的,个别表单数据是否序列化不统一需要特殊处理)
  • loading 可以使用 iView LoadingBar
  • 使用拦截器 interceptors 处理抛出错误
  • 取消有点麻烦

简单调用示例:

this.$ajax('getUserList', data).then((res) => {
    this.userList = res.data.list
  })

API 抽离

为什么要抽离:

  • 如果 api 分散在代码中,不好统一管理修改。改一个接口需要全局查找,难免遗漏,维护成本高
  • 一个接口可能存在重复调用
  • 不方便数据 mock,重写一遍接口地址、请求类型?(当然 proxy 的时候没这个问题)

将 API 按模块划分,写明地址和请求类型,举个栗子:

  • index.js 作为 api 的入口文件(因为 import 默认查找的是 index.js,所以入口就不要写成 main.js 那类了)
  • common.js 为公共 api
  • user.js 为 User 模块的 api
└─api
  ├─common.js
  ├─index.js
  └─user.js

  • common.js
export default {
  login: {
    type: 'POST',
    name: '/api/common/login'
  }
}
  • user.js
export default {
  getUserList: {
    type: 'GET',
    name: '/api/user/getUserList'
  }
}
  • index.js
// Common
import common from './common'

// module
import user from './user'

export default {
  baseURL: '/index.php', // 前缀
  common,
  user
}

  • 优点:
    • api 模块化拆分,方便管理
    • api 写明请求地址、请求方式,方便 mock 数据(后端有数据的情况下,使用脚手架的 proxy 会比这个方式更方便)
  • 缺点:
    • 同模块下,命名不能冲突,命名麻烦(脑阔疼,命名是程序员头疼的问题之一)
  • RESTful 情况下,这种数据结构不合适(比如 /:id 的情况)

API 调用

数据获取可以单独封装、维护,需要返回 Promise,方便管理(Promise.all)

  • 普通方式
    • ajax
  • vuex 方式
    • dispatch / action

代码规范

Programs are meant to be read by humans and only incidentally for computers to execute.

可以看看 《编写可维护的javascript》这本书,挺不错的

代码尽量按模块功能拆分,方便维护

脏代码必然存在,尽量丢到一起

注释,还是加上吧(虽然我也经常忘记写注释...)

后台这种项目基本是永久维护的,不同的人有不同的编码习惯,接手的人多了,风格也就多了

项目中个人还是倾向使用 ESLint,虽然开始用起来,可能大部分错误都是代码格式错误,但是,基本用一段时间就能很快适应

if

使用 === 代替 == 判断数据类型。
如果自己在代码中对于数据类型都不能十分确定,这个代码写的就很有问题。就像改 bug 一样,为什么会出现这个 bug、为什么这样解决、影响范围有哪些都不清楚,往往会改出其他 bug

if 嵌套不能太深:

  • 可读性差
  • 超过两种情况的判断,优先考虑 switch

let、const

消除使用 var 声明变量

  • let: 声明的变量可以改变,值和类型都可以改变,没有限制
  • const:用来声明常量,所谓常量就是物理指针不可以更改的变量。只是保证变量名指向的地址不变,并不保证该地址的数据不变(除了不能改变物理指针的特性,其他特性和 let 一样)

CSS 部分

做后台管理后,已经基本不写, 也基本不用写 css 了。
虽然 iView 框架已有内置的许多组件样式、栅格化这些基本的都有。但是实际用起来,感觉还是少了一些什么。比如布局、间距调整
然后问题便来了,额外的样式怎么处理。

iView 和 Bootstrap 样式混合

...
import iView from 'iview'
import 'iview/dist/styles/iview.css'

import {Select, Option} from 'element-ui'
import 'bootstrap/dist/css/bootstrap.min.css'
import 'bootstrap/dist/js/bootstrap.min'

import 'font-awesome/css/font-awesome.min.css'
import './assets/css/style.css'
...

看到 iView 和 Bootstrap 的样式混在一起,还是很懵逼的,而且还写了一些自定义的样式 style.css = =
那么问题来了:

  • 为什么需要引入 Bootstrap ? 因为用了 Bootstrap 的一些组件,比如 Modal,其实 iView 也有...
  • 为什么又定义 style.css ?因为需要设置一些通用布局,比如页面框架布局、margin、padding 等...

iView 和 Bootstrap 的 normalize.css 版本不一致,其他地方对于某个元素默认设置还是有些出入的,引入会有隐患问题。

瞄了一眼 style.css:INSPINIA - Responsive Admin Theme,一个基于 Bootstrap 3 的框架

感觉这么多混在一起,很乱,如何让接手的人知道如何复用已有的样式是个问题

scoped

<style> 标签有 scoped 属性时,它的 CSS 只作用于当前组件中的元素。
如果希望 scoped 样式中的一个选择器能够作用得“更深”,例如影响子组件,可以使用 >>> 操作符:

<style scoped>
.a >>> .b { /* ... */ }
</style>

上述代码将会编译成:

.a[data-v-f3f3eg9] .b { /* ... */ }

有些像 Sass 之类的预处理器无法正确解析 >>>。这种情况下可以使用 /deep/ 操作符取而代之——这是一个 >>> 的别名,同样可以正常工作。

<style lang="scss" scoped>
 /deep/ .a .b { /* ... */ }
</style>

数据流

目前许多公共数据是放在 Storage 里面的,那么:

  • 何时获取公共数据 ?
    • 可以放在根组件 App.vue 里面触发
  • 何时更新这些数据 ?
    • 如果放在 sessionStorage,那么新开窗口打开后,将重新获取数据
    • 如果放在 localStorage 里面,那么不手动清除,将一直存在
  • 可以考虑 Vuex

公共数据为了节流,需要避免重复请求,可以存在本地。但是,如果存在本地,则需过期清除数据。那么,何时过期是个很麻烦的问题。就拿公共配置数据来说,配置数据何时被修改,是不可控、不可预期的。设置半天过期,那么,用户拿到的就有可能是旧的数据,会产生错误数据。所以,不如每次登录后,拉取服务端数据一次(重复请求,读取本地数据),存起来,这样请求量也不大,对服务端压力也不大。

vue-router

比起 iframe 的模式,vue-router 还是轻便很多。router 多起来之后,维护也是个问题

路由按模块拆分

路由按模块拆分,建议懒加载的形式(目前 octet 已经是这样了)
每个子节点路由,可以新建一个 index.vue 文件,这样,便可以在这个节点的生命周期里统一做一些操作,比如获取这个节点所需要的公共数据。

component: {template: '<router-view></router-view>'},
children: [...]

转成:

component: () => import('@/views/user'),
children: [...]

vue-router 页面刷新

路由不变的情况下,如何刷新页面?

  • 看之前的代码是写个空白页面做跳板,先跳这个页面,再跳回来
  • 参数或查询的改变并不会触发进入/离开的导航守卫,原来的组件实例会被复用。加 query 参数,比如时间戳

其实这几种我觉得都不是很好,不过也没找到更合适的方式

router 命名

菜单使用 router 配置数据显示,比如 icon、name

  • 目前菜单名称直接读取的 route.name, 可以把菜单名称放到 meta 里面去
    • 因为菜单名称作为文案,经常发生变动,而 route.name 经常用于路由访问,不大合适
    • route.name 可用做 router.push({ name: 'home' }),虽然 path 也行,但是 name 写起来更简洁,也易于维护 path(路由访问使用 name,当 path 变的时候,只需要改路由配置一份代码就行)
  • 菜单的 active 可以根据当前路由信息计算出来(子路由访问,不会 active 菜单,是个问题,纯路由判断会有问题,matched 目测可行)

路由权限控制

  • 现有权限控制是通过显示隐藏菜单来控制,当用户输入无权限的路由的时候,还是能访问到这个页面,无非完全避免无权限访问的问题。可以拿到权限后,追加一次路由守卫,无权限重定向到无权限页面。
  • 可以先接口获取权限,根据权限动态注册路由(router.addRoutes)。那么无权限,这个路由根本就不会存在,也就不存在访问的问题。不过 router 并不属于响应式数据,所以动态注册的路由并不会在菜单渲染,可以考虑 Storage 或者 Vuex 的方式传递路由数据。
  • 或者,获取菜单权限后,再加一个路由守护,没菜单权限直接跳 404(或者做个无权限提示页面)

浮点数计算

数字计算还是比较危险的操作,可以使用可靠的开源库,避免前端计算所产生数据误差。

表格打印

之前的打印:

let oldstr = $("body").html(); // 保存原始页面的 body 内容

$("body").html($('#print-table').html()); // 整个 body 主体换成需要打印的表格

$("body").css("height", "auto"); 
window.print(); // 打印
$("body").html(oldstr); // 原始页面的 body 内容重新塞回去

如果直接打印,不会出现什么问题。但是如果取消打印,则会报错(只允许一个 babel-polyfill 实例,估计旧内容导入了 js 代码冲突了),再点击打印则无效。

可以新建打印页面,只插入需要打印的表格数据、表格样式,以及打印逻辑,这样便不会报错:

    // 页面的 dom 以及脚本
    const html = `
        <button id="J_Print">打印</button>
        <script type="text/javascript">
            ...
        <\/script>
    `

    // 打印页面的样式
    const style = `
         <style type="text/css">
            ...
        <\/style>
    `

const $tableSheet = $(e.currentTarget).siblings('.table-sheet') // 获取需要打印的表格
const printWindow = window.open('', '') // 新开标签页

printWindow.document.write($tableSheet.html() + html + style) // 塞入表格、html 、style
printWindow.document.close()
printWindow.focus()
printWindow.printSheet() // 调用打印页面的打印方法

缺少日期格式化

不可避免的,页面上一个时间显示需要格式化处理,可以考虑 moment 简单封装。公共方法还是按类型拆分开来维护比较好,一个 common.js 太难找了

Cascader 、Tree 数据处理

这两个组件用起来还是不怎么方便的(其实感觉都不是特别好用,使用 Select 组件的 remote 模式,有默认值,点击再点开,query 会读取 lastQuery,此时 lastQuery 是空,导致被清空.....),因为数据需要转换,神烦:

  • 转换可以放到公共函数里面,如何转换可以参考:iView Cascader、Tree 数据处理
  • 或者把 Cascader 、Tree 再包一层
  • 又或者去 GitHub 提个 Feature Request。嗯,刚看了一下,400+ Issues了,感觉这个项目要刚不住了,维护人员太少了

交互

每个交互都可能会带来许多额外的代码

Loading

  • 目前 ajax 的 loading 是使用整屏的浮层转圈圈,也可以考虑 iView 自带的 LoadingBar
  • table 数据加载的时候,也应该提示加载中,而不是无数据显示
    • 无数据
    • 加载中
    • 加载失败,xxx
    • 列表显示
  • 表单提交的时候,提交取消返回/关闭 按钮,应该禁用状态、并且不可点

返回列表是否刷新

  • 创建数据,成功后返回之前列表页,并清除搜索参数、页码

    理由:大部分需求,期望每次创建数据后,都回到第一页,并能看到创建的该条数据

  • 编辑数据,成功后返回之前列表页(保留搜索参数、页码),可考虑 keep-alive 组件,参考思路:vue-router 之 keep-alive(但是 iView 的 Table 组件的 hover 效果会被缓存,没有想到对于的解决思路)。但是这种每个都写 meta 属性也是很麻烦

    理由:只是修改该条数据,返回还是那页,能看到那条数据,不需要重新搜索一次(想想每次搜出一批数据编辑,编辑一次重新搜索,真是...)

表单提交错误提示

  • 创建、编辑失败,错误信息,最好放置页面上,而非弱弹窗的形式。因为弱弹窗只有那么几秒显示,一闪而过,基本不能让用户看到完错误信息。


    表单提交错误提示放置表单底部
  • 表单,在退出后,需要重置(数据、错误提示)
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 158,736评论 4 362
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,167评论 1 291
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 108,442评论 0 243
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,902评论 0 204
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,302评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,573评论 1 216
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,847评论 2 312
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,562评论 0 197
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,260评论 1 241
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,531评论 2 245
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,021评论 1 258
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,367评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,016评论 3 235
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,068评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,827评论 0 194
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,610评论 2 274
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,514评论 2 269

推荐阅读更多精彩内容

  • 目录 UI组件 开发框架 实用库 服务端 辅助工具 应用实例 Demo示例 UI组件 element★13489 ...
    余生社会阅读 19,514评论 7 233
  • 转载 :OpenDiggawesome-github-vue 是由OpenDigg整理并维护的Vue相关开源项目库...
    果汁密码阅读 22,985评论 8 125
  • awesome-github-vue 是由OpenDigg[https://blog.csdn.net/opend...
    我是七月阅读 2,392评论 0 20
  • 傍晚 树下那把木椅 像个情人 等着我去 赴约 夜色准时的 将孤独 抛给肉体 平静的湖面 一只遍体鳞伤的鱼 爬上了岸...
    长白散人阅读 191评论 0 5
  • 挖坑 —————————— 首先,先实现FFmpeg+SDL+MFC实现图形界面视频播放器。 在搜MFC的时候发现...
    梧叶已秋声阅读 996评论 0 2