Explorar el Código

Merge branch 'wang' into test

WanGxC hace 1 año
padre
commit
b0f99188d9

+ 1 - 1
src/components/MetricsCards/mCard.vue

@@ -5,7 +5,7 @@
     <div class="metric-value">{{ selectedData?.metricVal }}</div>
     <div class="metric-pre">
       <span>{{ selectedData?.preVal }}&nbsp;&nbsp;</span>
-      <el-icon v-show="selectedData?.gapVal">
+      <el-icon v-show="selectedData?.gapVal" style="display: inline-block; padding-top: 2px">
         <Top :class="colorClass" v-if="isBoost"/>
         <Bottom :class="colorClass" v-else/>
       </el-icon>

+ 28 - 0
src/theme/app.scss

@@ -415,4 +415,32 @@ body,
 	}
 }
 
+.overview-tabs {
+	display: flex;
+	gap: 8px;
+	padding-bottom: 8px;
+	position: sticky;
+	top: 40px;
+	padding-top: 8px;
+	z-index: 10;
+	width: 100%;
+	background-color: #f8f8f8;
+	box-shadow: 0px 0px 0px rgba(51,89,181,0.16);
 
+	.el-input__wrapper {
+		border-radius: 0;
+	}
+}
+
+.overview-top {
+	background-color: #fff;
+	position: sticky;
+	top: 0px;
+	z-index: 9;
+	box-shadow: 0px 0px 12px rgba(51, 89, 181, 0.16);
+
+	padding: 0 12px;
+	display: flex;
+	height: 40px;
+	gap: 24px;
+}

+ 2 - 2
src/types/views.d.ts

@@ -349,8 +349,8 @@ declare interface ShowMetric {
 
 declare interface MetricData extends MetricOptions {
   metricVal: string,
-  preVal?: number | null,
-  gapVal?: number | null
+  preVal?: any,
+  gapVal?: any
 }
 
 declare interface Portfolio {

+ 312 - 0
src/views/adManage/ad-overview/chartComponents/dataTendency.vue

@@ -0,0 +1,312 @@
+<template>
+  <div v-loading="loading">
+    <MetricsCards v-model="metrics" :metric-items="metricsItems" @change="changeMetric"></MetricsCards>
+    <div style="height: 350px;" ref="chartRef"></div>
+</div>
+</template>
+
+<script lang="ts" setup>
+import { ref,onMounted, onBeforeUnmount, Ref, unref, watch, computed } from 'vue'
+import * as echarts from 'echarts'
+import { spCampaignMetricsEnum } from '/@/views/adManage/utils/enum.js'
+import MetricsCards from '/@/components/MetricsCards/index.vue'
+import XEUtils from 'xe-utils'
+import { buildChartOpt, parseQueryParams } from '/@/views/adManage/utils/tools.js'
+
+defineOptions({
+  name: "DataTendencyChart"
+})
+
+interface Props {
+  fetchCard: Function,
+  fetchLine: Function,
+  fetchLineMonth?: Function,
+  fetchLineWeek?: Function,
+  query: {[key: string]: any},
+  initMetric?: ShowMetric[],
+  metricEnum?: {[key: string]: string}[]
+}
+
+const props = withDefaults(defineProps<Props>(), {
+  initMetric: () => [
+    {metric: 'Impression', color: '#0085ff', 'label': '曝光量'},
+    {metric: 'Click', color: '#3fd4cf', 'label': '点击量'},
+    {metric: 'Spend', color: '#ff9500', 'label': '花费'},
+  ],
+  metricEnum: () => spCampaignMetricsEnum
+})
+
+const metrics = ref(props.initMetric)
+// const shopInfo = useShopInfo()
+// const publicData = usePublicData()
+// const { dateRange } = storeToRefs(publicData)
+const metricsItems: Ref<MetricData[]> = ref([])
+let chartObj:any
+const chartRef = ref()
+const statDim = ref('day')
+const option: any = {
+  dataset: {
+    source: []
+  },
+  tooltip: {
+    trigger: 'axis',
+    axisPointer: {
+      label: {
+        backgroundColor: '#6a7985'
+      }
+    }
+  },
+  legend: {
+    selected: {},  // 控制显隐
+    show: false
+  },
+  grid: {
+    top: 50, right: 150, bottom: 30, left: 65,
+  },
+  xAxis: {
+    type: 'category'
+  },
+  yAxis: [
+    {
+      id: 0,
+      type: 'value',
+      name: '',
+      splitLine: {
+        show: true // 设置显示分割线
+      },
+      axisLine: {
+        show: true,
+				lineStyle: { color: '' }
+      },
+      show: true
+    },
+    {
+      id: 1,
+      type: 'value',
+      name: '',
+      position: 'right',
+      splitLine: {
+        show: false
+      },
+      axisLine: {
+        show: true,
+				lineStyle: {
+					color: ''
+				}
+      },
+      show: true
+    },
+    {
+      id: 2,
+      type: 'value',
+      position: 'right',
+      offset: 90,
+      name: '',
+      splitLine: {
+        show: false
+      },
+      axisLine: {
+        show: true,
+				lineStyle: {
+					color: ''
+				}
+      },
+      show: true
+    }
+  ],
+  series: [
+    {
+      id: 0,
+      name: '',
+      type: 'bar',
+      encode: {
+        x: 'Name',
+        y: ''
+      },
+      barWidth: '20px',
+      yAxisIndex: 0,
+      itemStyle: {
+        color: '',
+        borderRadius: [6, 6, 6, 6],
+      }
+    },
+    {
+      id: 1,
+      name: '',
+      type: 'line',
+      encode: {
+        x: 'Name',
+        y: ''
+      },
+      symbolSize: 6,
+      symbol: 'circle',
+      smooth: true,
+      yAxisIndex: 1,
+      itemStyle: {
+        // color: '#ff9500',
+        // borderColor: '#ff9500'
+      },
+      areaStyle: {
+        // color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
+        //   { offset: 0, color: '#3fd4cf53' },
+        //   { offset: 1, color: '#3fd4cf03' },
+        // ]),
+      },
+      emphasis: {
+        focus:'series'
+      }
+    },
+    {
+      id: 2,
+      name: '',
+      type: 'line',
+      encode: {
+        x: 'Name',
+        y: ''
+      },
+      symbolSize: 6,
+      symbol: 'circle',
+      smooth: true,
+      yAxisIndex: 2,
+      itemStyle: {},
+      areaStyle: {},
+      emphasis: {
+        focus:'series'
+      }
+    }
+  ]
+}
+const loading = ref(true)
+const queryParams = computed(() => parseQueryParams(props.query))
+
+onMounted(() => {
+  getMetricsItems()
+	addResize()
+  // initLine()
+  setTimeout(() => { initLine() }, 0)
+})
+onBeforeUnmount(() => {
+	if(chartObj) {
+		chartObj.dispose()
+    chartObj = null
+	}
+  removeResize()
+})
+
+const initLine = async () => {
+	chartObj = echarts.init(chartRef.value)
+	const items = await getDataset()
+	option.dataset.source = items
+
+  XEUtils.arrayEach(option.series, (info:any, index) => {
+    const color = metrics.value[index].color
+    info.name = metrics.value[index].label
+    info.encode.y = metrics.value[index].metric
+    if (info.type === 'bar') {
+      info.itemStyle.color = color
+    } else {
+      info.itemStyle = { color: color, borderColor: color }
+      info.areaStyle = {
+        color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
+          { offset: 0, color: color + '53' },
+          { offset: 1, color: color + '03' },
+        ])
+      }
+    }
+  })
+  XEUtils.arrayEach(option.yAxis, (info:any, index) => {
+    info.name = metrics.value[index].label
+    info.axisLine.lineStyle.color = metrics.value[index].color
+  })
+
+  XEUtils.arrayEach(props.metricEnum, info => {
+    option.legend.selected[info.label] = false
+  })
+  for(const info of metrics.value) {
+    option.legend.selected[info.label] = true
+  }
+  // console.log(option)
+	chartObj.setOption(option)
+  loading.value = false
+}
+const getDataset = async () => {
+	if (statDim.value === 'week') {
+    if (props.fetchLineWeek) {
+      const resp = await props.fetchLineWeek(queryParams.value)
+      return resp.data
+    }
+  } else if (statDim.value === 'month') {
+    if (props.fetchLineMonth) {
+      const resp = await props.fetchLineMonth(queryParams.value)
+      return resp.data
+    }
+  } else {
+    const resp = await props.fetchLine(queryParams.value)
+    return resp.data
+  }
+}
+const getMetricsItems = async () => {
+	const resp = await props.fetchCard(queryParams.value)
+	const data = resp.data
+  metricsItems.value.length = 0
+	XEUtils.arrayEach(props.metricEnum, info => {
+		const tmp:MetricData = {
+			label: info.label,
+			value: info.value,
+			metricVal: data[info.value],
+			gapVal: data[`gap${info.value}`],
+			preVal: data[`prev${info.value}`],
+		}
+		metricsItems.value.push(tmp)
+	})
+}
+
+const changeMetric = () => {
+  const opt = buildChartOpt(option, metrics.value)
+  chartObj.setOption(opt)
+}
+
+const changeStatDim = async () => {
+  loading.value = true
+  let source = await getDataset()
+  if (source.length > 0) {
+    chartObj.setOption({dataset: {source: source}})
+  }
+  loading.value = false
+}
+
+watch(
+  props.query,
+  async () => {
+    // console.log("------watch-----queryParams", props.query)
+    loading.value = true
+    await getMetricsItems()
+    const items = await getDataset()
+    const opt = { dataset: { source: items } }
+    chartObj.setOption(opt)
+    loading.value = false
+  }
+)
+
+const resizeChart = () => { chartObj.resize() }
+const addResize = () => { window.addEventListener('resize', resizeChart) }
+const removeResize = () => { window.removeEventListener('resize', resizeChart) }
+
+
+</script>
+
+<style scoped>
+.metrics-cards {
+  display: flex;
+  justify-content: space-between;
+  align-items: flex-start;
+  gap: 12px;
+  width: 100%;
+}
+
+.chart-button-group {
+  display: flex;
+  justify-content: flex-end;
+  margin-top: 5px;
+}
+</style>

+ 80 - 0
src/views/adManage/ad-overview/daily/api.ts

@@ -0,0 +1,80 @@
+import {request} from '/@/utils/service'
+import {UserPageQuery, AddReq, DelReq, EditReq, InfoReq} from '@fast-crud/fast-crud'
+import XEUtils from 'xe-utils'
+
+export const apiPrefix = '/api/ad_manage/summary/report/trend/'
+
+export function GetList(query: UserPageQuery) {
+  return request({
+    url: apiPrefix + 'daily',
+    method: 'get',
+    params: query,
+  })
+}
+
+export function GetObj(id: any) {
+  return request({
+    url: apiPrefix + id + '/',
+    method: 'get',
+  })
+}
+
+export function AddObj(obj: AddReq) {
+  return request({
+    url: apiPrefix,
+    method: 'post',
+    data: obj,
+  })
+}
+
+export function UpdateObj(obj: EditReq) {
+  return request({
+    url: apiPrefix + obj.id + '/',
+    method: 'put',
+    data: obj,
+  })
+}
+
+export function DelObj(id: DelReq) {
+  return request({
+    url: apiPrefix + id + '/',
+    method: 'delete',
+    data: {id},
+  })
+}
+
+export function getCardData(query: UserPageQuery) {
+  return request({
+    url: '/api/ad_manage/summary/report/total',
+    method: 'GET',
+    params: query,
+  })
+}
+
+export function getLineData(query: UserPageQuery) {
+  query['dateRangeType'] = 'D'
+  return request({
+    url: apiPrefix + 'daily',
+    method: 'GET',
+    params: query
+  })
+}
+
+export function getLineWeekData(query: UserPageQuery) {
+  query['dateRangeType'] = 'W'
+  return request({
+    url: apiPrefix + 'hourly',
+    method: 'GET',
+    params: query
+  })
+}
+
+export function getLineMonthData(query: UserPageQuery) {
+  query['dateRangeType'] = 'M'
+  return request({
+    url: apiPrefix + 'hourly',
+    method: 'GET',
+    params: query
+  })
+}
+

+ 145 - 0
src/views/adManage/ad-overview/daily/crud.tsx

@@ -0,0 +1,145 @@
+import * as api from './api'
+import {AddReq, CreateCrudOptionsProps, CreateCrudOptionsRet, DelReq, EditReq, UserPageQuery} from '@fast-crud/fast-crud'
+import {inject} from 'vue'
+import {BaseColumn} from '/@/views/adManage/utils/commonTabColumn.js'
+import {parseQueryParams} from '/@/views/adManage/utils/tools.js'
+import XEUtils from 'xe-utils'
+
+export const createCrudOptions = function ({crudExpose, context}: CreateCrudOptionsProps): CreateCrudOptionsRet {
+  const pageRequest = async (query: UserPageQuery) => {
+    const params = parseQueryParams(context.value)
+    XEUtils.assign(query, params)
+    return await api.GetList(query)
+  }
+  const editRequest = async ({form, row}: EditReq) => {
+    form.id = row.id
+    return await api.UpdateObj(form)
+  }
+  const delRequest = async ({row}: DelReq) => {
+    return await api.DelObj(row.id)
+  }
+  const addRequest = async ({form}: AddReq) => {
+    return await api.AddObj(form)
+  }
+
+  //权限判定
+  const hasPermissions = inject('$hasPermissions')
+
+  return {
+    crudOptions: {
+      table: {
+        height: 800,
+      },
+      container: {
+        fixedHeight: false
+      },
+      actionbar: {
+        show: true,
+        buttons: {
+          add: {
+            show: false
+          },
+          // create: {
+          //   text: '新建广告活动',
+          //   type: 'primary',
+          //   show: true,
+          //   click() {
+          //
+          //   }
+          // }
+        }
+      },
+      search: {
+        show: false
+      },
+      toolbar: {
+        buttons: {
+          search: {
+            show: true
+          },
+          compact: {
+            show: false
+          }
+        }
+      },
+      request: {
+        pageRequest,
+        addRequest,
+        editRequest,
+        delRequest,
+      },
+      rowHandle: {
+        fixed: 'right',
+        width: 80,
+        buttons: {
+          view: {
+            show: false,
+          },
+          edit: {
+            iconRight: 'Edit',
+            type: 'text',
+            text: null
+            // show: hasPermissions('dictionary:Update'),
+          },
+          remove: {
+            show: false
+            // iconRight: 'Delete',
+            // type: 'text',
+            // text: null
+            // show: hasPermissions('dictionary:Delete'),
+          },
+        },
+      },
+      columns: {
+        id: {
+          title: 'ID',
+          column: {
+            show: false
+          },
+          form: {
+            show: false
+          }
+        },
+        Name: {
+          title: '日期',
+          column: {
+            width: 100,
+            align: 'left'
+          }
+        },
+        Spend: {
+          title: '花费',
+          column: {
+            width: 100,
+            align: 'left'
+          },
+        },
+        TotalSales: {
+          title: '销售额',
+        },
+        ACOS: {
+          title: 'ACOS',
+        },
+        CPC: {
+          title: '点击成本',
+        },
+        CPA: {
+          title: '订单成本',
+        },
+        Click: {
+          title: '点击量',
+        },
+        CTR: {
+          title: '点击率',
+        },
+        TotalPurchases: {
+          title: '订单数'
+        },
+        TotalUnitOrdered: {
+          title: '销量',
+        },
+        ...BaseColumn
+      }
+    }
+  }
+}

+ 125 - 0
src/views/adManage/ad-overview/daily/index.vue

@@ -0,0 +1,125 @@
+<template>
+  <div class="overview-tabs">
+    <DateRangePicker v-model="dateRange"></DateRangePicker>
+    <el-select
+        v-model="selectedPortfolios"
+        placeholder="SP"
+        style="width: 200px"
+        collapse-tags
+        collapse-tags-tooltip
+        :max-collapse-tags="3"
+    >
+      <el-option v-for="info of portfolios" :label="info.label" :value="info.value" :disabled="info.disabled"></el-option>
+    </el-select>
+  </div>
+  <fs-page class="fs-page-custom" style="margin-top: -8px">
+    <fs-crud ref="crudRef" v-bind="crudBinding">
+      <template #header-middle>
+        <el-tabs v-model="tabActiveName" class="chart-tabs" type="border-card">
+          <DataTendencyChart
+              v-if="tabActiveName === 'dataTendency'"
+              :query="queryParams"
+              :fetchCard="getCardData"
+              :fetchLine="getLineData"
+              :fetch-line-month="getLineMonthData"
+              :fetch-line-week="getLineWeekData">
+          </DataTendencyChart>
+        </el-tabs>
+      </template>
+      <template #cell_percentTimeInBudget="scope">
+        <el-progress :percentage="scope.row.percentTimeInBudget > 0 ? scope.row.percentTimeInBudget * 100 : 0" />
+      </template>
+      <template #cell_campaignName="scope">
+        <el-link type="primary" :underline="false" @click="jumpGroup(scope.row)">{{ scope.row.campaignName }}</el-link>
+      </template>
+      <template #cell_MissedImpressions="scope">
+        {{ scope.row.MissedImpressionsLower ?? '0' }} ~ {{ scope.row.MissedImpressionsUpper ?? '0' }}
+      </template>
+      <template #cell_MissedClicks="scope"> {{ scope.row.MissedClicksLower ?? '0' }} ~ {{ scope.row.MissedClicksUpper ?? '0' }} </template>
+      <template #cell_MissedSales="scope"> {{ scope.row.MissedSalesLower ?? '0' }} ~ {{ scope.row.MissedSalesUpper ?? '0' }} </template>
+      <template v-for="field of Object.keys(BaseColumn)" #[`cell_${field}`]="scope">
+        <DataCompare
+            :field="field"
+            :value="scope.row[field]"
+            :prev-val="scope.row[`prev${field}`]"
+            :gap-val="scope.row[`gap${field}`]"
+            :date-range="dateRange"
+            :show-compare="showCompare"/>
+      </template>
+    </fs-crud>
+  </fs-page>
+</template>
+
+<script lang="ts" setup>
+import {onMounted, Ref, ref, watch} from 'vue'
+import {FsPage, useFs} from '@fast-crud/fast-crud'
+import {createCrudOptions} from './crud'
+import {useShopInfo} from '/@/stores/shopInfo'
+import {usePublicData} from '/@/stores/publicData'
+import {storeToRefs} from 'pinia'
+import {useRouter} from 'vue-router'
+import DataTendencyChart from '/@/views/adManage/ad-overview/chartComponents/dataTendency.vue'
+import {getCardData, getLineData, getLineMonthData, getLineWeekData} from './api'
+import {BaseColumn} from '/@/views/adManage/utils/commonTabColumn.js'
+import DataCompare from '/@/components/dataCompare/index.vue'
+import DateRangePicker from '/@/components/DateRangePicker/index.vue'
+
+const selectedPortfolios = ref('sp')
+const portfolios = [
+  {
+    value: 'sp/sb/sd',
+    label: 'SP/SB/SD'
+  },
+  {
+    value: 'sp',
+    label: 'SP'
+  },
+  {
+    value:'sb',
+    label:'SB'
+  },
+  {
+    value:'sd',
+    label:'SD'
+  },
+  {
+    value: 'dsp',
+    label: 'DSP',
+    disabled: true
+  }
+]
+const tabActiveName = ref('dataTendency')
+const shopInfo = useShopInfo()
+const publicData = usePublicData()
+const { dateRange } = storeToRefs(publicData)
+const { profile } = storeToRefs(shopInfo)
+const queryParams = ref({
+  dateRange,
+  profileId: profile.value.profileId,
+  campaignType: selectedPortfolios.value
+})
+const { crudBinding, crudRef, crudExpose } = useFs({ createCrudOptions, context: queryParams })
+const router = useRouter()
+const showCompare = ref(false)
+
+
+onMounted(async () => {
+  crudExpose.doRefresh()
+})
+const jumpGroup = (row: any) => {
+  router.push({
+    name: 'CampaignDetail',
+    query: { campaignId: row.campaignId, tagsViewName: row.campaignName },
+  })
+}
+
+watch(queryParams, async () => {
+  crudExpose.doRefresh()
+}, { deep: true })
+</script>
+
+<style lang="scss" scoped>
+.campare-switch {
+  flex: none;
+}
+</style>

+ 88 - 0
src/views/adManage/ad-overview/hourly/api.ts

@@ -0,0 +1,88 @@
+import {request} from '/@/utils/service'
+import {UserPageQuery, AddReq, DelReq, EditReq, InfoReq} from '@fast-crud/fast-crud'
+import XEUtils from 'xe-utils'
+
+export const apiPrefix = '/api/ad_manage/summary/ams/'
+
+export function GetList(query: UserPageQuery) {
+  return request({
+    url: apiPrefix + 'hourly_trend',
+    method: 'get',
+    params: query,
+  })
+}
+
+export function GetObj(id: any) {
+  return request({
+    url: apiPrefix + id + '/',
+    method: 'get',
+  })
+}
+
+export function AddObj(obj: AddReq) {
+  return request({
+    url: apiPrefix,
+    method: 'post',
+    data: obj,
+  })
+}
+
+export function UpdateObj(obj: EditReq) {
+  return request({
+    url: apiPrefix + obj.id + '/',
+    method: 'put',
+    data: obj,
+  })
+}
+
+export function DelObj(id: DelReq) {
+  return request({
+    url: apiPrefix + id + '/',
+    method: 'delete',
+    data: {id},
+  })
+}
+
+export function getCardData(query: UserPageQuery) {
+  return request({
+    url: apiPrefix + 'hourly_total',
+    method: 'GET',
+    params: query,
+  })
+}
+
+export function getLineData(query: UserPageQuery) {
+  query['dateRangeType'] = 'D'
+  return request({
+    url: apiPrefix + 'hourly_trend',
+    method: 'GET',
+    params: query
+  })
+}
+
+export function getLineWeekData(query: UserPageQuery) {
+  query['dateRangeType'] = 'W'
+  return request({
+    url: apiPrefix + 'hourly',
+    method: 'GET',
+    params: query
+  })
+}
+
+export function getLineMonthData(query: UserPageQuery) {
+  query['dateRangeType'] = 'M'
+  return request({
+    url: apiPrefix + 'hourly',
+    method: 'GET',
+    params: query
+  })
+}
+
+export function getMyList(query: UserPageQuery) {
+  query['dateRangeType'] = 'M'
+  return request({
+    url: '/api/ad_manage/summary/ams/hourly',
+    method: 'GET',
+    params: query
+  })
+}

+ 140 - 0
src/views/adManage/ad-overview/hourly/crud.tsx

@@ -0,0 +1,140 @@
+import * as api from './api'
+import {AddReq, CreateCrudOptionsProps, CreateCrudOptionsRet, DelReq, EditReq, UserPageQuery} from '@fast-crud/fast-crud'
+import {inject} from 'vue'
+import {BaseColumn} from '/@/views/adManage/utils/commonTabColumn.js'
+import {parseQueryParams} from '/@/views/adManage/utils/tools.js'
+import XEUtils from 'xe-utils'
+
+export const createCrudOptions = function ({crudExpose, context}: CreateCrudOptionsProps): CreateCrudOptionsRet {
+  const pageRequest = async (query: UserPageQuery) => {
+    const params = parseQueryParams(context.value)
+    XEUtils.assign(query, params)
+    return await api.GetList(query)
+  }
+  const editRequest = async ({form, row}: EditReq) => {
+    form.id = row.id
+    return await api.UpdateObj(form)
+  }
+  const delRequest = async ({row}: DelReq) => {
+    return await api.DelObj(row.id)
+  }
+  const addRequest = async ({form}: AddReq) => {
+    return await api.AddObj(form)
+  }
+
+  //权限判定
+  const hasPermissions = inject('$hasPermissions')
+
+  return {
+    crudOptions: {
+      table: {
+        height: 800
+      },
+      container: {
+        fixedHeight: false
+      },
+      actionbar: {
+        show: true,
+        buttons: {
+          add: {
+            show: false
+          },
+          create: {
+            text: '新建广告活动',
+            type: 'primary',
+            show: false,
+            click() {
+
+            }
+          }
+        }
+      },
+      search: {
+        show: false
+      },
+      toolbar: {
+        buttons: {
+          search: {
+            show: true
+          },
+          compact: {
+            show: false
+          }
+        }
+      },
+      request: {
+        pageRequest,
+        addRequest,
+        editRequest,
+        delRequest,
+      },
+      rowHandle: {
+        fixed: 'right',
+        width: 80,
+        buttons: {
+          view: {
+            show: false,
+          },
+          edit: {
+            iconRight: 'Edit',
+            type: 'text',
+            text: null
+            // show: hasPermissions('dictionary:Update'),
+          },
+          remove: {
+            show: false
+            // iconRight: 'Delete',
+            // type: 'text',
+            // text: null
+            // show: hasPermissions('dictionary:Delete'),
+          },
+        },
+      },
+      columns: {
+        id: {
+          title: 'ID',
+          column: {
+            show: false
+          },
+          form: {
+            show: false
+          }
+        },
+        Spend: {
+          title: '花费',
+        },
+        TotalSales: {
+          title: '销售额',
+          column: {
+            align: 'center'
+          }
+        },
+        ACOS: {
+          title: 'ACOS',
+        },
+        CPC: {
+          title: '点击成本',
+        },
+        CPA: {
+          title: '订单成本',
+        },
+        Click: {
+          title: '点击量',
+        },
+        CTR: {
+          title: '点击率'
+        },
+        TotalPurchases: {
+          title: '订单数'
+        },
+        TotalUnitOrdered: {
+          title: '销量',
+          form: {
+            show: false
+          }
+        },
+        ...BaseColumn
+      }
+    }
+  }
+}

+ 129 - 0
src/views/adManage/ad-overview/hourly/index.vue

@@ -0,0 +1,129 @@
+<template>
+  <div class="overview-tabs">
+    <DateRangePicker v-model="dateRange"></DateRangePicker>
+    <el-select
+        v-model="selectedPortfolios"
+        placeholder="SP"
+        style="width: 200px"
+        collapse-tags
+        collapse-tags-tooltip
+        :max-collapse-tags="3"
+    >
+      <el-option v-for="info of portfolios" :label="info.label" :value="info.value" :disabled="info.disabled"></el-option>
+    </el-select>
+  </div>
+	<fs-page class="fs-page-custom" style="margin-top: -8px">
+		<fs-crud ref="crudRef" v-bind="crudBinding">
+			<template #header-middle>
+				<el-tabs v-model="tabActiveName" class="chart-tabs" type="border-card">
+						<DataTendencyChart
+							v-if="tabActiveName === 'dataTendency'"
+							:query="queryParams"
+							:fetchCard="getCardData"
+							:fetchLine="getLineData"
+							:fetch-line-month="getLineMonthData"
+							:fetch-line-week="getLineWeekData">
+						</DataTendencyChart>
+				</el-tabs>
+			</template>
+			<template #cell_percentTimeInBudget="scope">
+				<el-progress :percentage="scope.row.percentTimeInBudget > 0 ? scope.row.percentTimeInBudget * 100 : 0" />
+			</template>
+			<template #cell_campaignName="scope">
+				<el-link type="primary" :underline="false" @click="jumpGroup(scope.row)">{{ scope.row.campaignName }}</el-link>
+			</template>
+			<template #cell_MissedImpressions="scope">
+				{{ scope.row.MissedImpressionsLower ?? '0' }} ~ {{ scope.row.MissedImpressionsUpper ?? '0' }}
+			</template>
+			<template #cell_MissedClicks="scope"> {{ scope.row.MissedClicksLower ?? '0' }} ~ {{ scope.row.MissedClicksUpper ?? '0' }} </template>
+			<template #cell_MissedSales="scope"> {{ scope.row.MissedSalesLower ?? '0' }} ~ {{ scope.row.MissedSalesUpper ?? '0' }} </template>
+			<template v-for="field of Object.keys(BaseColumn)" #[`cell_${field}`]="scope">
+				<DataCompare
+          :field="field"
+          :value="scope.row[field]"
+          :prev-val="scope.row[`prev${field}`]"
+          :gap-val="scope.row[`gap${field}`]"
+          :date-range="dateRange"
+          :show-compare="showCompare"/>
+			</template>
+		</fs-crud>
+	</fs-page>
+</template>
+
+<script lang="ts" setup>
+import {onMounted, ref, watch} from 'vue'
+import {FsPage, useFs} from '@fast-crud/fast-crud'
+import {createCrudOptions} from './crud'
+import {useShopInfo} from '/@/stores/shopInfo'
+import {usePublicData} from '/@/stores/publicData'
+import {storeToRefs} from 'pinia'
+import {useRouter} from 'vue-router'
+import DataTendencyChart from '/@/views/adManage/ad-overview/chartComponents/dataTendency.vue'
+import {getCardData, getLineData, getLineMonthData, getLineWeekData} from './api'
+import {BaseColumn} from '/@/views/adManage/utils/commonTabColumn.js'
+import DataCompare from '/@/components/dataCompare/index.vue'
+import DateRangePicker from '/@/components/DateRangePicker/index.vue'
+
+const selectedPortfolios = ref('sp')
+const portfolios = [
+  {
+    value: 'sp/sb/sd',
+    label: 'SP/SB/SD'
+  },
+  {
+    value: 'sp',
+    label: 'SP'
+  },
+  {
+    value:'sb',
+    label:'SB'
+  },
+  {
+    value:'sd',
+    label:'SD'
+  },
+  {
+    value: 'dsp',
+    label: 'DSP',
+    disabled: true
+  }
+]
+const tabActiveName = ref('dataTendency')
+const shopInfo = useShopInfo()
+const publicData = usePublicData()
+const { dateRange } = storeToRefs(publicData)
+const { profile } = storeToRefs(shopInfo)
+const queryParams = ref({
+  dateRange,
+  // startDate: '2023-11-01',
+  // endDate: '2023-11-15',
+  profileId: profile.value.profile_id,
+  campaignType: selectedPortfolios.value
+})
+
+const { crudBinding, crudRef, crudExpose } = useFs({ createCrudOptions, context: queryParams })
+const router = useRouter()
+const showCompare = ref(false)
+
+const jumpGroup = (row: any) => {
+  router.push({
+    name: 'CampaignDetail',
+    query: { campaignId: row.campaignId, tagsViewName: row.campaignName },
+  })
+}
+
+onMounted(async () => {
+	crudExpose.doRefresh()
+})
+
+watch(queryParams, async () => {
+  crudExpose.doRefresh()
+}, { deep: true })
+
+</script>
+
+<style lang="scss" scoped>
+.campare-switch {
+	flex: none;
+}
+</style>

+ 36 - 803
src/views/adManage/ad-overview/index.vue

@@ -1,820 +1,53 @@
 <template>
   <div class="asj-container">
-    <div class="public-search">
-      <DateRangePicker v-model="dateRange"></DateRangePicker>
-    </div>
-    <div class="container-main">
-      <!-- 卡片内容 不要删除这个类 -->
-      <div class="home-container" style="margin-top: 2px">
-        <el-row :gutter="15" class="home-card-one mb15">
-          <el-col
-              :xs="24"
-              :sm="12"
-              :md="12"
-              :lg="6"
-              :xl="6"
-              v-for="(v, k) in state.homeOne"
-              :key="k"
-              :class="{ 'home-media home-media-lg': k > 1, 'home-media-sm': k === 1 }"
-          >
-            <div class="home-card-item flex">
-              <div class="flex-margin flex w100" :class="` home-one-animation${k}`">
-                <div class="flex-auto" style="margin-top: -10px">
-                  <div class="mt10">{{ v.cardTitle }}</div>
-                  <div class="font30">{{ v.num1 }}</div>
-                  <div style="display: inline-block; margin-right: 10px">
-                    {{ v.compareNum }}
-                  </div>
-                  <el-icon :style="{ color: v.num2.includes('-') ? '#59b939' : '#e36f53' }" style="display: inline-block; padding-top: 2px">
-                    <template v-if="v.num2.includes('-')" style="line-height: 25px;">
-                      <Top/>
-                    </template>
-                    <template v-else>
-                      <Bottom/>
-                    </template>
-                  </el-icon>
-                  <span class="ml5 font16" :style="{ color: v.num2.includes('-') ? '#59b939' : '#e36f53' }">{{ v.num2 }}%</span>
-                </div>
-                <div class="home-card-item-icon flex" :style="{ background: `var(${v.color2})` }">
-                  <i class="flex-margin font32" :class="v.num4" :style="{ color: `var(${v.color3})` }"></i>
-                </div>
-              </div>
-            </div>
-          </el-col>
-        </el-row>
+    <div class="overview-top">
+      <div v-for="tab of tabs" :key="tab.name" :class="['asj-tab', { active: tabActiveName === tab.name }]" @click="tabActiveName = tab.name">
+        {{ tab.label }}
       </div>
-      <!-- 折线图 -->
-      <el-card v-loading="loading" style="margin-top: -5px;">
-        <div style="height: 350px;" ref="chartRefOne"></div>
-      </el-card>
-      <el-row :gutter="5" style="margin-top: 10px">
-        <el-col :span="12">
-          <el-card v-loading="loading">
-            <div style="height: 350px;" ref="chartRefAcos"></div>
-          </el-card>
-        </el-col>
-        <el-col :span="12">
-          <el-card v-loading="loading">
-            <div style="height: 350px;" ref="chartRefCPCandCTR"></div>
-          </el-card>
-        </el-col>
-      </el-row>
-      <el-row :gutter="5" style="margin-top: 10px">
-        <el-col :span="12">
-          <el-card v-loading="loading">
-            <div style="height: 350px;" ref="chartRefTotalPurchases"></div>
-          </el-card>
-        </el-col>
-        <el-col :span="12">
-          <el-card v-loading="loading">
-            <div style="height: 350px;" ref="chartRefImpandCli"></div>
-          </el-card>
-        </el-col>
-      </el-row>
     </div>
-    <!--<el-button @click="changeCardData">按钮</el-button>-->
+    <component :is="tabsComponents[tabActiveName]"></component>
   </div>
-
 </template>
 
 <script lang="ts" setup>
-import {onBeforeMount, onMounted, provide, reactive, ref, watch} from 'vue'
-import * as echarts from 'echarts'
-import {useShopInfo} from '/@/stores/shopInfo'
+import {onBeforeMount, provide, Ref, ref} from 'vue'
 import {usePublicData} from '/@/stores/publicData'
 import {storeToRefs} from 'pinia'
-import DateRangePicker from '/@/components/DateRangePicker/index.vue'
-import {createCrudOptions} from '/@/views/adManage/sp/targets/crud'
-import {useFs} from '@fast-crud/fast-crud'
-import {getTotalData} from '/@/views/adManage/ad-overview/api'
-
+import {GetAllPortfolios} from '/@/views/adManage/portfolios/api'
+import Total from './total/index.vue'
+import Hour from '/@/views/adManage/ad-overview/hourly/index.vue'
+import Daily from '/@/views/adManage/ad-overview/daily/index.vue'
+import Weekly from '/@/views/adManage/ad-overview/weekly/index.vue'
+import Monthly from '/@/views/adManage/ad-overview/monthly/index.vue'
+
+// const shopInfo = useShopInfo()
 const publicData = usePublicData()
-const {dateRange} = storeToRefs(publicData)
-const shopInfo = useShopInfo()
-const { profile } = storeToRefs(shopInfo)
-const tabActiveName = ref('Campaigns')
-const queryParams = ref({
-  profileId: profile.value.profile_id,
-  dateRange
-})
-const {crudBinding, crudRef, crudExpose} = useFs({createCrudOptions, context: queryParams})
-
-
-// 发送请求获取数据
-let totalData = ref()
-async function setTotalData() {
-  try {
-    const data = await getTotalData({ startDate: dateRange.value[0], endDate: dateRange.value[1], profileId: queryParams.value.profileId })
-    totalData.value = data
-    console.log('totalData', data)
-  } catch (error) {
-    console.log('获取数据失败', error)
-  }
-}
-
-onMounted(async () => {
-  // crudExpose.doRefresh()
-  await setTotalData()
-})
-
-watch(queryParams, async() => {
-  try {
-    loading.value = true
-    await setChartOptions()
-    console.log('queryParams.value', queryParams.value)
-  } catch (error) {
-    console.log(error)
-  }
-
-}, {deep: true})
-
-
-// 卡片相关功能
-const state = reactive({
-  homeOne: [
-    {
-      num1: '125,12',
-      compareNum: '140,23',
-      num2: '-12.32',
-      cardTitle: '订单统计信息',
-      num4: 'fa fa-meetup',
-      // color1: '#FF6462',
-      color2: '--next-color-primary-lighter',
-      color3: '--el-color-primary',
-    },
-    {
-      num1: '653,33',
-      compareNum: '1000,23',
-      num2: '-42.32',
-      cardTitle: '月度计划信息',
-      num4: 'iconfont icon-ditu',
-      color2: '--next-color-success-lighter',
-      color3: '--el-color-success',
-    },
-    {
-      num1: '125,65',
-      compareNum: '140,23',
-      num2: '17.32',
-      cardTitle: '年度计划信息',
-      num4: 'iconfont icon-zaosheng',
-      color2: '--next-color-warning-lighter',
-      color3: '--el-color-warning',
-    },
-    {
-      num1: '520,43',
-      compareNum: '590,23',
-      num2: '10.23',
-      cardTitle: '访问统计信息',
-      num4: 'fa fa-github-alt',
-      color2: '--next-color-danger-lighter',
-      color3: '--el-color-danger',
-    },
-  ],
-
-  myCharts: [],
-  charts: {
-    theme: '',
-    bgColor: '',
-    color: '#303133',
-  },
-})
-
-let cardData = reactive({
-  cardTitle: '花费',
-  cardMiddle: 123,
-  cardBottom: 1123,
-  cardBottomRight: 12
-})
-
-// 接收一个newData对象,更新cardData
-function updateCardData(newData) {
-  for (const key in newData) {
-    if (cardData.hasOwnProperty(key)) {
-      cardData[key] = newData[key]
-    }
-  }
-}
-
-function changeCardData() {
-  updateCardData({
-    cardTitle: '新销量',
-    cardMiddle: 50,
-    cardBottom: 60,
-    cardBottomRight: 70
-  })
-}
-
-
-// 折线图相关功能
-const loading = ref(true)
-
-const chartRefOne = ref()
-let chartObjOne
-const option = {
-  title: {text: '花费 & 销售额'},
-  tooltip: {
-    trigger: 'axis',
-    axisPointer: {
-      label: {
-        backgroundColor: '#6a7985'
-      }
-    }
-  },
-  legend: {
-    selected: {},  // 控制显隐
-    data: ['花费', '销售额'],
-    show: true
-  },
-  grid: {
-    top: 50, right: 65, bottom: 30, left: 65,
-  },
-  xAxis: {
-    type: 'category',
-    // boundaryGap: false,
-    data: ['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun']
-  },
-  yAxis: [
-    {
-      type: 'value',
-      axisLine: {
-        show: true,
-        lineStyle: {
-          color: '#3a83f7' // 第一个 Y 轴的颜色
-        }
-      }
-    },
-    {
-      type: 'value',
-      splitLine: {
-        show: false
-      },
-      axisLine: {
-        show: true,
-        lineStyle: {
-          color: '#f19a37' // 第二个 Y 轴的颜色
-        }
-      }
-    }
-  ],
-  series: [
-    {
-      name: '花费',
-      yAxisIndex: 0,
-      data: [820, 932, 901, 934, 1290, 1330, 1320],
-      type: 'line',
-      smooth: true,
-      itemStyle: {
-        color: '#3a83f7',
-      },
-      areaStyle: {
-        color: {
-          type: 'linear',
-          x: 0,
-          y: 0,
-          x2: 0,
-          y2: 1,
-          colorStops: [{
-            offset: 0, color: 'rgba(58, 131, 247, 0.5)' // 顶部不透明
-          }, {
-            offset: 0.2, color: 'rgba(58, 131, 247, 0.2)' // 中间更透明
-          }, {
-            offset: 1, color: 'rgba(58, 131, 247, 0)' // 底部完全透明
-          }],
-          global: false // 缺省为 false
-        }
-      }
-    },
-    {
-      name: '销售额',
-      yAxisIndex: 1,
-      data: [220, 182, 191, 234, 290, 330, 310],
-      type: 'line',
-      smooth: true,
-      lineStyle: {
-        type: 'dashed'
-      },
-      itemStyle: {
-        color: '#f19a37',
-      },
-      areaStyle: {
-        color: {
-          type: 'linear',
-          x: 0,
-          y: 0,
-          x2: 0,
-          y2: 1,
-          colorStops: [{
-            offset: 0, color: 'rgba(241, 154, 55, 0.5)' // 顶部半透明
-          }, {
-            offset: 0.2, color: 'rgba(241, 154, 55, 0.2)' // 中间更透明
-          }, {
-            offset: 1, color: 'rgba(241, 154, 55, 0)' // 底部完全透明
-          }],
-          global: false // 缺省为 false
-        }
-      }
-    },
-  ]
-}
-
-const chartRefAcos = ref()
-let chartObjAcos
-const option2 = {
-  title: {text: 'Acos'},
-  tooltip: {
-    trigger: 'axis',
-    axisPointer: {
-      label: {
-        backgroundColor: '#6a7985'
-      }
-    }
-  },
-  legend: {
-    selected: {},  // 控制显隐
-    data: ['Acos'],
-    show: true
-  },
-  grid: {
-    top: 50, right: 65, bottom: 30, left: 65,
-  },
-  xAxis: {
-    type: 'category',
-    // boundaryGap: false,
-    data: ['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun']
-  },
-  yAxis: [
-    {
-      type: 'value',
-      axisLine: {
-        show: true,
-        lineStyle: {
-          color: '#3a83f7'
-        }
-      }
-    }
-  ],
-  series: [
-    {
-      name: 'Acos',
-      yAxisIndex: 0,
-      data: [820, 932, 901, 934, 1290, 1330, 1320],
-      type: 'line',
-      smooth: true,
-      lineStyle: {
-        type: 'dashed'
-      },
-      legendHoverLink: false,
-      itemStyle: {
-        color: '#3a83f7',
-      },
-      areaStyle: {
-        color: {
-          type: 'linear',
-          x: 0,
-          y: 0,
-          x2: 0,
-          y2: 1,
-          colorStops: [{
-            offset: 0, color: 'rgba(58, 131, 247, 0.5)' // 顶部不透明
-          }, {
-            offset: 0.2, color: 'rgba(58, 131, 247, 0.2)' // 中间更透明
-          }, {
-            offset: 1, color: 'rgba(58, 131, 247, 0)' // 底部完全透明
-          }],
-          global: false // 缺省为 false
-        }
-      }
-    },
-  ]
-}
-
-const chartRefCPCandCTR = ref()
-let chartObjCPCandCTR
-const option3 = {
-  title: {text: '点击成本 & 点击率'},
-  tooltip: {
-    trigger: 'axis',
-    axisPointer: {
-      label: {
-        backgroundColor: '#6a7985'
-      }
-    }
-  },
-  legend: {
-    selected: {},  // 控制显隐
-    data: ['点击成本', '点击率'],
-    show: true
-  },
-  grid: {
-    top: 50, right: 65, bottom: 30, left: 65,
-  },
-  xAxis: {
-    type: 'category',
-    // boundaryGap: false,
-    data: ['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun']
-  },
-  yAxis: [
-    {
-      type: 'value',
-      axisLine: {
-        show: true,
-        lineStyle: {
-          color: '#3a83f7' // 第一个 Y 轴的颜色
-        }
-      }
-    },
-    {
-      type: 'value',
-      splitLine: {
-        show: false
-      },
-      axisLine: {
-        show: true,
-        lineStyle: {
-          color: '#f19a37' // 第二个 Y 轴的颜色
-        }
-      }
-    }
-  ],
-  series: [
-    {
-      name: '点击成本',
-      yAxisIndex: 0,
-      data: [820, 932, 901, 934, 1290, 1330, 1320],
-      type: 'line',
-      smooth: true,
-      itemStyle: {
-        color: '#3a83f7',
-      },
-      areaStyle: {
-        color: {
-          type: 'linear',
-          x: 0,
-          y: 0,
-          x2: 0,
-          y2: 1,
-          colorStops: [{
-            offset: 0, color: 'rgba(58, 131, 247, 0.5)' // 顶部不透明
-          }, {
-            offset: 0.2, color: 'rgba(58, 131, 247, 0.2)' // 中间更透明
-          }, {
-            offset: 1, color: 'rgba(58, 131, 247, 0)' // 底部完全透明
-          }],
-          global: false // 缺省为 false
-        }
-      }
-    },
-    {
-      name: '点击率',
-      yAxisIndex: 1,
-      data: [220, 182, 191, 234, 290, 330, 310],
-      type: 'line',
-      smooth: true,
-      itemStyle: {
-        color: '#f19a37',
-      },
-      areaStyle: {
-        color: {
-          type: 'linear',
-          x: 0,
-          y: 0,
-          x2: 0,
-          y2: 1,
-          colorStops: [{
-            offset: 0, color: 'rgba(241, 154, 55, 0.5)' // 顶部半透明
-          }, {
-            offset: 0.2, color: 'rgba(241, 154, 55, 0.2)' // 中间更透明
-          }, {
-            offset: 1, color: 'rgba(241, 154, 55, 0)' // 底部完全透明
-          }],
-          global: false // 缺省为 false
-        }
-      }
-    },
-  ]
-}
-
-const chartRefTotalPurchases = ref()
-let chartObjTotalPurchases
-const option4 = {
-  title: {text: '订单数'},
-  tooltip: {
-    trigger: 'axis',
-    axisPointer: {
-      label: {
-        backgroundColor: '#6a7985'
-      }
-    }
-  },
-  legend: {
-    selected: {},  // 控制显隐
-    data: ['订单数'],
-    show: true
-  },
-  grid: {
-    top: 50, right: 65, bottom: 30, left: 65,
-  },
-  xAxis: {
-    type: 'category',
-    // boundaryGap: false,
-    data: ['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun']
-  },
-  yAxis: [
-    {
-      type: 'value',
-      axisLine: {
-        show: true,
-        lineStyle: {
-          color: '#3a83f7'
-        }
-      }
-    }
-  ],
-  series: [
-    {
-      name: '订单数',
-      yAxisIndex: 0,
-      data: [820, 932, 901, 934, 1290, 1330, 1320],
-      type: 'line',
-      smooth: true,
-      lineStyle: {
-        type: 'dashed'
-      },
-      legendHoverLink: false,
-      itemStyle: {
-        color: '#3a83f7',
-      },
-      areaStyle: {
-        color: {
-          type: 'linear',
-          x: 0,
-          y: 0,
-          x2: 0,
-          y2: 1,
-          colorStops: [{
-            offset: 0, color: 'rgba(58, 131, 247, 0.5)' // 顶部不透明
-          }, {
-            offset: 0.2, color: 'rgba(58, 131, 247, 0.2)' // 中间更透明
-          }, {
-            offset: 1, color: 'rgba(58, 131, 247, 0)' // 底部完全透明
-          }],
-          global: false // 缺省为 false
-        }
-      }
-    },
-  ]
-}
-
-const chartRefImpandCli = ref()
-let chartObjImpandCli
-const option5 = {
-  title: {text: '曝光量 & 点击量'},
-  tooltip: {
-    trigger: 'axis',
-    axisPointer: {
-      label: {
-        backgroundColor: '#6a7985'
-      }
-    }
-  },
-  legend: {
-    selected: {},  // 控制显隐
-    data: ['曝光量', '点击量'],
-    show: true
-  },
-  grid: {
-    top: 50, right: 65, bottom: 30, left: 65,
-  },
-  xAxis: {
-    type: 'category',
-    // boundaryGap: false,
-    data: ['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun']
-  },
-  yAxis: [
-    {
-      type: 'value',
-      axisLine: {
-        show: true,
-        lineStyle: {
-          color: '#3a83f7' // 第一个 Y 轴的颜色
-        }
-      }
-    },
-    {
-      type: 'value',
-      splitLine: {
-        show: false
-      },
-      axisLine: {
-        show: true,
-        lineStyle: {
-          color: '#f19a37' // 第二个 Y 轴的颜色
-        }
-      }
-    }
-  ],
-  series: [
-    {
-      name: '曝光量',
-      yAxisIndex: 0,
-      data: [820, 932, 901, 934, 1290, 1330, 1320],
-      type: 'line',
-      smooth: true,
-      itemStyle: {
-        color: '#3a83f7',
-      },
-      areaStyle: {
-        color: {
-          type: 'linear',
-          x: 0,
-          y: 0,
-          x2: 0,
-          y2: 1,
-          colorStops: [{
-            offset: 0, color: 'rgba(58, 131, 247, 0.5)' // 顶部不透明
-          }, {
-            offset: 0.2, color: 'rgba(58, 131, 247, 0.2)' // 中间更透明
-          }, {
-            offset: 1, color: 'rgba(58, 131, 247, 0)' // 底部完全透明
-          }],
-          global: false // 缺省为 false
-        }
-      }
-    },
-    {
-      name: '点击量',
-      yAxisIndex: 1,
-      data: [220, 182, 191, 234, 290, 330, 310],
-      type: 'line',
-      smooth: true,
-      itemStyle: {
-        color: '#f19a37',
-      },
-      areaStyle: {
-        color: {
-          type: 'linear',
-          x: 0,
-          y: 0,
-          x2: 0,
-          y2: 1,
-          colorStops: [{
-            offset: 0, color: 'rgba(241, 154, 55, 0.5)' // 顶部半透明
-          }, {
-            offset: 0.2, color: 'rgba(241, 154, 55, 0.2)' // 中间更透明
-          }, {
-            offset: 1, color: 'rgba(241, 154, 55, 0)' // 底部完全透明
-          }],
-          global: false // 缺省为 false
-        }
-      }
-    },
-  ]
-}
-
-function initLine() {
-  chartObjOne = echarts.init(chartRefOne.value)
-  chartObjAcos = echarts.init(chartRefAcos.value)
-  chartObjCPCandCTR = echarts.init(chartRefCPCandCTR.value)
-  chartObjTotalPurchases = echarts.init(chartRefTotalPurchases.value)
-  chartObjImpandCli = echarts.init(chartRefImpandCli.value)
-}
-
-function setChartOptions() {
-  chartObjOne.setOption(option)
-  chartObjAcos.setOption(option2)
-  chartObjCPCandCTR.setOption(option3)
-  chartObjTotalPurchases.setOption(option4)
-  chartObjImpandCli.setOption(option5)
-  loading.value = false
-}
-
-function resizeChart() {
-  chartObjOne.resize()
-  chartObjAcos.resize()
-  chartObjCPCandCTR.resize()
-  chartObjTotalPurchases.resize()
-  chartObjImpandCli.resize()
-}
-
-onMounted(async () => {
-  await initLine()
-  await setChartOptions()
-  window.addEventListener('resize', resizeChart)  // 监听窗口大小变化,调整图表大小
-  setTimeout(() => {
-    resizeChart()
-  }, 0)
-})
+const selectedPortfolios: Ref<string[]> = ref([])
+const portfolios: Ref<Portfolio[]> = ref([])
+const { dateRange } = storeToRefs(publicData)
+const tabActiveName = ref('Total')
+const tabs = [
+  { label: '总览', name: 'Total' },
+  { label: '按小时', name: 'Hour' },
+  { label: '按日', name: 'Daily' },
+  { label: '按周', name: 'Weekly' },
+  { label: '按月', name: 'Monthly' },
+]
+const tabsComponents: any = {
+  Total,
+  Hour,
+  Daily,
+  Weekly,
+  Monthly,
+}
+
+provide('dateRange', dateRange)
 
 onBeforeMount(async () => {
-
+  const resp: APIResponseData = await GetAllPortfolios()
+  portfolios.value = resp.data
 })
 
 </script>
 
-<style scoped lang="scss">
-$homeNavLengh: 8;
-.home-container {
-  overflow: hidden;
-  margin-top: 10px;
-
-  .home-card-one,
-  .home-card-two,
-  .home-card-three {
-    .home-card-item {
-      width: 100%;
-      height: 130px;
-      border-radius: 4px;
-      transition: all ease 0.3s;
-      padding: 20px;
-      overflow: hidden;
-      background: #ffffff;
-      box-shadow: var(--el-box-shadow-light);
-      color: var(--el-text-color-primary);
-      border: 1px solid var(--next-border-color-light);
-
-      &:hover {
-        box-shadow: 0 2px 12px var(--next-color-dark-hover);
-        transition: all ease 0.3s;
-      }
-
-      &-icon {
-        width: 70px;
-        height: 70px;
-        border-radius: 100%;
-        flex-shrink: 1;
-
-        i {
-          color: var(--el-text-color-placeholder);
-        }
-      }
-
-      &-title {
-        font-size: 15px;
-        font-weight: bold;
-        height: 30px;
-      }
-    }
-  }
-
-  .home-card-one {
-    @for $i from 0 through 3 {
-      .home-one-animation#{$i} {
-        opacity: 0;
-        animation-name: error-num;
-        animation-duration: 0.5s;
-        animation-fill-mode: forwards;
-        animation-delay: calc($i/10) + s;
-      }
-    }
-  }
-
-  .home-card-two,
-  .home-card-three {
-    .home-card-item {
-      height: 400px;
-      width: 100%;
-      overflow: hidden;
-
-      .home-monitor {
-        height: 100%;
-
-        .flex-warp-item {
-          width: 25%;
-          height: 111px;
-          display: flex;
-
-          .flex-warp-item-box {
-            margin: auto;
-            text-align: center;
-            color: var(--el-text-color-primary);
-            display: flex;
-            border-radius: 5px;
-            background: var(--next-bg-color);
-            cursor: pointer;
-            transition: all 0.3s ease;
-
-            &:hover {
-              background: var(--el-color-primary-light-9);
-              transition: all 0.3s ease;
-            }
-          }
-
-          @for $i from 0 through $homeNavLengh {
-            .home-animation#{$i} {
-              opacity: 0;
-              animation-name: error-num;
-              animation-duration: 0.5s;
-              animation-fill-mode: forwards;
-              animation-delay: calc($i/10) + s;
-            }
-          }
-        }
-      }
-    }
-  }
-}
-
-.down {
-  margin-top: 10px;
-}
-</style>
+<style scoped></style>

+ 80 - 0
src/views/adManage/ad-overview/monthly/api.ts

@@ -0,0 +1,80 @@
+import {request} from '/@/utils/service'
+import {UserPageQuery, AddReq, DelReq, EditReq, InfoReq} from '@fast-crud/fast-crud'
+import XEUtils from 'xe-utils'
+
+export const apiPrefix = '/api/ad_manage/summary/report/trend/'
+
+export function GetList(query: UserPageQuery) {
+  return request({
+    url: apiPrefix + 'monthly',
+    method: 'get',
+    params: query,
+  })
+}
+
+export function GetObj(id: any) {
+  return request({
+    url: apiPrefix + id + '/',
+    method: 'get',
+  })
+}
+
+export function AddObj(obj: AddReq) {
+  return request({
+    url: apiPrefix,
+    method: 'post',
+    data: obj,
+  })
+}
+
+export function UpdateObj(obj: EditReq) {
+  return request({
+    url: apiPrefix + obj.id + '/',
+    method: 'put',
+    data: obj,
+  })
+}
+
+export function DelObj(id: DelReq) {
+  return request({
+    url: apiPrefix + id + '/',
+    method: 'delete',
+    data: {id},
+  })
+}
+
+export function getCardData(query: UserPageQuery) {
+  return request({
+    url: '/api/ad_manage/summary/report/total',
+    method: 'GET',
+    params: query,
+  })
+}
+
+export function getLineData(query: UserPageQuery) {
+  query['dateRangeType'] = 'D'
+  return request({
+    url: apiPrefix + 'monthly',
+    method: 'GET',
+    params: query
+  })
+}
+
+export function getLineWeekData(query: UserPageQuery) {
+  query['dateRangeType'] = 'W'
+  return request({
+    url: apiPrefix + 'hourly',
+    method: 'GET',
+    params: query
+  })
+}
+
+export function getLineMonthData(query: UserPageQuery) {
+  query['dateRangeType'] = 'M'
+  return request({
+    url: apiPrefix + 'hourly',
+    method: 'GET',
+    params: query
+  })
+}
+

+ 145 - 0
src/views/adManage/ad-overview/monthly/crud.tsx

@@ -0,0 +1,145 @@
+import * as api from './api'
+import {AddReq, CreateCrudOptionsProps, CreateCrudOptionsRet, DelReq, EditReq, UserPageQuery} from '@fast-crud/fast-crud'
+import {inject} from 'vue'
+import {BaseColumn} from '/@/views/adManage/utils/commonTabColumn.js'
+import {parseQueryParams} from '/@/views/adManage/utils/tools.js'
+import XEUtils from 'xe-utils'
+
+export const createCrudOptions = function ({crudExpose, context}: CreateCrudOptionsProps): CreateCrudOptionsRet {
+  const pageRequest = async (query: UserPageQuery) => {
+    const params = parseQueryParams(context.value)
+    XEUtils.assign(query, params)
+    return await api.GetList(query)
+  }
+  const editRequest = async ({form, row}: EditReq) => {
+    form.id = row.id
+    return await api.UpdateObj(form)
+  }
+  const delRequest = async ({row}: DelReq) => {
+    return await api.DelObj(row.id)
+  }
+  const addRequest = async ({form}: AddReq) => {
+    return await api.AddObj(form)
+  }
+
+  //权限判定
+  const hasPermissions = inject('$hasPermissions')
+
+  return {
+    crudOptions: {
+      table: {
+        height: 800,
+      },
+      container: {
+        fixedHeight: false
+      },
+      actionbar: {
+        show: true,
+        buttons: {
+          add: {
+            show: false
+          },
+          // create: {
+          //   text: '新建广告活动',
+          //   type: 'primary',
+          //   show: true,
+          //   click() {
+          //
+          //   }
+          // }
+        }
+      },
+      search: {
+        show: false
+      },
+      toolbar: {
+        buttons: {
+          search: {
+            show: true
+          },
+          compact: {
+            show: false
+          }
+        }
+      },
+      request: {
+        pageRequest,
+        addRequest,
+        editRequest,
+        delRequest,
+      },
+      rowHandle: {
+        fixed: 'right',
+        width: 80,
+        buttons: {
+          view: {
+            show: false,
+          },
+          edit: {
+            iconRight: 'Edit',
+            type: 'text',
+            text: null
+            // show: hasPermissions('dictionary:Update'),
+          },
+          remove: {
+            show: false
+            // iconRight: 'Delete',
+            // type: 'text',
+            // text: null
+            // show: hasPermissions('dictionary:Delete'),
+          },
+        },
+      },
+      columns: {
+        id: {
+          title: 'ID',
+          column: {
+            show: false
+          },
+          form: {
+            show: false
+          }
+        },
+        Name: {
+          title: '日期',
+          column: {
+            width: 100,
+            align: 'left'
+          }
+        },
+        Spend: {
+          title: '花费',
+          column: {
+            width: 100,
+            align: 'left'
+          },
+        },
+        TotalSales: {
+          title: '销售额',
+        },
+        ACOS: {
+          title: 'ACOS',
+        },
+        CPC: {
+          title: '点击成本',
+        },
+        CPA: {
+          title: '订单成本',
+        },
+        Click: {
+          title: '点击量',
+        },
+        CTR: {
+          title: '点击率',
+        },
+        TotalPurchases: {
+          title: '订单数'
+        },
+        TotalUnitOrdered: {
+          title: '销量',
+        },
+        ...BaseColumn
+      }
+    }
+  }
+}

+ 125 - 0
src/views/adManage/ad-overview/monthly/index.vue

@@ -0,0 +1,125 @@
+<template>
+  <div class="overview-tabs">
+    <DateRangePicker v-model="dateRange"></DateRangePicker>
+    <el-select
+        v-model="selectedPortfolios"
+        placeholder="SP"
+        style="width: 200px"
+        collapse-tags
+        collapse-tags-tooltip
+        :max-collapse-tags="3"
+    >
+      <el-option v-for="info of portfolios" :label="info.label" :value="info.value" :disabled="info.disabled"></el-option>
+    </el-select>
+  </div>
+  <fs-page class="fs-page-custom" style="margin-top: -8px">
+    <fs-crud ref="crudRef" v-bind="crudBinding">
+      <template #header-middle>
+        <el-tabs v-model="tabActiveName" class="chart-tabs" type="border-card">
+          <DataTendencyChart
+              v-if="tabActiveName === 'dataTendency'"
+              :query="queryParams"
+              :fetchCard="getCardData"
+              :fetchLine="getLineData"
+              :fetch-line-month="getLineMonthData"
+              :fetch-line-week="getLineWeekData">
+          </DataTendencyChart>
+        </el-tabs>
+      </template>
+      <template #cell_percentTimeInBudget="scope">
+        <el-progress :percentage="scope.row.percentTimeInBudget > 0 ? scope.row.percentTimeInBudget * 100 : 0" />
+      </template>
+      <template #cell_campaignName="scope">
+        <el-link type="primary" :underline="false" @click="jumpGroup(scope.row)">{{ scope.row.campaignName }}</el-link>
+      </template>
+      <template #cell_MissedImpressions="scope">
+        {{ scope.row.MissedImpressionsLower ?? '0' }} ~ {{ scope.row.MissedImpressionsUpper ?? '0' }}
+      </template>
+      <template #cell_MissedClicks="scope"> {{ scope.row.MissedClicksLower ?? '0' }} ~ {{ scope.row.MissedClicksUpper ?? '0' }} </template>
+      <template #cell_MissedSales="scope"> {{ scope.row.MissedSalesLower ?? '0' }} ~ {{ scope.row.MissedSalesUpper ?? '0' }} </template>
+      <template v-for="field of Object.keys(BaseColumn)" #[`cell_${field}`]="scope">
+        <DataCompare
+            :field="field"
+            :value="scope.row[field]"
+            :prev-val="scope.row[`prev${field}`]"
+            :gap-val="scope.row[`gap${field}`]"
+            :date-range="dateRange"
+            :show-compare="showCompare"/>
+      </template>
+    </fs-crud>
+  </fs-page>
+</template>
+
+<script lang="ts" setup>
+import {onMounted, Ref, ref, watch} from 'vue'
+import {FsPage, useFs} from '@fast-crud/fast-crud'
+import {createCrudOptions} from './crud'
+import {useShopInfo} from '/@/stores/shopInfo'
+import {usePublicData} from '/@/stores/publicData'
+import {storeToRefs} from 'pinia'
+import {useRouter} from 'vue-router'
+import DataTendencyChart from '/@/views/adManage/ad-overview/chartComponents/dataTendency.vue'
+import {getCardData, getLineData, getLineMonthData, getLineWeekData} from './api'
+import {BaseColumn} from '/@/views/adManage/utils/commonTabColumn.js'
+import DataCompare from '/@/components/dataCompare/index.vue'
+import DateRangePicker from '/@/components/DateRangePicker/index.vue'
+
+const selectedPortfolios = ref('sp')
+const portfolios = [
+  {
+    value: 'sp/sb/sd',
+    label: 'SP/SB/SD'
+  },
+  {
+    value: 'sp',
+    label: 'SP'
+  },
+  {
+    value:'sb',
+    label:'SB'
+  },
+  {
+    value:'sd',
+    label:'SD'
+  },
+  {
+    value: 'dsp',
+    label: 'DSP',
+    disabled: true
+  }
+]
+const tabActiveName = ref('dataTendency')
+const shopInfo = useShopInfo()
+const publicData = usePublicData()
+const { dateRange } = storeToRefs(publicData)
+const { profile } = storeToRefs(shopInfo)
+const queryParams = ref({
+  dateRange,
+  profileId: profile.value.profileId,
+  campaignType: selectedPortfolios.value
+})
+const { crudBinding, crudRef, crudExpose } = useFs({ createCrudOptions, context: queryParams })
+const router = useRouter()
+const showCompare = ref(false)
+
+
+onMounted(async () => {
+  crudExpose.doRefresh()
+})
+const jumpGroup = (row: any) => {
+  router.push({
+    name: 'CampaignDetail',
+    query: { campaignId: row.campaignId, tagsViewName: row.campaignName },
+  })
+}
+
+watch(queryParams, async () => {
+  crudExpose.doRefresh()
+}, { deep: true })
+</script>
+
+<style lang="scss" scoped>
+.campare-switch {
+  flex: none;
+}
+</style>

+ 10 - 2
src/views/adManage/ad-overview/api.ts → src/views/adManage/ad-overview/total/api.ts

@@ -1,4 +1,4 @@
-import { request } from '/@/utils/service';
+import { request } from '/src/utils/service';
 import { UserPageQuery, AddReq, DelReq, EditReq, InfoReq } from '@fast-crud/fast-crud';
 import XEUtils from 'xe-utils';
 
@@ -57,10 +57,18 @@ export function getLineData(query: UserPageQuery) {
   })
 }
 
-export function getTotalData(query) {
+export function getCardTotalData(query) {
   return request({
     url: apiPrefix + "total",
     method: 'GET',
     params: query
   })
 }
+
+export function getChartTotalData(query) {
+  return request({
+    url: apiPrefix + "trend/daily",
+    method: 'GET',
+    params: query
+  })
+}

+ 0 - 0
src/views/adManage/ad-overview/crud.tsx → src/views/adManage/ad-overview/total/crud.tsx


+ 911 - 0
src/views/adManage/ad-overview/total/index.vue

@@ -0,0 +1,911 @@
+<template>
+  <div>
+    <div class="container-main">
+      <div class="overview-tabs">
+        <DateRangePicker v-model="dateRange"></DateRangePicker>
+        <el-select
+            v-model="selectedPortfolios"
+            placeholder="SP"
+            style="width: 200px"
+            collapse-tags
+            collapse-tags-tooltip
+            :max-collapse-tags="3"
+        >
+          <el-option v-for="info of portfolios" :key="info.value" :label="info.label" :value="info.value" :disabled="info.disabled"></el-option>
+        </el-select>
+      </div>
+      <!-- 卡片内容 不要删除这个类 -->
+      <div class="home-container" style="margin-top: 0" v-loading="cardLoading">
+        <el-row :gutter="15" class="home-card-one mb15">
+          <el-col
+              :xs="24"
+              :sm="12"
+              :md="12"
+              :lg="6"
+              :xl="6"
+              v-for="(v, k) in state.homeOne"
+              :key="k"
+              :class="{ 'home-media home-media-lg': k > 1, 'home-media-sm': k === 1 }"
+          >
+            <div class="home-card-item flex">
+              <div class="flex-margin flex w100" :class="` home-one-animation${k}`">
+                <div class="flex-auto" style="margin-top: -10px">
+                  <div class="mt10">{{ v.cardTitle }}</div>
+                  <div class="font30">{{ v.num1 }}</div>
+                  <div style="display: inline-block; margin-right: 10px; margin-left: 3px;">
+                    {{ v.compareNum }}
+                  </div>
+                  <el-icon :style="{ color: String(v.num2).includes('-') ? '#59b939' : '#e36f53' }" style="display: inline-block; padding-top: 2px">
+                    <template v-if="String(v.num2).includes('-')">
+                      <Bottom/> <!-- num2 是负数时显示向下箭头 -->
+                    </template>
+                    <template v-else>
+                      <Top/> <!-- num2 不是负数时显示向上箭头 -->
+                    </template>
+                  </el-icon>
+                  <span class="ml5 font16" :style="{ color: String(v.num2).includes('-') ? '#59b939' : '#e36f53' }">{{ v.num2 }}%</span>
+                </div>
+                <div class="home-card-item-icon flex" :style="{ background: `var(${v.color2})` }">
+                  <i class="flex-margin font32" :class="v.num4" :style="{ color: `var(${v.color3})` }"></i>
+                </div>
+              </div>
+            </div>
+          </el-col>
+        </el-row>
+      </div>
+      <!-- 折线图 -->
+      <el-card v-loading="loading" style="margin-top: -5px;">
+        <div style="height: 350px;" ref="chartRefOne"></div>
+      </el-card>
+      <el-row :gutter="5" style="margin-top: 10px">
+        <el-col :span="12">
+          <el-card v-loading="loading">
+            <div style="height: 350px;" ref="chartRefAcos"></div>
+          </el-card>
+        </el-col>
+        <el-col :span="12">
+          <el-card v-loading="loading">
+            <div style="height: 350px;" ref="chartRefCPCandCTR"></div>
+          </el-card>
+        </el-col>
+      </el-row>
+      <el-row :gutter="5" style="margin-top: 10px">
+        <el-col :span="12">
+          <el-card v-loading="loading">
+            <div style="height: 350px;" ref="chartRefTotalPurchases"></div>
+          </el-card>
+        </el-col>
+        <el-col :span="12">
+          <el-card v-loading="loading">
+            <div style="height: 350px;" ref="chartRefImpandCli"></div>
+          </el-card>
+        </el-col>
+      </el-row>
+    </div>
+    <!--<el-button @click="changeCardData">按钮</el-button>-->
+  </div>
+
+</template>
+
+<script lang="ts" setup>
+import {nextTick, onBeforeUnmount, onMounted, reactive, Ref, ref, watch} from 'vue'
+import * as echarts from 'echarts'
+import {useShopInfo} from '/@/stores/shopInfo'
+import {usePublicData} from '/@/stores/publicData'
+import {storeToRefs} from 'pinia'
+import {createCrudOptions} from '/@/views/adManage/sp/targets/crud'
+import {useFs} from '@fast-crud/fast-crud'
+import {getCardTotalData, getChartTotalData} from '/src/views/adManage/ad-overview/total/api'
+import DateRangePicker from '/@/components/DateRangePicker/index.vue'
+
+const loading = ref(true)
+const cardLoading = ref(true)
+const publicData = usePublicData()
+const {dateRange} = storeToRefs(publicData)
+const shopInfo = useShopInfo()
+const { profile } = storeToRefs(shopInfo)
+const tabActiveName = ref('Campaigns')
+const queryParams = ref({
+  profileId: profile.value.profile_id,
+  dateRange
+})
+const {crudBinding, crudRef, crudExpose} = useFs({createCrudOptions, context: queryParams})
+
+// 广告类型下拉框
+const selectedPortfolios = ref('SP')
+const portfolios = [
+  {
+    value: 'SP/SB/SD',
+    label: 'SP/SB/SD'
+  },
+  {
+    value: 'SP',
+    label: 'SP'
+  },
+  {
+    value:'SB',
+    label:'SB'
+  },
+  {
+    value:'SD',
+    label:'SD'
+  },
+  {
+    value: 'DSP',
+    label: 'DSP',
+    disabled: true
+  }
+]
+
+// 发送请求获取数据
+async function setTotalData() {
+  try {
+    cardLoading.value = true
+    const resp = await getCardTotalData({ startDate: dateRange.value[0], endDate: dateRange.value[1], profileId: queryParams.value.profileId })
+    state.homeOne[0].num1 = resp.data.Spend
+    state.homeOne[0].compareNum = resp.data.prevSpend
+    state.homeOne[0].num2 = resp.data.gapSpend
+    state.homeOne[1].num1 = resp.data.TotalSales
+    state.homeOne[1].compareNum = resp.data.prevTotalSales
+    state.homeOne[1].num2 = resp.data.gapTotalSales
+    state.homeOne[2].num1 = resp.data.TotalPurchases
+    state.homeOne[2].compareNum = resp.data.prevTotalPurchases
+    state.homeOne[2].num2 = resp.data.gapTotalPurchases
+    state.homeOne[3].num1 = resp.data.ACOS
+    state.homeOne[3].compareNum = resp.data.prevACOS
+    state.homeOne[3].num2 = resp.data.gapACOS
+  } catch (error) {
+    console.log('获取数据失败:', error)
+  } finally {
+    cardLoading.value = false
+  }
+}
+
+let optionSource
+async function setChartTotalData() {
+  try {
+    loading.value = true
+    const resp = await getChartTotalData({ startDate: dateRange.value[0], endDate: dateRange.value[1], profileId: queryParams.value.profileId })
+    optionSource = resp.data
+    await setChartOptions()
+    return resp.data
+  } catch (error) {
+    console.log('获取数据失败:', error)
+  } finally {
+    loading.value = false
+  }
+}
+
+
+onMounted( () => {
+  // crudExpose.doRefresh()
+  setTotalData()
+  setChartTotalData()
+})
+
+watch(queryParams, async() => {
+  try {
+    loading.value = true
+    console.log('queryParams.value', queryParams.value)
+    await initLine()
+    await setTotalData()
+    await setChartTotalData()
+    loading.value = false
+  } catch (error) {
+    console.log(error)
+  }
+
+}, {deep: true})
+
+
+// 卡片相关功能
+const state = reactive({
+  homeOne: [
+    {
+      num1: '',
+      compareNum: '',
+      num2: '',
+      cardTitle: '花费',
+      num4: 'fa fa-meetup',
+      // color1: '#FF6462',
+      color2: '--next-color-primary-lighter',
+      color3: '--el-color-primary',
+    },
+    {
+      num1: '',
+      compareNum: '',
+      num2: '',
+      cardTitle: '销售额',
+      num4: 'iconfont icon-ditu',
+      color2: '--next-color-success-lighter',
+      color3: '--el-color-success',
+    },
+    {
+      num1: '',
+      compareNum: '',
+      num2: '',
+      cardTitle: '订单数',
+      num4: 'iconfont icon-zaosheng',
+      color2: '--next-color-warning-lighter',
+      color3: '--el-color-warning',
+    },
+    {
+      num1: '',
+      compareNum: '',
+      num2: '',
+      cardTitle: 'ACOS',
+      num4: 'fa fa-github-alt',
+      color2: '--next-color-danger-lighter',
+      color3: '--el-color-danger',
+    },
+  ],
+
+  myCharts: [],
+  charts: {
+    theme: '',
+    bgColor: '',
+    color: '#303133',
+  },
+})
+
+
+// 折线图相关功能
+const chartRefOne = ref()
+let chartObjOne
+const option = {
+  title: {text: '花费 & 销售额'},
+  dataset: {
+    source: []
+  },
+  tooltip: {
+    trigger: 'axis',
+    axisPointer: {
+      label: {
+        backgroundColor: '#6a7985'
+      }
+    }
+  },
+  legend: {
+    selected: {},  // 控制显隐
+    data: ['花费', '销售额'],
+    show: true
+  },
+  grid: {
+    top: 50, right: 65, bottom: 30, left: 65,
+  },
+  xAxis: {
+    type: 'category',
+    // boundaryGap: false,
+  },
+  yAxis: [
+    {
+      type: 'value',
+      axisLine: {
+        show: true,
+        lineStyle: {
+          color: '#3a83f7' // 第一个 Y 轴的颜色
+        }
+      }
+    },
+    {
+      type: 'value',
+      splitLine: {
+        show: false
+      },
+      axisLine: {
+        show: true,
+        lineStyle: {
+          color: '#f19a37' // 第二个 Y 轴的颜色
+        }
+      }
+    }
+  ],
+  series: [
+    {
+      name: '花费',
+      yAxisIndex: 0,
+      encode: {
+        x: 'Name',
+        y: 'Spend',
+      },
+      type: 'line',
+      smooth: true,
+      itemStyle: {
+        color: '#3a83f7',
+      },
+      areaStyle: {
+        color: {
+          type: 'linear',
+          x: 0,
+          y: 0,
+          x2: 0,
+          y2: 1,
+          colorStops: [{
+            offset: 0, color: 'rgba(58, 131, 247, 0.5)' // 顶部不透明
+          }, {
+            offset: 0.2, color: 'rgba(58, 131, 247, 0.2)' // 中间更透明
+          }, {
+            offset: 1, color: 'rgba(58, 131, 247, 0)' // 底部完全透明
+          }],
+          global: false // 缺省为 false
+        }
+      }
+    },
+    {
+      name: '销售额',
+      yAxisIndex: 1,
+      encode: {
+        x: 'Name',
+        y: 'TotalSales',
+      },
+      type: 'line',
+      smooth: true,
+      lineStyle: {
+        type: 'dashed'
+      },
+      itemStyle: {
+        color: '#f19a37',
+      },
+      areaStyle: {
+        color: {
+          type: 'linear',
+          x: 0,
+          y: 0,
+          x2: 0,
+          y2: 1,
+          colorStops: [{
+            offset: 0, color: 'rgba(241, 154, 55, 0.5)' // 顶部半透明
+          }, {
+            offset: 0.2, color: 'rgba(241, 154, 55, 0.2)' // 中间更透明
+          }, {
+            offset: 1, color: 'rgba(241, 154, 55, 0)' // 底部完全透明
+          }],
+          global: false // 缺省为 false
+        }
+      }
+    },
+  ]
+}
+
+const chartRefAcos = ref()
+let chartObjAcos
+const option2 = {
+  title: {text: 'Acos'},
+  dataset: {
+    source: []
+  },
+  tooltip: {
+    trigger: 'axis',
+    axisPointer: {
+      label: {
+        backgroundColor: '#6a7985'
+      }
+    }
+  },
+  legend: {
+    selected: {},  // 控制显隐
+    data: ['Acos'],
+    show: true
+  },
+  grid: {
+    top: 50, right: 65, bottom: 30, left: 65,
+  },
+  xAxis: {
+    type: 'category',
+    // boundaryGap: false,
+  },
+  yAxis: [
+    {
+      type: 'value',
+      axisLine: {
+        show: true,
+        lineStyle: {
+          color: '#3a83f7'
+        }
+      }
+    }
+  ],
+  series: [
+    {
+      name: 'Acos',
+      yAxisIndex: 0,
+      encode: {
+        x: 'Name',
+        y: 'Acos',
+      },
+      type: 'line',
+      smooth: true,
+      lineStyle: {
+        type: 'dashed'
+      },
+      legendHoverLink: false,
+      itemStyle: {
+        color: '#3a83f7',
+      },
+      areaStyle: {
+        color: {
+          type: 'linear',
+          x: 0,
+          y: 0,
+          x2: 0,
+          y2: 1,
+          colorStops: [{
+            offset: 0, color: 'rgba(58, 131, 247, 0.5)' // 顶部不透明
+          }, {
+            offset: 0.2, color: 'rgba(58, 131, 247, 0.2)' // 中间更透明
+          }, {
+            offset: 1, color: 'rgba(58, 131, 247, 0)' // 底部完全透明
+          }],
+          global: false // 缺省为 false
+        }
+      }
+    },
+  ]
+}
+
+const chartRefCPCandCTR = ref()
+let chartObjCPCandCTR
+const option3 = {
+  title: {text: '点击成本 & 点击率'},
+  dataset: {
+    source: []
+  },
+  tooltip: {
+    trigger: 'axis',
+    axisPointer: {
+      label: {
+        backgroundColor: '#6a7985'
+      }
+    }
+  },
+  legend: {
+    selected: {},  // 控制显隐
+    data: ['点击成本', '点击率'],
+    show: true
+  },
+  grid: {
+    top: 50, right: 65, bottom: 30, left: 65,
+  },
+  xAxis: {
+    type: 'category',
+    // boundaryGap: false,
+  },
+  yAxis: [
+    {
+      type: 'value',
+      axisLine: {
+        show: true,
+        lineStyle: {
+          color: '#3a83f7' // 第一个 Y 轴的颜色
+        }
+      }
+    },
+    {
+      type: 'value',
+      splitLine: {
+        show: false
+      },
+      axisLine: {
+        show: true,
+        lineStyle: {
+          color: '#f19a37' // 第二个 Y 轴的颜色
+        }
+      }
+    }
+  ],
+  series: [
+    {
+      name: '点击成本',
+      yAxisIndex: 0,
+      encode: {
+        x: 'Name',
+        y: 'CPC',
+      },
+      type: 'line',
+      smooth: true,
+      itemStyle: {
+        color: '#3a83f7',
+      },
+      areaStyle: {
+        color: {
+          type: 'linear',
+          x: 0,
+          y: 0,
+          x2: 0,
+          y2: 1,
+          colorStops: [{
+            offset: 0, color: 'rgba(58, 131, 247, 0.5)' // 顶部不透明
+          }, {
+            offset: 0.2, color: 'rgba(58, 131, 247, 0.2)' // 中间更透明
+          }, {
+            offset: 1, color: 'rgba(58, 131, 247, 0)' // 底部完全透明
+          }],
+          global: false // 缺省为 false
+        }
+      }
+    },
+    {
+      name: '点击率',
+      yAxisIndex: 1,
+      encode: {
+        x: 'Name',
+        y: 'CTR',
+      },
+      type: 'line',
+      smooth: true,
+      itemStyle: {
+        color: '#f19a37',
+      },
+      areaStyle: {
+        color: {
+          type: 'linear',
+          x: 0,
+          y: 0,
+          x2: 0,
+          y2: 1,
+          colorStops: [{
+            offset: 0, color: 'rgba(241, 154, 55, 0.5)' // 顶部半透明
+          }, {
+            offset: 0.2, color: 'rgba(241, 154, 55, 0.2)' // 中间更透明
+          }, {
+            offset: 1, color: 'rgba(241, 154, 55, 0)' // 底部完全透明
+          }],
+          global: false // 缺省为 false
+        }
+      }
+    },
+  ]
+}
+
+const chartRefTotalPurchases = ref()
+let chartObjTotalPurchases
+const option4 = {
+  title: {text: '订单数'},
+  dataset: {
+    source: []
+  },
+  tooltip: {
+    trigger: 'axis',
+    axisPointer: {
+      label: {
+        backgroundColor: '#6a7985'
+      }
+    }
+  },
+  legend: {
+    selected: {},  // 控制显隐
+    data: ['订单数'],
+    show: true
+  },
+  grid: {
+    top: 50, right: 65, bottom: 30, left: 65,
+  },
+  xAxis: {
+    type: 'category',
+    // boundaryGap: false,
+  },
+  yAxis: [
+    {
+      type: 'value',
+      axisLine: {
+        show: true,
+        lineStyle: {
+          color: '#3a83f7'
+        }
+      }
+    }
+  ],
+  series: [
+    {
+      name: '订单数',
+      yAxisIndex: 0,
+      encode: {
+        x: 'Name',
+        y: 'TotalPurchases',
+      },
+      type: 'line',
+      smooth: true,
+      lineStyle: {
+        type: 'dashed'
+      },
+      legendHoverLink: false,
+      itemStyle: {
+        color: '#3a83f7',
+      },
+      areaStyle: {
+        color: {
+          type: 'linear',
+          x: 0,
+          y: 0,
+          x2: 0,
+          y2: 1,
+          colorStops: [{
+            offset: 0, color: 'rgba(58, 131, 247, 0.5)' // 顶部不透明
+          }, {
+            offset: 0.2, color: 'rgba(58, 131, 247, 0.2)' // 中间更透明
+          }, {
+            offset: 1, color: 'rgba(58, 131, 247, 0)' // 底部完全透明
+          }],
+          global: false // 缺省为 false
+        }
+      }
+    },
+  ]
+}
+
+const chartRefImpandCli = ref()
+let chartObjImpandCli
+const option5 = {
+  title: {text: '曝光量 & 点击量'},
+  dataset: {
+    source: []
+  },
+  tooltip: {
+    trigger: 'axis',
+    axisPointer: {
+      label: {
+        backgroundColor: '#6a7985'
+      }
+    }
+  },
+  legend: {
+    selected: {},  // 控制显隐
+    data: ['曝光量', '点击量'],
+    show: true
+  },
+  grid: {
+    top: 50, right: 65, bottom: 30, left: 65,
+  },
+  xAxis: {
+    type: 'category',
+    // boundaryGap: false,
+  },
+  yAxis: [
+    {
+      type: 'value',
+      axisLine: {
+        show: true,
+        lineStyle: {
+          color: '#3a83f7' // 第一个 Y 轴的颜色
+        }
+      }
+    },
+    {
+      type: 'value',
+      splitLine: {
+        show: false
+      },
+      axisLine: {
+        show: true,
+        lineStyle: {
+          color: '#f19a37' // 第二个 Y 轴的颜色
+        }
+      }
+    }
+  ],
+  series: [
+    {
+      name: '曝光量',
+      yAxisIndex: 0,
+      encode: {
+        x: 'Name',
+        y: 'Impression',
+      },
+      type: 'line',
+      smooth: true,
+      itemStyle: {
+        color: '#3a83f7',
+      },
+      areaStyle: {
+        color: {
+          type: 'linear',
+          x: 0,
+          y: 0,
+          x2: 0,
+          y2: 1,
+          colorStops: [{
+            offset: 0, color: 'rgba(58, 131, 247, 0.5)' // 顶部不透明
+          }, {
+            offset: 0.2, color: 'rgba(58, 131, 247, 0.2)' // 中间更透明
+          }, {
+            offset: 1, color: 'rgba(58, 131, 247, 0)' // 底部完全透明
+          }],
+          global: false // 缺省为 false
+        }
+      }
+    },
+    {
+      name: '点击量',
+      yAxisIndex: 1,
+      encode: {
+        x: 'Name',
+        y: 'Click',
+      },
+      type: 'line',
+      smooth: true,
+      itemStyle: {
+        color: '#f19a37',
+      },
+      areaStyle: {
+        color: {
+          type: 'linear',
+          x: 0,
+          y: 0,
+          x2: 0,
+          y2: 1,
+          colorStops: [{
+            offset: 0, color: 'rgba(241, 154, 55, 0.5)' // 顶部半透明
+          }, {
+            offset: 0.2, color: 'rgba(241, 154, 55, 0.2)' // 中间更透明
+          }, {
+            offset: 1, color: 'rgba(241, 154, 55, 0)' // 底部完全透明
+          }],
+          global: false // 缺省为 false
+        }
+      }
+    },
+  ]
+}
+
+function initLine() {
+  if (chartRefOne.value) {
+    chartObjOne = echarts.init(chartRefOne.value)
+    chartObjAcos = echarts.init(chartRefAcos.value)
+    chartObjCPCandCTR = echarts.init(chartRefCPCandCTR.value)
+    chartObjTotalPurchases = echarts.init(chartRefTotalPurchases.value)
+    chartObjImpandCli = echarts.init(chartRefImpandCli.value)
+  }
+}
+
+function setChartOptions() {
+  nextTick(() => {
+    const chartOptions = [
+      {chart: chartObjOne, option: option},
+      {chart: chartObjAcos, option: option2},
+      {chart: chartObjCPCandCTR, option: option3},
+      {chart: chartObjTotalPurchases, option: option4},
+      {chart: chartObjImpandCli, option: option5}
+    ]
+
+    chartOptions.forEach(chartOption => {
+      try {
+        chartOption.option.dataset.source = optionSource
+        chartOption.chart.setOption(chartOption.option)
+      } catch (error) {
+        console.error('设置图表选项失败:', error)
+      }
+    })
+  })
+}
+
+function resizeChart() {
+  if (chartObjOne) {
+    chartObjOne.resize();
+    chartObjAcos.resize()
+    chartObjCPCandCTR.resize()
+    chartObjTotalPurchases.resize()
+    chartObjImpandCli.resize()
+  }
+}
+
+onMounted(async () => {
+  initLine()
+  window.addEventListener('resize', resizeChart)  // 监听窗口大小变化,调整图表大小
+  setTimeout(() => {
+    resizeChart()
+  }, 10)
+})
+
+onBeforeUnmount(() => {
+  window.removeEventListener('resize', resizeChart)
+});
+
+</script>
+
+<style scoped lang="scss">
+$homeNavLengh: 8;
+.home-container {
+  overflow: hidden;
+  margin-top: 10px;
+
+  .home-card-one,
+  .home-card-two,
+  .home-card-three {
+    .home-card-item {
+      width: 100%;
+      height: 130px;
+      border-radius: 4px;
+      transition: all ease 0.3s;
+      padding: 20px;
+      overflow: hidden;
+      background: #ffffff;
+      box-shadow: var(--el-box-shadow-light);
+      color: var(--el-text-color-primary);
+      border: 1px solid var(--next-border-color-light);
+
+      &:hover {
+        box-shadow: 0 2px 12px var(--next-color-dark-hover);
+        transition: all ease 0.3s;
+      }
+
+      &-icon {
+        width: 70px;
+        height: 70px;
+        border-radius: 100%;
+        flex-shrink: 1;
+
+        i {
+          color: var(--el-text-color-placeholder);
+        }
+      }
+
+      &-title {
+        font-size: 15px;
+        font-weight: bold;
+        height: 30px;
+      }
+    }
+  }
+
+  .home-card-one {
+    @for $i from 0 through 3 {
+      .home-one-animation#{$i} {
+        opacity: 0;
+        animation-name: error-num;
+        animation-duration: 0.5s;
+        animation-fill-mode: forwards;
+        animation-delay: calc($i/10) + s;
+      }
+    }
+  }
+
+  .home-card-two,
+  .home-card-three {
+    .home-card-item {
+      height: 400px;
+      width: 100%;
+      overflow: hidden;
+
+      .home-monitor {
+        height: 100%;
+
+        .flex-warp-item {
+          width: 25%;
+          height: 111px;
+          display: flex;
+
+          .flex-warp-item-box {
+            margin: auto;
+            text-align: center;
+            color: var(--el-text-color-primary);
+            display: flex;
+            border-radius: 5px;
+            background: var(--next-bg-color);
+            cursor: pointer;
+            transition: all 0.3s ease;
+
+            &:hover {
+              background: var(--el-color-primary-light-9);
+              transition: all 0.3s ease;
+            }
+          }
+
+          @for $i from 0 through $homeNavLengh {
+            .home-animation#{$i} {
+              opacity: 0;
+              animation-name: error-num;
+              animation-duration: 0.5s;
+              animation-fill-mode: forwards;
+              animation-delay: calc($i/10) + s;
+            }
+          }
+        }
+      }
+    }
+  }
+}
+
+.down {
+  margin-top: 10px;
+}
+</style>

+ 80 - 0
src/views/adManage/ad-overview/weekly/api.ts

@@ -0,0 +1,80 @@
+import {request} from '/@/utils/service'
+import {UserPageQuery, AddReq, DelReq, EditReq, InfoReq} from '@fast-crud/fast-crud'
+import XEUtils from 'xe-utils'
+
+export const apiPrefix = '/api/ad_manage/summary/report/trend/'
+
+export function GetList(query: UserPageQuery) {
+  return request({
+    url: apiPrefix + 'weekly',
+    method: 'get',
+    params: query,
+  })
+}
+
+export function GetObj(id: any) {
+  return request({
+    url: apiPrefix + id + '/',
+    method: 'get',
+  })
+}
+
+export function AddObj(obj: AddReq) {
+  return request({
+    url: apiPrefix,
+    method: 'post',
+    data: obj,
+  })
+}
+
+export function UpdateObj(obj: EditReq) {
+  return request({
+    url: apiPrefix + obj.id + '/',
+    method: 'put',
+    data: obj,
+  })
+}
+
+export function DelObj(id: DelReq) {
+  return request({
+    url: apiPrefix + id + '/',
+    method: 'delete',
+    data: {id},
+  })
+}
+
+export function getCardData(query: UserPageQuery) {
+  return request({
+    url: '/api/ad_manage/summary/report/total',
+    method: 'GET',
+    params: query,
+  })
+}
+
+export function getLineData(query: UserPageQuery) {
+  query['dateRangeType'] = 'D'
+  return request({
+    url: apiPrefix + 'weekly',
+    method: 'GET',
+    params: query
+  })
+}
+
+export function getLineWeekData(query: UserPageQuery) {
+  query['dateRangeType'] = 'W'
+  return request({
+    url: apiPrefix + 'hourly',
+    method: 'GET',
+    params: query
+  })
+}
+
+export function getLineMonthData(query: UserPageQuery) {
+  query['dateRangeType'] = 'M'
+  return request({
+    url: apiPrefix + 'hourly',
+    method: 'GET',
+    params: query
+  })
+}
+

+ 145 - 0
src/views/adManage/ad-overview/weekly/crud.tsx

@@ -0,0 +1,145 @@
+import * as api from './api'
+import {AddReq, CreateCrudOptionsProps, CreateCrudOptionsRet, DelReq, EditReq, UserPageQuery} from '@fast-crud/fast-crud'
+import {inject} from 'vue'
+import {BaseColumn} from '/@/views/adManage/utils/commonTabColumn.js'
+import {parseQueryParams} from '/@/views/adManage/utils/tools.js'
+import XEUtils from 'xe-utils'
+
+export const createCrudOptions = function ({crudExpose, context}: CreateCrudOptionsProps): CreateCrudOptionsRet {
+  const pageRequest = async (query: UserPageQuery) => {
+    const params = parseQueryParams(context.value)
+    XEUtils.assign(query, params)
+    return await api.GetList(query)
+  }
+  const editRequest = async ({form, row}: EditReq) => {
+    form.id = row.id
+    return await api.UpdateObj(form)
+  }
+  const delRequest = async ({row}: DelReq) => {
+    return await api.DelObj(row.id)
+  }
+  const addRequest = async ({form}: AddReq) => {
+    return await api.AddObj(form)
+  }
+
+  //权限判定
+  const hasPermissions = inject('$hasPermissions')
+
+  return {
+    crudOptions: {
+      table: {
+        height: 800,
+      },
+      container: {
+        fixedHeight: false
+      },
+      actionbar: {
+        show: true,
+        buttons: {
+          add: {
+            show: false
+          },
+          // create: {
+          //   text: '新建广告活动',
+          //   type: 'primary',
+          //   show: true,
+          //   click() {
+          //
+          //   }
+          // }
+        }
+      },
+      search: {
+        show: false
+      },
+      toolbar: {
+        buttons: {
+          search: {
+            show: true
+          },
+          compact: {
+            show: false
+          }
+        }
+      },
+      request: {
+        pageRequest,
+        addRequest,
+        editRequest,
+        delRequest,
+      },
+      rowHandle: {
+        fixed: 'right',
+        width: 80,
+        buttons: {
+          view: {
+            show: false,
+          },
+          edit: {
+            iconRight: 'Edit',
+            type: 'text',
+            text: null
+            // show: hasPermissions('dictionary:Update'),
+          },
+          remove: {
+            show: false
+            // iconRight: 'Delete',
+            // type: 'text',
+            // text: null
+            // show: hasPermissions('dictionary:Delete'),
+          },
+        },
+      },
+      columns: {
+        id: {
+          title: 'ID',
+          column: {
+            show: false
+          },
+          form: {
+            show: false
+          }
+        },
+        Name: {
+          title: '日期',
+          column: {
+            width: 100,
+            align: 'left'
+          }
+        },
+        Spend: {
+          title: '花费',
+          column: {
+            width: 100,
+            align: 'left'
+          },
+        },
+        TotalSales: {
+          title: '销售额',
+        },
+        ACOS: {
+          title: 'ACOS',
+        },
+        CPC: {
+          title: '点击成本',
+        },
+        CPA: {
+          title: '订单成本',
+        },
+        Click: {
+          title: '点击量',
+        },
+        CTR: {
+          title: '点击率',
+        },
+        TotalPurchases: {
+          title: '订单数'
+        },
+        TotalUnitOrdered: {
+          title: '销量',
+        },
+        ...BaseColumn
+      }
+    }
+  }
+}

+ 125 - 0
src/views/adManage/ad-overview/weekly/index.vue

@@ -0,0 +1,125 @@
+<template>
+  <div class="overview-tabs">
+    <DateRangePicker v-model="dateRange"></DateRangePicker>
+    <el-select
+        v-model="selectedPortfolios"
+        placeholder="SP"
+        style="width: 200px"
+        collapse-tags
+        collapse-tags-tooltip
+        :max-collapse-tags="3"
+    >
+      <el-option v-for="info of portfolios" :label="info.label" :value="info.value" :disabled="info.disabled"></el-option>
+    </el-select>
+  </div>
+  <fs-page class="fs-page-custom" style="margin-top: -8px">
+    <fs-crud ref="crudRef" v-bind="crudBinding">
+      <template #header-middle>
+        <el-tabs v-model="tabActiveName" class="chart-tabs" type="border-card">
+          <DataTendencyChart
+              v-if="tabActiveName === 'dataTendency'"
+              :query="queryParams"
+              :fetchCard="getCardData"
+              :fetchLine="getLineData"
+              :fetch-line-month="getLineMonthData"
+              :fetch-line-week="getLineWeekData">
+          </DataTendencyChart>
+        </el-tabs>
+      </template>
+      <template #cell_percentTimeInBudget="scope">
+        <el-progress :percentage="scope.row.percentTimeInBudget > 0 ? scope.row.percentTimeInBudget * 100 : 0" />
+      </template>
+      <template #cell_campaignName="scope">
+        <el-link type="primary" :underline="false" @click="jumpGroup(scope.row)">{{ scope.row.campaignName }}</el-link>
+      </template>
+      <template #cell_MissedImpressions="scope">
+        {{ scope.row.MissedImpressionsLower ?? '0' }} ~ {{ scope.row.MissedImpressionsUpper ?? '0' }}
+      </template>
+      <template #cell_MissedClicks="scope"> {{ scope.row.MissedClicksLower ?? '0' }} ~ {{ scope.row.MissedClicksUpper ?? '0' }} </template>
+      <template #cell_MissedSales="scope"> {{ scope.row.MissedSalesLower ?? '0' }} ~ {{ scope.row.MissedSalesUpper ?? '0' }} </template>
+      <template v-for="field of Object.keys(BaseColumn)" #[`cell_${field}`]="scope">
+        <DataCompare
+            :field="field"
+            :value="scope.row[field]"
+            :prev-val="scope.row[`prev${field}`]"
+            :gap-val="scope.row[`gap${field}`]"
+            :date-range="dateRange"
+            :show-compare="showCompare"/>
+      </template>
+    </fs-crud>
+  </fs-page>
+</template>
+
+<script lang="ts" setup>
+import {onMounted, Ref, ref, watch} from 'vue'
+import {FsPage, useFs} from '@fast-crud/fast-crud'
+import {createCrudOptions} from './crud'
+import {useShopInfo} from '/@/stores/shopInfo'
+import {usePublicData} from '/@/stores/publicData'
+import {storeToRefs} from 'pinia'
+import {useRouter} from 'vue-router'
+import DataTendencyChart from '/@/views/adManage/ad-overview/chartComponents/dataTendency.vue'
+import {getCardData, getLineData, getLineMonthData, getLineWeekData} from './api'
+import {BaseColumn} from '/@/views/adManage/utils/commonTabColumn.js'
+import DataCompare from '/@/components/dataCompare/index.vue'
+import DateRangePicker from '/@/components/DateRangePicker/index.vue'
+
+const selectedPortfolios = ref('sp')
+const portfolios = [
+  {
+    value: 'sp/sb/sd',
+    label: 'SP/SB/SD'
+  },
+  {
+    value: 'sp',
+    label: 'SP'
+  },
+  {
+    value:'sb',
+    label:'SB'
+  },
+  {
+    value:'sd',
+    label:'SD'
+  },
+  {
+    value: 'dsp',
+    label: 'DSP',
+    disabled: true
+  }
+]
+const tabActiveName = ref('dataTendency')
+const shopInfo = useShopInfo()
+const publicData = usePublicData()
+const { dateRange } = storeToRefs(publicData)
+const { profile } = storeToRefs(shopInfo)
+const queryParams = ref({
+  dateRange,
+  profileId: profile.value.profileId,
+  campaignType: selectedPortfolios.value
+})
+const { crudBinding, crudRef, crudExpose } = useFs({ createCrudOptions, context: queryParams })
+const router = useRouter()
+const showCompare = ref(false)
+
+
+onMounted(async () => {
+  crudExpose.doRefresh()
+})
+const jumpGroup = (row: any) => {
+  router.push({
+    name: 'CampaignDetail',
+    query: { campaignId: row.campaignId, tagsViewName: row.campaignName },
+  })
+}
+
+watch(queryParams, async () => {
+  crudExpose.doRefresh()
+}, { deep: true })
+</script>
+
+<style lang="scss" scoped>
+.campare-switch {
+  flex: none;
+}
+</style>

+ 18 - 6
src/views/adManage/sp/campaigns/crud.tsx

@@ -105,7 +105,8 @@ export const createCrudOptions = function ({ crudExpose, context }: CreateCrudOp
                     title: '广告活动',
                     column: {
                         width: '200px',
-                        fixed: 'left'
+                        fixed: 'left',
+                        align: 'center'
                     },
                     search: {
                         show: true,
@@ -165,20 +166,30 @@ export const createCrudOptions = function ({ crudExpose, context }: CreateCrudOp
                 startDate: {
                     title: '开始日期',
                     column: {
-                        width: '100px'
+                        width: '100px',
+                        align: 'center'
                     },
                 },
                 endDate: {
                     title: '结束日期',
                     column: {
-                        width: '100px'
+                        width: '100px',
+                        align: 'center'
                     },
                 },
                 budget: {
-                    title: '预算'
+                    title: '预算',
+                    column: {
+                        width: '100px',
+                        align: 'center'
+                    }
                 },
                 portfolioName: {
-                    title: '广告组合'
+                    title: '广告组合',
+                    column: {
+                        width: '100px',
+                        align: 'center'
+                    }
                 },
                 suggestedBudget: {
                     title: '建议竞价',
@@ -201,7 +212,8 @@ export const createCrudOptions = function ({ crudExpose, context }: CreateCrudOp
                         show: false
                     },
                     column: {
-                        width: 180
+                        width: 180,
+                        align: 'center'
                     },
                 },
                 MissedClicks: {

+ 51 - 51
src/views/adManage/utils/enum.ts

@@ -1,22 +1,22 @@
 export const spCampaignMetricsEnum = [
-    {label: '曝光量', value: 'Impression'},
-    {label: '点击量', value: 'Click'},
-    {label: '花费', value: 'Spend'},
-    {label: '订单量', value: 'TotalPurchases'},
-    {label: '销售额', value: 'TotalSales'},
-    {label: '销量', value: 'TotalUnitOrdered'},
-    {label: '点击率', value: 'CTR'},
-    {label: '点击成本', value: 'CPC'},
-    {label: '转化率', value: 'PurchasesRate'},
-    {label: 'ACOS', value: 'ACOS'},
-    {label: 'ROAS', value: 'ROAS'},
-    {label: '订单成本', value: 'CPA'},
-    {label: '推广商品销售额', value: 'TotalSalesSameSKU'},
-    {label: '其它商品销售额', value: 'TotalSalesOtherSKU'},
-    {label: '推广商品订单量', value: 'TotalPurchasesSameSKU'},
-    {label: '其它商品订单量', value: 'TotalPurchasesOtherSKU'},
-    {label: '推广商品销量', value: 'TotalUnitOrderedSameSKU'},
-    {label: '其它商品销量', value: 'TotalUnitOrderedOtherSKU'},
+  {label: '曝光量', value: 'Impression'},
+  {label: '点击量', value: 'Click'},
+  {label: '花费', value: 'Spend'},
+  {label: '订单量', value: 'TotalPurchases'},
+  {label: '销售额', value: 'TotalSales'},
+  {label: '销量', value: 'TotalUnitOrdered'},
+  {label: '点击率', value: 'CTR'},
+  {label: '点击成本', value: 'CPC'},
+  {label: '转化率', value: 'PurchasesRate'},
+  {label: 'ACOS', value: 'ACOS'},
+  {label: 'ROAS', value: 'ROAS'},
+  {label: '订单成本', value: 'CPA'},
+  {label: '推广商品销售额', value: 'TotalSalesSameSKU'},
+  {label: '其它商品销售额', value: 'TotalSalesOtherSKU'},
+  {label: '推广商品订单量', value: 'TotalPurchasesSameSKU'},
+  {label: '其它商品订单量', value: 'TotalPurchasesOtherSKU'},
+  {label: '推广商品销量', value: 'TotalUnitOrderedSameSKU'},
+  {label: '其它商品销量', value: 'TotalUnitOrderedOtherSKU'},
 ]
 
 export const spCampaignPuchasedOtherProductsMetricsEnum = [
@@ -29,31 +29,31 @@ export const spCampaignPuchasedOtherProductsMetricsEnum = [
 ]
 
 export const dynBidStrategyEnum = [
-    {label: '动态竞价-仅降低', value: 'LEGACY_FOR_SALES', color: ''},
-    {label: '动态竞价-提高和降低', value: 'AUTO_FOR_SALES', color: 'success'},
-    {label: '固定竞价', value: 'MANUAL', color: 'warning'}
+  {label: '动态竞价-仅降低', value: 'LEGACY_FOR_SALES', color: ''},
+  {label: '动态竞价-提高和降低', value: 'AUTO_FOR_SALES', color: 'success'},
+  {label: '固定竞价', value: 'MANUAL', color: 'warning'}
 ]
 
 export const spCampaignPlacementEnum = [
-  { label: '搜索结果顶部', value: 'top' },
-  { label: '商品页面', value: 'product_page' },
-  { label: '搜索结果的其余位置', value: 'rest_of_search' },
+  {label: '搜索结果顶部', value: 'top'},
+  {label: '商品页面', value: 'product_page'},
+  {label: '搜索结果的其余位置', value: 'rest_of_search'},
 ]
 
 export const spCampaignStateEnum = [
-  { label: '投放中', value: 'ENABLE' },
-  { label: '已暂停', value: 'PAUSED' },
-  { label: '已归档', value: 'ARCHIVED' },
+  {label: '投放中', value: 'ENABLE'},
+  {label: '已暂停', value: 'PAUSED'},
+  {label: '已归档', value: 'ARCHIVED'},
   // { label: '', value: 'ENABLING' },
   // { label: '', value: 'USER_DELETED' },
-  { label: '其它', value: 'OTHER' },
+  {label: '其它', value: 'OTHER'},
 ]
 
 export const spCampaignServingStatusEnum = [
-  { label: '投放中', value: 'CAMPAIGN_STATUS_ENABLED' },
-  { label: '已暂停', value: 'CAMPAIGN_PAUSED' },
-  { label: '已归档', value: 'CAMPAIGN_ARCHIVED' },
-  { label: '超出预算', value: 'CAMPAIGN_OUT_OF_BUDGET' },
+  {label: '投放中', value: 'CAMPAIGN_STATUS_ENABLED'},
+  {label: '已暂停', value: 'CAMPAIGN_PAUSED'},
+  {label: '已归档', value: 'CAMPAIGN_ARCHIVED'},
+  {label: '超出预算', value: 'CAMPAIGN_OUT_OF_BUDGET'},
   // { label: '', value: '' },
   // { label: '', value: '' },
   // { label: '', value: '' },
@@ -76,25 +76,25 @@ export const metricMap = {
 }
 
 export const barOptionsMap = {
-    'ACOS': 'ACOS',
-    'ROAS': 'ROAS',
-    'Spend': '花费',
-    'TotalSales': '销售额',
-    'TotalPurchases': '订单数',
-    'TotalUnitOrdered': '销量',
-    'CPC': '点击成本',
-    'CPA': '订单成本',
-    'Impression': '曝光量',
-    'Click': '点击量',
-    'CTR': '点击率',
-    'PurchasesRate': '转化率',
-    'TotalSalesSameSKU': '推广商品销售额',
-    'TotalSalesOtherSKU': '其他商品销售额',
-    'TotalPurchasesSameSKU': '推广商品订单数',
-    'TotalPurchasesOtherSKU': '其他商品订单数',
-    'TotalUnitOrderedSameSKU': '推广商品销量',
-    'TotalUnitOrderedOtherSKU': '其他商品销量',
-    'TopOfSearchImpressionShare': '搜索结果顶部展示份额'
+  'ACOS': 'ACOS',
+  'ROAS': 'ROAS',
+  'Spend': '花费',
+  'TotalSales': '销售额',
+  'TotalPurchases': '订单数',
+  'TotalUnitOrdered': '销量',
+  'CPC': '点击成本',
+  'CPA': '订单成本',
+  'Impression': '曝光量',
+  'Click': '点击量',
+  'CTR': '点击率',
+  'PurchasesRate': '转化率',
+  'TotalSalesSameSKU': '推广商品销售额',
+  'TotalSalesOtherSKU': '其他商品销售额',
+  'TotalPurchasesSameSKU': '推广商品订单数',
+  'TotalPurchasesOtherSKU': '其他商品订单数',
+  'TotalUnitOrderedSameSKU': '推广商品销量',
+  'TotalUnitOrderedOtherSKU': '其他商品销量',
+  'TopOfSearchImpressionShare': '搜索结果顶部展示份额'
 }
 
 export const TargetExpressionEnum = [