|
|
@@ -1,6 +1,6 @@
|
|
|
<template>
|
|
|
<ele-page>
|
|
|
- <ele-card :body-style="{ padding: '20px' }">
|
|
|
+ <ele-card v-loading="loading" :body-style="{ padding: '20px' }">
|
|
|
<!-- Header / Filters -->
|
|
|
<div class="filter-container">
|
|
|
<div class="date-ranges">
|
|
|
@@ -8,7 +8,7 @@
|
|
|
v-for="range in dateRanges"
|
|
|
:key="range.value"
|
|
|
:type="activeRange === range.value ? 'primary' : ''"
|
|
|
- @click="activeRange = range.value"
|
|
|
+ @click="handleRangeChange(range.value)"
|
|
|
>
|
|
|
{{ range.label }}
|
|
|
</el-button>
|
|
|
@@ -20,49 +20,57 @@
|
|
|
range-separator="到"
|
|
|
start-placeholder="开始时间"
|
|
|
end-placeholder="结束时间"
|
|
|
+ value-format="YYYY-MM-DD"
|
|
|
+ @change="handleDateChange"
|
|
|
/>
|
|
|
</div>
|
|
|
<el-button type="primary" @click="handleSearch">搜索</el-button>
|
|
|
</div>
|
|
|
|
|
|
- <!-- Marketing Overview -->
|
|
|
- <marketing-overview />
|
|
|
-
|
|
|
- <!-- Surprise Red Packet Activity -->
|
|
|
- <activity-analysis
|
|
|
- title="惊喜红包活动"
|
|
|
- :stats="redPacketStats"
|
|
|
- :new-customer-ratio="0.6"
|
|
|
- :funnel-data="funnelData1"
|
|
|
- />
|
|
|
-
|
|
|
- <!-- Share Price Reduction Activity -->
|
|
|
- <activity-analysis
|
|
|
- title="分享降价活动"
|
|
|
- :stats="shareStats"
|
|
|
- :new-customer-ratio="0.35"
|
|
|
- :funnel-data="funnelData2"
|
|
|
+ <marketing-overview
|
|
|
+ :overview="overview"
|
|
|
+ :trend-list="trendList"
|
|
|
/>
|
|
|
|
|
|
- <!-- Full Reduction Discount -->
|
|
|
<activity-analysis
|
|
|
- title="满减优惠"
|
|
|
- :stats="fullReductionStats"
|
|
|
- :new-customer-ratio="0.65"
|
|
|
- :funnel-data="funnelData3"
|
|
|
+ v-for="item in activities"
|
|
|
+ :key="item.type"
|
|
|
+ :title="item.title"
|
|
|
+ :stats="item.stats"
|
|
|
+ :old-user-rate="item.oldUserRate"
|
|
|
+ :new-user-rate="item.newUserRate"
|
|
|
+ :count-label="item.countLabel"
|
|
|
+ :funnel-data="item.funnelData"
|
|
|
/>
|
|
|
</ele-card>
|
|
|
</ele-page>
|
|
|
</template>
|
|
|
|
|
|
<script setup>
|
|
|
- import { ref } from 'vue';
|
|
|
+ import { computed, onMounted, ref } from 'vue';
|
|
|
+ import dayjs from 'dayjs';
|
|
|
+ import { EleMessage } from 'ele-admin-plus/es';
|
|
|
import MarketingOverview from './components/MarketingOverview.vue';
|
|
|
import ActivityAnalysis from './components/ActivityAnalysis.vue';
|
|
|
+ import {
|
|
|
+ getMarketingActivityStat,
|
|
|
+ getMarketingOverview,
|
|
|
+ getMarketingPayTrend
|
|
|
+ } from '@/api/statsAnalysis/marketing';
|
|
|
+ import {
|
|
|
+ activityDefs,
|
|
|
+ formatPercent,
|
|
|
+ showValue,
|
|
|
+ toNumber
|
|
|
+ } from './activity';
|
|
|
|
|
|
// Filters
|
|
|
const activeRange = ref('yesterday');
|
|
|
const dateRange = ref([]);
|
|
|
+ const loading = ref(false);
|
|
|
+ const overview = ref({});
|
|
|
+ const trendList = ref([]);
|
|
|
+ const activityMap = ref({});
|
|
|
const dateRanges = [
|
|
|
{ label: '昨日', value: 'yesterday' },
|
|
|
{ label: '7日', value: '7days' },
|
|
|
@@ -70,63 +78,115 @@
|
|
|
{ label: '30日', value: '30days' }
|
|
|
];
|
|
|
|
|
|
- const handleSearch = () => {
|
|
|
- console.log('Search clicked', activeRange.value, dateRange.value);
|
|
|
+ const rangeDays = {
|
|
|
+ '7days': 6,
|
|
|
+ '15days': 14,
|
|
|
+ '30days': 29
|
|
|
};
|
|
|
|
|
|
- // Mock Data
|
|
|
- const redPacketStats = [
|
|
|
- { label: '红包拉动成交金额 (元)', value: '1999.9' },
|
|
|
- { label: '参与人数 (人)', value: '200' },
|
|
|
- { label: '客单价(元)', value: '200' },
|
|
|
- { label: '订单覆盖率', value: '20%' },
|
|
|
- { label: '红包过期率', value: '20%' },
|
|
|
- { label: '红包使用数(张)', value: '200' },
|
|
|
- { label: '红包领取金额(元)', value: '200' },
|
|
|
- { label: '红包消耗金额 (元)', value: '200.00' },
|
|
|
- { label: '红包使用率', value: '0.10%' },
|
|
|
- { label: '红包待使用金额(元)', value: '200' },
|
|
|
- { label: '红包待使用量(张)', value: '200' },
|
|
|
- { label: '红包领取数(张)', value: '200' }
|
|
|
- ];
|
|
|
+ function handleSearch() {
|
|
|
+ if (!dateRange.value?.length) {
|
|
|
+ handleRangeChange('yesterday');
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ loadData();
|
|
|
+ }
|
|
|
|
|
|
- const shareStats = [
|
|
|
- { label: '营销支付金额', value: '1999.99 (元)' },
|
|
|
- { label: '营销优惠金额', value: '200.00 (元)' },
|
|
|
- { label: '参与人数', value: '200.00 (人)' },
|
|
|
- { label: '支付订单数', value: '200.00 (单)' },
|
|
|
- { label: '客单价', value: '200.00 (元)' },
|
|
|
- { label: '助力人数', value: '200.00 (人)' }
|
|
|
- ];
|
|
|
+ const activities = computed(() =>
|
|
|
+ activityDefs.map((item) => {
|
|
|
+ const data = activityMap.value[item.type] || {};
|
|
|
+ return {
|
|
|
+ type: item.type,
|
|
|
+ title: item.title,
|
|
|
+ countLabel: item.countLabel,
|
|
|
+ stats: item.buildStats(data),
|
|
|
+ oldUserRate: toNumber(data.oldUserRate),
|
|
|
+ newUserRate: toNumber(data.newUserRate),
|
|
|
+ funnelData: {
|
|
|
+ visitorCount: showValue(data.joinUserNum),
|
|
|
+ paymentCount: showValue(data.payUserNum),
|
|
|
+ unitPrice: showValue(data.unitPrice),
|
|
|
+ conversionRate: formatPercent(data.conversionRate)
|
|
|
+ }
|
|
|
+ };
|
|
|
+ })
|
|
|
+ );
|
|
|
+
|
|
|
+ function buildDates(value) {
|
|
|
+ const today = dayjs();
|
|
|
+ if (value === 'yesterday') {
|
|
|
+ const day = today.subtract(1, 'day').format('YYYY-MM-DD');
|
|
|
+ return [day, day];
|
|
|
+ }
|
|
|
+ const offset = rangeDays[value] ?? 0;
|
|
|
+ return [
|
|
|
+ today.subtract(offset, 'day').format('YYYY-MM-DD'),
|
|
|
+ today.format('YYYY-MM-DD')
|
|
|
+ ];
|
|
|
+ }
|
|
|
|
|
|
- const fullReductionStats = [
|
|
|
- { label: '营销支付金额', value: '1999.99 (元)' },
|
|
|
- { label: '营销优惠金额', value: '200.00 (元)' },
|
|
|
- { label: '参与人数', value: '200.00 (人)' },
|
|
|
- { label: '支付订单数', value: '200.00 (单)' },
|
|
|
- { label: '客单价', value: '200.00 (元)' }
|
|
|
- ];
|
|
|
+ function buildParams() {
|
|
|
+ const now = dayjs();
|
|
|
+ const [start, end] = dateRange.value || [];
|
|
|
+ const startDay = start ? dayjs(start) : now;
|
|
|
+ const endDay = end ? dayjs(end) : now;
|
|
|
+ return {
|
|
|
+ startTime: startDay.startOf('day').format('YYYY-MM-DD HH:mm:ss'),
|
|
|
+ endTime: endDay.endOf('day').format('YYYY-MM-DD HH:mm:ss')
|
|
|
+ };
|
|
|
+ }
|
|
|
|
|
|
- const funnelData1 = {
|
|
|
- visitorCount: 32,
|
|
|
- paymentCount: 32,
|
|
|
- unitPrice: 32,
|
|
|
- conversionRate: '65%'
|
|
|
- };
|
|
|
+ async function loadData() {
|
|
|
+ loading.value = true;
|
|
|
+ const params = buildParams();
|
|
|
+ const nextActivityMap = {};
|
|
|
+ await Promise.all([
|
|
|
+ getMarketingOverview(params)
|
|
|
+ .then((data) => {
|
|
|
+ overview.value = data || {};
|
|
|
+ })
|
|
|
+ .catch((error) => {
|
|
|
+ overview.value = {};
|
|
|
+ EleMessage.error(error.message || '获取营销概要失败');
|
|
|
+ }),
|
|
|
+ getMarketingPayTrend(params)
|
|
|
+ .then((data) => {
|
|
|
+ trendList.value = Array.isArray(data) ? data : [];
|
|
|
+ })
|
|
|
+ .catch((error) => {
|
|
|
+ trendList.value = [];
|
|
|
+ EleMessage.error(error.message || '获取支付金额趋势失败');
|
|
|
+ }),
|
|
|
+ ...activityDefs.map((item) =>
|
|
|
+ getMarketingActivityStat({ ...params, type: item.type })
|
|
|
+ .then((data) => {
|
|
|
+ nextActivityMap[item.type] = data || {};
|
|
|
+ })
|
|
|
+ .catch((error) => {
|
|
|
+ nextActivityMap[item.type] = {};
|
|
|
+ EleMessage.error(
|
|
|
+ error.message || '获取营销活动统计失败'
|
|
|
+ );
|
|
|
+ })
|
|
|
+ )
|
|
|
+ ]);
|
|
|
+ activityMap.value = nextActivityMap;
|
|
|
+ loading.value = false;
|
|
|
+ }
|
|
|
|
|
|
- const funnelData2 = {
|
|
|
- visitorCount: 32,
|
|
|
- paymentCount: 32,
|
|
|
- unitPrice: 32,
|
|
|
- conversionRate: '65%'
|
|
|
- };
|
|
|
+ function handleRangeChange(value) {
|
|
|
+ activeRange.value = value;
|
|
|
+ dateRange.value = buildDates(value);
|
|
|
+ loadData();
|
|
|
+ }
|
|
|
|
|
|
- const funnelData3 = {
|
|
|
- visitorCount: 32,
|
|
|
- paymentCount: 32,
|
|
|
- unitPrice: 32,
|
|
|
- conversionRate: '65%'
|
|
|
- };
|
|
|
+ function handleDateChange() {
|
|
|
+ activeRange.value = '';
|
|
|
+ }
|
|
|
+
|
|
|
+ onMounted(() => {
|
|
|
+ handleRangeChange('yesterday');
|
|
|
+ });
|
|
|
</script>
|
|
|
|
|
|
<style scoped lang="scss">
|