ylj20011123 c78652a8d1 update
2025-10-23 18:35:54 +08:00

851 lines
22 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="product-report">
<!-- 报表标题 -->
<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 sales"></view>
<text class="metric-title">总销量</text>
<view class="metric-value-container">
<text class="metric-value">{{ formatNumber(totalSales) }}</text>
<text class="metric-unit"></text>
</view>
</view>
<view class="metric-card">
<view class="metric-icon revenue"></view>
<text class="metric-title">总销售额</text>
<view class="metric-value-container">
<text class="metric-value">¥{{ formatMoney(totalRevenue) }}</text>
<text class="metric-unit"></text>
</view>
</view>
<view class="metric-card">
<view class="metric-icon types"></view>
<text class="metric-title">商品类型</text>
<view class="metric-value-container">
<text class="metric-value">{{ productTypes }}</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">各类商品销量占比</text>
</view>
<view class="chart-content">
<view class="pie-chart-container">
<QiunDataCharts type="pie" :opts="pieChartOpts" :chartData="pieChartData" :canvas2d="true"
:inScrollView="true" canvasId="productTypePieChart" />
<view class="pie-legend">
<view class="legend-item" v-for="(item, index) in productTypeData" :key="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>
</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="barChartOpts" :chartData="barChartData" :animation="false"
:canvas2d="true" :inScrollView="true" tooltipFormat="SalesRankingOfProducts" />
</view>
</view>
</view>
<!-- 销售趋势图 -->
<view class="chart-card">
<view class="chart-header">
<text class="chart-title">商品销售趋势</text>
<text class="chart-subtitle">最近30天销量与销售额变化</text>
</view>
<view class="chart-content">
<view class="line-chart-container">
<QiunDataCharts type="line" :opts="lineChartOpts" :chartData="lineChartData" :canvas2d="true"
:inScrollView="true" canvasId="productTrendChart" :pageScrollTop="pageScrollTop" />
<view class="chart-legend">
<view class="legend-item">
<view class="legend-dot sales"></view>
<text class="legend-text">销量</text>
</view>
<view class="legend-item">
<view class="legend-dot revenue"></view>
<text class="legend-text">销售额(÷100)</text>
</view>
</view>
</view>
</view>
</view>
<!-- 商品详细数据 -->
<view class="chart-card">
<view class="chart-header">
<text class="chart-title">商品详细数据</text>
<view class="table-actions">
</view>
</view>
<view class="product-cards">
<view class="product-card" v-for="(item, index) in productList" :key="index">
<view class="product-header">
<view class="product-type-tag" :style="{
backgroundColor: item.type === '保健品' ? '#576EFF' :
item.type === '茶叶' ? '#52C41A' :
item.type === '食品' ? '#FAAD14' :
item.type === '饮品' ? '#FF7875' :
item.type === '工艺品' ? '#B37FEB' : '#666666'
}">
{{ item.type }}
</view>
<text class="product-code">{{ item.code }}</text>
</view>
<view class="product-body">
<text class="product-name">{{ item.name }}</text>
<view class="product-metrics">
<view class="metric-item">
<text class="metric-label">销量</text>
<text class="metric-value">{{ formatNumber(item.sales) }}</text>
</view>
<view class="metric-divider"></view>
<view class="metric-item">
<text class="metric-label">销售额</text>
<text class="metric-value">¥{{ formatMoney(item.revenue) }}</text>
</view>
</view>
</view>
</view>
</view>
</view>
</view>
</template>
<script>
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月',
totalSales: 15678,
totalRevenue: 2345678,
productTypes: 12,
// 商品类型分布数据
productTypeData: [
{ name: '保健品', value: 35, percentage: 35, color: '#576EFF' },
{ name: '茶叶', value: 28, percentage: 28, color: '#52C41A' },
{ name: '食品', value: 20, percentage: 20, color: '#FAAD14' },
{ name: '饮品', value: 12, percentage: 12, color: '#FF7875' },
{ name: '其他', value: 5, percentage: 5, color: '#B37FEB' }
],
// 销量排行榜数据
salesRankingData: [],
// 销售趋势数据
trendData: {
categories: ['1日', '5日', '10日', '15日', '20日', '25日', '30日'],
salesData: [1200, 1400, 1600, 1800, 2000, 2200, 2400],
revenueData: [100, 200, 300, 400, 500, 600, 700]
},
productList: [
{ type: '保健品', code: 'YN001', name: '云南白药牙膏', sales: 3456, revenue: 456789 },
{ type: '茶叶', code: 'PU002', name: '普洱茶饼', sales: 2890, revenue: 567890 },
{ type: '食品', code: 'FH003', name: '鲜花饼', sales: 2345, revenue: 345678 },
{ type: '饮品', code: 'KF004', name: '云南咖啡', sales: 1876, revenue: 234567 },
{ type: '保健品', code: 'SQ005', name: '三七粉', sales: 1567, revenue: 456789 },
{ type: '工艺品', code: 'YS006', name: '玉石手镯', sales: 1234, revenue: 345678 }
],
pageScrollTop: 0
}
},
computed: {
// 饼图数据
pieChartData() {
return {
series: [{
data: this.productTypeData.map(item => ({
name: item.name,
value: item.value
}))
}]
}
},
// 饼图配置
pieChartOpts() {
return {
color: this.productTypeData.map(item => item.color),
padding: [5, 5, 5, 5],
dataLabel: true,
legend: {
show: false
},
extra: {
pie: {
activeOpacity: 0.5,
activeRadius: 10,
offsetAngle: 0,
labelWidth: 15,
border: false,
borderWidth: 3,
borderColor: '#FFFFFF'
}
}
}
},
// 柱状图数据
barChartData() {
return {
categories: this.salesRankingData.map(item =>
this.formatXAxisLabel(item.name)
),
series: [{
name: '销量',
data: this.salesRankingData.map(item => item.sales)
}]
}
},
// 柱状图配置
barChartOpts() {
// 计算最大值并生成6个刻度每个刻度都是100的倍数
const maxSales = Math.max(...this.salesRankingData.map(item => item.sales));
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
}]
},
extra: {
column: {
type: 'group',
width: 12,
activeBgColor: '#000000',
activeBgOpacity: 0.08,
barBorderCircle: true,
linearType: 'none',
linearOpacity: 0,
}
}
}
},
// 折线图数据
lineChartData() {
return {
categories: this.trendData.categories,
series: [
{
name: '销量',
data: this.trendData.salesData
},
{
name: '销售额(百元)',
data: this.trendData.revenueData.map(v => v / 100)
}
]
}
},
// 折线图配置
lineChartOpts() {
return {
color: ['#576EFF', '#52C41A'],
padding: [15, 15, 15, 15],
dataLabel: false,
legend: {
show: false
},
xAxis: {
disableGrid: true,
itemCount: 7
},
yAxis: {
gridType: 'dash',
dashLength: 2,
data: [{
min: 0
}]
},
extra: {
line: {
type: 'curve',
width: 3,
activeType: 'hollow'
}
}
}
}
},
onReady() {
// 获取整个组件的数据
this.handleGetAllData()
},
methods: {
SalesRankingOfProducts() {
console.log('1111');
return item.name + ':' + item.data + '件'
},
// 获取整个组件的数据
handleGetAllData() {
// 拿到商品销售排行榜
this.handleGetTopSalesRank()
},
// 拿到商品销售排行榜
async handleGetTopSalesRank() {
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,
sales: item.SELLCOUNT,
})
})
}
this.salesRankingData = res
},
// 格式化X轴标签文字
formatXAxisLabel(name) {
if (!name) return '';
// 获取屏幕宽度决定显示长度
const screenWidth = this.getScreenWidth();
let maxLength = 4; // 默认长度
if (screenWidth > 750) {
maxLength = 6; // 大屏显示更长
} else if (screenWidth < 375) {
maxLength = 3; // 小屏显示更短
}
// 如果名称超过最大长度,截取并添加省略号
return name.length > maxLength ? name.substring(0, maxLength) + '...' : name;
},
// 获取屏幕宽度
getScreenWidth() {
try {
return uni.getSystemInfoSync().windowWidth || 375;
} catch (e) {
return 375;
}
},
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;
@gold: #FFD700;
@silver: #C0C0C0;
@bronze: #CD7F32;
@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);
.product-report {
.report-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 32rpx;
padding: 0 8rpx;
.report-title {
font-size: 30rpx;
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: 32rpx;
.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;
&.sales {
background: linear-gradient(135deg, #52c41a, #73d13d);
&::after {
content: '📦';
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
font-size: 24rpx;
}
}
&.revenue {
background: linear-gradient(135deg, #faad14, #ffc53d);
&::after {
content: '💰';
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
font-size: 24rpx;
}
}
&.types {
background: linear-gradient(135deg, #1890ff, #40a9ff);
&::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: 28rpx;
font-weight: 600;
color: @text-primary;
font-family: 'DINAlternate-Bold', sans-serif;
line-height: 1;
}
.metric-unit {
font-size: 24rpx;
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 {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 20rpx;
.chart-title {
font-size: 28rpx;
font-weight: 600;
color: @text-primary;
}
.chart-subtitle {
font-size: 24rpx;
color: @text-light;
margin-top: 4rpx;
}
.table-actions {
display: flex;
gap: 16rpx;
.action-btn {
font-size: 24rpx;
color: @primary-color;
padding: 8rpx 16rpx;
border: 1rpx solid @primary-color;
border-radius: 8rpx;
}
}
}
.chart-placeholder {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
height: 200rpx;
background: linear-gradient(135deg, #f5f7fa 0%, #c3cfe2 100%);
border-radius: 12rpx;
margin-bottom: 16rpx;
.placeholder-text {
font-size: 32rpx;
color: @text-secondary;
margin-bottom: 8rpx;
}
.placeholder-desc {
font-size: 24rpx;
color: @text-light;
}
}
.ranking-list {
.ranking-item {
display: flex;
align-items: center;
padding: 16rpx 0;
border-bottom: 1rpx solid #f0f0f0;
&:last-child {
border-bottom: none;
}
.ranking-number {
width: 48rpx;
height: 48rpx;
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
font-size: 24rpx;
font-weight: 600;
color: white;
background: @text-light;
margin-right: 16rpx;
&.rank-gold {
background: @gold;
color: #333;
}
&.rank-silver {
background: @silver;
color: #333;
}
&.rank-bronze {
background: @bronze;
color: white;
}
}
.ranking-info {
flex: 1;
.product-name {
font-size: 28rpx;
color: @text-primary;
margin-bottom: 4rpx;
}
.product-code {
font-size: 24rpx;
color: @text-light;
}
}
.ranking-data {
text-align: right;
.sales-number {
font-size: 24rpx;
color: @text-primary;
font-weight: 500;
display: block;
}
.sales-amount {
font-size: 22rpx;
color: @primary-color;
font-weight: 600;
}
}
}
}
.chart-content {
.pie-chart-container {
display: flex;
flex-direction: column;
align-items: center;
.pie-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%;
}
.line-chart-container {
.chart-legend {
display: flex;
justify-content: center;
gap: 32rpx;
margin-top: 16rpx;
.legend-item {
display: flex;
align-items: center;
gap: 8rpx;
.legend-dot {
width: 12rpx;
height: 12rpx;
border-radius: 50%;
&.sales {
background: #576EFF;
}
&.revenue {
background: #52C41A;
}
}
.legend-text {
font-size: 22rpx;
color: @text-secondary;
}
}
}
}
}
.product-cards {
display: flex;
flex-direction: column;
gap: 16rpx;
.product-card {
background: #fafafa;
border-radius: 12rpx;
padding: 20rpx;
border: 1rpx solid #f0f0f0;
.product-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 12rpx;
.product-type-tag {
padding: 4rpx 12rpx;
border-radius: 12rpx;
font-size: 20rpx;
color: white;
font-weight: 500;
}
.product-code {
font-size: 20rpx;
color: @text-light;
font-family: monospace;
}
}
.product-body {
.product-name {
font-size: 24rpx;
color: @text-primary;
font-weight: 600;
margin-bottom: 12rpx;
display: block;
}
.product-metrics {
display: flex;
align-items: center;
.metric-item {
flex: 1;
text-align: center;
.metric-label {
font-size: 24rpx;
color: @text-secondary;
display: block;
margin-bottom: 4rpx;
}
.metric-value {
font-size: 24rpx;
color: @text-primary;
font-weight: 600;
display: block;
}
}
.metric-divider {
width: 1rpx;
height: 40rpx;
background: #e0e0e0;
margin: 0 16rpx;
}
}
}
}
}
}
}
</style>