ccy_DIB/pages/DigitalIntelligenceDashboard/components/OrderTransactionAnalysis.vue
ylj20011123 7cc6a538e7 update
2025-10-24 14:51:57 +08:00

762 lines
20 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<template>
<view class="order-transaction-analysis">
<!-- 报表标题 -->
<view class="report-header">
<text class="report-title">订单和交易数据交叉分析</text>
<view class="report-period">
<text class="period-label">分析周期</text>
<text class="period-value">{{ analysisPeriod }}</text>
</view>
</view>
<!-- 关键指标卡片 -->
<view class="metrics-row">
<view class="metric-card">
<view class="metric-icon total-amount"></view>
<text class="metric-title">交易总量</text>
<view class="metric-value-container">
<text class="metric-value">{{ formatNumber(totalTransactions) }}</text>
<text class="metric-unit"></text>
</view>
</view>
<view class="metric-card">
<view class="metric-icon total-money"></view>
<text class="metric-title">交易总金额</text>
<view class="metric-value-container">
<text class="metric-value">¥{{ formatMoney(totalAmount) }}</text>
<text class="metric-unit"></text>
</view>
</view>
<view class="metric-card">
<view class="metric-icon conversion-rate"></view>
<text class="metric-title">成交转化率</text>
<view class="metric-value-container">
<text class="metric-value">{{ conversionRate }}%</text>
<text class="metric-unit"></text>
</view>
</view>
</view>
<view class="metrics-row">
<view class="metric-card">
<view class="metric-icon order-users"></view>
<text class="metric-title">下单用户数</text>
<view class="metric-value-container">
<text class="metric-value">{{ formatNumber(orderUsers) }}</text>
<text class="metric-unit"></text>
</view>
</view>
<view class="metric-card">
<view class="metric-icon deal-users"></view>
<text class="metric-title">成交用户数</text>
<view class="metric-value-container">
<text class="metric-value">{{ formatNumber(dealUsers) }}</text>
<text class="metric-unit"></text>
</view>
</view>
<view class="metric-card">
<view class="metric-icon avg-order"></view>
<text class="metric-title">客单价</text>
<view class="metric-value-container">
<text class="metric-value">¥{{ formatMoney(avgOrderValue) }}</text>
<text class="metric-unit"></text>
</view>
</view>
</view>
<!-- 交易时间分布 -->
<view class="chart-card">
<view class="chart-header">
<text class="chart-title">交易时间分布</text>
<text class="chart-subtitle">24小时交易量分布情况</text>
</view>
<view class="chart-content">
<view class="line-chart-container">
<QiunDataCharts type="line" :opts="timeDistributionOpts" :chartData="timeDistributionChartData"
:canvas2d="true" :inScrollView="true" canvasId="timeDistributionChart" />
</view>
</view>
</view>
<!-- 交易地域分布 -->
<view class="chart-card">
<view class="chart-header">
<text class="chart-title">交易地域分布</text>
<text class="chart-subtitle">各省份交易金额占比</text>
</view>
<view class="chart-content">
<view class="map-chart-container">
<QiunDataCharts type="pie" :opts="regionDistributionOpts" :chartData="regionDistributionData" :canvas2d="true"
:inScrollView="true" canvasId="regionDistributionChart" />
</view>
</view>
</view>
<!-- 商品销售量分析 -->
<view class="chart-card">
<view class="chart-header">
<text class="chart-title">商品销售量分析</text>
<text class="chart-subtitle">TOP 10 热销商品销量</text>
</view>
<view class="chart-content">
<view class="bar-chart-container">
<QiunDataCharts type="column" :opts="salesVolumeOpts" :chartData="salesVolumeChartData" :canvas2d="true"
:inScrollView="true" canvasId="salesVolumeChart" />
</view>
</view>
</view>
<!-- 商品销售额分析 -->
<view class="chart-card">
<view class="chart-header">
<text class="chart-title">商品销售额分析</text>
<text class="chart-subtitle">TOP 10 高销售额商品排行</text>
</view>
<view class="chart-content">
<view class="bar-chart-container">
<QiunDataCharts type="column" :opts="salesAmountOpts" :chartData="salesAmountChartData" :canvas2d="true"
:inScrollView="true" canvasId="salesAmountChart" />
</view>
</view>
</view>
</view>
</template>
<script>
import { wrapTreeNode } from '../../../util/dateTime';
import QiunDataCharts from './qiun-data-charts/components/qiun-data-charts/qiun-data-charts.vue'
import request from "@/util/index.js";
export default {
components: {
QiunDataCharts
},
data() {
return {
analysisPeriod: '2024年10月',
totalTransactions: 45678,
totalAmount: 23456789,
conversionRate: 68.5,
orderUsers: 12890,
dealUsers: 8834,
avgOrderValue: 5136,
// 交易时间分布数据
timeDistributionData: {
categories: [],
transactionData: [],
amountData: []
},
// 地域分布数据
regionData: [],
// 商品销售量数据
salesVolumeData: [],
// 商品销售额数据
salesAmountData: [
{ name: '玉石手镯', amount: 567890 },
{ name: '云南白药套装', amount: 456789 },
{ name: '普洱茶礼盒', amount: 345678 },
{ name: '民族服饰', amount: 234567 },
{ name: '三七粉精品', amount: 198765 },
{ name: '鲜花饼组合', amount: 156789 },
{ name: '鲜花精油', amount: 123456 },
{ name: '云南咖啡豆', amount: 98765 },
{ name: '银饰项链', amount: 87654 },
{ name: '手工皂套装', amount: 65432 }
]
}
},
computed: {
// 时间分布图表数据
timeDistributionChartData() {
return {
categories: this.timeDistributionData.categories,
series: [
{
name: '客单量',
data: this.timeDistributionData.transactionData
},
{
name: '交易额',
data: this.timeDistributionData.amountData
}
]
}
},
// 时间分布图表配置
timeDistributionOpts() {
return {
color: ['#576EFF', '#52C41A'],
padding: [15, 15, 15, 15],
dataLabel: false,
legend: {
show: true
},
xAxis: {
disableGrid: true
},
yAxis: {
gridType: 'dash',
dashLength: 2,
data: [
{
min: 0
},
{
min: 0,
position: 'right'
}
]
},
extra: {
line: {
type: 'curve',
width: 2,
activeType: 'hollow'
}
}
}
},
// 地域分布图表数据
regionDistributionData() {
return {
series: [{
data: this.regionData.map(item => ({
name: item.name,
value: item.value
}))
}]
}
},
// 地域分布图表配置
regionDistributionOpts() {
return {
padding: [5, 5, 5, 5],
dataLabel: true,
legend: {
show: true
},
extra: {
pie: {
activeOpacity: 0.5,
activeRadius: 10,
offsetAngle: 0,
labelWidth: 15,
border: false,
borderWidth: 3,
borderColor: '#FFFFFF'
}
}
}
},
// 销售量图表数据
salesVolumeChartData() {
return {
categories: this.salesVolumeData.map(item =>
item.name.length > 6 ? item.name.substring(0, 6) + '...' : item.name
),
series: [{
name: '销售量',
data: this.salesVolumeData.map(item => item.value)
}]
}
},
// 销售量图表配置
salesVolumeOpts() {
return {
color: ['#576EFF'],
padding: [15, 15, 15, 15],
dataLabel: false,
enableScroll: true,
xAxis: {
itemCount: 4,
scrollShow: true,
scrollAlign: 'right',
scrollColor: '#576EFF',
scrollBackgroundColor: 'rgba(87, 110, 255, 0.1)',
scrollWidth: 4,
scrollHeight: 8
},
yAxis: {
gridType: 'dash',
dashLength: 2,
data: [{
min: 0
}]
},
extra: {
column: {
type: 'group',
width: 30,
activeBgColor: '#000000',
activeBgOpacity: 0.08,
linearType: 'custom',
barBorderCircle: true
}
}
}
},
// 销售额图表数据
salesAmountChartData() {
return {
categories: this.salesAmountData.map(item =>
item.name.length > 6 ? item.name.substring(0, 6) + '...' : item.name
),
series: [{
name: '销售额',
data: this.salesAmountData.map(item => item.amount)
}]
}
},
// 销售额图表配置
salesAmountOpts() {
return {
color: ['#52C41A'],
padding: [15, 15, 15, 15],
dataLabel: false,
enableScroll: true,
xAxis: {
itemCount: 4,
scrollShow: true,
scrollAlign: 'right',
scrollColor: '#52C41A',
scrollBackgroundColor: 'rgba(82, 196, 26, 0.1)',
scrollWidth: 4,
scrollHeight: 8
},
yAxis: {
gridType: 'dash',
dashLength: 2,
data: [{
min: 0
}]
},
extra: {
column: {
type: 'group',
width: 30,
activeBgColor: '#000000',
activeBgOpacity: 0.08,
linearType: 'custom',
barBorderCircle: true
}
}
}
}
},
onReady() {
// 获取整个组件的数据
this.handleGetAllData()
},
methods: {
// 获取整个组件的数据
handleGetAllData() {
// 拿到交易时间分布的数据
this.handleGetTradingHoursData()
// 拿到交易地域分布的数据
this.handleGetDistributionOfRegion()
// 拿到商品销售量分析
this.hanleGetShopSalesVolumeData()
},
// 拿到商品销售量分析
async hanleGetShopSalesVolumeData() {
const req = {
action_type: "getCommoditySaleSort",
province_code: 5564,
rowNum: 5
}
const data = await request.$cloudUrlGet(req);
console.log('datadatadatadatadata', data);
let list = data.COMMODITYSALE_DESC
let res = []
if (list && list.length > 0) {
list.forEach((item) => {
res.push({
name: item.COMMODITY_NAME,
value: item.SELLCOUNT,
})
})
}
this.salesVolumeData = res
// 最后需要的样式
// salesVolumeData: [
// { name: '云南白药套装', value: 3456 },
// { name: '普洱茶礼盒', value: 2890 },
// ]
},
// 拿到交易地域分布的数据
async handleGetDistributionOfRegion() {
const serviceReq = {
Province_Code: "530000"
}
const serviceList = await request.$apiGet(
"CommercialApi/BaseInfo/GetServerpartList",
serviceReq
);
console.log('serviceListserviceListserviceList', serviceList);
let serviceLists = serviceList.Result_Data.List
let allService = ""
if (serviceLists && serviceLists.length > 0) {
serviceLists.forEach((item) => {
if (allService) {
allService += `,${item.SERVERPART_ID}`
} else {
allService = `${item.SERVERPART_ID}`
}
})
}
// 这里要全部服务区的id 因为服务区会变 所以得调一下全部服务区的id
const req = {
StartDate: "2025-04-01",
EndDate: "2025-04-30",
DataType: 1,
ServerpartIds: allService,
DataSourceType: 1,
}
const data = await request.$apiGet(
"EShangApiMain/Revenue/GetRevenueReport",
req
);
let list = wrapTreeNode(data.Result_Data.List)
console.log('交易地域分布', list);
let res = []
if (list && list.length > 0) {
list.forEach((item) => {
if (item.children && item.children.length > 0) {
item.children.forEach((subItem) => {
res.push({ name: subItem.Serverpart_Name, value: subItem.TotalRevenue.Revenue_Amount })
})
}
})
}
this.regionData = res
// 数据要求张这样
// regionData: [
// { name: '云南省', value: 5678901 },
// { name: '广东省', value: 3456789 },
// ],
},
// 拿到交易时间分布的数据
async handleGetTradingHoursData() {
const req = {
Province_Code: '530000',
Statistics_Date: '2025-09-01',
Serverpart_ID: '',
TimeSpan: 1
}
const data = await request.$apiGet(
"CommercialApi/Revenue/GetTransactionTimeAnalysis",
req
);
let list = data.Result_Data.CommonScatterList
console.log('交易时间分布', list);
let categories = []
let transactionData = [] // 客单量
let amountData = [] // 交易额
if (list && list.length > 0) {
list.forEach((item) => {
let hour = Number(item.name)
if (hour % 4 === 0 || hour === 0) {
categories.push(`${item.name}`)
}
// let hour = Number(item.name) < 10 ? `0${item.name}:00` : `${item.name}:00`
transactionData.push(item.data)
amountData.push(item.key)
})
}
this.timeDistributionData = {
categories: categories,
transactionData: transactionData,
amountData: amountData
}
// 最终数据格式 要这样
// timeDistributionData: {
// categories: ['00:00', '02:00', '04:00', '06:00', '08:00', '10:00', '12:00', '14:00', '16:00', '18:00', '20:00', '22:00'],
// transactionData: [234, 156, 89, 123, 567, 1234, 2345, 1876, 1567, 2890, 2456, 1876],
// amountData: [12345, 8234, 4567, 6789, 34567, 67890, 123456, 98765, 82345, 156789, 123456, 98765]
// }
},
formatNumber(num) {
return num.toString().replace(/\B(?=(\d{3})+(?!\d))/g, ",")
},
formatMoney(amount) {
return amount.toString().replace(/\B(?=(\d{3})+(?!\d))/g, ",")
}
}
}
</script>
<style scoped lang="less">
@primary-color: #667eea;
@secondary-color: #764ba2;
@success-color: #52c41a;
@warning-color: #faad14;
@error-color: #ff4d4f;
@text-primary: #333;
@text-secondary: #666;
@text-light: #999;
@bg-white: #ffffff;
@border-radius: 16rpx;
@shadow: 0 4px 20px rgba(0, 0, 0, 0.08);
.order-transaction-analysis {
.report-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 32rpx;
padding: 0 8rpx;
.report-title {
font-size: 36rpx;
font-weight: 600;
color: @text-primary;
}
.report-period {
display: flex;
align-items: center;
.period-label {
font-size: 24rpx;
color: @text-secondary;
margin-right: 8rpx;
}
.period-value {
font-size: 24rpx;
color: @primary-color;
font-weight: 500;
}
}
}
.metrics-row {
display: flex;
gap: 24rpx;
margin-bottom: 24rpx;
.metric-card {
flex: 1;
background: @bg-white;
border-radius: @border-radius;
padding: 32rpx 24rpx;
box-shadow: @shadow;
text-align: center;
position: relative;
overflow: hidden;
&::before {
content: '';
position: absolute;
top: 0;
left: 0;
right: 0;
height: 4rpx;
background: linear-gradient(90deg, @primary-color, @secondary-color);
}
.metric-icon {
width: 48rpx;
height: 48rpx;
margin: 0 auto 16rpx;
border-radius: 50%;
position: relative;
&.total-amount {
background: linear-gradient(135deg, #1890ff, #40a9ff);
&::after {
content: '📊';
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
font-size: 24rpx;
}
}
&.total-money {
background: linear-gradient(135deg, #52c41a, #73d13d);
&::after {
content: '💰';
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
font-size: 24rpx;
}
}
&.conversion-rate {
background: linear-gradient(135deg, #faad14, #ffc53d);
&::after {
content: '📈';
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
font-size: 24rpx;
}
}
&.order-users {
background: linear-gradient(135deg, #722ed1, #9254de);
&::after {
content: '👥';
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
font-size: 24rpx;
}
}
&.deal-users {
background: linear-gradient(135deg, #eb2f96, #f759ab);
&::after {
content: '✅';
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
font-size: 24rpx;
}
}
&.avg-order {
background: linear-gradient(135deg, #13c2c2, #36cfc9);
&::after {
content: '🛒';
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
font-size: 24rpx;
}
}
}
.metric-title {
font-size: 24rpx;
color: @text-secondary;
margin-bottom: 12rpx;
}
.metric-value-container {
display: flex;
flex-direction: column;
align-items: center;
gap: 4rpx;
}
.metric-value {
font-size: 32rpx;
font-weight: 600;
color: @text-primary;
font-family: 'DINAlternate-Bold', sans-serif;
line-height: 1;
}
.metric-unit {
font-size: 20rpx;
color: @text-light;
line-height: 1;
}
}
}
.chart-card {
background: @bg-white;
border-radius: @border-radius;
padding: 24rpx;
box-shadow: @shadow;
margin-bottom: 24rpx;
.chart-header {
margin-bottom: 20rpx;
.chart-title {
font-size: 28rpx;
font-weight: 600;
color: @text-primary;
margin-bottom: 4rpx;
}
.chart-subtitle {
font-size: 22rpx;
color: @text-light;
}
}
.chart-content {
.line-chart-container {
width: 100%;
}
.map-chart-container {
display: flex;
flex-direction: column;
align-items: center;
.region-legend {
display: flex;
flex-wrap: wrap;
justify-content: center;
gap: 16rpx;
margin-top: 16rpx;
.legend-item {
display: flex;
align-items: center;
gap: 8rpx;
.legend-color {
width: 12rpx;
height: 12rpx;
border-radius: 2rpx;
}
.legend-name {
font-size: 22rpx;
color: @text-secondary;
}
.legend-value {
font-size: 22rpx;
color: @text-primary;
font-weight: 600;
}
}
}
}
.bar-chart-container {
width: 100%;
}
}
}
}
</style>