|
|
@@ -118,42 +118,69 @@
|
|
|
border
|
|
|
style="width: 100%"
|
|
|
max-height="600"
|
|
|
+ :default-sort="{ prop: 'cost', order: 'descending' }"
|
|
|
+ @sort-change="handleSortChange"
|
|
|
>
|
|
|
<!-- yyyy-MM-dd -->
|
|
|
- <el-table-column prop="date" label="日期" width="110" fixed="left" >
|
|
|
+ <!-- <el-table-column prop="confId" label="配置ID" width="90" /> -->
|
|
|
+ <el-table-column prop="groupId" label="账号组ID" width="110" show-overflow-tooltip >
|
|
|
+ <template #default="{ row }">{{ getGroupName(row.groupId) }}</template>
|
|
|
+ </el-table-column>
|
|
|
+ <el-table-column prop="date" label="日期" width="110" sortable="custom">
|
|
|
<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="dynamicCreativeId" label="动态创意ID" width="140" />
|
|
|
- <el-table-column prop="cost" label="消耗(元)" width="110" align="right">
|
|
|
- <template #default="{ row }">{{ formatNum(row.cost) }}</template>
|
|
|
+ <el-table-column prop="accountId" label="账户ID" width="110" >
|
|
|
+ <template #default="{ row }">
|
|
|
+ <el-button v-if="row.accountId > 0" type="primary" size="small" link @click="goToTencentByAccountId(row)">
|
|
|
+ {{ row.accountId }}
|
|
|
+ </el-button>
|
|
|
+ </template>
|
|
|
+ </el-table-column>
|
|
|
+ <!-- <el-table-column prop="adgroupId" label="广告ID" width="120" >
|
|
|
+ <template #default="{ row }">
|
|
|
+ <el-button v-if="row.adgroupId > 0" type="primary" size="small" link @click="goToTencentByAdgroupId(row)">
|
|
|
+ {{ row.adgroupId }}
|
|
|
+ </el-button>
|
|
|
+ </template>
|
|
|
+ </el-table-column> -->
|
|
|
+ <el-table-column prop="dynamicCreativeId" label="创意ID" width="120" >
|
|
|
+ <template #default="{ row }">
|
|
|
+ <el-button v-if="row.dynamicCreativeId > 0" type="primary" size="small" link @click="goToTencentCreative(row)">
|
|
|
+ {{ row.dynamicCreativeId }}
|
|
|
+ </el-button>
|
|
|
+ </template>
|
|
|
</el-table-column>
|
|
|
- <el-table-column prop="viewCount" label="曝光数" width="100" align="right">
|
|
|
+ <el-table-column prop="dynamicCreativeName" label="创意名称" width="150" show-overflow-tooltip />
|
|
|
+ <el-table-column prop="cost" label="消耗(元)" width="110" align="right" sortable="custom">
|
|
|
+ <template #default="{ row }">{{ formatNum(row.cost / 100) }}</template>
|
|
|
+ </el-table-column>
|
|
|
+ <el-table-column prop="viewCount" label="曝光数" width="110" align="right" sortable="custom">
|
|
|
<template #default="{ row }">{{ formatInt(row.viewCount) }}</template>
|
|
|
</el-table-column>
|
|
|
- <el-table-column prop="validClickCount" label="有效点击" width="100" align="right">
|
|
|
+ <el-table-column prop="validClickCount" label="有效点击" width="110" align="right" sortable="custom">
|
|
|
<template #default="{ row }">{{ formatInt(row.validClickCount) }}</template>
|
|
|
</el-table-column>
|
|
|
- <el-table-column prop="ctr" label="点击率" width="90" align="right">
|
|
|
+ <el-table-column prop="ctr" label="点击率" width="110" align="right" sortable="custom">
|
|
|
<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">
|
|
|
+ <el-table-column prop="conversionsCount" label="转化数" width="110" align="right" sortable="custom">
|
|
|
<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 prop="conversionsCost" label="转化成本" width="110" align="right" sortable="custom">
|
|
|
+ <template #default="{ row }">{{ formatNum(row.conversionsCost / 100) }}</template>
|
|
|
</el-table-column>
|
|
|
- <el-table-column label="1日ROI" width="90" align="right">
|
|
|
+ <el-table-column prop="incomeRoi1" label="1日ROI" width="110" align="right" sortable="custom">
|
|
|
<template #default="{ row }">{{ formatRoi(row.incomeRoi1) }}</template>
|
|
|
</el-table-column>
|
|
|
+ <el-table-column prop="incomeRoi3" label="3日ROI" width="110" align="right" sortable="custom">
|
|
|
+ <template #default="{ row }">{{ formatRoi(row.incomeRoi3) }}</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 prop="cpc" label="CPC" width="90" align="right">
|
|
|
+ <template #default="{ row }">{{ formatNum(row.cpc) }}</template>
|
|
|
</el-table-column>
|
|
|
<el-table-column label="3日LTV" width="100" align="right">
|
|
|
<template #default="{ row }">{{ formatNum(row.incomeVal3) }}</template>
|
|
|
@@ -211,6 +238,7 @@ import { Refresh, Search } from '@element-plus/icons-vue'
|
|
|
import { useRoute } from 'vue-router'
|
|
|
import { getDailyReportList } from '@/api/dailyCreativeReport'
|
|
|
import { getAccountGroupListAll } from '@/api/accountGroup'
|
|
|
+import dayjs from 'dayjs'
|
|
|
|
|
|
const route = useRoute()
|
|
|
|
|
|
@@ -219,6 +247,9 @@ const loading = ref(false)
|
|
|
const syncing = ref(false)
|
|
|
const reportList = ref([])
|
|
|
const groupOptions = ref([])
|
|
|
+const groupNameMap = ref({})
|
|
|
+
|
|
|
+const getGroupName = (id) => groupNameMap.value[id] || ''
|
|
|
|
|
|
// 同步表单
|
|
|
const syncForm = reactive({ accountId: '' })
|
|
|
@@ -234,7 +265,7 @@ const queryForm = reactive({
|
|
|
const getDefaultDateRange = () => {
|
|
|
const end = new Date()
|
|
|
const start = new Date()
|
|
|
- start.setDate(start.getDate() - 0)
|
|
|
+ start.setDate(start.getDate() - 7)
|
|
|
const fmt = (d) => `${d.getFullYear()}-${String(d.getMonth() + 1).padStart(2, '0')}-${String(d.getDate()).padStart(2, '0')}`
|
|
|
return [fmt(start), fmt(end)]
|
|
|
}
|
|
|
@@ -243,6 +274,9 @@ const queryDateRange = ref(getDefaultDateRange())
|
|
|
// 分页
|
|
|
const pagination = reactive({ page: 1, pageSize: 20, total: 0 })
|
|
|
|
|
|
+// 排序参数(默认按消耗降序)
|
|
|
+const sortParams = reactive({ sortField: 'cost', sortOrder: 'desc' })
|
|
|
+
|
|
|
// ─── 计算属性 ─────────────────────────────────────────────────────────────────
|
|
|
const totalPages = computed(() => Math.ceil(pagination.total / pagination.pageSize) || 0)
|
|
|
|
|
|
@@ -250,7 +284,7 @@ const totalPages = computed(() => Math.ceil(pagination.total / pagination.pageSi
|
|
|
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 formatRoi = (v) => (v == null ? '-' : (Number(v) * 100).toFixed(2) + '%')
|
|
|
|
|
|
// ─── 加载数据 ─────────────────────────────────────────────────────────────────
|
|
|
const loadList = async () => {
|
|
|
@@ -268,6 +302,11 @@ const loadList = async () => {
|
|
|
params.startDate = queryDateRange.value[0]
|
|
|
params.endDate = queryDateRange.value[1]
|
|
|
}
|
|
|
+ // 添加排序参数
|
|
|
+ if (sortParams.sortField) {
|
|
|
+ params.sortField = sortParams.sortField
|
|
|
+ params.sortOrder = sortParams.sortOrder
|
|
|
+ }
|
|
|
const res = await getDailyReportList(params)
|
|
|
if (res.success) {
|
|
|
reportList.value = res.data || []
|
|
|
@@ -283,6 +322,21 @@ const loadList = async () => {
|
|
|
}
|
|
|
}
|
|
|
|
|
|
+
|
|
|
+const goToTencentByAccountId = (row) => {
|
|
|
+ const date = dayjs().format('YYYY-MM-DD')
|
|
|
+ window.open(`https://ad.qq.com/atlas/${row.accountId}/admanage/index?start_date=${date}&end_date=${date}&tab=adgroup&query={%22operation_status%22:[%22CALCULATE_STATUS_EXCLUDE_DEL%22],%22system_status%22:[]}`, '_blank')
|
|
|
+}
|
|
|
+
|
|
|
+const goToTencentByAdgroupId = (row) => {
|
|
|
+ const date = dayjs().format('YYYY-MM-DD')
|
|
|
+ window.open(`https://ad.qq.com/atlas/${row.accountId}/admanage/index?start_date=${date}&end_date=${date}&tab=adgroup&query={%22operation_status%22:[%22CALCULATE_STATUS_EXCLUDE_DEL%22],%22system_status%22:[],%22search_name%22:%22${row.adgroupId}%22}`, '_blank')
|
|
|
+}
|
|
|
+
|
|
|
+const goToTencentCreative = (row) => {
|
|
|
+ window.open(`https://ad.qq.com/atlas/${row.accountId}/admanage/index?tab=dynamic_creative&query={%22operation_status%22:[],%22created_time%22:%22-89%22,%22fuzzy_name%22:%22${row.dynamicCreativeId}%22}`, '_blank')
|
|
|
+}
|
|
|
+
|
|
|
// ─── 事件处理 ─────────────────────────────────────────────────────────────────
|
|
|
const handleTimeLineChange = () => {
|
|
|
pagination.page = 1
|
|
|
@@ -299,6 +353,22 @@ const handleReset = () => {
|
|
|
queryForm.groupId = null
|
|
|
queryForm.dynamicCreativeId = ''
|
|
|
queryDateRange.value = getDefaultDateRange()
|
|
|
+ // 重置排序
|
|
|
+ sortParams.sortField = ''
|
|
|
+ sortParams.sortOrder = ''
|
|
|
+ pagination.page = 1
|
|
|
+ loadList()
|
|
|
+}
|
|
|
+
|
|
|
+// 排序事件处理
|
|
|
+const handleSortChange = ({ prop, order }) => {
|
|
|
+ if (prop && order) {
|
|
|
+ sortParams.sortField = prop
|
|
|
+ sortParams.sortOrder = order === 'ascending' ? 'asc' : 'desc'
|
|
|
+ } else {
|
|
|
+ sortParams.sortField = ''
|
|
|
+ sortParams.sortOrder = ''
|
|
|
+ }
|
|
|
pagination.page = 1
|
|
|
loadList()
|
|
|
}
|
|
|
@@ -320,6 +390,10 @@ const loadGroupOptions = async () => {
|
|
|
const res = await getAccountGroupListAll()
|
|
|
if (res.success) {
|
|
|
groupOptions.value = res.data || []
|
|
|
+ groupNameMap.value = res.data.reduce((acc, cur) => {
|
|
|
+ acc[cur.id] = cur.groupName
|
|
|
+ return acc
|
|
|
+ }, {})
|
|
|
}
|
|
|
} catch (err) {
|
|
|
console.error('加载账号组失败:', err)
|
|
|
@@ -327,8 +401,8 @@ const loadGroupOptions = async () => {
|
|
|
}
|
|
|
|
|
|
// ─── 初始化 ───────────────────────────────────────────────────────────────────
|
|
|
-onMounted(() => {
|
|
|
- loadGroupOptions()
|
|
|
+onMounted(async () => {
|
|
|
+ await loadGroupOptions()
|
|
|
if (route.query.dynamicCreativeId) {
|
|
|
queryForm.dynamicCreativeId = String(route.query.dynamicCreativeId)
|
|
|
}
|