update
This commit is contained in:
parent
7cc6a538e7
commit
6c38e982b3
@ -73,7 +73,8 @@
|
|||||||
<view class="chart-content">
|
<view class="chart-content">
|
||||||
<view class="line-chart-container">
|
<view class="line-chart-container">
|
||||||
<QiunDataCharts type="line" :opts="timeDistributionOpts" :chartData="timeDistributionChartData"
|
<QiunDataCharts type="line" :opts="timeDistributionOpts" :chartData="timeDistributionChartData"
|
||||||
:canvas2d="true" :inScrollView="true" canvasId="timeDistributionChart" />
|
:canvas2d="true" :inScrollView="true" canvasId="timeDistributionChart"
|
||||||
|
tooltipFormat="timeDistributionChartData" />
|
||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
@ -87,7 +88,21 @@
|
|||||||
<view class="chart-content">
|
<view class="chart-content">
|
||||||
<view class="map-chart-container">
|
<view class="map-chart-container">
|
||||||
<QiunDataCharts type="pie" :opts="regionDistributionOpts" :chartData="regionDistributionData" :canvas2d="true"
|
<QiunDataCharts type="pie" :opts="regionDistributionOpts" :chartData="regionDistributionData" :canvas2d="true"
|
||||||
:inScrollView="true" canvasId="regionDistributionChart" />
|
: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>
|
||||||
</view>
|
</view>
|
||||||
@ -96,12 +111,12 @@
|
|||||||
<view class="chart-card">
|
<view class="chart-card">
|
||||||
<view class="chart-header">
|
<view class="chart-header">
|
||||||
<text class="chart-title">商品销售量分析</text>
|
<text class="chart-title">商品销售量分析</text>
|
||||||
<text class="chart-subtitle">TOP 10 热销商品销量</text>
|
<text class="chart-subtitle">TOP 5 热销商品销量</text>
|
||||||
</view>
|
</view>
|
||||||
<view class="chart-content">
|
<view class="chart-content">
|
||||||
<view class="bar-chart-container">
|
<view class="bar-chart-container">
|
||||||
<QiunDataCharts type="column" :opts="salesVolumeOpts" :chartData="salesVolumeChartData" :canvas2d="true"
|
<QiunDataCharts type="column" :opts="salesVolumeOpts" :chartData="salesVolumeChartData" :canvas2d="true"
|
||||||
:inScrollView="true" canvasId="salesVolumeChart" />
|
:inScrollView="true" canvasId="salesVolumeChart" tooltipFormat="SalesRankingOfProducts"/>
|
||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
@ -191,6 +206,14 @@ export default {
|
|||||||
|
|
||||||
// 时间分布图表配置
|
// 时间分布图表配置
|
||||||
timeDistributionOpts() {
|
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 {
|
return {
|
||||||
color: ['#576EFF', '#52C41A'],
|
color: ['#576EFF', '#52C41A'],
|
||||||
padding: [15, 15, 15, 15],
|
padding: [15, 15, 15, 15],
|
||||||
@ -204,15 +227,12 @@ export default {
|
|||||||
yAxis: {
|
yAxis: {
|
||||||
gridType: 'dash',
|
gridType: 'dash',
|
||||||
dashLength: 2,
|
dashLength: 2,
|
||||||
data: [
|
data: [{
|
||||||
{
|
|
||||||
min: 0
|
|
||||||
},
|
|
||||||
{
|
|
||||||
min: 0,
|
min: 0,
|
||||||
position: 'right'
|
max: yAxisMax,
|
||||||
}
|
// 使用splitNumber控制刻度数量
|
||||||
]
|
splitNumber: 6
|
||||||
|
}]
|
||||||
},
|
},
|
||||||
extra: {
|
extra: {
|
||||||
line: {
|
line: {
|
||||||
@ -224,36 +244,77 @@ export default {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
// 预定义颜色数组
|
||||||
|
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() {
|
regionDistributionData() {
|
||||||
|
// 预处理数据,计算百分比
|
||||||
|
const total = this.regionData.reduce((sum, item) => sum + Number(item.value), 0)
|
||||||
|
|
||||||
return {
|
return {
|
||||||
series: [{
|
series: [{
|
||||||
data: this.regionData.map(item => ({
|
data: this.regionData.map((item, index) => ({
|
||||||
name: item.name,
|
name: item.name,
|
||||||
value: item.value
|
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() {
|
regionDistributionOpts() {
|
||||||
return {
|
return {
|
||||||
padding: [5, 5, 5, 5],
|
padding: [5, 5, 5, 5],
|
||||||
dataLabel: true,
|
dataLabel: false,
|
||||||
legend: {
|
legend: {
|
||||||
show: true
|
show: false // 关闭原生图例,使用自定义图例
|
||||||
},
|
},
|
||||||
extra: {
|
extra: {
|
||||||
pie: {
|
pie: {
|
||||||
activeOpacity: 0.5,
|
activeOpacity: 0.5,
|
||||||
activeRadius: 10,
|
activeRadius: 10,
|
||||||
offsetAngle: 0,
|
offsetAngle: 0,
|
||||||
labelWidth: 15,
|
|
||||||
border: false,
|
border: false,
|
||||||
borderWidth: 3,
|
borderWidth: 2,
|
||||||
borderColor: '#FFFFFF'
|
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}%)`
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@ -262,10 +323,10 @@ export default {
|
|||||||
salesVolumeChartData() {
|
salesVolumeChartData() {
|
||||||
return {
|
return {
|
||||||
categories: this.salesVolumeData.map(item =>
|
categories: this.salesVolumeData.map(item =>
|
||||||
item.name.length > 6 ? item.name.substring(0, 6) + '...' : item.name
|
this.formatXAxisLabel(item.name)
|
||||||
),
|
),
|
||||||
series: [{
|
series: [{
|
||||||
name: '销售量',
|
name: '销量',
|
||||||
data: this.salesVolumeData.map(item => item.value)
|
data: this.salesVolumeData.map(item => item.value)
|
||||||
}]
|
}]
|
||||||
}
|
}
|
||||||
@ -273,35 +334,58 @@ export default {
|
|||||||
|
|
||||||
// 销售量图表配置
|
// 销售量图表配置
|
||||||
salesVolumeOpts() {
|
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 {
|
return {
|
||||||
color: ['#576EFF'],
|
color: ['#576EFF'],
|
||||||
padding: [15, 15, 15, 15],
|
legend: {
|
||||||
|
show: true,
|
||||||
|
color: ['#576EFF']
|
||||||
|
},
|
||||||
|
padding: [20, 15, 35, 15], // 增加底部padding给X轴标签留空间
|
||||||
dataLabel: false,
|
dataLabel: false,
|
||||||
enableScroll: true,
|
enableScroll: false,
|
||||||
xAxis: {
|
xAxis: {
|
||||||
itemCount: 4,
|
itemCount: 5, // 减少显示的标签数量
|
||||||
scrollShow: true,
|
|
||||||
scrollAlign: 'right',
|
scrollAlign: 'right',
|
||||||
scrollColor: '#576EFF',
|
scrollColor: '#576EFF',
|
||||||
scrollBackgroundColor: 'rgba(87, 110, 255, 0.1)',
|
scrollBackgroundColor: 'rgba(87, 110, 255, 0.1)',
|
||||||
scrollWidth: 4,
|
scrollWidth: 4,
|
||||||
scrollHeight: 8
|
scrollHeight: 8,
|
||||||
|
rotate: 30, // 旋转30度避免重叠
|
||||||
|
fontSize: 12, // 适当减小字体
|
||||||
|
margin: 15 // 增加标签与轴线的距离
|
||||||
},
|
},
|
||||||
yAxis: {
|
yAxis: {
|
||||||
gridType: 'dash',
|
gridType: 'dash',
|
||||||
dashLength: 2,
|
dashLength: 2,
|
||||||
data: [{
|
data: [{
|
||||||
min: 0
|
min: 0,
|
||||||
|
max: finalMax,
|
||||||
|
data: yAxisData
|
||||||
}]
|
}]
|
||||||
},
|
},
|
||||||
|
categoriesReal: this.salesVolumeData.map(item => item.name),
|
||||||
extra: {
|
extra: {
|
||||||
column: {
|
column: {
|
||||||
type: 'group',
|
type: 'group',
|
||||||
width: 30,
|
width: 12, // 与ProductReport保持一致
|
||||||
activeBgColor: '#000000',
|
activeBgColor: '#000000',
|
||||||
activeBgOpacity: 0.08,
|
activeBgOpacity: 0.08,
|
||||||
linearType: 'custom',
|
barBorderCircle: true,
|
||||||
barBorderCircle: true
|
linearType: 'none',
|
||||||
|
linearOpacity: 0
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -370,6 +454,44 @@ export default {
|
|||||||
// 拿到商品销售量分析
|
// 拿到商品销售量分析
|
||||||
this.hanleGetShopSalesVolumeData()
|
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() {
|
async hanleGetShopSalesVolumeData() {
|
||||||
const req = {
|
const req = {
|
||||||
@ -472,10 +594,15 @@ export default {
|
|||||||
if (list && list.length > 0) {
|
if (list && list.length > 0) {
|
||||||
list.forEach((item) => {
|
list.forEach((item) => {
|
||||||
let hour = Number(item.name)
|
let hour = Number(item.name)
|
||||||
if (hour % 4 === 0 || hour === 0) {
|
|
||||||
|
// 控制x轴标签显示间隔:每4小时显示一个标签,以及0点
|
||||||
|
if (hour === 0 || hour % 4 === 0) {
|
||||||
categories.push(`${item.name}时`)
|
categories.push(`${item.name}时`)
|
||||||
|
} else {
|
||||||
|
categories.push('') // 其他位置为空字符串,不显示标签
|
||||||
}
|
}
|
||||||
// let hour = Number(item.name) < 10 ? `0${item.name}:00` : `${item.name}:00`
|
|
||||||
|
// 所有数据都保留
|
||||||
transactionData.push(item.data)
|
transactionData.push(item.data)
|
||||||
amountData.push(item.key)
|
amountData.push(item.key)
|
||||||
})
|
})
|
||||||
@ -485,12 +612,8 @@ export default {
|
|||||||
transactionData: transactionData,
|
transactionData: transactionData,
|
||||||
amountData: amountData
|
amountData: amountData
|
||||||
}
|
}
|
||||||
// 最终数据格式 要这样
|
// 24个完整数据点,但x轴只显示7个标签(0点、4点、8点、12点、16点、20点、24点)
|
||||||
// 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) {
|
formatNumber(num) {
|
||||||
return num.toString().replace(/\B(?=(\d{3})+(?!\d))/g, ",")
|
return num.toString().replace(/\B(?=(\d{3})+(?!\d))/g, ",")
|
||||||
@ -756,6 +879,64 @@ export default {
|
|||||||
.bar-chart-container {
|
.bar-chart-container {
|
||||||
width: 100%;
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -11,18 +11,15 @@
|
|||||||
|
|
||||||
<!-- 对比分析切换 -->
|
<!-- 对比分析切换 -->
|
||||||
<view class="analysis-tabs">
|
<view class="analysis-tabs">
|
||||||
<view class="tab-item"
|
<view class="tab-item" :class="{ active: activeAnalysisType === 'compare' }"
|
||||||
:class="{ active: activeAnalysisType === 'compare' }"
|
|
||||||
@click="switchAnalysisType('compare')">
|
@click="switchAnalysisType('compare')">
|
||||||
<text class="tab-text">对比分析</text>
|
<text class="tab-text">对比分析</text>
|
||||||
</view>
|
</view>
|
||||||
<view class="tab-item"
|
<view class="tab-item" :class="{ active: activeAnalysisType === 'yearOverYear' }"
|
||||||
:class="{ active: activeAnalysisType === 'yearOverYear' }"
|
|
||||||
@click="switchAnalysisType('yearOverYear')">
|
@click="switchAnalysisType('yearOverYear')">
|
||||||
<text class="tab-text">同比分析</text>
|
<text class="tab-text">同比分析</text>
|
||||||
</view>
|
</view>
|
||||||
<view class="tab-item"
|
<view class="tab-item" :class="{ active: activeAnalysisType === 'monthOverMonth' }"
|
||||||
:class="{ active: activeAnalysisType === 'monthOverMonth' }"
|
|
||||||
@click="switchAnalysisType('monthOverMonth')">
|
@click="switchAnalysisType('monthOverMonth')">
|
||||||
<text class="tab-text">环比分析</text>
|
<text class="tab-text">环比分析</text>
|
||||||
</view>
|
</view>
|
||||||
@ -64,14 +61,8 @@
|
|||||||
</view>
|
</view>
|
||||||
<view class="chart-content">
|
<view class="chart-content">
|
||||||
<view class="bar-chart-container">
|
<view class="bar-chart-container">
|
||||||
<QiunDataCharts
|
<QiunDataCharts type="column" :opts="regionRevenueChartOpts" :chartData="regionRevenueChartData"
|
||||||
type="column"
|
:canvas2d="true" :inScrollView="true" canvasId="regionRevenueChart" />
|
||||||
:opts="regionRevenueChartOpts"
|
|
||||||
:chartData="regionRevenueChartData"
|
|
||||||
:canvas2d="true"
|
|
||||||
:inScrollView="true"
|
|
||||||
canvasId="regionRevenueChart"
|
|
||||||
/>
|
|
||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
@ -84,21 +75,8 @@
|
|||||||
</view>
|
</view>
|
||||||
<view class="chart-content">
|
<view class="chart-content">
|
||||||
<view class="pie-chart-container">
|
<view class="pie-chart-container">
|
||||||
<QiunDataCharts
|
<QiunDataCharts type="pie" :opts="distributionChartOpts" :chartData="distributionChartData" :canvas2d="true"
|
||||||
type="pie"
|
:inScrollView="true" canvasId="distributionChart" />
|
||||||
:opts="distributionChartOpts"
|
|
||||||
:chartData="distributionChartData"
|
|
||||||
:canvas2d="true"
|
|
||||||
:inScrollView="true"
|
|
||||||
canvasId="distributionChart"
|
|
||||||
/>
|
|
||||||
<view class="pie-legend">
|
|
||||||
<view class="legend-item" v-for="(item, index) in distributionData" :key="index">
|
|
||||||
<view class="legend-color" :style="{ backgroundColor: item.color }"></view>
|
|
||||||
<text class="legend-name">{{ item.name }}</text>
|
|
||||||
<text class="legend-value">{{ item.count }}个({{ item.percentage }}%)</text>
|
|
||||||
</view>
|
|
||||||
</view>
|
|
||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
@ -116,29 +94,40 @@
|
|||||||
<text class="region-name">{{ region.name }}区域</text>
|
<text class="region-name">{{ region.name }}区域</text>
|
||||||
<view class="overall-score">
|
<view class="overall-score">
|
||||||
<text class="score-label">综合评分</text>
|
<text class="score-label">综合评分</text>
|
||||||
<text class="score-value">{{ Math.round((region.traffic + region.vehicles + region.revenue + region.profit + region.growth) / 5) }}</text>
|
<text class="score-value">{{ Math.round((region.traffic + region.vehicles + region.revenue +
|
||||||
|
region.profit + region.growth) / 5) }}</text>
|
||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
<view class="metrics-grid">
|
<view class="metrics-grid">
|
||||||
<view class="metric-box">
|
<view class="metric-box">
|
||||||
<text class="metric-label">客流量</text>
|
<text class="metric-label">客流量</text>
|
||||||
<text :class="['metric-score', region.traffic >= 85 ? 'high' : region.traffic >= 70 ? 'medium-high' : region.traffic >= 55 ? 'medium' : region.traffic >= 40 ? 'medium-low' : 'low']">{{ region.traffic }}</text>
|
<text
|
||||||
|
:class="['metric-score', region.traffic >= 85 ? 'high' : region.traffic >= 70 ? 'medium-high' : region.traffic >= 55 ? 'medium' : region.traffic >= 40 ? 'medium-low' : 'low']">{{
|
||||||
|
region.traffic }}</text>
|
||||||
</view>
|
</view>
|
||||||
<view class="metric-box">
|
<view class="metric-box">
|
||||||
<text class="metric-label">车流量</text>
|
<text class="metric-label">车流量</text>
|
||||||
<text :class="['metric-score', region.vehicles >= 85 ? 'high' : region.vehicles >= 70 ? 'medium-high' : region.vehicles >= 55 ? 'medium' : region.vehicles >= 40 ? 'medium-low' : 'low']">{{ region.vehicles }}</text>
|
<text
|
||||||
|
:class="['metric-score', region.vehicles >= 85 ? 'high' : region.vehicles >= 70 ? 'medium-high' : region.vehicles >= 55 ? 'medium' : region.vehicles >= 40 ? 'medium-low' : 'low']">{{
|
||||||
|
region.vehicles }}</text>
|
||||||
</view>
|
</view>
|
||||||
<view class="metric-box">
|
<view class="metric-box">
|
||||||
<text class="metric-label">营业额</text>
|
<text class="metric-label">营业额</text>
|
||||||
<text :class="['metric-score', region.revenue >= 85 ? 'high' : region.revenue >= 70 ? 'medium-high' : region.revenue >= 55 ? 'medium' : region.revenue >= 40 ? 'medium-low' : 'low']">{{ region.revenue }}</text>
|
<text
|
||||||
|
:class="['metric-score', region.revenue >= 85 ? 'high' : region.revenue >= 70 ? 'medium-high' : region.revenue >= 55 ? 'medium' : region.revenue >= 40 ? 'medium-low' : 'low']">{{
|
||||||
|
region.revenue }}</text>
|
||||||
</view>
|
</view>
|
||||||
<view class="metric-box">
|
<view class="metric-box">
|
||||||
<text class="metric-label">利润率</text>
|
<text class="metric-label">利润率</text>
|
||||||
<text :class="['metric-score', region.profit >= 85 ? 'high' : region.profit >= 70 ? 'medium-high' : region.profit >= 55 ? 'medium' : region.profit >= 40 ? 'medium-low' : 'low']">{{ region.profit }}</text>
|
<text
|
||||||
|
:class="['metric-score', region.profit >= 85 ? 'high' : region.profit >= 70 ? 'medium-high' : region.profit >= 55 ? 'medium' : region.profit >= 40 ? 'medium-low' : 'low']">{{
|
||||||
|
region.profit }}</text>
|
||||||
</view>
|
</view>
|
||||||
<view class="metric-box">
|
<view class="metric-box">
|
||||||
<text class="metric-label">增长率</text>
|
<text class="metric-label">增长率</text>
|
||||||
<text :class="['metric-score', region.growth >= 85 ? 'high' : region.growth >= 70 ? 'medium-high' : region.growth >= 55 ? 'medium' : region.growth >= 40 ? 'medium-low' : 'low']">{{ region.growth }}</text>
|
<text
|
||||||
|
:class="['metric-score', region.growth >= 85 ? 'high' : region.growth >= 70 ? 'medium-high' : region.growth >= 55 ? 'medium' : region.growth >= 40 ? 'medium-low' : 'low']">{{
|
||||||
|
region.growth }}</text>
|
||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
@ -178,14 +167,8 @@
|
|||||||
</view>
|
</view>
|
||||||
<view class="chart-content">
|
<view class="chart-content">
|
||||||
<view class="stacked-bar-container">
|
<view class="stacked-bar-container">
|
||||||
<QiunDataCharts
|
<QiunDataCharts type="column" :opts="typeAnalysisChartOpts" :chartData="typeAnalysisChartData"
|
||||||
type="column"
|
:canvas2d="true" :inScrollView="true" canvasId="typeAnalysisChart" />
|
||||||
:opts="typeAnalysisChartOpts"
|
|
||||||
:chartData="typeAnalysisChartData"
|
|
||||||
:canvas2d="true"
|
|
||||||
:inScrollView="true"
|
|
||||||
canvasId="typeAnalysisChart"
|
|
||||||
/>
|
|
||||||
</view>
|
</view>
|
||||||
<view class="type-legend">
|
<view class="type-legend">
|
||||||
<view class="legend-item">
|
<view class="legend-item">
|
||||||
@ -220,14 +203,12 @@
|
|||||||
<text class="region-name">{{ item.regionName }}</text>
|
<text class="region-name">{{ item.regionName }}</text>
|
||||||
</view>
|
</view>
|
||||||
<view class="area-badges">
|
<view class="area-badges">
|
||||||
<text class="type-badge"
|
<text class="type-badge" :class="item.serviceType === 'A类' ? 'type-a' :
|
||||||
:class="item.serviceType === 'A类' ? 'type-a' :
|
|
||||||
item.serviceType === 'B类' ? 'type-b' :
|
item.serviceType === 'B类' ? 'type-b' :
|
||||||
item.serviceType === 'C类' ? 'type-c' : ''">
|
item.serviceType === 'C类' ? 'type-c' : ''">
|
||||||
{{ item.serviceType }}
|
{{ item.serviceType }}
|
||||||
</text>
|
</text>
|
||||||
<text class="status-badge"
|
<text class="status-badge" :class="item.operationalStatus === '正常运营' ? 'success' :
|
||||||
:class="item.operationalStatus === '正常运营' ? 'success' :
|
|
||||||
item.operationalStatus === '升级改造' ? 'warning' :
|
item.operationalStatus === '升级改造' ? 'warning' :
|
||||||
item.operationalStatus === '暂停营业' ? 'error' : 'info'">
|
item.operationalStatus === '暂停营业' ? 'error' : 'info'">
|
||||||
{{ item.operationalStatus }}
|
{{ item.operationalStatus }}
|
||||||
@ -246,8 +227,7 @@
|
|||||||
</view>
|
</view>
|
||||||
<view class="metric-item">
|
<view class="metric-item">
|
||||||
<text class="metric-label">利润率</text>
|
<text class="metric-label">利润率</text>
|
||||||
<text class="metric-value profit-rate"
|
<text class="metric-value profit-rate" :class="item.profitRate >= 15 ? 'high' :
|
||||||
:class="item.profitRate >= 15 ? 'high' :
|
|
||||||
item.profitRate >= 10 ? 'positive' :
|
item.profitRate >= 10 ? 'positive' :
|
||||||
item.profitRate >= 5 ? 'neutral' : 'negative'">
|
item.profitRate >= 5 ? 'neutral' : 'negative'">
|
||||||
{{ formatProfit(item.profitRate) }}
|
{{ formatProfit(item.profitRate) }}
|
||||||
@ -263,6 +243,7 @@
|
|||||||
|
|
||||||
<script>
|
<script>
|
||||||
import QiunDataCharts from './qiun-data-charts/components/qiun-data-charts/qiun-data-charts.vue'
|
import QiunDataCharts from './qiun-data-charts/components/qiun-data-charts/qiun-data-charts.vue'
|
||||||
|
import request from "@/util/index.js";
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
components: {
|
components: {
|
||||||
@ -276,17 +257,8 @@ export default {
|
|||||||
regionCount: 16,
|
regionCount: 16,
|
||||||
serviceAreaCount: 42,
|
serviceAreaCount: 42,
|
||||||
avgRevenue: 3425,
|
avgRevenue: 3425,
|
||||||
|
|
||||||
// 区域分布数据
|
// 区域分布数据
|
||||||
distributionData: [
|
distributionData: [],
|
||||||
{ name: '昆明', count: 8, percentage: 19, color: '#576EFF' },
|
|
||||||
{ name: '曲靖', count: 6, percentage: 14, color: '#52C41A' },
|
|
||||||
{ name: '大理', count: 7, percentage: 17, color: '#FAAD14' },
|
|
||||||
{ name: '红河', count: 5, percentage: 12, color: '#FF7875' },
|
|
||||||
{ name: '昭通', count: 4, percentage: 10, color: '#B37FEB' },
|
|
||||||
{ name: '其他', count: 12, percentage: 28, color: '#666666' }
|
|
||||||
],
|
|
||||||
|
|
||||||
// 区域营业额数据
|
// 区域营业额数据
|
||||||
regionData: [
|
regionData: [
|
||||||
{
|
{
|
||||||
@ -325,7 +297,6 @@ export default {
|
|||||||
serviceAreas: 4
|
serviceAreas: 4
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
|
|
||||||
// 热力图数据
|
// 热力图数据
|
||||||
heatmapData: [
|
heatmapData: [
|
||||||
{
|
{
|
||||||
@ -369,7 +340,6 @@ export default {
|
|||||||
growth: 75
|
growth: 75
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
|
|
||||||
// 服务区类型数据
|
// 服务区类型数据
|
||||||
typeData: [
|
typeData: [
|
||||||
{
|
{
|
||||||
@ -403,7 +373,6 @@ export default {
|
|||||||
typeC: 1
|
typeC: 1
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
|
|
||||||
// 详细数据
|
// 详细数据
|
||||||
detailData: [
|
detailData: [
|
||||||
{
|
{
|
||||||
@ -515,7 +484,7 @@ export default {
|
|||||||
series: [{
|
series: [{
|
||||||
data: this.distributionData.map(item => ({
|
data: this.distributionData.map(item => ({
|
||||||
name: item.name,
|
name: item.name,
|
||||||
value: item.count
|
value: item.value
|
||||||
}))
|
}))
|
||||||
}]
|
}]
|
||||||
}
|
}
|
||||||
@ -524,11 +493,10 @@ export default {
|
|||||||
// 区域分布饼图配置
|
// 区域分布饼图配置
|
||||||
distributionChartOpts() {
|
distributionChartOpts() {
|
||||||
return {
|
return {
|
||||||
color: this.distributionData.map(item => item.color),
|
|
||||||
padding: [5, 5, 5, 5],
|
padding: [5, 5, 5, 5],
|
||||||
dataLabel: true,
|
dataLabel: true,
|
||||||
legend: {
|
legend: {
|
||||||
show: false
|
show: true
|
||||||
},
|
},
|
||||||
extra: {
|
extra: {
|
||||||
pie: {
|
pie: {
|
||||||
@ -599,12 +567,46 @@ export default {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
onReady() {
|
||||||
|
// 获取整个组件的数据
|
||||||
|
this.handleGetAllData()
|
||||||
|
},
|
||||||
methods: {
|
methods: {
|
||||||
|
// 获取整个组件的数据
|
||||||
|
handleGetAllData() {
|
||||||
|
// 获取服务区区域分布数据
|
||||||
|
this.handleGetServiceAreaData()
|
||||||
|
|
||||||
|
},
|
||||||
|
// 获取服务区区域分布数据
|
||||||
|
async handleGetServiceAreaData() {
|
||||||
|
const req = {
|
||||||
|
ProvinceCode: "530000",
|
||||||
|
StatisticsType: 1000,
|
||||||
|
ShowWholePower: true
|
||||||
|
}
|
||||||
|
|
||||||
|
const data = await request.$apiGet(
|
||||||
|
"EShangApiMain/BaseInfo/GetServerpartTree",
|
||||||
|
req
|
||||||
|
);
|
||||||
|
let list = data.Result_Data.List
|
||||||
|
console.log('服务区区域分布数据', list);
|
||||||
|
let res = []
|
||||||
|
if (list && list.length > 0) {
|
||||||
|
list.forEach((item) => {
|
||||||
|
if (item.children && item.children.length > 0) {
|
||||||
|
res.push({ name: item.label, value: item.children && item.children.length > 0 ? item.children.length : 0 })
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log('dadasd', res);
|
||||||
|
this.distributionData = res
|
||||||
|
},
|
||||||
switchAnalysisType(type) {
|
switchAnalysisType(type) {
|
||||||
this.activeAnalysisType = type;
|
this.activeAnalysisType = type;
|
||||||
},
|
},
|
||||||
|
|
||||||
getAnalysisTypeName() {
|
getAnalysisTypeName() {
|
||||||
const names = {
|
const names = {
|
||||||
compare: '对比',
|
compare: '对比',
|
||||||
@ -767,17 +769,41 @@ export default {
|
|||||||
|
|
||||||
&.regions {
|
&.regions {
|
||||||
background: linear-gradient(135deg, #576EFF, #7C8FFF);
|
background: linear-gradient(135deg, #576EFF, #7C8FFF);
|
||||||
&::after { content: '🗺️'; position: absolute; top: 50%; left: 50%; transform: translate(-50%, -50%); font-size: 24rpx; }
|
|
||||||
|
&::after {
|
||||||
|
content: '🗺️';
|
||||||
|
position: absolute;
|
||||||
|
top: 50%;
|
||||||
|
left: 50%;
|
||||||
|
transform: translate(-50%, -50%);
|
||||||
|
font-size: 24rpx;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
&.service-areas {
|
&.service-areas {
|
||||||
background: linear-gradient(135deg, #52C41A, #73D13D);
|
background: linear-gradient(135deg, #52C41A, #73D13D);
|
||||||
&::after { content: '🏪'; position: absolute; top: 50%; left: 50%; transform: translate(-50%, -50%); font-size: 24rpx; }
|
|
||||||
|
&::after {
|
||||||
|
content: '🏪';
|
||||||
|
position: absolute;
|
||||||
|
top: 50%;
|
||||||
|
left: 50%;
|
||||||
|
transform: translate(-50%, -50%);
|
||||||
|
font-size: 24rpx;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
&.avg-revenue {
|
&.avg-revenue {
|
||||||
background: linear-gradient(135deg, #FAAD14, #FFC53D);
|
background: linear-gradient(135deg, #FAAD14, #FFC53D);
|
||||||
&::after { content: '📊'; position: absolute; top: 50%; left: 50%; transform: translate(-50%, -50%); font-size: 24rpx; }
|
|
||||||
|
&::after {
|
||||||
|
content: '📊';
|
||||||
|
position: absolute;
|
||||||
|
top: 50%;
|
||||||
|
left: 50%;
|
||||||
|
transform: translate(-50%, -50%);
|
||||||
|
font-size: 24rpx;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -903,9 +929,17 @@ export default {
|
|||||||
height: 12rpx;
|
height: 12rpx;
|
||||||
border-radius: 2rpx;
|
border-radius: 2rpx;
|
||||||
|
|
||||||
&.type-a { background: #576EFF; }
|
&.type-a {
|
||||||
&.type-b { background: #52C41A; }
|
background: #576EFF;
|
||||||
&.type-c { background: #FAAD14; }
|
}
|
||||||
|
|
||||||
|
&.type-b {
|
||||||
|
background: #52C41A;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.type-c {
|
||||||
|
background: #FAAD14;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.legend-text {
|
.legend-text {
|
||||||
|
|||||||
@ -260,7 +260,15 @@ export default {
|
|||||||
},
|
},
|
||||||
xAxis: {
|
xAxis: {
|
||||||
disableGrid: true,
|
disableGrid: true,
|
||||||
type: 'category'
|
type: 'category',
|
||||||
|
rotateLabel: false, // 标签不旋转
|
||||||
|
fontSize: 14,
|
||||||
|
fontColor: '#666',
|
||||||
|
// 控制标签显示间隔,让x轴只显示部分标签
|
||||||
|
boundaryGap: 'center',
|
||||||
|
axisLine: {
|
||||||
|
show: true
|
||||||
|
}
|
||||||
},
|
},
|
||||||
yAxis: {
|
yAxis: {
|
||||||
gridType: 'dash',
|
gridType: 'dash',
|
||||||
@ -308,11 +316,17 @@ export default {
|
|||||||
let orderData = [] // 销量
|
let orderData = [] // 销量
|
||||||
let amountData = [] // 销售额
|
let amountData = [] // 销售额
|
||||||
if (list && list.length > 0) {
|
if (list && list.length > 0) {
|
||||||
list.forEach((item, index) => {
|
list.forEach((item) => {
|
||||||
const day = parseInt(item.StatisticsDate.split('/')[2])
|
const day = parseInt(item.StatisticsDate.split('/')[2])
|
||||||
if (day % 5 === 0 || day === 1) {
|
|
||||||
|
// 控制x轴标签显示间隔:每5天显示一个标签,以及第1天
|
||||||
|
if (day === 1 || day % 5 === 0) {
|
||||||
categories.push(day + '日')
|
categories.push(day + '日')
|
||||||
|
} else {
|
||||||
|
categories.push('') // 其他位置为空字符串,不显示标签
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 所有数据都保留
|
||||||
orderData.push(item.TicketCount)
|
orderData.push(item.TicketCount)
|
||||||
amountData.push(item.SellAmount)
|
amountData.push(item.SellAmount)
|
||||||
})
|
})
|
||||||
@ -322,12 +336,8 @@ export default {
|
|||||||
orderData: orderData,
|
orderData: orderData,
|
||||||
amountData: amountData
|
amountData: amountData
|
||||||
}
|
}
|
||||||
// 要的结构
|
// 30个完整数据点,但x轴只显示约6个标签,避免标签重叠
|
||||||
// trendData: {
|
// 所有数据点都支持点击交互
|
||||||
// categories: ['1日', '5日', '10日', '15日', '20日', '25日', '30日'],
|
|
||||||
// orderData: [1234, 1567, 1890, 2345, 1789, 2123, 2456],
|
|
||||||
// amountData: [234567, 345678, 456789, 567890, 478901, 589012, 678901]
|
|
||||||
// }
|
|
||||||
|
|
||||||
},
|
},
|
||||||
formatNumber(num) {
|
formatNumber(num) {
|
||||||
|
|||||||
@ -95,16 +95,24 @@ const cfu = {
|
|||||||
"option": {},
|
"option": {},
|
||||||
//下面是自定义format配置,因除H5端外的其他端无法通过props传递函数,只能通过此属性对应下标的方式来替换
|
//下面是自定义format配置,因除H5端外的其他端无法通过props传递函数,只能通过此属性对应下标的方式来替换
|
||||||
"formatter": {
|
"formatter": {
|
||||||
"ShopTypeNumberRate": function (item, category, index, opts) {
|
"regionDistributionData": function (item, category, index, opts) {
|
||||||
console.log('', item);
|
console.log('', item);
|
||||||
console.log('', category);
|
console.log('', category);
|
||||||
console.log('', index);
|
console.log('', index);
|
||||||
console.log('', opts);
|
console.log('', opts);
|
||||||
|
return `${item.name}:${item.data.toLocaleString()}元`
|
||||||
|
},
|
||||||
|
"timeDistributionChartData": function (item, category, index, opts) {
|
||||||
|
|
||||||
|
return `${index}时:${item.name} ${item.data}${item.name === '客单量' ? '单' : '元'}`
|
||||||
|
},
|
||||||
|
"ShopTypeNumberRate": function (item, category, index, opts) {
|
||||||
|
|
||||||
return `${item.name}:${item.data}种`
|
return `${item.name}:${item.data}种`
|
||||||
},
|
},
|
||||||
"tradingTrendData": function (item, category, index, opts) {
|
"tradingTrendData": function (item, category, index, opts) {
|
||||||
|
|
||||||
return `${category}:${item.name} ${item.data}${item.name === '订单量' ? '笔' : '元'}`
|
return `${index + 1}日:${item.name} ${item.data}${item.name === '订单量' ? '笔' : '元'}`
|
||||||
},
|
},
|
||||||
"ShopTypeDistribution": function (item, category, index, opts) {
|
"ShopTypeDistribution": function (item, category, index, opts) {
|
||||||
return `${item.name}:${item.data}种`
|
return `${item.name}:${item.data}种`
|
||||||
|
|||||||
@ -3,7 +3,7 @@
|
|||||||
<scroll-view scroll-y @scroll="handleScroll" class="digital-dashboard" :scroll-into-view="scrollIntoView"
|
<scroll-view scroll-y @scroll="handleScroll" class="digital-dashboard" :scroll-into-view="scrollIntoView"
|
||||||
:scroll-with-animation="true">
|
:scroll-with-animation="true">
|
||||||
<!-- Tab切换区域 -->
|
<!-- Tab切换区域 -->
|
||||||
<view class="tab-container">
|
<scroll-view scroll-x class="tab-container">
|
||||||
<view class="tab-list">
|
<view class="tab-list">
|
||||||
<view v-for="(tab, index) in tabList" :key="index" :id="`tab-${index}`" class="tab-item"
|
<view v-for="(tab, index) in tabList" :key="index" :id="`tab-${index}`" class="tab-item"
|
||||||
:class="{ active: activeTab === index }" @click="switchTab(index)">
|
:class="{ active: activeTab === index }" @click="switchTab(index)">
|
||||||
@ -11,7 +11,7 @@
|
|||||||
<view class="tab-indicator" v-if="activeTab === index"></view>
|
<view class="tab-indicator" v-if="activeTab === index"></view>
|
||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
</view>
|
</scroll-view>
|
||||||
|
|
||||||
<!-- 右侧悬浮导航栏 -->
|
<!-- 右侧悬浮导航栏 -->
|
||||||
<view class="side-navigation" v-if="currentNavItems && currentNavItems.length > 0"
|
<view class="side-navigation" v-if="currentNavItems && currentNavItems.length > 0"
|
||||||
@ -183,7 +183,7 @@ export default {
|
|||||||
},
|
},
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
activeTab:2,
|
activeTab: 0,
|
||||||
tabList: [
|
tabList: [
|
||||||
{ name: '经营数据分析', key: 'business' },
|
{ name: '经营数据分析', key: 'business' },
|
||||||
{ name: '供应链数据分析', key: 'supply' },
|
{ name: '供应链数据分析', key: 'supply' },
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user