vue2.0 项目开发小结

项目架构

项目目录

├── build
├── config
├── dist
│?? └── static
│??     ├── css
│??     ├── fonts
│??     ├── images
│??     ├── js
│??     └── lib
├── src
│?? ├── api
│?? ├── assets
│?? │?? ├── global
│?? │?? └── images
│?? │??     └── footer
│?? ├── components
│?? │?? ├── common
│?? │?? ├── news
│?? │?? └── profile
│?? │??     └── charge
│?? ├── config
│?? ├── mixin
│?? ├── router
│?? ├── service
│?? ├── store
│?? └── util
└── static
    ├── images
    └── lib 

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

开发实践

动态修改 document title

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

import Vue from ‘vue‘;
import Router from ‘vue-router‘;

Vue.use(Router);
const router = new Router({
  mode: ‘history‘,
  routes: [
    { path: ‘/‘, component: Index, meta: { title: ‘推荐产品得丰厚奖金‘ } },
    {
      path: ‘/news‘,
      component: News,
      meta: { title: ‘公告列表‘ },
      children: [
        { path: ‘‘, redirect: ‘list‘ },
        { path: ‘list‘, component: NewsList },
        { path: ‘detail/:newsId‘, component: NewsDetail, meta: { title: ‘公告详情‘ } }
      ]
    },
    {
      path: ‘/guide‘,
      component: GuideProtocol,
      meta: {
        title: ‘新手指南‘
      }
    }
  ]
});

// 使用 afterEach 钩子函数,保证路由已经跳转成功之后修改 title
router.afterEach((route) => {
  let documentTitle = ‘xxx商城会员平台‘;
  route.matched.forEach((path) => {
    if (path.meta.title) {
      documentTitle += ` - ${path.meta.title}`;
    }
  });

  document.title = documentTitle;
});

根据 URL 的变化,动态更新数据

通常在一个列表集合页,我们需要做分页操作,同时分页数据需要体现在 URL 中,那么如何动态的根据 URL 的变动来动态的获取数据呢,我们可以使用 watch API,在 watch 里面监听 $route,同时使用 this.$router.replace API 来改变 URL 的值。下面是示例代码 common.js


import qs from ‘qs‘;

export default {
  data() {
    return {
      queryParams: {
        currentPage: 1,
        pageSize: 10
      }
    };
  },
  methods: {
    handlePageNoChange(e) {
      this.queryParams.currentPage = e;
      this.replaceRouter();
    },

    replaceRouter() {
      const query = qs.stringify(this.queryParams);
      this.$router.replace(`${location.pathname}?${query}`);
    },

    routeChange() {
      this.assignParams();
      this.fetchData();
    },

    assignParams() {
      this.queryParams = Object.assign({}, this.queryParams, this.$route.query);
    }
  },
  mounted() {
    this.assignParams();
    this.fetchData();
  },
  watch: {
    $route: ‘routeChange‘
  }
};

我们将这部分代码抽取到一个公共的 mixin 中,在需要的组件那里引入它,同时实现自定义的同名 fetchData() 方法
mixin API 文档:https://cn.vuejs.org/v2/guide...

export default DemoComponent {
  mixins: [common],
  data() {
    return {
      // 组件内部自定义同名查询参数,将会和 mixin 中的默认参数合并
      queryParams: {
        categoryId: ‘‘,
        pageSize: 12
      },
    }
  },
  methods: {
    fetchData() {
       // 发送请求
    }
  }
}

Event Bus 使用场景

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

beforeEnter: (to, from, next) => {
    const userInfo = localStorage.getItem(userFlag);
    if (isPrivateMode()) {
        EventBus.$emit(‘get-localdata-error‘);
        next(false);
        return;
    }
})

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

EventBus.$on(‘get-localdata-error‘, () => {
    this.$alert(‘请勿使用无痕模式浏览‘);
});

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

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

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

// 坑位埋点指令
Vue.directive(‘stat‘, {
  bind(el, binding) {
    el.addEventListener(‘click‘, () => {
      const data = binding.value;
      let prefix = ‘store‘;
      if (OS.isAndroid || OS.isPhone) {
        prefix = ‘mall‘;
      }
      analytics.request({
        ty: `${prefix}_${data.type}`,
        dc: data.desc || ‘‘
      }, ‘n‘);
    }, false);
  }
});

使用路由拦截统计页面级别的 PV

由于第一次在单页应用中尝试数据埋点,在项目上线一个星期之后,数据统计后台发现,首页的 PV 远远高于其它页面,数据很不正常。后来跟数据后台的人沟通询问他们的埋点统计原理之后,才发现其中的问题所在。

传统应用,一般都在页面加载的时候,会有一个异步的 js 加载,就像百度的统计代码类似,所以我们每个页面的加载的时候,都会统计到数据;然而在单页应用,页面加载初始化只有一次,所以其它页面的统计数据需要我们自己手动上报

解决方案

使用 vue-routerbeforeEach 或者 afterEach 钩子上报数据,具体使用哪个最好是根据业务逻辑来选择。

const analyticsRequest = (to, from) => {
  // 只统计页面跳转数据,不统计当前页 query 不同的数据
  // 所以这里只使用了 path, 如果需要统计 query 的,可以使用 to.fullPath
  if (to.path !== from.path) {
    analytics.request({
      url: `${location.protocol}//${location.host}${to.path}`
    });
  }
};

router.beforeEach((to, from, next) => {
  if (to.matched.some(record => record.meta.requiresAuth)) {
    // 这里做登录等前置逻辑判断
    // 判断通过之后,再上报数据
    ...
    analyticsRequest(to, from);
  } else {
    // 不需要判断的,直接上报数据
    analyticsRequest(to, from);
    next();
  }
});

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

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

如下图中奖金数据信息,我们需要将后台返回的奖金格式化为带两位小数点的格式,同时,如果返回的金额是区间类型,需要额外加上 <span style="color:red;font-weight: bold;">起</span> 字和 <span style="color:red;font-weight: bold;">¥</span> 金额符号

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

Vue.filter(‘money‘, (value, config = { unit: ‘¥‘, fixed: 2 }) => {
  const moneyStr = `${value}`;
  if (moneyStr.indexOf(‘-‘) > -1) {
    const scope = moneyStr.split(‘-‘);
    return `${config.unit}${parseFloat(scope[0]).toFixed(config.fixed).toString()} 起`;
  } else if (value === 0) {
    return value;
  }

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

在组件中使用:

<p class="price">{{detail.priceScope | money}}</p>
<div :class="{singleWrapper: isMobile}">
    <p class="rate">比率:{{detail.commissionRateScope}}%</p>
    <p class="income">奖金:{{detail.expectedIncome | money}}</p>
</div>

axios 使用配置

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

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

import axios from ‘axios‘;
import qs from ‘qs‘;
import store from ‘../store‘;

// 全局默认配置
// 设置 POST 请求头
axios.defaults.headers.post[‘Content-Type‘] = ‘application/x-www-form-urlencoded‘;
// 配置 CORS 跨域
axios.defaults.withCredentials = true;
axios.defaults.crossDomain = true;

// 请求发起前拦截器
axios.interceptors.request.use((config) => {
  // 全局 loading 状态,触发 loading 效果
  store.dispatch(‘updateLoadingStatus‘, {
    isLoading: true
  });

  // POST 请求参数处理成 axios post 方法所需的格式
  if (config.method === ‘post‘) {
    config.data = qs.stringify(config.data);
  }

  // 这句不能省,不然后面的请求就无法成功发起,因为读不到配置参数
  return config;
}, () => {
  // 异常处理
  store.dispatch(‘updateLoadingStatus‘, {
    isLoading: false
  });
});

// 响应拦截
axios.interceptors.response.use((response) => {
  // 关闭 loading 效果
  store.dispatch(‘updateLoadingStatus‘, {
    isLoading: false
  });

  // 全局登录过滤,如果没有登录,直接跳转到登录 URL
  if (response.data.code === 300) {
    // 未登录
    window.location.href = getLoginUrl();
    return false;
  }

  // 这里返回的 response.data 是被 axios 包装过的一成,所以在这里抽取出来
  return response.data;
}, (error) => {
  store.dispatch(‘updateLoadingStatus‘, {
    isLoading: false
  });
  return Promise.reject(error);
});

// 导出
export default axios;

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

import axios from ‘./common‘;

const baseURL = ‘/api/profile‘;
const USER_BASE_INFO = `${baseURL}/getUserBaseInfo.json`;
const UPDATE_USER_INFO = `${baseURL}/saveUserInfo.json`;

// 更新用户实名认证信息
const updateUserInfo = userinfo => axios.post(UPDATE_USER_INFO, userinfo);

// 获取用户基础信息
const getUserBaseInfo = () => axios.get(USER_BASE_INFO);

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

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

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

window.onresize = throttle(() => {
 this.updatePlatformStatus({
   isMobile: isMobile()
 });
}, 500);

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

methods: {
  loadImgAssets(name, suffix = ‘.jpg‘) {
    return require(`../assets/images/${name}${this.isMobile ? ‘-mobile‘ : ‘‘}${suffix}`);
  },
}

<img class="feed-back" :src="loadImgAssets(‘feed-back‘)"

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

// 动态渲染不同规格的 dislog
<el-dialog :visible.sync="dialogVisible" :size="isMobile ? ‘full‘ : ‘tiny‘" top="30%" custom-class="unCertification-dialog">
</el-dialog>

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

开发相关配置

反向代理

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

dev: {
  env: require(‘./dev.env‘),
  port: 80,
  autoOpenBrowser: true,
  assetsSubDirectory: ‘static‘,
  assetsPublicPath: ‘/‘,
  proxyTable: {
    ‘/api/profile‘: {
      target: ‘[真实接口地址]:[端口号]‘, // 例如: http://api.xxx.com
      changeOrigin: true,
      pathRewrite: {
        ‘^/api/profile‘: ‘/profile‘
      }
    }
    ...
  },

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

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

nginx 配置

upstream api.xxx.com
{
 #ip_hash;
  server [接口服务器 ip 地址]:[端口];
}

server {
  ...
  location ^~ /api/profile {
    index index.php index.html index.html;
    proxy_redirect off;
    proxy_set_header Host $host;
    proxy_set_header X-Real-IP $remote_addr;
    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    proxy_pass http://api.xxx.com;

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

线上部署

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

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

location / {
    try_files $uri $uri/ /index.html;
}

优化

开启静态资源长缓存

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

location ~ .*\.(js|css)$ {
    expires 1y;
}

开启静态资源 gzip 压缩

// 找到 nginx.conf 配置文件
vim /data/nginx/conf/nginx.conf

gzip on;
gzip_min_length  1k;
gzip_buffers     4 8k;
gzip_http_version 1.1;
gzip_types text/plain application/javascript application/x-javascript text/javascript text/xml text/css;

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

Q&A

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

原文地址:https://www.cnblogs.com/homehtml/p/12210057.html

时间: 2024-10-24 13:06:52

vue2.0 项目开发小结的相关文章

vue2.0项目实战(3)生命周期和钩子函数详解

最近的项目都使用vue2.0来开发,不得不说,vue真的非常好用,大大减少了项目的开发周期.在踩坑的过程中,因为对vue的生命周期不是特别了解,所以有时候会在几个钩子函数里做一些事情,什么时候做,在哪个函数里做,我们不清楚. 下面来总结一下vue的生命周期. vue生命周期简介 咱们从上图可以很明显的看出现在vue2.0都包括了哪些生命周期的函数了. 生命周期探究 对于执行顺序和什么时候执行,看上面两个图基本有个了解了.下面我们将结合代码去看看钩子函数的执行. <!DOCTYPE html>

Vue2.0项目

什么是 Vue Vue 是一个前端框架,特点是 数据绑定 比如你改变一个输入框 Input 标签的值,会 自动同步 更新到页面上其他绑定该输入框的组件的值 ? 组件化 页面上小到一个按钮都可以是一个单独的文件.vue,这些小组件直接可以像乐高积木一样通过互相引用而组装起来  ? Vue2.0 推荐开发环境 ? Homebrew 1.0.6(Mac).Node.js 6.7.0.npm 3.10.3.webpack 1.13.2.vue-cli 2.4.0.Atom 1.10.2 安装环境 打开终

iOS开发——项目实战Swift篇&amp;swift 2.0项目开发总结二(开发常用)

swift 2.0项目开发总结二(开发常用) 一:相册中选择相片到App指定位置 随 着相机像素的提高,实际用户选择的图片都是很大的,有的高达5.6M,如果直接使用用户选着的图片,非常消耗内存,并且也用不到这么高像素的图片,可以当 用户选着好图片后,在UIImagePickerController对应的代理方法中,先将图片进行重新绘制为需要的大小,在设置给iconView 1 /// MARK: 摄像机和相册的操作和代理方法 2 extension MeViewController: UIIma

iOS开发——项目实战Swift篇&amp;swift 2.0项目开发总结一(开发常用)

swift 2.0项目开发总结一(开发常用) 一:新特性(版本判断)的实现 1 let versionStr = "CFBundleShortVersionString" 2 let cureentVersion = NSBundle.mainBundle().infoDictionary![versionStr] as! String 3 let oldVersion = (NSUserDefaults.standardUserDefaults().objectForKey(vers

vue2.0项目 calendar.js(日历组件封装)

最近一直闲来无事,便寻思着做一下自己的个人项目,也想说能使用现在比较流行的一些mvvm框架来做,于是就选用了这样的一个技术栈vue2.0+vue-router+vuex+webpack来做,做得也是多页面应用,使用vue-router,也是想说把多个功能模块化,单个模块spa,实现更高的效果.当然现在还在做的过程中,如果感兴趣可以过来star一下,哈哈,https://github.com/xiaobinwu/Wuji,git clone下来看看. 今天要说的是在做这个项目的过程中,自己想加一个

Web项目开发小结

经过近4个多月的历程,该系统的第一阶段也算是完成了,总算是能挂到测试环境让人去测试,现在只等待系统上线,以及下一阶段的开发进行,相信也不会很久了,不过能够在年前将这个东西弄完,也是算为2015年做了一个稍微好点的开端吧. 说起这个系统,也算是泪牛满面了.从项目的需求分析阶段开始说吧,其实这只是风控部门的一个构想,他们将这个诉求提到公司高层那里,得到上面的批复和肯定,但是需求并不清晰,只是大概知道这个东西做好以后,可以更加的方便风控部门进行数据分析,不过也会方便自己部门进行工作量化,同时规范自身的

用互联网思维来开发客户端软件——项目开发小结

随着智能手机.平板电脑的快速发展,台式电脑在个人用户那里已经没落了,但是台式电脑仍然是企业用户工作中的主要工具,且具有不可替代的作用.客户端软件在企业级用户那里有着不可替代的作用,结合时代发展,我们应以互联网思维来做好企业级应用客户端软件?研发快速迭代.快速试错,把大功能拆分成小功能,分阶段实现,追求微创新. 通常企业级应用的客户端,就是企业管理应用系统,一般分为BS与CS两种架构,CS架构要求在用户的电脑上装上客户端与数据库,或者数据库安装在数据库服务器上.这种方式我们经常会碰到一些问题,比如

《TD项目开发小结》

从7月24号开始就进入了TD项目开发了,到今天退出.一共是7周工作日,不算加班的话.整整35个工作日.这是我跳槽后完完整整的从一个项目的开始做到了项目的上线.之前做的XY项目是差不多已经做完了的,一进去就是改bug.通过这次项目开发,把我存在的问题全部暴露了出来,思路不清晰,效率低下,代码敲的少.之前做个人办公模块的时候还好,到了8月中旬做客户详情的时候,各种数据不全,表结构不一致.而我又不怎么去问别人.导致5个工作日,一直卡在客户管理模块,那是第一次被领导批评.到后来第一次交给客户审核,提出来

vue_使用npm搭建vue2.0脚手架开发环境

前言: 在使用vue进行开发时需要搭建vue的运行环境,这里主要是使用淘宝镜像cnpm进行搭建vue的脚手架开发环境.主要是分为mac和window两个版本,两个环境的搭建都是大同小异. mac开发环境的搭建: 1.安装Node.js(这个只需要去nodeJS官网下载安装就可以了) 以下的命令都是在终端输入 2.在电脑终端使用命令 node -v 检查版本(检查安装成功) 3.安装淘宝npm镜像 sudo npm install -g cnpm --registry=https://regist