前端项目中很多时候会遇到这样的业务需求:

  • 所有的客户都有共同的一些业务需求,即基础需求
  • 基础需求需要根据业务发展进行升级
  • 在共同需求的基础上,每个客户又有不同的需求(客制化

如果只是一两个客户还好,在基础版本上分两份前端文件,分别维护。但是客户达到一定数量级后,比如5个、8个甚至更多,这时候这种机械的本办法就费时耗力了,如果再考虑到基础功能的版本升级需求,那这种方法就完全不行了(升级一个基础功能就要分别复制到每个客户的项目中,同步代码)。

以上情况就是我司的实际情况,下面介绍一下我司用的方法,不一定是最好的解决方案,如读者有更好的方法,欢迎留言交流。

  • 针对第一点,首先根据产品对行业的需求了解,指定出基础版本的功能,基础功能涵盖大部分客户的最基本需求,开发出一套前端对应的版本,即基础版。
  • 针对第二点,借助git版本管理工具,每次大的升级都创建一个分支,基础版为1.0.1,升级版本后,历史版本便封版,不再做改动(bug除外),后续可以根据客户对基础功能的需求决定在哪个版本的基础上做定制化开发。
  • 针对第三点,客制化开发主要通过前端动态生成vue组件统一放到一个文件里的方式满足,也是本文的重点。

思路:通过node动态创建默认路由文件和路由所需的组件列表文件

在package.json的script的dev和build命令中配置CLIENT字段标识当前启动项的客户,如果不加CLIENT字段则视为标准版本开发或构件

启动项目的时候同时运行指定js文件buildViews.js,该文件负责动态生成后续router需要的文件

buildViews.js中通过CLIENT字段拉取对应的客户路由配置文件./anxin/index,和标准文件的模块进行对比,以客制化文件为准,覆盖、替换或删除标准文件里的模块,组合为新的路由数据

"scripts": {"serve": "vue-cli-service serve","buildViews": "node build/buildViews.js","buildViews:landq": "cross-env CLIENT=landq node build/buildViews.js","dev": "npm run buildViews && npm run serve","dev:landq": "npm run buildViews:landq && npm run serve","build": "npm run buildViews && vue-cli-service build","build:landq": "npm run buildViews:landq && vue-cli-service build","lint": "vue-cli-service lint"},

通过node命令fs.writeFile()使用对比更新后的配置数据,在指定文件夹下创建后续路由所需要的的视图模块文件,动态生成的文件大致长这样

const Home = resolve => require(['view/home/index'], resolve);
、、、、、export {Home,、、、
}

下面来看具体代码层面的实现

首先安装依赖json-templater/tring用于生成文件模板,chalk用于在控制待打印信息,cross-env用于设置客制化信息

目录结构:

这是初始的目录结构,client专门存放客制化的路由文件:客户landq和anxin,比如版本信息和info.js、用于生成路由的index.js,standerd文件夹存放标准版本的相关信息;buildViews.js用于动态创建路由数据

先在info.js中把客户名、版本信息等添加上

standerd/info.js

module.exports = {clientName: 'standerd',clientFullName: '标准版本',clientSystemName: '系统名称',version: '1.0.0',packageTime: new Date().toLocaleString(),
}

client/landq/info.js

module.exports = {clientName: 'landq',clientFullName: '客户landq',clientSystemName: '系统名称',version: '1.0.0',packageTime: new Date().toLocaleString(),
}

重点是buildViews.js,整合标准版本和客制化版本的代码都在这里,对组件的添加、替换、删除操作

console.log(process.env.CLIENT)
const fs = require('fs');
const path = require('path');
const endOfLine = require('os').EOL;
const render = require('json-templater/string'); // 模板渲染
const chalk = require('chalk'); // 控制台打印带颜色信息// 获取标准版配置
let { STANDERD_IMPORT, STANDERD_ROUTERS } = require('./standerd')// 获取客制化配置
let CLIENT = process.env.CLIENT || ''
if (CLIENT) {var { CLIENT_IMPORT, CLIENT_ROUTERS } = require(`./client/${CLIENT}`)
} else {var { CLIENT_IMPORT, CLIENT_ROUTERS } = {CLIENT_IMPORT: {},CLIENT_ROUTERS: {}}
}// 获取版本信息
let versionConfig
if (CLIENT) {versionConfig = require(`./client/${CLIENT}/info`)
} else {versionConfig = require(`./standerd/info`)
}
console.log(chalk.yellow(`当前执行版本信息:${versionConfig.clientFullName}-${versionConfig.version}`))
console.log(chalk.yellow(`编译时间:${versionConfig.packageTime}`))// 生成router/importViews.js
const IMPORT_VIEWS_PATH = path.join(__dirname, '../src/router/importViews.js')
const IMPORT_VIEWS_IMPORT_TEMPLATE = 'const {{name}} = resolve => require([\'{{path}}\'], resolve)'
const IMPORT_VIEWS_MAIN_TEMPLATE =`/* 由build/buildViews.js自动生成 */// 引入所有页面组件
{{include}}// 导出所有页面
export {{{list}}
}`// 以客制化为标准,更新标准文件,替换或新增
let useViews = {}
if (CLIENT) {// 新增客制化组件,组合为新的文件信息useViews = Object.assign(STANDERD_IMPORT, CLIENT_IMPORT.update)// 删除不需要的标准组件CLIENT_IMPORT.delete.forEach(name => {if (useViews[name]) delete useViews[name]})
} else {useViews = STANDERD_IMPORT
}let importAllViewsArr = []
let importAllViewsNameArr = []// 生成const [Login = resolve => require(['./src/login'], resolve),Home = resolve => require(['./src/home'], resolve)]
Object.keys(useViews).forEach(name => {importAllViewsArr.push(render(IMPORT_VIEWS_IMPORT_TEMPLATE, {name: name,path: useViews[name]}))importAllViewsNameArr.push(` ${name}`)})// 生成主模板
console.log(useViews)
let importMainTemplate = render(IMPORT_VIEWS_MAIN_TEMPLATE, {include: importAllViewsArr.join(endOfLine),list: importAllViewsNameArr.join(`,${endOfLine}`)})
fs.writeFileSync(IMPORT_VIEWS_PATH, importMainTemplate)// 生成router/router.js
const CLIENT_ROUTER_PATH = path.join(__dirname, '../src/router/router.js')
const CLIENT_ROUTER_IMPORT_TEMPLATE = 'import {{name}} from \'{{path}}\';'// 默认路由模板
const CLIENT_ROUTER_MAIN_TEMPLATE =`/* 由build/buildViews.js自动生成 */import { Layout } from './importViews.js';
{{include}}export default {path: '/',meta: {name: '首页'},redirect: '/home',component: Layout,children: [{{list}}]
}`// 以客制化为标准,更新标准文件
let useRouters = {}
if (CLIENT) {// 新增客制化组件,组合为新的文件信息useRouters = Object.assign(STANDERD_ROUTERS, CLIENT_ROUTERS.update)// 删除不需要的标准组件CLIENT_ROUTERS.delete.forEach(name => {if (useRouters[name]) delete useRouters[name]})
} else {useRouters = STANDERD_ROUTERS
}let importAllRoutersArr = []
let importAllRoutersNameArr = []// 生成const [import System from './standerd/system';]
Object.keys(useRouters).forEach(name => {importAllRoutersArr.push(render(CLIENT_ROUTER_IMPORT_TEMPLATE, {name: name,path: useRouters[name]}))importAllRoutersNameArr.push(`  ${name}`)})// 生成主模板
let importMainRouterTemplate = render(CLIENT_ROUTER_MAIN_TEMPLATE, {include: importAllRoutersArr.join(endOfLine),list: importAllRoutersNameArr.join(`,${endOfLine}`)})
fs.writeFileSync(CLIENT_ROUTER_PATH, importMainRouterTemplate)// 生成模板信息
const VERSION_CONFIG_OUTPUT_PATH = path.join(__dirname, '../static/version.js')
fs.writeFileSync(VERSION_CONFIG_OUTPUT_PATH, JSON.stringify(versionConfig))

现在来看一个简单的标准版本需求

要有一个layout页面,一个home页面,login页面。视图页面已经创建好了,标准版本根路径路由指向home页

配置build/standerd/index.js

/*** 针对引入组件页面的配置(router/importViews.js)*/
const STANDERD_IMPORT = {Layout: '@/layout/index',Home: '@/views/home',Login: '@/views/login'}/*** 针对引入组件页面的配置(router/router.js)*/
const STANDERD_ROUTERS = {BaseRouter: './standerd/baseRouter.js',
}
module.exports = {STANDERD_IMPORT,STANDERD_ROUTERS
}

此时运行npm run dev便会在router文件夹下生成router.js和importViews.js

此时src/router/importViews.js

/* 由build/buildViews.js自动生成 */// 引入所有页面组件
const Layout = resolve => require(['@/layout/index'], resolve)
const Home = resolve => require(['@/views/home'], resolve)
const Login = resolve => require(['@/views/login'], resolve)// 导出所有页面
export {Layout,Home,Login
}

src/router/router.js

/* 由build/buildViews.js自动生成 */import { Layout } from './importViews.js';
import BaseRouter from './standerd/baseRouter.js';export default {path: '/',meta: {name: '首页'},redirect: '/home',component: Layout,children: [BaseRouter]
}

此时配置src/router/standerd/baseRouter.js

import { Home, Login } from '../importViews'
export default {path: '/home',meta: {name: 'home'},component: Home,children: [{path: '/login',meta: {name: 'login'},component: Login,}]
}

配置完成,页面效果如下

点击按钮,调整到login页面

现在加入有个客户landq想要定制化一个客户列表页面,用来展示客户,那么就需要进行客制化配置了:

home页和login页是基础页面,我想要增加一个客户列表页面customerList

新建空页面views/landq/customerList/index.vue

配置build/client/landq/index.js

/*** 针对引入组件页面的配置(router/importViews.js)* update:需要更新的页面地址* delete:需要删除的引用地址*/
const CLIENT_IMPORT = {update: {CustomerList: '@/views/landq/customerList', // 在标准版本的基础上替换或者新增Home文件},delete: ['PageList' // 需要删除标准版本的组件名,没有则为空]}/*** 针对引入组件页面的配置(router/router.js)*/
const CLIENT_ROUTERS = {update: {CustomerList: './client/landq/customerList.js' // 替换标准版本的system模块路由文件},delete: ['Customer' // 需要删除标准版本的路由模块名称,没有则为空]
}
module.exports = {CLIENT_IMPORT,CLIENT_ROUTERS
}

此时生成的src/router/importViews.js

/* 由build/buildViews.js自动生成 */// 引入所有页面组件
const Layout = resolve => require(['@/layout/index'], resolve)
const Home = resolve => require(['@/views/home'], resolve)
const Login = resolve => require(['@/views/login'], resolve)
const CustomerList = resolve => require(['@/views/landq/customerList'], resolve)// 导出所有页面
export {Layout,Home,Login,CustomerList
}

多了一个CustomerList客制化组件

src/router/router.js

/* 由build/buildViews.js自动生成 */import { Layout } from './importViews.js';
import BaseRouter from './standerd/baseRouter.js';
import CustomerList from './client/landq/customerList.js';export default {path: '/',meta: {name: '首页'},redirect: '/home',component: Layout,children: [BaseRouter,CustomerList]
}

比标准版本多了个CustomerList的路由

然后手动新建src/router/client/landq/customerList.js

import { CustomerList } from '../../importViews'
export default {path: '/customerList',meta: {name: 'customerList'},component: CustomerList,
}

点击首页的to customerList按钮即可跳转到客制化页面

这样就完成了一个基于标准功能的客制化开发的项目搭建。运行npm run build:landq可以定制化构建生产包。

用node在本地8000端口起一个服务(搜索一下,挺简单的),将客制化的压缩包放进指定目录就可以看到页面效果了。

运行npm run dev构建基础版本包,放到本地服务目录下对比效果

可以看到基础版是没有customerList页面的

git地址:https://github.com/LandQ123/client-vue

这种客制化的方法避免了每一个客户都维护一个分支的复杂,对于这种业务需求,整体来说维护起来相对来说更轻松,当然也有其本身的一些局限性和问题,比如后期以及迭代了多个版本了,发现最前面的版本有bug需要修复,或者说有客户想要在之前的某个版本基础上定制功能,就需要切换到指定版本开发或修改,蛋疼的是要从前往后同步代码,基础版本必须逐条分支合并同步,难免会有冲突的地方,会耗时费力;客制化分支也需要把之前改动了的同步到后面该客户有客制化功能的分支。

不过分支的管理是每一个项目必须面对的,只要有迭代就有维护和管理,只能在实践中慢慢总结优化。

vue项目中动态创建模块以满足客户定制化需求的解决方案相关推荐

  1. vue项目中实现H5调取摄像头扫码扫一扫功能+生成可识别的条形码。单纯的h5网页不涉及真机

    vue项目中实现H5调取摄像头扫码扫一扫功能+生成可识别的条形码. 单纯的h5网页不涉及真机 demo链接 前端同学可以加我一起交流一起进步 案例描述:需求是生成条形码并且在vue项目中实现扫一扫功能 ...

  2. Vue项目中公用footer组件底部位置的适配问题

    vue项目中公用footer组件底部位置的适配问题 需求: footer为公用组件,其他页面都需要引入,这是会存在一种情况:有的页面高度很高,footer组件要放在内容的最后:有的页面内容很少,高度很 ...

  3. vue项目中通过WebSocket实现实时消息提示及遇到的问题

    vue项目中通过WebSocket实现实时消息提示(前端代码) 需求说明 后台有新增消息通知,并实时推送给用户端,用websocket可以很方便的解决这个问题,但是websocket有个弊端不兼容IE ...

  4. vue 项目中,动态修改浏览器标签页的图标

    vue 项目中,动态修改浏览器标签页的图标 需求: 项目中有一个入口可以修改平台的名称和图标,图标同步展示为浏览器页签的图标 实现: 1.找到项目中的app.vue 文件,动态创建link标签,调用后 ...

  5. 怎么改vue项目的标题_如何动态修改Vue项目中的页面title

    前言:在项目中,我们有时候需要修改Vue项目中的页面title. 方法有两种,①如果需要动态设置页面的title,可以直接使用document.title:②可以使用router的beforeEach ...

  6. Vue项目中如何设置动态的TDK

    TDK是什么 TDK就是网站的标题(title).描述(description)和关键词(keyword) TDK在哪里 上面大佬对TDK的概念解释的很全面,但是在网页中的TDK在哪里呢,作为开发人员 ...

  7. 如何在Vue项目中引入ArcGIS JavaScript API​ 创建三维可视化地图(含vue项目创建教程)

    新手上路之在Vue项目中引入ArcGIS API​ 视频教程 B站搜索 X北辰北,感谢up主无私的教学~ B站地址:https://www.bilibili.com/video/BV18E411K7B ...

  8. vue项目中遇到的一些问题

    或访问:https://github.com/littleHiuman/experiences-about-vue  欢迎补充! vuex 状态 vue-cli 命令行 vue vue vue-rou ...

  9. vue项目中使用echarts完成图表类的开发之饼图,环形图

    最近在vue项目中使用echarts.js完成诸如饼图,环形图,折线图,折线图加柱状图,和柱状图(水平柱状图和垂直柱状图),以及柱状图堆叠,下面请看图片展示: 如上图所示基本上echarts的简单图表 ...

最新文章

  1. html如何用v-for遍历,v-for循环遍历
  2. 技术网站 --入门无忧网
  3. 去中心化钱包CoinU诞生 黑客攻不破的铜墙铁壁
  4. C语言学习之有一个已排好序的数组,要求输入一个数后,按原来排序的规律将它插入数组中
  5. MySQL数据处理之增删改,MySQL8新特性计算列,完整详细可收藏
  6. CSDN2013博客之星评选(求投票支持)
  7. ES | CentOS下基于ElasticSearch的head插件安装
  8. 用户输入年份,输出当前年份2月份的天数
  9. ASP.NET-第二天-加强课程
  10. RestLet框架的入门
  11. Java UDP通信详解:单播、广播、组播
  12. Spring Boot实现QQ邮件发送,用户注册功能——前后端分离版
  13. 《此生未完成》:她说,名利权情,没有一样是不辛苦的
  14. win7适合oracle哪个版本下载,win7系统下载--Windows 7下成功安装ORACLE客户端
  15. 「三楼总版主」葫芦侠创始人-流火
  16. 中英文金额大写转换器
  17. 360导航底部的效果html,jQuery仿360导航页图标拖动排序效果代码分享
  18. Hibernate【映射】续篇
  19. 利用Java提取excel内容
  20. 撰写SCI论文好用的免费工具(下) - 易智编译EaseEditing

热门文章

  1. 互联网+下的慧算账体验式营销
  2. GORM报错sql: Scan called without calling Next
  3. javascript第三天---标签内的属性和数组
  4. 玩转Kaggle:Classify Leaves(叶子分类)——数据分析篇
  5. 最最喜欢的韩庚……这个资料-----顶了~/(≧▽≦)/~!!!
  6. Kelvin connection-开尔文连接
  7. 史上最简单:SpringCloud 集成 mybatis-plus(以若依微服务版本为例)
  8. python小项目超级大脑抱香_“超级大脑”来了!丰泽区建成全市首个区县级大数据中心...
  9. 深入实践 Spring Boot PDF 百度云盘下载
  10. 利用python深度学习神经网络预测五年内糖尿病的发生(全代码)