|
@@ -1,5 +1,5 @@
|
|
|
<template>
|
|
<template>
|
|
|
- <div class="template-data">
|
|
|
|
|
|
|
+ <div v-loading="loading" class="template-data">
|
|
|
<!-- Header Info -->
|
|
<!-- Header Info -->
|
|
|
<div
|
|
<div
|
|
|
class="bg-gray-50 p-4 rounded mb-4 text-sm text-gray-600 flex justify-between items-center"
|
|
class="bg-gray-50 p-4 rounded mb-4 text-sm text-gray-600 flex justify-between items-center"
|
|
@@ -47,7 +47,9 @@
|
|
|
range-separator="至"
|
|
range-separator="至"
|
|
|
start-placeholder="开始日期"
|
|
start-placeholder="开始日期"
|
|
|
end-placeholder="结束日期"
|
|
end-placeholder="结束日期"
|
|
|
|
|
+ value-format="YYYY-MM-DD"
|
|
|
style="width: 240px"
|
|
style="width: 240px"
|
|
|
|
|
+ @change="handleDateChange"
|
|
|
/>
|
|
/>
|
|
|
<el-button type="primary" plain @click="fetchData"
|
|
<el-button type="primary" plain @click="fetchData"
|
|
|
>查询</el-button
|
|
>查询</el-button
|
|
@@ -77,15 +79,14 @@
|
|
|
<div class="text-lg font-bold mb-1">{{ metric.value }}</div>
|
|
<div class="text-lg font-bold mb-1">{{ metric.value }}</div>
|
|
|
<div class="text-xs flex items-center">
|
|
<div class="text-xs flex items-center">
|
|
|
<span class="text-gray-400 mr-1">较前1日</span>
|
|
<span class="text-gray-400 mr-1">较前1日</span>
|
|
|
|
|
+ <span v-if="metric.trend == null" class="text-gray-400">—</span>
|
|
|
<span
|
|
<span
|
|
|
- :class="
|
|
|
|
|
- metric.trend > 0
|
|
|
|
|
- ? 'text-green-500'
|
|
|
|
|
- : 'text-red-500'
|
|
|
|
|
- "
|
|
|
|
|
|
|
+ v-else
|
|
|
|
|
+ :class="metric.trend > 0 ? 'text-green-500' : metric.trend < 0 ? 'text-red-500' : 'text-gray-400'"
|
|
|
>
|
|
>
|
|
|
- {{ metric.trend > 0 ? '↑' : '↓' }}
|
|
|
|
|
- {{ Math.abs(metric.trend) }}%
|
|
|
|
|
|
|
+ <template v-if="metric.trend > 0">↑</template>
|
|
|
|
|
+ <template v-else-if="metric.trend < 0">↓</template>
|
|
|
|
|
+ {{ Math.abs(metric.trend).toFixed(2) }}%
|
|
|
</span>
|
|
</span>
|
|
|
</div>
|
|
</div>
|
|
|
</div>
|
|
</div>
|
|
@@ -100,51 +101,92 @@
|
|
|
</template>
|
|
</template>
|
|
|
|
|
|
|
|
<script setup>
|
|
<script setup>
|
|
|
- import { ref, reactive, computed } from 'vue';
|
|
|
|
|
|
|
+ import { computed, onMounted, ref } from 'vue';
|
|
|
|
|
+ import dayjs from 'dayjs';
|
|
|
|
|
+ import { EleMessage } from 'ele-admin-plus/es';
|
|
|
import BaseChart from '@/components/Chart/BaseChart.vue';
|
|
import BaseChart from '@/components/Chart/BaseChart.vue';
|
|
|
|
|
+ import { getCouponStatisticTemplate } from '@/api/marketing/coupon';
|
|
|
|
|
|
|
|
- const info = reactive({
|
|
|
|
|
- condition: '25.00元',
|
|
|
|
|
- amount: '2-4元',
|
|
|
|
|
- validity: '2024-10-01 00:00:00 至 2024-11-20 00:00:00',
|
|
|
|
|
- updateTime: '2024-10-05'
|
|
|
|
|
|
|
+ const props = defineProps({
|
|
|
|
|
+ couponId: { type: [String, Number], default: '' }
|
|
|
});
|
|
});
|
|
|
|
|
+ const loading = ref(false);
|
|
|
|
|
+ const detail = ref({});
|
|
|
|
|
+ const trendMap = ref({});
|
|
|
|
|
|
|
|
- const stats = ref([
|
|
|
|
|
- { label: '发行量 (张)', value: '658' },
|
|
|
|
|
- { label: '累计领取量 (张)', value: '658' },
|
|
|
|
|
- { label: '红包消耗金额 (元)', value: '658' },
|
|
|
|
|
- { label: '累计使用量 (张)', value: '658' },
|
|
|
|
|
- { label: '累计使用率', value: '65.8%' },
|
|
|
|
|
- { label: '累计支付件数', value: '658' },
|
|
|
|
|
- { label: '红包拉动成交金额', value: '658' }
|
|
|
|
|
- ]);
|
|
|
|
|
|
|
+ function showValue(value) {
|
|
|
|
|
+ if (value == null || value === "") return 0;
|
|
|
|
|
+ return value;
|
|
|
|
|
+ }
|
|
|
|
|
+ function formatDateTime(value) {
|
|
|
|
|
+ if (!value) return "-";
|
|
|
|
|
+ const date = dayjs(value);
|
|
|
|
|
+ return date.isValid() ? date.format("YYYY-MM-DD HH:mm:ss") : value;
|
|
|
|
|
+ }
|
|
|
|
|
+ function formatAmount(value) {
|
|
|
|
|
+ const num = Number(value);
|
|
|
|
|
+ if (!Number.isFinite(num)) return "0";
|
|
|
|
|
+ return num.toFixed(2).replace(/\.00$/, "");
|
|
|
|
|
+ }
|
|
|
|
|
+ function formatThreshold(value) {
|
|
|
|
|
+ if (value == null || Number(value) === 0) return "无门槛";
|
|
|
|
|
+ return Number(value).toFixed(2) + "元";
|
|
|
|
|
+ }
|
|
|
|
|
+ function formatFace(data) {
|
|
|
|
|
+ const min = data.minMoney;
|
|
|
|
|
+ const max = data.maxMoney;
|
|
|
|
|
+ if (min != null && max != null && (Number(min) !== 0 || Number(max) !== 0)) {
|
|
|
|
|
+ return formatAmount(min) + "-" + formatAmount(max) + "元";
|
|
|
|
|
+ }
|
|
|
|
|
+ if (data.faceMoney != null) return formatAmount(data.faceMoney) + "元";
|
|
|
|
|
+ return "-";
|
|
|
|
|
+ }
|
|
|
|
|
+ function formatPercent(value) {
|
|
|
|
|
+ if (value == null || value === "") return "0%";
|
|
|
|
|
+ const text = String(value);
|
|
|
|
|
+ return text.includes("%") ? text : text + "%";
|
|
|
|
|
+ }
|
|
|
|
|
+ const info = computed(() => {
|
|
|
|
|
+ const data = detail.value || {};
|
|
|
|
|
+ return {
|
|
|
|
|
+ condition: formatThreshold(data.thresholdMoney),
|
|
|
|
|
+ amount: formatFace(data),
|
|
|
|
|
+ validity: formatDateTime(data.effectStartTime) + " 至 " + formatDateTime(data.effectEndTime),
|
|
|
|
|
+ updateTime: data.dataUpdateDate || "-"
|
|
|
|
|
+ };
|
|
|
|
|
+ });
|
|
|
|
|
+ const stats = computed(() => {
|
|
|
|
|
+ const data = detail.value || {};
|
|
|
|
|
+ return [
|
|
|
|
|
+ { label: "发行量 (张)", value: showValue(data.issueNum) },
|
|
|
|
|
+ { label: "累计领取量 (张)", value: showValue(data.cumulativeClaimNum) },
|
|
|
|
|
+ { label: "红包消耗金额 (元)", value: showValue(data.cumulativeCouponAmount) },
|
|
|
|
|
+ { label: "累计使用量 (张)", value: showValue(data.cumulativeUseNum) },
|
|
|
|
|
+ { label: "累计使用率", value: formatPercent(data.cumulativeUseRate) },
|
|
|
|
|
+ { label: "累计支付件数", value: showValue(data.cumulativePayNum) },
|
|
|
|
|
+ { label: "红包拉动成交金额", value: showValue(data.cumulativeDrivePayAmount) }
|
|
|
|
|
+ ];
|
|
|
|
|
+ });
|
|
|
|
|
|
|
|
const dateRangeType = ref('day');
|
|
const dateRangeType = ref('day');
|
|
|
const dateRange = ref([]);
|
|
const dateRange = ref([]);
|
|
|
- const queryTime = ref('2024-06-28');
|
|
|
|
|
-
|
|
|
|
|
- const activeMetric = ref('collection');
|
|
|
|
|
- const metrics = ref([
|
|
|
|
|
- { key: 'collection', label: '领取张数', value: '658', trend: -48.65 },
|
|
|
|
|
- { key: 'usage', label: '使用张数', value: '658', trend: -48.65 },
|
|
|
|
|
- {
|
|
|
|
|
- key: 'consumption',
|
|
|
|
|
- label: '红包消耗金额',
|
|
|
|
|
- value: '658',
|
|
|
|
|
- trend: -48.65
|
|
|
|
|
- },
|
|
|
|
|
- {
|
|
|
|
|
- key: 'transaction',
|
|
|
|
|
- label: '红包拉动支付金额',
|
|
|
|
|
- value: '658.68',
|
|
|
|
|
- trend: -48.65
|
|
|
|
|
- },
|
|
|
|
|
- { key: 'buyers', label: '支付买家数', value: '658', trend: 48.65 },
|
|
|
|
|
- { key: 'items', label: '支付件数', value: '658', trend: 48.65 },
|
|
|
|
|
- { key: 'price', label: '客单价', value: '658', trend: 48.65 }
|
|
|
|
|
- ]);
|
|
|
|
|
|
|
+ const queryTime = ref("");
|
|
|
|
|
|
|
|
|
|
+ const METRICS = [
|
|
|
|
|
+ { key: "claimNum", label: "领取张数" },
|
|
|
|
|
+ { key: "useNum", label: "使用张数" },
|
|
|
|
|
+ { key: "couponAmount", label: "红包消耗金额" },
|
|
|
|
|
+ { key: "drivePayAmount", label: "红包拉动支付金额" },
|
|
|
|
|
+ { key: "payUserNum", label: "支付买家数" },
|
|
|
|
|
+ { key: "payNum", label: "支付件数" },
|
|
|
|
|
+ { key: "avgOrderValue", label: "客单价" }
|
|
|
|
|
+ ];
|
|
|
|
|
+ const activeMetric = ref("claimNum");
|
|
|
|
|
+ const metrics = computed(() => METRICS.map((item) => ({
|
|
|
|
|
+ ...item,
|
|
|
|
|
+ value: showValue(detail.value?.[item.key]),
|
|
|
|
|
+ trend: trendMap.value[item.key]
|
|
|
|
|
+ })));
|
|
|
const chartOption = computed(() => {
|
|
const chartOption = computed(() => {
|
|
|
return {
|
|
return {
|
|
|
tooltip: {
|
|
tooltip: {
|
|
@@ -159,7 +201,10 @@
|
|
|
xAxis: {
|
|
xAxis: {
|
|
|
type: 'category',
|
|
type: 'category',
|
|
|
boundaryGap: false,
|
|
boundaryGap: false,
|
|
|
- data: ['1月', '2月', '3月', '4月', '5月']
|
|
|
|
|
|
|
+ data: (detail.value.dailyList || []).map((item) => {
|
|
|
|
|
+ const date = dayjs(item.statisticDate);
|
|
|
|
|
+ return date.isValid() ? date.format("MM-DD") : item.statisticDate;
|
|
|
|
|
+ })
|
|
|
},
|
|
},
|
|
|
yAxis: {
|
|
yAxis: {
|
|
|
type: 'value'
|
|
type: 'value'
|
|
@@ -191,23 +236,86 @@
|
|
|
]
|
|
]
|
|
|
}
|
|
}
|
|
|
},
|
|
},
|
|
|
- data: [100, 140, 220, 100, 130] // Mock data
|
|
|
|
|
|
|
+ data: (detail.value.dailyList || []).map(
|
|
|
|
|
+ (item) => Number(item[activeMetric.value]) || 0
|
|
|
|
|
+ )
|
|
|
}
|
|
}
|
|
|
]
|
|
]
|
|
|
};
|
|
};
|
|
|
});
|
|
});
|
|
|
|
|
|
|
|
- const handleDateTypeChange = (val) => {
|
|
|
|
|
- // Logic to set dateRange based on val
|
|
|
|
|
- console.log('Date type changed:', val);
|
|
|
|
|
- };
|
|
|
|
|
-
|
|
|
|
|
- const fetchData = () => {
|
|
|
|
|
- console.log('Fetching data...');
|
|
|
|
|
- };
|
|
|
|
|
-
|
|
|
|
|
- const reset = () => {
|
|
|
|
|
- dateRangeType.value = 'day';
|
|
|
|
|
|
|
+ function buildRange(type) {
|
|
|
|
|
+ const today = dayjs();
|
|
|
|
|
+ const endDate = today.format("YYYY-MM-DD");
|
|
|
|
|
+ if (type === "7days") return [today.subtract(6, "day").format("YYYY-MM-DD"), endDate];
|
|
|
|
|
+ if (type === "30days") return [today.subtract(29, "day").format("YYYY-MM-DD"), endDate];
|
|
|
|
|
+ return [endDate, endDate];
|
|
|
|
|
+ }
|
|
|
|
|
+ function resolveRange() {
|
|
|
|
|
+ if (dateRange.value && dateRange.value.length === 2) {
|
|
|
|
|
+ return { startDate: dateRange.value[0], endDate: dateRange.value[1] };
|
|
|
|
|
+ }
|
|
|
|
|
+ const pair = buildRange(dateRangeType.value || "day");
|
|
|
|
|
+ return { startDate: pair[0], endDate: pair[1] };
|
|
|
|
|
+ }
|
|
|
|
|
+ function calcTrend(current, previous) {
|
|
|
|
|
+ const cur = Number(current) || 0;
|
|
|
|
|
+ const prev = Number(previous) || 0;
|
|
|
|
|
+ if (prev === 0) return cur === 0 ? 0 : null;
|
|
|
|
|
+ return ((cur - prev) / prev) * 100;
|
|
|
|
|
+ }
|
|
|
|
|
+ function applyTrend(current, previous) {
|
|
|
|
|
+ const next = {};
|
|
|
|
|
+ METRICS.forEach((item) => {
|
|
|
|
|
+ next[item.key] = previous ? calcTrend(current && current[item.key], previous && previous[item.key]) : null;
|
|
|
|
|
+ });
|
|
|
|
|
+ trendMap.value = next;
|
|
|
|
|
+ }
|
|
|
|
|
+ async function fetchData() {
|
|
|
|
|
+ if (!props.couponId) return;
|
|
|
|
|
+ const range = resolveRange();
|
|
|
|
|
+ queryTime.value = range.startDate === range.endDate ? range.startDate : range.startDate + " 至 " + range.endDate;
|
|
|
|
|
+ loading.value = true;
|
|
|
|
|
+ try {
|
|
|
|
|
+ const data = await getCouponStatisticTemplate({
|
|
|
|
|
+ couponId: props.couponId,
|
|
|
|
|
+ startDate: range.startDate,
|
|
|
|
|
+ endDate: range.endDate
|
|
|
|
|
+ });
|
|
|
|
|
+ detail.value = data || {};
|
|
|
|
|
+ const list = Array.isArray(data && data.dailyList) ? data.dailyList : [];
|
|
|
|
|
+ if (list.length >= 2) {
|
|
|
|
|
+ applyTrend(list[list.length - 1], list[list.length - 2]);
|
|
|
|
|
+ } else {
|
|
|
|
|
+ const prevDay = dayjs(range.endDate).subtract(1, "day").format("YYYY-MM-DD");
|
|
|
|
|
+ const prevData = await getCouponStatisticTemplate({
|
|
|
|
|
+ couponId: props.couponId,
|
|
|
|
|
+ startDate: prevDay,
|
|
|
|
|
+ endDate: prevDay
|
|
|
|
|
+ });
|
|
|
|
|
+ applyTrend(data, prevData);
|
|
|
|
|
+ }
|
|
|
|
|
+ } catch (error) {
|
|
|
|
|
+ detail.value = {};
|
|
|
|
|
+ trendMap.value = {};
|
|
|
|
|
+ EleMessage.error(error.message || "获取红包模板数据失败");
|
|
|
|
|
+ } finally {
|
|
|
|
|
+ loading.value = false;
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ function handleDateTypeChange() {
|
|
|
dateRange.value = [];
|
|
dateRange.value = [];
|
|
|
- };
|
|
|
|
|
|
|
+ fetchData();
|
|
|
|
|
+ }
|
|
|
|
|
+ function handleDateChange() {
|
|
|
|
|
+ if (dateRange.value && dateRange.value.length === 2) dateRangeType.value = "";
|
|
|
|
|
+ }
|
|
|
|
|
+ function reset() {
|
|
|
|
|
+ dateRangeType.value = "day";
|
|
|
|
|
+ dateRange.value = [];
|
|
|
|
|
+ activeMetric.value = "claimNum";
|
|
|
|
|
+ fetchData();
|
|
|
|
|
+ }
|
|
|
|
|
+ onMounted(() => { fetchData(); });
|
|
|
|
|
+
|
|
|
</script>
|
|
</script>
|