Vue项目:iView & vue-i18n & echarts遇到的问题和解决方法

目录

  • 一、i18n国际化切换
    • 1. data中的数据
    • 2. echarts图表中的相关元素
      • 2.1 轴坐标单位
      • 2.2 各种 tooltip
      • 2.3 legend
    • 3. iView框架中的组件
      • 3.1 table表格
  • 二、echarts中遇到的问题
    • 1. 使柱形图、折线图等尽量占满整个 canvas(设置 `grid`)
    • 2. 将 echarts图表放在 Tab中切换展示时,图表渲染不出来
    • 3. setOption()第三个参数设置为 true,报错了:[Vue warn]: Error in nextTick: "TypeError: Cannot read property 'count' of undefined"
  • 三、 iView中遇到的问题
    • 1. 内置的图标不显示
    • 2. 子组件(包括后代多层子组件)中的 Tabs标签渲染到了父组件上
    • 3. TabPane高度不够,最下方的 Page分页器的每页条数下拉选项框被挡住
    • 4. Modal的确定按钮点击之后直接就关闭了弹窗,实际还有其他的操作,比如 Form表单验证,或者后台请求,需要根据结果判断是否可以关闭弹窗
    • 5. Form表单对数字进行必填+格式校验
    • 6. Form表单结构相同的``根据条件切换,切换后原先条件下的``的校验报错信息仍然显示在页面上

一、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')}
${ this.$t('common.tipName')}: ${ data.value}`
; }) this.option.series[0].markLine.tooltip.formatter = `${ this.$t('common.tipAverage')}
${ 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')}
${ this.$t('common.tipName')}: ${ params.name}`
; } } } </script>

2.3 legend

legend的数据需要与 series的数据中的 name保持一一对应的关系,可以将这两处都传入国际化对应的 key,在 legend.formatterseries.label.formatter使用回调函数的形式返回国际化处理后的显示值
有个问题:在语言切换时,需要点击一个图例才会触发legendseries的切换,所以还是需要通过 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设置为一个比较小的值
Vue项目:iView & vue-i18n & echarts遇到的问题和解决方法_第1张图片

2. 子组件(包括后代多层子组件)中的 Tabs标签渲染到了父组件上

**问题原因:**嵌套的 Tabs标签需要在 Tabs中指定 name 属性来区分层级,在 TabPane 中设置 tab 属性指向对应 Tabsname 字段。


<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;覆盖,暂时不知道这样会有什么问题


<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 状态,需要手动关闭对话框,常用于表单提交。


<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,并且 也要指定 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' }
      ]
    }
  }
}

解决方法: 仍然默认 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表单结构相同的根据条件切换,切换后原先条件下的的校验报错信息仍然显示在页面上

问题原因: 根据 vue的渲染规则,切换条件会复用原先的 组件的 dom,只更新其中的数据,导致原先校验的报错信息还会保留在页面上
解决方法: 给不同条件下的 添加不同的 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>
      
      <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' }
          ]
    }
  }
}

你可能感兴趣的:(Vue,vue,数据可视化)