Files
medical-mall/pages/mall/admin/marketing/integral/statistic.uvue
2026-02-03 21:35:57 +08:00

481 lines
15 KiB
Plaintext
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<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>