锁死使用的 UI 版本库
由于 UI 库的特殊性,官方每次升级会对组件样式有细微调整,而每次的调整都可能存在与之前版本不兼容的情况,当后加入项目的人重新 npm i 或者自己执行了 npm i 时,就会导致样式错乱
锁死版本很简单,只需要在 package.json 里面将对应包名后面的尖括号删除即可。
{ "dependencies": { "ant-design-vue": "1.3.17" } }
|
element-ui 的版本 2.8.2 到 2.13.0 版本号是不兼容的,如果出现原本的组件错乱问题,请锁死版本号,例如 element-ui 的 级联下拉组件
锁死版本号:
{ "dependencies": { "element-ui": "2.8.2" } }
|
文件引用使用 @ 符号
1、项目之间存在一些代码互相拷贝,使用艾特符号就不用再去改一次路径了
2、更简洁方便
如果使用了 vue-cli@2.x,需要添加一下配置
1、安装 eslint-import-resolver-webpack
npm i eslint-import-resolver-webpack -D
|
2、修改 .eslintrc.js 文件的配置
settings: { 'import/resolver': { webpack: { config: './build/webpack.base.conf.js', }, }, }
|
如果使用的是 vue/cli@3,放心使用,无需再加其他配置。
IE11 打不开项目的解决办法
asama-vue-zhichan 里面打不开是因为 element-ui 的引起的,如果你的项目里面使用 element-ui,可尝试进行以下改动:
import ElementUI from 'element-ui/src'
{ test: /\.js$/, loader: 'babel-loader', include: [ resolve('src'), resolve('test'), resolve('node_modules/webpack-dev-server/client'), resolve('node_modules/element-ui') ], exclude: [resolve('node_modules/element-ui/src/utils/popper.js')] }
|
切记不要全局修改通用组件的样式
造成全局污染。
如果要改通用组件的样式,除非你确定项目中所有使用到该组件的地方都要统一,否则请局部定制。
嵌套的弹框一定要加 :append-to-body=“true”
<el-dialog :visible="visible1"> <div> 内容内容内容内容内容内容内容内容内容内容内容内容内容内容内容内容内容内容内容内容内容内容内容内容内容内容内容内容内容内容内容内容内容内容内容内容内容内容内容内容内容内容内容内容内容内容内容内容内容内容内容内容内容内容内容内容内容内容内容内容内容内容内容内容内容内容内容内容内容内容内容内容内容内容内容内容内容内容内容内容内容内容内容内容内容内容内容内容 </div> <div> <el-button @click="visible2 = true">打开</el-button> </div>
<div> <el-dialog :visible="visible2" :append-to-body="true"> <div> 第二个弹框第二个弹框第二个弹框第二个弹框第二个弹框 </div> </el-dialog> </div> </el-dialog>
|
否则会出现以下这种情况:虽然弹出的第二个弹框,但是出现了一层 z-index 更高的一个遮罩,将两个弹框都遮住了。
![]()
设置在 data 里面的监听属性,赋值时先拷贝再赋值
const a = { b: { c: 2 } };
export default { created() { this.a = JSON.parse(JSON.stringify(a)); }, methods: { initA() { this.a = JSON.parse(JSON.stringify(a)); } } }
|
请求数据的容错
接口返回的某些字段可能为空的时候。
axios() .then(res => { if (res.success) { this.record = res.data || {} this.record = res.data || [] } })
|
数据渲染的容错
如果接口返回的数据是一个 JSON 的字符串
<!-- 首先这种写法是不推荐,如果你一定要有这种场景,容错写法如下: --> <div v-for="item in JSON.parse(files || '[]')"></div> <!-- 你可以在获取到数据的时候就处理好: res.data.files = JSON.parse(res.data.files || '[]') this.data = res.data -->
|
获取字段容错
![img]()
多层对象(a.b.c)、多层对象 + 数组取值(a.b[c].d),做好容错
if (a.b && a.b.c) {}
if (a.b && a.b[c] && a.b[c].d) {}
|
通过索引修改data定义的数组项,视图不更新
1、出现问题的原因:
数组和对象是引用类型,只改变其中的值无法触发vue绑在元素(array)上的set方法,无法触发vue观察者,从而无法触发视图更新;涉及vue的双向数据绑定。
官方详解:https://cn.vuejs.org/v2/guide/reactivity.html#%E6%A3%80%E6%B5%8B%E5%8F%98%E5%8C%96%E7%9A%84%E6%B3%A8%E6%84%8F%E4%BA%8B%E9%A1%B9
2、解决办法 Vue.set,示例:
var vm = new Vue({ data:{ array:[ {id: 1, name:'xx'}, {id: 2, name: 'ss'}, ] } })
let change = this.array[i] change.name = 'change name' vm.$set(vm.array, indexOfItem, change)
|
除此之外,还可以使用 push、splice 方法
data () { return { banners: [1, 2, 3, 4] } }
this.banners.push(5)
this.banners.splice(0, 1)
this.banners.splice(0, 1, 6)
|
相同标签v-if判断时,无法正确渲染
1、出错场景,如下将不会正确响应事件
<a-button v-if='type==1' @click='tap1'>按钮1</a-button> <a-button v-else @click='tap2'>按钮2</a-button>
tap1 () { alert('tap1') }, tap2 () { alert('tap2') },
|
2、出现问题的原因:相同元素,v-if无法正确判断
官方详解:https://cn.vuejs.org/v2/guide/conditional.html#%E7%94%A8-key-%E7%AE%A1%E7%90%86%E5%8F%AF%E5%A4%8D%E7%94%A8%E7%9A%84%E5%85%83%E7%B4%A0
3、解决办法增加不同的key ,示例:
<a-button v-if='type==1' @click='tap1' key='btn1'>按钮1</a-button> <a-button v-else @click='tap2' key='btn2'>按钮2</a-button>
|
对后端返回的数据进行合并
页面需要的一部分数据有默认值,一部分需要从接口获取然后合并到一个 data 值中。
data () { return { values: { name: '张三' } } }
post.then(res => { const data = res.data || {} data.users = data.users || [] data.type = data.type || '1' this.values = { ...this.values, ...data } })
|
局部修改组件库样式的方式
1、使用 scoped + deep 关键字
<template> <a-card :bordered="false" :class="['abnormal-card-view', viewClassName]" > <div class="abnormal-view"> <div class="img-box"> <img :src="imgUrl" /> </div> <div> <h2 class="status">{{status}}</h2> <div class="tips">{{tips}}</div> <a-button type="primary" @click="onGotoBack" > 返回 </a-button> </div> </div> </a-card> </template> <style lang="less" scoped> .abnormal-card-view { /deep/ .ant-card-body { height: 100%; } &.abnormal-card-user-view { background-color: transparent; } } </style>
|
2、外部使用一个唯一的 class 类名包裹
<template> <a-drawer :width="width" :visible="visible" :title="title" :closable="true" :after-visible-change="onAfterClose" @close="hideModal" wrap-class-name="drawer-modal-wrap" > <a-spin :spinning="loading" class="drawer-modal-content-main"> <a-form-model :model="values" :rules="rules" ref="form" layout="vertical"> <a-row :gutter="48"> <a-col :span="12"> <a-form-model-item label="股票代码" prop="stockCode"> <a-input v-model="values.stockCode" placeholder="请输入" /> </a-form-model-item> </a-col> </a-row> </a-form-model> </a-spin> </a-drawer> </template> <style lang="less"> .drawer-modal-wrap { .ant-drawer-wrapper-body { padding-top: 55px; } } </style>
|
这种情况不能使用第一种方案,因为节点插入方式不一样,超出了加载器的范围。
表单自定义校验一定要调用 callback 函数
如果不调用,点击提交的时候会出现 this.$refs.form.validate() 函数不触发的问题
![img]()
动态表单联动
场景:a 联动 b、a 联动 b,b 联动 c
<el-form ref="form" :model="values" :rules="rules" :label-col="labelCol" :wrapper-col="wrapperCol" style="width: 600px"> <el-form-item label="下拉动态切换表单" prop="selectType"> <el-select v-model="values.selectType" placeholder="请选择"> <el-option label="类型一" value="type1"></el-option> <el-option label="类型一" value="type2"></el-option> </el-select> </el-form-item>
// 直接在这里取值进行判断 <template v-if="values.selectType === 'type1'"> <el-form-item el-form-item label="股票代码" prop="code"> <el-input v-model="values.code" placeholder="请输入" /> </el-form-item> <el-form-item label="涉及证券名称" prop="name"> <el-input v-model="values.name" placeholder="请输入" /> </el-form-item> </template> <template v-else> <el-form-item label="标的金额" prop="money"> <el-input v-model="values.money" placeholder="请输入" /> </el-form-item> <el-form-item label="涉及投资者交易编码" prop="tradecode"> <el-input v-model="values.tradecode" placeholder="请输入" /> </el-form-item> </template>
<el-form-item label="中国证监会处罚决定书编号" prop="punishnum"> <el-input v-model="values.punishnum" placeholder="请输入" /> </el-form-item> <el-form-item label="违法信息披露事实" prop="message"> <el-input v-model="values.message" type="textarea" placeholder="请输入" /> </el-form-item> <el-form-item :wrapper-col="{span: 14, offset: 7}"> <el-button type="primary" @click="onSubmit" :loading="confirmLoading"> 提交 </el-button> <el-button class="ML10" @click="onReseetForm"> 重置 </el-button> </el-form-item> </el-form>
|
data () { return { labelCol: { lg: { span: 7 }, sm: { span: 7 } }, wrapperCol: { lg: { span: 10 }, sm: { span: 17 } }, values: { selectType: 'type1', code: '', name: '', money: '', tradecode: '', punishnum: '', message: '' }, rules: { code: [{ required: true, message: '不允许为空' }], name: [{ required: true, message: '不允许为空' }], money: [{ required: true, message: '不允许为空' }], tradecode: [{ required: true, message: '不允许为空' }], punishnum: [{ required: true, message: '不允许为空' }], message: [{ required: true, message: '不允许为空' }] }, confirmLoading: false } }
|
forEach
修改原数组,无返回值。
拿到接口数据之后转换成页面需要的数据,或者将数据转换成接口需要的数据结构。
res.data.row.forEach(item => { item.fileInfo = JSON.parse(item.fileInfo)
item.statusName = item.status === 0 ? '执行失败' : '执行成功' })
|
map
将数据处理成接口想要的格式
返回新数组
const data = this.values.map(item => ({ ...item, time: $moment(item.time).format('YYYY-MM-DD') }))
const files = this.files.map(item => { if (item.response && item.response.code === 0) { return item.response.data.url } return null }).filter(f => f)
|
filter
筛选出符合条件的数据
返回新数组
const data = this.records.filter(item => item.status === 1)
this.records = this.records.filter(item => item.id !== record.id)
|
find
找出符合条件的数据
返回找到的那条对象数据,没找到返回 null。
const item = this.records.find(item => item.status === 1)
const hasDelete = this.actions.find(action => action === 'delete')
|
includes
是否包含某个值
返回 true 或 false
const hasDelete = this.actions.includes('delete')
this.showName = types.includes(1)
|
判断是否为空
undefined、null、空字符串、0、NaN 在 if 里面都是 false,如果某个值确定为 false 时一定返回的是空的,可以直接用变量来判断。
if (status) {}
if (res.data) {}
if (values.time) {}
|
![img]()
解决办法:
指定 v-model 绑定值的初始值
如果出现调用了 this.***$refs***.***form***.***resetFields***() 方法之后表单无法编辑时,多半也是没有设置默认值造成的。
el-select 获取 label
总是有需要获取下拉列表里面显示的 label 的情况。
// 标签定义 <el-select ref="select"></el-select>
// js 获取 this.$refs.select.selectedLabel
|
使用 const 和 let 替换 var
区别:
1、var 全局作用域,let、const 块级作用域
2、let、const 不存在变量提升
3、let、const 不会挂载在window下面
const:
1、声明之后必须马上赋值,否则会报错
2、简单类型一旦声明就不能再更改,引用类型(数组、对象)内部数据可以更改。
// 枚举常量 const status = ['成功', '失败', '审批中', '被驳回'] // 设置默认值 const defaultValues = { type: '自然人' }
// 根据不同的业务类型有不同的值 let tips = '报错了' if (res.code === 601) { tips = '请先登录后再操作' } else if (res.code === 501) { tips = '服务器异常' } this.$message.error(tips)
|
尽量使用语义化的命名
js、class 等都遵循这个原则,同时尽量避免通用名称的命名。
<div class="box"></div>
// 尽量加上业务属性 <div class="case-info-box"></div>
<el-dialog ref="modal"></el-dialog>
// 尽量加上业务属性 <el-dialog ref="modalCaseInfo"></el-dialog>
|
vant 里面去除将 placeholder 转成错误提示的行为
https://github.com/youzan/vant/issues/6378
通过 Form 的 show-error 属性禁用所有 Field 的 error 提示。
computed 的使用
计算属性是基于它们的响应式依赖进行缓存的。只在相关响应式依赖发生改变时它们才会重新求值。多次访问计算属性会立即返回之前的计算结果,而不必再次执行函数。
表单联动的时候,根据另外一个表单的切换显示不同的下拉选项
客户名称切换的时候,去获取不同的产品名称
![img]()
computed: { searchProduct () { if (this.searchValue.company) { return this.selectValues.productList.filter(item => { return this.searchValue.company === item.group }) } return [] } }
|
根据不同的值来进行规则的检测切换
![img]()
computed: { searchRules () { return { fieldName: [{ required: true, message: '字段不能为空', trigger: 'blur' }], operator: [{ required: true, message: '运算符不能为空' }], value: [{ required: true, message: '条件不能为空' }], min: [{ required: this.searchValues.operator === 'between', message: '最小值不能为空' }], max: [{ required: this.searchValues.operator === 'between', message: '最大值不能为空' }] } } }
|
refs 的使用
获取组件的实例。
<el-form :model="values" :rules="rules" ref="form"></el-form>
<script> export default { methods: { onClosed () { this.$refs.form.resetFields() }, onSubmit () { const me = this me.$refs.form.validate(valid => { if (valid) {
} }) } } </script>
|
加载数据
<ex-table-auto class="el-table-nowwarp" ref="authListGrid" url='/asama/user/UserAuthInfoRpc/authList.json' autoFill autoPage emptyText="暂无符合条件的审核信息"></ex-table-auto>
<script> export default { methods: { doAuth (status) { var me = this var data = { authId: me.authForm.id, result: status } me.$ajax({ url: '/asama/user/UserAuthInfoRpc/doAuth.json', data: data, success (response) { me.showDetailDialog = false me.authForm = {} // 重新刷新数据 me.$refs.authListGrid.loadData(this.searchValue) } }) } } } </script>
|
获取实例里面的属性,例如获取 select 里面的 label
// 标签定义 <el-select ref="select"></el-select>
// js 获取 this.$refs.select.selectedLabel
|
nextTick 的作用
在下次 DOM 更新循环结束之后执行延迟回调。在修改数据之后立即使用这个方法,获取更新后的 DOM。
如果发现实际要获取 refs 定义的实例取不到时,可以尝试在 $nextTick 方法里面试试。
this.msg = 'Hello'
this.$nextTick(() => { })
|
v-show 和 v-if 的区别
都是用来显示或者隐藏元素的,v-show 使用的是 css 中 display,v-if 是对节点的添加或删除。
// if/else/else-if <font v-if="scope.row.copies=='one'">原被告共用一份</font> <font v-else-if="scope.row.copies=='each'">原被告各一份</font> <font v-else>原被告共用一份</font>
// 存在列表再展示 <el-table v-if="formValue.case_infos.length > 0" :data="formValue.case_infos"></el-table>
// 附件内容展示 <div v-if="item.attachmentShow"> <label>附件:</label> <div v-for="(file,i) in item.attachmentShow" :key="i"><a>{{file.fileName}}</a></div> </div>
// 组件之间的切换 <step1 v-show="currentTab === 0" :values="values" :on-next-step="() => currentTab = 1" /> <step2 v-show="currentTab === 1" :values="values" :on-next-step="() => currentTab = 2" :on-prev-step="() => currentTab = 1" /> <step3 v-show="currentTab === 2" :values="values" />
|
vue 中冒号和艾特符号的区别
冒号传递属性,@ 符号传递方法
<template> <a-modal :width="width" :visible="visible" :title="title" :after-close="onClosed" @cancel="onHideModal" @ok="onHideModal" > 内容 </a-modal> </template>
<script> export default { data () { return { /** * 1、宽度可选值:720、560、320 * 2、操作以简单为主,需要内滚动或是长时间的操作,或是展开内容信息较大且和当前页面的场景联系不大时 * 建议使用一般的页面代替 * 3、主要按钮居右,次要按钮居左 */ width: 560, visible: false, title: '弹框标题' } }, methods: { showModal () { this.visible = true }, onHideModal () { this.visible = false }, onClosed () { console.log('完全关闭之后的回调处理,一般用于清空表单的操作') } } } </script>
|
冒号传递的,通过 this.xxx 访问,艾特符号传递的,通过 this.$emit(‘xxx’) 调用
console.log(this.title) this.$emit('cancel')
|
== 和 ===(非全等和全等)
项目中涉及的判断尽量都用 === 替换 ==,尽量遵循严格模式。
=== 表示类型和值相等, == 仅表示值相等。
按照官方写法或者复制其他地方的代码过来总是有问题怎么办
对照写法检查属性是不是遗漏了什么
复制过来的代码,出现某个控件显示异常时,去官方文档对照一下 api,看下是不是少了什么属性,比如:a-select(el-select) 组件,子组件是靠 value 属性来匹配选中值的,而有些地方的写法就少了这个属性,导致无选中的奇怪 bug。
下拉 value 数据类型问题
value 字段如果是变量前面是需要添加冒号的,如果是静态数据且值是数字,务必注意取值的判断。
<el-select v-model="value" placeholder="请选择"> <el-option v-for="item in options" :key="item.value" :label="item.label" :value="item.value" > </el-option> </el-select>
|
a-tree 的 select 里面如何获取父级数据
template 绑定 data 中的数据不需要用 this 指定
<span :id="this.id">错误写法</span> // 错误写法 <span :id="id">正确写法</span> // 正确写法 data(){ return { id:1 } }
|
<el-form :model="values"> </el-form>
data () { return { // 绑定到 el-form 的 model 属性变量 values: { files: [] } } }
onSubmit () { // ... // 错误示例 this.values.fileName = this.values.files[0].fileName this.values.filePath = this.values.files[0].filePath delete this.values.files this.$ajax({ ..., data: this.values }) // 如果接口需要的数据需要进行处理,用一个变量来进行接收,而不是直接改动使用到的绑定给 model 的变量 const data = {} const { files } = this.values data.fileName = files[0].fileName data.filePath = files[0].filePath this.$ajax({ ..., data }) }
|
输入框删除字符鼠标聚焦位置自动跳转到最后
升级 ant-design-vue 到 1.7.2 版本。
时间字段发送到后端对不上
已提供 ex-date-picker 组件,请优先使用。
1、如果是 element-ui(具体格式看这里),给组件设置 value-format 字段:
<el-date-picker v-model="formValue.planOnlineTime" value-format="yyyy-MM-dd HH:mm:ss"></el-date-picker>
|
2、在调用接口之前,调用 moment 转换:
const data = { ...this.formValue }
data.planOnlineTime = this.$moment(data.planOnlineTime).format('YYYY-MM-DD HH:mm:ss') this.$ajax({ url: '/apollo/app/BugRpc/saveUpdate.json', method: 'post', data, success: (rs) => { } })
|
注意:转换之后后端通过字符串形式接收
table + upload 使用时,upload 失效问题
hg-table + upload 组件一起使用时,upload 组件绑定的值不会更新,因为 hg-table 里面维护了 data-source,这个值并没有双向绑定返回。
js 里面写法:
data () { dataSource: [] }
loadData () { this.$http() .then(res => { this.dataSource = res.data.rows return res }) }
|
vue 里面写法:
<hg-table :load-data="loadData"> <template slot-scope="text, record, indedx"> <hg-upload v-model="dataSource[index].files"></hg-upload> </template> </hg-table>
|
前端通过文件流下载文件问题
某些场景下,需要前端读取到文件流自己下载文件。比如 https 协议网页要下载不同域下的 http 协议文件,通过 window.open 会被拦截,而 form 方式有些文件又会变成预览,比如说 pdf 文件
基于 antd 的脚手架里面,使用 $http,指定 headerType 是 download 即可。
$http({ url: '//haigui-static.oss-cn-hangzhou.aliyuncs.com/test-dedicated/10.pdf', headerType: 'download', method: 'get', fileName: 'download.docx' }) .then(res => { })
|
项目中如果没有 http.js,可通过 hg init 初始化脚手架之后,去 utils/http.js 里面复制代码。
form-model-item 里面加一个 input,type 是 hidden,change 的时候更新 values.fileList(处理成上传需要的数据格式),同时更新 fileList(用于显示,直接赋值)
<a-form-model-item label="中国证监会处罚决定书编号" prop="fileList"> <a-upload-dragger action="https://www.mocky.io/v2/5cc8019d300000980a055e76" list-type="picture-card" :file-list="fileList" @change="onFileChange"> <div> <a-icon type="plus" /> <div class="ant-upload-text"> 上传 </div> </div> </a-upload-dragger> <a-input type="hidden" :value="JSON.stringify(values.fileList)"></a-input> </a-form-model-item> <script> export default { data () { return { values: { fileList: [] }, fileList: [], rules: { fileList: [{ required: true, message: '文件不允许为空', trigger: 'blur' }] } } }, methods: { onFileChange (info) { const status = info.file.status this.fileList = info.fileList if (status === 'done') { this.$message.success(`${info.file.name} file uploaded successfully.`) this.values.fileList = this.fileList.map(item => item.response.data ? item.response.data : item) } else if (status === 'error') { this.$message.error(`${info.file.name} file upload failed.`) } } } } </script>
|
element-ui 里面的 el-upload 写法参照:https://blog.csdn.net/HTingJing/article/details/107960921
JS中循环(forEach,forIn,forOf)总结
- forEach 遍历列表值,不能使用 break 语句或使用 return 语句
- for in 遍历对象键值(key),或者数组下标,不推荐循环一个数组
- for of 遍历列表值,允许遍历 Arrays(数组), Strings(字符串), Maps(映射), Sets(集合)等可迭代的数据结构等.在 ES6 中引入的 for of 循环,以替代 for in 和 forEach() ,并支持新的迭代协议。
- for in循环出的是key,for of循环出的是value;
- for of是ES6新引入的特性。修复了ES5的for in的不足;
- for of不能循环普通的对象,需要通过和Object.keys()搭配使用。