| | |
| | | <affixTime @selected="clickBreadcrumb" /> |
| | | </Card> |
| | | </Affix> |
| | | |
| | | <Card class="card"> |
| | | <div class="chart-wrapper"> |
| | | <div class="chart-header"> |
| | |
| | | <div class="my-chart-container"> |
| | | <div class="chart-wrapper" style="height: 800px"> |
| | | <div class="chart-header"> |
| | | <h2>商品/视频浏览量完播率 趋势图</h2> |
| | | <h2>商品/视频浏览量完播率 排名图</h2> |
| | | </div> |
| | | <div class="button-group-wrapper"> |
| | | <!-- 第一组:商品/视频(靠左) --> |
| | |
| | | </div> |
| | | </div> |
| | | </Card> |
| | | <Card class="card"> |
| | | <div class="my-chart-container"> |
| | | <div class="chart-wrapper"> |
| | | <div class="chart-header"> |
| | | <h2>浏览数据/订单时间段 趋势图</h2> |
| | | </div> |
| | | <div ref="orderTimePeriodChartDom" class="chart-container"></div> |
| | | </div> |
| | | </div> |
| | | </Card> |
| | | <Card class="card"> |
| | | <div class="my-chart-container"> |
| | | <div class="chart-wrapper" style="height: 800px"> |
| | | <div class="chart-header"> |
| | | <h2>商品复购率 排名图</h2> |
| | | </div> |
| | | <div class="button-group-wrapper"> |
| | | <!-- 页面样式需要 --> |
| | | <div></div> |
| | | <ButtonGroup> |
| | | <Button |
| | | :type="repurchaseCurrentLimit === 10 ? 'success' : 'default'" |
| | | @click="handleRepurchaseLimitChange(10)" |
| | | > |
| | | 前10 |
| | | </Button> |
| | | <Button |
| | | :type="repurchaseCurrentLimit === 20 ? 'success' : 'default'" |
| | | @click="handleRepurchaseLimitChange(20)" |
| | | > |
| | | 前20 |
| | | </Button> |
| | | <Button |
| | | :type="repurchaseCurrentLimit === 30 ? 'success' : 'default'" |
| | | @click="handleRepurchaseLimitChange(30)" |
| | | > |
| | | 前30 |
| | | </Button> |
| | | </ButtonGroup> |
| | | </div> |
| | | <div ref="productRepurchaseRateChartDom" class="repurchase-chart-container"></div> |
| | | </div> |
| | | </div> |
| | | </Card> |
| | | |
| | | |
| | | <Card class="card"> |
| | | <div> |
| | |
| | | import refundRow from "./order/refundOrder"; |
| | | import affixTime from "@/components/affix-time"; |
| | | import * as echarts from 'echarts'; |
| | | import {pvUvData,getOrderCount,getViewAndCompletionRateCount} from "../../api/orderStatistics"; |
| | | import {pvUvData,getOrderCount,getViewAndCompletionRateCount,getOrderTimePeriod,gerProductRepurchase} from "../../api/orderStatistics"; |
| | | |
| | | export default { |
| | | components: { orderRow, refundRow, affixTime }, |
| | |
| | | // 图表实例 |
| | | chart: null, //pvuv |
| | | orderCountChart:null, //订单日趋势 |
| | | viewPrintChart:null, |
| | | viewPrintChart:null, //完播率 |
| | | orderTimePeriodChart:null, //订单/浏览时间 |
| | | productRepurchaseRateChart:null,//商品复购率 |
| | | |
| | | currentType:"goods", |
| | | currentLimit:10, |
| | | |
| | | repurchaseCurrentLimit:10, |
| | | |
| | | |
| | | // 统计数据 |
| | | |
| | | orderOrRefund: 1, // 订单还是退单 |
| | |
| | | const dates = []; |
| | | const today = new Date(); |
| | | let days = 1; |
| | | if(param.year !== null && param.year !== undefined && param.month !== null && param.month !== undefined && param.month !=='' ){ |
| | | // 当传入年份和月份时,生成该月份的所有日期 |
| | | const year = parseInt(param.year); |
| | | const month = parseInt(param.month); |
| | | // 获取该月的天数 |
| | | const daysInMonth = new Date(year, month, 0).getDate(); |
| | | |
| | | // 确定天数逻辑不变 |
| | | if (param.searchType === "TODAY") { |
| | | days = 1; |
| | | } else if (param.searchType === "YESTERDAY") { |
| | | days = 1; |
| | | } else if (param.searchType === "LAST_SEVEN") { |
| | | days = 7; |
| | | } else if (param.searchType === "LAST_THIRTY") { |
| | | days = 30; |
| | | // 生成该月所有日期 |
| | | for (let day = 1; day <= daysInMonth; day++) { |
| | | const date = new Date(year, month - 1, day); // 月份从0开始,所以要减1 |
| | | dates.push(this.formatDate(date, 'MM-dd')); |
| | | } |
| | | |
| | | }else{ |
| | | // 确定天数逻辑不变 |
| | | if (param.searchType === "TODAY") { |
| | | days = 1; |
| | | } else if (param.searchType === "YESTERDAY") { |
| | | days = 1; |
| | | } else if (param.searchType === "LAST_SEVEN") { |
| | | days = 7; |
| | | } else if (param.searchType === "LAST_THIRTY") { |
| | | days = 30; |
| | | } |
| | | for (let i = days - 1; i >= 0; i--) { |
| | | const date = new Date(today); |
| | | date.setDate(today.getDate() - i); |
| | | dates.push(this.formatDate(date, 'MM-dd')); |
| | | } |
| | | } |
| | | |
| | | // 生成日期数组逻辑不变 |
| | | for (let i = days - 1; i >= 0; i--) { |
| | | const date = new Date(today); |
| | | date.setDate(today.getDate() - i); |
| | | dates.push(this.formatDate(date, 'MM-dd')); |
| | | } |
| | | |
| | | return dates; |
| | | }, |
| | | |
| | |
| | | if (this.orderCountChart){ |
| | | this.orderCountChart.resize() |
| | | } |
| | | if (this.orderTimePeriodChart){ |
| | | this.orderTimePeriodChart.resize(); |
| | | } |
| | | if (this.productRepurchaseRateChart){ |
| | | this.productRepurchaseRateChart.resize(); |
| | | } |
| | | }, |
| | | initChart(){ |
| | | this.chart = echarts.init(this.$refs.chartDom) |
| | | this.viewPrintChart = echarts.init(this.$refs.viewPrintChartDom) |
| | | this.orderCountChart = echarts.init(this.$refs.orderCountChartDom) |
| | | this.orderTimePeriodChart = echarts.init(this.$refs.orderTimePeriodChartDom) |
| | | this.productRepurchaseRateChart = echarts.init(this.$refs.productRepurchaseRateChartDom) |
| | | |
| | | this.updateChartData(this.orderParams) |
| | | |
| | | this.updateOrderCountChartDate(this.orderParams) |
| | | this.updateChartData(this.orderParams)//更新 pvuv |
| | | |
| | | let form = {...this.orderParams}; |
| | | form.currentType = this.currentType; |
| | | form.currentLimit = this.currentLimit; |
| | | this.updateViewAndCompletionRateData(form) |
| | | this.updateOrderCountChartDate(this.orderParams)//更新日订单 |
| | | |
| | | let viewForm = {...this.orderParams}; |
| | | viewForm.currentType = this.currentType; |
| | | viewForm.currentLimit = this.currentLimit; |
| | | this.updateViewAndCompletionRateData(viewForm)//更新完播率 |
| | | |
| | | this.updateOrderTimePeriodData(this.orderParams)//更新浏览数据,下订单日期时间段分析 |
| | | |
| | | let productRepurchaseDataForm ={...this.orderParams}; |
| | | productRepurchaseDataForm.currentLimit = this.repurchaseCurrentLimit |
| | | this.updateProductRepurchaseData(productRepurchaseDataForm)// |
| | | }, |
| | | // 更新图表数据 |
| | | //------------更新图表数据pvuv start |
| | | async updateChartData(param) { |
| | | // 生成日期和PV数据 |
| | | const { pvData ,uvData} = await this.generateChartData(param) |
| | | // 设置图表配置 |
| | | this.chart.setOption(this.getChartOption(this.getDates(param), pvData, uvData)) |
| | | }, |
| | | // 获取图表配置项 |
| | | // 获取图表配置项 |
| | | // 图表配置项(恢复原始颜色) |
| | | getChartOption(dates, pvData, uvData) { |
| | | return { |
| | |
| | | animationEasing: 'cubicOut' |
| | | }; |
| | | }, |
| | | // 生成pv/uv图表数据 |
| | | // 生成pv/uv图表数据 |
| | | async generateChartData(param) { |
| | | // 1. 声明为async函数 |
| | | // 2. 使用await等待接口返回,确保数据获取后再继续 |
| | |
| | | // 3. 此时pvData和uvData已被接口数据填充,再返回 |
| | | return { pvData, uvData }; |
| | | }, |
| | | //------------- |
| | | //-------------订单日趋势 |
| | | //-------------end |
| | | //-------------订单日趋势start |
| | | async updateOrderCountChartDate(param){ |
| | | const { orderCountData } = await this.generateOrderCountChartData(param) |
| | | this.orderCountChart.setOption(this.getOrderCountChartOption(this.getDates(param),orderCountData)) |
| | |
| | | animationEasing: 'cubicOut' // 缓动效果,优化动画流畅度 |
| | | }; |
| | | }, |
| | | //------------- |
| | | //-------------end |
| | | |
| | | //-------------浏览量,完播率, |
| | | //-------------浏览量,完播率,start |
| | | handleLimitChange(limit) { |
| | | if (this.currentLimit !== limit) { |
| | | this.currentLimit = limit |
| | | let from = {...this.orderParams} |
| | | from.currentLimit = this.currentLimit; |
| | | from.currentType = this.currentType; |
| | | this.updateViewAndCompletionRateData(from) // 重新加载数据 |
| | | let form = {...this.orderParams} |
| | | form.currentLimit = this.currentLimit; |
| | | form.currentType = this.currentType; |
| | | this.updateViewAndCompletionRateData(form) // 重新加载数据 |
| | | } |
| | | }, |
| | | handleTypeChange(type) { |
| | | if (this.currentType !== type){// 避免重复点击 |
| | | this.currentType = type // 更新当前类型 |
| | | let from = {...this.orderParams} |
| | | from.currentLimit = this.currentLimit; |
| | | from.currentType = this.currentType; |
| | | this.updateViewAndCompletionRateData(from) // 重新加载数据 |
| | | let form = {...this.orderParams} |
| | | form.currentLimit = this.currentLimit; |
| | | form.currentType = this.currentType; |
| | | this.updateViewAndCompletionRateData(form) // 重新加载数据 |
| | | } |
| | | |
| | | }, |
| | |
| | | formatter:tooltipFormatter |
| | | }, |
| | | grid: { |
| | | left: '5%', // 视频类型左移,给完播率标签留空间 |
| | | right: '10%', |
| | | bottom: '0', |
| | | left: '4%', // 视频类型左移,给完播率标签留空间 |
| | | right: '12%', |
| | | bottom: '2%', |
| | | top: '5%', |
| | | containLabel: true |
| | | }, |
| | |
| | | min: 0 |
| | | }, |
| | | yAxis: { |
| | | inverse: true, |
| | | type: 'category', |
| | | data: yData, |
| | | axisLine: { lineStyle: { color: '#ddd' } }, |
| | |
| | | ] |
| | | }; |
| | | }, |
| | | //-------------end |
| | | //-------------浏览数据,下订单日期时间段分析start |
| | | async updateOrderTimePeriodData(param){ |
| | | const { viewData,countData,dateData} = await this.generateOrderTimePeriodChartData(param); |
| | | this.orderTimePeriodChart.setOption(this.getOrderTimePeriodChartOption(viewData,countData,dateData)); |
| | | }, |
| | | async generateOrderTimePeriodChartData(param){ |
| | | let viewData= []; |
| | | let countData =[]; |
| | | let dateData = []; |
| | | try { |
| | | const res = await getOrderTimePeriod(param); // 等待接口响应 |
| | | if (res.code === 200) { |
| | | viewData = res.data.viewData; |
| | | countData = res.data.countData |
| | | dateData = res.data.dateData; |
| | | } |
| | | } catch (error) { |
| | | console.error("接口调用失败", error); |
| | | |
| | | } |
| | | return { viewData,countData,dateData} |
| | | |
| | | //------------- |
| | | }, |
| | | getOrderTimePeriodChartOption(viewData,countData,dateData){ |
| | | return { |
| | | tooltip: { |
| | | trigger: 'axis', |
| | | backgroundColor: 'rgba(255, 255, 255, 0.9)', |
| | | borderColor: '#eee', |
| | | borderWidth: 1, |
| | | textStyle: { color: '#333' }, |
| | | formatter: (params) => { |
| | | const date = params[0].name; |
| | | const view = params.find(item => item.seriesName === '浏览数据')?.value || 0; |
| | | const count= params.find(item => item.seriesName === '订单时间段')?.value || 0; |
| | | // 优化:添加换行符转义,避免潜在格式问题(可选,不影响功能) |
| | | return `<div class="font-medium">${date}</div> |
| | | <div>浏览数据: <span style="color: #165DFF; font-weight: bold">${view.toLocaleString()}</span></div> |
| | | <div>订单时间段: <span style="color: #00b42a; font-weight: bold">${count.toLocaleString()}</span></div>`; |
| | | }, |
| | | }, |
| | | legend: { |
| | | data: ['浏览数据', '订单时间段'], |
| | | top: 0, |
| | | textStyle: { color: '#666' } |
| | | }, |
| | | grid: { |
| | | left: '3%', |
| | | right: '4%', |
| | | bottom: '3%', |
| | | containLabel: true, |
| | | top: '15%' |
| | | }, |
| | | xAxis: { |
| | | type: 'category', |
| | | data: dateData, |
| | | axisLine: { lineStyle: { color: '#eee' } }, |
| | | axisTick: { show: false }, |
| | | axisLabel: { interval: 'auto', rotate: 0, color: '#86909C' }, |
| | | splitLine: { show: false } |
| | | }, |
| | | yAxis: { |
| | | type: 'value', |
| | | axisLine: { show: false }, |
| | | axisTick: { show: false }, |
| | | axisLabel: { color: '#86909C', formatter: '{value}' }, |
| | | splitLine: { lineStyle: { color: '#f2f3f5' } }, |
| | | min: 0 |
| | | }, |
| | | series: [ |
| | | // PV折线(保留原始蓝色系,优化点线衔接) |
| | | { |
| | | name: '浏览数据', |
| | | type: 'line', |
| | | data: viewData, |
| | | symbol: 'circle', |
| | | symbolSize: 6, // 保留原有点大小 |
| | | emphasis: { |
| | | symbolSize: 8, |
| | | showSymbol: true |
| | | }, |
| | | lineStyle: { |
| | | width: 3, // 保留原有线宽 |
| | | color: new echarts.graphic.LinearGradient(0, 0, 1, 0, [ |
| | | { offset: 0, color: '#165DFF' }, // 原始PV主色 |
| | | { offset: 1, color: '#36CFC9' } // 原始渐变辅助色 |
| | | ]), |
| | | // 关键优化1:线条端点设为圆角,避免“切口”空白 |
| | | cap: 'round', |
| | | // 关键优化2:线条拐角设为圆角,避免衔接间隙 |
| | | join: 'round', |
| | | // 辅助:控制拐角圆角大小(与线宽匹配,避免溢出) |
| | | miterLimit: 3 |
| | | }, |
| | | itemStyle: { |
| | | color: '#165DFF', // 原始点颜色 |
| | | borderColor: '#fff', // 保留白色边框(增强层次感) |
| | | // 关键优化3:减小边框宽度,避免遮挡线条导致断层 |
| | | borderWidth: 0 // 从2调整为1.5 |
| | | }, |
| | | areaStyle: { |
| | | color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [ |
| | | { offset: 0, color: 'rgba(22, 93, 255, 0.2)' }, // 原始填充色 |
| | | { offset: 1, color: 'rgba(22, 93, 255, 0)' } |
| | | ]) |
| | | }, |
| | | smooth: true, |
| | | showSymbol: true, // 保留你设置的“默认显示所有点” |
| | | }, |
| | | // UV折线(保留原始绿色系,同PV优化逻辑) |
| | | { |
| | | name: '订单时间段', |
| | | type: 'line', |
| | | data: countData, |
| | | symbol: 'circle', |
| | | symbolSize: 6, // 保留原有点大小 |
| | | emphasis: { |
| | | symbolSize: 8, |
| | | showSymbol: true |
| | | }, |
| | | lineStyle: { |
| | | width: 3, // 保留原有线宽 |
| | | color: new echarts.graphic.LinearGradient(0, 0, 1, 0, [ |
| | | { offset: 0, color: '#00b42a' }, // 原始UV主色 |
| | | { offset: 1, color: '#72c140' } // 原始渐变辅助色 |
| | | ]), |
| | | // 同PV优化:端点圆角+拐角圆角 |
| | | cap: 'round', |
| | | join: 'round', |
| | | miterLimit: 3 |
| | | }, |
| | | itemStyle: { |
| | | color: '#00b42a', // 原始点颜色 |
| | | borderColor: '#fff', // 保留白色边框 |
| | | // 同PV优化:减小边框宽度 |
| | | borderWidth: 0 // 从2调整为1.5 |
| | | }, |
| | | areaStyle: { |
| | | color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [ |
| | | { offset: 0, color: 'rgba(0, 180, 42, 0.2)' }, // 原始填充色 |
| | | { offset: 1, color: 'rgba(0, 180, 42, 0)' } |
| | | ]) |
| | | }, |
| | | smooth: true, |
| | | showSymbol: true, // 保留“默认显示所有点” |
| | | } |
| | | ], |
| | | animation: true, |
| | | animationDuration: 1500, |
| | | animationEasing: 'cubicOut' |
| | | }; |
| | | }, |
| | | //--------------ebd |
| | | |
| | | //-------------复购率 start |
| | | handleRepurchaseLimitChange(limit) { |
| | | if (this.repurchaseCurrentLimit !== limit) { |
| | | this.repurchaseCurrentLimit = limit |
| | | let form = {...this.orderParams} |
| | | form.currentLimit = this.repurchaseCurrentLimit; |
| | | this.updateProductRepurchaseData(form) // 重新加载数据 |
| | | } |
| | | }, |
| | | async updateProductRepurchaseData(param){ |
| | | const { rateData,goodsData} = await this.generateProductRepurchaseChartData(param); |
| | | this.productRepurchaseRateChart.setOption(this.getProductRepurchaseChartOption(this.getDates(param),rateData,goodsData)); |
| | | }, |
| | | async generateProductRepurchaseChartData(param){ |
| | | let rateData = []; |
| | | let goodsData = []; |
| | | try{ |
| | | const res = await gerProductRepurchase(param); |
| | | if (res.code === 200){ |
| | | rateData = res.data.rateData; |
| | | goodsData = res.data.goodsData; |
| | | } |
| | | }catch (error){ |
| | | console.error("接口调用失败", error); |
| | | } |
| | | return {rateData,goodsData} |
| | | }, |
| | | getProductRepurchaseChartOption(dates, rateData, goodsData) { |
| | | // 工具函数:判断数值是否为整数(避免100.00显示为100.00%) |
| | | const isInteger = (num) => { |
| | | return Math.abs(num - Math.round(num)) < Number.EPSILON; |
| | | }; |
| | | |
| | | return { |
| | | tooltip: { |
| | | trigger: 'axis', |
| | | backgroundColor: 'rgba(255, 255, 255, 0.9)', |
| | | borderColor: '#eee', |
| | | borderWidth: 1, |
| | | textStyle: { color: '#333' }, |
| | | axisPointer: { |
| | | type: 'shadow' |
| | | }, |
| | | formatter: (params) => { |
| | | const goodsName = params[0].name; |
| | | const rate = params[0].value; |
| | | // 整数显示无小数,非整数保留两位小数 |
| | | const displayRate = isInteger(rate) ? rate : rate.toFixed(2); |
| | | return `<div class="font-medium">${goodsName}</div> |
| | | <div>复购率: <span style="color: #722ED1; font-weight: bold">${displayRate}%</span></div>`; |
| | | }, |
| | | }, |
| | | grid: { |
| | | left: '3%', |
| | | right: '4%', |
| | | bottom: '3%', |
| | | top: '3%', |
| | | containLabel: true |
| | | }, |
| | | xAxis: { |
| | | type: 'value', |
| | | axisLine: { show: false }, |
| | | axisTick: { show: false }, |
| | | axisLabel: { |
| | | color: '#86909C', |
| | | // 坐标轴标签:整数显示无小数,非整数保留0位(取整) |
| | | formatter: (value) => { |
| | | return isInteger(value) ? `${value}%` : `${Math.round(value)}%`; |
| | | } |
| | | }, |
| | | splitLine: { |
| | | lineStyle: { |
| | | color: '#f2f3f5', |
| | | type: 'dashed' |
| | | } |
| | | }, |
| | | min: 0, |
| | | max: (value) => { |
| | | // 限制最大值不超过100(复购率最高100%),同时保留1.2倍的间距 |
| | | const calculatedMax = value.max * 1.2; |
| | | return Math.min(calculatedMax, 100); |
| | | } |
| | | }, |
| | | yAxis: { |
| | | type: 'category', |
| | | data: goodsData, |
| | | axisLine: { lineStyle: { color: '#eee' } }, |
| | | axisTick: { show: false }, |
| | | axisLabel: { |
| | | color: '#86909C', |
| | | interval: 0, |
| | | formatter: (value) => { |
| | | return value.length > 10 ? value.substring(0, 10) + '...' : value; |
| | | } |
| | | }, |
| | | splitLine: { show: false }, |
| | | inverse: true |
| | | }, |
| | | series: [ |
| | | { |
| | | name: '商品复购率', |
| | | type: 'bar', |
| | | data: rateData, |
| | | barWidth: '60%', |
| | | itemStyle: { |
| | | color: new echarts.graphic.LinearGradient(0, 0, 1, 0, [ |
| | | { offset: 0, color: '#722ED1' }, |
| | | { offset: 1, color: '#EB2F96' } |
| | | ]), |
| | | borderRadius: [0, 4, 4, 0] |
| | | }, |
| | | emphasis: { |
| | | itemStyle: { |
| | | shadowBlur: 10, |
| | | shadowColor: 'rgba(114, 46, 209, 0.5)' |
| | | } |
| | | }, |
| | | label: { |
| | | show: true, |
| | | position: 'right', |
| | | // 柱状图标签:整数显示无小数,非整数保留1位 |
| | | formatter: (params) => { |
| | | const value = params.value; |
| | | return isInteger(value) ? `${value}%` : value.toFixed(1) + '%'; |
| | | }, |
| | | color: '#722ED1', |
| | | fontWeight: 'bold' |
| | | } |
| | | } |
| | | ], |
| | | }; |
| | | }, |
| | | //-------------end |
| | | formatDate(date) { |
| | | const month = date.getMonth() + 1; // 月份从0开始,需+1 |
| | | const day = date.getDate(); |
| | |
| | | this.refundParams.pageSize = 10 |
| | | |
| | | //更新表格 |
| | | |
| | | this.updateChartData(this.orderParams) |
| | | this.updateOrderCountChartDate(this.orderParams) |
| | | |
| | |
| | | form.currentType = this.currentType; |
| | | form.currentLimit = this.currentLimit; |
| | | this.updateViewAndCompletionRateData(form) |
| | | |
| | | this.updateOrderTimePeriodData(this.orderParams) |
| | | |
| | | let productRepurchaseDataForm ={...this.orderParams}; |
| | | productRepurchaseDataForm.currentLimit = this.repurchaseCurrentLimit |
| | | |
| | | this.updateProductRepurchaseData(productRepurchaseDataForm)// |
| | | }, |
| | | |
| | | // 实例化订单概览 |
| | |
| | | width: 100%; |
| | | height: 662px; |
| | | } |
| | | .repurchase-chart-container{ |
| | | width: 100%; |
| | | height: 662px; |
| | | } |
| | | .chart-container { |
| | | width: 100%; |
| | | height: 400px; |