XuqmGroup-Web/tenant-platform/src/views/bug-collect/BugCollectFunnels.vue

143 行
3.8 KiB
Vue

<template>
<div>
<h2 style="margin-bottom: 24px">漏斗分析</h2>
<!-- App selector bar -->
<div class="app-selector-bar">
<span class="selector-label">选择应用</span>
<el-select
:model-value="appKey"
placeholder="请选择应用"
style="width:220px"
:loading="loadingApps"
@change="setApp"
>
<el-option v-for="a in apps" :key="a.appKey" :label="a.name" :value="a.appKey" />
</el-select>
</div>
<el-empty v-if="!appKey" description="请选择一个应用" style="margin-top:80px" />
<template v-else>
<el-card style="margin-bottom: 16px">
<template #header>配置漏斗步骤</template>
<div class="funnel-builder">
<div v-for="(step, idx) in steps" :key="idx" class="funnel-step-row">
<span class="step-index">步骤 {{ idx + 1 }}</span>
<el-input v-model="steps[idx]" placeholder="事件名" style="width: 280px" />
<el-button v-if="steps.length > 1" type="danger" text @click="removeStep(idx)">删除</el-button>
</div>
<div style="margin-top: 12px; display: flex; gap: 12px">
<el-button @click="addStep">添加步骤</el-button>
<el-button type="primary" :loading="loading" @click="analyze">分析</el-button>
</div>
</div>
</el-card>
<el-card v-if="funnelData.length">
<template #header>漏斗结果</template>
<div class="funnel-result">
<div v-for="(step, idx) in funnelData" :key="idx" class="funnel-item">
<div class="funnel-step-info">
<div class="funnel-step-name">{{ step.eventName }}</div>
<div class="funnel-step-count">{{ step.count }} </div>
</div>
<el-progress
:percentage="step.conversionRate"
:stroke-width="24"
:color="funnelColor(idx)"
:format="(p: number) => p.toFixed(1) + '%'"
/>
<div v-if="idx < funnelData.length - 1" class="funnel-drop">
转化 {{ step.conversionRate.toFixed(1) }}% &darr;
</div>
</div>
</div>
</el-card>
</template>
</div>
</template>
<script setup lang="ts">
import { ref } from 'vue'
import { bugCollectApi, type BugCollectFunnelStep } from '@/api/bugcollect'
import { useBugCollectApp } from '@/composables/useBugCollectApp'
const { apps, loadingApps, appKey, setApp } = useBugCollectApp()
const steps = ref(['', ''])
const funnelData = ref<BugCollectFunnelStep[]>([])
const loading = ref(false)
function addStep() {
steps.value.push('')
}
function removeStep(idx: number) {
steps.value.splice(idx, 1)
}
function funnelColor(idx: number) {
const colors = ['#409eff', '#67c23a', '#e6a23c', '#f56c6c', '#909399']
return colors[idx % colors.length]
}
async function analyze() {
const validSteps = steps.value.filter((s) => s.trim())
if (validSteps.length < 2) return
loading.value = true
try {
const res = await bugCollectApi.funnel(appKey.value, validSteps)
funnelData.value = res.data.data
} catch {
} finally {
loading.value = false
}
}
</script>
<style scoped>
.app-selector-bar { display:flex; align-items:center; gap:12px; margin-bottom:20px; }
.selector-label { font-size:14px; color:#606266; }
.funnel-builder {
display: flex;
flex-direction: column;
}
.funnel-step-row {
display: flex;
align-items: center;
gap: 12px;
margin-bottom: 8px;
}
.step-index {
width: 60px;
font-size: 13px;
color: #666;
flex-shrink: 0;
}
.funnel-result {
max-width: 600px;
}
.funnel-item {
margin-bottom: 20px;
}
.funnel-step-info {
display: flex;
justify-content: space-between;
margin-bottom: 6px;
}
.funnel-step-name {
font-weight: 600;
font-size: 14px;
}
.funnel-step-count {
font-size: 14px;
color: #666;
}
.funnel-drop {
text-align: center;
font-size: 12px;
color: #999;
margin-top: 4px;
}
</style>