943 lines
27 KiB
Vue
943 lines
27 KiB
Vue
<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"
|
||
tooltipFormat="timeDistributionChartData" />
|
||
</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" tooltipFormat="regionDistributionData" />
|
||
|
||
<!-- 自定义可滚动图例 -->
|
||
<view class="custom-region-legend">
|
||
<scroll-view class="legend-scroll" scroll-x="true" show-scrollbar="false">
|
||
<view class="legend-items">
|
||
<view v-for="(item, index) in regionLegendData" :key="index" class="legend-item"
|
||
@tap="onLegendTap(index)">
|
||
<view class="legend-color" :style="{ backgroundColor: item.color }"></view>
|
||
<text class="legend-name">{{ item.name }}</text>
|
||
<text class="legend-value">{{ item.percentage }}%</text>
|
||
</view>
|
||
</view>
|
||
</scroll-view>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
|
||
<!-- 商品销售量分析 -->
|
||
<view class="chart-card">
|
||
<view class="chart-header">
|
||
<text class="chart-title">商品销售量分析</text>
|
||
<text class="chart-subtitle">TOP 5 热销商品销量</text>
|
||
</view>
|
||
<view class="chart-content">
|
||
<view class="bar-chart-container">
|
||
<QiunDataCharts type="column" :opts="salesVolumeOpts" :chartData="salesVolumeChartData" :canvas2d="true"
|
||
:inScrollView="true" canvasId="salesVolumeChart" tooltipFormat="SalesRankingOfProducts"/>
|
||
</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() {
|
||
// 获取数据的最大值来动态设置y轴,将字符串转换为数字
|
||
const allData = [
|
||
...this.timeDistributionData.transactionData.map(item => Number(item) || 0),
|
||
...this.timeDistributionData.amountData.map(item => Number(item) || 0)
|
||
]
|
||
const maxValue = Math.max(...allData)
|
||
const yAxisMax = this.calculateYAxisMax(maxValue)
|
||
|
||
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,
|
||
max: yAxisMax,
|
||
// 使用splitNumber控制刻度数量
|
||
splitNumber: 6
|
||
}]
|
||
},
|
||
extra: {
|
||
line: {
|
||
type: 'curve',
|
||
width: 2,
|
||
activeType: 'hollow'
|
||
}
|
||
}
|
||
}
|
||
},
|
||
|
||
// 预定义颜色数组
|
||
colorPalette() {
|
||
return [
|
||
'#576EFF', '#52C41A', '#FAAD14', '#FF4D4F', '#722ED1',
|
||
'#13C2C2', '#EB2F96', '#F5222D', '#FA8C16', '#A0D911',
|
||
'#52C41A', '#1890FF', '#722ED1', '#EB2F96', '#13C2C2',
|
||
'#FAAD14', '#F5222D', '#FA8C16', '#A0D911', '#52C41A',
|
||
'#1890FF', '#722ED1', '#EB2F96', '#13C2C2', '#FAAD14',
|
||
'#F5222D', '#FA8C16', '#A0D911', '#52C41A', '#1890FF'
|
||
]
|
||
},
|
||
|
||
// 地域分布图表数据
|
||
regionDistributionData() {
|
||
// 预处理数据,计算百分比
|
||
const total = this.regionData.reduce((sum, item) => sum + Number(item.value), 0)
|
||
|
||
return {
|
||
series: [{
|
||
data: this.regionData.map((item, index) => ({
|
||
name: item.name,
|
||
value: Number(item.value),
|
||
color: this.colorPalette[index % this.colorPalette.length], // 为每个数据项分配颜色
|
||
data: {
|
||
percentage: (Number(item.value) / total * 100),
|
||
originalIndex: index // 保存原始索引用于颜色映射
|
||
}
|
||
}))
|
||
}]
|
||
}
|
||
},
|
||
|
||
// 自定义图例数据
|
||
regionLegendData() {
|
||
// 预处理数据,计算百分比并分配颜色
|
||
const total = this.regionData.reduce((sum, item) => sum + Number(item.value), 0)
|
||
|
||
return this.regionData.map((item, index) => ({
|
||
name: item.name,
|
||
value: Number(item.value),
|
||
percentage: ((Number(item.value) / total * 100).toFixed(1)),
|
||
color: this.colorPalette[index % this.colorPalette.length], // 基于原始索引分配颜色
|
||
originalIndex: index // 保存原始索引
|
||
})).sort((a, b) => b.value - a.value) // 按值从大到小排序
|
||
},
|
||
|
||
// 地域分布图表配置
|
||
regionDistributionOpts() {
|
||
return {
|
||
padding: [5, 5, 5, 5],
|
||
dataLabel: false,
|
||
legend: {
|
||
show: false // 关闭原生图例,使用自定义图例
|
||
},
|
||
extra: {
|
||
pie: {
|
||
activeOpacity: 0.5,
|
||
activeRadius: 10,
|
||
offsetAngle: 0,
|
||
border: false,
|
||
borderWidth: 2,
|
||
borderColor: '#FFFFFF'
|
||
}
|
||
},
|
||
// 启用tooltip显示详细信息
|
||
tooltip: {
|
||
format: (item) => {
|
||
const legendItem = this.regionLegendData.find(legend => legend.name === item.name)
|
||
const percentage = legendItem ? legendItem.percentage : '0.0'
|
||
return `${item.name}: ${this.formatMoney(item.value)}元 (${percentage}%)`
|
||
}
|
||
}
|
||
}
|
||
},
|
||
|
||
// 销售量图表数据
|
||
salesVolumeChartData() {
|
||
return {
|
||
categories: this.salesVolumeData.map(item =>
|
||
this.formatXAxisLabel(item.name)
|
||
),
|
||
series: [{
|
||
name: '销量',
|
||
data: this.salesVolumeData.map(item => item.value)
|
||
}]
|
||
}
|
||
},
|
||
|
||
// 销售量图表配置
|
||
salesVolumeOpts() {
|
||
// 计算最大值并生成6个刻度,每个刻度都是100的倍数
|
||
const maxSales = Math.max(...this.salesVolumeData.map(item => item.value));
|
||
const roundedMax = Math.ceil(maxSales / 100) * 100; // 向上取整到100的倍数
|
||
const yAxisInterval = Math.ceil(roundedMax / 5); // 分成5个间隔,总共6个刻度
|
||
const finalInterval = Math.ceil(yAxisInterval / 100) * 100; // 确保间隔是100的倍数
|
||
const finalMax = finalInterval * 5; // 最终最大值
|
||
|
||
// 生成Y轴刻度数据
|
||
const yAxisData = [];
|
||
for (let i = 0; i <= 5; i++) {
|
||
yAxisData.push(i * finalInterval);
|
||
}
|
||
|
||
return {
|
||
color: ['#576EFF'],
|
||
legend: {
|
||
show: true,
|
||
color: ['#576EFF']
|
||
},
|
||
padding: [20, 15, 35, 15], // 增加底部padding给X轴标签留空间
|
||
dataLabel: false,
|
||
enableScroll: false,
|
||
xAxis: {
|
||
itemCount: 5, // 减少显示的标签数量
|
||
scrollAlign: 'right',
|
||
scrollColor: '#576EFF',
|
||
scrollBackgroundColor: 'rgba(87, 110, 255, 0.1)',
|
||
scrollWidth: 4,
|
||
scrollHeight: 8,
|
||
rotate: 30, // 旋转30度避免重叠
|
||
fontSize: 12, // 适当减小字体
|
||
margin: 15 // 增加标签与轴线的距离
|
||
},
|
||
yAxis: {
|
||
gridType: 'dash',
|
||
dashLength: 2,
|
||
data: [{
|
||
min: 0,
|
||
max: finalMax,
|
||
data: yAxisData
|
||
}]
|
||
},
|
||
categoriesReal: this.salesVolumeData.map(item => item.name),
|
||
extra: {
|
||
column: {
|
||
type: 'group',
|
||
width: 12, // 与ProductReport保持一致
|
||
activeBgColor: '#000000',
|
||
activeBgOpacity: 0.08,
|
||
barBorderCircle: true,
|
||
linearType: 'none',
|
||
linearOpacity: 0
|
||
}
|
||
}
|
||
}
|
||
},
|
||
|
||
// 销售额图表数据
|
||
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()
|
||
},
|
||
|
||
// 计算y轴最大值:根据实际最大值自动调整,确保6个刻度时每个都是100的倍数
|
||
calculateYAxisMax(maxValue) {
|
||
if (maxValue === 0) return 600 // 默认值
|
||
|
||
// 为了确保6个刻度都是100的倍数,最大值应该是500的倍数
|
||
// 例如:500 -> [0,100,200,300,400,500]
|
||
const baseInterval = 100
|
||
const tickCount = 6
|
||
|
||
// 计算理想的间隔值
|
||
const idealInterval = Math.ceil(maxValue / (tickCount - 1) / baseInterval) * baseInterval
|
||
|
||
// y轴最大值 = 间隔 × (刻度数-1)
|
||
const yAxisMax = idealInterval * (tickCount - 1)
|
||
|
||
return yAxisMax
|
||
},
|
||
|
||
// 图例点击交互
|
||
onLegendTap(index) {
|
||
// 可以在这里添加图例交互逻辑,比如高亮对应的饼图区域
|
||
const tappedLegend = this.regionLegendData[index]
|
||
console.log('点击图例:', tappedLegend)
|
||
|
||
// 可以显示提示信息或执行其他交互
|
||
uni.showToast({
|
||
title: `${tappedLegend.name}: ${tappedLegend.percentage}%`,
|
||
icon: 'none'
|
||
})
|
||
},
|
||
|
||
// 格式化X轴标签文字
|
||
formatXAxisLabel(name) {
|
||
if (!name) return '';
|
||
// 如果名称超过4个字符,截取并添加省略号
|
||
return name.length > 4 ? name.substring(0, 4) + '...' : name;
|
||
},
|
||
// 拿到商品销售量分析
|
||
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)
|
||
|
||
// 控制x轴标签显示间隔:每4小时显示一个标签,以及0点
|
||
if (hour === 0 || hour % 4 === 0) {
|
||
categories.push(`${item.name}时`)
|
||
} else {
|
||
categories.push('') // 其他位置为空字符串,不显示标签
|
||
}
|
||
|
||
// 所有数据都保留
|
||
transactionData.push(item.data)
|
||
amountData.push(item.key)
|
||
})
|
||
}
|
||
this.timeDistributionData = {
|
||
categories: categories,
|
||
transactionData: transactionData,
|
||
amountData: amountData
|
||
}
|
||
// 24个完整数据点,但x轴只显示7个标签(0点、4点、8点、12点、16点、20点、24点)
|
||
// 所有数据点都支持点击交互
|
||
},
|
||
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%;
|
||
}
|
||
|
||
// 自定义图例样式
|
||
.custom-region-legend {
|
||
margin-top: 20rpx;
|
||
width: 100%;
|
||
|
||
.legend-scroll {
|
||
width: 100%;
|
||
white-space: nowrap;
|
||
|
||
.legend-items {
|
||
display: inline-flex;
|
||
gap: 16rpx;
|
||
padding: 0 8rpx;
|
||
|
||
.legend-item {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 8rpx;
|
||
padding: 8rpx 12rpx;
|
||
background: rgba(0, 0, 0, 0.02);
|
||
border-radius: 8rpx;
|
||
border: 1rpx solid rgba(0, 0, 0, 0.1);
|
||
white-space: nowrap;
|
||
transition: all 0.2s ease;
|
||
|
||
&:active {
|
||
background: rgba(87, 110, 255, 0.1);
|
||
border-color: #576EFF;
|
||
transform: scale(0.95);
|
||
}
|
||
|
||
.legend-color {
|
||
width: 16rpx;
|
||
height: 16rpx;
|
||
border-radius: 4rpx;
|
||
flex-shrink: 0;
|
||
}
|
||
|
||
.legend-name {
|
||
font-size: 24rpx;
|
||
color: @text-secondary;
|
||
max-width: 120rpx;
|
||
overflow: hidden;
|
||
text-overflow: ellipsis;
|
||
white-space: nowrap;
|
||
}
|
||
|
||
.legend-value {
|
||
font-size: 24rpx;
|
||
color: @text-primary;
|
||
font-weight: 600;
|
||
flex-shrink: 0;
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|
||
</style> |