前端开发有2种方式:前后台混合开发和前后台分离开发。
前后台混合开发,顾名思义就是前台后台代码混在一起开发,如下图所示:
这种开发模式有如下缺点:
- 沟通成本高:后台人员发现前端有问题,需要找前端人员修改,前端修改成功,再交给后台人员使用
- 分工不明确:后台开发人员需要开发后台代码,也需要开发部分前端代码。很难培养专业人才
- 不便管理:所有的代码都在一个工程中
- 不便维护和扩展:前端代码更新,和后台无关,但是需要整个工程包括后台一起重新打包部署。
将原先的工程分为前端工程和后端工程这2个工程,然后前端工程交给专业的前端人员开发,后端工程交给专业的后端人员开发。前端页面需要数据,可以通过发送异步请求,从后台工程获取。前后台开发人员都需要遵循接口文档规范开发。接口文档的内容是根据产品经理提供的产品原型和需求文档所撰写出来的。
- 需求分析:首先我们需要阅读需求文档,分析需求,理解需求。
- 接口定义:查询接口文档中关于需求的接口的定义,包括地址,参数,响应数据类型等等
- 前后台并行开发:各自按照接口文档进行开发,实现需求
- 测试:前后台开发完了,各自按照接口文档进行测试
- 前后段联调测试:前段工程请求后端工程,测试功能
YAPI 是高效、易用、功能强大的 api 管理平台,旨在为开发、产品、测试人员提供更优雅的接口管理服务。其官网地址:http://yapi.smart-xwork.cn/
YAPI 主要提供了2个功能:
- API接口管理:根据需求撰写接口,包括接口的地址,参数,响应等等信息。
- Mock服务:模拟真实接口,生成接口的模拟测试数据,用于前端的测试。
目前的前端开发中,当需要使用一些资源时,例如:vue.js,和axios.js文件,都是直接再工程中导入的,如下图所示:
上述开发模式存在如下问题:
- 每次开发都是从零开始,比较麻烦
- 多个页面中的组件共用性不好
- js、图片等资源没有规范化的存储目录,没有统一的标准,不方便维护
现在企业开发中更加讲究前端工程化方式的开发,主要包括如下4个特点
- 模块化:将js和css等,做成一个个可复用模块
- 组件化:将UI组件,css样式,js行为封装成一个个的组件,便于管理
- 规范化:提供一套标准的规范的目录接口和编码规范,所有开发人员遵循这套规范
- 自动化:项目的构建,测试,部署全部都是自动完成
对于前端工程化,说白了,就是在企业级的前端项目开发中,把前端开发所需要的工具、技术、流程、经验进行规范化和标准化。从而提升开发效率,降低开发难度等等。
前端工程化是通过vue官方提供的脚手架Vue-cli来完成的,用于快速的生成一个Vue的项目模板。Vue-cli主要提供了如下功能:
- 统一的目录结构
- 本地调试
- 热部署
- 单元测试
- 集成打包上线
需要运行Vue-cli,需要依赖NodeJS,NodeJS是前端工程化依赖的环境。
NodeJS安装
验证NodeJS环境变量【NodeJS 安装完毕后,会自动配置好环境变量,我们验证一下是否安装成功,通过: node -v】
配置npm的全局安装路径
使用管理员身份运行命令行,在命令行中,执行如下指令:
npm config set prefix "E:\develop\NodeJS"
注意:E:\develop\NodeJS 这个目录是NodeJS的安装目录
Vue-cli提供了如下2种方式创建vue项目:
通过VS Code打开之前创建的vue文件夹,打开之后,呈现如下图所示页面:
其中我们平时开发代码就是在src目录下
然后就能显示NPM脚本小窗口了。
vue项目的热更新功能对于8080端口,经常被占用,所以可以去修改默认的8080端口。修改vue.config.js文件的内容,添加如下代码:
devServer:{ port:7000 }
第二种方式:
命令行方式
访问的首页是index.html,但是找到public/index.html文件,打开之后发现,里面没有什么代码,index.html的代码很简洁,但是浏览器所呈现的index.html内容却很丰富,代码和内容不匹配,对于vue项目,index.html文件默认是引入了入口函数main.js文件
import Vue from 'vue' import App from './App.vue' import router from './router' Vue.config.productionTip = false new Vue({ router, render: h => h(App) }).$mount('#app')
上述代码中,包括如下几个关键点:
- import: 导入指定文件,并且重新起名。例如上述代码
import App from './App.vue'
导入当前目录下得App.vue并且起名为App- new Vue(): 创建vue对象
- $mount(‘#app’);将vue对象创建的dom对象挂在到id=app的这个标签区域中,作用和之前学习的vue对象的le属性一致。
- router: 路由,详细在后面的小节讲解
- render: 主要使用视图的渲染的。
main.js中通过代码挂载到index.html的id=app的标签区域的。vue创建的dom对象挂载到id=app的标签区域,还是没有解决最开始的问题:首页内容如何呈现的?这就涉及到render中的App了,如下图所示:
那么这个App对象怎么回事呢,打开App.vue,注意的是.vue结尾的都是vue组件。而vue的组件文件包含3个部分():
- template: 模板部分,主要是HTML代码,用来展示页面主体结构的
- script: js代码区域,主要是通过js代码来控制模板的数据来源和行为的
- style: css样式部分,主要通过css样式控制模板的页面效果得
App.vue组件的template部分内容,和我们浏览器访问的首页内容是一致的,如下图所示:
添加script部分的数据模型,删除css样式,完整代码如下:
<template> <div id="app"> {{message}} div> template> <script> export default { data(){ return { "message":"hello world" } } } script> <style> style>
前端开发模式MVVM,vue是侧重于VM开发的,主要用于数据绑定到视图的,ElementUI就是一款侧重于V开发的前端框架,主要用于开发美观的页面的。
Element:是饿了么公司前端开发团队提供的一套基于 Vue 的网站组件库,用于快速构建网页。
Element 提供了很多组件(组成网页的部件)供我们使用。例如 超链接、按钮、图片、表格等等。如下图所示就是我们开发的页面和ElementUI提供的效果对比:可以发现ElementUI提供的各式各样好看的按钮
其官网地址:https://element.eleme.cn/#/zh-CN
- 安装ElementUI的组件库:
npm install [email protected]
- 在main.js这个入口js文件中引入ElementUI的组件库,其代码如下:
import ElementUI from 'element-ui'; import 'element-ui/lib/theme-chalk/index.css'; Vue.use(ElementUI);
Table 表格:用于展示多条结构类似的数据,可对数据进行排序、筛选、对比或其他自定义操作。到ElementUI的组件库中,找到表格组件
复制代码到之前的ElementVue.vue组件中,需要注意的是,组件包括了3个部分,如果官方有除了template部分之外的style和script都需要复制。
ElementView.vue组件文件整体代码如下:
<template> <div> <el-row> <el-button>默认按钮el-button> <el-button type="primary">主要按钮el-button> <el-button type="success">成功按钮el-button> <el-button type="info">信息按钮el-button> <el-button type="warning">警告按钮el-button> <el-button type="danger">危险按钮el-button> el-row> <el-table :data="tableData" style="width: 100%"> <el-table-column prop="date" label="日期" width="180"> el-table-column> <el-table-column prop="name" label="姓名" width="180"> el-table-column> <el-table-column prop="address" label="地址"> el-table-column> el-table> div> template> <script> export default { data() { return { tableData: [{ date: '2016-05-02', name: '王小虎', address: '上海市普陀区金沙江路 1518 弄' }, { date: '2016-05-04', name: '王小虎', address: '上海市普陀区金沙江路 1517 弄' }, { date: '2016-05-01', name: '王小虎', address: '上海市普陀区金沙江路 1519 弄' }, { date: '2016-05-03', name: '王小虎', address: '上海市普陀区金沙江路 1516 弄' }] } } } script> <style> style>
ElementUI将数据模型绑定到视图主要通过如下几个属性:
- data: 主要定义table组件的数据模型
- prop: 定义列的数据应该绑定data中定义的具体的数据模型
- label: 定义列的标题
- width: 定义列的宽度
Pagination: 分页组件,主要提供分页工具条相关功能。
复制代码到ElementView.vue组件文件的template中,拷贝如下代码:
<el-pagination background layout="prev, pager, next" :total="1000"> el-pagination>
对于分页组件需要关注的是如下几个重要属性(可以通过查阅官网组件中最下面的组件属性详细说明得到):
- background: 添加北京颜色,也就是上图蓝色背景色效果。
- layout: 分页工具条的布局,其具体值包含
sizes
,prev
,pager
,next
,jumper
,->
,total
,slot
这些值- total: 数据的总数量
修改layout属性如下:
layout="sizes,prev, pager, next,jumper,total"
对于分页组件,除了上述几个属性,还有2个非常重要的事件:
- size-change : pageSize 改变时会触发
- current-change :currentPage 改变时会触发
对于这2个事件的参考代码,可以通过官方提供的完整案例中找到,如下图所示:
首先复制事件,复制代码如下:
@size-change="handleSizeChange" @current-change="handleCurrentChange"
Pagination组件的template完整代码如下:
<el-pagination @size-change="handleSizeChange" @current-change="handleCurrentChange" background layout="sizes,prev, pager, next,jumper,total" :total="1000"> el-pagination>
紧接着需要复制事件需要的2个函数,需要注意methods属性和data同级,其代码如下:
methods: { handleSizeChange(val) { console.log(`每页 ${val} 条`); }, handleCurrentChange(val) { console.log(`当前页: ${val}`); } },
此时Pagination组件的script部分完整代码如下:
<script> export default { methods: { handleSizeChange(val) { console.log(`每页 ${val} 条`); }, handleCurrentChange(val) { console.log(`当前页: ${val}`); } }, data() { return { tableData: [{ date: '2016-05-02', name: '王小虎', address: '上海市普陀区金沙江路 1518 弄' }, { date: '2016-05-04', name: '王小虎', address: '上海市普陀区金沙江路 1517 弄' }, { date: '2016-05-01', name: '王小虎', address: '上海市普陀区金沙江路 1519 弄' }, { date: '2016-05-03', name: '王小虎', address: '上海市普陀区金沙江路 1516 弄' }] } } } script>
Dialog: 在保留当前页面状态的情况下,告知用户并承载相关操作。其企业开发应用场景示例如下图所示:
在ElementUI官方找到Dialog组件,复制如下代码到我们的组件文件的template模块中
<br><br> <el-button type="text" @click="dialogTableVisible = true">打开嵌套表格的 Dialogel-button> <el-dialog title="收货地址" :visible.sync="dialogTableVisible"> <el-table :data="gridData"> <el-table-column property="date" label="日期" width="150">el-table-column> <el-table-column property="name" label="姓名" width="200">el-table-column> <el-table-column property="address" label="地址">el-table-column> el-table> el-dialog>
并且复制数据模型到script模块中:
gridData: [{ date: '2016-05-02', name: '王小虎', address: '上海市普陀区金沙江路 1518 弄' }, { date: '2016-05-04', name: '王小虎', address: '上海市普陀区金沙江路 1518 弄' }, { date: '2016-05-01', name: '王小虎', address: '上海市普陀区金沙江路 1518 弄' }, { date: '2016-05-03', name: '王小虎', address: '上海市普陀区金沙江路 1518 弄' }], dialogTableVisible: false,
其完整的script部分代码如下:
<script> export default { methods: { handleSizeChange(val) { console.log(`每页 ${val} 条`); }, handleCurrentChange(val) { console.log(`当前页: ${val}`); } }, data() { return { gridData: [{ date: '2016-05-02', name: '王小虎', address: '上海市普陀区金沙江路 1518 弄' }, { date: '2016-05-04', name: '王小虎', address: '上海市普陀区金沙江路 1518 弄' }, { date: '2016-05-01', name: '王小虎', address: '上海市普陀区金沙江路 1518 弄' }, { date: '2016-05-03', name: '王小虎', address: '上海市普陀区金沙江路 1518 弄' }], dialogTableVisible: false, tableData: [{ date: '2016-05-02', name: '王小虎', address: '上海市普陀区金沙江路 1518 弄' }, { date: '2016-05-04', name: '王小虎', address: '上海市普陀区金沙江路 1517 弄' }, { date: '2016-05-01', name: '王小虎', address: '上海市普陀区金沙江路 1519 弄' }, { date: '2016-05-03', name: '王小虎', address: '上海市普陀区金沙江路 1516 弄' }] } } } script>
ElementUI对话框的显示与隐藏通过如下的属性实现:
visible属性绑定的dialogTableVisble属性一开始默认是false,所以对话框隐藏;通过点击按钮,触发事件,修改属性值为true,然后对话框visible属性值为true,所以对话框呈现出来。
Form 表单:由输入框、选择器、单选框、多选框等控件组成,用以收集、校验、提交数据。
<template> <div> el-dialog> <el-button type="text" @click="dialogFormVisible = true">打开嵌套表单的 Dialogel-button> <el-dialog title="收货地址" :visible.sync="dialogFormVisible"> <el-form :model="form"> <el-form-item label="活动名称" :label-width="formLabelWidth"> <el-input v-model="form.name" autocomplete="off">el-input> el-form-item> <el-form-item label="活动区域" :label-width="formLabelWidth"> <el-select v-model="form.region" placeholder="请选择活动区域"> <el-option label="上海" value="shanghai">el-option> <el-option label="北京" value="beijing">el-option> el-select> el-form-item> el-form> <div slot="footer" class="dialog-footer"> <el-button @click="dialogFormVisible = false">取 消el-button> <el-button type="primary" @click="dialogFormVisible = false">确 定el-button> div> el-dialog> div> template> <script> export default { //数据区 data () { return { //对话框是否显示 dialogTableVisible:false, dialogFormVisible:false, //表单双向绑定数据 form:{ name:"张三", region:"重庆" }, //宽度 formLabelWidth:"120px", }, //方法区 methods: { }, } script>
需求说明:
制作类似格式的页面
即上面是标题,左侧栏是导航,右侧是数据展示区域
右侧需要展示搜索表单
右侧表格数据是动态展示的,数据来自于后台
//ElementView.vue //模板
//脚本 //样式美国有苹果 中国有菠萝 手机 系统信息管理 部门管理 员工管理 Element测试 查询 {{scope.row.gender==1?"男":"女"}} 编辑 删除
//脚本 vue脚手架组件// 路径:项目下/src/router/index.js import Vue from 'vue' import VueRouter from 'vue-router' // import HomeView from '../views/HomeView.vue' Vue.use(VueRouter) const routes = [ { path:'/', redirect: '/emp', // name:'emp', // component: () => import('../views/polo/EmpView.vue') }, { path: '/emp', name: 'emp', component: () => import('../views/polo/EmpView.vue') }, { path: '/dept', name: 'dept', // route level code-splitting // this generates a separate chunk (about.[hash].js) for this route // which is lazy-loaded when the route is visited. component: () => import('../views/polo/DeptView.vue') }, { path: '/element', name: 'element', component: () => import('../views/element/ElementView.vue') } ] const router = new VueRouter({ routes }) export default router
// src/main.js import Vue from 'vue' import ElementUI,{Table} from 'element-ui'; // import ElementUI from 'element-ui'; import 'element-ui/lib/theme-chalk/index.css'; import App from './App.vue' import router from './router' // 解决 ElTable 自动宽度高度导致的「ResizeObserver loop limit exceeded」问题 const fixElTableErr = (table) => { const oldResizeListener = table.methods.resizeListener; table.methods.resizeListener = function () { window.requestAnimationFrame(oldResizeListener.bind(this)); }; }; // 一定要在Vue.use之前执行此函数 fixElTableErr(Table); Vue.use(ElementUI); Vue.config.productionTip = false new Vue({ router, render: h => h(App) }).$mount('#app')
将资代码/vue-project(路由)/vue-project/src/views/polo/DeptView.vue拷贝到我们当前EmpView.vue同级,其结构如下:
前端路由:URL中的hash(#号之后的内容)与组件之间的对应关系
点击左侧导航栏时,浏览器的地址栏会发生变化,路由自动更新显示与url所对应的vue组件。vue官方提供了路由插件Vue Router,其主要组成如下:
首先VueRouter根据我们配置的url的hash片段和路由的组件关系去维护一张路由表;
然后页面提供一个
组件,用户点击,发出路由请求; 接着VueRouter根据路由请求,在路由表中找到对应的vue组件;
最后VueRouter会切换
中的组件,从而进行视图的更新
需要先安装vue-router插件命令如下:
npm install [email protected]
如果前面通过命令行安装vue-cli的需要执行上述指令,图形界面,已经安装了router。
然后需要在src/router/index.js文件中定义路由表,根据其提供的模板代码进行修改
// 路径:项目下/src/router/index.js import Vue from 'vue' import VueRouter from 'vue-router' // import HomeView from '../views/HomeView.vue' Vue.use(VueRouter) const routes = [ { path:'/', redirect: '/emp', // name:'emp', // component: () => import('../views/polo/EmpView.vue') }, { path: '/emp', name: 'emp', component: () => import('../views/polo/EmpView.vue') }, { path: '/dept', name: 'dept', // route level code-splitting // this generates a separate chunk (about.[hash].js) for this route // which is lazy-loaded when the route is visited. component: () => import('../views/polo/DeptView.vue') }, { path: '/element', name: 'element', component: () => import('../views/element/ElementView.vue') } ] const router = new VueRouter({ routes }) export default router
路由基本信息配置好了,路由表已经被加载,此时还缺少2个东西,就是
和 ,所以需要修改2个页面(EmpView.vue和DeptView.vue)左侧栏的2个按钮为router-link,其代码如下: <el-menu-item index="1-1"> <router-link to="/dept">部门管理router-link> el-menu-item> <el-menu-item index="1-2"> <router-link to="/emp">员工管理router-link> el-menu-item>
然后还需要在内容展示区域即App.vue中定义route-view,作为组件的切换,其App.vue的完整代码如下:
<template> <div id="app"> <router-view>router-view> div> template> <script> // import EmpView './views/polo/EmpView.vue' // import ElementView './views/element/ElementView.vue' export default { components: { }, data(){ return { "message":"hello world" } } } script> <style> style>
但是我们浏览器打开地址: http://localhost:7000/ ,发现一片空白,因为我们默认的路由路径是/,但是路由配置中没有对应的关系,
所以我们需要在路由配置中/对应的路由组件,代码如下:
const routes = [ { path: '/emp', name: 'emp', component: () => import('../views/polo/EmpView.vue') }, { path: '/dept', name: 'dept', component: () => import('../views/polo/DeptView.vue') }, { path: '/', redirect:'/emp' //表示重定向到/emp即可 }, ]
打开浏览器,访问http://localhost:7000 发现直接访问的是emp的页面,并且能够进行切换如上述案例所示
前端工程开发好后,发布需要2步:
- 前端工程打包
- 通过nginx服务器发布前端工程6.2 部署前端工程
nginx: Nginx是一款轻量级的Web服务器/反向代理服务器及电子邮件(IMAP/POP3)代理服务器。其特点是占有内存少,并发能力强,在各大型互联网公司都有非常广泛的使用。niginx在windows中的安装是比较方便的,直接解压即可。下图所示就是nginx的解压目录以及目录结构说明:
如果要发布,直接将资源放入到html目录中即可。
element-ui低版本报
ResizeObserver loop limit exceeded
这个错误的原因:
如果在一个动画帧内,ResizeObserver不能处理所有的observations,就会触发这个
ResizeObserver loop limit exceeded
。监听一个元素大小变化或者屏幕横竖屏时,需要监听window.resize事件或者window.orientationchange方法。由于 resize 事件会在一秒内触发将近60次,所以很容易在改变窗口大小时导致性能问题,所以 window.resize 事件通常是浪费的,不过我们有节流可以有效提高性能。 可是 window.resize 只能监听 window 对象,如果对于一个 div ,我们是无能为力的,还好有
resize-observer-polyfill
,包里面有用到MutationObserver
API 有兴趣可以看看 可以监听到元素的变动。所以想要 table 自适应,需要父级的宽度有触发到变化,addResizeListener 事件自动会触发,自适应自然就解决了。
解决办法:
转载至
- 父元素设置 position: relative ,table 外层新增一个 div, 设置 position: absolute; 这样 table 就会跟随父元素的宽度变化了。结构大概是,(实测只需table 外层新增一个 div 设置 position: absolute; )
<div style="position: relative;"> <div style="position: absolute;"> <el-table/> div> div>
既然这样我为何不在图二右内容的宽度 在 resize 是设置宽度,都是 resize ,设置多一个宽度 不会造成什么浪费。这样就能强制其子元素宽度发生变化,table 也就理所当然的改变宽度了。 所以 table 的问题解决(未测试)
Echarts 自适应
echarts 提供了 resize 的 api,不过需要自己去调用。方法很简单。
data: () { return { chart: null //初始化之后赋值为图表 } }, mounted() { addListener(document.getElementById(39;analyze39;), this.chartResize) }, beforeDestroy() { removeListener(document.getElementById(39;analyze39;), this.chartResize) }, methods: { chartResize() { setTimeout(() => { this.chart && this.chart.resize() }) }, }
转载至
resize时,给回调进行节流,使其1帧内最多执行一次。代码如下:
//main.js import Vue from 'vue'; import ElementUI, { Table } from 'element-ui'; import 'element-ui/lib/theme-chalk/index.css'; import App from './App.vue'; // 解决 ElTable 自动宽度高度导致的「ResizeObserver loop limit exceeded」问题 const fixElTableErr = (table) => { const oldResizeListener = table.methods.resizeListener; table.methods.resizeListener = function () { window.requestAnimationFrame(oldResizeListener.bind(this)); }; }; // 一定要在Vue.use之前执行此函数 fixElTableErr(Table); Vue.use(ElementUI); new Vue({ el: '#app', render: (h) => h(App), });
升级element-ui至最新版本(最新版已解决此问题)
npm install [email protected]