481 lines
15 KiB
Plaintext
481 lines
15 KiB
Plaintext
<template>
|
||
<view class="admin-marketing-integral-statistic">
|
||
<view class="content-body">
|
||
<!-- 顶部时间选择 -->
|
||
<view class="filter-card border-shadow">
|
||
<view class="filter-item">
|
||
<text class="label-txt">时间选择:</text>
|
||
<view class="date-picker-mock">
|
||
<text class="calendar-ic">📅</text>
|
||
<text class="date-range">2026/01/05 - 2026/02/03</text>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
|
||
<!-- 核心指标卡片 -->
|
||
<view class="stats-row">
|
||
<view class="stat-card border-shadow">
|
||
<view class="sc-left bg-blue">
|
||
<text class="sc-icon">💠</text>
|
||
</view>
|
||
<view class="sc-right">
|
||
<text class="sc-val">744904340.25</text>
|
||
<text class="sc-label">当前积分</text>
|
||
</view>
|
||
</view>
|
||
<view class="stat-card border-shadow">
|
||
<view class="sc-left bg-orange">
|
||
<text class="sc-icon">🪙</text>
|
||
</view>
|
||
<view class="sc-right">
|
||
<text class="sc-val">59026484</text>
|
||
<text class="sc-label">累计总积分</text>
|
||
</view>
|
||
</view>
|
||
<view class="stat-card border-shadow">
|
||
<view class="sc-left bg-green">
|
||
<text class="sc-icon">💎</text>
|
||
</view>
|
||
<view class="sc-right">
|
||
<text class="sc-val">3189</text>
|
||
<text class="sc-label">累计消耗积分</text>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
|
||
<!-- 积分使用趋势 -->
|
||
<view class="chart-card border-shadow">
|
||
<view class="chart-header">
|
||
<text class="chart-title">积分使用趋势</text>
|
||
<view class="chart-legend">
|
||
<view class="legend-item">
|
||
<view class="l-dot bg-blue-line"></view>
|
||
<text class="l-txt">积分积累</text>
|
||
</view>
|
||
<view class="legend-item">
|
||
<view class="l-dot bg-green-line"></view>
|
||
<text class="l-txt">积分消耗</text>
|
||
</view>
|
||
<text class="down-ic">📥</text>
|
||
</view>
|
||
</view>
|
||
<!-- Mock 线图 -->
|
||
<view class="line-chart-box">
|
||
<view class="y-labels">
|
||
<text class="y-txt">3,500,000</text>
|
||
<text class="y-txt">3,000,000</text>
|
||
<text class="y-txt">2,500,000</text>
|
||
<text class="y-txt">2,000,000</text>
|
||
<text class="y-txt">1,500,000</text>
|
||
<text class="y-txt">1,000,000</text>
|
||
<text class="y-txt">500,000</text>
|
||
<text class="y-txt">0</text>
|
||
</view>
|
||
<view class="chart-area">
|
||
<view class="chart-grid">
|
||
<view v-for="i in 7" :key="i" class="grid-line"></view>
|
||
</view>
|
||
<!-- 线条绘制 (简单 Mock) -->
|
||
<view class="line-svg-mock">
|
||
<view class="trend-path"></view>
|
||
</view>
|
||
<view class="x-labels">
|
||
<text class="x-txt" v-for="date in dates" :key="date">{{ date }}</text>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
|
||
<!-- 底部两个分析卡片 -->
|
||
<view class="bottom-analysis">
|
||
<!-- 积分来源分析 -->
|
||
<view class="analysis-card border-shadow">
|
||
<view class="analysis-header">
|
||
<text class="ah-title">积分来源分析</text>
|
||
<view class="btn-toggle" @click="toggleSourceStyle">
|
||
<text class="toggle-txt">切换样式</text>
|
||
</view>
|
||
</view>
|
||
|
||
<view class="analysis-content">
|
||
<!-- 饼图样式 -->
|
||
<view v-if="sourceStyle === 'pie'" class="pie-layout anim-fade">
|
||
<view class="pie-chart-wrap">
|
||
<view class="pie-circle"></view>
|
||
<view class="pie-label-line">
|
||
<text class="pl-txt">后台赠送</text>
|
||
</view>
|
||
</view>
|
||
<view class="pie-legend-list">
|
||
<view v-for="item in sourceData" :key="item.label" class="p-leg-item">
|
||
<view class="p-dot" :style="{backgroundColor: item.color}"></view>
|
||
<text class="p-txt">{{ item.label }}</text>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
|
||
<!-- 列表样式 -->
|
||
<view v-else class="list-layout anim-fade">
|
||
<view class="list-head">
|
||
<text class="lh-col" style="width: 50px;">来源</text>
|
||
<text class="lh-col" style="flex: 1; text-align: center;">金额</text>
|
||
<text class="lh-col" style="width: 200px; text-align: right;">占比率</text>
|
||
</view>
|
||
<view class="list-body">
|
||
<view v-for="(item, index) in sourceData" :key="item.label" class="list-row">
|
||
<view class="lr-rank"><text class="rank-txt">{{ index + 1 }}</text></view>
|
||
<text class="lr-label">{{ item.label }}</text>
|
||
<text class="lr-val">{{ item.value }}</text>
|
||
<view class="lr-progress-box">
|
||
<view class="prog-bg">
|
||
<view class="prog-inner" :style="{width: item.percent + '%'}"></view>
|
||
</view>
|
||
<text class="prog-txt">{{ item.percent }}%</text>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
|
||
<!-- 积分消耗分析 -->
|
||
<view class="analysis-card border-shadow">
|
||
<view class="analysis-header">
|
||
<text class="ah-title">积分消耗</text>
|
||
<view class="btn-toggle" @click="toggleConsumeStyle">
|
||
<text class="toggle-txt">切换样式</text>
|
||
</view>
|
||
</view>
|
||
|
||
<view class="analysis-content">
|
||
<!-- 饼图样式 -->
|
||
<view v-if="consumeStyle === 'pie'" class="pie-layout anim-fade">
|
||
<view class="pie-chart-wrap consume-pie">
|
||
<view class="pie-circle-c"></view>
|
||
<view class="pie-label-line-c">
|
||
<text class="pl-txt">订单抵扣</text>
|
||
</view>
|
||
</view>
|
||
<view class="pie-legend-list">
|
||
<view v-for="item in consumeData" :key="item.label" class="p-leg-item">
|
||
<view class="p-dot" :style="{backgroundColor: item.color}"></view>
|
||
<text class="p-txt">{{ item.label }}</text>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
|
||
<!-- 列表样式 -->
|
||
<view v-else class="list-layout anim-fade">
|
||
<view class="list-head">
|
||
<text class="lh-col" style="width: 50px;">来源</text>
|
||
<text class="lh-col" style="flex: 1; text-align: center;">金额</text>
|
||
<text class="lh-col" style="width: 200px; text-align: right;">占比率</text>
|
||
</view>
|
||
<view class="list-body">
|
||
<view v-for="(item, index) in consumeData" :key="item.label" class="list-row">
|
||
<view class="lr-rank"><text class="rank-txt">{{ index + 1 }}</text></view>
|
||
<text class="lr-label">{{ item.label }}</text>
|
||
<text class="lr-val">{{ item.value }}</text>
|
||
<view class="lr-progress-box">
|
||
<view class="prog-bg">
|
||
<view class="prog-inner" :style="{width: item.percent + '%'}"></view>
|
||
</view>
|
||
<text class="prog-txt">{{ item.percent }}%</text>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
</template>
|
||
|
||
<script setup lang="uts">
|
||
import { ref } from 'vue'
|
||
|
||
const dates = ['01-05', '01-06', '01-07', '01-08', '01-09', '01-10', '01-11', '01-12', '01-13', '01-14', '01-15', '01-16', '01-17', '01-18', '01-19', '01-20', '01-21', '01-22', '01-23', '01-24', '01-25', '01-26', '01-27', '01-28', '01-29', '01-30', '01-31', '02-01', '02-02', '02-03']
|
||
|
||
const sourceStyle = ref('pie')
|
||
const consumeStyle = ref('pie')
|
||
|
||
const sourceData = [
|
||
{ label: '后台赠送', value: 59021632, percent: 100, color: '#778899' },
|
||
{ label: '签到获得', value: 3620, percent: 0, color: '#FFB980' },
|
||
{ label: '九宫格抽奖', value: 0, percent: 0, color: '#FF7F50' },
|
||
{ label: '商品赠送', value: 0, percent: 0, color: '#5AB1EF' },
|
||
{ label: '订单赠送', value: 0, percent: 0, color: '#2EC7C9' }
|
||
]
|
||
|
||
const consumeData = [
|
||
{ label: '订单抵扣', value: 3051, percent: 95.7, color: '#5AB1EF' },
|
||
{ label: '九宫格抽奖', value: 138, percent: 4.3, color: '#2EC7C9' },
|
||
{ label: '兑换商品', value: 0, percent: 0, color: '#FF7F50' },
|
||
{ label: '后台减少', value: 0, percent: 0, color: '#FFB980' },
|
||
{ label: '退款退回', value: 0, percent: 0, color: '#D87A80' }
|
||
]
|
||
|
||
const toggleSourceStyle = () => {
|
||
sourceStyle.value = sourceStyle.value === 'pie' ? 'list' : 'pie'
|
||
}
|
||
|
||
const toggleConsumeStyle = () => {
|
||
consumeStyle.value = consumeStyle.value === 'pie' ? 'list' : 'pie'
|
||
}
|
||
</script>
|
||
|
||
<style scoped lang="scss">
|
||
.admin-marketing-integral-statistic {
|
||
background-color: #f0f2f5;
|
||
min-height: 100vh;
|
||
padding: 24px;
|
||
}
|
||
|
||
.border-shadow {
|
||
background-color: #fff;
|
||
border-radius: 4px;
|
||
box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.05);
|
||
}
|
||
|
||
.content-body {
|
||
display: flex;
|
||
flex-direction: column;
|
||
gap: 20px;
|
||
}
|
||
|
||
/* 时间选择 */
|
||
.filter-card {
|
||
padding: 24px;
|
||
display: flex;
|
||
}
|
||
.filter-item { display: flex; flex-direction: row; align-items: center; gap: 12px; }
|
||
.label-txt { font-size: 14px; color: #606266; }
|
||
.date-picker-mock {
|
||
border: 1px solid #dcdfe6;
|
||
border-radius: 4px;
|
||
padding: 5px 15px;
|
||
display: flex;
|
||
flex-direction: row;
|
||
align-items: center;
|
||
gap: 8px;
|
||
}
|
||
.calendar-ic { font-size: 16px; color: #999; }
|
||
.date-range { font-size: 14px; color: #333; }
|
||
|
||
/* 核心卡片 */
|
||
.stats-row {
|
||
display: flex;
|
||
flex-direction: row;
|
||
gap: 20px;
|
||
}
|
||
.stat-card {
|
||
flex: 1;
|
||
display: flex;
|
||
flex-direction: row;
|
||
padding: 24px;
|
||
align-items: center;
|
||
}
|
||
.sc-left {
|
||
width: 64px;
|
||
height: 64px;
|
||
border-radius: 32px;
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
margin-right: 20px;
|
||
}
|
||
.sc-icon { font-size: 28px; color: #fff; }
|
||
.bg-blue { background-color: #409eff; }
|
||
.bg-orange { background-color: #ff9900; }
|
||
.bg-green { background-color: #19be6b; }
|
||
|
||
.sc-right { display: flex; flex-direction: column; }
|
||
.sc-val { font-size: 28px; font-weight: bold; color: #333; margin-bottom: 5px; }
|
||
.sc-label { font-size: 14px; color: #999; }
|
||
|
||
/* 趋势图 */
|
||
.chart-card {
|
||
padding: 24px;
|
||
display: flex;
|
||
flex-direction: column;
|
||
}
|
||
.chart-header {
|
||
display: flex;
|
||
flex-direction: row;
|
||
justify-content: space-between;
|
||
align-items: center;
|
||
margin-bottom: 24px;
|
||
}
|
||
.chart-title { font-size: 16px; font-weight: bold; color: #333; }
|
||
.chart-legend { display: flex; flex-direction: row; align-items: center; gap: 20px; }
|
||
.legend-item { display: flex; flex-direction: row; align-items: center; gap: 8px; }
|
||
.l-dot { width: 12px; height: 12px; border-radius: 6px; }
|
||
.bg-blue-line { background-color: #409eff; }
|
||
.bg-green-line { background-color: #19be6b; }
|
||
.l-txt { font-size: 12px; color: #666; }
|
||
.down-ic { font-size: 18px; color: #999; cursor: pointer; }
|
||
|
||
.line-chart-box {
|
||
display: flex;
|
||
flex-direction: row;
|
||
height: 400px;
|
||
}
|
||
.y-labels {
|
||
width: 80px;
|
||
display: flex;
|
||
flex-direction: column;
|
||
justify-content: space-between;
|
||
padding-bottom: 30px;
|
||
}
|
||
.y-txt { font-size: 12px; color: #999; text-align: right; padding-right: 10px; }
|
||
|
||
.chart-area {
|
||
flex: 1;
|
||
border-left: 1px solid #eee;
|
||
border-bottom: 1px solid #eee;
|
||
position: relative;
|
||
}
|
||
.chart-grid {
|
||
position: absolute;
|
||
top: 0; left: 0; right: 0; bottom: 0;
|
||
display: flex;
|
||
flex-direction: column;
|
||
justify-content: space-between;
|
||
}
|
||
.grid-line { height: 1px; background-color: #f5f5f5; width: 100%; }
|
||
|
||
.line-svg-mock {
|
||
position: absolute;
|
||
top: 0; left: 0; right: 0; bottom: 0;
|
||
overflow: hidden;
|
||
}
|
||
/* 趋势线条 Mock:用一个带有波浪背景的视图模拟,实际项目中应使用 Chart 库 */
|
||
.trend-path {
|
||
position: absolute;
|
||
width: 100%;
|
||
height: 100%;
|
||
background: radial-gradient(circle at 20% 40%, transparent 10%, #409eff 10.5%, #409eff 11%, transparent 11.5%);
|
||
background-size: 40px 100%;
|
||
opacity: 0.1; /* 仅作为占位示意 */
|
||
}
|
||
|
||
.x-labels {
|
||
position: absolute;
|
||
bottom: -30px;
|
||
left: 0; right: 0;
|
||
display: flex;
|
||
flex-direction: row;
|
||
justify-content: space-between;
|
||
}
|
||
.x-txt { font-size: 10px; color: #999; transform: rotate(-45deg); white-space: nowrap; }
|
||
|
||
/* 底部两个分析 */
|
||
.bottom-analysis {
|
||
display: flex;
|
||
flex-direction: row;
|
||
gap: 20px;
|
||
}
|
||
.analysis-card {
|
||
flex: 1;
|
||
padding: 24px;
|
||
}
|
||
.analysis-header {
|
||
display: flex;
|
||
flex-direction: row;
|
||
justify-content: space-between;
|
||
align-items: center;
|
||
margin-bottom: 24px;
|
||
}
|
||
.ah-title { font-size: 16px; font-weight: bold; color: #333; }
|
||
.btn-toggle {
|
||
border: 1px solid #dcdfe6;
|
||
padding: 4px 12px;
|
||
border-radius: 4px;
|
||
cursor: pointer;
|
||
}
|
||
.toggle-txt { font-size: 12px; color: #666; }
|
||
|
||
.analysis-content {
|
||
min-height: 350px;
|
||
}
|
||
|
||
/* 饼图样式布局 */
|
||
.pie-layout {
|
||
display: flex;
|
||
flex-direction: row;
|
||
align-items: center;
|
||
justify-content: space-around;
|
||
padding-top: 20px;
|
||
}
|
||
.pie-chart-wrap {
|
||
width: 240px;
|
||
height: 240px;
|
||
position: relative;
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
}
|
||
.pie-circle {
|
||
width: 180px;
|
||
height: 180px;
|
||
border-radius: 90px;
|
||
background-color: #778899; /* 主体颜色 */
|
||
box-shadow: inset 0 0 10px rgba(0,0,0,0.1);
|
||
}
|
||
.pie-circle-c {
|
||
width: 180px;
|
||
height: 180px;
|
||
border-radius: 90px;
|
||
background: conic-gradient(#5AB1EF 0 95%, #2EC7C9 95% 100%);
|
||
}
|
||
.pie-label-line {
|
||
position: absolute;
|
||
right: 0;
|
||
top: 50%;
|
||
border-top: 1px solid #999;
|
||
width: 40px;
|
||
padding-top: 5px;
|
||
}
|
||
.pl-txt { font-size: 12px; color: #666; white-space: nowrap; }
|
||
|
||
.pie-legend-list {
|
||
display: flex;
|
||
flex-direction: column;
|
||
gap: 15px;
|
||
}
|
||
.p-leg-item { display: flex; flex-direction: row; align-items: center; gap: 10px; }
|
||
.p-dot { width: 10px; height: 10px; border-radius: 2px; }
|
||
.p-txt { font-size: 13px; color: #666; }
|
||
|
||
/* 列表样式布局 */
|
||
.list-layout { display: flex; flex-direction: column; }
|
||
.list-head {
|
||
display: flex;
|
||
flex-direction: row;
|
||
background-color: #f8f8f9;
|
||
padding: 12px;
|
||
border-radius: 4px;
|
||
}
|
||
.lh-col { font-size: 14px; font-weight: bold; color: #515a6e; }
|
||
|
||
.list-row {
|
||
display: flex;
|
||
flex-direction: row;
|
||
align-items: center;
|
||
padding: 15px 12px;
|
||
border-bottom: 1px solid #f0f0f0;
|
||
}
|
||
.lr-rank { width: 30px; height: 30px; display: flex; align-items: center; }
|
||
.rank-txt { font-size: 14px; color: #999; }
|
||
.lr-label { width: 100px; font-size: 14px; color: #333; }
|
||
.lr-val { flex: 1; font-size: 14px; color: #333; text-align: center; }
|
||
.lr-progress-box { width: 200px; display: flex; flex-direction: row; align-items: center; gap: 10px; justify-content: flex-end; }
|
||
.prog-bg { flex: 1; height: 10px; background-color: #f5f5f5; border-radius: 5px; overflow: hidden; }
|
||
.prog-inner { height: 100%; background-color: #2d8cf0; border-radius: 5px; }
|
||
.prog-txt { font-size: 13px; color: #666; width: 40px; text-align: right; }
|
||
|
||
.anim-fade { animation: fadeIn 0.3s ease-in-out; }
|
||
@keyframes fadeIn { from { opacity: 0; transform: scale(0.98); } to { opacity: 1; transform: scale(1); } }
|
||
</style>
|