吐槽以及总结(记第一次去客户做项目,第一次负责项目的经历)

我最近近一个半月,忙的真的让我迷茫了!项目实在是太变态了,客户!呵呵,原定45天的开发时间硬生生被客户反悔压缩到21天预上线,造成这样,就是因为甲方公司的领导的一句话。。。。连续上班30小时还不让休息,每天11点之后下班,周末还经常加班的日子真的是日了狗了!!(...此处省略一千字)

我是前端负责人,也是我一个人来进行前端开发,pc端和钉钉应用的开发还有一个pc移动都要适配的机密文件列表项目!

毕竟第一次,之前只是写页面,写页面,写页面。现在从无到有,发现收获确实多了很多。一个完整的前端项目在我手中出现,是真的很有自豪感的,虽然有的时候会害怕自己能力不够,还好都挺过来了。主要是pc端项目居然还要兼容ie10+,幸亏只要是10+,否则我真的要吐血!!

下面就不吐槽了,不想那些恶心的人和事了,总结一下自己的收获,项目的从无到有!(纯粹是想到什么写什么)

  1. PC端
     

vu-cli2 + 开发(为什么没用3,客户是baba??),基本没改什么配置,因为是简单的页面

1 新增test环境

   在config下新建test.env.js

module.exports = {
  NODE_ENV: '"production"',
  ENV_CONFIG: '"test"',
  BASE_API: '"https://api-sit"'
}

  修改dev 和prod.env.js 新增 ENV_CONFIG 属性 一个为dev 一个为prod, BASE_API 我这边其实没用到,因为我这里有多个系统,不只一个base

   修改build.js 中

const spinner = ora(
  'building for ' + process.env.env_config + ' environment...'
)

    修改webpack.prod.conf.js 中的

const env = require('../config/' + process.env.env_config + '.env')

  修改 package.json 打包命令

  

 "dev": "webpack-dev-server --host 0.0.0.0 --inline --progress --config build/webpack.dev.conf.js",
    "start": "npm run dev",
    "lint": "eslint --ext .js,.vue src",
    "build:prod": "cross-env NODE_ENV=production env_config=prod node build/build.js",
    "build:test": "cross-env NODE_ENV=production env_config=test node build/build.js "

dev的命令 我加了--host 0.0.0.0 是为了可以将自己的ip地址代替也可以访问项目

页面标题栏添加logo

  在webpack.prod.conf.js 和 webpack.dev.conf.js ,新增代码

function resolve(dir) {
  return path.join(__dirname, '..', dir)
}

 在 new HtmlWebpackPlugin 中添加 favicon: resolve('favicon.ico'),     注意favicon.ico 放在和index.html同一级下,

重新启动项目即可,chrome下会有缓存,请强制刷新一下,就会出来。

3 scss全局变量

 scss导入依赖不说了,建议cnpm,修改build目录下 util.js 的 generateLoaders 函数,

   scss: generateLoaders('sass').concat({
      loader: 'sass-resources-loader',
      options: {
        resources: path.resolve(__dirname, '../src/assets/scss/_variables.scss')
      }
    }),

_variabled.scss 中 类似

$base_color_blue: #1E42A6;
$base_color_black: #151515;
$base_color_grey: #999999;
$base_color_normal: #333333;

4 icon使用

原本是使用的svg-icon,客户前端说为了统一处理,说不要这么复杂,给了个字体库 http://www.fontawesome.com.cn/,做到一半嗯啊给我全删了!!!!!!!!

我直接npm i font-awesome --save-dev,之后直接在main.js  导入 import 'font-awesome/css/font-awesome.css',注意这里面有字体库,会影响pc mac 与windows的字体设置

4 elementUi的按需加载

按照官网来即可

此项目没有登陆页,单点登陆

so,我将在进入首页前给其拦截,当然你也可以设置全局守卫,在router文件下的index.js 中

 {
        path: '/',
        name: 'portal',
        beforeEnter: (to, from, next) => {
          let token = store.getters.token
          if (!token) {
            if (!getQueryString('username')) {
              window.location.href = `${POR_BASE_URL}/casLogin?redirect=${POR_LOGIN_LOGOUT}`
              return
            } else {
              let _data = {
                username: getQueryString('username'),
                password: getQueryString('ticket'),
                client_id: config.CLIENT_ID,
                client_secret: config.CLIENT_SECRET
              }
              getToken(_data).then((res) => {
                store.commit('SET_TOKEN', res.access_token)
                getNameDetail().then((res) => {
                  if (res.rows && res.rows.length > 0) {
                    let _data = {
                      name: res.rows[0].name,
                      gender: res.rows[0].gender,
                      unitName: res.rows[0].unitName,
                      userName: res.rows[0].userName
                    }
                    store.commit('SET_USERDATA', _data)
                    getTodoToken().then((res) => {
                      store.commit('SET_TODO_TOKEN', res.access_token)
                      next()
                    }).catch(() => {next()})
                  } else {
                    this.$message.error('用户信息获取失败')
                    next()
                  }
                }).catch(() => {next()})
              }).catch(() => {
                next()
              })
            }
          } else {
            validateToken().then((res) => {
              next()
            }).catch((err) => {
              if (err.response.status === 401) store.commit('REMOVE_TOKEN')
            })
          }
        },

没办法,进首页会有点慢,毕竟我这边需要两套token,建议将信息放在cookie里面,而且我这边是会话的cookie,我之前是放在localStorage里面,这会有一个bug,360浏览器,QQ浏览器不是有两个内核吗,如果切换,因为他们的切换 localStorage和sessionStorage是两个东西,就会导致切花后token不一致,用户不一致,Cookie就没问题。

6 不同环境下多个baseURL的封装

**
 * 配置不同环境接口前缀
 * 如果未配置dev为默认配置
 * @param {*} base
 * @example
 * {
 *   dev: 'dev环境配置信息',
 *   test: 'test环境配置信息',
 *   prod: '线上环境配置信息'
 * }
 */
function conf (base = {}) {
  if (process.env.NODE_ENV === 'production') { // 生产环境下
    let env = process.env.ENV_CONFIG || 'dev'
    return base[env] || base['dev']
  }
  // 开发环境
  return base['dev']
}

// 登陆退出重定向base
export const POR_LOGIN_LOGOUT = conf({
  dev: 'http://dev....',
  test: 'http://test....',
  prod: 'http://prod....'
})

环境配置也可以模仿这样写

7 axios的封装

不细讲,类似

// portal base
export const porApi = axios.create({
  baseURL: POR_BASE_URL, // api 的 base_url
  timeout: 15000, // request timeout
  headers: {
    'Content-Type': 'application/json'
  }
})

拦截器,是否需要统一在这里将错误给抛出去,有的api报错并不需要抛出来,看具体情况而言,

说的是

// request 拦截器
const requestInterceptor = config => {
  // Do something before request is sent
  if (store.getters.token) {
    config.headers.common['Authorization'] = `Bearer ${store.getters.token}`
  }
  return config
}

这样就可以在每次请求中,请求头都携带token,我之前陷入这个坑蛮久,我以为,只要写defult即可,我发现第一次文件加载的时候。token并没有,导致后续api都不会有token,需要显示的带到api中,太麻烦了!!!

401 的操作

8 api的封装,个人比较懒又封装了一下

/**
 * 公用方法集合
 */
const requestData = (nAxios, methods, url, datas, headers, timeout) => {
  let options = Object.assign({}, {
    url: url,
    method: methods,
    data: datas
  })
  if (headers) {
    options.headers = headers
  }
  if (timeout) {
    options.timeout = timeout
  }
  let listPromise = new Promise((resolve, reject) => {
    nAxios.request(options)
      .then(res => {
        resolve(res)
      }).catch(res => {
        reject(res)
      })
  })
  return listPromise
}
const queryData = (nAxios, url, query) => {
  let tempQuery = ''
  for (let key in query) {
    if (query[key] !== undefined && query[key] !== '' && query[key].length !== 0) {
      tempQuery += '&' + key + '=' + query[key]
    }
  }
  let listPromise = new Promise((resolve, reject) => {
    nAxios.get(url + tempQuery).then((rst) => {
      resolve(rst)
    }).catch(error => {
      reject(error)
    })
  })
  return listPromise
}

header是和tomeout单独给其添加进去,是为了应付需要不同的headers的情况,timeout的话,因为我这里调的第三方文件服务,如果文件很大的情况,会需要时间很长,延长timeout即可。

如果需要兼容IE的话,queryData中url需要通过encodeURI解码,才会保证参数中传递中文不会乱码

使用: 

export const getProcessList = (count) => {
  return requestData(porApi, 'get', `/api/。。。?limitCount=${count}`)
}

9 clickoutside 使用

直接在elementui中将clickoutside.js 和 dom.js 中拷贝出来,我这里需要一个需求,一个页面需要点击一个按钮,出现一个下拉列表,点击再次点击按钮 和点击下拉列表以外的地方都会将其列表收缩,

修改 新增 clickoutside.js 代码即可,

10  mixin store filter 我就不讲了。。。。页面适配:

在index.html中加入

  <meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no">
<script>
    (function (doc, win) {
      var docEl = doc.documentElement
      var resizeEvt = 'orientationchange' in window ? 'orientationchange' : 'resize'
      var recalc = function () {
        var clientWidth = docEl.clientWidth
        if (!clientWidth) return
        if (clientWidth <= 1366) docEl.style.fontSize = '24px'
        else docEl.style.fontSize = 24 * (clientWidth / 1366) + 'px'
      }
      if (!doc.addEventListener) return
      win.addEventListener(resizeEvt, recalc, false)
      doc.addEventListener('DOMContentLoaded', recalc, false)
    })(document, window)
  </script>

ui设计就是按照客户需求,1366来的,我这边以基础12px的两倍24px为基础,实现rem的适配。

pc暂时就写这么多吧。。。 

  1. 移动钉钉:

1 普通h5开发

我之前是想入E应用的坑的,奈何甲方不愿意,说不好维护,那只好普通h5开发。简单粗暴的在index.html中引入

<script type="text/javascript" src="https://g.alicdn.com/dingding/open-develop/1.6.9/dingtalk.js"></script>

2 移动端适配

采用淘宝的lib-flexible 和 px2rem-loader 进行适配,这种方式对内联的px无法进行转换,而且会对第三方库的所有px都会转成rem,谨慎使用,因为我这边页面较少,采用了这个方案,但是按375pxiphone6来设计,应该不会出什么问题。

3 使用了vux的search,better-scroll和lazyload图片懒加载,fastclick点击

4 我这边并不需要dd.config,首页拦截

在页面进入的首页

 beforeRouteEnter (to, from, next) {
    if (!window.sessionStorage.getItem('AUTH_TOKEN')) {
      dd.ready(function () {
        dd.runtime.permission.requestAuthCode({
          corpId: process.env.CORP_ID,
          onSuccess: function (result) {
            _this.$vux.loading.show()
            tokenForDing(result.code).then((res) => {
              window.sessionStorage.setItem('AUTH_TOKEN', res.data.access_token)
              next()
            }).catch(error => {
              _this.$vux.loading.hide()
              _this.$vux.toast.show({
                type: 'text',
                width: 'auto',
                text: '登陆失败',
                position: 'top'
              })
            })
          },
          onFail: function (err) {
            alert(JSON.stringify(err))
          }
        })
      })
    } else {
      next()
    }
  },

token放到session中,每次退出应用,sessionStorage都会自动清除。

5 vux的toast和loading在js中使用

vux提供了插件形式的预览,和alertModel不一样,但这也是基于vue实例来说,so, 可以在main.js中

import { LoadingPlugin, ToastPlugin } from 'vux'
Vue.use(LoadingPlugin)
Vue.use(ToastPlugin)

在页面中导入 import _this from '@/main'

使用: _this.$vux.loading.show()

6 title统一设置,路由缓存

在router的index.js中,类似

  {
      path: '/detail',
      name: 'detail',
      component: Detail,
      meta: {
        title: '详情',
        scrollToTop: true,
        keepAlive: false
      }
    }
router.beforeEach((to, from, next) => {
  if (to.meta.title) {
    document.title = to.meta.title
  } else {
    if (to.query.type === 'NEWS') {
      document.title = '新闻动态'
    } else {
      document.title = '发文公告'
    }
  }
  next()
})

我这里有两个应用,代码基本一致,so就不打算copy一份代码,直接代码共用,通过钉钉设置的type来给其设置title。

App.vue中

<keep-alive>
      <router-view v-if="$route.meta.keepAlive"></router-view>
    </keep-alive>
    <router-view v-if="!$route.meta.keepAlive"></router-view>

7 better-scroll记住滚动条位置

效果就是:列表页跳到详情页,1 返回不刷新,2 滚动条还是在那个位置

在列表页,监听scrollEnd

scrollEnd (pop) {
  sessionStorage.setItem('scrollY', pop.y)
},

在watch中监听route

  '$route' (to, from) {
      if (to.name === 'news' && from.name === 'detail') {
        if (this.$refs.scroll) {
          this.$refs.scroll.refresh() // 必须先刷新一下,不然报错
          let scrolly = sessionStorage.getItem('scrollY')
          if (scrolly) {
            this.scrollTo(Number(scrolly))
          }
        }
      }
    },


   scrollTo (scrollY) {
      this.$refs.scroll.scrollTo(0, scrollY, this.scrollToTime)
    },

我这里将scroll封装了一个组件
  1. 机密文件(pc与移动):

原本是打算用一套代码,只是部分css不同,适配不同,部分js代码不同,结果客户需求一变再变,其实也就是个噱头,通过首页function来判别移动端还是pc端,最终通过此来写代码

1 区分移动与pc

新建一个js文件,

import Vue from 'vue'
(function () {
  var ua = navigator.userAgent.toLowerCase()
  var contains = function (a, b) {
    if (a.indexOf(b) !== -1) {
      return true
    }
  }
  if (contains(ua, 'ipad') || (contains(ua, 'rv:1.2.3.4')) || (contains(ua, '0.0.0.0')) || (contains(ua, '8.0.552.237'))) {
    return false
  }
  if ((contains(ua, 'android') && contains(ua, 'mobile')) || (contains(ua, 'android') && contains(ua, 'mozilla')) || (contains(ua, 'android') && contains(ua, 'opera')) || contains(ua, 'ucweb7') || contains(ua, 'iphone')) {
    Vue.prototype.$isPC = false
  } else {
    Vue.prototype.$isPC = true
  }
})()

在main.js中注入:

import '@/utils/client'

注入全局变量$isPC来方便之后的代码操作,具体参考百度webapp版

链接:https://blog.csdn.net/kongjiea/article/details/17612899

移动端进行适配

首先区分是移动端,之后再进行适配,pc端因为比较简单就没有进行适配,移动端是大头!

在index.html中

<script>
    (function () {
      var ua = navigator.userAgent.toLowerCase()
      var contains = function (a, b) {
        if (a.indexOf(b) !== -1) {
          return true
        }
      };
      if (contains(ua, "ipad") || (contains(ua, "rv:1.2.3.4")) || (contains(ua, "0.0.0.0")) || (contains(ua, "8.0.552.237"))) {
        return false
      }
      if ((contains(ua, "android") && contains(ua, "mobile")) || (contains(ua, "android") && contains(ua, "mozilla")) || (contains(ua, "android") && contains(ua, "opera"))
        || contains(ua, "ucweb7") || contains(ua, "iphone")) {
        (function(doc, win) {
          var docEl = doc.documentElement,
            isIOS = navigator.userAgent.match(/\(i[^;]+;( U;)? CPU.+Mac OS X/),
            dpr = isIOS ? Math.min(win.devicePixelRatio, 3) : 1,
            dpr = window.top === window.self ? dpr : 1, //被iframe引用时,禁止缩放
            dpr = 1,
            scale = 1 / dpr,
            resizeEvt = 'orientationchange' in window ? 'orientationchange' : 'resize'
          docEl.dataset.dpr = dpr
          var metaEl = doc.createElement('meta')
          metaEl.name = 'viewport'
          metaEl.content = 'initial-scale=' + scale + ',maximum-scale=' + scale + ', minimum-scale=' + scale
          docEl.firstElementChild.appendChild(metaEl)
          var recalc = function() {
            var width = docEl.clientWidth
            if (width / dpr > 750) {
              width = 750 * dpr
            }
            // 乘以100,px : rem = 100 : 1
            docEl.style.fontSize = 100 * (width / 750) + 'px'
          }
          recalc()
          if (!doc.addEventListener) return
          win.addEventListener(resizeEvt, recalc, false)
        })(document, window)
      }
    })()
  </script>

具体参考网易做法 链接:https://segmentfault.com/a/1190000012225828

页面中以750px为基础,1rem代表100px

3 UI选择

ui采用elementUI 的部分,因为功能小,elementUI的input button loading都可以在app中完美呈现

4 input框

你会发现 elementUI的input框在ios和safari上有一个小样式的bug,它的光标会撑满整个input框,导致很难看,只需要判别再将

line-height 改为normal即可

5 小知识

你会发现在移动端登陆页面的时候,登入input框会被顶上去,但有的时候顶的并不是很理想,软键盘会将输入框盖住!so,给其一个

 focous () {
      if (!this.$isPC) {
        window.scrollTo(0, 150)
      }
    },

150的高度差不多,根据自己的页面情况具体设置多少而定。

当然可以介绍两个api 绑定input框,就不怕软键盘挡住input输入框

Element.scrollIntoViewIfNeeded 和 scrollIntoView

失焦的时候,有的ios会将页面顶上去就下不来了,给其一个方法即可!

 blur () {
      if (!this.$isPC) {
        window.scrollTo(0, 0)
      }
    }

6 IOS微信的扫一扫

后期加上免登,就是二维码中带有用户信息,微信扫一扫打开自动免登的功能,发现只有ios版微信有这个问题,问题是:我这边接受到了参数,将参数去掉部分,结果发现ios微信中,分享出去的链接永远是首页地址,我这边就是调用其自带的扫一扫,而不是需要注入jssdk的配合,google搜搜搜,发现,需要location.href 重新刷新一遍即可!

区分微信浏览器:

const isWeixn = () => {
  let ua = navigator.userAgent.toLowerCase()
  if (ua.match(/MicroMessenger/i) == 'micromessenger') {
    return true
  } else {
    return false
  }
}

区分ios

 let isIos = !!window.navigator.userAgent.match(/\(i[^;]+;( U;)? CPU.+Mac OS X/)

7 二维码免登

客户提了个需求,自己扫二维码免登,分享出去就是需要登陆(不考虑二维码被泄露),毕竟其实也不机密,讲个思路吧,最先想到的就是将token带到二维码里面,但是token长度很长,导致生成的二维码密密麻麻,所以就搞了个api,token来换uuid,利用uuid和登陆账户带过去,我这边判断如果有参数的话,就去获取拿token成功跳转详情列表,失败登陆页面,否则都是登陆页面。

8 指定页面浏览器放大缩小

因为我这边机密文件,其实我们这边是获取了永中服务加水印的html链接,是用iframe嵌套的,用户需要放大浏览器,这就和viewport有关,只是需要某个页面才能进行缩放,

index.html:

 <meta name="viewport" id="view" content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no">

vue页面:

  mounted () {
    document.getElementById('view').setAttribute('content', 'user-scalable=yes, width=device-width, minimum-scale=1, initial-scale=1, maximum-scale=3')
  },
  beforeDestroy () {
    document.getElementById('view').setAttribute('content', 'user-scalable=no, width=device-width, minimum-scale=1, initial-scale=1, maximum-scale=1')
  }

但是这在钉钉上打开,如果页面放大的话,返回虽然最大已经变成1了,但是页面放大率还是没变,需要手动刷新一下才可以,beforeEnter路由守卫下

beforeRouteEnter (to, from, next) {
  let FIRST_PAGE = window.sessionStorage.getItem('FIRST_PAGE')  // 这是和我的其他逻辑有关的
  if (from.name === 'detail' && FIRST_PAGE) {
    window.location.href = process.env.FILE_BASE_URL + to.fullPath
  } else {
    next()
  }
},

想不到啥了,其实很有很多细节点,暂时想不到了,就算了,反正收获很大!面向google编程是真的有用!

猜你喜欢

转载自blog.csdn.net/shentibeitaokong/article/details/85111979