美文网首页vue
前端-Vue2.0 项目开发实战经验

前端-Vue2.0 项目开发实战经验

作者: grain先森 | 来源:发表于2018-08-14 22:34 被阅读48次

    作者:离尘不理人

    https://segmentfault.com/a/1190000011066120


    项目架构

    项目目录

    1. ├── build

    2. ├── config

    3. ├── dist

    4. │   └── static

    5. │       ├── css

    6. │       ├── fonts

    7. │       ├── images

    8. │       ├── js

    9. │       └── lib

    10. ├── src

    11. │   ├── api

    12. │   ├── assets

    13. │   │   ├── global

    14. │   │   └── images

    15. │   │       └── footer

    16. │   ├── components

    17. │   │   ├── common

    18. │   │   ├── news

    19. │   │   └── profile

    20. │   │       └── charge

    21. │   ├── config

    22. │   ├── mixin

    23. │   ├── router

    24. │   ├── service

    25. │   ├── store

    26. │   └── util

    27. └── static

    28.    ├── images

    29.    └── lib

    项目目录是采用 vue-cli 自动生成,其它按需自己新建就好了。

    开发实践

    动态修改 document title

    在不同的路由页面,我们需要动态的修改文档标题,可以将每个页面的标题配置在路由元信息 meta 里面带上,然后在 router.beforeEach 钩子函数中修改:

    1. import Vue from 'vue';

    2. import Router from 'vue-router';

    3. Vue.use(Router);

    4. const router = new Router({

    5.  mode: 'history',

    6.  routes: [

    7.    { path: '/', component: Index, meta: { title: '推荐产品得丰厚奖金' } },

    8.    {

    9.      path: '/news',

    10.      component: News,

    11.      meta: { title: '公告列表' },

    12.      children: [

    13.        { path: '', redirect: 'list' },

    14.        { path: 'list', component: NewsList },

    15.        { path: 'detail/:newsId', component: NewsDetail, meta: { title: '公告详情' } }

    16.      ]

    17.    },

    18.    {

    19.      path: '/guide',

    20.      component: GuideProtocol,

    21.      meta: {

    22.        title: '新手指南'

    23.      }

    24.    }

    25.  ]

    26. });

    27. router.beforeEach((to, from, next) => {

    28.  let documentTitle = '商城会员平台';

    29.  // path 是多级的,遍历

    30.  to.matched.forEach((path) => {

    31.    if (path.meta.title) {

    32.      documentTitle += ` - ${path.meta.title}`;

    33.    }

    34.  });

    35.  document.title = documentTitle;

    36.  next();

    37. });

    Event Bus 使用场景

    我们在项目中引入了 vuex ,通常情况下是不需要使用 eventbus 的,但是有一种情况下我们需要使用它,那就是在路由钩子函数内部的时,在项目中,我们需要在 beforeEnter 路由钩子里面对外抛出事件,在这个钩子函数中我们无法去到 this 对象。

    1. beforeEnter: (to, from, next) => {

    2.    const userInfo = localStorage.getItem(userFlag);

    3.    if (isPrivateMode()) {

    4.        EventBus.$emit('get-localdata-error');

    5.        next(false);

    6.        return;

    7.    }

    8. })

    App.vuemouted 方法中监听这个事件

    1. EventBus.$on('get-localdata-error', () => {

    2.    this.$alert('请勿使用无痕模式浏览');

    3. });

    自定义指令实现埋点数据统计

    在项目中通常需要做数据埋点,这个时候,使用自定义指令将会变非常简单

    在项目入口文件 main.js 中配置我们的自定义指令

    1. // 坑位埋点指令

    2. Vue.directive('stat', {

    3.  bind(el, binding) {

    4.    el.addEventListener('click', () => {

    5.      const data = binding.value;

    6.      let prefix = 'store';

    7.      if (OS.isAndroid || OS.isPhone) {

    8.        prefix = 'mall';

    9.      }

    10.      analytics.request({

    11.        ty: `${prefix}_${data.type}`,

    12.        dc: data.desc || ''

    13.      }, 'n');

    14.    }, false);

    15.  }

    16. });

    在组件中使用我们的自定义指令

    使用过滤器实现展示信息格式化

    如下图中奖金数据信息,我们需要将后台返回的奖金格式化为带两位小数点的格式,同时,如果返回的金额是区间类型,需要额外加上 字和 金额符号

    在入口文件 main.js 中配置我们自定义的过滤器

    1. Vue.filter('money', (value, config = { unit: '¥', fixed: 2 }) => {

    2.  const moneyStr = `${value}`;

    3.  if (moneyStr.indexOf('-') > -1) {

    4.    const scope = moneyStr.split('-');

    5.    return `${config.unit}${parseFloat(scope[0]).toFixed(config.fixed).toString()} 起`;

    6.  } else if (value === 0) {

    7.    return value;

    8.  }

    9.  return `${config.unit}${parseFloat(moneyStr).toFixed(config.fixed).toString()}`;

    10. });

    在组件中使用:

    1. <p class="price">{{detail.priceScope | money}}</p>

    2. <div :class="{singleWrapper: isMobile}">

    3.    <p class="rate">比率:{{detail.commissionRateScope}}%</p>

    4.    <p class="income">奖金:{{detail.expectedIncome | money}}</p>

    5. </div>

    axios 使用配置

    在项目中,我们使用了 axios 做接口请求

    在项目中全局配置 /api/common.js

    1. import axios from 'axios';

    2. import qs from 'qs';

    3. import store from '../store';

    4. // 全局默认配置

    5. // 设置 POST 请求头

    6. axios.defaults.headers.post['Content-Type'] = 'application/x-www-form-urlencoded';

    7. // 配置 CORS 跨域

    8. axios.defaults.withCredentials = true;

    9. axios.defaults.crossDomain = true;

    10. // 请求发起前拦截器

    11. axios.interceptors.request.use((config) => {

    12.  // 全局 loading 状态,触发 loading 效果

    13.  store.dispatch('updateLoadingStatus', {

    14.    isLoading: true

    15.  });

    16.  // POST 请求参数处理成 axios post 方法所需的格式

    17.  if (config.method === 'post') {

    18.    config.data = qs.stringify(config.data);

    19.  }

    20.  // 这句不能省,不然后面的请求就无法成功发起,因为读不到配置参数

    21.  return config;

    22. }, () => {

    23.  // 异常处理

    24.  store.dispatch('updateLoadingStatus', {

    25.    isLoading: false

    26.  });

    27. });

    28. // 响应拦截

    29. axios.interceptors.response.use((response) => {

    30.  // 关闭 loading 效果

    31.  store.dispatch('updateLoadingStatus', {

    32.    isLoading: false

    33.  });

    34.  // 全局登录过滤,如果没有登录,直接跳转到登录 URL

    35.  if (response.data.code === 300) {

    36.    // 未登录

    37.    window.location.href = getLoginUrl();

    38.    return false;

    39.  }

    40.  // 这里返回的 response.data 是被 axios 包装过的一成,所以在这里抽取出来

    41.  return response.data;

    42. }, (error) => {

    43.  store.dispatch('updateLoadingStatus', {

    44.    isLoading: false

    45.  });

    46.  return Promise.reject(error);

    47. });

    48. // 导出

    49. export default axios;

    然后我们在接口中使用就方便很多了 /api/xxx.js

    1. import axios from './common';

    2. const baseURL = '/api/profile';

    3. const USER_BASE_INFO = `${baseURL}/getUserBaseInfo.json`;

    4. const UPDATE_USER_INFO = `${baseURL}/saveUserInfo.json`;

    5. // 更新用户实名认证信息

    6. const updateUserInfo = userinfo => axios.post(UPDATE_USER_INFO, userinfo);

    7. // 获取用户基础信息

    8. const getUserBaseInfo = () => axios.get(USER_BASE_INFO);

    vuex 状态在响应式页面中的妙用

    由于项目是响应式页面,PC 端和移动端在表现成有很多不一致的地方,有时候单单通过 CSS 无法实现交互,这个时候,我们的 vuex 状态就派上用场了,

    我们一开始在 App.vue 里面监听了页面的 resize 事件,动态的更新 vuex 里面 isMobile 的状态值

    1. window.onresize = throttle(() => {

    2. this.updatePlatformStatus({

    3.   isMobile: isMobile()

    4. });

    5. }, 500);

    然后,我们在组件层,就能响应式的渲染不同的 dom 结构了。其中最常见的是 PC 端和移动端加载的图片需要不同的规格的,这个时候我们可以这个做

    1. methods: {

    2.  loadImgAssets(name, suffix = '.jpg') {

    3.    return require(`../assets/images/${name}${this.isMobile ? '-mobile' : ''}${suffix}`);

    4.  },

    5. }

    6. <img class="feed-back" :src="loadImgAssets('feed-back')"

    7. <img v-lazy="{src: isMobile ? detail.imgUrlMobile : detail.imgUrlPc, loading: placeholder}">

    8. // 动态渲染不同规格的 dislog

    9. <el-dialog :visible.sync="dialogVisible" :size="isMobile ? 'full' : 'tiny'" top="30%" custom-class="unCertification-dialog">

    10. </el-dialog>

    下图分别是 PC 端和移动短的表现形式,然后配合 CSS 媒体查询实现各种布局

    开发相关配置

    反向代理

    在项目目录的 config 文件下面的 index.js 配置我们的本地反向代理和端口信息

    1. dev: {

    2.  env: require('./dev.env'),

    3.  port: 80,

    4.  autoOpenBrowser: true,

    5.  assetsSubDirectory: 'static',

    6.  assetsPublicPath: '/',

    7.  proxyTable: {

    8.    '/api/profile': {

    9.      target: '[真实接口地址]:[端口号]', // 例如: http://api.xxx.com

    10.      changeOrigin: true,

    11.      pathRewrite: {

    12.        '^/api/profile': '/profile'

    13.      }

    14.    }

    15.    ...

    16.  },

    然后我们调用接口的形式就会变成如下映射,当我们调用 /api/profile/xxxx 的时候,其实是调用了 [真实接口地址]/profile/xxxx

    1. /api/profile/xxxx => [真实接口地址]/profile/xxxx

    nginx 配置

    1. upstream api.xxx.com

    2. {

    3. #ip_hash;

    4.  server [接口服务器 ip 地址]:[端口];

    5. }

    6. server {

    7.  ...

    8.  location ^~ /api/profile {

    9.    index index.php index.html index.html;

    10.    proxy_redirect off;

    11.    proxy_set_header Host $host;

    12.    proxy_set_header X-Real-IP $remote_addr;

    13.    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;

    14.    proxy_pass http://api.xxx.com;

    15.    rewrite ^/api/profile/(.*)$ /profile/$1 break;

    16.  }

    17.  ...

    18. }

    线上部署

    如果路由使用的是 history 模式的话,需要在 nginx 里面配置将所有的请求到转发到 index.html

    nginx.conf 或者对应的站点 vhost 文件下面配置

    1. location / {

    2.    try_files $uri $uri/ /index.html;

    3. }

    优化

    开启静态资源长缓存

    1. location ~ .*.(gif|jpg|jpeg|png|bmp|swf|woff|ttf|eot|svg)$ {

    2.    expires 1y;

    3. }

    4. location ~ .*.(js|css)$ {

    5.    expires 1y;

    6. }

    开启静态资源 gzip 压缩

    1. // 找到 nginx.conf 配置文件

    2. vim /data/nginx/conf/nginx.conf

    3. gzip on;

    4. gzip_min_length  1k;

    5. gzip_buffers     4 8k;

    6. gzip_http_version 1.1;

    7. gzip_types text/plain application/javascript application/x-javascript text/javascript text/xml text/css;

    开启了 gzip 压缩之后,页面资源请求大小将大大减小,如下图所示,表示已经开启了 gzip 压缩

    Q&A

    文章到这就结束了,如果有遗漏或者错误的地方,欢迎私信指出。希望这篇文章能带给大家一丝丝收获。

    相关文章

      网友评论

        本文标题:前端-Vue2.0 项目开发实战经验

        本文链接:https://www.haomeiwen.com/subject/yjvbbftx.html