This commit is contained in:
ylj20011123 2025-11-25 11:30:18 +08:00
parent 43c76f2f6b
commit 24cb57ea15
6 changed files with 1518 additions and 1 deletions

View File

@ -0,0 +1,326 @@
<template>
<view class="supplier-category">
<!-- 品类销量排行 -->
<view class="chart-title">品类销量排行</view>
<view class="ranking-list">
<view v-for="(item, index) in categoryRanking" :key="index" class="ranking-item">
<view class="ranking-index"
:class="['rank-' + (index + 1), index === 0 ? 'rank-gold' : index === 1 ? 'rank-silver' : index === 2 ? 'rank-bronze' : '']">
{{ index + 1 }}</view>
<view class="ranking-content">
<text class="ranking-name">{{ item.name }}</text>
<view class="ranking-bar">
<view class="ranking-progress" :style="{ width: item.percentage + '%' }"></view>
</view>
</view>
<view class="ranking-value">
<text class="ranking-amount">¥{{ formatAmount(item.amount) }}</text>
<text class="ranking-percent">{{ item.percentage }}%</text>
</view>
</view>
</view>
<!-- 库存预警 -->
<view class="alert-cards">
<view class="alert-card-item slow">
<view class="alert-card-icon">📦</view>
<view class="alert-card-content">
<text class="alert-card-label">滞销品</text>
<text class="alert-card-value">{{ alertData.slowMoving || 0 }}</text>
</view>
</view>
<view class="alert-card-item shortage">
<view class="alert-card-icon"></view>
<view class="alert-card-content">
<text class="alert-card-label">缺货风险</text>
<text class="alert-card-value">{{ alertData.shortage || 0 }}</text>
</view>
</view>
</view>
<!-- 库存周转率 -->
<view class="chart-title">库存周转率</view>
<view class="chart-section">
<view class="chart-container">
<ChartLoading v-if="isLoading" text="数据加载中..." />
<QiunDataCharts v-else type="column" :opts="turnoverOpts" :chartData="turnoverData" :canvas2d="true"
:inScrollView="true" canvasId="turnoverChart" tooltipFormat="turnoverChart" />
</view>
</view>
</view>
</template>
<script>
import QiunDataCharts from './qiun-data-charts/components/qiun-data-charts/qiun-data-charts.vue'
import ChartLoading from './ChartLoading.vue'
export default {
components: {
QiunDataCharts,
ChartLoading
},
data() {
return {
isLoading: false,
categoryRanking: [
{ name: '食品饮料', amount: 1568000, percentage: 100 },
{ name: '日用百货', amount: 1285000, percentage: 82 },
{ name: '汽车用品', amount: 982000, percentage: 63 },
{ name: '文化用品', amount: 763000, percentage: 49 },
{ name: '其他商品', amount: 456000, percentage: 29 }
],
alertData: {
slowMoving: 23,
shortage: 5
}
}
},
props: {
selectTime: {
type: String,
default: ""
}
},
computed: {
//
turnoverData() {
return {
categories: ['食品', '日用', '汽车', '文化', '其他'],
series: [{
name: '周转天数',
data: [8, 12, 15, 18, 25]
}]
}
},
//
turnoverOpts() {
return {
color: ['#1890FF'],
padding: [15, 15, 15, 15],
dataLabel: true,
xAxis: {
disableGrid: true,
itemCount: 5,
fontSize: 10
},
yAxis: {
gridType: 'dash',
showTitle: true,
data: [{
min: 0,
title: '周转天数',
titleFontSize: 12,
titleOffsetY: -5
}]
},
legend: {
show: false
},
extra: {
column: {
type: 'group',
width: 15,
barBorderCircle: true
}
}
}
}
},
onReady() {
this.loadData()
},
methods: {
async loadData() {
this.isLoading = true
setTimeout(() => {
this.isLoading = false
}, 500)
},
//
formatAmount(amount) {
if (!amount && amount !== 0) return '0'
return Number(amount).toLocaleString('zh-CN')
}
}
}
</script>
<style scoped lang="less">
@text-primary: #333;
@text-secondary: #666;
@bg-white: #ffffff;
.supplier-category {
width: 100%;
}
.section-title {
font-size: 30rpx;
font-weight: 600;
color: @text-primary;
margin-bottom: 12rpx;
}
.chart-title {
font-size: 30rpx;
font-weight: 600;
color: @text-primary;
margin-bottom: 12rpx;
}
.ranking-list {
background: @bg-white;
border-radius: 16rpx;
padding: 24rpx;
box-shadow: 0 4px 20px rgba(0, 0, 0, 0.08);
margin-bottom: 24rpx;
}
.ranking-item {
display: flex;
align-items: center;
gap: 16rpx;
margin-bottom: 24rpx;
&:last-child {
margin-bottom: 0;
}
}
.ranking-index {
width: 48rpx;
height: 48rpx;
border-radius: 50%;
background: #E8E8E8;
color: @text-primary;
font-size: 24rpx;
font-weight: 600;
display: flex;
align-items: center;
justify-content: center;
&.rank-gold {
background: linear-gradient(135deg, #FFD700, #FFA500);
color: #fff;
}
&.rank-silver {
background: linear-gradient(135deg, #C0C0C0, #A8A8A8);
color: #fff;
}
&.rank-bronze {
background: linear-gradient(135deg, #CD7F32, #B8860B);
color: #fff;
}
}
.ranking-content {
flex: 1;
}
.ranking-name {
font-size: 26rpx;
color: @text-primary;
display: block;
margin-bottom: 8rpx;
}
.ranking-bar {
width: 100%;
height: 16rpx;
background: #F0F0F0;
border-radius: 8rpx;
overflow: hidden;
}
.ranking-progress {
height: 100%;
background: linear-gradient(90deg, #9B7EDE, #B794F4);
border-radius: 8rpx;
transition: width 0.3s ease;
}
.ranking-value {
display: flex;
flex-direction: column;
align-items: flex-end;
}
.ranking-amount {
font-size: 26rpx;
font-weight: 600;
color: @text-primary;
}
.ranking-percent {
font-size: 22rpx;
color: @text-secondary;
}
.alert-cards {
display: flex;
gap: 24rpx;
margin-bottom: 24rpx;
}
.alert-card-item {
flex: 1;
background: @bg-white;
border-radius: 16rpx;
padding: 24rpx;
box-shadow: 0 4px 20px rgba(0, 0, 0, 0.08);
text-align: center;
&.slow {
border-left: 6rpx solid #FAAD14;
}
&.shortage {
border-left: 6rpx solid #FF4D4F;
}
}
.alert-card-icon {
font-size: 48rpx;
margin-bottom: 12rpx;
}
.alert-card-content {
display: flex;
flex-direction: column;
gap: 8rpx;
}
.alert-card-label {
font-size: 24rpx;
color: @text-secondary;
}
.alert-card-value {
font-size: 28rpx;
font-weight: 600;
color: @text-primary;
}
.chart-section {
background: @bg-white;
border-radius: 16rpx;
padding: 24rpx 24rpx 0;
box-shadow: 0 4px 20px rgba(0, 0, 0, 0.08);
margin-bottom: 32rpx;
}
.chart-container {
width: 100%;
height: 400rpx;
margin-bottom: 20rpx;
}
</style>

View File

@ -0,0 +1,281 @@
<template>
<view class="supplier-cooperation">
<!-- 合作时长分布 -->
<view class="chart-title">合作时长分布</view>
<view class="chart-section">
<view class="chart-container">
<ChartLoading v-if="isLoading" text="数据加载中..." />
<QiunDataCharts v-else type="pie" :opts="durationPieOpts" :chartData="durationPieData" :canvas2d="true"
:inScrollView="true" canvasId="durationPieChart" tooltipFormat="durationPieChart" />
</view>
</view>
<!-- 供应商依赖度 -->
<view class="chart-title">TOP5供应商依赖度</view>
<view class="chart-section">
<view class="chart-container">
<ChartLoading v-if="isLoading" text="数据加载中..." />
<QiunDataCharts v-else type="column" :opts="dependencyOpts" :chartData="dependencyData" :canvas2d="true"
:inScrollView="true" canvasId="dependencyChart" tooltipFormat="dependencyChart" />
</view>
</view>
<!-- 合作统计卡片 -->
<view class="cooperation-cards">
<view class="cooperation-card long-term">
<view class="cooperation-icon">🤝</view>
<view class="cooperation-content">
<text class="cooperation-title">长期合作</text>
<view class="cooperation-value">
<text class="cooperation-number">{{ cooperationData.longTerm || 0 }}</text>
<text class="cooperation-unit"></text>
</view>
</view>
</view>
<view class="cooperation-card new-supplier">
<view class="cooperation-icon"></view>
<view class="cooperation-content">
<text class="cooperation-title">新增供应商</text>
<view class="cooperation-value">
<text class="cooperation-number">{{ cooperationData.newSuppliers || 0 }}</text>
<text class="cooperation-unit"></text>
</view>
</view>
</view>
</view>
</view>
</template>
<script>
import QiunDataCharts from './qiun-data-charts/components/qiun-data-charts/qiun-data-charts.vue'
import ChartLoading from './ChartLoading.vue'
export default {
components: {
QiunDataCharts,
ChartLoading
},
data() {
return {
isLoading: false,
cooperationData: {
longTerm: 68,
newSuppliers: 15
}
}
},
props: {
selectTime: {
type: String,
default: ""
}
},
computed: {
//
durationPieData() {
return {
series: [{
data: [
{ name: '3年以上', value: 45 },
{ name: '1-3年', value: 38 },
{ name: '1年以内', value: 45 }
]
}]
}
},
//
durationPieOpts() {
return {
color: ['#9B7EDE', '#52C41A', '#1890FF'],
padding: [15, 15, 15, 15],
dataLabel: false,
legend: {
show: true,
position: 'right',
float: 'center',
padding: 5,
margin: 10
},
extra: {
pie: {
activeOpacity: 0.5,
activeRadius: 10,
offsetAngle: 0,
labelWidth: 0,
border: true,
borderWidth: 0,
borderColor: '#FFFFFF'
}
}
}
},
//
dependencyData() {
return {
categories: ['供应商A', '供应商B', '供应商C', '供应商D', '供应商E'],
series: [{
name: '销售占比',
data: [28, 22, 18, 15, 12]
}]
}
},
//
dependencyOpts() {
return {
color: ['#FF8F6F'],
padding: [15, 15, 15, 15],
dataLabel: false,
xAxis: {
disableGrid: true,
itemCount: 5,
fontSize: 10
},
yAxis: {
gridType: 'dash',
showTitle: true,
data: [{
min: 0,
max: 30,
title: '占比(%)',
titleFontSize: 12,
titleOffsetY: -5
}]
},
legend: {
show: false
},
extra: {
column: {
type: 'group',
width: 15,
barBorderCircle: true,
linearType: 'custom',
customColor: ['#FF8F6F', '#FF6B45']
}
}
}
}
},
onReady() {
this.loadData()
},
methods: {
async loadData() {
this.isLoading = true
setTimeout(() => {
this.isLoading = false
}, 500)
}
}
}
</script>
<style scoped lang="less">
@text-primary: #333;
@text-secondary: #666;
@bg-white: #ffffff;
.supplier-cooperation {
width: 100%;
}
.section-title {
font-size: 30rpx;
font-weight: 600;
color: @text-primary;
margin-bottom: 12rpx;
}
.chart-title {
font-size: 30rpx;
font-weight: 600;
color: @text-primary;
margin-bottom: 12rpx;
}
.chart-section {
background: @bg-white;
border-radius: 16rpx;
padding: 24rpx 24rpx 0;
box-shadow: 0 4px 20px rgba(0, 0, 0, 0.08);
margin-bottom: 32rpx;
}
.chart-container {
width: 100%;
height: 400rpx;
margin-bottom: 20rpx;
}
.cooperation-cards {
display: flex;
gap: 24rpx;
margin-bottom: 24rpx;
}
.cooperation-card {
flex: 1;
border-radius: 16rpx;
padding: 16rpx 24rpx;
box-shadow: 0 4px 20px rgba(0, 0, 0, 0.08);
display: flex;
align-items: center;
gap: 24rpx;
&.long-term {
background: linear-gradient(135deg, #9B7EDE, #B794F4);
}
&.new-supplier {
background: linear-gradient(135deg, #1890FF, #40A9FF);
}
}
.cooperation-icon {
font-size: 48rpx;
width: 80rpx;
height: 80rpx;
background: rgba(255, 255, 255, 0.2);
border-radius: 16rpx;
display: flex;
align-items: center;
justify-content: center;
}
.cooperation-content {
flex: 1;
}
.cooperation-title {
font-size: 24rpx;
color: #fff;
font-weight: 500;
margin-bottom: 8rpx;
display: block;
}
.cooperation-value {
display: flex;
align-items: baseline;
gap: 8rpx;
}
.cooperation-number {
font-size: 28rpx;
font-weight: 600;
color: #fff;
}
.cooperation-unit {
font-size: 24rpx;
color: rgba(255, 255, 255, 0.8);
}
</style>

View File

@ -0,0 +1,265 @@
<template>
<view class="supplier-overview">
<view class="section-title">供应链概览</view>
<!-- 核心KPI卡片 -->
<view class="metrics-row">
<view class="metric-card">
<text class="metric-value" style="color: #9B7EDE;">{{ kpiData.totalSuppliers || 0 }}</text>
<text class="metric-label">合作供应商/</text>
</view>
<view class="metric-card">
<text class="metric-value" style="color: #52C41A;">{{ kpiData.avgDeliveryRate || 0 }}%</text>
<text class="metric-label">平均送达率</text>
</view>
</view>
<view class="metrics-row">
<view class="metric-card">
<text class="metric-value" style="color: #1890FF;">{{ kpiData.inventoryTurnover || 0 }}</text>
<text class="metric-label">库存周转/</text>
</view>
<view class="metric-card">
<text class="metric-value" style="color: #FF8F6F;">¥{{ formatMoney(kpiData.monthlyPurchase) }}</text>
<text class="metric-label">本月采购额</text>
</view>
</view>
<!-- 供应商类别分布 -->
<view class="chart-title">供应商类别分布</view>
<view class="chart-section">
<view class="chart-container">
<!-- 图表加载效果 -->
<ChartLoading v-if="isLoading" text="数据加载中..." />
<!-- 实际饼图 -->
<QiunDataCharts v-else type="pie" :opts="categoryPieOpts" :chartData="categoryPieData" :canvas2d="true"
:inScrollView="true" canvasId="supplierCategoryPie" tooltipFormat="supplierCategoryPie" />
</view>
</view>
<!-- 预警提示 -->
<!-- <view class="alert-card">
<view class="alert-icon"></view>
<view class="alert-content">
<text class="alert-title">预警提示</text>
<view class="alert-value">
<text class="alert-number">{{ alertData.totalAlerts || 0 }}</text>
<text class="alert-unit">个异常</text>
</view>
</view>
</view> -->
</view>
</template>
<script>
import QiunDataCharts from './qiun-data-charts/components/qiun-data-charts/qiun-data-charts.vue'
import ChartLoading from './ChartLoading.vue'
export default {
components: {
QiunDataCharts,
ChartLoading
},
data() {
return {
isLoading: false,
kpiData: {
totalSuppliers: 128,
avgDeliveryRate: 95.6,
inventoryTurnover: 12,
monthlyPurchase: 2800000
},
alertData: {
totalAlerts: 3
},
categoryData: [
{ name: '食品供应商', value: 35 },
{ name: '日用品供应商', value: 28 },
{ name: '服务类供应商', value: 22 },
{ name: '设备供应商', value: 18 },
{ name: '其他', value: 25 }
]
}
},
props: {
selectTime: {
type: String,
default: ""
}
},
computed: {
//
categoryPieData() {
return {
series: [{
data: this.categoryData.filter(item => item.value > 0)
}]
}
},
//
categoryPieOpts() {
return {
color: ['#9B7EDE', '#52C41A', '#1890FF', '#FF8F6F', '#FAAD14'],
padding: [15, 15, 15, 15],
dataLabel: false,
legend: {
show: true,
position: 'right',
float: 'center',
padding: 5,
margin: 10
},
extra: {
pie: {
activeOpacity: 0.5,
activeRadius: 10,
labelWidth: 0,
border: true,
borderWidth: 0,
borderColor: '#FFFFFF'
}
}
}
}
},
onReady() {
this.loadData()
},
methods: {
async loadData() {
this.isLoading = true
// TODO: API
setTimeout(() => {
this.isLoading = false
}, 500)
},
// ,
formatMoney(amount) {
if (!amount && amount !== 0) return '0'
return Number(amount).toLocaleString('zh-CN')
}
}
}
</script>
<style scoped lang="less">
@text-primary: #333;
@text-secondary: #666;
@bg-white: #ffffff;
.supplier-overview {
width: 100%;
}
.section-title {
font-size: 30rpx;
font-weight: 600;
color: @text-primary;
margin-bottom: 12rpx;
}
.metrics-row {
display: flex;
gap: 24rpx;
margin-bottom: 24rpx;
}
.metric-card {
flex: 1;
background: @bg-white;
border-radius: 16rpx;
padding: 16rpx;
box-shadow: 0 4px 20px rgba(0, 0, 0, 0.08);
text-align: center;
}
.metric-value {
font-size: 28rpx;
font-weight: 600;
display: block;
}
.metric-label {
font-size: 24rpx;
color: @text-secondary;
}
.chart-title {
font-size: 30rpx;
font-weight: 600;
color: @text-primary;
margin-bottom: 12rpx;
}
.chart-section {
background: @bg-white;
border-radius: 16rpx;
padding: 24rpx 24rpx 0;
box-shadow: 0 4px 20px rgba(0, 0, 0, 0.08);
margin-bottom: 32rpx;
}
.chart-container {
width: 100%;
height: 400rpx;
margin-bottom: 20rpx;
}
.alert-card {
background: linear-gradient(135deg, #FF8F6F, #FF4D4F);
border-radius: 16rpx;
padding: 16rpx 24rpx;
box-shadow: 0 4px 20px rgba(0, 0, 0, 0.08);
display: flex;
align-items: center;
gap: 24rpx;
margin-bottom: 24rpx;
}
.alert-icon {
font-size: 48rpx;
width: 80rpx;
height: 80rpx;
background: rgba(255, 255, 255, 0.2);
border-radius: 16rpx;
display: flex;
align-items: center;
justify-content: center;
}
.alert-content {
flex: 1;
}
.alert-title {
font-size: 24rpx;
color: #fff;
font-weight: 500;
margin-bottom: 8rpx;
display: block;
}
.alert-value {
display: flex;
align-items: baseline;
gap: 8rpx;
}
.alert-number {
font-size: 28rpx;
font-weight: 600;
color: #fff;
}
.alert-unit {
font-size: 24rpx;
color: rgba(255, 255, 255, 0.8);
}
</style>

View File

@ -0,0 +1,297 @@
<template>
<view class="supplier-performance">
<!-- 送达率趋势 -->
<view class="chart-title">送达率趋势</view>
<view class="chart-section">
<view class="chart-container">
<ChartLoading v-if="isLoading" text="数据加载中..." />
<QiunDataCharts v-else type="line" :opts="deliveryTrendOpts" :chartData="deliveryTrendData"
:canvas2d="true" :inScrollView="true" canvasId="deliveryTrendChart"
:tooltipFormat="formatDeliveryTooltip" />
</view>
</view>
<!-- 供应商评分TOP10 -->
<view class="chart-title">供应商评分TOP10</view>
<view class="chart-section">
<view class="chart-container">
<ChartLoading v-if="isLoading" text="数据加载中..." />
<QiunDataCharts v-else type="bar" :opts="scoreRankOpts" :chartData="scoreRankData" :canvas2d="true"
:inScrollView="true" canvasId="scoreRankChart" tooltipFormat="scoreRankChart" />
</view>
</view>
<!-- 绩效统计卡片 -->
<view class="performance-cards">
<view class="performance-card excellent">
<view class="performance-icon"></view>
<view class="performance-content">
<text class="performance-title">优秀供应商</text>
<view class="performance-value">
<text class="performance-number">{{ performanceData.excellent || 0 }}</text>
<text class="performance-unit"></text>
</view>
</view>
</view>
<view class="performance-card warning">
<view class="performance-icon">!</view>
<view class="performance-content">
<text class="performance-title">待改进</text>
<view class="performance-value">
<text class="performance-number">{{ performanceData.needImprovement || 0 }}</text>
<text class="performance-unit"></text>
</view>
</view>
</view>
</view>
</view>
</template>
<script>
import QiunDataCharts from './qiun-data-charts/components/qiun-data-charts/qiun-data-charts.vue'
import ChartLoading from './ChartLoading.vue'
export default {
components: {
QiunDataCharts,
ChartLoading
},
data() {
return {
isLoading: false,
performanceData: {
excellent: 85,
needImprovement: 12
}
}
},
props: {
selectTime: {
type: String,
default: ""
}
},
computed: {
//
deliveryTrendData() {
return {
categories: ['1月', '2月', '3月', '4月', '5月', '6月', '7月', '8月', '9月', '10月', '11月', '12月'],
series: [{
name: '送达率',
data: [92.5, 93.2, 94.1, 95.3, 94.8, 95.6, 96.2, 95.8, 96.5, 97.1, 96.8, 97.3]
}]
}
},
//
deliveryTrendOpts() {
return {
color: ['#9B7EDE'],
padding: [15, 15, 15, 15],
dataLabel: false,
xAxis: {
disableGrid: true,
itemCount: 12,
fontSize: 10
},
yAxis: {
gridType: 'dash',
showTitle: true,
data: [{
min: 90,
max: 100,
title: '送达率(%)',
titleFontSize: 12,
titleOffsetY: -5
}]
},
legend: {
show: false
},
extra: {
line: {
type: 'curve',
width: 2,
activeType: 'hollow'
}
}
}
},
//
scoreRankData() {
return {
categories: ['供应商A', '供应商B', '供应商C', '供应商D', '供应商E', '供应商F', '供应商G', '供应商H', '供应商I', '供应商J'],
series: [{
name: '综合评分',
data: [98, 96, 94, 92, 90, 88, 86, 84, 82, 80]
}]
}
},
//
scoreRankOpts() {
return {
color: ['#52C41A'],
padding: [15, 15, 15, 15],
dataLabel: true,
type: 'bar',
xAxis: {
boundaryGap: 'justify',
disableGrid: false,
min: 0,
max: 100,
splitNumber: 5,
fontSize: 10
},
yAxis: {
data: [{
fontSize: 10
}]
},
legend: {
show: false
},
extra: {
bar: {
type: 'group',
width: 10,
barBorderCircle: true,
linearType: 'custom',
linearOpacity: 1,
customColor: ['#52C41A', '#73D13D']
}
}
}
}
},
onReady() {
this.loadData()
},
methods: {
async loadData() {
this.isLoading = true
setTimeout(() => {
this.isLoading = false
}, 500)
},
// tooltip
formatDeliveryTooltip(item, category, index, opts) {
if (item.data && category) {
return `${category} 送达率:${item.data}%`
}
return ''
}
}
}
</script>
<style scoped lang="less">
@text-primary: #333;
@text-secondary: #666;
@bg-white: #ffffff;
.supplier-performance {
width: 100%;
}
.section-title {
font-size: 30rpx;
font-weight: 600;
color: @text-primary;
margin-bottom: 12rpx;
}
.chart-title {
font-size: 30rpx;
font-weight: 600;
color: @text-primary;
margin-bottom: 12rpx;
}
.chart-section {
background: @bg-white;
border-radius: 16rpx;
padding: 24rpx 24rpx 0;
box-shadow: 0 4px 20px rgba(0, 0, 0, 0.08);
margin-bottom: 32rpx;
}
.chart-container {
width: 100%;
height: 400rpx;
margin-bottom: 20rpx;
}
.performance-cards {
display: flex;
gap: 24rpx;
margin-bottom: 24rpx;
}
.performance-card {
flex: 1;
border-radius: 16rpx;
padding: 16rpx 24rpx;
box-shadow: 0 4px 20px rgba(0, 0, 0, 0.08);
display: flex;
align-items: center;
gap: 24rpx;
&.excellent {
background: linear-gradient(135deg, #52C41A, #73D13D);
}
&.warning {
background: linear-gradient(135deg, #FF8F6F, #FF4D4F);
}
}
.performance-icon {
font-size: 48rpx;
width: 80rpx;
height: 80rpx;
background: rgba(255, 255, 255, 0.2);
border-radius: 16rpx;
display: flex;
align-items: center;
justify-content: center;
color: #fff;
font-weight: bold;
}
.performance-content {
flex: 1;
}
.performance-title {
font-size: 24rpx;
color: #fff;
font-weight: 500;
margin-bottom: 8rpx;
display: block;
}
.performance-value {
display: flex;
align-items: baseline;
gap: 8rpx;
}
.performance-number {
font-size: 28rpx;
font-weight: 600;
color: #fff;
}
.performance-unit {
font-size: 24rpx;
color: rgba(255, 255, 255, 0.8);
}
</style>

View File

@ -0,0 +1,310 @@
<template>
<view class="supplier-ranking">
<!-- 供应商销量TOP10 -->
<view class="chart-title">供应商销量TOP10</view>
<view class="ranking-cards">
<view v-for="(item, index) in supplierRanking" :key="index" class="ranking-card">
<view class="ranking-medal">
<text v-if="index < 3" class="medal-icon">{{ getMedalIcon(index) }}</text>
<text v-else class="medal-number">{{ index + 1 }}</text>
</view>
<view class="ranking-info">
<text class="ranking-supplier-name">{{ item.name }}</text>
<view class="ranking-data-row">
<view class="data-group">
<text class="data-label">销售额:</text>
<text class="data-value sales">¥{{ item.sales }}</text>
<view class="data-growth" :class="item.growth >= 0 ? 'growth-up' : 'growth-down'">
<text class="growth-arrow">{{ item.growth >= 0 ? '↑' : '↓' }}</text>
<text class="growth-value">{{ Math.abs(item.growth) }}%</text>
</view>
</view>
<view class="data-divider"></view>
<view class="data-group">
<text class="data-label">销量:</text>
<text class="data-value quantity">{{ item.quantity }}</text>
<view class="data-growth" :class="item.quantityGrowth >= 0 ? 'growth-up' : 'growth-down'">
<text class="growth-arrow">{{ item.quantityGrowth >= 0 ? '↑' : '↓' }}</text>
<text class="growth-value">{{ Math.abs(item.quantityGrowth) }}%</text>
</view>
</view>
</view>
</view>
</view>
</view>
<!-- 品类销量TOP5 -->
<view class="chart-title">品类销量TOP5</view>
<view class="chart-section">
<view class="chart-container">
<ChartLoading v-if="isLoading" text="数据加载中..." />
<QiunDataCharts v-else type="column" :opts="categoryRankOpts" :chartData="categoryRankData"
:canvas2d="true" :inScrollView="true" canvasId="categoryRankChart"
tooltipFormat="categoryRankChart" />
</view>
</view>
</view>
</template>
<script>
import QiunDataCharts from './qiun-data-charts/components/qiun-data-charts/qiun-data-charts.vue'
import ChartLoading from './ChartLoading.vue'
export default {
components: {
QiunDataCharts,
ChartLoading
},
data() {
return {
isLoading: false,
supplierRanking: [
{ name: '优质食品供应商', sales: '186.5万', growth: 15.6, quantity: '12,580件', quantityGrowth: 18.2 },
{ name: '日用百货批发商', sales: '158.2万', growth: 12.3, quantity: '9,856件', quantityGrowth: 10.5 },
{ name: '汽车用品专营店', sales: '142.8万', growth: 8.7, quantity: '8,234件', quantityGrowth: 6.8 },
{ name: '文化用品供应商', sales: '128.5万', growth: -2.1, quantity: '7,652件', quantityGrowth: -1.5 },
{ name: '特色商品供应商', sales: '115.3万', growth: 18.9, quantity: '6,890件', quantityGrowth: 22.3 },
{ name: '进口食品供应商', sales: '98.6万', growth: 6.5, quantity: '5,432件', quantityGrowth: 8.2 },
{ name: '地方特产供应商', sales: '86.2万', growth: 22.4, quantity: '4,876件', quantityGrowth: 25.6 },
{ name: '快消品批发商', sales: '75.8万', growth: 4.2, quantity: '4,123件', quantityGrowth: 3.8 },
{ name: '品牌代理商', sales: '68.5万', growth: -5.3, quantity: '3,654件', quantityGrowth: -6.2 },
{ name: '综合供应商', sales: '56.3万', growth: 9.8, quantity: '3,012件', quantityGrowth: 11.5 }
]
}
},
props: {
selectTime: {
type: String,
default: ""
}
},
computed: {
// TOP5
categoryRankData() {
return {
categories: ['食品饮料', '日用百货', '汽车用品', '文化用品', '其他商品'],
series: [{
name: '销量',
data: [12580, 9856, 8234, 7652, 5432]
}]
}
},
//
categoryRankOpts() {
return {
color: ['#9B7EDE'],
padding: [15, 15, 15, 15],
dataLabel: false,
xAxis: {
disableGrid: true,
itemCount: 5,
fontSize: 10,
scrollShow: true
},
yAxis: {
gridType: 'dash',
showTitle: true,
data: [{
min: 0,
title: '销量(件)',
titleFontSize: 12,
titleOffsetY: -5
}]
},
legend: {
show: false
},
extra: {
column: {
type: 'group',
width: 15,
barBorderCircle: true,
linearType: 'custom',
customColor: ['#9B7EDE', '#B794F4']
}
}
}
}
},
onReady() {
this.loadData()
},
methods: {
async loadData() {
this.isLoading = true
setTimeout(() => {
this.isLoading = false
}, 500)
},
getMedalIcon(index) {
const medals = ['🥇', '🥈', '🥉']
return medals[index] || ''
}
}
}
</script>
<style scoped lang="less">
@text-primary: #333;
@text-secondary: #666;
@bg-white: #ffffff;
.supplier-ranking {
width: 100%;
}
.section-title {
font-size: 30rpx;
font-weight: 600;
color: @text-primary;
margin-bottom: 12rpx;
}
.chart-title {
font-size: 30rpx;
font-weight: 600;
color: @text-primary;
margin-bottom: 12rpx;
}
.ranking-cards {
background: @bg-white;
border-radius: 16rpx;
padding: 24rpx;
box-shadow: 0 4px 20px rgba(0, 0, 0, 0.08);
margin-bottom: 24rpx;
}
.ranking-card {
display: flex;
align-items: center;
gap: 16rpx;
padding: 16rpx 0;
border-bottom: 1rpx solid #F0F0F0;
&:last-child {
border-bottom: none;
}
}
.ranking-medal {
width: 56rpx;
height: 56rpx;
display: flex;
align-items: center;
justify-content: center;
}
.medal-icon {
font-size: 40rpx;
}
.medal-number {
width: 48rpx;
height: 48rpx;
border-radius: 50%;
background: #F0F0F0;
color: @text-secondary;
font-size: 24rpx;
font-weight: 600;
display: flex;
align-items: center;
justify-content: center;
}
.ranking-info {
flex: 1;
}
.ranking-supplier-name {
font-size: 26rpx;
color: @text-primary;
display: block;
margin-bottom: 8rpx;
}
.ranking-data-row {
display: flex;
align-items: center;
gap: 16rpx;
}
.data-group {
display: flex;
align-items: center;
gap: 6rpx;
flex: 1;
}
.data-label {
font-size: 22rpx;
color: @text-secondary;
}
.data-value {
font-size: 22rpx;
font-weight: 600;
&.sales {
color: #9B7EDE;
}
&.quantity {
color: #1890FF;
}
}
.data-divider {
width: 1rpx;
height: 24rpx;
background: #E8E8E8;
}
.data-growth {
display: flex;
align-items: center;
gap: 2rpx;
padding: 2rpx 6rpx;
border-radius: 4rpx;
font-size: 18rpx;
&.growth-up {
background: rgba(82, 196, 26, 0.1);
color: #52C41A;
}
&.growth-down {
background: rgba(255, 77, 79, 0.1);
color: #FF4D4F;
}
}
.growth-arrow {
font-size: 16rpx;
}
.growth-value {
font-weight: 600;
}
.chart-section {
background: @bg-white;
border-radius: 16rpx;
padding: 24rpx 24rpx 0;
box-shadow: 0 4px 20px rgba(0, 0, 0, 0.08);
margin-bottom: 32rpx;
}
.chart-container {
width: 100%;
height: 400rpx;
margin-bottom: 20rpx;
}
</style>

View File

@ -125,6 +125,24 @@
<FestivalRevenueSumInfo /> <FestivalRevenueSumInfo />
</view> </view>
<!-- 供应链生态全景 -->
<view v-else-if="tab.key === 'supplierAnalysis'" class="tab-content">
<view id="supplier-overview" class="section-anchor"></view>
<SupplierOverview :selectTime="selectTime" />
<view id="supplier-performance" class="section-anchor"></view>
<SupplierPerformance :selectTime="selectTime" />
<view id="supplier-category" class="section-anchor"></view>
<SupplierCategory :selectTime="selectTime" />
<view id="supplier-ranking" class="section-anchor"></view>
<SupplierRanking :selectTime="selectTime" />
<view id="supplier-cooperation" class="section-anchor"></view>
<SupplierCooperation :selectTime="selectTime" />
</view>
<!-- 商户电商生态全景 --> <!-- 商户电商生态全景 -->
<view v-else-if="tab.key === 'mallOperation'" class="tab-content"> <view v-else-if="tab.key === 'mallOperation'" class="tab-content">
<view id="member-mall" class="section-anchor"></view> <view id="member-mall" class="section-anchor"></view>
@ -181,6 +199,11 @@ import SupplierListBox from './components/SupplierListBox.vue'
import MallOrderStatistics from './components/MallOrderStatistics.vue' import MallOrderStatistics from './components/MallOrderStatistics.vue'
import ThisMonthBenefits from './components/ThisMonthBenefits.vue' import ThisMonthBenefits from './components/ThisMonthBenefits.vue'
import AnalysisOfMember from './components/AnalysisOfMember.vue' import AnalysisOfMember from './components/AnalysisOfMember.vue'
import SupplierOverview from './components/SupplierOverview.vue'
import SupplierPerformance from './components/SupplierPerformance.vue'
import SupplierCategory from './components/SupplierCategory.vue'
import SupplierRanking from './components/SupplierRanking.vue'
import SupplierCooperation from './components/SupplierCooperation.vue'
import moment from 'moment' import moment from 'moment'
@ -210,7 +233,12 @@ export default {
SupplierListBox, SupplierListBox,
MallOrderStatistics, MallOrderStatistics,
ThisMonthBenefits, ThisMonthBenefits,
AnalysisOfMember AnalysisOfMember,
SupplierOverview,
SupplierPerformance,
SupplierCategory,
SupplierRanking,
SupplierCooperation
}, },
data() { data() {
return { return {
@ -221,6 +249,7 @@ export default {
{ name: '运营中心', key: 'business' }, { name: '运营中心', key: 'business' },
{ name: '客群画像', key: 'customerProfile' }, { name: '客群画像', key: 'customerProfile' },
{ name: '经营分析', key: 'businessRevenue' }, { name: '经营分析', key: 'businessRevenue' },
{ name: '供应链生态', key: 'supplierAnalysis' },
{ name: '电商生态', key: 'mallOperation' }, { name: '电商生态', key: 'mallOperation' },
], ],
// Tab // Tab
@ -249,6 +278,13 @@ export default {
{ id: 'business-structure', name: '业态' },// { id: 'business-structure', name: '业态' },//
{ id: 'festival-revenue-sum-info', name: '节假日' },// { id: 'festival-revenue-sum-info', name: '节假日' },//
], ],
supplierAnalysis: [
{ id: 'supplier-overview', name: '概览' },//
{ id: 'supplier-performance', name: '绩效' },//
{ id: 'supplier-category', name: '分类' },//
{ id: 'supplier-ranking', name: '排名' },//
{ id: 'supplier-cooperation', name: '合作' },//
],
mallOperation: [ mallOperation: [
{ id: 'member-mall', name: '商城' },// { id: 'member-mall', name: '商城' },//
{ id: 'hot-product-list', name: '榜单' },// { id: 'hot-product-list', name: '榜单' },//
@ -263,6 +299,7 @@ export default {
business: '#6F86FF', business: '#6F86FF',
customerProfile: '#FF8F6F', customerProfile: '#FF8F6F',
businessRevenue: '#38C9A4', businessRevenue: '#38C9A4',
supplierAnalysis: '#9B7EDE',
mallOperation: '#F1C84C' mallOperation: '#F1C84C'
}, },
// //
@ -276,6 +313,7 @@ export default {
business: '', business: '',
customerProfile: '', customerProfile: '',
businessRevenue: '', businessRevenue: '',
supplierAnalysis: '',
mallOperation: '' mallOperation: ''
}, },
tabScrollPosition: 0, tabScrollPosition: 0,