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


- <template>
- <div class="funnel_wrap">
- <div class="flex_between">
- <div class="sec_title">测试</div>
- <!-- <el-checkbox label="平均值" v-model="averageCheck" @change="changeAverage"></el-checkbox> -->
- </div>
- <div class="flex_center funnel_con" v-if="flowList.items&&flowList.items.length>0">
- <div class="flow_con">
- <div class="left_position pointer">
- <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>
- <div @click="changeFunnel(7)" :class="['left_top',chooseId==7?'leftactive':'']">
- <div class="left_title">{{radioList[6]}}%</div>
- </div>
- <div @click="changeFunnel(6)" :class="['left_cen',chooseId==6?'leftactive':'']" :style="`${chooseId==7?'border-bottom:0':''}`">
- <div class="left_title">{{radioList[5]}}%</div>
- </div>
- <div @click="changeFunnel(5)" :class="['left_bot',chooseId==5?'leftactive':'']" :style="`${chooseId==7||chooseId==6?'border-bottom:0':''}`">
- <div class="left_title">{{radioList[4]}}%</div>
- </div>
- </div>
- <div :class="['flow_item',chooseList.includes(item.id)?'active':''] " :style="'width:'+(255-(12*index))+'px'" v-for="(item,index) in flowList.items" :key="index">
- <div class="item_lef">{{item.title}}</div>
- <div class="item_rig" >{{item.newValue}}
- <div class="funnle"></div>
- </div>
- <div class="svg_box" v-if="index!==4" :style="`color:${chooseId==index?'green':'#DFE1EB'};right:-${(15+12*index)}px`" >
- <svg-icon @click="changeFunnel(index)" class="pointer" :style="'height:42px;width:'+(26+index*12)+'px'" :icon-class="'funnel'+index"></svg-icon>
- <div class="title" :style="`left:${(30+index*12)}px;color:${chooseId==index?'green':'#212848'}`">{{radioList[index]}}%</div>
- </div>
- </div>
- </div>
- <div class="flow_echart">
- <line-vue v-if="lineOpt.id" :opt="lineOpt" :heightNum="300"></line-vue>
- </div>
- </div>
- <div v-else class="none">暂无数据</div>
- </div>
- </template>
-
- <script>
- import { defineComponent, onMounted, computed,reactive,ref } from 'vue'
- import { useRouter, useRoute } from 'vue-router'
- import { roomflow } from "@/api/analyze/index.js";
- import lineVue from "@/components/echartsChange/lineVue.vue";
- import { thousandthis, valueTransfer,processingData,$formatTime,millionTransfer } from "@/utils/utils";
- export default defineComponent({
- components:{
- lineVue
- },
- props: {
- opt: {
- type: Object,
- default: () => {
- return {
- roomId:'',
- userId:''
- };
- },
- },
- optIds: {
- type: Object,
- default: () => {
- return {
- lineId: "funnel_data_id", // echarts图表默认id 同一个页面多次引用当前组件 id不能相同
- };
- },
- },
- },
- setup(props,context) {
- const router = useRouter(),
- route = useRoute()
- let averageCheck=ref(false)//平均值flag
- const industryAvg=ref('')//平均值
- const formatTime=$formatTime
- const lineOpt = ref({});
- const chooseList=ref([0,1])//选中id
- const chooseListArr=ref([])//选中趋势图
- const flowList=ref([])//漏斗列表
- const trendList=ref([])//曲线图列表
- const chooseId=ref('0')
- let radioList=ref([])//占比
- let colors = [
- "#4CAF50",
- "#556FFD",
- "#91CC75",
- "#EA8533",
- "#283E81",
- "#097C38",
- "#48D9D9",
- "#93BEFF",
- ];
- let renderColors = colors;
- const initData = (res,arrline) => {
- let data = res;
- if (data && data.length > 0) {
- let xList = [];
- let seriesList = [];
- let maxArr=[]
- data.forEach((element, index) => {
- element.points=element.points||[]
- maxArr.push(element.points.length)
- let arrnew=[]
- // if(element.points.length>0){
- arrnew = element.points
- .map((obj) => {
- return obj.value;
- })
- .join(",")
- .split(",");
- // }
- seriesList.push({
- name: element.title,
- type: "line",
- showSymbol: false,
- symbolSize: 6,
- seriesLayoutBy: "row",
- emphasis: { focus: "series" },
- data: [...arrnew],
- markLine : {
- symbol: ['none'],
- data : arrline?arrline:[],
- emphasis: {
- lineStyle: {
- width: 1, // hover时的折线宽度
- }
- },
- },
- lineStyle: {
- width: 1,
- },
- });
- });
- let max=Math.max(...maxArr)
- let maxIndex=maxArr.map(item => item).indexOf(max)
- xList = data[maxIndex].points.map((item) => {
- return formatTime(item.date,'HH:mm');
- });
- lineOpt.value = {
- id: props.optIds.lineId,
- resize:true,
- options: {
- color: renderColors,
- title: {
- text: "",
- },
- legend: {
- icon: "circle",
- // selectedMode:'single',
- itemHeight: 6,
- itemWidth: 6,
- left: "0px",
- itemGap: 24,
- // top:'bottom',
- textStyle: {
- //图例文字的样式
- color: "#596076",
- fontSize: 14,
- padding: [0, 0, 0, 0], //文字与图形之间的左右间距
- },
- // data: ["签约汇总", "计划招募", "计划孵化"],
- },
- tooltip: {
- // 鼠标移入的展示
- trigger: "axis",
- // axisPointer: {
- // type: "cross",
- // label: {
- // backgroundColor: "#6a7985",
- // },
- // },
- formatter: function (params) {
- let res = params[0].name+'分析数据\n';
- for (let i = 0; i < params.length; i++) {
- res += `<div style="margin-top: 4px;font-size: 14px;line-height: 22px;color: #596076;">${
- params[i].marker
- } ${params[i].seriesName}:${thousandthis(
- params[i].data
- )}</div>`;
- }
- return res;
- },
- backgroundColor: "rgba(255,255,255,.9)",
- borderColor: "#E2E6F5",
- borderWidth: 1,
- padding: [12, 16, 16, 16],
- },
- grid: {
- // 图表距离容器的距离
- left: "1%",
- right: '4%',
- bottom: "3%",
- top:'22%',
- containLabel: true, // 是否显示刻度,默认不显示
- },
- xAxis: [
- {
- type: "category",
- boundaryGap: false,
- axisLabel: {
- color: "#9095A7",
- fontSize: 12,
- margin: 13,
- },
- axisLine: {
- lineStyle: {
- color: "#DFE1EB",
- },
- },
- axisTick: {
- show: false,
- },
- data: xList,
- },
- ],
- yAxis: [
- {
- type: "value",
- // min: 0,
- // max: function (value) {
- // return value.max < 400 ? 400 : value.max;
- // },
- // interval: 1000,
- // splitNumber: 4,
- axisLabel: {
- color: "#9095A7",
- formatter(v) {
- return valueTransfer(Math.abs(v), 0, "w", true);
- },
- },
- splitLine: {
- lineStyle: {
- type: "dashed", //虚线
- },
- },
- },
- ],
- series: seriesList,
- },
- };
- }
- };
- //格式化选中线条
- const initChoosrArr=(arrList)=>{
- let arr=[]
- chooseList.value.forEach((ele)=>{
- arr.push(arrList[ele])
- })
- return arr
- }
- //获取漏斗列表
- const getList = () => {
- let param = {
- userId:props.opt.userId,
- roomId: props.opt.roomId,
- };
- // roomflow(param).then((res) => {
- let res= {
- "code": 200,
- "msg": "ok",
- "data": {
- "flowRank": {
- "name": "flowRank",
- "title": "测试数据",
- "items": [
- {
- "title": "数据1",
- "ratio": 1.0,
- "value": 2833543
- },
- {
- "title": "数据2",
- "ratio": 0.12883587790974055,
- "value": 365062
- },
- {
- "title": "数据3",
- "ratio": 0.85563822035709,
- "value": 312361
- },
- {
- "title": "数据4",
- "ratio": 0.09972755881816232,
- "value": 31151
- },
- {
- "title": "数据5",
- "ratio": 0.016532374562614364,
- "value": 515
- }
- ],
- },
- "trends": [
- {
- "title": "数据1",
- "points": [
- {
- "value": 30000,
- "date": "2023-07-02 09:25:00"
- },
- {
- "value": 35000,
- "date": "2023-07-02 09:30:00"
- },
- {
- "value": 50000,
- "date": "2023-07-02 09:35:00"
- },
- {
- "value": 100000,
- "date": "2023-07-02 09:40:00"
- },
- {
- "value": 130003,
- "date": "2023-07-02 09:45:00"
- },
- {
- "value": 190000,
- "date": "2023-07-02 09:50:00"
- },
- {
- "value": 230000,
- "date": "2023-07-02 09:55:00"
- },
- {
- "value": 250000,
- "date": "2023-07-02 10:00:00"
- },
- ]
- },
- {
- "title": "数据2",
- "points": [
- {
- "value": 6000,
- "date": "2023-07-02 09:25:00"
- },
- {
- "value": 7000,
- "date": "2023-07-02 09:30:00"
- },
- {
- "value": 8000,
- "date": "2023-07-02 09:35:00"
- },
- {
- "value": 9000,
- "date": "2023-07-02 09:40:00"
- },
- {
- "value": 10000,
- "date": "2023-07-02 09:45:00"
- },
- {
- "value": 11000,
- "date": "2023-07-02 09:50:00"
- },
- {
- "value": 12000,
- "date": "2023-07-02 09:55:00"
- },
- {
- "value": 21810,
- "date": "2023-07-02 10:00:00"
- },
- ]
- },
- {
- "title": "数据3",
- "points": [
- {
- "value": 4500,
- "date": "2023-07-02 09:25:00"
- },
- {
- "value": 4700,
- "date": "2023-07-02 09:30:00"
- },
- {
- "value": 10000,
- "date": "2023-07-02 09:35:00"
- },
- {
- "value": 10214,
- "date": "2023-07-02 09:40:00"
- },
- {
- "value": 12000,
- "date": "2023-07-02 09:45:00"
- },
- {
- "value": 13000,
- "date": "2023-07-02 09:50:00"
- },
- {
- "value": 14000,
- "date": "2023-07-02 09:55:00"
- },
- {
- "value": 15000,
- "date": "2023-07-02 10:00:00"
- },
- ]
- },
- {
- "title": "数据4",
- "points": [
- {
- "value": 400,
- "date": "2023-07-02 09:25:00"
- },
- {
- "value": 800,
- "date": "2023-07-02 09:30:00"
- },
- {
- "value": 1100,
- "date": "2023-07-02 09:35:00"
- },
- {
- "value": 1200,
- "date": "2023-07-02 09:40:00"
- },
- {
- "value": 1400,
- "date": "2023-07-02 09:45:00"
- },
- {
- "value": 1600,
- "date": "2023-07-02 09:50:00"
- },
- {
- "value": 1800,
- "date": "2023-07-02 09:55:00"
- },
- {
- "value": 2000,
- "date": "2023-07-02 10:00:00"
- },
- ]
- },
- {
- "title": "数据5",
- "points": [
- {
- "value": 0,
- "date": "2023-07-02 09:25:00"
- },
- {
- "value": 2,
- "date": "2023-07-02 09:30:00"
- },
- {
- "value": 13,
- "date": "2023-07-02 09:35:00"
- },
- {
- "value": 14,
- "date": "2023-07-02 09:40:00"
- },
- {
- "value": 34,
- "date": "2023-07-02 09:45:00"
- },
- {
- "value": 40,
- "date": "2023-07-02 09:50:00"
- },
- {
- "value": 53,
- "date": "2023-07-02 09:55:00"
- },
- {
- "value": 63,
- "date": "2023-07-02 10:00:00"
- },
- ]
- }
- ],
- "industryAvg": 100
- }
- }
- if (res.data) {
- if(res.data.flowRank.items){
- radioList.value=[]
- //格式漏斗右侧返回占比
- res.data.flowRank.items.forEach((ele,index) => {
- ele.id=index
- ele.newValue=millionTransfer(ele.value)
- if(index!==0){
- radioList.value.push(processingData(ele.ratio*100,2))
- }
- });
- // 漏斗左侧百分比计算
- radioList.value.push(processingData((res.data.flowRank.items[4].value/res.data.flowRank.items[2].value)*100,2))
- radioList.value.push(processingData((res.data.flowRank.items[4].value/res.data.flowRank.items[1].value)*100,2))
- radioList.value.push(processingData((res.data.flowRank.items[4].value/res.data.flowRank.items[0].value)*100,2))
- }
- //绘制图表
- if(res.data.trends){
- chooseListArr.value=initChoosrArr(res.data.trends)
- initData(chooseListArr.value)
- }
- flowList.value=res.data.flowRank
- trendList.value=res.data.trends
- industryAvg.value=res.data.industryAvg
- }
- // });
- };
- //点击漏斗
- const changeFunnel=(val)=>{
- chooseId.value=val;
- if(val<5){
- chooseList.value=[val,val+1]
- }else if(val==5){
- chooseList.value=[2,4]
- }if(val==6){
- chooseList.value=[1,4]
- }if(val==7){
- chooseList.value=[0,4]
- }
- chooseListArr.value=initChoosrArr(trendList.value)
- // 先判断是否有平均线再重绘图表
- changeAverage(averageCheck.value)
- }
- //点击平均值 val=true有平均线
- const changeAverage=(val)=>{
- if(val){
- let arrline=[{
- symbol: "none",
- silent:false, //鼠标悬停事件 true没有,false有
- lineStyle:{ //警戒线的样式 ,虚实 颜色
- type:"dashed", //样式 ‘solid’和'dotted'
- color:"#E98433",
- width: 1 //宽度
- },
- label:{
- show:false,
- color:"#E98433",
- position:'middle',
- // padding: ['0', '0', '0',tableWidth.value],
- formatter: function (params) {
- let res = "";
- res += `${params.name}:${params.value}`;
- return res;
- },
- },
- name:'平均值',
- yAxis:industryAvg.value
- }]
- initData(chooseListArr.value,arrline)
- }else{
- initData(chooseListArr.value)
- }
- }
- watch(
- props,
- (newValue) => {
- console.log(newValue);
- if (newValue && newValue.opt && newValue.opt.roomId) {
- getList()
- }
- },
- { deep: true }
- );
- onMounted(()=>{
- // getList()
- })
- return {
- flowList,
- chooseList,
- changeFunnel,
- chooseId,
- radioList,
- trendList,
- lineOpt,
- averageCheck,
- changeAverage
- }
- }
- })
- </script>
-
- <style scoped lang="scss">
- .funnel_wrap{
- margin-top: 24px;
- padding: 24px;
- color: #212848;
- font-size: 14px;
- background-color: #fff;
- .sec_title{
- font-size: 18px;
- font-weight: 500;
- }
- .funnel_con{
- padding: 24px;
-
- }
- .flow_con{
- position: relative;
- padding-left: 100px;
- padding-right: 112px;
- .left_position{
- position: absolute;
- top: 30px;
- left:0;
- .left_top{
- width: 100px;height: 198px;color:#DFE1EB;border:1px solid #DFE1EB;border-right:0;
- }
- .left_cen{
- width: 72px;height: 146px;position:absolute;left:24px;bottom:-0.5px;color:#DFE1EB;border:1px solid #DFE1EB;border-right:0;
- }
- .left_bot{
- width: 25px;height: 92px;position:absolute;left:72px;color:#DFE1EB;bottom:0px;border:1px solid #DFE1EB;border-right:0;
- }
- .left_title{
- position: absolute;
- line-height: 20px;
- top: -24px;
- right: 0;
- color: #212848;
- }
- .leftactive{
- border:1px solid green;
- border-right:0;
- color: green;
- .left_title{
- color: green;
- }
- }
-
- }
- .flow_item{
- background-color:#F8F9FB ;
- margin-bottom: 12px;
- height: 40px;
- line-height: 40px;
- display: flex;
- align-items: center;
- position: relative;
- .item_lef{
- width: 116px;
- text-align: center;
- box-sizing: border-box;
- }
- .item_rig{padding-left: 16px;
- position: relative;
- flex: 1;
- .funnle{
- position: absolute;
- border-bottom:40px solid #fff;
- border-left: 12px solid transparent;
- right: 0;
- top: 0;
- }
- }
- }
- .active{
- .item_lef{background-color: green;color: #fff;}
- .item_rig{background-color: #EEF1FF;}
- }
- .svg_box{
- position: absolute;
- top: 25px;
- right: -10px;
- .title{
- position: absolute;
- left:0;
- top: 0;
- }
- }
- }
- .flow_echart{
- flex: 1;
- }
- .none{
- margin-top: 12px;
- color: #9095A7;
- text-align: center;
- }
- }
- // .svg-icon {
- // height: 3em;
- // }
- </style>
- export function millionTransfer(
- value,
- digits = 4,
- unit = "w",
- decimal = 2,
- removeZero = false
- ) {
- // unit = unit || "w"
- const valueNum = Number(value)
- const transferNum = Math.pow(10, digits)
- if (!isNaN(valueNum)) {
- if (valueNum < transferNum && valueNum >= 0) {
- return value
- }
- const num = floatDivideMethod(valueNum, transferNum)
- if (removeZero) {
- return `${parseFloat(num.toFixed(decimal))}${unit}`
- }
- return `${num.toFixed(decimal)}${unit}`
- }
- return value
- }
-
- export function thousandthis(num) {
- if (!num && num !== 0) return null
- if (num === '--') return '--'
- if (!(!isNaN(Number(num)) && typeof Number(num) === 'number')) {
- return '0'
- }
- return (num || 0).toString().replace(/\d+/, function(n) {
- const len = n.length
- if (len % 3 === 0) {
- return n.replace(/(\d{3})/g, ',$1').slice(1)
- }
- return n.slice(0, len % 3) + n.slice(len % 3).replace(/(\d{3})/g, ',$1')
- })
- }
-
- /* 最早的数据没有亿,只有万,兼容之前数据,后面转换万和亿的数据用这个方法 */
- export function valueTransfer(value, decimal = 2, unit = "万", removeZero = false) {
- let outputVal = value
- const valueNum = Number(value)
- const transferNum1 = Math.pow(10, 4)
- const transferNum2 = Math.pow(10, 8)
- if (!isNaN(valueNum)) {
- if (valueNum < transferNum1) {
- outputVal = value
- } else if (valueNum >= transferNum1 && valueNum < transferNum2) {
- outputVal = millionTransfer(value, 4, unit, decimal, removeZero)
- } else {
- outputVal = millionTransfer(value, 8, "亿", decimal, removeZero)
- }
- }
- return outputVal
- }
-
- //保留两位小数
- export function processingData(data,length){
- data=Number(data);
- data=Number((parseInt(data * 100) / 100).toFixed((length!=undefined?length:2)))
- data=data+''
- return data
- }
-
- import moment from "moment"
- export function $formatTime (time, format = "YYYY-MM-DD HH:mm:ss") {
- if (time && time !== "--") {
- if (format === "timestamp") {
- return Number(moment(time).utcOffset(8).format("x"))
- }
- return moment(time).format(format)
- }
- return time
- }
- import flowFunnel from "./components/flowFunnel.vue";
-
- components:{
- flowFunnel,
- },
![]()