• vue左侧漏斗切换 echart图表动态更新


    这个需求是根据点击左侧的箭头部分,右侧图表切换,左侧选中数据高亮(图片用的svg

    一、效果图

     

     二、vue组件

    1. <template>
    2. <div class="funnel_wrap">
    3. <div class="flex_between">
    4. <div class="sec_title">测试</div>
    5. <!-- <el-checkbox label="平均值" v-model="averageCheck" @change="changeAverage"></el-checkbox> -->
    6. </div>
    7. <div class="flex_center funnel_con" v-if="flowList.items&&flowList.items.length>0">
    8. <div class="flow_con">
    9. <div class="left_position pointer">
    10. <svg-icon icon-class="arrow" :style="`color:${chooseId==5||chooseId==6||chooseId==7?'green':'#DFE1EB'};position:absolute;right:0;bottom:-3px;width: 6px;height: 6px;`"></svg-icon>
    11. <div @click="changeFunnel(7)" :class="['left_top',chooseId==7?'leftactive':'']">
    12. <div class="left_title">{{radioList[6]}}%</div>
    13. </div>
    14. <div @click="changeFunnel(6)" :class="['left_cen',chooseId==6?'leftactive':'']" :style="`${chooseId==7?'border-bottom:0':''}`">
    15. <div class="left_title">{{radioList[5]}}%</div>
    16. </div>
    17. <div @click="changeFunnel(5)" :class="['left_bot',chooseId==5?'leftactive':'']" :style="`${chooseId==7||chooseId==6?'border-bottom:0':''}`">
    18. <div class="left_title">{{radioList[4]}}%</div>
    19. </div>
    20. </div>
    21. <div :class="['flow_item',chooseList.includes(item.id)?'active':''] " :style="'width:'+(255-(12*index))+'px'" v-for="(item,index) in flowList.items" :key="index">
    22. <div class="item_lef">{{item.title}}</div>
    23. <div class="item_rig" >{{item.newValue}}
    24. <div class="funnle"></div>
    25. </div>
    26. <div class="svg_box" v-if="index!==4" :style="`color:${chooseId==index?'green':'#DFE1EB'};right:-${(15+12*index)}px`" >
    27. <svg-icon @click="changeFunnel(index)" class="pointer" :style="'height:42px;width:'+(26+index*12)+'px'" :icon-class="'funnel'+index"></svg-icon>
    28. <div class="title" :style="`left:${(30+index*12)}px;color:${chooseId==index?'green':'#212848'}`">{{radioList[index]}}%</div>
    29. </div>
    30. </div>
    31. </div>
    32. <div class="flow_echart">
    33. <line-vue v-if="lineOpt.id" :opt="lineOpt" :heightNum="300"></line-vue>
    34. </div>
    35. </div>
    36. <div v-else class="none">暂无数据</div>
    37. </div>
    38. </template>
    39. <script>
    40. import { defineComponent, onMounted, computed,reactive,ref } from 'vue'
    41. import { useRouter, useRoute } from 'vue-router'
    42. import { roomflow } from "@/api/analyze/index.js";
    43. import lineVue from "@/components/echartsChange/lineVue.vue";
    44. import { thousandthis, valueTransfer,processingData,$formatTime,millionTransfer } from "@/utils/utils";
    45. export default defineComponent({
    46. components:{
    47. lineVue
    48. },
    49. props: {
    50. opt: {
    51. type: Object,
    52. default: () => {
    53. return {
    54. roomId:'',
    55. userId:''
    56. };
    57. },
    58. },
    59. optIds: {
    60. type: Object,
    61. default: () => {
    62. return {
    63. lineId: "funnel_data_id", // echarts图表默认id 同一个页面多次引用当前组件 id不能相同
    64. };
    65. },
    66. },
    67. },
    68. setup(props,context) {
    69. const router = useRouter(),
    70. route = useRoute()
    71. let averageCheck=ref(false)//平均值flag
    72. const industryAvg=ref('')//平均值
    73. const formatTime=$formatTime
    74. const lineOpt = ref({});
    75. const chooseList=ref([0,1])//选中id
    76. const chooseListArr=ref([])//选中趋势图
    77. const flowList=ref([])//漏斗列表
    78. const trendList=ref([])//曲线图列表
    79. const chooseId=ref('0')
    80. let radioList=ref([])//占比
    81. let colors = [
    82. "#4CAF50",
    83. "#556FFD",
    84. "#91CC75",
    85. "#EA8533",
    86. "#283E81",
    87. "#097C38",
    88. "#48D9D9",
    89. "#93BEFF",
    90. ];
    91. let renderColors = colors;
    92. const initData = (res,arrline) => {
    93. let data = res;
    94. if (data && data.length > 0) {
    95. let xList = [];
    96. let seriesList = [];
    97. let maxArr=[]
    98. data.forEach((element, index) => {
    99. element.points=element.points||[]
    100. maxArr.push(element.points.length)
    101. let arrnew=[]
    102. // if(element.points.length>0){
    103. arrnew = element.points
    104. .map((obj) => {
    105. return obj.value;
    106. })
    107. .join(",")
    108. .split(",");
    109. // }
    110. seriesList.push({
    111. name: element.title,
    112. type: "line",
    113. showSymbol: false,
    114. symbolSize: 6,
    115. seriesLayoutBy: "row",
    116. emphasis: { focus: "series" },
    117. data: [...arrnew],
    118. markLine : {
    119. symbol: ['none'],
    120. data : arrline?arrline:[],
    121. emphasis: {
    122. lineStyle: {
    123. width: 1, // hover时的折线宽度
    124. }
    125. },
    126. },
    127. lineStyle: {
    128. width: 1,
    129. },
    130. });
    131. });
    132. let max=Math.max(...maxArr)
    133. let maxIndex=maxArr.map(item => item).indexOf(max)
    134. xList = data[maxIndex].points.map((item) => {
    135. return formatTime(item.date,'HH:mm');
    136. });
    137. lineOpt.value = {
    138. id: props.optIds.lineId,
    139. resize:true,
    140. options: {
    141. color: renderColors,
    142. title: {
    143. text: "",
    144. },
    145. legend: {
    146. icon: "circle",
    147. // selectedMode:'single',
    148. itemHeight: 6,
    149. itemWidth: 6,
    150. left: "0px",
    151. itemGap: 24,
    152. // top:'bottom',
    153. textStyle: {
    154. //图例文字的样式
    155. color: "#596076",
    156. fontSize: 14,
    157. padding: [0, 0, 0, 0], //文字与图形之间的左右间距
    158. },
    159. // data: ["签约汇总", "计划招募", "计划孵化"],
    160. },
    161. tooltip: {
    162. // 鼠标移入的展示
    163. trigger: "axis",
    164. // axisPointer: {
    165. // type: "cross",
    166. // label: {
    167. // backgroundColor: "#6a7985",
    168. // },
    169. // },
    170. formatter: function (params) {
    171. let res = params[0].name+'分析数据\n';
    172. for (let i = 0; i < params.length; i++) {
    173. res += `<div style="margin-top: 4px;font-size: 14px;line-height: 22px;color: #596076;">${
    174. params[i].marker
    175. } ${params[i].seriesName}:${thousandthis(
    176. params[i].data
    177. )}</div>`;
    178. }
    179. return res;
    180. },
    181. backgroundColor: "rgba(255,255,255,.9)",
    182. borderColor: "#E2E6F5",
    183. borderWidth: 1,
    184. padding: [12, 16, 16, 16],
    185. },
    186. grid: {
    187. // 图表距离容器的距离
    188. left: "1%",
    189. right: '4%',
    190. bottom: "3%",
    191. top:'22%',
    192. containLabel: true, // 是否显示刻度,默认不显示
    193. },
    194. xAxis: [
    195. {
    196. type: "category",
    197. boundaryGap: false,
    198. axisLabel: {
    199. color: "#9095A7",
    200. fontSize: 12,
    201. margin: 13,
    202. },
    203. axisLine: {
    204. lineStyle: {
    205. color: "#DFE1EB",
    206. },
    207. },
    208. axisTick: {
    209. show: false,
    210. },
    211. data: xList,
    212. },
    213. ],
    214. yAxis: [
    215. {
    216. type: "value",
    217. // min: 0,
    218. // max: function (value) {
    219. // return value.max < 400 ? 400 : value.max;
    220. // },
    221. // interval: 1000,
    222. // splitNumber: 4,
    223. axisLabel: {
    224. color: "#9095A7",
    225. formatter(v) {
    226. return valueTransfer(Math.abs(v), 0, "w", true);
    227. },
    228. },
    229. splitLine: {
    230. lineStyle: {
    231. type: "dashed", //虚线
    232. },
    233. },
    234. },
    235. ],
    236. series: seriesList,
    237. },
    238. };
    239. }
    240. };
    241. //格式化选中线条
    242. const initChoosrArr=(arrList)=>{
    243. let arr=[]
    244. chooseList.value.forEach((ele)=>{
    245. arr.push(arrList[ele])
    246. })
    247. return arr
    248. }
    249. //获取漏斗列表
    250. const getList = () => {
    251. let param = {
    252. userId:props.opt.userId,
    253. roomId: props.opt.roomId,
    254. };
    255. // roomflow(param).then((res) => {
    256. let res= {
    257. "code": 200,
    258. "msg": "ok",
    259. "data": {
    260. "flowRank": {
    261. "name": "flowRank",
    262. "title": "测试数据",
    263. "items": [
    264. {
    265. "title": "数据1",
    266. "ratio": 1.0,
    267. "value": 2833543
    268. },
    269. {
    270. "title": "数据2",
    271. "ratio": 0.12883587790974055,
    272. "value": 365062
    273. },
    274. {
    275. "title": "数据3",
    276. "ratio": 0.85563822035709,
    277. "value": 312361
    278. },
    279. {
    280. "title": "数据4",
    281. "ratio": 0.09972755881816232,
    282. "value": 31151
    283. },
    284. {
    285. "title": "数据5",
    286. "ratio": 0.016532374562614364,
    287. "value": 515
    288. }
    289. ],
    290. },
    291. "trends": [
    292. {
    293. "title": "数据1",
    294. "points": [
    295. {
    296. "value": 30000,
    297. "date": "2023-07-02 09:25:00"
    298. },
    299. {
    300. "value": 35000,
    301. "date": "2023-07-02 09:30:00"
    302. },
    303. {
    304. "value": 50000,
    305. "date": "2023-07-02 09:35:00"
    306. },
    307. {
    308. "value": 100000,
    309. "date": "2023-07-02 09:40:00"
    310. },
    311. {
    312. "value": 130003,
    313. "date": "2023-07-02 09:45:00"
    314. },
    315. {
    316. "value": 190000,
    317. "date": "2023-07-02 09:50:00"
    318. },
    319. {
    320. "value": 230000,
    321. "date": "2023-07-02 09:55:00"
    322. },
    323. {
    324. "value": 250000,
    325. "date": "2023-07-02 10:00:00"
    326. },
    327. ]
    328. },
    329. {
    330. "title": "数据2",
    331. "points": [
    332. {
    333. "value": 6000,
    334. "date": "2023-07-02 09:25:00"
    335. },
    336. {
    337. "value": 7000,
    338. "date": "2023-07-02 09:30:00"
    339. },
    340. {
    341. "value": 8000,
    342. "date": "2023-07-02 09:35:00"
    343. },
    344. {
    345. "value": 9000,
    346. "date": "2023-07-02 09:40:00"
    347. },
    348. {
    349. "value": 10000,
    350. "date": "2023-07-02 09:45:00"
    351. },
    352. {
    353. "value": 11000,
    354. "date": "2023-07-02 09:50:00"
    355. },
    356. {
    357. "value": 12000,
    358. "date": "2023-07-02 09:55:00"
    359. },
    360. {
    361. "value": 21810,
    362. "date": "2023-07-02 10:00:00"
    363. },
    364. ]
    365. },
    366. {
    367. "title": "数据3",
    368. "points": [
    369. {
    370. "value": 4500,
    371. "date": "2023-07-02 09:25:00"
    372. },
    373. {
    374. "value": 4700,
    375. "date": "2023-07-02 09:30:00"
    376. },
    377. {
    378. "value": 10000,
    379. "date": "2023-07-02 09:35:00"
    380. },
    381. {
    382. "value": 10214,
    383. "date": "2023-07-02 09:40:00"
    384. },
    385. {
    386. "value": 12000,
    387. "date": "2023-07-02 09:45:00"
    388. },
    389. {
    390. "value": 13000,
    391. "date": "2023-07-02 09:50:00"
    392. },
    393. {
    394. "value": 14000,
    395. "date": "2023-07-02 09:55:00"
    396. },
    397. {
    398. "value": 15000,
    399. "date": "2023-07-02 10:00:00"
    400. },
    401. ]
    402. },
    403. {
    404. "title": "数据4",
    405. "points": [
    406. {
    407. "value": 400,
    408. "date": "2023-07-02 09:25:00"
    409. },
    410. {
    411. "value": 800,
    412. "date": "2023-07-02 09:30:00"
    413. },
    414. {
    415. "value": 1100,
    416. "date": "2023-07-02 09:35:00"
    417. },
    418. {
    419. "value": 1200,
    420. "date": "2023-07-02 09:40:00"
    421. },
    422. {
    423. "value": 1400,
    424. "date": "2023-07-02 09:45:00"
    425. },
    426. {
    427. "value": 1600,
    428. "date": "2023-07-02 09:50:00"
    429. },
    430. {
    431. "value": 1800,
    432. "date": "2023-07-02 09:55:00"
    433. },
    434. {
    435. "value": 2000,
    436. "date": "2023-07-02 10:00:00"
    437. },
    438. ]
    439. },
    440. {
    441. "title": "数据5",
    442. "points": [
    443. {
    444. "value": 0,
    445. "date": "2023-07-02 09:25:00"
    446. },
    447. {
    448. "value": 2,
    449. "date": "2023-07-02 09:30:00"
    450. },
    451. {
    452. "value": 13,
    453. "date": "2023-07-02 09:35:00"
    454. },
    455. {
    456. "value": 14,
    457. "date": "2023-07-02 09:40:00"
    458. },
    459. {
    460. "value": 34,
    461. "date": "2023-07-02 09:45:00"
    462. },
    463. {
    464. "value": 40,
    465. "date": "2023-07-02 09:50:00"
    466. },
    467. {
    468. "value": 53,
    469. "date": "2023-07-02 09:55:00"
    470. },
    471. {
    472. "value": 63,
    473. "date": "2023-07-02 10:00:00"
    474. },
    475. ]
    476. }
    477. ],
    478. "industryAvg": 100
    479. }
    480. }
    481. if (res.data) {
    482. if(res.data.flowRank.items){
    483. radioList.value=[]
    484. //格式漏斗右侧返回占比
    485. res.data.flowRank.items.forEach((ele,index) => {
    486. ele.id=index
    487. ele.newValue=millionTransfer(ele.value)
    488. if(index!==0){
    489. radioList.value.push(processingData(ele.ratio*100,2))
    490. }
    491. });
    492. // 漏斗左侧百分比计算
    493. radioList.value.push(processingData((res.data.flowRank.items[4].value/res.data.flowRank.items[2].value)*100,2))
    494. radioList.value.push(processingData((res.data.flowRank.items[4].value/res.data.flowRank.items[1].value)*100,2))
    495. radioList.value.push(processingData((res.data.flowRank.items[4].value/res.data.flowRank.items[0].value)*100,2))
    496. }
    497. //绘制图表
    498. if(res.data.trends){
    499. chooseListArr.value=initChoosrArr(res.data.trends)
    500. initData(chooseListArr.value)
    501. }
    502. flowList.value=res.data.flowRank
    503. trendList.value=res.data.trends
    504. industryAvg.value=res.data.industryAvg
    505. }
    506. // });
    507. };
    508. //点击漏斗
    509. const changeFunnel=(val)=>{
    510. chooseId.value=val;
    511. if(val<5){
    512. chooseList.value=[val,val+1]
    513. }else if(val==5){
    514. chooseList.value=[2,4]
    515. }if(val==6){
    516. chooseList.value=[1,4]
    517. }if(val==7){
    518. chooseList.value=[0,4]
    519. }
    520. chooseListArr.value=initChoosrArr(trendList.value)
    521. // 先判断是否有平均线再重绘图表
    522. changeAverage(averageCheck.value)
    523. }
    524. //点击平均值 val=true有平均线
    525. const changeAverage=(val)=>{
    526. if(val){
    527. let arrline=[{
    528. symbol: "none",
    529. silent:false, //鼠标悬停事件 true没有,false
    530. lineStyle:{ //警戒线的样式 ,虚实 颜色
    531. type:"dashed", //样式 ‘solid’和'dotted'
    532. color:"#E98433",
    533. width: 1 //宽度
    534. },
    535. label:{
    536. show:false,
    537. color:"#E98433",
    538. position:'middle',
    539. // padding: ['0', '0', '0',tableWidth.value],
    540. formatter: function (params) {
    541. let res = "";
    542. res += `${params.name}:${params.value}`;
    543. return res;
    544. },
    545. },
    546. name:'平均值',
    547. yAxis:industryAvg.value
    548. }]
    549. initData(chooseListArr.value,arrline)
    550. }else{
    551. initData(chooseListArr.value)
    552. }
    553. }
    554. watch(
    555. props,
    556. (newValue) => {
    557. console.log(newValue);
    558. if (newValue && newValue.opt && newValue.opt.roomId) {
    559. getList()
    560. }
    561. },
    562. { deep: true }
    563. );
    564. onMounted(()=>{
    565. // getList()
    566. })
    567. return {
    568. flowList,
    569. chooseList,
    570. changeFunnel,
    571. chooseId,
    572. radioList,
    573. trendList,
    574. lineOpt,
    575. averageCheck,
    576. changeAverage
    577. }
    578. }
    579. })
    580. </script>
    581. <style scoped lang="scss">
    582. .funnel_wrap{
    583. margin-top: 24px;
    584. padding: 24px;
    585. color: #212848;
    586. font-size: 14px;
    587. background-color: #fff;
    588. .sec_title{
    589. font-size: 18px;
    590. font-weight: 500;
    591. }
    592. .funnel_con{
    593. padding: 24px;
    594. }
    595. .flow_con{
    596. position: relative;
    597. padding-left: 100px;
    598. padding-right: 112px;
    599. .left_position{
    600. position: absolute;
    601. top: 30px;
    602. left:0;
    603. .left_top{
    604. width: 100px;height: 198px;color:#DFE1EB;border:1px solid #DFE1EB;border-right:0;
    605. }
    606. .left_cen{
    607. width: 72px;height: 146px;position:absolute;left:24px;bottom:-0.5px;color:#DFE1EB;border:1px solid #DFE1EB;border-right:0;
    608. }
    609. .left_bot{
    610. width: 25px;height: 92px;position:absolute;left:72px;color:#DFE1EB;bottom:0px;border:1px solid #DFE1EB;border-right:0;
    611. }
    612. .left_title{
    613. position: absolute;
    614. line-height: 20px;
    615. top: -24px;
    616. right: 0;
    617. color: #212848;
    618. }
    619. .leftactive{
    620. border:1px solid green;
    621. border-right:0;
    622. color: green;
    623. .left_title{
    624. color: green;
    625. }
    626. }
    627. }
    628. .flow_item{
    629. background-color:#F8F9FB ;
    630. margin-bottom: 12px;
    631. height: 40px;
    632. line-height: 40px;
    633. display: flex;
    634. align-items: center;
    635. position: relative;
    636. .item_lef{
    637. width: 116px;
    638. text-align: center;
    639. box-sizing: border-box;
    640. }
    641. .item_rig{padding-left: 16px;
    642. position: relative;
    643. flex: 1;
    644. .funnle{
    645. position: absolute;
    646. border-bottom:40px solid #fff;
    647. border-left: 12px solid transparent;
    648. right: 0;
    649. top: 0;
    650. }
    651. }
    652. }
    653. .active{
    654. .item_lef{background-color: green;color: #fff;}
    655. .item_rig{background-color: #EEF1FF;}
    656. }
    657. .svg_box{
    658. position: absolute;
    659. top: 25px;
    660. right: -10px;
    661. .title{
    662. position: absolute;
    663. left:0;
    664. top: 0;
    665. }
    666. }
    667. }
    668. .flow_echart{
    669. flex: 1;
    670. }
    671. .none{
    672. margin-top: 12px;
    673. color: #9095A7;
    674. text-align: center;
    675. }
    676. }
    677. // .svg-icon {
    678. // height: 3em;
    679. // }
    680. </style>

    三、utils.js方法

    1. export function millionTransfer(
    2. value,
    3. digits = 4,
    4. unit = "w",
    5. decimal = 2,
    6. removeZero = false
    7. ) {
    8. // unit = unit || "w"
    9. const valueNum = Number(value)
    10. const transferNum = Math.pow(10, digits)
    11. if (!isNaN(valueNum)) {
    12. if (valueNum < transferNum && valueNum >= 0) {
    13. return value
    14. }
    15. const num = floatDivideMethod(valueNum, transferNum)
    16. if (removeZero) {
    17. return `${parseFloat(num.toFixed(decimal))}${unit}`
    18. }
    19. return `${num.toFixed(decimal)}${unit}`
    20. }
    21. return value
    22. }
    23. export function thousandthis(num) {
    24. if (!num && num !== 0) return null
    25. if (num === '--') return '--'
    26. if (!(!isNaN(Number(num)) && typeof Number(num) === 'number')) {
    27. return '0'
    28. }
    29. return (num || 0).toString().replace(/\d+/, function(n) {
    30. const len = n.length
    31. if (len % 3 === 0) {
    32. return n.replace(/(\d{3})/g, ',$1').slice(1)
    33. }
    34. return n.slice(0, len % 3) + n.slice(len % 3).replace(/(\d{3})/g, ',$1')
    35. })
    36. }
    37. /* 最早的数据没有亿,只有万,兼容之前数据,后面转换万和亿的数据用这个方法 */
    38. export function valueTransfer(value, decimal = 2, unit = "万", removeZero = false) {
    39. let outputVal = value
    40. const valueNum = Number(value)
    41. const transferNum1 = Math.pow(10, 4)
    42. const transferNum2 = Math.pow(10, 8)
    43. if (!isNaN(valueNum)) {
    44. if (valueNum < transferNum1) {
    45. outputVal = value
    46. } else if (valueNum >= transferNum1 && valueNum < transferNum2) {
    47. outputVal = millionTransfer(value, 4, unit, decimal, removeZero)
    48. } else {
    49. outputVal = millionTransfer(value, 8, "亿", decimal, removeZero)
    50. }
    51. }
    52. return outputVal
    53. }
    54. //保留两位小数
    55. export function processingData(data,length){
    56. data=Number(data);
    57. data=Number((parseInt(data * 100) / 100).toFixed((length!=undefined?length:2)))
    58. data=data+''
    59. return data
    60. }
    61. import moment from "moment"
    62. export function $formatTime (time, format = "YYYY-MM-DD HH:mm:ss") {
    63. if (time && time !== "--") {
    64. if (format === "timestamp") {
    65. return Number(moment(time).utcOffset(8).format("x"))
    66. }
    67. return moment(time).format(format)
    68. }
    69. return time
    70. }

    四、父组件调用

    1. import flowFunnel from "./components/flowFunnel.vue";
    2. components:{
    3. flowFunnel,
    4. },

     

  • 相关阅读:
    无涯教程-JavaScript - MATCH函数
    量子随机预言机(QROM)是什么?
    ssm+vue+java微信小程序的英语学习激励系统#毕业设计
    一、基础算法精讲:双指针
    如何使用fiddler实现手机抓包,Filters过滤器!
    什么是找出芯片bug的最好办法
    SystemC 等待异步事件解决方案
    乐优商城_第3章_-认识微服务(Feign+Zuul)
    强制用户使用定向推送功能是什么意思?整改措施请收好
    UPS负载箱的工作原理是什么?
  • 原文地址:https://blog.csdn.net/m0_55969466/article/details/132538061