ccy_DIB/pages/DigitalIntelligenceDashboard/oldcomponents/InventoryFreshnessAnalysis.vue
ylj20011123 2900c384eb update
2025-10-29 10:02:45 +08:00

665 lines
17 KiB
Vue
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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="inventory-freshness-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 fresh"></view>
<text class="metric-title">新鲜库存占比</text>
<view class="metric-value-container">
<text class="metric-value">{{ freshPercentage }}%</text>
<text class="metric-unit">良好</text>
</view>
</view>
<view class="metric-card">
<view class="metric-icon warning"></view>
<text class="metric-title">临期商品数量</text>
<view class="metric-value-container">
<text class="metric-value">{{ formatNumber(nearExpiryCount) }}</text>
<text class="metric-unit"></text>
</view>
</view>
<view class="metric-card">
<view class="metric-icon expired"></view>
<text class="metric-title">过期商品数量</text>
<view class="metric-value-container">
<text class="metric-value">{{ formatNumber(expiredCount) }}</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="freshnessPieChart"
/>
<view class="pie-legend">
<view class="legend-item" v-for="(item, index) in freshnessData" :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">最近6个月库存周期变化趋势</text>
</view>
<view class="chart-content">
<view class="line-chart-container">
<QiunDataCharts
type="line"
:opts="lineChartOpts"
:chartData="lineChartData"
:canvas2d="true"
:inScrollView="true"
canvasId="inventoryCycleChart"
/>
<view class="chart-legend">
<view class="legend-item">
<view class="legend-dot cycle"></view>
<text class="legend-text">平均库存周期</text>
</view>
<view class="legend-item">
<view class="legend-dot freshness"></view>
<text class="legend-text">新鲜度指数</text>
</view>
</view>
</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="bar-chart-container">
<QiunDataCharts
type="column"
:opts="barChartOpts"
:chartData="barChartData"
:canvas2d="true"
:inScrollView="true"
canvasId="expiryDistributionChart"
/>
</view>
</view>
</view>
<!-- 临期商品预警列表 -->
<view class="chart-card">
<view class="chart-header">
<text class="chart-title">临期商品预警</text>
<text class="chart-subtitle">即将到期商品清单</text>
</view>
<view class="warning-list">
<view class="warning-item" v-for="(item, index) in nearExpiryList" :key="index" :class="[getWarningLevel(item.daysLeft)]">
<view class="warning-icon">{{ getWarningIcon(item.daysLeft) }}</view>
<view class="warning-content">
<text class="product-name">{{ item.name }}</text>
<text class="product-code">{{ item.code }}</text>
</view>
<view class="warning-info">
<text class="expiry-date">到期{{ item.expiryDate }}</text>
<text class="days-left">{{ item.daysLeft }}</text>
</view>
<view class="warning-action">
<text class="action-btn">处理</text>
</view>
</view>
</view>
</view>
</view>
</template>
<script>
import QiunDataCharts from './qiun-data-charts/components/qiun-data-charts/qiun-data-charts.vue'
export default {
components: {
QiunDataCharts
},
data() {
return {
analysisPeriod: '2024年10月',
freshPercentage: 87.5,
nearExpiryCount: 1234,
expiredCount: 56,
// 新鲜度数据
freshnessData: [
{ name: '新鲜(>30天)', value: 424856, percentage: 87.5, color: '#52C41A' },
{ name: '临期(7-30天)', value: 42345, percentage: 8.7, color: '#FAAD14' },
{ name: '紧急临期(<7天)', value: 12345, percentage: 2.5, color: '#FF7A45' },
{ name: '已过期', value: 56, percentage: 0.1, color: '#FF4D4F' },
{ name: '无有效期', value: 5670, percentage: 1.2, color: '#8C8C8C' }
],
// 库存周期趋势数据
cycleTrendData: {
categories: ['5月', '6月', '7月', '8月', '9月', '10月'],
avgCycleData: [45, 42, 48, 51, 46, 43],
freshnessIndexData: [92, 89, 85, 88, 91, 87.5]
},
// 有效期分布数据
expiryDistributionData: [
{ range: '0-7天', count: 12345, percentage: 2.5 },
{ range: '8-15天', count: 23456, percentage: 4.8 },
{ range: '16-30天', count: 45678, percentage: 9.4 },
{ range: '31-60天', count: 78901, percentage: 16.2 },
{ range: '61-90天', count: 123456, percentage: 25.4 },
{ range: '90天以上', count: 201836, percentage: 41.7 }
],
// 临期商品列表
nearExpiryList: [
{ name: '云南白药牙膏', code: 'YN001', expiryDate: '2024-10-28', daysLeft: 3, quantity: 234 },
{ name: '普洱茶饼', code: 'PU002', expiryDate: '2024-10-30', daysLeft: 5, quantity: 156 },
{ name: '鲜花饼', code: 'FH003', expiryDate: '2024-11-02', daysLeft: 8, quantity: 89 },
{ name: '三七粉', code: 'SQ005', expiryDate: '2024-11-05', daysLeft: 11, quantity: 67 },
{ name: '手工皂', code: 'GS008', expiryDate: '2024-11-08', daysLeft: 14, quantity: 45 },
{ name: '鲜花精油', code: 'XJ009', expiryDate: '2024-11-12', daysLeft: 18, quantity: 34 },
{ name: '云南咖啡', code: 'KF004', expiryDate: '2024-11-15', daysLeft: 21, quantity: 78 },
{ name: '银饰保养液', code: 'YS010', expiryDate: '2024-11-20', daysLeft: 26, quantity: 23 }
]
}
},
computed: {
// 饼图数据
pieChartData() {
return {
series: [{
data: this.freshnessData.map(item => ({
name: item.name,
value: item.value
}))
}]
}
},
// 饼图配置
pieChartOpts() {
return {
color: this.freshnessData.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'
}
}
}
},
// 折线图数据
lineChartData() {
return {
categories: this.cycleTrendData.categories,
series: [
{
name: '平均库存周期',
data: this.cycleTrendData.avgCycleData
},
{
name: '新鲜度指数',
data: this.cycleTrendData.freshnessIndexData
}
]
}
},
// 折线图配置
lineChartOpts() {
return {
color: ['#576EFF', '#52C41A'],
padding: [15, 15, 15, 15],
dataLabel: false,
legend: {
show: false
},
xAxis: {
disableGrid: true
},
yAxis: {
gridType: 'dash',
dashLength: 2,
data: [
{
min: 0,
title: '天数'
},
{
min: 0,
position: 'right',
title: '指数'
}
]
},
extra: {
line: {
type: 'curve',
width: 2,
activeType: 'hollow'
}
}
}
},
// 柱状图数据
barChartData() {
return {
categories: this.expiryDistributionData.map(item => item.range),
series: [{
name: '商品数量',
data: this.expiryDistributionData.map(item => item.count)
}]
}
},
// 柱状图配置
barChartOpts() {
return {
color: ['#576EFF', '#FF7A45', '#FAAD14', '#52C41A', '#13C2C2', '#B37FEB'],
padding: [15, 15, 15, 15],
dataLabel: false,
enableScroll: false,
xAxis: {
disableGrid: false,
gridType: 'dash',
rotateLabel: false,
itemCount: 6
},
yAxis: {
gridType: 'dash',
dashLength: 2,
data: [{
min: 0,
title: '商品数量'
}]
},
extra: {
column: {
type: 'group',
width: 35,
activeBgColor: '#000000',
activeBgOpacity: 0.08,
linearType: 'custom',
barBorderCircle: true,
seriesGap: 2
}
}
}
}
},
methods: {
formatNumber(num) {
return num.toString().replace(/\B(?=(\d{3})+(?!\d))/g, ",")
},
getWarningLevel(daysLeft) {
if (daysLeft <= 3) return 'urgent'
if (daysLeft <= 7) return 'high'
if (daysLeft <= 15) return 'medium'
return 'low'
},
getWarningIcon(daysLeft) {
if (daysLeft <= 3) return '🚨'
if (daysLeft <= 7) return '⚠️'
if (daysLeft <= 15) return '📅'
return ''
}
}
}
</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);
.inventory-freshness-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: 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;
&.fresh {
background: linear-gradient(135deg, #52c41a, #73d13d);
&::after { content: '🌿'; position: absolute; top: 50%; left: 50%; transform: translate(-50%, -50%); font-size: 24rpx; }
}
&.warning {
background: linear-gradient(135deg, #faad14, #ffc53d);
&::after { content: '⚠️'; position: absolute; top: 50%; left: 50%; transform: translate(-50%, -50%); font-size: 24rpx; }
}
&.expired {
background: linear-gradient(135deg, #ff4d4f, #ff7875);
&::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 {
display: flex;
justify-content: space-between;
align-items: flex-start;
margin-bottom: 20rpx;
.chart-title {
font-size: 28rpx;
font-weight: 600;
color: @text-primary;
}
.chart-subtitle {
font-size: 22rpx;
color: @text-light;
margin-top: 4rpx;
}
}
.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;
}
}
}
}
.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%;
&.cycle {
background: #576EFF;
}
&.freshness {
background: #52C41A;
}
}
.legend-text {
font-size: 22rpx;
color: @text-secondary;
}
}
}
}
.bar-chart-container {
width: 100%;
}
}
.warning-list {
.warning-item {
display: flex;
align-items: center;
padding: 16rpx;
margin-bottom: 12rpx;
border-radius: 12rpx;
border: 1rpx solid #f0f0f0;
&.urgent {
background: rgba(255, 77, 79, 0.05);
border-color: rgba(255, 77, 79, 0.2);
}
&.high {
background: rgba(255, 122, 69, 0.05);
border-color: rgba(255, 122, 69, 0.2);
}
&.medium {
background: rgba(250, 173, 20, 0.05);
border-color: rgba(250, 173, 20, 0.2);
}
&.low {
background: rgba(82, 196, 26, 0.05);
border-color: rgba(82, 196, 26, 0.2);
}
.warning-icon {
font-size: 32rpx;
margin-right: 16rpx;
}
.warning-content {
flex: 1;
.product-name {
font-size: 28rpx;
color: @text-primary;
font-weight: 500;
display: block;
margin-bottom: 4rpx;
}
.product-code {
font-size: 22rpx;
color: @text-light;
display: block;
}
}
.warning-info {
text-align: right;
margin-right: 16rpx;
.expiry-date {
font-size: 22rpx;
color: @text-secondary;
display: block;
margin-bottom: 4rpx;
}
.days-left {
font-size: 24rpx;
color: @text-primary;
font-weight: 600;
display: block;
}
}
.warning-action {
.action-btn {
font-size: 22rpx;
color: @primary-color;
padding: 8rpx 16rpx;
border: 1rpx solid @primary-color;
border-radius: 8rpx;
}
}
}
}
}
}
</style>