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

1179 lines
32 KiB
Vue

<template>
<view class="custom-data-analysis">
<!-- 分析控制面板 -->
<view class="analysis-control">
<view class="control-header">
<text class="control-title">自定义数据分析</text>
<text class="control-subtitle">业主可自定义按任意空间和时间进行各类服务区数据服务商数据店铺数据分析</text>
</view>
<!-- 分析类型选择 -->
<view class="analysis-type-tabs">
<view class="type-tabs-container">
<view
v-for="(type, index) in analysisTypes"
:key="index"
class="type-tab"
:class="{ active: currentAnalysisType === index }"
@click="switchAnalysisType(index)"
>
<text class="type-tab-text">{{ type.name }}</text>
</view>
</view>
</view>
<!-- 筛选条件 -->
<view class="filter-section">
<view class="filter-group">
<text class="filter-label">空间范围</text>
<view class="filter-options">
<view
v-for="(space, index) in spaceOptions"
:key="index"
class="filter-option"
:class="{ active: selectedSpace === space.value }"
@click="selectSpace(space.value)"
>
<text class="option-text">{{ space.label }}</text>
</view>
</view>
</view>
<view class="filter-group">
<text class="filter-label">时间范围</text>
<view class="filter-options">
<view
v-for="(time, index) in timeOptions"
:key="index"
class="filter-option"
:class="{ active: selectedTime === time.value }"
@click="selectTime(time.value)"
>
<text class="option-text">{{ time.label }}</text>
</view>
</view>
</view>
<view class="filter-group" v-if="currentAnalysisType === 1">
<text class="filter-label">对比模式</text>
<view class="filter-options">
<view
v-for="(compare, index) in compareOptions"
:key="index"
class="filter-option"
:class="{ active: selectedCompare === compare.value }"
@click="selectCompare(compare.value)"
>
<text class="option-text">{{ compare.label }}</text>
</view>
</view>
</view>
</view>
</view>
<!-- 数据概览卡片 -->
<view class="overview-cards">
<view class="overview-card">
<view class="card-icon revenue"></view>
<text class="card-title">总营收</text>
<text class="card-value">¥{{ formatMoney(overviewData.totalRevenue) }}</text>
<text class="card-change positive">+{{ overviewData.revenueGrowth }}%</text>
</view>
<view class="overview-card">
<view class="card-icon orders"></view>
<text class="card-title">订单量</text>
<text class="card-value">{{ formatNumber(overviewData.totalOrders) }}</text>
<text class="card-change positive">+{{ overviewData.ordersGrowth }}%</text>
</view>
<view class="overview-card">
<view class="card-icon customers"></view>
<text class="card-title">客流量</text>
<text class="card-value">{{ formatNumber(overviewData.totalCustomers) }}</text>
<text class="card-change negative">-{{ overviewData.customersGrowth }}%</text>
</view>
<view class="overview-card">
<view class="card-icon satisfaction"></view>
<text class="card-title">满意度</text>
<text class="card-value">{{ overviewData.satisfaction }}%</text>
<text class="card-change positive">+{{ overviewData.satisfactionGrowth }}%</text>
</view>
</view>
<!-- 主要图表区域 -->
<view class="main-charts">
<!-- 营收趋势对比图 -->
<view class="chart-card">
<view class="chart-header">
<view class="chart-title-group">
<text class="chart-title">{{ getMainChartTitle() }}</text>
<text class="chart-subtitle">{{ getMainChartSubtitle() }}</text>
</view>
<view class="chart-actions">
<text class="action-btn" @click="refreshData">刷新</text>
<text class="action-btn" @click="exportChart">导出</text>
</view>
</view>
<view class="chart-container">
<QiunDataCharts
type="line"
:opts="mainLineChartOpts"
:chartData="mainLineChartData"
:canvas2d="true"
:inScrollView="true"
canvasId="mainAnalysisChart"
/>
<view class="chart-legend">
<view class="legend-item" v-for="(item, index) in mainChartLegend" :key="index">
<view class="legend-dot" :style="{ backgroundColor: item.color }"></view>
<text class="legend-text">{{ item.name }}</text>
</view>
</view>
</view>
</view>
<!-- 服务区对比分析 -->
<view class="chart-card">
<view class="chart-header">
<view class="chart-title-group">
<text class="chart-title">{{ getComparisonTitle() }}</text>
<text class="chart-subtitle">{{ getComparisonSubtitle() }}</text>
</view>
</view>
<view class="chart-container">
<QiunDataCharts
type="column"
:opts="comparisonChartOpts"
:chartData="comparisonChartData"
:canvas2d="true"
:inScrollView="true"
canvasId="comparisonChart"
/>
</view>
</view>
<!-- 地理分布热力图 -->
<view class="chart-card">
<view class="chart-header">
<view class="chart-title-group">
<text class="chart-title">地理分布热力图</text>
<text class="chart-subtitle">各区域经营状况空间分布</text>
</view>
</view>
<view class="heatmap-container">
<view class="heatmap-grid">
<view
v-for="(area, index) in heatmapData"
:key="index"
class="heatmap-cell"
:class="area.value >= 85 ? 'heatmap-high' : area.value >= 70 ? 'heatmap-medium' : 'heatmap-low'"
:style="{ gridArea: area.position }"
>
<text class="area-name">{{ area.name }}</text>
<text class="area-value">{{ area.value }}</text>
</view>
</view>
<view class="heatmap-legend">
<text class="legend-label"></text>
<view class="legend-gradient"></view>
<text class="legend-label"></text>
</view>
</view>
</view>
</view>
<!-- 详细数据卡片 -->
<view class="detail-cards">
<view class="cards-header">
<text class="cards-title">详细数据</text>
<view class="cards-actions">
<text class="action-btn" @click="toggleTableFilter">筛选</text>
<text class="action-btn" @click="exportTableData">导出</text>
</view>
</view>
<view class="cards-container">
<view
v-for="(item, index) in tableData"
:key="index"
class="detail-card"
:class="{ highlighted: item.highlighted }"
>
<!-- 卡片头部 -->
<view class="card-header">
<view class="card-title-section">
<text class="card-name">{{ item.name }}</text>
<view class="trend-badge" :class="item.trend > 0 ? 'positive' : 'negative'">
<text class="trend-icon">{{ item.trend > 0 ? '↑' : '↓' }}</text>
<text class="trend-text">{{ item.trend > 0 ? '+' : '' }}{{ item.trend }}%</text>
</view>
</view>
<view class="satisfaction-badge" :class="item.satisfaction >= 95 ? 'excellent' : item.satisfaction >= 90 ? 'good' : item.satisfaction >= 85 ? 'average' : 'poor'">
<text class="satisfaction-text">{{ item.satisfaction }}%</text>
</view>
</view>
<!-- 核心指标 -->
<view class="card-metrics">
<view class="metric-item revenue">
<view class="metric-icon">💰</view>
<view class="metric-info">
<text class="metric-label">营收</text>
<text class="metric-value">¥{{ formatMoney(item.revenue) }}</text>
</view>
</view>
<view class="metric-item orders">
<view class="metric-icon">📋</view>
<view class="metric-info">
<text class="metric-label">订单</text>
<text class="metric-value">{{ formatNumber(item.orders) }}</text>
</view>
</view>
<view class="metric-item customers">
<view class="metric-icon">👥</view>
<view class="metric-info">
<text class="metric-label">客流</text>
<text class="metric-value">{{ formatNumber(item.customers) }}</text>
</view>
</view>
</view>
<!-- 数据进度条 -->
<view class="card-progress">
<view class="progress-item">
<text class="progress-label">营收完成度</text>
<view class="progress-bar">
<view class="progress-fill" :style="{ width: (item.revenue / 500000 * 100) + '%' }"></view>
</view>
<text class="progress-value">{{ Math.round(item.revenue / 500000 * 100) }}%</text>
</view>
<view class="progress-item">
<text class="progress-label">订单完成度</text>
<view class="progress-bar">
<view class="progress-fill" :style="{ width: (item.orders / 4000 * 100) + '%' }"></view>
</view>
<text class="progress-value">{{ Math.round(item.orders / 4000 * 100) }}%</text>
</view>
</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 {
// 分析类型
currentAnalysisType: 0,
analysisTypes: [
{ name: '业主分析', key: 'owner' },
{ name: '管理方对比', key: 'manager' }
],
// 筛选条件
selectedSpace: 'all',
selectedTime: 'month',
selectedCompare: 'area',
spaceOptions: [
{ label: '全部区域', value: 'all' },
{ label: '服务区A', value: 'area_a' },
{ label: '服务区B', value: 'area_b' },
{ label: '服务区C', value: 'area_c' },
{ label: '店铺1-5', value: 'shop_1_5' },
{ label: '店铺6-10', value: 'shop_6_10' }
],
timeOptions: [
{ label: '今日', value: 'today' },
{ label: '本周', value: 'week' },
{ label: '本月', value: 'month' },
{ label: '本季度', value: 'quarter' },
{ label: '本年', value: 'year' },
{ label: '自定义', value: 'custom' }
],
compareOptions: [
{ label: '服务区对比', value: 'area' },
{ label: '店铺对比', value: 'shop' },
{ label: '时间对比', value: 'time' },
{ label: '商品对比', value: 'product' }
],
// 概览数据
overviewData: {
totalRevenue: 2456789,
revenueGrowth: 12.5,
totalOrders: 15678,
ordersGrowth: 8.3,
totalCustomers: 8934,
customersGrowth: 2.1,
satisfaction: 94.5,
satisfactionGrowth: 3.2
},
// 主要图表数据
mainChartData: {
categories: ['1月', '2月', '3月', '4月', '5月', '6月'],
series: [
{
name: '服务区A',
data: [234567, 256789, 278901, 298765, 323456, 345678]
},
{
name: '服务区B',
data: [198765, 212345, 234567, 245678, 267890, 289012]
},
{
name: '服务区C',
data: [156789, 178901, 198765, 212345, 234567, 256789]
}
]
},
// 对比图表数据
comparisonData: {
categories: ['营收', '订单量', '客流量', '满意度', '复购率'],
series: [
{
name: '当前时期',
data: [345678, 5678, 2345, 94.5, 67.8]
},
{
name: '上期对比',
data: [298765, 4890, 2123, 89.2, 62.3]
}
]
},
// 热力图数据
heatmapData: [
{ name: '昆明服务区', value: 95, position: '1/1' },
{ name: '大理服务区', value: 78, position: '1/2' },
{ name: '丽江服务区', value: 82, position: '1/3' },
{ name: '西双版纳服务区', value: 65, position: '2/1' },
{ name: '香格里拉服务区', value: 71, position: '2/2' },
{ name: '普洱服务区', value: 88, position: '2/3' }
],
// 表格数据
tableData: [
{ name: '昆明服务区-店铺A', revenue: 456789, orders: 3456, customers: 1234, satisfaction: 96.5, trend: 12.3, highlighted: true },
{ name: '昆明服务区-店铺B', revenue: 398765, orders: 2890, customers: 1089, satisfaction: 94.2, trend: 8.7 },
{ name: '大理服务区-店铺A', revenue: 345678, orders: 2678, customers: 987, satisfaction: 92.8, trend: -2.1 },
{ name: '大理服务区-店铺B', revenue: 298765, orders: 2345, customers: 876, satisfaction: 95.1, trend: 5.6 },
{ name: '丽江服务区-店铺A', revenue: 378901, orders: 2789, customers: 1023, satisfaction: 93.7, trend: 9.8 },
{ name: '丽江服务区-店铺B', revenue: 312456, orders: 2456, customers: 912, satisfaction: 91.4, trend: 3.2 }
]
}
},
computed: {
// 主要图表图例
mainChartLegend() {
return this.mainChartData.series.map((item, index) => ({
name: item.name,
color: ['#576EFF', '#52C41A', '#FAAD14'][index]
}))
},
// 主要图表数据
mainLineChartData() {
return {
categories: this.mainChartData.categories,
series: this.mainChartData.series
}
},
// 主要图表配置
mainLineChartOpts() {
return {
color: ['#576EFF', '#52C41A', '#FAAD14'],
padding: [15, 15, 15, 15],
dataLabel: false,
legend: {
show: false
},
xAxis: {
disableGrid: true
},
yAxis: {
gridType: 'dash',
dashLength: 2,
data: [{
min: 0,
format: 'yAxisFormat'
}]
},
extra: {
line: {
type: 'curve',
width: 3,
activeType: 'hollow'
}
}
}
},
// 对比图表数据
comparisonChartData() {
return {
categories: this.comparisonData.categories,
series: this.comparisonData.series
}
},
// 对比图表配置
comparisonChartOpts() {
return {
color: ['#576EFF', '#FF7875'],
padding: [15, 15, 15, 15],
dataLabel: false,
legend: {
show: false
},
xAxis: {
disableGrid: true
},
yAxis: {
gridType: 'dash',
dashLength: 2,
data: [{
min: 0
}]
},
extra: {
column: {
type: 'group',
width: 40,
activeBgColor: '#000000',
activeBgOpacity: 0.08,
linearType: 'custom',
barBorderCircle: true
}
}
}
}
},
methods: {
// 切换分析类型
switchAnalysisType(index) {
this.currentAnalysisType = index
this.refreshData()
},
// 选择空间范围
selectSpace(value) {
this.selectedSpace = value
this.refreshData()
},
// 选择时间范围
selectTime(value) {
this.selectedTime = value
this.refreshData()
},
// 选择对比模式
selectCompare(value) {
this.selectedCompare = value
this.refreshData()
},
// 获取主图表标题
getMainChartTitle() {
if (this.currentAnalysisType === 0) {
return '经营趋势分析'
} else {
return '对比趋势分析'
}
},
// 获取主图表副标题
getMainChartSubtitle() {
const spaceLabel = this.spaceOptions.find(s => s.value === this.selectedSpace)?.label || '全部区域'
const timeLabel = this.timeOptions.find(t => t.value === this.selectedTime)?.label || '本月'
return spaceLabel + ' - ' + timeLabel + '数据趋势'
},
// 获取对比标题
getComparisonTitle() {
if (this.currentAnalysisType === 0) {
return '服务区表现对比'
} else {
const compareLabel = this.compareOptions.find(c => c.value === this.selectedCompare)?.label || '服务区对比'
return compareLabel
}
},
// 获取对比副标题
getComparisonSubtitle() {
return '统一时空条件下不同维度的经营数据对比'
},
// 获取热力图样式类
getHeatmapClass(value) {
if (value >= 85) return 'heatmap-high'
if (value >= 70) return 'heatmap-medium'
return 'heatmap-low'
},
// 获取满意度等级样式类
getSatisfactionClass(satisfaction) {
if (satisfaction >= 95) return 'excellent'
if (satisfaction >= 90) return 'good'
if (satisfaction >= 85) return 'average'
return 'poor'
},
// 刷新数据
refreshData() {
console.log('刷新数据', {
analysisType: this.currentAnalysisType,
space: this.selectedSpace,
time: this.selectedTime,
compare: this.selectedCompare
})
// 这里可以调用API获取新数据
},
// 导出图表
exportChart() {
console.log('导出图表')
},
// 切换表格筛选
toggleTableFilter() {
console.log('打开表格筛选器')
},
// 导出表格数据
exportTableData() {
console.log('导出表格数据')
},
// 格式化数字
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;
@success-color: #52c41a;
@warning-color: #faad14;
@error-color: #ff4d4f;
@text-primary: #333;
@text-secondary: #666;
@text-light: #999;
@bg-white: #ffffff;
@bg-light: #f8f9fb;
@border-radius: 16rpx;
@shadow: 0 4px 20px rgba(0, 0, 0, 0.08);
@shadow-light: 0 2px 12px rgba(0, 0, 0, 0.06);
.custom-data-analysis {
.analysis-control {
background: @bg-white;
border-radius: @border-radius;
padding: 32rpx;
box-shadow: @shadow;
margin-bottom: 32rpx;
.control-header {
margin-bottom: 32rpx;
.control-title {
font-size: 36rpx;
font-weight: 600;
color: @text-primary;
margin-bottom: 8rpx;
display: block;
}
.control-subtitle {
font-size: 24rpx;
color: @text-secondary;
line-height: 1.5;
}
}
.analysis-type-tabs {
margin-bottom: 32rpx;
.type-tabs-container {
display: flex;
background: @bg-light;
border-radius: 12rpx;
padding: 8rpx;
.type-tab {
flex: 1;
padding: 16rpx 24rpx;
border-radius: 8rpx;
text-align: center;
transition: all 0.3s ease;
&.active {
background: @primary-color;
.type-tab-text {
color: white;
font-weight: 600;
}
}
.type-tab-text {
font-size: 28rpx;
color: @text-secondary;
transition: all 0.3s ease;
}
}
}
}
.filter-section {
.filter-group {
margin-bottom: 24rpx;
&:last-child {
margin-bottom: 0;
}
.filter-label {
font-size: 28rpx;
color: @text-primary;
font-weight: 600;
margin-bottom: 16rpx;
display: block;
}
.filter-options {
display: flex;
flex-wrap: wrap;
gap: 16rpx;
.filter-option {
padding: 12rpx 24rpx;
border: 2rpx solid #e0e0e0;
border-radius: 24rpx;
background: @bg-white;
transition: all 0.3s ease;
&.active {
border-color: @primary-color;
background: @primary-color;
.option-text {
color: white;
font-weight: 600;
}
}
.option-text {
font-size: 26rpx;
color: @text-secondary;
transition: all 0.3s ease;
}
}
}
}
}
}
.overview-cards {
display: grid;
grid-template-columns: repeat(2, 1fr);
gap: 24rpx;
margin-bottom: 32rpx;
.overview-card {
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);
}
.card-icon {
width: 48rpx;
height: 48rpx;
margin: 0 auto 16rpx;
border-radius: 50%;
position: relative;
&.revenue {
background: linear-gradient(135deg, #52c41a, #73d13d);
&::after { content: '💰'; position: absolute; top: 50%; left: 50%; transform: translate(-50%, -50%); font-size: 24rpx; }
}
&.orders {
background: linear-gradient(135deg, #1890ff, #40a9ff);
&::after { content: '📋'; position: absolute; top: 50%; left: 50%; transform: translate(-50%, -50%); font-size: 24rpx; }
}
&.customers {
background: linear-gradient(135deg, #faad14, #ffc53d);
&::after { content: '👥'; position: absolute; top: 50%; left: 50%; transform: translate(-50%, -50%); font-size: 24rpx; }
}
&.satisfaction {
background: linear-gradient(135deg, #ff7875, #ff9c9c);
&::after { content: '⭐'; position: absolute; top: 50%; left: 50%; transform: translate(-50%, -50%); font-size: 24rpx; }
}
}
.card-title {
font-size: 24rpx;
color: @text-secondary;
margin-bottom: 12rpx;
display: block;
}
.card-value {
font-size: 32rpx;
font-weight: 600;
color: @text-primary;
margin-bottom: 8rpx;
display: block;
}
.card-change {
font-size: 22rpx;
font-weight: 600;
&.positive {
color: @success-color;
}
&.negative {
color: @error-color;
}
}
}
}
.main-charts {
.chart-card {
background: @bg-white;
border-radius: @border-radius;
padding: 32rpx;
box-shadow: @shadow;
margin-bottom: 32rpx;
.chart-header {
display: flex;
justify-content: space-between;
align-items: flex-start;
margin-bottom: 24rpx;
.chart-title-group {
.chart-title {
font-size: 32rpx;
font-weight: 600;
color: @text-primary;
margin-bottom: 8rpx;
display: block;
}
.chart-subtitle {
font-size: 24rpx;
color: @text-light;
}
}
.chart-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-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%;
}
.legend-text {
font-size: 22rpx;
color: @text-secondary;
}
}
}
}
.heatmap-container {
.heatmap-grid {
display: grid;
grid-template-columns: repeat(3, 1fr);
grid-template-rows: repeat(2, 120rpx);
gap: 16rpx;
margin-bottom: 24rpx;
.heatmap-cell {
border-radius: 12rpx;
padding: 16rpx;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
transition: all 0.3s ease;
&.heatmap-high {
background: linear-gradient(135deg, #ff7875, #ff4d4f);
.area-name, .area-value {
color: white;
}
}
&.heatmap-medium {
background: linear-gradient(135deg, #faad14, #ffc53d);
.area-name, .area-value {
color: white;
}
}
&.heatmap-low {
background: linear-gradient(135deg, #52c41a, #73d13d);
.area-name, .area-value {
color: white;
}
}
.area-name {
font-size: 22rpx;
font-weight: 600;
margin-bottom: 4rpx;
}
.area-value {
font-size: 24rpx;
font-weight: 700;
}
}
}
.heatmap-legend {
display: flex;
align-items: center;
justify-content: center;
gap: 16rpx;
.legend-label {
font-size: 22rpx;
color: @text-secondary;
}
.legend-gradient {
width: 120rpx;
height: 12rpx;
background: linear-gradient(90deg, #52c41a, #faad14, #ff7875);
border-radius: 6rpx;
}
}
}
}
}
.detail-cards {
background: @bg-white;
border-radius: @border-radius;
padding: 32rpx;
box-shadow: @shadow;
.cards-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 32rpx;
.cards-title {
font-size: 32rpx;
font-weight: 600;
color: @text-primary;
}
.cards-actions {
display: flex;
gap: 16rpx;
.action-btn {
font-size: 24rpx;
color: @primary-color;
padding: 8rpx 16rpx;
border: 1rpx solid @primary-color;
border-radius: 8rpx;
}
}
}
.cards-container {
.detail-card {
background: linear-gradient(135deg, #fafbfc 0%, #f5f7fa 100%);
border-radius: 20rpx;
padding: 24rpx;
margin-bottom: 24rpx;
border: 1rpx solid #f0f2f5;
position: relative;
overflow: hidden;
transition: all 0.3s ease;
&:active {
transform: translateY(-2rpx);
box-shadow: 0 8px 30px rgba(0, 0, 0, 0.12);
}
&.highlighted {
border-color: @primary-color;
background: linear-gradient(135deg, rgba(87, 110, 255, 0.05) 0%, rgba(87, 110, 255, 0.02) 100%);
}
&::before {
content: '';
position: absolute;
top: 0;
left: 0;
right: 0;
height: 4rpx;
background: linear-gradient(90deg, @primary-color, @secondary-color);
}
.card-header {
display: flex;
justify-content: space-between;
align-items: flex-start;
margin-bottom: 20rpx;
.card-title-section {
flex: 1;
margin-right: 16rpx;
.card-name {
font-size: 28rpx;
font-weight: 600;
color: @text-primary;
margin-bottom: 8rpx;
display: block;
line-height: 1.3;
}
.trend-badge {
display: inline-flex;
align-items: center;
gap: 4rpx;
padding: 6rpx 12rpx;
border-radius: 16rpx;
font-size: 22rpx;
font-weight: 600;
&.positive {
background: rgba(82, 196, 26, 0.1);
color: @success-color;
}
&.negative {
background: rgba(255, 77, 79, 0.1);
color: @error-color;
}
.trend-icon {
font-size: 20rpx;
}
.trend-text {
font-size: 22rpx;
}
}
}
.satisfaction-badge {
padding: 8rpx 16rpx;
border-radius: 20rpx;
font-size: 22rpx;
font-weight: 600;
color: white;
&.excellent {
background: linear-gradient(135deg, #52c41a, #73d13d);
}
&.good {
background: linear-gradient(135deg, @primary-color, #7c8fff);
}
&.average {
background: linear-gradient(135deg, @warning-color, #ffc53d);
}
&.poor {
background: linear-gradient(135deg, @error-color, #ff7875);
}
.satisfaction-text {
font-size: 22rpx;
}
}
}
.card-metrics {
display: flex;
justify-content: space-between;
margin-bottom: 20rpx;
gap: 16rpx;
.metric-item {
flex: 1;
display: flex;
flex-direction: column;
align-items: center;
padding: 16rpx 12rpx;
background: @bg-white;
border-radius: 16rpx;
border: 1rpx solid #f0f0f0;
transition: all 0.3s ease;
&:active {
transform: scale(0.95);
}
.metric-icon {
font-size: 32rpx;
margin-bottom: 8rpx;
}
.metric-info {
text-align: center;
.metric-label {
font-size: 20rpx;
color: @text-secondary;
margin-bottom: 4rpx;
display: block;
}
.metric-value {
font-size: 24rpx;
font-weight: 600;
color: @text-primary;
display: block;
line-height: 1.2;
}
}
&.revenue {
.metric-icon {
filter: hue-rotate(45deg);
}
}
&.orders {
.metric-icon {
filter: hue-rotate(180deg);
}
}
&.customers {
.metric-icon {
filter: hue-rotate(270deg);
}
}
}
}
.card-progress {
.progress-item {
display: flex;
align-items: center;
margin-bottom: 12rpx;
gap: 12rpx;
&:last-child {
margin-bottom: 0;
}
.progress-label {
font-size: 22rpx;
color: @text-secondary;
width: 120rpx;
flex-shrink: 0;
}
.progress-bar {
flex: 1;
height: 12rpx;
background: #f0f0f0;
border-radius: 6rpx;
overflow: hidden;
.progress-fill {
height: 100%;
background: linear-gradient(90deg, @primary-color, @secondary-color);
border-radius: 6rpx;
transition: width 0.3s ease;
}
}
.progress-value {
font-size: 22rpx;
font-weight: 600;
color: @primary-color;
width: 60rpx;
text-align: right;
flex-shrink: 0;
}
}
}
}
}
}
}
</style>