Browse Source

feat:日报

pudongliang 4 months ago
parent
commit
c7620856a3

+ 7 - 7
portal/src/router/index.js

@@ -97,16 +97,16 @@ const routes = [
         meta: { title: '转化归因管理', icon: 'DataAnalysis' }
       },
       {
-        path: 'daily-creative-report',
-        name: 'DailyReport',
-        component: () => import('@/views/daily-creative-report/index.vue'),
-        meta: { title: '创意日报表', icon: 'TrendCharts' }
-      },
-      {
         path: 'daily-adgroup-report',
         name: 'DailyAdgroupReport',
         component: () => import('@/views/daily-adgroup-report/index.vue'),
-        meta: { title: '广告日报表', icon: 'DataLine' }
+        meta: { title: '广告日报', icon: 'DataLine' , noCache: true }
+      },
+      {
+        path: 'daily-creative-report',
+        name: 'DailyCreativeReport',
+        component: () => import('@/views/daily-creative-report/index.vue'),
+        meta: { title: '创意日报', icon: 'TrendCharts', noCache: true }
       },
       // {
       //   path: 'adgroup',

+ 19 - 1
portal/src/views/ad-plan-conf/detail.vue

@@ -267,7 +267,7 @@
                 <el-table-column prop="creativeName" label="创意名称" min-width="100" show-overflow-tooltip />
                 <el-table-column prop="creativeId" label="腾讯创意ID" width="130">
                   <template #default="{ row }">
-                    {{ row.creativeId }}
+                    <el-button link size="small" type="primary" @click="goToDailyCreativeReport(row)">{{ row.creativeId }}</el-button>
                     <el-button v-if="row.creativeId > 0" link size="small" @click="goToTencentCreative(row)">
                      <el-icon color="#409efc" class="no-inherit">
                       <Share />
@@ -377,6 +377,7 @@
         </el-table-column>
         <el-table-column prop="adgroupId" label="广告ID" width="150" >
           <template #default="{ row }">
+            <el-button link size="small" type="primary" @click="goToDailyAdgroupReport(row)">{{ row.adgroupId }}</el-button>
             <el-button v-if="row.adgroupId > 0" size="small" link @click="goToTencentByAdgroupId(row)">
               <el-icon color="#409efc" class="no-inherit">
                 <Share />
@@ -581,6 +582,23 @@ 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.creativeId}%22}`, '_blank')
 }
 
+const goToDailyCreativeReport = (row) => {
+  router.push({
+    name: 'DailyCreativeReport',
+    query: {
+      dynamicCreativeId: row.creativeId
+    }
+  })
+}
+
+const goToDailyAdgroupReport = (row) => {
+  router.push({
+    name: 'DailyAdgroupReport',
+    query: {
+      adgroupId: row.adgroupId
+    }
+  })
+}
 const handleReset = () => {
   queryParams.value = {
     confId: route.query.id,

+ 81 - 22
portal/src/views/daily-adgroup-report/index.vue

@@ -90,44 +90,62 @@
         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="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 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="adgroupName" label="广告名称" width="180" 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="100" align="right">
+        <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 prop="cpc" label="CPC" width="90" align="right">
+          <template #default="{ row }">{{ formatNum(row.cpc) }}</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>
@@ -184,7 +202,7 @@ import { Refresh, Search } from '@element-plus/icons-vue'
 import { useRoute } from 'vue-router'
 import { getDailyAdgroupReportList } from '@/api/dailyAdgroupReport'
 import { getAccountGroupListAll } from '@/api/accountGroup'
-
+import dayjs from 'dayjs'
 const route = useRoute()
 
 // ─── 响应式状态 ───────────────────────────────────────────────────────────────
@@ -192,6 +210,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: '' })
@@ -207,7 +228,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)]
 }
@@ -216,6 +237,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)
 
@@ -223,7 +247,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 () => {
@@ -241,6 +265,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 getDailyAdgroupReportList(params)
     if (res.success) {
       reportList.value = res.data || []
@@ -256,6 +285,16 @@ 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 handleTimeLineChange = () => {
   pagination.page = 1
@@ -272,6 +311,22 @@ const handleReset = () => {
   queryForm.groupId = null
   queryForm.adgroupId = ''
   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()
 }
@@ -293,6 +348,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)

+ 95 - 21
portal/src/views/daily-creative-report/index.vue

@@ -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)
   }

+ 12 - 4
src/main/java/com/moka/gdtauto/controller/AccountGroupController.java

@@ -1,5 +1,16 @@
 package com.moka.gdtauto.controller;
 
+import java.util.List;
+
+import org.springframework.web.bind.annotation.DeleteMapping;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.PathVariable;
+import org.springframework.web.bind.annotation.PostMapping;
+import org.springframework.web.bind.annotation.RequestBody;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RequestParam;
+import org.springframework.web.bind.annotation.RestController;
+
 import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
 import com.moka.gdtauto.annotation.AuthUser;
 import com.moka.gdtauto.common.Result;
@@ -9,14 +20,12 @@ import com.moka.gdtauto.controller.vo.UpdateAccountGroupRequest;
 import com.moka.gdtauto.entity.AccountGroup;
 import com.moka.gdtauto.entity.SysUser;
 import com.moka.gdtauto.service.AccountGroupService;
+
 import io.swagger.v3.oas.annotations.Operation;
 import io.swagger.v3.oas.annotations.Parameter;
 import io.swagger.v3.oas.annotations.tags.Tag;
 import lombok.RequiredArgsConstructor;
 import lombok.extern.slf4j.Slf4j;
-import org.springframework.web.bind.annotation.*;
-
-import java.util.List;
 
 /**
  * 账号组管理控制器
@@ -135,7 +144,6 @@ public class AccountGroupController {
             @AuthUser SysUser sysUser) {
         try {
             List<AccountGroup> accountGroups = accountGroupService.lambdaQuery()
-                    .eq(AccountGroup::getUserId, sysUser.getId())
                     .orderByDesc(AccountGroup::getId)
                     .list();
             return Result.success(accountGroups, "查询成功");

+ 3 - 1
src/main/java/com/moka/gdtauto/controller/DailyAdgroupReportController.java

@@ -44,11 +44,13 @@ public class DailyAdgroupReportController {
             @Parameter(description = "广告组ID(可选)") @RequestParam(required = false) Long adgroupId,
             @Parameter(description = "开始日期 yyyy-MM-dd(可选)") @RequestParam(required = false) String startDate,
             @Parameter(description = "结束日期 yyyy-MM-dd(可选)") @RequestParam(required = false) String endDate,
+            @Parameter(description = "排序字段") @RequestParam(required = false) String sortField,
+            @Parameter(description = "排序方向 asc/desc") @RequestParam(required = false) String sortOrder,
             @Parameter(description = "页码") @RequestParam(defaultValue = "1") long page,
             @Parameter(description = "每页数量") @RequestParam(defaultValue = "20") long pageSize) {
         try {
             Page<? extends BaseDailyAdgroupReport> result =
-                    dailyReportService.listReports(timeLine, groupId, accountId, adgroupId, startDate, endDate, page, pageSize);
+                    dailyReportService.listReports(timeLine, groupId, accountId, adgroupId, startDate, endDate, sortField, sortOrder, page, pageSize);
             return Result.success(result.getRecords(), "查询成功")
                     .withPage(page, pageSize, result.getTotal());
         } catch (Exception e) {

+ 3 - 1
src/main/java/com/moka/gdtauto/controller/DailyCreativeReportController.java

@@ -44,11 +44,13 @@ public class DailyCreativeReportController {
             @Parameter(description = "动态创意ID(可选)") @RequestParam(required = false) Long dynamicCreativeId,
             @Parameter(description = "开始日期 yyyy-MM-dd(可选)") @RequestParam(required = false) String startDate,
             @Parameter(description = "结束日期 yyyy-MM-dd(可选)") @RequestParam(required = false) String endDate,
+            @Parameter(description = "排序字段") @RequestParam(required = false) String sortField,
+            @Parameter(description = "排序方向 asc/desc") @RequestParam(required = false) String sortOrder,
             @Parameter(description = "页码") @RequestParam(defaultValue = "1") long page,
             @Parameter(description = "每页数量") @RequestParam(defaultValue = "20") long pageSize) {
         try {
             Page<? extends BaseDailyCreativeReport> result =
-                    dailyReportService.listReports(timeLine, groupId, accountId, dynamicCreativeId, startDate, endDate, page, pageSize);
+                    dailyReportService.listReports(timeLine, groupId, accountId, dynamicCreativeId, startDate, endDate, sortField, sortOrder, page, pageSize);
             return Result.success(result.getRecords(), "查询成功")
                     .withPage(page, pageSize, result.getTotal());
         } catch (Exception e) {

+ 2 - 0
src/main/java/com/moka/gdtauto/entity/BaseDailyCreativeReport.java

@@ -26,6 +26,8 @@ public abstract class BaseDailyCreativeReport {
     private Long confId;
     /** 广告主账号ID */
     private Long accountId;
+    private Long adgroupId;
+    private String adgroupName;
 
     /** 账号组ID(来自 AccountGroup.id) */
     private Long groupId;

+ 36 - 7
src/main/java/com/moka/gdtauto/service/DailyAdgroupReportService.java

@@ -298,19 +298,21 @@ public class DailyAdgroupReportService {
      */
     public Page<? extends BaseDailyAdgroupReport> listReports(
             String timeLine, Long groupId, Long accountId, Long adgroupId,
-            String startDate, String endDate, long pageNum, long pageSize) {
+            String startDate, String endDate, String sortField, String sortOrder,
+            long pageNum, long pageSize) {
 
         if ("REPORTING_TIME".equals(timeLine)) {
-            LambdaQueryWrapper<RepDailyAdgroupReport> wrapper = buildRepWrapper(groupId, accountId, adgroupId, startDate, endDate);
+            LambdaQueryWrapper<RepDailyAdgroupReport> wrapper = buildRepWrapper(groupId, accountId, adgroupId, startDate, endDate, sortField, sortOrder);
             return repMapper.selectPage(new Page<>(pageNum, pageSize), wrapper);
         } else {
-            LambdaQueryWrapper<ReqDailyAdgroupReport> wrapper = buildReqWrapper(groupId, accountId, adgroupId, startDate, endDate);
+            LambdaQueryWrapper<ReqDailyAdgroupReport> wrapper = buildReqWrapper(groupId, accountId, adgroupId, startDate, endDate, sortField, sortOrder);
             return reqMapper.selectPage(new Page<>(pageNum, pageSize), wrapper);
         }
     }
 
     private LambdaQueryWrapper<ReqDailyAdgroupReport> buildReqWrapper(
-            Long groupId, Long accountId, Long adgroupId, String startDate, String endDate) {
+            Long groupId, Long accountId, Long adgroupId, String startDate, String endDate,
+            String sortField, String sortOrder) {
         LambdaQueryWrapper<ReqDailyAdgroupReport> wrapper = new LambdaQueryWrapper<>();
         if (groupId != null) {
             wrapper.eq(ReqDailyAdgroupReport::getGroupId, groupId);
@@ -327,12 +329,14 @@ public class DailyAdgroupReportService {
         if (StringUtils.hasText(endDate)) {
             wrapper.le(ReqDailyAdgroupReport::getDate, LocalDate.parse(endDate));
         }
-        wrapper.orderByDesc(ReqDailyAdgroupReport::getDate);
+        // 应用排序
+        applySort(wrapper, sortField, sortOrder);
         return wrapper;
     }
 
     private LambdaQueryWrapper<RepDailyAdgroupReport> buildRepWrapper(
-            Long groupId, Long accountId, Long adgroupId, String startDate, String endDate) {
+            Long groupId, Long accountId, Long adgroupId, String startDate, String endDate,
+            String sortField, String sortOrder) {
         LambdaQueryWrapper<RepDailyAdgroupReport> wrapper = new LambdaQueryWrapper<>();
         if (groupId != null) {
             wrapper.eq(RepDailyAdgroupReport::getGroupId, groupId);
@@ -349,10 +353,35 @@ public class DailyAdgroupReportService {
         if (StringUtils.hasText(endDate)) {
             wrapper.le(RepDailyAdgroupReport::getDate, LocalDate.parse(endDate));
         }
-        wrapper.orderByDesc(RepDailyAdgroupReport::getDate);
+        // 应用排序
+        applySort(wrapper, sortField, sortOrder);
         return wrapper;
     }
 
+    /**
+     * 应用排序逻辑
+     */
+    private <T extends BaseDailyAdgroupReport> void applySort(LambdaQueryWrapper<T> wrapper, String sortField, String sortOrder) {
+        boolean isAsc = "asc".equalsIgnoreCase(sortOrder);
+        if (!StringUtils.hasText(sortField)) {
+            // 默认按日期降序
+            wrapper.orderByDesc(BaseDailyAdgroupReport::getDate);
+            return;
+        }
+        switch (sortField) {
+            case "date" -> wrapper.orderBy(true, isAsc, BaseDailyAdgroupReport::getDate);
+            case "cost" -> wrapper.orderBy(true, isAsc, BaseDailyAdgroupReport::getCost);
+            case "viewCount" -> wrapper.orderBy(true, isAsc, BaseDailyAdgroupReport::getViewCount);
+            case "validClickCount" -> wrapper.orderBy(true, isAsc, BaseDailyAdgroupReport::getValidClickCount);
+            case "ctr" -> wrapper.orderBy(true, isAsc, BaseDailyAdgroupReport::getCtr);
+            case "conversionsCount" -> wrapper.orderBy(true, isAsc, BaseDailyAdgroupReport::getConversionsCount);
+            case "conversionsCost" -> wrapper.orderBy(true, isAsc, BaseDailyAdgroupReport::getConversionsCost);
+            case "incomeRoi1" -> wrapper.orderBy(true, isAsc, BaseDailyAdgroupReport::getIncomeRoi1);
+            case "incomeRoi3" -> wrapper.orderBy(true, isAsc, BaseDailyAdgroupReport::getIncomeRoi3);
+            default -> wrapper.orderByDesc(BaseDailyAdgroupReport::getDate);
+        }
+    }
+
     // ─── 映射方法 ─────────────────────────────────────────────────────────────
 
     private ReqDailyAdgroupReport toReqEntity(Long confId, Long groupId, ReportStruct rs) {

+ 39 - 8
src/main/java/com/moka/gdtauto/service/DailyCreativeReportService.java

@@ -61,7 +61,7 @@ public class DailyCreativeReportService {
 
     /** 同步时请求的指标字段 */
     private static final List<String> SYNC_FIELDS = Arrays.asList(
-        "account_id",
+        "account_id", "adgroup_id", "adgroup_name",
             "dynamic_creative_id", "dynamic_creative_name", "date",
             // 曝光点击
             "view_count", "view_user_count", "avg_view_per_user",
@@ -295,19 +295,21 @@ public class DailyCreativeReportService {
      */
     public Page<? extends BaseDailyCreativeReport> listReports(
             String timeLine, Long groupId, Long accountId, Long dynamicCreativeId,
-            String startDate, String endDate, long pageNum, long pageSize) {
+            String startDate, String endDate, String sortField, String sortOrder,
+            long pageNum, long pageSize) {
 
         if ("REPORTING_TIME".equals(timeLine)) {
-            LambdaQueryWrapper<RepDailyCreativeReport> wrapper = buildRepWrapper(groupId, accountId, dynamicCreativeId, startDate, endDate);
+            LambdaQueryWrapper<RepDailyCreativeReport> wrapper = buildRepWrapper(groupId, accountId, dynamicCreativeId, startDate, endDate, sortField, sortOrder);
             return repMapper.selectPage(new Page<>(pageNum, pageSize), wrapper);
         } else {
-            LambdaQueryWrapper<ReqDailyCreativeReport> wrapper = buildReqWrapper(groupId, accountId, dynamicCreativeId, startDate, endDate);
+            LambdaQueryWrapper<ReqDailyCreativeReport> wrapper = buildReqWrapper(groupId, accountId, dynamicCreativeId, startDate, endDate, sortField, sortOrder);
             return reqMapper.selectPage(new Page<>(pageNum, pageSize), wrapper);
         }
     }
 
     private LambdaQueryWrapper<ReqDailyCreativeReport> buildReqWrapper(
-            Long groupId, Long accountId, Long dynamicCreativeId, String startDate, String endDate) {
+            Long groupId, Long accountId, Long dynamicCreativeId, String startDate, String endDate,
+            String sortField, String sortOrder) {
         LambdaQueryWrapper<ReqDailyCreativeReport> wrapper = new LambdaQueryWrapper<>();
         if (groupId != null) {
             wrapper.eq(ReqDailyCreativeReport::getGroupId, groupId);
@@ -324,12 +326,14 @@ public class DailyCreativeReportService {
         if (StringUtils.hasText(endDate)) {
             wrapper.le(ReqDailyCreativeReport::getDate, LocalDate.parse(endDate));
         }
-        wrapper.orderByDesc(ReqDailyCreativeReport::getDate);
+        // 应用排序
+        applySort(wrapper, sortField, sortOrder);
         return wrapper;
     }
 
     private LambdaQueryWrapper<RepDailyCreativeReport> buildRepWrapper(
-            Long groupId, Long accountId, Long dynamicCreativeId, String startDate, String endDate) {
+            Long groupId, Long accountId, Long dynamicCreativeId, String startDate, String endDate,
+            String sortField, String sortOrder) {
         LambdaQueryWrapper<RepDailyCreativeReport> wrapper = new LambdaQueryWrapper<>();
         if (groupId != null) {
             wrapper.eq(RepDailyCreativeReport::getGroupId, groupId);
@@ -346,10 +350,35 @@ public class DailyCreativeReportService {
         if (StringUtils.hasText(endDate)) {
             wrapper.le(RepDailyCreativeReport::getDate, LocalDate.parse(endDate));
         }
-        wrapper.orderByDesc(RepDailyCreativeReport::getDate);
+        // 应用排序
+        applySort(wrapper, sortField, sortOrder);
         return wrapper;
     }
 
+    /**
+     * 应用排序逻辑
+     */
+    private <T extends BaseDailyCreativeReport> void applySort(LambdaQueryWrapper<T> wrapper, String sortField, String sortOrder) {
+        boolean isAsc = "asc".equalsIgnoreCase(sortOrder);
+        if (!StringUtils.hasText(sortField)) {
+            // 默认按日期降序
+            wrapper.orderByDesc(BaseDailyCreativeReport::getDate);
+            return;
+        }
+        switch (sortField) {
+            case "date" -> wrapper.orderBy(true, isAsc, BaseDailyCreativeReport::getDate);
+            case "cost" -> wrapper.orderBy(true, isAsc, BaseDailyCreativeReport::getCost);
+            case "viewCount" -> wrapper.orderBy(true, isAsc, BaseDailyCreativeReport::getViewCount);
+            case "validClickCount" -> wrapper.orderBy(true, isAsc, BaseDailyCreativeReport::getValidClickCount);
+            case "ctr" -> wrapper.orderBy(true, isAsc, BaseDailyCreativeReport::getCtr);
+            case "conversionsCount" -> wrapper.orderBy(true, isAsc, BaseDailyCreativeReport::getConversionsCount);
+            case "conversionsCost" -> wrapper.orderBy(true, isAsc, BaseDailyCreativeReport::getConversionsCost);
+            case "incomeRoi1" -> wrapper.orderBy(true, isAsc, BaseDailyCreativeReport::getIncomeRoi1);
+            case "incomeRoi3" -> wrapper.orderBy(true, isAsc, BaseDailyCreativeReport::getIncomeRoi3);
+            default -> wrapper.orderByDesc(BaseDailyCreativeReport::getDate);
+        }
+    }
+
     // ─── 映射方法 ─────────────────────────────────────────────────────────────
 
     private ReqDailyCreativeReport toReqEntity(Long confId, Long groupId,  ReportStruct rs) {
@@ -367,6 +396,8 @@ public class DailyCreativeReportService {
     private void fillFields(BaseDailyCreativeReport e, Long confId, Long groupId, ReportStruct rs) {
         e.setConfId(confId);
         e.setAccountId(rs.getAccountId());
+        e.setAdgroupId(rs.getAdgroupId());
+        e.setAdgroupName(rs.getAdgroupName());
         e.setGroupId(groupId);
         e.setDynamicCreativeId(rs.getDynamicCreativeId());
         e.setDynamicCreativeName(rs.getDynamicCreativeName());