Files
medical-mall/pages/dashboard/PurchaseUserPie.uvue
2026-02-02 20:07:37 +08:00

208 lines
4.2 KiB
Plaintext

<template>
<view class="chart-container">
<view class="chart-header">
<view class="header-left">
<view class="title-icon">
<text class="iconfont">O</text>
</view>
<text class="chart-title">购买用户分析</text>
</view>
</view>
<view class="chart-body">
<view class="chart-view-wrapper">
<EChartsView
v-if="!loading && chartOption"
:option="chartOption"
class="echarts-view"
/>
<!-- Loading 状态 -->
<view v-if="loading" class="loading-state">
<text class="loading-text">计算中...</text>
</view>
<!-- 空数据状态 -->
<view v-if="!loading && chartData.length === 0" class="empty-state">
<text class="empty-text">无相关消费记录</text>
</view>
</view>
</view>
</view>
</template>
<script setup lang="uts">
import { ref, computed, onMounted } from 'vue'
import EChartsView from '@/uni_modules/charts/EChartsView.vue'
import { PurchaseUserStat } from '@/types/charts.uts'
/**
* PurchaseUserPie 购买用户统计饼图
* 展示不同消费特征的用户分布情况
*/
// --- 状态定义 ---
const loading = ref(false)
const chartData = ref<PurchaseUserStat[]>([])
// --- ECharts 配置计算属性 ---
const chartOption = computed(() : any => {
if (chartData.value.length === 0) return null
return {
tooltip: {
trigger: 'item',
formatter: '{b}: {c} ({d}%)'
},
legend: {
bottom: '5%',
left: 'center',
itemWidth: 10,
itemHeight: 10,
textStyle: {
fontSize: 12,
color: '#666'
}
},
// 视觉色彩方案
color: ['#1890ff', '#36cfc9', '#ffc53d', '#ff4d4f'],
series: [
{
name: '用户分布',
type: 'pie',
radius: ['45%', '70%'], // 采用环形图设计,更具现代感
avoidLabelOverlap: true,
itemStyle: {
borderRadius: 6,
borderColor: '#fff',
borderWidth: 2
},
label: {
show: true,
position: 'outside',
formatter: '{b}\n{d}%',
fontSize: 11,
color: '#888'
},
emphasis: {
scale: true,
itemStyle: {
shadowBlur: 10,
shadowOffsetX: 0,
shadowColor: 'rgba(0, 0, 0, 0.1)'
}
},
labelLine: {
show: true,
length: 12,
length2: 10,
smooth: true
},
data: chartData.value.map(item => {
return {
name: item.label,
value: item.value
}
})
}
]
}
})
// --- 数据获取逻辑 ---
/**
* 模拟获取统计数据
*/
const fetchData = () => {
loading.value = true
setTimeout(() => {
chartData.value = [
{ label: '未消费用户', value: 342 } as PurchaseUserStat,
{ label: '消费一次', value: 156 } as PurchaseUserStat,
{ label: '留存客户', value: 218 } as PurchaseUserStat,
{ label: '回流客户', value: 84 } as PurchaseUserStat
]
loading.value = false
}, 1000)
}
// 暴露方法
defineExpose({
refresh: fetchData
})
onMounted(() => {
fetchData()
})
</script>
<style scoped lang="scss">
.chart-container {
background: #ffffff;
border-radius: 4px;
border: 1px solid #f0f0f0;
padding: 16px;
}
.chart-header {
display: flex;
flex-direction: row;
justify-content: space-between;
align-items: center;
margin-bottom: 20px;
}
.header-left {
display: flex;
flex-direction: row;
align-items: center;
gap: 8px;
}
.title-icon {
width: 24px;
height: 24px;
background: #fff7e6;
border-radius: 4px;
display: flex;
align-items: center;
justify-content: center;
}
.chart-title {
font-size: 15px;
font-weight: 500;
color: #333;
}
.chart-body {
position: relative;
}
.chart-view-wrapper {
width: 100%;
height: 320px;
display: flex;
align-items: center;
justify-content: center;
}
.echarts-view {
width: 100%;
height: 100%;
}
.loading-state, .empty-state {
display: flex;
align-items: center;
justify-content: center;
}
.loading-text, .empty-text {
color: #999;
font-size: 14px;
}
</style>