1179 lines
32 KiB
Vue
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> |