|
|
@@ -0,0 +1,310 @@
|
|
|
+<template>
|
|
|
+ <div class="space-y-6">
|
|
|
+ <div class="bg-white rounded-xl shadow-sm border border-gray-200 p-6">
|
|
|
+
|
|
|
+ <!-- 标题栏 -->
|
|
|
+ <div class="border-b border-gray-200 pb-4 mb-6">
|
|
|
+ <div class="flex items-center justify-between flex-wrap gap-4">
|
|
|
+ <h2 class="text-xl font-semibold text-gray-800 flex items-center gap-2">
|
|
|
+ <svg xmlns="http://www.w3.org/2000/svg" class="h-6 w-6 text-blue-500" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
|
|
+ <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 19v-6a2 2 0 00-2-2H5a2 2 0 00-2 2v6a2 2 0 002 2h2a2 2 0 002-2zm0 0V9a2 2 0 012-2h2a2 2 0 012 2v10m-6 0a2 2 0 002 2h2a2 2 0 002-2m0 0V5a2 2 0 012-2h2a2 2 0 012 2v14a2 2 0 01-2 2h-2a2 2 0 01-2-2z" />
|
|
|
+ </svg>
|
|
|
+ 广告日报表
|
|
|
+ </h2>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <!-- 查询条件 -->
|
|
|
+ <div class="flex flex-col gap-3 mb-6">
|
|
|
+ <!-- 第一行:筛选控件 -->
|
|
|
+ <div class="flex flex-wrap items-center gap-4">
|
|
|
+ <!-- 时间口径 radio -->
|
|
|
+ <div class="flex items-center gap-2">
|
|
|
+ <span class="text-sm text-gray-600 font-medium">时间口径:</span>
|
|
|
+ <el-radio-group v-model="queryForm.timeLine" @change="handleTimeLineChange">
|
|
|
+ <el-radio-button value="REQUEST_TIME">请求时间</el-radio-button>
|
|
|
+ <el-radio-button value="REPORTING_TIME">上报时间</el-radio-button>
|
|
|
+ </el-radio-group>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <el-select
|
|
|
+ v-model="queryForm.groupId"
|
|
|
+ placeholder="账号组筛选"
|
|
|
+ clearable
|
|
|
+ filterable
|
|
|
+ style="width: 180px"
|
|
|
+ @change="handleSearch"
|
|
|
+ >
|
|
|
+ <el-option
|
|
|
+ v-for="g in groupOptions"
|
|
|
+ :key="g.id"
|
|
|
+ :label="g.groupName"
|
|
|
+ :value="g.id"
|
|
|
+ />
|
|
|
+ </el-select>
|
|
|
+
|
|
|
+ <el-input
|
|
|
+ v-model="queryForm.accountId"
|
|
|
+ placeholder="账户ID筛选"
|
|
|
+ clearable
|
|
|
+ style="width: 160px"
|
|
|
+ @clear="handleSearch"
|
|
|
+ />
|
|
|
+
|
|
|
+ <el-input
|
|
|
+ v-model="queryForm.adgroupId"
|
|
|
+ placeholder="广告组ID筛选"
|
|
|
+ clearable
|
|
|
+ style="width: 180px"
|
|
|
+ @clear="handleSearch"
|
|
|
+ />
|
|
|
+
|
|
|
+ <el-date-picker
|
|
|
+ v-model="queryDateRange"
|
|
|
+ type="daterange"
|
|
|
+ range-separator="~"
|
|
|
+ start-placeholder="开始日期"
|
|
|
+ end-placeholder="结束日期"
|
|
|
+ format="YYYY-MM-DD"
|
|
|
+ value-format="YYYY-MM-DD"
|
|
|
+ style="width: 240px"
|
|
|
+ @change="handleSearch"
|
|
|
+ />
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <!-- 第二行:按钮右对齐 -->
|
|
|
+ <div class="flex justify-end gap-2">
|
|
|
+ <el-button type="primary" @click="handleSearch">
|
|
|
+ <el-icon><Search /></el-icon>
|
|
|
+ 查询
|
|
|
+ </el-button>
|
|
|
+ <el-button @click="handleReset">重置</el-button>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <!-- 数据表格 -->
|
|
|
+ <el-table
|
|
|
+ :data="reportList"
|
|
|
+ v-loading="loading"
|
|
|
+ stripe
|
|
|
+ border
|
|
|
+ style="width: 100%"
|
|
|
+ max-height="600"
|
|
|
+ >
|
|
|
+ <!-- yyyy-MM-dd -->
|
|
|
+ <el-table-column prop="date" label="日期" width="110" fixed="left" >
|
|
|
+ <template #default="{ row }">{{ row.date.substring(0, 10) }}</template>
|
|
|
+ </el-table-column>
|
|
|
+ <el-table-column prop="accountId" label="账户ID" width="130" fixed="left" />
|
|
|
+ <el-table-column prop="adgroupId" label="广告组ID" width="140" />
|
|
|
+ <el-table-column prop="adgroupName" label="广告组名称" width="180" show-overflow-tooltip />
|
|
|
+ <el-table-column prop="cost" label="消耗(元)" width="110" align="right">
|
|
|
+ <template #default="{ row }">{{ formatNum(row.cost) }}</template>
|
|
|
+ </el-table-column>
|
|
|
+ <el-table-column prop="viewCount" label="曝光数" width="100" align="right">
|
|
|
+ <template #default="{ row }">{{ formatInt(row.viewCount) }}</template>
|
|
|
+ </el-table-column>
|
|
|
+ <el-table-column prop="validClickCount" label="有效点击" width="100" align="right">
|
|
|
+ <template #default="{ row }">{{ formatInt(row.validClickCount) }}</template>
|
|
|
+ </el-table-column>
|
|
|
+ <el-table-column prop="ctr" label="点击率" width="90" align="right">
|
|
|
+ <template #default="{ row }">{{ formatPct(row.ctr) }}</template>
|
|
|
+ </el-table-column>
|
|
|
+ <el-table-column prop="cpc" label="CPC" width="90" align="right">
|
|
|
+ <template #default="{ row }">{{ formatNum(row.cpc) }}</template>
|
|
|
+ </el-table-column>
|
|
|
+ <el-table-column prop="conversionsCount" label="转化数" width="90" align="right">
|
|
|
+ <template #default="{ row }">{{ formatInt(row.conversionsCount) }}</template>
|
|
|
+ </el-table-column>
|
|
|
+ <el-table-column prop="conversionsCost" label="转化成本" width="100" align="right">
|
|
|
+ <template #default="{ row }">{{ formatNum(row.conversionsCost) }}</template>
|
|
|
+ </el-table-column>
|
|
|
+ <el-table-column label="1日ROI" width="90" align="right">
|
|
|
+ <template #default="{ row }">{{ formatRoi(row.incomeRoi1) }}</template>
|
|
|
+ </el-table-column>
|
|
|
+ <el-table-column label="1日LTV" width="100" align="right">
|
|
|
+ <template #default="{ row }">{{ formatNum(row.incomeVal1) }}</template>
|
|
|
+ </el-table-column>
|
|
|
+ <el-table-column label="3日ROI" width="90" align="right">
|
|
|
+ <template #default="{ row }">{{ formatRoi(row.incomeRoi3) }}</template>
|
|
|
+ </el-table-column>
|
|
|
+ <el-table-column label="3日LTV" width="100" align="right">
|
|
|
+ <template #default="{ row }">{{ formatNum(row.incomeVal3) }}</template>
|
|
|
+ </el-table-column>
|
|
|
+ <el-table-column label="7日ROI" width="90" align="right">
|
|
|
+ <template #default="{ row }">{{ formatRoi(row.incomeRoi7) }}</template>
|
|
|
+ </el-table-column>
|
|
|
+ <el-table-column label="7日LTV" width="100" align="right">
|
|
|
+ <template #default="{ row }">{{ formatNum(row.incomeVal7) }}</template>
|
|
|
+ </el-table-column>
|
|
|
+ <el-table-column label="14日ROI" width="90" align="right">
|
|
|
+ <template #default="{ row }">{{ formatRoi(row.incomeRoi14) }}</template>
|
|
|
+ </el-table-column>
|
|
|
+ <el-table-column label="14日LTV" width="100" align="right">
|
|
|
+ <template #default="{ row }">{{ formatNum(row.incomeVal14) }}</template>
|
|
|
+ </el-table-column>
|
|
|
+ <el-table-column label="24h ROI" width="100" align="right">
|
|
|
+ <template #default="{ row }">{{ formatRoi(row.incomeRoi124h) }}</template>
|
|
|
+ </el-table-column>
|
|
|
+ <el-table-column label="24h LTV" width="100" align="right">
|
|
|
+ <template #default="{ row }">{{ formatNum(row.incomeVal24h) }}</template>
|
|
|
+ </el-table-column>
|
|
|
+ <el-table-column prop="videoOuterPlayCount" label="视频播放" width="100" align="right">
|
|
|
+ <template #default="{ row }">{{ formatInt(row.videoOuterPlayCount) }}</template>
|
|
|
+ </el-table-column>
|
|
|
+ <el-table-column prop="videoOuterPlay3sCount" label="3s播放" width="90" align="right">
|
|
|
+ <template #default="{ row }">{{ formatInt(row.videoOuterPlay3sCount) }}</template>
|
|
|
+ </el-table-column>
|
|
|
+ </el-table>
|
|
|
+
|
|
|
+ <!-- 分页 -->
|
|
|
+ <div class="mt-6 flex justify-end">
|
|
|
+ <el-pagination
|
|
|
+ v-model:current-page="pagination.page"
|
|
|
+ v-model:page-size="pagination.pageSize"
|
|
|
+ :page-sizes="[20, 50, 100, 200]"
|
|
|
+ :total="pagination.total"
|
|
|
+ layout="total, sizes, prev, pager, next, jumper"
|
|
|
+ @size-change="handleSizeChange"
|
|
|
+ @current-change="handleCurrentChange"
|
|
|
+ />
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+</template>
|
|
|
+
|
|
|
+<script setup>
|
|
|
+defineOptions({
|
|
|
+ name: 'DailyAdgroupReport'
|
|
|
+})
|
|
|
+
|
|
|
+import { ref, reactive, computed, onMounted } from 'vue'
|
|
|
+import { ElMessage, ElMessageBox } from 'element-plus'
|
|
|
+import { Refresh, Search } from '@element-plus/icons-vue'
|
|
|
+import { useRoute } from 'vue-router'
|
|
|
+import { getDailyAdgroupReportList } from '@/api/dailyAdgroupReport'
|
|
|
+import { getAccountGroupListAll } from '@/api/accountGroup'
|
|
|
+
|
|
|
+const route = useRoute()
|
|
|
+
|
|
|
+// ─── 响应式状态 ───────────────────────────────────────────────────────────────
|
|
|
+const loading = ref(false)
|
|
|
+const syncing = ref(false)
|
|
|
+const reportList = ref([])
|
|
|
+const groupOptions = ref([])
|
|
|
+
|
|
|
+// 同步表单
|
|
|
+const syncForm = reactive({ accountId: '' })
|
|
|
+const syncDateRange = ref([])
|
|
|
+
|
|
|
+// 查询表单
|
|
|
+const queryForm = reactive({
|
|
|
+ timeLine: 'REQUEST_TIME',
|
|
|
+ accountId: '',
|
|
|
+ groupId: null,
|
|
|
+ adgroupId: '',
|
|
|
+})
|
|
|
+const getDefaultDateRange = () => {
|
|
|
+ const end = new Date()
|
|
|
+ const start = new Date()
|
|
|
+ start.setDate(start.getDate() - 0)
|
|
|
+ const fmt = (d) => `${d.getFullYear()}-${String(d.getMonth() + 1).padStart(2, '0')}-${String(d.getDate()).padStart(2, '0')}`
|
|
|
+ return [fmt(start), fmt(end)]
|
|
|
+}
|
|
|
+const queryDateRange = ref(getDefaultDateRange())
|
|
|
+
|
|
|
+// 分页
|
|
|
+const pagination = reactive({ page: 1, pageSize: 20, total: 0 })
|
|
|
+
|
|
|
+// ─── 计算属性 ─────────────────────────────────────────────────────────────────
|
|
|
+const totalPages = computed(() => Math.ceil(pagination.total / pagination.pageSize) || 0)
|
|
|
+
|
|
|
+// ─── 格式化工具 ───────────────────────────────────────────────────────────────
|
|
|
+const formatNum = (v) => (v == null ? '-' : Number(v).toFixed(2))
|
|
|
+const formatInt = (v) => (v == null ? '-' : Number(v).toLocaleString())
|
|
|
+const formatPct = (v) => (v == null ? '-' : (Number(v) * 100).toFixed(2) + '%')
|
|
|
+const formatRoi = (v) => (v == null ? '-' : Number(v).toFixed(4))
|
|
|
+
|
|
|
+// ─── 加载数据 ─────────────────────────────────────────────────────────────────
|
|
|
+const loadList = async () => {
|
|
|
+ loading.value = true
|
|
|
+ try {
|
|
|
+ const params = {
|
|
|
+ timeLine: queryForm.timeLine,
|
|
|
+ page: pagination.page,
|
|
|
+ pageSize: pagination.pageSize,
|
|
|
+ }
|
|
|
+ if (queryForm.accountId) params.accountId = queryForm.accountId
|
|
|
+ if (queryForm.groupId) params.groupId = queryForm.groupId
|
|
|
+ if (queryForm.adgroupId) params.adgroupId = queryForm.adgroupId
|
|
|
+ if (queryDateRange.value && queryDateRange.value.length === 2) {
|
|
|
+ params.startDate = queryDateRange.value[0]
|
|
|
+ params.endDate = queryDateRange.value[1]
|
|
|
+ }
|
|
|
+ const res = await getDailyAdgroupReportList(params)
|
|
|
+ if (res.success) {
|
|
|
+ reportList.value = res.data || []
|
|
|
+ pagination.total = res.total || 0
|
|
|
+ } else {
|
|
|
+ ElMessage.error(res.message || '查询失败')
|
|
|
+ }
|
|
|
+ } catch (err) {
|
|
|
+ console.error('查询日报表失败:', err)
|
|
|
+ ElMessage.error('查询失败')
|
|
|
+ } finally {
|
|
|
+ loading.value = false
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+// ─── 事件处理 ─────────────────────────────────────────────────────────────────
|
|
|
+const handleTimeLineChange = () => {
|
|
|
+ pagination.page = 1
|
|
|
+ loadList()
|
|
|
+}
|
|
|
+
|
|
|
+const handleSearch = () => {
|
|
|
+ pagination.page = 1
|
|
|
+ loadList()
|
|
|
+}
|
|
|
+
|
|
|
+const handleReset = () => {
|
|
|
+ queryForm.accountId = ''
|
|
|
+ queryForm.groupId = null
|
|
|
+ queryForm.adgroupId = ''
|
|
|
+ queryDateRange.value = getDefaultDateRange()
|
|
|
+ pagination.page = 1
|
|
|
+ loadList()
|
|
|
+}
|
|
|
+
|
|
|
+const handleSizeChange = (size) => {
|
|
|
+ pagination.pageSize = size
|
|
|
+ pagination.page = 1
|
|
|
+ loadList()
|
|
|
+}
|
|
|
+
|
|
|
+const handleCurrentChange = (page) => {
|
|
|
+ pagination.page = page
|
|
|
+ loadList()
|
|
|
+}
|
|
|
+
|
|
|
+// ─── 加载账号组 ───────────────────────────────────────────────────────────────
|
|
|
+const loadGroupOptions = async () => {
|
|
|
+ try {
|
|
|
+ const res = await getAccountGroupListAll()
|
|
|
+ if (res.success) {
|
|
|
+ groupOptions.value = res.data || []
|
|
|
+ }
|
|
|
+ } catch (err) {
|
|
|
+ console.error('加载账号组失败:', err)
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+// ─── 初始化 ───────────────────────────────────────────────────────────────────
|
|
|
+onMounted(() => {
|
|
|
+ loadGroupOptions()
|
|
|
+ if (route.query.adgroupId) {
|
|
|
+ queryForm.adgroupId = String(route.query.adgroupId)
|
|
|
+ }
|
|
|
+ loadList()
|
|
|
+})
|
|
|
+</script>
|