1. 程式人生 > >吐槽以及總結(記第一次去客戶做專案,第一次負責專案的經歷)

吐槽以及總結(記第一次去客戶做專案,第一次負責專案的經歷)

我最近近一個半月,忙的真的讓我迷茫了!專案實在是太變態了,客戶!呵呵,原定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程式設計是真的有用!