972 lines
26 KiB
Vue
972 lines
26 KiB
Vue
<template>
|
||
<view class="member-report">
|
||
<!-- 报表标题 -->
|
||
<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 total"></view>
|
||
<text class="metric-title">会员总数</text>
|
||
<view class="metric-value-container">
|
||
<text class="metric-value">{{ formatNumber(totalMembers) }}</text>
|
||
<text class="metric-unit">人</text>
|
||
</view>
|
||
<text class="metric-trend up">↑ {{ memberGrowth }}%</text>
|
||
</view>
|
||
<view class="metric-card">
|
||
<view class="metric-icon active"></view>
|
||
<text class="metric-title">活跃会员</text>
|
||
<view class="metric-value-container">
|
||
<text class="metric-value">{{ formatNumber(activeMembers) }}</text>
|
||
<text class="metric-unit">人</text>
|
||
</view>
|
||
<text class="metric-rate">({{ activeRate }}%)</text>
|
||
</view>
|
||
<view class="metric-card">
|
||
<view class="metric-icon new"></view>
|
||
<text class="metric-title">新增会员</text>
|
||
<view class="metric-value-container">
|
||
<text class="metric-value">{{ formatNumber(newMembers) }}</text>
|
||
<text class="metric-unit">人</text>
|
||
</view>
|
||
<text class="metric-period">本月</text>
|
||
</view>
|
||
</view>
|
||
|
||
<!-- 会员增长趋势 -->
|
||
<view class="chart-card">
|
||
<view class="chart-header">
|
||
<text class="chart-title">会员增长趋势</text>
|
||
<text class="chart-subtitle">最近12个月会员数量变化</text>
|
||
</view>
|
||
<view class="chart-content">
|
||
<view class="growth-chart-container">
|
||
<QiunDataCharts type="line" :opts="growthChartOpts" :chartData="growthChartData" :canvas2d="true"
|
||
:inScrollView="true" canvasId="memberGrowthChart" />
|
||
</view>
|
||
<view class="chart-controls">
|
||
<text class="control-btn" :class="{ active: growthPeriod === 'month' }"
|
||
@click="changeGrowthPeriod('month')">月度</text>
|
||
<text class="control-btn" :class="{ active: growthPeriod === 'quarter' }"
|
||
@click="changeGrowthPeriod('quarter')">季度</text>
|
||
<text class="control-btn" :class="{ active: growthPeriod === 'year' }"
|
||
@click="changeGrowthPeriod('year')">年度</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="member-levels">
|
||
<view class="level-item" v-for="(item, index) in memberLevels" :key="index">
|
||
<view class="level-info">
|
||
<view class="level-badge">{{ memberLevelObj && item.label &&
|
||
memberLevelObj[Number(item.label)]
|
||
? memberLevelObj[Number(item.label)] : '' }}</view>
|
||
<text class="level-count">{{ formatNumber(item.value) }}</text>
|
||
</view>
|
||
<view class="level-progress">
|
||
<view class="progress-bar" :style="{ width: item.key + '%' }"></view>
|
||
<text class="progress-text">{{ item.key }}%</text>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
|
||
<!-- 会员活跃度分析 -->
|
||
<view class="chart-card">
|
||
<view class="chart-header">
|
||
<text class="chart-title">会员活跃度分析</text>
|
||
<text class="chart-subtitle">最近30天会员活跃程度分布</text>
|
||
</view>
|
||
<view class="activity-analysis">
|
||
<view class="activity-grid">
|
||
<view class="activity-item" v-for="(item, index) in activityData" :key="index">
|
||
<text class="activity-label">{{ memberLevelObj && item.label &&
|
||
memberLevelObj[Number(item.label)]
|
||
? memberLevelObj[Number(item.label)] : '' }}</text>
|
||
<text class="activity-count">{{ formatNumber(item.value) }}</text>
|
||
<text class="activity-percent">{{ item.key }}%</text>
|
||
</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="source-chart">
|
||
<view class="source-item" v-for="(item, index) in memberSources" :key="index">
|
||
<view class="source-info">
|
||
<text class="source-name">{{ item.label }}</text>
|
||
<text class="source-count">{{ formatNumber(item.value) }}</text>
|
||
</view>
|
||
<view class="source-progress">
|
||
<view class="source-bar">
|
||
<view class="bar-fill" :style="{ width: item.key + '%' }"></view>
|
||
</view>
|
||
<text class="source-percent">{{ item.key }}%</text>
|
||
</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="value-analysis">
|
||
<view class="value-metrics">
|
||
<view class="value-item">
|
||
<text class="value-label">平均客单价</text>
|
||
<text class="value-amount">¥{{ formatMoney(avgOrderValue) }}</text>
|
||
<text class="value-trend up">↑ 12.5%</text>
|
||
</view>
|
||
<view class="value-item">
|
||
<text class="value-label">会员生命周期</text>
|
||
<text class="value-amount">{{ memberLifecycle }}天</text>
|
||
<text class="value-trend up">↑ 8.3%</text>
|
||
</view>
|
||
<view class="value-item">
|
||
<text class="value-label">复购率</text>
|
||
<text class="value-amount">{{ repurchaseRate }}%</text>
|
||
<text class="value-trend down">↓ 2.1%</text>
|
||
</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="retention-data">
|
||
<view class="retention-item" v-for="(retention, index) in retentionData" :key="index">
|
||
<text class="retention-period">{{ retention.period }}</text>
|
||
<view class="retention-bar">
|
||
<view class="bar-fill" :style="{ width: retention.rate + '%' }"></view>
|
||
</view>
|
||
<text class="retention-rate">{{ retention.rate }}%</text>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
|
||
<!-- 今日会员动态 -->
|
||
<view class="chart-card">
|
||
<view class="chart-header">
|
||
<text class="chart-title">今日会员动态</text>
|
||
<text class="live-indicator">实时更新</text>
|
||
</view>
|
||
<view class="live-stats">
|
||
<view class="live-item">
|
||
<text class="live-time">{{ currentTime }}</text>
|
||
<text class="live-event">新注册会员</text>
|
||
<text class="live-number">+{{ todayNewMembers }}</text>
|
||
</view>
|
||
<view class="live-item">
|
||
<text class="live-time">{{ lastActiveTime }}</text>
|
||
<text class="live-event">活跃会员</text>
|
||
<text class="live-number">{{ todayActiveMembers }}</text>
|
||
</view>
|
||
<view class="live-item">
|
||
<text class="live-time">{{ lastPurchaseTime }}</text>
|
||
<text class="live-event">会员消费</text>
|
||
<text class="live-number">¥{{ formatMoney(todayMemberRevenue) }}</text>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
</template>
|
||
|
||
<script>
|
||
import QiunDataCharts from './qiun-data-charts/components/qiun-data-charts/qiun-data-charts.vue'
|
||
import request from "@/util/index.js";
|
||
import { wrapTreeNode } from "../../../util/dateTime";
|
||
|
||
export default {
|
||
components: {
|
||
QiunDataCharts
|
||
},
|
||
data() {
|
||
return {
|
||
analysisPeriod: '2024年10月',
|
||
totalMembers: 156789,
|
||
activeMembers: 89456,
|
||
activeRate: 57.1,
|
||
newMembers: 3456,
|
||
memberGrowth: 12.5,
|
||
growthPeriod: 'month',
|
||
|
||
// 会员增长趋势数据
|
||
growthData: {
|
||
month: {
|
||
categories: ['1月', '2月', '3月', '4月', '5月', '6月', '7月', '8月', '9月', '10月', '11月', '12月'],
|
||
data: [145678, 146890, 148234, 149567, 150890, 152234, 153567, 154890, 156234, 156789, 158123, 159456]
|
||
},
|
||
quarter: {
|
||
categories: ['2023-Q1', '2023-Q2', '2023-Q3', '2023-Q4', '2024-Q1', '2024-Q2', '2024-Q3', '2024-Q4'],
|
||
data: [145678, 149567, 152234, 156789, 158123, 162456, 165789, 168234]
|
||
},
|
||
year: {
|
||
categories: ['2019', '2020', '2021', '2022', '2023', '2024'],
|
||
data: [45678, 67890, 89456, 112345, 145678, 168234]
|
||
}
|
||
},
|
||
|
||
avgOrderValue: 456.78,
|
||
memberLifecycle: 186,
|
||
repurchaseRate: 68.5,
|
||
todayNewMembers: 23,
|
||
todayActiveMembers: 1567,
|
||
todayMemberRevenue: 23456,
|
||
currentTime: '14:32',
|
||
lastActiveTime: '14:28',
|
||
lastPurchaseTime: '14:15',
|
||
memberLevels: [], // 会员等级
|
||
memberLevelObj: {},//会员类型的枚举
|
||
activityData: [], // 会员活跃度
|
||
memberSources: [],// 会员来源
|
||
retentionData: [
|
||
{ period: '次日留存', rate: 85.6 },
|
||
{ period: '7日留存', rate: 78.9 },
|
||
{ period: '30日留存', rate: 68.5 },
|
||
{ period: '90日留存', rate: 56.7 },
|
||
{ period: '180日留存', rate: 45.3 }
|
||
]
|
||
}
|
||
},
|
||
|
||
computed: {
|
||
// 会员增长图表数据
|
||
growthChartData() {
|
||
const currentData = this.growthData[this.growthPeriod];
|
||
return {
|
||
categories: currentData.categories,
|
||
series: [
|
||
{
|
||
name: '会员数量',
|
||
data: currentData.data
|
||
}
|
||
]
|
||
}
|
||
},
|
||
|
||
// 会员增长图表配置
|
||
growthChartOpts() {
|
||
return {
|
||
color: ['#576EFF'],
|
||
padding: [15, 15, 15, 15],
|
||
dataLabel: false,
|
||
legend: {
|
||
show: false
|
||
},
|
||
xAxis: {
|
||
disableGrid: true,
|
||
itemCount: this.growthPeriod === 'month' ? 6 : this.growthPeriod === 'quarter' ? 4 : 3
|
||
},
|
||
yAxis: {
|
||
gridType: 'dash',
|
||
dashLength: 2,
|
||
data: [{
|
||
min: Math.min(...this.growthData[this.growthPeriod].data) * 0.95
|
||
}]
|
||
},
|
||
extra: {
|
||
line: {
|
||
type: 'curve',
|
||
width: 3,
|
||
activeType: 'hollow',
|
||
linearType: 'custom',
|
||
linearOpacity: 0.3
|
||
},
|
||
area: {
|
||
opacity: 0.1
|
||
}
|
||
}
|
||
}
|
||
}
|
||
},
|
||
onReady() {
|
||
// 获取整个组件的数据
|
||
this.handleGetAllData()
|
||
},
|
||
methods: {
|
||
// 获取整个组件的数据
|
||
handleGetAllData() {
|
||
// 获取会员等级分布的数据
|
||
this.handleGetMemberLevelData()
|
||
// 获取会员活跃度分析
|
||
this.handleGetMemberActiveData()
|
||
// 获取会员来源分析
|
||
this.handleGetMemberComeData()
|
||
},
|
||
sortData(data) {
|
||
return data.sort((a, b) => b.value - a.value); // 按照 'value' 升序排序
|
||
},
|
||
// 获取会员来源分析
|
||
async handleGetMemberComeData() {
|
||
const req = {
|
||
CalcType: "5",
|
||
OwnerUnitId: 911,
|
||
ExcludeTest: true,
|
||
StartDate: '',
|
||
EndDate: "",
|
||
MembershipType: "",
|
||
MembershipLevel: "",
|
||
MembershipTarget: "",
|
||
type: "encryption"
|
||
}
|
||
const data = await request.$posPost(
|
||
"MemberApi/Member/GetMembershipCount",
|
||
req
|
||
);
|
||
let list = data.Result_Data.List
|
||
this.memberSources = this.sortData(list)
|
||
},
|
||
// 获取会员活跃度分析
|
||
async handleGetMemberActiveData() {
|
||
const req = {
|
||
CalcType: "4",
|
||
OwnerUnitId: 911,
|
||
ExcludeTest: true,
|
||
StartDate: '',
|
||
EndDate: "",
|
||
MembershipType: "",
|
||
MembershipLevel: "",
|
||
MembershipTarget: "",
|
||
type: "encryption"
|
||
}
|
||
const data = await request.$posPost(
|
||
"MemberApi/Member/GetMembershipCount",
|
||
req
|
||
);
|
||
let list = data.Result_Data.List
|
||
console.log('会员活跃度分析', list);
|
||
let res = []
|
||
if (list && list.length > 0) {
|
||
list.forEach((item) => {
|
||
if (item.value > 0) {
|
||
res.push(item)
|
||
}
|
||
})
|
||
}
|
||
this.activityData = this.sortData(res)
|
||
},
|
||
// 获取会员等级分布的数据
|
||
async handleGetMemberLevelData() {
|
||
// 请求一下会员等级的枚举
|
||
let objReq = {
|
||
FIELDEXPLAIN_FIELD: "MEMBERSHIP_LEVEL_YN",
|
||
FIELDEXPLAIN_ID: "",
|
||
FIELDENUM_PID: "",
|
||
FIELDENUM_STATUS: "",
|
||
SearchKey: "",
|
||
type: "encryption"
|
||
}
|
||
let levelObj = await request.$posPost(
|
||
"MemberApi/Dictionary/GetNestingFIELDENUMList",
|
||
objReq
|
||
);
|
||
let objList = wrapTreeNode(levelObj.Result_Data.List)
|
||
let levelResObj = {}
|
||
if (objList && objList.length > 0) {
|
||
objList.forEach((item) => {
|
||
levelResObj[item.FIELDENUM_VALUE] = item.FIELDENUM_NAME
|
||
})
|
||
}
|
||
this.memberLevelObj = levelResObj
|
||
console.log('this.memberLevelObjthis.memberLevelObj', this.memberLevelObj);
|
||
|
||
|
||
const req = {
|
||
CalcType: "2",
|
||
OwnerUnitId: 911,
|
||
ExcludeTest: true,
|
||
StartDate: '',
|
||
EndDate: "",
|
||
MembershipType: "",
|
||
MembershipLevel: "",
|
||
MembershipTarget: "",
|
||
type: "encryption"
|
||
}
|
||
const data = await request.$posPost(
|
||
"MemberApi/Member/GetMembershipCount",
|
||
req
|
||
);
|
||
let list = data.Result_Data.List
|
||
console.log('会员等级分布', list);
|
||
|
||
let res = []
|
||
if (list && list.length > 0) {
|
||
list.forEach((item) => {
|
||
if (item.value > 0) {
|
||
res.push(item)
|
||
}
|
||
})
|
||
}
|
||
this.memberLevels = this.sortData(res)
|
||
},
|
||
formatNumber(num) {
|
||
return num.toString().replace(/\B(?=(\d{3})+(?!\d))/g, ",");
|
||
},
|
||
formatMoney(amount) {
|
||
return amount.toString().replace(/\B(?=(\d{3})+(?!\d))/g, ",");
|
||
},
|
||
changeGrowthPeriod(period) {
|
||
this.growthPeriod = period;
|
||
console.log('切换增长周期:', period);
|
||
}
|
||
}
|
||
}
|
||
</script>
|
||
|
||
<style scoped lang="less">
|
||
@primary-color: #667eea;
|
||
@secondary-color: #764ba2;
|
||
@success-color: #52c41a;
|
||
@warning-color: #faad14;
|
||
@error-color: #ff4d4f;
|
||
@gold: #FFD700;
|
||
@silver: #C0C0C0;
|
||
@bronze: #CD7F32;
|
||
@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);
|
||
|
||
.member-report {
|
||
.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;
|
||
|
||
&.primary {
|
||
background: linear-gradient(135deg, @primary-color, @secondary-color);
|
||
color: white;
|
||
|
||
.metric-title,
|
||
.metric-unit,
|
||
.metric-trend {
|
||
color: rgba(255, 255, 255, 0.9);
|
||
}
|
||
|
||
.metric-value {
|
||
color: white;
|
||
}
|
||
}
|
||
|
||
&::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;
|
||
|
||
&.total {
|
||
background: linear-gradient(135deg, @primary-color, @secondary-color);
|
||
|
||
&::after {
|
||
content: '👥';
|
||
position: absolute;
|
||
top: 50%;
|
||
left: 50%;
|
||
transform: translate(-50%, -50%);
|
||
font-size: 24rpx;
|
||
}
|
||
}
|
||
|
||
&.active {
|
||
background: linear-gradient(135deg, @success-color, #73d13d);
|
||
|
||
&::after {
|
||
content: '✨';
|
||
position: absolute;
|
||
top: 50%;
|
||
left: 50%;
|
||
transform: translate(-50%, -50%);
|
||
font-size: 24rpx;
|
||
}
|
||
}
|
||
|
||
&.new {
|
||
background: linear-gradient(135deg, @warning-color, #ffc53d);
|
||
|
||
&::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: 8rpx;
|
||
}
|
||
|
||
.metric-value {
|
||
font-size: 32rpx;
|
||
font-weight: 600;
|
||
color: @text-primary;
|
||
margin-bottom: 4rpx;
|
||
font-family: 'DINAlternate-Bold', sans-serif;
|
||
}
|
||
|
||
.metric-unit {
|
||
font-size: 20rpx;
|
||
color: @text-light;
|
||
margin-right: 8rpx;
|
||
}
|
||
|
||
.metric-trend {
|
||
font-size: 20rpx;
|
||
font-weight: 500;
|
||
color: @success-color;
|
||
}
|
||
|
||
.metric-rate {
|
||
font-size: 20rpx;
|
||
color: @text-secondary;
|
||
font-weight: 500;
|
||
}
|
||
|
||
.metric-period {
|
||
font-size: 20rpx;
|
||
color: @text-secondary;
|
||
font-weight: 500;
|
||
}
|
||
}
|
||
}
|
||
|
||
.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;
|
||
margin-bottom: 4rpx;
|
||
}
|
||
|
||
.chart-subtitle {
|
||
font-size: 22rpx;
|
||
color: @text-light;
|
||
}
|
||
|
||
.live-indicator {
|
||
background: @success-color;
|
||
color: white;
|
||
font-size: 20rpx;
|
||
padding: 4rpx 12rpx;
|
||
border-radius: 20rpx;
|
||
font-weight: 500;
|
||
animation: pulse 2s infinite;
|
||
}
|
||
|
||
}
|
||
|
||
.growth-chart-container {
|
||
margin-bottom: 24rpx;
|
||
}
|
||
|
||
.chart-controls {
|
||
display: flex;
|
||
justify-content: center;
|
||
gap: 8rpx;
|
||
|
||
.control-btn {
|
||
font-size: 22rpx;
|
||
padding: 8rpx 16rpx;
|
||
border: 1rpx solid @text-light;
|
||
border-radius: 8rpx;
|
||
color: @text-secondary;
|
||
|
||
&.active {
|
||
border-color: @primary-color;
|
||
color: @primary-color;
|
||
background: rgba(102, 126, 234, 0.1);
|
||
}
|
||
}
|
||
}
|
||
|
||
.member-levels {
|
||
.level-item {
|
||
margin-bottom: 20rpx;
|
||
|
||
.level-info {
|
||
display: flex;
|
||
justify-content: space-between;
|
||
align-items: center;
|
||
margin-bottom: 8rpx;
|
||
|
||
.level-badge {
|
||
padding: 6rpx 12rpx;
|
||
border-radius: 12rpx;
|
||
font-size: 22rpx;
|
||
font-weight: 500;
|
||
color: #010c13;
|
||
background: linear-gradient(135deg, #b9f2ff, #69c0ff);
|
||
}
|
||
|
||
|
||
.level-count {
|
||
font-size: 26rpx;
|
||
font-weight: 600;
|
||
color: @text-primary;
|
||
}
|
||
}
|
||
|
||
.level-progress {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 12rpx;
|
||
|
||
.progress-bar {
|
||
flex: 1;
|
||
height: 16rpx;
|
||
background: linear-gradient(90deg, @primary-color, @secondary-color);
|
||
border-radius: 8rpx;
|
||
}
|
||
|
||
.progress-text {
|
||
font-size: 22rpx;
|
||
color: @text-secondary;
|
||
font-weight: 500;
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
.activity-analysis {
|
||
.activity-grid {
|
||
display: grid;
|
||
grid-template-columns: repeat(2, 1fr);
|
||
gap: 20rpx;
|
||
|
||
.activity-item {
|
||
background: linear-gradient(135deg, #f8f9fa 0%, #e9ecef 100%);
|
||
border-radius: 12rpx;
|
||
padding: 24rpx;
|
||
text-align: center;
|
||
position: relative;
|
||
|
||
.activity-icon {
|
||
width: 48rpx;
|
||
height: 48rpx;
|
||
border-radius: 50%;
|
||
margin: 0 auto 12rpx;
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
|
||
&.high {
|
||
background: rgba(82, 196, 26, 0.2);
|
||
}
|
||
|
||
&.medium {
|
||
background: rgba(102, 126, 234, 0.2);
|
||
}
|
||
|
||
&.low {
|
||
background: rgba(250, 173, 20, 0.2);
|
||
}
|
||
|
||
&.inactive {
|
||
background: rgba(153, 153, 153, 0.2);
|
||
}
|
||
|
||
.icon-text {
|
||
font-size: 24rpx;
|
||
}
|
||
}
|
||
|
||
.activity-label {
|
||
font-size: 24rpx;
|
||
color: @text-primary;
|
||
margin-bottom: 8rpx;
|
||
display: block;
|
||
}
|
||
|
||
.activity-count {
|
||
font-size: 24rpx;
|
||
font-weight: 600;
|
||
color: @text-primary;
|
||
margin-bottom: 4rpx;
|
||
display: block;
|
||
}
|
||
|
||
.activity-percent {
|
||
font-size: 24rpx;
|
||
color: @text-secondary;
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
.source-chart {
|
||
.source-item {
|
||
margin-bottom: 20rpx;
|
||
|
||
.source-info {
|
||
display: flex;
|
||
justify-content: space-between;
|
||
align-items: center;
|
||
margin-bottom: 8rpx;
|
||
|
||
.source-name {
|
||
font-size: 26rpx;
|
||
color: @text-primary;
|
||
font-weight: 500;
|
||
}
|
||
|
||
.source-count {
|
||
font-size: 24rpx;
|
||
color: @text-secondary;
|
||
font-weight: 500;
|
||
}
|
||
}
|
||
|
||
.source-progress {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 12rpx;
|
||
|
||
.source-bar {
|
||
flex: 1;
|
||
height: 16rpx;
|
||
background: #f0f0f0;
|
||
border-radius: 8rpx;
|
||
overflow: hidden;
|
||
|
||
.bar-fill {
|
||
height: 100%;
|
||
background: linear-gradient(90deg, @primary-color, @secondary-color);
|
||
border-radius: 8rpx;
|
||
}
|
||
}
|
||
|
||
.source-percent {
|
||
font-size: 22rpx;
|
||
color: @text-secondary;
|
||
font-weight: 500;
|
||
min-width: 50rpx;
|
||
text-align: right;
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
.value-analysis {
|
||
.value-metrics {
|
||
display: flex;
|
||
gap: 20rpx;
|
||
|
||
.value-item {
|
||
flex: 1;
|
||
background: linear-gradient(135deg, #f8f9fa 0%, #e9ecef 100%);
|
||
border-radius: 12rpx;
|
||
padding: 24rpx;
|
||
text-align: center;
|
||
|
||
.value-label {
|
||
font-size: 22rpx;
|
||
color: @text-secondary;
|
||
margin-bottom: 8rpx;
|
||
display: block;
|
||
}
|
||
|
||
.value-amount {
|
||
font-size: 28rpx;
|
||
font-weight: 600;
|
||
color: @text-primary;
|
||
margin-bottom: 8rpx;
|
||
display: block;
|
||
}
|
||
|
||
.value-trend {
|
||
font-size: 20rpx;
|
||
font-weight: 500;
|
||
|
||
&.up {
|
||
color: @success-color;
|
||
}
|
||
|
||
&.down {
|
||
color: @error-color;
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
.retention-data {
|
||
.retention-item {
|
||
display: flex;
|
||
align-items: center;
|
||
margin-bottom: 16rpx;
|
||
|
||
.retention-period {
|
||
width: 120rpx;
|
||
font-size: 24rpx;
|
||
color: @text-primary;
|
||
font-weight: 500;
|
||
}
|
||
|
||
.retention-bar {
|
||
flex: 1;
|
||
height: 20rpx;
|
||
background: #f0f0f0;
|
||
border-radius: 10rpx;
|
||
margin: 0 16rpx;
|
||
overflow: hidden;
|
||
|
||
.bar-fill {
|
||
height: 100%;
|
||
background: linear-gradient(90deg, @success-color, #73d13d);
|
||
border-radius: 10rpx;
|
||
}
|
||
}
|
||
|
||
.retention-rate {
|
||
width: 60rpx;
|
||
text-align: right;
|
||
font-size: 22rpx;
|
||
color: @text-secondary;
|
||
font-weight: 500;
|
||
}
|
||
}
|
||
}
|
||
|
||
.live-stats {
|
||
.live-item {
|
||
display: flex;
|
||
align-items: center;
|
||
padding: 16rpx 0;
|
||
border-bottom: 1rpx solid #f0f0f0;
|
||
|
||
&:last-child {
|
||
border-bottom: none;
|
||
}
|
||
|
||
.live-time {
|
||
width: 80rpx;
|
||
font-size: 22rpx;
|
||
color: @text-light;
|
||
}
|
||
|
||
.live-event {
|
||
flex: 1;
|
||
font-size: 24rpx;
|
||
color: @text-primary;
|
||
margin: 0 20rpx;
|
||
}
|
||
|
||
.live-number {
|
||
font-size: 26rpx;
|
||
font-weight: 600;
|
||
color: @success-color;
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
@keyframes pulse {
|
||
0% {
|
||
opacity: 1;
|
||
}
|
||
|
||
50% {
|
||
opacity: 0.7;
|
||
}
|
||
|
||
100% {
|
||
opacity: 1;
|
||
}
|
||
}
|
||
</style> |