数据可视化【原创】vue复合数字形式展示

2023-08-30

做数据可视化的时候,经常碰到需要很灵活的数字形式展示。

先上个效果图:

如图包括名称,数量,别名,单位,上升下降,环比等等的复合数据展示,并且需要支持样式灵活配置。

此组件包括2个模块,父容器组件box-group,其中每一项的子组件box。

父组件 box-group

 1 <template>
2 <div class="box-group">
3 <div class="box-wrapper" v-for="(item, index) in dataProvider" :key="index" v-if="index < dataProvider.length && index % col == 0">
4 <box
5 class="item"
6 :theme="theme"
7 :style="{ marginBottom: lineGap }"
8 v-for="(bb, idx) in colArray"
9 :dataProvider="dataProvider[index + idx]"
10 :key="idx"
11 :openType="openType"
12 @selected="selected"
13 ></box>
14 </div>
15 </div>
16 </template>
17
18 <script>
19 import Box from './components/box.vue';
20
21 export default {
22 name: 'LiloBoxGroup',
23 props: {
24 col: {
25 type: Number,
26 default: 2
27 },
28 theme: {
29 type: String,
30 default: 'blue'
31 },
32 lineGap: {
33 type: String,
34 default: '10px'
35 },
36 dataProvider: {
37 type: Array,
38 default() {
39 return [
40 // { label: '总人口数', value: '123213', unit: '人', color: '#ff0000' },
41 // { label: '总户数', value: '123213', unit: '户', color: '#123312' },
42 // { label: '总人口数', value: '123213', unit: '人', color: '#235234' },
43 // { label: '总户数', value: '123213', unit: '户', color: '#444444' }
44 ];
45 }
46 },
47 openType: {
48 type: String,
49 default: 'popup'
50 }
51 },
52 data() {
53 return {};
54 },
55 components: {
56 Box
57 },
58 mounted() {},
59 computed: {
60 colArray() {
61 let result = [];
62 for (let i = 0; i < this.col; i++) {
63 result.push(i);
64 }
65 return result;
66 }
67 },
68 methods: {
69 selected(data) {
70 this.$emit('selected', data);
71 }
72 }
73 };
74 </script>
75
76 <style lang="scss" scoped>
77 .box-group {
78 .box-wrapper {
79 display: flex;
80 margin-top: 5px;
81 .item {
82 flex: 1;
83 }
84 }
85 }
86 </style>

子组件 box

  1 <template>
2 <div class="box">
3 <div class="icon-wrapper"><div class="icon" :style="iconStyle" v-if="dataProvider"></div></div>
4 <div class="box-wrapper" v-if="dataProvider">
5 <div class="label" :style="labelStyle">{{ dataProvider.label }}</div>
6 <div @click="goTo">
7 <span class="value" :style="valueStyle" v-if="valueShow">{{ dataProvider.value | toThousandFilter }}</span>
8 <span class="alias" :style="aliasStyle">{{ dataProvider.alias }}</span>
9 </div>
10 <div class="unit" :style="unitStyle" v-if="valueShow">
11 {{ dataProvider.unit }}
12 <span v-if="dataProvider.hasOwnProperty('percent') && dataProvider.percent"
13 class="percent" :style="percentStyle" v-html="percentFormatter"></span>
14 <span v-if="dataProvider.hasOwnProperty('trend') && dataProvider.trend"
15 class="trend" :style="trendStyle" v-html="trendFormatter"></span>
16 </div>
17 </div>
18 <!-- <component ref="component" :is="openType" /> -->
19 </div>
20 </template>
21
22 <script>
23 import variables from '../../../../src/styles/variables.scss';
24 // import Popup from './popup.vue';
25 // import Drawer from './Drawer.vue';
26 export default {
27 name: 'LiloBox',
28 // components: {
29 // Popup,
30 // Drawer
31 // },
32 props: {
33 dataProvider: {
34 type: Object,
35 default() {
36 return null;
37 }
38 },
39 theme: {
40 type: String,
41 default: 'blue'
42 },
43 openType: {
44 type: String,
45 default: 'popup'
46 }
47 },
48 computed: {
49 themeObject() {
50 return {
51 icon: variables[this.theme],
52 label: variables[this.theme],
53 value: variables[this.theme],
54 alias: variables[this.theme],
55 unit: variables[this.theme]
56 };
57 },
58 valueShow() {
59 if (this.dataProvider.hasOwnProperty('show')) {
60 return this.dataProvider.show;
61 } else {
62 return true;
63 }
64 },
65 iconStyle() {
66 return {
67 'background-color': this.dataProvider.iconColor || this.dataProvider.color || this.themeObject.icon
68 };
69 },
70 labelStyle() {
71 return {
72 color: this.dataProvider.labelColor || this.dataProvider.color || this.themeObject.label,
73 fontSize: (this.dataProvider.labelSize || 1) + 'rem'
74 };
75 },
76 valueStyle() {
77 return {
78 color: this.dataProvider.valueColor || this.dataProvider.color || this.themeObject.value,
79 fontSize: (this.dataProvider.valueSize || 1.5) + 'rem'
80 };
81 },
82 aliasStyle() {
83 return {
84 color: this.dataProvider.aliasColor || this.dataProvider.color || this.themeObject.alias,
85 fontSize: (this.dataProvider.aliasSize || 1) + 'rem'
86 };
87 },
88 unitStyle() {
89 return {
90 color: this.dataProvider.unitColor || this.dataProvider.color || this.themeObject.unit,
91 fontSize: (this.dataProvider.unitSize || 1) + 'rem'
92 };
93 },
94 percentStyle() {
95 return {
96 color: this.dataProvider.percent > 0 ? variables.red : variables.deepGreen
97 };
98 },
99 trendStyle() {
100 return {
101 color: this.dataProvider.trend > 0 ? variables.red : variables.deepGreen
102 };
103 },
104 percentFormatter() {
105 const _percent = this.dataProvider.percent;
106 if (!_percent || _percent === 0) {
107 return '';
108 }
109 if (_percent > 0) {
110 return '<i class="el-icon-caret-top" style="margin-right:3px;"></i>' + Math.abs(_percent) + '%';
111 // return "" + Math.abs(_percent) + "%"
112 } else {
113 return '<i class="el-icon-caret-bottom" style="margin-right:3px;"></i>' + Math.abs(_percent) + '%';
114 // return "" + Math.abs(_percent) + "%"
115 }
116 },
117 trendFormatter() {
118 const _trend = this.dataProvider.trend;
119 if (!_trend || _trend === 0) {
120 return '';
121 }
122 if (_trend > 0) {
123 return '<i class="el-icon-caret-top" style="margin-right:3px;"></i>' + Math.abs(_trend);
124 // return "" + Math.abs(_trend) + "%"
125 } else {
126 return '<i class="el-icon-caret-bottom" style="margin-right:3px;"></i>' + Math.abs(_trend);
127 // return "" + Math.abs(_trend) + "%"
128 }
129 }
130 },
131 methods: {
132 goTo() {
133 // if (this.dataProvider.href) {
134 // this.$refs.component.show(this.dataProvider);
135 // }
136 this.$emit('selected', this.dataProvider);
137 }
138 }
139 };
140 </script>
141
142 <style lang="scss" scoped>
143 $blue: #0058a5;
144 $grey : #585858;
145
146 .box {
147 display: flex;
148 .icon-wrapper {
149 flex: 0 0 20px;
150 display: flex;
151 align-items: center;
152 justify-content: center;
153 .icon {
154 width: 5px;
155 height: 50%;
156 background-color: $blue;
157 }
158 }
159 .box-wrapper {
160 flex: 1;
161 display: flex;
162 flex-direction: column;
163 }
164 .label {
165 color: $grey;
166 // font-size: 1rem;
167 }
168 .value {
169 color: $blue;
170 // font-size: 1.5rem;
171 font-weight: 550;
172 cursor: pointer;
173 }
174 .alias {
175 color: $blue;
176 // font-size: 1rem;
177 font-weight: 550;
178 cursor: pointer;
179 margin-left: 5px;
180 }
181 .percent {
182 color: $blue;
183 // font-size: 1rem;
184 font-weight: 550;
185 cursor: pointer;
186 }
187 .trend {
188 color: $blue;
189 // font-size: 1rem;
190 font-weight: 550;
191 cursor: pointer;
192 }
193 .unit {
194 color: $blue;
195 // font-size: 1rem;
196 }
197 }
198 </style>

调用案例:

<lilo-box-group :dataProvider="boxData" theme="blue" :col="6" lineGap="10px" @selected="boxGroupSelected"></lilo-box-group>
 1 boxData: [
2 {
3 label: '初等教育',
4 value: 12447,
5 unit: '人',
6 alias: '1.5%',
7 // percent: 0.3,
8 trend: -120,
9 valueSize: 0.9,
10 aliasSize: 0.7
11 },
12 {
13 label: '中等教育',
14 value: 579160,
15 unit: '人',
16 alias: '69.6%',
17 percent: 5.8,
18 valueSize: 0.9,
19 aliasSize: 0.7
20 },
21 {
22 label: '高等教育',
23 value: 66622,
24 unit: '人',
25 alias: '8%',
26 // percent: -8.6,
27 trend: -86,
28 valueSize: 0.9,
29 aliasSize: 0.7
30 },
31 {
32 label: '研究生教育',
33 value: 3734,
34 unit: '人',
35 alias: '0.4%',
36 // percent: -0.2,
37 trend: 189,
38 valueSize: 0.9,
39 aliasSize: 0.7
40 },
41 {
42 label: '有房',
43 value: 459386,
44 unit: '人',
45 alias: '55.2%',
46 percent: -5,
47 valueSize: 1.2,
48 aliasSize: 0.9
49 },
50 {
51 label: '有车',
52 value: 63210,
53 unit: '人',
54 alias: '7.6%',
55 percent: -6.3,
56 valueSize: 1.2,
57 aliasSize: 0.9
58 }
59 ]

1 boxGroupSelected(val) { 2 console.log(val); 3 }

附录:variables.scss

// base color

// $blue: #324157;
// $menuBg: #304156;
// $menuHover: #263445;
// $subMenuBg: #1f2d3d;
// $subMenuHover: #001528; $color-background-s: rgba(0, 0, 0, 0.9); $blue: #00417a;
$commonBlue: #0000ff;
// $menuBg: #00417a;
$menuBg: #ffffff;
// $menuHover: #00355e;
$menuHover: #f4f4f4;
// $subMenuBg: #003462;
$subMenuBg: #ffffff;
// $subMenuHover: #002546;
$subMenuHover: #e7e7e7;
$menuBorderRight: 1px solid #c5c1c1; $light-blue: #1890ff;
$red: #f56c6c;
$deep-red: #ff0000;
$blood-red: #d50000;
$pink: #dc67ce;
$green: #0bbd87;
$tiffany: #4ab7bd;
$yellow: #e6a23c;
$deep-yellow: #d69737;
$panGreen: #30b08f;
$purple: #6771dc;
$grey: #585858;
$light-grey: #f0f0f0;
$orange: #ff8037;
$deep-green: #07885f;
$light-green: #0bbd8722;
$light-purple: #6771dc22;
// sidebar
// $menuText: #bfcbd9;
$menuText: #002546;
// $menuActiveText: #409eff;
$menuActiveText: #08ac76;
// $subMenuActiveText: #f4f4f5;
$subMenuActiveText: #00417a; $sideBarWidth: 230px;
$headerHeight: 54px; // the :export directive is the magic sauce for webpack
// https://www.bluematador.com/blog/how-to-share-variables-between-js-and-sass
:export {
blue: $blue;
commonBlue: $commonBlue;
lightBlue: $light-blue;
red: $red;
deepRed: $deep-red;
bloodRed: $blood-red;
pink: $pink;
green: $green;
deepGreen: $deep-green;
lightGreen: $light-green;
lightPurple: $light-purple;
tiffany: $tiffany;
yellow: $yellow;
deepYellow: $deep-yellow;
orange: $orange;
panGreen: $panGreen;
purple: $purple;
grey: $grey;
lightGrey: $light-grey;
menuText: $menuText;
menuActiveText: $menuActiveText;
subMenuActiveText: $subMenuActiveText;
menuBg: $menuBg;
menuHover: $menuHover;
subMenuBg: $subMenuBg;
subMenuHover: $subMenuHover;
sideBarWidth: $sideBarWidth;
}

参数解释:

col:每行的列数

theme:主题,这个是我的整个插件有个variables.scss,里面有很多主题色

lineGap:行间距

dataProvider:

[{

label: '初等教育', //标题
value: 12447, //主数值
unit: '人', //单位
alias: '1.5%', //别名
percent: 0.3, //百分比
trend: -120, //趋势值
labelSize: 1, //标题字体大小 rem
valueSize: 0.9, //主数值字体大小 rem
aliasSize: 0.7, //别名字体大小 rem
unitSize: 1, //单位字体大小 rem
color: '#000000', //所有文字颜色,颜色优先级为 具体子项颜色(下面的颜色参数)> color > theme
iconColor: '#000000', //左边边框颜色
labelColor: '#000000', //标题文字颜色
valueColor: '#000000', //主数值文字颜色
aliasColor: '#000000', //别名文字颜色
unitColor: '#000000', //单位文字颜色

}]

数据可视化【原创】vue复合数字形式展示的相关教程结束。