Vue项目:iView & vue-i18n & echarts遇到的问题和解决方法
vue-i18n & echarts遇到的问题和解决方法data中的预置数据echarts图表中的 tooltip、markline等data中的预置数据data中的数据初始化一次之后,不手动修改是不会变化的,所以直接在data中使用this.$t('xxx'),只有页面首次加载是有效的,中途切换的话,是没办法跟着切换的。解决方法:将需要用到国际化的数据定义在computed里面...
一、i18n国际化切换
【粗暴直接】如果国际化切换涉及的手动修改太多,干脆切换语言的时候直接刷新页面重新渲染算了,以下都是不刷新页面的方法
1. data中的数据
data中的数据初始化一次之后,不手动修改是不会变化的,所以直接在 data中使用this.$t('xxx')
,只有页面首次加载是有效的,中途切换的话,是没办法跟着切换的。
解决方法 1: 将需要用到国际化的数据定义在computed
里面
<template>
<div>
<ul>
<li v-for="data in list">{{ data.name }}</li>
</ul>
</div>
</template>
<script>
export defaut {
computed: {
list() {
return [
{name: this.$t('common.title1')},
{name: this.$t('common.title2')},
{name: this.$t('common.title3')}
]
}
}
}
</script>
解决方法 2: 在 data中的属性值定义为国际化对应的key
,在 template中使用{{ $t(key) }}
的方式来引用
<template>
<div>
<ul>
<li v-for="data in list">{{ $t(data.name)}}</li>
</ul>
</div>
</template>
<script>
export defaut {
data() {
return {
list: [
{name: 'common.title1'},
{name: 'common.title2'},
{name: 'common.title3'}
]
}
}
}
</script>
// zh_CN.json
{
"common": {
"title1": "标题1",
"title2": "标题2",
"title3": "标题3"
}
}
2. echarts图表中的相关元素
2.1 轴坐标单位
在 watch中监听 '$i18n.locale'
,在语言切换时,重新配置 option
,使用 setOption()
方法。
(此方法对于echarts中所有配置项都适用,但是过于繁琐,有些配置项没有必要使用,比如有 formatter属性的配置项,可以对 formatter使用回调函数形式)
<script>
export defaut {
data() {
return {
myChart: null,
option: null,
}
},
watch: {
'$i18n.locale'(newValue) {
// 设置 option参数
this.option.series[0].data,forEach( data => {
data.tooltip.formatter = `${this.$t('common.tipTitle')}<br/>
${this.$t('common.tipName')}: ${data.value}`;
})
this.option.series[0].markLine.tooltip.formatter = `${this.$t('common.tipAverage')}</br>
${this.$t('common.tipContent')}: {c}`;
// 使用 setOption()方法重绘
this.myChart.setOption(this.option)
}
}
}
</script>
2.2 各种 tooltip
不管是全局的 tooltip
,还是 series
中单独定制的,(markline
在官网中没有提供 tooltip
属性,但是我添加生效了。。。)tooltip
中的 formatter
属性均使用回调函数形式,即可实现国际化自动切换
<script>
let option = {
tooltip: {
// 使用箭头函数,否则取不到 this
formatter: (params) => {
return `${this.$t('common.tipTitle')}<br/>
${this.$t('common.tipName')}: ${params.name}`;
}
}
}
</script>
2.3 legend
legend
的数据需要与 series
的数据中的 name
保持一一对应的关系,可以将这两处都传入国际化对应的 key
,在 legend.formatter
和 series.label.formatter
使用回调函数的形式返回国际化处理后的显示值
有个问题:在语言切换时,需要点击一个图例才会触发legend
和 series
的切换,所以还是需要通过 watch监听,手动触发一下 setOption(),不需要重新设置配置项
<script>
export defaut {
data() {
return {
myChart: null,
option: null,
}
},
mounted() {
let key = 'common.type.';
let legendData = [
`${key}type1`,
`${key}type2`,
`${key}type3`,
`${key}type4`,
];
let seriesData = [
{ value: 0, name: `${key}key1` },
{ value: 0, name: `${key}key2` },
{ value: 0, name: `${key}key3` },
{ value: 0, name: `${key}key4` }
];
this.option = {
legend: {
orient: 'vertical',
left: 'left',
data: legendData,
formatter: (params) => {
return this.$t(params);
}
},
series: [{
data: seriesData,
type:'pie',
radius: '60%',
center: ['60%', '60%'],
label: {
normal: {
formatter: (params) => {
return this.$t(params.name);
}
}
}
}]
}
this.myChart.setOption(this.option);
},
watch: {
'$i18n.locale'(newValue) {
// 不需要重新设置配置项,只需要手动触发一下setOption()
this.myChart.setOption(this.option)
}
}
}
</script>
3. iView框架中的组件
3.1 table表格
- 表头
在data
中定义表头时,在 renderHeader回调函数中定义表头显示内容
data () {
return {
column: [{
key: 'name',
renderHeader: (h, params) => {
return h('strong', this.$t('common.name'));
}
},
{
key: 'age',
renderHeader: (h, params) => {
return h('strong', this.$t('common.age'));
}
},
{
key: 'gender',
renderHeader: (h, params) => {
return h('strong', this.$t('common.gender'));
}
}]
}
}
二、echarts中遇到的问题
1. 使柱形图、折线图等尽量占满整个 canvas(设置 grid
)
grid: {
top: "40", // 预留y轴单位的展示空间
left: "40", // 预留y轴刻度数值的展示空间
right: "40", // 预留x轴单位的展示空间
bottom: "30" // 预留x轴类目名称的展示空间
}
2. 将 echarts图表放在 Tab中切换展示时,图表渲染不出来
问题原因: Tab
一开始是隐藏的,没有宽高,echarts在初始化时,没有获取到宽高导致图表显示不出来
解决方法: 每次设置完 option
调用 setOption()
之后,再手动调用一下 resize()
方法,或者每次都重新执行 echarts.init()
、setOption()
3. setOption()第三个参数设置为 true,报错了:[Vue warn]: Error in nextTick: “TypeError: Cannot read property ‘count’ of undefined”
三、 iView中遇到的问题
1. 内置的图标不显示
问题原因: webpack中配置了 url-loader
来处理字体文件,大小低于limit
属性限制的资源被转换成了 Base64
解决方法: 将这个 limit
设置为一个比较小的值
2. 子组件(包括后代多层子组件)中的 Tabs标签渲染到了父组件上
**问题原因:**嵌套的 Tabs
标签需要在 Tabs
中指定 name
属性来区分层级,在 TabPane
中设置 tab
属性指向对应 Tabs
的 name
字段。
<!-- 父组件 -->
<template>
// 在 Tabs上设置 name属性
<Tabs name="tab1" type="card" @on-click="tabChange">
// 在 TabPane上设置 tab属性
<TabPane :label="'标签1'" name="overview" tab="tab1">
// 子组件
<my-component></my-component>
</TabPane>
<TabPane :label="'标签2'" name="transport" tab="tab1">
</TabPane>
</Tabs>
</template>
<!-- 子组件 -->
<template>
// 在 Tabs上设置 name属性
<Tabs name="tab2" type="card" @on-click="tabChange">
// 在 TabPane上设置 tab属性
<TabPane :label="'标签1.1'" name="flow" tab="tab2"></TabPane>
<TabPane :label="'标签1.2'" name="speed" tab="tab2"></TabPane>
</Tabs>
</template>
3. TabPane高度不够,最下方的 Page分页器的每页条数下拉选项框被挡住
问题原因: .ivu-tabs
设置了 overflow: hidden
解决方法: 将分页器的 placement
属性设置为 top
,下拉选项框就会在分页器上方展开
更新: 其他一些下拉框也会被挡住,比如作为查询条件的下拉框,直接在 css中设置 overflow: visible !important;
覆盖,暂时不知道这样会有什么问题
<!-- 自己封装的一个简单的带分页器的 table -->
<template>
<div>
<Table
stripe
:max-height="maxHeight"
:columns="columns"
:data="tableData"
@on-selection-change="selectionChange" />
<div class="clearfix" style="margin: 10px;">
<div style="float: right;">
<Page
:total="pagination.total"
:current="pagination.current"
:page-size="pagination.size"
:placement="placement"
size="small"
show-elevator
show-sizer
@on-change="changePage"
@on-page-size-change="changePageSize" />
</div>
</div>
</div>
</template>
props: {
placement: {
type: String,
default () {
return 'top'
}
}
},
4. Modal的确定按钮点击之后直接就关闭了弹窗,实际还有其他的操作,比如 Form表单验证,或者后台请求,需要根据结果判断是否可以关闭弹窗
解决方法: 参考官方文档中 “异步关闭” ,给Modal添加属性loading后,点击确定按钮对话框不会自动消失,并显示 loading 状态,需要手动关闭对话框,常用于表单提交。
<!-- 自己封装的一个简单的带分页器的 table -->
<template>
<div>
<Modal
v-model="modalhowFlag"
:loading="modalLoading"
:title="xxx"
:ok-text="保存'"
@on-ok="executeSaving"
width="60%">
<From ref="formValidate" :rules="ruleValidate">
...
</From>
</Modal>
</div>
</template>
methods: {
executeSaving() {
this.$refs.formValidate.validate((valid) => {
// 数据校验
if (valid) {
// 发送请求
axios({...}).then(res => {
if (res.data.status == 'ok') {
this.$Message.success('Success!');
this.$emit('savingHandle', true);
} else {
this.$Message.error('Fail!');
this.$emit('savingHandle', false);
}
}).catch(err => {
this.$Message.error('Fail!');
this.$emit('savingHandle', false);
})
} else {
this.$Message.error('Fail!');
this.$emit('savingHandle', false);
}
}
},
// 保存结果
savingHandle(flag) {
// console.log(valid)
// 关闭 确定按钮的 loading
this.modalLoading = false;
if (flag) {
// 保存成功,关闭 Model
this.createShowFlag = false;
}
}
},
5. Form表单对数字进行必填+格式校验
问题现象: 同时添加 必填 和 正则校验时,如果输入非数字,会提示必填。
解决思路1-实现有问题,只是记录一下,以后面的解决方法为准: 将“格式”校验写在“必填”校验之前,并且两者都要指定 type:number
,并且 <Input>
也要指定 number
类型—— 页面无法显示必填标识*
<template>
<Form ref="createForm" :model="newObj" :rules="ruleValidate" :label-width="150">
<FormItem label="xx号码" prop="xxNumber">
<Input v-model="newObj.xxNumber" number/>
</FormItem>
</Form>
</template>
computed: {
ruleValidate() {
return {
asNumber: [
// xxNumberPattern是自定义的正则
{ type: 'number', pattern: xxNumberPattern, message: '格式不正确。', trigger: 'blur' },
{ type: 'number', required: true, message: '该字段为必填。', trigger: 'blur' }
]
}
}
}
解决方法: <Input>
仍然默认 text
类型,“必填”校验使用默认方式,不指定number
,“格式”校验需要自定义
<template>
<Form ref="createForm" :model="newObj" :rules="ruleValidate" :label-width="150">
<FormItem label="xx号码" prop="xxNumber">
<Input v-model="newObj.xxNumber" />
</FormItem>
</Form>
</template>
computed: {
ruleValidate() {
return {
asNumber: [
{ required: true, message: '该字段为必填。', trigger: 'blur' },
{ validator: this.xxNumberFormatValidate, trigger: 'blur' }
]
}
}
},
methods: {
xxNumberFormatValidate(rule, value, callback) {
// xxNumberPattern是自定义的正则
if (!xxNumberPattern.test(value)) {
callback('格式不正确。');
} else {
callback();
}
}
}
6. Form表单结构相同的<FormItem>
根据条件切换,切换后原先条件下的<FormItem>
的校验报错信息仍然显示在页面上
问题原因: 根据 vue的渲染规则,切换条件会复用原先的 <FormItem>
组件的 dom,只更新其中的数据,导致原先校验的报错信息还会保留在页面上
解决方法: 给不同条件下的 <FormItem>
添加不同的 key
<template>
<Form ref="configForm" :model="config" :rules="ruleValidate" :label-width="150">
<FormItem label="选择条件" prop="condition">
<Select v-model="config.condition">
<Option v-for="o in conditionOptions" :value="o.id" :key="`o-${o.id}`">{{ o.name }}</Option>
</Select>
</FormItem>
<!-- 如果不加 key的话,条件1的情况下,“选择设备”的 FormItem 报错而不纠正的情况下直接切换成条件2,根据 Vue的渲染原则,这个 FormItem的 dom会被“选择A端设备”所复用,原先的报错信息因为没有被纠正而保留在页面上 -->
<FormItem label="选择设备" prop="deviceIds" v-if="config.condition == 1" key="device">
<CheckboxGroup v-model="config.deviceIds">
<Checkbox v-for="o in deviceList" :label="o.id" :key="`o-${o.id}`">{{ o.name }}</Checkbox>
</CheckboxGroup>
</FormItem>
<FormItem label="选择A端设备" prop="deviceAIds" v-if="config.condition == 2" key="deviceA">
<CheckboxGroup v-model="config.deviceAIds">
<Checkbox v-for="o in deviceAList" :label="o.id" :key="`o-${o.id}`">{{ o.name }}</Checkbox>
</CheckboxGroup>
</FormItem>
<FormItem label="选择B端设备" prop="deviceBIds" v-if="config.condition == 2" key="deviceB">
<CheckboxGroup v-model="config.deviceBIds">
<Checkbox v-for="o in deviceBList" :label="o.id" :key="`o-${o.id}`">{{ o.name }}</Checkbox>
</CheckboxGroup>
</FormItem>
</Form>
</template>
computed: {
ruleValidate() {
return {
condition : [
{ type: 'number', required: true, message: '请选择条件', trigger: 'change' }
],
deviceIds: [
{ type: 'array', required: true, message: '请选择设备', trigger: 'change' }
],
deviceAIds: [
{ type: 'array', required: true, message: '请选择A端设备', trigger: 'change' }
],
deviceBIds: [
{ type: 'array', required: true, message: '请选择B端设备', trigger: 'change' }
]
}
}
}
更多推荐
所有评论(0)